pax_global_header00006660000000000000000000000064134714100130014504gustar00rootroot0000000000000052 comment=3c0e8049e22a61a8dae081680a4c20012f7bc533 Quaternion-0.0.9.4c/000077500000000000000000000000001347141001300141445ustar00rootroot00000000000000Quaternion-0.0.9.4c/.appveyor.yml000066400000000000000000000032401347141001300166110ustar00rootroot00000000000000image: Visual Studio 2015 environment: matrix: - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 QTDIR: C:\Qt\5.11\msvc2017_64 VCVARS: "C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Auxiliary\\Build\\vcvars64.bat" PLATFORM: VER_SUFFIX: win64 - QTDIR: C:\Qt\5.11\msvc2015 VCVARS: "C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat" PLATFORM: x86 VER_SUFFIX: win32 init: - call "%QTDIR%\bin\qtenv2.bat" - set PATH=C:\Qt\Tools\QtCreator\bin;%PATH% - call "%VCVARS%" %PLATFORM% - cd /D "%APPVEYOR_BUILD_FOLDER%" before_build: - if defined APPVEYOR_REPO_TAG_NAME (set VERSION=%APPVEYOR_REPO_TAG_NAME%) else for /F %%I in ('git rev-list --count HEAD') do set VERSION=git%%I - set DEPLOY_DIR=Quaternion-%VERSION% - git submodule update --init --recursive - cmake -LA -G "NMake Makefiles JOM" -H. -Bbuild -DCMAKE_CXX_FLAGS="/EHsc /W3" -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX="%DEPLOY_DIR%" -DUSE_INTREE_LIBQMC=1 -DDEPLOY_VERBOSITY=%DEPLOY_VERBOSITY% build_script: - cmake --build build after_build: - cmake --build build --target install - cd "%DEPLOY_DIR%" - rmdir /S /Q bearer qmltooling - cd .. - 7z a quaternion-%VERSION%-%VER_SUFFIX%.zip "%DEPLOY_DIR%" # Uncomment this to connect to the AppVeyor build worker #on_finish: # - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) test: off artifacts: - path: quaternion*.zip deploy: - provider: Environment name: GitHub on: APPVEYOR_REPO_TAG: true - provider: Environment name: Bintray version: "%VERSION%" on: branch: master Quaternion-0.0.9.4c/.ci/000077500000000000000000000000001347141001300146155ustar00rootroot00000000000000Quaternion-0.0.9.4c/.ci/bintray-release.json000066400000000000000000000014121347141001300205740ustar00rootroot00000000000000{ "files": [ { "includePattern": "build/quaternion-VERSION_VALUE.dmg", "uploadPattern": "quaternion-VERSION_VALUE.dmg" }, { "includePattern": "\./(Quaternion.*\.AppImage)", "uploadPattern": "quaternion-VERSION_VALUEVERSION_SUFFIX.x86_64.AppImage" } ], "package": { "issue_tracker_url": "https://github.com/QMatrixClient/Quaternion/issues", "licenses": [ "GPL-3.0" ], "name": "BINTRAY_NAME", "subject": "BINTRAY_SUBJECT", "repo": "BINTRAY_REPO", "public_download_numbers": true, "public_stats": true, "vcs_url": "https://github.com/QMatrixClient/Quaternion.git", "website_url": "https://github.com/QMatrixClient/Quaternion" }, "version": { "name": "VERSION_VALUE" }, "publish": true } Quaternion-0.0.9.4c/.gitignore000066400000000000000000000002701347141001300161330ustar00rootroot00000000000000build .kdev4 .directory flatpak/app flatpak/.flatpak-builder flatpak/repo CMakeLists.txt.user* CMakeCache.txt cmake_install.cmake Makefile quaternion_autogen/ build_dir .DS_Store Quaternion-0.0.9.4c/.gitmodules000066400000000000000000000001161347141001300163170ustar00rootroot00000000000000[submodule "lib"] path = lib url = ../../QMatrixClient/libqmatrixclient.git Quaternion-0.0.9.4c/.travis.yml000066400000000000000000000115271347141001300162630ustar00rootroot00000000000000language: cpp dist: xenial git: depth: false before_cache: - brew cleanup cache: directories: - $HOME/Library/Caches/Homebrew matrix: include: - os: linux compiler: gcc addons: apt: sources: - sourceline: 'ppa:beineri/opt-qt571-xenial' packages: - mesa-common-dev # Forgotten dep of qt57base - qt57base - qt57imageformats - qt57svg - qt57multimedia - qt57quickcontrols - qt57quickcontrols2 - qt57tools - qt5keychain-dev - os: linux dist: trusty compiler: clang env: [ 'VERSION_SUFFIX="-compat"' ] addons: apt: sources: - ubuntu-toolchain-r-test - sourceline: 'ppa:beineri/opt-qt571-trusty' packages: - libstdc++-5-dev - qt57base - qt57imageformats - qt57svg - qt57multimedia - qt57quickcontrols - qt57quickcontrols2 - qt57tools - os: linux compiler: clang addons: apt: sources: - sourceline: 'ppa:beineri/opt-qt-5.12.0-xenial' packages: - mesa-common-dev # Forgotten dep of qt512base - qt512base - qt512imageformats - qt512svg - qt512multimedia - qt512quickcontrols - qt512quickcontrols2 - qt512tools - qt512translations - qt5keychain-dev - appstream-util - os: osx env: [ 'PATH=/usr/local/opt/qt/bin:$PATH' ] addons: homebrew: packages: - qt5 - qtkeychain before_script: - if [ -f /opt/qt57/bin/qt57-env.sh ]; then . /opt/qt57/bin/qt57-env.sh; fi - if [ -f /opt/qt512/bin/qt512-env.sh ]; then . /opt/qt512/bin/qt512-env.sh; fi # VERSION is also used by linuxdeployqt - if [ -n "$TRAVIS_TAG" ]; then export VERSION="$TRAVIS_TAG"; else export VERSION="git$(git rev-list --count HEAD)"; fi script: - mkdir build && pushd build # TODO: add building with an external lib - cmake .. -LA -DUSE_INTREE_LIBQMC=1 -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=/usr -DDEPLOY_VERBOSITY=$DEPLOY_VERBOSITY - if [ -f "$(which appstream-util)" ]; then appstream-util validate ../linux/*.appdata.xml; fi - cmake --build . --target all - export DESTDIR=$TRAVIS_BUILD_DIR/install - if [ "$TRAVIS_OS_NAME" = "linux" ]; then cmake --build . --target install; fi - | if [ "$TRAVIS_OS_NAME" = "osx" ]; then cmake --build . --target image mv -v quaternion.dmg "quaternion-$VERSION.dmg" fi - popd # TODO: maybe move AppImage generation to CMakeLists.txt # (pre-wgetting AppImageKit to avoid CMakeList.txt dependency on Internet?) # FIXME: The AppImageKit downloadables should at the very least be verified - | if [ "$TRAVIS_OS_NAME" = "linux" ] && [ "$CC" = "clang" ]; then mkdir AppImageKit && pushd AppImageKit wget -c -nv \ "https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage" \ "https://github.com/darealshinji/AppImageKit-checkrt/releases/download/continuous/exec-x86_64.so" \ "https://github.com/darealshinji/AppImageKit-checkrt/releases/download/continuous/AppRun-patched-x86_64" chmod a+x ./linuxdeployqt-continuous-x86_64.AppImage install -m "u=rw,go=r" -t $DESTDIR/usr/optional/libstdc++ /usr/lib/x86_64-linux-gnu/libstdc++.so.6 install -m "u=rw,go=r" ./exec*.so $DESTDIR/usr/optional/exec.so install -m "u=rwx,go=rx" ./AppRun* $DESTDIR/AppRun unset QTDIR QT_PLUGIN_PATH LD_LIBRARY_PATH ./linuxdeployqt-continuous-x86_64.AppImage \ $DESTDIR/usr/share/applications/*.desktop -appimage -extra-plugins=iconengines,platformthemes/libqgtk3.so -qmldir=$TRAVIS_BUILD_DIR/client/qml -verbose=$DEPLOY_VERBOSITY export APPIMAGE_READY=1 popd fi after_success: - sed -i -e "s/VERSION_VALUE/$VERSION/g" .ci/bintray-release.json - sed -i -e "s/VERSION_SUFFIX/$VERSION_SUFFIX/g" .ci/bintray-release.json - sed -i -e "s/BINTRAY_SUBJECT/$BINTRAY_SUBJECT/g" .ci/bintray-release.json - sed -i -e "s/BINTRAY_REPO/$BINTRAY_REPO/g" .ci/bintray-release.json - sed -i -e "s/BINTRAY_NAME/$BINTRAY_NAME/g" .ci/bintray-release.json deploy: - provider: bintray file: .ci/bintray-release.json user: $BINTRAY_USER key: $BINTRAY_TOKEN skip_cleanup: true on: tags: false - provider: releases api_key: $GITHUB_API_KEY file: build/quaternion-$VERSION.dmg skip_cleanup: true on: tags: true - provider: releases api_key: $GITHUB_API_KEY file_glob: true file: AppImageKit/Quaternion*.AppImage skip_cleanup: true on: condition: -n "$RELEASE_APPIMAGE" && -z "$VERSION_SUFFIX" tags: true notifications: webhooks: urls: - "https://scalar.vector.im/api/neb/services/hooks/dHJhdmlzLWNpLyU0MGtpdHN1bmUlM0FtYXRyaXgub3JnLyUyMVBDelV0eHRPalV5U3hTZWxvZiUzQW1hdHJpeC5vcmc" on_success: change # always|never|change on_failure: always on_start: never Quaternion-0.0.9.4c/BUILDING.md000066400000000000000000000253441347141001300156730ustar00rootroot00000000000000# Building and Packaging Quaternion [![Quaternion-master@Travis](https://img.shields.io/travis/QMatrixClient/Quaternion/master.svg)](https://travis-ci.org/QMatrixClient/Quaternion/branches) [![Quaternion-master@AppVeyor](https://img.shields.io/appveyor/ci/QMatrixClient/quaternion/master.svg?logo=appveyor)](https://ci.appveyor.com/project/QMatrixClient/quaternion) [![license](https://img.shields.io/github/license/QMatrixClient/quaternion.svg)](https://github.com/QMatrixClient/quaternion/blob/master/COPYING) [![Chat](https://img.shields.io/badge/chat-%23quaternion-blue.svg)](https://matrix.to/#/#quaternion:matrix.org) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) ### Getting the source code The source code is hosted at GitHub: https://github.com/QMatrixClient/Quaternion. The best way for one-off building is checking out a tag for a given release from GitHub (master branch is unstable and may or may not work). If you plan to work on Quaternion code, feel free to fork/clone the repo and check out the master branch. Quaternion needs libQMatrixClient to build. Since version 0.0.9.3 there are two options to use the library: 1. Use the library installation known to CMake - either as a (possibly but not necessarily system-wide) package available from your favourite package repository, or as a result of building the library from the source code in another directory. In the latter case CMake internally registers the library upon succesfully building it so you shouldn't even need to pass `CMAKE_PREFIX_PATH`. This option can be forced by passing `-DUSE_INTREE_LIBQMC=0` (or `NO`, or `OFF`) to the configuring invocation of CMake (the one _without_ `--build`); alternatively, the build system will fall back to this option if it doesn't find in-tree sources for the library (under `lib/`) and `USE_INTREE_LIBQMC` is unset completely. 2. As a Git submodule. This option can be forced (disabling the fallback mentioned above) by passing `-DUSE_INTREE_LIBQMC=1` (or `YES`, or `ON`) to CMake. If you haven't cloned Quaternion sources yet, the following will get you all sources in one go: ```bash git clone --recursive https://github.com/QMatrixClient/Quaternion.git ``` If you already have cloned Quaternion, do the following in the top-level directory (NOT in `lib` subdirectory): ```bash git submodule init git submodule update ``` Depending on your case, either option can be preferrable. For instance, Option 2 is more convenient if you're actively hacking on Quaternion and libQMatrixClient at the same time. On the other hand, packagers should make a separate package for libQMatrixClient so should use Option 1. 0.0.9.3 is the only version using Option 1 as default; due to popular demand, Option 2 is used by default (with a fallback to Option 1) from 0.0.9.4 beta. ### Pre-requisites - a Linux, macOS or Windows system (desktop versions tried; mobile Linux/Windows might work too) - For Ubuntu flavours - zesty or later (or a derivative) is good enough out of the box; older ones will need PPAs at least for a newer Qt - in particular, if you have xenial you're advised to add Kubuntu Backports PPA for it - a Git client to check out this repo - CMake (from your package management system or [the official website](https://cmake.org/download/)) - Qt 5 (either Open Source or Commercial), version 5.7 or higher (5.12 is recommended) - a C++ toolchain supported by your version of Qt (see a link for your platform at [the Qt's platform requirements page](http://doc.qt.io/qt-5/gettingstarted.html#platform-requirements)) - GCC 5 (Windows, Linux, macOS), Clang 5 (Linux), Apple Clang 8.1 (macOS) and Visual C++ 2015 (Windows) are the oldest officially supported - any build system that works with CMake should be fine: GNU Make, ninja (any platform), NMake, jom (Windows) are known to work. - optionally, libQMatrixClient 0.5 development files (from your package management system), or prebuilt libQMatrixClient (see "Getting the source code" above). - optionaly, [QtKeychain](https://github.com/frankosterfeld/qtkeychain) to store access tokens in libsecret keyring or similar providers. #### Linux Just install things from the list above using your preferred package manager. If your Qt package base is fine-grained you might want to take a look at `CMakeLists.txt` to figure out which specific libraries Quaternion uses (or blindly run cmake and look at error messages). Note also that you'll need several Qt Quick plugins for Quaternion to work (without them, it will compile and run but won't show the messages timeline). In case of Xenial Xerus following line should get you everything necessary to build and run Quaternion: ```bash sudo apt-get install git cmake qtdeclarative5-dev qtdeclarative5-qtquick2-plugin qtdeclarative5-controls-plugin qml-module-qtquick-controls qml-module-qtquick-controls2 qtmultimedia5-dev ``` To enable libsecret keyring support, install QtKeychain by ```bash sudo apt-get install qt5keychain-dev ``` On Fedora 26, the following command should be enough for building and running: ```bash dnf install git cmake qt5-qtdeclarative-devel qt5-qtquickcontrols qt5-qtquickcontrols2 qt5-qtmultimedia-devel ``` #### macOS `brew install qt5` should get you Qt5. If you want to build with QtKeychain, call `brew install qtkeychain`. You have to point CMake at the Qt5 installation location, with something like: ```bash # if using in-tree libqmatrixclient: cmake .. -DUSE_QQUICKWIDGET=ON -DCMAKE_PREFIX_PATH=$(brew --prefix qt5) # or otherwise... cmake .. -DCMAKE_PREFIX_PATH=/path/to/libqmatrixclient -DUSE_QQUICKWIDGET=ON -DCMAKE_PREFIX_PATH=$(brew --prefix qt5) ``` (read on to find out about `USE_QQUICKWIDGET`). #### Windows 1. Install CMake. The commands in further sections imply that cmake is in your PATH - otherwise you have to prepend them with actual paths. 1. Install Qt5, using their official installer. 1. Optionally, get the QtKeychain sources and make them available to CMake (TODO: elaborate the steps). 1. Make sure CMake knows about Qt and the toolchain - the easiest way is to run `qtenv2.bat` script that can be found in `C:\Qt\\\bin` (assuming you installed Qt to `C:\Qt`). The only thing it does is adding necessary paths to `PATH` - you might not want to run it on system startup but it's very handy to setup environment before building. Setting `CMAKE_PREFIX_PATH`, the same way as for macOS (see above), also helps. ### Build In the root directory of the project sources: ```bash mkdir build_dir cd build_dir cmake .. # Pass -D if needed cmake --build . --target all ``` This will get you an executable in `build_dir` inside your project sources. Noteworthy CMake variables that you can use: - `-DCMAKE_PREFIX_PATH=/path` - add a path to CMake's list of searched paths for preinstalled software (Qt, libQMatrixClient, QtKeychain) - `-DCMAKE_INSTALL_PREFIX=/path` - controls where Quaternion will be installed (see below on installing from sources) - `-DUSE_INTREE_LIBQMC=` - force using/not-using the in-tree copy of libQMatrixClient sources (see "Getting the source code" above). - `-DUSE_QQUICKWIDGET=` - by default it's `ON` with Qt 5.12 and `OFF` with earlier versions. See the next section for details. #### QQuickWidget Quaternion uses a combination of Qt Widgets and QML to render its UI (before any protests and arguments: this _might_ change at some point in time but certainly not in any near future). Internally, embedding QML in a Qt Widget can be done in two ways that provide very similar API but are different underneath: swallowing a `QQuickView` in a window container and using `QQuickWidget`. Historically Quaternion used the former method; however, its implementation in Qt is so ugly that [it's officially deprecated by The Qt Project](https://blog.qt.io/blog/2014/07/02/qt-weekly-16-qquickwidget/). Quaternion suffers from it too: if you see healthy QML chirping in the logs but don't see anything where the timeline should be - you've got issue #355 and the only way for you to fix it is to get Quaternion rebuilt with `QQuickWidget`. Unfortunately, Quaternion's QML is tricky enough to crash `QQuickWidget` on Qt versions before 5.12, so there's no single good configuration. As of now, Quaternion will build with `QQuickWidget` if Qt 5.12 is detected and with the legacy mechanism on lower versions. To override this you can pass `-DUSE_QQUICKWIDGET=ON` to the first (configuring) `cmake` invocation to force usage of `QQuickWidget` or `-DDISABLE_QQUICKWIDGET=ON` to force the legacy code. Further on the choice will be made (and an override handle provided) at runtime, depending on the detected Qt version. ### Install In the root directory of the project sources: `cmake --build build_dir --target install`. If you use GNU Make, `make install` (with `sudo` if needed) will work equally well. ### Package We're still somewhat short of packagers, so please step up and support your favourite system! #### Flatpak If you run Linux and your distribution supports flatpak, you can easily build and install Quaternion as a flatpak package: ```bash git clone https://github.com/QMatrixClient/Quaternion.git --recursive cd Quaternion/flatpak ./setup_runtime.sh ./build.sh flatpak --user install quaternion-nightly com.github.quaternion ``` Whenever you want to update your Quaternion package, do the following from the flatpak directory: ```bash ./build.sh flatpak --user update ``` ## Troubleshooting If `cmake` fails with... ``` CMake Warning at CMakeLists.txt:11 (find_package): By not providing "FindQt5Widgets.cmake" in CMAKE_MODULE_PATH this project has asked CMake to find a package configuration file provided by "Qt5Widgets", but CMake did not find one. ``` ...then you need to set the right `CMAKE_PREFIX_PATH` variable, see above. If `cmake` fails with... ``` CMake Error at CMakeLists.txt:30 (add_subdirectory): The source directory /lib does not contain a CMakeLists.txt file. ``` ...then you don't have libqmatrixclient sources - most likely because you didn't do the `git submodule init && git submodule update` dance. If you have made sure that your toolchain is in order (versions of compilers and Qt are among supported ones, `PATH` is set correctly etc.) but building fails with strange Qt-related errors such as not found symbols or undefined references, double-check that you don't have Qt 4.x packages around ([here is a typical example](https://github.com/QMatrixClient/Quaternion/issues/185)). If you need those packages reinstalling them may help; but if you use Qt4 by default you have to explicitly pass Qt5 location to CMake (see notes about `CMAKE_PREFIX_PATH` in "Building"). See also the Troubleshooting section in [README.md](./README.md) Quaternion-0.0.9.4c/CMakeLists.txt000066400000000000000000000245661347141001300167210ustar00rootroot00000000000000CMAKE_MINIMUM_REQUIRED(VERSION 3.1) set(IDENTIFIER "com.github.quaternion") set(COPYRIGHT "Copyright © 2018-2019 QMatrixClient Project") project(quaternion VERSION 0.0.9.4 LANGUAGES CXX) if(UNIX AND NOT APPLE) set(LINUX 1) endif(UNIX AND NOT APPLE) include(CheckCXXCompilerFlag) if (NOT WIN32) include(GNUInstallDirs) include(cmake/ECMInstallIcons.cmake) endif(NOT WIN32) # Find includes in corresponding build directories set(CMAKE_INCLUDE_CURRENT_DIR ON) # Instruct CMake to run moc automatically when needed. set(CMAKE_AUTOMOC ON) # Set a default build type if none was specified if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) message(STATUS "Setting build type to 'Debug' as none was specified") set(CMAKE_BUILD_TYPE Debug CACHE STRING "Choose the type of build" FORCE) # Set the possible values of build type for cmake-gui set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo") endif() set(CMAKE_CXX_STANDARD 14) # Setup command line parameters for the compiler and linker foreach (FLAG "" all pedantic extra no-unused-parameter) CHECK_CXX_COMPILER_FLAG("-W${FLAG}" WARN_${FLAG}_SUPPORTED) if ( WARN_${FLAG}_SUPPORTED AND NOT CMAKE_CXX_FLAGS MATCHES "(^| )-W?${FLAG}($| )") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -W${FLAG}") endif () endforeach () # Find the libraries find_package(Qt5 5.7 REQUIRED Widgets Network Quick Qml Gui LinguistTools Multimedia DBus) # Qt5_Prefix is only used to show Qt path in message() # Qt5_BinDir is where all the binary tools for Qt are if (QT_QMAKE_EXECUTABLE) get_filename_component(Qt5_BinDir "${QT_QMAKE_EXECUTABLE}" DIRECTORY) get_filename_component(Qt5_Prefix "${Qt5_BinDir}/.." ABSOLUTE) else() get_filename_component(Qt5_BinDir "${Qt5_DIR}/../../../bin" ABSOLUTE) get_filename_component(Qt5_Prefix "${Qt5_DIR}/../../../.." ABSOLUTE) endif() if (USE_QQUICKWIDGET) find_package(Qt5 5.7 REQUIRED QuickWidgets) elseif(NOT DISABLE_QQUICKWIDGET) # QQuickWidget only stopped crashing in Qt 5.12, use it by default if found find_package(Qt5 5.12 QUIET COMPONENTS QuickWidgets) if (Qt5QuickWidgets_FOUND) set(USE_QQUICKWIDGET ON) endif() endif() if(WIN32) enable_language(RC) include(CMakeDetermineRCCompiler) if(MINGW) set(CMAKE_RC_COMPILER_INIT windres) set(CMAKE_RC_COMPILE_OBJECT " -O coff -I${CMAKE_CURRENT_BINARY_DIR} -i -o ") endif() endif() if ((NOT DEFINED USE_INTREE_LIBQMC OR USE_INTREE_LIBQMC) AND EXISTS ${PROJECT_SOURCE_DIR}/lib/lib/util.h) add_subdirectory(lib EXCLUDE_FROM_ALL) include_directories(lib) if (NOT DEFINED USE_INTREE_LIBQMC) set (USE_INTREE_LIBQMC 1) endif () endif () if (NOT USE_INTREE_LIBQMC) find_package(QMatrixClient 0.5.1 REQUIRED) if (NOT QMatrixClient_FOUND) message( WARNING "libQMatrixClient not found; configuration will most likely fail.") message( WARNING "Make sure you have installed libQMatrixClient development files") message( WARNING "as a package or checked out the library sources in lib/.") message( WARNING "See also BUILDING.md") endif () endif () find_package(Qt5Keychain QUIET) if (Qt5Keychain_FOUND) set(USE_KEYCHAIN ON) endif() message( STATUS ) message( STATUS "=============================================================================" ) message( STATUS " Quaternion Build Information" ) message( STATUS "=============================================================================" ) if (CMAKE_BUILD_TYPE) message( STATUS "Build type: ${CMAKE_BUILD_TYPE}") endif(CMAKE_BUILD_TYPE) message( STATUS "Quaternion install prefix: ${CMAKE_INSTALL_PREFIX}" ) # Get Git info if possible find_package(Git) if(GIT_FOUND) execute_process(COMMAND "${GIT_EXECUTABLE}" rev-parse -q HEAD WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} OUTPUT_VARIABLE GIT_SHA1 OUTPUT_STRIP_TRAILING_WHITESPACE) message( STATUS "Git SHA1: ${GIT_SHA1}") endif() message( STATUS "Using compiler: ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}" ) message( STATUS "Using Qt ${Qt5_VERSION} at ${Qt5_Prefix}" ) if (USE_INTREE_LIBQMC) message( STATUS "Using in-tree libQMatrixClient") if (GIT_FOUND) execute_process(COMMAND "${GIT_EXECUTABLE}" rev-parse -q HEAD WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/lib OUTPUT_VARIABLE LIB_GIT_SHA1 OUTPUT_STRIP_TRAILING_WHITESPACE) message( STATUS " Library git SHA1: ${LIB_GIT_SHA1}") endif (GIT_FOUND) else () message( STATUS "Using libQMatrixClient ${QMatrixClient_VERSION} at ${QMatrixClient_DIR}") endif () if (USE_QQUICKWIDGET) message( STATUS "Using QQuickWidget to render QML") endif(USE_QQUICKWIDGET) if (USE_KEYCHAIN) message( STATUS "Using Qt5Keychain ${Qt5Keychain_VERSION} at ${Qt5Keychain_DIR}") endif () message( STATUS "=============================================================================" ) message( STATUS ) # Set up source files set(quaternion_SRCS client/quaternionroom.cpp client/imageprovider.cpp client/activitydetector.cpp client/dialog.cpp client/logindialog.cpp client/networkconfigdialog.cpp client/roomdialogs.cpp client/mainwindow.cpp client/roomlistdock.cpp client/userlistdock.cpp client/kchatedit.cpp client/chatedit.cpp client/chatroomwidget.cpp client/systemtrayicon.cpp client/models/messageeventmodel.cpp client/models/userlistmodel.cpp client/models/roomlistmodel.cpp client/models/abstractroomordering.cpp client/models/orderbytag.cpp client/main.cpp ) set(quaternion_QRC client/resources.qrc ) # quaternion_en.ts is updated explicitly by building trbase target, # while all other translation files are created and updated externally at # Lokalise.co set(quaternion_en_TS client/translations/quaternion_en.ts) QT5_CREATE_TRANSLATION(client/translations ${quaternion_SRCS} ${quaternion_QRC} ${quaternion_en_TS}) add_custom_target(trbase SOURCES ${quaternion_en_TS} VERBATIM) set(quaternion_TS client/translations/quaternion_en_GB.ts client/translations/quaternion_de_DE.ts client/translations/quaternion_pl.ts client/translations/quaternion_ru.ts ) QT5_ADD_TRANSLATION(quaternion_QM ${quaternion_TS}) QT5_ADD_RESOURCES(quaternion_QRC_SRC ${quaternion_QRC}) set_property(SOURCE qrc_resources.cpp PROPERTY SKIP_AUTOMOC ON) if(WIN32) set(quaternion_WINRC quaternion_win32.rc) set_property(SOURCE quaternion_win32.rc APPEND PROPERTY OBJECT_DEPENDS ${PROJECT_SOURCE_DIR}/icons/quaternion.ico ) endif() if(APPLE) set(MACOSX_BUNDLE_GUI_IDENTIFIER ${IDENTIFIER}) set(MACOSX_BUNDLE_BUNDLE_NAME ${PROJECT_NAME}) set(MACOSX_BUNDLE_COPYRIGHT ${COPYRIGHT}) set(MACOSX_BUNDLE_SHORT_VERSION_STRING ${quaternion_VERSION}) set(MACOSX_BUNDLE_BUNDLE_VERSION ${quaternion_VERSION}) set(ICON_NAME "quaternion.icns") set(${PROJECT_NAME}_MAC_ICON "${PROJECT_SOURCE_DIR}/icons/${ICON_NAME}") set(MACOSX_BUNDLE_ICON_FILE ${ICON_NAME}) set_property(SOURCE "${${PROJECT_NAME}_MAC_ICON}" PROPERTY MACOSX_PACKAGE_LOCATION Resources) endif(APPLE) # Windows, this is a GUI executable; OSX, make a bundle add_executable(${PROJECT_NAME} WIN32 MACOSX_BUNDLE ${quaternion_SRCS} ${quaternion_QRC_SRC} ${quaternion_QM} ${quaternion_WINRC} ${${PROJECT_NAME}_MAC_ICON}) target_link_libraries(${PROJECT_NAME} QMatrixClient Qt5::Widgets Qt5::Quick Qt5::Qml Qt5::Gui Qt5::Network) target_compile_definitions(${PROJECT_NAME} PRIVATE GIT_SHA1="${GIT_SHA1}" LIB_GIT_SHA1="${LIB_GIT_SHA1}") if (USE_QQUICKWIDGET) target_compile_definitions(${PROJECT_NAME} PRIVATE USE_QQUICKWIDGET) target_link_libraries(${PROJECT_NAME} Qt5::QuickWidgets) endif() if(USE_KEYCHAIN) target_compile_definitions(${PROJECT_NAME} PRIVATE USE_KEYCHAIN) target_link_libraries(${PROJECT_NAME} ${QTKEYCHAIN_LIBRARIES}) include_directories(${QTKEYCHAIN_INCLUDE_DIR}) endif() # macOS specific config for bundling set_property(TARGET ${PROJECT_NAME} PROPERTY MACOSX_BUNDLE_INFO_PLIST "${PROJECT_SOURCE_DIR}/cmake/MacOSXBundleInfo.plist.in") # Installation if (NOT CMAKE_INSTALL_BINDIR) set(CMAKE_INSTALL_BINDIR ".") endif() install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} BUNDLE DESTINATION ${CMAKE_INSTALL_BINDIR}) if(LINUX) install(FILES linux/${IDENTIFIER}.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications ) install(FILES linux/${IDENTIFIER}.appdata.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/metainfo ) install(FILES ${quaternion_QM} DESTINATION ${CMAKE_INSTALL_DATADIR}/QMatrixClient/quaternion/translations ) file(GLOB quaternion_icons icons/quaternion/*-apps-quaternion.png) ecm_install_icons(ICONS ${quaternion_icons} icons/quaternion/sc-apps-quaternion.svgz DESTINATION ${CMAKE_INSTALL_DATADIR}/icons ) endif(LINUX) set(QML_DIR ${PROJECT_SOURCE_DIR}/client/qml) if (NOT DEPLOY_VERBOSITY) set(DEPLOY_VERBOSITY 1) # The default for *deployqt tools, out of 0..3 endif() if(WIN32) install(CODE " message(STATUS \"Running windeployqt at \${CMAKE_INSTALL_PREFIX}\${CMAKE_INSTALL_BINDIR}\") execute_process( COMMAND \"${Qt5_BinDir}/windeployqt\" --verbose ${DEPLOY_VERBOSITY} --no-multimediaquick --no-declarative --no-test --no-winextras --qmldir \"${QML_DIR}\" \${CMAKE_INSTALL_PREFIX}\${CMAKE_INSTALL_BINDIR} RESULT_VARIABLE WDQ_RETVAL ) if (WDQ_RETVAL) message( \"windeployqt returned \${WDQ_RETVAL} - check messages above\") else() message( STATUS \"Quaternion and its dependencies have been deployed to \${CMAKE_INSTALL_PREFIX}.\") endif() ") install(FILES ${quaternion_QM} DESTINATION ${CMAKE_INSTALL_BINDIR}/translations ) endif(WIN32) # Packaging if(APPLE) set(MACDEPLOYQT_ARGS ${PROJECT_NAME}.app -dmg -qmldir="${QML_DIR}" -verbose=${DEPLOY_VERBOSITY}) add_custom_target(image COMMAND "${Qt5_BinDir}/macdeployqt" ${MACDEPLOYQT_ARGS} DEPENDS ${PROJECT_NAME} WORKING_DIRECTORY ${PROJECT_BINARY_DIR} COMMENT "Running ${MACDEPLOYQT} with args: ${MACDEPLOYQT_ARGS}" ) endif(APPLE) Quaternion-0.0.9.4c/COPYING000066400000000000000000001045131347141001300152030ustar00rootroot00000000000000 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. Copyright (C) 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: Copyright (C) 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 . Quaternion-0.0.9.4c/ISSUE_TEMPLATE.md000066400000000000000000000027621347141001300166600ustar00rootroot00000000000000 ### Description Describe here the problem that you are experiencing, or the feature you are requesting. ### Steps to reproduce - For bugs, list the steps - that reproduce the bug - using hyphens as bullet points Describe how what happens differs from what you expected. Quaternion dumps logs to the standard output. If you can find the logs and identify any log snippets relevant to your issue, please include those here (please be careful to remove any personal or private data): ### Version information - **Quaternion version**: - **Qt version**: - **Install method**: - **Platform**: Quaternion-0.0.9.4c/Quaternion.project000066400000000000000000000204551347141001300176670ustar00rootroot00000000000000 . cmake $(ProjectPath) -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=1 mingw32-make clean && mingw32-make -j4 mingw32-make clean mingw32-make -j4 None $(IntermediateDirectory) cmake $(ProjectPath) -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_EXPORT_COMPILE_COMMANDS=1 mingw32-make clean && mingw32-make -j4 mingw32-make clean mingw32-make -j4 None $(IntermediateDirectory) Quaternion-0.0.9.4c/README.md000066400000000000000000000401211347141001300154210ustar00rootroot00000000000000# Quaternion Made for Matrix [![license](https://img.shields.io/github/license/QMatrixClient/quaternion.svg)](https://github.com/QMatrixClient/quaternion/blob/master/COPYING) ![status](https://img.shields.io/badge/status-beta-yellow.svg) [![release](https://img.shields.io/github/release/QMatrixClient/quaternion/all.svg)](https://github.com/QMatrixClient/Quaternion/releases/latest) [![](https://img.shields.io/matrix/qmatrixclient%3Amatrix.org.svg)](https://matrix.to/#/!PCzUtxtOjUySxSelof:matrix.org) [![](https://img.shields.io/cii/percentage/1663.svg?label=CII%20best%20practices)](https://bestpractices.coreinfrastructure.org/projects/1663/badge) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) Quaternion is a cross-platform desktop IM client for the [Matrix](https://matrix.org) protocol. This file contains general information about application usage and settings. See [BUILDING.md](./BUILDING.md) for building instructions. ## Contacts Most of talking around Quaternion happens in our Matrix room: [#quaternion:matrix.org](https://matrix.to/#/#quaternion:matrix.org). You can also file issues at [the project's issue tracker](https://github.com/QMatrixClient/Quaternion/issues). If you have what looks like a security issue, please contact Kitsune Ral ([@kitsune:matrix.org](https://matrix.to/#/@kitsune:matrix.org) or [Kitsune-Ral@users.sf.net](mailto:Kitsune-Ral@users.sf.net)). ## Download and install The latest release (with links to cross-platform source code archives, as well as archived binaries for Windows and macOS) can be found on the [GitHub Releases page](https://github.com/QMatrixClient/Quaternion/releases/latest). For those who want the very latest version (beware, you may find it not working at times), automatic builds from continuous integration pipelines can be found as follows (all these builds come with a fairly recent Qt bundled): - Windows: Surf to the [AppVeyor CI page for Quaternion](https://ci.appveyor.com/project/QMatrixClient/quaternion), then go to "Jobs", click on a job for your architecture and find the archive in "Artifacts". - Linux and macOS: go to the [Quaternion CI repo at bintray](https://bintray.com/qmatrixclient/ci/Quaternion), pick a version under "Versions", then click on "Files" and find a .dmg for macOS and an AppImage for Linux. If you want to build Quaternion from sources, see [BUILDING.md](./BUILDING.md). Packagers are still scarce, so please step up and support your favourite system! ### Requirements Quaternion needs Qt version 5.7 or higher. On Linux, this is compatible with Debian Stretch and Ubuntu Xenial Xerus with Kubuntu Backports overlay. On Windows, all needed Qt libraries are included in the archive - you only need to separately install OpenSSL (see below). #### Windows Since we can't rely on package management on Windows, Qt libraries and a C++ runtime are packaged/installed together with Quaternion. However, OpenSSL libraries (ssleay32.dll and libeay32.dll) are not installed automatically because of export restrictions. Unless you already have them around (e.g., they are a part of any Qt development installation, see `Tools//opt/bin`), your best bet is to: - find the pre-compiled libraries yourself (searching Internet for "OpenSSL Windows" should work); - download them - either install them system-wide (which probably makes sense as soon as you keep them up-to-date; make sure the location is in your PATH) or put them next to quaternion.exe. #### Linux Unofficial package for Arch Linux: https://aur.archlinux.org/packages/quaternion/ Flatpaks for Quaternion are available from Flathub. To install, use `flatpak install https://flathub.org/repo/appstream/com.github.quaternion.flatpakref`. This is still experimental - please file issues at https://github.com/flathub/com.github.quaternion if you believe there's a problem with Flatpakaging. #### macOS You can download the latest release from [GitHub](https://github.com/QMatrixClient/Quaternion/releases/latest). Alternatively, you can install Quaternion from [Homebrew Cask](https://brew.sh) ``` brew cask install quaternion ``` ## Running Just start the executable in your most preferred way - either from the build directory or from the installed location. If you're interested in tweaking configuration beyond what's available in the UI, read the "Configuration" section further below. ## Translation Quaternion uses [Lokalise.co](https://lokalise.co) for the translation effort. It's easy to participate: [join the project at Lokalise.co](https://lokalise.co/public/730769035bbc328c31e863.62506391/), ask to add your language (either in #qmatrixclient:matrix.org or in the Lokalise project chat) and start translating! Many languages are still longing for contributors. ## Configuration The only non-trivial command-line option available so far is `--locale` - it allows you to override the locale Quaternion uses (an equivalent of setting `LC_ALL` variable on UNIX-based systems). As of version 0.0.9.3, Quaternion has no translations to other languages, so you will hardly notice the difference in messages; number and date formats will be following the setting though. Version 0.0.9.4 gains German, Polish, and Russian translations. Quaternion stores its configuration in a way standard for Qt applications. It will read and write the configuration in the user-specific location (creating it if non-existent) and will only read the system-wide location with reasonable defaults if the configuration is nowhere to be found. - Linux: - system-wide: `$XDG_CONFIG_DIR/QMatrixClient/quaternion` or `/etc/xdg/QMatrixClient/quaternion` - user-specific: `$HOME/.config/QMatrixClient/quaternion.conf` - macOS: - system-wide: `/Library/Preferences/com.QMatrixClient.quaternion.plist` - user-specific: `$HOME/Library/Preferences/com.QMatrixClient.quaternion.plist` - Windows: registry keys under - system-wide: `HKEY_LOCAL_MACHINE\Software\QMatrixClient\quaternion` - user-specific: `HKEY_CURRENT_USER\Software\QMatrixClient\quaternion` Some settings exposed in UI (Settings and View menus): - `UI/notifications` - a general setting whether Quaternion should distract the user with notifications and how. - `none` suppresses notifications entirely (rooms and messages are still hightlighted but the tray icon is muted); - `non-intrusive` allows the tray icon show notification popups; - `intrusive` (default) adds to that activation of Quaternion window (i.e. the application blinking in the task bar, or getting raised, or otherwise demands attention in an environment-specific way). - `UI/timeline_layout` - this allows to choose the timeline layout. If this is set to "xchat", Quaternion will show the author to the left of each message, in a xchat/hexchat style (this was also the only available layout on Quaternion before 0.0.9.2). Any other value will select the "default" layout, with author labels above blocks of messages. - `UI/use_shuttle_dial` - Quaternion will use a shuttle dial instead of a classic scrollbar for the timeline's vertical scrolling control. Shuttle dials usually control change velocity instead of value; in this case, moving the dial away from the neutral position increases the speed of scrolling. This is more convenient if you need to quickly move around without knowing position relative to the edges, as is the case of a Matrix timeline; however, the control is unusual and not all people like it. Shuttle scrollbar is enabled by default; set this to false (or 0) to use the classic scrollbar. - `UI/autoload_images` - whether full-size images should be loaded immediately once the message is shown on the screen. The default is to automatically load full-size images; set this to false (or 0) to disable that and only load a thumbnail initially. - `UI/show_noop_events` - set this to 1 to show state events that do not alter the state (you'll see "(repeated)" next to most of those). - `UI/RoomsDock/tags_order` - allows to alter the order of tags in the room list. The default value for this key will be set by Quaternion if it doesn't it so that you could edit it further. This is a list of tags/namespaces; `.*` at the end of the string means a namespace, other strings are treated as fully specified tags. E.g., the default order looks like this: `m.favourite,u.*,org.qmatrixclient.direct,org.qmatrixclient.none,m.lowpriority`. If a tag is not mentioned and does not fit any namespace, it will be put at the end in lexicographic order. Tags within the same namespace are also ordered lexicographically. Settings not exposed in UI: - `UI/condense_chat` - set this to 1 to make the timeline rendered tighter, eliminating vertical gaps between messages as much as possible. - `UI/show_author_avatars` - set this to 1 (or true) to show author avatars in the timeline (default if the timeline layout is set to default); setting this to 0 (or false) will suppress avatars (default for the XChat timeline layout). - `UI/suppress_local_echo` - set this to 1 (or true) to suppress showing local echo (events sent from the current application but not yet confirmed by the server). By default local echo is shown. - `UI/animations_duration_ms` - defines the base duration (in milliseconds) of animation effects in the timline. The default is 400; set it to 0 to disable animation. - `UI/highlight_color` - set this to the color name you prefer for highlighted rooms/messages; HTML color names and SVG `#codes` are supported; by default it's `orange`. - `UI/highlight_mode` - set this to `text` if you prefer to use the highlight color as the text color (the only option available until 0.0.9.3); the new default is to use the background for highlighting. - `UI/use_human_friendly_dates` - set this to false (or 0) if you do NOT want usage of human-friendly dates ("Today", "Monday" instead of the standard day-month-year triad) in the UI; the default is true. - `UI/Fonts/render_type` - select how to render fonts in Quaternion timeline; possible values are "NativeRendering" (default) and "QtRendering". - `UI/quote_style` - the quote template. The `\\1` means the quoted string. By default it's `> \\1\n`. - `UI/quote_regex` - set to `^([\\s\\S]*)` to add `UI/quote_style` only at the beginning and end of the quote. By default it's `(.+)(?:\n|$)`. Since version 0.0.9.4, AppImage binaries for Linux and .dmg files for macOS are compiled with Qt Keychain support. It means that Quaternion will try to store your access token(s) in the secure storage configured for your platform. If the storage or Qt Keychain are not available (Qt Keychain is off by default on Windows - you have to rebuild Quaternion to enable it), Quaternion will try to store your access token(s) in a dedicated file with restricted access rights so that only the owner can access them. Every access token is stored in a separate file matching your user id in the following directory: - Linux: `$HOME/.local/share/QMatrixClient/quaternion` - macOS: `$HOME/Library/Application Support/QMatrixClient/quaternion` - Windows: `%LOCALAPPDATA%/QMatrixClient/quaternion` Unfortunately, Quaternion cannot enforce proper access rights on Windows yet; you'll see a warning about it and will be able to either refuse saving your access token in that case or agree and setup file permissions outside Quaternion. The work is ongoing to enable Qt Keychain on Windows by default. Quaternion caches the rooms state and user/room avatars on the file system in a conventional location for your platform, as follows: - Linux: `$HOME/.cache/QMatrixClient/quaternion` - macOS: `$HOME/Library/Cache/QMatrixClient/quaternion` - Windows: `%LOCALAPPDATA%/QMatrixClient/quaternion/cache` Cache files are safe to delete at any time but Quaternion only looks for them when starting up and overwrites them regularly while running; so it only makes sense to delete cache files when Quaternion is not running. If Quaternion doesn't find or cannot fully load cache files at startup it downloads the whole state from Matrix servers. It tries to optimise this process by lazy-loading if the server supports it; in an unlucky case when the server cannot do lazy-loading, initial sync can take much time (up to a minute and even more, depending on the number of rooms and the number of users in them). Deleting cache files may help with problems such as missing avatars, rooms stuck in a wrong state etc. ## Troubleshooting libqmatrixclient has its own section on troubleshooting - make sure to look into its README.md too. #### Continuously reconnecting though the network is fine If Quaternion starts displaying the message that it couldn't connect to the server and retries more than a couple of times without success, while you're sure you have the network connection - double-check that you don't have Qt bearer management libraries around, as they cause issues with some WiFi networks. To do that, try to find "bearer" directory where your Qt is installed (on Windows it's next to Quaternion executable; on Linux it's a part of Qt installation, usually in `/usr/lib/qt5/plugins`). Then delete or rename it (on Windows) or delete the package that this directory is in (on Linux). #### No messages in the timeline If Quaternion runs but you can't see any messages in the chat (though you can type them in) - you have either of two problems with Qt Quick (or if you are extremely unlucky, both): - You might not have Qt Quick libraries and/or plugins installed. On Linux, double check "Pre-requisites" above and also see the stdout/stderr logs, they are quite clear in such cases. On Windows and Mac, just open an issue (see "Contacts" in the beginning of this README), because most likely not all necessary Qt parts were installed along with Quaternion (which is a bug). - If the logs confirm that QML is up and running but there's still nothing for the timeline, you might have hit an issue with QML view stacking order, such as #355/#356. If you use Qt 5.12, please file a bug (it should not happen with Qt 5.12 at all). With older Qt, you will have to build Quaternion from sources, passing `-DUSE_QQUICKWIDGET=ON` to CMake. Note that it's prone to crashing on some platforms so it's best to still find a way to use Quaternion with Qt 5.12. #### SSL problems Especially on Windows, if Quaternion starts up but upon an attempt to connect returns a message like "Failed to make SSL context" - you haven't made sure that SSL libraries are reachable by the Quaternion binary. Re-read the chapter "Requirements", section "Windows" in the beginning of this file and do as it advises. #### DLL hell If you have troubles with dynamic libraries on Windows, [the Dependencies Walker tool aka depends.exe](http://www.dependencywalker.com/) helps a lot in navigating the DLL hell - especially when you have a mixed 32/64-bit environment or have different versions of the same library scattered around. OpenSSL, in particular, is notoriously often dragged along by all kinds of software; and you may have other copies of Qt around which you didn't even know about - e.g., with CMake GUI. #### Logging If you run Quaternion from a console on Windows and want to see log messages, set `QT_LOGGING_TO_CONSOLE=1` so that the output is redirected to the console. When chasing bugs and investigating crashes, it helps to increase the debug level. Thanks to [@eang:matrix.org](https://matrix.to/#/@eang:matrix.org]), libqmatrixclient uses Qt logging categories - the "Troubleshooting" section of the library's `README.md` elaborates on how to setup logging. Note that Quaternion itself doesn't use Qt logging categories yet, only the library does. You may also want to set `QT_MESSAGE_PATTERN` to make logs slightly more informative (see https://doc.qt.io/qt-5/qtglobal.html#qSetMessagePattern for the format description). My (@kitsune's) `QT_MESSAGE_PATTERN` looks as follows: `%{time h:mm:ss.zzz}|%{category}|%{if-debug}D%{endif}%{if-info}I%{endif}%{if-warning}W%{endif}%{if-critical}C%{endif}%{if-fatal}F%{endif}|%{message}` (the scary `%{if}`s are just encoding the logging level into its initial letter). ## Screenshot ![Screenshot](quaternion.png) Quaternion-0.0.9.4c/client/000077500000000000000000000000001347141001300154225ustar00rootroot00000000000000Quaternion-0.0.9.4c/client/activitydetector.cpp000066400000000000000000000053201347141001300215140ustar00rootroot00000000000000/************************************************************************** * * * Copyright (C) 2016 Malte Brandy * * * * 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 . * * * **************************************************************************/ #include "activitydetector.h" #include #include "mainwindow.h" #include "chatroomwidget.h" ActivityDetector::ActivityDetector(QApplication& a, MainWindow& w) : m_app(a) , m_mainWindow(w) , m_enabled(false) { const auto chatWidget = w.getChatRoomWidget(); connect( chatWidget, &ChatRoomWidget::readMarkerMoved, this, &ActivityDetector::updateEnabled ); connect( chatWidget, &ChatRoomWidget::readMarkerCandidateMoved, this, &ActivityDetector::updateEnabled ); connect( this, &ActivityDetector::triggered, chatWidget, &ChatRoomWidget::markShownAsRead ); } void ActivityDetector::updateEnabled() { setEnabled(m_mainWindow.getChatRoomWidget()->pendingMarkRead()); } void ActivityDetector::setEnabled(bool enabled) { if (enabled == m_enabled) return; m_enabled = enabled; m_mainWindow.setMouseTracking(enabled); if (enabled) m_app.installEventFilter(this); else m_app.removeEventFilter(this); qDebug() << "Activity Detector enabled:" << enabled; } bool ActivityDetector::eventFilter(QObject* obj, QEvent* ev) { switch (ev->type()) { case QEvent::KeyPress: case QEvent::FocusIn: case QEvent::MouseMove: case QEvent::MouseButtonPress: emit triggered(); break; default:; } return QObject::eventFilter(obj, ev); } Quaternion-0.0.9.4c/client/activitydetector.h000066400000000000000000000035341347141001300211660ustar00rootroot00000000000000/************************************************************************** * * * Copyright (C) 2016 Malte Brandy * * * * 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 . * * * **************************************************************************/ #pragma once #include class MainWindow; class ActivityDetector : public QObject { Q_OBJECT public: ActivityDetector(QApplication& a, MainWindow& w); public slots: void updateEnabled(); void setEnabled(bool enabled); signals: void triggered(); protected: bool eventFilter(QObject* obj, QEvent* ev) override; private: QApplication& m_app; MainWindow& m_mainWindow; bool m_enabled; }; Quaternion-0.0.9.4c/client/chatedit.cpp000066400000000000000000000132721347141001300177200ustar00rootroot00000000000000/************************************************************************** * * * Copyright (C) 2017 Kitsune Ral * * * 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 . * * * **************************************************************************/ #include "chatedit.h" #include #include "chatroomwidget.h" ChatEdit::ChatEdit(ChatRoomWidget* c) : KChatEdit(c), chatRoomWidget(c), matchesListPosition(0) { } void ChatEdit::keyPressEvent(QKeyEvent* event) { pickingMentions = false; if (event->key() == Qt::Key_Tab) { triggerCompletion(); return; } cancelCompletion(); KChatEdit::keyPressEvent(event); } QString ChatEdit::sanitizeMention(QString mentionText) { if (mentionText.startsWith('/')) mentionText.push_front('/'); return mentionText; } void ChatEdit::appendTextAtCursor(const QString& text, bool select) { completionCursor.insertText(text); completionCursor.movePosition(QTextCursor::PreviousCharacter, select ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor, text.size()); } void ChatEdit::startNewCompletion() { completionCursor = textCursor(); completionCursor.clearSelection(); while ( completionCursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor) ) { QChar firstChar = completionCursor.selectedText().at(0); if (!firstChar.isLetterOrNumber() && firstChar != '@') { completionCursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor); break; } } completionMatches = chatRoomWidget->findCompletionMatches(completionCursor.selectedText()); if ( !completionMatches.isEmpty() ) { matchesListPosition = 0; auto lookBehindCursor = completionCursor; if ( lookBehindCursor.atStart() ) { appendTextAtCursor(QStringLiteral(": "), false); return; } for (auto stringBefore: {QLatin1String(":"), QLatin1String(": ")}) { lookBehindCursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor); if ( lookBehindCursor.selectedText().startsWith(stringBefore) ) { lookBehindCursor.insertText(QStringLiteral(", ")); appendTextAtCursor(QStringLiteral(": "), false); return; } } appendTextAtCursor(QStringLiteral(" "), false); } } void ChatEdit::triggerCompletion() { if (completionMatches.isEmpty()) startNewCompletion(); if (!completionMatches.isEmpty()) { appendTextAtCursor( sanitizeMention(completionMatches.at(matchesListPosition)), true); ensureCursorVisible(); // The real one, not completionCursor auto completionHL = completionCursor.charFormat(); completionHL.setUnderlineStyle(QTextCharFormat::DashUnderline); setExtraSelections({ { completionCursor, completionHL } }); emit proposedCompletion(completionMatches, matchesListPosition); matchesListPosition = (matchesListPosition + 1) % completionMatches.length(); } } void ChatEdit::cancelCompletion() { completionMatches.clear(); setExtraSelections({}); emit cancelledCompletion(); } void ChatEdit::insertMention(QString author) { // The order of inserting text below is such to be convenient for the user // to undo in case the primitive intelligence below fails. auto cursor = textCursor(); author = sanitizeMention(author); insertPlainText(author); cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::MoveAnchor, author.size()); // Add spaces and a colon around the inserted string if necessary. if (cursor.position() > 0 && document()->characterAt(cursor.position() - 1).isLetterOrNumber()) cursor.insertText(QStringLiteral(" ")); while (cursor.movePosition(QTextCursor::PreviousCharacter) && document()->characterAt(cursor.position()).isSpace()); QString postfix; if (cursor.atStart()) postfix = QStringLiteral(":"); if (pickingMentions && document()->characterAt(cursor.position()) == ':') { cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor); cursor.insertText(QStringLiteral(",")); postfix = QStringLiteral(":"); } auto currentChar = document()->characterAt(textCursor().position()); if (textCursor().atBlockEnd() || currentChar.isLetterOrNumber() || currentChar == '.') postfix.push_back(' '); if (!postfix.isEmpty()) insertPlainText(postfix); pickingMentions = true; } Quaternion-0.0.9.4c/client/chatedit.h000066400000000000000000000042761347141001300173710ustar00rootroot00000000000000/************************************************************************** * * * Copyright (C) 2017 Kitsune Ral * * * 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 . * * * **************************************************************************/ #pragma once #include "kchatedit.h" #include class ChatRoomWidget; class ChatEdit : public KChatEdit { Q_OBJECT public: ChatEdit(ChatRoomWidget* c); void triggerCompletion(); void cancelCompletion(); void insertMention(QString author); signals: void proposedCompletion(const QStringList& allCompletions, int curIndex); void cancelledCompletion(); protected: void keyPressEvent(QKeyEvent* event) override; QString sanitizeMention(QString mentionText); private: ChatRoomWidget* chatRoomWidget; QTextCursor completionCursor; QStringList completionMatches; int matchesListPosition; bool pickingMentions = false; void startNewCompletion(); void appendTextAtCursor(const QString& text, bool select = false); }; Quaternion-0.0.9.4c/client/chatroomwidget.cpp000066400000000000000000000614531347141001300211570ustar00rootroot00000000000000/************************************************************************** * * * Copyright (C) 2015 Felix Rohrbach * * * * 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 . * * * **************************************************************************/ #include "chatroomwidget.h" #include #include #include #include #include #include #include #include #ifdef DISABLE_QQUICKWIDGET #include #else #include #endif #include #include #include #include #include #include #include #include #include "models/messageeventmodel.h" #include "imageprovider.h" #include "chatedit.h" static const auto DefaultPlaceholderText = ChatRoomWidget::tr("Choose a room to send messages or enter a command..."); ChatRoomWidget::ChatRoomWidget(QWidget* parent) : QWidget(parent) , m_messageModel(new MessageEventModel(this)) , m_currentRoom(nullptr) , indexToMaybeRead(-1) , readMarkerOnScreen(false) { { using namespace QMatrixClient; qmlRegisterUncreatableType("QMatrixClient", 1, 0, "Room", "Room objects can only be created by libqmatrixclient"); qmlRegisterUncreatableType("QMatrixClient", 1, 0, "User", "User objects can only be created by libqmatrixclient"); qmlRegisterType(); qRegisterMetaType("GetRoomEventsJob*"); qmlRegisterType("QMatrixClient", 1, 0, "Settings"); qmlRegisterUncreatableType("QMatrixClient", 1, 0, "RoomMessageEvent", "RoomMessageEvent is uncreatable"); } m_timelineWidget = new timelineWidget_t; qDebug() << "Rendering QML with" << timelineWidget_t::staticMetaObject.className(); auto* qmlContainer = #ifdef DISABLE_QQUICKWIDGET QWidget::createWindowContainer(m_timelineWidget, this); #else m_timelineWidget; #endif // Use different objects but the same method with the same parameters qmlContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_timelineWidget->setResizeMode(timelineWidget_t::SizeRootObjectToView); m_imageProvider = new ImageProvider(); m_timelineWidget->engine() ->addImageProvider(QStringLiteral("mtx"), m_imageProvider); auto* ctxt = m_timelineWidget->rootContext(); ctxt->setContextProperty(QStringLiteral("messageModel"), m_messageModel); ctxt->setContextProperty(QStringLiteral("controller"), this); ctxt->setContextProperty(QStringLiteral("debug"), QVariant(false)); ctxt->setContextProperty(QStringLiteral("room"), nullptr); m_timelineWidget->setSource(QUrl("qrc:///qml/Timeline.qml")); m_hudCaption = new QLabel(); m_hudCaption->setWordWrap(true); auto attachButton = new QToolButton(); attachButton->setAutoRaise(true); m_attachAction = new QAction(QIcon::fromTheme("mail-attachment"), tr("Attach"), attachButton); m_attachAction->setCheckable(true); m_attachAction->setDisabled(true); connect(m_attachAction, &QAction::triggered, this, [this] (bool checked) { if (checked) attachedFileName = QFileDialog::getOpenFileName(this, tr("Attach file")); else attachedFileName.clear(); if (!attachedFileName.isEmpty()) { m_chatEdit->setPlaceholderText( tr("Add a message to the file or just push Enter")); emit showStatusMessage(tr("Attaching %1").arg(attachedFileName)); } else { m_attachAction->setChecked(false); m_chatEdit->setPlaceholderText(DefaultPlaceholderText); emit showStatusMessage(tr("Attaching cancelled"), 3000); } }); attachButton->setDefaultAction(m_attachAction); m_chatEdit = new ChatEdit(this); m_chatEdit->setPlaceholderText(DefaultPlaceholderText); m_chatEdit->setAcceptRichText(false); m_chatEdit->setMaximumHeight(maximumChatEditHeight()); connect( m_chatEdit, &KChatEdit::returnPressed, this, &ChatRoomWidget::sendInput ); connect(m_chatEdit, &ChatEdit::proposedCompletion, this, [=](const QStringList& matches, int pos) { setHudCaption( tr("Next completion: %1") .arg(QStringList(matches.mid(pos, 5)).join(", ")) ); }); connect(m_chatEdit, &ChatEdit::cancelledCompletion, this, &ChatRoomWidget::typingChanged); auto* layout = new QVBoxLayout(); layout->addWidget(qmlContainer); layout->addWidget(m_hudCaption); { auto inputLayout = new QHBoxLayout; inputLayout->addWidget(attachButton); inputLayout->addWidget(m_chatEdit); layout->addLayout(inputLayout); } setLayout(layout); } void ChatRoomWidget::enableDebug() { QQmlContext* ctxt = m_timelineWidget->rootContext(); ctxt->setContextProperty(QStringLiteral("debug"), true); } void ChatRoomWidget::setRoom(QuaternionRoom* room) { if (m_currentRoom == room) { m_chatEdit->setFocus(); return; } if( m_currentRoom ) { m_currentRoom->setDisplayed(false); m_currentRoom->setCachedInput( m_chatEdit->toPlainText() ); roomHistories.insert(m_currentRoom, m_chatEdit->history()); m_currentRoom->connection()->disconnect(this); m_currentRoom->disconnect( this ); } readMarkerOnScreen = false; maybeReadTimer.stop(); indicesOnScreen.clear(); attachedFileName.clear(); m_attachAction->setChecked(false); m_chatEdit->cancelCompletion(); m_currentRoom = room; m_attachAction->setEnabled(m_currentRoom != nullptr); if( m_currentRoom ) { using namespace QMatrixClient; m_imageProvider->setConnection(room->connection()); m_chatEdit->setText( m_currentRoom->cachedInput() ); m_chatEdit->setHistory(roomHistories.value(m_currentRoom)); m_chatEdit->setFocus(); m_chatEdit->moveCursor(QTextCursor::End); connect( m_currentRoom, &Room::typingChanged, this, &ChatRoomWidget::typingChanged ); connect( m_currentRoom, &Room::readMarkerMoved, this, [this] { const auto rm = m_currentRoom->readMarker(); readMarkerOnScreen = rm != m_currentRoom->timelineEdge() && std::lower_bound( indicesOnScreen.begin(), indicesOnScreen.end(), rm->index() ) != indicesOnScreen.end(); reStartShownTimer(); emit readMarkerMoved(); }); connect( m_currentRoom, &Room::encryption, this, &ChatRoomWidget::encryptionChanged); connect(m_currentRoom->connection(), &Connection::loggedOut, this, [this] { qWarning() << "Logged out, escaping the room"; setRoom(nullptr); }); m_currentRoom->setDisplayed(true); } else m_imageProvider->setConnection(nullptr); m_timelineWidget->rootContext() ->setContextProperty(QStringLiteral("room"), room); typingChanged(); encryptionChanged(); m_messageModel->changeRoom( m_currentRoom ); } void ChatRoomWidget::typingChanged() { if (!m_currentRoom || m_currentRoom->usersTyping().isEmpty()) { m_hudCaption->clear(); return; } QStringList typingNames; for(auto user: m_currentRoom->usersTyping()) { typingNames << m_currentRoom->roomMembername(user); } setHudCaption( tr("Currently typing: %1") .arg(typingNames.join(QStringLiteral(", "))) ); } void ChatRoomWidget::encryptionChanged() { m_chatEdit->setReadOnly(m_currentRoom && m_currentRoom->usesEncryption()); m_chatEdit->setPlaceholderText( m_currentRoom ? m_currentRoom->usesEncryption() ? tr("Sending encrypted messages is not supported yet") : tr("Send a message (over %1) or enter a command...", "%1 is the protocol used by the server (usually HTTPS)") .arg(m_currentRoom->connection()->homeserver() .scheme().toUpper()) : DefaultPlaceholderText); } void ChatRoomWidget::setHudCaption(QString newCaption) { m_hudCaption->setText("" + newCaption + ""); } void ChatRoomWidget::insertMention(QMatrixClient::User* user) { m_chatEdit->insertMention(user->displayname(m_currentRoom)); } void ChatRoomWidget::focusInput() { m_chatEdit->setFocus(); } /** * \brief Split the string into the specified number of parts * The function takes \p s and splits it into \p maxParts parts using \p sep * for the separator. Empty parts are skipped. If there are more than * \p maxParts parts in the string, the last returned part includes * the remainder of the string; if there are fewer parts, the missing parts * are filled with empty strings. * \return the vector of references to the original string, one reference for * each part. */ QVector lazySplitRef(const QString& s, QChar sep, int maxParts) { QVector parts { maxParts }; int pos = 0, nextPos = 0; for (; maxParts > 1 && (nextPos = s.indexOf(sep, pos)) > -1; --maxParts) { parts[parts.size() - maxParts] = s.mid(pos, nextPos - pos); while (s[++nextPos] == sep) ; pos = nextPos; } parts[parts.size() - maxParts] = s.mid(pos); return parts; } QString ChatRoomWidget::doSendInput() { auto text = m_chatEdit->toPlainText(); if (!attachedFileName.isEmpty()) { Q_ASSERT(m_currentRoom != nullptr); auto txnId = m_currentRoom->postFile(text.isEmpty() ? QUrl(attachedFileName).fileName() : text, QUrl::fromLocalFile(attachedFileName)); attachedFileName.clear(); m_attachAction->setChecked(false); m_chatEdit->setPlaceholderText(DefaultPlaceholderText); return {}; } if ( text.isEmpty() ) return tr("There's nothing to send"); static const auto ReFlags = QRegularExpression::DotMatchesEverythingOption| QRegularExpression::OptimizeOnFirstUsageOption; static const QRegularExpression CommandRe { QStringLiteral("^/([^ ]+)( +(.*))?\\s*$"), ReFlags }, RoomIdRE { QStringLiteral("^(#[-0-9a-z._=]+)|(!\\S+):\\S+$"), ReFlags }, UserIdRE { QStringLiteral("^@[-0-9a-zA-Z._=/]+:\\S+$"), ReFlags }, HtmlTagRE { QStringLiteral("<[^>]+>"), ReFlags }; // Process a command const auto matches = CommandRe.match(text); const auto command = matches.capturedRef(1); const auto argString = matches.captured(3); // Commands available without a current room if (command == "join") { if (!argString.contains(RoomIdRE)) return tr("/join argument doesn't look like a room ID or alias"); emit joinCommandEntered(argString); return {}; } if (command == "quit") { qApp->closeAllWindows(); return {}; } // --- Add more roomless commands here if (!m_currentRoom) { if (text.startsWith('/')) return tr("There's no such /command outside of room."); return tr("You should select a room to send messages."); } if (!text.startsWith('/')) { m_currentRoom->postPlainText(text); return {}; } if (text[1] == '/') { text.remove(0, 1); m_currentRoom->postPlainText(text); return {}; } // Commands available only in the room context using namespace QMatrixClient; if (command == "leave" || command == "part") { if (!argString.isEmpty()) return tr("Sending a farewell message is not supported yet." " If you intended to leave another room, switch to it" " and type /leave there."); m_currentRoom->leaveRoom(); return {}; } if (command == "forget") { if (argString.isEmpty()) return tr("/forget must be followed by the room id/alias," " even for the current room"); if (!argString.contains(RoomIdRE)) return tr("%1 doesn't look like a room id or alias").arg(argString); // Forget the specified room using the current room's connection m_currentRoom->connection()->forgetRoom(argString); return {}; } if (command == "invite") { if (argString.isEmpty()) return tr("/invite "); if (!argString.contains(UserIdRE)) return tr("%1 doesn't look like a user ID").arg(argString); m_currentRoom->inviteToRoom(argString); return {}; } if (command == "kick" || command == "ban") { const auto args = lazySplitRef(argString, ' ', 2); if (args.front().isEmpty()) return tr("/%1 ").arg(command.toString()); if (!UserIdRE.match(args.front()).hasMatch()) return tr("%1 doesn't look like a user id") .arg(args.front()); if (command == "ban") m_currentRoom->ban(args.front(), args.back()); else { auto* user = m_currentRoom->user(args.front()); if (m_currentRoom->memberJoinState(user) != JoinState::Join) return tr("%1 is not a member of this room") .arg(user->fullName(m_currentRoom)); m_currentRoom->kickMember(user->id(), args.back()); } return {}; } if (command == "unban") { if (argString.isEmpty()) return tr("/unban "); if (!argString.contains(UserIdRE)) return tr("/unban argument doesn't look like a user ID"); m_currentRoom->unban(argString); return {}; } if (command == "ignore" || command == "unignore") { if (argString.isEmpty()) return tr("/ignore "); if (!argString.contains(UserIdRE)) return tr("/ignore argument doesn't look like a user ID"); if (auto* user = m_currentRoom->user(argString)) { if (command == "ignore") user->ignore(); else user->unmarkIgnore(); return {}; } return tr("Couldn't find user %1 on the server").arg(argString); } using MsgType = RoomMessageEvent::MsgType; if (command == "me") { if (argString.isEmpty()) return tr("/me needs an argument"); m_currentRoom->postMessage(argString, MsgType::Emote); return {}; } if (command == "notice") { if (argString.isEmpty()) return tr("/notice needs an argument"); m_currentRoom->postMessage(argString, MsgType::Notice); return {}; } if (command == "shrug") // Peeked at Discord { m_currentRoom->postPlainText((argString.isEmpty() ? "" : argString + " ") + "¯\\_(ツ)_/¯"); return {}; } if (command == "topic") { m_currentRoom->setTopic(argString); return {}; } if (command == "nick") { m_currentRoom->localUser()->rename(argString); return {}; } if (command == "roomnick") { m_currentRoom->localUser()->rename(argString, m_currentRoom); return {}; } if (command == "pm" || command == "msg") { const auto args = lazySplitRef(argString, ' ', 2); if (args.front().isEmpty() || (args.back().isEmpty() && command == "msg")) return tr("/%1 ").arg(command.toString()); if (RoomIdRE.match(args.front()).hasMatch() && command == "msg") { if (auto* room = m_currentRoom->connection()->room(args.front())) { room->postPlainText(args.back()); return {}; } return tr("%1 doesn't seem to have joined room %2") .arg(m_currentRoom->localUser()->id(), args.front()); } if (UserIdRE.match(args.front()).hasMatch()) { if (args.back().isEmpty()) m_currentRoom->connection()->requestDirectChat(args.front()); else m_currentRoom->connection()->doInDirectChat(args.front(), [msg=args.back()] (Room* dc) { dc->postPlainText(msg); }); return {}; } return tr("%1 doesn't look like a user id or room alias") .arg(args.front()); } if (command == "html") { // Very crude HTML-to-plaintext conversion - just strip all the tags auto plainText = argString; plainText.replace(HtmlTagRE, QString()); m_currentRoom->postHtmlText(plainText, argString); return {}; } if (command == "query" || command == "dc") { if (argString.isEmpty()) return tr("/%1 ").arg(command.toString()); if (!argString.contains(UserIdRE)) return tr("%1 doesn't look like a user id").arg(argString); m_currentRoom->connection()->requestDirectChat(argString); return {}; } // --- Add more room commands here qDebug() << "Unknown command:" << command; return tr("Unknown /command. Use // to send this line literally"); } void ChatRoomWidget::sendInput() { auto result = doSendInput(); if (result.isEmpty()) m_chatEdit->saveInput(); else emit showStatusMessage(result, 5000); } QStringList ChatRoomWidget::findCompletionMatches(const QString& pattern) const { QStringList matches; if (m_currentRoom) { for(auto name: m_currentRoom->memberNames() ) { if ( name.startsWith(pattern, Qt::CaseInsensitive) ) { int ircSuffixPos = name.indexOf(" (IRC)"); if ( ircSuffixPos != -1 ) name.truncate(ircSuffixPos); matches.append(name); } } std::sort(matches.begin(), matches.end(), [] (const QString& s1, const QString& s2) { return s1.localeAwareCompare(s2) < 0; }); matches.removeDuplicates(); } return matches; } void ChatRoomWidget::saveFileAs(QString eventId) { if (!m_currentRoom) { qWarning() << "ChatRoomWidget::saveFileAs without an active room ignored"; return; } auto fileName = QFileDialog::getSaveFileName( this, tr("Save file as"), m_currentRoom->fileNameToDownload(eventId)); if (!fileName.isEmpty()) m_currentRoom->downloadFile(eventId, QUrl::fromLocalFile(fileName)); } void ChatRoomWidget::onMessageShownChanged(const QString& eventId, bool shown) { if (!m_currentRoom || !m_currentRoom->displayed()) return; // A message can be auto-marked as read (as soon as the user is active), if: // 0. The read marker exists and is on the screen // 1. The message is shown on the screen now // 2. It's been the bottommost message on the screen for the last 1 second // 3. It's below the read marker const auto readMarker = m_currentRoom->readMarker(); if (readMarker != m_currentRoom->timelineEdge() && readMarker->event()->id() == eventId) { readMarkerOnScreen = shown; if (shown) { qDebug() << "Read marker is on-screen, at" << *readMarker; indexToMaybeRead = readMarker->index(); reStartShownTimer(); } else { qDebug() << "Read marker is off-screen"; qDebug() << "Bottommost shown message index was" << indexToMaybeRead; maybeReadTimer.stop(); } } const auto iter = m_currentRoom->findInTimeline(eventId); if (iter == m_currentRoom->timelineEdge()) { qWarning() << "Event" << eventId << "is not in the timeline (local echo?)"; return; } const auto timelineIndex = iter->index(); auto pos = std::lower_bound(indicesOnScreen.begin(), indicesOnScreen.end(), timelineIndex); if (shown) { if (pos == indicesOnScreen.end() || *pos != timelineIndex) { indicesOnScreen.insert(pos, timelineIndex); if (timelineIndex == indicesOnScreen.back()) reStartShownTimer(); } } else { if (pos != indicesOnScreen.end() && *pos == timelineIndex) if (indicesOnScreen.erase(pos) == indicesOnScreen.end()) reStartShownTimer(); } } void ChatRoomWidget::quote(const QString& htmlText) { QMatrixClient::SettingsGroup sg { QStringLiteral("UI") }; const auto type = sg.get("quote_type"); const auto defaultStyle = QStringLiteral("> \\1\n\n"); const auto defaultRegex = QStringLiteral("(.+)(?:\n|$)"); auto style = sg.get("quote_style"); auto regex = sg.get("quote_regex"); if (style.isEmpty()) style = defaultStyle; if (regex.isEmpty()) regex = defaultRegex; QTextDocument document; document.setHtml(htmlText); QString sendString; switch (type) { case 0: sendString = document.toPlainText() .replace(QRegularExpression(defaultRegex), defaultStyle); break; case 1: sendString = document.toPlainText() .replace(QRegularExpression(regex), style); break; case 2: sendString = QLocale().quoteString(document.toPlainText()) + "\n"; break; } m_chatEdit->insertPlainText(sendString); } void ChatRoomWidget::reStartShownTimer() { if (!readMarkerOnScreen || indicesOnScreen.empty() || indexToMaybeRead >= indicesOnScreen.back()) return; maybeReadTimer.start(1000, this); qDebug() << "Scheduled maybe-read message update:" << indexToMaybeRead << "->" << indicesOnScreen.back(); } void ChatRoomWidget::timerEvent(QTimerEvent* qte) { if (qte->timerId() != maybeReadTimer.timerId()) { QWidget::timerEvent(qte); return; } maybeReadTimer.stop(); // Only update the maybe-read message if we're tracking it if (readMarkerOnScreen && !indicesOnScreen.empty() && indexToMaybeRead < indicesOnScreen.back()) { qDebug() << "Maybe-read message update:" << indexToMaybeRead << "->" << indicesOnScreen.back(); indexToMaybeRead = indicesOnScreen.back(); emit readMarkerCandidateMoved(); } } void ChatRoomWidget::resizeEvent(QResizeEvent*) { m_chatEdit->setMaximumHeight(maximumChatEditHeight()); } void ChatRoomWidget::keyPressEvent(QKeyEvent* event) { switch(event->key()) { case Qt::Key_PageUp: emit pageUpPressed(); break; case Qt::Key_PageDown: emit pageDownPressed(); break; } } int ChatRoomWidget::maximumChatEditHeight() const { return maximumHeight() / 3; } void ChatRoomWidget::markShownAsRead() { // FIXME: a case when a single message doesn't fit on the screen. if (m_currentRoom && readMarkerOnScreen) { const auto iter = m_currentRoom->findInTimeline(indicesOnScreen.back()); Q_ASSERT( iter != m_currentRoom->timelineEdge() ); m_currentRoom->markMessagesAsRead((*iter)->id()); } } bool ChatRoomWidget::pendingMarkRead() const { if (!readMarkerOnScreen || !m_currentRoom) return false; const auto rm = m_currentRoom->readMarker(); return rm != m_currentRoom->timelineEdge() && rm->index() < indexToMaybeRead; } Quaternion-0.0.9.4c/client/chatroomwidget.h000066400000000000000000000077151347141001300206250ustar00rootroot00000000000000/************************************************************************** * * * Copyright (C) 2015 Felix Rohrbach * * * * 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 . * * * **************************************************************************/ #pragma once #include #include #include "quaternionroom.h" #ifndef USE_QQUICKWIDGET #define DISABLE_QQUICKWIDGET #endif class ChatEdit; class MessageEventModel; class ImageProvider; class QFrame; #ifdef DISABLE_QQUICKWIDGET class QQuickView; #else class QQuickWidget; #endif class QLabel; class QAction; class QTextDocument; class ChatRoomWidget: public QWidget { Q_OBJECT public: explicit ChatRoomWidget(QWidget* parent = nullptr); void enableDebug(); bool pendingMarkRead() const; QStringList findCompletionMatches(const QString& pattern) const; signals: void joinCommandEntered(const QString& roomAlias); void resourceRequested(const QString& idOrUri, const QString& action = {}); void showStatusMessage(const QString& message, int timeout = 0) const; void readMarkerMoved(); void readMarkerCandidateMoved(); void pageUpPressed(); void pageDownPressed(); public slots: void setRoom(QuaternionRoom* room); void insertMention(QMatrixClient::User* user); void focusInput(); void typingChanged(); void onMessageShownChanged(const QString& eventId, bool shown); void markShownAsRead(); void saveFileAs(QString eventId); void quote(const QString& htmlText); private slots: void sendInput(); void encryptionChanged(); void setHudCaption(QString newCaption); private: // Data MessageEventModel* m_messageModel; QuaternionRoom* m_currentRoom; ImageProvider* m_imageProvider; #ifdef DISABLE_QQUICKWIDGET using timelineWidget_t = QQuickView; #else using timelineWidget_t = QQuickWidget; #endif // Controls timelineWidget_t* m_timelineWidget; QLabel* m_hudCaption; //< For typing and completion notifications QAction* m_attachAction; ChatEdit* m_chatEdit; // Supplementary/cache data members using timeline_index_t = QMatrixClient::TimelineItem::index_t; QVector indicesOnScreen; timeline_index_t indexToMaybeRead; QBasicTimer maybeReadTimer; bool readMarkerOnScreen; QMap> roomHistories; QString attachedFileName; void reStartShownTimer(); QString doSendInput(); void timerEvent(QTimerEvent* qte) override; void resizeEvent(QResizeEvent*) override; void keyPressEvent(QKeyEvent*) override; int maximumChatEditHeight() const; }; Quaternion-0.0.9.4c/client/dialog.cpp000066400000000000000000000070421347141001300173700ustar00rootroot00000000000000/****************************************************************************** * Copyright (C) 2017 Kitsune Ral * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "dialog.h" #include #include Dialog::Dialog(const QString& title, QWidget *parent, UseStatusLine useStatusLine, const QString& applyTitle, QDialogButtonBox::StandardButtons addButtons) : Dialog(title , QDialogButtonBox::Ok | QDialogButtonBox::Cancel | addButtons , parent, useStatusLine) { if (!applyTitle.isEmpty()) buttons->button(QDialogButtonBox::Ok)->setText(applyTitle); } Dialog::Dialog(const QString& title, QDialogButtonBox::StandardButtons setButtons, QWidget *parent, UseStatusLine useStatusLine) : QDialog(parent) , applyLatency(useStatusLine) , pendingApplyMessage(tr("Applying changes, please wait")) , statusLabel(useStatusLine == NoStatusLine ? nullptr : new QLabel) , buttons(new QDialogButtonBox(setButtons)) , outerLayout(this) { setWindowTitle(title); #if (QT_VERSION < QT_VERSION_CHECK(5, 10, 0)) setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); #endif connect(buttons, &QDialogButtonBox::clicked, this, &Dialog::buttonClicked); outerLayout.addWidget(buttons); if (statusLabel) outerLayout.addWidget(statusLabel); } void Dialog::addLayout(QLayout* l) { int offset = 1 + (statusLabel != nullptr); outerLayout.insertLayout(outerLayout.count() - offset, l); } void Dialog::addWidget(QWidget* w) { int offset = 1 + (statusLabel != nullptr); outerLayout.insertWidget(outerLayout.count() - offset, w); } QPushButton*Dialog::button(QDialogButtonBox::StandardButton which) { return buttonBox()->button(which); } void Dialog::reactivate() { if (!isVisible()) { load(); show(); } raise(); activateWindow(); } void Dialog::setStatusMessage(const QString& msg) { Q_ASSERT(statusLabel); statusLabel->setText(msg); } void Dialog::applyFailed(const QString& errorMessage) { setStatusMessage(errorMessage); setDisabled(false); } void Dialog::buttonClicked(QAbstractButton* button) { switch (buttons->buttonRole(button)) { case QDialogButtonBox::AcceptRole: case QDialogButtonBox::YesRole: if (validate()) { if (statusLabel) statusLabel->setText(pendingApplyMessage); setDisabled(true); apply(); } break; case QDialogButtonBox::ResetRole: load(); break; case QDialogButtonBox::RejectRole: case QDialogButtonBox::NoRole: reject(); break; default: ; // Derived classes may completely replace or reuse this method } } Quaternion-0.0.9.4c/client/dialog.h000066400000000000000000000102151347141001300170310ustar00rootroot00000000000000/****************************************************************************** * Copyright (C) 2017 Kitsune Ral * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #pragma once #include #include #include #include class QAbstractButton; class QLabel; class Dialog : public QDialog { Q_OBJECT public: enum UseStatusLine { NoStatusLine, StatusLine }; static const auto NoExtraButtons = QDialogButtonBox::NoButton; explicit Dialog(const QString& title, QWidget *parent = nullptr, UseStatusLine useStatusLine = NoStatusLine, const QString& applyTitle = {}, QDialogButtonBox::StandardButtons addButtons = QDialogButtonBox::Reset); explicit Dialog(const QString& title, QDialogButtonBox::StandardButtons setButtons, QWidget *parent = nullptr, UseStatusLine useStatusLine = NoStatusLine); /// Create and add a layout of the given type /*! This creates a new layout object and adds it to the bottom of * the dialog client area (i.e., above the button box). */ template LayoutT* addLayout() { auto l = new LayoutT; addLayout(l); return l; } /// Add a layout to the bottom of the dialog's client area void addLayout(QLayout* l); /// Add a widget to the bottom of the dialog's client area void addWidget(QWidget* w); QPushButton* button(QDialogButtonBox::StandardButton which); public slots: /// Show or raise the dialog void reactivate(); /// Set the status line of the dialog window void setStatusMessage(const QString& msg); /// Return to the dialog after a failed apply void applyFailed(const QString& errorMessage); protected: /// (Re-)Load data in the dialog /*! \sa buttonClicked */ virtual void load() { } /// Check data in the dialog before accepting /*! \sa apply, buttonClicked */ virtual bool validate() { return true; } /// Apply changes and close the dialog /*! * This method is invoked upon clicking the "apply" button (by default * it's the one with `AcceptRole`), if validate() returned true. * \sa buttonClicked, validate */ virtual void apply() { accept(); } /// React to a click of a button in the dialog box /*! * This virtual function is invoked every time one of push buttons * in the dialog button box is clicked; it defines how the dialog reacts * to each button. By default, it calls validate() and, if it succeeds, * apply() on buttons with `AcceptRole`; cancels the dialog on * `RejectRole`; and reloads the fields on `ResetRole`. Override this * method to change this behaviour. * \sa validate, apply, reject, load */ virtual void buttonClicked(QAbstractButton* button); QDialogButtonBox* buttonBox() const { return buttons; } QLabel* statusLine() const { return statusLabel; } void setPendingApplyMessage(const QString& msg) { pendingApplyMessage = msg; } private: UseStatusLine applyLatency; QString pendingApplyMessage; QLabel* statusLabel; QDialogButtonBox* buttons; QVBoxLayout outerLayout; }; Quaternion-0.0.9.4c/client/imageprovider.cpp000066400000000000000000000133031347141001300207630ustar00rootroot00000000000000/************************************************************************** * * * Copyright (C) 2016 Felix Rohrbach * * * * 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 . * * * **************************************************************************/ #include "imageprovider.h" #include #include #include #include #include using QMatrixClient::Connection; using QMatrixClient::BaseJob; class ThumbnailResponse : public QQuickImageResponse { Q_OBJECT public: ThumbnailResponse(Connection* c, QString id, QSize size) : c(c), mediaId(std::move(id)), requestedSize(size) , errorStr(tr("Image request hasn't started")) { if (!c) { errorStr = tr("No connection to perform image request"); emit finished(); return; } if (mediaId.count('/') != 1) { errorStr = tr("Media id '%1' doesn't follow server/mediaId pattern") .arg(mediaId); emit finished(); return; } // Execute a request on the main thread asynchronously moveToThread(c->thread()); QMetaObject::invokeMethod(this, #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) &ThumbnailResponse::startRequest #else "startRequest" #endif ); } ~ThumbnailResponse() override = default; private slots: // All these run in the main thread, not QML thread void startRequest() { Q_ASSERT(QThread::currentThread() == c->thread()); job = c->getThumbnail(mediaId, requestedSize); // Connect to any possible outcome including abandonment // to make sure the QML thread is not left stuck forever. connect(job, &BaseJob::finished, this, &ThumbnailResponse::prepareResult); } void prepareResult() { Q_ASSERT(QThread::currentThread() == job->thread()); Q_ASSERT(job->error() != BaseJob::Pending); { QWriteLocker _(&lock); if (job->error() == BaseJob::Success) { image = job->thumbnail(); errorStr.clear(); qDebug() << "ThumbnailResponse: image ready for" << mediaId; } else if (job->error() == BaseJob::Abandoned) { errorStr = tr("Image request has been cancelled"); qDebug() << "ThumbnailResponse: cancelled for" << mediaId; } else { errorStr = job->errorString(); qWarning() << "ThumbnailResponse: no valid image for" << mediaId << "-" << errorStr; } } job = nullptr; emit finished(); } void doCancel() { if (job) { Q_ASSERT(QThread::currentThread() == job->thread()); job->abandon(); } } private: Connection* c; const QString mediaId; const QSize requestedSize; QMatrixClient::MediaThumbnailJob* job = nullptr; QImage image; QString errorStr; mutable QReadWriteLock lock; // Guards ONLY these two above // The following overrides run in QML thread QQuickTextureFactory *textureFactory() const override { QReadLocker _(&lock); return QQuickTextureFactory::textureFactoryForImage(image); } QString errorString() const override { QReadLocker _(&lock); return errorStr; } void cancel() override { // Flip from QML thread to the main thread QMetaObject::invokeMethod(this, #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) &ThumbnailResponse::doCancel #else "doCancel" #endif ); } }; #include "imageprovider.moc" // Because we define a Q_OBJECT in the cpp file ImageProvider::ImageProvider(Connection* connection) : m_connection(connection) { } QQuickImageResponse* ImageProvider::requestImageResponse( const QString& id, const QSize& requestedSize) { qDebug() << "ImageProvider: requesting " << id; return new ThumbnailResponse(m_connection.load(), id, requestedSize); } void ImageProvider::setConnection(Connection* connection) { m_connection.store(connection); } Quaternion-0.0.9.4c/client/imageprovider.h000066400000000000000000000044031347141001300204310ustar00rootroot00000000000000/************************************************************************** * * * Copyright (C) 2016 Felix Rohrbach * * * * 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 . * * * **************************************************************************/ #pragma once #include #include namespace QMatrixClient { class Connection; } // FIXME: It's actually ThumbnailProvider, not ImageProvider, because internally // it calls MediaThumbnailJob. Trying to get a full-size image using this // provider may bring suboptimal results (and generally you shouldn't do it // because images loaded by QML are not necessarily cached to disk, so it's a // waste of bandwidth). class ImageProvider: public QQuickAsyncImageProvider { public: explicit ImageProvider(QMatrixClient::Connection* connection = nullptr); QQuickImageResponse* requestImageResponse( const QString& id, const QSize& requestedSize) override; void setConnection(QMatrixClient::Connection* connection); private: QAtomicPointer m_connection; Q_DISABLE_COPY(ImageProvider) }; Quaternion-0.0.9.4c/client/kchatedit.cpp000066400000000000000000000145111347141001300200700ustar00rootroot00000000000000/* * Copyright (C) 2017 Elvis Angelaccio * * 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 . */ #include "kchatedit.h" #include #include #include class KChatEdit::KChatEditPrivate { public: QString getDocumentText(QTextDocument* doc) const; void updateAndMoveInHistory(int increment); void rewindHistory(); void forwardHistory(); void saveInput(); KChatEdit *q = nullptr; // History always ends with a placeholder string that is initially empty // but may be filled with tentative input when the user entered something // and then went out for history. QVector history { 1, new QTextDocument() }; int index = 0; int maxHistorySize = 100; }; QString KChatEdit::KChatEditPrivate::getDocumentText(QTextDocument* doc) const { Q_ASSERT(doc); return q->acceptRichText() ? doc->toHtml() : doc->toPlainText(); } void KChatEdit::KChatEditPrivate::updateAndMoveInHistory(int increment) { Q_ASSERT(index >= 0 && index < history.size()); // Only save input if different from the latest one. if (getDocumentText(q->document()) != getDocumentText(history[index])) { history[index] = q->document(); history[index]->setParent(nullptr); } const auto* nextDocument = history.at(index += increment); q->setDocument(nextDocument->clone(q)); q->moveCursor(QTextCursor::End); } void KChatEdit::KChatEditPrivate::rewindHistory() { if (index > 0) updateAndMoveInHistory(-1); } void KChatEdit::KChatEditPrivate::forwardHistory() { if (index < history.size() - 1) updateAndMoveInHistory(+1); } void KChatEdit::KChatEditPrivate::saveInput() { if (q->document()->isEmpty()) { return; } // Only save input if different from the latest one or from the history. const auto input = getDocumentText(q->document()); if (index < history.size() - 1 && input == getDocumentText(history[index])) { // Take the history entry and move it to the most recent position (but // before the placeholder). #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) history.move(index, history.size() - 2); #else history.insert(history.size() - 2, history.takeAt(index)); #endif emit q->savedInputChanged(); } else if (input != getDocumentText(q->savedInput())) { // Replace the placeholder with the new input. history.back() = q->document()->clone(); if (history.size() >= maxHistorySize) { delete history.takeFirst(); } // Make a new placeholder. history << new QTextDocument(); emit q->savedInputChanged(); } index = history.size() - 1; q->clear(); } KChatEdit::KChatEdit(QWidget *parent) : QTextEdit(parent), d(new KChatEditPrivate) { setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); connect(this, &QTextEdit::textChanged, this, &QWidget::updateGeometry); d->q = this; // KChatEdit initialization complete, pimpl can use it } KChatEdit::~KChatEdit() = default; QTextDocument* KChatEdit::savedInput() const { if (d->history.size() >= 2) return d->history.at(d->history.size() - 2); Q_ASSERT(d->history.size() == 1); return d->history.front(); } void KChatEdit::saveInput() { d->saveInput(); } QVector KChatEdit::history() const { return d->history; } void KChatEdit::setHistory(const QVector &history) { d->history = history; if (history.isEmpty() || !history.last()->isEmpty()) { d->history << new QTextDocument(); } while (d->history.size() > maxHistorySize()) { delete d->history.takeFirst(); } d->index = d->history.size() - 1; } int KChatEdit::maxHistorySize() const { return d->maxHistorySize; } void KChatEdit::setMaxHistorySize(int maxHistorySize) { d->maxHistorySize = maxHistorySize; } QSize KChatEdit::minimumSizeHint() const { QSize minimumSizeHint = QTextEdit::minimumSizeHint(); QMargins margins; margins += static_cast(document()->documentMargin()); margins += contentsMargins(); if (!placeholderText().isEmpty()) { minimumSizeHint.setWidth(fontMetrics().width(placeholderText()) + margins.left()*2.5); } if (document()->isEmpty()) { minimumSizeHint.setHeight(fontMetrics().lineSpacing() + margins.top() + margins.bottom()); } else { minimumSizeHint.setHeight(document()->size().height()); } return minimumSizeHint; } QSize KChatEdit::sizeHint() const { ensurePolished(); if (document()->isEmpty()) { return minimumSizeHint(); } QMargins margins; margins += static_cast(document()->documentMargin()); margins += contentsMargins(); QSize size = document()->size().toSize(); size.rwidth() += margins.left() + margins.right(); size.rheight() += margins.top() + margins.bottom(); // Be consistent with minimumSizeHint(). if (document()->lineCount() == 1 && !toPlainText().contains(QLatin1Char('\n'))) { size.setHeight(fontMetrics().lineSpacing() + margins.top() + margins.bottom()); } return size; } void KChatEdit::keyPressEvent(QKeyEvent *event) { switch (event->key()) { case Qt::Key_Enter: case Qt::Key_Return: if (!(QGuiApplication::keyboardModifiers() & Qt::ShiftModifier)) { emit returnPressed(); return; } break; case Qt::Key_Up: if (!textCursor().movePosition(QTextCursor::Up)) { d->rewindHistory(); } break; case Qt::Key_Down: if (!textCursor().movePosition(QTextCursor::Down)) { d->forwardHistory(); } break; default: break; } QTextEdit::keyPressEvent(event); } Quaternion-0.0.9.4c/client/kchatedit.h000066400000000000000000000103371347141001300175370ustar00rootroot00000000000000/* * Copyright (C) 2017 Elvis Angelaccio * * 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 . */ #ifndef KCHATEDIT_H #define KCHATEDIT_H #include /** * @class KChatEdit kchatedit.h KChatEdit * * @brief An input widget with history for chat applications. * * This widget can be used to get input for chat windows, which tipically corresponds to chat messages or * protocol-specific commands (for example the "/whois" IRC command). * * By default the widget takes as less space as possible, which is the same space as used by a QLineEdit. * It is possible to expand the widget and enter "multi-line" messages, by pressing Shift + Return. * * Chat applications usually maintain a history of what the user typed, which can be browsed with the * Up and Down keys (exactly like in command-line shells). This feature is fully supported by this widget. * The widget emits the inputRequested() signal upon pressing the Return key. * You can then call saveInput() to make the input text disappear, as typical in chat applications. * The input goes in the history and can be retrieved with the savedInput() method. * * @author Elvis Angelaccio */ class KChatEdit : public QTextEdit { Q_OBJECT Q_PROPERTY(QTextDocument* savedInput READ savedInput NOTIFY savedInputChanged) Q_PROPERTY(QVector history READ history WRITE setHistory) Q_PROPERTY(int maxHistorySize READ maxHistorySize WRITE setMaxHistorySize) public: explicit KChatEdit(QWidget *parent = nullptr); ~KChatEdit() override; /** * The latest input text saved in the history. * This corresponds to the last element of history(). * @return Latest available input or an empty document if saveInput() has not been called yet. * @see inputChanged(), saveInput(), history() */ QTextDocument* savedInput() const; /** * Saves in the history the current document(). * This also clears the QTextEdit area. * @note If the history is full (see maxHistorySize(), new inputs will take space from the oldest * items in the history. * @see savedInput(), history(), maxHistorySize() */ void saveInput(); /** * @return The history of the text inputs that the user typed. * @see savedInput(), saveInput(); */ QVector history() const; /** * Set the history of this widget to @p history. * This can be useful when sharing a single instance of KChatEdit with many "channels" or "rooms" * that maintain their own private history. */ void setHistory(const QVector &history); /** * @return The maximum number of input items that the history can store. * @see history() */ int maxHistorySize() const; /** * Set the maximum number of input items that the history can store. * @see maxHistorySize() */ void setMaxHistorySize(int maxHistorySize); QSize minimumSizeHint() const Q_DECL_OVERRIDE; QSize sizeHint() const Q_DECL_OVERRIDE; Q_SIGNALS: /** * A new input has been saved in the history. * @see savedInput(), saveInput(), history() */ void savedInputChanged(); /** * Emitted when the user types Key_Return or Key_Enter, which typically means the user * wants to "send" what was typed. Call saveInput() if you want to actually save the input. * @see savedInput(), saveInput(), history() */ void returnPressed(); protected: void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE; private: class KChatEditPrivate; QScopedPointer d; Q_DISABLE_COPY(KChatEdit) }; #endif Quaternion-0.0.9.4c/client/logindialog.cpp000066400000000000000000000124751347141001300204270ustar00rootroot00000000000000/************************************************************************** * * * Copyright (C) 2015 Felix Rohrbach * * * * 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 . * * * **************************************************************************/ #include "logindialog.h" #include #include #include #include #include #include #include using QMatrixClient::Connection; LoginDialog::LoginDialog(QWidget* parent, const QStringList& knownAccounts) : Dialog(tr("Login"), parent, Dialog::StatusLine, tr("Login"), Dialog::NoExtraButtons) , userEdit(new QLineEdit(this)) , passwordEdit(new QLineEdit(this)) , initialDeviceName(new QLineEdit(this)) , serverEdit(new QLineEdit(QStringLiteral("https://matrix.org"), this)) , saveTokenCheck(new QCheckBox(tr("Stay logged in"), this)) , m_connection(new Connection) { setup(); setPendingApplyMessage(tr("Connecting and logging in, please wait")); connect(userEdit, &QLineEdit::editingFinished, m_connection.data(), [=] { auto userId = userEdit->text(); if (userId.startsWith('@') && userId.indexOf(':') != -1) m_connection->resolveServer(userId); }); { // Fill defaults using namespace QMatrixClient; if ( !knownAccounts.empty() ) { AccountSettings account { knownAccounts.front() }; userEdit->setText(account.userId()); auto homeserver = account.homeserver(); if (!homeserver.isEmpty()) { m_connection->setHomeserver(homeserver); serverEdit->setText(homeserver.toString()); } initialDeviceName->setText(account.deviceName()); saveTokenCheck->setChecked(account.keepLoggedIn()); passwordEdit->setFocus(); } else { saveTokenCheck->setChecked(false); userEdit->setFocus(); } } } LoginDialog::LoginDialog(QWidget* parent, const QMatrixClient::AccountSettings& reloginData) : Dialog(tr("Re-login"), parent, Dialog::StatusLine, tr("Re-login"), Dialog::NoExtraButtons) , userEdit(new QLineEdit(reloginData.userId(), this)) , passwordEdit(new QLineEdit(this)) , initialDeviceName(new QLineEdit(reloginData.deviceName(), this)) , serverEdit(new QLineEdit(reloginData.homeserver().toString(), this)) , saveTokenCheck(new QCheckBox(tr("Stay logged in"), this)) , m_connection(new Connection) { setup(); userEdit->setReadOnly(true); userEdit->setFrame(false); setPendingApplyMessage(tr("Restoring access, please wait")); } void LoginDialog::setup() { passwordEdit->setEchoMode( QLineEdit::Password ); connect(m_connection.data(), &Connection::homeserverChanged, serverEdit, [=] (const QUrl& newUrl) { serverEdit->setText(newUrl.toString()); }); auto* formLayout = addLayout(); formLayout->addRow(tr("Matrix ID"), userEdit); formLayout->addRow(tr("Password"), passwordEdit); formLayout->addRow(tr("Device name"), initialDeviceName); formLayout->addRow(tr("Connect to server"), serverEdit); formLayout->addRow(saveTokenCheck); } LoginDialog::~LoginDialog() = default; Connection* LoginDialog::releaseConnection() { return m_connection.take(); } QString LoginDialog::deviceName() const { return initialDeviceName->text(); } bool LoginDialog::keepLoggedIn() const { return saveTokenCheck->isChecked(); } void LoginDialog::apply() { auto url = QUrl::fromUserInput(serverEdit->text()); if (!serverEdit->text().isEmpty() && !serverEdit->text().startsWith("http:")) url.setScheme("https"); // Qt defaults to http (or even ftp for some) m_connection->setHomeserver(url); connect( m_connection.data(), &Connection::connected, this, &Dialog::accept ); connect( m_connection.data(), &Connection::loginError, this, &Dialog::applyFailed); m_connection->connectToServer(userEdit->text(), passwordEdit->text(), initialDeviceName->text()); } Quaternion-0.0.9.4c/client/logindialog.h000066400000000000000000000044741347141001300200740ustar00rootroot00000000000000/************************************************************************** * * * Copyright (C) 2015 Felix Rohrbach * * * * 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 . * * * **************************************************************************/ #pragma once #include "dialog.h" class QLineEdit; class QCheckBox; namespace QMatrixClient { class AccountSettings; class Connection; } class LoginDialog : public Dialog { Q_OBJECT public: explicit LoginDialog(QWidget* parent = nullptr, const QStringList& knownAccounts = {}); explicit LoginDialog(QWidget* parent, const QMatrixClient::AccountSettings& reloginData); void setup(); ~LoginDialog() override; QMatrixClient::Connection* releaseConnection(); QString deviceName() const; bool keepLoggedIn() const; private slots: void apply() override; private: QLineEdit* userEdit; QLineEdit* passwordEdit; QLineEdit* initialDeviceName; QLineEdit* serverEdit; QCheckBox* saveTokenCheck; QScopedPointer m_connection; }; Quaternion-0.0.9.4c/client/main.cpp000066400000000000000000000115411347141001300170540ustar00rootroot00000000000000/************************************************************************** * * * Copyright (C) 2015 Felix Rohrbach * * * * 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 . * * * **************************************************************************/ #include #include #include #include #include #include #include "networksettings.h" #include "mainwindow.h" #include "activitydetector.h" #include int main( int argc, char* argv[] ) { #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) QApplication::setAttribute(Qt::AA_DisableWindowContextHelpButton); #endif QApplication app(argc, argv); QApplication::setOrganizationName(QStringLiteral("QMatrixClient")); QApplication::setApplicationName(QStringLiteral("quaternion")); QApplication::setApplicationDisplayName(QStringLiteral("Quaternion")); QApplication::setApplicationVersion(QStringLiteral("0.0.9.4b")); QMatrixClient::Settings::setLegacyNames( QStringLiteral("Quaternion"), QStringLiteral("quaternion")); // We should not need to do the following, as quitOnLastWindowClosed is // set to "true" by default; might be a bug, see // https://forum.qt.io/topic/71112/application-does-not-quit QObject::connect(&app, &QApplication::lastWindowClosed, []{ qDebug() << "Last window closed!"; QApplication::postEvent(qApp, new QEvent(QEvent::Quit)); }); QCommandLineParser parser; parser.setApplicationDescription(QApplication::translate("main", "Quaternion - an IM client for the Matrix protocol")); parser.addHelpOption(); parser.addVersionOption(); QList options; QCommandLineOption locale { QStringLiteral("locale"), QApplication::translate("main", "Override locale"), QApplication::translate("main", "locale") }; options.append(locale); QCommandLineOption hideMainWindow { QStringLiteral("hide-mainwindow"), QApplication::translate("main", "Hide main window on startup") }; options.append(hideMainWindow); QCommandLineOption debug { QStringLiteral("debug"), QApplication::translate("main", "Display debug information") }; debug.setHidden(true); // FIXME, #415; also, setHidden is obsolete in Qt 5.11 options.append(debug); // Add more command line options before this line if (!parser.addOptions(options)) Q_ASSERT_X(false, __FUNCTION__, "Command line options are improperly defined, fix the code"); parser.process(app); const auto overrideLocale = parser.value(locale); if (!overrideLocale.isEmpty()) { QLocale::setDefault(overrideLocale); qInfo() << "Using locale" << QLocale().name(); } QTranslator qtTranslator; qtTranslator.load(QLocale(), "qt", "_", QLibraryInfo::location(QLibraryInfo::TranslationsPath)); app.installTranslator(&qtTranslator); QTranslator appTranslator; if (!appTranslator.load(QLocale(), "quaternion", "_")) appTranslator.load(QLocale(), "quaternion", "_", QStandardPaths::locate(QStandardPaths::AppLocalDataLocation, "translations", QStandardPaths::LocateDirectory)); app.installTranslator(&appTranslator); QMatrixClient::NetworkSettings().setupApplicationProxy(); MainWindow window; if (parser.isSet(debug)) { qInfo() << "Debug mode enabled"; window.enableDebug(); } ActivityDetector ad(app, window); Q_UNUSED(ad); if (parser.isSet(hideMainWindow)) { qDebug() << "--- Hide time!"; window.hide(); } else { qDebug() << "--- Show time!"; window.show(); } return app.exec(); } Quaternion-0.0.9.4c/client/mainwindow.cpp000066400000000000000000001463121347141001300203110ustar00rootroot00000000000000/************************************************************************** * * * Copyright (C) 2015 Felix Rohrbach * * * * 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 . * * * **************************************************************************/ #include "mainwindow.h" #include "roomlistdock.h" #include "userlistdock.h" #include "chatroomwidget.h" #include "logindialog.h" #include "networkconfigdialog.h" #include "roomdialogs.h" #include "systemtrayicon.h" #include #include #include #include #include #include #ifdef USE_KEYCHAIN #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using QMatrixClient::NetworkAccessManager; using QMatrixClient::AccountSettings; MainWindow::MainWindow() { Connection::setRoomType(); // Bind callbacks to signals from NetworkAccessManager auto nam = NetworkAccessManager::instance(); connect(nam, &QNetworkAccessManager::proxyAuthenticationRequired, this, &MainWindow::proxyAuthenticationRequired); connect(nam, &QNetworkAccessManager::sslErrors, this, &MainWindow::sslErrors); setWindowIcon(QIcon(":/icon.png")); roomListDock = new RoomListDock(this); addDockWidget(Qt::LeftDockWidgetArea, roomListDock); userListDock = new UserListDock(this); addDockWidget(Qt::RightDockWidgetArea, userListDock); chatRoomWidget = new ChatRoomWidget(this); setCentralWidget(chatRoomWidget); connect( chatRoomWidget, &ChatRoomWidget::resourceRequested, this, &MainWindow::resolveResource); connect( chatRoomWidget, &ChatRoomWidget::joinCommandEntered, this, &MainWindow::joinRoom); connect( roomListDock, &RoomListDock::roomSelected, this, &MainWindow::selectRoom); connect( chatRoomWidget, &ChatRoomWidget::showStatusMessage, statusBar(), &QStatusBar::showMessage ); connect( userListDock, &UserListDock::userMentionRequested, chatRoomWidget, &ChatRoomWidget::insertMention); createMenu(); systemTrayIcon = new SystemTrayIcon(this); systemTrayIcon->show(); busyIndicator = new QMovie(QStringLiteral(":/busy.gif")); busyLabel = new QLabel(this); busyLabel->setMovie(busyIndicator); statusBar()->setSizeGripEnabled(false); statusBar()->addPermanentWidget(busyLabel); statusBar()->showMessage(tr("Loading...")); loadSettings(); // Only GUI, account settings will be loaded in invokeLogin busyLabel->show(); busyIndicator->start(); QTimer::singleShot(0, this, SLOT(invokeLogin())); } MainWindow::~MainWindow() { for (auto c: qAsConst(connections)) { c->saveState(); c->stopSync(); // Instead of deleting the connection, merely stop it } for (auto c: qAsConst(logoutOnExit)) logout(c); saveSettings(); } ChatRoomWidget* MainWindow::getChatRoomWidget() const { return chatRoomWidget; } template void summon(QPointer& dlg, DialogArgTs&&... dialogArgs) { if (!dlg) { dlg = new DialogT(std::forward(dialogArgs)...); dlg->setModal(false); dlg->setAttribute(Qt::WA_DeleteOnClose); } dlg->reactivate(); } QAction* MainWindow::addTimelineOptionCheckbox(QMenu* parent, const QString& text, const QString& statusTip, const QString& settingsKey, bool defaultValue) { using QMatrixClient::SettingsGroup; auto action = parent->addAction(text, [this,settingsKey] (bool checked) { SettingsGroup("UI").setValue(settingsKey, checked); chatRoomWidget->setRoom(nullptr); chatRoomWidget->setRoom(currentRoom); }); action->setStatusTip(statusTip); action->setCheckable(true); action->setChecked( SettingsGroup("UI").value(settingsKey, defaultValue).toBool()); return action; } void MainWindow::createMenu() { using QMatrixClient::Settings; // Connection menu connectionMenu = menuBar()->addMenu(tr("&Accounts")); connectionMenu->addAction(QIcon::fromTheme("im-user"), tr("&Login..."), this, [=]{ showLoginWindow(); } ); connectionMenu->addSeparator(); // Account submenus will be added in this place - see addConnection() accountListGrowthPoint = connectionMenu->addSeparator(); // Augment poor Windows users with a handy Ctrl-Q shortcut. static const auto quitShortcut = QSysInfo::productType() == "windows" ? QKeySequence(Qt::CTRL + Qt::Key_Q) : QKeySequence::Quit; connectionMenu->addAction(QIcon::fromTheme("application-exit"), tr("&Quit"), qApp, &QApplication::quit, quitShortcut); // View menu auto viewMenu = menuBar()->addMenu(tr("&View")); openRoomAction = viewMenu->addAction(QIcon::fromTheme("document-open"), tr("Open room..."), [this] { resolveLocator(obtainIdentifier( currentRoom ? currentRoom->connection() : nullptr, QFlag(Room|User), tr("Open room"), tr("Room/user ID, room alias,\n" "or matrix.to link"), tr("Switch to room") )); }); openRoomAction->setStatusTip(tr("Open a room from the room list")); openRoomAction->setShortcut(QKeySequence::Open); openRoomAction->setDisabled(true); viewMenu->addSeparator(); auto dockPanesMenu = viewMenu->addMenu( QIcon::fromTheme("labplot-editvlayout"), tr("Dock &panels", "Panels of the dock, not 'to dock the panels'")); roomListDock->toggleViewAction() ->setStatusTip(tr("Show/hide Rooms dock panel")); dockPanesMenu->addAction(roomListDock->toggleViewAction()); userListDock->toggleViewAction() ->setStatusTip(tr("Show/hide Users dock panel")); dockPanesMenu->addAction(userListDock->toggleViewAction()); viewMenu->addSeparator(); auto showEventsMenu = viewMenu->addMenu(tr("&Display in timeline")); addTimelineOptionCheckbox( showEventsMenu, tr("Normal &join/leave events"), tr("Show join and leave events"), QStringLiteral("show_joinleave"), true ); addTimelineOptionCheckbox( showEventsMenu, tr("&Redacted events"), tr("Show redacted events in the timeline as 'Redacted'" " instead of hiding them entirely"), QStringLiteral("show_redacted") ); addTimelineOptionCheckbox( showEventsMenu, tr("&No-effect activity", "A menu item to show/hide meaningless activity such as redacted spam"), tr("Show/hide meaningless activity (join-leave pairs and redacted events between)"), QStringLiteral("show_spammy") ); viewMenu->addSeparator(); viewMenu->addAction(tr("Edit tags order"), [this] { static const auto SettingsKey = QStringLiteral("tags_order"); QMatrixClient::SettingsGroup sg { QStringLiteral("UI/RoomsDock") }; const auto savedOrder = sg.get(SettingsKey).join('\n'); bool ok; const auto newOrder = QInputDialog::getMultiLineText(this, tr("Edit tags order"), tr("Tags can be wildcarded by * next to dot(s)\n" "Clear the box to reset to defaults\n" "Special tags starting with \"org.qmatrixclient.\" are: %1\n" "User-defined tags should start with \"u.\"") .arg("invite, left, direct, none"), savedOrder, &ok); if (ok) { if (newOrder.isEmpty()) sg.remove(SettingsKey); else if (newOrder != savedOrder) sg.setValue(SettingsKey, newOrder.split('\n')); roomListDock->updateSortingMode(); } }); viewMenu->addAction(QIcon::fromTheme("format-text-blockquote"), tr("Edit quote style"), [this] { QMatrixClient::SettingsGroup sg { "UI" }; const auto type = sg.get("quote_type"); QStringList list; list << tr("Markdown (prepend each line with >)") << tr("Custom (apply regex from the config file)") << tr("Locale's default (%1)") .arg(QLocale().quoteString(tr("Example quote"))); bool ok; const auto newType = QInputDialog::getItem(this, tr("Edit quote style"), tr("Choose the default style of quotes"), list, type, false, &ok); if (ok) sg.setValue("quote_type", list.indexOf(newType)); }); // Room menu auto roomMenu = menuBar()->addMenu(tr("&Room")); roomSettingsAction = roomMenu->addAction(QIcon::fromTheme("user-group-properties"), tr("Change room &settings..."), [this] { static QHash> dlgs; summon(dlgs[currentRoom], currentRoom, this); }); roomSettingsAction->setDisabled(true); roomMenu->addSeparator(); createRoomAction = roomMenu->addAction(QIcon::fromTheme("user-group-new"), tr("Create &new room..."), [this] { static QPointer dlg; summon(dlg, connections, this); }); createRoomAction->setShortcut(QKeySequence::New); createRoomAction->setDisabled(true); dcAction = roomMenu->addAction(QIcon::fromTheme("list-add-user"), tr("&Direct chat..."), [this] { directChat(); }); dcAction->setShortcut(Qt::CTRL + Qt::Key_M); dcAction->setDisabled(true); joinAction = roomMenu->addAction(QIcon::fromTheme("list-add"), tr("&Join room..."), [this] { joinRoom(); } ); joinAction->setShortcut(Qt::CTRL + Qt::Key_J); joinAction->setDisabled(true); roomMenu->addSeparator(); roomMenu->addAction(QIcon::fromTheme("window-close"), tr("&Close current room"), [this] { selectRoom(nullptr); }, QKeySequence::Close); // Settings menu auto settingsMenu = menuBar()->addMenu(tr("&Settings")); // Help menu auto helpMenu = menuBar()->addMenu(tr("&Help")); helpMenu->addAction(QIcon::fromTheme("help-about"), tr("&About"), [=]{ showAboutWindow(); }); { auto notifGroup = new QActionGroup(this); connect(notifGroup, &QActionGroup::triggered, this, [] (QAction* notifAction) { notifAction->setChecked(true); Settings().setValue("UI/notifications", notifAction->data().toString()); }); auto noNotif = notifGroup->addAction(tr("&Highlight only")); noNotif->setData(QStringLiteral("none")); noNotif->setStatusTip(tr("Notifications are entirely suppressed")); auto gentleNotif = notifGroup->addAction(tr("&Non-intrusive")); gentleNotif->setData(QStringLiteral("non-intrusive")); gentleNotif->setStatusTip( tr("Show notifications but do not activate the window")); auto fullNotif = notifGroup->addAction(tr("&Full")); fullNotif->setData(QStringLiteral("intrusive")); fullNotif->setStatusTip( tr("Show notifications and activate the window")); auto notifMenu = settingsMenu->addMenu( QIcon::fromTheme("preferences-desktop-notification"), tr("Notifications")); for (auto a: {noNotif, gentleNotif, fullNotif}) { a->setCheckable(true); notifMenu->addAction(a); } const auto curSetting = Settings().value("UI/notifications", fullNotif->data().toString()); if (curSetting == noNotif->data().toString()) noNotif->setChecked(true); else if (curSetting == gentleNotif->data().toString()) gentleNotif->setChecked(true); else fullNotif->setChecked(true); } { auto layoutGroup = new QActionGroup(this); connect(layoutGroup, &QActionGroup::triggered, this, [this] (QAction* action) { action->setChecked(true); Settings().setValue("UI/timeline_style", action->data().toString()); chatRoomWidget->setRoom(nullptr); chatRoomWidget->setRoom(currentRoom); }); auto defaultLayout = layoutGroup->addAction(tr("Default")); defaultLayout->setStatusTip( tr("The layout with author labels above blocks of messages")); auto xchatLayout = layoutGroup->addAction("XChat"); xchatLayout->setData(QStringLiteral("xchat")); xchatLayout->setStatusTip( tr("The layout with author labels to the left from each message")); auto layoutMenu = settingsMenu->addMenu(QIcon::fromTheme("table"), tr("Timeline layout")); for (auto a: {defaultLayout, xchatLayout}) { a->setCheckable(true); layoutMenu->addAction(a); } const auto curSetting = Settings().value("UI/timeline_style", defaultLayout->data().toString()); if (curSetting == xchatLayout->data().toString()) xchatLayout->setChecked(true); else defaultLayout->setChecked(true); } addTimelineOptionCheckbox( settingsMenu, tr("Use shuttle scrollbar (requires restart)"), tr("Control scroll velocity instead of position with the timeline scrollbar"), QStringLiteral("use_shuttle_dial"), true ); addTimelineOptionCheckbox( settingsMenu, tr("Load full-size images at once"), tr("Automatically download a full-size image instead of a thumbnail"), QStringLiteral("autoload_images"), true ); addTimelineOptionCheckbox( settingsMenu, tr("Close to tray"), tr("Make close button [X] minimize to tray instead of closing main window"), QStringLiteral("close_to_tray"), false ); settingsMenu->addSeparator(); settingsMenu->addAction(QIcon::fromTheme("preferences-system-network"), tr("Configure &network proxy..."), [this] { static QPointer dlg; summon(dlg, this); }); } void MainWindow::loadSettings() { QMatrixClient::SettingsGroup sg("UI/MainWindow"); if (sg.contains("normal_geometry")) setGeometry(sg.value("normal_geometry").toRect()); if (sg.value("maximized").toBool()) showMaximized(); if (sg.contains("window_parts_state")) restoreState(sg.value("window_parts_state").toByteArray()); } void MainWindow::saveSettings() const { QMatrixClient::SettingsGroup sg("UI/MainWindow"); sg.setValue("normal_geometry", normalGeometry()); sg.setValue("maximized", isMaximized()); sg.setValue("window_parts_state", saveState()); sg.sync(); } inline QString accessTokenFileName(const AccountSettings& account) { QString fileName = account.userId(); fileName.replace(':', '_'); return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) % '/' % fileName; } QByteArray MainWindow::loadAccessToken(const AccountSettings& account) { #ifdef USE_KEYCHAIN return loadAccessTokenFromKeyChain(account); #else return loadAccessTokenFromFile(account); #endif } QByteArray MainWindow::loadAccessTokenFromFile(const AccountSettings& account) { QFile accountTokenFile { accessTokenFileName(account) }; if (accountTokenFile.open(QFile::ReadOnly)) { if (accountTokenFile.size() < 1024) return accountTokenFile.readAll(); qWarning() << "File" << accountTokenFile.fileName() << "is" << accountTokenFile.size() << "bytes long - too long for a token, ignoring it."; } qWarning() << "Could not open access token file" << accountTokenFile.fileName(); return {}; } #ifdef USE_KEYCHAIN QByteArray MainWindow::loadAccessTokenFromKeyChain(const AccountSettings& account) { // check for existing token file auto accessToken = loadAccessTokenFromFile(account); if(!accessToken.isEmpty()) { if(QMessageBox::warning(this, tr("Access token file found"), tr("Do you want to migrate the access token for %1 " "from the file to the keychain?").arg(account.userId()), QMessageBox::Yes|QMessageBox::No) == QMessageBox::Yes) { qDebug() << "Migrating the access token from file to the keychain for " << account.userId(); bool removed = false; bool saved = saveAccessTokenToKeyChain(account, accessToken); if(saved) { QFile accountTokenFile{accessTokenFileName(account)}; removed = accountTokenFile.remove(); } if(!(saved && removed)) { qDebug() << "Migrating the access token from the file to the keychain failed"; QMessageBox::warning(this, tr("Couldn't migrate access token"), tr("Quaternion couldn't migrate access token %1 " "from the file to the keychain.").arg(account.userId()), QMessageBox::Close); } } } qDebug() << "Read the access token from the keychain for " << account.userId(); QKeychain::ReadPasswordJob job(qAppName()); job.setAutoDelete(false); job.setKey(account.userId()); QEventLoop loop; QKeychain::ReadPasswordJob::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit); job.start(); loop.exec(); if (job.error()) { qWarning() << "Could not read the access token from the keychain: " << qPrintable(job.errorString()); return {}; } return job.binaryData(); } #endif bool MainWindow::saveAccessToken(const AccountSettings& account, const QByteArray& accessToken) { #ifdef USE_KEYCHAIN return saveAccessTokenToKeyChain(account, accessToken); #else return saveAccessTokenToFile(account, accessToken); #endif } bool MainWindow::saveAccessTokenToFile(const AccountSettings& account, const QByteArray& accessToken) { // (Re-)Make a dedicated file for access_token. QFile accountTokenFile { accessTokenFileName(account) }; accountTokenFile.remove(); // Just in case auto fileDir = QFileInfo(accountTokenFile).dir(); if (!( (fileDir.exists() || fileDir.mkpath(".")) && accountTokenFile.open(QFile::WriteOnly) )) { QMessageBox::warning(this, tr("Couldn't open a file to save access token"), tr("Quaternion couldn't open a file to write the" " access token to. You're logged in but will have" " to provide your password again when you restart" " the application."), QMessageBox::Close); } else { // Try to restrict access rights to the file. The below is useless // on Windows: FAT doesn't control access at all and NTFS is // incompatible with the UNIX perms model used by Qt. If the attempt // didn't have the effect, at least ask the user if it's fine to save // the token to a file readable by others. // TODO: use system-specific API to ensure proper access. if ((accountTokenFile.setPermissions( QFile::ReadOwner|QFile::WriteOwner) && !(accountTokenFile.permissions() & (QFile::ReadGroup|QFile::ReadOther))) || QMessageBox::warning(this, tr("Couldn't set access token file permissions"), tr("Quaternion couldn't restrict permissions on the" " access token file. Do you still want to save" " the access token to it?"), QMessageBox::Yes|QMessageBox::No ) == QMessageBox::Yes) { accountTokenFile.write(accessToken); return true; } } return false; } #ifdef USE_KEYCHAIN bool MainWindow::saveAccessTokenToKeyChain(const AccountSettings& account, const QByteArray& accessToken) { qDebug() << "Save the access token to the keychain for " << account.userId(); QKeychain::WritePasswordJob job(qAppName()); job.setAutoDelete(false); job.setKey(account.userId()); job.setBinaryData(accessToken); QEventLoop loop; QKeychain::WritePasswordJob::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit); job.start(); loop.exec(); if (job.error()) { qWarning() << "Could not save access token to the keychain: " << qPrintable(job.errorString()); const auto button = QMessageBox::warning(this, tr("Couldn't save access token"), tr("Quaternion couldn't save the access token to the keychain." " Do you want to save the access token to file %1?").arg(accessTokenFileName(account)), QMessageBox::Yes|QMessageBox::No); if (button == QMessageBox::Yes) { return saveAccessTokenToFile(account, accessToken); } else { return false; } } return true; } #endif void MainWindow::enableDebug() { chatRoomWidget->enableDebug(); } void MainWindow::addConnection(Connection* c, const QString& deviceName) { Q_ASSERT_X(c, __FUNCTION__, "Attempt to add a null connection"); using Room = QMatrixClient::Room; c->setLazyLoading(true); connections.push_back(c); roomListDock->addConnection(c); connect( c, &Connection::syncDone, this, [=] { gotEvents(c); // Borrowed the logic from Quiark's code in Tensor to cache not too // aggressively and not on the first sync. The static variable instance // is created per-closure, meaning per-connection (which is why this // code is not in gotEvents() ). static int counter = 0; if (++counter % 17 == 2) c->saveState(); } ); connect( c, &Connection::loggedOut, this, [=] { statusBar()->showMessage(tr("Logged out as %1").arg(c->userId()), 3000); dropConnection(c); }); connect( c, &Connection::networkError, this, [=]{ networkError(c); } ); connect( c, &Connection::syncError, this, [this,c] (const QString& message, const QString& details) { QMessageBox msgBox(QMessageBox::Warning, tr("Sync failed"), connections.size() > 1 ? tr("The last sync of account %1 has failed with error: %2") .arg(c->userId(), message) : tr("The last sync has failed with error: %1").arg(message), QMessageBox::Retry|QMessageBox::Cancel, this); msgBox.setTextFormat(Qt::PlainText); msgBox.setDefaultButton(QMessageBox::Retry); msgBox.setInformativeText(tr( "Clicking 'Retry' will attempt to resume synchronisation;\n" "Clicking 'Cancel' will stop further synchronisation of this " "account until logout or Quaternion restart.")); msgBox.setDetailedText(details); if (msgBox.exec() == QMessageBox::Retry) getNewEvents(c); }); using namespace QMatrixClient; connect( c, &Connection::requestFailed, this, [this] (BaseJob* job) { if (job->isBackground()) return; auto message = job->error() == BaseJob::UserConsentRequiredError ? tr("Before this server can process your information, you have" " to agree with its terms and conditions; please click the" " button below to open the web page where you can do that") : prettyPrint(job->errorString()); QMessageBox msgBox(QMessageBox::Warning, job->statusCaption(), message, QMessageBox::Close, this); msgBox.setTextFormat(Qt::RichText); msgBox.setDetailedText( tr("Request URL: %1\nResponse:\n%2") .arg(job->requestUrl().toDisplayString(), job->rawDataSample())); QPushButton* openUrlButton = nullptr; if (job->errorUrl().isEmpty()) msgBox.setDefaultButton(QMessageBox::Close); else { openUrlButton = msgBox.addButton(tr("Open web page"), QMessageBox::ActionRole); openUrlButton->setDefault(true); } msgBox.exec(); if (msgBox.clickedButton() == openUrlButton) QDesktopServices::openUrl(job->errorUrl()); }); connect( c, &Connection::loginError, this, [=](const QString& msg){ loginError(c, msg); } ); connect( c, &Connection::newRoom, systemTrayIcon, &SystemTrayIcon::newRoom ); connect( c, &Connection::createdRoom, this, &MainWindow::selectRoom); connect( c, &Connection::joinedRoom, this, [this] (Room* r, Room* prev) { if (currentRoom == prev) selectRoom(r); }); connect( c, &Connection::directChatAvailable, this, [this] (Room* r) { selectRoom(r); statusBar()->showMessage("Direct chat opened", 2000); }); connect( c, &Connection::aboutToDeleteRoom, this, [this] (Room* r) { if (currentRoom == r) selectRoom(nullptr); }); QString accountCaption = c->userId(); if (!deviceName.isEmpty()) accountCaption += '/' % deviceName; QString menuCaption = accountCaption; if (connections.size() < 10) menuCaption.prepend('&' % QString::number(connections.size()) % ' '); auto accountMenu = new QMenu(menuCaption, connectionMenu); accountMenu->addAction(QIcon::fromTheme("view-certificate"), tr("Show &access token"), this, [=] { const QString aToken = c->accessToken(); auto accountTokenBox = new QMessageBox(QMessageBox::Information, tr("Access token for %1").arg(accountCaption), tr("Your access token is %1...%2;" " click \"Show details...\" for the full token") .arg(aToken.left(5), aToken.right(5)), QMessageBox::Close, this, Qt::Dialog); accountTokenBox->setModal(false); accountTokenBox->setTextInteractionFlags( Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse); accountTokenBox->setDetailedText(aToken); accountTokenBox->setAttribute(Qt::WA_DeleteOnClose); accountTokenBox->show(); }); accountMenu->addAction(QIcon::fromTheme("system-log-out"), tr("&Logout"), this, [=] { logout(c); }); auto menuAction = connectionMenu->insertMenu(accountListGrowthPoint, accountMenu); connect( c, &Connection::destroyed, connectionMenu, [this, menuAction] { connectionMenu->removeAction(menuAction); }); openRoomAction->setEnabled(true); createRoomAction->setEnabled(true); dcAction->setEnabled(true); joinAction->setEnabled(true); getNewEvents(c); } void MainWindow::dropConnection(Connection* c) { Q_ASSERT_X(c, __FUNCTION__, "Attempt to drop a null connection"); if (currentRoom && currentRoom->connection() == c) selectRoom(nullptr); connections.removeOne(c); logoutOnExit.removeOne(c); openRoomAction->setDisabled(connections.isEmpty()); createRoomAction->setDisabled(connections.isEmpty()); dcAction->setDisabled(connections.isEmpty()); joinAction->setDisabled(connections.isEmpty()); Q_ASSERT(!connections.contains(c) && !logoutOnExit.contains(c) && !c->syncJob()); c->deleteLater(); } void MainWindow::showFirstSyncIndicator() { busyLabel->show(); busyIndicator->start(); statusBar()->showMessage("Syncing, please wait"); } void MainWindow::showLoginWindow(const QString& statusMessage) { const auto& allKnownAccounts = QMatrixClient::SettingsGroup("Accounts").childGroups(); QStringList loggedOffAccounts; for (const auto& a: allKnownAccounts) { AccountSettings as { a }; // Skip accounts mentioned in active connections if ([&] { for (auto c: connections) if (as.userId() == c->userId()) return false; return true; }()) loggedOffAccounts.push_back(a); } LoginDialog dialog(this, loggedOffAccounts); dialog.setStatusMessage(statusMessage); if (dialog.exec()) processLogin(dialog); } void MainWindow::showLoginWindow(const QString& statusMessage, AccountSettings& reloginAccount) { LoginDialog dialog { this, reloginAccount }; dialog.setStatusMessage(statusMessage); if (dialog.exec()) processLogin(dialog); else { reloginAccount.clearAccessToken(); QFile(accessTokenFileName(reloginAccount)).remove(); // XXX: Maybe even remove the account altogether as below? // QMatrixClient::SettingsGroup("Accounts").remove(reloginAccount.userId()); } } void MainWindow::processLogin(LoginDialog& dialog) { auto connection = dialog.releaseConnection(); AccountSettings account(connection->userId()); account.setKeepLoggedIn(dialog.keepLoggedIn()); account.clearAccessToken(); // Drop the legacy - just in case account.setHomeserver(connection->homeserver()); account.setDeviceId(connection->deviceId()); account.setDeviceName(dialog.deviceName()); if (dialog.keepLoggedIn()) { if (!saveAccessToken(account, connection->accessToken())) qWarning() << "Couldn't save access token"; } else logoutOnExit.push_back(connection); account.sync(); showFirstSyncIndicator(); auto deviceName = dialog.deviceName(); const auto it = std::find_if(connections.cbegin(), connections.cend(), [connection] (Connection* c) { return c->userId() == connection->userId(); }); if (it != connections.cend()) { int ret = QMessageBox::warning(this, tr("Logging in into a logged in account"), tr("You're trying to log in into an account that's " "already logged in. Do you want to continue?"), QMessageBox::Yes, QMessageBox::No); if (ret == QMessageBox::Yes) deviceName += "-" + connection->deviceId(); else return; } addConnection(connection, deviceName); } void MainWindow::showAboutWindow() { Dialog aboutDialog(tr("About Quaternion"), QDialogButtonBox::Close, this, Dialog::NoStatusLine); auto* tabWidget = new QTabWidget(); { auto *aboutPage = new QWidget(); tabWidget->addTab(aboutPage, tr("&About")); auto* layout = new QVBoxLayout(aboutPage); auto* imageLabel = new QLabel(); imageLabel->setPixmap(QPixmap(":/icon.png")); imageLabel->setAlignment(Qt::AlignHCenter); layout->addWidget(imageLabel); auto* labelString = new QLabel("

" + QApplication::applicationDisplayName() + " v" + QApplication::applicationVersion() + "

"); labelString->setAlignment(Qt::AlignHCenter); layout->addWidget(labelString); auto* linkLabel = new QLabel("" + tr("Web page") + ""); linkLabel->setAlignment(Qt::AlignHCenter); linkLabel->setOpenExternalLinks(true); layout->addWidget(linkLabel); layout->addWidget( new QLabel(tr("Copyright (C) 2018 QMatrixClient project."))); #ifdef GIT_SHA1 auto* commitLabel = new QLabel(tr("Built from Git, commit SHA:") + '\n' + QStringLiteral(GIT_SHA1)); commitLabel->setTextInteractionFlags(Qt::TextSelectableByKeyboard| Qt::TextSelectableByMouse); layout->addWidget(commitLabel); #endif #ifdef LIB_GIT_SHA1 auto* libCommitLabel = new QLabel(new QLabel(tr("Library commit SHA:") + '\n' + QStringLiteral(LIB_GIT_SHA1))); libCommitLabel->setTextInteractionFlags(Qt::TextSelectableByKeyboard| Qt::TextSelectableByMouse); layout->addWidget(libCommitLabel); #endif } { auto* thanksLabel = new QLabel( tr("Original project author: %1") .arg("Felix Rohrbach") + "
" + tr("Project leader: %1") .arg("Kitsune Ral") + "

" + tr("Contributors:") + "
" + "" + tr("Quaternion contributors @ GitHub") + "
" + "" + tr("libQMatrixClient contributors @ GitHub") + "
" + "" + tr("Quaternion translators @ Lokalise.co") + "
" + tr("Special thanks to %1 for all the testing effort") .arg("nepugia") + "

" + tr("Made with:") + "
" + "Qt 5
" "Qt Creator
" "CLion
" "Lokalise.co" ); thanksLabel->setTextInteractionFlags(Qt::TextSelectableByKeyboard| Qt::TextBrowserInteraction); thanksLabel->setOpenExternalLinks(true); tabWidget->addTab(thanksLabel, tr("&Thanks")); } aboutDialog.addWidget(tabWidget); aboutDialog.exec(); } void MainWindow::invokeLogin() { using namespace QMatrixClient; const auto accounts = SettingsGroup("Accounts").childGroups(); bool autoLoggedIn = false; for(const auto& accountId: accounts) { AccountSettings account { accountId }; if (!account.homeserver().isEmpty()) { auto accessToken = loadAccessToken(account); if (accessToken.isEmpty()) { // Try to look in the legacy location (QSettings) and if found, // migrate it from there to a file. accessToken = account.accessToken().toLatin1(); if (accessToken.isEmpty()) continue; // No access token anywhere, no autologin saveAccessToken(account, accessToken); account.clearAccessToken(); // Clean the old place } autoLoggedIn = true; auto c = new Connection(account.homeserver()); auto deviceName = account.deviceName(); connect(c, &Connection::connected, this, [=] { c->loadState(); addConnection(c, deviceName); }); c->connectWithToken(account.userId(), accessToken, account.deviceId()); } } if (autoLoggedIn) showFirstSyncIndicator(); else showLoginWindow(tr("Welcome to Quaternion")); } void MainWindow::loginError(Connection* c, const QString& message) { Q_ASSERT_X(c, __FUNCTION__, "Login error on a null connection"); AccountSettings as { c->userId() }; c->stopSync(); // Security over convenience: before allowing back in, remove // the connection from the UI emit c->loggedOut(); // Short circuit login error to logged-out event showLoginWindow(message, as); } void MainWindow::logout(Connection* c) { Q_ASSERT_X(c, __FUNCTION__, "Logout on a null connection"); QFile(accessTokenFileName(AccountSettings(c->userId()))).remove(); #ifdef USE_KEYCHAIN QKeychain::DeletePasswordJob job(qAppName()); job.setAutoDelete(false); job.setKey(c->userId()); QEventLoop loop; QKeychain::DeletePasswordJob::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit); job.start(); loop.exec(); if (job.error()) { qWarning() << "Could not delete access token from the keychain: " << qPrintable(job.errorString()); QMessageBox::warning(this, tr("Couldn't delete access token"), tr("Quaternion couldn't delete the access token from the keychain."), QMessageBox::Close); } #endif c->logout(); } void MainWindow::resolveLocator(const Locator& l, const QString& action) { if (!l.account) return; auto idOrAlias = l.identifier; idOrAlias.remove(QRegularExpression("^https://matrix.to/#/")); if (idOrAlias.startsWith('@')) { if (auto* user = l.account->user(idOrAlias)) { if (action == "mention") chatRoomWidget->insertMention(user); else if (QMessageBox::question(this, tr("Open direct chat?"), tr("Open direct chat with user %1?") .arg(user->fullName())) == QMessageBox::Yes) l.account->requestDirectChat(user); } else QMessageBox::warning(this, tr("Malformed user id"), tr("%1 is not a correct user id").arg(idOrAlias), QMessageBox::Close, QMessageBox::Close); return; } auto* room = idOrAlias.startsWith('!') ? l.account->room(idOrAlias) : l.account->roomByAlias(idOrAlias); if (room) selectRoom(room); else QMessageBox::warning(this, tr("Room not found"), tr("There's no room %1 in the room list." " Check the spelling and the account.") .arg(idOrAlias)); } // FIXME: This should be decommisionned and inlined once we stop supporting // legacy compilers that have BROKEN_INITIALIZER_LISTS inline Locator makeLocator(QMatrixClient::Connection* c, QString id) { #ifdef BROKEN_INITIALIZER_LISTS Locator l; l.account = c; l.identifier = std::move(id); return l; #else return { c, std::move(id) }; #endif } void MainWindow::resolveResource(const QString& idOrUri, const QString& action) { if (idOrUri.isEmpty()) return; auto* defaultConnection = currentRoom ? currentRoom->connection() : connections.size() == 1 ? connections.front() : nullptr; resolveLocator(makeLocator( action == "mention" ? defaultConnection : chooseConnection(defaultConnection, tr("Confirm your account to open %1") .arg(idOrUri)) , idOrUri), action); } void MainWindow::selectRoom(QMatrixClient::Room* r) { if (r) qDebug() << "Opening room" << r->objectName(); else if (currentRoom) qDebug() << "Closing room" << currentRoom->objectName(); QElapsedTimer et; et.start(); if (currentRoom) disconnect(currentRoom, &QuaternionRoom::displaynameChanged, this, nullptr); currentRoom = static_cast(r); setWindowTitle(r ? r->displayName() : QString()); if (currentRoom) connect(currentRoom, &QuaternionRoom::displaynameChanged, this, [this] { setWindowTitle(currentRoom->displayName()); }); chatRoomWidget->setRoom(currentRoom); roomListDock->setSelectedRoom(currentRoom); userListDock->setRoom(currentRoom); roomSettingsAction->setEnabled(r != nullptr); if (r && !isActiveWindow()) { show(); activateWindow(); } qDebug().noquote() << et << "to " << (r ? "select room " + r->canonicalAlias() : "close the room"); } MainWindow::Connection* MainWindow::chooseConnection(Connection* connection, const QString& prompt) { if (connections.isEmpty()) { if (connection) return connection; QMessageBox::warning(this, tr("No connections"), tr("Please connect to a server first"), QMessageBox::Close, QMessageBox::Close); return nullptr; } if (connections.size() == 1) return connections.front(); QStringList names; names.reserve(connections.size()); int defaultIdx = -1; for (auto c: qAsConst(connections)) { names.push_back(c->userId()); if (c == connection) defaultIdx = names.size() - 1; } bool ok = false; const auto choice = QInputDialog::getItem(this, tr("Confirm account"), prompt, names, defaultIdx, false, &ok); if (!ok || choice.isEmpty()) return nullptr; for (auto c: qAsConst(connections)) if (c->userId() == choice) { connection = c; break; } Q_ASSERT(connection); return connection; } Locator MainWindow::obtainIdentifier(Connection* initialConn, QFlags completionType, const QString& prompt, const QString& label, const QString& actionName) { if (connections.isEmpty()) { QMessageBox::warning(this, tr("No connections"), tr("Please connect to a server first"), QMessageBox::Close, QMessageBox::Close); return {}; } Dialog dlg(prompt, this, Dialog::NoStatusLine, actionName, Dialog::NoExtraButtons); auto* account = new QComboBox(&dlg); auto* identifier = new QLineEdit(&dlg); for (auto* c: connections) { account->addItem(c->userId(), QVariant::fromValue(c)); if (c == initialConn) account->setCurrentIndex(account->count() - 1); } // Lay out controls auto* layout = dlg.addLayout(); if (connections.size() > 1) { layout->addRow(tr("Account"), account); account->setFocus(); } else { account->setCurrentIndex(0); // The only available account->hide(); // #523 identifier->setFocus(); } layout->addRow(label, identifier); setCompleter(identifier, connections[account->currentIndex()], completionType); auto* okButton = dlg.button(QDialogButtonBox::Ok); okButton->setDisabled(identifier->text().isEmpty()); connect(account, QOverload::of(&QComboBox::currentIndexChanged), [&] (int index) { setCompleter(identifier, connections[index], completionType); }); connect(identifier, &QLineEdit::textChanged, &dlg, [identifier,okButton] { okButton->setDisabled(identifier->text().isEmpty()); }); if (dlg.exec() == QDialog::Accepted) { return makeLocator(account->currentData().value(), identifier->text()); } return {}; } void MainWindow::setCompleter(QLineEdit* edit, Connection* connection, QFlags type) { QStringList list; if (type & Room) { for (auto* room: connection->roomMap()) list << room->canonicalAlias(); } if (type & User) { for (auto* user: connection->users()) list << user->id(); } list.sort(); auto* completer = new QCompleter(list); edit->setCompleter(completer); } void MainWindow::joinRoom(const QString& roomAlias) { auto* defaultConnection = currentRoom ? currentRoom->connection() : connections.size() == 1 ? connections.front() : nullptr; auto roomLocator = roomAlias.isEmpty() ? obtainIdentifier(defaultConnection, None, tr("Enter room id or alias"), tr("Room ID (starting with !)\nor alias (starting with #)"), tr("Join")) #ifdef BROKEN_INITIALIZER_LISTS : [=] { Locator l; l.account = chooseConnection(defaultConnection, tr("Confirm account to join %1").arg(roomAlias)); l.identifier = roomAlias; return l; }(); #else : Locator { chooseConnection(defaultConnection, tr("Confirm account to join %1").arg(roomAlias)) , roomAlias }; #endif if (!roomLocator.account) return; // The user cancelled room/connection dialog or no connections using QMatrixClient::BaseJob; auto* job = roomLocator.account->joinRoom(roomLocator.identifier); // Connection::joinRoom() already connected to success() the code that // initialises the room in the library, which in turn causes RoomListModel // to update the room list. So the below connection to success() will be // triggered after all the initialisation have happened. connect(job, &BaseJob::success, this, [this,roomLocator] { statusBar()->showMessage( tr("Joined %1 as %2").arg(roomLocator.identifier, roomLocator.account->userId())); }); } void MainWindow::directChat(const QString& userId) { if (userId.isEmpty()) { auto* defaultConnection = currentRoom ? currentRoom->connection() : connections.size() == 1 ? connections.front() : nullptr; resolveLocator(obtainIdentifier(defaultConnection, User, tr("Enter user id to start direct chat."), tr("User ID (starting with @)"), tr("Start chat") )); } else resolveResource(userId); } void MainWindow::getNewEvents(Connection* c) { Q_ASSERT_X(c, __FUNCTION__, "Attempt to sync on null connection"); c->sync(30*1000); } void MainWindow::gotEvents(Connection* c) { Q_ASSERT_X(c, __FUNCTION__, "Null connection"); if( busyLabel->isVisible() ) { busyLabel->hide(); busyIndicator->stop(); statusBar()->showMessage(tr("Sync completed - have a good chat"), 3000); } getNewEvents(c); } void MainWindow::showMillisToRecon(Connection* c) { // TODO: when there are several connections and they are failing, these // notifications render a mess, fighting for the same status bar. Either // switch to a set of icons in the status bar or find a stacking // notifications engine already instead of the status bar. statusBar()->showMessage( tr("Couldn't connect to the server as %1; will retry within %2 seconds") .arg(c->userId()).arg((c->millisToReconnect() + 999) / 1000)); // Integer ceiling } void MainWindow::networkError(Connection* c) { Q_ASSERT_X(c, __FUNCTION__, "Network error on a null connection"); auto timer = new QTimer(this); timer->start(1000); showMillisToRecon(c); timer->connect(timer, &QTimer::timeout, this, [=] { if (c->millisToReconnect() > 0) showMillisToRecon(c); else { statusBar()->showMessage(tr("Reconnecting..."), 5000); timer->deleteLater(); } }); } void MainWindow::sslErrors(QNetworkReply* reply, const QList& errors) { for (const auto& error: errors) { if (error.error() == QSslError::NoSslSupport) { static bool showMsgBox = true; if (showMsgBox) { QMessageBox msgBox(QMessageBox::Critical, tr("No SSL support"), error.errorString(), QMessageBox::Close, this); msgBox.setInformativeText( tr("Your SSL configuration does not allow Quaternion" " to establish secure connections.")); msgBox.exec(); showMsgBox = false; } return; } QMessageBox msgBox(QMessageBox::Warning, tr("SSL error"), error.errorString(), QMessageBox::Abort|QMessageBox::Ignore, this); if (!error.certificate().isNull()) msgBox.setDetailedText(error.certificate().toText()); if (msgBox.exec() == QMessageBox::Abort) return; NetworkAccessManager::instance()->addIgnoredSslError(error); } reply->ignoreSslErrors(errors); } void MainWindow::proxyAuthenticationRequired(const QNetworkProxy&, QAuthenticator* auth) { Dialog authDialog(tr("Proxy needs authentication"), this, Dialog::NoStatusLine, tr("Authenticate", "Authenticate with the proxy server"), Dialog::NoExtraButtons); auto layout = authDialog.addLayout(); auto userEdit = new QLineEdit; layout->addRow(tr("User name"), userEdit); auto pwdEdit = new QLineEdit; pwdEdit->setEchoMode(QLineEdit::Password); layout->addRow(tr("Password"), pwdEdit); if (authDialog.exec() == QDialog::Accepted) { auth->setUser(userEdit->text()); auth->setPassword(pwdEdit->text()); } } void MainWindow::closeEvent(QCloseEvent* event) { if (QMatrixClient::SettingsGroup("UI") .value("close_to_tray", false).toBool()) { hide(); event->ignore(); } else { event->accept(); } } Quaternion-0.0.9.4c/client/mainwindow.h000066400000000000000000000146501347141001300177550ustar00rootroot00000000000000/************************************************************************** * * * Copyright (C) 2015 Felix Rohrbach * * * * 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 . * * * **************************************************************************/ #pragma once #include namespace QMatrixClient { class Room; class Connection; class AccountSettings; } class RoomListDock; class UserListDock; class ChatRoomWidget; class SystemTrayIcon; class QuaternionRoom; class LoginDialog; class QAction; class QMenu; class QMenuBar; class QSystemTrayIcon; class QMovie; class QLabel; class QLineEdit; class QNetworkReply; class QSslError; class QNetworkProxy; class QAuthenticator; struct Locator { QMatrixClient::Connection* account = nullptr; QString identifier; //< Room id, room alias, or user id }; class MainWindow: public QMainWindow { Q_OBJECT public: using Connection = QMatrixClient::Connection; MainWindow(); ~MainWindow() override; void enableDebug(); void addConnection(Connection* c, const QString& deviceName); void dropConnection(Connection* c); ChatRoomWidget* getChatRoomWidget() const; void resolveLocator(const Locator& l, const QString& action = {}); public slots: void resolveResource(const QString& idOrUri, const QString& action = {}); void selectRoom(QMatrixClient::Room* r); private slots: void invokeLogin(); void joinRoom(const QString& roomAlias = {}); void directChat(const QString& userId = {}); void getNewEvents(Connection* c); void gotEvents(Connection* c); void loginError(Connection* c, const QString& message = {}); void networkError(Connection* c); void sslErrors(QNetworkReply* reply, const QList& errors); void proxyAuthenticationRequired(const QNetworkProxy& /* unused */, QAuthenticator* auth); void showLoginWindow(const QString& statusMessage = {}); void showLoginWindow(const QString& statusMessage, QMatrixClient::AccountSettings& reloginAccount); void showAboutWindow(); void logout(Connection* c); private: enum CompletionType { None, Room, User }; QVector connections; QVector logoutOnExit; RoomListDock* roomListDock = nullptr; UserListDock* userListDock = nullptr; ChatRoomWidget* chatRoomWidget = nullptr; QMovie* busyIndicator = nullptr; QLabel* busyLabel = nullptr; QMenu* connectionMenu = nullptr; QAction* accountListGrowthPoint = nullptr; QAction* openRoomAction = nullptr; QAction* roomSettingsAction = nullptr; QAction* createRoomAction = nullptr; QAction* dcAction = nullptr; QAction* joinAction = nullptr; SystemTrayIcon* systemTrayIcon = nullptr; // FIXME: This will be a problem when we get ability to show // several rooms at once. QuaternionRoom* currentRoom = nullptr; void createMenu(); QAction* addTimelineOptionCheckbox(QMenu* parent, const QString& text, const QString& statusTip, const QString& settingsKey, bool defaultValue = false); void showFirstSyncIndicator(); void loadSettings(); void saveSettings() const; void processLogin(LoginDialog& dialog); QByteArray loadAccessToken(const QMatrixClient::AccountSettings& account); QByteArray loadAccessTokenFromFile(const QMatrixClient::AccountSettings& account); QByteArray loadAccessTokenFromKeyChain(const QMatrixClient::AccountSettings &account); bool saveAccessToken(const QMatrixClient::AccountSettings& account, const QByteArray& accessToken); bool saveAccessTokenToFile(const QMatrixClient::AccountSettings& account, const QByteArray& accessToken); bool saveAccessTokenToKeyChain(const QMatrixClient::AccountSettings& account, const QByteArray& accessToken); Connection* chooseConnection(Connection* connection, const QString& prompt); void showMillisToRecon(Connection* c); /// Asks a user to pick an account and enter the Matrix identifier /*! * The identifier can be a room id or alias (to join/open rooms) or * a user MXID (e.g., to open direct chats or user profiles). * \param initialConn initially selected account, if there are several * \param completionType - type of completion * \param prompt - dialog box title * \param label - the text next to the identifier field (e.g., "User ID") * \param actionName - the text on the accepting button */ Locator obtainIdentifier(Connection* initialConn, QFlags completionType, const QString& prompt, const QString& label, const QString& actionName); void setCompleter(QLineEdit* edit, Connection* connection, QFlags type); void closeEvent(QCloseEvent* event) override; }; Quaternion-0.0.9.4c/client/models/000077500000000000000000000000001347141001300167055ustar00rootroot00000000000000Quaternion-0.0.9.4c/client/models/abstractroomordering.cpp000066400000000000000000000034411347141001300236450ustar00rootroot00000000000000/****************************************************************************** * Copyright (C) 2018-2019 QMatrixClient Project * * 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 . * */ #include "abstractroomordering.h" #include "roomlistmodel.h" #include using namespace std::placeholders; AbstractRoomOrdering::AbstractRoomOrdering(RoomListModel* m) : QObject(m) { } AbstractRoomOrdering::groupLessThan_closure_t AbstractRoomOrdering::groupLessThanFactory() const { return std::bind(&AbstractRoomOrdering::groupLessThan, this, _1, _2); } AbstractRoomOrdering::roomLessThan_closure_t AbstractRoomOrdering::roomLessThanFactory(const QVariant& group) const { return std::bind(&AbstractRoomOrdering::roomLessThan, this, group, _1, _2); } void AbstractRoomOrdering::updateGroups(Room* room) { model()->updateGroups(room); } RoomListModel* AbstractRoomOrdering::model() const { return static_cast(parent()); } Quaternion-0.0.9.4c/client/models/abstractroomordering.h000066400000000000000000000075721347141001300233230ustar00rootroot00000000000000/****************************************************************************** * Copyright (C) 2018-2019 QMatrixClient Project * * 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 . * */ #pragma once #include #include struct RoomGroup { QVariant key; QVector rooms; bool operator==(const RoomGroup& other) const { return key == other.key; } bool operator!=(const RoomGroup& other) const { return !(*this == other); } bool operator==(const QVariant& otherCaption) const { return key == otherCaption; } bool operator!=(const QVariant& otherCaption) const { return !(*this == otherCaption); } friend bool operator==(const QVariant& otherCaption, const RoomGroup& group) { return group == otherCaption; } friend bool operator!=(const QVariant& otherCaption, const RoomGroup& group) { return !(group == otherCaption); } }; using RoomGroups = QVector; class RoomListModel; class AbstractRoomOrdering : public QObject { Q_OBJECT public: using Room = QMatrixClient::Room; using Connection = QMatrixClient::Connection; using groups_t = QVariantList; explicit AbstractRoomOrdering(RoomListModel* m); public: // Overridables /// Returns human-readable name of the room ordering virtual QString orderingName() const = 0; /// Returns human-readable room group caption virtual QVariant groupLabel(const RoomGroup& g) const = 0; /// Orders a group against a key of another group virtual bool groupLessThan(const RoomGroup& g1, const QVariant& g2key) const = 0; /// Orders two rooms within one group virtual bool roomLessThan(const QVariant& group, const Room* r1, const Room* r2) const = 0; /// Returns the full list of room groups virtual groups_t roomGroups(const Room* room) const = 0; /// Connects order updates to signals from a new Matrix connection virtual void connectSignals(Connection* connection) = 0; /// Connects order updates to signals from a new Matrix room virtual void connectSignals(Room* room) = 0; public: using groupLessThan_closure_t = std::function; /// Returns a closure that invokes this->groupLessThan() groupLessThan_closure_t groupLessThanFactory() const; using roomLessThan_closure_t = std::function; /// Returns a closure that invokes this->roomLessThan in a given group roomLessThan_closure_t roomLessThanFactory(const QVariant& group) const; protected slots: /// A facade for derived classes to trigger RoomListModel::updateGroups void updateGroups(Room* room); protected: RoomListModel* model() const; }; Quaternion-0.0.9.4c/client/models/messageeventmodel.cpp000066400000000000000000000676211347141001300231340ustar00rootroot00000000000000/************************************************************************** * * * Copyright (C) 2015 Felix Rohrbach * * * * 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 . * * * **************************************************************************/ #include "messageeventmodel.h" #include #include // for qmlRegisterType() #include "../quaternionroom.h" #include #include #include #include #include #include #include #include enum EventRoles { EventTypeRole = Qt::UserRole + 1, EventIdRole, TimeRole, SectionRole, AboveSectionRole, AuthorRole, AboveAuthorRole, ContentRole, ContentTypeRole, HighlightRole, ReadMarkerRole, SpecialMarksRole, LongOperationRole, AnnotationRole, // For debugging EventResolvedTypeRole, }; QHash MessageEventModel::roleNames() const { QHash roles = QAbstractItemModel::roleNames(); roles[EventTypeRole] = "eventType"; roles[EventIdRole] = "eventId"; roles[TimeRole] = "time"; roles[SectionRole] = "section"; roles[AboveSectionRole] = "aboveSection"; roles[AuthorRole] = "author"; roles[AboveAuthorRole] = "aboveAuthor"; roles[ContentRole] = "content"; roles[ContentTypeRole] = "contentType"; roles[HighlightRole] = "highlight"; roles[ReadMarkerRole] = "readMarker"; roles[SpecialMarksRole] = "marks"; roles[LongOperationRole] = "progressInfo"; roles[AnnotationRole] = "annotation"; roles[EventResolvedTypeRole] = "eventResolvedType"; return roles; } MessageEventModel::MessageEventModel(QObject* parent) : QAbstractListModel(parent) { using namespace QMatrixClient; qmlRegisterType(); qRegisterMetaType(); qmlRegisterUncreatableType("QMatrixClient", 1, 0, "EventStatus", "EventStatus is not an creatable type"); } void MessageEventModel::changeRoom(QuaternionRoom* room) { if (room == m_currentRoom) return; beginResetModel(); if( m_currentRoom ) { m_currentRoom->disconnect( this ); qDebug() << "Disconnected from" << m_currentRoom->id(); } m_currentRoom = room; if( room ) { lastReadEventId = room->readMarkerEventId(); using namespace QMatrixClient; connect(m_currentRoom, &Room::aboutToAddNewMessages, this, [=](RoomEventsRange events) { beginInsertRows({}, timelineBaseIndex(), timelineBaseIndex() + int(events.size()) - 1); }); connect(m_currentRoom, &Room::aboutToAddHistoricalMessages, this, [=](RoomEventsRange events) { if (rowCount() > 0) rowBelowInserted = rowCount() - 1; // See #312 beginInsertRows({}, rowCount(), rowCount() + int(events.size()) - 1); }); connect(m_currentRoom, &Room::addedMessages, this, [=] (int lowest, int biggest) { endInsertRows(); if (biggest < m_currentRoom->maxTimelineIndex()) { auto rowBelowInserted = m_currentRoom->maxTimelineIndex() - biggest + timelineBaseIndex() - 1; refreshEventRoles(rowBelowInserted, {AboveAuthorRole, AboveSectionRole}); } for (auto i = m_currentRoom->maxTimelineIndex() - biggest; i <= m_currentRoom->maxTimelineIndex() - lowest; ++i) refreshLastUserEvents(i); }); connect(m_currentRoom, &Room::pendingEventAboutToAdd, this, [this] { beginInsertRows({}, 0, 0); }); connect(m_currentRoom, &Room::pendingEventAdded, this, &MessageEventModel::endInsertRows); connect(m_currentRoom, &Room::pendingEventAboutToMerge, this, [this] (RoomEvent*, int i) { if (i == 0) return; // No need to move anything, just refresh movingEvent = true; // Reverse i because row 0 is bottommost in the model const auto row = timelineBaseIndex() - i - 1; auto moveBegan = beginMoveRows({}, row, row, {}, timelineBaseIndex()); Q_ASSERT(moveBegan); }); connect(m_currentRoom, &Room::pendingEventMerged, this, [this] { if (movingEvent) { endMoveRows(); movingEvent = false; } refreshRow(timelineBaseIndex()); // Refresh the looks refreshLastUserEvents(0); if (m_currentRoom->timelineSize() > 1) // Refresh above refreshEventRoles(timelineBaseIndex() + 1, {ReadMarkerRole}); if (timelineBaseIndex() > 0) // Refresh below, see #312 refreshEventRoles(timelineBaseIndex() - 1, {AboveAuthorRole, AboveSectionRole}); }); connect(m_currentRoom, &Room::pendingEventChanged, this, &MessageEventModel::refreshRow); connect(m_currentRoom, &Room::pendingEventAboutToDiscard, this, [this] (int i) { beginRemoveRows({}, i, i); }); connect(m_currentRoom, &Room::pendingEventDiscarded, this, &MessageEventModel::endRemoveRows); connect(m_currentRoom, &Room::readMarkerMoved, this, [this] { refreshEventRoles( std::exchange(lastReadEventId, m_currentRoom->readMarkerEventId()), {ReadMarkerRole}); refreshEventRoles(lastReadEventId, {ReadMarkerRole}); }); connect(m_currentRoom, &Room::replacedEvent, this, [this] (const RoomEvent* newEvent) { refreshLastUserEvents( refreshEvent(newEvent->id()) - timelineBaseIndex()); }); connect(m_currentRoom, &Room::fileTransferProgress, this, &MessageEventModel::refreshEvent); connect(m_currentRoom, &Room::fileTransferCompleted, this, &MessageEventModel::refreshEvent); connect(m_currentRoom, &Room::fileTransferFailed, this, &MessageEventModel::refreshEvent); connect(m_currentRoom, &Room::fileTransferCancelled, this, &MessageEventModel::refreshEvent); qDebug() << "Connected to room" << room->id() << "as" << room->localUser()->id(); } else lastReadEventId.clear(); endResetModel(); } int MessageEventModel::refreshEvent(const QString& eventId) { return refreshEventRoles(eventId); } void MessageEventModel::refreshRow(int row) { refreshEventRoles(row); } int MessageEventModel::timelineBaseIndex() const { return m_currentRoom ? int(m_currentRoom->pendingEvents().size()) : 0; } void MessageEventModel::refreshEventRoles(int row, const QVector& roles) { const auto idx = index(row); emit dataChanged(idx, idx, roles); } int MessageEventModel::refreshEventRoles(const QString& id, const QVector& roles) { // On 64-bit platforms, difference_type for std containers is long long // but Qt uses int throughout its interfaces; hence casting to int below. int row = -1; // First try pendingEvents because it is almost always very short. const auto pendingIt = m_currentRoom->findPendingEvent(id); if (pendingIt != m_currentRoom->pendingEvents().end()) row = int(pendingIt - m_currentRoom->pendingEvents().begin()); else { const auto timelineIt = m_currentRoom->findInTimeline(id); if (timelineIt == m_currentRoom->timelineEdge()) { qWarning() << "Trying to refresh inexistent event:" << id; return -1; } row = int(timelineIt - m_currentRoom->messageEvents().rbegin()) + timelineBaseIndex(); } refreshEventRoles(row, roles); return row; } inline bool hasValidTimestamp(const QMatrixClient::TimelineItem& ti) { return ti->timestamp().isValid(); } QDateTime MessageEventModel::makeMessageTimestamp( const QuaternionRoom::rev_iter_t& baseIt) const { const auto& timeline = m_currentRoom->messageEvents(); auto ts = baseIt->event()->timestamp(); if (ts.isValid()) return ts; // The event is most likely redacted or just invalid. // Look for the nearest date around and slap zero time to it. using QMatrixClient::TimelineItem; auto rit = std::find_if(baseIt, timeline.rend(), hasValidTimestamp); if (rit != timeline.rend()) return { rit->event()->timestamp().date(), {0,0}, Qt::LocalTime }; auto it = std::find_if(baseIt.base(), timeline.end(), hasValidTimestamp); if (it != timeline.end()) return { it->event()->timestamp().date(), {0,0}, Qt::LocalTime }; // What kind of room is that?.. qCritical() << "No valid timestamps in the room timeline!"; return {}; } QString MessageEventModel::renderDate(const QDateTime& timestamp) const { auto date = timestamp.toLocalTime().date(); if (QMatrixClient::SettingsGroup("UI") .value("banner_human_friendly_date", true).toBool()) { if (date == QDate::currentDate()) return tr("Today"); if (date == QDate::currentDate().addDays(-1)) return tr("Yesterday"); if (date == QDate::currentDate().addDays(-2)) return tr("The day before yesterday"); if (date > QDate::currentDate().addDays(-7)) { // Make sure to capitalise the day name. auto s = QLocale().standaloneDayName(date.dayOfWeek()); if (!s.isEmpty()) s[0] = QLocale().toUpper(s.mid(0,1))[0]; return s; } } return date.toString(Qt::DefaultLocaleShortDate); } bool MessageEventModel::isUserActivityNotable( const QuaternionRoom::rev_iter_t& baseIt) const { const auto& userId = (*baseIt)->isStateEvent() ? (*baseIt)->stateKey() : (*baseIt)->senderId(); // Go up to the nearest join and down to the nearest leave of this author // (limit the lookup to 100 events for the sake of performance); // in this range find out if there's any event from that user besides // joins, leaves and redacted (self- or by somebody else); if there's not, // double-check that there are no redactions and that it's not a single // join or leave. using namespace QMatrixClient; bool joinFound = false, redactionsFound = false; // Find the nearest join of this user above, or a no-nonsense event. for (auto it = baseIt, limit = baseIt + std::min(int(m_currentRoom->timelineEdge() - baseIt), 100); it != limit; ++it) { const auto& e = **it; if (e.senderId() != userId && e.stateKey() != userId) continue; if (e.isRedacted()) { redactionsFound = true; continue; } if (auto* me = it->viewAs()) { if (e.stateKey() != userId) return true; // An action on another member is notable if (!me->isJoin()) continue; joinFound = true; break; } return true; // Consider all other events notable } // Find the nearest leave of this user below, or a no-nonsense event bool leaveFound = false; for (auto it = baseIt.base() - 1, limit = baseIt.base() + std::min(int(m_currentRoom->messageEvents().end() - baseIt.base()), 100); it != limit; ++it) { const auto& e = **it; if (e.senderId() != userId && e.stateKey() != userId) continue; if (e.isRedacted()) { redactionsFound = true; continue; } if (auto* me = it->viewAs()) { if (e.stateKey() != userId) return true; // An action on another member is notable if (!me->isLeave() && me->membership() != MembershipType::Ban) continue; leaveFound = true; break; } return true; } // If we are here, it means that no notable events have been found in // the timeline vicinity, and probably redactions are there. Doesn't look // notable but let's give some benefit of doubt. if (redactionsFound) return false; // Join + redactions or redactions + leave return !(joinFound && leaveFound); // Join + (maybe profile changes) + leave } void MessageEventModel::refreshLastUserEvents(int baseTimelineRow) { if (!m_currentRoom || m_currentRoom->timelineSize() <= baseTimelineRow) return; const auto& timelineBottom = m_currentRoom->messageEvents().rbegin(); const auto& lastSender = (*(timelineBottom + baseTimelineRow))->senderId(); const auto limit = timelineBottom + std::min(baseTimelineRow + 100, m_currentRoom->timelineSize()); for (auto it = timelineBottom + std::max(baseTimelineRow - 100, 0); it != limit; ++it) { if ((*it)->senderId() == lastSender) { auto idx = index(it - timelineBottom); emit dataChanged(idx, idx); } } } int MessageEventModel::rowCount(const QModelIndex& parent) const { if( !m_currentRoom || parent.isValid() ) return 0; return m_currentRoom->timelineSize(); } QVariant MessageEventModel::data(const QModelIndex& idx, int role) const { const auto row = idx.row(); if( !m_currentRoom || row < 0 || row >= int(m_currentRoom->pendingEvents().size()) + m_currentRoom->timelineSize()) return {}; bool isPending = row < timelineBaseIndex(); const auto timelineIt = m_currentRoom->messageEvents().crbegin() + std::max(0, row - timelineBaseIndex()); const auto pendingIt = m_currentRoom->pendingEvents().crbegin() + std::min(row, timelineBaseIndex()); const auto& evt = isPending ? **pendingIt : **timelineIt; using namespace QMatrixClient; if( role == Qt::DisplayRole ) { if (evt.isRedacted()) { auto reason = evt.redactedBecause()->reason(); if (reason.isEmpty()) return tr("Redacted"); return tr("Redacted: %1").arg(reason.toHtmlEscaped()); } return visit(evt , [this] (const RoomMessageEvent& e) { using namespace MessageEventContent; if (e.hasTextContent() && e.mimeType().name() != "text/plain") return static_cast(e.content())->body; if (e.hasFileContent()) { auto fileCaption = e.content()->fileInfo()->originalName.toHtmlEscaped(); if (fileCaption.isEmpty()) fileCaption = m_currentRoom->prettyPrint(e.plainBody()); return !fileCaption.isEmpty() ? fileCaption : tr("a file"); } return m_currentRoom->prettyPrint(e.plainBody()); } , [this] (const RoomMemberEvent& e) { // FIXME: Rewind to the name that was at the time of this event const auto subjectName = m_currentRoom->safeMemberName(e.userId()).toHtmlEscaped(); // The below code assumes senderName output in AuthorRole switch( e.membership() ) { case MembershipType::Invite: if (e.repeatsState()) return tr("reinvited %1 to the room").arg(subjectName); FALLTHROUGH; case MembershipType::Join: { if (e.repeatsState()) return tr("joined the room (repeated)"); if (!e.prevContent() || e.membership() != e.prevContent()->membership) { return e.membership() == MembershipType::Invite ? tr("invited %1 to the room").arg(subjectName) : tr("joined the room"); } QString text {}; if (e.isRename()) { if (e.displayName().isEmpty()) text = tr("cleared the display name"); else text = tr("changed the display name to %1") .arg(e.displayName().toHtmlEscaped()); } if (e.isAvatarUpdate()) { if (!text.isEmpty()) text += " and "; if (e.avatarUrl().isEmpty()) text += tr("cleared the avatar"); else text += tr("updated the avatar"); } return text; } case MembershipType::Leave: if (e.prevContent() && e.prevContent()->membership == MembershipType::Invite) { return (e.senderId() != e.userId()) ? tr("withdrew %1's invitation").arg(subjectName) : tr("rejected the invitation"); } if (e.prevContent() && e.prevContent()->membership == MembershipType::Ban) { return (e.senderId() != e.userId()) ? tr("unbanned %1").arg(subjectName) : tr("self-unbanned"); } return (e.senderId() != e.userId()) ? tr("has put %1 out of the room: %2") .arg(subjectName, e.contentJson()["reason"_ls] .toString().toHtmlEscaped()) : tr("left the room"); case MembershipType::Ban: return (e.senderId() != e.userId()) ? tr("banned %1 from the room: %2") .arg(subjectName, e.contentJson()["reason"_ls] .toString().toHtmlEscaped()) : tr("self-banned from the room"); case MembershipType::Knock: return tr("knocked"); default: ; } return tr("made something unknown"); } , [] (const RoomAliasesEvent& e) { return tr("has set room aliases on server %1 to: %2") .arg(e.stateKey(), QLocale().createSeparatedList(e.aliases())); } , [] (const RoomCanonicalAliasEvent& e) { return (e.alias().isEmpty()) ? tr("cleared the room main alias") : tr("set the room main alias to: %1").arg(e.alias()); } , [] (const RoomNameEvent& e) { return (e.name().isEmpty()) ? tr("cleared the room name") : tr("set the room name to: %1") .arg(e.name().toHtmlEscaped()); } , [this] (const RoomTopicEvent& e) { return (e.topic().isEmpty()) ? tr("cleared the topic") : tr("set the topic to: %1") .arg(m_currentRoom->prettyPrint(e.topic())); } , [] (const RoomAvatarEvent&) { return tr("changed the room avatar"); } , [] (const EncryptionEvent&) { return tr("activated End-to-End Encryption"); } , [] (const RoomCreateEvent& e) { return (e.isUpgrade() ? tr("upgraded the room to version %1") : tr("created the room, version %1") ).arg(e.version().isEmpty() ? "1" : e.version().toHtmlEscaped()); } , [] (const StateEventBase& e) { // A small hack for state events from TWIM bot return e.stateKey() == "twim" ? tr("updated the database", "TWIM bot updated the database") : e.stateKey().isEmpty() ? tr("updated %1 state", "%1 - Matrix event type") .arg(e.matrixType()) : tr("updated %1 state for %2", "%1 - Matrix event type, %2 - state key") .arg(e.matrixType(), e.stateKey().toHtmlEscaped()); } , tr("Unknown event") ); } if( role == Qt::ToolTipRole ) { return evt.originalJson(); } if( role == EventTypeRole ) { if (auto e = eventCast(&evt)) { switch (e->msgtype()) { case MessageEventType::Emote: return "emote"; case MessageEventType::Notice: return "notice"; case MessageEventType::Image: return "image"; default: return e->hasFileContent() ? "file" : "message"; } } if (evt.isStateEvent()) return "state"; return "other"; } if (role == EventResolvedTypeRole) return EventTypeRegistry::getMatrixType(evt.type()); if( role == AuthorRole ) { // FIXME: It shouldn't be User, it should be its state "as of event" return QVariant::fromValue(isPending ? m_currentRoom->localUser() : m_currentRoom->user(evt.senderId())); } if (role == ContentTypeRole) { if (auto e = eventCast(&evt)) { const auto& contentType = e->mimeType().name(); return contentType == "text/plain" ? QStringLiteral("text/html") : contentType; } return QStringLiteral("text/plain"); } if (role == ContentRole) { if (evt.isRedacted()) { const auto reason = evt.redactedBecause()->reason(); return (reason.isEmpty()) ? tr("Redacted") : tr("Redacted: %1").arg(reason.toHtmlEscaped()); } if (auto e = eventCast(&evt)) { // Cannot use e.contentJson() here because some // EventContent classes inject values into the copy of the // content JSON stored in EventContent::Base return e->hasFileContent() ? QVariant::fromValue(e->content()->originalJson) : QVariant(); }; } if( role == HighlightRole ) return m_currentRoom->isEventHighlighted(&evt); if( role == ReadMarkerRole ) return evt.id() == lastReadEventId; if( role == SpecialMarksRole ) { if (isPending) return !Settings().get("UI/suppress_local_echo") ? pendingIt->deliveryStatus() : EventStatus::Hidden; if (is(evt)) return EventStatus::Hidden; auto* memberEvent = timelineIt->viewAs(); if (memberEvent) { if ((memberEvent->isJoin() || memberEvent->isLeave()) && !Settings().value("UI/show_joinleave", true).toBool()) return EventStatus::Hidden; } if (memberEvent || evt.isRedacted()) { if (evt.senderId() != m_currentRoom->localUser()->id() && evt.stateKey() != m_currentRoom->localUser()->id() && !Settings().value("UI/show_spammy").toBool()) { // QElapsedTimer et; et.start(); auto hide = !isUserActivityNotable(timelineIt); // qDebug() << "Checked user activity for" << evt.id() << "in" << et; if (hide) return EventStatus::Hidden; } } if (evt.isRedacted()) return Settings().value("UI/show_redacted").toBool() ? EventStatus::Redacted : EventStatus::Hidden; if (evt.isStateEvent() && static_cast(evt).repeatsState() && !Settings().value("UI/show_noop_events").toBool()) return EventStatus::Hidden; return EventStatus::Normal; } if( role == EventIdRole ) return !evt.id().isEmpty() ? evt.id() : evt.transactionId(); if( role == LongOperationRole ) { if (auto e = eventCast(&evt)) if (e->hasFileContent()) return QVariant::fromValue( m_currentRoom->fileTransferInfo( isPending ? e->transactionId() : e->id())); } if( role == AnnotationRole ) if (isPending) return pendingIt->annotation(); if( role == TimeRole || role == SectionRole) { auto ts = isPending ? pendingIt->lastUpdated() : makeMessageTimestamp(timelineIt); return role == TimeRole ? QVariant(ts) : renderDate(ts); } if( role == AboveSectionRole || role == AboveAuthorRole) for (auto r = row + 1; r < rowCount(); ++r) { auto i = index(r); if (data(i, SpecialMarksRole) != EventStatus::Hidden) return data(i, role == AboveSectionRole ? SectionRole : AuthorRole); } return {}; } Quaternion-0.0.9.4c/client/models/messageeventmodel.h000066400000000000000000000051141347141001300225660ustar00rootroot00000000000000/************************************************************************** * * * Copyright (C) 2015 Felix Rohrbach * * * * 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 . * * * **************************************************************************/ #pragma once #include "../quaternionroom.h" #include class MessageEventModel: public QAbstractListModel { Q_OBJECT public: explicit MessageEventModel(QObject* parent = nullptr); void changeRoom(QuaternionRoom* room); int rowCount(const QModelIndex& parent = QModelIndex()) const override; QVariant data(const QModelIndex& idx, int role = Qt::DisplayRole) const override; QHash roleNames() const override; private slots: int refreshEvent(const QString& eventId); void refreshRow(int row); private: QuaternionRoom* m_currentRoom = nullptr; QString lastReadEventId; int rowBelowInserted = -1; bool movingEvent = false; int timelineBaseIndex() const; QDateTime makeMessageTimestamp(const QuaternionRoom::rev_iter_t& baseIt) const; QString renderDate(const QDateTime& timestamp) const; bool isUserActivityNotable(const QuaternionRoom::rev_iter_t& baseIt) const; void refreshLastUserEvents(int baseTimelineRow); void refreshEventRoles(int row, const QVector& roles = {}); int refreshEventRoles(const QString& id, const QVector& roles = {}); }; Quaternion-0.0.9.4c/client/models/orderbytag.cpp000066400000000000000000000172551347141001300215650ustar00rootroot00000000000000/****************************************************************************** * Copyright (C) 2018-2019 QMatrixClient Project * * 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 . * */ #include "orderbytag.h" #include "roomlistmodel.h" #include const QString Invite = QStringLiteral("org.qmatrixclient.invite"); const QString DirectChat = QStringLiteral("org.qmatrixclient.direct"); const QString Untagged = QStringLiteral("org.qmatrixclient.none"); const QString Left = QStringLiteral("org.qmatrixclient.left"); template inline auto findIndex(const QList& list, const VT& value) { // Using std::find() instead of indexOf() so that not found keys were // naturally sorted after found ones (index == list.end() - list.begin() // is more than any index in the list, while index == -1 is less). return std::find(list.begin(), list.end(), value) - list.begin(); } auto findIndexWithWildcards(const QStringList& list, const QString& value) { if (list.empty() || value.isEmpty()) return list.size(); auto i = findIndex(list, value); // Try namespace groupings (".*" in the list), from right to left for (int dotPos = 0; i == list.size() && (dotPos = value.lastIndexOf('.', --dotPos)) != -1;) { i = findIndex(list, value.left(dotPos + 1) + '*'); } return i; } QVariant OrderByTag::groupLabel(const RoomGroup& g) const { // TODO: maybe move the tr() strings below from RoomListModel context static const auto InvitesLabel = RoomListModel::tr("Invited", "The caption for invitations"); static const auto FavouritesLabel = RoomListModel::tr("Favourites"); static const auto LowPriorityLabel = RoomListModel::tr("Low priority"); static const auto DirectChatsLabel = RoomListModel::tr("People", "The caption for direct chats"); static const auto UngroupedRoomsLabel = RoomListModel::tr("Ungrouped rooms"); static const auto LeftLabel = RoomListModel::tr("Left", "The caption for left rooms"); const auto caption = g.key == Untagged ? UngroupedRoomsLabel : g.key == Invite ? InvitesLabel : g.key == DirectChat ? DirectChatsLabel : g.key == Left ? LeftLabel : g.key == QMatrixClient::FavouriteTag ? FavouritesLabel : g.key == QMatrixClient::LowPriorityTag ? LowPriorityLabel : g.key.toString().startsWith("u.") ? g.key.toString().mid(2) : g.key.toString(); return RoomListModel::tr("%1 (%Ln room(s))", "", g.rooms.size()).arg(caption); } bool OrderByTag::groupLessThan(const RoomGroup& g1, const QVariant& g2key) const { const auto& lkey = g1.key.toString(); const auto& rkey = g2key.toString(); // See above auto li = findIndexWithWildcards(tagsOrder, lkey); auto ri = findIndexWithWildcards(tagsOrder, rkey); return li < ri || (li == ri && lkey < rkey); } bool OrderByTag::roomLessThan(const QVariant& groupKey, const Room* r1, const Room* r2) const { if (r1 == r2) return false; // 0. Short-circuit for coinciding room objects // 1. Compare tag order values const auto& tag = groupKey.toString(); auto o1 = r1->tag(tag).order; auto o2 = r2->tag(tag).order; if (o2.omitted() != o1.omitted()) return o2.omitted(); if (!o1.omitted() && !o2.omitted()) { // Compare floats; fallthrough if neither is smaller if (o1.value() < o2.value()) return true; if (o1.value() > o2.value()) return false; } // 2. Neither tag order is less than the other; compare room display names if (auto roomCmpRes = r1->displayName().localeAwareCompare(r2->displayName())) return roomCmpRes < 0; // 4. Within the same display name, order by room id // (typically the case when both display names are completely empty) if (auto roomIdCmpRes = r1->id().compare(r2->id())) return roomIdCmpRes < 0; // 3. Room ids are equal; order by connections (=userids) const auto c1 = r1->connection(); const auto c2 = r2->connection(); if (c1 != c2) { if (auto usersCmpRes = c1->userId().compare(c2->userId())) return usersCmpRes < 0; // 3a. Two logins under the same userid: pervert, but technically correct Q_ASSERT(c1->accessToken() != c2->accessToken()); return c1->accessToken() < c2->accessToken(); } // 5. Assume two incarnations of the room with the different join state // (by design, join states are distinct within one connection+roomid) Q_ASSERT(r1->joinState() != r2->joinState()); return r1->joinState() < r2->joinState(); } AbstractRoomOrdering::groups_t OrderByTag::roomGroups(const Room* room) const { if (room->joinState() == QMatrixClient::JoinState::Invite) return groups_t {{ Invite }}; if (room->joinState() == QMatrixClient::JoinState::Leave) return groups_t {{ Left }}; auto tags = room->tags().keys(); groups_t vl; vl.reserve(tags.size()); std::copy(tags.cbegin(), tags.cend(), std::back_inserter(vl)); if (room->isDirectChat()) vl.push_back(DirectChat); if (vl.empty()) vl.push_back(Untagged); return vl; } void OrderByTag::connectSignals(Connection* connection) { using DCMap = Connection::DirectChatsMap; connect( connection, &Connection::directChatsListChanged, this, [this,connection] (const DCMap& additions, const DCMap& removals) { // The same room may show up in removals and in additions if it // moves from one userid to another (pretty weird but encountered // in the wild). Therefore process removals first. for (const auto& rId: removals) if (auto* r = connection->room(rId)) updateGroups(r); for (const auto& rId: additions) if (auto* r = connection->room(rId)) updateGroups(r); }); } void OrderByTag::connectSignals(Room* room) { connect(room, &Room::displaynameChanged, this, [this,room] { updateGroups(room); }); connect(room, &Room::tagsChanged, this, [this,room] { updateGroups(room); }); connect(room, &Room::joinStateChanged, this, [this,room] { updateGroups(room); }); } QStringList OrderByTag::initTagsOrder() { using namespace QMatrixClient; static const QStringList DefaultTagsOrder { Invite, FavouriteTag, QStringLiteral("u.*"), DirectChat, Untagged, LowPriorityTag, Left }; static const auto SettingsKey = QStringLiteral("tags_order"); static QMatrixClient::SettingsGroup sg { "UI/RoomsDock" }; const auto savedOrder = sg.get(SettingsKey); if (savedOrder.isEmpty()) { sg.setValue(SettingsKey, DefaultTagsOrder); return DefaultTagsOrder; } return savedOrder; } Quaternion-0.0.9.4c/client/models/orderbytag.h000066400000000000000000000036201347141001300212210ustar00rootroot00000000000000/****************************************************************************** * Copyright (C) 2018-2019 QMatrixClient Project * * 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 . * */ #pragma once #include "abstractroomordering.h" class OrderByTag : public AbstractRoomOrdering { public: explicit OrderByTag(RoomListModel* m) : AbstractRoomOrdering(m), tagsOrder(initTagsOrder()) { } QString orderingName() const override { return QStringLiteral("tag"); } QVariant groupLabel(const RoomGroup& g) const override; bool groupLessThan(const RoomGroup& g1, const QVariant& g2key) const override; bool roomLessThan(const QVariant& groupKey, const Room* r1, const Room* r2) const override; groups_t roomGroups(const Room* room) const override; void connectSignals(Connection* connection) override; void connectSignals(Room* room) override; private: QStringList tagsOrder; static QStringList initTagsOrder(); }; Quaternion-0.0.9.4c/client/models/roomlistmodel.cpp000066400000000000000000000457621347141001300223200ustar00rootroot00000000000000/************************************************************************** * * * Copyright (C) 2016 Felix Rohrbach * * * * 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 . * * * **************************************************************************/ #include "roomlistmodel.h" #include "../quaternionroom.h" #include #include #include #include #include // See the comment next to QGuiApplication::palette().brush() usage in this file #include #include #include RoomListModel::RoomListModel(QObject* parent) : QAbstractItemModel(parent) { connect(this, &RoomListModel::modelAboutToBeReset, this, &RoomListModel::saveCurrentSelection); connect(this, &RoomListModel::modelReset, this, &RoomListModel::restoreCurrentSelection); } void RoomListModel::addConnection(QMatrixClient::Connection* connection) { Q_ASSERT(connection); using namespace QMatrixClient; m_connections.emplace_back(connection, this); connect( connection, &Connection::loggedOut, this, [=]{ deleteConnection(connection); } ); connect( connection, &Connection::newRoom, this, &RoomListModel::addRoom); m_roomOrder->connectSignals(connection); for (auto* r: connection->roomMap()) addRoom(r); } void RoomListModel::deleteConnection(QMatrixClient::Connection* connection) { Q_ASSERT(connection); const auto connIt = find(m_connections.begin(), m_connections.end(), connection); if (connIt == m_connections.end()) { Q_ASSERT_X(connIt == m_connections.end(), __FUNCTION__, "Connection is missing in the rooms model"); return; } for (auto* r: connection->roomMap()) deleteRoom(r); m_connections.erase(connIt); connection->disconnect(this); } void RoomListModel::deleteTag(QModelIndex index) { if (!isValidGroupIndex(index)) return; const auto tag = m_roomGroups[index.row()].key.toString(); if (tag.isEmpty()) { qCritical() << "RoomListModel: Invalid tag at position" << index.row(); return; } if (tag.startsWith("org.qmatrixclient.")) { qWarning() << "RoomListModel: System groups cannot be deleted " "(tried to delete" << tag << "group)"; return; } // After the below loop, the respective group will magically disappear from // m_roomGroups as well due to tagsChanged() triggered from removeTag() for (const auto& c: m_connections) for (auto* r: c->roomsWithTag(tag)) r->removeTag(tag); } void RoomListModel::visitRoom(const Room& room, const std::function& visitor) { // Copy persistent indices because visitors may alter m_roomIndices const auto indices = m_roomIndices.values(&room); for (const auto& idx: indices) { Q_ASSERT(isValidRoomIndex(idx)); if (roomAt(idx) == &room) visitor(idx); else { qCritical() << "Room at" << idx << "is" << roomAt(idx)->objectName() << "instead of" << room.objectName(); Q_ASSERT(false); } } } QVariant RoomListModel::roomGroupAt(QModelIndex idx) const { Q_ASSERT(idx.isValid()); // Root item shouldn't come here // If we're on a room, find its group; otherwise just take the index const auto groupIt = m_roomGroups.cbegin() + (idx.parent().isValid() ? idx.parent() : idx).row(); return groupIt != m_roomGroups.end() ? groupIt->key : QVariant(); } QuaternionRoom* RoomListModel::roomAt(QModelIndex idx) const { return isValidRoomIndex(idx) ? static_cast( m_roomGroups[idx.parent().row()].rooms[idx.row()]) : nullptr; } QModelIndex RoomListModel::indexOf(const QVariant& group) const { const auto groupIt = lowerBoundGroup(group); if (groupIt == m_roomGroups.end() || groupIt->key != group) return {}; // Group not found return index(groupIt - m_roomGroups.begin(), 0); } QModelIndex RoomListModel::indexOf(const QVariant& group, Room* room) const { auto it = m_roomIndices.find(room); if (group.isNull() && it != m_roomIndices.end()) return *it; for (;it != m_roomIndices.end() && it.key() == room; ++it) { Q_ASSERT(isValidRoomIndex(*it)); if (m_roomGroups[it->parent().row()].key == group) return *it; } return {}; } QModelIndex RoomListModel::index(int row, int column, const QModelIndex& parent) const { if (!hasIndex(row, column, parent)) return {}; // Groups get internalId() == -1, rooms get the group ordinal number return createIndex(row, column, quintptr(parent.isValid() ? parent.row() : -1)); } QModelIndex RoomListModel::parent(const QModelIndex& child) const { const auto parentPos = int(child.internalId()); return child.isValid() && parentPos != -1 ? index(parentPos, 0) : QModelIndex(); } void RoomListModel::addRoom(Room* room) { Q_ASSERT(room && !room->id().isEmpty()); addRoomToGroups(room); connectRoomSignals(room); } void RoomListModel::deleteRoom(Room* room) { visitRoom(*room, [this] (QModelIndex idx) { doRemoveRoom(idx); }); room->disconnect(this); } RoomGroups::iterator RoomListModel::tryInsertGroup(const QVariant& key) { Q_ASSERT(!key.toString().isEmpty()); auto gIt = lowerBoundGroup(key); if (gIt == m_roomGroups.end() || gIt->key != key) { const auto gPos = gIt - m_roomGroups.begin(); const auto affectedIdxs = preparePersistentIndexChange(gPos, 1); beginInsertRows({}, gPos, gPos); gIt = m_roomGroups.insert(gIt, {key, {}}); endInsertRows(); changePersistentIndexList(affectedIdxs.first, affectedIdxs.second); emit groupAdded(gPos); } // Check that the group is healthy Q_ASSERT(gIt->key == key && (gIt->rooms.empty() || !gIt->rooms.front()->id().isEmpty())); return gIt; } void RoomListModel::addRoomToGroups(Room* room, QVariantList groups) { if (groups.empty()) groups = m_roomOrder->roomGroups(room); for (const auto& g: groups) { const auto gIt = tryInsertGroup(g); const auto rIt = lowerBoundRoom(*gIt, room); if (rIt != gIt->rooms.end() && *rIt == room) { qWarning() << "RoomListModel:" << room->objectName() << "is already listed under group" << g.toString(); continue; } const auto rPos = rIt - gIt->rooms.begin(); const auto gIdx = index(gIt - m_roomGroups.begin(), 0); beginInsertRows(gIdx, rPos, rPos); gIt->rooms.insert(rIt, room); endInsertRows(); m_roomIndices.insert(room, index(rPos, 0, gIdx)); qDebug() << "RoomListModel: Added" << room->objectName() << "to group" << gIt->key.toString(); } } void RoomListModel::connectRoomSignals(Room* room) { connect(room, &Room::beforeDestruction, this, &RoomListModel::deleteRoom); m_roomOrder->connectSignals(room); connect(room, &Room::displaynameChanged, this, [this,room] { refresh(room); }); connect(room, &Room::unreadMessagesChanged, this, [this,room] { refresh(room); }); connect(room, &Room::notificationCountChanged, this, [this,room] { refresh(room); }); connect(room, &Room::avatarChanged, this, [this,room] { refresh(room, { Qt::DecorationRole }); }); } void RoomListModel::doRemoveRoom(QModelIndex idx) { if (!isValidRoomIndex(idx)) { qCritical() << "Attempt to remove a room at invalid index" << idx; Q_ASSERT(false); return; } const auto gPos = idx.parent().row(); auto& group = m_roomGroups[gPos]; const auto rIt = group.rooms.begin() + idx.row(); qDebug() << "RoomListModel: Removing room" << (*rIt)->objectName() << "from group" << group.key.toString(); if (m_roomIndices.remove(*rIt, idx) != 1) { qCritical() << "Index" << idx << "for room" << (*rIt)->objectName() << "not found in the index registry"; Q_ASSERT(false); } beginRemoveRows(idx.parent(), idx.row(), idx.row()); group.rooms.erase(rIt); endRemoveRows(); if (group.rooms.empty()) { // Update persistent indices with parents after the deleted one const auto affectedIdxs = preparePersistentIndexChange(gPos + 1, -1); beginRemoveRows({}, gPos, gPos); m_roomGroups.remove(gPos); endRemoveRows(); changePersistentIndexList(affectedIdxs.first, affectedIdxs.second); } } void RoomListModel::doSetOrder(std::unique_ptr&& newOrder) { beginResetModel(); m_roomGroups.clear(); m_roomIndices.clear(); if (m_roomOrder) m_roomOrder->deleteLater(); m_roomOrder = newOrder.release(); endResetModel(); for (const auto& c: m_connections) { m_roomOrder->connectSignals(c); for (auto* r: c->roomMap()) { addRoomToGroups(r); m_roomOrder->connectSignals(r); } } } std::pair RoomListModel::preparePersistentIndexChange(int fromPos, int shiftValue) const { QModelIndexList from, to; for (auto& pIdx: persistentIndexList()) if (isValidRoomIndex(pIdx) && pIdx.parent().row() >= fromPos) { from.append(pIdx); to.append(createIndex(pIdx.row(), pIdx.column(), quintptr(int(pIdx.internalId()) + shiftValue))); } return { std::move(from), std::move(to) }; } int RoomListModel::rowCount(const QModelIndex& parent) const { if (!parent.isValid()) return m_roomGroups.size(); if (isValidGroupIndex(parent)) return m_roomGroups[parent.row()].rooms.size(); return 0; // Rooms have no children } int RoomListModel::totalRooms() const { int result = 0; for (const auto& c: m_connections) result += c->roomMap().size(); return result; } bool RoomListModel::isValidGroupIndex(QModelIndex i) const { return i.isValid() && !i.parent().isValid() && i.row() < m_roomGroups.size(); } bool RoomListModel::isValidRoomIndex(QModelIndex i) const { return i.isValid() && isValidGroupIndex(i.parent()) && i.row() < m_roomGroups[i.parent().row()].rooms.size(); } QVariant RoomListModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) return {}; if (isValidGroupIndex(index)) { if (role == Qt::DisplayRole) return m_roomOrder->groupLabel(m_roomGroups[index.row()]); // It would be more proper to do it in RoomListItemDelegate // (see roomlistdock.cpp) but I (@kitsune) couldn't find a working way. if (role == Qt::BackgroundRole) return QGuiApplication::palette() .brush(QPalette::Active, QPalette::Button); return {}; } auto* const room = roomAt(index); if (!room) return {}; // if (index.column() == 1) // return room->lastUpdated(); // if (index.column() == 2) // return room->lastAttended(); using QMatrixClient::JoinState; switch (role) { case Qt::DisplayRole: { const auto prefix = room->isUnstable() ? QStringLiteral("(!)") : QString(); const auto unreadCount = room->unreadCount(); const auto postfix = unreadCount == -1 ? QString() : room->readMarker() != room->timelineEdge() ? QStringLiteral(" [%1]").arg(unreadCount) : QStringLiteral(" [%1+]").arg(unreadCount); for (const auto& c: m_connections) { if (c == room->connection()) continue; if (c->room(room->id(), room->joinState())) return prefix + tr("%1 (as %2)", "%Room (as %user)") .arg(room->displayName(), room->connection()->userId()) + postfix; } return prefix + room->displayName() + postfix; } case Qt::DecorationRole: { auto avatar = room->avatar(16, 16); if (!avatar.isNull()) return avatar; switch( room->joinState() ) { case JoinState::Join: return QIcon(":/irc-channel-joined.svg"); case JoinState::Invite: return QIcon(":/irc-channel-invited.svg"); case JoinState::Leave: return QIcon(":/irc-channel-parted.svg"); default: Q_ASSERT(false); // Unknown JoinState? } return {}; // Shouldn't reach here } case Qt::ToolTipRole: { QString result = QStringLiteral("%1").arg(room->displayName()) % "
" % tr("Main alias: %1").arg(room->canonicalAlias()) % "
" % tr("Joined: %Ln", "The number of joined members", room->joinedCount()); if (room->invitedCount() > 0) result += "
" % tr("Invited: %Ln", "The number of invited users", room->invitedCount()); auto directChatUsers = room->directChatUsers(); if (!directChatUsers.isEmpty()) { QStringList userNames; for (auto* user: directChatUsers) userNames.push_back(user->displayname(room)); result += "
" % tr("Direct chat with %1") .arg(userNames.join(',')); } if (room->usesEncryption()) result += "
" % tr("The room enforces encryption"); if (room->isUnstable()) { result += "
(!) " % tr("This room's version is unstable!"); if (room->canSwitchVersions()) result += ' ' % tr("Consider upgrading to a stable version" " (use room settings for that)"); } auto unreadCount = room->unreadCount(); if (unreadCount >= 0) { const auto unreadLine = room->readMarker() == room->timelineEdge() ? tr("Unread messages: %1+") : tr("Unread messages: %1"); result += "
" % unreadLine.arg(unreadCount); } auto hlCount = room->highlightCount(); if (hlCount > 0) result += "
" % tr("Unread highlights: %1").arg(hlCount); result += "
" % tr("ID: %1").arg(room->id()) % "
"; auto asUser = m_connections.size() < 2 ? QString() : ' ' + tr("as %1", "as (disambiguates entries in the room list)") .arg(room->localUser()->id()); switch (room->joinState()) { case JoinState::Join: result += tr("You joined this room") % asUser; break; case JoinState::Leave: result += tr("You left this room") % asUser; break; case JoinState::Invite: result += tr("You were invited into this room") % asUser; } return result; } case HasUnreadRole: return room->hasUnreadMessages(); case HighlightCountRole: return room->highlightCount(); case JoinStateRole: if (!room->successorId().isEmpty()) return QStringLiteral("upgraded"); return toCString(room->joinState()); // FIXME: better make the enum QVariant-convertible (only possible from Qt 5.8, see Q_ENUM_NS) case ObjectRole: return QVariant::fromValue(room); default: return {}; } } int RoomListModel::columnCount(const QModelIndex&) const { return 1; } void RoomListModel::updateGroups(Room* room) { const auto oldRoomIndices = m_roomIndices.values(room); Q_ASSERT(!oldRoomIndices.empty()); // The room should have been somewhere auto groups = m_roomOrder->roomGroups(room); for (const auto& oldIndex: oldRoomIndices) { Q_ASSERT(isValidRoomIndex(oldIndex)); const auto gIdx = oldIndex.parent(); auto& group = m_roomGroups[gIdx.row()]; if (groups.removeOne(group.key)) // Test and remove at once { // The room still in this group but may need to move around const auto oldIt = group.rooms.begin() + oldIndex.row(); const auto newIt = lowerBoundRoom(group, room); if (newIt != oldIt) { beginMoveRows(gIdx, oldIndex.row(), oldIndex.row(), gIdx, int(newIt - group.rooms.begin())); if (newIt > oldIt) std::rotate(oldIt, oldIt + 1, newIt); else std::rotate(newIt, oldIt, oldIt + 1); endMoveRows(); } Q_ASSERT(roomAt(oldIndex) == room); } else doRemoveRoom(oldIndex); // May invalidate `group` and `gIdx` } if (!groups.empty()) addRoomToGroups(room, groups); // Groups the room wasn't before qDebug() << "RoomListModel: groups for" << room->objectName() << "updated"; } void RoomListModel::refresh(Room* room, const QVector& roles) { // The problem here is that the change might cause the room to change // its groups. Assume for now that such changes are processed elsewhere // where details about the change are available (e.g. in tagsChanged). visitRoom(*room, [this,&roles] (QModelIndex idx) { emit dataChanged(idx, idx, roles); }); } Quaternion-0.0.9.4c/client/models/roomlistmodel.h000066400000000000000000000124261347141001300217540ustar00rootroot00000000000000/************************************************************************** * * * Copyright (C) 2016 Felix Rohrbach * * * * 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 . * * * **************************************************************************/ #pragma once #include "abstractroomordering.h" #include "../quaternionroom.h" #include #include #include #include class RoomListModel: public QAbstractItemModel { Q_OBJECT template using ConnectionsGuard = QMatrixClient::ConnectionsGuard; public: enum Roles { HasUnreadRole = Qt::UserRole + 1, HighlightCountRole, JoinStateRole, ObjectRole }; using Room = QMatrixClient::Room; explicit RoomListModel(QObject* parent = nullptr); ~RoomListModel() override = default; QVariant roomGroupAt(QModelIndex idx) const; QuaternionRoom* roomAt(QModelIndex idx) const; QModelIndex indexOf(const QVariant& group) const; QModelIndex indexOf(const QVariant& group, Room* room) const; QModelIndex index(int row, int column, const QModelIndex& parent = {}) const override; QModelIndex parent(const QModelIndex& index) const override; QVariant data(const QModelIndex& index, int role) const override; int columnCount(const QModelIndex&) const override; int rowCount(const QModelIndex& parent) const override; int totalRooms() const; bool isValidGroupIndex(QModelIndex i) const; bool isValidRoomIndex(QModelIndex i) const; template void setOrder() { doSetOrder(std::make_unique(this)); } signals: void groupAdded(int row); void saveCurrentSelection(); void restoreCurrentSelection(); public slots: void addConnection(QMatrixClient::Connection* connection); void deleteConnection(QMatrixClient::Connection* connection); // FIXME, QMatrixClient/libqmatrixclient#63: // This should go to the library's ConnectionManager/RoomManager void deleteTag(QModelIndex index); private slots: void addRoom(Room* room); void refresh(Room* room, const QVector& roles = {}); void deleteRoom(Room* room); void updateGroups(Room* room); private: friend class AbstractRoomOrdering; std::vector> m_connections; RoomGroups m_roomGroups; AbstractRoomOrdering* m_roomOrder = nullptr; QMultiHash m_roomIndices; RoomGroups::iterator tryInsertGroup(const QVariant& key); void addRoomToGroups(Room* room, QVariantList groups = {}); void connectRoomSignals(Room* room); void doRemoveRoom(QModelIndex idx); void visitRoom(const Room& room, const std::function& visitor); void doSetOrder(std::unique_ptr&& newOrder); std::pair preparePersistentIndexChange(int fromPos, int shiftValue) const; // Beware, the returned iterators are as short-lived as QModelIndex'es auto lowerBoundGroup(const QVariant& group) { return std::lower_bound(m_roomGroups.begin(), m_roomGroups.end(), group, m_roomOrder->groupLessThanFactory()); } auto lowerBoundGroup(const QVariant& group) const { return std::lower_bound(m_roomGroups.begin(), m_roomGroups.end(), group, m_roomOrder->groupLessThanFactory()); } auto lowerBoundRoom(RoomGroup& group, Room* room) const { return std::lower_bound(group.rooms.begin(), group.rooms.end(), room, m_roomOrder->roomLessThanFactory(group.key)); } auto lowerBoundRoom(const RoomGroup& group, Room* room) const { return std::lower_bound(group.rooms.begin(), group.rooms.end(), room, m_roomOrder->roomLessThanFactory(group.key)); } }; Quaternion-0.0.9.4c/client/models/userlistmodel.cpp000066400000000000000000000146101347141001300223060ustar00rootroot00000000000000/************************************************************************** * * * Copyright (C) 2015 Felix Rohrbach * * * * 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 . * * * **************************************************************************/ #include "userlistmodel.h" #include #include #include #include #include #include UserListModel::UserListModel(QObject* parent) : QAbstractListModel(parent) , m_currentRoom(nullptr) { } UserListModel::~UserListModel() = default; void UserListModel::setRoom(QMatrixClient::Room* room) { if (m_currentRoom == room) return; using namespace QMatrixClient; beginResetModel(); if( m_currentRoom ) { m_currentRoom->connection()->disconnect( this ); m_currentRoom->disconnect( this ); for( User* user: m_users ) user->disconnect( this ); m_users.clear(); } m_currentRoom = room; if( m_currentRoom ) { connect( m_currentRoom, &Room::userAdded, this, &UserListModel::userAdded ); connect( m_currentRoom, &Room::userRemoved, this, &UserListModel::userRemoved ); connect( m_currentRoom, &Room::memberAboutToRename, this, &UserListModel::userRemoved ); connect( m_currentRoom, &Room::memberRenamed, this, &UserListModel::userAdded ); connect( m_currentRoom, &Room::memberListChanged, this, &UserListModel::membersChanged ); filter(""); for( User* user: m_users ) { connect( user, &User::avatarChanged, this, &UserListModel::avatarChanged ); } connect( m_currentRoom->connection(), &Connection::loggedOut, this, [=] { setRoom(nullptr); } ); qDebug() << m_users.count() << "user(s) in the room"; } endResetModel(); } QMatrixClient::User* UserListModel::userAt(QModelIndex index) { if (index.row() < 0 || index.row() >= m_users.size()) return nullptr; return m_users.at(index.row()); } QVariant UserListModel::data(const QModelIndex& index, int role) const { if( !index.isValid() ) return QVariant(); if( index.row() >= m_users.count() ) { qDebug() << "UserListModel, something's wrong: index.row() >= m_users.count()"; return QVariant(); } auto user = m_users.at(index.row()); if( role == Qt::DisplayRole ) { return user->displayname(m_currentRoom); } if( role == Qt::DecorationRole ) { return user->avatar(25,25, m_currentRoom); } if (role == Qt::ToolTipRole) { auto tooltip = QStringLiteral("%1
%2") .arg(user->name(m_currentRoom).toHtmlEscaped(), user->id()); if (!user->bridged().isEmpty()) tooltip += "
" + tr("Bridged from: %1").arg(user->bridged()); return tooltip; } return QVariant(); } int UserListModel::rowCount(const QModelIndex& parent) const { if( parent.isValid() ) return 0; return m_users.count(); } void UserListModel::userAdded(QMatrixClient::User* user) { auto pos = findUserPos(user); if (pos != m_users.size() && m_users[pos] == user) { qWarning() << "Trying to add the user" << user->id() << "but it's already in the user list"; return; } beginInsertRows(QModelIndex(), pos, pos); m_users.insert(pos, user); endInsertRows(); connect( user, &QMatrixClient::User::avatarChanged, this, &UserListModel::avatarChanged ); } void UserListModel::userRemoved(QMatrixClient::User* user) { auto pos = findUserPos(user); if (pos == m_users.size()) { qWarning() << "Trying to remove a room member not in the user list:" << user->id(); return; } beginRemoveRows(QModelIndex(), pos, pos); m_users.removeAt(pos); endRemoveRows(); user->disconnect(this); } void UserListModel::filter(const QString& filterString) { if (m_currentRoom == nullptr) return; QElapsedTimer et; et.start(); beginResetModel(); m_users.clear(); const auto all = m_currentRoom->users(); std::remove_copy_if(all.begin(), all.end(), std::back_inserter(m_users), [&](User* u) { return !(u->rawName(m_currentRoom).contains(filterString) || u->id().contains(filterString)); }); std::sort(m_users.begin(), m_users.end(), m_currentRoom->memberSorter()); endResetModel(); qDebug() << "Filtering" << m_users.size() << "user(s) in" << m_currentRoom->displayName() << "took" << et; } void UserListModel::refresh(QMatrixClient::User* user, QVector roles) { auto pos = findUserPos(user); if ( pos != m_users.size() ) emit dataChanged(index(pos), index(pos), roles); else qWarning() << "Trying to access a room member not in the user list"; } void UserListModel::avatarChanged(QMatrixClient::User* user, const QMatrixClient::Room* context) { if (context == m_currentRoom) refresh(user, {Qt::DecorationRole}); } int UserListModel::findUserPos(User* user) const { return findUserPos(m_currentRoom->roomMembername(user)); } int UserListModel::findUserPos(const QString& username) const { return m_currentRoom->memberSorter().lowerBoundIndex(m_users, username); } Quaternion-0.0.9.4c/client/models/userlistmodel.h000066400000000000000000000047701347141001300217610ustar00rootroot00000000000000/************************************************************************** * * * Copyright (C) 2015 Felix Rohrbach * * * * 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 . * * * **************************************************************************/ #pragma once #include namespace QMatrixClient { class Connection; class Room; class User; } class UserListModel: public QAbstractListModel { Q_OBJECT public: using User = QMatrixClient::User; UserListModel(QObject* parent = nullptr); virtual ~UserListModel(); void setRoom(QMatrixClient::Room* room); User* userAt(QModelIndex index); QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; int rowCount(const QModelIndex& parent=QModelIndex()) const override; signals: void membersChanged(); //< Reflection of Room::memberListChanged public slots: void filter(const QString& filterString); private slots: void userAdded(User* user); void userRemoved(User* user); void refresh(User* user, QVector roles = {}); void avatarChanged(User* user, const QMatrixClient::Room* context); private: QMatrixClient::Room* m_currentRoom; QList m_users; int findUserPos(User* user) const; int findUserPos(const QString& username) const; }; Quaternion-0.0.9.4c/client/networkconfigdialog.cpp000066400000000000000000000125211347141001300221660ustar00rootroot00000000000000/****************************************************************************** * Copyright (C) 2017 Kitsune Ral * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "networkconfigdialog.h" #include #include #include #include #include #include #include #include #include #include #include QLabel* makeBuddyLabel(QString labelText, QWidget* field) { auto label = new QLabel(labelText); label->setBuddy(field); return label; } NetworkConfigDialog::NetworkConfigDialog(QWidget* parent) : Dialog(tr("Network proxy settings"), parent) , useProxyBox(new QGroupBox(tr("&Override system defaults"), this)) , proxyTypeGroup(new QButtonGroup(this)) , proxyHostName(new QLineEdit(this)) , proxyPort(new QSpinBox(this)) , proxyUserName(new QLineEdit(this)) { // Create and configure all the controls useProxyBox->setCheckable(true); useProxyBox->setChecked(false); connect(useProxyBox, &QGroupBox::toggled, this, &NetworkConfigDialog::maybeDisableControls); auto noProxyButton = new QRadioButton(tr("&No proxy")); noProxyButton->setChecked(true); proxyTypeGroup->addButton(noProxyButton, QNetworkProxy::NoProxy); proxyTypeGroup->addButton(new QRadioButton(tr("&HTTP(S) proxy")), QNetworkProxy::HttpProxy); proxyTypeGroup->addButton(new QRadioButton(tr("&SOCKS5 proxy")), QNetworkProxy::Socks5Proxy); connect(proxyTypeGroup, #if (QT_VERSION >= QT_VERSION_CHECK(5, 7, 0)) QOverload::of(&QButtonGroup::buttonToggled), #else static_cast(&QButtonGroup::buttonToggled), #endif this, &NetworkConfigDialog::maybeDisableControls); maybeDisableControls(); auto hostLabel = makeBuddyLabel(tr("Host"), proxyHostName); auto portLabel = makeBuddyLabel(tr("Port"), proxyPort); auto userLabel = makeBuddyLabel(tr("User name"), proxyUserName); proxyPort->setRange(0, 65535); proxyPort->setSpecialValueText(QStringLiteral(" ")); // Now laying all this out auto proxyTypeLayout = new QGridLayout; auto radios = proxyTypeGroup->buttons(); proxyTypeLayout->addWidget(radios[0], 0, 0); for (int i = 2; i <= radios.size(); ++i) // Consider i as 1-based index proxyTypeLayout->addWidget(radios[i - 1], i / 2, i % 2); auto hostPortLayout = new QHBoxLayout; for (auto l: { hostLabel, portLabel }) { hostPortLayout->addWidget(l); hostPortLayout->addWidget(l->buddy()); } auto userNameLayout = new QHBoxLayout; userNameLayout->addWidget(userLabel); userNameLayout->addWidget(userLabel->buddy()); auto proxySettingsLayout = new QVBoxLayout(useProxyBox); proxySettingsLayout->addLayout(proxyTypeLayout); proxySettingsLayout->addLayout(hostPortLayout); proxySettingsLayout->addLayout(userNameLayout); addWidget(useProxyBox); } NetworkConfigDialog::~NetworkConfigDialog() = default; void NetworkConfigDialog::maybeDisableControls() { if (useProxyBox->isChecked()) { bool disable = proxyTypeGroup->checkedId() == -1 || proxyTypeGroup->checkedId() == QNetworkProxy::NoProxy; proxyHostName->setDisabled(disable); proxyPort->setDisabled(disable); proxyUserName->setDisabled(disable); } } void NetworkConfigDialog::apply() { QMatrixClient::NetworkSettings networkSettings; auto proxyType = useProxyBox->isChecked() ? QNetworkProxy::ProxyType(proxyTypeGroup->checkedId()) : QNetworkProxy::DefaultProxy; networkSettings.setProxyType(proxyType); networkSettings.setProxyHostName(proxyHostName->text()); networkSettings.setProxyPort(quint16(proxyPort->value())); networkSettings.setupApplicationProxy(); // Should we do something for authentication at all?.. accept(); } void NetworkConfigDialog::load() { QMatrixClient::NetworkSettings networkSettings; auto proxyType = networkSettings.proxyType(); if (proxyType == QNetworkProxy::DefaultProxy) { useProxyBox->setChecked(false); } else { useProxyBox->setChecked(true); if (auto b = proxyTypeGroup->button(proxyType)) b->setChecked(true); } proxyHostName->setText(networkSettings.proxyHostName()); auto port = networkSettings.proxyPort(); if (port > 0) proxyPort->setValue(port); } Quaternion-0.0.9.4c/client/networkconfigdialog.h000066400000000000000000000026741347141001300216430ustar00rootroot00000000000000/****************************************************************************** * Copyright (C) 2017 Kitsune Ral * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #pragma once #include "dialog.h" class QGroupBox; class QButtonGroup; class QLineEdit; class QSpinBox; class NetworkConfigDialog : public Dialog { Q_OBJECT public: explicit NetworkConfigDialog(QWidget* parent = nullptr); ~NetworkConfigDialog(); private slots: void apply() override; void load() override; void maybeDisableControls(); private: QGroupBox* useProxyBox; QButtonGroup* proxyTypeGroup; QLineEdit* proxyHostName; QSpinBox* proxyPort; QLineEdit* proxyUserName; }; Quaternion-0.0.9.4c/client/qml/000077500000000000000000000000001347141001300162135ustar00rootroot00000000000000Quaternion-0.0.9.4c/client/qml/ActiveLabel.qml000066400000000000000000000004601347141001300211010ustar00rootroot00000000000000import QtQuick 2.2 import QtQuick.Controls 1.4 Label { signal clicked font.italic: true textFormat: Text.PlainText MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor acceptedButtons: Qt.LeftButton onClicked: parent.clicked() } } Quaternion-0.0.9.4c/client/qml/Attachment.qml000066400000000000000000000021111347141001300210110ustar00rootroot00000000000000import QtQuick 2.0 import QMatrixClient 1.0 Item { width: parent.width height: visible ? childrenRect.height : 0 property bool openOnFinished: false readonly property bool downloaded: progressInfo && !progressInfo.isUpload && progressInfo.completed onDownloadedChanged: { if (downloaded && openOnFinished) openLocalFile() } function openExternally() { if (progressInfo.localPath.toString() || downloaded) openLocalFile() else { openOnFinished = true room.downloadFile(eventId) } } function openLocalFile() { if (Qt.openUrlExternally(progressInfo.localPath)) return; controller.showStatusMessage( "Couldn't determine how to open the file, " + "opening its folder instead", 5000) if (Qt.openUrlExternally(progressInfo.localDir)) return; controller.showStatusMessage( "Couldn't determine how to open the file or its folder.", 5000) } } Quaternion-0.0.9.4c/client/qml/FileContent.qml000066400000000000000000000054411347141001300211440ustar00rootroot00000000000000import QtQuick 2.0 import QtQuick.Controls 1.4 import QtQuick.Layouts 1.1 Attachment { TextEdit { id: fileTransferInfo width: parent.width selectByMouse: true; readOnly: true; font: timelabel.font color: textColor renderType: settings.render_type text: qsTr("Size: %1, declared type: %2") .arg(content.info ? humanSize(content.info.size) : "") .arg(content.info ? content.info.mimetype : "unknown") + (progressInfo && progressInfo.isUpload ? " (" + (progressInfo.completed ? qsTr("uploaded from %1", "%1 is a local file name") : qsTr("being uploaded from %1", "%1 is a local file name")) .arg(progressInfo.localPath) + ')' : downloaded ? " (" + qsTr("downloaded to %1", "%1 is a local file name") .arg(progressInfo.localPath) + ')' : "") textFormat: TextEdit.PlainText wrapMode: Text.Wrap; MouseArea { anchors.fill: parent acceptedButtons: Qt.NoButton hoverEnabled: true cursorShape: Qt.IBeamCursor onContainsMouseChanged: controller.showStatusMessage(containsMouse ? room.fileSource(eventId) : "") } } ProgressBar { id: transferProgress visible: progressInfo && progressInfo.started anchors.fill: fileTransferInfo value: progressInfo ? progressInfo.progress / progressInfo.total : -1 indeterminate: !progressInfo || progressInfo.progress < 0 } RowLayout { anchors.top: fileTransferInfo.bottom width: parent.width spacing: 2 CheckBox { id: openOnFinishedFlag text: qsTr("Open after downloading") visible: progressInfo && !progressInfo.isUpload && transferProgress.visible checked: openOnFinished } Button { text: qsTr("Cancel") visible: progressInfo && progressInfo.started onClicked: room.cancelFileTransfer(eventId) } Button { text: qsTr("Save as...") visible: !progressInfo || (!progressInfo.isUpload && !progressInfo.started) onClicked: controller.saveFileAs(eventId) } Button { text: qsTr("Open") visible: !openOnFinishedFlag.visible onClicked: openExternally() } Button { text: qsTr("Open folder") visible: progressInfo && progressInfo.localDir onClicked: Qt.openUrlExternally(progressInfo.localDir) } } } Quaternion-0.0.9.4c/client/qml/ImageContent.qml000066400000000000000000000040431347141001300213040ustar00rootroot00000000000000import QtQuick 2.0 import QtQuick.Controls 1.4 import QtQuick.Layouts 1.1 Attachment { property var sourceSize property url source property var maxHeight property bool autoload Image { readonly property real imageMaxHeight: maxHeight - buttons.height id: imageContent width: parent.width height: sourceSize.height * Math.min(imageMaxHeight / sourceSize.height * 0.9, Math.min(width / sourceSize.width, 1)) fillMode: Image.PreserveAspectFit horizontalAlignment: Image.AlignLeft source: parent.source sourceSize: parent.sourceSize // Behavior on height { NumberAnimation { // duration: settings.fast_animations_duration_ms // easing.type: Easing.OutQuad // }} MouseArea { anchors.fill: parent acceptedButtons: Qt.LeftButton hoverEnabled: true cursorShape: Qt.PointingHandCursor onContainsMouseChanged: controller.showStatusMessage(containsMouse ? room.fileSource(eventId) : "") onClicked: openExternally() } Component.onCompleted: if (visible && autoload && !(progressInfo && progressInfo.isUpload)) room.downloadFile(eventId) } RowLayout { id: buttons anchors.top: imageContent.bottom width: parent.width spacing: 2 Button { text: qsTr("Cancel") visible: progressInfo.started onClicked: room.cancelFileTransfer(eventId) } Button { text: qsTr("Open externally") onClicked: openExternally() } Button { text: qsTr("Download full size") visible: !autoload && !progressInfo.active onClicked: room.downloadFile(eventId) } Button { text: qsTr("Save as...") onClicked: controller.saveFileAs(eventId) } } } Quaternion-0.0.9.4c/client/qml/Timeline.qml000066400000000000000000000411461347141001300205020ustar00rootroot00000000000000import QtQuick 2.2 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.0 import QtQuick.Layouts 1.1 import QMatrixClient 1.0 Rectangle { id: root Settings { id: settings readonly property bool autoload_images: value("UI/autoload_images", true) readonly property string render_type: value("UI/Fonts/render_type", "NativeRendering") readonly property int animations_duration_ms_impl: value("UI/animations_duration_ms", 400) readonly property int animations_duration_ms: animations_duration_ms_impl == 0 ? 10 : animations_duration_ms_impl readonly property int fast_animations_duration_ms: animations_duration_ms / 2 readonly property bool use_shuttle_dial: value("UI/use_shuttle_dial", true) } SystemPalette { id: defaultPalette; colorGroup: SystemPalette.Active } SystemPalette { id: disabledPalette; colorGroup: SystemPalette.Disabled } color: defaultPalette.base function humanSize(bytes) { if (!bytes) return qsTr("Unknown", "Unknown attachment size") if (bytes < 4000) return qsTr("%1 bytes").arg(bytes) bytes = Math.round(bytes / 100) / 10 if (bytes < 2000) return qsTr("%1 KB").arg(bytes) bytes = Math.round(bytes / 100) / 10 if (bytes < 2000) return qsTr("%1 MB").arg(bytes) return qsTr("%1 GB").arg(Math.round(bytes / 100) / 10) } Rectangle { id: roomHeader anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top height: headerText.height + 5 color: defaultPalette.window border.color: disabledPalette.windowText visible: room Image { id: roomAvatar anchors.top: parent.top anchors.left: parent.left anchors.margins: 2 height: headerText.height source: room && room.avatarMediaId ? "image://mtx/" + room.avatarMediaId : "" fillMode: Image.PreserveAspectFit Behavior on width { NumberAnimation { duration: settings.animations_duration_ms easing.type: Easing.OutQuad }} } Column { id: headerText anchors.left: roomAvatar.right anchors.right: parent.right anchors.top: parent.top anchors.margins: 2 spacing: 2 TextEdit { id: roomName width: parent.width readonly property bool hasName: room && room.displayName !== "" text: hasName ? room.displayName : qsTr("(no name)") color: (hasName ? defaultPalette : disabledPalette).windowText ToolTip { text: parent.text } font.bold: true renderType: settings.render_type readOnly: true selectByKeyboard: true; selectByMouse: true; } Label { id: versionNotice visible: room && (room.isUnstable || room.successorId !== "") width: parent.width text: !room ? "" : room.successorId !== "" ? qsTr("This room has been upgraded") : room.isUnstable ? qsTr("Unstable room version!") + (room.canSwitchVersions() ? ("\n" + qsTr("Go to Room Settings to upgrade the room")) : "") : "" font.italic: true renderType: settings.render_type ToolTip { text: parent.text } } ScrollView { id: topicField width: parent.width height: Math.min(topicText.contentHeight, room ? root.height / 5 : 0) horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff verticalScrollBarPolicy: Qt.ScrollBarAsNeeded style: ScrollViewStyle { transientScrollBars: true } Behavior on height { NumberAnimation { duration: settings.animations_duration_ms easing.type: Easing.OutQuad }} // FIXME: The below TextEdit+MouseArea is a massive copy-paste // from TimelineItem.qml. We need to make a separate component // for these (RichTextField?). TextEdit { id: topicText width: topicField.width readonly property bool hasTopic: room && room.topic !== "" text: hasTopic ? room.prettyPrint(room.topic) : qsTr("(no topic)") color: (hasTopic ? defaultPalette : disabledPalette).windowText textFormat: TextEdit.RichText renderType: settings.render_type readOnly: true selectByKeyboard: true; selectByMouse: true; wrapMode: TextEdit.Wrap onHoveredLinkChanged: controller.showStatusMessage(hoveredLink) onLinkActivated: { if (link === "#mention") { controller.insertMention(author) controller.focusInput() } else if (link.startsWith("https://matrix.to/#/@")) { controller.resourceRequested(link, "mention") controller.focusInput() } else if (link.startsWith("https://matrix.to/")) controller.resourceRequested(link) else Qt.openUrlExternally(link) } } } } MouseArea { anchors.fill: headerText acceptedButtons: Qt.MiddleButton cursorShape: topicText.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor onClicked: { if (topicText.hoveredLink) controller.resourceRequested(topicText.hoveredLink) } } } ScrollView { id: chatScrollView anchors.top: roomHeader.bottom anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom anchors.rightMargin: if (settings.use_shuttle_dial) { shuttleDial.width } horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff verticalScrollBarPolicy: settings.use_shuttle_dial ? Qt.ScrollBarAlwaysOff : Qt.ScrollBarAlwaysOn style: ScrollViewStyle { transientScrollBars: true } ListView { id: chatView model: messageModel delegate: TimelineItem { width: chatView.width view: chatView moving: chatView.moving || shuttleDial.value } verticalLayoutDirection: ListView.BottomToTop flickableDirection: Flickable.VerticalFlick flickDeceleration: 8000 boundsBehavior: Flickable.StopAtBounds // pixelAligned: true cacheBuffer: 200 section.property: "section" property int largestVisibleIndex: count > 0 ? indexAt(contentX, contentY + height - 1) : -1 readonly property bool loadingHistory: room ? room.eventsHistoryJob : false onLoadingHistoryChanged: console.log("loadingHistory="+loadingHistory) function ensurePreviousContent() { if (loadingHistory) return // Snapshot the current speed, or assume we scroll 10 screens/s var curVelocity = moving ? -verticalVelocity : height * 10 // Check if we're about to bump into the ceiling in 2 seconds if (curVelocity > 0 && contentY - curVelocity*2 < originY) { // Request the amount of messages enough to scroll // at this rate for 3 more seconds var avgEventHeight = contentHeight / count room.getPreviousContent(curVelocity*3 / avgEventHeight); } } function saveViewport() { room.saveViewport(indexAt(contentX, contentY), largestVisibleIndex) } function onModelAboutToReset() { console.log("Resetting timeline model") contentYChanged.disconnect(ensurePreviousContent) } function onModelReset() { if (room) { var lastScrollPosition = room.savedTopVisibleIndex() contentYChanged.connect(ensurePreviousContent) if (lastScrollPosition === 0) positionViewAtBeginning() else { console.log("Scrolling to position", lastScrollPosition) positionViewAtIndex(lastScrollPosition, ListView.Contain) } if (contentY < originY + 10) room.getPreviousContent(100) } console.log("Model timeline reset") } Component.onCompleted: { console.log("QML view loaded") model.modelAboutToBeReset.connect(onModelAboutToReset) model.modelReset.connect(onModelReset) controller.pageUpPressed.connect(function() { contentY = Math.max(originY, contentY - height) }) controller.pageDownPressed.connect(function() { contentY = Math.min(originY + contentHeight - height, contentY + height) }) } onMovementEnded: saveViewport() displaced: Transition { NumberAnimation { property: "y"; duration: settings.fast_animations_duration_ms easing.type: Easing.OutQuad }} Behavior on contentY { enabled: !chatView.moving SmoothedAnimation { id: scrollAnimation duration: settings.fast_animations_duration_ms / 4 maximumEasingTime: settings.fast_animations_duration_ms / 2 onRunningChanged: { if (!running) chatView.saveViewport() } }} Keys.onUpPressed: { contentY = Math.max(originY, contentY - height / 5) event.accepted = true } Keys.onDownPressed: { contentY = Math.min(originY + contentHeight - height, contentY + height / 5) event.accepted = true } // itemAt is a function, not a property so is not bound to new items // showing up underneath; contentHeight is used for that instead. readonly property var underlayingItem: contentHeight >= height && itemAt(contentX, contentY + sectionBanner.height - 2) readonly property bool sectionBannerVisible: underlayingItem && (!underlayingItem.sectionVisible || underlayingItem.y < contentY) Rectangle { id: sectionBanner z: 3 // On top of ListView sections that have z=2 anchors.left: parent.left anchors.top: parent.top width: childrenRect.width + 2 height: childrenRect.height + 2 visible: chatView.sectionBannerVisible color: defaultPalette.window opacity: 0.9 Label { font.bold: true color: disabledPalette.text renderType: settings.render_type text: chatView.underlayingItem ? chatView.underlayingItem.ListView.section : "" } } } } Slider { id: shuttleDial orientation: Qt.Vertical height: chatScrollView.height anchors.right: parent.right anchors.verticalCenter: chatScrollView.verticalCenter enabled: settings.use_shuttle_dial visible: enabled style: SliderStyle { // Width and height are swapped below because SliderStyle assumes // a horizontal slider - but anchors are not :-\ groove: Rectangle { color: defaultPalette.window border.color: defaultPalette.midlight implicitHeight: 8 Rectangle { anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right implicitHeight: 2 width: chatView.largestVisibleIndex < 0 ? 0 : chatView.height * (1 - chatView.largestVisibleIndex / chatView.count) color: defaultPalette.highlight } } handle: Rectangle { anchors.centerIn: parent color: defaultPalette.button border.color: defaultPalette.buttonText border.width: 1 implicitWidth: 14 implicitHeight: 8 visible: chatView.count > 0 } } maximumValue: 10.0 minimumValue: -10.0 activeFocusOnPress: false activeFocusOnTab: false // wheelEnabled: false // Only available in QQC 1.6, Qt 5.10 onPressedChanged: { if (!pressed) value = 0 } onValueChanged: { if (value) chatView.flick(0, parent.height * value) } Component.onCompleted: { // Continue scrolling while the shuttle is held out of 0 chatView.flickEnded.connect(shuttleDial.valueChanged) } } MouseArea { id: scrollerArea anchors.top: chatScrollView.top anchors.bottom: chatScrollView.bottom anchors.right: parent.right width: settings.use_shuttle_dial ? shuttleDial.width : chatScrollView.width - chatView.width acceptedButtons: Qt.NoButton // FIXME: propagate wheel event to chatView onWheel: { wheel.accepted = settings.use_shuttle_dial } hoverEnabled: true } Rectangle { anchors.right: scrollerArea.left anchors.top: chatScrollView.top width: childrenRect.width + 3 height: childrenRect.height + 3 visible: chatView.largestVisibleIndex >= 0 && (scrollerArea.containsMouse || scrollAnimation.running) color: defaultPalette.window opacity: 0.9 Label { font.bold: true color: disabledPalette.text renderType: settings.render_type text: qsTr("%Ln events back from now (%L1 cached%2)", "%2 is optional 'and loading'", chatView.largestVisibleIndex) .arg(chatView.count) .arg(chatView.loadingHistory ? (" " + qsTr("and loading")) : "") } } Rectangle { id: scrollindicator opacity: chatView.atYEnd ? 0 : 0.5 color: defaultPalette.text height: 30 radius: height/2 width: height anchors.left: parent.left anchors.bottom: parent.bottom anchors.leftMargin: width/2 anchors.bottomMargin: chatView.atYEnd ? -height : height/2 Behavior on opacity { NumberAnimation { duration: settings.animations_duration_ms easing.type: Easing.OutQuad }} Behavior on anchors.bottomMargin { NumberAnimation { duration: settings.animations_duration_ms easing.type: Easing.OutQuad }} Image { anchors.fill: parent source: "qrc:///scrolldown.svg" } MouseArea { anchors.fill: parent onClicked: { chatView.positionViewAtBeginning() chatView.saveViewport() } cursorShape: Qt.PointingHandCursor } } } Quaternion-0.0.9.4c/client/qml/TimelineItem.qml000066400000000000000000000526221347141001300213220ustar00rootroot00000000000000import QtQuick 2.6 import QtQuick.Controls 1.4 import QtQuick.Controls 2.0 as QQC2 //import QtGraphicalEffects 1.0 // For fancy highlighting import QMatrixClient 1.0 Item { // Supplementary components SystemPalette { id: defaultPalette; colorGroup: SystemPalette.Active } SystemPalette { id: disabledPalette; colorGroup: SystemPalette.Disabled } Settings { id: settings readonly property bool condense_chat: value("UI/condense_chat", false) readonly property bool autoload_images: value("UI/autoload_images", true) readonly property string highlight_mode: value("UI/highlight_mode", "background") readonly property string highlight_color: value("UI/highlight_color", "orange") readonly property string render_type: value("UI/Fonts/render_type", "NativeRendering") readonly property int animations_duration_ms: value("UI/animations_duration_ms", 400) readonly property int fast_animations_duration_ms: animations_duration_ms / 2 readonly property string timeline_style: value("UI/timeline_style", "") readonly property bool show_author_avatars: value("UI/show_author_avatars", timeline_style != "xchat") } // Property interface /** Determines whether the view is moving at the moment */ property var view property bool moving: view.moving // TimelineItem definition visible: marks !== EventStatus.Hidden enabled: visible height: childrenRect.height * visible readonly property bool sectionVisible: section !== aboveSection readonly property bool authorSectionVisible: sectionVisible || author !== aboveAuthor readonly property bool redacted: marks === EventStatus.Redacted readonly property bool pending: [ EventStatus.Submitted, EventStatus.Departed, EventStatus.ReachedServer, EventStatus.SendingFailed ].indexOf(marks) != -1 readonly property bool failed: marks === EventStatus.SendingFailed readonly property string textColor: marks === EventStatus.Submitted || failed ? defaultPalette.mid : marks === EventStatus.Departed ? disabledPalette.text : redacted ? disabledPalette.text : highlight && settings.highlight_mode == "text" ? settings.highlight_color : (["state", "notice", "other"].indexOf(eventType) >= 0) ? disabledPalette.text : defaultPalette.text readonly property string authorName: room && room.safeMemberName(author.id) readonly property bool xchatStyle: settings.timeline_style === "xchat" readonly property bool actionEvent: eventType == "state" || eventType == "emote" // A message is considered shown if its bottom is within the // viewing area of the timeline. readonly property bool shown: y + message.height - 1 > view.contentY && y + message.height - 1 < view.contentY + view.height onShownChanged: { if (!pending) controller.onMessageShownChanged(eventId, shown) } Component.onCompleted: { if (shown) shownChanged(true); } // NumberAnimation on opacity { // from: 0; to: 1 // // Reduce duration when flicking/scrolling // duration: settings.fast_animations_duration_ms // // Give time for chatView.displaced to complete // easing.type: Easing.InExpo // } // Behavior on height { NumberAnimation { // duration: settings.fast_animations_duration_ms // easing.type: Easing.OutQuad // }} Column { id: fullMessage width: parent.width Rectangle { width: parent.width height: childrenRect.height + 2 visible: sectionVisible color: defaultPalette.window Label { font.bold: true renderType: settings.render_type text: section } } Loader { id: detailsAreaLoader // asynchronous: true // https://bugreports.qt.io/browse/QTBUG-50992 active: visible visible: false // Controlled by showDetailsButton opacity: 0 width: parent.width sourceComponent: detailsArea } Item { id: message width: parent.width height: childrenRect.height // There are several layout styles (av - author avatar, // al - author label, ts - timestamp, c - content // default (when "timeline_style" is not "xchat"): // av al // ts c // state-emote (default for state and emote events): // av (al+c in a single control // ts spanning both rows) // xchat (when "timeline_style" is "xchat"): // ts av al c // xchat state-emote // ts av *(asterisk) al c Image { id: authorAvatar visible: settings.show_author_avatars && source && (authorSectionVisible || xchatStyle) anchors.left: xchatStyle ? timelabel.right : parent.left anchors.leftMargin: xchatStyle * 3 width: if (!xchatStyle) { timelabel.width } else if (!visible) { 0 } height: xchatStyle ? authorLabel.height : visible ? authorLabel.height * 2 - timelabel.height : 0 fillMode: Image.PreserveAspectFit source: author.avatarMediaId ? "image://mtx/" + author.avatarMediaId : "" } Label { id: authorLabel visible: xchatStyle || (!actionEvent && authorSectionVisible) anchors.left: authorAvatar.right anchors.leftMargin: 2 anchors.top: authorAvatar.top width: if (xchatStyle) { 120 - authorAvatar.width } horizontalAlignment: actionEvent ? Text.AlignRight : Text.AlignLeft elide: Text.ElideRight color: textColor textFormat: Label.PlainText font.bold: !xchatStyle renderType: settings.render_type text: (actionEvent ? "* " : "") + authorName } MouseArea { anchors.left: authorAvatar.left anchors.right: authorLabel.right anchors.top: authorLabel.top anchors.bottom: authorLabel.bottom cursorShape: Qt.PointingHandCursor onClicked: { controller.insertMention(author) controller.focusInput() } } Label { id: timelabel anchors.top: xchatStyle ? authorAvatar.top : authorAvatar.bottom anchors.topMargin: 1 anchors.bottomMargin: 1 anchors.left: parent.left color: disabledPalette.text textFormat: Text.RichText renderType: settings.render_type font.italic: pending text: "<" + time.toLocaleTimeString(Qt.locale(), "hh:mm") + ">" } Item { id: highlighter anchors.fill: textField visible: highlight && settings.highlight_mode != "text" // Uncomment for fancy highlighting // RectangularGlow { // anchors.fill: parent // glowRadius: 5 // cornerRadius: 2 // color: settings.highlight_color // cached: true // } // Rectangle { // anchors.fill: parent // border.color: settings.highlight_color // border.width: 1 // } Rectangle { anchors.fill: parent opacity: 0.2 color: settings.highlight_color radius: 2 } } Item { id: textField anchors.top: !xchatStyle && authorLabel.visible ? authorLabel.bottom : authorAvatar.top anchors.left: xchatStyle ? authorLabel.right : timelabel.right anchors.leftMargin: 1 anchors.right: resendButton.left anchors.rightMargin: 1 height: textFieldImpl.height clip: true TextEdit { id: textFieldImpl anchors.top: textField.top width: parent.width leftPadding: 2 rightPadding: 2 x: -textScrollBar.position * contentWidth // Doesn't work for attributes function toHtmlEscaped(txt) { // Make sure to replace & first var eTxt = txt.replace(/&/g, '&') .replace(//g, '>') console.log(eTxt) return eTxt } selectByMouse: true readOnly: true textFormat: TextEdit.RichText // FIXME: The text is clumsy and slows down creation text: (actionEvent && !xchatStyle ? ("" + toHtmlEscaped(authorName) + " ") : "" ) + display + (annotation ? "
" + annotation + "" : "") horizontalAlignment: Text.AlignLeft wrapMode: Text.Wrap color: textColor renderType: settings.render_type // TODO: In the code below, links should be resolved // with Qt.resolvedLink, once we figure out what // to do with relative URLs (note: www.google.com // is a relative URL, https://www.google.com is not). // Instead of Qt.resolvedUrl (and, most likely, // QQmlAbstractUrlInterceptor to convert URLs) // we might just prefer to do the whole resolving // in C++. onHoveredLinkChanged: controller.showStatusMessage(hoveredLink) onLinkActivated: { if (link === "#mention") { controller.insertMention(author) controller.focusInput() } else if (link.startsWith("https://matrix.to/#/@")) { controller.resourceRequested(link, "mention") controller.focusInput() } else if (link.startsWith("https://matrix.to/")) controller.resourceRequested(link) else Qt.openUrlExternally(link) } } MouseArea { anchors.fill: parent acceptedButtons: Qt.MiddleButton onClicked: { if (textFieldImpl.hoveredLink) controller.resourceRequested( textFieldImpl.hoveredLink) } } MouseArea { anchors.fill: parent cursorShape: textFieldImpl.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor acceptedButtons: Qt.NoButton onWheel: { if (wheel.angleDelta.x != 0 && textFieldImpl.width < textFieldImpl.contentWidth) { if (wheel.pixelDelta.x != 0) textScrollBar.position -= wheel.pixelDelta.x / width else textScrollBar.position -= wheel.angleDelta.x / 6 / width textScrollBar.position = Math.min(1, Math.max(0, textScrollBar.position)) } else wheel.accepted = false } } QQC2.ScrollBar { id: textScrollBar hoverEnabled: true visible: textFieldImpl.contentWidth > textFieldImpl.width active: visible orientation: Qt.Horizontal size: textFieldImpl.width / textFieldImpl.contentWidth anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom } } Loader { active: eventType == "image" anchors.top: textField.bottom anchors.left: textField.left anchors.right: textField.right sourceComponent: ImageContent { property var info: !progressInfo.isUpload && !progressInfo.active && content.info && content.info.thumbnail_info ? content.info.thumbnail_info : content.info sourceSize: if (info) { Qt.size(info.w, info.h) } source: downloaded || progressInfo.isUpload ? progressInfo.localPath : content.info && content.info.thumbnail_info ? "image://mtx/" + content.thumbnailMediaId : "" maxHeight: chatView.height - textField.height - authorLabel.height * !xchatStyle autoload: settings.autoload_images } } Loader { active: eventType == "file" anchors.top: textField.bottom anchors.left: textField.left anchors.right: textField.right height: childrenRect.height sourceComponent: FileContent { } } ActiveLabel { id: resendButton visible: failed width: visible * implicitWidth anchors.top: textField.top anchors.right: discardButton.left anchors.rightMargin: 2 text: qsTr("Resend") onClicked: room.retryMessage(eventId) } ActiveLabel { id: discardButton visible: pending && marks !== EventStatus.ReachedServer width: visible * implicitWidth anchors.top: textField.top anchors.right: showDetailsButton.left anchors.rightMargin: 2 text: qsTr("Discard") onClicked: room.discardMessage(eventId) } ToolButton { id: showDetailsButton anchors.top: textField.top anchors.right: parent.right height: settings.condense_chat && textField.visible ? Math.min(implicitHeight, textField.height) : implicitHeight text: "..." action: Action { id: showDetails tooltip: "Show details and actions" checkable: true } onCheckedChanged: SequentialAnimation { PropertyAction { target: detailsAreaLoader; property: "visible" value: true } NumberAnimation { target: detailsAreaLoader; property: "opacity" to: showDetails.checked duration: settings.fast_animations_duration_ms easing.type: Easing.OutQuad } PropertyAction { target: detailsAreaLoader; property: "visible" value: showDetails.checked } } } } } Rectangle { id: readMarkerLine width: readMarker && parent.width height: 3 anchors.horizontalCenter: fullMessage.horizontalCenter anchors.bottom: fullMessage.bottom Behavior on width { NumberAnimation { duration: settings.animations_duration_ms easing.type: Easing.OutQuad }} gradient: Gradient { GradientStop { position: 0; color: "transparent" } GradientStop { position: 1; color: defaultPalette.highlight } } } // Components loaded on demand Component { id: detailsArea Rectangle { height: childrenRect.height radius: 5 color: defaultPalette.button border.color: defaultPalette.mid readonly property url evtLink: "https://matrix.to/#/" + room.id + "/" + eventId property string sourceText: toolTip Item { id: detailsHeader width: parent.width height: childrenRect.height anchors.top: parent.top TextEdit { text: "<" + time.toLocaleString(Qt.locale(), Locale.ShortFormat) + ">" font.bold: true renderType: settings.render_type readOnly: true selectByKeyboard: true; selectByMouse: true anchors.left: parent.left anchors.leftMargin: 3 anchors.verticalCenter: copyLinkButton.verticalCenter z: 1 } TextEdit { text: ""+ eventId + " (" + eventResolvedType + ")" textFormat: Text.RichText font.bold: true renderType: settings.render_type horizontalAlignment: Text.AlignHCenter readOnly: true selectByKeyboard: true; selectByMouse: true width: parent.width anchors.top: copyLinkButton.bottom onLinkActivated: Qt.openUrlExternally(link) MouseArea { anchors.fill: parent cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor acceptedButtons: Qt.NoButton } } Button { id: redactButton text: qsTr("Redact") anchors.right: quoteButton.left z: 1 onClicked: { room.redactEvent(eventId) showDetails.checked = false } } Button { id: quoteButton text: qsTr("Quote", "a verb (do quote), not a noun (a quote)") anchors.right: copyLinkButton.left z: 1 onClicked: { controller.quote(display) showDetails.checked = false } } Button { id: copyLinkButton text: qsTr("Copy link to clipboard") anchors.right: parent.right z: 1 onClicked: { permalink.selectAll() permalink.copy() showDetails.checked = false } } TextEdit { id: permalink text: evtLink renderType: settings.render_type width: 0; height: 0; visible: false } } TextArea { text: sourceText; textFormat: Text.PlainText readOnly: true; font.family: "Monospace" // FIXME: make settings.render_type an integer (but store as string to stay human-friendly) // style: TextAreaStyle { // renderType: settings.render_type // } selectByKeyboard: true; selectByMouse: true; width: parent.width anchors.top: detailsHeader.bottom } } } } Quaternion-0.0.9.4c/client/qml/ToolTip.qml000066400000000000000000000021161347141001300203200ustar00rootroot00000000000000import QtQuick 2.0 Rectangle { id:tooltip property alias text: textContainer.text property int verticalPadding: 1 property int horizontalPadding: 5 property Item zParent: parent width: textContainer.width + horizontalPadding * 2 height: textContainer.height + verticalPadding * 2 color: "#aa999999" Text { anchors.centerIn: parent id:textContainer text: "Gering geding ding ding!" } NumberAnimation { id: fadein target: tooltip property: "opacity" easing.type: Easing.InOutQuad duration: 300 from: 0 to: 1 } NumberAnimation { id: fadeout target: tooltip property: "opacity" easing.type: Easing.InOutQuad from: 1 to: 0 onStopped: visible = false; } visible:false onVisibleChanged: if(visible)fadein.start(); function show(){ visible = true; zParent.z = 20; fadein.start(); } function hide(){ this.parent.parent.parent.z = 0; fadeout.start(); } } Quaternion-0.0.9.4c/client/qml/ToolTipArea.qml000066400000000000000000000013471347141001300211160ustar00rootroot00000000000000// from https://github.com/bobbaluba/qmltooltip import QtQuick 2.0 MouseArea { property alias tip: tip property alias text: tip.text property alias hideDelay: hideTimer.interval property alias showDelay: showTimer.interval property bool enabled: true id: mouseArea z: 21 acceptedButtons: Qt.NoButton anchors.fill: parent hoverEnabled: true Timer { id:showTimer interval: 1000 running: mouseArea.containsMouse && !tip.visible && mouseArea.enabled onTriggered: tip.show(); } Timer { id:hideTimer interval: 100 running: !mouseArea.containsMouse && tip.visible onTriggered: tip.hide(); } ToolTip{ id:tip } } Quaternion-0.0.9.4c/client/quaternionroom.cpp000066400000000000000000000122061347141001300212110ustar00rootroot00000000000000/************************************************************************** * * * Copyright (C) 2016 Felix Rohrbach * * * * 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 . * * * **************************************************************************/ #include "quaternionroom.h" #include #include #include using namespace QMatrixClient; QuaternionRoom::QuaternionRoom(Connection* connection, QString roomId, JoinState joinState) : Room(connection, std::move(roomId), joinState) { connect( this, &QuaternionRoom::notificationCountChanged, this, &QuaternionRoom::countChanged ); connect( this, &QuaternionRoom::highlightCountChanged, this, &QuaternionRoom::countChanged ); } const QString& QuaternionRoom::cachedInput() const { return m_cachedInput; } void QuaternionRoom::setCachedInput(const QString& input) { m_cachedInput = input; } const QString& QuaternionRoom::cachedUserFilter() const { return m_cachedUserFilter; } void QuaternionRoom::setCachedUserFilter(const QString& input) { m_cachedUserFilter = input; } bool QuaternionRoom::isEventHighlighted(const RoomEvent* e) const { return highlights.contains(e); } int QuaternionRoom::savedTopVisibleIndex() const { return firstDisplayedMarker() == timelineEdge() ? 0 : firstDisplayedMarker() - messageEvents().rbegin(); } int QuaternionRoom::savedBottomVisibleIndex() const { return lastDisplayedMarker() == timelineEdge() ? 0 : lastDisplayedMarker() - messageEvents().rbegin(); } void QuaternionRoom::saveViewport(int topIndex, int bottomIndex) { if (topIndex == -1 || bottomIndex == -1 || (bottomIndex == savedBottomVisibleIndex() && (bottomIndex == 0 || topIndex == savedTopVisibleIndex()))) return; if (bottomIndex == 0) { qDebug() << "Saving viewport as the latest available"; setFirstDisplayedEventId({}); setLastDisplayedEventId({}); return; } qDebug() << "Saving viewport:" << topIndex << "thru" << bottomIndex; setFirstDisplayedEvent(maxTimelineIndex() - topIndex); setLastDisplayedEvent(maxTimelineIndex() - bottomIndex); } QString QuaternionRoom::prettyPrint(const QString& plainText) const { return Room::prettyPrint(plainText); } bool QuaternionRoom::canSwitchVersions() const { return Room::canSwitchVersions(); } QString QuaternionRoom::safeMemberName(const QString& userId) const { return sanitized(roomMembername(userId)); } void QuaternionRoom::countChanged() { if( displayed() && !hasUnreadMessages() ) { resetNotificationCount(); resetHighlightCount(); } } void QuaternionRoom::onAddNewTimelineEvents(timeline_iter_t from) { std::for_each(from, messageEvents().cend(), [this] (const TimelineItem& ti) { checkForHighlights(ti); }); } void QuaternionRoom::onAddHistoricalTimelineEvents(rev_iter_t from) { std::for_each(from, messageEvents().crend(), [this] (const TimelineItem& ti) { checkForHighlights(ti); }); } void QuaternionRoom::checkForHighlights(const QMatrixClient::TimelineItem& ti) { auto localUserId = localUser()->id(); if (ti->senderId() == localUserId) return; if (auto* e = ti.viewAs()) { const QRegularExpression localUserRe("(\\W|^)" + localUserId + "(\\W|$)", QRegularExpression::MultilineOption | QRegularExpression::CaseInsensitiveOption); const QRegularExpression roomMembernameRe("(\\W|^)" + roomMembername(localUserId) + "(\\W|$)", QRegularExpression::MultilineOption | QRegularExpression::CaseInsensitiveOption); const auto& text = e->plainBody(); QRegularExpressionMatch localMatch = localUserRe.match(text, 0, QRegularExpression::PartialPreferFirstMatch); QRegularExpressionMatch roomMemberMatch = roomMembernameRe.match(text, 0, QRegularExpression::PartialPreferFirstMatch); if (localMatch.hasMatch() || roomMemberMatch.hasMatch()) highlights.insert(e); } } Quaternion-0.0.9.4c/client/quaternionroom.h000066400000000000000000000057521347141001300206660ustar00rootroot00000000000000/************************************************************************** * * * Copyright (C) 2016 Felix Rohrbach * * * * 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 . * * * **************************************************************************/ #pragma once #include class QuaternionRoom: public QMatrixClient::Room { Q_OBJECT public: QuaternionRoom(QMatrixClient::Connection* connection, QString roomId, QMatrixClient::JoinState joinState); const QString& cachedInput() const; void setCachedInput(const QString& input); const QString& cachedUserFilter() const; void setCachedUserFilter(const QString& input); bool isEventHighlighted(const QMatrixClient::RoomEvent* e) const; Q_INVOKABLE int savedTopVisibleIndex() const; Q_INVOKABLE int savedBottomVisibleIndex() const; Q_INVOKABLE void saveViewport(int topIndex, int bottomIndex); // FIXME: This should be deleted as soon as Room::prettyPrint and // Room::canSwitchVersions() become Q_INVOKABLE (canSwitchVersions() // may - or may not - become a Q_PROPERTY even). Q_INVOKABLE QString prettyPrint(const QString& plainText) const; Q_INVOKABLE bool canSwitchVersions() const; /*! Get a display-safe member name in the context of this room * Display-safe means HTML-safe + without RLO/LRO markers (see #545) */ Q_INVOKABLE QString safeMemberName(const QString& userId) const; private slots: void countChanged(); private: QSet highlights; QString m_cachedInput; QString m_cachedUserFilter; void onAddNewTimelineEvents(timeline_iter_t from) override; void onAddHistoricalTimelineEvents(rev_iter_t from) override; void checkForHighlights(const QMatrixClient::TimelineItem& ti); }; Quaternion-0.0.9.4c/client/resources.qrc000066400000000000000000000015331347141001300201450ustar00rootroot00000000000000 qml/Timeline.qml qml/ToolTip.qml qml/ToolTipArea.qml ../icons/quaternion/128-apps-quaternion.png ../icons/breeze/irc-channel-joined.svg ../icons/breeze/irc-channel-parted.svg ../icons/irc-channel-invited.svg ../icons/scrolldown.svg ../icons/busy_16x16.gif qml/Attachment.qml qml/ImageContent.qml qml/FileContent.qml qml/TimelineItem.qml qml/ActiveLabel.qml Quaternion-0.0.9.4c/client/roomdialogs.cpp000066400000000000000000000453661347141001300204630ustar00rootroot00000000000000/****************************************************************************** * Copyright (C) 2017 Kitsune Ral * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "roomdialogs.h" #include "mainwindow.h" #include "quaternionroom.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include RoomDialogBase::RoomDialogBase(const QString& title, const QString& applyButtonText, QuaternionRoom* r, QWidget* parent, QDialogButtonBox::StandardButtons extraButtons) : Dialog(title, parent, StatusLine, applyButtonText, extraButtons) , room(r), avatar(new QLabel) , roomName(new QLineEdit) , aliasServer(new QLabel), alias(new QLineEdit) , topic(new QPlainTextEdit) , publishRoom(new QCheckBox(tr("Publish room in room directory"))) , guestCanJoin(new QCheckBox(tr("Allow guest accounts to join the room"))) , mainFormLayout(addLayout()) { if (room) { avatar->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); avatar->setPixmap({64, 64}); } topic->setTabChangesFocus(true); topic->setSizeAdjustPolicy( QAbstractScrollArea::AdjustToContentsOnFirstShow); // Layout controls { if (room) { auto* topLayout = new QHBoxLayout; topLayout->addWidget(avatar); { essentialsLayout = new QFormLayout; essentialsLayout->addRow(tr("Room name"), roomName); essentialsLayout->addRow(tr("Primary alias"), alias); topLayout->addLayout(essentialsLayout); } mainFormLayout->addRow(topLayout); } else { mainFormLayout->addRow(tr("Room name"), roomName); auto* aliasLayout = new QHBoxLayout; aliasLayout->addWidget(new QLabel("#")); aliasLayout->addWidget(alias); aliasLayout->addWidget(aliasServer); mainFormLayout->addRow(tr("Primary alias"), aliasLayout); } } mainFormLayout->addRow(tr("Topic"), topic); if (!room) // TODO: Support this in RoomSettingsDialog as well { mainFormLayout->addRow(publishRoom); // formLayout->addRow(guestCanJoin); // TODO: QMatrixClient/libqmatrixclient#36 } } QComboBox* RoomDialogBase::addVersionSelector(QLayout* layout) { auto* versionSelector = new QComboBox; layout->addWidget(versionSelector); { auto* specLink = new QLabel("" + tr("About room versions") + ""); specLink->setOpenExternalLinks(true); layout->addWidget(specLink); } return versionSelector; } void RoomDialogBase::refillVersionSelector(QComboBox* selector, Connection* account) { selector->clear(); if (account->loadingCapabilities()) { selector->addItem( tr("(loading)", "Loading room versions from the server"), QString()); selector->setEnabled(false); // FIXME: It should be connectSingleShot // but sadly connectSingleShot doesn't work with lambdas yet connectUntil(account, &Connection::capabilitiesLoaded, this, [this,selector,account] { refillVersionSelector(selector, account); return true; }); return; } const auto& versions = account->availableRoomVersions(); for (const auto& v: versions) { const bool isDefault = v.id == account->defaultRoomVersion(); const auto postfix = isDefault ? tr("default", "Default room version") : v.isStable() ? tr("stable", "Stable room version") : v.status; selector->addItem(v.id % " (" % postfix % ")", v.id); const auto idx = selector->count() - 1; if (isDefault) { auto font = selector->itemData(idx, Qt::FontRole).value(); font.setBold(true); selector->setItemData(idx, font, Qt::FontRole); selector->setCurrentIndex(idx); } if (!v.isStable()) selector->setItemData(idx, QColor(Qt::red), Qt::ForegroundRole); } selector->setEnabled(true); } void RoomDialogBase::addEssentials(QWidget* accountControl, QLayout* versionBox) { Q_ASSERT(accountControl != nullptr && versionBox != nullptr); auto* layout = essentialsLayout ? essentialsLayout : mainFormLayout; layout->insertRow(0, tr("Account"), accountControl); layout->insertRow(1, tr("Room version"), versionBox); } bool RoomDialogBase::checkRoomVersion(QString version, Connection* account) { if (account->stableRoomVersions().contains(version)) return true; return QMessageBox::warning(this, tr("Continue with unstable version?"), tr("You are using an UNSTABLE room version (%1)." " The server may stop supporting it at any moment." " Do you still want to use this version?").arg(version), QMessageBox::Yes|QMessageBox::No, QMessageBox::No) == QMessageBox::Yes; } RoomSettingsDialog::RoomSettingsDialog(QuaternionRoom* room, MainWindow* parent) : RoomDialogBase(tr("Room settings: %1").arg(room->displayName()), tr("Update room"), room, parent) , account(new QLabel(room->connection()->userId())) , version(new QLabel(room->version())) , tagsList(new QListWidget) { auto* versionBox = new QGridLayout; versionBox->addWidget(version, 0, 0); if (room->isUnstable()) versionBox->addWidget( new QLabel(tr("This version is unstable! Consider upgrading.")), 1, 0); if (room->canSwitchVersions()) { auto* changeActionButton = new QPushButton(tr("Upgrade", "Upgrade a room version")); connect(changeActionButton, &QAbstractButton::clicked, this, [=] { Dialog chooseVersionDlg(tr("Choose new room version"), this, NoStatusLine, tr("Upgrade", "Upgrade a room version"), NoExtraButtons); chooseVersionDlg.addWidget( new QLabel(tr("You are about to upgrade %1.\n" "This operation cannot be reverted.") .arg(room->displayName()))); auto* hBox = chooseVersionDlg.addLayout(); auto* versionSelector = addVersionSelector(hBox); refillVersionSelector(versionSelector, room->connection()); if (chooseVersionDlg.exec() == QDialog::Accepted) { version->setText(versionSelector->currentData().toString()); apply(); } }); versionBox->addWidget(changeActionButton, 0, 1, -1, 1); } addEssentials(account, versionBox); connect(room, &QuaternionRoom::avatarChanged, this, [this, room] { if (!userChangedAvatar) avatar->setPixmap(QPixmap::fromImage(room->avatar(64))); }); avatar->setPixmap(QPixmap::fromImage(room->avatar(64))); tagsList->setSizeAdjustPolicy( QAbstractScrollArea::AdjustToContentsOnFirstShow); tagsList->setUniformItemSizes(true); tagsList->setSelectionMode(QAbstractItemView::ExtendedSelection); mainFormLayout->addRow(tr("Tags"), tagsList); auto* roomIdLabel = new QLabel(room->id()); roomIdLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); mainFormLayout->addRow(tr("Room identifier"), roomIdLabel); // Uncomment to debug room display name calculation code // auto* refreshNameButton = // buttonBox()->addButton(tr("Refresh name"), QDialogButtonBox::ApplyRole); // connect(refreshNameButton, &QPushButton::clicked, // room, &QuaternionRoom::refreshDisplayName); } void RoomSettingsDialog::load() { roomName->setText(room->name()); alias->setText(room->canonicalAlias()); topic->setPlainText(room->topic()); // QPlainTextEdit may change some characters before any editing occurs; // so save this already adjusted topic to compare later. previousTopic = topic->toPlainText(); tagsList->clear(); auto roomTags = room->tagNames(); for (const auto& tag: room->connection()->tagNames()) { auto tagDisplayName = tag == QMatrixClient::FavouriteTag ? tr("Favourites") : tag == QMatrixClient::LowPriorityTag ? tr("Low priority") : tag.startsWith("u.") ? tag.mid(2) : tag; auto* item = new QListWidgetItem(tagDisplayName, tagsList); item->setData(Qt::UserRole, tag); item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsUserCheckable); item->setCheckState( roomTags.contains(tag) ? Qt::Checked : Qt::Unchecked); item->setToolTip(tag); tagsList->addItem(item); } } bool RoomSettingsDialog::validate() { if (room->version() == version->text() || (room->canSwitchVersions() && checkRoomVersion(version->text(), room->connection()))) return true; // The room is the same, or it's allowed to change it version->setText(room->version()); return false; // Cancel applying, stay on the settings dialog } void RoomSettingsDialog::apply() { if (version->text() != room->version()) { using namespace QMatrixClient; setStatusMessage(tr("Creating the new room version, please wait")); connectUntil(room, &Room::upgraded, this, [this] (QString, Room* newRoom) { accept(); static_cast(parent())->selectRoom(newRoom); return true; }); connectSingleShot(room, &Room::upgradeFailed, this, &Dialog::applyFailed); room->switchVersion(version->text()); return; // It's either a version upgrade or everything else } if (roomName->text() != room->name()) room->setName(roomName->text()); if (alias->text() != room->canonicalAlias()) room->setCanonicalAlias(alias->text()); if (topic->toPlainText() != previousTopic) room->setTopic(topic->toPlainText()); auto tags = room->tags(); for (int i = 0; i < tagsList->count(); ++i) { const auto* item = tagsList->item(i); const auto tagName = item->data(Qt::UserRole).toString(); if (item->checkState() == Qt::Checked) tags[tagName]; // Just ensure the tag is there, no overwriting else tags.remove(tagName); } room->setTags(tags); accept(); } class NextInvitee : public QComboBox { public: using QComboBox::QComboBox; private: void focusInEvent(QFocusEvent* event) override { QComboBox::focusInEvent(event); static_cast(parent())->updatePushButtons(); } void focusOutEvent(QFocusEvent* event) override { QComboBox::focusOutEvent(event); static_cast(parent())->updatePushButtons(); } }; class InviteeList : public QListWidget { public: using QListWidget::QListWidget; private: void keyPressEvent(QKeyEvent* event) override { if (event->key() == Qt::Key_Delete) delete takeItem(currentRow()); } void mousePressEvent(QMouseEvent* event) override { if (event->button() == Qt::MidButton) delete takeItem(currentRow()); } }; CreateRoomDialog::CreateRoomDialog(QVector cs, QWidget* parent) : RoomDialogBase(tr("Create room"), tr("Create room"), nullptr, parent, NoExtraButtons) , connections(std::move(cs)) , account(new QComboBox) , version(nullptr) // Will be initialized below , nextInvitee(new NextInvitee) , inviteButton(new QPushButton(tr("Add", "Add a user to the list of invitees"))) , invitees(new QListWidget) { Q_ASSERT(!connections.isEmpty()); auto* versionBox = new QHBoxLayout; version = addVersionSelector(versionBox); addEssentials(account, versionBox); #if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0) connect(account, QOverload::of(&QComboBox::currentIndexChanged), this, &CreateRoomDialog::accountSwitched); #else connect(account, static_cast(&QComboBox::currentIndexChanged), this, &CreateRoomDialog::accountSwitched); #endif mainFormLayout->insertRow(0, new QLabel( tr("Please fill the fields as desired. None are mandatory"))); nextInvitee->setEditable(true); nextInvitee->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon); nextInvitee->setMinimumContentsLength(42); auto* completer = new QCompleter(nextInvitee); completer->setCaseSensitivity(Qt::CaseInsensitive); completer->setCompletionMode(QCompleter::UnfilteredPopupCompletion); completer->setModelSorting(QCompleter::CaseSensitivelySortedModel); nextInvitee->setCompleter(completer); connect(nextInvitee, &NextInvitee::currentTextChanged, this, &CreateRoomDialog::updatePushButtons); // connect(nextInvitee, &NextInvitee::editTextChanged, // this, &CreateRoomDialog::updateUserList); inviteButton->setFocusPolicy(Qt::NoFocus); inviteButton->setDisabled(true); connect(inviteButton, &QPushButton::clicked, [this] { auto userName = nextInvitee->currentText(); if (userName.indexOf('@') == -1) { userName.prepend('@'); if (userName.indexOf(':') == -1) { auto* conn = account->currentData(Qt::UserRole) .value(); userName += ':' + conn->homeserver().authority(); } } auto* item = new QListWidgetItem(userName); if (nextInvitee->currentIndex() != -1) item->setData(Qt::UserRole, nextInvitee->currentData(Qt::UserRole)); invitees->addItem(item); nextInvitee->clear(); }); invitees->setSizeAdjustPolicy( QAbstractScrollArea::AdjustToContentsOnFirstShow); invitees->setUniformItemSizes(true); invitees->setSortingEnabled(true); // Layout additional controls auto* inviteLayout = new QHBoxLayout; inviteLayout->addWidget(nextInvitee); inviteLayout->addWidget(inviteButton); mainFormLayout->addRow(tr("Invite user(s)"), inviteLayout); mainFormLayout->addRow("", invitees); setPendingApplyMessage(tr("Creating the room, please wait")); if (connections.size() > 1) account->setFocus(); else roomName->setFocus(); } void CreateRoomDialog::updatePushButtons() { inviteButton->setEnabled(!nextInvitee->currentText().isEmpty()); if (inviteButton->isEnabled() && nextInvitee->hasFocus()) inviteButton->setDefault(true); else buttonBox()->button(QDialogButtonBox::Ok)->setDefault(true); } void CreateRoomDialog::load() { qDebug() << "Loading the dialog"; account->clear(); for (auto* c: connections) account->addItem(c->userId(), QVariant::fromValue(c)); roomName->clear(); alias->clear(); topic->clear(); previousTopic.clear(); nextInvitee->clear(); accountSwitched(); invitees->clear(); } bool CreateRoomDialog::validate() { auto* connection = account->currentData().value(); if (checkRoomVersion(version->currentData().toString(), connection)) return true; refillVersionSelector(version, connection); return false; } void CreateRoomDialog::apply() { using namespace QMatrixClient; auto* connection = account->currentData().value(); QStringList userIds; for (int i = 0; i < invitees->count(); ++i) { auto userVar = invitees->item(i)->data(Qt::UserRole); if (auto* user = userVar.value()) userIds.push_back(user->id()); else userIds.push_back(invitees->item(i)->text()); } auto* job = connection->createRoom( publishRoom->isChecked() ? Connection::PublishRoom : Connection::UnpublishRoom, alias->text(), roomName->text(), topic->toPlainText(), userIds, "", version->currentData().toString(), false); connect(job, &BaseJob::success, this, &Dialog::accept); connect(job, &BaseJob::failure, this, [this,job] { applyFailed(job->errorString()); }); } void CreateRoomDialog::accountSwitched() { auto* connection = account->currentData(Qt::UserRole).value(); refillVersionSelector(version, connection); aliasServer->setText(':' + connection->domain()); auto* completer = nextInvitee->completer(); Q_ASSERT(completer != nullptr); auto* model = new QStandardItemModel(completer); auto savedCurrentText = nextInvitee->currentText(); // auto prefix = // savedCurrentText.midRef(savedCurrentText.startsWith('@') ? 1 : 0); // if (prefix.size() >= 3) // { Q_ASSERT(connection != nullptr); QElapsedTimer et; et.start(); for (auto* u: connection->users()) { if (!u->isGuest()) { auto* item = new QStandardItem(u->fullName()); item->setData(QVariant::fromValue(u)); model->appendRow(item); } } qDebug() << "Completion candidates:" << model->rowCount() << "out of" << connection->users().size() << "filtered in" << et; // } nextInvitee->setModel(model); nextInvitee->setEditText(savedCurrentText); completer->setCompletionPrefix(savedCurrentText); } Quaternion-0.0.9.4c/client/roomdialogs.h000066400000000000000000000060651347141001300201210ustar00rootroot00000000000000/****************************************************************************** * Copyright (C) 2017 Kitsune Ral * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #pragma once #include "dialog.h" namespace QMatrixClient { class Connection; } class MainWindow; class QuaternionRoom; class QComboBox; class QLineEdit; class QPlainTextEdit; class QCheckBox; class QPushButton; class QListWidget; class QFormLayout; class RoomDialogBase : public Dialog { Q_OBJECT protected: using Connection = QMatrixClient::Connection; RoomDialogBase(const QString& title, const QString& applyButtonText, QuaternionRoom* r, QWidget* parent, QDialogButtonBox::StandardButtons extraButtons = QDialogButtonBox::Reset); protected: QuaternionRoom* room; QLabel* avatar; QLineEdit* roomName; QLabel* aliasServer; QLineEdit* alias; QPlainTextEdit* topic; QString previousTopic; QCheckBox* publishRoom; QCheckBox* guestCanJoin; QFormLayout* mainFormLayout; QFormLayout* essentialsLayout = nullptr; QComboBox* addVersionSelector(QLayout* layout); void refillVersionSelector(QComboBox* selector, Connection* account); void addEssentials(QWidget* accountControl, QLayout* versionBox); bool checkRoomVersion(QString version, Connection* account); }; class RoomSettingsDialog : public RoomDialogBase { Q_OBJECT public: RoomSettingsDialog(QuaternionRoom* room, MainWindow* parent = nullptr); private slots: void load() override; bool validate() override; void apply() override; private: QLabel* account; QLabel* version; QListWidget* tagsList; bool userChangedAvatar = false; }; class CreateRoomDialog : public RoomDialogBase { Q_OBJECT public: CreateRoomDialog(QVector cs, QWidget* parent = nullptr); public slots: void updatePushButtons(); private slots: void load() override; bool validate() override; void apply() override; void accountSwitched(); private: const QVector connections; QComboBox* account; QComboBox* version; QComboBox* nextInvitee; QPushButton* inviteButton; QListWidget* invitees; }; Quaternion-0.0.9.4c/client/roomlistdock.cpp000066400000000000000000000306431347141001300206450ustar00rootroot00000000000000/************************************************************************** * * * Copyright (C) 2015 Felix Rohrbach * * * * 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 . * * * **************************************************************************/ #include "roomlistdock.h" #include #include #include #include #include #include "mainwindow.h" #include "models/roomlistmodel.h" #include "models/orderbytag.h" #include "quaternionroom.h" #include "roomdialogs.h" #include #include using QMatrixClient::SettingsGroup; class RoomListItemDelegate : public QStyledItemDelegate { public: explicit RoomListItemDelegate(QObject* parent = nullptr) : QStyledItemDelegate(parent) , highlightColor(QSettings() .value("UI/highlight_color", QColor("orange")) .value()) { } void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; private: QColor highlightColor; }; void RoomListItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { QStyleOptionViewItem o { option }; if (!index.parent().isValid()) // Group captions { o.displayAlignment = Qt::AlignHCenter; o.font.setBold(true); } if (index.data(RoomListModel::HasUnreadRole).toBool()) o.font.setBold(true); if (index.data(RoomListModel::HighlightCountRole).toInt() > 0) { // Highlighting the text may not work out on monochrome colour schemes, // hence duplicating with italic font. o.palette.setColor(QPalette::Text, highlightColor); o.font.setItalic(true); } const auto joinState = index.data(RoomListModel::JoinStateRole).toString(); if (joinState == "invite") o.font.setItalic(true); else if (joinState == "leave" || joinState == "upgraded") o.font.setStrikeOut(true); QStyledItemDelegate::paint(painter, o, index); } RoomListDock::RoomListDock(MainWindow* parent) : QDockWidget("Rooms", parent) , selectedRoomCache(nullptr) { setObjectName("RoomsDock"); model = new RoomListModel(this); view = new QTreeView(); // proxyModel = new QSortFilterProxyModel(); // proxyModel->setDynamicSortFilter(true); // proxyModel->setSourceModel(model); updateSortingMode(); view->setModel(model); view->setItemDelegate(new RoomListItemDelegate(this)); view->setAnimated(true); view->setUniformRowHeights(true); view->setSelectionBehavior(QTreeView::SelectRows); view->setHeaderHidden(true); view->setIndentation(0); view->setRootIsDecorated(false); static const auto Expanded = QStringLiteral("expand"); static const auto Collapsed = QStringLiteral("collapse"); // connect( view, &QTreeView::activated, this, &RoomListDock::rowSelected ); connect( view, &QTreeView::clicked, this, &RoomListDock::rowSelected); connect( view, &QTreeView::pressed, this, [this] { if (QGuiApplication::mouseButtons() & Qt::MidButton) { if (auto room = getSelectedRoom()) room->markAllMessagesAsRead(); } }); connect( view, &QTreeView::expanded, this, [this] (QModelIndex i) { SettingsGroup("UI/RoomsDock") .setValue(model->roomGroupAt(i).toString(), Expanded); }); connect( view, &QTreeView::collapsed, this, [this] (QModelIndex i) { SettingsGroup("UI/RoomsDock") .setValue(model->roomGroupAt(i).toString(), Collapsed); }); connect( model, &RoomListModel::rowsInserted, this, &RoomListDock::refreshTitle ); connect( model, &RoomListModel::rowsRemoved, this, &RoomListDock::refreshTitle ); connect( model, &RoomListModel::saveCurrentSelection, this, [this] { selectedGroupCache = getSelectedGroup(); selectedRoomCache = getSelectedRoom(); }); connect( model, &RoomListModel::restoreCurrentSelection, this, [this] { const auto& idx = model->indexOf(selectedGroupCache, selectedRoomCache); // proxyModel->mapFromSource(model->indexOf(selectedRoomCache)); view->setCurrentIndex(idx); view->scrollTo(idx); selectedGroupCache.clear(); selectedRoomCache = nullptr; }); connect( model, &RoomListModel::modelReset, this, [this] { refreshTitle(); SettingsGroup sg("UI/RoomsDock"); for (int row = 0; row < model->rowCount({}); ++row) { const auto& i = model->index(row, 0); const auto groupKey = model->roomGroupAt(i).toString(); const auto expanded = Expanded == sg.get(groupKey, groupKey == QMatrixClient::FavouriteTag ? Expanded : Collapsed); view->setExpanded(i, expanded); } }); connect( model, &RoomListModel::groupAdded, this, [this] (int pos) { QMatrixClient::SettingsGroup sg { QStringLiteral("UI/RoomsDock") }; const auto group = model->roomGroupAt(model->index(pos, 0)).toString(); if (sg.get(group) == "expand") view->expand(model->index(pos, 0)); }); setWidget(view); roomContextMenu = new QMenu(this); markAsReadAction = roomContextMenu->addAction(QIcon::fromTheme("mail-mark-read"), tr("Mark room as read"), this, [this] { if (auto room = getSelectedRoom()) room->markAllMessagesAsRead(); }); roomContextMenu->addSeparator(); addTagsAction = roomContextMenu->addAction(QIcon::fromTheme("tag-new"), tr("Add tags..."), this, &RoomListDock::addTagsSelected); roomSettingsAction = roomContextMenu->addAction(QIcon::fromTheme("user-group-properties"), tr("Change room &settings..."), [this,parent] { auto* dlg = new RoomSettingsDialog(getSelectedRoom(), parent); dlg->setModal(false); dlg->setAttribute(Qt::WA_DeleteOnClose); dlg->reactivate(); }); roomContextMenu->addSeparator(); joinAction = roomContextMenu->addAction(QIcon::fromTheme("irc-join-channel"), tr("Join room"), this, [this] { if (auto room = getSelectedRoom()) { Q_ASSERT(room->connection()); room->connection()->joinRoom(room->id()); } }); leaveAction = roomContextMenu->addAction(QIcon::fromTheme("irc-close-channel"), {}, this, [this] { if (auto room = getSelectedRoom()) room->leaveRoom(); }); roomContextMenu->addSeparator(); forgetAction = roomContextMenu->addAction(QIcon::fromTheme("irc-remove-operator"), tr("Forget room"), this, [this] { if (auto room = getSelectedRoom()) { Q_ASSERT(room->connection()); room->connection()->forgetRoom(room->id()); } }); groupContextMenu = new QMenu(this); deleteTagAction = groupContextMenu->addAction(QIcon::fromTheme("tag-delete"), tr("Remove tag"), this, [this] { model->deleteTag(view->currentIndex()); }); setContextMenuPolicy(Qt::CustomContextMenu); connect(this, &QWidget::customContextMenuRequested, this, &RoomListDock::showContextMenu); } void RoomListDock::addConnection(QMatrixClient::Connection* connection) { model->addConnection(connection); } void RoomListDock::updateSortingMode() { // const auto sortMode = // QMatrixClient::Settings().value("UI/sort_rooms_by", 0).toInt(); // proxyModel->sort(sortMode, // sortMode == 0 ? Qt::AscendingOrder : Qt::DescendingOrder); model->setOrder(); } void RoomListDock::setSelectedRoom(QuaternionRoom* room) { if (getSelectedRoom() == room) return; // First try the current group; if that fails, try the entire list QModelIndex idx; auto currentGroup = getSelectedGroup(); if (!currentGroup.isNull()) idx = model->indexOf(currentGroup, room); if (!idx.isValid()) idx = model->indexOf({}, room); if (idx.isValid()) { view->setCurrentIndex(idx); view->scrollTo(idx); } } void RoomListDock::rowSelected(const QModelIndex& index) { if (model->isValidRoomIndex(index)) // emit roomSelected( model->roomAt(proxyModel->mapToSource(index))); emit roomSelected(model->roomAt(index)); } void RoomListDock::showContextMenu(const QPoint& pos) { auto index = view->indexAt(view->mapFromParent(pos)); if (!index.isValid()) return; // No context menu on root item yet if (model->isValidGroupIndex(index)) { // Don't allow to delete system "tags" auto tagName = model->roomGroupAt(index); deleteTagAction->setDisabled( tagName.toString().startsWith("org.qmatrixclient.")); groupContextMenu->popup(mapToGlobal(pos)); return; } Q_ASSERT(model->isValidRoomIndex(index)); auto room = model->roomAt(index); // auto room = model->roomAt(proxyModel->mapToSource(index)); using QMatrixClient::JoinState; bool joined = room->joinState() == JoinState::Join; bool invited = room->joinState() == JoinState::Invite; markAsReadAction->setEnabled(joined); addTagsAction->setEnabled(joined); joinAction->setEnabled(!joined); leaveAction->setText(invited ? tr("Reject invitation") : tr("Leave room")); leaveAction->setEnabled(room->joinState() != JoinState::Leave); forgetAction->setVisible(!invited); roomContextMenu->popup(mapToGlobal(pos)); } QVariant RoomListDock::getSelectedGroup() const { auto index = view->currentIndex(); return !index.isValid() ? QVariant() : model->roomGroupAt(index); } QuaternionRoom* RoomListDock::getSelectedRoom() const { QModelIndex index = view->currentIndex(); return !index.isValid() || !index.parent().isValid() ? nullptr : model->roomAt(index); // : model->roomAt(proxyModel->mapToSource(index)); } void RoomListDock::addTagsSelected() { if (auto room = getSelectedRoom()) { Dialog dlg(tr("Enter new tags for the room"), this, Dialog::NoStatusLine, tr("Add", "A caption on a button to add tags"), Dialog::NoExtraButtons); dlg.addWidget( new QLabel(tr("Enter tags to add to this room, one tag per line"))); auto tagsInput = new QPlainTextEdit(); tagsInput->setTabChangesFocus(true); dlg.addWidget(tagsInput); if (dlg.exec() != QDialog::Accepted) return; auto tags = room->tags(); const auto enteredTags = tagsInput->toPlainText().split('\n', QString::SkipEmptyParts); for (const auto& tag: enteredTags) { // No overwriting, just ensure the tag exists tags[tag == tr("Favourites") ? QMatrixClient::FavouriteTag : tag == tr("Low priority") ? QMatrixClient::LowPriorityTag : tag]; } room->setTags(tags); } } void RoomListDock::refreshTitle() { setWindowTitle(tr("Rooms (%1)").arg(model->totalRooms())); } Quaternion-0.0.9.4c/client/roomlistdock.h000066400000000000000000000053241347141001300203100ustar00rootroot00000000000000/************************************************************************** * * * Copyright (C) 2015 Felix Rohrbach * * * * 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 . * * * **************************************************************************/ #pragma once #include #include #include //#include class MainWindow; class RoomListModel; class QuaternionRoom; namespace QMatrixClient { class Connection; } class RoomListDock : public QDockWidget { Q_OBJECT public: explicit RoomListDock(MainWindow* parent = nullptr); void addConnection(QMatrixClient::Connection* connection); public slots: void updateSortingMode(); void setSelectedRoom(QuaternionRoom* room); signals: void roomSelected(QuaternionRoom* room); private slots: void rowSelected(const QModelIndex& index); void showContextMenu(const QPoint& pos); void addTagsSelected(); void refreshTitle(); private: QTreeView* view; RoomListModel* model; // QSortFilterProxyModel* proxyModel; QMenu* roomContextMenu; QMenu* groupContextMenu; QAction* markAsReadAction; QAction* addTagsAction; QAction* joinAction; QAction* leaveAction; QAction* forgetAction; QAction* deleteTagAction; QAction* roomSettingsAction; QVariant selectedGroupCache; QuaternionRoom* selectedRoomCache; QVariant getSelectedGroup() const; QuaternionRoom* getSelectedRoom() const; }; Quaternion-0.0.9.4c/client/systemtrayicon.cpp000066400000000000000000000061651347141001300212330ustar00rootroot00000000000000/************************************************************************** * * * Copyright (C) 2016 Felix Rohrbach * * * * 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 . * * * **************************************************************************/ #include "systemtrayicon.h" #include "mainwindow.h" #include "quaternionroom.h" #include #include SystemTrayIcon::SystemTrayIcon(MainWindow* parent) : QSystemTrayIcon(parent) , m_parent(parent) { setIcon(QIcon(":/icon.png")); setToolTip("Quaternion"); connect( this, &SystemTrayIcon::activated, this, &SystemTrayIcon::systemTrayIconAction); } void SystemTrayIcon::newRoom(QMatrixClient::Room* room) { connect(room, &QMatrixClient::Room::highlightCountChanged, this, [this,room] { highlightCountChanged(room); }); } void SystemTrayIcon::highlightCountChanged(QMatrixClient::Room* room) { using namespace QMatrixClient; auto mode = SettingsGroup("UI").value("notifications", "intrusive"); if (mode == "none") return; if( room->highlightCount() > 0 ) { showMessage(tr("Highlight in %1").arg(room->displayName()), tr("%n highlight(s)", "", room->highlightCount())); if (mode != "non-intrusive") m_parent->activateWindow(); connectSingleShot(this, &SystemTrayIcon::messageClicked, m_parent, [this,qRoom=static_cast(room)] { m_parent->selectRoom(qRoom); }); } } void SystemTrayIcon::systemTrayIconAction(QSystemTrayIcon::ActivationReason reason) { switch (reason) { case QSystemTrayIcon::Trigger: case QSystemTrayIcon::DoubleClick: this->showHide(); break; default: ; } } void SystemTrayIcon::showHide() { if( m_parent->isVisible() ) { m_parent->hide(); } else { m_parent->show(); m_parent->activateWindow(); m_parent->raise(); m_parent->setFocus(); } } Quaternion-0.0.9.4c/client/systemtrayicon.h000066400000000000000000000036051347141001300206740ustar00rootroot00000000000000/************************************************************************** * * * Copyright (C) 2016 Felix Rohrbach * * * * 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 . * * * **************************************************************************/ #pragma once #include namespace QMatrixClient { class Room; } class MainWindow; class SystemTrayIcon: public QSystemTrayIcon { Q_OBJECT public: explicit SystemTrayIcon(MainWindow* parent = nullptr); public slots: void newRoom(QMatrixClient::Room* room); private slots: void highlightCountChanged(QMatrixClient::Room* room); void systemTrayIconAction(QSystemTrayIcon::ActivationReason reason); private: MainWindow* m_parent; void showHide(); }; Quaternion-0.0.9.4c/client/translations/000077500000000000000000000000001347141001300201435ustar00rootroot00000000000000Quaternion-0.0.9.4c/client/translations/quaternion_de_DE.ts000066400000000000000000001474121347141001300237310ustar00rootroot00000000000000 ChatRoomWidget Choose a room to send messages or enter a command... Wähle einen Raum, um Nachrichten zu senden oder Kommandos einzugebe…. Sending encrypted messages is not supported yet Das Senden von verschlüsselten Nachrichten wird noch nicht unterstützt There's nothing to send Da ist nichts zum Senden /join argument doesn't look like a room ID or alias /join Argument sieht nicht nach einer Raum-ID oder Alias aus Sending a farewell message is not supported yet. If you intended to leave another room, switch to it and type /leave there. Eine Abschieds-Nachricht zu senden, wird aktuell nicht unterstützt. Wenn du einen anderen Raum verlassen willst, wechsele in diesen und tippe /leave dort ein. /forget must be followed by the room id/alias, even for the current room /forget benötigt als Argument eine(n) Raum-ID/Alias - auch für den aktuellen Raum %1 doesn't look like a room id or alias %1 sieht nicht nach einer Raum-ID oder einem Raum-Alias aus /invite <memberId> /invite <Nutzer-ID> /%1 <userId> <reason> /%1 <NutzerId> <Grund> %1 is not a member of this room %1 ist kein Mitglied dieses Raumes /unban <userId> /unban <NutzerId> /unban argument doesn't look like a user ID /unban-Argument sieht nicht nach einer Nutzer-ID aus /ignore <userId> /ignore <NutzerID> /ignore argument doesn't look like a user ID /ignore-Argument sieht nicht nach einer Nutzer-ID aus Couldn't find user %1 on the server Konnte den Benutzer %1 nicht auf dem Server finden /me needs an argument /me braucht ein Argument /notice needs an argument /notice braucht ein Argument /%1 <memberId> <message> /%1 <NutzerId> <Nachricht> %1 doesn't seem to have joined room %2 %1 ist dem Raum %2 anscheinend nicht beigetreten %1 doesn't look like a user id or room alias %1 sieht nicht nach einer Nutzer- oder Raum-ID aus /%1 <memberId> /%1 <Nutzer-ID> Unknown /command. Use // to send this line literally Unbekannter /befehl. Starte mit // um diese Zeile normal zu senden Save file as Speichere Datei als Next completion: %1 Nächste Vervollständigung: %1 Currently typing: %1 Aktuell tippen: %1 Attach Anhängen Attach file Datei anhängen Add a message to the file or just push Enter Füge der Datei eine Nachricht bei oder drücke einfach die Eingabetaste. Attaching %1 Hänge %1 an Attaching cancelled Anhängen abgebrochen There's no such /command outside of room. Es gibt keinen /Befehl außerhalb von Räumen. %1 doesn't look like a user id %1 sieht nicht nach einer Nutzer-ID aus %1 doesn't look like a user ID %1 sieht nicht wie eine Benutzer-ID aus. You should select a room to send messages. Wähle einen Raum, um Nachrichten zu senden. Send a message (over %1) or enter a command... Sende eine Nachricht (über %1) oder gebe einen Befehl ein... Timeline (no topic) (kein Thema) %1 bytes %1 Bytes %1 KB %1 KB %1 MB %1 MB %1 GB %1 GB Unknown Unbekannt %Ln events back from now (%L1 cached%2) %Ln Ereignis zurückgescrollt (%L1 zwischengespeichert%2) %Ln Ereignisse zurückgescrollt (%L1 zwischengespeichert%2) and loading und lädt Unstable room version! Instabile Zimmerversion! (no name) (kein Name) This room has been upgraded Dieses Zimmer wurde aktualisiert Go to Room Settings to upgrade the room Gehen Sie zu Raumeinstellungen, um den Raum zu aktualisieren Go to Room Settings to upgrade the room Gehen Sie zu Raumeinstellungen, um den Raum zu aktualisieren CreateRoomDialog Create room Erstelle Raum Add Hinzufügen Invite user(s) Nutzer einladen Creating the room, please wait Erstelle den Raum. Bitte warten Please fill the fields as desired. None are mandatory Bitte fülle die Felder wie gewünscht. Alle sind optional Dialog Applying changes, please wait Änderungen werden übernommen. Bitte warten LoginDialog Login Anmelden Stay logged in Angemeldet bleiben Matrix ID Matrix-ID Password Passwort Device name Gerätename Connect to server Mit Server verbinden Connecting and logging in, please wait Am Verbinden und anmelden. Bitte warten Re-login Neu anmelden Restoring access, please wait Der Zugriff wird wiederhergestellt, warten Sie bitte MainWindow Loading... Lädt… &Accounts &Kontos &Login... &Anmelden… &Quit &Beenden &View &Oberfläche Dock &panels &Panel anheften &Display in timeline In &Historie anzeigen Normal &join/leave events Normale &Zutritts-/Verlassens-Ereignisse &Redacted events &Gelöschte Ereignisse Show redacted events in the timeline as 'Redacted' instead of hiding them entirely Zeige entfernte Ereignisse in der Historie als 'Entfernt' anstatt sie komplett zu verbergen &No-effect activity &Effektlose Aktivität Edit tags order Ändere Tag-Reihenfolge &Room &Raum Change room &settings... Ändere Raum-&Einstellungen… Create &new room... Erstelle &neuen Raum… &Direct chat... &Direkter Chat… &Join room... Raum &betreten… &Close current room Aktuellen Raum &Schließen &Settings &Einstellungen &Help &Hilfe &About &Über &Highlight only Nur &Hervorhebungen Notifications are entirely suppressed Benachrichtigungen werden komplett unterdrückt &Non-intrusive &Nicht-Aufdringlich Show notifications but do not activate the window Zeige Benachrichtigungen, aber Fenster nicht aktivieren &Full &Voll Show notifications and activate the window Zeige Benachrichtigungen und aktiviere das Fenster Notifications Benachrichtigungen Default Standard The layout with author labels above blocks of messages Autor-Kennung über Blöcken von Nachrichten XChat XChat The layout with author labels to the left from each message Autor-Kenung links von jeder Nachricht Timeline layout Layout der Historie Load full-size images at once Lade Bilder in voller Größe auf einmal Automatically download a full-size image instead of a thumbnail Automatisch komplettes Bild anstelle eines Vorschaubildes laden Configure &network proxy... Konfiguriere &Netzwerk-Proxy… Couldn't open a file to save access token Konnte keine Datei öffnen, um den Zugangs-Token zu speichern Quaternion couldn't open a file to write the access token to. You're logged in but will have to provide your password again when you restart the application. Quaternion konnte keine Datei öffnen, um den Zugangs-Token darin zu speichern. Du wurdest angemeldet, wirst aber erneut ein Passwort eingeben müssen, wenn du die Anwendung neustartest. Couldn't set access token file permissions Konnte Berechtigungen der Zugangs-Token-Datei nicht setzen Quaternion couldn't restrict permissions on the access token file. Do you still want to save the access token to it? Quaternion konnte die Berechtigungen zur Zugangstoken-Datei nicht einschränken. Möchtest du den Zugangstoken trotzdem darin speichern? Logged out as %1 Als %1 abmelden Sync failed Synchronisieren fehlgeschlagen The last sync of account %1 has failed with error: %2 Die letzte Synchronisierung von Account %1 schlug fehl mit dem Fehler: %2 The last sync has failed with error: %1 Die letzte Synchronisierung schlug fehl mit dem Fehler: %1 Clicking 'Retry' will attempt to resume synchronisation; Clicking 'Cancel' will stop further synchronisation of this account until logout or Quaternion restart. 'Erneut probieren' wird versuchen weiter zu Synchronisieren; 'Abbrechen' wird weitere Synchronisierungsversuche dieses Kontos abbrechen bis zur Abmeldung oder Quaternion-Neustart. Before this server can process your information, you have to agree with its terms and conditions; please click the button below to open the web page where you can do that Bevor dieser Server deine Informationen verarbeiten kann, musst du den Betriebsbedingungen zustimmen. Bitte klicke auf den Button unten um eine Webseite zu öffnen, wo du dies tun kannst Open web page Webseite öffnen Show &access token Zeige &Zugangs-Token Access token for %1 Zugangs-Token für %1 Your access token is %1...%2; click "Show details..." for the full token Dein Zugangs-Token ist %1…%2; Klicke "Zeige Details..." für den kompletten Token &Logout &Abmelden About Quaternion Über Quaternion Welcome to Quaternion Willkommen bei Quaternion No connections Keine Verbindung Join Betreten Joined %1 as %2 %1 als %2 beigetreten Enter user id to start direct chat. Gebe Nutzer-ID ein, um direkten Chat zu starten. Sync completed - have a good chat Synchronisierung vollständig – viel Spaß beim Unterhalten Couldn't connect to the server as %1; will retry within %2 seconds Konnte nicht mit dem Server als %1 verbinden; Versuche es in %2 Sekunden erneut Reconnecting... Wiederverbinden… No SSL support Keine SSL-Unterstützung Your SSL configuration does not allow Quaternion to establish secure connections. Deine SSL-Konfiguration erlaubt Quaternion nicht eine sichere Verbindung herzustellen. SSL error SSL-Fehler Proxy needs authentication Proxy braucht Authentifizierung Authenticate Authentifizieren User name Nutzername Password Passwort Copyright (C) 2018 QMatrixClient project. Copyright (C) 2018 QMatrixClient-Projekt. &Thanks &Danke Original project author: %1 Ursprünglicher Projektautor: %1 Web page Webseite Project leader: %1 Projektleiter: %1 Contributors: Mitwirkende: Quaternion contributors @ GitHub Quaternion Mitwirkende @ GitHub libQMatrixClient contributors @ GitHub libQMatrixClient Mitwirkende @ GitHub Quaternion translators @ Lokalise.co Quaternion Übersetzer @ Lokalise.co Made with: Gemacht mit: Show join and leave events Zeige Betreten- und Verlassen-Ereignisse Use shuttle scrollbar (requires restart) Pendellaufleiste benutzen (erfordert Neustart) Control scroll velocity instead of position with the timeline scrollbar Der Laufleisten-Schieberegler fürs Gesprächsprotokoll ändert die Laufgeschwindigkeit anstatt der Position. Request URL: %1 Response: %2 Anfrage-URL: %1 Antwort: %2 Tags can be wildcarded by * next to dot(s) Clear the box to reset to defaults Special tags starting with "org.qmatrixclient." are: %1 User-defined tags should start with "u." In Etiketten kann ein * neben Punkt(en) als Platzhalter eingesetzt werden. Entferne das Häkchen, um auf die Standardeinstellungen zurückzusetzen. Spezielle Etiketten, die mit „org.qmatrixclient.“ beginnen, sind: %1 Benutzerdefinierte Etiketten sollten mit „u.“ beginnen. Close to tray Schließe in das Benachrichtigungsfeld. Make close button [X] minimize to tray instead of closing main window Schließen-Schaltfläche [X] minimiert in das Benachrichtigungsfeld, anstatt das Hauptfenster zu schließen. Show/hide meaningless activity (join-leave pairs and redacted events between) Zeige/verberge bedeutungslose Aktivität („betreten“–„verlassen“-Paare mit retouchierten Ereignissen dazwischen) Built from Git, commit SHA: Aus Git heraus gebaut. Commit-SHA: Library commit SHA: Commit-SHA der Bibliothek: Open room... Einen Raum öffnen… Open room Einen Raum öffnen Switch to room In den Raum wechseln Open a room from the room list Einen Raum aus der Raumliste öffnen Show/hide Rooms dock panel Dock-Panel der Räume ein-/ausblenden Show/hide Users dock panel Benutzer-Dock-Panel ein-/ausblenden Access token file found Zugriffstokendatei gefunden Do you want to migrate the access token for %1 from the file to keychain? Möchten Sie das Zugriffstoken für %1 aus der Datei in den Schlüsselbund migrieren? Couldn't migrate access token Zugriffstoken konnte nicht migriert werden Quaternion couldn't migrate access token %1 from the file to keychain. Quaternion konnte das Zugriffstoken %1 aus der Datei nicht in den Schlüsselbund migrieren. Couldn't save access token Zugriffstoken konnte nicht gespeichert werden. Quaternion couldn't save the access token to keychain. Do you want to save the access token to file %1? Quaternion konnte das Zugriffstoken nicht im Schlüsselbund speichern. Möchten Sie das Zugriffstoken in der Datei %1 speichern? Logging in into a logged in account Einloggen in ein angemeldetes Konto You're trying to log in into an account that's already logged in. Do you want to continue? Sie versuchen, sich bei einem bereits angemeldeten Konto anzumelden. Möchten Sie fortfahren? Couldn't delete access token Zugriffstoken konnte nicht gelöscht werden. Quaternion couldn't delete the access token from keychain. Quaternion konnte das Zugriffstoken nicht aus dem Schlüsselbund löschen. Open direct chat? Direkt-Chat öffnen? Open direct chat with user %1? Öffnen Sie einen direkten Chat mit Benutzer %1? Malformed user id Falsche Benutzer-ID %1 is not a correct user id % 1 ist keine korrekte Benutzer-ID Room not found Zimmer nicht gefunden There's no room %1 in the room list. Check the spelling and the account. Es gibt keinen Raum %1 in der Raumliste. Überprüfen Sie die Rechtschreibung und das Konto. Confirm your account to open %1 Bestätigen Sie Ihr Konto, um %1 zu öffnen Please connect to a server first Bitte verbinden Sie sich zuerst mit einem Server Confirm account Konto bestätigen Account Konto Enter room id or alias Gebe Raum-ID oder Alias Room ID (starting with !) or alias (starting with #) Raum ID (beginnend mit !) oder Alias (beginnend mit #) Confirm account to join %1 Bestätigen Sie das Konto, um %1 beizutreten User ID (starting with @) Benutzer-ID (beginnend mit @) Start chat Starte Chat Room/user ID, room alias, or matrix.to link Raum- / Benutzer-ID, Raumalias, oder matrix.to Verknüpfung Edit quote style Zitat-Stil bearbeiten Markdown (prepend each line with >) Markdown (vor jeder Zeile mit > ) Custom (apply regex from the config file) Benutzerdefiniert (anwenden regex aus der Konfigurationsdatei) Locale's default (%1) Standard der Ländereinstellung (%1) Example quote Beispielzitat Choose the default style of quotes Wählen Sie den Standardstil für Zitate Special thanks to %1 for all the testing effort Besonderer Dank geht an %1 für all die Anstrengungen zum Testen MessageEventModel Today Heute Yesterday Gestern The day before yesterday Vorgestern Redacted Entfernt Redacted: %1 Entfernt: %1 a file eine Datei reinvited %1 to the room lud %1 erneut in den Raum ein joined the room (repeated) trat dem Raum wiederholt bei invited %1 to the room lud %1 in den Raum ein joined the room trat dem Raum bei cleared the display name löschte den Anzeigenamen changed the display name to %1 änderte den Anzeigenamen zu %1 cleared the avatar entfernte das Pofilbild updated the avatar änderte das Profilbild unbanned %1 hob Verbannung von %1 auf self-unbanned hob Verbannung von sich selbst auf has put %1 out of the room hat %1 aus dem Raum entfernt left the room verließ den Raum banned %1 from the room verbannte %1 aus dem Raum self-banned from the room verbannte sich selbst aus dem Raum knocked klopfte an made something unknown tat etwas Unbekanntes cleared the room main alias entfernte den Haupt-Alias des Raumes set the room main alias to: %1 setzte den Haupt-Alias des Raumes auf: %1 cleared the room name entfernte den Raum-Namen set the room name to: %1 setzte den Raum-Namen auf: %1 cleared the topic entfernte das Thema set the topic to: %1 setzte das Thema auf: %1 changed the room avatar änderte das Raumbild activated End-to-End Encryption aktivierte Ende-zu-Ende-Verschlüsselung withdrew %1's invitation hat %1s Einladung zurückgezogen. rejected the invitation hat die Einladung abgelehnt. updated the database aktualisierte die Datenbank. updated %1 state aktualisierte Status von %1. updated %1 state for %2 aktualisierte %1-Status für %2. Unknown event Unbekanntes Ereignis upgraded the room to version %1 Das Zimmer wurde auf Version %1 aktualisiert created the room, version %1 Das Zimmer wurde erstellt, Version %1 has set room aliases on server %1 to: %2 hat Raum-Aliase auf dem Server %1 auf %2 gesetzt has put %1 out of the room: %2 hat %1 aus dem Raum entfernt: %2 banned %1 from the room: %2 Verbannung von %1 aus dem Raum: %2 NetworkConfigDialog Network proxy settings Netzwerk Proxy-Einstellungen &Override system defaults &System-Einstellungen überschreiben &No proxy &Kein Proxy &HTTP(S) proxy &HTTP(S) Proxy &SOCKS5 proxy &SOCKS5 Proxy Host Host Port Port User name Nutzername RoomDialogBase Publish room in room directory Raum im Raumverzeichnis veröffentlichen Allow guest accounts to join the room Erlaube Gast-Kontos diesen Raum zu betreten Account Konto Room name Raumname Primary alias Primärer Alias Topic Thema About room versions Über Zimmerversionen (loading) (Laden) default Standard stable stabil Room version Zimmerversion Continue with unstable version? Mit instabiler Version fortfahren? You are using an UNSTABLE room version (%1). The server may stop supporting it at any moment. Do you still want to use this version? Sie verwenden eine INSTABILE Zimmerversion (%1). Der Server kann die Unterstützung jederzeit einstellen. Möchten Sie diese Version noch verwenden? RoomListDock Mark room as read Markiere Raum als gelesen Add tags... Tags hinzufügen… Join room Raum betreten Forget room Raum vergessen Remove tag Tag entfernen Reject invitation Einladung abweisen Leave room Raum verlassen Enter new tags for the room Neue Tags für den Raum eingeben Enter tags to add to this room, one tag per line Gebe Tags für den Raum ein - ein Tag pro Zeile Favourites Favoriten Low priority Niedrige Priorität Rooms (%1) Räume (%1) Re-sort rooms (TODO) Räume neu sortieren (TODO) Change room &settings... Ändere Raum-&Einstellungen... Add Hinzufügen RoomListModel Invited Eingeladen Low priority Niedrige Priorität People Personen Ungrouped rooms Nicht-gruppierte Räume Left Verlassen %1 (%Ln room(s)) %1 (%Ln Raum) %1 (%Ln Räume) %1 (as %2) %1 (als %2) You joined this room Du bist diesem Raum beigetreten You left this room Du hast diesen Raum verlassen You were invited into this room Du wurdest in diesen Raum eingeladen Main alias: %1 Hauptalias: %1 Joined: %Ln Beigetreten: %L Beigetreten: %L Invited: %Ln Eingeladen: %L Eingeladen: %L Direct chat with %1 Direkter Chat mit %1 The room enforces encryption Der Raum erzwingt Verschlüsselung Unread messages: %1+ Ungelesene Nachrichten: %1+ Unread messages: %1 Ungelesene Nachrichten: %1 Unread highlights: %1 Ungelesene Hervorhebungen: %1 ID: %1 ID: %1 Favourites Favoriten as %1 als %1 This room's version is unstable! Die Version dieses Raumes ist instabil! Consider upgrading to a stable version (use room settings for that) Erwägen Sie ein Upgrade auf eine stabile Version (verwenden Sie dafür die Raumeinstellungen). RoomSettingsDialog Room settings: %1 Raum-Einstellungen: %1 Update room Raum aktualisieren Tags Tags Favourites Favoriten Low priority Niedrige Priorität This version is unstable! Consider upgrading. Diese Version ist instabil! Erwägen Sie ein Upgrade. Upgrade Aktualisierung Choose new room version Neue Raumversion auswählen You are about to upgrade %1. This operation cannot be reverted. Sie sind dabei, %1 zu aktualisieren. Dieser Vorgang kann nicht rückgängig gemacht werden. Creating the new room version, please wait Die neue Raumversion wird erstellt, bitte warten Room identifier Raum-ID UserListDock Users Benutzer Open direct chat Öffne direkten Chat Mention user Nutzer erwähnen Search Suchen Ignore user Benutzer ignorieren Kick user Benutzer herauswerfen Ban user Benutzer ausschließen Kick %1 %1 rauswerfen Reason Grund Ban %1 %1 ausschließen (%L1 out of %L2) (%L1 von %L2) main Display debug information Zeige Debug-Informationen Quaternion - an IM client for the Matrix protocol Quaternion – ein Chat-Client für das Matrix-Protokoll Override locale Sprache überschreiben locale Sprache Hide main window on startup Starte mit verstecktem Hauptfenster. FileContent Size: %1, declared type: %2 Größe: %1, angegebener Typ: %2 Open after downloading Nach dem Herunterladen öffnen Cancel Abbrechen Save as... Speichern als… Open Öffnen Open folder Ordner öffnen uploaded from %1 hochgeladen von %1 being uploaded from %1 lädt hoch von %1 downloaded to %1 nach %1 heruntergeladen ImageContent Cancel downloading Herunterladen abbrechen Open externally Extern öffnen Download full size In voller Größe herunterladen Save as... Speichern als... Cancel Abbrechen TimelineItem Resend Erneut senden Discard Verwerfen Redact Entfernen Copy link to clipboard In Zwischenablage kopieren Quote Zitieren SystemTrayIcon Highlight in %1 Hervorhebung in %1 %n highlight(s) %n Hervorhebung %n Hervorhebungen UserListModel Bridged from: %1 Überbrückt von: %1 ThumbnailResponse Image request hasn't started Bildanfrage wurde noch nicht gestartet. Image request has been cancelled Bildanfrage wurde abgebrochen. Media id '%1' doesn't follow server/mediaId pattern Die Medien-ID '% 1' folgt nicht dem Server / mediaId-Muster No connection to perform image request Keine Verbindung zum Durchführen einer Image-Anfrage Quaternion-0.0.9.4c/client/translations/quaternion_en.ts000066400000000000000000002205071347141001300233700ustar00rootroot00000000000000 ChatRoomWidget Choose a room to send messages or enter a command... Choose a room to send messages or enter a command... Attach Attach Attach file Attach file Add a message to the file or just push Enter Add a message to the file or just push Enter Attaching %1 Attaching %1 Attaching cancelled Attaching cancelled Next completion: %1 Next completion: %1 Currently typing: %1 Currently typing: %1 Sending encrypted messages is not supported yet Sending encrypted messages is not supported yet Send a message (over %1) or enter a command... %1 is the protocol used by the server (usually HTTPS) Send a message (over %1) or enter a command... There's nothing to send There's nothing to send /join argument doesn't look like a room ID or alias /join argument doesn't look like a room ID or alias There's no such /command outside of room. There's no such /command outside of room. You should select a room to send messages. You should select a room to send messages. Sending a farewell message is not supported yet. If you intended to leave another room, switch to it and type /leave there. Sending a farewell message is not supported yet. If you intended to leave another room, switch to it and type /leave there. /forget must be followed by the room id/alias, even for the current room /forget must be followed by the room id/alias, even for the current room %1 doesn't look like a room id or alias %1 doesn't look like a room id or alias /invite <memberId> /invite <memberId> %1 doesn't look like a user ID %1 doesn't look like a user ID /%1 <userId> <reason> /%1 <userId> <reason> %1 doesn't look like a user id %1 doesn't look like a user id %1 is not a member of this room %1 is not a member of this room /unban <userId> /unban <userId> /unban argument doesn't look like a user ID /unban argument doesn't look like a user ID /ignore <userId> /ignore <userId> /ignore argument doesn't look like a user ID /ignore argument doesn't look like a user ID Couldn't find user %1 on the server Couldn't find user %1 on the server /me needs an argument /me needs an argument /notice needs an argument /notice needs an argument /%1 <memberId> <message> /%1 <memberId> <message> %1 doesn't seem to have joined room %2 %1 doesn't seem to have joined room %2 %1 doesn't look like a user id or room alias %1 doesn't look like a user id or room alias /%1 <memberId> /%1 <memberId> Unknown /command. Use // to send this line literally Unknown /command. Use // to send this line literally Save file as Save file as CreateRoomDialog Create room Create room Add Add a user to the list of invitees Add Please fill the fields as desired. None are mandatory Please fill the fields as desired. None are mandatory Invite user(s) Invite user(s) Creating the room, please wait Creating the room, please wait Dialog Applying changes, please wait Applying changes, please wait FileContent Size: %1, declared type: %2 Size: %1, declared type: %2 uploaded from %1 %1 is a local file name uploaded from %1 being uploaded from %1 %1 is a local file name being uploaded from %1 downloaded to %1 %1 is a local file name downloaded to %1 Open after downloading Open after downloading Cancel Cancel Save as... Save as... Open Open Open folder Open folder ImageContent Cancel Cancel Open externally Open externally Download full size Download full size Save as... Save as... LoginDialog Login Login Stay logged in Stay logged in Re-login Re-login Restoring access, please wait Restoring access, please wait Matrix ID Matrix ID Password Password Device name Device name Connect to server Connect to server Connecting and logging in, please wait Connecting and logging in, please wait MainWindow Loading... Loading... &Accounts &Accounts &Login... &Login... &Quit &Quit &View &View Room/user ID, room alias, or matrix.to link Room/user ID, room alias, or matrix.to link &Display in timeline &Display in timeline Normal &join/leave events Normal &join/leave events Show join and leave events Show join and leave events &Redacted events &Redacted events Show redacted events in the timeline as 'Redacted' instead of hiding them entirely Show redacted events in the timeline as 'Redacted' instead of hiding them entirely &No-effect activity A menu item to show/hide meaningless activity such as redacted spam &No-effect activity Show/hide meaningless activity (join-leave pairs and redacted events between) Show/hide meaningless activity (join-leave pairs and redacted events between) Edit tags order Edit tags order &Room &Room Change room &settings... Change room &settings... Create &new room... Create &new room... &Direct chat... &Direct chat... &Join room... &Join room... &Close current room &Close current room &Settings &Settings &Help &Help &About &About &Highlight only &Highlight only Notifications are entirely suppressed Notifications are entirely suppressed &Non-intrusive &Non-intrusive Show notifications but do not activate the window Show notifications but do not activate the window &Full &Full Show notifications and activate the window Show notifications and activate the window Notifications Notifications Default Default The layout with author labels above blocks of messages The layout with author labels above blocks of messages Tags can be wildcarded by * next to dot(s) Clear the box to reset to defaults Special tags starting with "org.qmatrixclient." are: %1 User-defined tags should start with "u." Tags can be wildcarded by * next to dot(s) Clear the box to reset to defaults Special tags starting with "org.qmatrixclient." are: %1 User-defined tags should start with "u." Open room... Open room... Open room Open room Switch to room Switch to room Open a room from the room list Open a room from the room list Dock &panels Panels of the dock, not 'to dock the panels' Dock &panels Show/hide Rooms dock panel Show/hide Rooms dock panel Show/hide Users dock panel Show/hide Users dock panel Edit quote style Edit quote style Markdown (prepend each line with >) Markdown (prepend each line with >) Custom (apply regex from the config file) Custom (apply regex from the config file) Locale's default (%1) Locale's default (%1) Example quote Example quote Choose the default style of quotes Choose the default style of quotes The layout with author labels to the left from each message The layout with author labels to the left from each message Timeline layout Timeline layout Use shuttle scrollbar (requires restart) Use shuttle scrollbar (requires restart) Control scroll velocity instead of position with the timeline scrollbar Control scroll velocity instead of position with the timeline scrollbar Load full-size images at once Load full-size images at once Automatically download a full-size image instead of a thumbnail Automatically download a full-size image instead of a thumbnail Close to tray Close to tray Make close button [X] minimize to tray instead of closing main window Make close button [X] minimize to tray instead of closing main window Configure &network proxy... Configure &network proxy... Access token file found Access token file found Do you want to migrate the access token for %1 from the file to keychain? Do you want to migrate the access token for %1 from the file to keychain? Couldn't migrate access token Couldn't migrate access token Quaternion couldn't migrate access token %1 from the file to keychain. Quaternion couldn't migrate access token %1 from the file to keychain. Couldn't open a file to save access token Couldn't open a file to save access token Quaternion couldn't open a file to write the access token to. You're logged in but will have to provide your password again when you restart the application. Quaternion couldn't open a file to write the access token to. You're logged in but will have to provide your password again when you restart the application. Couldn't set access token file permissions Couldn't set access token file permissions Quaternion couldn't restrict permissions on the access token file. Do you still want to save the access token to it? Quaternion couldn't restrict permissions on the access token file. Do you still want to save the access token to it? Couldn't save access token Couldn't save access token Quaternion couldn't save the access token to keychain. Do you want to save the access token to file %1? Quaternion couldn't save the access token to keychain. Do you want to save the access token to file %1? Logged out as %1 Logged out as %1 Sync failed Sync failed The last sync of account %1 has failed with error: %2 The last sync of account %1 has failed with error: %2 The last sync has failed with error: %1 The last sync has failed with error: %1 Clicking 'Retry' will attempt to resume synchronisation; Clicking 'Cancel' will stop further synchronisation of this account until logout or Quaternion restart. Clicking 'Retry' will attempt to resume synchronisation; Clicking 'Cancel' will stop further synchronisation of this account until logout or Quaternion restart. Before this server can process your information, you have to agree with its terms and conditions; please click the button below to open the web page where you can do that Before this server can process your information, you have to agree with its terms and conditions; please click the button below to open the web page where you can do that Request URL: %1 Response: %2 Request URL: %1 Response: %2 Open web page Open web page Show &access token Show &access token Access token for %1 Access token for %1 Your access token is %1...%2; click "Show details..." for the full token Your access token is %1...%2; click "Show details..." for the full token &Logout &Logout Logging in into a logged in account Logging in into a logged in account You're trying to log in into an account that's already logged in. Do you want to continue? You're trying to log in into an account that's already logged in. Do you want to continue? About Quaternion About Quaternion Web page Web page Copyright (C) 2018 QMatrixClient project. Copyright (C) 2018 QMatrixClient project. Built from Git, commit SHA: Built from Git, commit SHA: Library commit SHA: Library commit SHA: Original project author: %1 Original project author: %1 Project leader: %1 Project leader: %1 Contributors: Contributors: Quaternion contributors @ GitHub Quaternion contributors @ GitHub libQMatrixClient contributors @ GitHub libQMatrixClient contributors @ GitHub Quaternion translators @ Lokalise.co Quaternion translators @ Lokalise.co Special thanks to %1 for all the testing effort Special thanks to %1 for all the testing effort Made with: Made with: &Thanks &Thanks Welcome to Quaternion Welcome to Quaternion Couldn't delete access token Couldn't delete access token Quaternion couldn't delete the access token from keychain. Quaternion couldn't delete the access token from keychain. Open direct chat? Open direct chat? Open direct chat with user %1? Open direct chat with user %1? Malformed user id Malformed user id %1 is not a correct user id %1 is not a correct user id Room not found Room not found There's no room %1 in the room list. Check the spelling and the account. There's no room %1 in the room list. Check the spelling and the account. Confirm your account to open %1 Confirm your account to open %1 Please connect to a server first Please connect to a server first Confirm account Confirm account Account Account Enter room id or alias Enter room id or alias Room ID (starting with !) or alias (starting with #) Room ID (starting with !) or alias (starting with #) Confirm account to join %1 Confirm account to join %1 User ID (starting with @) User ID (starting with @) Start chat Start chat No connections No connections Join Join Joined %1 as %2 Joined %1 as %2 Enter user id to start direct chat. Enter user id to start direct chat. Sync completed - have a good chat Sync completed - have a good chat Couldn't connect to the server as %1; will retry within %2 seconds Couldn't connect to the server as %1; will retry within %2 seconds Reconnecting... Reconnecting... No SSL support No SSL support Your SSL configuration does not allow Quaternion to establish secure connections. Your SSL configuration does not allow Quaternion to establish secure connections. SSL error SSL error Proxy needs authentication Proxy needs authentication Authenticate Authenticate with the proxy server Authenticate User name User name Password Password MessageEventModel Today Today Yesterday Yesterday The day before yesterday The day before yesterday Redacted Redacted Redacted: %1 Redacted: %1 a file a file reinvited %1 to the room reinvited %1 to the room joined the room (repeated) joined the room (repeated) invited %1 to the room invited %1 to the room joined the room joined the room cleared the display name cleared the display name changed the display name to %1 changed the display name to %1 cleared the avatar cleared the avatar updated the avatar updated the avatar withdrew %1's invitation withdrew %1's invitation rejected the invitation rejected the invitation unbanned %1 unbanned %1 self-unbanned self-unbanned has put %1 out of the room: %2 has put %1 out of the room: %2 banned %1 from the room: %2 banned %1 from the room: %2 left the room left the room self-banned from the room self-banned from the room knocked knocked made something unknown made something unknown has set room aliases on server %1 to: %2 has set room aliases on server %1 to: %2 cleared the room main alias cleared the room main alias set the room main alias to: %1 set the room main alias to: %1 cleared the room name cleared the room name set the room name to: %1 set the room name to: %1 cleared the topic cleared the topic set the topic to: %1 set the topic to: %1 changed the room avatar changed the room avatar activated End-to-End Encryption activated End-to-End Encryption upgraded the room to version %1 upgraded the room to version %1 created the room, version %1 created the room, version %1 updated the database TWIM bot updated the database updated the database updated %1 state %1 - Matrix event type updated %1 state updated %1 state for %2 %1 - Matrix event type, %2 - state key updated %1 state for %2 Unknown event Unknown event NetworkConfigDialog Network proxy settings Network proxy settings &Override system defaults &Override system defaults &No proxy &No proxy &HTTP(S) proxy &HTTP(S) proxy &SOCKS5 proxy &SOCKS5 proxy Host Host Port Port User name User name RoomDialogBase Publish room in room directory Publish room in room directory Allow guest accounts to join the room Allow guest accounts to join the room About room versions About room versions (loading) Loading room versions from the server (loading) default Default room version default stable Stable room version stable Account Account Room version Room version Continue with unstable version? Continue with unstable version? You are using an UNSTABLE room version (%1). The server may stop supporting it at any moment. Do you still want to use this version? You are using an UNSTABLE room version (%1). The server may stop supporting it at any moment. Do you still want to use this version? Room name Room name Primary alias Primary alias Topic Topic RoomListDock Mark room as read Mark room as read Add tags... Add tags... Change room &settings... Change room &settings... Join room Join room Forget room Forget room Remove tag Remove tag Reject invitation Reject invitation Leave room Leave room Enter new tags for the room Enter new tags for the room Add A caption on a button to add tags Add Enter tags to add to this room, one tag per line Enter tags to add to this room, one tag per line Favourites Favourites Low priority Low priority Rooms (%1) Rooms (%1) RoomListModel Favourites Favourites Low priority Low priority Ungrouped rooms Ungrouped rooms Invited The caption for invitations Invited People The caption for direct chats People Left The caption for left rooms Left %1 (%Ln room(s)) %1 (%Ln room) %1 (%Ln rooms) %1 (as %2) %Room (as %user) %1 (as %2) Main alias: %1 Main alias: %1 Joined: %Ln The number of joined members Joined: %Ln Joined: %Ln Invited: %Ln The number of invited users Invited: %Ln Invited: %Ln Direct chat with %1 Direct chat with %1 The room enforces encryption The room enforces encryption This room's version is unstable! This room's version is unstable! Consider upgrading to a stable version (use room settings for that) Consider upgrading to a stable version (use room settings for that) Unread messages: %1+ Unread messages: %1+ Unread messages: %1 Unread messages: %1 Unread highlights: %1 Unread highlights: %1 ID: %1 ID: %1 as %1 as <user account> (disambiguates entries in the room list) as %1 You joined this room You joined this room You left this room You left this room You were invited into this room You were invited into this room RoomSettingsDialog Room settings: %1 Room settings: %1 Update room Update room This version is unstable! Consider upgrading. This version is unstable! Consider upgrading. Upgrade Upgrade a room version Upgrade Choose new room version Choose new room version You are about to upgrade %1. This operation cannot be reverted. You are about to upgrade %1. This operation cannot be reverted. Tags Tags Room identifier Room identifier Favourites Favourites Low priority Low priority Creating the new room version, please wait Creating the new room version, please wait SystemTrayIcon Highlight in %1 Highlight in %1 %n highlight(s) %n highlight %n highlights ThumbnailResponse Image request hasn't started Image request hasn't started No connection to perform image request No connection to perform image request Media id '%1' doesn't follow server/mediaId pattern Media id '%1' doesn't follow server/mediaId pattern Image request has been cancelled Image request has been cancelled Timeline Unknown Unknown attachment size Unknown attachment size %1 bytes %1 bytes %1 KB %1 KB %1 MB %1 MB %1 GB %1 GB (no name) (no name) This room has been upgraded This room has been upgraded Unstable room version! Unstable room version! Go to Room Settings to upgrade the room Go to Room Settings to upgrade the room (no topic) (no topic) %Ln events back from now (%L1 cached%2) %2 is optional 'and loading' %Ln event back from now (%L1 cached%2) %Ln events back from now (%L1 cached%2) and loading and loading TimelineItem Resend Resend Discard Discard Redact Redact Quote a verb (do quote), not a noun (a quote) Quote Copy link to clipboard Copy link to clipboard UserListDock Users Users Search Search Open direct chat Open direct chat Mention user Mention user Ignore user Ignore user Kick user Kick user Ban user Ban user (%L1 out of %L2) %found out of %total users (%L1 out of %L2) Kick %1 Kick %1 Reason Reason Ban %1 Ban %1 UserListModel Bridged from: %1 Bridged from: %1 main Quaternion - an IM client for the Matrix protocol Quaternion - an IM client for the Matrix protocol Override locale Override locale locale locale Hide main window on startup Hide main window on startup Display debug information Display debug information Quaternion-0.0.9.4c/client/translations/quaternion_en_GB.ts000066400000000000000000001433721347141001300237440ustar00rootroot00000000000000 ChatRoomWidget Choose a room to send messages or enter a command... Choose a room to send messages or enter a command... Sending encrypted messages is not supported yet Sending encrypted messages is not supported yet There's nothing to send There's nothing to send /join argument doesn't look like a room ID or alias /join argument doesn't look like a room ID or alias Sending a farewell message is not supported yet. If you intended to leave another room, switch to it and type /leave there. Sending a farewell message is not supported yet. If you intended to leave another room, switch to it and type /leave there. /forget must be followed by the room id/alias, even for the current room /forget must be followed by the room id/alias, even for the current room %1 doesn't look like a room id or alias %1 doesn't look like a room id or alias /invite <memberId> /invite <memberId> /%1 <userId> <reason> /%1 <userId> <reason> %1 is not a member of this room %1 is not a member of this room /unban <userId> /unban <userId> /unban argument doesn't look like a user ID /unban argument doesn't look like a user ID /ignore <userId> /ignore <userId> /ignore argument doesn't look like a user ID /ignore argument doesn't look like a user ID Couldn't find user %1 on the server Couldn't find user %1 on the server /me needs an argument /me needs an argument /notice needs an argument /notice needs an argument /%1 <memberId> <message> /%1 <memberId> <message> %1 doesn't seem to have joined room %2 %1 doesn't seem to have joined room %2 %1 doesn't look like a user id or room alias %1 doesn't look like a user id or room alias /%1 <memberId> /%1 <memberId> Unknown /command. Use // to send this line literally Unknown /command. Use // to send this line literally Save file as Save file as Next completion: %1 Next completion: %1 Currently typing: %1 Currently typing: %1 Attach Attach Attach file Attach file Add a message to the file or just push Enter Add a message to the file or just push Enter Attaching %1 Attaching %1 Attaching cancelled Attaching cancelled There's no such /command outside of room. There's no such /command outside of room. %1 doesn't look like a user id %1 doesn't look like a user id %1 doesn't look like a user ID %1 doesn't look like a user ID You should select a room to send messages. You should select a room to send messages. Send a message (over %1) or enter a command... Send a message (over %1) or enter a command... Timeline (no topic) (no topic) %1 bytes %1 bytes %1 KB %1 KB %1 MB %1 MB %1 GB %1 GB Unknown Unknown %Ln events back from now (%L1 cached%2) %Ln event back from now (%L1 cached%2) %Ln events back from now (%L1 cached%2) and loading and loading Unstable room version! Unstable room version! (no name) (no name) This room has been upgraded This room has been upgraded Go to Room Settings to upgrade the room Go to Room Settings to upgrade the room Go to Room Settings to upgrade the room Go to Room Settings to upgrade the room CreateRoomDialog Create room Create room Add Add Invite user(s) Invite user(s) Creating the room, please wait Creating the room, please wait Please fill the fields as desired. None are mandatory Please fill the fields as desired. None are mandatory Dialog Applying changes, please wait Applying changes, please wait LoginDialog Login Login Stay logged in Stay logged in Matrix ID Matrix ID Password Password Device name Device name Connect to server Connect to server Connecting and logging in, please wait Connecting and logging in, please wait Re-login Re-login Restoring access, please wait Restoring access, please wait MainWindow Loading... Loading... &Accounts &Accounts &Login... &Login... &Quit &Quit &View &View Dock &panels Dock &panels &Display in timeline &Display in timeline Normal &join/leave events Normal &join/leave events &Redacted events &Redacted events Show redacted events in the timeline as 'Redacted' instead of hiding them entirely Show redacted events in the timeline as 'Redacted' instead of hiding them entirely &No-effect activity &No-effect activity Edit tags order Edit tags order &Room &Room Change room &settings... Change room &settings... Create &new room... Create &new room... &Direct chat... &Direct chat... &Join room... &Join room... &Close current room &Close current room &Settings &Settings &Help &Help &About &About &Highlight only &Highlight only Notifications are entirely suppressed Notifications are entirely suppressed &Non-intrusive &Non-intrusive Show notifications but do not activate the window Show notifications but do not activate the window &Full &Full Show notifications and activate the window Show notifications and activate the window Notifications Notifications Default Default The layout with author labels above blocks of messages The layout with author labels above blocks of messages XChat XChat The layout with author labels to the left from each message The layout with author labels to the left from each message Timeline layout Timeline layout Load full-size images at once Load full-size images at once Automatically download a full-size image instead of a thumbnail Automatically download a full-size image instead of a thumbnail Configure &network proxy... Configure &network proxy... Couldn't open a file to save access token Couldn't open a file to save access token Quaternion couldn't open a file to write the access token to. You're logged in but will have to provide your password again when you restart the application. Quaternion couldn't open a file to write the access token to. You're logged in but will have to provide your password again when you restart the application. Couldn't set access token file permissions Couldn't set access token file permissions Quaternion couldn't restrict permissions on the access token file. Do you still want to save the access token to it? Quaternion couldn't restrict permissions on the access token file. Do you still want to save the access token to it? Logged out as %1 Logged out as %1 Sync failed Sync failed The last sync of account %1 has failed with error: %2 The last sync of account %1 has failed with error: %2 The last sync has failed with error: %1 The last sync has failed with error: %1 Clicking 'Retry' will attempt to resume synchronisation; Clicking 'Cancel' will stop further synchronisation of this account until logout or Quaternion restart. Clicking 'Retry' will attempt to resume synchronization; Clicking 'Cancel' will stop further synchronization of this account until logout or Quaternion restart. Before this server can process your information, you have to agree with its terms and conditions; please click the button below to open the web page where you can do that Before this server can process your information, you have to agree with its terms and conditions; please click the button below to open the web page where you can do that Open web page Open web page Show &access token Show &access token Access token for %1 Access token for %1 Your access token is %1...%2; click "Show details..." for the full token Your access token is %1...%2; click "Show details..." for the full token &Logout &Logout About Quaternion About Quaternion Welcome to Quaternion Welcome to Quaternion No connections No connections Join Join Joined %1 as %2 Joined %1 as %2 Enter user id to start direct chat. Enter user id to start direct chat. Sync completed - have a good chat Sync completed - have a good chat Couldn't connect to the server as %1; will retry within %2 seconds Couldn't connect to the server as %1; will retry within %2 seconds Reconnecting... Reconnecting... No SSL support No SSL support Your SSL configuration does not allow Quaternion to establish secure connections. Your SSL configuration does not allow Quaternion to establish secure connections. SSL error SSL error Proxy needs authentication Proxy needs authentication Authenticate Authenticate User name User name Password Password Copyright (C) 2018 QMatrixClient project. Copyright (C) 2018 QMatrixClient project. &Thanks &Thanks Original project author: %1 Original project author: %1 Web page Web page Project leader: %1 Project leader: %1 Contributors: Contributors: Quaternion contributors @ GitHub Quaternion contributors @ GitHub libQMatrixClient contributors @ GitHub libQMatrixClient contributors @ GitHub Quaternion translators @ Lokalise.co Quaternion translators @ Lokalise.co Made with: Made with: Show join and leave events Show join and leave events Use shuttle scrollbar (requires restart) Use shuttle scrollbar (requires restart) Control scroll velocity instead of position with the timeline scrollbar Control scroll velocity instead of position with the timeline scrollbar Request URL: %1 Response: %2 Request URL: %1 Response: %2 Tags can be wildcarded by * next to dot(s) Clear the box to reset to defaults Special tags starting with "org.qmatrixclient." are: %1 User-defined tags should start with "u." Tags can be wildcarded by * next to dot(s) Clear the box to reset to defaults Special tags starting with "org.qmatrixclient." are: %1 User-defined tags should start with "u." Close to tray Close to tray Make close button [X] minimize to tray instead of closing main window Make close button [X] minimize to tray instead of closing main window Show/hide meaningless activity (join-leave pairs and redacted events between) Show/hide meaningless activity (join-leave pairs and redacted events between) Built from Git, commit SHA: Built from Git, commit SHA: Library commit SHA: Library commit SHA: Open room... Open room... Open room Open room Switch to room Switch to room Open a room from the room list Open a room from the room list Show/hide Rooms dock panel Show/hide Rooms dock panel Show/hide Users dock panel Show/hide Users dock panel Access token file found Access token file found Do you want to migrate the access token for %1 from the file to keychain? Do you want to migrate the access token for %1 from the file to keychain? Couldn't migrate access token Couldn't migrate access token Quaternion couldn't migrate access token %1 from the file to keychain. Quaternion couldn't migrate access token %1 from the file to keychain. Couldn't save access token Couldn't save access token Quaternion couldn't save the access token to keychain. Do you want to save the access token to file %1? Quaternion couldn't save the access token to keychain. Do you want to save the access token to file %1? Logging in into a logged in account Logging in into a logged in account You're trying to log in into an account that's already logged in. Do you want to continue? You're trying to log in into an account that's already logged in. Do you want to continue? Couldn't delete access token Couldn't delete access token Quaternion couldn't delete the access token from keychain. Quaternion couldn't delete the access token from keychain. Open direct chat? Open direct chat? Open direct chat with user %1? Open direct chat with user %1? Malformed user id Malformed user id %1 is not a correct user id %1 is not a correct user id Room not found Room not found There's no room %1 in the room list. Check the spelling and the account. There's no room %1 in the room list. Check the spelling and the account. Confirm your account to open %1 Confirm your account to open %1 Please connect to a server first Please connect to a server first Confirm account Confirm account Account Account Enter room id or alias Enter room id or alias Room ID (starting with !) or alias (starting with #) Room ID (starting with !) or alias (starting with #) Confirm account to join %1 Confirm account to join %1 User ID (starting with @) User ID (starting with @) Start chat Start chat Room/user ID, room alias, or matrix.to link Room/user ID, room alias, or matrix.to link Edit quote style Edit quote style Markdown (prepend each line with >) Markdown (prepend each line with >) Custom (apply regex from the config file) Custom (apply regex from the config file) Locale's default (%1) Locale's default (%1) Example quote Example quote Choose the default style of quotes Choose the default style of quotes Special thanks to %1 for all the testing effort Special thanks to %1 for all the testing effort MessageEventModel Today Today Yesterday Yesterday The day before yesterday The day before yesterday Redacted Redacted Redacted: %1 Redacted: %1 a file a file reinvited %1 to the room reinvited %1 to the room joined the room (repeated) joined the room (repeated) invited %1 to the room invited %1 to the room joined the room joined the room cleared the display name cleared the display name changed the display name to %1 changed the display name to %1 cleared the avatar cleared the avatar updated the avatar updated the avatar unbanned %1 unbanned %1 self-unbanned self-unbanned has put %1 out of the room has put %1 out of the room left the room left the room banned %1 from the room banned %1 from the room self-banned from the room self-banned from the room knocked knocked made something unknown made something unknown cleared the room main alias cleared the room main alias set the room main alias to: %1 set the room main alias to: %1 cleared the room name cleared the room name set the room name to: %1 set the room name to: %1 cleared the topic cleared the topic set the topic to: %1 set the topic to: %1 changed the room avatar changed the room avatar activated End-to-End Encryption activated End-to-End Encryption withdrew %1's invitation withdrew %1's invitation rejected the invitation rejected the invitation updated the database updated the database updated %1 state updated %1 state updated %1 state for %2 updated %1 state for %2 Unknown event Unknown event upgraded the room to version %1 upgraded the room to version %1 created the room, version %1 created the room, version %1 has set room aliases on server %1 to: %2 has set room aliases on server %1 to: %2 has put %1 out of the room: %2 has put %1 out of the room: %2 banned %1 from the room: %2 banned %1 from the room: %2 NetworkConfigDialog Network proxy settings Network proxy settings &Override system defaults &Override system defaults &No proxy &No proxy &HTTP(S) proxy &HTTP(S) proxy &SOCKS5 proxy &SOCKS5 proxy Host Host Port Port User name User name RoomDialogBase Publish room in room directory Publish room in room directory Allow guest accounts to join the room Allow guest accounts to join the room Account Account Room name Room name Primary alias Primary alias Topic Topic About room versions About room versions (loading) (loading) default default stable stable Room version Room version Continue with unstable version? Continue with unstable version? You are using an UNSTABLE room version (%1). The server may stop supporting it at any moment. Do you still want to use this version? You are using an UNSTABLE room version (%1). The server may stop supporting it at any moment. Do you still want to use this version? RoomListDock Mark room as read Mark room as read Add tags... Add tags... Join room Join room Forget room Forget room Remove tag Remove tag Reject invitation Reject invitation Leave room Leave room Enter new tags for the room Enter new tags for the room Enter tags to add to this room, one tag per line Enter tags to add to this room, one tag per line Favourites Favourites Low priority Low priority Rooms (%1) Rooms (%1) Re-sort rooms (TODO) Re-sort rooms (TODO) Change room &settings... Change room &settings... Add Add RoomListModel Invited Invited Low priority Low priority People People Ungrouped rooms Ungrouped rooms Left Left %1 (%Ln room(s)) %1 (%Ln room) %1 (%Ln rooms) %1 (as %2) %1 (as %2) You joined this room You joined this room You left this room You left this room You were invited into this room You were invited into this room Main alias: %1 Main alias: %1 Joined: %Ln Joined: %Ln Joined: %Ln Invited: %Ln Invited: %Ln Invited: %Ln Direct chat with %1 Direct chat with %1 The room enforces encryption The room enforces encryption Unread messages: %1+ Unread messages: %1+ Unread messages: %1 Unread messages: %1 Unread highlights: %1 Unread highlights: %1 ID: %1 ID: %1 Favourites Favourites as %1 as %1 This room's version is unstable! This room's version is unstable! Consider upgrading to a stable version (use room settings for that) Consider upgrading to a stable version (use room settings for that) RoomSettingsDialog Room settings: %1 Room settings: %1 Update room Update room Tags Tags Favourites Favourites Low priority Low priority This version is unstable! Consider upgrading. This version is unstable! Consider upgrading. Upgrade Upgrade Choose new room version Choose new room version You are about to upgrade %1. This operation cannot be reverted. You are about to upgrade %1. This operation cannot be reverted. Creating the new room version, please wait Creating the new room version, please wait Room identifier Room identifier UserListDock Users Users Open direct chat Open direct chat Mention user Mention user Search Search Ignore user Ignore user Kick user Kick user Ban user Ban user Kick %1 Kick %1 Reason Reason Ban %1 Ban %1 (%L1 out of %L2) (%L1 out of %L2) main Display debug information Display debug information Quaternion - an IM client for the Matrix protocol Quaternion - an IM client for the Matrix protocol Override locale Override locale locale locale Hide main window on startup Hide main window on startup FileContent Size: %1, declared type: %2 Size: %1, declared type: %2 Open after downloading Open after downloading Cancel Cancel Save as... Save as... Open Open Open folder Open folder uploaded from %1 uploaded from %1 being uploaded from %1 being uploaded from %1 downloaded to %1 downloaded to %1 ImageContent Cancel downloading Cancel downloading Open externally Open externally Download full size Download full size Save as... Save as... Cancel Cancel TimelineItem Resend Resend Discard Discard Redact Redact Copy link to clipboard Copy link to clipboard Quote Quote SystemTrayIcon Highlight in %1 Highlight in %1 %n highlight(s) %n highlight %n highlights UserListModel Bridged from: %1 Bridged from: %1 ThumbnailResponse Image request hasn't started Image request hasn't started Image request has been cancelled Image request has been cancelled Media id '%1' doesn't follow server/mediaId pattern Media id '%1' doesn't follow server/mediaId pattern No connection to perform image request No connection to perform image request Quaternion-0.0.9.4c/client/translations/quaternion_pl.ts000066400000000000000000001423721347141001300234040ustar00rootroot00000000000000 ChatRoomWidget Choose a room to send messages or enter a command... Wybierz pokój do wysyłania wiadomości lub wprowadź polecenie… Sending encrypted messages is not supported yet Wysyłanie zaszyfrowanych wiadomości nie jest jeszcze obsługiwane There's nothing to send Nie ma niczego do przesłania /join argument doesn't look like a room ID or alias /join nie wygląda jak identyfikator pokoju lub alias Sending a farewell message is not supported yet. If you intended to leave another room, switch to it and type /leave there. Wysyłanie wiadomości pożegnalnej nie jest jeszcze obsługiwane. Jeśli chcesz opuścić inny pokój, przełącz się do niego i wpisz tam /leave. /forget must be followed by the room id/alias, even for the current room /forget musi poprzedzać identyfikator pokoju/alias, nawet dla bieżącego pomieszczenia %1 doesn't look like a room id or alias %1 nie wygląda jak identyfikator pokoju lub alias /invite <memberId> /invite <memberId> /%1 <userId> <reason> /%1 <userId> <reason> %1 is not a member of this room %1 nie jest członkiem tego pokoju /unban <userId> /unban <userId> /unban argument doesn't look like a user ID /unban nie wygląda jak identyfikator użytkownika /ignore <userId> /ignore <userId> /ignore argument doesn't look like a user ID /ignore nie wygląda jak identyfikator użytkownika Couldn't find user %1 on the server Nie można znaleźć użytkownika %1 na serwerze /me needs an argument /me potrzebuje argumentu /notice needs an argument /notice potrzebuje argumentu /%1 <memberId> <message> /%1 <memberId> <message> %1 doesn't seem to have joined room %2 %1 najwyraźniej nie dołączył do pokoju %2 %1 doesn't look like a user id or room alias %1 nie wygląda jak identyfikator użytkownika lub alias pokoju /%1 <memberId> /%1 <memberId> Unknown /command. Use // to send this line literally Nieznane polecenie. Użyj //, aby dosłownie wysłać tę linię Save file as Zapisz plik jako Next completion: %1 Następne dokończenie: %1 Currently typing: %1 Obecnie pisze: %1 Attach Załącz Attach file Załącz plik Add a message to the file or just push Enter Dodaj wiadomość do pliku lub po prostu naciśnij Enter Attaching %1 Załączanie %1 Attaching cancelled Załączanie anulowano There's no such /command outside of room. Nie ma takiego /polecenia poza pokojem. %1 doesn't look like a user id %1 nie wygląda jak identyfikator użytkownika %1 doesn't look like a user ID %1 nie wygląda jak identyfikator użytkownika You should select a room to send messages. Powinieneś/Powinnaś wybrać pokój do wysyłania wiadomości. Send a message (over %1) or enter a command... Wyślij wiadomość (poprzez %1) lub wprowadź polecenie… Timeline (no topic) (brak tematu) %1 bytes %1 bajtów %1 KB %1 KB %1 MB %1 MB %1 GB %1 GB Unknown Nieznany %Ln events back from now (%L1 cached%2) %Ln wydarzenie od teraz (%L1 cached%2) %Ln wydarzenia od teraz (%L1 cached%2) %Ln wydarzeń od teraz (%L1 cached%2) %Ln wydarzeń od teraz (%L1 cached%2) Unstable room version! Niestabilna wersja pokoju! (no name) (bez nazwy) This room has been upgraded Ten pokój został zaktualizowany Go to Room Settings to upgrade the room Przejdź do ustawień pokoju, aby zaktualizować pokój Go to Room Settings to upgrade the room Przejdź do ustawień pokoju, aby zaktualizować pokój CreateRoomDialog Create room Utwórz pokój Add Dodaj Invite user(s) Zaproś użytkownika(-ów) Creating the room, please wait Tworzę pokój, proszę czekać Please fill the fields as desired. None are mandatory Proszę wypełnić pola zgodnie z życzeniem. Żaden z nich nie jest obowiązkowy Dialog Applying changes, please wait Wprowadzanie zmian, proszę czekać LoginDialog Login Zaloguj się Stay logged in Pozostań zalogowany Matrix ID Identyfikator Matrix Password Hasło Device name Nazwa urządzenia Connect to server Połącz z serwerem Connecting and logging in, please wait Łączenie się i logowanie, proszę czekać Re-login Zaloguj ponownie Restoring access, please wait Przywracanie dostępu, proszę czekać MainWindow Loading... Ładowanie… &Accounts &Konta &Login... Zaloguj &się… &Quit &Zakończ &View &Widok Dock &panels Łącz &panele &Display in timeline &Pokaż na osi czasu Normal &join/leave events &Zwykłe zdarzenia dołączania/opuszczania &Redacted events &Zredagowane zdarzenia Show redacted events in the timeline as 'Redacted' instead of hiding them entirely Pokaż zredagowane wydarzenia na osi czasu jako „Zredagowane”, zamiast całkowicie je ukrywać Edit tags order Edytuj kolejność tagów &Room &Pokój Change room &settings... Zmień &ustawienia pokoju… Create &new room... Utwórz &nowy pokój… &Direct chat... &Bezpośrednia rozmowa… &Join room... &Dołącz do pokoju… &Close current room &Zamknij bieżący pokój &Settings &Ustawienia &Help &Pomoc &About &Informacje o Quaternion &Highlight only &Tylko wyróżnij Notifications are entirely suppressed Powiadomienia są całkowicie tłumione &Non-intrusive &Nieinwazyjne Show notifications but do not activate the window Pokaż powiadomienia, ale nie aktywuj okna &Full &Pełne Show notifications and activate the window Pokaż powiadomienia i aktywuj okno Notifications Powiadomienia Default Domyślny XChat XChat Timeline layout Układ osi czasu Load full-size images at once Ładuj od razu pełnowymiarowe obrazy Automatically download a full-size image instead of a thumbnail Automatycznie pobierz obraz w pełnym rozmiarze zamiast miniatury Configure &network proxy... &Konfiguruj proxy sieciowe… Couldn't open a file to save access token Nie można otworzyć pliku, aby zapisać token dostępu Quaternion couldn't open a file to write the access token to. You're logged in but will have to provide your password again when you restart the application. Quaternion nie mógł otworzyć pliku, aby zapisać token dostępu. Jesteś zalogowany, ale będziesz musiał podać hasło ponownie po ponownym uruchomieniu aplikacji. Logged out as %1 Wylogowano jako %1 Sync failed Synchronizacja nie powiodła się The last sync of account %1 has failed with error: %2 Ostatnia synchronizacja konta %1 nie powiodła się z powodu błędu: %2 The last sync has failed with error: %1 Ostatnia synchronizacja nie powiodła się z powodu błędu: %1 Clicking 'Retry' will attempt to resume synchronisation; Clicking 'Cancel' will stop further synchronisation of this account until logout or Quaternion restart. Kliknięcie „Ponów próbę” spowoduje wznowienie synchronizacji; Kliknięcie „Anuluj” zatrzyma dalszą synchronizację tego konta do momentu wylogowania się lub ponownego uruchomienia Quaterniona. Before this server can process your information, you have to agree with its terms and conditions; please click the button below to open the web page where you can do that Zanim serwer ten będzie mógł przetwarzać Twoje informacje, musisz wyrazić zgodę na jego warunki; proszę kliknąć przycisk poniżej, aby otworzyć stronę internetową, na której możesz to zrobić Open web page Otwórz stronę internetową Show &access token &Pokaż token dostępu Access token for %1 Token dostępu dla %1 Your access token is %1...%2; click "Show details..." for the full token Twój token dostępu to %1...%2; kliknij „Pokaż szczegóły…” dla pełnego tokena &Logout &Wyloguj About Quaternion Informacje o Quaternion Welcome to Quaternion Witamy w Quaternion No connections Brak połączeń Join Dołącz Joined %1 as %2 Dołączono %1 jako %2 Enter user id to start direct chat. Wprowadź identyfikator użytkownika, aby rozpocząć bezpośrednią rozmowę. Sync completed - have a good chat Synchronizacja zakończona — miłej rozmowy Couldn't connect to the server as %1; will retry within %2 seconds Nie można było połączyć się z serwerem jako %1; spróbuje ponownie w ciągu %2 sekund Reconnecting... Ponowne łączenie… No SSL support Brak obsługi SSL Your SSL configuration does not allow Quaternion to establish secure connections. Twoja konfiguracja SSL nie zezwala Quaternionowi na ustanowienie bezpiecznych połączeń. SSL error Błąd SSL Proxy needs authentication Proxy wymaga uwierzytelnienia Authenticate Uwierzytelnij User name Nazwa użytkownika Password Hasło Copyright (C) 2018 QMatrixClient project. Copyright (C) 2018 QMatrixClient project. &Thanks &Podziękowania Original project author: %1 Pierwotny autor projektu: %1 Web page Strona internetowa Project leader: %1 Kierownik projektu: %1 Contributors: Współautorzy: Quaternion contributors @ GitHub Współautorzy Quaterniona na GitHubie libQMatrixClient contributors @ GitHub Współautorzy libQMatrixClient na GitHubie Quaternion translators @ Lokalise.co Tłumacze Quaterniona w Lokalise.co Made with: Wykonana z: Show join and leave events Pokaż zdarzenia dołączenia i wyjścia Request URL: %1 Response: %2 Żądany adres URL: %1 Odpowiedź: %2 Tags can be wildcarded by * next to dot(s) Clear the box to reset to defaults Special tags starting with "org.qmatrixclient." are: %1 User-defined tags should start with "u." Tagi mogą być wieloznaczne przez * obok kropek Wyczyść pole, aby przywrócić domyślne ustawienia Specjalne tagi zaczynające się od „org.qmatrixclient” to: %1 Tagi zdefiniowane przez użytkownika powinny zaczynać się od „u” Close to tray Zamykaj do zasobnika Show/hide meaningless activity (join-leave pairs and redacted events between) Pokaż/ukryj bezsensowną aktywność (pary dołączenia, opuszczenia i zredagowanych wydarzeń pomiędzy) Built from Git, commit SHA: Zbudowano z Git, commit SHA: Library commit SHA: Commit SHA biblioteki: Open room... Otwórz pokój… Open room Otwórz pokój Switch to room Przełącz do pokoju Open a room from the room list Otwórz pokój z listy pokoi Access token file found Znaleziono plik tokena dostępu Do you want to migrate the access token for %1 from the file to keychain? Czy chcesz migrować token dostępu dla %1 z pliku do pęku kluczy? Couldn't migrate access token Nie można migrować tokena dostępu Quaternion couldn't migrate access token %1 from the file to keychain. Quaternion nie mógł migrować tokena dostępu %1 z pliku do pęku kluczy. Couldn't save access token Nie można zapisać tokena dostępu Quaternion couldn't save the access token to keychain. Do you want to save the access token to file %1? Quaternion nie mógł zapisać tokena dostępu do pęku kluczy. Czy chcesz zapisać token dostępu do pliku %1? Logging in into a logged in account Logowanie do zalogowanego konta You're trying to log in into an account that's already logged in. Do you want to continue? Próbujesz się zalogować na konto, które jest już zalogowane. Chcesz kontynuować? Couldn't delete access token Nie można usunąć tokena dostępu Quaternion couldn't delete the access token from keychain. Quaternion nie mógł usunąć tokena dostępu z pęku kluczy. Open direct chat? Otworzyć bezpośrednią rozmowę? Open direct chat with user %1? Otworzyć bezpośrednią rozmowę z użytkownikiem %1? Malformed user id Niepoprawny identyfikator użytkownika %1 is not a correct user id %1 nie jest prawidłowym identyfikatorem użytkownika Room not found Nie znaleziono pokoju There's no room %1 in the room list. Check the spelling and the account. Nie ma pokoju %1 na liście pokoi. Sprawdź pisownię i konto. Confirm your account to open %1 Potwierdź twoje konto, aby otworzyć %1 Please connect to a server first Proszę najpierw połączyć się z serwerem Confirm account Potwierdź konto Account Konto Enter room id or alias Wprowadź identyfikator pokoju lub alias Room ID (starting with !) or alias (starting with #) Identyfikator pokoju (zaczynający się od !) lub alias (zaczynający się od #) Confirm account to join %1 Potwierdź konto, aby dołączyć do %1 User ID (starting with @) Identyfikator użytkownika (zaczynający się od @) Start chat Rozpocznij rozmowę Room/user ID, room alias, or matrix.to link ID pokoju/użytkownika, alias pokoju, lub link matrix.to Edit quote style Edytuj styl cytowania Markdown (prepend each line with >) Markdown (poprzedzaj każdą linię >) Custom (apply regex from the config file) Niestandardowy (zastosuj wyrażenie regularne z pliku konfiguracyjnego) Locale's default (%1) Domyślne ustawienia regionalne (%1) Example quote Przykładowy cytat Choose the default style of quotes Wybierz domyślny styl cytatów Special thanks to %1 for all the testing effort Specjalne podziękowania dla %1 za cały wysiłek włożony w testowanie. MessageEventModel Today Dzisiaj Yesterday Wczoraj The day before yesterday Przedwczoraj Redacted Zredagowano Redacted: %1 zredagował(a): %1 a file plik reinvited %1 to the room ponownie zaprosił(a) %1 do pokoju joined the room (repeated) dołączył(a) do pokoju (powtórzono) invited %1 to the room zaprosił(a) %1 do pokoju joined the room dołączył(a) do pokoju cleared the display name wyczyścił(a) swoją wyświetlaną nazwę changed the display name to %1 zmienił(a) swoją wyświetlaną nazwę na %1 cleared the avatar wyczyścił(a) awatar updated the avatar zaktualizował(a) awatar unbanned %1 odbanował(a) %1 has put %1 out of the room wyrzucił(a) %1 z pokoju left the room opuścił(a) pokój banned %1 from the room zbanował(a) %1 z pokoju knocked zapukał made something unknown zrobił(a) coś nieznanego cleared the room main alias wyczyścił(a) główny alias pokoju set the room main alias to: %1 ustawił(a) główny alias pokoju na: %1 cleared the room name wyczyścił(a) nazwę pokoju set the room name to: %1 ustawił(a) nazwę pokoju na %1 cleared the topic wyczyścił(a) temat set the topic to: %1 ustawił(a) temat na %1 changed the room avatar zmienił(a) awatar pokoju activated End-to-End Encryption aktywował(a) szyfrowanie End-to-End withdrew %1's invitation wycofał(a) zaproszenie %1 rejected the invitation odrzucił(a) zaproszenie updated the database zaktualizował bazę danych updated %1 state zaktualizował(a) stan %1 updated %1 state for %2 zaktualizował(a) stan %1 dla %2 Unknown event Nieznane zdarzenie upgraded the room to version %1 zaktualizował(a) pokój do wersji %1 created the room, version %1 stworzył(a) pokój, wersja %1 has set room aliases on server %1 to: %2 ustawił(a) alias pokoju na serwerze %1 na: %2 has put %1 out of the room: %2 wyrzucił(a) %1 z pokoju: %2 banned %1 from the room: %2 zbanował(a) %1 z pokoju: %2 NetworkConfigDialog Network proxy settings Ustawienia proxy sieciowego &Override system defaults &Nadpisz domyślne ustawienia systemowe &No proxy &Bez proxy &HTTP(S) proxy &HTTP(S) proxy &SOCKS5 proxy &SOCKS5 proxy Host Host Port Port User name Nazwa użytkownika RoomDialogBase Publish room in room directory Opublikuj pokój w katalogu pokoju Allow guest accounts to join the room Zezwalaj kontom gości na dołączenie do pokoju Account Konto Room name Nazwa pokoju Primary alias Główny alias Topic Temat About room versions O wersjach pokoi (loading) (ładowanie) default domyślna stable stabilna Room version Wersja pokoju Continue with unstable version? Kontynuować z niestabilną wersją? You are using an UNSTABLE room version (%1). The server may stop supporting it at any moment. Do you still want to use this version? Używasz NIESTABILNEJ wersji pokoju (%1). Serwer może przestać go obsługiwać w każdej chwili. Czy nadal chcesz używać tej wersji? RoomListDock Mark room as read Oznacz pokój jako przeczytany Add tags... Dodaj tagi… Join room Dołącz do pokoju Forget room Zapomnij pokój Remove tag Usuń tag Reject invitation Odrzuć zaproszenie Leave room Opuść pokój Enter new tags for the room Wprowadź nowe tagi dla pokoju Enter tags to add to this room, one tag per line Wprowadź nowe tagi dla pokoju, jeden tag na linię Favourites Ulubione Low priority Niski priorytet Rooms (%1) Pokoje (%1) Re-sort rooms (TODO) Ponownie posortuj pokoje (TODO) Change room &settings... Zmień &ustawienia pokoju… Add Dodaj RoomListModel Invited Zaproszone Low priority Niski priorytet People Ludzie Ungrouped rooms Niezgrupowane pokoje Left Opuszczone %1 (%Ln room(s)) %1 (%Ln pokój) %1 (%Ln pokoje) %1 (%Ln pokojów) %1 (%Ln pokojów) %1 (as %2) %1 (jako %2) You joined this room Dołączyłeś(-aś) do pokoju You left this room Opuściłeś(-aś) pokój You were invited into this room Zostałeś(-aś) zaproszony(-a) do tego pokoju Main alias: %1 Główny alias: %1 Joined: %Ln Dołączeni: %Ln Dołączeni: %Ln Dołączeni: %Ln Dołączeni: %Ln Invited: %Ln Zaproszeni: %Ln Zaproszeni: %Ln Zaproszeni: %Ln Zaproszeni: %Ln Direct chat with %1 Bezpośrednia rozmowa z %1 The room enforces encryption Ten pokój wymusza szyfrowanie Unread messages: %1+ Nieprzeczytane wiadomości: %1+ Unread messages: %1 Nieprzeczytane wiadomości: %1 Unread highlights: %1 Nieprzeczytane wyróżnienia: %1 ID: %1 ID: %1 Favourites Ulubione as %1 jako %1 This room's version is unstable! Wersja tego pokoju jest niestabilna! RoomSettingsDialog Room settings: %1 Ustawienia pokoju: %1 Update room Zaktualizuj pokój Tags Tagi Favourites Ulubione Low priority Niski priorytet This version is unstable! Consider upgrading. Ta wersja jest niestabilna! Rozważ aktualizację. Upgrade Zaktualizuj Choose new room version Wybierz nową wersję pokoju You are about to upgrade %1. This operation cannot be reverted. Zamierzasz zaktualizować %1. Ta operacja nie może zostać cofnięta. Creating the new room version, please wait Tworzenie nowej wersji pokoju, proszę czekać Room identifier Identyfikator pokoju UserListDock Users Użytkownicy Open direct chat Otwórz bezpośredni czat Mention user Wspomnij użytkownika Search Szukaj Ignore user Ignoruj użytkownika Kick user Wyrzuć użytkownika Ban user Banuj użytkownika Kick %1 Wyrzuć %1 Reason Powód Ban %1 Banuj %1 (%L1 out of %L2) (%L1 z %L2) main Display debug information Wyświetl informacje debugowe Quaternion - an IM client for the Matrix protocol Quaternion - komunikator internetowy dla protokołu Matrix Override locale Nadpisz ustawienia regionalne locale locale Hide main window on startup Ukryj główne okno podczas uruchamiania FileContent Size: %1, declared type: %2 Rozmiar: %1, zadeklarowany typ: %2 Open after downloading Otwórz po pobraniu Cancel Anuluj Save as... Zapisz jako… Open Otwórz Open folder Otwórz folder uploaded from %1 przesłany z %1 downloaded to %1 pobrano do %1 ImageContent Cancel downloading Anuluj pobieranie Open externally Otwórz zewnętrznie Download full size Pobierz pełny rozmiar Save as... Zapisz jako… Cancel Anuluj TimelineItem Resend Wyślij ponownie Discard Odrzuć Redact Przeredaguj Copy link to clipboard Skopiuj link do schowka Quote Cytuj SystemTrayIcon Highlight in %1 Podświetlenie w %1 %n highlight(s) %n podświetlenie %n podświetlenia %n podświetleń %n podświetleń UserListModel Bridged from: %1 Mostkowany(-a) z: %1 ThumbnailResponse Image request hasn't started Żądanie obrazu nie zostało rozpoczęte Image request has been cancelled Żądanie obrazu zostało anulowane No connection to perform image request Brak połączeń do wykonania żądania obrazu Quaternion-0.0.9.4c/client/translations/quaternion_ru.ts000066400000000000000000001701471347141001300234200ustar00rootroot00000000000000 ChatRoomWidget Choose a room to send messages or enter a command... Выберите комнату для отправки сообщений или введите команду... Sending encrypted messages is not supported yet Отправка шифрованных сообщений пока не поддерживается There's nothing to send Нечего отправлять /join argument doesn't look like a room ID or alias Аргумент /join не похож на идентификатор или псевдоним комнаты Sending a farewell message is not supported yet. If you intended to leave another room, switch to it and type /leave there. Отправка прощального сообщения пока не поддерживается. Если вы намеревались покинуть другую комнату, переключитесь на нее и введите /leave в ней. /forget must be followed by the room id/alias, even for the current room Команда /forget должна сопровождаться идентификатором или псевдонимом комнаты, даже при использовании в текущей комнате %1 doesn't look like a room id or alias %1 не похож на идентификатор или псевдоним комнаты /invite <memberId> /invite <ID-участника> /%1 <userId> <reason> /%1 <ID-пользователя> <причина> %1 is not a member of this room %1 не является участником этой комнаты /unban <userId> /unban <ID-пользователя> /unban argument doesn't look like a user ID Аргумент /unban не похож на идентификатор или адрес пользователя /ignore <userId> /ignore <ID-пользователя> /ignore argument doesn't look like a user ID Аргумент /ignore не похож на идентификатор или адрес пользователя Couldn't find user %1 on the server Пользователь %1 не найден на сервере /me needs an argument /me требует указания аргумента /notice needs an argument /notice требует указания аргумента /%1 <memberId> <message> /%1 <ID-участника> <сообщение> %1 doesn't seem to have joined room %2 %1, похоже, не присоединился к комнате %2 %1 doesn't look like a user id or room alias %1 не похож на идентификатор пользователя или псевдоним комнаты /%1 <memberId> /%1 <ID-участника> Unknown /command. Use // to send this line literally Неизвестная команда. Используйте //, чтобы отправить буквально эту строку Save file as Сохранить файл как Next completion: %1 Следующая подсказка: %1 Currently typing: %1 Сейчас печатает: %1 Attach Добавить Attach file Добавить файл Add a message to the file or just push Enter Снабдите файл сообщением или просто нажмите Enter Attaching %1 Будет отправлен %1 Attaching cancelled Отправка файла отменена There's no such /command outside of room. Нет такой /команды без указания комнаты. %1 doesn't look like a user id %1 не похож на идентификатор пользователя %1 doesn't look like a user ID %1 не похож на идентификатор пользователя You should select a room to send messages. Для отправки сообщений нужно выбрать комнату. Send a message (over %1) or enter a command... Отправить сообщение (через %1) или ввести команду... Timeline (no topic) (без темы) %1 bytes %1 байт %1 KB %1 КБ %1 MB %1 МБ %1 GB %1 ГБ Unknown Неизвестно %Ln events back from now (%L1 cached%2) %Ln событие до текущего времени (%L1 кешировано%2) %Ln события до текущего времени (%L1 кешировано%2) %Ln событий до текущего времени (%L1 кешировано%2) and loading и загружается еще Unstable room version! Нестабильная версия комнаты! (no name) (без имени) This room has been upgraded Эта комната была обновлена Go to Room Settings to upgrade the room Перейдите в Настройки комнаты, чтобы изменить версию комнаты Go to Room Settings to upgrade the room Перейдите в Настройки комнаты, чтобы изменить версию комнаты CreateRoomDialog Create room Создать комнату Add Добавить Invite user(s) Пригласить пользователя(ей) Creating the room, please wait Комната создаётся, пожалуйста подождите Please fill the fields as desired. None are mandatory Пожалуйста, заполните поля по желанию. Обязательных полей нет. Dialog Applying changes, please wait Применение изменений, пожалуйста подождите LoginDialog Login Подключиться Stay logged in Оставаться в системе Matrix ID Идентификатор Matrix Password Пароль Device name Название устройства Connect to server Соединиться с сервером Connecting and logging in, please wait Выполняются подключение и вход в систему; пожалуйста, подождите Re-login Войти снова Restoring access, please wait Восстанавливается доступ, пожалуйста подождите MainWindow Loading... Загрузка... &Accounts &Учетные записи &Login... &Подключиться... &Quit &Выход &View &Вид Dock &panels &Док-панели &Display in timeline &Отображать в истории событий Normal &join/leave events Обычные события входа/выхода &Redacted events &Удаленные события Show redacted events in the timeline as 'Redacted' instead of hiding them entirely Показывать удаленные события с пометкой «Удалено», а не полностью их скрывать &No-effect activity Активность &без результата Edit tags order Изменить порядок тегов &Room &Комната Change room &settings... Изменить &настройки комнаты... Create &new room... Создать &новую комнату... &Direct chat... &Прямой чат... &Join room... Присоединиться к &комнате... &Close current room &Закрыть текущую комнату &Settings &Настройки &Help &Помощь &About &О программе &Highlight only Только при &упоминаниях Notifications are entirely suppressed Не показывать уведомления &Non-intrusive &Ненавязчивые Show notifications but do not activate the window Показывать уведомления, но не активировать окно &Full &Полные Show notifications and activate the window Показывать уведомления и активировать окно Notifications Уведомления Default По умолчанию The layout with author labels above blocks of messages Вид с именами авторов над сообщениями XChat XChat The layout with author labels to the left from each message Вид с именами авторов слева от сообщений Timeline layout Вид истории событий Load full-size images at once Загружать полноразмерные изображения сразу Automatically download a full-size image instead of a thumbnail Автоматически загружать полноразмерное изображение вместо миниатюры Configure &network proxy... Настроить &прокси-сервер... Couldn't open a file to save access token Не удается открыть файл, чтобы сохранить ключ доступа Quaternion couldn't open a file to write the access token to. You're logged in but will have to provide your password again when you restart the application. Quaternion не смог открыть файл для записи ключа доступа. Вы вошли в систему, но при повторном запуске приложения вам придется снова указать свой пароль. Couldn't set access token file permissions Не удалось установить права доступа к файлу с ключом доступа Quaternion couldn't restrict permissions on the access token file. Do you still want to save the access token to it? Quaternion не может ограничить права доступа к файлу с ключом доступа. Вы все еще хотите сохранить ключ доступа? Logged out as %1 Вышел как %1 Sync failed Сбой синхронизации The last sync of account %1 has failed with error: %2 При последней синхронизации учётной записи %1 произошла ошибка: %2 The last sync has failed with error: %1 При последней синхронизации произошла ошибка: %1 Clicking 'Retry' will attempt to resume synchronisation; Clicking 'Cancel' will stop further synchronisation of this account until logout or Quaternion restart. Нажмите кнопку «Повторить попытку» чтобы попытаться возобновить синхронизацию; нажмите «Отмена» чтобы остановить дальнейшую синхронизацию этой учетной записи до выхода из системы или перезапуска Quaternion. Before this server can process your information, you have to agree with its terms and conditions; please click the button below to open the web page where you can do that Прежде чем этот сервер сможет обрабатывать вашу информацию, вы должны согласиться с его правилами и условиями; нажмите кнопку ниже, чтобы открыть веб-страницу, где вы можете сделать это Open web page Открыть веб-страницу Show &access token Показать &ключ доступа Access token for %1 Ключ доступа для %1 Your access token is %1...%2; click "Show details..." for the full token Ваш ключ доступа %1...% 2; нажмите «Показать детали ...» для полного ключа &Logout &Отключиться About Quaternion О программе Quaternion Welcome to Quaternion Добро пожаловать в Quaternion No connections Нет соединений Join Присоединиться Joined %1 as %2 Присоединился к %1 как %2 Enter user id to start direct chat. Введите идентификатор пользователя, чтобы начать прямой чат. Sync completed - have a good chat Синхронизация завершена - приятного общения Couldn't connect to the server as %1; will retry within %2 seconds Не удалось подключиться к серверу как %1; попытка будет повторена в течение %2 секунд Reconnecting... Переподключение... No SSL support Нет поддержки SSL Your SSL configuration does not allow Quaternion to establish secure connections. Конфигурация SSL не позволяет Quarternion установить безопасное соединение. SSL error Ошибка SSL Proxy needs authentication Прокси-сервер требует авторизации Authenticate Войти User name Имя пользователя Password Пароль Copyright (C) 2018 QMatrixClient project. Copyright (C) 2018 Проект QMatrixClient. &Thanks &Благодарности Original project author: %1 Оригинальный автор проекта: %1 Web page Веб-страница Project leader: %1 Ведущий разработчик: %1 Contributors: Разработчики: Quaternion contributors @ GitHub Разработчики Quaternion @ GitHub libQMatrixClient contributors @ GitHub Разработчики libQMatrixClient @ GitHub Quaternion translators @ Lokalise.co Переводчики Quaternion @ Lokalise.co Made with: Сделано с помощью: Show join and leave events Показать события входа и выхода Use shuttle scrollbar (requires restart) Использовать прокрутку с контролем скорости (требуется перезапуск) Control scroll velocity instead of position with the timeline scrollbar Полоса прокрутки меняет скорость прокрутки вместо позиции Request URL: %1 Response: %2 URL запроса: %1 Ответ: %2 Tags can be wildcarded by * next to dot(s) Clear the box to reset to defaults Special tags starting with "org.qmatrixclient." are: %1 User-defined tags should start with "u." Группы тегов можно объединять, указывая * после точки Очистите поле ввода, чтобы вернуться к настройкам по умолчанию Особые теги, начинающиеся на org.qmatrixclient: %1 Пользовательские теги рекомендуется начинать с "u." Close to tray Свернуть в область уведомлений Make close button [X] minimize to tray instead of closing main window Кнопка "Закрыть" [X] сворачивает окно в область уведомлений вместо закрытия Show/hide meaningless activity (join-leave pairs and redacted events between) Показать/скрыть бесполезную активность (пары из входа и выхода и удаленные события между ними) Built from Git, commit SHA: Скомпилировано из Git, SHA-ключ коммита: Library commit SHA: SHA-ключ коммита в репозитории: Open room... Открыть комнату... Open room Открыть комнату Switch to room Переключиться на комнату Open a room from the room list Открыть комнату из списка комнат Show/hide Rooms dock panel Показать/скрыть панель «Комнаты» Show/hide Users dock panel Показать/скрыть панель «Пользователи» Access token file found Файл ключа доступа найден Do you want to migrate the access token for %1 from the file to keychain? Вы хотите перенести ключ доступа для %1 из файла в хранилище ключей? Couldn't migrate access token Не удаётся переместить ключ доступа Quaternion couldn't migrate access token %1 from the file to keychain. Quaternion не может переместить ключ доступа %1 из файла в хранилище ключей. Couldn't save access token Не удаётся сохранить ключ доступа Quaternion couldn't save the access token to keychain. Do you want to save the access token to file %1? Quaternion не может сохранить ключ доступа в хранилище ключей. Вы хотите сохранить ключ в файл %1? Logging in into a logged in account Вход в залогиненный аккаунт You're trying to log in into an account that's already logged in. Do you want to continue? Вы пытаетесь войти в учётную запись, в которую уже вошли. Хотите продолжить? Couldn't delete access token Не удаётся удалить ключ доступа Quaternion couldn't delete the access token from keychain. Quaternion не может удалить ключ доступа из хранилища ключей. Open direct chat? Открыть прямой чат? Open direct chat with user %1? Открыть прямой чат с пользователем %1? Malformed user id Неверный идентификатор пользователя %1 is not a correct user id %1 не является правильным идентификатором пользователя Room not found Комната не найдена There's no room %1 in the room list. Check the spelling and the account. В списке комнат нет комнаты %1. Проверьте орфографию и учётную запись. Confirm your account to open %1 Подтвердите свою учётную запись, чтобы открыть %1 Please connect to a server first Пожалуйста, сначала подключитесь к серверу Confirm account Подтвердить учётную запись Account Учетная запись Enter room id or alias Введите идентификатор или псевдоним комнаты Room ID (starting with !) or alias (starting with #) Идентификатор комнаты (начиная с !) или псевдоним комнаты (начиная с #) Confirm account to join %1 Подтвердите учётную запись для присоединения к %1 User ID (starting with @) Идентификатор пользователя (начиная с @) Start chat Начать чат Room/user ID, room alias, or matrix.to link Идентификатор комнаты/пользователя, псевдоним комнаты или ссылка на matrix.to Edit quote style Изменить стиль цитирования Markdown (prepend each line with >) Markdown (> перед каждой строчкой) Custom (apply regex from the config file) Пользовательский (применить регулярное выражение из файла конфигурации) Locale's default (%1) По умолчанию для региона (%1) Example quote Пример цитаты Choose the default style of quotes Выберите стиль цитирования по умолчанию Special thanks to %1 for all the testing effort Особые благодарности %1 за тестирование MessageEventModel Today Сегодня Yesterday Вчера The day before yesterday Позавчера Redacted Удалено Redacted: %1 Удалено: %1 a file файл reinvited %1 to the room снова пригласил %1 в комнату joined the room (repeated) присоединился к комнате (повторно) invited %1 to the room пригласил пользователя %1 в комнату joined the room присоединился к комнате cleared the display name очистил отображаемое имя changed the display name to %1 изменил отображаемое имя на %1 cleared the avatar очистил аватар updated the avatar обновил аватар unbanned %1 разблокировал %1 self-unbanned разблокировал себя в комнате has put %1 out of the room выгнал %1 из комнаты left the room покинул комнату banned %1 from the room заблокировал %1 в комнате self-banned from the room заблокировал себя в комнате knocked постучался made something unknown сделал что-то неизвестное cleared the room main alias очистил основной псевдоним комнаты set the room main alias to: %1 установил основной псевдоним комнаты: %1 cleared the room name очистил название комнаты set the room name to: %1 установил название комнаты: %1 cleared the topic очистил тему set the topic to: %1 установил тему: %1 changed the room avatar изменил аватар комнаты activated End-to-End Encryption включил сквозное шифрование withdrew %1's invitation отозвал приглашение пользователя %1 rejected the invitation отклонил приглашение updated the database обновил базу данных updated %1 state обновил состояние %1 updated %1 state for %2 обновил состояние %1 для ключа %2 Unknown event Неизвестное событие upgraded the room to version %1 версия комнаты изменена на %1 created the room, version %1 создал комнату, версия %1 has set room aliases on server %1 to: %2 установил псевдоним комнаты на сервере %1: %2 has put %1 out of the room: %2 выгнал %1 из комнаты: %2 banned %1 from the room: %2 заблокировал %1 в комнате: %2 NetworkConfigDialog Network proxy settings &Настройки прокси-сервера &Override system defaults &Переопределить параметры по умолчанию &No proxy &Без прокси-сервера &HTTP(S) proxy &HTTP(S) прокси &SOCKS5 proxy &SOCKS5 прокси Host Хост Port Порт User name Имя пользователя RoomDialogBase Publish room in room directory Опубликовать комнату в каталоге Allow guest accounts to join the room Разрешить присоединение гостевым аккаунтам Account Учетная запись Room name Название комнаты Primary alias Основной псевдоним Topic Тема About room versions О версиях комнаты (loading) (загрузка) default по-умолчанию stable стабильная Room version Версия комнаты Continue with unstable version? Продолжить с нестабильной версией? You are using an UNSTABLE room version (%1). The server may stop supporting it at any moment. Do you still want to use this version? Вы используете НЕСТАБИЛЬНУЮ версию комнаты (%1). Сервер может перестать поддерживать её в любой момент. Вы все еще хотите использовать эту версию? RoomListDock Mark room as read Пометить комнату прочитанной Add tags... Добавить теги... Join room Присоединиться к комнате Forget room Забыть комнату Remove tag Удалить тег Reject invitation Отклонить приглашение Leave room Покинуть комнату Enter new tags for the room Введите новые теги для комнаты Enter tags to add to this room, one tag per line Введите теги для добавления в эту комнату, по одному тегу в строке Favourites Избранные Low priority Неважные Rooms (%1) Комнаты (%1) Re-sort rooms (TODO) Пересортировать комнаты (TODO) Change room &settings... Изменить &настройки комнаты... Add Добавить RoomListModel Invited Приглашения Low priority Неважные People Люди Ungrouped rooms Остальные Left Покинутые %1 (%Ln room(s)) %1 (%Ln комната) %1 (%Ln комнаты) %1 (%Ln комнат) %1 (as %2) %1 (как %2) You joined this room Вы присоединились к этой комнате You left this room Вы покинули эту комнату You were invited into this room Вас пригласили в эту комнату Main alias: %1 Основной псевдоним: %1 Joined: %Ln Присоединился: %Ln Присоединились: %Ln Присоединились: %Ln Invited: %Ln Приглашен: %Ln Приглашено: %Ln Приглашено: %Ln Direct chat with %1 Прямой чат с %1 The room enforces encryption В комнате включено шифрование Unread messages: %1+ Непрочитанных сообщений: %1+ Unread messages: %1 Непрочитанных сообщений: %1 Unread highlights: %1 Непрочитанных уведомлений: %1 ID: %1 Идентификатор: %1 Favourites Избранные as %1 как %1 This room's version is unstable! Версия этой комнаты нестабильна! Consider upgrading to a stable version (use room settings for that) Рекомендуется обновление до стабильной версии комнаты (используйте настройки комнаты для этого) RoomSettingsDialog Room settings: %1 Настройки комнаты: %1 Update room Обновить комнату Tags Теги Favourites Избранные Low priority Неважные This version is unstable! Consider upgrading. Эта версия нестабильна! Рекомендуется обновление. Upgrade Обновить Choose new room version Выберите новую версию комнаты You are about to upgrade %1. This operation cannot be reverted. Вы собираетесь обновить %1. Эта операция не может быть отменена. Creating the new room version, please wait Создание новой версии комнаты, подождите пожалуйста Room identifier Идентификатор комнаты UserListDock Users Пользователи Open direct chat Открыть прямой чат Mention user Упомянуть пользователя Search Поиск Ignore user Игнорировать пользователя Kick user Выгнать пользователя Ban user Заблокировать пользователя Kick %1 Выгнать %1 Reason Причина Ban %1 Заблокировать %1 (%L1 out of %L2) (%L1 из %L2) main Display debug information Отображать отладочную информацию Quaternion - an IM client for the Matrix protocol Quaternion - клиент мгновенных сообщений для протокола Matrix Override locale Переопределить язык locale язык Hide main window on startup Скрыть основное окно при запуске FileContent Size: %1, declared type: %2 Размер: %1, объявленный тип: %2 Open after downloading Открыть после загрузки Cancel Отменить Save as... Сохранить как... Open Открыть Open folder Открыть папку uploaded from %1 отправлен из файла %1 being uploaded from %1 отправляется из файла %1 downloaded to %1 скачано в %1 ImageContent Cancel downloading Отменить загрузку Open externally Открыть через приложение Download full size Скачать в полном размере Save as... Сохранить как... Cancel Отменить TimelineItem Resend Отправить повторно Discard Отменить Redact Удалить Copy link to clipboard Скопировать ссылку в буфер обмена Quote Цитировать SystemTrayIcon Highlight in %1 Упоминание в %1 %n highlight(s) %n упоминание %n упоминания %n упоминаний UserListModel Bridged from: %1 Переправлен(а) из: %1 ThumbnailResponse Image request hasn't started Запрос на изображение не запущен Image request has been cancelled Запрос на изображение отменен Media id '%1' doesn't follow server/mediaId pattern Идентификатор файла "%1" не соответствует шаблону server/mediaId No connection to perform image request Нет соединения для выполнения запроса изображения Quaternion-0.0.9.4c/client/userlistdock.cpp000066400000000000000000000143171347141001300206470ustar00rootroot00000000000000/************************************************************************** * * * Copyright (C) 2015 Felix Rohrbach * * * * 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 . * * * **************************************************************************/ #include "userlistdock.h" #include #include #include #include #include #include #include #include #include #include "models/userlistmodel.h" #include "quaternionroom.h" UserListDock::UserListDock(QWidget* parent) : QDockWidget(tr("Users"), parent) , contextMenu(new QMenu(this)) { setObjectName(QStringLiteral("UsersDock")); m_box = new QVBoxLayout(); m_box->addSpacing(1); m_filterline = new QLineEdit(this); m_filterline->setPlaceholderText(tr("Search")); m_filterline->setDisabled(true); m_box->addWidget(m_filterline); m_view = new QTableView(this); m_view->setShowGrid(false); m_view->horizontalHeader()->setStretchLastSection(true); m_view->horizontalHeader()->setVisible(false); m_view->verticalHeader()->setVisible(false); m_box->addWidget(m_view); m_widget = new QWidget(this); m_widget->setLayout(m_box); setWidget(m_widget); connect(m_view, &QTableView::activated, this, &UserListDock::requestUserMention); connect( m_view, &QTableView::pressed, this, [this] { if (QGuiApplication::mouseButtons() & Qt::MidButton) startChatSelected(); }); m_model = new UserListModel(); m_view->setModel(m_model); connect( m_model, &UserListModel::membersChanged, this, &UserListDock::refreshTitle ); connect( m_model, &QAbstractListModel::modelReset, this, &UserListDock::refreshTitle ); connect(m_filterline, &QLineEdit::textEdited, m_model, &UserListModel::filter); contextMenu->addAction(QIcon::fromTheme("contact-new"), tr("Open direct chat"), this, &UserListDock::startChatSelected); contextMenu->addAction(tr("Mention user"), this, &UserListDock::requestUserMention); ignoreAction = contextMenu->addAction(QIcon::fromTheme("mail-thread-ignored"), tr("Ignore user"), this, &UserListDock::ignoreUser); ignoreAction->setCheckable(true); contextMenu->addSeparator(); contextMenu->addAction(QIcon::fromTheme("im-ban-kick-user"), tr("Kick user"), this,&UserListDock::kickUser); contextMenu->addAction(QIcon::fromTheme("im-ban-user"), tr("Ban user"), this, &UserListDock::banUser); setContextMenuPolicy(Qt::CustomContextMenu); connect(this, &QWidget::customContextMenuRequested, this, &UserListDock::showContextMenu); } void UserListDock::setRoom(QuaternionRoom* room) { if (m_currentRoom) m_currentRoom->setCachedUserFilter(m_filterline->text()); m_currentRoom = room; m_model->setRoom(room); m_filterline->setEnabled(room); m_filterline->setText(room ? room->cachedUserFilter() : ""); m_model->filter(m_filterline->text()); } void UserListDock::refreshTitle() { setWindowTitle(tr("Users") + (!m_currentRoom ? QString() : ' ' + (m_model->rowCount() == m_currentRoom->joinedCount() ? QStringLiteral("(%L1)").arg(m_currentRoom->joinedCount()) : tr("(%L1 out of %L2)", "%found out of %total users") .arg(m_model->rowCount()).arg(m_currentRoom->joinedCount()))) ); } void UserListDock::showContextMenu(QPoint pos) { contextMenu->popup(mapToGlobal(pos)); ignoreAction->setChecked(isIgnored()); } void UserListDock::startChatSelected() { if (auto* user = getSelectedUser()) user->requestDirectChat(); } void UserListDock::requestUserMention() { if (auto* user = getSelectedUser()) emit userMentionRequested(user); } void UserListDock::kickUser() { if (auto* user = getSelectedUser()) { bool ok; const auto reason = QInputDialog::getText(this, tr("Kick %1").arg(user->id()), tr("Reason"), QLineEdit::Normal, nullptr, &ok); if (ok) { m_currentRoom->kickMember(user->id(), reason); } } } void UserListDock::banUser() { if (auto* user = getSelectedUser()) { bool ok; const auto reason = QInputDialog::getText(this, tr("Ban %1").arg(user->id()), tr("Reason"), QLineEdit::Normal, nullptr, &ok); if (ok) { m_currentRoom->ban(user->id(), reason); } } } void UserListDock::ignoreUser() { if (auto* user = getSelectedUser()) { if (!user->isIgnored()) user->ignore(); else user->unmarkIgnore(); } } bool UserListDock::isIgnored() { if (auto* user = getSelectedUser()) return user->isIgnored(); return false; } QMatrixClient::User* UserListDock::getSelectedUser() const { auto index = m_view->currentIndex(); if (!index.isValid()) return nullptr; auto* const user = m_model->userAt(index); Q_ASSERT(user); return user; } Quaternion-0.0.9.4c/client/userlistdock.h000066400000000000000000000045731347141001300203170ustar00rootroot00000000000000/************************************************************************** * * * Copyright (C) 2015 Felix Rohrbach * * * * 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 . * * * **************************************************************************/ #pragma once #include #include namespace QMatrixClient { class User; } class UserListModel; class QuaternionRoom; class QTableView; class QMenu; class QLineEdit; class UserListDock: public QDockWidget { Q_OBJECT public: explicit UserListDock(QWidget* parent = nullptr); void setRoom( QuaternionRoom* room ); signals: void userMentionRequested(QMatrixClient::User* u); private slots: void refreshTitle(); void showContextMenu(QPoint pos); void startChatSelected(); void requestUserMention(); void kickUser(); void banUser(); void ignoreUser(); bool isIgnored(); private: QWidget* m_widget; QVBoxLayout* m_box; QTableView* m_view; QLineEdit* m_filterline; UserListModel* m_model; QuaternionRoom* m_currentRoom = nullptr; QMenu* contextMenu; QAction* ignoreAction; QMatrixClient::User* getSelectedUser() const; }; Quaternion-0.0.9.4c/cmake/000077500000000000000000000000001347141001300152245ustar00rootroot00000000000000Quaternion-0.0.9.4c/cmake/ECMInstallIcons.cmake000066400000000000000000000304771347141001300211700ustar00rootroot00000000000000#.rst: # ECMInstallIcons # --------------- # # Installs icons, sorting them into the correct directories according to the # FreeDesktop.org icon naming specification. # # :: # # ecm_install_icons(ICONS [ [...]] # DESTINATION # [LANG ] # [THEME ]) # # The given icons, whose names must match the pattern:: # # --. # # will be installed to the appropriate subdirectory of DESTINATION according to # the FreeDesktop.org icon naming scheme. By default, they are installed to the # "hicolor" theme, but this can be changed using the THEME argument. If the # icons are localized, the LANG argument can be used to install them in a # locale-specific directory. # # ```` is a numeric pixel size (typically 16, 22, 32, 48, 64, 128 or 256) # or ``sc`` for scalable (SVG) files, ```` is one of the standard # FreeDesktop.org icon groups (actions, animations, apps, categories, devices, # emblems, emotes, intl, mimetypes, places, status) and ```` is one of # ``.png``, ``.mng`` or ``.svgz``. # # The typical installation directory is ``share/icons``. # # .. code-block:: cmake # # ecm_install_icons(ICONS 22-actions-menu_new.png # DESTINATION share/icons) # # The above code will install the file ``22-actions-menu_new.png`` as # ``${CMAKE_INSTALL_PREFIX}/share/icons//22x22/actions/menu_new.png`` # # Users of the :kde-module:`KDEInstallDirs` module would normally use # ``${ICON_INSTALL_DIR}`` as the DESTINATION, while users of the GNUInstallDirs # module should use ``${CMAKE_INSTALL_DATAROOTDIR}/icons``. # # An old form of arguments will also be accepted:: # # ecm_install_icons( []) # # This matches files named like:: # # --. # # where ```` is one of # * ``hi`` for hicolor # * ``lo`` for locolor # * ``cr`` for the Crystal icon theme # * ``ox`` for the Oxygen icon theme # * ``br`` for the Breeze icon theme # # With this syntax, the file ``hi22-actions-menu_new.png`` would be installed # into ``/hicolor/22x22/actions/menu_new.png`` # # Since pre-1.0.0. #============================================================================= # Copyright 2014 Alex Merry # Copyright 2013 David Edmundson # Copyright 2008 Chusslove Illich # Copyright 2006 Alex Neundorf # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # 1. Redistributions of source code must retain the copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. include(CMakeParseArguments) # A "map" of short type names to the directories. # Unknown names produce a warning. set(_ECM_ICON_GROUP_mimetypes "mimetypes") set(_ECM_ICON_GROUP_places "places") set(_ECM_ICON_GROUP_devices "devices") set(_ECM_ICON_GROUP_apps "apps") set(_ECM_ICON_GROUP_actions "actions") set(_ECM_ICON_GROUP_categories "categories") set(_ECM_ICON_GROUP_status "status") set(_ECM_ICON_GROUP_emblems "emblems") set(_ECM_ICON_GROUP_emotes "emotes") set(_ECM_ICON_GROUP_animations "animations") set(_ECM_ICON_GROUP_intl "intl") # For the "compatibility" syntax: a "map" of short theme names to the theme # directory set(_ECM_ICON_THEME_br "breeze") set(_ECM_ICON_THEME_ox "oxygen") set(_ECM_ICON_THEME_cr "crystalsvg") set(_ECM_ICON_THEME_lo "locolor") set(_ECM_ICON_THEME_hi "hicolor") macro(_ecm_install_icons_v1 _defaultpath) # the l10n-subdir if language given as second argument (localized icon) set(_lang ${ARGV1}) if(_lang) set(_l10n_SUBDIR l10n/${_lang}) else() set(_l10n_SUBDIR ".") endif() set(_themes) # first the png icons file(GLOB _icons *.png) foreach (_current_ICON ${_icons} ) # since CMake 2.6 regex matches are stored in special variables CMAKE_MATCH_x, if it didn't match, they are empty string(REGEX MATCH "^.*/([a-zA-Z]+)([0-9]+)\\-([a-z]+)\\-(.+\\.png)$" _dummy "${_current_ICON}") set(_type "${CMAKE_MATCH_1}") set(_size "${CMAKE_MATCH_2}") set(_group "${CMAKE_MATCH_3}") set(_name "${CMAKE_MATCH_4}") set(_theme_GROUP ${_ECM_ICON_THEME_${_type}}) if( _theme_GROUP) list(APPEND _themes "${_theme_GROUP}") _ECM_ADD_ICON_INSTALL_RULE(${CMAKE_CURRENT_BINARY_DIR}/install_icons.cmake ${_defaultpath}/${_theme_GROUP}/${_size}x${_size} ${_group} ${_current_ICON} ${_name} ${_l10n_SUBDIR}) endif() endforeach (_current_ICON) # mng icons file(GLOB _icons *.mng) foreach (_current_ICON ${_icons} ) # since CMake 2.6 regex matches are stored in special variables CMAKE_MATCH_x, if it didn't match, they are empty string(REGEX MATCH "^.*/([a-zA-Z]+)([0-9]+)\\-([a-z]+)\\-(.+\\.mng)$" _dummy "${_current_ICON}") set(_type "${CMAKE_MATCH_1}") set(_size "${CMAKE_MATCH_2}") set(_group "${CMAKE_MATCH_3}") set(_name "${CMAKE_MATCH_4}") set(_theme_GROUP ${_ECM_ICON_THEME_${_type}}) if( _theme_GROUP) list(APPEND _themes "${_theme_GROUP}") _ECM_ADD_ICON_INSTALL_RULE(${CMAKE_CURRENT_BINARY_DIR}/install_icons.cmake ${_defaultpath}/${_theme_GROUP}/${_size}x${_size} ${_group} ${_current_ICON} ${_name} ${_l10n_SUBDIR}) endif() endforeach (_current_ICON) # and now the svg icons file(GLOB _icons *.svgz) foreach (_current_ICON ${_icons} ) # since CMake 2.6 regex matches are stored in special variables CMAKE_MATCH_x, if it didn't match, they are empty string(REGEX MATCH "^.*/([a-zA-Z]+)sc\\-([a-z]+)\\-(.+\\.svgz)$" _dummy "${_current_ICON}") set(_type "${CMAKE_MATCH_1}") set(_group "${CMAKE_MATCH_2}") set(_name "${CMAKE_MATCH_3}") set(_theme_GROUP ${_ECM_ICON_THEME_${_type}}) if( _theme_GROUP) list(APPEND _themes "${_theme_GROUP}") _ECM_ADD_ICON_INSTALL_RULE(${CMAKE_CURRENT_BINARY_DIR}/install_icons.cmake ${_defaultpath}/${_theme_GROUP}/scalable ${_group} ${_current_ICON} ${_name} ${_l10n_SUBDIR}) endif() endforeach (_current_ICON) if (_themes) list(REMOVE_DUPLICATES _themes) foreach(_theme ${_themes}) _ecm_update_iconcache("${_defaultpath}" "${_theme}") endforeach() else() message(AUTHOR_WARNING "No suitably-named icons found") endif() endmacro() # only used internally by _ecm_install_icons_v1 macro(_ecm_add_icon_install_rule _install_SCRIPT _install_PATH _group _orig_NAME _install_NAME _l10n_SUBDIR) # if the string doesn't match the pattern, the result is the full string, so all three have the same content if (NOT ${_group} STREQUAL ${_install_NAME} ) set(_icon_GROUP ${_ECM_ICON_GROUP_${_group}}) if(NOT _icon_GROUP) message(WARNING "Icon ${_install_NAME} uses invalid category ${_group}, setting to 'actions'") set(_icon_GROUP "actions") endif() # message(STATUS "icon: ${_current_ICON} size: ${_size} group: ${_group} name: ${_name} l10n: ${_l10n_SUBDIR}") install(FILES ${_orig_NAME} DESTINATION ${_install_PATH}/${_icon_GROUP}/${_l10n_SUBDIR}/ RENAME ${_install_NAME} ) endif (NOT ${_group} STREQUAL ${_install_NAME} ) endmacro() # Updates the mtime of the icon theme directory, so caches that # watch for changes to the directory will know to update. # If present, this also runs gtk-update-icon-cache (which despite the name is also used by Qt). function(_ecm_update_iconcache installdir theme) find_program(GTK_UPDATE_ICON_CACHE_EXECUTABLE NAMES gtk-update-icon-cache) # We don't always have touch command (e.g. on Windows), so instead # create and delete a temporary file in the theme dir. install(CODE " set(DESTDIR_VALUE \"\$ENV{DESTDIR}\") if (NOT DESTDIR_VALUE) execute_process(COMMAND \"${CMAKE_COMMAND}\" -E touch \"${CMAKE_INSTALL_PREFIX}/${installdir}/${theme}\") set(HAVE_GTK_UPDATE_ICON_CACHE_EXEC ${GTK_UPDATE_ICON_CACHE_EXECUTABLE}) if (HAVE_GTK_UPDATE_ICON_CACHE_EXEC) execute_process(COMMAND ${GTK_UPDATE_ICON_CACHE_EXECUTABLE} -q -t -i . WORKING_DIRECTORY \"${CMAKE_INSTALL_PREFIX}/${installdir}/${theme}\") endif () endif (NOT DESTDIR_VALUE) ") endfunction() function(ecm_install_icons) set(options) set(oneValueArgs DESTINATION LANG THEME) set(multiValueArgs ICONS) cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) if(NOT ARG_ICONS AND NOT ARG_DESTINATION) message(AUTHOR_WARNING "ecm_install_icons() with no ICONS argument is deprecated") _ecm_install_icons_v1(${ARGN}) return() endif() if(ARG_UNPARSED_ARGUMENTS) message(FATAL_ERROR "Unexpected arguments to ecm_install_icons: ${ARG_UNPARSED_ARGUMENTS}") endif() if(NOT ARG_DESTINATION) message(FATAL_ERROR "No DESTINATION argument given to ecm_install_icons") endif() if(NOT ARG_THEME) set(ARG_THEME "hicolor") endif() if(ARG_LANG) set(l10n_subdir "l10n/${ARG_LANG}/") endif() foreach(icon ${ARG_ICONS}) get_filename_component(filename "${icon}" NAME) string(REGEX MATCH "([0-9sc]+)\\-([a-z]+)\\-([^/]+)\\.([a-z]+)$" complete_match "${filename}") set(size "${CMAKE_MATCH_1}") set(group "${CMAKE_MATCH_2}") set(name "${CMAKE_MATCH_3}") set(ext "${CMAKE_MATCH_4}") if(NOT size OR NOT group OR NOT name OR NOT ext) message(WARNING "${icon} is not named correctly for ecm_install_icons - ignoring") elseif(NOT size STREQUAL "sc" AND NOT size GREATER 0) message(WARNING "${icon} size (${size}) is invalid - ignoring") else() if (NOT complete_match STREQUAL filename) # We can't stop accepting filenames with leading characters, # because that would break existing projects, so just warn # about them instead. message(AUTHOR_WARNING "\"${icon}\" has characters before the size; it should be renamed to \"${size}-${group}-${name}.${ext}\"") endif() if(NOT _ECM_ICON_GROUP_${group}) message(WARNING "${icon} group (${group}) is not recognized") endif() if(size STREQUAL "sc") if(NOT ext STREQUAL "svg" AND NOT ext STREQUAL "svgz") message(WARNING "Scalable icon ${icon} is not SVG or SVGZ") endif() set(size_dir "scalable") else() if(NOT ext STREQUAL "png" AND NOT ext STREQUAL "mng" AND NOT ext STREQUAL "svg" AND NOT ext STREQUAL "svgz") message(WARNING "Fixed-size icon ${icon} is not PNG/MNG/SVG/SVGZ") endif() set(size_dir "${size}x${size}") endif() install( FILES "${icon}" DESTINATION "${ARG_DESTINATION}/${ARG_THEME}/${size_dir}/${group}/${l10n_subdir}" RENAME "${name}.${ext}" ) endif() endforeach() _ecm_update_iconcache("${ARG_DESTINATION}" "${ARG_THEME}") endfunction() Quaternion-0.0.9.4c/cmake/MacOSXBundleInfo.plist.in000066400000000000000000000021231347141001300217440ustar00rootroot00000000000000 CFBundleDevelopmentRegion English CFBundleExecutable ${MACOSX_BUNDLE_EXECUTABLE_NAME} CFBundleIconFile ${MACOSX_BUNDLE_ICON_FILE} CFBundleIdentifier ${MACOSX_BUNDLE_GUI_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${MACOSX_BUNDLE_BUNDLE_NAME} CFBundlePackageType APPL CFBundleShortVersionString ${MACOSX_BUNDLE_SHORT_VERSION_STRING} CFBundleVersion ${MACOSX_BUNDLE_BUNDLE_VERSION} CSResourcesFileMapped NSHumanReadableCopyright ${MACOSX_BUNDLE_COPYRIGHT} NSPrincipalClass NSApplication NSHighResolutionCapable True Quaternion-0.0.9.4c/flatpak/000077500000000000000000000000001347141001300155665ustar00rootroot00000000000000Quaternion-0.0.9.4c/flatpak/build.sh000077500000000000000000000004151347141001300172240ustar00rootroot00000000000000#!/usr/bin/env bash flatpak-builder --ccache --force-clean --require-changes --repo=repo --subject="Nightly build of Quaternion, `date`" ${EXPORT_ARGS-} app com.github.quaternion.json flatpak --user remote-add --if-not-exists quaternion-nightly repo/ --no-gpg-verify Quaternion-0.0.9.4c/flatpak/com.github.quaternion.json000066400000000000000000000012231347141001300227020ustar00rootroot00000000000000 { "id": "com.github.quaternion", "rename-icon": "quaternion", "rename-desktop-file": "quaternion.desktop", "runtime": "org.kde.Platform", "runtime-version": "5.9", "sdk": "org.kde.Sdk", "command": "quaternion", "finish-args": [ "--share=ipc", "--share=network", "--socket=x11", "--socket=wayland", "--device=dri" ], "modules": [ { "name": "quaternion", "buildsystem": "cmake-ninja", "sources": [ { "type": "dir", "path": "../" } ] } ] } Quaternion-0.0.9.4c/flatpak/setup_runtime.sh000077500000000000000000000003371347141001300210330ustar00rootroot00000000000000#!/usr/bin/env bash flatpak --user remote-add flathub --if-not-exists --from https://flathub.org/repo/flathub.flatpakrepo flatpak --user install flathub org.kde.Platform//5.9 flatpak --user install flathub org.kde.Sdk//5.9Quaternion-0.0.9.4c/icons/000077500000000000000000000000001347141001300152575ustar00rootroot00000000000000Quaternion-0.0.9.4c/icons/breeze/000077500000000000000000000000001347141001300165335ustar00rootroot00000000000000Quaternion-0.0.9.4c/icons/breeze/COPYING.breeze000066400000000000000000000222301347141001300210400ustar00rootroot00000000000000The Breeze Icon Theme in this folder Copyright (C) 2014 Uri Herrera and others This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . Clarification: The GNU Lesser General Public License or LGPL is written for software libraries in the first place. We expressly want the LGPL to be valid for this artwork library too. KDE Breeze theme icons is a special kind of software library, it is an artwork library, it's elements can be used in a Graphical User Interface, or GUI. Source code, for this library means: - where they exist, SVG; - otherwise, if applicable, the multi-layered formats xcf or psd, or otherwise png. The LGPL in some sections obliges you to make the files carry notices. With images this is in some cases impossible or hardly useful. With this library a notice is placed at a prominent place in the directory containing the elements. You may follow this practice. The exception in section 5 of the GNU Lesser General Public License covers the use of elements of this art library in a GUI. https://vdesign.kde.org/ ----- GNU LESSER 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. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. Quaternion-0.0.9.4c/icons/breeze/README.breeze000066400000000000000000000002401347141001300206620ustar00rootroot00000000000000The icons in this folder where imported from the breeze icon set. Repository: anongit.kde.org:breeze-icons.git Commit: 00f3ea7a763dde4d676ece8186c1cdbe52f6c2fcQuaternion-0.0.9.4c/icons/breeze/irc-channel-joined.svg000066400000000000000000000074161347141001300227150ustar00rootroot00000000000000 image/svg+xml Quaternion-0.0.9.4c/icons/breeze/irc-channel-parted.svg000066400000000000000000000074431347141001300227240ustar00rootroot00000000000000 image/svg+xml Quaternion-0.0.9.4c/icons/busy_16x16.gif000066400000000000000000000526741347141001300176130ustar00rootroot00000000000000GIF89a  !!!"""###$$$%%%&&&'''((()))***+++,,,---...///000111222333444555666777888999:::;;;<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz{{{|||}}}~~~! NETSCAPE2.0! , HA$؎AᚶyӱXp:~eD3fy+׮@qU9IGpݸq #Eu-hl(=SGМ&RsΠ.d֬oǬ n'mx<0 ! ,  !!!"""###$$$%%%&&&'''((()))***+++,,,---...///000111222333444555666777888999:::;;;<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz{{{|||}}}~~~ HA$HNsђp\7X0\8~%̳_dMFmUܶqGP6m ԉ@@5:pM:+axv 2݂fZ*KM3Ҹ^½ɶ6~p=۶po} ! ,  !!!"""###$$$%%%&&&'''((()))***+++,,,---...///000111222333444555666777888999:::;;;<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz{{{|||}}}~~~ HA$8?q0\uQX7~ۺ#G bv;fm9ڶ9q`9ln{p`k׸u6YZQz H*O}׍(n;J4iƑEl 7BnL޼L0 ! ,  !!!"""###$$$%%%&&&'''((()))***+++,,,---...///000111222333444555666777888999:::;;;<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz{{{|||}}}~~~ HA$(o׽[:M,?lĉ+G0ar6$z߲lU4qq804h )(?@BdT[R J$X<㶭`9̙&B538X]p5~p˶p`n} ! ,  !!!"""###$$$%%%&&&'''((()))***+++,,,---...///000111222333444555666777888999:::;;;<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz{{{|||}}}~~~ HA$?nS0uuX"i‰wP۫b lWo9e LY6)&M:yAmzv7mcsl719JU%jƯ,ad (.! ,  !!!"""###$$$%%%&&&'''((()))***+++,,,---...///000111222333444555666777888999:::;;;<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz{{{|||}}}~~~ HA$n?m;ۭueX"?eԼy;F7N`wĵ+xN8#ke"M aÀ5hPсPy#mNxM D2,pĆ#Rƌ11Ό5*f ҅9Lx`@! ,  !!!"""###$$$%%%&&&'''((()))***+++,,,---...///000111222333444555666777888999:::;;;<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz{{{|||}}}~~~ HA$-?lSx۪uMH0Ca͸q:.7Nv޶+.W8cث5'[ϟ%Hkn|HڲmA 5~: FH a N2Sp3-Ć[~.M޿! ,  !!!"""###$$$%%%&&&'''((()))***+++,,,---...///000111222333444555666777888999:::;;;<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz{{{|||}}}~~~ HA$ ?kySxP'vH0[6~mۆ-bB'N :vڮ+NV8#-iۡC7[lg\Qu1qQ XU秒/_} '.]wBlwڴL0 ! ,  !!!"""###$$$%%%&&&'''((()))***+++,,,---...///000111222333444555666777888999:::;;;<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz{{{|||}}}~~~ HA$?hq0$vXCXe֌8#:uמ+Ń#-V,gщN7XZt`GkW+m-\;u;JƯ#]d/,fCA$nրoVEL0 ! ,  !!!"""###$$$%%%&&&'''((()))***+++,,,---...///000111222333444555666777888999:::;;;<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz{{{|||}}}~~~ HA$?eiۡuݽXКS]{F8tӆy+N7묽#MuN`ϟqVt 9D#W0&{iXo7v=-[5%9EƋ\eW`@! ,  !!!"""###$$$%%%&&&'''((()))***+++,,,---...///000111222333444555666777888999:::;;;<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz{{{|||}}}~~~ HA$maa ڞuݍXZ5~`Ylp2; @sV0]&p=sG)R ӦnF-8(r| ^_@l&ndƌ@d[oۂV49G; %b L`@! ,  !!!"""###$$$%%%&&&'''((()))***+++,,,---...///000111222333444555666777888999:::;;;<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz{{{|||}}}~~~ HA$^]u]Xp4~RULo"ۂ8 [`:I); Nw:?-zt`9<5.iei&KoKzȽDn4Zcs@~ ! ,  !!!"""###$$$%%%&&&'''((()))***+++,,,---...///000111222333444555666777888999:::;;;<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz{{{|||}}}~~~ HA$m?[Uu1X4~BIKm{\8 :EK|V:?-zt98`]c+OJR+gr 4XCn@x &! ,  !!!"""###$$$%%%&&&'''((()))***+++,,,---...///000111222333444555666777888999:::;;;<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz{{{|||}}}~~~ HA$x?XQPuXY3~49s&+?mb\8d`:AՎHt : AZQd#N8`)WOvx#(L?wرv9rᵫgܻg{ǯ.r|W! ,  !!!"""###$$$%%%&&&'''((()))***+++,,,---...///000111222333444555666777888999:::;;;<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz{{{|||}}}~~~ HA$hm?UA۶u͡XP2~{&y\6lyh@rNV0]nͽjGP"E bNOB.N lǎ+ѩK4~ W/\w-|,q tT]~! ,  !!!"""###$$$%%%&&&'''((()))***+++,,,---...///000111222333444555666777888999:::;;;<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz{{{|||}}}~~~ HA$XQ1˶ُtםX1~r3F?kZQ7}3 :9;Ŏ`6BdF: ZڰaGc:a SΝ;QW~ș37M?p n۶v-l]~..! ,  !!!"""###$$$%%%&&&'''((()))***+++,,,---...///000111222333444555666777888999:::;;;<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz{{{|||}}}~~~ HA$HmN!ö0ٌtqXp0~i j8Yz`:5Ď w֫9?-zt;tծ`;qUNK \fyfp4w׮[En!tm} ! ,  !!!"""###$$$%%%&&&'''((()))***+++,,,---...///000111222333444555666777888999:::;;;<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz{{{|||}}}~~~ HA$8K uAX0~`U?jš0[75 :1Ďvv9رZP-[GT`:Y @uT1FPIlO[΂鎹Cƒ̝LMBf ! ,  !!!"""###$$$%%%&&&'''((()))***+++,,,---...///000111222333444555666777888999:::;;;<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz{{{|||}}}~~~ HA$m?HP؆tXН;~X?ic7r =v{Net= :˜v?t с#NR]c4PT_3j6v$p ӑGr  L`@! ,  !!!"""###$$$%%%&&&'''((()))***+++,,,---...///000111222333444555666777888999:::;;;<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz{{{|||}}}~~~ HA image/svg+xml Quaternion-0.0.9.4c/icons/quaternion.icns000066400000000000000000023460411347141001300203340ustar00rootroot00000000000000icns !ic12ĉPNG  IHDR@@iqsRGBvIDATxZ p\ywᄡZeF&,0` 5 ii! iÔ!$) B`H]H!`/d-j{g$_TG{Tb`)b` y9U;zH}`t:K\  I8LoᡖА2󭫯zƧt,Nrk&T7?m)d"\6;<!H4,RBdO:3m+2N9Ooؖ<\f]Q<d֑|@#ҙ\OiDTUWC\$f'{#{~+>sJ8dֻuFjmaD$gj!0!R=DDUFdF]XTtp嚿ُ_l;TP\XzRYA =P"QX!1!јʼưLؕU;u4Éycw%m; XJ HOXE P('I0<.׎h8 mXs޹޼r7L?{R IPFN׸&PϙHm$(;QLa=FxO4kr^}Arue pB@Ͽn~}&-yGޘ>xb\<"rN%r^%qH@BF{7lk 4qH("]w~$L=O2Ha,p`vE3 G#]6YjGے7A}Ψez\a۲KWb PɇƝ{i?oAK{F[`^+0X~3 d#t #rt[|~,l ʺ t@Qll9206d݉kRM*߲e@+GV̱%so,ï[ɡa[6)a3Ћ i b>X8(w֐|窨LtPw{^M?;XɅI5m563G`^y+OѯpIAoiWrJ1"0cQK,8 1R稬;)wpâ\1 v!Zcu K3#nixA||Œ A5'Hq06,'6%xjSc.]&(uXWfTڞw` WJG5GpA>ծ/K͛`}19ayF_="za|+0*+xr|f%(BY38|)YΩXؠ*C2Bh˅:@'ji9ŐA@#^3*Yw !_J"\Vϕ!zOfj9V("k O5eLk}.|{H ꥹ* 6m!yU;<ڛ>fYUuTӞ>/HZiu5,77X3b.beFj$D?/8 %8R(K@Uq֬lF* M8_n}[Ufa ޹mr$BPFuP@ j5"rNSi֑gHVY>8,f13FKehuyFK("!?IPxӄ9ڭ,[UP }Y17$[`43;GX[mo@`+LS#/r+ V-n}KTk^1%3n!yNs:Y҂\z|@&&gKH N2ƴ_F tAU؆}DEd(_=ޕ/퀥W-(;<K۽C^0d<6tN$Yq:4"iՆ. 1L_<0 @<';K8p elBYVZYS:.! 0 __փ ;HhTWޘRaDU>|KNl6JPy)m5A/WwFe"t p#sP;&Տ²!W6t/NGٴx 撄p

0^C` fQv@H@H9-Ϳ>sgӥGh]Dgی/LjUJ8j(o&'4}οzoOozl j 0Nw9Pi !Y~f\1k; ҟ@`+ep!\A|*Jg-}!+뢙Q֏lxm@MsmY95!Y<3"V B!zq6rkՓJ0&*$j;ܼ/!0²9"+%$ hֱ;>{ aKbF#@e̗@ۑUgW:Kk `D۟Wp eUp ´ Tv٠SW2X&pyi#d  ecry p%Q r<k\vF.(W̉ɯ8eQf4z´g:S_!gCxe5 cFr e5( r{p#gH K?~hJېǫblEڅ YmFO ;Ls8R/І &_|iIBXV_ԷQ-\hrz|HXG*,?{ׯAA!c`|`,f' pxy,\@H8H! @k<|rm_=ipQ;&$1{Z֜[w,#V+U @3#nqPWa$qDʥT6.h\+<#XH^0.'S?oo`aB`B84Nj #8lc }0_al'Abowڑ -T|y-~X\U\/'#+1~d=!/xyz9^̸pxw?8-T)b`)o0hNG!EIENDB`ic07:RPNG  IHDR>asRGB:IDATx}i]Gf޷WJR6$V `ct`dӍal3gOOL̟?zF w ;0,,oŒ\%{|{+hI$ˎUf̓'39yrϫT)P@u )P@u )P@u )P@u )P@u )P@u )P@u )TU^z"yd*d[M?@ -/}O K/fgL= mMީxcW^U+/P?<'Xqޔ*{*JE9M@!|lm陇PwOܾ+V>UnyޛS闕[)Sr?o쌓xo== JR۷ps;^ziMd-ts}N5Q[`}2|\hUnģɄ!n̫@(*/zE/r7Uvc=٩m|3%ֻɰ~l`%{ǒkҝx,vmd˾*J*JW^j?·CjAUNL*S*"~48/͸E?zݕi}֮xW@K[kӞn'/ /*WPgysᕊ'5?zcr`|b`؁6@ W*G.T7!@JSn2쉑;2Pds&3u xE;NP˧K`8YD CZZ0\E7Ħ_0ġ+tL5Nb*pTw^czεkY#\O5Ƴ_;Y[N-*Z}|L;je^&6F;xDmOa5!fb* $w(+ ݷUƧ_}kN~juM\RCnfy5GmgIg|+`(|26O4_T јrT[ZBFH'+ IU]&Z1הۿTpn\I涻Dk|A"6@@BF]Ƌ2i7I0hAPڠ!hHVTsa~씺`T~{7f(e:P{j|Wsm jq nO/84BpI+d~4}Jf0/Eɛ}ԣwgf>4dygIRholiiͶ=P7fY_͠sWa1?HѺKfsE/@cX'F ry_^a.;f!44@{CLį1eowv_4A!x`߾}T7G?PhXC$odm!j)BhCϡĪ#CCxø/kX lpTKF)N /_h* WŐl7rvϤ:cCP/BtS9Kq{FA/@ P^So h`Uu@ d'ӂqK@ g<ì {ZLi`Uo c0tGlCsK8k3Wtm|YSiñ956෱-V5ťfuC%8Z+pp&𘫎tB8˜W&Τg%K&\@# Kc&J]Qk(Uz-2elA͟/$>k1עVݺ2nXPˡbsc$vن̚EgtyW~fYM]B~FGjN5uC1wkPXh5 $dfkÍ?LVθZV CƦE2Zm:5_#&p^O#&&+k@ƏaEhiB]qٿo;R ϚKyZOq\=vO-mUcmpk`uPDZb4*F:f𰨃ȍ ՑCH@"tQ`A 8~RՂ{v()3ZeT{ TPap5ݾ˙ѩڿOfRi$Ma|PL#zTYÿ c`,rs;\K!/˪ j?=W}EtR'RT(N2Vd?7|kD`3V.7۪-̕Y+jSw~#߼I;Ǚ*b1#.U?ܱ*ېTAݧDLWK1~zܧPOH4qYP3fN? m^i.jks.U|t T>zHIيzLS6LceDċ"Q GC[*86v s*\HA|xy;|uc{F!1WKh45XM2~~JdEe0mMxu}R{ "\iAz9۶yhkJɺ= %[J_OMYWW;:Um}9OOX8rFEkStY;APv&}_/)BI˜9n_ N~͒1\Љ^;f֞ _5t.lla,hE=w򔱙[@M8V]25Iu1Kqį3jDPBٱ t0? Aa qX&48S7LgF&_Ta/ySh>ɨXYĐm5ГulI2{@sJ6WDMN6&Wo.FrM,bXi՟mKg'@n /̵L׸S79|x/̔:jSg#jrloA(0|ZJ$Q&~#9sGckZc2yEk'JRn9%5/fU2Ǽ} miRmYa>rrQ3̷F&2n%  b-p|#M;-<8gp q݁<!1p|49}j&歉y͒#sݖ|j]/դ1fMK(4!FZJ}zsB݈{:20zIz@&ox|keƀtޘkCC GX[syU9l!76dpiuW}0v/Eqmi c? 䲩TrEono.m_`I5kߋ[g.(d |G{ꎮD4捿:0T8d|d́1@8WdH?5b N>o!z`=&55r Ў$ik4q9wqzTnk%ؿvI``fu0ٽiiHqՌ44!5lH(MXȹgu+5<#}quh`ތ2Leڏ0LGM6#pd'C7'k_DcÊPkX1`_V?|cJz%_ր phE $R:P̥+7QziK&7$r{tJ. ̛Yf20j'6KX31&9?a-@Nߐa XC<&…ihV 4ӑC2zxZToS??>-NsF _9sTAjaЧA8zlܞK&ӷꟋ/n&KN.ǦlA!ƣPтst纤e? ~ujZ{<8&^@&n /6OY&p-@K֯]R[B5ަ:xY}ws`6]~L%idh u~Y +f'6M9, <֩d3VHiCK 1]ŝc?6^9<͡ED00 *i ӥі_X..1M\7lgzh3$<^9披L?Aq&̘\B}zG%h\u~u-Z *4רq_Y`f^'q=i*1tS(K3H|=sdJOExăuML2@FK-FF42LmBP59?Hhpi>dY@7r^uE q2t,ulgQw'-<$;!#Z , 7f{0˻#S=z SCZרN)RP}ΜLC}qu]/3$0ϷM'&%+`R0,uN`q̛p;1ՏsxȚ4zOX'_@\i`Db*?/>%.euFz0ͅ0Wry%/SGڵZ_ |h2IOa"x(0Щԍٙ45 JEFCHeք :öGݔK 3<?h|1ls'a/| fA aHBLd1lT7t USB "aꙂ:2)\^oH dq3YNf΄Ӷf/+f#$XH9T>Oͪq,ǒ`tZڶ̨v,`(AѵX a#8H覃 $#Sl>xD}B};'Q<2fkMwSBݻAA$/'0 gN ;XR[,%K24[˸hT?.5pp^ì;0[P?|m\jSG^T8 cmH!)i $r 'CԖs@ιÃ%;]O6}?W#1LGS;PiJ?|F25 mÞwf?11O omq/pdxuލ8KFw-Dp k[Q3X{P^Ԯ̅'iLh1yMcKý3y5T?6rx{2ÈbЇB@|38R7uZYzM-;R2 ürk0trDnV\1 km  a/|rN0ΟnT#BSCGg ䷭ɪ_G}v }Q<*hNnxŅ4Jz'o6|Sp_Pao*Q]2KiWCKQa\3 fվx[gy̷AHt7K 8Gwk,b' -XDLO0 U½k5BfUNf3!$̷r1/b]=R\㉢F wRtʫN:}x| ǵx Kx!V+X_NL@@ղ)62~6Fn 绍g>:iяug 0Կ&L(dz*Em F80tmd^A9+l9FHa.t; $Ƕ:>hTNu3xd4ңseqe=Yald'a}ZH:n6\o HD 7ZJxgGѮV:_!εCC%}(Ecg1;xbQ3_+ (!; PEW2rAqYV?*JF! a#L]:AlPM#|oa2e {@0(\6"v3,Sj!H8`M5}BcƢċ 0a:C1&Mn.ABc')ĉa[=dp3C/"? "4ppiXeR!vƇ~TIVSsPmMȉ-Hya@0[!LtJ"|ȼ" I:Db&@ "_OjK9 "i/7no4sA3` x0Lrg,痭*G D`f8O\*yM@UPNv 9n>hBxD`N? $7 #h #^0q% 3 ~XبNG?鏎B_vPL%J 1p#ђX{Kh0 Ǡ 4_d -Io`Q'H|op8]!03}X0jejS ]?Wp[lg HXApY 2փ&5aH C x+@aK0BS# 04Ba~4_ٵX$U6!5RƵm!(Kt(ȏ<(uR"Ai `lS}J|4v3XPæcӐpoO& F7}-L_K k6- Ð\'i ~ꆮz8n9hN # ؑp qyϚm#ؼhCgM> Ac uyOugvT-Q8$aa'Hr=3v&}`2l$@Z l:q߰+hEe~ٔC 5l]@n_pȴ-ӵ$/(̥k&9щU}La'kKag])ӁINPa@x!XE&L&rT2QגgX 8ķG%L@!.OƓ3j}kx{]=sl^`I*Ү,j:lk* 0y_cgD Gz==, –h %D M!AC [`@H;4j0_C&>QN]"E,̤?#'F㣮P"R]G0i <52K"lc)lWbLife(6OƄ%SNc6OCNG!U ba?>AX7~MA%g*:vY~R@D/ý$to5g+X%La ޣ쩒:@TS7.<@BMӳʶg8E90CM'a3$md7t(s @֙RFQpY͂pJlz1LW~$%&47EgF_KGF=+4Ref0NČVDS xETY\'xXC]-4S mx CA@l0ik0?kN[Rא1 ݘ74s`+?>ϠEtLFU.d \q|Pc*OV )$Aa'8NOW֒7U C!L OLң`߼l؄vӰv;p=^qxY8 <.`@!s Y }76f~WP??k/qU~”.ߵd,v5No(8 MNB*k6JS x'lM'N?|KfAmDNe 3MX. 9& &ejS_Uݼ:ȸF!*,h/`|{6գ0~96APtZ7 FŵsΊ%Y9s1$9ETeϾ{T ĢlٗhUAJ $- e%.4hFMBb5s8/89\LJo!ʹ$.3M`a 1z֦W>ЂOԫϘbJEk{]L"]v ʗ[y=3Y4d0D20ڌ7ς8n|Je'h3X:} %`f* bxX%$C&hqj.X:M$o)58cEJQOV9M`$gtlϸ:  ;s_l! B3%9|w qZb?HZ12xL5o/`;w:D)XG=N`+O_Ncoa}o_-h?۠YkKw"O5?-OqzA8Ӿ}`F=}OC85{{wZ}B#lXNCc<;;E݆(4A-`:!E1Lא,0Cwnog{ՙ)QkuG"bӹiߝ*3l~oSe\`t B CH>f ew@ :^7RINNFݵ:3TJצ Ԍ'([p? aOPN{N:O,e|Ucɛ'O??%>6Qp;x=:qM0 oiB!델:.?ǤFTXwR;2 O6 0_ܜB ;ie\f 4Lk!\JIC Cn ˑxlCE K\ yWRyy[GJQk"iƄw4_Ɉj6=h.Cu˪,)`,nOoC0#4p'J2ź ߌ6 iҘ+؈?>ZAGUӈV~lz , hp:QBl|ځ+pZ]wt uAƧWcȶU ~`v aJ*<^!pp(WN imH kzL^CS$/aO OB7QvB3pXxFbĩhkYٟB _x.Vv>ͣ+ JIsΞ=zښ֏q'8Ǽ9']Ji( ͘. m=~0= xx3Wb񡅀=n}O kvt߷&Z¶4 ;^ThuxS$ӐɈ&2ËuQm(X12h688ȯB{؄/aJ"%HZk&W+o߾ֳ7wc n92y!mql%*OCLe~ PHM'Ć?hU".P;"Mx_}.~vx $ނHpg>ڋ{Z^cfڜi84ͤ J@S ]x]YWxsz5Ŵ9 .IOa+/NQ|oa mC3Zd.6β6D.m v4 <=pOF8>Fр5Π݌fh8\}GLܪQ % W)$+Wݻ'z߾G^}br$V½u58ëbtmaNxЖq Q!4],soz nǨ!tZPx%=g(bKi=J^'{y Hؼ"vNS`bdo BN(h~;eDdrJ<(gj}F.}^Kwh& oy𘊕b9崬Q~ˆ@QN"Db-40U;iQoDL/VًgJڀC)Sz6Cd5a##eL1c8Cs> í1_nT$I婢|oI*0.h ]M]55y)^ i cw -8g qS@͌-9V^lTa6ѫH> q3XӣڅOp瓘1ySg ӤYƗzDQ~M {tR5!hkH=)gokK64wT0o̡[B0qQIm3nRN~ (D@pl\ f1?@-a|$uIE툠֙xTg6 Nvb"6ڥׄfsvM:t G-KyvLUCǕղM$f fsL) U5IEq/HE3ݨy0EZd;]L ]ym'>?PkҞ۳^o>]?S sNvftZ*%`.2Mv5BhNDƇ|6$/&g {{- ݍ iΩ9Y]t2,?fhLI3!QPe|k*si4QfsY('8CH*g8/;G>5(Q]T&adxD)38er<-dU,-h<%Da CfAH)WbW Jt2_XaVD!1T4LSq|s81,RQ`kZ,,£Z#B>@K[._qYuˬYUQe:~4qs(xRM# 0iQؠ/rh Eej :O`c|$U^ۻJl٬ /*trT=8QRչŏgv >$zxgze_6A,oIޖTr2h ~W -5_Ga|HChCӖ;0xMPuܸ-31osödcbnX`J4fT~i|` /kk='?85zFSN:SN:SN:SN:SN:SN:SN: bIENDB`ic13PNG  IHDR\rfsRGB@IDATxYuw[{W3 v;,b(pa?o֣^8-q [[/rXb! H;AY0[]]{N޿[U==UsnfPl?X?`6ۅq=9zk9PGaMQҜ;]U٬a_Y8jgP92=nz;~4VG%=nfgf0A GT$1r0C-C&4ðˌ)!ϣ׫"ȭVnT<;lϮݩn+ChUs\}K *5J֯gYs?S\ֿ4[@6n&:%4I"#s02N? =3ġQ2ClY]#m,r#.MX S5 ;]tWk͕tsˋ;n,v2Ws(`}(ʩnLmT3xPFwj+['*0>$ju:7Qgf#Ԕ 8QRAX75o &\B7F"6PHj7.oC ],YvFQlv71ule8Ⱦ2fpStjo'zK7.. 6~Rkjuu*m׭-@iUmwsS |t3~pbH57b Be S ^Ύ^G''#!O&`X@ƩP `^d2f%ǹk-l[tXwvNr!Aw To=޾Z֩k>3ʲ֫õ'dprͯsiUS#SMNaE58(̀tv,XʪfX;Seufk "~}i$_FjܙKP1odh~nT{SslM7fHvH~W|.2<!@+n%P+T"b;gٿ:Sk軝G~M[*)x ņWՕa|pmskg> oj̣! q0;2;uh8:zt}j:84/peSwf~/U 58|&4|\+"„aEl.M0[n,]ݨ>vumJ;nJLyj򕯜i< 7\Vo|՚ZnOFiUpېc{vD=[-4<# NS$/tڃ/Ā $Y?j3ֱ^OUf >4fvI|MqL߅߁{t} dR&`N(p#a jxVH G &OyV,-sTYښ{~珫תF0SǨ9l _FƧ k6;ؚkٸ^orTaҞ[D0.)8K~ fh309f8DhFuC6fyڡ2Id(=.:f%+O'L*zVcRxfcXvzJ?Xc |Ca4{o>9@/3#TI*CǸÃ&:Nq-]i}=LS&v0 HpƆxIC;GB;w/5Q !ajmmmN`5/ΩJ>=9k9ׇ䡪M<3F7`g?wyx&Og&!Z<̻ OPj .[ N.aRnNsj5ߖ3ΌaE@]k%.zOv΅ʠj}Yc{\-=O! 1PCњ|5!*7X[dbFyN;_5W}W2|W31/U+ׇ6gb+'9g߂C:0ǶGC[ ^0#0 yH7x!X?P7ID|9.HW>]!LKϧfv[kCy;.;aXnvw_oCw&d;sN}Ro'\#e-Xs>A 06D3 9/rNacȣ/f>y_f GdzbQ]ͪs~tỒP*/jpKPCΏp7C[_0;v#>BhG/E!У]J_ׇ!OYھ 8IڦVr6q\ԣjX}+JM]0U_ʠ94V?곟5Nu;p*bDU*$;-#CE$eOn)lN}skvfs0^Pvt~2}ɸGm,38Y!s3`6^0{ƣE[ Z0";v#57~$MmlCyH3*n )d l̃ioh66#˂BOAڗkk1wek;C&p2;Wƾ?+/BH4F[J7'˅r s6V G]64GG7rootCIQ3ܧN}́mSl:?IS&'}—MdeCOփeE? )|csuH{,w,#fQ(*X*u[sUm\݁[Pv%ŷ3s#M? x' qDp)N-,UsjחN?j3z:?hfهFASdžܴSIbp!=+V<fUwGa_WSeט6d֩P[`vP,- hUnݕzO6~!<D\8&"q J͎/?hzz?ÿsxJ ;Zj3T>kc~Rtbݸ[` py.Ae۳s,߀n<+ '&xaVoq!eA}5 V`v%\y#Xif 5`oB C%e^R m!_:yvds7?sy}"ZPc ubg  u2nıc3c+?a`lͲY@hn_cvktYh R4\ $&>|Y9t~-.Qmb*&]#̜rMn=0 yEܼ IM257z^pon})a¬vw-jd| '8W@{*yX/™)!A͛9XǹPL 08ro7oWQ{*?nd[F5\cv5`XTx -|ٶ,-8?Pq04Tw7 ܾLU.z? t] zvǥ= .? ̀ 0&Gx JԁIH0nP&j?&a`0iˤn& '鰅?At|[j0fP{yk1Ls4\|j=]LwZCWNIx1T;;O6JW Ev-+prNj syg 6#,bR]T^r63w n2|ϧotJJ7[Ї`Țϖ]M$t _h"J>鰜Xvd 5LĬCx>;jYw6P0JN}ΨcZSc~6"vnF35cX{4n9X;3(Ux~&:;^eu{aF;S`82;(W\6u;Cm簀/݄|fhFh3h؉Q8nϗKoG39,[VG wHЋZ ??| {pCv+V\ݙ姸:pQs9クcc}7VݧCxg׭b˷n ZA]4Tv.zz~wBcp:7?Mlzzos9Qo I~6UhO5&!E8£+֤|Aq=Ԗ8⽃V {Fj _l/6w9h?4Cw.vOWq?64C-EDjm5iX^O/ytbbTomn̴ HzS7=ipz0\ d8S'<=baΆ8a`I O;m-~691O8_+ZD7˲/Asuq}w6L鯳i O[ h_1 &(/Pc+'l6hϐ}w[=wdgF򕮨Fg+ln:~Jjf cC9\^\Ŝ6j;X^JatQ<M%f3K ʨߚ~ ž h纆Ս!avz1Ffj?@l$pM(,\Hq[OE9,qp?ñ:g6:|}:DƥQ3H_;]Q)`+c+/>H 6*C>l,>T4iWv??j܁iWx}hHq-Vp*3w v?snaC /`;y *LV+mh$ss gUp?V;?:wpWkx &` clEw_eۅއqF׃ jakyCoܡq>ơD3z t'UHR uoc#+Xz ^ØKlhʏ"eËOnMŸֿ/➘@-th=X.Xf ˚8Uv={_&Uqw~F'Քdx,L f;lb4| +k3+X}V{OxY|K4* ?X#)@4>;2` 70/FPfn?ò}M:6r [e1F7)+-2 Wzl?$х_a7>K ¨N ~ѕ=Q+>{MqNM#8[([4KF38?ޅ&0r_o:pT6EF\D13w\qawt<7}vTkm[[OcO(iO[Ǡ~RRsk-,\K<&{{ {PUq&KpcL< e$;?JX>]'ͥ(_C"5.w;'|^!3 !-QǩuLDA=}փpV`Y'ݹg;ߘ9syg7{8z\Bc&VS+Sq#ͦ5PsObuMP{aݽj6<#2$…F93AOs9sh5Cl&^E˳Rb[&[gG |/o xB_YF !cSޙvV4R>0HReH74?8NbO됆pxnT:ŭY;Cyb̲A[&u f l{|o.N Wjzc,[k4y^zX:[n{DؕGT*$`s`$ uMr!娲IZuXw0up4X~.ae^hhǮg}Z$<;4 7 hNA=["{-i>GOj_܋ϯ;UX1(5̏<&Hd<Ewp W ;3Q>ZX3gGm8==kդ?UiXɓؓ 9'@vr.9`qH}O]7^_W߄σ,ii:eIUxj,qS7{&/XL J|BtKXf)g<]KǍ/?IT8C_1dɇTU1 2AӺ[ߠY\X޸CW_4p~c~D|f!ƙK[9K(u9Wmı 4w ^T OaD!J$nTF^$EZ[8=^řkt<(n Di㚁#ՉogP`G0{MHPZy|"1}Oc#; q F ]c*<ܜO]{}}۽Vqw 0_c}~W1~#ppK7Wz,١z# _^}Xk?>3%α?k\+ª+1Kx ep:]I8'ס>wR #Ӱ9)]C1Gj:sǚ^?1#xp-' hJ,J]֏862N O<61ϫi46Pfx/nm=W1g*.}L%h*oiҊ60C# ۠}%Q܀1ćGT@̇p:|¢ 0-pna iq-1OK>릝}{b>9cd?x{:]lm!Nu&S(d&Ih}L*Z[qp{%-sq~=%]4kt.>j,ؓXE|3Y<~԰/a_oo*Ȧb: \ep6q+7HaGD(.u  ҽhXΜ_= a~L,O$6 cr=u.; {PQX\ *'Nen 7\]TחR3'w&D?ut862Ns;V?~TlY)5^9t{y?.3"<C;JЌ%YelÜnc7!+l|J^pQUAţX Ӫf S3 L񖤗uҽ{i \3mxJqiQ0CWq_l[;Tg53n̏N"Dpȟ'qI3 + tn!6C9 U5!m:BU!Ex/"FND7igdWyWÖ7,/IḚR[x5 .d$prݮM*#Rim}; ~:kuΠ{eN|D!ǒc wPw t~.rMXeXImgln{؜}ꥯ}kxe=Xjzhn+?ԑJ67!D`,s"+^6P4w)M ѧ'XxFc( 48Q"VpH[x?S+\Wph R|Q6qL~lZ.ocoBL4vWj}|Xʋ,Og'w82werN@FUC9Tj8:u9Ukn4\@#9_sgn/ީ>6m>F“ƦǫOb^e[Ly??xb $L=ػH.; L6Rw[&8"o Õ,m"-ġ7q3$ɇΌz3kWqY f?Eq>=7]ae;=ys{X@wa$l.q_EV_?7-\+|̱b?8ɵQ"^9b h+S?0 ?@FtÏZ0g#&,•^ (wO/ҳ`0q! ㏰O%% Uah8vZǩ[Xۢiy\Blo(/)y~C);<.]1$_[x fK&`(b3\ᾁaS;8uwd{1֠wUyj lΰ9.oywG:VN=(PxjZFGUgQr- ?‰0@ӸJ8 8][ \G@6}H<\䏺xhi~\ > ^ 0$iO,nc%I3OnHO/z,.ZC]Gj?W N*=<3P#hOcW}earYpW]'i0x7X7v?rv+q]P|5X0r;s7*e8@bAk4X0٬Kq?>ٽ܈rnn\gǗnO?+|<Cn)l2bqc 8(lFO̳rSN.&xuiѣO1ewp>u\:w\xs^qx>1w?cwr ְl8܁5QH?_mݖ!9?̲g憫?'v1.k4rpB<0IO x'1zKgXqWp{R'_Y"Đ#AAqĤbCbB -@@= D(tAFBRO٩!uq"4)9)x KJ;͚R :@0Uv5t7W3'XYmb!w$/Ru;6 ț zaNfB#y@b"[TqTq,ޯ6ԛfcx!_J8eE'lǀ<ix*lH@!ЧXJ9 HW#}^ U_]UՠF1+>'~aXbdRdiϳ/; `=$ž')EtgHbY 1\v}ހ-8ڜ@-+S1.T6Kp`6[PP9 ] 1i`(A8,=s'8ư~n'k{dP>Ơ/N2(%C|C5<_a >%J0LKQ<ºF8D今eFwGFΐsGp1'Cf1.Lot0iEz!'ie*4m.b%T M8Zh@Uvn@{WX+;dC#DDL7?8sxg" $UJ~],&M1wC[@I>( /A{~m@/Fvc PPfp\Қ&".A6&pIUVxl: `T vna`+PBeS ׺<[~.b5!+8ˋG*ĩZP דqQnExKs+r~x:|'Ϣ4RH<S=>gy$p/q?=3V2Ȱ( 1 x*1<HBwkɿ{Z+qj\ˍe LC_FWU}zvkpBQF66\ng@IDATf'd.Bk5M 6<(p7`en<>8},n܇ο++(l.cCaǝnكjJ|bJJz ^hq$oJύ&ˡ/bg;2 ?E#>F@5kۻu!ZȢ>L a"<1'H)˜c?Vf#;cnbEUY܁_?o(/Ύ GP" <+Dž:w@w/F0ɣ1rg3Fv3$hz(}SX[[~/0vP ̰v1d3 tOFQ3GN!A2?p9w>5ag)lpnAPl\}6DЖT9Go#ڎw&)R:d"$ YN aK}:n"jw^}s#ӗWŨLb>ì6h4885V?d'1xߙazvX /l*=j 7q VdC.qmk&QG+톍XNs'lnᲞ8? :կP&ly\'uI [k7r0Anc>$+ .|/p~ۀ:g!) ߕ;&<8)Nα}y>`9a]" ƪ ?'y #&46vk8G/NŧAP7> Wk54#4GZonبa?ԼPMMg8791q$|sh>jYDjIJ"(~M{[8N~qI0J=BFqn”x ӟP$7.t HZLЬN~?ΏxF@ֱƮp\2x?0sz>EIC ȱ>q㋾e׉`S uh4mrr0f;3ж '~oպه0j'xG}ٿٓ/ #ϱc ea%hFͱ V&9M6L[/¡"H[بҡJ` ғF726f^c:DaoS*/c"7Qڛ V6I'OOPi<Jlۃ<ZT޸*so \D':72Q.Pp0O< TlƤ84Z=~u.lc HֽU2~i]I~Fe~}В( =aOp2llT#y>U=y - %o͎G: sb$kjISdE(2[7u+,mH*gYnI%u+06Kw §w0,!a'{GmהQb2&T:`5ʍi6wj]h1pf=ZRp`Ahº2n&Ձw෯Yo7@p!i`Q5%1XL !|\ v4>Хq0|Y.ߎ C⧆Ma˘ CQd_6AIA@&*]i#W`neFy ('?o#>FBXDTi&a;ŕ; 7 Z<*+42T mcp sK髸tu'|sUw˚MuG2dDM0Lo+\u9R-]l`2É HbaѤ0+-.Aip-O bu%u0\KTɆon~b. <-d<] Ḯad j`n߀CȖD$y=unC!a<Ӎp`, m$tD>4 # ]1ͅpҕo9j_rc1ONw#I+iHA]ɠ T5(nZ@ b9OGe.;]f+>m6*6vL"^kӜ%.; Jxxy`+d4ⱍ1 ^{xi %?cx|OAy؀ 1ЖIlҗ2 JdwbLЃ:2W:V6\V;:L73kF@c6W=RUEdkQg)`qJ~g%fbouT&hvMtxv?'8`Ӥ0`>s._I 1;6?l&fj00`Gnp'$$?}m$:8CC,̴cWz:a|8ضङSgv w"K`G">r 07Y2< (2n~o p@K)OFZ:ȳOǑ2m,dCY+Nx+qI8^a^x l6 N pK ;dhuLE# !Vk0HH\C3 t}4d[(jx v.B?uaթVx@a&#k$)3?c`ఓ8av(q J3*J LcpES`&}!DX x;*F H%uHf v3%u3pR#lniBWHG~i]0vWjqV4<5@T !Cot7?p| &G.'*JE &7m &  v($4p[҆F1)!+NO $?:Vw;$r@c:Us];25ңv^/;=I7.0qw*\:"w&ʃXAM)EJIPLL=#_£w\縹kFKaн0vpXXP!t.m8l`ٛÑ6Gxk֠VvKu2?nQO@xpM[!(*i.1.H4B(J^&qZ ?1 5k1\rF$YSW~@yv -C H;?`/s}bD!S[8%A_oc%~$8:,Q6$C> c[,P6/8%d{ 0U坄)/!0>)+OKųܸ(h'r'L@ RO::nO4r}v:qd'1,DM )e'eIQ)0![Mz s"bcy ldh+ G<p0&b_Npٓ‹I~-&W=KDD%Rw~a5ȓC KSwXU -xjߒ2bG#wnlj@ndX-= w2$SFZ\BwOTYH`m*pg LޗǏ@ I:`l\h3ͽʷtCA\O-Fp z"QbBv puNJxv@ԁgPr-<D^e^Q O$38XbyFI?褖iu,aȯu'<kJTфNlg{ҰSRۧq|.TiBvkc8.^l ŵΊCTg)R}O8z1 68]%J&.JFU* ?K ]qNňx;^L/U+1<?&hi <]%S 4Wk [E шIs$ #,v1@NC R0HR(@3 Sy8,6`@Lcሂ8i|F4 AX"Z'":_LOUXE0jmțO2o['%AŴVӰ=)'7qd1)|>8cȣ"b{ Y)GУ&; B&Q{/'Q^ /k%LAȼ&pc:r ؘ@E@+2HoX߇{"~51e0=*v@|T.ѐ@ ?!IhGrz@NxLPm4p?gַ|>y<Us1 ,nHZ2+( W  9[PP:1Mg :ИN &ɩ1>XLˌħEړ~1?&| >)$^r<% U `O ^lFMr 4D;yo*RM^@$";ퟠz #3Fpҍ OŽdxtGV*R6ǘǚ*+{,70N$b8t&M5iȒXi'Rw>QD> dv;헾dq] /'gnbbj$ +kn\}w>3T^ 0D{h*X|?;]Exyv?By orX"@'Dg6Z45c1c~Bύ~9<|:I>| 1X  7ɏtk,}T@?|1+QL'&]q(~!2⑆iTpHC*.NBXncI~yL.b{_xyFEn8>EpePyjyaEv*^JN5o .'Q&.[a Y>e']O j@X>FV:Fe$RC(W6q)n#; +sxƷCd'̂ ~qSYhJp9vk#Wm % F{V `C .m Z{kᝀoKL'U<ˢ&1F*@St#Yeb$t*X~Qv BƋow& 3@ ,I?\pcG)b@eHMlE]K~OA+:9} wGk?Gtc =SI'*S~HϨǓ3/B7}4h Apesh/Ip9!U2({)ѷVY2bc6 ̥+aRrkڸbL~htRMG:պ'a\hˤne32w /o“]\v1<^뾗u{ |! Ll)P5C]52,-7*%qpL,\z[w-@?2[5{a/Q}9y85ᦣʀO?@AF<؛'( a<< =G*/PBGx ?v}&.@wy SF!!m3O4܅広sڝ 5(e !1IT+,A>i\WvۯV#]tS؄K/e|>̖Rb+d Pv>T2Rԭ` +?(-_S&$wY"LqemxO#; ?e1r hFnBPsEjWE,En8pC,[m9Ko@XH@A!Y$C5"蒂pw?4aF_O`t.>QS ?o3~3Or5HQطUw/_c߽u/p 0M){K!|"`I1́,5Fnu̞_ni8lVn8&;Wfu\A=۔vBJ} `6 mh$h9w5O`o%Eŕ]!bx'Y31D$D'^- 1G *^nI AfFtA{+"Ye/ޕ[|CB\ݏ{++" eW8ԭbSoosE^*JjXg7p>E~ ǂd^k7?VmwlvxVFǘ/ ۀaaIKν>r:tkX汉l|t}\aI;5 A @F0%vtx` tY-6MpB`P9v /%\=5&DF}n|1O!}0KmoM(;,J3£ ҭC/$1ci?ڲcg Š9wj4l.ڧM8;;C/~B݀݃#[l ,߀|٤Je !g'`'8x3ݺx;x1eDjh!XU(2fvD2{Z}cy{u.Npp.^%\xlŝZ ~\ ``;{7cTЏ¬ΰ2u!} XFFSw6ʾAajkK%bӆ [!X0J^`ǜ FybbPyBxB% jFs=Ar)O :P—/ F24ѥ;IL 7_uu!{?L o?F aFD$ij w5:oZLF2KIC;Y|4y^.a`-(c@'c0cK@Ȍ|}09d#00OHщ# t8x1hlNIF0= Oي.ɯVnCSТ B*Fa`CbxqAc1O8w.OHf6|jϽ&y"k1qԈ hͱbz? adXe Pýb ΰ5P.zGGy?׏U>fƍ ftg#c~u哉[$"w!}>;!#._X8lxS=l1=Loյ߹w}U*<'OGsrB5/ATE9Ǎk[]Ǵ(d+*JaBrGӔdAK2kKjۥҐ 0 pmgz%hωU&TaOŞq aQAy="vvŃ9*=2*>KWK}'DI۟w^Z ND{>RxsKxs$]i[P%MY"  VP;'Y{ecPLإ7N.8=D3lnQ`2N,ߒXmօO6] :V:!Tt͐[!.YL:1Y 6B&+ @0] Cjo[ԤQm68ـR?~>UVuX+Cf#Twt|GSGHuU32jC~6OKWY?i\}!<=6B/(}KxW?: Gv1(z:kKFO@r|~{uz\QyFԀޒtt?z~T?JϾfr*p䷷8+R}A⊈5t\Z'2y3: @7.L7A5E?=]i4%_XohEQfuPY3wd61[iP N:,'Ex!0Iy9dm9'l{d8LC*"Լm{E\@2/|W:N%#B?㛜n rUOG%pz]m/\m`/Jnm$wlu&}WL (r+ #dWQ 4PyGQjݨSEK:(''dS|kXAHۚH4Aq鴿kpayJq&J"0"?j: =Q wϘ̞/׃Uy Ė&]}ƍ_XqN$at՟LiF'μm%$?X ~~.Sz È~3~ @ XF>o珗s.h7Es&Z p NVpR+)*kt5h n@Y63.kp9;ٷfT%p\ӑ6&~Nm+Ge awfkT9N4p94KEc;Ze)¿M^P`i\2IQ~EvW^eNԭ ᇪAԣgs߿T+Y!>\KND]<EǸ̊ՖiYˌ-D[ wJbښ$y;z(CKJq6<.+@+ LǀQ9zB+e*/ux4xZ3C1- ,ddhF &JP?E{[HO,) 7B#3co ~2ڥ##۬tsz5z%o&].WF+0|HI^p7?Ѕt7@$ Y@4#a7Ӿ<|v.VKzIC˺LDd3iҎ͈q tlX2!VY Q2nYb/#n%#LXNTHlmHf)Y]-0.J=vB hXgyq$q i {Ӯ̝Z }"O]ِ#BI'ζZ,`W5gI ^=WkE |`(>ZgbcA$7^ Vp1#3e!LIj­I +E0h奇BX4L& "MשB:!Dl7GoЈ@# p(~znU7Rw(K9cֿb:(nqԏZZ+ޯA6LL2xڍjq6vgYYTc7uY,Ww ,h;tD9jJdIJN1I@OK%LwILZpje{wȢ 6X B?Y)ǎ:DGFOԾߦke[GD@GmHFۻr'*B>촲gV%͆m;k h3 588vI^c#Ok6]F71" `3iM=%"-?0S2a$:B3B LP=4_R2DY֡:0N7afL2ia{wj Kt \:f'/ꨵ壂0ŀXC@6!U<[DN ɤu/ڲ$X)XxC~*8hK!oY ۂ6YDd)7(ۯ[˭׃Et4鋏J?HdYm5 o2?R82|s/(xQym{eFW}^6 $`kS[6k}sMfqlA;=w:Vgp0xW*5?n3xD@uYKjGoqn=҄#gG6CyC=R*!S: ְ, 3&E$ԁ]uTF(b]uh)W.}h' lG@Dvh'ZfXcM")/3KcCBhoFֆoodjh7dYm1;4~鼠$N@BPw=(D0FB($DR%EA[(s BpL䞒kBL TXHF0!Fa 0 1F) (Y1HŞeHR괻#KRxL 9c,j5zˋmu?U F{oVaUGHeGي`"3ۢVɱi'aNg#U2`֌G>TFt~%? lִ* ~! F8 Xҙ{b& qp8mPأ Gǚo;?1Eag"nBc9ɡCTD:/`ƌzК1<)qB{o=@zxii kx!X)r6 0|17܀Ԥ @_^vHAf^Ngu9P͎y{=W6ߤ @K!e_CNv.'敷Ap?~?*$3@,}dYqP<ea=~ q}:RyPlmxVtEk80-r>(yPџXi\+(Xi#Lzh . ;"Y΅ۃi &[o" 'k'!*^\f>[t 7w|cBckP920k~&%[hu14= dA|hClr@) G5I5"f[XzV=J8^X ȜI J:a{ri)WP[ml[@ `w57*ւ)HN?Hdžϴ[ >m8SV̉QZ8ZEڻø5Gba`9l5}xK"=lUW/]^na(;R=QQ'>`7Oz24s̥ZNv2 _,+3"iԋ5 @(Em2ӷh2x]ji tz~ h (.č%L V(J) z^-gE?/rziUC&,o'f^'iA8K(KvH 5šnT=3}^u dO)pw:ұt^+xXL޺ܨG bHv#dC=%C>ie( ?)"|%f>uT̤%!eN VK <444kij4K絛 .C=aFu<=86g+?|b 2CUAm ȩ|Yr3{dKp4Ve;Uըc MϽQ- *̡2N\5nKfXՀڈ鴟'R1[/IG]:$W^q `!+}Θ,\y6 XebK!B, K9h/t T 4={Y a^3:֝[5lOj`vNwt8k)p^qBpVMpvwFG\]NB3w ,7N>jk}q}݊ &4 D``ϩA{ L'`lg/=؇/hYzb𷎍 y'*lH9=JX@,#Т[oE5=(Y_*ܶv siz篟5Ll>xvn ˆϋ:糏/Iע8p //?j]aR?E*ݍy- @ '̆Й 3",T3а]Kn _&[rF̈q(R Wi7aA逖( 8=Raѩ{FXQYi8v'eds&xޮb~ۺ/ŧO?w'Kᮑ(u+xmNָ 'u I:@.'W.|/0n3R" lr3ϏbfsP`)ߺ*?9:)͈Ҥ~UteWi)`w+%]$LႮ/ji<>ϙ)P+4[v۔*뛂%_&.N-?ԗ#mmj Lc6`Vr8p|j@'aQ:h(*GU4K]ڽ^!F(Ŗۈ0 p‡s=y+%.M%ͤs"U}A3Iv6"Hչu+ ̈3#ǽH_~}WsD)W;%x4KYhj/2Riݥ^v∼?7 {4d}/Ԃ_\ɂir 6ްdݾYVpwI7dr+:,cZpZ'^vDIM'݉ v6EM?9#N CA&!1 ړ5z+/t8t|&s=L;ֺ,+6}IҊkD pHI#CD1Id^x"gb\Bu+v-u-A?eB HgGKfH4[1نʨ-h>kam}H걦Zz.U}Il؋Z>opl%WC@tڒWn?|T%#!%7 wJ.x6Ayܶc=n{m}ֶV+>U[a?qꗭ4D,Dž_EWp'B" "#wep !UQ[p,UG?1+?^n/rObYnr,\+! Ax&Qر;FG<~8 Z{ELg 1l!lY:(p+8(hEg0S(lyO86U[U),i5$ˢׄwe"pBA~$JAJd1lo; _% )_=X_A"ՉOVzk;thZ+YŹU.m !ݡ@eD1D ~Jp-D2îӖ%Mԣ';>0a' L&4dyk䛘4zqkz[OO+B9]*kh3 K]ⱉ;K8mSE.0Vws&$Bݹ}H{b2@F+{W[?qh*/2Om0JcgV0*"l#@$w̔Og64 i0aACoº0G ='Įo263D5hQqyޞ>%n MF `?~w+ڝł49'QyM{rpl5B)קT!A?֩B[<_ʉI2|s[5x1wMWNw2vm_̛ߟׂXBy*EsxhA8>PQS1"G !ЎDNڂH狋q$63~&^JK 뽏2FIC¶P}UFh$WMjJ+G߷<LcyfVM>}izWe]F!܀Z 1ȇkadֈh 5 Z}ŇˬIsc`!w_GsJ:2r \n%вC$xE; eE #(XI.Nd3#<>J?5ǕQW\!g#O͍~}TZʍްvx'v-o ?AKD:d Y q 3;L318'""&8xF3@M4lCrƇ|jfNCPMt/7K*)~g^R281zdMN-JU|:w 1`2(.1)<.04 !$ `B"LmM߬`&l_~MA5(>zxvH&GQucВTkĘ*R*2hfjce ;,kGۋK78PM3z Y}Ng=9u CIedlf@tS?#4G}=yodr+ƎȽ*Lͯt@Y釡Υg`cM#YQ32>/b,G< 5qtG%܀D0f툸 5zͶaӓQ|X}?.*8X›2p?]rbq^k4 Yf.fI9ӼLa9CS;vbL&+Has43[`2&.)CH$Q{6Dl `;'Bi'P?>j]'Ju$GL0.#7QU_UN6EXk^ז&*v{;#%YAVpX)ĀJ:.hid)A)b@nI/,: NC $H;IE'Dݬ.Z駌ltϸt(Y"R7O*Wo4cZ"XC,MP>x3lkjG,qbOEe! 2upv.#7iBQVb#ܛd)ÜJǵp=C q;yBZmXNVjE5K๱/\5RiG ejs)hbTh~ݻ)Pl?X?`6ۅq=9zk9PGaMQҜ;]U٬a_Y8jgP92=nz;~4VG%=nfgf0A GT$1r0C-C&4ðˌ)!ϣ׫"ȭVnT<;lϮݩn+ChUs\}K *5J֯gYs?S\ֿ4[@6n&:%4I"#s02N? =3ġQ2ClY]#m,r#.MX S5 ;]tWk͕tsˋ;n,v2Ws(`}(ʩnLmT3xPFwj+['*0>$ju:7Qgf#Ԕ 8QRAX75o &\B7F"6PHj7.oC ],YvFQlv71ule8Ⱦ2fpStjo'zK7.. 6~Rkjuu*m׭-@iUmwsS |t3~pbH57b Be S ^Ύ^G''#!O&`X@ƩP `^d2f%ǹk-l[tXwvNr!Aw To=޾Z֩k>3ʲ֫õ'dprͯsiUS#SMNaE58(̀tv,XʪfX;Seufk "~}i$_FjܙKP1odh~nT{SslM7fHvH~W|.2<!@+n%P+T"b;gٿ:Sk軝G~M[*)x ņWՕa|pmskg> oj̣! q0;2;uh8:zt}j:84/peSwf~/U 58|&4|\+"„aEl.M0[n,]ݨ>vumJ;nJLyj򕯜i< 7\Vo|՚ZnOFiUpېc{vD=[-4<# NS$/tڃ/Ā $Y?j3ֱ^OUf >4fvI|MqL߅߁{t} dR&`N(p#a jxVH G &OyV,-sTYښ{~珫תF0SǨ9l _FƧ k6;ؚkٸ^orTaҞ[D0.)8K~ fh309f8DhFuC6fyڡ2Id(=.:f%+O'L*zVcRxfcXvzJ?Xc |Ca4{o>9@/3#TI*CǸÃ&:Nq-]i}=LS&v0 HpƆxIC;GB;w/5Q !ajmmmN`5/ΩJ>=9k9ׇ䡪M<3F7`g?wyx&Og&!Z<̻ OPj .[ N.aRnNsj5ߖ3ΌaE@]k%.zOv΅ʠj}Yc{\-=O! 1PCњ|5!*7X[dbFyN;_5W}W2|W31/U+ׇ6gb+'9g߂C:0ǶGC[ ^0#0 yH7x!X?P7ID|9.HW>]!LKϧfv[kCy;.;aXnvw_oCw&d;sN}Ro'\#e-Xs>A 06D3 9/rNacȣ/f>y_f GdzbQ]ͪs~tỒP*/jpKPCΏp7C[_0;v#>BhG/E!У]J_ׇ!OYھ 8IڦVr6q\ԣjX}+JM]0U_ʠ94V?곟5Nu;p*bDU*$;-#CE$eOn)lN}skvfs0^Pvt~2}ɸGm,38Y!s3`6^0{ƣE[ Z0";v#57~$MmlCyH3*n )d l̃ioh66#˂BOAڗkk1wek;C&p2;Wƾ?+/BH4F[J7'˅r s6V G]64GG7rootCIQ3ܧN}́mSl:?IS&'}—MdeCOփeE? )|csuH{,w,#fQ(*X*u[sUm\݁[Pv%ŷ3s#M? x' qDp)N-,UsjחN?j3z:?hfهFASdžܴSIbp!=+V<fUwGa_WSeט6d֩P[`vP,- hUnݕzO6~!<D\8&"q J͎/?hzz?ÿsxJ ;Zj3T>kc~Rtbݸ[` py.Ae۳s,߀n<+ '&xaVoq!eA}5 V`v%\y#Xif 5`oB C%e^R m!_:yvds7?sy}"ZPc ubg  u2nıc3c+?a`lͲY@hn_cvktYh R4\ $&>|Y9t~-.Qmb*&]#̜rMn=0 yEܼ IM257z^pon})a¬vw-jd| '8W@{*yX/™)!A͛9XǹPL 08ro7oWQ{*?nd[F5\cv5`XTx -|ٶ,-8?Pq04Tw7 ܾLU.z? t] zvǥ= .? ̀ 0&Gx JԁIH0nP&j?&a`0iˤn& '鰅?At|[j0fP{yk1Ls4\|j=]LwZCWNIx1T;;O6JW Ev-+prNj syg 6#,bR]T^r63w n2|ϧotJJ7[Ї`Țϖ]M$t _h"J>鰜Xvd 5LĬCx>;jYw6P0JN}ΨcZSc~6"vnF35cX{4n9X;3(Ux~&:;^eu{aF;S`82;(W\6u;Cm簀/݄|fhFh3h؉Q8nϗKoG39,[VG wHЋZ ??| {pCv+V\ݙ姸:pQs9クcc}7VݧCxg׭b˷n ZA]4Tv.zz~wBcp:7?Mlzzos9Qo I~6UhO5&!E8£+֤|Aq=Ԗ8⽃V {Fj _l/6w9h?4Cw.vOWq?64C-EDjm5iX^O/ytbbTomn̴ HzS7=ipz0\ d8S'<=baΆ8a`I O;m-~691O8_+ZD7˲/Asuq}w6L鯳i O[ h_1 &(/Pc+'l6hϐ}w[=wdgF򕮨Fg+ln:~Jjf cC9\^\Ŝ6j;X^JatQ<M%f3K ʨߚ~ ž h纆Ս!avz1Ffj?@l$pM(,\Hq[OE9,qp?ñ:g6:|}:DƥQ3H_;]Q)`+c+/>H 6*C>l,>T4iWv??j܁iWx}hHq-Vp*3w v?snaC /`;y *LV+mh$ss gUp?V;?:wpWkx &` clEw_eۅއqF׃ jakyCoܡq>ơD3z t'UHR uoc#+Xz ^ØKlhʏ"eËOnMŸֿ/➘@-th=X.Xf ˚8Uv={_&Uqw~F'Քdx,L f;lb4| +k3+X}V{OxY|K4* ?X#)@4>;2` 70/FPfn?ò}M:6r [e1F7)+-2 Wzl?$х_a7>K ¨N ~ѕ=Q+>{MqNM#8[([4KF38?ޅ&0r_o:pT6EF\D13w\qawt<7}vTkm[[OcO(iO[Ǡ~RRsk-,\K<&{{ {PUq&KpcL< e$;?JX>]'ͥ(_C"5.w;'|^!3 !-QǩuLDA=}փpV`Y'ݹg;ߘ9syg7{8z\Bc&VS+Sq#ͦ5PsObuMP{aݽj6<#2$…F93AOs9sh5Cl&^E˳Rb[&[gG |/o xB_YF !cSޙvV4R>0HReH74?8NbO됆pxnT:ŭY;Cyb̲A[&u f l{|o.N Wjzc,[k4y^zX:[n{DؕGT*$`s`$ uMr!娲IZuXw0up4X~.ae^hhǮg}Z$<;4 7 hNA=["{-i>GOj_܋ϯ;UX1(5̏<&Hd<Ewp W ;3Q>ZX3gGm8==kդ?UiXɓؓ 9'@vr.9`qH}O]7^_W߄σ,ii:eIUxj,qS7{&/XL J|BtKXf)g<]KǍ/?IT8C_1dɇTU1 2AӺ[ߠY\X޸CW_4p~c~D|f!ƙK[9K(u9Wmı 4w ^T OaD!J$nTF^$EZ[8=^řkt<(n Di㚁#ՉogP`G0{MHPZy|"1}Oc#; q F ]c*<ܜO]{}}۽Vqw 0_c}~W1~#ppK7Wz,١z# _^}Xk?>3%α?k\+ª+1Kx ep:]I8'ס>wR #Ӱ9)]C1Gj:sǚ^?1#xp-' hJ,J]֏862N O<61ϫi46Pfx/nm=W1g*.}L%h*oiҊ60C# ۠}%Q܀1ćGT@̇p:|¢ 0-pna iq-1OK>릝}{b>9cd?x{:]lm!Nu&S(d&Ih}L*Z[qp{%-sq~=%]4kt.>j,ؓXE|3Y<~԰/a_oo*Ȧb: \ep6q+7HaGD(.u  ҽhXΜ_= a~L,O$6 cr=u.; {PQX\ *'Nen 7\]TחR3'w&D?ut862Ns;V?~TlY)5^9t{y?.3"<C;JЌ%YelÜnc7!+l|J^pQUAţX Ӫf S3 L񖤗uҽ{i \3mxJqiQ0CWq_l[;Tg53n̏N"Dpȟ'qI3 + tn!6C9 U5!m:BU!Ex/"FND7igdWyWÖ7,/IḚR[x5 .d$prݮM*#Rim}; ~:kuΠ{eN|D!ǒc wPw t~.rMXeXImgln{؜}ꥯ}kxe=Xjzhn+?ԑJ67!D`,s"+^6P4w)M ѧ'XxFc( 48Q"VpH[x?S+\Wph R|Q6qL~lZ.ocoBL4vWj}|Xʋ,Og'w82werN@FUC9Tj8:u9Ukn4\@#9_sgn/ީ>6m>F“ƦǫOb^e[Ly??xb $L=ػH.; L6Rw[&8"o Õ,m"-ġ7q3$ɇΌz3kWqY f?Eq>=7]ae;=ys{X@wa$l.q_EV_?7-\+|̱b?8ɵQ"^9b h+S?0 ?@FtÏZ0g#&,•^ (wO/ҳ`0q! ㏰O%% Uah8vZǩ[Xۢiy\Blo(/)y~C);<.]1$_[x fK&`(b3\ᾁaS;8uwd{1֠wUyj lΰ9.oywG:VN=(PxjZFGUgQr- ?‰0@ӸJ8 8][ \G@6}H<\䏺xhi~\ > ^ 0$iO,nc%I3OnHO/z,.ZC]Gj?W N*=<3P#hOcW}earYpW]'i0x7X7v?rv+q]P|5X0r;s7*e8@bAk4X0٬Kq?>ٽ܈rnn\gǗnO?+|<Cn)l2bqc 8(lFO̳rSN.&xuiѣO1ewp>u\:w\xs^qx>1w?cwr ְl8܁5QH?_mݖ!9?̲g憫?'v1.k4rpB<0IO x'1zKgXqWp{R'_Y"Đ#AAqĤbCbB -@@= D(tAFBRO٩!uq"4)9)x KJ;͚R :@0Uv5t7W3'XYmb!w$/Ru;6 ț zaNfB#y@b"[TqTq,ޯ6ԛfcx!_J8eE'lǀ<ix*lH@!ЧXJ9 HW#}^ U_]UՠF1+>'~aXbdRdiϳ/; `=$ž')EtgHbY 1\v}ހ-8ڜ@-+S1.T6Kp`6[PP9 ] 1i`(A8,=s'8ư~n'k{dP>Ơ/N2(%C|C5<_a >%J0LKQ<ºF8D今eFwGFΐsGp1'Cf1.Lot0iEz!'ie*4m.b%T M8Zh@Uvn@{WX+;dC#DDL7?8sxg" $UJ~],&M1wC[@I>( /A{~m@/Fvc PPfp\Қ&".A6&pIUVxl: `T vna`+PBeS ׺<[~.b5!+8ˋG*ĩZP דqQnExKs+r~x:|'Ϣ4RH<S=>gy$p/q?=3V2Ȱ( 1 x*1<HBwkɿ{Z+qj\ˍe LC_FWU}zvkpBQF66\ng@IDATf'd.Bk5M 6<(p7`en<>8},n܇ο++(l.cCaǝnكjJ|bJJz ^hq$oJύ&ˡ/bg;2 ?E#>F@5kۻu!ZȢ>L a"<1'H)˜c?Vf#;cnbEUY܁_?o(/Ύ GP" <+Dž:w@w/F0ɣ1rg3Fv3$hz(}SX[[~/0vP ̰v1d3 tOFQ3GN!A2?p9w>5ag)lpnAPl\}6DЖT9Go#ڎw&)R:d"$ YN aK}:n"jw^}s#ӗWŨLb>ì6h4885V?d'1xߙazvX /l*=j 7q VdC.qmk&QG+톍XNs'lnᲞ8? :կP&ly\'uI [k7r0Anc>$+ .|/p~ۀ:g!) ߕ;&<8)Nα}y>`9a]" ƪ ?'y #&46vk8G/NŧAP7> Wk54#4GZonبa?ԼPMMg8791q$|sh>jYDjIJ"(~M{[8N~qI0J=BFqn”x ӟP$7.t HZLЬN~?ΏxF@ֱƮp\2x?0sz>EIC ȱ>q㋾e׉`S uh4mrr0f;3ж '~oպه0j'xG}ٿٓ/ #ϱc ea%hFͱ V&9M6L[/¡"H[بҡJ` ғF726f^c:DaoS*/c"7Qڛ V6I'OOPi<Jlۃ<ZT޸*so \D':72Q.Pp0O< TlƤ84Z=~u.lc HֽU2~i]I~Fe~}В( =aOp2llT#y>U=y - %o͎G: sb$kjISdE(2[7u+,mH*gYnI%u+06Kw §w0,!a'{GmהQb2&T:`5ʍi6wj]h1pf=ZRp`Ahº2n&Ձw෯Yo7@p!i`Q5%1XL !|\ v4>Хq0|Y.ߎ C⧆Ma˘ CQd_6AIA@&*]i#W`neFy ('?o#>FBXDTi&a;ŕ; 7 Z<*+42T mcp sK髸tu'|sUw˚MuG2dDM0Lo+\u9R-]l`2É HbaѤ0+-.Aip-O bu%u0\KTɆon~b. <-d<] Ḯad j`n߀CȖD$y=unC!a<Ӎp`, m$tD>4 # ]1ͅpҕo9j_rc1ONw#I+iHA]ɠ T5(nZ@ b9OGe.;]f+>m6*6vL"^kӜ%.; Jxxy`+d4ⱍ1 ^{xi %?cx|OAy؀ 1ЖIlҗ2 JdwbLЃ:2W:V6\V;:L73kF@c6W=RUEdkQg)`qJ~g%fbouT&hvMtxv?'8`Ӥ0`>s._I 1;6?l&fj00`Gnp'$$?}m$:8CC,̴cWz:a|8ضङSgv w"K`G">r 07Y2< (2n~o p@K)OFZ:ȳOǑ2m,dCY+Nx+qI8^a^x l6 N pK ;dhuLE# !Vk0HH\C3 t}4d[(jx v.B?uaթVx@a&#k$)3?c`ఓ8av(q J3*J LcpES`&}!DX x;*F H%uHf v3%u3pR#lniBWHG~i]0vWjqV4<5@T !Cot7?p| &G.'*JE &7m &  v($4p[҆F1)!+NO $?:Vw;$r@c:Us];25ңv^/;=I7.0qw*\:"w&ʃXAM)EJIPLL=#_£w\縹kFKaн0vpXXP!t.m8l`ٛÑ6Gxk֠VvKu2?nQO@xpM[!(*i.1.H4B(J^&qZ ?1 5k1\rF$YSW~@yv -C H;?`/s}bD!S[8%A_oc%~$8:,Q6$C> c[,P6/8%d{ 0U坄)/!0>)+OKųܸ(h'r'L@ RO::nO4r}v:qd'1,DM )e'eIQ)0![Mz s"bcy ldh+ G<p0&b_Npٓ‹I~-&W=KDD%Rw~a5ȓC KSwXU -xjߒ2bG#wnlj@ndX-= w2$SFZ\BwOTYH`m*pg LޗǏ@ I:`l\h3ͽʷtCA\O-Fp z"QbBv puNJxv@ԁgPr-<D^e^Q O$38XbyFI?褖iu,aȯu'<kJTфNlg{ҰSRۧq|.TiBvkc8.^l ŵΊCTg)R}O8z1 68]%J&.JFU* ?K ]qNňx;^L/U+1<?&hi <]%S 4Wk [E шIs$ #,v1@NC R0HR(@3 Sy8,6`@Lcሂ8i|F4 AX"Z'":_LOUXE0jmțO2o['%AŴVӰ=)'7qd1)|>8cȣ"b{ Y)GУ&; B&Q{/'Q^ /k%LAȼ&pc:r ؘ@E@+2HoX߇{"~51e0=*v@|T.ѐ@ ?!IhGrz@NxLPm4p?gַ|>y<Us1 ,nHZ2+( W  9[PP:1Mg :ИN &ɩ1>XLˌħEړ~1?&| >)$^r<% U `O ^lFMr 4D;yo*RM^@$";ퟠz #3Fpҍ OŽdxtGV*R6ǘǚ*+{,70N$b8t&M5iȒXi'Rw>QD> dv;헾dq] /'gnbbj$ +kn\}w>3T^ 0D{h*X|?;]Exyv?By orX"@'Dg6Z45c1c~Bύ~9<|:I>| 1X  7ɏtk,}T@?|1+QL'&]q(~!2⑆iTpHC*.NBXncI~yL.b{_xyFEn8>EpePyjyaEv*^JN5o .'Q&.[a Y>e']O j@X>FV:Fe$RC(W6q)n#; +sxƷCd'̂ ~qSYhJp9vk#Wm % F{V `C .m Z{kᝀoKL'U<ˢ&1F*@St#Yeb$t*X~Qv BƋow& 3@ ,I?\pcG)b@eHMlE]K~OA+:9} wGk?Gtc =SI'*S~HϨǓ3/B7}4h Apesh/Ip9!U2({)ѷVY2bc6 ̥+aRrkڸbL~htRMG:պ'a\hˤne32w /o“]\v1<^뾗u{ |! Ll)P5C]52,-7*%qpL,\z[w-@?2[5{a/Q}9y85ᦣʀO?@AF<؛'( a<< =G*/PBGx ?v}&.@wy SF!!m3O4܅広sڝ 5(e !1IT+,A>i\WvۯV#]tS؄K/e|>̖Rb+d Pv>T2Rԭ` +?(-_S&$wY"LqemxO#; ?e1r hFnBPsEjWE,En8pC,[m9Ko@XH@A!Y$C5"蒂pw?4aF_O`t.>QS ?o3~3Or5HQطUw/_c߽u/p 0M){K!|"`I1́,5Fnu̞_ni8lVn8&;Wfu\A=۔vBJ} `6 mh$h9w5O`o%Eŕ]!bx'Y31D$D'^- 1G *^nI AfFtA{+"Ye/ޕ[|CB\ݏ{++" eW8ԭbSoosE^*JjXg7p>E~ ǂd^k7?VmwlvxVFǘ/ ۀaaIKν>r:tkX汉l|t}\aI;5 A @F0%vtx` tY-6MpB`P9v /%\=5&DF}n|1O!}0KmoM(;,J3£ ҭC/$1ci?ڲcg Š9wj4l.ڧM8;;C/~B݀݃#[l ,߀|٤Je !g'`'8x3ݺx;x1eDjh!XU(2fvD2{Z}cy{u.Npp.^%\xlŝZ ~\ ``;{7cTЏ¬ΰ2u!} XFFSw6ʾAajkK%bӆ [!X0J^`ǜ FybbPyBxB% jFs=Ar)O :P—/ F24ѥ;IL 7_uu!{?L o?F aFD$ij w5:oZLF2KIC;Y|4y^.a`-(c@'c0cK@Ȍ|}09d#00OHщ# t8x1hlNIF0= Oي.ɯVnCSТ B*Fa`CbxqAc1O8w.OHf6|jϽ&y"k1qԈ hͱbz? adXe Pýb ΰ5P.zGGy?׏U>fƍ ftg#c~u哉[$"w!}>;!#._X8lxS=l1=Loյ߹w}U*<'OGsrB5/ATE9Ǎk[]Ǵ(d+*JaBrGӔdAK2kKjۥҐ 0 pmgz%hωU&TaOŞq aQAy="vvŃ9*=2*>KWK}'DI۟w^Z ND{>RxsKxs$]i[P%MY"  VP;'Y{ecPLإ7N.8=D3lnQ`2N,ߒXmօO6] :V:!Tt͐[!.YL:1Y 6B&+ @0] Cjo[ԤQm68ـR?~>UVuX+Cf#Twt|GSGHuU32jC~6OKWY?i\}!<=6B/(}KxW?: Gv1(z:kKFO@r|~{uz\QyFԀޒtt?z~T?JϾfr*p䷷8+R}A⊈5t\Z'2y3: @7.L7A5E?=]i4%_XohEQfuPY3wd61[iP N:,'Ex!0Iy9dm9'l{d8LC*"Լm{E\@2/|W:N%#B?㛜n rUOG%pz]m/\m`/Jnm$wlu&}WL (r+ #dWQ 4PyGQjݨSEK:(''dS|kXAHۚH4Aq鴿kpayJq&J"0"?j: =Q wϘ̞/׃Uy Ė&]}ƍ_XqN$at՟LiF'μm%$?X ~~.Sz È~3~ @ XF>o珗s.h7Es&Z p NVpR+)*kt5h n@Y63.kp9;ٷfT%p\ӑ6&~Nm+Ge awfkT9N4p94KEc;Ze)¿M^P`i\2IQ~EvW^eNԭ ᇪAԣgs߿T+Y!>\KND]<EǸ̊ՖiYˌ-D[ wJbښ$y;z(CKJq6<.+@+ LǀQ9zB+e*/ux4xZ3C1- ,ddhF &JP?E{[HO,) 7B#3co ~2ڥ##۬tsz5z%o&].WF+0|HI^p7?Ѕt7@$ Y@4#a7Ӿ<|v.VKzIC˺LDd3iҎ͈q tlX2!VY Q2nYb/#n%#LXNTHlmHf)Y]-0.J=vB hXgyq$q i {Ӯ̝Z }"O]ِ#BI'ζZ,`W5gI ^=WkE |`(>ZgbcA$7^ Vp1#3e!LIj­I +E0h奇BX4L& "MשB:!Dl7GoЈ@# p(~znU7Rw(K9cֿb:(nqԏZZ+ޯA6LL2xڍjq6vgYYTc7uY,Ww ,h;tD9jJdIJN1I@OK%LwILZpje{wȢ 6X B?Y)ǎ:DGFOԾߦke[GD@GmHFۻr'*B>촲gV%͆m;k h3 588vI^c#Ok6]F71" `3iM=%"-?0S2a$:B3B LP=4_R2DY֡:0N7afL2ia{wj Kt \:f'/ꨵ壂0ŀXC@6!U<[DN ɤu/ڲ$X)XxC~*8hK!oY ۂ6YDd)7(ۯ[˭׃Et4鋏J?HdYm5 o2?R82|s/(xQym{eFW}^6 $`kS[6k}sMfqlA;=w:Vgp0xW*5?n3xD@uYKjGoqn=҄#gG6CyC=R*!S: ְ, 3&E$ԁ]uTF(b]uh)W.}h' lG@Dvh'ZfXcM")/3KcCBhoFֆoodjh7dYm1;4~鼠$N@BPw=(D0FB($DR%EA[(s BpL䞒kBL TXHF0!Fa 0 1F) (Y1HŞeHR괻#KRxL 9c,j5zˋmu?U F{oVaUGHeGي`"3ۢVɱi'aNg#U2`֌G>TFt~%? lִ* ~! F8 Xҙ{b& qp8mPأ Gǚo;?1Eag"nBc9ɡCTD:/`ƌzК1<)qB{o=@zxii kx!X)r6 0|17܀Ԥ @_^vHAf^Ngu9P͎y{=W6ߤ @K!e_CNv.'敷Ap?~?*$3@,}dYqP<ea=~ q}:RyPlmxVtEk80-r>(yPџXi\+(Xi#Lzh . ;"Y΅ۃi &[o" 'k'!*^\f>[t 7w|cBckP920k~&%[hu14= dA|hClr@) G5I5"f[XzV=J8^X ȜI J:a{ri)WP[ml[@ `w57*ւ)HN?Hdžϴ[ >m8SV̉QZ8ZEڻø5Gba`9l5}xK"=lUW/]^na(;R=QQ'>`7Oz24s̥ZNv2 _,+3"iԋ5 @(Em2ӷh2x]ji tz~ h (.č%L V(J) z^-gE?/rziUC&,o'f^'iA8K(KvH 5šnT=3}^u dO)pw:ұt^+xXL޺ܨG bHv#dC=%C>ie( ?)"|%f>uT̤%!eN VK <444kij4K絛 .C=aFu<=86g+?|b 2CUAm ȩ|Yr3{dKp4Ve;Uըc MϽQ- *̡2N\5nKfXՀڈ鴟'R1[/IG]:$W^q `!+}Θ,\y6 XebK!B, K9h/t T 4={Y a^3:֝[5lOj`vNwt8k)p^qBpVMpvwFG\]NB3w ,7N>jk}q}݊ &4 D``ϩA{ L'`lg/=؇/hYzb𷎍 y'*lH9=JX@,#Т[oE5=(Y_*ܶv siz篟5Ll>xvn ˆϋ:糏/Iע8p //?j]aR?E*ݍy- @ '̆Й 3",T3а]Kn _&[rF̈q(R Wi7aA逖( 8=Raѩ{FXQYi8v'eds&xޮb~ۺ/ŧO?w'Kᮑ(u+xmNָ 'u I:@.'W.|/0n3R" lr3ϏbfsP`)ߺ*?9:)͈Ҥ~UteWi)`w+%]$LႮ/ji<>ϙ)P+4[v۔*뛂%_&.N-?ԗ#mmj Lc6`Vr8p|j@'aQ:h(*GU4K]ڽ^!F(Ŗۈ0 p‡s=y+%.M%ͤs"U}A3Iv6"Hչu+ ̈3#ǽH_~}WsD)W;%x4KYhj/2Riݥ^v∼?7 {4d}/Ԃ_\ɂir 6ްdݾYVpwI7dr+:,cZpZ'^vDIM'݉ v6EM?9#N CA&!1 ړ5z+/t8t|&s=L;ֺ,+6}IҊkD pHI#CD1Id^x"gb\Bu+v-u-A?eB HgGKfH4[1نʨ-h>kam}H걦Zz.U}Il؋Z>opl%WC@tڒWn?|T%#!%7 wJ.x6Ayܶc=n{m}ֶV+>U[a?qꗭ4D,Dž_EWp'B" "#wep !UQ[p,UG?1+?^n/rObYnr,\+! Ax&Qر;FG<~8 Z{ELg 1l!lY:(p+8(hEg0S(lyO86U[U),i5$ˢׄwe"pBA~$JAJd1lo; _% )_=X_A"ՉOVzk;thZ+YŹU.m !ݡ@eD1D ~Jp-D2îӖ%Mԣ';>0a' L&4dyk䛘4zqkz[OO+B9]*kh3 K]ⱉ;K8mSE.0Vws&$Bݹ}H{b2@F+{W[?qh*/2Om0JcgV0*"l#@$w̔Og64 i0aACoº0G ='Įo263D5hQqyޞ>%n MF `?~w+ڝł49'QyM{rpl5B)קT!A?֩B[<_ʉI2|s[5x1wMWNw2vm_̛ߟׂXBy*EsxhA8>PQS1"G !ЎDNڂH狋q$63~&^JK 뽏2FIC¶P}UFh$WMjJ+G߷<LcyfVM>}izWe]F!܀Z 1ȇkadֈh 5 Z}ŇˬIsc`!w_GsJ:2r \n%вC$xE; eE #(XI.Nd3#<>J?5ǕQW\!g#O͍~}TZʍްvx'v-o ?AKD:d Y q 3;L318'""&8xF3@M4lCrƇ|jfNCPMt/7K*)~g^R281zdMN-JU|:w 1`2(.1)<.04 !$ `B"LmM߬`&l_~MA5(>zxvH&GQucВTkĘ*R*2hfjce ;,kGۋK78PM3z Y}Ng=9u CIedlf@tS?#4G}=yodr+ƎȽ*Lͯt@Y釡Υg`cM#YQ32>/b,G< 5qtG%܀D0f툸 5zͶaӓQ|X}?.*8X›2p?]rbq^k4 Yf.fI9ӼLa9CS;vbL&+Has43[`2&.)CH$Q{6Dl `;'Bi'P?>j]'Ju$GL0.#7QU_UN6EXk^ז&*v{;#%YAVpX)ĀJ:.hid)A)b@nI/,: NC $H;IE'Dݬ.Z駌ltϸt(Y"R7O*Wo4cZ"XC,MP>x3lkjG,qbOEe! 2upv.#7iBQVb#ܛd)ÜJǵp=C q;yBZmXNVjE5K๱/\5RiG ejs)hbTh~ݻ)[(. ZTj90*%^G QNC11*$SM NQ,)$ v/ W6}m%K @y  A-vPN$ 53QL% * >   Ճ ǚٴ ݲ ¯  ˕~ ŵ Ҧœ wsy{y} ڟic14.PNG  IHDRxsRGB@IDATxɓ%ur HP >DYj m^ˬl3iZ&Lc^3If=H(HS5tw<ҦӹO9Ʈe^oGGp?k95/эό岚y'+fҨF:ʎe6q,1.!k8h(AVzC=152&%;l'p)>ܕ_{m'8 ]/<Õ+K[˯uUQu,ey6,POXy3G@ O ӉdG ((=:hҮw1+bM WnlpmJjb%SJDꯚ'&UJn2W?{OL81^#0 ͵OeF i`bb9} |bJ7 +t7 ZKd/D UC\SPpPf/&I xOVL>wG,f d~t f'`&6Tg4ZCxD XS{fq2ssI^ۯÁY^>lqeSz^(L\T#'=#OMz0#Ҍ0o M BADgc%<4E2X[\{[w?;D )0 \|yη6G0G`6.c){Z+Škgt-{}f6P4 uOM嫋8>T êN ջZa7:B‘>odm>vdwm|7hgSN|ӱCN#vQX6vn]c&,VwJYHL" cT@an lEc-k_[w{5XHWRLk-ֵn3 <=>'`kvß}uϲl郎ƒawեrw9x?&IBjc䍻 Q`0S<']B)&HF}չnE© ɇ'&ou4C"]i1#Ƞ!U%&,z:3zoT+߸/vV h:zٳ8=TwO,.<ɾEx]/O2NC:w_ D\\vT,ug[sK>멯  Em?ź G{GtL;$CA&5)4IQR4"fuZ6ymؤA&|OJ*Xߔ#Ž$^O TP} qgK#{Ҵ\#3sʑJv k[~ J#cp#O;EMvş,@p<τk drYF*{iˆR4dIG5;?/'a ) 1 a淘psmF#-Ǒ:Y)w"58Y! VC6Є)ælx -#W yj"xp z؏F^xÿdck: {=+F.Kg:% =Ygufc@:xR`m_]_[ⰪVDzCT; cll\~"&jd_;(>lac> s˷,vPO10Dc<皏8Kco>G+ՔAu +!8Gg >&NaG>7- At]Q\H%+I'82 $N$1QXUQKl:-}so˖6g흏L|i="#?^\pKZżG!a'AN`L-ΨܟL>}fw.]3p؟ET$\AaDu-4aiCPK-䱰ۏ遤4iC"0lqRFEa"qB,ЁF`dף1baNWuIW߁80нbJ u2Ód0_C+2|/2ZG]+Ylkz/|y\x_}ELhQ|0bZC_C[46=ô=.-0Um?U _6iFXҠcˋa q. &:? ? ]c)J/6sc@_H]X(253!" pw}-h {(!IQ‚.p<X<'&~TUoFh7eZWX/gedp $"0<\Å~y_`I\]b|C!bI0^s`4Sz1O>'hQu|rVTgS`ϊiS!Y@,^gn??g2 'A>- D, 愳>@S2Ph`ĺzTgwJbnglf`prv|yc;ڋMjxpF^C!`:~i~>UÅO 4{)9Z^u;_ZVgA)X}ax#_O+7ac܊3:xF O8,heL$4JLg~h隈ig?4埽(+{MjcɌ!5>MHC(%1_T"2aG^3n1dPijRB.bmU@ tU^OU{ gn/+^s_<?" _񿰶QˣjPhRTjG:n~jg`&F,7 q>0by| O1tEkŨGzAPٜ8"id㵡oh"'#`HqŨ`"фzf,EGyq(X?]Q N^gJ`8 PSfx{Gƒ)pTuFP 6]0!z0#$E͋c3 8Xͬj[M}FT{zN#)tA $tA臇G/]*ƻM5_W=61Me^Ujm3ޣo4 =)1ԾÀ8 !fPUhaJ\:E~Kid˹9YGn]6mz`~`+!a Wc G Lh S;#\;\?DHu8rídQHJ 7એ*䅅o,C #zr*oKǿ]Z_7v&`Ek>}ag=.}"vI;co1*?a> Z~'(xynV4v83/>߯sGo\8ґl$o RNgϥ&  ϳ1aAg֔H WL$G-lK lu62^ºsYX᰺~xXpayOx=T:tCUn(HfZ}P^u~CՒ4)^#kL}Ǧ=C)7MPc0 ` p Z tboss8WBsǙ_j[j@}p[;KKe% R*f` ˟|rE<w\g!W +yy_W㾠)rkї;݅ͽ"N[1f5#iii?lҧ b$.ΙP@!@q G:ޮs4#O'-!l? #iBh1Oj,(u[ TR=ZS"h>b'1?*,ş6N#/ VTaHf.VaJbGB_ L zaT{Q(P)zZW@YIS* #e^Ȇp|mX3|Wp,v5}Omt |!`vz tE.FH'H˥?S;+.Y\/%J yαYvH6m@bkMtcр h@QDq2c (oILFȢrzQ;d65NW6(=.itnW ٚe#p?'_I-M䗆Vd]Q7zFǏwO]8azcWn@XMGAИ>q{D jag@+̾e됔ަKoz:=z^a*yPh'B| o켑 ,dc0jzj6ZoRću=PFVwWe} sp?'ls{O/QYޒvt/KϳQr{WJu7P!+%Vڡ3Wt`62szЙazeBoI3 8 SjeK8J޳?Zt 7rfhFca^S'd`g'Rti:Za[K폺K-D\х_G 9~HP_*wdHxWӨ޽4VY-;, c`vUSaz\!ϲj᭧W g~.\ P=[ َԵiR IŐG‘?!jO'eqIG(,E ZĔR63cdރҹ?eܧ^ F~(?yj(8K򚠪Fܑ"U̴^5\E AL}8޽{_GO5^!-n]|UX%5ƽi'j irr-yòogUi ?uJtqPćP]4Ïg+@-i9Yq1.D!;9T<{KWDQB}6 P& FrzA(ŖD;ڥ4SN[@Lt|2VWTm9{\?W@/t>Yٸ?XzBIY`iެGW,%UZ}[jhӷqx&o4J4~l,z^Ե1m^|Ls@ >G^蟅M4`+9R8Hg[Q/6Le|GTtsD QvIegw:ybJ\ƧJ]|e]ƛ9r}lu.kwv-?8w w<t/O:&ke +CuzXGz~ӷ fMeDOb=4NJ_ *q68J"&\~pҒ#sqwf\83^,z2u747Uu|^H\^a3ԸZ\E@*n}sFOAz22^`ĝU֬zB=+19 G`O ͔;FҫdԟwnאַPJf3@jEQ1@ 4& S@!ak6#M,ˁU o/kowd4j@ͳd_Zꖻ\}-T4}o]M_U5tLRCk1B9Ye)FƮ%j )uD3b|bQribĢxS| GS{GK<ߘ/F@/,# kM0@)9&?UOс*\ /F^'/l,BwGc9ŵbղ[|g4^Jg _d1E#W7y~ޚ30{N~YWc3 =?lhh%D(5ԸͨJSg bIs4;nk@@яE"RsFUzIÖbǡgOFQ׿`|UD4~mcvV8< `c&B#:i-eiνʚהx+4ڳX1}I@B0ez{gA^JXQ*\2$`$}q1N&kN7>]6E/݃^ۏ7-!zu ҍ;kbyղItX=e?#}f/b)64qz8iW'?Ǧu%Xy1I==ƃweK@[K)x t+͌gH׷btM dE Bֆxh6ӟo\^4$\8 !O7`JL—eἪ sԜ~0_Lj_/mii#-,@C1 @X}DS:/Y:\&Qia|@mnf {LoWwG)_8_7sDoW/\\,_=퀆_i^?cL}bߖE7`Ï=!VzB0|7?q0Ņ/Llh㭵HsAڽyx~ 9O#ZSdk8+]_Hժ4Y:^w꧃ z΂P_X7o%}qEɖ7ch-%苐Yhk}3&`Y1;7~ )ŝ#0B )K_)ڨޫjflIb(v+l5UR|-N %"B~ ρFB^ *\8:\-8>U֬$ ͺ>xbn,˞9,8KvcYG 0r8X; ?gaVɊhqsPlq'vO ~N@/] ˽:Wp 'cR0Zstѹ%\fdYz(0~Gg_X0cQ#~IXQ]py1I=OmUvu{A^U꼇/Y/8>71e.!^7@kRV: ^prv x 0<p߄{U=}`4Xkꗋj }n対~0|c,` Ӱaȸt )w$b*OR4Cx>"͐O,xXT<O}rjdFiy){ӥ<sq+2ƟT{谉"DkFHif/#5ftZa4.=On7=ʑOu]Nj㉀ s=]_D:ڦ;|j~\:2CF珱~j`0}z$3/Ok2권δw1*s1n^xa7(Cd1'S?i1)b2ȵCd}֮/h?UڬkGRYETqJ9i_jb'/AFYqSsC?y Bv>Z]<=Vɏ67o'Gnx`CM+j5.ER᠞w1KBIF*10T0B6O㧡:t0vZIsy쬲f5%x+4ڳ9ꞥ.'T\ϳ~|ܱfwC+ fxJ2J ea"6B:+ K`d#`5D?VUvZ?I=m]?Y/8[Z㕼,i>tzas4. :[T- cV$$#ψm|kK;s^|LR)Kmvkk! >Ko#k53@^Jxlo>_nhkZ'}_'{!C1J~ / ﬏/ΚZ-3Zj0h1|<0|a&j&j|v2GJsMLgbSR]ycY|b!ҽJ3 8 SgZ^?i %ti:z܊wSԏg7:;``[+a*fW^UOB jwVU`{u)*㎀ tY9 uQ6,( FQjZ 3#% 5G Si)]_VK>wl;N#H1DWW^N˚׿_,K~IB/ONCiPh}u{ Z"j5jYj`0*\>.m*G^pVB2TYM?r1}O ~wrW>|G0k[!]!۹q'ѩxܣ=g `6z xO "~mXD= $, Z , '˴Њ 4c9GDJL]k^wF3bIŦ?Ң$ȥNu^G,TX)P WϞ*oԯ{zY}7x1Cc: 뺵W$=EM)Bw/ [s/B? Cg)}}C4!(j6;<ؠZJ-TYJ*s]a{𞐎B:{RCqa'<Ax>`Vh3ղFW<_G x[6k7ͲFG5:ӯѺ ]*_tzk4 b6Ћ-pVl =}44Cyl!*Ӟtj]È/IIwe@H{fHQe=a0u#9[\q]EZ[4dc? 1atp]>B_k]$fYŘn0XP";Gut?@@)hJCG߯fhU@xNV5}b'*!tǘ~<΋j({MAw 6@PYypދ^ a#>&f&Ǽ^_{_X(v\^]ꍉ5:C 9mޗ^tJʸWtl?)uCE<.=5{٢'L#O#—N?DLӴaʢ Ñv/tVx<"iR?TR7wQJ.z0mEu7AH 濬D9 \|^Li[r S+?.2w Gqz8OHo ;:^0ha0n=P~l.HؓJ[nd{>T 4ꦄk{:9EWW9\Gmw$0ljb)]O˛$1 gDYBS?̬" >gK4lUebAa(;"M?$%0'Go>U/坎LW|ooG6[F~]/:cX=:ztf4a63=4K\+h)VwVr K ~G/a@xE`Ɍ;Ć NwY.*w}UҦEWǾD^bM1CHhA'ղFW fn B ss("`:~O;jyxcuʮoYWy4k,ՓW+`={~sx>܄@SFIJROWܝCK걲*az#CZ4b8mhu G7 NLDA MIS@G`[~jY+)},.u@#JC F L솇PiFuyϒTW WE ;#T06mMwGj@~ }N.7u-zA7z&ec? sQxCI;\ ֐75}(XF_~D?muk-M/Dգď7 z*G0 c SLܕ+¬m%|Z0flF/-'__-w/esmN$ S]/wcnu6/1Ϭ}j86GIg7 )w$vr; mMsb k# ᣗ!}1K+aiH~_CH2CTsuOwFav_\=~y Wwd(@z-*Y1" .-b0+H\+b*8EψMPlbyML#$4YOB9̾{(P R|a[ƠP yvC6" بnsBU{eV^;[6y=.<ϧ:~yHU0ϟ}? mK# \0 iΟm΢8kWdlXuiVc: yjID(gȧl#lymRV0;XF.* 0d%}*]=EŸ觐ğxO]<cyq}XFz!J^2d{[*Bh#y %QGnj ޓxArUl _z72F?ճœJc g㿨K6GϡƬiJa[vWаfn>SCw8at<@1iZ4C ΢IO㧾G{c@LHi0)oЩ*Vǥ͢?k\z=p8bA:h,c OjǫK} xk xwwSY5w4`bIzĥz拓jTs^gɞqө%ʘ/Mx>MI4_쮷YgU #k䋚g|tCyZ.g~  0dG'o`q':jA"{ BW=Zs?QqMK~|K}1]y}}![8 Κ{TiaLPX>^-TV`IBU3E U܀MbzlRO~Zoucs6ν{Sv v6 @_4q_96]?XY$e}Y۟QC9fELӴaʡ@u\?-&O/SKyu K- i/tnQڃr :~u(A:OT+f/7ǡlC0} H-.X zL8PwC7>T>D=+/ ;T"0 B]6y:@ug =q́,G?b9&Wf*b Cf1,?Ib`eO8c, ?1T:<۱mO|c`/AD0)rKЦ Ұ#?oɒC c bO&O%fVgZE16j3MOjy#XI1/ 3o=&5}Cޯ7K&!0mf XaӇba\;)%Msy{WlfxPT/PN0`U%I4;앲Dd ݗj-_sUFAWENhM*!<` |!4+bMS7#Cag_َWX<4qrݲ3/B-=k>PfNoz==õmyw؍UI⌡o[AOtrV3TF9 @(>њ>IddP  Y6  ,ɿ.#.z`5Jc?Oeu'5} =r?qÚn$"Osc,^I35ZX]# 􆲏&D?3"'X.ƨO':ŸV)Zc=hT> 3e6&`QOE/b_>^C չrQ5-uYL^ :eAMnV\&j<>MXμfA=QϤ){=+!)gzF~i tr?$"Fpr q3mbI} NM7$աF-A:Gv:1鳫I C:NKal_bK$~FJfX[ ӷ ]bW|"i#a0H0-O@Kc!*H L: ?[X{S.OK5!=~F@D36)~,lfywRR\xﻝ_J;LʝOֳAwK/`iYFwLhKGkж6}WUhth$6)|d51CeROAYgu@^4+ ШD/-KV; K`'ngjAohxW0J(ޮa>DOC4t_G2u燘0@g],$] # Ͻlxl8Pa݉sDƆH` HwRQXͧ;{f_?g~La4/u帿{c#l/ol ; c2tY̫j!7o :_/6f"$#DTyztq#L~!m,š_S+ 3?.璬CC/sLl#i>yyx'm%*3sfQ&YQk(+M+O% 'KCjo_,wk6:} 9x,Px$O%Ą.sĞؽ+cxI?-P"Xo_[Bp\r2 L靽1sSB?NU<6~1k:,KdM33 [",'L >R3zzv(Ə@Q:k=Ѻl5޿) lC$Yp0 +q!p拔ri:qǸ>ӯx6+:ZYo.0$^=Ӳ` 0?\>熣) ISuS`nZhSk/7冸C#B9wfWԇMIz ̀#DMc._ϑ_ M}f?&(ґGVfKy>Y]\o|W_}K+mB5'ҫuncxZY(_\`.?(Z}X"?- c .C(9M419MIIwesV揵3&A:~e>6a^?s-_] :e : `gJ&a=NJn+ik e' ~&Rc\$cHH:n71UGмxp6[$<%vbX{\fyꦓ s:˴߯?2XZ@;4= @6&"Yw6lW u+ jOW˗gn0iܗH G/o/<_Ϳ3V_ɲzkݥ"St6 b<4.#Gˇ?xnx$vr; MsM`z`,g_Pŕ2֧ 0 ^y8y }?T>!`OȨ#3Q)-hS|YKMd(i%OS?$][έ6Iԡ# !A2XpDIL](S[V%gW&z&S,_K|V*'v~ 8Bxݓt!;Q( M\qV\|X`:PaqCʪRڼ6')&/iX`?/jkpk[o07 mѼtu񅗕U}-pQWh{ }^,4 0"[4c,6gi+7*+0\0c-_-K#$Ҧq3q3pC=^g7̲E~eC(s[.h#T橼5Ŭ׮K;&O.:x6 x*'[dx؞\\.$R j*1=h.POα{/xlCt0~fq?AW*--$5eGEYSq .)YVXX$yabh&ݏ{Z懣z+ΘZ0[_~ά~,B;%LӣGVCǯik$쫧ϙ~+ӏR+xw=\i=Ri1~^MD<*CZ$@`,w'R=OP23ńtќNSbTHxt g0‚FVAW@UvFZt({&ONX[?no_~Go&YxF٪zsX]%>> g__C2fGh&ʘĦ#2" j?q5Yǟy믷?ZIFib<4>X!cq[̋2b9J㙤y9C^wZn$>/K9_X}dξrCEiTid-,揅? ?+[pd?=P|n+0?1-Ӽȇ!fd  Vyv8͋OYHA*c$] f !M-@Y#PK!"fb wjqpvܼ*~1!OjxY`"a`:yauSiAIT*.m+??I:_]|y[ռ `pb%1!E_hHcnd?P15C\u%ѻ_Wcyi0VC%2t,̃ðLSѳ>㿡i~QǟÝ!g#ERw-6AӺUuOU61]'U cfSE8իlBSKYM|Jui[YhEHok),/ϱyH5$9D ȄƄS >t0Z͖&鄹l5t% ..t8-U{ VhO]Fh|Dlz Hj;L{ q]+jEVcZAOdLj*q4H]Ӱ>G2c2dwyYte~:π O)\b(qrХ#S6 ;Y"T4@ck~N A(7 I2"_8׏eimE4X|k-k-`)itP-b˱e $q/nJE=+7'ٿ\cwnY ͫ_zlD<[aVܼtme Nu4F 5:ݪOkbCG 6\0@2ko*a >V"C`XP ?BDqghO]޼2.̼Q{4U_# 2dJ/ 8PÈ!=IVa(X)m؏~蕱 3{}N=['e4ΊKKkq^c^r[JϬQOjy7y5[!"P R4|1^=PǷMn#l{6#Y {Bj(?7g_Xnbx?.G^!iZʆ%IKdk40/N:1Bk;>rH0u5*5SѨ~~Uͽ90x zvQj{(Qpz;:WqiwT&7洵'~""Lu%owس0u1FC%W5!CyU?hHg&G _+VL:b߳6WW@ 깻GUཧFz ڍxmT.n,`7pqֆ '޾6LKfًzUZOҙbӒtO|۲M{u?c=B?Sj BB1:k3SsUXNdb| wE;+nnw@{o4uCEYf; ,gXBJ(`TK@~V/Oלi.o´eQ6 dB=nO[DK_/](nbE n[[];sep^W^譭[#3\S[vmJ10O_ xU&uq!!'*`F~ab{E3}>'O_-<ӱm|I#nCsI?vGپmc v ^;6?\FIip#:Z2fiO#Zw Sv%+&|zu2RL؀fҏdO Y3pZig*/Ɣ=RuZԳP_XjR:@d镬`{,h~WF_Fn3ePj+79sTz7wsD<7/h[I`n}!EF:wKxW@l~W^5Z[.d#i`OfW.s:{^S;?Ԟ_^!Pz^fj>BvykƄ) Lg1I=M$۱Yer]:,{F?koi!gCGXF23\5]!C5uXuH' wֵgŝTJ?kDz5^g]__O (Qqyńn[X"A"ϴpF}BJV0w`Sa<p(P,l.SB#}3?u {qp^JZwqٱ狚'za>7?>~HϏ[asu[Q,޻4ɲw~U]><1d4j!ѴR[} h'3}m70Q\L\dFӌ5|]}{瑞YY}s##2{UTa3QgSckydL=qxkķ7*Y^c:>9>?t5z{: ˉBS=WN>n,]w7r7]yQVm񒉏ʲ-ucÝz6A.T>}~'H HLWZyh̔0O,6?j)A!0aY͒? {i_pOJQ9nh 4j`PW_\ewUDhso\Z)@/4!;Oy4ѫX^-Pa9Y]=+[, ݤݳ^ E7ѣڷR`+1bp{:H /(1?T%@ZhھeBAujU*\܈wO/٢ݚ+%AgzHHo kulɗ6 7POglD嶾Mmt{LRg ]}_.S L/i´=xQ[yVudF癮c? 3gEtT G7y۶d9񻡑#9ͤXG۟wMKThX;OHhj>/VKiPq.gb'̃5mͣqaיӺ'mUPQ_eg1\-|#([!#=kCyiX4& CPDԉ|VD] +sS%dtosw7vScA~[lb۞cc-R?>0#Rir̢?c_s8o'!%N<=|er*oH ࿩D+uqbNډ#^3PJ|ŤFHjB4"øMxW~&I^$Lv^|yO.~=+Et/\3yyÃ}'V5 ʯqi D]cXaxۄDwGwo=# P:h5돊&8s hOzm"GBb0u;Θ6wLM$Wq{&fW$swd@uXI;໪u0ZkGNoF;(HSP3.G 1lqU[o?X;&GO?[ pM_u%\SA~oO&w}p7\h:VPw z6CE4-Q{pê0z?HM9>ye4C{:g7L:{M,s'E{ӓS!L=-s(K\/%c~^؟B- mEL,!D69MfQU8'<9 barCA]K!9-v?G /y+eukzwMVHXm?`^>sO=e+Mq*fV<-f{--!@KmOd O?6{_hE?vW#&n)q:nǰ7 _㵹7?1ᾉ0Gf,m}]EZaթrqqS' ;;#Im5?N77rXǘa ,P_~Bs̮(}&X%gȻ;=fmw靣/ώUY2~w+`oO4dc2dm|w{pw4Zh0k_c:-eϭtIZ@kpFqÜUoք=w~iYL~uw8ϜO4@c;Vg9k~f?u=*o'o-#|hFӊ]w[&ZEA)*o /܊*KIS57a%(y"B ; g =^+"ĔK~C\=ݽu{eZxf3i64H?UkzO3:t]BCMwDTӋ1o_ _sIСl+^N*^L,v2` ^F|/U P/1_[`{R8]^_|g??N[W%:RݻwN}]):Vͅ#4׼wKF3i٭4f6=Yh+=:CfK4ؿ e'qo>ޘ_aoG4Ȣ>;[\zÖe ̪Q.ے ڇR1 mHM'&`hR p8?DHDɽ RQ 1S 0xH/?ˍ;ս˔9q(ەY7]L'Uq_=  @Rӑ?Uŭ.ϊ6ֱU7L ñpЖPj])?]m VLnxP62*?eXu!L\JKp L/6u*{.CX K~s^Q0n2WſϽXW4kp]@Sj+3ۃ&83^VΦf3O 5s܏ΈU: cc4HQżTO/?9:/k4a\&w4͔rc|݇݇ЉZ AUZ{]4%ޑw|qZ|zc{zT{fUw[tȟUƞ6=jn`h)(tu#:6ʎ~ zفF.C>J:POۙM4:}9XeJ`L2q}wpjMkd:l=Z<־YFX)̛-asϟ}zT6X7vs/?]erJDWz?}=tKm?c(,EacQ6? g4Bvs<~HƜo(*^FKI@ȩlcn4-*C.28 fJԊ:1v5P"fo 8[R ʼ$0V)'>Evv&C:)ñnk3Lle,2 N0/q# X)v.8S~ק[3ߋW^%zW̹]9buQp=~=ua^ fqB{2NsyЦM&tPjǏ>wOǒ|cEQ;S{r2< ?n;Ӓ){։ & Xuoa}c>e3qf/ߖ?x~ӇBKRyV'Jk=)=ؖ«'>FlK dVKwWG9%1}Sj 8Xx/AI\^֡fT߬%z]wt1'*B4bf47PUMj6{E0]ЅpL (7Ba8rǜϋ`(J٩{|/gf7RBYj{ٞPq;^Ss6Ύ"N*6<~b}N.yR)Ko&h0 7"gMptg 2dԻ`L=]PK<ԌI5+-jϑ/u)ftGwnGXWzsۉ]ivtb6wzB'ZBjs[ZW~q3m"0 ^xXTmKKj ϗ&9#/r%/װnj 0 ; t \?W)_OmY29"ZâxW)Ho!7 >:]7yGtE] q%3i?@}p 2KhTmSQٸJEs*\7ݍO*+oN֦>P+>ƿrjV@ǒt5UJ]}3ܙJiӘ%P.M8L2{ 3zlVX( l(VDYeJ4eg-wj5Mx^Y]B ~O[(1pN.t{l5kW-.bڭeKڰt,ev)%!CUO'84BB~V$,}U6R 4nu7m~ؘƠIf?|_35[^N9/ \IԯVy4 Jt7D nEn $"G6h[SwtG Z<˱%::P67H3ȳo{~ϵ B *P7!l6 ^ 2Ο}Xq} Hka^ -Dz_4Gേ:]tNv/z6s-v6Y!ѠΧdi?M׊k;`]=6Q?@eA_glTkuS6vVTMq;zo#/u^H't QvLX jpv$yg#g]`EudMLX i0x*MIlS?f׹V[ZZtm}!zi8?0_thv3eg1s?G^fI6gD{W˞c'd7~pǽioM&>cSگ qNVƾ|C]HG~ j]@Q,eJ`,aVѸAnET{҅d%Ka@MX?]%4NJ0둒OEViA@YRnleʄmX9/Dfk)Ui,^TC6֤s7*k)cYBo.D = tx:3G) c WJ~r)3Q2N.`rx ƿ9ߖTWlRnJz^Xgd\πE=lҟ˨ pN*;vŊpI{8]Z`t`wn\<ƪދU_gSxGc0/1E&]vr3kZǸܾu~ѣG_|OWJh-zJOp)nҵtSK|iɹuR[eAnBu{a^,y-~q?+Mz'HYWikѸ#-i2e_:Em]Z "At/kmG2zܗn ICY# 2jѧ,7[/t%D?4?퀧'#]{[rܕ?Bx_Gv/LWK8Q/罋\9pK|9l2TXL[t'νw>=^>o?սe˦TPK&=p)ũ>UeTFeŝkFE8M|S3Z҉{AM5u1ý yNPXR\h <>oO.<ֲG!tREjHU7C-,ּ~#nGzm \VX.y~Eā}CMʤ6)mwG6b?U>;H1^XBas>g4 T%:Hk358Mi5ZiI~%R6޻ υZ4{sC u>4vl^Հ{mJ ֕k W)n7ᇟϠNpߖ$>;uUP_' !,t"υbNԽ m:l{;F:CۓP& )é$@".$ -5C!MMNjuF\+5u GK}kurNw?D=ʴJh+[\^^o\wSNS?Ρ:XdÔ?0&4^a̟-n>@49-Ad7xt2)pv^yi{Ҷ`<{~g츻yaMm? FcM4p[U׹TɇF C} 1fTs]a]"y^gGz°&[f~g iV0.] [! OE߾N贽~L?|Պ~?]njg@Q 77%N n: ~=DfvDIL=Q:G/XKc֡K7h|yIE<-ٙG sZnot/mG2oz]#?ѝMv ZfgjdϟS-teR;-)/D=QL}*_K'7x~w>#S}ñ{ֽMmqLS>#M-DZ)H.:-SkO5r_aϟU𙖿"yo.点{ `%r>䓭ߓjOJ9Nw=?\ି2J:G{BWuwPD70m/^ /5b5( 3}=/>ct<4%M9'(iƁrZt_W+:|?,=/ِPYcxnאg,ftC6?]ƑtpLH/2zH1"Vcbi"Hnƚʙ-xUZ" {:`&~m5fLÖ:VI8 aѹc Q5(wުX3bC:ϥwp,F) 3q 0|5ˣT+:ۑ?1>ǐVNx8襔m@{@H۔ ^"+ߘ^cRӥkuuC+Zn.JGE=@%anXv '5#5kcܛmi x 0ZB E+,3VFSP,ˬ641fm]nBʾ>HP6 (1m֕碹?|^~9wtGa! W ^hRɀ SdTLuB";+ @V OtJfK+b;uM*lBvW_7VZd*K:5dg7 qQ>&:~8'jyCz=WnH4u?uDD&`jf)i[J$yW1 Ĉk 3K{=+]vI1?>/_k1E+cs;8R9,j%YJ;/e;C<(1>'v@9=u:i `-n;MpIqbD*GpT,48Y]!J_LjW״i|AzL [4"qVآ{&EFNLP^7 [nF\.BNiǼb#Nl\*D#KŦny uW*H+{VLTtɄ<Î\v$\05 *k5g]=-sLtUw]b^;Su Rv:X[W*np.k+7]~>e:x0cgXH +h3 /\1AxCM_9@ wwst֤FT|G|ד9=ܑH(k_ˇ )@>gsF, ,v8Ř[{_]#2XϛIzJh5G5(@KUDC&@h8bL~4C'܌b\$^/:)#+'kk+68]/{Jmp_v|80^+93k-*Rgc,ŀ2=w.T;F_tll7hځcW2~z/ؠ'E_\ /h)aJT]2+ٸ1xUTc%ht`__}|Siu>߬W=ph*%k+c&R5tHG Dw H桟vEIH-ԯZJɩD]G=F/ ns_G2VI<zgYF0 ڙ۹{+}`3+GZ `&o^ 6p?e;WɋtxmӋWlᩜ)ZacBL^'v@[j Dr~9"?Gf][@ zE+ӳW׆7oQ;ެ*RtKW.uw=?)36YFT+MãF{M1?|cMo I9.[7lTX3${UwRH_ .;<&^BrF9=~Ddx.d̫B#G@Z3nö:v na ŨZ8QD@V'vtXv_eZn FܮFI;FE<@vlu"> !IMr=3H~ȭ?8OeʅI<>X}} wH f:pNBcDR= $v'sp. 6>7e9ǵ2j ԃן>lٰ_ުi5:יY:9sļ pxW-;͌ ha)aΕ24]\a Y_<U9^rE?\NrkG;g+Eww~KK줛3p3qna 7!A{ny l]o iF{&d,ף'H;rx&V $ L7t'e`I Xm-7ٚ /?z'O^d؈ oi5@mј'HMc(A `HIF+MSBiNh"T' miCU+J`% '?]^ݽ 0Y?-oSRć(3:(oc Whx)8W@ݗ&ilpjj66!0 u ~ d1@mHBn+б/M{ZYO7i?*(MV {Hs)n˅jHlAv3V D ˑ_SJ}smx&j&X_bnJ6W_'?N+V$0f(ٲlq }vc<#_VX}D?0cHThB$# 0(UK .$X&q; !]~v5)kkkz^ӝL_b>ghgDwnZH?w@\?NTX ` .O[JѴ#2D%Ô zgY)? Nf̼,+VtױjC6C8gQ8z/yl{*3Urf 92wC.F#m@jgtAl V=Q˹y &NLL94>l4=L-UO>yX MZ\tsRڕl-ěZyu;$Ią=D )P+?}P&v ]O ^$М%fy075SƓu;ONy oFYU?V `PdpcRbO~?ݧ^g|%T2>1:y(=N ]%WR~h>q~uZȫ#Ú SeTI_VTZݎn'ZZ+hyW}K̈́וaV\^jSDWԸ(=3 3]%0?<>ezy>:v򝓇JA~W3ウaO#̳ N`|A1|_{;ԨXjL2O44C-} ّ[b= \B̬J~%e}U_дX3V7ѺgM$RC}-q`e`E`4- v z< @`h44S8@_Dn;-J[4K/M|33gCwnKZ{THdk%.8 ~m4N~Vky&ȍ׶43,9"Q?.AJP"W)qnuxk◶h;=ӿ) < 2B5xMv2_kUF:#qͩV@Ոʤ_?U.]<M68?8?[}u5N!@})`o %dIԓ7t&#ڮ(eXob ~2րϒ?&RfS>۪KTz=Ne@ae9^arߐZT:wCl\l.Ut2€麰~;+$]&$ h[Vʖm L[*tOQ;]n Lc}4qcyRW?bS"S5eX )p7>~u[rW*PEzW:.r)IrPPcJj~e5r/q8]]vqM` uuCBJ-S͎B&' 360˃.D/24 ?h.?!w}=™կ3/}YGI8ƣ 3|x^U yfxc<}܎bHo:qzgNIGJ n٪6@CXF#er*8:O}#02Ҳ:N vKۣr }`a?x+AwOVFkJHi#ttxLBQOqP? h30-pC@ָδFw{Y~|i3*/'Ă>H%}$N̔ ?yPoE83z?'|74WT?xIJO$q./pARjfR(r/-{/K=)አؕIGSL$UĕUcpi@+r|Fȏ'Zg ͆|8BtxЇu,oh*yY lu}{;y}q,AQkNj `ZƯTR/꧔;~;9~\p6ڰSuſ@ߣ_ƩRЈj0=@MJp^V% U}5SO'c)ڵt"&W4 T,+0uP~5OT5RUK&F4/*Z+q&X+ _`"PLfPՔn~_K#%fFgZ /y:}gϠLeK:{ l9tWB64c ⱌOlR0f&i tO.įrg]3tfU?/yMz.|8]avs]%na]'?N 0٠:퀺3 V#(n0+WKc3P% "S V;<Y_\|gQ't >.TA`!G i3^9nk1a&Ra`&V}-kr/̢u'ydΏP7|Rjb ļ Y}yxgn'TΊjee}}bŒc|?.VH#4"}'F7GX?-wR?܎a]mDwîݓ0=ttNtf ] ~o畹 +X[Xꪴ5i]8iWڕ0~OMQb( ' bޣ;QI @#N?-LfSh0<]~_mG}ٯ?#|2C7zפUq8X[ @㨰aN-L H :hK4Mf H̞hA]]dH~6,2`o{C,rsҐ`^l5EX`o|c9p0~||f繹сRFM~/B2(_ \{D̄d RPD2ūh%tyW2B `ȼHwsw#&XUp?Vh1O.~nOF0?Kcn=+j 'ypڭ rsZ/9 |rkd+'\tjS=3Vo@킖t-1iG_J\*;G;1ƿ?ٯN~h#; _ j?EB 7TlG$b4H(g",Om\ͨI%Ŕ[(C1 .;KЛ I`9֓ߑ䁟ՇmlP#]}E1rȑ.7Zp3nslxX ]/#_%Jc/4( ^超JuuZ)93%R PHBՎ {8gYgR?(r*zL>BgCYWr0{fJp${*"1ZބcRT4cMzs 5լ#9R7Mnқ7i=<4fY[W6h#} >3M nl^$=: +F|pTXpPmrpfPV(Rk{r7S8?ӆٿmmY } ^ {Acͺ?}@.B#;VOƀܥ_ Kc/w4\[Y΂{O͑#hp1֥8,S)JZM}jzU>ĥ&{@@IDATtMzE%;> tTMT\M f%/vkT@U[ r&XK+4FֈhhsMy_Iޥ m%hv] WK|o{4|VihnKֺ?DQ}]U_!M,q?Gݎf;Ew[WN^Vky-Dk }UhAT l`}|R7YlJzD{K?1`5&r5]t8sH{㥑`As'KYGcJ^eaK;joo6>?ާo{otsV[-vˡVɣ9DZ c$!7M[54$f EyUy/C5ɗs봝f>}&ng zN)b0&[#l-EuI |E`ehȖd4XBI@T͌?濛N&?ښNwO><%,O%FXF $Tbɼ~Y Yti ֭zWx~:b ~}4OFoS)1㷋p nY x5˭(q;~+ĩ$N0-)6> l4J)] ?Q.VI *IoUIG)# \W izcB~y?f*b. XB:㘻AqG@ 'h7| m}rlZ޺ (, l_gAfdp_ws‡lRiW(91&F Ri:0 7 RP:.tzˠ2ȐgT GRP,Z""-n F鞺 ׆w<5حsKJG u|OY rh ׋@+FRpdSBz[o"v_OGZ=٧tgcP( lŬ?W)M*)&X[7 N^Vky-D[! r~x'9bj,v͉돕lsA+=bY ,ĺHj2wM]qLq g=7UL}mR' (pͭtf_C-CmY(dC< v*B;۪]Rp3#y[ E)#v+Yv^qr~~ZHO[vֻXW" ~#pO0Y)Nb}~bD7hE<_En΄JDBpD@ I P<#^IW^ԯI@-2u\pdx%+қI[Se>(݄] d1KߐmmiltV[,H"֐J<8hB.O]%5~,Dݏ r;Gx0S)~?X NiJGM AE#HI RTXSyW/Sԩ*KtʄUUK" t~SM} ~ )HU&/UTH:DJw` XT"mUC7^P6!_Ml>˧" /Y'ᗶG`uaor5 ǔ[ҁ wx4y Ǝ;RHm)`ۍw49[ >M߿ 0Rpg R'-<. Dr}xl6Eq\Ù.-;},*Ftfxg_?${U$b:@#SE;}1Rtw&ԌD*l @uZC+xIP0_H? 86CM7BѻLcYz&6WMi5t_:yqZ##E!tK@̹Rwarh)u# ˳+R޳P+uK`NlX =&u(tYIb y?+\nbP]N+@ %N&|fskSÀS`*.hD= W4\fݔrӟMmͫX ۍ7TU(.‰kw_+RR9)q;AN{E|xɌ͐ox3ŚWQ~Cq#nbe^XďtÝa=N QVDw,=ƼYysng/ؔyQ?Gl2!칖q_ P$ Mh$7:Hנ@?ϵNBE@V̷%P%Ha"iP^-ջu.v[vRn%Y^^M#?7r@`_VP2-S:~Ur:֘)Rf)#N_y3k: [ӃK#ݗs{׃9nj !:T/kq@t#E7@pz̚] ' ɦ"w7_;^/=TbGZBM-cx3k{ZO_׫jk2Tq15$^@~=sljsW$_z9Pz0x4?{; !n`D'Mcx;iupMl t.6 QBCsez^q\3tNP,2+[MP%-G@hx ~ƫ EO"BV5Q2UXKߠCO8EW-g@iT[KU32T2H׼_Gʓi=s;<~dZ|Wg]Kj6X :O {!J:#aG<; Oq_Hww\?&|HMJxGwtNtPpL7q#fje$Ob9I6!I4 ' )ɡ/7$b7M{O!K- jOT *t˒fE'W5M9Q^6mG3E map;BG^-O @Sik)?"u`zs]e$fLl{ߔV-I9+mprxDUU:meiuL*=n[ Wӳ6@6iXHk2!e:W;v*C_m Щ3!)3ҹ/^ruP|_,G3,RA5_@%ampcrN M@Ņj<Ɲ 'y ¹tܖfZ{,[Qcٞ +2~2?pz@/:͐:=K+am`^%s#8ܺ9v`7'츿r*Y~=,nv[ID FٜF0J7D<.q 2mC⧦g|{0?th:_ t7sZUC1#=:fWNx)tx@s}HH+sUDa s#/tRn/t]f֜OPrC7O⧳{9PzdfFH` 7,܅Q3I&(u|Ab`#%>+ BRE-R]pâw?:Y=q͐ >/ʼsw_! 18hѱ(<.㞡/5̈́{!xb3P湭ͪ1pw&T8B7w3{V)V.ؤOi]]'~zS KNTg.1=~e&a(c񉥮&ho:xk 3[*0$%tU$ZQX~Q23< ! <@sz$jU6e1+Gc8#9Fam k7=(IiaCʅk3VW㥑bdb MTXŮ?jSʰL4ڟ2˘ Z-^NUU|o {!M`ND}MZWgeOO4CgOou h3sg͙g^$vGzf?8 $Y)0-~5Jp P?8-;o1Z&'@Xfys&&XU_Wt@ ŞM٦:֬vij_ZM*]m+u(] X9P0* BBDoo[zBnb)U c!<{Po¦2L=gI(`=KvOvQc`Ⴁw ˛֩a1] _e~XmĀų0_=VW><34Η9o"<~w:nK0$@- W4яnLmB nO7gзcI1Հ67$WoKؖw"`z!78 0!N}&TxRW:k^oEݱ1'|P/XL$` 31e"\JiD͑̆{Rĉn0rLpPo 6ыF#c/7ŇABJE\ü K7ʄ ny<=$Ͽݮ% WZԧcsOoAuW3c ; xɣ^[<{?Dg =U0c"+9UQBJHigI-*(KMi wD \tg[5JypKt/襈\\Ú|<śķ⿻Av)ʟTOgB T5M` QKVwǙFŞ :3ГGh8}HgWr>3$ż4vǥ;Ј'^*jm.;-@)bO_Dɂa"Ge~ f6[-LYjP35ku/Uأuj C=&yβ|6cpݞ>%o#nGwvGka'qoi 9K!As73$hsDZ34?!gKg(!]\w-Iǫ/14^:I+b E۽pu-c@Zgk6K+=rc3$4V5o#3P9 Hp E#36=}VX-Tپ@qJv.Ԟ d'ـ?E7y<~>GO'yem.>]>^7{јm&UEUpzZsrN1Εe)l{_ziqYkˁۃ2ˠttPoꀯPII= ^܃&7˰n#‹n/~a+fe:]pCA$"ž'@#\ FӅ#O {)E>//xğ.?Uz 6[=`@ZVk(Ex\I9dtCN,zGH1U&a!l[Ҹ'\$3&53/y1&Nu٤NI=dN(&SSQO/l d.kpͿVJhʬ\Fù~ym=GEON+֯TX/4X/ٵ.xYa ǡKv:%+[G:Ҵ{bty_-}D|>M,ưξ \jwYy͙Mf7b9Zb{kC[R? cW69˿10?| fmjnu>:99#3-A`6\NnW'-k'^^Qʥ6 W= b-gщQ[Xh- ^:}/`5j[.>b<*Ag?FċtQc"̮RU(e2F#C|au&/^Y@NCulWģ\.|NwAnr"wuh:ii6qD:f1[v׀[};O۲Dn5y9 Fj^y}W#{,mEx,A5e|a EK2̈Ը,\ԣ ,c25QmUo 5ͷ>nq-Az-(;^~l u!}~b1 b" jӱ, Q-ÌM `)j''Zm" }Qb"U$^{s;/mu?tiիٖWߠm :w ^cQs6Ն):Aդ1_ne_,rE}q"ଈo[6{|#"1ξ*@-=<灢 Lj}@.Y3 ,5ܾ.>ʋXYN/h\%#<6wRȺg_0Ɔ;y0JWEV"E,h7pV+PBٕ]+jEzvixO\0G_`^~|m/b{=)p;W-۰ĬMg+kHR&%.v>6p;B]N#Q?/\1݋`B#G|J^~)%,CNۯܢ"T4t tE_ujЏo~3H/bI_U#^}OikOK+> +,W6W4|ȵ^Kxw?s&rV@!ms\nգِ¬pGC?5j-AMa&=+[Ne,8?LڱB$ Y6t8bQ hZs@V {] tг)###w;T(opKAQo'ٺnNbl33 t bJD|]2>%dS$J \ofs*:Zp~_$pMN^y.%V~X'B-}3#\se"=BloGev" }JOol0?i]N,.v|`bOUhTVXaGnG>_s yjx)l-xӦF d`6=&'G_v߆a65zּiW6qki' y J_ZgKe-ZPr{zT>9=XG+i- Bc簞vt2ְPNe}ۢ!4.D #{6a{#qy;[wlm;1)ϔiaiPzFbɈ ̿CIQS p(}ƴDS,CdN2p*\h(17F0ϒO$ {5N QȤL9Ȯ|Dʑp FY.:\JNoG]5Ll6Q`r4MyR(q$Q!%?OnJ&y%lؐb,/-WyG &T07ЛȈw qCճM%\2(Bɮ rw^?xD`p}u.RVq\T7)nu ~0'{nerA2N6 ްEt* q ɕ*<],J/7T=KtD?dq/mQ)W fieQQF OsS`UI=x,~տ]k;0e[r5:HYZ)삦 u ^MWT5}:kT̹' N %+טk *E|=۵#3H=P |w~ArӥϿ_oC~Ƽ7_yz"@;Ƀ@ }2g ܭ@:Cq;U<=.]Ũ]2UMhqYj\'u$7T=uEg (.*\$|N `?zr7Xsr:tN[@1"=}ik*umzIܧVX Ҵ9 ;9UwlSU赟9x7MYyD}PE_0'خS[+Nݠng>E/3کqjGSY#\ ~9 Ր N6&ώ҆t {!s?= xHQ=NJ: V1*._Ȁ"MىK `kF/P]͂9ܿRĠ>oTd 6>[V1LW򛥍`M AeC-juSu~4Zμ*= {$@x䵦_>VݑtS}sSv@wX{Vkjlj|MvTlMf/j :#pU3d|W~d4gw>^f'Ry'-i\Qi<1Cz%HFKS*F/0=((7;ϩe*&—mu/P-DF!#ʜlmh= xn- 0q${#(''W/_P0.¶-w۔e2T?kU1|pgˎfSI#~o5JxQsoUPDk#RC8C W1O흍,W)"vy1EX7(%im&@mw$5O۰9Ј8m謰B͵bS|b_35V 8T7pv&!PW"Sʅ^ZyDs~ջ6 .bn䔐~ט1cm2؉R1ziVttӍgOh|~w~~S%~:VHdhkǽ|Wh2dr&Ԏh(~SR!uZAILk[!Z\ǝD2h&`fXiucFXu{k-}Js@_"F&kƥ|?:_}O߼|cov|PuU0;~͌ :#apH[[Y sQǡ;jF6z^m}΋@g?a;fįBCotw@܋`:NV7 \/*C +.9JA#}I(̌wSJt].@{.CdN ֥Ӻ2]ѩFgm~w7aw9;fg_iZ1he=d|Gӷq*2Q&C~y=*Mf#? ,#{[ OžiCıNF0J ~6RsOAz t$]pk`@PXVC* paA+ׅ.q|]"%:E\>8Wxw{U7]?Ot]?}:M(+KV8Հӆ"Cr߼xf$ώmء7hΏ>"l)uIb09 eܯQ WB]HP\k9vYn(£,KHU-(񆸦?Q.:Ts]HKQ6/^t TM9-tM)p;1pgsP)!}WG:ܗOi,rY/Ygw/2x#~ֶ+XaiS9mvm5?-#=6NZP%[ s'`{ "z@d<`UOw$7i*ulYr.U܁*W* X? l  A|CEx`"I7kم !@]?дzk=d5PwUyYj㺗 (Õ@"{6a80s3OFi3. eLqro#pE+ \H }HWLs~-<@u} v_:ANrel_6}s8|:]5d( %@8wbT[;jK^e*FW{yG( ?Bh6}(v8 f4'cpg>pՄA' ǫ4ui \|ԡX7M?<:n"F@NܑM4CO5Ofa)]L x}(|yTO;&Bi{r_v4eHT #GML*'!XhK \k%hc,FV)F2: ]H**tܪdqanC\Xv;x^^n2[obkGD=uì["{mXobZIpymgЕ 3(DVLElGN%8C*8 .ҵš6|(ou l)]x7]ӆI:wׯ_횧;Jς],{QUTPS2\e5M6;h%F*Q.8å`+k)?69"kw +*$>!? ҍljsoN>uYqy/oufV@J PY]|": Z99|̻S~4*U ,L,nw\*μ]<O:m3a٭ݬӝh ?ڳ4!NA1F{< =b{~: IۊY/TF#428"D9CH_]PvL%wWxws*W_tFCt-Z&e 2&XP]UW@aqrnG۷GQɯ~qB~ jϴ*JXl.}1Wέރ661:jYL_pL}TضqlY+3 %bKF5+sx_%_Ǘ@K3JRbLpLH n@x-F&TmQn=0%HQ%,of !))yu|]\ݫoCףd4ߛzn#s nM PY|C3vQ!Ĵ럢VT3\7=,9_ɭ 5(8oIJEtMic Mjmʇ}PGp V&g)aQДD|W.zdM YCVs|y" D (肻^K =r.+3m}~l7?i##oMs=)xk ` MNsl:L+۝4p,Y =y}~-䃨&:Yk/|[ 䦾PцGF}{L5 Q?zi=vMnlg 䥥"F-U laݎ0 R#0]]R76AF[%^K!5|`4䇠Cy_ce, gRv]P>w߸X)zͮ}csH1<TIꎦXwT|OHՄgj< DCb@6\P]Hg^wY#?#)Y{kJuQS8G Pl| KaƆH i# .tR)eeʣ (]|dSgF3^-D E܉Kk)5RX#1Ë4qpm1k ץS큓FQUN(c9-X~u#5u2 M5\ˊGiw*G9oaK>c`4 @3ֿ!H[\W ;!I z46ԗw l=Я<8~B te =C쑆O`[@@~X7BB\cT.?f$a4[5,O!Rm H(M!*]\:lOo7zfڟNͨf@{$L|Gtb& fUr=[[b(ьŌ7?r|@- s; Gsц6CfpϕP<,# / r{؈5bؙhw8%(W@xQ _\.":4sn{R_.uZړ2LS>ټfГ1Po~@6aM.P|%nδ2Eָ7umrNљҔ /-_ǫzWar۸ɤ Og]_0ʄv&j)EZܶ3§Z"9Ra;2-V=p|/ /):(omI_؅*Ewɛ#qb@e@UC4;CZ h8 CEԿg{9g.a9.m*ЬtΠBgހ*G\\F'U# I8$5T3H@z\+i!ǕG|^z\ ='%\:rѿS (? 6C?\+7-1ZPW/>|o7{z6=8:x6[b(U1hL +Cn ^%HSҍ^LaaѢRl*o^e{{#Rc8-oWi#k;pFH4~Tho^t6XD[?D9į? |E @5&LCaaQҍ[=ߚ|2zOev5s,pVmz/4eZSs+G[yt'")#CA+/v|{/N6pߞ>8?fokMev .92=ddIQ:E|6)<.8M6qmtD{DOT`ZSU0;+/*->*# Mj +tO@Ҟ!܌$JF|lfOtDmyД=QBɚՔŠ10CceUS v[v" '",h,[?.SL\j+\;"Q܀fC>Q7\H0w`g! kKѝy5Ϧ*Zei˅/ЧgZ; oSQf/tFK8|YY#KuՕӭ;EN55?7ɛgoV2r> r+<;~9(5@ǒQ@-ML2&Zk)S 6T\\0DIJJAxY@xG <0WB?Ry+jK6QW`zCEk,5ݍMu |sSe2o_l6NMFi=,O/+ް_e65#՘ @5l 1g`X4+wb:f׏F")KE$C`J| G@q| ={xˣ!ےEw <:n#'—!wY|j{SEckڧ#Gw:"l'xf#m}ZzG758y,ʷD/^w:Rlc6Uhu=Q$g#0*:hCw&4=wu܏Ft}T5Scc@}~?5x)\RKvKҊMK QrH3:e8ȭ^hOg`CQZ#y AUMQ%:$$ƀw! 0s怂< }V.+2b7&xiK.ҕXWٻi~lTߦβ݉"k\ Ʊ[!A4tx EhvӬ;=] h^kZ,5驮M(v-`!m1Q"6GڼD3,w46EXN|tm߲nl_oE[8`Ǟݭ`{ј`V0ȥ]ŢmO 1x–hjzv}ۤTkF#E ë߀<Go0G?wmIr'r/}#ot 3R>Կ\ldhI{h_wdPxJMfCT8[ME|[hõ) TzLk|\*{ưqVבu}m3j\oOUyt)+yKk}`k7mkޑ9w{|[hzĺRdU7l6ԍU}L٭ Tף`IWO_Uʸ~nIpͣm @5i-I`o?P/Gdg2GRo5_MxyKi@ <.FZE(5c w{z ԸwK]em Tsbic)szC>w: S{x }r,c(b\>rW;i37V5HkO"mʃ! V M7F3%rC =ZeNc/().C.9WeܛϢflW}e:\]+DhC:S]x5FrVV ]Ga~num]bwTYKtwO}e J-[ycηC`rsN~CL7 "DCrwk6s3 yqH2M0D%vըmPҘmrKaQtUE5*%%]<SE\CL.B ܥC/Jɉ,1e en` 1|B#mty9c¬fS14^p>65nXw:Rg_}17Z5l?ڜO?-=2AMF.c+C5!h{ڼl޽* 7}k68ྦt(w4j{ ֱs\fT^f{' b P;YQWnŹ@x(x*La.u:%==e κ˃ ǡG U!5wƠ/#5gc;nN(Cwl;jtǎ:5gv=ʡ6i3 Z~߱Ct0Qv 55ӒojD8GA7 }">ƒ䝊C=u1FӇI]x: 1+~~; ^hoxm6 53im4wӷMG?>Zt/r!qY @GO~6?Q8LDihFBkzte(Cq@ƺL!: ~Z85C'BSSm\]N^\6F(hu8~6#R[SZHhz@",LX{t)uTg9/UкܛϢq"+ ݁nc?K*rw%0JSt/y:ǃUIp/M _×qٟ>t\8$ ̷jR:mPW18bښ|J)=/E+;E8T1T0tCL 4> "\.&$TGf5 Gi&!ۿcĔ΀gնIjQ5;JW&}./uY+֦fvӬ}{Z=J6'9rHoZy읖m?&^%v;])L69`;9gX˴1`iUrۨh{Lի%h![O#5>18|_l$oT|FKf8&r ]Ε-XWmf[5ǀD2(놧AZYɍM`laqxH\ĆW?s;tmhWP3cS {ƱIotVHmnN~ObWG\•ź3Z4ű*ﯞTNO>PxǓ[ZwF~xvQ(H3ݯcu @+Ɓ;t~mm 'NӠv/#S9gQ9tnN@-Mz+kS`W[6g߼ٵ̖LhSbII{L2#@ͨ9uZ+PF,lM:@Ps: IO* ?MMSTJ !3N~QSBӸ]z494]8mp4$f™wP8Ot_mߚw޲9dSMug|s:ɱfި,X."H~[yp<ߧUP&E?̀/tʔےC4 ,2@vaaW`LA@%&a*+fHM_RMUP^fAYֹv8`qs:D9Ѿ7Yk C^cijr]Mgx~fk=85 ?q{'{^qO?&|Xtn#k_Vn\FVb G%/ZQZD3-yXuz65xJbɏu12r>: N PiJ `YE`F㦗x1e!p<).JH/hF3oK:A^ Jn>;%p[{Vg͟DZَg}m, Տe }vp Ӳnk_2ϝlRJT'{[SS(Utl $Z+H*i`!H^}f\eiCxQ[]4IPF:g6 *ƴ;wE1iFT37DW"$PqaTnnX[O1-N0]W&sVK@&mT}( S\srw]r[g2)Lgᾦ5ϛ7M:)1julk-ɱ'wmJ1iti_zZ :9eDۙWOƉ.o8QHTE U> ^nh3w}s'ky }x /`gs+-v!Yjj"E5?< uFqwMmr 0`.ӆCEי.&$fckęk1a՟0pԹJa;z.DRi.=D\+[4PB7jTZ!\r Զ?^Dk7~c6`rٝ8V:};?>̎zq2 SYKơ4 B)F02-TA4T8sN>+lumUk36! Bok)i .R cr᜜i: WEqF;_rǝF|G9F{F%J a5WJu6Z2  ,!2E*V @npm6Ǣq1w]M滳Mg_C~NA_0[9fn,_:A:467=%b@Jcwٟ>tXwF&|X_mRHo9ܶpJ_*FT1CE-LKougr/u^y`[I'g2T?H>5(Џ-\Kz_ pP7~F9jdϐvbO?>A96{.hٝ-эնbZUɿgLXo:EcQVR_A_c:j56L^=R$Q3|XbA%?Ͳz&_k5G5^4P7G0YQyn:utݯ~nտmٔI6=ި>3y]LP"e[tL D1 />9՟^9yhܶu)ّ~N hډ9QY^n' NvzϞO~ˁT䁢q##=L_;s6ij+!.ӌ5 @FŚ;ͶH0 $Nķ mV~O)D~nvC$p?X`tv(pcl)c&q2ɓ3a:*tSb #] hvh lJ8-矻ˀ|X0Wo<9:M~t= VXS*O}t:RX"<><z A44 'z.bWڹ%Hpns~9zqΙrυ81%tB1"qZ‰FЎ@Ezd [wpQ g0x/s2CŲK$ԫmi\4|?FwX2G4qIJ?qʇK8#Off#)-\b"WI:ܾskN1Ph|)LsA El7vܪl* * 4|0GHe}>VqfZzWCdVMZsRK$-"m P$=9ւ!1dAт- 0V3$bm8kT;|[pܩ}6?y . !@o?S-?me ee|s,k/esSt Dqkۚ_ڥ3ۜMnULD?ӈfSzM9qZQb#ĖZP >hh9%0O5R\k]>f8c7X8Ӛ(ꭀ')%dQzɡAv |/!(M0D[\e/XP iv1`} ˎ ̯ %G0u . Sr m?xi{蜾65̒ջ;S`vi$UVE_sT|drvƯ.,xL(Te͟v)i߿m=-h-O1e؟WY2'}tnY&79>7Qu`ɯGj0Um +j܄ L QAqQe0 ^n Ȝ yح`*?cpn?1BӐu4!ޖPoNwv_M'O$ͱ rB#ru4 o0i7@7 >!ng$I:F]u5܆0m +FeދRzLSJK =r#uh 31C~MRP5*e2Kĵq)=kۥOb㜮V$Xg?SzO3:,ZWKCS'؏:/o9i--'"ʵ%rTv^&eRèX*5sM>mn^ZYKΰ`/1d9,Foۻ&bSý^$Qٖ 3^\fhYÜ8)t3 JDIoF c[*R*4>x8x^<14cT{l|Y.yG?pGMkN!xFCSſ>Uz,gߜۨQQ_S #}jޅ>.Ȗ7'(/vl"\%8zm1l.?-pz oj@ 4ͩg_?ߍD(M^p0Za`1vx1*U*+L`_W?o_M?G̯ 0Wg7Fw|ȓ"2CD!M `r0M"_lzNڢYF3[]\K3 _IRm25v-d އe$+K~)}m$KƙvƲ)`v͘:~M'@Yގ.,f1(6ac4Z”~eJT/0QWifKZnu9 .z4{=tƾ!Gy-)[PO7}y^[}0 ~8R˺?86gFDe&]:l4cr""q\ )+Wׯl%S?M>O^ x'g) mXzJ#CF?ν'ࣻ4i'9#JL9˝Ui^:I ӆ4HF{%yO^*W*dLq6vl5ZF[?H;Ki퀳o Xx+nٖh53B 1gJ:fy fg~e)3K8<ҮsN0_uviF,<O۰QGeizCB╦Hu$ A®Zg |͟O팬 WgSbn|c:`/%~Ñx@IDAT>}cu%Ƥs&%mc>ҽ;hUl+5zJ uNsS~ 5s\n!̙/ o~5qQ`>Z  O'֐k~K^ 6ξG̘^.y4㢠_}XN Ur\>_0)FVʦ2 v:djXïˢ~#8;#n؉W9hxI k೷qfuЌnjd#ڞ6BG}O>7虉6 2ԛ/s33!zbq`|HV\r6#D+lH\n_EZY>/]u[]ÇۓBkTZu E1Cc~*]~r2yڱ>@-J'T 3 1__r ;+J`Ź*©^T UģDFvsj E(+RmWen*vU0ztQSb4*bG4Ԙ~L4LNEvҶ -r{sq|"ۜ5pa8p+]R KtŹQ4vtN CgJN}5(?EW=P{`0c"aFx,BG\hi濡[4ȿ'wŅtEr1? u_}d)( |9~xmϟ]Su/WkM5JېY:6]"#xJ%nlF%& a1;&̶OH5žhy~j3Vx .|x,#f)Ѕ!r*ש\% ^ `EX'(sP8{NMW+ QLSpac >pzɡc :1B6QНRشZOhn#xL2 >.liņ5,*>Q*SoeRyeOֽysꊿBu`E_?7f=OfiH֑U: ع 5E*j48"D9˿ALX~]xX'&zg\k\LX} vhtha5hɀ.l4=jL`v~ሦ۶QQ2MVPL`)otoCv/&S fý 4ec7zSkijǝ:4, =-\݃@Kԥ3t^{,$jINi!"t ["G P>3W?x5h?=P&@z4cgHBB3>[S/gi|0#}gV`66y=!M}mu OďNpFE t"C";a~\> f5]jϲ b9)a,*rƙotyWQ45mNPos2&Z>_q3m 1J dll}"PD+a< Pյ$J9kU4Ln\i%nZDr'.#~vNF#mD6hY*  ZqĈf9#I)+L䄃 "`n񈺻v-NH#eH.hXmX ;#T 5JCㅸAg_+般mV CoF{TƟ5'?h-j̤.:̼Ph-=-'RJDu=̵_Tj Ӯo'pML'ʔ6֭v!Bvam>͹=5 :$;9Z| XNsOI}[T1|ZbO+ Bev4v9&;CL #!PQJ594aN7cJiIBɲ~$Trcq1}"|<",'߾] Z{۶ nkEkٚhu"6'} `;~jGHt/l(va! b+L?qU[#b__5\*XI|[>Q?o7+5}Vo)u>u.=WS;bݡj;gj5O5={NxTx g9s6/ wA+-"*:}#CWcy{Zk֎d|r̢hOlwF'@ 9k$l@TiÕD4Դ Qt#` e@ɷTAp7!$k)&HI92T&B,'_*NZjslf@{.οpA 6Br}uN8ϬBPtwso)XRωP?G[ %7 ~s+:ѹbVD˾=o~_^^o17iMڑl2W;:@'H۱N%/yq|X<  F::/2d:`,5Vat*:8RSO^ԈC\!bhܗr e:a<8-Xɕp#.2rEL/ȟc82l jcd}Ş:ۺ>@1CmIg~߈cZJK` aL'צ}l)[ߐ2~L -;!g:>nYw.{>yxKր/7)wO;j\*3d:fzwfgwn9:/ytHJ @sp9{V^Y`qD*W|_Z8vvVks|qD;`Jhnu&ܰ){ *"A*8\)bf68nL*=_j# IǂY@IzSMl煮Ek--NQ`wglozNciBml0S/T[ot^U#m IHp`A%K ud_-83ٌlH`"lm62֖5Cp_̙4]xgښ1L⣟Pݣ # Fzٿf<%bV(Sk9@ٚ< рԔ=0q*ZM58JQRH;"M#u 0:<(!+#G9ƼۏngBqRfCcF说GR=y>ۥSfH} Δh|;NܰGH3L õ6hK:ks)n;矨f-ShS UklJ.Gca5iպۧ:M]V] yX=j`t&67tB@#Ռo4 5gN"0nq}ݭ@@F2 7"b =񜝻Ek2^aO@joM$I3=3bψʨ5k@w`䁼K@ E /pg>PO<!f0 2$R-]B**X}3SSss wXfOOebBEPEB`\(icm,5 ߼:` oYF)re{;`4HP, ry(P4K>homgbny"|/idU)%WKS1TV;fUv2/vt9/PA>F#JNGn$?Ky1\_"J1.^c/fm-1\)S'U(9 @s(pngL**+gy%-3n̑4420d<4ڀKk*E1N 6T#'>"M0Oŋ7B8 ƄFxČIͦ5yJ*CI[Y;n1{ؒ g7X1(7̂I0(APpO7R]͎!/PB F9T~& ^`GL1It]y2?!Mt. dhF 10k,ڵ(ƛՅG# ӱ8oڝZґGZ⼫QpE3XO&E$DS52:+_k@e؈ i: +|*&w6c m27Cا5ķ씏-"xv(&6D.*Q(^[ g|Ҋ|B?Mx)l}[;>[b>Ψ/c)X`x(1iYb[7G΢O^VR)lljF]HVl)o]V٫Pq"hI~wrg crQ,cG+i^\C 08'cAK8zϾwV(GTɱ*TPx]gC_tk;6nLn|:+Yq d7zTYX%n[ `Dd`u _U7Q%~sT9 r GYGR\ۡ6[7=p^)-f5>CGHǙ_tpN2r0tU/?!rh(`W%30V/R< rHJG}6WĚ\1qZi#Y 9m'7\ re9K,pK[v<]~J]ɣ>ϴ=ЮVwC'>?''Hv'] \տk7EYk2E S&K$A@*= 읕Zk-h~JZm]-B97oy0-F7={EIa  DVNl2ڎ`c׀T* F# PNl+F w48c~dsߑB<ٷmB3f`K8a{怨V+jXO%.W cG1--,P-?}Vh%~o, Ӯ;M;`۴B!AM!df[%.YUM "YN4 h/jjDz170RM-oK6F-,8yI0a u1HQGs5hxlKw0eSye0 *Js@l?sOQbzHjZ˨Me6#`L= Ch4Ij)J Si1uWSGN$#T:0 GՀ"DK90CLC@Ow=4w^hGý1C@ƂI+Kܜw%M,LME3w4z"fQ~Vb.ĎI9FY8yoH깠:,Nm`iGj#vu䘶cUG"kZWz HgSvsKYu^]y,,hkn=wMm<Q<趍/^i2.@iHC5VN{}CM|Z69*؞.G8д|v{Nt{ïKϊ2֯G7)*:ܴٗN3[]DH6AK_>%?׶Ֆ~~jAUL&˘ 51Ҳk ĬBaUD#>9h/7@RhK<%1U?PrJfVG`gT-wj/4_b w=46ɘ*C{sZk/&:60o3WߡURo9VhTN-a,'[aw9fM/Io cK<%wץg@l[7>MNfyg6J8x3s'5眓 - #z-{R-;}Ŭu6tmVOa;C;0 ):c<cy ь"JNI^0Ce{M.5)=FInIkWhd/X,i3k` 2P&>ZAp|UtL'2H::$ )b 4Am0'ET3WVoV5K7X\I:Qe}CG, `QƘFHX ugJSFpoO 3 b[224-ZB~T͵FпO?K{f@j|lM1Q](y5E! PvɣOz4('ŏ`y"fO4UA {!nDa93$1A}46@jBp 0K:PR\+ c)l,3bl[^fCdB lXi@5{>S1& !{ƒ=L,ӣ7<ށM>s*hA6{aIUFd6 vsd?@ #@ V,bեwZ > 2PA juM',/.|jJ.ؾ%G⻹կEK& 4k=Iٗh#>kLNƝ >Zs;# U1 e&>+ޣ&V㹌J5wS(j YblA̐~t~MqF@Ҟbf < daǘCt6%A%uqݨ'oh9%N!JkpղmA{`I4b"@H^G))b3g%|5k[G(F/r'+P?]0&OBT6'@ŠL!qS`& 8Mi9IL? P`lL=@P$L B\E7G9 L.t(Ґ^fIo7T1s3 z\`j@&ǟ-eWRI֏,6믵ن44uڷ޿6TH#MvހuA~?xWCbuTbߍOˆ<-In8wO.Opi݇D#|``F2C=ʦ6)eğVl`nshe',@qEI[T =GH2\fC|/BrB}U`O KeN֬ JS- R^"D_Īw{ k]Q|stOmd 2cf0f=ֹ9 Ø0ɉK鞐e&M,#Uua[fyKkƉ eL1Hȧ.k%e ͞6{_74KU>*rכ[UbEz#`f:XT@L600~9 od&_|'F;rG%\J% OCSCsRx$ /%f7RW.Z]Ǝv>P. V؊afjC]Oi9/$;CL| *@T!ZV!B̾/:48' !||~V113 tQ0h%<^`3y!ޔ#VƤ;3+u:1gAIJž?Hpä<ҞeRC/wA<afڶ0}E?T%SnI8*]dRɊm$1 0bLde ŧCm|23_hHEL &)M@nQXv x%V3v섛6KZU1QLF&F^7z֚Iϟ dL-{g0S8)w33Cp\}tdt#+w߬Rgxwb\oԎiTڈT؀*Ǚ_Bg|~s,1 T|[`@KTza} qGa[Rޓjzk95odum7$FiPU:|@2;^VB€!A@` xj_1 z>!㒊a`FҎcP̛2d #5Ui AʏaQfH  ˨eFY7"d5_A9۲8Br{bG{ݎ +% F)%+ERԟ)-},B .je=Ԁ&ci,CI.[E|$|K=Y3M^T._\6H̾[9Pw<~[҆ƿITO :VB2},dfw7#*\u*4afU}EW~ 'QE;x^i)DOnl_V5w2tZ Բ'lVIK@Qϱ:$d 0 q> Tb̓?̠t%ӴDfЊ]@̒?`;0+~\@0VU\xt8I 1}IĊBXKcm7a /cHԎ) cU1[p:ӐSZkHoمNj_-H2'ԙx 3y`T<䇺+`^+=`-U:ٚf TɺE(NXV6IA)9%`PI\xFSG%fF%IP}KU5B4A8\%h1 gĮ-Uxg1f/9UMy) JVXMtO8+%1 ԍ3 ;u1z}۟Xmagec y""ġ^hBey炭bB!mn=^|}џ?օm"̢~O e x':7ߝdYQCCʷ +X|kӓ!\^}NK5V% }7[RNx**f\,v QP,6qHn{񦧕a P+ӄ)ie;6WğhI}_+G&~v Kǚ-*gbXVWWFȘj d sz60C^B6#X Zn_:- EC]  ==j[w°;p ǚ\2xWxo GԒ#L +<>:lI&Ɓgd=v;xffB<^ o.fYBc2V]n͆EĴ/ilVMqݍ~r*ZbK>@񿠶:pIZ/|Ov:GZh}mj7olaOi`_٧(b`iH|F7cO93ɏ9 9λȆ,i4k&!dk:qOBLTEi{7uf6*O"Lv w d-Fi<5 uzb%јFےY.1;:Bt*s*<*12jyBp3"Y&Y:2Ն`LXJ;3Œ0>6>*n{c_㋰kL<~,YKK=u+KQ&w~o$jH& vX `ϕH͖mJ(uݜU~.S^#r ћreR {LnCHk쫒0U`%؞T{^< dZ ?h PyѰ7{ & 1OgV 6naMf(!LM'J,҈EY-5bZ PT' D| ݙ҃힔/lP g#޳R aX1C+:ZѪڊ [pAyp5aE&]}kOhV_ $ nv BA%PunR N}H t *шJ$,TR!` 3VHD1s3W0cŝc97n 񧜖K%k ehJ L]1oCf-9EQ0n(l s,5MYgB9j||*Fmd38{7S$Nay:( +L*%K3D&`O2i4+DD|M Ќ=mgZGPΒ1,4dmrzGNMPh)G275d&#Tf;Q)M(AX6g5g]-grM-tPRO-eIA/ir*RvM ܆&@.mi?/,"( rUe14/''\ŶU@j*]ʣPG>J zD?{O~rlK6a[R|2.3`7>@7 ޸ 5lhaW+-:(&jGY?!o\I =o\z]ˈ2"= he GrwW74'){7^ @GL!Ѿ)EAH}g`L\@xC,OC%aӊ8SQ ^#KhM{׶~f/x;`M r6IDATamP j)`giA/Me]VR,uGLޞ6ÝMu.%!iEuCCuIGGgmX=?p(T:Y'eC1I-XͯN6n"D$.3oO2ͤvkQ2:tWXM#AS,.$Bhb7/w_-`;c*7=pS0^WRSj{;nBSr_OD\_@تPSrR2o=}T2^;Ep e0J,Mލ{> "32O/ܓ"Vuls&dO (09U¶PNP]2hI`}C& !#(\A }̊8akG+[t3pYc;롈鯟e(W>>O[w%sV䑎!з O1ꃜ ASPM }\򟑎'\V 9tOE>ŧps}J&[,, Jj#{CGojqWC  5tyht&JPgt41xOE-01fDA4H2{ a 0bvZY>3 xFEM(p-쩢7v~ ޺z_%V8L@lCf,a !?^qhVwW W!FlqAk'o5ԟ;'z>$lPF92J㆝E#aqprҖe~YxMd$mk[꽥'4̴#L=XbnRFO?Q63y$0q`F'&ΜTHՏ1rRa`;>2)S0<0T @Gfm!@-Hg3&r+<D Cqjf\aK֓\~ɝ%4ϱϵȑ!` #Q~K[Œu4ŷʃ% ~%W8?Bw (-W"c.cQ*;p[UuoߓL@Qk/57__ɾunX\cW_۶&]CA,# PgK@< Ղ@P3Bɘ؈hP̸F >p .vщ~gKpB7QGǮ%Q~;BHxA !:PGbq0(⊖`wdXzdN zAmI^ʘ$YJmh Y<ncˀBSxQd$E0(cA"#,$c2?{wV5rpŲ>2hFЇm|SgOYXLjysOJ4&63&<|VL2r7t \3q,'o oDz?0eQ-=FBiBtetF&`x*^!@L8zX>'%p,Ƶa+e|=f辇`Cm0 3+M!hVoI!~i)ā)0 t\L]ĖI/= QVU*!GP`W$LcȅY?"}? 9%NDa.oЧY#u FxY]3wbD`7J?lwLZgL``jp'P}/u^0OL&F~߬R"0,j넛59H(;dzy~;.ZtOFQcQ\ Ϭ  ?ZZa3J /K%j9 SU薶 $i ACőX5l`sQU2&/9^6Unc9Cfd1;\Lnoaa*䀸jZ A| XyO% 3 Fc(8pQZ1$un|sO(vHqJKז 4Agʂ(LN 52 e V dF`, Y>=OI0Fr;FwŲwjg#PYz)Xg&˱T,HP%YD$ak&`Q-w#P;ۉ Yظ:q @I^zl(#`ɃS1,aͮ6uIvaY%f28lپ*!`ߝ҄Vl) 1]eX iHڠ6gh1}8X B]`us9pr̥~o;_U˲(Rcٛfߺnu$KDu71tF0CSa藝etD&@kQ.(`&~9*I~B12)gj5 *( "7BjdUWRQ_iw.dlV c\v [\+LG;>YG1;?Tˢhw geC!<_Aےqqyo C<$IA,'G(?=S-,?kui Ѕe|tga-bH6%S>Br{(d/>uci ,1?so'8kvw|A'\sll-ws[lI&Kd 4X~nwF N-m"&2bp !{R-ƓJS ,҄6AdՓ0 6cF "]Q;#z':ȓ?foP!-AOC8a` A9eE0ē`@M9@VKrX3YŰ@]iwTlK믭 2&'mB}̖f^F0Hd7* nXy+2Hi9Y0տj_y-oJ`5l||"!Vd18a-uL zM4Cyeo(pAb3 ^';`5ޕL@]M;Ec Ьd%8 ‘a* `+|adE0Q}dc xʉ1A$;TČ "|HgD50!$D u' 0h#dϑCxC6Sm/t>$(WT$T@MOa,b@awN@DXL3v X !xϡݬRvˬ*"L1oօajڒh [bf;ڲD9vɊ 2+r_<xmrn% PIG?q睗Ww{emKzJ3L@Jh~- P#dW"P 架0"Pf1@v^e5X'Ƒ&W-1Z$WYGLO"Wqx#y0yF%:' ؇% Ý+.nҟ-ȗ퀶 H5u !x1c;=؏KE ȟ`?DPn#^,Bp/YGt 0ut~hW|e!VsJhS4jHVQN o){K/u!Z]ZW-4@&1 $6sp{W@Md ıTq-2PIJQHL~QKʧ?dPʥd$OD?0PʢC6'p=hb~4` V%Sr9Z/oveJP5O1P'`2z!nl$IZIa1IVS Vou*z@2Y ;3⣚{' W|lkGw~ԛVqs=) M 1F#Ѵq;Zۄi҂rT/,(]x1 y; Vs?{c< rcrFYcx lsM ŸD cՀSʫG^t,䌸H ƖCt l=kU];Ay#ߊaV<~-c:d9=sK;|0ꉎaçn}(g'b8?8b/ϭ4L'snw 轛.4k|X"\"SWG 1I[12 -?# }.'@߉;~Z_H;\$yX1ri,rC1N \Vc+ rl\"7;p`bDsWj_-bJᣓ?\6eǝZkI"JhH=D!z_m`744h5&/=6N3~}5 @zt!*W83%-% 2;i.9.W{/@(u~E`zDt@ȠgS:Y$"2odە".1LnM)k0N + JYOjJBVIe0UDU >rcHOjgpñobŢ\&7K/S P9b% qƞ'xA@x /wɟy}E<;B888Z|d>D\3/n:iv>ogs=5"aa 2-Agiвِ=1slLѽ8Tߢjb$~`OfH)&Z2XDeNQ>/#p^$2:Ь-}mM ,,kf!&hl.dJ$]hSa:v=@c1Q&ЧrRFX PpBrろs{9#0 W?p뫝_5[_IZJ&ER,h3}sh=dL3ͤ[U0}q@6!:bte|EŞ(;2Q' 2,>Gd=kD蓝fg& AWtZmϚ\?0{vX8pΐ mU;OoIl-k7o7UMiހ-.3U~Usp"?]b*)f׬+/I-0 6z=v$'bO2*4=`+HiDV'Rr*7 !X1i'ŌiUߣzsy9v_ἙLiKoStEe_2~p<͏?LW-!4G?xfD]āмLٙUqϧ?!ȉ@8=@qObӧ4!d1 T cO!w:#py!}pG<.{I+/z7Y%c KdJl$EO|}.KHbԈ0&(aVgi=JuR%;pqB v[KOZfmHjf6HO/$#q;G5=xRp`O+2s@=*33ƛR&YH)T "XqQ% @?t=8"@a9)dEd9sgGr~gR(˙R99PGbEɩ^\&> zI>akBoZ?ĸ/ r#_Gty`Jh}c DeEd?nRbc$^C[2~6䥲+8TZDF|i@(j< 0 @DŸ3#P]ZITgHQz?Mtaٿ!:izi[sq"C`86Xf$x _!3i@4xlu!p"ASo;# 0vJ>q~wWZoE贮nXX{kQ$З ˟ul hNyS-$"L[ zX׻d Lh0gaaH3;~Cc^ˎ> ?H%z?ytGkzq5+Ҡ?sJso'X)|_/Wi[畚3 +>}啯/Қ/^jo~o)KF @^N7yNH$9.!d`IDÁ<}\`[yd$/Zջr5&F84YgIqpLH#<:ıRD&=ι|wC5kpk=Q(q,I46\s 5U>@;w~pkuM/%o}7[74aP2{ iU#(tLK?@: ѐJS[ĘE*yzeLmL`^sӓu,S 1^_G# h\eʞ}mKВ3vP.,7df z {vL0B#@ؓw@ Q#4lb9^31B \b;i]Xw5xވ_%#o}.4jPs|--.X}[.[}MOT ȵ vY"+S~;v+EֻdM>T9Hg1' Lc Cy ctX:1%qbU*wCgM`M7O@lޗgE Em+Wt{>V]HӅZ?JVjז뿿-}#~-~'Y8n*zlYF}#0 4%4hx>EQ!g:}a2لaMk 2WSV-؍!"/sy98W?jhՏŔ;R3oVؕt7Ũ@FÏ׷DtXOoܬE^j#/U}mhg1"j`!B*xLDb t~ %p&&62aVPiY0_@':a sg5IF?q;=Qͯ# AS˞6jk%#5KACq6ApP9 A]ā'STH->XO-bQ~q &~r B3yO';+i/'"#~ _}qZ2 gPhy4>Qxs UNnK v9I6wH0[Ld9Nˉߔ*G)K2\_u.!9]"3G$;{.}9lbpSgtsp^>ifQo/[\?i%-DFFoZͷiv3tptQbF,P9 ~"$`̀ DPq9 ` ("~dY/❭uV7F83w[|N?{q]j{IgL)3;SU8B1vh~ggfI=e}bWܺnAF 9/ߐd+Iqs}=}_P H2n\p#:4rZkPFW;-(gSk F ʃP|6*ލ 'aY,Rb2PJ"I> \J9pڔ0Q?ԓ=Y=*4v"ȧbZ7`_!# i88#jDC`' 3J0@13 ky n|-dk-n࠭m--+Q0]뙥 `"? 3ӂ䌃)JgShapwSmTfPI6{s-]5]ڳ52105- 2$Z+#- kG-4ߖfW Od[2~bؓ4 H@ b `̠ve)Di+hl5 bLF@Kkd `g v` J釒wi(9'?"]qbTӯp/Y>Z(ղ}\&aO_98{)?+2y]10L}X sp[9ܑ@jNdnwwWo??RB~9pQ szjhǀl `{fA?B1?xЎ2C'Qm ?T2$;n@>>MYg ;Mz1L좤_.SF_ ~W}}> s~3׸/[գrd$64 |f hֳhՒ&wH=ٙ!bS7MFac8 9 E&XOYfQN(<*M`LU anKVKǂ9H_ψCNըC՗GijS߄Gow&D#w;:T$p,\&~=>|=H^_EXfXhSOR6q1GO~Ww1HD;zjqRa *2#Xd\ < /%l@B7S'jn$lad/E9Xj!sO(tb6XΫF;ΔgNlFtRmOU'"?twΨsG! UokR!<^[_'R.Y2dڢLnAjȵM#r9z?ʮ@hʃ,[H0"xuŐS K/ Fo{+2$?%@{hqe(+Ixg6OuD^C`zsSR y#?7&gm=O5 =Ydjri|@DSB#Y\@W2:ⰝYMa>cnǙh:X>q F* AK`r" t yXG0CXYr&jY:/sbQJ/ > ؿ"`7J$X au Jheb3WBޛo_~z;ke ࡋo.gﮬ׿-f@g 8Rhz + ;mCqVk06IHUNNΨKc ҄o) /56ROVfT+ﴥJ-OV5Qf,6S1^mӂup~^9"}T(4FHk!q|# gV.UEϼȶUJ)xjQ#pYvaY`&bԁx\c 5n|xx_.@w4⡦::`{x?K졈S+[KoٖB6`;(a,D@[<1XiO,y[x4c8L2cDYj m^ˬl3iZ&Lc^3If=H(HS5tw<ҦӹO9Ʈe^oGGp?k95/эό岚y'+fҨF:ʎe6q,1.!k8h(AVzC=152&%;l'p)>ܕ_{m'8 ]/<Õ+K[˯uUQu,ey6,POXy3G@ O ӉdG ((=:hҮw1+bM WnlpmJjb%SJDꯚ'&UJn2W?{OL81^#0 ͵OeF i`bb9} |bJ7 +t7 ZKd/D UC\SPpPf/&I xOVL>wG,f d~t f'`&6Tg4ZCxD XS{fq2ssI^ۯÁY^>lqeSz^(L\T#'=#OMz0#Ҍ0o M BADgc%<4E2X[\{[w?;D )0 \|yη6G0G`6.c){Z+Škgt-{}f6P4 uOM嫋8>T êN ջZa7:B‘>odm>vdwm|7hgSN|ӱCN#vQX6vn]c&,VwJYHL" cT@an lEc-k_[w{5XHWRLk-ֵn3 <=>'`kvß}uϲl郎ƒawեrw9x?&IBjc䍻 Q`0S<']B)&HF}չnE© ɇ'&ou4C"]i1#Ƞ!U%&,z:3zoT+߸/vV h:zٳ8=TwO,.<ɾEx]/O2NC:w_ D\\vT,ug[sK>멯  Em?ź G{GtL;$CA&5)4IQR4"fuZ6ymؤA&|OJ*Xߔ#Ž$^O TP} qgK#{Ҵ\#3sʑJv k[~ J#cp#O;EMvş,@p<τk drYF*{iˆR4dIG5;?/'a ) 1 a淘psmF#-Ǒ:Y)w"58Y! VC6Є)ælx -#W yj"xp z؏F^xÿdck: {=+F.Kg:% =Ygufc@:xR`m_]_[ⰪVDzCT; cll\~"&jd_;(>lac> s˷,vPO10Dc<皏8Kco>G+ՔAu +!8Gg >&NaG>7- At]Q\H%+I'82 $N$1QXUQKl:-}so˖6g흏L|i="#?^\pKZżG!a'AN`L-ΨܟL>}fw.]3p؟ET$\AaDu-4aiCPK-䱰ۏ遤4iC"0lqRFEa"qB,ЁF`dף1baNWuIW߁80нbJ u2Ód0_C+2|/2ZG]+Ylkz/|y\x_}ELhQ|0bZC_C[46=ô=.-0Um?U _6iFXҠcˋa q. &:? ? ]c)J/6sc@_H]X(253!" pw}-h {(!IQ‚.p<X<'&~TUoFh7eZWX/gedp $"0<\Å~y_`I\]b|C!bI0^s`4Sz1O>'hQu|rVTgS`ϊiS!Y@,^gn??g2 'A>- D, 愳>@S2Ph`ĺzTgwJbnglf`prv|yc;ڋMjxpF^C!`:~i~>UÅO 4{)9Z^u;_ZVgA)X}ax#_O+7ac܊3:xF O8,heL$4JLg~h隈ig?4埽(+{MjcɌ!5>MHC(%1_T"2aG^3n1dPijRB.bmU@ tU^OU{ gn/+^s_<?" _񿰶QˣjPhRTjG:n~jg`&F,7 q>0by| O1tEkŨGzAPٜ8"id㵡oh"'#`HqŨ`"фzf,EGyq(X?]Q N^gJ`8 PSfx{Gƒ)pTuFP 6]0!z0#$E͋c3 8Xͬj[M}FT{zN#)tA $tA臇G/]*ƻM5_W=61Me^Ujm3ޣo4 =)1ԾÀ8 !fPUhaJ\:E~Kid˹9YGn]6mz`~`+!a Wc G Lh S;#\;\?DHu8rídQHJ 7એ*䅅o,C #zr*oKǿ]Z_7v&`Ek>}ag=.}"vI;co1*?a> Z~'(xynV4v83/>߯sGo\8ґl$o RNgϥ&  ϳ1aAg֔H WL$G-lK lu62^ºsYX᰺~xXpayOx=T:tCUn(HfZ}P^u~CՒ4)^#kL}Ǧ=C)7MPc0 ` p Z tboss8WBsǙ_j[j@}p[;KKe% R*f` ˟|rE<w\g!W +yy_W㾠)rkї;݅ͽ"N[1f5#iii?lҧ b$.ΙP@!@q G:ޮs4#O'-!l? #iBh1Oj,(u[ TR=ZS"h>b'1?*,ş6N#/ VTaHf.VaJbGB_ L zaT{Q(P)zZW@YIS* #e^Ȇp|mX3|Wp,v5}Omt |!`vz tE.FH'H˥?S;+.Y\/%J yαYvH6m@bkMtcр h@QDq2c (oILFȢrzQ;d65NW6(=.itnW ٚe#p?'_I-M䗆Vd]Q7zFǏwO]8azcWn@XMGAИ>q{D jag@+̾e됔ަKoz:=z^a*yPh'B| o켑 ,dc0jzj6ZoRću=PFVwWe} sp?'ls{O/QYޒvt/KϳQr{WJu7P!+%Vڡ3Wt`62szЙazeBoI3 8 SjeK8J޳?Zt 7rfhFca^S'd`g'Rti:Za[K폺K-D\х_G 9~HP_*wdHxWӨ޽4VY-;, c`vUSaz\!ϲj᭧W g~.\ P=[ َԵiR IŐG‘?!jO'eqIG(,E ZĔR63cdރҹ?eܧ^ F~(?yj(8K򚠪Fܑ"U̴^5\E AL}8޽{_GO5^!-n]|UX%5ƽi'j irr-yòogUi ?uJtqPćP]4Ïg+@-i9Yq1.D!;9T<{KWDQB}6 P& FrzA(ŖD;ڥ4SN[@Lt|2VWTm9{\?W@/t>Yٸ?XzBIY`iެGW,%UZ}[jhӷqx&o4J4~l,z^Ե1m^|Ls@ >G^蟅M4`+9R8Hg[Q/6Le|GTtsD QvIegw:ybJ\ƧJ]|e]ƛ9r}lu.kwv-?8w w<t/O:&ke +CuzXGz~ӷ fMeDOb=4NJ_ *q68J"&\~pҒ#sqwf\83^,z2u747Uu|^H\^a3ԸZ\E@*n}sFOAz22^`ĝU֬zB=+19 G`O ͔;FҫdԟwnאַPJf3@jEQ1@ 4& S@!ak6#M,ˁU o/kowd4j@ͳd_Zꖻ\}-T4}o]M_U5tLRCk1B9Ye)FƮ%j )uD3b|bQribĢxS| GS{GK<ߘ/F@/,# kM0@)9&?UOс*\ /F^'/l,BwGc9ŵbղ[|g4^Jg _d1E#W7y~ޚ30{N~YWc3 =?lhh%D(5ԸͨJSg bIs4;nk@@яE"RsFUzIÖbǡgOFQ׿`|UD4~mcvV8< `c&B#:i-eiνʚהx+4ڳX1}I@B0ez{gA^JXQ*\2$`$}q1N&kN7>]6E/݃^ۏ7-!zu ҍ;kbyղItX=e?#}f/b)64qz8iW'?Ǧu%Xy1I==ƃweK@[K)x t+͌gH׷btM dE Bֆxh6ӟo\^4$\8 !O7`JL—eἪ sԜ~0_Lj_/mii#-,@C1 @X}DS:/Y:\&Qia|@mnf {LoWwG)_8_7sDoW/\\,_=퀆_i^?cL}bߖE7`Ï=!VzB0|7?q0Ņ/Llh㭵HsAڽyx~ 9O#ZSdk8+]_Hժ4Y:^w꧃ z΂P_X7o%}qEɖ7ch-%苐Yhk}3&`Y1;7~ )ŝ#0B )K_)ڨޫjflIb(v+l5UR|-N %"B~ ρFB^ *\8:\-8>U֬$ ͺ>xbn,˞9,8KvcYG 0r8X; ?gaVɊhqsPlq'vO ~N@/] ˽:Wp 'cR0Zstѹ%\fdYz(0~Gg_X0cQ#~IXQ]py1I=OmUvu{A^U꼇/Y/8>71e.!^7@kRV: ^prv x 0<p߄{U=}`4Xkꗋj }n対~0|c,` Ӱaȸt )w$b*OR4Cx>"͐O,xXT<O}rjdFiy){ӥ<sq+2ƟT{谉"DkFHif/#5ftZa4.=On7=ʑOu]Nj㉀ s=]_D:ڦ;|j~\:2CF珱~j`0}z$3/Ok2권δw1*s1n^xa7(Cd1'S?i1)b2ȵCd}֮/h?UڬkGRYETqJ9i_jb'/AFYqSsC?y Bv>Z]<=Vɏ67o'Gnx`CM+j5.ER᠞w1KBIF*10T0B6O㧡:t0vZIsy쬲f5%x+4ڳ9ꞥ.'T\ϳ~|ܱfwC+ fxJ2J ea"6B:+ K`d#`5D?VUvZ?I=m]?Y/8[Z㕼,i>tzas4. :[T- cV$$#ψm|kK;s^|LR)Kmvkk! >Ko#k53@^Jxlo>_nhkZ'}_'{!C1J~ / ﬏/ΚZ-3Zj0h1|<0|a&j&j|v2GJsMLgbSR]ycY|b!ҽJ3 8 SgZ^?i %ti:z܊wSԏg7:;``[+a*fW^UOB jwVU`{u)*㎀ tY9 uQ6,( FQjZ 3#% 5G Si)]_VK>wl;N#H1DWW^N˚׿_,K~IB/ONCiPh}u{ Z"j5jYj`0*\>.m*G^pVB2TYM?r1}O ~wrW>|G0k[!]!۹q'ѩxܣ=g `6z xO "~mXD= $, Z , '˴Њ 4c9GDJL]k^wF3bIŦ?Ң$ȥNu^G,TX)P WϞ*oԯ{zY}7x1Cc: 뺵W$=EM)Bw/ [s/B? Cg)}}C4!(j6;<ؠZJ-TYJ*s]a{𞐎B:{RCqa'<Ax>`Vh3ղFW<_G x[6k7ͲFG5:ӯѺ ]*_tzk4 b6Ћ-pVl =}44Cyl!*Ӟtj]È/IIwe@H{fHQe=a0u#9[\q]EZ[4dc? 1atp]>B_k]$fYŘn0XP";Gut?@@)hJCG߯fhU@xNV5}b'*!tǘ~<΋j({MAw 6@PYypދ^ a#>&f&Ǽ^_{_X(v\^]ꍉ5:C 9mޗ^tJʸWtl?)uCE<.=5{٢'L#O#—N?DLӴaʢ Ñv/tVx<"iR?TR7wQJ.z0mEu7AH 濬D9 \|^Li[r S+?.2w Gqz8OHo ;:^0ha0n=P~l.HؓJ[nd{>T 4ꦄk{:9EWW9\Gmw$0ljb)]O˛$1 gDYBS?̬" >gK4lUebAa(;"M?$%0'Go>U/坎LW|ooG6[F~]/:cX=:ztf4a63=4K\+h)VwVr K ~G/a@xE`Ɍ;Ć NwY.*w}UҦEWǾD^bM1CHhA'ղFW fn B ss("`:~O;jyxcuʮoYWy4k,ՓW+`={~sx>܄@SFIJROWܝCK걲*az#CZ4b8mhu G7 NLDA MIS@G`[~jY+)},.u@#JC F L솇PiFuyϒTW WE ;#T06mMwGj@~ }N.7u-zA7z&ec? sQxCI;\ ֐75}(XF_~D?muk-M/Dգď7 z*G0 c SLܕ+¬m%|Z0flF/-'__-w/esmN$ S]/wcnu6/1Ϭ}j86GIg7 )w$vr; mMsb k# ᣗ!}1K+aiH~_CH2CTsuOwFav_\=~y Wwd(@z-*Y1" .-b0+H\+b*8EψMPlbyML#$4YOB9̾{(P R|a[ƠP yvC6" بnsBU{eV^;[6y=.<ϧ:~yHU0ϟ}? mK# \0 iΟm΢8kWdlXuiVc: yjID(gȧl#lymRV0;XF.* 0d%}*]=EŸ觐ğxO]<cyq}XFz!J^2d{[*Bh#y %QGnj ޓxArUl _z72F?ճœJc g㿨K6GϡƬiJa[vWаfn>SCw8at<@1iZ4C ΢IO㧾G{c@LHi0)oЩ*Vǥ͢?k\z=p8bA:h,c OjǫK} xk xwwSY5w4`bIzĥz拓jTs^gɞqө%ʘ/Mx>MI4_쮷YgU #k䋚g|tCyZ.g~  0dG'o`q':jA"{ BW=Zs?QqMK~|K}1]y}}![8 Κ{TiaLPX>^-TV`IBU3E U܀MbzlRO~Zoucs6ν{Sv v6 @_4q_96]?XY$e}Y۟QC9fELӴaʡ@u\?-&O/SKyu K- i/tnQڃr :~u(A:OT+f/7ǡlC0} H-.X zL8PwC7>T>D=+/ ;T"0 B]6y:@ug =q́,G?b9&Wf*b Cf1,?Ib`eO8c, ?1T:<۱mO|c`/AD0)rKЦ Ұ#?oɒC c bO&O%fVgZE16j3MOjy#XI1/ 3o=&5}Cޯ7K&!0mf XaӇba\;)%Msy{WlfxPT/PN0`U%I4;앲Dd ݗj-_sUFAWENhM*!<` |!4+bMS7#Cag_َWX<4qrݲ3/B-=k>PfNoz==õmyw؍UI⌡o[AOtrV3TF9 @(>њ>IddP  Y6  ,ɿ.#.z`5Jc?Oeu'5} =r?qÚn$"Osc,^I35ZX]# 􆲏&D?3"'X.ƨO':ŸV)Zc=hT> 3e6&`QOE/b_>^C չrQ5-uYL^ :eAMnV\&j<>MXμfA=QϤ){=+!)gzF~i tr?$"Fpr q3mbI} NM7$աF-A:Gv:1鳫I C:NKal_bK$~FJfX[ ӷ ]bW|"i#a0H0-O@Kc!*H L: ?[X{S.OK5!=~F@D36)~,lfywRR\xﻝ_J;LʝOֳAwK/`iYFwLhKGkж6}WUhth$6)|d51CeROAYgu@^4+ ШD/-KV; K`'ngjAohxW0J(ޮa>DOC4t_G2u燘0@g],$] # Ͻlxl8Pa݉sDƆH` HwRQXͧ;{f_?g~La4/u帿{c#l/ol ; c2tY̫j!7o :_/6f"$#DTyztq#L~!m,š_S+ 3?.璬CC/sLl#i>yyx'm%*3sfQ&YQk(+M+O% 'KCjo_,wk6:} 9x,Px$O%Ą.sĞؽ+cxI?-P"Xo_[Bp\r2 L靽1sSB?NU<6~1k:,KdM33 [",'L >R3zzv(Ə@Q:k=Ѻl5޿) lC$Yp0 +q!p拔ri:qǸ>ӯx6+:ZYo.0$^=Ӳ` 0?\>熣) ISuS`nZhSk/7冸C#B9wfWԇMIz ̀#DMc._ϑ_ M}f?&(ґGVfKy>Y]\o|W_}K+mB5'ҫuncxZY(_\`.?(Z}X"?- c .C(9M419MIIwesV揵3&A:~e>6a^?s-_] :e : `gJ&a=NJn+ik e' ~&Rc\$cHH:n71UGмxp6[$<%vbX{\fyꦓ s:˴߯?2XZ@;4= @6&"Yw6lW u+ jOW˗gn0iܗH G/o/<_Ϳ3V_ɲzkݥ"St6 b<4.#Gˇ?xnx$vr; MsM`z`,g_Pŕ2֧ 0 ^y8y }?T>!`OȨ#3Q)-hS|YKMd(i%OS?$][έ6Iԡ# !A2XpDIL](S[V%gW&z&S,_K|V*'v~ 8Bxݓt!;Q( M\qV\|X`:PaqCʪRڼ6')&/iX`?/jkpk[o07 mѼtu񅗕U}-pQWh{ }^,4 0"[4c,6gi+7*+0\0c-_-K#$Ҧq3q3pC=^g7̲E~eC(s[.h#T橼5Ŭ׮K;&O.:x6 x*'[dx؞\\.$R j*1=h.POα{/xlCt0~fq?AW*--$5eGEYSq .)YVXX$yabh&ݏ{Z懣z+ΘZ0[_~ά~,B;%LӣGVCǯik$쫧ϙ~+ӏR+xw=\i=Ri1~^MD<*CZ$@`,w'R=OP23ńtќNSbTHxt g0‚FVAW@UvFZt({&ONX[?no_~Go&YxF٪zsX]%>> g__C2fGh&ʘĦ#2" j?q5Yǟy믷?ZIFib<4>X!cq[̋2b9J㙤y9C^wZn$>/K9_X}dξrCEiTid-,揅? ?+[pd?=P|n+0?1-Ӽȇ!fd  Vyv8͋OYHA*c$] f !M-@Y#PK!"fb wjqpvܼ*~1!OjxY`"a`:yauSiAIT*.m+??I:_]|y[ռ `pb%1!E_hHcnd?P15C\u%ѻ_Wcyi0VC%2t,̃ðLSѳ>㿡i~QǟÝ!g#ERw-6AӺUuOU61]'U cfSE8իlBSKYM|Jui[YhEHok),/ϱyH5$9D ȄƄS >t0Z͖&鄹l5t% ..t8-U{ VhO]Fh|Dlz Hj;L{ q]+jEVcZAOdLj*q4H]Ӱ>G2c2dwyYte~:π O)\b(qrХ#S6 ;Y"T4@ck~N A(7 I2"_8׏eimE4X|k-k-`)itP-b˱e $q/nJE=+7'ٿ\cwnY ͫ_zlD<[aVܼtme Nu4F 5:ݪOkbCG 6\0@2ko*a >V"C`XP ?BDqghO]޼2.̼Q{4U_# 2dJ/ 8PÈ!=IVa(X)m؏~蕱 3{}N=['e4ΊKKkq^c^r[JϬQOjy7y5[!"P R4|1^=PǷMn#l{6#Y {Bj(?7g_Xnbx?.G^!iZʆ%IKdk40/N:1Bk;>rH0u5*5SѨ~~Uͽ90x zvQj{(Qpz;:WqiwT&7洵'~""Lu%owس0u1FC%W5!CyU?hHg&G _+VL:b߳6WW@ 깻GUཧFz ڍxmT.n,`7pqֆ '޾6LKfًzUZOҙbӒtO|۲M{u?c=B?Sj BB1:k3SsUXNdb| wE;+nnw@{o4uCEYf; ,gXBJ(`TK@~V/Oלi.o´eQ6 dB=nO[DK_/](nbE n[[];sep^W^譭[#3\S[vmJ10O_ xU&uq!!'*`F~ab{E3}>'O_-<ӱm|I#nCsI?vGپmc v ^;6?\FIip#:Z2fiO#Zw Sv%+&|zu2RL؀fҏdO Y3pZig*/Ɣ=RuZԳP_XjR:@d镬`{,h~WF_Fn3ePj+79sTz7wsD<7/h[I`n}!EF:wKxW@l~W^5Z[.d#i`OfW.s:{^S;?Ԟ_^!Pz^fj>BvykƄ) Lg1I=M$۱Yer]:,{F?koi!gCGXF23\5]!C5uXuH' wֵgŝTJ?kDz5^g]__O (Qqyńn[X"A"ϴpF}BJV0w`Sa<p(P,l.SB#}3?u {qp^JZwqٱ狚'za>7?>~HϏ[asu[Q,޻4ɲw~U]><1d4j!ѴR[} h'3}m70Q\L\dFӌ5|]}{瑞YY}s##2{UTa3QgSckydL=qxkķ7*Y^c:>9>?t5z{: ˉBS=WN>n,]w7r7]yQVm񒉏ʲ-ucÝz6A.T>}~'H HLWZyh̔0O,6?j)A!0aY͒? {i_pOJQ9nh 4j`PW_\ewUDhso\Z)@/4!;Oy4ѫX^-Pa9Y]=+[, ݤݳ^ E7ѣڷR`+1bp{:H /(1?T%@ZhھeBAujU*\܈wO/٢ݚ+%AgzHHo kulɗ6 7POglD嶾Mmt{LRg ]}_.S L/i´=xQ[yVudF癮c? 3gEtT G7y۶d9񻡑#9ͤXG۟wMKThX;OHhj>/VKiPq.gb'̃5mͣqaיӺ'mUPQ_eg1\-|#([!#=kCyiX4& CPDԉ|VD] +sS%dtosw7vScA~[lb۞cc-R?>0#Rir̢?c_s8o'!%N<=|er*oH ࿩D+uqbNډ#^3PJ|ŤFHjB4"øMxW~&I^$Lv^|yO.~=+Et/\3yyÃ}'V5 ʯqi D]cXaxۄDwGwo=# P:h5돊&8s hOzm"GBb0u;Θ6wLM$Wq{&fW$swd@uXI;໪u0ZkGNoF;(HSP3.G 1lqU[o?X;&GO?[ pM_u%\SA~oO&w}p7\h:VPw z6CE4-Q{pê0z?HM9>ye4C{:g7L:{M,s'E{ӓS!L=-s(K\/%c~^؟B- mEL,!D69MfQU8'<9 barCA]K!9-v?G /y+eukzwMVHXm?`^>sO=e+Mq*fV<-f{--!@KmOd O?6{_hE?vW#&n)q:nǰ7 _㵹7?1ᾉ0Gf,m}]EZaթrqqS' ;;#Im5?N77rXǘa ,P_~Bs̮(}&X%gȻ;=fmw靣/ώUY2~w+`oO4dc2dm|w{pw4Zh0k_c:-eϭtIZ@kpFqÜUoք=w~iYL~uw8ϜO4@c;Vg9k~f?u=*o'o-#|hFӊ]w[&ZEA)*o /܊*KIS57a%(y"B ; g =^+"ĔK~C\=ݽu{eZxf3i64H?UkzO3:t]BCMwDTӋ1o_ _sIСl+^N*^L,v2` ^F|/U P/1_[`{R8]^_|g??N[W%:RݻwN}]):Vͅ#4׼wKF3i٭4f6=Yh+=:CfK4ؿ e'qo>ޘ_aoG4Ȣ>;[\zÖe ̪Q.ے ڇR1 mHM'&`hR p8?DHDɽ RQ 1S 0xH/?ˍ;ս˔9q(ەY7]L'Uq_=  @Rӑ?Uŭ.ϊ6ֱU7L ñpЖPj])?]m VLnxP62*?eXu!L\JKp L/6u*{.CX K~s^Q0n2WſϽXW4kp]@Sj+3ۃ&83^VΦf3O 5s܏ΈU: cc4HQżTO/?9:/k4a\&w4͔rc|݇݇ЉZ AUZ{]4%ޑw|qZ|zc{zT{fUw[tȟUƞ6=jn`h)(tu#:6ʎ~ zفF.C>J:POۙM4:}9XeJ`L2q}wpjMkd:l=Z<־YFX)̛-asϟ}zT6X7vs/?]erJDWz?}=tKm?c(,EacQ6? g4Bvs<~HƜo(*^FKI@ȩlcn4-*C.28 fJԊ:1v5P"fo 8[R ʼ$0V)'>Evv&C:)ñnk3Lle,2 N0/q# X)v.8S~ק[3ߋW^%zW̹]9buQp=~=ua^ fqB{2NsyЦM&tPjǏ>wOǒ|cEQ;S{r2< ?n;Ӓ){։ & Xuoa}c>e3qf/ߖ?x~ӇBKRyV'Jk=)=ؖ«'>FlK dVKwWG9%1}Sj 8Xx/AI\^֡fT߬%z]wt1'*B4bf47PUMj6{E0]ЅpL (7Ba8rǜϋ`(J٩{|/gf7RBYj{ٞPq;^Ss6Ύ"N*6<~b}N.yR)Ko&h0 7"gMptg 2dԻ`L=]PK<ԌI5+-jϑ/u)ftGwnGXWzsۉ]ivtb6wzB'ZBjs[ZW~q3m"0 ^xXTmKKj ϗ&9#/r%/װnj 0 ; t \?W)_OmY29"ZâxW)Ho!7 >:]7yGtE] q%3i?@}p 2KhTmSQٸJEs*\7ݍO*+oN֦>P+>ƿrjV@ǒt5UJ]}3ܙJiӘ%P.M8L2{ 3zlVX( l(VDYeJ4eg-wj5Mx^Y]B ~O[(1pN.t{l5kW-.bڭeKڰt,ev)%!CUO'84BB~V$,}U6R 4nu7m~ؘƠIf?|_35[^N9/ \IԯVy4 Jt7D nEn $"G6h[SwtG Z<˱%::P67H3ȳo{~ϵ B *P7!l6 ^ 2Ο}Xq} Hka^ -Dz_4Gേ:]tNv/z6s-v6Y!ѠΧdi?M׊k;`]=6Q?@eA_glTkuS6vVTMq;zo#/u^H't QvLX jpv$yg#g]`EudMLX i0x*MIlS?f׹V[ZZtm}!zi8?0_thv3eg1s?G^fI6gD{W˞c'd7~pǽioM&>cSگ qNVƾ|C]HG~ j]@Q,eJ`,aVѸAnET{҅d%Ka@MX?]%4NJ0둒OEViA@YRnleʄmX9/Dfk)Ui,^TC6֤s7*k)cYBo.D = tx:3G) c WJ~r)3Q2N.`rx ƿ9ߖTWlRnJz^Xgd\πE=lҟ˨ pN*;vŊpI{8]Z`t`wn\<ƪދU_gSxGc0/1E&]vr3kZǸܾu~ѣG_|OWJh-zJOp)nҵtSK|iɹuR[eAnBu{a^,y-~q?+Mz'HYWikѸ#-i2e_:Em]Z "At/kmG2zܗn ICY# 2jѧ,7[/t%D?4?퀧'#]{[rܕ?Bx_Gv/LWK8Q/罋\9pK|9l2TXL[t'νw>=^>o?սe˦TPK&=p)ũ>UeTFeŝkFE8M|S3Z҉{AM5u1ý yNPXR\h <>oO.<ֲG!tREjHU7C-,ּ~#nGzm \VX.y~Eā}CMʤ6)mwG6b?U>;H1^XBas>g4 T%:Hk358Mi5ZiI~%R6޻ υZ4{sC u>4vl^Հ{mJ ֕k W)n7ᇟϠNpߖ$>;uUP_' !,t"υbNԽ m:l{;F:CۓP& )é$@".$ -5C!MMNjuF\+5u GK}kurNw?D=ʴJh+[\^^o\wSNS?Ρ:XdÔ?0&4^a̟-n>@49-Ad7xt2)pv^yi{Ҷ`<{~g츻yaMm? FcM4p[U׹TɇF C} 1fTs]a]"y^gGz°&[f~g iV0.] [! OE߾N贽~L?|Պ~?]njg@Q 77%N n: ~=DfvDIL=Q:G/XKc֡K7h|yIE<-ٙG sZnot/mG2oz]#?ѝMv ZfgjdϟS-teR;-)/D=QL}*_K'7x~w>#S}ñ{ֽMmqLS>#M-DZ)H.:-SkO5r_aϟU𙖿"yo.点{ `%r>䓭ߓjOJ9Nw=?\ି2J:G{BWuwPD70m/^ /5b5( 3}=/>ct<4%M9'(iƁrZt_W+:|?,=/ِPYcxnאg,ftC6?]ƑtpLH/2zH1"Vcbi"Hnƚʙ-xUZ" {:`&~m5fLÖ:VI8 aѹc Q5(wުX3bC:ϥwp,F) 3q 0|5ˣT+:ۑ?1>ǐVNx8襔m@{@H۔ ^"+ߘ^cRӥkuuC+Zn.JGE=@%anXv '5#5kcܛmi x 0ZB E+,3VFSP,ˬ641fm]nBʾ>HP6 (1m֕碹?|^~9wtGa! W ^hRɀ SdTLuB";+ @V OtJfK+b;uM*lBvW_7VZd*K:5dg7 qQ>&:~8'jyCz=WnH4u?uDD&`jf)i[J$yW1 Ĉk 3K{=+]vI1?>/_k1E+cs;8R9,j%YJ;/e;C<(1>'v@9=u:i `-n;MpIqbD*GpT,48Y]!J_LjW״i|AzL [4"qVآ{&EFNLP^7 [nF\.BNiǼb#Nl\*D#KŦny uW*H+{VLTtɄ<Î\v$\05 *k5g]=-sLtUw]b^;Su Rv:X[W*np.k+7]~>e:x0cgXH +h3 /\1AxCM_9@ wwst֤FT|G|ד9=ܑH(k_ˇ )@>gsF, ,v8Ř[{_]#2XϛIzJh5G5(@KUDC&@h8bL~4C'܌b\$^/:)#+'kk+68]/{Jmp_v|80^+93k-*Rgc,ŀ2=w.T;F_tll7hځcW2~z/ؠ'E_\ /h)aJT]2+ٸ1xUTc%ht`__}|Siu>߬W=ph*%k+c&R5tHG Dw H桟vEIH-ԯZJɩD]G=F/ ns_G2VI<zgYF0 ڙ۹{+}`3+GZ `&o^ 6p?e;WɋtxmӋWlᩜ)ZacBL^'v@[j Dr~9"?Gf][@ zE+ӳW׆7oQ;ެ*RtKW.uw=?)36YFT+MãF{M1?|cMo I9.[7lTX3${UwRH_ .;<&^BrF9=~Ddx.d̫B#G@Z3nö:v na ŨZ8QD@V'vtXv_eZn FܮFI;FE<@vlu"> !IMr=3H~ȭ?8OeʅI<>X}} wH f:pNBcDR= $v'sp. 6>7e9ǵ2j ԃן>lٰ_ުi5:יY:9sļ pxW-;͌ ha)aΕ24]\a Y_<U9^rE?\NrkG;g+Eww~KK줛3p3qna 7!A{ny l]o iF{&d,ף'H;rx&V $ L7t'e`I Xm-7ٚ /?z'O^d؈ oi5@mј'HMc(A `HIF+MSBiNh"T' miCU+J`% '?]^ݽ 0Y?-oSRć(3:(oc Whx)8W@ݗ&ilpjj66!0 u ~ d1@mHBn+б/M{ZYO7i?*(MV {Hs)n˅jHlAv3V D ˑ_SJ}smx&j&X_bnJ6W_'?N+V$0f(ٲlq }vc<#_VX}D?0cHThB$# 0(UK .$X&q; !]~v5)kkkz^ӝL_b>ghgDwnZH?w@\?NTX ` .O[JѴ#2D%Ô zgY)? Nf̼,+VtױjC6C8gQ8z/yl{*3Urf 92wC.F#m@jgtAl V=Q˹y &NLL94>l4=L-UO>yX MZ\tsRڕl-ěZyu;$Ią=D )P+?}P&v ]O ^$М%fy075SƓu;ONy oFYU?V `PdpcRbO~?ݧ^g|%T2>1:y(=N ]%WR~h>q~uZȫ#Ú SeTI_VTZݎn'ZZ+hyW}K̈́וaV\^jSDWԸ(=3 3]%0?<>ezy>:v򝓇JA~W3ウaO#̳ N`|A1|_{;ԨXjL2O44C-} ّ[b= \B̬J~%e}U_дX3V7ѺgM$RC}-q`e`E`4- v z< @`h44S8@_Dn;-J[4K/M|33gCwnKZ{THdk%.8 ~m4N~Vky&ȍ׶43,9"Q?.AJP"W)qnuxk◶h;=ӿ) < 2B5xMv2_kUF:#qͩV@Ոʤ_?U.]<M68?8?[}u5N!@})`o %dIԓ7t&#ڮ(eXob ~2րϒ?&RfS>۪KTz=Ne@ae9^arߐZT:wCl\l.Ut2€麰~;+$]&$ h[Vʖm L[*tOQ;]n Lc}4qcyRW?bS"S5eX )p7>~u[rW*PEzW:.r)IrPPcJj~e5r/q8]]vqM` uuCBJ-S͎B&' 360˃.D/24 ?h.?!w}=™կ3/}YGI8ƣ 3|x^U yfxc<}܎bHo:qzgNIGJ n٪6@CXF#er*8:O}#02Ҳ:N vKۣr }`a?x+AwOVFkJHi#ttxLBQOqP? h30-pC@ָδFw{Y~|i3*/'Ă>H%}$N̔ ?yPoE83z?'|74WT?xIJO$q./pARjfR(r/-{/K=)አؕIGSL$UĕUcpi@+r|Fȏ'Zg ͆|8BtxЇu,oh*yY lu}{;y}q,AQkNj `ZƯTR/꧔;~;9~\p6ڰSuſ@ߣ_ƩRЈj0=@MJp^V% U}5SO'c)ڵt"&W4 T,+0uP~5OT5RUK&F4/*Z+q&X+ _`"PLfPՔn~_K#%fFgZ /y:}gϠLeK:{ l9tWB64c ⱌOlR0f&i tO.įrg]3tfU?/yMz.|8]avs]%na]'?N 0٠:퀺3 V#(n0+WKc3P% "S V;<Y_\|gQ't >.TA`!G i3^9nk1a&Ra`&V}-kr/̢u'ydΏP7|Rjb ļ Y}yxgn'TΊjee}}bŒc|?.VH#4"}'F7GX?-wR?܎a]mDwîݓ0=ttNtf ] ~o畹 +X[Xꪴ5i]8iWڕ0~OMQb( ' bޣ;QI @#N?-LfSh0<]~_mG}ٯ?#|2C7zפUq8X[ @㨰aN-L H :hK4Mf H̞hA]]dH~6,2`o{C,rsҐ`^l5EX`o|c9p0~||f繹сRFM~/B2(_ \{D̄d RPD2ūh%tyW2B `ȼHwsw#&XUp?Vh1O.~nOF0?Kcn=+j 'ypڭ rsZ/9 |rkd+'\tjS=3Vo@킖t-1iG_J\*;G;1ƿ?ٯN~h#; _ j?EB 7TlG$b4H(g",Om\ͨI%Ŕ[(C1 .;KЛ I`9֓ߑ䁟ՇmlP#]}E1rȑ.7Zp3nslxX ]/#_%Jc/4( ^超JuuZ)93%R PHBՎ {8gYgR?(r*zL>BgCYWr0{fJp${*"1ZބcRT4cMzs 5լ#9R7Mnқ7i=<4fY[W6h#} >3M nl^$=: +F|pTXpPmrpfPV(Rk{r7S8?ӆٿmmY } ^ {Acͺ?}@.B#;VOƀܥ_ Kc/w4\[Y΂{O͑#hp1֥8,S)JZM}jzU>ĥ&{@@IDATtMzE%;> tTMT\M f%/vkT@U[ r&XK+4FֈhhsMy_Iޥ m%hv] WK|o{4|VihnKֺ?DQ}]U_!M,q?Gݎf;Ew[WN^Vky-Dk }UhAT l`}|R7YlJzD{K?1`5&r5]t8sH{㥑`As'KYGcJ^eaK;joo6>?ާo{otsV[-vˡVɣ9DZ c$!7M[54$f EyUy/C5ɗs봝f>}&ng zN)b0&[#l-EuI |E`ehȖd4XBI@T͌?濛N&?ښNwO><%,O%FXF $Tbɼ~Y Yti ֭zWx~:b ~}4OFoS)1㷋p nY x5˭(q;~+ĩ$N0-)6> l4J)] ?Q.VI *IoUIG)# \W izcB~y?f*b. XB:㘻AqG@ 'h7| m}rlZ޺ (, l_gAfdp_ws‡lRiW(91&F Ri:0 7 RP:.tzˠ2ȐgT GRP,Z""-n F鞺 ׆w<5حsKJG u|OY rh ׋@+FRpdSBz[o"v_OGZ=٧tgcP( lŬ?W)M*)&X[7 N^Vky-D[! r~x'9bj,v͉돕lsA+=bY ,ĺHj2wM]qLq g=7UL}mR' (pͭtf_C-CmY(dC< v*B;۪]Rp3#y[ E)#v+Yv^qr~~ZHO[vֻXW" ~#pO0Y)Nb}~bD7hE<_En΄JDBpD@ I P<#^IW^ԯI@-2u\pdx%+қI[Se>(݄] d1KߐmmiltV[,H"֐J<8hB.O]%5~,Dݏ r;Gx0S)~?X NiJGM AE#HI RTXSyW/Sԩ*KtʄUUK" t~SM} ~ )HU&/UTH:DJw` XT"mUC7^P6!_Ml>˧" /Y'ᗶG`uaor5 ǔ[ҁ wx4y Ǝ;RHm)`ۍw49[ >M߿ 0Rpg R'-<. Dr}xl6Eq\Ù.-;},*Ftfxg_?${U$b:@#SE;}1Rtw&ԌD*l @uZC+xIP0_H? 86CM7BѻLcYz&6WMi5t_:yqZ##E!tK@̹Rwarh)u# ˳+R޳P+uK`NlX =&u(tYIb y?+\nbP]N+@ %N&|fskSÀS`*.hD= W4\fݔrӟMmͫX ۍ7TU(.‰kw_+RR9)q;AN{E|xɌ͐ox3ŚWQ~Cq#nbe^XďtÝa=N QVDw,=ƼYysng/ؔyQ?Gl2!칖q_ P$ Mh$7:Hנ@?ϵNBE@V̷%P%Ha"iP^-ջu.v[vRn%Y^^M#?7r@`_VP2-S:~Ur:֘)Rf)#N_y3k: [ӃK#ݗs{׃9nj !:T/kq@t#E7@pz̚] ' ɦ"w7_;^/=TbGZBM-cx3k{ZO_׫jk2Tq15$^@~=sljsW$_z9Pz0x4?{; !n`D'Mcx;iupMl t.6 QBCsez^q\3tNP,2+[MP%-G@hx ~ƫ EO"BV5Q2UXKߠCO8EW-g@iT[KU32T2H׼_Gʓi=s;<~dZ|Wg]Kj6X :O {!J:#aG<; Oq_Hww\?&|HMJxGwtNtPpL7q#fje$Ob9I6!I4 ' )ɡ/7$b7M{O!K- jOT *t˒fE'W5M9Q^6mG3E map;BG^-O @Sik)?"u`zs]e$fLl{ߔV-I9+mprxDUU:meiuL*=n[ Wӳ6@6iXHk2!e:W;v*C_m Щ3!)3ҹ/^ruP|_,G3,RA5_@%ampcrN M@Ņj<Ɲ 'y ¹tܖfZ{,[Qcٞ +2~2?pz@/:͐:=K+am`^%s#8ܺ9v`7'츿r*Y~=,nv[ID FٜF0J7D<.q 2mC⧦g|{0?th:_ t7sZUC1#=:fWNx)tx@s}HH+sUDa s#/tRn/t]f֜OPrC7O⧳{9PzdfFH` 7,܅Q3I&(u|Ab`#%>+ BRE-R]pâw?:Y=q͐ >/ʼsw_! 18hѱ(<.㞡/5̈́{!xb3P湭ͪ1pw&T8B7w3{V)V.ؤOi]]'~zS KNTg.1=~e&a(c񉥮&ho:xk 3[*0$%tU$ZQX~Q23< ! <@sz$jU6e1+Gc8#9Fam k7=(IiaCʅk3VW㥑bdb MTXŮ?jSʰL4ڟ2˘ Z-^NUU|o {!M`ND}MZWgeOO4CgOou h3sg͙g^$vGzf?8 $Y)0-~5Jp P?8-;o1Z&'@Xfys&&XU_Wt@ ŞM٦:֬vij_ZM*]m+u(] X9P0* BBDoo[zBnb)U c!<{Po¦2L=gI(`=KvOvQc`Ⴁw ˛֩a1] _e~XmĀų0_=VW><34Η9o"<~w:nK0$@- W4яnLmB nO7gзcI1Հ67$WoKؖw"`z!78 0!N}&TxRW:k^oEݱ1'|P/XL$` 31e"\JiD͑̆{Rĉn0rLpPo 6ыF#c/7ŇABJE\ü K7ʄ ny<=$Ͽݮ% WZԧcsOoAuW3c ; xɣ^[<{?Dg =U0c"+9UQBJHigI-*(KMi wD \tg[5JypKt/襈\\Ú|<śķ⿻Av)ʟTOgB T5M` QKVwǙFŞ :3ГGh8}HgWr>3$ż4vǥ;Ј'^*jm.;-@)bO_Dɂa"Ge~ f6[-LYjP35ku/Uأuj C=&yβ|6cpݞ>%o#nGwvGka'qoi 9K!As73$hsDZ34?!gKg(!]\w-Iǫ/14^:I+b E۽pu-c@Zgk6K+=rc3$4V5o#3P9 Hp E#36=}VX-Tپ@qJv.Ԟ d'ـ?E7y<~>GO'yem.>]>^7{јm&UEUpzZsrN1Εe)l{_ziqYkˁۃ2ˠttPoꀯPII= ^܃&7˰n#‹n/~a+fe:]pCA$"ž'@#\ FӅ#O {)E>//xğ.?Uz 6[=`@ZVk(Ex\I9dtCN,zGH1U&a!l[Ҹ'\$3&53/y1&Nu٤NI=dN(&SSQO/l d.kpͿVJhʬ\Fù~ym=GEON+֯TX/4X/ٵ.xYa ǡKv:%+[G:Ҵ{bty_-}D|>M,ưξ \jwYy͙Mf7b9Zb{kC[R? cW69˿10?| fmjnu>:99#3-A`6\NnW'-k'^^Qʥ6 W= b-gщQ[Xh- ^:}/`5j[.>b<*Ag?FċtQc"̮RU(e2F#C|au&/^Y@NCulWģ\.|NwAnr"wuh:ii6qD:f1[v׀[};O۲Dn5y9 Fj^y}W#{,mEx,A5e|a EK2̈Ը,\ԣ ,c25QmUo 5ͷ>nq-Az-(;^~l u!}~b1 b" jӱ, Q-ÌM `)j''Zm" }Qb"U$^{s;/mu?tiիٖWߠm :w ^cQs6Ն):Aդ1_ne_,rE}q"ଈo[6{|#"1ξ*@-=<灢 Lj}@.Y3 ,5ܾ.>ʋXYN/h\%#<6wRȺg_0Ɔ;y0JWEV"E,h7pV+PBٕ]+jEzvixO\0G_`^~|m/b{=)p;W-۰ĬMg+kHR&%.v>6p;B]N#Q?/\1݋`B#G|J^~)%,CNۯܢ"T4t tE_ujЏo~3H/bI_U#^}OikOK+> +,W6W4|ȵ^Kxw?s&rV@!ms\nգِ¬pGC?5j-AMa&=+[Ne,8?LڱB$ Y6t8bQ hZs@V {] tг)###w;T(opKAQo'ٺnNbl33 t bJD|]2>%dS$J \ofs*:Zp~_$pMN^y.%V~X'B-}3#\se"=BloGev" }JOol0?i]N,.v|`bOUhTVXaGnG>_s yjx)l-xӦF d`6=&'G_v߆a65zּiW6qki' y J_ZgKe-ZPr{zT>9=XG+i- Bc簞vt2ְPNe}ۢ!4.D #{6a{#qy;[wlm;1)ϔiaiPzFbɈ ̿CIQS p(}ƴDS,CdN2p*\h(17F0ϒO$ {5N QȤL9Ȯ|Dʑp FY.:\JNoG]5Ll6Q`r4MyR(q$Q!%?OnJ&y%lؐb,/-WyG &T07ЛȈw qCճM%\2(Bɮ rw^?xD`p}u.RVq\T7)nu ~0'{nerA2N6 ްEt* q ɕ*<],J/7T=KtD?dq/mQ)W fieQQF OsS`UI=x,~տ]k;0e[r5:HYZ)삦 u ^MWT5}:kT̹' N %+טk *E|=۵#3H=P |w~ArӥϿ_oC~Ƽ7_yz"@;Ƀ@ }2g ܭ@:Cq;U<=.]Ũ]2UMhqYj\'u$7T=uEg (.*\$|N `?zr7Xsr:tN[@1"=}ik*umzIܧVX Ҵ9 ;9UwlSU赟9x7MYyD}PE_0'خS[+Nݠng>E/3کqjGSY#\ ~9 Ր N6&ώ҆t {!s?= xHQ=NJ: V1*._Ȁ"MىK `kF/P]͂9ܿRĠ>oTd 6>[V1LW򛥍`M AeC-juSu~4Zμ*= {$@x䵦_>VݑtS}sSv@wX{Vkjlj|MvTlMf/j :#pU3d|W~d4gw>^f'Ry'-i\Qi<1Cz%HFKS*F/0=((7;ϩe*&—mu/P-DF!#ʜlmh= xn- 0q${#(''W/_P0.¶-w۔e2T?kU1|pgˎfSI#~o5JxQsoUPDk#RC8C W1O흍,W)"vy1EX7(%im&@mw$5O۰9Ј8m謰B͵bS|b_35V 8T7pv&!PW"Sʅ^ZyDs~ջ6 .bn䔐~ט1cm2؉R1ziVttӍgOh|~w~~S%~:VHdhkǽ|Wh2dr&Ԏh(~SR!uZAILk[!Z\ǝD2h&`fXiucFXu{k-}Js@_"F&kƥ|?:_}O߼|cov|PuU0;~͌ :#apH[[Y sQǡ;jF6z^m}΋@g?a;fįBCotw@܋`:NV7 \/*C +.9JA#}I(̌wSJt].@{.CdN ֥Ӻ2]ѩFgm~w7aw9;fg_iZ1he=d|Gӷq*2Q&C~y=*Mf#? ,#{[ OžiCıNF0J ~6RsOAz t$]pk`@PXVC* paA+ׅ.q|]"%:E\>8Wxw{U7]?Ot]?}:M(+KV8Հӆ"Cr߼xf$ώmء7hΏ>"l)uIb09 eܯQ WB]HP\k9vYn(£,KHU-(񆸦?Q.:Ts]HKQ6/^t TM9-tM)p;1pgsP)!}WG:ܗOi,rY/Ygw/2x#~ֶ+XaiS9mvm5?-#=6NZP%[ s'`{ "z@d<`UOw$7i*ulYr.U܁*W* X? l  A|CEx`"I7kم !@]?дzk=d5PwUyYj㺗 (Õ@"{6a80s3OFi3. eLqro#pE+ \H }HWLs~-<@u} v_:ANrel_6}s8|:]5d( %@8wbT[;jK^e*FW{yG( ?Bh6}(v8 f4'cpg>pՄA' ǫ4ui \|ԡX7M?<:n"F@NܑM4CO5Ofa)]L x}(|yTO;&Bi{r_v4eHT #GML*'!XhK \k%hc,FV)F2: ]H**tܪdqanC\Xv;x^^n2[obkGD=uì["{mXobZIpymgЕ 3(DVLElGN%8C*8 .ҵš6|(ou l)]x7]ӆI:wׯ_횧;Jς],{QUTPS2\e5M6;h%F*Q.8å`+k)?69"kw +*$>!? ҍljsoN>uYqy/oufV@J PY]|": Z99|̻S~4*U ,L,nw\*μ]<O:m3a٭ݬӝh ?ڳ4!NA1F{< =b{~: IۊY/TF#428"D9CH_]PvL%wWxws*W_tFCt-Z&e 2&XP]UW@aqrnG۷GQɯ~qB~ jϴ*JXl.}1Wέރ661:jYL_pL}TضqlY+3 %bKF5+sx_%_Ǘ@K3JRbLpLH n@x-F&TmQn=0%HQ%,of !))yu|]\ݫoCףd4ߛzn#s nM PY|C3vQ!Ĵ럢VT3\7=,9_ɭ 5(8oIJEtMic Mjmʇ}PGp V&g)aQДD|W.zdM YCVs|y" D (肻^K =r.+3m}~l7?i##oMs=)xk ` MNsl:L+۝4p,Y =y}~-䃨&:Yk/|[ 䦾PцGF}{L5 Q?zi=vMnlg 䥥"F-U laݎ0 R#0]]R76AF[%^K!5|`4䇠Cy_ce, gRv]P>w߸X)zͮ}csH1<TIꎦXwT|OHՄgj< DCb@6\P]Hg^wY#?#)Y{kJuQS8G Pl| KaƆH i# .tR)eeʣ (]|dSgF3^-D E܉Kk)5RX#1Ë4qpm1k ץS큓FQUN(c9-X~u#5u2 M5\ˊGiw*G9oaK>c`4 @3ֿ!H[\W ;!I z46ԗw l=Я<8~B te =C쑆O`[@@~X7BB\cT.?f$a4[5,O!Rm H(M!*]\:lOo7zfڟNͨf@{$L|Gtb& fUr=[[b(ьŌ7?r|@- s; Gsц6CfpϕP<,# / r{؈5bؙhw8%(W@xQ _\.":4sn{R_.uZړ2LS>ټfГ1Po~@6aM.P|%nδ2Eָ7umrNљҔ /-_ǫzWar۸ɤ Og]_0ʄv&j)EZܶ3§Z"9Ra;2-V=p|/ /):(omI_؅*Ewɛ#qb@e@UC4;CZ h8 CEԿg{9g.a9.m*ЬtΠBgހ*G\\F'U# I8$5T3H@z\+i!ǕG|^z\ ='%\:rѿS (? 6C?\+7-1ZPW/>|o7{z6=8:x6[b(U1hL +Cn ^%HSҍ^LaaѢRl*o^e{{#Rc8-oWi#k;pFH4~Tho^t6XD[?D9į? |E @5&LCaaQҍ[=ߚ|2zOev5s,pVmz/4eZSs+G[yt'")#CA+/v|{/N6pߞ>8?fokMev .92=ddIQ:E|6)<.8M6qmtD{DOT`ZSU0;+/*->*# Mj +tO@Ҟ!܌$JF|lfOtDmyД=QBɚՔŠ10CceUS v[v" '",h,[?.SL\j+\;"Q܀fC>Q7\H0w`g! kKѝy5Ϧ*Zei˅/ЧgZ; oSQf/tFK8|YY#KuՕӭ;EN55?7ɛgoV2r> r+<;~9(5@ǒQ@-ML2&Zk)S 6T\\0DIJJAxY@xG <0WB?Ry+jK6QW`zCEk,5ݍMu |sSe2o_l6NMFi=,O/+ް_e65#՘ @5l 1g`X4+wb:f׏F")KE$C`J| G@q| ={xˣ!ےEw <:n#'—!wY|j{SEckڧ#Gw:"l'xf#m}ZzG758y,ʷD/^w:Rlc6Uhu=Q$g#0*:hCw&4=wu܏Ft}T5Scc@}~?5x)\RKvKҊMK QrH3:e8ȭ^hOg`CQZ#y AUMQ%:$$ƀw! 0s怂< }V.+2b7&xiK.ҕXWٻi~lTߦβ݉"k\ Ʊ[!A4tx EhvӬ;=] h^kZ,5驮M(v-`!m1Q"6GڼD3,w46EXN|tm߲nl_oE[8`Ǟݭ`{ј`V0ȥ]ŢmO 1x–hjzv}ۤTkF#E ë߀<Go0G?wmIr'r/}#ot 3R>Կ\ldhI{h_wdPxJMfCT8[ME|[hõ) TzLk|\*{ưqVבu}m3j\oOUyt)+yKk}`k7mkޑ9w{|[hzĺRdU7l6ԍU}L٭ Tף`IWO_Uʸ~nIpͣm @5i-I`o?P/Gdg2GRo5_MxyKi@ <.FZE(5c w{z ԸwK]em Tsbic)szC>w: S{x }r,c(b\>rW;i37V5HkO"mʃ! V M7F3%rC =ZeNc/().C.9WeܛϢflW}e:\]+DhC:S]x5FrVV ]Ga~num]bwTYKtwO}e J-[ycηC`rsN~CL7 "DCrwk6s3 yqH2M0D%vըmPҘmrKaQtUE5*%%]<SE\CL.B ܥC/Jɉ,1e en` 1|B#mty9c¬fS14^p>65nXw:Rg_}17Z5l?ڜO?-=2AMF.c+C5!h{ڼl޽* 7}k68ྦt(w4j{ ֱs\fT^f{' b P;YQWnŹ@x(x*La.u:%==e κ˃ ǡG U!5wƠ/#5gc;nN(Cwl;jtǎ:5gv=ʡ6i3 Z~߱Ct0Qv 55ӒojD8GA7 }">ƒ䝊C=u1FӇI]x: 1+~~; ^hoxm6 53im4wӷMG?>Zt/r!qY @GO~6?Q8LDihFBkzte(Cq@ƺL!: ~Z85C'BSSm\]N^\6F(hu8~6#R[SZHhz@",LX{t)uTg9/UкܛϢq"+ ݁nc?K*rw%0JSt/y:ǃUIp/M _×qٟ>t\8$ ̷jR:mPW18bښ|J)=/E+;E8T1T0tCL 4> "\.&$TGf5 Gi&!ۿcĔ΀gնIjQ5;JW&}./uY+֦fvӬ}{Z=J6'9rHoZy읖m?&^%v;])L69`;9gX˴1`iUrۨh{Lի%h![O#5>18|_l$oT|FKf8&r ]Ε-XWmf[5ǀD2(놧AZYɍM`laqxH\ĆW?s;tmhWP3cS {ƱIotVHmnN~ObWG\•ź3Z4ű*ﯞTNO>PxǓ[ZwF~xvQ(H3ݯcu @+Ɓ;t~mm 'NӠv/#S9gQ9tnN@-Mz+kS`W[6g߼ٵ̖LhSbII{L2#@ͨ9uZ+PF,lM:@Ps: IO* ?MMSTJ !3N~QSBӸ]z494]8mp4$f™wP8Ot_mߚw޲9dSMug|s:ɱfި,X."H~[yp<ߧUP&E?̀/tʔےC4 ,2@vaaW`LA@%&a*+fHM_RMUP^fAYֹv8`qs:D9Ѿ7Yk C^cijr]Mgx~fk=85 ?q{'{^qO?&|Xtn#k_Vn\FVb G%/ZQZD3-yXuz65xJbɏu12r>: N PiJ `YE`F㦗x1e!p<).JH/hF3oK:A^ Jn>;%p[{Vg͟DZَg}m, Տe }vp Ӳnk_2ϝlRJT'{[SS(Utl $Z+H*i`!H^}f\eiCxQ[]4IPF:g6 *ƴ;wE1iFT37DW"$PqaTnnX[O1-N0]W&sVK@&mT}( S\srw]r[g2)Lgᾦ5ϛ7M:)1julk-ɱ'wmJ1iti_zZ :9eDۙWOƉ.o8QHTE U> ^nh3w}s'ky }x /`gs+-v!Yjj"E5?< uFqwMmr 0`.ӆCEי.&$fckęk1a՟0pԹJa;z.DRi.=D\+[4PB7jTZ!\r Զ?^Dk7~c6`rٝ8V:};?>̎zq2 SYKơ4 B)F02-TA4T8sN>+lumUk36! Bok)i .R cr᜜i: WEqF;_rǝF|G9F{F%J a5WJu6Z2  ,!2E*V @npm6Ǣq1w]M滳Mg_C~NA_0[9fn,_:A:467=%b@Jcwٟ>tXwF&|X_mRHo9ܶpJ_*FT1CE-LKougr/u^y`[I'g2T?H>5(Џ-\Kz_ pP7~F9jdϐvbO?>A96{.hٝ-эնbZUɿgLXo:EcQVR_A_c:j56L^=R$Q3|XbA%?Ͳz&_k5G5^4P7G0YQyn:utݯ~nտmٔI6=ި>3y]LP"e[tL D1 />9՟^9yhܶu)ّ~N hډ9QY^n' NvzϞO~ˁT䁢q##=L_;s6ij+!.ӌ5 @FŚ;ͶH0 $Nķ mV~O)D~nvC$p?X`tv(pcl)c&q2ɓ3a:*tSb #] hvh lJ8-矻ˀ|X0Wo<9:M~t= VXS*O}t:RX"<><z A44 'z.bWڹ%Hpns~9zqΙrυ81%tB1"qZ‰FЎ@Ezd [wpQ g0x/s2CŲK$ԫmi\4|?FwX2G4qIJ?qʇK8#Off#)-\b"WI:ܾskN1Ph|)LsA El7vܪl* * 4|0GHe}>VqfZzWCdVMZsRK$-"m P$=9ւ!1dAт- 0V3$bm8kT;|[pܩ}6?y . !@o?S-?me ee|s,k/esSt Dqkۚ_ڥ3ۜMnULD?ӈfSzM9qZQb#ĖZP >hh9%0O5R\k]>f8c7X8Ӛ(ꭀ')%dQzɡAv |/!(M0D[\e/XP iv1`} ˎ ̯ %G0u . Sr m?xi{蜾65̒ջ;S`vi$UVE_sT|drvƯ.,xL(Te͟v)i߿m=-h-O1e؟WY2'}tnY&79>7Qu`ɯGj0Um +j܄ L QAqQe0 ^n Ȝ yح`*?cpn?1BӐu4!ޖPoNwv_M'O$ͱ rB#ru4 o0i7@7 >!ng$I:F]u5܆0m +FeދRzLSJK =r#uh 31C~MRP5*e2Kĵq)=kۥOb㜮V$Xg?SzO3:,ZWKCS'؏:/o9i--'"ʵ%rTv^&eRèX*5sM>mn^ZYKΰ`/1d9,Foۻ&bSý^$Qٖ 3^\fhYÜ8)t3 JDIoF c[*R*4>x8x^<14cT{l|Y.yG?pGMkN!xFCSſ>Uz,gߜۨQQ_S #}jޅ>.Ȗ7'(/vl"\%8zm1l.?-pz oj@ 4ͩg_?ߍD(M^p0Za`1vx1*U*+L`_W?o_M?G̯ 0Wg7Fw|ȓ"2CD!M `r0M"_lzNڢYF3[]\K3 _IRm25v-d އe$+K~)}m$KƙvƲ)`v͘:~M'@Yގ.,f1(6ac4Z”~eJT/0QWifKZnu9 .z4{=tƾ!Gy-)[PO7}y^[}0 ~8R˺?86gFDe&]:l4cr""q\ )+Wׯl%S?M>O^ x'g) mXzJ#CF?ν'ࣻ4i'9#JL9˝Ui^:I ӆ4HF{%yO^*W*dLq6vl5ZF[?H;Ki퀳o Xx+nٖh53B 1gJ:fy fg~e)3K8<ҮsN0_uviF,<O۰QGeizCB╦Hu$ A®Zg |͟O팬 WgSbn|c:`/%~Ñx@IDAT>}cu%Ƥs&%mc>ҽ;hUl+5zJ uNsS~ 5s\n!̙/ o~5qQ`>Z  O'֐k~K^ 6ξG̘^.y4㢠_}XN Ur\>_0)FVʦ2 v:djXïˢ~#8;#n؉W9hxI k೷qfuЌnjd#ڞ6BG}O>7虉6 2ԛ/s33!zbq`|HV\r6#D+lH\n_EZY>/]u[]ÇۓBkTZu E1Cc~*]~r2yڱ>@-J'T 3 1__r ;+J`Ź*©^T UģDFvsj E(+RmWen*vU0ztQSb4*bG4Ԙ~L4LNEvҶ -r{sq|"ۜ5pa8p+]R KtŹQ4vtN CgJN}5(?EW=P{`0c"aFx,BG\hi濡[4ȿ'wŅtEr1? u_}d)( |9~xmϟ]Su/WkM5JېY:6]"#xJ%nlF%& a1;&̶OH5žhy~j3Vx .|x,#f)Ѕ!r*ש\% ^ `EX'(sP8{NMW+ QLSpac >pzɡc :1B6QНRشZOhn#xL2 >.liņ5,*>Q*SoeRyeOֽysꊿBu`E_?7f=OfiH֑U: ع 5E*j48"D9˿ALX~]xX'&zg\k\LX} vhtha5hɀ.l4=jL`v~ሦ۶QQ2MVPL`)otoCv/&S fý 4ec7zSkijǝ:4, =-\݃@Kԥ3t^{,$jINi!"t ["G P>3W?x5h?=P&@z4cgHBB3>[S/gi|0#}gV`66y=!M}mu OďNpFE t"C";a~\> f5]jϲ b9)a,*rƙotyWQ45mNPos2&Z>_q3m 1J dll}"PD+a< Pյ$J9kU4Ln\i%nZDr'.#~vNF#mD6hY*  ZqĈf9#I)+L䄃 "`n񈺻v-NH#eH.hXmX ;#T 5JCㅸAg_+般mV CoF{TƟ5'?h-j̤.:̼Ph-=-'RJDu=̵_Tj Ӯo'pML'ʔ6֭v!Bvam>͹=5 :$;9Z| XNsOI}[T1|ZbO+ Bev4v9&;CL #!PQJ594aN7cJiIBɲ~$Trcq1}"|<",'߾] Z{۶ nkEkٚhu"6'} `;~jGHt/l(va! b+L?qU[#b__5\*XI|[>Q?o7+5}Vo)u>u.=WS;bݡj;gj5O5={NxTx g9s6/ wA+-"*:}#CWcy{Zk֎d|r̢hOlwF'@ 9k$l@TiÕD4Դ Qt#` e@ɷTAp7!$k)&HI92T&B,'_*NZjslf@{.οpA 6Br}uN8ϬBPtwso)XRωP?G[ %7 ~s+:ѹbVD˾=o~_^^o17iMڑl2W;:@'H۱N%/yq|X<  F::/2d:`,5Vat*:8RSO^ԈC\!bhܗr e:a<8-Xɕp#.2rEL/ȟc82l jcd}Ş:ۺ>@1CmIg~߈cZJK` aL'צ}l)[ߐ2~L -;!g:>nYw.{>yxKր/7)wO;j\*3d:fzwfgwn9:/ytHJ @sp9{V^Y`qD*W|_Z8vvVks|qD;`Jhnu&ܰ){ *"A*8\)bf68nL*=_j# IǂY@IzSMl煮Ek--NQ`wglozNciBml0S/T[ot^U#m IHp`A%K ud_-83ٌlH`"lm62֖5Cp_̙4]xgښ1L⣟Pݣ # Fzٿf<%bV(Sk9@ٚ< рԔ=0q*ZM58JQRH;"M#u 0:<(!+#G9ƼۏngBqRfCcF说GR=y>ۥSfH} Δh|;NܰGH3L õ6hK:ks)n;矨f-ShS UklJ.Gca5iպۧ:M]V] yX=j`t&67tB@#Ռo4 5gN"0nq}ݭ@@F2 7"b =񜝻Ek2^aO@joM$I3=3bψʨ5k@w`䁼K@ E /pg>PO<!f0 2$R-]B**X}3SSss wXfOOebBEPEB`\(icm,5 ߼:` oYF)re{;`4HP, ry(P4K>homgbny"|/idU)%WKS1TV;fUv2/vt9/PA>F#JNGn$?Ky1\_"J1.^c/fm-1\)S'U(9 @s(pngL**+gy%-3n̑4420d<4ڀKk*E1N 6T#'>"M0Oŋ7B8 ƄFxČIͦ5yJ*CI[Y;n1{ؒ g7X1(7̂I0(APpO7R]͎!/PB F9T~& ^`GL1It]y2?!Mt. dhF 10k,ڵ(ƛՅG# ӱ8oڝZґGZ⼫QpE3XO&E$DS52:+_k@e؈ i: +|*&w6c m27Cا5ķ씏-"xv(&6D.*Q(^[ g|Ҋ|B?Mx)l}[;>[b>Ψ/c)X`x(1iYb[7G΢O^VR)lljF]HVl)o]V٫Pq"hI~wrg crQ,cG+i^\C 08'cAK8zϾwV(GTɱ*TPx]gC_tk;6nLn|:+Yq d7zTYX%n[ `Dd`u _U7Q%~sT9 r GYGR\ۡ6[7=p^)-f5>CGHǙ_tpN2r0tU/?!rh(`W%30V/R< rHJG}6WĚ\1qZi#Y 9m'7\ re9K,pK[v<]~J]ɣ>ϴ=ЮVwC'>?''Hv'] \տk7EYk2E S&K$A@*= 읕Zk-h~JZm]-B97oy0-F7={EIa  DVNl2ڎ`c׀T* F# PNl+F w48c~dsߑB<ٷmB3f`K8a{怨V+jXO%.W cG1--,P-?}Vh%~o, Ӯ;M;`۴B!AM!df[%.YUM "YN4 h/jjDz170RM-oK6F-,8yI0a u1HQGs5hxlKw0eSye0 *Js@l?sOQbzHjZ˨Me6#`L= Ch4Ij)J Si1uWSGN$#T:0 GՀ"DK90CLC@Ow=4w^hGý1C@ƂI+Kܜw%M,LME3w4z"fQ~Vb.ĎI9FY8yoH깠:,Nm`iGj#vu䘶cUG"kZWz HgSvsKYu^]y,,hkn=wMm<Q<趍/^i2.@iHC5VN{}CM|Z69*؞.G8д|v{Nt{ïKϊ2֯G7)*:ܴٗN3[]DH6AK_>%?׶Ֆ~~jAUL&˘ 51Ҳk ĬBaUD#>9h/7@RhK<%1U?PrJfVG`gT-wj/4_b w=46ɘ*C{sZk/&:60o3WߡURo9VhTN-a,'[aw9fM/Io cK<%wץg@l[7>MNfyg6J8x3s'5眓 - #z-{R-;}Ŭu6tmVOa;C;0 ):c<cy ь"JNI^0Ce{M.5)=FInIkWhd/X,i3k` 2P&>ZAp|UtL'2H::$ )b 4Am0'ET3WVoV5K7X\I:Qe}CG, `QƘFHX ugJSFpoO 3 b[224-ZB~T͵FпO?K{f@j|lM1Q](y5E! PvɣOz4('ŏ`y"fO4UA {!nDa93$1A}46@jBp 0K:PR\+ c)l,3bl[^fCdB lXi@5{>S1& !{ƒ=L,ӣ7<ށM>s*hA6{aIUFd6 vsd?@ #@ V,bեwZ > 2PA juM',/.|jJ.ؾ%G⻹կEK& 4k=Iٗh#>kLNƝ >Zs;# U1 e&>+ޣ&V㹌J5wS(j YblA̐~t~MqF@Ҟbf < daǘCt6%A%uqݨ'oh9%N!JkpղmA{`I4b"@H^G))b3g%|5k[G(F/r'+P?]0&OBT6'@ŠL!qS`& 8Mi9IL? P`lL=@P$L B\E7G9 L.t(Ґ^fIo7T1s3 z\`j@&ǟ-eWRI֏,6믵ن44uڷ޿6TH#MvހuA~?xWCbuTbߍOˆ<-In8wO.Opi݇D#|``F2C=ʦ6)eğVl`nshe',@qEI[T =GH2\fC|/BrB}U`O KeN֬ JS- R^"D_Īw{ k]Q|stOmd 2cf0f=ֹ9 Ø0ɉK鞐e&M,#Uua[fyKkƉ eL1Hȧ.k%e ͞6{_74KU>*rכ[UbEz#`f:XT@L600~9 od&_|'F;rG%\J% OCSCsRx$ /%f7RW.Z]Ǝv>P. V؊afjC]Oi9/$;CL| *@T!ZV!B̾/:48' !||~V113 tQ0h%<^`3y!ޔ#VƤ;3+u:1gAIJž?Hpä<ҞeRC/wA<afڶ0}E?T%SnI8*]dRɊm$1 0bLde ŧCm|23_hHEL &)M@nQXv x%V3v섛6KZU1QLF&F^7z֚Iϟ dL-{g0S8)w33Cp\}tdt#+w߬Rgxwb\oԎiTڈT؀*Ǚ_Bg|~s,1 T|[`@KTza} qGa[Rޓjzk95odum7$FiPU:|@2;^VB€!A@` xj_1 z>!㒊a`FҎcP̛2d #5Ui AʏaQfH  ˨eFY7"d5_A9۲8Br{bG{ݎ +% F)%+ERԟ)-},B .je=Ԁ&ci,CI.[E|$|K=Y3M^T._\6H̾[9Pw<~[҆ƿITO :VB2},dfw7#*\u*4afU}EW~ 'QE;x^i)DOnl_V5w2tZ Բ'lVIK@Qϱ:$d 0 q> Tb̓?̠t%ӴDfЊ]@̒?`;0+~\@0VU\xt8I 1}IĊBXKcm7a /cHԎ) cU1[p:ӐSZkHoمNj_-H2'ԙx 3y`T<䇺+`^+=`-U:ٚf TɺE(NXV6IA)9%`PI\xFSG%fF%IP}KU5B4A8\%h1 gĮ-Uxg1f/9UMy) JVXMtO8+%1 ԍ3 ;u1z}۟Xmagec y""ġ^hBey炭bB!mn=^|}џ?օm"̢~O e x':7ߝdYQCCʷ +X|kӓ!\^}NK5V% }7[RNx**f\,v QP,6qHn{񦧕a P+ӄ)ie;6WğhI}_+G&~v Kǚ-*gbXVWWFȘj d sz60C^B6#X Zn_:- EC]  ==j[w°;p ǚ\2xWxo GԒ#L +<>:lI&Ɓgd=v;xffB<^ o.fYBc2V]n͆EĴ/ilVMqݍ~r*ZbK>@񿠶:pIZ/|Ov:GZh}mj7olaOi`_٧(b`iH|F7cO93ɏ9 9λȆ,i4k&!dk:qOBLTEi{7uf6*O"Lv w d-Fi<5 uzb%јFےY.1;:Bt*s*<*12jyBp3"Y&Y:2Ն`LXJ;3Œ0>6>*n{c_㋰kL<~,YKK=u+KQ&w~o$jH& vX `ϕH͖mJ(uݜU~.S^#r ћreR {LnCHk쫒0U`%؞T{^< dZ ?h PyѰ7{ & 1OgV 6naMf(!LM'J,҈EY-5bZ PT' D| ݙ҃힔/lP g#޳R aX1C+:ZѪڊ [pAyp5aE&]}kOhV_ $ nv BA%PunR N}H t *шJ$,TR!` 3VHD1s3W0cŝc97n 񧜖K%k ehJ L]1oCf-9EQ0n(l s,5MYgB9j||*Fmd38{7S$Nay:( +L*%K3D&`O2i4+DD|M Ќ=mgZGPΒ1,4dmrzGNMPh)G275d&#Tf;Q)M(AX6g5g]-grM-tPRO-eIA/ir*RvM ܆&@.mi?/,"( rUe14/''\ŶU@j*]ʣPG>J zD?{O~rlK6a[R|2.3`7>@7 ޸ 5lhaW+-:(&jGY?!o\I =o\z]ˈ2"= he GrwW74'){7^ @GL!Ѿ)EAH}g`L\@xC,OC%aӊ8SQ ^#KhM{׶~f/x;`M r6IDATamP j)`giA/Me]VR,uGLޞ6ÝMu.%!iEuCCuIGGgmX=?p(T:Y'eC1I-XͯN6n"D$.3oO2ͤvkQ2:tWXM#AS,.$Bhb7/w_-`;c*7=pS0^WRSj{;nBSr_OD\_@تPSrR2o=}T2^;Ep e0J,Mލ{> "32O/ܓ"Vuls&dO (09U¶PNP]2hI`}C& !#(\A }̊8akG+[t3pYc;롈鯟e(W>>O[w%sV䑎!з O1ꃜ ASPM }\򟑎'\V 9tOE>ŧps}J&[,, Jj#{CGojqWC  5tyht&JPgt41xOE-01fDA4H2{ a 0bvZY>3 xFEM(p-쩢7v~ ޺z_%V8L@lCf,a !?^qhVwW W!FlqAk'o5ԟ;'z>$lPF92J㆝E#aqprҖe~YxMd$mk[꽥'4̴#L=XbnRFO?Q63y$0q`F'&ΜTHՏ1rRa`;>2)S0<0T @Gfm!@-Hg3&r+<D Cqjf\aK֓\~ɝ%4ϱϵȑ!` #Q~K[Œu4ŷʃ% ~%W8?Bw (-W"c.cQ*;p[UuoߓL@Qk/57__ɾunX\cW_۶&]CA,# PgK@< Ղ@P3Bɘ؈hP̸F >p .vщ~gKpB7QGǮ%Q~;BHxA !:PGbq0(⊖`wdXzdN zAmI^ʘ$YJmh Y<ncˀBSxQd$E0(cA"#,$c2?{wV5rpŲ>2hFЇm|SgOYXLjysOJ4&63&<|VL2r7t \3q,'o oDz?0eQ-=FBiBtetF&`x*^!@L8zX>'%p,Ƶa+e|=f辇`Cm0 3+M!hVoI!~i)ā)0 t\L]ĖI/= QVU*!GP`W$LcȅY?"}? 9%NDa.oЧY#u FxY]3wbD`7J?lwLZgL``jp'P}/u^0OL&F~߬R"0,j넛59H(;dzy~;.ZtOFQcQ\ Ϭ  ?ZZa3J /K%j9 SU薶 $i ACőX5l`sQU2&/9^6Unc9Cfd1;\Lnoaa*䀸jZ A| XyO% 3 Fc(8pQZ1$un|sO(vHqJKז 4Agʂ(LN 52 e V dF`, Y>=OI0Fr;FwŲwjg#PYz)Xg&˱T,HP%YD$ak&`Q-w#P;ۉ Yظ:q @I^zl(#`ɃS1,aͮ6uIvaY%f28lپ*!`ߝ҄Vl) 1]eX iHڠ6gh1}8X B]`us9pr̥~o;_U˲(Rcٛfߺnu$KDu71tF0CSa藝etD&@kQ.(`&~9*I~B12)gj5 *( "7BjdUWRQ_iw.dlV c\v [\+LG;>YG1;?Tˢhw geC!<_Aےqqyo C<$IA,'G(?=S-,?kui Ѕe|tga-bH6%S>Br{(d/>uci ,1?so'8kvw|A'\sll-ws[lI&Kd 4X~nwF N-m"&2bp !{R-ƓJS ,҄6AdՓ0 6cF "]Q;#z':ȓ?foP!-AOC8a` A9eE0ē`@M9@VKrX3YŰ@]iwTlK믭 2&'mB}̖f^F0Hd7* nXy+2Hi9Y0տj_y-oJ`5l||"!Vd18a-uL zM4Cyeo(pAb3 ^';`5ޕL@]M;Ec Ьd%8 ‘a* `+|adE0Q}dc xʉ1A$;TČ "|HgD50!$D u' 0h#dϑCxC6Sm/t>$(WT$T@MOa,b@awN@DXL3v X !xϡݬRvˬ*"L1oօajڒh [bf;ڲD9vɊ 2+r_<xmrn% PIG?q睗Ww{emKzJ3L@Jh~- P#dW"P 架0"Pf1@v^e5X'Ƒ&W-1Z$WYGLO"Wqx#y0yF%:' ؇% Ý+.nҟ-ȗ퀶 H5u !x1c;=؏KE ȟ`?DPn#^,Bp/YGt 0ut~hW|e!VsJhS4jHVQN o){K/u!Z]ZW-4@&1 $6sp{W@Md ıTq-2PIJQHL~QKʧ?dPʥd$OD?0PʢC6'p=hb~4` V%Sr9Z/oveJP5O1P'`2z!nl$IZIa1IVS Vou*z@2Y ;3⣚{' W|lkGw~ԛVqs=) M 1F#Ѵq;Zۄi҂rT/,(]x1 y; Vs?{c< rcrFYcx lsM ŸD cՀSʫG^t,䌸H ƖCt l=kU];Ay#ߊaV<~-c:d9=sK;|0ꉎaçn}(g'b8?8b/ϭ4L'snw 轛.4k|X"\"SWG 1I[12 -?# }.'@߉;~Z_H;\$yX1ri,rC1N \Vc+ rl\"7;p`bDsWj_-bJᣓ?\6eǝZkI"JhH=D!z_m`744h5&/=6N3~}5 @zt!*W83%-% 2;i.9.W{/@(u~E`zDt@ȠgS:Y$"2odە".1LnM)k0N + JYOjJBVIe0UDU >rcHOjgpñobŢ\&7K/S P9b% qƞ'xA@x /wɟy}E<;B888Z|d>D\3/n:iv>ogs=5"aa 2-Agiвِ=1slLѽ8Tߢjb$~`OfH)&Z2XDeNQ>/#p^$2:Ь-}mM ,,kf!&hl.dJ$]hSa:v=@c1Q&ЧrRFX PpBrろs{9#0 W?p뫝_5[_IZJ&ER,h3}sh=dL3ͤ[U0}q@6!:bte|EŞ(;2Q' 2,>Gd=kD蓝fg& AWtZmϚ\?0{vX8pΐ mU;OoIl-k7o7UMiހ-.3U~Usp"?]b*)f׬+/I-0 6z=v$'bO2*4=`+HiDV'Rr*7 !X1i'ŌiUߣzsy9v_ἙLiKoStEe_2~p<͏?LW-!4G?xfD]āмLٙUqϧ?!ȉ@8=@qObӧ4!d1 T cO!w:#py!}pG<.{I+/z7Y%c KdJl$EO|}.KHbԈ0&(aVgi=JuR%;pqB v[KOZfmHjf6HO/$#q;G5=xRp`O+2s@=*33ƛR&YH)T "XqQ% @?t=8"@a9)dEd9sgGr~gR(˙R99PGbEɩ^\&> zI>akBoZ?ĸ/ r#_Gty`Jh}c DeEd?nRbc$^C[2~6䥲+8TZDF|i@(j< 0 @DŸ3#P]ZITgHQz?Mtaٿ!:izi[sq"C`86Xf$x _!3i@4xlu!p"ASo;# 0vJ>q~wWZoE贮nXX{kQ$З ˟ul hNyS-$"L[ zX׻d Lh0gaaH3;~Cc^ˎ> ?H%z?ytGkzq5+Ҡ?sJso'X)|_/Wi[畚3 +>}啯/Қ/^jo~o)KF @^N7yNH$9.!d`IDÁ<}\`[yd$/Zջr5&F84YgIqpLH#<:ıRD&=ι|wC5kpk=Q(q,I46\s 5U>@;w~pkuM/%o}7[74aP2{ iU#(tLK?@: ѐJS[ĘE*yzeLmL`^sӓu,S 1^_G# h\eʞ}mKВ3vP.,7df z {vL0B#@ؓw@ Q#4lb9^31B \b;i]Xw5xވ_%#o}.4jPs|--.X}[.[}MOT ȵ vY"+S~;v+EֻdM>T9Hg1' Lc Cy ctX:1%qbU*wCgM`M7O@lޗgE Em+Wt{>V]HӅZ?JVjז뿿-}#~-~'Y8n*zlYF}#0 4%4hx>EQ!g:}a2لaMk 2WSV-؍!"/sy98W?jhՏŔ;R3oVؕt7Ũ@FÏ׷DtXOoܬE^j#/U}mhg1"j`!B*xLDb t~ %p&&62aVPiY0_@':a sg5IF?q;=Qͯ# AS˞6jk%#5KACq6ApP9 A]ā'STH->XO-bQ~q &~r B3yO';+i/'"#~ _}qZ2 gPhy4>Qxs UNnK v9I6wH0[Ld9Nˉߔ*G)K2\_u.!9]"3G$;{.}9lbpSgtsp^>ifQo/[\?i%-DFFoZͷiv3tptQbF,P9 ~"$`̀ DPq9 ` ("~dY/❭uV7F83w[|N?{q]j{IgL)3;SU8B1vh~ggfI=e}bWܺnAF 9/ߐd+Iqs}=}_P H2n\p#:4rZkPFW;-(gSk F ʃP|6*ލ 'aY,Rb2PJ"I> \J9pڔ0Q?ԓ=Y=*4v"ȧbZ7`_!# i88#jDC`' 3J0@13 ky n|-dk-n࠭m--+Q0]뙥 `"? 3ӂ䌃)JgShapwSmTfPI6{s-]5]ڳ52105- 2$Z+#- kG-4ߖfW Od[2~bؓ4 H@ b `̠ve)Di+hl5 bLF@Kkd `g v` J釒wi(9'?"]qbTӯp/Y>Z(ղ}\&aO_98{)?+2y]10L}X sp[9ܑ@jNdnwwWo??RB~9pQ szjhǀl `{fA?B1?xЎ2C'Qm ?T2$;n@>>MYg ;Mz1L좤_.SF_ ~W}}> s~3׸/[գrd$64 |f hֳhՒ&wH=ٙ!bS7MFac8 9 E&XOYfQN(<*M`LU anKVKǂ9H_ψCNըC՗GijS߄Gow&D#w;:T$p,\&~=>|=H^_EXfXhSOR6q1GO~Ww1HD;zjqRa *2#Xd\ < /%l@B7S'jn$lad/E9Xj!sO(tb6XΫF;ΔgNlFtRmOU'"?twΨsG! UokR!<^[_'R.Y2dڢLnAjȵM#r9z?ʮ@hʃ,[H0"xuŐS K/ Fo{+2$?%@{hqe(+Ixg6OuD^C`zsSR y#?7&gm=O5 =Ydjri|@DSB#Y\@W2:ⰝYMa>cnǙh:X>q F* AK`r" t yXG0CXYr&jY:/sbQJ/ > ؿ"`7J$X au Jheb3WBޛo_~z;ke ࡋo.gﮬ׿-f@g 8Rhz + ;mCqVk06IHUNNΨKc ҄o) /56ROVfT+ﴥJ-OV5Qf,6S1^mӂup~^9"}T(4FHk!q|# gV.UEϼȶUJ)xjQ#pYvaY`&bԁx\c 5n|xx_.@w4⡦::`{x?K졈S+[KoٖB6`;(a,D@[<1XiO,y[x4c8L2c;9:;7301ˎc:bSL9BC>;731+5-l$"iw[N^R9731-).u "a KlSHrɲ`8731-)'%* 1\PIKJ?76731-)'$!_VLF?;<9631.*'$ TZH@`V6631.*'$  g\H8}611.*&$  fE9H_),)'$$? J9/-&&$ . Z;07ϟ$>D  h( I0'Aմ8 c1 SI k<' 2ݤbC?! i=c6"DҴ$ x  i9!  @(Q0'( ľ Ǿ÷ǻʷùϚºЦ˜Ȓґ፱鏍͠欁֨z~~~う{睜}}񬕚흀㏔ׂ΋֟~򒁄ؙ헀󤀅Ҷ뎂󞄍ſ|xpoqz ⶑ.ی ߈ ׁ ؁ق ؀فޅރ ۀفۂۀ܂ۀۂ ܀ހ ݁ށ  ûic10RPNG  IHDR+sRGB@IDATx˒Ǖ׼Ue @h:-3dW8dxƍѨntQz{KŢ#S9X]O`w-dI_r,{,k3 4p\:?,?s?Ѡ]9\Sdl`-#GH3ӸPy9wro"WrH;k@繸ݭV=h:{Vy|ߩHD@D` ?wTްJկw EM<;S z@c3Ha0Fr=:Yt֞{lWifl*4 e:d2y.V 0G&@bt`5``7~Dy"h:s B}E3xߣ[Xx70Sj1h&? ~l3{\XG;5߿ݹs'^aHeD@D@D h `IUD@D"]Yqp2YT~{|4 {у>ni5WLdiv;/6xsPw|89:tP&2`4idр4sO+5O.aFOcs5o*V'5ԭp~!?QX `fe"mfX.UˬyyuVnϋcb/vK#jЬWpUdl*=D2[īiNF-GŲhomu8P" " "4ДED@D`+O~i1ho8]t3,7 5jcI815a 77{~mhgC*lg5x:c kZq+=+Y1(=|mь(f'C0G&F>f'$JO)H2]LsuUCh+#Cy}>`ݫZ @=8X V%"Z$C)=\+!Y!ƎWմɫ7]M M˴il9G˂x ên5R]AǮQoX\HcʒaWk;%x A ?$dcr" " "4MD@D`[r:Njk0# lkNk, wyE)i n^Q7M0!>4Ãr+A" * ۍ7U,l?7ɬ7CC?AFӈ 旙Q4`%ѐ|f .a .o3 `ka5 ԰$4&(vyo;m'oZ_.Wl.93^+GRFBB,@ i5xGGxC݀mOcɂzYΊӪ -" " "l4l[D@^=\ {nq:rfٕ;I}Uv ,8k={V  g,1ɢvtfG01?z}p͎7Ḽ2,Ox ?sTCFJ} Vڇ6Mi1|azA%iV_5yҖ3-uhv|V|Ryq^wyr%+!nkpAgrceHcb 81ViWy3f?ΰȮzdïdu~};k),MwO{l0,A5Ne&D@D`{ÛoW~hW{0G <0Q9w+_oKѴ`jpT`YW4kN}W5lG?An-jD;˟uҙ?m!>}{jOmh Ϡ= m/Mέ :79ztk^H/Lg(S|ϾQdY]C xepInA`jsz3?:oW5sah,ʼkm5؛͏A>\dW:Xz`h;mLLA]~7ӷ'p{6!-EaJ@y#>(zm{|V`"*y8c^ N_9/6[dNW,؈pe*EY_5J0?q6@ †&/Άl|lbm_[me7y5XI22Y/FQzvhW8>qEr" " .7~{{fQ, c5C y̰994 > ^7ڬ}צXroYZ؂ OJi` SB6e \G4}vpDA +iRoSiO}ͭ%6B^t&pQ]^~߶5 S'xp4KhTa d0ai.B 6=IUP np!omǖ&F:Pn0FNP_ikmzd} X .}tgϓ>wUHOiv]{70mD]m4ן ٙ|#^5ίG ᚄ0f 둿peBw3IuכC\?1[͒?) ~iJ,|dŀ ; aofYwmz<~ 4$_D@Djub" "N`:擯ǘS܍qvW6uX X|<ŷ4QL IC[/^Q)5Jl"+fia֍0'X}IRHeix=_,a kE bk [DuJimfeyoY(4ghŸҫ=hq`J0'}=|+g7:BY[e]M&V%*Q/ryaoܧ< (4oSaK[soK^1L}6OtZ^"OU/Ȟz`ZF$4 ? yv͇&~X q {t\i(GGŹ4&/B)K7 [ coPM;r" "  e|:KW뢽w-8mwݝl~_7 7ia_Ã/^fO棡,v;6| pe~$#Mg`BANȐ{E`U֧6q ?1n:si#ɓLI<ʶ`eQY\l+?3YaZɘu.*!X (i%<?W 2N$,݀cXa Oz>Pa;v<{K(~wѿLS_ " " ? tU\L46L&շQQe=\肧vo1dOfhbÅB? >d1E؝vA>Z7nb3H|k1~_Fєoq4͡^(ln4mk;ԏ5Ջ6F>;|nWpH_D^B}zo7zm7b硂& gD8^Ұms&[Up@iBnt09̇ẢOrrZp>?~=?pZw Dq,[%:HX4:=mOq=` AD@^xAD@"{oh|~Xbscu |u AWA]Yմ"ow^܇!mI؂ qPOip (OC@Fz68 x_.W}Z, xZC;4ީۛi12zq q.K'^bYICua۰܎fU*W n%c&~g$ qzp-Vw~< JSDlce-{_ߎ2NB^7Gj4Ó~&29;meP6PxHefSTfp:_2Wz9?a^C[pq 4paWlA['} 8wxfXx|U<6=zt` sVcxlgc]FY{!h1COIaˬH4Xs<fAO]D]8/ i-xQ'u䩎z]v:vwʹ43 ~0lM$)&O EýFQ b/ൄo'p-0 ?$snA)?TmWp/2˪7Oz9[gn9qNg-!evEtXR<}hwGyI* " "OZ5w' {fQ]/tpE]a{ ~V21 M)Hnc4G7Uqo]Ù/,OSB|zJ|/րx` hr V,ؕd-D%BpZ[P޹al&Ub> LBiuzO~?@!wu/.s4aCip6gQ/l.廔c<7zep^&3ùQE@ofq`ڦKy>y%}Y,U9yj\e-Շks w3-0;Fa8/Eʙ ovK{ƠʛMdm;6t y-vnY/3,_0[ 7XFr,UV;n+ëͽO>poF" " ? dUO -Խ85XӦ.c=L i})&,HJLzp>-^+N9v&-1(._]ȳJgJ0]V:@}I3iҹˆe<lJcL~{aLX=S-=-MLTUCy!Egf3 }`DQ%{yXzZ+8ʽ2ʐ*R7Y [~`^sqv r/䎿lw~yiHAO~ACsG*ؖ˰`: `||91X|VJiػw8ObHOqGvE 1`Ͳ{ƍ{{ '" "sI_u@B`*ף1Ctc~A^vX?d`9aX!S3o2K̐U7St ` ѧ]ivC*&$FPQ` i=a//ʧp`zg8#oo-pw͖wi|/ a/O o;` ǴTzv 8}iX[{E!1?d.5<#ʼ _9xoL0#h|Pq!”3O8rwP VMkN/E;n-Gn\5JAD@D" QQcpвu|~l80_"$p`fJzT2xyl̛d}fQ3xr؄'9l+OW㝉 ;>Oq Êlꇗ"hᜃNXR*Cm $^/j]SU>nVw?M/w(zܹ t(ꬮ$" " OF@OK" "p>{`GGG cӲ̳alwW1F㶫 +uhZIpW%pWxOߞg63(YNw/^u\nS+(MM,)4s}!̨}NRNG[-xSy-ff|[_,y2m,\>^GtGZZH4Kc\cϧ6`v']f١CQ'cC`Qm~*)* Ś܇'FbQ/zr:˼9Oeot[w==:Z޽n{w 8#" "4_D@43 kWr_d֓Xx?8~ٲtz>:E{zG[[5iEpe~&xj[\u9H`O ҧ3͠;7XB枸Vg{C`C$M{ Lk["Nӹ|KGq/\yr,=g:LP*"1S4=#&y|YT[T9 |&3 !;/iVR̳2X;3rDD^MR@``s9(:t^Φ<㧽y1ga;l4W,pwxnae,+E]V4Z,Y3z:|jj`Ӓ4}$0IUEۜͽM cylSn ܞupp8{4b˶ա7b,)>w^vU˪n{ˢ;l:kˆ]Ag>'x)4i8@Hg m&3 F[黁2za43㩎1=xa~m;pl&n&3q&zg^|(KAyS17 oME'|xa^;&"Ă8/uiNrfGqpVA:ѵƳj ޜe8,Q3;Y {U%p@ʲifYʭ_W_qA[QtOr" " OK@OKND@D`{~;ndMn]x?ౢٴ4st"[6S;/n4[P"}Sw |ȷ33&'{c?gvksZfPF)!ȩg"~4e抎y<))˦!Cz LDY=5H&^ڎ4_ؾ *lgXZ=~Vmr4LRBP`-)4уN1T?>bkLtJTf{"GC?fSH_R9׈VUnmnV[]{`ySraP*1tɲhT0c nϿTLȉ<# <#@e'xXU5ߣ~ sf>;Ҙ#l.;xq1zeWg5:X<5 8 %Ѓc=<" 9bI3к]vajR~n Ng O!wFĩCG=y@pAeyoycY 7i8-6)-߼wz}oq><. <.)鉀i>hw_+}utz@=g7ti マ2ó9nїGaseDV/MfW:=i4eGF |`Vuc2tGytiy@&[DS/S/a&[K>vɽWmr^6&mKzKZ*KeOFۜU~0ux]Ӱ"i q6n +i gԯ*[//2eq18KuE3)1>}‡09I8]t_.v;^ϊn^I" " OL@OLD@D`;?`ϾIMc'UQ|ϸFz ~tkv}0>wU0^/1P>^=+D'<'-F4۾Uuhx~ Dz3lA02-ԧnҡO9]8'R.Km K`'4! 4jsjHuQ[KmaAf/hJ3Py?Rb͟ d F`u"6k-3քI$<ǧ+o/#lZ-1 uJ} yh!Ewʊ/dIy4@IDATԭӳSNJ_;ֿ4]Kϱ|/aWt#?Fatx'yQ 9g:f׳3;U}b`x{=f쳗*Ҿϝ2oOO Tc،o!2=J,z|@'zzB7s%3qһޕnFf~~'asQmM0eq3Ou`Nl ݐb3 4@?ԉYz'p{{3@67)/*Sg  x {a8 '4X"^7f5pѶbxf;֤7 ww!@0z;u " 4}:_ V~?;uU*?-O~v +o4ƚˣx)n9}' {for>.bw{6 Va,JPZiqfIEnSNi-,%"LggM'qe~b$qi$6}.0_t#xXSFGwafmv:4R6T/ql (mԎqogz^ٰ6#|U5ٗHpݍ[,'|]wtt=|o7aY)&"&_ @?,7zkۣ9˹z֝f [փ|_T|t%Ӯ[x?}[ OUF)b7#:6)H)$0HSs)|* xmFH-G*1hQ(xSia:t {҆n ! 63)'lH*hμϥ^J[gdyx@#yxhi1LAxzR9yO qRJLxN\>VhѼLOѕlQ]5 #*fDp &M,iWo;9*eO3dHeE@DK@د^'."I޽ᕃ^Q^m(Yv ;:ȎKvlƯꚲ`UbfWW10;5Sjxtx- >.{\1|R1S3fOQ?7 a2E?conSzFdz &Y-Ǵ'|i8-g<չa?wyi+5mM9jӰoF>#.E` p?)ϝj8E--& {x{!&> "g ɘOr^<6r/Vv;D\ND@~4u" p??I [vO~ߴkr @ ]ZٱT$-5ˏdkY]z){'&9I_tO߲eo߳eȿ;s\;MFM⿦@yP0EInw纽O8 hg]n3|f5!K7KGXv?n0V_ŀk޽@n9ÀRڅ<̳hQ>Vwo;9#]" " L?,_^<*55F 6fGuL!=H0W1%|,aktd!<813_w=͊ +??fd3p4=4ɣo(gZj>Ėc>"3'3*Ae$$|;yڲ^9yi/Խb~Vyi̫y=-ii~w#b ʘQNC9~6՟IC} qX+ 54Luaf#&2x-GlD PͱJkPU[6cR-/<)x䋀$KD@؝~AENL…8}O̧ީ+4t@1{^V|Rǎ+Tp U7ؒz+ .r(idw'z)!$I ;ug/貴digo*K3쒧:?gр?O缴p^5xWTpgITȓ, 90OC:nW;VФ[\BT[гO>gXU1K&˥YYlpmбk]9a]Q r" " %'" e|}7>hL!Kzh>ȪzP4؋I<4l~NɷP ~&OLC֞V\.Ƃ|&HL5غ& %@:9=͞2=!_y$ Jqy WE%=ͯ3Ml_sT;w]@ 1[ ~{6ycE!¸2~jJJƢ^ T!8>sE@6@ϑ;Z|oPWߨ!REx0ww-pF94p)F@OV5WU^uKZeغeEzy44Ѵ],bPǺP+g0i;e/?O}+8\4ʖSDĦ'e1߿06󠍔Lیhk/^k!mlܦL춅4oץ:F.¥m*Jc7.a l*Հ{v۴kb/0ٗ!Fe,ӣݻT^/Fx:4O?-Vos_TGEvp`}˙=j}Y1'A^̧|tӼ W01Afҿm^.>##>G^YC>~]  +^{7~g}t4L3۸ xfAxNeyn ! 63)TwҷY޲J7Y:f7!}h3=)6 bVee]Oc,8Gaq ,*` gd1q ܸ2.r:ǽ|6+t:h|fSD[[-ӣeiA._D@^Yxe:5\D`X]Y1:^,F`P3lg:\ fXϗ^WUe^d˫?Bُelr`Nh1QAua|\OW뮮r;f#2*aQ1?4`LEl`#&g#^R6K.K\pzi8=]T煄QH~Q4Yyi?e |;&f:GpƙƏ;!AeX:ooP*iflA3fY"|-)(d&Ϯ6EVA^Տf|QsLZ"f#XlPNюZbɖnf|?R]U右٧3mد i" "" ; 1x}ovwelw7Eա?:|])kXGlp Fmt*Бgɕ ]tE yA Lc؞YO,-vi1>7!ew {z]g!a ilЧX1N ?1[\݉17&M;6vlMK YyChpZVJ(u+' 3?˙6-IڿAQ3Ґ$]9U 2Nsܒe"ygA͸GnsQf80/[UE#(>tnqw\Y#͖0-$nƒfl.!fcbQ4 n, cZp,uS?ϿlR|9x hx[aUaWE~M^aֲ7KGjKN t$axb4BRM Ye:>A*Bטՠm [R048pr0%naz82,RtQY^LLuRg _TEzEE+%m݉n,&.cz1li=>H Q?Ҍѝ} 2w`wbkW~ |Ϝ<û0+YW]7:3y@ =[A~4ث8 C"|-GUݛ7n^[UW^/N8K>1PGjX\똾:z~\`g> dm1v%%AtkLSi>\zCO mԴn{БeGtL?X>P=N/P6G<.j.1= yi̫}IXgi'cײ@iHSANlN0?8Rpghǿ ?$}Lj0Mp$/nO=>CY2;wXfE5ϋϾ+FW^ٯN $?I5LAw# $g3s }A`0)f[B^$es8/>fE(gWPwC{{Hzv.ۦ.EO6\6Y)2Ǜ 䩎» \ď%s<s&>}%=oF< ym句t~([47ws 90kYbe<lj@%h ["kgJ <lZVIYuےOf*$" 0 _.Xw?Ŵww3燇xwöm s>§EM4]Ft(٧dԱ؃zH'z|%}8( qis~0;Cy-)cBt Σ}f S_`8E#4m[Kʹǹ>7g5"H#d|%O d7lfC 2E8[_J3.Z~?~JLh9(G* 9j=,ǤMÝ 1FFH0ī=~?D)}ϻ,vΝ;DM 42| jϭG듪0ʶ`'GσH4 k<ʦ \4MwAvm0h5{JNDe!P;D@ ѽׇo\vZ>(fyNO꠩ЍaW {;PvCaC'0<ݧefX6V.uWXzgGwzl*jT~X ,0fQar\l7j1 .Gm\KF;88n cŭ"_zmC-FAZ1v";-Ţg_dw7#'" ?; _ "IT8?ͮe7]Ӽ]ŵ0ȱzmYSx]QO0#kgKDa?vbW^ZלwSa>!['vI{G2vM8z[&sANu+?!/Jߙ1.*ǬFj"p) nU*g#`>)?!-{ @[3496i r?5]q>ތ`oqck0"Mʢy0kny9oVPő' a *E/ڶ~p8ƍ{{{ʹ:A@?u)"Tn}6כ" a[ ŊP,T`V@^G_!ЅĤЉ=vZ=v<;fO_ʹ u0}N$sH:_L< YS8Gy^U9ni.K'z/۾Ҷ?'W69/`@Q%q~ﴘ{4& {M1ChƱlk-i çcaoI0_m{ @@0>9_&5I}%O@sF<`?/bV" "ϊ_< Q|}a_󶛢gxWn9wr0<&{z#}KXo{x38bc}gR DgyF9.ɐhy.K.(;ռda!@u,񦲴]TGᗗoC{qtdL Z8gX1U 4t(C)lrcGp`1Hxd}̃06YbC@ EUiB"l\pv~rmQzr˛1[GPp$EbdSw=ɘp=)6l`?G)WyQbSĂVaƮ j\  vըgA?|ǷܹIpNa9x4,tn!/>ȧqCe$g Q[|>Tpudy=VRϾMۡQc>U1@ pMRO} i 7z }(r@YOGm5nmƷLR Lt,2y&}GmxL6-L,xeX` ɽ22R+ %;s| r."*.%d38#;^fywp|h=r~td_;mcOv{*3a r" "b hU" hg޽ͽ{M5 U}RIX;,Op}gv c=񏃢Nb~|4U};  ;rlNαrƹ>8x'tJOmLEsQ!- iǕy.ma{'M+|6dd̸K~P ~6yof1#p9[\`P夷‬Q] Dp߆0`eO+~JYbyaC Bl ;T ^nrYr1ۃWý78=cbEMmgZ## pԯ1<! s10Ās@*[sTMRdn1yXߒUYKV{f{n_ʣ/ ݿMJIIIJYI6MH - |VV5|SNDPf<;[̳sTl~C&hh_ku^VҢa4/3s˶6-0V Wp\(CtCZZ$pаF_7}K3lKps$]?^phq8~u榆g4$yxUFp,6oi_x,8ܗ&\ӻo_6}od.* Fuݮmѫ`: '4>\7#KСQ>e!t݆-deQ}cOW A4:vyMǼ5X mk+Pɠݙ;R4K4 8nj@Plx($PI4+:RB1H']-l^p rK'Ìa6ISn B#^?I(:y xnMC/ZlCS[6&+qB`D;z+W[QN~Mn flJ︸ya_R8ZYk{ٟ|E`-[G!p_` 0VYv}`/g.+\٣7Q }Tx]sU|Yif}5P2mgx'žK҆e^V)=ulj ,:p_ezgT/a=k'l>S*y,v6k9]m!ݒR3mb$$ЗvvӪLk3ndH ޱT,L*']΢UӤ@}\-`_yQUo<l3@uA׳br\>#3mA,ul |O'xl@'Tun Q# n+6Udm' $ < {]jVWI.ubf ox`D㋬/ЁLBG+CiCZ_H쯲'y1Oū Z ʦޝdes2 8S98iF4ߨd-v7O^,rR,I I II u<5Q&FII$||n1r2,͛|]ߗ4eW j/Qjiys~:fa͆8Wm:Z[WNr/s:[`8S G(Ђqs Kg=:Ě*tHʳ{Ty>yq;hn!7%7¤-Cr ]}n gq28:pu6V|WuYjj^/v5/Twf4' Y\LGEUg{mE% $ $ NONY=Tbh>lwAc>@1Ӵ0 RCFjyI:]\.@Oƅ 'ZK+3x)Ax2t[p:,`},ذ?xS]0vư8u&zOq{<;چk)ULV7\QfcXcs.86uLCm4%MO0O@ 5k΃D&M=u]βa iKHHxH^)$WORvPXϴ,Gp(+#<D֎,Jj |Rlnj|/9T0- ȇ g ZCB`Ε3>8S[`Gd @ 3p. jcZ_AYSJ ]kK cID oKw$狾4 B{7۾ /sЮ|K9.tr.tB[6 ;@{-0RZ3QO΍f8&x7if>l>Cm x-I I I)H5*HHHpg{?};O_};Ҋld"%D VvA ji["k= 6PŽsR#40 .xKIfu6pkY6je2ˀ<σ4lwf>ۙ}ߎkiW?b#۴'.%p)1%$$J?e9}j8ϤTF-X>;-Y`{B®_\SbKe@bSB)z0]Z.S>ۻU. {*L\`1>/H`VH~i$ \Rq׻̯5_sFmV]vcؓ.5)s0eh. IhpNp5:Ƴ2o-f并P5E>joQ9ӱ7jwfs%<_ʣ}qN}+ Y$$$$pe +01HHp |ò?;8YwLl3]-?+wVG@9nʦҦa( 1~6TWlpU3EQT Cxy#a8O8ȅH:Z2:%Hs):t' uqc=ϻ틈M;l-D!($4: 0Ј>ɺqR@!:6^D0:p\ `{W!#+ mlDpVεҷ2m1oUtG稞SuQoq6gg_mUxXp % $ $ \IJKb p4|o-ᛃDZ8)c,Gu1xGʒ:IX;R+`dDk)i7K3K}k˺7EPPq*a67S8L۴$'3$,@1 m8F[R$f:谥D["[ӈ`6uvV8-n9 |J\NÎ prk]O\r"~xUgɵKwXoKFE~w۸o_Ǐt01tߗsG C]LBM۔U-wnM% ` 3?RĘbySP.|>3 8#D[>7i~stI`:mۆm S$$'{﹭頠-w`ui?`d ܅N* u8 rlwwUvx[fZp:lp8|yqJ$$$K u\]C@@+,rݎ&X߮uoHٗ5ɚLa>5 +FbPe%XAAC3ÞvFX"9״Bhsma;ƽor4-.36W} \+#Aw2 U&*liS>;67E~9JR<vjhW,v 0io|L w!\- , 5Sm;@T0@.ToԖ~_g}Q % $ $ <R .%KH(_'{寱b)Z[Iڗr H4j^jTJ= _n\0Ea3`*WwVq5Fv53j-@怃$}QQW{~9,f ol< .ZxcX:xL%wQ6Lտ]`K(f8Yl?EЦ t7|^G ׌u'CmHtD{ vq_aKa $ $ $ \IJK^Q h?igd?ӡβT Bv4;EQu6*)LRWt8XE\Q߃|0Sd(ʭM#798`%?II$ڛ]R8[V.W+fX]BFN5|i!ˀ @ommh==$8Fax8m{@Q~aOfJ(jʥA1VLɍj1:-G -o>%I_p: >||#OI8$$$9% $ $ <Q?;|tXgP[/zq-eHּ;4;6QvVfj,H wE%B!B}ϔ6 ((R?S 5_Ÿb/Ҳ_R*>]G h8LZ蜒IMpeif]=N3[h~:( LtfCG1vwwN5.=_<կ~F[vK.I I I$:REHH4ge9:?;foꎴjVԣ̫j_{HKa#־눿bF:P:/w>8B8[/Є:.nu7.0-Ǐpq8~u晅ɜpϬ`ETMgXk:K|O\^\?-Bؿl Y _'IvTS$.oc3Myw(j m/(dWSk((xQ8c5wynhG|tgd6,5a~aU/?۷~YfA,KnIIRNx" ;&}6O߁F'2Miwd迣 ^Z~ mTY$UUQL.1 G)|El#MԢ5fm:`K"Y6  #1 '5-ۚFh)A\v/ko1+tFrűȅe:wtY18ʿJҴ Txup/&.[!"ܖv[c}T 8mXm=.U=ux6 ljDqa3(|!@y*,xv ³0]ڗ7<`.[ =tZ2|]@DCr4cXG}5o\[ginsa/dP+ `v'{-BSW?گs,~1!I I IR`ÿΤꈿGZSH5E#)3{gE@IDAT&y^jCդ.G{"JZ0MfT(J(#6&% :iS I+8a8ǝͶ{n 6d |W-6W[%|@Kkj,3]lvGG acsp-G 2Whbm޲%nK Z07Y [,E=+T\ ĉM3 Sw(7jY3}8 7]' \Vq;|c' pʖӰ]>"\jزӎ`6K~hi[VL4A  k0ɉe^ZZG%8IQ׃,M/gIIR+G%XW/I?m! 4߇o >`1Lk)E;"fXG{Z6BpSPoQ|룓 X(^ ;s}\?.]' \F|OֹUUufIO;o퀳Ѡ|k?ͤv^,2g2&V3)ةlw|2=|(CM f:gѽ{$K.' HϜ2I\?yV|ٗ۷[;;j2M.b{^{ڰn5bX;{bv'+T踢Zk Y lDB,ocjs^Sb]nN!0351)@"16pB :wޕpyQֿ=K\ / %QF\=?*XOX%V2L$KJ gzǰ:xL"Ôϟˁ yyCS$b/6i[,HG\"0IvPA]E6»Y}n| K ?~w#6$$WFӃ& 0#_]F囋s{^UMUl')YA|6e_i09R6:VSh4;MH^w p^fv*]EfCA %-zN,UU-LhN1gCX1y@k<%ݒnRmӓ% \kene)~)U:x_D 9p;srBukaQyks"OTwfGRPh1m?d$?&b1+((N8GQ[-NBpïdY8yn9l94"F O {,]xa`]{0+-(oOl[УQ5ڈob:8]vem7.qeR`BYRmi? >è9}e\GEhT[oGץ\lï8Ìg PB>Eԥ_UlGf˸8:Bceg:Ի:Ò۝OtL`0iϠ'ZQf(;otz`=ҵ?63b~I NyT6W 5ïZe93, !$WAU3& `ÿQu:OvGRD~*-=>#{(8~ cK6ÐR\:K^JwЦվԎ]ָ#Fc߱ GpRE ?qoŰ wǰ oK 8y= mۆj߆jo~OHx$!Riq?z 6κbv^b~pQa&@`F-Zas@`?|^⍑:GX;TPh2>}8;+?o7-I IK u8=`U#1oJyؗa1(A Ah0"ЊB |U%K{^{ƿC:c9b0%[ped84sN Vcup"XGg_]{\kU߄[E\./^\:{'\Q@yhvb%Bhq:ONmh2Ǚ~Zi- y/PCů'nO͊V`PC,fe5.Oj7:f{1IHQH7L$YO>$;::^N4*@Jv<<6}0TYi Z`*5΢=x)ED-#adP L{$MzՆQrO4`1ƆOLĚ^B^ ;,cr# Vc^ֵ6O`ˇoA{B,2lf9xޔχg`ֹҒ+cSۯ{eg?ng$$.px4L> W.fFkT;MQCu+E5>ЭqLqF1;_avYpYc3sLZJ M)Q~Z>P*K# ]T8jyTЫK(n p֙zFOhfV2 ̴' G 8ʐb!%Z' { P~9 L5,F6j_?7m;=||ᇬBqĒKHx%:^0=AdXBgJȆ>:bO#F.ٮ&#ekŀF^ ix-4@P)(~-}pPJѮA/"`nL@ o l)~F = }S &y' ,I]gjm#MuAp~H7x]`m݅Mriln6-lo>f;?9劉~8xo}toq>%$pS$:n/#Iu}嗣l>QVol85ZZ] KxKЄDڗҠZsG WMQ4pXSf4L8w=J+t8 1ǘӼHUy. {~ny0ZuX\U+%JldI@\a1u&y fӄv ujra m/Zep>.B'\+_Sԩz(X|v.` r6j|;9սӏTܿoE|-I I@xIءrt{P͛<3ƳŠC)u1ܛŻuQܑ0f1 Ar͋uh)4ºfOSp"ޏ3o/Fڠ# a۴}wqaF˿pܷpT1M ]iY̯i؄ӾJX.q/M>s{^UMUlp?&\Rq1to00?A1WBhZIBX#ifnhN>? ` :!lM ŻeYh`}XQVNj6ih诟|gLYA$K uaz$N?Fht7TYM; 4ޙw/_B3È{ M X;¨[vHl& !&׍RJ8tuYZ]v-ᎦMdu 7J' ,R<?@]yN>mFu󶀧u^<-X>i_(_k?$id\@?cz$ 6U<J}xO]?Ү{?a Jߙ|:ү*¬V2|np#d(MÕGYF1_Cj'xv>p.݇y97}mۆj߆jo~OHH%7&tSZϙ ve/o_u.xM?v?-y=,i1ifoX絝CqODpX77*V}[w"$$"pC~I.tZL{ը@6/E]KYittjhF+pba#A0}@JBPFB@|:_qH:羔&_7+32z oŮ|q)6zT? wrrѴ~$9Uҳ;%!B\-s8Zg0yyB) R>Dțff-/OGqv%$$pC$:n#I$A-ٰe~mi @vN bIf45Ah!M|SS,+ ʈts共ݱ~ 3Q1`SpAׇ-)"Oz&?q3kbz x݃_Xinjgw6qMo@FB.IE#coo} @픚]}&tP sf05Z,toLQ6Wߎ3PS5@J q#n$ u LƝ"r"0G- `h;kQ$_v ĿiO#/R^~q>ldU[y0Zz3ŰMgt1$|sb]|铟$MՎ': H:ȃo}5ˊyai9N ozRFDUTBk+Jf)ԍRA-#TѨY 7Կ~?P¸_B`% $ \g 뤲% c>:Lbd?9XTUpV H_8Q Zoڄ֐ @/Jsg ( XP`Lj}_R-.Xr7G\׏K"0NDujk"۫%A@C~`%R Ǿ1}oUFQS0I IK u+t)"mi{KN w-_ wK8Eׁ@\9agyr~llbyr { %ӤIrQi_,`1Su,*n:A$  bsBfkf~Y_+˳|^j9@>Bf>Ͼ99w?WN?Rr5o% $ \ & tY9#_[w'iwƃxvE5,<]w†fKsV?H0"6}&/pG @ GyUҜMA,1T)!tp!mJHI`Y]oW e'/-d%foK先q^ Dd78-tȏDH|wfĎSSO s.&:V7xn[E[Yb]R^BF.mU<1x/< o@6=|b9 ꯯ut#<pI($ҺL.X/Sbߨ= ev^sq2=YJ u: |E"`] j?iaT+ݱz$Cqꃢ\LӪ:wߏ2H$$k/pT$e }f~(4_ip"%xO-ۑ@u\I_MԼmDI'BϡpG5lDI,؏/sf #^dTw8*Xa, va*ZQW'ɂ)ܾU6: /1;#@p <[f]o(:*4|qxVFmߞ t w. 1 8W x8sxLpSx v_##p s0u|E0_D6K $AEHdٵ? .H4)^}y|-\^X;/0[8u~Y/9pa U#B+Єk:i KLrM3b|[} l=I Iॐ@x)~TWNGΚoكO?~|<6gMWo-?ҊaF\W3pX N,ͳtv>(߰Kp43 A~<ƥ)7 9b~r}s%eK$tC>Rdt@9H-Ho),xH`WДxڙ#P N KgtmzГ)+g{OˍvXㅁM'B39| ˳ۃ3CO"9`9{(}֒+n7$",=Mn -(B]5# .Kk'VC+gpi>`2jogx)k|Q#R"z0m$Bݤ~<ٙ޴ܙOٟ>RSaߛ}ӏ>+u{@C*E@'/G[`ݣlpTb1̛nO|zY{V+_٥#C3OSO8,aBl++4O4ϥeb f p(KZefԗ-%F\E?*XRqD< Z)0wjr:}ľcpk3E)̤XV:̌|Ny6J%bBgAGqi9 xK$_ ys}5؏g?n]qpS{x]Ý% 5ґa z~ 7M3` {]"9<.Ұ%2+Aeit*`;qY͋xXg5oNۣ?d{LYhrIBZ IA?Fht7M>Mo1q_^6F﨡>$QOo-P'"iUЌvSS{kC{3uw*((Ά|hP0-rAzޕMpn :NY8y8l94"F O {,]xa`]{0+-(|\˗=*ZT7Ume /#iQ]u൥H=pt"8 G'6dOý|n@6< xq8߈^OPQ"b%`N( `f7?uѿIB6Hq&>FugyЌ  'e2a>{GhFu yI_iQͼ~U@o?~>כf-IH u\!!I%6ǣ?Wt1SVO4Żj9fͿi9.aV:ħe:4;@-nF / 4#[mX ),8\6|W-6WSz`G uvufPCz#,uuOkx(-mP%&FmYLaVYAKgGNz{][:GQenȫZl~h(HEYFm\VԜG^cK$PZxo.:xRAFn="o箏 Oim\\V΃ @}#@Y.!&Е']zNbrXa5, ]>Eⷿ5>l>C#g$$g.E2HI@?Gۿ0? &|R|PG̪^Q׻2 nj"VZNYoȶi68VPB^4B%H`oȪ_[*:N Q?4s1)ƿFgc+3ELMa`/XUgݥ?f(gy ßc *SAs%ct@>|wYJl┋o;hi ܫ#arOM~q V/8`ueT:Ay'Y@y/e0-Zff3tOii ޛ}ӏ> `"L$$Ip `\g_~9kg>LeQqŰ*ES6{Z~M4įjеY:ҨF:go]I}qU8bVNƭ*ea/C^.XsXw,oc><h{uF~t'it_w }1Gw-:K+țN80+@[~g>|Tu`|x`C9 _8?3IQҳӻaFf/،*23@vq1,NӤcJ  ( E1|xJu/l`:c p. uͺmPiؒ*5{Q=i TG3mo4j|웓ÿï~u#)E[Q4$$!3&YUb#v.uM_vO`ko5١6:rC .\n:(@8/]v:р5nh_hhq8~uEfH+솴PݘԂa?(uqht¤5b0VJ`.20KUt )I/a7&eWz蓻9S7&U!ouJ!MUlľ*(=WPF=U'8tY*B lW[X9]ɎWoP`,~,W1؄[EIX6oioR<~8 8QWi3D)}ٷN8h'88*pD qh3?|1+ qS,Άb>'[)A?d?gҍY:$!_Zb ݢ ;61+t,pXprͱiDfeY6&+qB`D;z+yxS,u3_ (拆U ]!@WFsW]\L[_-@`_*p*MuUy@]6R<÷r( [t^b x=ToBWɀsҷQ6'x;exA:U\;#My݉o](&\;cwqiBIbLG@n [ 8;W|]cW2}{m&ĉuqIY0Pg*{4o*je3n[~>HgA>% $ <[ %$$$6ǣ?Wu;yQDZ=wߞZP:UW#&ڟ! hزiki7_kC EQ(6B!X )äŹbZ~?>m>o+6U˻6Ob8c3Boƽ(t2JҘo<ۓ3B dQAJ1h( eӸA n a9Hʄ&m N%Pq. :.'3vjJUCʸ2Xus ɵ`]8 F\q'Lj.ݒ^o8#K;i2ǸMax e鿬j~{bj|EM1 m3o9~FeƝhZK$ SFH߾S'x0ltЙ1nr@kucz~Ms:l_Hs/uŠY Ht erlt| ۖS;S{gI\;dlR.(8#m>7@wsoj%?cr"Mڊ]3PFD-I .gNJOf:z4￿ Rst&I% qsu;2:տFY+Y2>P_?tzy< Hd#)nv2ѱcC|P݂$5tfxhͫ]fثǨqpK`Wr7WU:P㽐c1h >>Ss$6/q[/_l 9߭4 ٚM"x$7whSY@nݲeϨoס+Yok˟y[bHR$6/gO'GoU(A'/ L#EMtӿPT)mifAvTuiDHFa;CoÇ͇~|b% $ \]2L% _៏O>)>^?;Sc8boV707XGcni01}MY4fCęAހLw6I\@зBuwұpkT|I l7H>F=՗11m>7~玺$EJcCrlƬ Rk,[Fi_U8gz2Wuӿ9U2>il8g|IɇHϳg'>:̪z0xo_L?}N~% <% $&I%s]}|2}IQ͚:dhFO9ԮշM #65ZA}qbõ LL?bPQ rW XJF"È9|Lh)R#7A qUR=h.M@%f$ {Ѳ㟎vgFP.: k [T./3IOZ{RbƘ1/3iTTvjsz \ͥ]-hXV}p:G/J9mW@,Ty y4/ LMU̾q4gL=wl㪞;Ԟ0G%lOٷggҭ 0[SErIIOIiY|~ͪ)~V2t$/q-Jm'G9+F, KZ`3e`͋:FpZYL!7CckFZ]l!O#LnJ1^|'p[ϑfk(}]pn :NY8y.8l94"F O {,]xa`^h,Bn3:?QdN0"t(!< ?j1tKw%m(wD>O.IZINM|at\giDL N5*z*Zu7\pNN7@(L5ټEÝP,9@%ـ2mxxG%r eoŹ"&]vNof."Nc:rpLІ7VFOBTg +4t6by`3ՇƻOE,?jFMqRN9):SOq}O]){ ou3#_drruC!GkHP4 1VV[c_gJ+Έ {:c ,B"d?K6,exo+6eD6jԒN%vd2 @F830H>95 t( GZ0RPS$-_,I.I:I:ɤ,|cKMv:k4?e3`T XOp~h6Va~USf =u EhÂN% m#l  V8 oY$/IK8nlwk*#T>`L|}75>|Њ}x3<IK G9 &\Lagd1+aB؟E<#̐; 8jl3^< -&K<<B:ѭ3E)8E42^`xݖsRӐb$JoYWՠɄAŁڵyZ cuע6M/f94XB4Sf%W3"K[W  aOd?A6 hH3*dVJRn|߱{.Nw'#c%ynf[1fvmC<;`L-} ,N[;a8å!U<ȩ4 K:X|M Jxa< 9C(*R]$]fl>8B` @qɘ9lDQq6q)[YyPݽY}o@r( `Tx"1ГڄYg6vrfj4bau45tGZI?c8qigx؉1VFxg.E䉪g-,U h ~a<O~<$50?h.dwy- /+<_[`XshZ(pCj" ,8`co\|ͭUD`m;~QxǬʱz›%5j-DҏSc}H` &~ڲ*:y4&*mg?;\~O>9}۫df:@ǁbF8r9`Ϡm-t]LaAߘ,s ztN8;5pǶs:#bwpTHp $0Z(wX *FA߇|l2Y8\ҿ&xeԎL&mcn n״o-|Ko >Yһ_ +S*r62b5V,45v]m޺}Wq謹n븼GO( $]|^ĩrŔXcFm5.. P} Gl~GLJ?dAO|v_}40"6qVt /vztmm): k~vtw1Gݦk:4ٻig6=e*ݨ"Zg:}u${NYygYìοL=;f=w} ҇ʁXeZ[[!)eK $⢾3b@KNXD,BvLfd!ri5K!<b< oq?Mu++??yAO)Qs b sVV Ti-|IEz@@gfɱ^Wwg_uV+ _!u^ӺeEc}"&'@f2)Q [#Ghvwzw9:7|`0]=x\ϑ s ESfJQMi><DO\:dx/N8vf/Pajw&xЈQ&M:[_vׯ ^<;FP,ą'NS8OTx?Dq34<}L~[!oO^(䑼p;XF1 WhGڗL0B(G,mjFx(Vd.pD>XOj8'Yjҭ]%y%~_G_V(/8'?K_ѬCV!J hQ怷WKw(dj7L/+A[XWCní%޵{Vi_|,P<8TXV<3uT# _Q~|kEs9cӞ$ @G*K4{oGݝGٰ?>fW?{pw?:t &tx8ӭs5K;vqjU`c* TH!ϡ 7Oh4 w ބǺdh9[5u5)_AS8(v9vߵa :99pMmqlí1gw]Hpi,/kVU~*KbhGK&hq4TW(k^UMYvxS(La44 W`ԄxbүAMcې45<ܔŲ [`nm\㍺•u<BʢwP"*-ad[j\" %{- Ɵ ȫv\ ?~:}sKUܪoPdm9ėi8p )Զyj XWĵ^ p%/U*Z\:XIWSKx%!ܖ[qWS =W!Lsr0K0>öm٫S.-'Ψn @Iȋ].@> ~r?Q,toHg;|uhQ?}&vg:t:qKN{sܡgt\pvw`JOTQP1Ը@e@ rP"=n ]5*u_yI:MF[{|wMic^8=f y_/99ȋ4y™vT_U i7#+:!tҗۙ>3l?/JALB <0Ӌ$MZzCMm-}<Ņ)1!MQwUS?' (L!Ql5]JPHB$ː)R^[mjQp}<RB٬og<;%gHJ0B)ឡ8U~]PHCBq@\*dCB#RR؍8uj]ڏ˜5*`hm?m^ArQnAh'15ҟ\ Gfa1Ϗ~t:q8)nƧ9l[0cxH 2əձw TWlbv1l"jԸ6<=ᾏ1\d {S G}rySe@,Tį̌. w ? 84S9?o^a9OtQWRda~/l*u66Ac e8dF 3W4ISBOPl"Қ/[_rE\RaVSV'f,eoԚ}|!g@='iHg>\Rpoֈ&L?BjM3xgx,(U8AO9t SV Xҩ=Fe˨-(,o寏c]3B1dlI_lQFyŨwr2G)Wo0cv<+|.>Хuf/Cӈ/\$yW.˰PVxnF0 /Xw'Զ$7|K+͆il!(m+HLQv5A"W B 4"d'ڔZ}( ܖE  *.)+Gh!z\i'.2JH+~4 7+zm_r3kOǫW0\缳27ޜ򾀭S\Ɣo%mH8wϬr~Uov|oT$WÒoMoQ2b]C`8ەl[:Zb}` |9sr㤹tk ńՀkởnƁNp3>uT.8ϭ3'}6xx|pޣ&Wƃl|@5Sd+Z{EGad] Qc&;F1w7K֚|G g RQsp g{'bKc??>Hc𪣌Dk e5: qT+bڜ '`aZ%l+4 qDh[eclU,gM8q7UGq 83t'; Eo}荸v9c),fvSAB uKvFͬMa]_~˼f쐦ʔZS`9Jޙzp$ǟ\ޯ"@KOvXFģ$,킺U֩uJ`|[ s|P1w0'\/\scfie~x)mdDx|FC`$~_ڇ|8mkЦ?Ex>'Am# pzg=EH gEtC{nqxiw m~?߆pϟ>>ab x[{Rh1 sab`2fD|Q< c#ѭ11wV6(nwBݷM,$]'GJ(th9C AX]!p-q8ޯdm :N&" <4!X^Cj+?+&S"DEݻ=+i+9"WyDHHôW]Ý*t+jJW!~kp5R'a3?VZ UX7S7Tpm' h\"E5XLzO:YMI[9lࣧ t@ǁs.o8p9Ͽ?xxQ{Gtv3Zv/\ݟ>@JqdA9S,C[ZƊxL#U*vufm0=OFp,/pQһ~(e]Z= F>[8 ?Sg{,/@- xh}q;J6u4 JPQ*,=|"xg>-Gަ L;nEwHJb.t@\ֺ.wߧ}'qgLw4n m#[ng~cq50Cẃ__{gup0]҇̕G$sq悍k}i_sA}$KYWF*/ ;:Q8s(`D˔Af 6MSQ +ͰwEr#) Lg>ݻ?z N8BˢA%m}|̫_JֱmaCz o*`U@P˿?=~,U(Pa6W%oe"זҒB/fq}|zXmBu Y?)[}T9sPC~;c:*T#8-'ѯQM*?QyWn'loMdR |] mE#}wW|_/߹ ^2wJmY͂DX*wQypU_\1(dˇ _11*G-L~vhT8q7dTGv8F3{'̲G__;>fɄ>2WNK%{uջG&qFJ`I@A_aqi׏cKO3EuZe)>0p ´}gnQS!7h 6[*:Pξ*k%d=cC!=pFa\P8 &r64 !e A_iBP@[|743Ammô R_Me@ |.ןB ;&밳Jnb;-y |u;֛ K_b=ì !C>T,BYuHeyU"$Jm̃tKnoɜgvY˰_LJf͌vQx)oWk6Pς-&?m~O JR1t8):q`H^i_>|>甙"yrS'NZ!}^J7?.7M#\&2ξRI VzSQxw?fA n߆i‡s(ޡ| ~3\h?6bW%3QEP x#fo={܉?zRw%n?NC EiBҺC٢Pog^6 k=z>S;HF3Ql `- n Q֍&ۮ&΍uJ.Q]¿ȩ٨Cqmkj.WqjKi,<}tG[MَDPQQQez r9ߙ}uÁ8}vo/'O<կ>N [;q@hY9:(s0|yєUſɸ?/w]J658a+`%;Ŷd,a+TSqnSMa=o^c9?!p{GEx8q6Y~)^v9Y"fqP?_moT'Rv6>6q]fh˲kg| /x\0 ?]Ȭ>7lYPª#r)w{;\t<ޣog㧣Oz)Jާ6Ig:t 4;6t8p@!ޙeǏG,G Nfr|zvƖyȞ`p0EqHNgbit4U)l`,"Xkjw ı 4"KﻙpkuJ]ZdYu ?|u}֚+C<h;o9qrGt5A~Auշچ[S6~L(~RdSY9 yڤg,ٟ)C#l9'h|_O?;el2{[d3.q[ps=_~GxU?l7vd0Xp=o>yq ,meMtd1ƥ~NZvNjp0<[fT ϿtOl2٨^sv_ǁ8?<|;[7_?~@f9 {3SM!g cхơ[-UU?d :QoTkm/./ ._8PC)+5~+{3!w;Kg Д ZL2Mvw2P<뼳 G!Uq- a7}{-V48k : sӊtPA5٨8K)m[0 8x4 &Kq$ݦNkdeSS6q||Ei2 W*L[L8ں8Ls#\b;n]3[2t턭F$ww'_PZ5Ux翿[6f5I#%au޸CBvVaA7>{C8TҘW,C{rG㯞~uǏOdx{{!C#3\KT0̓OM5W&qZvܔQKѼpcW¼+bO?5Iwvӗ.~h;6r?Qآ(qhmȶ8;_x69{(Y=GVwXQ!@к%B9OAJ:!hnr[NGG3T3:$`:q@ˁ_[ov|>M?xH"g8u6D:!w^ # <@IDATK7fo^}#^*}ԐI٥:,xӭ5$,`5.MQI3qG]R`ɪI4ƷOao1>p0wgmuV_oBX> ~~e5S| * o3—h.;0e<X*?2 )P'v=8R"D XAKY&f d&]%A_ _&Ȥ2dIFP2rM方WzcϷwk/~C1Ɍ6p/𤫞W`Űk@E{؃<=ct8_NkLǁ :@W'2Tea4t28\N;l3OM^0ܵ)Ұ0ŠV4k +Q,ӎ;ˆ&jEU;c䱟_[a~ǴLĥ(M!Z@u܊vvQ~nz}sV__TzT--ˁKkb ӝTH4Ȅմ`σio{}ޙU&8W|p++ L;/ҝھg]p8(./6-KYK74 ^b W p7 cX~*K+kX̢8jBwy@wJ 6 -E;ܙ|8o~_~O>2LLǁρNp˸{U {՟ٳ|~? GLr`o?-G˹]~g$z࢓Sh:;Df f:4%~)Y]ژJ>6[nPϰ!}A쾧kwQpw3\{ˊ"ܗR¾Xg8\ FGf'|,;q yҚL(b"]u fqڮ> M]_!N-qc*g:-efMJj85~USbrrYp.s{v?,ϟ<+~j3v =pg63 `( S?5|~ltz$8mijwYKܵ 6 q`]M7&csֵ>"5ه])*~! (8O‚$yA(=POФ [hׯa+m4omMZd>֦񸶟`hoT6I24t cp9{coݽ⛳`g1{3;8ç}i"}@ܽc8C{t4Ygڣ98~޼7r߈Ch~ffbT=n.KāL `+0SaZijw´7FmAK|7q\YVFmg!~SgblCN{okv\P¡0,Cn x<&\yF8 Bv3>B:e|{ \g<`5|sw^WY~E?{W=o(ۊ*<¡%m1uf:]G ^Gw͗ڽʋmUڻ7#́YѩܛZN+%D7e5q2B(߬} 3'\ȊOuV:L^gnNѼWlWmt򋪅' 0S|DQ"PY,Frf7{*Co;gG?_Ά쫇/t*`:q~pS܏r޲'wy3{}(cJ粻Ɯ-;Eiά?`r.gpO([<pxMT"ĥ@}mvW[&P6E `sX~uGLǁ;ρNp狸{s},~o4-c|OW3ExY6B`1ٛU^WzCAFo't-~bL43iǀFxʽ ^ÿP/!uBYA>U>xx06~wQLx=XJ Xa9V1 4LMZڛXowxLhc~]YJ`M SP^I8Bi7lUg A ħHDc__!WۊxmS~m3Z2|.nc#o? {[r4da8:9}H~lIO!ܑY!mM@Ar˖7%꒬567Y,SaV8T""72 i̇hwL53[18lg_~~ǏO<1tl8 tp5b!sfG 'ۯ0M3`%~츴 22.ƎG!L_z_fיM}֥x{`yd4UA̖2Z\7.wA" p jDQ Qgg|ϨhU}<_Y{g>#$VW7ދ6il]TnKo_aAiJy~dLED ^BK*7o­56ΥJ 9%lE?f%@Xሙ\>gm$Ph'M+\nmdmo3\&^6:; omےnË㟰8V[%؏L6zPdZ89k4G56S9C tԄ:¯Rlw|v/"@7Eݽhr`̑0Nb>}{No0(8Ce8P#f``ʌ~@H=#蔴)o„d[d^wŠo3fwWܝw.τQlt=l f,lq,dзf@ծj *gH ;cY?oP,zGLbmFX/p(hW%/$5_ W:@]O԰+5[^p` @l ׂ_"LMok:j;(Jl#J='r+DJ6 #$kX7k7n:J.ru@*֗hR ꚢ@)c-ff;.B3h b`# >=8p_8X3~c{!CzSL*xD''N'rߙ:g2ݍ Ǵ &+mhl `و\4q;s㡘/X@P7,x cUi\:0ئi@ń9n|:?]Nf_rggya_Aɓθ"p駱Ť;qpSܙ^8 Y#/Gw)gD"aѬMa}v8A6:;ԀcL .o W8odclnE霰u'pupc0Ɨv'L{B`$~o Ɓ{O).CĪ)[V S Q4ƫK_4&Suޒ06lg:$6el!Z :@)$eS`6;7Aß+\YIAⳭM2l-d-1gwMnV6*:?B:Vغabl<`Un~Sf<61 ]š䙁)G偟SGBJR H)gwl4p'F B:Š-}>,>ybELǁ;Np'{79*?+y]OGR?vTv\vFhL d7x~S._$V.'W;e}~2UV02 x9?Eߩ qjs@Guycfpegs =lŇ. ?e#М;=#gTR H?gĵ`p̑_+2c]@Ќ,\~ڎk]!c\8>)H*\3},UN`OJ$Is"E`;?^?JOs<23o5"gmLɌzF}pLU[d`MuDx;G??Lä1WYƠta5b>;x矟~LǁNpWJ{s3|@0!Lr\1' PoLƱ3 Zh)^)=mP {`] I&la3f*0yF lpjW)ɗYϚ=Φuޏ68<A7&)n<~ VQ4_5^eBJjy} &F77FzBOX}j;nXDmA&}iɐn"@{:`OjH16#?mC0(a`[.E/ȷt`gU~~^#mҵ\$ mve}X6R=*;W Ւc=2#pFw?@){vx8={6Ń}PR6w/i7rpy2ñ ; =NC,֎NƎJ{]VM΢Y԰)5ms̝%i,Ouߍ|FŴR=F3@c ̔Z.ٕ#1f0EQ~;t ;P+m'ǽ ?at>aW?`XfENn@q((@ DG'V4 2i ]Uܯ\1 @@qv?f_1-~}Sf]W8 :k_f̊Bk_%{=OZ^~#.|6.;yZsg,`^j2fb H3{hl+PPNm[;q⣽M5;".2H:XAU10өH5~4lmNX^ևx[R |.cXvZ;d??.ҰppSqҟt3w(i9$+Z'}6x/d. _Gf65Y&SDR; MW:vK6μU).Y@AgT K=m*;@q}ȏ=Q"\ =8QL+GJ~Sؐ*nտ!l2Pg`WUdK*)o(])K=@v46ЍA g۫U%qqvm@-L֯w}8`9igER¬YGc<ӯnrY,?WHtkwD)Ý={w8A 3Ea1iqzvL8g;-ik. x ˪)l5m]F.}a~̂ 4kRz5Ne̾K!>2;f퉳r w( `+h?4L[K4뢽)%fq`]Yh_Ԛw )ȦMuWѺ<8ƶmσBP[4 V%; 9npЧ6Kmiװ:M澹Em$YGS4s=V'+(XJ۠ADAgw#w (`qߙg_||w~t8]gc{˽et2X._S8^TtfaŶW}N?nwcESlcIUx($[==FҞџb8`*J~TtL"PQ?:~:ytw~ǓȊ?b;tҨk0Dw<~%JVle`3:;9/3&:$ i)=J5;US MژQGqBCˡ u4m6}g L"H9 \7{{Ԍ]F1sQda 8rpj4+l){H_W*ogSpO%7KD+=ϕӯVaIwue䌊Zb#.mmN:6%o22Sÿ )kHhp2ڭӪU3kcb_MWGYG#] '}M/1RSMsi֙UQ$^\dz[GZ3 fC\i4~J!?H{ERZm/{>[vKHNh>!קh*8FW. $EhPnջŏg<[ T.x.ɰ'pR@\xޘd׽,噭}]$QolO}6QBV[,@Xa< P^]@+%ٽGpZ???>X*׿t{ V9e1e"W X0/hx< q]k Aa]O*s]_Unutj: 9f ,m^\N Sf )Cn|>!l__Qw /J+y\}ulq0r>65Z3gp5G 6saֹA~2Lվn٦ҿa4(_4l v?[A2ύx;{6X)4WmmmmlcյNhrӕ4{]e@Sd79)C٣m9''KlּN|~ۯR!T>{¾LO[lU =)אJĊШAks[ KpS6w.~rrdM/Jc=yp)X'>@>v8i㽑IjC#A$Ҿ.}p(;`p;(`|a-XB ,R2pE؇P7a9-T3 wٯ$# q?It^n%:@$p~ijwdE$J[^d|Yŝ?^p ,=Sš/X5Eci )t&+@m7vH/??;st?҄hlgĻ~@7LׯxoIr^pˇd~l Ǥc3u‚ZrCxT3Fb3\D{Bu\#:" ?eu:XџߋAIXu[ c8e1R`A3l}-L# ZnW^B̫d5N}w)ntɿ/(ym85A_sJ.0{ᜊr8rXՙwn=| /jOo?ga4|8rsbKvp`ٛ]KNeA4Z[O; 7̛E^?]68\\9x-^c묗aX4YϬ~~\`? { *^X6a 8`(˰*Ŕ&:r8P|N8S[le5C583Tgn,q8F8&z[[e*p[ĝq`UD~ˊrnO/r]-̚M+sL`(իY!?L7~g/r',JQ@Ggu\^_Op!G=?~^/{na3ly~sWh)Ig:o{ u^pgxNjd6/ce?F9{2 (-x(Ej'#3ê)l5m;䘏cZ:wϧ.=ϛ]>UcJT&ȏ_e~l[}nk!^C(s'{]K&w B 84F̺to {#5Q~5~MsWEGng}uK{[l}ƺ[%&o۶^Ez6l4VliLKS n42jߩP4nI3wRZ \T??럽7;Ώ'ӝd||w|x:ퟆ駱 !~:t PJ][3_<:=`0Az~Fx6~r?X@W^a^c-'e尾\iX߲S\/<PiOu^ 薀r_yZ/=+oq=޽i6av3]WCní'͗ڽʃmUK hCKgF0 b) WjOh|tXh^}CtWv=DwnUin8{ۜ+OSZ|{.G=Ey!H\Ú廛v'ڧtWy[nӗruGVE#) T2.Mt`(j([p`l;; FgqNjW;/t*[{;sK8)nIAu,'OG3 [?EhsJkr; ɣ /{kdx.-GtXVpx)9Yjau2 aٌ}em-ÈطlhIidME+= h_wh 8}H3ħuFu g|UӬߙ}6϶O ^E8ށJ)"2Rbf-i \mEMm[6UIl?2Dp |[ܶje"}Lۨ w o99#uwQP(FЮ4\·|+l`,p9d. Jos1cvtx{8)ޞrr ?|?Or.+}up*(kޗ6b?yh .UdUNʹ6{~S.7eP@ఓ}P0Dg?`-¾JfXE@D V2$-i\ փc Zr~B?m_=CX,7.*qJz<e&\b. =դXDi3e;4OyBuU*++pKiGDOwHzSP[ (E Xi[cw˷Ʋr,4W+vBQϲ>WpNq-Ԩ?{(>:ԛ?\{ Yt~Gf3n :).rt9}?38sAO[%kFV9'}+8 H(MbuFxhօ G1[H}ӌX C>;S|Y"5J K t^~tk+8q9+YŠRW8tKUl)1X}X'coLc֝.Mj[4!s6Ws|ot{mu|ye{I &b{~*W6 IKzektV<io?_ۼ3Ϳ4AqRpoF >U Uk>\9!~Ͷ}Jj.[ͯtf ~ cWXtz/`?d$yθoSǏXzpwt61?~kv@=eh^xy~7o-y؂`0:ڗsT4Ht} (['QœuQĮ&uMҽ^=R 'w_ #1K ~ -C|gϰ*h-rsܙp; 9s m7=a 2(Kg.מqL֯VG ^ty%?vHPa6 0|J%3umx >ͪLR@e%a*tlLauMso: 󱬝. x~7c>*DS"ܜB bob7 Iij\݅9w},y28p+8)nE1u90vJwOٮ՛ R Nn--51 {텭 6^ښҸCؕM4y.v> 9N#ZAOwe@ݍCJzɬ=%~e)+W`s{ (ba*HYϰ4\Gj9PjqbjsvW3T*|}"mPw<4m[ܸĬzCYi&ۑ%GʬɮZ^.rݻ^v#z ]~b-KƩÓÓ]AɦW30TUTtUNl*<=qQJS` ~Cڲ̗3<~QI''f6X_n§D^4XZ,hU(h8 > J,8 ͝(()V{uc iѝ3;gK3L9_ OL;[,buƑ5d'amj0'nwU{ Fs+?o*?wA\fXӟnam0 .;(0\y0~/eG)L-{ljT;,U@IDAT_//ײnT m@g ۰s&d`p&u׳]3,:m,eSs$))˼!KCR &B[X(-;Hjxv%#{8 +\8⣍ʎNy_%O{_~Xc t(J>Km ߉)K*B,vQ7=NCu> ܦ>z>J9ely6e" $yu&VKwƀ{ʹpϪx;`!wD(~s" 5dǨLZ $9.ߺr45<q8)t'I vxZOB|g,cWx/\fUb6cw\TSJw8ViK)|u4KE}O,) p{+7>n'm^<p#BGI^?c-'o8 X$G1_r?}VZ7':a?ܾsJ K \rrLg8A vT(M"&`m禸eA35WP®+J8)-D5~r*)T (=}b Q'Ȏp?)/G']M0}TfҙO 5VWK \`9Ða EbLXI!a豪 Sσwt;(desFs]xMhE~cO:꾫7>w_O<ΓU zۯϙg%s:91KN>p7Cb=vx nR±P!崣rirvW~_ɰt(`{_&1+4l:)a_>U}#!v8[NA;:gvPQn_Fl9^E/E/*D 㸷di4}q_N˔mMz,mbB MzPcxAoo_ޙO?"wE( z.9qZ/Õrx'L KF\WHM1Ysis@{W˺%N-sN޵ma'"8PQ8CP-⌴M]J-p3$ғY _%xgHR`&1p>Fun3mOXp HzXZUkW?.wmr+^λʜHn |aX̺_׶#_~(λ1קAU EhQ=.!zJ6̏,hvE[4o[.OF%B9, R}&ݥY@(.BoQҙOੴTWΠO[o~xy:;KZ''N Ka,7ugaٿ+v.b)ZdXY6X(xT(:H; P=4{p;lBY?G+iqöM8msy;N-~_`~W<0g4tP;wiJgX Im2swhR3 ejL+?V!Ѭ,*p"̄73гF/ wXg *3Zg?0ʿgcEßxIl_^dz6:F!i3\YI]vޚIEW pxN' 1W6Ǔ=v.G္'g &G*'^1x<Γb +a͊Us&?Bج@iZQ[3O _)@NX`ܨ'D`=1Mz4iֳo3L2E|xl#jd*{M דl_&ϯާrKUi4,t t i$Lφ{{{_ƣ?qqr2>|El7xۨU;T4>y¯rQ:q)2P̏¥8]w?><_\pc'DW!9'V>;䓀00BJ Ͱ%n;\k BeJi:n6O/p$k??VǍ^Ib9f]Q`I?Ǚa2vG٬Uo1wRiR{t4W̰w9 Θ(16q7ɐ9!#sއgыGw<9^|wl?O?G?)PSLStps?ptpxEbWu KrE^ۻ×;NPMsSX3A!X[Sm{8 sm>紏;Y =ȏg?Fw'nw7O']w)/DHϸ_|2 M?j=¹7fͻ\1?q6K1N? CL#ePoV. ̻ f;M><0'b@PxD$\l04OA;/_N>Yto1 /ѫ?S c(C;]Q,-1_.Oˏ^`qN`hяઇ(e<.nD_F,M°1ঝdxQ:JBYWoSIwAJ韀C n,x ¿{Y( Kb3o0^_+P\RuQʳit7LԽ:x#Q`$]#UH@%tg1 VڛJ[r0\_PBv-!xFwaUY>(e~#~N΁; 1Ak,`Sw811B@ 몰 jF#  ddIdr8ɰX}aΦ0sδYJO9ҺTfEc0N٠|~;01Nxv97pVWV,m}KHI0owKnñOMc'B3^0?N8@,& Q&MKSm&p'g[tcSXe\Tk>[9M6}4΅Uu:Zhׅf+#c[2ӇҬ`%RnA&z{)ifM+A&Nf*ޖdy#{}gSxΠ$A29, oC6,qi%Q ~wb%[u|9Xw= u:4XE^k[ǽ,0^robZR:x9Kckƍy>vG%ʴ,]a ;k)n@>a Զʷ@JPh lޭ#y~@&)l?EZϏ?3Gd9S8Y`U~t~_ϗ?u!t t h墿+6r{9ﰢVYh0^_pS`v܌䟷^Ar&2 &>fxˢL]>F{sS an9uN_"}+Hl l?VhŀN"!lmH}}ɿYӛi ~*<53~NxڷStF>D뒼.%Z.K^JMô#Ag8/O~*Xh0,Wݡ?J*% <6,K 7o Ů?@'od>hئ}iq'8fUM5lR|l5]dE$?^^egvt t i R?y' FΏG &dY:h~N Ǧ0y̟3Egw] |AK\/uǸۿek(Xl] nbj1xXҿ0x?7t t i +p@&ˊoqpbX| s_\Ƣ(+D(G,ډ`X0(;ary#( VWwU(OpB5ʜT@]Q4X IHBN"@&N ;n]778v8\=hŮ,}Q`ex(Ф@gr*a%:xӹߎCey=}bo#Ċ>e{i2֌sX6G準kKh.3l/88kϚi^Ckl QBxp2@J}*XNlh C`|6q?b];kz:@fFfZwj厛٢Gȕ/&S-pqMy$ H A9wRc"~B Ev r?W_ӊ?₊neQ#~E"ɿ0)~Ҏ*|T^6?Z?p EENQSTstiR`n[wNO5$-xn6Wx`c榰f/o߁SIBpK/ VS3\[c:~il/8lt:6'gOU81)B̭ wV6mŹ)M?jڽ%1LX @d PzJNs&DƩ2^qK@*$y\ v`sW~9<8C갏_}S>/yڹ: `U(m< *9_xw7'ӕy;9 &N`xc.N]/9su+ g]M ".?9W\Տ>򟳗ҧ/-q0lTig6ln|>Nr: ms,+%ğg)(Ҳ|n-Fx&?޺&hRK4qo+o kW`eKw]3iX8օDw a|fR1Ŏ#&S̎ݗ+yHݤ0V$Hz;U)Oυ129wOyj\:$qD9U(893tQSXtI)d.v, =cVQ|{nu1GA׎X].Svc`uVͦtgX#m6x&P2կ2l5fuqT~l2:vR ȓ#wv1d3 xAA *1=D-ڧb2I"(]w/'z᩿SiX~ 8_ x ? lF X wVK\L#jF! :\u+jX cmpuܢ]Wt4 GĭYEm³CF˶ae^M28-nCd0EP jw^[WVԯꟑt#,,C%(+qnӌf+~WMn7+XUkKHUg#e5fRt0,x5<ˊwh{<&V ewr1loR~ȯ&epk*6 ٭55Wp®+J-+iՁ5\/ ̶G|Ci!e3/F19NJMjyMpt.QSRkteI`Qo4; sUg*i[*Tx< =ӓ.6ej~QI~䢃.X=aT+.;m~+>23lVM&iTyCt< "0aQܴLA;{xʪ3v`+8YzgOP~a0dvqjbV1`T_rv3ބ:uw xճ=+"Wtȗq'a* ۞ 336ž} ޳@>C1g?qA59b*x$ߨqx&p QD!9cl*c5KGMNrlgZVhDh/(]}/*5(wIpb : 1ުh]MNpҿ~רSgsO>]˯gpɮuڞUEH巧r_\:Mev nS(rb%{L(h՞{O8Og p- - M(v\+ ,Rk᝻@eݿ%N 0 Ňar9F>7bߡq`pq'gs#~>#8IZ9 l&3HNghztE h̄"`&Y,:DFcP ȹo% ˧~\Q6sq^(*I?igXY'/x4* m.`+^G@/ "BR/ Ls0@.}2MbhI6{EloYsx,`g_ble>*+SlᡍyƘEUF8ja0ܳ²"ydV_W֫NIfin>b-J=LV%p),z7rV)='/GΏ/O'')+˯O?9g@v?FN`2ps?ptp͘er?|y4ZN^ GGG9"uo)d]cu{k^78iE5v V)l5ֻKB V<&*9}%RԐg8?V IW~c?eOf {߳^K3ܡ ,R1f*ߢ5MauQGm-Iv-S Mi˪J?+eUʋy+ngx[.^L,/`172) d,C&m!݋F s?`2f.ԇҤ3<fXg#cwzNDG"о*q$ QG~9.pNφE2~0g*zMn*( B.L8&?*n֛~4 *bq/!13fܻr#T6殌rY55SnxwSC]w3.IJG}/=RG|4Bx`Pӹ/A}YN:߷s:oxvg ;cK<| y_|0_ښvw*¤[ +V7Ѱӹ/)iːKצKwUֻt7k)ʳ) 0<d6;]9kMUztK-X-XGȇl *R9NX`Ǽs]5[f3w{%q_r?9bǵb8hE0JKUȵPʢknf){&FB(vY %xU>z{nfpѤw Ĕ`~"|1WJ^||{^Gsdcֻ]CPѺ3?I_O}0x?_xNr~'e7M)돏>L 8  6Ht|V:ylݲňQ8K%1*x_GxXS$2z3r8d)ʲeJ~/$0uBeCvȗ_ ! Sh Uw6ttKB@.q|/7ߔ[Q"y;J3M VwR8smX1,{ ^nꤏi*7}7MW@o"i,;ž6Xvڐ5Żقɖ&F 2"*CFuo|uKuP.ejl +-9*;'r5r])OA |o U `a|>8i hdSq<:XdySG3f`Lb2FtBص*BژK%Jn&'YA\{|d 1I^9AI|p /<1.# .Wݍ°zN%춰s%͖֠u:ke-@]-Q'ҝ#]bd]fhlKUl[evpp-˳buAgt9X|E28f9>˟~qD@;Q(<%T)>:p4:`7+ _}sA?GX2_"MLM8y\ ,m['VXoBCd v hV>V^g>plt:k*+y+7j]ё۹|stibG_D#ϕxvw3ұ|sqޛ.ZN3S(6٥Qp*^˯{*cU߇:v.4iCMoC&fl3ʤf紽DsjdC?\^(a zsƒS4NޠaGT;N\ B -_1^+K:ۀ69j0bDrdžxMl2D GE Ԗ|1/XOy9sׯ__&C(pM. |?wFG{{`0,/`l 8;B@BB%ـW ̒!$(#S$dJIt&8P$mwqUxs[OQLܟqw@\GWqHtGjCùYu&ޓC$McnO_GuFR7G7hSػL\okXxo9Q1֊VKN9nv<yآ=cv,?oc(%C/*GT>`Jp?e>ɼ1=dk1"ݚmnm*'EZ bOVL"m~rUȱ3܈DNe콃`[Fo_?S3oR]zPәLt4/]8`>/ Ebae~e*C@6aʗkTeC)9z+e76@5~}Py6nw]yǛ&PW_NunE(] [޶@t*l{.j9ւx8x1ԙIN ?3"G=&$YzS Di:?0& FUXQ6HaH'lp 7'/$VnXuj3D #.RBRX.Sl!]i0MN. {/ lm*ir ?|W擿sRF%^r7;!jOxt&G;i>DŽ_@aM'TeM(W8f˗M3Lx,ߋ X埋? WD=;I,Ta) cīPw/|ҟ\U~RYa岆hV,LJF_f`mOcWak@ID Kȉ}ZW>Pco<޷?)|9k_puo3+tr `O)T!;(t[^.@]3`]T |: $ ;Q!)qS2~\'2N5rNWQ`O)?t-l \2  1LSX*@#U̺J`5+P+M̠k\jReܲl3+7n3ؾ- 'ëAC>JEt!`V~|?'11?fśY|43hR^6ϝRN} ”))4osgX>\$0w,C`V^mX0ی4m2m[K4aa8%s-Z[u۲IuiJ}1*EUuh?E5.^{o/CK=nKC]aZe˙֖T5r1lFf ˃A\> 腭yly^y ScXot͌'@bsp5/F(/ʤ * 01Q>IK? p\t_{o8f6bx3:8IYrŠne_I5(`l/|T \|`QDdߧ~}"' Dh;:xF.<=e{lA[e@}ҷxO(IM"Ȗat`pWAf߱6}{N> W{~.oQ7*@Urv _N&TMa)1nR;CWOg}jI Ni _*Uß9^6G9{%?>?٘Ӫ ev}ʵ.\`Q I=3wOda%@}=z Fa d`%)wJMB-*\WC F^nӮ6,~On >qޟ%Kn ? k* d{2 cX6ކ'W _Ey!]Z_m؛kXxo9mQxJ\W/'i/_mgFef<c߻w߰JeB^@IDATq5a[% #s:G 9wHTW@.6U ޘ,*I| ;9L=bOF0a:Nrd`'YM#O^i5f;͡ݍlN=S1@v0.?co/?-Gv?4-*Me-ӔAN&uv Ĺ?JcgXkw-O<EԭnT^!"GhWRZ6Kf"=nf/ms:rN$Wu$xV+;wG&>W +K-)q9GʿXLo~ies }E5_lMqgW g2쪿`g,^̹rʲN|z{ 2NW: ? 2%FnƲՓrUEMXkp߻b@`s裐0wY{|y0f'Tu?+CdPj*8/~:qU_^o'}|A)Ybf֨}vi2s⠇ߕ q7" n?B'3uƕ} O*gRs +_ Τ5&sy<1>moY +;eAF,'s ,/P"]#XK\bxXi~j .h)ld'BW1} BJ+ j:X O٘sEũ&]/t`Vǩ>d(Xeph?zۀFlV9~WyCd/іt :V%B&?~gýf=8=d yF6LNͨnL53Or4+x\& K1D_'V^~?*\$_ꃹ'Z?>lmmS[4ÚM3ae?+au 4X#_Gg7 ֍v-D.vKKΉt+x|lH8>"m(5ɓrQ?h,c0sfnd?b;PVx9r2Ν(RWJL޷)ü(Yqa3iO+kϳޚ?'_*Ӣ2wf̨=+}ѧ\H0iBéV/\u[s$*B hMMjrX򽯿e`!bJ} *k~kn<ȑrq0Xxx8Ntzlg'T>8)$ә[@ D2o?|>w/?~|6 .Fb[|ŗ\bwa8Bɬ&?>Θ_Oه}."L[*yq?8;`e8;̋iqp?{LGsڗ:Q`;ۡcJE_٤ LE}&_0dx?=ۗA=+'Qć>`sq` %a+Qޝa;A_G+XL"87p8l2˰l9J~{];1J8ҥR@2Wsn8X /3oկV( \oۜ3/sb Ƨ=wOe/ D{-T130D1dYC/(GҸeߢCqgc^d{}Gi-k$M,:"0dSe@ӾG[Q&M{3x@4?/*Bcv?{'9pcq6ZNNA٬r0=}򧟼U((p t Pù1^_|1?97>:vҁHP <>Er4c@SqC X}ui`ʨ'Ŷ)\Ue@gHH(4>H~ ݉>h/^j9c߲=}b},Cx9_ xv4˳/׿_fԙ[@!d^Ⱦ{LNgv?2u1KNjۄ&Xf%3Myf8jtu6=V$i$yQW}),62#l5N%P"PmNWT6mSUЏCTw; װK&u.eMwGJjܿVJd*Fm kïa9eP`duOӳi\dwM/œG<컘Xzޡ<r6x/ߍ$L3ALg: lGGoѩs[ԏ`l/n/`z(u刪sӡ E [|klȋ583Lw6m>LTC#WZp*D+ : ߧ[|U'l K !]*p*}4x.&dMm2swhR3;FQɪU_nlm_ɫ#@9 JlK>pg,`߫X_`=l[ؾm^^.ҹOe%TRvwa8ƢZ˺4}H93UJXIߔh%<Ƚ5%CVFe,`4Iy|q_@N"vIRlHyz\$Im?@8+Kn7͓Vu*T30Ra"s>JPu eF|eK\6XSIn2}+/3ؙ53?OJ9mL|z~vB LqOOhc<Ͽ-I4c[ȟr>)[Mua&.q `:κ];4O)=43* @m)[fR^IfuϾJ#*Ⱥk}hb q\cᶕ2p΍C^L[@!dR1:P}! U'MТ:Va m#s9ՠ_!~&VP.N빇lNҮd9~(qTaOwiIԁut2o5{FE&iY6]Vg:0'(,Inp4UMd8b?*jӮ:[K*cc/6 4`W"-v&'m>j/̱vx1 -@ ICcYɻSyMAsJ8T+0q`9;h[\eM9@Y6M} 6؛іl@BvTiޏ:Q`{ۣe s!&[N=5ϴ՛T^./44ۛ4RnQ,Z%ITp-7;zyT0%B4ʄӟbGǭgLϑ좯@, OI}w'> :!xmtnR]~%}ZlC4:x;+i[j8wnuSXwş#+gSXg| &>J2ݛ'lJna<JʶٳY}Wӧ>75U2>|"{=*eMb113UP~ߴ7 eyyf/N 3\i%q12NVV-2L+oX^D8(ݗɵ)\2m%!Wr_%mr>9DPLüF*nd2Q`+[!cH.k2R.2b/sc_0am*H &vjc&v%;54Y cui/3@( WEov624T6;Kҝ&`mX1,P`爛oڊUɝ& N}킛r=;F\TE";MQbs1?l#$\8yu0}ȋG(M/un$wMĤxZSC>ƋsX[+ Ȱu,ki!4b6RNZ*(cd~~3o߷94~!k\0yB۹P6FDmX6OdIV): ܙ$XOzǽOc˒kII`Ib@Jz$&lRn=ʰ)l{%x@3V^2?vBXظ_o͝Z\ Ne attwzNT %O 8T )8Y8l_[\v㻗HEp/JV{[%fXG'B'% y~ /we<.XW6D?҅s=?Og3ӏsugG a7eL0"=w9岞޼{N?z6=Fc8<&ԝ䛢_#FH# e}C:Q|t sfa86s t;H 5g&fVO 6~9gE@*<~n/OgQ\8炀1핵p42EVv@l`Kayɧt4Ԁ< SmJF<\~'W~#pd<C)Z.{ٟ_{] feBn*W t WHJ:r'a !h7Ka%~Wam"׉ wQ\ən" ||'H@|B˰2V gݹ-BQ]vuI>MDCL!U/l}.O2n1G^~ϙT QCS]\V VwM }&V⯃8{)P5 G+__ ˵qz_ɉʟU3![JPի.WxTXBWf-( x<s&4 ":m{|Ѝ)9YF/2tN~"H'jr-~V +^lr0mFL◻a(/Ɠl1]Y,ǺjQ.w^w-.S?Y[?)jrge@d+P`vmg_;6)$~?3?W4bҏG>}'7_;;gJPBY@DS9Y <vena*+jlf}4Oc~aiUf< @_sqKaws0?)@?F$I0x?9gJ Ji0e?v P я0 wR-UTBxP#܅sޭ *.rܭ8P f",7Zi6j`Xǻ 3 &}0ݶJ .ӗv΁5Cm-o 񼿜M>?~+z~?8HLg: ܎voI9 M'#ԝ.؉Xb/h᱌'V*ɿH\;O) &)6}(&Ī~LXV'n錕JKrQM%*bʰ@(-V2Lx_{S7UmitSI6L vBɗ÷広0M[Xy,oO{u֝y?&T[#d9~Q1 12f8ٸ]ca4t%B=m _ Aؕ,Ѿl.OU\ұJhMkgDDʼI`|4@qq Wl ʛgzGϧ^Ϗ^:]?g??ؖЬ3@:[P9v@P_.-`F )FMLuFzL/͒ k]Ǖ4q`|;uxx_NLӳgx|ƫ'Q}q٘[g: ܈Fdގ\7Kr?F-' lN1c0n2S A8xCs-ck]}GY]|Ĺw46B{s[r5 E9/+*ckE.E% c4_q K)wLQt5yP0:xSu+e=F5wu+eKw]ֻt7)ʳ) am]2Y K/$Mn;`a?T, |]ťRc]{7 `t06dxq-@<^ﯾяz#`{(p3 t ѩú!~=g@ 08(4[f_p:vq '/2Pѓa+5Ɗd]fX V_`k,[N?UoFWk8J!?&LC}%zps!',#Vk%?ƱA':;ޞ*42tɴ7!@ W /D{a29ɝҝ&`mX1,P`|cxV3,OU!3+(m4266L4SXH6JߵWmqkwѓWLLJ'b*J%O^\{g]: exNװ.ó{mXhnD$ W,a*Wsjjuf0sœw0Cn'0Cf @:lXߢљ૘=eek*rO O+x|qiU_$T4jT*z tF?S6 kVe>ֽ`t@Em򦎨 v3i:V2 x0A'n~yꮾ"i,y/F4iw~Wy~ T ъ?`2Y%w2Dϸ\9w:#H4 "/HN7vZܥĥ8u# AQ;ws 4bb=F+{;YqrhX/3S< >am(=.2U'VNI|-9RAR3]>o'(CBi%*l{ElWI`80!XSUyW8ނqy,EFRrw,(JXqZҜ N4 @3(A)ltP/[Vv$Zt1YD\|oܟ2?CAkdD9W/?h0`_~:gCJ(p=B6(p xߧK8,MM!}8|˗$f)?dGBUY\u~o6Sx|NM)揽j%%[}([:U ߊ aHV"3] ?Еs5FZwjI\i5:xSݹA 4:x;3}1zWewݛ2N#[n oA3L*W*z3/YW⧙soWj \6.L>˸)]O71xdqq*/o:~ʸ{]hiT/Mi6V#hN]pʋʉ!e(*M#*AV 753\)Bqd,Dz=;BjOi FO(ĶK|4(‹KX>A,s GlR~wf6Q: t uoEJ//Z,Z2G N*a v5뿍%G̰26XκsW|& = G lVB¤HӔY4oI }I9* s %kt솉2QvV Vwm&o⮃7:ӡ@nsXdz0Ҏ8VУZN̓7&K.%nL%׋ꩼPaUvǓq ´$U+ n? SeȊEnhe X'꾋,uєI%}͛{)wHoJp6G̀v|s/'bo#lI8!K%ԒJmmU]W/үc> ⛾@|_L4S%35Hsrw v!rpLpL(p7 kN :˟lgF?~X &'UƳ&MWr8]X`\}:̧Ќ64vG@1.n*nU; wȞ\7<2T̳;NV_ OEh~+ӣ^A~:ӛ u\pSK~&{p&6E@Wߘ1bsrjJnzbBL1N3wg\wi>AJXMY}@ma} EB>!82.hA]W'8䅞z17aYYxߴ󢟡c%#:9x`\7z =d}$qgvjk&="B RTW\flu>ug,:CI lfs*U:b@[\NkPKܗr4w~&~w|`zu=9;OK1bJޣ9;SOJj|ڞ0u>[B=C9~=B8Єdg|ʛ}ʭ <Kk Y-uj{]ugS]mkh]ۻeɯܖ}]뽻"pLj1勞w75Ege W/ё?)y韓W'暝 u,7cT1dez􏄙B܊;awΉꋰUom}k99\D?n}0^>:ΡMx/* @ad7n7 (X՟3|M~}VmFZY:v+k{-挫$c :缉m%pcR aD:MA&(p{ j;/A;xg'``v7?Xg_ìNWӆ¼ (S^*7@bߤ(̦hem郵Z@؄hVE&m(VkU 8-TV:nVOp64 +*4xt<"/`:fѯ/0:ҩ2`9qe|C!(;tťxtN 終/qn+עp=|+vvKwoi>vA99;67u_&\ fo9oƻ.ue w',o| [lؙ:dGiɯŞ{S 7֛׊1.#a>{7;3{ )< }GNxYhYM.4_`6vԶ,@ak =UWjrP ÂU~_SYnL)%aكЦB% elˎbL MotBE]Fs`Lڃ%`|O>nOAp{Z P[z_Nv~W ;-OW4Ǔtq ^.씛OatvK@d$eϭ6aK7v+vvF{ԲAj?yVʿ;q`#GW;o<oёV5ٱtDzQ[Mtku7ɿ(0Pi(o~˜4DŽ;1.}wYT8̤q1$Qwk2`\\ `*uvZmX mQqΈ }{Gf>R=: Oy"W 8=ryؽp0 X38pez}yy3i`svـNItP(mxQ1֕igioPyC Y8Vnxv\qv/j~/ в5Pу?#ٿ-ҝΏ#+xڡqzdAw~Vd.U lcM[z|7][X̳W3;3tTPR>]5)1tP㪿E+S xlmt?_5k{^<ߔ7κ^u o ٮliþɯ܏GSpLD.`q0o]Ub,Xwxl+L'? O 7.<=&VsTMFq58V#`ĴQlMFvޗM~}eWa5:lЖpFd>V땑b)ڀ4$M gR'}toq= 9=vcv``)}pV0NS Uǵb>ؒ)S@y, T;!R~'hW @8uOA#;B bU8[ʈQ S,U.ãroma[;@,}Q 0LSc1nIDЬg cxq9toَ7 ɥj@BEEl p?z>M[NV "omfUN$X>ޢ@IDAT{T.eYS5'p'm^e|F gݭg/ G\0r#YqaJY>#W@_o֎Mh$!lR6_N,j$'j8pIjO\<1FrGgD$vi7$-*`n-< xPt^>>>8p1o캅0V*u*<@_ (˖Q2=W[?K_ q _/V]}e]\/_ׯBBU'q)%w]=]?m=Syo' 8Z;,)m[.h&v|2o&_7/Q-/cs}_Pq7/y@A4Z!B 8SE~TU$դ)mUџ>t\TF¤hyEit!t_\YW $Lk-cdʼnEpA 3I6)05),ž?n SH0V'2P%߮5iV K k6 V ?V=Kȅq oXc_gqU1?*KH\G&}}$NXKF>(Cl_mau(`#ӻXy7\pI)U>$oAܗb#{)7:-p'O?g, ?a>)^ۡcpG1˝KDc//#x|EޘǢ'6kM=PE%V԰Y/]m{ezAco:sJ⮉9|HMPtI*܍u j(0@u?*sYaE-}RF8+y;b}5W*e.٭ukۡᴫgo]ƱB8Prph8hߌ>ci%LF ΀DʸouUჺ'ڈT~%ԫmׅZqޢ.wm&.)}ׇ%XlmF_6n G^*T\q} [SDvx_!)kN5O`6/\l5Ow,Biw΀ xmt޾ߞN⥀= `WDm L wI .iyN?Й`0 ң# hAB ӁBX(8UK{::U[6Z7p4$7 4Iowc\F&vaƑ吇^yu%(pG 0@P࿌>~ۙgPXLs09>c9 2ܟ;f%^Ieզ `|A؄?umb}*bqW6`NXpK:t dp#F/ Ȩ]+ pŃj/}#6eL,3h<: SLF~*arkY0%禐US'- @j[q8 `qUWrOhT+i=[Htuvo;pYeJx$Z%NNR'^bv`)ܼQMԳ[mob_ `Q38*w X"o~g0wwRbׄn&L&:Nۚ:|{>Uèk{_\73}F'fy+xGt~bKɥC~ar6^ keP;D_ӕT@i4`R` GJ#]œ<٥KXnm'293Mn,$yiXan W;V /w0glʨr^@_#!ֶi7)wO8ysrإ5Fq"  ŶBc~5Pnw׀PsG+XHSv$…ΆEaת,eViS}O ˼7;v0b'|L jW"hlW*U{r߳ NhO P M°ed=^oN6U!vumK*orK&>[2g9}앴2YŭoV^c\;lz'xˑ @ב.`70Q ./ ;U'*V+HcʭոۧZfDVܜ4SDav?r9byj`Mj  ,&34iÿɯ27"4q'z} y Լy`eƱyu所@;S`ܙdC2!hCP~N3hѥJX5`gA (A.&5ha팘E ̢,贖,j%D Gl)C9 # Xa\ הnj GQ9 4L9rmĝf ֒ K}ji'[L]_ K4˸i_ïq߆=C juuSKڽĞ*B6N6䓡aAzKy'~NE OF,V(yp/Ie9R+vE(iWSg SMARv:yV#,u r>AR UK$,SdWkڍקpvg69=.ۿhO&I`PQ`9{5 qeمj(!݄ {vuQavvs/q~el?棬egW.-9pr5`@ ReH'㩌VհWHбaΝutI뺻]5%uݯ/'7&u%Ouq$ G*Ui>jcM~mV0zgHNh+DW q|:,$vg`?>̛-Ty7cp-#8ߛV?`뗚·*'*pZ%gRPG.%KL݃o./|/_Fߜ\?ŷU4gP* m>۬"KhL+&p#p)hOUuK+46>gKq1!39Gm]g| vw1P_vqbj`m"=r#M-oO/RdυzHx^wONG96ծ6H?Kg*;Ρ1_1<𮪬 iB$&" % 4pgw/(8͑/Xh|K/A @_$sl9۹.NN>OFA"|?'ővvܥ:.A br\xǸb2AbWGNA mɓkv΂WV W#m~"\6qt^Jũ`mLg5R})oyN0cMnt /gTu^ױg)}zo.-nTv F_  c[X\VӮ-Z-7lg. UJe" {wDY~ɴ=~ ]]=WrM3vG~=:RNvB?IlXUT/1?@|ُLA#b跼 PUWB2D`+g G(tpb}(: JqT6,Ssڅ$ȑ׌YŸEbsy:YE=݋_}/N5P hI1XC+O# LXf3NnH3 , ec: *4rK\I5;`~zdۇ(dp0^14.Q[ͬѫ[_Y:x+=~wnl_i _a ?u׸6ĻGd|jgn^=`o;HޜqC2'ezI8s.V`.=aؘO,1r@/-P]Yl%иLϷu0ObwN EfgzW+31~ VחwWӾK"4؉᎕;}ݙa'f&(>~^ow*QQ?#/f\)kYw >? |:8ݝ|O4~$AM YV_g3׃l1s8Q00mG'8|¦Y^-2w9[Iv s0!y_jwX} qM%) su|OBlLkml( d[{'R66e|6\xz>Pm٨LKnNbpswqþIs]*' :<}iX9N@S!EA;R0k#pn[3P5A~O\qvggnr!bgh)Klf\ 09;ӫPS`lo#FfU?RϾ͎vǻW\'i׀ *Mo9ՠLl5s 3c?k&+sγ#;m;w5ׅzxIo.jw 21Ct}Хrx\in󳅼l#n>VHfEp1i43wQ"+ndi.ĺ5e]ᓰ%2u[_ױ*6~Mߟ&/EC6@j?+r6sgV8ޠ T1kC oo&?Lٯ柾};;Xxr#@ }9@EZ(|7>fӋt>;/>_3,>9K )^w)ݤM/Pu9~. Nռ/(@R8h캷,VtZ|s3"(JJBMW 4M$|jC(0Po{6-yt' j5G}_U;&: ~/wi|Is9y n&gxz^wp2LYz/˷¶/%Vqt{n0&`v1)+SrY]ս[;Ĉ!-b,j3h[*kw]^g? A X X!@s?0 d2t:[\vxPt#;>/dVk͋pP:5Kۙ}z;x/KɿnJ`|R1pCp[?wDMFjF^wqI-F\Oi]^Ƿ8Pl>އ`5mj{]u-rnYon]3ڞdK{M~5^^>;/m % pIz˗d˻o z&SSs\4]D ޘƥ~M*KRoy쇃b*X:0)<ӌ~Yg}.Ч+/oDՎ8M%SޕH# @ -v"3T\UYtWQFͧƩ^U:˰Co;EeLu:=Ҥu&# b xQ/:?3| ?Ლ cp_,&h ϯ_#؇N}@i~ v念UJq&MFZVn3z8![ cתv!Ba">! $H5'>`*7v`gHDa. sA;pt舁@3X<)@/FI8-J#bUebrSt썦A諚:lmoi~]2$)h῎,G 3Ou#txFƳ/:\m+pSZvTuVq2:Lmoyw o?z3u4ϕ Xn>IU}.Mo҇_"1oYT^oOǣ; xӞK1˦YIdֿ;>%i:fS:x8+ / f[_ z#9+oMw@+[JMto=*3]U7]J& ~`wuߧ}&Ϙ %1Y,a !Ql6-ye.25#/=/C.]cHb^֕93p' \kj@Dи5 Oϵc2S ^u'8fy W)p s+WXWAoOOx:i1F{R#U"8la,0[ySA3SR/1QN؀]% j1Q8FvaP(ޠhwlv&;o Wن+H5pD  9Ǐ?ww2e3|LB9tc} ~>9ɼBqc SOQcjhw2jq][ҕO;#iroTyQk@oF)^W:x}~ +tm")rA-ƻh/hK`La/R=K<ۡl W?r0'_,&΄mG~K&`^@" ؍6Ή"xDߊ|;xTPX ܒii#eJT;(0h8hCl F6+V kmIÚ~ j>PKdZްu˗ o|K7:Tq9WW`D2Ť6n)%  -'y?0heXN}V СVN"BvJXqk%עckHo *I/%ŠPH}vJD`O ")1K |\)=8C= ϥ4{NA.쭰*?L{{aRF(SE yv1vTMwvVZ,fDLWByЕQO aƠ ?[ž68K@eu;nnji)uRK2vݯ/ ]r __aIXAmЄd( (Ok*%.WnMq.YUWHnzwm_xZo`}[, pguWΏQՍ x44Ifl3f!"uܤnE8:Jׯv[FHۦ"MmP}Jυ"?_<ۦqɔ& |PK D_׽fj,93>0%-?GZ&ۑ+5u~/ ;"xԽ9~5ý$2 *sK?G PΏ ךmAI PJ`iTw//S%铹u݉ Y-``헰cD>>)sKvl{KskG cZ;9<<Y^łWD^/Գ_f98]A8BZY‹ gh/ \Pn8dޙ$^%gk"wJbR}~]Eڄ[70Kb >vIq}{m;qIO4$BFoog.pwA'[eoХCVwo!mPMЙ_:unýsK=n {wiG}mn+Y1m(X֬Yn_ p]y۝>& sgxe 8I7j#yv(tC ; (ށ͐")+vWs]:RϾ,ux d LLZkƯ`%TeHyM"&xFK iH|G?@j`IBNT %kO{v h;vs:^gymvk ^L{+ \ݧ}/I܄(ǷO#_8znH`hźuyk{f<]wOL#xˁUb_7wN֋Arp`%:;:xVY4y_2cf%x`~_5co:~c(}$W8ƪp҄  O+`/o@ !/;*1+h]㌙}R*Şy1pCio>ں}s@_=><7/؊jmxQm\k_0߄c$A [y\]4y㾦(+r B F\!kCAGim,_j:ھٵ~:?CG^'HY/B]p˾b¦Gj3DbNFf}iQ=/'&v#Lv5 Ӵ.Aߵڌ_) ׀Kıd~hU6ei;I>00c=>Ё\LI;^@OyT+0KDw#}:z;~H@W}u"lEg7; Tohw\?톓þc* ;.#8q6ͫ1Y0ɾ.x0x g|F#R.ұ50qM0WDwD3}Ŀ Ǟ `-!FJS;n[_yJ[rcI)ms/\p,]c};/qV~a>V(@@ey0V[:JJ'TLwJS>V2:`:xei'ď>:}`CW'A(ͧR^;t?DŽ?HNZjY%먰n"~ -am0Xeٻ䬛oϜ^VnnowmnR(>VqצqD%k>&l/M_&& bE(wR~]8۾7,>3="cߡIף4h )%vkV$lO68VùrF_fՒs3_i45| -)K^^)Z \NU3no{d7[vj'vqۿ&}v[Š W6p<[-;$+Ÿjjdj~5B+^/"l4.q&AVf !>q[bI|_. 8 nAhg'ƖA,YGu@]P ԡf}Qo֍prYd]|-np)pvԇ2MWjW#LW,D8LŊ?Fv_uϱ|n>A'H(M-O? ?@/6TLD0QoӅdfs#Grٶjbc-iJ(sI/LY*/ČD7v_5`2Ea =\-`.rˆ4˙8z] D>2) J{ذ~7MMY[ c/,ڪP;MFz>q˷w0Kx3`1֔ɤ+=r$QϹ~AS^FX4Y'!=Wߍ;4_"i쭟ABx{>ڤ*:^ʺ)rhstu82tְ:&'D|r < UVmn|-\N4~<8\<`Lpg_ ߤi?p7K'xIA,mQ-]KX}OUMB{-Qy\z, *vDoN_\B F4[K'iSI7{뀆8 =ۀi^rۣY>؏9baΫ<%kכ FPikO{Q r:WJ$#qQ̨D_[ 4аC)h7[lL׀k~}mh#3Ѕ9D3Yb@8Om0f62PP` -a?;hxdvԾ00aJ>H0Yy S]M&O[W|Dz_m]L>ኾ+jW2pBwi=@G`nMڄD;eLփF j$x~4'̴x2?T; R3Gn͌3]W{vٞ1 ! @BL8֥)S(CBaY2:t2/]]r`v\yJY'% *9w/q8nUV1զ7?fކ_7l;K˓3n(t ~ v/ g_n19ww1LtKI#8K7G7wOq؅(dأe !W!P]J^&7l{>EyGH4Xt`M@ s2YQZR5zo/=F7xlT:d(PQV3] ]hwd$ɌcdIQu£+";o44(Pۆb9 x F%!P*ijbvɴ- t. 0J㎋)ce))887N2%,;}B 2'w:)i9T3s &wЗKqw<1̌'. 0``o]hyKV^5}6Ļc%gGyB~k+s";k. x%c^j#6,kj8>6Wgeyl@X ض|u?&%wGHEc!a)"qg7!̈2QO,׍fH;Ѷ xl?%e]ԫӽn.A֨E>3.k25B}Bo&vѩJxgg%?JuIcvS~yӔH Exx<]\M^^-AX!ODAL ɲ &NZdB`L C|آwx-!P-'N9OyL!igXZyIXXv2U=fE_N~xi ~<"z2Ia=qG8Ã4'Jǰ{L *@wBG=0~-&*5Wwf Ov`F\|Ԇ)~55 ~u;^}+Uٗ>XF_p>2XGnnkwXI3N߽v.@s$<+2_ /1Fp4@uyd^VPp)9Km_ny7r9̃)jzv)LWt7p[&NPa#ƒ n~n$UOx,7}a:ReIWf879_}K~6F>/~ra)M;Q"Ñf2ZZuu w_xVcwBuM@TT8GvF&T&옮@.l7%Sf\% ~x: ?pr tazf0d|)+Mơ%2,x#-W#TfX{8s ]^o@4V"sF|'K]aK[i_MhU<;P8ԯոW K%3䴭 %}9ֽmե~B󑕸ER: Sφ= E?\߭h{T_'賀;)@)49 Brc_reR&# qqKՇ}~}lӴRHoIZwv^}\L~g?w_F|3Fg~;+BAw免ZT;yWˊвO#,LHx"%c,OpԒmgZc4rvNT8찝lbƺu%iA|Ey,g{Е )`8oV+eE-eB?ӍN}B!~( 3&ژ0ބO8 q$ NYoYgxɴE |[Q)}z 7y`\+N^ӌ }g9 MooI綰D,ۧ@ݎ[{5^)M߷18s?ʴfCغc @ooP ֛]qhwKWy'܉ TSi| ۏE }%Ih pj]pW#BjMb9 k_ѽ`rCe@X#x1?؛\~;{G?zt:>XON.xw?xW576^pm`|Cv"ft:ֺ0  j\K ^e{e wvʲ=.%7 (hw-EŔe DԶś #My;;6o6Bزˀ)~xwqtv1Vd]PAXha8M  H f{F)mg  ү b_ԌHt#@DژcuPghvP~#q!1.'h]eߺ: /m݄?FXoṥOem_qު.{m˻^<=3fM{_Fno`u r_}-$_[sȻ:^局~3&b 8#Ui?m Uo.lשj{2˴[;~ڃA'~OG}饰㳾Sy0TI22zy-|֣i[%OW`o7s 7δwq6ieɏVeΏ %=*/\HdjaW ?:xq|a`~138Aw?~~PT]=ȓ':/0:o8ISzϋS'my`)J&p>c5ZJũ`mԁlnyr'T޽IuQ"I>Nm$Xc[_o Pp61(C#ߐ.m7e9 &; ƣo>0uŀ dl+I3al;tzQV>q113×yYQy"Exqewv/ghXEИpB4ma4,]M|F:wEKTOסɍ|A_^>3w gJ:`pǗ!$("~Nq6u1Ll~B@{߾W@D]S]R O~VߣR"Ca5*e<&SеT _œH%}uB2Ok6 lo}vS4m|u{gƱ|+U:ٶo\ZuF~+iauװ D45`~y ȞvXSN/vFǾƩdpQ~qYόnjjzWU ߻cNg>Ժ#ztTJh|j`LBV}v-E cm'm;.wZ+t1?.'y9LCo;'gP=-Zv, IFPx H[ޮSsEpɑű~T(&Ɯ* p/KƎo|z)u0ă鱜:KdY| NcS1IrLa= g-F X<*ڥ%|xhlLa(U8l`R hMh/ax&D.~@]0H?fpyA aqúA؊ӽENn]2[Ƌ*˟lphk'^oifGxh+&GN0bO'%!fQ7BHj%'~2]/A)BI˴ [=gz >#1% oD"TO#VӰTPȋ 2dlD?ۓ[U[,In|vhͯQIM?ӺR\>b'!?𼄤XLGG;W;>_wp97A# wTٷ-g?\Z.nyT/U,v x]ۉympLl1cJCwúU%i4%<>eԨ޼xu⭰O-U=#9zGYopb Ům`+tc{%j>!V{'ڀYȰF@V?yMV=uAr4f[z*%+6Z3?->Y\**UKj>PKda5~/+:;ymL ɽsѹf +鐝 x *4hOLYOA$#u݉-3;$חI W!N;`5`v~IÑ/L;\*`x,_%j۹^.ug[kQI$6Ǥ"s6{N NR,gG*IϬ^[+4Y?vMZ=FB/ߕXޏb%QinYtc{$)ߣzOYpC]wW6SR\˴ܧ¯jq@h1Ռr_i7CAf U('~v;:_]>=jOL1h!NPO$˼s'/Oy0#Nc zW_Gh/#NNa09xtn MZ{ON5!~KzORPPxKO,}]OE|k퓟0J3uH7vg2a*{>E.b@Re$`) RIGGkA1us m:4ÂeJdp0I̙h3e"4`YqLbAcQ=ƒ |8i_b5ũ`=K3U?}~r쮿79 ޘ;.p@.9Fg韗;ת㬽mϼ͜fo(U *!R TOv2:ոU?sHxf)HMOKRֹAov;KX[&LOc'A`/ |S>3t6.UXpN> p'jm6(UPKWpK~[~5k&}Cy0eDߌg5}S[-ôwd{" &kXzՈ3hpКv]uU'mNki_nZŽZZw5?-ls?{oKqRi[l nدc6@CF7nZRթH~oɵ F ?ŇL0H@1^R6mP y6>M܉ſLC?M/ y(9a]B-tD}LTǟOM#.圮KNupE. 8/,HKӀhqUŸ .:xr_ kNYucF֓*\|ȲY_(`O>'#ak6e$v9WXRChc qu8vq\~nJV ߯9|_􋎄iP +U_.^gW%磐m䪓`O痺~( f@I2siA;~KOpqM{:uBޜf':zܑ͈{M<)Hm1A&߀kF~i̠nvM0nsMrz :]+0 63ho!Ҩ__AwƄ6^5̺'}ON%&.lowQ%K(KmN~ WJ]G(sx%#ʣ`-[_Et2> XӵWMǥc1/lx`:Mxf !,ye^_Q탱^CN贂teC>+tv/iY;% Ka@huÏ 5UQTE ' DZxl(}г; Q á_d@G?'[щI9·xotxc`̦4S9tc&ƞ0wR0-il|Rl6 SgDUʯJiR:3 &柴N%&׏7?i&H>e4%MSšb" 2k[{@x#5Hvfjd3\M8.Zw mWE&>"s&{158BEetg3USMX 5 ɰ2< lM'n=F+oJvxdS>~x~~ׇ@\s5R;@k 8aTU*0Lp-Mϙ6D& 9I a>wx8/J^Du}45t-\wp5-SѲ:_yWcL.7@&^H)"knH^Fp}|Cxl )M{Bg ~PܠW#I=d "|lL9<CI@ nh+wnģOWŲ 5C+PP?[h|ULXoJMrֆV~_걲LJg?zO_owrv5w6L3^wv>*mw؄~ż񟅿^kpb@0<,o&oqlOkt_r?Iz 7_df?A׹}Oh@30 6t, "GKb^0 ph-"\'ռ=)E=nGn1V?/x('}ӤGNf[O *= {`P٠yؓ ^d|E8c|Ԅ1/xקŬ7M!-!zyg μ^g!ף4>AnR7>e쏘,xЋ F@Ns ڂOCV_`2`sIN?m$Y8c๓E[kPk3O: /=4ȧ q7&,*SYjkh7:|SͶLicG/Ѣz(hch.2pΦT |)3sFNpD\'L8Jh&bX5os"cGr^f8oQ`Dptw\Fߤweg:A"<({>Z!6Ժ-Do!6Eͷsc[zkN&/ĎnL4N}Ռ C{Y-ّ%m7>U+ vU]syImO 7-&8bdN ѭWm(ϾS/&6L^68D'Luݱ+=7Dѯ'UWZq?J1;:@ƎjT";*Vg~rz,M/dYRkBjQm[,r^Yw4z}i-~zO8o廻- ۪Tڟ~AGN7|{ӝ?SߠQ/Yl1 ǁ|bG'.Q2˥˼.?d&g+jhpݝi'^wɵ'oeC 4j^e'4Q>d&kt`&JSy_r|;.w mWE&>"s8/&(O %ZAMi#`k ;!0q/¤JbH>?h g=O]u$+)!.ƢiB;c*lv1L>f@p"}-r[p۪T߱ /_^_{xHOS,5fYH]x- xΟIL`3EV\/Zɔiu|*1*rzGS6t8UڤN؟N$L3&l~<'Y38>'Y?bfRpdiyIդmE45ha2) l t.Gt(%][H`cpY<Oy\^*>{MΘ쎙4CjqE~g'>v?BNz7McmS9*9-ۑ-GdK-t;C;奯k- Hy8!%9ڴ:3^_- "ga~bE5]ٰaC9ó%O9 <ʠ !(EMk󁟊HMk@E\Qzq7/dNϬ|^z] {O CZY=_,) "Y{#`xs-oI1G?>3I.37v[_I34gR?Fa71)|T8-±H}Fn6|S //QZ9 -0#vB+N./Sh/PyX任= ۫s'迩P/NvQЏS`"::թX7C8.09CXQ)lp$*(}1ֈȜYlHSN" cgF}Wgfxhbo|zb 8t&Ο}64Yf759gחE4Qɑ!n^&cޘ;˦&ҧR3@x Tdm Y֣bnxj;N2̹L)pURڙ'3HL )}PĒ1>1GQ }qMֲ^M.<ϗT@IDATՀ黏 ^h7l8;mX}#7j3Ъ߃U=_ь~#yCJ+ $ Ch/?HuU#٤Ynp]m?OR/v*|y|C'uĢx=]mʸAƝ,7:7+ Ż=DWNٺxRC:0O'ONxJwJ?=sEyv HD݁- ~v_7LMg?NhV 7t(N["tVPr]%Ktm$m=]9Zy3y%̸>N{/3MKd+ ѯ[Z['cAwjvS:1o H]D; Ov_|Ÿ^` ȋB!f:Zd2'h4,_0SXv ,Uuw)>~chVߩb1= [r~KkgN1UJ=DcH}|1hqS(6X7e)%F?j&NlUkIdYط"_5aH&8[h_uFsDDP(AFn wч/X~+y{+H4EDj|WE$7l!\ ,΢+;fxڜJ}ǐ8ŅiK܇PJ2ưnwnP'V/ ӿq9* YU|u0!}sޔg"F0Ae цͿBtH*f?aӣ<<˰| GXtw[UV_tߥhx P#qgp\T G?.]^D'WL?Y:; 2`G/}d!)kQ! ZxBJv۴@znn8z&`csjZwKv >>"xYJ gEԝUtQ^ >^KJHbHT\$fePqH63l^,y?}tD6gd/̵4pu(Nis22kz!ܹ0ʬGڜ mob3.)Kje;: keA48Z\״V.1MMZ,8ƌӸRBi i>Y~g{Π8_Ӡocyl0qzYXrF[$~ŎȖy ^[ x^R<|Ȱ١p R=gB] 77yc*p.|(7Rd!߬3+N-06oZ%(bU飬H q>: "yjW;MŜZHr ;N[ܠ>}*!-i#Mެo h"xgsX"cs 1d*QW@]d738Νގg~jp|6/)= @܎ЪpKӠ3a]\Lnvfdh!]hE\gpT'C{Q Y=tYnQkii5p/0`!|ַc tO$>jgAB{G:[JQQFMcSkjIRcX(zh- Y1}B;E'8)-+#5ߛvx)픶x0+K ’e/WKI` !Ȗxy}I q䟗fqx/z61FPuc~Ssp,[cy*X7 1GkwTm=[HK:̕}L\i 4)fryc[+Nsc, VI?ƸG8_ .|Zܱ53W$٠t`\O3;2X9-"ǀwh:QbE:9kME5s׻^2oQ4oΕ7Epo7&A=wϜ./owO|P I/-'rv# [aIJRU$ M̚2%LH_hfֈ]QB1٤ą8xĥzͻ!l1A& /V Y8Wɦ1͹^7WkMbN݃ſИ`&]]ЗVw3w~l<)I)'/rTNtK$k=}%Z:)˧~+CK,Mc6Gw+_43>]uU'9XFmL6Y.'-VeȏDel-4M"b𶑭rAG٘ncH+SvbQDiQAݏPe[cb|ЪEFd!;/~`uT(c²0Q31g= l~) n GVy|ckh~lO n%h4s2>E5 z \]RbmL~m+S?b4Jא \$GsW)&ѵ=j@3SJp`/}* ƬxèH/('9 `:'KH6t,7ne+>?]!!/rHrpT3jZ/9 \Vv]2aмNʍ&g3 =u䎰92%G'X JyG3³!0:oAfo9op ́b+Y ]搿x463<\) ^as14qDڿtG~p1MM?#8ػ6: >tIztO~?漺uc.ҏ)a[]jvcp;ꂛ(~jRwep[}*_k]40C3D1 aW3.үr{:op* Z;Q;CiI!zZRWL6X MODrOiecǝ-ڥՓBtaзE`¸@ `&39[&壡B[dK w].2e}q!@“ҫ%zyb^xKй]G}_JЀpsS%ߜ  Z&ܳc\A;a`87gkT5/GLC^)@'eqn=t'Ȯ** )L"+3"[afCGv$1]n5 YׅڎiPE#+T>0h:,f \g!͸G +]^YOX /Ů"F+[^'ɀ',MohOz\gtB&g:gdJ/޲U23-&?X`Rc܄;<KEobrAJihq) n$sg1FJ)206dq>-p)$Q\ $؉(j#Ypuט+f-(Y`bדY~C#͝cL3lg L.*lx~CfnPO#^UӪ(PC#U۷S!H34-\i` ao_\"j{&k%jWm("kFޢ?y^_xgϟ7 ><p݇t̓EU$\I~ Ͷ&gӍIqUʖ>y7~iQA[vq*H糐v̟KIݫ]`:x;vvE%}{N5u ipN1ΚΕN!rtG[u:]vMDm.D*ӐhI>GlF61 o)~pLss/,33"EnZfR|O6 S<@OttJK^ ^CcSB[)'rObF> 3*挦K k]W67LN&GiYZp<[C**q۹.X-o~,={>Yzc 36[7M xW ݡgI3J\;8SsZ@gT ')×F5em2l<tٙCD_UݷmBYi[n}v~{Gb}G]얧l1_c{@j7c#,8[,ϋX_?c3Ųq9:Lf M`<jžpZ^XB7^8+"s-F嘳@Nmob/(1Q`c!+›I9Yȳ/xoCv^|,p߾,/IGsz${^8ˏ<; - e!.^e|0-z-p !G\l8OCGdK㢀LݝZUQ)! o2agxmM.mk_7 l2ʹ(Y('~;QbaaPT&Z8N/NA NV$l۴@z/3yA&ܽ=h)|VEXݨbnt<^뗻bX+P|^WR,9&MP4Xг'eB 2|Hϣ#OB\j;p o"&Ҽ )760/ݽOUrno&n8]b/GY9xB(僆K2mtMT!ɖ,MDkDW፞^sjtJ t{_fu"/pXi=Ly x%Gbe\HJ4j_0-xl\8$lrmSYɇVFִ:>bFxFd1YݶX> uX sCӝQ]Q}U/df,bH9uH/~*bpY9q!2-";}CIplq1-p%>]H%:ܐ_Kpe*aV F*2vhޔ-@$"Vvi&~ T9&G[& @_4[r'| !ǧ$G;6ߓƎ;@£/?hlVSl^VLڔ &x.f.60."um@t#K$g6 +̒TGᮯ1͹^7W?r#L@:-rInRwQ2/DU%-,nЇzXsg0fUED1iF (Ò'a782{ 9NhFqWNcR2*HNo,GyOZO;dǥ-їwz@mZ8/s?iރ{҄M3s#6IvllpIwN7ңϢ=?QsIN*x IXLр`vMȔi _y$ wz@JE^[NN9|]"Nq:vmxE`wYa+rIxD t6(Lƺk۬w{' PHMB8],WcYgki6`XԠ6۫!/ǣZ.jx`m0< GD8{kc#6C4+އ-2 &i`fP1盥g樦"t EeIzTpZ]irX{CE&N  = #@D\\Qt;FkM`(FnAT@Ji`Quw!)3Ro駊"(0O1p'+tXIPыbQg"–!\f㕛X$͉F?I#-4?'aAȏZ= Q rLS\8''pgIS$3J@dCfo ,M\ ؾ`Wutm5}mA/DKɣ%ܐ{yFq$㢏9OՅ_Q&'IDvcH*~\wߐ71cZ\Eni`7xƟ;_uw"O@M o8hx2FJl]IO)?[N>P|پkU}ǯIߧ]`Vg%݆eWl(;-oD\})`u @cG{=WU*l[MoW@*u-1zc%qXRK][D5^8~qu3$i@yǴ%Ms4ZTҥ-їwz@yZW/,X/Xz"\'YWpHZa< A>Q+g9kkp-[* wghV8x)c,7nKiQw!AAނuzX{ _m?*sb$@i땽Is)7F;ON.ۧBS;s/ w ݺ}V~pAZG¹ZK7*8lsKLE?Y WMA8oQvUbbwY Sy#[`Wo\IEn[ /qߢ#" )Z@UrPHFGi[kA'i-<iMӌԞ]!? N#<qp y6k{24pexau-VAZ".x y ϊzQa;jgAp E~e5(hޟx1rE5]Oi<$㻐'PݘU1K.I"t=\[qc﮶@ln)4puz8chnCu$ H\>Դl&ͤ3 D{N,_M>qyVHv(%]u~Kw~sƞ&lx"pH# KY[qe1}q+:4ປ­Z:ӯйy]0C*3,7nSXvZjZoCYѵduP JPjF54pu8E>ԱUޗL-"4M_L|"}o`jUW^w,Gg㎥37 Դu^Hc?j 5Y'sYۗzB"I m 9DwʟGxŒ?L-x0@{, m .9|ٳMyǑc$h0mr}d]W"eO#p죧2oi9j>(tL6ZY {c)ı+Zu|k[J?vlS|SÔRc"GQmh)|4_q:Gk Պp8i,yOY|SȢG:0<ߥKsÓR=һmIl _cNމ/f-7nS߬ks`.N#{WR GI DikfY,=Kj.NPIZfLǨ%p#ZLHF`}!XdVhr}gP'G  EM9Ye8$OVxӀ[£G̫?4P n J\ybQK*1֑9.V^1pe"ĥ.-t;0Ak$eMȣG扌HꇕzDpiBOŬI=t|XinsdͻԡI_lZg@VæmZƌHxXqiڀchcz2>f+Ѣ`nno3o廻) qa56.N~zm-dGfܒ%&y .,lQltxfOh(Եb\O;6~JP+M5]7DnZQf`>wP.[,0Ӳ8jovf>A={[gA 6s^~ο)QspoZ>'.Fm Gz j&-7nڇBOwi?p^DټkcJ!+ NX_lH9#`d@cn_v.>f8AsE0ϹC>w x;~K (ye|r$03Y(PfvP8X3w(:zG$s?`ZiiGݔMU?COFv-\sq7KEal-ξ}0= Lu$;r.OO;*.Ton~yNJ2k)0fcʁbu=j*pRL*Ym?c%#r2,"|a".[[`} mu   @7q,+ɳ\XFcEF^o7O\T踁D{@8tl,/=ğgM۷~yZW/jӢp1yü!ei1 :['UbB2[Z^;$gz Ғżt=zQRLPw}R\I (@Iwx/g+S~GY jG:XE>*;"5mDy̚g.VO!iu79>07cznf=ɝMy$4B]2^C:ء+Fj̅ۤiXol`-8翄襁R۝ .rq\SIuqռ~3<QIX X V[8hSxOw!eóq|Rr|wuۺ[¾p{u>X/+p漗 bw9nLu Ng#{ک{WfIiCz/ 1(4vrZgN[82EZN*0̕o[ב:'±tSs0Ol S-m(֫%{"fp+9ZHVh5q>^gBC揾"C[OKwի.fTC6ܳkݖzV}[iy'o+ԂϢ>-2 2;#rGaf&fjEx'$-3NctAѥ|g^pKN ,֑/pMYTß'0zzwj/ ZJoku'iGegtڡ e*:ܻ9}WCX4ed30UDB{PF]/Z)k+[U;@qSbuYZ?*u AbNhԓBğiQD# :(yLؼEF5QD@IDAT46 xe}ʫ1xPNjzT#a%<ܣý>4-6*!ߖЬbx\S bXiާ"Krԥ%k)%zK1q1mp p#{x_m/܃62:pe%?B\zkl+ݍXoHE_c1#({حcYNpFLÇ= @~ 8Eq>iN~0X9]s^^۷>ep}dϔVsp֒T w!G-/ܯ19PQ{Û@j/ݵ-ඡ/u8p~ְ/n.YVyux qQ` [ MXl~\̏6H5 z7dvf]E^Yl%g]V@zjN)AC=GT^+gFj#"vĪѺWs9mb~5m 6ib ~EP7ѱ큊?D[ J[wZ@ 9|ݖ]@Yk#gdMk ۤDhZZZ˵!0 .F..\iu|c1-M)equ5mslf;PnQv~szK@ԚU֛?baXeo{]epOZq / lubo̻n,ۤOD#.G($ lqO,-)+@g`Tb*w*].}@7/P]}Yo˒(?gAz p>S"qU>l bOe4:%E8{]>%w҄{P7 kHQhC\}-b irbuJsn(nGc*~:q{(DZNW sSvp-h-#Zռ{ 1ܽiPY<{ uy=`}}PJ~-#|5WS i!A>yn],mt͟Pk"eG8It)th~KN#<+uK⏚N(x{VBdNB zg=FaJU`|Rb;CNhϤ1gZ1fO13)DMl1oǗtpJVp0/t0C2pɧ>SWC}Bm^I^OB(sOQ}:w"rrVh# 솈ijZ#ykqsw?TnBncswڥ[o\z :p2 wb Y<pE ~R"A2d18"0LgMr$x;ȘAN}S]G8< c~mںg!ZߔI1S7r )t<|fK~8hOxZa'̶n`1<|fb̼]pշ܍I;.녱 g4qr8X7`@_MW27h"Ƕ@l~#.3<QIX XW_0JoIt~zx;8ݭi7s8z;}`EBo}Vj[` QdID֊oSěK Rodj]E\ɫiS2#~4s@EI3%k7|jsh-nzoڊcǐ/4j:mf;zb_ww[U߽g}0Z7Ӛ+?0R"_yb ^hu1imj*-hǻsrMG!szuG@8{gdk]5j Ɂ3.)T69d -"/#m2N8o E{\bFsZ;MěOTS3ɴ(qQ>s1Uۈps7ž ݦb.}B+n/jsgiij2qPp7<[ñlSMt j9L#& L4#Em<]\47r648-±Ssplfj(K pL6<w >g9 a HKh'E"\4Gy.Su'{nnZoמ'ޖAbxtߺhZ gywla@ol.,79 >BzZ`oXo>ҝn(:2lD_'k8O4KN F~`5Z@'},̦c y~n}+|^ꣃSlz : An΍>C<Sd#<"dYrTpZ:];dpQ%y-RQwFSxiWa45;ǟ5!#Mqc#y/ڽ8+`w-*F,xg"KZiƐ-BBOEF+IGmK;-U-, **GKWcdȘ4"A.M>YQ)>4)Hwi`W2H1M4t-LZauaި4G d~eިAV TaCu'?2GαwL"N3'G-Sxh7nlIhF#/(X&;p敖-~Půc*pGq[WK"&8Gk5m-u{&Z\5OzpH=$3@8ӊ9Z N=OgI!%aB-z _m?[i]5Q41wTOkls. @lmHbZf*8n2j%" T@5ZJ`u{ :yj?/Wl&8 z]0Zl-:0uz[ۯǻv-zw_q,*uWG^]N+[-`GX ui[4Iגu [m"m2< a/w!JIvSspPs \D\$:_AՊp=8U«vgPå@s|d9ljG u-Ñnp,/w/43fD8а ~2DZV,GG6pPƏf b"Z4r˿Tp@_Bl,m$3G[,=& 01=2clH?-OC̻[Iv.鷒zE1tuT.!tQr }w',&۞-s4"64 xw:i-FKv8jt-l*k3N8|(N'.-8.jEzm&qQ)|BO1Әz-ѴImk L[YolekJ4:gYT^m-os0H>I}Ziu|*]~쥕m-iXk_I~ߺtymp׵ػ$#N0(Z " (6]Au,\HuӋ8cТp3;[`ャ$͠Ama "p-cp4[w zXeZWpQ7;cíҬ[T- Ϣ-jV󊸘b y|Eq{W h#>v`3?+qO--&# ;‘i y4;[kn jR`S<"\[]p9X:EEhMQja ?>sg IG[]Zm[n|wpT+µjsRڳ{#;Ks?f1:O]ct^vIaV,7J//FtG bX Z6Aɟ{@n}m|-yJ)1vWhd% tdK6XYݍ; |xwU!Ghw }`8iO+Oc8j N[=wܭB cgئҙtMp-Y-\o Igg-N#j/7bΑbHfH?Flg#J{/$mA];_7 ` 65"yWPɹ.=p9qPAN]H@49>hY$E1#xX,+FXGe2#cQL%>ط06tj"y!L%t+UFAčL1nȌo|ϹtCsƆDߢ74_5JV"dN\{"T z2-)IJʠA^92-hp6wM&wevt[<}~Wo)UZ{~Bƿx8OIDdo=^dg7gb^[ 4}o^^OD9JJŁ\SU$) 6gD!tʪ|:{>ܗ2M^7YC tV@Wsjps5 F5h3i|jXqZ8[moEg+G\Tux^2ݻ"]`Ω|!C}+4 /`HUhkuf[.TڪCh۪x̫^5:U[g̻`;)T{Fs{`O؟g5Y@+4P^;n0iGWS!3PeƞEoGb84=ܫ/U4I7V s`8CFT޻RUEGwMץ ΚT5/O^$襗^bkW%q4.&Nׅ{CX+KJ*|z`C;tzy΄cؙWvh9/wVRk_MS5@]XC'v쐼>.+dNˉ-l]$YQF !!Ts0Qx&_jj,5- tI=Bl$&lŸ.2L avW2&t $NO郩czgZTK_<^ b+EeT5~5PoPobXtX_<勜Dhb2w:t2̜QK9xurWOY3fe>&."0xpW@J) ho;Rapg&O0 g#Sy;$(죪<DZ#d-Na=NpS-LesdLÎߔ+xSX Ŝ,"qo1Ʉl!,Xԓ!ސ+}t]9/}&UΠ+A;kÖ\yXʐA^6t仒w k:X-[7/P/sXkK|/&(2+DYsقf `61NYuM~M`fȢi`V6'EU+ ۊiGD_)ϵY+ 00.nPC1zšېY~\ch!Avl&dދʥJ=`IiT {00c.UujT{=tފ!=uE+/y_'.κz;!ُי_wHY?LE%# Pm /7H&dxA_k_M By WdP MΛ[-=r]ʽ {ў96Vٜ` ;Gpv6 3qmx$E: {o6~PG_0m>gCG:CC8vyMU@= Mݣâ]@o6 TN ;x`58[ Yu"B.z^ߋLطg|*???C6b/E 4FYph <zd5Ok J"XwylS Ye"2~H΄wTIUߙTډ"SwWW5J{o ,6X#(0mvQ4CH7\Ggjj Ui2^b}Ճ.MvY˒*-D\W4+r3 w`X(^#텆J.YvJ].Xƫ@i_{'+u^C. Nfl˿Ǔ 29l;v@]I]x( t† بhxιz]On\5p4 x&NBt"MQ(/@Ew@J`0%{{2G8h`vt$aps"S,,0pQN[k.^'Cޮ&=>+ugEȹik{//v{[fأ6ӏW^kI=c(G<\lY!$2y/E}utK Խ8oxLL{+:\`Q._+i/-xƓB3Q3@Z@us9pm[(/zxofkl+j` x,%lt%NOd '~SG~eRZ6;hf*1Nkj3ufgY.AO${4&8Y lބ_I'[ă I?n*?5_kf_ õ8o,s?'ϳm'RhFp\}Tn7H٦ȆӶзc|^q,-ZPlk$VʝlXdo#LŴ1x>l,sxew!)"PVx"&,sE%R{etc4Y p:8 9g%|,s/] ̉8F!ˆ&@qTv *x xƊQl,+ROlqkӋw"#oʊωRr50[Shog=X6,#,$Z[EI-S*TdSE]Xѹ0B7Lc\rWa^kl%Ĩa'fTт}p'٦y7V(ԑfw%Vl/beS^&uEG-]ڎ`$œ5L0|CѴRcءmKۛ4KeL⽶TXu+o5ѷi?;شԀ.r?U<{;@F3Z;~xlO#DBr}Ѵi2oH|>=]%iSJÙMgM)2d=MNc{I3$ӕ|8bm<1g omŸlZJ<_ `p( /(d| K]\d PȾm #Dzԁ/C ;ϖ3Ip=O=5p`将H.Xp[he8;,7 GGI]*SD].XWڽ`nz?^#hT#f/?5Wfv%V?Y*^MT5P5* Wc`P&N ?PKוH܊e(^<foIl^4CW7G\6if_+|J}`qWJ(U ci";l81>xn C~Rl_RPm[uueuv3rn1M#ž=cjeu=g' %v_v|*mi._ZbD=g^i+= r)2Y]Vy~B'ѝ&fTm\GTLfї'5tCEaŤ7&~ ݦ2n¿wl*&lo_w8BϝL! `-xdxǓ)zʃL1>+b7=]eW5p, 4 do!k?fl:jg=^jHw^[MwMs _`huiVtMލ3A3ic}p)#3 hJ, U=>vesB8Ad))"imi&rxܦ|tiӒ nD ԞY>Krn+ @g;+H™ dЀM}|5 h;me[X,mʿiE}hPq07? " ǬEA爰/Zم Eip]}n>[׉{4 9DYtwmSQ'ɯi oé3&e| F)q!?5mNʿxim~5 t<6? -/xh9zxEy{V1\K#BEEXUv3nۊ~W :+"Ӽp"2bZ.2fb4`8]E{d؛/}O7gQ1AqvP6|ѠgćTGeڡH 5Ȏn='1%~L95/7 k&w^$R۟{>uFIqx82ߔ> DŒ*g&B@IhodW! 6Xt&;dŀWqhzM]5iˡRU r e _68M}P:L,k@g|cɾ"O׆\ZQz1_gAt۝H0o,㥺ӀKoH(m,$DXijx' DV鷺!^h,?}=SEW0`ix+x˝ ^3fZ9U T 좁!}Ư2%cŵW`*CYFn( `Q%`b#cl7L;`bxM l<]3(GٖE9GvQ%U}Xo'sl;HO^]dA V'.n ؄?9>YK.xXFg\PǛ"p:TW5P5pd #+Բ@#Lgm}z0w%Y wZOaLתY^ͷvE;U} ]b6_8*gk>qUӂxIYKޕ y[sŔͥ;]/SC vBS[ؿ"q>A~i=1:=XY>SGy[ LHu*kQ.&G=\G|̐[QB-5ԫ|T[>;v]vp#Á3xx3 B#gx|dP8`'s2`#4 _ޜܶ϶GBy#, 45Ti| +jv|¾aC ΰž' g/f"#=p4M_j u2?x61Ȍl9==n:j fodVvE(j<2l}e cUmVoƪ;vavFU /4g]hgZ5E3(8 ,&qRP]֟|FJl f#O*:n b+8&p%ܮg {&.8;.pnpd%u7k 6,q< 'T C(8v0O%@ ZU VkžMN!k^ +]:f{u,R2J3-xW7wCq/tW`ZI5D:|Bw k h^^1̆y<+Vw^.*QeJ$*aHi^ IK;L[5p0 Txn6W^`~6O]].?mǂ1Dϭfwa3ٿύҀQGĮ0],Ȭ]Zʯjj`Z_niJkX.TN+0QΎ/4 {4lY(G9n'Kɜb}oFt1m D7tG[eR{j ـ?}ͭD0|c%oϪS]k{Ϫaz5B;SZ>*C .$yՖcۄoUR`5P@(9ʧJ) =|BRmsXg^G/Z%*W ;pZD!wMw6#郻kwRG++ʫ $ [iEZ#,0lIe"B`GF.\|g;+(#cv]O&}Q#4)R>x#= &3Xp%]$ۆ̯LIѵc z=x1G  Z`|b*fS“"Ljac Q, |IϋS4.KrɜowuX}8 Q+o2-;]/EuRj:|?*MhOC=\/7e@X4c&B|pn:Xf?./:GE؉ͻ>1.ri& ] v׼裌Um{$V߿ 7LvτbtO0k<)/6Ig  > CJ[yU \e{SjV`` LpF"-`uJ/4V=ԕ=aoa;-C@Vߙ?j>k+Nbp& O O-0W,y;,y&#sAi|S?U4[i#,#Ʌ^#M 更ҪT?8z_*g~4O/dE9mK^5^5P5p8 0B5&cix"0՛cهfzc]!|w0np Zm!@ 6z|_OoU ? >EzVY`w~p&}ZEp8hBTI@︊)]C>D飩R)nAPK^4s/ls1|^ ā` RЙx`/Sj|=ck.['i;ZSt `ZgV<ﴲ,0LyX{|-br$|˝<aˀNMM`ד@Gkj! gc&B{ӏ(_]#/tր Hp6dࡽmX+K%(J>FYi9K *6\.7d-+hzR ;Զ_Q71\X+iw(N lcdWv|&e+lBŇ\G TL9 Ҁ3yP}WfU{h.존sN_x?/?\2k^|rT_4'(QN SƤT7\IKlw ]66)HQ3Ft%Ґ3Rgqcv1ɭvEߍgM +@۱`9,|Ĕ(a(ͪ4TDs4 tщƾi"Pj8Ö`MCP|]>=_&zwƏgaJNgAky]~[a~rL[t0W6p1.eJ3K@ j?Zxח[1,>Lk7/:oߪOEde^mCIavv5–Ee+&X=w!6tǀw9`+aHi?ȷAa![oK5 x0{kS 8`+LF@IDAT6-48oj&isvD/P 6,Y(Jf 2^pJUX$@ka,\NQFWƉ0)]J \ڂ}/1}GcugCmZtCLW`_>}pimFboWhn]bh3n'";q]hW,B \2^uѧT6Y$[ѕ, oJNb. pZ OBam>%t?Yub:(c:prM[=|OhS@vZ_iD3'{s+~+1]7E2yрA a_u0c/0⅍'@>/Nk)m¯M\U@]8zOXyGxzr߿L?/_i qPWmYѱ΋9| M w}Bޘ'r1fiX|Ľ s*&NϋmjI|__@`_E|" 0#3 Ʉ\b6]1\hILfr3#dUZ+p>gc3sΐ1@;}TþJ~Q'g޶N7po`}j?2y&7Zbp3Q8vgt>`Zul2rXoUwirE{{,0ݴ~+o=](UBT| $ <x4UZտ i哗Î?m`a 㮴20V.}(ؘB #IlŒg ʷj`fCXGO_u_0zr|5Qr=j8kևxEF`ڌ[D_r2܅wǯ8L@ʴԮ+-Gf[;]țQ|,/L9YDI(PKj)Gg 8FZ8;'XR;^hFw@>b)qJb]j68a彉Ckҧ\wpwT  wRc`@|Xr1o_g:*Pxy_  >TJ3cy3pW4y;qVYl`J :p  O(H 3 3Zȏ>f0IH{Lk^u ;͒UNM]Om'Xv*;-g|LoeZ(>^CN_^8F >ڡ|@]JMC>%/tC^ݐhDEۧ͂p (8e. ƞk^h8ğ ͠5(i ;.# O$o0:;vW4U#Ž%D#,]*킡>eilYR|$oمgyc_ XoA Qyр4h-Pi(X]@q4P3E_Oo̬(1#Ʌ%P?p7sc}sREpk=j{Ku.-am JFɺ$+- eGwy;m4 T-(H %߇ mjå7ztsup__Fu#טzey0m Ф?v"\9&Ճg4 >yr:y8c@L)#?Moԓ0%~F81#5eA3R+JWeFNk^@o6 6R`fRL c0̕7:ke 7=w2;y= `O3?Nܒ>xငmA`mT T AuJ>,tX'7f4> \@lۧ]͉$/tٶ6cv}p$E?-D2 38>A֞7Sw`IxVҍ~`!B[(zpw}>6,T'% 0eLD(ܖK3>p_A&= fHCN;wgLH-@ 0_MҢ؀nzH\Ԣ.pm;Ə}G@`7e3M_>'Lvo)g_[>Gp_Q% 2B&R~]X! /o֏`+d݉YKMR8F-'Dd̴;,ݤ[ 8C]/F'o饛tݒiQ)fhfQOO׶ɿ|l44,?Ce҂1 ϣNba1iK^l@d˺)Ms:e ݞ#5!Ԁ-2]b4P.)h2VCAyօX}x>M(!]o C8d]Cj9qvp]SH=kv0$ ϐy L) [8ڇ 2̗c8hk ɷQ8*ΓU% (;]/Se{ġ.ejz k/%nSb&L{d\񱻼繒M~s}';dG3a,*aF2t|3!|Ut7c:Zսy&<8a`-H&9zɻGsuXlڔ2눃{_ֵ.\25WKh;e)eUQҗs_ 0G0l2p[?`ic|b-㋊f!bѐӝsT٫Yuk^f.w|W!5 )77Oݺg?Knx,jĂO&:\ 2xː&Z.i STPJȔQM@Ep?$HU"ݩ H-Ip/Oe%]o.v`:J4&x'@Z(l83Zȹ?_{ 3YnS5P5pL cj ;-7#ttt=Mzba}HsVa/| &Qt8;0QCˎn@?qShlBPPG v dƤ1f} Nmۈ+FP&%~gI^/P6 p)p C BGz"y^Gpx=jzKu.-am JE6JZ^ ۼK6,Lv{O8!v* N?[uBUW5P5F oSVb<|N|׬7CVi*JOE{,s{[0z^c̄rW\I(CɁD4lW3~h#z(#ɯ 92 Ub5|"eT)(Jie> /[[۵M9oOGԒٞǻyv:{anku*SiC8B?qHL_e-l?ʴ?0uh!<v@]Ye?'j&c-ٺXu.S9u5achLVvBS>0^՝wCLÑai0B:{ۓ\g!x '}c`T? Ձ/\ylk2 P}>8W:;h|D(s#0񢽁5`E ,p:Zb`G|LtP+L $1YL=%LvdLÎ7!1uRyoV1R3ד=)bN@P6 LYzRURK x/RCiMM`(Pԋ`򉐥u5"̫+ Xa s~G9螶S cp=EQ BGw~x.uaJtGe9FtL] lo%ű4&`Wɲ rN?O>LFݦy 50ђN06Yϣ{Q=O'2TЙ^^)DxMl^âWu`_ͽt:v1i(~Ө2iR)L1 LKI%1?5n0^sFvؖ;N3 2wEMezx5{ d>k2+ˍ.j1Y{N*6EY1Des}?@RWK>x?׈9wpD|Eߕcq`VATkUK܈q9nK:TvSg[5p\lZvwL0'˼"]rXy"E <,4'ΤY EM0e5P5P5pT 2q9&+eMfУWN^Z 9_6C46WǚRt%lHJ38^koNjv'LM`ӮcYض &P)~IF"?"u-Pk9Vb&]?۰@}Ʒ@TiUjxBk7j0,r.ŀ[)N RȁIGX biC`o1/Er^U T s,&W]uz|7ҞWZwF2tQUČ*QEzCcۅ.gYR `<ܚrEd jdzU)]gM>4P~J%MRC1><^).Y&SŮD$a s?o@[j.O:':ml@3;;حM}kVz⤽7␾R3c`xݘC \) 8 =>`݈v^^4!qK@簆([WD{Ɍ#ɏsA]ۜj4=t`Z4@)Oߏ*$*$^IGDěoq>_ç>`'KY)ptQl!6ce?L"wJ鸌-ȹ)u5ds԰n#Jo+L {ΚLWq~:;͙]euća\+QE *kߞU%邁Ǵ5|#Ȥuq Poif~>EE{Ѿ!5$2%hjj$4PNNO1cu^\?[RD+F^ǰ潇ۧ P?^Xqg { ov:4}!, qčn|zoDaw7?!GĉM/N[SAddzKi&>5W)aOEbOb7pP:o >xAv3ꌺ2uqlӟ ,\de6p%ecCh7:JEhݽOE-}h ^P>p G BiO(bU T MTW5~ YT7Vȱ:Lԝs2BxOս݄;;R"ytfWJ-wv /ĝo:OGV< A@ #`;<0ӾÏ7PW1xR`+"sk]k|Ck-!vA&B$%S䎃N&FQzM,|g}c8 frnw&wMmUp8m\?q>ku:^^ -yoEsŃ) C&,3ILѳl7 L}y\؍ϢahBʣjj` !x|Ry77SJuX+L&A_5l&1%Yik+:0qZ GȴL ųSM٣ϵ?om@A/Z߅a7cwko>[mNFy с-0hp"d%(+>i@&[9$Sk7 69UT_f  ;N/d"".lF> ?'X&oeo?h@+1͜;V kQ'Z+tk]|TLO|7вb;uWM u&Omp8xl|21hal?dA{1F7%LW1 .h P0+X] KC]0S.tQX/Xtw|.jU*uGXd45|߾ q&<6 u:m E&,fd>ބCGWV L@]TgI`>Ku>muy.Eu(C.t`3 BM^plY .?ZVjޟ&ٗHXK"<wr. Ԅ S:)n~]qʸU25PV[PIIARAUֵ.\aN 8`oeY HRnGcv#2xAW]@ij.fT?h T{ɧ#*.>/$ @m*!7jY$Ҽ7Tc|0Ķ A`:VIe"%J@yZ=GJw9N%{I_X+I5[%ŭF.M}͡ 4}N~x};Me0{/SdX4 ȧu,3w}uTp0uaL +E@0e&TeT5P5^jDv1af78F7~9 q F,jT :S| Z+?. dj2S,#M>H3B99SbQ(PdB#6c^\JaEɆ˺FЮvbVRi U*ii չdC%xx|,#^Ћ^C D*@Y?KͰj`[ m5uAt|arXOU/?g/c]w.Q7OAgke\<OSTjy!dS9[WѰΨw!|k1  P~u_g&[؝ҀOK*͛kVK(LXh2ESg03U|޿{mB fbΠxVSmHR<8) &T7-`deluAl|(2ذ-je,L%wT_̀tg]1Mw͵:AY\O.3LmdZm_OO|{';imL^Py~ .FATY7p1"Qh+B wϮ%K [ F}X6E.!LȆ<HǦ4shh[UMHRɀz# FfM@<M$\Alw l6[ ЛV ǨY 9[p|}"ÛtM1mUvTZ6d1V[ùecC(}AAS-3n>18w^ rK|m tٞ*رxxj35i7sᓺuIooe&p](1Iw ii{L?>iGMy)> il(قwªPu Y35z}xγT=&m' a5`zN*G`Y cWUKz$Y}tfT ICDe/Eb .y*Rٛ7aa)b00\7 Tґ:P sI# sz|~J]#M W 88>c>wفЉtXc84 gMѶ ɧ},r<v@]M_C/F3};n[wfj{Y{|df!NNskTr MtEά2/نO yͣ࠮P_M07E͂@ 4Pҗ4oҖFm-~A/t%_,bpWLqկ(5PھJ mGof7e@?u2 cSY1"e?L'"~ }ϰ8!z82^ل/W㔭|5]2TؠXצJ\Ti`]{uޝR^Qۺnl&;l|'98'a Hl/NW6&'z8A >e~5-_tzKN /F3N#"9Z4"`Y\x[ P،^^1(,lz  Oq^1I$.ܜYa,6"xH6a*J(Q,6SÝ:û`Հ.tcVڣp5zd(I GNgm^h>w:8ɿ9gAS4 ;?"NQuk eo~?xvk?tBNO_S|Ced0I:0R @,w g j}Y\NGߜC$g@LF20B < Tz x8eDc$(lydX3v.Qʉ 橰v&%FaGz"KU̹`Xd>qoҝZM ÕG`x8 </}{rP1\\((<S{=v6S-!1#P_c2vshҡq 4<^2(?,3 -o,2 NKuͥկ~͞tTsN^}:k}u] .3qNHys,a4C7WQ@&#5@}{Cj:x |B2w ^>O\)蝶)pJ0 >贠z}GϺ \u>mdWLӐ褔tTeѳrӺ\2Qʌ˾z7ޤ:ҪT6fOfke\3KJXd@l~~~at'{[ ׭nST)!g=6unZ~ :lA 05e(\ML8 4RF_ꯟ~鏋Qǿ7lQ4~2+M29Fz)o p$x-7׶z]} LB\g[6N A M4LESL13S:-ڌ2H+3G'3_(pR|HewR&dC L$9{AiC-}; ; Ul"t1kH"l-x.Ӧ+MEj5UR/I6&L *A)trؐ}.By;ܳAv vS@OZb&crM k/*k ë׾hVǁO>GӽlNqn`?^IiZt_yr:d a3&+q=j[3D*9]=50t m"qh}d~5PoU2/5koo?|o:ulu2]y|O~xY۾R}DӸ%F1/X]VKR'} l~xmwJ ."xmדO:w0] 7[?\mc<*NN$Y-Z!1 hD+J h(N$\;Wcs?P=*4Ye;015F%\enhFnH {\>xJuxNt ۵ci6m;*  G*O aOms>ҿ--fQ??O&lɴ]oC.u;fRȧ&i;Z?; Z)?t<0_uUUpzu/ףh/_^fO4Ξ=_/~y6djufL+G_{CT(ZŰ2-6vࠠg-4)p7B'r0 l JFIVg-4y6G[ 2bֶte|9EJ-!EHrDZȓx\ZsX g)NGAF]0jU/b>x᪁]4@T6l%W1h/TFwcZ 1xoc % LER|ə 3KP U T  VEy; ,W?O?oW?Oy>ɳ^ ~ڟRs:#f\%fE0Nf.p% vŚ_Mh"^XKws/] %L_5JvP~TG@lsee\G[^RH_.1MgqF摇<9o9zcQ@X0x^$p8HyU T er?]O'׭m]V QFw EJ2f"2Yv?w㈃{wRI1,i8P^?XZ~Ѫt +Lst$|yŰ_D1S~A㈻sx-I ̮oZdEPDtuQфֵ.\, ~d钭w'8쥽oS3^jv0㣰 |^Nm e.K0v ̧||~ cjfVW5P5p 'Y-+͟F_T<xB57fCL0gX̄ W9 -{3 gC ۷'AQU+00!Op20B$R`_X:i8%7Ȯ[E,".Xo&>FtnKsvXwBF+ NaRIʁϟG{93Q8cy$ŠwnzI? F/"d o rFئ<< )jUh.v0kLڸɿ&Cgc,98(GkE"”] ; i ΃f,sޓ*)︷ }Nqa|r͵ns 3"[/룹JEDN# lrPSPeDg;bqb%UnPl ʥ}:)o8F:r@[>xd}32uޮ*]H/8;.=7B,cWIOCOS)`%e&/Å{8)wd * Lm!(&|Zs4B|n"np~G2 t֓'u|8Ea?Эe<.]u[W=2 7*@sf Y 3TL2p$)'v;5Vɘ0DŽ:awڵ=:H$uC? *ਉA2`y̝"gZxB d^q Ձ+-y j.8pLL [p-Gq8i<~~.1ݔ̏NOc66$=jkG]e$g/IodouWLw=Gn'#N s:Ҳ`gI;PA]vr AϺ"I 9C(81 Ī✲>hx-w2HDdzwͿMJwǶ4<*+Ѿg[ړwkӶpnlƲ3y>);Ӆ9ܮ[|F %V )ª2'z0U0 u]BgDh<0S# y*? =ƷWzhrPĹVXѝۂ8?Fj N jUPpkLJo![pEصyeXdԼyL@k=-9Q/8o`a~j'zJX4o/{ic4!턻'wc='=e#p/ N2=OyE3.=5ەÏ2r?㎀`wL yWoNR?9(,xm]%eV05!b& FtǏwR<eEb/A)}'?*" w>a9HoSm}̑rJyEleDP-Xnv| РgdPj*m&!^IܚIQ` ZAKT9-#UgKr(\P8(S8IGEx9tCcWYe:wvMqb؋e_hWEr]3pIK<@IDAT]ylCH?< /Y1icM_nyB%>5SD`Y?h^< E;=Goc ے^#oM[5P5̟x߯kdMlڡ4 MϨjUQfI? s8kv6'n|"'GOՄ":[7('}y`Og'0#O#Yh%arRN{=ai,77' \\]. u"݆b9=E؅J\}45\5homs 7*ng(lNMkkͳ7L4xdpγaZivЀxaXyZ)m_%!ʪ~ɺ?Mt+bob'ČPVv [Ds78`eދ::$#Esʖ0y )L|nf0(cpQ>w%\ q"gM#u w1ɧJ`:/H*).IĚ͙jl/}8;ӢMlt>mL6gU` r,7xqEi^&Pe\5P50* 7h1b 0H̀@m+h4Yzx hT.}ԲUuc2j{g.l* !5N;p{h#z pW-ÆM&]i,m@`Q 8ܡ2(Dܹ1ރO)C٥\'ꤤ5F@K V5J S{-go`7K\ѻ $!]:Ig8i| ,؛g3\ )`]5P50`a\,D)r.6슩 ܹ b.>xL[UCj?i^0n8&\88[{^|? Ϋle챗1ģɿ|?dZw~WaCjP|:W s[b&OL&]0VZ ~aX%՛$Xqlo]41GcM&h_+S3ڏ5/UjQ]\Uxxi62U/tfj@cMƙD646 &'\0=aH"1Y#MomNP ;8кz%xS%F7Np):\I{Vq/XVrZu$@bxIB~` wfNWrt{&_4v+fY]#ж5FQa&, i!@'@a:`T]@i.]?Wg*`G[*($i/;1 7FW$S8FXc7,sW7lH}ߴjѭCv=Q 96Ժ; j8Vo9tPMl,Nt68sGC~5\bA+ldLRZ[;;YY?6$fpC%EjhmLKtβʷޞp?W9+q+;-C!muiohG|s=iW}Bx`j{kXUgٝUȷ?K>(]=n9~u0(hC$&x[` d ;p9`a[8YvXo,ܦ|teR`RɽUAtq`q/3{e‚鮫bo )*3O4yxҕGL&srgn&T}>I(K0V@i4-ף 6vNI3²u|؁ɏIcǁH M+G2?8x56]&t2ˢy?$&?hn#zJ)lt&TAY"TIFDrTm@,(M>M1L?. 9HՕ L <&v֮Xpv\w&oi( &+є&eݕ>(J-R.Zf<T=ɶK}:ҽ3n㱹Sna']g'{BEzM@*@> `c%l-xdshvq>A ?j,Kv ~,x2qoioQݭ#@ث%km3N<}Xt4@R](۾moG/dxIq`vV&8ft-@MN!w}R߲L^X[_jͼoth#[ U]dbA4#lqіn|HGv!Dt= ].㫲P hΏo`pGSh8#H9\<'oRɲxn})>u95IN$hcr4umcG2mNW|/Χb&.p]?"~~*l";`wZ 4UE&S2Eo0K;dv g*ij⪁ck.[(?ůΟSj%aHx4$OM>{5 &:-yu 45(C6=kE@c;9 jؑAHj"LrhMD'e4N45q1ÌwE]xlܹMkNF9uwѾ+*pf#/">x᪁5cF?d ^``@B\Rwx0ɷoe%2 8~HN*H%!%?!eUu\k֧9O5l >akn! 05-r.FD.&ȅK膕z_9^{7upQ臉T?zA I 䲫5b08/],lm "hJhrox{ fNS TܣβV"eeW W#Ar 0S_&vw'XXOυ k0STW5P5p> SW'#zdxx)znd5&T ;Pksޖ ÌBLM6F$p2E? A\fa?-@}/c4ʑFN3dɂ=Mm81+̕Hl@0д Z_ "l 8`է5A(k7Х ͂ J ]]$(-žWu(qe|kK҅]QNMHacd/} o /|Clc'Ǿ2C<6f ^]@Yj.eП57J74g@Yx'ЍBEeA,VoMa,P ɸ7s =ku_P`: cz*擰{z͠Z`3ЈSHS >IlgAJjL *58S +dH3\X"X%@٦!K!~pU+8:]U\.tHo@OrWql7ZݶIp.8iYV6>9[/ܓO_#5 1b䟱Ejssh֝Ka76+Ma5Pw7@G:?t7Q`pۃ=|Xp'{ ϧ`}}'GLOf߳h?ڹ'd_\·s{#c7#;JF hRpR(KW ~z+ V|[MJ[sS>%ߛ).|k:Xo ?+Q?wxa)»eXSfpS29@Ղ vUh C4|b"{&SBu=  4#g`LjCzKص_QY/VnmB1GhG~>Ħ\k%K^ico7_L8&d'ei'--uZ] ,m^gZx]gA|ZG|}zW 72qҝv| #v{xa2 [U,'m5M,_3M!M^4fYe},3 yѪz 8 >?T0nGmRz I&g' ?rW]hOy3/B M%4a]o,3ݭ_nDDȌWnL']hOgjA_z˩G) o׃_wViGϓo/_P9W|Bt`(ểpmH L~x[6~2MM-doH=uhrp s7r0} -ܸ[F*R%NN }Wcf%=+D- 7^RY!POsN@LrWj)FZA0lp&JZ-l鈤:e;N`eZq/"OOeI;_~tSzt7 ~dž$OHitwe8 i @W-sX:f]} '5#f_n}a(T)^xU0 mRqqwbٿ[Tr( ~y@-4i{@OgC^l 7~=vp,..GUx1<.ΆӟgcĔB1aL~)S$ݚ}K W>`6b2.sIxΛpd{?Ra̔5lhp%09 uI q"(\!K1f4ciH m]uݖXSS%jM&TmQtp=Z d W2.xvO:S6'WxTGwzWkrl'<`Œ@0|/ y 9c,\Yy-wnNS)+/2osMWovO 29 w;- R) Wp~\I ] [pSn_~unO9CX_qП>t+Пf)TCccg*nVƞޝ$PEHOO TD惻2O1۷KPNM@n7)3:tL޾UHߥtA/{=8sqɔVUlwkscœPROƘ #$DXewo}-FVCq8~R* >G_ Uߋϑ %uzCzS_>qW)sK ]2_f&߹9{eR%]q;oPq,3q+nsvxJO6^.jp7>odv&s9{CLٗP/F)BҢf0VY%Ed>D̄ >k]ʲ`Sxcf+MTXC Z5=X]XeګcS)&]~Y,EHX].X H4 "?٭|qL=\w}u aJ'l[4xA/.@!Jݮ @/ wEjijXnzS@xʵq':?KP ܓ=0F8DM Հ"-~rh0hna&즂Ue80#ڙu;K!2m{TPV%&K?)E 5iJ#F'1X&=gI,OX=c(-@o)fʃմۭP;PY}^s2sL0EHǗki`hڪF4wWf3<9(T/XU.U܁[\pAp<[B.&hSKԣp]KCgٶ|2jgĝ .:H(?q3I@Ou|Ϟj+hWȰD1+ᬻ'PYʀٹ% Tĵ,e.јܗDT "g HQ\)w/jPMB~&VfHYYB2;C3X<ƑŔJ|3:>Kxn3|%|+v kiІɩ0b&5;BJϳ~-uDJ^BƳX5+p7M?~Y b__\t|<3[g6713޺]g~KI+b&Lk6f'sV]ɴ  -y JU[.%ʤ9(nBfve+v%[sc'Hg0n8EA ݴϺ)i7U$|[ۤF"pUnF&ogS=7Өq knde6>9 h)̸Nw reр{yܯ@ǸmyeF !4[p_2+Fbg*NP\•H,bxSrWX,䏇~1藵.іvDM%4~ObmV >n[¼uUoaNW'm8 w' =3)b(7=Js1X:P6r0Qv@VBȤ``a1|"k&.X厃I؉Yj o,'QP 8+>F0LCE\ Su^fZ 3QBm%M)o1 Uv/ xujMy @iS{VY̺V V$IVdӻ{ l4rI80 C8#f_l?9fLU%7\cBdU -P;԰]2>XO'L^+繢 `{u,ZƕSLr4(T-R LzVq+sc}%6㖀 H`S$&$5^m밷rwrF` r-V)>@5ukw]rwץ]JgAߟ|z?*KݾD Q 3>b,ular:(c*" ׏EۄÖ.o@^n+ӿ,=VxT G%>E ENGǁ,Nv?aazs_ HUP0@UDh3cP)62.y,l*SMbg`%@|扃+ ږ-A[}!^KE,cˬ-kԁPT:oGӤu˯ iRJ:گW|\?jОPR.jĘm|٦=(;4 *} ip|ӛ=^z˩'Udp| t9=#PȒËj /dik auA`]g\|?EVPoi建m:L4ex%I#!FW]%ѱl>s#ș,ȺѯcUǽ{Z:.Xpn[Rs]=;QKX^]$IMOl}y{GH¼o}ޜ~|B'Pk>ʔ}gH^g=/# OJRgaB<=^&zˬ׏Z*p6`op03*|ebҏgE%}0Q!x<<%SD24!ҫ7O!9m*&r>#S g}ΰs_s"0TD:TVQXt819kx`R"cntHغ&h.q8^oǕO`genVi]6X>Âq_e]͘{=2]&؆^ױo=l)B<}nUs|c4+u'-ObeduL+*6X8h}sm(ѽCt{¿ "CV,Zkv;#G)S)PWzP==t6N߃Q.!|VuuL'2$^Dⴝ9- U$'q51Y@6xѦ x\mϪY"ca7T OLh7W$\M~Eq(_f&G_8xE4l7=nh2 qW)ڍn@m Ra\:>c]*UF8*[ލm1  Po޷MFN'tޡ>n3^p#~n p_`~G7=z 0 V8 e#/aDQ ev%廈f#V ]1C&frjNW !׀QsYL*:ǯ"}7ŻS]ְHྒ,WgeS PRoÏGdv˲Ȥ샛x#y4:9,Q* @ý3'.3EFr}g q8p!7P/q!TƬq;Sְ6quV{aǏ2A0{Gjm7?i4̀=4b=mk//o~(Uq2[\IM#kzUo|& WU0#F2 DoʤLyn9PB|(]a3hpFD&A_X&,kUWQo]ԭѹɿ}WO?_8!W[~B[~rimuʺMŷ'(/Q+;?>a/y'_menNA7י*V[캨F-հXKȪkW=ycW wӕN{/?R^ANgN2;= o#dGCUαi:'a|0 .wԋUx{֟%zP@wHST Q +t/O9 ̅fToj>64 ޿`ZkxzzB=j:!h!HOd&j 0*XH{SrW L_lqI6ȅ m/>3瓨7jeKeVɒT[86i?l˞go=^ҥ0guLWtѻm`؇(\Wg?T\# ƢT}C;Ai61>2XeޟgWwᅧwC?'dXdIa{@OG^c\(=N@gq,dKN(ŔM@/prpX4 w.XWJ#~PX d]&68ta@_qi!Oȳpɬ̜ L7BoE&ϫy). ~ku sNv-1FI' D' J &I$J| 8xВWgaZ(B9>1=0x"UqL?WrЬ͇5~ k=ok[$))@ Z_[Q`*1 kbSe,ba@Hp)Oa HBR&^KrE*k|J NTdFg7`D9]yଊ0xxyΩO:QJ@&dP# gXM.0%`G  aL/2 2Z$2gg؇2ULTΦ8L{]H4 *?*(mnL{j3Pbogrbi0%HW,7D $q  IVgM&2iW¶DƃP lC]~] {(]1Nw'wyΎ; vpӮH6pϰ5I<86O{ҿO+\t\/?FyxzON5F9x7dX GwL:ʍ`xf3^ h!mx3n='.FXnI8 Bp>M uIS Fŗ,#59br $e׵*GvuZ_B [O~~nKXJĬNXB]_^y,eѰ,~*g įN &xwxL ecMe7 3"՗Gs؎ j33`Y?c?Wu1J;pW NqPÈ5Pn >/V)f*Ghwvq2IJ3>Gc;`yʀ!']5,sm:sgwa8;)=Cؚ__^Guu!p=4Y )|8NL#VLc>ccP< EOT!0׾Lh8_7_)+_S*;ϰ<֝#),)+e=]n_M}*.۝ p8p4g|?TE3!Ŝ˥ۑA: 8Fl>]r~̔' Yx1 A_ O;qx_ Pì} dz>x}=21 0Z@g5)ý㚃(ZJwW@Iy}+n|9%jj\؂m(H{}GSpbl UUQy3\"yTp)P,A-eQ~<[R^@)dz7x?"~jH⡳~_!3B*r~0稼vOrTۖ]뻴_GƓP{W$3; I:]tI)S%Q@7=Fv/'?bq:-&w錋Fg/""Y,. i[v1ڍaz;Ir[(MooG޵W^],n/iYg7qx$=IiT{9:.d^ ꩌPWUsU aAGf!"g5Ιy 6p\O2M8odF*-SUƠimZBI&1qA(&C, *O k!7;.J'Y$% kgpBeZXXٔo5*1"d,: ʘ&?Cm)TQw߶n˅)myiKBd$7L7c K+4J:Mב3k,[FK}t`I}=}omjWek+)^~`Cms? 8P04 y඿~T*%c߳(OYJ?sҫ/_Oz@E޹ |`_^_|wSt4:~}<\^|:|;YLfa^ϐɦ_'@As`5LFeDYe(mS |-NAO*` :;røG*^L4f"S#$5swpX`9\ wea b]/,(/!p+?ҥ-I+NTb"Vgȷuf Ʊ(!>(f*BIXV-xu23kkX&xwxL fނq半PC P= iIC%b;? ﹗_e|>?g<kaN5T"0V԰CTz d \l7,XG 2g;xvp~; OY\Xp)*G+X&c1"$7SpAFÕXjl s (I xЯ}ÇEruK*1>q+s?/IzS@+q́esz'̷o;;_]\MĹS͌+$ᨅ'dh-}{W7u &]Fvy2G#}⵩mDPd36 1sq'3_U|+f>9Z ;+XPBxWˀ{m7 v|D L;TNx~]qfqC!ǎ+mAXKMƄ7?Mk$(@IDATЀmy7%Ii_ G*dBPˋ1ow"aX衴*}`pMd.~opr|#oz )+>Ew_ :?M#s@- lrBwP(sLMۓl#:ۄij>,CPLn  |*KMidEAr@E7]pClGIM{jEOGjgJy҄:l4#i旯mNxjL*ä1\>Ѳn 89*WWB H:rۧӮsV߃B y0} .ׯ7=z QJR f:=ͬX1l|B T%,kLhB#H@#QvDutȴׂy×73aFj.VP sŘwfpWLW;kjt0צ,6F J1y$pcbQJ아?< 1zsin3Xx.G? D'} ‘8cMƒU`tXC]ǁ7hM:$] ZwHG6W2Lo( dsGM \(V1c. ?bipW6MCl*O^2n'l&33cc|T'PMOz'VOE&k"FsBLOY>-DX&G3booh*!jVvb*eYi,OS>RWšI'q6#G'\n\%cKOQTSOW99=PŀM~l8j5EG55/f=MV\ކ|CRױ9u̸kfq>7t#lKXv6tCéW\pJˉb;;ֹB xoAzgOOJR)hkr=ΐsT>1Z(ý^Q:`!MNs\6+fCkXD$Sn g*qkwkae4d\y0cD=<,:ZT8~ m-AaH=PJ$/Xj7fl\9 c$'?+|h*KJ%Bjw!Xid m"+x"6)lѾ d8Bn2/Vߕy&<_F]wy_cܾ^ -Mг" 3[vmʳD$Ne54 WҮR7Ưp~p 摏,vϱ'>~dpMAgǷ?33 W= o (钪[T$57Eyݔ*&WCVH:S(+}) ; 2; %neRO1yHP@SuSalW ^dkT63'aӖ$cfoG ּfuY?ȥq&T]2/vXy6YTzAg8@n14N{W uOK += 3H&b Z!yEB0i꣼׸/Y ބ*z׿[|2dj x[3իv^dv5= nuM:ݙa :h +_]|D.pfUŠ"!f.V|MbEWY`zw& m2u]?s+拾+o(`[ݨF^#W1^1F ~I(pbOVFB cP p5fa>;5 j (GLqfMˬd\J)/n9_U#2X45#]mfn )įvp=aF O?I#׶RФш(Q|$MTť 4lkfT% 3\ }u8aVm]OJ.3GhHC8a3>ww2SkBGU{gFKAˬi԰:&xwx, ؤJ_ۿh_9CPVFd$߮ JvG(+rrw>goGo(dXfCFD;!l[ac?߇eضݯM*lf.n2FS{>b9Nc)/tx@Ҿu,Fˠjg:V( !|0]AeAo,ՙ"!G``4KAIPèyjNNgq`ϠERX?c)]F2KU$ѕ&ZV` {L-I9g LPP6X3yLxbF%@&Gg< x⺧D tǕHH)o\j {Z&3ݘPL>M)]eSxM4# mWC60T (42=^hE`|L*4 Pqg࿨rjP~^= dz1Ty O]%7f 0A?+H7wb/M;+{ <0<6 nrL툞 3GŘؚj*X7U$]A>ca}a㶿ݛ=z tRWt>-o~7/{)Ge1vx|Ys0acpsc(#/"5rMg}%BpQCUlpiJw2%Ϋ>M9#fBU K qu)]Hz"xk|W+YkucSϱ=5tqjOݙ::]Ǽ>򋎳^a_vUw8SiT7{-TaVvK}S4/ D.IpFKPQA`7LXhGIzKSwڽ:'Dڽ=ק?m~Aǜ۾"m595#DTI7 JcaWσv=||<ǾnH}BlWV_?4lPO}A?}7X|&7 r>0t%_ߠj!018# _LGs=G"j8de2d>N.8>Ay~_|q^}M'WDv&n'NA~︶ƐnO|1'4o  "|g)S@M^PSw?8~oߍOwOS(? wF;tt|6K.`,"%?#۔M6H، #ͨ>r7 8Bj+IsZp ,=ZmH~5PqoVTsxPQΒUJf=D]ƥijzcxg-52WadT(( ɩyMW=~\9e}7%+bQar?Տ\4_\JYe*nih *RFτKpPV 'ä+`"-Lf.ɢ$c1Hx7" HJܱpB$qP"*n z.JO&`ۿ+uɭ#J4(XEeZԃ^B.|1Y"dƞeڵ7`8?O Fd'\:, Xmu2mh뎻Nl#iA_ Q~. ~g槓9lhc6̘r0~H %w1Ǡǎ/ׇ(nHoNt/'/u;F=/ ebƕL ,HXw$8'#o>+Aln3Ηփ^^8xwq85r1**#+Q*|ber4,a}~&N VDw2 r+ #\Z9.L >.„I>Fϼ6mq &K̴iS*+d 3&i;aӔ9kymVc||ST~>@+ȤSM&X',i~2\ ۂdeUpt͘Τ",^|c( f6G(Yf}Lھi]cӏ>$A^>^dM#}e.=.J!y>zZ^@"7=#Gi8t8*~V!BOl㽄LTI%zqT)_*`w[0}bt͎8k>=zp7-o~?:PzǷxo69afb>u?#p>/FjxYO9 V^/ Au[ -IQ Og$df6b63!Ȋ צm AXAAƙHRfb9L ԓn:AĚRLjVLd-KKJ--w`C &nQLh-~P8ew%={~[I0ӽݛ7 ؖ<P%%=CV w!y.DQRx{զ.ߺ`u޽} uR.g+1yϧ^eDe8sk1.~syv+V^N٫KXƸXcor:s^bn Of֩_$K/L 3Kà8sh=ZK~xF]8m+?-nM1ڞC '\w,Y09~h I#[`Xm t„<6$g֎򭧢/eMk6DZa)K=`)u ɧ"ѻw`^?6zg7D_|\#b>*jA+^yL\ :APʔQ]}&IJNa[ZwdڃXEwa]ouYu%W>)Ӫؖ;Z/[ޕ>Ῥ٧<Ӹ]}i[)(7=W/w.9u b0 H=Ya-# f0 @IOXMi䖉01',e4J*,#I-ݪ5^]Ws||\/7sBU>Ld}۴r$DžWg\^焅?%jyVG7Wv{,wa6qؔ}be9 0ܡ (^!7^e,,?&Sa>BO\]Z* &()\w/ 6 è13:l9߱s/[0ۣ}-F.w֫ Pi`߱G3{6YۭmZWT iu=}ǛJN9dySɽQXK{8z'Uϣ*-K,5eYZMSWy\fM5F xOxoodm/s#׈L F,"+xJg &95{tE0ɌcSh > AvNa! ea;@ qQ>4*x~|VٺEwʰ4qu`qֿK ӽ q߳L締Ԗ%$*`LD/Gj&T2$I8f9G(x:qk0N@MNQUmDC$07XZocE8cTt<93\fO̡(@y |84>M*$i s؇#J7 ;$<\]+0A??qŒJ6W) dS0^mDm~}Ɩ&WZUyv-x8IWW@ƴ kUWx ~u!PFŗWa@ pHp\*iT9DXE^o#CeqQ)> <#H^b=?$`XJĹ;~sӮM6muo }#et_ Cz ܋|*{SiQU-.ƻ—Ӧ 2t#9X0k92/s M67$Y8:"cSƒAB0t 5tY *Lch*P7mw3= F/2&*f=Iq1BN;^V#^3&<~a\Lk:N0"a̒5ud*5K[3sD!Qw8Y~Eek=I+i{ɨamaFAAEOgsU†+r썲mJ%)@EHj&gǎǟ0(.QܱH7}2ߕ*Tnn ~rxPC|/,YLi\ Ĺ[7/NCh{kNx3C~ws[5͊j|;l*\g>A'(Bue*\yc~53RK4 bw>C9QfS H{#3_~kA~jͧNmjqՊm~ xWw]TƷ")T+6[}>Xiq<*T S7nϾ3 )Sf iԇ`xaXej6CTbpWX1:˨, fa6GKmԅ1Lo)PSeW[[%z/AF>f>dGjdwiF)U m-8)K1Ra_ԥ ߏӢ-jkX~zG66@jaPV"'}&K Q"ұb^K=>׭dm`~I"&Q+titcahO9;e2 0̷ uzu̼ u?֙M=y΅-NxfF\ Hۘq(Ɔc-V.8~2 ٠DDZ{]~),)ļGR`ZP#/; 9#w5|8:V(pg+b kz kdXBߗۘuunOf;ۚ)FRl˶%=kB磭v3g] 8/׬}l1)~W]5sAX@;8Ӏz@OB^!> N>/fbg||^WQC&B3F K !Y3]NkR((Ed2IDF'xz8|ƙIèХ~F}mZsiL?"1H9mRl:' WlmtiW Ȩْ ƲP,kʙ`[lѲaV^*X\L˴h^f"`"<; ppGo^Kޯ%7Y*?VC/F OEOA~6c6jT ,3_[<`Lo*B<5DhijUFh42k_,ܭ#\ ;ͷW!_kzye o( /+LVdCtSpBϪvHNZkkO=n@F}Gcb:>[#8eܬ}ƈ}8G ;H0lAіbf)8nG`: @ RnMn860mق8290.c+3S؃oyjC$,q,JtY4F P?y7hdJ90Lpxm.TMcK׌2@a/qK(0Bk73+ţpNݔ+i\e~N\7$L&f ؠ01ǫ04ħ4ű$b!D%ά%*򼙅 m:yf*0Gt)]4 Y[Ѕfp ޴L5PyI[CO,(@)l[GiC/ss7VTeAM.|C 3*kcnU[#mϽR nQ}aG|biӿٴHtM7w@VDL6C}mƽgO)g7=qalF3Dsf;'::_ W2Ƿ0f8z1wwsM}Ȉ>#O`ݭS !w6l=?qݑWwSS2ZXi?!Øص 6APv&/gr)`zp:O,a ៟s:kBO߬%5}ѭI滰1=ߺ/^hdB1"9Tig"k@UEO}fCwYg.WiN'~ʲC T@qe.J:8mBxxu9ݎKm+~E#j5]'(2TZ|6z]cͳ7Ϗx){k_¶]%+Ӿ_Ӟ8cѾm<3\l6mNァ{m ?q*@[r)<{imS@O[S [GxH ~廿KXG&s6s _0&zpK99ϖs4 3|%o8k/&61[u3.梎[W2QBདྷTಖ~ku~~v)c0W$lїqY|;39|~t}=9 ޘzR@١{H8 DD;f?ň"*5ܳʅfTM DM8,.8|]اL[:-gi9E qm7FW♰ھίhfv[+tMۦ\ C3&oex5(@n’|SS9('/w]ACsBÕ 1αO1F÷yv|@42:0XA/w5Jǎ]ڭ.g @WoǦ26g2ULϴ>e3?WǕd6czOիߧk)`@" |jZOPdoz <- |`_^tы/zxxtq:X\ o)54 bV;+!po~?1eܗRT%@_0*q+=p;T4QN.qF=qLgz+Zna(Gʜu:$7PB{P`w.?~q4_;߃) fm,z{hH9Z5Dsunױ;ǾM D]ziVا1|Rʠ(?]_Va~%h~š!+SHL;t˜%PQ o>x4 !r3_:޺`u0^\ΏBZTyۙTxHm[S[4l;]7ř3}Cۿ}C<]h42m٧SK^/0ʋv|u9|t9< ybplyJ'cN`8un.ya,IקnG1$-mj?umӗ'V@4 ͩ/LR63M8(?aVO3 8 e|$sTeVcx/+UTQ^M#XcJjA6H)Iy]muԉu(wԖe][਱S1Œdoy Juxr5cܾc_Y@ wAX(3횞ciyQU m9\9̿m%8b?q6HÑQrw_ ~qр50=|giG }ήˋ%FAv7W(t*W&a p6d<vO\K#dҤ n 9XamG23i hu (5 lfMȈߔ/Lu#)3-ږΔXI үW}n?.A+ias)0ʚ g!~ ǶBBZ%F TeQ V(~ آ۴}6?]*ߊY^g{yoἆ0ϻMb%TU_Q?|ͤ07x9Jo^U0OKm2o'A^IT+$ً=kg^-F81?gXOHۑ.>%B n[@IDAT_x5I:M:' #c.GAѴj$L}]|:e43=g ;8pycT lkY'#8e']i]YO3֊ Z4%ҽ\!"&xR[ Vr%[(9M0ÏGFlQu;Y κ|Yb[Ԛ՜ud[[^BU*Y-AdLKN 2(&m>'+ )jkUEQV3q'HO++mE+`+RepQv1O g\=I89:LTK;% ncs/+U.']xBO2zTnGY>22wr 283tL2b4FT8\82J/:QbݡF-P:YwQ2tΦ_pJ}:?eT3.BOGVpj뉬l9 ~/ ]N(?D)@SN] l`ϸ**8 mg (HY4򥙦ZJV3Ql~.Zv.8' }ּnZC4&E^ Ғ8˙y餿Ņ)dbQ탖E.k !NᎮ WhfˣώQR7h/tzl]q;@ӥ@6$lΘ61XcW.3Vx>n8cv ce56=;ǶY)l˶miy}bccϣ@Ox>kP}h q{w==;pT&10+ґ=6)@x)<^?̘uK;SJXe]\:WpYK@52 ~+LVҪ3n<Xy ɷɒ<:J68dka48~ 2w^Dnj/?ԏ9**>ċ&{=/hW(TN8+jwLks:҉˩ꡈk P aW +<c%@Jpf5H_!+ϻ~]VFՂ*"n\K0"BDzD^W&&x2e&Z'P{ 5E1p|XsGE4]ϞW)pz`sVS3z0Ug'~g6woUBPvGuWUf 5ߧp2@Lnn]pwѠ֐ BOqȡWM]P_n&|f뾴\L>űӳ[Y=&O?@!, 9">At*l}98Gh'hxťY*^N7wio6wY2b]- C1n+vvڥ \~` Uո˛~lWNJk23z(vЏ\.Z j>)(~uhhds ]$ &چ?7"_?Uߙ+=4)JT)@Ok& u:2d8%:wo&^8b9zlSw7ʎʺMMAKbc B&c /R镜oRGs(~[`ЬCUj:]I8P*;EbK]08wg;iѲdd' Br: }U~*0L ѝJٲe65w]idoQ&k(NQ 'ZϮzdP#R!ghHG5X ·sInTGQ~sc=~S> _߮δ *p NwNt:<^t6]ɮ@ oqLy*=P" LH)g_SMw )-t+_׻cP8,תX`#VQ*F8 F,V3.b:rg;A0!eSM*RC8*WR3,eNaTrLT J_aVR]ky:3)l4= Zd}3hd\B\7afӭM-Cqł:XORWuY;VXI~ƸjWǮ4۪]M + ,&C$S4qd ?Nj2\Kʹ@x .؋⛇!8Y[/T?&z5s%Կʯ̇Ua k:Afh"}bnnkSSdg:`?&{*H[}_R 1B) c^YEǾu uXsPlп c7Y.zOn6'Qm&!=ూ=49xKyk"Mc[Gsg)nY 5G}T n뇑`UxV/!3*fh$Z@:ULX%Ŧ.|f ߘH3%;52"Q&lOo.o֍PMcߏU_"Pcg@֛Cxә6W _So<}"nHĊtx}ӕ˟}+ozG9 Oս:t 5tK 0jo@vΙ' e'+\+肾[!/5ȿx1SI M EP˄}θ_1ܮڪC&ν[be ;`2|fy<;M"6eDUG#z+>hLMT'o*0/{ l-h//mQuFXܻiql|kĹu-V䦟^VZFmK3g?wdJ,/ R{Cq`7G #ӕ1mѻZMfk YMۿb;o B7cwx[7 !~k%+#d4p~'::G8v*$g |[-*~ OyE-)ŗ2t A`[f@`6au,G{#w)8I>h>KpQq}4F.ˢ@5U9-j9;c*M?:W[ܚhxj5q/[$<@೉kp0w<\*[,GJkN8y,ї 5GWw/ZZfS+Ķ?m)gڐnkml$Z([)@I75uwu#R 3*Bze)n\W&D 2iNkçcjlpjw0sOCl!\ b8{ f, /$Rz.,e 5= fZGo,/㶕PӀgpt>xF ڜXY*wj Pk|Oϲqۍ.9&nٰ@QޚU>gǧtxo uu0?WqIYW"'}J_@OUA/xi k֝:W%͐Jy~*\#xJ)gxM`ԝ\g]q٨G10= Q†=4gl+%pqpzG1 ޴7_ sk WA|\g7E o>lMJ2O%?ARVEEqڱr:\iW欺_쵠{m5NIS)Y84B\W!\[&xtvn PAg&GEsu5.\eO_smY2۫&\1;y~x_TH>XՅ8`k0K_38pc. %46xᆏĽaV]:֢x}꫿w~˵}@3yVK?^k/;伫\w5S{+W&̾ڹ0Ngu±5;sS8!^CJObrϻ]A=4-L a=KxJW'ɻTfJ8zG9nL (?oF| cʐ馂Rᴣu[!mUbvoT[k`D{Dz0d|7nVb_S$Y{4c0?zJ'tS=~[YL+5Ǜ5LMSNmC, R7)djAW22FTcS7#>Hܥ|];BjWBQkds#pȼd*@^Y$+|uꊯicMH&܎ϸ#.+xN_;ff,])& g;A@m@!3[׽Nq5;6~1\ f=تެ?;`p8 u; X ]ᒐ`pUEՐ"`gAL99;bJ^),(Kڼe~wLYC+ZN2o[ XeqjMm׮aiK#`JwyKv5X4n0{\TޕgamӏI߷~Rs)71޼75pdz*䊦R%hiK榹B —13JM7ihT)f )Z^o*F:tj'-Ux3ۘeoE]c !|0 q&?*{dwZ'tm\id2 \}T:>] {`=3p۞C$i fnuu9w+y}t,.U[2 #"DjXk^fOghnn KZ L|=gpn 6X!ӷV$E^O_$8D&so'qǗ3i 8N͌kdt@e_U_T(ay ޺7OO8h /``+\@-*a_!y2p6`P 9O[4_P]17hLsnnoG{D^bnO3"& 8ny c/ӴT W3/3t& 2Q TQhfAvJ=yM {~b0~fel˴'nKܛӅ Bl>uU`!׽7|wӟ|:}u4_/'^-_A?KX>4owLP/dAO)K[ -5?Rm*܇: M~V|s[h O EJPp 4|o;x1Kz= ?>=nYD GPsUJC)x 3.wXqY)cRq7@73uJ~o9'B;20 ҪuZO<6A,.C]Bv;cGXLc)2Kp~ ;Ge+Gŵ4cˏ(f(Ub|6a9 Eo`X c04>5Q=n&~׿1UhEU'Rr`9)?9 ) T_r>Y\(洣]a_  x9dWЦwL1{[-kXOi{^]j__?zџehvw/?ѢfFr2@@TiޅB5S0*Nd9Vթsn>bK(7*ĝ  m% b&k<;1>V8F3M:shY77p+{ӊQR+O!R&6;ݶ]vIgRnY)5@8m"RHCK$w8v:mz3Mq~ACbf ?B.$f)1B"'T.^Ija*6x=m ܾꥱS"*W$ 5ʁ4:~N6?W451sQ)Q&"v0%v4mwKh6[Uٺpm:{iiJLIxUvUCY>jHae^[y63պ32XiiK₴0Yq~\ c%V< ~t;e5n?  jt n<˂/s@ 9mVXUPt%g[R^_~}/cLgYTGNr;X Xg=7> A;C3 5=էy;Qrv rVI162X8T=2 YH!F %qŁpiio:fITzIbH1JA0| r&E1~[E&eD@!@j-*R Qɂa&fΗW=G Cq a;ܧn06jġd-駿܋QRat/jjbZ⫱y8E3Z QE]g{vcv\dJhI?w @WLq݃ټ"3,UHd{4z%au妚 cCrM{d{8J[0722 [wܔxn+v7d3汇~QyӍTsT,Z>ݭkԗ>&~{[%O`Nl]::3z:AÁ*`]Aɣ;UT(We9DHbAbB5.6 DkV3ģOJ[ ̰riW;sSH4B]f_r`bSW&+C8`JTBjiAi g؜x9Ă FO VTʾ'Eh 1K|8i 4)cx̀<?ǨWcCp[&xNQ?"W?=W"a_W*r+@?;o 2tN>ot A]!ܙ~`5:=V ى W"s<%”p ^(%̌LE20n05I$A65rZKM~kL73<[m׮aiKc$#diA˄xUm x4G,BBzO;M>+¼ϲllӘLe"jĝ'Dgdb8߃[5X9. ! Q{'[~ ߦ_"QT ]ZZ6 i .jK]ūJbYŻ^;7zG|h(Vj:'MQywذWoEQK\qxoW53JslHN3jlAhAfc\my#;:{O&F3{(kle1@ˇmqw= ?V㧂S3jt`-{`M BܭloiaPڞn[x'M;JNoV#L{@{(t| =$δ#9X!~2H @Ưx9)W "zKЯΔ*@Ms־\)컸X% [pr48B,2]/`aݳGѦx'8 1jR-S,zhBh>.k/*ˈCОldNOÞXa`D{O}Î *R4gܷ3P*\֛J4OYQޏz|5~cqЫ*e_g]U?! cJ0M܈B⧣SSt(9-+34~DC p_ja)`HZH Xw|vxhoCJg:|xta\td̫~ Va #}kL?7el}[i)݊Oynrk?" v`! uOU)?RJ^ ) yZdT,kEFg@!C$A= J9uQaND!- =qjQtɗeml+Ibl5/(G-*V*s$R'6/S=]mW`eɯ}UubTWk{_sum< ֠[fJ3c  4rM{m]ӱfrNnTYl S8fp\tn-jz$DxxKńU ", gip8.6OG+un Npku%+MU2V%V/!@cY5)t"B3-*wa@B@{m%3"c4to7~v bDUfcf$KEskOb_«vr7dh,гMʸ#*5bR5X$8n0 2ӶMsWlX*aJ՞Q!4Ӵ7ˢD2Y6pm]VA{!lK) 칚rCjQYxjZ8eNMuQ[ T% @%n85cWRn P%+blkkg^ Bop[RZb%@v3A@7B<{w)h{{l?*%\lM]%ʍ̤0QAN1) TUdr*Kߙu޽Ac@HsIk8<pS`:Tij⊰v0w}_B Tq$F2r1@:j֪)PhPU֛$Dsk|ůzTε_X|4=~hodQy?Z}{ eV:m-P\S>&Z.ˀ)t=~Wrbi|MԚ+a/[};R{uS`(i<+7bqj;x,tW,@ RpS0ĠxY 5At¹9^ɷx-:DSH_+`=6i9 t9(gз]N7dČ}w 4! fеjvt<3w: ^sӡQ-+hS-h;~BTCYA -.17e͢q5 ɭ؛U?|qMU~a\}_Y!4E D'˄|W@UbUWrМ+0uTԺkp nșGxUѴ>MftT%7Nq#VmF{6Kپ[6Ǟ{41 Z= X&37 aMaF #.}\}5 }=,{ 4j8n<.ӄfv6M^MD\s,TqLߘ6}*h 8[<=u&gJd(a}`?bwle'Q^yJAڦ&Rp沘ۑCo'X>l9Z.\wȌȚ*vLcEE \ٮ4Gjju0@K`N Ne$fɯsoy4`o \Õ=WU>£F﹎ GS }cnDZY[Ub5u wH1~h~5hߕ"U.d7{ BsBS* 'ߍ ֖hd(S Gc,[+iZ , 1#|;P!lK!ti8*YjG4H$8I`OYtN0;À] },gdnw0\~XT)! aEg28GPl&j0Xwglhs07  \SkEs>Sn]Qp{~*'{cO.oyƕGT}N7\H:v,|wxog:t0!cۇYVOꗿwh4Ku4>bB<`"Xo>.9KxoΤ/a' ='Uȋ=r~ppo]tNtRRAE4`btruUJvv?K  `S7`q!֞V0| O4) k=B9Y>:Xz ZQo4s ϸ%npe ~x.Kjy6)F?ogHY)"*eݴho o#pүƈފ8xUBR !ˬϹl;c@27?} s~ا<Ӿ?BsQ<77.+ԥtHq\{i6yS>^떔"::V)з׳>3u)Ґ-ٕgt3-^cy57X(ص~rܷ.G`zJwY.;ĸޕ+pk _jᄏߝ?-OOЖ߳ `8g t!L 89B>p@,Xhwj- X8^`$>d=uJ]ej+vM~P*ٵͬ*5b-pRXQ'g:z!YoEg>Vx86sbIfS?n%W0ώ\ ,טIl0GC'H lC+|h&_EY%SՓrg0ԗ-J$?!_0EJ.3o-ig;?jkLK˘oR'3ܨ5ny۷) oly["{-,xM;Z9z֦՗HVEXV1kxՏWy֎aF*^3K\dU7L9}y3}/+r0\tH Tƫ]6O/qW{+}!4h"d9sgkfybvI6bhu$Bf[ /]mokfkv6 qhǮȶ+y+BjQ#1&>vnyjBHDxL^Sʪ`%T6OAnqF8E|CK?{q]kui׸7 oT[~o0;yp>Lj~ݴ󟿜b20LF3Nr/V/'^5_0AײmT>WL%I@]-DԨ R Cm9_W"i^~n|D` Of!F:MOFEǢ&&rb4-wWY[>UBJ^@ʔYo?EgV_PV3 D' Au&4Y06\g{o ]P5Z䊳~%X5Pt0ze}'i1פwlȍ2F* ~`[ k2PNT2WqbĖk~GPE;ʌLG5󻬶$PKKB;zfY?k6 7,50?!4%װOT=v]ommX 3pKE%vJv9,B .VGK&x Txbڟ)V<Ȑ޳E?ٶ`` ԟQOM0! {zСv6eM[WA}B@23>(| W_B8=~??-QnK,iELJT񱛺 ( z\ 8kvR_@5 ºN|*:!'2%q L:A@'xW!@5Kbֆ؛`)hʢ -'z2_"UJ3G Pcnv*4\̈:\cwMs3$f bcЊ^W?-Ui4OL8%*2y;&A5qZqӵk2`MqM,ZoWd2HpÃu PKm'K7i4P.hGϊx4%q!v{Rjh˷[Q^(5Z4Ջ+y񫟍:5wg AÅ@spkլ@|~^3Ypv.昳^+Ǿc\muKc:v1f\a𯸄E3| wP+؎gzeMX65=2EE2̒[9'ICIm51 ̷ߏX SA0Rx0XGxHԪ޼ω؇l0AFB9"13`%LgPQE|Fzb 6M7  0~_16,Jfn~qBrr#E9HHٌjO84v[ ՐIp~e{gՄvM}3B#uᨪ݂+Xp}tZX^f)viI"><#@ԸqTB"o-cheD/){2neCs~V"(Z8Z\@IDATyig5y\_S ? EC# :k</_P{*uioߙ:n;k策T B)J*ȳX:T[JkC'Vv[d;mYo~˯~9応?a0?7AxXmL] ƨyuz1Wx@rRf+gPR`avFx,/v㞌XPG; Dv]W D %W89:R^&wgH5_\] rϵwKsODGP^:Ag;?Nт!-x)0~=E~SS8mM}H&!Y.>36=8zmȦlVX+6D`tƘ/`:=$g> /|-oXo'rl[PcK_Eڧ̑_ x zx6 'qZ BP1!+tay>)8XaxYVp3h< Fxx ).՛8[uߦٔ^S6<|yncC+ 9d_/P $ccx C\ҥ{w B|-k#W_/~y5c?^^DYB=@HrZ|lY=jp*& ^9d۷VjdBկ:fbWxe׻f0"H*d9mUĸ֢W[},"F8gp[,ڄ{_;[62LgKOc-Q﷟GJy`;$?sE#FW6wq{GFߩ3tUTq\`1M >c2[cP敩ۥ~~׉BtA(6ޛ[\oi|ܽz]uEc܃S΁m|C?d=T6x%W<'js5ܾ[9`H{t $ D3w\@W_8:oߎNӝ;PeIq1k 5ʠ FŬ +yR 1xwf}(lGWU^, /+~w[[DzЯl D/GYQ`$@ӟeM`ىRfkn afd6UU*ٛ>DlO9oP$'N۰`)CnYpZj/8Nf⻫2 <PM?y)ѦLݶ&E#qҟ)rh<@7 3dҾحB nQ)OJAS@'xj-շ@?2_.X-\6Fp989d7z$z.%ٔk}%jc `ܓ*6 5N:V&5QI}gښl&w},H3M+j.UttW|A$bro[䧪, E'2]-9h;KЂ<=q1`):kԬ(\-f-3U©,ܡ=3vfڦۇκۺYgp.6V vm(3 ElSGmقMYdWj&aߺ!d Wm,TC[ao/{0!pS } GS8*}wSA9J##yyX!v~YoafbݞKaicWoUmz缍} 붫ҥA@<л,7^~d|43g "A5@8#¯,1r$  kܐA0qN㴞S;M3&YF]d%ȈPq*9 fL5Qqŕ1ЦkZ5ay|g~b^v ] DDK rTow:$IB;gC1~F8O p>VR+(!="onWy&+U22GMoIUB1æC}hfB@KB4JL}<~$lٯ0_mipcAԍG*_us,;Cc,\-qPM( Tx AU 7Uf͋14.4 q R;E>BZ")`Qҽ  k|ξt [\K6.dg_9cA듫Y1/Q`KފV]/[}! *g[ s ûH ڴ7c_ ԑF5 dVc9D$&:;Wt$# b.aD`}ϭ#۰z{&CFs"2a21slxq(֠?!s=C@eM| ܼ, (Tj`+NC=TKᅹ% UAhu*F;aTyS^V/?݌ٴ)g]ڳǺR`e-zw;-['l+=n?QޕUdC&ѻ`bZ;q1TF+k&$"}2b쯍 *>CK 3uAq|z@8ǽc^ol~ggvr,a' 1>j=WŃ)<?cU#[>"-= kY?_nc`Mrp#6 >W#,ʈ\M -"L~.q3A`3:A@o~/|8a8nq n<2^N DƎrfnqa}}~5Պ-kn]S1A\dܛ9j8R+=;t cӇPo~+xÏr7<\,aj<+Va^>e&5iOz0Q-)4ii=6 l[98n XR<=E'pSt?-a fvBW;ߚ1/J9̬%K +Nq!s6?@4fB ݫԻ׽A Eƨ.~aăOt#Vڇ[S0W<3` ַXARl1=s`u=/~))5h7QP@VRf+ć=i>Yu:A@_[~Ww/Vh.z&_t>;X̦y>9c#}"3c•0 w9@~M) +fە@!X'+o%З=z(xzu.IKLL4\{Hc}me &i,0{XfpE!:Mjjޮ2 a>1tB* CJk_(󒓪unrS`馓[]Zi6O#>4(~ޛ*_bmi1j %M,}k(d;Q&AN.x v\w| NPQ0ټЛHMo и2]JFû@j%5]9_! C'_/m^6ѥo, (:NC肟N?Q`R ,crTЙ:Ԙ]U9l]|y?60)S 5LLNKʄ˭8>B3a@G19f+$%d ġ^%XRm9m6'GW+K }XOvͩM! ps$2р8,@eҋ>uO&0^i?)1<ܫiE4>HbVz۠{lni70'WAFUzr?9J&MbRWݎ0s/ë>}NETu 8,W\tm _&@*b4?s,_5Y:yjU]4cŸ3-ԯ7~~UtgdkǶ `K-hs<2w|ˤdJԕ$%j/۾wglhCq >~^0ӣZq)N*Dʦml]7Q!_4[d}OA)})4pONպߦܙStb? &Y1^<$uwXUTp!LPa+3mL|{ ~FHcjUVޛJn2mrk?j;bw H,sBY%T*G_[O+coCopQR *nZ0@e[E&JbSδ['T.Pb(/Դl@S% (Unߧ!DB.lN!m!)N+Q3blXe_1mjZ5t#X#*iV&ˡZ2S3&n *0IԈ2P:F7UWitχ@5΁m?@J<,q›4 zY->I;bߙrl1D7\'K܎Ua8;1qԱ5|aMA#]3:ϟCOF.&W'H%]wʉt SLaeHt;·aui̶.Glkd /UR7&sDJEmS{ܮnV=}aHh/# $iX3O%;-2v {%,)[Wf4⋌7pD!n|er}r0Vd/KKpzzCF=y}肇5)6 FM/Gg[?T?.&·JX5sZ͘M{(Nm~` ܍6ɆWq޶`)[Gac8ǃLĤ$q3>3P@D_?#0#k`3׸˕&}F8>jPEĴ;CsZƱ 6G L+WR)" LcQ>dS s =Txc m/́& Q+"xbEܪ@JgK i҈1pDtjXeo T$Tx!Λѝ+ BW}m.ާ\Q^{]&.U2NiQB@yA:{( A! :=p*NemL)c fiџ =T뜐/9LH KRUdcb(S5;WKv{[ F1XM:r BBJ ITR=%ҽ"b^2!$ e0J]ƣb&?,BUe$w ?=,+9n?à *Fj[cC"Sf67Ǿo]-헩E]Sp &Xwd0DB9A~*~N{#1cMoH&vOP0혌IKVuQϬ֐yh3@X'~%Qh^U^;Fa"Ga|lNZIo@VpRd2C߻vd=tr> 20ƄZ ^_ ̜ZNJ&&B8I&.ZfnN kLUk|sg)4 uν f4'K{Y@IC74PS)a%sW0mW*qȝH$%T{{@('֗aM)!XHTPIUt_=OLj#L$q} 1 AI *4Q>OӏB暿Pk48+1╔ЮS֗E?INwI2"-mGf$yG9ңSV/'&@}-~_siٟ9 &4qk"AVXaR s1u'<]Un3>ZkVH%^(t|gDbuu˄Hn'C]]A7Pa3C n^ᨲLas]F@#bzZ$?-#ToMstJhdL5ɜ OFJtڮ~/" WFP~2;NVYM@XDHex93S"2@@&U88$ cj $?PN%^@ EE#J61&F6˥{04M]y(@sE.5_#.c(a=G&V` ;vV%˯aI4Wk#~#l.Mֿ jeohs >ĉRxal > 5ݍO~>њ-1P Y- .xCUZbCC (ͥrcʸQTPgUxeGq7} lA(gXj(Zbg9@qj 7iSC-2:֠">Ms_@٩:ZG fsX3&½ s)gj?pŊR%4A`g=6f v7 OdNyox74Zd=!Yf$nQ񥭊n^%ʵ),+I;ӈG0PU5iqBeW'_>pJz6UYBP1@F nn$Тf_1FJM \Pf>3I+6X cDu.rU@"ւ8vQҾ%n ksGT&Ev2er;nwl FK!J8֩#8u!`ďn4ihwiW0jq|R81:C%IOOv-ķg/O< b pu5n=pOung:#k8hx61+5(@?_`< P̶.H:&A(AP1FՃ0jeU@E #BwTH `\ZMqwN&&nzw[|HTGTЦt?Ό୪)$uGX-mjD( oݵA מ}:.\пUn}l H.rhhڂck `W~ d,# (a5vS }ÙByG+3N?C^z_VDo7+%blo0ziXө@-<VX3:&pUjnٶj|YlʷUhV{1s y\ WՃ:d]pr]+uH t\ |Eߥ*?ƛ8䔱J =#>G3Fp;3`~6;B*5&8).zn~L:N]) zϖVl_-W\ggDSfrQHנ8J4mu}   b?hPʹYYY*bwIT'HGKeíAoUՓ{8ӄ=.^YN'sec!XWe&RDR ӷ{=OB:1IžLb)k|J=vSdUW6 5$ H҉~oDCZ#e+yMߥoF_`#卶.i0$V߆1MEr("IU~p]g\{Ch:*d$w1!z \6ԫK0r(*poUGtP϶j~"> +XpEN< Nn&;4QW5= ó> X&ܼtJ8JS̑{lM4ڶTO?;l+uʔL .46g^Ӄ0ٽM'Mt,k oyb% (Bf1<7q]GOK;!%~6uq:<;ABo_}/g/_gY]R-Wd-GpѨ܇wpZ2 2R jCo V$h”A=Yҕ%rSɱr"@_4;W=vSF*u D/%ddv&p7& as_s\'~>: lzAN"jBc%DX| R9UC6 ?35U_͊c:9lHPj\I{f`4MxIU׀Nc~!<&?q%s3Ei4MmfAC?̻c|ͯw fOT`8!Uώ|?Xp(>UZWCV2jGUɑ94 ]e0!b(W mwC)I~balmc o^N VKf6v)|֓ <8.S_2Iq[K|oe9H;nbuI9#q!3ٿZt^Cz >BN{Z܋^el gϐî&P@f<"N&¥PUɈ8kkGXJ=axׂE+ }iH'9b4G{/9}3 ۓ7ٟK#|Bib8hT^ pYOŏm4>PRw.OJُY--Y78ъ, }sSEgrJ鯓Ƹk\z7387kBj**Hٳf}6{d6ol|Fd 8ek?9=dd|8˅e1*x(3zntɰ =y dzp+%wo?_&tt &[$R/ ߥƂTxq U2]! ӸLL,!kd[ߴt aRsQ*N@yՅBXq ;"? s (d |F& hMו7^J|֭w&TzF%pw?p5Ah<ҵKu7k~ \fVXAo}ۺ3*S-hz~8t.|[&6O:#i#2cVZ6ҼbbJa3nr?A%2ZA%$QV `RR:xxYݟϛ/{ӭǃ'Ov{/_q:5̎nҸh@>[1kgf"Y nrP;;8,FgcnSR.xu3ZnY#Őbҧ 3 P Iph&gNtv\h̤, e̵[GM+;u{_@3Vhu^Nˠ<)q03C7i}/YYI Ζ gfJi& wo37 R߆"wĶPl`YNĵcUFio"X:qXmWz;m.FEqBi\ϲ ^l/{yc79U|ߑ5s!|Pג p$<  0'[yg4nH8T8ۉBmKf#C1 &(fcoB.0%±i#eWL+ুm8 m >:fVbW/sk¥~?3v![+⚾eLplKlpa9}~X?%-A1$şm"@WyR:7%*T HW x偶  4onQoXvg= <*,s ij ibzB&Vu5߶;!,{ȀNC¿ܺO+')3?T9F+*5w궶)/ʝ(T)P)p  [ bMCLxИ_|=m 7#0;b!w@IDAT']:i`M,dedO4T,\X9E9K؈SCx t5Bn-&rm7TOS\ٞlL&ura$)g!N3!|jb;HrON:Z]S?+ZxYr^J,iC׆Uq/ \+LKWz3چY.*s CR?MLWF!V|Ն8Zvvw1R} ;/E"O# pf+UPLxqѥ&x#+̿cg t!4'M8Έ') Cy`<6oDt VlN.6+/ԭ\E+{F.c/rMSePHfS׸=H [7iNśge_σV GWx% -JJEj**n{ɏl6i'4b؄AO)@Y]c7?çEA5 q|8`sdW`9 fZ0Klunxu_a#c|y0 }I0eTPLAAb*u˸؅€4r3A .k{-m$ha"9c =er5@A߸*!8:#'PxpĦ?ʀ,BH04#6w m_@2& "+Rx1FHChT;wi?B @Pb,0dMw\zOĸn(">O5+{C(",{mm]P'QEP<@ X?~*-z `9$H^&h?AM:fm϶2-o5dqh#W1ۤp*STZj6Nbx83Zo|T T ~^Wn3〯)3/a,T?M\h \0<5Po3>\J`L$>!^sv@c0đ0q|o/e4Y~hk%Zaxi/ҋe&A*XZ {ƴEtM2 !zd2,Ì =رLpMeΗsG /%n>Avs B2 }4' .6| cFM-t6fq{ cw6xtX %DX\Y[25"ҶQO)zf,hH~ʉ (HYD\삫0!P!5x~ c4-,VUD. XorPƍ0wN-[o@{}p58ۺHygX5v!\>U].ӾAye9֓YXu^SjKamj;ٰ;5?4TQ( Ǫ3ߦQ˟;Yߚ3^a%50I܊ɰ l`ġ.gFiF Dw^&'o,346];na̖<i"~rƯeѬ¥*,'(2E]lm.\Ub{ Xc_ N7\L MR%)naT0 ]ْ۵́q'99UcBYAA>*O"ߧ|Lڃi7{'')\F  <5HvD j"OOoPF;Ս>X #-b o'[mtqkgC4M{q U[,_ګy/r[-@ŧCC}j**aC-{Bg>`oxO{G0ʷBs;bØ_]nXGL;$H46$/fhʎ$۩9ڰ(G_s([G)UHv'x9@o\fj\_ct 7e8Kf ) b'D.h?QIfJf*ե2Wn1eiZ2vFc<` ;V{Կ5#i!M#}NFs_)o'G*&>3sEX <@05MV2LxBZ9)(_wSH1} nq&i?h "~t-,?$B+aT%!l0%=6ۥݶSq?ȑ )hpJaԽBڋ~3/x`r)Tgr#k>R1mЦ, SRQ & u>oۆFLP^˴S 5<)^:XUN>G F,bܣY-/*Ͷi?d*sCg)i$yuu\|d!aHly{mfT4RVW~$e nU"iF;Wpa[t yC_5J ބүU>n}{'={gO6qh8b(s1؝l|$64px!?Bg7U5vy . 6?WK \F譻ϧ8hqc$-}xu<-͚,t *o|^#52 ϺNlĚz:M\SZ)߃1s93.vIw9'sJLq.J0f?bM}-|n-lA$@'[W:aar`JI aOopԻ !^& 'E@ˆp`ֱOs)>CvdU/u_tGkŸ@/4$4EWVp v?o+a}  X( >@97QUl!!-~k=C8Uv}@oXR埻L ߤ5¿sl^Q1K=·aв?F9@e9{|ܶ7,֕YԢ'Q7l{:x2YmS>meҾCQ0$L$J7BK:ծ*㫩E <}[<{F?9ژ?o/ǃǻG('cHf f[29#plVQT&a<{.qqKe;)e'nz/]6"#)C~'-Ɔ0)gRO]J&grbbqyp6pIȷ'vy}@Uo%`k'Ms^!-$Ɇ.,yc"wLO}BANԠ? z& eөp0NyNS= 9ݴ ^fqa@}f϶_[k[fW6Mh6񃲎–v3FsU k\J40|ɚwTߟ2Jا(U+ +8O]q`?Q@}!%{M7iλ|k*TTlx]4jS^tWYĻWYU T \B@5R7Ox|N~^d>q3Hl<3cVD.Jr&]$STPoA֜TDXT{0j:(uz5*d(K8)Spe*/aR* = "Xy;E8흃@ۗ3aN8"ͫ$N^OF\ ~PS:vjk;Z*fK3lgʵx`p^yV.koT-]ԭ;ۣWyJ`@v!z_q5Z(ЭKUo=\猹eRKUҠ=-Gw?蘕RETP+B˶X]UM@CK5O~ږM/s G^s(yU6Y C<\lZ0r1FP.JRfukw(|JLnUuwh,c9 c8KM6lSrLF ACfno 55Rjv=/^_pJk"<گk,07 6 gVpJ0XT7@T;? ڀpe(li{#3Ug ോIcU(Z#c>GcT = %J` Dk&¬k bseRA?N+؃gApN(:A1_m]M@P*5JS z;!ϸ=lpAX{p1[O;\ ,⊫ L+b?'@C&Y;4hdFr(÷ang;\JWXpBY$M<<49TƯٳͩ6cMF5 8:y8v`S/yӁ)Kt}3 +]*hdX)07A$U(cvD7^Qʚ)`ݕ*e1q夆 \J>O4l{_&d P䗲?no}_̎M;pT T /~]s)_zw84Ѵ2zd@06&n䭁pZ-u=w9ĶjHo؛ w=5|al%Z>P*Tx0ʹئ;oۇk,cmO޲߁awTl@Y TYTHQ^4{{h:7a?Y ߃%b@? G Fb2^ 2aaJCԀ"CZ _TP]9 ~?պ{ P~ԏR*&s(6w1 #0j1-$]⠩?rؘ pg+w7x=y(Րӝd<|&)%YiJ^ִm5"4_M@ɭ? "Ϻ$ ĩPtӺQHw=&)?!rnLbZOt*veco*{^̈́+Z_ϣ/U/ WVB 7D<ʷ߾kc5-i*\af\[yH=&%wl1cW| Q%JJ@UkN?s;Tãcg_s֘`ض~(5hMaX5\'}{mr|b  9 r rpuݡo)NG3^1>߀ۆċHMOMg-O|r|2m6mM|2<>bt(@xKO{\= 1d`}`@ "3M+"hj4wr.H7>zxܚy{5Q}J4\ 5XATURx~d|^e,z=sǙIc֪-TM:l%=1tA%D]—DmE85fL_gJ'Lݙ? S3z6ql>QanR4Ә^q?7{ͬ"~\ߠ ԑƒ-=cыr{zx<,^Of}6)g` p((O?hy`w .CPVcClDD,38 !w#';,yzxƓ~8g\Z(Aeŷ|W; U|syx./QQZwD.E ) maVjƴL;v;@?dl.lS@~=P,p\bʙ0g;eJǙGT+DŽ/ hk x"=&)wQBg}{Z?[d28d6nk!L%_}ON / @r'8sT{QǺ?V`\`)T8s54Tϕ`,0Yh) WK6~5GǓj**&|/zl6'PA8:eJS$k6E`Qt,Fbh@K?n:c9J߉je0'O|mg5Ӹ NiT 8K4AR,y)`L7h7&,8 #co,yX)P)Х@UtQݕ& f~f쿞"q9dcl]lIÂh +YuW[69 r"z\(G_vzEqwjLN8U0\T*]2迮RͱrZMT@qzV.Yۓ3*^c<3dP=GӬ? J¼OR O`KPAm]#3vR)7?G[F./>&i-F+Nh~_>;Y)^Mن#ݭ>FоKƏC#p͟ĽfV=Q*ܱާh~pO wF! 80a-[j]dס֭dxAo5@",*J^h~7ݘ&i6{3˲ 0x4m^= [\ΰvCfYg{>{QkzDZxf#rw<U;FeY 쁇.Ja\J܋.bJ>YdWcu8ung+alKa& 6@٬Ϫg" uoKkjڋ&! b4d^pqzt{qg ىSsL`}T ܘ_RJ/jAfLoen!=l-~(\ 8ƏtY7ܼ[LCJgJW f{̆p |s ؿa1!,^^;y%>~Ic(Ԭ(ɣyRfsTK>ط/^)(Rٳfg/mos2F9ní'|cfЏ7,Э;Č̃}L+|p{SBB5V1 :G,$!\ӓt;$7M̈́Ɣ5AWXD h`L=Pt4~wVwzbu eĝ0uҠ~H% lzv-N5It¿p vo7B1¼3*+/O S P;@vx~ʆ#H'{jw> &qK,-"kb6}c}ܡu5}+Xw%><'(Bٞ6ǃǣ"vU7sv-x}ԇ3@e#4GRl;RpakA;{#*WxDJ ark }QyY:Wztƕ0/)iÏKP 0z*Y@}q sf=̩(6F(&h* ncvm8Mj*}hU E:$UH2>p+$yIG7?vpƏǷa;}}RnOAȝ[:>tG?m~Q Ktv*)>$dcE}** .5RAP<},|vޯ'0x8e aYg&UV 쬣n`vo"g8.I,LBK&RF0%4ROt\GRtK~xf @@b2)9C쥇nrJ#ӛf߻aֱWS[)p;Xm[~Y~µM ev(Tw9Yݘ.+>?~FV-[HuNS_3ai60LCjj{@ϗP٤^cm{5<9 jw(]R`hs/q{fG(5$oҦxnPd7֥4J-ɇ + UCIYVhDJT T h]s lh~8_r7V}F-G[~̳Lyk"v0.` V[Ƙ T411ڛ|8.d7! &bgnRY ]`ϟrðž1_{Z( Nvu{E_TS+N(Q|#}k:-qg3 Nآ m  E6Iܝ%[|&ʗxN mo !~>kWw]oOVyAACV؍Bb"/:v-WY2ӟιP K(p =( _߉x:Y/0ۈv/y(bM㝥M5 <{̟7嗿=l,oO'N|<GlL'xPeLe3bR_bĸ!㊐&YGA? 9p U*v5@϶, Bdj{v)p,urՒ169?t-ZSq:Mih h,q"RuR8/+ Ҫ~l%mL@nkM^7Gآ;D=X VTXr[cA;sc];(w9l?[ x?U0*TM(ؿ8lz_o>*hئr8{US)P)p uͩRV(ioY?9z{LfxxáhG.x5-X" ?ĶR @YܙX,4˟be q.2va{0}m׾Ҷjh6rNB8gz~W *T8Dn߭6 nҖO^ݕwO{*I&Ƒ|E{b<9j,~4.qeuÉFg `Rld ;+=a<ūqO N& #;P_`IM1=$]_aHWEWM3B' Ƕ}KZxvU`]U+J)Vц ." Ig䝡3؇CRwVFG|"A??(Kݒw1s~+KfտX=wIKִ+/'bh0w43jZ?8v̖G`jChx/#2.XI4Š=ꦣ),H߷KuBnJ}wCOnUR.9ffeSQ}Pڌh{e:gH2.LYA]FtЧџva 0"p7F 3 q`@$ [K 3۞=E[1i679nQCm~F=l"0L2)I4uvc"wS>FAiHN?6H 7NnX:ے!bZgvh4HR NM3S %68~GZKQ4r?WXQ 2W8Q&HL̏$H@IDAT 3_Si"Ai>o@oj{jC%z7(\-uۧO~9]qG0Kw6߱Hpwd+bwEC^{0xր[gfT+TJJ{@Uk6A^h~7ݘ=Y/h9NcmFMpʠOz?2Ŭ'AïapҵFx S)¸יYW(|_fNW!䈎L' 6eSih[Nh\P`\,^=T VY|o|:mcF13Ms> RLi~P3}>?{q衂S>k ?PeZZQb>o8nA[\~T T < US)P)P?`notQl1LXÔp7&C)~y$C Q#c.T.u4B(͕3ERQ CQ2q4q]қ84)sF]g0ݰ5QڔW;g-SW\bKkR잇 Ҿ?35)BP}]|v&~Kw(6I)y_ʐ6;bݙ.ClZ=v6C0#< kk (a+]F|F㒆1ŋL' 0> "\y.y 'ނsQ.*o[[ӭ+a2>H?&a`ˉClg5_KKy>'1 7fq+@D8i27=ӵ$a ?iOx5~sq?pr|SB:|}RI-k%pU;Mxs[h rMvqE[m{6'x?wӷHniq?QTA^P#@x'a/U PIf <3U3W ep3}t~Z6t;ɼ_K  T@mPgϚYo嗿NfƷhg>珹xj2<2˧39p>L9b8pT ځ9#H@' vLb+ 9N[;y"wG&oJuuSqn@R`4Fkim/',ǫX:[NX?)ŸJ>c~?0`^P>aM̷i'-c2 +J ĵ 6mW.S\^x*M̳vyx2Epv Od&N OE[{7\=bVqi;zߓw5߫31}W綛fn( Pa.(TӬx3 TѭƪxxxыMW4lld?Of'gdV(|(0V%o 0-7P"!(xHENLbjzZ'c­&Ejޕ)PR0ӧ̵57LK{?A?1bBAPe ق"Z5$ x Gl)5" j k#_fe] ⾱[)uJZ@w_:ins E-ưu@óGAi>V@ހ ?fuf4 $V0'l ;Ki|eiqc)ߩBRNZ.B8 E6R$0ݰglx(6"p8to\ŋk8X#N:JHJlᬃ-GJX-r9mI)ǣ|CߢJWh?mHg"-0'wl>+&t@%ڕU*R)a=M_ώQ* {oyŸb3dma^WℇAYbd}!lpiX,ij/Bn`TJ pL^Mi5&q͓W s ]w%tK%_~~3 *9_PDR½n˅- /&B wQAdqUTpSsƿi7gw8D0'Qֿ M] ߧv'n 1N~fV4x9kPqp\eV&;Kx+>P ݗ[@isi6:JD-]COUoו9 ~T T  wj)+n_}z d[9{pi"@?8qPd kC_Fjb*):lɩ8nr1!Owap j@m%` Iu֗TL[pv7nz%nxtx8_Z-cUP\TAXaCQgW1Sm >H;Z_`6-tBH@5ע@J](B{op t0 >b4JߐbJJwU.V-k )ӟ?_pl4q~%SnV2a@H{0؞9,3ɼj~(dNbBL02`—9@ $I UE1"~bP[ܒk7^ΒIt$σwqiI>ߴbݥy.˫@Mfcf&k9R5VPaAX`}" GwpyDMo0.},U\wў+hYmўq@& MEiwd!Gp]3zS̛=Mu-ߤ7&]=NiSxǿ>SEfKz# ;`|e #~>gѫb:올MiΪSqd5$*^xlL7f'O6 f>ɓ&[%lNw6?f/g` f~CvIt"1xKB$ :x<Э!h3Pk6vScay +8&ގmhy+ҭKѺ~[~lef߇puZ|{IU!]b3d*_U@4܎d&iA Ox#>?)ڟ# Bۨ8t! N*BR_\UDvu $'ypVS)P)p^h\AƮ^rvgMܤ1U{ױݫ5EH# )oqYŏfbZ)6 GXIpŻ$p^p/؅Yrs =uWgE-)g;sV-N!IɹsLF|&n ,[Aś1lH)_;2O'EWU6`錂|=? ǼsɟL=¯ L !wA@ p~J#nbJ:/~IЛ6"jHٸ}WGį^7|ύ2ʗz!@ _ހ9Qr˅mܔz?thG^(qJ~Ǥw3x xkކm;\J5㕨֔m; IC@U|h5^KgϚ>Mɓ|ocbIN'fxO?eИ19ǵA0 ? yXY0Ç)PqI&>ZJ 0 \FkH4f&(k ,48==8hBn+uƺQo\jpA-,(+n[)` MZ<xi6w h֘5ʱZNV3+[.t#̋~m2)[A"pɷwoUO!UvnNu3ʗ^yn5͹fM=u?Onգ(OaC8]5`dHMN<êyH(5ShYRc[ȇhC4>-=CM%+gÀd?9蝜ZS@)LxCcwE`+++'"VFfD#b柮2!@ S;YN`u_@\u2G@EYׅ^``xƿ}N%3Z.:N<J?uEY L{סc] e08m^q˲***Q(o9fc;o>ٙ W|߰*5޸OH Y=G?LȰe]6. G>eSB'e $Xl>Őj놫&eU#`+It Zm.$tK/S%.oQ^&@ k%dGS_-.EXF}9U:p/5LIDjXv@z fBYO3v?i]%xKSUjR( )|/y?:枀F3 ?d?C5褒&q'^?/&u>#]~> G$W vETr眀~v!ϓƒvs:8)(F5~1eSPv gHtQiVVWyn{5~`{Ze~^ūg)nhR)P)R*JT)o=k KR8+*oG运SpBf>`?ǛFl]{QL )qiA{Y)D(: Tde"gcX ^mXΛ]Zu`&$BN%u.nR!u3+`%Iͼ }+*[l0ѻbZ(cڀ!+}Yw u썼x)aڬ0P_LXk-ha%5Tn.ISZHA?0axARM710tg;rOw^Ӭj_k$P^˸}WGį^7|cBt|kQ Mٙz;Jv{_'¾~s Npt{6C4l8MFӑT#^1,|.?W'60W`{@,0LwVHw&W$]ad {xҶk^ K,Ǫ 3|/JJJJJJJwU.^-{QW7y_x`p<l8@Y?Dg1> !;,@.RaH(LHlXb\vȱ@OU _Q*ާڬR))~џ~`AȺp]Iof ?A!S ? 53y2v\?@7$ /a!MRJ۔ippT*mԮ k(ÂfXKx*SrIqUG2QLhg*r.%^X^I>Ӆ [ OX XJ%YY%4}-ooؚxLנ]Q˓k[sdҢt(]g3yӻ!o\&糔)_I%~|_ cls惥@U|U__R(Ϛxrsk4~=?:d{>] zoLFCCnhfzٷ u|~_P|`w$X 1a*Ks/;@%&N?A׷.y^g΃ýLG7 ŋ"OݍxKn)*Y^77w|7.݋..Ӿ~0ib([?<δ?ۜp3CP@do@Vl K=t_]wd\uRrw c_?#>fhASşP@zu4Hۧ5Rj0qV%^+******W TZ**l8ߚ/hz=Φ'p!Bؘ[Y2`37# !>qt{uǬ_J|8 ((1ʚίf"rRY `*d2wu2kw۶˾(aex(X #jغ2w JuT؇@jcW_j=v/@?Dc}˱clyf`sQ SvGy܃hI P t1&I80cB<:JIpb!|]@-%<b%]d !.©a~ƻN@UUW ^)Q?iYrKƸoz}4A߭͘p=Z<[ M\H"!cYa^)|_Lqt4BK{ GG[r8`V 3JkrX/`-OAh Y}| ݔ6)*\˒$ǕiEDF$Xl-ӋEY'f-pA(&Q2B`' pwcۧގ^HW=G (Xs}&JV_%2e/e^oS"U_Eڣ  PBx%={V;϶?_|;)E[T`ޘ ߬6,2)e~x+tJDy7 V(C١@p Kj&=TaAk6ʼtY%KD$:+.%߾b\B)-{'fX @ p_ `< k G`ϧ_4ϟ~]/tt_>- gzdeJ C]/? PWyh )oxҨz%,5Sw VV# ZIU@?;^i@TGTrw׺\&N>u5ԄiY\j/ۭ;qz'8*tN`$v$&MRm7TVKZ*Org/Wv- \6QWZٻqQ旲:UAwI7pN/8K D.y+?zSRe'я^ |LER~࣢ `>^'W?yR/~>`w|7x0`1ζ'b 5i&w̥O5' `*҆p9URF@[L78Zy$!p(b%? <6IPyn̉Aw%YD'FI~K}11j2!:pG$w M>S=+9﯉3s #R3 YɣYYC9NV=ֳPn^.KqeDJH7JXƀV=ڽ}-mqgzd5wR$8-wG\IMa龼2^(ҬZXGySY+׫;]sK:Un p"x& ɸh5Ut`{)wN ?~>[<{oKJV/?~E=:U0 /-ڊؽ @;q{~:,a eNcCb y%Ood^Fm5o5o0 @#j^_.N;KTp߾q7@EG4Z߳f Syq셖GʯJ|>&jB+zd  A yA+-: р jӚ(x@u@ku.'z#|Vf4AI^Q*]~e挃>f#HOb!@%:Ǟռ˶WQu·=@?|zp5zzQot:vk[{zwf2 E= a>RUQý@GE#l>+VܣϤk<I"+3jN}*K)⻞Jm֖ꗳ^T@4.Wr髪wb{s>ޫxc2Q\+KW'Rc#_+jEYWC olXU>2P/~@iaHڛe8|@9Iwƪ+CwgUK\enR+IHYG&-\*j~+Db9)-v9ύ `37$l E˝ d@w#)|f.S"Q^ 2m@g"ٳܝO&7RRoy9)j >SQ 5mgO及A>іwt@aclJyY{MxLϤg:JF;y9ї$$)Iɯ/>vOFŜھeԥ?í Qz7nIWڏ])֓o[8{soC +$ R5 p>}|:zӯ|>O"HLIG;RzMѣY+ &)d}kSgW5iXeGZPx}hF`Bצ 6m\ _ɍ^iWJolѿ6"g) 8Oo\=.|J{|Q%諸 S}*Sʿl=$:˸pTcW*Ix3_w 0܅o{! E~^LOwwg'|8~"+vKzטq h2ζAYEAQ:!p}VVZAk-4iPzwJ)=(i ^lau'o)yܟ@;ͷB. !ߖ<;rO9ηw;U@%>FptBLoeǻn={$%r @ @憐TH`@,֧AxUٴj9;׺Bg~T݈Tz; BwɄs]56:AM &/Gj^M p ~Y=o5ٸNz~ Mz_: -K׻|jK |,+w`e:ko(eVGL JY*}q`((h(!W٥Yit"l9gjBv)~٢qKERȉG" 'ռr_鵬 @wxVr)szpixM_瓇N5s=Wfz9^ -L p(hi~9`+"_C=0QK5FSۅo{jJJlvA] j E'[Ne Nx[@*oKyK=9+unHtn@ %3O;O/ZvL"_wA˜k9o'\h^G0umoU}U>RSxةI){j@-%~qt8NٳgwMߗͰ2jG~n?tCӕϱ$\/ ̫jTDo(Ҕ7HZf}: TJtz(\ebBC =3}+T L m7t0xIN(S5ʓq h XW-gE%GNR9x0lԥ0BN"  M@&G@BpPfOޟkYe'e]h+\Jc5T)ցu'C=5ɨ|bÀxkʥ -a9~ǫ.JU7U;^qu&_2a&f;%u~A[ = }W꤂Pr2^. 8~SK)4~O#~U _wtT~{Itt#qbT+$4EZ? Ιq:r?w;ur3'b{*#d4^ԇTZ/(wyCLr3[h~<}_hgkQomb\?F{JnʹS@z6Rxasm- 6  ZJzDG{4'CJ|"CNyItc_.הuGt<'A~i~ *`G8 ^pJ]~?5U*J=e9xgnA@e0ܲ/B/'qV=y(Φ_6۞{0V~p}X C6|WwG8n9._k=ޫzǙVuH`ĭC{>~+3K{mzV5UV6tϮtb^S)}~["A|TxuKEMRF3X>ԿtMi @X7&C",BE ?ZH|V//ʲ=T ۔aps}sPG^>_Jה)}?긞4{PsBn䷌'^wS6ޚ>\Ϋ! EO+ޖ|ꐷ'XL _뷶B?MƏw7{ ᧼W3ZP佊,H{Bʱ~wF%)@Xvem Rz eRMDDjM-:lo2)>!@H_ռ$2"e^Vy=0ܞBWL;xSU|V\)u!pYZ2=]N.|DߑdU3g$[YHUO:DZglHة]qWs >yli}-wZCp $}a}{ՁlVRiϤH{~OlHm.9l7hwzbMo{'(3e.hҼ+.d pr-d`&1n]T6nw.>@`À:U?#*)6W{2}o:1IjKInEE˝Z1WKv#OHxWY+*A7@RO-}1oٳz{wwAhX~YVӋ-o=#n\c¨u5Ť,wYSYUW zs@U+3M ϦZ_(1۱j[ _IOtvZ$2x`ׇF?I8K[xCucN9֬[J;MW(N(Y @qQztھ|y)ԹXhqt6\c]ZW (~iE˝ d!pC `_ ݂@^?~SGz?>Z&4?SSA)C<gGً*g:ZwjG@cr^*/:Tl]]dWrS{­xͧv<R;jvNQ=fv(|ZbyC[gS2-R_e.njR/"^;KG/R,"O! VhepS?dRu`Um+ P5qiZ(2&Wšm_!o?jE5ڔ~Da7 6= # }/g#A \²hԵ ٤^ڑ-2vZ#]7wɠO]AtJ{ X}n{ռ\*\nVbg^껣$)@ ïNtSeù{1 `C&74>z oT|S={Vw{6|pe^uSh_î hׯ~9TPp5Ѐ2 e)܇7Rm_~uTڱi|Zw"߾]H&i#VUVĝl˰h^yE J~KJS[ŭ:_W*wgymbvY]= Fc^-xc7Pҳu޹{D=U@j |GU/w㝿tk8AB[< su1b60? bg:.0Ty]ʿ){u5  3%YYo~y{er=vL(rsT|H(^8U46oXP8S$s72Gg iw(f!m;s-|wn k'׏Q'->gf wL[>+6exßO϶z4.j͛ϴ_*Yy[@:n<& c}+ p ׿ b}鶻uOj|Ш!Ql5T;MR8,҆Z/UsJ +`kp@WWǹ+EH<K~(xo(Q!ILܩp~Q}vBwȟD},+n]+ 9RʞR_cۄSʟTlmkm'oo7;:P,b]+%CJM Ji,uZK|XvbZzBOc}x|'밳0ԅw0ܻn*?ܭlYk%tg^/tz BgR4=pؓN/G]eOTS#ǓowGbJ;#Rz,H<"`1=PKw9`*ZyƇ:;E2q{ª|tkx`Evy]x#%ʻ翣?>{G%.عUv t!Ӛi 导 |"?e8hH@qS@3P5iS0]ܽ#}0 pS <{{w>ի|UYUQ` ڕ[@9o>;|vc)$X'oz o,@%%;Mr;=pwXx!_Y1|s4+Rc )²;Cw4s9uFI:m }8=Y8!2Rq+]\__'gÉνNɗp/xJYJXק%KFVsT|*ygjk3yBԝU}{;)wzTpk~!яuҮ-:'_CgVM/奈R8`7+cm_HcU 5Ѿ<N9}">}+ p |?=ȯfo|]0K~XM`H1Ng׺ ý}X/z`{,1̋AYl ^z(*^'ﲚxzUHpO sɤ|TOmP%X&iFc8P0&)'|ߘkS$9z8 bgGGr'=J?B wMuUqv[&a#U]w+{r-wIK=b~t =\ZOwr ިIWݒLt)XIO,p3?6o+~+J+Lh針!?P8o:P[ia,n'qa k-WG0ܣ/[n6?X"^!;ԃh񠘗{:#@NܻY6XU߬ƃlh`k\|0̪ 8!^eݯܗ5,Cz^a& *ta ɂaQYpo2+U[w7M { ذЙuUr¥̕hhmފH+_TIahI oz?{}7צKP;.`In>%Tg?>UO_ܥ}& p?.)߹p%S^ZUM3f@Y> ]AWUeSح8#(c Y6ЦEʋo/9W8ʿG>~3 pc <~( #ojYF?ok~Wp%~7o?eoa6[H"?[c5*\&~NkSm?Ԫb*xyĢmGBVf$BHp֥\7IbXjiOWmȑ5;R7[A0V\ "U@< ߆R&HrKƏnB4Hr骱"؍^v'y~=DggvMQvL UwK w~b Py˃/µ(~ϝ?v@$A!d\nK?wC7@^?~<>bv+كg?i#JJ;G!ۮFqC ^P2z\ 3!<ЫkIJ+yuYM(b _^Svw f`\,0=Tt8(k՞Gi^24}=w t4PB; ,P:]&*b~zT_1UHěH[Uz"N6$+9CXvwr7亇Sb\\0&}T$|Yv5&;o_8[I^%C)bw5zg__u:_hܭon_G;Wu|/?`ُxǽ8;GaC6k7) !I`>x\Mb&}_wuH^_{?[qyכJB KNz^z}GE6`wKxaׯ)IlhhUtɺ\ڈ.]. .w{^+ ;dbɵJ6Y+o9߳%VJl tI].ݺ|&v*t?Jw}w:~E\yC1˴7kxH#DG,ej_=pg%ତ p /ﶶfNg/s},AǨ=9H;sO*`[B]h벨rǎV+ =)y$6.Mah%1oi:sog/0SXS.Ûc!oBB@qwy+jo'ZE㽍dW2REgl>&$o_򟦆]GnxQ [Խϴ^*|_9WRaz~sP{#1@j|? P5{<71ll|n!{g`3_%7wϞ=;{kggwnH=pF\YB7hs_s  HwFYS=FPMM2||)Ҧ~"n@}hG.3bnHdI/5&ʷаa$XW@`r*mwK 5] 5S/1o'Ɛ^o>۫Ё>=7TOM? 3]ޙp{s]%׏}4O_d?U_t2Vl_z _hlR>*G".|z@Xn$[i{MRyGt qf?~ Hb\r0>W_ C}}3N[x@lptn>cBWlW_<ؙ~燃{{|ρf:?& iݓάvf6(IB>lQ6q&0f^ȏo+V_hfaߋ֕-68}6jrj"%8)UD6]YɭƗ2/YWk/Mpyv.;KźHI0W^WrB%q) ,?7ʿs$P"i{tJ`T @z<~(fo{:+,N_) dlPf[Zf^Ur6ǣƨy9c/!4~[ v}_4JU<ҥk[xd@i:W0 tut~uq,˷s M,fH4hޡB1B<V} /O/&/%)~ ;m(HPPa}>ڵn:ɯͩ͞ˀpjm=#59|Bm#7(- {(dĝ޶cIMn=癀W١OObz_}M-Π{7 +sYD/3;GLxb''1Ai?뵃:y!ʾnT(זVRyQu) E?pM4U6Y}jZxJ?w9|guigl1@YXjW}1RxWZ'z> UBpojn ubE%ݼ&H_>}HëIc.b"z'R9KTnFq?u`@wmeb"!p./XrMBʯ[jR5Iї.r)Ϳ" pn#QIÙS<=5t0q.~,͇Bm/M~y.v+Kn&=f~ N^}+cQF͈ M4P_t N!\W_}Uo x_/UBV74!ܣªv68LCBė&#B.]:a(P 40PVk|4GqY;Xqo<RZj(,낭s]w~d5y@U}yG>]|}x׹6ς6e K ݌iuJ/+C4~I;]ICcۓ<{Qdon.cbW bo𛽽AU]LfuB +i_VTfVL(2в^r=6N:Xن^W0ʀ\5ܨ?.v>xk֏[z@ꅆ/nG^?PCQn~*I8ͤtcs$B>2 ]bvQ2Jow]j' 2p}9+=nOUC&[l?7pUȡZo J-=75sKAUZi~Kl{ @g#q&#F?ٻNձ{`\V}nmH3÷뚤w:Ú[Mlvt)j`^ xݨv z`P@/~Ė}OS縕8VPZå}dH:30+qEP۲i'j.`կ8%ÞӓK/eF2o9?.KU%5- +XqĉL@A8䨕8Ǿ">Ɨ0`CGR'OX8{1z=Vs.A ?O>[lm=̷\݃cuO=#St~b*'w+G_1ެq4ǩjңyBo'BYOhVk b*RvwNA;|G>\iH\8 ;Ҝ$Ѽ-a0R^rS^x?QH,u}ͧ8@>/?C_}g_}?k> `m^]`@;J‹giz` @",ߐ;.fyVÁu_aX ^Qb-;aX5^-[aOM%]{CGȇ +ro&VN+H=׍nGKeɤ,터>{i&uY@IК15k '@$tt5`,= {2WE}8kٮԽuߴ]%~ {FӧN WUsɑƞCys̏f\Mb-GKȩLP7%uY+;ɍ8-%OSB#{$H).9!w$ bsCfXH(qqFղ=" p#\Vn}PO3^͸#i 5ʯQW5\:NwWIr~88ۍy+B`J^TuB'5ܓ(IIڞ,)R/+i,/V^1MS6y#tuGi3#]&#d[ us}m/[ɦ-!WI "O.t{ŽoW;QQ&kLŠcxl@qoʿl䟗UypW':Qli$Im]w˝AWjg9{ ׋F9ӆȝ6EcPܥ@Gii"'OoEp{es6Oo4l.b~լ'٬yH/쨶N,mYxMΪy,[ @SSVL+~]=Z [V/Y"SS|OdmIr]Hdgm5${@6\@7שd]hIy>#|BFCcʒ`BfY5ӫqo<d|׬)np ;AO>/wx>0Lrw:*~@tB`v{opܻ$w8Fn:QMᥗ8n*!]A.2 KSww=/6/-2رA@KzնSۻ~]o6E /p8Ofaξ{". \:ċퟗ_vۤDhJǍtsvvwCFύrb"}M%uK[+O" jk eb i&}'Q`[i}$:y~6[fQDHA!@N6O>~럮I[J:E_@/݅v}1Wk_o\8@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ $?,IENDB`ic11PNG  IHDR szzsRGBIDATX V[l=s]{u$CbJilH4(4Q(/V-*@D()R>UT5\DTEHTj iriKBp 1;eg%Ά8K?9_wO#@\նu< fIsu]L{g7^ڶm[b(Wg:9YE$8-07G'6o<3y /o?r:\7QPu\x3B- "<*UO ߤ3U~[-^ <3d9 ,v4XW 9t OUHO5aU)My%>'x.>2j:lۆGWhVfBL]L=[؜H/۰Ԗ851n;7fvMGOg^ֶNM+ % .Y=T܅٠B}ir4&i ɼtㆭ[ 璠uSW~{<^k1r1ڦ:nV>Q HY331Yq'М"ݯ&[,1E; d5q}/L MӸ^ c#*ZƾUlYf-V| ȑ9`U$^~"~A{%Q*=ɮŕ7K`LIJBYзHaC|oCv]Վ/'h(D8E ']B 0亥"~ASC,݂bê4x~O0\R?F/ଲ$ UbEfώX [o0nkQ9o0_7.UoM3h$>Fdb3%! <՛S,L#Zh y50kJ@ z\E 9 )E^EH,㹢`B=]LOgK|D+5I HiD!PըnPaml;אXBsy@@ AI|%)7[jz*8ࢊ:k&)$iZr|[\8.ۮ _5cׄ-d$jLD/%aivY$$p qpg=]ph4]н2*+#ݐ?-$Ripg Ȃzj {zCjASMFv`Ftk,!DDu,(f3 8!pgǯv|+ݗ+z2LR;`LiJ䷘[o$h:I}V }y~ ƦQ+՚Sp >39/k#;-?xg'~ǿx))DNIENDB`info bplist00X$versionX$objectsY$archiverT$topU$null WNS.keysZNS.objectsV$class TnameTiconZ$classnameX$classes\NSDictionaryXNSObject_NSKeyedArchiverTroot#-27=CJR]dfhjlnsx}Quaternion-0.0.9.4c/icons/quaternion.ico000066400000000000000000002235361347141001300201530ustar00rootroot00000000000000(Vh~  00@@(26(sXE=6/|)x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"z'JkBx"ttttttttttttttttttttttttttttttttttttttttttttttttttttttttHMz'ttttttuy~ɀ̂υфӆӆӆӆӆӆԇԇԇԇԇԇԇԇԇԇԇՇՇՇՇՇՇՇՇՆՆԆՇՇՇՇՇՇՇՇՇՇևևևևևևևԆ~z']~-vvuuvɀ؉щ&Jx#x!w w؊)¬By$y#y#~"֊""!! *O|({&z%ʄ$$%$#"!! *q~-|(}(ˆ(''&%%$#"!!!*³=~+}*ˇ**)))'&%%$#""! *¤}1-,ޓ,,+*)))'&%%$$"!! )Z0/֏//.-,+*)))(&%%%$#!! )B00000/.-,+*)))(&&&%$#"! &Ebp~ņņ{kW;"(`)A2̋221100/.-,+*))))'&&&OƒܵΚe1!|)>4֒44321100/.-,+*)*))YҠy,ӡК)²;6ܖ6654321100/.-,++3~էăʍ)¸A8887654321100/..2Ä`eث)F999887654321100/j֥yX8$ 1HgʑƇN#)ÏL;;;:98876643211B۷{9'%$#"!! 8خ:,)Ûd<ޚ==<;:988766432bɓA*))'&%$#"!"! ($,;)«?ژ=>>=<;:9887665njǎ6,+**)('&%$##""! ۴4#N)DΒ@@?>>=<;:98877֥ذA/.-,+)))('&%%$#""!ǎL֪e)ÕSǎAA@@?>>=<;:98:޶m100/.-+*)))(''&%$#"hmȌÃ)ĨÍBBBA@@?>>=<;::ܰD22110/-,+*))))('&%$K˔nҡ)ďIܜDDCBA@@?>>=<;֤ٲ:532211/.-,+*)*))('&7ݻ!I )ĝh˓FFEDCBA@@?>>=Ƃԧ876542200/.-,++**))(,, ,9)¶ďHHGFEDCBA@@?>cԩ88776541100/.--++**))թ."! /&)Ɲf֛IHHGFEDCBA@@D޾<988776431100//.-,+**…߿`&$#""! 0ў)¹ŒIJIHHGFEDCBA@צB;:9887654321010/.-,+bʓ>('&%$#""! D[)ĠjٜJJJIHHGFEDCBeX=<;:9876654322110/.-.a.*))('&%$#""! m%)ǓNLKJJIHHGFEDCÈ?>=<;98876654432110/.-,+**))('&%$#""! ֫Nj)ĨٞMMLKJJIHHGFEz@??>=;:98876665432110/.-,+**))('&%$#""! -1)ǚ\NNMLKJJIHHGFbA@??=<;:98877765432110/.-,+**))('%%$#""! r͗)·қQPONMLKJJIHHpܵCCA@>>=<;:98887765432110/.-,+**))('&$$#""! -)ƥvQPPONMLKJJIH\DCB@?>>=<;::9887765432110/.-,+**))('&%$#""! h)ɜ^RQPPONMLKJJSFECAA@?>>=<<;:9887765432110/.-,+**))('&%$#!""  )ءUSRQPPONMLKJ͏vGECBAA@?>>>>=;:9887765432110/.-,+**))('&%$#"!" ÅH)ůTTSRQPPONMLKIGEDCBAA@@?@?>=<:9887765432110/.-,+**))('&%$#"""!1ɑ)ǣsVUTSRQPPONMO۱IGFEDCBAAAA@@?>=<;9887765432110/.-,+**))('&%$#"""!)ТdVVUTSRQPPONvHHGFEDCBBCBA@@?>=<;:987765432110/.-,+**))('&%$#"""!-)ݧ\WVVUTSRQPPOҜTIHHGFEDCDCCBA@@?>=<;:987765432110/.-,+**))('&%$#"""! Hb)XWWVVUTSRQPPϓSJJIHHGFEEDDCCBA@@?>=<;:988765432110/.-,+**))('&%$#"""! !nj)ijYXWWVVUTSRQPԠvNMLKJJIHHGGFEDCCCBA@@?>=<;:988875432110/.-,+**))('&%$#"""! ڳ)ǬZYXWWVVUTSRQPPONNLKJJIHHGGFEDCBCBA@@?>=<;:988876432110/.-,+**))('&%$#"""! ԩ)ή[ZYXWWVVUTSRQPPONNLKJKJIHGGFEDCBBAA@@?>=<;:988876532110/.-,+**))('&%$#"""! Ƌ)ױ\[ZYXWWVVUTSRQPPONNMLKKJIHGGFEDCBBA@@@?>=<;:988876543110/.-,+**))('&%$#"""! z )ڲy]\[ZYXWWVVUTSRQPPONNNLKKJIHGGFEDCBBA@@@?>=<;:988876543210/..,+**))('&%$#"""! m&)x^]\[ZYXWWVVUTSRQPPOOONLKKJIHGGFEDCBBA@@?>>=<;ߚ:ߚ988876543210/..,+**))('&%$#"""! f+)x_^]\[ZYXWWVVUTSRQPQPOONMKKJIHGGFEDCBBA@@?>==<;ߚ:ߚ9ߙ88876ߘ5432110..,+**))('&%$#"""!k,/|__^]\[ZYXWWVVUTSRSRQPOONMKKJIHGGFEDCBBA@@?>=<<;:ߚ9ߙ8ߙ8876ߘ5ߘ432110ߕ/.-+**))('&%$#"""r"4漂`__^]\[ZYXWWVVUTUTSRQPOONNKKJIHGGFEDCBBA@@?>=<;::9ߙ8ߙ8ߙ8ߘ76ߘ5ߘ4ߗ3ߗ2110ߕ/ߕ/-+**))('&%$#""†>㼆a`__^]\[ZYXWWVVVUUTSRQPOONNMLJIHGGFEDCBBA@@?>=<;:ߙ998ߙ8ߙ8ߘ7ߘ6ߘ5ߘ4ߗ3ߗ2ߖ110ߕ/ߕ/ߕ.-**))ߓ('&%$#"͜Gྍba`__^]\[ZYXWWWWVUUTSRQPOONNMLKIHGGFEDCBBA@@?>=<;:ߙ9ߙ8888ߘ7ߘ6ߗ5ߘ4ߗ3ߗ2ߖ1ߖ10ߕ/ߕ/ߕ.ߔ-+*))ߓ(ߒ'ߒ&%$#޼Sbba`__^]\[ZYjԮǕzbUTSRQPOONNMLKߠJHGGFEߟDCBBA@@?>=<;:ߙ9ߙ8ߘ8ߘ787ߘ6ߗ5ޗ4ޖ3ߗ2ߖ1ߖ1ߕ0ޕ/ߕ/ߕ.ߔ-ߓ+ߓ+))ߓ(ߒ'ߒ&ߒ%$ҥ _fbba`__^]\[Z[ЧÍsYOONNMLKߠJߠIߠHGFEߟDߞCߞBBA@@ߜ?>=<;:ߙ9ߙ8ߘ8ߘ7ߘ776ߗ5ޗ4ޖ3ޖ2ߖ1ߖ1ߕ0ޕ/ޕ/ߕ.ߔ-ߔ,ߓ+ߓ*)ߓ(ߒ'ߒ&7zߏ! uncbba`__^]\[ZӭRONNMLKߠJߠIߠHߟGFEߟDߞCߞBߞBA@@ߜ?ߛ>=<;:ߙ9ߙ8ߘ8ߘ7ߘ7ߗ66ߗ5ߗ4ޖ3ޖ2Iߖ1ߕ0ޕ/ޕ/ޕ.ߔ-ߔ,ߓ+ߓ*ߒ*ߓ)ߒ'qL"ߏ!ߏ ߍ۹xdcbba`__^]\[߸`ONNMLKJߠIߠHߟGߟGEߟDߞCߞBߞBߝA@@ߜ?ߛ>ߛ=<;:ߙ9ߙ8ߘ8ߘ7ߘ7ߗ6ߗ5ߗ5ߗ4ߖ3z]ޕ1ޕ0ޕ/ޕ/ޕ.ޔ-ޔ,ߓ+ߓ*ߒ*ޒ)լ&ߐ""ߏ!ߏ ߎ)һÀedcbba`__^]\ЗzOߢNNMLKߠJIHߟGߟGߞFߞEߞCߞBߞBߝAߝ@ߜ@ߜ?ߛ>ߛ=ߚ<ߚ;::ߙ8ߘ8ߘ7ߘ7ߗ6ߗ5ߖ4ޖ3ȕ͞ޕ1ޕ1ޕ0ޔ/ޕ/ޕ.ޔ-ޔ,ޓ+ߓ*-ٴߑ#ߐ"ߐ"ߐ"ߏ! 7ȽŎfedcbba`__^]gҫ΢PߢNߢNߡMLKߠJߟIߟHGߟGߞFߞEޝDߞBߞBߝAߝ@ߜ@ߜ?ߛ>ߛ=ߚ<ߚ;ߚ::ߚ8ߘ8ߘ7ߘ7ߗ6ߗ5ߖ4֯ߖ2ߕ1ߕ1ޕ0ޔ/ޔ/ޕ.ޔ-ޔ,ޓ+lvߑ%ߑ#ߐ"ߐ"" Měnfedcbba`__^][ߦWWWe~ǖԯ[ߢNߢNߡMߡLKߠJߟIߟHߞGGߞFߞEޝDޝCߞBߝAߝ@ߜ@ߜ?ޛ>ߛ=ߚ<ߚ;ߚ:ߚ:ߚ8ߙ8ߘ7ߘ7ߗ6ߗ5Tޕ1ߕ1ߕ1ߕ0ޔ/ޔ/ݔ.ݓ-ޔ,޾2ޑ&ސ%ސ$"!! n«{gfedcbba`__^ԟߧYߦWߦVWߦVߦUߥUߥTߤSߤSf輁OߢNߢNߡMߡLߠKߠJߟIߟHߞGߞGFߞEޝDޝCޝCߝAߝ@ߜ@ߜ?ޛ>ޛ=ޚ<ߚ;ߚ:ߚ:ޙ9ޘ8ߙ7ߘ7ߗ6ɖޕ2ޕ1ޕ1ޔ0ߕ0ޔ/ޔ/ݔ.Mױݑ(ޑ'ޑ&$"!!! +ݹȉggfedcbba`__mߨZߧYߦWߦVߦVߦVߦUߥUߥTߤSߤRߣQߣPߢOߢOߡNߢNߡMߡLߠKߠJߟIߟHߞGߞGߝFߝEߝDޝCޝCޜBޜAߜ@ߜ?ޛ>ޛ=ޚ<ޚ;ߚ:ߚ:ޙ9ޘ8ޘ7:ޖ3ޕ2ޕ1ޕ1ޔ0ޔ/ޔ/ޔ/ծ[ޒ)ݑ(&$#"!!! @μʕmggfedcbba`__zߧ[ZߧXߦVߦVߥVޥUߥUߥTߤSߤRޣQߣPߢOߢOߢNߡNߡMߡLߠKߠJߟIޟHߞGߞGߝFߝEޜDߝCޝCޜBޜAޛ@ߜ?ޛ>ޛ=ޚ<ޚ;ޚ:ߚ:ޙ9ޘ8QLޖ3ޕ2ޕ1ޕ1ޔ0ޔ/Qݒ*ޒ*(&%$#"!!! ZÞ~hggfedcbba`_z\ߧ[ߧZYߦVߦVߥVޥUޤTߥTߤSߤRޣQޣPߢOߢOߢNߢNޡMߡLߠKߠJߟIޟHޞGߞGߝFߝEޜDޜCߝCߜBޜAޛ@ޛ@ޛ>ޛ=ޚ<ޚ;ޚ:ޚ:ޙ9lŎߗ4ޖ3ޕ2ޕ1ޕ1ޔ0kݒ+)('&%$#"!!0v̐ihggfedcbba`_迈\ߧ[ߧZߦYWߦVߥVޥUޤTޤSߤSߤRޣQޣPޢOߢOߢNߢNޡMޡLߠKߠJߟIޟHޞGޞGޝFߝEޜDޜCޜCޜBߜAޛ@ޛ@ޛ?ݚ>ޚ<ޚ;ޚ:ޚ:ޗ5ޖ4ޗ3ޕ2ޕ1|ݒ-+*)('&%$#"!Lӻɖuihggfedcbba`ǃa\ߧ[ߧZߦYߦXߥWߥVޥUޤTޤSޤRޣQޣQޣPޢOޢOޢNߢNޡMޡLޠKޟJߟIޟHޞGޞGޝFޝEޜDޜCޜCޜBޛAߛ@ޛ@ޛ?ݚ>ݚ=ޚ;ޚ:ɕHޗ5ޖ4ݖ3JZ-,*))('&%$#&dãʉjihgggedcbba`ٸߧ\ߧ\ߧ[ߧZߦYߦXߥWߥVߥUޤTޤSޤRޣQޣPޣPޢOޢOޢNޢNޡMޡLޠKޠJޟIޟHޞGޞGޝFޝEޜDޜCޜCޜBޛAޛ@ߛ@ޛ?ݚ>ݚ=ݙݚ=ݙ=ݘ7ݗ7ٵ;/--,+*))('&)`νŜʊkjihgggfdcbbaԠwߧ\ߧ\ߧ[ߧZަYߦXߥWߥVޤUޤUߤTߤRޣQޣPݢOݢOݢOޢNޢNޡMݡLݠKޠJޟIޟHݞGݝGޝFޝEޜDݜCݜCݜBޛAޛ@ޚ@ޚ?ݙ>ޚ=ӪߞDϢ]0/.--,+*))('EvΗukjihgggfdcbbamާ\ާ\ߧ[ߧZަYަXޥWߥVޤUޤUޣTޣSߣRޣPݢOݢOݢOݡNޢNޡMݡLݠKݠJޟIޟHݞGݞGݝFޝEޜDݜCݜCݜBݛAޛ@ޚ@ޚ?ݙ>l100/.--,+*))*bмś̎lkjihgggfdcbbjjާ\ާ\ަ[ߧZަYަXޥWޥVޤUޤUޣTޣSޢRޣQݢOݢOݢOݡNݡMޡMݡLݠKݠJݟIޟHݞGݞGݝFݝEޜDݜCݜCݜBݛAݛ@ޚ@ޚ?ݚ@ԫ32100/.--,+*)Ksї~lkjihgggfecbbuqާ\ާ\ަ[ަZަYަXޥWޥVޤUޤUޣTޣSޢRޢQޢPޢOݢOݡNݡMݠLݡLݠKݠJݟIݟHݞGݞGݞGݝEݜDݛCݜCݜBݛAݛ@ݚ@ݚ?׳9432100/.--,+6hҼŝϖqlkjihgggfecbbȅާ\ާ\ަ[ަZޥYݥXޥWޥVޤUޤUݣTޣSޢRޢQݡPݡOޢOݡNݡMݠLݠKܟJݠJݟIݟHݞGݞGݞGݝEݜDݜCݛCݜBݛAݛ@ݚ@v޿;65432100/.---\x͗ʉmlkjihgggfedbb͐͢ݧ\ާ\ާ[ަZޥYݥXݤWޥVޤUޤUݣTݣSޢRޢQݡPݡOݡOޡNݡMݠLݠKܟJܟIݟIݟHݞGݞGܞGݝEݜDݜCݜCܜBݛAݛ@ޝEܺ<8765432100/.-GkɾªҚ~mlkjihgggfedbbɆoݧ\ާ[ާZަYݥXݤWݤVޤUޤUݣTݣSݢRޢQݡPݡOݡOݠNޡNޠLݠKܟJܟIܞHݟHݞGݞGܞGܝFݜDݜCݜCܜBܛAܚ@ݻ٫<988765432100/9i޻șИumlkjihgggfedbb}ӭ߫cݧ[ާZަYݦXݤWݤVݣUݣUݣTݣSݢRݢQݡPݡOݡOݠNݠNݟMݠLܟJܟIܞHܞGܝGݞGܞGܝFܜDܜCݜCܜB@~{;:9988765432103cr¿ΗΔpmlkjihgggfedcbsЧ߬dݧZݦYݦXݥWݤVݣUݣUݢTݣSݢRݢQݡPܡOݡOݠNݠNݟMݟLݟKܟIܞHܞGܝGܝGܞGܝFܜDܜCߞB@IW=<;:99887654321Zpνљˌnmlkjihgggfedcbc٪ݾ异ݧZݦXݥWݥVݤUݣUݢTܢSݢRݢQݡPܡOܡOݠNݠNݟMݟLܟKݟJܞHܞGܝGܝGܝFܝFߞEAA@@?>=<;:998876543NnۼÞљʊnmlkjihgggfedcbaŀƖsݥXݤUݤUݢTܢSܢRݢQݡPܡOܡOݡQg۽ӭݠMܞIݞHܝGܝGߞGDCAA@@?>=<;:9988765Jp~əљɆnmlkjihgggfedcbabИٸձڹ۽^ܞIܝHޞGFEDCBA@@?>=<;:99887IpxĿ˙љDŽnmlkjihgggfedcba`c֣}ߠIGFFEDCBA@@?>=<;:998Hqtɾ̗љɇnmlkjihgggfedcba`__ɇUHGFFEDCBA@@?>=<;:9Mrv˾͘љˊomlkjihgggfedcba`__^hΒˌUJIHHGFFEDCBA@@?>=<;TtuϾ̘љ̎pmlkjihgggfedcba`__^]\[jɈӝܲ޶բƂbNNMLKJIHHGFFEDCBA@@?>>]uxξʗљϔxmlkjihgggfedcba`__^]\[ZZYXWWVUTSRRQPONNMLKJIHHGFFEDCBA@@EivzʾəљљƂmlkjihgggfedcba`__^]\[ZZYXWWVUTSRRQPONNMLKJIHHGFFEDCBARux}ţњљ̎slkjihgggfedcba`__^]\[ZZYXWWVUTSRRQPONNMLKJIHHGFFEDFdzy迄ÿ°Ιљјȅnkjihgggfedcba`__^]\[ZZYXWWVUTSRRQPONNMLKJIHHGFFYv{|پȘҚљΔ{ljihgggfedcba`__^]\[ZZYXWWVUTSRRQPPNNMLKJIHHSq}|˿͖љИΒ~kihgggfedcba`__^]\[ZZYXWWVUTSRRQPPNNMLKJVn~}~޾ƝЙИЗΒ~mhgggfedcba`__^]\[ZZYXWWVUTSRRQPPONN^vŀ~ƿƛϖЗЗЖʊyiggfedcba`___]\[ZZYXWWVUTSRRQQ\l~ƂƁƀśΖЗЖϖΓȅynfedcba`___]\[ZZYXWWVUTXerDŽDŽǃǂʿåȘϗϖϕϔΔ͐DŽ|tlea`___]\[ZZ[`gnuƀɇȆȅȅȅÅĜɔΔϔΔΔΓΓΒ͑̎ʊɇȄƂƁǂǃȅɇʊʊʊʉɉɈɇɆʼnÙȒ˒ϔΓΒ͑͑͐̏̏̎̎̎ˌˌʋʋʊɉƊÒ ÙĘƕœƒƐĒēԘ???( ȥxՙ@ٍ&ڊۊ܋܌݌܎!۞I,ZȍɏN:KSEթq1͘P+ح(аFجL29N*ըfdΕ?93,&jV_[NGA:4,&7„gjǔgG@ߙ9ޗ4ߖ1E})wh迆RߞGߝ@ߚ:tƐDH۾gէ迈ޤUޢNݞGޛAܻϝ,׵~g֨ش较|_d:WhjʊDž[Geǿsmt۽( @<<tFŇ3ʅ'̂̂͂́͂͂͂͂͂͂͂ʈ/™aՊ' !¬Ԑ4+)%! -2, Ŧ}71//{۴Eʏ ñ>86\D##߽#qE H?:730,)&"}!hVSqQJGCA>:830,)&" I!eYVSPNJGC@>ߚ:840,)&"8"i]YVTQNJGC@=:ߙ8ߘ41ߕ.)&C-s`]НҪXKGߞC@=ߙ9ߘ7ޗ4:ޕ.ߓ*lߎ =轂c`zΣٷص]KGޝDߝ@ߛ=ߚ:ߘ7ˆߕ1ݔ.ٴ˚"XٿgcașߦVߥUޣQߢNߠKޞGޜDޜAޛ=ޚ:DPF%!׶tgcϔkߦXߤTޣPޢNޠKޞGޜDޛAݚ>ČТ,)4ƿ“kgc߼iަXޤUޢQݡNݠKݞGݜDݛAƒ7/,ujgeĐݦZޤUݢQݠNܟJݞGܜDYݶ>62Lǿªzjgd͒ڸ̞əӬϥJC@<9GԼâ~jgdbDŽܱݴʋRFC@Rټëȇogd`]ZWSPMIKgӾŜƄre`]ZWT]j羆ĢŒʼnĂ„‹ſ?(0`ZZxQ9|){#vvvvvvvvvvvvvvvvv@C~ ҅!>Ї&%"!"c͉,+)'%#!!ÑKߕ00-+)(&MzўخٯМyCD֦$!ƑJ5410-5ȏ+3R!ƕS:8641w̗_3!!<{(w!Šl>=:88զ{.*'%#"̖Ң ޹͕"²DA?=;޵A1.+)'&q"ϛ۴"џYFCA?қ߿:420.+*Uԩ3~"įIHFCc?97411//r5&#"ΚÂ"զfLJHF]>:86531/,*(&#"$/!½ROLJiܵB?=:98531/,*(&#"dǍ!ɰSPOLܱfCA?>=98531/,*(&#" "װyUSPOHFCBA?=:8531/,*(&#"Λ>"lWUSSИVJHGECA?=:8531/,*(&#" el"gYWUSPOLKIGEBA?=:8641/,*(&$" K„"f\YWUSPONKIGEB@>=ߚ:8641/,*)&$"?Ȑ#k^\YWUTSPNKIGEB@><:ߙ86ߘ410-*)&$L…*p`^\YǍ鿅jTPNLIGEB@><ߙ98ߘ6ޗ4ߗ2ߕ0ߕ.+)ߒ&io6|b`^\ʛNLߠJߟGEߞB@ߛ><ߙ9ߘ8ߗ5ߗ4Fߕ0ޕ.ߓ+ߒ*ΝCߏ J㾋eb`^ԠWLߠJGߞFߞBߝ@ߛ>ߚ<ߚ9ߘ8ߗ6ŏ@ޕ0ޕ.ޔ-ߑ$" ggeb`oߧXWߦVhhߢNߡLߠJߞGFޝCߝ@ޛ>ޚ<ߚ:ޘ8֯}ޕ1ߕ0jɔ&#!ָxgeb`}YߦVޤTߤSޣPߢNޠLߠJޞGߝFޜCޜAޛ@ޚ<ޚ:ޖ3ߗ5@(%#3ǾÌigeb{_ߦYߥWޤTޤRޣPޢNޡLޠJޞGޝFޜCޛAߛ@ݚ=ߜ>͟ȕ-)(%auigeb۰ߧ\ަYߥWޤUߤRݢOޢNݡLޠJݞGޝFݜCޛAޚ@‹޿5.,)2ԻƎligedںߨ_ޥYޥWޤUޣSޢPݡNݠKݠJݞGݝFݛCݛAQH20.,a±ƀkigeitަYݤWޤUݣSݡPݠNݠKܟIݞGܝFݜCܛAM7520FϽŜykigef߷ݿ|ݥXݣUݣSݡPݠNݟLܠKܝGܝFBfC;975>ẆȔvkigebȄۻxGDA@>;9@vʔxkigeb`{ܲ߸~HFDA@>FvĿɖƀkigeb`_\Z]`SQOMJHFDAV}Ŀś̏sigeb`_\ZWVSQOMJHNl㿍°ʕɈtgeb`_\ZWVSQOYmϾȖ͐Ƃvlda^^ahq}‰äŘǓɐʏʎȌŎ?????????????????????????(@xxaG2}+x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"x"S{AwxȀх։ڊ݋܋܋݌݌݌ތތތߍߍߍߍߍφ!g~,~!׉ #{0˅&%%#!!$GƄ+*)(&%$! #®9ڒ0/-+))'%$" %?JJ8% Y#©È6210/-+*)-lٰӤ_"Ӡʍ#ªȌ:75310/->ӣÁΕ#ʎ>:875314ǍذwN' 1[8e۳#ǑG><:876C|2)'%#"!׬PM%$ƘX@?><:8V˖6.+))'&$"ąt80$éCB@?><:9875310.,*)(&#"!ӥ#VQPNLJܲFCA?>=;9875310.,*)(&$"!q%"ijUSQPNLʒGDBA@@><9875310.,*)(&$""#e"ͭVUSQPb\HFDBCA@><:875310.,*)(&$""ݻΛ#ذvWVUSQ|ب_JIHFEDCA@><:885310.,*)(&$"" #lYWVUSQPNLJIHGECBA@><:886310.,*)(&$"" e#j[YWVUSQPNLKJHGECB@@><:886410.,*)(&$"" O#k][YWVUSQPONKJHGECB@?><ߚ:886421.,*)(&$""I"$n_][YWVUTSQONKJHGECB@?=;:ߙ8ߙ86ߘ421ߕ/,*)(&$"Y+r`_][YWVVUSQONLJHGECB@?=;ߙ98ߙ8ߘ6ߘ4ߗ2ߖ1ߕ/ߕ.*)ߓ(&$j5}b`_][^ٷ˞龂hRNMKߠIGEߞCB@?=;ߙ9ߘ886ޗ4ߗ2ߖ1ޕ/ߕ.ߓ+*ߓ(ߒ&˙ F侉cb`_][tNMKߠIߟGEߞCߞB@ߜ?ߛ=;ߙ9ߘ8ߘ7ߗ5ߗ4J;ޕ/ޕ.ߔ,ߓ*ޒ)Ǐ"ߏ ]ecb`_]ʚߢNMKߟIߟGߞFߞCߞBߝ@ߜ?ߛ=ߚ;ߚ9ߘ8ߘ7ߗ5jyޕ1ޔ/ޕ.ޔ,AYߐ""޳ymecb`_ˋiWd{ǔԭߢNߡMߠKߟIߞGFޝDޝCߝ@ߜ?ޛ=ߚ;ߚ:ߙ8ߘ7ܺޕ1ߕ1ޔ/ݔ.ɖޒ*ߑ$!!!ӻ|gecb`bӭߧYߦVߥVߥUߤSߣQߢOߢNߡMߠKߟIߞGߝFߝDޝCޜAߜ?ޛ=ޚ;ߚ:ޘ8Ρߚ:ޕ1ޔ0ߙ8čޒ)%#!!7þďhgecb`٪jߧZWߥVޤTߤSޣQޢOߢNޡMߠKߟIޞGߝFޜDޜCޜAޛ@ޛ=ޚ;ޚ:ܼrޗ3ޕ1ѧ;)'%#!buhgecbiߧ\ߧZߥWߥVޤTޤRޣQޢOޢNޡMޠKߟIޞGޝFޜDޜCޛAߛ@ݚ>ݙ<ݽزޗ5e,*)'%,غƉjhgecbϔɚߧ\ߧZߦXߥVޤUޤRޣPݢOޢNޡMޠKޟIݞGޝFޜDݜCޛAޚ@ޚ>龃Y1-,*)'Q¿àvjhgfcb羈ާ\ߧZަXߥVޤUޣSޣPݢOݡNޡMݠKݟIݞGݝFޜDݜCݛAޚ@ߟJE0/-,*2ߺɎmjhgfcgƔާ\ަZަXޥVޤUޣSޢQޢOݡNݠLݠKݟIݞGݝFݜDݜCݛAݚ@\420/-,]ſ¦DŽljhgfcnضcާZݥXݤVޤUݣSޢQݡOݠNݠLܟJܞHݞGܞGݜDݜCܚAÌ[86420/Fڼȗyljhgfdiǖީ^ݦXݤVݣUݣSݢQݡOݠNݟMݟJܞHܝGܞGܜDݜCRL:98642;v͓tljhgfdcբִ澆ߩ_ݣUܢSݢQܡOܡPj绂ݞJܝGܞGCA@><:9869jǿ͓sljhgfdbnߥUGECA@><:9;gоª͔uljhgfdb`iբڮiHFECA@>asBIT|d pHYs$$P$tEXtSoftwarewww.inkscape.org< IDATx}yŕy]շԭ[B^$^l{ǻ=3?goݱo33`؀l!07F-HnWuݙoȌ̈VuW~*Uvdߋ/^Df-B -B -B -B -B -B -B -k ծ\P/_lZa0&R -3p0yNpl=R_6%^Sp[=P2o: #Dp_ p&s `٢(]ژ=FMs\X6&tf"W`s!ù{1#4F[xނͷYo\{68!`+'MڔA %.;.DB09aB`Sc@n3Τ.J6W]YֱWY'4 'lޒ7W:M![!*@vII _PzC&C:c 8 W.ٟK۟96=8NMr&ަ \0^"L)9!_BL3Н`0"['G_Z6of}' F,N8Rtx@:ِǑ;@! ;ͩs?q+V|WoJ%~"?ZtG<狯cIqȿcock "p+[&?k375{wG;3qB"0! eG|^m1%i f9ؓ-9v릶'i 6v'Gy`H8Tpxyay$g4 b }_krǍ|1K] kf" ey'"\cy)wwbeډ.A VXX"$D$_η+pR1T-6~侦u, S5e L&ySC:F%~.k{I]:cjyGaQwLExe*boq%i$üC f(#E¾I}Y#E XߦsLJ^|\^DqI=fBҮ Lz4֣n g#Es#Ovذ|"_@ò'*/M?\>u,<a(kvL?[EtۯԚ_ '<~7,c"_\&U]@lPsYͯymSdYMX;R .4N΀tymNqCx`<̠~m@v qS{48DcSڅ|eb6D1=Qnbh0gU&$l-Yȕ9J6G$tX1U{/oHv4P[H3"_;prαobr?؜90E!p7M,7N4?3%ϖ @azϚ|{ae E>˒_m׋9On[?ψ89^WxN펶&K=Ejo.ߝT8^gK\4<كdW*8/OЛ*m_7H[YEO"QS4c]7#/لvb?~ w@%|L>1~pV6%a>+ qM1#^s%D4T̑{J3#Vvi:j"~sߛˉ9 0?!g!b0{/xzn2ܸ.:'Pܣk?҅BF p[hc|5};+ms{f yS(G Tĥ|D^)$81KS{ o9lbu/-hװUr%u?"svLL9k00|:5|xm5E7>4dKEϪp)|?>>{A|xI)p])T10o `N`%]I=V䋆$>rYpÃ{䦕; |Uݻ<.yƔ + dG/k/Ou:tp8GLuᄏpm]?c̉$lEy1db4pï0ܴBͣz^Eoa \6PeK7Y8`-MM'.Ick. a .:՗ʡ|}3M, 9xlܴeٲjjG^p8?K0 3hh|};%3q\{P{s&+d3M'm?^xA%]HuRHuL/1;뗩`So={{p^/q ADo߬ gL4hL BOגwb",,09T"xrQi WoiO@*[^,+|7yO;@10U O,x9K0 uQ'*iCבBtc#N h¿>;D/v)_@h#_6ǽu TGC ~ڲ#WJk^TW(EsNo}wɬi`XFc'WЫ'`9j'pOˣ]Vl, /y\E'|@0u`~lI}١F bڸi=mB.kOX+P粁3fZ4M(xᬐ"vN9& AՎ )}ȈtTCTO|70f_>Hσ؝ÎE%YqlXTI!ǹi4Hƴ7-zo^a(hpo}~ᛗ@ABՇ?(9 m=^,yĻy(ls:q(B 'W-.x 5,GSOi۴Vgb  "MT|r>x@~W6\ P/PI=RДI>8frJ{^ 7D(H{'JJLh̫Tޒ(9sCQɺШMXӟIj?bY@`QՔvlp8[^Wm#xz!%kZ,,)4.i5]1b͌|p|uO<7s]vΘHX6` (ۏD#]6@evX6\_ʭK~K'!ӑx~r$V%S5E4L {]ɮj2ܓ2l\uZ2.m5w;ç'pIpSLuqN 2$N&@Bo-i&]R e2l\цʁ{q@Xq< 4=!L63u}9C4EJ_4򉀵*r_'ŏZx-Ce:@-,F`P6Pߵȇ*8B].`x1/vqe;Ƌ ˒sW; ΁$(ԁ0^⩷))6 6ovXL';fWC%|!|| >(oj a0kaˋ=`. /MJo'8@y}l`_3NаTEOffV4k$>C/:գbiM .C2J=p ;G,#teH'kX➐rV*BcΨ#1Z[r،gdOM9,pp]ܩA'˧z$>V|& Vxe :1 m_]f~ .+0eu4 2up=o:10u?U7hCg1L+/PCHfk "t.(6ǿ ūy=C-C/--eTnHXs8bQY`&IԊ٢qWp9[j\;7@=~5|Q(`b\&vm|_by*כ{_ک cH2~zFchXSxgD'<t#Q==lE[F%T!ȗBK Ehxڗʮ_N*> ʹFk0^xQ@IM5KߖFѰx3X B.M1e 3No&c[ 4uf徘h+Cً$DʐwGO5ߏX^T1<ES`=\1逸SM6 c0û{LYAxVKh!Bp^ƽaQBAa6hE>=`9 `Iwf<e"Tk^)-}dĆ^iSCKW눅rӴthTH%/"`s`爪.\dP@NMfP5)8- -ݶ4eҋAOz]}K%K|3J9ŽCNXÓ6S)<9yB`|<<v GXna%$(ĤWya_}Z޽%xj:fc/ȖS{ǚAoh]#@UgI Cy|aEm rHŽx oa  u2PB l`V*E>=hH \ļtm'0 B$ ,<{ˬ6 HNjkH|ycފ'?}NJ`Yh|D\?>y; BzGDƀ?] r@{CHhq 2S}04oؚJϣɂSiq_[;wjy=Ҹ hc\*b]qܴ"1wq|6 3{ȚaPB?A2޺w©! JGv/ 81E ]΀y~zryyBJi70 _by\71U#2Lyh@G\çe,ʣ.5OʳhoX%jo |ؐ91\MIKLU`AL L5*rA*gx˧$1|nCzjwkgdďJ>?~e%[e=|xFmɱ"pH3\a6C8s^ : \ur LwFu\EЕqUXխ?bMNEy6=rsm&9yY_dk?ݠ6zߖ6}C#|'oؚ ;2_4˳L\Wu>u7Vx("4j~Zwp㘳 prygEe?zᅵގOm肮k mP΃)w~8rYu7z[8cewY]Gl i5_7'Srw ~Fyr#%Ҁ;*LJ9zS ~#ewrBj.!䈗8P q)Ĥc+ :#G")PZѝe/8y44̙Ȣ bnqӑ~t]sVU@J1dBZD" M#q oYT bxiQe]Zu?-)?k|xjXʳܰ1F:v%O#/B^O\ ˸JU=I߼ЉTC B;c;7{37gbF:/<Ǐ_PVg6ڣH0#̩o#];t:E8oA\i/pJ'`ט[ ך #wO) VX-߆pɲ8%,2 X 1`8`84JFwܐhg.i۝9og݋ҽ]6|IۢevqsyܵCޏR#M%_fNdUp;z,-*Pռ3nV~q՝2BD3*]OOdԉ9}Lwa2@,Upt=9O1~[z`8oa:g3eQ5!f+Gt+ju)3v3:[9kuex \WL =eD!#J"'oڱ9a!$woA ,LXCis%4ɱoH`;RYNjTIDAT:ftg`ӊN' jk: 0ƣ'Cã+I`418kϬf Ar>emQ)`iެ13ltz xxh_qZ5oZw.To?]Rj/`a8fS胏0f1]}Ӑw: ̈́y}>ǂv3CSB|%I auyxZృwŅ;gݸmʿЍ~k1"0\-"cǰie.O`yMp~e*P g|n^ kUF_[GjU8KAT8 Ș|>Vplg\@&0q΂X^6[O Yx|CF|PG f :n<7.!`qg>EgRXyP$h:0o%<:p/R;BN4Iv|&ΐi-Dp@P{uQ @ߒƭO>KSgyݑIY#V": A>=-ru:~5(uec<@/n8A,N t-"w X%p O}N5~@0{3ϭ6sLv^@Qs׶?/#~2啋kCRC䥯c/&:^;XEz+Pqπ^/G Gܘ#-}JI2N&UǏi}1~DS+ m6q:%U5 8N=~uvlZXGԭg'L}@4[p0hoFI|L1@`gHsσ;6/ץ̶* 8# σ;?GzyATs7=uKv)e)-=ԼZ6Iŏ`ډ(@):jFCHou_B -B -B -B -B -B -B -B ?Sh 3IENDB`Quaternion-0.0.9.4c/icons/quaternion/16-apps-quaternion.png000066400000000000000000000012241347141001300235230ustar00rootroot00000000000000PNG  IHDRasBIT|d pHYs|4ktEXtSoftwarewww.inkscape.org<IDAT8=kAμzJbBbl|AH(/ ""` +?@R b%B 1IsEsX%:O91<#?O:/:u>T%xcB &YpH ~Z~4:LS0>z⪧+P)E̲+KH7+ SEീr)P=>k3GbF$abFIÍkj7X љ@;v6h4S^^ju?H=˛Z ̑ZňBC>GCm@3561un+H#uJ~'6+4[ spv D[Ilh+2j@lf[@6U[:$dK>* D jF25sg\dEj=By y ],?5Aђe!U-afPR._-6`z/:A7q1nowf#?"lIENDB`Quaternion-0.0.9.4c/icons/quaternion/22-apps-quaternion.png000066400000000000000000000020061347141001300235170ustar00rootroot00000000000000PNG  IHDRĴl;sBIT|d pHYsXXtEXtSoftwarewww.inkscape.org<IDAT8KlU7B[)HR,$jD67&jpa0DӅc׺.4QVX ARʹәo:qKJҳ:9痛s/BhX=k3[-{⿆ij>[ADS>,5ï|ܪ醚Ņ+ ٦L?#lM}]p1Db5p*hh=+UciMj̧OExRc=AqAس}S9'gP]>n^*Om3/2r|bU*`P bṔYr 8upTSH=X7 U>7){.&J.6(u)ݭYݾ|Lok*\>Sedyً/'r84^ezLQ ^?x&JJYfs?qqv 9U3m3ԃU !UCC66RԛVbf!2q.4U! dqc.%DbpA }mK_ZԜA@.^ ̗fFWLUaޡz\}e :&&5TDǀ;/^]C_uV7 j̚N ^ǟ@qυ+IENDB`Quaternion-0.0.9.4c/icons/quaternion/32-apps-quaternion.png000066400000000000000000000027711347141001300235310ustar00rootroot00000000000000PNG  IHDR szzsBIT|d pHYs : :dJtEXtSoftwarewww.inkscape.org<vIDATXm\UϹΝٙlun%P( 1M#RZ'~MST M~j"Qm5DD %b/hݝ;s9m]rrd%?P搵3༊"9:׶}okU{ oYϞƍzp8糫 9"~w?c^k6\ljb)lN1kzL#23[{z=4hrWDajC_ #\NS􊘁XoXW[;Sk~<ݹqPس!ĩ3)z m ?zA;^̻[W+}G~l7H|v^iPxK%@{ Ʉ VVr /l YCGߞVazhs9^hjmbђݻ_l[R@KTc=6'sqťx͠6o?X"F8_d aS///)N*겡p5F%3x~b+LԯǛL<w;-PƵf3%t,\\;h;ks>;*!wB_PŧGKxkѹs[ >p5 8W"0R5ahv<© )c^ԡIsxIuWM6-d6Tz V }b(st 8UAqXr19ܺj«2PJ<ⱳH@Nt'ߖe'8^ɹfn(ļ}!Zk3J|_-t'GgX{eءj+*Cm+Dň(ă+]#qIum  ! DDg>zJ7r5IENDB`Quaternion-0.0.9.4c/icons/quaternion/48-apps-quaternion.png000066400000000000000000000051171347141001300235350ustar00rootroot00000000000000PNG  IHDR00WsBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org< IDAThk\eϹҋݖ,k@-X ʭA E!H5Z` "A##bҫV@K+^fwnsfvt|lvvw88 ݻ|xR&Xtd+:&8iE 挑bۋ8+B?=tQWnlJz-?( UF\GHPAƗЪn̅LҞ-.M!*+={ͅ#_@7]%ߐ##x6'K+G ْ"ٖ&gT /~ƾcN`mhOlI ~ZpBƏ._43jy{a힐z}?&g)P֗6g5;\?ǡ [l̇FOӛ}Ҿ˃+{xO0 $, םǛt(hғ0VY=˲l+@2hl3h2mv ,y5@,˩W sள|Nvo6g(Z"X xCΪsX2Oo^j2ў檥Wf^s(W3#/o= }@$cWc:S|Z\\*Q N `MU_(\=6۳,ݸR%`B]P$ҺJAUߑXc&/HjXsa[b)p,%ް?`T+RјDPLSg=ę֩5UȖ,y{QwZ5=\ZOF1 bGXםc<[K~v &h^p_'1M0TD5$;l_g'e|," DX {Cma Z/0ĚHN GL jVy ,h+rV [!`ThE;VGzwC>a~0P@`U *mO1ojnhI#rRq XLnc5Js<6@io^}/ϊ *;>%r:*}֞dKuBk~liKm룅<He|MUoV:Q?\=ОqZF9"& D yc̔{D I8 N4 $@hM|LF߈D^zC~zNe':NUQF2(E#2|ρ{(/=}ʅ_:Ig3zϐq WΩ9i w屎UXkyg_+|+MK+;KcGX]bn$=(I T)դXǭÚ=|>t3MgJBκʮt$Y8#I^vlWE\='IϨoD8(~ֆQٝURZIU̦Լ%d- ͜qX]Ab]-EgZ/KӴyԷMRaq|_ѬL$aJIENDB`Quaternion-0.0.9.4c/icons/quaternion/64-apps-quaternion.png000066400000000000000000000074101347141001300235310ustar00rootroot00000000000000PNG  IHDR@@iqsBIT|d pHYsttfxtEXtSoftwarewww.inkscape.org<IDATxyp]}?.ᄃ'Y%CBS1; -%I` NKeBBBwg%Liô -5CH Y [W/v~},HөΌF{|#czL1=ס>oQlt/\j,P'(C: (@xrC^4OV^c=c&'-SS0eRt4h!ԂzP (Xf=恝{-yNwmBUyp° 5Ot#Hu;~P8 TD O|T5r|+ҿ. 8t0hP4县(-pJ\s2^)w~i(T}ظpVAfͧ>H9E5ժyM|0Xe/74UP|ao]S ϊS'PAU_\59wTƇ/ذbɒ~{ j x|;eugD? g/{YX`玊.!'Z?6үnH P)JI6xnW;G(-gL佡~_^,:ak|o[c[ hiK~({=| ?HbxbĿ%\$O8"}u|R)kB#\#׼up xT%P(IFx2|,NKҁ<5_S0=`2!Jm|mJX+b(~{mN6X1`fx!$O3na#x[1P 5CVx_\x/ +2 o]5s#zX6Wyjk5ikR7' 8qN8g~d7x͏#X27j$;JUo2LC__eX9Ϣ(j/ E-KpZ?OV! ZC6yeJ^ ^ |N", P¥r؛e5IlN׀8R߫aw9k6jJl>zHcp.h\C.g]ᙓHxf #~ts-!6lu~EezlyibUl`EVauY||n-F0߅6rQ`9ÒckE3F`):->.^i\#m6o% 1/aPǻՀp$jV!Pd0Cc LW4) }U\iO gsBZh4=uO\C] 9s2Z (8]=y\?  _| nuD|'J,I|q8+3罼7r[HVP^U왴"vkm1V*CIe2}r|^ZSN\4BI¡Ftg_ьfsaPh}K bf!ަ-pbs<_;L5ȮYhӗa%0X8 [aVqn\d )lVzx@&?bfu\Yyt3]=ےad<"tȈ_M$ 2i1EO>jVeۤqx}?§qCȈR:OT8V{j;K LN:chIWuQfQJv5hXq|M~y(ۨne}u@C0 8sB/=v1[묰YA+8!R1m8>qfm8 rn:*6 7?$ڪChul.iy{TIO69E<͍B-#xdk&YABCH怣esݞL9du%9#o5qu8q,8Z`E`۰}Q-^tOj vUUm-*Gb4Ԣq"gUnq"01 u<jP`N[C^GJa$Qi^wi^m}XGI3[`=իժho(nZ^baOū!@h|c]sFfcFy`=>[Uw|0IC;$>~C^>4ÎZ^F,mѢvoo*˻x99F=,?6LMKD)`ڳ˵{[X/@=MޭE9NTzT"v#'C8⳧ʯt>(@ٕ v{wuW*81sm~٣Y1`v,>AࣕV&heEwBpއ1XѼ? *kǯ/3`rÿi/}- _ckϮjR÷ kV=Z=7-Os݄8'ht !/ܾsl|r#4a~_+FzMO,w~a˿!=hsB=PQm|ffɅ |Et 9@r@^5/a+.>!#o{TiyPȯp\ɂ&nR']`kKm#'l0pY}FN*Pٞ?SS֜ٺܱ&OP }`76h(Y'}'$#W.8! ڜcaC<7;.O$ۣ_v~'hz_?Ȃ'6vt 8\GU/!?DbP tS ́qO>7bŀ͆]3Kq[uUY2kt8٥E5llX6?6[0$Еi,td_ 4v| pz+c&:ٺ˜M͘6 ~.Hcg|$c֗7tu*,öwaIJAY(g3NHP]/C]~zay3c{yZ :#Of(Y^w,czL1=w(UIENDB`Quaternion-0.0.9.4c/icons/quaternion/sc-apps-quaternion.svgz000066400000000000000000000101531347141001300241100ustar00rootroot00000000000000AWquaternion.svg[n#Ir}(_aV1Jv^D%)\,^}"N%2=]S++/'Ndpfno/&88Tdi-uyueHo벼fɺ.Lj)>^}nwbR7+6r16M-w3٤/hl]h}U.6Is/C%11"Fx-Ӈx25u5O5@G oC6h ]~W,Zf6+g?c2fɲ\i?w`mznE5zY^_L Ol?H9K}xx^^L0wQU~[2o߰ j4/w1;xYEmV?2[z5 =,-R5\TU4Ob"dbڷT* uK"1@۶o'le~.Ƽu ٔ}i)^䛼|^,jҽ G .&Hj`L>96,#56j; Mo؛l}6l7Ǖ*??y6fq=8&sJ CU+ ƏwkfwH+bku*?mVzjI?gu57 †Pᰵ\Y[ςos"|KSxU]bPas0#jĂ'0/bLwP"K<\ CK#Ņh1;]"Bc~X6q-O( :i9W}uinۧs8OLt'Lz3s*[σ:{Qg.|= jSD5.y t~NOv Jg{D{# V)7$sPL,m+|O oԡߎkˑY)jvmYbfK.((@̈́z Atz"f!VO +*OШg\-G%ڭ V19c{YoOs0;b`:Q򟶳oFlo#wL' K!*%;T<\-SpUtK2u)ǝ-6Z;B+-Q>:={ɺ_\s^-=NXO"[l~\bb,{-^rx8s ggr$~)4RrIcEg_ *^g2)/C"qI~x%sIeK`HG6zmq>C_% o`D?EiΘ̼羦Fv)P%8,Z#:g=NmyAKO܎n)$s;H~M:Hq֔W{lvͼ`[Iﻅ# %a(pOR_q9~ٞYBVk9IßI^O)K 2mp^eյe^,Gg^U@yuﱦ_~N?,=m^>x_.]Ҋ*w}8h^Oؤ~5ԶJ7 l7^Ɨt,m|/Ͱ*%=~ 7.Y<bӦ]TWUhI!η-qJgxճ6h2GJdkV:Kﭴ]@B 4w G?aA%P.тvP^[F9[lַqq| l"Y5+V~u[_n{! l7aӻ2?Y?Ĩ]UKbsV[oˬm?W wP- ^iTeh=[zapʻȿdq #/_ޕeveMixجke QP$F7?8Bz{U0 ~!=(ˍe6tVv9O)w~ǒ-\BpiyWd=E?,o׀rscSQكR.5 1O 1[#ħ1E{6Xg,Ud`M_@Cə~ʙpL.  +B"[3,Q5h1WT%j+h*f-VY1%)n0ZN#H+b`t00V؅TU@WZȃ tbZ7F#wpK`X':qa)8l 0c"*AB7+Q,-3 E炂Y-qJyE  +b&r70gU6BT b8.BRTS`pߣoBeDGê1X&F/FaKgS%(-P7A^o?+{N x}KTZ@9mU࿁YWކ|X] +:dìc[Zk]<G5[&M(-M7Æ(ɳCQ<u![tI R RFVq (rё!%jFXk >r3_H"bn(htM9y {9>=#g\' 2XRiUf23(L V4GF "r"O"CmP4ѵ.`kD&U Lh*z(tџI $R<@\+j$45н"@:xpB!B^^CVs;܄=DidQEj$)V4GXW@X >Gv=C8U'\e34!FXnATã ZqOJR1•<σq$ Yѱ,YS x#d=T4pXD&%~)$2Q@ExUDAOlOz+ *I TX 2ECD +tफ़@ ?qAp /&_0X\vָгcjit/ DdA ᎀ E֥;gҰ.hc°́V s +S2 "!$c-n*ījI`XFn)4DIʄXG2 j =te4te`NUἤ=\_\h |@F^ 6fCppVzކ3hݛ-~F+YI/YQ5A }9xڪON[yS 3&#G&١:HKs:3;z0bE"g"ϥ_$>oBD9l[m]7fBFQuaternion-0.0.9.4c/icons/quaternion/sources/000077500000000000000000000000001347141001300211275ustar00rootroot00000000000000Quaternion-0.0.9.4c/icons/quaternion/sources/quaternion-green.svg000066400000000000000000000431171347141001300251410ustar00rootroot00000000000000 image/svg+xml Quaternion-0.0.9.4c/icons/quaternion/sources/quaternion-red.svg000066400000000000000000000431151347141001300246110ustar00rootroot00000000000000 image/svg+xml Quaternion-0.0.9.4c/icons/quaternion/sources/quaternion.svg000066400000000000000000000431021347141001300240350ustar00rootroot00000000000000 image/svg+xml Quaternion-0.0.9.4c/icons/scrolldown.svg000066400000000000000000000034161347141001300201720ustar00rootroot00000000000000 icon_newmessages Created with Sketch. Quaternion-0.0.9.4c/lib/000077500000000000000000000000001347141001300147125ustar00rootroot00000000000000Quaternion-0.0.9.4c/linux/000077500000000000000000000000001347141001300153035ustar00rootroot00000000000000Quaternion-0.0.9.4c/linux/com.github.quaternion.appdata.xml000066400000000000000000000035241347141001300236650ustar00rootroot00000000000000 com.github.quaternion.desktop CC-BY-4.0 GPL-3.0+ Quaternion

Qt5-based client for Matrix networks

Quaternion is a cross-platform desktop IM client for the Matrix protocol.

kitsune-ral_AT_users.sf.net InstantMessaging Network quaternion.desktop https://raw.githubusercontent.com/QMatrixClient/Quaternion/master/quaternion.png https://github.com/QMatrixClient/Quaternion https://github.com/QMatrixClient/Quaternion/issues https://github.com/QMatrixClient/Quaternion/blob/master/README.md quaternion QMatrixClient Project intense intense intense Quaternion-0.0.9.4c/linux/com.github.quaternion.desktop000066400000000000000000000003711347141001300231220ustar00rootroot00000000000000[Desktop Entry] Name=Quaternion GenericName=Matrix Client Comment=IM client for the Matrix protocol Comment[de]=IM Client für das Matrix Protokoll Exec=quaternion Terminal=false Icon=quaternion Type=Application Categories=Network;InstantMessaging; Quaternion-0.0.9.4c/qmatrixclient.kdev4000066400000000000000000000000661347141001300177710ustar00rootroot00000000000000[Project] Manager=KDevCMakeManager Name=qmatrixclient Quaternion-0.0.9.4c/quaternion.png000066400000000000000000005225741347141001300170560ustar00rootroot00000000000000PNG  IHDRwjm IDATxy\M{۾ӦTD";#4”uc,`Dv&FAHFB"$Unn̜ߝJJٟg>{v{!!B!P]K+_ saaaakk+##B!BWWWgeuѣG 0x„ =zB!BѣGmyn RQQ!B!ޙK!B!:j p.B!B  $ d0x"Ы_ B!PK$I644p\PHmR!P(\.WBBBZZ < ,B!:G,uuueeeT"++ۙ ;FY!B&11=zHKKSB`khhdee555q8w K1 B!ZxMMM,LRRR))={2L>DHljj-/////mjj/"B!<$I|>o``@ ϧ7GZZRZZ||!B}B@ <|F':pիW_z?Q{,.+++kaahaa!++r!B!E)**=<ɅfQQQrr2%'444o һD={ t@ zjXXQO\{WO>Brl厤!"KFf69M\"m YSS5y Q޽{ĉ%Khhhl޼ޞZBBBZ'Ǐo߾=00dһD۱cGVV۷:1H-/!5oGW!AL YEUmCsΎF]dkSJ.,x[7/_]P+`Hʩj;rc׽@.`0IQ) .ܶmۦMOnhhpߌBb'_jUhhhhh9sX,V888\tiߠA]]=f1''`HHH`C}= R/=($_>x;Qtm?H2-.򻛩`uspd*I7wgⓢZn$IaUv[Y ##XW~~"rU}Bu1 8A$I$8`'OĘ,]BTUUݚ.˟YUU):::''ƦkODzjLL]|;w돯OnJ܈O}~NZDǯ|rB&1YU=3A>@BFFJJJJ4z…bj!<5kll@ &&fmmMM͛7ֶf޼yߧvD튔+/ ^\;I$z<$ XSuAz̢M wJ@@Sɓjg:*w˿}.yyɬ$zKe{]q%#r{M$y8_@04Zɉ\Ko͔" /#UU}l2/F^}VWdeg7@I;c&(jo2O!?ڽ@$IyyzII 2s\랷h1w"/7E/l-# z?QA5# թNщ]r"hBHHJ@xM$p3grI $]=`:3<ͪ'p9- ayֺkX}==5%Yb RP]UCX?TWKc4T׶r@RH Ia_1$#j1$h5S[I07` `)p1= gM=Fxr>}%`8P{O'1r =xE˽@OXSS&B}\W$$$ddd$$$L@ hllxZZZ\.OQBu~\Vg@ 5SVmUJH@1<}">ɐR7e Q}ꖑ !.!A~1T5TYDE}%!|H,U U@k#y<.E'$$VϨ6 ǻ!ސ$Y__/##cjjZUUU]]]^^Y,VRRjjjaB]U2!!ibc&M*3g'F [X^TUq [;I`NZ G7Z ?D-;t 9Ss=|aS'NtD@Y@0dcW UXv]yvo"TĈ|D eU%Hs8wX?FY!QdݫϞ1kJ\OW!sa/OH3~t}}욂Wo}EfS]eыza}Sw_~ڂ>i"Fl߈%!"5O+!?r`qwHsVnoy9W`Ge04 uew' :z4)/^Wf]'ajo&^c,ؼĨ;uB XZX!dBvrs"BYқ!:mP=1Bg܃𴊲sI:"gh>=0 60QC ;BX}sߏjRe\Iy F !c9vg/JE5֒Zcg}|~S󘍋cOHH3L^2SjOn@(ZB{ +.Xk*38ࢅI!}c ;4 S\ZQfV/P;2eǙ?,ao(%`0eO!k=faЌ=TXLq9MAٷ,q1U5aN7!e04p 1Rc0"% 9#Wb2br:&/X0܀It2 i뱳F}MI`JȩtS`肕];JOYVŒS7r6cqvB"/ Buŭ[RQQ122:wXfΜibbnnnIIIׯ_ozɖJ@@@;::B!eX/~}޽7@dddX4##LJ 3f|)oo 6\r̘1SL rvvGd .۶mke$VVVŁB!! &?~ĉﵯԩS ,M?u;$)'R,Yһw)S|FUСC555/^tppTYYlooJI5B!iꂾ .yyyȑ#-3ō1BOOOWWwРA/_ҫ-[feeaffO$+==}ҤI΢a٫V=EHH.**6}}}=z㣭mdd4v'OP{Gm6 4B;飥e``vw.oo'OgDFF BCClv^^ɓ''N{.dee[vX{%++W[[[OOoڴiyyy^|9~G')) ?;;[WWN󋉉/..?\B婮vww/..300ؾ}{RRRlluѣG^:77w޽&L`X˖-ڹsѣ=z$##naaa}]˽C 7ogϞpOnaaq__I&M<LMMZVU3()):thccc@@7 klڴiB9rdF+--ݶm[JJJRRt[]]ߪ^|9$$$//sB!0˗/Ϛ5d9rtU[[rJsΉ*@HHHvvLJ ˖-LHHPQQYfرcTGP{nڴiԦ΂ ^:rHuuuݻ7vvvt4k֬mDDe>} ooV/_\II)..NJJ ƌceeiӦÇ;;;GEE555'%%M4),,666dmmMŮץ</??׮]{v]t)&&fTʤIzr?999T^z͞=СCheegz344Ԧ… o޼n0iҤ˗_tՕW;^xhnnǏ6l؆ LIUNJZI:Tͨ8{l`̙k֬ 2xׯ͝;J 4#466fX|ѣZQF9rf?~-jߌ3"##q)BBVuvcq@AA-**P{\w},Xcc!Viiݻw2w\$;4( _nS1򛙙۷Ot[g;677755BaTTT^D;h7n>|P(p8GLL.99Ҩ:F.^Hű<8p`'?MLL,-- A;<w>| .Peu=z!x{{7[$t]&bkk %%%| bbbѿǍGum{cbb,--wsYYYVVVgʭjvҥ=zP!FFFǏv^LLu =3\rˋ. ;;; Nt3gիW !B,$=ݧOC7=-[PPj:gxD}}}o,:IBBX9}ڵcƌYlYpp˗/;YL&꫹qFqq1dP{n@EPwqqqTuM@@ꌹs>}RRRa[^^h~CCC+*C[`0 /fCCCY@ U0L{>}ZWW700^˗-_~ q;sƶY˒֭ѣGuttƍZ[[i''#-=uT(tuu( ==fwB!ZԈ7ndgg7|#~$Ve X,*~kεoE k?YY}-]СCG9p.Q\ 6DEE5՜T)͚5˫㘘$''O0TVVv II? -[`SN]O,eOySfKRR{9`0\]]]]]gΜhhh( {O?ΜլY%oillƍkرɓ(Ç7K V[hhho:f?O0LB!SusNѶfxxxBBBVVx --/^<xhbvvP(^f_G/^tR___tg޼y))) RVVn55MSxĉ6 ILHHqF~..1eʔ]vO#Gn]RRBS<bikkSJPݽ{[ Au(UTT["uZQׯ_LӋ'TǔTSD5[Rٳg(B!P}ׯϜ9>~Q".] G11ٳfJJJΑ.) IDATeeetݻ`Ĉ #####Ix,ZNtjѵ p:\Į]JKKEg%5ڻwMXSSBu[ihhXZZ&$$<|D1իWz^a̘1 p˗/ϟ?BՑ%z+#""JKK4444KURF󖏊~S}xSb 8< EԴGUUUﻠ\]]m))I-q!B_wˊr=zΝ;!BPts׮] ,>*^> !Bk"@=}e)%%rassF!Be3|>MQ|17 XWommURRР1}+VV~?!BG͵/di^ [#ByY!B!QB!BaB!B_Vez !B!,rA!BhZD!BQB!BaB!Be!B!FY!B!0B!B!B!BgϞy{{X mc5ǭ[Jf2VVV3f1bgq555{=zƍ 77wÆ W^9rdPP]vMRRkF"'eɌB!'O?{l5wz o߾iӦmmm{ƍܹCD^V\iiiN- ץKOcc5k077ݻ{+V={Rk&%%ج\o߾tDmZV;?7|XXX3eʔ۷O2[rP(n|MM;Izzz>ܽ{k.]Dĺu6lذvZj:hkZ.ŝ;t`eF!xbÆ +VB;w1[n/;tА!CbccmllݻqDYO<߰a÷~ro޽[QQQO>UWWQFϞ=[^^رccƌMӦmo2qD%KzGYǏ_l\p4""׭[33355 &߿l6;Rܹש *xYɉ q *xP-3#B}ٶo...>sLjsڵ Tu\tŋϝ;#TTTN{mmmmmmESBBBƎaaaW^)Sk͚5gΜMӦ}yYjjjVVVtJyyEmmm׭[rEv___??{ 6LCCx…<=zYYh<=uT kkkVBC+IVUUPHmN>;v ږxfKq㩊HK B}UUU?ORRh sĉzzz^^^wm~w3gեF ;vGKK-OZWWwiiii]n>|8bl@@;wDآ'M|ѽNNN MIMMe'OѣGo۶ l6n=YfQSѣGϚj366ښ @JJjׯ_///Mצeçٺfuu{qqAFFۓbccړn߾4vرc&%%իf444M99UV>|XZZ|}jii%$$̝;ZEGQhuR B!L6-33300PSS3???11ngdd 2>}ڲ(55uȑjjjsaW\?g9.88X^^ٳqqqTD2OYf٭XӧOfž={ D"I2##W4P(|ѠA>J}ie~ڵ`޽Ԯhggg*W^g>tЬYړ CCC(o}e\\ի{pddd߿_YY~WZorE58qbԩvvv{!ӻwxP`o׫]#E+{ Bf> w H]|R\\3jӦM2$%%yyy۷dR)/_$-fRRRޞڔUPPHOOoۗ-[&))B%((hǎǏ733{U;88P1a޽ߔ溣c}}= qZTTTAAAHHYWWHuOѨիDmFY˗/_|9<""m<|=[ޫV[33ؤFFFm޼YSS [Խ{wDSSDGG B''' ݺuhVP:J4êe#(_!zg돤FFFǏӧh#ƍB]rr2 ,<<|߾}SNmciii5558&Mڽ{whhرc ɓ'w-JKK޽GXs퉲ڣ= 511CMBBB44ѻMXpܹO>}]yyy9(a`` ,Z5rG);|*V7-++ڵkO>]|?E`ۋZ ۨd-#~\!BѣGuttƍZ[[KWBݻuD$&&VVVUUUmڵknjl233/_zf &O={411ᅲ,Yt$&D}}}!ږ4ʕ+=3g6 (&J} M.oӶ7233suuuuu]lYjjS],$fI#fV]miii'Ir̙555PU j؆U˨<#/B!Ե`H166?wڵky}MӦpd2W^=y䐐j֘1c֭[Ϲ#""JKK͛Gm5CG56brVT Ǐы<~Vt-\IIIZ>Ёv|u2#BuΝ;ǍGߴiB$rуjC lh!(UTT0`{x<Aѩ|S ~{[Ĝ###WXAOIڽ{76nݺr&&&F Җ#}v ??f鞞<sr'Nݻ7՗M.+>qSNWWf!888@JJʐ!C#Ghii;wƍAAAf6m:m4%%˗/;wϏ^l̘1s hlly?aY]\/,,mM>cǎ?vXQQGi6;~uyxx^qqq7n,--UWW`:Z2߾}UUUJJJVVVTmetoݺ\SSsСK,'2feeL>B!>/...KBCC ŒFFF!!!SN%&&n޼ɓBy>|xXX؉'@WWwΜ9SDOBB"!!mk%%%'Oy> 6lؽ{w]]-[D&Lr)##O/+V8p2]%--};v|33'O׏E6щfeeggۯ\r…|G;}jҺj%wR (VmƋ[C*T\1eO铙޽V3$yرo B/… -{D/,,lp~ lˏ=:m4 :$fN R:WуIdX VLgJwرc PVV6lԩS⥥QbB}\ , >&:㋿17o75իUTT֬Y+ ǁ^TqvB>?{K:Ó~{7sɶ. 򴴴W^qqq]xicƌ ?~>FAA{yy%''STMMM\\ܕ+WbbbD,--?~<|+V7 ʢR={e˖7o={V4ۘ1c>}JeʕgҥXqB! W }/e%KYp@ivS@ ÇS!֪U 322 \UDURRyʕ۷Z=,YK-M{gϞf{Y\m6j jZB!Neݾ}}zg|,pyRmt2й;~9*.`Ω'П9s䂂Ԝ2eʚ5kܹ@/b~W9997o?~ѣGoZTpŊkOO˗/@aaa=TÇ޽[TTu!BSQGiЇ*sEPZ.Wb̎GŽ3K_"wlTpKUVV& +++کQ---?oZÇ۷|1EЯ%SE3#BwT^1w;Pg較1OrM wۉ 'HuS) %%enn6frss  &&6bĈݻgff^peN5!26!BaJp JKn>9Nndݾ)`ԖٌVD :\ w#mٲe˖-{ImN:… /^laa!&&VQQ1lذk׮cA!rD@@@@@hRAA3gO{7nր۝iiiims/ Suyy'3W$C\y&tgϣ IDATj|C~ SBI@c0B!nڵe})++{zzFEE}^X]]ݳgO6M=kx͚5Ҳ_lY~~g]Qlӛoyyf?l+mW󳈖! XBy$ C,B!qĠP(NJJ1cFHHgTd?Cqq L2%$$ /^ػwAJJJ>j1yd{{t鴟5$K_4Ю̷ڝD0:sA K* !B}(㱱?lڴ|啓C-EHl۶~vuus}Ղ S[[YWq X$x[-VryT%a]}S/ B!>Zeeeշo@___r?v566nٲQCCC__ĉ>}ojjĉ 544׭[W_V%KlOAAEDDHCC}ث$iaaagOYjjj```߾}'|˷SoŊ֭СC]EEE ,PWWٳܹs_|)޶39sf;::kjj'''.ZG:::nnn@GJqqҥKmll444=<>^tY#GRAH}}}XXXQQћtɓ'755!))vԣGL(TU3</''g׮]?lذ7]urrOJJ lx򥻻{YY;vܹs@̤6ܹ3nܸ={R)wMJJ255 YYYt|[^^^^^>w\`XNNN/_,,.WWzw!x1&Ð/B!q^VmmٳgO8c>LXV*,,000hll\j,\Ȅ^xj?y_~=xCCcbb … k1A```SSS=zTXXf8իWe{ 4[fƍ~~~͢f}`ffֽ{N;|l߿@F҃ ׮@X96+8<8Rv_"&oߝpwx]똟H*{@ Bo+ F /dܹ&L Hv0aBcc(((_#TSS3m4iHOL&GGGO4t++{J_ׯ_CYBiz٩gL&}vAAm۶mL;o@ رcǎ;Jssskkkg{uf~=NKp#mקov(|6!_Flsϗo8:tA @mdr\\͛mmml6Bxcǎ4i0hРׯ?^]]J'%%QH$ . 4N͟?j{{ӧPT&O?XB&/_ kv1hhhPTcmm|6͛7H]]۷o;|3fӣRd2YOOoΝYXuuuo3{v+k{)>~d3q^ֈFR'^}U,}MǗ@ h, |}/,[ϟ Y={ X0+Vfee}!O233 [\)jjaLn[+ش@XX`n2;‚׮]+++355Dqqq %==E=~X\7WΝ;׬Y^z#G6nܘhhh ſBrrĉќ#>'OtgQQݻج}i9Yr% 44~H`\bHH.01]="7Ystmu3t]Fkf*C|u1.{=vXzÆ C\\ܜ9so߾ݻw+((455t+@ >g555sssG~_Skkkcc E *|0߇|(UMM֘1c>>O<8qɤI^xhutt9ٳQFijj.[ _paPS:dffr/^ZZZׯ_ aaaZZZ @ + V +28W\lܸq%%%K,kjjf̘!nݺ޽{xzz^~ZÆ ;x `/_NLLرcGn,,,._|eccc#644899^]IIMPRRH$ݽ{Cdd?z{{Ξ= ={6|5k֬[Օ0Nf$88xٲeo߾uss{I; 'NpppXzu^^ޔ)SjkkǏݰaìY޽;f̘F#777.dɒ#F@kˋL&-^ҥK&v'455u…+V())9sϙ3g̙n:P㓑~@|1aulC(\"yKs4LLLbcc#Fٝ9sV0KNN>|`` Q,nƌ_jUlll=&QLWWd*))-MMM҇۶m[NNN\\ȑ#;XHH=33ʕ+W#055߿˗L&eʔlc߾}/]))$rB >Dž48Mv9sťRZZ 0 W\inn%&&vjbi ~ пT]]}WWWDHRmllҤK <855+*&&&ߊIO5,0@Lu6!abnܸCrs疕1w2vXhb#G&ӦMr,kСY\ixTãWheAtB1a,x[P4aeI h4 #Z t#kK6%IWO}v]]] )cPسgO8 ?&2_.ʏ_ 1jʕl6;??ҤI]6!3lf~C^z(Eu^zLCCh|-BqtmmwumZZZJ-Be޽1cS$ 6),,ѣ{EEE6--99977wś-555ݻ Nh?xBӧX4E " ΊS5o*,OF[[;**hlll gXÇ~ w%''1ׯ_︛X,&L̏X?j(hV?>>>// ?N|ͽu떃_ tI^&%%}s䤤t"Ȱ%&&FUU.@ Bp۶m<b1RCA Sjа 石fBvڝ佑 ,W8,Jݾ}KKK\RXXB߾}g͚m۶İX,Wʒ%KΞ=;sL///ҧOt:t:4pwmڴ (!!AEEE իNNND͛71bȐ!ކՙ4>zѱ~߾}T*uŊׯܲe DJLLUU7nlذjjj݂Bahhh]]%K$ɪU~ h h4UU>}J3%%%<<ĉUUUNNNDKKˣG\r---}Yv(D[[ȑ#񇫫+rqqtG(v̤ITTTnݺaǭO:5tЮw@ ė>EFFDeՋXeJJJh TXXl;ѣGvvvL&3'''99y޼yV^Hdggɓ'OUUUO>.((AZYY&&&aJy{{777>..lK,!,6=x`b8OOO_իWdimmCKwJ$0g;tk@ yFrr2,Q^^|0bհaæL###-[6mڴcǎ޽3333f RorI)i{}YdԩYYY111gϖn@>geemܸ6~Vۗ&!!ŋn4337olmm@&$$1Æ KOOGW7SwVj͞kOgd2 @ <rrrսsΦMET*5##cذauuuQQQT*uڵhJΝ}*mbH$C"6l`hhd2{.P}H]:x>><fٝ?>}t<dv}]>x^߲e s|ɒ%SN%'_xIN8[pB5|òe+ɓBKKK$bqRR(Hg̘!v|СStb6mz]rr3l8p̙3,X@t޽ɓ'1cƘ8qM+kѢE ÇmPyxxHںukcc#@ :ꊔɓ(> ҧOd B0-%%%p1$$ŋs^bt0uT?T& >ĉDt\\\+ʋ ϟ_p5==}ʕA8+))–wu*5qysssL6MKK!!D}Uhq9iiiӧOoL퇞5 ۇ@ @ >r])֭[7E!qYWWW]]]EEEq p2zΝǏO81<<|РAL0JJJ---DZ@^˫<99P\\)***2 zzzz;vlvvۗ-[&ӇdN<رc0>СC={۷oӘkjj3D"v<]m*@3ZvvLiȜ8@ VqII Vj&ުhwwwo߾{eeeaX]ݛ E6Co۷[ 7%IVQK… O>-**j/qۻˍ ]LR8KRt}tbY߿Çׯ_A@ ,e3&!!a߾}w(ӧO 1cyyy@P1qDuĉAwSUUTWWtuu:866MCNަ}ۏ+5 ߼y|x&&&W^]} 2&r*)))**90 X\^^8r0~@zXH$*//777_t)Q~С)))EٳgII J;w.!"k9I@,:?zh77!C۳XǏ/]յ=ڵk߾}~ڵkUTT:%MPPPJJ.D:wʹiCΝ;}||O^\\|ƍ˗;<`͚54mm߿?aiii&Lh+kڵYYYp_,Yĉ ,޽'Occc#-0Al߾v֬Y޽]9k{ ?~UUΝ;u/rrrNJL>|GXpaRRLE1iw d2YL!d2`4558+aAgQVV0a4@ y왶j#IWxfeeu;vUU~ѵGhhP(\fMmmmwDuѣ>}z媪*fddi&6{l==m۶544x>TKKkѢE䦓1&o߾~]v577/_+zxxlݺuΝ 9@kgϞl6KOOիq_uX&D,!%)m,|-񹨨1@ e8Ç7Ń8?H7jΝ;Ν;Dq-իĝ|!"544]JA1}@!"P^^.tzKK '8bpΜ9477|7oݻIGG(/_I@ge}͈wK P#p Sh43cƌM6?+b롉E& 'XSSg2D bbbb^^1ΝDP._xڵ͛7O49@ ~<9C ($4`0bcc/\f,šb2ᅄPQQQSS ԤgX_~عs'FśiZ@ de}q $T >/uuu/^xqOdi4!A p/_n``CBB?NlUPPb ڵkݻw #ȗ!">zǏ?G  [[[kjj`2yk @ ,bf}}=H$%2ZZZ^#@ƽpG2@ (bݣY__8| YЌqCB 444B@PQQ!`!ry%JÖZi+H]]PWW{&ijjZQQQYY) -,,0 /++knnRjjj~6@P]]- r\ JJJ***zzzD1G@II ] @ ȗhhh{& Sp)--zbݻwښSWW' aT}}}CC@|@8ЁF\TTTRR" FECCC^^^SS㭭@WWW777caX,,//Rǥ Acf+@ ?HkUWWè?MMM' srrZZZJJJ|>쯫r[[[bjjZZZ m6333X;OQRR0,??p銊p["sL]]]IIKpWVV"ݻwBJCCF솆>/drSS ]@ B >T*UUULoz{d2ǃ=\niiiccD"!***H$QWW-v ,τF SWWH$@ڧbp-djii;L&õP[[+p0LEEz.K8B@ 2I@|4CV? ǫ|X,_J,l%%|*:̀7oȬ`.&B0 +YfW%%%** kkk\.4 L&@ F&\zVC @ :eݾ}{C'dK# /h{|>???_$Hw :::uۃJP<"sCZ}---PƐ@EEqaLc@ w`eA +ݹs'((hΝN0`gumm-G|v8(((ҢP(BH)++744477l:b-!eQ[[pZZZ82T(**e0bY ԯ64@ ˕/-X|>_ZcP҃D @|gVYŌ 777wwl 2db!P1VZ]UUUYYDzBLfʠeeiiIӵJJJS FV@CC#77PZZ w`0]Ul6͛74BL3iTUUa}0t/@ I>2/o߾(++۵kX]]=|mmmemm}QbՃ0 {ن `4Yxx8 ;:tբE  ȑ#oܸwVQ(DP0 #H CSSS[[nd2uuui4aT*(nhhHlH& q`ݺuc0t===]]]NSSF8.H555j~`JUTTB""z믿OHJ!#P>^cMKK+!!aF{{@ s͜9חdڴiLJ>^jmmy+,Y[/_N>C"$1 x({J~(/^|9de!?ᓔlvKK |j}Œ%K0 &ݻw H"##̘LQpp0QNWLLǏ`0,X ?KQ̘D"ټyO?گ_hu(H^|߫W/3gK--BF ||ĠH$̴  i&nD@׋ ݻw/%%e޽{ddKlll C|2LјLf]]]kk+Q}  X,q E[[[: zK0M0f̘3ghjj?~looojj ػwCFFFd鎎/VQQIJJyEDDg׮]EEE~~~KOO߿NNNRRR{3qrfLLĉҲ\\\7o4h@(^rd&&&zzzmo޼iii)/AgQXX8j(H믿jjj0`֭qqq+W$qSSfϞh"kkGyرcD猌e˖ۯ\ή+L3aÆZ[[WTTܽ{0i$2z;vX[[ϟ?zh馦o޼ )))~zc~iiinn^ @)6I$B`$$јF_:|pMM :e``PQQaoo7dȐ!CZjРAVt:aoD"( A& .HkhoP(H [PU?hW8ɳٸqƔ)S=JLQ@jjÇw1c5_6صkK"c={|9=ÃXںukcc#G}罽#7baVQs.b8H@B0\"%BL G| 1/_>~g|իW= CB -+8S(**H`4BE2X2{CD"&bo/L"ܐ*td={l2K$< #SSSwRVV1m4i%K8@J?&MəIII SN%r;t:sZZ}ɓ'p1..ð3gN:E&=<<:Z_fffdbux`#;w=lhhh 3fXbűc`a\\R999bXKKC`EDD_~eڵ ,蠘 ;;;22޽{eeeJ4{ cqq^['՛5uU,'E& Pj5zF0=紲>|a2r-,,v1g8SNQ^z͙3#`0F_\\d2wo߾s碳GII 񒒒snݺA4T$IJJ 2d=bmm#6mjj  >ѣF2d ߿Immm^^H$222/,i 2͛]v\2++w 6DDD@ ***(((''GOOhч='N`Hc L2Ӊ2(ze<ؽ{wvprr0,55UII)//oܸq$iȑaaa))) jӳ^gWfCڗH̳7nܘ+"Q"XWl?el#G֬Yk׮hB15Maʥd LQx#h~0"J^1݀ 9g(f7mԦV~'GiBB)Z IDATڱS/:Z[[\.L.**999cJZ^ŋ:::]/leeU[[ݻ[0!%o߾wrrr,U7OOUV%$$ݻ˫͑חHAt LWl H$ҫW߼y#H ĩN&x<ݻwi43`G}yff&Lj$ i<==߿&9qDbXޝE޽͛WZ5bĈ zzzjxSNwZtA ]# HĢ-1Ԝ/n $U$,$IJH8..x "##-[^ Ø5k֤xD| (SSSSRR!={,((HMM{.4dt,dއH߸kjjΜ93vXOOωRZ q(H"OnQ|:tsN>0ƌغuboo10K#F(((e⒞:bxFI&#""D"QB~͆4,?a;`_rNNN|>&)**>z m Eiiiz!q,,EMm ÁJԪ4WaQZk+ @n͗kĀZZZ|~M⻆Fddd)**h4XED 4-h? 0@$d>/TPP a $55W>UUU@WWD&;ѣׯ rsssss=x𠎎N]]&999Ҿ2iFiӦo744ܽ{w&&&w4h?lll\UU=z,#vѿ}Ϫ׮];sL@@8JJJ;w񱲲>}zqq7/_>vX-[w 7QQQ߿UUULy8fbyxx_۷o'ڝg͚w7oތ5F^x1##_9wjjj/_9r$0AAABpԩ...{ ?~UUΝ;{:ujAAQoX[81_b+ӕ-F`TsM^ +"_bHC"CB 9tAQQQvv2L)RuuuwS(b#:էO}SSӓ'OAFMnnneeFȃ 7ϟwѣGo߾]KKkIIIݺu[t,/K0q .0ٳ6m:z lv~\.ѣGׯ?zhEE߿O=222V^m۶ݻGEEA/ٳ"""y<3a8d*Jlz776mΎ| ;d  @ )"ѓ'Okj/I'0/PRRo6S"j"3pp\.:x522Zv}8L9JwwwXjժׯ_;::feed?vX>aÆ%Kܾ}{Ȑ!2y}}H??[n :mmm=r䈓STTƍsrr\]]%CLL̸qtuuٳlٲ766AѣeIchhf쓴 rtti֭"@ 7LIu7p^W/[[ls8J_Ws1 aH$a%8@$ `Xk N8 `n">5JMMŋD!C`ԩS֮]{Y]oiii0aŰ̽4;vXp!\KNNRL_&:kii7ݻG_xIN8[Ch @ ]¥h< SRjjjpX,%7ib$2M,`dH,q\$D" EBD%D$D"\$l Evi~\'Seg###GGǿ[WRR=uT&BatYځ+IJJjhh:u*t# _@ XJѣّc0p?jX8D&I@ f?)(X#;~f v:yׯ|D"iiiՕ^444LJJ0d਩I+AdB <""ڵkp$2r&&LiUOB @ :qU,)2q4qc{ُ_%H5ՓD zzz/]tƍjjj$I .OvnnG4668rgyyyhz|þu"D"@ `8@܉Nd2e,--/''']];wlڴICCCJ6lX]]]TTJ]vmܴiA|}}Ts;e,+44.={6!!JJJ;w񱲲>}zqq7/_>vXt| ]e-X 66Ϟ=^^^h־Y$;utt/^ ]\\`ٶBM&@ /xhwֆn=9Б$\I 8Lق473mԩS666'O߿ܤ;X[[ߺuF\r&&&7o޴jsoƍ;}4J]lٚ5kmOAWW̙3aĈyyynݒӐf׮]֭۶m-ZcdddiiO n*,, rC iS[?JOO߿u}||.]+ ⒙"/^c77vv G%@ _gϞikkqگtD<َr{-a8 LZssV!J֞0>{&O~||ԩSѥ5êLݻ;eZ}}}4>OCAAAll-lyϟ~…;wD #ARnh81M0Qc qpik'..L&#E'V͛uعs'a@ddD"YnE$Idd422 =p@.;|pMLLtzw KHs(((x05=g+W 5jThhѣG;wW]]N/Fikk;tI&-Z-// h0oqqq-~QHHț7o;7x-`׮]EEE~~~KOO߿NNNRR\sEB㎎N7ne˖qd3qD,n޽{VVVK.RWUU 0ɓ'7nHIIRpRggg+WJ$]v?>55PCcc}UUU``ƹsfΜ 76mZBB˜1cfΜgXK'>fu6 Џ`L2E[S'<"bi$iI&FFz$P2dHzz޽{+**$Ixx%Kxo\K=N 033"SRRD^N8aXvv6aڴiJǫLСCE"\daa) q/**"Ɏ3j{ƾNȒ}+%D"K r%IҾ]i%*Ț[TBVV,e$!߹g3Ιڐ766B]Vgg'JYjrݻwOGww7yyyȑ(gŊUUUM61xA`` 7-Z$$$Uj~~~޸q#:,,,6l@466Dٺu+7Pqpppppp~^zR? ʖBQ( r=Vv% ˗bJQ(e~8C0XS^^^_/#әzdIB444:::&;w;;Wss3,޽{ccc ""qΝlllusrr|||bMffYއ"""Vy Ю UUUeddTVV`m۶MuGFFddd0޽{WXX} MMM|?@ >kŇ߱͝7G[k~AeHh \ oEGG!ɘD"QNNքNs`[ֶgƌS2-C`q'''Զ-lxJxR#B\?'w(a~k ]C;66#8H$@pp0"IKK#׏iB*JkB9^@Ptuu=9>w\xvS` iMbccYXXO4iqpppppppppp) T,jҳ"++ B+Р/C.hhh@Wğ@h"X`MMme0)ʻwh-3[[[ǭ*ۗ/_ZZZ0K_0zhలwbgg tR FGG BQАH$;w2uuueee!%rqq 2rGSSS޾ KJJjnnY]]|aJ+))'۷o߿_\\\YY(+**߿;okdÇ KJJ޽{G?ٹs'@`0M_ϟ'OK.'[KQQQ-644@ !ujΞ={˖-\ w3pႸOwe[.::ܜL&߼ylڴiNZ8''gddUFGG_~}̙3"""bcc!7<<|NNNӧOoll߾}eh( k.===[[["+333GGG??T[[[ooo/_9sF]];.U_f͚lMḾʺ<ѣG߽{ð0999(scb(++3(۷/>>F\bEkkÇ׭['""HV?~6mڴijjj|2w\PYY{*Z^^ͭ166VYYYSS8vccc?򱯯ohhUy󆕕UBBHr劓>*;ŋj)TGG'==Ɔe СCGg_?OͧN̬R nnn~𰜜ƍ嗯 WM;/^&+s: ng,pÌ˖-CQ?;)k֬ ޻w/J111A. 9wܝ;w$%%n*&&fmm\6scǎ:uGHHؘN;wJHH啗|a֬Yϟ?Gdu^^^?۴iࢇD"KKK1pҥ555˗/{YW#$&&"#Z__ߌ5<IJJBfooǏaL RXX?n```275553(e<}TIIKR;::޾};{l$fcWWW]]訌 m G^sswmիW>Ν;NFtVVVo޼?q۷o[XXO|䉦ŋWpgg≜QvBR744t۶md2.빹W\_dv>>> 6666k,rxoT˗/`)ڹvگ7o>w\CC{rtttW4_BBb۶m_ׯEEEWiCo IDAT||| ;^~ 1**wrrrvww)))A 0< cccз ]#oߊъ ###EEEo߾GM c?׸rss YZZz 9' `ddTZZ 0??ݍ7rrr]re:LtqR!!!rrrlll QQQW^-_D"-] R}}}...>>>'''*qrÆ \\\bbb苹cbb988$$$~7t4 sXpaLL &P (,,~%ZOǏD@֮] FFFܪd`` \.=D"СCJJJ!!!谨G5k;;kkի={qpp@k@)))NNN\bpEGGϚ5 &g m^g666.\8::x̌{GB@  4tuttHIIO| MAhϢUAWWݻw)++a &+((w܀Ǐf033Ddgg"^[ffٳgWWWDT e[WWĄNSSsttfxx}Y__ϟf̘_|prrrrr"'O\xqmm-4SNHH vZtt4"tvv澾>==vaaጌ gg羾>tox6]5'N,_|Ϟ=>} dSӓi}}2Ruڵ?~9͛7Ϟ=srr9|pSSݻw "##?}oyzz;wΝ;E:t(((_MMÇHI?%%Օa퉉prr2HtvvfGGGl2)/HSTTtpp+* }}UV111۷0 DYYYv122gx!==iӦMMMMgϞz :렣#ɓ'mmm͡ ]gfΜ 3>zzzJJJ|||&H?@Qp{ۆ¾wY~۷oіl<011 mhh C?qssKJJQSS{ÇkjjO+W~zǎyyy)Ǐ8.$i\<$ @HHϏ%!!aݺu&~~~/_vZG:88ス666EEEP2̛7/11 +++8ƇpوGGG''':&WSx_}999˖-?t --;n//W%;;kjj߼yCR{zzݻ7:: /w^WWr ˗tŊQܻw/??L&c[[ۋ/JJJ둛bՕ7,--0N&ܹsqȀ;T*5==pU䬣# T^^]hD.`CCCHcc#q^̒/"%M'O"HJJNT7o~"f::: mmq]p ގ\i&&&fd3 Wfd8DmǴ4@~~D?~ --=44L~))s0+tymŊJl߾nDׂ)))ƍ u,<\\\`>>䈻;@@ӧ iG}BE0B844D vCG_OOo˨T*Lfgg3呹4884}ahr^wllP@cfLi&FV(@`ٟ>o޼v~||`wZ5!""6|ooo/,,P(,,,EEEh-F[ci2X7z;*Uh;gΜǏӦv`;...cccH$O>AE"#@ N8wP`oohzϟL-h|i&b7n T&y4:o|/>\VVWРcHlt&&&;;;1 D deeUPPʢj_>-fͺ}vZZZHHHwwf||zЭ]7s<;vwpuuFVt:=:@SSSII)11 *III\\\0I # ,,Lߡ|dd$22իս ehhh衁 Ld9WPPnllIhX(naƆz1>렱Zj3: 6PNN}PNNQPPPΌP[[;66&""2n40##R^^c\#1B"/͛7zyy-[ `E}MMMAAsAWC>&{1$:̺A`p}0ȤjΫoawJ)E}D-++[]] wXyÇ̼ Kff>ST8M $t-}}}:::mmmAAAZZZ<<)ye!`Ъd'WSx^< <?)e7-%%%B#--=N޸ӧ( @ёKMMG~GZ[[']Јjkk͛D:<mmm))) HL7oljjBF1Z af?~^C)))"XUU>XSSCP6T,~){ݻ:M|:DE\F/ѳzIQ:a;` 0㟭B^޾}v޽䡡!ٽ]uuurrw !H:.ZDHHHOO/!!NF,*𝘘ߏ6b5DRi7mMgdd;444,e/^1 T=(CCN>055je/h;=:ED;cc㔔 괵ڋMuDJJJ/M__8?..NPPUPPpΝ 0ҺW"3phh(>>~ݖI''L+S081c 2vhщ)ŕ|LJJUqp~. G@&1L a_G:th…02dΝׯ__fMvv@eee]]m F+"""bccivtuueee۟ͳ`e˖ }1..Ǐڵk˗/y&:*:666]]]GGGht&555UG酆z{{񙛛Ʀ/+++$$F':C@g"++u˖-CCC0?ѣGmmmuuu=<|8===))EYYy)#wA&""rܹ;wHJJnݺULL !̝;ٳgAAAW\immZt)rիmmmo~y66+W;vldYvvv'O 1ccǾڸ&::YݱcT ʕ+sA;aR}}})fZZZtttOO<+ m۶ 6}*&::zΜ9gϞMOO6119x c~J0ލ999PGgt,--֯_o``-RRYYپ}N:ӣ .,\0_eˠqD dgg733KIIillPPP8{츛G#))+((}.))ddd JYLLLš'OlkkyNq/_|ҥm۶M*eM=<<OGʚ@DD1٣Gbg;|?#((h۶mVppn^^^ߕ ]wSZZ3m888?` FW\y%|@h񤤤888O鋯_uJXkD#88bcbe}o߾㛒Eš5kdeeoѡ!:!q /^E$&&&::=F}Ugg'S%t/455b$ɥp(H{yyha ]]]fw˗Ї󍰳ݺuƍʗ%&&nذatt_1l߾};?ss/;w"##CTѣ¢cNNN^^^7Weώ;-[DKٳ'>>t>ӣG_|'ObccUUUg̘a``|䉓ɓ'7o,###&&j*Z~wwwggg CBB.\8c YYٕ+W{^-G3g 6aDD:LL 򭡡SNΜ9SBBBWW5x"J3g&Q3g=dAs,B rttshh(88VRa̙!!!k׮fMZQQ%**JOOaӦMk.555$Ç,YtRSSZCuuM6͝;w޽PԲf7Kȑ#K,9q#w$Hߏڱc_ff&5**J[[z޽!R+))4o޼xdڴiǏWUUݺu+%g!!!,YM[F @ `Ǐ%KY/@cee%%%-p;R*?\#~M 111D2k֬q DgΜ9 L&OJJJ/_ ̾}דqVRVVf䎆D~9HFݹs`so?M899"~qP(gϞpuu-4 ŋP^^SN }&˂Kv~`ccC'Hb:.<<<%O'D###h R" B_dSI,,,͡m K{j,^,,,,11/33ݻQᣣ_|H߿?0˒ rObDDDD0oz ",9%.[ܼHKm۶! IDATtXO>!IEE#흴ELL,$$k׮xm! *,DFGYğXK200i}}}-[lѢE!??°0Fhkk ,wͽZZZIW_{*o888"""XYY)J\\ܯ3ݻo-ܮ^mcc3<<eddxի>| kЕ+W bN>maaCJAa)&&ATTTVVرcCCCcccvԴ+;::.]caaTB1~iiik֬prr%;Ot _v͛7YYY>|Q ޿҂Dellܼ?z( 斖e˖իWK&&&?TRRڲeKkk+QQQ766F@$`ffFΞ=.N+tj{1nn?^xn: VVVxlx 3336x3nܸA `Brpp9rc[XXh``i{{zY2 ,,ť6"رCJJ]YYٳ?f~R544$//8lϧ}mmm\\\222vvv#fffڵٳg"""v Xm޼Y^^>**˗CCCrrrk֬@][[K"ͷo߮$4ӦM;wUTT)==SN033ZYYA*"gϞ[nQ((mذfbx̙333y{{Cы~֭qL\p \cmm]ZZzcǎ!NbtX|{III,,,&&&II^+++Ç'Ozzz#vJKK²^xi"\p966a---xKyys碣O>\&!!aK!BMMfΜGyyzz|օ:::&&& ͅb5R&\bRMZ͛򋦦/***JJJ _xϡuggq__ߦMH$ҥK,Yr}MMM5kܺukƍJJJyyyࠏ(2-66psss{{{KKKә0=t𩩩o߆yq&6w277y󦾾>rټpppPTTrϟhmΝ Z'pB ++;Z=22 ~;igϞ#Gdddm˟>}cdq2 :""‡vÆ rrrFFF`kk9svZΜ9F+899IJJߤ|D*))ӻuJE999pߝ̐ kÆ FٷoakkkUUӧOC3gdӧOa Ϟ={Νwޅ8~~nnn;^ bff6o޼I,:^TT^attƍl3NHHF ޻w<>>> *XXX455њ1"utt˃șfBo:::n۶-55Y%/出ӽx555P PTTa@mAߋOOO111vvv99} Sk֬X@ :t?x @#((HSSf022ٮCt萴4d UTT @q! GB||| * eee۶m۽{7UTTccuKEEE˗  ~pp- ŋK,)//Au{ي+059tPPPZkkÇ6?%%ՕUXXSx__^{{pFFs__4sk QQQ7nYYXX?~, PTTiLLڞٳNNN***fffO>VQQy捍 t~CUvv6fIrrrYY.MMMn݂zݞgϞmܸlׯz;FFF3o޼D=h4_|yڵ$)<<YBO&U~eHHH~~~aaW;::<==rss=J&S؜e˖=iii=͆ss-[7X}}.޽{MV\\l``~ҟh>>jjjϞ=;|pMMMbb")122 D AWL ;xHrrazzi~lݺȑ#D" o6юܠ|rdd$4`^f wuq%%%UXXXXXݽaÆQff枞OtҹoGGǎ;/^y xzzZZZn18{{PDR$Q__ <<<prxxx_j+$++I DP޽{Cj1(//?UWW'%%0 2uvv88?LD8;; !b\1!hhhQVV_ Fp}dɒӧ_p*SBBBhhSSS޽vCx=FXz۷\k׮۷o ~D"1??}>a--->},UUU$Ђ"ZĂf666𗸷ޞãѣGfff###PhϯfrgccCLO̙3ᘳ 6' `" *##'---0KDfLEi\k""F4ߐ#˗/~&)))++BG\PP‚~e 7iCDD[^8 ,@g$//T^^fff p :~KΝ;ntssc|2=2ϟt~zz:R>}KJJܺT_\\<66{Ν///ٽǽw  yrѠ=߿oddD>KHHbRh~Ĉ޾*'' )) z83XO n#abbE^Ocǎ_e9 ..pPׇ޽kkk744LKK{T+|GGGԪU,--uttN8AA{zz??*\޾}[AAYZZnٲ֭[Vz֭[CCC=zah77;hvn?hFF WGDD@((b¯Mرc7o>p7Cߦ6tELW922y^ 244ۘ~m"3gΤnv) [466|rΝsپ}{~~y~~6Fڂd2c`* =ID]]@NNbJ?|} L4 qyίJI711}΍7.]:|[n糱?2~~AHHHEE%>>H\\Gl-Y;""l&**JWWW]]]VVɓ'JJJ$m^zR]]&''믿³~~~ /_?nd\p!222&&fݺu 6Vi]]]&QMtkB&V񚠭! :4!ӧO[Ձ>PҎ,fyaƔ٤3)BD+и>@(**孫[|9H433uKs_;7_и#]t___{{{ Ӄk~-[v=44`0s>{,((ʕ+<<j _p\w)0eb,-SIy/++K΢EHHhܹ>deeW\y< &ִ©@NND`@.@xq/Fn4')vĉ'N|u 8?'D pp~_FKHHhkkC^O }6_wrrRSS &%yyy>|ظqʿ'ڭ\:TUU-_@ nVDOOb҂Auqq1 h"VVx...tDKK˖7ojkk#oJuODCC͛ա2,)) Yg~H4+V<} 5>00pe==ӧó ,5nOm`đޡoi]uuDKnԔ%""gk\ԢJPL&#f%%%qqq_C@@@UUѣGG¾YZZT?~][qs?\ܯ_F c֠P2¤Rrr/_wƗqEE:;&@9y=I;AAA ^obbRZZZTTdjj h IDATfffLLLǎCGCKH(WR?0p]Ώի˖-8s挦&;Uttt899 :!tҀXg9=(((66ܼ:Ҽ~ZLL ]UU=66QVVΞQqơCLLLTjIIIzz… i+X[[RSS:dccTVVrrr7NNN--"kkkA>f ̙37nloot0`v?dbbx"777:-Z/lڴ 677!H.]jhht<}z%%W>|LJJj޼yfffPڜRseΝׯ__fMvv@eee]]@A%((h׮]zzzD"իWfffzMвjժǏ޽ǏjjjgϞŸFęǏ222&|v ׇ\%''C# accЈRSSgnnΈmrJlnn u Xxݻw׮]ؤ=zVWWÃ%##Lخ]RSS}}}EDDJKKݻH!m/\\\\]]adIEuEGGԘ7o766>qD^^^TT<ϯ, @G猁322pժU0vS׫MfE޼y̙3YXXmӃҮ]Q&*0''gΟ?;s̸KJJb.Y_~...h{777x#$I]]}׮] ֮]<(++{ޙŸLS͔iDMHKB )B$HdKBh+h%KR$+YEmq}ϝiZyz9s<-]^>SQQ~wĈ^^^o߾eƍuoo޼{#GdyyhMS{^|*-- 7W\ d2JY%͛7ĦO >|pvv4m u͚5 eԨQ{mxČ3BP(k~ر $,̷oVZ y4 )))4аNHHJ,Zh+o:t%Z111 bddTPP%\~RRR޽cɧ>\1&&ܹs˧ON0OLL,;;$ s̡h6l:hŊd2yz! @AAL&+((gee<|:L]vm{{;>SN񉋋xħOٱ/A}.b<Ph"|_իWǣ@ ![~=j_Ǐ7"~HY2e F/!!#]_!Ϲi`&&ɥ\ O T)ܰa "߯e˖͝;wƌ5۶m;t*9sfɒ%q @ŝ;wfΜ.##p޽4h+A"O;Ѹ}vhh(7{Z ~vV^zjL& 1Tݺu+>>d*))EDD\ @R'JKK̙M&%%%=q~"+c|4G 8}k vuu9;;;88$$$FDD())IKK~!::ZCCc0=?,,,dddTѣGJJJÇCCC444Od2w9zhAAAQQQCCCRWWtR|lHyyy&qFIII rayIܾ}@ T*v¢4>zhڴi"""֜ê#PL0B`v&N(,,,$$w%ݻi4ٳgJIKKWVVeeeUUUbbbÆ 9 ק颢X4+++ @ ===G233UUUd2tKR @ ,+##۷7o޽[WWۋ-jll$˖-sww?HLJJRUUuvvRwuuG uvvP(~~~<<<---^^^$ؼysXXX``NuuuQQ-Ĥ600PRR̙3kjjZd LPUUF&cǎET"8o&4im;uT^^^ii 6ڵ 0&c{{ѣGOظm6;;/_M6 mmm.\x𡈈&~d2iii沲,퐛rʝ;w|/**ZXXO;99)((lܸ{GGG/]^~-00pܸqW^ūh´Y)9rΆ^^^)))|HiiiӦM2eܸqxsuT@ 蓾5߿߽{d_|ЋNx=&< cpH0z۷o۷Y#GlKK˪*shh(&b\]] ?|0|AAA,kkk L&|(D׿v걑UUU1 "bF@7'oPŅNeeeot;G"@ 14RV]]<C!(} (qEEEHHHP(J&ϓŞGYY3;v={ ʕ+ʰdeeewܡذa4Ԏ;/?.((4 y Ɍ?ׯ_Fr,Y.{QNN`^UUUPPN~,K!@ [cPXX0vX!!]vEFF|211Q\\ "" elݺULLH$N0%OxBqH@ю=~'&&߿*12LcccLc̘1,"WW׵kזɝ8q/d2{<򉯯ٳ?:uvr[`{0{4~0###͛)SvC @ FU v  ŵM6 Ԥ999EFFbX5ӧ2}&PQQٹsu&Ol2WWWjv"θ[.##CKKGuA[UU<>|8犋;88W ˗,g\ŋ_w0!@ $}k Μ9333JJӧO}ϟ]\\ݫ>}J"߇0x}+WP8qZZZ9"))\!^N[XX466_xگ&8q3gN<`0,,,zLfooؽ{7} GuɓI$Rtttgg'ׁ 9ɀsތ8vewcccllW⚛|a l>3_oaA%"## ›7oSZ[[  ;񽫏7/W&hH *}e͙3'22r…)))$H$CSS399BVVƍЬcc9s %&&~,Jn%QPP{Ytt tzӧOϝ;ŋ---Ϟ={M^^:.Y͍HYlmm_zebbPTTg%%Xccc]]ŋ+**֖A@TT4,,l͚5&&&ӧO'=qqq INN @ǏضmT<}tFF>@8q"j!ƏFyyyAAD/7T}}ƬY~?RRR㈼|RII M]]]Ђ Q %((iggg``hjj*%%sε8NNNL&S__?###>>}ظqgv]SS|ѹsr!>>>99 //j*7oܲeKffI$7r̙3}}}+++{SӧO={FB!s̘1w KIIhFFFփիWٳgӦMD"Q]]:ujdeeO:ljj*((hkk[XXhll% 033RWPP`09:F300ߺuKCCͷ!$""IYC7fb#(f d2~?jjj7oMS]]t@R`ѥ۷o_v-bJKKkŊ,Ν;s4[nݺukoOǧ/hpv޾~ŢL&_~)8>vk9s̙3+m^)n߱f_| d^ V^^]N8۷o-[lق&R[[{5CCJ"Vggٳg{p5Իw6l NTWW ]reĠb&4@ ?# m۶7oC5ﻹ =>|8Ba0!>>ݻ =1c??#\]]߼ys…#G677oܸ@ L}ѴiDDDl###+++]\\lmmʪĆ faaq= III|||rrrk֬iooʲ:u 96n  EVVd2ĉÇGb ק颢n>wqq J2eʣG~왽=J9s7oƍPgdd˳py=== "##P[[ uq{lgϞy{{khh 2y}_BEEƍY}!...$$d``K.IIIOh4aaa?el:{ t:FSWW`OWWW^^qx+!!Շ_~tvvƧc?/_;ޖd_(X,HK2?BFFJjkkm"##Lƍ%%%) \2ADDD@@@WW[}}$??Q~~>\uCBBFttuu,ڵk _||<@dggEzn&okEwwwDDJll,BAįM_ C ˗/wiio:入?ww/_L8q7oP(/^6lֱc,XP&&& 0>>Դ+SYY9i$uum۶ݻwԩ6l۵k۷o{ f9{d^^^xxxyyyjjۥK0喺/:;;h4۷o޿ގ\v-''[JJeeeG>}ccm^|޸q122{8qbYY<ӄ VX!%%UPP`jjW111>|uKyrrrNyf@FFDO<{9s_|e˖O>ancADDȑ#޷o@VVY[[{퍍׮]ƖMMM&&&gΜ7o^SSS֒<<<֭6lXaaYoGW^upppttnnn~w_988TTT\~]VV͞ŋnjc~Ϟ=رc_|Fŋkkk,X^>Gmmm;;;fbHݍT*CKG%IIIP|_PP*''wU0++… ݰ^J${tVڵkڴik׮CDD^)@<'/^nܾ}pС'@2\xˠ)j 1@=]]]2229''[0uhh(BSSrttpO _=;D"իW w^KK N9s&Q\\ʕ+M ϟ?@̚tx/{߿ //_@˗[_R, H$SUU؜{:^UUYx1@x8>7l؀%>y$@KKaf̘y9w@^^۷]@@ʱ @EE422gP=&VUUw2~xؓ=j(|z<`Yg0CXO>:;;cyyy]]]>|5 IDAT_eeeQQQ#GlKK˪gϞ瓛jffMCAO??={ɓS.wEEEGGȑ#_4559А5sNtvŽ)[l9~3glR]]mmm}1>NKHHk_eϟ]\\ű+WQ  ۞؝iӦ.***5j%EZZ:==`ii +V Aw߾}{I6y9W\ŸKKKO<8"6DĿ@VVΝ;1k% d;%%JΜ9 ''wɷoߎ3,Ox޼yd2h8._bDX;1bzzzCo޸qݽG}'hZ#`o ^UUEc艉 7o(++Wr,B#"ۜ3,--uuuUVVP(,$쭇սΝ;CɳȻwX%g<[lIIIsvv~:K===+++Pfxðdžwy466 ,--;d2}L&C0\iBBξEۡ w _3 x9OB3a0,ŋ_+++|}lmm5jD~ (((TWWs9< KX _hSSSzj333!IÛ%[ 9;;Zׯ$ޖIKKsrrDpX;Dh+K&1/WG=MW0S++#Gܽ{zڴiPʂN '/\$5.Aw1cTTT0x쌌 %%%2ӉD"|c70119z(xh}6ތf͚%""o߾Rfh𖝝ݯ&M?ӣ ʪs<Ԅ*|G_x 􉥥>9}TskAqqqzÛٕ;G===?}qƮގa051;;;w={vʕbtp~(ly|-\ʹl}2&YZZͷ`8?jf NNNϟ?ϬǏWVVbO?᱕vNNν{zS577'~ /\~_t@|w~?c UUU2|;w`/ , IKK`dggoݺFQQ599KNNVTTԌ566]xbmmmIIE6$\r%55ΆBЛYogdd888,^XEE` #Ǐضmq:}tXzujj)S Ɵ3ӧO766^d D:s {tcOf͚t[[[ii[n]r7u={lrrr?޳gG\\<))7q]}--KKKyy!O̚;;;eeeӤ$>>۷yKJHHhjj&''[XX޸q#<<\RRs-e޽{ /^,**zҥSN@:,̚5kΝ޽)++KHH/+WLOO>}Çtuu$8zvBhggsΎޤAN^999~~?x kǏ  L@ ) @􌀀?~(((CaO|8((1CPP͛[l<~8Dj3rׯ>W^MryMZZZ,--׮] ϯX^rΝ;Ǐax|+**?ǰOL:zҥ'O666JKK[7>d2322{ >^VSS+.. jhhPQQ1[2￧uI꽀qS5{K,&5ZU ODZc***0]v,"یi 3A %%%.=4 tHeg$22@ =RRRB >?K4|]rSCE/D$7BQFRtD翜vw+2|9T5!I<$W^mݺ'ŋ={#Sq˧L`08[>755$+b۷o̘1;wڜo>lذ t Ο?w~Q@@`.Z7F %)Kj'B¤/Nz`hzTߑ=.@$j|M]t^<0$5yV4a_q7RׯNgϞ[< 6edd<==Q֭[L&SII)""bʕhl-"""Y۳g><.Z:_77SN)**սzȑ#~@ Pʢ6x?d:d蒢OÛi49ܮ-E3]m4I\QB"̬ŋrJIIQ h$2-ď… O:GN&ϧϘ\UGpQn?3"5z>&IGa^ fݡ0w\.7Lu՜2JLs<ek3b1.'2o5_KHSdɊW|ncHG@ ʕ+'N^jLaaa!##CR=ʒU]] N8q">#Fpp,FRR3f(** ())eJ:&D?V gNtTKMS$$!#%k:&*lO|\]sђn;u0%>^=T`7Jʫ;H< x[_`NՍo} #A#ѽ hX @@aiժUĤ$UUUggg*~WWWyyySSS,mVWWmnn^TT7 Xr%D:z{KK… ˖-ۼy3L A}M4&8$Q:y2euXƻ Sn{R}pܩU̿vvu/L} 8*mUlMaMQjҴ__Z !z:֪bHB Uជ32B¿ 4bbbΝKYfffAgggUU 6>}K@" ْ%Kׯ_Mڵ KTRRrY$e2p-EIlhA7Չ3yx.2B"EW5o _EH#DW7E-]sO$q>K+@] tSQu@ _`˗/W3g-:.!!{9s`9ʕ+ݸXG3gĴ\]]KKK{|*K_ehKn xZJZbV@x;ݭLc熟Kas?U?|m6A̸ä|򲷷OHH sjhW(˯"b_EŅ(G|H͢ !>@U? taye;!@ 888nݺ4555iiiNNNllldIVQQ]h4!!!||rDGG3'OB-r0]V0JYyC #A RU;_S&q2yv_Z:^T7$!%awG /F;(++kJJJ^v:;;WX}o߮ϯZUU}`]EƎ՘,ACs9###+QGG'222994P3PAAsʕVd^~oaa?:qdqqq---@WWD꒒;E) Tk,[ Ԓtѕ mh˵jNQQ-5_O&Zq,u|&]݇Gד-l  ~lh`.4`hHQGI MREikk#CXXRWrHP^^=mmmCawwwss3>C޼y+\zɁ$$$?榦&哟. rrr6CkzB 'ٳgd2СC?qf_B_D"1b;vؾ}%K%]BBBSS3999'' K,dɧ1...<<̌D"u|||Ƒ111fffϟ?ߴiJJJ>}5yd;ѣ#hU~d}YZ?o]/k_)?gt?Ða"j&~]|^2>rӓ7Vz?%$չ'о22-o,L}te4oį Jm۷CCCׯ_#-""pĉ%HOOvwwz{{{yyΥ D*//2?s e' ϟILL+K,;v yᩮ LJJ:s挭-@LL !ZCŋ~p r+VL}:Ld23221ӧOX"88 33?exxx ###w]SS4|m:::֯____f&z?)euC"nR6w-[Xѓύcw 7Eo _#%|da~dq'OAJU[[{5hU#gϞ.C g۷>̚u 6UbbbPFԄ/w/dNr]vٳg?`_{[B5x9+));wwSSS{<޶m۶m oΝ;u/5)SPTaa3f|GVVOQQqڵ---ا...߷RSLy~ """0@b񸸸 vttn;,!e2񇅅 Jƛ0AsǏo޼yȑ("" p…#G677oܸBEEƍ0ǹ5XR<0aBDFFLٳ&L4M]]= k<߾}Օ1h J7n,Vї.]Һ~Çaׯ={!((`0͛w(ώ=ЌJ߿?##CWW_^^ӳKݽcEEE>>>55XW__Ϟ9LJihh$%%UP(-)((P(|..HiiJaaa!!![[۲*www11aÆYXXܻw_VWW{xxHHHh4}},,1snΟ?[# H$CǏ/((HӭQM: 9)JKKDDDtuuYiii]yf6"##%%%͛38qn^^ތ3BBBF]]]vz/Ï=6muqq1- iӦ%&&?`0^^^FFFu񟕕5n8AAA 77ʿٗޖPjM yC MLLH$녅 /|2nܸO>y{{+))=|0"""??3q㆕cdd3qIJ2RYYi&aa'OFc)Ujjjqq1۟>}?ݻK.Ĥ Ij IDAT$UUUgggwuu755ŋmL&ÃN8qbժUk׮:v옽 @DDȑ#޷o;7BqqqPPIpp>˧W^upppttnnn~wسjkkspp~:Kf͚w^?Z@sMMM'O޽;gee/_nٲӧO/_ყ׭[8PVV ?]n֭[V\YSSe:'@ Ϗ'99ݽ2sǏZJFF"//% hPTVVN4I]]}۶m{:u*// vwT*u7*++--- Fpp0Ɍqppv횉I9͜S*++Cq%##̌Q;}LLL6mёjnn9mڴ>Kۜb֭[撒"""O>ݱcN߸q9MRޯ UUUeffiӦXss"L… ˖-ۼy3L ֓ {{y566HII|:&evvvN4 ǿG;w~w'O /^vZcaT0>7dN^^^wwwrr2B魏Lܹso߾}e5551czH'pttݽ{wSSl~ѣ-%KZZZ?^[[+**Z]]c "\jmmwLnjpBr?pۯ^zj,/^9rvicc /%%% YWTTTN8WUUMOOmNqV6l͛7ߟb]]ݪU&NuM/_n``eggק`os??? R\\ C.YdΝχC\_JWW;,5kfhh3g]va)JJJΞ= ܌+Wܹ%OQQBFIAAaƍ0\n޼.e fdff޺ukÆ ؊أa766Ϛ5+==;vebbez\9/~`|zx ??ܸqP#**DBBB@@BTVVh8٣P(trMkzy˖-)))rrrD OzzzLL<~*++ aB\jӱx&CJJJ#FW &9sFNNNUU522O7!N JFè07p%%%"#Pcׯ(egg'++KR)Jzz:6***h4T&Eqe-"LRuuu:;RDrC pƌX:@ uCM\8C }d sNII //)S &++{ycc{a1iii[n#&L`m }kҞ{zz>|8..n„ /NHHF77~7n܈#FEPXpOⅬOd,@r̺_WUU? ׬Yy]]T@90 ?Q=}X?0كr_xD&~///{{yyy^^ހ;wd2{l(Ξ=2c{ kqAnƒ\[|5@ KB C  /Xv^:0_P "XTToffM/lmm/\`ggjjjҜ"##R8gdIzL3!**h"L9022ǧffffئ>T[[[\BBBSS399BVVƍᒒܗOOO/99YQQQBBBGG}nbb;~x:nkk;XnݳgƏ/''={HHH@œlll|}}>9O===>>k655utt8;; U\\\``ҥKkkk(޽{ԩ_~=pFS1663gPbbׯ}%KH$ҙ3gXjĸϚ5رc?NIIʓa̙ d9bS,OٻwŋEEE/]t)zP---w4cbbb&L 6455O7[[W^444ٳGII)66XWWwŊ%%%jjjXWh`pجYv;x/$$o>mm9s戋1??0V1HYo\XX8vo߾]zP(B//K9sիم=zãVVVMMMъYkk+DffQjjjFF=cǎ K[hh9s9dfth[VnӦM7n܀W,X[[\2))رcMOO޾}{[[[pttܽ{wNNQ)"##lcooXOOO謏ƍmllv޽lٲƣG(|%aaٳg[YYegg;wGP(666iii^^^OHH`w%&&}ȡ߃iӦA׏9sYn]hh(Fqvv۷/ СCǏS9łZqqaTTWjB|xTT_bbȑ#1ǥ%{;@ϵ\׾Z,$K!<= J)*(*)ZTTU!$KEִڹ~9{̙3gfιy/ ;SSS4MZZݿ1nOhkk_x 5 66!((˫6wIFFfڵǏG[ (--{F~梙-{OVSN:5j{Y&Mb7%GQ@|Wdrrr*IշǶB0ILL ++HU~#~X:vH#k@ _YfIIIAASgϞf⃃X}ݸq۷ƍ333۲e ڲ@ 汱VSSknnݑ#GPx322fϞ-!!ƍTWWW|P`ķPQQt⛤,ȇO:5Tvvo744|⅍˛KJJw܉ @ ggϞ{nKK HTWWqP$~nnnXXXSSBQPP D.Ȅ 1D_5@ ~E)K__?,,lٲe{0݌2--ٳg{{{D"*`K.]ZZZH]]RxzzzzzN}b`._DDŅ*;˗cG2e 1UK˗YXX!h9uT|EP8999g͚E"VZE$U2{@ 3,NNcǎ:tKl2nܸݻwD OJJʕ+v+AcŊ۷o믿Ξ=`}Qy344ھ}… oܸ@ @|rʾ},Y2aݻwڵ͛730|ѢElllN8S}||͛7rssc"##O< ]/]; GUU  ]n"߿՞HPP@k.t9LUUհEQ`̋/FZᶶUVIJJrss[XX((( /^i1wǏG@ O)((ܿ ׼y>iӦݺu ;EkURRxb111NNNEEE|t"xuMMMNNqƭYQII… %$$899eeehMŎ92n8@HH@`c sqq)((R]]D$Z_|yҤI5&&F]]*y­~N8D"IJJ:99yhKP薉'((hر eϞ=$ÇuIIIqqqihhDEE 5ƨ:UEE[ZZzٲe@|]tIEEc̘1>>>X3 1&@ ~N:ehhcuuuf.냓{sΉ9;;?zHWWwċ1,,VZvmJJʒ%KcƌZ݃ϟ?r3EP={FYmdd%&&rrrVUU)++Wڪ:tL&ZYYeeeVz1[|˗/yyyYYYiii?whmm5007n޽{I$ׯiii,ZۨIII߰aTzz޽{D/v3gBCCLBmmmWp͛ SRR֭[vDヵX|| Ϝ9RPP*]bELLFQQѾ}***C366ɉH$wtt577{zz'&&.[eˢ"{{ &TTT۷*..~P5" IDATW-mD@ ?MM+Wߋ-2e_bbPH$RGGGii///;vÇiiiׯgeevss;wWHY8󮮮6/_zzz`}T\ku֭ &&&޾}JYth޼yqqqt,www66Biiiڳ_.++STT,_#**YXX677GGG^OUTT/)))|ݎ=^xqAAARR^:~xNNԩS !poɒ%QQQ?>}:`ժUrrr7oބRVjjj||Ν;0cҤI[l~Pʊ>>TV/^ #G~GGٳg:;;3e! n]]ׯWpႷ7zH@ i "- }}5L6MRRRAA-(( ŋ˖-̛7/..".Pkk9;::M|pxH0 t碢"*%C*HD8O7u&>f̘7oި 0ߌ7o޴!Hqqq 3fc\* VJJ*UMM UtxEE^tJuꁁIIIPktK(,,ű{n%`;88L0D"A*痕Pɫ_ gtc!C4C__ &_ׇYZZ׬Ygffu07eʕ#m^^%+..?\bddtҥd---iiiss7޽{k`` öetLC탃޽ˌvZKKK谑m˖-TҰA ;;;]9^>/BJKKDfΜyAlgϞ͘1CVVz숍UW<A1HB - 7o&Ozf}(y Lbaapܹsw)//QxpmS|"%444h:uӧ鞞 &`p#* VTT|$QQQ˖-;s֭[*44TZZ:!!07Z'''C8Hyy9ՎwvvǏojjk/ǘsuwwO8q(XSS?@1?1@$$$XJ|| NT~~>޾+""_ ? @1HB ,Xƶf͚w>|ayyyVVVN:rܹs988tsN>>> Յ3ԌSժ*,˗/aaaO ttt7nܭ[m۶i&&k⢨8o޼}Κ5ݻw{Y@@ ,,,)g;;;|}}O8qFfv:s̀[XXظ̙3^u떩)]wBWWpmϞ=a-_<,,ȑ#!!!7oVTTlkkcޓ'O^tO<j*m+<==KKK/^LRYYY__Ϟ=vvv.]G ն#2C ;ѱޞkkk ϙ3'77D]]]KK 'ÕnPPdSS//6>>$i "?ee;w-^x```)))л4;v#""m۶;v={ܹs q<֭svvNII ݻ7** ⒑quunn-[9r_UU5%%E__:??̞=L&33/Yfg 4@ W#Ge+ˤpEEšC"~Yrrr&**jbb1#wYSSs` [L@ <O\\jKMMMPPsʔ)ҰP7 KY;;}-YΝ\]][[[w |???@ <{l酅YYYϞ=۹s'UacQՍpvvNKK+**y&jtww&2-` wҤIvvv);OJJbC73!!.7FB c};wTUU:u 3RJ:SE|$#..[xHP\\lee%$$ť穫STT|aff&TAd0$gFmjjrss߱cGWW|XHH(22ё?$$Шinn677wrrrtt,++ )uƌ /^ dggOHH @ `ll|iOOu577 MJJW^*FDWW""",,,7o޼v>ŋSRR6m竈຺͛˗/N"DEEׯwqq9r䈣???}tLMqʔ)NNNgϞvڵkS!H#~)kPZr%''Pڨ"PAAOђalaaaKKKc#*t2 PwA @ LJKHYfffݻwY&** F /QSSG]HR0gt3 u@ s))) ARspp5#dz+**( J]TTʊF𰷷P^^ RNN]@ e8P__?y䰰0͈=8p=+,,ldd؈%;v `xÃ7(0::T\xqyy9fLLLCC.fnSxUNNNvvvYY󬬬J 3p`.:;;={#&)(( 222UUU?YXZZ^tww* »O\+??ԩ5=|02 ω'ӦM۷o_XXuDDJOO|T7ٳ[n]fªU͛7-X`֭gϞݸq+4551лÕ+W]dU[[ϟ{ppUKKKMM ۫<_/^`cc:u jkkjhUX1޽Ks[… 322y򤫫B|q„ |||&L ɝJJJ\\\'NlhhSBLKYY[TTTFFc篯ojj[7I&IJJ|'6TnڪE}'){PWWgnnohhtRpNNwO_M~~Ϻxxyy^C5Ӂ;@ FhuuuDZwCÃW 3++wѣG*/?7o jjj$LP(p B "Pjpssvttc| x``CRRRZZ@ |򅇇ohhh־yFNN֦VAAAPPgĉ?ݻgttt~0*֘IIIX/ⴵ͛7355N}||lD"5בg*}]YXX\z5))7cƌSA  joMgggnnn{{2`‚IS,,,lllpc FvY&LKIIUWWCؾ>EB$) f%  <}PHII|D2ξn:@KK9srssAAAMMMbbbڷn݂g_ " "''ٹg[(..dpByyy...__^9[ZZǏO"222sD]]]nnnAAAHkegg3333QQQss,II… %$$899eeejkk>| .!Q(gΙ3GJJG]]ʕ+?}ߟ>}JKK.իWVVV>|`aaN vvvp[x޼yՍ˗/3gӧOWdbbbGZUWW+))ċ/rss#%NDDd֬Y>MMM˖-Cɓ'?xxS( %%%NNN999???=Tii޽{899޺u6ݻwӍ;BٳG\\D"]z@ Phiidɒ"466 gw}?P|׭['%%ť_ueb}2eҥK/^hkkuͽ{63H$YXX?;&777yy;vtuuagթɓ')++緱1c-ϟEEEM_EP ~J"&a$66I&~eܸqx<<< cǎ-*Ǐ===#W122JHHDNNΪ2h0%+W,XÃL&8p¢sCC)ɓ'-Z{ P55hKK+V\544wW>LRRR7l %%w^"HwoƦt۶mRRRuuu阀wUCCV}𡱱uPPлw?n``P]]j``0nܸ{Hׯ_ EFF:::󇄄ddd~%%%%[[[pcΞ=g| PTTyyy,}ԩ!""bΜ9n#͜9uΝTW544̝;wҤI9q$%%wrQcc7o`TΚ5o/_h9s&44~K,nmm;wWXN:ehhcuu6WXQTTo߾ Bqvv~mvϞ:ujݺu[laggƞ366ɉH$ܹ… P,l>|hjjjkkkkk[ZZzO'$$@鈮jaIJzzz͞≉˖-pqq5T KӮ]>~(..srr***\ [@⍹}MMMpp0Θ1UAAŋCt5kVOOϦM$$$233gϞNfkk~!2_MJ;w "ߟ;w~3/_~epp˗/߿ֆݽB|9##A=P(O>={ :nJ̙3鞂 >r.[7((xyy돑}6<… b`d 6k׮222z^h]O6m`cc#zjsfZCCC[[\papp@ ^i``gWW^SS-,,'N2ݻHKK[[}}}5ԭǍGB8مN/jvva@LL m۷&MtY2AlX;`ƍGspp2 333, u]]]^^wQÑ@" ǏBCC%Kpssgҥْ᝙ 8y$`fPcǎZW IDATwر𰽽sƌ===tǰRss3x݄}ʤfϞXSN= 810T t_={c)fwEʺ{o߲`288rʉ'+))1$>;[n[XISNŗ ߈˗/Í7Rph!O? 4E*<4 ӧO*..niibeemoo ~:???%G&1v^^^PSSS]]-..>}1c`j#˂]O?~ldd4k֬۷o <\R=@?py(,,)}?~˳7oތaU 1EEK.9:Խ{ܰ$Hnnn 5|;`;())C9_ bbbtǐA "##iTۗ, j*)))ooܻwOIII__KC 3Qݵ8=zdoo/--MxӧO2… ˗/nݲo8>';YYYb``׊J7;;6uuu7\r%;;; >|w@Xt)999әyv I.''QcccYYw|A!SFFfΜ9`ggkרvimf>;xF;(V[nM8~@IIɸ8&OMM8q"rL&L6D _lllƍ1c deeϟ?\QQ!.. ===}}}]]] SQQQee%L&wuu}𡮮 BeeeMMMo߾HKKkkkTK)ܹsMMMsrr`0íF///555AAANN)SbHoqq^SSۉCx7oZXXH njcaaeӽ"vjXDDDll1c$$$yFUUU__?&&lcǎ鉉qqqHލ򇛅[nMMM=~8m >&00p̙to&ŀ:T3#0''G|M&Uʚ5k ?qFWW|x &V.7o rqqM8QDDfPUU-+++((`gg7n`Zy.]400%--mnnqƻwrqq >}>}://omm9Uij  !]$<<|ڵgΜ;v,ۖ-[ ɓ̰0MM͑ޔƍ`iiibbr0{{w e˖@g Ex;xNYXX̘1C[[;33PMMBUUsY&::zմy^Dnܸ y<;.[ŋG\M+k#i +--*o`A|D rڵSSEwZQ̳3 2O B9siw|a,XҥKGpႩ$m6ƭ=,C&nBġ.J/܀@Rg[ʴ&*;m~l zf[&DRWW566>uӧOӡ鋂„ RRRDEEyxxɝ;wN: /liiэ~f?~< 4P(t[244TZZ:!! Ν;gΜsΔѡ@_'@#e˖9sZMPObcc/^G1077˗/drmm̙3|bbbmmm ZZZǑajVVVTBlmm^m۶g׮]T=͛ɓ'c^~?~|gg<9` 2GBtt44%kii!ɘ$[]]m}3n3*/ njj--Zv̡0no \;+++UM'm1cPL[J"MDQ gϞmoo\xYXXdff>~J,pQ:a2Qlllcc#x1 hKDn_UQQQVVC&&&Naaa"""_=El ,`aatxxx3 ++UJJ t5o__ߒ%K^5~2̏sgg'U6|9> GFttǏj͚5{prr:Ν ?`x[[[ALMMCCC"WNMMv횂>˗K.uww03Qy^%z_z׵yѥK1srrjlltpp?W6R,^Ѐ4 v||mڴիW ߽{ƍnnnt#>^SSsӧOzk`dϯ> L.((Xvm~~~@@t=yx`vv69\NNδi===[ZZnܸllذiii2 QOzUx;{# -!$III //Z`2 KKR=!M>~(!!s΀}RoDPPZ555Ec19Y2~8""bʔ)ߢuTx)3Ff<++/}!',Ĉ155+,,ܶ:tyoW^^~Gb"V}}P'JY}}}ZZZǏTUU999qrrΟ?7jmyyyMM؆N))5k!($H$%HU5;[RLLlBC ,ďR;w\ʖ@ |KA$ixzzhri]INNF懵ę3g~HJHqq@ o%((yKJJ?222 vvvUUUCRTT4qD %Kܸq;{u1cHIIܺu ;(((8{cǎM4IXX899YPPPPPX΂}v,^AAABBbڴiX2:c AA7oKHHL| &&D`~~]]]^|eΘLLL͛>&#w]lY__@ HTDƍUϟ?_QQ˗/G޻w{Ϟ=4̙Ǐomm#''g{{{jjyaOO++L?wɓ'CBB zg@ 0c7>>>11QLL PQQ{ʕwp=޽+**6;;X!!!yyy~~~}|YYٱcͫ?JpႼUm/_VTT<UUՁ7?ŋ߿SRR>>[n !@ ]̙Xx1 naaXr%khh $'Npqq_0%)) n mݺzݸqcrrrWWW~~Vvrr2ݴtғ'OBӳ`oo Xgggiii<Օ;,0N>|ͅeZZZo޼ `eeLiYYY2Ts`ҤIfffxIÃ@ HJJ:88ٳ'??wcǎQrh@ ,Aa@~.8  @ vhf> A,1IUU?'3twHMLLГzDDB`Jt@EPS"c{Y||9yxxիWoܸ>lݺȑ#; VVV*KU}qqqiiiUUΝ+** pQWQ~~~7Wlo ͼz^__@NNNg m0`{2.FD@ ,PFXb@!|@5PoNJY;@ X[[[[[geeZ)***((:|ԩSGToooŋV{]lƍGd RRR v9,,, ӧgǍ["xa.@ P3@@<VkP)) "{ZYY BBB{zzeee1cǖ-[?nj cX)jAA0}t)))ҺZZZ>}~2=Y[[vϟ|-`0;ի$rR//'Oܹs'66`mm hoo_~wﺻ߾}O[tIR^^>;ĉ0e̘1={34iҦM~ ŋaPP@`E/>oZ39bժU#]FDΟ?/## Ͱ?*[,y -H@ ̪F(Hڵk钒@PTTܵkơRVVBŰ@_… VVpjkk+%%%//?gΜÇcGYXN<ѱqF ^NNnǎ0)uuu{n gϞ7o]XXrttn׮]SUU2e'OhuIudee}%::N,]F&/_ 3DEEfv\jjje<889*1:)?yyy)((pppikkbyecc# ߤI<<<^zgٳÔ~S:;;;::Fe… I/g}^hL&c\=8|0\Kˬ߿wppPVVu]@;n @={P4z6Zoy⿰ ))8$e }}c C욚aaa!&MڵkW`` obb"""ϯ7Ԋ Ƙ (qaeeeKKKsrrNSUUEzYV.\G7zӓ"))Z--}usq͛7o޼yܹqqqwߘ\rʕ}}j$Jb~𽠠 8dBÇqHTWW_>44H+fK|3TTTzT0?jjj~:m:uy'ZC!KOEr111|||h3R7o"iiiOOτЁy!mG=snjO/_ ?[/^d Y߿Kj*&e䧀 0`0:X<qqqGh'ND`hDWW/))5k dӏ:l&&&T*UJJϯڵkBBBho:'-''FnMAL+v%n6lؑ#G֬YzŅFqGdee) ٿhĉ\\\QQQT*UCC#&&%2 RJJJ䠦BBBiii7o466@4MJJӳ. ܹsD3B! $$ ZGGGttQddd<==7ѣ in{ :88|墳ȑ#Ν;QQQJJJT*U]]b Ņ嗥KGDDL:ۄ\GooCL0*LC%!--- k&CO }$$$n\b_|"zOVG7n888R褥1ѡR ,ӧOYYY4.$$D}||Z[[-b ,8<##0@Jct(+**b„ DX~pnX?֯_?AG!**2m4󣰴lnnz*yŷo>TWW⸸ݫf͚QQQww /޳gO`` @[ϑHOOWTTҪDܦ&}}{:;;oݺ&&&Nst:˗+Vhlltpp8{,:boo:ĵkע<* 1YXXѱhѢ={XXXDEEp ίþΝ;gooess$WWW:7cƌLwww"ݻw]{nÇr)ʮ]](Ekڴiڋϟ˯^RO]FlZ6l(,, ڶmBHHHtt4qkŊSN?f̘n:K.KHHXma6}Çܹ388---x loo/**"? ::vXUy~HWWhU-144ZreLLi&\\\[VV0lذxKKgϞΞ=)۶mCoࠈbO=nܸ1**䷞ ۠OOϠ ii^;%z^Xkjjk.[ݻ*.jjjl$׿qF```LLwpp09OBB֭['Ok.JEEEKK JάYf!QMMÇǵ,B(b=@-N "W+Zoff&***""{Iέ+V@&MMͽ{^S[[LJ8Ź̙3ߟ?>'OݫiKk_oh@fS?ܸ?\r$??_YYYIIș`J\رc&&&_~-++[|%Ke˖u 222DGtB<ܒv!}tU$ĉ+W\v-9"BĆ Њ DUNݻwQΟ?_EEؒ )ݻw`̘1iS!vlmmԈ VXXx-]N}VO1{cdž6t=MuZ/t3 '..Ã۷oWWW;hfee544|F&M"'~)Ԗ' ?mAX<jRb]!g(KLL\pҥKyyy/_L4a%-))#å,Y˻o߾s綶SRRv+++k׮][n݃v.]lddb ԚԨ>x`ݺu^:}4Q(mqttsNHHӧOKJJK(a ? x.o6`mb0 ;2k UYY??1RRR(&޽{ ,??ԨQ`edd8C^&))IXF}۷o:zuz:...b=@G: /Zu;vLLL~]|9ybў@1~X|1BYYUϜ9ZiJ(?h\nn3g6yPԩS>}rvv&N'9t:L4F//ߟ?;;;ƈȊCAAY6:uJ]]\Q^^^}œ9s-WOO*uuu-^ڧWnnn䷞`"<<"Ni e 0`1*uxR^cPP~UUիCCC>Lo޼9///00)%%E[[i^^%K,ZBCCc乩[ڵ+<<\CC>l믿ŭX"++֭[)ɑyyyWE0''0__3gtpႆ/ ,Ȥlٲ'O_|>꼃, t:ٳ=wuss3S9uoٲHJJJPPP@@Dϒ'//oggۧuuuLJ]]W ֖ gM6͙3[dImm-K=znBBB999=92d9v???m)7n~{;;sݼy1&&Аyc[~%&MaKKK _3TvX  ׉|k-[/^~ImOVBGGGD~~~wwzrx9~dy7WWKOjc3zg ?~\OO͛QQQK.]~=<ǥ~򤤤BGWWW!^~V۷z䴴d"xɾ))) <<|8)55+WZ[[/Zݝ$;v쐓;|0j@ :T]~%77wԨQiG/**277Xfaau…RSSSֶΎpㄑ#G~YYY4ju 'HII3… |||4 ͢L2ew޽~: KYYرc^"?A4d"`t'''4[;(d$%%.]oM0UDQG~@_;^3pX'O=y=~T #Gbڊ !0La'L= /ibRl@Hh oct+ X Wcd'K^1|sO]@bqϟRև\?s eݺu= Ŵ.^xY䈊М".DFFr4-&&fFT666ܛ6m"prrrtڼwܤ;vҥK=l߾[KKuС/_,--]lQ֚xѧOPH2l޼$11L4);;[RRp100񉎎~왉ɫWvIP_^^~555l>硡999222UUUgΜܹ=+W100֭[PXH]]]~~vggn{o!L){F.]rppjmmGz]@ ۶m4i҄ ϟ/))y#GwWѦj鼼݆}?{ϸ` LrQ s@ 8yyyw1sL:J4tСdBjkk:t(Z'GbbbddΝ;PAA%lRVVJLӱZBRֲ0e||.a166>s E >}666x%JUSSKIIAY ezz={mmm*++8kŋ3IIIhOXt.22211t޼y6lhoo_zBCC ҥKzSίӧ땙3go߾=55uذa6mꇟϔ)Sn߾}ĉ>>>%%h???eqŋ/R(ee t"9ܴiYh'O$!'M8?@[(**N6}q///yyM6mٲӧORRRt:P(vTTbjj&(^a@$%%=:%%%77WHHrڵDnn'O455ihhDEEݽ{퍌ٳgcbbtzYY9;ϯ]uذat:=,, KJJ͘1cDӋɾS5k>6qʁM;5ƥKVZ55;vԔiYSLa<}-f4t~v,`-KRR222244hƌ\\\o߶qqqJohhBR322YP'K~BB‡ܹxPAAյO֫^-?uV?1ߚb6wZ0 ?kȟww/_V]dIBB“'O6;w#qqʩ"F(;U `#  @*G!#h48C A766.^xȑ666O>USS;w\yy9BP(DظpBYYYAAAmmm#W\P(o R#G;v?Kjj*??ڵk{κ}%$$DEE*((0iii})))}9""=ױcEf͚5l0*~GQ `07U*>gEP@ p/ 64zFJzzz.]0a9]TTtΜ9MMMDǢ///pttsNHHӧOKJJ Dzz(Z9|phii122jhh ssskii!uqqhAAA3f̰LHHpvv700Aooo?dx*m7onذLLL.]DD~mPPЁ<==LLL233P$YMMͦ&33#FDEE ܿGk`0 hx/|hhx+3p-kݺuƞZZZ!ݻwgff.\߿F999}lDe˖EEEf_v 1&&&V&vlkk#lԴC[[YYihhe=z )lrww6s``dee%`vrr9rdDDDnn.Q[nIHHSAAxL+++e`0 60\GAwAEEꀀhkkC&M;v,1ӧ|GGG!!!>>>55{vq277WKKKII/t۷okjjݒ*!!1e}Ǐ=zĚ c˖->}\a_o~ɘ1cLMMz*`0 oiYAAA//jeeP&((²gff*))C/^,++?~|hhڵkz8044daܸqD5T*ܽ{7UnCLa"&`ooѣwuu=0 `0ԅv+""윝rիWS( ijjjjjrJCCÕ+WS9rd}}=y յg%%%pK,ٳgOdd$ߴ#Fׯ_TևB񹺺߿-%%X`0 `}2`0)---#FE) ,xUDDDgg'Y! ~hnn$%%U__ɓL"τ ƍBPbss֬YJKKQ##؀qqq[[˗=z_-**kmmyqII255J ϟjkkt(11PGGg W\@ݻ_WW7##CYYYJJJ[[ٳgׯQVVKFF888`ݻwoNWOs~ v1KR(33oӆ<}TNN >|H ߗ  ;\w޽jժ?jΜ9ld5F@@~~~dOpWWt/F;|pps,Yf͚[n͞={OxQbS/C:99{Ǽ1c]6mڴ/Z(..Ǐ_jUPP˗/555o_RR:::?B\}^^^rrO]u999?vJϯI]]})++Ƞ?Dzmqq~`};H52ǗU_.**:cƌ.ώ;vm~!!3% GGG]dtuugU@BB˗/=<==K2dUTTPLkhh0EB?W=u@5s 2M;IӂC}#zZ'***&NY]]}MD}Ȑ!+--]__&!!!((hffvEM)WWWLL ??Zbb Fkoo'n$++KP={F8q&aaa C;vLWWJ6,00iQ(ӧOΝ;W^^l0񣷷СCEDD&LO>q…dKE W}Y FR?~\XX爈,H###%%%-,,ݲBBB3gg]ue koo*""2fnffF#KMTT"J`䎎QF xzz>y򄩙n߾&''GRuuu#""F)((8vpLXzooo)))aaa===&w˗/{L D_~\ERÉW_>|F[YYUVVrs"zjqㆵVoߞ>}`~7tvv477oܸQLL,88&ÞӧO81::afffUUUfϞͽjժm۶ikk`-..~С0t4--͛77n<|uu5FC^x`__ߪ]v֞:u zܹs޽+,,L:%%eǎ...&M3gNfffSS[TT@}TCCcڵ.\Bؗ?((hӧO {ELL 77evvK իWT*㾾ӧk׮-\_z秢r֭͒;883&>>攐HOOݾ}; >8쬥q;v888TTTP---FFF AAAyyynnn--->>>{USSو#߿_RR"&&6nܸL{{{//yRb~o߾3f677oذN|沲f͚%((hkk;tLl...4}?nkkoذaΝ;ׯ_gndÆ AAA|||ddgg\WWEz*%%ʕ+(p."?c [[ۚ???AAgggyyy۝x5r[bHHH ӧO W\)&&VYYijj~@D;޿?!C޽{ɒ%W^-,,\po^^^dd! el߾}FFFk׮moo677͝>}:'N'FF)Suuufff"""pM###UUUz<))ҥKcǎ_^ uq@[[׻0>RTT4`~E_>ooo"ܳfB?w4'OrA(--Gr2܌R~0fӧO³g&N?}4l۶ h ƍD4G~ ?lW\!/\DDʜ9sh4M6J}6״iz779q˗h.CA :ChZfݺugXX؈#lmmQьSWWZZRRBo>4VPP%#}pȑnDSSԔZE766D`*?{B#b@xx8VǏvvv#F h4OOϞEvisѢEܯ_F?;;;t:9s`AAAB P%+** =rHF)+W$#~~~AWPUU JA$C !l,[vgS F' e #ʈ /B ݻwhg .&//?a{n2 T*̌1c 6ÞWFk."e׮]~8@---gcc̙3Cv{`I Q`w---b0JjbbԀ dzzz/_D?O:'gg'6%GիWh󮮮=-C@ĤIƎK?}6E@Ι3gXdrR3gtvvm222փR#FPVV&盙9s -++555EGGEɦt-|yMM͜9sOVRUU$d#77WKKKII[:[r;6+lJ]]722?CPm`nnNH#r@?+++_xSh R(͛7s8}`0?sLNNN|||DC?d}h""CAA)`ԩ][p}y4?PSS#ةS;` ̾'N$ܤ%%%'MDTڸq:uߟhYׯ_ssrB!Ar644;880 L)c0  m{VD@mAܬYBDD~t@W@kzSʂ ^zM44bjss>w[<'<|{{'OnݺeeeemmR4禯Ʋuuu눹TTTqA|.dի$֬YwV?Ē+yyÇ?yd̘1YYY=-B}688xܸqT* m{ZƆ> 9"PIVVF#Vz_iiiYYY/_dUشQ]]9ׯ_:q 袵j-8y= i , ӧO{Չ!O4 6_~M6 ΋T*-F+Q"""윝rիW3='OFMwZZ!ˏW[[;n8"dbaau…RSSSuvvN<TVVFȃ*))ԔLs?W?91(uwssC0>HQQѣGPJccc>aMW\yq544CA/|ѣ}AYYرc^!Onc6 n$6з55UNNٳgJJJh.2O>Eʏ\!tzbbbKK Q]ٶ7Ԓ%Krrrf̘v֭;wp( L_~Μ9yyy(LYOڲ˗/?z诿ZTTZSS㒒o8qb666_|A Nfdd(++KIIikk nذaȐ!\\\GE(Jzz:///~nwwwZOOOb=rHDDO ›C |||={fbbի;wh4g]^󡡡999222UUUgΜI?d@www777={vGGGff;w߿MDYh쉉:::WVVnhhr劆 o`~x. N?;44tڵUUՓ'OΙ3ĉǎ#G۸qc[[O|JZJUUp B֭֭ؗsŋSSS.]qFtuu ى{{ t ---ZZZĪ}fjjKKK"@ee.\xⲲ2BU={vnn.//oHHȺuƎBnDxxkzz[h;wnɒ%k֬uٳË)''}vP~~}}}t4##cV-- IDAT zÏ9gkkǏWVV2E&)Stkptt{5y"~ ӧIJJڶmۍ7~;v_ti >uk^IIٳ&L jll,++5jT_h}_O8v ...$՜2f̘k׮M6m-kkk_^ B''=|c0l(..8q/ xυ{AAƗH ~ `9HKK{yy`ܹs8"..S*<|*JD<`~$999zoѣG0 /kYW\!6ڵkXpp0eeeAD`0x_`AJhY~~~[nEQ0 C ֲ0Aׯ# `0gΜ7oΜ9355)ω'޼y;u W^++/^1bq_7oe˖QF-X`۶mkjjBCC \-X. *p$a\SUUpKEE444,6sL۷ƍRcǎE&RO^^^999uVII1cyzAkkk +W_6mIdhooܻwozzzpp0qJjjjZDGG͟?յB@aa]@@SvYWWwU_/_~ik޼yDN:eeeE lsaGٳgϦR?Wmwtt|Oj*{{{3YR)JZZڬYtnnn;~%KիY]O:uǏS,T8APPC]]]L]]]ww:yyC͜9SHHZZZ ޽{9i$aaǏuXL~޽{q=x7O O7nܳgȉo߾uvv633KKKߕ͛x̙R2f̘dѣGuttx}x<<|8y$kX BJݾ}{֬YÆ RVSNsssTh)?zY:oĈdc/%{zz[ީ[Amhh#f %!!accIφ U+x𡢢"e `QQѣG6lذx &5ՈB~/^7;wM ֲ0>1#&/Tiiisssz@^^~ݺuݚn޼IX]]]?7GbddtСIIIJJJnnn5!-ڳgETTƍѡ{{FtJ__ƍ111?8wBll *++XeeeDJ~~>J-,,$‚O2eر gϞEW3gѣGᤊT}-ݻw]{nÇ/kccuɓ'ڵ׮]|]*+++++Y=e綶z/61ZZZɳ=έ7nz=9xyyXbԩnj3֭[G|fOGGÇ{;;;tvv ;LJJJD\v]?~k.wwG1e={5k`۶mYK:99ܹƆ,xsdOOo+1VP%%%Lrȑ/_*..njjB^{6O=Tprr?ׯ_Ϙ1x>,''G|dĉ]NgKKKs͕v ;(rg+4UPHSQ**glرrΉNEE * l7.,.v2L$͓'yM~\~;|/SΑUUU/_5j"y5L^xX,~=N9st"111x? (++~#N1bD`TTxȇaaahߗxQg ܿ899BQQƍdr^^@ 0Akkk;;;h p8555xȜ9s0 { c><%&&Jbb1e0χ0-1娨( ”޽ +;yd6YLʕK.p\xf1 #”)S޽ ,2֭-"R(xxxt,r Q'O:x?bq]\\!;vL:׋-PA#@g_x3#سg㙙cǎ0,>>o߾x3622Darrrb/ |7v{iii@ Sv+O{솚8<(**677ėӧO?#ȖqtttF xRPYfٲeRfΜd2U@q(::qX(>y}\OO… 777:t(t!C7G:~:{J&Mt>(++_xOweC|ˬX9B(**ڵk׮]% #F1b1 Bɑg^@,aaa~;w(..~Y|9|`x𠦦&##dZ7m4z޽{:#*Q\=~Ç6m|qxxDjKC%LUeee@VTTԬYRRRrss$6HХO/T)X,tp@kÆ #r 8<411#:::}E>[RR >xi_~lгSN/@dB ٳjժѣGAcH$RLL q}]w^OWY[[?zOn6oޜb 0VCCZ8&22RGG… @LMMwhѢCCCTXX(D"Tw6l؉'rsskkkq}.:thZӧWZuMxvUtttkkkbbb߾}e.sx :N\ ǠΝuA -||} Q ---FFF.5kݻW^=n8ةիW8sSN͝; qMJJ 8e.gh 5~tǑ.*4ޞ={vܸq)))+V$ˆ@ ٲgzB_+**""">|8'[e K\\ܨQ?Ç1wĉ!C{---Pi:W^OJJL7ŋDDnuttp^pḻsܶ63g#C}~SSS"mmm++Gu3Ν[UUqFP(K4Kb15<@Tuuuw~Ab}ڧx.}|u pM>ud2y˖-_}fFGGnذ7mԣGE"-Ga뚤X-.݄:+viv yj@@[N>F)ˆ@ ٲ & .))ijj"HHHPQQiii)++|ryy#IppÇCCC?+(ӧOyHqxxxLL̒%K=zD"V^4uׯxTE8pѣEEE6lٳ'RUU 22 goo?hР9s樨{HHGBP'MD"ggΝ|>Z֠A,--=ᡯa(+6lءCVX`wKl,//߷oxY,֖-[TUUI$RRR+,[ٳcǎ]pi~~Clll>z!C""".\p|#((EEE4 v˳!j̘1;v={vss;wV^-aeX3fؽ{ӧOLTWWWWWkjj~>nmm?VTT'557o&vk׮^^^FFFcƌikkKš.Ee,E"Qgyj@@ ~!ȓ;{pG ~lmm%:Cvdⱶz"zyh4c^pä: q8EEEGG?u%%5k.{xظj*cccf taxΛ7OCCN>^CC=p9`0aaa[ 'M $z% ?޽{wguHJJ")ܹ;{]ѹڽ{w޽i4u렋lk=zlڴISSF >\"3gTSSkllzgψ)))d0***ӦM{/.{MccҥKi4ˉ Uﯨf7l 7gX,:4p@6M̖/_w]/۷SLYbD*//~ðKB5˗/?comm]f.۷/ Oeee'NTSS#ꞞndSSS*jhh]wd[ͲCqI{rHd7TT!O_ԓGFœ;"q"W$<;!_b"rB 2.]*@ ={&5hp8)ܻwl_z*a?x ڗ@ ҳgΚ5 X@;AOr۶mѣʕ+L@Z@ >I˚?>ϷݻwGlG ,@?4m3@ -e!@ Ҳ@ iY@ ,@ @ - @ @ @  IDAT@ HB @ Ҳ`}"""0 +//khhBAAaH_{{{+++$@Zaa.\Ңa… I_SSN鹻>^SLp8,5;;x755uFFF,xݺu)v9`62x`񧾾~,:!!x+44А`hkk{yyIի{niiٸq#,nHhyyy >>>O>Q7777&VM2f|1 <] ?''ðgϞ/{f2fff۷o !!!t:(,,Yٳg.+0""̌d{d`ׯ_GߡY#[VPk. 6lذ/_"""E"ƍ544 ŋet|¡CN>=`fe]͛d2ҘLfrJ/\f9Έ#$Կ[ۗ`hiiB ?4׈@|=bcc/^?~gϞ>>>"̙3g֭111۶mã%$$o"&s@]]]SRRtzrbŊ]v=:,,۷o'WnnIQQ͛n޼ٳg'NL>]YYy]]]D533SPP8rH@@СC4ǎkgge˖H77w^*-Z/hu~M6Xںݻ!CխXBCC#99yڴiP ~٪U+**RSSJ&jUUU766߾}{ZZZFFFkĜ:ujذa]V"`ƌN ݼysQQqvϏ`丸 %A!+s|}}Mrsrr455᭚+V9s&00F͘1#22/___?o|pB k̘1={EgtC |ӧGK;11ѢEUUUGzjNNS@ dBBB/ׯ_#_ׯdBȟ g͟ ]p7l2gΜlkRSSۛޒcjj*1'^EDDdgg}K^n8p/^p\H&.yի׍7>|УG8~khh|>)c.x 6.\]ii)˕BɁ>>>&&&pWAs^S6lX||<M~~ի ?y{{988(((ș D.eee9D266.++Ȍ3l +T(jiiuxҙI;;;dKYUTT(**KpE#ygeeDH$^`llL xLIII]]]"B{{7oП+c=-#1*B0 b`"ygg q!>dB 6)))MMMP(3gμ|wﲳKKKg̐ܓH$OO)SDGGÝgOznj3'%T*UB("$qɓg͚E ]"bI%v4%wu)+Ha#y:93Z:K]Vkg]lgG@ 2HdFDp6P T*JЩ$2D&TBe!_CC7o޴O:hsٲe ǏYN*//7119r$ ..N:~eeezzw=JKK#Kľ}wޝ9sfҤIXPګ^EEѳBYY"4.EFF\pw@e155ݹsgxxEdamm!CdWKxxSxx8X"8e?"M I$ݻw\\\ի>>>Bp _EJ122z⅄P\\L4H0q>}㏑EKv%Ip$|Opi0NcccMM1G;K]vutt뉫_zYn;V@aa!(WXXիp媪*buR( c5u d:Dh4"b@HEBP  1 FB|"Ȗ@|m0 HNN޿OgOOK.&M7~HĄj\;d`` ]/pӧd藢[H$444u8'!)))kkCD%%%iiip' uttp6''G"}qwwrmmmqƽxn8޿>} K,G)Uvvw)++;{2\'e OV%j0iҤBb8uTuuرc;Kj+V6lʕ+믿KxDԟ;mroEZZZUUU?bee$[[[> U;c߾}#ɞr!!_gKh4CIS3L6Ƞ+0,:A:|lY@``?ɓ7ʈ "ruuUTTׯNKK͛7ܹSVVĺǏ![n%CBBB"##~!::ZGGÇ0°a:bŊ ߿_bȐ!C""".\p-,,uuuo߾uVbT*ƍ>|"##T 78pѣEEE6lٳ'~jAAAddҭ^:))iԩׯ_x婩\.jfff #--޽{t[[۸8###uuukkDe֖N577|???h©<{aJ <x[t%%%yNP@ OœCyUׇo9f{Nsw;L ?^d?ܗanEx䉖֙3gP=c!!!DW^ь@|RRRH(..655ݼy5k>:윟%8F ?~{ 'NA[x왶JGG΋i,]d2d20H( yV^{[s[{3~DOgggL&s8kkX, <=zCbbb$"`UbѾ,e֭t:]Zwa2***ƍFRE >,))IOO駟N:::III...>>>D}DZLh4*&*ʚ>p6LQH L5kĈiݺu۱cS]zƍZM- ~i.]O?O%Ax:b!@ :a0d*SE/{BSPנ2)0ƠQYtH444+++H$ݵ㥟Zv.N߿llrrr0 @nܸ~aaX@|i-  Cr@ >;좻.;>?!AŢ`#a$YJ=zh6йH4&& dB&Ir{rY&$)66OAAȑ#CY~e˨Tj|||PPǛ7o 'N$֭۷o5#@ @teH  "b%JzFS.D@,&Sb d?|P]]ݻwWTTx{{{^|eQԬ,駟f͚eH~z}x: 66$@ Ŀ a@m3˲*|F爄B#1B"Qd,Ye``0tВSvᨫz8aAtz@@@mmm^^HB @ -K u-@9ZUh336P(B$d,[ֲeˮ]v5---&9l0߳gϐ!CY,Duuuz5.# @ @|+` Eⶶz>KQ0Y 0N׆ ݻ Xtsrr?~ڡ'qIewڗ@ W 1D@E$]MmvIh׊QH@,J3k֬ݻw^zܸq,ݻwgΜ4iRDD TTT/:::G (HvA,@ ĿNPHdF}hlb)/K_.xmQP(dyR#[lym۠N044#ܺuUmmmqqqjjjGQPPTWWA,@ Ŀ EL $oi"BO}&hMnjcǎٳgYXXŹ޾}{֭t'')S0̸Çv֖N577|??=zZV⟊P(|UPP 'Odff{R!>L#P__[ZZ?~y 7/qvo!""ðr8%d/,mfddd2jjj|DAA!((HFٳg8_ޢ/G$Q)dRb1Xt^{oe;.PY[O Ftj7F;vxV$&&L4Ν䬬#F޽{ٲe\.766vܹG rY 2m۶*v@,?MѣG=1 +((())155yyy BL">}())KDžl6{---ϟ?gD IDAT0_&,Ϸ?~翜0 suuj0&&oW_իWO>}ᵵZZZb1ڨ!</...>>>//O -\088IB RjSZ{h4,hl7Āon :||Ȑ!';::Ɨ/_&ލ -[l"<$20jԨQFA,?HTYYX,H$zIUUP(r---fff,KQQo߾â"\RVVf2 ӧVSSՅ&ee媪3P(/^'G}O Xd 2vrJϞ=;6mڴK~pϟ#3|ڵk-ZO?Ν;8C (stL,-=u $A ^Wko𡥉ڂBOٲ=b˗Ɔd\H$RVVWUYaԚl6[,777+++3@ B!N0IAABOѣGeeX,VPPnhh(//e޽{aVヂbbbwUyƍFԤ {NGG% KJJJ Xre||9spy 5:n\ lP+"YbtACB|:Ȗiiiwއ I$>#H ^~  bnͲ111P(++[YYikkVVV|>q)FDv=zXYY=z^IIIuuuSNuvvpȑsA]Q__ܹuuupЉaÇsss1 [n1ի{niiٸq#aKh4Z^^ӧOֆt##0ilmm񳳳1 C |}}'LP^^dSfRSSSRR4hŋaƍu777s_tٙ(**o|>Ɛ7f̘={X,cǎN2^^^ jjj#Gð͛7!0l֬Yxȵk0 ;w|ѣ{쩬r1 ,fXs`CCC  WZZZgepႣ#p8#Fʂ񇥥+?$>YKmP n>]F"+wɹ,o޼Vӧ_II\e_x1 ]]݀| )_HUA{U__ ɽ%ʫ?..}SR]UV𶾍֣)?ħlYS(߿g0:::4pBׯߋ/JKKI$@o5@P]]/DHss۷o455;S:t)SΝ{A#8psΝ7o޽{JKKoܸ1l0##yWbɓ'}}}g̘1sL@qfȑeeewvuu---޿PUU5|cc۷eddtH*gg綶Kjjjfdd :ÇV5???++m۶q1cdff2dĉd2yݺuX1cƌ7n---O<0?sssXdIϞ=oܸ1k֬ϟرsaÆ7.""իW{%JLOO/===,, dLիW=?$ɓ!C5:t-,, cǎ]d ݲeOQQp˽2v$cǎ͚5k„ mܹc6{WWW_~`x"55_6mڴb kkڻw] RWWb iӦ577;&Lx٪U+**RSS;M߿?44tȐ!?3?}[bbѣw%[)))Լ{4(vWtmOOO?????gϞ:t=+ooo@}z)Gedd] U6R"1F&ad X lkmSH@,#Y!>ė?TkkkQQQVV_$nݺ%`Px֭|>_$566|<޽{1t>>(<HLL?# 55T$y]@\\\x1 -- .-^^Bgԩx/^-Z/iBXXa<4h SSS̙3ð'O%o><4n bb1e0vvvp X,NX\__4nܸ󙜜 r Q&555&&&}-OhjjRTT8q"~͛x:%%E:NNNfoƍd2Fggguuu@ֆaؚ5k:&gnn5'''KKRn 4 SҀUl``ee#QA]vu+ɓ_.\Aŋ1$--x{{!D>}Zđ'vb +&FsɈFnۍV " tccc{{{W__bd2ÇSa؛7oV ҕ\.DÇjhhu$&&F$M6 гgOOOӧO0sL* ͛73f̐_J|iŋG2eٳgLڵk}%:60{P*   CCCe{~L.[ =x;|GKK˃ںtR%%+W@KBnnuuucƌ\.J0Сv֔9č7ΎNWQM6I0`/}VfbbU޽4=<Acǎɓ'/^XGGgٲe0HiiÇeٰaT&zzz>>>fff9:_!ҍQF v&v~+"d窢GuWJV8zhgSB@Z!զI$8;kddJ޼ysr\EEE.huuu=z7o #"Bf07oB&.@Ԃ zj[[[bbĉֹ.!R 4GzfjlHvڊ_5''ٹ}VKbef~233E"իWG 9rjjj222L&4+… hinnvtt5kVJJJnnnRRWی. _-{%%% b_PP]xNNNYRXZZ̲fSS܏@#D io+.:ޭeD"9.O4)#K V>}#ޫWm,666a #2s12C=z455HD<<|x^C I$Raa!1H$uX0lذ'NZ\ {32D:ׯjԨ b9KsΜ9׮];wsh[&MTXX:uzرRSSl:11󳳳'Ov+9'."Pۑpe03goٳ#~MMM RRR=zՇ 4h vt4%%%_^^ӓfo߾íGǎK"舷8;mkk7ŋODK#]#F(++*[6Y% qqqelllkk#e|}}?N|\~SSSe,D gٲ/NBBBkk $‡jaai&97H̘1cݺugΜٸqcgqtm\\YlٳgǎpBSSC,\^z500pҥmmmgϞO@^#G\|NvvvJJJ6~f>}GGG==oݻW]]I}4hΜ9***"qƗP(I&Hٹs''Z*}lٲёbmٲEUUD"%%%qHqտ%wg͚eeeﯦ͛qԨQ׮]_Z[[0fjAAAdd\zꤤS^~Ύr\++Ǜ1{mݺU:c,nnn'N'O|YBBK[A/?2HD RSS[z_eeeUTTw>}ݨtݭ\]ܹscƌ3giUUU^^~2l._}hqqq֭STTGb0YYYׯ?sLuuVhhy@`` Nj`^^^-YYY+Wܲe a)))fffRQQ9{ʕ+ϟ3vaÆݹsxaaa.ӧ׭[gϞ&SSȐ5%%%;;wBem޼Y]]?[`nniӦZEEE{{{h׮];tP6흕աٸq[-Z(ýQ۱c,߿^EE:55Zz%K};s͛O:ERu̙7o0LSSӣGΞ=ÌM:UUUu˖-`kkk}|\Kn(#y*gϞ+WǏ *l]Fnw2W<OLL|t#dLLL82# zIII8# hԧO[,yfxx?<{ٳg2\#v3 oÁxC#[ėJE?x ڗ@|OL?@𲩩I~>yA!9h իw...`&o߾x#G0̻wFEEYXXtvv*@ S^gd=}*++iki!Y!@ ]JJJLQ\^lll>mmsnڴ _@  /UKJْBHIQ ,MgRSSѣ$t'g?9%~9r7e!@ Ҳ@ iY@ ,@ @ - @ @ @ @ HB @ Ҳ@{wEu?9lv7&!IJJRVPmE-^U^RڢQ,$B ;dl6;1v ITkwۙ7;wMpWbpRx_(0fl'{"@ *q۴jdYNh, IYVxiqhOQ8BRDA{ZZvEHZ9 yTeUro?(aw)ƄȢ CAhLQhˆMH$ "1x$$S[-F1K5{D!fwi2MxYi"DQ1DY”E[tZ IC``@ $==}ɤh:I=i10b)c ( hoL* ,qQElR$1Q25^fń IeYDY $2p(Tq(ɑdiRnEBR4[2*gN[m%YB&dFZ%Nx^x(m矿w޶6P]]݂ ~_r%e( B322nx{֊ Z}7ov\) vYVVfaoW+eYjz('2cN-:{V-Ӫi8Zc0 F\\:_ۈlR=Y$$Phϟq拢D|0;x >Dz[o]xv8~gc6$? $l8!DkK(aSًN[>nkzިxG B("5wF~ɖWx$uF&s&4mha̭zWTT _|x_hQioooooM{} +13g?t:ojCPkkyNdUU8;w|˖-s\<y睱X '_  ^z_?;;{޼ywuWAAR`k׮(ؿff矿rJŒO>gBHYYٍ7xקK]{w",Y?Yy?|Э޺jժT ,?Z^z߿iӦk޽6 . xVIGQϛ7oFQ9 ~e^8a„C_N=ַK/mݺh4[l,++K:::zzz80@BsQٳ鼼桡! CQQVMDZZZ57 MFd2."6n܈3fHzɤJICzx\B?(fϞ[ZZbĉm6m_m6[~~~jkGGGgg̙3!FfOn[p 2RB)6'DJ,%¡ζJFκ=TP'[x) c#DV@i4л+-p86lذrJ~Kfeemذlmm]v}jjj~Gʫ+V<̟gwlgk Ι3.]vw۸q͛Yo DY߮\B_~AԽР|ꩧ)HE);wnݺuXb*Y`AAAAkk[ozjBߕw-Y$Uo})5twwZO?A9~'^y/bcHeBݛ}h1Y@UUΝ;B?Sy}}r\dϘ1#=H744T[[iZٵkٳAᚚFSTTڵkӕp"WWWc v-*((`UVV"hNβlnn.577BR5{{{].^BJ4Z twwwuul,e=nQɤPՑH#TTT%Ljxi  $&No>Д)SΝZc_r%;w|ӣ*nA>~{yy9BhѢE~U"V|?󫪪 a?яFX9|yÆ wR1ꪧ~ooF uXj+xߎ{7ҋ߿O?,ƍ=BVdJKKׯ_qO?!/;㩄n!>C ?BSNMK>>rVy把O>7Tr[(~C/kĉSLq8SNݼyszo錌+boҥKSϟ8q,(($i$RYXXvAP:BMMM,VTT8QQQAQTjsGG('Ou8'N̔ob4Xh4nY˝N*Wy),۝DF"SQQ1a„|Fs6Q¶)SnYRRRVV644Օ^^4i,**r\HiӦ\"AF"Y/|FqQ]f72!3Niߝ9$?:;L|<îH$ K-ɔ,OSJKK#{ʬYKq#6l0ەk|JAnX~9sM֭[WVV B_~yvvk_8n\_~ykI+?yܹguֵ^^7L];s͛)_:J~*c? 6(CzG駟={%KnOK h44bUgRm|DjgffJ|no~F>fq7lç" {xEQ5T[Ȑ₊K-[8dL)+gYsJs-Nm*IBddd7?qy\.NR^{a ˕TM:Cؽ{W_]\\l0T*ˤԠ\vdjmmvnwkk+|Q s7T*qjpװԙ@2JM6Myúeƨf]|D_GqwWא!Gbc{jjjzzz%bիW<]]]Wfw=묳Qb1a.F#h7 aY6}('2'jKAe,)SF8a(*k>BnݚZZ%k51)oq\6m{r=TLF+?,9lʉ"H 8ɈP4]zHOInǎyBOəg}25RUU~LigM] B}%8p W_}g…U( @l^-IO3_n݂ AM0O{}/_˔)%ֺlr%wU9'^f͚{SN6J| ?ϦMp_=OSe>6$JA–^zi˖-x< ơWv_W .뮻;.kL}h4/^vmoooAAEQ,[Tj>.R%xW_}:Rb?!}x䈌Fhoo&)Lvuu){l{KKKwp8T*(`PIh|w8. J'ؾ}eYO lۻhFF8Ϙ1#}&999}}}uuu.Kфᮮ.^?l~6wƘ'-jduT9$;'JMLM8[)ed#wTdddk/.]t:.\pw˗˲<]>hoo~˗s=K-Zj5kkNo#X{*77o+WX J׿ӓ׿(eucYvݺuzmg͚C]6S_tժUDdɒ%r˗ufX9眷zK <=sZwwwnn%Kf͚\SX/KCCC__JMfiڙ3g@ӴhLlTՕMMMc\QQwj(((jhh0LLHXVVf0^okk+!DRY/-&EUVV*]Xǹ\.%B38fu-yZ|KJ:K54϶6*F,fA7&e%"_i!<Ƒ / ӗԙ%äq_y^o(|jJhXFZ@0m:\|̳ X'"$`7=w1pYYbIM_갤#O>$epb>2'g؈!DqpQlr9P(VZRCz{CCJK4Yb54ob=A65SVM c9Ȉ Bw%٢ŋkiӦl6O{s|qoroLQkb6^o^^u]wm`w7 Í (qݖ<ʹY[TR&ɲAo8O|ӬĬQw7DbAF:ռʔ!!F4b8Tba1(t?Y^,7`׮]'9pd?in~k?~YKeNZwc`(Hsdd)Ӧx;wSe,S!LIE ( L=.)BeV,6Gq( @eDY ,( DYp` LΛ7o<{2 S^^Z888Dxu8,pU&d2b:燕my:Рh]{ϟ}YGzfRV+˕|`pXіDZFeeSOu:]KK򠢢"Z,/ep\F{1*rF-  D"&2!D L&Syyy hnnnoo6mEA_1DYw`1*trw>d2"8 =`H7HvQ%Dgg322rrrt:Qk Cc/G<.\5!AN$gkڤ`?6uBhPGjz.]aÆ|uڵ EalвK,nhyuK|V~h2fa;;lVwx;v%21D׭[w7׿k~믿nٔW-ZhѢTa߿jժH$j%,**Bp Z矿a_6hpRRxj+b2Z=}F.}~s9N^&`y6"D !(ē#ߕ^z?.ŋ2ԒYf)!GmذvqKy *VVѲ( $8Dc%q1Ex]Bj5A/hItʕ?ϳ?{Gyd۶mdR$,˩.+|AAB;.Czhq'O#.B>󦦦qz~i,;J/ƍY> mܸ90E=m5Y,˘(*!H2$P xm] !e))fgPY`/vտolR[[bŊC>=,NpW]u] 8VXe Si !mmm04f:{102qQi!Cc2<挢ÉL *1 IDATVrt<>>Xzʕ+N5\#|͚5x7޸'Lutt?mmmE9Nqepba>Ib̙ o[|#k pX=ߪDty.e"ј "4MKZgqZ]GN+i%9MjXVcY ?3g׬YsOyI|<%TUU {;RO}YЙg ;xd Xϼ:$_I˗/B^{-BhѢEK.U^tGA#'[:c)剜"4,,e q*5KQC1Q"C`e(EjεG[n߰aEQvҥKW\yO>3|ꩧn~cz ń N?e˖9_k׮ DYBZDhoo~˗s= 8E(1q<UB6A^Bq6|eGo:;;gΜۋtg|;,˰²,_q7o⋇X#WU(Rʴ_~VkX__\.W23fHZ fffZD"xv5{lez8h4Jڮ]vM>]Ӌ|a]F |!vesss1>rz}(joob'NSAhnncDQN&CVG"@ PQQa (D l6-www 0m4WŢS t)|pD9:M$;ۧf,CɈȢLJdD$VxVse05Ҝ/x,eN>[o[ e ( XBE3Z샻{,;A~9H*V /`޼y|[ZZ{챕+W)!IJe|Mc=ve3Fyeff]re믿8Svۛ&Oi<cFia (>K<[MRT@&,"01Bє0FY"De"QYFɄP,cd,Fy'$BI*P$v熹pi"Z8dEEŻ;FIe׬Yq_<묳ػwbo6B~QZd2;+bKFt1ɲ|L*qJio_IXR$IRGOVGaT,Ko(ALfecLcF E @a c#İ:j9Úe0 f!ĈHi~ukLaY>ڣ8[hڵk{G}tɒ%y<?p֬YrJdnnW_}oڴuҥfr݇-?$=_Cx<O*bAV㔔#fTnw%hu-33fkiiH K&999,C# *a+,ahZa-[ !|-?UV#n ^_VVz_i: ]vecpJ0+\^x!=،Fhoo߷o_www[[[MM EQޗEdΝ]]]uuu---ʫyyy ޽gpN۷o_oo fU[[xm&ꚛ^occz}*a ,NF - =(d+%ao}„ 0'jz~+Puu~4M[teee7o馛T\T\\&iƌȶWΜ9j466vttBRjuee`Pnvdu222 "HCCC__8סDfoZ5GQTee;x`媨HM:le˖-;z< @Ӏj@b'/}L BI^gуwPeMyfiIH@'O-[dffp|U1T2œO> VI-N# I2拹`$Tؓ2a`& XWwp\PhO]]*Y۳܈D $F0SUtݷ:I`0 !6nڼٕZxEm۶7hH3g@UUUa8Q=B#1ϓ&SXb~0I+arrr233`Df8-Kyi6VUyj$Y6 P,N(KBN[aV <f3/ FzfRV+˕X+}Rx`` ##cx\ӕt:P'JEYw-INc t:|1LNWQQ ,?htϞ=Fb#KzE%C{Uم~|=yE |kKK6@bV鍴,!)"1) $Aa#Z%%%X,L* 9S@G$5EgL,:QIتwpMٰ! $/:δd۲{}:ul[N,$hZE1Q,8"I"//OAe`lb8%D!b-+Ti]mf6m7oOهɁ!x yo%â9ހUADb %a 1( (h #:wX"03z_tƏ7.] FeTsFPhHhRyE CNVoļNRĨ$˄b!)0 .s$ܹs׶vFS`M4]u-I<]_y/ZXzyk텎W|is/&s`(Cޖ}L,H'BH$ mR/ $IiOM"K VVjk=R,dYf_tFMtʔe7<xoBe o[#L*vZw~NQ^Rk ZCT,pbiʝbA1f,-rhlRndt3N-=ۧd!۾`!$fa5bjhΦ}ޘޒqڽm߷f@c=Oӈ0{6$N>]=S[ D4ifowo3dRY$t,xRIQi=S<u$wid@x20]}c;ϥ) ;_^ϠIp,N"^&|ᇰ[0bdI=}z锢>ICPo َܢvf \d"a(I*ʲrB@H(d9GIr/֝SNī ֨wT6?pҥKa@7ƨ:ń?$:cddQ%"l{h(&?RӲ bZL2/0ia AƬѠ飝:gSD޶r-C/0bxF I O@OOGG;Mѐ+Q  cTͽ糮_:{g]|YegdSfiFbYYʬhxƬRW(DHĒpUz뭷NRn/c2wqEE!䡇r\4M'~1WJ|K~V .طowcehtfgw'±bLN R2.cuVcE9fzoir s,扚?kZs qHK2mVI;EUUŋeYF577r-[lիBFo B(;}Yccl,pЫ՚Ya6)gr4E1H?6)\z΍(aNKV93PHW;u%Ź,PXk{yɇ1xw+!ws=$i?_Qh4֭۱cܹsB~ЗN@y#@!̳L$г(ʛ }ڙWn_wG$v2*#pZh{`CO|@S^%YN%!" +E(ڱcBSNBsݲe˿C ϙ3gӦMcb\ .P"s=!x` / 2iLNlPYgeyd ݝ fMۨpo8Դ~;oʲNE"nNf;\ىXXLąDR,4-D;x<PyyyjɓG,|UW'BM>]y`ە(®8@_8Z^ŪZīi"2A<̆h4ʒ-ю)2b8L1*#r(‰P*rWڷ{ 4e0Zmp0HDY( cV1Ech?T*Ոe 8d2)0ư)ЗN@VeiV2:ZZnyC%xRHn[-DB*v7{wQTMvlz!@ 4B &RD0wEƵ]4ኀ"MD:$@Hi63sM ^G^gwfΜ2gٺ抂tSORk?C'\_SZ+Y˾zqrr^ddd sssoq\ HED*fHR,bQsSsSum[s}K]}}U%$TFŠ6\Tqh.=%c m`-HJN4$FYHZLXJQbB; BIHH㏳m۶o>&DYwObJ-%S*NNU̲v.A AXq{k3k lV J^4b#i0v; 2KچhHeo[oŽx8w\[[[&DYw JJ:D"ʚ) IDATXJ9X9ۋdƎr1MFb08yXT*ނvX°J -3"B+%2|YPFv)P4Kz7uԯzR/:t(A?{f{Pmcs{tvH,D*g-r+7mD,&ڙrf< e{Xk(8DD lsD+f r)eokXhHDRB_$v!=]N3Ynݺu 5,[rݪUDƊDL!ۛbPW5ZXչW]FDfsrv'f cI;"KZ"g -Re&"1C o1bM?CJJ !d8X{>h\E#% mi'1D!Wv:[-RLi'*]cD$ zBzQg3m4P^$zQhl&BP%:qg}F(J$q~peVPdE" a[ZU,VRN̒"톚ZʒI*\cSWYgj*&C,k-U%TFa{[C]xaww1cƼ8X+F!bJ|yキ420 BnҮokԶv\"a zZ&0))K XC'EQ&,C9FY= ,-rH:ZJc:)"˔XJhM QXZ2;FbT+;*:[%4m HhL*U)HeF" R,edNyUGpRBIX*Vh%K)e$7__*D"'"ZA:;i#ca%R\&(ժvf4-S[I,-6 Z"%"E,agQmYp/bXaDr%+0DBDDSl'K"Y1-Y+l@oEhZ`kakCP\!h DHYX - ò E%BmYp26׋;v@H2X蝗1XC;!BCkdbG[V+Rkh hKHŬ2BXb4 K e=MɒN#e:L{+K b+BѠo'4XX#! D&%z%b)+3+DD,1tADe I( qmle ECc! E1,1S$~EXb!,a D"ɥFf( bJV!'%-e aYͰMC'ok4 E>e[ةEB-mTt0 #bH$BLΈŔLJI$YYXR)ˉTPR"H$)fJ gS0`$ͭs@2iG$c)()FY6D7Ew}N)(~ʝV{-%:^$ӈ$DDVD!r[?Qj;k&"^g?yW ?kii9gΜ?{l⒛˽}衇ͤ5hʕEJ.kE߇( o۷7_`V*ݨ`0 +T'O6o(*!!fxdqDYw_uGWs=WWW7`djwWnݺț{g( e.+Q؈u(Jͻ]~]QBjjjN>=tл%7.]iӦb]]]JJc=p]eˈ+)+#ʊd$OƆX[/@,,Hl~t$2.<BE"dBQ}e3gdeF$J!JڿHFR K/uvvrUUUŹd2//K_|(jnn\.g+Wttt?J ۷o0}}}E EDDɓ' )0V󳰰:tg}^XZZnذa~~~r_=cǎM6KTz{{|={Pu=ao>{δ#RRiJJʨQ,,,4O1bhhjjk4Rkgfʕ P(o6_z]`Xl\.wtt| ^Ǝ˿Φ(JxL%/?(((44TE} e/_-|||֯_/˻MgQ/(ꫯ◼4MWVVroÇslܸq>iE} J~-!䡇={v]]O>L몦fĉk׮??zh#o˖-SLqqqٰaÒ%K~Ȗ>fرc.\xĉ~xܸq΋/>f~z믿^n|7iҤ:B… KJJ9"1:thڴi*[#?GNNNTT͝;7ޘ|xqq1ȑ#ׯ_e.{md2+V L64;;sowMyw?Wގ1bذa}94MgeeKf̘E|~2$,,k}n5d[uV>۹E>A~MMM*j|b^w[Y%/2w_qF.*O?IJl[[FyO7l@9q.af)SpoO:EOo&!ҥK&+5BW$k ڶm!$))/ZO=JZr%dɒ%"/?000$$DgϞ%|W]KBIIH$rmY^ǎ˽>z(!瞳dYQ$,(x`0Y%>< *\W2gΜ+Yz*!䭷4dᾛ)!~d!ovOdf_zʹ^Nx7{z4_82lѢEL._,b577k477_;##aY-Ux*..mYpO1mmmiii&]t:]EQj_+--mmYX---(**"}x6SOkDzq%\@;MMd>cgdt)7/$|㊇K/h~Ϗe{/yG}7o< eA.\O0ev?O>ioouEi'_ǎF0+3Mt}-,,ݪG6556*,,L&4-O [(:%=uA䲎\>k֬}<'_{zzFEEq!D>(#qKyኦN~a罾emDgϞۈíncpr}Y~o߾~͝;̙3\h܎=',+//ٳCBBLBQի/rgbyy9!mǎEuiBٙAfJ=2/}̴\4f0 aÆl8p@GGԩS)=N0d2!Dw[\dI``FQ(\G2>.\wA),,LJJ3gpb=<<1pJ;bfEj' zmrqq0`W=p0cǎ?~|bbbcccccczz:?pv~s[.\د_ҏ3kڿqBBBlllɓ2x!~EM {{{e'Ovtt亨Bv9zhOOϮ\fȑ#J\.///79̔{d{1z5dȐ$駟ܺ~:!d„ 23MpPC@GpkRRRZZZ}}}R)W})|a333k*C )))IMM5/]{)Tm۸$F\tmݩX/A8ٻ7xƍ|Ajjjdd$]"tmcYd!u2ta0QT_uVV֜9snɪb12>{Ghϓ/^8a„r/mq꺤ۭ*7oNkP*AqEe6m?Ç#L0hР#Fpk.BI~qobFT&tSͬ(Ry\hJ?ǍiE%%yyy{?~<@z%0**J$} Q#L4ԩSgϞ}"їŸ/DdfQQQ4Moڴo?f0>}z\ &D+V!Bƍgee~z>$hoo߸qN9r(?~3fps}ݝ(LR,[XXc555ŭ...];1$0|9!x04ddRB:;Il, &^^d*JHVyu"ܹ+Wțos m&G}#!_}Umm-7՛?񏬬ÇVTT]Ύ_oy䑿o>>>/_ްaCpp0?`ԩK.}VXammlٲSzժUoFQQѐ!C>s` &tuM0.nnn<@\\w9{nƌ駟~瑑׮]{wZ-j*WWW??_]XdZU%Kddd]ۻ:O>dAAAO<񄭭miiɓ'_y啇~8///66V*z >|Ru:M{cbݺuj xܹs>رc֭['HA@HLL̜9s6nxڵ|P*8pܹs]ͬh>eˊFUVVa UVhmm_{xxcҤIk֬)--Nk1c~ŋwtt|.puu[nɓ'O\__yfggno|yzcǎ_[j#""vmccÏR*~YOn0vܙcǎjzg{1SwA;;a IDAT*::%))iٲe}/!~de2YhhW_}egg7d~^L G۷o3g!?8...88xʔ)%%%/ε ܸqSuu)Sj?^`ߌ3+**7o3=3{gggAAAbbbMM A8;h4&%%wa---gUUU'Nhll0IVTTM~ܹ23s3=!%.9YeWb aw:6J$;[^t>dl` kw&N6$urbO>a amcY]z}nGV%9s]ظxb777Tگ_^zQ'O92&&&999""d&w !ptr<""ԩS/?~/rҥKM6@8… m6h TtR=?gronn,w_ ~d2Y``_~k8ɴ3+gϞT(O9p6o3Ϝ9sF ԻzEЫׯ\D}dff'k4+cJJ jgWVMg_=^CtS*ݸqٹk2ϼŅ,rgggn^C[ev[Շ}#gWz~vbٲe/N!j3æ>>̊ee ܖ!w)DYpS& YnoKtRŽcvٺH47>"MM_yQ{ݲeKVVpwEЫ͛7[&¾o\?u)S!粲:;B !njnR;9:"Q)Fskbg͚5'O޽{w!u+D[o[oa_C_+-{ǹֶ9r$67/ŋ.X?a\ܖqYf`\ծ, Q,DYQ,DY( Q,DY( Q,@( Q @( Q @( e @( e @e DYe DYe@w?&%% :ǏG&},BX^p%&&"Q@/Ln,ei#VއR( Q󪫫WXQSSe(+ZFk555˗/_dVE mYp+++K9ptt1tСNN}rي  jkk !\-@p/u/^LOP]SckkxⅪ* 8:A%n.4Ee_Ͷهdg{iI r C>k@@ e܃jkk e2Rtrt(˷F.z%l2 V\@ 7emmm*rƥFDjZm{{ݻw_WDDĄ p/vر^{M.#7s:nŊO%@p/`#a rZmkKիWS%F*gΜ)--5*!88NS\\y3g8%7nزeԩSFѡ=Б#GΜ9/ k֬'>}ĉVVVޣF>}:++A.s .Ew eO>=zhm@gGY ߯BzV  8q2|%33h4b>??Q֝h4ݻb%:'' **J"pKrq];~kDGG+ʖ└ * amVYY9hРʴQFBƍnݺ'Nk:s̙3g{cǎtvvtѣG0zUUѣG (?~|ڵkaaaYYYN|[Ǟ={222PVVP[[;m4BHAAݻ}}}:;;+**ܷ~[YY9rHJАǏ6ׯ_ߵkMtt4!$33s۶m±Խf/hСˆtMMMaaaZ"))`Μ9/_FYEe;v`&"""(999QmSׯ_6JIIܢFcFFg̘!쐙v9???r…{4(,,9))hѢERKpٳgBd2իW͛Gbqmlljjj~n /..޾}EDDBݻwoUUfQQQ~~~@@~~~jjjSSSaa TQQ2k֬?|^vaCTӧOD"aK39L9rٳgԔ}̙3]0Yb-,ז+H؊߿4-Fg.х[l !:tHP̛7N3x5kϜ9Occc3c '|EY mmmsz)))=m?ϿUTv*..߿髪yVmի/]3pIk׮jlˤI!RԩS#GZ}Q[F`ȑ|M}Ĉ}cǎsAWDDĶmn6{9%%%,˺Kրٳg{zzrK\]]ᇴp??777s5fe333=<"=z4333<<< %%%33666r-W}Y/EQǏ7bB9rȑ?hnn&JJJRG111 ;|pXXبQFhmmS. Q,8pJ 4vtt%I,QiBKi_bĽ2qyIIIXX0߆ v~K(899 dddn1 Äx{{s{ 'e ʂ7 ۯKLH:::###!!aSN gY699999Y$g&k &sZYYْ܆ -eFjʫX,^WW0ʕ+M~WWWw/- {ٱcMHHHHHL&uFBW}^F {N{yy|QBu:˗~FFT*7^  :H8F?-_|^^^&M惏OEEErrrRR͛鹔mmm&QVG Brtt4_]#|7bv/[E)//?sLqqqKKߛ5ݞG 2knGR5'NXr%U9Łiii FCi&Km"V,s!mY( .2y0FB#\JnO{g TɓhqsS o e|8eaXn>6Ȱ,'EYTf4FQ8n䦨jobkkk)ouggF1JT*q`~Rn^-[8q'4\d{}^MsjkjjL* |sY@@']`w_a8a5IɵTWW ۅb6d_Jyaƌ1jkEQ…,=nݹsj̙3Io!\Y]]pKLJ5dB''쫄pҒ;n˲&zZ$YXX]t$TqEEEqq1ҥKf6Ƥ{BN:ef|kt:pyOVu:]FFFGGޮ1k䆣XYYgee]zUVn!:;;M\vX]]D"eyyy, CjjR{-Jp ׺ ''grĉծj%99Fzƍ}}}u:X,/..v 3f7|y搐Heuqq(hРAܒaÆeff~7666ܬَ῕ MaC?~IVSU^^( y ''9dĉ?m0˗+++M2ܷoƍy_ F!|d\D?!C/},ϟ?rHXXX[[[JJsUrj>_|iӦ`R{հxsΥ+ѵ{aaaΝ۵kO{{ T*5.D"ҥKi6yO?yxx4 >-[5vFj/Rice{嗋555g&{xxz;z8((vE"zJ4}t"eoopSN]|E&L`F7oѣGxzzΙ3lZ{[rx7o͛ocrCwFF0`W,**:u`5jL&c;33I,L< 8p̙3O:uQL6hРݻw JQ(!Ν=uСG;4h)H׮]kkkDf̘1aaa|TWW744ݍRx ef͚%/ beeaaa3z rΟ?w^wwwnN3/|}}'Lnii4nܸ5ktK.HR.t ,8qDrr^8q"9c4O<޷ pGIDATncc3rHeojZ|PRZ[[6LVܹ?j5tܩG*Nꫯ~G&_6G w}mwaa$zA^7n]K~>rR7}3(##>7|Szܹŋp?-Ӣ,VdVyYA/߮_^뛛W˻vLOKXefq:pwEYwZ- . 3t:+"lB,tz ½L*ZYY) KKKkkk7~2L&r\.ɤR)%%%Ғak׮<&.|bԩSnݚa?GZXX;Y} /_^SScccõb![Q=H$) Wnԯ_VeZرc FԩS pS֖-[͛w@p'rqq9s&,YBz~Z7e `\Eݖ4kF&ȑ# ( 6Z w@( Q @( e @( nۥFܞ(kС?=e @e DYe DYe,DYQ,DYQ,DY( Q,DY( QJuQII A[,DYPqqqa߹-IENDB`Quaternion-0.0.9.4c/quaternion_win32.rc000066400000000000000000000000611347141001300176760ustar00rootroot00000000000000IDI_ICON1 ICON DISCARDABLE "icons/quaternion.ico"