pax_global_header00006660000000000000000000000064131632437310014515gustar00rootroot0000000000000052 comment=c5e62c5a005860459d6ec6b8d104347003e981e9 pytango-9.2.2/000077500000000000000000000000001316324373100132105ustar00rootroot00000000000000pytango-9.2.2/.gitignore000066400000000000000000000010301316324373100151720ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] # C extensions *.o *.so # Precompiled headers *.gch # Packages bin/ build/ develop-eggs/ dist/ eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg *.eggs *.cfg MANIFEST # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports .tox/ .coverage .cache nosetests.xml coverage.xml # Translations *.mo # Sphinx documentation doc/_build/ # Qt Help generated *.qch *.qhc # Backup files *~ # PyCharm projects files .idea pytango-9.2.2/.pylintrc000066400000000000000000000373741316324373100150730ustar00rootroot00000000000000[MASTER] # A comma-separated list of package or module names from where C extensions may # be loaded. Extensions are loading into the active Python interpreter and may # run arbitrary code extension-pkg-whitelist= # Add files or directories to the blacklist. They should be base names, not # paths. ignore=databaseds # Add files or directories matching the regex patterns to the blacklist. The # regex matches against base names, not paths. ignore-patterns= # Python code to execute, usually for sys.path manipulation such as # pygtk.require(). #init-hook= # Use multiple processes to speed up Pylint. jobs=1 # List of plugins (as comma separated values of python modules names) to load, # usually to register additional checkers. load-plugins= # Pickle collected data for later comparisons. persistent=yes # Specify a configuration file. #rcfile= # Allow loading of arbitrary C extensions. Extensions are imported into the # active Python interpreter and may run arbitrary code. unsafe-load-any-extension=no [MESSAGES CONTROL] # Only show warnings with the listed confidence levels. Leave empty to show # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED confidence= # Disable the message, report, category or checker with the given id(s). You # can either give multiple identifiers separated by comma (,) or put this # option multiple times (only on the command line, not in the configuration # file where it should appear only once).You can also use "--disable=all" to # disable everything first and then reenable specific checks. For example, if # you want to run only the similarities checker, you can use "--disable=all # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" disable=blacklisted-name,invalid-name,missing-docstring,empty-docstring,unneeded-not,singleton-comparison,misplaced-comparison-constant,unidiomatic-typecheck,consider-using-enumerate,consider-iterating-dictionary,bad-classmethod-argument,bad-mcs-method-argument,bad-mcs-classmethod-argument,single-string-used-for-slots,line-too-long,too-many-lines,trailing-whitespace,missing-final-newline,trailing-newlines,multiple-statements,superfluous-parens,bad-whitespace,mixed-line-endings,unexpected-line-ending-format,bad-continuation,wrong-spelling-in-comment,wrong-spelling-in-docstring,invalid-characters-in-docstring,multiple-imports,wrong-import-order,ungrouped-imports,wrong-import-position,old-style-class,len-as-condition,import-error,no-name-in-module,no-member,print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,literal-comparison,no-self-use,no-classmethod-decorator,no-staticmethod-decorator,cyclic-import,duplicate-code,too-many-ancestors,too-many-instance-attributes,too-few-public-methods,too-many-public-methods,too-many-return-statements,too-many-branches,too-many-arguments,too-many-locals,too-many-statements,too-many-boolean-expressions,consider-merging-isinstance,too-many-nested-blocks,simplifiable-if-statement,redefined-argument-from-local,no-else-return,consider-using-ternary,trailing-comma-tuple,protected-access,global-statement,unused-argument,redefined-outer-name,redefined-builtin,bare-except,broad-except,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option # multiple time (only on the command line, not in the configuration file where # it should appear only once). See also the "--disable" option for examples. enable= [REPORTS] # Python expression which should return a note less than 10 (10 is the highest # note). You have access to the variables errors warning, statement which # respectively contain the number of errors / warnings messages and the total # number of statements analyzed. This is used by the global evaluation report # (RP0004). evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) # Template used to display messages. This is a python new-style format string # used to format the message information. See doc for all details #msg-template= # Set the output format. Available formats are text, parseable, colorized, json # and msvs (visual studio).You can also give a reporter class, eg # mypackage.mymodule.MyReporterClass. output-format=text # Tells whether to display a full report or only the messages reports=no # Activate the evaluation score. score=yes [REFACTORING] # Maximum number of nested blocks for function / method body max-nested-blocks=5 [MISCELLANEOUS] # List of note tags to take in consideration, separated by a comma. notes=FIXME,XXX,TODO [SPELLING] # Spelling dictionary name. Available dictionaries: none. To make it working # install python-enchant package. spelling-dict= # List of comma separated words that should not be checked. spelling-ignore-words= # A path to a file that contains private dictionary; one word per line. spelling-private-dict-file= # Tells whether to store unknown words to indicated private dictionary in # --spelling-private-dict-file option instead of raising a message. spelling-store-unknown-words=no [VARIABLES] # List of additional names supposed to be defined in builtins. Remember that # you should avoid to define new builtins when possible. additional-builtins= # Tells whether unused global variables should be treated as a violation. allow-global-unused-variables=yes # List of strings which can identify a callback function by name. A callback # name must start or end with one of those strings. callbacks=cb_,_cb # A regular expression matching the name of dummy variables (i.e. expectedly # not used). dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ # Argument names that match this expression will be ignored. Default to name # with leading underscore ignored-argument-names=_.*|^ignored_|^unused_ # Tells whether we should check for unused import in __init__ files. init-import=no # List of qualified module names which can have objects that can redefine # builtins. redefining-builtins-modules=six.moves,future.builtins [TYPECHECK] # List of decorators that produce context managers, such as # contextlib.contextmanager. Add to this list to register other decorators that # produce valid context managers. contextmanager-decorators=contextlib.contextmanager # List of members which are set dynamically and missed by pylint inference # system, and so shouldn't trigger E1101 when accessed. Python regular # expressions are accepted. generated-members= # Tells whether missing members accessed in mixin class should be ignored. A # mixin class is detected if its name ends with "mixin" (case insensitive). ignore-mixin-members=yes # This flag controls whether pylint should warn about no-member and similar # checks whenever an opaque object is returned when inferring. The inference # can return multiple potential results while evaluating a Python object, but # some branches might not be evaluated, which results in partial inference. In # that case, it might be useful to still emit no-member and other checks for # the rest of the inferred objects. ignore-on-opaque-inference=yes # List of class names for which member attributes should not be checked (useful # for classes with dynamically set attributes). This supports the use of # qualified names. ignored-classes=optparse.Values,thread._local,_thread._local # List of module names for which member attributes should not be checked # (useful for modules/projects where namespaces are manipulated during runtime # and thus existing member attributes cannot be deduced by static analysis. It # supports qualified module names, as well as Unix pattern matching. ignored-modules= # Show a hint with possible names when a member name was not found. The aspect # of finding the hint is based on edit distance. missing-member-hint=yes # The minimum edit distance a name should have in order to be considered a # similar match for a missing member name. missing-member-hint-distance=1 # The total number of similar names that should be taken in consideration when # showing a hint for a missing member. missing-member-max-choices=1 [FORMAT] # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. expected-line-ending-format= # Regexp for a line that is allowed to be longer than the limit. ignore-long-lines=^\s*(# )??$ # Number of spaces of indent required inside a hanging or continued line. indent-after-paren=4 # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 # tab). indent-string=' ' # Maximum number of characters on a single line. max-line-length=100 # Maximum number of lines in a module max-module-lines=1000 # List of optional constructs for which whitespace checking is disabled. `dict- # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. # `trailing-comma` allows a space between comma and closing bracket: (a, ). # `empty-line` allows space-only lines. no-space-check=trailing-comma,dict-separator # Allow the body of a class to be on the same line as the declaration if body # contains single statement. single-line-class-stmt=no # Allow the body of an if to be on the same line as the test if there is no # else. single-line-if-stmt=no [BASIC] # Naming hint for argument names argument-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ # Regular expression matching correct argument names argument-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ # Naming hint for attribute names attr-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ # Regular expression matching correct attribute names attr-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ # Bad variable names which should always be refused, separated by a comma bad-names=foo,bar,baz,toto,tutu,tata # Naming hint for class attribute names class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ # Regular expression matching correct class attribute names class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ # Naming hint for class names class-name-hint=[A-Z_][a-zA-Z0-9]+$ # Regular expression matching correct class names class-rgx=[A-Z_][a-zA-Z0-9]+$ # Naming hint for constant names const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ # Regular expression matching correct constant names const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ # Minimum line length for functions/classes that require docstrings, shorter # ones are exempt. docstring-min-length=-1 # Naming hint for function names function-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ # Regular expression matching correct function names function-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ # Good variable names which should always be accepted, separated by a comma good-names=i,j,k,ex,Run,_ # Include a hint for the correct naming format with invalid-name include-naming-hint=no # Naming hint for inline iteration names inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ # Regular expression matching correct inline iteration names inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ # Naming hint for method names method-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ # Regular expression matching correct method names method-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ # Naming hint for module names module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ # Regular expression matching correct module names module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ # Colon-delimited sets of names that determine each other's naming style when # the name regexes allow several styles. name-group= # Regular expression which should only match function or class names that do # not require a docstring. no-docstring-rgx=^_ # List of decorators that produce properties, such as abc.abstractproperty. Add # to this list to register other decorators that produce valid properties. property-classes=abc.abstractproperty # Naming hint for variable names variable-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ # Regular expression matching correct variable names variable-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ [LOGGING] # Logging modules to check that the string format arguments are in logging # function parameter format logging-modules=logging [SIMILARITIES] # Ignore comments when computing similarities. ignore-comments=yes # Ignore docstrings when computing similarities. ignore-docstrings=yes # Ignore imports when computing similarities. ignore-imports=no # Minimum lines number of a similarity. min-similarity-lines=4 [IMPORTS] # Allow wildcard imports from modules that define __all__. allow-wildcard-with-all=no # Analyse import fallback blocks. This can be used to support both Python 2 and # 3 compatible code, which means that the block might have code that exists # only in one or another interpreter, leading to false positives when analysed. analyse-fallback-blocks=no # Deprecated modules which should not be used, separated by a comma deprecated-modules=regsub,TERMIOS,Bastion,rexec # Create a graph of external dependencies in the given file (report RP0402 must # not be disabled) ext-import-graph= # Create a graph of every (i.e. internal and external) dependencies in the # given file (report RP0402 must not be disabled) import-graph= # Create a graph of internal dependencies in the given file (report RP0402 must # not be disabled) int-import-graph= # Force import order to recognize a module as part of the standard # compatibility libraries. known-standard-library= # Force import order to recognize a module as part of a third party library. known-third-party=enchant [CLASSES] # List of method names used to declare (i.e. assign) instance attributes. defining-attr-methods=__init__,__new__,setUp # List of member names, which should be excluded from the protected access # warning. exclude-protected=_asdict,_fields,_replace,_source,_make # List of valid names for the first argument in a class method. valid-classmethod-first-arg=cls # List of valid names for the first argument in a metaclass class method. valid-metaclass-classmethod-first-arg=mcs [DESIGN] # Maximum number of arguments for function / method max-args=5 # Maximum number of attributes for a class (see R0902). max-attributes=7 # Maximum number of boolean expressions in a if statement max-bool-expr=5 # Maximum number of branch for function / method body max-branches=12 # Maximum number of locals for function / method body max-locals=15 # Maximum number of parents for a class (see R0901). max-parents=7 # Maximum number of public methods for a class (see R0904). max-public-methods=20 # Maximum number of return / yield for function / method body max-returns=6 # Maximum number of statements in function / method body max-statements=50 # Minimum number of public methods for a class (see R0903). min-public-methods=2 [EXCEPTIONS] # Exceptions that will emit a warning when being caught. Defaults to # "Exception" overgeneral-exceptions=Exception pytango-9.2.2/.travis.yml000066400000000000000000000035571316324373100153330ustar00rootroot00000000000000sudo: false language: python os: linux branches: only: - master - develop python: - 2.7 - 3.4 - 3.5 cache: directories: - ./miniconda # Conda environment - ./.eggs # pytest eggs - ./build # Build environment - ./cached_ext # Previous extension before_install: # Add conda to path - export PATH="$PWD/miniconda/bin:$PATH" # Install miniconda if not in cache - conda -V || wget http://repo.continuum.io/miniconda/Miniconda-latest-Linux-x86_64.sh -O miniconda.sh - conda -V || bash miniconda.sh -b -p ./miniconda -f # Update conda - conda update --yes conda # Create build environment if it doesn't exist - source activate buildenv || conda create --yes --name buildenv python=$TRAVIS_PYTHON_VERSION # Activate build environment - source activate buildenv install: # Install build dependencies - conda install --yes boost - conda install --yes -c tango-controls tango=9.2.2 - conda install --yes numpy # Not a strong requirement yet # Use conda prefix as root for the dependencies - export BOOST_ROOT=$CONDA_PREFIX TANGO_ROOT=$CONDA_PREFIX ZMQ_ROOT=$CONDA_PREFIX OMNI_ROOT=$CONDA_PREFIX # Uncomment the following line if the tests are running in parrallel # with pytest-xdist (see https://github.com/pytest-dev/pytest-xdist/issues/41): # - pip install -U pytest pytest-xdist six mock before_script: # Make sure old_ext exists - mkdir -p cached_ext # Touch the .so files if the extension hasn't changed - diff cached_ext ext && find build -name _tango*.so -printf "touching %p\n" -exec touch {} + || true script: # Build the package - python setup.py build # The build directory has been updated, cached_ext needs to be synchronized too - rsync -a --delete ext/ cached_ext/ # Run the tests - python setup.py test # Kill all the hanging tango test processes - killall -9 TangoTest || truepytango-9.2.2/LICENSE.txt000066400000000000000000000167431316324373100150460ustar00rootroot00000000000000 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. pytango-9.2.2/MANIFEST.in000066400000000000000000000002721316324373100147470ustar00rootroot00000000000000include LICENSE.txt graft ext graft doc graft tests global-exclude *~ global-exclude *.pyc global-exclude *.pyo exclude setup.cfg exclude images exclude src/precompiled_header.hpp.gch pytango-9.2.2/Makefile000066400000000000000000000205461316324373100146570ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ ################################################################################ # TO BE USED BY DEVELOPERS ONLY ################################################################################ # Makefile to generate the PyTango library # Needs the following environment variables to be defined: # - TANGO_ROOT # - LOG4TANGO_ROOT # - OMNI_ROOT # - BOOST_ROOT # - ZMQ_ROOT # - NUMPY_ROOT # # if target == install also needs: prefix= # ex: make install prefix=/home/homer/.local/lib/python2.6/site-packages # # Optional: # - OBJS_DIR: directory where files will be build (default: objs) # - PY3K: if defined use python 3 boost python # - PY_VER: use a specific python version (default is empty) (ex: 3.2) # Build "in parallel": # make prepare && make -j5 # # Install "in parallel": # make prepare && make -j5 && make install prefix= ifdef PY_VER PY_EXC=python$(PY_VER) PY_MAJOR=$(shell $(PY_EXC) -c "import sys; sys.stdout.write(str(sys.version_info[0]))") PY_MINOR=$(shell $(PY_EXC) -c "import sys; sys.stdout.write(str(sys.version_info[1]))") else PY_EXC=python PY_MAJOR=$(shell $(PY_EXC) -c "import sys; sys.stdout.write(str(sys.version_info[0]))") PY_MINOR=$(shell $(PY_EXC) -c "import sys; sys.stdout.write(str(sys.version_info[1]))") PY_VER=$(PY_MAJOR).$(PY_MINOR) endif PY_VER_S=$(PY_MAJOR)$(PY_MINOR) ifndef NUMPY_ROOT NUMPY_INC = -I$(shell $(PY_EXC) -c "import sys, numpy; sys.stdout.write(numpy.get_include())") else NUMPY_INC = -I$(NUMPY_ROOT)/include endif PYTANGO_NUMPY_VERSION = \"$(shell $(PY_EXC) -c "import sys, numpy; sys.stdout.write(numpy.__version__)")\" ifndef prefix ifdef user _PY_DIR=$(shell $(PY_EXC) -c "import sys, os; sys.stdout.write(os.path.split(os.path.join(os.path.dirname(os.__file__)))[1])") prefix=$(HOME)/.local/lib/$(_PY_DIR)/site-packages else _PY_DIR=$(shell $(PY_EXC) -c "import sys, os; sys.stdout.write(os.path.join(os.path.dirname(os.__file__)))") prefix=$(_PY_DIR)/site-packages endif endif SRC_DIR = ext ifndef OBJS_DIR OBJS_DIR := objs_py$(PY_VER_S) endif CC = gcc PY_INC := $(shell python$(PY_VER)-config --includes) ifdef optimize OPTIMIZE_CC = -O2 OPTIMIZE_LN = -O2 else OPTIMIZE_CC = -O0 OPTIMIZE_LN = -O0 endif TANGO_CFLAGS=`pkg-config --cflags-only-other tango` TANGO_LIBS=`pkg-config --libs-only-l tango` BOOST_LIB = boost_python-py$(PY_VER_S) PRE_C_H := precompiled_header.hpp PRE_C_H_O := $(OBJS_DIR)/$(PRE_C_H).gch PRE_C := -include$(OBJS_DIR)/$(PRE_C_H) LN := g++ -pthread -shared -Wl,$(OPTIMIZE_LN) -Wl,-Bsymbolic-functions #-z defs LN_STATIC := g++ -pthread -static -Wl,$(OPTIMIZE_LN) -Wl,-Bsymbolic-functions LN_VER := -Wl,-h -Wl,--strip-all LN_LIBS := -l$(BOOST_LIB) -lpython$(PY_VER) -ltango INCLUDE_DIRS = ifdef TANGO_ROOT LN_DIRS += -L$(TANGO_ROOT)/lib INCLUDE_DIRS += -I$(TANGO_ROOT)/include -I$(TANGO_ROOT)/include/tango #LN_LIBS += -ltango -lomniDynamic4 -lCOS4 -llog4tango -lzmq -lomniORB4 -lomnithread else LN_DIRS += `pkg-config --libs-only-L tango` INCLUDE_DIRS += `pkg-config --cflags-only-I tango` #LN_LIBS += `pkg-config --libs-only-l tango` endif ifdef LOG4TANGO_ROOT LN_DIRS += -L$(LOG4TANGO_ROOT)/lib INCLUDE_DIRS += -I$(LOG4TANGO_ROOT)/include endif ifdef OMNI_ROOT LN_DIRS += -L$(OMNI_ROOT)/lib INCLUDE_DIRS += -I$(OMNI_ROOT)/include endif ifdef BOOST_ROOT LN_DIRS += -L$(BOOST_ROOT)/lib endif ifdef ZMQ_ROOT LN_DIRS += -L$(ZMQ_ROOT)/lib INCLUDE_DIRS += -I$(ZMQ_ROOT)/include endif INCLUDE_DIRS += \ $(PY_INC) \ $(NUMPY_INC) QUOTE_INCLUDE_DIRS = -iquote $(SRC_DIR) MACROS := -DNDEBUG -DPYTANGO_HAS_UNIQUE_PTR -DPYTANGO_NUMPY_VERSION=$(PYTANGO_NUMPY_VERSION) CFLAGS := -pthread -fno-strict-aliasing -fwrapv -Wall -fPIC -g $(OPTIMIZE_CC) $(MACROS) $(TANGO_CFLAGS) $(INCLUDE_DIRS) $(QUOTE_INCLUDE_DIRS) LNFLAGS := $(LN_DIRS) $(LN_LIBS) LIB_NAME := _tango.so LIB_SYMB_NAME := $(LIB_NAME).dbg OBJS := \ $(OBJS_DIR)/api_util.o \ $(OBJS_DIR)/archive_event_info.o \ $(OBJS_DIR)/attr_conf_event_data.o \ $(OBJS_DIR)/attribute_alarm_info.o \ $(OBJS_DIR)/attribute_dimension.o \ $(OBJS_DIR)/attribute_event_info.o \ $(OBJS_DIR)/attribute_info.o \ $(OBJS_DIR)/attribute_info_ex.o \ $(OBJS_DIR)/device_pipe.o \ $(OBJS_DIR)/pipe_info.o \ $(OBJS_DIR)/attribute_proxy.o \ $(OBJS_DIR)/base_types.o \ $(OBJS_DIR)/callback.o \ $(OBJS_DIR)/change_event_info.o \ $(OBJS_DIR)/command_info.o \ $(OBJS_DIR)/connection.o \ $(OBJS_DIR)/constants.o \ $(OBJS_DIR)/database.o \ $(OBJS_DIR)/data_ready_event_data.o \ $(OBJS_DIR)/db.o \ $(OBJS_DIR)/dev_command_info.o \ $(OBJS_DIR)/dev_error.o \ $(OBJS_DIR)/device_attribute_config.o \ $(OBJS_DIR)/device_attribute.o \ $(OBJS_DIR)/device_attribute_history.o \ $(OBJS_DIR)/device_data.o \ $(OBJS_DIR)/device_data_history.o \ $(OBJS_DIR)/device_info.o \ $(OBJS_DIR)/devintr_change_event_data.o \ $(OBJS_DIR)/device_proxy.o \ $(OBJS_DIR)/enums.o \ $(OBJS_DIR)/event_data.o \ $(OBJS_DIR)/exception.o \ $(OBJS_DIR)/from_py.o \ $(OBJS_DIR)/fwdAttr.o \ $(OBJS_DIR)/group.o \ $(OBJS_DIR)/group_reply.o \ $(OBJS_DIR)/group_reply_list.o \ $(OBJS_DIR)/locker_info.o \ $(OBJS_DIR)/locking_thread.o \ $(OBJS_DIR)/periodic_event_info.o \ $(OBJS_DIR)/pipe_event_data.o \ $(OBJS_DIR)/poll_device.o \ $(OBJS_DIR)/pytango.o \ $(OBJS_DIR)/pytgutils.o \ $(OBJS_DIR)/pyutils.o \ $(OBJS_DIR)/time_val.o \ $(OBJS_DIR)/to_py.o \ $(OBJS_DIR)/version.o \ $(OBJS_DIR)/attr.o \ $(OBJS_DIR)/attribute.o \ $(OBJS_DIR)/command.o \ $(OBJS_DIR)/pipe.o \ $(OBJS_DIR)/device_class.o \ $(OBJS_DIR)/device_impl.o \ $(OBJS_DIR)/dserver.o \ $(OBJS_DIR)/encoded_attribute.o \ $(OBJS_DIR)/log4tango.o \ $(OBJS_DIR)/multi_attribute.o \ $(OBJS_DIR)/multi_class_attribute.o \ $(OBJS_DIR)/subdev.o \ $(OBJS_DIR)/tango_util.o \ $(OBJS_DIR)/user_default_attr_prop.o \ $(OBJS_DIR)/user_default_pipe_prop.o \ $(OBJS_DIR)/wattribute.o \ $(OBJS_DIR)/auto_monitor.o INC := callback.h \ defs.h \ device_attribute.h \ exception.h \ fast_from_py.h \ from_py.h \ pytgutils.h \ pyutils.h \ tango_numpy.h \ tgutils.h \ to_py.h \ attr.h \ attribute.h \ command.h \ pipe.h \ device_class.h \ device_impl.h LINKER=$(LN) $(OBJS) $(LN_VER) LINK_TASK=link ifdef optimize LINK_TASK=link-opt endif OK=OK! #----------------------------------------------------------------- all: build prepare: init $(PRE_C_H_O) build: init prepare $(LINK_TASK) init: @echo Using python $(PY_VER) @echo CFLAGS = $(CFLAGS) @echo LNFLAGS = $(LNFLAGS) @echo -n "Preparing build directories... " @mkdir -p $(OBJS_DIR) @echo $(OK) $(PRE_C_H_O): $(SRC_DIR)/$(PRE_C_H) @echo -n "Compiling pre-compiled header... " @$(CC) $(CFLAGS) -c $< -o $(PRE_C_H_O) @echo $(OK) # # Rule for API files # $(OBJS_DIR)/%.o: $(SRC_DIR)/%.cpp @echo -n "Compiling $(= 9.2 - `Boost.Python`_ >= 1.33 Python dependencies: - numpy_ >= 1.1 - six_ Build dependencies: - setuptools_ - sphinx_ Optional dependencies: - futures_ - gevent_ .. note:: As a general rule, libtango_ and pytango_ should share the same major and minor version (for a version ``X.Y.Z``, ``X`` and ``Y`` should match) Install ------- PyTango_ is available on PyPI_ as ``pytango``:: $ pip install pytango Alternatively, PyTango_ can be built and installed from the `sources`_:: $ python setup.py install In both cases, the installation takes a few minutes since the ``_tango`` boost extension has to compile. Usage ----- To test the installation, import ``tango`` and check ``tango.__version__``:: >>> import tango >>> tango.__version__ '9.2.0' For an interactive use, consider using ITango_, a tango IPython_ profile. Documentation ------------- Check out the documentation_ for more informations. Support and contribution ------------------------ You can get support from the `Tango forums`_, for both Tango_ and PyTango_ questions. All contributions, `PR and bug reports`_ are welcome, please see: `How to Contribute`_ ! .. |Doc Status| image:: https://readthedocs.org/projects/pytango/badge/?version=latest :target: http://pytango.readthedocs.io/en/latest :alt: .. |Build Status| image:: https://travis-ci.org/tango-controls/pytango.svg :target: https://travis-ci.org/tango-controls/pytango :alt: .. |Pypi Version| image:: https://img.shields.io/pypi/v/PyTango.svg :target: https://pypi.python.org/pypi/PyTango :alt: .. |Python Versions| image:: https://img.shields.io/pypi/pyversions/PyTango.svg :target: https://pypi.python.org/pypi/PyTango/ :alt: .. |Anaconda Cloud| image:: https://anaconda.org/tango-controls/pytango/badges/version.svg :target: https://anaconda.org/tango-controls/pytango :alt: .. |Codacy| image:: https://api.codacy.com/project/badge/Grade/c8f2b9fbdcd74f44b41bb4babcb4c8f3 :target: https://www.codacy.com/app/tango-controls/pytango?utm_source=github.com&utm_medium=referral&utm_content=tango-controls/pytango&utm_campaign=badger :alt: Codacy Badge .. _Tango: http://tango-controls.org .. _Tango C++ API: http://esrf.eu/computing/cs/tango/tango_doc/kernel_doc/cpp_doc .. _PyTango: http://github.com/tango-cs/pytango .. _PyPI: http://pypi.python.org/pypi/pytango .. _libtango: http://tango-controls.org/downloads/source .. _Boost.Python: http://boost.org/doc/libs/1_61_0/libs/python/doc/html .. _numpy: http://pypi.python.org/pypi/numpy .. _six: http://pypi.python.org/pypi/six .. _setuptools: http://pypi.python.org/pypi/setuptools .. _sphinx: http://pypi.python.org/pypi/sphinx .. _futures: http://pypi.python.org/pypi/futures .. _gevent: http://pypi.python.org/pypi/gevents .. _ITango: http://pypi.python.org/pypi/itango .. _IPython: http://ipython.org .. _documentation: http://pytango.readthedocs.io/en/latest .. _Tango forums: http://tango-controls.org/community/forum .. _PR and bug reports: PyTango_ .. _sources: PyTango_ .. _How to Contribute: http://pytango.readthedocs.io/en/latest/how-to-contribute.html#how-to-contribute pytango-9.2.2/doc/000077500000000000000000000000001316324373100137555ustar00rootroot00000000000000pytango-9.2.2/doc/_static/000077500000000000000000000000001316324373100154035ustar00rootroot00000000000000pytango-9.2.2/doc/_static/PowerSupplyDS.py000066400000000000000000000051441316324373100205210ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """Demo power supply tango device server""" import time import numpy from tango import AttrQuality, AttrWriteType, DispLevel, DevState, DebugIt from tango.server import Device, attribute, command, pipe, device_property class PowerSupply(Device): voltage = attribute(label="Voltage", dtype=float, display_level=DispLevel.OPERATOR, access=AttrWriteType.READ, unit="V", format="8.4f", doc="the power supply voltage") current = attribute(label="Current", dtype=float, display_level=DispLevel.EXPERT, access=AttrWriteType.READ_WRITE, unit="A", format="8.4f", min_value=0.0, max_value=8.5, min_alarm=0.1, max_alarm=8.4, min_warning=0.5, max_warning=8.0, fget="get_current", fset="set_current", doc="the power supply current") noise = attribute(label="Noise", dtype=((int,),), max_dim_x=1024, max_dim_y=1024) info = pipe(label='Info') host = device_property(dtype=str) port = device_property(dtype=int, default_value=9788) def init_device(self): Device.init_device(self) self.__current = 0.0 self.set_state(DevState.STANDBY) def read_voltage(self): self.info_stream("read_voltage(%s, %d)", self.host, self.port) return 9.99, time.time(), AttrQuality.ATTR_WARNING def get_current(self): return self.__current def set_current(self, current): # should set the power supply current self.__current = current def read_info(self): return 'Information', dict(manufacturer='Tango', model='PS2000', version_number=123) @DebugIt() def read_noise(self): return numpy.random.random_integers(1000, size=(100, 100)) @command def TurnOn(self): # turn on the actual power supply here self.set_state(DevState.ON) @command def TurnOff(self): # turn off the actual power supply here self.set_state(DevState.OFF) @command(dtype_in=float, doc_in="Ramp target current", dtype_out=bool, doc_out="True if ramping went well, " "False otherwise") def Ramp(self, target_current): # should do the ramping return True if __name__ == "__main__": PowerSupply.run_server() pytango-9.2.2/doc/_static/arrow03.png000066400000000000000000000130451316324373100174110ustar00rootroot00000000000000PNG  IHDRZtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp i| IDATxpyAVKICl=S M\ɤL3![ $K@vlHI۴IvՐI1ekb&5ƺn|O+rg;}?;}w|!fB!, BX@!Ba!BX@!Ba!B!Ba!B!B !B!B !B!, BX@!B!, BX@!Ba!BX@!Ba!B!Ba!B!BHR0?PJ}Kxק$w o7d?ԩTeTdG$}1R2queov}|n 3M3!fϝ;wtxյ?+xY26S|W]}Q|p0|6;߈]ы+U 魃+ZqU!Fj/S'_O:K{E\|.#߫wD|*`2\9sr|17b+Zr]_bLퟱeW{n<-FTVN^՟XvTeo%_cUS|TTX*|O׍.7v&`!1V^u~E7^-p/굟[C7׎#F}V*USVUʩV|d*,Зo\|ż3"vc-U UmKYTї!'-T!}K_vڷk0c7}}$_zK驴|!BZ* lK_җ}Ȣ*,7 *1@H 2D%q?o|qL_v T*;w_kzX&ikm8>*1wY78µf}{Fśn\Y~ܸhG5Ţ{GN psd1u{7r|6LF\ܿ[#IpW8B'ոn灺%&%(.pNݏ<4rT.I˚Jw.p gv"K+ *Eן=3dhS)Wn3nNp#\ H1㨸4ȯZ ;H txj~~#1 Gu cV{>~XYٶnMڶ?czrX.p g#aoguw~JFqE*L;]ao[U+2w<ϯTZ:^o?3a=Vw5'p;bvVD]}O9{?'T*뺡m[mUww$e8 p3"NcXufq[ bFbr$A='5 "NcXu`ʕ+w]ׯV+Z&V^{M?T:Np#\ wINv11͑iydEJe[mf.}p5OYW81@,&' i5CYWI f$r~U'|pw@)N߰;<<3rP7˪,ߗeah]Vz} Ny1M p3b̙~&Xe&@Jg}Fbp%:G6 bux/6ɪ{PqJՁfLPBġ|p;bX  "6|V8dLYy2 vbGj8K0 gM2s bW_yaA~uq;.˯G¹A~N*޾}UVuZ0MSy gIgCohntp: AB̯+z.瑈H~G :bvڕ=W>tϟ?)jh_ew8 G|/7 g!bٷ|m7ґYᇽ~e;8hTȷiWzCֈ gv4Ģ2#!Fqז zq K~mĨݾ*+Ţg߿-$)wlzǖ_#a= FpW￯?_ۈQ}㲅u?Պ;ۮ3i?"+"^Zj { pL. Ĥ/k@+Ͻ([}`$#\WGL$^Ygjbb}qVH:];˶olcWHG|p@1)Qg| B++Ԑw678pG jT$6qeJlqQ˯/1 I`~GǺA-oJ o4lz|bҞ-+ T.׊np]=Q}GA-4CJ"|:ؘ)qˤ-핃S/tn/\78pG J(GwKAחXlNP~W nuՃڈb9pW↑*'&'bRٖl;eÝw~iu纐p}'p;bX &ﺺ=N_:16>9Dܹ"h)^ᏇC;_m.?kpE#\ w<_XM~ 6%N_.w}ȾSp˯?SEmc;L3U(ɉ X**JRYsݶ>sYo]') g#E $+eylVǪoZU?ɛ<\߻瞚#\ w c  5$*a5A~}1|~9R6 P,{tp,VxSItss5'ܣGAD $L"lZظNkoZ{*=68Bt578aw⻰׃l5IP~ kvѪogHɖ"29aKʤo;H|~p ;V}Sl:ub{n(Kf/s.k u7NL^ճ3@!RDR%[$iy;Vɯ- <"o%=[?=[(%&}븶k|Vu3yuپ2drZeRafÝ\[]8ϤR)Tjo]%] $ĢNK~ϋ#u*K(itA٭Xl>tuϛVt=ƴ1i{`bjMͽWROkoqu%n[cNĪkT_3# z'}xߠeHv8rkF37 mjtJv Q5IZї\ҷwD}e[;?-wӗUiDk Jȡc w*ӗIMġ2}naB rJS_C +ᾼ +Ѿ_@Z}$xmaK)yޕJkff!_̢UѵZhK;IT*eWXyA'̤3]7S2/O8+q>b٫e=&߷Z?3 -)ѷocvyvue(ax3knoz>ncX."Vi_~?>nݺ ~'kL(۶}3[[?_ރzm/cX|?/ķ/F=*%_/D_˷#6CX7d~RĘZ֪ F"|ᚒ18mU"y.XKX or|/*5߮/ Iуahz~L`BL0;i˸{C.UHmBwXj{ׯ[TS3&ү/ovoB!,މN!BHwKAIENDB`pytango-9.2.2/doc/_static/banner1.png000066400000000000000000007202011316324373100174410ustar00rootroot00000000000000PNG  IHDRX[sRGBbKGD pHYs  tIME ;4"qo IDATxyeW]&Z{87uQ J a$CYRT"6s)l F>$ia(dre*[RD)!RD L$Q 5Tƽ Xx5qnZ8^M% zg̩g*k FzzZFdy!2mڦmڦmgi hj\u3jb܌iփ0kZQ]XعzdVk.x扠&`10F{Fk4uR @0Z  @{0`CŽЬ70"YVo 74?W })i "o.|$@A;P8w4uG|0ڳW_bρK7>²k5Rʗe:ekD"$LD$q8s,` X^!(&=}rb``iFADF($(q$ĂgArJiu"3|Hh<͌_ݏd`DJ%؊s£$'Wɓǹ6m6m۱xoF#hb&{0 E*0FQCD"i|%A=г;wn!A=& }iPgq;VZe 5NNJCd"˒T*ϱc&XlБNbG4T 4R1)CR(p實ROTXX +$ˠ(@hHiM!03O`taQ N8#aΝ}N3FAT@8Gg>h5[ʫ0>|2h@Q# @)8LCED8rWPT~dp~b ' TedF8 q9[&hrN!sBKQR-~22%;l e>Ghz`@₲#^`]M۴Mx+`mkjf-*k`JD֝zcՠvBٖ@q(*aVٶ{nK٠xF(%f%-̲GXHA7*lͶyh=O"s\s v4ۈDS` fI1*C*Al10BS0?#= /,;s\CD%[M]67|]ybY`E?Ҫ!鯮$9m6mӶA#8pUi8$Js b- FzsJ9_YZ@s6̼9YOJ:3L~y,R'HKpKJˋIؑ,̮X9E+w:x'x+ Qq54RfIr#!% Rj>SVT>uWE@` 6 r1F!X ଃMbI05fF+$$8̎/e,,ˆC°N@F3>V~ j5ԛ 4M4[MMavx_~P |haF)(5(/Q0JA+ BD g mJPdC&bRIV쿲Emat1y@WƲ&*M)牤 |qFcR⎋ N1{MJ " QJ 7]ixF5NʼnKkZ'Ž" (J31 D4p]oK9X6%°^k(YZNY32 -AM۴l^&`MqpN4z[F-Bkfauf2ko6"DWh`V(7D(Y8NEbȡTRYe3dn)l^Or)SfI2Z(2Jr*32n0&b6s/ュ"B$ʅ7$cy;#\"a-vqYJs 9 kQ@l-Ba 4mt ^x)e<zVffFaxJ15kAO~-FiC蛫:yAdyS3]5Fp0>R 3)H8 R0ҁN~ә/(Dܯř7Y^"W\8.cDJBIqцU5'JW=9&22"0Hi !Wm[f.g LtR6 DIVݔ&VON2;K~g*"Z9ث#y%Dс-'VXǐ Mκ~4_G/ O9;diFٴMg׾#ڞm+F-O(Nq1fW}jȉrE!qiXAr ML QZ_tW0r Ih<9|#jY%@9E2wUIpΥIX$!ċNJ?AN{P:UHiMJ$ 0DQG f:3hh43dcL&zM s:0B(@K>!/]RL%xٹظ軹xܑQn]V:o'@)\473]2rh|MzWLMrJm$z4W7{8fX`EbQ:R ~Vj9FL? Lx~Jx^{w7 ll^}(>-K] 3P)gSMT WWA*{r%T!P"s$K^}_YG?X&ؼm\<Q̢++`eI 'yjGQz FniN]t'-SlѰG̎m;̾mzlG5jiݩ`EYYH|ϴ6k:a-ߘV&i\oMwF)R;py=Y1SZ/))q>0`Ky\@JCe,Y.14I^oܫ{$M٭Ms-,>8!fs@"E:a$19;k vm\eYd9+B=׺h7q;wO_w{+Ow:vnmfUjfݯ~ڶRH@܌gt]+]gv-7$Vd㚰IgH4q:gPAƨ"o~fl,xri{w~ ;C?w\\7nl}?Ϟ9Cx?\‹o;r%N/hxÞpxyW-7GкFͯp}__;۰kp#qjN*ZcO(E%5OR`qW"{nVHD"}՚Z`Pju'7%`'tY uL+go@k湙nmmkZ'l6|ι jO\b[Z5a斯aCRgHU[JsN #IW"E(<@9H*%i<*c̣a;qUl鸕UfnJ8)Q$UR8ƌd`Uv$pV`Q.JRxeVʬ1)M!8cRAި^k g"?|A"ү ᇩ<&Z:&06))긒ujeLҘhoGxTNQDUv* cDYhT"s`»,su(!5,߿g }Z"M=9S'C}?_/x[GQk&<@jA1Mbgq> g|g? <=s ߻ .|xfW]Hp_R~lxo>o<[G,/lk8s:;_]uK|a|_[{~gp9yFZSbR o7p^ ւ`{;|٨z6=:֭ZZgHl$ b#O fG(V`a@Y1W|4۩17K,lb:s,D}8瞖ʄO*11)TTe*NK)+#^&Lㅧ!qL>!.il SM0f@Ԓh2p@ƊJ&Eqg !Q(J x mقn;efteśRPZAiM L `<)ƅ\" @V۽P^ ձ'U5UFTT%qUI\ V01)W2Q\ "9Y ]r Ҁ6U$;y9f:>}cQ?#xO][o9|Ÿ݇9~{D_}7\f<!.x8]cE5ƞ8s :׿H8F d?sc9_HσX_YǕo{=|n|SpA-(O,6 c+h{]覃/toPTA&%=g1KYVYFZUZ8Jj1lKf]Ԅ V5 IQ)U9&˪5őC2H0: =kW52c3Sa.ޏtGNKdG4VU+/s"3ˣ9,ˋ҉x>O d^2͖2KZLY J*&ek,,"ZCu_wU['.m^sEW)BJ'Qz/'`q?22ߣ]}Exc]aϿdZvluf:V#m7^`t` $xFٺYnRzF+j1&:0Dn%'h!em6R0o䟞DE|~e|;B#i+oB$:t 4HbW\3=m’EY[sFhp ̎zŒ!SP"6h>Ig7!˂fL3PUl)h Du*D\t]589ӇO;3t@E=ABz]{yASd `d9#L".+ ʗ(?^BiyOe3#;S뭲YB%2jUeB QUH 8=UZCC1)Dā?Gm.&R]Y;>$[9$|?ע8^\\.9v|ieľpue7\~)8t `ڎsv=Zt$pI5N:m̧|}u ߉?7>к=3m#|3Ο9GHD?{ S8vywr˿ 7\xtrߌJ\yV|u 7vis.47} YGxoۛ;ow7_5O|~/˯Gg`pQ\tA~a2)*cݜ }!)2S*𤔕{&(S(;D7~Ψe2fa=2!"/g  flW J!jN@bT&P4(Nz([gxОZW/7 jlim:;3n6g[pwni5Vy3F-7}kNlKF)_}k@96"p$N.]pr,\sWR詰ʘWrr:,Ŵ"X8!  ˊZUztм%r+N]7?q󥯔[t$os?|W`-xhC{o%/z t'bu_+14^D+=L U@*"C!ۏV: ȕaJ0J01FBbfv[lέ[0y3: {^CQG-4 ) Ι ҺOYD X I,*S5OzcbOIer>VKIq,.5ǂ 7ăc^E+c"Rl*JB,'p94{;[O G~FdBTe87ĜFvdYzSƇsN7R qq`-~wwMoA\^?sW8p#ƣKo ;Y ϢXVx{㏣?8m?ߊ/}.ڻsM'D1@XkcvE$dRd<`5V^`Uc$ؕ"8"~|fxIETNY.e|d Z 4 ]{3JYgF}ԙ"A'Eǰ8a=wÃ~´B au"&V(lU? I&kLJҽ! | *gH֘Tj0yXHH+Bj?PƤ|p֘4lQa$ #MdI> 2Z ^JsuubZs?nY\g\r gOqv%(\_{k_i\ҿ=wSp'];>~$^s݃Ǟ_}M860~/W/t>(>yx[ކk7[owݴ_K8V#0Vw GCZ?|?6]>iqV~/x`8Dufs1.ɧg}`-&́X.?T{n71sLfϽgdK>3eOpWgr[ jK)AʵddΙ*Y`4&Uic tB6*5HP[hLa5H @UVe9R+Y.HIŔ",)NݱhIǬ>3KVS,| OƌVjeN#X:|g`.#$̈]gȕY.I =IRVZ6\܎+d0"|ϐ򲰍rxp3X9}}؆/|>`п}k > YWny|h< V͇EC$iU\jtjh4!o`L_ k5Zok~^QlQ:5" MFqΡ׭Lw37jQ&}IRk"8z cj^Z&BJ\BƯ ^H4̗ Vŗ4!}Sv sSEc4Ʌ(*|cT!ԝ a̼_p`gl> .fLƇ8 2¾ˮY{kk ?uS Dci,lpHa CzObh-X_?ūV{0K<[!h]э6EHCèǨ_I־~4?^Ό~ϧq#nP.@Gkغ[/Av>=hqm_B&gve?t W?sa|/G}o^\o=0>gCWa7a˷};Us| ˦di"?{&uNŎ fALbERdK,ے$,ݵw}%k{]wʶA,S@JAL LU]]=P GetuOws}o(O}W [YU8|OdF8S,Q S鮟2Ur_ y|zqMi(k%xJʋ+r/swZ,VBb*SW"N1tABP5P8B8v x_pE>?q~瓟b0w0qPms)a=N]] ֶ027~.ͦYbYfg1-Stw/>ЇOq<cǭ;^-!=ý}JG~-:jk镫}iD;n=4s}qtF AHwTu-q$q5RqH`VrݑѕojE'*XH, CHJ<_ ANB,ݮǕXBeeDaTMG3tWV[C,'Q[]M]] HCw&C!5V'6@YVRy&W,.X;Vt覉RHQcú.f%KWqf?_>/͛-8EFf9fSSnlh6ARq^0܄d4Wgme:~&dbG>[CK?}x+BҵknRc}h‹X~3Zfc3hϟ6T8}t*Mcz>͜?sJe(唦 ,Ͷlۡ?.ԼZ)I+d%M+G42(h(3&tfz1(,qTU3~تW)]s#*T VqliUt'ʜ7PS Ld?bWJn,HQI,7#d[.pH\2o8U$<)I5)AO2@l-+(Rɿc ~?Ǜ̀P6iGa 6n蘡 T4 #-Dq ^$I,s4TO+=~eXބW#=۱lo9~Oz&Si퀵̤`O˴Z ޅrQ4]/_!aerۊ 2*o YsdR2?eEhеA/qW)*J6v@t\)!Tѹog AQ!4ýjn8'"B{AER,^R:bTb0CD5XǽIsMS*:bu@OUqC33I6IfP%LNi)Z@m;B5h^FZL DHVD2?KkS#Y[/>*za=^؋d!s?p?AmLL La-φ%q!a>o܉srT_{??S)0cYy6X\6CSz6,b C%J&8`o{ϡUBKF?J}sd?I:uմ( Jc 8ڶ ږlP5 4(/h]GW` F_"LHz&|Lb'u&!*J/[a'BAU@ *TU īD"Qי;P$PYh.P7P5Ӽ|=,AlǭѨE[)rsY& TXJUBk6Y$9Z,4[4cXY0 uǯ].-~=dPPS&xNX•a|<*2=R NJ[)/_Bx4M\VQ|ARv5G -cb`̑bł;s97|6QT ^=4dB!tBƞcP.PE9QsΧDh6Z͉оKI+ ]4YH #-hM̒+D"dtBCנhJ:N~Ts1}O]{lZ[sOH:S h7\a\m_ϝwgn6w.LuoQ籯=mg-7d__UnY }^?JJH_ ^]Uȓ8mq.^¢pRR,No=A._&O^TBHsKvD:gK<"2?3Ve3_;=KcOW~]w?H\tn7԰g!H17 ! on ZŲ,&OZ /ԡtZKSkh 9|?G"1إ NF±8J" ?þG^ְcc=N5yM'S,YEzfOz K?5Ⱥ>w[^]M*4Usۦ5C!_Xp:=:U#$lrV-=WW`*[VT/]/m+ o& Q)0N E wv/W" *aGjA2]wl Q=oh$J(V fH' ڊڝJh-XV묲0S"zm;rn"e>c0uZ*<TwD#1 /+)K˼& ̔*N Wr~0 ^%t9 M'Ke)K.J햕t TE13bQ‚咤Y`fM\Jk?YqplcٮbҲpeH‹*F/m#K@_Qݜ@vTMGMPÌj* @WmGW--~Z+݋xuU)RE-3@Ed %Qɳ\GnONJThebS:$W\8#XO-00tPu5xVնD,TX1Y)Ɗk ټu/J%t,q(Xg5ogUg'#S؞*aYnz/xV1.ֱu0r {{Fx#yG?EVxvsOq$w}'s8vfe6 N>aK/&##8TUu6RЄB&V& OWL6VoiTz(LNRr2s$E B󃗐@13W届w:w1x!8s5< t Z2;lx;M=yç|MyOQܬBfmE?s]{{5hvܸ۲\eybrd+ki",*.jR/5t@E*4*AbS{\ +Ej~`8H񎷪-4E10xאZ!Gx'1>? -w{+WĢ-~~qAwL,rdR)_ "mSVrI?!031=MTzqx{ϔ+*b c95\$.Ҍg2hCSilf Ea:5Qq3>| iXCϳuj.NE*NrcE<igΣuܳ'>ooY!vz^aB93c7vvi X82FuՌk/P'7Z:#|yC˒[ߏs)Z%xQBux*!=muUtB/+Jv۬y*2*Hab~>^a4ܽKcc̤ 75cZ f#9ڛb Oеr%QCHES>tft~F&x_cʲ*C}#E߅ Lө/ƍ+*B|p_ȒW0ATEEv+#ʭ{b|蔉%Ţo@pj~+UJ~hIGz9˝pĻ‘{zX ϠYE:~Jt4@7Mse8N+ XYfYW aB6fx*zpe( ؎M>evzƲ{!l>A6x_Cb9epNQfBBa397zuW*d9ʷ9s85d2Db.0jZײ4'p˝T{pyRc= M(SÜ?yqgČNrxE"QtU]ܹ7jj6z->}u+Zs$ \hi%MbFk!YffjhP W6S}Ul޶3j`Xdf.A~cfCg'sofہa[9寮}%<O{h;H}aT*˓3Rp7T-þtã6-QI&qV͛> \@h Nη Qb. [7pEa+gعmE[{*Wdm#4MuMB51B/p^ʦ,kBe~zd}tb0CpB>kY0u% }իQYA~ y|qiJͥ*SU㮩8d<굄UCot++7C.0 I*HA}Ӝ+W|ՏJEY.XIlU毆t5U]n"Tr,TjtEVAQ]att _zƿ-KsqzN \\,@i}|)rM d:^'.L_|ť~4 Rk(?gٶ=|/)*,byʼn{_ĉͤqg~bf PnDCq:I%PB$Va혓p5,3 rt6wpvl)klylncfh.A`Snn]Qŗu ;#elbfqƧxo6۟r$׿-ʼ>ѳyE|>7;{v1ϩ[`x%"h377%hN+BG:x2+Kbp6JJp%,TU7ixXP(L454 7$uyGk )U\ ˒SJ"T*6e_t),MSH&'4_d&)*`>1dH{k3#'dyk \E0ul:,5et^cI}C1pv6D͊RB Ngѕ7d 8u)Ge57R-.K>:/}ԡW·v6Ou;q,As&a,oJٰy)oe' nN˱IkcqD$3FS Fk]Yf:6 b=6^Px`H@\uIjr]8oaHP8N<A7 4Mm)TB_Mxjb|!ŕve4o_Jƫ[Pl#,.S܁`3p:"BQ*,7w OO3?=hmMG?w˜~=}9#S F#[8~vNVmHum:Nr cp&`5ɹJ˶4amIfTiE+K&ƌ@,c+`AsC _f h_̟{YjbYrjN_ ɳiV\_Ă@Trxn92y%c7@Ы8.^o>K]鿁p8i躁nDQB!U݊S$?t 2a[E2GKz>(l;m\&puU0yrۖ^b~vE1>tWΥhB+Er#8|+s#FY_dܪ۶s\ٲnBgӯq$S;o^CMHqlIkǚ11|(yg}'Ir(4vЪf8Kws5Ξc~*Mkns<8KªzuKk93ʶ˓k!~F¶ܹ2UlYSC{r:V)8.G׹3QmldVŢ^[z+mJ*{lp D,}4U :Rz4MG7Ltꥌ, ̽*kEQ*g!I2$Bi IDAT+|Bw*"UҿMX[T8 fD["6'xpRsUhY^ߣϸsg8 PL|b\m&3$#%.13I2!%tF8~N9fg˦Udrj;o~9M_‰\cFtjk#6x>N'ыL$ :rc1J8',WBiz Gu0cD"a 4M"ix| M=NRn)"J/K>`<#Dz\?$"IQpbs*s\g2La"4َXR!!{}$uS=R5m5qd8MuhĵnشF&Z2>6GTb ٰv{ e+zvkw ō4A!ò,Ex4Lp(MM)%#eZ3䶾\Y.5@ (g tZi!OگqRArBQ jFoP"HHN\Tv_e}׶L;fe]'umlVUNJv"K-ڢhLg958V \iz;G!3Ƒi#&ṟ<{r8L<#&5-m\An.=?@r &,/y̌ [Y-yv0QVLoeMxqQZg ܲ#dIYٴ#L+2 HfԵZ8JM;"E/_MUUsPElucYhӑTErb[xlEDZ؏--wJu`j ZVZUȻm|ԱmR$tD*TCm*K&ֈ7\͹xqVۀX$gǙ'νoXo@9~5FRMr,3IDK.}{^*Z .rxj,N}ucqL?-nff M4V=l"|eO?4zDd\>K.$57C.bAnǢپe{Iv]D=8wٱc\+0ӗH_ǚ -ǶPUg0{OgӍ;D%6f\FH;LM͠Qjxi zH-g4z lN2̒8  eS(ZTGl1t,@Mm3|\+wlpԔ$op[iBE ?4;Wzh CV j0tAX+%1k9: = ҶH$hUh\EWk8e>??pCwRfp7$"-}m AM'_0eswm׶ʵ7 f^{q Y& ;;{ .дi'fb6D"m(:qR~Ebvd"CMVג8Xz Uܶa #'ƨWMV>'Z:,{!Q;kJw(lJAMtٜB[S5+VqݦM-MiiműmJXCk(8taV!" 9r%EVl각rQUQuaf(rLڣ{FQG]ՙ+`k˺[*(Pr˄Ibp^4\E^'}\1+Þ9j$໿#ꗰtY'Z/1t=';Xm'ut(ͼ*|n^&Bu۹aR mcK7:xs5&ҵ'3K?!U]vR*X׮k۵Um?t=҄`ff3=>!jZYd(]s)cq1?YDWgEWK>)O*"NIY(<աCfvKw(qraLME Z!<iFB,K<ץ% !mSlVsRă'*0y4GfM^a=g#uH,c6,EIݖ p Yu7~= f]kיt^5kiy5j]|O$Fb6(Ef,W>˹scZc&,mPʱ'-7-q\]~~PebaReK[ *tPҢ}|jͫ%Z]G*q2C'Al~%|5 /gYyIϗ4eVԠ?48ׇhmWCZyL_Qڠ@QZδ Dv.a[B-dT-]XZܰ W TZ_W׶Ju۲"4`ٶæ_iǟadlHm-:6¬dt"_+l.e%$TUu;n!Y *[︓M,kgvj[Y܊dʣs/*OkZζ7smy/G|Xk~??Ҫ psܴjIyuMwmXdgv:&2bܬ4ǶpmׯW8;L}z&=E<5z87Ź C<˿>:ώ) Љ6TEm-9vtdK<3|~史4nc*M/-p Ŵwp2YBAB5QURTkd٣6aM`-92)Xۍ7*o-rX4Vj#*;?qW@8ft?pk` jTPꖱ֥e", mTTieG`zm[>+o!&@b kJ?#DeR)RrE޽,qI9n`R ~ۭ&G4qp㴴3rhuvűǿpfZ9~"4aZryƄ9ɇob\nQs{|Vwя ,Wiv'+;Mo|tJmN,yk N8lܰ0WԈM]$+\O)B8n(H߫+~Cٯ] Ӄ8KذFv!KɭȕRT%S4z+,viDQ$\B)^ ,ϜRo#kh)M!’q$Vʃ^dkHrTw`1TjFY` ,vcE|`Bhӣ:Cf>̩y93G{[}A9)KUYɳs %tX ''kYѽ i:H:Iݒ5<;хJk<5m)񈎜<#d`&n}_z([nnZxz>;{MaBȚ3_zMmHr X"8bA#%JLF_G: "Iʄy%F^j#!PCek1ݳwO̿SeWRanq )k7 0Q0'<7/Tp˸zt])/w_xS~{c*l*oUZԔ*Ng E!tK.Xtl,XV\lU'u !}A-hDh Wa (7tWѽ}31frh0t,]Ch:\n&o=i`>}/:ZyŢC.bj073š!st-T1 3>KBdM {坏"˻u7ɾUX]dH;DZf5ݲ(.G_́#g{yYkIvFL==Gwz܁ _ >Or}Z'ATCJsFnڴBAwP7l*LݲUg*M5Fx-N(dRI a[۱B5YCOgߝFp8aH4ݛQ%.<KO,zUw9u@&Z>epBJw7yB֦yBt6dWwS#E.\6]if֚7HH'rf4u7n`iF6o6[XOs.ce/M!;\%JR` ę>NezHOfm#Kxmk%Vnؑ>ΜaqBpڦNE>rVJu7a%ulry::ɭFE@r8O>:9GiVu+ƕ}B~&+v g.NKׯƺHOEjCtEwʕ]v|NP- )N2?2<5HϏ31LC6<|M"!X9{[>{!>{(^i>;fNF<dKs+u d%KiOFqpXaz$\ʦZ3g+h< $8{|nq#YMrds^}aw=#aڗ50Zk j@o>"lsa sʬmC0;|loR*5Yg2f,T,3+o"i =TZL{SZt EӍ-9rTԌa9,,L؎ V:Ed)S|!<Hs6Vw2bo&i~_\Pz IDATѳBSQFA~ VPNLO[zb*54o&JɕB4裹8'w^ܔu@A1_;N+¥!'cAI6:;rEj_%qlo\z=|OC &H& SMеs&579=3=gʟ}H[&ocCH ^(ܸ݃f=l\3tw12ta~M78{7YcsiZ`e/wS9g1y&"RUYW;f ]Ǐ0pR!br0>6f8!M1xJ&.sY>yu;rj+DrQYOQ,8*f|{oYl3v֮IkivǸ?foˆ5ei<ٜ/_ܻ?jg̙?bKϑ.Zlnf̩tb03*h Ro?o)*|KOb7&4hXƱfɜ6$U(2aˉbMvP-p0Vl%f0*|QcjU2t:εf\J ~HTx]0E)T\AWv~,fd$F=^lXMz"h@dWe83]zN@(E5źS%EL+iu&2/R@ f)Pt@2KlOueO.]\2ئRO̘^g`e7}M( 8=KGԨg&Ua V1y4MgL ma,Sb G7~7 U^z9n){_`3 vA_7^ {O\prv0}ӝ6 ևs),55TϹo~1g˝CEuN'&U062ȁ \9,{+_iV^{q'Gg W /59&W_a\:(ʶs&"f=iFʊ©'QنwNz.._V{87^ݬp]?@mu P1c!%_RIife̪8*΢E(-K f<fg7G`%}(E h 2r>Pk`7a͊ Lb..]{p <>LKK+ODc! |'?T{\b\>y.)Ͻ]'َc˓ƶ +7ݛ)*s) |rnNDGU a3lٶ7n:S/doǸk7cX\`StY^O'DxnCم"{k~qC<_cGB<??W᣿Ŧ`T-Ϗ$Ɏ(?a{&?\:ſ?}P8]l,'⠱# UxȨ!΋Y}hN۶?E[4m/׏>J$0HvmwY)x>2iY,fP:31ͅء}t lw`F![~ɧ~ :;;8>J$0Y/ &2.sG >$~`)equ>YT b }Rj \'E^86`NkoᑏT-BW})Vqff׾;c^ڊ`Q8%U8gNtϡz)/lIPg%,NJhgOgՒ9;8xpBS\Ţ OD+*8C%̂n 7*XL gnM٩;\(ARHr0{/2w!)P"R6K3t8[6ԩrit#UZ D'輚J<4NFA @ٸ3_55N@äʴ Ƀ$%2ڝ.ꎕj \E6K!O[]![Xq T:R/&CpO??:-opϺc,ذh7;¢MwR>fЂcwt0>:΅ Pw}n{0k>ԲeajNs4'hiC.`ÆYV'iS xb,lVRQUppѰUۗWt|kL,*fQo}V/2DdO-j8),-Y Pt5(E'l2ȄX_jdBE&9]Mww0r Hłbu_o)~ ÚIvjBJyI6|gD5&dEC&ș^VO80L4X{GP,tqeYOv*؝ wJ4 gH+P\X1g[J/ ؘ\hb ?Q/=t9s`̸ _14F# BqО=ݱ!.ˑ}{QT eUU45F8r,~'%fʢ$@:.Api="ńYH|T^ʹNN ͊C{=_! \NGY7 ;4G83BYEp{wlzPQY8+'gV vFT%J̱a^~k/#ia&+ͧq{(/)dWDM]2}ݐ̤1R9i(<,LJrA"1)ϤkLVjjA7&k$P d[&*9ArBZ^.wҊ*Dm U08l&D#AdbߣJ+Q[+?xddْy#oj)R9k/Cm}Gsh?O~(_A#ȋMR=>;O.3RFZA3;:ذ8o+/lpW1E{6Wby2nN~tuCY&-~E J I ن+L4 Vo)vowʓ#V{GR2oHs + @/CAjeEk?Oi~6&cy1.(Yv |Q`.]N/0mzճ(w gզCBw"&ŮcǣH*;¯g?mb F34+, R3i|r#?,%˵SG}` LL4Jkގ<0LD6*b~zT"ZchFj}gMc; aJ.hlMāvu=h8HW+@xY7I`6̖j*cbdwY &M!ͅIqbC=C*vʋ1YزɳE"jĉIUٓhؙJQÑ.c )I2biMa"-NH cY-]JXr]-LQIu)DS42if9Ib27!D!~Z[/ d3szn)VEQm?ǑcUsQՀd ;_l>iox _^Ƴ{Qv.~8~p':L8JV@yZGܾαOSʱ\e-L_׷;asJ*T-߾*Rϧ*4ӯcrΥlf=QMCteMJQ  )H|=m3ҭ̫Iy39qf0Q-[JyRZd|&(&-٨#uSђiP'휯c ht?nrHg~FN{9ur fԹ^"-X<.ʩd ߘQ ġ]K92{@N[R O(Lġ=7'4Kͧi"wm^d`,荔tA AcY"N&[2CjAn:MwJ?㣌%yLHŤ\:-Ʃ};xu~TO)}!{q=LF9{9g>EӒ[V 6 V$NfErT 30g,3#'FGԦRTL͂rᗩW|B\N&CKx>I[/pzEys7rJ73|&l\ IH$r'Źq @N?d};߱/m#3_^aȃYQoWA(mDX|SuoSy!?9%8^?gOb'5;>I֬D?MV~QPHo:@ ]n45pj>5wuIoEsRm4}qT0VŎ*tݚ"UJ̙r#S%vgAt\-YCTÔ&T3H_g4ƥb|=aYF: @,?`ld+4567ڍspZc+b1FOƆQ-] <A w5Xz;邒 %ٛhI{=R$)}L^*2֌ M'dWj)AQ qOaE^avv)مӨ)CQL`1j58us](ΣrE)%9tƈYQ͔:pRBaL 7^z76cҞYR[RME{IfOMמb-&)bkN0jqpE0&U1vh` 0H-CjQgfSMkk ̭!qٽw~;|jˢSȍxm|B,[E`܇CQ)_[aNAH ,nrdq,X4Ts_S?OysWfl%/sV3;)MuP7?p-jRPT1= !$j22UYHD"FH(.%rh 4 ]Y:(a 0$bd%jwlk,ߗQ賖sELTCCHGR"( z=v=?{FBE~w^hUcex(tB} #-^Oa,T,!S>c/L Q$b4Kcwݵ mj|ʼN7Cp IDATaN5p>uCK]$d\ q?&W=;v@j:z{y;f/{N+VU?fG 3.3 XTҪjUB1v%rseIj/K,kSwW2A h$k޽LȩKt2:L΍NI#d`&fУ0y;B}ȢBH"t*se̍5F@$bo0>E%+wgֱG!4" uʗWѾnƭ^ʲmW xCAbحfQl'8Hp?{v~Tr.d rxݨYn^ $:Amr|{Q0n kTLIQZ$DZ_l^}-lK=cufQX9Ue>+(Yȍ7f/ڢ0R^^NYEL(!cB4ӡ(-מ{/eM Oa=oo\c[){HQ" p♾88`(e{! Ōn)&~j&S|o-rSׇaּũvYR3;'iN9a!IH N΅mOrkNO.%vr݈s<-,x'uN}de9Me,6v,*s7R',$[F *Z<[έx)ݘ&a[\&]t9Bl )Av%a!I{r%ZLE9A޵{Ai|$}_"Dh̍7gLN\/j:{ ڟC$_6=^Crb3:$}J),B)%NO1>t7Q_Bd9޲3w}ٌ!U~%-%uwVH\b*ephՎňE#LLub@0 LXlvТÑW嗢"WڛxL1eY=,dphiq0z1᜙8wP -6"f;}!&7U(R6\ogr`A#nhmfqU.8ri"R#nH9<ǖfa}N`[ 5+xI ,[YPP\*FctJnq:0)%* xT@eݝPڂ)[ l_w&%kV'A^^Ï?Fq=W5h"igdP "JaQW:pʪ,]89*nb/`~K2> P! l*/^DUxfB^wusc|U̒/]!\ʼr=9rY' Um;E(fv]-AfRȯ%u`r-M7s;~KyN%4ff㋪.X( iF#N EQDE< 0~uM/nj,Js4JB唦y!(Xgе(JF1DR`%#RtpOXbU7&{h*&,c65dƔ!#A'ձ-S sZpp yfŘ/#0>7X͒LB s * ,xܴ~%[)J[oEFO:O_ʦvlwϞ$(Ǩy7(8Kl;x;ﻏojUqx;D9vf~gh0}sT>x0Vc6;͵c<*b1NMXvث dz]CK6ER5QYuu Qڮ 3.r(peS\ƕ3'j3,jk&{v5.[bB!T++oځ~/ӭBe]44lbngUWgnLb2ٖ[Kbi} %x?<:|iFvQ%ˋ*5i hORqquh3eILO,}s&~JZܜ ƮɌc3E쏾4gzIr}IqS+7%H)Nz"Q`OuʅK}{}hYGp'Zٷ9"&vfZ|ٶu'?Wpy1BbMe;]I"b# ڧzM[XU?57mȆR: ^7vq9";zQ½nir_jM ,ޏy9 dĢJ'B(pYuaCɨx7N:;0e2q Ks3a\~/&\N (f63X` v]!`inEs 1]Xr30Ţ]JR-~E T~d))x _(:ccQ<4ڡȨ-M&K\ED,:upTJ#m54StS -4oUj>~K&ka&lA7-DtufgS\^/7P|9:11NΟ?˂ncE.W0~/cP{hp x@XM 4; b9C:uV'W gR^5Ꙙxӊ&3)'?jdeS8ŧMEu%&t AJ>?edŔW[X.3~V^+I蟷z!l)SeCyTJX\YV'5~ŠvogVnw7,Kh'OP:3a"4::X`g7WxlAvhH,BA4āob) tŤbw:%Hfsfbp7(eFyo|>:Xn}Ge53B5ZGC港cN^4L%6%a}uIV/옒)pdRS^@9{Itُɚ%>\,dF9))>M?54): RI߆/ 'sPR/Ұ3[4cl!C Qʋ:\O~vN{0H&=BWIbtc~r3OX`a_Ih'JF^2*aϪjr06:,f3 w/^|㾫wg#5kc sOI^n>93OPXC~(]mjƆF"ttikspO@@.F82M !zuh+> +c-$fL6 K:P% i1S'>2>Cc=10U5L 0龮/}JL:zc ל 1e_ F;pzT:&2jO75xI@Mڨ[xj0XdD C#i(ՒC8iȄ{2$Q-5 pzC!1PičHb]_>ػP>@eg<1 3 Q NB{GKgl5At &yi2⮿?&+LN:t3qS@d_3% "﮳IŤ):QF^i9v?I*YYLRy_ L*Z,?N\&q"!m}Scd#CG@(< HxokTT3{vr/<6LÙfJz[ \jcez.2s';)(/ pF!JO/]Cܻg[޻\׌LM8Uؔ?)}~f&6sȘM\Tf蒳iqAJM̄U%L5BQJ$SN_d>yl4-B~xTX4PTH8^d¤(9X|$P XTBFل7_Fۚ"A X~"RlUY?LA@fn@K%YJT|t lQ!;2w4Li33g4x]eAQH 0λ ^:NˈFAI%sJ+f``WV}ʕYL``|>ÙW-}4?lq e˘q\7rٸqsyx„jD#;-@DrG(eJ;LO,V;YUx./cs(;vX$L8cRx?& \WKؙ4k4I^\eOT19M&UĀz}3mAhi1DCjB'uJgȥ%`]~jj1%2Eb( JK)\ad(`M8x6i 1>4H(caAQV|,8's%wmHMŰ8VӴTLD!9)ф@1X\YXzZhD# geB4ĸ?ײ c}Ge$:+5 ~ْfy\o9mEnan"-;K8Jn5}ظ3U, ,-Sih |7$8G{he/20O}a׋Eh'eƙXvC׾DUQ-Gpgg P6-YD`hPh*c10QMC A t24 WY:V/BkNFD~>䉅9{&lAaXghX]R$7sB0o֭YA'1EK\a,'8 {&2r%BidDbAhHp1@_ fCUaH?k2"-p` HquҸ7R5a1t7a˫y<E0əsMtz϶RpEn};ya6NoC)Jyl)B0}Y[PlM*}iDa-yw”+,eSވO)d㚥w5o|.;ǥ!zza#!ʦ1iBQY_uB&ΫYҗĤk4ӐT148d>Z$2$8O;炣T)6ɬ$M!4d`jg{$UT%&3jr2߇?jiF IwUROc84®/r3%XG݅EUsvrB0Y[Ȱ.G Ea mE͙zzziԍ͓#:‘]+8lZ6sտ{O^f$Y%w|/W9b*&s&A-D!rJ7>B(n#8F#q 8Yl"|_M iY5ORz,UKv 7E.>¸u1KQVUɶ&}1C=:t,JzzG~/IJb1X:3Gr@(E%Y~79|UkptXd&_q_" c4`}?Suen:J(Kf8r_͜ЙQI5tq8)- ^SPX4 J“',/P%<ڸ|ň$jsWx]])'D|bA=nQ(`$q&WƟj[חL^SzAjw-) Δ1!g}0F|V$f"eԙ4E!`;96nH\1k*/e~BMm-@g;P5k0ǎkGA|T89o=#af.]Ê2Zi贲ni#-d~%7ϯ8i10xKj fb߾x^ rLT4ar!=wKq5QSvAQf2FdS-ta$;zZgRT E5]1t itK1Z<1ݩLM)EFӌQ@zh('ACHc1$c9k2YrWZ,tfIR'@j~.S]Hkױ"c}0ԭ̆eq*iMJE 6.)4D[[n/CRz̛ߧ=LB:̨:DE=ÔFVT?qlˢgԍŤX9)Bƿﯟ1H{r`9@O#s|;o$_D9YE?;Ng^z\Mnd(8ZaMUw9Żes8 7,7Qy$DcQ>?cc8"8+ÈXQƇjZ{ 38JJ pCW\v3wݲM'O3"}#fs# y㻡n݈oBiDGW/`o^ o))+eeA( B-wqϽj"V :467[X6>CTtjʧP^Ył57g~GE-eZ,&ju1k IDATwbs-"M0[b3etyťUF<[˵zdIz3skj̰^FHe`DS"+W1 9:B0g厈4ed!>& E  r qn6??+( v̢yQ]\ǎܓ¡st{N[c=;3pz;-W8t~PCn'~W@|ZmYX  Iܣ )$c*cm|7#=ŋn5H N|qF~RTI*ч&oJg˲R7c35Z$'I6QJR&xȲsI*fY fB2Z əbԊ /_~^$vnܸN[WIV1;J\''KJA zIMr' A2Z4~n!oSo1;7osU^s,+q LMր͢^ONM) IgSgeb7Yq07K =u`_|/~f|-"תށhv#!GoKvQ2)#Gog+㒞ܼrg̦K襴Or huZ Pd&Fpba1hp3 HMd3]669ʮ{ hFQ:,Mƨ ( r⛞M(ӫTؼ{'Vw=AmiڡloH +p=8Js۪y\8.#^+_W/&+ :uO"iepXF_M,6+VT?HNNCmQP;B8F]ݽI9 VQ\΁WE /[NZ;ۈK  JGywZTMIiF&JJQB>]j3(YU5EoFK<Ý{d@`=`gQYB$Y:$hXLh!,&N\bPI!/+oh{F p ~h$ F#!&I.G+ 옒3ΘDf+7ڰI TT5)9TW ǽ$`w캍i2"# jS_H`]k4wȵ.0/ x.R 1$%bEQ*P^AE^gd޲,D4ΐeʕVGJQʜUP9&FuAcK&"j1u~1ҼxE|.KT +JA3SDIn^I] 6^SE 26:'Y+9ݿC{;J NR,C]Jaў6QY]MI*϶` _vg˓Ϟ*xvЦЇ(Y/ɡLٰ=$^S]ο|l\Z%p=g(2蓩\OSw\=E} sE^Q**:hǷgƳn#!w6bMEM2.I$&]~DO_UKu9u qJ_;$>vnEVF=[RY $n{t<}o4k/>L岠VzK'}C=O ZK Xڈ>,Llb\\O!LLa`BC֡ qh,I}HSL/~^>y0߻v {_?2 WOVw@g ;n\ '!P dElHp杷ХQod?@c '1':V̯aqyhl(ʻGb'QKj~WG)8),p`08q0et#T$+|و Si+=s _ާ9<.7~e<_'+NvQ1"+'!IhllarbUϥeLeAm䅷*[7sl©8hQ_~[r(_*.;o[Ŕ/~z[jja|d>z|ӧX$ )82 3Y(/ǞF`W͡"7=E# Su#tw?KJ.%~tXS(Wf,!H`MOzJ&5z|WZ%W!s@]N!z5%+Y|LB))kNwWhZlY(j# !DkB}dVSOfnau!ʤLB~qY 0?5ǥ n]Oi-敦nl~9[d9)%Fg<%1Y&oB^ (XM4^Tg'C 3&۪[;D,ٴj@YaI@EA˗_0FodТ 5i#0=.hD #K (ߢ'0+슱VZ\fi4`4:UT"qRK ^c6ȭ^JZ$jGс@FN. MLJr, zK6My9( 6YFoe;Ch&gܒj]XTV1>6D;ˆZJgbd Fg"|W,4Ar+t AT!j@D&!ɑ EaQ&gW`~!GεscGFY-J MC'Lw34N"ur]r :QYpkjl9\;1̙^m!%ԢQw[~;KliVY!zU*Xt VmFDA L[$fAmE5+]QgfYnAͩLY#ZEQ@h"A3m,hDYba]L*D= bB֟ HM&t;UvB 2!țϼBm䦘u 6Rr vPC7`2Aq&$&}O=;m>;-'IC`_|MX2maҚ((.d^KEI&yQF]jGv>i;r?f$ +<uK{)a"/cֵXhuz2s2HIuP_Lz9',ze{:0cM?u+t4%,9ۡ_ɣ{sh5!,x-΁S2T7ɀOU!!*&OC+(@ (7oݿsڸ*H(*-Iቸ›|7B;#ϣNogڵfc9Cr.?L*х_YV-#=%]}*(IJ{/+ju8r+UHȢ >Z=:O|AԀ`L(>7ɰXv/f$GVA\JdEAo27E3+1ATi~;ƣBC܍w7TTQ:ʩYdNw[?|I:::(rɉ <Ȥ&pM)Rg!&<^&iomw( p- 9L@O0ӫXݭ44qf+WΜ t4suzj'Op~[鋔%gnm]82|/63af_>hKb"Zާ}MFf#}mRQ,ή̫(d)4[WA}2QSl"7I`U +wo409Lƫ\pRNJ,XfLMEwl]`G.?$4c4dW!{y(%2]]= VWmy=2F|il޾CeQ.~6fV ˠU&%466 rBR&=H.'J֯J'9vCc6lL>&%K56?BΝ5+ $ttdj琬WPJOA H8~V6Sl,?*$1aiF5d1BdFoĞFZZIf,04{"z^.A"bPQ茤C3Әz$!%0DnMdFuFAј,4ۅ_Vƞg_{܇99{IN׎SU%YE_@Gtߢգ0NȔAMS9ri k0 S>~9YZH#>td,?tb#/3F㚠zifBufK2gۍ<؃ȧ炻#4%ok7_wYnJ2ұ +^ƍsYr'9 _~Nek&L5 s»'=3L-ĔPw^zWnH,)GTo?ǹ!%d&]xC >d{Vhz;#,ٸt2M1pEe3K7meJ4v6n[]Q#ћ/"7^xlWQ;1s*;R1YT,XƮ[)M!"x!uuUv5\%%Ťaz3-VGH,F;X5Zņ-1Tzl)Fj FDףՄM w=늪<{L%$M3;gSW 0$𓖝0}^L&sq) {)2-AP%9}G[Z- \cHE'pwßڕ_] yF8AIB>VIJrr_"D $wLH˧rSUŷ'3>stp_y+ks292Bw)ÝMxEy4qy}rt\<>,ȱKP})F}M?blEo[]YvErJҁcԬu,$7ǝk-n޵$¡cD8~Rz'[P=F94=KgfǧH5z"8eϟ'5#d[rHlbVPDB1]NyE WΝA11nu{,F3yYKWfCzǍTUy"[wǜT8v>>`EƓSZcGr 4IF[I0G()/dh;{tR8qUYc ̈"oWeskuV-^ݬ6h8{)"z-I!tmf$ƫ8dqN73FiBc  T#_6.)481]xOנb#Od^r PXXg6d<{8bC IdWԐg gRR\@+c7xVB^$i#^CSO޿&A 3ٱb~ZF""LM :좼&5D~ mj 129`ގ4"M!nZKIfeZ> {V7Zٶ4yOOeܵŎɜýr;Zkͤ}4)|i&ʊe9W#jQ#er'F NKAAMZJ>[VÂekuhE# 4(kDE^~^"rϜlb?,`.:t:1 iTAԠ3hb3%\¿U,SaJЄ;E;EaZL dEyu&j?=3KvU+aZ 0-\ր3=OEKb( )Kj,nEFNT%A$;og|8RRT Z񆂳7gcɒT$V'PT4qW o8?鍄O"n2"F{c/g2} qk0vN6ǛAkdC4^Z{;f}o39>^gDh~!)لQMV$|> COÏSsu)d{}jξ}ׇQ@QS[ؙ8&qdfa]O13 Na4&hkASWғx0ht-еZMdp$5ztb䨤 C$dRkcEUB (K\\}+e R[P]#V{&pEvd+'d߅1y}ۖpwbvn~ėOc'8~cn)%K}Y)4yY[Z6Yge4O=:K6$lo,jDG Y˱$lbN>wJ J_?x?e֗Hg;/#aILgtb`y{ z$r JYv 91fnU9{_{urWP ͚% qlf^qCM= 9jk`C`gP[ASLB$g<^!ĕ9۷rTSRȏFԚLs`jmN#\o^`pp*Qk"Ǧlg$B1p: 2v%<Tܹ0ܣP\C\~FAIJ/REǷod* cP噺#i]4^ܑIͯ tڨL #VQ%BE (T-X>c鯲lɜCKJ6-t(2,dZ/1>'"3C NSĦH Q2}V62YNi^% }BTvL2~NzQ* ZHRb]paK5 %Dtehx#y/&PgdnmI ɾ*~.H_lf*b@g;Ӝ #a֙@a{&'pR3OG‚_v\9/:1Dy:[6m\cCطaRtv򆳛+YRh9uv{k€?/<'nj Z-Z -Lf=bAUwXbǻ>7=]iHJ5`4<ݓ#% 0Nd;iJqDQI9I=޸xsZR$#I&,-_hdJ*j*EN,O-AUXu1n,Zw.%٤;$m̜\Jc0O}Ay‘Z;?q.L&FNZ!z=A`#F%D4.x-dIv4-'vl)lAlD$D(b 1+y$"zm:\7$ezJnLbeHSKgޛ%O G(`ZڨTHd-OgdX\`FU( I3#?k*%!3Rkf::~j)$fKA-STƣ (nmm5,띔P嚈#&!âc!Cf6˯ƚd`ޢ%Hښ[ٸp=VdE«sf숡anZ@GKͦ,)&hL kp瑉lbLA7dngfa&ӈXԭgЀ-3unQXXIװjoa,)6#GuLF;KRXV`-``6jYp dw"Ei~E]¦℃/H/!,~*#%9dG -yȱ Q+YAKS29|sTC5qG՟&u`I2B%gԉE1l 1yO+U5 Ҵ0KY"f,T1\^}>L lfT?q+qzhz٢,"hѯihIԪ}fڹ~_ڔ!0  FN bnm'\̱))\ɳYz3x mV~h!/$#f{CڶmcnzU[vS~.02UHff1k6bQ:vOhFksو),CN<-GLVRa_dEBoI#GU̵bXi+Zi}$#ڑuBD'g GQ"`X@4IHe21fVԮ[H,))p$1 `!ffc!U# BiRbi.l$LVfטYrcZ)m6014=|?[6R0wJ;%98*1: 6M4+{B4po6c0IEqO[{-j; נXGuvk~eEž#c~3~]a.H#/w>;;X>>լ&:XkIJgzp.}aDhb (j*]Ѯ$UotxcpD`:"ֽDbSejZHp)?$d%'({Vq YomTnf[%[ۭwZ%Xס72@+PXCEߎ0M 'Nb evPĻU,:wn6 1+cͮ }SYVq h`PeQ&`E3pW,LWVNTi @53<0q3f3 fcԟ2%vtbB\clJ"ۥx0VC 5˾&Lb(8R?X)񅀒x_'1v+L{Eĭb `LLݞb)!'RBhZU72 0Z*t[(bAI3R)).Ġ ZbTx6 ,N$p4*\ FzNjt&ִ3}gt81gb!aAqVgSfL-̍T+BB^YzYTTY vx5ۑhȩ$t B|B%b锄ǯKbr/'$L̲< .?Lէi/YE ȑx!}ÌR\/9rgἘh8Z4`@~OG|Dx?qp hRnӠTZAZz)"@G 2) Rz#I;̜&BA} YcN]"7 T{YUUUTfŅ-EQd9ҥ*dT} usIJ{kml޴4ʮd(7I]wf3j6ޱ=ӛ) hw߹hbdhQKP+yE41ÞT*_OzW#b KuԌ48G'U.ZlHf&8'@Jb'WlU^GzT_+*`*0ScrתMXALs2AAڑ j p>g[%fz8(}V눷Sb>?5L;?/hT=H"|% 7 `.o3hT$.UQLYjs#FI16H!>IFV wpvF\n!Yc#!C0x|/yR>\g' eQ 2>ˍki4HA[{Ǹԇ_cuQ4=AzlS8u !現éS'9ۜхt^=\yHH3©'v:Ao<*B*, 1(bKA;<#rϓ1?oF0 きkj8[ڙnof˪ɴjimn$jA@)dD&$A543=`m*yH%G2y eAj;"FCP$BАLuI] 掩;nmϽC[Q'$*L3ţxSJf\2aZVڈPI6^ 僾V,!nL|SIeV+tds)PLQxE_/LcC`uz)Of[}(F>KVpSfy(P,XQyF4;פ n$bMvEVb%?E' (# qEp 3S^ D0A39ƘQ'"$BRϋޒQ Ht4)рlc\E" !kMd;2!A~v:aG\HA/>Bc]8'dC#qj&}3PP\J*ov zmbZ{ѩ$lVy#&%ޟѫ;W5s=Z` Ѿfy4+]$/ΣUpn oXd3^1]g:s1` svW?ηݫ:2o—vd?eCAh8ʞ}Hn}92VneYO8 װ, ٨b!dͦ$*.BQ07hn&VHKJF/#p*{:gA0w MO2'AJPpyY\lS;2&=z*G~u9x՛RΥ,9X4l4 c< \#4tTT׭f^I w?Q +#GxC$ex~ WNB^H͜r 2WQf LlZG;nm +œ'ޙdFWW!CΜ1)+0DDm*!nZTg.'tߩ}/FKʪ}rg@ bbTʹ48}CN,t2laGpRLԟ:K X`ntEx'ik'379RQ7qGR =rne9 h"XL90ş]eT)Hw9~ɀіu+:|I} tT.Zj+{x}9vnH^_l%f9jp^?KG@V(*?Q##sq1&K7nAi9kNJrR}B>:sA줭9QNġXOrjs\^|e:-;>^]fͲ~r)R-|+?#N6ܥ㙃xytF]MB>sM0J1`5yl{eŮ(jWԔL=ȃMU#wo_@@kOLnMfxQ_ѱ6IM*YRcrG_.'\C,_ҤVXhd?}cL9΀*Ul1@*(r8~(3Y $[X,d鹬Zɛ1sYz#\͋x}WxsM,f~E0do=?tK :kKO_#@|_1ov'Mt^A)+/r7Zu̍fF&&x}q+M@9?}^CY Xebg>'y#]gh^IXl ȱsuVv|//}_gۣVPU59Xt)DL62eWm4O>SNG;y}˴7f8GT´,!lu2EGK- 1wrJ6XMfN~z)&֥<{ww?G;dl&IK jwNs!_k%lF< SHHtFG 츥a< ٸ~1SãH -Ipbzf6d;?@4ϣǔ߇NPObd&4c."[fxxB=UyTQ^<do}o| (~ r 2I7b6F$b I"VJ!۴=Eg0aiD9ADQWFwY(Kobwy@\vo |>bN Xټ~>6Zcb*J|{J"yE$Gգ46I[2NyFF/"?Ed3OqiX䁇ơz-:s udʳ lA]KwU?[1Q\"$/{.*r)HԸ+u9s@vo׃Tme],X(@MgM=@ޢ,/3x 믓Wu6^w[z6rm'ӯ̲ &nSSGR:;&;&%IpM.Yq?? /Jt^GvQol-y(v>Za ֗>ųx7QmLf;wnct)ol8ŕؔ -3쑙h΅K1Rk8 .fNeOI]]AVQH=ԸLV^6nIfɮg+ؒmյ:#- De(IȢ70N:~JDžp=Tcήd)`?|Bn+v-حv>'d,t(}{]{85"lޱMFSs/{ab/ -GnEqo)^$AgnvyyoDu8T,m8\.Ts%a*|ݯw5U`%q׃:ouZ(DocǑs؜., $Lhz˧((-ƦL?ϩx]́򟃰Rcc5(辄(0߅aV3O` 7]n& 388`dP\Ʀ(W0{px)/-H$-Ld^#nŠKe +).#fsܽf[xy}v>Z\ Oi5pnKJLT4s!e,Yښ,+=/_|Vb,@vАhoaQhYq wL勸M>v*C(ft yvԝRʩ,@Nq^u 1Qݾ5R!UKn2M| <Ѯ >UL.E8*kq8t ^\#(& >($;bG2c=]DPq՜l#R/=8w^8Z,P,i[p(nG! `-bMb:$-4&6Yyq^bW;j&5÷SK02 ijL`fR>b!s5|Bi ,cʑt_OOӟe]SnUnI-|L\`dↃW%oD sw0Ѧ3 _M%{D9y:Qے8^C0NPE$z2WNh} W ڼ*=xM p8CiE紵/#1ކJBi9-3NazGb\m仜 :O$+qv =#WVnC8T `qu^$bfY[j|qtaֻ %a04:ci#tv+a+ܖvD}c\fz¹QlhlJ,"1{{{cYcuzWy,]ڎ.5I\ISM\Wod ?YQWS Hc]I{[o~`ZNAbF& L9{$ x<.zϛdtpY+PZ讝l;:qao'^|?AYu '^W'PPh1zBd4cͯM۹⣼*&ťeoD^Ii{=Œ>qgj{*1ILX?\ՁUNm8{ ɤ ,nyP=J3xr<9}q6R>avc5G4c8-~P0dr`7Ptt}Hr4x) EqS I &Ex倔╒T7.~I W#J+)$ '[rr)̎GtJv'kQVp `=J7P˚yO*gx%A aJ?J&0aϦ9@Ogȡ%(rbo>-DC/07fu:ޚ߈hԈٿckr x*c/'%; GvBIԹFvȈ9kIct?ʥkk;2@\xO.bBalt0["Y|5u-AL66JkI?2;<,XIF ݕ|U+J8O,N WFKk>F2^$Z J3Ns**f,T(&yrD|FjEUM ާl.UubfN7Y%{ ȟd ˎINEQ;M*Ϊ*Uξ $D&))$_X|-?L]+ zbR ;HC\:OQuD/-nk,k?FYve/wr/T$Bu"jKF8ujbUEvʪw8uzU3L&ij\SqvnAr#Hbڎ3Q.ƑfXgǜe>+ x ENfm%Ğ\yoۨbv~CQLIe-ɉͧ@<STAŌ@`vrP\ZuٜT@U/A,EVTb NT4'8}Zf79S06pxQ[79=r1bdj vSԴ/;R-nų/`8lԷe2o7vs| ?qAyi0[ej(y s8/ob}$Z'8od%(1^7F;1nnkUBL;Ͼe?Cg%Z]M"8@j&;%NNuOrۭ+6퍼y ]O,w3;wG"9?ʶgx1!GPb ?nE0>5{_3?3.U^%$Y{bj,_.(Ä/_R"zϯHiZ 5tvf`ѣL#8/x{aV߉Ŭ"n[wJ$Ŧ&(w=P8[dgW.3óuڄ$3(,ǔ+읫9y7 tMT"ICX1NP$cfZɘ7#I;W\yٯD*01̖^W^`΃\[xtq([x>>Dp*Ͽ%2?`oW7zx{3<,D qYa6ͣѾ\ )8!/p/Gf˯^PXpň1>>UvǾxkyJW~Sa olKHȒL lJ"q.+'7^?jNuvr~&m!]DF.@<EX4?[^Tûp0*+*#gϮĀ39~$aDÄ:9y?C'p?+DcC#t v{h'Uװ XPI4Wb1V'F`+DSee†*J| J(tP-c'G>uP;Il>Pm^}<Įfht fӄIP,A<6vf\+7Qʹ qmWIA9 \+h?Nvc/p숋&ԉ+ `j|~S#tBGr`2Y;`bjxQbqQY]atNދf+fE{#`c4XGKM! X̸|y(B<E6XwǃTsiϋ|/ʪ;6K'霘@ŭ-;zQ#hx*ʁCh98]E,?}MntT#N7cOc GxѧZM^9sŐ6.cy}{o>N g\Mpfkirl;~v?9_yg'HM0M&"8oM0[6E<~DcPXMx03sP_][';;]zk:?cAӞ>njNưX,8\LMEBRTC:MKR8P}+1,V+"QywY"dMeM>>D ^f44=A i2Q'^YQ1+ '(9ڞ"L204, 0 !2μB\00 0ٜVZhI0#gDS6-,'%'$D[6zWe#PlTڵs sME8 vx}k)4gR$TٌjT{<D\`^suXҎóϾ>iV6 ]N/?Gɐ+gQjrx\1bDj.XHf a`ͯ EOe&@ \)&5 ּJIF#=!yKC"WYG4Ug%Ѣ%!QMQ\"cajsmT2tH${P$x0Iˋ$IiY )a9{'NZg?(%~iZ*imɋ +ؘ5-uX +c>)lc#gsUA=6ߎ6xx0jaD'0fc3 65iCWO̍)eB?-'\ױn%>EH().F 3Q^Q  0rq[%$O \l+>;#'f - ZW\ҖMbh tWbGڐpahǣ>ġ_rʷxcyZW#wo`䵣#4-ZKd[*Û pwW_zrx6NXj)=G%:vkxiYTEϤS8\JbsX:ſk=zny㯿ѿY^ V:,__qK"K?Yb߭wX&|OzVG>{?IJ5lyu_dK{?x?xb5 LtW?f#sESGbEOH \WoԹ!yXegՍK ? kqdF#atEex0DKpZrO\R!w?Fx,!wWykVb򹘸K,Jc/a^=#2qa;o z[ U7Й탔9{eTFB| <ͺ?,=#SOh1nf88yշԦex5*S])k^zr>νEu-5YޭTs]'q #|X̂EXv>>*a01Dlf) HnKk8Ku?ߠvXTW›fNj0`;Ƴ?&pJ/]Ɏ'͍i:%Iq&,hqQ^XEEU 'x?em5؅[ U=|['wT$cBgx& /+Y\A|1$lTܫ_#5$maUI@ b"cFO:}<͕e%0Y"[7ĒD"p g&*1 _؅H˛QLf{A1 2rX\$./-6PQ-Ȋ9y:_9 r#NYb`DC!2yx|uDDf q/A2NN=)#:xC8 eb}ZoV;`-.'>ދd\XPb#2I"8:_LQe ϝ@uT.)f.N\E zkTZ,Bh G=\>XĎVC`w;.t*ECQ8"裌~ ?E=NCKG^&Cmerr[x`11 `Iny训h\oφ%OVr6;~?EMT8/294gm9vVSȬ͝OVVb3Ŕ.auڶ 9O^~!-KSm3_Ч>SE|m Du-G %e[K?lQf4 Fdjw`*n?É #XcJ!.pT^{Қ:{&Z+ְ=!ʽ NA),-booo3'-,-8zGwuu;˖/Fm$?"u 0~$ZslR $QܴO:ʭ;?5,ŵ,W蝈JtZ󱧽\k<6-7R]UUzE/n=ʦ JiU+K5pӦyT9!pe}t_UrT1ى'ӌ,^'Ѳn s~7~ESZ/dmG\+ 'IGHHIMC*FBElb0R HCX /c.;1htvt'R:tB#V2yi @19P-f|tq!%B#Li.E1ѴF,H&+1Tm:=-$ xۈ$avYI#~9&{)FN;.B 0~~ѩXPT@y&i\M4|Kč c'K~ @AF6@> ׭Sĵ&np맘 GFzЍiy7{Y1&zrٹ[QeV'M-VUX:^yz|sh-Fbt B4Ɣ9 QGai {lZ;]ՋT07tV{)lV3$zShN`xoB(V{?9,.v">r.]D]ՂDuwоBO6C 3nY1Xr&Wl@jy_fM3PT:rniߧ@jc+1%DbnU4/T45O}kguSBK0z?ƺdP{?9Sj,b/R0 ,.=ăچP$ )V$ dƛOQ+Ye7> BE3\(0w|Ԃ')YR] UP w< &zCbgDENIs swbXS-3yʪ%g,ӖȎnM\&BdF2[oZeC4Уă` % ߇hK^ ̺OGh,ߜ&8n">K,}tE<4ߝ|J࿺YJ|M$B'r+1۬䩠u%}ۿO,+ Kģ bc Zx -%FHW1u"c'v.*c{`F??]Z,WHuDF ;ѣa . G$˃TYs1m@dIΚ6*rF&\llՒ^dPUWOm 2 dd!L9ZjrjNL44 0IZ2bj޸sY)iU,˘e(c P3v(05pٖEbz,˯5n 4-2|̊l Yt?Ý?Mf/vkKddTF -FQ9 'tXO3A(XS=0\=Dwh$ `7,ᡋ"Gi-S>F8l= V?gQhlbha&."Yޜ峨'0Ů~ E_t^%gD& ƹҵyn4lzᖥL-W1[L7%=53^2SK.ȾT@u}֕=aU,IRlU{rΗmKΤLKx,I } @ΰ5KRR,[8I9{LM8 ^Ḧ~)I;iHi1)iך09VVaYɲk; C̙-!]5yɋ ?՘bě80phVK!i L',EM[vw(sZ+'~F,YNf02eMI~Nwߵ|@q J 5M'ۍ8f/zg dpjk>qMp5l\]dϽR֟E\(di_^S>( J3ȦQ*(jLLBJB շHfp*fh8cE-op?ɘ-T~}K2* &"皧5CJc@5{qL/)2HR YARμ$R,4Kbn TMYP{e۞#M]fTfod)RIRV[; 6"ȸ e)$fL*\2Rrܨ_̖Yda3 b6{ w~w#yȢǘX?ŴXmD`J $iZDr`fiJT^/QP&.PH%5 e ܕ`hژGD|ET+8 }=VLH}LHrR!QEu"_GәU3+JNKuUoR`6mbݪ%kiZfq&[!o<kVQ\9OEIlQ]U27ރ< $ xc3k-}~z7yu$;&eJwzRKү<7IY?Q3^6ѥe˝PS,ц,er%myͶȧ_gK¬$8$rɌzg'./8=ܥsS(nNkCNSZmʲNkCXs%gR{Zkis!e)`)ă0 k1`<(#|e 4r u'L\g. SW}6T:}(Ը1) LPPC' έw?xsij;ʳpijQfIg79vqx;ؗpÏR:>Ko?k֬{d: w~lX;gBeU5NJ?}hN'm\ULs0̈椡Whr{je+j 8X4'@[]<8ydΜHQY#>(?`pbi;3ja5$W׀":vc=F{ ]tGSis5]L,`HыvNbzfsŢCКi\\JRDlGRUlҬ֜ԟeʛ~zprrw#{A!3gwsL3@Z5Y`{%Ҁ<-XA3YK禞Ŧkx H ē^9Z&A\w X ALIث%"~J!3oDJ"Dz")iIɛk+$:膁ȒJqi xJ*hhOCxT=Eǁ`ݍ/GOlr_d$5uG֛h#[︃ЕN:Gœƹ#RQUKJ)pM,IA}GOr߇amebDGXOu^MMq0O|q~7(YW0it#̥*~SXx}:5ȱ vyY'wvҹDV^*ƻύ"3Lw!aXTZA̰'2&;.qH,"@)77)'g[r~Wdd|E>@X2 OIL)F~t43)(M%3"߯qNe fL_adQ$irĝ WHqi@/}k_MK)йL֝}lue,I͌F:QqgW@!əXF95Wfjse)ˎ5 NII 6m`** CQ.ca谠@KL( }L0p\5oG>s![^{wqjY6Tpk??|E]3oA5G+v?9B8ql*#Iwu:FK=y(Z#_Bb[ygx$J4%Ų:_BQTՄ[WDkKK|'m{c@AY d7m- pxyJL?η2:w},XWnmӇױ ܺ}G{{Y< iD"p`0T T0؍rz%$@ΊmL3rI q8!C:Cw\gy}j{j{d5۲-cl:@|j  18 e[Vm%V+i{}gvv9cڙYل+!1zK׮vgws<=L1UU! M"DcYs>45EWB1$X<ІCABh8HXk  5NhRѹEps9q p3A5G&KG@6NU$i3\<"""u[2L}N 38oܥvdbLjw&"3vJɪD)Q,qYp4r *qHA G@Q2҄Ԋ<ȁP Y epTH@w'!UEUDTޞT5:g/FD%L_s;#(FrKPE)Y2n[C)&QS+z~N RM1ukx딭*a`rAh HJӃϩpLPT T`v"#xY*ĪwdQLg2 I0@Z YrVnZt?cCx}>;+iU9y䡻l\cGZ C0=AQb&{sr+R^KsC=]#suXTlFgD(sYgц^ aj V+BG_G+-=E#YN+#7hgd+}hg=2M^aW/p NÑ_58̉Dry8w Srt8sBz gaMM>b$i9dv0q4YC{3]eUY^g aLERLakLpT0g}E'p`G+8 1:i,kdbџ=I/GY%ǂC/7\Rdls,̹"e[IN 2cDW(ftZ]ҿ3z9o[] zd~2%KVkե,[֭CJWv1f&'@1L͜,GQYQ8\Y(,V<;. MAAl̴C4Oe@U1cGAd.B(!g.t?u% "s2G8 3Q$ ށ p"7QqyFy_Pbz'#3(Ox$_TӄQQD..v6!LV| 7m%aFQƆij:FiC.Id3\tS\A8{_{B[:!ۚSbrPs__XO+= MWC{$G;T@__Q}#C(zHB:P2BdO pA28w gOd઎ƦvEЭګ:MH:BFb=:12'3?KUr6i6m.*h;xpY LaɎin (2A_N(90TN7Nc,Ifl:L]=ݎx!mTČ:[oe,pZKMd׬feç\@ωxx7N]#?ūݼwKP *r4DW{3nVj:/~u:YeM}i® vQobt@ B</VDfGz:UYf-e91dEA4XЛcgVB]~<(Zv89đ.vl\C'|6^Ʒy?} xZPO3>>~OW,z*DIXk+^Ka3bR@ž6|%' Ƙ9.1+ IKT/ 4]n{YUUukV yT{l( D*bA1-$[7>/F{a`'#0S"Nk *r\sTF}^>sbKfg@00:>ICSc#-[Bˁ(It^ stbъ\禧ܴllf#Iri<-p0hw| v#%;u1[B>FoT@Rəe&i4bq(((7sAeê\yo1U%Qf:JrCw~[ qhWٺ&[Ӣ/hd Tqkcd"@݂(H)M \V M6**ɛK؂^xܥE3եE>IPgDZĢbVmU{*wߍ.Q+R 8y*-=ڹ-Juu>w2Ƨ(/A/$RrJ@Mi5ZH$ c ?rӭ8g9q:2tUyU.FfyEHa:ǑM?Op2/]Fz¡ >^"41AGkoEGy^5lY=0: ;RߖGyY)& #"&#ǿqĮBQ(֥/2u( rlasj=\-0t535V0EZu7\zX,2}Ĺd!## D*H,\700uRyn7ͅfGrs[,xσxc8>mlĢ8JEu^c_=uT?OHXWYLl”ޠO7vB֭1h\[!Љ4Ȅ}(^;/@wOV_%  Dh((5,}a4[wZ@ /ڱ B+J]CtH^/NhYm=,++ĕe#?jUd3RTˎ-rsDqri65 ptq.GΎٲ Ŕ ZYa/LwO04&G(u"AKk b-ǹv\#؝vU'yENs/,%(؎j]NϝF0wmIMѲgeih|knTU8rr)ZŖB$VmdΎNj1BgFwuzCʟUH& *7)wUl)0ɔU0IܪvB&f:̆>X |DWn1֬"9w>GV.yn!ū9dg[C}=\TŴ幃71IfhubY]eBrear'kbDDsE9;O>Ԙd#g"P^m&rMxۻC!SJ,IRB\V+wQn"2:hcZ@ !m#tBΞ=(_Ĺ=ز1;'U&8FY)Nn2Y꽢|>z3!X&A@Dt,1f?tS=lߵ c\X5q *j("MH=F_?~vՀ"QhB2>%+:Hh0h2"D4\!#źxhs!BEfJ0*f3EALXJЁ}tF xd(UD$Q?BiSdvcpk+E<Ҫ::n*Gqܱ,E% ƈhI jqsҗvso֯[\^}D0 g^lXp(td;͝N Lʋ{σ:@:,DTKǵ~tvpwKw /} kf`7XXt:IB g ; `u)= !B ,+U5Ylzt ۄլCEJQ[j#+`05 ZavQdbZD قjl2  DI(!: Yl%uUEɂnEbQ9^ӣ & F#6ɀ  fv;fA1[mm6RJ1̘4t@2IAbqhh4`K%t56b6o|UM ^r$a21 H$0L B&>Ps)/GDNDhDbL\[EALYz$AQQ0ct[jSFcQ@s^̀ '8TAW/!bd$рd0Ӣh0 5 E@o\\33zDr K)qc0}EX*#tzYN1=9,I19+rW ]<73@4f;xp"|(Nf^x5@Eb :\豓Lkk{X[ E< >NQi5~P^ۏXwZI<4\z _o$l܋w=`zbiBiI~#w/y@AVuX6h?{i|ܒJJCng GdKhqw2ʷn4(|8Y٥TV`+fc]љ~:0 oq Y/ʵXXpĉNPW }|7O}wS>]geƍ`o7e58+ "vQXp׮;.T'EM*YouLNyOSRnAZڸg-X(P(C,A/p8E9՝ES\?SgYocS0Eϴq2n lgqAdUǜowc^@B3Jh?2Ik5EA$\n|3 3cQ'bҋJW0bIxƣ0S3 %\ǕNE+)J88f^]KY5f֙&ədyi" C͑)$wƨ*c ĥ.#ͩѤISRF tzZu&eDgOj #M:#9q/G2d֭ ;WMo'Hg ͗JLcTA@T%xSq}Im/Z҉jR'L-pukDq@L&+J +J`?BG5zzK.n6ʋO߇? e*sD" 8ݥ|'N~?й,޸O?`H6Pv 6zYOxnpTŕ_M  Ӯwx%{'y̬Yկ\dqu&Z7ryx=UG <34 2_P@"ʬ7Ɩ-Xq.v27;az)y\ciك:}3/,^ʝcZ)0蠨"5s?{!vwt24=cǡvlf.)ocݚ[iad2ºԕSV^H4azzi| >AN<[p >JMu }~K| /Q;_ha 1O$$8):߇MLD` ӭ%jiһ )R}c[J{.JR/M; 9($R1'(Lu43ԭʎ۫X߀vJ vpAzCc'ŕ|._S;hfը Kkɲ)+)!8gն5F/p1:4E7Mw0ΒZm\G(RV?,\ G@ 8mVg/a\ Sj~Q` >T}qVWQ["Md.^O.T8I8V^w5Yv!Y9dW74E&T2`2qڬLwp{p|G(ɳ㟙)k7{z:: tvET9)+rcZ&I 4,Sr{$0ߺ' ::% ̫p>|I)V8 5fܔIQw.BxNgspKn&Ss+;&Rh@] iLayn1XƼ :S4,F6 x;Ӳo<<]\:{qYGس׾7s 1XtQ4us_1p8[7ŃO,_?<=0YOSG%k33ޗ1md0{)MӋ}mp|VqY&B wE&s@ 颇7)I*)d:EgD< 2)#C5Q\OHpв*ٕkRKSS;!$M{TK IDAT'&F)gd<3 0DjZorlAwӅTw"}2@IZRsm7IdQ<N7UaRN?7BK!}}ZOƆ7eZ8mg?5䑪(47dR\Q` Z#2;G7dZu~'BLKcܴ/0_USo~_ov,-(Oc sYB0| 4c!ߌӮ/UˠeB Ig/UH7ּZyܜudS?,OgB3p0YqRں=; qd/^pafb[}B)**mMC] ,266;FyN@S#c+ KIUDF[G8xae[XB4ssۂֺ,)*%~a^PM^qbYAymP#!(V5Vg)dI4#=bhDΚ"s!4KL2*7 J B0IEm33V{(* tHFpMpIo pdn~_𸂚TMTЀ.USFFMMO כT휘I[NV HZy#vWL7idmhjJIKW!:ce3 MSoA3•ù{yyND1yٟsa' t)X@}v<}JJKi=׿-?餬Cgb1e0ˍ Mmg鞒*sO2iY@K e^~KX vS`q%X!ξ,?EsqUo4ك7,tX 70i#䝠1&B:\63q6L:1XϊSFغa zbPYQF JH(CA02zLu1X:XDh?"Kblc!gEQt" NJ~{1XBL~#vW6LODDM 2ƄFH`艳m=Cbn[-JY'RC2NÕqLS,/+"U eB0׮OX8Mɪb1D%Y'KLJ,$z/9, X<?g PR[@4[:oAE Uo|v [{"ʢEUo⮷xyI'a%߱H4LBd5њ͊K6M$FVD*jJNhm&bggcQǻ/O@kN5z|‘TZHPQP2UV%w$|<8# ղ}n35SOJC{38ʓd0KzGnOAH$#9RVRAp|')tI O[Y{a+2-Ns,7R.$R=nsm Sl=N:iًUF{F {O>{/|/?ǀcLsZ;ǬM*Z_$,Yn;.AB`6'c} Ӆ'H)ޓtb=<ӟƋ_GlGm+Z_|~Z^L'8p?D%+kfCM6M^HzA&:Baeh==@ui~-(q &{{Oc&t5<85oMC4ЧjEH!5ux+[5A3e;:Js "*s]FIϮUrٺ {><ºl6l*#c[8Q$ eʫs0hnEeAOZHY.%/l}o۾kVh"3sQ*+J1疳kuyN ټ!v-rhq|;A?5JgIR]]Eme)yyX :$Miq!˗-#_䡣 {”֮djs q[YWTS>qGԧ?ŞձZ&!*&E`@IWP^g= 9XWٱ{*b̫eHPU@aQ)N+pьёϮ>7D΃>;8 vRP3:p:7.(*Gyg_:w~U6w:> c~<;1jKo_'Spʐ;UduP[=S\O_ȽL }wb]dR,Ml y|O>\A+`Gb ښV[&ѤO?k.E@T*ު5n,O @jh`g'4//[H(uWh4V&ɺ UJ<^uAywQ.1+?+D%Ud("Ꮵ]RUUmMͲu,Y4\mAAbf~xND *mo':]cmLX4%U%c/ssP]kt;4p_|\U!*+I2U۳wjzeNr9t2J t^?,+EQjvP2-ݔWWL RŚM[x#&=sfjV,Żn7 V #SX>S˄lEƘG~?ol6-Q}%KyRL9nܒmX ̷Za95-Tm QRpDtAPD^3U %3%YE2XqXmv,/o=;hc".ёO͆N3:Œ|w+t"G)]{r90#.|U[нv_>-զusIb M!Q-SQAVgo[vH6|k:cM{jsPRTVh.,M'GQ2lnxw$EPu#<mkҋPLUhMV*F c'5I8 oF9zWT綦?_Gx (U>XU#oلә~>DUFNKS7ױ(I[Eǿn((JTs&$vu.ܘ>KRӺbtgtv ׺(\DEZ&j3-27m>{ & {!){bzlPElbkN<`;)2p~uV~ VTQS(Dc_DԞ5kLu>j2M*WKX0RTYʥ5LGy (ٲU9SX y%۠zC-!*"h se5_>ƫM}LFTTyF.>†vz Y 9ɮ;r굓J+q>*>H!^'#{/m!4KһX}5y(kPv{?~%4܂M'"+ܸt EaI%C]t^G'{ʄ~d9(LOORTZ^CoN-6 k_gT9-}=d2Q0:%E{ou5͛fOdGQtӜlVՔf=2{8v`{/A(Qխ"؎uIu')$΍؉E&K(Q")@lߙ1 Po|#!ݝ|)׆ J 93~l0dɶx1˼Z"mv̈%Jo?i)V#$.ۑEL PT e+O4caD̕sc3NE" Y2ÁfkKH2ڎYs-=ҭ0P``BVn6͑E#=~K#hŋR~k9+"~mԴHeQ"6x4LLx\ g EBu#H\i! Ci( zd 4P*PDbRWq*pLJ$q L0p8n<.ɩ հw+y>LGtJh\Ϙ: p9T'Haa L)ZCzjؕ>`i #G\ŖqgJۢS5 SIkK* h[`Osd$æMk&PKs'Bn23Z.1m&0RI^I]'L>E!%;Ϟkg疪<FRs8i l&α)UҜEFt %mf"J'!p:}FT9fUZ ٸiv+khY:g$jϷl8aן@Z$\)%Å,x*W\PT4YTq(-aQB#* ,0,10W$.LOkBzJ:ǩYnh-$4Q{XfCNQ%ydcR-ėˬ'p?DfͳO"gRoceʷ|ゆIAvFt2NpR,y+08Kdr2xEXz]+R͋[QPkgTaybj`]7p8H=F\h.B0ƒΟ?Tq$ ߡ=GľO0sP? !l AFlTCQFOw[@ynԊTѶ,EO}9sJ/(t1{@B $ NWPμZ2>?>F:9YCwP+ ?~Mwޏ?;/}9v#ʡ [CgL"-gသA <`2v{ IDATa%63;df`ipNx%z2r`όI Io!iӉGY,9#: vfid ()m2r( `*GYK@RQf>AWgc=蘔Ymt(gݗ9(IX۟b97/Lt` e>X++i#H?1Ky11K<@T>RΈIv2PkQI&ՍnM0b@n +J@"L.R1Y*m pӮ=fieb)&=Oߔ*M|iز%i22IR%M'HF:|idh]I7W"GqI0ުd2a%nM"C{R8d&ts=ey#?"es 9IErV.jlO6K;w5{Wc?䕑}y|̜?~ wDQ^;:OeO[sa/<Ͽv U9~}48sϿD \^Y/`麛|Yrin 5g3{Tt0hf3ٳg3 >fģztV2X#Wb3ƯMe$^h&}1̹̜L~%9,f50Ӯڷ=^ܳ0<瀰i=|tGD*vdm~2Zɚ":$- #!DQ*A,S!:(xʡt1d *WA:py[ 7@z+yE<zquXya,ZPK(,Gqx<466hlvu&£.b%B,g6 xf k,Bwwo<¥A6uUu4_ܦFjuC҉#y0䣉ŢCABѨ:baz9q M+ְyBEp߻dez:OF KZ ?̶3p㚥dWP^\A8#J>01|}#S;K6n%vNe,Wx`;E ,ZX2cΎ02PPD4!0b6"sGi;zvqYL1v}i f,mV+yS1L32%},M[݋18펰fDi+' jw"uہY;l H0fk83ΕHG&2Mb&h0,cX!LsРd: ܂9V09Y PX/>1|S$m_ (nv#u?|.h&&|%y=OͯiN-%Tq){)i]Rc{y\y' 5rs}UFBStwuw {)(+B#@/Ã:ފmald 5!DW=[Xp!uU$삤4p84m憛WwHߥ|{=w #:ϯnb'w2:4Ý#s2:шܒݻu1|~JWX_u ~eu,%Mmwɩg~ľD!G<:N{?991G(p{v&bnVW2JӁzF RtC"fy]&kp/LG҅}k2ӛU0uЭfS,=ZS7pdoBLej~\9:& pf#3s[wgjgRYX>'>oF>#}c.Lt-ey3Bx _f4_kbzl1ﳃ?; 6j";[y%Qm )#̐|xZ)ZlZ TпYjK#l,[uf4Kd0"3@[kО .mjeҙj E6֑(2'Ōv{,㦗S96vy֒;sR256 .˪0&J" s4 (عTվ5KY %+UHn"O@!LpĈ ʖ+G0. jCA> zğ롾I<()mt16'mHј}v15?|,z D`X}W>/ǡ ,a4-HP]h ? ^?͹0Ň*Gp,%; ߽o%Z]0]d'9v}ArKXdE$<|=2M0!)*-L L[s~듟`^#(^VqFG D+*%ۣ212JVA!DÄб8s\Aۡ,oKv%4)s28=Bb]/04/l?dd?[ KaҰFr;YDa{L˄ ۘ'V,O,:Ҭ&̕jh*i :(Kd$mo6ac'@mE+4۶t40PC&;Jm+0-ҵrcriPZT#H;h(g i$PoI]!If=o 4t%Bc8 IV@TɐY%hWǫ D0zֺc:/D|='z8vTGq8],\LE]\{SLÃiMMUK $.H+khg_JNk좧h8oCqc]>=B^Cq:C7(:O׆Zt]a n^!fBm#\PZM;^UhB8]ww+ M1vB$2BQLKK?kW.&磴Lǡ}7顥*^ExQϚ#G rF86n=f>ZvA;6|b|) `2epw{ed/ kRpRXX)[Ѣ!^}cuuy郄Ƈ9~rWBMIL+}xKib ^>ЬMS?WU\WGMkaZ.g!S5KbӌF4JDQ8~8ko sKNrMgy]O}9% %M3ap͵hv# ` xwT1w7,4{VnDڒbLC<>pfz+mE(W/dӴyչFQ\RWSZ=Y@IJ"QU8HbM$#XdvM 6[)116KeIap`F7|ҴhIpe+{G%|'[HZי['6\&SCP(AۓҀx6 B6Kxcg dF,vퟺ%2tsˈc39gq} +@HRVFF `Z) qӭ TȎt8XRΟWpe`a OŠCa4CuCre^`P>mfس%5(z=x7O4%8=(@QdeP$Hxu:,¹\d1V/b>?1 sb|^4J <ǂ P`"0EnV> Avn)˗ut]mbR}hMEYVrѯs55LPjrYw=)ܴy U"$ I,73NtF\}?´;x9,YOďJ,Ǟ(|>/|s ?Go?yO~ekV> SU]H7oA4fNlW^؉)rkHjU:HO1En0)`325ªĂc&ܴEp^|c~ZV!zr'%?S|& oaʛ3}o}[ f7Ѳf@gAL88MSֻ=L&uKhWCxt@@UE7}Ux+m8pMla.rbmL @yXXc@5mDloΩluFr}*xS< 胧g%'z' %;@PZX2b͒Þ/ח[3 Y iӔ4_B|2)+/%hij]_D+qor]j(1xT]"u.ͼDEáB}N`0u4ڂ!5†]I$(aN|>>/#Sa/YMEE>J+cQ#+6mw2:>Mmh*TB!$Gi'9*Y4 T4{ z!VZ5h{ ;48i; ̩*!<sPtJka4(Pֵ,y;ċ h$Dp"•K(8žCGx,zZDEV\Wߥ[jn'NE}' Fon17|=|+? _6gDakF 3+ٕJl r3/?ɮ_yM8!,O']0;/%hb. as8R-[ʁ|GOzXW"#+4/[3ÂE}&b㦠a {?3åd_q[N^x2gxOh4̩#IGM Tg sAq|9E^qr/p(va0ʞ3u"Ց^9xc,xf%4=ӏ>ɢ?4e>N\:̫gt=wD#ũ&aIzŐ(76r}Zj6YfhDR.M= QRf!VEZ\.ղ)=5x\I͊h5 K\ ͊ e ,Yeڀ %E%-@R)QjCgřkU-MCpO:$Vp|%$RT5Qe7RPA}q=•М^TT& R*oߋCǕSxl;I>z k+qi ़-  _V0m]Iq s_dpt-dn]!/[uTWTRUiW'cY1=0\P '?ȅ1Ʀi.!eZ(J Aq9\@uƩGpEϽ6>_e}f>ku5*A^|8ԫxq ]pn IDAT,mee*:A$ (躎rןHA ,։R[s# rf?/ߤ莵lV5DN'zmwMm{ϗx>}zc̐HxQ eb(h"P(ʉ]/+e p'O^PI^W&!" s8ɸ4770MYZƻ @7)&e ɎN{ 7ex`$  <ƚCq<PRbT,3V.R(iJS"}[VeHCGsg!q|>XvXp wQLlE޹ulN oNRh5mW#A'qSHvBN:cg7g5#b72ߞgL3ʆg^£0ib5(z|_Xחo.ܝһ2fɌg1~=x' (.20|Wx'Y g.{$%U-lټ7oOq-nR{m8u~֬XDmw ,duhݼv^n>4rdNGC̙T&xӯOc]S[ NXsCtuhBQy Z^4oh2 (srgyGCL"dsp;T. qr}ӡTpzuL2KKhh˗i߄!,p晝2#H(NZq dȇLypT93H(txpkut{pLc' VYe&573WSN639:?&%Eiiy|ˡX.:Jم,-*: ʚ[yE&e8.Ưj3=:ă ]xՅs_3ǍbZyv`Am߷HKȮ s  :qhnrI^>rUOv5!b)L ѵET[Cי,&x5(Rs0S^6߿B={N`Ǩ/u%]o&{Tc%pKi=mb|U"\_f9X0 7Tu0XW?g kLfR?;Fas#NN;xU8,ťkE (,ү`%vr.TM+ķ^z}ay~/'v+!n5] 4MCsxcN t{10^ u\<hͭT/,$]_Fs(w'=\rsLPݴz_[  v2TwߢfQB8(_nk\YumáEUﺇ9Z9QҴKQApD[]k\M =ǿ÷vPpyG*P /{?AU&RgrlM^=ab|ejdW_}ZϟOkvW %1)'U{'.P1Qɖ۸. !8W,Ybw=†5晕&8y.9}|x =ME(*¡OON/*.5q'"nT@(`L3MJBNn(h<4o5 g=ryq #bM\X68Yti ,}FQi%EUueha2,_Tƪ20*mCX@Mmul!sqr̟^չNR} $~~m4LPYV(#6֔PH/{MrpL1b~y 8{+ٸz)o.K;2l]kd~s9sp8Jg4e,} 9ETg3}oa E.Nf?}.>O#P @̀D?m=`rlx8(<*, /ǃ)~~IQY uu$7[#qC8.y8Ee8L;vSScH\d&Yfg`qW݆0/_wC 2$pӗ{A(H'6basb Oc#93u<'O1qeC &89;5U!dJl!tT`(#kq95q=˄aÇ@*`N]NMu*pby+ovah8NTuzC3oEZY5lz+M mV\ r ςuMi\>qZP^V@:az:@Vq5tFhnc׮}fM RH-n kANn1gvnЍi(B5W_[_`|sjkV6b6DyQ r9zwvPmg_Gp/U IiߴxuMz+q{hzˍ4<|3*N-) ݈T>UBOga+b7DjV!14xN~XbV˅b볆C.?7RɠxfUac-Mi1VVLLMA41{ W!8eoDՓ.֚0/&ӊay;<щY_s9D1|]YHXs*X2TO>Ks)pf!!TGna)skB\j;L{E!ʢ[Mko KȜl:O5&\j?ƑSXn=LvA=ZWm&_s>@G;O>s.?S}3GN|-0Č^$O<{.N\fzN^?sZ֗$KVM{J?F{DU/EU|e99>>V4:]tT^?>-K9y~y[ƚ*zzPdpUX =h@;ԛS 7[n9x _B_eF̝[Ioeei}|aiyb0v< m;sRP_͂-8Qwsh*:L_L>JKu8*F'o?@9̫k`ѽMMb"pWj7zP+ sQ 0H$yTiEՔS"܍\h%ӈ7: /Q{_&8Od̋{Ϋ`rK5ՋȯAPÌ8]`>F$ ϿZB{$ss%Gfd.^[*1[,,4FH&bJvp{4aXM+i^2 IpL5%8aDLͽDZv; 6B 7HE%NU" F(W0=fAÝ*u>&ZQi):hFV^1K9) }8TAY:Gc18Bb:˨k=qpeŦk֔3g)ivI!U8؝%i LsЄ0=rG; 8ڙɑ~$v!-%)}䱍Hf,$0=z4 Aqe}$_x4񐭪K\\3֨sjத2q4,sT#2·UsjK@J4W6koՆ=,oK`eWavCHEAgQ#`ynT !xo@T6! H́in]o<K1<6_lgi7/~t7~v&Ғۈh]^ke5ABAq 1ؼ_Dpi$za/co"PMT͟EZhp )Uq&"VYXiocI,3 .-V&Q~͘2"2BQuI\~ܥG@T$[MSX$$eEi+&3 {)U3}~GjFg.K{[qb:‘'84hͲ_˗T^D\UX~m,͝Xe+^浓_E͋Ӧymϲc>d//u|4-XLiq.V gdS%(n),)31{ENO:':˗CaQ1|o;Wxur۽J1vh~\ZKQ Hi08$)gVs2 J%_otJ*SJ?486DXښj@⟷ۮF͝*>tcH:8p L?YMɆ+it!={FJ.CKU`* Q:'t,BBAQ>eu̐yu_0X3u;7ٍbZwN33йnOQ{UaJAֶ}. ( C ifJKa5 { O@&OB^PS.(*ww *w6xp^ HAOOѾo WgB|zARh}ZUVv?t7[vކAy6?0)Ce(`Ny9.`W{[G9meUx=41Ns}!M+OF5_[j>7XW(s'9=* T.B^¾k ?z uCdž9izGFi9px[>i\rNV>qKI۰?2OoG^g+9FYW'(kbj[NM9}?rkؼ='z&μ o*JS:юxf;y#=)mW `Qz\" 2ŋONyf.jfIЍޖAqK  7; :ykYP Q߰Z'@(K+p8)4EUl'tH[* Qp5sr x"S$`9svl: C7k [m5ڭڦ@-Eճrzr[9AnALFa' WJxۍLե]p IfŻY P rH@, * [LVWjp腸4F r`CR>W.njGp!u ODSrRyXBs0}ԗ|h?1dI%":BHEx~B?K|MW8J R^ABIUn:ep;hW138ioO*7c#wY|s]ΪOz9H/tz'z.tEXx>G[|9{8sӍTFwO24`C{ o BDj=HAJ:'yaS̪-'q &薔6-;_<̱tFFHRxox$D$gK# ڂJbHɨ }߿M\^p6I\-+iD8u9Ɍ h9X x+k.YErV/[$GK)u;Qxn-?yY*ݨt+%}r>cMdac-%~7-n~= I$cteVUsYO0C2bΜ94lټrU' 3Lƈ%SN7tc''HǠ{>z 9GpbF"j9I2Fq(?80R- ]b0ObnwC{xv*n*USfI<2ƯuOvYsV/Q*AOKkԻb͜_kDY,{O: ױe[߲-OQ$N>KǮQ+l1댣6^I 衣P>`c eieqOf(jJ*A*α\I3`` ,;2m@4G6l02Ş%t}%(h#i\U!1(q5B"(_A&AC$56\I@u Trq:EQu}'uuri r̯IH{Wk) _G_|o|gq?L49|U+6'a#Vw2#{1@H_bsMX m`2 3)`(%xJ|x+L RDUģΙkWcɪuc:+XNV6~vn#ѐAE $Q }rǻ(,Cki[sC =]E8Uh,A%'Y T/Z$ȧxaAB)'U0geХ'jH#VR5o]W%_R/ZJAu2զPSKgH3q 4OѰi; xD$X8[#;:@8ģQb J%æz̓+9|9IdQ9Ƕ9U44\uy1@sztV.iWS^YpY^8@{oJ=oVm`QC%n]w 0y'hk[~6a~aQ!OEOJVwmw}z3/1o vxϴ*QS}ե6R 5Xq6aw gޭQ4T_3{0-Pd4+P@Îl}Q)3J%J m=dgaPH . 5a V_m NMlȠ#|'QT%IxwEfzb'|3g ΰ7hRv[i76X1 ~Hno~Ϣ'$qb_-ګy# H%LC&KOڲ313$Y7~;43ryTy׆9K@jl]JUe5VocSKV8ش V5q:8]Nd믦~v`*Z[H5fS46w$ש!px|6TsW?մe3?қ?/xɣF:xFeQC(纇XKXzJI/) e>NMJ2_VA*Mz xT!b 2[Cc(e8E[r tP5#'6:Fx+Fa}R_!39~Y{Λl%~GP!|zWW-`;qIRH4Tb3ƏLUNY2EIT[[ɩMj2]>ƔHQ@¹lX–)r\R@l/$b c?8ܠMr\}k M;]oS N؞EEh^AXK(ɈLlZ8 =FJd?4JkdUf(2?3+Ҳ "nU=ݝLD(*8 Y4{. @sqXANMXi(/-,ͳ (EYuz0ٓS+2O,X~ #~S:a^Zq[ iw~3i:@zoi5+Uh(o`>Y5- _i%.Eǹr>՜. (U'?Q(ce<.û>Ao>'{?? \\O3|mo!lv3Yuw7v35.P0!f Cް =4&R*Z|8T=% ^P 0w+h.7"aߓl./n$g0)Qpfjs3nECZ:X,}%z59؂Ew]nSVW)\MI)u)v,ke$DP4ĩ qCAU$zRG**b~?2fؑݫJ-ƪs]f)\ bILBx*1#dHKI fiVtU@cV2](Q"GdA`l?SYbE(dճc2>y]ƩȌf[:EeMBü4̃P>#QˉSS]x)V3orJ@;ǍRMo%kBuquj|>K$$e?D"pt?s,i鮙n"7璫XeA"gbTlP]*\^*n '?UKhl\b/cބJ_G7Ebܵ] k HʪaS UC)*+ dIDk/ëHG`j*)v~L@.\K3bP- Hds/qrxgnl/ctUsݚ @q8:rU_"vГǰNP`V. >H*n{f RȬjZ Gz۫X0v'mdY,_gֵYׇns;8A澽^V%U@ᡲ“\.O=Ct!UHj ((ք'@iɂ|C:#ifT0^ʼf_(Ye1ZH%ţ~#ViV^fK?8w\{) KnrΜޥ}MfM,;O?qӖK[Xfe[[W`[+CeqbzYp.>Ɩ1Ι7M7nt Zb(\Xs**K)nN,*åjz YͤW4TP[d^AKlEm'!䌜7\V"tk!d-˂4#L9eM;ɞ:Ev-[N B-f~P*\!1v+mCLd igdV$={I+8(Ȝx@w{(PdOPU`A ƚrh9E\]̖mʢ2SRH ŕEEZX;>x);C`+#rr~u}PA`93>5s)Yzުr$#'U. 4,_DfKY`6MK2g'}} 1 T;MۑCzZOe&bz/v1XΒY)FEjaJVzqXir'WAEc+ݙ ;v2z?~os73 +rc3v5nλ6aSBL\' ` {y`G6l|Xӥ(c@s@sEIkRhKBA(jg236ku]Jȳ P( E]Qk-Xrl_XumgpLhVsH^ g+oB"='ӐXS8GFܧv?_oR27hNq==7ݿŗ=1/hZV4բkb)zFWpUV⩙QoBs OYG#bKh%)X OI'5e^x)+k _ .ԓvV0(rXX؃_@N=U@iidӮYLK_ E%[1AU$[wž-c"LoyG,Ob,$Hџҙp r|֞>]l쾧OaM\z6>žGjB-yT^f?сs< $^+/[`_k>p׵]|kyb_Gk?XBNdo2GlMvXD+[{Z)XNJbRSL$bsǰ#Ro5}RJ.,uPZ֭( S02/O՛pLJ`W %rQ]wQfl2t=ſ|p=8C9/VR_N y4%O> gz@$3Gw oRadxD4B8+]|uD&2$"+?h M H` た({A6{6[no ztѡ)Z.^ E+ZъY&YUdt ɬiLrOZEd4k\KhE++|e1ahE+Z elV `d kDVъ]F_e m{{a5)Zъv̞]jSPd`1QoDuR"hE{I#@D>hVHLb]I!VaE+ZѲclBX"#6Mme݅V VъV7jg2ei&{+fNuӁB VUA*躎aRS{=01XX2n"*Zެ+(CJZ0s> ,NlBUS-q3= RA2 ۍfkThJaigdd@Es`:U|m^EUIWQ9ŸLT&JKK'0Ae1 N~M.7Mĉ֒H$!H$(++pFQUәΊz^E+Zl#LzP3jv457)@*06O*DJÂYEUY39ѥe#M8vng̞֭=02BULޜ½h Xmτ! +];iJ?N" Ѐ磫 ʕ+R=TUedd8X~̙9}4|>G\\.TÙ]r,vit:q8vpTHt:4kS80X,Ƒ#Gga/_NKK ݴHMM uE+Z.^. f kBXA 7`,'0xƪtnPz`_QMYS^χB~ mvwLZ6Mplڴ Ác…Xv-x<&e],1eݢh؛f*Q8'e!3+`)>(_& 4nP9ؠAKGB3t8s`/Tr/︕-~y`;Vx}-ĝWOO/aæ̝U7d~+XbK'xn}~38@`;vA,ĪpgSc8=,.Uض%Z/ '@bFtF)QblS#B}Eðp\g_{$½;y.I#vlcR Pg E IDAT^]ǒ%^JKa)J,^b޼B#eڜJFqhFYYYxj% fܤ%eN.˅lWUU3߷:KtRc;18,NȽմF{mk؊j: ,6KH2Dٽ j-̽R0vP2  A JY zYbQqCVs͝u։ OEN%8)Z ~Cx@qzIZ9҃qe+6qfxx.`8;_aY55tKSTVWS_]Ftԁ7O?Ν!v4 ,lڲcwbBr |] OvX2+~A,i/r-p%|YdB$Iw7=+Jɍ7".'|ttt0{lahqK&B!xbt]'H( . әqR s:(B8&JeT*E"@UU^/^Ӊre\۩ئ})M^YEyM2xE~bd y:@](jA40]0E HCI"; zH Jѐ clT̆V'6Q| Ƹ̔IC$5#q#CItIEyk.]/ͽ:f--ý|?GCQ͡N_l|w=Hǩ$gg.$;?&52 R'hyG}hlOLX nM@GŠUux\s K H3cG E`2ciBc#̚0_:uُgfUWWS^^ns3lܛX,rvwwr̸FU<Ľ%I( 3@.1::J4tRRRɀ1GEEUUUvM}~+'[@\zÚD^~(qN'/& NgvVkY__%sߊ Fޏnf3QPFB! /4oTt\)I2 :+CI:jD[ 6B!I*Bg9wDW;/1nc|灧KS.m/G JmpǓdb쓞3v1<32/ɢ][NtOX ]x<+kIu2 5РO2,ȲU_<(TUIS3KOH܏߀b " )a:XHBH#ΜJݾ~vd.{^.ܷAB"Gxuww B躞qeN<$0B!:;;QGYY`*6n܈oIP)B}9s}?f[?y!cٻw/=7o.Xd mmmQWWG8M]]D1t]rHR|p(@Iil+! QDR#'%ZpAo5(,h Jr{eY"H4m/c,``~A)->Ƿ\ζoKO؛~wb)[`Mnx{nf0>,cӦM9zf@-4aKfJa~vv-^-8Dz29f%;; y$[>'v+bIu,߽cY!g7zWjV=C/}Kf͚%̙3ٳJʌ{vypپ3W0a{f1Pd2~,ZGr]wpB122_ς .rƘ={6hgud"3f4 NsسLAFjPZ'PT :$(N@aKݒ8FaaS1h}Z22 .Њ(쑌vA/z;S}X2n{RE{2rձAN'>1iii%nb@'s1e uZ} ]Wt(;Ȳ܌hjș Ax;A?dtM 5;ewAZ^A4M+œnjF# ̹'ڽ{*8p -a ' GIF*=Adٜ$Lw],fY7ӦMa vZ>R__ٺu+]v_W3U>яfdSϟϽД}%"lzX=RH}fg5^pgmcy U* $bG'8b|Cň+pNWfPM_{ CyZ1@Q@ UxK(y&L@^|UhOUUU={Q\.p;wחM8& t:_Ӛ\=޽f@71RAp*w ,sMm&=农'6U@y!. 4 d[I-dǝ,BISVZZJGGT ˅a|sƗ_PAcc=uPSSAiUU2^UtDžvv;p8T03=7iNNtp8R9/ lG1%Bzj~rswwoH) άY2BMӨϗ(0ɞdCeazB@J>8׏jRW]y9.#ʙ^$H1g%4QnaZ:a&cc&ZLq*~nځ[A 8u555^d2@Qftt}47GQ BbhX!Cq: TUG$Rө(-(+"Hyr/EI h4N`0HII@ݿά]A8UU|s˖-cppwf4I*d…(w܁iR)JK˦폯vl8.W7S@H=;|g ,?l'o*'}qޱc+Ekf} ??{ؼi);o&ce6ƣ5 Yӓw=??M}T@;c2n| 7dU>y5*L%7n4?P2*op51 YV&[͚肒~iX^*q]]HfIbۧ;# F"( Lt0E{%DQ2"}VOEE)VP[[EU|@mux\x.nNgYlyVxWpiBhSrY&&HVLȴq_Z|34D<λ>xvWp-|lz9mM/c3VWNpŎq’7vv9=}< ECx/G҆lc/w馶6IBh/`<$h!i#iI = oK%Hu0M*Rٿ l_OߙYcf݀KIυ˥!7( Nx<*^׫r5HTUx| J(--!,(m@LI +?- 4% 7+x9Pf=h,Y#pwH(7IC*pA%#p=N[Zf7.`Ez]G:^UtKòuY+ y' n]q;ax+wtc԰i=O?Úe }qh4 Hν_vgt3?q9~Q o/kgƄPI7*89( qW7C}}B.Cxcf۶mv)5(0 N0/9&fECOV?G$Ӵ:x^[&JxUQ۷e:Dzz>oRdզ0EU|>>|["D26 @<~eeA˃n-3(i8 >-Z#2X2Bx"S1'MY` ;TUh0wMV=av=8'ζ1m:JoM>nꤻuyfŞUcc:ccvf`؜xP404(}3CH*_RSS;CF_ڒad ȲeŞOm^waGe1VoATyCaE ~eb<^,_|}َNWok*^=1~Z%{')^Wڛڔ)ULDg1[F@3f`Lц4вB1cHcFkKk1{^Fl:B*z#8 @U4N?ALO12ulOp:̏"5>H[AQ"t TaV]}jK)%>`0tttt:ɬڨ,]B{v;pq3nz=KF),,sPQa3]lV,BKSS[TMbԱcQ8僒B1 Űǰ\9.)(C@1t;``q~.BYb,gK)Ρfs%9q:{I .b+ļ%餬aN캮{n>P{g}6Ӯ),BaRfݜǎAZX^ t]˔DZ/ bxxd2I?dg?;*o.ppFiӊ7`̓> srvzh<ɥ(\j%aPX0"5lܴ1g4_UW'4B#g $fQE*ؖ,[lzI;{wxz={l˶`ɒD,FsFP]gnh0Ȓ8σUUU9{;wLf!3Վ nCn^>4|4v:W*`PcZ"!ªLIy)"fxI^Q yvF+ey b.oGh9ӽ^vlYKkcUsy::@TUiǍ?Fծ쟒$)dY&-- gϞ|09N|>V5R Y~}ՠ0WdV 8AE ?r`ۏ裏Ehm\~_G}F(++L831`hW@֍ $\Is\ `I`0b4[Ixa>9ZNVŵ丟?M qsv8qW[>kr0 Fsyྻ9sU2QCV|n';ȪH)&ҷp XXj#YC#^yUԗfp>em7n֛wډQ砝lfeY@ 0D"]IY~?:‰kG%!L^E8|߾6g#fQQQAvvJ鮻'|*կo}{I+&>^an *t8p+dʹߖU|t. *;dʇu/my/\]G(F9&MapCN/@k|{֬g#?=N8g?}Aͻ^98Jn1Q IDAT**78<[w ,?t N`cFa^P͸G>̢dpnj3q/4o@hĐE[ rrri|gB|u _}!~+r@bQx"Ȳ#Èǭ~cocǿB:Qj4@o?9ʯ*0fwMb4&HZAe0ظq#L&EH$23/**nbRm$pA_yj8dAE +5Mg>xi==7wqA!RJK/ꫯr뭷7 jkk1̈́Bv ͝ůe=N-'YWق4c~2d)M0(S%U&ZLV{ޒƞOMF$LP\ή>EmޟȦ x9~fw18γ?W6܍[NjQ?i2&EBTcd^F@?@!8fcQ`D"~~J^Ez8<^J6aPfVW|/{38yBi-OWSS dA &n,f3efoXX, Kj#u5'&$IW>omO|ƅۗNfL=GSp=(s]K}eRM}y p8Ν;)--M`≉ `݈ |}X t>,⢡3W_EjCnI/Ieg0C44D`+0" [>hK9:(J"ѶOL+Wf0sU>Fp(Ov1~0M<+laޣ :yVԳ%BM&:O+k;447Q]k/Og -5<8tY%Kx56,4Ok7|&'GLl\ث@o nlvO{4ۭg>V͙UFByd+Ğ[~Va깬BeiWn<Rd-MHMMѹrs'WrV>j A܅bRt2[dP| nPΰ;  6~[j:x F.x^$Bx&vg_OCʘ}@4 TJkr 16flzI ces].4|s3`߆4 煟;|T8wFpKaZO0cotz5Oa<޽Ý^MbT rw͑gӥ#\dgx_˶]{nw4>|ހF1LCOl%Q?ēoAtVźvS9/>"JuM L䦽9kFf"18Ya! lBj+%Xtus]+8g؍k+Oֶ"01ǤdY<At-LumU0`['QQGU4"8I'h8c40I8HFMM8:4 D a"aNGu77~W~䁟qKӱ9|ui)@4)4sp(@wwxtpԛ'Mz;omAS1#:{o(3:= Kls;K)5>@0D ]=hQZ^,If,f/9QtקEso-7o%cn>MV*L3رCk)WONV\TiXOKj~6<'4cՆ&naKzc$NHɓq|x=or?~|WI KK.K} ~A"maQLr8,T\GV+}uv^xKȶY`04L+fYK&^|05<<5v?vdf[G0$"u=X1&\},3<<㡢McXIp~x$$C6WCnDh:dN Ap zބPDmsbL&a/ ^0; #=֓1~z=0XMf<^/ ko2?'2ꗹPG?ui f#2Q԰E{u Ӹ #g(\3Ffyѵ !@61%QD sb'E%=l۱hSaֽRWMGs QYxdJ}RxBҲդXX7.0Aww @u49!h @){X Hh nXP"n}L) *0 ( )wywsXLT < TMh`(Ƀo`R{G( Fx%oLR; u'} i<vܵ{'o4Dn~ #ͬfe"$aeOkđL N9*p@Tc|b14Ō93+9gz_[vPZVŪj`Hff&Ӕa0B331HKKK0e=AAə:\nyRM66BSJ?kxH$" .S#8wc2Y)*~^$,^R*&~9: mSA1W ~E,Y]QIÁg׳7~ 5k7s'o FW];珟kLKiPR T,SUؗjRJwq[fyn:= ?]QPVjĻ9w%L.t#>"S+18wk:ˊha a+%fHgNoWl>\;5v>Kqi9&9,Vvv6iݻwϜx<3Bvat|PSpNDz$p$j~=)6z}|vgV-W^YpVRlRsbTOqBJUd{[y??CU#m43L;[8]zL;[GQQ4c&ed[,v'2{/@(5c,%I9Y"nb||YYˋ2(-W;א/%&+9]$v;ׯK.^ٌr0WD!IcgY6'@bĬ!l| OdtT!ߋ/E!3MvO lnrlWN^ͷlԾrI8r2QL e6fUW191t܃M;I]~JF#yaQ"bdl 49)1.i,e9v,53S͝6ꏌf5c۞#,Ο@$aX dd;aԨHiHRU $I"==6pLk^2+.|=*r[޵ ~#=ؽniJ`%-݄.8g\KNMޯq[)XK\Y`*/8:!r(%;lb>{(˱p:>eN-X,p5A]IZan"RXɹA`v2z]xKb1_zАM*J1Haɰ[0x4=쨱/Ϊ%O#[s'c4)UEN8%@g9sD'^_6Q^?^g1UU-.yZ/˝鷇ge35>A1\E` CȒ&$#KhaM}}] ;8 xlرڲ ^}i?EUkKrZm'MԬf@+/w&A"ž4D 7|]kZuzhIݵpm7Q@7~2%Vn`*x]aGu+\}CA$O>L=5L 5syP\z+>u?HSC;-#ܼw=t|~$,(TFYY>X2o񰋨b"ϔG3ؘՋܹLKh=X06JjsY9 2`MK'V53t((' ٚIavCCl9d+ǦݴWVBYy%&bшlBCƞ.s&2aJ7 DZ 0rA%9`Gx<}Ql VJĀ7݄yyW.Ih%zL OO<2˱RVJNו9:F䁓X*? opq v)\> D*JЋS( cDa4W cmUѳK̸B!/cܼ_zCc!k7nd9 )b&L{KLf+eQw RM&B2PVV ?1dsL3vOcL&/ؔ?/~݁oSn>!>zRD͎[oÑIEG Sa@M]KhN&;&- +?@O'5 d[&9t x{m RvUTp$+744˞={X,9s"th0̙^CWpo0;{W7 Lip<{/W\T1MRRRBZZݔbZzD"v|S,Ep ȕ%ik.F.AD 0:1ITR R3a{r\8er+

$Itw$Y1 )5ƘY7cZLIΜn^!Sew zHO8ݭd5왳z02#dtҰZJ4EedY&766"+++ۻ"inU"s{\:냽<_`iiBE)ѐn)))!s)~aSRR&0A_K=hB\P] Jul/w%gv\FjҜE1rݣ7~/jW<CnƻVpORUST[8~M)LS!HALSWG߹c} .fI|34>8}QCH]"J("v413 ͳFD1nW,dL&k֬狀z&p>GA ۛt7*ڀq#3sĚC!FFFx2A΄^} ^~gΖW=9c0grc(qPVa#vϲ==_O͋&.'u12:5D\tC%Biejj i^j# 0<6Uф`b\OCЧ|>o?K9:_'J4)]X41l6c4]U0%d%x*!!<۷⣵:XoCNiiLYz,kOջ"A}[Vdb+\%),d@< !V|pO뭥E_p/| ʋ.Vo犢;ݝw9}]wi!vHӨ,,J.Y @J" "1@'z.TLJ\N41}*iܴ6֯ʢ j GeCE~y K*ᆳ<{KZIUgO! #7]D!ǧK/{]g}q|KTkUmO<]Mz,hy_<xsRN t;{g{ lWaCth j)cKùK\h /3  W{ylb$'Ngaoo{߳% oy9[$,EE)Y1Xhk@֯uG=ئJm2M7090\ `\[d,>&@,/Jj;XL`S S cX!x b@ jH*Q++)`HS\v̳?@O<U2~Sܳg;g_xe 6)p;PR$²/kjTotLȆ,6l/I"q~x =H2;~ L;oEq^0 AQ BO2LT ijZc @{jtx9OR+',Иx9Y976-ժ3/B+j.Y:jlԕ_J}1W v͗I(`)=f@9!E2 Ȓ J"R h,0rL@%$UXeՋJׄr395|H{bڵ'M?7Lv5?jgZ۹|S.a/Qd=ǡs=7􀬘<0xK̘-6rs>Nnl=3J:hp~ً{pƶa8^U#D<ʑphŪ +Y]$9x z+0::ʦM0IL.8rDeX*[kfu'֬^K`ݺK2X@`"Y= SW%T2ۖXF% _B $(" (+$CX 0$h4Ar_ 4ǀDR=hK\]}J/-!@BI,I{$5v2st3Ml_ 8V10贑uuh9܃|o&Zɷ\K^Q tw0V&/XzMfd̿t/Mq%+HߺL4$n#*<Áq.Xa7$@~EOOv9y$wyddihH/;C j:U>F!tkF`UV51fkh] BW)`̏Z8_m`ׁMB_ThJNc6S&%&HTSk&" D6]ZZ#untBBOKJd(rq%f.2nP H#4 9>CC'Y]6}"ݾ¬{^OtE E Fʹ J(sdr[a*O'ו3?xkw{T(&opFf荺t$׈(vB鴵Q\\"d$"))BFK ǽ* hHK@V* .i錓t.h>~~w!6gt8¸sgc\=:tP`? b"_~£˪*/֚Z҇y" |m4zH[1uO@ 6;*Ґg:hH4FÁ$I8DQŤ,Mݻy`5x +^oP_bR%M%4])e&08kl԰qRMRB1J#ؙ"K&,dDF"! ͨRِiګz۷$zf JխELnCBta"2; F )1cM+;|KCkҍx.fl&#HJk|6wߌ)WJJ)Ѓ׍F#V_۶mc۶ms@((B0qz+X,q^]gps3} ֌ RUTr b7 0 9@5ʁZA:'ԉvjNX ua  DPp*vRR)riߴ!dVtzX iw=Ir?l?E~`F5$ـlFh*PxYtLbTP6,8VZH!QKH/NIuLT,J6pLLz.3gtcj:Wa*ТTrO JqdMtڀr0[HP *YCO5]F-mTNci}ՠ9ZQ6MQ#%i0LA5xUx S(zL? ,5P` P$ ǾIIjW;M"L r)q+[wJ;w e~Գ]hA 1pOa-&觵} 3aR$F8S3[ZV,J D{\-09Α$ ?ӯŹm)"<{VmP'>233!''g6$k%8ZU8k Ţ uLht 2d9+@4NAʥ>*#Nh@a" L5&u]7Ba& _V:ui'3`0 Z`M"KE(%l>gZbP@S1r뮇ٲ̫&|'+U.8ګu=ly#z-i-,!TZ~q38_YR,řdcw2>$p |&gW拉bmݪ'e0c>*Fh-*sK%31EHWlF]XJ*V Sva{P6"f @6 J4J6[ 8Czee. KWa!uma6rK\S-Azf6VWc6j&qu4>bҾ=[T|wՀH' <" J#D5>P " $0貗b" yB0ː亿 b&`Qs, HDEV$p< N!Mm[$k;(49u!Uš +e|6gWl66h,9BUUPJaa!@<2 1s8"uDpQ}RE\iii PZZJ[[ͬZ>^x 픔S0wbW$C0$B 斒cub.29V ̫FChЁT Z9xLK&lHieh2ktIJ(.DK18|N/}><~΋L7q^xf.~ +") KRևY}UU64 7tByA+fXNYنd6-F+L&l6P͛7SPPS[[KGG%%% xg&#!IA̅5h7q]]UA44D՚>X*UiB# D%<,%t^Q0Eۛ c&E-J܈a3:wuvOCRȲs\8:8l3Ui`-;0=}9 *h4f[tB( )HhɒI83P%D~YW g! G9LdQDi2i(TR`"Lɔa\F#Lx{rЬ AZxZ7.` $3VlyKSWS{]=c7O]ƒdb626e=DǛ9d{O})Fy{%wyTJ T/?ñ!eDEl "̀+/Rz_)mӅG[[a慁o +dawq#'-`P?gM摙*c In%0eeB= Ug0(g5br#*)[sd"#A0pz!fhd]x'w{@+zAPQ^: 35 ̔?dn,g/a u% ;8iOm]Cym ;TUν9k>W!<wu%&> ߿iOUp~˴>wS>w"?z GM[$QՊ?}x;jA*4xS{渕,ᣣwj85FNf.ٌf-3|i"n;ǘV^ܲ^˗BNqq1 8q{=gЮX \Rp{Q&Y̬Cr\a?3' A_Yاh?Ltt W2 0Q3zg?%$Cx==`"c%2 0k{͑iX#$$lLffͣ;ϭ꽁 D"E-nIdٲd8L9de2mL^&oތ;vؖeɶDR7Q bߺ{uWWG/h4A%98^Vխw?,Yk -bBOka-ݫbfܴ ښ]XSV!YٷTʨ{k\2bOQNgJ{<͡:B%QK޿k_+bYת{^k1rW?c[K5 uXy=gX"jq )8U 3ګi]U-M>K\e-f }_W*7 !ySl}!?h;7sQi[wJw{3vf^yda3=l q7߹OWɟ־7puhlfsM*k:4$Q\L$NrL$y-[0{ I y0wǑVewvE$uA^2]̝q\]NBdYNΝ;WZ&zzج^xx))REܚkMBu\ WY$_yf|!AmAZ֍< |`*U-Pr(z{WYz_]|~#fcm\` y; Ώ|-Igߺ[tFXׄa4К#C93T(OUMeTNWW&ӟ+gt6O?Gx`K_9@8T&%238LŌk/uV~4lي v[Kގ%'|ՅupmÍ7ŋ{f][lϕ p3jR2iŠT&NO;Uޝ IDAT,A+k=J]'\xWpYŞe} *.|> 4ž :e/go9&9}WҷӼt O|_sൟzS9Ƈ[X6H$±cGIGwEN˱_ctr߲_Cv=υf&Ȏ?ˉ-̥gp_n .W\Ve(_.kIa-Nx/˥|- ^esʲTA^aՎ`y\*bׇ\_d.(_=RZ5VvI7XdnEJ0J2s`&!19rl߬"sU[<'!!-(/!4g9~l m[d̏8uq-[r-;n`X`d4-o 6~A/v:a+W=iN~rPP.ed(Z(u{gc\r£T Ac<+)5 R_*O/h4ٳg@V~Rߋ2Q \+,L5Ub*Tײj{e)ʒRgeVd\tn'G' v6,cb J&* c$;VR *wZIf%YF:8 g0 #&ܣYul/E/,#Eڔqx/%?[RJ?ڮҪBˈG]s 堨6XahVc,k4C5ǹ39teu[yrg|#䵱Š^G FU qk<\*|5`L\GUk`TzBV{4B` C$G!D![(F9Mn87%I BBH '#NĖU0-\S:mSFrl5q0+tv6ٓKCrٹɊٿx ɆZ`>"rit]ܹsl߾ N< 7@OO%jU+&Wb6*k~Pa%{YZlծ^rvz%/kw]JKbU*@y>e<@sy3dYqiw:WBzolVII(E%"h.Qmm!9%H6 &/8e{1.G^_FL~.ʜ(e100@gg'H49z(&LݍX,F:ƶRq*հYB2WzĔU~v=ZyUR¶m01{uj"ÅׂeY1MדJd2tww#0 \.WE.X%u0%]8Ry&b Gik^G^>Cb. R+!?'16'ǼM΀ rl^[l׻IۥG].ׯGJaϞ=tvv( -[&E{{;w6~MWW>ǃiKJ飯躾DK|m{Y뵚ܙZzc*O<'kNO`l  Sujmoo^رcI.{%Ͳ{n؊jšu~j禔%/#_4F%%mpur ?% p{ա {IΫ ꓉mJ.}MՅ(X]winnFQΜ9G>H&͑H$!JMrKTn7BN'OSSm3==M3D)U2XVvS嬖mC(;vK/p{]JKOX48aX;vnm۶qQn&N)+:{ue3Yb^<9o/arՁ,\UGMuۻ:"m{bp9FFFhnn@ PPmJD"LOOFbK'NH$ |>TUr:N4(J),Yd*ϧ|2[+m[i_ .l޼(@WOx'@*k\N<g?P-[pQ?ΦMp\b1X<$rzWK+G(װj-?qOysvXŞee.TT]xF"}}},,,p1vISSDb|( x'%Sv=R/`]IoLV`ݙm9t 1qƵ͜$/N. eIZN{{;(Bsssbv*WX,'Np8mGym۶r%'n&hY(xCcc#dӧ B~r`O(" ]2\*)bVj|5e+X 슒Y Cϫ`].U DR).\@$*٬t:YfMFJ%ZZZJ@1)H.L۬` Ua,ruw\Quaޱ|쉻xqiNlɉ 2XL-Gy)Pd6w}wլޫd;n6Ν;Nj/odx_)sxUl-ӮUXQ/{JxB)EIGRs88Ϋ6TX W >P\V*n\O`RB{`xSۥJ˾54b|jhҠw=$'Ooo'w 89)Se~IvYϸ\AkWRՀYmjmNq8|sԩSp^Tcvʿ4'O:333~7JH$i2La0::J2DURȬii& SJòB{p"H+N n ()QVqu]v`aKAWe ?'2X7( )_&2[$ܾ'?(^az6q6^ye/|y xzQh%k\%EضDWJA6Wsfqtj.WikkC4RT.!Dx"===055Ess3'N`͚5x^^r9,*䕃t:4L4I$Ʌ  맋)hzV-*1xծu5kɃ<`ʏ)y%8tHr`ۢ^Xus\``UEH[]^?\mY$BڿK(*BHl|`qNd;={*^M³[θ#]1[ݖ#w\|BNjNA6 Ԫ6vH,|s28g-z<|c |~Y pͯɽ3X7|[a7Mxdơ$9Ww?1DveȚɠ#ZM.vӓsE'3o>|R:1Puޠ<Ѽԙ=>+:BwwjUU2o) !ijiua0,Qyg[[cccn~?m388H"`||;sYmcf!mh4O_'N K" ]]]%t_5M+f-Ao[G@cMSe#RYH-[]fMFWQJThC P @̙rڭDt~nHCoɭ} >,t7lm>Y^!TUAs89 ;>[ofmbqyBl 2&ֱx8H g:>;γ{9z-{!WDؾ|WVd*%&+%dÆ KXmkUFGGd۶m o@[[Dl6XqżJfr 1;;˚5kdY0Rh2ɔ4aYN҂r-S/Gq>y3Q +]ůg[*]1~{,{@*`WqHJ{AIrEy27 qCk~?fYgo-{Exýt{DxicU;q}X)d.MNL)O5HGG 9v<O{}5OI.|K#,g8ڋW~ {H9\M[oa'yIzoropۃp xqZ C uwGkb3nܴC6LΛ吝u`}z )'V bJĢvL-邕JV- ?YnGkkk)Dx7J Վ*파SOߏB3gx<|  "NL&1 t:]ZٸPJfa3ÁfjjuϷsM4N˅KWb"k]O,/yreJ.ٽ6R/ ` U!Zf6BD^xfbm6>rIr#tlY,f֋崈,vqcFG'$a{;d7g~Dx|ۈ Jyw=J~@DZ҆t< "EG贒 6ۛ^c#_ ^WC#,c=QЛ0F4'{/C'^/ 7D:6Oi\JÚ/z^?t]"U9r{*bj:ӣ FUj EH= ]JIRǼq,V%`+VŰd+OJIgggcrߕ메5<< 7nv:fƍi&&&wߏ[b}+?aD"lۖ:6XQ#G|^:;;z~NgIMXO,U[~y@V+]PW%ʦU(Z/[+bOOr`ab9+{{>N8p$/֟׫a,%q#(|C80(gMT P%Z0a) o5A8Zn);r%b-҄|UU$bj5*_-"G?CCC$ Nw} [g$7︙P`['sHR@g1tn#C>Y-mm[i麁 26=Gv2]=O^ _DR|;!}a(׮3z9mbkزd |N9|md}8 #V2M$ajrPyA,EJHDTXX%X]$Z߭zYTK,Wj}WJ,|>_*gøB!@u ,Ν;G:&˱c˶RX*"JH� \.WIWWǣ Vu[~\2i"BŪ, &=>XhkefEsYK3g/;Xq7Wpws`%?̿-<%|3/1Ƨ>i1L3W9?[ H~%81RIF/,i2Rfןūy(<Xp)Ե u]dɹ4o8ȼ37c|ncdY" >1a8Z%QA*bϮ 4lheM8"-y¼(!!j"UJe%oV=( ⥔%JՔ)/s!v?ԩS$봰i8e!NEQp\Փmmm4lfbbIIl޼ުׯku ^Uy4B8xry %B)m)eyȼ-( M-2Si^UY#cMu$9܅X&-`WcB%VAAr-EЭi -b2pضm+x>$)׳#`6Q ult2 hFn:[\Ŀ16mȯ~N+M|w~NFow?ρ)޿4>-bvjQBZYRlͮ-9 ^u x*31fc6^Jd>8eZБy&g$i^e:ӿf,B8ovB /勄 IDAToó>`'6w-DŤ|zdM6jWz:j+%ג(ڰaC atvv0r-KT+Ý|5f1)>orRXѶmb333l۶Đ3 嘚B4K+4].JѪaaajЖ~_]꘍V. !+m&|RD1ŞU@e)DWM}`&!ome =G)x!^TY8X<ɂ;'.FBATəHiwkg>Mkp7p7w䁑 StoȦ>#OMzn~ç:hyxDmnmegwn'v[I-X Bwu>{Ҥ3YϜ"I16{?6uot ]Μ(+՗/餬4CZ5#mӖ(O>Cw9wZa~/>]݌w,sn EBkBܳ?|oI|tt7G.~my֐ʮs#}2 M %iddi6mڴ"t+Y+r5k,)j\6oL ZYފ]]]vm l޼kײo>1Mى=+g1Ĺo>ϟ~X,ƅ 룫k)(θ5,_gVYW?Qx/d\(*t Qn8BBiIL!$$X6ӀC$i@|ƞ49_:{ 'pw?} Όb f&jkb&%܏HfܴF6wzhlrM&9'~f&|<w̩ M/mی]8_S|A,yk~7<L1@b-f8N9s.\NtUOSBhB hw봷睯G7" ";`hG&ֵCMB$,al81 r124BA,>9 O<E.:ʫqTU:KiR?mTΧ~%j-4MBpJt:M8& ]\.Baxhjj񰰰믿i ~ e*@˾ӮZTuFg_bQ˅law=^rE |P?^L&3\\F|st? k:!as0c~Tm9tWswqK {0)-:ȏ /GS kڥH Vn+p+/gdEQVɔy+35khoorFbtvv255E q*-}`0m݆a߿*mmm?~X,(J۞4@-S*Svbʝ\[ݪ_YOl:`KThT(@ +<=)g$ͱ:yk]/:L-pt:4cr~ǶL kG?FsCѽ|,?}f7na*H;31G bG.pwƞ kB}ui__]o6,LoVّ?63Ss;kK@ԭnEyEK.{%η0J<ӡ죱y @Q I%?*L|<+!P/޽JF9>ӑXHp~曚QSAֻ .]d,uT(  顧g Kv5/)%裏*\T*Eoo/}}}̰o>illm>hhf ɤJ4 Clۑg(B\rdW0cK,Ë+xQA;{޵!yZfN^fĶ"wNMSs ML06atp(_*_fjb3k,)xlG뽸nﺩ". 0ꨛ:KLXN>&(1Fu8]הױe^;ƞN:X" ٶ]JD/ww*cQÍd*:]K &]ثr?suN[[;0$qfff  3?ba!Bdah4K,#NNK,M.ɀe9 n3[bMQhK]Z ֻ L+A-OE TbRQ'_`X8tIta^ ^nv]Hvk[5 +*!II}4D7]<7yG>pJg3-m#|c8a!#!.^ٷ~h_ԿdYq1>-DBQTYHn^6ڶ-1UpbQrbB,\B Žmkl$MQ*>J1gt Kۗ*yS\hw-6¦E`UJxihh#|5zzzK}UJeY-0t b9IL"M& 311K$…id2ɤM,#Uכ Yp^}5sZBȃ北`VVb%s3d Y,?UQYQ6.}\uXP; @<|b8M>l LTܖ.|$OFnP` ^*S27J] Mrvf,gK˙,#p3_b"o}hi [ /4e 1B(én(VRJ!P WA)RP=H)Ry+ς ]p܊ȐT[=x=׿֘*?Aaj%`\p8%Nz,CԐ| -chhE{{;33s fg9{$aɸLOd2 D&/31q*cH4gEUd &R+}2Prkx)%K W *V_ @_<4Dh0G)H^'$̇)Rze-^llY~U jvvc)ݧN֖f!F-tMwtn.tK<^t:58R \+ +]pby&J!(Q(2ͥrgp8I VnץطJz8q444, .?+Ilضs,_JvU`R1%UV-oYG"_k$%XY)*Gl%vFT6YSj @MV^ [~|!@JrzY5 Pln o]tBH%;cd<ɤ2LʲNS5-v{<>W.߯hF KGwq85Ru8Ԅ:)m!+ƝEj{a+UQ[}U}v֯_-WSǰ򻥫s)-u]s169u'0F֬Y=ܽ䱏D"$IR4DJicx8J8&$FLLvCpT1GKe\JȕXY5^yPwX]Y;E˂8{, 'X*p"@cV  g {}1 j+SvFr.y[[hkM475 rx=rmqCSLH3t̥rL:Jܥi)ehN%if5#b.Ss١!w, KӝnPUKtݥ;~64&r{*s!%R*NMW4-UUeN,8RW!H9Trٷ9ȥ!AZK,YR222B<gR( ivE>x {'?d#G}vn7sss;wg2;;Ni4q YS@@8ɯZɯW'9ذ .`&5ȕ_!X 畆v (˦˓JW*ge RTeKX' ;YEsr[P,Jך-%2("EUU E>ٿn_N]CkSz=v*Ra'))(B=ϥᘦ{N_>BM~;tDMO&]DґL0̚ 5C?h2†TJ1u:S2iMJ˩CtPuf#mp8ܪvvֶ9ʏB/iȑXšj,8" +|BR~B Q(ܰ *ZYݮ?Ukej_kr1OOO\.BV7VZJ &  ʅ Ǔd9b8pxKן]{`rP]^Q ?^[}^PcO0KݮSuDY3L ptxh8K$f3CU<^ouguMpj.c^)-ΦPéDq&gf0add6Ņm۲ l( R8=~[ZAbI)Sma&lFA*9Tٌ:9ǧ]o;Rux\4EQ5tjNg.(*J~(8th(:2YN,Ek)SVdJaɊJg[}\]{p]njX@ZKR/O$LLLBn:0'Nxoۄa|!|Њ5<|Fkk D\²!ljǓ9az&1mӄp8ԔI&|oy m pyr9JLYUUeW ˛X%A,c`%UvvKѫ`CPtv-tPPCǃ91lH&St:=4 D|bnfv.F$.W6 B[ќbIq" bdlJ G"J4pD[Ν6 5As{Qܖ/UҖR;|BHÉ:\n=jztNMé8Nt M@Qe'*λZa%] ֥+gŐ?,]{V 4m̺u8p< K/F*W{ihr)4==+S$##ضM_:>O9r|x<^4eYd&ӳ\0$R, ,,$ɯxlBE"䓐di"KWrXbp6*H2 rRP=T'jԲGe>)ZZt)ҩdt:d33t|$ ð>_9D>kmoҬ8Nu!sLL͸F#Lϕ>,ZZZ}tvv KWT=΃n'NNp'Nn6GG444VW۴vm;w޺䜳Yh4B"4-,,$dfftda!ɓ8p(G'"uWH^9fQȉ2Vm\;b9`ەȠ],UT'j B9h&4MCלK44D{k3YMߺ1Ȫ&qsjj&{qb"x:e֠qꮜqNGCScг[kom`PEJB,.GFy3g3rqL& licY,`Q^~ni ! !r^zPrY9!^0,6B:PU"mP*P(W}؅C,;BdAeqt$RX"`IٖmٌII0i[9R3t=yƟJ\.Ɛ١|~Uם3UA~hhj@h.7J!PEDQ";RՊeIe,(˗M.n-nSŖԁ5: ]f>jwߕRm0dǎˀT5&Y2$N=[l! qwꪪ_ryhk[zw%14x ]wv{f%]t:šCo{=@уaɸmed2 &1E%%Pvf{>*tZJq\ܸq]]fAn@ !2 rYPH$rrzpdpXŤ.[-X^ * cŽynϼԠqUlp )-?{o#Kz^cܳԽo/MRlH850 A Ây$E0`x<̃ǂ/wٲkl?L^?8t]Ǜo~׮]׾5*p\'t:}ܿ*.]q} G?˪ʕg0ck1>67`VtƸ~}ou[[{)UaL+FH`~ SߣKcY `-?Q&4#&K.?GF+K3Oч P^_u:w;p0zi}* n5%#Bt%onܻo~/[[<ǞxC;nk7p57jUm{~m>>ڪI$A7}~6o޽Mp2]i9i^ V;]3qF }p\ `[XZua06'д $W^rP'A;w qוèشdO8q0M~rf#N8e- Ti#j&HBA7,F0MPJ0̣$  @xG}<ѝFv$Q "T]ۮ5M{meI[[]jUX)TH!@ ˭­TQm,r+0 R &!!J}oD)DD)E<*VI'8)jKӰ$g[n < >Ps:%+cx7''-<#׿&tp9|CK/|@n^nS)u|ehd| J1sA't2o+1*tpg_0 vnŸ":o|z+++3 Xߟba0GVC /z x뭷h4sMS.}|ځ<ܧi2X w_"TW4H Dþ[E`8@{ϖU*@JIi86ۆnhV̵'܇Vi"J(m&hzs^.]\<^~ Ǘ׃jJwm[_ Lvή >Q) O{ R3O ?Ap}/ Dk It@fD@*CGab4N56 Iժʐ0~:#4p)P \3()B̜\vgcߠI`)%!HHf33c%$I8! :ܕqh Ҥ )JGaY*m5kyZmQq]tMTȩVQ5a;.tJ  H0 &޷9,1|`e vd||p[:!>} Ǧ?vh6`f|_ݻw/}iڴ{SWV<^w~wo~wp8ċ/U} | _81qfOSO`I WX| .?=ApR#:p92PU CϟՕerKx:mHMrZEVCqݿyp-kyn#˶=s?eaO ;qG_~n޼mz=G1#cA8N(祷.;#1Zt d2Ct| "W>O%%<AJYJBPb>baa\|Bٓ1d4|dC*bHXKU$(Q*RQI8I@Eqb썵иsovm۹x8K5i3+0ttv]a96&l"R tÄ4À& !''0H 缤RUS&_5/a-[$h4@jyy+++"&"[ocggs7~7|_>{1M$IMN_;UC:wдZ 'k5L Hcvc MH [83D$h4R*0H5Rc2QV`[k5qnumm+YYnW}D]Z?G2moymuמWּvyy<ʣX۸پ}okuPדE5[Ԭj W!7lΥ4'\Tl1{(H)V5V*^R%+/ʣ be+0tmg]IBI2S1}:6=( ̜Y3C"K//x*5MT h]X" #Ӓ pIGQpjԧ>m[#7oHnܺ߉㨭knѨ9mW-C7JM CL[N*4L[X͚a0L>ڤ  "f$HTvLa+qfL)5MO#*fb+ ': 8;[~~"4/]O% Dx+p]ꫯ{xg/vO=R5gff#q4]-@&hHF75Xz&Pk{7ˑ.{`:>.I2mΨ7WvR e<"cOhjPua:.?'>z64]iJB$0:I!n7^}WW cRa… /._n߹k68Nvo+NV×z=hu/^ޑSu8af$=3L{e5`ttoqgZ@ R 2þp܁BEh_"ț<ɚ=H!A0Zޝ Uq%+E~H04$4 ՕT+.**)$xyy VCAsx `pp8ԴV`8xCJqg}A ?Donmo9ϟEU)ŞSŲ J>s5r/_?o@Z0pM8# es?SjR=slL bBYER>m=J&{BqϗC9"d2HRֆSV;~g1@!.mbRˁ,l b;Ng*`+K.bK^]m坑'_iJǸ*&}g ;hl70M@o*9tih^ci梊g eV֟1 a=a6` ' ]'4ض,LBHjړOcWl4Zza *bus֋vvjY'V+~c/~ ӛoc;m{o: #Jr]ZP9'8_Ajsl&]<{-@N9Cfs35==)u[sTT@DHXtߝʮ"`J#f֗IYE)Dsu2֬(f{H˴t,t.6۸j#M%rўh $H9af$aPl9DTRcjF-Ic9XFjUm[J]IM[[aYRɩ$?yOX[14Jǩxl2$IleZsjmPRh"}]NKՅ9X>O<TyַwM8h[{5"r$KI jfni[mY-m!۶Td !ef'Mj,u 4 U9Zgjs Haɐg,#6ӀS]3t{V`Q yth-L? & K Lj=`%@ӀpX pޏE /㸰LaqmvG8V +mФDYzAGa{ }ǵRv 8MیmZsI c {oc|oוbƾ<ϧ0 -)>Q%AһNXZGKy)%q<.Hҝ;wKKKT0'2#d3$r&w y$п= 6}TםO?K$&驢9 $eT\8E^QUɉX% 8@[i#Qj:X^YQ|{cC} PDQhDSӓ@K3'I$Iʤ<>t<Ƅy0Rz=RJaii @ET*-//sgTs"xKRJ5e''̩<_'/hB sfJZ~6ϸmLR&lXyIaZO=wҧ\5Q[QdfŐ4ݣg)uUWyĜ$ N*Q[IpLj@=cg y&P F0 `a;kjUZ4-4 M7`Y ӲaU4 bH 4ے8*ug rYތHey^ 8}`Wxcp r2 IXYYe5j7ܨȲL؎˭6/-ٶmb? ~N?vGxa8)@^co7jhp¤(B:C;!=77!F$JylpV0t@u+ *=^)u=.<a¶mq˸|2VWWٲ,03!$R 43= 3z<σ$IzVڜ1)ysRG:P DW ]N*\b; 﫜ʩ% $Ϭ=gu;ߓV:3MTDP@FPOM|oFkLz+ B{y5y,؄QB 29xdwNظ_}Ijj9ci*3D$ ]'REVV{ NӴ 놩46aN6ZՐcbYAi]pJl /!5iuă5|{~I_23Bʉg[paV|*/ǶQq,ԫD5a4$ (aí6 yeyeě[ Q+_ #9)bq|l8U9Zj%`J-gjVt] 33 @E^'fF}iu]0MnJ0`Y ]ק᠙(r1vqLdMhmm֎&+0d3clZQB/l=EF.;_U +JXS`* 1 Rx { N!kY0؞"C@'򾻩3cGt*۫ٱ&~wшƣ `01-<ǃQ%c'NT+q|@MC2jU*i&5M)ir`9.Jtd1O,^sO#yLe>ǍeOtE]> <c[0u<#ݬn2tHiDAI)$=0{avᑔa0TnOQLNwG]t("bfR+c$3iQd6$0/sOIG8\Y*\VWWiii R 4M0 jJ0 U$1$I2OqV`f4Mr]Na4@JR>۶4~)eƪ\4`N:|C4f J<>Ť]4Nhx9VjMK^+YoII?T"eL'NTd>dImFPEq̌njwe(IFf.Ke=uirC$Rbssib<ҥKEHPQ+QE/&|TDnT %`/b$0+@?aA#{h+k0EZec( "[D$D M ihf1(xIA%.+J|ov~n J8 RJ8l4jF)D$5؎ ө[Դd MwvkX 1a9V*BQ0 ;f?-p5&Y?Q5/`ׅX[[E@^VNcaZXYjӹUZ^j4 Y$EQ8{^npkڞlڮUܑkۉi TF}Mܸq1apdFcQH~<@uIl J@Sb¼>ܤ]ב:DĆa Iʞpt,$R_[۶ T&j%IBI}F#ܹsIpr4MȉPJQVafN*$lJϚ4Mh8iZQg3 3 c΂G~1΋iuesY/J(Q~ O!! 9so9&7Ǘ"0dp$шy2}}4Z;n׼yNJՉ⺶8YTM4M ҥT+7[pi$ABm9idFx\wgg_e-|a"97~Bi)|O'qQTd8a6hAw?7ZTq]V}˶B?7]{?`0 0i4S<䄇 86x9Zԯ4ϊ9b1!DxۈAX]]@D`!Ԍg#ʿSx/@Ӵ)TVyii)YqR(T>DEl0h6l6BRH*ۧҜ_>5N.\p8D^ϘyX%LiXhIى4&!Ad QYeJǬiY ȠwV:s I5`˩Psi`*PJ TS( <ᰯGCh`v'b+"&ja&mX&5MjU8l6VBj0mfǩԌZ EΖ3̜S)LH̜X4M'+٧k?+KÐ6wĽ?͛Av:s>ܳ>r%p'?ݏonckoW`ah4`HQ"K0!3@~DsWd yzy1T`"^'λn5Jਁ3۶͗.] ֭[tppfJ)ziuu} D|b*ߤ*0SYː1;IRJ4 i4LyϢ$I8I~6͛)kh4h4u]J*N>4 뺜$ ݼyS}t]˟'DE_ʰ(oR^2ĔnLIj_l7Y4jD@HAԠZN9!YĀh 4eחPA0mf^6ta㸶pARAŭmh4ۭi9p*UR+/*ҐftEg뽘h)0F.u C[FƕK&/?4,C B^E\cVܭU7\^WZRוiIn_}u?s􃗩YQG$$b0O{XY P=<9()̯-U"9R^Yxl P.ΫTMW,uI+Bte h"cŋYu(pppqm}.u${%33PY\䌁Z4N*~*g&$I0 _~ `۶jq"!{{{m.{98tEQe2v*H,RE@(' CdjwY>@ұv[*4鹉Y/$d_LbrlD$Џۭqsi5 R>;b40 yh5޸6ДBXz]of*.qp5ruT-vu& "Ji,e RA!>t5`,3_x>} /r65UR N ØJQG0 }/Qam՚Χ~A| # o!:qz*D)0y<)oQ_3?Á(^EBeq%/{`9)< e^SdfVi?>4M\E=L6D!|Z*/晟 -͌Gi=kFms1$ Fyh҉<q;wr]EZEǜ$ɴb2'K0 ܹsz*!K)իҴ|.6}NfNkӸL:64ϛNx W^6_3s_3^rZk:LA @08P_%kֽ(sD g$jrޱ3X(蓏(E?SE>TP+}a|Y>WHԃ叇s?Ϋ6*ƝNGͷW_}%ˤ(m?+_|N…p{~["`y23 )$Lgc X"4M9ǿ,@IHuxw|90ue,JW MS>~! SO13CJ)2)8Li9i)/.r><3H.u td, "=X5e)Թ,\ '`on/?&-Ko%_M ڌ Aɷ4pb0 ,--ʴa5MG#t:~:a0P82 +++r!>O\n ,eS|<aZHN.p03,˚F`dRIf\TPcQA)Tj:뺎4 m͛7nQT,iJBJ"!&|t|ߺu >,^uH)O|ju|0e t3pcͦX|2 GgSРYݪuՓSFj7;`W  Ү {o)T*,~QѕPgOT&H85C[uGXP~:4h!X1pLN0Z&mdPĜV̨Vh4=(KFQĩ5DMӸZŋl9n:ƢpGW\`0{oƫ̫kkO`ggmömdqJ2yݻ躎(pҥ)S6I Ʉ#@ 5ӢaUlv- I<A1Yت/r}Y]X~ 7D;dn]HQslG}8WnXIqJՊaYlǁm;Mu[k0+Ril6ifo>|Y~| *-cw C:eO3x昳0X8 %7> 6%>:X(c>(s!'吝ܜf}8:+\ЊXαbEP (p_SV ֭ŋiq/<(vr=ϛ@&ʀr .l_"9YüߒylVр\ vUE~TU%<>e8FXYYa:cA!'7|4QVh4ʘ^.[:n߾_~//qebmm >g +4|Ͽ\4@Qn晠vJ!Hj6Z?_? a(G 䍆(!йqG6XV2Zu,R]ǵX FC"՛M,CðtbM&$D0LMˁI Hh:R"`^,;KKe)) \o?em9j9<$^YUBK @h"āB@" Z6 XY)NMtJ ,2c7ɞ[oG+oquiOїտxD^1V e@mQEc'MOc? N "DQ45XcBpV5 IDATU18˗7@<31 T*ʇpyP\TDQrXJLC*XE`Wf/2t4K\q?{o$y_ ߗGU}'I&axYDyfcǮ6Yް7ffgóD'bfw {<3OQ)x@ $@7>_eguW% Ȭ{$.gK'NX+8,R~coo/F9-../WҥK,$08JQWWgYRܹsxGֆ'N`pp}}} ~/ G7)|G( L$ N3 q`l۲`!x Ǫ@WN/^3! U~eqo.CL'm[vJm're a¶-+!_hd: 4aZ ضM$+%I&RSi`B2#4Mھ}H;NFa&XI?&|eia-fI;' 3j@!x!=V'ށDEW8݊&4ZC6J :;%~= N]z!l1%\7kFs`ˍߍdfت}lFq,#(:r`ffRJT*K.addzzzRa fgfgݡNdLm0Oq ^2Lj^Jo\UT1l/_}4EAd̾1tX4ʫQeƀRǙ~p { b rzR}+ ( m<*_uIT"۶MΝ㧟~ߏ[ {>0aJ!72"CrWJ !ζamrmw-o,.,Y(jy\b,Nձ,\Mo/tלjlA zL%I#Θt2aUHӔLga'S 0v$) ˲~_\s$mF!\(,ӗinFC1R@a 0ۀ<0q:LXdvf0>p)T OJ7Yµ^mu-2 L?V@U3s3N@737e+:֏Yxܟe>zޱW#Αp:I]Fg?x>w'+dcs#,^D\Hz "CIw̉D}}}$N&f! q% yڲŔ^R6L5fSQ{,52q TXcbX1+2 B)|0Ycca {WaݘU&nw XNQ\P~w ̼1Ja,F)MABpww/2č[ʷa]t}Om "C_vcbϽw"#d ý)>(9("v{_:M.'*DRj_D\i6 NPki%05dy,]EP;ȅ|hrxdyeb pfSv ;+@qLV=8:;ՠԦ@\3! B`L"8xezU':;ӷ$!`#ҪۢuH}늠H;I_&&5hzřu)SY?i@#i-ʥQ9@cv+1< "6 a!_乽{WkT4ʥZY兢`+a ![řV^2yxd6LdvJe'Rall::;u=VR_g K F tv(->`= oNH@@Gpc\V軓̨>.}끣8`ZM_Az/j8U`B8V*џ 48͂pyy{o0RAJ'!G0_L---f,#Nz/yQLM -@2\vP*3@M xCz寝"X F@ g1ӄ.Pe,H? $3[^"@.Bmqk3xΏEyhSa'2xm4e~q_9&"K%~akᇟx ,yw㾻v{}#5mcs7GwWp0csgy$ٗ`ɋ0y1RsHf͘|bfC:LFLW^ͤ_&OԆL G0MD,"N;R^_g🢉4;iKxŐ7c~_ǧxE2z5 -jlg4ܝ/ff#YO48%D05bGDT:CG_1dzVBmhAq.~_:m6kd[naϞ=8u݋BP%LkΦ}~2خl&K׾3f)s]ʫ )\E:S]ݽ+i.yŹڹmkk>[gH(W_a < W{/1js@iH { Xi2X`\?M_ ,+Wyfx + +;RQ"fXCl OJh5#d%Sp+N*d[ fQSK7OW3inksf~!a)tsKyC߁*'ƅ/z7Z[?V ST$oT_ # gY6j,C} @PJ5Hb˸~:$P,U6E{{;E "a3_L!]\q ЉJE$M&R\YM+Ҫnʖ燹 ՊKA+i\x8uj`r"] Ud.KFg(WB_(Usu(@P=t=1~{n:u `f9r^}ܹ;vC=Yuםf I%x)aIXW hT*:u W^Ů]`6mې>8w Icc`+ &ɓ::@!"9kE&;$l~~PeFuJ-Y]>O{]+=t̀ZyL"Ӊ_;]:y>y5wF5ʕJx1tx^^8U3u}=6³ͽ#xwt>?QĮG|Wq)>w< ] { Vj$yM+N{ $ŁfiDڪ|# soD*\YϓB& ,Rl333Seԙ z{gzzdHI X1!Qa Q4 D)@⢣M%l +K:e@u4=^:<4s$@ aX0C$[d[&e]4B/9R?Nu[ػ}*.ik,hA"9P1 )*,M u(=#M^@UþXEt|AfC6a*oS{{;7qi],#n l҆nۊ]vAB #ܤ0ceeT*8y$Bn<3_UDYVRX4'nGj)Ȳ, Njէw@}`8l"XI@H20) g-^#NbAJ"P?%jtUvj>xs U*5B;7~BOW7 )DD?{E{~ӟ'_Rvϧs/=ç~Lsqjl;TNL+CV}WGRX&Bk&zi%gk=q=y6*C E8A$3ò,߶c@DC߽|MxU" (5=R@"@a@b˴8T-p"&.0G,SU€! l]Wr"+ҭ DJ):Yɮt2 \`ߢ)$=˘&<.Ru\ pH hVa>zG#E߫҆/Ib4Ä<'6V*F ۨ1\*ۊH/Tʕv*#:ض,~<m۶ Lm&ǩp*ha 9\Tc!+AF  $v®]}سgccA:@cZ .]¾}022G0 ;w:|;.@5֯χ޷1A vvAMO" fcZDm$r_ c>.8i̗k4zx^?&;}}]xsG7 P><o8T+p\IKf~qG(q Ksxtuʓ ;Ǐ^j|ijY3_Z+A0ED1HqQ jfܿ`gv *,&ʯ&R^ӟ#H4Ȉ`PvBFFӧ SS==6fR5ʀsi^))d2IT`%-,,AfS͢ЖG*@`t1`H;W UӻR TPN'mB*) ,Ip&r!R+QڲU急OFKuvP #CdW8N>n@J(?;f`jH>WK#)  "]R(_]^̧LI"lg=7.?MzDL /4Z C[lV̴,19ӊ*h}ɤB:Sbx8={z{oǧ>42$::ڐͦpoښ(߅?~B-Rsso4 X53}Ϋd̂:?P&+bؑ8fiKMo^|[؁{w#Wiþ=i{~tnUK"tcdhmpO43s ۆiҧ~x-/‹לƳ , /2Ʃ{(V~:x#F~m$m>o6YN` ۈT| ;h#<$^%𫷈D\ V1*PD*\oKT*(0P*VJMHN$a  0 !'װ\"HS@*iéH%lS)Z$0ٲ@ ;rj `p]InayWDahJ3&p?dv`| zPd,!QzWxHD \lk 2դ߆&1"A2#@~lfȢAO10R6}:qEMƩSg0x jC_.;w18 c^`p6RE?mۆÇ#;@6P:\Zɒ]eM!"rz$7&ST:<9U#ClW=UwcЙI7\~]iO௟S|μ:v|◿_#y ~kђ#xӟ%-Μ~3 Jy ٶf|b[G6J2o*y9̩D#lFky5 J2U23a@)q`&uFG}r==bQ i^kʡ CA\%7Ȥ\v ! dS6j|]1 `6ن@&cR)Ss qAlzeA&24{ɕ.HHmi` Y ?9w3+$ô?DD&_ IDATLӂ d\Tj s0MŢvvI΁o.Z6N]" ,(: ' #z{p%/ 1\k }P< 5)qfyȌe8G ޴VAq*^})A Ґe*:UpBA,K]$+d1?s=–-[pA|3Y#3Rr_xDkpڏ@6zLhtdm$\DX-֑x~ %}N8~qtom{Qo3P9O ĥk쉜!yu.w=mSv=+4:)-x_?ĵ`{ONR{&REZG]B'R [ nfLmLĵ  @2h ֘ 8<2 ֭[D___8jFX3m ''|{g"()@*H)9 a j/$aP ʜdL%ᢂT:"E$3[J s}4QYCHR0P`[&HòXXpRH6eVz{&j@/@zDvyLd"m[P 6P`Jҽh蛄~ƒ)ѐ ƚ5B顈dFH)IABBuͱ>؏5AMhFÆ& 6uX~i@tQ$DWi2W0(vRuK;Cn A)R2²ilގۻcG?l244q޽7oCP$lJ DIAqsEc-mo3x~<-`(e wV,yo6 6S\}`K @; ,l&,LD:0]EVQ:H4f 䲀ey{O C8‚em0 ApR#˲8JRvHR]],`X&eoݺ0Ph/j jU 0 '0`Y&=~FbfJ-O=CՕJ%< %8. (%!@ui1P)\A$fm&L;U:Uv? b40#JRJ")՜y3~z..`'[%B2zjRPB4T4X)&1F+XBM=\'-{IX ˉ<=u /֞;M@2_t}oIjؾ v `˖az J%Q./ڵ <}Muf*N ;۶gd2vXU_Ȣ `X/>dgĖ 병l56hhy͓_,.u'r }q $#C@)i|μ"&&wb>dX)6m T Iy. b4Izm3H(O<8yR5O'D`?|@5ẑ/ڂEu]Ejŋ0MiYݍ` x Z O\_e]H1#xT_),UiԢ$].dk9x Ҙ5GL`3 0303SóAdd1:֭ H$lض ˲/.Z@K3P(`qqxqM9Ynv>|eDOO^ipq?enJE6 @tZtmxQ PV+~x^?\ixnF{2Lμu LK2!U8i G׿M*NAw8@l1ut䰼Tl{a%\+{x eʩTn4cZ̪udZ`_Z1>z1V*l{3Чv8?FuD5{ xH nM\0"ž={P )Lڻ DLg9KéV0?;QB`b\Iass Pḽ)*U*_^0skD$SxEIe[DmòlTɤ H~*-ͨ\Nժ ǀz= ah^DB &ATBb` diĮWl2\ZA%˗G|RǀdiRAnGOZt>=ZoV*ydvE8uk3.0C+yXbW G/ՏMF_s(t/9r!%j#VԳB+Cc$`&0VVe k :Xpm=mصk Σ#\.|>l6^X*PىRCRJ8qRJi|+_//)ZOs5 BEio"l`R,$itcGO P~v2}U`SY t3/?qzlzxwlYw&NHϜpSge' )W!a2m[%:q Koy]vNWPry^0j&!mH;@뵞kpZ1*&lֹ?l Mm(QnraLXARzp=tAI:8 E&E&` y^գR)C@9PŖeI:deY0A*yJgLXz mXbn.3d@&Ԧ\\ #GXS2]?4`&\OmZlR `Y I@733XJxaMC0L@#&޽IJYVc5ʡ(YHctƊIXa=_)Z f)Ž酹QVjͶE/4ڤ,ItUhXlqi`LWtGG)Dp{#ZÉp:1q1Z[aWd/fN)|7߼ bpPhkoc˖6chy vcpm(Wю!8NJ 8.\>/"AJZ֔kNŁ\ DV(nlŏPHU0 g`$vGm2:Pcf`|kvؽ??sotF/O >,M-7ЀP0L [bq:O/Ua{Vf$޳=(M۶ w܍;;x|.LA*VBSzƓr8v7Pm$^2ѧ6 63ת ?dF(bf0eAd JbidXӞSﮞ$98PvYjH@JA+s=ȶ+%=R[ " 0s"AHA q9Pua@Ri\$=M0L?[\wbQ̙ +z{-eP(x'hk3ӳb.]FooO|zzzbY63͛x뭷000Ar9 "˭y(kj(V=ܮi2t?&&W)k 'sm .[Bĥ|6w_q^Y*Lbr=֚dcKtDĤK`ZIO/Jx"Mai6~5uY3&poB)rHato">U/戕WȀJ hz1= F eUC"Qe-!^ƶm#ؽ{ZAww9 Yd2)%>UiU2QBPJajj hooN111۶1::d2Xϧ/r6B~=j N(?[3{gc60>¯{^b,5\SFvG/v&Mf.,bʎ7|O‡V ߹m a4lu? ߺs${²UWOZd%*a\rgϜe+Fw߾\HmK+ۙ&+8;2a˓I:.zPRA)F&mY $6j9N l \49qk:50 5Њ'ab"0MSӥ0 40<)a3P.0`IՊ&:zH[o._~Y& `ӿf la$tz=3P?Ji@Ҙ,bVB)5FX: o'4Ȍj֪~PxMC8y$LӤ{wϟ?D"{R-sQl˫Q\ҶOOڰq(:||x ˥14T@oo]]itvOcx~dM> mO<^{멧‰'o|}}}XZZB&k裏և/6tI2 ǝno"^?vd`~|Ǘ|Cc[Tt&u}VP]2mPv1$[c2րl.!,k T#oCosWp#CuhwG37/R[Y_xydƷb S׾*5w}g:^}f/ۧ.{ '8! I.C`YY$ [Wͤ9-լÐ'xehZM;| evmZq'*ỴwW=tI4\ӴReNf3ю80=3JF7:Q$Wn>6Lz)rH$IfnM̭2;H%M,q*a!ϳ$%&\!yRaȓD )%Ld$ -P àd"$ׁDR)2,-ˤjA(«^a+u"Z iL_H@?s@@prUcc9nV&RГ#Ǒ/UdAQ\)A3S$J '9dW铂MUz_QY' nc48{NRDmmm8|033ypp}`ղnӾ +m+|BcD`cCsQvJ) nkx!kNp{GڃgΔD2dttX7׆o*;FYf1jkm̜ч \</~ <` 1샩n.hv-`jdl'lNi} *yI{538/0Wm/_?U)G˘ &$_~D9[3 FKssx19{Rk/)߈|2uP|ʅojv3LFӅbmdVeͰMĐ۩$LqÔ0wց+fvKFƹ*{u9Y`C56 V%2޸^ Ү;Qȧ8=7fmu~Eѵ_ehm4w,O\c$} _Ѿ SW*B@)J1}O+Wy99:7 zcoPJ(b YZɥ@D-}0G<62 nB2~ݹ yIm۶xݯڳ}L<_,PV ây<000LL&OS\+(J1UxV p 3o ˗tuKN%fnd`i\V@$OgYa9W>#°INXAmCI~Rq@B %ƸQl{tVh IDAT +bo 9RL@ % w/u+u k5$8\gb`Xfߐ8xzzZhgF7#N"ts8!HWS:$H&ؾ};m۶ Dsss};vT*%\ ͍ j #NJeѪkC :"Bc}D#>i&XNEFAO{mu]oϹs DKj0 FW:;p'ṣKK HmDv  s ^~e\v =Ja6vڅd2$ tttԇ 8< 1?JoK* @5 @ywu3MzP/?+ ; C@NJG];j ̼R.#Mc~&Xr C#سwO}_{]zwƟŷ1]k_CxfUeATl:<<؋S(-bk@O o߱\2goEnEǓ? ݻ wNyT`3bj^~ B&&ZLsgG,{z AB+j0l6\.MSS 幨TKh$\*ڄ1b"{ IJ̬cf{s38 H(겤!GveieJ\QOq\ {g{+<˯@2#<:{0 3&0 -m-Mm{=57(M);b,mY`LhTo{e** ?)rp/HnP&1`_ט<@S=WcXa|d,@gg )GxOoa+w]$Za"-V$zCey.&5ABɰI> (+kG&%T}U@IR|x7~9 <`ФPb tQCs@mD I-!tZ"v vĮ]ؽ{+{ٙE6@&F2p{M)-"8p r^uܸqH8wnN>\.;vѣgdMh PaM{C&@<-Mf;w/p\rܟMX39^.61X6mNL_} ?83=kg2>}h+Ǭ4ߘÝpqdTk*-FVIvX= {9.$NV44=y}0MI ܃X泗ؓp}qlex-76-8iJ|6nNomȷ^cׯ^ޛtl`"̲,0]\%6- &::\)W)L`JɖS 05 c"JaaeDJB0m ' o50vKHaXH lJ$SKJ$k57JJؒN&zaP ø:XёBrN#ӓ@ooczށT*ư 90;GX˗a&ۋ͛7رc}]ܹ}}}?Lh6d9`Y+9]0IƔX) >TH8Χm 8'uYgx&7+a.RFr\zM+7IvpsMޘ(P+"ێ~78w께?TD5_qժѷu`z6&3p܌ߝd2Vf}ީ&PAM ]yazz85~sbqns2]8;óѷk7/W4VVV;uرC;="-'Jers]s+A)&(fq" [ ʚr)tPA)FiҦ #ymT(5/Q>Ŕiq8J(%$y͂ȁeD>/֑ZhH7o߄\p7oD6EP/~lᥣ wҏN((g )p mD[v0 ׀LPeTJ֯tGRf o Xb]@~;qaܛV̀_JSZ *JP-cjrYDz-8V@)u>I/GOOii̵az}ۜڃ_^svrT≣\ V jGzmF{. ؒ;Gx%Gix>pwoafv[ɓX_㙅l TVJl;3䃫%Hcyenv !lÊ#Zy@s30-bK/UЕ;,}y a4Mr]\@`;6 {a!\ &)Xeql eٳ@AGv&8OT ] ؑL ۮ3)N !,D$ 6?mo'iMlTY#-0, w|/~(cHI잦&+"I]G)QFXĦ2v-#)iBEC@ާ"nCK)m/{ 亩Qe\E6ԧV!`(qQ^rvT3mLcf`kۨs{O R2,,*,K"chiHزe x2î8:kIF0h$,flTiL TıKPeQC\a= s:jNӞPi\m2+W+hVv쬃Y0PmD %'b`oonN_Dj  ػ!G?{G061@=>̓㣸{6éҌ~~A7L6geϜ9=qpvdֽ/\wؽ}#x*=B;eb 2.^-'1pT yn~O<޲{_{w0`! LT"Xd2Iey,}.UȊQY^gYHD&k׫dץ0LR#ڳ8yY&w+XI{'hN`&WA1 i@ |sH! yO*O]a}K`zUp>OLB0!oW>pP *+TߓXO<۩<;;Kn=mM쒲+$Q;oAq*?znWNp>5: u[ <-S7C [y@Eŧˡp-ʹO$SIaX!8I@!CYbKH ﯔm _-0XUQ]694LDZa ~С1X5Zd$:d2 L1]c yqQnGzf@„x'u|8 ~7Y9XY-|5ɋd]Lv$-guE##[M,Wm{4EJP`ĒґH$%9 >44l̿x+C'wV$\yZpOcrk2FN_o dy!4u^q,T$mقꤹLȞGE8y ;Jwwwa8ߝ.4 3Ilj:`C@ +6cto/L9@2FK0=}2|iq.-gRSTm^3S0ia`X2l[R aǶ, O^X,䀩-r ضJ**ç2 #`YFF$߸a;y_j bL뺧>xeYs #"va\Fj: ]lT˸ %OC &ߗ2V;P@E- uNe g2j IK-SeC=RTQ]p \EёJzڍ"EJ4YѦ( GBhȜV*SX*Q<!jS̛AyLJT^ "TslilWQѯem_A=&*?;;Z/\%% KMN0/Nv |G\* >j GmFؒ kSbKVcHMJys7y|!O?};1,Wm։R ~ ͆Ŏa>̙(~In*Vssg\+Tsx)DMot@n$ZAn@J|0ep }9v{ [{cᅧsGB3),׫ KMv(8m c&35?"K%>ގIWʜܷyqYLgCJX=M*SEx(BjqjyazߞjrNyV̠Dp ~hks'zz5@N&O;jҠrԨcfGk)R\H&Bp>Goo/MNN2Rkt1dtؠY.Hc%Uरn*1zն >Ŗ4/ɩ׶`!KTht_ NmpfخM,M0j3,h  @=@y* mYT5Pu>z=V@> k:rLght?oq =89xg7^G1vp` xMt 'X2X6& s-\W7M幷u6Б6ꏎ ݈*{ ex:TMMzTiZ9BDXcX+LVphUL<.0aK3yZM8h #֙T8_c#g,F/|s;#9왝j%~6 )~ ~(|;>iONci^pֵAԪ+U[ܠA@'V ~mͿ_N~|؇|Oڍ',—ixsϽeGOp;C,K^}2ʳKbqP|7n@:j?%,Y:9_ǝǹ1 rb$ KK0a~~V<~H7rXĝQeY0 ʕx< Ol[eApTw@2I%N'2~6Ȝ"PaRY ÐJh` ޽`X̠yta~_2SUx.0/4.=ʇDjv=_*SyMjTZ%7`ku?[T)QDD9͢DDF>ZFB wb#L8~zy +6F]j^ߔ(V(c0@P!+ ZzBTfPk+=i50F`)=k$4G:!멁H"̑r,_HRҗd5\e?%m5pD˫@ Owme8ipy^''v$:rlS4Aاu/$Lࡡ!8ߺ3 l;FC[,i[3Wd&Gwl7mK_4^_Kl_ŗ}swx~c1MT=( =HYmz36 1ww㘜/]X!L<gҕ1X|{_Bn\_韰MGix6 ]G^G-Æe::ڹ=Z,TZBwwj"P2duRrXxAIܦ2QR6o6q Ym<;E.N_!'cJ>WVޟЙ_@Dx ӡCA[SoIO_wf7q.;ǫHdS>q{ BXxsV,y|&/Ǯ]X^ɹIpcw•mKdcø_4JJ/Ǹ+}@Ɖ X{B2rRtv>[ܙ񓨱Ji0z"KKqB7LQ.TY$L}#;ks: og+o~קS2lvʾ.i4ٱ\KJ-_XahxO 9reb12M$zX,G6@P. !C?K%[MM׮E$e#PR~)}K7w~p3XP>z @J*}zu \DE,A(>榃5ҋu NՀU뿗w>3K]+," ꝃ\/5]w솇ʐ͹8vJ{1"NcyyW\GjSwjPg1Y9}Bg0QªwCNC"]~/IjD*m1&ӤIfƐZ&ZKpDr~E>yA/<9NmBE ,JfuI>ԗJ('I~ݖ,n^G_gp e#8s5-FR@5CɌ!⁝0P?QioUƎm^_pJeZD,ղOg^:ǟ =;:S8 qL}g.s:GE.^}o߼mGsof8%oϔ {Ѿ:7Fu7oVKK@ Fs<OB䶑!1!B:ו &Q{}iG!=,j|EmTœmcn>/P(B)@2d)%j5uZDK)b(5sJ͠"a&:x8q@O@w7 ̮$ͨ_I[I'T^Sϩ !| 1R T}rE}(QKKH&f{c]R/P&?TIO!ⅉ#G fRgh^ bl`hQ~t01tpXW6m_9cJ@+W: VM"BB Zϓ^%}I@-# [qCضsǪst ~?۪ۿuk6WpiH,De,fRUyX73z+׫3tMY.-V,e Tޡ:f53"d L+5N,LS{,O0swޞnwbxh P,s0>*Fڱ87EmWC^mîeZK( [TMd?LxvGH)g,˂eYƫa"X,e'zdxj{oگFLTӴajoW/՟eVBKKK SYb[a nMQ bʲ*h, \ϋz.}='&mI-ۏ6=L\XhrAf]&μ(qAu _:*  Q6$`O8늰=tia08"$>SE9_ ,% @lG(7ὧkBj9UQ9K3ob @27ZIlZ_=z@TSL)<8 NN[T [yM'cw!V:#xPuW ٖ#O7^;v1x~Q,Y7u, xZlCcjލCRԋ0RY汱 99W.|ozy^_W_lWNePwz06Z`[Z:eGpml~ӜXI޳m Ub 6ΏѱIw g0>WF^i~[RwP4JNJ׫lZ1xqq333\.۶Y#0 t|_eYa%qq Q@("A78tpO>ɔH|yQ祔a ;UcBfm{:uʍ^h(;N 6/s9"0`*їLcZTP2i4ʕܗ)4I{hL^!G}PJ)5Xɮr \5ԉSkh/VٮTujxj(BzHX,nC6IׂZRBu ITPjMTr֌<){F wDTs'fuhlm̓h丳=tJK?@OS|gP98xOM-ކkgp)L̰SvbL\BiZ1iZo@rGoo/߿ؾ};(H W)U Jfwd`D{#A> '$&3M3XhS>{Rzǝ:k>s󂺻Ii!ˉH|6LtPL@ n`fE\.i:}:2JTD5?Jv|oxbƠ47,UZT'ւLq`!uHfij& _硳4 PIZ EI}`BrT]QaM2!3)ᮬ=Mo VY#U|J,)}:zhrX.!еo~!`p?i[f \+sG>\qř26]?iw\^x;>$M21C^<ы>w7멆k0sV_XaPVC_zRݱ3o_ JhKl,l=,zZv-$sF*݇A޵c2ܵE臘.9|%CTU|tՊJkP! |TL/籼^\\03a2 LӤx<3>xjʖ@gZFoRUٳٳDGl S-TMJ'c.%}텥#g}OifyU&_ڈsѮڡL.=0T)3V(]7*z^)r Ka0HDUx2 4L] ˓#070SH8!CKvVfTj-ߣGO"צX/S{BGOōHѼפD]4-9*ނ[j-CMIX['XWCl:@tsIhFZZ`H2+> _ed F(ߜaVcmhގ'ų|sE|ɣ+<1_q8'6/q}clЃPe*FVF5kuNO+, uĶ|xoOZm"ZA˱1 /$|Ŭ3ejJ XV;=;n4P&J QTn"#CGז~V1BZi>d^ c'.7f<3?5*oVLZҹj&-sI%?BRay8jNZ17'71>:?s+Tp ̗?vF1U.@O Ȱ!0c 0SAhFOwmĂBֿZ(BM<AIp]^qzȭ}{u;|7;qM8${ȌCzy@rxa>a}xqj6 '&h| zD+^V@O|gcbO}ܓPܓ‘;wIn)[HiM$9|" *!#V 3!q<wbʯA8߿?`z{{kuH)Q.X,[NJȲ,H)}zQ & =Û2^yFɓKD=mہc^W'l*x:Urﲅ`T@Ǐ0}+|j,ޡPRT@h !6 ѷ4i.*Hbj~MKTc~CC hU(BcӠ0qEgΔk:Qu| .bմcB| o eX<' и1c&01HBs#x`7b|Z,6NoG1|bT x bG^ܮ ;>Г`r r}y'AzQ[ ̄+2m_*/WL|3O%#+0zOtPUqo E9ݾޝB>E]dЦ!d;6N͏P ±X]aH)^ZϓU*f|TmۍI&X#ab<|Ĺs3gC|vŽ}`dolf(T'`(1ފe,4=nowЄ_(\2V`]EytNszԁث?G$C)Ts!8F_sմ}{pD, iR 6B2=5KdMZ DD /kH0B+*ajqiu4ֳlj׵!=@Q8Eȴ *Hά{{GSҌo1^%c`mļ9MpXH$AuXwmOdgF8 VVV\D"!S/k2 ۷9ɐ8А BEsū^b1OO8aXK|eIO3XeQ|e?(4(D>&<52U)P6R%T.TLFһrȏԋT Qj^g`6%w8π }@*$ A`;,UF=&c!B /2* 'x >ڲ,b :>./V/<{]!8;wxlr'|&<{z^ <F\Cvm-ZE>E##NDYlY&R)XJ%|n64" D[[ueb+0J޴ @2D*Bgg'6o,KKK4==MBp&ahF|L&a]}mwAYr(pfa܋p'mۆ[n;w=DHvP&;hOeؗIaBH<74%[uU/O4g8 l_SDO~ۀl'` 7  ‚̵1nwS YCuwi,T]"kQ֠й; Ԩ&ym1ZF$ٰӸ;ƛtgMQ7Mx.wp{S N2IpvD8&A`%il+ gn0&`0ֱ&%++l}Ѳ>VY0LnkKbLGC.jUHoYegмh2sJ5M:j ~ C*%, l2HĬ4l+Mk|߳Y)̚O3td, sVX& > 듌2}NEe [;A@ړNmD Mqn\ KgY>8LƓE-P$zn? -6~k $ !8NS.c_ 3Ka0gd0 ffd2N9Uَz{{iǎmhbboܸD$zz /G{{w' R`U5I&gm3߾t@$vߓPs,<ՠN?l1 R3?doTI2dAgt|)CJՎ.)݄MRZˣ4JQ'pF ;ɔApekX^|E fggi R)rJq/4/Xպ lʀ; daI.G"t}aHX#K3{yB&I .dr(dE,,wt7j d \3.ӛ| Efј"DXs3hT@b:  Z?z4i날rmPh%T%V*Fٯ bk |tuXeRz@pP˲0,ޕB!W>i&uo5=DCücx7 o)_'o_bק?v*^z]>/$fg_9ܾZqQ`5=|\E#J 3&سg߽yJ}/kZ/~POZ+j~ijn Jq^mmmH^2I۶|," $O6̌NfMZe~ at`n$_zwc,b1x<.]==ׁ̐ͨjeSS{'Y^ :%͌ `Fh%Dژe k>of6gND + Ph49tΡ}{oݪ 1Un{Cck0^Lv'$PlRC[_.RO%1z04QJdK.c;|Na6;KJ Ž\{  vYfU D1cpC۱uVzo>p* Zp( `Iv%BN[Abb&Ri n+mF>XPWt'ۘc@7,K$}]tݗ  RV݌L'M}ck>  w}GyͪU?\zCHuARN(aPG[BKsβ"~ԙru{BӧW=~%~1U\GGK麂)(L4!p[kMޡ+ /d)_i'0ֈFvZW #w\8)6Hfq{{ut71<>NcccT(L&twjbn$q<9yP4JyV7Ř4Ȑg0F!!h 3Yy-n9fh_zzc^HkD/9ACY5ᚲdd@ԧ[sGmٲ{{{G? 5y!"BM€%)م]e?hcKgDr=0!&4 k@ X2H[ 6Yl9lD*MPja# Z 0/&bj!p3uJ%J/Ւ4-eKK#`7:J#3)6Xӵh@ZE3T A*Br/5xd-WR6)⪝bn_X?uu:4uY}Z뮯&hK+.J{ x#Y4%KCޜ{vRm,&zmLbTZk ۫e~ZO%D́K/U7u/z%F:ʶ>li/h<gy^|SsSd.h2y&ZStaaT ֶ3ܻqp >^zZ32^llݸPOPNt `i9&zOar");3sWWRk֬`kU=i6q=%7`يEacGl".Jg.k⎎&ttT3cdd@ORX,cccdxժUhii1| DdǐCR g'sn-$ Ze{ΖF%Y)I,\.czzXmkb\Y]BP{PhcɱLP :`?lizPG68s $^Q$`( Pn@W4*%LY` hlPh `ͫ6cp8LeA+BgvFP̶r*BߍA %;cʌ:lQcea@ p:͙y.lrLXx%x1NAY\Apn IwgP5-Gkt5ZnRl=>;=1"*]Zq|R9MaM+3.-8-$[e"n lo?5Wo_'kKϜE=ƥ,-J8{Z`i_U͢.,֖A/?= vlۂD"3SinmmD"PJnYrkm*MƩhd~ew`JdA.5mBĶ`Qb1ڰaybbtF9ŋqIbL&Jd8Ns<p8̦clEyOv6 %$TBa12ɱ{cۼ,' fvHK ]@FnĚhaiV$R^ӧJV?Էq(˻U rp "4lG3")C9N*' e`L@[$@4@6ATb-A҂Uxx(알ɱ荸nyIfu @s~ ճU3KXu\7^.0H?mXgK"5Z )\Nq ;ܽM1]!KY(A}wP딵vvssS(.P+\ƼOO]*3u(wnAN 8-w׾޺iڱm;S!DHXYXj䉏XGC&l6.ATjxQ/g["@A6Catt׬YT*={kE(\. hyzz`Foo/[PpX' Z;}=+ni~_ѱ)ܘ_jO/\-|vi3wy|ѠKbˬe.\F BR#urྫྷyd85ohB*D0͝]i$\*+^U!c E "\xld$P7q>S(BOOqL&1_uAc.Ւ1&޳+lԁ h4ղsJr}J Z)-VfA|k&vizI;&bβ:SSv;ZeB8Ȅ VS[)Q"n"ڶ :3YD{bEP4y@AP;6Λ\.W.8!Fĩ2DRWY,9ST8rM;ie{i9ˆ,fd LR.vs*݊kG{M& X[YRDR }Q%.N~#6ކ?xf 6廾v'Z IDATA":;=  7ί0?忥oϗ*ޣe^&%^^|t H\%{j.a#5iob??ɭ]Ġ9}Z(YڥEtUЪƖ;PDpua˖-PJ\.G3g033H$7x#44qFy^\\DXZƃLPiOY[.Rb@a#e5Y1;^e*o!k^>'vgY}vZ~ҖV %_at@ʝt|..UR{ ]"W zpaQ`FL4 EUS__ zn \wZ%tS+RfPEF.#| va~ >J(@CPP.+~x3U֪]rٻY䞀Yҋ ƋEBiI.Pt:m6ڦVKλ! OD"6+ıcX)E===̱XNɵ0ھU'kh8Fl\! \s@ӵk. s 4eE =YP)RXQ(Uj`56^*OLF@Qk@PvNϼuXv{qXy`vp`;a RTh[hjGwmvZсIDf Ҙ' hȢE#X^ 45>% !Z%]x9n/,Ք`-VJ[6/Bmd+DYpˀvkýw'ʽ@ՒS2Ֆ`#]!Lt1E<]>jYnνq㾽رq_-ں7CxyZo:~)M .adpp9*DKKRj8^,8TGE8ƍ} ^ ʥ]aS,3BVJ'@J ]o+()P( +Ik \(B%bE>+,-'S2 a8¨95aN+X)@ͶC@4~GT ===tuQ6AO4?;;ˋ)aƍH$D"ʧfM>cSS+&:2}Y"KjJϛJ߾ ,gn5݂f_߬Ye4Ǭٮ~)ԉ$*#("^IW@' VTc   'wqꠁ-[V]Ad9dBȨY4,PS8Yݢ&mv"wt_8E>g0g W:1B`eHj-Y4[):05m`i_8eJ4򅹛k+Y|sbSջ?&B$eW;ex7Qv+l8=yDz催RzGazj#?BGb9]*08+ef\Fe>]h-|9'^ =m-,_`TIY' @@rT(a*P*+KJ)L.z V"Bq0NA/'KipղkIO)k !D\v4l[L=,R'qssƣ\.#cvvrgffٳ@L---qGG-ʆ2LCY#e͍~oU,ybbw>@^Evi6fa|J = ۊAl`@AƚdX}ʿnNJ`Y/0 Ln%l.tℍ`|CۙTkC-% g r aTZKtx,(9@8K9! LGbU[6̠ CȲj5 .U:" @@>]wo:iN@ ,"jfI?\3/ x˦PΏWJ{gN<W?F^o>[x9K}p2牑3+}\uRRʏagpqxR|/,eh.xs$y2ZS` qc {d( tjLQ| X@:"k[[v!"6ߕ"m˸S 6Ď\.x\Ji)A$)&@hBm"kr\F\\.l6Y,,,`aacƍA[[Sر<ɜkӃ.]m1dY >=Ұ3۾B~."t B}.4E:u(m߾ΪWvi-_r*1QvYҠx,>0&4ceFVd'@ZA#R{mT1b`qք NB % 85Ti/v G,2H(eݒ`!S!6R:2H(b)YLWkKYE+Bؽp-zgWOP.6 .JGgρ "iD]xnsqחӅ^nwy/Ώjze}b 'x:u C'C9'/W^# RϾs`X8rq|GNs9NA ZY?ڍ5F0Vn@˸57ckizͯgooF^y|augRdÔH7anpwތ#O"m7cr4|NqаT7R  GK9= l쥑_s& Ē<+xnM<v  '7ϙV/a\Z!RKHAR@FXhS?]!+?~ ؾʛod3#\.cIl f?%MCɨYet N"dV'H$=ׯB* 444dT6U\׭[W]u0̬a{X_vX,s壂|7>ٌ/G/XgaL_|053@H^Xk>KT>12*d36uJJ<'PwaBDʉ BH\aoo*boXeR]F9N0)5:.]I!XZ};$75ݲۖDaP8pKʙ^nw7f(?;B/fO' jt5ːDn`Ye*{!iSsQ*η7gz۹#Z}ֽoIӹ?.dpi~gܵ%h=\x~ (kP!;6#N 20_uʄϥFIzy&d \-Ag:D#0 Ad fLԪ1%fF8Fgg'uvvzb|>qcǎ577W^IxD${ P 4땱FsK!p`.(6H LDd-rU/G 1CEdϱ;~6^񗺔#c,vF:/b=M^-0۾ @r)QSNX0%9^^v".{}|k3{ָc7`+@j3E_JY#yZ!3>to4_H9ãзd?{SbTo:e{Y UAꙧ=t{|Y¡(Ղ8i/,6]^qjesP֦߼(T'rrTmG{` p{60w}X9aBRZ)1@x%Hxz6xYLMP.C'5KZ7 ޺?ořg[B Zh)7%0xjѨVp*E!tnY:fs\JZd첣dڌ5tE /!eKg,{J*33Roo/bhiiᎎ:`gNJo.GlYrd!|,ϛʄ4Bt|%;+\}\zbjܘXVYxfBiK}Ce+S#e_,V Ñb~5`h%$pc2Yܛe#s"]΀ 2A5~(D]$㘦i<*H%c9ѻsܷ ^2o^ .q$cDLhu,@놃&緲+=W,&b䲵|Mb&\=:VgA+Y X63*Q: @%B|6S@%iXZξDNlGibR%ڨe1=|5̱C4zc̱g>x>ħ& >;9]ipU丹 .~ p,څMʴɶ02*Dg3>۲K1jE,6JgLYk2(ja2ˀg;PHwAGۺ*yuk[(($GtG!y|l=ߎb1 b&+C Dꣂ N= Qh @+dg HT'"7#dg h$3uJe>)\ܙgAw*UE F0\ve1>ePՑ/WUN L!eЩx("X2[ۯۦG} EG{{{>ʇM7ݺp;O{~|Ӷj#>wC{ggy*fIDAT T bIly=6ޫݯ?t&ys8;1yxE9nl ,cCZGb]h1/b^rvmbwypZ]Cl+d}b~/SNg.An'9s)_1A`hil,X q -J=Ϋ6BǷ9RoH ʋxxpuy^C_;%+#+&h=x]6e.:UnR}KI(j:*R68NRn:mxy{V6Kgrg%"ظ_oD;=iPܰ2J: Fx3wֳ`LL.˾|DmKB djX@Fٜ ҎQ8\Ǯ@ANRzQ݈dqR$YXU@/Hn\!`b*I> aZkG vcrW6;4>ž~uQs{jqpwcba׫_cw+}Щiѩzt4aiJ[kEXZHO[c+ڽ(mo±ǾN)3ݻy7ݞ{ ׽Cyɏ?qn޳=yu]m\NKTҕ2HU=P~(n|z\ͺ2pz)Mن: TQTAW.o p 0)- j!(#X()VT+"[~Hd#%zK2bYFw$\6!1;OY}敱>d$S1 |?,Y* P'){?mC5Q6z7B9اj(D,fe]pEYy* +7S͝_8l^w08kn{+zΎa?ǯz8ESiN'ܛF,;?;!T|>g~*V|d^8>&O^*ϱ28VIDW_>ت쾪1X,#)ˈ]f&O6.d~<#Y(̰ЛI{]&q:2 +~.̌BeOj~IR F.G^w-E>߼k8MWw߾fxob9;4KP^ATJL McM9/(N#(@)!9F (;n.޽jdB:1<{\ђl>豃[goކctdc㕰?/_8~'R=t )< Wd%kUɎ<! 'rAܲza2j4^41 s<.Gw(K=K[\VrZ=diPփǜ2AL,ȶB@Q&d") ^> ?*F،QMYҩexFyu,ƌI ב#`_؝nY;6b ɻy\ c0u~ɣP8B;:p˘mGxE֗ =['eu"ՌL*YM8=|`8J؇3O}<{=m4&4gZ0:7Bn5-<~/5XX~RX֣3MxGCChJđѕ d +U>_Dpe{`$_cD< =nG .B#^v< e.JFBNnjIx@Հ-3-$:|a¨([jY"(oyDWJ6XH-}Dg gcuvJݒ㫲+O,bud7$<%1F%XVRU{+Ä2ejyY Uq(\a`P0??,R9D/r86Pg&mE-'\,lӉvV7T$T**d1=Rկދ;sO]ޖ6L\C8&:s4B<`3YY~ ʴ2Y};>YH[v`>W^9^?[0X,]PUJ l Zc};EVafPgff9sؾ};_tccY7g)b1{_WX.S|6E~'2ǰTxc"2k X.֑Uʕ첞CKnWym탮q S?PzBucL$CuF9)QJ!h԰YՏ 5R 8}W*R07X\B iM.>t'?6^ŸOBb9>%chNf-Iak?<GhFa>Οo+*’|VJ8q|~inkki=@! vfl+\~$r 87z5AlTN-6e6g8>?_%;GjܲV:c|M;~ぇ8rhr-PgV%Md3  áO*eRrnlu8Z0[%m$ۀ̎SlJ%tuu8,<77gm GxaAtY81\^DQ\ )%Pt۠\F P4! ~!Ÿ=F((0%=|h6Ҩ(j@@Tזm ٞ |4 ]4:V Kl/ &X0{^3Oe ֬Ea8ɤX:q<Cs:Auz.;g?=8xh~SOQ5 _bp~k/nXW+2R[(Oc+OTO2mvYa^ю$<8e3"yҕ07v޽{Y5v q0ft8?|TH1G]  cn$ dKU UJ0> e9,D(Kj5=O,X;*]vRtmYǎ, m?c=:LB J͗@9 b*k'rO<mY`18a3XhۆPvށIfZz s8N9h,0S2+w!Co>j:Np(ܧ6 ?VR) %&OVXэ s!–kÑ'Ѽfs|mXkL9+t iCtS>9@-(DX#T/C{|:}zn<>>իNj/ҡC\,* -pss)T!b%D)kH 'fv:>Y[IJ|@IJp>T܈/xٶ^pAm FdBw0#g"`e>s,ݟؔ"9ܤfvyHbQ0z/imرxW}cvѻމp_ӵQ_Ff5=~7C l08Zt2ޏtpS-v A( M>^~'pI<)}W+9WNsљDSSlL?D>3G/̞p^~,pݟeZ>p8ΫVq9zKDHn~~O}ҡC}p!)p @ ?X,Ѻ|b#X=4bReYM˲\u8ōKoKۈGz:-AI ͗Ŋ_Зb1^Yb`Orۍ'êC`p}xU6V%A&HmYL%{M? -|;O?(?h8u=p0зܪNg9vF# $ׁr}kG }lYȋXK&s+ѹ}BEx+aah [ML?S>4񦽫R.jL/ϴ:  2.(`wl C4 @nj"UđH;wqhxI;Ң'G0xb$mC0# MhJ3f,`NzoI ?@?7U-g*̯Vm˽oQ$1:09C4\9`+xՋ 4]2ǧ2lFeʂ=Z^-1mƻ׏g9G_>S8H"kAaf_"uME7eϩ+l!|l Uy!ըXL;~Rq2^xihDqۘlB:Q8nw=H#]ɊVW>Y9W̌J3U)Q%bb%BPOU?c#<Cra#s~.p9NXq?$-",gɈ{*ভ 4cm1;B龗VZ| fs{sxaô2^j&{aJc K&%S&o@^XD(cQ;fgg/-x[@qa`sFqXf'ar)^\*ڎ P5.= p=0e,fJY\`9 rGUCPvoT2VdʯGY;_Ra6CsՖAr^*hS1vE23޽]a"s琛'xf PL d+3@U@ NP" ~O/`^߫o&{pWnb+OGWDŢ7tI5^9\+ӏ$Tr FPrvǔE E P.,%i3:Wڵo7p0N{s6Gld:˗5~L3c~DYtINϭ?ꨬPye\ U\^{]@ BI.jDIȨjfNB +pY*`׭}>X(_7ȝ0ˠ7Svp$(9ȴXfZ L Uȡrx{Ф5teRJ,5{ʪr0-21^ j6& q b)Pq {M 5M@ pH ܹpTN_Øp ԮՂ+Xp#t$p2W*fPAx B:Y2'y1dƩb|A^63oƝ>Lxx3#!sx׻RM~۩X C2d:~(ǵN@ P(1*~Y?vuM4 ׆< ΋$d$;'= |[.:$gɼsxB;)=ZQ^ U)jN&annw@prP$NE@LsC@D (-  Ey/2J~>fPV;Raj.^9`+ӏm%G,iY(|ΝLI[ XF-$im-R.C A0vŠHv3Iq?ΝXXg/,̲wfù}^I1+sV9J* F@6͈M3l6 -a<~899;w <Ċ+t@^ Be9'+?5G;p:U*SW0*4.RꋼZbNABU}b+uݒN}Eab\P hWN2}/Wnm9}}l`q|{ \L_ϯ ka7vs, ;!qcFqHh1~;+A&c*@֊b􂪪* L{J"GG}NO_!vv&\h 8R-(^Je}Pj^5 \6uo><}WZYU\ߖK{<=QLSG% I4_ia>wKVenZ3dIENDB`pytango-9.2.2/doc/_static/banner2.png000066400000000000000000002164671316324373100174600ustar00rootroot00000000000000PNG  IHDRX[sRGBbKGD pHYs  tIME ;# IDATxwTN ,M@6T4j(K~HQl  #0"JGz.le~ΰ%p<f=}9G,K/E3Q S?t:ёz9tf#|}j&ݩ̜^f$ 4W(000008mMe ԱWj< 1w:/#v^w@%%AlU_ kj47!-W޼틳Z֊Kk63= _"XhC+ӕE$j`,3K@?D1LuSZ [kn肀`'.(X_fфhh$AB„qUщIdZ 'k i:nAuA$ 1񀎢x]'ӅV| BXIz͗t]Ho~%ьi蚆*9g̃3A }g8fBlq (dUCLVv]D1FE4UE4& R<jEu5}wA0'3>kYBc{ѭٻhz$BG?D'EX1^ j'%Xl S$avKZR0h5+!3F)~ ef'&-Ғ4Pi%**#QXg˵  Ho66=L4%8r6"bB  REM$ߟD'ɷۓj"(Ȫl$ k診bj308c~?0/NX?;vG WE(T0k\4HN7o6dcYox*Rb̈́mUW,FŸ uNtObͦ1TͮǑ gmEg`p ʰs^-OnQ U~w`|JNElYV7B)9wוX5zG!'p3d$0SRh0,IC$[HMi6 BP+ߋ8{s/%lĺvOu\b IgC,&>d Nȕy1Obm1TWy]kxo0[,ʱGaEр hDQ@D]ce_3qF4tCݷտ''#UHK_-5>,f*JQݜtN1sl%YP0"fQG7 ptT!LbrtQ 7ӴݎjyYӫRYDuj<2&*7`59tdM``NgV\}%#4&),dEDu ƺ%|Ã*- :%Nk6(p?(ƷY4×epaX:(dBLZ At.1*rJɀE&P1 d|\V(,۹cte lG<}r=kmm$J+ݸ LN@ѐD1n2 eQZ"!)E;1N4T-,8'Zlc ]Ei\nA$ϳ sbI%!ɆXxbtqĶ*aIAuMl6㬵ƕS>%a: *_%XN\ԃx2iXg[uk "Hdt!4/Iih^K27.ab w%h2VSQ#y?#LϺZ8rnЁrUAPF!fċ5 +/*B%cRuB 񲡟g=W~#iԞ^:qЂL'=c+O n\nC`.̸ڊf-F18_-A ~нtt?B~;R"rAc,U&FW500{]8[dy^` ucv !nP?? uef Bo{:V. !(~дr]ױLR'M@׉󒘘H]fEQTE%<LbccBO2E>lG fI]q8hF 8vdYFU.WBBujNf Ee{p³娭V GbC108ь=Gޔ_~zcc2ضm+K.c׮]۷ٱc'GQh޹l6,Yڵkٹsyy=t, o&{CMg?(h$IO$n$kTVVrxHJJS,EiV #]ӪbauhFjj q:wLDh"Z DK[zBX(/Z l6+|^s/0ri٧|RRR|FRq8\-qq1|Gt .Mf2I,Y9`VkZ~-)9Jmm- ¦MIJJl6і-[8zf;<1>Kj$u*+ʾ.ܼp:=X޾g_ZAMM-GtҰm8p*$IPUU ;;cÜ9򳟍!7V1.L p\ >?>III\rŤw(/Q9v(}G ʕHJJdǣi:uՒLjj*N8E! Y/\s uuN}w.ӦME49dk׮ȲL MIJJ"P[[KzzIII@YY^,V+Ȳ!ۇ=^bc˥<̙Os뭷0tPJx<Ɛ먪JII iiiA߿_P(//񐙙I\\\pYKrss)++gwFo׿.WL&|>\.$%%PVV*!)) z+H'99˅nGrF]]qqqE;ݻ4nݺ*gt܉8.?NZZ*TW rz@NNN6008sS~0hZGUUV+:eӹs'FaaaE ɼywKaƨ#UU!''=̢EKXbV5k a߾Q1v,\r( xSZZʮ]y٨K(,,p=UUnwjjVX߿?6M6p"/My饗׿VP\\y9`>vi$TUSVVN=p:8t(\bcc}6l;?JCC~;w}NADC< ݻJjr!N=wߝK  |L& GaF6n܈Ӏ$ITVVs_I`0Ȕ)c-,XWV^lpCcMP^^1b0<]CUU=\ٴiwC,]Q0ˑ#$&&ҳgmO&++/ ((GCZ9p˩III&++N' ŦMѣ'}oYEvvWS,:]q㮈 QEb)/zd=]ޛb„8p:4MekHIIgР|MڵիWdR|X^x8V7EuxhCCλѫW/VXI(.eٲe;Vʎ;ܹ3|-ׯB{()9|_y?i(Jc"`eV.r#"GP~x=zGso~:bb/qpݿFf~kuΝ;f~?&SXX((BMM O?i >},wshhh@UU$Ik7|Q.p500_O ߍ!,ۺ/~q#K쳅bI [nE= 6IOҰl3x`hab1i'4@LX,hvG /"lʪU9s&lgʕ= 0n8RSS>}]7]w^=t:*³>?l6uuu|(o~jILLf"&DQp g&rsbXv Aul0ү'%%SVV/y`Ip\\/>};w" 0-X,fbcck1P>))ٌٳ'6ldL f=,qڸH8 >l͊,x<RRX,8$11Lff&+4h HϞ=+?o߾X WU:dYǟDu۷@s;6E"VUÃhԑNtv @~ѧOo c:iD:μyDL>oqP> tudVZMMM W]5Q0:?EQ8XV?ÇzIHHOTUUkn&M708cl6 /r%0x z($TU(*y hNCCVaÆ( ݎ+t6Ly,ihhTbcc$11.]:ϿlnefȑkՓnݺ1tybø_3vuF}}=t횃,+x< h$xϯK.fÆ4hcǎȩ_…{HOOO~\qeQ{w@ H(v p:~F͈#{lݺ M4Ç Yh1bРLl< ΔԈr+|ڣKNa+ho8"jV IIIhFUUVkNiiEEGX̜sN/:\I())i%1L;}HHH$66, ٳp1A{nKp@$HMMሡ+\#N}NYE*+HH'))<;(2999dggSUUEZZ$QSSj%!! bbv $--}RWWd"!!4DNEEݻw? RSSCvv555tԉXRRRzv3} o¶mAevӷo_:I'++TihhGhFIQz@BB<ǏWp؉aiiiÑ#Ŕ#++^z"ǎC%())&55qZEŨ#(rp!зr]1L>;Qׯ^AXt6z$+޹uׯ!VeOW׷m6}db„_4vrM;ȰP4}zAfQA?@+n6csuD=@'h6+ O><#qQ$2oFEN'k׮kn(QϚ(ҘX,f.]Ƒ#%l۶Ql,"eb'tW]3N`Yvz$"Cyբ((2gⴂ!٬ocOcE!i \1%%G3q4 ߸f?a2Idd4} Ǎ(,ˌ5sΩ;&}״-vuHJJD6(Orxx63̰ l<h4؟,Cu" .hQZc{.pd'(A L<'q8yAkl6ݻAst )ԩ]tin't^k\XXMն$NkRiSOOzqC:<)^鼧*[d(?觸xNXX,6vYk|0000008 *Hҥ+gz Y4x}#C},$'4-,15GtI +)9W'>>X(a228_ĶmX|OXdVQ!6:{g^+<dzm,yBXt:nVLj㷦(E?ޓD6;/5qleY g\l@J2I}d w$+--e7iI'- lڴ-[7먛) !2]['-hhhfa2ׯnmR ># T&P:ld]nD. zt铉Ė @aa!wq#Fn7>8EƌÚ5kZdmo<~i,444j57YYY\|sjn#֭7$ iZ4f ѨV袡P{(Jt" j劮Ꚋ`Qe=`M"5NMmM0TWW2Ԡ"z9!ثDuz'|g5Ms6Kj3j(\58q"3 g<:6݁$ Ug S4]'W!Z^@_$P_"qcx#}s; Trm\ǿ&%\Q[nZ%I[.)a HVcOm})D@0;6ƛF籱|ut>J|O k}zzڤעe'KKKy뭷3gg歷bHÙ6mZ^/ rsi&M"..G2k,rssyo;xB k׮lݺW\^o3PWW#Cx7:t(<0108e`0g6 rK`x Z Mт:ZԠ&\)q$chq+S@ JeZj4qkYd ri~Ӏ%Ab;p,K|v-_#\k y5,*d֮*r KEEETlSWWnGAEJ9O>||ܴ ]w'N$///{EE='Nd֭3c &L/Ù3g|l 8p ,ছn"++ &0fKdY?dرL:?O\{}0r6d[}QjI<ǂ5A"X!YiRcū"ZO5, FQPZ+A: :Vkz=ns1ٶmG{@BBBnE)220FQT,&9]?ӹ;]sŸ̴l>#X{dJN%V 6YףEqrY,:`mΠ@\c+h()^ހIdB* 愶/5^voAzO[Js]P@RC*! n,abc,TJFnyv/2fΜ_y'2dH+uV^y^z%n>XO6NUU<P`0J{qll,`0W_MQQǏ . ))MLē޽{1L1yQ__ϱcBQAPXXFUU^/@`0W_巿-$Fݱl\y啌3N:+D[<"5O;bD2JbDVp ~ >JW7`K0;6g=?TWWGoll,"4M# E 2a? 7#p8lIII߿z6mZag=4]#sztEΝI 998,V+1X,Pճ=GޔbN-nwUAp ylxf҉d&P]@۞95Y$cAi #H`Ⱦ8s@r?F"{ߨ%c.Ɔg =imRY=@ 鄼:C @ߪÚ(QC  $"K= 4Ϸk(mlkèQX,v;䐓 /l%~?se툢Hee%& 1p@t]')).]IEE|=ٳٺu+&--`0HNNYYY;vpM71h KEE>uuu 2BZZ̞=Rؿ?cƌAEv;#Gk׮ҵkW{9VZ_LbbbBq2 ҇uKb(Rv:_dP|:iXwQ/@lg3# qTm3zN'$J]wYx1 ";;8yꪫb֬Y\p,[4FMEEs\tEHD\\}+#F?2zhe ,IHL$ D,G'@, Ue_n^u؜rʧ=;zĥ\z5$'TN)M"U?B #zKq{|^ym 5WG=Y>ޑ?}*[{ky|n?dXzFi{K5ʖ{m5MﶮKGld``Ú:ѣ'>/=Nev3.~:nyl1lgζ" &d}G"h;CvO:!tqvpjb<͚2&SGV{ZΆl/Ҟl]DDG֤:ٵiVW{[,B$: EWӎF2 Ɯ,mwuN?fw2Ѻ;n:TǴ%NEIl6ߞuv1iAAv:ThC200000000! e````````,C`X: "+&tӟDSy~zw&Sԥ#4#eWUzTUA«ikkkqݧ磺:wU#upݸ\in7fux<%꺎indYnsM۴{EwHtEt:Q{PUU^/Q@[=nUl6SL!--YA駟& ( 7cǞ=5Mc۶m̛7PWW |>V\IEE @MM ׯȑ#mv:>_>#Gb[` <^x'|<o,~Ν˞={p\JYY6l`Νюr_rhزe UUUvDW^y%SN;dɒ%}:gD=5UUUJ^^| 6mرc@yy9EEEX"j#UUo)((@EݻwsСvEdbڴiR[[n]~;ֹD֭[nܸÇG=N-VXXH||<'O&11/ǏG=ϵx^{1 @ff&>(#Fzj֭[@QQ|wj*ʨW_qVv|?t⋬]oMoXN{(..{=nM6,C`Eq8ȲnGu}]vA(=,s50k,{=~m.Rp]wxbDQd߾}L4۷_yꩧ(**￟ow6߰?Cnv-[?O6QF HLLdĉs9{mn?M@}}=yyy|nj53f0c z~atҥK^e9x _=v۷Aqm_2iҤVe( c…L21cc:t(̘1-[Ddmm-{f 6e˖QTTĝw}L{j*7oFebbbxꩧعs'k׮媫Bez)Fɺu8x =zGe߾},]k& uV$IB6{{qomd5000FSLaѢE :ٱ1lIKKcΜ9ۍk׮7sF=bǏ{e92*Xr%n:ks!"\wu|0m4f͚9z+ߘ>}zEΚWNG~G/~K/6V#ޒbX,MUUL&SM9in3qDf͚EϞ=[v@06o(йsg$Ib1i$p8m$骮[nmTCMIDQdҤI\}L0qN=@~~>=zhq;="6mx۲Yyy9vqAhŋٹs'>wQTn6I6=! TD J^Ћ/6D`CA)"l4􂢠@jH z IDAT)}Ganr<Ssfws=^AP^W}f~znʲeȹokVV5Bk3unT*ZulVvv6111n˗=kΟV+L>uy|6##hy+{8ҭ_b&LٳgFպh4reZjU8vݷ9bAT)B?wlWK\\KUX,,ל f$XW>Z=ǎq뼇?_XW{7Bntw?4 k֬;ʕ'KVUw(xll|nTT&B2;\ 07jo?FaĈus7M\v$i5nإWM}Q֭[Q\9;yf\4i#-Gqպ!gz*W&? YpiRe˖uQ;vp9rrr4i֭[jʹ\ɾ !VC~_!Ck/C*Owrw9;D1}:*If͢R& *j{nڿ?=ӧOev;Ѽ㏌?&Mc3f &MYfdff2oCV+3gΤ[nn.I& Po΢2%%;2f6n܈nb`Xҥ ;vyTVV"I,zmdŋ;].x$/K/ǜ9sD$ ~;$%%k.ONn&""B9KZ-[<'x˗_B`ŊL<NΝ]D||<111hZ&MDYY'Ndڴi?~<Ǐg̙qFMF˖-iҤIon^ ;;[^WJ޽jh4V+|ӦMM64m￟4^u.^̙3Ȑ>h׮DDDF˖-e/^P@ jœ'k,CۊA?Ч@BBBkLHfC08Bh4{7”J%=z૯I&.C7+p՛j;<(8MA^P(DիW 33}`C(I^^^u=2Lxyy8vyv{-?///^u BvPT5I0 T*T*,zɉ' lFR%ǑNzT*w +~߱ _/qq-WUT͘C8quhs{U{)ڊkvJe5BA1=_)r5`;rpsq܅HP̝;H}]Y08!\.Ov4orսR6$mT_y+SC_:Wߐ{!uw\ogCls@ jxb>,s .]b5D[sرkF5kjLMފ.tjj*SLa޼y\rx]K.QQQQGԶљ3gXxܸq $--N<=kU$mɓkY]^gU;Gʱ܉]I_|A_$rrr?~|aP(ݻlKW0##t:owu{㭷ޢʥ޽{7+W׌9K/h/wxp1\h]wG)))DDD`6ܓ-`\MS9T{뭷8r >wm`45j?3$QYYɬY3f Gu)o{KB?OFF0\tɭd?kۣ1WA/_x; EJM:-Z+?RRR8qaaaBYYhZmڴ4Fmŋ@׳sNfaƍGrr2~~~l6rssyG4hf|???ڷo/E;v G?pq9/Es_;Ƒ#G(((qK/k.>%tKiiܗf^gl9rDh$;;???və3g W ;;ÇӺukmFBB\r0سg;#j o<@nn.~~~hтO?#FԱBԩSdeeqQ5j6ÇNzz:-[ٳ(JڷoUUU?4rssXq+V>iӦgϞ%55<5kR9s'N˗ +W8r.]"::R(/]@"$$\7$Q\)S<f[Wxp۶m^|f̘! sa2x뭷0 XV._\#믿ٹs'>ӦMܹsX,rrrX,FBBB(++jٻw/ "**y\M[Gy^u:BAy衇߿??ǏCRSYYhDVc2H饥[/&77Nff&v186v0c yz=[V do>JKKYd ɓTUUŝ;wex7z=UUUu!>|,u:=vkײw^T*NO>3go ~G@GѰh".\$IL4+WCYYA:N.GPPo Ml6׿BHHmڴ)S`٘6mT*ۇngժUP(t_\Y\͟?@ƌ wB+W0 t:BBB0 :evYPPV%55+W6vQꫯhСC,[ B… "T*7nNNN_}| {ӧaW9zSJh4 /PUUje޼yv,oj۶-'++;jiݺ5\r}]3oqE͛bd21tPrrrnoVވr7n巡j5:FVV/FPPPPP;,MƉ'o4icǎٳ(,,d\|rx9<;|Wz"wlݺz={c6 ;sΤreHMMn˗/gʕ4jԈO3fchժUu]riiilܸEx3fDVII @Trwۻ0a/_^cCkgVE_fG۶m޽*,,pDEEѷo_lrsdgÇINNI&.RaϞ=TTTsN BN߿?qiy~a>#Y8 v; ӷo_9cǎӦM|A E3bزe rQx?>111 ***ۛÇm6ҥK~~~rQWJwӜPA%%% ‰'Xv-ݺuo߾r{tڕ{woVBl6{n6mٳ<(w҅CllK۪T*|}}ٹs'r[t ɑLIIaСӇK.qqkܹ3gFVӮ];پ}; .ߟ_ .?h4zXt)IIIL&JKKIOODFFް'fҤI9sF~ɓj*ifƍdb֬Yji޼958 uz?/_ܹs1h Zhtfbcc;v|-] 6ԩSr<`nq#|:܎_V;BF\"PHNNyyyl6L&| ;wfnU*l6.\@ͱZR9s&5e97o{Y9rec… ҥ ˖-cժU.˕|aHJJB~6r^U=MZjfFѣ]FwNvfQQQ^wbXlll .4E}B@b6ywHLLd2Ϯڜ:uK2dS{鸟:LƦMG.s.\`޽ڵ]ؠn}8E… tx{{`5ko? {wzxeq"@4w.8i9S.I&̝;{w\BGPx94V=T~/_WXXlJ gٲe$&&R(//믿O?\T RRˮӧOӬY3Yd {VOGGEEa0/dw^';;b;?OhZ(..fL::\hؼy3o6EEE<3éSdڵk/턅ϖ-[HOOLJ]vfϧ~*n׮}WF$6nHaa!M6uoL￳m64 *#G_m6xܹs%22P~W/_ea X .]pY.\Ȗ-[ҥ J /^͛0`@ɓ9z(?~J4iBN5keee@u+Wb fʕngDZ\Y&%%qU,Yݻy衇X,lݺ>M6ѵkW9O紧LBFF999?̢EXt)+W]={w.mh;vqFj5曔QYYɊ+󓽍=z믿>СCtСN{v:tlf].]p2o<-ZD@@͚5s)|||ڵ+&MBڰazFƏ/ڠmY^^.oZ,XEIHHH^UUŚ5k8{,k֬!%%E^Vτry>),,dӦMxyy.6W&::իW3|5jD@@@yyySOm6{b"Hir:uDPPD"""X,?ݎhd޽X,ZhA\\z`6i۶-QQQ\:uVbbcc IDAT_7#n$NcW^ĉxyyVdeeCnBsJ%:uh4rA M6%<Mvv6=z INʕ+$)))iӆ=ǽvrrr@^^ǎgϞh4vڅlщ'!66֭[|k`0PXXHhh(gΜcǎfߏh$))I=zDZÇӡC((( $$D|2gfܹ=γ| :v(_QPP@V^w]w1|t:;v$ Fzz: 򾩇b`Ziܸ1ӤIy@l6Ν#77;R8z(:͛\۳Att4?4:b!))@zܦM&{_qZ ~X ܅C1hYWBW^!##3gҾ}{"C.ӭvο!ԔkCq7Izq^vv6/" Łd_W6 =ũ$#G0m4Ô)S'zS)dž\awyzt:5r;v,~Xt < ۭvvrp?Hl7X)E.DqW ǯ{僤x/E \ k2vWކ>= (wo G&MoeX酀7v'n'))I~vECVCԐ{YC)Okh3q7roi-wz@ ֿKCF,7+]ېA⏴ g;[GF?n<&ω? "L<075>.#!}o @ %@ @ X@ !@ @ K @ jsTO>Vu691H_&kw+ncݛ@0DQR}[px9tnCs^}i7h;\tkWo-Ⱦj ձssp@ oDx\ W.3p]|ۓ8%datXvG 7' Hy.]"hT5l|-|ZoߟG^M͜:u;`۶KO,䑘؈fgɩ"+ƍ)W2$Kva^|Ym`I mHe??ot:7…Rڶ `p\ adgwo.%T%veG^1>]DhH-YY =نbJDxKK |ض"GȖ-#zU/ }c0c0Xh"*iʑ#y4n%*Ownn%;wfPH$'7# @y8QDRR#ڵ dgW` T2*jLzz1ؿ?li߾:sJػ7pzh$ITU8u*Ε܌*L4oS@ }+$$6l7g=/%KX20&ʩSW]zPf0׽N֬9KH%K'(*b=GP3UUi&ъ)S~֭X>cmҥY0*K/Nvv%ĉ0|a  ;vd_CmcwdO> /_j+;+ڕ'#_߃l|,+O?+^^ /nSQab4/wq\ ӦϏsP~U?_࣏'Ü=[rʭZd<'OV裂bߩ0׿n`˖ ܙuͼn*iiEyTfJ O&|+@$T x3atI~ۗW_b޼V;fL&+f ~W L__/JJLeyf֭;˂Y iiB\ `7GiÏ?fb wlvY>'"—GI̪U2.:k6lt^mLTN$&&oDBrZoƏZ)P'oi$X,g3fL[l6;6w݄3iR')!5ʕܜQڰ{w6 qU9ƱnkStЩS[15抖-y8l6;: Fǎ|ithfCXlVt:?|@oV;J i[oEL隷f~ VODe>TJTV嗋(dBYӧ[-;=zDRY}}aPFl- Swat&t:5Y TtYskU}i<s1ߟq4mbȑ h,c Xl*55j~!ѣ={x͌Նw ^,@ $B!a2Yk 6^^= g . FMLjEmBHOy;-v;?'a~r9-8ڣLrr3-jL˖ǭ4m_ZJR]f'<ܗ=ϗU=%z5u۱Xk  ~'?=}3Ŷm#i_+@ ZFD$;ןc⣔x=L~'AAjJ //~~b$0PMH1cSOgVw.O1nU*h5DVPo\bb\.ꮞvoc QCBB(  x?^Ė-8q sP>O?;ㅏZyTlv)]dO#<&ΝNB[*ժdA1bD<.Eaxvо}8O=?eԨּ.fQ+嘂}xڂBV0|&X</FN`yeΜ^لAAjZo LCIM׻ѳgS7੧V+y̙s7G0vObcd[R8-w=ĐV$O)~{?X&L` >+Mhi<NBB0O? 99|QoT*sMA5l!D@ "gjœ'k,CۊA?Ч@BBB=V1WWu<{nn%?IE^lѣ>> V ?OT^]NE f)| s㩞.gԨY/:|:/Qh4^.]_7[~VL& f$M(w%%gggР87s[U3Q-Fgodͺ ~߱ _/qq-WaX͘C8quhs{CWn6ܛ@zd ēOvp= ׿ADv//c.O[6\IX,6RR)/7[=P70]nW1xfsӹjziu;:,f55)0zt*&>TJ(J"\+@ (nez fvHWSP'h_cV*a6nrWFNPq؝#7LQ 2i^{ڴ1ǚ?2UwuX= k7;׷MͤGoB X{-}ne2"Ш@ X@ B` @ %@ K @,@ whvqFi@pCh0`4rlV+j0X@ nRbanK ͆j3 U"׷?+K]G>w.OC||=mr{wi׷tsgܵ<]}CWώeWo\;]GPk;p۝>{3vs$$!5^q{Jʂ=ߨARy~4mߎ] 剢*ΝAAC8z4ņ$B!*oJ>=jvr!))@t:#OR)1\OE׮M\ ǜɄ`߿^(D/..&00R:5`41ߴ0 :u ( t[OB…$//B$IЪU+׶>>>5$00pl&Si)͚T!.ĝ`**BR RYY)9mca!`$/jUdiaP]ySжj]t KY~Z'*32PGD`3 fI&MJ C+ Hw˛W? d_$6]PQab MvNvE)S^n$,[2$IB7WwW_~/]ݑ7n<ԩxᅟ録!+Kgٮ^6{RPP?Pg뜲2q^/… #fs 9\Ύϟ?ϖ-[]'S6Sa!FyXtobXhLuj¢EKK(NM8%Efkȃ{a)+s9uPS|l\߮:GRa2,Q݌s8{~B֭[9vx ЁOg~3EX:NygBݻ9C&кu(-Z3dH11LIIl$IbKޢ Ix$'I^_'P(<:w_ovۡCH"##;FYDJ:vWuaӦmcc(Yyyyu]k$&&믿AΝ>|8> $??8&N(dڷo#3x`zVe4nܘe˖ѬY38pFɓ'ӻwoHMMEPӫW/N~G m۶eFwΰaPT 6^zT*INNG/=h#G$11xB;w.cƌaРA-;5jDHHHu]Vf 90|8 #)IM Ç9[HDܹ4aêֵɎ]yNv oBB={?dwg ..[1c?|8Qm ʗ__? ,|j?Ǧmm.*j4ʞ7IHLLdÆ .U z C` 6^ 1EPH?gX=7*U~QvF3`aƌx$,_>ٳwRVf`K8r)fɓtԃ޵}|ThZ"B!\i*sd"##ٵk O<57;wbZ:u*?f233y0ٳGycҦMYx1'NdĈKҵkU(Z'2{laٲe?~ KzͦMX~= l6 7n$--G}WQu֑B`0пuVcz;+jrԨQ7={0qDRRRb޼yxyyOcX8x ͣ ?Nzz: ;wCTkƌtЁ9s0yd]wɓ'9}\'rE?(} :Tol,;n߶- U>쳴1Ik֬4'rʕĎqclQW{>&z(~kΩ^pxn<?ϛwh(6+_}}6Sy-;4J$ OμmxC^We58w>[mkFv-a=z`kժ)׼pbP c,FÞ;Е"I v;jq-K,,AرN |ʔt7{MdF(/7RUeeP1,sQ07T6RۭHl,\8"tbDӡ0ͨT*<͛ILLDc61[ \Zl @RRfR7n @||h[.ĉ\3ee1I"{ZA%F^gs]x\|hRQwh(vSa!WA$zI"jp֬\XH# 6%%ק $QvޡBֽ,jڮFT֘72Mt$Z,X, ƒ儘"3؂`@q {,fP׮=O$p`1X,6Lz3du0~9N^^~~*ύ.V>BzU}\ZO?#3E<T* (//СC4mڔÇs%V+V)ɄjUV9r/o>T*͚5#%%޽{t9sp%, z Ro IDATĘ1cի$ĉh42331X1)""͛Ӽysj,\f}ժU99ճlX,F#:tᆪ*++̙3L&8m2dxyiڴ)z{wW_}EqMNNf׮]uĥfv @? IId^ @ԃRvvMd$6m˖MHɓX %"J??*ϟ7JN_(=|6VQuBuTkՊ:.][,7amPDXt:,eeeX(gBwhff6c0Y.\@RRx B8($**)).UQZRiѻ@,|,֯?EU?Lҝ;7{?kɩࡇ[秢E`iA˖!ݎg;j4/:tdȐ6 fp^+4_Ɉbg**LX,gHxK/-[ϳ>˾}7niii?ر#zˋ{ooo.\HϞ=/1c0`9My,Y»KHHf^駟&55Rȑ#lw}q={ԩn'""-Z+XMǿ{9=z4w}7!!!CѸqcO?^Ϗƍs1|pܹ3Lj5t/Z/2O\#q4/"=,7Guz<ȨQc"Dt݋8:-&In4A" +-D8sդ{Kr`t4In#+B/۹V<Ƈɜ?_0=HGHXG&kd{Evm:RG/3;X,+ƌ;GH X۳G(ӯD"lܸ$˝ytYYY^wPZ1hllD8]&ouu5ZeU_vԂb FsL6DVAGGu 7L,\jD1Je{a0^pkDkjn0Ϥ9z #?^G466sy xbbP*9{-^dR.^lLŋ DE!H(+k"3 ьL&CAYY-ʭ?{;`Ѣxx(HK+es̙3n'ZK 1:f+2S䶔_PP?RDBMM F| 0RRR22ݑd\p ą !<<^OQQ  DTrixxx80wYf\.'$$z222(++OOO***(--ܹstBFԩSfj5s)Z-H+5 \x,d2^'88z|Aj5\zA"T*jqYNZj|}}Q*:u ^OHHH,(BVXA^^S]]MFFFII uuuB@V^l6M}}=gΜ]:w߱i&tBTTb1 >V\\̂ HRBBBjrI A*uV"<<TJmm-466D"APVVFvv6z\Η_~ݻҥ +tV)(( 448AJ%eee "88X)))hZ 466FccKGRd2DII vRh( 0X,?$+1f,ĖK02ujcDr-qxxȹpCFEr%+ɓelڔy8zuZ-|+:>(;vCl7QSaӔ69r)*AmŊi$$̙1{T “c矟?'$$.|_x1yyy!HXr%.]⧟~b޽,_FYz5J :V͜:u\Ν;֭[Yf ~)?#ͼ{\xEtӷD"!55׳~zIMMu(JBPP[[KEERaڵXV/_η~˾}XbV/X`?<$&&ri^h4h(--d2G}Dss3+Vʳ>F!??_Xijjܡ_~Y8qF)))`0g}Fcc#O=۶mc߾}X,|MJ%K.Ëi]]ZJEii).]>^yCRRjW_ҥKXd l޼ѡNdҥKrJZ-6l.]JRd O>ÇSQQV\6m?lO>۷D"!##g}.^(`0p%fC] 9rfYh?#6m\z=Νo笠wyݻwYd lܸC9oSRRBQQeee,^Zͳ>Kiiig"DðMH&тߓw+&…ΞbML]wa +3z3zt$:_D||0wwJK:,\.?O'";?# qٸq#o&f{2ftѣG&55?,[ J\.LJGP(ٳ'JR KmJ$tQ*jbcc7n` &''zoQ؎jbnL0X C cܸqL8~ȱcHOOGբV馛xٱcӧOs=pwRVVӧڵ+3gnvq61p@FJ@@~)$::BVV)))tޝ3f`Zٳ'QQQ=Z5ZۗsΑ={ˣ[n׏z, V///5jݺu'GZZpy|AzNR\\Q6773zhxln6N/ZD mɤ=IK5*ue2L& 3L&A"ތ[T*lmIoNG2jTSҵ؜ӧ~***ضmرe˖1qDtՋz~gpssCRm>|8qqqIj;2p) -T\.G& `2ߜt9s NV /RYYĉqss~wwwG. VɄBd3LgL&t_MsY~"##0`dL0N^[=6$MCѣGihh G}F-Xb.]ʋ/fJKKٺu+L>]{MM f閡\.G*O?M}}=&Mζ+JbP(-UٌD"ҝnB!|>u{Ã!Cy1 !a`0:z͈#8x L2Ldd2 @R@ZWT*o\.zmhdɄr7b0{6ݖm6***xᇝ$L&+H^ z^\ D uVTʙ3|)v端N NgD[A]Shl_ގc6[1̨Tz BZZ{] 1tS7Ȓ%ɩ!,ǩ ,[zEvv6-o!jo}b t^#`'FPP0`FKaa= x{3`@UmMჷQQ~?_GLLfz JMCC3g.ݻp}),hbcJ%4׮M''W_Kh7gVa00-4+С"V.\];ݺ;hV+.]k׮0p@V+¢DPPVt틧'fSRRBHH @PKŢm, .]TL&W^-:NpBWTTUU/=sE9+%XNGnn.2SWWGFFAYYxyyQXXH߾}_ ?PVV o߾¢VL^^zx|}}?7ǎl6ӻwoH$ 233J :*0`RY,"##)))F*++&88\ nd:nF (**" >} @ЧOAs!8p R&ҥ.,,^2dr2T*\tB޽qss!,,NsΡR:t1RQQAII`4hXV>wRE1k,MFUU999xxx'uqY__/lb.^HTTbWx{Vd2a21^C" +-D8sդ{K$X B*fNgoҨPРOo*l83Lj~9nf0~)^3&ډ%qcQz˪T9rQF CmLuqIL&vdp$6ܹs/_.秥RZ4iK]uw&{mζpG̎r ℷl;w$)):.\hK+++ J%\ƈ#P(Wl۞sѝ+}?:vN#;;[Y,BCC۷WgsLzKrssj| q%T^t) '&& I3[} GV_Qz[G!--smכL&nnVrss;$[ۺZKϭq [D"9s{OJرk",d ,[B\V˪U#mϯʗ_~)|on۹f rI$v^]]k׮Ef+믿 Dbɒ%|W444t|Jd͚5|۷CN}vaWʕ+)**rzmkҶ d֮~Z:{,wjb4ټy3/&''DBqq1޽Dt:]ۿ{n'cv%#yg\UKHD`Z;>;#"_W_Mg„ gժ Ncc3{R&Xk9{K-Jy;S\Hcc3R-۷pO60rd$={j&!a*h4`WL굵-.E;552FITT|N!Qj%//PTT$LAA`*.._~TN#''{7Jnn`-JKKc߾}Cm\ƈX^^HR gf455ɓO><@XXϟ aLJR ©S0 XV {/ӫRZ"D%BhjZbo ؿ'Op[gƍn6lȤ\ŋ/G **Tsfy݃ԹFԬV+FEaC&~ʃB. D8o.&8`Y,;ŋIJJ"??ӧ{{n^xӹ;PTeii)Q %%˗3w\ +V`ݺu$&&d5^zm۶-C , 33CQUUL&²Qdffrq?>F'O׿l۶ DBYYO='N5jϟ'//j * s ۜ䳳9q)))8qBflz7|T>3 d:*++9t@Ynv2339|0f'N~z6o̮]`Ϟ=9s^{JvwMzz:'NcΝ 11ѣG)++c׮]B;yrss,qjjQmQ .УG+8}4l۶, 0a`Ymjjbڴidee1eN^l8p xbV+7o'//'|z҄񑞞`bEtto}/rxcc#&WnFGGT*a>(˗/%KE YE\=ҳg쳣923@n%'F_<(t:#UUZ;f 3!C8~eqy|2OO'H\?V_x{ ͛ׯEEEHRZբP(jF… Xx17|3={dժUL0La 4͛7s 7~MRk׮?ZX, œ9sٳgLݹ{]-\[nhdggs7:ԓ`YÃzp򗿠_????K/rq[ٻw/wur777<___<==1 L6za̙=~87ts|wdggw!HӟDcc#P׍7Ȯ]ի?`.\:9jO\#Gv1cV_ĉ;vpرϒBLJQF?O<ƍ:{ܹs>j}vt:rjjjj&xptZyf˖- 6 Hcc#& L'&I QQQTVV]s 2,Kj$$Le8d2p~MFCCsw}Ŋ;JBbE.Fc`ذ01?xpZVΈh`0X,Hii)ZmwmkagPlf͚5¹gΜooo.\ q].SOqAoj`Z,jKǦM?>cq>̡C8~0}NLLo߾3jn&((dHOO\x`dy.]ؕ燗-X"~Cff5?KaasΝ;dž  ~zw-z_\.GT|gy7Yr%۶m###F>}:}rJE\\|}x"iii,^ݻ3x`fΜ_Zfٌ?~ɓJ7n6̚5k.[>}@(//'..̑#G?^ $AAAӭ[7:ϧ'O<<(<󌃹B)--frrr(..)//'22]v1|pBCCfԩ[\7TʴiӀ_\uj <==W^O>I@@u(B᷾n1:^ɓo#((V!L-OZŊOUD~~>驠I&=+77_|¬Y2'뉎#8?MISS3%%MɈ B"iq_(z%KnhllFY,D*jEP`0 B&FO>ۛ%KЧOn6V7J7|C=Dhh( /oooCEE4C`` TTT%<1&˹s/YdN6Fk׮SYY)l=VWWBuu5fYXx"""P"##ZLTT[a ͑m dC8 777v*X***ȝ^G&!˅Puuut])//GTҭ[7LRQQQAdd$lٲ{ӽ{wm +P(ѣ`)jnn`]eZ;bT*H$L&|}}qwwގ[~ӷ]m?G&3}tƍԄRLEE (˔<==yXv-QQQD||<^^^455 XvlNaaav}.[VgϒٳJh4L&{VE $&&{ >it`swwwbj* ebٲevڬYυ ?݋8`LwAF#H$dE5hgNp``'4vۆ|6Z^d2g9rcx!;WUUj=sw8L4xy) p j|Xlj|G裏2j(8yzz s+\ ipxnGO:U?F4 N_w-^ ěX,h4r-qV^͌3zWA0]uΐJAA`ysV^GH+iNtcT*dtZ+#˗s)M֩d.k4xyy9tVmٶ{Ft}}}dH$ z(uuuD$X" ͂ujb p𷍑hiC{޹W+k{ #rllFWew;Z3 FGH^ٮ> 53ZIՖ_zޑE6W,B$X O pcGS8rt][ߎZ!AG\dsUn+ؙqB!J9Zyޑ닎D龚wR:&Й|GDtpUhmcn7bN/2GZ!B$X"yWɡ-0m#{+ڵkqj8{,| ~!'Np=sUU~T*֬Y#6Rk.|Wb-Rxw8ޑƎvD V+h}h"ڵ޵& +**R&9 o%m믿;ɓBMʕ+y饗&SNNj9b1-ӧQfe),xjWz~hhWj(?>:?$/\͛&GVEG6E!B`:ڮŶH [-| Fa17ofn6>S.4qqq :n1걽o{ߖ`0p9jkkRո)j_úu눋~d2ʰI`8{Dۮ,`іGvN>?CWaIIÃ{///!U~骡A acvz_رceX8x iiiv ͛vWO"_m ƍɓ?~o3w\n*KR IOO<̞=NGff&SL`0bٳL:.ցѣcʕfn&˨QgwT?3gyyyX,{9LСCihh~̘1/:$>:'OF"sNF-ıR*<補9s3ge%jkro͟'Ξ={p/^$66V[evՊ]bLNΚEK/XzԅRx^}qlT2~MQHx#F* 7͛#ǎ9)BbctN6LCTC -bヒd"!!Eh"***3gwuaKB ɦ&-ĉ_lBpͶ3f 6l577ϣ8s T*ʘ7oG_~CBBc׏I&G@VV*vMii)Zz>cNRR{ ˋo֯_Ovv6ɜ9s={tR^zi&ớSLv(>> *$9r$='Of˖-x{{3h رc򳲲xǘ8q"Æ L`Ŋ|>޽{ϩSHIIaڵ,]o,X_|-ѣGӥKFbaڴi1Bu@@}_^2|j5?&MBףV T)}lBݝ_PS{tcc~.|ڢ"cc[(I; G"t$RRp B_^WL .m߿;!BhA'cڮrm(aҤI$j5:Sؚ vzFۗshxokfi [q[q^|@\\+V_E&^Cxe<&&`L&d߿kOi-ےm3BBkba:7l=#DFF:%ȋ/&??(MDDpmRz[ng_ /^y1`+m7)) ///-ZDff&555 2Tܹs4lz IDATF6ˣ7lhhh@TL"u`Zk4 ݺuϏp,X :`nn&d8b|Љi8qSS~!H1w.^=zP};\S*++?Aaa!:@$GHʆ ())aȑdee EgϞAZVJ%444P[[{YzEHH:lfڴi$&&Rؽ{7{̟?'"J h?qD=*ԥhD bCPеkW;Ʋe˄ByMMML4d۲ BÇP(hjj?u]BRt:JDAA!!! ^\.g@[AA0HJff&Rɸq(,,.Mll,IIIh445 RRR"把 T* /@=8tXV|||PD~0 ( BBB2 $!J_^5:LJɓOIkWj5չsh (-osF0a L&#**-[8D!@f3)q=ɛe[P )11 NI &>>b}gF.sAN8IOOgРADDD0zh&66ѣGcw}7/@J% <kL4$;w.رc̞=!C`͛73f?N>}#11Z̙C`` 455ZfذaC~l(oNRRÆ [oӓ1c7ߐ͸qo߾zmH$t֍ ~W&L9y$=zٳ&Lm!::777 ƍO>ߟ~TbccF.SUUEss3={Һ ͥYǎ˔)xFD ss⧟0o~''chӟ P+ݻw( &p(8~… ?-3g0i$F͔)SP>>EQ3gFUUF7|LƸq=z4=W]uO?4EEE4eHQ~F__NcժUt֍otÔ)Sx7۷s*R#O0m4rssodgѢEq<  @۶m~ߎ;9r$vNd6Yz5?欬^ONɓi#@YFyskזGbq$&͆C?n#wsRwPUUFŶmۜXEQ/P2.:q;U?`0+Ό3xy'0a޽|e˖]v1c {srr(--Ã6n܈f###͛7Mll,_5c2}tz=wG:t;3c2c gfvƍ__~U{=|}}y/2e ƍsf*GUUk͛lLJ5kp׿խOG*VbΜ9ʩSc<,\E/WOu>SVV=\Eذas=Ǒ#GxiӦxp`ZVΝ˛obgҥ$%%=B'c-.&r8{ģU+"[o[I bÒK/SOardyy1`JOL5߭}.tL&JJJ(--u&ݰaSX `5uj\qq1& ={dg{]{9oٲy摕Ŝ9sؾ};֭#22|9tP~1 F֬Yĉ?~Q裏Yfq 7p1gY8~OyWdڴi"UUɫ(;UVq-5\Ê+())q+**l6SRRgJaaa%39#|rrdw/++#%%UU8psUkY{2pj ݻ; |0 ӛLӑm*gaP*[aŋ_M -i"%ko;+UEQHIb)o|ng)hw%rT[{ ̕W^ƍӧ&M"11UU???&Mw܎$zVϝ;oJԩSl6O֭[ /3gtK@@3OC1oxN?>m۶eܹ=z0`f͚ ѣr-dgg(Gz^z%uc=Ƃ  bŴnݚ.]8K||< 222dzaK^^_~%]ws̡K.$&&'2vX>s<䓼;t:fϞ=CvXlst:=Zm[I [F>}҅!Cz+<6|3Y[7𰧖hJC)MK#u(C֣GҺuk<<dr0eʥfrwhͅݑʠ6ߣ( &Mr1XU[1C{U-rd]MY]sM?00Э/ʼLW͵,*.̑*??-3}ppp]8]Qu[wDDWmdP6tR]ZO< ,,zV RADBh3_H*rPo*䚲 s-UWVUU\_wT5 kMet!\D4MuuuڵV͚ʿ:avDjUDU3]QUK,~ոRV\-*K]Dž~$I:..f.7iy]7_.`u+Ajt(hTlΐnRGA,r<|))yRA p8Wܳ7LAG`]ti~mz4 BC#-y6t䞟_G%&fr9{)HAA<2gOӦt{-99'Xbs.},Xӭfn' B#M)Q8&Hݺ_ gԶTv=D( "X-:5$HAE/S;  "X  "X  "X         % HQAAjZc0l(:]\( )EAA=z#Gu.S쉬U JKAADt$:A^o`0bxx b& 2y  @ѪU(F#FaۋsPUU . B-QUl.+6fB \ 4y'QUUΕ.PZZZi .Vqh BH,RXXŋ9s FBQÉq P\"n& ѪܺAK.-ZD@@wuIII9vB^\Yj3ٳg4h9zN$P"WBKA&K^^L&F>5\ã>JϞ=x?~}'N0l0_lwތ?x.]yntAK.w'Nرc̟?s'1;wRVVo7ߌ/ݶ 5c0عs'֭ѣvZƎٳgٸq#mڴaƌ 87x@DD.3gdҥlٲ͛7KKhU-VUΝ;ӹsg ;ܹ3PVVF׮]1}qWp]w1e0\s57~if̘رcۈg%::ٳgs燧'9 %T[[V@;fl6"\,_|6BCC dx<==֭Wd2ѩS'dtSՒJFKPP~-> 7o&"""n<==IKK㫯zZN:Erѷ 0pbp@@L1x#uۧ۷obZ9sgbbb H=Lnn.'>>( Dtt4уKnn.^^^///7Tʁسg3REqq1^{-7t^{- 7@ii){n#44+pݻ7Ӈ09 Ob4zώ`( vOǶ.RK"Xq޽;ƍcǎVXA\\ݻwې(### x{{3|pi.dggpBBBB0L ?t VN|=`*VK/d@߾`A~Z ͍cǎL>}aFc6GY{+'f b۟O{DŽT"Xez/T 6 T(-Yoߞgҿ~wODD;-@;3DZcPq+%_J||<<:t`ݺu3JUWZh AyS-V)uЎf7߄gfBA^zpB~{7Z"VBUH".Ȍ3xgX,(Bzz:Æ Gu"WFHHp0AwP;w,SF:?_Xz-^}Uzڵk(**k׮̝;vUy)OOX,.Ah ixU"V `#`0#99oooC=\9}4'O"5x0?ʨB$$''drssE>AD. ooorrr&//ظq#cƌaϞ=+99K[oiΞ\N8Ann ֥a0ر#6mbϞ=L|ETeH*W@7AbUh@  ,\MT8@im ^nshfRfz82,V5d|9 W^Y._FD`yyyaj|||h#ٻY hPkR7yRZ3 p9~1R!)@Z3d"XBsgLX@,* _/FD+0Jii)Fݻs)V+piӉvVVQL\9}/X8bUqf" 4@+>]siD>88Ad^=5U FkNޡE${Qo p0h,A "XBpua"1'p)[y,///rF-U{zۖ&0x1Dz Ps;v+@E8gIVcoOI?(BRh9H$H J_*чkBC%UrSjZ+GK,CT@Ȩ$针QOE=D%$abOP3RCrLGٞl#R7` "XBӋptU{hޅŊZ3GBY,t.Y@  B,m)('i#ƚBLGl(ile`Ꚃ1ĂAP'j3FZ+r.t>9x, D85k?|TA61ӡ{wٯ¥HV-}h)&|ȥr$d~*,(Nvc0Gß|Ƞ#чa@E;2ԑ,h-Tw\) ,Ye*\ґ=Y"#P9Bs)9)>GzClu314BLltZ^e4PG&/` Moh6<liih¬(Z߫4& ! MTJP'߂8J*E }'r@@.jJ)zՆji٭v Ւ+4)QE6ͭ([Q1O ֕Wh̛=侯ί!("dC˷Zy`s1l,ted-P@q| ` nTNNwSt: ?8YYYFUj與sϞMBS8پ}PT=zKt}prrV ` 5vx'yW _~AQ֭[GII 7|3;vp^jYQ 2Rm{{sԟMXlwÞH[d ) 05ET.AK+ѣL8Μ9|hl߾ˢ28ܗK}t$o`phZdEQ8u;w$--M$NEU, Z]!' A=Qj %&bQXU=6EAѩZ-s IpF, RRRGnns Xgh sh,]*Z]0cNW.^nh4"͆GVet ʲ &FLiS81'1ػN@[u3d&se @+`jIhH"*OOO<==9t6mwޔ0ydqIbccFz{{e5݆#quJaS+E o&B0Rl>؀@*T5ke~q5 ta@/P lWBrdi֜"đAEe&^9 Mi`IDKhU"%c <?N׮]:u*ٳ\9~̙Z.]r+￯7}pǏM eVHktJ!us#j cCBUeaӕf/#iA!HR8A,sɹ3pJQdIQ&Dj)v{˻v튪 2M.JTڵӚRS!:Z$r_}|9xzB׮R.Bav*,ZFNQE'ErvN P7rIb9A,bI@Z3dYNJDAP uYSw|}5ZN\bOx8n1T$&RoNơG1ޜSB*6UҚ4ZD; (I?8)d]DKjYƿbka(f r+^EDz"&SH?: XPo t&\2gJJ:aΎx6@ 8BY D]y?(,^,rU3g}+E(upQ>0\$us"΃PZA+ hs兓DWࠪE!0ZJ'%` %ճSivJ\hh=pbcۻ:8\ !1i*\_Mv'[巙=${R N:(. %4( ÛojUB`6kW^^v߼%A'tqlLEw S~[W! S{ 2hѫ3d,]R]X;BHlC\].`tMq$/RQH#菵Bn8 YMTU\N}ih탇ϹFAKhIZ//Ɩcয়U}HΝ;ٷoz+____nFXt)111 8HRA2@Bj, Shg5Qb3vHX ` ͸2ٴZ}0`B˛@XlJKKٹs''O[o>c|}}ٶmt޽gK5εP|@1Z*-"fkr%(` ɻi7e &X5i"]dH&m3ռM| (jdyyiߤSĉ( u!Z @DD'N$&& III!55___W6 hM蚫dAy4RlGs-a?&d4JKE"XB V_% ?%zR.(Z{Bit[,Y^z{n6oL߾}4i}O>L:ե"-p[Ky@yTAKUOdhF*[EݻjA$zUl6f͚ŬY8TXtEvP+ZPSEرiVы\կ:xM9}"Uy굖hT,+{EW]{hH+Ee &ڷ ֯\ %QS^jQQZ민DbADf¬Yh#\' ||r%e- %4C\#U˖ VݖB~>$%a˥ADfapׅ/[Wڶ "" 4ԽA,EQUص JKL҄ue PVp&r% %Ɋ޻| kM ODSL&veiA6:L>;شij* ЫW/bbbt{Cī̘U0ry( lؠMݹs+'Uv  fs%EQͥ{5-[8_ bܸq,_R%*\6Ȗ#"W]HQ.r%v[췦(ȎApCg:<z h4RRR>KUU}y֭|FN\\\]k"fhN$*uDkצ[Fcvĉe' `Voŋd:t ;Fnn.sw;5+|/ &dAf&|9,XPYڹѧOCJJ 6ɞ5+> `]* 66_~tBCCݾ="j _QD㐩9sQ 6Er=~VkޚNJd Py.ώ'm۶m۶E֭#<<^_#pž\ͫ\*\}ὬL#27Άaų66)7|Ï?Hqq1|פ( ǎcʕ,V}U*HTUMu[D8X:ܹ-;'v;5 뢢"z=c_`Ҥ#A: RZ (._n1~Ŭd*Lqq0m_pyyˑ@b-޽Z5mΥlw]ku> X*ު>*O! //ݻwSTT:عs'iii^9*ƨlTpNwoܹUowÚ_~ s@h(,Y.WV})ZlڵArr2RPP;(--m.PbEHHUۥ4or` 5VyHHHpVdosAV\Ifff~EQHMMmVQ(((,G0~<=MSr&a,UUrexIpM^6 :D'++Qu~~>%%%"Wq+Bnn.oe{eDAC\K \ d姾q(VE`vڵ( zn{0LZ fC@@@~ٿ?%%% .X;wށ^2LV~Kj"|r<|6aq1Q7Lbb"!!! Zz222f5E`ɓu*Ӎfs }Gii)s%66믿???lX@i~03nR}]E pizWʶKWW(h߾SEAIPP&Trf͚EZZZV}鈏g߾} Z:*+BBBBJ~;HMټUa*h]!<\[ukݎ $!!ACQf3 D( C]vn7& RXAѺukL”)S4iSN?DQZZ<7,b1FheiK Ӡק~;k͞6uyDZ4Ҿ}{e[n壏>V^z%Ș1c0a$1b55ID}_UUzs} mlλGUU뷬u:]mwuWۊEubTQ'OdٓGy+Vc=F֭߿?dΜ9 ڥstYQYF@\˳.ޱ?:g@At?ƵzS.@v䧶N藺mGj˸*(V |-[NǏӻwo  'Ubb"+WÃ3gj*RRR6m^^^u~|w$%%q}QVVƇ~H0`| 2Nȑ#XÇ˗_~Iii)v|72eL&Su[,֮]ˁkر#K,m۶9L zŐ!Cd]cҥdffү_?z= !!!?|V\Itt47tzN֭(../u]ڵM6qw?@rr2'N$88DbbbfL0MZMƴiӜ᫯"441cvJYKÀaɒ% 2}?Yz5cƌ!>> ϨQbr-`4/iXl>>>L<>y 0_ݻwswyQe}F˗s FǎY`ݺu###/=z0t*]_^^>=gϞe?~?N:q7sA~Gn&t"rBየܪ#}5kpW]뚔TLm6Xbs0aJ^{5 '|RO___ؽ{7o6xzz:GDsv}ۨ۷/ͣ}q뭷ңGN<ݻۘn{1ǎkts\{lڴ~.]w^233Og大_Ҷt:ѣyرcfzjnND 'nœf3%%%3tPv]b͚5,Zb3f k׮ٓ0F#k֬aذajN8Aҥ uTU%**xz=&'O /p9>*z{yA@ YK"{8ҥ*W]:wjjǹ];e̵-ת̼^nQ{c*ţA0 *1t}Qg~|ٛNNLDbb"Ayy9f"&& 馛HOOo zL&MFttt0۷_FV+$$$YѣH}}=3f̠'OIZZտ*/0yd.\HnnnHV6tvvj*9q3f̠5+恶3vXlܹlڻロɓ'SYY9u4 NbYj~- 2n8FM}}s\.eΜ9躎i3sLn. &p`Æ ٳ׋$%%FQQ⋇8"nD @4,ˠ=m6^|E>#&N< ַ@ }z_޽I&NEE%%%X(, c͚5믝jR[[#iii$%%1qD8y󈏏PxG(8p(B}m_i=-`Ǯ[^P?`9|8N,V ٌbb`XmCE^9C` fzc`v&vr tk<`WRۗ{__V߾Jv+8 2K!BB!,!B XB!BB!,!B XB!BB!,!B XB!BB!,1/m!BHj˅GB XJ| a}2k,TUv!"̲ . 6>NGEUUp8,ity 3s)5 u6v1t{iEbQu6¬DF/.7]!5Eĉ:6nE}`x*(p|Դ( !`gjhpɤǡ:%:up;99^Nnp%BHRJLL6bb0TF #2Nx#ðTW%5uuumoӟ>ۋش(?ªU_yQDMY|SLMG))i3)TUsY4B"[_zM!Os9iTT41=$;?<ĉùna4 4ib$ny,p/p;f6ySB (X666 ݁tt:vPHĩl==v'NԳc"Z[;in.1 tZ@}};A{FcQQLyp-J;yՃddl$&&KWͼ\4-('C&W]Ua12]!Ā|=@QTEEQUAAd60LsK]7]׹Iw =BNWWf[/N ;zt9idgذ.L&u}{PԳMEQ _Kuu+qq836esH &:|*J!įשiLB3.za(2T(Bs^e~>!^:~䡽]ᇧ>ϟ}{ΜX&ڴ^|߯3tGpCUU:7]!/;w wx=|>͇ϧi>TU%v>{ j5#<܆sa;0t58]w%X fvZyÅB\F!„ *j(>͇i477i 2ZN B KvCz@ K^4]̜9SN 8v>F '.>;q\28( _}U~iy_-! ^y/8._?~ڤՃ  !Ⲿ{>LĄ 8s *5aK2L&BTQTV87=E1 "᪯`) s$h !+UQijh><?uDG׻bl)'~eZ'ߺYFLBȑ1T7e~ku^ U7.GU* ;OP,U-B\dz:& FѩXtn\͹jnnӧJES! 0tX7p5o~IENDB`pytango-9.2.2/doc/_static/banner3.png000066400000000000000000002425471316324373100174570ustar00rootroot00000000000000PNG  IHDRX[sRGBbKGD pHYs+tIME :70tEXtCommentCreated with GIMPW IDATxy|TΒL}_HH]@nUX.mZۯZm֕Zm*E)*PAP` !d'dsI&E+s{>s#!8mY)ƎO$M`@ PUuI>ս#4AuީͷCNS/Ͷf„96]$)ԇh&(=b1SUUEnOIȲL?VY9yBWjҽ:&ө:Vk ;9eeek22/W%8 l6cZ~ǁ$I'S<?3񉉟[@ r顷X,^/nS}X,x+]_G~wuMӆ!9{/ؗA,$ MXX!$IBQfsNޭN*Kp"KMsgMӐe׋ -M5 ϐ/ $q'*6Ww^]dȨ(,&QC?$$YFq(*a,&mm-||]K17P$uq=}uOo]ש#>>Ƞ.!IXX b2hmX`695FZr M͔5~dF6wWzH&Z˿y={zF b"q<(.7& MQNCa1ȏ9R]ͷj G5$X0( &l ,&՗Zduuurhkk'!! _j:Vh4m๥(ʈk|l6OggH B` 쩓 cʄ"+Djhmƥ ֗iCGԓ@%,9˗^η‘wUKC6h{QDCaZnoϹ[YS[t{8mPi%*>sΙK7M\^R3wl1C$ Э$I?z+pBh4Qn20qxJ,..jV+yyyzAPxit?SWgRdSS]E[{;=a/dHI8:صeMHr y7܄]VdeQbaʹ3!=Eqi&2&W䣃Uر{+u-..58&I,㙖Nyy 5L& NxxɉX,$I"11Q6+)S)$I8=NWWhP !fz+)jNf".2ChEu;A ʂ/㡻Aj8͜K Ry[mcƐJ_ $TbM:DG36/ []kx}DŞǘDYTiC[$V U( Ham2N#e Y$Ө"IuL&MM(ʀIUURSSZ!,Mp\$ reiľKHOOUhoo%Yi`^ j]l1lG 3P.(9dWWZi;05x=Sҹ`U$n/$_Lwt1q|"*a&Tw?e{>"rttM( Ed'.. ^>Gɚ0Mx 1¬ln7=Iz=%u LTKWS 3枇,I(7Q4N\4Eghx< %,DUUdY:1.\t!^6d(U4p\."i(IɄf;%b2p8Hy\.\.7 ^ǃ& B` UL88di:H؞eYVU@B}~@DL<ϻy8=*,#:^P)RXx$ Uϸ'ܞ@࿗Qtvv|fQQQx<:;;O9p@Zl6:::>QJJg:Q K$%%让~a2_0.hLZZڿ-O}ollg#S7ĥ~ KCӨA Pbbjg>32::4a0X0-TUr.g K(=ZFaş[Hf22B` |$'0qB` QQ$&&^E93)$&&. &K ,.@ XɘX: TE񢪪1"W p}^~ןڧRZ}vtC(Ni nLq9 $` {F@9q8bנ (ƒ%NJkBx4%n2K40 DV?q>an:/ÇBt'MR`^]&al$Iz .uww7.!B44Md2  vl`4M 8{h uzX"eNl3fx4pkqH:u&~ucBzz{~;SeY ",[Zzz?|bbbqƒ%]lHN}~gBoc:^砧cC8U 8AON:J]}QbVCz4I՛$IpwSRR2N>j7$پ}id f;߶m]]]eŝ vOEButUxqw3Q>H[+Γ_n7SNg׾ΜY󈎎A%!mD+ #y̰6;vfVG_Dj츻gV`Scu=~*oί[w,s7rw3 ik1e( qg0&7lf1E(" ŒM %|$$Ӏw!('6}#t Πut)J u078{Gц1GK8I 'OM %$ӀD=7w7JHLLH"}Q\. lݺ9spw u]|7 v;_Ċ+M74k(6:dD«bHnW%1x{T12fL\K#"Ռ;*ӌ5FeYu),,$&&]INNE뺮y8QUSHvv6EEEM '>>t$D&MDll,撜LJJ XVgܹL<$c}\\)))(Bvv6mmm$%%Jjj*&ME1i$"##IJJ"??ߨL3qcifVRf؈ʵa&fk1DyQ!uFISqwΊ *bM<\.NLl6iiiȲLrr2$%%ĸq&!!B!33d"""$""$<.!k%q>s9Q}jjN jkjhmn&!!\늰i&8}YM[JJsڴIL<,|sm,"KKKKW,<=kCUakc49AZxH 7莺&dSW VW0 fqwm4N,3\3m7mFlp8:R;B$(^^/wAztMXJ TW.XPYHy~Brz:=,T;HiZ]בd3](3\B}_ w!̘td`OȒ|W .q&l+vH '6a@ K @,@ ! @  v8r(_E,<7}WC9:2j[Jzk~p$+VXbvW^y(uGSS7n'd߾}Co5k(++ mmme߾}455y4{8'? /"{AU{>#x㍀b ^bW"$Ib׮] n//r8|!>v?0w_x`xIVV?y)//bop_GyzzqrrrhoogʕX,~K/Nrr2?Ocx$Iv~( 6l ---`'''ӧrJTUeʔ)vCH>쳌7"JKKyᇉeѢEk\.opq>Lcc#wF>?dڵ1} /pwls=Gaa! Vbl6s%pMFii))))~?i&&Mĺux嗙?>aaal޼:Jy駉;ॗ^cr7cX2qG*f9~ӦMСC){CC*---w} 466rANgPXQQv2y.]ʛoŋYx1}_ Ə~#{=\{&JJJXj(++cٲedeeUUƍǾ}zuMJJbɒ%׿V,3gΜ;B Yy԰zj.]Jmm-N&<n#2aaa|;ߡ ς (,,nm6y?/W_}=Æ xDXx1wqIIILVVV@~..\nFyjjj\IPnZ[[B w`&8}k,g``EnCÙ3gԩSus1n69BMM ?Oj%<>ɓu7:}~>L^^{n] t:پ};jÆ߻3\}=z>ooM _db׮]̞=;]۴i|T;RuHe92ʡCؿ?̙3'ԖY@zxe,Zx fEQwoUQpܴwupEw^ZZZ"bIX.L) 8x2e 'N4)l[l6- H{}a^?Z̭Mk`{k# U O3*X7F ݆kt^`A}F!}7X`eo0Qk[T▍^Pipb~4hh0X~n(NԎwG@ K%h}PcP)4 ~6 ϳ~_J=C-G('i-@pz!v @ %@ @ X@ !@ @ "4iguTU%..uzﺮt:q\XV 8h(SI8wpɖ>‚:X,aKk@p\-@,APկ~ECC2pVcc#lݺ~ۍtvvR[[k|Ta84}qW{ny饗`7H}}=$p8Ihiinyڵ-[B!~رch(Ѐ$I >>޽{9pbx(ׯ'==͛7`V+^z/S"2,X{Lrrr馛l]@ O$IbΜ9̙3C?e˖1~}:||k_R2>Wq0w\Ӊ+`455g;v,ӦMX,)//nYp!))))OIxؾ};\z8pť^ܹs?>N ) Lpnݛiٹ㓘s8s;RIFZZڐ-heUTj ELOG3ZCyHj@  "A$cKiv9]~ikkc|j8xG;vK4n7UUUhFww7۷os_`Ҳ+bx𕫽;Z]]yx^F t=^xp8p:w^=j/]KMM \.>;P`TUUák􇳕vDObݻwx;Tr{Éwnkk i_SXO6$mݺ;wrAW.zDQy7ZUUUq 1B% ?֭[Y|1wvv*O>$EQztuuV,k.c$I=D(N__a7R^^nϰ~m۶ +^u~z,}}}i&֬Y31|ۋ諊vHtS__nۨo@l4EQ i<Ȉ)BOSn7W_}5^{O?~3"##yٷo ,`ҥ<swS]]իimmod֬Y;Cv*++wgyf@4tEQ6=tbXXx17nD$.]bƍ|G lݺm۶1m4bbb3~i&Mĵ^KllQ6Ú5k&==$^z%˜?>V-[έʮ]曹{[Xv-6tƏ?DDDSɋ/;ό3xgz}٬\Ntt4?OxWسg_~9kdeeOzz:Gzz:w{!66zCQXX-‘#G/~E]ĵ^믿uV^}U[ٿ?O?4]vguV2BWl+LJ~֭[x<(^{-)))L87x^Ê+HJJbɒ%l66mĉ'y衇p:,[ t*Ou9)//'55X.mfL?b{G}F233;xG[⮻"&&v~_f֯_ϹKVV> ӧOgFhի?ilzظq#nydØf~>۹[NU*--)Sկ~{oC;,c6$'NPYYIAAB>l.ƎK__cǎ%,,{rEm4AFF x<fc۩nwޡ{w;3k:NYt)7t3s:-"112Yl$QUUŷm*** !KGG۷o3PQQAyy9O=]t'Ok%==]י:u*cƌ1UU,G1qD"""hnng~ x'ؿ?sW$ITU套^窫"??o~?~KQOΦMl6Fff&fZ,YBRRi…<zcٲeDFFRZZlg?%KyL&~_aِ$~}ٜs9XVƌCXX!}rW=6lYxx%>(\r 3g$jx|uJ KTTT30ex뭷زe EEE(bLX,q\zȲiꫴyc0-..c4PPP`xbΝ<#\uUCXEE?<,3m44>ۘyר1vz]6l ..7HTT'Noy?~ÇS&Z4曬Yٳg믿Ǎtz-ꆬSNNvkt:g[Hf3Fx<>}:YYYƲrJ Loذ8h^x!`͏gL&Yeg}:\4dYvbiѢEtttpYg̙3yQUO]]!t}oڴNcf3Cxx8ƍ+xlB}}=/**4M-[+0o<^/&ɘNJJG?'O2k֬>=o޼!}d2QWWڵkill$55jLӵuVnJss3v<|cֲ~NчJKKy(((N&>Fc;v,r!RRRrQUUENN111n555ƚ)S( 'NfƱcP &*儇SXXhx"u]GQ q *O(Bbb"FZZ1 ZSSb(Š+ edffSȚ5k !STTDSS X,8~8$KWWv233X,Ltt4FF1lNAQQ455bpPQQAdd$cǎd2QQQAnnnH7#4|.k@l_o8&4t}Uwʶۄ.é;|m޼_˗5|5\fPBr]wO~8㌐ b 7kSol ehbYPV_t}k02`E]Cݣ #=yѼCjPRAEѿR}Bl&%@ K @,@ !@ B` >GZ)P0ZZeijP9MBI狶@ % *ouC4P7xO0qj0Sj_s)X@P0`Pr$;dAK=8t%(@,i$Ix*۶ңG;?T^K y]].^y7I(@|ZLb@6ڵǰZMx<Wa͚8^ >{(Νwn.֭u46ejC!(\ IDAT鉈.$qP+\w,)+_ýn_ǂFa޼LRS#1#NdYfaKέx<*$'3{vII6z9F}N/{4hX2Ӧm ΞANN4eeŕWK,_g- rXn7?~>,[|[ذÇȈw.JZZ$6Eј4)Y 3SQ]xq/\Ʈ]lXtzmwc6K~t}EE3aB[ XU).N䗿o>n׫3,3Y'jQQXŭUsZ[VzV_[۫׊E[$ @ , $!}}cfNf& ^ϛW^3gsmf8>IsPU뮛@KYccfǖ.iaYBp='s < ^^Wy8'Ⱥul \0F/.>{\RFee#~mc$%I.a_Kee#FecǎVLd,x4nu>?LsgW\q< iotHͦpyV>U))N.>;Z1c@nwy"~qOUxIO @%BA'L0 9tȋ n֯oDenmaD}zK[ѶB!⨵3G2n=,xq5|p4t dѢ\YOqq:W7|A|s/N(#Sy㍽\ M*:;CtwaڴB<&֮mda{;PU3exN>(a.ּ*9ddؓ xf,f֬l64y\xLO]]7O<5kq@_\n܎4mۚp8T\.#hm F{& ~"551 f?5kQ]݁AM}v uSYȌxovB\ o,o0K͙?ᄩ[\&NDCz8*mmrsh&ide9u.JF7a` #*΢`)lQA JK3,ap8 qxlyf)K#ȬYŔd덐퓦8 !<;A/С.MCCÆyR麉YYiw[9fNgg/Cv'n::Bde%,Fǔ*jhTЅHO$ؿK$E9xtKɐ!44b,tݠLCzP~0lI}jo"I1ӁáPP0LBD":eeCeXyO/psQUA4HH$\4Bm=w.7n5owb%BA?֫(n!2$8lXU>Q+cO&I!C9DY픕 +@  NS [iw\&$1bĐqLsRm/1 ~86SefTfidfYs?4z)0#AF:NKS7.:6M#20MӖOӌ':&P?>/脘Ů]998ztvRߣBQ+@ @o;ė͎?>ω$͵N^^8$.>n$B'3yr͚*R-HŬxSJQd>ִÁ믯r%q 6X .F+*'E &lbs,hsy$S/= XPI()daD"M65X dYILKK ===p׿<K B` Oee% hJ6ӟn]=^oYr2qb>;w;HĮ]̜YΝ-<9}DJUN;+4>4M7na 5k\R?BnSe&;ǯMEz>5Y@ B` %Rz5++|HO\ H %oom;;1 5$=tg넽^u]]\-s@i %@ {}zI]_S{x;0  ttuYSCWWSP;D>Bƍx:wvcY(v;uVz磻Sׅ`X1LZ?#] #jV=lz$*j}ngHi)xn$Yƙcj}J/)Ae҇p4L]^tΆu#G8bF@ _E`@.(uRl֬E'?q"3$Us1uS-c&q .,dHy9#Fn>ӈfH-5,*EG@ ",X$2GdC(:dFΛ_NȪJt l IhJWn.k@R$Eq:>_s0ЃA$IM_$ -Xzeૈ` `&cƐ1|8$1q.(@ l N<"F_r VҲ0“N]Tt$1uu},XCi\=UP4X(9:@,$as:9c&g 05'gNuřk-)Y#GZǎ,ҲD\\d > -3h3m˅t w@ %&1T\*\81QF{<}(tN+@ %?4,h 5@ 'wWF% oJMڱ?zRCjG{ߟ^@ K X4M4M# YM a`pM-_׋GAMjA0$`Fee]Ģ>A(3M`,PlGi\7@ @ .4*++;]ϧfܹ',^OyIcʕֹ2=1.?;xW\lv---.3KÇY`o6{oG$~m6n܈ap >|.~@ K &~.]j H$W\W\޽{+ B ,_w}/_| <k֬ȑ#\ve\{˞={xg(**RUUE$!++[755qALdǎtvvNuu5s{족v9rHҘ@,(4tp8I]]]]]{nnV ~ i~TUtӃ1s:5jC DL}}=H_0@۷sꩧr3sL*jkk1 R ȰaۑeZV^a($IȲ59bNZZ999̛7Ӊ9rN(//GQƍGvv6yyyTTT( ddd0rH#??$'|g3'%0bNn۷s50vX;O$ rM71f<g}6%%%%B(JmM {w^jkjh]8|Yo| 'Leݺde2q11_XYKviX!MӰl5p:[4 fqZCHp@ueߡ'( cqAclj]( ,c&[k,1Kq N$?sEQ4d> h<@(eƍ477n@7hT Hen ݞt#R(Q<ąI\ M|ki"2z-ޟvh"xS%u<%ıq%BwW%;7rϕ@,`! >o]_//:7rN 8Q+ԓ'YT ݠ=!@ 8vLرSB RlPQӍ@,@ >+pf̊&%߳G%|)Pjd(yv2_Kr|4)**JjS`sik:P:a$!` _A@MM /駟 YyGXj]]]\qH|@OOټy3Y%Kkq>7`֭}$Ilڴ@ '`0gI*۟Ege ,GHdEoo  B~;t]{O>DF|9A8POCAF`&p%|WӦMcԩdgg3{l|MBezgK/ &[o1gٸqc1gVXA /?<|{D"~qedTUeҥ̟? 6~z.rnv:::K.Gu^}UfϞիX^/^z) .D$~ :uuu<\p0%==O?/ŋ( ?8gϦ6.b*++s{^yW4M~sE{ŭW\AKK ꫯ/+@UU.B9^}맲3H"@,_ϊeۭNoͣf̘={۷dzo>/^art7xBD"y=\֮]_7OSl6w}7sv" v_²e3f =tttfnf~̆ xx?ְ#Fp}!Igy&O?4mmm'?3fzjOW^(..0 jqz)rrr̓O>'L^^/`-a{n~swb zzz5j< YYY,]o.EYˣeeeX^z%>V9@ @ Z4M# cRSSiii_SNaŊgvMmm-nͿ;wIgg']w[n%`&>ݻw|sa+ݻ??Yhw例.m۶`Gwe}].%%%}ݴ0i$,zW^y4پ};'pN;wߦ `0Ȏ;aÆ <޽ۊ~w^|>wq1 6BnV;3sL֮]ˬY9z衇6mZxEL7+v:8. XKi >0(//GUUFɓlԩS93غu+oۤ%EonnF$ΝKNNPb&MDAAv&***PUF͘1c顫3f0b4McL>6|>ƍcҤI )))8I@~ˤI3f n3e Ɩ-[NcͤqI'PXXȦM6lgFu<ծ餶Brss 9SO=I,-k_ݚ+dYY@Cqq1۷oSNrM}}& IDATEDDb:~GYRL@tg?5M7􇲘o."ῆJc{NTkg`򗿰g.+a<[r ax^<OB\}}IE*-ihHi!:E.BAǬ_C\˪RϏ+*ԟHK<_;mo}?~s`&YYYu9l|Ԑ,1`1P %|, sy>k z$Y }0Cj;b\gH F&>p4!1Xbc]3ظ>k2B C"@ K @,@ !@ B` @ %%hg){|:S|Wg`6g_Ab+|oH$,˨|F"$IBUդ1H$IA<%i8Z8NO<(VEQdВ$p8ğJ,(XikRc,Tuᰕ>(5V$Iݺn=!>~躎n ( Wu4MCUU3/|},WRzst@u4-zD__RYM1FoQ-&ePYY-£>ʾ}bOZ-zyf>KȄa4MK񲩂+^@u>ZZZډK8N8F>#p]w@gg'wq*~>A@[˖-{8'O`Ŋ] ϣz $rJ{1.rjkkKvmٳCq]wc 04z|=^?@PD#4 L#D$@LnK%YAV$Yfppq9{\dTvtt@)Uf***(//g֭]ysNyꩧ=z4YYYL2_~***8q"1fyq:|{^r.袤19rg}383gi&1Mロ &Lɓ@zz:ϧ͛7c&W^yeRiwɋ/HSSׯ G]]EEETTTݍir-,]s''y~~>Æ ѣGn:4M~3~x:;;2e &L泥\fԩ0g:,ΝqgϧnF~2zhIzz:˖-cĉn@W_rq!a>/ňilغOWs?= ሆnDiδc44C28l*6 MA-))"KMcna躁iIDӈ-͝!>@0Md{brN3Σ%2$ X~=&L4M&Nڵkk.~zj:t(t?YΣoC8fѬZ,jjjx< e8Ngg'{$##*͐!Cbʕl޼n4 믿駟NnnnJaa!Ȳ~;~YlіLqݴfyԨQdddrꪫػw/-[--- :v@ @p**"IU8xEnN.YYYdgQ~ 򆐛=Yx.6l($ /\Ch1A.7wp;Yj#==\N:Km s1&|"KuMƌ3rՅ%Izz:6 M͵D,8Br|<POÇ^_v5Ƹ1 MssՖꫯ< K-b̙3eoNii).9czz:\pO?4$qUWY~T9lV5M1cӧO"e[1 _U|uVWWs[~TeHmYѦMFMM SN%m6JJJz䠪*"W % *0;vװjfbcnj䴓O$7' 2#!cHԹ"FX)nBR]U) !rr2(/Zd044M['SX3Nf⸑(.fS4?CưaèkǏ'77O>41bTTT`9裏$ɓ'3zh<%%%3iӦqwo} ܼy3/SN_W466p8+9餓lrgs}q]wn'|MMFFUwww7k֬aƍ?dټL8xW3f #F`…8nVK$ٻw/۷og<#|'lܸvyvx;þ}9s&[neÆ 444p}a V^u9»˔)S3gIhoo磏>bʔ) : y^xygz~up\hOBKk;?DqZ|ٟ`,4PcVK/cxPyC^ a /kYY %s' FQ}@0L$ l64v-꤮H޼//J֍7v4!ihiJ]23\HlݹM3[QBYQZAW 8N6@o.fڷxs0X;G;>kVΙ@ K @MF23=9;`0$  G /G/}3$$2c+{G[FL,k7WF^qgf,Ns`wؑe  nfg0s U{^4Mz7s@ K/($aY•fǙfrqDw :6T5w t=[ LD L1hHY鍉%K&&$' D `Q{ٔdIeZ" ´wS{n{m6 @ @af $#. hm3QU ݁e'&3tAÎFc]YK,I4hB+E>还5cJ":p`0B  Eaሆ.SʲjSqآ"0jزj4"#@,?CbF:$rԒ[ 2pX#e$LS #[4HF4d-RTXZXjC7 H$B(! H)!+2vbP^04dRdE@ t݌cЉ6IQ}dFd&1̤k)vIi)O$$9s,߈^+K-erIP$)jo}01lX"|zuR\ZČ9ǂOYVvz3"f/J$1W1ɉ)A%c Xc:چ!)&gi)3.zbA4TʶAwƮG-\ D))3I䙽J UcHw4~k;<'ckRh@ kQ d&1`2U $ݓ LZrUtibX|+)Zkc"*6ɴ:l2|  hjjr &"*<.siZD݆aJGGF̯zB~+1Hg{{;@wP %f,]r "̨E]}lU,äPQR,uHƍffla"`Qt=fZv6)EII;/"555HD  cĖ_1g~۪'@MӬ~?+N|>K,Hdc&^_TWWc&fΝVP(@ 9So͖-[D|h.$Ic; IyWo~sL6bzKBbRI2-?(3vMF_Uo؅Ԑ1&HȱB )IR-;a`%82$%bH1WcェI&1m4V\Icc#˗/'77SO=UVqwxb3੧R^^%\ /Ν;q\/3n8}Yex^{*N;4̙#}:,[_p 8pR‚%,CG$) zdF0X ךe"+qga0AB,D2EO5$ٟ]ZX}-kK$ /1O\( .d̘14551d-[Fkk+x<jk𩧞kᮻ"//7|C]]h,Ybo˓O>ٳz<裌5۷k4i&M"33K.&uz,YBqq1˗/ZG[UVZˢqE!## @{YgOEgg ! @׵h)ջ|fJe2RUlI>H$QLuٳgsgɄ N %XL30c>X+)f՗Z.4w *Ѻ$%!S{$I1Q.|▭Ti7p` /r0eNzz:z*oikk`0șg駟ηmp qt:ٽ{7W^y%6vt]ȑ#dee1w$kwMgg'Ǐ'//~}z@W4M&MDww7^x!YYY\|rWՅ%///N/K/%333I|AVV%%%~zzz宻BQDpW@CldߜNʺue)_^4yk˘Y_x%Iyf@ Iq2qakzXG8Փ*t]' xmb<]4Moƍz@ %'~#q̘>ARlJR7*VI +Ei&١7M%)1eN<'a?UbR;{3$I($h?7֯_o /@UUUrc? PEz~}zSձnݺ$X^eV\IKKˠe׳j*-B` "C0Lt݌9 GevxPȊEem$)~hhJnqSRpVͧ&ahhl`}]^\N'W7`"2j(J֮]9sOʚ5kx衇0M^x6~?N.nv;%\‚ |;gʕ}jkk93X``.{/ 'e]ƻ޽{;w.W]u3oS~򓟰w^y'صkWq75505jz+g{{;H_</@ K4M7qk34?LG`x}A~pHDC |A.o;ZC4"+TÎaÕfOgCQdTEAQX4TBeQkkDb;a@ D  #cB?ݎͦⰫl*i;.t t & F8Ca"4 /YaVXA{{;'tg7ߤý˕W^=C$aժUTUUI'd7nÆ c͚52tP ʕ+ihhoLYr ~!'tEEE<?`׮]Ϋngʔ),Z#G~H$Ơ:+V`Ʈ]8쳱/N;HDvv64M233QU5IxU~(2`,ݧK-,lB !_)z}L "Y2*SA n/Ntt2xq966EUAFD'u>cMXS00t kA|>__/ 1 ͆J#nGUc771ӌp t]՟@H|~B$5>Us4q,\0eZWj_%jSgWG *B` V.YLÌ:2g@$01t4MG۰٣|"˱%<)e_4cQ5- Bx}~z~tC‘hTU9TT M'iHSJNfӦM5*il $I[\p}DYp;ZT*@"(.4ݧ?WFQN;4jjjhbwv?{GulѪ[V1c060@LH ɛ/H!C @M+c@ƽ۲d[UV3Uifn]{vggΙ3;s'Ĩz}رcpYfYZ,Xܰl,|50kizDt"]v ALvcƠa  bnU 5"E Gt ˺BfaIID}AzޥuCu/,,;s;Y>|x7aԱ+:hb6Q$A*m1.Q;Twf&|)=| > sNoE]ď~#6m#</LKK /~67p~;EEE^srwvc,Xo}[PZZyBII &L'? +WdѢEx<׿r)joR^^+d޽oc̙رV^^δiXt)(k.;<|AEᡇ_dʕB!nflق <| /D4_|p8}… x~~2MHLL$##@ s<,X` ZʠjTUyQ5"N] D*b:1'-Π,D5-Pua=T&BR.!|Pu_A@e=p_ݻw3dx *++ $%%?믿{?O1p8/}GYYդaFСC[隣MӘ={mu2c ?|~_s]wx"33OP^^O~~~|r^x>LRR?OxG(--<#r-ӟ4/O3׿n)Sp5P]]ӮY'NxxG4hPKɓ'QUk[`R,)X*EZWϔ4bJtk pbJ?CE/ZTSҢ >|k7+e˖^So7ɨQ8p .".]իihh //gn)2ɼݻ9sG  pt}.ӎancI8LsQQDIϓ2!ceui-] ::}Dw$w/H0FQu%la Ov( xwtڎ&n!1CKhkkԩS$$$p9 (..n3rHN'cJMM nAѣGx<3EQ8~8NÇ&UU9t2bv;EEEȲȑ#4V@(ĉÇ 3f zTTTQHJJ!`r>|8R\\Lbb"Æ CUUn7x<4<~?3h  ==!CPZZJkk+&##S=lw}@ C7nòi@(reseP8D8&  +0*CCCk޼"AU=' jj0{1V  Q᷈Xj E>hB >͘Id6rT+9B1OMjEG3{G"5]O$'(%%%t^iƔ)SL/iii`ڴioQ0aBgN:..7Tۍ>n3}u_kFff&Qe hfMeN4 փ= K[JX`BBh@XQQh;HvB@0ˣs&E#lg$ʱ fVhe,"td&] aUHCB8}BgRRu6LRyGE[3n_L]I5۰fZ`"XspT`.Y|3¡kV V"FTBIE!1;1YEgWE]0{ tttejkk{pv<0Y~}P^_䣷v1֋&@}t'67^+B(/ pa|>T4jjjXbe`Yπ(DN4 1F0{$>]:Y]v#і[FQ 1JR۴DLS5԰߶mXz5z j Ȫ1 ~s# Jx >5Bv zI8ЎYsR׋ v'W>Ϝ b3c`SYY7n4 z1dkɒ%Jz#mmmpwx,˂ g +— 'aFSjX4 D R/*!PE*QR$J0/]yZjTFPCUmzއfY.(e P8]Y7.?ӧO_ڵkyGYt)?K.'|AqM7QTTq\u]deeA^wC]]< C ?!|禛nbr[o1m4/^$I,XA,X@aa!?ɡ2|~ߑ/~ 6nHCC /ٳg3i$fΜ#< <e]ѣGO ̞=bo3tPx 8=Z~6n܈ $%%1u:bE1eo* ,X g IG hF>Erkޖ vse"#d"JiJE4-VE5TOZiYB!Vr httv2 ]eY&..>̙3eR\\Lyy9@TS z{/I~~>6l //Ahnn$'&&?nlkkCQOΏ~#f͚Ş={ؽ{7'Ng%// 3SO=ѣ"33փ_")) AhllkO?eĈ@jj*-+Wr}1|>C4McС,Z+}{\}TUUQTTĴiصkx\z饦Bg`0޽{ٷoG5IT222hmm,X/JJgD4}v8 D,DC0gA4!8T 5/:ϼkyĴ4:ҝU 5*HP vCe2hᡣ/aSlڴ7|cr%`ۙ={66#Fk.ikkc֬Y;l޼FIFFW\qyyy=ښ|@SS6I&f>#v8޽CƨQp8̄ կ~ٳ\'x&͛GbbI nvmfN0'Of„ \pZ0|{tϬ /7 4;3>Nw]s0tPfΜʜ9s999<#TTTڬY!I\p~oVAAW]u6t͛4'v9sIOO7iqqq|_qe&1~x+݂ g/6XCߋhtۮbh7}rۨiM &Ω,$86\q]N✸\Nqvv&% YFQ"ge4 %(+B @OGǏC{χ?ۨn.ېd MP8$+=ilݵ6\q/M- hԂ`Y9dvv?ιD Su" J"Nn ,Q#Z]%o"j$1^X51"0U5*UwyW\-=/G:J*(  ˀDlD0d"::}R:Άyu6TݷכeA_}ĜwUo?MӘ?~3K_ĩ1:`R,X`, 6$7mL?$rI'(5RFGG}sqNiI$"1>8Վ MeA%h T"ʘ^"G(tQPTB!!@H!VQTI]WגN]l`(LcSGT3ʽgL]uegnef>6,X`, | 0ݞ'HOK"+#̴dvܜ4$V!:}AܝTյ!`mz BI6 M2ÇvNr(*jB0 M0@P8L8&h(J,*㝸\.;)I"b0^Vw'm5$!w` U1,!nq{qwx);UCbDiiId> I@% 4" S7@ "  0^pT5EA}-M"5ŁMN3 ?˲,""BM]{ Z8{dAb@51ׂ ,XW+Jg\H^/Vinnʚ:6i ;3'6YO`$'9Yr͊rY H"5UO|C Sz@XG]c mC*v Yel&E1Bym ,e— S0-"yRFHTU=$Ǒ@Jr"8v MӐDI'5"1Y=<" wEU% tzuAmc3--M#6TUY(ۿ b1, ,X`, _ "ꪪ"!i"BDvb|n5dIaIhC +\+IE IA/Q몧%og'Bavmn/>_%]#^[͢˔ͺϺ ,X_E bAu5RϬD(VDQ j** i "t Mߎ^dXWTU> EKꈢe؃r$ThX۶ζ։k_2m3ї8vv~?q"g=^,X= #Ƙz TU(HН"("n$h*S`ȆQ"^z%8Ν;9t萹'xm۶PTTÇzܹ#Gi@۷S^^ >}5kK/}^UU yghhh_d~UUXt)---Z mnk׮];v [Js,c{UEƂE,XW~Qª`)߂@$<1D$I@$zO  Q1:!&1CETT]BG"U$*u)W(IbT8S0r5KUTa_1n8.ϳ>޿r~_#>(ž={غu+1^EQxwioog͔cǎ}v4M$%%pB<+WrwĆ xw;v@ >&@eeIZ[[!֘Z IDAT[n[{n\.W̹%l۶jYv-\T~?~ӧ4'Of֭[ihh`ŊTWWꫯDkkkj*?㏳}>ɲ̹瞋өם_^NQz=#F !!Ax{)//tr!|>>UVviٳ{STT硇( ?8 |rطO bȐ!n~m:::Xh^)BKK wf1rss3O=-_-M0ndZ[[1c[nD˳ƒ,eBw bP(D0DQ5J$\JD$IF$DIa !FR'# Fr@WTU+FUF Ͱ( !!F(Yiz*ƌU}tl2-[{Gjj*0`&L0UV}ٳgt:),,W_evTU%'' &PWWi$%%q7""\y|>:::媫7tR֮]k[PP5\CNNcǎeĉ <Q:t(("IVbXСCYb~a1ظq#\s >6?^x]vQWWGNNƍc̘1hFvv6$5jvP(ڵkYd 6l u]ǀz, ưaHMMʲl IXt)ַHKK5dj~or0``8 <#GL6m4 ƱcǸ袋LB%\INNw%!!tm/FHLL.#++p8̰aØ0asSLa֬YTTTČngȐ! :|d9v`ǎTTT0{l|Arss /EhKSԈr   !4J3+A'1 Bf`$J1bWIHbW.ADH`ZE@ĪMaUCCoMFs$]aUH1H4 DcJX(2EQ\ʕqa$J6l⋹INN&''G}{wx`0HUU~̙3),,$7n&##GNɓH㸊Ⱦ}$ ٽ{7)))?믿aÆ1(PSSCVVǏ.brrr;w.YYYh"Sy7bҤIlٲÇ3qDN7 rrr_P*>1:JE^c0 !LGQ,: n* : lܴx;oѫ"Y/gG0z#&-4 nަGp/EQ⋌ow[} 7+!ېeTz;f̙/щ͉l "gZO4b:$w _?ƅQe\̿=2+C,= &uEK̽Ȍ=1S5be#FU~֢Űf- é=wI@eDr戼%9ɢd 3,k RLʙB 6ȑ#GX~SNDhZ/i3j~VQN<(b04sO棏>" s^oc\RR함EJ(,, UUٳg6m5=V7o@ @MM p*0wD2<ΤFlذǏs×=0gF4[neǎwցزeG>e};,X˂>~d%Inp:q8]\yT| $J,>ɲJ0vI"DJItUUFjb^p0I.*}݈_iSҕ4,HfaɶHtضcW}@c}-lnڅ`q81> B?LBBzjCQ\\LKK p)8@ss3qa9c^isAӴv{5/JKKzlٲ?444PWWٿ?MMMg$YէH>`Æ 1%Z[[Y~}$т`YЇbPnrLbR2 q/k)dv+B$]xLXbID-h!u괞%=fp26 ]&!ErRu۰,]NsS-_3j&Fjp\9Wbb;9r$3f`Ȑ!0`sӧٹs'o6n~?o| znx 6l2@7|饗hiia,^7|`0ٳٿ??G\[oe9~tin>M6v~G{{;Pe^ۇ,=z5kְ~zoߎ5 駟( MMM<<RQQʕ+FCC֭ߦL6.۷o+_ذaC'b9Ž;LPXXȤIzgM̤Э&~r!$IUVe TUUqqN]w,3bرcwKYYx^:;;)--宻bر=$Ib{痿%C [oeÆ ~GEaذa\dee!s5{԰w^GfΜ9\q֒˂ رcǝwɌ3x'  >W rԩS2e >PQF1RFcc۱l17ƹo2c vMcc#fbĈ߿_ +—`KUUdHt w%Ax^JNVqTn*GA m@)(!7;. Hz]@ TO~7*txQ'[RDwm6ŎC]cu 7SWZar5,T@8 A4Lc pW^yɓ'3e x77o TTTbN߿?{.SLaݔ2mڴvq^z%\.IL"v?, ~?/0yd cРA1;̛7>͛73aNʥ^ʠA8 :J:::3ϧ Z[[F{{;7pFbʔ)fU0$uV>MEQ;v,f䐟K/cr4M#33Ǜ K.e̘1(ʕ+9r䈩f/eLOJJbĈ8NZZZ(,,ѣp81c˖-ĉz9s&ɉ'>|8=  vǏ7ǡzddd 2v3WYYikv4hN6̲5Ͱaz,PZZjcǎqkR=LSSn|>ըJnn.񔔔 2ȓO>СC馛VZ6 ,eJn*(IDp8l6vNk9]BM]-mDAnEf,FU"Z;(zHQdm;vRH&'+YHO zޒA$I2g̚X}OƱ7z#ݭ x^̙3{ޏ%K0^gsG{K>vt^N}4 uVcїWooNgg'_~=N8'|BRRrK\_Vg?&'' Yg,E,|B< 2~hUU51'Pwe<ቌ5 YQP0' :/I:s886vӉ,I5MŒeD$Oݟ"T?zY<"t)2.+\u6½ݾBv&?.YMN.žq8S6f̘ay|i{:ӹֽȍ(z3ПeH_dL߻3ysY-+WBz2pn7sHHHЋǹ NSH'/7s3|`FC7f;z(G aİ 9'AdgeB+I !2Ng$&Na&FXuO`m?hkQTT'aVAॗ^,tI,_yr`ܸqQSSC  S__n07Aφhql۶WҿtRnN:e 6PZZGա( ---<3F>aLss/fL6c||vŒ%KHHH '''9rEQQn?rHN3nK.eܹfHڸy)//g唔0k,6os=ڵk)//`ƌ,X{g5j .$;;/i&nv6lwAGG̛7/,?dggs=Ęn޼J&Nȸqb޽$RRRz =\.O`̟?~?~\wu9rIؽ{7L29e]fzp9N̘1'OPRRBAA&MӧsW`v;W]u+W4I_W\?ξ}2e )))m{Kcc#Ǐ 555\ 2"EA 3PsrrHHH 0w\֬Y@zz:7t@ N8~$IŋOىn?s+o;cǎ58%%%tMdddy_ϵ^ _{p8S(Trss{Dرc+<_軘̨L6n馷=¨;a-GޒG>ϣ{geSgui; ㏳h"f̘O?͉'x<,Zn3fO<֭[ٸq#?C=c=ɓ'&I0 ,[O? B!}Q3V__o:D1~a^u4 r)rssԩShf=eee̝;;w&Ob?&??EQ|&91c .3cǎqM7qrHJJbŊ$%%em =zEz믿o9z(| iiiTUUqLlO{{;Gȑ#lݺ( n^//2k׮孷" hkkCUU&LMزe #Gf)v'O?>{n̹=Crr2+{e^p /ux뭷hiiZ y<|A C ;@UUx<17{gUuם5d& (q'nVHZk+EѺ(ZT@D((&l!@d$>G3\弞'O{=z4۷oK/mcߒFVVׯ/?f…klld޼yjWfMz̘1L:_ 0o^z^{K/T)^/.pp8pv5  R.j~ 9N?DJDEEHiD$m%sBk I%JB=W`R|TL)}].,c49sa0c^z?վ> ~;֭FWxx<,[[oUbnU@ͅR&%HgPEFFtO;<=%?xNN`sKKؖwL(BE{^~_I FjU}/gܬSz 5.e֭[f~Ql`F4x) ~9d뗛l}$ 9zi]h׏qU\F|sW@wtb;wb.@i(|T=X00Jb4ʧj M2E^ddm1bjz-=ƶXٮRe_EBJy&D29/>(|jD*p"Uē|a2ڌbطo. &qP̲eˈaĉ~os k][nůW K d`X( :PwE1"d]2, UJ(CP2P `Y[¡čTF{C{WpWte85KS{#x6%9X0B4̞=<>#Ν@v.wx&}f3N3l^I/))) ihI=ڑ@w[(1XXnULlݺsε'#F"?+̘1R$I믿CSLe977we |#+++`֬YTUUqN'fSNhxꩧTq\q8mKw.Ty5 ?yGB :}o *RC?:egg3|~4@,MSK)?ŋ3|pV\RSSCNHJJbŊ477STTW_}TbcoǎS#wE,xԺRsslJ¾466ukAyQQR7Kk¿/jkkׯ~"_ O!a ooTMJپ`f3iiiwcҤI5;2VZŠAկ~7rAF͛ cȑ撕hgϞr=0sLz!>#rrrx?>`ܹ{̘1_g}m۶ǥ^J~x1p~_͛ٳg޽;< ;w;wެX /5k(..Kl2N>d;K.$''swcl6?85550IZ~?S[[K.]={6| MZZO?4& ă>/NGp L|9233q\DFFr 7=z'IQQIII!<$W_>|n v;< dqݤ~>s"##3g̘13g2|p,XҥKYbSN%222hצbS~ansx^6mĩSx'<ٶm$W_{i&-[FAA%%%m|!;wo%&&L||<7x#_}uuu̙3ݎjeРAt:X,~K8<deeϔ)S$%%>JAA?ngl޼bٽ{7:u={e՞Wڵ?Xvd2o3ԄRngΜ9lٲFG/^L^֭g/ҥKٴitޝŋ"V_?~nݺz }gp-O+pM7ܹsٰawqqqq|V"udXx'hiiaǎ̟?z e]{Gii)6m۶駟S󈌌Yfq9Uzl6cXؾ}; sQ>V+MMM :<6mD}}=)))Za(<|gT;ylGf֬Yۗ\z!f3]"\.MMM~]y:u̙31c7xjl3g3gΤ4$IR6 Vv8Ν[#Gɂ^Wz,˲ln컏lVr]]v}}\.f=oρ*[ZZe񍍍i믿.8p s8,?9''obi^-֕BCCCf}=Z=mU߁ۛe'Nȓ'O;,n-Ke⯦sr\\\є=z[߾}Y|yP3(O>_X8xƠASS{J`CM "pL0_W PەPXg7sWagϞf]z4iy j bK O#rW_}w$IO>Q쫯R}R `G|͠2Y_&\.9nn æ&,;v_9nS[[FݻwWg2|h4Obڵ!E`{|߿Pu @, ZuVZE޽ٴi{>}Yd2#8v옚}q?j9sPVV=FP:ıcTSNq؃r9dY&77#GBGUŃ`6ٵkzlSN8rgΜQ=)--m#^/ǎcΝTVVvԩSݻnuZ\\'ɓ'U h4q&z%˲?3eJ_nr8TUU1zhغu+.SWWGuu:Ajɓ8q˺uXz5HDII WTrգJ iiiL0L:G!//7| bo>?NYYYAxn6֭['dAO-x)jo>Ə)))Aדڵk9{,555\ Xhcƍlٲ 6pi)//l6SXXj塇b˖-޽Yyꩧطo,Cq!VZj ''?+Wi&>crrr<;vz)--Y9y$۶m 33e˖RQQ}Xx1m/ 6p)"""Xf ;v`SWWGMM ߿_<.\/lfӦMl޼^{q D,|`…dgg:t(}Y`Pc|gy/LNNlذB}UF2,[l֯_ ZTUUqAx6uXp! .TMhݹs'ǏСCl6v;z<X0tnNʰaèdРA˾}tζm۸{;HJJbȐ!3j(uBbր8q"jr̙33gB޽կ~baΝѣG"##e_t̘1c۷/4 {Aҽ{wz=gϞ>|XF!++S2zhz=0|p1}Ĉ\}@kVjj*^ϸqHKK񐝝N#==Ne̙~NHbI)ڵ,D.rM?^M;v,qqq%\¨Q7nդ1sL> @^+[yWdddEJ˙>}:&LЯ_?.rF&##)S PP&6 mdTTf1cƄR K ,;v,6moÆ C/k.Z榛nR#SiӦ`-ZӧV˗cZHNYr;EI߾}yjL&N<ɼyܹ3w:[|||.]_7ߨS|߿;MXX9sFCUUU[n^zcۙ8q". CRRn[5Zlڴ>m۶ծ-lDGGh駟VG}Ee]ѣ5jOp85ks+o0j@p9-Zā8pQVVFCCc{уB?3~xN*ڠnӥKu4߿?w&77Çsϱj*"##ݻ7{eݺu4O 0QFqҩS'mÇ$''LV 16 ܹsː!CM\\" 3@i$l.dffi&f͚Err2MMM;wDL&f&vN'QQQ~Ӌ466#|rt:Záxr p`0p:h4l6Z&VKee%qqqĨEhlll6ӭ[7UT+BVX۔CЀhRXXH=z4MMMȲLddZ00.ZZZ#%%VG}DUUO<Ed뗛EjtS,IU$%'sQjjj"nv&@,!~F+X=++ ԮBr].yyyw{eo{"6YձnG$Z傂l6 :\5559sȰa:Zs""":K~ѫ`%(U`!X)2zͤ}HuTP UP VPl;ܞUȲL^,,ӹsg?s LW!ςɿﺎm1<{=_/ 9ׅ|C|}n?.&D@ X@ B` @ %@ K @,@ !@ B` @ %@ @ @,@ !@ @ K @ @ X@ B` @ @ K @,@ X@ B` @ %@ K @,Ye};.p{GsZB/q;^Ⱥ#!?=[Ϲs-TV6qtOQQф$Im~I$I@m%I#Ux<^l6'NP_oSrfϞg̙3mK/)KVUUyz ~őGoK9>$srhtjnկ_i.)!Bu8\.,hHOOgϞ=A ':QQ,j%Z ͿņV9+z?TM$;dÆ0~|8,S¸qB^W{9\{m7kRd^bhڊXf̘f"n /@]]w .c̝;Xqx DJKKٳg$ѵkWz.qƱvZ&Lׯ*_/7k|rΜ9ŋ1 u]qdffZ:gMZE8*+q640ڼS?O=z CbO?QSCөS }}ξ:ht}-(&]/*h4 q` w  '"3ˋ#hQ6k 㥥EK0-?^ qqa_NqqaAg4j$+_̀]()i4~B,=ncXhjjvzٶm͘L&ضmO&66lnJ>}hnn&555hbwBB#F`\r%~n7f_F#dffr0 7PSSCTT;wdPA׫WwJŚ5bcƢ G<6={b̤ L}"݄ 5:hzLQ~=у]Bvc4Ua-蕄,%ēN*~FAxV{Q[ 7dpͽ #9Dq굪Dt:ݿ$$=o/[-[Hn:Osz $z)SQQFDKpr9&22izϒ$QUYARr2G&[o,an fi`mF0%2QQz Hj#JdYd2зobHLlw [J:oO{mnZ+")SH2E]WhoҾr"!%+2:o/* -oGe1DGuҞXkO^yCE|"nBd XA}ĉ?໐ q%1P @,@ !@ @ % kS]Ǽ9~ @ IGS۷`TTX),dI6W!oӺWVgvzr|uZDNNo_C$hAb\z]|~EDmPɘ1=hnv2p`MQȪUǸꪮ̛fUWS0m<(/5'0?ry4$ N""Bϝw~BChvQ--md߾2n/zO,}lܸ~;Μ9Cff&=zࡇ⣏>R'\ӧ?)--^rb9r$rY@ ވ Bez Hxٴ :0~ԓoLoKxadd$p`z$I8d -55tF|#R V;^eF#_=Çb^u3sL&Mđ#GK/INX`W@ KS!QP`sTUY$p:x<^7$'p܀$&FPP`z<N"9t&J9z $[ѨpD-Z=[^˅vQ{@LL >|j9}4۷oK/n/@ XεЧO"MH)ddc47'f{dd3fLRRLՃJ+ըH\zig IEV+qP%^aժ\r? v}F#B+1cp 70bnfrss4iqqqtM Ξ=UW]'| +@ VTpѲaúΝ; 2%2`_`#mp8| dn&o@c73p`gt:_Dzmvԥzh~ōd뗛zt:=z$ITUVѣGm/NDC^YE,:o=/*Y cddzQEG80)5Bgo <@ E(h+$ CsM :?ԑq$Ix]., u!HdH$6%%/nr v~ab){+PԅZ'Xӧ|/ZD_h;9dYt\U WK WK n}q.7 pYٰK~>^)kr.Y67/ڛnZYj^_mw2z:u@ %Ip #*-Sˣpr/;9v-Mx].7nݰct4qmk>UCQgׯ:'\OF#.#.CTVLDb" 6nl @  IL=dz+7sfz*S0DF8,d{m-n=v*z&+I1}ddeYKڵLCQۭ+2)#i! gHrEzIۗ~S?u(:LʕW38} N|!#F gZhFluux\.4Z-q8zlFӑ|TdeniQŔñ {4WWsϑz5:Gc#Φ&uLjM@ #2{/b 5yY'c#_6Y 4ؿZi,*"wo61O '>X` ڪ D'%=pYw!:N i4^6}d$ x]@ ovleY̒:+|]G+_@ %pD׏ pp8I @S  9S@ aQF1ۼbZ,wy:F#F0$I"))6 O=m~/} m &C'P Xmjjj8z(%%%vɣu::FNCբhjIINN&""͔A?E&I0rH8pzkO us`ݹnrrr>|xHLJJJhll$##zV )dРAt^/\zl۶ ɓŃ~ZA|CCdggx0LJ׫JWF_N'NOLL ݻw'!!Ngx͛ǧ~ݻ֭Ν#<<jkkL&(**rѥKbcc`0-`X(++vMZZ'N@лwo=ʕW^IYY:u wNΝ)((HzF7Ge֭ ,YaÆQ\\LMM =z ..\t:2|pˠA0 W_N~~*~Vݻ{RQQj`0Cxx8&0LE%I,"ZZZ8z(񤧧pJoNdd$lڴ B>}X,lذ;3GѧOF#>(;v %%kF4-[Xb׏w^ꪫzر2gٳg?>'NogȐ!~"##IOOgٲeXV:D^7n}!--{q2}tvڅF!33K.Ȇ 2d}au]B\ B` ?mW\}|駜8q:pjjƈ#4hh4\.$EnDFnn.ݺu[nh1n7 0V^ĉٱc5|Ǿ}ڵ+ÇGz0aڵ$GpWPYYIll,7n'** 60w\y~vڅVe޼yXy"""Xbo6mf̙>;wҩS'3D҂,DLnZYr%3f.>.BU(x7Xv-'EQ0ah4񥥥,^0}tRRRxӧOgȑhZUh)݆J#\^^` ))P u߳P`Nf@,' ( lfͼ;8p@5 a=6` t444+p>:uꤊ,h`FCΝHV(c; B`uS|h@ K?.| C8&**JTQd*pL@quVN8#<•W^fks^Yҥ<EE(AC(xiOCq 7㏳w^$Ih4NYS~|XhND `4W_%''Gރ2l6{ ͎>wM wlxXVHII!66(z:-Tr^ Yd K,FQ\*ӗ(9jijjb 4V\\Ldd:|Y#]_{uڞp^uvQZ@ K!hu5/..Zt:QQQTWWSQQh$""xbbbHHHPG .FC^^k׮e'|E \w0ak^NS__ϫ3 1 t҅u"K1.. `޽͛7srW@TT7oɓL0ᅬ^gرtܙ>H&NH||<{aZ=֭[#* IDAT9|0~;.믿&--;3SZZJuu5r _|,3tPZjQ^^СCE% B` MQQzw Iuu5z52vx|l***ӧ#GTO^vmii)۶mcΜ9|'8NRSSIOOg0{lvɁعs'iӦm6bbbԩ_|Æ @ @2t:q:tF%\dB$222۷/UUU;wYUѡt)5 GnXub[A]]Hcc<<"+|s)ۃE̔<,^'x/LeZ-vJ!#??\$Pݿ5 /at:fϞG֯_wάYT+UhjjjߦO>TUUѿV+TVVoŘ1cZDGGrԩݺuc׮] 0O?ǏhѢ@,EHKK Z*JKKILLTiEn3ݮHee]Ɛ!CZXVv;^Ν;&{<%77|1  Ɛ̰3hW ;aÆ1tԐhjҹ`s:TWWM^&Odr}!27x#Ȱa ܃nݺ5kְw^\.6l`ڵA6^/gϞ UYYɒ%K8|p(iGٽ{7|:@^v;~)555!f7n?W5k裏x<d$cǎ[oQPP,|g|'X,s(Њ*x)gS(++&<<.]m|NgF?Pװwзg4^M^'""Dtt4111$˥a)s)5Jo"oJ)55 ۼ,x< iiivfffbXHHH ??ZyjjjHNNfż\veꫯEjj*YYYl޼G2tPvɻヒ;sΐ3朌'I CZu^ ՖNEVjA{u(ETQ2"Ȝ3O?{OԊ_aӦM?K2zhZE8Ovv.Y&L&S$.{-{.&ZaGFOi-=О|]J@ &ԩs~[9990uTF Ǥ)zwSZZJ4Z"K}%(!;ݓspN 7Çꫯ3fp8ԩSپ};̚5ѣGzj>l̙C]]_믿SĪUٲe ǎرcFXn/`55kP[[ˎ;0a_~9tvv2j(xWnK/3Ȓd(--;^СCa2332}]nvCkk+d<xY|9l6s%իWþ}ؼy2qڴivi-;fјL&, XH$Boo/477c؈& )C`V+&IQOjeĉރ566 ٰO>$>r~uV8UUUcٰZXV%l,رcJ=ɡJZZ_~9JG! q:L2buY9@qq1gqx8쳇L%%JgPj"[ZZZضme_bǎܹ#G"VԨKkES^tp8̶mصkb1D"I$QQQ1^KoY]?:tHq?5+Uˍގ;x׸袋 33BdddeN'8 ;v,+V`۶mL8R)--%--f&LjeJ:9oXN5伖?0\lٲKray3`0K/' o3c ӟDCCXV^|En7G9ɓٻw/GСC3k,n70n8J;\XXt~?k֬Qbl6uĈe`0/sA|M/w^ND`2+<WWK?gh45 rȘ4%`0pB{=g_ͩ p#HmR?{`00qDg(r,@ @]]dff*S,FRn7׫ fBhp8OЩҳ](~ U<W6f%4 bN}rndʢLnuNN2=zTe@]6M٦zEVr"N=.~0!u8vYG;T~WO. X,)'XX!!z =KJN47u\sWTVVztvvz&##c@\(uC*,dy\YYVK$! tE1X0I`0(X,FZZZJ`UcwFb=QQ1,97{t"C*W!"G닥`0z\8xToXjg=B` N^O CÔKD< g}B8 ay&^<*9>F.U lET~~n#'D999ҋe%lZ1>=p8L4% )I? N'Vzbi@ K 6rC$?d2kԊ=Gyu%vXSXT`w'裏*^yt]/{D  >7Ivzc fMn'D6 LGGrY'PQ7Oo}$a x<=zXK.buV̙ʕ+8tz Ĉ#8YjH &0zhvލLgժU9_|`Ŋ3sLH`x<A8ӦMp l6EFFfl޼N|AƌÖ-[Xb+W+WD8vŠ!+x9묳xG"oRPP|x뭷=z4k׮B%{@,lԑۑש} 9.;ZԊ+yQ+8GISXX/pĤZ`$ *++q\L0ts̡lܸロ5k`4yyWO^RfQZZJ2o_*<555dffrw2i$p\C&C! I4'ZR%cZgY|<.np8r %:ZgTL&9992qDƎ=عMƴi#rUW9sw;s: |J*s=,?_xw@ ,X:AFg%R B qīrt40q7L&J}m墴fϞ}B"Oq|k_#334jG=Uk49֩Bz!d2 FaM]/ZKrR9pBO0_TzP7n_WK%TTT(唔L-C4Mhj^e'B` 'GHV%}#Z_cR)eX,J^݆P' FA^^^@FWަDeΜ9\xX,,vN -u"@ Koixdkb! pjPZKgg'>5H>_VVhTIM*h?!ac XD"wꪏ5T@ BJNNJٹs'wV"~Ł^+pRYQ.p8L0VT3.KW\Z'Y`)ʿn-'>@,5Hv,׊ >@hX,ذa`Rl6gs}z)J(.h4DALB1Ax<έ~>W'bQN#?8~ k\k;@,)afYse6gRZZJVVCa*ZZZcҤILaYCp8 TWWk.կ*B`޽; @6j(+OD"AAA{/< uuu34jkkh4*)N'Fb )))IOOG+ Lv` ZdAq8/Eyy9nl6""}}}tvvr~?_|0aK,og~Ø/fٲeL>o,_?p8 Autt+3$##Ezz:~;gJڢJ~1i$[PPP{6mb̘1ʌb233%##%K3+I!e@Milڴ\&MzaeHMm B466*= JvD"immiӦDn GȨо}bZ :::8r&P(nGyds{$kt['Rl&U^Sz'S#_x<222UY|:F#] ͜6Ӕ\wf yhkks̞}yP੉"|tn0`Ju5>JſCiLb`@ @SS~)2 gti1F;vh4lVBp ,ZHq8Ս`iR%Nul_.&rE9TUU>TxXA?NxX_LNN |cZ9sq\Ja% %]}^isʅB!ZZZ}$`y_6,.kPw"i\Dc>380R8B` )EReGx8f@ @ww73f̠_:_ЉTdggo\ww7}}}b1Z~_t:}>j89Rӫ{9\'@`0O-ڤ2`ve^Bs>_`0Hwww΋L<P($rV B` >,Պ墴\l6ʰvN^^P(D R6XvƌkJ% dզMX`w_\`0?K/đ#GugZ,|AWo Blذ!L2c=r~iV\ʕ+ <<裺" yfznooGaƍmmCN0 Klܸ_NDL˓O>Igg@ Kp{UNNt|>~elYq%mK__SH{Sm4W\mݦX>:ؾ};Xs2b꫕8?[[_vE__W̛7qan*Gh^[na޽/X,Ɗ+ַ+Bcc#?Yh6l`˖-<`0Xv-sW^`0 c,Yyimmeܹٳn.B}]@h"n&}/^̭ҥKصk_Yhzng}X,w-ž={x衇׾ƪUXz5oؿ_W,?OyW>&,BGndQrj466RXXR].tuuWP|C4 L&vءQ=Yԍ``ܸq̝;_OёuEl2~o}'xuqAZZZp,^n.\=C__7I{{;۶mcȑttt|Sh1cpuΒ%K~xwuO<s E<5d,?s}̔)S3gO>$mmmtttPQQe]5\(V_yFŋ)..oj.)yW^yga˖-nPRR``Ν,]Taj9N~_3n8mmm<x<aKil%''I&1rH%J&+~_q׳8E"7n\كa>%~ŋ3o<֮]KFFVd2QVVF8fҤIdeeL&Yl#G$++uŔ)SBTWW+ v81--M j8vFOK.%iiiQfQTTz8$h4JSSPH$BCCMMM裏R__OFF---/L("Ikk"N s;F oNWWXr%P]]ͽڵk㔖׫ BڵKɫK8VZ1c$_&t^XXDcavAR7.KrC2TfEŢ Bj*BK vН(/0L1b%ɢ^{ nj3袋!??#F`2S=O:ݻwsUWQZZXbʰvXeƍfsepYgsN =z4%%%8%;;z)..XezL K?F4),,رc|>3S6ꆥp8LWWS۸qj& Tֵ4`0 'jg7UTBJ2'Cj dȆeh<ouxEhi玗e ъG>N}/]][ϡWZ'zՖg0ѣ0beKsfQ.XϽ[gz<0aF@ @,Sh?EmRNNO,?,uߊ .B֭[ǁ1c-"yf.B?sNz)z{{yx9s&sfӦM\~? pW/r1:~P[[˖-[5k/qM7ql2F#?yg1Ltvv/yXt)W^y% .$ O?lL(aFJX ֬{oރ~ ߐ~=Eo@ET? kk‚Mr]ZT"!?g vWKzzcnfϖҥp饒 ছs \q(K-\%ms}:+z̄=\sw?WߡvI,d/4wђ8Nx~(/+{y)IݒadIuvW" krxC,:t6Aq1]+]3z4I<%%_­Bu5Du=tGm^ihPmu씎@@wIb~|7Ks,hnh9#TUUxH$Oh룷w4ьF8p~rٱc 8x \.ٿ?b1%o^4fi&),,d2ŔPC/Ԥ؞^/D]vЀ#//{%H.35+3Mjt}f4. %J,ZDSMy$r` I\_/Y]57O 55ۼoD -0r$<" IbHX,R94 w=H ֣쑮=[:n-o6ûv>xIV3_vLvdyK$$j2II!Yٚy-IRo"]7Hu.{Y(%R:sKbdဦ&7N8ʒ0A*:>WU% #@sΑ,c_t#GJ©T:GwT& PH:Z}SR"#%1X_/US"|6KTǙ6ǝ |>%`<Dv8|0njimm%suץLIs2,9:veX0T>Oq땬RY7Ŏߛ~3= %nM'4*L1p8Lww7===DQF#VMww73i$e`0pW*9OK+RE{ 1J=ӻ“YƂuGy>X!F/tA2jRPP!Ioo/0Q!MYkl6B!^xԋ+@ %|*{2faٔLC%@,C/!@q" @ %@ @ X@ !@ @ K @ @ X@ B` @ @ K @,@ !@ B` @ %@ @ @,@ !J$dr};N}*C O;fQTw'ζ"\ ]  Eeeo`7DC=$ͽplL<@oF Hb41Ʉ`~Dm | Z^[61|ddFRX"a?:A,bDP< Lb21 DqF&@[oeϞ6Ǝd2f,X[c<nrsXB\ O^}{`00 &#FH<rQT\"*L ` 9bzzB`G3?JIDATcFoyevQSSO;`68rnzzB,ZtVnퟔsʲ;_$HGGw=Ƒ#8(*r!k/7aW |2B!N9RH&I$>pgH&I$MFIX̤dXFX9K2Iq[>ztG(,@ "<?CZYc_\ fM2 n\ldf̴,K ]p0/"-{ b|K$K0  ##Z!da.ow8,<ЗSgP d2j2[G4%q8(+TB\ NYu#KW*=zJo=6q@p btP0.@{& W>Ч] Nr`21LF@I,;;%~*K$%gHx<.D ԍewyۅ!Td0HHT&%Hqw&a@4$yih K::`4PQYFGT )HbYf8uF@D"AYY0ID(zB` N]^woQ@L&x|!qM~`KcJIENDB`pytango-9.2.2/doc/_static/boost_python_install.py000066400000000000000000000150601316324373100222340ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ """*build boost python script on windows* Purpose Build boost-python on multiple architectures (32 and 64bits), with different toolsets (vc9, vc10), using different python versions. The different versions of boost-python DLL files are placed in a directory structure preventing overlapping between the different versions. PyTango Visual Studio solution configuration is compatible with the output of this script. How to use it This script should be used together with another boost configuration file called user-config.jam. - Download boost source code from http://wwww.boost.org - Extract boost to a directory (ex: :file:`c:\\workspace\\boost-1.53.0`) - Place this file in your boost extract directory (ex: :file:`c:\\workspace\\boost-1.53.0\\boost_python_install.py`) - Place the user-config.jam file in :envvar:`%HOMEPATH%%HOMEDIR%` - Open a console - Switch to the boost directory - Execute this script using python (ex: :file:`C:\\Python\\win32\\26\\python.exe boost_python_install.py` """ from __future__ import print_function # b2 --with-python --prefix=c:\boost-1.53.0 # --libdir=c:\boost-1.53.0\msvc-9.0\Win32\release\shared\threading-multi\26 # toolset=msvc-9.0 address-model=32 variant=release link=shared # threading=multi python=2.6 install import os import subprocess boost_version = r"1.53.0" toolsets = r"msvc-9.0", r"msvc-10.0", address_models = ("32", "Win32"), ("64", "x64"), variants = "release", links = "shared", "static", runtime_links = ("shared", "runtime_shared"), ("static", "runtime_static"), threadings = "multi", pythons = "2.6", "2.7", "3.1", "3.2", "3.3", cpus = 8 silent = True debug_config = False simulation = False stage = "install" DIV = 80*"=" # ----------------------- # overwrite defaults HERE # ----------------------- cpus = 4 def to_name_and_dir(key): if isinstance(key, (str, unicode)): key = key, key return key def main(): try: _main() except KeyboardInterrupt: print("\nStopped by user") def _main(): global toolsets, pythons toolsets = r"msvc-9.0", pythons = "2.6", "2.7", "3.2", "3.3" compile() toolsets = r"msvc-10.0", pythons = "3.3", compile_boost() def compile_boost(): prefix = r"c:\boost-" + boost_version silent_args = "" if silent: silent_args = "-q -d0" debug_config_args = "" if debug_config: debug_config_args = "--debug-configuration" base_cmd_line = "b2 -j{cpus} {silent} {debug_config} --with-python --build-dir={{build-dir}}".format(cpus=cpus, silent=silent_args, debug_config=debug_config_args) options = "prefix", "libdir", "includedir" properties = "toolset", "address-model", "variant", "link", "runtime-link", "threading", "python" cmd_line_template = base_cmd_line for option in options: cmd_line_template += " --{0}={{{1}}}".format(option, option) for prop in properties: cmd_line_template += " {0}={{{1}}}".format(prop, prop) cmd_line_template += " {0}".format(stage) fh = open("NUL", "w") kwargs = { "prefix" : prefix } for toolset in toolsets: kwargs["toolset"], toolset_dir = to_name_and_dir(toolset) for address_model in address_models: kwargs["address-model"], address_model_dir = to_name_and_dir(address_model) for variant in variants: kwargs["variant"], variant_dir = to_name_and_dir(variant) for link in links: link, link_dir = to_name_and_dir(link) kwargs["link"] = link for runtime_link in runtime_links: runtime_link, runtime_link_dir = to_name_and_dir(runtime_link) kwargs["runtime-link"] = runtime_link # Skip invalid compiler option if link == "shared" and runtime_link == "static": print("--> Skipping invalid compile option link=shared runtime_link=static <--") continue for threading in threadings: kwargs["threading"], threading_dir = to_name_and_dir(threading) for python in pythons: kwargs["python"], python_dir = to_name_and_dir(python) info = " ".join([ "{0}={1}".format(k, v) for k, v in kwargs.items() if k in properties ]) python_dir = python_dir.replace(".", "") lib_dir = prefix, threading_dir, variant_dir, toolset_dir, address_model_dir, link_dir, runtime_link_dir, python_dir lib_dir = os.path.join(*lib_dir) kwargs["libdir"] = lib_dir include_dir = os.path.join(prefix, "include") kwargs["includedir"] = include_dir kwargs["build-dir"] = prefix cmd_line = cmd_line_template.format(**kwargs) args = cmd_line.split() print("Running {0}... ".format(info), end='') ret = 0 if not simulation: p = subprocess.Popen(args, stdout=fh, stderr=fh) ret = p.wait() if ret == 0: print("[OK]") else: print("[FAILED]") print("\t" + cmd_line) fh.close() if __name__ == "__main__": main() pytango-9.2.2/doc/_static/database.png000066400000000000000000000326361316324373100176670ustar00rootroot00000000000000PNG  IHDR>asRGBbKGD pHYs  tIME % IDATx}yfUfh4%ZL٘l1&LA$q6T\ DEلX`ppl-/B6Kh4kmo'}_>}ݣ߻ܳ^{^{^{^{^3sDS]xxc3dC̼R `@P1Os jf< "lQV,XB\p9M7pr";&T5]uk[÷w.--FDw Aյ:^fW)"BȲ Y"M{fHY,K2i42H@& "$J)TR eYA)P1q8p4tCfu3|5ڃػgY@U$:BTBB$ %!MHTQVX]8yx1#OP>v}׽~?|}~?pM75GS;cQV5R_2E!$Rbqii(k< }.-aaa>?}Ϸ7ї%8VTeZ)|k^!J_ŕ&_|4]X񥨪Y{_Xyϫ~'o_|4v )$eYؿwv #3kE) k,n`\| $!>jƓ??zs˫2K, ͯ A\ĥ5 /a*H$1qz I:|s~k.4A#L&bTSLÇЁ,^^çFN /؃s-<~iirh;1نheY OYba~7?;n?D XYceQPkoY;E6/!RJ̉!|5O6sGhn 0 kٕ`QUk[o;A!^{fqу7 M$.~   ].6#OG&1b~.f>B_RiS@ bNMkJ߰F U3k^+TP\=9d qq^bȋ QqYj %hg d"&|PGIBE#p`/G1IL'D"%UUc4,J&%ijgQ Y@nm. 2[qe7\;P%B>.1.lb(`0`4hTb( k1$d"i2ICWbC%`LPN"3t-&g g0@^Uͪ K1~ŀ`}: B jQC^PVZ }p/+sOJimQՌ֧(Je,Q֨{БH`VlCF{$$4 I)'`/wR7WdMbD.s<`]uz|Ǔ:QVh@`%A;CF"y-" rZ8yf-du=O" S 1q`)!X;UkF,o#tx.8vDaHx&GrL`BhZ{:]GBh`0hE0Q 0vYα؄ A6>\+ja$MX#44 ?oO&@HzVNZltsK<{S,(` Flu82d6j3 :SŜ+s5l: vm㬲hv L ğ$\t ;<F886q5:/yLДgB]0v @3e~@#q@@lAT86|V3ʹ$ CNrKSho!`'T;L$u]MH PF$,o0ܾ(Њ*mSɓ?Jv@Z wL1UlyiTյwdRs\M:c7^~GFW)[<$81'tcT !(ec ַ~0T ;mlPvA1Cxxu  <'f>tw~ E L^P3 <8t5Ts43'Pյxr'>8iV g#ÄK!Lq`s")WBD(;^xa0@ϡ>Z¤wR2H5 !A ?-zrSyE;(rE_@ fb[y oHo\~/ӧ'8ك'<&@ll_qs1AOMosH#@顄MD(<jS$]3o_i;޼=rw&ŋn<&CӴ܇s o Ȁ&H+ eYbщ$|y9LY+E=cha MdE!!&څbOq'ߡW('K F阰6h?(Jâl;x ܻ!xg?{;cOsKODYWP%"X/7BDSsr?Pդ*G42ɣ0 `8MqK vdSf݊&yzQw}#-ئ9 ㏟xmn]Wqii+k}y Cf P֌J)TuZAi \}.2O&(0ht|ro2Ep) Q)b&C5oe]rLS] 8s3]({|㵨w\<[V LGpmcy FCl ƺVQ"/kl V BHdD3t;j餺WJ$I )|'4A1)!I(:F]+1rF911VL f6DK`,\3@Q*_A?8DB)#4]+@JeG?Gt{#pn&|Ҝ{m(/0 ep>6&L}ѠP!= ` &m$]!4%I"J-]une&^+@[qe<}kke$I 'pFJR$\neؿw VeDb DopB뺻tDQ38k(@ ],j8/zY,uTc0˴)uO$Us+Ijt:S N ],:α1}ƺ3Hҵk;t9Nr&E/3ss]; L A٪R )t !fm*N^AB@>FS?k_oJ]?Xצe\X#%A,+Ee*K G9vi^ndIQC)$vzd{ZC*aR җ5 #.,.#Ifgu13Am*[41Kj:-f@p[mg8ڱ'fMfcU(0?c0ڤ D5u1e"[yg@Qts#Kj_OsYVPJ!Ȳ1T"M&F ~n!Au"1jU5Ψk-*L8/1k?11MrUQ vu}R(An&b% $ ̾@#_l4Dqu:%=3db@ne\fW`&CD;I +j, b~hɕ0!D1ɚR'L#&NYl%5Js MA-]0 MND;Kذ'n4IҠC#'G$#$5!:Sii'Д b(("d7U)s"֝ Wy}fL\T "@XqҢN! Y>N;ęJfI 4,R4`Q<5GW7mmNR[r0N ʬ|9oZmfy+U8LQFp0yB/شa,ς@ bw%@ c3\9rѤbF nrL{#??@Pנ; bg:S\&)e KjwJ3FԠX9uߨ}`5N3741LR,*H{I{\dF5PlLFգª&@ Q3xon-Eh*ꬮ (!v ᠫG<b,Wg5392pZ(i 7DAφf\$0qwNGc@6_V` tr#YP p+0<BE$[tS۾YHBHdaaOT0JA+9=1k062;GVPxբ`Nh@a"%S(GǍN>Woc7oHP8|'#Q !nr1r `yubm X'"B7E!!|M )gLP2E&qMK&r.A' 9Vq`ek -?h5JFy3598zl) SM0Apر'^pxڿ^_P!cp_/0ufчB8l=bǜMKm5ҭuq27< Deֹ(Ȧ%sd_W*ktE$"ßy?-^p$~-/g3?X%"kӮ _oFlXs0є1~rI7nΏkdaǻp!Ď9 F((b6 1)M'YM5i(v߿jiySwff\QDև.Z_@ζR,Mv" C4fp]=1Yl7z6q͔0Aݍ49<$}&fF?n}ͫQ]oo;veZ)/{t EUk6T/K]B|~YK`ObֹS^-}n p3lS㨞6&Q.\\ƙ1 MfgEQg'Au&EvZ(K]8 `6}BQWGa什Ž5ɼ¾=TlʐFlt])+lJh| !B:}g_8/ P Rx__r3η! N/,=;?׾.py r.ہP;l5h`?d1&s%WzQ'qSNLwPX7mt&$B]+ll 1KTvfRO+qzQ?SCNM&|#P,"bea)bF D>M2 BU(UG 2Mp`n<~GُT =3cʌ`[Ap9yH"ԊQ 5`ҵ.ًk=3Bzff7624š;0Db8Ǟ8z GA]JB+MpA+}\Z^jkC,qe:36z`n.zݶM$[Ch~'R n%M` Sl"U`֣JS:X[`?B0p(uPnTAoanq`<fgeQZPDbmc/=yQ%(Z;;8CsKI Rk!MTyتv-7FJ" c,>7py gWIV̖eg 27 IfY$'/JwDY\ yY!+agt#'NkP UM8/0án5ͨҌͮT\\NAʖ8 8;AL&eƤļLRǣG\:sL>{+͢\ՐR: 2T=IL2x0j\?@]eU*B1Os7EII֍QgN `(J!TlpD iEz|`/r#>M'yS?Д1n)6vEQa#4C撜gEaaʶEϝ&48~K A.<㠯Q;Ĩ߬H6\CH+K|p!][@`bjSf'Ѭ rP~m3vA-t &a9JAk2: `[sUR(5S8,b \9bp\<_i κ~7!8H8&LFr8.@ƶ-r@łۻG@Ex9!VLxϏP!JhLu(<0:2: )IiISO^޴o > +mdg CŽȞf|c )0"JˠyA|k+IP蕄8a}K3 AtiT@qLTœś~Fhu0 - w*LS~ EC*Ͷ}+(Ԃ؝R5#kY!`džtW2]#XIpg@4C'fFA2,lKw5p|'}lzS$<=Ly4B'&^X=ϗ_)e$ibz=zǾ4 (ncd ^ BS@rmSLb7˘WFiC݂9). &KwM(FcJ?t]@6Ykgrr))(Z prA:GC$I3 BxASf6O < g ~rĞM͗2 Iu]P|B""4" D&LO:mjK`a5@"e,=Y,}PM~$Eۘ EAmEۤLԃ,;`nDp9֜욀9P5ۇ) gQ:0{_@kkDY)Qs=tnIS"I)յ5pQ}il>\ 4~@ dO8j6aj)鸑KoH8(N_auӷM¥ iȫ2 \Λd&I,./'Ck[`uu7ˠ~;M3%Թ^(i bm ]ԋ"BPX(62l?zFr($!21-%@N0ȠVLx|޽]KKKJjru9{Ozb[ZZeU}4# gĪFAHfD; ؁!|ΟݰJFL  TxJ( PPPz_8FFYXB 7ΒWh8΀ʲ'>stGُ]G y1^ZZ¶ X}e+-x3sn=sYc-_^X$$t\mfJAB_W em`UQf]Y4CmٗnKx [kZD$~O0o_9Ve|' ̫m3mwێ\w&YxƝ/ |=n˧ɟ R3RJ$59BS|7_}{ +&q Z+3.BYi8(`Rb7N P0)%AZ`੧>}Ne1ZZx6lYW0uWN@0@oӝH%l5n0c$N8$qj{ҟ( dM߇6Ct ],4DЪ^I@V`eYZ8s"zKxɧf4I__䙧́F[;_ ϕP@ dj+ Iݾjɍ=]ikUi;&#ngE4Ax ?`m"(9\_ڄ\5 &nR7ɔUn[V2kpM-7_GǏ}H$"iEAA&YHA*#?g[ۮriu\hԩgp|" G9|4̅OS~>UCSUo>Uy$*xJJR44lh滭*+ko4~=(-k9\{_{;BG 뚍׮|goy4f0Apé-$Lj=I|u 9{g^o QU덪=^O}#~rp|΍0pwff'¾#x?w`|Lko3O7xHU&f!|* @:;`wf~ܞ{f"9ȄRfs 42s&ͱX`ADD4B /VE~'`uy"MT66>Zjc7܎gN>-mC.곯F( J?m.~h7@a rc7i9s(V.v^?nÛ4%1%6gfjOӓu5_!l g+vL6h&DWyR0hگMJri lWt*e3!ni<3v/ pivluu]IP+Lϻ}޽vkڽvkڽvkڽvm?s^IENDB`pytango-9.2.2/doc/_static/device.png000066400000000000000000002005651316324373100173600ustar00rootroot00000000000000PNG  IHDR>asRGBbKGD pHYs B(xtIME ,I IDATx@!|||b. (1EEE).  )/') ((/)/'/  )-') a---666xxxz|N|||%FLҀD/((0ˀ:B)/(n6dT"""(((uƀ&.( 000hhh999xV\ IDATf111222bAAA@@@Gi 111@@@FFF꽽_) MMM666D@@@ i;;;gyyyS\;!!!FFFQ'''EEE xc???]DkI;;;'''~~~OFFF+++~XAAA Xr0aJJJv=AAA [}}}߀T")))!!! u@@@333Y&<<< 6"""...MMMF~~~(111+++ ***... 888;;;3||| JJJ###333JJJ666NNN3MMM  8@@@C!!! n `h@@@J"""O7$d @@@Q%%%G~~~_# , IDAT@@@,*V~~~i fr@@@(,Jwww >>>EEE`1$$$%c~<<< ;;;z _'''  +++j"YYY222!!!rrrT"$$$888555  ,,,Xb222 "/<DJJJ))) ***hPPPxxxWp'&&&JJJ 4~~~^*** rrryyyf\\\  ,,,\V"""8rrr/Q )))?}}}RRR&+---@@@GGG񽽽 IDATaDDD)))>>::: 1 bbb www ,./.-:::KKK8nnn;;; MWmmm ]vWWWIEEEZhLLLYYYgNNN}}} ^^^ Rπ@)wLLL___"5%9 UUU }!!!  ]]]TTπ-<U9###AAA 6ggg  <<<'AAAK ***%%% &&&>>>:::555 AAA???|>>>444 f1 IDATlv +++ @@@A!!!~~~Drrrr-...r?.RRR E@@@ >>>$$$+!!!...+++ZXPk FFF555LLL---'888((( "+++}_DDD @CCC@III  222 MMM   000   333y1`|HHH 1w IDAT  (SSS      W***###  w666 U~~~@@@T      yr333@pAAAF m 1   c,o RRR oCCC333e ^ ЏsssW 555***VVVVc MMM򀙙))) EEEXa   ͩ[[[ """/###///SrrrUUU+++YJJJp MMM6I D)))'''輯\\\zi::: S999DDD`???###􊖖 HHH03 1- 444ԇgggA Vcccmpvvv @@@즔AAA;;;@g666>p999=!!! ˮ000:p*** Fa222- "̣ؖUUUL!!!V Yx IDAT  7111  IDAT25c(IENDB`pytango-9.2.2/doc/_static/green_python.png000066400000000000000000022327111316324373100206220ustar00rootroot00000000000000PNG  IHDRz bsRGBbKGD pHYs  tIME  ,*~ttEXtCommentCreated with GIMPW IDATxieY]t;GDFIB6"PV7ZO Z8`mۖBJ+-N(Xb8!ڈ%2I $S13a5/E?m$>Ϲ}:Z`0 p`0 MRJS×rۼO? `9G</}۞'^>'~'~%uUX)BbuD,1AJ2%_\wupQ`t/K|sn"3 `(~G˿>Gs7[N/~䗟xߓk")t(Z3U+BL(!I!dȋ ) -N[7bLάK7`0 C{>ė>)nz;=w|/_r//ػ L2*vHc} RbD)@"FM $,Bi]xJTnIع{E}o~b5 `(Oܑ_{ݣ=L te dF1rnEp=>:!$z@"*)hZGIԚ$4>IUѻ'dF OyBqө3C`0 G7_C^}yRwb) ae4#HH%!0&'ń15XI: C mK1ciKvH1٤NoʞtS?v I `0z_^?4~xʽOhkl L Z-CC:zI qq%3Z@+(Q!04B ->$ΈIbD,>\rF1s_طd_o9O`0 C_9~=P_iBg<8r]׽_sY)5$o0 P|w$˒T_FuBըԁ#'#b2hI1gG hS>y=d,'-1IX9pIP=r mC$HaKL1k]Іל<}O?/8qn(`0|^z^:‡w/OǒtEd4HZ-F!4R \0BI>Ǭ#tdE¹!5>%\P2٘nQ/k( $M^T451D  +*z^XgBXC fu_j7o0 magyfE&ZFF i :45H!AcFv5)hh$ZKbL2|̎c<"%DFT%G] 9)Y8A kI°v£lNLLvn3Oo$C `0|^z\:˭ksW<+yǤhG EXkHɁ,(g%J %R4#RDSA ̩5wt{b^X[fgdT)6RΉ멛.zUCJE1BM9QTxp;ǧ1 `yQ slL +’,J,'@wȬER#  M)&T)R! Bh9&p~E JR4u`%vP Rqy2'H_n>u}^>cN&1`keMjS)G+brF* \ BUN"f&>:;L.֑Zd+\jH)BHhm #ĔSe)u{"熮0D)KnKf91z@%)J2<$wHJZ I"$CD"$J lӹD" .83VgU`0 3wp~nR0iI{l>=E*t\1s 1 %RIRPDZ+R2r"?Af@жe XE)bLsr H%IqLp"_C06!HLkZyn10=K/IFPe#+;$]C<`0}|Fg|o{ՇEKmD\Ϥ$MIpӥGH4hm1!wd k8ZiV5.'3 NPcbfR#QhS(<)u{4k!mCWNZ (&1fB&!-> {7=?;_  `_襔?յ>vxp.͂QB"YXiښL)r F% %GsX"CQ!ZKR'dQ▛IsВзQ*y+&%F)l>qe"<Ǥx|jN !rQ >&\cB޸c zFz<[8w@~WJ+|{u}hTvM__~~ky+^c?oʗ 7v0 C7柾qw&.8^gSM.U\{ #:tXamF^nEmNR@C/I!$=|HH-dl ]KJ%vL6*%;bZhT,ȋ)"Yb@$O jT!`lH1!c$t:DzasX;b&ecm=י =:E/^_|߿)Z[n??}M[s{F oR[!C$zHRWHRWGOHҰuc?U?;`0 w7~}ścA&ZJ\w4eX^w1;R?'I!#B$%F) er9s䈩&FGD1:lryĪC)6 2)ʑ@YC\_ FgqBt4)@ T*_'Б#7b' k@BE2wDx2X5=62V>K&i?C߽< oOHFRA (m׶1C 79)%Ce%N$S_O/=tx?w|0 ޥK\uzOz2.<*8v79q4RK(Z @2!̉)Z̖FEϲ!e${S2Co hh(Juݽx-1u$^bdθu@@J{qMM!YЉY!$fY("$9]@$.DdvL#%G3Y,}<ǣ~3ޑy?̽^>~b/"} :!ADs>ҹ<+Ai$%Aj$$,jO+aʍd-/Jn>u˳nq`0|z}?/lT*L'c)6(x,"yTeIf}7'}V%rA;fF0f̬SF#]_}ҁ-bhQPkoPJaTl}Mx#E6k8Z\e42ВdzV%MQVbW#E|X-[-#QkKL "'P˜>']__f~so7M.sy7ͯ|"02(e6r뙛hj˥GbUOp;P=2L1Xj՚~G9ؿLtm\|]"˗8jbJ(Τ< tM  67G˒A@w }P`4!C7t]ҊL\rR GF[t$x!rSrz$^v/=@"7ޢnh˸eb?äI" DI!1&xX>VCfH!B}"zD#Uж- F@ĪuX[1ѠtˍO/}Ꮚ~w?k4 O5z;͊ U)R5f;T騗Q['ݽe QB!ͬ e"!-&t b0%zC֚g:jCXETɲ1*Y-Ĕ@HbYF  (لE3gVDk%}Sc=5ĴijGb%dr ,Y,1]GAUMe}۞z<\ݏRï>;c=\C"KJEj'f @I+;Ho `($JQ9I&b H!F%FkzIP\͢nX+F1|n乻gnsw> ?8~>]\[YQ2. r͓$:$*ayΉ|1_<GͯQTt 'RΡ"Hf(U6݁dq]bhQHk(ܧ>-FV.L H}$"c2`TNRm:"Sk(2EeM0YF>8!pOS9KrJJ%IDR+H RF詛_CApiw]x|3Où?~~B212Uݒnb<]wỈq!De4?UQFzGjDDi^mB&AQBl<|oz{_{0" sgF.u&.ĉӧz2U9 x̙HMbNX${WH22qb{!*ɦt !I4booQƘ<;~j :Bh}k@E݌9::`֦#unVGOP2jn vgWHѓY𽃤:d4m[9=]I'hF+dV bs+zC̚BjBw'?ٷY/8h_tvMpff5uEFtmMe١DrNA&.Ԡ5֘NwpuMV؜|IRߋ)%Zk(3-sώH)aM0J/&'"%MUm'waq/|Wq34 5z ߑsT1-RJ|rzM K5){ gx?HdfB^ndFzp\#D"t휺> 0* sLTr Hqxw!YB3L 9br\ٻʨ`m !eaTew-}fBK\Rnj5)_zUR=TPM&;w'Ndoo)AĈ&Պ]AgYG6rmO+?{?o{ w=tW^O 9@:zKQZ]6q IDATGnh"ab2hmT$Āk-sXU0mF,F!bי:#qB('' LFkh]PjS%MRUh;":r)=]E_4 BWnNJ=Nn%nL5Lc"%t*Owx@5tg@ڎA$%l_u'/1&%I^X,4݊47%Y8`dO)kG.~/> R5I /\h;z3I#Rxt %&'%m}jDXbUFY&!QzRq!]א5!9M]m@ -F]!Elv!uӳy m3p;39ұ5l~E0gN9 U1MɲU3;:bմon3lk.PcqSJvGG>7?;˿~`S'W/h=j{`!m1Z،\Ȕk7&)la?:bq ̧>hIbEOjeFsKq݂FnciNѴ?bwG$ah/P8kɔAGll>irm}2PBu|R"zo 1"E&K߄OÕdR'={>HǍ0Yg-)Y$ ^@>jQNi4=Km5?B|Ϲ?q YRe>] BJe !}CJFZW7N" .^~]JFB >` ˦h ^ ud7$gL$(|N]w'tNW##BSUM%dٴ}Ȫmk0L ӲFO fDZ"Riyr !E5HY\ehmSoLDQUhu(v9QddƤ׺ m{ y~|H9ML!]"RxD:4+d$PF"mf%_l[6&T\ϲiSK{ƣSǸXg2*&%.$]Fy|$RЅH$uC2,떾oP:Dž@^$剾_9/xg{(|? ?>p[Ӵ\Qk)M*4)\rck{~=}X'WӶ )NR5-1ydY }؃H!Tcf}mU9e:bI"O]WGyNQ*ZsE-P7+B)e{FRՇ8;_۾ۇj0 K_W)"79BbJ9Ycmsms،* ɓtx[X׍ذضHQ DpvTIY¹5kХHK0C9!;nR1Mɳф !q}!jb<5R!$(#BX\ lQ86$XtnwG6+cJ+H?dΐ/}kj0 53z\>|Ι[OP-QF5YuKھ : fmIaSTٔ*4n/_Aʎtr,1Bb3W!bBR5(ɵ}n w!\CD#h=bBKHc QaXVlдGI #ٜ(>s]"E.Hwepu z@ $#|$H2%,IzTa^-ٙ=$m ED;~1_e_+B(>+hR{[\bl;& -E5(kk~ Fka9]`\| B؁%Zs+xr53|ꉮ]Sl`vxDR\7"Ibi[O#вhjG̗ wkgKPĊ\˫Vƍ}VI`0 A??38{/D6f:(A6@Dž"k4(Jũ#ޏ+p_ħZ JT&+3eo'1GHd@TM"Gkk[tmքϒ+M E0vU9ۧֈ$yAH9#=5 u{՞<ٜ0f4Σ1kD>9bH:AAgk%!29H%X0$I\CTmS%!)miRugཏ<īx7I3!+bUkh)zvFKo QQ?ՒqQз+2c<\ H'F(Hc e>"FYd:G7D)@mKLΨZO0K|rYBkKV ]ScgDq}㷽Wa`Z{\}-&vbmQ!a %B%'vN| s.JeZg-( c<{2_9+%V/uNTL&̎Zf!$,/SiƤd2 + сl9_cUϩ(LhRsj;Vm{H <k3Bi$ɘj\Cd0B#rr3 DBK59fXmHCCko0J!(1:#B k H%DW}Ӟ٧}Ϸݪ P@, .`! N@l%'"b 1 "Xd꺺Oiu ~~gYk{͇Y/tm ٵ}woS 79Z(wɴ2S[K\ >aBtzb!2*'Tg+BHh!0$,;Ju -" !(TENn2ʬ`4>%NH,)Jmn%ʨ= "'§!<6x04Ml6cTe͊wY7K7{^kowlǎ;vz/{O]GxOS5V9ydGYCUHQc<ڇh8C刔"%͒e˨>1f:)Pbn FFT40^p!z*g:)('{eќZFbH!1FR 4}=l4+}UHk07x:~IӷPZ#FiMJ Rd$7%B, QU!0 DbY2of7?돿O;_y/[d}:mU$'7!D1R(LtfrfĄӋ! QdzY;{w~O~piG= zBp{HT䰢}%&S=L6ai6mKW5 ?Ƞ4g'c6C\\\"x ewPbkav+ mN pjiͦ[XeGDbDt2&$ԁIl e"V-Y Mۢt.EޯѢ$S^|O z~Dd% !@VTCO)i~CӶL&\9" Kc4n GS~}Q7ە;vر{~xkyo=~q`O\>.,WNlzBd`9l1 #JX=WLbĻDYjt(iP"05msy~IQLЪ"xI`A_д\^6T@l>DݦmWEc՞b@ QݴMEiPR$%I,9}pdƁ;m4Йp0cdTDoQRfrYSfe Uŕ+׶ݦ(`^ W ~`:2XG6LM!J>0ߴo;>]Jijǿ~!6w™88tBrMf'e}xlp]oI΁tBR]vsB!1'@UxS wt;lRec鈮[!hg@ S)a&Hgcp"BXgY-Z6mq\s$Ās-J֫ <7۷8u{/ٿr_Ï_ʿmtvرcǭ)~[~sG&ԕ, 32c:Æht2AKm|qTC H riI ?@fJ&&!0%xk<%[ ]2㑺g6UѪGD$F)eUǘ !M3 JVdfXI R=VkGL ${o-7O 'DG$9Mj{6 !ʒSqE32 d( $eEӵ=?qv1GiޔbIdK k.|כ"}_?m̟'E%7nI]dT+ר#1OKƹB2|x2*Cpud>5 @(\aiܵ + ubLfrMݾb[Lz/׽~߹Wg|n_ Uh01.5afŪٞ˴`=P%27Yt8Mv ")@oMch(<)ɲF|FQ٬{"eh# Z9_GF}dap؛Wňqu*D,VWԔ,&& cӧh,lΩ12 +OKroTa6]KYV5RAID댫Wv{S5HLLf3]_ _||b--i7x?k/≗ b*bwB rxzdr2؎]*]`qd:Qc(5(Q>q\ٿA]eTPWh]"Uu=C$!ءe<˒IY1() H},2 #%]!$[vIȈP$%x#he Hќ=ųyGu/xד@7 cǎΏw%|#VVd:"'^PⰡ$BL{!")A^08AUAg%& {PrA7deh:s 6n_ . 39Rjl麞"Ъ H"3EUf#5db"2ӌzC?xF^HTfAr|0T0h camG头Wף|U7hvx 2@%AIGGt@L(i#U=BMe.0yɟo[}?.~w?|Fbͨ@Q&\QVSBʹAPdĈQ}F (=&/f,G1(1дm 5%\0Ud(9ʺ&P2'Rnz,tvELInm?@J $Ff\9AYaBE䀄1) #xd\eٞB=)jCq1JD=HҚuu]\.`o\w+~ &ˑs<bsq{gu |kx{^9=Z"H!+%eY&UOu.J%J#dckM.xpzi,)H=pjݧHQ2_;}\R!e~z͜"^iI"q\\eM'j&1 "FIC3t8J2-SF8!B֚(ֵ/#:r]b$䉮]-͎EͺY~3" 'F~{>/'.رcǮ1R/7p8 LFDۣ( &Z|x'K #Jddz¨<` #8ܿF,6iηbF=B/&2 Z!517Ȋ)C/R9>eUD/h(ss({4vYnK|\ӻKVx!"%>hzjXs:ý[m0b ngDJT݆R BI\br3GJEKDe E[f`hnĀF-.ܯF IDATw|,o7;1?5m\bsxr =M6NY+"'^z3\ ,NԑBpo~F^/[Ƃh[&Ȝ+/:P9]QnƴxoD0ۛ\R"78 H@ LG)`qR& # o q؎@arǴ3HdEKZ%?>v@{ݱcǎ]Gc7|B^_zerrȔ'SzF!y{i>"Hx+t5.WshYhnڎIVti-gg\/Xf^COp79_\r" JAR"GHJR[T BG^)=.X!ɳ4RVg89zB0fTEfG%^ \nαiskudž$c "GzR"D#&'f{۳d@( Dι)Ll/"F\dbwdTEVuZID  &T$"аBY MgM*0Ff(,)/Gx߫ D9vX15ʌ*/ZS&)AxVs3 ZB(]0c=ݻF#cٞ{w tMz;O90󑧟e4^ī>~)8E9emǨ-kkhr RB5J$4vko"@@JRdTM{W+H$e9 o3؎ܼqCOQh6xJ(tY^I/Zb@"&OU8oA{@Dij꫾~/Rݎ;v ?)}?jrlh[S2;>UP}GV,DO LW Q!Qh|Q섾w(LAJI `\O)h{ Č'^*Sˎ#)eQE ǨLk֋KH)Jl~)&Ĥ1c 18Jkbl ٔPg(+}}W_?TzlG널<=RIrp#^K"X]ghED[UE k9:F֭iK`kɢe;vc_O~~ofIY(OIEEL<ˉsϕ#BhU0؀Ș0*#EH*eLjJmdNj"q(c%'DHYcw Cr]!EF9ᡫOZu (rJ0I"y !(LQ8i/AH񱣱,6wX5 )A3;xbTsg[t%1mh9I w=dYrxx)#4l!$ U!"ַnT ! Et`Fn;} A)DQ䭿?0{3їv׽?Y|DIɭ9WhRm2|#eh&r MFhޤd:cs. tI1\r;Ѩ.&f7)C*ʚD谶!WtLfWh{KYBbm0X Ib 2Ye9Xߠt`vkn߻KRJxsy$(I O煭 N۶5β{{{\..Ib}7d^ӷ_c3إ;vc|w}{gdVpmUBQ#GVM V3BVZf'ܼցH$ظ&l6a3 zkY-zLn!̷5Hh7 R+5&F:-@wL9](&(Yo64Y@shl32>(0 =}{Ap W`*i}5gGޏs=uLg a5[@hReMyO`^1 uKU hM3(0>Y^>u<|;v 4CJ[ƹ@J\"Ɍhb!ZT{=XQQ2l Z( !%BYT{$F0:CDHȍbd1˞c2-+&);{İ"Q剗.~.\b@DazR]2 iMI9!O٬aΦOB%y.ȋ=DCpzPϸu)_?sχ,eM6kVbHE ;tEAKLrrb@ M;2GߞO_>u?+.ì Qa8{pz3XG$DtUY S@VF%1ȣ9slϑ9mKIY^Γi2Q0t)V%f kArڌͼƜZ)#?=2@2LbQv@$REhQV O=_9Dj<̱@<~pL30g2,WKEIA&la /~(6g~/}_'vvر?y˟K;{uRb:Ձ` H)@78bJT"xgh%K.2#&ň$Ya(jŃ;i;S4`v!ag8sQ\Z5vjb_KRڐR8~, HbRp#DOzoBBR atMf`[m1g!D-љn~g8xyї3g87 (kWTer9jf65$ Q"+F\{N=W}~m?c~޿Ň %Q FHAf2LVRVS{[R l`Ti/pEL"H!%$> 1B5=rB۞r|˧\3k v`:6>CT g@F ="CWvtnԺ`Y! 'D$4#_ %zH! )E@pZKbs_7 Q1s "䄭Ĵ+\2~-wOb-օBX__0=T8-9""%AʆT N[pr5c(Ku.YMpۗ?ǹ(eOg<ݴ8 Kؐ\"!L&pU9A 4Ρ}h0Ղ!I#G ndMf)QS<~%'4#Na! ȹGI(SⲄ 3#'Cb2Z0A0I~- c#[RgQD2%UÞPFKJ,g<~MOu!_6S=Pa(J@W n^|;^|KhMDgB*C JdR53kv@):DAI~wzY&^rxDU/Ed(e.I%ȎqbJhi֡RQWqrrׯif-Ø͗G~v?w|:ꨣFoҿ6hQIz3h] kui 'Q9Th%QRBjHaGNi9o!m)2bJH!0j8KQrv4XȎ49HR(5GɊ6TU~19R88yzӴ QXfn7T %@R4,gX#3JP8tq5[ρ  M]]ì=iN8??i8_]C;FIjWa2'(c{w tZ9z|QBC! qz1 H0Cpu7X+DM0 FiiK!gKvΓ_"b#r(3Ki1ŪFL?5T\@Jr(mH+N#d{BXm>`$ǩ̙ieyɛ_߿f9SXSoipUɓb{Qwg8劫+wXH@\-0F1Rhq)H-{bSBbY9O' ֐Jٳg% 3m/9 W_8>GuQG߻%i IDATgS3~2;R)ĄJii* g{Ikv~} aYPS|NQ$Z;R N2t%8sBS;U{F Rؖ mC? ĘQK }b|]cBd(5S-,N5X='f5ayL lX,43M,[>{1VD)9pw}7}t}K_2m . Z51 aP aH<>{eŋ9]6UbtC~`,O? i1 SZI씮 'X;W,Nx;"5o~1*r2Y->k͒!f8b*Qb*C)$R5! f#msHi`S$"IB8;{e`X VIq(L?X)%lbFY5䒐b%Ɍc&2u*I9B9 e@ k$Ǟ>Cs5 )JU4@AيGz3WǟGo~ؘqQGu4z׿#?Aъ{ۼ[RK9HH`\P=Tk$mӲm RB9"JAJKF-q8;kNVm|ZC񌾧E fqf~fJ ФES %Z."D G4͒T 񆜷(]0֡usTYw/j2OOyy;l866;..d1c}{I)1vc'7,3Vwk0,Y0a˓,ZRX<}W䘦s\cdfuZ 'R]՘1zOU[Rsqve*w5v|ɯذ>|n{j+rh8_}dy;&@QWC33nny}p0{^9P&+$JH .#I !%+DJ8fg-%-gv 9T&@*E3m}sCb?SAՐf)V,OX^VKN"Jb~Bl)c;0VaMW-cӍ;Μ#-͎=9D{8 .kJAI~sWy=q 0ԶB*K f`CH. *}OnN'-ZM(C!Ğ N臨)ϑL% WUdz"qifo9xAb5lu\>{'9;gЍĜQvqKm.h6 㖫k3t{Ҝ./1攇DHfo35Ϟ>bF$ _/swwC#ZMS;Q !#aJ)@s&L(Y CʹbWz DJbBM5wc%L !Zǁ靖4MK)R2Jkza8r9'NcW}gǾ_uQG} ok;fŗ-OU娬,ÊL84ЪPH= (DKR ?Ξ# K]['6K R2 tcO4C2'\3(Oebr-_΋vgSpOʁ(($kBZ LѳB (dQn2!*]Ѵ55ZxNB:䭷6̛nҲOdćFRr~n5FQV҇ad29{ TRF(W#+D![oWtNCJ,x5ExR4M)<5{mI>\̸šHg =jQ@+"_٣]bt8r~PR@5S\߾iJJV䔱B 'OX@.Pe£1R BHΠu5]֊Y)MR->6!a@LnnZ`!Pf([~GD^\]_??oģ:oQ' F)YKۑMacZZ*a?AS@PG BMjHf`0v.-}#> $9 C@ۚc-%TIdvtbYdwtݧ~ J\|FbB~@SU 0 >2 #ZȒ@= A,'pxm 9xJNTvxQ "#%w+aYd?i%XsZ$a}iLK)(#)1!z)%4 }#I:a,|wĤ 9EO (ŏZ>TH Z)R* MB%1%.]0 d >dR3JA)z5,ϖl{Ê4RDYbc# {2ðƏX2 Z$B1"vc6HH䰱͆P=1 )aphߓ(hhqcGS;L䜘V4X? +,BB<ߧIl?P|㯡ٿ?N+?u|:ꨣU?s .:`6CϢ#0jV(,)91?@ =z%)$i$0&v!%BMr(ڦyޣ]&ā~cG)jPߗBUirzR{-J ь@[8Sqv~IP-S/<ha)9lB ݁77?i<ꨣ1z_#~o?3E{a˚X:(ZDc"%vtp#U]o^!DI-$%O,8W#DA@{RJ 4KCLsR v$igHuGY0o. 9N+.\9B&'+>!nbePjB!-%KqfAJ3ݡ0h,Kz,11 X/|7}g6[P FheRpO'-Oi䤵݆kPw7T`p(@*bXY|9t=j @Zp,PR%>; ރ('^qZ!UfOr@՚(CD)2zOqfeAΊ c=|bFz4 Q{J"dA Ҁqz{MfDRQ)RdCH COP(=/BMI5L #9)1c% RtE qԭ~}C*#oQr&41F%wׯv}'S ˯i<ꨣ1z'P=%mp*M&K! R[#wӇx0ME - Qk]b=n&rwhMTQjor V! +*8]<6'lod/QBh+䎛+j:rgŽ_@$KHC,ʝbM~?`!(D?QYD˖i=W(iSD2Z?,WDJ9X.o U@k6-dO4MmPRO^"Ÿ9ln!Gr@]g2hR(e‚(%jYWMAUszb~Ɠo]0(ՓS )J#BKɖ,o=d=1ʠB ʶTvauB{Ow) *cdPU!2'{jX.Sş̴m=EdQGtg_JuQG}3=?ÛFT!ejj*~Ga@TU3N=pH@X 8D<{Gm4u1MȏiEaY.xrV8kH}3(7OIgwآDJK,KdaLI, hY2W},YalS5ۿ;MK* Qf$GO-a6sN/.۽"{$k)9(j>ԆMpN׈E]Ӹy$£g" MV2(b(Elw H-9a+ .Xj|ωiDzۈ ѩ1YHY,<12=uӠKTn(0e0~*B%H%CU9P&Q{6^GxW8} ed9jA,I02TTZXfsR) Cg4*DN2h-.ROIO>2䔦R.Xq`AB=c+EnjuɊn=1@L)~G0nYRR !3s+ù{I|$}Q?}_ 﨣:߫>܅-*4 KdjXEøGtP5R4'yrNΑT 5'-2uy7/0t^)4!Gҁ8cRq~pyiy|O v+U8(*k g@H!Aɔ V/YƩ9k p1ϘRGq5HaH;=cҼ{+rHG_GpE{ڶdliZ$df씻ڲU4& F N5u͛߼& 0@.jJP-%Rv3ȀD)E;_QW+ N.Wg2Z ICFbi݊{H<5uSg߭:QRD & +*7G f.t'|1c{ .xcܮoh1xU@ MmZR"4 3kqOU; ~;dܥtsM?'?x1qQGjucЭF2) ,'h!VdRaFU$FNsV'+.Wqӊ|N ϿAFTA"BO>1Ca8?[m`Tdf}X?0@%4! A81//A ub@iMU[Nf\o>a}72Z[>X_H=, cFHΞn@+sg(q~f9xG8͜q<:Ik!Gdm [\U(iYa cfcka 'RT ĉwd>y[63ӎ9lU3nnnY.Sܬp{pš_|b2a9Rb!%XmyhlpmfaKZ+BJT-JsB`=9Gȅ1D`KB0)B GD~cV\\f{R4PD5R"I( J`Dnb+ZYP@&aIr!lkЁĜҏR2BK@\@vV 00<HҨ"i+Zn'^ܿv~juQG}3_rsVu#T݀YFi70)SJvR(RÈ笷/Hi`qR:m۰%((l ,'<:yuΰ.]7U+G  ln?]?FHIeh%jIj2>RJTmK=ղn1NB0 > (%0*sOI#яc0%DIv;̤|`{E{ Yk~;~R2g^(E(2 IDATcF*Pm2S8aP:T%XZm1{V}w)sZzsacrVY|0 1WKBqTXod RJ|!6,۫k.ΡRɰ 11$Rm[6EJR)H!yJU# mxwُW',ZEZ 3!89YۮQ|STh)Yb0lH1qq* r.%axֻ ~ d @R04FG)ShY#kCrc &N;J>w% V'4Eh%8?[aDVJ Xkc Hʑ,m{BƁ"BHAIŬQ9FYԄqBĒ$B򤘰Qzr$~䜦m HJDݿ?_?c'zGuQ7zWOGhV _cmyK:Cj1ueAL)5Ej0P55Ō-1tS}VVH$F;!> !r!IʂX$RXamra,x馭[svSjb ,ynnSYFϨx/g\?ޠ"wWܾ^sRbu[K59yDu FW4MK̞? XpE)MS79JDjI4%ERh] !f*QBжsb*H2a$Gjf~ԉV컑!n7XF2R(rBLbI!U÷{x!qjТ -@D c>q!>0CHgZ-5g?_4==;igsTmwǝNlDB`DA  "$$fH(AARlTik~gûc )@7Zǭyuuc E9xLG#Z1Yb( 2KbEZ4&#J!P1j ڶc? 5Zjόc ɬH$DJ Fr !{`z!E1qzj҆,BHb NbYhrX[DMU)Ϩc$"Sx[b(-'!4A|nthc(JḾGr e#"yn~7?'tI?(;X4, ~ [X]O#P0)( f2l0pwQA ]lΨL.S 9**|э7t5m2?qqRsZ"s1 Fh5cXosgˌ.q1rhwd8[5|1EĈՒ \D"k-)),dPaPښq†ښy-(E+Izh#yj[|K_0vt%Ҕaw]۰=8\BjDuY`;gOk|dZ"r%#!&d1"Pr\%%O*)s(в%2, $%+.Y,nzCXa8,~ d&rLgLSaq@+8&ZRS?3olk@H"BfsMR;J[LQ@kap@[Թǐ%-tjCry2,cFjTa_?O_N:?ݼsLz)A@ B 7RE, \RV\^.9ж;#!FzȂ1b 9 ,Kb qKD$B>E,HBs?gl7lv \,h0$  enŒzJS]FȆ(Ur0) u~%:1| 3t4*+#(ҒSBk5Xߐs`!11TĠqc˜)3? !XMz1(;@ھ{{j#QҤ,Q_~oSΩwI'\}k_{JB"ȐGAQL QF1./ѢDՆY]v #JLYtggW йc}FAŢaB'ַJW8cA+h]1tjMᇁs>)}L6ֱ5gĨC 7r<*#n$K4ҋ33@%=r͎4fd $'wYa!0/f\?obJbB "x@DfHeynILvS(AjAOx5Jfr(e0FJK P9gQ`ˆ{DIJAHYbYy.TLYs*kH4Q3_лW;RȆ2~ "p /&cM;b5(- B2fB.'thQz26H1:reK3k!).P28O?t$H4PGDU--V }@< &MJ dHΑ!E">N,vM /ߧ#N:K,Aun=YPjD0򺔔IQ0=/su;b' -c $ MN!.1puvL%*eˡ%J78r~kJdǀj ZZRoY5HU{p1'j@Q[Eufd(03$>W`(ŌBhSP8j{MSQ 竚s]?|4n611+gDBaM ub}fBjj/|8p~IIph=l{1D]@rraNSQRs z#E(Q J$ ̌ La1YEC?NN((MAS՜/bB%.x(m'¨XŜod1nɑs(H7 -B @r|ļYph7*:ӎ8v\]0f4 r~L 7L3ԕ  &HG} C^&e=.e%?7scwKH!Q5hbq YE?ִ=1:n@(#__='tI?޿Oԯ{w(fQxZxM(+K )5VG֛v($v;R&I 1&LQbF(K=C7ДQxT(Gno mv% %٬VI`ԂyDhO5ZMCe$]80>,I"Ra}G H?+{ .$ !1t#qLH+dyf@nnxC._B1Ǽ8=RI ɟb8cJ#9Ttl> Wo|N37n& rga8L#!'PS2 H~Uebz-KL6!x8}k,UKi҂زT+^o_2/4X21J4&[=R5Z kDTf U]"Tߐ$rx︺drw(I9ij9ҏx##a<bϛ_ы( 0Z2o VH)h6 HiPBcuw8:VJⴿY><~K;bhKdV!ƚ|}f s-ΉAM?x0fK5 qc@kBZEJ% \KH1^yW_?kN:9ѿuwF!K118RHeYjijť5;H]Ut]Кg3#vMUZ/V7K9mrlv8x/y+-bd$q?ng9#Ѻ`>[Ě$1..3[OExv hd%CUn ݰ!0Ě{$oX8?a 1#x` -펡 W\_8bq@Hi'琢mK_oғN:O_Z$-:1b&D\w#H9c %IԖkY<- >|N#eYД Ɋ[O;7 "qjAYq ŜS@)lJ 1I1tbX4sI +5'M d T圾D!zN64O9:`Uv|1]bov:u,O =8v;fKK?.V#7D#U,hJAڰJ>~* ^}*^i[O, ! )[) ?S*.H-=18lo~U#ռ}]}H+]"=-ai$,^b~ Z,F?I"D-Oݺ}l) kBh5gu9#EQ% 7h)1@7ST,| )DL$T)0zȉ8x2gġ,5JX[`݈̔KÄ28"#Ø3 sugSt#1Hd$)=1eJ|p#SMI(R ΨE?G,PzIS?/xwqO=VL9i,I.3~lr~ɼYqvI:9| {,;;R lII#$(Y]9'rbc"%Y%9$ /\KBbKa2k{a,-ݚ cB(Oq* A5O{<緾+-#R)S#1e vJs$MS@ [L<UQM 1 ).,P %A?:9ȢY,WTDfG\rǤի/h*E]-zFU5$Uixk39RSQ d55O1D*-ލT % I 6~ˬ.i3$InJQ5 v !bObs*Y܀4WQvEaj3jqa(KJi]a9?"TM8!3 ,DC@Hmq)r %gSI't;zÁj$Iy  ɬ@B GxѓEDJCLL;mUE~KiwΙFF$/139r-3IO\0%!OŊ!tKr2)\X^f2 7f45ъ|rw{#ƨ Uf-]㑘 doZRBLk-~LEW~o؀'=ZgʄjU--LY(b^P()K)$HP H* Kd.Xps9bS~@cx|ya')JY|J&gH$fI5 $9R`Lxƨ-wVU4& IDAT͂fcHQ\^A"BR(,YYkDth)?3JVÆjy'(=!B$d1Α|d,2Ʉ9#J̳[福%Cet#RkF?p7(m -Yp@j,.m.|Ǿ1aJO2Idz 65)jZ]rd)kGnӻtcebHQD#nnz)R*H(le84t[f2zf2kHy =Z)j!ƌi 0d:Jx1,B(HA*$ĉn!Čі'|KrNYU$A8C#@g힛}( lQc 1k+Vx=6w7HƬ̛"JDa_H9$S5UgO_m8%DP5GPي,&)QBSٔOXӶ=&zHQ]F,JI~C4R@È%%JHA ;޾}ց9]BRؒY`ޓS )uU"]"UTwGGi+VK SP"Z%΍!ʐ+?~x˱wecP$)Idpt 4 .1Ɛ |?~x{)L+cF X.SG邏N:_?ǒY}Qd|Iy@rFah T՜z$a6c:Zgrغ"厮]UËsڶû̡P:P ! "e )*{nk5([ȡHS_`KG!eyų?N] =p(~vzͽ闐RQA;dN$S5VUVWBj-hP$3nn?G%1Q2];aeDzβ CRS HNG.-xR%2 Ofvhha&L]I112Te|ly aدy@YN#L4$Hd &>ms|?* ˈ,0 O5u}w”(@SXa@)%7[ ViDS5F,+6$y D"LS3 fYxhڶ{>\?zs%@aǏWWqs~īl@hD88GS+3CNKvK+./`]3kjl?gȊRY22-Rk`?:B$#Z+>MĶ=5TtGH7x@aԁKTnx|?L.)TÓԦm;,Q=f/45quqq8RjLbHQ9-=SHERAeuG_oo^M1&(!"c@ RQ >|̪G `6@jM;:!"(tRRf|EE) j dxp9Fh mk,>utq眑R3^%7 Z ȉ<^8#1Jb$TU- !㞶cs;.GRh [bEk=<22Y(LQ#BZ8S?zj{c@UFǹ-1bh2kA+ϟ= ~,,f *X/hG\H9w[Cw49k__?'tIߏqwǿg?Ձ*8n'KTT|An..2:[.MeЮ15;<~_]|9M׾MYi[?{r֐ShRp"m;n05m)P"5KnknlBMQܯoG aӎVTՌΰE5u4łaM:|SJ n(Y`18 7!TvO\3oJJ#BHx4ŌUs12xLa*'ooh1rv4a9OU" ,B`KK{ XrHHmF֌@ FH &zKQA}~lT H>ovNkkRm4)DOf}0w߃h\[%"dB ^xsnv, ̪9ƚ}G;zׂd1s–d<#t޿O8:EH"UڂR"Ljuih9:. -ݰC9fcK2ztbۻxF ~Zl!FJ h,SJݰ*()a(lr{)_adw#Dd䔐R0N-rX r~vjPTWi#Y p1ӹ>'K b.d|HI:2-ahJDHh&O$$E]/)g[fkm qy)I0%7/w5D2Qm7R5}ȱc668̅ʂŢSB5.)1#eMY1X7z<#s۶S-j\P:C6DZI%n ܣ-n<{㡿GJq٬+NUH~D!)ϿIӏ2BYDd<12)2Qle&t! cMlɩE&etQ3-HNH"sTSTK)( /H@ʁݶ='ƈ%8ITuЍ@L00we"zFS rDIMJ(Cђ"߼a$ 2BNE-C> SfEtV,?=9qr|wGATlַVeUE&ZclSA)!#hrHY`TCUlXԚ7) =)V%%rxR"(Vk(Wnr DP}(Us b` (=c2zUad!FȊeb:oҐ('IOΉatlVOx8c"=uYF쒪Y2/Q2ps}EdEyS9}061DYh?iVk} wBC#EN#pb`9eȜWH#OGy_Ǝ[}T/'\ F)-QR6HcЅ#E2 B%*LѲ@$͆ɍܿADU4?f1M2`0"'Ġt B,&-y8һ~Ҋ֬͆$Kϡ=9`=2bZ(II Y,ٵ#!Y$]5d6'5LHqNg&rx׷oNH]M~d!9ʢî(9z" jsaHY шy` hfi2&@ƣ%,ʆfA+ʔޒL"%Zp= <.MAQpvx (@+ȳB,Nߣ(/yá89]~VbiFlQ҈Rz m J4@70DQ*DԨ\S5.݄}n N+HQ2EABc"0l Т \{TU# 0ui᫯sBxw:rwþ0R1-ےD^FHC,5EUrhqR2LO.i9{b c$C OziM~TDrrv`8FBa Cʚg_O_̏#@2{$o_ 9,Ɩs#dICx7?_|pSDk@io5C7sr ~ Bs/Ӟ$zeIY$F((l-Jg{JjR YlJs ]P ELo=)EL(#>$rN9UꂔiPf펪X!uE,W+ڶc6AIȬ!kO9ZVP7au&cؓ'恐ûCA 3\8w).!_q|#[)P\R) J4kM]{:a^Ƃ!"-pw|8ܳZ7n'H@hM!,JjH)#Ea#JδF`E6L}[|K*&yEiV)X6}xj6E$z1%c, % P#D(H^R ./I!\Uc>{?aeY3G*Q!T!̂lhcdб*sy~Ɠ '|y=o^Ę% Slqjy~N0!r (`%u~ a.SlPY4 40t\$=].7sD% ӷw ~fN6QD&Zr'B̶+ʀtGR䤀H,IE`v]CVhYR%8>YCK̎*)@BkŒap=eY D ]ӶlAhHY!rEa겁rK9QW+nJbTP#;_ݱ9>@ي S$R՞5)$b 2%K}O|c=C;o5Gy_0=_0u=i)h%Y,VH`dݡ 1lGf3jhـX]&sV~hoR,)&RPĨ* QeK֢!?Hs"1ft6LcRw.vɗ_0x=/^Fb8LAQ/v HKY'pӔ ay{39ίP. ? -P9?;FL{hy I >Q°>rn89=/fp`}To crRdHYDÓO8=`G [ZS $JWH]07]Qj4h) 2 æւRXx{#H"k?$šL)!&(*[Usa#dfKH4 Lxs b Ql %X,h ɡI AHH@XS"> 5hKkui\p~-V#nRiV'K%:ŏ=ӸC1fm5)΅m!S۫~7|5c0vT"H7D?*`99}$UY϶?@"!4NLSq{@!q8?[C?\Q׳ # _ǴGy_T:_|w_tvZx]^˵dЖnϮs\i$hiIn~`bEÀ=r/Q)!uؒeF%R04|tEY0HѢ`zrD*41E\' /?:Y3dO#J$)(4e&ܶ ~Ӣ .)MA(j?=G [_KR@&=#TKH[B,ɺ?>Sbh)~˫/Y- OVL+FZ)H*91+MID^!arg{w G^mb{>Z6G'|Oх ŚZ"U L)lsD>|W,6[N\`bCM]`Śo S%9{v~$ !'c9:(U`hGFDNH)Hy0K(=1$HiQ r(%I#a{Pfav54v 2,',)5iJ IsA*ϪnV'a$MdQY5#A)E k42i a$uo8؜.y9srza)$$g=%V.#lI<҈mqzЪA(Q&EQ#EbI]nO }%19]dͪZo8 tBR,$~d1=K`|mOnY,<<)!JT*P@Kt i5]~5n9XoPFutl)| Ch#fV\OYZOyEJQbvsaH$ dEá,jX/=l4&X["\))]#F 4IBa[jw@%ĻL-=!ut-.*7eh'? H(8׼zSrvTł|Ran߱Z.(E0M5֔UBKE7_1J D!=)F( 2 !#]ے L"˦ !F8p8Jϝ=I/\D{#Ę0v!d7_G#o=+{B+ꥥv|fbu^׿Of6Ma$@$-b,rЪ"w|sj]US9ݜvQm5ma q*HPahW VSB`AC H)/[bl0y8w%s_&QXT 3Q8F#@ 0)|ŏ(+E:B*43) GOp/>2 &.Nސ";Y00.x%9F-΍!%Ƅ$){HJ$ *WtVб?PXC] G)I?8(;()im4_OGy_dg!`GKME8q7} ) #%3ͪY/]c?{ C qg?Ӝnh53๻~M Z0 ւ )IB zA7,*T ɤ(X4 v;li|RjGK#J!1lW/oh%O{߼`,9XՋK#ES1@w8?):r Öa5l"|6+S18b((5cb7 $HI"Żj6DF"By\Oi,qjOw+c6g7|0niqlIqvе~$ҵ[tQ0N$SYT7FT U ]jA~Ϥ5ƉUSE $>)-QdQyGZRk)Z.Ⱦ%)֫ lSNC2'[l )i.fA1AQ!d37g1%)B(Jt@F'l<@%REǟ.}*[Gk>|v9+FSS \) 3( p -!8?,Qv(.,.麑㧜l19 G/7\>F.fa=GyK) H<9(Ȃa+WhiJi$: >t8P1D6hBF臖bQSՒUhGE^#Rl"$I RH? 6ˋ96Z@*a % )tOh CyVMÇ/(N*~ŏa6P+F#!˒wCe4>Enw"#QBaf&-<nOi%nGw]ۢu%V)IQpzzaϔVb]P-`QՊ=b(?ԫQooM :SFhClQ~7\?ܑH$%J+hAh;L2fI($Ąx!PeA &P5YΝh]rtkn@fEagpʢ9;gytc-GK1uWe"Ϟ>cg.NSNXeB%OϘ\@[EG%)F P Tg_>¶%s~e2>> Jr wo۳5 1DizIư,7[P!rtcD(%訫7RRL.`*{ǟ(KP|#GyT?T&eiS)")lY*TeL \3:r1d& )"}|8FDvNK 39Fx7!Eƹ4% rwwj͓ˆKt᳟EʂU%  #>pyptCJa7?cbl];~kU{33tvMnI\ "XEE@ qpRRD\E ؄`8}gv wb{v_TKJk=o7咮9v?vme;/Qft(#jȨ "4݊(D ZRP(+B  +`QjܬN%rvdd16h4) l =`w;!އo3.hm׌ 6X%S%S~´x+J:'ھf9+q}IZb#Inwú[!;XQr~G36D!alpCI*Jr*YNy -Z,Χ@XRoOaf JC ))tA &Fb@ ɐ2'EKM9+bH) tRh( @=^|c\a=CHi ȬL '2??lX7LK6hwB+ICOPJpuyn7@ǵ }S#e9:ԕkӆ]q|2 =i{;NRϿk!;4q![ HXw+64}M@L.K$ SMJ 6s[Rdw"DZ(H7i 2?)Ƴ>\E=ҔFWc2Uª r]a2m}TD*d2pup]Oas\ A(KRȱjGDb2H=G֔$->443;aXHQw4O}B D* 7YIl19Ќ``:-"b;KQQ9G% 6kGJEwLPfkHBb͔̜-I(>x4@Z\%3( vsAn1RGJ 3MJ uwM0x 4"f1kX_~Љh@J,o}-3Wgv#݆iQ1),0+Id\kBXgY 3BJCY*ܰe2LeA-*g(@$ D"H XP͊c*LJAh,_:sDKCv!`dc7{>= M,$Bh3qbK#Rirf@OD1 -ē/?߾uջӝtwGg{gW E"HiP,(wȌO.CD|~)'1u PC/UGWYм maq1̎@Hf* 9qCOiSv59wheIbR85#{\ߐeO=zH"~tA4):Ĥ^?$șK@K =yiax65C)%!ydCJ5gI*g2$2yDǴ miFfznZEq\z~y IDATjwؽ`Y1̧K[I6e(98mJB ӵ-F\'; %*;) P-B z G?ˇ.r2llQ3BtV6x[<./F-CJmK#zRs( >Fv 2((lΐq)S "8Ze 6EBGZzH$?FM^f?pNc&>t87g]1+1CEXN,Ïx̗|~NjÇȌ!+j$SW;Nw#(|[ $RhD(iH!!$C rԇ7,gw+bQՈ$)z99z %%I9niD!e)NPAC RTK/B: $/s&Q;xU$=G{(+| Uut1 [~M!Y̱fJ=ZE!C␨ ^Jn-Qw}N'[/=e iQ`w; €"K+!䋜'_7TIy9Ch*s:b3Ct~hǑL fKؿV }/M`;&"܀s=JEȑDRK%;FJ95u1،GMO1[$N4uw )wh1Ͻ~8puuCG^~ JÁ B 1;${BgMv @){;Ni__ (5 ъA*Cr )SDjlni!a#XB$R=B8tG %DX 9 HRhQN m}둇&$1$ ;Aˊ%M3 E B $7' aͶ@Hd1AJ֫Oܑ$%]b>;Ũ=R$VxG!q~vђ; @5<|xf]k78W,S#v 9:$1^\mx &FAWxXg B(+~I ]$ȼo}.͗Dzy6{r[L)GT6#v G~g$46#'fMjMe5n4Þ[2 $mIDCClI$X)~'X`2@vFs/5!!4jXJr=!$ 19*8P7+\Rw qOw>Tw{c肽رݴB $!p}DHQZ6kR($ٴ7nwIIudv|~LHM 5١pQ tw;~KFH0H ]b:[p;&g"׫?N?~wNwR2/|e$Y!e ǧE $Hs$1+"z%32["dlIRd6ss|oYo톳3u[)."$F3Q{xKJ垔H$I 9B8>:FJ]\bDQJ\LBeLՁ%͈1!H)g9IP {;Nǎ>;yeeL9BӢ3E $rbJeF1F(1ZHĔ>\V<:{i$92@YDv aJiD{IffXY gDI4-K% c@+)HiuDa2d憦ŅfΡ.N %%ViR$1*C&0J`Ώ8t7+ZGvŹd.t )E].ĐCșc25xvBhd[t|k|ay<#ҊZ",!5? GW~c,$9:J4Y>sx7n|n^_rt Dx}N91ENJzP"Ǥ(qfs6"32L)}H 3=&3R.Q% ?V$GÄs$STիвRdh1A e!!U { T2e,_!$p(Z."1,F~ OCkQC[*~GR 0G )BYo[b!r?#_pb&D?D$F_yz'ARwjrZ #1F5fTFfq<;懿̧3Vϱ6@JiHe5o~-nwL&dd{BT__Nwӝ_X|+_ıbc[˜LJ)&#ɂZMBR"EH1b*6{'ܬȫ)H"Q)m'2tֲ}@Y 3iRr{ Øj'!!%a uad&2;21$P JNf(qC`:c'$hs.>b}a>=5D"'tmOUn(m ("~{ܖ!;R!DneNû˫{Yo9>v[Ts %\cԜ;ó3Bw5? N!"ĚL0Yj5ZbP2'DQdEf4'A*za|nK^ oV2Y1[,cm Ck-JBS*@XsA7Ȉ)q'yIۓ 5$Rt@D*w,9u䊮=b0\SWRp{-4%-BHbUfk2Gpwh!|I{ L˒o1&@OfsyE5xJi=;)ϟ}1!t@ ,Q,ٳkw't;}? $(UvkPCo  Eɲ"!GR@%EsU󟠘Nw ~&y@0R Hn(y}͊iu*ۜ $: 4 4]9=9R$9U1?^\?pT!(J3rFkM?h4aHiQ^x p\'%sߏk,ٷ=7̧ (X_`>f>A mK^q^1}h2%$ɄHLngq 05A~ѹ 2&j<9CD#.$rt"bJN$IPT9J†g7|^2^{u?!"EPRRM*i:TTF%nX3DC;F/(Kf) R!'-oUAY̐ 7Y~BW(>62N oP5%5m?9F INnد_˫'fPrt@^ 0@ӵ)5}?&Ŝڳ;\{HRaJE=(f^QZK  89=7H2CxCDJ8wFz5a;_ +_?wǝt ͫ~*܊%`8?]rX2DOzL?8KDbU 15yld-7Ox2Kri>Aꚼ0.իbLsoiCu }aɬ; hcr \_;¨{fi] (ӓDפ(ւtF::IQRG(, g9 }4+%%xpse2g%XcJʧtTc:XVaOVI )TYQ3_,ȲnĔbeoYmVHVt4dS-W,&l!Z &q{B9.M16r ~h]d">c<B݊z3&h{%(S>|)LeZ([f9O)˒:Gxpiv횔\9fCLu*\aUa2)4[ (p^`ER;sDD5|oл=kqA5yGn_^e6-Z}gϾKQ䴇= Rh!Ք/Xi-6E3sE*[XRrlV$]Jv#@jBqGhsh:* h!@Fs1O)z1 {jwQsG ίHbC-YVPUV 7Oט Bv{KV3`y EPDy!QwZkH 4x"R "wV r",8@qfH !'[Ct "wX}7зJd(YdZ|=6 KNp a-o61U B bcj`Z%yhzTDP*Haܳ)1rs{Ma,.4$rd4,hA(g @4"\'?Ւyb.4R/M&4Mƣ?H$K{bl[6R(Ex1͡xq?f:acZdv7|=8ox=R! AW-Im邶}R`sn׎?[}Oޣ:p|b L%hlQZpu a> (=\d6!9)D 'AB~Jp͡y0lOi՚@ n+>D2#k\]`6Y )2v8H6_~|wn)n 08NHI0h)=%^H ڻ~wG9! |bFFxpý~gOɯi/Oon;N,Η _.,&d={Tv]$EPjĮĠ!S|Ba'(H lQCz]|zZ)b.RHO5ۀi U-r9yh~JN^p|f[$+bI?x%jo \N眞h3P77, ̀s-RU9&dzJ %djF n!:6 kݖ%1a%WߧSC%Y^A }Ϥ8>9B I1afZ4 1eq@ JYA<4F,GK-PR{(m${B(A EE9$Kќ<0CF\\š c ε4Úsf6i-J\!DtqKaHhmQ ֓"o8tig2%&4m7nC!h=)L|\ z!ׁ'oRfS0^psZ_E8<+yGBB q4 $Dv{\\skgb]hBgVW()KhaV-1A\L)釆鬢m[]/8Z>z'O?J1hc? B!+_?c~[gWEƧ1_a%3>zE GyIXO8}ttj~~}s,ӝǻ?p^"iv|m!! ͞{ 5TzJUN2TC‡%"bH Pcr5a9=bWhDrn!04>Ya&GEAtWGH!2 BphHbvHShqz6Xi Nr×Qm8`gDxPR#ci#) h,"| *yxdEdlK^#w=02٢euaw< %1cGt=((bTQ䘔(G\?/%!823𡡮ܮ)- &ϙ,*횫s*C[Б[CYVpwrJ6X# qh@:a-YaIq;ΑیtO!C R(!V R[aO5 ?Đa~OOǾƇ7C+3icNNfy̦bN ^ߋO3$@7l-_O8k~X14*Gāv1هL#t|]B {m?jf씦eĻ["$ɊhW/vܯʯ_~G?;)߼x[?8T):. zw +4Y&Y,&GIR!,Q!^~^W^b6{_ӻNwѭO P+\8:^% H"2:ln^0#S9uV +3E !h+:w @*D hG]c͂>%s n /HRC :D$25]2t-Q MwK4=y,GQƞz3O G׬`픐C>+h!v Btt}b4⻞ܖ4 $#3튁[|5i;KYi^ܶ1CvKu=)d6gq}sDH&g$ YYmɊjlQ=)),';}5WpN+2k35.-CiXDԔ[0ʠ2w81bvCHG,O+zw G"D%r>xo!~M?}~_fiQI5>5MN'&1dH#ƇBAZ]//w+ֿOr;EGw?_=t2!tH&j "2 1Lze-Fk0BkŴX-Ԕed0.@\ &X!HN a*s;\p$VK4!O 8y6K"CFsE(;O$E"zHN "KtG^>{1Ydqrvo5-Z' ex9ya%J*~@ r%]?c*9>} MH(<I(##YmI-6'ʆ}5oȒ,`m֖*_'v7|On&'WSł"+9??#/Wږd|K">H Lncl[m,ylfI) &|' NeуxX23!aɊE~@)IuAk7xr3AH@v XMVa" IHBHe9vU1JZ"HcI_skG$5cu1}"Ɔ:ATŜ3auBh ]b1;}#B8]rus)*=?g1͘L5k8-M3I }M]7/V{ʪ xGgG1* 08bTDCz( uy[<՗xfڮ}Mmog;vlgƵ܌J i'TqZA8!QA*Hi!$ifM&vm;k|;==r|PP?ZKKKz}_n?kO ގq-nH*w?xs7Š'??~1yXZh$PJ6v+=yJCtCgXٵGWj6s^'/+?,sH!RyurK&8 o5}pr|{@" w+t!Eȑz_ڗAEQɔ(P OM?l0:>}7j& pivv^cC5bIEKCirܰ'2z*ՌUj)S^]V#, HU&#6RSr$7S%<>yk{fJ mD [ȧQGI+nxB7 tƞ$>9*P&]eUce6o .cAQpaē9{<|묷-y Ycm"E9np@tCh ( C1o{wyWچDjX DRH!,Ym2KIB)"P̱]¹tɈD'Mf4h<={̘$T[c:9&Vd&Flq'%vز zn+/udŨqn#o" 6 $Hd~?ˢ C$Mn 1XȘW蜬*:~`2IaMHJ.FC( IΣ@Id"l26{ XY|zHR!dIɡrýKNϟcvdnIPBy4[Kf9nb2m" n|Ç<@!a#IH`I (˒Jj("*S'5Y!?,?ogNnj-E(g9a^qz'6԰kqCG;{fI5z}Mt@^y16;rSM)X+x)"IR$IMe=gDADahGv?K-mw`.'( 0('&2pq>'ay9yf}:buDjAf b,'܎]R@ \`f6P׊C˯}$&0ڠ%.n{qc:JX ) nA Ssh1vl^9`X2뫁Z/ݺr9!;t8;6X{n Y{ $:# ۳_w.v|7 RJh%$.w7^<|!IK)IRR+ʿſsӡ}?hg?Դ/8CnumHcnc?p`!ȔAEJk9D" H 0YH$l}G "1$2SCJUCL&%i7&UQFkEQo>ÅR bj9}@3nX1e^ `9Cp,S]sNrgmh5oqp$22W]Ή+63Vg9>`6)0!d1w`:C݂T3!T aԜ=2%BD'AG0qlgu]a]CghC,Hj4\(%pvle%HUYbbh6,&S6W{WUV Xm(}Gs|>ݻX]]bVW-q. S&-u]-!cwoQܽ??a7y>ßK~>髫3;Kt^PN'cp!!$PR#4G )1e zy#eBH0Z@KELM $J1xO= ;}?/ys7RJ$Ade2ooR bk野81"䘫Vd3␡Ӕ}(!D&qno}|-s,P*!RÚRcY~ иAEM{\p(i)CPPKH3 )sr<|pH=WpzmwAmPs (N pqv|6@J̑c*zo}KL& R%G Y&l3B,K2/QzuF=È̷}MNHK<ڈ6+Xo٭"YK(Lu Yim'$Ig]pj_ѿc7.KJ]="rIQeupzyŻ_u0Rcs 躎gj)gR 'R9q[@H|χwAj~?O>7 Dg4uԕnCf2ujtBNJe];RDUAA-ۓ9ɂ~BˌvӐ;wcDFӮg`~Bsry(Mn& )s*I2F:bp,x#°5cfhy2ap}hvԱmtN2[2;oݟ( NW 7HFmd$AJzKUd*.f3ʬ.fBg}}s|;e9}"u"%r lygCCUd֬WO,)'owݟbݶOhBXDJIr<.@2:ch) }ɸ\NJw>uh18\~rzv)m!EW)4X,7l؃(r3L"!1ضZV[bz/,jQ]#6D)i[KtI {ڮg>s|`]#&3{;:@1Tph!ShƆ8$i6ȑAa}ol7Lg_/ ou?lys7.WX+K/wq6p},gd&x>ItsX$(IE"D!1IP!~x#F*jOc߭LaHJ -R2g9}X\m.9oFQw}Z"cS25c6HcTGp=#@ ZEcxrqY9aِ) 1UNB%X",5=$,3 >""w I ]_ev yr~M[|;6q^ٔK%!1f|NU08^)Zd&56t$hQj !a7CۆLlW;]N/AyO c-l5AȌH6͸X]Oߤ=oHE!FdFF4G MJcB;}Ň,2 h{ZK&$*KH) S $=TVDRGF X R!E' <$H1`LN0W6{cΧR so!?Q/&7bw烬׿5G$CeLHa. IDATlɳL   6hw~m^a0yQ-ل!yy+T M{Mg7dET-1ۭc ADF%Rf I0Kp2tDܝ[lf=9o~M<# 10@ng}uFk^{h$(t](R9 '06m(x†qK[sCyV2#|p͎٤d>CnOl))}أTE&t)Fx u55y&3е' FQ l%Z*Љ}jí8{^%04Fh$xqnXƆ~h=(Gd a?'/ @D!H%%zh!0J!R(U,@JD$.K&äm%QR\h)=֡H(mG RTLBM[RuƤJFQeb|7<\\?%X_7dZ1,z r9hn>H.Xi{1z-G!׫sc=rvѢr J#$5/كՊfas~1яH2y#:rx\l7tK;lP"` vǼ*koz?GryUY=9GGu9ЇY 1Α+Mߵ h''EZT$&5ӡqЅ$)-qc[W9M1yIJqcL9j$D9nxF+?~np z^OQ Cɋ]k&/e;V$rLgAJ9nhsr.ֵ]VCO-􏩲#zH<~t DNH -,)vٔ]ABAOqvR #i2H-vM)Mku ]9ƴ]Og-v[Ƿf_}Kt\AҨB r^B0 ="b:gkQʰ^cµ9X=EYbt^2_.ށdwh"W~`1:CKxɫtB +V@%v]Br<{zMC&"ζ:zR O^Cd9?dZ-ptr5!g9T!䀏~C<<*n ~պ(5W+Ĝ4RJk AI^ ہ\krS"BrWE٤†su>ggeɭ%O=!hh)<`RՊwulW` #>|kfoт̑z ( oۿ#)մ%JuB@=]!& PZR)LtvTr.6&Ӥz!EV /~{+mFh9EҐ5Bw #hb0j&IEކ:HZ^{#hS~_y,JEKaX/#E!pt|)QZ\H"tapѣBjb\G=B<7Dn`ZLI^3 qd ĹLe.qpp !r6l #EQFM8>~oaiɔB -s`Q*cZg5GhGs>pppL*M$C3OHIӶ 1XHaȌAjJS&lvW8|Ch;''w?X :9ۡ$GP/0bʢ> !O{]Kk78>5)Mu&R@`z}v۲Vell[Vg=zĞ_Ӵ z&uNLHB$sKYkykoG~/wKbE9 H1[j*$mA¾ (1EHbNe)r$G e"$og9 68aH-!6|jE$1"2xъJJr>HcmOeb>_~K_8>& nǷs֎"n4e,9 mX?Y3_`TIY.6CK %_v bA9\ETi5#ČyDs}s zO ð#3c\IHe>g1˩A=Ğb> TvȸcjN# rZ׿DVE P䆘uႣkvHm=Di*(P2'Da{'黖'Ыt]bqQsv dFB mGCk;Y08jܰF1i[O}x+x"5JR?nּ C(!i%f{lVZ!kC #% YIi R@E=QMIdy֛%hȋb4y8ˤ"aײLI B$L|)1;0y5v- + _֊}ADaTQ>༤`w-Ӊpql RU5wx76+kQ(1&)!Rd3rA ,+a ŀHb4I71 R@wNgZ^atFFgxKTeƋ/-8z;Hl6Ggs }1~T%20n뱃kJ, Bl7;B,5(}'QmՄgTkLKwS o_^|p)*Pr@!&l`rTnHC@[ȕ$="m躁b#Fpopz'p}轢.NeO5-\QUdyG)2!9"y6A I4\__ʊjQSGl.9Wue63v'~C")u,Z\/)%Di'^Dʌ 2s RH^|ӃI┶dՔK=G-!RT% 19d$A7!)j;.r_?Ak%^]sq}Ʀo@m!etSM]R &$%PRMBQ31,XMzy< af\NTؾGFC "rTB`*\nV15*xɍAHyDEƠ!z>!uǎh )#;&g I8#)x-R)Ь.-rOJ\dp3' dN+d21*(Rº5M(1cͱi@hhN{{':&Sd}sM7Xn tyq8hݳݾM& IMf^:D! :@|kݞ18;mfn%|;G4{lv)E "bdzaY/ʗs?2#l!yYrZѵ;>y+<|&Պse6(ڀ6z!aa8dtſfqTQI?}ϭ_zJ/ qrO;v۞>wNQV TR"fyw\^qթ*W\v\61qLGJ@ ) Bd@ (B@ I -eGT:uݝݭُo\qSFݚk-i5L!)ih.x)1* 2#I1S"BKkZyG rdD c,q3p"H)&,ƍ[,..0`$6$N+);_G8zzߑBOM޷$Ѻ, JPkH%NWT)8fC>ʘKVgB;>QCڲ7\,?UM>\zgRiCwM?rGKk`8":%h@Vfس%mF)CyиY> SɊA1'%U 144%3"+pF۴)ͨc:ٳ-^qrvnIY3lƬwk# z4!BRCkPkyIXt9!a( {4g(;`! IeGe)hښ"1ED B@J 'XW}No )IHYFRβ?9I%($Zu'sma>pU,J*MSS V3vMC0aѕ}YU麞olLbThEBf@kM̧#/7) v(J~i,%]$Dp4ڵ}f^dVMMU?@0 :$,nHfE }bkvL up\Gyخc\xml >~G(P;RPJ|P {ʅ0z@S[.Q@+Nr6;G6jzni]AIt4GÕT(4p0@hPW'_iUZK&9+Glvyr E3 hxR" Dylo BGhI M]マ4n#DTHS c"P*t.tHLP3ВVBt_dǚ"㟼_E/B/;VٮMA*w5H-YF*)#S"ZWIҢhES;(nL]uNItv09<>7ȌnY!Y?ښܼ Qt(BT$4x6$Ȫ>ǮVLWy7{-0d$1*F dȥbwm.&2Ƭ;l%9zģX>uEKER 6z\1E\#tk@K%5GR )Hd"thb6GȆ<|t5@hhmC[@ T_iuCPRq6R RbH*%2Bsk-&*$](I #:.^O\;ģ'llfwNj1$*9y1e/VXS$ .64ZǶn軪]FP-3MJwW (ێe}NP-Qv͎e&oYޥ(q!2(D`DU}Pe\9C+MS0).]J>WN ,.و} fr0%@9)y7xr(tx2"y>1Y2ӓW2-Fe}V]1̨͎]Xnv\j,'E'(yjMz e!Z)r!;He9@H % gx@ <]"dgqaL/ۯőB/Bj~%{`7F-HؒtKYw/ooxs)I'B$Ah+, Ķr-j-UDD ʔDi )C>(IrxֳH3:M!zNJcR!%_9.S GG46dž) D`ـCoRYq]ܢ厳% Xo*Z'X+Z/88 r&pfE[SQ#FeIp" 1haPIamVp͔˟`4:f[u[6;\g~&O2*l5DA#cB"d~xP0i1 IDAT/Np&;b$AB,}c PM%4yӴ5^8BrJ|Yќz':=hY9)Ȟ, G{4)Re} zԓPkw) #%1,1 C5$x9G\cXnTmv0atx2b~}"GI `v5P=T (i0~{!ybJ@)G -H)c;6"g8vwR%)fw/pt&D0YHkq%FOxRH躆#xEK\:ES+Z&Wtޜ7Xq6qm,.&XA>aXUbR qlApBed&cT(-%$);vEYWd89dXwԍO ; ضeo2A:Z򒦅g:Lg%킶?ܧm=n\WOx.J LH%AHJPd I1b[&``ϜT|hS}vJ\َs=jLuY (T!FhQ}Qphb>*&)$l?#;؋^腾e҃g>';?e9 S,CJ )FZ(u0ȴ&H)w ʼdX1e= \kD2j9{=L,O*$u]Dp QbLhKH"1:0u ,bLDib]LxSӧV1\e[*yp ٘ϯ"Q1⢢X;PFWgt]Tݎ{@(cG,ksCN^2KvՒ IYѶa1K|oSG{{]ZB(Fh= 1 2>]װ.1FRށz҉W7e {cֻ \L緼w]f<ڞE;_"MG[OiD窉(3G[tj(kǠE?/Rhez>sT,Py6@P;`<'l$3K)rb, I> B CM&tզJ)f?Fk AjEH9::ज़H2t୷!g>W9>:F{ G%cK<>:ESmvOq?YG<]<ź n޸󎇏bW%:GL{ѶrXb?'vFGeI1ϟ 1LJ:0+K -=Zj24%:!RbfbC;KH/(< se!ƀm \~!#[;s ^DRSy mS&92? {ۈdo=/V^^[[}+Oҭ)1Yu+4?+p"@ʌ@ DWWf.@bޡќRqyz!z fv3ÅѠ Ӓ{3LiBؠh3FѤk.qp<"b%-B xO')\<"ȣL'SvSݒ ]Ґt|^@$ƓŲ|Hi:=k8Ѓ | 1 ZA %kہJ=軑IBHh) 1Ni(!P2aDCn!>ŶhRC04d\\V4]GQ(dtbT:#X"KX`;EYX-N{,7\tkW.3.fHѻXQw:pgzMRLlg,i`+_A'o;L1ڍzۘO={@G38W` mCfbRHJ;9MW2AS6m"W^fD*&x=eho> -]zH!6%P{W9]m]*2=DvCԄPZT5e9@pHRϊV\>o=Ȳ8ʼnB/BBOO!AvHG|1`T׻@Cζ4+TdYtFDVR}6<=yHmuPĐc9/s4튶;q-n2!8?GۜQY%>|D[rkHز8ڵ+ rm`\q1C[jw]UM fMRi HϞA #v} q_|;6>W.o~@p!z8xBYh| y]?ePfEӁVi;ssDʣiJe""\Dpt|6\]#FOjbX3(Kf۪n:h TAni >1H:b"Ϟ҇~?_N/B/-zwPb}$IdNK)!EKBO@h,.2 eh4F+1Ҷ-j/mLfs*QB&R\l =d)>l2 cF4"$zrԚg(!Ŝ*+-Su j`2Uf]3LYA>yllXmDQfSA.Kb K/䫿>zrh^йdȘ(ʌ{|BȠ#Āf#bh쀦VKKH9Kn()hz25d<< b_!R8ؿTU[Qu=Ibْ >x(mKv r}ǵ-AAprfCblw+Bx(IE L&88_.Q%R 4#PdS/Su \;l433Ev;l8xv}|:Ǩ~G~G<}|d2f:*88#Eg[b ]홎ܼz̸̸tLJp\px逢X,N8xD{'{#Aݔ-9cvcv3\LG3,.v'PHhYlN1y†%͖$m]ƻ1# 3d6înhrђe%W]lӊ?~sMuc٬׌0zOw756x\D)yja?cWo89g 5j)RcSQ>ZEDP|ćΤDI@ tZH#!uFLU"Hmͮ8{E@iTJ="3TUE:1(H1;G0P-{{s) H灠Abco6@" S }:zOhzOH^9ZG֒ H).] "I*b_(Տ;+dl)tI&7t! zͼkhg<i-!uTSۏ\F1Ƈ1:?#oqr62e:#ƣq]Tg4" iwmb[PZJh3qm"#j^y>#ZD7_;>zaw$lm$C[S(`6ú%uCL4*fLL^ŃvL(fo4eZL̎q)<;j;[MWf\<;mb(sYVެxWh&&ụ̈́!N UHh3|u:H1iJ|2bzKR!`j91ȇl NO%O~DSb''y| oMe.iRv ssmȵD:a=Gi5*fc4bmw(q..~JSU>_t,52-mp~wߣSc|, 1 ͏Yj$IBDz*ǮXn^tJmk& gkjto D'<+H1ɌeRBC@Ev B> rpfK2Xg҂@BL0e3EI8хfaD6 ԕebgϘf,톘di5$~^+_9~/|?@>o+_w}&?'y7޾Ko&-D5Sk_os~EB*vGIknS*QΦHX0l)4RKB讜9={mLFCtS @ʒ?ǥEQP9n͠ض򑢘`; 3pa@92}=vG;s\aLh쌮pnQƓA1@4׮|}k4Ląd9n\A]7\y.4|dìOo-eVimkr /H1%y>$! 6rhXptxydF`rh%/r px|fP |d+c m Р@!P)Q =B)9bۆߍ;%Br1I +O\\\0.sٌ2un-ܿ{u._*ZAHGI>CK.$Ra/4.\"&Em->D|pZӁ'O>Ku@G0jlK%ޟ;[QU!ڔ8e' >>iuH$XF1wp)4La99}6T"/? &MSBIE?7d6!1xd6{G9)m:vA[L3:Ѵn }ҀRK/"UUaOJ)g4S?w_/O}w{;?3_}WkU%?S[?_iTy|?s_?HG^ԗ>譿 %|#)B;)[w޽k]=:}_H)\ev/_׮_~>W<|ߴ]Kko|/ !R~7K/ ?/k~_b< ,{MKiWxcPf@B{t= CoJv{FS-ɅZd:,y>m/^=ԉ8ײYV|pP )%Bf(9rWܽkwd"}{̧WiW_y}p:b7& G4d0k ۿk`[o!X!%,笶 :1|}UEi2J\\q#@0w}Ngr'\9FH|!W._?7߾dlY Ȋ#M]AhDi')\0F+TGn mY>},FiQB1,8>z ՂmDez*)cP̘jx4" vrX9:<ރq~>( c+@)gˏPb91@+oP[BCH}DOGW9GZخf!aP$qcS_`=F+ ˇlaё6Y< KW# }k4u|t<":^''5]} FK5j*6G=eB_;-N@ ɓBcBxE1dݲ7?&EIk(>0V̏&Tݖ<_3*2(L n޸9UҺH#Ҹ.0d%JH]g#m=p?]!H\])!SD%I m`Jm(1i")/n2l*йM}cơT 2c:>G+??${ⳋgo?~?"N e4 ߧr$9:>dP5ZkeadAr0tхDR EkWo|;]ѡ{o !Ϟ|zwǟ+ȧY_ٯmyws۪{oU$HudI ;e!H P ;!p-9X 5LbY$-Q"Yb6uӟ}v5Au&N.Pwg h0% :aq2nRdQX1%I|{|idq2cd #lD%oh7LE A8D E1P!KC 5a$lO\`/Qu:'f8oMC 0AK`Zp܂Zd=7D.B@Ua΢ޭk鬡Z #h*тP8I|}O/Zw|jk?YŦ<_ ][0t`,? КuȗaZE Mˋ/4b6}_[S\PG!_W8}"%}tyg_;ǿ&X'?\HVk\Q&%54xgX*1a4bga;(6kFqEhx`Z٭($IQ\mxk:_}- weۿ?=9?o?|ք8!P-Q0Zn|bfcp!Q 8ScL$D1ތo}-'*ލ$qL[n_ǖQ<}F?cS9[[(?KLMMwuݼ~w8>}o 8&RzH6J #؎(x9Ql@lF3&,%.)KY0(a,]U u /_<+7xJ$kXdYJ q.;[79X3m*t]Go0KfI*@c^kjQ!//뜦`G錝Dj9hUcBTpks""g!U~\-4] Xrl^]׬7+ے Hp~zA5]8hE y/^lo;9Wg9~&GOd8#Z:&@;gC(x[,,S,&4m˦άs {ӄgo̚$7]?+ SlqD`bҨ"&I8.OҐ[ -@yGW2$B\!Mw^M]oeZ[?'~+?z/`y))[.ޡNfS\1Ed򌝝}.H`@ ڶFDd-qtp4F  ^1]S"'V6UCg JMΟry$Оl4`" `[YW%h )GOQo,iK+%qF3ߢ@ w? m/wc,,0lh 8ktDd!c0l3Nyr6VJ7xk).lJh8<8dMsMWL&3LҐ7rh:#J4mEiܑo ),r7C](ָ.ǚFX)XՀk/P b8Np1He|( "bZ[_MnA8bw{Ywko[o#Dĭwv88/}x2!-LRojd!)hiYsi)$lKWWy߇}\[p|vLk Ղ A ,I^7cِ-hMy/}0Rk{wtM:w Ż `0i;ȃ6S"w^zkׯs.O /ܔ4m:˫Zڪ#4S>\̯X64mC[YuTa4N'aruqI ,E❇޲d@ qơ$@ ]ELoSv fs!|Ya{?_E]SFO'?;lB58_A5uD+V'g'DQү}-ڶƘ<ɘ(HȲz1 J4d8lOcBTX r%7EumH &%J E&St5L]NnH)AQWW'Ղ8Ҕ#3EɈzl$s~u vlɢ zlQwpzɪkg\^"58K$uU3JܸW-`:٦(W\{/6qγ(,`+˒^c%&Y( ((7CJ(D1lM mfwD:'<dIA(!Vth-J,(%u[U[Ҵ Ɓ0 0rjX*R`:K][H"DfSlRR=%u  5 tE ј%BD ta8Lt(퉤bk-F̗kRObw= }ʲ_4G_Ksw(*NIRp 6 Gv)Yi1Z2MX,\]7Q89=*{aSsZE)o1$zȺt&I&cN3Ɠ1w_ ]ײY/8?yLI _#&'pt:k:͆?:y:mk:G٬AGjt<%BȾJD(P"^UMKKTw>ʃ?ByOwXV't;1Z7E w BKeLG\]1tvxpPw7J>cڮ&J6WWȠAFӋԾ`6M8>yH0^ dM:'88lִ% c HQ4v)QB=i;t7tfES'6G )xvvǜ3  B.ϱ~ biTlMvM]T0_| ^ Zk8X.C)Git,ocb}zmrF4MN[WQdD2Hfk")M-*C|pDS 3_<>tmIǤc mWrqyүAp="/KcꮣkBԼm qN!wBk6Sź)ol+k[pP(CZcږ)ax.J믽_XٯrCSGXBƊ-Ek B&|cꔯ{ɐkz NOOYrDQLӴ8 "9R*Lc/#=XGeg[G5A:$bh:QUj{}'I5}q}dKpdYL]8/J -Zkr+, Fjsij I4Jhږ[_+{IُW^(>7٪맜,bm+H+%rjz/cnPt5i #ld`8zG<KtpreAUtMl[xp19yQpqurb5cX7^ qrj$Ja$EYOm8O4]by#8WclM'=TAUـlK[$ķ1 9عcnߺfqILmtA/}gg޾E˓j!Tt6lxajYu$iF݁zwz}sjcQ^˫PNT1k8` OjPFz mpm6r()@iA( X(gML&gLI? LA[N8y~"`2و[wbGeAY]^>'{}u n@β.kZS|-pxꦢuUDNg.(9`i)_Қ%Ow(''oqtrA8x7^d4%8 4i'9>d}Ӌe u/eMWZԻdLm~۸y} ' *Ì n)bAY7tPGR/I(OW1(JC:kA)B(:P Q՞ {!ӚǡamCGD4]EYW4U88WQlS9H֊<;ئA*o)*joWw_+_G'~`f |?.{>N44xl4 ,/e2HeɓgGlDB4"N$JIZ! gܾs\r[tvaڈAvȇ>' ?x/&g՛bMƜX.%[[ A0"SelVv&&#ʪj2D'<ئ&z IB R`SU/+Nu/fYa14ՂA4e|OwHWY<Եck;W1eY;mrd]NnRTHWEX8ߡ Bnzq ̠h=wLU|6$qC:WYUxJU 3j _;OJr3ȫA|bu$PX1M4 Z zH]mtD"b3:0HR֒tVnX;=@!M)fUN90Mqt`lia g=Klۡ @/lrag/˜ [LSc˵=Pqqqќg5^ ΁(qZ2B`jx&[ g eo61{{{(2R0G p EkiƳ˳>A>.4q )Qi(ZvmI|HD nq(* ֒Ɗ8.[V($u 8ck|BDP+_pڮxrFI!$Tu0]CQ98#D\,NW~?*Kw>[wv?~]5œE\j ʐ1aQ5jA4I.q0 y_x0KAJi;l29o%|#ٌ9DG> "N>)=o^_o7>Ƴ36努h>xW98f$02f*.Y=F6&}b3لEyJ[7d[5eF؛Run>I9:arIg5uk`~y^[nrhǝ;wL`@+LGwR0L@bp-׌8aFYGBZۧ P W>Mryy`=8gңC;mGY{dijq] u"@iΡ', i{t_/ [[p_ͣ>kٯsZvE#,"L5V8> I0Ko8*܆J*o8~vmnn#v/y~qhGQ_]$z9uNgc|?q^/}|ïCV0pAk9պ3:|(2?Blo]2g;bu'W$UFC0c 50ou$ ͦCيp1_d AklD{p1Xhͥx5uo{F/h6#bIc%5xFK0%SB"e@)Hg)c0ZH=BAQDD2BXM0x@7lZ$ӌ!7{xӳ'5Z%8SI2,4Lq\h2aCʪ,}/cFBA6ҡF }=ĩ HaLz;A Z#3EA$Yٜ  ~:8:}HSvg0ﳿiZj 0G\+*.dX?˟a2X찾(ZQTK| $Q 6lVnpJ+z<8ٛ처3x/!"onqk4e2dnw|NY_!Eotj4y͕PMqPQ#ڥ`S\!TGۙ,4D]0Cң*ZFx088Mu0#H*ln E(5olu!z#b5đd{8{uxC4'VA駯rrmUAUZRTsZe:mle'Xa&"EWux8[AJ5Swu۷iH#s-UQ0T5%J /0΃LRh|SҴ!ڭ='Z1Qй൷;75X038I"EyK6qHEDT]xeU,^,rl@ h Xs鱄a 6- m1 /򾗿_GL(pjst+"o<|楻/\ ` Lg_zeuγ[9mqh^@{p$Iք0 XXi8<|b6L .H)Q*\]^Z.i^\"U%Z0WAj|Ւ41 F{G90`6ں7|{? ~;?KA,:X BZ{2JWϙNR|kήSdkHKjhqE-G(?%~ o*$t0nZV: u:FI̓o!UAh߁5DYRe '1ׯ79k t EM=:Zp0}ם* MRjӛeZus)l_mJEzd<}vsuycNcC(B3&=@Ht$!ԛ4SĚ(VLkDrHY^!=q|?B8w iu-NEDe0ĸ^aDCdID'tm紶{G5}hIeoqQH-x $q\/iM/p}(:fyM9̧NUwWݮNdF"$D\ABn R_,@wC.B =ꮪ:5q=o\c e{K^YEkѭhqmI8 U[Ӛ)7HjIU̧%Ycsg#Ŝi=;#Eł76 C Ԭ0ր ܼvw[DQly˧$HO.>f>?"gΦ! %UP E& R9"-g5k6ZP0"OC1A"襊@0\W/,Xi*PJwZ)hj3005ZPG(8XքA$UiZXwW9AT^!U[7^W|_{7_/~?x*EivWS8rt؆X](&8YS΂c%?z>`d( <0 1uI)./O{o~q)Ag &7^d5d&G|yG1{[Tˆ^yjey!$>?.&6u5S%{߼/gwz>EͭGKX%GNOW[Wv*KEh ܼ~_Wv)z>OIP׮q v1"o|), KO|+ Ҕ35~K 63ЖMF=nܾKh˂aCx;7>s?Kq~9a :diubAD0O)g-/2ǧ̗KxDS bwh6chlҞ]3[]rvyB a2ZMQ2s9;']"Og~77>+|~Boo+}p9RH& 8ۉ,p 7k],-҅AJ 5ÌqF)e;a]?|^{h7nSDyzـ(Jk@BnzAɇB$uᣇ u9#MX@<9zJoEmpmNZz!+&DHpzW1ggKR!Gvb#hC?#FX (?"MzD`:]rrg\ݻBo00Y k@PWkE9eggBgwgz}R%c*xJ1MHi,ٿEL Ì^o&c~0Y`|B$_,s"+Z u5U[Һg& 8t$ B}e\EXJ,N uWhtKhJ)p*,u8tapNV&DN}W>i )N., .,K񄡢*@)6P8H{ct@ӯޣ: cmM7]RNb%Jx%ij6lnf!Ybښ@ =.M[|KQTQ]k2:uCcj(´]ˊwIj]G {W9;;mB":4-JkE4~?7/ֿk3_'%y748ķodkq3˜\\Xp,j?c{G|}kZrr:OQkt, ^)'_|ޗ׾h0MHޝ<>~|/p-CУB_$bƺ\'1EQ1%M6X**^ywǏ>?wYsʺr2g03no ƛeNQH-{Y,e:2 z tso\G  "Otw-BWhcJ:¸{ ,vի!'o rpPH-I1[eEm()OZX.[&9[WH|#]Nz*&KVS⤏V I@1[DT&_a%Y)FԠ,E5g6=|u$QL^N5lm ary" @iunަ b],N(9OsXk)$0L ℲB<1JqK*OIB1#ICsGbL#<1YM[hIUD(K(jRxŪ- /ܽ)JX. gϐрgGM^OQT Nϟ0Ӌ/9>=*Zn޼͋/e>c>yĠ(7_S|F 5\:ӓ q0mpz~𒺩%}ЬeI/$ή\69匦ݡE9\RԾ#B 0};c0[p0deg>9 a5pkǧ'lQG[}?zw%d?M IDATY7]EUb^(vwR)sm=BH-вl4΀ԥG"J=H$5eQa#4|.HNxiJ˰`3mi-yq$[@Atd%AH R^W#"'R:[[o]p9ʜf82~o@]Tez5)N6)B C6wHCXύ{auW/;rB*v菆4..DݸIQxr>qes;嵟|(5Ae=[xr`XIF 6z(999KNϙN֏xG?7rtv'^~hMxs@ݖ1O5Ɩ|B|Ū,q°{ula PTӔo^i?8n{ATyђd(=`8gvn0_a<> 7֐D#..|~ c =au{_X||~,B[~_ݢ(V, ${o|T%%Jx(N&hL#>j./Ϲs*zqg,,'vNAKhhͭps#|YZ泒4I#j[5'OJ& ~{ut|0Zij!eaXj&HgqJ@YV Zu>ؖ]3lnT@O(%4X^rq& "li%g״vEcxWiPQk& =$Xn fg)"wn} QXИ%exA DIH^ɮr9 Hq^:T!q/cZNZs! ZZ,qhA]Dlm\ἡ+ Mc &Sʼ41X Iǫ2eeͧW.C8 Cn\ʣzM.&a@]s$Y䄓'H-mB!yqAm q1[2]=6ԭƍ{rrNv{ cʼ_2?+G)MUq9a`<Q uU^V(2og}aXf1i`jiWSNΧHMZÃxs!{/rݐ7”gOOXyŠ.;;2#z9Ŕ}~t"cwn1XW5;8 USvSqJZ4q|I1Q%Nx^S500 ֲXcg1Wwv 84 )u]1_,j,ùa1FBmZ3L @v*%pw@Jl@!o)[6 c{ezrAI^x:+XBx׵ySՌ]R68} AS޻|^ѶXxoP#°ԤYJs]67vݺJ;Cnpn9^QV+Nώ^ƍwpu/(e=[2_q߸x]35[C޻ߧ4DwP>%o1^^X- x|bxͭ-(™?jvs^gxܽs),KP4IGHY;.9@C a~G,hGu4/>(۟7uc#`L |͠lCTMATFE^+,@KxoUiGȘ^o@$q?MѥzaAoYX=~QE"~ ̖l_٦6)X FmRIRs I8dIV\۷x}vJk 4ցWMhc,x!BHY[ D8C)!8ixvvbB.h)QAK,(֧9qX ) BX|EwD1V@¢$2!{uWe%I9)%R <=d{w1%mBЉ6]$F :_\At΂wTAs0cl'|8ۀsh| p$PJt&Y: / ..bHbM5m-H1ngf.8:=K/;{\N&Q RBe e IF1MhA](CmZL"TCi)ںHImU & S&wnfr1:x{ Ӷx!%gٿhLg<|tlV H3^Bk=ѠGiG8u 9 }ސ xv4m4DGXS#m0dwgz})A7x#~iU7~>yV|׹ ><=zbvȵ!oM?__[&< N U˝׹ypb~N]]-ez1$i/{"_ Zk$ΰNP`嚝=8 i8 1=%D^֧m[ڠ(Ly|>N,-X.$iHJ0 I>gΏ֠& B8J8Mo~?f||~uk+mA)+"iaT ֨Bᛖ5I¨/1Ӌg8VL%xt>("${ !M2V5QBv(z:>X6\L%;='j tFvݕest+{WpzBG "\~|yN^PAi-JjfOr֌FeFIV!ƔF)Ɍ[oӘ씧Dł=7\L2qI(%(޷xtGS+ʆ+{U[ 1 (˺^vtס9~CuA/~-'0u'_xϼmnS]a/Mq!8\+mT#h Qu%PZaXHRa@OXN/ 0nz5{ԍyAg^/ސfiNNdgo)91J{(Mx[\F GߋufRZ,[[Xt +)ޡJ^o2DA'$ƶ-Jz:;pZx'0ew-eMQHP55Z$IJ.X9q%އ CG'89?ZxcD1E?9>:#_Jp-xŤ`:w֒Fڹ ^C,'2dƛ/}Un\;윏>zU g8P,׬ ΎQ3HKIli)X2Ґ$-Dpƀ8Զy~ *( zܼ!)y9Yox ]G |>kPWUWQ"9\-OˆhLDaL'/<|.ln%ϧ@q*qHINώx6F#F˔?+/WHUú^SSQ Ⱥn:BvQ!|rBH0=i*YO<Ęn2_^S^וy19#*X.!bO0N zE,I-#F;Zwy)-9U$˒yf҇(z$zbr CB.Wyx7UcPAqx49 _"e 94KpHl@0b]-1ĘgskQڢ qq J8qĵZG(N>1I6"R< KH8k/~qXXcGPȒi⢛)( t+--4Jh bgk @> t@ƄZ >#![}=\␣A?E8hb9:jQh31gs{'QK@YwɌ@/rڦ[ )Tx 8iNB)\MT4Aˀ8hl:_P5BCMEDzxcrq-R5 .[S+`L8Y Ypb='ʟ>K4b/c],WEJ c=qP B H a 3qa/B`^^1W(gAIm+~ɫ:P 8ж^"T͓oRTϐ&T`ύ\a:jё.r$:DF%IhGkzĹk[%tuiqb]LJl*Ǡ7씦(ugК(Jz2n@PKLSeYb]W7\.Ҁ 8PaӔS!Wv5ѨO/KaU̱ -( B9BHΕ+DQ ŤwXhX_:n]ߣ7YSrs&:S'[c8.'ϖ;`\ύ bA: I1{{{ !e]3 yӟU37Fr\O̹ym %65Oуوm-wޣ?a.x_Rזr({(O Fu]rqyJѮ9x4]]Х,Zh"!io@CVZJX14EN/M F*MU c6F#j:_0H^XŃw?nIW K ZQnV8JP7 U7Qtµ -{HєH*PUQf%`2;QDn1SF0i0PaWPi;ܹ r;\iCn ZfF^LFy1Ŋ$0ŋN&zD/آ{ܺq /Kgx_bg;m ڑG8br1Sp.xZ\-tPlI€4N9F ֕H6K$x߬`js` x ܽsǬW9ƴDY@A^Oiܜ֯Q @n+mIMPDT[70_.!Ơ}խk}ώq&<ζ2&KDfP7!3(F(j;ǘ )@j$# mS>S1H.eSRYP!#>mGL eYLБxGij aEMwVK$ j lD7lnm/aJhkG#rM/Ѭ5[ j݋-mI$!At)]Zײ/iM*_"'oX>>~ꗈtıXL1S ua*PJ;wc dgz ku?_# p7[puu#j3[.i\Ktz|=g[nYnni;OK]t_CƓՒ{8PUE֔E_7x曜_3Z lQUWAC:RpzrЫLZ+vwX+(v³g?x iRU|Ex]qkӣSw+h_?'b0zr.޵eA<GG}Vpuu]BF_qptBQ4}/s_~:)tuM+Exw681I&рM^ HvH$*dD{+FS vEh`_L"C,ݙ#1_0Yt Biu[vR{T(Xoݯ 2F؈A!7QbuW`)]c:#;9%/kofyM][َ0 zO,͢"44&zޓ:NvY6HP“ R:㻤%B84;{G]CѬ-?y1c3i d~-;:A+CSt-:}N!D >)<~긹!%,]y O]ڱX6^ABtdC՜OhH !KB$ QU?yH41Hy&;#onY,L@hhH:`451u͆H-I:՟ËO!Uw&; Y`ruy``k{\Uu:dLʼ# Owrm.. hGYr-''vbNOvw|b[9=rf]ps3()X.( q->֎p! 4e2[|9i=7|+ Cn'*%N' 7:GgQm~-ujUbe5{pv:SULJC1}|~rUTA=$uח.R jٽ#-S8(vv'\ܾXQCru+x!F,%h<9VB%#pJYy.ngȆ!$ք |M[YV#ʼ"Pa9o>M{xD(o$M^y+NӵuCȦx󞦮ikd!z¶Ԙ JX(K_(eAyƣR+t0LLwx⒲LG).NXi~x;8:0Ift'PoU뚻5{C;)y@ 2$2._g8J8<9bwoۛ;K,aAzd:i?X}/R `gOO8Lpf+-ho}خ /9|}^_n"R1._KTENV\\+!@ٶ1̆ },):᭷D$sZS-V' #m< YV$q3,!N[`lMYռx3.LӿjbbrC+z/Av!^81Hٯp4bwĶ\uG*l!8lgbl-ֲ/8><¸ޠV7kX_C՞S0$M".^1(;T`Ig)Z88<{$4 c . htn7Z@ o  `+E>֎v1H1ZEfY ;{cZ۲n)ʖHGEp~9i;A4'@$N$OܧrXCt%CK֛Y?AaԎ0JT<<5n_`\Kj"SvGGnjs~%{FCn G TDc ƀ5לc9~^oP tL۶=+OztJoE8G״$M'h#MmR;)Y. dGoZ~ǼM8b8ʘLǤ!}Z+?>d|>B){/%^suDWɄ5LF'CR( Ź2a# 5+@K !7w7QHSuT][ujnF$fw4nkof.oI7{; `=,kpeCkؙd 7fHl,79-y}]jT5á`Q@UQ9]ċM^na@ [`nKU ",a>!Q#NrC;G״</ϯy}t8 ! _O=-W_;p1$$2=+t(Ig[# c]Gli-eS1LAH-vF]ls)Ҿ^H:ey5nʶcg,R~Z%x"0f|G 0qw }dAt]q1O}M5'l-wŖk:#OvM-ꏕRHٛyyuaЎ_0Ta^!m:D@]x# #`H C6-> Wġk#3hRl(8pwē7P97t;osts..g<~m|$ >e59;m9Jq6$P)Q4G^TmG$Dɐj1g,;#1ְZlprMs Ŋ8p#~THγz9 O2TL)I^c> #IH]2H3ʢHpNÌ=jWruuŽLF{$ِfIi8B:A^0[vc£Ko[Mv09><"R c-5{(qr!{Sڲj n*#z*@Q7 ҁr" %e=6IHuD񐷞~l"/Iyhrs{j"咗fΗy/1L&!%w08yCvO9՜zp,:g]N85QiE%#˜Zf״mMvXӣՌ3hx>ݔ d=قtӺ^uKV5xOUUf4tuIElyd4NbjQ0Eœ&btN&#1M 0N b:SӚ y^\!%JYHbkq#=\2ȆPV9;{;8cݻO&e(vW4Vko|z)E 'I=tЁlPE*{ƔGIߣltOzET5W[&]ոNq(3$)74[W"c$쀏>6x ]"|dsqP MU*B0F 0b]2["G4]ǛOXp07%4+lӢ|zUU?3'iE´1mE[{!BidYqE5(oёj#JBZہ$ z!Eݷ;zT C <׈*)a!U>`aoٔ5{\\_p}wx䐫tbҚlKqd&ZkYo I8^z=ҁfggD,+ںf <g zowt)ZTeImZ}QzfF#R=L;,'gGxHUac4HxԲ?DH5K>|=>|҇gǩtCX230ٙd!Elc@, ¨"HIa2 M' p})zc'y12L`0ki\oW8A/y)&Ӏ0'ttmG#/*lkpX:S!%T PAB $6@+찶 } :)"pD #˗? nY.ɒ%#q6@ p.f&:WӴK-6,H^R c8٫J”=>y#vCٔ30B1H(]] A P:b/1LXo(-QLg  nnhLKXWʪa-PRkXF:X"\WBx̀8˨0 I5GOK`m( n44%G.[6 (]ΣCMQHRy3 :HAzzB=$@(YxxJ>ԙ-Yw t;ﱳ?<{0 +=yru˦-Wt ''g7s.GTxMQu5NX+6ےo/?fE`o߿GJzA-ȋ4-JW0M@SUu 8:9ik,WK1L*,`ÚpH۶X!I(t[W9CAZFÔl2j*㹺:X9MZ =vMu=O[5..5K^]@ڦcU') >P(<݄:i-8vs#5qF)t €Zr;1-ʆ0x/fmuYqZ>F8ۑ\\^{]Z͉k:f>>䘮&&4M6/n(+%EW$ Ehɳ? CY(ˆ2'g8ZEea6@ 2'Hh2ȆX';8FjXgk0Ѵ vS/k_yJH=UAB_{_W_L-_??oqfIbcOX@4mҚ z< kZ K"2'P{TUp8%GklW#GyA3 -9a?#$XI1RGf 1-Px:YQhC1 kL J@(XK V:C ^j5|,AXU c/ 248)4LC(){?hP7Bt-Pamjdd8A oQR!FGpx/?!e<'gLOl]hHӔ4lsYa-bAgf%Tg)񀋫ϱm*cgO<{c-RKɳg@[咼Cg{Zgcvveڅsc+nMk ʶFGd^0]^Hx`zr}NO1h 0|I:T8nMg ' ښsh CAaz]XXI#ERV*hgΩjC6hqHabLuX$!M"4`>IP &1U;٬ŶfowJ*yP-! %HAđFbu8n [F)%muc<.PEK(m%Ozm EYֿVG8ӧּS~Jc*x`)1^]p^?cȠ[:Ђi0H {G [haDJ (3OҀ1ރ{(-Xg= )D uW0_t>1`8R1_eQ/S|րN4?WCz6_D=e@EYuس0 (ɒjIklB!]sxq˫KQ);nEkJ>ٟps۟R\|~ehе$J8:8(jL[E_gb \]^h$g4a:wޡi,w)Gj Ɠ)JF}ȳwx#@ 1-TaRז3!i@R7\\jz)=/6uV0_.8pVWi 688:>E_3 v5&H'8>s\TTGQ;Pv@db.//I!u_!;LW8aAҚ8(W8 ӦL䘯~d I@^QKjA޽ĩ%!ؠ$`4؈1Hտ܄%G%)qtcpg U! #ƃ)>vv|3#Vm a'),aS̙.q =}2"2 iK6ߡR3-nhg-J(p?HС!Εhe65m1i=J$ɀR0PԮq oRێ+YMd2b83oo++dq>9=yV!MkizXl6uCMI IhZ;G! CP#}O0A q/E֏$qaloaS\G(ac E] *"M&-֕3HBEgϞQTMWccga:18ٝRu *eE wa=,!m]:ݧuu`S5Ihۜ^JijG:9 CG7?xt*3d;"uD vN ?c4<@|(TՆ0W|vͧDQwwzg~L[甛yF!Jubz5GQJXm+)j0J66s//yt-Xof{;mIo3N)ʜ $&se"/S5yUUq2ɣ{oYg]pw~y9kȮ5e1@"/+v`0^m 2,,e7힪rL,g}Mn3ތ<~.a͊}hH4G](%ɲ4J]RVѷ˦ $iދ<1UX7$d$ m 3bgh0:/o#f]vvb]\/NɺX)HYN~׿z~G1l8]k"F]ƣJi֫U& qa\1B8R0,֗]I>x 2HҘZ3E#"fCj BpZPdk*#;p,+>FXF \"c:9a/ kHF$:ES.ϐy%z/nHbKq¸(fo.I ~X ǬCI Yꮙ&,G20 ' '?tkWd^4;$f<3,״uCe (9@0"Pַ$DGB%S=Ǽ[z)׷gHJ&;>ycn6x Q!hgҿkx/HR4K)! IVyfWרH]H%x!{wExAdx1]{X!M3mE5Z^|EY H%4U`~g={O՞HF 1_~%>b0-  ;jK4u (>J:7yw8:kq ϑBg[ni}Kk*N#78Y*IM#ҴV TŚ${AQXX##Ґ)mXyMo qcqTXCg]g1I %Q$xE9dkmܚhPV"!1$ZjDH*|AIZ-;TbQ%~[?mS!d_Jsyu ‘1mA0i,Bk t1▋?y)#erMcϾEd2agokDR+wOe,71ߝ=pH-e8z+fEJ $V)#E*#fd1p@ɋoskKdiJtg 0]6Q!Ùkḣ"!$GX ӵ]W9DR"U.6X>4fEծIƴܬ2ԽTQ|,.X#b=QU]גF)XD:a>ڡZTc2$Q1掵8Y\C)EUs\ P2a0]Vr\Rc4wId- x$wG Gu騛Hc{/qci,UCf>g}ώĖs►<j*.n L8ʎ֠cw59oi 1hއX{I$"5J ʪ ޻55#"4XP{l ?OۂsX ]{KTm #ʲCw}1%QLClvHND:a\+V/?3o&m{tŚ9ŗ?b>ui3$\)AHt7~'snW%oϾBeum;3MZ}KK6Ŗ$gEb躚u/ ͊tHp4N ^}rbLT c mS^UrzeQ-mruuIQOr FYbo0];op4vuCӵUCxdz-Cg۾=mIbM &4l7cvf3Koft}\i֠cMpV:c$I4K׵[˶\suyAtmEk,M"G3?[7}w߿џ7R {pѵC%-:RH%ImA'cAѳd*!N3t ùҤh xzwɘk(˂4=T'('3״vE1)6jiMӶWlsƓ>eQc3q' ^Zlf'I"p}!++03p⼥jXEHZil(lk !Lג$IL>Q7s|~MO.^R4 "YgE*ZvsT)}M۱{0OxҢd?g6IF Ҙub5u b8k(Aƶl-S.[m7׬1MP?_;Qf Z*P9OTM|źd:9l6e7uŊdQRc`RP75U[kO`0$4mCU4Ä( Dǘ֖͖@s5+a&o&!{c-)`4X\]oA$ ^HCʈSVU>o.'UmLρw-Rt]#/,,UdgB:P |O<}r~ThpS/no9>ә-s~/ b ~ِ˫3 {,%i1YӶ M]qt|BH%x띧Ɣ$f8֡Ed8V4V0LS$e>W|q>Պi)CZ:&8a8 "φ"DJl:$MRwZʲDݖdIt%52JZ;z +ݯFנk_{k~Hw$Q`g>6%" %EU"d>5$mӁ<نHӊz(Z|Qpgs~emyCgז$R( ;Ǜ :F42򇬋 :ߑCq$2b3Hb+i-:8ؖ5 )R(눴}]"DhD BCGQ3XoV(h%ۻH"C q-ۊd@k| EqbuF\_َ?x^-l+t@5ydB%;y,7T#5[%gK>;,7 G#lHgqﶹ][j<kxH{muǶMY+4MBֵَ@ M3!eޜ8\]A2ao~l:55e]`2p Ϟ=%4W$Dھ𣅦*2q㇏?!1ɒ=$m1i:v5 f]putgb{1-||Eʎͺ@!?;ۼxu c>|O-Qx`7ՂoQ5kjEnHbiWc?ED*GY΃ǏQ:!xIx8q}}NQt#GTE_:AYkq8h 3OygLv2CEUmA!O>}7 o’Gt e>S}YK9Z1mE #.!8Dr¶Y`3KVz<{6UomQW5qӚSTdݮ˚. & |Ǽ~!f/(tm0i,yC)0m )q}oΟ+xWkЛje|;訤6 TϦZb-0m.bq!))3(7#1xA}OdkHF+# b)ǤɈ E# A.PZ gNCjsTg)ɘA2! vbCњ|kCh (6b# ڨza3F8ʪE!B7]B=E1KkнHjIEgQ,/"I"X 2жe_ <BfL&3F USCWcuIHX4uME k5@UI1y>@ [q 9Y43c$4!@c$!V QL{]} o|֢O% q1NG4tY,9]A!)o8zI֬64+^~qOv7?aeyj`[Gsڮa]a%\)(֞w߽Op5]YS:<%Wi{ krIg Z).w=d"R73 Rh77Kz? C[U 3`R[%'LlױY0nKnƻ̟3~5|o~Ϳ|D>ؒ^CGPO}xk IDAT!k:0ՔH ~bL_`Z]! Bsw.E8Ma>ATA:%x !]RWޥAȨG\,6]$޹UUY31H'P93Z!NܬV\,Y.e%uߒ%M|2G+I7[t-J0ޙ}P`t5JIˆ㣷8x %]@r"$.8㜪WA92>DQ%iIqR noٛP &Ѐ2 ,bMSt!D90ߖzNORA&d, pdE{8#T_-6NT4knoCC$%΅>K5^X򆋫l[ c0V5&u߂;@ 6d 萺۰)mfSprz RYoj,Vvfl w8X5hz{cK(J6Osql ؿ͟8a88?{ E3 YQ˂(himz[Ҙ~JVۆ y곣UUG &R0E:a.MdBW֓$%I`Ze@kyFEh%,eנB>i R<ޢ5D ` L&#\pl-8XfE]{Dp]3G9uRT&@vcLo'XQv5Y0LHӌmQ2,6 Eɲ""dw/oێt`!f(3{>5-jO>cޢd<1D2泏?c[U=dH\ެ9>zh7<8=7tm"Or,n*65^ NRw-MCD5EE|S!?ml6zujnkMޜ7?04ZVDrC }|?$IcTEǜ `0#5"gsno'QAw(*V 8zET(-^ dp4%MS88dž/?c:wLScto}뗸'5whEc5B,)D"Abs=um* 8kxsHӘb雷wι~CJKl\fSUH1|B(vdO>,M[O> ?y jf}|ȧ?%j$:GIO&g,V%Ç^:$`ʎy0rH7 fY K883eX~o_|xX, {y:ƴ0gHxF4Wdhb ! h;!?'Z+DPn֌&cHCYL3|ͦ:8&)Zprz@X,i[g#vڦ3|X\55OٖߒexGYlHҌA6YOdDqf&Hl+t$ښz`R5;cvh)/潯דo;%^JyGH&1][9&ҁº(-UUQ ޕ[W<& EF^~#%1j_,ZX8a4ssH2zi+ * "FJI'8zEiTv|ۆ׷d-%Z*H#B-k;)~Xm%)JF;Yњ q'x|~B<A:pMN]8NY4fZMI=vf(-_|qƳA<cv3 5]p{[rtv%46VGJ3R;]Y_y ys}} D3m|0FZG۔Dʑ=0X16G$Jb4fCH{Wx4Kn;v (ƹ i !Ě@etMC]lf'_~A.~kT5RƸ`q K]ׄVh4d],qv Ŧ-J<5Ta<Q%ĹuÓl/rCR-O(IlV|B,đd>18ߠŪ(ú@?3R(q>Wo;ƣ9rA۶ꦤZAR)V5U7tXC,yRuKPJ׭9G6UKl65a(s~Σ O4 I ]5vI]xkD$Q8 &I% 7h)Pa=D9<^H#urYpxl2fqu^PVWWTmfa]0싫k, |{sņxƻ'FIě7L# 9O|c*- zfCUV&fXcIMJSC_{[ݛrqJkkOy?9gWkLf4DQɽSvٙnȘLxQ4R4m$  (K3ͱ6StD4LGcԕmIs|OUwv[|ឌ2޼y劏?{Nku`UQG)<7eWlhvHbRLS&83JRUEiP5qk?8:WoDe)?az~σޯeֳ?DGJv(cPp#b8mEk di[oMO^@!x OQ*‡c7 !B19%^Q5O)g_2v)7-h!(YI Rϸ]Nz^|)B5qw׏WW\^\EI l5*@0LvlnimASt`&RTu_БZ#8Gv$ޅd٠Ϫ_ /!j$Ғ$ ?`]-Lk ZG8G.X,ST1 RcGBu3ߡni!I1e4 R,`o~!_~~īkF>y@SuCL)tmMg46pq]SP% Mq&pY6$!0iJ$f23۝seٔ89}xx!|g1q| w8]^P5IY϶췊M뉤@KZ 7HZ8F\';{$ڎtM+!nB@  seH$,,xzCOW6h$5_|c޽G_wbl(MsNԖo733~=g>||/U[nH W/_{rI$gOߛpyZ0@x)>|\_!t`nxBCTs\rqKKk Q*k: .:yXO9>zHSmY/Y-O6kë/°Z/Y+ڪ.:Lc<)te!v֖8etmEٖ FRy$W 6 `YW5:d'ua\NI:IWA"޽=b!6 mcFi!NU Ztcv}\&4ِm]vB f9uWLfe_{Ə~}y8ҟK14]]]__4CbR$$hMsِ(IIڶ OD'8z\̉i3tH rں L{<|5ί.{q} ޷,/^ѐ>~ҩN<ŭ[wx| ݡ92& (di Hn?,5%N8W4u@cjKvg\^y@ @KE$Q.e ד :ba:2^LсǦZ3,u>N`,8x4 X+ (|>+i,y,+]>z1S>[m$}J1u7[Mmi8R1 D};L30,4W1ДE tѰb9! Wg,sɗsחHv߭O_^Z4V Z3m` w=7/2pxc08'Ъ)9WdEBPڮIH*Q@6':xm֨Zۆhi"u kZcxAVGaOkԍCh+Ҽ&J||OP6%ɌdFIk}<-"-0VC{$ډٚ*wvvr@bjK,V%~ 3ywy]R4,#JJ.'7F]ex˫S8}[|ǜ4&xQ/wz^t4MM(:u%E$,*NLK*?3+8( W1^i˓ /.vÛ[(8-_g7',Quÿ~ /q"hh࣯ e48haퟡn6+:$%E)jkx̘Hррf 9q@SU&2Lc8u|} 5?iU D>$a0_,0Ʋ^\^䌆]nݸ!9?=OGUX+K^1:oN78,r?~7~R-^_||c׻]׾[7G<)BX#%=,a>wy?X{\Bx-.B }f! or؊ヶT8':@?=`>tC$)5O^@EVԕ#"ܻu:zm^%.Wt{>_|)fɽ{?|h/Џo=(a`M%;xj7<}7 %k$q^GQ;;*mu@/0&SŐ&*(KbCU]^m#$lZ% LmihxL%mǕm;RVUQ[ 4ۣ-nqz %#jP)lZoo5uP$ (-}Ҽ+{'_vyB/NW{n{r|K/w,CAl~rdo'|'˿ G?ǔEʗOhٗ%* brxr;w tzC~!N &G/)*wTe[6E*QZ3"f!t$mqXjӰ3Ҵ}IPT]9PTuEt c1Q7ma4 Qck?!iIEMV:7ԥ:GVZW|hngR( TܬJԕ7&xce4$!4ۻ{LfS&{ȫxyr\'40![ejS_{Sꦦu|fZ3_rthEI'587~oqEnu|֬Vc<)/ <%֡q@RɒF(5l :lPG u|u/(*&k./A*v1ck7)h0*x39Xm$JOVoFh;5z5(˒Ãlv,S$'/V4M) tȰwHuQea8 &/Jv}Fڰ^sjWlo98zlNFx EYojo/xGڤHUiPҴ7! lQҶnh\\ic0LדKwT#WC@bM`xubV-B%J.G7$ un>0,@N8DbMU-1TyH ijjI*ZOa^J*Nbӊ5IW2Ƽ<'Bo>.M]%0MӚ5\ Oj=z,+{ Vd]N_oXW\^"e^( ϧTEv&Z4dkj6)kS3Ǹ){ۜ_=1Y12-C*!A>Q#$lSt>q<?0"x<1m,AQ^q imW!k)(SGƲ; -N6bM6셐 IDATElFh^ u@H.ԦsY(`kŲ-ҝ@;8q#M8ݰXHʪ_K^wdz͋gy#}]޼z6c~}f]Ig3(V4[qycl/ <׿_0B}"w<q'i#E82Y菺OYfSF#M Hm- ]Fe9O-Jʼ6-d AJʌU1[n5έ[6+EID Cq8k< ݝ0<:&N_Yg ae55)2H ua3 b}mvvlK|=Pf '-4cMb;s鰞ϙ\ux7:?ycý3$a0b k|?("{~,9>jpl҆%yf{y^T$݄nKףi,quC&Y&+KkX+>dEj=NG3>ktMh,l6՚O |EyG&L,2#G{w08!S6 pqﲽ5nF|?XnJIekKx(~ ҉4Tu$RJ֛gO/$}׏)s~-PRW)In9ד)G7Dz+5u]>~29ggcO4|1Q;8?z9Ol6WS7L/K 8=Wƌο~tg}8nu%ʷhvky>~j)U;ߏH:QW1Y5mx1~sC(4جJz. Ԑ0QV)Y>EsHEUy1gWqpcbS:Eis)}vW%!C9CU4ՆM:Ù ߫mJ{,+ufQz [ܽ{p~MʠF'|{.2_\ᖪ!Lkqmr*`&=^xRO _DJĪK]K:BYp5Z\qv)~PqxV:rq%;CDԻLJ +$' eFR"7)|/, B3rqKH{ E3<9'OywH!W㗼:}r5a2s?淿u]yy@0L'5wlͭ[{l\Xe+/|3,k NBh/c\ lJNBe)HmҧD<cZ':xW(%iS8ݽb,EZ^c8iL͋*c؏ND(›)cn()ci@uβHqTAIE]QVuS! %B9pqxCfK..)a!E3S[F5hg2߀K0 tXB(d9qܹrpk%B gg(-58=yFM>OO.7bshApH [./9y'KecHp|sW|i(jK^dH Z ڴrgL vd$a^#0Lkgׯ'n OK.Y^q_7_U5_??Vo{A,b-<ݡ%IE{>R |+H%Rv*( H:YWHQ\9iJCף Ejx!-t0n,ec]J][8R8`df Gs.?hNpnB^Z$ *z-Lm<>䣯,U9Lf,D$~z}>-ZhtipNY/8iʚX ANh( -VkOBy6Q/"w\b.PJXk2n^6z#0"/L{ C$b9|b5EnK+l09BFEE]Rm*N2]{#>9! ݹ͛ޤ Ox罟`o 6eOgwڲ2>#¤ ~5 NSvTUV!'|$Ւ,ɋ+axr0@uA{nCTd#CV?n"/K ZHmV8>zkӊ8ړX )# G8P`l+=wj)OZo5>si<t8+t:=#Y-Wa}H?`:)UY$ B: tΦX`\!|*Z!*Cp!$  J+le7?&;;bgO)5{e٦ hN#]B]k, |/"Έ(Q%ߧrN^}֖G_QW%|0ԕAz1ZD7NQUkkb0e|#_Ƨ;}M ‚hΠ$(4 0V|gXh 6AOaLӡ,R ByFvj5TMVXh]6D볘,؏Bo~K/yq;{pd(u<jqMvhOtc銲*٬ i?6Ob1X {~/IxkX2ήl cN/ UHaMNDapG_-Kv{ي0b{kt=eSX+qFJ*hME,sq8qܡ.^0mw9fIcB6]y>R5e a),a98'NvF}I׺Ŝ(3/ɪĝ>qגu@CvZG\PNBL#6%npܿSW>CHTgy}kUyz5Dok7cV5͆= ^3u6=^kTU>US) s N,+K6,AEl>C㈴GE*!h/!:0ƒ%BjjECy!UvWX,JPT-!/ OyDaQI9Zs/Hhv=gj|3O7<58>| )B+&W͒\(} p4]xjk QJ5~ohk +[h:Mvp+R)6i'ngQVz:-# {(wH^3|]F8:dN"qeo/yahTb22+CcA}5aFz7n찘]3;\]?e#m^ ZX |1VI4!SPu v)urNSgMI'تuGdeהeEc-E~Q74qE5(4k4XVϞYe(fh | )8 :@{(/-UUc҉cz1+PQs͟eW/&ܾ&}Y9cSKL] W㫈Ћqe aDۧ, wyw)jjĚ^7Ę0h𕇶INzZJir%i>ǓNgop<&@pv(9<8FgWYɩ1O]UysܴӦ)-Q84٦mϣC}mkƋ( y69uc򆣣#*[8CWXhi(2"&mTڵ<[f^S-f13yj!-jf&c*ۋ^3PyN'z| 0t!ertkjH0lݐ-n)í!ҤeEYנupֵTfMzD6H$\w@,S345|壕Zm $McP$qJ)Mc j#vlMk=M FVuqEmX/rlx%US'_ZNvfW . )y6yVO^?#^/١X\u{f Mdrp0ï?($@8C' MƲ޹8LVơ^r2K<锧$ uYCVavo@$ 5{sG ۼqvjCOrI ,H 1B0ўr̭#`9E~Q'a<"l6+uJ*]knߺdz'OX\(!|(b.E|, ꊺ^BV(ZMUŤ_t6EBc6&p4**USq Vp~$O .^׍9==b^tM2­ K7yrFY._Y~ \Xe+'T {w9.y쌍]FM̰w|'ygz.n s?%&XIW QΜgJs"11K71;_!Awh0FYR.%5rj!IB} cc} HK$|rXYN>=.`9S%W]|Ѫ;-6]'dZ&hc`z|$D!lAy\t uˊ,ɲpD'1"\ٻǀ&Y-: z< caHx΋/<;9v>kkW~uhD<;O_{Ɯ .ocDMݤxҴ!f>ppꄤY=%^|leufl׮à #^ǰNQD%M]iԊ,;E=}>T G]-` ~CQ ;ln]cmsO?`|@'h[#GU5Ԫ,˖WXb%Yh*Cϡ͖4yׯ;oa^+Ɋ9O}Ǐ~#o~[\+_VCvvK`]BՄѐoX.iL""-+p5 "OSVGZd ]*]`%,5 }"m16` }0ۑdՒ1J]AVdYES[VӊdUqioQx8Eu]: E7mljhKTuI'Ԫ``th"֐' Qc2ΧX-^y%>]FՒլ1n"PXn 9dEC*Ar%lmoώ7z9 %FHpr2gm'<Я9P)M)ΘǒbF' sgY_@.Eh"pM7vzmnʆaOp&ݎG$ɜbΨ3B._ȷ"K {.0`Gϭ۠V9MSwK{7yGz|9'666C7JehsVp֡+$AzيAOץњO|<|mViµ79=]n&KNaˬ wxY-4#zG6-NNDu"}<]yOWs huTa0Ӎ}B@'Xgۤ!2F d߉]m(lnmHUM[&]Hn^2e>=lr\?>?L:?0tu>x/mS 6ksYVi$'#اWNSޠG]$I #ڌ> q+m( auBLӴB]iJ $xNЮ8K9lt99}ĥKHA! R6H<%dctղ@HK= &(pt2Z`x5:Oyﳵͫ'9f|o N*>c?UFCv\䔼*}[vZ_qx}&}%Gԍye4A7*9O@.V̗sz>8Bpq{h[knn%Nf.%^$(GGsp\ǕTU|XLeΈ*Nϙ( Uۘ.Jzޝu}.Yf32tp8b{c^7XK4(:v>ڪ-`j4"Y>+lj?h >d CNi`eo"A#݀,/mj,4)2KY>tӆA )+E(@ɩh\_==N'`s} ۬366>Te{])V54up@ +Lg'l_xs.qס ]sjevmU_09]͗qFzy2Cpe'IF+&SʦfmNC{&(۴jGm g1"͖@oإ7[-epァt"kVAt1Ce s޷VHϰI誢3%pyt9zvJ$8q2R cUlo![$} -[:1xHqlHiR~/nً˴UFl;[H9n>y?_E=7o_zcÎ 4G,G:-!Ղ8 BS5K y= !h0Fs脃 $RazָzKϛcQuR ۨiևc> _Nz>ʇ~tSܽa?pr"[aeU l?]&9Mcq M8.^crϥ1 u!# 2%kJRSMYSZ܎G-yI ,ab08C&yrB Jׯ_=T]yPZh{vUEQфC෿SI3[丁t{=JmjКi(D]|IUGxNN\,ׯP5eq U3'ϧLKm:=$O Z[ /"enyZӔUnݺjRcu,<%LJ'.S>LioC=zJ9>+꼠LZyۜ"RO2GcjM<|" =ʲ@lo?|~h'|i!bZZ$ υkW/0D gKeNG}&S&Eyϯ!bޣۍiŅ2],; ղ;/cLǭ)ՊNC*grqUvPڰ==%t,By [ѐWo>?}|uBD~_ҿ /F??}ӟ{w"))? 矯i*(B(@zpa<&2<.ӳ9Vnr\`rS8>ƴ #A[j]uT]:!wȳEq 2TstiyW_)ΧU4e1(iK%,$+>!!la6]#O]kqݔ%8tF4d4(e:X-t\tI}U>=˄!u>E^%a煛wx7 cAv*h8:=o(xH$4Pm"D*uUe%ÑX I ]r )<$|xU5gK\<h,>Vԥ"tGHQb)+=^7ӱ85. ҄DJMQ<=G8+ m 1fVFzJ-P7OT~8Br /\+w r? c1?a8ؠ5~ "Yt;c|S7Y<' |;a<`98z6C8n5|XF[/ߘvɛU,nQOll8<=de \m&2hj.[àQA="Z'>-F!ъ븸j,˴h¶l=@[MpD@V,`˜$Mx#?rXNCA@7ö2BWj\)(u{Dž ƷK7|I!8<:egg'SW 瓧%7]4)a{{ UP ^sSVY`Rs|W҉%YY҇nQ,5xHUeZ`)KOYݻ/P?FC!Jchpqۧіuc ~''~ N"`,g7FXÍ˻ܾ}$qt~F5n]1 8~|BQع>{v?WvwLqY }U:4^g@?Bل0TǼGX,*!o}`Fqv:!o qUՊ9n#tzg\pqo,xQEV8<{Jz•,KFMnܼ|ηX,ɒYcJj`=:wqcC9Iۗ +AY#Jd0Q FWx;84ز3DG_ ?wA/yXkols=8x nݰ&kwnJz>'nb.kgGm["WX"&D)=uȊwuIr0Uay=bJ9x5uZ6y^#>ku(%ReG8=ǜ/Ҙ)PM$9BH&OxxR[&c⭷<=n`'X1OSC^y*)՟ze#Õ(A 8~ ]Da+p9.E 4EݮZMQH48tEM6li,2:]vvq"MϨ hvP$tON- }Q-D7<Ѫ}]aH]R C2/N[e>ϠM̓{^(wnb1?S ~eY/9^JU<~r-4m>4I B_ѨBwZEM7:>JGq)>{O~szk|?ĈE>'p`1q숟O?fJ02e>_bmuLPG8:%U⤬F۴-YAon,o󄝝}\Ch4|1o^d:C*C[~A?_%@|Ǐ1\[|,5T>A'dQ|#}W^̦9y:KfݸEs'\$:p]5%Kvng9O9:7>{=n[ >+W.q+ƗS?'V~<=?UWvwFVnE^@~e'ٕw6?>8ڻ|_7ޕ-ġD %LS#i+=\y=c<*4p i9DQpDQ8BQ낦N&=޷.:q CjUUc$qLOkHrg|_n .7Ƶ*r䜲u8Ces]w&GZK9B UYbј x1"Oq5BFloflػlٻ4M՚,O'> GfW^!:шgGO&VN^ۋ笖s74 )S5)hХ4h_rag&mQA7^4+t)؂4`aeʐ&-:P5q!@8 W&G55yRbm<ʵHbLMnjFL'K|4aR `m & [x6̗K>7oTTp78x!bAY 'b-zAr1.k $tQщt>A~bY`F3qrrz(kZ: CعCh\$:x“Gm4劼1Hڢe0 H%Mݐ J|RyAP+:. ʢ_)"%ʢDf-<ߧ+,rr1ExBX4M{rk-ݨ˨7*Jdu.( . C2A׆MUB1YB# :P9A\R`}kgg 1N X9e ӥsqqgH%QA I*faox·X-*=&9OD8xb9kv.e)ԕewwS7%+YV%YcC7]GGɒ3,#;r߃0Nxd!\^{u:~3aÇQ+P7Ab6o2]ަ1' tٺ!l/xG]}Tum$0 vVZ^~c͉yTHUdNVUZ.]ϳT-ؼFer8@lnoل…~&O>r=f)ll:ՌSݝrytpa2c_Gӓ}IRrʋL'3&g'-T{ptvB^ll\bE_h}?O_?~立ۅ#Dy[Gl^}c<2_NX,'XX.MHd+nUqBm@ Fii WCk_|?%qw6BW6_Q\c0rh+^KvIQ * ]CH1œnד%T!&8ZoRpufEȫVg(PX KZFXj۞8*º~Gt nd~#% (Kx/'C|JU,$5޸&f8fcg{7*QTsu}UIȘR8&q 0Hs8eM7$7ټ,fǜ 2HV 66IeTu`О*hMGDр*q\AJ2+a1}qd8L*˷M^"Zݸt^y>wck$M Q ۶)ɲ Jkvh*G﷟S4tWC (e AKWq<1Ru@8.eN^#,U5HD[5~~aJRHK]У15cXu>.Y[V?7̈ a()u0 el] ژn'Q@JF}|?`cm@ Մ4Im ɪ$oZ a%ZV1KıG^H|"+>/ń)U]+wyϹre c>wX&r n^JO|?}][Wy[g\M^Swd*Q%>kyv|Fm1U1{{WH$T v'O)˂$+5l^{H!}]hy6u-t&҅ZZӔ+F0*r$B:w;\pl>?PF>b3_j1AD'LfK.]yX[dk{Sm}z>|t /lonSe w_(0QٽCS/99;@p= G!q˫(GW\6֯j|sWb9|۸_k/?oO-ÄVD~wuQ'` '_7'Oi w}[?|e h׌=i4VhUHuXVn]\Q V.R!c4MܧnR C8yiN0"RJ'_xW^x? 'nu (3,G$iC_cU+ðXk}Tbk\`j纨| $ڸ,0b%E:X]S0 4YNǼ}MDAIӜU!`Q)u#-Q4BkhǓL2B;9\z+h l%֥k J%[`Dj:m8A獱 uC(ܽyUz}*d6;&sE$|w:e[ܺ2[;|g3v70=kw\t Wyx-8+uSb`>["G/@}y2%|Ȳ|l$B a4UB1xcNOUi!V {.~*O,K\72u<|8qPA Ac4 <%/r1-!K.q OG{Cs56ָzt>kCv.lL&<>xDdeAߏi .4APm8|R:N"oV iHBzhk'AHS&tC8I-T<\\bQ,AZyAg>Xg<3]N1ݸr8. pC9!*\סxnȪȨK1ZWûe|W)CХB"hLyl1cwwY> FH 0(rp)i$nE:c(\n/wt$,&QMXѰ[hwCBfkwG(N@SVLK q|xL;F^ F F[.]e?.^В/|o&ep6HGKNF6%s׮QuQ2r~v eƠB Up6bLJ pGIb|6ORQe9+E݇Op!c .g1< Bƪ\r H)+\Jyba%d)x> tوO2=awoW_yo~Oi4JWZ}ţ'̳^.Yn1Gӧ8?ß7 mkֵ/??k'QE•Z>WV;󗐮tl]ׯi'/,Itcϩ# /ȋBX\OAD Tľ)Nq]# ȥ**U.FǭubL8J==>_9]_A?ߙ/5^ೱǵ(=RlF{mscq tN:K+%ZXLAk V N`mN]xaEjTSpNF'\@ ȋUTȁEyJZJ1H,G.5-hwH!TEârIEr:1n,4vv 6e6 )a@M5K[o]"]{fyR s"%NG -344:G+E\iptM-i:goo1{kL揈: =P5%BZ|7 4E}6ְrA< #]!-J6:$hz.#CeabY, O X/m|ߧ "*48&!899Fd9( ȡi4j\[yrMZ)]`;C29E+r8g&ePW yn]D!V@^),. q@IbEhU#A0\lQk̰](Z.! R(T \Gkwe!h-ʺpDLuB FSdhIUBiRDU]Ux.qu Ävܥ+p$i %Exv :]+Ϧt Vi-%ʃb5[Zv|r#HeVUHbtY)^/1:;q$ɐ4h*M+ \,*Mt6,ta>|2O|x)YdS:'g' >s#IᅛR9cb68G6H,a9EcsDGlml8>#&g{_'#^x9vטGޥ1lvyOQu\tY Q>8FW*GJB,_H*%'iR+Um# }2z\lF'I!h Dq8xl: 0bk4K|~Jcg0k}K,E8IZHsa_l8f6X߾t;ŗ_cyukt>tq/tV5IìkW?֊ޠ%1kpx 8lsx|@+!B¸%5Qk$&O8ނ(pٔ5>UfsX.( ZEj I9>535N _zئBH*)( Bvk=puR!xN/hOHR^6Z0Ta VKJ1dAQY U-kuEL;.nPI1U[63so~0Y'1;ϸ)Z)8Z+:ٔ2/%[dǵe8N`}6̲9SU2/s]{Ըlo_`G8 d4$(<'-/R4wpTF6wwv;"K'h4EvP*㣏>Š%n4>U zÓmAl0g+W.2i"5pawӧg;EV9Qgi!\nQ;Iҳ94[Ϳo1Ѡd⋴["' s;xQ@܂2qx6ŗg6fq\Φs6Ibwcg`0N[:{$`åAшG%q>QsUQ>I(:ݾăi7)Qʷ?_Q7y||j!kk=t\ IDATmCA/t_%JTZ"%u YqtxBZm:/pJ0H,(*4#U R|/&,}`QU_zN@GXYPiVzJ ZgTeA E*x a:;,3, [x23h.%'׹}pzxH+ gW/pz~HrTnlm-Ȳ m4Ne|(VWW|̨k*"j]c>wc&EJa&s\y.h(UYYf%e $V6G!e]P#CF!g"X-v'&$^d^Špz<1q(Ck$֘Hau`u ڲT=keIܼyVrTS>BT ubknhEXrM]{h!p=,m6`E:PEa-AFQԈDž[M|xb =A/q<͠gFoɔV ^~&Ó\sM4 u /!/|_a{?o'[\tew] )ec^ݷ`⥗_;o}Օ3KW/'6r2 8kWqaoak6MQ`śmtYY$"Õ HW/]K#FCŸO8|oq::xڦY0[&>阪 `s$6|mm4KOQ8R9ϑGVT~p(^ƍ[˜}< YDk_飍VʈKWuXÑK߭86G>Kxrt1cee UJ+;4Eь_{Yg*r|~?|/wϼ/?{mL[N_CnN")U]4Qs*͵^ ρ)qX[[!OILHc0T̂&]\)"|~)iH6XxDGX(2E>nYaYLpÃfit$UFU z ~bzBǖ1)E~_˿?gw~{tbhJNAZL]btʔOGeFYll0̱֢#XoB/n,"ܒN4h1Z0n`kxUVV6Y0#IZ4Mp4~%WhAo*Ӹ^B'lo3ΈU`Gף)S:Z1Fݦ),=ֶ: s>; _r.{7#Oƨ#qd0YVq6{|G?f<}b~4t1R.o"Ge%^lt鲐`!.ҔE+n Xڭv+a1[P*qnL]cLYQF`{HiߢYlbNWX+ #n=nyf3§nd46m~ȃ8Hǣ(b%JyƜ-"bmE1,SZxujk^Py4VI7֍s6|{Aa4:ㇾGohcʺ\E0Ye_JK>X_ReuJvia-8;"?\z)lNҊ9<:!YAct ⤅{TUAY7j9T :i}uTqL'ٜ.h(KD^D7mB/^B)0sJ'OYL'da5uQʆ$ja`RX)]),]F"[XdB%|cewgFGBI9|іv+/ ~o|.]zVCQll^ f>9oX_1k_ m.S#X[ks<|O9:8tÄmzCvݝUmrrz,c~xwK\>Or녛8%K'LF&SGB!Ōǧ9=E{9Q0 |\!/R21 ɔקҊX#ګa]![`wq|T=&I fT1/hW0,?cѴ=cЊck贺h|,.YV̦hhT(eu.iW?1VDJ62tKܾOE1K7]0KˀwpiZ>?Ox_z?5?N4C[Fip͌iPEZqagM)E:h+/ T$qc +6ﲿM9O'`.QVa!I(X6ﵦ. (3ŵ TN ^Yh"rG)A!i 6vM+ɋ)kqȏ%\7! :̧s4cz~FEKn>~xtB+p9Jqųk-9>;_y//scueKopzr 4|ק>g!m\ߧ<#P%*\cv/$>1c|Q!%ZtZmds+5(asuOFEƭ]_dOHQ{.xF( AKkCTH%I˒M,]Nɹb>ӄ~ O<ϣQղU)6׷9?O2 \_bi4/R-iEmZtz-֨fo=T!|{;ۜΘ·sRss<*Љ :=&3]ĕ箐3FgQ ) Q[ܸqׯ(KHAׂv#KSVWxrrdS(,u i(#EM;|`eII.^ޣn T`X_TA2*H2gƂ]n4]בtWn\ cZc e6Np48W/ CTqѽQɄ{WIӊ!ww1WHYI`/hq)Evц7>Y+{=|9Oq5K: gÒl-3+fSmQpQ3m ʔ}sD{ﱶ?yoX[:b錶q6UUtEy2LUt;mʢTM3'?Gx5a`,30MM[1yh|DQ^x:jr76#MSlb4LIҎk #1%/.PWtp KKjURdd2>C:0JGEjNhT"sWBJ/pz!eBlk~ʽ1cC9%I :6kH)iRa9tꓥt fO__Kw/zH3>糺6;1M :dk maue-Ѳ8<!Qz!2@ q\JI9)P`2@ lNFiM 0_̨ʜ4H+u8SR "E+v\l8$cmeW^}|zd:(ks>{5>k{y xOpplM, \_['/>^q9ۗZxC }〼THv'}vp4B%hM $Czkj,'CYŐeXzσ8N {l15 QB+Ѣ}&iGN@]h"W6dFaD*>yPvqr/0_SJ3Mg)ʒtxzttV9_^Ar4:f:/۽`.xMق14%K6co&ʀty1irm8Ia $Q1g;x$ ,0|jxK`2.iR񶵵J+8h, $6[۫>(NƖΖR׊tnH!3识)Qђ?;ᄑ!6 +s_|lnON|]ݹHƕ_2acQdŲ05e|{ gӂ4(%>~)q]cٹH%:8cds{mA1~G;JL&ٜ/g}M^Nڲ6ӊB )sb?̈4'iStڣ[]>n`M:%OsA#=4g:(U,? Һ<|TU5 <ӧ,)± IɣԵ asgy1%_vO$'!MSo^=݄!dYt :g<;Cz,'V{rʚ3zdt7djRa†| aY0ol4e-X P6|a AD$\u*r1GXQOL sW|>ڨit;n:,Ȋ)54_RT)cnڭVا> IDAT7YǶʑ244UiVCP Y29EjEN%Y2@ո DeY0ꊣ[w0&MyCx: y1:R4f0L^E6T?UՔEmߵ??*gы/so#SjIİdC7E&k!˒.^hr~m|ruEFTUEjx-KS \W6UQSMFi\F52 ]Y%)uݘ5ъ,j8=! ptpcJIbgRU#v06HqΛlj;;CleTRbO !;;P9l M $2L!hJ(B"hQW&o}MNN?⧟1ŧzARkQnʷ,MTѦ[t;,6KsJDk"Y\$fԪ`<GȘ LF+pLPuݬ s./Y8`o mhMrq?!EcX زqr$*cjjj p y+ۦw<(Ҥ #pm4*uATEnH^HF]0M\b>_ESNοR$ۂ48FxJ^E&*:.|(5Ѷ&hux31Lb`"+H11,i㙔Uy`<G[\G&‚4)XoR,±=Y.~ܽÛ|٧ p,jo ضIY&u4^RH(6m|TUe CXqm9L} 7 2W޽in$O2[+c >,SMdbo2d YTq~`Y%\]L}*xyrI{NGwHȫ ϵ0`wm̯=x΀$Yp7?.ck;ӝavQFsڦ`~}v=CTSNY|'<9mWȉ5ERUGt!j%̓W = j:0 &"R$evu%lAziVxp4abSL}F;aa[{w ,I5Qjv'nKX\6A006;RfuA7s(!LT-(~Z jR] -@*ZkK$EB^eUsh a+`I ÀMl yQdh~ghFS|/?jNo\ ; > R=KY!^Y/.k֫-`5a8!ELJE!eA؎Eo0ln&2zQ%шOG aqW9f2N0M眽#>ψ-?fg|? ֈB,#/3O( 7RryƝ;X~E?!KϨ#e3ی]./Nngx~[:Zm8(rZGh]ڂ~gmԕ,r i$N`1G*(!*)tE204uv&-fњ'OmD0i=5aF5aR,&ڴZ!}dQ\ѵG8ggwnITyb%+J?| ?1 Yj ym°G% mtݘ~_W??<FcmǵBF=b+pM"%9ltVJjh /au 3ezDx%3,F1&G7vvƶ  ) $C0m4\ }H3Rq׶)s Z6ɲ5QQ+mh|vum9E #q-S[·}nKBo Ո}BCHEL'hc&Z`l+rJ!ʼ. Y6VPxr?$i|чf:BӔTLuxah-8frm<+y>Y^!ez[ıd)w[f^&k)^ *q\Ӳzs-|"MV8JacbE9e^ DZ ƨ-F Utp<%2-w>f\QUEd(6_`29p6;#-TuNU,[.dQ31P6u!Ҭ[dـ:IY"ka6OS0kLĴ-J!A)ej׵) 9Ư:oj>kU 0LlضAY5yZc[:F QR6p5o! 4I`9BKZm(7>坷ߠz؎AR&sV8:?p iZmZ#6U:( ma-| $I j iZ!K62dNYȢl8h@+T l#bBF[*y ^R!)ݰb1Gi^ 6eVmz\ք{[Xvw^O>1{>+^آ79%/8ai.N9?j:g0W+l;wq,w6'DQ6a~'<},կyQB=`>_^/ 2\qv&1q<|z>E"l)86|l5El ,0 WntoRzbr,$qTpqve`J-BntZ-ŋsx="rEZt!zՔ)+go4~iϳN#La㺍vذYt{m<%X:kiZqM9A#N>gTe;:@+n[ZuzqɻΤǏ~TJR6Yخfq.٦+{x ώ?#T2쌖?$Yfy+4kŠץjւNOTYэiA9켆gُs2y‹Yqe8b,slJ,%ek-?0F]~eܱlzU~SankHc<R*l:cYS919=&KEQ lM.y~vBiIa NG%(z?~gOٮv[7ps:Y-8rc.wnxgO> ,F>I*bLuJq 4l Q(R*aۂO0  z;_\!L6(eDVl(TT2E5.BD)fO>3LGa{ɳ hv x]"T<4bv)$~j2hzi:diNjo?ӋG/gup-*Y!U*oSc045(Ju@ȪqKN7[SXhZaaմ{>I-qua[hUchMUg &=rE"0%*ͧMJ{W-,FiBYl. ӂ4DV9y'SXUhcZ>nfvK2v]=2Q" ,s9i>9uTxǟ|Ab,biK;m,!^gxn6uql􌫳\l9WȲNx~~,\n錈抴YV`pp]Y()ɲ<_QTteKjU`6/^7O65QE+xmm"jM:-^ zl8&JȺ@隝&MhHJ+Uq-6yMtf=jzMn)RjB]Tn!3Uot|2KH!aMsJD6Q@X.JA5,JlPf%ݻ`|\DVmWK X8IFeMUh!;iJ5(\a *ATEE%%߳0 m|$iEklCmvVrdpe8D$58D+{Qʚ'ɚ6b,M{>~4^qyvoﱿ7u$,^{>4hz1=dw4`Xmݻ̓Jr}6٢=XVI/Ejr~RHVfŲMХ HRYjAG;{Ll'O?9ӧVI/$MAH7ey huUf]nwp=Vf< yZiybv[G)~oQ2[\&KL"ͦd&#l! =@hc%va21N1dgΦ=嵷1 /+"cg4l[#kqusL'ϧئC5jA%e^"La"oQh3|ktm bwoC[b%M A]ig 6Yko~+^,Ymcm!̆hf)Bր88=.. wPϿ ӗ-w0Hn7`<bE'[Wa9Ȑ21-TUsyvmYYmDxaZUJU*Dz ah|UĴu_^ dyFUgl6SvLF ~tц%\$%as%Zf8`ggR``Tu6jZ+6fu&%І$#Jq!I [f .%E^Q!z?/WE8y^GyC X Da;4Bz`MTEUJ q_5 JT IDAT2}30^M6,M@%͒M2\Ekm4yrrSq=EQx\N/BadE2$m$ E^Zlv ,+KlMlqMd|vgXFz^ug̏>3,v4a:֠kTplh$ޝ/}<}i):mvKP)JͤΨ真,ODɖ]VW/1\|),߽vbD*z=0t 2!-kl("c Z>TeA]CX84ʗmίT~WmӲɲ `е}]l/bt8:g>A51LmU04ӵȫ-`ȃ_bӊ$l,u5~/a˟.-%P G}y-}1ˆGX.l7G#$!$agXp~0 @?|Cz,V+4.g'dYy1NC OΗ~G',[Sx p4G~Ɨ|2+^] p7❷6 2b:!NltK'8E]$[l`zqŠgw˭ۯ3a]ҴԂ:lJ_uMm\1_=G5E]M:CR)HˈLYl#c<鵻ԥr3[ Ȫ+' 2 "m!+^"@.;?iMZ6hKGOX=!D5B(*f^gg8nAQʄ(&/؞FAMٝ]o68az\ϮDR6@*.1M&&g'}l*jʶAXﰉDs UE&Z*aX HX[7\2lVDWەco&?& ~׿՜"(qYUԙf;+h~G=?%[o0sܰyCA-mQ.;yx>2=:- ƤI&.g6Vtz,nkuqpć?iym7n2_\r~5Κ)`'m|1|M/t:nHuAh<&MJ|\L3sm4ҡߟ0[,&l޳V;v'Ov7vxN$%$Y*r$anGU33 Xi2 uD vٟ~Mk-"]??4;Ɉz5sr<)J47K,r}:Q9e8Mf\]?Èkƣ}ҴBЪĒ5NH5EUprz[nK:AQiWjPK܍Y,Lfن)p=mez3g-p]dMپmkiBHvܠU54MQE6%p<|>Ͼ\#~C)W_d4E3bE(o⨃a; -} |G|{oΏH g| amCuk/%TU- Csn&ݾ[ sTC`;YA, , < ϳ-\^_2,q#hxyՔܻ&Eo}^]"+>'\5tcfk?e>kNQ u<:ͺ=^gTeţÈPk-r@aK QdriZF~fvݮ_7l)W}O v]f havGL&KT#wZi0Ys˨ }P;'!iI%η~JP ^OBZ,xCz?f] y.։6U _R+ݐ[ǷD1Ih놭α8;?#/TD#R4"m^R ؖ4BV5-EbI64f"R-&;ܿ$\z;nfEYeG}05O|HVcq{8]_'|71~ ?W6٨F~yr5 l׉#7ЋGpus0/p|.Քy24Y^vP=׶62S%sl\lj]Q+EYԭlpmZuJYKTu=nd2A1j*v FH[k֛ EB钢lP{FaL dZ!{;c.TbrSQoKYUX$ -'I(ݐd (¶$M]4 RH.Ҵ1 ^"M!nW/?S,K0kJwr/d86\a5z!ѐ|Eu/|I])ƃɒj1m-a6ŚjS`6_0^l|{w8ut-Gh6Nċ 'wUBVszNxj:aZ8{;=|5"7tcwb(L_P+txM-E2 (OOw HXn7̷+z;;lULl` |f07x_o{wD^Lɲ;-;b[ptp@NlHnӍZpht2!}g^Yowi t.eQe+V7 q?ıF1r?jJ-ZKH֛%XlUWaYW`YE^oiկ~ Ecj ;i[`FC+&IqC5!pQ$2s[%E!D l6`[-hWI^$ ̀ߗؖq|bS[v^8aDׂ?F.l5{Љ}x1ED%#Y6$YBzz6#%B(]`.ܥd:gƔE Dq0.pl(#ӻy9O ƧDA'gO/szYdٖbdMVmuh[3%]ѵEDINwfۺ&#d0T8dYFx!e mbEzFK7YY m +kUQ" 4EA %*lۥ{>/~(8<>\"ghB#F#^xv}C̘O_`Yٔ{'g̖s׋+>c.onx;|㿡R a(D{fܦl;'bI\M3naQԺŋgf[˳y._QTK2Gb!eZ966WY G <5j!uAY58IJ%pm뵀qa4M݂ђ M])sC~S"ZyFe2"]$p}Ҩil? H"aTv-<{wns}yEV$^IA]s}Q/y,7E,!w&/aK:5e]xYnt]8<D<32a"@JpGXdBdn~@DG$eo7ܧy)7ω1r=Nk uDP~ء;`իW$.kzUɶ@"Ƴ]v=^f6!+ :$_eY$mh6V,+e B Ex=vw!E ED'1Z%Qj:aLXE)Cu8l6!I1xX)I>A:%ѯ/%*(ˤ %IZx]x =7ϯ~}9ǃa40R!}MVf`+ E32i|0`r3XB hugƴ Àm:nr"at] IDAThq%<𰭀pjIlɋvv{5Hm,Ji8}ijtocNƶ5 !:Q, - vwgf'wXlW\NUB^(]"pw;1ezH TUorޛ<}Z0xTjIlL/]n ()ؤ3lj UH5t1f{xx"+٤W=8R9yT1vqm @X[o;oc\\zYjnC5ՄHw@^$IBh]#ݞ[bYR7pZViݐ9Ae"cRPx{}0W_<08;;cf4mxGYhYy;g)H҂F) %^<7q;x~LQ<}o}{mKQ]N=(%4R`t!q\ q=4M`mz mDHV1c TMm1+4 jAcY?KSi E9vBmscoghqz > FU3 r@76+\<צn;]7l yQST5yY"*f t- O>{tUkdg<J/oQ mpC70duJ0I$ih8@ ITe|B6Rqz=,-ɩYNX&OfZ8b`5eaƵ\:A24JjMgGL]Sepu#0.XD^ Q$Śvbնϓ$C:6 QǢ(q4B4B ˶=|ףC&Ӗi!^'e:]e QPx.~ )q,peh0F3_,$9ᘨK./ {=1պєl,tRK(L{T K5ɚao'7*)tl&M3dpbD뢭U Ji,E5(f[Nj 2TÝ"ޝwKa"!،MYM F[`TM]䛜w<uJ7`8a>S5nfKY4|gx^$u8D낿ιpח ~69u{+-w,Wt>>݇ls{=vGE4X.+q Bpr?ȫ /?F FCL#zUSt a,^P¶hx+,hwNd9ݰ3uSdӥ 0p,҉ ]6su&"ISx\޼B5n R zc6[R)n-2',!UQ%$q0prl~6Iv #fk֛Py%a%nă\y0m%I\)Jy(]&IXgZHXM]1ܰ]UOS7XMFt]n׬6,G-A?ۋQF钪`ۚ,+ sxt^7fFA0)R@H7?`6&/V4**3$(bM]lҔ7`mx~%w*^Os=yNZ,g^?e>apẉoҏ;Pnׄ.ɓW<~>gxkٖѨ7+’9뤡T0||acܹy-\j{Ǥٌ"Cf%BA^ }4([y )kNrq~Cז'|ϣ(R$W$''dٳyPTr}16 |޼p~vI7@ta^aPQJ|B6ss}D #,m߉8YtƁ66'YkÏ fӄ]u@]@ț5ڮd2#or,cg$mI7!i3[vWvW<#bNquʃo|!;ܾu* tYYZ7HEti;>O>fgDYlx띷I`!F;4UK.+.2!p(ʒTԵAj[o=c}y7߾s(I\#4$:tCDcD~^HtcOksihL:F:HUAE&EW[#]Ҧ +@*T~hlZ+"!+V4ݸ)USV[ jPK)nV$1LQ+|!ٮܢ,k j!Y)K%)mlɭ79}BX,"hm@ MCoFw@U4#lPɆhMR-hc['squM(j%0xtdlM-tͲN}9/>E۔jXͷ zcn\IԱt6ɔUzg$QRJ3gMpYl Ds|p.4uE|3t_/2⹂WNk<`KIik 4 7t-&x۫0PJ [z%>dd2 nX.AhL]VdINp=LY.'PFRV5uYվM5)%HF !plLݰYOm9\^_sp{-+>}KKۚã`n(۶30X4]7ܓ/Y.C{L?d P\q|r44 ݬ99h4bwo*(jKUk Jx)YnBAؚG]-_MӶ={^{fmGc8>EY, m$y0.YZ\\N$-Gܻ&{GM/< KLS1e#1YJ^4Q BX-LMM[dz 5az|.?TCY-1 ()xT9Pܾ{lNGOQu~M4>Kzk4UE*:#K {]{GarF(Csrr络_s1"h,^'$Ehq6R56{GS Qxm~[+3_Tl66ay9E16Y`^^w:/`^>gc֋9/29u%akdȫHK3M=n'"MN ySVy^s[ntb+ۄhrwjǿj¨ѰBoXnVycf)싗xn{} ?N;Q[)J%NTaߑuTtWJl0blBe ^ZL(gpn[H8ݖd9AVHK6UUO HI&)jKSTuH@ʊBԤZde-N ƠkW#`.[nSx-NHo4r,`* :^HT\LyuF+]wo- m|_ٿ\y_߹;bN) ҭKH\)q\B(S!V-zْߣ`\cYTѴ i#,aI,ۢV FSrZL%>ag%EY/KJ?ҍA9:اQD/D=l?sTmcOGlW~|L5|C( %VR-"ipppDZyGl%Q'`0Ȁ.Nqr|ǶqаؼS9 >;)t! qUȷ!|ۧo}N/5gq=,ÀG^xN:ŚzEvwܘ4ה q'^>p>m ےxK`w.Ք-`11L\\F0jp,wU EzQsb,Pd9F篁przNecXeC'q|o3D] ꭦI" 'o.syіn2eBU)˿IV wol5OP4p5cU7:,WӶP`4yp!n/x怦VA;w՚L<}z!W,W7-scs.kBwpUU,V+jUbkЦu-겠{;45 {[\S)EcdI#srj(c$#^X؄^H'쑮Ҷ f*R4T4V H)<vm.JlBQ#>hYQ,k KJ cc6벽IS[nJ"윍;*!jmf7LWEF>򊳫gϾZz'_?,W3XVIr*m4ehZ=LS1Y97IcƑK њ PDҠ8mA #kTSKCrtM7{\],y[jzEUPٔdd-7+F#,&IsBG7zc6&x~@'+|O2:G9Mj4*qw?D@ IDATy &>q:YGhE]eՊ7Oq5 ]) unw.ڪ9=b9|^\ 9<>`_Aàt=^g\]?[G{m :A}"}TT@Ur'Kkpp@MX3{]VJx>.vP2]BHA I-6' BvBjU'VLm Zitэ@Y.;?dk{>{LC9.kv{.' opjUhMU$Eﶫ 5QJ?3 q\ӳS/Nd2fg{{?Ȁ8hU`]X^T%-q;O#)T*#V4McYZӍt0hOcECm]XRNў<m..U]e9M>9auPv=,<\"KfhI*[^S5IDMWݥ`0 ϟ]~G?ޝ}<'KE66s!WUi,mH ti }473)|L?ijt_.,cܹϫWht8x^5e(2es#? +xɐ +v7F]($cy^opDU$Id<&2c7pyyn*,BQE Z6N.Rr>_0VA,|a?\_=ꪢ#qy}L 튣d~}lGX6H;LQYcLa3yɠ S0ztZ0ѕAIYT5B; "*u6i;>Jk&[<v.Eƒ57k:QDC.|%c;;{ Gc#¥X󶩬4 uew߲N7XwrMU;]66x>zUL'8~R%A|vӿq%Ah3u8|o0 ;;̖7`U8-aۛ!o^ԆXnMSkS)8V Pi"%eA Q8a4aQ")2+hdzsAxt1iڜrqa VR063Nރ<ハ=Ϳw9'mȡ(S Ƃ2m]JUۘc*#DӠt@ea=G(CXjݫ\VT!i-"#W$銋9J1QeF[hr:HIQ4H' #VuFԥ¶}&M|#&c2qWDވ,Qop#.n_}ŭ;\\?V:,k~ѐu ,X6"oȳǵHˤ9 7g<})(TJV$WsnΈ_&sv:.)ݎސ7'/P\6=͇U}eAK LQ;ޭwߢr,Tho9xJǸN@46i^WXK)[oS y#D1@]N@H-Ð,k7}.gg?b2)8ۜ] QڎdzOzdU-GYԭK%˶zߑdsNOppxC8=Fq~Qpca7$['qzYj7/nѨJkwyx.e ϗ='RiMR*n{M a4Rm66)Kܺu 02ak듧`j6.?dLk[gq^V$ylvEgEl@ˢwYTq;/VDfɂݽE~l v&^I8q~˯o>ꒃCAU ҄t Ý[lb۟XC<N{Dc<'d1}uiFcJjQJao)$MUq*菰`mY8becؖdIz]fKeQ)! ,B$ X͗\]\!Dn"+ 4.FLg ,OkuP) R4oImV57UͳA?ݼH)1o%#L1 @ʺ4¼5ea̝h#)i?ȑ plB75RMmI0imڲlT<!iA]k޼Z҉\O.]ebkvZ'P:F7 sm Fb[%fqyC%ݧ51݁Kc_}GLdhum8:<& #0Rv"l7^Ouem.A+0L#z7L '%~ßrvvj=\weAVOQ54& l';_]RΑH6;Hag.G,W1'L6#_dAմ)|\٣nZB>U^;{0%Fk0KLmy}?wm [cSٗLgSJ:1=F؆xlj1mtCea;eXv+t#4I0RaDCVdAlJZ%hq=rxxG~+IFe6/^=&p#opG8;C(7[+|y<Y"K'{=% ˬf{o;ͭMVIFׯo}C7tQMCx 痯8">n=.O8<%i3]\/GU+B a@S uNn4" -FUYs<՛WUA!/kVq.;{{XW~I5|\w!VT՚ lؘf9kbȗNbU2gK`MXϋ0FQTKj֬Vxcz@h`[:J3]Αc\(N7²<\}7d<4Ua[5ai%P49\uPe4KmMuUknP'Lgk*%޳^ z_7VwtKY&x19#0i<6T"Ko4Vf ٜlstpЍp݀cI2GJ BHl7 W%U] zu`qD~A't:akr"z%%dEdT 3 Ƕ+kNý}t^- ۿsJwBz7Imf4Mrj:HjU iI0-4iYo>AR7YSWoS-ӓWlyI,]SdY{?9eVcooek0tvPʦۙ` wYf4M>T\9qD:2[^PV8>ui>윲lv涩u|"PdiVcB[;^a:R)-|= ^o@p}s5ulp||E6%U:EJ]ed!Su` -NUCU֫^ `JpZX~UTɌ_?J:ޣR%׳?ȓ̗S./(dupr9eĒo2x&/3Z8`Fb)tEejl 7a־b;T-rue6')MN]5$ΠE8$"d>v_xGS`¸;a>]-H MVnCQNה&CL(;'O~r~.l眝h'|G>Co^ }1!"'|%7Ʋ""I2z>ǷHas|2ӆ?Ç|9Ōru8z_Q51ju}e(+MZnYB@粱g\d2`9.u CV5Z)ݹ#5MT_ŗ\]Ōq}qƄိIY %%䧟\,g)&Uj$[9;RdcDD]z[D; tQin Xm.JMESkrmy48(m1FI2EZY'%1Ge|Yc/H׋Ҕ$7Y&+lfTlۡ(*i1Zee YNz!ni˄$y)cfst]p!k2qA9I(w|Ug#ѸGfiaj"F*8;?E5rg{ 4w{= 0$c봐W/g#4ׄ$)'||$fs& >$i-Ҳz:-X9!-iJu t#oDBY-UeYmEAVܽLJ؜k:^#D5,$R`ق0P7Q:6e]ReYB(yD A [cYHaF/sj\F)|E * ~HdhhD%Y!u- t_-7'9<_w՛qQ$'vzm3 2p L뼵ZK bskmz!Gn7e9iq|ۦmq4Xvh]^>[1  iK8-JmrՔ۷!f7pG~sV9pq~Nw=V is]THv˥~[v(NO^_9± X\Z,V7(3TS+"}:;ǶCRE5TEE'T5nE^ ؖ`47S45u]G>YPꜢJA4KlAqCNjyMeeX4a -A]X CFUDAbFE6Qס0)F c:&Iԉ`Utyfv g<~y}op3?rӋ(˘( }ng@UTuK[t!-iUPikYԕj5tmԥn qȚȊ`x>Qgy"+פٚȑxDр`r=#/R@SU `@;`kcے+kAʥ&O5A+$m?G"l4, {xE>%*Lr|lvɛʄFk·Nru2ܽM %eRV딣[7"[;̧w/Ni`Ψ~\^3<+^^A3/rtu{? /2\3&M \S` û(3,+vwÈCN_]r%[s|I#<~J'.E^8;;# ]W::c?J&ܜl!76A+/d49o XdB 8XhcB mQtzę։b1_=²4qa4#lm-rwTbz:;G9fi b6T`.*&kacc˳%#{#J4 qlcf\s]xs:X`p2SzIuBDIq=l+ I2T]q=EUN&HcPiI79m] <ʼ]r朇0^2_u:L&^sAQ Ѳ\M[- @ 帠]|##v0J,DT54NƢ 'Tu"۸YeDĠR6gDo~q?n??tmW(Af48um4@?+^?Z Fo^Q:n>Ndcf'y.q|߱Rcuwql |ZdYzQ^)&1??ٟ|8Xؾ;t7'OD)ڇX=ʲTk2<ˡ-$'Nvl$R;Mi4mQUVP[Z4ZvS4޾<'O|/s|9%qFlmb6o^2]Q)*ű;+]6o5R:8G4ބ,͜/U'\\%ncI޼~FP/6۬3n. }Ќ6q[8|yCZ`Vtz6۶w[LQjj}NiT.;~38z5 ݴ6:*:d2$u5DuEp0dJ:hUI_>c瀛 Xdd!C^|!Ŋlɻ>u<>8ķ%Knnc EIӂhnϷ|Ax4!MYY\ !Lcq@Z4Un}=`|O"eeKpxx2f{g` -/ʑ4X!?p-aJ.O۾LHc"pt~gRA4R a B8xND/o؜lx5qgS%[R)M;ߛШZmPj !#`gofJ#|A#Kj+~eO7v+,µZZ4Gtu҅篞0 /θxŠhC5&9`wu 3" F}^~@^ܐ$Sv N0uD4aq B2+H0ڌ=v7T@NHWpu}Fd:^qqu6`3' UCгI%f`ͭ!ٔ>zz 8>>O?#6<}ы|G䛧kpcOw$FZ>^o)L&GHG -.itG|w^[.YRNO.Zqth8-=Z4*A+5BZUCUfZu;$]{Z5ǝ[xEJ<جVKlc]77,3 %,p($˔ yLc;do+Gl8}ac;t>v\?ޤǶ,={Zߧ?߸}7fSYbH,J2IXC6؀=؞ yd ll)0eVVUfewO~?Dĉ{2ԔuF丁?S_C-ƴE΢N)mKQ'18z}l9!ajH7)Rmi#@:n 7]&({`(1"kk`PZϟu"9ܿIH/ ;>/٦3jSM 6iB-GGu:$Ng@Y@c%YeO?Y瓗Wg{~/pu14֧:E7nkuJAc)<8w%y!lcxKU(Y${O>ecK*QhpxbtZ2q=dqn:i5B({w|S*FH|ˆ5tI<;HaE6Q.R)ξdP)͆J\^Rꊣ}b:{;ǔD됇oH'O>m^[wyS>+4eAu)KZeu=&9ouZM.84YnJyU){w>asjr@8QU%Wt7ÝJ)$w\E!7oq}%h*<<-il5a-pHPU IU7(*:Ѕ!!͓0w>b'iBVՖ4k-cn*eˆX_G0ɫ1Qkrz]N04URxD乸JRʼ6!aeP뒫3z1%U^1>)Y/fP9ZzaE%uSn٦a]0WezQc<zJl1iE+$V@IU :#Ku`kW8Nz*PuAm"t:x8dS{]Q &gmx/۽GCZkJkOo˟gvLh =|HUq||DU5uT +hpA&ĕ =wpU;OF1|f'ɫ5d&)XHrv>ZqT^Vw&+ \a8pq6eЏ-Çܽu].,#Jxeػ'w=^==a0<7qzwAu6%m[hl]SU%Np(dS5R8Na%xf;Ǒ Ew u I-w:|19{as:$BV넪H!cD)EYms1Ǩ'n,mZ2$I7UM઀tܻX\IA#F3|u\ԹSCBo~|@]t٢D"x8A].OO(aRT( mʵIY\-/OL[1)T I&9Ue66&FN.ΏG~eW4v~o!(=8 7'WN6]1MP"Iqݴsl+c"M3 "TB)֛'<}Mml ֫Sƣ)uni*˃;a6;!$%!8jWng1] }!ʍY,R]֚ڔ Ʉ<_>hnOks\2 Qץu}h, uՖ.u)K.d)Mue^!'o=䜲h菇>NϾ䫗1Y^Feͻzt0Tddt*(\KZ,lWX*(ף6a/|bܹPt~t J 2*r51TEM^@؂3slCQԌdU6`ƚccMŢuF,qMl~awUʠw(ʌjF8>a^[N7e"* ^H7q6X?v矰HU(u>7o*E MCûoCWc]kzE]-.8M t~|( L/꺍3ZKnܸ֥k2RNΘȊm[b ؤ[ *QQXN4G<|GI$yFQ\H)֫׆ej)e*+n O_}A^&kL#e;G<6gxΈ7qxx̫/I+ݸέkD%H鸒рX-I-;خ6Lnij_|LC5nr~qn,y)ػvf0./X&DΨG/ӟ,MQ!6a+KQЧ.7oUՒ.Y$k|luEV&8A@m8$Yvw]0 $slߘxRɔǏsyu1xnpɇ"eW'de1"O€NbȠsh6;GtE`Jl̓7HTBUhւ \V+bMc].uQҍ{," Xk4G~HS׌]E.5X-]?d6a,8G=*늢(܈h.g%b^DAyM00XoR5HIwPc?/WYbTBR%e ]KHچjPu=ǧ٧e)-9W/5xB9hې5BW <)I %M9t]45Pdlkw~LU&6kVRghSSM^hۺtIӴq+(tnrQRRT-^锦ia Oy&a2ӠH !^'z?|J:oBc*6-.VlV<#\L#Pֶ.mjBQ Ewtn9SBj)J@Hp˫?IƠ9U:G` bzZyFY:m"/gX]1ZǸN?pPY } .ѵE)Gӧm{i1# NL9ܽF58)Mn991QT'e%<SxRq:z I$,OQJZ%ܹOF'r۷.a3ZRf ;ܺubJohlI^fA!-f='ٲI!Q*#<|'ZKJT&<3<'S%뗘?a2;GBU8~UBSa7F MOѴ-v*\l`QVytI-exH槟'd;d`)c- 9ܿh ~(,5RL^9Hޜ7D9ņi(k MbOxCckl<\/ B<5Vcw|Ġ7fpv :? ZΨ,.QN8`l8KckC62~wI1F cf!. ;v1Ֆ^Mo6;װ R25O%u՜0vtN5?_6O.yD7)w45giF^Ͷ4!?\]R$)tP0e%JYF>%ʼpm20Tepg1KvC Q%lE1g'gܽ{0xG'.xC.!GL' ߥ-"BZ5x;-kDBdr uY8>Hk%t[o}8lQN ͗|xHGtk[0/ۡĝ%m`X,qz{jAtV{wM}ܼ03"'tDrE2HG7̖)qhMEΐ8ܣdv(}~lCQk8˧0zġ:RNOr|˯~89{M8`JCgdypвE A˺)itOp7BҖnVUYP,Tۜkg/0kUkckH+)L6-c,iŋ\ h.(444SmZKn ¥і$-N IDATMAT@+[@'drB/Ҍv} u!h]28A C*L21ڒ3y|Ѱ4ׯnϙ/hWx%GC|9y3~{wcZhUIR4ڬn}#)E &ܻϋ8҉2!aa%r+ih7UYz>qC~1/pkwb]Gb2/-4Qg%YqQ $hQeq>)j*ΩMri0=TUTöJ'l Q ksTaqZAnKA -iZ҂H!o\y^-yD8H/b4' w]֋%Ֆ r7.VF4(ѴLkZ %Eᇂ .84E)"tctbKH串W1\|J A 5u{rGC~eMtkE qC9 oS {[ LVg<;[2{TUʋ/(3n񃀼ȨmAzPQ0*9ؿM ]R7"|wNfxO)J?֭{|srM/$ۗ+ryyOxO8>>DHˇ ҄\\#1"clAUhz$vJVn:}Ҵu;(7B..Op\iU{L9M"h#S|U ;ܸ}^ίv<{4/KYW6 ^LӔxd0 91Ƌ=.W,YV)>>eyNn$U;tHhL<;}K eUQZkv,s*+WRjK{e0vB`G Y$+u| G^dMCT8*D c`M6,SHTHy9e <$!RvuƬ+6 Acf[F'Ȫ2 4؊ڐRQЍZLSM3=ǂd<اG.!\`k-{H#L;:fg%ee)N`G x~-x15_{aOg>mu;#{\~EV^ZяgԂ::-q}?₻(+b9Ͼ3:}IUE79{G 9ܽI:$sqdbKUmɊ%IQ^;ꦡ!4fl7aBaiϒޓȳb# zV4t&۰ ۚB۴b1MqL' F,Y^Nõo!/t9d3|nҢ(:ݘ~bX!?sA¸ck0DJ7CtYŗk,S썂PE9|{w8~ק' >>LS..]?,3,vquUއqrrJ^]3_LYr߿$+Nv;|ԕ;4Eōl]a]1Oq;p谜$X-n2NAW%Ρ)QXLAz3 E*XXN7ܺp8FS=w -4 #uX.(HCG<{ hHCIrPTPW" XK4_ ) fͣ{oc,O bwɋ9¤[0Ox뭇HixfuA)i,Aof5(uƣ]f)U`82еeIRYp8&KV5^LwBul[C(xp0嫗Ya\M 0&špXQ7Kj~-6: Q$H<[S`DAc3JvIdyqDe,yY'h-Ly(x^4Xm'gW_?ѣw¡5iai ;~Hw3@W'/_6B[Jx/o{7?oWԺ" )0MV8%i&ҶN/YZ% aأ* o={ zmFYoɚf;eR.B(,Մa[lm#FkКg]T{^k {g3n߾KL>p>/O~{d^PS LcN Me]1eQ/shRsXn/y,Q65cDxvAl9>RclM=fWh-8{˲s|sVN5tUuul6IZ ђ%^ MZ& ^Rm` ۀ`æLT7R9UfF9xqz@͋{9~>Ws=f 4(78n#\ܽP (H%ө =锢ʹv[^c41,d+ji`n4Jo8hLruܸ~(ڒ[,vK "TpMl E))^gYc8h4iaqUmZ)'hwH”C()_vuMl,CIij:7Z1Xx\8ۼ9SU ǵtZ'CfƐRה th4aD@QK2-]rbS5p,?00ㄪ( $T&eog??%+8(I-0J1L(neVR.ZIӄJ GC[,WgdْUxIż!Wx޻Kd'K DQ]&Md*lg2` J(vIm5fS3O aRc{iRV%lJ8N)K?ɛ ߸eܾtC] fsxEe;`9bQl6Yc:a}"/kHRl$Ԅ ՒZ.Q"{Qqv6IYf<9?+<{k7mW#oq\a<&KF;tȺ6 PypJ e,cdQBP mZyQb8_/(VW*miZPHZ G]._D >Y ?FMr<FZir=QK|dZQMw8`NplBUcgQaĠ4 $ƌc0ymPKׯ.X.CV@41 K..Ȋ7/il[L9?{Y,/٬gfrq]V 6i1 B]fL.N2vvm&(Ipq(Ғ Mg_#S1Y-ؖ붰&ϬFki`81mT%H-:CU"F#Kmiwv Zc|NkG"tCt" Ô(Ud5JhBhJQohRM L)U7 lL<-Zj8vS(rQDIN^oH '<}j΀ t0MVeaB霧/K ~{c9~&Ӌ)iZ kt8FC?g/f4{?_XXƲYfbZhEZ)L?UmYBEP6hFdD)0Lv6Q+lS拐,vY8%-UIlTFPʫ -B4A`CTUoGsz"MF_="M^^O_r΄GODmؘo_pc)Ƕ->Qd]i~BWW>{|Y$bI-Ϟ:\K_m+Daj3x0^Ц3 33f/'O>'?fٙt\f)ӴdmFhZ dYr||./_=q,\&~0جDyPjﵐJ&9R)JbZQQR 8UaEE*ɓChhxRa4הiQ]i ^nС ò6a\諟}7*4@!NӬ< K2TZ?[Ae4)Od- ޞ&ex~@^ԔU!2 #j&+LcABd-,Qx\cI:*F=dkj# p,Ժ #Ah#h7dy|ps;CN,"k_ȉ-9͂x}H%hYwy7mvvXϧī91Q%e20)YDhen IDAT"g'O)-j6-^,W:6i T鶸zt ˰y'eJwY#N^O.RUKx˃{جW8>3\%p]RJq2=gb6I'iVu<^n(f4tl泐~bNY5pZT%MqZKR ,WۄZ D+&W lKyrA~3 q0;-n^h9-N/x`>˿-pq% )U Z^xm Ml/SuK.=s` 5L\4M q,$lld!9g8bۂ$mho3wieZ^oLiΠˠ4 $qD|ÏUΘ0 jR.m7PͲShj&Ihc[=5,a0,u!S Fo #$6jjU#ɋ/.A}\Ko8ڝstH[ t! +#|' 22rIeʦHnhql~9E)txOy4g\ 7HScɳ4la8bܾɗϟ]/lW|k>oÈ|UvFGtZC,sR]zL/BnܺZP)Ia; A&ahv???Lw?:ydyI]TecHeP2aB%+Bc i6ϰ5a Jf2%/F2]цjc:~rdcMDH"*k@`: %J4da6 (-ieQb>ֈcoDͱ'syye_4Ռ'dx^M$c#:,?g=c}[?`ԪBya.ʑ4v Jc{VPi{^W哯o_'kUu6Pp?bЙ0n8As9_ ՜3v{v\_Py"zPT7`q\;|_/]除e|scV²@KjQfxOQkdA1wo^îKS'7oqZ~ "4i!N(,,a笓L,71g],V.&-LSS9ysp8bb0ڡ?%ܬ)Wa7osz2e{oosz:,R9o~qQ֚"h]Nآ:ƭ#./xhn޸N]S%vN+Y,jD, WG9{;0ϟ=*Ōdejރ<~}dzmmZքrdm es 4j)Vv5ea[mؖ<@Y؞E4E<Eh @a*"# ś.J#T6BW J!+M%Fñ,,L!R*<`?%Ir)u03|W*yysV3R 0<Oxk$sL&BH&Wv ,uyǜOg3|IЇ `Z>BضKtHkOXom25i2#Nh%2a2Z^ } >e.uwƬS֛ ylot-<²LM^HڭEQDlA3JHY!uIe86ҸjNT1ׯëW36o}7X/ONyk(q0ݦgz_̦Eɔ56y*O-N1 1y&);GWPIYE8SGV_otXPts}_ϾKgAiaA Zm* オ%6;{EoN -^2쵐J)(V6n6<}\Ki&J=:|%)TXz1t̷AmptM:= ./lW+@&27GU>1-=߼hBQ%:Cf%M\1L-HRT28yt-U^>Z[9umr35&YL۝4k Bn޼IlZ*a8t DUsE$Kz>} =~#xFM)J}5F 蚽%A`6w\W4/Wb;6#,Fkخ-|C|&ڬlW+6eѨLDݠM\zˋ%,+~q9_-kӤDJ*Dz-,E,-}0nh@ F]._&RN@)!%} #r" 5ٙ|9ggw9U3zmsl.oc A]5ԅIp-$N%Ycr1PUx0q<brm>7X1 ڃ6Y鷸7sޜ0"s2vL!q1\79d;,#+Et{}5E8>^py55 ވ?N_.~)S\]7WAiDQL%fC!$&-ql! <ǣjsv~ee1Xr| ϩYd8b(de D ,۰hyIRʌΚ#_cڱAFܼWvRd5&.2´1A55JJ m3!fRfm&R5Ziw*I=z6h4Jx~A ^ٮw;\\LvDѾi8[ /o="ô g[[-K>!WyvF(P m0LCX 4lǢ UkC-JZ=؀(rŋg)A qIӈeR%EplӲLJf AkABs+Wqfr$M" 6gڠ5HU)n[a@ E^PU ôцAM*~oI3b)ZXF0ئt)`;9VrY1P+\=mېJ-0tm$f4d>?AHmvǻ6%rqb~T/t[Tⷾ|Y-{ cߧ(P &`v8xoqfz`1D*RjZ y7Yŭ=8S vpLKT//^2iF9v!3*_BضEČ#旗S0 @?+gifŌ4  K*&2 N@-%;A q-&/ȲUդir[wނ{'?..x_C^xvwdyqTyƕ oޠfwt,Mu0ȊJT¶ =<7 r%Ux^ )y⸂"٢;wEop%UD)rsA.CjSZi{5m&ROFA6W~ya(pYs)UFߥҒ(/xfrv1XQ''$`.8~~1(?#nj qj[ڊ'~'1_Nt:C8&/ PZjM kS 2&e]70V9EUE|2\Z]?-Z;8dlA6U *7RUKlœ醿~ <9i7<߰3RE^max,xm*a}C^I 29:>El% {!~3v\.U}_pxY\\\`PȘUST VS%(i4i>I^A?HX6E3KX,KLi \*2_\Ϲwa`c6 *\ RZQxouHe*P5Rj CIh=\/`rvyXfdz۴Cz 5:~[$|303=ա2<'Nr)ӿ3{L,SdY!sE\%-єEJQjLaMuLL`q`sYmFo0v&e&(ut,%I12k yQJMYh\q< E)ȋ%eiY.%9ePfs266>} ˦KTV`**nT -Z(Ֆn{ |&|CBzwcxI<P)^5|5;q~zQuMpeV%%.~"K7s},lF.-o@or^pre!u͛KxxO|z>UU2]Lo0XL/GdaLHW//q}6(eG Yo ne]G\yjJi,O>~ݸw(&{9?{;lV˷Y Z]8?,sL VRlk8Ac09اѽ] IDAT$)o1p5e`;N3Jd!HѪuѲ*lݝ @iv@աTq{bl8ɈF!8; jUSȂRdyit:m*;h:_Gkv[&MHf`ؖI'p (@WgS9eYP!iWhC9c;YkM]  nhZBS5ea6)I>W^Lh I%K3<Ĵj\e3}h\Z.j''n ðr54٬Qtp\( DQg[Ml)]% \; 5^OIwxA'c,Suk C  íPd2Mg+ka 8OڦY!y)ˊ*Hj D`&RXeYvqʼQiF8vh3pz1H&3:Vls:˿ +cϿ|ޟ{b+bl(V(3´5gS%4EQQvM<-) ƴM*Bh@,1Y,gNSnE+еumנi\خGjڝ.9&aW-><7\59 ٜlҢ$27)rã?h嫟ԘX7W6p^KrR&ԢQ.klG r/c_|$VFmٌm8EV qyW?{<9@)$mkG׸zt` 7nޠslA6x-AQi"/\bXoN&#j!-B jjk-E--ahtLE|dx2TE[~ xN/>Az̦fɋfQ<UTu zCQ8 4iu!Ic3 ٬P ʭCqAoB?lK.u-Y.Wl #,ҚdxǶ4yZPU9eS5yVa]A(KO]yxV,ۂ^G};7ѲJ.ܺ._NM44) .M&XA5K5Q2G)ZPXF\_ xnУ*5g^ Lϧ|'3...I71WnrsD]*K*$qBkQk(*F qO~ӓo~do_ιv}o^SUTtcq=!aJeyI(jEU59S1UBF`gaĽymy5uǾ}l6MLɢ3$@'1 V Jcd4DDMTv߾SUg>gZc?T9yH:l;n_9Mﷻ,ujR4I]4M\]'DÀ/N?yI\_dM-"c{k=ԴlQ3EQ emg8챱 M1pMA͍u\]`&P)vTfҐeEU6 C9gs\K }^ti;6Y* 0C&c!RH8fX"8G8)F5TuA8e؄~mLSPXR%aC?rqqm( ڎH-JӐrNoإ.[M&g%beJQb4m8>r-s]Lӣi4 Vsz.GqA&Zacpm[xp]OHnK[-V5>SL +T ׳ o?//C/·~tc,GS:mynJRU%ZIc§yKﱳq@^4$I:i-8)M(jmqcSV%yc;!g/xN)Lcsm-ea daHϹKr$gogAwtq+?S./n>_ʏg/e<q3dogᐦtkM',WckŘ4OKgs!< K0[=fLYcoG.Y !Fۥ]rq?4:e6YVhP5WlltD?ɫ<7iH1/_=!JCo8{|DZ.0͊(8HuhzA>sIkH1 m-FHZ{ FՌ|lpuxk,+LE}4-ֆ4`S{c>}ΏOb^!cDvv0t ϲiI[D% Vu}],e"ll`Jk>@P$iDkL[rWEN]˰cskbt!¤4NޠGFV3Ē#9Ze 4*a1 >:ggF x~zB^4JS?3l;6y{!wt6m ɊR4Kۢ?!kTR;Ǐ)<ι?ҼxUs)YbϳIi' mGQ`ժDQMU7l2qm"+M"J-p-$%t-x>pv`$y|9luSP3ͨk ǻ\]0_hz>.WKаj14>Ymza.%U1'ϸڗ2>t8ʨK#6t]<A:ZE{*4ysM"h,ЪhwUIӔMa HiJ4$))ݔ4l>RfafLG*i_ZפUS۳fy^#QY Y҄$) MxKSi<'\0\bYÍ>uS! [xvƓ.8?DJMj2MYLq0HKBf\Myrԭqj* + 8K0Mv`1MHbs}n( DZ6y/X,WDqN {:WJ!ynJlBU&NNt4gm8;L'c mdYlom'+aPU{Hoita, t0Ǵ=*"WH AErA E>kdQM\[o]PA-I=ևڠd8ܢV*(UBr]&1E= 6MMz^{ɽ{_0=$ t;[<;$񘼬2EHfƾ~YC+O{_~_o߰] ;},I0-l8;$ZFdYA|ݷ~,Y%K.oQ7U 6ֆtmni6骸5801#Oc,O8;?a<66$!+JХ_xJyޠkku.'Wܬb i31]|LF(VOvlNΈ`Q:ò@kx.WVcH0vDQ)ƳKr0L\mCQD%77(4lS[mT)6z>"^enx1-gc{j eQcKlv}&'ШUlI0 IGضuk0eA&PuނE Fp(kppH$yISkQ-q0baYGإ(wa19st6"NDQFcϵ뜪,0 h$EYaPV%!n' :A@72P=C v-MvҚ?PA44 ,A&GGG툺nAإ)%~H'tC$o $Ð6Gl8Np]<0M*( VX6t4 i"n;B et3ar,ɧ'M&B@ kl[-S9*do.6QcuZJ1lI'Pv!QU6uc2Hä(˔؆KiO%U)= "j EUsvu`s^o͇oI볽GcXF*֨ɓLiENؾB{!%ɼieVu8uz6d0ސVItz Cf%ub O;ۛ\bW7'*!$~hKs045ZVuRrd(hݾcc&Pa<9c|LƘ6Ikt{4:I'!/IM]kl#@`i:>糷CR E# byiOx%Rc6 vin(򘺌\OxV Dh`X1 c;4Yܜ,#RXEFr^QJ;\7ϟryq`z366h*:>74ӧӨj44nH%9"'J4/9hZxRcU$y!mD0QDJ,iH5Ղn(kA)\1%*/hJeY ݠ)kh!;2]+bLVNd}׶/yq ^"MFyţ7o#ErzAb}mo[cr}E\"$rl& iTyƫOɣ n:!ɘBvi }ԍrY6{ʏ|'B䌛ɘʈz}./9ĩfs8;Q;HTSg5k}*, CET^` ^{pL=^<%H! {īk6< <'?r4.}>~{l' QSHXE3la)f+._RT1q:a[@4hdE*naJ@eڶ@7 Yt1y]7lmդ阢X+|'qv֔&5  ~6CЎhFhN^$qh4akc2Z[dYN'MjuU" MCwv0L^eLFVae4"/3%YB Jfks(P%ea;lEYT1_.X%+®OUWloÇ<}ӳy.i%u6FkIjNHE"89=z렕 xnc>(l Jx\_24,ʲY"8;fs}oi,KYѐ9W1/x3^ tL#])J-nXV.z>b*O(HC#DqK6X o<~U!tCEϟ &R $- -rZ iD8ʡ&i IDATl9e>x4U别Z{#yc|ߣKh*,à F$z>eY1YDQEbK:AH,K:z!1&RQ9~S{>7W\_R ˶pMkij` *'KS>{ ǵdnMiYA() )%CR(iꚺ[iaJ8=9j݀>%vNH,f6Q k!,t-iֱE+I{41h~Jh3Ű+ V)%aekk@& 5e2L)Za&>FYjY:FHNh:`Yc;=Z=C,KBQ*I^Aa?*^3w7n;ghFmRi;PC4uVJot]!uj*a0,|FkkXW,U%(Km:Xex)%Ei6Elx> ˰ЪMM{7E圞Cpq(unL aiP5yQ Ôp.4aň_E#,яv -'&V{!~d|OM0LUttqAϱ:52< Vb  \4Y~? ۱@H!AaHf/ɫt􈳂bBYr̦s]r)~o J[$iƻcV솺Ȋ ixc膵Nrf#ǒ ]k0%i>.ڬTHd6 /DqA -66]h9 ]FQDe(E4V5-n's- eUӔ Ƕ5M {k:}lD<#wh ^{̃btiI2< EMMLUXF#75ɖu Y%eCoƋg]6-~/|吤 .0 G|~VcD9!Lz]>9_e<"K+/ωyq19O>?)OΟ|#^<像?{{͌ûIkH۱uvHjA-j0-(v;YQ&st3Y!L''+)EyަDN#bǧ,ӣJi>1 $r5^mCC AeK,=LvIrAWT&9ؾOvnߧ ZcYVcF1el:G lhD:kES,ShHq!WWmGugk< CzAUHa6%R,)^!eUK?5Α&u:yVa(JyڴIkMRh6f?~x+ FZ 4¢M|rTݠƾچ)7h j1t|5b:t,PYJ,=[tq=h9v1 pLs"5B5X&oA `lXD1.'ϓS:f 2u,5bFHv-gv_`0\>;)FeiQM4WOԥuH UJP͊/qtٻ^_ ͒%hl5 ,0ZpLɳ5`눎-3^߶OBp,lstc '=~gRV+Tc|22,4yQPՊ4/I󚢄 0@@rI][7X;̖3唺L-P {fKu LC{10OupijeHҼP`fcsby{Hr$&d~o AY){G<~5vtsyN뒬flnn\MA0ӧU :ã_ڐ㝯ϝ[t!Q̿S>Cv7yyqg/r3)i4@4 %#ꦠ7pyAV g7aTkɌ$)X,RXSV(jeY(WU-06tXa#Ĕ6z>w}{0m p@ q' 㳹EQS uL!k3̸ybX8&r*WW! NH Z%;Gy|<UUtClӦ2$-x8|L[0\TCS+ʢ&Cg8x)=5( qlՒ@}^7^ eVsɲ;==a` ngYn;RJ|ߡ*|#OFaFeסR i(jEP%e].^]XB`>EՆ:&ŴJQT+ eШvVh ԒtE`*YR@bٷ!?ӯW=_bs:g/?9 6w{ْ*1DVJ#l=J}#i viIq+p7кiue؎CU5(> eXLn",,aS%cUIA8]t%PsY.TUкu`"p 0LqlrqElmXF7*Q6ًҢ<w&w~nwegA-Y.|DɌ=ԭitsm2_ް\^aCQY,nm~X6q$wAtÀS:}~#o q2ow,CX{!yV`}VK/"Is=zUl:(˒ԺDѐW-}ZZ\!e@UxހU݂'+h)ゖ @Ѽ֛|sNOhnhclNp}? YDhH9R8n@ף%} a(:)P&)hʲ [Xj,t,Zn9U2^\DHKeCWVQraKeG'P%qtN\L1mbtoYSAՊ8-f`hʺsvg$QX(QB hDV0A>!/JшB`TH%[8KuTkNN?zǩ)JeM{8θV\]۬opɒx$OSzyS9E$dbI\L-/UFOu]xg'gp }MnoRPU%ОaZh_`Yɔ*B0(slI@78g%4k.&TؖIZ{;pqqja0_Oy)ePwp= XtM(9PnAlr5VQ{3~/{ϓ`KxxxpIU ;޿Xa?g}3]0csWpu /ps t8X?'?u%j%Oh-y7]ӓS45Ҩ} TSPqL@׌ciFYB^X5"̐`-|j{8ΐc^>>8D&D  r?r  >>ǎO_ujjgߌ əSk1x}C-hOQdd?m0JLiK.D6%.AkD7ArY5JI ^ P)Z8;*5Bk6񖲪Y.WdiR:. ߿{ KS>z|:Dzj6$$*0h; ;nX.XQzMksjRHU7,`[6Eݻo^mu>! Iw?>=y)e¿ >}~ wl5ENGUv3' (892H{ݿ̮S~QZqV QC ]f3&N[iidu«+2-PE>aYS;yasj<;{59NwC<))RRӸo!J -pRk\d4 sͰ]~o^w 2qH(v"mJ8Vdr$n~S؞arEeS`~%I"#tYp)?Oq\ 1\OX)%)/_s=%!~oE㸸(YT4Ʋ,i}Ld^\,Py8>>/&$TA k a |\ۡ*2 =ha Y9% a B11ʤ@YDhy}-m פ1IsU CO|YrJS 0jZce0Ef!lL-(U0IcJPTE3:!-k]ܽ.yR\6/?G(qt|XDk?G?cggW1^~\ljMضژҢ,kt !!Oc9E-jܠ iBu hS-MQLO-zKgd,W;y_Ǽ|uZ,-laotethz&C8a[`6{:#&)gبr|Z50wy}qA\ƃGC|"^Sw>:<'׬6 ܖdXm4&\k(+S-ZfMs0Mr=c)T*GH0 O|_WA+K wYr|W2t -E5 o xIѸV)l<QMJ#Tcɸs猋K\ |.G{3Fͻ' 5q~&'Gń[<{9!p"pƼzn"G?#Z-< Λn;ouA:\'h)IKEgӛ ˠ2$*F=~4qC:squl)k3wҟ|!y[X8EfZ7XZ|-Km+~  ._؎E)]hF3ھu%iRR0T4** /3Ҍ(B6?',fWٖؠt8;:t,[Λ 퓥0zkYEΝ{wx}65aYa8K)klסb;.dY wUTU1,V)($b QZ \Vlܽєm IDATiJofhӌ'_ UP "-vC ¶F Qa6wPK$/ ^M]q3 Xf :7/)ˮ1MMFPk\ ;C&WH|(Y!Qu4\Bh0iE9 7u"Za8&,[ތgA ?1sc6"+M](-0.^kAċO5Utz{;\\?7Br7(uA \xCx=缼xLQ NXW!a` IEԵ%UYQ*[mq}8è;ZeHA!Lt%(뚤TAUئդ KKIS40M:݀֬+*CUkc̯[vyZquڈY-o ! ,mQ]Lۣjȳp4MLъ<+0.R9/}k|_f#e3&Lgu8|Mvgt38s N(W:q9p<$$l 1lMM47$nN.B.(g*Y6>QB)LZ~=~1W[򕏸p5r*z]vaIڐً Ƿ\aH9ސwTEgcjtIN˦״7ou-C4(U6jRe9l-ߧXAc~iΘVIByE[*U"4RvxΗ96#KRx[f*eg4୷8KneL(0AYfej,KS ׆4Kٮ0qrP ?'4n$u|2`d`ʜWϙ.o ++^ A6^6FPAU r)9Þ`!E߲uB|+_QUwzy`(te= l,`p@^e΋gUMUԌvÒQH]kNOh[HS'J(}kVӘ$PuFuM;}t]Q 0wcAo@5Ri(3͗-w͒s:k8,gEreEE])a8~A 7Z紂eY;xN/X6k#A49 4g$ي.F V hq<>br@3HSqLlx~oc9LM mj!͊J$T:F˜MFRMgZqVV՘2Ll ,Bx>(R\N'?J G'' yجoѭMQG_:a$@]WDcZU:.yY25|Mix*C2t%oLR$q* TPbZӲTeiYWK^W5Rڤ동sΨR9ɳ8ʑ" aw7= ÂuxMg"˧$_tP%J ${Ln&Z&͆,+wO?f3puv!byMf6xtx5qzGR\U% GLo^:6ڿawq51 Bn&Çod[ǵ$i&(3Q{DEcؖG,W1ݾ/\7D|qvvBGHS\L1KZRk O^`w[x !/ϟ)6[ dۡJd5o9Qpx"۲OVԈ-PR|b$Fa6Ɇ,A=$fGF"ۻْ~)͆`[k Ff2yAU}xaސ&zAU) dyMU 4#cF;YIIO_ldHz6!r# ]%qehesubXmKlBkaP9RnӮ2xK]jfcIDZp]pj$c2! VݤܹN[&Ϟ>!O%:;œǗx^l֊QkN?ŵ:\mql)Ljm Ii+g_%?[ G,VuhФi 4ئ,ibhhlF1Us3$/3!J$y2C?q|Fuǿ_;7`EIHRpsvq trfsrKۡv"NQԸ`o? y0H&͚lh?ujeO!-<#c0[U?}S98:fw!*,ƔeYICQbb4A`\S#B-l'eY`Y&4HҔn`8f)*,x7&U\t>'oM$&ZSe ˢi ۴QĵY>R3j8t@KM *ic0`.߬i-HaKZzxnEe @S7O\44B$IFg8&˱հfSW_޿$´!/",44~`iw{"" CbMoM,Ta瘸mdeik)kj0Lt]JQ{Kpr1e1/PfLϩ*E]bo}2螲m1eSgo 5g(-?>OX3[\6 aawG5tr%_گ#$t+^]~F,W(Q%>9uDtl +))&N6S>YZX|t񊬘P9-E]k|ߡ(*]SU^B]Uea Nc!IӶ*qL6J" K[mnV AoN9ػE'bCZ>Wy*cU6]7B SڬV[\ߣ&c ae)ҪR!*MIw O&p%Ʒ࣯ly3.o^`Hiz| q3Y\_ |NvY-oϹzn&;pǴجdQL);XN8͈" ã[N@!ULB*`F͏?dz/;gZPGf$qi LGvzl7$# aGJ =^?.J\S8U`Hðxu>r2gIQe(!Ku  $[eJ%Ti˔"Mi{hÃ#$GWYDۘϯh\>K\].9;AkfՌӓl7ߦ'ǷY,64G/|CvMfeNшt0 |/蘲Tfsm԰ 6aڀzQH \"Ic͟_4^kwTV2ddLm #qerw֫$0 t2MBXJG\c-͟?Y\sFC+1i!k,M7(qxl>c>_ Io?C< rjUQW)8) Coy"K;񽗅a"+1U+VZ՘a!qRF+(hU,mi~ :mu0 m)k6;e! \# |vqEjkMjJ U ha ,ˤFX.Rt|rr.QZp}3 0JAFcZZHñ},{nاe=|!uCqp=R!%[X <}imbaan|??;?Od`kJ0B5*!uT9y^" p6Լx5Qn#ziM8NMFcJZ4ardSB"ьB SF[_2? l$b&1`!+E>wby>m0Lޠј9ádz ߜ]dݣʧeEB6,_K7t{]*CJХm 3lIzrqe8Vl+(<63鏩T03&'lK*$#Oضm0е2'٣?`zxlcmy!Z\M) a:AiXl7e]QScڂ*il8p"phTJ dyvIW/C\c0  Ua=7|lXt,NH#ѓ)UMUIx!vU)*Qm! gϯYLC* PUJG'p)Vn.RlǢ4nv1߬X7Ip<ˈ !µLnDBh-%IpCelaVuѐҸ$* t-899lX<{ K:B"nIB2f,[TEAP7*9iH;#sHD@fa؆$ip}s_<[Dᖲ̚ð¤$Ұh,%2znnLg+:1W36'\_^wx]p1"+7Ih-Z~>1oSW9E^r}5mQlaZ!BH "SV{}?guҸfXÇHe3vb]q VE=:E&Z6)ϪDMxA@Yj&7Kۘ<4WQe0%mل| 9q=λyuuO?W_t)k%yIZi3I70,)-9QXɓHt>qieTጦyEaPjĪ1 Ӑzuy~5EH4i1qL0J CڸQ`dk[h ,4T2ukFࡵiKk>(״.Q$h&(U L[!MBr$KR6&|C6]aMYd]\#- Im؜J*pHvge{?'a?8~ckl۠ Q#-(ʢᕙK׈2$EJR:*6ZŽoQW\nhG|7=6 N@VHJ,i7@>AeeIAoak>`wxjC&_>QybSf&mXEk~"-U+Ҥ)?!'HC&93`4:O?tR.oteZ8K3.$t#4"Ig (d=Ĕ6Eݯ#G]քבH]!ee|uóg _syS˪H SHLcJQadXND)Je6u-PBaX./_Fـ2 t^. )2W qvv/I:qx--~ϧo3]uvn4QaTJ,k=s 􉒜l`kXngoiRۇBL,=K)ZÐp!łE{R"mnCl[rUĬ[igHӧ( Ff<{|`pp@~ł~K&x F48(I`;fj IDAT?J+lK{6SjQ77j $uQPZTY$ ܾrnϡ%mXEF+𽠁9O9O_~JNP%ejcƣ#NO''aa/pMeBmz!l6|<$DHDJn)P\`؊H@ rp])lTG؎ %Dg2] nZ~Dm vzvj}I͒)5ABj.'8njgܹ60XolX_!Ӫ'Ezv< M o%v5,B|yZڌGm)I%P~Tc޺sPL7X;fkqcmNQ`.0b<]ol B4H17socw}睇5UU3w9v0  $$B7]2܂+ٱv9}sUkzyx7D\UK*UZ7|hqb4mCzL=zbF3yHQ;&%1 j1mFA8jb"EV6GfdYaUE)^$k@L RofYm `qsryJDDQNU*޿x2"Ĝ]s>'xvMPi8:YVפ)h[<?ܬ瘶]w2_\J.1ϱȓ S wE7,Wb#yt{{L[|ěo.m^:mِ9u`63 m#Āq BziKbf^Q+x2QiE%i4٘/ܹ?{eYL'#T]%oa:`H~¡,Ǡ .zj떪nٿ5fkgjdZRP-HAʊ+m69uбI*ʲ}}V)d:Zsp8($d1_GwY4MhDG&Zx&hvn3۽ztz$)b8c;.??Bo ˮH%m g'~|~"| |D-Z)!(6cI۔(ݢj{C,fgOX/şy"&\p֐o^3\V)(yBQVYNT{5],Rs@Ӵ4M D)Zs`Mg+`H/Q-iQGI㯿f, .4Gxp0pl2z!Eb.u͒H;NYD)Mg@ A'f#uPbjp ZMu.ǡi4WlOC YE/uɻh2eul2a^'pY~nT͓<}$t[zݦR-/_)=̰]6yvF cf]^a'O*c>Y`#l%<&jdkojIYl6aot՜(8;y;&b6F}vEA&]g1?o?ekkHSweǑVPiZE'JJ B/ĐՒVUEe ,lG2"Mrtc[IAwjφٌ┲I6QxaH&R0& !$m$iIxd2\CMdzaog|ݻwx%g|o>~G<~ t{_=wrpצ1Qϣ)rBs-fct! xiєhl,pL[]D%ED$6 LW3w~s;ssn˟q9l3 URQ}$/;J(hi5A8f0B`;6k vHiV"a5e\r~utkBdEF]lxmZGhnnx℺\ ZZxy|"g4w [vZޠ.TUPQ#*K8Ʋ-\²Y&:.6,HbIqA?,3tR p>eVR%m茓؎b<0L9yg/;swm pllz(PmZ~ C$eeeMY5loaҴ*Vͮc;Q/sm4YlϱF;{̦8*sRFQ'TT*\W*(fP׶pqoԡ 댬ۮfEV-)(>ao.rEY888s⨡yMVL&;X?KXݚ_xoWx7kVnj]hV) ӠmIhmyXt#J]QD8ZcY:yVJa&AG\U ,kLON?Ʊ<o] w T f:o ^ոA+n9>o=6Qɨ?r~S p-ںĕD94,7/ȫJ*G̹[<6!Mz GlJư4ɰ7@錬~XDݝ{|{# <_ҠDYE 9uئb}:05eVVPU{yBQFpquMYv2s4^,h.WXRatre:DIAB5 ÞA7_rurTrt%ո2DW gdG!mm" 5EN i2ՓO)Mj5Z/Eޠ54~cł$S4"/jzGVXH3D} Gt474$- A.6MLV,JahtW,/???ڝ 8l㯾FKj׳r^P ^(ЭmL&;!e$koY%7خ *MEQ6ضl M@hGZTB&|LXsD[vC`8MiVk:H/ZQ7 m#(gO[+]Cvx0&LOH)yp>咤)FG,|XXhZQݺ,۫?ϯ8XHG^M1M$pMhcL u MN@i^ `NNAP ih jeY#i0H9w/Y.2̞u*nr鬏a<{qʯH Awe˼xko3>_|T|{VI^P7-Z[[ m"Z4NdﱾֻRV4MhU`Yu[q=<ǧ* Gм:%1M iƴdK$,MAi3Y2m1!1u;p{ bGjJ#m:>)PmUqmʲ.ypТ>pP:<;{*>!)#eP5=O")ؙqt1V-X,DE,x WQg5C\["m*#69۳̶%CYKz_M;ˌ*r Wr~qM.1(v䫯>%W "(ZxXQ?ՒLKEzxI*KI(iM8?gssCS?{1<;o^gϾ`4,n.)% O}y ,[6T9&,ϑW&tBo!}Q%J.R5'稪ӄLBGed@ASA+t Mєw/U[{^4-6ǚ#|lMTQ1L|`%6 {YD ~׆8NC?41D)mߟR)apy~mzNoBc&UհYι "1Y'u[j IkFdvI. Y>!XUa+lCdeor"dyNO1|R䘮{&M*Vˊ$op.j1i$8خMմt<_-Q o2&S*JgIFw\<~hp&?0zeqY-@9zWOB0\(5yYr,Vgx *^}zAt0D 0ڣ44;;3T]p]yD^ C C<`0 MS0r,c@aMPvӈlٻ{fMeC2 ˴T"!)Kvw&XB^) ؞h Ӵh ϑTMiBQ$9`I*u1>?C\ϦJKlGbLOa? hHW,  Hz]6!O𬀦kn'=XJ85Y5?`.BHWC*tr~zmY@)( wV9w?OC"C`̃G cѧX]/2*jYՊ4i)uH3c4P !C1qu@`{öp<"/)%5eE#4)R)y )$is:(#T IVxWئ988krMD|3x ?{&>]6y˳3D]>w0XP+h 7-mlpה4UBPW MvxѷI6% urvpϾzjglM$E0mRA ck|GRg?#dva`9(Cr0Vn+槜!k|ϡi5yRB) [TOP42*pB`DqL;,/x#~[{UBEN( UUqqqLJpw:jU4״bKe\N>iYV킳̆3mF r/ _>d:ܦ*VD? ߦh*..O(꘺J9<%ڰ~Ҫ#;RevǶ^-Au~VgaK:nQoCMݴlkN/?~Wt۲< DZw IDAT0CdQE[7HmҴ뜚۰:ddjuCe!Eb>yb 7_ eBCd;L3?`6|M<Ϡn6,6ElkOy_wpJiNϞ RM1l<{M`44aw9!61aQhE[*Li4?z|_d88xVlMfbwpqdz]ާ nҔV-Xu'lMwp2M 6M:^79ycǛ`!15T:C#ќ_, y[oK7gv>?"p^\sx&o}?@↺,h۴ *aqsAGa9;F& Yr3c6k͚(˒U\^\dO?_>)K*wfoТ@Pڂ]ƸMq<ǟZڞȲbyA*0mpղP4ME/{] cQMUw;j-m\k!LGlopusMZ.d'׏g7ߺKhEQe´n5*hA[9LG~=%B(,TIYa`,7岛iٞ<ޝ愖q|B.9#AB׼_b=lo[oٗ_&N*lAH;ĵ'h-klq%hAX&p ˮiK4U$z<%kaX`7NʹnѶ~1 $\t[+:yf#, WL??rQeJLt$Uw8ޘpyq\@X9J$f31Z'iB4uITHeRjoQqrO>Ȉ,K]pi[TKQWIQTucnhM(єy#aXX]K0cٰ6ix2O%iZw%ٿuĭۇ]\b0EQb4l àrSwR2a6A?z>G)IGYMYelJFes|rfMYؖEZGGG;~zhaNcll(b{{J]W 㹘R0pb|x-M$꒪(1mlLG]-]S шtLY1g1Pu @(>=gD@ג[fl)Pic B4F )ӠmkT>0Qx5Py̯i2O)$.($+Z\?azҫYFwM;C mNή=r 8xDX6 &'+l6BrmMeʮAF4l* 2(reQmMZ4 6NƬsBipr|NQDKj, C)W '/Z4i8,5Z8m;a@^qv ;?I҄,s6EuKbF5_H-lɰ4MܰG(Zb+4qB ИWdǐQqN]j<杷؝ i[xnhbyia5Aap* E^dWrk2-]آֲ{N^P73&rH !ՂIn3kؿ}]D lCQ$V4E^ssZdEhSyi,חYFo-_=9ÏԵfx';DI*JyO?@J6V7=ACQf\.bxMU,7qܽ p]!5{{S|Ϡ8?ek<{fj#0`dyzdDuKЌ[IIE{çl0M vk=j4*tEvYeef8Oodtau*c~!uHIc L!=I݀xDqAUe8?̽ɳd}9o~U ]hA %'mlyoK#",9Bo!G& ^o޼9Nj[`x?w̸y}>!N V,1 Go@>kK<?ı"$O_SI(0LAj(w$,K(&6BJ&vEmSՊ4ۢ.*۷Xf_X ;m3IV)OkޜÙ n: $ۭq|YBH6pvc!f81̧TЍYmIX--|Cł/_>zۀ41X-g~vHQԴ4j, VEImj0$M3LiEbAb.xUWG A}.GEJj|/@,j\ֺk\Ng YAokYA8Xj4(*)FY3b6^GXmeΛ+M' Q*#l=Z~}mR|G*Ar<*ojU4BJTJS9X(Us=;HTLz_ML"_Ψʢk0/^7{=]RV5-c{wwv6Ԯ Uo|j( m*TxVr#,Kի׼xvlþvEc<0tAU4o"KSCV@ԅ&_(  -a$Yhv"rtS6]ƲlRZ781x ][U<u:M-Zp~yNɌwb&+jfkwԕs ,8;Gh{h] -^g2(9/(2qt%V8ve,0\I;p܈'|g,V't:M쿱NQ =, %EJtl)$]%volsz|,N:Ak"ϗl64 #Te8k]5RHA)Ŝo|lf~uFU# RJxHLr..e^]t*a*/XNO)7o!]x KX`tCUQ5)Kn2(*PEݝasuu|zEq||ńq_^1w?V"Kc|)k;kIa[ysZvɪ46:FXLg)QGHEZ.ml-=n"r_{uֆ1M;,aFLu]拔?d<Cb9goo% ,X3ߠhC߲e| ڝ>1kAsھ_jm/پt E N._tzɰ %]؄C;j"`Q57BDNYQ¶HV pH]z*k,-E6~{ky^ĄY=Ve a0xKV$bqq]޻bBҶY,upƍot? NDr~vd2!lΠK%ۻT.~賈S<, ME Fך";- K(V"-kfd@Y {}{;|˖,SnOc4ű$TPetQUMUH#{xE ūCҪdkg",%Kg ]iRh%y|aUW.^pIdyAu 4E)Ŋ:MFHWւ-%-u-Wx=ZmELϐ"DZ%Sj0HP[J`u"Z³zXxFkA Pi:gGR)E&TlGWܿ.[|mpmWuɿ[.؎FGB+Pc J{=F86.zӳWdEBD<w})FH[a;8eFNm -CUDH;oPnGf d՜^jJ8<9bmmg?+Ih>JnRZeh\^Ny#NFXVS,!Yr5kXvcrE^c>cc+&HYDO8GyM KG͛5Y9uez5 HލeM4FJ:Tq<P7ng)8Fk@ZlorrzR:x~HU&ӄ\r?b>˘vOɲQUI#uHS& )-Ų”zWԔu2LVmc <,`iX&%xec[ؖ@J1[:H:>~b$uJP5xLYdi ޾QkxRbs#3"Y&$˄Awn{,P6B[|Qd9 o`J{V,%UWgt[l:ώJ I5n|H"e|)Ә,[4JwXۺ.Eٌʼ,5׶x>眜st6rR/QÒ>NPU~ܽ*\~˫Y5O/l˱] ÷V˲IRm;eRE]8>hLM^yhu>u&q !lm,ȼl(j1'HKZh.b,'s}^9rm4[ R9`Ial1F&H4&3O,-Ă G{V#ڭH9::%/WHVTut-"dY c6(=qju,-:.ŒZ4nO"W VԺ19Ēk’Ec;:]ІX*/)5UM^@TG к eyL$`6Wx>ހg/>f<3AH֐w>_/?<}Wp:vꦼ+yAZVAK)1b<&r&]t{V_;(- )-RjM{wM](:ur9j:'4bvpltNV퀫;fpweɩRؖ8ND4ݽ5f)!ySA)5t) RA/὏xff^\R1UPE@۽χ_;|o ϛ/h\_cS*=Fd9ǶTb{-&˅ /5P ?TTf5Gǯާ\q(dl-K|6p㹜RV+6F >./\!,"]fv՜[ɹͭuRf4s[s+)\g1g9U*+u IDATvt6gxt? ii,. ih=ۢrLI%QbK$Y5[k4#amcFk*ZB -UN&qHzXBbKmA yZyn߸O]YL3./.9?dkk >|9;(JVY2MCU:\]+@k{0Ws*S@ۻxl~AZNNm f_{v7/P'_!wo/=UN< k76*)K:UM?jBit;~ZMfYV.ۻLf3fx>!]Ŝ9|/j5,: ~EkHKf15޾=b4Nx|D-7Ƴ%o!dY6v8ܾs8NY%q\ !xݿv^f)|>G?qtQkznOe2_g)8v!ɖ JilF ~`SsH4NFׇ mwq|zHnl,))1Yk zċ[z]ONj7oKn=j:{syp>'Gja,ٯAkdSV]ϳ  JAS5HplNtmIC׳"VLJ*psA)7 4b6la>**>έ}UJp1ڬ\ll*4ˬf0ܦ(5k]3̐e}mp) Cc8:BOȪy%FojŢ@2,™к,|k}8GO 6JUNQ5˾,vTyTk>;[C jƓc*&-fR/_&?#N[7P$)Ai8rBn5_Ū{ k$#/[ذ;*#"{rBM R4m8.ea*kZ66bG"m\Kq1*]<'•Y/ |[, ,۰=2#- J(NDzw1r@i988^%i9,R<.6x[k{DKu2ǏPW.yaS.If{UVQ׍/ 1I?c2 ^/lSU5醚{go>um1c8>9k1CA'iˌ$If3H6jś#2%ln첹M}k}!Y]$s\%^N2||1r-Fn+$4Ij88Hd5ltқ[YI*yEJvp]\ к~);LsGoUy5ZYcLX+Ҵ,!O zؖ!NX1 YlcR ֆBqɯ}]F1??_r}؂E(:V`$I#,jΎIVP*iQ1H=`5,s&Q^'F;Ul|I,́ܵz ׯ9eQ4IZ1$lY6YL]fܾ]=GKJJ8i΋/ ٪`6KؾvZI9yA"/V `csbj Z6*!iGilg)~ecs/`y!צ27؄=qDk\#F-ʢf:"lHr6''|UUP I+TaPu($Iwp/=aPB) , ʺ⫯nhT. 1@a%6X4V.P#@ \2.q.a#Rwo>͌%U0[y%^ySɷ1Ybc}lc[62fNQG_=?''Xr!rrJẒRR`U DJEM5@Tᰇ$jR,fiVU9XWO^ߠ^wҊY+BF y `[GHY.%Cb,"]Hcŝ,r*YjǶ6g6GuͭdqCV8.m1*5ĵwC-(e6lN\˲)6˅s>\^qSV鄢a!ٿ~t{TE((i|`(b2-ѻmJ)Jr-ڭll=},ry~^|γQ5I,[.͏ܳ^T,R7Ӯ-q&2V+AP`F.Id*jkI,F[ZHǢFuebEeQSM^8<~o;v oc..XTYEK\\&3֍zp:ۛۄ+ UYX]2U#d*rhzxvdo߾`; ۧJ UJI䷰&Kr r9޹(jC ZPEeb6_󓟟H~<}|ov2Hb=~?_߷xF'sf+EDm9yR$IQsh>8??(cBQג}`<~M...)UEMWU[71vy d l,wU5llχ2W8 |"g(ݼ^p1( b2ͣGy+>xv&6|+xݻ~%^d4’TjER ;ݎDHiv61Fi7}kln$NhjjU%X1FHa1BJ~)4c? {wXՆtF^ԦyVs\#DaHQ5z4#$h#H/a!!8˵YI3`.͟ ¯LOWTRep^<_btKPkxDZbŽ8+KIucITQ1b[=[eɳ'O(#d>u]R+,[[c!r٦֊Fj^|9L @ x1- Y#$vѰE&"_!KzeAQ M7+ eE<#MT*4O+bp,8]2i7پ膻T`gMh a{L&SlBy*x#^|:oճ{}josq56_݃/wuٽ$"lq6Vc(1FN+~eXK] qN][`6$MW #F( % ރ;W'Ͼ ϋ"bc&ip][B}KJXD-jeL%lb13TeM2 NMlqb9*]Ԫgim&y%(JCfnD$^gPe"FSھEԍXfS󧤫![`ZIEntVĵ[zǭb2Yko!,e㹂kEƤYFEx^@eĖ*TY7SCU$Q׾W/^$'Ҳ<>fm *!솼z!Ŕ>vwh"UtY|ۏyy9emgo yprB]ay ߶E;"N$TKlpt U;w\LW3D:-zo#d2_Jrڭ./W_ ^(aS5EYc`23eec8<"(+ SZX誠ײ܂?}znHo9Ag]~ȫMUV זԚwaOҌ$cލ]78`>M|i$ݻ\#gÊY|S%wYx(] h0Tijk tXr9S6Zga*SYZ"?>yǯ6J5r 6۩vS~ח(%e<,_pxr i*VܜE2,3~[-DmZ{ueCt~ ϟ iwBTj /Q kxV0x-,)=''ؖĦ*KPoy,o}M;n~,.'E5YVa; E}$8ppk_Q%. F#zz.Ww -߅xIEq UiʲBj(-ZJ^wm]f QeZc vib{>O^wnq|xv*+~} ɳ ˌy3gh#XZ ,. +q .)!4ReB+jU2_qxx*O˚vP!6g9mA!4|b: OFؒ7Ekf9>=OE.<}QJrc._|;(%yJ2B;stvF^T*ŲmfIJj[hڡ ۴$%STiJCU^\߹N:*F1'' v2ϛ|#V //LH˧ RK*Y0' ׷'#Dk89 %тӌZCsp]k{}}*^6Ϙ-Sg JS6qy/I낤ZE>nnjF[-rEʨ ^ֆ$-<+PylȚ'O+^:a9] d@YEFi,g'xv)gW_!,.ݺC2ܼC2|v@ȋ E5'/Lh,f\ݏ2Cc /Y-&(#eI<7DUuH5.ѦԛbX8Rr%3)_6˱]Ц,s@.'\\t5mS>awkm,B:sZ-O5+x]R5ڪAXFl.-ǒELzxRf#j;(PRԵ"pq%Y^ i!ˢHsD-0RģG)r` -ٔ x+w/f`=>8vq1V2N`cLEQWi}lj"K2T]k )U|5'h.StI{ʲ !YqxWMK|GkEڐgMۨn?G5ޠj5f"lMX,Nk5^3*xۼzu%Ylsڻd[V!NP 6/1ۣ]"JN4`4'"_t<:;[D"Q(m~ITMWec;<`^S)NHIl2R8M'z]6G}&)O?k}ʼ$^xGƖV5 ϲmLS[;Ok;x$Eb٤X"/q&c m6%Ka v˂O1X, ZEQImn1ޠ֜=' 7*CY~WMeXRP2` vtR,Kwe`UA)Sg%X|TLI"EA<{qt^qMV4Mqcl;ͯGYGNWH)9`'qx|g`Xt:;lݹ|VZmQ)xd>+JdyF2lt ?lER ,71@6Bވxɀ#vFa3vq=`4kQ'g5g1Oyy|Nos7!M:CjJ3X&s67H>u1w,)CdŊ$0[0$@RknaX6F[a7La4VUB6jil Ƽ9V9^/V5yŒ# fHch#B2X)};[\]\r*Q-uZt#ImV'WkFCIJX"I@ pÝcCElrz9 +;Da|:[o5yQRV !B 6eeGkKKrV$siuvBR$3tNE=./f\)U_b,ב ~w:|k✫o6V..m"koC`^Ye;hBFuVUEQaK .N'O5GTJS[؆^a\ͩUEXЧ:EPn߾˳&`] \ߡE1dܸS4RlnvHxQ3J5a 8:AjB>[3;wO;|7Ȋ5/HӄV^\].B**% ,3 rEQ4#vpMs,X ɔ4]PVyToXy'8=l6g)V&n,r$< |? m^15)˒osje]kdow,/\\-U9Ԫu=66ZX&ʲ,k0`篸-?w֏?D ce=!BbًK\Ogي>9yq|n>2F 8}-8dY`k4"CI >gOx+ 5tiJ=ɓ"z=Nq,mcjp]) Yd>gG`W' 2*R%XzC h\jĖVC ʲ,KTyj8!Z V?-Wt,Sī%;TRg7zu;'D]#i OkV%Aw%Orn׿eFYS cjs~9% =,!sChkҔU=L-\lJk"v#T9m݀˟`7<|r*N0"m$MtM\)j;ew%6U',5ο ,T"ʡL]!M]nJ9xd-Hãk\/P2ŋTi?p0w%T($_f9)/^p|~'^/H:ag{y{>Z"R bڐv_ϸ͡@kזqAr57Jc LƯaaa6ݰWlcy$iy.ŒvI)*nēS\Y:簜Ǹ孛P8]p5"YMyda(=xs .)5uFoئLkUxh#I/)Z+ڡȨTl9RcATE^(S#&jExAFaea"_s˳9U^o,xeY%Qg}9X6;G7y U@댪mmQ$Q'mۄaH]WD} -,bzivR J+,mQT\)gHJj%mzYlonqzrd9FI*pUz<{H6llltŏ?sbs89yK阃 ^(:m6mWt Iy} >>:` s|M&}\_G'| 9.]Z:Ib1,VDFXXԵjg-nUi4' B cldO{Gk '{o9glnn[\^]`)mvIufjUiv=A6V!MƠC5J~B0E ʲ8RRTUYTuN )U])Z19?{EUJUlnla7nj8h,LFWB;m67{$yE,LQKHEn3t)sM]~nC2w], ^J5m}(xH$)SU)zdJ+Ɠ1s4N8{5go}l8`Y⺒jh>~)/OUI"d0haD^D"dߦV%O_s{,񊃃\;:,eVk.;X“)RKSk*URil[JUxZV3`ai&44M,lor,-Q 0+5(墰Qkvw)ӌ^]^8֍~Y?{O?vk\Oz)+(u~34q>e:=_LE8xLW;mQwfklZ70HA]7=Ukx@bY mUY`{6xt%j q]2,zH鑦 l>&kLMOyuoru5g'ԹC\;>GYAopl9y9766~3.^щI78>q ?ɗ\1UkBM;`{%(ըYEE^͍ :k4M|p,,)HW%EnpO&<*AXmB4V) ݮ%g|q>QIUݡPO^<@s-/~[+x8ҡ~tSka~UP5ۢ -JcIlAoE!46cp&cJ %h[ENa} ?s׮lB/#D3m0r|A]MY✬H:xL"Uc ϚFl&[DHg(2%pC&FXe`x7Jt`8i.yaf!\ k bLR(tj=C?}? qA&Kv}޾uL/rp=9_7>>,O/~K.!9Q'$}&9 U׌C|u]|ߡ(s4#lԺi;#^5EmN4wHrx6?b,~c*q.;@.EϩFJ5~s)kE4WX(ʊ>(rp,Gt!F&k~wc~Oyݻ̗sNO_ricW!}Ym|;דsm~7l3,H=ڦ%rI u Hc䛋K^UXU%N0Hz%L] lqfp|&`uSH V6X B:UQk7g!-#1ķ%g3d)x O3[q^hNg8bf1]^$,,tK<{ [$Yʝ<~'er$  `=_s:|.׏/yuzd>!j6lﳿGL&K6Yf2t0ٳS. ZQhp^KlGb29~`mwV@ jz/pmJ >2_ym ٘grNy HVO᷾_ٿCIz-Za"Ls~r5T1yaIӘqUi1d3"l¡ɂFcIέXZoEY{!WƱ-6GtM#InZo<~D/l8ZƦ{B4j^@y_r%Tipgܼq4mn!FZ<~)+z:Qxހ}l+:nc2 E\ORKBO|5 no89)y>9:Utn0i*3%vq]nx)#Z 66R.VO]Ե&-,6OYX(cE}4nxZyQ'uby(}^j>h'-RSJ#7vyu_fKݏOI)~Ŋ9>"ly,&׫4׎ʢv.S*esM^h¶M/ÈVtLVH#)Gj3:ft:[tz;:ń8Ͱ'ln v IDAT8JQ4|ϗ؎1AKss,f80:J'gzLܡLks%mo4׶ͧlm"T~V% -Y&Sg%7dՔ2oI;Qu\`)C* ׳%RX:CUYkH(ɲ!%hRA/vt|:gw4B,1]Xˢ?óԙHbg,k\[fPf9UQ0uYVEv>ıϞ>o|`g/QG4N%+J)1m/pg8Y1]"[bQ2NIX66 TiA׾C;b>_';HKlaSd1Nw,jQF}P&/  0T]ST%F%ݦmY! [6p)^wg(7g\8~ ?&Nb>*K4ei ²Xgn2a0ɪf1MSxAH?㏿ų'_ΐEfw$U#櫄2# 6rw[O>/9*$pG+W9эfrNqct_#BZ-Ƥiwm >lǥ*|EMfܾ~tFW 6YprvN؉(TvpU="2*Ƒ9hG6BJ&eݨ8:%Z 5Eb׳Ҡ yvz^zt2C;$6pYU2Y ;!\M/P杷㽻,iֹQk3mcdOk&^F0,k!/pJZhYwC,(9nsttDi3(8e4`pK*U'M hGǻR5S‘0r\NPUWS&aX$/mN"]6j|GaEct.,Tqos)t/+]NNX1*Ɋs Z<{^l??(F1o_Cݹ}z+2? "&/t9PԊbFK*n\d:$O d0'6YlƘ9W%ӱj}O>yFsu|-c\/P& TQ4zR7[d9A%rb8Q$EC>ߺ??]gGzF! [XHO[h9ݰ54iQk|j*erK7lkt] ѰZ- ]YMTK%]LO8Reg AԽilywwS>l"i%YB1b q|01 _p/q)ȈHn}sgOUjZo>?jzuUMRi҂,;(+IXǟ_,*a[:tÀvpzeկ*2Y/>$j{hBD 3inOT(@zrk.lƣfh P޺S.}sVRFGD9NmZr޻=!- [<1KE4Ա5h\pm  yurDD5!u*,׵I tW+p&9wd2lQ#faY8"J mThu2-*jBJעۢ,$e&Е7y԰\Dz( W䌽[TJ뗸NB+9Wn}1{T@Y篹47 b.2uf1U  LVsM-LWe#R4 a\'XXbγlps mlr1jv=^u\^3("99}Nkxr2X,lmmqM^,r<7~<^|bOq~"s_gQek{!Jώy+_\MO1o@\݅i\ ڍ>I6ڼ|FGAb9֭ۼ|V#^FgY /|_{s_oJ4&6+vGYGs#)  BQuY)b[UBJd]sy9,4ȊȘMuB~ߐ׫z \ǶeM+l:deOe J^gkq(dBY%V"'/H-GQ#k׮$gH RszhxgtbDӚ,Yjl{;w>(veZ#*ȨI),[ṂU4f*>'`o^dtFVc=vXQ眏>'?_?h⌲(1 ! d$a7C<?@KJY"K"SȄEE:u-Aw4\%NH#>Oϟ=$U9VgprW:T2BY8nH2\c5pXIO%vKnRʊkWosr2dxV]p5 %99b69c!v߽/YE1aC#Gش.aPZhdqA+l%ʐ4Zʒ"^!PJ@jͦ:Y!ȔqfƲZ$&yKZc&ѡR:Ypt6_?&P(E!0MttLj6m(2]ߦ29Ior|Jzxl:G{۬V>^`aai>)*d`;' Պ(8y}cEM[vŔӓgFiĜ}N4;4u,M@e vvzfgK|Ayab5h&l ,/1]A*Ǡ'fsjCP5B*.,{`$ϱ|f.899 y??g)5\66M:m+; 6{we5y~[mj; GΆ3su7ާ_a= ٦\! k,Pǽɱõo o-DSA>]ܡߢ492IXg6AO]T2Y&>UXB_gWD@J7 Ekn޾3 YP+P1S$8&h² S<]jzFN Fؘ@kt#p,0|t# J$Acq6y#Z.-.JLL%J|E k r~c.RUxuT:f~2Zba:\6k\#\wmh5ܽ{N3b/c +p0tf9Y }ew{ _^b&S儺,i}n*~'ϟJXNaHj-#Fnt1"Jje4(+Z$*YR[[;' Bvv(] aJYbXBT >=!+ j$K:MrWC(=nܺ鐲9zrxf \' D Jl>ɳ/Y4(V8eZQ I.ȥ-J$IDW@5z.Q$b0L(OkhotO0\V#jk7H҈h6CLǯ,F4Za(j`Ip[JxtA \Ǧ(JVqEc[ 4VGpBGD`,Kl4=LFk:$st+ksd UH&J^liRU!?c:ƪ5 :\ߤo6 ]®/%=J=7ay6e3K jdY96Ex[s=]i9xԔzH|/c*tzm>}FZ2gXr[<ólLZ v'gдlyN@Hy6wn46%vyc&Q:g}1 ^泏?2|R0xKcY>ɜAwk?khXg6nbJZT"J8VE ٜV~&9AYT躅a-v\y4gDT(S:q ,׊0MIӊZhÐ$KiMLO3{*)^<$O$ɒ|DU!fF>f_J\GxKG|^he=rm·#F#"n7D1^6׮˿ &) {;dYI\QI4EU?~'Od2bPJTM*8~sE$lկGUkʂ0qM^lg8p9% ׁlDvŜ)N^2&5ew>/& Jݥ>۽C@ ڠ;i[6*6nF]zks>:;\\_.*>'o3hђFç% DY;%K~{q훦t;=~[b6V˜tcplxjH2LS6P^࣋ 1te{X 1O_>Dv+3X,Kw0拔1vCF /ܢ(2U,r2W%~1[jDYt84[>Q<7rs,;sr ǷtOc6$BWI]Wh:t7BcȊ5J7U%)e!>! т8Jv6Z=.GS4ayBj> FFuBD)Uqr9$&膤I2YLrcϩ\$Qg 4kpz[s.{H2u1ha @{m IDAT[.bnhZM-A 4ͦ(k,ǡ+rY"L,pC4KY̦ˮjR"uBQK "4_(a7o xs\Ϡv8^`8Ӌ1Ac!eB]d6j 2x.X4D֒$Y1^RP JSHQW p&iZ..>:6BӰ,oy~1™.X%׷g]axQ1 ( 6qU!Y1_pmo{6ۛ7?#Ma%xȃϞɄR(J -JY0YFc}sFsfzklϓ''`[ChvC^?GTuF.;|J,qldž~Vk Mixlt[࣏?fX#hxī4e$IblBoگ'v|dA$$qF'`:0lO~ʋGlޤѼFo.+1]nCe, l>Mlq%T25 &Ibdn -^|Jm2_cZ5ESrS5y%:0O&t:m޾ `8:Gm{LKqb3HS3,D֧֪ض.t0m(uzUi4|,sIa79/Ec$Z=gulFgNNloRWO'hb TրY4#Z.xec;:V<] #³CLxS(H9Z7Z(j _VuC3@;h 2_mvg F] \קi1٘AǣkIKXENՠO?_yz>yHͦt:Őn?@(NHrnXl9%EVH{pJw9zvx2&K2\{_AVpy9fwwlF>p2|'\!&l zTJox!UE 2RF% -"(2ZZlkR3i#9ٌ,/UM)0tYkT06/'8MVFci6vdH}0ئOe42Bia-dYR*Bʔ(ZT-]JP$%,CkykT&YlppxxB+lpy1`pk|S^'1W^2^prbΤi 1MM ΎGlIrjRlnywxST. sM,ף@iM:8N vh..hwt:k\m"&*\K8F^ _<|EV(^ :ݐVƍƓ1x;8Tt: v69:zp2Co0Oy|DKA/us͙-/Adik;.v;l1^weܺ.谘ad)hK*1]N\$Qlom#(Ѝ4 ,R hQ☫W8>|Wy U.R FZ;'~_A?/ɇW|ѤI%yXok:rr1Ų2b.Y%Lɳx+25eaY:B؞FK,Dӵ@Iȣ١|45ibXWb21Mjt_s, S4Cn;i< 2¶(HuaZϙMKJLM#^1ϕ<򈲼Y.I*_:.ab]+ ڕ=f1kcMi.<34FV L@hMkFVV|1a:Qk5%zmkJ}iA)Ц5 YbY&BT4-t]P)날a6O^"B7,4h i-'Hm4^Sc&cVkJ'^zEYX@SrE뜊 qC(t!؎eYUToI.]dYBHlӤD5CF ^[0\fgg@^d<Jdi.'CB'8jakidYRױa4{ۨBAU*k!*єBUa8見,+ҴAlj )KJQqye)P)r}zJnW`;6;-PL:Qv8zwcsFt]FEN^lWFl3NDV ߵ%fÃ]4bxdYIc8:Y0\RVؾOVښz_{O04 4AW5=f0h"!* b8afx9TB7t†b$Ok4tb̙rlO3ܢk^=S7hέL//ðtϿǭ]wt)t!JRs:Eh]CWk7aSQy*#$RٲeB* 6M77w!0MQD%-,'ĵMlGR 2")MWMqUv6wx9^hb6\6aRȢWPئO i( *4%/*PR(LL}7M|ӏڡX;!<0}FMi-|OS)qlc  E4QwgEF'<~1~k Qi{:д,ۦ yvtLQJj4tCMU _}ESI[aZIt\zk|G<1qn1l0| )SװiضhtN$y"th\WLQ7\u?ӟ.!rJ%dYB["kɊxJKGƓng5j"5vƈ"aV$ pi/>])KtHϐUAŚ9Xp4cϠ{3gq Dw{׾ z?_ۍL P w#cxE]뜟ISeR1h>E{]/1-i)B0źiUURjtS]ʲJlo x9ƶm@g<ϟ~)Fw$Yo9*õu4&P, Iru>etl)qUxhjQٽMvwoCsϘ/IVs,d>(rl3aV|Z-XF#,a{k"/(󌼈5]74a5x~Hr}Є,u 'IVMA*_&^$1M ڍ@&^hSzxw4c8$rd-P,^AœǏ)\ flJ-|>"]E[ԲZ5(0J $IEpхAQ#E!m0 UQu4,4(JjY#{~H#hQJɽ{wwUA-хLܺzr:,Jһ҄_R e:t]tEXPWz0fx9"ְ_ RgsT"GGG\DwLuaCA>ecჲ ior ZdE6QA4ˌI>kd`Dnܿw R* ** 4%J5T H#czXiZu[XKY\9 Z(RLC#KdŔjHUhJ[7oϱ vc@tOalwP!.?S4Skk \|77ql\_Dz%^@>}}ql7p{;#)BS*\OJRG^ضM+vw_`( UJ7) O?G5p\4lQa"Lݝ-wq= ! NMnHUekR%Oqz:?{1O}*^"JՀ}ܽEOb!bKosm,itB^N) +ݬCôL< Rd $*s$tif3{M~>x}sfkut3/?,W5{&yx!Sܹ5>)~..x̡P)JIN:i覠4):.9-LRW&ьR0.܊@O9P!(jLLƊZAH$V(yybwxT+RȺĶu]-~%6ԸzpȝoCmRX5}Y蚉oCFʩѵ-ӟ1B"dN`ߣ|R.yr8M +1 ^bZ2eﰍJ$0 t·QJtNYi5(v]:r%%4}: ' DNrM+p\VV@0m~GNb:~.y Uhj ö("WXIYI\Ųlz>45\;$I24Mr$ɨr[:²M]diIqKcloQv@4 s1‹GH)9똌mF täVPX&~@8l sf 4DZB脼dc擘fez-gE#|}@Fboop19dzέ]>YXV%[XT *OS@RiE 0ϗ-]tA5y61k3>g/0>G[O6X|vIbBkJWO=CeJ jZM[mKڦq_5=ɴfcm‚Xmt)}D+@U hMڒڀA֬ .2M:PHa1;XNSנJU |Ŗ &kp҂xNOӤL3n$RU5CSz{hRzXҊ(F. oNg~'zIW8a|^!3xˆx`M&)Ȫ,%“di6K๊n2>'ʠ1Ɛ.fss|!U[JhPYQд͊w\lʦ4=l,Җ݈0ภs4.qms$ґ,Ͻ~'n5|L$ãJw;/S?O\bvmEFkr(@[8x#A:! 28@T(9aQ%r=c1_biMc5d9ݾbQjvTG``tQaطdJfxz.I!OhXf$Iĺf8 IDATdEFUgeE;v,[ll:Ibcx~`I/)S.i2k^EGDQH[JIts.~h1>d6$.X‹42h)>N?'nO-md}}K: Oθ0.ݸϽ;8;:si钝6JQNO7N8<93b xoq1sypz67v#KP(*P1\a}7D$ty考G˯hu|h5U:csǿ~:_ @l6_%lNS  Eؚ0 Rk\\S59|xrIt$X1ut\gCMQ|eUx8V^;A2Zh&BAU~~/ ]^PS B))i Bh5WD.i5a{1׳t9'p#Ys=0z2ŗƔ25U2RiA2wIgTÏXR"1H21$9ܽOZ\pŲ jr{rl.Тn=ƓI'd:*0l5'II#ko~ 7vvG|۟ZsMk|v}:ÀMNO bF|FZ%$3p~~!Q\_].sDEEh]Y89RFsy>AJ#04Mp06We8mK!,Ua@ȕZ* D(6)A&Ss=#-E'$4VlIuHeEW`cm3%YpR =E3@vѭcTs+9ϩq4Z 5J ph$L:Tb9D04im*lK2fos>E+vKD^g{֥'<Ͽ"/|O#O|?{3vn ''̗&Jض KHWxדhm Whkq&a1圦]1FZ=~mXBM8咬-h֨j*jTunFJ9݈y.}~~o|FX (MV2DyUN\NQ^mMmIU0"ϗ`,yw8;>& am#՛!w=tp+},*ꦢUa,/ɋm!ۊV74uE#=lϢ(])4(ض7R i(EY5f B)EFL 0i[eJ/G[ ||e:-܌.y&㌽6^Y铧mA]62#ʫWDIG|q {GvMư; ?855ˋ^і|[O.ڸtޯ[gK-Q`ig(dAuHڤdXeV6q,)8:zt⸋Ò[ZՌNw);;nrtDdHgGqeb ڸEΐZL3l)K!t<"KP Wbj <62Ћuo8 zn2$ یS5FU&|?C^/k\QklЪW(^z.U[O vPviGhQȗf8$K'HoF]dL?¶,ںMC-iqck)]Ts<}v j҆J7-Ʋ9>=n4e6UB/B0H<^>b]w92أa5-fXp5>aww5Z,G~slZlbcY)m]] (|8Ķ\ߕlRVQ uWF\^\S,c}cr_ |<>J[,E^wq\i6=l\\/QMQB!_t:$yNҍccY-ە Ft71E M]ضpCg `x@z6modum6GC.$Jo2[<[{7?*}W͍mgUKome1\ / <:IbQg%MJU+h& {wG`$%J5dbu(4ym<*GNn#-ni?nfӊn7*Jltm8`Y9ۇcIEUi|?DJiAEgŤihͫ/w-6#Ot4h)%kɪוdł"/@hUQ)HƲ̊Z5v7n7cYEh_[9,-R:zImZݐ%A2h#˜,r`1O}{;ܺ#Ւ I0{6 IZw0eF<_p'Kqx˯q5>v$*4mE2h&FVOɲ)ZjSɳ)~' 1:(5p3>9Xsf0)a/Giuxw2>z?*w)N9x.(vɪnC\ͮ tݽ{ G#۷;xNE o+ PĖ4Y%[k7ȳ| QV%i>2Btz>kKi4Y=cmfs&Ǩ&`mM0Z899'KݹG'ՒF7٣5gT0kWW %y6qGXj.N/p} m)nrlqdAoÃ4AhB62F)p]Oe'Z7' z`%5/|y:e8&]|% l ?G=}'w/ۏ߳מOfOھ4hƨKK'杷"plIk[UI?nI Lft;ԋ~a+NȲ /|OM|#_3DS;oxXVHE0[EY֫US53ϗ&# b|} -Kկ&r5 Md\̯c2S|56.~%-NH+iC2\r4ˈB +ʲ^ΏtR3C0 cO9<~auḚ݈|FaY[d8ɣGq<ϱ6[@X ; SoH <|F8cQ+ mlEˊRTMQK{4oxCu8?+w{/@u 9>GRPT5ERZ ]{v%soEL/Tmr YtU6atRg 7nฒbKft1ӌ7Ɠh< Ʋ:EN b1?A ezEik\_"R MΏ!!شcb E^ !V#--4]ق2#,[К mJvͭ۷89yF4+c!lhvF+c ˀH6$Y_hM:K) XNZ#\_Z>0p)JAt B {aӓgMJXQ1Lhƪ5o$Cb"w|hncCo6?;8>', "lIR)Qɔq9aƈ?dnc >|LJl{ׄ=W^Zq~5k_ztFQĵCxSWõZl|s?X.ܺ;@ښϽE.Ζ?m"h$!i4\OR3, n~5,9asz2r`c I޽\1O>bEY,3\Zqh-OOE-}`2;'\~{4mCIP(mR VU8*E 1c%4Ϯ.5Z,J 9.N) 䭷?1ȧmk鸸2L}kՖfsA^V;(v()}:eSU+ЫUh 2'Rll88OGSS ꪡ[Ǩ'B4EUu1 >Eo }(lCV]a1:Q-Q\z:Z20}tp"۩X$\]||[ЧK?QNK<\O&F6~sѓ q **D2ry>6͝ϽDJҍ(R3Ƨ+4! ]F(Plqwɚ CjKge5e6#}}AURy6Rt넪w,+ƨ7CNNNY߉9<[:PvL,% 9c}㓇 {[Lg9Q[7xvďV2ogG'F#XR ipE]<9Wll3L.9<[Ҳ4i B8`)lfHZ̧J|'lʕ2i"v)6qgMڶoR5 죨yvRxı(zՔRZ.jX.*Ty'%08E]C:M[A&ZPX,mqr8&=S?` :_Ҷy^U+%_0n7ķ[BeflE-\iHijT[;6QQ‹ښ񜘲Li_v@ ,[VيU6Pm &]W4EmwqX̲Ik,˨ KUy|p -p| ZJ!FJA' ,I1ᄂ[{[/+8;y2-:pvq@ yW~@M t`Hӂᦋִ.[[뜞•_/c#m$^yE0ds}(Vt(( f}c`06\%oGܽ}w@oYXz~`ݽ<K*CU Q1%Eܘ_Q&U`a R.NMc$hC5V *Jla4F,,W}"k9:pc{ٲ'5ijC] 8ټ.Z, $"?~UK/ݠmד Nrew7bw6E}R5e3 8<|p. ""Dmirxz{sv֩'f|_jVIŒh|Xg3Ff Zڶ\e{=乽>Ϩ9!c{d}okߕ:_׿us9 F>Ff_7IF'OIB4&DkP ǑV±q%ٌ'gϰmI~J{Dy cPz¨cski>I0m%_b]3:$t\b##,GCO>-]=ZVEj,-A AVW q(D~HmR\Ϧ yIؑDKY+Tm $%cvc8a<^Ts.Rr ݛ>ηVrW\^3p}!F\ m!SXpGٻ(s!HRtLi*\rZŕ]W |7F5-$a֌}P;<~1.^dȊ9Um(SoG]1sONO]l\' JWt\{O?,3( miQAX.qbIYd8VMʢDjtM6uMpC2u(ȋcOhf`3|:f>mjp:u\h*&!E^vMjs*oK*yQg4aZjAoqkq+ɟƔMqj[*M~Wb.␓S$sF0X`8$ cNt-jb,+W'yeέ JܼdrE]q ydOp5QƳ =lg>= b Ieðe!ŸÆ)alؒe")X(Q2Mg$nX7TS' p'LPSw{gGXe0%Qs7?l>-q;XvՂbz.N;}DۡK "BkH8GG',5RzL' i8[0\ٺG?ϾF;΂6ui'pt=Ҵ擏r ֆCtnLNw?Dcv{,{e͝!Eci 3(D.Mcq&}6xĘYeeksϽ&Q\/i63{xBQW>w/}z_VO-3í7W#t]"EQ늆j+BӔK4FIVΘ/.iB^TF&rR ci*9HWҊ$USӊ$qsK̗w%,`Jk!|eui4ڢy]A;c>c}}j&!ϞR5;?`븨:qqgG|&nA\^2[4'rzbbXXqe"9VHv, ! |<%p6BHp 'pHOR'Gܼ)NS!nLmrq//2<Kף(`5SgfFSŊe "^{My!ma%~U "7a,W`ƠWWPV%ZE3*E0M3oO )jaǨS׎!R~g6aow[lQSg9x'Ǐ[1aS%eQ-,/\ayxCգ j0wsx8j !YQ_yE=j22GMUb|1u|(BZ...Lfkp\C Ϲ"7."%8@Ձck)J+!eVbˇ7?iQUY.Ic,qM'R?|Ze&i rt}l2,޾ٮ_ U ࣍.Y nzB &)Mmp˴$5E .ySvR4rŔ+lD.oSʥ&j~7~ Hai żЉ};',sGCwB%UӐ$ RX+q\|{.t\Z67#f QuC|MYF+' ٔX9YYmX듄1{X1 Iӊ>>rNIvXIԢ FGkSO(Vo)6wS%CO2)Q /P䂧1ڸ[[~ܠqBtfv432\^`)ݵVZqL`3\ (KLQkEtG]<ǑEGi:ܺݵŲlgqoo'A`zo5Stc{>7_hء(ϸpUzP%M 68ƲĥݎZ\\0ԪBwu]jV )C8kh]R3J +r|/WfV-+ dHL#4;#%c4)?(BJXbÍ5|#f41P+ORK'c5FU&M iXq|679aߦ.%r9k`v;|dcsӂvA2L(e,RUa#Z!i:Q7g8R1Rͭ!ސȸ8?ekkq@'EAamsMIӥlpPJ)*0*6`]CFR\!҂iBTAV͢WZO+ 02x#*?q9?=!]F!frv4q$ k(]0Xpqq½$dutzI~ LSbA:y&Ϟ=6KS1 q61*|,d>F mN\UAtjp̈[GH0uJ%(U^TMdyJMX,DI|.K\eAZ5 vD *kD4%9[=Ř6aT\<agT+\EJXkB뷘g_0ԕAkM^6~`EWȺ"K+鐥K?{o?Zf5"%=9^ rM{LS6bY:Cv*Zdk`O3~ ȲU 4t)S%KZa(|/f]#&hPljr퍗h( 9:|JQdiEM]}N@O+VxQmvv`N Igct{=f˚ $nS7/q(܂#ͧD K50 KQQɐ'OߥhĨ8_/_˚9>Cz#r"s"邵mV+Kӛ;|?"o&x3ْ8O?"DGJrd:M:$.;wr~|A^#}/{Htyta v},/ \]&Ӓ)!yQJ1qi&fo-rF'O҆H EQM#6{xAesr]b_Qز&/Rʠ IiNs=\0uM:Hot;.;DEsvqND+]Ƌ'pzxZ5>s@:ysogk+˒|+_/~7痑e)uuq\\J0.v q$#@X{̏QGYNjY!E1%$-c t#4J5hBGb4"+a)Z5 A A7pɋk Bi\QTyzq,P:GԂ&p,MSc4/q6JǬo@{?\~+ߠ?ϡ}6%Q8GKmpeo% B<:5[# )(Xf=!u=ϑ%$#r:X+7%'O(O%>YTMX)Q-jI8.Z gS~:x5['єEFgܿ -[Pn0G =w1"I)iZRf5_gysf< k0goM-hwt:mр~5O|v(}lj91볿woߦ\d,gK>i4;꧘mu@dns\NɗEIzxat^~c kȲ1D /YǏ_Nvq=}S"䃏>@iÈ"/Ä8'Dz Ui6.GG+~ȇ O}B7&1(&:eq+*S&=$}0xO^<=; _LQZC?_w۠|,?П1/ -4~`_~O`Tc`}sJ<K]7UtCvvwyc\݊mָ8",JqJ!Eۆypݽ}QU"-ւ4TEn*bIYe@MUȫrA]BAR5VTecMXf *I&#QZYA.acJ4hV(lqvyx$WMjKtRrˌVi ;XEUuA FkH)uQQ5pL&KDZw;xBXpEHbYAi#uZn VXdA l>Ó)}t rg5,<&W99- +|.)UsUw01zg?g{O3/$?|v7@5)Wo]'lQkųs2, ˔V.M )ڒfUL.s>9Gx7M/yvxyaKlL1bf¬rEaqmHϗN=|A;8e{P1:ytpB`"_. "j{#Sspݝ}Œ _zm/j\if ʬ!Hz+έj* !Wzvmj6ٌl0֮A5,  aQZ4k_}]|amӔLQURe{']0qZa}}U6(X Vk lYe=%_dlakIe2^A)estr/KB)qYoquAr:yFM˟z-KJ%iŸaDoMA;&Q>^!nLQ-2u$ IDATX[x~f2IcT=lYsq|#\z-[ {]kǏ&8Nc^}3l]c>b45 $aW|ӟr9rz~AtZ{7^^gw%K ^}u拔''=ϵ{.R|5xE$-%6\z) f\\;;<~zpԦӎ9|<3oN=`><)8:|̭n.4͒`1[Sz2[ ^X;.qfPVa-xND|?o GO' 7xo08 Wy>As13INǃOnkcc4EbFCU)(a}cW^% $o3Ly9>|JoݘaZjyjOEt+-JZ-tWN[9!%hss%=|Lwp3]l՚^gD`tIqu{ _ϥĀr!nEl<|<|7)I'l쳳Gc!?JL %G'_%R ^%a4/L/钺jpkSUYOn[)]#Wž M'VdLirB4X5>^5ҭh7l347]A ESI1ADR/"pXC J\O7^jChU#.UJg+C /~kF||'wjJ-Ȧ 6BY /8ң8NSFk}FWX. *R( 5X{#, A4W/j Q)myI\"(ZQ5 Zv9Z |OPښheY [Ni&ni]ؐ.&iDiu[ʥuV\U4jݓ!SBzifgS8"蘲(ܐaK)Zvf9Fl7:d;ĭ%Uu0v`tP -e pVJ RzrڐenXA3Eu6TőƮ<\ץRк+ d%65NdMVY,sf5Bjg1 RGຖ@ͼ8$6`-l?ϓ5Ǒ++ F+Fy1 =>vDUMp|MYOC=}tjKU)Clv'i%]iAwDG,3A7ZUOJʴ(rdWZH/Q K0?B3\#,B J}BK0 lq#%i5:჏?c@W|/*q0 |z(4 c?3gw>ߒ?8R9y9CN7dc"[׏0_*\GQ2 nosqdsưˇF\ۿObʍ=?`m7\NY,SvRCH uMуwC%g'66:8pt~S6hYP)jЎO^jׁ(J$L/c<|]泒_/WK=~@/kYW,n`-+% }Lɳ1ac,J zh'AohElh(1n"]N򜪶9  }=[h.^CUm՟`j8|0˼DGʳh5dxL+\P U_ԙY/'^wMS#wqOOXV%F1_$OS-YVKl{KsI k-o|ş)uYBuK/`9)|YV0Ix(!sCG_ywy%Ws yn0ħif^S:c|6 nі/.F7loutC66ZdْӌdI\$$$00_5Ľ!w`|9gT%؜ xt-OLM8،.*M+#\lupp,n WH01O1g1Wn`KW^r6eQyȐs)Yjp>xHD]`MӢ?X'B@(Y ?q}0b>/(˂NfgcѦ!*zݤKSTC([[g% TA#;8rM(Z]KtM$[\rmoN;.3:-A'VlC>ϽlRX(^&{/֫?[oQhCVרǚqh0b{{5L]#q@sV1ئjƋ V,+)Rߏ pMӠDyYcb2JHjrL! FuOO&1 ,ɼďH7b6AQs>9c0葕J7+ESz.Y RF`W-J|f9Ljf^^xAAt(ˌL/ jUܐJ<BU C#u-03?~j¿g^.awU-&'m$DE%V4B)EQ.RNc*@rGlu*/۴]Ϲ>rɻosyOOC<EJGϒ6&G}̟o}6۾bDҊTSJIEHA ^*oE:Qv|9&.^QTjUH,SmLeЍ@XhE.ΧtZ-:qFt{F.*b<9߸!+Ԇ8Tj7@,B4 [vGGK/ "ZIAF1RztZ=-~>wrpZ)]^|mL+$D S+ X{ c|^rv6cbzܸGYV錪8'_>a9{m '-ŌtAXy%"?l1pi-Y74 }L5GKxpʠcڤi 12gLzk( 5(Ortb^ eɯU""tkPl_4 1?%v)gK2;׹L*Wnul]?eyMlѳ'=叽|$1'\*6>|dAU>.-M5Xl˳3'v+nsm /1ۿϗ'PzCOy<ŗ^,5eN5o[&wYaq׏„vM5A51+ 4$/j4h Kָe4R5nD.J"NBYKk#"9O=AjArvC6IҡVܧq(~2^ABoGH>Ve1{^Kyr**kn$@S$(l 0l6h!JdaBaZ6)Bf7-=VהYy29y=s|;b}~a"?Oc/5~N$SaMlT"rtNo*|KzZZ8ҥPuS.º`|Z賽qG(lJ31lL)) Efq 09Rhl]9nk)ˆyGmR!("g%>_=;-?}|'}WcHeݬI*0HOCT,~<@QdG> T X#RV%iZr+5Wzܼ~\ttG{Cs($Y,[;;U0k\ݻ ~ĕ-f r%Uux:\ګ0@Sy1sc>zk՛|3.#N/#,G|_ѣOy>ynISAoʃ{dEʇ?f2ə3Ҵڵܼq'$vMۣ.k^Ø/hhE:k ѠyY9`5_`|+#]Z#s*h(%?}ÏNȓGL&S)VH |"'IS0_d(q~q+DIfCU %/KEQZtIJt4$ә($w__ y]lnlQ%vw{H k$Ш(EW.Rnݦ׻KwN$)d"n|;\Ytb'vOe[>?fHJi#uQ>BuFKh 8>8hrmۦ,k0n})ES y Eӣ]qxt1GH%}g1QV5u]HH(&0_M9>9`}k,͛]٣is|t]E8W_acxG<9m‘!֪ E.YWgghahr{!3.f S>R̗3)VhբXKƗK<l :]|/u}9<( 7B)ng\בQDav G6Sͭuj4y)Ajn0)kwU77XuW*ASUS@:. !\G"tLV$+pdt2w((|]iV8E-)Ƙó( դhaʜ(NfLfSpvqD%U] rq]> ۷7y,g6Uz'c?x:'yOF yRIp\PVUMZtdDPzHep\D*c2b8`]"cHYY[QΏ/}.`4?΍LJ?(\ThٲXMt]S 8R AD(.h] 1Kwo5yypo.%gg鱶騃%eMtZ=bc gG:/Y{K^IɠK[3]NK߿)R9ڵ;=+\!R7H WRՖ,qN2sGnLX#d2 |&**] ʌWiB48Rbu Dlm %\:qD̉:e<1J8DZ&ř, #+;[sF {[xl:H+v2ˑp&sq}=CJPi3FYI8z$|7_;Dvkwч]P0DI"VT@RT)iJiFYT mM %|1*㳾g[c6Yqxx/R*ΎckǬNR% }31]˜:lA{ْN/k?UV"E8,KZq% $u<TE^(2bYp<)Jt$ǧly_ϟ}L9/F*yB! DE" AS)V@d$<#IY֍j08:{rjBePN nt|I|Am]Ulb5YlK.OOɃW_|/GLcs.I9;;}N/&GK7eXs.O?b1?"YUH\b،0XSщ%ň;x駴U]cEbFDf$fdx^ch=ڭ!FYȷ~ٜ o:nvgOػC(9y~pcUt"K7$L[1z+ΏgNV$ ld{o)%EuYp>֤whu(we̗.o|O(r.T;,WϘ-U osuǟ}t҂VuI (+ )2Hzqst $MZ!}V$%t#dY0Lي%5q;K.UePNC`{6a9/"4pz6+_1<|ܘ~w(۹__5d6nъtu{Wnn2ަo4s\) *3,fQ b+9c<SO?ާ+_.e$,ȓ%t.VȜXh<5ˢŬo\8*(,k1\b:K蚴wG:8£(*PyB9+4eUC'Q2fwOmX4yYNLatPa1K)*D/ L&X# m*zQ-05R3~(w ; qY>,cڽ$QCtEX U]aL猾pNX4H( p(E'躩H,`m\ 1-/Vy7sxtyʰߢ,3vvv$ej`Z`lU(OI]\jK2dJE؊Of`F뒊V+@ך<3"4HmY-*Z-qR)txr%0ϓbAX|28BQkp^kӨ^pp /9?fc}M$+F3 c6L(V䰜L&o[\TO'N%N e=#z M &MƓ3(`4d2)w1TZ:a-MK*."͏nByvyr囌/OPK>7ъ$%SyE\E8BHUQMD y^HK>|<{)xuJP9iQk2^eKЫ:-&%^4 _['S18dj,Jkق)u LZƂ˚$'cQirN?`:Yᅒ#ڝ6-,/H;[ƭ{WC^tQiYsj-UP+)tӳiƑs1YI9Lnz 9>gS A^eU [i&`w&uFY! ڠ}Lۼ6OΐcrKW!+ns9CGl]n76k5E9!W￉1%^D+ MpZ*m~@;k^4-Y *pV28&%e1$H) __aM0@TdjzMFn/|#$PJNGHסk_$j_J`zeQPW3덩cns˄nk(/G(!;oq6eU;B`UK5Ji*rnsuw^a}E mUr|k,-&iN9k6ו8E*MQh#)Ҫi<2 a]& Cԥrn߸ڰ6Y!EGf`2F0-2r:.X|D*_-DaP TY<Ki$d6fNX qVd>)'lmt4!ޝc}*b^9=ƣiolR>U"l,%4Ï<- : >Cƣ Jte%V+\) uhb.LJ'5$Y|gOЪ9^'4ʡ,`HHKFX(.s U9i%+G;. "`9t;-G #Ӭٍ]Y{!1_拊,?0b<#52ΘLYc FWtZOq1s6QAUhV)a lɧ2's(93 cib.,Řf"YgxCYHC! ݀ p5}O2l89w=iqB/q/9uSFC*c_rUvec`k n2A@܋iuZ&8C Jx" W<1_*%)_M* 6YVPɸ.Sur)PW0RXQrxr.iQװ]7INq 믐j0]Syt-Wq0y͍u݀4;dǔEO0R6ohM?[@h1Ί+ *QQMjd]gEFEMUY PtpZ I'7nT\{WxƟ~"]a!7ﲻӤksKsbV%ɘ(XMāƀng}!CE'5coFZCV<:.''Ub^Rg  ˋL ϴEq:q6ZbWܾǭ;ÿR|?,DaE|"} W(`rq@ynw.Q''ʥ׹B %wNxvtl *^'CVQ{@Vh!,AV$>=]V .Q~nϋn]9H)Isw f$85(#4J-TUJM3$+G \!Rnz}۝M*E(GR#X9P Z0wߺN]6| _'YSc Yd#C" Pmw"0]T`m+&)1;2l_EI,oRmc7P+tYpes$iCU[B_Q:#P.k3أLJҊ̧# m̗+:.Wn1qڻ0tD3d<:Gg3zC302.̨uujX:M1p 6#FDK.c {;nߡ%'d 1~!w_Uwȫ\Jc<`e(kʫ$露<?@IA]k_w !d000 :G7`ssgQ$kv6)F&m͋e-0,fZCUvD34M*MT5<3v9|!2$˔,7ۯg3}u?`lJUR` aEVVE!Uʭ-J[RV*ER8!uEFZ+Y*j+kZZŽ?&jڱe0xwHsxBvZCk]_r&eipC9zI9hSQۜ䜪Jn|d8cZP5LfSZj< b /b ȌJQRaH42Da2kU(Ss/B³f(̒er2s19JPAgWsNN3\kQ)yDʀݗw0"dbDyRC퀖Q4-q\cTASNps:k 1ڼHbZ #-QgVA ,(_*Fhc9L Mߪ\KZ- q]+MXY|?=EKQ  -o_a:!Z(դˆԬRbCr%iV|js%)8D:iVSsqI `:YruV|T}ecEpr9maÀh+q|$sHf1ZYPʡ~ U+ϟ!Dr|_)B*4r{k|kǿEܭ) b4FZД͗8.ᇏM%)1/'^EUgesnM|9Q3RWa&EUr9> nߍiEk~Ч2*!ߎ G]er"Ef> OUtlWm6o|va 0peIrn/&]0c7^!Y/G|lxg#OyG|[]]V݈w!YcMrV1ggL(Њ^ IyP X=<8F2&l2dEAe`B"AXļPwkE^p65Ղ7opvzAU N "/U] Zjf8"TSÐ0.XjVt$Qc$K+btJQֆyg贶8:>!z.Q;Qkkۼxq@e2F1q[v}x2c\i3dw.Q0Dnoa IDATP>VкC;t;C|__&V|!ve2hƵ=ƗH!Ѻ"n GGgl9&8Lf#0^Ekk>w(Y6B M x!nm\AvN;Zť*JV7^ӇO+E?`2[G?AO~[ӝyri*P 9ي0 eI%AGVoZWKj|y*=3DecKeIF`GoFҼ ~+drƳGxD*IKE=vG1_#^< j;loW9z~6)Jܢsn aZ)WK'xbss@YsƳQK̞_(g"۽r˜&@A:Gub3@L*wդyK].vQ/,r9;;F)EwX%S&x a֦iŰMme4藕M cj|Z \ټ1 E K]Y Uq],ӎl$ жFqBN+0]d4&WŲl4Qu>l5&s(w|J#DJt,V[tQQSjZZN^.,urfk#N&|&?/K~HO1+F G8%)9l9~W#HeȲݴNCJHc%;*,@xlo nl4ؠ!A ac#VYɗuVi;klo 2Kd:k8@T.I1!2aL-yă},SsU>7n?>Y`s|ut:ݗx\ګ2OWE| 0].\ LCf:rMc(J.-'!/ew{kF9i! (٢Uϟ4^!O%uaKa{wx/s&Zr]N_nw*)c8Tܺy xn>0Ð)p֫>}Ne,VYbj2>#%+M  A̦NFk Z0q =Np\0~W?=]xrn~I%b$o:?>dǍ:g?|lj]7_w.uey]>uUr.[Qĵ:OS9[;L'3&t%È7_{Kfӄ,mFGf.R"/ц,OfXRӧ#Եb1K .iZ kܺ}Gqq5f1S$jIlon}{:Ě˂’U=>,ŜŌE>3 ~[_KGܼV?~'$i{:a_JV 6P8.;8tٿ*kkY.5e;[Gof2n}˶YAB i`k˿ۿn% (Pj[)yV0pT@Im P,%k> <#Ř5Eb9w^hIﲾ*?}ۣ,p¬3m윺s]<pӧ'8&<:q 2F:.~).G woD]:xހFЪ,bKy[^]є5N+V9e>#ֆ븮F}\G]Xָ}gFzk$՘ZkL 3iX,|ӧG$cqƶkm埪i.J/9q#N۲4%(A'>VsVْy|{H`R sp{ЍX,4hֆcRד|>*! YHUlmu]Ukln O1J bskrṛO2Ac4 xDAc%~"q&r R Leҫ()(k W0\_O8m\j~0׈Ѵ`k{Gc q? Stq=CcArUp1~gyY%3Mݟ _jwbRApZ]mJ0p&t5";H)7wRhlIQoixH` 'phD'e3;X-<"KS% Ɣ* <z=*IƮ 5.5u^qxk$yBc![))vxoO%%FהfHH~?By~[Q5ִ O7޺ΏBa:[x~`7ﲽEhu Dʚ lnRJԅiU9XWϙNO"I|aEHAQTX#9vlLvbyxz19 _g{g'HkѺBۀPa:dFj!+&z&P1w0/!sZ<5HiMFW<%7hFG4Z a(±8:k^Z:!Qc,)!jd k}|7`_#Y\;"wR1,Yuu.r0d8^fPԸCXX }]ZGjLڢx@IymZ/j]7;f9 +$s| &/s\z@YH)C:a1( peÝ[7آJ&157>> Z{x.[<~tRkL &㔭;}ҥe8ٳ _/|H}3W4cb:K-Wt-eӲ.IJ }k Ӕ\]^$3:݈0zA^.YFX[KIBwr^Ҙ #J-);TA9'O%^3Hd5@]\^{9+pBҴaVhc:=YZV/ xw!u3<|nL͹Z!ŋ5PUuc*TuIQU=Fа6‘>A^+[uYplQz=|-gM*sttg-zF7n`ubA|:}őu:ISWqxKΟQ钷<7ol]~Ԗ I咭!W6OO/ek/BE64"/~>1̧ {Xax=bjHpy78:pϵ׹~u5Ξ>9^0#)cS7~IJd]gHӌ&錤q>`o_w{N܉ R:Lg+m5<. ߋѕB.[w MLtQC{霺JY,GqHc qg5HJP*Q8Nېuкr琖#jd6ňL8>y@UglonRd9u]E!A0KXaQJyX[!Ĭ<49;;\\%ԍJMȊ!$J!n7t2#Ȱ3 IԶY1SWɳK|ʍ[Gߵp"<6dyEuCYeXk0F[MȚsҴb42tkvq6/d:[ z1ٔЏZT"OɊKMf YS%I6|E\O+p~u<, +ѵŖpt+1xB9eU 5uIX) U//(2*L݀iD>B֍suzOi Eu,2`:ɹy%?[8^>eJ\4#bXi^8uSl# tBtvFSLqe9^O C%DРHC؉(m$]Qʆ`3Nf<|z̳ \Iԍ zuÃG#pv~!RzdyAQ,W) xHkF놺?|LF#G,BN!mѓSiI#,HE^,s,K۟h=z@,EZc2;:%&͂(VTM"S邼p=pc{"EU>敗E^d2J8y|1Lsrr7MTe믾#ܛɟ9slo|KSҠMCm5QWIt(5єU$Bp6jS(IQGaMMQdu52E /Ή=5It$yYƽO Ŗt9+AGqtMUgLg%2FxncRlAء IV0NtbC[! tm|agw~>NgG}l^">eYkMivAܿjYhCHVS8?1EG>백gwoݽu&1gg z?oC C$iP5Պ͍-1_aMٜX?!YqN/ɋeFCUUǥִ&P+O:u>et c@ZW4RxA㻖ˋSNNcYnoB{!o>("Մn uAlbetz1`(Qk>_ $t5EM|WQ i2,r899,3N<+\[ 1ߏ(S&xHKr,&4*tQ^ DT9y.W,c<%<1J|rt}xdQdX!tI^ky.U`-cf)EhFӴ)%X"KB'bQE*O,-He1eN #(c,,[_y-> 5xirmQ7kJ@w=Ɠy*lm+HyKK{O_>}@Fqȳ)'ykX$<7~~ -;[ѿ×E^>S s%R(&4[]^Q EC]S=D7 瀐c E754(WV6r,ڬ1F7@kMYX,d"+ KևkL'#v}jA Ćpݻwy||h>Irv1GL#7YZ.Rs9;?6TShhy5a}8}֯ H("U{oq}3v!J$y|Ŋ^?l\r) !rz#VExꫯ`hOV3=o{gW<~Byi~Ig|;[Cg9>9f(u?G\Uʯ"}%ϨKvC|D]/Z5%JcA Ib"){,s% ӥQ_\l~E[pwZy>Aň"׸ZZ8 L1 6AF`& bj5V8ͦ4 rʖTZ)%=p"e/r<`ZA|/xrSUY ;deFYBwMz#7o2X>) LQx}zMMUi$.( K+Ξ0_rzSX" p]7`n]ETe5Jil 4P뚦iHRhT8>QXL(Y9N!oX]i^%i2MJ ),` k0b-c(ʺ&8h ( ;1AbUl`9vC<7<ԥAZ jy8"J ?^jueab: Q|\N* F+\-pDA㱳9@W)jRm'O]<=y]nGEFQ tup^t|<4*hǓYCS"]BXzQƐY(Y2Ig\.<|*#Xsq<ؒ^71%yQ5Z1QZE r58 BJKӥӍc\ϡ,KdTj ND8*fsstFuM]yJX{?1[ (JVlm>#)rµ...泌Uk9X+y뜝?em}6眜9:0p9U)o||G4~<=}̭[ y3(!G7bs{s?KwbcmGTeCQXL޺y@R${\ͦ,PG~T0_̰̦KU|kP Y`{gnJBE!^}&©`<*T߹t2S!m;3a2Ibn%Kk>dɿۿwWq6itO\,o~޻QK/2/yso{?= 12zu7^'KwmUBmXl0L@XƣT%n]o 1=~?_2R8BY+tِS'SjóGӏйFgω;;X]%9=?["{} `xBVTn@䈦-|ObA Ce,i"jKhm4yUXJ]'Ef3~Hck,kt:;>;E86.^b0t0"jUDv8ʭ8~c;GC;4ŒD$CZx w Sgfryu9qACt F(Z ixn*aEk>$tS5+WdY咕E)WS.W6(k)Z](G4-\r,BXkI (9n8b͵uF 6s\\#!Q(|Һdt5gcc uЦ55tERshESA*⎇{.uR*%s<,ys6E1C8rKk>_y狤?n#τjK3>;*<!-XWa%l5*pl*#+fydU2*(ݻ|ze׿&'xd/yp _l`>Yw)ƖXi8Rc1{6iw[(3Km痬KD) 2puV%к{v& lpm =]$hT 4G⡔3X> jtzRT *p%w^`%..fL&S( ϗ9E2s;[_̨&x̖c:iz c5J 4%Clxv0qbGu6H\l]~ˋ%׉>iyv1b0(#(L#F#=AU786!%y."@ݖ=$ \O%@P?1!Xݖv1/v]P i^LX,(!=0` ntBV74 PrCUAYhm$]lnl8.Nr^'䥗[e(iLG.]?$ "nK~Z")Q".y:u#D B`ﳨ42c4qzulyEe *C)ɛo"Kz1% =E3nrRbh}+ ;HS͛kpIBwC՜e5v789}7?O8{~Ƶ\g7{]x a'/}[~W@luɖsD aQ)1VSU9ٔ*ж FHRU.+XMEZeRH(2$- ^~oHU5/|6`=lKv_Ow>O3 '!)m@ÀN4|e[l0$DQHjg3gɝ*/ھM SuQ޵y2& hTvj B\еۆ\SaP*8~oHNgHiqp8Mor1 YsZ-LpdQD5$da锥]8 c8.=xw>|d6st +N~4:VNs;&=$ZxIQ|_G,fΚ. \gF!e^:X)~Nxv2]06]IPΘ..k2\"QI Hnlvr94|X&ܻw'cEclPd[ Bu,B4]zmmuǤq67XqPEa|$v۴("+iBUY uC54W.i"PUo9C<#s SkbYZ*] 4%rEixד4/QJༀ ][؞GǨ UQ AVDq뻵*,jo\9!00횅5Jh\o14ZA2.PJ#O1ٽyP,vJ ^e<_S X4:}*is=Y}tz:g4%AIUs&QŴ2f x2|d^rpv,!maf RdT2Ŵ0C'eiJNNdG?[7w?oe<'&<)|NY&Q(ij$iNŦ!N 9 !hv(q}3% 01u *nՄ8 ͦ,^2TY&(6FTyZiF,֊ xw8x ]VY/23>{|nZ{Uvk4@,h5lhE fFtUUj*ruNYoꚟ}3:ES!^jzl $PKm ۔fcm_.mrB~t?eJEJi4 -*A4ݮ=tScsk<*8=2Fyg}JܢW_eXsy1f9Oy]pTU*X5=67= C)דJE|:c1Iitfru}Q,]NM|3 wp< zAd^^AL X.I5i\\VEr%ޛ|gXI$#9YZϱ] Ƿj9!#4?lr|~ī ]d [1f%a~&E*:EYI%QTquYO˄J8͛ z%V՚e= ӬAM)#|Y YLא2d:c:edg9Y^Q(QV9[i1]9y8.RP(S,T`MP6.Cs +oPSVY Qi(k&dʲ[&R$iN3]]# ͔L M_28P4\VC'gVWQiIRl99@}nz4b F!@7-0D* iH !UyVQeFضY-8*]X-<,s@! Աl2b2M[z|Ϲ`wy?ocfMΗ3 UPUq|c{ϛr{;dy4a>/akBjE=@E!ã/BO1lNz^ed)đ0]4aSq`6B@U)b$Kz]Ekj)2.wULL` ,G-[scw Xa:nrjI .A+i1lk8Zh~:,OOP.yG(I<5 % $֤tALޛ\?CS{'OiQ;(M J b.I 4E9dHs%$y@_`0hFE^.(I&Z3QYҧާa{%4%L IDATB/>'(!D!A:9R8^DNJ4%D.%YQr=]DN6̳eDV-.>"HMC쑥 M{{ ޝw)RVsHP %d=:YQgKGuvgocRREӼ^&sƣ%ei̦stF3]d0BJ<-h-px:"c#V 0J4?>ON-/jg>=$^Aץ%^a,[=嘵M$yJ!1h:Ӛe(8>3n3 M i&R@eiAQ.%Gf#9NϷzABae1_s=&jZ^$&ihf"cx)V"/( &Mnl4}ݻOZsc7oޥ( C)+4MgRI‡}DEw892|b}k=FklmB'KðllY̖3o8=$W^ޠpmˌ;;?~:_Wm3q_{L:-bFF.W~'Trt!(UbZTln2_ΈҨLD}9Ie[tm*q6[utpyaX,3#ˈ2Dv*\7jUR)UU4Z}dM=n~?yjE$g8Tc5տCY\"4U4]S"G74ȠRuL|Mۈe Mqb6eХQԄF樲qI5< Krz1"s{llvʄzIƔ^ M.&L9ICVTzI) M)1 Ϟ=zzjŚ$I(Y^/ e5MPIpq6'C ֫%$iFg±zCw6[q[ލ]^aot((^$+ M&Z99&K gk{EUnji2\YHln iLf-DE"# 5CZnm7 G?)?W{6⧼!?wW)ٲފn{wdikњl S鐦9iR_3f9I^%蒣5er*r8U ԗ)BjZl:$Y/}o~]Id2E!wk_)p,K>חgn*V˲E갷}6WYrb:_s~DmW lM+M%$Y;tzmEH).tENdNMx'vS@("U膅88<|V_4M'Wtt7}*ZR9gZM(@A/`قu Ic$$Mc4C15Q UBRAaYPU`[6MYb=(, c/9y^RM/P+֋lIehd0vl4#J$0Ӭ.mZeA')YV!I4&vs\Wqz %eQS5 "fZ1njx&ʊ, ,&N}.Ohm.'46 94jw1?'US8 {:_x˫ 1IJ15Lw]ls0LvEbkg8jq[>3.NJOH!J S'T*dooW_ϧ| v5M r!RaLP A!ػn3;]18:^r~Cz13z&^:E7\J2lmx}-`8lSw'ϱ,I<~gϗ|oGst|V=!Wa|YnJؒ?'ll *rs- 2'gDl`ϓ8`wkfcC""Ζ(b4Cz`[& P*F0\l:"XJgEO [#EϮWK;-~ɏVw_y/" X||Sw6Y)ٽM*&2J)hA{C޽CBlJ%Y,Ny#L"+r,K@34:U%ˆF*$/,O@ե]7Ѕc5X.$`4:uu*0 J4vndK|I^$PA;]T.)sxj@3,JWIA# 4:ZQYj*Űq1gb4joګX,Vs4w7߂J犼P8~jEG@nm{S4 TްIExrM^' E /32 ] @E]*x:InheZuf1+ ’V:aKIY,R|[㥻w ֑0u46BAQ?QHQV@dE)[uUR5Rib&D R#ECE$i̭7i6L'JF<$YB0_,DaP7jJ,e3@ã'EAIxU Sca:(TRɜ$ N |i:y&u@bU@YV4&7vn۸KDLnHnitZz[$Nvr)^U:ɝ۷MJ6qF3l4!N&c.HcG_ؿ34enac⨤H+Jt1m蚊CRcˑ\4/D)*ggg+xK_?|m~|OGq|675lD#CȌNaTI(f9RhT*G݂|/($_CdE^䨢BJ5R(4 p[yd]*G7mؘ`:F{qHҘ*|!t\b6cxۘMθs.kh5cZ6VzAe:M?%! σ /gݼGx4ŵL|FTFg (t6k0MJ' 2RkqRb:BI$a/% +^y|Vٌna՜n{@Uf6gK4:Í.*xGesChD3[_[FtClZPdeJnlﰽy~< ߥ="VL3FZqqxr{,+y1M&KhMW3p]]7eP%v,je)!cL˨Wc H^,W 8&K z={i}aL\]ػgwo95gϞ6x.mtrz]-X/M 8=jŚ<Ò7c>͹1oZ,ؿuDƓxνS:w[243^XNѴRd ٽ-UHa;MSYac"%A˸Bnp|>eo_?i/G_;7/GbNq[KQFt6&/ Cg1]jG\kc*RY" u=jhZGωk`;L*PB7-J÷uX7T]+.>TMT6i&S I lv^Ee[7Rfie;eBmZ͠4a$piceU+2G^A) Ҧ۹ T_=?m,SGrRUUv2M~NIe6x[G+,w~_>=NAr->S~;[mڽ4%[:刢Hp)m+xekZIl )E`:*I|;HN4#ӧOPFkz-@tѴhiKns= nEH,q\ \RyYPgLڸ[ٽb̍͗"CkQV$"g/>F%aDU{7;ԜѲv؆A bߣ1XE!o:B iVz}{n]olCU%w2-gTR_~~]~<`6-~Fɽ{9;;ip~vL<,S~!v_d*K*4&qaX[{xpy=>9gg 8'e늆lRp֐s`x\]px0$K_~cta LiHxG_-2_#חXv9T6ilJvy쐆79;7_磏3^էkl  *\MӮh ܆-I4AYlon&1q@^:T"&K{ݾz_?XijmZOof%vJ3P6n E:X]5UKZA3^ey3ss:g+tUW*vV[M6Em@$,|a8. C|c-C۰. D9uaA l07ϳG0 sl@kiy ,uZ|mR6XvMMТ<@H,ـ[/l_\\7@k$41 _\A j JDrܸ?}37ys]wll%n\dz'xEͱpP.67nbLNO\$E$diRͦ,fLGs - IDATu q: 5C෱-hs~:pqBm|g! Y_PT%FJҺiNkU^5/A۱^nA4 8'Ė6m8lh|B^ƴ:>l4 y'Aأ2EAM,!apmԺղZak&l7W:bRPPC]BCk5 m :4NYcYj،uF 3R`.v)vش/ˈ$Q^da"^mu ֐  m05e!KUHZPUISMYF1X̀UW5NVhMU5(sJPV1E6BQU)E]LEkVжSaEؒO ^7$;N$1i{MACBmW2_$yc7Siir=~O ]XHp?GhM(a[ZH!Eќh? +?Ty׿'^@ܿqOo)p,V0Z-5b#58&sTѠ:eG4CZvs p\t2P`9 DfAM’! Yv5X7#\yf,!#`r]$ߦ*}Ft_k˵ p@@L JVWC' },&FP1X=IƳ v@񸰽y B1O?&XZjƛ|1M6Wx~l~mi֙Ny뭻̣pH*VW6PܿOexm;$I2^@HqBi'%q7vJ 9O)v/du[lmRUYsr8&jdsg`Uas|ToH%vCq(aJ?kI ==ovhش}Bq:ln^(4ar>="ΨՄ\K!@lonEgnOWcslW4Zi󑖃exO$h49 xgK~#.βϞGy5]\Vefmm^M.\x•iR<\' B=}xpX]*s>}B)ecPh_{7IaUU$8.am<"-,j\?UU1OtPF.Ʉǫh Ps(BZ*]H -Wy暻ϾL]Wy*b>JjB-bd:`1\Zd:iH/X[&j?xBgt6- XYYnڊyIH\i,*,'BE&зUg9TeI^ha\cI 6 ʼzxe9,ő90PdeM朜i.},IhDڒbajUkp&>i9yϘbjc#ZKDQ=& -X)2Ni-KKdb[7|2/2%BZkx|ʉiKwV`_OOK7װ\FJudEʐeЕfkmS|ۢH Tm-T>*˩Ɩf.Bkhl̕Tcmƣ &q$H0/B:#p\EMh)!,)ɂ 6/R!rܖǃGg)IYQh(i\rC?:t4;XB,#R%:UG1KËk<}eK)٘Hmz!eI->#aU|\Ri:݀V;@AlmAۦPֆ(J9gًgoU붩늛wnJʲT+d*N"bP˻['?9;6kD$.w{%~_!w]&Jf<ҥ Wx uNcr6TtC q]ΨTIDhKPC}QujA׊%NA qH!eck`4[[vY8'3h 'sTԪBRuDo`0"(ÐO9 7Bcp෶,A+ˊ028<٧r}3?KZ؎2ր ƣ|)O39 mڭ~˓(RMAw6k:g/ecsshDG+F17ޟU:ܼ~Oz,RG3^6Wo^ai65GImuzUyvmVחy$ױp]'.+C2Mv}w.gw]a␺8=9er._& h;oY +jdd:3ReA򕯽ˇ CM^fF}F1n˲,9nHaUv9q2n%stpQB UJ7v9>tQ m$·]=̰fop>Zp6KO5_{FgS\}UG_Ѓ?Ͽ_G!u2ti<"? -Y UBB m?` G36̢S*" ,(֌ ZBՍK" Y^6E(\FZv#kG7ۥ!q4k}TeQA@YDќVkUu#}5I }cc,Q`Lpb:;GՈF荪Fcؖc9cQ&_L'gUR 9>IS~ )ݰD-R vڔ^슦;!gE (֊Qhܪj(gzEQbXl~MZ6v͍uc: mw2"(a}}]'`u8<: |"gDn0.u-(ʂd"]\7]a4=%&1;V"9E<Ϲ{#NNfx'r|xt[/ Y۸O޿O/uF],c%R8M 0UMXv)ic97 Y^Y'M2N#70Cg~ %ZhQ5|+w/BU؁q$EVaIΡ~&Ϟ쑖1F6 ȩuҼZLm`gsGdZ#eăGc@bqJkRSU,0Oe9saݝb|>!lyh!c dEO|˒-R UƵ+1"p<,,KH??/e Za[q4{<{ã=49lll`iQJ)MSʴ- Ϩ+p-2W\Fpu~}ģy<}9ɘv|ϸy2N3֗zt.<;/EF鱲z  Y2% N ;}>|]X]%qFot>#N3yܸ$o_섳~QneiPSE H-хxG/1+}f,CQexv%:QBY[AqK-n^:RE֚l04Jq+,ia[MVD FAVBEqCda0\ggZ{-dsu.]1P( y,#"s\cye @C>UmIEsaHH9Ch>mdv틗^ v6@Ueiey*4EVh. lF(6EjH(xXsKoR ]?ﳾ;X3fcsH/ ghkѠ= +lei -PUŸ:R ۛ1cieb PL#< Ƕit}$cyg4#,%j߸F,WnZ1-ټDC3Zvs.ESF`Re maR5 I^i4!|:i1&#ך3?=l>Y3ǘ)4!έWӜVE(,NMz(x@,Q^_mKk,>X ts&S񔝝 ?l1%+*QNZ)v( ͵;,sNciPeM+t(u!J),Gie"cMD2_pŌJV am}c dKٽ9'1"!/ ?4Ʊo\`6;#BX(yKh^} SA믿ʇ},6_?m=~ɏx啋.\h#E^TuU,Sk"MrpyeeMcfQ`0DZ~Ц?Ɋc"-e:v-]iGOִ:.\ D.acs>9GcgSbFkcuUkȏw\9<~LUb5eX^ Nfr–x0)Ȇi9&P`\~+7^#iϦ#|Q`j\¶%x%QYyn7ĵ%uUknx!F8%NrO$Pd5p,M.>۴h΃GN/"T]<Ǣ,rlː&)6Vz2OH 4|ƕ ܾ{ll q<}³'_ ɧ\]auy@r5d4KBK<~$QײYb9!ш(#|E!ZUHBG deƅWn]Rd82[Lt'g'DiL&G fF).)Va$,oDyV5~󛒇;xKmHS?k#riz(F`II;j7WIAa ]՜ze.59å #5j|RڌGsl'V,Z!4=qpmccmK È4}LPF7n]DZ]QA6uUQUGN K4۶ID0$ KTJpo IDAT>i6["PEE$|gg{xNKͧ4V;u\:a* h2#J22]W^lJU8Rt"9n9v ʱ]ڤ4HYp)g6N@3'#Ҭ`|pqws||Q}vgOӚK6~  ,#z\&}|/: {#A1 h[ =˲qA{HB`p<`,'{Ϲy&~b% ^a USJ͉sJ`ن:mFS+lm:nKIR/f(YR!E8H-Pڧmk^)9^Uh.Ab[tI \VZch95hٔV勻3_XS=x O?aWn#Vp^ C_-j\e65Lgh4\77f (gg*qQcprq-dh,NN999 c$YZ K-6/np ~, Y#^ЦDA^d)ll\b1ƇON蹬pzzN&gؖdmu3^}Q4!-X]Q%ل.^dusRLUbτ,ry6OcLaJy;[l^gyɣ{qD$eAQ)拊,-246=og;__׿#& m lP 6t{q4SGZSBR)%8;U>667(nZjhYc!$E!A(ky^]lekdaye'{!-RLx>K=mPh` R`&yqa\,"GcVWV /H)y'<Ue4#K]^gʗGnѪ୷舽S|MGgSvvLД*wL]a9vM{+؞^ae"<ݸl>c/o8ʰ8=jrkdEAUkbu_||!d` _{8Y^u:,Jha.mI{8 nDOR W/pz|ܾvgʚU&9fmGX[pn/{\ OΩWPg{($GG8N4O))U@`ܐ4$˻<j.>x/'ahG!,%AX ,E,9^-***|c:pUlKZõ(pi enZhlFAe6!v-&%=sNHPq\фT"j`͵6 샱g$Ib5iVS=t-عM'LƧ̦#V!<.]` W J>} I5v?豽Kvp6n͘6v??8DY,S5^2䄡i76c$94p2xǕ˗ $nHo&/6R\let6'ϚeQ+͍7k, +pe%;Sd2)vWw5> Ylo;޿Sq]~Ηܺo]3,[@*G3Tc(0x-)NAjJRf=ey޳<9sDNU]sWUd ]`t!X6 ,P/|cؾ-Ȃ$&EI0El]]5et=k-_dDo>O6|ZH.&;\)UUb^ кl C6Y, PDB^NDNUPT%[[U %MB=pVYfmU_/ا* n߽:nN:?(F "s0J fy U.gJ(x~=.FZd)^ 4J U:F;5VaYna\\]RJ8|dazXAAVphBa:A;w q*[>=$2ʤ6Lz pv}B%%KK-8::f|1'Kph:o >ZJ:->0Osz^2SNN!u{bB<>I15(^x:N,*^z6~Zlm#ut~p% |ySm9(c2*jԧ7_q5''E2opϐ*N+DIƐ"Nr0l4-Z LCjDCD)UZGOO Ia^W_J_Ka[\Rs@XP̒ B(;{ˏp[ͅ*NEyynRTm6*Wh =sݻƓg\Mf^PW4i B M\~/챳NY|?$K$o,s.. {Tu!/ݹ޵e.ΟA`]tn޺d:%.ir~~x2,(UsxrJ ?h3(ʌ$]pzrL:|P䂗^rCq ܸ2؎͝;ɒu^2\]oszrL^嬭v])U&QRrEلak;<{zHi,S6V'IM,chJTԵ 4 (@ l:U 0+,zoC40U`)f<}Lq)2E+xlo}>c&%q>'+bLsCT])h1 \aI,;g)2f y]WSZ6B(u(iBgLf%갳yX6WsGQ1VHF5A0@"Xޡ׺l O|Cڥ-lKd: Ʉ @]KlCPU5 "S꺦Ɛ 300L%EpJM4(qu{,/30Fp=2q]u|JY(CPՂ=ւ'Cm '/W fCk$ؠH5c:>2h5NJVeƐ`ZU^[dyNWxN@Z0U]"sI](.O.xS={Sk(aZi[TZbYvat|(c0tȒM:6!H<i]:)qn߾˷9 :ah|IQgr[²{lldgYTYB]8 +Ld( Eq\67)ʒӳ3Z86R(]ch k>YK((1Y]YeyyIͯ΃G&8>9 w헼r>Z֘}2pm 0Tha 2iqӶϵ]dUch%tv=4#)RQix+K#T5&h2UـSBfHZB._{ۼ7uoꐦo&?#3^#ZT`8CJ Y9S+ӤF%-\0YdTڠRSINQI̛o|@I삃'%j~gO?C ZqulyєNu-qlSTuj]Vۥv)넝O8$.~Giײba>:/z岵*'_XE+VVVaw_l}b1g:w:蒣çiJs?\\2LI'ia*Mp[|gEė,dl@8eU#,t>\&I* wXpy1j4+/Vxtcg dR?ã? 7[ؾ5يQ)Rh*tF'&Ե4mL =˃eq&I *b6t|?9&Í] /Ơ{/{%c״7h-AIvl\6L<Ŷ,T8OUJa 42PbB 4%X.ܥ,d).0(ȳ5("M"J泜N{=Wv} V7H9w^"̜ 4Y$c<ࣟ|e5g:4j,İAQTE'3rDXY <%[d( 8><, fӜɤ(+@3ut(]`&y<Nea ͍M"/+ʼCeeܪfiy| J;`["5 f JcL'$`spE0Y<}Tu:k?b>qb;}̠}u|)[{ p k^DIZ$hS0,!LRj<\#L2/R?0:-eLJ.O8=?biСsʼ$$ ,"ciY,w-ڡIw, yvpBgdy">$c$(l!LTaxfqR r8Ťɞv]OXyTEk}.>b:SU5됦1w`;çdy;o`0 [2JuY[O?T`0XY>C>[/Nϟ PzE<[^۱Ӂ͕犼)kMU `Z;;tmIF]y6~v׶11,p\K(j.(<(N&X$՘ ΎNQ&=Lätvٻ5峵OryuY! ".`Z>z~Gl푤3Eiw|,G1]N<{vŽ{oQy4g41cDZ Ð^hDU`!$R 8"+b"2maߥ Ec04q2oOfsC-O1]R bQGN) Ӊb Ɋ7ߢ}`2❯ŏ>q6F? uկ G="O;pp0,Rn 0j d*m5ciJͶ  +T&+7XarzA2rI4J43ðR*ociMc_6)Z`;>ek:K+$DǧǬ4}>L^j*!Mh_V(,uz.|SPk+IYĬ,ɳ$Y2cQ-6;}7OFqE)\sh5Z<|;o,{/w?T~8^1 kK&,-Bk7ǜt>A?x(qtxE{ǧxt2'Ivw;لi;G'c%JE ZOqTqh𵯾pf?[K,OӘ<+4q v  VYZZf}ç9I,y7 ل"f4y7Xg()/J_k *\˓g0L,--quyE&ǔU)V9v9EU"ŽWtZ!0XZZ"MTu sϋje t]b&uUX eT(lN%v"8x|D(6b|o?e_ 8ѽeؖC5Ab D sl@9J6[:<θQBPI.B?&!$BDLMOFEb&Jah@jMUK432rP׵(2StFxNUe5bQkk=ZMG& P%+т|JͱlmShE;ܻUF1ń[;fS0&Y tUIUxCDH5$iAfxAt 57[j{mfcc}`v7`F2`7KS +@>>ZZl97X][Sp Yvi5eTE{= <\ߥn,dY6L\Si0p]ҔEm9$QւN,/#Es0gFVW\FE^QDEk7}i:m0\^iQud Yqu5E ;} .*++t]H9iq7V77ӟÇ1gY2^" A*vy`4k77I(QT9ZAZZ &i-Ҋ%4j~?丆k}NI%KQtSZUNY$ٌ1)pcyES./Y1:'M5|?4yoG(Ҋ^͓'q< v \[egc`+\E8xA@lp}E^ e:]L\\!O3i!GdY0L z!ߧXTN/W .sLۢV)hpMaMNAQ aaؖ|vlr"e1d2>g0CB Ykw\f'f+ Z o2_ՠK=CbAUWsx K22Q"\^h\F)̰ln@4; ׶@tC\(SQeV:-ץ0wK|ߡ2,ec{,\u}G`rT^{6[{qeQ+s|td'}m ^u .ܼ~, j1E[l2B<\H%{.+]^y*tFwip-6VUŵ!!1< f j 7r4颔Ѩ*E0늬X\BJe:K.!ݮ(SVS4(aoO>CcP5H |ymm 8>0IS٤7^>IBQkyz9?wo<9}[x04l>/ȋas3A"L e.hBYEFCc (QF(: YP)v*Eg,-1Qv0MiZE:kUI%Z(1.Ei[̓1\xsd`KǤB:&/ep6W08zFV3=#gggi# ^&>Qt0n7nY9AΩuEZau//0-psl4GQmZv.3L"KG blj*s:c:&R5yBu "\ Pj}lMfﮣsr"`8&BdaHrLǠP5^7WVIy:|rF)k~EVv@(s-X%.(HqdEUh4c \עjmw&\T5f;eBZHSV)u-,w֤Yt>&Ns{L&KKTYcJYgڠX*a8.7Q)t2bmuݛ|~!ydyT$Mr> Ķ]J (EJ tLR~W~1F!JbF%;MNH)RTuEU-1MEQE,+4UU6{oq>:#/) ^SJU'%w/([/ʏc6xWx%G&~ Y|,k/l[^['pmm|BS.X^]%/r|?@ׂNG^`P9o#"L"WiYBƧVjJL>I:')b"a/uXii",*8<>" ATƴ]6L#Z,H7n^,g ~u \O]LHC:k'TZb4^ƐV(O#>giy`1mJZrmNOǼDiMQ4vf1}C7xO+ (낫'\.yh䜵i6! Jۦ Yo2鴺Ե4ӆ,h+؞G\̦W)|8:Hy79::|+_]~c~~R\N\]ͩKwxŗO>""ql,C4Xbt9j2%ISF9Z;ܼu8Y^p1zňV+ NG$iBW +|a6Y ,ͯ%SN89.zn'\MbRoY>6ŗWNpl@$Eڨ$ &2[ĔuLℤ(1REj<4S eQea=n|GhE YJ|$*C b:T׿6_~cwwkxoo? 3eyǿk]1ZTkvɲfnL6G%k45eN}XlmlFOsr ylA 2(KU;* o d4 `LZ8M;6J`u]Qy\8!,4#hh"MSj/ضb:1P9ހȊex|Vƶ+)I9BjI͙E:b_1)D3&1QvE)Vri̢9Ӣ5xA(Sp~(4N,u̠3aRFdłHe6A͛PhQ%cP523mRYI+{և[Z` kdʨ̯(,_$3"As1L0e]Vsp:!+rFѼDV")1CN?`kk/ɜvgksේSR5ni7hQ*(MY@H60MI^dh$S0pa" =Lyvy {/`.O$" <Ya 5uR1 W-:!+ۘIƽ_1 >cˡ$(܀Ase>I%?j IU0S-.e5TDZ٣JƓul<ű]V\UA^(Z\˱PJ?W|7OaW?>Rd;o1'Gd2<4/ %9xŤVy_?wE|6szxAUJ JAǴJba; c|_`Z9H*G!IޠV.++[imCQZuc4GDQA^* \&N`xq~y&)%&kk;4Wvv1-Rt-4O>Ghեu~}~׾믽Ƶ-\3G9BȺĴl\%M:Pa'{OY^_IL&J՘I Ke .is|vŭ_4R,d k7.g AJ@c߶ m2g z}/.L"Beڔy8E).0;<{pu][^zf){>;spE]%ϓ!Y2%fg9Jt| % )kåț\i' ./uQZ?ԽWli~]yUO}:wLwO7'1"- 6;a|cw  2lDe#L'jWZ9kb= (@U! !*t?z-mThN~,w~HAl,$@9RED&ıT)LLӮzpEQ$9 0]5ZA`1gEd4<&I"TnfHEyE+XyB^)P Qz=q)2Hm2Zwb!)J'iR)\&+ ,d:Yp]J4]ǵ[$ b Ui,#c1}4\ClϤouV*gmA`7m.3[aE"C%nHLv.Mp2aT'uMtEWCZV((`g8Vxy 0,3۩sx(, iT4ɘw.nhr_/x7 ,I TIZ|/0޹||򌝭>圢PtMLd:ާٶ9'0tUiQDHMpΝf<ڧ,b-MjغCTha{pE$ ֚X(g*DtNCvwXaD(R]a]`.*Bç!ͶGz{_!+'լYt/BWѪՕE`0MhrcyEw'.<|5Ç8Zx hcȼ@I49{fgD$1gpTk/\W_>`8"nA,y!/7?)Q3-ɕ pf4W^7QFU*n}%4٦*6H*bmy,V!e# ^`:qCۡR+\0%ʌ|q5X?ggo8ZqؖKx]OY3_%c04R>$R(KnRQa:Aє|].v0=qչGQiKkBdDa( KB^01p s :L]l"*A`H ?ut{92μrc9 `Nh0J,ƳҤ湕JخϿEE)c $Jlӂ0lm"W,e6hMjmW4EiruU1=`?&Q6f2iYO͝&,5hIh{=Dj:*)]7фĵ\YT>*р$È7^;%+)%g% 4(TĶ DaȔ2pm Z)hMף(J76mrr>#R4B'S!hf%둦PaD-J4hAYfX2YpX-}Lä,+4C *PyAwCHt1(KRbZquV#2gG wY(+ 'SZ6t:q䐬=$/(%*escrذ`a;$I(M6 z[aP÷-AUh4ʥ ,+4l$BXdC LsZa6xK  sΝ|9wf?=ӀTD]<06Oqi48<8bM.<.ݵK2EIK"K+G!(*(%RM |4TUf)͆ht"Ɏhu]J dHh5L4J,"rL-CUJ~gAdtGcde2{RrtQ^V# N@jtBiu61YB34;ٛ/u|'XfIo-<}vsn˯? 'SL[&`+ ř;ܼ_Nܸe4ŗ?C)} )-><~r)ÇKtM҅l XXNY()<tZ)UÒϞ7JV?x41ns+^ {&u >.IaX5,[JaÙK[lnuX.fBhNg1Y-읻7ϙ,?I4şOXNJ5$nFT:E#@evx^IL[_ۡvN $-glnXՒ&Sħձ'XVWsN!>Yq`c֡K9Lf1ɒݝf0w8O#5]Ɠi~";t{?_uퟹʭfQQ PJ;Tel64"v<4!I L91^**4p(Za"Ъ [אUIY B!(@ cӧ^dc q^0_͞ ݘB)(+&p,@jP%MJGL)22tlIWzl8M(G![qϖ V3LyMUqMc.Eҡ_Y!* ]T*U), '#JU`:l4OXqTy$~*)UeYyA5!q%u`kSs%p&+?ƶllˢpH#V%91i^RRJE'$IDaURH4p5\N#0$Ijcd^׶h4dY^ )k^YpLV8HSEY׷IZRV%Q5x|RVpJob6#?'3\U%0HNk;HS"bi>] ЉbA6y>Y@* TmXG},t.\cۢX#V~Ng)(pOY,f;3tJ*A%iy *!N(s$I&3\O1h6L"0`sQ$!v,[}:-f?Z8:IjlsMM\D !?7,PJGe>9]}{w& 钥^$#?O|mT,ׯp$IC^| iZMx΋c)EZu(I4)@T^Bs`k1]t iH,'slC1 5r%`K Ml6 *Q3pIhf@{M')&ՊɲRiF^{]ZMrȠ=O,B7,Dу[?x H8.ݻhG,#== ˘3jf !A8$&ᐜg"Dxg:~H =%`Jd*bFCWD9Qp ?JPJ ^&EQrx4T%i xpP Us#iT /_ kgm1UӧYsӴVu]^zk[=nrAL'dQ{%iYϾ3gOS儆gi\8gMOx?' XA`{g r*NOb8q)D\tgxMTXE~Ԛ̧'Cd ܅m^y:18>֞~ H~=smm4) )Y:GQk EJҌ+ϣPT)ihTJ iQf-/\(JOH s'Ua.I?BBh@j 0;N) 1A:=テE467{ALqDipbR9-{z[{>B3 Ƶzlm^d'r#i5M\K)Jm޿jXɳ|EVt\rã*QQ*{g9w;"LLdiLe y0 2=e:@C 5)uMN`nHf|EYT@%" 3f JL U*"P4泤4ˆfe5\ڨԱ=J4$Ҳ_W~ K3p (,2Af8:!J*%vXۯӗfI*LAZ)nx|}_߾K3*g{k ) Ld bvv<|| 2Mvt=lak4$ϙgQ42X(CӨiw3_hzZXM4)RE4#+cL[C%c:jdvb*0t@  ]aEQ(4[ 66f!iz.Qu%?c0q:Ҡ<ͩ+t;ڥU,#+lw88%I]밵g; 6̰m8TC6E@JR0.->o\d 99lKJg4/0RǟO99:`}}iH4M(/\s=\pSQiinhw4=()Z9Yd ǖ؆ABf(HvGg{w Ws㕫=988f:[1DɪvTK{!eJ$NbI$ YƇ0ЙL9BD9h5l ]ɥ{X&lnx Nrpt.p4bHx0f$ۻYj5aWsֶ6͗lnoiptTw/?DL.\Qlxvp@ c}̭o>HKJeskɈ/_/'B*S&$[xN(Xƶmt;e|^Gawǣ(I}GnTBmɲtD3,Eʜ8tRT $XNh!BjR!0"sbb5.@ݚgfbHh;4ZO1\dm#׳y},f6Qrnf)h즍۰BacXUdq]4OnqNx!qkW8X;H0Oq[.GАe,)[,Gg9}f,yޡHPex1"XC8<] &M4CQ1U\(뜌w*8yq4[[DqLY6ȓb.IRTB6\z gK!*ϗXPfK3śXf|_jB 0鶚<[qtV8PʠY#~C_x1}?+JU1kk;\zp"8<8~ i8ȧf)0d2qBN_J%p5i4qJr._G~ YRftaD4j"V>iyUI/MRctSV znV.BU.96%J*b_TTll5uH; #\.峏?B7.O?su[Opjaa8ۖ),C%k!Aj氵EM!ABÒx߾2ti~,#= JÙ-C'P*i!DIEAG]BÔ&ILpZC*Tqxrrb>/#AajDb§*`&ۼp})oi,0EB-YIb|U^XLD@H~`S^~ ;ylM>՟q;űL1nk[rY0(M@h# IDAT-{O>FP Uƀnz{3CmS)璦ĵ[J}bBkNG^*N&c {duL(eHh,nCl?fx2_잺Ȼ,[b钯h`>gHt c6qvW\E:jA&WNQ(*; U)f1HПs5(?!JNXeL4Y`i:*Ch:ni{gNaZ QHw1;^޻/0r5*W[Lg#ta҂ x+}WfgAo=@Hu^a: }@cY| -+Gj9%I y^UT$d?@ LfCJ$Y%]4YTYPOpM 0w|MNO sj@:%Z^n~Uz JP<}3̑>8ңm|qO^~Gpi7W' f8O0S:$ LNNȊ!vlڍ ]xN )$P,@QZ &Ʋ$E՗B?bC6pt,i f}ֶ f=0Z MbBaxy)X#"5= U%c2׉"3zinW" k+Ӕ T b9*a8zD|QD3bI U) !ì[)VbHiZ h,,x VC3%x|0" ,iG Ns(r%BؚD0,W ,'R$g nJNA&Y | i'9iRRe&yc-\1O8<~dvLEAxNvD6x88G46X& #,bgg4)6{{)W{βc4;fx *XgcmTF{=GQVU1_ SQekdLY*.?[zr>`inEqӛs4d42XkgaBiS U*YhZI  ih\q>(]4>է,C(Q3~/G"#S^|UƳ!~AxKؚ?W/_|wyVw?ӟr&u0}ː:*E5 iXM&M ' ~$)3Eio( tRH]ftlʍWɟJ*K4Аu0¤&Dhhg+:iPVe v*<: ¨v'H=bg3 AdIiiGJXqu[&U% MV{/$$I'!YTVs0=.]ćf23{k$Ju\zoW݇dY2\K,JrZJ,1Fäfd4XM0mSRڜ=Ϡdc{DGݷOtZo14lgOst0 4xoZLv:&?ca!$UN<#/(#< Ov9{TOJffEh:TQ95VgO)7-I&' ХkۼUCV9^ӡjs<6lkkLS66L#Μ>iǶYˬ|wqfׯ\b)zuDELjrF')J|!{=}i**G ł8Kӊ*!O/^; B 6[1yGzͭ?lwutm&u9EH!ЅVgVBi`yiҥk"uд1BPU)d08ͳ>"BlL\aaK`@ (\Riu@Jd0%I>*-~c&FKZ$Lc pd1W$ILB7t\W +$fy. ãgd %itl!e0.LfӶ/ 1 BTRtnY TTT%JT|!*Pt.?{L4F(bE&DiDVF$*!/ G1<_*. )VIsh2,P`&fѭ8 ɪB5RT O@V:y&vZf~(ZCgZh U)4 ixEqKT&hjcX]lKV8Y0<9FMB9]ڭX,,3*2JZ%i4Z,X 4ܽsg<|pj0+tGg)%)ހNeQj2'lE Q!ku4c8:AA=KKYͧ\xUi)JX[pj{8(h6;:_B%◿D ϠU.M#/υ!M:ݛ<ٶYϷv;sgߜ'Omu/TU*Ia h lt0M`YȨU֭}wsT2j~$ ij 0XZµ->cs}4yqMF3jiV(7M{geI66O)|_w~D2;b})=>}Tw_O~u6)jeLH)u=k\K⻂NÕPsضxHc0sltZj/+b:2CN-}nWuB%~G1mX1-.a5JB!fyys,-8|!eqFrI^Uⵗ? |1[{>Y[Y7Q5ma.uY!bFm YfpKӊ5i萴Lqg(89z,KϩdNX|e<$]иi"B)d6 Uﶹvpi[ow ʄ4\,If1Qe v8"s0ڠ 9;=#]o"|x4ei/t`3T___={C/-EY:7nl_*§>Ci^1CxKϙ~?" Yvp啀eyQTKK<{ !,֘NfܼygO,RWɒ1\uJQu& NO# Vk2CJIJJ!ɪPcg:((E% ` $DV BKXlqv4wo~o ߆?W_ßUm*q9dE0`9,E- ly!Ӗ-JgϰYH]p0\=KWnsl[c lGi(GBF\Wa6J)9JAWקpz`\,@]N'O'#jp|hhڎc!Xs*Kdz<DZm|ǶuHY]Bhr"kU9y9²|ϐs|PFRWm|̩eEYH) huyּt-AYմ6<5x'Y__G- PjvT*\F*8N!c[ؖKVѸ/XΉ)Y)IYZJ2؎z$Ie6XB^Ruq|C.nzBxVm s }5YlV`ȋvJ&Ԃ<1ΰ]A`i%9x!y8Jӂ2ɓl:ݠ}r2)ꜼJɪk`ʵVM!Ddիlloc+M _?T #|Knx|NN d*ۛ{(m,` 5eJE@헮 05F7VAb/8>~|q}\P3,Kr?'TuR1"^.e]HJR*XE7p,E:͛{n?(3NOgaܾ<$K*ʦde7peo;{="/E5)xKЊ$j˚:mj%bgo/ zu%bnɣ`r9d<",m 9G5+![;ClZjNHҘ8xshuB>;LG `{gdbmuy5CQ1F($iHPHCP.7vMD"eȲIqӊO?\fvpe瀬||k~s3{_uH,"&Nr*jXxv5B-mjkaY!I&qр0 Ъb49 `y!8Qc0dߏXZiS-84)RjScnE 39zm\q(tN$e *#N2ֆameq>=_ܗ廯s|4fcssM2tR*sB{M^u cP((a IP+˝EU+F +F.E6D:em*?ϓSڽ. Dv.AK*4)mxqRUc A!e6nGccɊ>;*M*+m/6 sև+Բ,*~U>x.0 `|!vE:NP!lI^'qqz_X Nf{&ϐve29c6$ fg{{@EV_6`Q6 =lD^Nmݔd/jԆDa0* (rgYIQXX&1_zxd +4DD? 3?﯆^tҵUWؖ48 lײ5QXHCuSU W֐lB1R4FBC;lBVjA|\'uFO8|+׿LQXR:{;lɃ>` O&X׾FH~4yd[ظO)RoUxʢ~(u% l(aLL |( eQA(lx-<`|v,UϹ8B#<a `PR"E]Jda:ܤnc!K!'',&Rcp+2ԕL-FBim?Jq*ԕw=\aUMAV5y(¨AGX6/ !$gQU9ɜ0]cKhN]$Z\/)kX;m,z݀(=AG/.8;>AKFt:_)u]X5FUsODAG<}Օ.4ApiHO>K!ABnmY[[ltJRj5KA*EackGbܔ$;giODdeX[Lg Ҹ$ XYGZ6qM鳻y?D(C{re4}.ҫ?g+ܽ:Iõm^} ~;\w6ۯ ;;{+ٔ~F6KV/zױ <ch4UM(VqRcw%ZO2`lCYIEorr6#vw׉ˆvիH[!ۢ-ܐ%'Ц6;s 6p..FUL-Em/vEЍ<\ˢi1].2-v.JçȲq9- \'j]XSfɈJ6CL#2fSI2\21JYKgR X 1NϏ)80PΝ;7o,zK+jDڕ[ܾ&u!xy&"dץgTu l(e ' y/|GXQJͻ98&fDa@>$(QZm_8;ǵ4Pln p,C-kPn  p]JH)I/h3XZ(Rby&IR5+Kbr0` IDAT:vFXQOL(lqzv[)ώ_:?{FHkw4q}\<D.Yx.1E.ɘg0E)9.J_>*8rcD׆^w,Vt+?ٟs_Ao!,U֐!UR9~q|`x^!#i^.0 ߵ kp\FQP 2( eUP L`U˶qTԕ-ELW{۷8|v{(PMyPq*ٜ1 tmؖOUh:DA(\PFf9I\je5t%(ʊVK~a zQ-AV +& ,\1׶@]o\B- "jwRr'SƓ1K"VMNʼgMPu!Lm%wv[Caˆn{j9\\>}E2hU ryl-*Y4Ke|%tjOQdUg8K=b Ѡ>J(O4cmx&J\w@pm>i-1X^gJlno6u-bTY1tw( qg\^\NsZ!_טLF?deٜ^RW кfƤq]x6O>l/yw99=c4!Os@iI^|&g[GȬ# ^-z0L1k[X@J 9%!xFv'byС иlm-)RJ ^N'c("A)M#8=OX_[rms ?{:%n?)7n\%+=?fwgd8gkC A\_pyx:*]SųAI,f5 #,b|L-,O&tk>~,`s}QF㏾?3B`ktJLvZ3<\ڭ{-EFDؖM5[,e;xcM.-1' S( m ull4>ilc:eze RWe25J T/"Wor]炰.i6~b ӀlX͵7qu< { EVe3_`0jVZKڭ.H,+qmA'HC4SLdEh:uBz!2bcuf %}.34gmm!,(j!ǬokwE%8pe ^&ŋAr!R$ٌJ`ԪZ1!l66OQU8N)y2bMfdeN)ky-a wSKѦbI$Z[hm{<7µlB# C(Ķl<Ʋi-LbKvǶs%͟ "'2}=6,)"Ƕ΀I.QHkT(YX6H C ߱誠u=[ܸz~o?,3\"2Řa@`}\+@Wk/}HSx.cyt[8 -2j,]R*rvF^ǔ2J*մgċ9QЦp+LOq=mN'˛6$KW"Y)gn߹5%on[?"3l<wy_W8>m_#R(R, }-%F,]e4Ķ,Y4nD'ꂱUI\n KX(/?}S%55/ Z%9DB.J-|u{_O ZX`jc+ K,^T'/VK Ǽ5])8%u4k88<,)˫=|V.)iV!f1" ݮGO)VM4L2]O&Hic'g'%eܹk/㷖RI]E|>%+2.X$YYQˌ <OSǶUV'#N,b\~8>>r4b|1"Rq|8b|cY%tbtG?RIK~rtIQլ BJ!$I5q,E`4>ö*@R (>N|:F++۸|T2ܼ4TE՝+ijEҎ,ȋ)K=?;2ٳe2Ӌx1o}sG'ׯSbeh&FIҜ6Q1ȊB-:i#+"e m(\./ܹ~V.)avYo.?$ڿ+o~>,aphX]'S,6eyTq:eYec[]uXG}[(=:eR7Iɘe0.6GH iFPX%t=fy0rO5g20]%;;+#ZN"+,9izDxNN' }PRi=C%JH(5F8 J 6U;HYcaDhQST)Q4v /NXX.a[v`y.q:)n$C>GLJ$y qf4G<bccҀ$ "E^HOH,/9=myWmj8¡tiv7vn*T_"]Լ{W_dq-))Y2RltekɌ==<Ou& s2yl9u9,€@Eċ^qYpy` uy.ٌ2PUD^@ Y Ri6b0ϙO+&I2õ_eooM& E)jEFx3g=oVUMdt}VW152#^8"kI]ƿk}NN۷)eE^:E0`17,Vٴ`*gj,K YoFkJ0Ȫn2n@HEY:]igCJ"4-)/z#*1Uӫ jޟFKQaSb{{w;n?fn1ظnM%ؖw| ."XZ2\0j5EOI mLNJ4K368'M Yŗ"?c}x&_l/_߱nrAdl1NBMjwo✨ehup*Nsq 0NIOnEFY8M-|/hZԲBP)MEyJiiFb4 ip94 d0_0[L`5[/IXiMU h &2,hi²M+Wªqs+H]U)1 ,lZ.ea.eInH]4>7n\6o仄-)-\7b֐qLrI o~>EXiRR(+8#<ˣGO,rᇟsTt]2XM##?|[s&>T(9;O>"X/8:}ȓ'R;'7ΝS%iQߤbD:rEL 4A0ZXqtt˩v],!c2k)Eg1Ed<IQ[ U\ul1Ɗl-APUuID2,Q a`QhFf)eY *l>Hy9BBZJa,̇Zx:qxlP Uj `>-!R𛸶xrx|D$h˔UHUzJ׎\!]^O- )cт01-UضmZXI&OJvw )XV,Ҋ_=&T¬0-b9&=Vj!ZeK KќtHYdQ\'HèU?9Q +Pr]\5yYC.;|AL͆foJ" R+z[xFe^zu>#>xdcnAo4 }b=OMR.?˅' 5cR-y.i5aI [yl>=R,W.d:YrtzJe Jk>׹GyHVnG>% (ڕ[2ybIÓ|ǿ7oHwŇޥ$/%kCN0eЊm ma>FvSW*O1Dk8;! NG.v&MEY LiF.#¤K 7IuaUC@!*lYWI I2YL(TEVdBz$VӮ*(#J=naL3,Ci\:SÔ#~?/ȓ$8?0- @0PBc96EYϼ /0,{(xv( Jm:lB(DQ4U%ϼ"iQ(jbnRu>mܾGGgl b9GJYs^~* Di!eX]>ѡs/ħL=lcx֟?"4f<CɈ"M)t)U%=>>g#PF䍟? .]'/+49%0 R\O҈YaZxRtUpl% φd*$;im2hocG"EP@Yp{qv2t*2)Ԋ q]IӢ}6| IDATQASDŽIc; ]2'J9$Y+\ƶ-Ҭ^;YDIVJT p\dz@JhQZE%$Ah8"B$Q&Bn8pb*m|_Y%iVlv\NCwc1S'Oo2Y8BcCWc9ݮSVvAP4ů:ϘhRLQ"0.)th\"%lvpmRL.tXrlin\AJ7CDsҲ1ܻ?wh5<~)톅lll0%EA;4-g\xhmc f%9<} c&JlK2S5(LlIQ ˮ>NgxnEq:I:'36(` *@ii"$SHFKE%څ)\R ( KHAF/I !%`<<ŵ!OcՒA^M2<"S* sӳc"ul*Ҩ`gkp*42 1WG4.)h JihBRUͶ,9af2?CU99eiR+T^G!a3G4}.^Jh v}OQKlnw|n6.._ck11_hEIxtr5fRe-dz]ڍ6;;4[>G'G)cg gc[ْR3Ƨ,BJ\eXE)9<:#K*mCdo"e&k4oz\rng@f8:* ?D|'JRVs/f.$'Or.bC]E΃x|tLf5OE) Stm1JgUj]ppi x M*[tf>_3^hZ뀛7npr<1]o48>;B[;`6 B-p6b.y&wwق$.i76."t5crdrB*T1Ea YWKyS"/뗿}FTlnX3*](5%-jM4*L[x&i|1\x@Y&MVO(LVa4 t8E*k9>oh;`BYqizU@65o}C hX9:;$hJA78Nyxɥ;uHUHPT`.BH5w l>Ocؐ Pt"l:]<V-wvHGcZY0,Ð80&(Uj&ר*4b&,ǠH[)K&m67`؞I`{{Uyгۑ0ZK"e,I˘vǷ[NfL m8qK*Bzf .Q)Chz;[xd2:"I|gY˱1&+޿ZDQFDZZ۱Mq])*L,N`5HҔ$K>i-ffCe=x酗9<<$Z9y_l`JÍLH /d:"yĠ[ 8W78<dܣq8_Q RhLBNӥFI%r% Xe` XWwIM8 p=f%^i7;yҚ0\!kٓ邲TKeCRU((F#1+4}N *UQSJZ jNq=]S_t\=FQTY_{GO# *UG .s6גJrvC~~jMloyDS)ͧۀWkr- %C3_f lW=n޸ի̫k?stz=:]lğ}xCqj6&uTGG'Φ,S|$0^+֋:c=K-ϼZNV'1/X [OghygҀ|NTO~k2s]^|y~=ڭ0=…b$K ]4a{g.!AB 57o^O>d8"I=ՊOa)o/eKvzM2X/)I-aBU^Ǐ5 Sjt1L qtzHZe!?çqtxVo҂&4 <.w}7 bNijm rrAr=efzEVON^ ygBm!A4ƿw/OgWkƓlפua e(/6%B ka/|aą")4Ҷ(KjQd˴pa%'6UYߜ- 5Jn<9O. DUelfJ)pEYiìUaDI&TZaSJllBhvAQVEk1ئ׺i}HAE(]`Jh-߲ԘO>?O՜() |CTdrL L)Bbj.ʪZj6ȊK`ufȈkׯ!%,)ݾG,oׅecJDf96h6q7ĶZiHSCDeQ Dyf3e!X >OKM}vspxa})|_aXчq-2b2QP"߽ǯگcxt6SɈ)!4cנ^5C)DQБ.7h.ecZ,VS\S ZE3U 2&bҤDEKD:&ea#U'Aq^QrEEDM w8>yB&hUzB`hxA٠Db951)Y9w2Œ|FXrUPVp=yd1 i%Hڕ\ps\lxnpbsa);[>1*23rzk7+:=EO'h65Wz!aYY*"嗞1)/)I<<|¹KU͡(g9#:m_yUe$ي;w0kE UTlAÔ,+Bspp?~GgL˦P%Jer a/Bo/\X ϵAXAK%w}>8EX&n!JB] C0 `=$dIy>ZJ!*I5 ]bomO߼*z7x:~ûo%.k0m=!*0D@ ؖumgS%DqR(U +RV5l#e6X$QeQ;"mjQweZ6*/1MJH4E#1-v c#EU ֫v|M([lޡ*&yVb9$SV!ic>&2mD`Ef|x,ӓ HQJM%,Crd{,jN"$l t:M !ECRjE@mgRJ2B ˑ̦)2>acc$qOl7H" %cVc|_b BUFYֱME%Ѐ!>lP2lXMԚ0YeAe,JU8ZK\{ ͠2qLxj c%JhK!U9JY!&czt9\<`2=%?KAX(UuRoPU1gCƳ%aTeByۿrf1]t;]ZmFtjb2'o{C[J7Na^8Hbt >uWB#DV`hs1Lx./)4*I:n0^ʈ"qZ{QXUN :ܾ|rc*4z6~EՒ_ox9ͷޥŐ_~oǛoGwyP+lOŶؖIJ aFdYIf47w}UR’EbኦI=8Q gpOw']f u0MibY+4g'w<:hcZu)"S4`X9 ) Z4:]yMR$I뻜O%2Ҭ#MB44/>)Y90 )<}GUt[-Gllvd4>e4=lj9s]| ]|zF9&>,kϷ);"bgGItx@ix2ŶM(*4Fgn\ag{lyh|iF+ SsL3,P%[ JYblREJ#,) (q2!֔J98qxx1iB"{adI*HRLia>wAhGo_+l8Qc6yeQ?M%i\N$ LtZMH,,ã6@UcئA1"SvflvwA5lNJ(louo1>co$GZPI|*|:syU~x4x&en<iaYfN=~6 SZxV` L1-x/lX'. 鲷‚8 Фs"Ƴ%o gS<ߥ,둫*oP۴A뗙(dx"DWư []J lNǵ5 ìlM.I9ZVS"~(QyB6lm^#*ZOarz<]Q(*RAQ 2{LF+zvL McLKu@Jh5HZ*"m40-( U +lP(z_U\;GF!>ðcTBӨW! 9F7|f)q'.''gy^#f* 1OL_g?c%o~{LFsmgxn)NSҲȲKMfц@Ir$#r( G! M׉StJm!mJa4=`ڢ)8=GI*|"Av7/p;$Z8f81>iv6$6{ )RAUr3/s=6>a,ixgln%0ű,VJRЪ D^)[riAR%N 5~<)VK S=F'dqkl;T)`:bc v7>GtssHp60yS}.Cv{;àpnoW^ ޏ[uFNg]v<Œ ǭp[TWgsp4#US#BB÷"P4<`*,$6;[,kTfwvcx+](# 3<{2ٟ yZ{` N#Z-k|+ak?v?|bYJ(6L m^i >|}R)# V+m3gV8.^ye`hx,dҌwu.|Dq̵˗Qť=)%ev[ O_,[Wk`q-1}/Y7&3vFUR a&D1Eby(PTD%R;ipUG3D%iyMV+/b,fCzzzdw=CaYnla@l<횑R+ UQԄ!,qmoT@\ ٕJiZKvPDh ӶRb Dɖ M91,KEEɄ9}tr]W89 8:YlO:*@! )wBES!Clß|$v+XGUH.NY+֚?[!lJՠ<@i-R ŷ <&+Hi! 雭8)Ab6Eɫ ]U;)cZ6/ނ۷wE#l"+2kB3g1LRPUS \ef>Ot*N9,!S!Ҭ.˶)+ Bsz2U!j*%?>ķ[=\bm 3*h.thmTw ˶HdJߺ:7<\P&V zm.pdH4mm|i6 ( XG1VE` g3<ϣ,6 [.N% IsQY6*Z]bQdmlcݦlmlJ@٠bsAVY.ѹa{#\a:lH)%I M"K~o}Z+]0IYn?sAykv9~ @ ?㲱I{mo.!~Eڡ!RDBko Eot%Syg3]I5)+E^D- (Ki-l.IQٚJTȂ}-#i1?x_s2|fJ$yNTIYr5B4 f7{k[z=+pnbJU(!j:hÃG A@ۚ ۀ:XMPA*YJ7{`]PO^8{Ѱ:E^ M* ^p%/1GDAd>7nrpxJoq>>,ln_'Hpr:$t2]]>A"W'Lf)N4߆7߸ε~x!% 鲶Ѣ9ptza8؎2Re?et; sV|t]gey8$@]<$ iac>kk+Lc<Ϧ .:kpeX*˨jsDQi X2ܾW6CN[xzh4nbh&۫.eUH薎?SJX^_b4stzD J"LciJ1C>qEVH,_c|>E`iلk{.lb/߼ǟOY%TPpXD%ObIEs,W Mݥ%yYI qHU lDQxBTz|fˢqUVߒ :NIZi3lo\8"y5uQ:#44,*Y`XR9FeU:Y^/ŽQ-N$fp6`,U,/.3䜝\˗"?=\uʲBQ־Fa"3xYɜ4 S*UIh|N/b"Kp܊7ߺ˯ҥXv{ p-quok7bZ?(ړ%cʝخK)eZ[tY0& CXX6e\|ǥP%Q%Vv1ye9J|OM&mCs->O$Y+ulv􈍕U,"fq!F͍enݺʃ2"U(4]2\ppĥm66h 3 \ '̃ ep4dH(L%6Iaa;wg,.Jsh6 2Fnal@*z4Ӷ)ʌH1̚ݘ43\Aͽ{g[ L%$H{wxn18M|?/~wA??f׳(IfyiFçR( ֢ML)KAzMh{ Zrsl[9ZB+} j1M4Ykk~opo{bo*W7pMnerQ2R &FE(3\# }^yg?b<,/24]0Yml"q׌#l;%RO=`Mc9̐UlQeU7|&,t7v6I;5i 2%˓&Mm rXYv\>*-') L4%/*RGlllb2>c29eV]jB0(ᰶCg8:f<5Xө3㘘iQ9i! 8 YAqq ;;X^`82ϑ@U.K:ex2FR!5xuX74EI=% ȣ1`RM^&W'ONtouQᔯ?gln`rLwIgkIYӨ0 YhJ5/8qpBө AYYXķ}le51 -|Ro Qc YD!,".3 rDSB(40m H QI4(,O)C5QKk46: uɡf:pq>!c*)3n8AuF$ ;"M`cs+x(888_ Q4Y')BXTRc:2 2vֱ2q42$/#YPyLIƕ%^Y[7IY?tp̓gL{>) "U9v9:8$ t`ﰲM>w ,4)aP9(`붸zenA0"+H) O2L!)ex;s܆Cq>zĭog밼΃ rRBuL#F)q΍{m,&/wf.O)OYL $! fgOv/r;<|yoaטI,& ᒧfJX^\%Z>0@o IWv]WхBw )MwXd!~F%FXz,.LH31i~J "2*DzfdIN&kJ UJb:nPY:Bd]گe^{o{ ()W.C59MCYXNXGy2q -ȑZE%q#Z5pNE4I)+}RaݎO^MvPEg4&qbHn`6olhyYRTa4'OL6p40uht;Ru1 xϏ ktcYm*!EΕ˷'1 G( 8*M!teLG(MQ"Jⷚ(ta`&YDJO G8^q\$J)+T%xN]T%Iw1 cuA8V ib&iPg<9i8 äu ;,jGn6Ő,1ɳC̃)c4 >r״0mL< k[>"L{x" &9yb|<7#\ .md}Y^29>`9m\GOONi;z]NOk|d}u"-X^Zjl!a2ׯC9:R)20rlbguie%E)q6h8K^45 ĵR pQv+D)k^iZ'S4ӨYU$XbVC0K LPelp8$\(u8{Oh7],K' ( lSj10 KȳnK\1uqed<'lm^]ON[si /XZ{?ny^`UD0lSCO BRDi^GLF40^gcmK; [4|+ '" ºX[b)?Ν'>oFۯ}᷒Y^{Mea赺F D[,/C2( ׮9˴+]y$''g8b;nF,5$`PNCUU*)*Qu%$(koc=6n )i2aFR %A"W8G>̧⭻> wgg) *ɋײ&^Li.)04 8NGz T 2 T*QR!eՍ@hP 4Фiݰ8<}L.CN1 tMQhؘf,GLn~L=t,4,eӰ|<ӁJK(c|_1Nit\4]aԖinnBoJft:&:ؖcG)*خI!R4PMOZBP9Y)0, k24,Ql^L&/24}0 &%orx|FTMLKs]|V9<%c*tR$9a_貾)C MUN+kMb6ƵWx/3^kL[oWO<GGGp~%W/]ᵻ_1Obhmm\c,ϙCNֻWv/`&BȊ^ah4ɀWL< qHH UVD& M]H9qt u).ټ5i6M)B(Y,4Mv|jvccs0y:PfYi\gRXD/C IDATrAm sڽh8Hö(dmmRPʊGRdG4+Rܹs NN<0 1 9?<{OpɪJ34qRPUYZ˷{ќt%}4.a2%#>=4k|fpeN/0͊x˗q]y0 /RS7n>J ;Ӌ Qdwm(rmx_7hSW0MdE:IRa.n͝enle2Q9X/sΛe`:b0Ϸp,`qvtPT(W޸*#@V3z]2,\%N2oE}' ;L!sZMظfy`;d 0/| "ϙ,@X/W~w/!~7!ǥtA4>% q2%#,G*0䤩92 s| >'gxHݦh*e:\r]7G'xPVv0--xncCϷZ:.PI&sʪd%q,*ϐU%BVxшLDaܸ&EaX M4M*#N\5jQU82pyE4趮peUGnAeӒf"4H)A01JEJC3L )F.r]o!m(rӡXbm V%%18>.ʴLi_7hAfgifiFTI500 Bn,i#dXea:8N)$214fK!L" f񀣋gv\A=nxJTQC=5?A%ixm77MUJϐJ g{%<ͧ'KBcMs&!W`63(NFWEX Y-g29c8= $_kK'ܸ2vw?ߙNNTųϸ{U<萢(Jս@) WL"<(q1:޺4@%EQHtӮXxW2Jg}y4N) Abqi4mY8rrcͨ:iǧ;HQ&I60&JB~~/,>a:tD";N4zKZŘzE'$q.:*U8`nH<;7?8as{mi|oh08t .ZkI@J zfi Cfь0J(Dōx~'O9IR*g'i,(w(1|D0`}}LF4< z*TJOh7uFQjk/c 6M͆k'3,#AXe&㱵1ɀ$y[:=L䄳S$<"WܽsGQD UL DQ`2fCnHlij4 nmc.EQ1LiaSV%EstHe:/]1$pmYIL" [|YA+<; ֜NǩtFƬ$Q\9 ,AA8G3M R$%]Wˬ@j uB$Jq fu'SQ?9b>sew{\߻dske23Z}(,"/BBqаrސ BW:L xvtixA;>R eLo5եMyt:d<=#.B4DjFA%E*iX \bi r>>-0B* D U6H鳶G80M M"$;7IU)4FʐzYuHizmyV`ZF)E%$-ϣ8VtHZy@HYPd%mnk& ^IdYL`zoEm,)%H<旾!y+*Ak7{?!&HU$_/P}J_Juvv.s Ww8o;s@ +L!q xo9>> B*˄Ʉ2 Ӵdiƛeg)i6n G̓9Q%Ya1ef(eaM\OQL㱆ncm6{Wk"[#EBR06ie9^)+<'MZE'eQR $yd2G7Zt:ˌF B7Ll&Bz ]d1i A).]ô pJYf$" lۥsr2jrULk$78?8oaiǿ6?1`uGoEO1 cPƵ}J & k+ئth499`234{Mzfc\fwXۺʧ=_48=6ZDє`q|:%Gx.5[p-)*$e:w0 d۱@:vyXDImkY^YhhD)rZhZ,i1}U4x^j,1PQQ:~eimHh86mR$9@Itє>m4-2 8?O=AY[kltp!YrvrcqAXz3tY W_Oy_y7c?_i:8f)D^&eRi(]AhZ׿x %&&"cz*q<$h$W%y4*1E|N!B}NAJL[.lL*4]\Ϥ4y[.Q` 1em4ѵ۴ZM>}#D9tLݡu9=m4&R.1EmqL͡,|4,<$4M,C2CEt)s^ZWȲib^0P!K*VWH$Nm t zJ@bwCPh4; fZǔ9̓yRTEQlӈf!MfޝO1 qO fih8Y4}p)$~a>Iݤ걳HF.a:;]( E%Ȧ.UyO e98Ge\\h1c60Ȋӧ*O9ԩJIt6u'<$.G\ ؾװе R(eg*k; Qe  ! E\dMVdy l*B1]2ԙ0044\T`EI\\69:yB&cT ;WNGLWË]3t7n]3PEG &ɸ%VWx$3G8EP'dyIH!]vv/)kO#2cmlOG(=|W´I ?}6=^~ i6#qOG7 l᰾pxRY.h:[k8 =y:ӃC*QɊd`>W^Cj-[!_R OEA0i+9x|kܤ8^`6h4 `0/OCv{m) x.؆B%?s[ܿ^~(IUZ %e!Zdm&&cxD)*r YtQ||02kgഀ>g`&MIR5D3,|'rR!-U۞ڭy>"aaq|1ׯaiE6ccme$DAjK)~;m$J4 hH){q1PI4XXnhwi88ӏ\Lq\f a;x a"noGkQ Ӷ("K,S#+B"~&lH8 }rI[,qdέn'K:6c=D)h6>B5̴'_oFow~)J2ujՙi؎V@UcI<u1MfIYdE *Y ƶ]ʢ$N8%+*Օ>3O1\0NplFjr a*Eg5AcZ,g)ґD8P]ΫyJUe誅&[8q~qCx`hRg׮^il |F&&D9R\mkD|`8$3Ѫ̻o2(S KiJJFeotY,!x(w\z`xESu2x=t=1t]3*!"b $`dyۀ5,xҰ(Q1f1uj_znv7)R4IIdIccV`2 H&@ 0rdd0XqZIJ)l^z~{i*/Lݜ {y/ˆ]&ǽ>ϵ0 A,-2tWY]_iub q(,*cMyJ a6GY{d+OG?FbEgxŗ}waX&뛧:sQ#Ie]~?]CS,G6|DͫѪ/mmP&aX#c2CTFɌzGtȲ YAZ(mb: "-k8*Wr:ZJG1RCI_ﱳ3 I}7Oa`Be`ZKga7q} CTRQ UV 6U8 l˦;: eT 2|>!`~6|lyp0 xܸ~^/ayBI78歷g0s /O~'"CYTJ0EI3΀!h [ ۲1LE^N*5x-,,%DI<'8¶q!͕gpw9' Ku\̻9*>w`6TU+ܽw"s^^EaZhaRpXh,h 6eif.0 |Ch$juP)9]QKX]Y%KsTVM"^K/`< m,a0`qy 3%+$m[iDקb&g||ǵGԂ6gAgiaMY(*U\C aQ <_t1R&xl>YM M*er{ý[yTiY(eQ eo#tnLj3Dec4rnD)Qv$U6B(d0mMw&C5]Q_dY|e08AkXl,r)>߁J Λ fB&iv0-vQB^|l/HVλ9Ua+_…s3.k=byLвhuZD IahpmTq`KVL!peyAe-z=" )(0 `0w$~h#bD^LJ;͕soq4`wǷHҘ}^Fk5iwOQo yE BhLmQykZ&ˋ$qeRs°? 7(b[.KK+,.p]G* 6A.m;h焇5;sW>?X(dƝۏZ0%nG=>K 9 ,0-4Ht%\F6n28?fyADkAzNYEH :MsW@#04h6" 㸏T sAPp]MÒ$ IKYbb~&~AѤT%X^Y<} a{( +ll w!pp4٬Zmj7}!wΨl:̓GM#X2+^}Mh.rk IDAT&iM#Ξ92!=xF!MsZEgOc6YiiF$ΰKhk1yH\JIWx,C&10emKlo79Up2ц x2< AJ#M}O1Vp8vH˓9o6SjS41EE(r)6ֶx |7ϿAWگ~^Bd#55)iA2L4Wcqy$dBV$A"0z۳o^hh iTɠ?tZ4Kl%GXn B Ԙ:1Z--׳7@g#a[K di L%CRlf)K+eLSxcZA=D!UdiQ9xfa%&$IFy2},;YγEBdM zL&'k\ǰ4+UƳ!i!"rZ^ApӟC,S^ȅDɐLQ$K lFXbh.rh-fMLF$rF'S^穇?_3 ik~w~[0O M$)B׶Z8J\Ga` KTX6dZ-s ӧwq2Qcee}L ę53x^]2tb9g^,?Gͭ5 IZ2 Kc=:m 0(sXla}"1`TJU)@PTZ`Y6mS58d)YZ`0|eƸOCEL^d!d8FL <}`2*y+~p,Kptbo"7ߤy{1Y#NfݥN0a0:񹗮OEcLjykI?:;w3q.11 a SdY~G\x 1.>Nj/…{HwKgy/=٭H$=BrGr#ʲLK8aee?|SN?u6Ie K( ,F7P|`T6ibwpL'$ِI6bfb:UPUH\rΝ?KS^PeIYY\mb,벹C2 \,Aimk*Ls^JS7pUi7_ ΟpI2{uvIxcL>q2`2;$#Vש*$p&GŐ\4Mɲ 4Qbiy>RVԛ xv0X&&ib$cXhmk_$7lcJ\Hu2&1>a1机,IBñ9}(S u*TUQ(+@@~iZT )KN4&RPEr5q:\ߥ\@Te.n]`aa.v98C&άCMg?~'l9eܹY rzdӺ? BNݢ慯J!`>Csޛ%&)Rj\~Y>Jw ;cmm^FYy!8u9i>Ʋ-X__c)nv"_՜~gcxiiBQgGضI^&:uG L u0Mt2E40h@FS(uFr u`μTI5r%Ac.Lo 晫HK+mȦxaVX5o)Ha9`0TmaK\,{{5,I!sd!XhR)G7|,²M 1J(b31,JK,q,iVEF`62 B`85EU *iEY0A0L8.Q`mes>H}6CwnPI%l^o?d놘FRs2Ϡ".]٦VyW.66gΞ ,⥳ DWЪwi7Vp͍-CdT Ef4Or[GT.abp(0eoize{{L'=)LІvLE iΞ'sW7iԺt[A8Y,./%hf`hQT9oE&Z[Gf1z Ú23+{Ã=dQDqF[++ܽs"KIf L1OY^\&M &2'Id2Tmcu㸔֭;! ,.I qmp-Vx4DjmBbp| !2Ŕ ͡<Ν§#Et8>~Lw?Ű3[y6!M3¹ gxvvKf'3ÈVNSlJ$-)mwi4ťuS mlIxKyczD!3yhJR<7^'yTFqO9w3gO7|*JlG{Z;+J VRAi\~wS~S "c$g bYahx.zSʴB(2xUn޸ͻ}^ԺTZ0:!DHH! x~Z;I1n "Ѱ׍tYi0@JhRb>Μǟٷyկw9#H6}'fwl2"|۲f F^(-*mXEIVGʔasjiEEؖv<,e8MZFT*CW%. 4$%B1.Xa1a3B&VUVf0<`h>ŀ,J1ĵ-lC m e5df%+iQ.BwxTh|<sndͭ%|@Nc2ffs}SdiIO%U5na eY KBwIxeET]'I4qJضAY`( A9VLCac=уo?7^gAx:MfZ-:9>vK=͝;;ܹO嬯ᘒYt`O<޿(,\=, \ߤui^V{E K"eT3|DAKg{6]?p;c K#p+ 96wnQI2e!pVs6qRxN蠍eiI!EjR&Om_ul>ycIɡx5|Id˵P N -NzCܚAd"8>2KVri){o]*bʼ)8Kr''(eT2oo>$mF1[? Msvz]{UOŷonw5黜9w,xX\_|+oR!EQa:YID̢0q,h\!?bdeH̝Ŕ1S)85Rp4񈵍U,Ȝ|-n'X^ 9erB]QkQw8?~#-E6Ifa?lIJy6 aIUs+K6VR(ȋoWS%_H$g\>K"d&+׿h4=Ξ?0I.Mq\5j Ix_om~ǚ3'a͡ժr@ >c<"٬,A4j5fьtBc)QRѨw DBg>`q.2|3*ܺs$%X,&$t;g9uZ,., irƓ>{cx6AJ$RJT\ri4{e0A-|_96Z,t./c'>wnlt*n:-Y.$F)f͵`0EK/Q3Op|A;# R6Oh!LJGF+Og^3/p;8HXI9/OubpzvsməSϰu,Q`#!;c2{ Dt8E٢^1i>bY]i@2Tah7꘶A(e# ͋%|ܽ(Ya!'o|J KKmSc Qa[(V?" DI,k^jQeЀpMt%zI^(LRBTs{+ Ԕ%h0HDZQLh i1MCFӣ8D74w0hCr{2JN?|@1͂ǸzOv1vn}lL,,}2ظvxaUyTu|'XbcI<wL#X LGZDݢ kJ( rC, z aUt6v@W;myY;Ar#OYC:2)C6]O(KM<-X:MQdY|0$Q2#ediEHY5vޢ;$M$ݳ,w;ulbhA8W^fm-Y!Bd2iN盿Mܺ# `:-նʗ~L&GG &o1g,|_' wW^}m6|n? aQ-QQQb^೼Ad$Jypw>a09FaW(yf RyvM曍͍+v,l!1 Ր4=~$Qllm3gYYYc4SSNN0Mfɘ8NiHUPVF+eY$qA-Z6I9ĩbs}<-G5˂^xk`p2i1\t`ăP.;wpL_pp O_reR7sY^mPV2 O4Q|;&ؖEװmfF:18&znw -LUe} ,?w2s/r-Jis~*=A0 pq$a s6ڌ)G%.}_O~zVd46\jn)ac^Ɲ]\D iXǗ:s 60-gh>ޥh2e舧.&IL1Q^ob{!OL[[H4($G>O- 8R˨Ɯ>IsCLh~g̍xoɴ[iuji&y뭯p\/cY9i<6+:*{AIf}髼h0˘D)aP7z4:i^a.w|JPS uB pG` \d)P@(]qOCHP>Ͽk]y9~o/QIJLdߞr6"lǣҚ͍uajT(Uʹ)i`Zp7oAΟ@4oa`(ɒST,CJ)`:%x&w?䀃O W!eJf,a;`:sȣCؘF@֥&&qɜ,+X`mE6^)*T: Ng"hӌL' a#LKl0`Ql$S&)JWTJ"*7 IDAT 0daXeF!3,diT3Iorz"(g25(c{*Y^^hLC0<>`6#X\j) ͖IЖB4M(Uy>syfNej"'/%i8C@*4?)4eZ iWhV"YX-cX(PdmZT$ Wp%ΜLա^vV-N2m4cdK,-@UG &+lǶ!qBKQ3Dn /U'RNXZj+_B?^-n޾w{$*%HV/X\ZrCF5K~~HĄehlLPM"ë5b^2 \bsVxnm=tqyNg0X];MUY]{;D ”( z %m\6yVb-;$fXCr9hITe 0EKle{g(4iC) % d5:" &wG)ٸDcs[ G1gή6ٹwѨ|k~=>Ͻ79:|ps~֙6؆KQ x8ЂYE ZWER͆ݲ04PRو]me8Q`}"sK,g: SCi]it]f ]}YKjuh,šaJUEX+/rseqݐZh<`pBH /y'CLv> Nslg=?wÀi`U{yEPDZ?%'Ċ cGOhc7&JL4T&SAE*nz ?k_ͯH?A~wϾjf~J=40d<Ɍ,)U+DCl,Ye,.,uAؖ$fD/ވ4VDgFsgqm `p?͗9{vw>+G1}ny~_bn)~1e lWoL7C92%y3쐦(4.<ܫ=fgwF[.'wX\8[[|?'SZ˴hLJlWZ(T$FUg#rC^7r$an F!&HӰryʓ|uZ O?%N$AAI MOMj2ӜvW^E*Kq鴻\}Yr>&<<'Na?~+ч7okkk{w?gdqD%#4nhxӚI@zRkXؖE`Qgomr E4FVLCǶMf(Clb;a @@ـGs-Gh: DzMm2J8kLC誈TG%UD0u(tP0 enj \ I!cT $Dߡ?:d[t&P$F a TY8V\a R$aDPB&0&R)$`:ynZ$2aB7Zˬ5񕋴GQavYA7 tL;%$Z+b۰CԠT`R۠lg12f"hR.Wh6<ffخ 8{4kфݗdr mPĂAw@ZT*3;פT1Bi$c(kKI XlsΆ$2"?A(MRkOӜm>20DI"C/3?O_f0n12$΢IC]D 3ue 'f" {cg0͵gSܻ)wܤR+bhcoLX^\txp{2"D[ǏwȔi5|%fy |DgLwGC ng{>O\Ɖ3?&޿ aO =<#Ƙ&h*CJbY#la$KF8aj9绩2SLQvJ8vrL7$E:Qse&m 2&ǥPlМZP!BM(8J3+=q?X>T 5 L#b66rx{?(R0 a  Qk񤈡490! M@sI"Fm:Ađ#G( GKƣNGb.Ξ`kka?!KzEݹEK._>CDܻCQڵw?u,^zI>]9ʝ;m.=Y竿z` "Ӝ6=\<BuBG*qQVL-)DgANHm i&8s"Ybp0 E.Sdc܂iyZBp-B`l4klmF#2QpNJljM!aw:I2:ӭYcvz*I.9$YHNiڨ %$)R]tR@(l[-8Dq5\.2AQp瘞;죛&_ BGd[j%툽1G|m< '1Y&ri-8u( 34%>o~{~ސcb62$EhؔSTk󴦖8voyp$P&b )9u* V {{<|x/8@!2ICB_#rGgxIc:&q3)fNR-,Ѩ-‡BF>BiTKuF0Hy'yhiR-S-b.QG8Qll 8v|O\7o^'CP{;}{,-}>*448o~5ra<?ޭtڻlk0?%W^~gsq&?KB;k?'7ڼœxfB؎3=SEH-BNdEMUD((R+R+-P+&!>$M#&M# NR0zxb$ i5\!/:d4D#C: ,ϝ2T.+'~noo9yihDӦ\.QTBETt}Dar8u vng@IPfp%,^}Y,r-tM2=b2vx(.Guk6_TT6 D Ȳ8Є@PVx`SX\‰mB3?OBfR.S88蠔T9~,7nݥX3Χy883kDq<1 )ɵ'vB(q2, ?x?SynܸE%'3)08$B.&#PUp$#4Dy % "YC_SA&s4@9( 2\#W9r<7W_ď?媉a("b\)4 4bCF>iz@Vb4lm0j4%VͰHv/JhКs)&>( ic&䰼hg4rnǩ LuP)zCZ)/'w)9rd 2vwwOg$ZDa!@wIRmԪHcl;\U/t~n3y/6q(9s<{G<. ɏ?!b4{!$ P,ƵMƣfs<]JBQu* >]]*E@"2A02EuV"l1eZNT* ncXCՈݽU4CMzrHQp2Cڊp+3eSu)96/]g|HMI0G\Oؑ;I2B4x pot"a pIfZĉgQ@V AD3fq,ynj ;!uH CNTlݜ4I1bJEdatn $ö )#r0LI.2 4\Z)vw$ILnQ5(,h*k3=S$J"&,= :S!bj"vAC(` 4A`Nw( >_ze%傉a4MF3Q]`<Ƶ\,>lS+cE{oHc6QZEn޼ Jx6m-# <^@f&N45} nݝ=4gzgh7]bCC?VQu[fzdo* 4l~xkQw"ӣQ 7Dq˪wŹs`k]rt)r|1w9j]ńJAS7Ϋ/D)EۦV(e0-+g#p|ggjGyK|Fv5ܺJ ఻^{'?:.iҬTHcYؤӏxE'`5٧3,KF8|izᅬ{cNXKIN;۬n%8EV1"sJjpbeowjq܈F7biL4I$rbrrTJF ed5y텯mdO=CH#4Ku P:BtenaM>Pf dDdq%^wvw01 c(5le8Lq,0S0li)-҈$I*Om 0vwZeg0ްnkX>!+$ur =w,jcǗ1 /QK4^hL3qj:zڇ=ې 3S|٧DaN<ͻ?$x{lnnr1a$?Uko=д(%R-b&xR u<ҕ3E iB_K@U DYSoN6/=2;[Dc~H)ЪըDsY\K^+z4kEl+,%#:Kg{Kq,o0ǥ)qcB( F}.(Rc !AgfdQdq`DkBrx8!cl`qi?'&Ñ7A6( zQȒ#K"bmXfJ*C90p4Ewf}p]ų+q:&4ax!arlyc'( ܂N{HbI(""1˯'X]{O` /h#6(R)M3;n;mEN3gۻGq|m|{XR6c?$4G9u<#֛_f0?`8xڋGOxywp+p)Nqvڟ|ZqJa=EFT4 ? t#$ (YY92Yx4"<4,azA\fmBh4y`_N8̿?H4ZB9{]R l8PVob7oQ(IҔT<3Ta1Jt:[?Z޽92ϑn߾LrX17@=UQ/yUPr\`@}ʶn8t l˥?̹z+|:}|ɻq<S.Dޘ.J9/2G~,F}TRtT s;G篰tc˗9xLh }Fa(Py&EAN*c<MxD.c ]"p蒀8hl˥^!Rl&NSsqtjIcg}dJ1͉;uPx /8eDX%ˋT볘Ptk , F˗_Xj0IYP5i6[?\s,KC)y&v[`ѐYL"shpp:G2 Lb@%%May^)k&xYZ9A; XX B;dg!vQ\(aSXxL-Je tS.W @)]c3loo`bHʑN,mhG>3S5HiNEa<*Vqu `ie*[6I2µMS8KeWggpH3 X#Vfv]FV1alSC7MJ&~p&xQdJRmTEgMqjDrЄn,H|)67_/?OTi7zCAQ8-T<\ݠ٪d2nsTH5P~U0pl$'<bbRrRH G(EXK%Dakh(dFfu6װI8'"4^bϕƠQMqE_n~77_`A8H;B5Woޘ09w".nonP2'OwFX A;KST2hF0RP9iHRn:{ocYz}ukںz߻g.CR$E IJفA$/bNUɖر(v$EJZH2\f齻kusU`z})]4C&_di~Ǐ`tl,`KI<  KSff)lT!uJw(*`nnXQ $^5\Wu V$鐢HecuZcT?75&1q@("W<|4҅jez 0WU&KkС7Ḭ#Sa>%K .c]>ܺ6Ƕ-|f2 #.^8Ib"Eeābn{18ؙ衕ߝljVW|Cn:-Eg9k+'x|<|>Bq)=~ȷ-n5<2,?ܺLș+u"DH: F]?j2HgL$-R8DdXY\ Ʊl,ӡZ6BbItb0l̔… ޽} ӚN$&#/}iwlܢٺKrUc8 + Va$)@jaH00EO[BLa!À(M((2h5a[.YOK& ıl8ȧCuQd$yH'4p2哦4M)Ll؜j@(8  B砅@gQ[4fLVy寶Ir p~Lp%j5߃[N{8'k%Ұ( `T 4ydQh<=lB(2EDAWj~sg/pYVW(>ŏo7Zuڸl8sK0 <|.{4J )]nkH?󗏳Zr5*a00`0 хAR1Sgky4ͦ$L';we4A@Pg: Q${~\}fWJEUΞ9K{?>n1s:sGZOq ozMBD*GBNժe2=!Gl7%CVX]]c2Sd!qd2fNK5L J+x*r I: TXs,\_`JG& ^I4 ;DE…sqNdgҌp\4;[tMK}(L۩Crla8<أmyDbyu,C(%rE'\8{A֒$!e IDAT$S0 HB 7gg(>SIShnTK%w8yK7Ϗ^{Q3:]Cr=ĭ<엩Wx G(tAVdiX#rǴy1:GF-\_gobyԤP$k(}LCpj)2!l*˰`+!ilsg{1{ a6`Q;ܹ<T&VKt{m>~ĩ,yx{v!Wᕯ(Y\`0L9N DbPx%jes$QJ$Oqj"ypΏQ#tyN%m%ZIĶFl;;뜉̟Aюa!ΈAVg~qLl9⴦R  PZCP13 {~/m̡3XlӨ/1=fNbKOs Q3kSIU4f+~(B.*}tx9k3"!háe.ɓW?qeoڶEOTSJd'Jh[cc"0Dj:Y lF3P%O_d(\4A)fH,!M`:Yhq\40u:cIg ( T8(( !1-R*),tڈ&mz,GB) fz{W?;7[\:??a<#V.aX#ע>;Cסb:`;.qeTX[=m(B1OH$ f \4Y\\FVw{B`R* A!K? I/$noh2$IS1lҼ`'H<ιx{C_umi xT+ 4xNjui9hN1gy'sW*n0 R1ý.B2;@JIH!Zek>OPK)K AeI"fhk*Dk1h[$)J,KAhL0ԮD cW2^e];MQ/Wkss.a2&NǘN&.ΓOYYYaq"GqMЍf1fQQ.Ql9j9:_<dlS |BwO"f*(QP0,EVG1IcH~c[4fjܽ{7A<̳ &)W/e<' /GdwaK%>xN $^#աDINH˜W-ԧ^qU|gX"ߡܦylXRq[qi6uwzdq< W `=䰽i9|uY>@ 3h=QUmBq%H!k5{$I  HSKx W |$H&8GK[q[=tBR(bA9`a~E<%qPbiĩSljgllnhx(U qGOLb$cn΋/_&"~<|uEϓA !uQGB6UM,?"OHh4md1favހ4UK랉~"='ݟ񜺖/t!1m$*Hiޣ8q@^˨4EaieI1 Hr}I8a4>ZsJ_}aZ LX=$`\8sNCض22La]R%3fPȂLKfHCT@Xs &'ߺ`L90/1yz>4ӌnsW̧>o;4:TK+'hxwh6xn3Ө{<|x.󋫴#36_h~2|sW8~rx Â4nuQ`&(mh{HG`J!B@IHRTb2inv>xiX 3%9&)0ETJ7!0eRc)& l[F65ZdY > 5_G a3$q4Kyeţ;mb BQ`plh6O3e1?Oh4 4iBK.-E"X v% L8׻CQxeaSs<~%p9Z ow( ݈ް`XZm3ZcMB[ .]!2d"ffb6I3n~'/mO>yp&~e0yi4I /\&/NY Ibz$VP ҲPJO5GoPI`*-zyWĔezL)%2Ji6o+ =р4MH'p fks++˴MڽClsʊƂTq1A_Q37s/׏#V<27yנּƓaEAk-IӘ[4@@m)$ 4> "(6]L1a)]$f29{Y餠H<͘7K+Ko<;{r/\=I`I 4td1m.y%Ɋ\SbSNW(1_3%AXJxv.s-&j>;Oxa;$T!FQixɍ?E^^cer;KtShBqk3F1O4{;4c<*.v$KK|ӟ凯iwیG1ȧ wyS/aE|sgQ2D;c<ðM6QXH0- I LRV}\ 8Nl01( 7ZiaI CbXS>h8$J)%((|Z@JUL&=%(t- l`TsjрpS{4mG$YNweu%hZdEB4㻒Svw돩,-s#ϟ/*H1\GZ֚~odJlcgu= 7nN1Dg4>fNF$FB Nя}6zuw~Woru_;ӽoE4$R$IapS&j߿-^x:s*Q=vstc|kq߿M0S\d# z7MRYm[S&6MCdEr]b$ڤHcLiaY(-I $-bx}_oX?:og?~g~テ6*Htm bSx1zax xLj1ufs7w8gwЦ7P*daRP͒6ON l&>^"Vs}\"b:!g\&N R( !LiiT!I ʱL )s};'{ ]<"Ǥi$z&.$c4,tJ0Lwj͔e`(pm814?(gpJL ,]qS$ b416 YQ)Ͱq|e.^@Zʥ|S/9u ]iN>O|wٹ}wEX EtcZ1A0Gt[cXj̙9~|`G'[~vw3R$V LS\z nwDpDe<61e KЋeqq'{8v4u^7x7y!{Ӡ1č? XLmŅy>"bk.z'OvyZIDZ䤙8y$;[|{Aly{ބj]p 9'O연-;mP啘)_f&HmXRaPZs%$ss3XAs:prdp"/B,/ cz4H))(ͦ[F)eQ' HGgZxC|lH1ae8LFI:$S àVbIHstmLQ{ a3Yyr띻(XTӶ,eir6*մZ{嗸~6eGi$]J+SdaqؼO?\qX^;I &Q1E!g6i8P1qB I^G}(mP%]RDiHEaL`YX.YcϮai*%2B@+4q] b0ϫD UP 1Ht1#/k?~4MQy8~MC yZШQҧ?P.!ܹ6p!n.CBslyZg] hwG F iadyޝWҢ7Ly,'3!x ~u=N8JoxUzÄ =V!((Ua4x~ݧt:=&Y Ux]9jPRɁ7޺K1dZmݽ(b;!/^Q$XX6 G$phH !ǖL%\H:v3Or?5OS-#i9 \a8T76VNJ{Au >h_8 UN\=vљs-?xĭ I4452y!!_c6O}M*:w߁gY#MB:yӨY\b0h1 c `Veqxǯlq7ʥMz.o! ^q~]XYn Qhb!UL9EEF glee$Qy4BA%uxHOGqMϽ&$%ƃ0 "._~fAo?ͯg\ǫz̔| ]vƔؚم HHÜ>{ IDATY4 TluP֐צ**ic;-$\Z DiNstqa&JfhM"\ Q`JMZb s=^ϭoo`'.8yxZÔ)Qz}ә9s +C 3Dz*Ef G=\Ӽ[Tsi8hTJL(KFE1e15iJŭq\#K($'|$V!KRL b"AJ6&rpV e KTw0m]LmS"eJQW0 xQ-,VRs<7>^ŠӂtӌLrI2 q\-XYEb޽DMΙ3p~[W-L~F2E.c@HY6˘Ҡvpm kkkK9uI<߼OP0P*Z+r A8V /s.1ܽ.߹ BS.WIjJBsȍW|:Iq=^{ 0iAA.r{!H9j1!:Tp3y1߿xa8"+\ [i4] Ԗ<#X;w0)\RgB)1K n"-F:dyAPSIQa('r)mffʔqy.PjZdžia$8bh̜fEQ8.YnBϳ7}IV&dYN4Im!$ZODXEYƠsg݇\x۷vpNq#sbSOP15ߣԚ8G+IY8ᙧ/]6q\ɝ7٠*3=; EP8>hf8L)=% m>+cKqq7(UFĬw|& 0nM- ss=u F4TVt;=f[sbv<.q4R i;.Ͱ4Ykmϰƽ;7ߠBU՚\>yno78R%޼CY.^\dcZ]:`LhyRӏD6C<ӏ2<4!(h.hc[\w|6HV*MBhKZ-d}mvۥ,aHc&QL3"o_FRzJwpi#<4EIw[ӝ%"ƒ.Q:,,]h;gU˧ծU1f[Ro;adZ(1Y:! $U_\Fi]ܽw/S'<ɀ21z ٝҢy/TE =4$40NJF v\_i_?"K޹iR%/D~BRt@ *-8ǜ;{pyf Oqx8TJZܳWX\^f{go|aa7 .k'X™&zwo"7o˵x ˑQH RzmV&f| $"46'0R6|ƃb2\qb KTJ(_\<[7⯅޿O_޷dd G#m{o*FAq$UY`@hJNōָ<'T$5WTOrpp@V~!Hߵ`EZMxD%s.2H FWbIU_wiwJtJ)K*1E-AЦRK9ڍu^ I™OhƟYA^ČC|1S`dE TH Gl ^bTJ3Lv(S"+*=Up8J)J8.F;:ZUi4a4S %1`[ҭ-&qB#dc,Vh%0۟SDFBs_`Z ja,O) Ɋ!m.N-wp*}̈́7^şMu/| F 5;M6>m> :6^`QTc4d2µ-RiQMd%p<]yK(<ϣxfmh9~ CYVlO\Q/K337CO2fCX?>z/8CRJ0Jh@^ #Z98u<n?_ .>kO\(,H:s M<+}{ gl6H a[؎%\,*utB#$B! :e: !+\i!vV5=v +YxSuY*T%˶i6=oڮ?8E1+&qbF6߽,B$lĊ4~ߩ%),НmGﳺ66ә*,\BX-vv' <%_\_Rc.~ɽx<ٸzD']b&W._d>"ޥR4?~x03&Yap]0d寜dp xe,}&CaIEX uDQ*y^#\7 Bf$خ Fx>~mzi'-F2;<ZY%®!tBUTI٤p &A"Nʵn00K (?o5( s3O? }xcLq>$G$i5&1ŢWUƊ$ēWxp#_o2$c>1a2, R(2gsg$#lz5(A"79[-@ZUe#$eUb6UY2mYԜV@F#O*|LQу1B4񼂭}*m:YȢ"ԝXpĹ3_:-$+r}{/t3AC*]~`y͎O3P *\KbzH c8HB:4]+жT9d0Te%,ס.(v,F#.LȊC<7qK)BObK-H1 f'9h_ y33ֹ&uF-&ssM`{.N0MZDUq,V9qtc˩k!4LiRN׃R:xvHc1FQLK]$aY4Z|TcMo_ [#sFyeXOQj Z,22ǑFPjEDIAUXvXD1R*|s\EvvU9H$5&BqdTDQIUDql$+7n_gs7n]h]W\BupA62(iF+$% FHcPbye)R|E%FD|AJی'GgEwd:W.0k╟^*S.w!,,ίr Fhessk>|zãjb kj.>* ˫|կ{)#B_BX]ZHL}y"m?>ʏ"wn_~6o#{]̙&Orm!Lyx5ʪqOg4!JD۲ʠk[%d!'NM3p8ܽw,)I]٬,rS(#NFƛ젪zxT{| )&.]Y"lP24C@](42,/D-\0Mȓ(B&8SslCrtj=%U`rBƱ  qrQ1NL2jG),#,[b(IBrtY[ky(t1ų ! k/;\yJ"mM!%˫iq RzcwpsT!t;4%%]|[++KlQKe:ē3i*XYg Nϐ^KG"S#>*`'T:Q":A%F$EDz(C$NK!ZҩCA(]Ƴf>]z_WKKkO*-r,9! UO=û׮Rcl0rTVHۦ2@Z,0FeSQ0\szxTYEé.wpaDlmnveyI 0R!!I#tYLa@~i:%GCj6!.4 a ,"MUc i6BقiOPQT1*J S"MIHC5Z0B1R?zdTy+5ҶZf6FWҦl4G5',.wLGT֘n%sh58Ar\aU=lGNA[ql%Mso 8ؙ8 {3;SgpBQ S) ![{19w$~el&KST>a哴}IV(+r k3Id[w} bue017n~G71Pf˧8v~atOPƠH À4+- J"mQt3vZvj]1Ʉ@q:e?m< Ac*\EE4HGdYJ+l7  RLYQTYAi¶FIIi@QhYW IDATFWIC>:sQ=<@x9( û׮ {^g^xܺu ɲ0dc1ҨQDwfAE=!rtL+~yWmixA= XZZ歏>`:ĭ[7OyӸNYZ<r ',f!~DX/7YYh9v\o'&/SƵ%hX;f}:RE("ea {,.q(3͌s>߾EP 8vYR*}FЛY$HYΝx4gfy癟`%^߻,`iu tLZNȒ(ѲdDh\s0ڥ0qs6GJc( mck< #xt΄(9N_#&]Db))) N qz3.B b .e0*#KhcEQd_Pn@g~`篅_};`44ƒ%ܽgrjacJdؖQ)8v=On6R %Iꟑ{غIdƍ)՘2Ӝl?omݫWit0T eaY=6CvW4kkfOЏ}|>>^p;~s˼}mb6GGvn?~ap-g{5F=`^ p@^Ȋ ˆi"J+l鐎SKyZזb<]VGޡ^M&Z;OM4Sc' ]~;fNIZNii60BFZU Th[O\ס?4.1Elmhw\lW;ɣTFŶ\MY=9a69d]޾ R< e sMVVq8%Pu,_| u zEUJ_"K#4¶Je贻,,.pZ]ܽ#ԼfcqȨ9QoX)Y^d8t%:EV6RBWPZkTGoٲxkGQPcJeqG6EfG\,;̕ 1Bb<ޣPO/6tepMD%>q Ɠ>Ey&9}m2db4.yE(fI!gg)JiL |aq `Lв_# :y˝ &,Wh(6m( 7 Ƈ-z6 */)`,Ά8l/>s?%>>&YFa.~=;`sg̴BxРLI2J1'a~aLREs.8ͰX_Sit}/|GD8< Ξ^wH7 7n!ϲkׯlC(sEE1cyӗG7Qt i J|WR./o7xx{4 ȊOAVU!mhuY잧xH%F8"MK4?:SMߩFP`opL4i $˃w?B/e?$b>lQ*I6Nmn`Kp4H0E ziN&VE2ŗ)\\W_L! m_@QR |FJCyւ2_Ʒ;Uԋg>Ib HMqv !Nt;%EďW8abTEKZUXMQcyiZF^feF^iJ4B:>ڪp\J؎ h>z@!-R@cT%q-xHg4j l{;)xQiC*"ų3mSqBhlkltQLQ1đK` 1%ӝsXF1[lnȲd/x ym6 |AwͥKѸ¹Klnn0ƔyLZ:5tfK9li>Fkv]TȊP&èpH \cGSUelx@K*UОiy>eY,ciiÌA1Y Ѩu*\m<;]{:E-Ux:#aQ!(m&v47H+ؾ@O!N ޣX4Gء=%YK"᰸q4ئ mtD.iavh4zxUְ<ӣffa뷘q(Ӕhvu6ߧ8 oԧ?w:.O67!ܻcz|(xLpY.?waET&A:P ZgVOwA钬c998N :#z8n邬H0"A h\r*4^hv\ZJ؀͍-n޼\ovM9Hb#}T5c!*RG\\oo~ewq!'~zSds;[V8|m2j F@c%wW~ /43^`cwqxA$*9؇)=z­{ɋ*Oc~iٿs;smJAtɢ>*ؼK4g/,q{pyΈvh հQ*4VhӘ9fss w /gH{1{[$q*UAm(U" ,mNA+5ݞMEeQSc?:/;_ˀa,*0~"ťd\p6k7~O}J =/}~[O}V9izYAeh% kk=,x-hB=lQ7h zyjup0t`qDip<C*|ᢍ +r,W`>>w,<_ڤiy (a"]ʥݚhP̣;q8xW3Z5,[p:FXIL,ۧ* Y'/*d%Ν#fvG mADYaOc,mW*+Fy<7 J&+p]4->)֔±6njfnnfG~)$~EC2G؂F#=]yBe8ʡtY ^_[e!Y!= CN!2 ߟ硵B ݞ1ES(HshsYZ*uX`g6yf?eyooyS}>Cݤ$b"JFGc#K`b "AP`GbnRd؜=t7Oꜚykr 4Uz=5+[рkH8::ϋP2[99~Çv_}p.gx1d>|qt|JÙ{GL&Sʧ6iqV=9"<,.%е, jz K˸.^oU:QkfE9޿YFkj$agk@[0Qx͍g9??D#LG@gkcd3Nҡ B( e&AxeQ \ =G^ݐV&Dacib$8LPbY[U>JIf! .]ϠMuCf9<6AQVvDQDa#@EC@WK{ K+8 /^S䅬m{iGL&8,;;,-ޝ}I?|T0Ex2AHXYۤc,o`:}x3k\yꆏSDM]k8A*pMO5|Ҥ+&e5AWcLmt-qu'-4l‘5fc0S(R[ܺu!,#1CS1F3X vwV}()ҫFb%dz>Y ҲxH˵[4YcM;~|a3xYC[o$AW/wLGXcs Vj旿 (Yqq!j"`iӟ~\|9%;;=&)YZ*IF7YY[ n,:NOy?d߰̋8c!n IZ+,5NXY k 5ݶGKX^X1y& &y¹bDJ?8*<[o}Fx?Boeyo~|]7٬ BVMQZO(NKV OƄ@V4h+\z[a+k 1Bj^VnLܸ>ƶ JX4yyAUu)"*T5qbip ]E!D^w#B Y^!q# jKd391YX]t G9/<6YJP Bi, sxR"G: #$#] )Rd æ˲偌#xrW,:|ٹݻ1!a*acU7Ej Z̧%&tmf?rg &nB=6wL3ʼրJg8gXZYc26dSbC:j~\ .tR|awEEQ(AQH Pۚ( |_4V"ȗTqvq|>A!(8ɲ+j(ڤ,xb4Oy+KDa _%$=@ΠKQQʁh X[!;W?Gҋ (Ycq8e($Y mku|-JQ߰A(Z6(!2Y0O/dqËȉV0Z 9OG ))Kt:Wv9*7,,x/T` w%>YX%RnjX]ipJO:7SX^y3><ʕ5>0"EZCd- 1MkN& t `ƣI_k'ƏEh{wRA ˠ'ҹ[X_ ȐW/|#]FydEN(-ΒtcvpknNll<8GXJ6񗯳Νޥo#2 o$a\[9VWZvtԏ5x*5/1!} SVT^h<ޛh;/h/g?Sy~&'zCw//pKMfGDWMX%15~+CҊFm(./Ȫf5yg`&\PT&P <ܧ]T\<̧ݹJ0h& l]0F`(ӔCZaDH8m1 ;% IDATxxCˤP RѴ~H(§kfa0 Pt:bw{>q@JI̳u](TBUEY\\᩽+*],Pe)"a:+J69tٴX B 4Ym Z0!R%'$րS mXh4f)t6/(mk Hk&N)OՎ2(-6w9::nsqjaeG>A$N (uFYLt$^+ϰsP3bĜw e9؜|HVrr3ՕA#Aے8()/:,RFɌٴDkJ|?byu<ϩ hzB /jI)0. YZyQRQ*RV6ݾZ0 g06W mrjzi6a>#B1 <|8 b ]blv|ϣ݊z)8>2 /s&g&Sn޺Nk7Ffc%#3d'_񜵍MNO/RGi_-ӳwyt> U>(%PNfuBHYCFlNʪkB"OX6 c>iSaF!MFMƖ8#O? |ze)q;K%ؒAcyU3Ty 7]S I.w8bxdrJS2YK%C8- uUH]WP?_TՌL۴lJe Sʲ`6;s.mRꂓ$tNΛwYX'_ɐv|Kz{O{jV~xdwO89=L'?ʦ#NJװƂd@p!qL%GS~-.~{2AxFI;GM%'YT'Sxrq1BkR3 ~0Њ[2IIJ<<)9Gz1B>3`{oC>cy3~}˕k^ӟx̦'}D>=f$e[˗{|W_b}pGGd)Sǯ闸:Ȍ۷o0aC!b2%Y6HJsq1֖O}zm9,qM*cp<|(.GZd8#g'#f HӜѐlFZ +;[|)yh%>~\}9`i0Xq,, jbY0$iO mɴf>)s[|7KΏ߸﹟F3-;O?Wۯ_;ed6#:AzJ$U]%谐Xp<EsST,Bkh^LAPTrPU# 00X[ J8$q| J]Ygs5"D2:JP~.фMױQs%Ry*dVGYTXP^@eN]AQXL.iEY|E+L9:vtFQQX㈂<7J@X-t:}E7;ɈV;sjSb6JE+nJ&~f^ F@IC^#ŵOD*WϱL:OY\Z$ Bt]' n Wd[W{uln+i6-cVfXm%()LC*#|5M}P8k(n<,4U`qq\L&3 aee(paĝHHŊ$%p, :xkQ 'ttiw %Kq胒(OQY}")ޢ#̳)q^yc89> u1$nC]Op ,A *m~VXf! DtvAg$5tRU U Ўz#3|/'$Edc2gH39lo>AB?$$Z|L1:Evyq`mEZ ;>sƙ1!l3˽.Q@Q|C<_`\LrL2JVB{1b61Kgx^⠅֎Џ1JJ#f2XYS\,GQ*Kzi|yVYj"_+;yF-B1. vhy@eHcP T^3 @J1^ͭ] ]R]5k!n,.鵚ɧlb?Պb6) KMuFf|MuMe!~鐍].ptxy<((&W0Xʲ"P <k]SdyZr|pέ/0<.8=]mбa22Ω*6 .8)RH$zxb{ck3HZ?Y>[=>!үfSNY~iH7t hSe9yy.I4#cIlnpp*жX]2:+rew~wBk$|88|L3lb 'xnϾBzmu8@ks`ه ,m*3?%tV-(O^LhuLǒ_/)wIC{qn=ݶ;-+b!T!ܸۛ,w_u#tgL.,鷹-iՂB’`x3\٠68" >V)jdFH0 xʂRDNɔ6)NH6/m‧n1͹qe^ > S^[R# J̻?8Nvi[ӋmNEѵ#C:sԅV ,z\~}̬߅G?=,QX#!R)t1%NZ Lt; 7dB9^ K3lv+!n L T3(*`iuBXNAwEUPY$^dRJ0r Ԥ3|6*_?_~ڵD_;ɳg{ܼuO]%JbPR8'/aOMCx,Se8“ Ov\I> Vt:!&TAwE@U3D@|st-Eޭ]UHOu%iQw:c<R9Z8$K0=Y.;^ǩ?F_:`*xO}GnhӹesR <H5u-JŒ,D0K.0> =''$O]kp "[8|/Ij.iR (QU*e2( |*]6N0FF=`ե޻I#I|W}\yX\9: lΟ?o_geޕ-NY-D+ yL * ~O}c, :.st)kCo aJ9n\ccN|sbC%.qOBkԄA@'VaumKPi:Fy.J5%y$>UY #N+Ӛ->hij^;|QU%^/$jGIH=D#FCE1qw<|VUWXX\Co1/kKoW?+f1'X\LWxtdT uD>taQd0QG|o>޷Q^<:G]V#(ʒ.i9<p UZ ]!dxjBS5xpNմp|zGG31U&h=%H~ :\r~w6Bݢcm}NQsvrNk+aC~pd&m "=A]7\:]gxAHDx~>enN6YHxC ߍ0_: |3L-h' rJ-<(_QW,xMՌ  ^ <'Zx sD<%| Bg%8i24%q?'O3`-lmxϿJ[#GgѮ (g-BXPN!d>*g] 66`Չۯ${gss|M-Uʙq|W3| N!7Iw][!ls_X}e̫ HORm aN:]:%&/X12zDM`$e☕80:#%Е`67㷸tyHJS0!x# %&&G K7.AttFi4+k<<|w"tֹuE}#~w<%˿O :}`7YhG&G&eu w?H%Ie:Q!_|o0qG1WcC$%皏>:_x'0$TirH,Tt[-;ޥ-t]px匃5Nõ?eﵱBc?찻{TFc+\h(`,+amݟ:˿?y]/W1Ʒ?O}@_SVA(M~/&I@TvƙIBYHVfkZw=Ϛzo]ws`N!IJDNb|H9Q8br Y9"+,l'` Mw=<|;k^rȊ|= IDAT T%}Z߽6(jMZVTmK44(A(2o(22=὎D{RmZKHhG '<}<㓟LF FCz%yQ-g8N8& y*/:_zjNU/@n8%,gtCYHiH!m[&UwAe<}z;[{ӓ4-;{mKSxk($Rzif#]C2 })P4lp0ڿ IPek<$5,E% EVMtiL~FI!\pgNYnRBZeB`ő.q<Պ)Yt=<7n-ƊT-%ұ(&šn4EYhExj[ ٲ; z1m :"!Nvt%ƓC/dYT au?Dxa0veEhM.؋q|#5) VhStz]@R75cϣ.Q]m*0Fg%>01(kXV!V+h+ ٹȅ Ws})KlEP q<)֮TSr ޘ3ꏹ|2!-(*eYSmmK$YohM`3_Οk81@Cնa/ ~=ʢi o}-s%% I۷胏(͢Da>d4?WzA)>s||B ^Q U Ҩشpwݞs,%ƖMOEn /^ڵā-UYg'*aS{DQH;7 YG̖0TQU:\qǟX,U|!ܹj Ysx8M2{?54p4`0OeÏvw׾AQX%fyzxC75EAݒ!uCոQWWe4tY{1GRwc )U]C^*ҍ g8a{/a.-)nVn-Q ]dUB6Ev+4eѐgy7 u~??a+?m3[// {~_Xip=+4.]sh<Ϧȩt 9#,a$8ahA2`KW>޽j=ɓ|a? ݪ-RoY{xbѺ횙X=?}{CZ!dx>Ҵ R:dYp0Ɠ],p( 8n׆cvyV!iQ e>A)n]\U1y˥K7AAxeN8GU('=$]+(V j$ a'Xm^jףUעb#]h@$A4ḏbTBX8NZ HțQ ,ZE+trvQes FBt kZ"qmΦyF^#Ymf E'A7\ZE HhV%=mVwZ%1+YMI!4TS!FV`Ƒ.L1uX554Hpw-yƪ4s 6u%veFC޽;!S@ǁ(ڶn+F2Hƨ!-T G;;Lg_5%ߝ[vk p=b70(Ң@K9$uF.JW4Z3X,2 4|/޼ÇONS€pttg, 7o[wq/n2ݽa265g{;LJŗaDkE"_1;YӴOZ. 1zZ=j2ʺ@ۆ̱  Iu '' iF^j5 Xnp{a=|/X|/"cڶެ3bv Ъb+Nӊ8 ڵA2"m $Bn2fg1풸/yoSV0qOU:ԕϰI|q nrKڒp'v<;:ɸS WSs-BxÍ7x)|tZ 6YAִeK)JOjؤ ~& G']`4lќl 1J;2{,\$lnwd2fvvƷ2k$a)R^viɳum`ZMh4*lO޿J˿?m3[/ߞ?|z|Yֆj\ņU"/Ja7܀i#r U8=>T%΄F.p-p·F=`{1EV$FJk' }L 65UV `|JSXGHַHBs_{}?#@rmӆ^ \t~/&vH$U5s$UU]'Xڬ) UUqJEgµk!9w ۗCQZcABUWQ-uՐ%Mۙ ~ѝXA-UӢE?(ƂQLF`p]E']zO荈J`D7%a4p=ʅj/CSGlsUS^y # tEK? {+?ݸ t&[uY^N+jKH%8!]{q-8 A() c UPՆh%A52)zm˿OAy~oJ)iV|V)]J:+E!q::r6^ i^q|,=vv.4uV%A/ys4'GO(򊭝=Ɠ(H)+(pJ5 %#7O4XkGKI>Ɵekxc<=yJ6LOE࢚0Vd-Zvz(W 7/|a"#8<5~Lx.Fa<!E7J?LHZ ggEOvgt!$*Q)w\\sƒatp@xݰЋ{m9ڢ veZ ŊFʐ4t0a4M~%*!Ҷp]NmYvZ+)҂rMXm$[x~a0JbuH*Wd#{.{?#NC}_rU?mX ZU9{!Ic2ޣN-WO6Xai9d6TelyL5ORی߼_h:VQWO? !%MQR]+"c2 Z7t(ΪHfP ɻtIݿ6`ZN|_'N:F^/q!78x7#vʗrz\E>y=9G(9'-qe ׸y-b80\⤇E4uCl#hREٔ MJ;/۟`):H]`]%VҝҊWnf=?6Aj]x}Âd­^UWja2"Ms?~6 yU1l6N4kZJ M[T6d<٦5ŌO?!Sz&ggOأ***zQH.') '?4-٬2Wp>o^࣏#rSžlyFdy믾)HجKm5M۲1Fՠ+\& uf8c\ ږԪb65Uk)ףjɓ['<;2'$ϧ?&a <ҍha<]6y}c%;{ [ ~V2%HҢ`Y#t-UQ5%u wMU 0*Ia!#iaժjlU 'G8! k >F[0hIh*yqGXn98ܡm+4*lFkJ49B4@U]Z |G\cdZ=?"/g4yhpo\MbEch +6ņ)i纘 )\D*]5XAYhdM^dnCh&nZMlz^4bwMAZqu>1q89=Wɋ5՜ww?zHUx#Nh!~ ܟ1Uu6X]֊8qn"_=+ʦbXIs!bW 0q >Zw.UULth^Au0E @:4eڰX8>$J4_ *}kX8Ę *TpGh#>E/ǏY,g!=d8:?\Zz[Ƭ(cp3fr`r Xz 3pG[n9;%oS%A)AQHWz.M[^=m-\WgFQ7 2 o>҂\>hy.Wrp~?,sVy4=~/1mɋc]2 "|3l\ \`eKFz!'1jn ;.-Y6)0Z8i:E8ALW)wVЋ  ]zMKiLK[ԅHr,~bTqٜ2("3`l8-6a8Jiݻ RQWE6IU" 9ػrAXC |y)FÄlM?e>CK7.f,9sfVˆ8nz>fͷ^[= p>/`MzDcT#89}x¹}fӓ<+X/rvv)|CEPU-q|d2jht!ْW_E?{YmTuCQVv)37?ׯ}OŐAyW[_??Tydk cH3|/@<*'q桔KSy(#DK'֜=w5/mnn|Gtg'1a4Ө yy%4|pZm~xIج3O|&BKz}o{4bὯO))l0pp)ZX+ aKk=f^H eώ IDATUk$Qsj=PɖTٓV.E t@H?x^p9z杷pv/)֛ |_aߍtUD.JuE3]Zo+cXt^yP#/}c`DW`ES%YZ1?d4c{Ϟ;&+Pk@X4h4Z>'' Z|}^6[7p] .R&86_,"lNXOcCo0"-6h]5yoZAsy6 c-n(-2>7#e,!pvZ._䄪ךW_}d$⭷^[6jp+"/q b&;[ RxpɷzF\?;xgvkq.۶C<#]v2iaHub4A  sEU8t4@QĉOK$i6n3acG@z8B^H > }T hT!,x0t!MOd, cl6đ#\m-8ػ,%3w KpKLγws)UJ,oIK1]0}7>N.jЪlI]>(* #e>]Sf^WΧx;ݹCjj.KzΑf+V%'s6pb ?Ȋ_!R&N<;Oxr?Yvb1c:/ieù?8ɓnY-jC(-7pqY-K_~g~ҟ;痮;g~g~(788:]$'iZCiE$\,9Ú^?Ŋ$ ai\Gp||`#NSs)|x4 VY:ߐޠGx016`%˲NǕfXqU&[L'JNfy+:gOȫ(*Kl.mhUݝ\4b Ev照O!jwq]Al60) itiE)~WE{\Zv&+TeM 5H}w=q]{C3'[Z6¥boϞM99:Z &[#:տgV3Gdg0<@(h~<"K96Q#yl'*`Z5EN/N$$ #rv5.nY4i,w^Ӻn/zH٬VPq]?ɹ*Ɠ#O^VUtxWıF *kdд9JѶ+2 <<H:O2ԍlu#?pzx[ccghѵI}#/*^eQP)a$O&L+\/y䘼ͦ ްjE!ʒBÈJԭe,h|"Yaz:,q/fٚ\l~JmEfvU^XqKT ge%up]Vizc /;}H Aƀ5ƃwiEj hCv{hFdEl:(J0<(FS-B;$1J$I{Ք =>qhe<2O!$D!Ԃ2d:W^gLi,CX<ڶnr';BAؘ~pKk̦{,ΦdkEU@]-(#Hz}>wP<{zFF`[#:i eCQeV]ԏ0F*. o Ŕg >#B/B-ٚ7obLf3cw/ƭLO~thnч = xܻU)KMQ  8![g4Ah`ugz:cd/?+_ݿ;wN~oWlY d4( M]QY݀BH0(l<(1rw&OӴ-7n\tvĦ(lV4ψ u<]z4A=BѪຂQ3T]=д))pY ͂]l k[!"T;ҹы,30FCT4`:;# H8t6:?dSbkUet:}YZw% pjCF3DDKYpGm|v###0#'}xxC|qwK"$[xtݭk\x y>ң; ^'ˆT`_ޭRCt 頝Zkn=(鲟%]gA@Zb•M,K`2ci]7T0huA=D]:$EQc ҧm${(6t] }k7^i\|g@l!ea^N9>Rv2u(rE/yM% ]k0ab8C UQzN?Ԛ2 z}Iҩ 0?ۗhp? GN$"]mFv5{$}$ {nB1Vj.qEQ1޺ 2b4٬s<|閤1)y%>tF#,A 99>rTe :Gz\"Zbo}]T!0Mmx5$ѓ#q(RMXܸ~ZJ=#](a{gӳSv( xKx>cb5e)LwXM+UПXny/3SeAkI0 mیѪcRb ^& $*6mHX3‘#h&^/2"q:#$ Gri4YV!D!fݠZ'5^ɧ?6I]+|E[GnPmX.^~9q? ˗;}em^:{Ovk|'h0ɀ7?g-3 MӐA[ heC]P#V̦AS_$a3 8=d,]qv~G:l|?s uǼu4cM-g3s=w5uuUWUWuu&LRl'%8e)Q #P$#F$Fȱ`8dN0bd,QRDʔd=]͚[w>9{ʇ}|U|s޳y' k#Llpe(av T0(Ybe(! |Oݵx㿢Oo 9|2!J ܽ.cr:ڵklC,Ƕl5jE`v:Sg41_`pxF$ƱZW6i7:$X̗Hsa0iUAnJw$ynjwk 0߹NFY^r&.+U ]kܼ6_DJ|F ,«y(ˬF9O,1*?eiḆlahr>[}Ķ=&ق|L$& S`61.0-BU89Nx "WLLaX&/߸>Y@*T.9=.>~eJ eÝ`Y_w.I\RdKx7xA0u=t|UmESWިg%"OVGB .nXLH0IJsRǔe>e(m6豻2ϟ2/@$eb1լ#tl:ö-IJ+~%lкڨ/eh@Q9i-418>YOdy}8ƫ# RQtN Ic0BK.}1TfGi-Aq56Vy:w)Iie1/̇r9c}cG' O\AkO8nzr`6z6/*auFYqs*Z9Y_TWSGqwq\PxnRUȑ4YSvPc˒v&Ɯ'zDF Ep +q,)2- i"t7qUv:/\H١YabQ*$ Ұй%4\w>C`ߦj`Ev= ]<$UnILg#2CipiԚP!Ï/XY%k)deN^>A)Ǐ8E;>~Dne4e&BK<(lRahAͯ)%%ZXQu DZ0MWѨ7)˪a,),Mǘ6Jf%WǴl$4d-  H2_H(˂FNҦ(ihW4YpxpGD?`kb>})q:fxah^y1QYfx*J)MFcVZM~wy$hQM.+rDFU߬ˢd)Hack ҬpU%PBl ˪<*S,:j-)q ?x&e8ĴbL0<>BWj@6]0aYL^VUKz))կy TibQcfqLiw̘Rt:he\q`(SȉyRo7j~|۬t-\'EK,F3&vpnlLͳ(KM-n|NemEN9:<`x8fjLuNq>& Ǽx'Os!a +R_''n_oſ pF'qh:um6nxMRxb~x|HQ|F3|m)|IwE]hY>!Im:]2K$cESj($a8V%IRv6/p p=sq2y@Y{, jv( ! wgiX@ Ƕt^<qӒjw0ئE S˄V{#jyY.nF6Y0i:؎K,%ФyDQ&J,Cc:=1i2xVVGbɬBd)dueC(]Amۥj)T"BUHnz<{IFu| |KhL#+uM So CxFYƘ8+WĔ˙/`+|ܿXB%:]\|gGz$KkR&yfn ~.s<פլ>B*Q1IZ>Me9N0O[5.(ʌ(xzç^k.BH,2,99QNO3.,`"/L456y)Ae9 vs_4'S67_szZ&VW\K.^A/QZiP)I&yR`u,1$/۶Ew\>k4Kɒ )*hР0g%[;fi<"1 KX ZzR6id`6i3M{JlpԂNmsdgg&WY  ۲DR,fsg}dVVkdg<5x4&BL[g(x'qx|HMCHʫ>x>xx/l6ȊtGOɑx~Vh4Bs۸u.v*@Шp]5LDBL`Be OȊt9q6t͍Uݽt4f9W!-+7>?2|2:O?Gg_wOx:b,K0+iJO!oQH,I2!ϗDWs A꒫ZCYNRjqm)\LVm~f1%Y-]=i G'ô)&E%%Me6Y.fg{,G<{ .`4֛ƶM,ChÀ~VOg 28kZcZB$qiKeW*1 M8CRq|!0Y`rL&eI9$iNVhכL&C66IH%YJdluq-4 ˴ywȓ'eAƶɳ=įոz*ض b7#˔7^ow(3-4%ʭ~a&,s泐^(*(%4W0 G gڣB!ȋН*QE=rH[ pb9d|RBTj3xDE(2hP<^t{t]6/uXF,ikl ]1(YcY]@rz8=S)g&S8`H)L0("tm(UVv YFSNgja` 8ŲvE,aqQd%FvE OKL$OsLê,(!S)E<+q=4 )y`c09(J]ǒ]~׷(uJO2d bX*ՙbIkVm' A0,c8¶\,"1OI(I8cylmlSC%qP"'LƧ>ARVED<) Sd(C#ʥ[ys\Kj;4Z/ G':Ix W_NΙc$n.4M\GN~hsquZ>1wK77pX[/%ϾAMNAH >%)Y*hhm9`=D& c8" e$N9xzgpMo4 !0L0Xaq"(!\׶{ZCYARdEiUm.]!x\[c[t99Z/|,- Jxt8$u6(Eҗfqtpm+wr2w?GX<;h/5 vm\z7xZ2|XJߕHYLH [B,ǦVN<~h| 3@uQ&54K+GnwkB QkNC֋X{4M0)$Ķ˷yTP4@Ԭ>ri -TK,-ee9`h(dQ8D MBEq,vCJi0@k'omgY@4MJU0DWpcˠ))%K|SZ--X'A|bznsW_Q!^MCFGLg{# ,d1]`Ơ? b>qٛ\< y>(9:|D4uU3NN %JeUr z@#P$ *01BX;vXߠVDz1Ed#D M%4ɣ1j Ջ˜+oQ‹1 c<#g>M,3dg|$ pa-9RM4-i,i< 2 Hc]OSn-C1B/рiX`2˶ʤ_3F2ѹf{/]{Ɋza4%y9 yķuN# m%\|Pk8L'd,'K\GR0aP]kJD9kfPTDIJ-lD,՜f, 0:E1oߠkݞ˛?*{f䥫/I0X]eksoηiܸw~7 6hAf^̗.[Qw*׷[mR9Q*%I3e9>ql5< M'(] E"G Ofq@x`>LIQqLFgq/"L>kp[VA%ئEI4%^6H\U|=iU{ خ&^񅷿c$Mc,K"V8@a->߫3o%a٤|ڭU޽j(EkEAq@i/Q$hpYXضGRb*xKlÈ[7g?MIp4$DhI(J9;یc*X]q|:%KZi^»~x`upJt}%Y*ϣJ`krp:c}K+89yxtBGlc~sW۲L| osz2~xTOOO?kE6 fy1-/3 L(# .^z$WiFQ*[, Q2K\?Ǵ "LJ3o*mR|oq\{mvIKE =ZgceI4B-=LB @WEVg(C3f Eat!)3I~ PRX6|GuhP$@!0灡X,攥$\22F>d4ږ|<͙Mׯ|?3ok舕Y>e q| K*<ڍUx:T^DlbMt,хcרbޚz!Ϣ f1[*,yX),)Rv?KOrY]f0D K/qFS.d%7.ܾç)5}"ө |L00_{:7Y!F X M4S0blN#JlۜQk-O!DU1 Tel6I,)IHOHBW#nos.q2:5D8 i6x:~e87s:.pmID\عbb1c:=s2vL0qbmW9>>&s8'3Hk6tY0Θ. N&9 Ƴ%J 0awwM~vS]c9ptہk_t?oq-y]ݹw# ,HX5gBQ$!vR'HC10EdX!iS,INE:DN!W\"'dyZښpqLUõU#qCYXNҴdcc~y^7fpǭst0ak2^tSgq:a~AYw<|0$J`cO~b隸&YN`ϛ?q|2bE)%imgpi nVOmل|Hˠ  V{̃1p"1YXb Y]'WSkngOsx03OOOO_rck M0ZtGáYGc%9ec1kb{MRRvF[/ neHkh6;@^SdC?|Ged2$-͙X̖3L[mM(Ifr&#Eri\KNQ(:]tTRK 6q^t"/4[VzGlnr۬oXWS4@L3.Ĕ-&߽CYh6{fyɕl2' c {14x#L-Ǣ\,YNcpa$i@a:U|2`{^qaϞMtMB2,1'Ǐ6.)I2i+rqV`y'EoqOovp/_))ِ0QYhSƳ!rJi3_1? Ԛ -B H6ɲm5lsp*B,CQ̧ =pnw >wBi_8O,xJ \0)+Ƶ,McѬ;Xx4T<Ϩ>|wcL9nf!YoG1C OS{]UnYRo؜tsG9~|1tbn:/9^!/BZ# ljVpcfZ+J|tD^dYU춺n hwHGp29BXjr2eVrp^"&/̣1jH2"Ng؎Ư xAıV󈂐4),+H2ť؆<;89CޛZyZ{g>w{`7b$ I6,pAl#ÉXb$$` d Ħ-YIqhIP5޺=^;/NAʫ^u~ky>xpt4pB9oSׯztqpx?7濈YUj%/pERwm\{~jV8<:^ (ˊ,"`J٠.fɳ)B*p L'Lg^ZY`:Qs1)(bJ% a$yFzҢвڗ LJSNNq(WJ *X]٥,lNguG;S0b,Pll!R/ Yief)hN-1m Ĵ.aioGqy1ôJOqᳲd0?ҵ]fQ@5c" 2(vu\D1y90  >L}tԅZ# -™ޥNw|x^edlmIJڬ@-K6O5^dTd<SiI*dS.oej7tr<«住I=0 {<9z!MfHlBl@HEbeNewU0;=dzx̛,W' &E: IDAT245v5<ߢ;"+&}kuOz6q, dJ,5.7M.JdxMH ZH4_C i5A׸9Oxt%k6o|ǩ ɐCt ,}t9(Xh*XߤT%H슼(CB8WH 2ɣ4$5IJY__⌳3fYzB-&%`jm/WƷhޢݪsg%*Ҝ8Kfueoc\”6EQȂ/~u9xE04?AhkG},BW%a"D; JɊFFm96ymCZGE<5 e)l>\\yRd q^yuuh66(AQjJe`ym±KN]c]G, )C@2<:&tN4rA(L[pi,s>>g0:$&GHDNG*]:Q4UQULJi:ð4I[$]f]d2Lۯ2iZ[m0a0-ye2ɐO%|ِ}_}P>zv/ϖWn"R.tQy! `2Q&%e^ P]&6C>I~N-J1FLD­79;?HBgUAH ,%RI|Tf-Ǵ1eХKYG!OM6텚GT`ӾMrкZ9*P[3zYpFpea`HpJn4XVcyytPDJ)MڭI0 d*$Ig 1V̆\ N88qJDhTTXA^<9~ ' hsy&=Glo{ !%APjD􉲒zk `d09!IPÂR%TDZ63=d,cMے.F"*/%~ꍯ!f~Nh 5.qrzi#rHL2`w$]nsJI6( &}4 Aa LsƳ-ca ) Lwt:û<GO4pyxKHoNI.KLKP1VDA7~ި- ]@fv-BXPil"*0tQ{ + i"%!TAZ(is3\σJU@ Vnuf! 41yk_²[dA ϦCT)BJiXR"+,c6.O ^x6W\g<S=uN4kUR!J3O8<޶O>{"ۄFͥu)$LyO0O ftxt-1̊!/UL}.Ξ`HE[-QB32!u6%O!4F[yUxaTؤqNqi .~Zݥ, n)U QJW艢]ͫ/d:1sxrAJҕO0 6ti'xCF}?o+/>y? __ǣ1G8goq>WSo6TŐJCψR00MYY`2QSŔz$&j**Y>!P]P!U'ctcŌ jƵK\ھ̕n/W(EEl &v ]Qd8YK$*,tOxLQV9P^Q5Y{g[ϼAEnIcY iHiQ( zʙ+B. ^!g Z<1#}cJQ(vv.nwQeA"2#b"GT]JXsLã(˂RsMVm:Q*´2Gh:0V$YdYxr{>yrmNN -(T,1qz0mMsTOLg#F zc0?b0Hc9BHQ.l)Fm*D2 Y>c gimZs,si50d )`b4 E,SFJWE$ISڭi UE?BA؎_k1g'gL3iF.K+ll29;푅Vͫ~oO( ͕;lVAܺuaZV^|o};\̓%%nMqE}7 NNa׷Ѕ’*ϐ@26w6#NZDE Ðaj ׮>mDI1+ˌs.y.M%0$XVl.{488EvIob *WDQL dzqq ǰ SeRs QTzѳNIrg8 ' ά -^|Ua0d zןMkfA{|pO0D*"wwEo?͏Atioso^o!MRZh.٨S8$K Z vǽO"At ,pl ]-RH(2t1*,12UB !Q4wvL$ F McaL0<&0{αLAAc:01\ sqeV jixk4{HeckCQ&D,0M,+20LsqkS嘆$O3**jLƣŔj0-4/Q@X L;P̣>/&m0d % i^aZ&>xxlOP>1CU8lmsf)''tg_ ~W~縼k^F*cαyQR`&Rs)TI'llnQo)$SSzF0OY\5XYC >̐R(ƪ-撕mVV6km67.38Vng8rtrL%34n< f5QUT@К\)L f^|gn"Q$9g!Ei6Zi$9gG 45X9ϼM+t12ŶJH)4f 1FJr;ۘ=z!k`ՕM1q}[6 At0 :,EP0-LY*ТTR}\aiBQ !dzM-UmFJHJD (Lg .uofKIFa䱳<!ARZ jFsO̸` Byg{5Y8(̂#O_Py0FRT( Q(+tXӯ _'p567spmnڭ,r61O3Z+dW) 1(blD,IO'MTn2'?tp''<|!6jL(CH *!4X.FrÏI>%B!q3v{/:?:Z`e ae~4x&ݥ5|[zNBPIsuNN第nr)$Y!+k6m,(ˊBl\ijFLILQDTZye:CYH\A,l -I-_ESdeb)͟Q˗APS8Iݯx8g49=!s<& mʼnӟGC~tkK46snx,MxуŤr&y덗NNPɌ( І0͊NåS #jN6~Eo3x4ڵmd˛L&3W^z8H‛72sgo^Bq*|';8}׿oލIҀ|/K_x" m&9b21XLg'tg>;432B ʐzp@%„$OtSBg b.&U?8:vv0 D+)ӓCT f&`!')e52TOB$.rxy0X4eu,F8`I`ܺU-SuP!5OpmyԜl=3x񜵵M`ƝR.GgDQMsg8.3>kQUa1- SJƐ&Q*0|y泜?)_!DB (k55|sy,2t[{9JGex5AkKn\kġ~&!-lR[dBmL!Ʉ8<"$įL%VV9;9ը3\E^0 )J( A8n)$EHCd>K/}o~ [tVǷ>>zt d*w$%.P]xe>s@8E!Vgblq ] Иylml!mT6L<$/5yY!1=lۣxKF؎\Selmm * +l۰Kul6ml MdYLX \GI:ETUF32u-Ys&o~!+T<.ks~ޏiWf:Q/$ܹc.Ov:{8& #wV% cIFgYiux tj$7Q2GW%ylۢl1 NIh1D'=NxKÊN' Stkꫯ9:;'IRz?O̭z|K޿&%$4i6ZceXLf Q),IS`JEU֊\8AQiVt0c cj>ZI6KKi87)˛4]&pDQFJJE~Aǘ LB&syN*ק4L4RCШ7 !c9&E\Fm>_/~[l_f2esVlmmS¨a $ ӰL2Q:ǐrQwLd97F<`pdrJiCTa1y1Wiԗ{2rx)1y>Ifv6_Gd=|هUDb.lU/|ooqtۛ]r;di9qk<9:·0llns|vק,l ,jbZ.لRb ff0hun^R9BJ*PEAwFɇԛ3.鴛؎A^Damlqugsn]y2ަt(XYijW^??< tc%ʣjxDB+IjBH8)jaIK67W9?=Qi㣈W_}W.;o~WŠ~eOO'o˯W#pGɓ rӵ5{x<&Wa; 8Hp, IDAT.°Qو8Ɉ1!Ui%)FTh*YƪDU%EZ V!$Z)fӀ*6Z[$Y!L#*tFJb1,!] E ]-f)KxR8Q`%p\ ]Xb:Ib H-WDai,&AFp> ; }|²4}._7w.f8"*]1T4ȳ)]UNM`0E'q,u6ݾu<&g}@e JNbՕ5L$ G97n^Et@Rw}v/Q;tjK܋x.sNϏ0LAuӔ8-4,y/srtJY$HKa86QZDqLor6Nc׈ r?~D^_ J .jn%푦a/'?8prAg~ҟŭ_z[xE0% IYҼTٹl]`DFO1P@P" CVHgĔ\&qH](r*+LC>#I8͘coh0LIS @UN^PI]Nl@6W2lr#..)B/fhڔ껺T SSUCx8&Ro oˬu4ޜ ~!DaTD"MЕi8!"! #()Jl!i , /2tUR ]jlǥ,+vw)J4$b iQi C6R0%OYq<2 kx:ug ܸh49<>d:$q\gbuyTggʬ[E>계Q;~7dDp"cM&9gt+XGQ Lbm:&w}HQDH;{10.ЃOrA D5DQf3u`@8߫:&ŲrB!LStNDTZnaT]:+,Z+*8>@Lf|1n$MdyB4| Yce}p .wq*^m jS$M#tQY0E^Cwb6uLzܸ~5IlZ[ qTEC(,i9E;+IT3<ҥU9:g>18Fw}<@e۬-e\]7WYF@i1c i,trraX^g4cHRfJTJ&y4)F֫yXE+Ta#LT$ "4fYeEF 8@žU*"_|`4(Ő!+!X ^LsA@0<4*l(1 vh4MIL1{Y^cs}*"g6#BH+.prx@ӢP)E fh2lʥ+>iѠjiEI0m9>;agJ1^gk ih>'gi6-Ts>gO\ipy2e6#>l %(䬏%U, tˌfE̦%,Cf43M Ha1~fz??w?}] `DۢR%g=&IUmDAzh]Q1ONAT*2jAow}AUܤ\\bm},)KBUVTQP0hX¢U[fk*G}.++ˤIDu;m]ȣ ϩ!*MLB676xGYE%,)K¤\Ѭs~L`XSl0UU!qUk`{y(F; K=zER+/KP2R ij/7YnwaN lA׹8?y;ӻL jt2k8O$Ɍ6o}6L3Lc< E3Dg8V=67h-tN n=kӪoR/MRp6޸R)a1ESszg<̰.WYptF!UӬ{hU1-(zW^zREpxxx|pBVhln,3O&H೟?ydyFQOsxs.__~"St~tVChmD6WnۼOAPiMZ *XKZh9>ŊnSlo9Rkh * hi$ q6>^ np 59>>vNҮ9qztrw ˪3-1AS[[k $aH2ǸNnklHIx27]Bbb52\ 9W~_*Eˁ(Šno3Cago^${s[~=Sse48v` @.pH,bH$*hM3ioo%{|>[ǐeܹ.&,So5PFcMrYdIA{vI$G ۿO_K~[mVZ%}`e F# q;m |_",sRDQ"G`z7pl@ZsNqN$.9Z'`4t; v.gwK4U\'DW§hԪ1HWc|{$  Xkq`5èCtz,9 /0vpJPP> K,wvbvh5*%`:W_`u:ݳWy/n=ըE?d0rx\7WPܷiJe:P9J;(j" 4Q'\<Ѭ1M&~߭n,szr.U{:#bFL7j%90NN=~+ʨD5<~{ ̀b)%2yVP5xqЯ~ţٮ!d< +\I; $#a5ȒR)јdz!2vXh~AP%a2I2TW~AͭKĉ~?Ǒhc5zрR i+KN^pO v8<ଳ98By dijvg3|gcuz*,R,/SjDL'y*!ⰼvj} '=T>)Fq> .*O]|@Eb ?xhs~:hsS!p?G03^|&?fg>*[UXT)r>C8'(&S.N%W!I2*Q,KHg8>ZwO{G4.h*ܿG e8>' ln]$ qFo0%/ m T*U&Ox脅m/s5z#|p%Nb ZMp|t &3u a(&CPT"zؠPHV|3g<;:}I5W?~tǖ$7٦(N?8b8xΩ5t0xo9?;,?'A~Go/-/~MDHy %KO/tDw>w8z!F`2Pb}O裻$ǗX('[WSkK[P8>yZKCڤj{,OXEg:S88]"P92OG4Z@JA;,.PeAP*Ǒv{ \S.OSZ m} \x?RqETy/siNuMp+ )@&"{_d6Fi$D #j0XE;(56*#*hE*c933fIG"W8P;$ِRM(#&Mn,SJM@fq<6vHRECڢ0Mi4(h%Oszޣ@iD52wYE^k etJc( afGQ&8r~2d$)E>q#ߢռ[owI9DYE,Kz. 둥1Ad{Lc.l^{ Jɋqť&ffg:{ܿ/G!'~aj2cT U@)ES7~XzMTϼ>#_7| -A޻PkTyṗ }ptlU`))53/7yfDΡ"kCLy̅>RUd,UoQ>폩}2F>°ƍk7x1+U"~ȯRf"3%#ql@TD!K z8yIYBTNN#T)XjЎ&7x{yCfa.-z4vdS.T"(U:NY\XQ_#q ь(R8q}+2T\ Sk:YcRGs HFxp0%IK66.rm/]?hUk,^D!OѪsrzΝ;W0LpjpSTxp_s iDz. 59ͪVu+O]٬ƚff*Z{wOZިЬ7ATujȃGG UܸDgpLjvw8fs{):ݘ׾4[mN2jz\?gI$ԯ?Kr7~=?`4a6` fpt-+km[lb4(DKcu nq+̜_ \YO7|Դ=Ϗp02RP #|\/X C؀ՋSf'(SOYL=hʅlw#E^N(sC`%iVl<JIѤiJ]ץhqN Θ&]jQC5 FMX_wƌ!\ɓGHuj39g>I<4!Jy=~!`aqFoފh@+A{aGN7kԫ ^prN;<ؽq6( !H6.MԥQkf1fcfɔ`}>tbNIxNZ|nx 5ns|~@wpo|WHF)ms lo$ߟqUB{.Q̄N$KG$"5"NҢTsjdeyѸ 2W-'N&Blp\ Y>D iUgcprY^cDx%/gKع|۷_^hrr~D*%yt AdIAJRBx^BYw̴ ?k/ o6񄼴L Bܹ4O䑕=ZT! gI)لfk WJU"of)Oʀf{lDAj )\ɻwo;J:/&qpQ%,Ot}<_Pms0ܼuCbCVڹYfٹ>Y6ވ]1a2,5\&asyV0 y(eH`Εm6V<.^? V NdVӪ׸}ǔ'{ᐇNHfk` );> R+6RpCOAS\GbHG8#p|ߥQ2 q|.Š& j6yNӓF ifx ]:ܰQTh/K # k(mIc(iR*.Zg89bRg=N |k@SI*4kW.2If8AN'GuV/֐N4I(M1Bc@1خs9&Kc|nRΎOzTX]@Tk{x47wˌ{9wK/D ysϟ3<"O VV IDAT|?rhH Z_}t1??˟ɻ$IjٴGG/0'tc?lp<PHk祔YBh"R>ڧx<+Q\h7~;{{կ__I$/\IZ W>x2A8??f60vh5+% 8a<3MQu$e$SB?@Z4u#I4sD,`ޜZZhwãsK0If &-XZZ#2E9 E"J\' %Ât\Ұ!*6.FwxҧYoѪWYYi.Ey`C(m) .\V/hQ*ˌqph>b<RwX[S$Z} cѸ&1 ӷ^MIJQl_&SNOɋ<BRjx_Y__fuu"2tV]yLQj!`mI# RrB^,2jmO7xn\gs&Lf]YR~ Q, V8!)֝tP"D  JШiuaQQRV9>8[8|)%ը0,+Iӌ+Wv8HVXxJYh&ͭ%n ! ZIx< /Ьi+ॗ=>"c~_D'ϵ˜vx/~5~˓'F1!hp ?Sٸl%R ƱwIhkPڰBޘ-G5B,MbKM.3t\j RJp"tVR.Z{,47@xeh,+\M(UVj)ec4+q<) \uQfD^"B,.X]l JӬWU>IZنZ0i<0ܺq x^$-u2(Ɍ4a>F?4Kͫ"G$pvz WWQ&qbʅ Ԣ6fkc >6. lnQ sCNX]US ^u0HDli4*l·{XR.V3e2+V [(Aa;ZEV% #*AG(հck(*ѸɥK=*EƠoٹrk3ť(4հ>ҨU9:tVrhO3TPP iv}/}G޿s64ܡsa6VzDT[ O#ph *KH%k"\/<%j7 M2=z QXc6)6Kq:o佻ok ?q-jiF-lyb /t߯I|(e:8&8Bkp%X%"*5Ack"QJOZF# =&%,wyhJ* 9:4Amh΍,yBZ9.RhF{ L K8L1\I9vYZ>xu*Lr9&[ $1aHQL aF}n20o;T.Zǎ8YFZji2JC0đn׋XZDj@Y(6<ܳz] KQb lop{{OpH !dq-%l]w>zxadyK? <nܡwv T̳[ ]=x89:a`DllT |7;dvLs>p!s)pD8ϯfB+(KÓ'g H?4jgyh@X-p݀Rw%yaʘQ9R@J)U86ֱƧYXc8ŷf1_+AU%Vis<ڒ%̓ ?`MVu<5,MB4n7(gq*bJo Up*>`&V4JۿOV$h=! <*萳1h'; 脵fvZ3ch4?:f0WJc\Ъr<j\C FVFW.G:rZC8-=A-rT2B(-9J MBX];MV㓤I+?w8>;/i x $ Fw+/>8%jSh.I.VFxĥ,KEiRR%ˋT \xnz#lde C[R_~X}4z ʡ7:㑡Ge;UfTТDǂՍeߡ^ :!5'Ԫ-[M1xZ8Q*E!F !g&9iʉjB%X]iPZl\fsYG(eYZ\bwq|JQf$qԆ8|V׸~e<'K==Z.P QfI7%Ij/33.FYjQ +=QQH9(QLF%wnѐju |x.\zP焁qf]UۜHzEN{XW$gV$jH11‘`}{׫3eP 9;Cj]e\O$˼?a4e0> W鄳9 5ur9FDeYZPK<')y.X[_bs2ΐY2Jf `2聵ie~F[7rta RlRRqX[Yb}w6$IKl]b4Q81T /"K*URAއ9?˸~eϿCYeiqy"%T+<ڜ:Iy>s0*T*uPPm4"פfɫP$Ro 'pPV!8 +$,xhS` Nh5,2$+c23Ox蟝㹀Ή8%~ ̧h뺸_89C{!K gށ(e9xvkxDmDV+su秓9k0(1TFw5T*!:Dz'Ѩ,pq2/^b2a wr9b J}=O+<:$Rnݼ<{‘Hk<5soR- e@wP(5 ~PF2R% bH2k58uQF1e`tAbZ8cw%s6VVPZ CKDrAX^~UVW/J1v~S ks<7c2=,.?Q dM)M$V#jUN;(S"gyq uQYΤ?$pr|@JRdڅ۬l\'/$x_\yjAwxl@X{'`MЎ;rޟJC vx.o1U $ J;I?o?4Uj&vsХt\R8²B+I;d2yS_f09=7zow޾xXsο?~z듔IkQGg΍o~_[STwlop.:ywp\yL GS\סP@0F!%!+}!+jՀ~Y:aLp"D$wTu\.ڌ]fer|/^Vmst|FO90'QDf#|i\2awg@泏a.~JT[f:KC# -I]Szvc RKB_؂^GJ%)KH Q5`8hc4IZ*~5lqCJX ^ָrE6We{5 p vLQL׮>5q'vJZΰX<7@+e^y+e8$HT8r 8F(¢ x~B<mJc8ViFDcD+E2u{dyI?? J./w޻hs|2f6|kG47|_ft{)?_x/pG?d6-Xotq+yP"}Z;/4p]غ1㲾8F+AT]0M'(blt$Zk`sÓ H!:"6)#J,8cÕ.pFS:Zy>br3 QQDjGN̑"SEZU04I)U2%زđ(xq̐&9PsvKn\ES_ȹ&)sѬ^͍+4 88> }9V*rUē#ӟ *ܾyVh.b֟pӣ)C'g(Ԕk#\oX+!ip}hm|o<_I ᫟9=+%SWؼtOH {h<{oxHiZshFBƅm$pfkY`qa.] q:d<2.~' _Z`nَor}}{XY_Z + Vj[1iǦe{Ŝ= ѐ!mk~y5=xNU:tcwߞap %($ʤe7-%"!((!@$"`me|OUwqk=C^-Rim~>!$O3g1 Ef3<_x $ <Fh&C(q4U9#7AyշY.|Sx=:r X@8 " 鴡\HTNB/BXH%?0ҵ\˃@Y7Y,CҬ`tPfԞ(H.GO5&a2=aqcKV.rt0aw{kNOoE>:X|ȊsHiTr^7k;یJ'\巿{.88٣/~!9:d8ruV1y~|6 &"%Ne &hhUH/_8CV 7$h%1ҲX,iʲX5J+@:NsiڢJVYT!k2NNOȳSp|?U F˯~ēG7޹My˻cX5cYY X>aybj܈V-37 V5\լl z=lF\ؼlqt^Pւ~O2<#JZ)=*QA`l^*&6$]nL^,<6xmiB"]ρu)TJ\ຖ*h@5xh~ේ8Pd5|W/gSe #"ebHs֠ڧ4 ?Q%ЏJ>\vnKzPT)qBW$\6Z_"~@16a' iXO}GOȍe6څ[#j0<&ϟ2sA5);7y!AЧ l0l2;Y5%+k+گ ki E^0LKN<Ecx??N̢D 8 n?e^pic(ӌrl ɟӤiأ3 9w›?_~Ayk+w?yO?eeKC+''c4Koɽ?~?l2 .i64Y`QJӑVD <*V 0QW6Ux|[o}yNgT@EQ.6˜^CCy ;;WMO^2e" 4algGJ1'ɲGhXFE-rp#\{ WhsWT IDATU%Bۋټk/Bi9ϞoIT.ۧLkώvo2>Ypfٯ=*=EG\\b:Se"1_4M'gP)Ȑ'B|ay\ٽ1=.l\`8a6959If=V7o1X]gdTUq&D(+}V7שN F"9C<i@4!;h¤.vy~SW/{ ɛ^$U4J8n-4U1Pe5A)|0!qg`0b>9g:Z Lm I,eF"B(.DqD, 9'|>>OtijMh?._|ɃقK[ܼqkF J4lߞcL!uQ}PDtBKS|}^cc0*}3iyNQv3cYKU6a ADՔN49`Rٚ?ŝ9;]rgx5w~ъGX8]zV%~I'h -:GUY |kF芻/FJYRQS45IU$]!ecuWJTUIvgupx9 `o?e<=7o+7;&<}޼gne_Ͼo_~{}ջoGt_Ԫ% Iu7n_`14MNڵ]0rɳ)4Aa@!"tXR )P@HZR9j| p<Yy~[ܾv)UɳE% W n\+9$ 6`%Z5Lc\!Զ:0Zhs&+) CY5FaܺH1?'M >wQWszj/w՜6HŘG?fZR>f=7B SC^$0 es'( غxEGkv,b15F[:qWtY=67or븱lյbi\)u׹{U)Ub!t<<Ǖ.2e*0LxwѪ-"9hO {ܹufe:s}Fu6.0X.S⨃Ν8::HgǬp E14% E`x)-hc۬U=^d2A:/=IISU8CvdV5m3aq6eސ,MSMS .I`'vAs|7Kt! 'YN&,[ÑIk#,aݏ{6heٜyO8?c9y)`]q=c@5_qzEU#?,s+(=AY'EQ^s\4vȳht\p<`M̅#ጦ$s|A՜hB_ 0xp~O̗cp27r}Ǔ " xYLd9SV QR`-8pСs?irP7 q>Yt׹xa۷o$3IEh@482F H)W7.b +t3W.]GZlA5S5k=ơo3aRW9}ܞǢZ2O}I(,=S/Q{p8:{63ϩ.(qڒn`pxrJUmBx񋵐֊5٠EleSz+AҏtM >F)J-ۜEA/0 8?=2]<ƈ)J'$%q#EKT(rm}"#CPC: ,/vPHFo%EMI'_*T1{'aq7;v/Jlmr6~Ƈ>Y{8^a\!h`=l/iu$1dB<{:!I67qmMmvGn?|I]AY*?_|>c6_W^pK-888p~w}s|_eg^g<2im7%A ʛCFH@cVWdeAAZ`lM^&D8d KMzxuXGn.U#U9:ʘX&pDk뱴n-, pJTUݴn$B?嗲BX-tԪ`1V|6q,MD!%ean/cE!U`t")TuMl˅$E&?93.3O9=;'r|F],7^dNb-v|iUzq]!-*(eb@ҰH*u8nt!v{utigHWAhQoBb a肩YvYM, 5n;NTyn,+I|K5\r'O|Ht>cf u.nλ?^&CNOշ n^WoM3{>;ۮ4'K\"3@"dxN&Kc4y6 )ZEDVhIG:H)pIԀs#<.koH(+ ԥĨS[% | Ǿ ~ ܹqQ7s'|w9>y̵KL$%[[GUW, ׮b=8NDYam2Q4"zFPV|_pqc -"m^<)q:iCI.%!j:8&:\|[D:.\qđO23=)?6R";D9: a ٹhk=jWvhGy v<GmY.k|bђnwp,+H 0֣*;|<5ENX#€[Q YPזdYq6Qe@i/==;^^ccsRsW8<^}){O<!E~Z`EAgdr羼<=^'ZbՇHQ)Jc|\0^'\ЋG-GPE+<"7@5ڶ[rvAs#*n4)SH25Uk$G|ϧ-e M뚺I4HV::?Ъ$](MYZ@Șno!paee)H)B̒g.iQOrk 1N@'җ+/l]4M\n$=8p\ÓgPfꫯ3IŢawqak{r khk9`4AcD Ԧb<9V5Rxlv!qxl`$NY:^5.ɬ@ZIʥM.l^ Wy#)i\1%UZ! dW^zgy |Cv.g_?$>w/ψ;]mEyFj8a C0`1K(GXc O;Q:cJ4e#J)b2m0\`c}Nwl KNO8D2ʕLcɨ9^hY,&ۀsacș͏%㜢wc:c<78ހ|h6>׉\Q"=I^tiR~ ePD 8ny~Ч7 pZt! IxRK\a1$'C*BAp#aKP&g̘ [;[OǸ@6If{csNggTMyGCu.=,ܸ*}ãcƴFKT0pq|K_;L;O&.{GI Wuw9>HM/]ʬ䧾??ꐷ 7vyO8<9~|{ ާhO?gbs'K~)YL's,O!\"AQ ʂ#}Tc)a].u0.5ֹ e]3FaMdN{$\,PyyAmD(x^UQqt.E}t$?'}&eYidA#*j2Ly"f2?48IuTEG.tl9C)C(pJH4:gm8@ИN%M]mM)ݠ"fSZi"͙'dm' A- K }eFIM%ҍ>c}:atVr>/}mxؼ*kp$W!Gt> 23VG+DOOzx~ȣ(ghƖ99(=THO!yY3bZ)UAy!`g{)e hSE 'EQ JUQTV Bkݸl>gm}& :loos|r<%YJy~U,tFiӆ/~ӓ#N'L%s˕+^||:7\z9|>f2yW._C!TC7nx ͫ/5M:'Q5Z[ʢ.+ev> 6!%EխE q@5F!Mq, }ϓ4u]#.R84ӂWkq0O!& a{.Bp4B.UDq ڴk@>UePC>Znj;$%-.u/焾C]=?$v"rI:?LKҒ,Rg>IIQtcbX۟XY]C55jrh0`Mx)2'#*g2`:KYY,&t;!B%5BO V\X;  !O̖n$S޺3|~A?_ofՑ6_0͑er!F #)+T 45am<7s#ꪵ1b>0HDq@8C(UQ ?^ dR%BZ" b}숢(A鳱Ÿa,+ zA+8%Pq|{#/hSMh ɫSutÕUj2笭mԆ2ϹU+sik7{K:.O<||x奛|s/b^bpz<_zG8l '6Cl7A,"hp}GwEGj\hTe{( L8࠵h+E} k6mV8+.sYb[V3ٜIٺ񘺡 IDATpef]EQ(UrUg;[E\\1B 6E,\r< Bng2;~`e~xif.jGY)$-~O+R'sm.[?Vݏ>`Lrmkr1%GM1ɿrku;˟}Og?he KMܺy|# ő 9:EUҨ8eioA uC.[Tʐeq'$i(G pOH+q[ȅao0p+|nHI0feFĜ+ qc.ƺA#Uxr$]xr_p'~^?|VXk!=]ki}J[hiF*\g>Kȗ97ߤ+U`5Q*jRm5%IUT9uqN/}?ex#&˔Zkt$[KiuBKD\>UYӘQ Jkʢq`cAXh4dyҨʨU5H m+t.GdA-:V4Zx^ >i]b}{O\ t;dy͍wY7IvT0 9s:~t)GO7}-^e#ͻnˇc< y9C:5qa΅knȖ1Rt#\q$‚- &ZpB)Z/~* v, Ɠ?2i#*5/)eUu ɭ*M/y}yJ]L&sSty[C.mw{X[z3Ow%)EIFkٱ n\Hbiq '-h:"v@ԍCy-K"Ewϼяm?ק `~߳L #(1yFSvٹėMn\jƴZ-^{-Zq"8#"(4 dx%Vw,)(a+$B=:En@D^O5vWgye$-uE{<|_=  ڑ5kG!Aƫ3xWD65<}=E8*T傲Qs4*@ O9/udsM Z-)SGZ2"B$fzAY%LGLCʼ,㓌7$ QLB(WpjV~T%||Nݣw/&^a>}HS䔓]I)*PF%>6+ic5y< ^ȷo<$M y^Uj8w:ۻyw-ań흥*5|nMWBRKp/?hDm+Ym<9٘$K)eehuSz-Vcu`O a@XSykcєՂ_ڞg FZ<∂0rpσJkēaڂ0#rc"%h<|twk_믓c2G1XN~D"dF5N85%Q`uKxgXJ]qxtp<"K6KKKIRO a!l$UTm\%*xq)?trJU s%,8M'e +B2y(K]S&/JIb`ۻ,<; %gfo:q[oo~ `Di6'Y2' F[{tZұZ7eJG! ÙuKxlmȒj*}[f/!D1ǘ \xq^yiBxG8?c~K?{O;Q`pc8⩀4M )Ueð'2K{xyBz~dES%gzyhcF֗l"TrFk&K%6=ޝ[QVeciY<' y(Wb+Cw}N?lℏi,!-bt<{w dX;E`$!ȄYrp*fL#8ػ9ؔ^/m-.TFaРe8:a0ϑJ h dpx6|ʱso[Z7Dy k* hK^vV=茲XBxtװ֣?AYE(1%utb2ۛ3I(#"OkyE4$aȣ̳^oV3>^+O<2HJN@Ą~k,Z<3{9uO3BՠZe)h6B>aJ23STtO\,b&1| q c8"yLҷqCTTQr`u1@@ !}H@PU>|ߣ(GH<|p཯L YQ_'Қ 7ܾ}M`ko\gH}K4- Xk:ӛ̰s;GAQL nX*IO2eQrŨ`28ùzïo1CـKw8{qGܺ{fP(f8%E /&Kou<ò?qhREoz^?ƗJ1%8qfI/ͯ -gv`2'C<gf \qp8Dk>ݸGZ\p(MB̻{ϥWLW/&S[UF %kHTiGGlnh<-meI55J`j/F( $Bej㵐։E2g:M%a!4C8 QRPiME8+lT`ee,otxX#) Kե/QYQXL "A~n;G[&,ab0<УՈ啌''CHI6R*(2Bq^$O>{x}Fb@2WH6t#|=?F:˽G Kn+ |6%"B\<]9b`88ӄQd6( :K 676?ˋ~^ V6aإ2fee{G׈[$3&L3xBƻc]A>2 Ntz!N 1xx~92E(C&Xk(F9n~c H)($J hZyAVdhS -eeN=s۴ VV";2H3 (x{^9`Fp'ÌͭU^ӌPKtz]4%MJawg~#dI'ܼy,8ܛVEA"^4)3. Q-`lURy *<ߣKk= O^xGa}G3 8rLaL~0HAO>w  N,8:?I4ؘ;Xy\R A ʓ`,Zk4cmc0 $h6R0u*)"qjP BW9u"dwk&S"ߑ.NOH&j5(J(*$;*̓>;y62~(jc,ZWTeu%'P"$ڬ.oo&ׯA)"^C MUGUtڻܻߗ2&g 4MpVb]#RÓ߽ &)hsh .…'gk׮WҘI Q'9bM^uU O8achbMAl0OJ:U6U = kY~-JKRLfFD'hSg9RYE9׈b5P IDAT,+ܤirxOG$9۷q|rJ4&J(Wx?@Vڷs^)ޫʳ"k7H+>sZbJe̦ B|7~יM Ocр Ov8>JwLUC ΂ Wq2D]W{ll :Knxv_u|Ė, Y^Hr|?B ꦋ<gt=uC/>J<[]2Ԇ<,-i*)ˊ*17lJRGCȧr%a &QΧO^_(d8Xng(Yd<.;8E>0sbu K!aY`4~_51 %?{%B 䩣J4(sѢf8Y>>R()(b(tEi +a(^qΰ}٬po|V18I`-$"g1ӆ%>hx8b2_` Fnj'GigXSM^f t0:'Z-RgSP,m29>9!+&Mcfp=S|CTR?WQx_'<3]LZ%+ <:C!|Foi,UDkDOeX!qc43.%ERuNo3ﳴ"q*gXYIҕep%ڤ#:xGա؄Z#Y9I.KtU"ú@?>K]׃YJ5hw[ll!Y@92ҤxF#SjNSZBN/GW Vdy<)qBE/]aźN7&n4ï!7o=b>Ϟ >xsN{2YGn\?|Lqx8# c TmIqD(p^Ź'.>ԹUGoM1!Yc'M6^J@QRT-}%c<1RX[1tMB VV׸ww' L:h!}JD-zU(h4BGM"Q '${D6PwSP%''Q՝u^{tW$匓#&UiHЏY$*in^p2gHV(lտ(ɒcy^"1uoz]PXkmBTEGM^<mLyj'![ueMn|NYM+p"<H9ya@+dt.Z( I2 K,-tb.󵯽O=$J8EE/-zM|FuƳ'LJ[;?=a2q#&!Ō5^zϻi6c<[f239}nϓxRd1Xj{(4h2JsO C  #,sf ӄATAO"YjW:/;Zm#޾:h ф (./=<{c&$/osnNQ$ S9lgCz deyclE}j贺lnrrrt1 l[,o899, qyR'|oR9HI)>5,&  Uj8{n2Pd}s餤Պ8w' YY^!KJ\H AWZJ8 ~@d̥[)Vr/V%*ل@4^Dp8kxTYt |PʊGآ+QHU!'R\XSA#n]g9,Ipơ#*#k{v U`SO°(7oIM''cܰuGwU*lRde 2Hn#jXpA(Gdła#nDH娜×MסXwɳ9PblA!C zԈy鰶BxkoTc~^EVDB` LpYzUF#fɐD3 t֠lnh0~臸~tvdF;B ~QCus+^Kqds2~8QsƳc7/ )ɐ9NY>Dg 䌥uVO1+KWӛ4Nxa"oJG@0lF]{[ E^1~ Rוx  6El}RVa|h*G'W5R%Cdnq>En+Qgyif+,&sV&B(6TY|s)Qh<k`M.+K\$׮_CYʱHʕ TeRWs fӔ,{(K˯?θ^||s7|p|hL6/ƣ(Bx(R=aJ` چSgW_bsף-7Lh(lEYFV:Ǿcl'"E'%x?&˂dY.ق=A`-ᡜ= eHE^OtD[Y I031EhA>oa \ąW jQv%$E>8tVGKe81Oi6<.OfibQ_*8>dyG~dHU B(! 15'@S=PE`C)}8aqgX]_ɧ.โ^DZ6!W"Tahx;u09wd CqKJK G.I"ȦG@L5%Y%I?[(r'RPډQ@%[-._{^~uF'w1vh|(MO1MI& b_aKM$>Bss?7w~O;zy'u'_{+ι?]?i9?9*;buir=8B=\-z-h 5Gyi>JL)Lщ%~"MRQCKʌWL_IA,ҖLf'ߥ(*͠A,fS (+K(sDyB0IS*vmP2#CdFPSC>BΜa8誠r<?"-2{;= K! (\Jh&ȷ78Y" C&>.I9`uL/o67qUg["K5_S:+ kEY5~laHFpWS~_`:z+|Sů_!3yt&ҠSu#%),Z}q3|Z㘪(/ϙMĭ&E^5,*$Ӝ\ ti4"ue I*-}(E/%tN7ȳ!%(PE2#;h+AԈΆJfIJYi1e^1<҆ˆ)1:TvsK8=t!t‚*( %-C6Wwn i6F)l4qs<8GU$Հ;T8w .]~JYګ}s(RUCRRB+sAN P$F| $ 9rb%i,Q)wW^zwũr *twYp^P6\$1ƴ @{%TU*y^qx D% \λ|@'(VWe2;w-:oFOp9a8DASk=[XYtIGtJN%taQT,;~~KK8Q X9{Ȳy۔qIp:4ُ]nk68soEVhf@?@{s(ϖk}oR*\xcIxn}p;:xe})U]͝?`>r~)c>ڏѥȮEYTΣ$tAjKN^09.)bj39#0k֯n`9E iܩ%6zPe(HEI76,f{4͌$pMby(& U,w"zlCƔek ^ uZ^ҿh?n?~~zwo{Mqx'޿v_{DhK|h^ 4W^d1Ȧ%)tt(rlij.4LM vC'FBA4Z'T:ړd~)¥ GCfٌOQDJ(<4UMGwHQDB'Q5 +K+a{PR jSC+YvLm ;yx(V9sd x=Tu, umUȊ=rFgV9u4wcn{zļܥ osM4QU$ᝣ9$(x1wtVQouY;uЪ!`-hLIA gL\$Nwxdh$İ`x/^!B(2U03|H$ZRDlbƮڏV!m GbV}#)*: J*DD*CG/wAtr6K[KTk?۷qU՚_R uJQx6: )+d]f66pˏƙ OƜt+<~c>Xm IDATuU2 oagwkjX5‡I 4Uް Ad =4" g3^g?[\y{}>@QxM<4Ǹ|gFt|7-,: W| !n;c'$A'J|>Yƻo;oM'IY_[fסsf)ȀP =N6{:}NTa2qN09&b2nm湧qj#psUN=C)5O^oy2+iC,VW8T -=k >hQ0yMjE`;KYO2q,w/.~W_ok6G@/%|ًsW|kJ/]"[,(3Oؒã)eS#Bk?uY>%E\[Gxu e}c)y:AA #jA^dJ(lCYۿU0 H"2& y`%5 %$᠇1p:y<˘Uޠ8tN2$Rk>Ӥ2tId0!J&J C(`l RLSUʠ̵c23M8>:bmm .$ UaLK:] dl:a1:GUy)K-,Q֖\oa'In &1ZU8_E`;RP*il)T=TYD!&-ig-8޵}Mm^2_t>[ΐMry=bQ!eNggwW[֞C՟sܹucւ԰1-y|Nd6rptҚSq e}DV0AR8.4AI f4Xk9uN'F')DqTE@(,+QHF,NJFN*AS[SQ~W3u(K ׮cD Pg34{bu'07R DocZ25xu%ev>!XMRkLg5iDei$O:sM/ڑm['$Pk>|R*okxog8u W^^ħyc5}֖rx!p^e JZjD/0v`D;op\{W@E~^ߧ z}֒~ GKtܸ4tg8<.)pL Q ^cSS/0xG {և=w?O~,VxcO3kΜ?S^c 4b||H8w~'_M4f8u,Z0ܸ{y>C' # 6WZe%ソMYQs~ho7(jbQu AP aI`bquBb]@ylÃ,pQۦ 3 %Ze(RMDd9UnMܶla6gʋlnƔ_x̧qEpQusVga u{$Gگٺ x!ÕU e(vue"6PIE:u8M/ nNJ!HGTE1Q֢V'E Ғ4M[hc8!-qu8Pd4:|aƚ@D@ 3t:=Ƽ-v* (›AB ѬlrŏE1G h꒺h*G>̏+L# AR[Cu+209.a>)2O[mA^ fZ³1XևR틏>Tr-<:ju}`Nղ}(:c|mLcY "Nz"<\{9K<|8 o y3!Dot|XA(bxpR3+JJ39^̙M ';, LgO@Hr4:/!7N}?"pe?A`,f5Yݰ峬o&_c{ߟ//}[_s[\81<~(vvx}&s緸pn}wog<K\|^Os4>#8b:lF]5iTża6kJC ڂou}.%$Ui$' F#0\^?PT޵b="{lKăkc*vbHe>0XK%#x %{+sSq<9Ƙ !HtL@2/ڀUbG,;(٦C zu^7qFqα瘦DlE].XdVGe=gΞgITܺ`#d,wqvLSΉ HEڍOY)=59Z+WeT-k}:4p5ʢ!/jfzP"! b0IXw$}v9}njJ^( gYC/L#w޺I^]Ԥ%v2VW\x^[f<1~ou !4ip4dcs2RBxd C2ame )"P)/s2ׯ_ 3s .]a]Rm&N"v^Lf@x*bgk, c+Z3#^X[9G9?ǿկǏ6IQ.+? V[.ΖDFHtd zK ?I ^ UQX-5HuI`0`8`r 2N4lND1nLg~EÕXы46պDG:Ě}ϫzͭuo],#G(U eN_Tc67OgdE?1h\8wii ӂ 㝠 Hvf80'A1yi8|v2>0$1x5&CA6qk}%|uc%v>-x'I:sټ$ X$S@Y/888dMkE R^K(axoX8 Oiw&OzDhHmai.<6`efmM0Fi tL4x#OkAYl8"M"bAuiw!8GMJ1mh'IT*.5*XtŢDJX"4H%ZIb ޵lyLN-ڨ1Ƒ= ^EZ3>>fi"$'eMi>"0R5rx'YRPe0,su}Q>%oZS B;a|h_CTPF(I|h> O^E(-"-Ax֖+{hL]OR(վ2Z^|s/ujg/78}_}^y 7R߻E%q"1c8"-IM&kDp4etVI;HR(!\'/-I{zUD0\1\dQ<س|_|,;{?f}fc&H%.{~w_ȏޣ*~/$~˿מ֙<+SMIpe>e9iH zhu8V;NH1K(y'< wq"ft)GqVhY`MײY!B4JsiP6 (ҲD;#cP(KSY.ԞNQf3s|8f68oY[_"[bjK]S\|ɔx"+q5H >aGY>&4pt4-uo\pl~0pbE }$I'aLayOX&(PČ3㊪lm|;7èsFK/S>J5MOp?b> }nZRfeu NXTXHV 9R1αP{( O^`u<To%^'0 W5GW>eΝ~[?Χ4{'>ɟyn=}G^t>wB;ݥ8v1wnxp)EhItY[^gc}x(ԕGk$QN\e^G 8d>.bLIIDp AR+^c,vUr1UH!NT(&r\p-F+PMwA!ހTeZvn-4QYנFuD1>q(pO2}4"DHeytŬ&޻YΕheIbĸ%R#6O];ĉg}+e|t2ۣv4i8X-1B\`!x,ʢ"qֲXT(y[ފ/} WOh1 IDATլo#IֈiUn9zý/::ok/b늦X_[ ,n0/ &+]ʥ\#|pW._!ܹ{]' 'DJmJ[-s|4X=ٌvۍސ2^v%Ԗo;te|lCQLkE a13RCَԭ#:3 = 4XgΞ "^}Y2o?┢,̉WQ khL|atO8F,ɲ*\~査h'}?=/O[?zyF.k4M̓{-=.^sm޿>*BX'cDvKm0טF6BI%֘z jVdL-:8o6{t:1n--!X"GiHk:>x n"jDl'X|`eeCX#Mt ,*BѸ""xwNC e Z6RKkR'TfYC(OX Y[1;>]b8׼;Y[O{l>OL/Q,Ne'/D< 6x I!b^HCaXU603dgo@1UQ37u%ZY.]<c wQ{KS7Fal$(34կQW͘(x)F+kx!^gNg178KI-( X#HD0Jҡay V6밴FvT(|4FSdY|>c09RJu5gޛX}w9s[kW2=h)"cJhjqd@V(Q6 I  0l+h;,(RDb9C^ֻ]Mb ⨞/s֎]*,(D'%Dr5Ɠc&IhAJX\4{gI&4Uf@A#a4RB¹0[6 )ZF mpm*2@3 UFXT\|Vf٢&[X,,V/&8oX8Dyt DhZTٳڐT)j8d ؊ @!:ݮe4ݹA|D8Xd1"YW`>+i%Bj垶A#!HIżxw$a'_͒:#}=v*MFG~i넴l3+BKeC6)wg|Q[W:ں'>CQ̇?3wV&o}{p7isw~ϲƯϑĚe$NRF#D3uX[4x\8'$J`c2Ip֔u=NJ8-{WIf5q+¹NE]@Ԙ pHv}k\5bN+JX.gTY , ZN0M56>I'y!UURd%:j\TĪM3(biyB;`Ō,Q,3q4a}}GGH>KeP j8^x5rt meK9(vmbQ)12 c)vԊhwzTEMS%O )^;cd9_+E 2XtH8&;/psp;AAXʂe0|,3ZK]F:&ay^1YXG|WȲK# Vx(e}} b4aTxMmkѓn=Ko#[@+zP|#?9 a{zdVJ[AHbU4곗x9. &_.޳1!7/sP$:%{ : yI!B=;$τY.3+o+|AпG'3|;??-:~6Wh~?D5I£TDlr޻;;8"Tֱ!zԈ`o(t MpƳ4'4VHQ@CENb>7U8Y!Œ=(ʌX)$t܍ZhuL=ޠclPUU!h4cS.R ( ZA4죈li0MV I6/ G<ػEUM(KA(0Cxڀ:(*d>6V .-|eјbV)e8p 5IAO>$kpȅ[D(R*DZ%y{8Q*;Dhɫ~/~xwh5~]Ε˻+p_=R 3ȕr%vEIyi|@+tuu2l)K)icilf¹bCEcEt"ZV yI#QZP5£›|QspTpx08N5yCcBcH9NO $8D Q9wkjڝv'EJATI+8A h)t\T怴iJ23̳ia-đZ`tv ST?|ß;G>m7׷Ee},^xַRG/uglXՋ|/SsK2w;˜_ãu~/$O+V+TLcieV25ı7pֆ]}O/3zm ZJI4:nGǂڄ}t:ІnsW0_NYQӔ2>$pIC` Z eqaRc 'ӒSϭSU5hc}jvV[)7z?i eIݧjpNc΅ML\G^;F7Ϗdy.B% YDʒõ>YXf8Hf ZM HӔ38w8<7}llS֎ټS(]_CN`Тa:G^LȲ9/`T˗/C'shJ%u.qF-m't V;b0T٢.J=H'Gu6 GJjS6z3ޡ tR]XzQ**C*@jGS 1õ!R4ѠpawD8$`]ATTU|Ktcmdꘌ-ӹ!ˠ<ކE(l\[EwJ˫h5R(hLTjdnhOV+\F8U+B5I'kAUWDE8;΄Y)w:~cxLG\v=?|dBҘpW_{c f1:Q,EQrnݾC%\)GQW68 0wnOбfkkwN'wRzͭU`-L2䑊FĭK)vT!$4 qZ@t)Z;&IZBku i1^EoNZAV/⤢J [19S!)֒^OQʪd17x%y~[cn<>/)"o# 8sOPwH2b)9Iũ |)%W3) [TdmtnFts;CM.]h'R5u5454f:;a6ǻ*YʳYNU7 CS J\:dž3CY{\õO8~Bo k bNE=Rj厮b[.,;Z^;_;=;΄Yi{wt2}}}+˗7t{m:s<޻{Dڱ{qe6[mizzۻܹ7h[= 9g}sk˱;P +%ZYp ]Ռib2o%zVhQ qrx<-AN;askM"(b"+!5 XJP:BjOYe [ Cޝ h-hL bJ.\bs|a;6Ia0[ `О<?8ID^5ZdAMc<ׯorx8c<6$'J6%]h!)$y񅗸;d<;v{G&5·Jr ( kmރk"ac :B((gw QS9B5đ$ VN \JFeƐ(VTUDc|LnZ$QD,)r6*`j2uXTHw.9SR`,;H}QBO")K]Iʓee $m:!k[6?`4uL4M` s2_c‡Xola*N,0+`~*`!5>m߆7D"I"^ uXk?ⳟs_>;΄Y)W?q/]~J5,󋻻x#wBOzŜ?"/3ZiL"#.USAoxI;GRѡKvļX1QrU61y/sƳݚPjk4[q?Ӊ2}^MP8j.˒尶(fOƌh9V1ǭ`BU%"8]_/=x<!# CyF]5Ͱ$,Rk.$ IDAT]g:3/0Q5ug8E5=EQC?ܹUܻIWu_;hXkcYE]b#M),uU`xidx) bZԊg? ۿ2܋ȗ+W+Wܹw|^bEYxv/n^7~wYK3|ZofΟoncgb!BhsHjN+(VޯRB Ҽ7(w.H[V!Mi7FzR( {:JT֡!<[[UIeLXXǑD$єuIyeQ^`jO$CK:JaCIm$JE]ŅD8w }Td!c%U1 H#J6^8в `MpힺqZP"$ET OOpn~[uj ^ X$S!eR~.\$ַ;ڞV[ {Α&mCCBᕣ +OswYSɤ%RP۴FkբOSkEk^"~NݾST*^0BJ#'CW~SkLY?Y7~ >"%6n/Bd2asMhDP6a]*YK>IcmZui9s_~dń#&1 _$&4HQ\ cY /&:EcLi!uD< ut$qNՋH 9::F 5+Df*KmbzoVzwo}}f# Qra< nKge 7$ NuJRW91k /:ɹDQP DJTpׯ?O5w$Tc,`6KYI҂ok{-xm%Jp.*"B08s|Ƶ.spOS6sdEWx+p! 3O|ϼixK#]-^?f>Z~QsOQcZD1 %Ysa'ѺAǖ~N k1^XxV8H$uŋı'f4RƄY!pԁ7ytlR7z1~:[1,y睯pמBS3Jk !0:*Cy@Ӯ!NƭӂHGx):!t:h>"?̖9q'%*"_x\OEi|{{ONjV;m; v&矿4izO+XWv;A _H҂$8ai["\S%w%m+4F눓 eI2 <]DqJ,'G9f K #:ሓ&nu*8xAm-RFLKFo893W:Bý%H"ɕakdvdrD\QpeQ`-hlģ1I P!;shjE;\~͍.F{p oW42=`,G]Ђ1B7Mu[oɜ@:/EQϿt^hO~/:w<&Rm(xuOS'@kE]A{0 6=AvL$BKW9H1FwI[NS EVtk /xD5K 9zMXh/4'%BACԞ"!IɈla(7@*y}~ȋ%e +*"#qf#39<'"E+aC ;->3vr[o>j uw$)rKV,u! hvM%V3\8$ ߟq|TPנTBY6H%իSR==SWqOkMc,rut$Èl|B;g'֙;W {?w;ן}'a~G? j\|Y̳W怇clѼ$=>f}="I ^…<{2ńGͧZ:j&}s>xm%Xo#!+,IKqgx=wٻECƜR4UCPJhFm^|E޽a{mlc(-$Jbݟs.|1a2>0oIiwcF"J%/{ٲxNYXS!qO| 7of|($u5߸{bЃ^;ی V(aj5f~$O8!KXPzZI`n!AC<:D#qQd;X$e<{_ڨ]$,eȱi^G,sn?`ZD.{8>p{V/qݛ4-x(c2=b9㜣Xԅ9ڈ+^f!cKKj w3ApHB$OQQUuh3[jj0-B(TOD[})Q>${HZJ֊gRN`w`i%a5tͻ2,yL'q4UC׼‹ ,rp%Ry1xD4m!(k4S*"_:'0X٧QmOEux;O{+z5ē Ph%Wˡ?׾?:zguVoޯ͗~yk)m0X۰FQ׌3ʻ!XjJZiB^gt]4LOjz]Ihz.VJYMh6v8a^и%ɓ :ֈU[(S: m=yf|-r̅[llXKS%EQNQlK tQW.xGD 'UrhA3"mu+ N`42>` 9,m!wԶf$MeUQ\}n>AƓ1i1Zd4-R$bmbifP/O~+\4S[6;;[||}lqC^x2ofܻ|:o~B3fQii,V&_C`n![]mzPQ%XZƅ9© M݄XX#(= %`8\F:vk l,&U4T4Yg[=6+KYcZDzHQ|#X,x>4fRd-4P5GhlQVhǏ"/ ykCtm%J tRwoh/>R01+ 2"0 &N: "=`$X["Z&RxZi c=Eաn`qA' IޱX,P$Nh ZWLU:*4E.XflnrbjY,l,HpI5q)A|*OrOEd=^ + Iv߸.G_wit&~3_JPam!J FC:g`A'8mh^Ixy665z$qs2 `SLt{(p?9蘪*AU4iP*b{s;Tup\H-hK(MIA¥|坯0Z ~ǰ?`+W71~wY,J4a#V)Zidł U16&Rn<{W_ ~ɜ{ZRUII瞽ޣG?cN>ŋyKo˚sm=w nwk1!˭#!v$L!|!OAax_H,V1Tq͝?xNWb1QⰶڐNW^fg7xNZhh9676s5Ζx[vȊ<8I()q{ADC@>uIRҤ ^TX_}G+褚Ɩ4h[flmx"QW!#FuT`6?bX yI+Iue ÔE^Q"4Mu een/J|ɖ]=]0Ƭ1zq*-+6 :L4~ckmY_+k~$/At&Ճ}rRKn'BI[圸Qf}}-Ě(X 1A¹m^+4.6l4;fe&M4NJi̦3B' qX \@X(ECAT4ȮHŲw8:xDiԶa(c+'4BH1d˜# f#[U~xwQkמa}cDӔ, $IJ]y |oXW3W8$JWHay.[k,< [A]yL ->uS 7$\ C巿jBsu2gp~<Gm&TE* OfDqXlѸbK֚'P)k#6lݘ6I-ATeE@GISe7!W;XjOŌOj \NW1tHi)jKύ?|sr ވ8dB;ghzTE|1g6͈f}t0+Q^" FWb._hVs`b-Cx}̓O 70i"RVk-M \>3w1MIYN,%QXI4DA*CeKb"EU4$IiDŤ(a<1Z[ūTɌ[nQ|BUϩ [ESc97̧TX<wEmpA,DH8>@%=Z!Gi7NͳSLYMpP5%+T'N4BF)Z-Hy WshF,41/;,nI;غFD!2+IcgQn'A*hjC[³>hIтvGLffcdzvxeVG, OKCU:?d{}8(ׯmwo%U^ Ěś]U\Tm irxxUM^XL^*^|W038:㬢s/+w[]ԵE,ij}ǫ шƚ7v-;{5W}9eR thk&$HDEb:jE"$QyEn!{6dx~'LS0Ά| e,JS4xs6eXX,i} 3bo` KpJK^G)Io0%ivls%MY8.$Ih4xYZ\e0̓l3_ȝ;BkR-S-8o!f-Jp2 i(kF1RŜ9sAG7w4b26B1JǹqU4b(0cUB{7BMw̦nf†)Fk :RH I%YXt85OhEX9q3Vh!i-1>q"(mQ9=KHSf sGa yTiDDQ2"Jh6΂x򜷷w ^oQ2Zx>/Ocv ?^EKT#l77z%e.5&~2x͹hk?[x7tm1,v"~~"pEg ݅%FYN\dc{̹*4uhvL#IwBtEkܺ##5YnYXYYp>>l:,yk^zWb:O$ýX^ڌ#Z/q4kd%4 8'0V"iΞ۠w18:‹oE(3dSo6$+'n,W0ƍH$q"Y]Yde˕+01Ve9Q)r򵬭O[[{d4irw4 3N &J%AT2 ǔސkn2Dv?]4]vzWcSVK3k4%Zv)=Fy>j@"ڢHnq`808FdT$fFY>d )^.h2fgg )%фlRpxxswq=d{'|)#,..ju:[<;MQd\d cswp.?$ָD26XF B0"x[a#EBS6p굋M*^6΅5V3ӬlD:ini16m2SFb1ƣuZ_c퓍a00d1O`JT;5GJ=;g:ZmZZ|)K#|_[ysn_`Bo^o~'}w~C?'?Q]w&,t\߼I(KDq2Il6 zdل€lAإ(F ݥ:I3Ɇh!1b QN$K[]Rl(G9wy;](Wolz3 ORɘFR)qfme%n^B v(ZIAQT0 Ѡٮ;4a^=+׸qc0d00D`0b24-nv8]Y GlN$@ 1XxKZ@ZPn# rCY;Ѝ q!q"3%84vWs58DyzM5qZggFBUyf*qqYf8cq9[O 9-bYn0F2:,(1٤O{I( p(ڐEVwsm\ݼ͝8{.&#t!7x֖;:Qݛca$ ٤d2tY[Yx9IR/<(CʂfpDp!+Mauuf;/%z7y2ѐhhQRzzmյuÌא(<ԩ Ʉ bmc}xF!ula9W/ !$wλnhpM6ls"5 (O+MZO FPllPFAI 0DJ"^L/ćtAF:fm]vZБ4j9RYdq2-!I"t$i@n$DKvd vwDB|d:CYJ<ۿN( Z"Bqph,7xW|O< 7]'{V $C)38•cM8dr̩ "َ`:Ykq%*+kA$ ԚY(s;Nmh7%ͶDX7!##Z8I0Fw4^CYn%!-Q/b]em4V<Giy$ùp% bSOPhͭ&yH#&Yd@"5 zG [$w( W~7|o֯c<2x#)KV4 )rVDQDI#!M߸?'Ll^|n^s7? _V{7_0;~KS3ߐHVhyN g8Fw#|~Qߢ4mN6$J ۺ9<}( Dg(cMAY:TEN+< 4Z`Kh$6v)tۜ9w^b&?4WvN3!}Z̈́{}gpt)p,..r)T0S-QT aN ͊B36eyIBλv88&2cJ3a=)ÅӪܵq$* X$\2?rt 7රt%G(M&2JY]`4ؼID)(P:jާxv)@)֖ C@0eǖ8N0좕!SaMA>,.}50f #IZ EaQ2:7anj0xϋ9ˑu̺tSR"r?iqU2lO|Bʂj |q"E˾4 qgyk,vcJ#ju ܾ8mxt(Ye 6ΎPRhך؜85uu+-d.;OĺMG)ܯZG֡^ob-.^R"h|0UGSl ǽw DOZ*ƸaՑg*_{ħٵ%_rG(<)VAQE&Ic\M7Bw.LtW@)0T Qsp%ReT^.v[1=Q ِKvbKκMx*q)*lXWaGө1ksmfWw-3ꍐgj/j.s^l-ੜJqM+3P (AɈ3gϣ"O>Y6"4 JSMrDRc#I"E^R08cO:yVp>pG%YA7 ko=>B) Μ2ӱyhcQ^BuNfκuB"}L2B*‡|QoHmM 򬮷bJI\74WP*l챽{^o$QA ^H5nEɄ7sy]`p Ȑ MQNgzfȣ}2.63FrHE) 4,,uh7Z4Z 8# ' 24j-v( ,Dr+Y_S\f4("Kc~|7W?]5zn찺:^)|7~$ fd8~7'.^~~FkEn\_ŋ cXaqR")I/wNGLh.KڭյVŗ~koI#~7]+o_cKcI3ı%/?|<ܸ>whI#"4Ŕ(YXX$N5:Ni5]͠?Ah$ZG)}[Ǔ$)ggl9 М";RLS0 ureQ kӓ'{w)N]!R掆G:pWYYkZM^ ^eclA \sW׈?F|dt’ J(]DIEO\5S ELY:l"ߛԦX) $essgcU%49eaQ24x:2OM;h4Zio $ǑZ!n{ir8LD DZ9*H]brVGpfsbݘޠPH+ji# V(⻤{S )bcoDZ08#@B5IƅEZFW6pRf#%I%OIYI4 cg(@yVJ w$ۑ2boPy}_CQ Cg"y,;Л׼+W.rמ}o?8kW?f^'NRjK~ix#9o=z%)QDqvIFaKlIꐌ-e0rHV&}EQA eƾr_ ǻ^އ]#U5- Zdۥ ΝSGme>a4k5ˉ' <n_ޖdňFȰEAQsЬ5Ns a`Ѡ$ˇt57xN`u !FT3BSOC~iѬ/q}qU2pP0B”SaH"F uboQ(3qv\؆s !W ! ^@x1ι*8g)5:r*( * Lv7eqEhvLX:kknbkg${#G*:(IItApc=^*xXwhԗ:{4)qnu8qyI"#&# 䃣CDqhBK<*8d?{2{d$M1e5:t;qUR4]"FP_Uo'᫿9}_͵s^s7y _{_(քԒBt "-̩ &"tnqrx,Jp:-nk Gpxx’6"&1"CH%I̠dLac}m&ZKƓ ҋrqMh Y&ЪF49L0% z\.5\~I6Ak笨xtc Xw|b6qlep")·5JiKl :8ܹ MɤrtGi-XTu$F=L=fow.^?$']RHK-5Xf{ vva~G(Sʲ;(%T,h6,,,ly3b`dDtQܑ&ha'u"DuV9:~7}ߡQw27&'YXX6Л׼}??P2>yDs3^ c bp Jj-+S+SXKESpi}""tz쯰ĚgAJxGU({BP ezPk⦣% +1IiԠVSآ(,|AH:icEXYp0$ŠF*Hj$-J )s,H#EVciqN'K_urj -i,1֠IVZP}IDKIY2lq8O m-bewOe9wl#(/XRi׈b`<`w{1ZB.UD-ɳ Dx?M&9wx쉇?`JǹΖ<}nqkѷV yk^'{cszm}7şDع#Z[,(% МC(q c Sb6t[LխNBsO:5('=xJH4F1cFSSkJb⺡DITzEh6.aX Iaox\PjZ]3 bUyE{ Awg Ikh9q+>tΨ1~agҟbѹ9'f\<]YTw.gH< UDIDATBpڟ:}YS!-J2Ij]آH0eh4`b[rjIXIJj4Ii/8qfS>62I޺sk,..Ju@xd16$hM&Õ%fZGG=z dYNgQ>n7;T#G(Adyyv={fsXwSO=%/{q7/o??i^s7yz?'^Z< j!aȰaJ9T1!n=LݙڙxmOÿ4MQ(r$Kfk 8rc:&s:&Ξ3Chh(KBso|x賟egYI>.B\zR:U ;'aK;g !BIDt2d K!l3aq"(U1.łUV@H*&!u'Kx[%SIQozuA@]'E/z!B7vRK㉢:euXq)66zܼ~`ގe0n$qD-M Z%wiqm:?dAcSx#Azha9i7YY_Vo bKW2FR-Xgk:Ɋ3S5ll,FQ~us~\k^/W|p0Qxf(+6DRpD!' Fc6L/{rl*pǂ TE#iAGo2칲(0Mh4R677Q2&/lfqN%*E3UnalovۦF1%qb ӽk;J:\傭+-cݰ/ M+'--b3@bgjPJ#Ա GJDRo(ME &!RY4qi7H MYYY]Aakk>z17n4]ht 0k!(]oHcUzNqqSc$^Rnuvl^eoaְحD$){58QeSIag<(qwm׿kG$h4Z]zkv~/O< yk^eͿq7Oqel4T9WМ 17!uEєAP\%,"u=vcřS#4[QaRP8m #eD>BچrT )1exlcM2qf21S5ŕLݱJb0: n{Nj,f_֢(ʪjCMЕb}0/xAxva_PIKDSKQ7%fRuNz=>1hqPׂ[*4E4 7<#A%pH:6=sNZQQoĕk[SptX+iJEiA6.- 8<8dwwc Jkp`טc1? ?w+I];GO}Ņswٯ?͏q_y~~\k^_??)-~_| jYm3*Zji9uJ ']a‹*ҡ~DЭcLVʲBHI5,Ě,8+t2Rh6;60iO\k^zIx/R8*QBEHdwNͺgAȄe`Dv6f];lbƉbt 6e%(edZı#Gw<2NKiQT&]A5J<O6M8 NfN>w9X̙;}a|Lʗ~W}~ʳoBܙ7x^ yk^zf_|W04 |=f.ϩnڙ; cP; R07QYU;a896 B3p Bꐹq8{_.ГaH`Ssgnt4>ڢو5cw/={̺Jmr|,lP)'GC:Hh3p4$r e(r_k|Sr.?397WE_r>/ co{Ύ򶷾}mm}|_O3+/>OOMW]>?}77]|~m~;w]}.{3{{~WK;sw^ yk^_껾;c_oxG>"1͙"BLeUWp֭b6 ] #ioZNTԜ!N7f0 ^*DIy}"Ik} JzZ8N$?:v[v.Ogg'm "ZފR-5RJ$NX$M2Hd2WVu$RIn2˾_uy_m^̹3`\k^]yݗa?3쓟q׾3&|䣿J#1Z(8J˜2BLj5A5R:ap%3Uq;NnQ81@kuBJ5KRӝ Qw"la"=YwwX?tLSD1Cqu)wElo>$HH:}ԽXldOi ~ի̷}_[߀yk.5y 7oqEwvYc?EW6E#8^5~2= !,6td0H83Wc?v5qJQnXgfy ;-#(faFN#˂!{ 3EL_LՈw!Z:uJG)1Zu}e^xdy?G}?׿qƞ׼Bo^׼5;Y}ϾPp0{H% Xvb|=O#!dչ!nIIR<55Ɗ gNW;md%cR---#? M61occݿpu*b=otDiL;{[@$(!ti)NdY31tn^_l}w#lo$AEj4?آ٫IENDB`pytango-9.2.2/doc/_static/green_python_original.png000066400000000000000000063330021316324373100225050ustar00rootroot00000000000000PNG  IHDRr&ؑ=sRGBbKGD pHYs  tIME &H tEXtCommentCreated with GIMPW IDATx{UZ{s q&BT c!LFQP@RX$dCGr)'C R+N9\؆h8 F!d]>s.{~Vx{&1"HS5u}٧]g߂뺮뺮뺮뺮뺮뺮뺮뺮뺮뺮뺮뺮뺮뺮뺮뺮뺮ߤ\ù۞xczu]u]u]ur-~y6⽿,˂YoB@4`"0Z?/ :Ńڷ뺮뺮뺮 /ܯ<_;gky|}>?p nfeYX#)j* !ˆH3c;nXjaۯg?˩4nn77|躮뺮뺮 Bџ}|CAG?|SkfZ=PL3 WPU$81<wpfGTO]:1`r<IC \]I3a}IH0B/|k~^뺮뺮Q0#GPa嚨 g>66vlDB q?u( fFJU NPAD!%j5BXC[x-unHs֬R{@a0B$āa/z?h$$4&Aed3H d8򶷾뺮뺮뺮 ӿ o?Ce>9\$5_1*In3a^g ff%VJ]BBkӸ#" e@a`H S:NWԍ1/o" @3%WZj%҈D)Ac-P|Z0M"w0!q˷}wƞu]u]u]u\oPOF1#I!i!Po\( ޔNL#aNRPkfaPR FP 8!RJnrq6"Bx5Q!!.;Ni8(`##2bv!8'HJ9pv&!T,8,I;DLi1~ں뺮뺮 9|ʌR}&&2#*aHoԑVhUT"044s(P@ Vpo4 !@ ឩqg!jsv)%꒩2-2@ujJ `ea& a3ml.lܹ"ȹz')1[F9) j qܩ5|]o~K麮뺮뺮B/;WwyS3giv >9Vx[ZQ S!AApR\]DPQjn<_Sk!&aԚmF="i\<10l8]p;Mˁy1 88H@DPQb@ dYQp,̘gb7w{D`8\]e;gD!MhˁÒ 8!W;s뺮뺮ѻ9r,I05o&&%JTh5GD1hV1hhfSqT( .Sڢ<=i zQ 躧PI|{gjkx *8 ,8c}%53mp8B"Đ&|,Kp8PJΙ&a˰9K WGn~J@ganiwwQ5_iu]u]u] 9L??nO])TT+!54 cdʹrBQ(eu}H?}Ǘ 9 3nv&DI ÀԼnFkGb0eúZqhbHgÄ@y9PsԍPh hH)q8l6;jis @{IΕ\ Vj5AGcc΅q`Ȅ\h7؜]obzo?]u]u]u+Y/:yc?x#Sj64R,,E%_VsSζ+hh)Q@O1ӈH1N8B)\+Q"1V h1) Qý1 AOŎh<57̔$010/b޿`N3pĸr$b\/4  ujWΙ-d`AF"4Ri!)q[A΍8'61LxN`$4Xao!]u]u]u+Fx%w/~~=NrkvccHx/8AHTѰ*1E4aǑ xn^d'6DqH15HpJ]e^9GEAѰv0@uqBP$70oS 1֎ ujcZ]p_שS1*@ϰ> {A1FеlNggg\_#CJ*5/ 3wm_w]u]u]uPPB0H)amf$Ĩ,:5pL=n^ZSn/[aTU44漼|? V5!/Wjbܬk7*c[}  Hxek m Y|Q')1 e2sspѸI8fnwKi@ ֱ._O7'>7>u]u]u]u]n_f3gyqct6*|}p<1"4F0DU)(Fi]'nl !(%GraBQGp Vȧ5kfm # "8H4s9xZB d]3.f( I Y_W%818S@eU Fkeݶ"9kN8 *1E3048QL()9P`$13~W?e>{C/t]u]u]u^Q9Gozח39`$2uf;%6@ڌ423vK+aH CiP1)8DT ##Զ9/T i@c8wkEQ]7CU "+"J =w1P];xEhH)8FRQn ܉HC"ƀ㸯'j,eՂu ymUFɅV 'k4MDՈS0B0#)%UZYpq?~gu]u]u]}.zEu.oc޿P3!=cf.ns\͝4!0j$@)G\ BGr[(2"^iz)gp7lsƑ&qbYkr9gbRJ)AH)Rk%@5xL)G13@ְau`;#$Xss*8lҸ!K‘ń6BJG81 D, 03q$ȲHЁ\Xl-0ޙu]u]u]}NyEV=8d,ϴĺ'F(Q ^{r8tَ) Dہ%eU1fh(?1~ ,~mK))*IqWT40(dAjl}.YÀ$͔a<,놨 HZP ABu ۂ Jufb 'D Ӵ.v YVQ5HL֔3ڎ]7 T D\2JCÆ[!L)R9i$g=b[RJ?u]u]u]BgՆYah3g0ƶEJqba<hL.h,wG7n01j}] ?mP ((蚕xmfkGK#mZeQZ BЄFJ1+dQ !!fF yE4`dpW;HF8 kNڄf82Z)$ ԚWJ;Қ4v߸*5k@ EЖ#"iHg"G?O>s뺮뺮 <+>Ia7ԈԈRq5B5nLZ0qJ+V@R96*DUQYo9KDN+7nd1bMDJqD)z{k`M0Ha2 a )a .kFHA(Ҽ9;Μu4Y'6M@\3z0oX+Z4 *2SڂcWjԺn@k 3F-kG! 9!lkѨ̲,'>۾m[=7뺮뺮뺮TxO>|㽧8\>Ť3%Le in^ro\]]Qg|%q~Yr)V k^HPqKõfĨB+ƒ+0ݜBdwMv 1s8\ FBulvLtFCL~s?ü\"u N!mP,%SZ%a"@NV!혆8Q#e-\!pTZegEa?cS*JIi" HΘR,0 Hڑ<}=_ӻs뺮뺮^Nb)˧؆[g16RjzDCe ]8_feYP_x 1Q*s4[3nqQYr*BfF%y%FEbLZ8#QaETW}>Ӵ΋<ܻfe]#k&8ݞB{4[ڰ:#P*>.N8`eak6SA*uVqaB=8A*x} MD$$Ұ9ur.Ri;(5.ulcB0Tw۾zgN&{_GC~&뺮뺮|N_(|ן.o%=)

q02 P4h|MPAq7е +9WAGj)Cqm(+44b2KJ=1PJNN94FDQbvHANlՄu-92! lܺ}C刐1( q(ňaʈ0-Ӹ#Hxy*("H)S!PZAl݂em VNq)1lZf@@(D3pݻܻw FnO|Fu0p 7lo|'ooQ{WwOp.ԇן_7 kOIqGy?;o~noS>뺮뺮녜jG>KH=>8MF#g&gN3s4T (kQg]{]8lgkĔ&!ĵFZ#2Xƭ``TTAcX[8"а!spG>19!:V\q$-ũmop4K9yOۥܝV@B,GҰawZ+hm! 9gy+^#²cd"PJC5CZ]sׇ#햳 (bs0]@r\`DeC+?܋9ݿP_|ߣo7?xߣ~'mkFT Q[!稔5\!"4|Mu͈}OV&{ݮ뺮뺮녜N?/>gRgv30M G|Pѡ iBiրb1uvk(CLfT,u,0cPmX?c D 807o]ێz|foJowGl]]OưLlݚ@|*O iCJpŚ`8:&SwQkF0n CB-Fokhrܯ۽†V@Թc"<R9b,ܿ8S3M!2aa qdqpyTF$ ,o~Q~Cqo ful+k@8b !ڹV[^l=n9B hP.cPJΔpfV=]ܐ.=6U&s|`&xrz {ѩ@k!R z h8.m5 h<"rt*hmfCx)33O ֜gNX{Ry1bCiʦMhV60qc!.30;C_7w"׿K>ُK38c\:j:1/'BO&t^hp&%>0/G8QJZJS8\)u hm€b'N࣍Ef߳qdgJ_[]Z"ETM=ۢGiǓGO|㷭*?x|: L%xKv-Lӄ@Hh#Huv;q>P9b4SZjJʔ"oKn0YŪh|ap&ޙD. }dmRxӡa +Y2ipgYR^M5> ~/=}2kƸ)u&'JZЗuo xam' R#&U,hvhQXReBR#9G <5 hz|ؒbp8R2]pcs{'PL+Eʙ%'rΗ!uqF밮VKUvd<1j\wC^_|هsS?8"5PyD噍 WtASK?љ#)-1jslI(i[;햛56xR*O#H\Im/bV.}ZTکd KX3`@Т\*|}+=%_𥫨.~.g~U|n8* Ӆ@uv~8o9n#1֠*AkrP1O'( U<Q x-D%R4oHYG;bmns2g牡VSޚ(#8bypt}[l6nKcrW9gri%ƀ30r<2gvׄs a@r[ށvw2=ڴ# 9B#)@T6k΁R2"mʁi]"(nn_̼ItΣUe2gr^U#Z0s#" *T *$gW2_~q>-XFGĨ`(IԘXbyx)`dwo9-hQR1-7b$1jJUP\G*9eN&h].ET1x-heD+`,1t?wysݣZw6w`K)ϿO}b f)\ 0BY&\#yS(-Ywh.lz17>:tF33ȄR528v }O-zÆT+(㧈(qd_=l4# D >Ld'RZ[٩9UjXӱ>{®|8=M R/]>sBG7׌|1d78[Jۡ|>3sLmڜDnꚾ˙y'c )+Zuh=%8f󔗞|T rnQ.P 51GNHi#ߔH4!Lx@)`@ų$K=73s.ٯ|{9=u6^` ^W V)?~xS6-*XZv *^^m{%SMUݖـt"((8F+ %+R})*-?e|YYYYYYYYYYyv|G~Pfj| ηx[ FiFjd`!Hyp{RR3NAuhH`ΌLR"RM[*Pӓd"Bpnw g7\\_

yq_F{1uh."͂* %TDh1758kZY#Ɠ{CU"*J&gJwQ'ZnCM"kՖB52723tlje#TqAerΤZ%3(6hri&45&B\FZ(M.r`^& )mr*m]; "9R񌳕Y4-=$M- /y~ϱ߉-RLwϰufaL@Ih3ͦ҅e1DZiXQ)Y0i V9423Uqy z-6%5Bd \D 'J*T2*/׍/,/oT"=ЎeNO zmBoo5;?|&p&1xO-GR蝡:rJif=:2 :^yZakRXRdTf!'|~@n2 @3Z+;YcunvtC I!,(tӟy gy{eBLJ32Dx-hc5\:8 H1q&3BŒufjLwC{K.gRP"-^2Eb\ U2iHGvztՈnCL#)-TIԚ)p,b8my"ƈk=)r(9qx,i9b#UFj#K|7~56cZRGR42͑ttfNtMH*kP57[j4#EH)c:@ A ˲/+xB.#4GFB*Kb)D跜|TeRЦCW*g' _/[핕w__38{ʂwjІ Uq<0N2Lgqlna\q>E\mQՒca<mSj5N{2R28!'n1Ƴ_g)2kA== hu Z-n[7OJ("( *PA Ԁ]{jmn:SeAX5_"$%}; ~gyQJ8,3JE+D6(e(Prs-K&ƈ%ZҤ\Q`L r"Z3X7Cp%'1RfxͤT}}{Lsd&Ur{t}W9bN_k~Ï|╟a^<{ Uf?xJ>q:QgRQr:H)1"6ZpVK,1c&K!B,57W]lbU2\:%9hۜ9(eGirHjZ7[R@TcK2m᪊R?߰^++++++++++ޕOW>DGW8軞.h4%Ohz*#NGc6V;X65%(e1:1l[=-k:ET6Xc1Yr9=G *1lP6hz(v(PTJisR[FMD*/3YwH- =U6uʣCK[RJMaY&-Yb) R". *gjQ8reBҜAyFD$P*(.%i$J8Al7;6H JԞK%IJ,qf\ ڀdy&wd+(cIKƹ-Y6JkT.3*>kW|Wwmm?fk+[/O,M9&`晒 lwWqxI.X %/Jw J;)9EThp&R[mҚ+"Z,/}D¶er-‚Q%qZWj@j`7*i:3GR>pO'wWLc,sb VKJw-RLɉT2ZR-ڒ%*BZ[+6z19\f/7~ݿp!'ݫH\a@+?ur:O=2RgO#0BUq<ϔѦ $+RLLq<-O]O*;.݆"(8SJw`gYKzMMJ33ԪAD~̕9~ryPj$Qi"P36FJl"ց=PRJẍ́眩*CRB;fg{uE?O4AhQVXpKD)wrRT3,BB-2PKAkA)0XU%q.뚛NiU-d"G~G0_u 9]>_qsx_Oh x~~c2/T-](d"%@T@n\?!F躁ۻ7 |`i$yK-c9bJE#W!n2eq(@sg1/\c\2A +&XkXD#"f8ѺPʂRm\UYΡ0tOvTYT[|x^,Ͽ"g:đ2e,D. ׎G^xg80>LR#>}~OI=jEkӈyqڲ5Z2 (!KKJd2Kw6htJQwy ƨƍ5Lt5c0> Ժck\R A[DZשR $F;#?fK5=~඲ 9~ן EKzËFGBgVH7##vCPňd,[!cK/]SJg4AtE|q-cѾCBczL(Ae@qlQa@3GDlMO]7-9{eT5t΢eԔ@WD&r>g.tVԀx{o"rN@:?~ʲliڡ92Zm1X6L}"J2]gI͂ע9]gl{N㙜5XRũ[D=w]&-r4´ԂL JaFYG\F+4mEyEnD<Y%kP[?ϐ{.l;ǣ8]AQ (w4 L,Kf:i0NeKF%c"E48sUZUZ _?~eeeeeeeeew-#yGc |@jBT^nYj}/Kf(fN" }8q"5%cPw/<ΔnPxvцyr8. 60M+b]GJjGg=&xm؎.td丐e;x^u+V=nLH8 jPUDs{( ="rUZQ,=/u/!"<<'i!8Zc+ 6/%*=(eh"X7E9P(fױVЭ(eZ?HΔDX?1l88#.Xzr29-P* 9%(*Z %ؙkls/KZ.]([*Jct]0G>7xƧqu6h˓'{|t<әfgxۈFQRsa4HZr߽I\΄08)-(b Z+RioRX@mse5Z[|FD!HR)ĴPkqjU`,ys=R xxyř~o1.#"vXӁ8 P%0#9&R0#nG]#]|Ι{zo9ʈ2+r]o4)"?&G>Lg*@P 7۞!h&7L|>iz5;htLQ+ԬPCOTx u9{W+n5 4EQkM>}udbʔȳ29gN ZEib[ҴxTJeqcdg0iڄ% l@)jΑ2QfrMPcI 5ψq䬉USg$?o5+|YYYYYYYYYY__p 7׆ PNrFˌBu!4N{Px"H8 ;@ɚ\% =`&=zꪕֲPc[A,PP'Ez}g=K(cJ[D 1%q$D8Ns\_{˔:o2g4g:{EYݫbPJ 3=I;a|@JeݢUO +XR؎)qg $.NnlSDqĺDQC5T"sLD)J0 ccEjPM\kP!gE3-QZv!0 b*},JM U+Ԋ}7%U/08 9㌳>v̱{k!?stx}dNb9Q0U܀e>2'8GZ6 ˻(a:L'OHppOc'$5#V2*MdT!@JZD'+mhokUj~֐\ny,j"|"%]E-dԀm@yp8e"p eEs<>,9gW {RTJGW>7~׿i~?o6!MPgLC%&`L;|])+vFefT<1/e&ke\?Pq`R!.0!l66cX3&ڥ)(4V&7GJ$6%c.*F3N<5G4'ηXvE("+fp.[̰&z~%&WWW1X-azW1Ʊ\1n*Z/N jW!~_aeeeeeeeee7rG^0' 80 lwҖB.x;OP0- 9"|` 6v '=t~ǜ`^j(,)a:lI|O%1<BUak+CHi"xwmi5bYÇG+ϸD 4kRk@h 9 !csUi4 bpmǻ ()FƱ:KL3)F(BO\" HpTZG j#ݗM,U3'R4,-Fe-% ͱuBNRIyY²L"USք ĒKdTۚΑK?>=~Ӈ>^O/ h霢eiYBR"y-;T޳l= V.+D۽TbMIJK"DRYr"D* 7yUŊù&{Z7wXgb} ڿ*M 2%fS#`( fu,2s8S+ǴI ޶* 7zb` qFP^޽䒰2Oqb,ˉЌD-/r'VVVVVVVVVV~|:r>_ؿij'lGŲ q8gpOka|?_3s>q:BL;\TXkqR鼀4BpB臑fd !xޑLŻVpL!!%bGQPk8BsI& o 5)IX5k[8Uu+΁qPs!HM3|:̂D)R-4g1t9CI#9-䜩P^sm][#t&#}ַkBtx Ѝu䬈b#-j:G_g뽰w>p:Or}йKԹ 9u-&41-儩- @ d=Czϳ''^6o:R-qA.=0 S9xDLaO aAY3{8D Ń` Z1{(P0HN$Z-d]c(ZD8WPk`j`yzN ~H@ӅrQj2~D. Rε+ \0lnwֱVs)G5 jsh8M# c,GsꂖQsBRj;p5LUm+BcuzɯO??;{!&R=,8k>!G7؁0י,zFkzc/B)4sl^{ -)%=1'ā"DRT+DRInja'|J-jV2!悱k0Ϊe#%gc0SlJiBu.R,Ej-ҵ*\v<%aDߍR5U%ʼn#Y~,F69Mg4gU8Ӆ=Eٖ֢B"G!qڿDB'6Cw%ΤP5ނ,i~DL= ➍/Pfζո~lE}jg3u9QˉϨi" ֶ%rJFpFA ƀ>vmV8kZP@]*1xD 5h"\#US)'tDDBAhy%H"pzuђ`fj)P\G͕4gC.M5W-a=)rXK1>3D\B'ň:Ûo܇?WO~beeeeeeeee—#؟&1aIJ81-gD yZ(5a  #_fojmQ9X3Tp&vg Aɖ'K*aܲmxy8xIm*8/̩л-T®6,I~CFR>ZPˉXf^}3bz)3\f4%s9ð1 6"$x3)zČ~C#]@o֯7gj w/7hԶ\TX.JY۶8f昲CExarb&JoZK5I$AL;K)/Pt͹__>5~?~~guJ<>o H9k&)Ԛ@qy< j9'arO:0z|0h sex+?$~ӟ图wCc:K&l! +J:Ö5b=%NԚ XՀ:@Zƌ ۫ K8#v#JGS@J3vx&rb1ZE{qsaཡ mNa ؑ4,cUՠҜ_"rZZLF=:K!T%C..xԪ_E=!j)rJ´&V,m~dBH{%j&xq{rU?-W9|8O0{=g~Ǿ/OX(#ށ3J?8T394c<^D  Gґ|>=-p$KdO@s;ې|F=`:Sb".e#}?Bɜ*ӱE|آs ʫR!&֞qawŹ D.bg>K-<~\f_قwJ'zgqb]lsF1"†ݎ6}#ݎ a0d"9Fe"J$|1T }_*3ʋq}h8VcM8i*BQ'ZDͦHNw5vS!r.sMismAx!a39JNTLj, k92&fQtw; =C*?}HOax wl3 m;X1B@205 KTR^|bm25o$8;sf+3bHg_8\?v TbԢkZ `+fLOc VT^ũ\_G[I|HVsRXBAoFT0М(#ҖlEUI)a6l6#%%T˻:p=0tt]|c<ͱ̹Eۮ6[%ѹ¶鑇+ ~Y^YYYYYYYYYrry3_lxpϴ̌؜Tmr}}͎f$CL34b xpẍ9!22ڜ7xJQWGdv0^!'|>7Oxo1bQDgd؎C67h@рfJRo:r9_c5wo3R8̒]n]! t?Xw_ ym)'zk lHQƲPS*aŘ[RfֱB=T\+#sN-dz[9º'??׾ӇҢ$c'S ̏ٙ " )k!RITS7ab't=20wnVҁvہS_~?Hߍi.Xmo8J]PRP59c!jgVZR5$PDjnn13!նUZ䷶E+5+!Z iZx8 ݎXA %ZԴ(u YZDVF &)o\?2-[NR+J9n/3/43C -߶:+++++++++O_ӏ3/=n{vہ7,=/ibY͎1Lf)]戝rR|-C+ՙ3l@g,A{VGĂ=ִ9jk Z2i =~4Dv@Is8k霹yAXB3>86cϸy@۫[b'HP3wD<ݰEua불! 8OGN#.sRjlT]8M+Uɹ"@.5i>pԚ1V)uA jY≇lvK Wa `:6#^&=9T-{ E*殹DXbLgBj`p֓%`uI+T#DP8? Fy v` eZ Fq c2 /oo;~8> vlZٮ:D\+5GvÆ7z^}9-x#`@?lȫ_:S)G)R`x_zsTI+HN3%Q*s iyxؓ0Y (xhW REp IUZ.Q#w`}6Ynls$.PrXbOK{1咓RJMEBUmKnX7K;k,厪l2?I}+?ש򕕕<,Ώ>sHG>gtKJ{ޠ虪3U#ցRQ!e6sbj IDAT|~d>W#}gv=T]y:mV Zb躀nALeD0FТ,ә#/_`a5c1MgLTڔaffN;41{hmG.c957/3!tx뱴K͙Z2Z.=B}Uڄ9Q0VRh-ZjIXc(RZc/]NkC0 =,P&,M0u>00;qcy>ȓlvayc\5|tzqnkVp<{Kah[J`2A}1ͅ3[̭^߻g36;MY&h#hj1DD8&9hMBiV!m 4҂,vBַ7={߇!ʸyxFk\*S *N=#x43%9 [bD/p`B /Zxꋀ2g4+>l̺l=O|򜴼$ 󲲲0֨v k ,hU4E`=qQi6dI)1'6Vm3r4m],)x%t:ၜ# B*ڷxy8<ǻ<^2/g8 )G!J6 ch1cM;XԔIuf軖(v$tV'Pr$g,x㮥 7S+h^UVq&ۜJ)mɯhEA#aQj%Jʉ% Hs2`鼧$Gg1'\A› d. g-epߢY.IiXPsXϾS~|ZK5vh馛n馛nf4"+5nP"klڷ ij3JV>\@DŐsc=(ֶUR2B8Z-iiZ3V^jV8gnћQ]Xiɤl[&_͡.g(ͦH?Ͼ5]_2NO~OYW  W.7*6C!t#8x{2ՁʴqYrIR\ `j~f:ׯ鯂B'h( %T])4SJ!0Æ\BH~εI,*hI<<J"+]sR2%\(%#ڠ)% XJQ%dxr)Z !0\.pm\)P7z(K`?6TË#ek@ԣQ 4q8 W0T |_<x7sM7tM7t MrG)Pĺuo){vYtM7tM7t3rRӉ>hd`+IJRo `'QI)e%SkEuZL \y!#1.Hs[5\a]4cr[J+VY "W~!8 ]J`0M .ԏ'EZ JJvt0ObYTvl\f>p<%ơgyϒΜ/ZjYrݞnǴ.XjTEKʙ \s\.\3b@""o8#!ƵBi잲j%^+,3=DJʸ!"5npSJsr=c2_sl{ OcԕbLj^O}ҿ%q8^pxy:LH*V @F]+Aqpd|p9B@y^q-nC.p8. |[B)9N3V "bsJtΒ|_|wO}׷wʟ_,y>P.¦wtV)(X6`2{Ӻ凼<Q1\q۶ѣu^ i]0@VKTO{}-3 {ѵEb1vhלt!L-kO'\.+RisRҖ֔>]G{s)1ϗV49]P]K3iPiiTWTAַ*j<-"JN^pGkk ܽ< iq0l |#Xp> #x&9;>}XKVU(R["%e͖%f Oơ'QVYוB]S$ NT֏'D1UE!-GcFSjčƐR"'Nq#bkV@yY2> 8|e9_2_&TY 4(x$sl;%MۣFADjTX t@3.X'=$uFRgTiZXdTC6]xuezh"ƅgkyJt~a kIyN= Q#EKVsDW=ۖRs~c@lN<軑~wz،3` <\N^}~\S|3<л 5$5: lwl't݆ແO\Lvt|y:nhf3 }ߪ:m}tҪ>t\.'xP+=F.vwyX1]s crWx2hR2tfXG.`ˆʚgfƨTʵ* )aXВI2KQ(#ֶV8 :%Ηr%kr`PIp:.`aJWVD 0(b*XBoT 2eB赿^Mg̦-L痬%i}td馛n馛nfF(Kx܅~p:3њ|@MY)YʒW,BL XR%6X ҙڀnTۢ"-y!`JQ1 H3MĶf3)/r^QBL#S0Ղu^2fDPA3bJ!8X,ƴ/kX&\FDJX|WZ5+ö,sځT U˙VڶD#}/0E ޱ_e>c>foKL5Ca3B+l6;3`;r;ưyg֥2e,K+"LHL|R 1PlxtO9!F"T#R34~XG. vL V5``=(|A?eο ]0@B)DXe6x-IY۱1Ԃ>x׊uĸx},d]na;sP5QJ(txӅ\~jq|8G8|>[4W=V6'\8^cRmuqgoin馛n馛e3~_埡ϿY[~%V"Cg1F5alVӅf蔜ZRB@PVڌ'zMX Ĝ!P5,K<}ف\. Tg%NrXyʺ1ZfT3DJ ,"ELp4fx@@ȴc'6ca܌ BJfjKdȱpf4Ooķv[&џa Çrꊦ#}Xd❡Bi#@ǫׯq HpwFhf87È#x,ӑq>0lT2h18_SZ*g{K[Z qCZ%IkyJ?j)-]iUI3JN-5kK3Aj }kHKPkb+k\DCE۪VLĸµYknfi? t]v{vGJZi=k\P BňY}JNW^YVj JyARYG-G0"벐Kmky{'_o6K#n馛n/$rjfճrdǓ%ί(J@Yjk,d)* m󅖠sF2w,K$0Xӂ֊3m:ju8n1F)U6`1p}t|v2l1{8S^s|ooË!CD Z$xT:|"ezKLڄZwXH xȹ͆}@pUR%Kl%2bҀDYfT' 4TcEVcfݖ'q8AF1KKxp+4o-0\aJE)!px+bj@v+C.t IDATi> !x;ȗM7tM7tM70rڊ͂7 Ow u0X,-NۍUR%㜿2 t\4GR`軎qc$qZOLћFzMt#@ ZRm3[벌@ֈ3/>" 86FG } m["L-mX yz V6]KBl<+gjjGN7aGye:- ȣG;VU)sd]'L,(J,1gbYHB2gDi/!-~`3)3[7Xl[LG#]S>6 Ƶ尚;1LKt)<`N s\g>zŠZ8vM㈻& Z'K%t#ﱶcsc@ja,lr~0/~FWissr`7 ﰬ, >:PVZ!3S_y7+19D+"|Ulۿ8nY6|?s_t>rĺ4Oʯz qNLE&0l6{;Rq|x9sOCᄏGkAJ0BO.+Ƽfg儮zT-1Hmܯ9kth͔TL)jxuX0Q Uqcr+8Kh,c<֘.}GUa]W^=ґsjKTuEXq*H`)bĘO=bkK\%3Tc DW-8}P)R+;w8./? [}2?O|wϼ|M7tM7tu/Jz!mŻTR+JҖQՖ*r^ $j򑩢 ŻݰA ^vXQk gg.KL!3Wn<*- T ͈uZO{ckeiS< F^>n ncȲ^X˙NB+{۠F1 "*T 9G |$džs{141#wǭTgˉWLcsm  868ݒIJ̅q̫ TZZKԙi%m% ,`pCs[CRp|Ҳ1#jaqtGQj-T*1xߵڠ*b 8/ _OZ[{#廿Yn.s~Sf6]nӌcZ 1n[o?l;\^Qrd衚k :#Ƣ~˺HqeÖ\ ݰø%JN0y N,F.r^&0wtck-j 8{1 l7/^&zFeD,j*⚉cE( c֕%Ej,qn=4qX'Rjk<օ5PUJQbjOJ_E 6QiLjW) kf+T%%-`1qPku.-JR7a-5;`0ZM|>'>S(.XbR4YI¯C.ėXtM7tM7t3r~*|OHMf=}xm:B0R!VP~-?b-b2˲fϣ\8 1W1&!X ,˒91q_LǺEk !%EJ[j8+ ~g6.q,Ӊfj8pĂJJ%녔/퐆c|J;vݾgN<1'bZ53MzE+Cɤ31ԒD-qZ(%7(k4+zt~I,+aZ8n h ;bp3v+ӴD-c dD1 u`oPJj2Y#T>]/U֖A>NyŋGJQnZ{kcokV$wk,VKݧJӁ))6v¦7H]!,!t;Nxg^&LήiWJBԅ0Ty%wv;RZYn7o̲R4Rx~Gj3|_`m[oJB4WL|8s:Ht>TN2씷 4`bZL3Wcƹ7/c*ճ,wJWSyIjmh L̔y&ׄ ,͘Z?֒(X(],+ A{m 02RJ,%Vk)%N'ib3W$G ~)5@Emf*bXB':kyhd^[gIM7tM7t+OB3cgG}76sx۸<ZE5Z#h[R1kdO8/ \0L. (Z)G33qmwA^W_Nƌlƻ\-3)4F^a};n,q-.5w}z0{eKSvJ$Re%ե%b$Qj$չuL\_nOD1<?$3qjW )֔LMX|=97Fc[Z*ji3JAITI`0L)bQe洜x}'[rvP=~ϛͣ;Nxec kgB3oU0FcSAkZ* 6}Y. BZrc3t(UМdXH* Oqk,9A[ԲJi~ _b|~{ ?cf*djZ1&u+F+ \2{y˾~Gt\Kw0Ƭz`Y(@+ðͷcԕ0$ IO>=0/GD*3b*N |xɴjEp8NPSYuź{ecx#j/'ֵ0-{}g~1 (zn׸ Rqch =站Qm0d!XB޳F5(mKRȢ @ŒKf*9+Tәa0:SJ+kmbŠ(1.  0 :d8b s벟\s+ֶc2-O|ٍtvß?ʹ馛n馛nfz|gx Doo[@L[fcL1i2OLV=-cCm-Vr.\ȴn8vFPer[Cf^9*Xwe jXjM%WԒC.lعyXqBړP<Ԁk* yry5 ߰w7oQ z-2I!c@r^tn`FCΑe=Q$斀S+̷0eZ.*9:^<8ZkqRT)Tݯܱ,G.0 ЇBlׇvæcn jQ hE4D*1g -i#5Gb!xsu5m&}M63W1oٔp6 9 Q`iIX7D`=@AJ%5`Pz5c43tr)ݸp|7T(CdM I̛oly_E9,鄚i:tВaqL\&ֹU31VRQp،H)[i~{;T:C{$X1H2ܠܡ\pB@8( ." %ĈX\ؖ[8>!3CLއu\M$2R=RnUzy~]P 8p-1%^/kj@k ZVD[rJ8ăVԕe3pT-"ƹ²>Pԑ+RaY+jf{] ( N-* {\4G5lZ&X0%~R2|OPP>ZkT R1z8k%ƕƴ4Ѱ kԲB]nyyͶ> ZON-:묳:묳kS V'69^2C.X籦j4ƉXy%w 6oFh@Rd$Y ,Sj1#}#z:0֣=e:.66h9}9rRpH;n0#I2{2XpAyq c3ZfƑgƍk#NS&[C)Go᢮IJ`Dm [R9WĶ ˒0 ,y䘱ᦈWUr-TSX*qT/.g?ʁʂ ;kRMa83r81ՊQV ] \"O$TsNzA^]GɑR5$=*<>VPG.gcäfiI`O+^1?GNҿ (8㥱SjZ$jK^(N W[YkVLZVRkYs%fUEYb-1,yB|%3DBRb&D T tIծ"1pwOV.,{av{Ƌ<6nYNhfd9RzѢvX n(މRKjBL&rYPP:6zc[JhT4ZeHYq%Vzt]Dza&{OY0ׅ+.lT,m%Ns{I(8OVʵ}(̨1t]3Tk DrFLpYJY OD:묳:묳:y||ƭ^f !0 www\^^ruuW|0\3#ÁbREh)LՄj] X(qBڲ-_)luYguYg]-b??g׎Ww@A/=͆R w{ua+PE(j,tU50RZ N>0[;T6À YR1SwZzBʙzpޡF8J5F=Ԁs%v_ ֶ'F 2nq90Lu#´Iizߞ[+(xT )EJXh1 t*sANRvH,uB<)Q^3Z,Kl zJVU됏hXx뱮h{_rf]XWo>Ltd6-bYc@߇6REyqGI+m wh5LӂvZj hMXc(5"Ɯ8%mYRJ+\#1h-ZZ'Beh3VzJZÀ9]%td;UGi&. a6^ߍTmg|Eϴ>pu / #x`^i zCg ; OsYguYguY߭&rr?|ērFnbѷ+7%AbbZ2 .@(RjW=Ct0ucPQBpt\%`bPMn5}{BA͈8|TVZSJK6vOQsEqִz=OoޓSS7?JQ!N1ZĵZ05ڎu=Gii8U%ȼXPC;BpgY8T`U wX0ER_\^lQ vc-BKYj)Z)id֚ƽ!PT Wӑ)K#зڜl[XF0 @LM6oנ^E T2 .J-z~ U3pQRJennn([bZCfcDw9DQgc7c={%H؎-6#R :Z *bǩ" 9/ ]`~KpOYଥ֖lFByjK R+h񖏌 F᰿xЍxa-qVkZd0tG[8ъmk測 CqFX\hD^,Ӂ۲:ńM rw{qf;n.c ^B{Z:exLL;ĉ.lO;`֒H,s*dp)mq:*& /cHrK%j)8$`b1x 8q^(ٲ.i]N/G=Z0#8q B^-}pYFj@iGMoSaG\W +D){|_f:*t8~0p.&a~fJ^=rhx99bmZ+ 2BѶ VДJ aBڜLSOlK3oo?lL'mU3l[VZh=9G{6COš*r@Øf?oW@-`JKf(.xa`}0^ <}qp8P2)>Xz58 U`؍s  *UX kz}}00ϑ)dGZ#K}7woI%sد#w<'J1λgx/bC2&CaYum9RMi&Qe]"Vv綸Z6SU[I:z RW0-=bn牓cbRj>`3r-H~XNPU@ےNwMV2pJ(LjQD!jjl@1zr-PΩUDZU΍SrVňǹkF[w_?cs9Տ__M|xES&wg[+H* 0w=vǸ`pa1Ϟ/C!}{O͸ a& >\rS Z1Vn8:.7XpCݧ]CG\^>z3Ň Dzj(em* ˲+Ás%k;T=;K\&-bT1ڮ7GYwZvo]!%YGb?wBYיZsW rť1==mK<)k+4IM.2cRRs{kD~ Z))3c F@+jzR~PrC޾=4)u!zb}OGoYJ EJXEb hZ9tkIUqCUS!C̑ FЎ 9C+SM8'r8o|3NrdS9Ysc?/~˿N3G- +RDOВJƂTr)畘 b2bLO@L+cúhȆF,>`xj0"'sƋsViתs=׼u<i1Æ׈iyC>xk L:/t]ϞG C@mHQ㼐sZ5Bs+FmN;,T[1dbiJcnQ]km>z躁~ؑs$%G-MqݽØ»~T8GQR-b]u,emƓjE b,RY#9gqVH ڌL9F8.+ۋJ*zJ{`=2twƶ*s&..л#Cׅl2ۏ%VοuYguYgu6r~/c230o>x\kmFMת{KtHKi>-=JשqiL{OƝ2&kdL6H5xg#H΅{1}}|Y)eEUI{bǗӗH3`Q1T-$-zd7 ')뺶is #L*:%"hQXiJK6.FX k9Dͅc' =trfY!-3b,C0M{Wk#ϿuYguYgu6r>į_XQ6chMq"#}GxUk^U,F%k[޹o3k Ji LAqAsVwtnhOxS㹴DPRK9KKR*R,8\$呴8o)5 +UW(-X`PRfgd5RgRMĬ\)dj8NÆQIkf]2dˆ_fidmǸVc{XrDs!|e?!80'ee4`)a@*AS4.1yMLBYJ"9.?\ i}7^8ݓӑQW6b+RZ)5ӊVֲ̑X!t\`Y >lxg !ƅW[Pi7V%n lXj$Ղ! 1<4UJ^ŪRӌ=92maZ F#L~/}Qw|W9;W[v{C͉3]6ہ~p@q%LS1Z:֜'nV}q:n۞8oSsOxv/ue=~a>#1=nt|5Ƿt}8O,gG5.zɖ3ΝlEa8xh2F"l}SrwfhOyu, Hqqa;8΅΍DLB--aU3 Z%!)6C: U/U+ <Xcy}ZP#dPyXʋwnxr\ǖƱm$#X:4q8ZZxb,9Am j@}yf]^jVViݪU^K5SqOs[G.~mK}Z0 :Wx7X)گ8묳:묳:lL.6Dj-X1x+IRV*4nemO^K~ZZ #f4Z4#JMZuP5P2R T8b(uj05CDڬyUǚ+1$J12ÅDhLZ@LǺJlGNȈ Ŵ'iE8~$WeK%rZ1%crũ%`w(j26iʕ.NcAH5Z kc^#ZZb hXu]/wrlw_˻?E:j)8]|냯&2{ҚjҎ6 24SRDLh8 %⚑ZcҼ3];1eD hhl` qޣ'?g}5q,;4yrKsAQVہ+>zJJYmI7۠X\̋w?r?=ЮX_:jR>|7|&~df\_=c>M~n.Շcn/-o7g 5pw[wax`[4=602=GRphq}+_ 488t]tw-(eH@P8x!xbN M!%>Y +)TXIԺ,XsnÑl\nڤ{j(<>VOWý}òX b˴Y⑘W*J9aG&rĬ)g#N3kJXJj6gZr7\:0*&=&k$j4'H`1T,<6t$>s7 uYguYguȁ!GX)"8kN>ʎ)ĒJ*!L-gϞ|O?49L=L!XT=-oxiS1t>X6aKEMveiɔ15S!D*3F[d bX2D~ùap9T T*%(40!GS a:bZ?Rڸ* Od*5YT0&]rssEsܳ~CJP ,:ൣ:bMdH)+UH+)VJD(WڒJE3Jn?+2aY5.//q6p2ϏtI$#))"sBL[Ŕ6 3GOb]W!?4ކq ];tˋyd^X#bIv;0 $MR)-Ĵ2 o-1FҼBCK:G0~D+S"/kjQmdR-7)?WO/_gx8ҷO7OGLw`ĶWqxa:&BQ/?V XGZ""b pO-ֈ~hҮyn>k|+7x[C)q>qާj^Ⱥ{oaO|roqameTJ^)2-hD0t@4!0brS=8a;wy{8n LB 2Y7Q`q^HiߖrKക4PvUJ+,8ceQZU*ZcK=9gib.y):%swg׼xV>h1LՖ }[EqEp "H{qh鼢\軎cfY"UU  ~x<Ҍ˴NvB+`K {uEq]'޼6ſEfYguYguYg#~>?G`;AkDjjc@ˋh$U:Dg;mL%qJrxx, 6iM{AB"PB+F""ڎ!#B(OI)%-TmJ.3H%#*F1gxY+ vldxxܣD *ĪTPcIJL Ȍs 1YƛW\\%W=>D7{^a}֡-wb#K3~EL3>ޓcTrh]K {>}sZ4Mw#:H*΂ºLN,bvxŚ\ !`MiFB-ķGjq f(m-Yu9u010]N=q]w݈5o3Cנ9fȅnt8RM8SK3n< Ufhiyk}[|pJi˅1FԪrc6^:! _s'Ll0v55qv$e_M7tM7tM7#19׉~/ &\Iy_ IDATi"E6~`Zִm>4GX+e^NA!5 E;@ۙ# Y+qxƧ)We:(| 5/ĺbR݂CQ&} ֶTL,W?žxdFѲ6Cc NAGh 8H&b\gLm[ :ےϧ;N2e<3+JJ B;T Җl*J-g AE3m5UIjIb[jJ }5e:3F+\f0b-3i̖RidD[+׈=} r%!Ja!PT9N n䆻ɉ?F: [Ĵs}(9F[%k͑f*-E)J-S;PYߦò6r&FM [g !Rbk$+˰[VX_el/jf~ }6Ph f%`ЇQa+|˟ze/uz|y+]8_6hKZbtldLPW*m6<4kXӦSJ )fxmsJBՀsJg{N,,39!Tril b; CnΩ-?QDH5Uxj, I`` Ęa`xGUOVE(hY<1f8QKF#ƌ2tm|D v 71N\WrtcX*(%Fr)RU CyLLU~K@U_ow!\)[ @:|JLLyEL3BcX1X#gXzg}Ěf^;AH S9_NXk3qLcMB9Gķs -3^̩ U#}_kJJfZV/L<=^@c˺bM` 3I<>>t~ofwpDY_f%'%aErba][p]{.Ԧkomƍum&J](9a 5l(c;r|f$3kXrB)R2jg1"xc%4yv䵭LœMBI"Җ¶eJv5l^%N( ct=7Y鑾'$kwO\sc BY3Cۯ[ꦛn馛n?aF_ yƉ`+dPhb5kEDP+JZ 骅uRJMT 0ioEmV6iUPSb0ryy: .8*FĒ*#y*+`hlmV8V1{Z|E#Gg*X Wv{x15P͂mjuݑ~ouZX"Ee:icu&BE5td-U(K"WdEmV[hm*y8#qQMĴڒBF9T-vt!l VI]bMK,T](P4F1ZA"˼ٯ{eƁr`g^, ^azZ=}5%WBJ1-%f.TZz\2w~=.f!oa}D%L+Co1Zi8k(FZ[M V\JYGđ;QN-[Ww8Xj)LĺDѢЖږ<)ƚcZqScBp&ʡ2֟JI UDr)rr+2+%\^Ψ(Y,Z0&p8'ұ,'w>Orȸ{ݡǐXĵTLPA=F3*ڞT3j6.XiI{Lt~DĶi70xu|>7C,ºqkf<%PGJq!1vfܾ1ۯRcbqDʲL^k?o馛n馛nWFN_ζY| qJ)uĐ(T W#d}WVA|2tTCJ V!, $R"uXkZ=U{DbLbZ:a(Z} *ci:QW6@ɅK--V5Sr;j*X8w\ɱBgyFe]XTfrf U%Kq|y3b=a]R^LIœsA)x[.Pb[Ɛso ըŹ넘#bQi\6 ޤyKb;0;whMXږQ@ TU &Pu&0H}ot[Cõl qĔڵ("rhƸG ļ6O>q]&QWBKaw8Rļ,>6qql3TȚHmf+Z̆%Y.;ş_䛿-P[߾if|A^Hw[][GbZF0'mx n@;;XE_HU'˒/\Ϗ{Kº\Hqs4˰pԾw,\#DH9T$MXPi׏k-Y fJޒE(!8nuJNsTJ^- K:c+8RV0m v`{|{drIt}2=GCb܀3`ÅG!m`Ԉ0ë55'J]Qg0VTtjeYZ RMR@"*b'e`iL@Պszrilj]Zmuƀ*CIMe"Ō=ĒQ:s. ouF/޾}bʲ<;ɿg_hpM7tM7tӟ(#w~-HZfX߸ "TsVe|`T)" 9n|HKTXVxǘBtvGLUv[YcbEԲb`LԈqsyyFI 9ؖl xU%ژ-yEVy9Ekq8gʜp1􁪉&Ĕyu7:#,} OOO<䒉}N )] A(u:i0Ԭxf2剘ϔ%|~t~H;(5nWP-5c70iI z4Ę1ͫY!m H7g{d(%YjQT@GԱܾ7pG=%h[BZ2αl=9ril1$UTa;O/'5uaZdTr^qZ%bbm$^^}Xz9L`$ށ*:c R&HNXJտR\ ̻/~ȧ_?p/گ~7ooesJzxfS-+!-Џ()k8K3knx밾LJtjՠS}ǧ_~L+h-}Bb-X#eŹLpb)Re z9g.89}sL%ŵwD"(Rf@JJlw=bxɹvnwGZ 5n57%xGfLYQD*.& (;tXeC;0my6։03DL9ے l#RKFKss*8P#swxC^?2p~.>RGz[qRjάqs'2Ydc#/!b}O`@; {Zxzz{rZee:Dh, 5c 4,5KKmj#v,4/ CJjKmZb }.|@cF)JXCL1F*a$剔tv.zG]W([ռ6l\e߱Ys!*VkZrzm_VԫTXopъbJUqBתeVZR4//UJu"3˥ٵ#v)|y?ۏ7tM7tM70rᤢu+xM/C\]~h& d:@m_cYݪb%Iz=w{ ȗ> 2c@v'wr>aZ9l1cX c+rRJ:Xj (mG:ҪBcD/̺>ZBK{F|\'.wf8`mvdT<)6FFJ\lz,kh"8;0Gሪ28֣tG-`tx CuK N@)lɶf4Sav8u#]?0#΅fZTUyZ9+%'.S[u;L d*Z&3X'Y敚ca(h;TR^YJ,qB5ni 3;֘[jYRQB](ib@ʉ<0jDLmaJ`)-ͰG<]];= ]?ߏ41_O8@ \#9"3Ct!R»Sv;nw#_5[5ZK7<F15#Pk>t[m2ƕ+V =RY1m~ 2wy~sk38cw"t#wDJm-.J$D.#21Ǘ'N`L9`So{qU8_sLL%bQ:JiɦRZmk(D=CJI-ijm@V*VZGl:8NԺ5G(8cO=tmeYx<|?}sg7nM7tM7tM?1'r?o䴠uyRT⻎Zk;H1miM39EڧT@[%t n8cG悵 R^9_e":x|yGLWfi([}mmƒXO-lr5 -.8ZMB-îAIBPBo|2Sʌ6PqzN:K;t+;PTG#}`Vvbȹyw&ԗ9€;j1=R!P2hm\g{RqBnd{gV~ =[cLk;yuy-(8K. Lf\'>g]g؏{e|2^ݽ;Ow䒷[0 m^{K-b)1ZuYl'X;կOu:6@,nװkP s<,xO>m?8AFqRRˊJp4?1_t V[Vaƛr-`T҇96w#Vou#aw`?{K3u~`Gr:9ơ-Q n)--16 ;. %F#JX=r)V( ta@Ԥ\黁xHU +JNLB.tlr)hG?kn]VJ-+kKJ IDATۚ[K.K|:q\{.IJ6t3o3,)=`7vWG{ Z"- Dzqji_nϿ"f ^T&j*F(Y 1|iFl)u=vHzJq3;R={Kg ijL~) ΅uYҪn;?үݒ97tM7tM7Ǟș/YB%l 88Y0{D@Zy5+5(eVY2[*vǂY7h[]HD'*y> tJPKԁ փxȱkEj[ASl]OuM"BIkz–1|źH53U*Z".mm'X( riv/rPa \`9j@ [sci|͑qy~Fxgޓ[9RRA++bR[B y~@4\[/:I5QgޞGT(U3juWb%=<_U'q|Ź<)]rDj.9 so52>4Hn^6]{/[ꨒûw?*o0PJ3{1x J@~0s}1ǙX2P8rd>ضWesA~gxS*K/ufY~MM5^ ^!aSk_Z-VzՑB!.~bW-@m@ZZ% !rzz=)fnuFMݮck<ƜQ Țt֊ւ܌m+ȝXݸ#֌VrXɹ+XV1.XԚyy~g?øoV 'r GFr]o?)tM7tM7k"??!ftxg7BM[>zw1JJgjؘqG~mZwX )/hTc*}c]b9mڲAgm[Ahڧ[G-J1X:D:|}lG#w9Vb\0zfJPRUks* 8VW%b.IPalSZF;\G׌}weI{ikpOFjU\%mֻ9㮱h1 H7:|/`DZk(5bZqs9\'1_4'C^h6s3)3(ÞO>8S2pzGjI1Cɵ%t:Vi&Hl1Vڴxv=_ʗOޢ5.-t"9gj+~LcwS 8W {rL\RZ)5ypmL'RiAqyڌ3݀Y+qmpka>ww?`^_PPy}ll^syx~_':RWS a+Ju;޼y--L˅XƵrJʅߑK.8S%* -kA 8kq0Vxzy.v">YZ%$vmOn УSDi%ՌH k[0t:#~?4OXƤՊv:뱶1~8*Z3,x/%JJ Ѐ`Pf[Fp.lKVfrZPVj@)1O)-Eu=J\on1y~9e㇔EL3tqv4o_3i1ߑ86S^[ \1_[۷TM7tM7tM?&rj9cWw\gt9FD'O͎yn[% Sk&ÃqtT6`d*Y35& %EJ)tΐJ3 oI;@ɑD|| +DJnAfoL̵-%i[邧+TpGJ*3ނKm}-h]1-ю=uP?{zz@q(ZjL6hb0)q*g]M-)OF(Psĉb<wwwXk*4Ђ5XuԜRZyy|G]GN.tg\?%ԶM2Ƙ־U h/`Sljˆ1( Q FiUc'h$UʘR|֚1j{$΅}e9ǘg<'+>Fr),<]YDN# n \{* "n`FY3|Pqkg]9Hf3xJ+AG4b 9N*HzC)||tT8~dXgN@'Sr|}-]8r^3]NurRJa$69}ZtQu К6!^>l h|U! 5/ C'N2.VTl>׆s31nؖάBm3SV[\&81FDϯq[Ψ[&x}pm8NJN(Hr~fMhzIDm]:AG>㹽C$hH@ZxoּpWګ-XQ*!#jblۚʇ =Ú S+ 'i'Ct8_^]vqq+,)>mɵUAZf)3en˅ۉן=q M ZcdM7y&<>cHO?gNKo/轑n 9gj\.Q6FI5Ճ>~WoRKe&aL{tHku@93ߞɹ㎵&ò|BgT<)/Ao[p.3//|tc͖w8Gv뒸 ])M>z:8vvy^)jCF)\^SzE[Le|֊JoRÀldqb<^;]=SJRb*bQₒRb|7\.s#.x4x޼{K+zcgvH IJ*y4:ch즑!4JJ;@1*?e?O?M뮻뮻Xz_~ﱔ0 7;o&ǁ̒rF\ RJ0 UpA mIk*b >duHmqB *FQg5 뚬6J o~M95DjK딦4\)7j7x*5L.J?S̒.WЂ0)tMzbpF6mp GZO ~IdY*)URiuR<uwOZ;)<Rj8L;݈p<x||ݸ3M~tRڍo\OtI,. ,bbٖWrYqXխUƚ˚Cwt/v=ӛK,7(9g~7 !<ҩ|f 1 ڹN6h+UvhVBԑv??»ljV th#ƑšnhdJɔZ*'!.G|QUBTsJeNyļ.\ 9[ԕZr+fKrlֽ[^1nofl8$.oYkJ*>T҉eyfBZx(ga0;:VjoN})ϗƜtZXm^oVtoƀJ# ޾y&byzd|:dN'rd2fjo%WVZ`ID?\Q  g~x/(J7V~oq3AlQVa%fj}Cuq^sZlFyj0;{-GvfٖDPNi8@ࢭ^PR ÑkjjXz'+^tb;Im}oI7R^Piy|Je];aYVT~p8RJx|W9|EsB%S?~Zu]wu]wwE9O,y1F >8|!j:.5jBSV3s1^-o`j2EM2ъn'b.mW`6,#tլT4I?GEha3k9SVuUKզI)u&ׄȚ/K/'ޝMAH#^hxV:o:Ŗrjxy/iO~qb{o#{楓Zfrf]_IV|nUWH8G OFSj3ŤHprhSۮSʺ܈q$J$-)BtxE|[%LoV,q. ~:э^ qԮj\"Sk^uϚvYSBD+)Îg|UZ)Ʊli\Mi#ҕ;02RʶUjviVyf>48%,MD:@o!8du%׊MǸMwaYno;XćDi3-:'9׫-93U` J-ߐr(z>Ѵ0hEZǫ&$S櫝^v&O/Ԗ2MV%*m)ODD0ђ#:31pji-ӴJX3X{oVS O|uUx8죙dTXʙ[MƝ ^^i1Urʄ9VEcZ(k1sYmJ۪OvZI焠a{ ,.*5j9/HӋ>B²,J\Uc }PSNb8gnqqx&qfX.'J|.xȹ0*W7mC?wwu]wu]w9Z!xab ĨLn+RɵRJJp}e&ue6#SLkbxxsVjŦpa]V 1{%+K*dW )#ׅ: l0Uklruh =t!-^(,DHtkRBPuN/wVuR ^A4a΄~xxǪ ;HB@J^ N".2<D) umHiv~冀J+ 3rZqTֲҶ6nQ+%V[!LkঝJ*rAj+XSؓs%-+yKh/>P6cb\.'4D[M*Ӱęyʲ5p:hP\Cr\5OnWK+m!IGt|%~I@\Aoy}L UܜX2R)@Zjl >~ ŌKFfkLjF钊DTn\Q~Lv186(IlIkLC4HBJ tԘ:hN-+8WHf o E7s48xnHTKxGi?kK}6xj\ÅF 9D|#UK@u|}7nJVkE]~bgc[3yZW3V1S5b)wx=?4zK\+Pmv1O@*mj8d`i]lb ~Oxx||a? R)yEc /ی}ZkZq"xHt*Uj;'ڡNӌ@wJSPȫRhQq8hM.5wv'ue^։X/7KJm6JYzҁVpr:)W~c]wu]wu]F>Q\C^ _ÀčJ'₣<_xvqJUۯw[BaAZ%ऑˍȲz:k+H< l;|*D%\G{Czy%7o%":$jb ͐{-#3$61/'TQWR)T\kA2L#b3q8c`Yn8Ӵ';jih"d=7@D +sb2apTiH7i޸]oMNBi0M]^/ġ3:Ӷ{u;̥b!`yIx05cspΙSSjg-\70Kc|+$0;e!WZm^T{\9t8ޞL4>дظ6ʇ8/ هRjqPKP:0 Gaϧ>E H;aiՃ YLnژ0 u Okulv;+5aYnLqJ<ʺfôU-s|xҨƨRٌӂ-uʑ-YX,xO}&HÄ"Ԟ93u[BImْ^}w3쟲% q?L4Pj W3z~jU^Z+Nӳ¤ Ne='VӒwP[" M+S<^'/~7Eu]wu]wk䴺%yc&r3ʃ*qpL@͙3Jit/LӞyq;[vy9֎JÞg5.6mV]>"qA˴QZb 4ѲO}"]-ReeiaNZΤ~̚RQ'"|C IDATӚԃF)WΗ8A-23TJ ڀ@GDz@'ь|i.Bk=nvZqc!BVoBF*W4z@w/uKr-г0Dȹ26V/5QSE];NэDW.8q @ JZQ8 f^wO,3 벰.vkH53 ϧ%FVT( VBZf(tP*/r\Ǝ4a7N|2}PpЬOÛi| ;Ը\Oe&x!B2LM?N.JF bwZe[udLqt>zwp6f8)3ISyY(.S#~^6.xfqژ\$:34MSόN JUg&5KZrnzeYC] x aKϞX*ERR8Gvӄsz%Am{87rDZl櫢΁*}3]0ȜJIXRL-s9haJ'tK0ZW4K9jc]gLnB2 ;KJa7N\=[u9%nKVɌF^W5Z2!{\s56ښ}yVn)3\= WzYu]wu]w b|ߦ? 10D6C yZHy!:O+2 ၯގ ^ Ӆ?+N,9+ > fsHئxp:R-+M(+bg$jd=r8"q7JW6eZeFHNO{);\U9ouQGh8oSRNe|T,Dj^ x2G²u0 `ա5Uj05]}%ۧ^tw6 biVBf{sAgf91P(ur'LJ5#ǝq4C=3=q&.m zJ*aq-!A2fx8BJjKaBk?xq{`tQtYhi R;cHn wNQ+Rn,K"2++KR!eG=&ZcDj D46rRk4څaOif q'dHjR@nF#]i1   :yjm4SsEo6A-UndK@֕R F>'ؽEȺRZe͉Jc@ERPlM˒P[YS6p`"8?&Z!-7rk 6JT=QR@TKz'O/N‡N-Qi%="ۢT#@mdr/h ]OG!砈zJTߒWD:qLNi6_.SK"#Z'%K2V:HMxN"x vs+?rUŞ[GjK]VuF]i0/gD= >_ ~_WQu]wu]wc|;,0l)}JUQz䴐UrZ)M'B-4n7szGq&˒! 1Tsoվo ZJ@l\50%@b:q8|`4rCVߎ:HAYvp~u&w>Se.8 'bƉج6Ɯ^ψ6h`1)3N˒y=Fu_- 3/+1H]xxx"bM6U1#y V،;fԢRtbPKep %CJ^ӀG:'ea"ga4rΒI- y8m~6:Wlim7~7}o#ϧN/T^YHqHI²,cV-R2Po3a͍5òVbP;ZΤ\BJ{ϲi% {GJ QHt#0AV ]6]VRlCSj5Xs9۴vtLp<~zr[*=ǸQraYo-JUb k*Vo+ZZ f:қ%.ՔZE%E's<<4Z3UR:: Lk(%WbjU;E;jwOoq2l)wm\|3E٘`Gǭ2PKg?h[ 53U֔Xfu&kVmܩljI̧qa.f*Ӏs7E`Tn3d@kF0 %JYk00M{޼r>nUV&J-b *:*͆G*U|6."_}!D~ӟsݻH6 v;{<@m3?~ge. Ɵ_7ԯ3o1뮻뮻{_&oĝ'A}]Dڶ նUA#q>w̼\~֋ё$UdfҪ@3nEa`ܮ+%QPIr[xQjTav %wPp>lu*02=!vXҶ@TQG5e"! cgN׏h"T6L4j&ՅT3./u?ZP?ZL;51x2 1Uc]3ii70 ӊc 9D"U fֈ'j,BoBoD͍iK}vBMZ'ěR#y~㇈Z:I}OYm1*xnohq+jqvx.q.|zNi1N{|J\ȩ~.P;Wi2 {g[j=&D<;jUƮ2V`2aoqG}Ñuq:?, H{)SP:WKhx wEJt#.6%T2>c?rF-; a!AtcP n܅^^13`|)mca1M{T"|c]Hip%ǫxaׁ֔Fo@.e-nP~6䳉h]@mif\5Q{6067-J1 apx`ӗ4`3]펼}ZF">=,y~cIRTi`[j3'h(nOe4SDz.qիl^+WQQ~]x| ߍ~mͧ7J-8U8c]V7Þȹ뮻뮻M#_¯oedRƆNPWͻ4si6! SXХJ3u|j5K88EHC8*@ӛC4]ą#˜hv|WF+0INqq80N;D+A׼FR7jWw8hs(StMX67?% zÇ8 ^/?g^5q])\0#F=ֲ2 U|vے lFq:Қ 2X#8FtaBM4KF$-X"ay,M'aG =PQq99 nQsb"[8iG!2hh*'R4CxiAXRry 8g4Gwg sBQ'Ttҗ4`K q8 XBf0 .Uj%^TNV;T aOh1LO grqGBorZJ'fHPQ . 1aB} We''|KbM+-~lU$6=n3sX֫T=Giw(yPuV"g6 :SP(ތpnp8gDਹzO Ex7DO;ܖLÎ+/9-=;Cq5z#dK omZmc&G=c*~KO4Ɖ7oV 9_;esڻvչG}1"E"B4#-I-!$K nF`  dd  A#o{ΩS_k?k&ĆvkHUwgo1~cR+S@㠌"+K BeT,i,k*TDk0=Y&ڲ38~u`%lCǰ/Շ\JCm" @K8kUL8Wq q߳ p=_(et-p~J!u&ؚLK4{FI< 5C`]+8aG2_5idR3R3v]jYD(i}jUkoMYbzCm:Kn]Z,DwC?r:u, 4&b]&0 O/kWw|S4.4XJ3 !%!ЅH+ćw(bH`{C0Ng:ߩ)$FcN ~ڠT!$ X0cxZeFժϓ*4e2f 8A Y֕$+Be$s2D&r9"TI<몗w~{\{Hb#]aN{'g 2^ψdVL1봎cݶ̴WXTD*BTɴ&Լ"yZ٪Xf|V-*f^su%_pL)3L#C6w6B;/,kE`KmIj5 VϬ݌kYޚN"8 ǴδSSU֘iwݎZ ܼk?-{0^ fuYԚ&/ZcV X#LE=tᰧ utb7(@~DɍaHyf3g3}g͒2> P.լӜ %'utߡ+2fV2 CƺT_lj\21 yHŊĂw:g N+0Uz?ǜN'.?~`:[mDO:\J i0Nփ #/)ng_ vw;|gyx>i3'B9I'n}`MWh9ö&]<.1 Z-}k)0t^X#p8BQp4 J6qNCEYu%BwcU2uh͛ٶ49b€ ;\$e}3")[,#:e9ׄ"R]ƴ T5RUv߳;tӸ>FP]y!p3)R{bZUK2)<:rI)[5+oMc]JLk{=]s0h IDATPqctkTuw?*yӚZHY]%+VY(5qJn1КCAu*Gy~mRXi3Ԩ .Ls}mQk!.03[?o~v3sn馛n馛ncX)E|TiRXz (V)uLٮ" ;A 30u*Th9"]'2lPsFeBj:Θ9<|Em3]',M Rj fИ@+XxXl#t'J7_ES$wk#3K~ewy[2NM| #r@C_x_yd\^V aDk SȮ?`0`nI'O`kHK:l+[w<<< \.m?Rw\uiW^ܙ^̼&",躁ZEUos|ǧ'_|W r;>ӕR2X64d}y]Y)51=-M!Hr@w?p8|o'`2Y:.Y(:o\AXӌsG LBx3iAZ#f:h!R6FUwae^^9ð`xy>+')/ q55TJ)VvPZB0m^e&trRbH8m=8,RլTXexnUQa͕l{Kpdl|Sj6jʔRXOCyҒyݱߟ=KzF̬GT&J+uagYٌT]\Ԋ)l,D0R) EaK*HblU@THJR^YZjZPk}[+lA-MYAnYeHӪyu:aeG4#83j^p0y)w gy1 p|y8s&Onu*5_Oob,NǠR[!r3G>27ǿڿoǎn馛n馛9_w!HJ+@3.ԅ*EnV8"9mlS 1C+nt@?RJc2)J)ѬZ!8#+r& GYߪW{Jy2-ES؆}$ee^ajmxXXzyb)pSXó;t.e)iT{0]#X^ "5Xl7D7GN׌ZkuKbFS= b2MV5 Bn9r92paLnB^L-+.u<{GaM`I+|eYfjV#M{pWtxHieJ#R=^gR,H3J2 &iN#{>l{G&6},gbr e|uHkv[BN+(l7} i(5ju]iz.k!BmBNՄqݎzs_x?&3]k)UStӇ/ϟ{wGsf#!xʸ?~eHv 0 Pb{_cx}c]E(5RZI bc  ]脷Wj+PZDQu3Lm7k8wԺ MVRagir͠uC:UVrmo˕p*St;gF*%,j 4'm4ƿeRe^'D22^4 Z0Ab{5Vpt6Um#V5)ﹿgάBcttβ|r>rypPȴ49R˙ae!XN;5J)ӌrYvȼ)E1b{4OJ[U*N+uwι馛n馛n韺c]H >:*JtJmF XC6~]b] [,9#u B %#FL >}+5b+ Fg"z0wxzyи.Wu"t0"PiJDa3i5:S|E]FY=i{O-y+2vZ3MXgs]GM'n 5R7xRIfa&mh.!8X.;Ѫ@p*18w۪ir5K;`8_PM|fY/^^躀RE#9G0-W&h)uQNe$BL SaY/4rVOx'Ցx!ƆmOO,a`-+9k]RlfɅnǻӑ,k*g5P]o'RpwwXo~x|y kO312Wֵ0;Hy:.XV孆"XXj~{B<>q# &XRdž P;\3ԈCZGY'66e䜷ߝXN)ӟ&a` !`#tGnged_`6x2[Dkj.x>Dg>=}ϴ^0BAjˆrmoƉ/=Mmj- Gv@=.$vq< q"R-烥IE{ՔfX;j=vsʴ'8L&e3_Q^Zc[^/z̸Zˑ&XƛQ+R[I_93].RW41xW# )y|yP0z'"ZH l BJf"!K! j6R>nI7MO̒Yk֧,@#ic8c TY(id[ΪIӊ$qt<%Tt"$S6^`c`mR*mY ZuF%=eg 1j1"8Ƶpyzywnn馛n馛O4??la$?tf㠬ԪW;B$|e,~HS1%<J #˕ogԢW4(+Uu` !Dax̘gr˸+8Y6Hq;pS驥Q*D7=Ł*;-+sd M]ÀOˌ1 tN)S8N'(O!Ѕ. B{,f&n8B\J3vui&v'ԶҨZ7xhP0q OnJ1Tc+M Uf}$6t55Aº\(ed-X[.CGRfj]y!#{0Rv}x:J^g+w:no`g- JD&3: lAW&cfƎR/Ǘ3I6`_۽Çu^YRI2blǏg쇀5 ۄ.n5 ZZ5Gݑq#˕5ge_7n7tM7tM79ti#۳?ܳ1Oџ׊>vDaVe$<1hBe\wQg1XC*\-L 3;ϒ&u"וVm)GlcMqMh(ptˬ $j`8>pڽ㸿`;Z6l1ձ '#jC9¼^He&K"u;3!8PĒr#ƚ3(E~xQxM8pkF,%>`{\'5Gn,O63~RJg)8+X+XWwz|}$+$ZjfR.88[E,US,UY?%ʹ1M,GBX,4!YA挔Jnҙug:C[&Z!KEi:mNJY'³Qd$7쇎-]׳0/OR`΀cYעk-]q<ŸG%`-bI{b̯M& ⓧJUx^^/x9}i&|9(m5Ryzy|%qt(;K7;)wkxz&JuW5ݪ`Al&G䭮5s;rYȴ fѯ]jє(|9CdiUo]q'AZ;SoVioSkx^!0X>w=^/g"yЪ,<}v=?ׄ踌i}ŲdBJmIyc-SDV 1p6rQvY 0Bn?q}|ucP] .MF<>8ޙ(Rh7#~MoKeYC"UEhJ1Xj}mk#- #V!FYjA7Ke&iQWq?,g ^84B3tu)+i"X#YW}3sn馛n馛njշ_8;=i?GC~n|':^0= Mk!]Et-u;?J;YObreM3&g-#UfJ]Hykx[ sQL) m;4|:^Rb&Z8awo?0_/8@5_ raZ^34m o˺2wf/@њyY0Q 8ėȓj45ZnD]1U&邧!x~>Ӆǯaeq%-`UE/ng81 Îi{M2\{ `GyX/Wg#KʉBa״FJ\Vq4RҺ E0N*ze^JΐS4sD4RMwyfPk]r4=w:GeU0`卥MhgK8GacEU#"3nc):zj- cv%<%){: hp]=u]^`pya?\_w_fuM7tM7tӟ /\% ¼^w<|ryUĜ^A#Z)ʈĩ)aXu%}Iuu(5CZ?3/q{KLƐjo!g6QkNF1cD(ufM²,8v|33|Kr-G:SM:M8C"__ 8t0 GB-Tg74[HqXAþcNÎPyyԕO;32`@co#9Jv@#ElܓRBԘ13HҔQlfLփʚ!Rߌ7Z+F9o2)`-\tG׋N4s+*VfC}V,c@䈱R!<F vHk:\{фBV03M{ރ5ɢ -I3͉f#y<<~0Rf UT3a"Wkn\\[BX)mL?(ւ"_JZ,R+1F8B{.UZoi k=k~gCO XG3q":2Jf>nPVi ȕy^-aFk}{\ o.8ȥ2ZI2F\jm}de, F`2EXXDZAs"ܪ@˲s@zu%_&f,6vk4co"i¢ J&ڲP%:,jX Qe%3-1t~ IDATƗ 'x8q/|c_WofM7tM7tM FίBLw5Lɳ?qG󊱉~ͷ<>Y><ܱrTj2'BFm2j ?nv4i8`o#18ho{7#a.PZfMyc:CHКAL &'\3bda#eI̋?}3ǻ]7WB; R>I%m$ƙ~JpG#eN 5zfaS^nXוq~Emds!n8su~tuz!a48\+iZ#:~8? H|/$6ofیoM+DNVk%iwtU+ %`V_4)933] ۜSFYo 0AMyV)䚰ށy&zx {|i∱Zu>BpJ;R-diH3`8OJpq`)м`uթ隖Pz0FT+)#ݰg^ϝ\grRꌴg{0AH XL#~<}u]PYv4,bPu*ݠϓ3SzR`r&%ZJr*5WJ[m!dZXg*X3t{NϼNԲbZ ~8l)3QKӿ-S-8hb)cw{Jy`Ѵ=4| wQ!)j zjo[pا$H7zҢRLI+鼶shӕ60NMj;ZʄֲV-j5CK|pϦ2CZ~-R#j:߻VmVnjqr+X94jL/yk4g\k}jeR3CԤښ}M'Z-Z9<}eL\o??yYn馛n馛4roxcC1.|sJw\>=;XP[V\DPg.xVn&B- YW&H+t%gDM"E֥saֵ'Ԩ,15F@l&ӷ8%|0keJ67,2OeN1D@`єhйo'5Ӻva35VJ^BVE1~i« ڙ3@[0セө5:zyO;}/Y*v3NFESabjG5rUVXho_|s]wu]wu_F UK^c+ yr%D8uҠ#h4RYUxaH{wX6{G=D)rae$<x3ld9NrX s"ՕiXTbtԪ:m3ᰶ`z#=v)4Ig=a<=u5yY*ߦKwi 0Y+}[Z.7֫;Rt(J,K׿5t/oŇv, 68Fy>}$!2D~?8qgw3kJ75\wZgmEo"xc֓)8rFCt}zưC-{#5ŻHo0ӤZ5ex Wj朋.gGԺb|TiԅŰRc{8YLgZOUW3慒{G߿u]wu]wJjտ/3??a^~W9I.[h`Pk0{ qtȥ#49< tqe3q즉nfNəR3(3>zr!8.cq.̻h*#-&03XִDXUx0` 1, maI'*p4)d. YAj*WTjKtkڂ9ޕEg#HOd tCIu~r]x<<端^WܖgFf.yMڐk]4\W萶\tvLk2Lp UƳ?ׂNZt8%RZα푦5s:1rԆqfj6Ъaf6}t|:#,i:`LJXqZ]CU`wxy%(M'9#M;>'D a锖!b&x09rN0sKWRJHY2o.,08vt.k'&AhɘmWc\[󑔄ےvڊg;!rP Gs~V Tex?0O{k]*ʥ&}0(9Ji? =[}ߛw>Duˍq**0;xVS,kV5Z4Seźu;`xkkʍ̻xǁ&$&#AV,iMjȋGn ߾c?`l`dJihѪ{!=KȥRj.T9c!R[ CZMo ӄ֍.m\HE hXSFb + 5e>B q 4 C%pJC@&J[YVD@N.3Uj0enepa66b"1Zs 3ZG5C+oRrRwm8*!FbLӎ25)שuȭ=yZ3,,|}ꮻ뮻G^_Q{gGL8/<_?rYh-Q$.Rp9 i &ic m[CÀ3 &4( D.+n"Zi7n̊PD}2CXB:tG. V,s&B8L =#J'i]gjSKk:"1NvC7Ӿ*{m Sk,kx LxW<) qVu$\?x5aʟty~Ps':ұ}e{Ϙ=׼20[bY CYwa$F8aȲd0]i+/zR28tbi5Z3tcFs,xa zO7DxӰf5*2JCdk.y1b~b]*1\&1R&Mu4.Vi9NO3Y {H-sI 8pF",ZNe-H߆RލGöN1Zk RK-&H%o{՗ca Eܲr!ltmKjKbta[KAD$o=; S<@1k~|b7ú\ߙGM"慾W/O a'z/EG[0E~޹@HׂQ 5+Qo&eipMCU#3ж.|ź#p9YބȎLEB.WjX۶heI䖕ap]tƇkut.xӕAs,H7ZO8q^1D^z͏~/|\oOܖV.3O2!nk}A,1Hw4Qpk =!<3Z+xh}³93 ZZG0uw3Svώ;M#ʲr:Hp3~xAz}Fc`v\K#Lȯ~Knx3=anwxؽBzf#VCH5a2:J[@Im!$IH]qF+Jq:!-IYZAPs kڬ'qQ U4%cG7:?&;쏯yٗƴx)Yח!؎]2ۢadrM *׻oG3z\䢷owL#{EPpBKN-? ~vN Oo/ fb n>fRCJ+zЃ84G²>o;R>Qd!_@ѿ@SrHESL4nRķ#uҔ_YO~޿u]wu]w@}(4VNeź5c:߷˰Pڱ&b̀7#L`H#Xg4ՂJP*=o!&`~7-޶j&3-9pޑNҿ~%/gx \E;vڮ梷X) <80Yy0հX 夫I6@7v0༦,4 cYo gp2 {ijXoAndC$%፾~lkzY9XӅ?ov540h8_a~L+mFl a0{B qLJ7|c7EXCM~Vkj<<<;'0NX|2[o%WN|Z*MkP`9nU|qA 9,|Jζ2e4ZoX dѓźkJ>ӱ? urp|]yŕDkMFT Z<-fXlm]VHݖgfZM}|rg?R&ʍQ# %( \kܡwu]wu]w?/"+tIDgEXH[^5k[}i<ðg~/_n=4ǹNpuyBzYvhxQaE (sy-iUw8_er!pkT3IVXo(RXSBP `?c@@}"v@K-z@Ci^=a\b]?r+ [p3_iC% "YqcS1X3խ[%Xo0΀U@.Xvv0ca`TKa|Lq"V8! v$؉ݸa?xWG,z80yk|y(&]ߙ FGF 3249.\/WJ-Vq4[|?`'ʧk_A˺a) TIa)R;v]岖6ȪsVb[/DM޽.tCJV=n$ih8ס73V`w8ϰ5cu6젩708`}xG8~UCRaGDsIg3):01i!)k(ͨpk֊~{LH@Kq<Ǝ 7IԞhҵ9]iFjHmAP&uE3X/+8_Sڊj# =4aDx|xdR ѫA^K#4NxX+dBTW)"!1XXMXbf4Zӡ)c 8F1V  wTG !&_'/Ww_y>z=օAfptiW[Dp&U  ޫy%j5hwC om5*Z k;IKBM^Z鲚 i*s #)'jQ_O?Y9wu]wu]w7r>e;Hn-LJ IDAT3t:;$j\G$z3&0n'}Uqm  8z3h0r&+1:V(oAwݏ_U'KnDɺx2OtVV|8#8 HSB Y.OrÄF%k&S;6GL8&n|dqvϲjF N841xN"+"7818z-Y˅WJ"] n<`G=#LJWwEW:Ǧ#Ҫx7aLOΕ*vZKTK#H~9;J]9g'uV<=g8@on CA‹G~WJJb j "+6a-k6ioƈNiߒ%34J+ϗgUA8&j qZ\*12#"in74aFIN58ֻts#Cuq ۈ'ƍΤ|Sj )yak,˙r&Z֩^Y[Ȼ@mWִp>ەJig|hZX+^z_}0L{aZyw!zlXuK6.pMYF~Vm0BkzvV`V ao-c]g5zB.EZDQȼa>݁| z_qjY]OSvPPٮy4kBmk򡶔-*d.j=C918FiGURo]4X7ӿ7˻s]wu]wuEPժߥѧO_fBWJ0 Yp؆$JZK5IO蒎Ul-8PNs[ӱrǑ&7e,˷߾8rk1Tzr;#&Fs4ƽ%im+/¯ c-) 013dh:@ )%HӕbVj"7Q#cz#RYN' #DFa|@Z}=xj,8q7Mp}d ;\f(]#r"z/QYvszg>qKg #C3o^"F~[ZI,y!Arp|s\.Z*73&ĬT>_~go<諯y¥VCgm9g-Tub"vjڄFpd񺁲|85Ueفa[jxlQFvAh!{ަCp"kit Qg,t| ^BgLjs[-3uzB\no-Onq} 'M72#U bMǠJP;.gBw\zÄhF'ݕkUkc?Nk+x#! ZnTN($ax`e%eUÿb4HKo%'r8LX8(tF5NdCgg'F)PoU 8޼7?r{ꮻ뮻??.ň>.tւuǑYQSB\cLY9}\ ̫) $j*TXY@sTN @I:} 0a*\ϤO~nկ9W8N)b% cV68VZta_x22 ܐ^톍zXʭPG*x[q!j-YL.%ԕ,HB JVWjXq,DKW\u]+nQ!=֏X t,2ך~8#] ۅC/?vme.R<O?3_y_"8~7qIgu Zlބ%__<# e]AbPppD+kVvmhs ~䳗/ctV5 z4 CK.<.f)bȭql[rm[2B ;5'Z AUVCȫp:=z5Nn&1+e?!Xі0b jPXoXוiyO)I.tz/ M'ĭ'x0yaԶ'@FIW3" r;eD;{6uהrdR vv[x|JΚrQVA3aKvH;r-X>ҚPo[jmGpʴ嶐ZxoUȬ | Ri| c4a`>NRSbM7>~|1zj.g߭55WVkr(yGtiD\90r8gV-E&++4@kYXDYA@kA'͈s#~b`gM%§#jZMgEUҨeԂi}ja]3äIɮ-3l7H[װq0^}w픜Y׎o!AVJwѪ)9gLGR뮻뮻vE0w)DJ#5 %>mENB589#ޮvGr~d-'&wAvw5[х~Ù'rb{'?1 ?P L# [7fcgl W'o - |kf ]:Ч18|5횰hŀܙ@k43nN!Ό@M߃[0,ǁi^\nWʛWo~@,rNx+D?`S$=V^|-{.˅];4m>ҕ݋G뙼>dJi kNWrrUەW pyG`"5;ޝޱ E]*:|"0s] E<cw8U}D k3iPBkϼ 5D\֪H,؆qqagVXʺܨ-kxՍyO kZxs|wʼ1 88Hy1'R^iFuҥ~?KSq$䶒kׄTuɈ!-!LlfZ8qQ\Or%*hBnFkweN'0:J*ufywV3R7dY|֕|EϺ(5zcMW]3R*vMY!28_O,k4u۠wJʛVF:2kTVrIn6r5qؑRх?yOg|xx'[jÄ@5CtŎWea&b[|'R+cd_e3]l8v!Wؿ`T5el8&p\M:H3߿u]wu]w9/'+ ;tS#z^VaVD)*FkFTu7HJ^v㎗-ov;cB'"QZc&l@,Ev;p`G@̺^|?p| ' cdw^h16z`58Z8gY|9XӅ< #;J b# K.H1m:htr' ME^>0̚p1tސUtjǭfM!H)<x/K뮻뮻7rs[icfEE6S18/1t}r[[%ai&у_t%_0:)]dO'YFGӫB-}gEreTYJ+ZzMVM8e(a~ƙxz0xRYWr^>gaΠl#8zsC+[Kh-U9; ]8w3FTV>|: <<|ŷKSOzsde^h"z(I #\ޚZ7Ii҄yLÁq|~%NME[ײ%1m3 6ް\d)HK@S齫kĨCD{%4M/{s\.'KT#mz8xmk:JUumuV#ueK _k+@ G$k#dZH ÄujnXmmz1h,/g^'<0 vp|ضzDR ]gZI8FO'ʇ?p~Ll`=XDѤ-Ԗ(uIA:bZ4e[5UW5S[L`Un0m_a14b ul`kIi'!Dt 0cNpq=:ZsGq>uwM΁4F.˙mIXcBth 66E*GXx2#6+޿gkfY[9}!. c;٭3]'F;Rq:.PR5g+u~9 ] zޡhbCPHۅWzSHZ]},%_,P`se2FiZSYU; ނBo0ʦ)s8H .R9Qg]݃ƀQ_<*mx;#){yJ@яtِVQkc >8|ܻ{*¶ftK+*:ZȶX^_NO -Jb$SkSX^;a80O 5wX1?+b]ȹ9mUQT,м1 IDATKn /zfulw͉|g/~OuyKBxdY*R &Q[vLP`>xa =jƊA[ZKuo2XCz8H)3X *N WkO&6mns&xE>X7-K#;3`: q=.Bܟ78qZZKE̟S; F+sMOׅOg~AdG. WlmH/-al D]KpKR`bDL9oAk'u & -)z靻ǁSItxOA!Wu=Tm;Ī[t4Kc4cqQ3f Nq8 @)ա|LLjƕ|$5Y>6 QVR]q"L \ >%ӑ:ֲWIԖ`h; FA}I#;%Ֆb7ly*5o#V} b!)q鵒SjW2%/8Z\j/4t[UO@nGÖ;iYc4RJni;]1 Ҥ1e4hoU<\Ϙ3JpLڌwoKbmF&ec+r%WNRHB.V]ChtbwF^p*}o4uPYawlD.ú] וގkjk^+o5;Xƿ9 d{CWWGiqGJSRB4)wZ֩R GǚH ZT*׌G$S:c͠.F۸T25r^k#\V,jGP{fǠ-6tYם5+8s zzkۦeY#r F O3z~yضفAH),xy~x`>,"!:RZX?,xGvx`UeRkpƷnaD3P"=`M@2GB0#`d.GH q"ekl6aq@;ҳB| x#`:^,dCYuřHҍcqOX pcM!N0Z^/(Lnɔ 7\)d/]V9q!r8e+k'8G7.ɻ 9gm:<+>pmHk&$M3bE7-5E%#c[WЙ&^ Bpֱ5#Ֆ} H[g] wO_i#88RrSΆA݅O=qhj(Y~o!XbYw囟~˯7L~{. >-X#aMم etw]jMAlNn+\6@n˾ Ztjy[Agy+}fiR{j@eewMp.]*[I,V9^* U߱97rknw -7dq˺[6I(^.m X LtwO9Z j@v5^zqb7|zN۪?1Mc],af*úZ 78a|i߰o6|UjRW4bi9m a⪊F0%K6O #s!;3 1*zXB5f0MX !9a^*/WGi]he!ȮeYiMa~\.HW>0Q~5PٶD-i1D(ߦ #VnM0&Tk 7hcV-aYJnw+mns6 9ۿo"zZnK" 5ހ]M" UiUkUcH'zZO>Rӧww>~8q\8?rq2FXgr;i]v(Ku!bRIq$J1x;Ы8D\)jV?qtwyq^p$tU1!Fߋ8\ӕ @^R#GňFif!2W-!ٽ L!! ZIn'j?S$azgI uY0n~c!զT\ۚ6yUZUd>tyC$л#o04곮8VyyKOewS 6ffkksAEb#ܡieq~zR(_)FB77,b\{>}u5әR [Q@,nAIymzuhD FƔJÑ =?|~4;(DkWZ"}Ú]( ;bT3dݮ$<LFQqԮpaR k5BYxA[ƀB %CH8J)vyxw e]1[!W7^@V 52UyQ": ˅o~b]5:1lgZJXox0]8#>&ɬ[a9ac 4fVka C^:[D%]@#bcՍf9|J9CIi@JGbjڸX+?i6DJ]]wz3ԚY?D/`U92M6 ZWWJkBɍT:#`m 6}-m8W&4j]~?ぞ!Fa{5QNv^rѺ:˂T Rq~ݻ'Jl[8#D;9RJa):yjСlg 554!bp&sPiȤ b hs5e#z?g?ūns6mn 9M:.<qUok!چXm|;;CYuc *pDv<6F֭ryq_fR?Fkcąxa7岀 ž-z2#i)s` FC<ܽrDDm'jq?j:9;~˥V:~  &^qqV8/xg6e``M# ;GKڣ%D4Cx-UZQYR/#Otlx,@"eaC-q]^-++No q1Hg3Z#6^c3 XJZm!$S~kU`8B.sFX#- H#+8x<)9RZ} > ^ÏLi% *^|vhO$JmWpY6:/Ԫ-wiF#Tٛԉc_@pLioki4ɸ&O yK-s #;XpxZ68k N9YeF7{ݫս;D V,PW]]g].aJY]g{l|hc,)UZ7|8m˺qV_!n%Ѧn¾ jt˙u;4H . ~^p[]05tƚ㘦7!t||q!Èu`-jWM*ڨ+eK<[(=Ӫ>6hsK5iio^ޢ1`1g>J.;Kco 1D,\^8knOebWبX04MZ0Ng*YѶ*+q֐c|ֲӠ^:D甽c0Vc]23m7{<9Ǜs :*Yo`03bH.W^_>IK9XiY( Z+.i i%Z31Fq Jn](`(7Ñ$!jPN1,8cqb.Zbϟ6bmԍ-%Q?K.+H)hNZ.ۅ>zh0!e#8v2bCB/ ZJ-Xs8:,|w%ʖ1:ƲFR %|Iw31XQV?>n%C,Զθ?5L_R+&d{fX/, %C #&z81-%ZO \mB΅y=?GQu ;+^_VzԨ~O9_xQ1R۩S[V\Dݱ6mnsr^N8_N`Ñ,Z0F012QY9tUB=bK|&i(S c‏Z:BZ7J0 *0qShڠZg>16*PJ{sX1f1x55 StWci$=DJ gm nUZډQBp4t? }jxYDi=5߿# |&o%0 L㐮Q k)u_Wwb XV*?LA5Ѥ`QGkj#Z,c )g]5Fw,vnn4HG:y|w4DF|8`0ī(C^G,20NgN9҉R ׵zZ@HI>:ib&%Wr6#FJj0<ø\g$h g:7(dSJPyHU&zͤ:JD0buiT >|OIto#t1~͇:+X0Fl1'ݨ,뒱D GMڐ%tVk%@k S =vT1`plTCsVwٛ:8.dw#X+`;>6RYY˫/.Nnȩ(DEoԎu9`{e>7af-X`1noHNx »G~\.'n)"4n7xläMd#5m x7pw ēwZJy<2K|^T$nNQ[U97?9r:>=S\jYT,ωzi{dҝ{(:U0hԯhn}SF9 jr{וLmq/ _Ҷ@{:CpC+ Cos6mnsr5?~_(L{V*ZO #  rz]5d c ;} /g;&z'KQiRtٽ%3OZW$xs<-+X3K S#Y]FcO뉗%rl&9hS-]:Bx"F8M mٻ&1AhՐ8;h;FDYc.tXFbM+ ÈZ ,)ejg<>}o{OgܹЭvAp8J,ލ֚`kn reMeM0Л#LkaPVLD'DlR;{޿G .tVz"fFEg1ҔtqN&U;84e*O8'&lyC @ D:xsk?n D] ݝ0 8?sm{=D_HF1 5;y'+LJRR`7%NԽ u㺮 È,zY%2> 8=r+FBp, wazݝW١!o@8gHk|Ǚp:Ycz<j|~kZ,k ny|;CJ4ucD"a,x/Ԧ0|`xZ1qnԖ Nc>xmH8 n֍q8yyɬ-1#JɟemՒ ,'ԶBIu=џ9Llk Qg_<.xƃsJkFkŐsf&==MV_g>DW7FqZzo:U§Oݱ6mnsr7NO?r|xx8/ n2\ G|%qi-+a%E[=D0VWkTZhЛUC 6\@Vfz)#8z`6kPr@mx]f3RňƑk:ǚV<>FȺ^(Ef"DL l$ @khRлgb i|$`ɩEcf<<>1G.PS}Ă#$aHo)_^^b1F#$RM$!ZrkH[=ΫX)"G %w6NmB/*r8Á|u;s]/lBi-\p!;GoStqXV[ " 9cqYkj ChQUyFֆw88!EUZw{/ p~693ོ; e .j02GycK+֩CAb E-ty޶q,: O'v϶fBP1NŮzR֗<01u+F[JӔb4V!4uX'8isb+u $a為*C;gx~.ȹR{%Htte(^Eg`MfzF´%NUҙmr<<24, XaK VkL~$Y/}. k+ҚYQ#\`~R a)k )]4 qW@ ଥ%g-\ w޽uUn} IDATa~_%ХZƬXx ;6mnsyBp0S%pIϼ;Na\eY\.&Q+]xAm}3\XCGm8lN 9ީ%87|V)yÑ8Ξq'l׍V Mq@ F҅nk)bmOl%[vtVRXu 6P/mylcv p`1ƐK[GO.L_<m#m@X1ӬQI3~_&h뜲<:na<=~zeI Re$D0xϺ- 1;ker`8j):8΄mpq&2}70q^wF׮]=% \)YV=CU?] 8|Ouچc}G6e4{5,Bւwqȗ_VjmZ T.iyt{ e9eم(%C[p7mFigeoV!HM03J|w&Ʋ(w_sRFoY#4낾AetI8{o+ٺni=_?猈e>{\ParpF!ac Q$<@*U qgg\MD1ޙy(_Ime͚+1xFU9johh?|g-CLv ffaFajEЃ$F<Omqe WKGy,3J)ƑDTLax'kCM6ZXAZ5h㝧wG#d 曁89- XaqNc<ܽwma tÑ/߳ʤ.U-(ˢOokIHPYaw~Hr9CfdHiݠZތ2nvn6.ԭm=Gw;ym (:F[; hw]_q3 b];9ܦQʢrb\-AVwỎzgggqOI^1V[編LT:I2,PimpTԍRw Zh%R" kzfĘy{xǏ?zk=`}=L'ܫ&fG{MuvXgSGZkpG;RL둒hUkrK"/eKCJ0T7(w1zى5{^o;<{9 |#-Wzu5Ɂ^+KW \kJwQ^TZŗ+ҺP[d)LsȪw Ώ=:,tΠ,P ,WӌQLVHM 五_$v.hSܘu&N{rRq&0%筵A8u03ZkNǁ-+˲ФGꞞt^Xׅi9==Ƽ9F@ 1#uZˬ+>#3zkhm 5RBr?M7tM7tGxey~荮wR][JT+Պ\V 1FJXk1{eI=˖^ۺ0MGuh ;Oa/J޹nÁ-?48N=_(㌶!PhΣU4dU)I]ΒP 'h<_@ʍ2rVV|:LyxO#C8v&=qx.5c˿ ~ߐBU3hj䀒jMS6y]xv=b\(ejl)38&hJ*>:`ӞP0Wn=IՎ5CDqU}yj,̖e;ϾHUYR ;8B c?O8މ~#<<<~ &3>8Gfz2CXU%&yA+GlgJi4kĠ M5͎ 'Ge:q+8viGO0Bׅ8CemP+)Qb"p@Icvrao?qxx|Ggɒ*pd\ :&1j(֒jָxtpZYEfK@K5)e915VPm^u(11RrFxzGNAרX%`^ v4N!`*kY 5ѓlEPj;ENV7r^y}B+Z0zڕ#|`لj%B-B)QՐҔ}M]wz뤺0/M`}3Fw0緟hm,)hb5oW~t<+cB*¯Ґ[jO=M(R\$7ΕT JW/ܝ< /~JeNǁkZgb;o_[*禛n馛nfe{G9gIɤѮɇm gr_5YA˔X9Y3p"X6X]Ve 8-O_Z֒(-c5J)qFpu{%ׄ I !3g#y9+ \.+0rYXʼQzDR!ƈ6~S;q:[7i$825xOk-Uo16݄ 1o G6z#=τAc0'-SZV(、h5׹Pk14jhVJd9jpw7`Up72x?m rfKVPP[_3X7 ]W欕*np(*)-|'Oq?H۷R3k^eVTYKK5NjXS" JJZ1N'12sZ8&{HWfP@s䒬7F1R[!g+[$#fe@,VRMR!+nN+ D_H)Ƌӻۅ̷ikʓ_锺&O]Ve*:ԱlH|c1\&v8׉9Ledj=3W ȥ`ٓZy[IyˆaB 6tM7tM7݌oQ{;1NQ[jC JL!;T4} .eIߓk¸#Nj8=q<IQ9yqZ+2j1MnX $+rll(.McUDU@500i 0VQF8rwwf6e-+1K4XHKE5GR(D֌h:o;9o`:?t7.o+50{-^VQ@F)hɥ`8NQ0;dJU Ao0j"hK?8|#&,_/o4MhQ6sݮ,34^d8m#?xXޖg:e9 %OX.3)8_ø'Tq\W"t)hŖWLh娭AiYTQjQ8e";wբdz5ue9g*6?GRЭ㽃!tX+A{Nd p 5ʶEZ-س hk b $ƢR!W4Vx|ĺ-^֐;p γ*JT8:@GZ JjbL޷m[)L9 $mMfh]CS  h(R8ɩcՈj|Fh5QjBobiIaK(sb8YNlsߤGKFj~tT{ϯ;Won馛n馛ocE>\;-R1U#(62+Vke#c8p<ޱ#=kqYOϝ)$f:V Z5ҫL{KY5g@v%&i|@ofTaBIytt7Pl˙W9|,Յa"QGRsBʍVF ,ia[P;耷^`Za]5Lǀґu=cЩ-(˙-Q;++{㫁UkukDѪBw֍\*9&:$N s8z2iLhY&3w?'~<75ŤGa8t"VVoR$8d9q3lMx#55f`^]4TT-UVrΔ(ek>Q/|en,Rx:(sfTPtİa=YLZkb.f4pVVv88O{ 4[$C,FW#C@&,@k96!Jn9g(%3 /_\Π`Yz&nލ4He<;gm.IJmu2Mbz(Ei]jdGY>kYx@5 Jg:EswV>vò»Ot]wxdRz^Űl2 VZ *i,id%7R,MJސպaOrt I)Yw\V[0x')wΘUM7 1UjVi+Y-z&+`hcń2ܲ<ǽ"ϳRhJ"j(õ5txp|~B°vXkr'vbzWI ^asR0QBosA`EVdQS+ wȹ`w#Z)qcuK-R)JXj8GQm κoז{G*:X3%w5cT_GQA 7̹馛n馛n9saYOy v$u#*I#魠TЪӫzyہO:0øOK{J`'R{(Jh#(4o2o.jLWUV2]n:kG]c^ O" ]g`@YC땘Fɬg6^<3yzH-Wh5.@=(mphw#20/gՄP1`+Z轰^)uŚLZuЭx&}gM[zh V;ez=s=oms㌬6 6>}Z6OIuNb- /Ο t7IW?NaK3BZ%1Ve3)%0k WJ+hrbK2sc0Σ, Jѕ%W8%_1 +Lߏ\+_p;Y#`ޠU&7Ų-+5X^Q{p\6#NwmwǑ2FK2L|yR-w"XU~6na`qp>0p w8븬gX|H`E1x?ah^0J.0Zn&c 5ą0d*hXיOteЍ{OJ%vPw($B^tr`oݱ, 0q>һbr9s}qn[(-\3aW5{h$ZRBJua)-ŧBuyަk^_[*禛n馛nf䈶o)jB L9/ҵ)#acmq])HG~m=pc 뼰m9lIg;TUjEॹB/ cG. [ ʏ(:+̔QS؂v2dYDQw ijB1_7t;V1hyrtzEYcx,H7Zf9W0]FuC-k w'Gl@59!Q) 0NEq\9_#9Nljqw-Iy)yymly-_Ќbաi1g UmCbmJ+ɬBl a.l!>hJNPZ]#gCEצriڢTeSzC. mvu_iuI{4(]3_f^_h*^ DT-tLS(&+q2~#ۏ'OX/yA_ƺv %2JYGar価SdYfrY҅-1Ǒ-%ZHlL Pxc!jX"tOdѪ.RqӒ-uwiMn:H ĸMꕽLIVAmEY˲וSTԦi]*]6I؆bDˡUkS:~iCWv9CTz3@Z3_Q{1RAkF.&ٓ0{sR5yY kyr4v)zTn0xIY\Fâz@% U|4pְp`|ZLʝ t:{بE#G+-U7D*uK˶ gHO+5wR̬k) )5{%,s %EYt:ʒ7Z!tV@kKiCmP:0M<܄af罪։Lo=JkRV',֍䔾fM7tM7tJVsa))AYMGͥ7A٫K1F蚜+ލXm{G^IUQbn:dZ4RA]QrBk`RmI{*#Z-1&6?rMGb.(q]jhdFK Ҩj"ؑ%j\Ɓnh$Z[׊5.LÄqk-g?_WBmo ]wRb !Z/(z3WO'<#-GN(p۷y=yxxthv!mߖmV4-%Gx|ķ K\!>pGGÑUqvao,+Jw$qQ+YוZ:Mz:]",e#8ھ{1̤;oh1y/фQ-&zQ+#I9y0a_7T]*N|];zx h#WM,#d-fJdRb;9%p=(e)'aa|44M(=<<><̞LzVq5GLWH:E3mi=VYT Fuq|`KVpZ*0hLk-' (mR"k0w88)|Lw_(y_wH4ZX齳m|q+R($Tz??4Mpo,'Z^C)Ĺ`JQj?}iXi5Sa0P SC9p ݑCD`[yW) Zx8u8۩(m1VQE_[*禛n馛nfO 5ET6j(9%e \hEiaH1cYڑ;Ue*Jnpٶc;G+ò佂I+#SM 4$B8;`5Jk;Gb\y{?Xa_fd*-o+ig[;MAw˶&;RJQ8xw]/,B|x:aTϿd( *a8*Kmi1w\/i't1%ł7Z4a h'sOhB3J]8xZKۗeAVFEZ[$R$Bݍ²ʂWٴ =]QB7B͉-Pt$eD1Rap̄kYIIq7z@ɊEp< M%.XI,xAI'G)IPAi,(Fwm(aw .՝+Ly&;>xF?U7.>v3Jf03§9ooon71$4 '$l*je-W7)Rozo&h8NLXu1zYA;D\C:karXs\gu><ợPwN#RW;ZJƺr>}s>w!RvrEJ! bv0Pw8cMO 5x`zoٶ+˾Rd//o滯흟8 q , rDIx>\o\}ˍ8:' :i~٫S0ЭJΝg|4sb֌EbIPr\՚qJo4mX6–p>N]-^|`pha!NUvJrL x5\Ԝ*칒7poCqLRyN4Ж SUlMP@oJ+UgDžmQTq}>k|1q(1<䣊JЛHhmۘ[^ٖL#+Tzi톗 ȭz&4ɭZ:/o_Ҵ!9~=5m)vŻbǶ<>%DO$?|;^?PsWaHʤ+KY5tPSr](5 &8-4w̿^~iHJL"G8Z+|TX8QD!hVn'ȶ7R:6b+mSg̦:\.~{#` [\h&R<#T=)eI*ЫEj+HGwj4=VWwChԶ3S=+=2m7&N #zn+ԾR{&&%l7Z/8<)i89Ngq8u]Ii@}QHsz}7 uHW#DV7ЂՎD|u0x3>@kfW.uqvjގqZkj1+,E޷Ů@;(=W̲xȗ_ZA4cb&RO>Q(303'vT Vy5ּ+yquݹ_L0g9h"nO2}s>w!?G ARq4ۉuQbԟ[Nf&[=]"=!Ymt |PZl¾</i|E&n{xk {zFoJ{UU o_@<ޟH!Y5uzaXN!.b9qg #)N,Τt3JV\wT+H'Tu>tJZP[u MfkalF`DΣNhEiq:}3F\?q92 3xfSZFwt 9!@ss8xh P\bNyi毗~#̧vD[" sf'0\ :UOoJGwX 8phiuנB kݮĄǁTJ7J-65 a-D'Ѝ:M-oζm2Gq C:FΙuc> @O(LOb6^?#%/ܖL3XStjO=φvPZKpS˗іi}GQ&RMT8bV!y.`R0\.}qJwԣ*xJfv0O g=gD LJ딺3 4g *PV[̉ݪxrm{dt沾""<7_~jQ=!\o?>}s܅x|1A ZqΣr dxT")1&֭{6\6z/ѺJVٷ- 0fAm81qLxo@S[8X!ol덭llI }_y:Q =p,> jN!GW*JA|nԶ 8)>EV :H6cwgj U]9=ǘ&lQCMd2N#.t| ;+˞ ɛmBbY"8Q9Bԃ5y:ohCqG-ǻH &|֔[$k.:Fk1G^B U(5ӏobcO|#ooN/ M JՌVs8O?p[WB{u?g&]tZԆwi<Ęp^X ]o4FҠ6jŚn^*~l ivZYyp~d7To"a(1 2'4Ӛ |&SR ^=vԄ^{R"h5ѹAN8L0F>|իQw.64`gRQ+LJ亳XOVAoP3b$Ɖf )5(ZQBtH3aQ2jH.jCitu\NVwwBm^wfsԍ*i(eբM]:A8 =dc^ 8ND)v)+BgeY%s~>!1naR#rוmpݺɛg!Z fV1<Pr(N#Ȕ[1f a]I!7D.ȽTT"C1 Bqߵ!ZU{{-bB5|H  ܮ\^ i8GGSR8//G?srs>}nj/  V<,20aC~GѺF12jly5DP4XԪCƺL.7ZG}g/ Jg=E$1G'Қ^6DQ?{ ޑlKwZwZ XC{ID\2׎:\8FJ녪+wz[)%r:?9Phѻq6-Xz΍G]Ъ #RLDı.*}5/v{Kc5/n44qGVyJ\?Sn+CNG6.&ڠ[ܨwE8oܕ `jBZًR*Z-e$jvyzs{4ቮWgc T:)4SZ痿bD IDAT5,xā;+EZo+SQe/<1FD"|XFX²X(r(aiTd1'ibׂoO}⶯u QNq!u2<&|x`.\;Qz$Eغn?{ +={P;~>1`u︎@'$:\jw"4{!zg7c"pej{ H |0H9j1&w0Lo4qD!: V iʺJpDVSsWEj 4UBpXICg+ASk~̶WYY4&Y<0DEt5־p}s 9Wmcx pJCt-Q#db-3V 1pb׷GbA:maFȲD0 RBmV ;"+1*J;v݀9G{vPK)Ga&Rv #idXD; qx^XW ьH{N;Џ8Ԣ5BGnPg\` ARXuap0@W'aY4ØNrH*ﺑsP璣oR-V5XKV_ډa>b'<)ى-z7Gl&ľw BQ*7h\b)B"T[DN~|/Ŝʾr4PJzspaּz0M w~rY ՜"XrJHJzbvxz=|ZT-TE]#>+SI>p]7nF^/+(GO[ *.(Z΢5G~&bԺ#$Nw/_L |zA\2:Y 3%!QʧO|~f+Wz7QY=OX[sJ mskx8`dk}b ahsIԚHgvJ4$|hƌj 81mǻ@!@??X88)TtQ?z?\|Zzx(bPxq~NN7|K9sEE^+[~aΜq1o|xH mF ej~`Y7r0|6{(5#JXEp{ɣ\=T]Pkf0^*`/t)b`}-8JR[XO;"xQr@]nӔ0ӵm?ʞolox׉^kLmg3Vٖ6rk% NtNV1; 8&ݭ?̐:ܖJ]XM$);waf/Z [-l:ҚbZX 4Dk丶:8A=^F hDM BE"g##^̀^ɪB}NwOj+$w}߹nsEժq`H~7ןXV8?0qGTm;"gğzoaJΕ|t:~{c/f3y8Sra]wZށ`m\9g]IIhӴ\ݧ1&ќHGq1њqmU )Y.Qo.U:̹;[Wg?ZvY ]WnʾY;ڶTkŻ:ۺHmb.^ǫs>}P̅jR`8J[h#V7.b(Xr1)oH5q4}Sz{C ]mab #'gs1!TЏ6mضBDibdO0oovc K9S,[ PDNpn|7⏿?vἧ"}'bppBJJBEz8s>?Ҫhڹ.+ 1{9?<H. |Sz8RRBJ)0#X 9Zm93j-FVj3qK6$i|7F#Ǟ/T$SFFi#|gwDψkt©Տ$O7LB)Fs&PbΏnL_?3G5{nqGەPj;=s`뽷(XLyUe-W! tЫ'qp!1$sZ潡'yGjQ^3nq A'ϗV attua8XU h_ 7vو}:N[,?׵fneY3{s;9Mi&D7Ęo/idA'm]yGf'ml8d`NPEP<"zTs>}, |K!s+GikXYun@C$r0$蛯e}\spRS/x1.e4|D[gJWf]*OOg<=@Pe݈)D+Jo`Tg_6ә13 ̺Pi,c~N+"a=k#H&xo⍙s:x^Y+۾ǯdL1Em;jBm+-HWn7G$݆  E[c8B'O\2'TaGqdD8O3uhR2U֋]ޓ%̾Gzz Nm])s"zķ-{yF0"M]<>PU=Z$xu]bJ;;nQJJ]4D1Jx\n g۸4?u%ŎCuu]֌6H`X4|ݧ{F|V mTuu# tNӌ0DF&n;tv}CDu)xӟqPk!z 1E9xZ/5ĭmS;!ju1tDw6]aWZŠM*Dr0ʼn] *!` kGʲ\tz"U |L0{ȹ? ׷wT!^'V͙4L'9k*\@艽,`;8eB o{b[o"adYVBR{Ny_heСt8Pe8 h&yN߲+AJwʾ3 {ٙ&'^^к"㺐sRpD@:ۺbsB;Ӊo_*nt挣s|Cg5o1q> L1$X_ 5GS2g䨑ߏ?"s㏿o?ʹ}s>xϾU*[]N#WWQK6A@[UHqBEy^ {j+)QcX[KjlKo4@#O˕]Y5#b^ }15_)9;֛ [۶"\C4B tǙ1 xF>|ϯ\.+43gb81/tO ˏax,/|~]Q,G;Z^nd"2M^ּ78<NG|@8s~|kaYYWjἝ֪Ã_v\%Z Ŕ́Ya4Sk#h.(ԶSJ7J-j&8vC2@8/&rޓ(59QXg{'D {e&>~ۺl IG/dMŞN<08s]e7+DkZ*y ߖҲ)TCtSvOhfAڔR+GbHBc[6`vmGҚ_77}s>οё.aoΎPr)֊H8<>D[9zs^\nvB:NDq^}şLNO#}$z+C[F1fK/)Մ#cXύ!W |#e]H^8=N)n8ם:] JkBL}fWE p\yxng! #䔱X<=7iq ʗ ,?К5?G8 Uzw5ZhPvKz. =f9΄BG.w[@8͸8V//3zˎ @tm]zp{Zv=|t@xaHYx!GKˍp?V{eOxy%wx]X+V] ~LħO.[DD4n$31xBR7s:;SLg|<}FZJYzh/3^Ta sz0Vն_Y׊RX!LH;BA1At>JgN6XkU8rӅ^v`| LLrzFQZ6EhBΞ3ۯw~xti*҄sƨB4n r]h;Ю8GpQm` QJ;+n|~߾Л#8'WJ]3hrGTP=wQ~T]{](FtJcۭw he.@a+Ez v[qrc>=W$퓱Zk(J |GW_r}Wq(|Mr,$v*9ڜ*^>Ctx\s%F7^Lt i7W_}ϟXqDjJ"!x>HZ<;q  \o/\/\iy?op(%fWWQ #dvR!.Z麽Fwdɨk3$Jbϭ֡ԕu7v|f 7R @lHW?4 1*[l0P17*vcHWG&\)fXb R 19$!ҌǃGs֎h`1vnnk? 4A>yT Ӛ;Tu&{5k˵Q ̺@V5u9nyz˗0FpHC;AuۑfpLwhSݲb!i6*K9 2hy\@[t:"ZضR cgq|FJJPk7ٕlMӴ]ΛsN85H !!q # LK „7(!TPUR#T'"Nf5 垠(&.K-[f^\:׹us&oCG9 Tb !03P{Z0PJQ.)p9 N`R&GhCRqް' p0/]G 4 Ym Z=W a?` k-qa]&=5.k㈡ac`-\51T7([LmCqƑs) IDAT!:#:qJBPKEJz%Jq7?tg'j+HYq*䔆T*Pc-YOlMA`Yf).I }Û+7NV=.;0tۻehMEixݻo{xCi?y:r9?Qc=H'8K;jNp힛?'T\]G7c$%m]rU^#3tAEZӉ,w<^t)X5Ҽ,Oz໿GĮcZYوuF1GR+Kf-LE!ǭp8aaؑ+Ѕy)Rk<6 wFfXhef->8ee!zaa,,Ě N\3T@Y]#[b~1w;~'3]g8t^+V>M\\'\W/pJ(`DgfkrT: }N*:m:HKC-R(њ !m!Cr&Қp9O !Du1`C&s\ęHiqUwyp>p^ZdQPs ˬ5,<%5B_~-| ?#@ejQ&"Z+o9ig LLT*MjtUjm>DwwD)E̫:Ln9 O|Q x4RQqg_?:ss\:׹u]rTּrTroCdhFhb0N'k-*tXĹ,vzq"F,-_D틲AO{w<^NnGe;4qXD7;ތ;BKƆ[PFO3fr=.^}% bQ8`lSgnM9 -kK%"L@3*3NZj~`)Gr9i>q* lc.>#P< Y4Q .4llCa$쌲 ,TGZ!Fw}DZ0w5~{>|]]+F?l7 VƁwG9#ov8L 5X-s~ûrOT ɉe>憿_;?Z.4ӥu8Y_\K0;%n0a8<(c 1 ˅yH:oCFEc 6|㶽~낲7օN qC]߱x~~69MH5cN%'Tf<ɫ;ŹF.Bi-1';bSǞ?uzCXlci9w[u<`^߽eJ,)aVCˆ%-ʚa6:c0VF;Ky<1 hs6G vFcuBj nFbgIi&DM۔9G Y&r]9^rYhRZûg=1]bc-zV]#)`Ǖ**T)naWF ޼гBt ҰNY;XaŴ5gG,,XYc:juVs;Tkr)~k2__ӏOS#8 &e5Y<'W"y%ׄqR˲*Bk4;?lրtaZ/ kjJ)cOkTEEJѸ\.3ΗWDX]9{* xzOO}U̹us\:sr'[^/#O/O!ȍVO8躎*eyt`5PhCJft8Yו$I+fȲrYo{B^5F1Wr;34M(FOQm#JU&BB "4"pp˚^ꅹUÀECIZ;/3SZ1H@PF(k6 *TUv5%Zv~2x8wy9=c354)HU%kٜ ;ʲ+ w="1 bS@dtq6KX[Ɇ8^wYy|/XJ(Ke7x7-, *鈈{Z2twoo؏3//Z}E_Q/[Dq9y~|oS酧'#5 w7/? T. Ԝ!Z3o8RZʒ2X?p[8pFBa]hjœI}Y։ЅMD-`k6ܴ匳#b3t$XCua.48ް9?=PZ,\q"4F Mrlѓ|?/́eԂH`jK1@+!6)+Tbב4%*}u4a8Y sgI4 }={XY)5sVy"c:C{I-p) !6# ֬qZ`$u;1+1/g~;+S+y)t0F F s8>3 ;nnwjazo(r)|7:׹us\:>oVy9{X_~Liͪp o2v!r.ZI!˝bgp]Zcݐ#ʒYS!8ƑUJI˅ǗOy6'c9a:fkRȰQ+; !etL᜶In`D43?}ybdzpa89U裏t'&0;3`i-g d|0vw۩u 6_B>= 2CS>VCoǥAuj`{0\7.Fqx!畧'7}ww~~ BX(%ʒpO*GYY+养$7qo3ϯ?1gRY<7/~7Rg_>r<=!G0v2M_L/,VJ1FyL&.Ӭ| gQ@*z)(tҔOw gMyQcloryoEk:9  `<t`WZ9r0O3iNxY+O&UhA ݎ*GΧGy?pX̒3]Qq&@Z&e~ƚ<䴐y[F-kN.TZ\T0.53k)(h5f.g(JqoQJC]9*IVwUlS)EE7麎{Tj4zQJaѤۚG[F?qi>z|&DK{e4"4Zb@ʡ ];nNaY//ΏLˉ=iCŚp`c1+nݻwX(HkfY(oH z9ُ0L{(3n%kV,~nBS!J{~Wᰣp<=B ĜNH5S[aOXQPFx>+L4CSFzFa8쩶tzryDCMX$3§O<-+?ʼ:md#[ub.`]KD#!*|z=z(3>mUKtG^08bߓKF { !pl{Tz>p>?5d9r>O6Xm;kq!RX3d^n{?˿5?==r[3 q赭B-.D$h{vqۯ4/_ z˸󞯿GcL] nooyk P1/7t!6MzZ RheoQ[e)5ơr??2O0]$y!j=KCsŅ >R?3OZ 9`=(ĹH{6?aE%RntAfiT)@MZ.!l\GR]8UP5L#Hb8`m$*l8x'X=3Ez2 Xp{sJ)k ~vTS8!-`M&М>"k#畒2F$5}8IB^UXBmR D\\.H580f> 4˲̀pX>XyI@0q\(ea#2d(='K#F-+;Ln3k"1x5 exZ3H+wƫs\:׹u\քeV%]xzc>N솁9S/tqϸ;pۇ{~O8 TRX]H&=yr]a%Y *@H TqB<(_g-+r9B<e*REA&PRfXK(2BJJ yEaNʶc#b E*E]?D]k+Yяv 1z*g\uJAp.R 23@kyse瘠/s3Ԣq[N/'07(y!FrΤ9c{r'rs?',`$`VMj-}7` H)_E*޿t:$ _H9T<6,FZ(hz#U2"3ؚ18 -QK%T&oaDL˲0]^ "VC<]OY+i~B1` 5e<qĮBCS 1{tf^fW1e"Xq3" *LKg `{3i 38ԖHeڜ:rUbk*.(+x 2ҏ CǼ^ "PVD!@k~4Z! G4#ֈ>Ԫ18v̼h=wJF[hΨ[Χ5lj.^TGm4:%n^8/ ;%7EZ^Vk\>qR6wW̚'uVuJ)(uc0B= YQ!L*".^]8hwZDk5zcȹ~ql64CF][$>>YWn{zGq mf6kݞnG"[ֲRJ<9ͯx+1]凧7?~[[yCmTmo,u^]luL nv7ox^Piy=x4/yt|Rwt j ;heUPmNۥG&„–wgn'E5ZB`k )k"zGvB+Nu=c M,<X+1RZⲞ֪2yGbNL)lU~?h3Y(0unG=i`fbf'&!)gj8a8JximM;j.,kfYμN&hH-"F#Ra5fYhFI*Wq{cf#// )Ce܌;$S(~{Bt]2-L^}رN?ќvf7La^.KjkwDN[LƇ'^Gbh9^#V6 ,nzrZ֣Geju&8ٍ78;gk-%I4iZcU] IDATTGY ˜؏:yoH)QJRXuW"4h3W& A2$D[ef8=FB.]B_4Z04kȒR5Ӭ5f(62BO-]o1.GeFx2=lM wܿù\亐BpCf7 X24ڸ=黁W'FZ\r^HvqiY4蕝F󌭤Ye?)<9QKSgVcYgZ#Z71ZYDmc}!Fiת4r*Ts ˬ̶.8ŎeY~yjmx<4XSVpT [S2}*l-uX+M\:׹us?k!slu;wde]1Eԅc.Dza.x=wϯ/$& +%/PFp6xpyuL5g#eQUe, it$8]p;i:j,K"ZK> ;0ʕIE'g7ޱ\&=0- -U0gfX)lh|i1s!I85ipnQ[[ceV# 86W.!<ܾZ˧OO o:刵_6b*IVIYDZ5Zy֘^#P%G:t>a]{a+7{Η\MXKm 3bL4Bm7Jgi&f8I0kn넶B  8.sx[4(s#",hw^7[hVg0'q;C)+Bgr7v?ʁLwt'8nk4䲐Z'|-u沮1ឧHԠଣFN3BRa66Drm΃u<~y;ܰHaN &M#,gy=Qm0R2i[mSf6bШ gYqZmHЊa1 ,"Z!BZW=ܪSaP;'B0>hARye Sqfc1V4R U6ʅV$#1CԊ`:DNy1T|:*8D15u]<6,Vkk5s>M EXˌ ^I_Z3C ;\x~~$?T7W(]Go=ϯ/GkxM> rԢQq@7o]{~,|2=xk~)5iv՘mHoVDP8`g]W:cb[388ֵy]Y7oi|6u04*hX8p>Up zC|)htGnDaba#b,+)e휶Ӝf3nnwXgƾ'_.,1B1@{߼'v:C!癴Q CC cdN#c'vL 8P:s<5VjUmouV^݂+m* Iwwc[)q!mE~EAGuk#ങiNǂ;XTPi͓lr?1CD2]52> B35c87f5bhP ڔվ4&_.Yb0ƓE[:m0E2VW_ \8X#g<<||s3>&+j|MVN.eVo9cuT]GkkMhZXgpR1 BRчF89 `]kE+sk ia-š2AZҲk[SFrF̄50p#%Q7P+[뷝\:׹us?g!` SAdb]M l k<~âKtwhTP̊5~de"* iIA`jЅ5 v,f+%{H5MvK0CZ\ #1Tm9OtnvZ*2S) Zf,B"]\^gGb̀uF'V1O6an@'ġy5Uz<kk20K%8@z9~x|,T' beqv2F }萦GyM|nke]`ue Vxө=KNB{e&L+ULzl&%mR7aBzY´N\Ng-g,mDDpD-Zl-zvfD5uT|tγh `0_fҺ`iD;m&B,-zMHe=f>~|̂w8ϔE.D5$i̹PPa&D\5UXb1/8*y:e>p̲"i` ync}i4k)"aR!Kc͍U6i6xu3QKQDy2I{ed >kbJO",k#Jn`Y5}˥&z-^?ÎfTTMyބKmKcngjWaxAZ4nPQYp*6i0v`w6ǏLD*HN'":;/{o#ۺei} 3/vueޛ9xZ4h)h7@tAB $J 2qƽڕ[Ic#fǷflmay9Ub~BhPZfKq/~n\τ"kAbgz]c}x-l!VV ԔQS!7(y%; aw 5?Cl;FJbp3FJ 0כcFZ,~Ro} oϕ' ]ǸҘHkZc$e:6_Wt?~U! F{{L-cu6mn(JъUc~ּoUVؘݕŊL4ۊ7#=gZGqk;0<e+I)֖V_ߋ7ZnY+web.ly.>37R U25l=>T-67q^1um?A-t;@VХcp|80" _> dɳ%]b(O)ڜ\E#Fbp]mgLgFz&18&8͌w?|ϲ,H38s8#/QMc/VZcYGlۆ]~600RGw#otɄ WUw< qwk 8j]WFRkXF] +#V2ި0F1rB)ep.l9qOX}q;:uׯ_Ja"{}yZ+wwuZ8hC% j8oqGz]X LOޑRf: aLŇ+8j,2ñ4^/c赑RpawA֨{4*VWN_K|ݎکgHBN:!jח9{ àαqcL#C H=QjrY07L> ?#¶64HN<4Z݀Ԇ5K,_Z^N#̤Q*YkF^e4Ԫm`{Kƶ 6X:+b:x~ d8*F:R#ReL]2q0-:bnx|4Cpċ{78^>Ke#c\.Z PRGpwÁZp3ZmQǁ;8ZXUJF$ili%n&S 10NY= .6"jTwnNC.۶7>}yXa<*ENEA#xmns6_ !)8ErK:ڊa 6B iֆ-aP+Cp+ Zz:q7p+hTczVs?L M0Q!^scVXtIp7Vy37Opi[HZ XUu"ncHJ3 t)zuO18>MOF\Uk. o3Dz݈nȖ[I0 db`S6P!N7?ryъaƇ,ք3mZ@ GZLT DZ{ww` jK11n@8̏,3UGi#]ua[+"V^ض+0FSjǠkMp^#8b^V<dYׅ5=e|?:N?. >NfƉV7.v_ ag=ϧfX lZ N3{UYÀκer?>Pl%>~1ufN|u{f>Ntoy??+l@Zk4^8`p&C|eo~=!s>ㆈql@ YXrfΑ<s ?!V'wR| g9fBm53Fb0[r-8o&a]clRZX\g; 7ho F,ٚ#)w17#޽O)FgK0Ow =nˊJ?Ti,Lgݞ%zm[yuXW!"8NGZkx㘇]ۗ rT|m3!j7t0N]p|1^O~.cRP3&a4j@2cJZR恼mx4b4r9]ά!xmBaPb.=?1˟]uaf\H[!QV {֞X .4GL֒#![6mns/ZB k F{pk k1wnLO$6 #DHp^ֹ\,ԯ' _!8OziM7y)'F˂Ha*<".0"LX(Q sc4ZfՆ!' 3-!3+̇H0׏|=i+*\4Va1(x^t`[To88p>BL1gp=⑪ww=p c.* ⇨Ptv<_)XgQ E2+߼Azg0TfG. >}͛ẇ Ta+bPT*T!xh,Wq>qn|+^yXDzp KZvuPpduY)0߅=iNۃBxіنVucg3gR.8qt-ƺ0LD7^iӇlI[i r(-~{xImcG˙?T1Fir`_34bm]2Y35ќ;HS1BCHtw<jK/qPFi^q |`K'u tL^qX!0b6r>J*' սכ`FBo-GscojXJ|y|~Ak? qx0XyCZ'zGVmyEmN'smV:%tx>=}jpI)0bc18Z%e#;z1LD2//Wy&2jsag)пV!N=}1{s6mn*<>༶)ZaVk= #X)c CWAlM+4HneZHIO *`Ѧ"g,U\PXd]y> o-Ҫrrbϴn0Df qĊqHiZ1+N?W, [)u:G>=/ԲЍ>]d~p?cr"D.W81]:R+5epnF a[h<+,V.*84Hi%JSAWMD)ћ8ICcI'JI8ll&T+*9r;먗3!x9"yrKUJa J8 [tyR iOˋ^ Rry]@(8'(13[#p Fe\Pkeo= Xcib01t3Z_x\k<<,a\n\))t4|BE)P@VH~= | ởhF#c̠hϏQ{#!Ҫ\C9^$p\R&/u%"NoV;UNWt(:1F"W: u0 V+9"mTVp${1ę_|q?e{Z!OO te*YԜN8hAŸ2~G)6-Lpi<^/*$4EBh |c: #NKBZ90`F7!# D7"RD{NK4B`]3C'Dz\3[J6"BB g,֪kĸZ [ m*idh4nos3"ӝ)%>?=|{Z:W`*yIҐ4һ^vzl)#|aY#V e?kh{e2 5k]h㓢=Sz0=fƀ Tqn @qq»ޫ|m,LBoo,\qrns6m҄T}Y]cqNwbSـ M˶3u %0:Bt44өh$XBfFqi@f\WMZ]X~ Ez0bvF`- sLeϟ_J<})1ݓRp IDATbIDZ xk?3:J)Hx = Sr|&g=ng"6u~i,a 8@JWjU%J: E5j <89W1>⌥e8n8ͼ}xӲ7xq>""̓4U-Pc.XcNtldXvc*mi<[.Tce窠gc0fc1jR6K2)eDq BÚ+8k;8x^ؾ|T GO3v0G1.cW}+ UU8KY֍kTNPC!$lwlB띆r>1OGzsl%c`5e ,6Zt3b:M`!"]H 6JOmº0N8 NYJ~4A~+(yÏ?r>꺻j 9׍>90rp5, ^ɦ'r])uCl))z|6 qf3Wi7pJ  -+Ҋ6b^wbG"R ݍ]űe; OG֙#֎ԩ2O i͜' >0>`XJSP`{oToU3nt(-\>#U|prŚ8F6J%F.Z@C(Fʅn}c;?#Vx3˲! a,ԯ-C]՚k5X񎇇D:'m!z&C!j6ڀu[, oUրU1 v֒p>+y+>|XqR6iywtA*A_/Se_Qk`_J-5_ nZ[K' p9OBmJ)r{HoAXkYI ϢlB=w,V9q>]&2N>_3ցk7p]zAJ/Vt6̨5wo8?O?\ME VF7RBuqº/O=Ԡw!U|XR8B' B4Q7xOAV7wC{[Q;zBy+m`L HY#}yhUpn@zq^TD^5n7΢ `ch,tRQ6mns 9?㬧ƪ]Zq +Ws;m m2lXRJBZ5m93txdMyoR}k*]ja Hm-C<2Gﵞ R$PUEYK7T1b(|Ĺpzł z،t00Ňf+~ⲞT iM8 LL'"e0=~ [Tsz5r[y8‡O>"R 0g\\ 8g0tuw Fy 3{O=6hܭwV+C;ONk"g`>G.ۀ3 ˉ^_9q<>4Թ^WÁZ+Hyk1n"; x/l/"c%!ݰ \B4!RkBʸ*u+hnZG8~Lg whU18iR!w¬n"B%QF3y$֌i Do3O˙~|~ cOhCS'iH~|DB.FJI"ߵKh9`V@ӏx Zw<ӫfSdܳ33]ޜs_aMi#o y x9#pi{C=ns 7`- Dy;ZWHp8̌ƮtY($ ?FLP\+M^ЭrwֿV:X4xq1j{O9>==78?03'.B΃a΢@e) 10Md猱VM|TF),[s3 m˅LۑilG?2 !F6q8>qp؅2NҖisw?kc! adFrMX7NΆiiҵ:w=☧RpuR 0>tmj4uhcۺҥ+`"h%0kT7,'zx7$s^.۪CR2#eKUٗ3ZaK'˂|0"ocMH /R2^q<u<\yL0Xr]0D40ut,׍X'Rqt'z̲)-fN?M2g>#D M~%-}*#ȏ=__+.K*ںrDE hL; 5ѫDֵ[v1"4V*h+1H)R: qR סii4)Hmז{8@y#r~!8Fmm- '+09k1 nݖ̲ qDۺ`1U,Y4YzAQ'<}ƾ,s(!ƘKHc vO)+ogKYunƫ:. 7zpjbIir=q>3Nk5ȳжL/|5zrHk\ c;o|ns6m҄u4 >L?2kz*8A 2F]5Fy~uA1Z޺6|`c1ܡ3 #htM!et] SÑgZ`q[e`LALrT2ډ':2c_Ī麜#8ӪTjRJXt`=`i ɵǁnb\\3ݽg0M)/ A4u<khe@R]\Jp?Q¶Ty>+`x[qrX>+g'w'/r)˗/Nk-t7KkW%uJ8&8 \g q"B?%|B:?*faI]w`ibȹnns6mB`M_} J]3N;xZ] tHR2 ^*)sC683VuoyzU5iٳP^y3[xyy] $rzak he+*x]EJM?oO2E^F <)]~`%ϟӨ RWBY)SO N2S|\ׅ\cF~j~ Ć0OwxgXFyʔ|fK'69].,]7jIo$SaԬuChmnzKTB$VF8Lw3!4g.K -ooCngt98O^>@,CLc!3uZDֽL0Im|1гV5.AViE8=o %z] @H"vJ5ۆ#9ZnLYIkC쀱#&) .0 emڶ", \5,Kat7ۏ嚱6V[eȬވZ2ÑqiYUi?{|^##n FHiB Cx=|ė|"I!2cTSr[qN.`!8SVNm١ʲ].([fwJ+UAΣ)Yw7tHm# /Ik09=GjPB01G_}M\K沤Y}K{\#Yp i㙈[gޡWz-tIb3n >zZcqmWlWN7V*t-].ZVfBHIcMTrP0x> \x/^g^?]0t"Y0Z icG;jb-;ܷ F:v&i.nyB t1\+-|在qlL;9UalԪ pb f:BhvUt+FRj"0R0MtIl + qp>iYm`[ A#ތdXo/'4b:4o{os6mn![! wX0ޏ G(Kқ6RYk9L3" xUzmS=꿍 6.!;E?o(1|G%s^.<Q`FTt=212t,F{"r內Ƿ(]6[[2vu(qc)M\ƑF-&r)%85UWQ')=PLK`PF8ZS IDAT! Xf5;%T1f{Χ3zxt\pzvs6yrXb:ZFiTC+uxہvĬũ)Ö& D$s'Om,KR1*.:t$UZzsTEiOJ Y!c!Ĉqfr0EO@kF.Zy~'?_yxr7dJusቾ송k.39-;@+(RE1٦R&Fh44=TO>|t9߲,1B*km-Zu*ĉZr^M#V _ዟ<3N#FUFP8N}|IļL#rJҰN>Eu !`]@4ul3RHs ðe5vb4"Z-ZBEୡRf] $(ƪ_Z 4yi$TB"צQzHjT)ma*FnV86VN7U"t~`I":07ZaJKաD!yZ39WrI1Zـ6tx~x돀{Rm|'|~3g{v`V YuyZ\.nn^g?Ҟ 3Y^Z[b|@fPO-m`U(%3z-=sc޼/|C¯s=_0twߢq7wˑRʂ3) n_q/gv0{G>櫯'u=5ϨUj`gCSƽsLsQ4ƩDwT788ㄷ&LbMl7`*E+FLt:ѪK7vp/fRPZUDH )%39Wb0 Lcd/LӅ"”&RMDVPZc!WK:׹us\OSV->(lK(YMmZv=Z g{;p8TsR.h9Jm冀B+>%QVU8XơTԉ߱l67y=g̲,E4Q+HX @Ն mĕd" y*:5p>4nnY  "6A˅IUhMAWY}(pFu[Q\O ˅Wo:O)7ˉiyF893~O٢eN#ΔaCd"ĝ Dtsh^9l@6hðg?Ъ_!΋M/ˁo˯|r-ktLRbcIZYH%ǟ|ΦzgOhB*(%}XztVjME'Mӌ5$FL︿A=9D ^6s #j;ׄNq:y恘UAј΁B?,,VaB<O!.R1kcl҂8סph{遦 y8PƲD΍+Tc/Xk1SQU%Z( seΠX›l+Wkus\:׹w!g&^||ϋׯ)mҔXZݬŢEy Zo0 w}H+%΃4`YmE;O-Zegk1hÎ`D w=>jl=8)P,G5($a*gԲTdIrЄ$jZ*Y'Fw䘘3whUhRj*ADÜ`yhEWJD 2č8g0FŹU3H9\C)nv]O)&~/_03!|x!̳JVʽC+i C B* o2!Zml:B5_DB#S<3t=)-e`ƒ3ĥ"k4Wmx@Ey[0VQ@K%e8e!ݾ#HqVi(TJ6>5}w⏾3w{J4(XE6)oT kd_t\:׹u'Aiuw8"Ei.,iTu8TS"-+Zk4*5<_am)JbU,UZ %"`Gm (9Cl#B}ng(y@EI,J9rR4 (Z!TM񽦙ĸV uԚRjM#L<G}4M,S( szhhX#bBmhM&6 cdl߫V Ön)JT-^0cq {4RF )ۻ/3Α4@yg3\rQHxߡU%.@bO²LT%,?SKh6fSʁHqZ8ZHtJIv1{,c&uV=J"i 0Zq\iJA eZ,X1X)9GJe=e!8GADZVGRm#=5kev#̒4*fN?^TFK*.෨܈nn˜R;Ggqb$ nv{{9G)yc,Lc&-+i؆;cdFv,)Cm˱iai<<1|B\N,Hp3{(Fm m,MAL 0`y+JՕs\:׹uYZS)mwL4UG57r4h%;V)XeiTJJ(>@#n#0&M#+/f6(Ъ/Bl0M3w~M iYɮoL$hcaVEۡ{ ]穹: \1N$}Njv~9K=Кg:47)vTJDpL#B0s:=tR)k0 Ɖ0n^r2ͤY3'EV48r|V ݞ|!e3E'rX)u9N uԖIr~)N)aH^7 k@K6bF\&f}Fl^cBJ#=)IBm% 3gr׷1s<$@ C"iP|}s|>SB߯ f;LΙӴPJ"tRY +)%3k0JUfZYb^EќIv}fS$jvRi;R1Zt <.<9Vd.Pš:ܞ'r@[q\.y,mGHhN t-sD[OJrOpjnUuw=LyQL3Xch9/bWɥP0ʒNR/?M㫯V]AMJ50Áo!D7"\YXVQ9Kq93NO\m~݋-y`*pFiJ&Ke{xJq:>aUH)"hv(eZB|35Zr㤰T|2`f<YIL"1c>8^Z9*0 J56Bg ;;޼i %t`ӿĺq"2> Uj9/\-pwl66n;3Z8O҂4x/?#. cO Kbyac MO 4I5 )e3F r, )Vxx/\'kiJrt-a#hn+wᄎ]:׹usqrRv7he8/x}CU_хk6Ty-n1NLH`#f"cz[ahg)Y*\Zj6->hDz޿vO>5|K 1rfq^(Z+`M/ `g)܀s,kL4-|%:qyF"Jn5V5\$2D1|p lt5KSŅ ̀:޼%^pҰU\X06 vC q1+KZqs{OP’ c\Hy Qy=Vq&V;yENFaJs&ўߡJjֱfJbO\օ4rNQ޿#/Kp<\0s{ӑW:5PELn=A iQ )O4}Gn9XXԌAWQǘ\Jlj3%XHt`Rbt@\PuٜrZ%Mxv/^p:s&Rs,MGC UcJCe lrj0s ߼2\?pswCJR#ecFkHC8 V ,@DӜV[H4 %Niff+Q~8ފ2+%3/iRE-F?:YxOgB,_D~b_.#Z[Σм,^߲nyqE#Fjh-( MIj.lZoTs\. >P0&=x4shX9m -/xz|~ ^nuK7h76XĒ wtQp5CBU\ 4FYţÇ2j1̔m-)EY\GELb1V44M60 ny~~[a9O83M~eaqVړZ@u(@s2J lih}Ok< 1Vyz&y9mUeʠ=qN9G (2c*ZBpӀBI#9]mo5RۂVހ*WZA)y'8,%HrVߠP+Hyfl1S[ *ˑ tg˘&~LˈvXKʄГZv0 #iyϰa#D6FnL՝a|tym g,n{)EE\$ Јօ牢4HIUb|bYP 3X4#MiR8#~v(El=>v,!}I/Dz\Loi\.ZygrssnaGyP2'='?s|ɯ`M`3@N#6jqSa-M7w4s<,R hr4:pwQX@ej)*"v]YҔY6xcRUAX*DYXEhZ̀֍TE:9^Xʅ4B=LnR_4NAi9/yb[BwK2%r.RYJJmRe,16QdLk (R:}%ųh6lz5I ^a]RwPk*ӤZ)_%9Oxm0UrwqəTqD!kREEYZhD`k4t6˲#y)-ԂfL'JRɵzOkR2xd<ΰoozE T0WJ4hJ@S\jrrqy˷O~CDj#J/,Di Fn+JI -RicVtn`exzלOxWԭ 1M]9rxg;Ænӱoh,y%ehؠ|~ãݖ/^0Nޠ 涣yy֚ux7&A&s!EᇅУǟW|i'ѵbiJQ3DQWnE}(}öh/ι:S-iƺP][I7?(mx>AK6-7Ps7lpƒfG-I|E~C\3˙T.ˆBL'"qiJҺe]70RKc6VYR֌҆۷#/n<{?isֲ݈0uRn, 3DN~N(?kmhʿ_;Bb$*5`4 ðZ{W| 6\<pә|`#jRZVք:jif\$ \GbJ߭E0o:qQ5:}X# gVTjI3^Dhtd/#JUzU^D @|;-0ߪdь܇1&^8YĵV2Jl=Yj+h-Q]@[\} H!j[bJ)23jSJ|yfzzSlO26ԎVE"CTd]օ):;yFrVM]`\F\#t f SJw`n)EqᔆZei-aӁ94fJӅB9sj1:ՁBɑ@U`k{8Ц0 rQc?{4gx̪RϣC@ #Ӌ`,e`c yiajCJ⋈w83{qʈ Mܐ|x빮߅* wCmT5sGJ\D^ UΥ(Lp@΁f-]giDlvהV5ፇN&TnaoY@S-pjG]?w+rZjeHml%SoQƱۏL^e#kTl.40JڮvR5Hatr:Ng1p=]8OXDJr[JZKH ], )9/*ZuڡH 7XskXqRA=!ep%bM5ޑkb"Sk5U{v?=gļ Ec+1frjx7JN`qΰ.3h~r0x5q'A,KUzni64h5#Pځn[VŠE:!H֎EZrKֆRs$J*FMEZqzbm*-D5lMQS$aPF4e^ Þ;beԂ6~Ц*TY)Ub5J5 "6=B`Y'[JmhJ tegBX@5Raa04)r^o|+1IE}PFULµrΠQ#gP8a2'>%hc#Z ⽇)rwKLm~@KD1BJtB;pOGqeY/< ):SJ( rԒl+ '͌5LVB )ٻR8씁"@f!%Fؽ}er^ɵi4xY&nqe9rs . ĉ5+&/pV R7ŋËۘJY [RwB?X ,D. $䭭p[o t%m LykSIE UU4_Zp3?ZR/,97B8G+ɳ;o~ˉ{:Zٞ{Ө-bf8^xx!L< 8n{^~RZ!>DLDt_"wN(}3vxIp9OTQqhk$ ]l|u7~?8?;syyy_AN}Sfx[rbG HC TrH8Zx7ǗxA(jP*M7@Ain iza#`:$Vvk-JKM$~UăXmbpr{.һ#i%zA&J*0BсZZ!bKi%PTJLX#5BĘ`0ME<MjQ"(( rZ̠YD Ԃ=D)ns7xݧ\?X @[EPlqX< V_RkX0Au^TFQ~Zb;ϋ׷~V-t˅@WǑn\N* 1Vaex| hq}GiZc^$RUˏ膎eY`Qְ;ǍG;q]/+ޡt@-ՠ4J.$3^8&ĺi08`YV*8kLRQmt30  g" J⌑Shš AͅhLLղj @:uՊnDR^FP9n2Kiucm@ipFx(% =(UǞJ!HSf49&:0FJ-BC#Z-4M ,Ժ1EUX麎y^VkKfYO(5VЦQj!n5L,R^q8A2[/Z.Z8Q"*jVEnGʫoae!gE@u&@8'N7ycv%|#0aI‘q=8ڸ^ԚNlG+X %ú|+8cy?t/cŹ5MՠT@+BNI2]7;y+5V9x{*& HTXj֨Sk?S_oɯn?L,+Ws;oSjo=UQNI)QuڐJJ c%5WR=氣 S89 =Ƚ? 8aU7R ǝq:;qiWՊ12?JuZN軎.}Gas|XGUVIR ݁r!轥yZ|]OpޣQ#^~I,ܨ-S6~Q` +A ̤XEHWSׯ?\(u%֊šA̚B7a=_';}/LvXWѱ KxDk|Se!"ÎnGx`L\7|X:VrXv)RbK6O(_Íǟ7ןyyy&t]Z~B3(7{Q|UQ`lls(c1VQ|f?pww;aY0rjCghÛ^lV*F)_ilJA*E PDk[5izoq֒5䴰+Kp}yZfj߿DʩRXk!kBXcjrĚ2Rub Z A#+4=Q`\2HʉҀPFf]"uNwP,V8c@DXPʓSX]^|w-$¦%@!B7,q/7%&ZA JCŻHCaD0v;޼y~?RGTܪQS%gR (Bm[֤$LA[15$?`1a(JӹTҔv{|癦y-R1eRDYc ͐38v|XHDi΋w6wˍTUrV28(+oJ"Z)iRRȜgTmt==<<9'4JCӊrp㵡 i ?Dz&.kʼxe=h Xq !.,r\^M e$]Sw@scgVJaagv"4赇β́\%JT*kI$+i]Buw"LX ({ i-̭iZj9~&2hist~dZXo9RJbzNXLɒTYeEq"1$#nc 18kȵɻ7{YLi e EB̌ݎ3躎t]O5,jIҜ4jb(;3y?wy{qt nIȽ4ðCaݾGc;7缵AXE[Lәt|x|G?v3`gس.uN cG+eZ2?{@ә5MxpZIVmx0ԒhXk Y (%/E(s$6tÎtt:a:=ZKXנVUR`ݵ<rw'ܷ^ %g<(qvӍ ~'d JNz憷5!&L38%^a)%>V+FSb%B|=j޾#;> _oYyyyy?s0Af*q_h+ W1]xy+t#|{ENԼ&-sv ;B h)g=pĺ4Gg`wiVq=[u"F M֜wɹʦ5Pf4{zz+1Uc|y|yGg Z@Xrcv_HJoO@qsxW5?0O3a]x5}ߣ!eQrdu,au?s9ׅ빒Rby||d_տ> IDATL?tz Fi>QTJ*xRxzdfAS*X엀dI[ML~"REZVa\#.+p+¦BwqabY{j[ĥ6ڰ55hJj]jMFVW裍4Y? Jbe)( Tkt9GC*Xt=I3ԹRDW;‚hZIdFn*aȏR+Jb5X2Z;%C:SLqXVzAUBZ]8zj,H0қ /wB͛ ?m,4[gY`j$6YbAi0-,Rtiz`^Oiv3Jv{Ok5mZ]Y$ÎG5bךBGspc? c FY0k[ab7jrVZQj%Yގk*]cb՚Rf3 ;Sx |W<((0Ǜa%R5J, u~Z uFYCuY>牾wǪ&k#N\@8Ty)IB̑$ǎVfΧĸ; LsowYyyyy?˺p]&>RRY=3!)y"9r mcԠjКL+(# 7^@GkK;Q3 u ,ԖI%Rl[ĸjͥoԶO_~%,3zcWz@;5\I%H)qu&pύh0a05T JKz3 mÎW/5>{{b !L8HeDNhhZ@:og*7y|zzX^aHIq'4$,-aZ hԦ*qX)04jDcE,aN=0qk@cƕx`?p{{xz+7= E-q#Xבʾ!G)L3]oE0V eb6RtyxrvYZhӑa jdWΒB*QU6sZ)qat]GNl=Қ2͔12SSt}uy+N-yu^Pk7eioҍyfiB^X Ko|Zk|ۿ10X/H4nP,:/\179'Zb&tsjEf M@RJX#jkVFC+Wi_¸Uj,ɻЬ0X|!~,:vZdT#'8Lי"i*26S8gQu[#֔r! uԚQ֚2亱Nދ)Z3XcY-^g-h1,,,d7ak MkZ4vL%i3V((-Q|2 k(up$TniҪ6 qP+:Qi<ׂ4-zx@)4M|8}nG̛s MՎLbY 뉐VG͞>~Ъc]gtu}|bf~@aZ#RX { Q^xuͬ!BGz)%sFk ȻjG]F+\4B`I+N+V_ʵt)u U U)-9oT,Z_ z}`Η^vX_q:/p:?fFqp әiH:no+pNKTYy҆C-s9N yWb Tס(EF`tCۂu"!MFD{e#΢R ĚfrY6@km.' Phm(2z7ܼȺ10Few{R\'b0ѨxoI%Bڰ\0u6EWJFab̀5#}w1`taᆞ8-9;޼~4=0'ZM) /~,ۻ Z[rՄw#VTy8qaף"*@k Ӛpt@0#%er\BnfTƱ̨VU%F'NWIʺ>PHk_J~hi{yOS0-Qra#s}2'~[>Nb֔s'-/{\煻Z!LW9a^rlOCRQAbnƶ?_q瘗sxDzëWwtzI<kQ-zf..GūW;rX;CIM36KPbBM({ڠ܊Vj@U1#gw 7~ۿ> :<<<'|?hp⌴u&G GU먀V]d z<$Q+|f󵯽;8==Ցw{.`λ@39 t$hEIHdRR1U4q~L,|jzݑTF˅ķJŚ\;!Hvg;q%!JWЅ*Ri(kFs~ [b0MNXS)fQ@35yi㑮}K^%+Py5)`xgA@Hp9c-Ơ q kҼ)zGIU1(Dhc;V]7#l?iKKlM C6jt;m6$F)9X7jg_or܏w'<;syyy]F}?ueY(˄ Z9(%'FfM)9d`2Hi2+$UfBm]{NcyXw S:E1Eo"z+^(AA"t4 zEi+ (h4IhhP]kikU_Wv"d/Xs1{ m{h"T&6o 8--R]7UCr"E۝Dh ŗ S϶J3\΁垧T0cwQH5bkuy'l)aH?XC)4~IkĚz(ɵ˶CANUc?Rr' D;?bu\]]q>?P,32!8CAUe0 (@d*/B4Բs&41@"54v(8ᇑ\)RΤat2V1I+7BMq^ݲ79xoXJ8cȭй9)a<9ut)]-Ii%Eg'^ܾf u9LkYڔfF;;J:jQL+.BHglBCͅWJ׏-m;s*eyUӄ1okno9xD+1]?SJaY/auh " -2-൒Rxﹿ$ -#? 3Jg5DN"lr{]\DԦv "Čњ5e.[#lDHuoB&FSi5k+Xeb-wa/ ْp>Psi dy8;ŕm0?o~?g͛_%R(VD+(i<4,B,-EJ TU:yEXbXeku8l[5%m 8g{cc);'nkmԦ}t M{TwgB3yK$ h=7^`orC>9<<<"hͲEfL5c5umUi%HUwOv!J(eɐ:u-c 6rXUi0B+Pb:%j@ T &'D>mZx,ras,C_%o$&44(GCQXM9l9\2T*Tt=TxV$:,T։bdZKĸW?Swpk9*,!Mh)JFiϴ(E]Y~`{lIBʐJd 7ɫϏ<޻ﲔLea;T[>պ?]!/e`Gwzޖ²,Xd1ll4x|XI*Z,8n9ƐS)xہ9 -W8z^~MNI"vZD;~b )8=TK珄Qd e]0B:J2ʬ!m!(ۜ4MhS%҈ 9RuCLM("a *%yvjCjZ T\W<99nEx\xx3-I, KC˼;s -J‘+ >Ϝ Pm맘Kn [8c,KΛUk24CSn?۱r=c(1َ:gb/ ЦND(79Pk٢h(J'FJ~hEMZ%etk3;MBw7E+"|pO/h NC;qĒ1J:Gmm݅AQ9Ѩ0RJJ~xqˋ/)%+BXw8Rj1d Z(b})R}7`C1%6ZJ,jDWq).P[o _ϔ&1\ 'F-sz|?0Gg<yxxjxk霃11qubJMZ IDAT[_[zkl{N'#CONV*MEi 3T"3l1j%GRr !1+!]*eS%ndZĚIyLAbQf|LFa馉Jie( o8L\rϺ˅5a<1n䔰0bNtU,Y!rR`]y1st,F$<9ՕRAQtjX3``3uBQI*;Hša?!)ZE8PHy}{ˊ5 JY5#_V;?x>|xÇ7x+o>۶Q8B ('VԳsW6Ji8kIa[n b̴ؘg7.= inU⻎\E.O 8oq4L1J6\>jú̈́ (=:,VCDyb.1D|mQ yZn,̇ǷĒfE3h3;ELqjm}OkQ+ƁPa: :(#?G*睕aꡕ=h9rYA(mg]#5* X*+e1<EIX6-8cXGZmk_Q]!DӚ b0:PRjG @̏4!JI8ԴJڡ럿Ç{5hQըֈLN82<%kZ>5buquX۷oYq#aۨY}F:ѦJW__S)m#eEn Mڨ{DTjR; haòr\%~R[hl ԜihÁ{Z\ aw/TR*q[1& |}l.i9E"*JxJ.ĴjLΙ5:;Ch;7Ĵp~@+ G]~&r\yL+3vbǾ&ue;Ʃw-,\.(*cAapvU?:-t.Qxz:0tw.˜:xd"س"V1MGNk6y96 }9Fq6qhu#7CWWG߱ȿ*Τȶ셛{P rG"TXrj[$׊Vu;Y( j?P#Ld˲ńRbӻ1ޫ?[=FUh-<;~g1F}{4Y8%AflقV|w_ϳ<<<nEFCe9pyy*̉R#LV8 1g5#zSJe AքỎ3yrxSA?LWyH[&:QE*c&DX&8ѺbHYo߄f k*#1>\=˲ Pǻ7hu4}?"],l)pu: |(-g;.h4rI•" AX1r6N"thI&)ywTi:E rʡNA*h;qnKE;q~*#:j., wAgk#9/(Q*ߧ@lmeЦǧw,=zHI[ĴuIkQ\c4J.{H%pYQ RL12˺r:\_I %mtaeIqf# C"0p{uŋWĸHST\O͙#e0wqsz~`FUO,l*2kHFq.(4@l䲑B-T+X$U}bZFʙuktbn<=ū[b q@UJQ,s&-̈́NݑvܜEX3%'QZh" HW;T}% !u]ps}Hoeyp-L 3:Ӛ\):?@$i 96jq)s@y˒XLNRۚX{oxqZ&0Fzҍ\26yX;wEw~EC?s?_c o9_O HNGMo߶\RZ)N[uv7Zm\Ɖ$00t{[\´AN Mh-tr0kF)` {䵢Z"孓HD)K>_?sy<<<<[9%؏QRPFq:]SJzn3xK)"=׶sXD#% ?@I`,0њ@}RVic 4j5Xz*kJ<=%:ד>ّן}0 {\S%FZE-YHR] *,m;!fh=F8?ҐϹFZ @AEI^ TTvcEq躁pBaXgRogj1.[+4VrۘO7kD+VT))Wr({ADx7ckf )eyfցtpy##C, xkB!R*(*J{h\=!rLguBhm:n:otڠЬ!0/ 8~:-mMUЩjz#u& Fi+0t:΁m H5l|w uRdA ):`ЪU\a]zuE%EJR'i#d!BN{9'&XŠpAbLVǎR=BWA/ y |סZG+F5AIfvlz݈Y꟭ k3h.R4Jeesb  N\Lg-VC2igB%lځjd6j۠iJu(ni 6i#@#xg̔"'enm C"XKU@j1Sx8s" ԛ_~}F΍9mՀWשrxy^4q:¼Yו1 K@pcs{$ceN KT"VN9Nh1U yqRILiWJ4*M0uP7GGc59G (t^px0 @TІKB.<==ɳƨ]NHt"˃4ĥ3۶E,V=8Ĕ#l+ӿN 8TFJ ;R\B_{Mk۫kS"P2hqa5-풒^Tn֙^ta%OVbe{J!$Gv{+&}uY *&?A%4 g5H뉵^`RFY(((ц륱+]7IGg4+tt<;C.fRY@@:҅WJV[HQMa 1ziR zL\7tic9:~/x7oޢRݵD"GޙJNI Z<7p5[ƏGlIDepg"cﱮ"BKmUZh 0bb6,.)fi~BJӻ@ e0e&0VU̅Z5(5UkJ"pCiLoFFVtS1WjvT*1 GkyXv(8E͉-{RWJ躎C7r-_xy"ܔH*/mRvPPZlGadyqs-MT) ؒpZ1PJr%8dWRLQƲI&WvZ-4]NfZ dre57Py*8 z^޳l~u!iQSF*&-n}fڕeuZPJSZ(Y`Ҭ0Må*ZuRH!v&qZ4JRļ$b9 7R*UXg-DewuϴRb!;-3p^{2Dp<*W9+&(!5bXi>WXg_[,ӑrF)3JaJLwop1i"Ci])v_*Th~ۅ?-SO>&|Wz2~ @!g W-4F9SpR.I !kjM]l@keY1RiZ)&~Nq$48) ڛ>R1Q%F0iĒrs_gȻ;~<<|xGb'RIԖ$JKecW16SK]NٝM$r ,"gb( ;mՄ3ZL <&L7})'BX@e bDPPS0 1o\;\/:Dl Jo7f:S ,8̖>uF謴<8h<01tٴ #D#Rj;H5R&-?Bá,;Wjkl>E1h%N’Vq8 rn%cqalqW,˲ va)av=6t}s֎Rx}pvTYξanDܬ)SqyNtgۄֈzPJj FX﾿ߓJl6tӄ-S񔞠4Z.9nq4DjԪ#JAZboO?.緔Ļw&N'rS4E.qn R.='^XED]꽦",[Vְ1hhkȥa%V Cp,썅Z+J 1 ‡?U},e ٪ҠM.U'h%?Agȿ?OΜyyyg![*Jq:ذ34}Cgdѯ<z'H{A)T%{cDn(2&heɱT=4i%#\F/ZZ ZE}yu Fwjy?rWʊsxX3EQ&QHDWJ޲%`BL-pVXm|OY7Znf$K/Jq¤…VZ){uU\9)r*ٞÞɗ7V)݀jZ1$Z (s2_(jZm⢨T\X=!8L`wxJuXkvKy߃8NTb\]"AϹaV;aTGD&yexDi6TF\:,| (^~1ڃJwt?rCOjz!HJ+zچ¡V~Tn]RV:B-R4 cP4i+'$}}c4Y*TjୣL(ݨ)HʛTch|jmP1 CvЭiLJw/zU {TtL&e=h>Bi_IT]ũ14NW/V eb"34 o<Jczǻ(Si4wGsjZD(j%Sjxhm qrJ*ض@ zz?Џ}kءdJXiÕq8ЪG/lX&ω&^Շ&LNK9Qf3m<.b4/N\ߜ9gS|ҀA;K)޹m-)\f\\'u^^b%m%j@7Rl y[p.3J\"BX3rtskRI<> _L=͠㈳R)n$X_Zĸ^'b {0tXUX+C<߰ 9%utx=hű<#Q~4ou~6M%0tte%EBTT;77J+s?-6~;CoOxqcEc+(bK,O3a,ٶ̶ʺs0:w ([qF[?U|zR(*u@9VFun`x38cXS^Y̾r*9ZZ% Z h|Qe 5Ji1N3?Ο۟޳<< jz{&Zr!%RD*,L'ф%B3 -ԭTvom7)*?ck>hp IDATwf8qYO+;ЖkR=htcOYkڲ8MT8UX d" kzfmA8>K"š1tF g[5 R7I5uU]Kp c) :,?jeih wRٮ_(9LפҘΤ"黉jk36t-LÁ@vj +%c$W_ױÁZJB@B.A )Q5 qV%gpq' 8|;[E[iSRb&+ !@OZ [q?s+V;9]oT'uR+a"g˲tV51nVɥ`Pa߽&Ěw#4M\go<<1L=OߣւRrR1cLe:J,LgXC&|8m3:뙆j:C+mN˜PcFH 9A 9̺W˅B\Jj?H&tDYML}ML ;ʶօ}/>ŋʶXט/[,+b&g0v"AWr<=kl=IgY6yݝvg55"|\LwgxH9P 8ہPl}ж#J΍~1H9/狌ҟGo|.L =JN ʺmVɩAk3 T(k" ? H-7\s {KiJʹ$ϸQC YHcbE\/8|@QkgNssتi\EѭG '雷# U@&޿a:Ato}kgW<<< 9}TfM@+jh%BZ;Z ]ߣ7ukSn\ \BZ8~b]"1e1f_|#R2?Mո{Nk*5-rF#:z?XX3Z9J H;;Iwm1v`.fV((qutR*eh9T~RQ-U*-˲AnHF4 (mٶH4V$5[n8 4-ӬgLQtGlt K$\ڐrK/kP αs:ߓDnJâ.ZZ(]Ka};tLB%t:HBjoM8[*k/nx>2Բ2QъDvlgcb ӘÌ+Fb++iCgR".ĒJ\5OV Vjɭ]H11F+U@ju 1ĩM]Y8R3%{*")o_ cۑ,J5r1b9݌lvf(EBBQ:-U(0:2s *DNвbzn;JZ8\N\G@l6],Lu=fNZR  |lh44[BN,T﹞.\.g 8x)­":a]'w4:vCYCJ9seYf\'Ya!U~haGOJ"aMj8?t(U<uaZ 1wիW 7ocУTDiO3NLĺے oE-_}E3:k;B򜧶:&x<~R}SJ#i03Z%9 M6}$y/t9Tpǁytal)0O#J\pBAJ4juѨ%mk|=\7cǮ]\ Wߗ6s9/Haa^&6 9W_IyyyUȹ^'%1lv 5)#@c ( fvj5-]9x=w{n(-m;HJIjRb@{ nTuz!̇S'?ss(4 \kʅ+;z9g0n7heyLWܶp9=Cy8ƞ5S γټxPPZlҏ4ԖR+4)'q u"]By=~5q \.tSݼ@3p=麁|5`?:vHɑ{BݷIJ7W( 2cf38qy(Yx6ތ8>fgAs q"X#Ljjw+6?{a;3\7\H׃4RԳ@&NҼ Eic2oQPUcY&\90╮7\#]7!W ).<>~pn51\&Qjbؠk&HU ֔HͲ,l[R Ɖ@cn(LߏtcG.X5/g c-Nk0}2\Byu4X+ΣZ;F߳7R9fi氡G߽!imyy%gXJX5csLѕsJ̧`z0a4ĖXE3JpN,S\D0,UJaE5i'@e 8܌}Jv[Qr#83#:3;~_RJ*~Y$n ݆(4’0ʮ)&8M |+Ps!jȨVg1Mo#멵rܓ0c9gZHk$,;\ty:aegc3&qGg7-C_Ecsøc#o}wlV7^*ԀUZ)Dz%UfVmyJ:b1tÎDΙ7S GSEZtH35FCK%։=cc+ڗj!,zE2!H$0օ*); 548sTM7y/t?}?Eeyr~GJS̰))jp+Knl؏Iim;RKa:1~\_[E%#ͤYRؖ(L3l8NĀ1[jҰX+ܪ2oP-0prUiR" O468mAgB6+/wro[39ZX%:+I=z[!E=M,-=q\. =m#͡QI).[_i|#K lFraNyin,H)1QӕLta8p{! ^Sa@9:?c6 aݢuOZpC\qD9v-Kn;rsذ 1p&΅ ;O9BqVcq$7G?lnm)m`^&a"|ŚvePb9Ooy6;tୈZA%R8;s]M)Fj $zbY&rYR"*{q0T*)9iяo޼c1_iO={BLJa^]}+BYEӍ2xΙfRV*h(-L-X7X(Д (nQ*q\U`^me1\[1"a51cĬZIJ"G(e(U"[t}i--v 4HPP~ !%b^PcOc % 9meU)qaɯ c s cMI:r-\•]2/gj R wЄE3tj9Vy]?їl7Ę\'D2]%ҹx#פ6m?X;r:,Oh&T$ǙZqb KZpz;(c!I})Ӑ8om( F+ saXFHyj)$m`F[لhR(,Q\1J9M*RVa!ՙU+O:,) kiiC3R.n $nh8Y8ǿ,<<<6 9- ,sa;址$Ps 3"AM,SfbGjJ J'25)^AvPh4h:QH Lq~"!2K^QK:;uzǰِ@#ejDpfЏ9ˑQnb\GJĘBmbLK JmY"Z8lxrϋ[. *-s<û ?|um$J3VEʹ`A9>=ZC؄_pXI1`m'1X9r9v#pog70:҂)she(Hsd\1=/^̷o~͇wNg*1t py^(2LF *)dj|/V%F~w?3,ɢ,OgP™&B?ŎՏ +/B+q42,Aen,VP9k"%ѕR3ZՄ2s,(]0JNvQ0׸_]Ee-.jufjZ\=+Rz I+G-F&rX+ P֠WEay鸩B96ÈՆ%&խ-k3r.T} XZ-1J)$ya+cduѦ}!s<9]LHpӪôH7xmw;۞q30 = |2uڠe=Oj,ܲ2cEPtB9>ynߡTD o8v6h "xb&T EEɕh?񉧧~_Zẑ o'+ J\ OOG!>ǧ't=uDJΘfl77w?3ƍBW4״ qitRmcI3fh/^|Ş/>0MJp M;0 aQְHzdiZ>CȔr=(` 3U8% /I3O.Ք3Hv@+"fD3 K`q!ȟy%_( 'DЩ0ωT XJn%jih 4(r,1@͠2awyyyMȹ{aY.*ð5@<&ӄ8kLN`T]fJMti+$SsZ1#/ǁL,WP.T*V{t1|ouZ5'B8RY0*A4'B~2s~83;nR ܮbT@4CЪ+d"Z K5w:R-kӈAi+*f2Yg /8_\#tOl=~0"H50ZnNt\Oc=F<՗/?aYfb:єTIKv)d ]R9-4 nןoy!Qi/H90 =ǷoXnJ+8Ti%Pv'q)!8z1pc2[6\"lg-UhU<_D(dJ҅eGZeA;\?6X.LNLDRAEmAo{?)k\N?xN(UZAZ bY\wR IDAT_r~wǴޢ#y01l6%XVE+UTjUJgS4ZͨZY1V7vh-"e ܹ%^K@; +q:QZ M-U{P(@HշDPK@0i@stQSJ!-7@6 YBȔ(Yc[rK,y4xvi9*Idy?`ZfD?:!GjVUR{]bB{c4)EZ9y:|>,pjkѵUVC;Ɨwpޱtax8NtrӣqՋ=rx|t9TqTX mhf7?)}Q6BGm5{4qނ+'HC!R!dsv7pssR&t LcZL+5_9qǗ0E~? ⋱3l6,UDgm-M)dJ J0oR*Lbznnn8/7PE:)ƮB?fbr)ԧkIޏPa7_Oٕ<<<",aU7PV[8ѪЪ'Lw[.GK!IR 4PMvЕn4k{"d6Z"N9ّwi4]6R\!v^vDLgTE/?ՠA+iѭFL&]ZXw-Mg=qp\@ƻՠCA.P'jԑ++h%dYPT*6j[(5}@SW+zVV ͯnS"'KR;|?Zv2[Y1RMGa%\grQX~uXf"Z!eB Na^PŲl-J9ΧRڧ]ͻ6 n빻1jbi+Uj~S`N%- OA)1,0JA Zyv[HEn#@wVE>/Dg{ ;*eaY\I)kGkV~#M+V`!^e QQB)5ہnǫ=o޼oc/> אrixG 8.RZF:5 ưJTnt` fCp.`~B+j)C 6S ]H`!fmk%6C 4n$D#uC{~<+Ѿl7`]]aʠ]"G[w޵( f:gq΢)7^8=UQ;fEQ2xJ֌BQJ uyJ)p~URK#SK&ՂQ`%WbkC1_41n7lm`4 ZIcWi"@Uu͠#Nr|jU䴈Xtk_U<1 (%0 Ei"1eҙ5w=w4]x|z<l6O#1ΔᎮŕ_=TŠpc {Lӄi&\Ylv~g>oV dZ4x5C! Z ,\QU-$*.aNsL=M)E*\.JiĐi0 Xf7=YY+`"DžtްXLg\[yA FK ]h?/(޵oBiL=*vfCo7Q N'.6tN )P[g-a|5&βSCJmm R / aצZ+VJ65U1tcGi{ׯ) {*7>g82d9_R)hXnHZ&̀4> J5jr||G|/ɩ~۳<<< Oĸpy9! nidbjhS1Z E-\CLT(B:ie<G詹|7e:t=s`/l[5Laf!'Jhc991Z VR =hmy5sx-$ⳂO{iRͰֆ]|sNTqifx9q|z//?_phHѐ*u\v|gZ(ih$R]PIr\[ןŗݎR O'3PȼL@J,F w)Tڞ21-v;ˉBb3tkeszeaHP1w6,RGk&byXRW4љE8~C\1VѹJFb(ݰZ\jextݸF*'ibNK(UdW|QGsmKH\W<5JwZĐ%ҼŻ1S>l鞆#\i9K-y^ g{㎾5Қ9+ZiPQ&'u[X4Z{j ҴW54 Mר[utC/̣BL3FRҴU*=Ej ߣt!3)]1 ez^|E|e>-lz~-r쀩)̳4_]iuf3}geUC7/O3!WuޑS^k-ZCJr>$-!̇z֚o,s"J*1>z8=s<[rm™B@ hilƎLVMQC5U"T*vXU_;|͝/FJI°"&6wJfYf\c4pPWfseLK$.{L]5{6CG)%Jʰ2(m3!iR Q+LxU,Áq3HlJqϬt'kLUjӧurƖe{qr{2 9eҕvry}%RJ%X4P Ki+miÎ~8==RZ^k@5jP g:J#b s W`I'NoWȊ\3q KR o!x~`co|Nъ&䄲N[Y Y]z5JiI$QFUEJZyawJƺjPZkEuETFEEk '4?Djk2rr,H3j(Uctk%N8EH0/hǸeYlӷ"

  • YxT)+B\6Q|F+7KRRfzU4eqV [{#.VHHz72[Vapŋ[jl6|{s^ٲ4=Yb8ӽ7nhH C% I I ȓ!@nMvuUVe޼85EYF{XyY tO6:k9gD8|4 JX5jaYWѴԴV>k)(51 =\kD[zlnҩ}H9ӊ; >š& Zi(K,zºyv;c49u^oSJDb*\'K[Fvav eVbzMӅXơa]OL6L5kpJj" bSV`6JW#5b23#M'ˇ'Bxۣ7䲠t$\hMȧwN)M V&Q)8;@x0w lbGb^[V6N^`1Tv&uywSqr2*7ǰ9]LFdX YU#}ϲbZ*%':Cj!P+8XW1Xۓ \/{'RZ<'ݎJtaWr]'0J!7Q1;9rՠ,p*8aJsi8n`i:9h0NjhCHTw͌=u 8M OE&Ƿ7/fzW=Z0pxt!PVdiCQ4cY &XkooJU%^/uF1 NA[Ew~2L^{gUɡ-5B(8 jRv-iUwڒDRFMQ[8s<:0 \-5jJ4 0]V9U} R- ih&,e^qO{z=W(oJU|iJ;Qdc2LsF&Y7bJGr9jGk(حT#L&0F 30 *-"Uq@UBǴ*r8 &esĜ4TBeZt`oF -Hii}aWj%śQ{~G[Z-sj5ZKds`\kH14u"DlQȮۓ{y_8N޼Q^_YZ`]p0XTE*cnW_u]Oxz%iZq& J#DIV3p8  RsXy^ݗ#^.WaGVᣈ5aNY-Ô$Rߏ{Z)hhz^J1jMXV=6zEϔȱ@+$ 9QD0[S*-x;8 h$ac4Zjf 譑ΐĩ<%&&q(PF\fTVUm)Ekϰ,.N-J:4fc)۳;;CmfkҴV&WoշY~89u^olJjDč iZg\W1Ao(-NgƱ"e+B&MxoJI Dk=֨Off]ލ\geۻ/( vxY;FnJ9U{!Q)YlFw0Z1ibjMa ,q<<[ָ2/r[l+ђyz"y>*o@⪈Ibl᥁Qhn[Z%e~Q1'(-kɫc Hfj-!jt2 7WTf)4qxQP@{gk[M3ƒky[C7T(EMhh%~w@!ΜV%S ְZ^6B@ :t̬1p{{nlPE ;%PKD)ikJ\y OҞ*輮^z]uBR1-9 #ȸs,J) J /7u!$v:eZ39,ƒPhkHb Yx7zkiKMZ)6ġ?=vLgjvl$6ngpR2Ĝf(4vہuVD[8&3eiEMsڅ!GҨ k͞:)kՅVS*1It@5ٔ*<(-5rAɦGk4ՠB&؏4eҔEc4S Co{ a8~12\:co}rfYޡ8MO&9a :S*)ÚdRE"rda0i=َKDCWYYQ7rXtU4?5:b1;ЍthKϗ3w;',+F$+ur[u.(Rqqj]aTqڐbu^SJsi,a]Rkk[6 V9lQJk 6 9H5 :UpKK/E1 ~VC<nBJ0ᴑeATWϼkCDc 3}_A\g֔i/7w{j-ˉ%""fkhSpRޢJ ZI#£0WJ rR}wnw` nOԶ`u:J+fQ3Z[#VgDW¼.(,nObNԠI+_IyC*ֻ\39A*krQ.P jruVyYGYbVM)bJqƊsmb]yYh HP fa-.WB]5.Mx)B.t}q$p4#M[9KcdT+ŲRUYAmX#u] Ө~c5ZW|/RӼ0395\׳كѴQZEiE~_+}x: a~yaڀLJm|:֤ 6jѦTG1m=F 0$a ѹR u&Q[n*JGmLk)T܀Q3z:cw%7h8*&hN=噾T%q$U&/|x {P20F ۵JBqXsz^9j/N)Ws%L<_x|~bN)9`Z#΁*#_|5)yf]Y;/O)Zsȹb$Ûox~;wNw<&w _>m&@3~a:oްs>o#k88YC&E[u;S9[ĴR ^Xq6V J 1JmXDzFiX3,5Y40;rp=Lr!3X(dVJՄs=*h6,k ̠*#칿ߍl6Jm hZ+LqSZô\I:ݞnpğz67M. ,YUjjJT2UL :ɐLa^Yr.h 8?/keqs9btL6BX2ipe"LĒɭlUm>L,eGIRqZX; ]svkR*R߳y# S163 yYy9?Z9qme&2ta~GJukb^bQ:|H+Zw(**E B1Qh14Rk鏀a#aJPޜ7Pw1eiuxTqhCBgFXj:D[JUXc6G9J-_땦$wwS.$8nq?2 #&01Mi`R =. O/3 H-&uUTU\HA-MSiM FjDmݚp1[VX3%|% #  BkxcY'1t:Gus,G*%aҙrVWZ`<˲ěxxs 9gwk ),Rka оCVW%1J+i'̍ZJ u*`nDfJA8rQ=aͼ\.pMam)U)KH,!UQMKȭ62V8FQ3^+}ϡ-:xEBa1hg5Jna|;繞~@4-v51_(6,iN1(0[u .<<WόOj8 K`Rxs`82{y{ϰ߱̑G+>!^ uz&G-;%ywqD>Ȭ1COUVb, /:-ov3yyXӚ&DH78c{s L&3qcwX#roRhC. kB9&]78ڹ4h5U 0edn4q\!FS T%R߂n81A`PKi|Ik@kϛ[#w̜OsOՙV ~ئ LaG14s<>Q%90<(}9j!BA3RK/8b7e^Ga#ha }?Ge lcg$6RUq> CBNF:#*@ ւjZ .1q?`穧)֚RpC=%CJS nxzJW׳ޒK`'g# W][-:cf?݈"r9GqYVTɷiNlhܠzkJz&V>YQAo^Mej[Q)J#~%EeXj+GXÅЧ"ĺfJdcoqtrj5%DJ)&F芊[4Dӿt.RuCLꐵ֨"h zg|B3)/]ĊH Q[ia yYSbd3h5(4Uy6WB*\V1(EmZSgDr+hL0e)9npt`?aFip%B`oєBiE;rZi6J*L.|Oc6(% fs-J--TV.y7TU|]) nnaLbY2ȡ|I+ _} š7e |偯>#IJ,(a]F5R(gu+)*DiR7y;07NVu^X2ZYq8Kʙ:(Cm(5Z]o/f8La8?Bc~[ k (c@+r+;45JɟxlJ {W8c*!L.R3~7oi$W_Ϋ+u^mrjmh뷬}n \hnD 4Fm LdR\kX0\/UK,4eFQҔ4de?:+7ڎnx'10W2tޡT MpU!WǠto KS*lVkHa=Xb\I|jJq7o,fbiMps+ѲLjQV8*Ej:MƁR!L*%xƚݸgx1Q6E't5xcL=`_x>}3nugRT4!EbΌ~z:= g,hSIQZ>rrZ6R[J?0=8%(6R $jd(ܶYmcȥ iy:e^.WN׉့)J|k R4_cs~/mfj(6f8İ%̘QGybs_ IDAT<"mR }҂0%Xl%S6xΣ j=FYBgASib!o@ھ]7D$kQZ mN4IØ:Vl˲,8)p<XEuVՆ* ͊[J+yU"ynM'the&JFp]:jP24P^D.iFkE8 Bc571QUPDVYqe].D+! Q ڸOϜaj1rQi^ڂrUNiIXeh-ʕ?'J-u=Uɤ2/*[noxy9S"TK\x<oI#pw\<ϔ]PWRc{͍wbzR3푮]O!rv)$"ZӍ5Nne O7ԲwnVX~F><~Ӈgj[轢脵Dfƽ夵8!lªLW^.B1.EZČ1P<=(M΅=ߔ?P7׎˒T8|ZZa',% ohtXgX( ཈!DRjxQE N㜦d0֔v[CV" c O3E@JK¦1͑;g?]z]u"?aӉ }~xisC[M|xo1@'긾lo~ R4$:Zm,ڈTixbYIeaO\ݰ'B.RXo,//Wnnn(%`]DHmҒJfM3XO z+$1D[+" gIn[TC鞒@=i2\./(3r87g.Z -5N2OК؏#96vT#/>p>}A HjA2,N{v7. 2]CP8 )% (MIyYk5yzÎr>QJE[ H'f k@aGIX@Vq]&p;GnjWo{GX/hxe]Ly0Ub䢱C~?r}F%* )Fݞ]?BL AZi⼡m]NcVk^ 9jM ZfJ9?)nGzhL4U0ֱ3iL*f[vqqNG\c(5p2j6=o5EIġZqX,˲l+rLP eZ])> %U),rYHQ(LLLضc eb٣!DZrgֲTklhՒ"nj Lj.oժU(uc%Q81RYģ?S<'cNZq'Ssmm/kvAOX,eSkXOZʨZeJEYCӴ%135$daQk} dZ6@X㙃L,C׈84\i(UjRR:REFN֭ҕsPWI*hE q*T"PSyz )Uс􄜈lG͕r t]ӳ3 E (>399ߡ-qTZe;j,kڦ!oΤV`vӸVE* ZmױJru7LabZV\] W@6T4uGDP Da1#fM)ӸnK%bl%. #чDDj P,d*5U1]Qjc vXHYbZpVXюZ%)bwXRD-(@K$%st- c,r*U"-I.@XShmI@5zPbf gΙ# ~z`y7oLux.r MpֲLkBt |LcDW/?eruyߋr5_(IQ1ݞZ ?eB;ME-z^|Ų}ů mZƨ"MFΘ(h[ ]HKK&og4x"(_8׼܇ׁ\p,PhE[CeYVH,N>B9}K*YYY9UTմB8ܔ"DJ X/)5_V㔢JQIO]8 WP? 4,kPG0Hd1hm}yy7AɹG??%'9}Zinz#sPWh_۶ρe#lt͈\SUyUHlɮ-e)^4LcBi'ܠ9;O*V̳8?@k$F'·*gUX7 o9W.{ 1~kRlWdqIe9v+q9s<\.mK^M]OЫK%gV^H.6"j)]S닳 mPc%q reVWlͺI=`FWJi-Y?ԉ;F3Ǚy>n+\ucIqyAB (kD@R]U 1-T H"y`PQVq=+Z(B^K`hӱ7('L bZ X4ZǙM{%1UĽs࣏oyo,i0/Ĭ>dG37/Z^~'|na gG\JI}h@۲ vW{3.GW_=ќ3δ_mv^] &/olYr$XIsGY]5z5zXd²8+R*4M?O~x8paP*p/X9lv7|'(8{9&-)oMtl7NDyUpVSs$My9=q~73WWUWb*8#ւkBt3е[ Zڍo1N,qZ]L-2AZ!BeP§]Zd ԌR'YcudTG8)59@\2VW̭UOҼ,T"Mc麖iA/Y(QSK-<28EQsd-s%Ζ Jeq°^YL:ҔY۷z5\ sE3Я,5aF)7?_^ٕ<<<,gOo}3[! KFDQz"DFP iYd#Y7<\d%\A1ѵa8&RFyݰu Q\Gw_Hkc/mK+_v㉡whCc?lHK~M$JN4mg3\xxe<UɬR $TH%Wl|į=K` ˙\FFXUS*fpX'/+<<Y,󼠔D,aO*8#y PtķTG1rO(hʒ2Drh0ܖZ0%IcRh%6,իZjߑk"!RJW^UhYZMbEq8^5N}Jo)MRsy˽KWOõ,Seji{Y;h+ՄxaGohf F1i2=V)S[vǵe qf5O+/_wW|稚O$xX+<|t>EYPʙ"ihe<`{JX[ʯ@ J%V(0kI71(~S1hzoer5+zmmJ)uuq#{BV.݆e ~17W/a٢p{8RRi6yn[6|:$v3 2Cݶ{޾#J>_.T/owhp\U7yl:w?pgtXבRa3U˳3'p({y|釆}ꅰjU,t_\ߢf3ˑiE [opm8_vlz~x _5a<s(dN54 mc1V"װF:S֖6D;dsP (mrԐ^ dȩPT5AVY"HڈK)~YhZ8xe.QwN,rCxGvĒ1šJ"|6J9jpڢXrr)"ZdrքH5L!ϿM(*F{Z<<|>1NGp垚Fw)EQ};`7=mrO҂U~ \|@ IDATҚL =(:Ng8 (1=U!jz-6Jq |[[n85q ^鰖c(9luƪ& X/(+ b ZPiJbc5;,xP`40\O߰p8Xf;RfZF5zchH9S E6;TU;s-8(#JR 6,skR(Q$)p|jO>Ei'xUlNݽ% 1S&yK}M?|O-%d2ZY+qV3qXhVmfeud" ^ V7MiEZV4EJIL+gPIlA|PJ{r@-RhJ1 C,ڝȔ,'ebU%~4"F2Y98bV):QWS]ס$0A ZE)l0>TŹF".HyrHo.^臖 gDLDt8*Vn^q8}GX!grMbHR"0PYc@ 3~7:8=[L#!-\رݵ#m[H Skh"Ir w\[u/U JRUNӭvR k()r>=ܰl2^)UZgiD+\qsswΧQ cRhۖ[b1.JU^,|!]FAc MkHc!̫?OԚi%//6˃p.j 9y,UBH i#(73\^^pqw-W6-˲B-FX38=X躊 ߽{-,4V:lU%EnlOXȵrr[4%kЬa'?STc`1@fYȪ"dC6|SƳPX1rui$?FN,lkU,B0(sXKaRܾHaoR umo)p> 1vm d MӣpdVh˄Xx9=r\fJXZJQK%yRx"3ƂRkU21Ǚq*X )!΅~ `c y$\4QjXIC9z*1խ@_UEPVa"V㈳'z]x1%1m#pD 3•IyuQ8RLSie'5gFi4ϑ8$BN:C)Y*-,XvF6Z5aeDž*/EJQ-rVѴe0ehS,ч"cD]a465&@R"PKdw\I 7U#mI5lڎ۰8ei58\蚖ׯn{w'q.2t"@ELee5}st:1/B)'RFۆn˰ex~G'bzHZF)?wt;:_{MXxuay<#Ud].'GX 7K64S,a wqS4hW~2N(eX4A av~|S7?|KmL}\f0t8f'2J8PҴ K1s:O ?ɏǑ__`͛7DB>qs%~8NhcB[rֶƒu-:X!RLE2'>jq|)qoZL%`F+\_r 8q9QYҸ2 =Na|r8߰'4JKX\_ɌTxxxDqhiچ7,͋1Aڮ%B6YT-kTɹ`fL4TrM²,t]X10_jgϜyyy1?Ngxݮr@DU* .cؕlRXXuV JZOjY7R,Zm; )h4z~~M U5OnYB!LJeI93N#M|>G ~Ϳ"#M[;HkXAi˙W{=sKi\6D8_Nyb^Nh).R,(btOlv?t brR6x81gJZ1( Fy)0 /(-߽_3BʑT*|ۭQB\S`#,(CcӴJkJ\@.e4!28`#'j% Qgt5)-?xq?_` 9밮,ƣM4Τƣ#%e톮UF3H*2M\.޿c}gjwM P Jyª5 XӲ^ᚎsDH Jc\.̑$1 rU51.p5rZ=%I}rZ{/˼2zQ1x1lXeF T UHR cxkGUKoE\Ӊiim1Zg&EcT dOmR'DN-s[pW6h*9NpGQ@Y79V\%ڦc74eN Xkh]'Q*V`1ηtMO~|8縿\0 Z7PBJm駯x5MB-̄PRaF}ik:6ԜxysvroG lŗ& .ĸ-2H31](5reY`4GgܼTC@U JW+vO;LhyI*;B8X@*y֣e*r!Ƒi:Rl623ϴ!,4)I%t>q˜wlGgn{8 }O߮<#1%B,¼(2Cotp 9Dw;aTY&LL 5l`xj𝰉|cupp=77xq q&8,8iQ+y6g1u%*U4 s^yR)Tf'dq4r.Lsk>zJ8S ʳyYS(LU%c-:Fb8KnYĪ%24Δ1Jc4XF/4YqQK4rdj4DQ\(\o(ppsk^]c9'5P}V1ky¹H۽zC&mm[v-sLx2hi1a caV؆-y%kƌ2 m˼)Sq4^ll-2-z).T'0ȋ1Ԉ`}K7 げ ͖✴{!FeH)/VRל␲V 9Dmpڳe~u3T.wwݦHHnMD D K "W RX8Nbbۍz霳wx5s:@)ΒJUuTg~?dQ\]\r~Ci׿U{MGH#>s~Xv︺ryaH8auC۷IZ8;_=pBi<躖%hm`x ebp]b"9 PXjhWgvoHBD=f&B%"F )3di)c%)-}wF4R9)L=A:," *=n`4}󳈛 {fkzH lk@p|<#h[>3$7x6!:HB[1!tCӭ͚3jz*)(HnݜHٓBFa]+YQZ|<dJZ !@ֺMCA2:2ٚ !NR45h% 6s6]͎ǙnգoVhդ\MF۳ݣn4F'ř1&%9  }{udD^ܽ gs5ĄKa6PZDiٞ1Ϟ#txb} )ȂNTSQ-feQ*c{jp8wj6&t&)B/`U5#\qs([l1E)fd .ضIVc B5V1 B^q9>&4?5E2TDTZh3{r X#ѶŹPu!Ƈa\%J nN#iDJóCE!A r|Ǘ #s!A.@7Я{2É9IO4 $@B#Ǒ9 $i* 5!;V4ݚa #Yj&(Q-+W*SkHӑ-XЦW=mk=Ed6gkR)L% @-6CZ qcF%2RL8Pd! KcvYL R4D5Yc0Zmtǐqahe8^>烏>`m}ƜPnE<-&;DU x: (U֐4E*`D+E68oRMx<$8zզJ‡R={N"BhP :voз+NDZbJRI/m.QEJMH3sʜ#)5T qi YFcZtZm+jEZV[,9YE$s'vUof۟Sra>Ē,!۰ةgV1E.:DTH sжE 1TF՛\^^b&/3?qswb>k7m_QJ7ҁ@p>LQrQːXUZ5H1F;#y t d"ʼn"9:J)zP?bF M৉.6/?x%'8OCznw'K#&$?*] = bn*<{LW@.8LIPÞ 9kNGwpqb~}3?Gel[n?d<3N{\أu$F-VmMU!Ie!Bv SxŧQjɁ;P2^R9]' ^Э֔j+j`$Q;l6˫+l2'q$<8RF`dM s'Ǐv4:q<<08@1(U+EJP>mӣA&4 Ees"OHE3VֺQ)V @CBX]oݪ[הg.a>pr\wV&o  2#nwcV]Ծy!M0ǴK=PVF^B UU祐C|ၣ;2O'H{JHWVvq(cAJB  Jf{AcGԶ!QD"SH) U(DKD)-!E(%=0[HϘ摮YQHBEl7W$ O `љ"@ vGѝ0VbQrmǑ{( LJ=!8v+=RM(e"Ck-`IIvњzKc%(~H $-g]/&kڶnC"/ l2~ DYF$<{ 햮x8I)! 怶 % Jڞ IPqB@N)5ծ7KB5|uH"sN cGWﱻ-40ZM#B6k-stiّUE RSRX2UvYj*k-E: $(!Yu=)xB򀆔.UqT_,j |=MӐ|5XW*7ۖ4swa8"MzO4Nba.zqQ>ض+Hv.v)%"dB.1 A+I$3nyؿQhKu@rZ E( c%2<)Ey-b腅3#wG2M09!LĶB`V9Wqk\}gۡLGi=|G#Jy D&hlFꖜ2s(HIВPmRiH LX~K*4c,t!RgPzs jaėFoq{{C4QV b gRG'fw{:) 46Yl6+5l=qxdD{HVOx_t]G7h[t-VSCg<|񷸹7L6"IIR6g}ATY.<8簍7/;%R;ri&A MF`cBUo{./<_o?,&b!9J h%QbKv}vsJg~ )%zvB&ͯFR&"F_S 9cf9꒗7/؟V#dY뤄%%EưZ]={-ڮ]^51F1N<}OrO>y N $Rn)' A!57 P"Fda :RwzrQ V+; 1oz(c (i: zDuQUt\lsvt:18|IɢM>k,[Y8n) Rh4pm?r{lqk+STs)F73Q4x_i2L_XNĽ'OIkzؚ2RgW|B5>P"E#{ƥ*fƲn;œO[6 #f?qpC,=xbյUnWȬ1Rc2h%4кZyrS@C ->}R wH-Hy&ejvDm@(2n#R0:CjZ0 իxDHd$GL (49UueKEO2GR(l[.),Tֳx;(#){}\mG(z!)%!v2d.jQӲ9e)P'R瑢^> ] lcDYJ\HvKn7TA*3?m.c?⽏/>^}~y;ǾS|'?%b}\_pb{<? tk*#ٿo[~=ևϽןz r~?8]Љ0wUҳ5 ЍFDP" EK's\dRuW_>b x=F J#Y.<{z[k'#! fx8Xxy5RNmz6VkwE$ LTռUߊ +W9g7/iF—Co) d+Wo.:݁TrQj+Jji9 `5|&DgK/(b27BEe+ QRit햾ﹼ<[InnǷx@K 3%@t<IU!?Vncڞ'[aƪ5v's cn1ppw?c\JhQVp1J#2e꡴ ִ( !IY\!!]oL~p55A1N<{/~P'<qMjɲV% ™sLJ@V`tp8cMh]J)n9=-RP!8N5.{6cL V]ZZ\6DHfC8?gH̎XZ׺D4,P"# 2yd1{b]GӶ4UJ]#yXkaOΟaimT-}dKN\*iۻH|@B|aޱ^oqqѢdB^B1Y*)W(gB Q8FXYu+"JE(M*}1#5H]]8UسjH>ݬ ]`1gcEKE*̧ -{ !/ *MJcjz !RJc lWۚۯ8⃏?bchxͷ5RRkj0snh;T3!j?yi@8B8[RRBY@Qǿ);o|?_Qz^,-1Toi&8_oFsH~CH~[8Jp"k5 ZPH⤵P!$dZ >9ζk6 T/&!ۆƴȦG ?+VzA&'x9BηW6p'Oy%_`^7ʲ1]q~q]ӥ[YqÁޮx1f4{B5(jPM=KJjKHE-dy;~cskyadGm,oyI'rP*C&Ȫg\v9Jo-ș0Y$pXL,LKABHG!ns~Oqj AgEIIJu3o(9cE`Ý@jUb1SAZun&TӴkBH\__UG_-1Ӯ S7γ7?[#m OM+Axb|iQk-%Z 4'g[īa_ÚJH)-lIiX JD(kc]Cp{R6s>\lxEѳ֯neXaD, IՄ2.VAhfpO;b[6-%+$0PX6ko+Ě\SB;F[J{xD5= E^6 !Qrˋ/*Wj $ |ʖh-VihLK!%xv$<=Ӂ}rEc,]#j-&z,UOW)%GX"Dkȁihƹ&1AXm1!B Ԫ]Z#q T9S9"~: C+ a^F1bL&OpkZbH4H\4Gz4%F6+noV SrԝPAH5C 0";(EJXUӚqPms}{T$볖 #$O"~̇~K0|;LH3zYZ\="@IHfc0b'|3a=o}(VQT D(ktc҂h9]qp983{_kEqy)nA+.oc@ fwHH7 8C( Q1NdLgZa@fOHJ} I>rq"P(UB Z 8\!L2,j"?$jCc59xs݈RZH mx/=~o|ʯ5L F /8!rAIhR4eNWxmfR-d1/8Af֟84,FϤc}%m(4q<\=~Diy;}o=opw|={y^ IDAT럓ANʅ+" BTXuk3t.>1:mȴL:BPbjV*J`R4#sJI&hԚqp}t Mfd ZjxqO^ޭ=7h"&ǺbT6<_r: $A)Eg " ,ǻbQX.hH|wl/0}KL<Og2Zu()PBb'띦_m aKr mnҭ J[>=@, " T8<@AIV5.Ox'D[rFҵr2 rLc?Eb")*Ӊt1"2&gRQM03O+%)`ꆲpx9bHlVk èqiDZM)P1c;5ߴ=9+.];ix8ք@Kf7cXW4el2s42&JBĘXSF$au Պ1<-@ɒ%%R%X%zQk R.4 dv{pw9$wݖ?'+>$_'&OX?h~Lm^%"U, x8u]b+C%*齣3;aF7-THeQ%e~0kY#1{*:Rͫx|c誄=V+ڽƘixxk/>0/ #?r>rw>AYGePGkLDx2Qh2 sH,DkBBL@d"%ANXۚ&0eu͞ŕ|C#)ZI?cT!g?>_jں#k-s;^~%.x=۶\?y3Ӏ NanyxzC f 9-+Yē)0Nq(!)Mq~q#;MZ"Y"Pe*ڔInrÁݦf^<9c'޾}[ʲ۞<$m1E mk=ȴx>|49w$jI֘l29`kb6|$eX>DvND 1D)?p<>I [yũ%V8WJ1"Dvsy;hkq^SYfS$d w7x N4apnBi0 ΀D&^BiJWyb(MSB\04iz(ÁeCMf/p\D=.:f7GH A=&2%$R& 9 6zJ :N;<25e wôd+Cif/}xz4݈ȅ-C*[N'd6(ZUi:R`ښ((-+gRL˂)Eb0}JKcX[=)%R˕1T)~)qB!FeJp%AwWJ8 j2eZ3N#͹s^?kތEHQմHk|4,2bHUpdX2ʠlJ+Rzi.Y|ɡucma-ns4vöBssK"M8CQjǼ9?b ִ/qKilVFeM3M"bq Uei =aTZ ]q8$@EPS|@k4%/ C039{ZA9mJp<Ǒ=9C@[ >staO K֜QU-$@f.!rmy.m)"Zck\"ܼ}tg|%X\ %B8]" $\€ # Е-)ƹx+H1CLHVAaA(?QnhmYg^[=L9#,5BSi"Ee )S )%rbu!jהz3yHm02(wMJcY@Y:R@Nn)iQ\yCUiBѶ2X 1y,DVEWЏrҔ&\L *-J"qD*\c?0,ҔV+r/~{O!ηm$!>=VI޼6ݦ/fh-)JTk.x/?-]1q:L~\\\i0+wĜ}b`"Jd|J5#}p|vK jykOzwGӵeӈi7"bgZ[JR'N=QJʐ<*RP9!^`ǻjTV 6ui"GId!pg;6\dXȦUU39(Spψ mK,g&Hv=YTin?̏?;I>u>O*n綌H?ijE5躅Mr&.+;1yAق"4 un X-YƨTTZ\HhM$c-~ `gNאBp|/XHiP i4)zZS&g/bgϾEYw-nI%brWjOJd !%Kϻ8Ѧj-ᔀ<"zᄟ's31uoAɚyNcQ"f6rDoDȀ4 %=;ئj`F!4RZz5R5HeAqvq>z雛|I+sz*H]6ɔA*ӱ'G0UJi..Js9Xl\ Rbsw?Sf8BkM/e:"rٌ$\ JiO븺~5? O=ϟS HEfX=CR8N,ˁyʡ+[ҠdP1GeXL]2_CЖWFkě7x Mcel&*tU.O(S!<'TyXS}BDt-J6$) aa}D*[ZsQ0a 4@>0Li$]i&bHRFiXsY76E" edY3G6]K? t*@ʤ)`-Y-nHISk,1Mψ1T Mզ#Sď34F BLR0Ϟ|>@W"舋%~Sy׽ |Ll.dL/5W~?&?9q 9BUKSKʞi8\`qpR;M'B )Ya8bG][nn>RW!.x|q "XQVRSejcƹ !#9{R{LheHq!ԶAytum$%Dv"xѲ#fKU_MGkZsEDIb{œJ[,s$ ޞ! uuG-f︿xO 0JDl)3.#4,)n4!遽\_bt4.,ޑ3 DLM8?1P |q!$,倔HXa5P((. [54]02"a`Zt[.ίPM HPRO=F*ή@eИp}x57ٔk­@gDX]9FNFQHY20a>o {g湀WwJ(4J,e68?B$Y,)S+ҙ"4u@ ȥم^7"r) wH[tO}XHʎa8D(J`".$K-01s*qYZ1 89,>ڊnρͅo$!9Љ.<!4Ĵ K)R"-9 `ԏQ"S)kc+rcVAX9{KWi BBj^A8U\_Y"RFRZȮ[MUY~[{4a,S23 |f:9 - 59,LUBXB3q@䄨ϾKYwX<9vCjI?@ߓ,n!n~ npn$`#s%#2S=*bZ*sq^譒s$]BV%4m]T,˄ h:#)D`$i K..."eheO>ݠ%}=22y1h_YVEl^+ V?"ڊՉrb&|t̾0e`Ta|ɹȓZ.%~*IsEݟx{LѺԓPsAk8΅'R\{WFTi%)}+8O,K hDnȂ]K % L,e!޲5s/$R(.Xb`<{l BhʐB0- aeI*r((oo^%͟ͿChyFR7+3x$BِeOyap81OEhUai+6[={T܂1_oÂm"}۷o94M蹮j\=z‹"wn{wl1r.#ĻЊih=VYv)$l*gHan3)*j L") nȼVn$' 3y%ir H҃ ֗ZFX..yO[qh3 ) ,n"FBv(#9?pxx lV%/Dƶ4+ETIl /8m|OJHairHtK`5>fփE"Q@9f춗 ]`I(Si\bPec\ t9a ZIK$]\_VR"WhbvãGiwLˈ'$R;">&XNHJUc,"Y8 )(%R0 ʔf7mǟ۝вVb.%ZU$;B \D'ՀJLHỴ8bN4TBR(13 բF$1"]OqukB91L$憶cmj.6H*23RKvg{޽O͆'Oqvc`چZkjcyw7q!';]nyͦ|U&\\Bf/hSb^rIQ5BTZW'+s IDAT.H)p%o^MH <ֲ_8lG }%fMna1I%=l 1 ._Fʕlz9W{RV]wӴUe㖉7WҨqF4idqvk&3N2ekuw/)af/)ݎ+?x[+}1lR@ftU7Vxxp[JXRb[7X-K<$2XKWW/kq6t92/4(1|Q:X)G*Ȃ;uU[SvzN bZ(iFbic d`ƕPB BNo^/Knnn%҄GEyr0ʢn*\dC`tq1qOt]kTV%ڕ@EV"T"q9O-> 4x9 cL%UEKy>O֍#8k$L=Aۖ3. K8wiD*lH(IdqLs$Ev#NÑ~cR"#Bb8>"-SIJm{@'2XBY6 뷯 '?B^u3MsIݴ!JtU5 n 1#KiUPUqYadq&f407,K,æ8|U%21 ȆxϮvCBdibZx8v|7m"(%KkI]kںFiA~eHvgy [!ʤ"`e1s(BftH)h*OQi(]ס`!`)5ZU),]*)&l%a%zX)|RBeDK B+M!W$YfD$B">rg늮=N |o /?!p!bks^<>/i;̴r؏(mR-R)Ȋfȅ}Tۆ*m/)f|θ%m4%#4],(# >LU 0Fkl6nwK1qy~mWc%i݆;bLs)+\(q4&xWTj6<}%c=M,D6 34qGnoC΂ڐ=H'=O?*f-_|_a򁺮bru8pssC&rwnP2"ԕ#?~پp80AStJ|Ց}< XS9 #8r< 4Mv-صMSYv۳R ;H֘/ (xR() )t$Q@^)/a! ̺j,E)'Bc3. In˫_~_#(|\FDȼ]t $O+5YWKvے`O)4i6HJ1J訪ggqSq 1D*=D %) S-@U8j8L,xTc~cWrJeϑE& O޽^&%JQ/ 3YdU*7[Њ~CeOxDˉ T§ wQDiʋ(E03G=uFXe^HF Uu5R#B]k^[:"+(ReZ ӱR5Vl65ۧ[N#<`6TU=|0+HD,cgB!4օ?0#Ε#kDN5k,L~\݁ dzDKp.Fꖘ2*e.gF'+n!x\JȞD7hݱ88"'2G Đ\(2#y͞#7o>'+TJvBgA^,<ݦ§F|V%V^Htޕ QPFRw煙N mmJ~mZ]6?JIEșFOصer6 / =hꖔΏ,ӌ[v3nPRpA@$ɫ BIS&ӬuR BZs𔔓DId! 0mihϱTySb 1BffT)8ʤ,n`mEZU!,ʐₐwQv!6(X."%g*18Ƙ yzCPۮ|9;ږk?z/HQ_竗o6$>&'Q|3C;gb踾zƋɕCfy{-"*y'\]_,q[*qHsy}Ƴ'|GltFȡhQ:iZ?~׿ ^z4lw=)뚶`M@3AyvMi%*i ,F[6-W/[+Ϯs)qVxG?>֢4SiKM5qY2U,h-h#SZ+#Ц&&/_rg~J]wۇ7 Co>tfn}vmf|zֵ.vIIkJ2 HTGZJbVǥ ;I[WO2rZaCrk-*R+7G u/KPJb*p!p++"2ʙp$5 Dݦ&L]۬\4 iq#.i^ Dhm Q!|$ ɗhz$Pj*hk-J%>͗ՋoŜ/+e~ )xz[\>~_-EJN!9tEa 2e$7=p>PUo0շ@| |Ov wׯ~a\TUųOCj2ȱ?pm{3RX}8Oy=w}-ʖe$4Rj.ι`C?;|}͕)>ӜZA\`h]ٞݝ3#BNi9&ekt=uw(M2HN8?*r lK?L4e"lk.n߽a^ӵ5T@]vLs4#BĘbCr2- NP"9$tei3nϓ'OHs8=-BdbZM[9yiY4*ѵ-?pi*ȺT՗"?#USF @,RY[BY(y.@B!8;;c3C %n<)ŕ"QBGrX&Ǽ j~\975ZYO XVDYhհm6*4Ai趒jxwsϻw˛30Dr*eY)ubZq@%Sd/ TBINCՆm#Zjؖ;$^"d&@!"V>B6K8-VLkkƩ{W˶=3^^7[&>JA(ĆeÆDAĆ $(()"BY$U*Ϲ;b^OcFd*u8Dk9~27MyсEҙ}@$2lr(2$s'0uw~˜mЪʓZC8-AQbZN9:#V(-) !D 縕u rUpn9X1Q<{tEߏkQ8M4: ?E e&S(@ $X.,6, <.o$@ꩪe[#7Ttqww0xsί퀝<:Q(\\<%Ƒn~s!%E>09'Osvw|Od .B)iy}o?brܽM=Sفv8ڊ?Gϩ@DrY151 !h%ZD ޣa#HYMM &ź]P5a׳#c`qQrdbvʭgH)yhzTᠬ JQRCgc@iOsB|bP|G577\>Yd[fNFlX?`,lrͲ<և? &Nav{'6D;!bDD TXI)&G o!dO+ 0Q%eac3\xs. 9!Ѷznt߅mib;PNDp[G:B`.OwjALx٭`ԄHʅiRbmOJ9R\ج/3ϧއMZFD?!ddZ09f?/?ֻ7},ۂ5_ۿ-\8 (;LY45"&t {Sv_\h[vv@*2aP"U'&CzT'87^SLh%)O3)Mbo^G'|pCOL#Hi Ki(j6erh^]/?ٓ[[ _?;;?:oI36qG nnO3) P$1a9\ٷ>͘4 XI$..Z3aG'Bs}IU=j&seYO ? !Ɖi߻ڶ]V(Yq?dtucH)vK- Sh賐<{2]dPIFHW8m~Xևɑ#xG@Yct$2o@&rͥ ~w;Q ^6\lM(IBCWP+N#RBtZ0td&' 2Pqp-et݀zI7Lt;E3gMQ7ׯq!jIvy[NDKn] 0ŊbAYԼyaS4?>$F2FJ7MO:"[bpw2ÎTh8@8y9θ~| #4n?_^^R Klв@D0GG#ڶ9-v<ქ%Rx)hVnoŒg<ɳgO;[#&6 # GRJ^Uq8~qM<\O7{E;T@In]R_}3WlV UY|,C?1b0mS]69P44$JDdGnК1ue2N=VY ] 0.HQ1zs <4cwP8X\__3=efsTMm"GG(5k.8;#wloy8r<6H"aۻVg%7*IEq.DA̦1R$R~>8#DZ\*2T2Z K2!{ֻ"1 >]dgM=eؐpcJ5;Zdn&|̮b Lv,4-^!Q0Lfa%~zLC=Wn4a!yB7HpE*CUi"DI[4^ܸNwbjW\5B˄6閲`{@*XT:H1.+vMSY 6._psGH54M8ɡL6(8NX.y8S/+=_}h:?'?BjԄ`y9)jj/n_D J0Mn=ݑ^R `Qqvu~яo#L H IDAT/H2a%@4N9?Ba4kww, O^<#Պir8h??bflY,lϮ3noyz{V`%Nw e4p֡U$x"qvf>؏ig,8b~ a<[+F0EA7E Bx4P0˿C>8ZU E)HLzB0'EK])(R~1Ų&x(]y1/LcOkdQ dF 1Jw/@Y83'O=($Xϲmf /_}7~T!F8aU!Yl<~r. TQѦ$Ah4m}4J >c~ "g@i$ޗ '4X!łڴ((嫯ݞ=]\1'݉ɉ0(UyM"3L|(%X/B=~0G.Zo],X > TΠ8]SryyNU|OvV E^`|.:n,RRf3&}%>ywIdl& Ɇ8Ba2oɺ!bE8 dmIDԷ7-'O:G ~GOHH]Oh q~bْ$ww7N[ ? S*P5 worՒdYҴ5J) |f((̐*ӧoi&FDH 2&$%"7EGwmCɠ1 ϛsDdڎ\d4MJH"FtQ3I$4*uv~p`,Ȁ"RTNI @?r3ȺY fUmPHz[;Pd58@UU6Um+ym޹ߪ좈1RZkVqawbMe("HO֎<~G9_Y0,gŽ-Mk8Xa* \eq؟кϱz+ơ]7Qpv WO.(׷x?df[!xz`IhL4+bb9bYb;suɡ;N#Mkr3i$Ib %=oҀ֖ D)0&Pln;8l jA,&H=bgt#{\w"P!iרBe\ qw:;Fj#8lpbT-ww=SH| {""v D> Uɩ?ӟln0%1F";DLA.IRg\?G|݁պ}Uvxg 踼:gל E)H2b D0 є9n`9`Zd>ߘ{QԙWqn_GPLQ6eUcgxW]2ؖ5/٥#%Y$P5%zMtc(P*}10Bȟ7Dri^ߥHSPSi}oi FyC 1NF)+y%u]"e )20 bïP&"Ds"eR, >7$w{qy;))RĦ]'M&` !G}D w|Aʌq;]ߥfy%R/Irt,kί!dt#o%m0Fg F2U˛{|g_޾&7oج֌cpvKi46|yxgO5=#?RU528e]t?iHI b$pv;h yQ*45YFg^79P/~975Z4dh>W>`t~r!Z7jJc$u.8]-xؽ `m**푾3]yc8܆$f,S>>@WHȰ{KPل,I⼤ikBĤh.cn0e{HY'H!- &bE5up;..Ϩ=pf]pyjђ|@p.;y{ew%GY-IEx)ͺZaa7Ha&tE+_fa !U7@$A @&I(|O# %0&Dw0E6=ͧP +B)%Dʍ2y#Y,R%TW2L]n+3'2EleϜ4fh-Q$>zrԊa 1fIqܾS.`tM%Q84"Gy#Oӿ9VsV(dAQd{dDaw{B)V5FhgB5xBH rPt2v= ɋ/u+1 G9#"ae?Gx97R#Jd$ZEi:=IF*S%O>RNy<j2!dL &.8o3RmqHL#E+]LN9U S( x8,7?aACDhIx;T1s=z>ٴoo5w?G1hDy(2 7)ߧ!zk3;&&0U#E>ƈ˫g|Vph%9ݞkBNJS};(M80l!o3J9cD"02'ELz<&8m@fw%R}/6 h3-Z"@$b 5bI aʑdQD%c1̨e%I!OާR ev 渵9 N-L~8åΓ}"k,dSKOi B$){:, N}T" !߹@ |^ )@J)al@usO7~GQW:sg}E٬DsȂqg-UQ?q:lsx#UQp?1 eX654^oX9 V%ԏHLb`?Nf'O_4 *Ѣ-5SK={J@ʚ7onLssvQl[VUf=z6Ǎ arlBo}x i]xA* ki૗_Z/ac -O2^ :>REȘg^ӶKꪥ]y[(iif^q<<0yOToѪT5e$yKBY='O=o^},ɍ8y3RGLPk~KYt`)*8lzjMawd5WP2׿zHIՒYQ SH\LEY *TLSl9wL *j0bG J UM.WGn&GÚ!"|fhG(GORVwQ1- eYbd--1M#)2H2C(*A_jSO$o1%qD|@kcdEp\~N3.9 Xq`3)E S.FX.pQ1NBknoۆzY3 aPJQ Uo8Q%ќ#Ua0ڼ 0!,!Ej(P؄$6dcߋm3'x Gv~1=9ELL'$Vt*R9b'VehK2;" 1KPun l, c︽c{6lTMֺP0#F8fa`)"G+vxк,Sa.O. A#rC S+(L79$qПH@i1,+\9uLdH@,ȱ s~al-=R9~ 6c̥EMHtBAYiB?Zk=Fg !#Eq RQM "?N#c7R%UCcdt1Ls+anz) 17H/B`]ORȢbcX4M)Z|mo)1e,zژxB;9bxssGY..X6UtbR"&7=q,@ GrŒe6Yk/(9;hc ;~!) P ]q~~NytN,+(AQBpnYm)ЍnTM?;Nևa9W;Ko =4GSx7eUК"Y ys<ϓ\t-)ד0яvr?b~BQ4t]O٢$%i 9WdYБ%̇{7 Q4t$,j3!Da%_Q{R/W&a7kV L\0NDՓy"~'_`<`̀os jb3c41 J{77[qţ |yo' >#E`\aJ$a )QsoGɎigR@V)%EQDB) 1P6)q7Wy~(zӧ8ٝ86q:YfwF$LRg)r0JC6Ua&i@ [ab D'@jHdv(C9t]RzB8g>f\QsYς'dSn[w #T͂tH"v6gkŚkHY EG9 k9,#·׶QDL9I51ْq\!}1 W >c-0`LѡRƍ󵊳$AD=uۀJ*|°!ٴ k=. ~ϑ meҜՕ#K1FY3x˟zY̡vk5DIm^u BJ $PJB] D q PU}/~?n*O~0Owvw1Cg(Y2Vj|6m{:R1EK2V܂i@ef{C+F('-]7pTE%*NW((fKnK))S>p쐡&Eͫ'>9[]?_%J v,| JI߼k#>L^9eZ|uf3|$~w˻Y4$O\:Pdu`Ykl^RY{MCPKNu!zi HY,Yrq,_~~aR'A4HY.Qe#tt9X,d$$fnaXZrU1 80sH`hφ&NiŒ~Ud^8e6J(z/_ۿbtևavlUISfF&AFNQV4q$DҒ8O@Q Tnu"x=h]E{کnXO<-[C8*bĥzñ\0o IDATK;я[;P(9}7[D690[M O(!ewBzBIғC*93l 0"tӟ6@[d+_˂Bsapw!6bgE R$qIṘ̇%smk T)[Jqs)D*M]krIY o;vD Nպ@+EB<}C.4&T9,F\ikTw[v{pԕ$6C'Ǩm)C ,@Qkbu)ϱhJ*Bt qf9%)rbG7x$@yRbeI&b.^{>L)ƝY͍cȞH+|1*"MU` =1Ik|d*ȳ,nY )U7)L92HLv,K!@rggI"0ZKl!{H]Gm|)vE 9rw'xKVպaIqb-P"Jb8կ~##R*A R D u br6GzLv09v (ˊf]38-EdG0t7K-%Zzm ω#>A°Z=ŋ( n~F1.){qޔKn23s/F0zǒ3v#Ee(59~߹9ӈ@πbQ8ͦN@ϣEF{iJivof4#g \q躎vP wL)p%k7[H⌧O>Z+~?`tg~%3c!bwQ,fJW$U$ڂ_+k_&oܾ-UUg4nj82451%nrGOi_$f5?37WUtw&c(AJDBB6HذC"Y 6 %b ^AlNbt}}:UL~VvN-=~>5ndR7%qlj]q8R0#TF{|.%DJwh!|Ѷs#)xCwd? 2t[kIU/ CI2enaBHn|)ClsT3f7xv Jr&AE>?E7(Jk0 CjJ!!&~{[*$T2,RB#l~H{{^o?dJHLϰͺ)Ѕf$ͱ|$18%IUA`qi"DO ]T:{"4+@D!uM""eY+!H!T-Gjyy?*6嗸*T$uJERB.Nh˒urq^;X$HFh0I R{lF cUÎw5$GDYpx1tA:jlp~A#s⒢lAHlL.ur}4\)r@iwl/GOQfA]OJj~o^b'`c>CJM'C78=E ҼiU$"]q.~$pjgmKQJBoQB(ʪf>ad\Z(?Ph+? JJVw?d]72D\{#J)\ȑDIa?͓lV$)qs}sU02 Sۡd"1"gpd(HZZ2矛 DG܊ޔD6Lbf (UnD:<ˆzz{ș$fGN{JhkҤ`ɳZ,-82w&gu>puEUsqvN]הe#}#~B? A]-1&Tj֎^SQT 2td lgWNJ5ZQՆƛHD2`*;Rtvd+α4s)Pu(Ms5R.jVS*mƉ(Aݲa2+ q83 ua79Ŭ KV2E%R˜q̰{ ץ+]OM#RW87C#VJ,gAs #@q6ОkG*uV%Jhr˝B'*?N#bhy7 =6B:H"2q'@iDm Qc"ogK( ?2Qkr[e ߫Pz,V.q~jņ\^^"UN.0yuN%R,ϐR526|K_._\QqU"ent .bT|C./xz/kJe&rTu=W/NA.R3Y{nG׏pcox7tǞ`<8X_\ ND@!(5 mn!/%͒e{ :T]RWeqgMZy&q,E99!?qFmxl?O?0d.,B"nLQ<QzNMnՊ,J\=p}GN#˪AkHm R7!(ADaڣoZ ߊ9o'䤘9).s\$rtU(&cRi&;Q %eR>X烄D(7>`yj9^3N(U fF0;EfkR 6,5Sy =sv,-."Q 8Y. BG(%Z&&m!Q1ၔv#>EUג> (4ahts&fe^~qlHyH -D"ydJ+C-)L$ʂ'hlCv GYfSaW$!H,Q_*֍CGcGOk(Scv5Fhali1N<7RbE.Ц5(r?l\'("b$2?%]!S!uI.ÛLp9v Fe\ͭ_F k}~PR9“2C,*mp ܚRQMSQXnnn(+MYhu7fWr Ә|D[`H5>Ei@(1S'+04rR樶Vw$xx'hr93p&RR1)e!6 $ k2''&#NM$GȄsy$F Y7,1EI]q!:(dчBKyF]RB y1eOa*TRdskV|?IgX >ZwרBP\f!r}BEnc1 R TY.хbX)owTUꄾ'R[l;ao^ 8,5R8gizuj/?awP %Ԍ}R3͍pewGg?P_|yãK~j6;~GVt>HdFJ%Fƹm#%aDYUsYҐl3-xٲh0)%RAB\EAy}x]d;C҉|3Q3NRܾzfɊbS'q@xnyTQ"t|19^\˳uxw`7zF|oQF D)Dź}G]{{ ӑJ(@ԶH@*W*E,#'5o`]Osۗ ?i?U"PHrM;2!uMV/t}n(`(y!F+̱ ]I U{;N82䖔3JHJI mP"o"*b}T!2P 42{ǼCmUس;6,QҴ]K01}!d={2yU~SkJ&)6aq1Mce $[J%0"2FO]y`@Q2$+9n`P#R7$FdvLv@`/x F(˒le]Ě~)b"-Seф"8pɒd`.# u >vI&PPAsܼBh #@""u#HLh#fT IHfQe*kBfP̶j="R.͆CgX`7Jd6%ReM$2SՁ"Jd!)B܍>ܚB$QZ\ QNO]Ut}OB7, .;hB A$B*XNW#Jf.V%[mIY,1fqJʪ!&!E, /A@Q q60EOH#MSp &JdĦ^D6p'|S|8p8LŎ{?X,ψDW 3-Un;!gg:^\?׿>?HwܰZ8* @7d6ٕ6{ OY*B -SŘVyxĘݹ"aĉq?aj)R9@ 9Z BcT6QszE ZP )5x|'rJ.Õ=Ţ _WAC@qu|kw~_k]vm+(ǏX-ՋO9|{ߥiV, ső&qsi=Phr/\}yO J0z-y7&\thId~WYT"&GǮGJI۶dzp+aw wD9GԌ͎Ю*Js}?p~~jdgKƱpR2~ge*JHbA*R1 = e4;)FTX( ˪Œ Qpyv%/p{w<_7ޯo9oI2ׁA6&۽% ?PIB#( @ D?CfFLls)s㈛ʦd0pzdOq07zƉź#1 vOQJ(x򊛗;JpzzJU6i-Y,:dgF* 8,< w55$IFP2ruEMIR,wQ#eZhՂŢɱ%7Q ( ]0siQZ୥m4FMwפ#Hw8"hrGkԳ&ɼ;wx2)Y~$"F|l;v`?t l{\)Va!5dCUrn!-> Ju RYE8TnI1R%m[\bn[ _؛4C(fs 큺^g'&g)MESQ'=$nawjyB8Gt4HU1vǙVOX"Rxop/H!G\zEaZXSgUґ~z8|6ɍ279PxvzNuh0tZ Γ#DY2Gͱ䳠1,hAB,Ĺ9,&G@D]+J<)FzbHJqg۴+=E+0,p.u{>8FbHBPe 5:W]:R|&k֔ܜ3sd}ƄR n8Z$07 hdVYYUR"Pzoq1$1&[᫢͇^hB DeM9fv_6Ffѥ(*u(ՠɲX"XIhbV N^cBpv.' !Ц <#eA5 IDAT3>#=8rK";Ʊ "n, u]sD")yB~peˢZgTYJ`>R"ña{0x\>yZJI]U, 1Zlnl$Q#m* BUUqo2y##Cn1:6;_> vo"鹽}Eb{+|'Os*q. b8QJӶ #mM+}xA`4=j53vcSٝEBB],[zlvӛ "93Jf`-pQ,P5u]. gDžIo vr]G!CYEd65UnL! A[TF 6waKO3J0L>c7|##1ʥʎ}vyxG 57i0E'ӯՔ;n6?RU1mr8i⣏8$,.2M@>xD8o: 4Uf8~S- )jbPJ'G?bַg Hd7P5ue(M)8ؑ Y,NhV'?9G;`r37MCy|y䔪\3K|EvifO~r}AuT!^Q{֋Kkǟ&wnd䄺Zqssv⊯cV'ep~ 5Ϟ~ <ácٲ'''tjQs%nnHCc 4hh@ `C!h4,7"LA}ޢD4嚪l7 ƃ{T( 袦9<hiSD+>J\VT%|Oڪ&Ğ?ñg*Y,VEEꪦn.?(}=R"[YrAJROINDeޜB S%JfFIe¥ ˄$' w;|JIJ(uT'O/m+8a'aORmЅSdBtٙZ@@ ;mSd0ñ'd ؁GRgWmλyx8<$(b"GqfkkF@d 1T!探3HUդT()̂>YR:Fy(#afduƣ''?#Yn#|ЉZd!e(rw#":J(YμvwKQUK>N+/VTUR)9*'h0 eFPFuI 'rp<$)Ų]Lgq(5I.mYR?{&owW,54KyjސeAUU,KCĺy*9&2Zv\E,BܺHIP4PU J!aFB, "R"QF*$"3ODU)ӭ(KP3gQ"scy`z .:gԹ5(@a-8mtfؐ!" mG/9rf) H虌̱" R(.&AÐ>T$ K/YNt.aDs\+ǎG)Ǽ6Z2Gꪠ, a?OeɵԺyOU"GEMZ=._D(2yv8Icf7Brh'zaKY.h -ybrlwck˖i &\8RT4'?d& ,~%فw^^!:~GR&._cn43ߩ{KHZ\.3sRv J3L9 j@+u}r[\}"1*)&?1E0ڠe.w[ Ua)?$1>'lcr̦,CkzɊ墢| nbn&G 1ܓ@CeY}$ F+b \H]"!P ZiLbly÷ r;Mk(5!I??/O#r~F_ˤ9/*;\ <hje|9~?9/\.99[λOno<0mCyAڄnƲ,8j/xn/}?kxQ Z%hpÀ>X,=B(|HcOQXkߥ쏹;?\^>d}f{Oc}??ဋ3#O~0VC%eMr#:W;Xs?g~ c ׷W\S}7) qxj8?]PW-[ZÄSM`/s_dU闟_D'szF*"1= 1<~z+Gx7"UvJ9쏈$0JrW7='̗?{6woJ-ϙƑ2;fZcDAJ9g'GOU9c$!sf:fdHYB&=f{MQ-iW,5g͚ۍereҿu漽^owB/ỿ7 0&Jⵅy씈1DH"`BI*tF tlΤ y!f _5w: B 2eNK-uYRB)Ti#YLe 5۝˛di麁Ǘ> -h%QH|l7nL¡SA A(,xtyС5]Nv0F,dHQF% g0brHn^$jh}r w4HG(F ݖIaa{M p<i*1FǼM`)eCaF&;#esѺىpd%C$ƐcxJUH͡ˇԾId+A-b<'H!@Fy+-h"ÒQ-!EV5BU(BXi‡fq[ӏ{(1,ROӗxj|oGB H"ٱ1N R8ww[4{'ʜfd Fђ=>̭oA!$A F7Q9w'8JX_H6׌CKR-ii+0'c8\Ǵ=X44׃|R3d]SC`&zm[q7QEYf㮧iBEݒ(s A NୢjV8K4j#s;bǁT>3F;$* ѱZ64ip6Rka]H2k/gƓ9(ۡ513;) yZKȜ%UU)7Be7s])r0 ļZOj @*4mU#M3GCz#97q<冟e[f0! uVy(C(4g'u.5F4{yJDއ\}x30?;THԚ^ж-χRԍɱuASD"o | JS%?x_wӿ|bb{E?Q5s,K?v>B1x\HYfT, Tw[| Lc{j0&GΟq~u=gX者0 ='50Kdy }#m!n 6CԧqYYargeXyqƲ(y4ȁr. i eۚ_FQO ؎O>{9f˂rlQAx\(*V1`G ;sM Ue5N4t;nsylA<I4et"W|>kG^zG?ìnG%Mr4ɲ*y#㋟}I{<%_Wph?W^B./٦aE[h7o>Kil ۛ_G?{̗)u'%Kap~~޽ Qsقӱa?!`pGSG?d^ՆȰ~_|SFsBv'|y5]סF9P$q%~~tZu]S|[`6۶kF/.z'|,4]dΕ)W,fKvX.0ú$U!G1fQ 6N{4#CtOHz!D,e,IpHpPB!qXOO`^-~( 5 3t!B("G',UU)"8fUά9;f՜q; e)arS;#oBlB'6urŗ{"%c "HH<"KSTjKUQp۟:syq.go*PV)AJEHAf!P E5wtT#s˱S7vԯ:.L 3v آ LCs4UWYd؀' ƍ ƠA8X:" "+KRg> D$؁tRpT ΁yVa>byr?qFQCbc RJ"@S9U w% :;bY9J8(X&B@B_9vTc^it*x gk:6e>>@@ЀO@b$(#CJSUFsD*z؂L)ْ< D6VKvDV+Vh/YV3DeݑJfC5syrsƢZrԌ *అ024-EZ {lVӷ[B7G-ic,Z9:Xc~t@.`N3 9(H#(8X@Yft-vl δ;:Vȩ)3afDC!ȓAJQ-Bja$sj|d6[q8b&8<nj;~_e^ed*ɘ͖3RW/nLqP[ >th Ms Ziu11$y' mJ H&Z,֛s^^kXbcڪi5yIY(r,c,vHݶlE$XÖ92w4HH_)en,y<*|-Wv`Xqww~zXsqqOiO#dF1Iܞ,=;ʲQBp[D 1=y^д=yJ{l!KK(|q UJ6KȲz 9eII2;!QAN VZ  DnjHh9h^x4avzP/F21TUwН:,JK5$bjy UIA4OׯYWڞр206,6.N$,JDg\> 0=W$> ;D&R"q&/BD"^)NecΣ&*r8B$ Md($d"F;&|#Y@Oqa K<'E](U5s.6'n?t:RwYԔYO<*208H6!5gFAid z&`})HAȄBWdB^o˜C{`x@9YY[魣n̴~w; O7?f}V<`s"ձ-4͙,AGdxM0*s돓/6 i 4.HP@bYrg؆]l ,K}) y6}M:dѓS{x ϡd :h9y69C9,F%#̼GL(Sa}8hs"mN`ǣ!)" H.$I3{,!21#=g~$٬^2 =V}1Σ@oh! R S,R,Is)RU3~'g|/Oڷdz մ!OY_6 #ZK[tx1I U??XiK>6U`hkO^h^tK09-&S%M2DO>yE_gHӌc4-yA"fh6iR`/ƅۇ3>?Ssw)-,>>(*^~͂XsFYܙiCqSMS8UUQf/yvéawFeDc>3~ӂ??G9޼?!\10cG$e!|`h;V*Uz$gbwg,JBl[nq0vbl9վu7X.qljvc[C*LA2 p[__k[ofѣ kgx#E 0u $Ėcݠ"!b-b6(?Ƙybb aLCojH9;Re c`6[svc%]M=N'pqy%[Ѫ8xċ ?s8޲^(yhf3oG׏xb"Ѻq|?$bPa:E q^LMQ< ޑhlIY&D30A%8o¡h#mi!QD;`gϺ:g#DtC8H gzs#޼avG匫k^xgi "h4+o|#)Zofԧӑ8 /{ AUf$2fy`!IjAW!q>Fd;Z`BbEUEط CK 0yYAU06 ܞ(zKB%TtT,To ;uLB1n4놋Kƛ78,T4+"RtFZ3珹~tg1J)YAey5'(ʊ4Pss#x5WWyqL#-1Ub쀱QaFGIEdY2pcD-I)dB@~gIOMwrjB@ r-`*UBW$%0zI\T(%s6I,I $Hivb0ϘMKIq4N@$\^\q8>t(-4F1[D'azr? `a񠃯IetӾ$_noGV>ņnؒ2gt螸bk[D۟Q@&}DžfcuC,k^~!Бd)o޾\\I IaR8H X5RgyAEA^|0գɜZqvvA,k5y@"#HY-w~|q4q?R&H9R(G/oO_w__+<(M)=,g((,ȦO%4SoU^-*1X; ,!Qg>SJ眝=fğM\ӱ>IzM&sTq8F9%M|_ޯqv~ N͑Sc~{1_(3kN1-g^{S{`*n_s},~%nP;~h̗3qsqyeU"cK}szpxlpN^l,Ϲ<$)k-Mp<2/HD+Ӌq4NujCK AZ $jQ9E!B93?3'MKۏqPADg$QC1U8=^|l!/g &zG3/7Û7o_ `:c c/8:kdl' % PX eŬ3aFrj}ƹFLchFT/"=GCv({Z/j~!֧+)f8q)(8NQ19Kn!Krg-qi q,+9f;'/|;o?$=ݰŅeBQ%\\9?z#ӑL/s拜$tC`7|xgj~嗟b\O]?9+(rAKkG6o| e_~ oq~[ ݞbEY,Km?$MRy|O$>| >xwHTJC>|>x³|7lYg "c6q87\?}#|zE%$ir+V ϟ%R}~;$: %ggTyE߶;zdwԂiy7Qrq8x>{->:N0H2jA ]fBkᰥ:Fcc8N LÈ bp#hAUYdXERk34=wc\s^"9K>k%gHϮpAi p%%W/~3lap$ ooޱ؜q~u#vGRR-Ziʲ1ڷZ&[oGկr_W?e>NFDBt*QjoQ*TRu#E6A3c=$J"R2US%L&D@j1=!T`=BFqEI"$EOSXlSq`#YcZ !(Fa,ؑgsDHUU,MsPғ("or"Gc}YOs<,)g t2:*`SLJ4MH=[o,4!6%%x3]+=`3ږ4X25ޙ3ukޢ;,f9H!K̪"Eo-8?p}~bd,<{!IRL5#ҝaćah#Gbjo*7*@#RjOkxw6DOCO S\Δ&HèTgxL;0N.; 4'1pY*Fx$Hc|Iئe-]ӏ1TU#RN9L`OI;U3֮7614;IY0y`!{ >ED$k269b+T!,Iްs\"Wg9?_9_FAX~ݮc h˔iҤlHHӂ"0e#p\dU;2~4MZsvviGlGHO25-8P*$Eь=\`?. ]ԞDI򲈛udID˖qWJLoFt&bdt5a1?'3;{If4P5m2G`GCẂ!^čs8%1"7|qxOq ʋhYc!Qē9y0gy_S}g?2/>.w۷|ϩGk\MG!-j Ax . &Z!b;a#l[Evw1bC R: /.ɁsOt!Ns`kb#c+ p!UaHE"{aHtIZQV }gqU |& (GPd1XoUn0͉qYu&]kv7USĉ-S*RVǒ_msǀ;~57 K2 À딕{y$=rؗX?\b!L&Ҭkn.1paP65_s!#tb;|L:pE+ܕ$$8D%~sGYM12zK@aZu=qNH.v x '_rsg>! OgNwMYl\g*IaˆE[R X#ULsO\lr$ I zɇa9,$m2،D\cdzqBc.iMg=Ŋp9btzA~D]Ρ9!f4 ?C@cːpGɌϑSrRrJ2{gt,% }n"hnD wgNmOa$כ R[֫<=^V]շo8<]Yb)F?Śa}+ }vxMVE&J"MEaJЄ8 }}jyy}7r83H'1̋/){:i#'Ϟ}J?N~~1E ͂;r6E[svs$DQiGvGǎ-~| ~s~>(ۖ_\\\a?.7?ECUGgW]̱ciժ0Ukx*h#)u e4%<ޠo$est#&J SLCp:uy# E |9F mOIMwZc//0{ ӢU)?~,"BxeY r>$S-~=Y7H)nr}OQeIUĔ;rJ"2:. 6}O~~[]{3.J &Ƿoo[E/Ma85늦2 jf^X4TURkGKˊL=$rCE3Ch|i"L%xRsØWl.iye%!ż]-6hKޔqLӀ#bߟ=> E]p? Y"F UՠT> ' ϩ1vjQіA9C!0)g?ES%}$;pU}&d=* OLeХ=;֫ECn˘;\ZEI:DE-)]1o|uzC`2}EfG}T"cʹ7*\Ʉ1_l+]v-[`:9\m:<1 0el5!| 0se#! !ܘ!N(#=e@ g<}ZJP 7OhՒ$:Z./\?mV w2#J)hxQcTᨫ˫%7Án~Q,㞺jY]ns~9Хpq g)cťÛw0A2BՏ[Ԝ3xvUKLiJ5)(r)iҤ%dz݈C g >d-HR`ʂ'&bbY23o߾e!pyH<{79O_ Z5hzD(0e5(zˇE'bHu)ֲZ}z-p}!PFX&;LYÜ@QP-Jz!:~?͸z#/(5H?aRr?( s&yÖF WMYT$Fw$G NЍuޠژ[cb&UMH)v ksG 3q\oж-M]#Bi}Rp>챮'K[,mc'c #EhL g*]|AIdL QC*2ko~K 45(ºLBJ(uƐ:p6]OY.2YCfh?BH%+DBưZ]%^}4Ю5H6o"C@KA^;HPE8a E( 42MJ$  R%HQDޚ&!=9dtWKʲ$#vI1Gkv!$;>g,2ߚТDW[s2>{i}Ŧe3 2=*rG F8OSjqn0x<cPs{{-uo^rѢ ءǫ,iBյb^0yboh[M]Lō'֫ SYX#1z󄔟UL p_1ɿ6L#+k"gEPY\_-Þ4;= l.V͂&^!$4ȡ;,(]bʆ/pDeXTY.V\\X{ylsfa-O?~?{2SEL\ N`NLÈpq?'77ӹ~{ǹ1evQeI$GUH\^\,+n6K=L1" eȁWoaa@ag*Z-醞HQ͒J$'_xB|rׯp:$)zk֌0<,WF'\;1=HQl9 G1eâiD6 ﹹ]\Yg}zKlQF'.^:jK֫knnn#OzɬI",)R&x8L>qyuya\4-On7x||3uORȮ8z&2%)C7 }w|޲?$VU^:,j%!g@˂~xw!ӗuހ axj]Fjgt+1}CRpHUy#VP[-yv2FʜYuaO>JRع%DvH|p{n$2}sebz6#S?p}!Ȼw4}I]Ja`&V nfqtwӁ5i"BU/8OOx?ҴAK08@fr#"~0cwZ˺O eba{&<̎H$EQdבe4a( |aߏ+3` 4ˆ"u$w7>E~],NX?֋W(v1q [ g'zXK81(V%H)s" T;gԋ/*4PZrqQCȋىzg4Mw8\|'s @]ԥ @j?96yKE(C`Zp ,QZfQHLEkZ DUurszB v=ǎic'?#:^P >}Shx|=>>P-׼|r}E Wh?l#qGUHquuE@vmC,P)˚|o_G70fgX' L<$%뵘E\(J4 h]Y%irV*#R\>e./@S/IJO oYTkf=ǫ$BUUkRJij}xutveWQND{s~~~}!/_-YM6+'C!IiB AL㖷o}'On;>9ٱzq.m%Q ]7`Rc"a1@Ăh RhO ,|ǿGQ)tLi7( Qi <2v9n)h]"A00M84R|v%B3^&*G.(K8΀i9bMٴ~v)KLY3#˦Y.٨ SgB.”4MLVr[Ljk) LUR`܄ " h1E$1!3 |ؕ2:;9J2;Yit-9dRnktQbLA apn+xrOSa>fST%Tf1$,( QFJɢ]ёBBDϟG$CQh H8TՂ9Q4DH% @ V0<7l6LQ A4\_]FYSLyxzva#/C)i;n8c> 1y k R EsIB/yl6פh#*L;TuDjEo`ms?.;.)>"r88u#SN2ώc-ei;,duٝ3g=%Ca 7ج*juarܟ9'b2|>=>(r,q&vw{L]"Uh:\báO8=e gu($ޝmFW嚫gE{rW*eUpv J$D /R-z.*bgkCt~Hi<ΑzRs~,(Sh(B9rSp:f1Haqqy8M@gm[CLx7Ph`'I.) ͓/>'{=|vK%E(ҊզEi+rНs!0cb3^e'hJӔR#F@k :r͊q:E~;w'LĹq:ITT̐I5U7_sԍ sMЁnL` \ltz1DZkezd2/Vt3RH6=})J0P%eKp:#CI$#%֑H)* ZewB|2 0\rb`d9!xñb>3Ma JiDϧb^ Xos_Rg#bEw֍!18.V Vn#"]IOQ|Ȅ3LZJ?l,@vh%9@»,Et1۶.<{v`;IhJAYN1b )|^=0 >逋OgY<&Ĉwi# i8ewYP2>xv[&xj }3r~4TՒ7o>9ؘb?ES~/:Va>>p݇{D]3Y,*BVvC)./.~7_Kݿ¯~WN,%3Γ;ɘ,q!Od&+:(XWZ,%v+4sb (=O?!EO{Fi0E>#H͢].+~ygtݙiw'67x),^,%, %u83]pvp:2t}^`@?|)xv4jd&]%1w buAv>8NfotQ }GUV `]TAY4lw?p:me疆Rv.Z #c cjc}d[?[~~=!wzG\]h .:z3 d^*pCnpxqI!o~!J kMhU[Pc/VB4e;d'9BR! x;L)Е@hҲ?rQPپysbvzh "-JȼZjI1T˫[,x?WNbQZ1vr)LMajD~6'zG",(JL eRjEm,0(%XϟޢLxz2Q&EK 6\T.Rs} x&ɑߞܝ"b)2*#_|qKVk|{c'IDw1eQ:TeEYWtsXc]HDO[2c9^ Dv(X/}|myI]T,%zDzn/f.h1Hw+AHb $,RZjЪ;}vi )E<0F$$乾~˗_p;s&U0zfR8&y}g.jE!R[|DWܼ;#?ܽl͚y( 8| ?!ͼ}&ʢ3نsn/o޼?2@ e \kkJ'TMbz#yQa&)0MUsyyEY2 V(gg6 k҅(58L7WT{a@rg'f?4S" j65-41CYv"d9*tQY;fQ ɏL"Ժ.KQ8N-U9╀GD(tI5q(Y0ŚWOd尅DAIb%C"f+M 2,§Yg1'L$0liGO? 4*x:8v-G9p8īqfv2\^@{<[=~<"*Cߵk?(X9|:$ELRs۰쉩ć~jKb`3 0A IqK5ĝg=cU4MCBc G;޽+bL0v\}*>d޾}؞]v5+J'*0fe 6-(<1L#J(-vLq4q8>p}VkAș(h*RanJHK@BH"!7,DG|1ZZ*SFQ5庢k'XXo7_́y݊][<#aakTK]#1"hEQhP)4w4,Ѭa=)D͚f%SKJ WfeUP% CaU^*(nYׄ /F)8;wjzqSb54 ~/1 v`4Ze Čԥ8Wcχ<NsӉ=e=cρWjt8r;Wny|}1&0͌֙]b.<0Z E1JP572=p_kn*6,];Ѷ='|L F@Ġ<,N ֊`\o)ሟ#!$s4e#u)xsn5p߳wogx8&ڰݞ1cQ`[6g5MS2̓D̲V0 ^kR~zqu4T߻T\*6g/u\Lq"82 -iF3TMSSyh&(Ky7[RJk*xxx&LW7D޽k~)%|lkw7o~깺XyՇwRk ډ|P9: 9r w-OOO4͊ ^.9aOOקr"GOJk r!` &XCbL^2*l%RJlr S+V^ TK\)DƳ?rz)jkX$3H: NV'RBʊzl7/6=)l v/UۣB[55j~'%صEahG̙i !#c8a #ۭcv,SVdQ3V.qL75 Վ5ˋ6Reru9N)! m8r~!}4gcsVY#PZbnJt+Ōj>ZeMǑ;1̓Dsƕ5}d9[ /a=@ep]rNBK蘰r4QԖEH1R R] !Q=  lJ/iSK;9 ZUnhbI) .Fe|=ԣW4Q*e |eUULZ (iTQeIc93iR19'VJQaO3q"Lda"uanaA>[i>+hO q9-I֐%HZ d;J-Ɵ `~T{OׅiPX=!d) `Þݳ?>2MGJy1Bg0MTMED3U6"%wɊ$k(u]G_|9!@tiكwqi΋)cRibx䜹{{Z%DrM EBlS. {-I̜}~FVb6rh( RVa=Hvם8J+iaFjG Ʃ'q7!7mYj(COPqaq؅ J 1$MrUa +0%,L&g>:jBwad`#gww"#4bZqǿ??3!'-];Ven}8N=3B٬k[K7"NXzokHYsuiز mc{Mz_Vq%6;4Q~ea%..yJtLP{895p8(D =u8̄ |l2ure.f??S/sc _}͊{-BwU81HkRĠh6+ 4[qsu-^L g-D ^\T˝Fġ3N5]U%1H+gn9߬0:aNo*jENJ®`]3MRd1O3K8O'>}Okzx$3MBXI4cmxliV3͎ݮw\oy~>1 DB g& n g/(KK۵4yէS 9*k@Eq(.a;=>dS2uQ"#Gb|b"!fW;./.iv]8K3[s+-+uH=9ԫ@[fQMr U٠iD\Z=vBQbM;<@ʒW 9PWb ¨ |kR`(KIL"SKU-j'C 9,nmeU6q膞a8-(clZkUUA4FY5dVcPF\eU` )%ZSX HQVY mQI&Ӂ;qj{LU%#䲒Jd *$?˽]9ؒ~w5P5W/z<w%Ep0A @˄_om 9FY } 5:"") 7eQPƲ^V(h'/BVakV"Ͷ$鉧C;u]ZE8h4j- a 49HaJU4yaRږTUM29@E8WV(%9 :H£,8W<癘,=s8թ(` &c&EQDWx]a8R,5&b((a}Eeaꃃ1Hi0TFP ^D@bTJ+q?z1 jB+CUUheځ1#F 1egTS,r2S$Gn{%g *Wgϟr|J^o!3d~W a&M<;y_M%S}tFI 2h653w)^7>>woy?Q5%=k-uC 9x\!5o?x=c;$EnלkbSSag__^?3vK.٥H7 #Ӝ(]Z$_^3aEf"谝%sSAIC# 3TU ĉպ.A?5W?z).Uαw\]rf'ad8G|h aYU.68W0qdgCkK$,D "x?qsr?^K4CNDVFNWtx$+XmG̟ȤOקӵ9߫?4'4FR2ͤD&9tM,qIIO+9(֛N<>?takxEAg-)n6KJ4 Dib7W-EU]1=dydMj*WcG?ᗗIb6DI(Qeڱw2 0PNU.;=]1 #Ԩ,aX@j(De{yfݒҌsv*U~_lxȀ3h,}`6[6M4s fj4Xgy Ƕ#- 2AUf;r.hfn8R۳s#*taH5,ePºzqH4c>Nb1FiBH8/WQXO&k>?Ҩ*ֵ ;~82^JQ%Fhm)8H9P4,{zy!$uĵl,EHHtO6uo±qjji;֌} xV~CYrº)Ri&(*ڡeJ)-#̓lpE jE h%N$c,$!Ckq}|.ڞZ2!rQX~^S*b5kp7#"tQXcmKt]V|~vF~ϿY8+.|nM݈hˋ ޽iDPZq5q٬VeɻZ9U8HPU6 kq˪nزX8eOH7dLۦw8rbFa*-@[q` n*]{?BADW|+9;V&YVf?z DLPzڬ׎܍gPQ\Cv[Pkd9ǁaXhbUcBB*B"*B5g4 טr~uԦ'g<>Rtcݷβmh'AQe,k6+Mלáp&g׬7UBKk8Is19DYS%t:H<(o+4/҄s4-qrIK)c'tdh;NH ke]1H+J\U}-UOȦ4F }S^svqI')FkeOLŸԥ;rLLC/32D@H{:ΔJd+)*W0"xicYoV0jmL t݀1Srl]^vxʑn'!𢊢d wѵG a4ez%Fv8u$᯿}Ϳo%8Q5~Y5ݦF%Kf;AbC6@YiY cT[K=RUU|ϻ dh6kxgk7y)( p|Q* IDAT躎լk63Ҝ1Zw#DY;<\__s8Xj//KxzLJcZ=)B]a;nE˱( F,>Ր>]ׇ/Z'+6UP&s@i "c^X,ͤȩI@e]J^mgOt# "!LQV&D\y9pR &@,co[V޼hC*1V&}}XĕNH2bgXJ%V eQML$?A.-Ea޾} 9٬0V(9IVk&\`q4劫5V*pyP[H#g漸 Q%Wo)Ǿ2qW6 8N'>KV2,/yJD&Hi{U 6y<*) sgqh`!%zUsuyvlҖh%®ДLc&L 6g|/0噿oMToޓ&VJ~qf-VȆk1VOS֔!aGF-AQT$v)O4X]-`veQ'Dgx<2IRY7Rg3>N'Ҧ13(*0M8JHuxʙ0꽰~r4ZQ)yr%C#fTV؅Sa5FImvQ8 W# ϶!V ]DHUz<>>2=E $, jgWfC߇!SH0F)8G>X<0K}DaD(]M`H"Z Er@~VmӷRl%P벤;XSpՋ\]ѬJ1 'V0z;K/9gvÉahy;G%ۗ7 C8h-aNܼ?ej%})J`hcJ3^)lV-D:W7qO&p@~Z#(_(Ea@dFb  @>LUWS;^+͜ZOx{7~@NqsU|vc._bL5 fG혦Β(kvu]#LL+U0㢰`h #JIRl"sd#!E%l#"K|o9Dո {B1R%eCS'Obdy>G6}NZLвIO310(TRh0z`.9Xh-"Nێ$49۳+v\^PI۞=W7޼dnk) jn?gJ#ʖxWU<r/~op}%~o^[{\U_qqtD ԵDEma*w sKRk5Gi5Ĺl;V5zU/E l|v\d"HU7L~YLbs~~LT c8v@dqS~qz|>0PFZP@f d%p:{R>qEɏ>1ZYǖi$ ՖO ]Y" pVX)eϞ(\e%՘ÔHʂrG\t}G&^.kCsV& 9+p8TM-m2,]FsXI4`0Tqgeê)+ C{4Mz;v o,(%FHH\Cf%bD J e~;vu[~RYRe޶׿}'Y2dQV)͉Fssu-o޾B#fVz k5);QueIE… Fw G,/.91yQgmMdŷ1{f@bk-iVj#)&^}W-yL!)ETX]cGTU$/.BgyHʁwO COi l(˒96swlw{0C08C%!Ds$bl(6!iLՉzDcsMz}3׷\]_RW[mY-_r{ Þo_=>Ѝ[R{^`Z'I,43G0łT-!x8Ѷ-}?I0*DBaUE 7zƚr-=?*L4ߋ*FsX9۬IbwIJ]0*lV<<fX2M!u-_rahkiK̖XU$ϕՒ ov{tKa@)Ił=IT!*-hZh-Z6%0Js| aJ~d:`Y)t<ـ4ڬ߿{u3CksfG.jb ha(ERR+&?e3x( b9 *ae 1X˂ 7TE) 7Y{n\,pNhY-Ȍ}wOʖn^4br)gӁq^jH)]f٠Q! JF]PpxS`M,K@1:ωk$z(KC[U%.:^}2pԝ^Rj5U1fBưZ\M%UeQFD ׀҉Ҭ+HP9L2EKEkI߅^j5M+ QtMU3[x| i#yY&"5\+40y(f&(˚/y!f=>R9fsjft?ͯFߣe;)vV4+ƞo_sR yKo[8G[+2NCObVS!8K.hʆ=mggk޾TEE wwEDiK /^ #U+sjLc(9UU!kqH(KO,RquĐ(KbR'YYd\-צ*>:;N !@j+U3Z2=gs>h(HI#aNuvH׍\]?o߾F~O Pdl(GQ/^sEkYv# OYXLrwL}DgC[4^q;b<=bMpΠkGTNFl,ִH -$?NR$=u~!q~[Ǟn׿ft/nX]ҟX+l"!2YPE4xQsѮ8~qv×Zp}~P55%ցxvg|a{PR(m'+Eakr]ﹾG)RE;$i E'./Ft<*r!͘*,' S Mkņ( Cmll#,&)1ԕ ?1?XNG( 7 䠘21捡mʚ+n`X1WxQՊF!ikJz4 rpSʲ 0 M'~m~kW#oR^7-)g\1sss :9[TiKY\\]<}0T~걅hIFQ؊jh%QjbrA42#6fjՠTf?o"9FE[i>[h޽{͛<k )Ĝb&B24BkN+$/ð2Ui+}qn4EY,%deEM*ԳXWUKJQ: 9HTa C?bLEG`)P :Qo-}Gr\7oQڂ5'}0m/(xV෤UAH61p h/7^rqvj`.ǞĪ,64$H0pys-FĔZ~O?떬Jjc FYZ# O UM(@EŌuK˩o7nRmبʒz;Ʃs$rĘ)%~5o犜5ʨ$BEt*ں B!\q:asbJx?rMzH؊e+j<~nd} Ԟsn^D\ȒRt1FsmNTJ&DUKQzJPF)99gyTX5?ɗGa_vϒIRZkH>"DKRWK(&TU:whxm9zI!쥾;)HfR˦$\^HiZ3;ryyAV,woѩwϼ{T@Άn9#DLc ~F [&ʪTk`Xn/HadzN x)PUtȯ/XVXT8R $ 6t`}&MZ,,ktNϤqJ9ijM%H&Tp}ժ?eN&uC["PVfx@mX8h% F7?$1,QRm d!w݈T+$K4%p)IsBcڰZ,$ݐ! s#]QgͅR yHWEbm6%X6 g$g)KHxFZ '54>x1dd !h)˅DajAErMJ0u)f$ޫ5 [Rfp'S=똦f*Z/dj %U!8S@!+\<<ùf_h7MVi?UNh(2V^Su! \>$"jb̔u $Il.9;[tCG78(-8Ei)ŋs{c7#_5_'@Qf ԍF50RhImbM!pi%C?\QZYy ~@4a4LXkW|cVY_,K 8xz`mhOWofm*2hTdVߦm,kr{B(KCH[e*GU`VX-X[VE)fZ45Kj3'[Xѕ+-肶ZK1fް!<Ĕ JJ1drҬkV9W4u ʒŲlHA3t*4OZH!Mֆ*{GA 1DlXWԕA)R*4Ͽ`J\37)b⒪.8a@e/>[Dp3Hs80j*qlOdiKMSq~~EXxǞ=mw,3>})?/䓗\/03G@*"gs.ί ENzf t}GHMIY5hU%&BH&LfpږORJӶM[ubGI@;ʲ ݞo^s:1IEQ2=#u] #GR 5&c*EQ#b1sGvvq!G%fSrYJQMK,(ˊӔp&Y+\0ٴ$c~Y>Q|xڥج6|ɳ}>P577hr8=Éc%jBkˋۏqSd'>cV˥$$40\dnKanZ$L44uI][NÎcgOc4u$ZG#8Eo3NULx`zոԿbLN UMz)E7MJ|YҙeO8`r~ES1M#>vAQ4wvLwTM;N]3S4%j1sYKϟWl6k޿a&ȱ)9;Y(xzzo>$%}c~#O"4%M]lx5ij78R5<$%oLJ&bٰ^"ެih87rĘ4:a]G'B)2j6g+T\\\ˏ{%8c&]IM*98]8NsEt!|ݑ).-5L(JfBlY6UBi[ +Ɓњ*&5M[b:Y_sd}x?%VR4Oc]UKi[Rܰ^4-˥.f惤t\WL^=(!iX-[%M$hʚi0PؒQ NljvQQsAE&?r<AD :ZE[К~EV\ Jx!p'VWjUׇ9~?EUiB\,%Mk~-ڣU+|8j%js}ڂn(Z80=]waY IDATk.nNĠTyb<$K[JR芶jP)Qa8#0S!ꇍ^JrP`HhS ĔX[RVe!O|oE@iUYPVCy0(Z&FEQu @)V %3KF)Mu"@Y*L:[E}hZ`܌)Xo Ñ~FOSs8QVlGN݉zf|F4uuV */,7?o;.ή8LQygAilQpvikba:poxu-ϻ{0fR5gPTL1|YV>X[jLQaZ\\^S݀-f6ZXZsUJFSUX8ZO>dPP5n /IĈBBf,1 z1 )&<,'jxU%|߿QTOǓX.i%>hi[6eEhOYkb:bmCZӟ~!ӉnK+ ɧ,0Qa4b࣏_rqiiґT&iG|'k|94Av¹N` It}DZ8LIPMq:N|)W7-=;3t` =Þ/O~ʧ >~TNGBLRuYrq~Hu9(vAYU(8S.K^J02q1&on)JMtIX#4)[a|p” m2-Tp<J6ۻիFQZEzbsr5h{y RVH79UT܋okVPA4ĜѓQ!I>Bjh n︸øyNx޾!Fj5_KVEYrs{V0{bp#jaz@ 1XU7! mC4}]M+C?vi|#rNqSK#0cLUVEˁ=Li+Oۑْk1{yMʎ<'OaKL!AͬL0q$Mhxx|'eMJ9*ѢH2e9k5W7׼y8W_ 9'C)wsqy%ap8xڽ0[Xnn^pv~59YNYh\]\?Y.Zv-nM' S`!dS1Q؂i$t4CĪE4}p ,*ṞY;Hy$"u[sI-1S ZF=>DO8b;дK.IIܽÍ݁q \'?ap}ZulG8;9I,NLH2,IdȦj$" yXqg%8ĩN[)VZ@ZX4mGvG hE&;X\ݾȟGх@^S&K9U<`< PJhۖO>EY'=|'?QV "b1F4-9'l$yb̳E|0gȩp8>Mca29 8S% 7zHkJr)0ܤX g痌m2yxxdn&G[|Oa\^x?s^Q7vl5AY1L'uEV41 9 aZʡ,E[(+zP)ME]YT%%48P +;#n$|z"}~w?<DUFgx*,} x֬4uM}Ĺ#nS):I*)gIsʦ`BUeړM|L~q1) n>'R z9,>(k b$Q9'+C"ϰ[i y>Lil6R?9nk2h( m닳 00N4b 0\iy]4|2 KYST%)RɏL)etY<Ajk׷4mp`ܱ !t H LSɧ/^W[u.Wka {~n@Y1ydŗ?W__v$-E{^޾?}-4P%,V MSaʂv!@΁ج,Kyn$4y 1n&crnC'EU|<<=sx<3dD]לN͚_9S2qug *8i82uM}= im*pQ2B`p2}z!^E/^afrfCZ%oO3R(BTUVOD2v>nNG Sl.( b=*O<*i/I=FYxwEQ3U2G}}(D7l}N+̼D2(Qٖv"Pb\,xyR&v=e+˞ x踾9;d-Z+sWIYV~ls3޾~Gt0n{5>`p}>$r4qU+lY.-fPU1&QX$k5ea0Zcŧ蹖1{v'w!E1wn\O*iLFj>x, AIVM FJsEITxs궸q0hCNR9Jj(uMJM"zK ꒶ޠU15VzEYhb@N*OaF9KaʹR`ʖ˫[vi<SNJV*f|NkRV UC̙{~S EP=2ۂd[%#$L)AY.*QL<פ 4m eIw\V ͔6ʐe(.%&QGQb0 gwk*؜׿mж k /"m!q~W}ȟɟa憪l8?K/g(q`;&/dX: #!DPJ@u5'ݢsUF :1&&%"mD[e) &)njP0<029BQ`,F@SW7sTٻ$P|^05K]Jei-KL!t988f\ss{9 Q2xX#q:u[|躭TOi<֥Aꖦn&>%uv^5DHGbpS73JY4,N OwkJ..GǛ7߱ۿc'L~Q[Zła~:0f(|.催/BIgbd&2`[XIkp~;adXa C q@t a9Oˏƞo77(>?m9:ˢZi˱@aK6ge%1 柪VRˀj[q{뇘fxfJ`F2$D)M+ƪ10 C8i5^-i\o*M;2#S Ώ]SU5SO?xb@ϩjJclAU87S\$\͜}\|&y}( ( =}'aoGb'~ՉSF:;m[R54RTEI ?sDFZY+C!`6pylEUYY![}dZ,WKR2KyNES`w/ι@+XZ?FNa1puyC#)KOk1p4FLS`콘dF[ d~70;&"5Y5&gpڊ?/Y.2ɹPL)+l%L, ͊*Y, (, CX *z9yE)al88|ǟ2R~»DaJY5f%nljy. Id!X4eEt4ՒB7on,/{8DjXi DJ(JX(Bkh9Xh Ë~Ѯx h,vs*C&R(mG+K,oT·!m}L?O ڢ$IFQTSH.9r:V qQ؆")ʲĮIZ+Li519-׷@[D(SA6ZI,kY;ȩ-jP֙Y@Ӵc8Dsq0߿NON3øQ5abN$nWMKS,A'JYG@ǀs AHĤVMUs<Ƙ^rZ@QSl mFe7$[Ӵ՚]\7K)oTYDj$`0`D1 !Ĵ h$$Ȓ(*dh<9YؒK1sfַyPPFrάYЇN1ZqPhP.aqx++a0K)Zf0| SrI*G-~Y,՚"u5 b#H{a 8j,sX-+qZN5JvD^I],BrFrPJrBVR(1TR4jXRށ X,ځ+60Ir%alR`$Ryˢo]xk]8&$a4?@ѧs2tw]dh5=ͬ6UA8wEXAH{I^֖m0ɼ#M(+]fz΋/5 k:{R͉Y=g9ې3l[>Du¿%oo7E diyҎԕ.oO(efNF1of"imcR9QEǂFs~|ޠ5n' \=?_'y)Y^xSlb0׼}wO_S-^jbCSGI+Ʋ7?>- A ]aԬkI|;0OT(q Ix]!DڶR҅aiZ>ɟ/y1w3]|1qXX;#/ e$zq@kt"2աݑjfdM)"_(#!1rAx_}u'bK ?Tnjs5+E"YoV8XoVʊu2&Ҙy@Pv-)Rjx|xn>jyMwlկvrmGN#̑g ݧAmŲ9O,#Cߣra1o 6 o,ղno{}G#󉻻;eŘz*OK%wވz3vG-k=qOnd1u͋<;b_͇go~4XW}Ӷ>%ߠJ;^H( dU!+rnU!Ƣ"HNbZ,f#*YMzR (|dHHuH\Ĝ@)\v`x_q9}9aBOh#!I5()fENEG%%F{#tf3H* `.Y vű3lAFBps}}j`H&h[ IDAThEQ`  lĬ5K޽vjS3aԵgl$,az}[Bje 8 3֠ ݻoJ] F{r0!aAIc@,čH$.F#qIBT^Sf& V;~O:>)2N@x2n0fx03}TGM'#0Ĕ()UʤP֡tSTw[0wŲHɊE[Xy֗swPExx?_{w.<{ 1uX#׋R F L pr}*zHd)q@jOdhdHŒ3G(&FHIL Twy3c%!e Dn)%Q^~"Rd Fȹ?s8ʈAx:dX\8{ş'恡?G=P;wyw|ohD.d6 #qLt)泆+fxxزZMCq,5Ivswwᜉ2Ռ}=^Gh_}+v\c 7l6#֖5_~sΧ~wa_0Vx䒔 _)6tǖh mHkܱ\.x9;߽*G!4.P2ԕٓzߣ l|vry1Lm۲}xpsaKbhΧ4#SY*˥0p8pUz Z W4?EcV"8*dM=tv[T x9Qb$ƄN)j*i2ùBSWh;G,7Ϟ/ ۃF"43f\$50=UM֞Y31A(VLu7z"[QJØ\ PP { 9Y @jc U$`:;1Ʃ&f{Xk|kZ[oo9L۝hfO/Yo޽t~`{"R%L22Hu  ޱ\X״|rQGɭa4FQi×(7~jc`Hמ\fR'S+^xIw'swb{q qdT3ZGUU4Sq{1IUUSR!%%4Zy6 8) xHgPr?q$q@iE% (Jԕp*Va1(mwhF }$P@HXARg%K]ӻt[hR %'ֆ%PZ)ʌ`rcc|1ϔSE֠02B1al(Ɖť@e裔ܣ8),V N=HRtCQ6iSj=P/]]fcxaa+//y9+'#qge,VɲQSE`#T3/hہ"zᓡh=zP0F2Ԣ.\-XVrS21cWi]9w ge3N`@ˀ":qd$[欖KRNVa@7! hɁ԰j秚!8lVԕz+I|㘹 Áҙn82ahD@Isf1Dݞpcc>4uZRT~]*jzRi(QUo%؇AFZ\pqqbbyG`Ҫ`$9~|JEhٳp>H):\<|>}xdݱ;b#S2ѓ@fu)Á=WK+4 ; CWgp[w{rJ,{Ç{DֱqqxOʙa8մ8v{V9Ղq˹=I(dh-X J&Of :1)ͬs}'|:|*au݉0Ћ-P.V+>cX,4u-|,k1LHJm9N}?jKR xg3[P_,]7\xP|d>oܬ$swoqfM#YI},~Wl^5nlw50|h-H]7Wklw[|]Mܠ5Tpsj*ak[g3ɯ{?5_?r~?Wn8a+EQm m5p3 M}$ R)B8=NV2"9)";Ƙ>?R_zbk c8I/EEŬpyr JC]yW/_3DU`}s 掶=rl8i=Y#J9RU|kDE"+~u'g5nw߱=X͉)SdAUZMƓRoV:Tk WW+f17a8H:6ǂ*QRc$*86KKs#4M-gbY 8~ ˕y'ӑk</x?Os5{O~w<>bza1_Zm/xw|G !FK%N%L=_ϥ6f5͘/I1gos[>~J-x>i=y9/ &a:8\^]qS u广%B')lp" )xZg0rvb1;H)N|35tCxnVcJ[cʜ@t!xOg3\.F1gnSb4ʰ7 _Kd8r ڳ/Ʋ,yAt}g(8O,WB1&=r:돜Y (h1jW?z D&\OqL'gͯ72%cW5 $ ls;cؐc:kϨ*G=C \88_ݟ7OZmf5ttCx S0(+5嚐"bռ"#Yq}lI]5!IjX/z"W\m +2%'#Ez@D+O1 WuGw|{6I,H$8 b4,szB I(%3'PbкXgzMʁTꛊ ||݃h]W}Jhy=whX,6xȦXQ:cRC{vbuNpfN lCQ,L#FFRbt1J'%)1f~`ZsZZ*A*kyDBƒhD:$Ʊ 6W,f+]YF(J@əЏ811ܽt>VW Z%daa0kt21hͧMsN2ΓFK19BY; kEZ(hI%1 QwབྷʝuXQ;ȴ^m*K.kŤC)d#ON8OdS TUXJbL#)EVXtYEΊa$*Q"Їw5$oBD aDtz}ю! b3~?}G (+')R iXsm7Fx&cOx#[r9G>ޣg\]]r>m?k~7|9/=Uy||(Ҥ81datnj#NIaϬ )RNjH(ƔcĊus=ўτa$ *aƼi eLXNY>#TaʲB}%Na!}!:Yb4 uU1_hf[Y2XD[/6\ek b2ű1Oi:Hu|93JsP*G]`&yn+[g8RH2G3a=t{QƓ L#MSMa2WWXo91sf64 )fr(DQj28)cE-hGyxxv UKt<3f Uss:{+ՆrN#]s{BL,V\^> к\6l9GkYյg͛/Z>%2- VYV5?b>}G߷C5qH"'+n݈u/^*}vY+ 2]k努( c _Ն٬ (|;SAdk2F!?b+W\nXA%KbZۻ}|1㈱b|6i*6x3rXpq8. ЍeoW ֣Q 5D}ߢ#1|Iy c4z@N8N)P躞 ^jIdmORjrb6_R5Z ``I,s1^Vr:1*sfn]'1ɪ)eij2SjGRLrrX]U{.X.X. C7 Y<}]o經4f(Wok_?J,/_(1_n8=}1PEJj:EdX,W99!t&yAB? 8Ii$ )/&`lO{~T[Bs:1:uRd˼Zr>S1f|IIy:(*QUI@ !Ԟhf69.qn0Em+.Eڝ 85b,49rY6+ ϡݣJFYF;I-0xEFӶ=(0rVAMٔe1 VK,xB#+c_i*|`u[TI6xFOQRϱ&fŹ9\`Z"e,n ,N k)J6LH2IY^eKђ8ǀΖb![ =bwMdV-8O<<>pyf1枪sOљv9=ü!I\_h91:τq!@hg IDAT5+5U== #~}ęs}}ͻwx=O+Wi(&T$Mc1ښ%O9|_p{˥/SJ8c$*f3f9zkۭpj\Ԍ@ s!H2V1N 醥4DbƁN|S,Вӈ^ڠ O$GJ1E\*)a#B+INڜ0ƌ63ɣѲxR.#(ID@qJ,Ur}(xkDxbجfh[p8 -FՄAZakbqNJ X4OLNX1ݜ㔨!cb97 y-IN' DS/X$= ;.pZl|nH3ḱ"9NTja96' -șH" /i(ȕ%QX">S+ƒ1i"G9w1N-]SUzj 1.xc1gE~1D{bDQiϨ~jeiy?џ9:a$MpК'AƈʅH b"1H6&Ba3%=Z)sy%ۜXBv RpFх d1BO!b+욪b1H&̬c;~X ,a +LSˠ:MeZzϜC;po777xn q' 0-du-ͯ@ΑB)O)CIXUh[xCO];IM:OJ2놑bK(U'\?a#W< /^>x:q:a̜cY\{3޽2'?aZ͌Ǐ Z審jPbq{o7?ًϹy0ֳXƈg^ɜNGf/_wО{SK e䨈I49G=m{$z"] ہqB-l^S{DgzsIߋg4͌b'Ona3wCGV9OJYb5mtݙa9׍T1)X㩪!:r48ųys>}I׍,%Jejpz wɮ)TnsQ,A/^|o81f1OQvѐgzƬjw@UTN:s^J"RYˢZ~ )iT1!X,!~# ΐSFVo>~|D7by- [ᰣ[n=b=C要R*= ]mgl. QM EK@GE9 "M8rXs00R~G5_ΧDwc1_ |m~[!i=]b*WK&{_M1ᄳs+R} EQ5ZUx,u"'"&I%2FIGI] )RI%R # t YSUs֛+n?TS{/'Ė\"uU}]phN8Q>Wm$DuJJEz4JrG#gc Rzw&^r +%Zc뚋Ն,\ 9 7Ðq03bUɹ vsNRA:(ȿ+U51ۙ,k(h%łW ;bL8zB fS+)YLo 6| >L?0buMQH#HN#> pG嫉qzO3ZMR8f^ͬF[M?Iv\%)Gc'鑺yQG9 $-io9wb=MJ)Պ|&)3&j0Ia) 27OohGR4TΚXY/ z 8bqӧ1=Ԉq-2$IVN*(EJE9qdlVs}uELIZ1HAӶm;8iE ran|VXxƱm  WY~1O82akj"`b4h]躖!TJP+Ԕ Zhq̴s+8p=kPH4#${nnC:MA5AGJ֌Qy?5Jgz2X l,U1QРϹwwG1'ў[>|H?]^eU [nyYCUV5oÇo)%dދ{+>K ćCnoo7|OX.gTZsBurx0kh/+%W7>*Il\LJ#uc$ő׶4znu=4rQ.@F{2a 1IIa cl!,1HfP.\ʪ̈۝vw:ﺷ2R("t㜽^{5mxvuʙah)ʂqEI7m0Nr6aA+^xn*t{ U{VMM7<>={h u]ˠX%F [ϲ<1 +QVxJqp-(8RG6-WW,+\] ~9s>9hD)^r8]֚$Vʲ$E`U(˂mi'D0?y#8]P*ID47+8;8+Sq~j9qu%kXexzod9 ~\"QCj%5uBivhЍ$j#amݽECN'H(ՎzCFDE臁ntgSӎ m;0M*Fج\_o3$;glĘFm7͆*.xxg2Xxx|LiԶ8B{prY+Ow<mR:Q׎)x= Û7/yvp|[>~CQj<>>?<ʔFk*{[^zMm-0`LAȊ^՗hS?vԋ5>$nyƋ_kzMHSTIo) 1~\/_pGԪmtp3<<>urpӷíew}}v{vR3 9 /g2T9/z%~LHUW(黖=E0&&Q:YjJz)ES1 0`WvEwt{Q4%iN큘ZGr]0Wg 1A h%1fr@`mE]-f.Y+QEƎ~/35'N]ߓQÇjzsELW.|Ow^߽~^9_sCԕbXQN>1łjMU62rCvNx"儏EQIUJIIXy`R3o%Jk1(,(eq քaHɂY˯Xo,5?bӕvV ?MIA8fcgFi Ώ=4X-9 RV6΋hN'R~*Λ,p|E1 -Ug(X:aBJqIlZ3o{cюnIY S~!鉊i[53y62̟3՟LZ fZrϟ [rqz J`==o?;'ӒrG>w=)ek鬣 9+ʢDdJJ7TWBQqO[,֤l R?$X6x?vu^/v{(agȥfQ\^񃯿ǫ/dꢠ=?1:kO-SQA%VsBl2PMI Lc=KS;CUWy샤a.0Ųūbt9d8Otc1JRI~8k?|P~GT<*SaފE!' "cWK\fst m2J'Ʃ=& hr LCof%CzN_ ? LJX ͆'G9n3 ʬ^0918F6^\_tu]|SrTbX4@S"++'Q4 }YXԕ<D ];`֌S`ڡ# >sn;無ԶqXE7RMCEjl Vi=EIםƖg.)GQD#xx|eh4@۞ig>ӓ U?ɏy?!%oqe!Um(*I(9g\_~fH9ZErya9 F*I}S77WOgN3Œj׼ze(U$%?rNTYa }F9 {vgth0[]UTeY\0=a]Afc&cLwGMLb1r]8Չ(P9Ѷ-9nnXRSZK39UYŋ\_PUr?>>sjJ,$Y-(Y:VPW5˪bQhO-O'Iok,),c˩eh!r:&ƕy2nBIjY;CY!|-!f@eJi֫-UYRW7 }%45lj;Ak|Aq<aTEbGߥr{}y{Yo.pY^P ʊZP1 'I,44KY[ExDFiD999$fY 3oee@9aբNs!E=(cA0 tvepbyx˿G?+10+a'Qq(LĘ gKrV4%qYә77r[,|JiXTtD둢hX.-59=Fgf+esd}y8R*-`:2p(K37a2 SΑvR$“KcN^*EF,v)!k-UcKܝ4MtV,54j6Wqc sXpzyRT2tOgl%(Y+I>)B;LHb}ɇ( 'zr3M#tyNN9#f]0L&|LD܊^ٳj IDAT6kY5z'nyqբ5u@~tǢ?7ųo|ͫWXW웟?m)49ɐh>B$hc –(-6-?mʶ\_`ߓxx:KzЍı%JLHLjhA8O#Gw?%i(bdQU 뚐2t.Zed.nRt0I;#8  eSxxdF=a 8mx|]?b|r` ƍ3O#UȞY e:1M$)Ȁ$e0sz,fJ/W1?޳X,麖8y9\)A[Zl_1ky6Z\3 3HQ* CNӱ'8[ 2tCma{]^\P 3]9 (5(@wN61>S9 /5ib"͎ꒇOL^EC9ל}1c&N EIp}yES;G?!݂*gihO=F>|xG͂W+_6? x)+GE@ex-K#C?u!ɁrGڔ߼0/Хb٬ 9^S8v}kIQeZ*UY&X-,!)v8aMEY,D*h&y-if:4QX9\Ig_,nwӝ+PΛ <ł,zYԤy|x.j|5ߦd쮯?vp`Xvf-C'./w("?J9ԙȩv;4[|$=\{?a\^{w ݹ6$zAe 776`4oӏ2t.0EX/W0̉Ueŏ& -S?N4rlVdc'ᡅلAF؋3;oQըhj(lIJp՚8ypqvJ<>Q.2&@ zȫW6;^jEQR,K0~l y'*1(q]$YxDDfHU̧JaAπ[F%<)I9=O\lx5pFxrOy [(}&!r*%}IK%(?1 :&?Y;۠r3 u$^omOl/6h3)a|k7&|$#>OMIUDlLO{gqOQb ;{0 U!l ?5Vbo/I<22W󦫐 O0NN%1ރ[XmԳX(%e%4trJkQ2cDe H&kR yփYxW ]t C%5*s&OaIE|, $Gb`lq(8t3]ߒR(gb̆8R* 2 C7p:$݃_W8P%/|ś^~eI7GP]Qh㘼@W0À͚f1aGŘ ,wYZq2p9۷߲ذ\.y9}w&oG4RyDY\R\\qi5EQZʈb1L"ww 77@4<3rϪ VɳjjAV,V nհZnxyBi4}z֝q'Y`U||t W7,O9&4JG HY,Ykb x) EI?<E]"&#5SSDkݎwA۷?{~@Dz·@Ls\*<4T:Oᄳjl<ZϤ|+4B#` ;3堬Lh3%:cL1"a('&߱^ғn4KZTDѺڒDB jꊤ1I"&tmᘦARpʑr=WaZR_ 6+N[Ǒjb^=M6X稊h> ,ˊj4DځnaU,VKǧ(9 c+v͝R39g)9E,ײiD׷,K8LRTU8XHaBE53E~7\ejK{3;u2l-ZՌݷ?/k{ߐ@4K1EE!i(**5$0we`G*x_嗿s9'~g[BrbZ;5#)mHi* |c#Ӊ,xTebEEJizӒ IA*A)'_39r>es|FJlKnVZm0!B$JKG+aY$fI$ |'=V5pEZ3(;SP BIX>O`<*gR\nX/7_yvbp5|OB׳%fD H9О1k!MjŢY36 ?O􏄯Y%rQaX4֛ YNSW̯mx8~Ϝ9e]7<9'r #kf#;33MrM™ҭi[P>pw$4Jq8GvqEYY0Қ~ +H!ԧalJ> (駑s14UŎDvX7'dbD0R4` t|T10 e8c@*eI>";I# SD a6GpLH]hmŬ4*e.mn SV9'\2VgUp aaQ/s•eU!c=cR(HdJΧ^Rz^%]ۢsAP˳ge4.6;oy[TRYAwn_p}iO7.(];n:ahT Xb{_͔M*yx ,Vc~7ә悾;Ol6rrŚwsF E#n M8+30 gp}yųÁ1?g<qC{h8[W; Cc'e] Gae^iE(W,W+v喫+x~p(*{Ky݁w=J !fsw&fH/<<>0Ę}O} \!p[J /j\ 1ttB%R|>z SPpf1ڳc`TUUbʙq́j֙Ŋqie=ԥS(gILUa-{Ϙ²^ Q~ z%򇾧o{r b=9gvZ [ib[Vrn\v0=UU,S$"tLSڊz͢ZOu]s}۷oyiXTd~\E4at=P$*-)\^J)ʺQViLa08rd)S-n\o/~s2k_?g%0 Ol65u(L% ShGF?Hi-y^H:ȊqsJZ=Sa({YՒE"gH O@-J"s-K2Y3K@Wԋ5 b]O,ˆ'&߃]8rT8[R'@Ʈ%Z(_qSA nFn1]Y [11ex~s$9B,l(]-֎6o|YZ+tCJEEE8n9N<=釉sf'omF+rEN%ѶZ8TuAݔ cK#Um*}O"PgU\q ϼ"7)N瞔ȓf?Bjv!fL[5ۤ> 2hmgތGߝB88Zx9dEYQV%!SPJ PQjxS>3@8"'+$|zÛ/Oʯ\֌@wh)RY>%/K.QV>,frjjn,wOLg{bޔ s4\- ߮JaÇVvj!H(;3\EQbw7g= ꒺yW7̇oy<}RyqEføq:L@8M|'a_..釞n-Cm'bŊXS ڶ9?~S]?Tu3S3t]~FG ?ĐZOS›/^֜{<3:,OOׯy >ۿ;|Fz~gÝa#tbuI&tbYQUxܟ8AIzt eb!J(#釞 /,F[)0􁾏8ǼaNT碦7!z3s(ܧ@?DILa1F8]Y 'K=ѕ%E%߇l1])%6,8H(dȲVd{QfOΌg<@a5SN+VCJzem a)Jah Vߙ`0N=}snb@ߏtnpc&I,^Z#*YY?֬qh3]wts>guu9<  mx8ʽjF?QʦϿ;Q:C8TMHrˠaח/xK>RTHUpuy4y֖ nl/ß-$e g,3LHw0Uro;v;v Nׯnx@?iێKxG#]wdCfK]|>nof>V&nsn?G)xv3kv[1-HߋynZsuyz!>ۇ5xX,E]P/Jh0Nԋ [o֔'@߷FlՆ>>Sۏ?3&Ki8S4+l94#1InV c%(p"\oGq"b8(Jk躖 8w0kq&)bL0$ ( I8׏+?8gBԋ%ΕIIʊ~_jd^j [̦]MUU©|8%U$&?_LJ;M0t1Fq>hI>ZnzEY.PѲpbd۲Y)˒;g8ֹYm?7_~-o[;u8zQ6 e#&;FѥC :8k9Y*5)cf<vE8e\q!`Ӭ) SIY+M#@V<8k1d.P(iRљ~" g9xƾCeM]erEQĄC>DUl7&?DU> EV2SNNQJxU39V5c<'$) 5>[3󩊒UеI!LŦaw((*RO۶hk*ϋΎs3yFgXăӬ v8>G J-&@ ^ wLcY42$VZQ7b|#Mbjម~"g-U-@gScM+/Sǡ}~F# WNP-uh;<%3Z,ˆ5sqDIC/nRb=ECVWlxG?}KU5/=]c$F?+ 1;RO2P͆7۹4Mӳw5c =t#{B 6|&};leK 3zpWUVo~'6/8:TʔJ?bouŨI1hIt s> 䀑OXѢFcIyf/S!b(-EZؾ 3t2)`!  o+әLs0{RpTԳiMwgWDQVMU]P[),VSݻA{Y$sN04 K,܈Z w#veTRLLUF-J՚ժa[g@Jq{bcݬX7r:8DQ {8Xc Kߍ~k. n=ƂQ'vso޽W;suÚ -v@ӈa(_s{su IDATW@Xu<<<4)jͨJa5gH$K; xeU-533 hO=VzS.f '|873~ܽ+~_<璆9xnRGR$-虜[*"@#摔ȬT$LF3RK){%@NQ%~v 'z'qZb"0 ]j%]I)bsC +t~ZR¶cTa'ŀs|\~5^)A]*6UU2%0<LyIC)0\sfߝ[V^D vFJɏ$Vk{^Rf'&%[}Vj^Zk\*:iϜ[cqNFCX 7q>QÑ;Η be| dt3\cȴ$Ҍ]=yVq{r>z)hWG?cg|?+y- Y'y~~"Ӂ~d0SְZg=DQT >Fnnvex|'/W /)*DQl7+b /nʜ?\[!br2HB;.GzdrPW{꺦 oO@]f\.2qh>=UG_Y ^S=3RZ)ߩ;9՘#@S ( %H[љ#&OLGcZG'oۗ쏏{r2ۈiTTՊqfx<Wl6[ i")Mtd#ei)hV1m1Jq 8)o VaڷdEN^qsw˗_~\D+F~n7]gnGs@آ"x於I&Ăg%ΏTd\ZpaF[Q%c$n>p"y˸dZb7厫w88wm۳^ >Ҷ=WW2bV\Okv)MQD_Ͽeoﯷ߿ I~(*IbhQ:1rbKEFa( H0" ,'quw 0$ ֘`!Zecmy$%lX֟ D8/61BveI( 4McP6/i,1CP(6D?S%͖発6|fFW_IY)R*mZ3]'Iv=E8ߞN=GͫWqw}Cu ]_5~GY՘ 3bȜ'avn7k\-q&xCf W^f`32K^. M`"3՚[x<=ȳwՆf_\!HṟӓwF\^1tt`_߿U_7z|zeƑC-2t&'ޘJN#KXEE]ɰyUQa Mf$zug^hɓqJ2QKC‘p*Y1z?ɬFcIl ˢ(*Xnn *EJh8OK$g&idB Ɂ,=+|U]&|tB<8QE.<Nެ~ۃ4I4{-_~ }78B)&Q1}j%i(Z;&7.zsjQ~- l6^Z%D^l]ǥ}rs:?S.0 3nLc6XBk>3VӁ{|ÇvRLATD\y&sL1ꪤ WCvן~o~l6-sKuywnW!~x&݉(Q&R֖;ѶG@ C8ղaA=):Ø $tAV6dYIQax9 Z8GYK+RنP[Ɓas#Y1=.(hSB)+@\I^S'Թd@d 򾿽}AYHD?q>$C[R\fٳ6*jR<1!yK,U<Üe(#̳(ZQ ҇KwkmQ`/3]w&DGJ63Pd9M]g~lyxxؓ/^]xsnDfc;.ə$Up~f{sm E-. D5LhXޒ m#}fF cvh}7 #s9DY4Lq;y\_s}#iOTl5uӅM^K3GCF _8/ZCUZd. LReD3-1[p2 qXWEt(ٮV{q|xidALm*P*!R4e&:( 1JK"uYзtÅi醞""&1-M]CL+R$kX4LG!֮@LX K[QDl xxK=9Tuft>^<1AUel7+VMMY8"3K0v\8R҆#zǏa@3zE ]G?1E4%z- UCݬ'"U|[\_(hS?O v䫯ߢ JSd[ӵGlsŪ1:^80O3V+VelL)Vlךfd9UL90M46zy݋ 7kp8=w/%v5#<GGBUҵ\X613{k8u/'\pxTܞHV)U&躉9L$튈?'hHiy e]m[έg72Ea a EjհnVXc$ +d;HYdY$$8DD c! (ìax~z&3PO~k/(s^yI е'8.7-ZyN&C幈mUMWmG^{{Eλ@*wע-3C1 #!F^ܿW3CH:a؂\S5o>zúٰx=펟G?oX}7);}nsa9/14:%gY&5$GhĸZXrMYec ڔR݉,E^&ZY2[RM'Sc#1 CEJ%ߘ /5,:/7 9herfh %ar9\]pB0 D~d=YnRIY4yi\%Ť_"KgcUN۶WRM׼|U}zuL([J=N'9;$ѐQH{QH 'Q:,f,$_zd4MI<#7LnrFlN~_ɿCh;cRʩ|^o觖in]Z٦Z>+)MKܔhW[PŨb9N66Ӣ"ͺ`4q1kr{N㲽RebOݙ$8LOCx8Rf403I.1"HJ6yLWH]i.ԬS =qad0~0\.Z>k=ޤ&Ɋ%.!,*ǪUo0T4ۦ t!q:3~9)H2l}KwLCKJm4i0*]nʔR e5) W$DWuMߍfĸfPR }xe FUxa\gb1P6b뻓H T:)3KU5S=u0sRLL=T.rir>23΃)Ż@Qj9h-Y.1 $䑓t$D#܏fYh 3»q~rIhfKլM֢Sh2[ v/0Z]XIY gF7R9u]yk<(3ơc6fٰYW2G2 y&h6kUۇo'p~px˻woi55?ɏ/k| S&޼ypuUUo?rz"V:̢BaƲ?$Ei 1j-+G(??r#Ғ$Χ']y< JIe8SUP)gTȐ CSK+s tfj3Zgr5|k^z?J$\Φ.Rx~p>_*-LڑiJZqwo~$Gkڡ'8TMju2pؾeYYo5Ea˥C]9{ijDkgvk~ǀDZq<1YYp"uCK"7'o9d.x||1VsssCjv#+*UM?tPԍ|_%/pNea ) ~x|W,Z%݀O&*#L>}[Ѩ_IԫS{(2~8?'xϪx_kNk8dEA]W\ՉfUlYjXՊ46br߸AO>%0f1J1}҉ReXcAܢ)О/- ϥ}xW 1<=?K8fMY487YO5P(*2AII.V!DPyV K\%Z Rea|WARVcp1f#QR&]ELn*-ˑ~9U-C ) 7wWTMt`;|rS֚Ͷk%Ϲ駟U pվT5ͺ[~yzHq"DQgy"~LS\5dH(RP7UNY$G2K +BlIUhx^)A_NoĪȋfG'Yv;ˁ9./oQ*1*)rSqnD)a>rA#( hi UƠtJ1$FMNfrkQ30lEi汧;?v3EfJg+,!.z I:TXqŤ8 <3uۇ/WZq4MNA> n8?tKG H Z/5%y(J>#Rt&r>0#Ɛl.qy&VIz*, E{^D])+CX*.=>9lnYm7m+Bc^y&QKxYkc (H(z#q!jE?N"Fqb&e(OO{,DXMSD.g2 ZJ[bmpb)6^1Ohhg;i4^{^zFGEƎK7clɛ?㓏?wy`cno觞34[P=T”o×_"W/늲)|^zM1 󒞘E|͛7ojG ^˗/ɤX%ޝЬ LUbEC@48+"Ǽ2ۇ=ϗs@^yfZx?Op-_}yU JYfY c14k޽{D$yx3"xW/t0*z{K]Ry"xpa[}g6( Mx5N\UzPDTThʪ%M0=<݄.{>_ȍ$}3@X4](~K+pU]_31Q<+in<{\m3Jq|4:_ ȸyɰGù5U)qĘ* 쟾^)wȥZreF /E'nnŲ"VUӈ'aHR0 =e3\.,OӅf%H":, eӞg\j(˒f]ҵvzqLN|~7m'ڿ~2e~frL)` (dEH m y.ys+k&gZDL!F @7 5} HkښHJoL@)H IyM0 n&[1җv4J)w$l䘦Djbӵ9l7نKgwћa͛7gR(A 4MQgt:UZMߞ)KͲBP E#I,oUL>'Is ;B@'~·&0[. D'P"/4x&70{ټ+nnnX~"/ u1FJUm&/ 8_pa"<,,8lpnpxd'lGUn]D'GƱkѓ k=Cǀ *_7 1uNL 7'¬x~&/ I"++ھGW+V/|nX7 a9bfج[T5mOQJ L. ;O[D/`EJm0Pb٬vhfGY,dEY6( biQ \?*75ˆ #nH3-e{3"ǚ 4)Zd8 $UsMGНEÌ1 E$@n 6`2\ 4 Gw7Te.'M/QJ;~ǧ~FW;wۖgOH&0NI߰n;""sJ7Eno׼cD?<{DVV\_Ñ?o*dVcl$?z[n a*M(rն{`Hj|󎬘yF@!yy 4n|nqgNe1sbQn.i6WԵԍxq=/_|«*'O+Jo)>} Qe&5q`F&{OU[r8 Bg|GW,-ۭ@QYceF?=];<.$lV[<,y~N%KV49Gq%ےgT<==mYh8Ok|f,B!*'?:,^8bTyʤcd-HplxqB͋PA-Or:Slni9ˌ-Sdf<3777xywNP|*GS*3yw{_?X?- (fSڈV$RJbL$؆V71R^y&(Mᐒ' eYR%UP5:iQ:BΧ{ #z%$0Jd"39#ɗ5~(uPS(ׯ? ÌVV6SO>*k2a_EE%bQUgʆ5*Thm')ݱ(r̡Z)Pf8V @3㑮UN5w$dk@VHJ!01L&'<1p} )%Y9e #QK>A*\6QQ I*E|Q:Z//9Q \R|Q,ǠHMi*~? {=\FQn֤f\A(-U&EY<9]MFYYR竷5^_Q5ASˆ9ё<nO P5FT嚦Zs9^H`A06Ugd%ƀ,R5אW-MS1ObJq&/,AyB~$D6)&.꺤rrmknaUY~d6hTfuLñ:bo`BYtϏ¶nxSj#5.cD$.ƃ1gJi(ʼkѸ馁+M4d7,IA(p8O! Z0.& GCLdy ō EQK0r>eZm߳jdy8;bIRr(!^ >یy!Rf(yQe(MBI*nݎӶ{bpHC󻿃J~ŗ_q:$j̱{T) C]*I [sxO |6Z8 :U(rO><B9eUS%{osig]{~]|ٝpBL]agsqgϟS%.-9Y^];! ਪϞgpnam x`'=7}˗/jdڰueH#NN/w xf:#v=>%tQqs{*4@HSUIѴ|wHe!ڳY4zl+B<ZcpXӏ{ ~`ٱZm(%+\_HAqt!F~GvI/Q2'b\5Tu>2$}ʢd8C$ջP< `Z 0Zqv$:vEҚ2@ ݆cGRL,K&)s9v{f{9ALt65{xO7ax?Q ctIpÁzD%n蒢4H%.R7e]1m`]4,G*aO Dv?m2.LK ?4Lۜ$'-&' g6#d\k%%z@0i&%͛sq[^_W?ߊDOUuEu섦 +Lǎ{ps4M)(2{&{HS>j߂1ײȖR~#@u6W!bhUϑJ a&b;dFs.'Z{܈RQx%$Z*D`BatEQ47B1BjA7IQct`iIl%"W8QhOG`h%AQm vD2ە$DA7Zf٣Kfm~OUC!21Y?uW9.") d+nHgwX; RCFǟSf훣e RJ\ GJ!$=Zx=EA8q}ͻ߰m0Ed:K}4]$ "FI2SNڔl/#( s$`:3.ܷ//Rޭy߼f=8dmQGEӶbØ2t,g'4zh5 |6BBIUUx6!86b$ۓ #cCDJC7ч~-H*SRvaB6 W7TMM][L1&v32,<is~`ywlH5fT4$'\2pB{QIR]Tv8֕cXUl+R˥sG6HzZs墨>( Eqt] Kf!y`G ysX5`1 .h:@+#MQV5"2 *%ta>_իP3c?4 nq."4B;OouU P=[o3zP%B9N`/_!8*˧O&z//)2THB Hs=] )oq}`MӠzTUyXĐa BQ5e:r\Xm:\T=!Hh&א& ݸg1 2ycDmwa~葚cjR%c_ Đ߹)YϩT3v9y#>gW.#Q$ ?I( 9PpuN 8|0(DʑlD =+e~0O4-UmP@7Z@i=欮C:2.m[r41/FEQPuf-P>柕f@f2I2ڮ=Cד|8 ZkfSG1AY #I )Ym O}JUTެ?pw/s_f2% C:h2N&*IQb]0i:)O˒f_0_, +޾-u]3 nd1S-vO>K~_!ÅzZ%FD6c|~'=iE]\^,}?1J޼~hP5.݊i9٣'0R3ҀqW?a^zh]4؄R5 Żń4,v889iءނpسvDei(R C>~-5xwlaGYp6Q*1E)l֢{!/-MP-m]3ͫ8;ۇ;ʪB,(JGj4{Yg43nc n?dIt) T%|Dsq1a]@iAJy(<_.ADܘMRBM6%7?Bj#&W <,L)\+V?&3G9 H頻 cJm~:QR}Ϥlqn䓏[,&a"C/~/dO> r\d(lJD luY#Zl<Itahe>x=J$y˝2{cqᆭ*'$pfm4ȣ6TT` R=BcԹʕox'_l Ō?тCa˾;pFR!U$Ƞd$s')۶f:myخzޒ@q!uF7Il1SF6!pE *[0d9?*iU3&> |U7AaGUN j$BpvvJ>E0 $2ܯxxbR7d($u_ƔHQ@aRN8=yL]7 1\3+|̚Nab. ݃h/mև7_|RNK@DM.uh,dӞ!Ga PH/"׳χ-Ul d>)񹡰c>;e.Hl6BMǬOTqp'>Rϛrޏ=>qSd6(F2PqH!CuGO.89Yp} gVM4A[Bȃ_ё'9P)G|ֲY(SdhzC>]xQ"W . s.PJ[ aX뎚HYj6<w7zNUL Soє>][u?Wo~P>1A9N*]|D-MS~V㼤'oa,h&dl[ׯh'MNƀ UpҶ ϟ<_}5/c$0 1srOL[ c7Q%0MvcZ˂5&=Ja43{W5_{wW/#byk 8`]Nǘ{[./(-1 c`0eIN>2G>2e ,h)iI!c߲ݮysuEVUJea3"R$1ҠK *яbΣj2a}r?O>6 r^y~LJO EFHB!ED/&KRJZp/dHg{B1wBGU%M9C)I)Kbx'NTGK=Y0L̖ .q1wCetJ"(g504>*&%T4Y;\&'|VԚSF{3C$E`vaa!T”n6#)1zRT IQa"AYgSfnREx/?lqѸo_dZ3v#vp \̰6!%̗ t}wT6Y}Hi inhݠg>\.~TO)4R\y:[}k]cQ8RUq;hy`YgHZB'tc6 %=ŔG'Xe$֎TUDt[ Le=PbÈt 1T4heEGRM-Hu=ԧ,fe%''~=( 1Zh?pL;"r*>v57 A#ET9 Ib1c\2=뻬fπ*e7YEOTeFTTFۛI䋷;^־{bN\#9ep#9QU1jR+|) Zg&sK,aQHbN\} E!W˒H~SfwiJ ?PWEN\qc/zxlO9V.t K)?ga.kaeY}1x!C[#;8&^dثl+Fi6=E]C@YHSИۏ[#(lk ~lP=ٌzҰޮxX}`9{4Zy}Ͱ |:NPZST` !!f!$l8p{8K@j*"%dQ#9=;a`{ild2[ayzFL~-a`٤b6GOn8FK?eJ|d=1L9ԑaSnhe2_4׷W EAf=3J %5E!*ch)dUZdy :#Bq$19} t^wT ǥN)Jx|rvgsREJCQU>C>o$aX-|COZR8`u.4GԌbZq-U v%tZɃ۾j&0)8Q/pXlGtO?ax#vFuF B,\\\=݁}? SGO8;]a22cpe5U֑| Cf:iDOfe<9&u;v|ܱZ $J+ںis}Gl肜X<FbD'9?}Y>!p~rǏ#u9!,ÁGO)+"oI)e)hoc vzcNW !ƏY,fK.//hSb}b2og9sBkvͭ_uLi3C)ba]wl_oj)|2oTYGGaP@jLsr~,kdp$ZT9=-vvsC0 ~uG?l9\tbbmf,\[ !D(AQfХi΢)MSsvǃΈe$z RPnnn6ER(Ir88=RfFKHy[PZ#MT%P32P>YT(m>lze0ee 0IBDYWi L59![&DK"CģcA)gpwաY|N[df}Џm5*f[zϐFu;L5SQSmQ]B{;CFQ+]by:{P}wp@RȗI$Q b2ΙO?\e8*L C~o2(U!i8\p(q0Tuվ $)TzD2Jx醞tAdV#EAY48A%DH*za bYRV"z40GJPA 8IB4J0RE2Ec!%0BŌ^=Pm#ד|ل; .QJKoGOG.ǡ&#c?fMi)G.1|т쪲!)HZ$%)T%MSu 4eţK9e!7/Yo)lB*dLȅfvXX?=h:qau۷7/8;;8;m۞+_c:`63:Ͼ7\ML}P5%d~?4̈́ DQ` ~ÿ\tY-I=eGaCdKHR!w6yfŅJ!<]OI -UV{kT%EU 'QЏٮbNu- Z CB/f:x*s:(2t)BV9RH 섥@]h8GBR);VT X .$Tޑ=$q8@)C6O9n)%t*sNHa nd2ێ07vDF3Yo)Kf6g~0lO>f׿(x#^]cmux)˒,.&ڦᣧ,}oRݾG 1FC$P È#odyί> uM]M1)1ig\\). &SNO`LIQYn!?B$B!,=u)A(z0ӆáSJ#m[Q51똝Clz4Fad~.2oٳ_Y.O9?yG?oVL-U3COU$5}#UNPɂ(a{ƣA%mmN_?c/{HiS駟߳]ꫯ8Y^0/v,NfN*ŘkZ[o]G'5)/:l8l)IYҏuQ< Ъ"E#We$1)W n{Tt|>GRtH=R;)yUٲn U~^-v)>N"ZдcJggA1:h#2%T9@`vR|P2B t6Lj4Y(qQ\u#e1'}aoy蜲h~E$W݆a6;.?Э/?top AHKH>n(8:G[ToL$b Օl(H!DzP?h)IGxjbyf|\^>_qsX?\ajG;y)D&mZhAf1?%%T)0+|Y[DѢMD݆evE$RVø_1U{cN JBʇߢy8b>H]k i29y&UÈwԥ1Itr$4DU6,8Qf˕G̃ZS558rI^B*GA!D! um dN=lEATiRHiC 1hχxhD DK"@2Cw,s-U₮۳o|)k\dyFQ&v+ux:g_| ka<`J|V1YVbA3"c֌)nwCoP*aM~' +cLKn,:Cc$G+)˓TDٳ]>0Jmr.Kl6xXp=eePeNnp8PWS*E=E J)=B򦸮[P Ixj4w:AB *BUAO$y!.BD"쐣Z2ifB%]wE4HЏUA?*@w C>XQ821),\w1ܞ, >x%ZTʃBKnFVt~Ͽ[<{ uSp}[n޽X "pq:Ox17/ݑld#EU:E mFUHH1tnΟ\2aﹹEH8_'1 >zc-xc2㦮[vv]e_ }h`NaǼn .g (")$L?7Wo8P4e=M[tI|zJv;Yy@h2@M v(4023;l !otʖ0XEQp6c9h0B my9r\6UUuDUZ. t1C2CV@1JӑAFM 6q~n0Ք p%@( v\ݿh* u%{BJ>xb(R+!eP*[)ϟ?e6[ "k=)%qdH[.y%Git -MNEmfV BLM8 tֳ͙N\}[\M,眝,(*5B@Sap.\.ʆ~js`9p;W+NO~C2WA;޽ɓKn}tPhR gE1X.ۿGY&1$1I&/qzzn7\^$!X,yw^HիGܯZ:\D׷+ݬNk?YG?{G^o^Uŗ_6|3x JdIj9w1-5wK;)ʲf)k=M9Y5w׿$@z{)Rpc^p[̗1|-?{.ƎI[)?yvHeY,||s7@Q:rhIdT(ɀhtMlv[J[2ll9FQUe8\^g:՜_KBx7bՁ>p d~gpt2#znG1mW?]BWSzo 'E+zp !g!xLS DAΊh˜xbVe!' xJ".3(B3#e۠e;HjﶔayvG>e2[݋f>E*_xP!?ԓ$%J%Aӓ3ڪf}$A[}8#ލ$ݶlg?FW??Տƴrtlj~EKQLlgx66$S/nGGaO;)Hͦ >\ Ay06C&HJB _.1EcǑfU-ZBU5)GV2 > NRNNXGSBEV(aF! )4i H!.ϖ!=Jxxʚ\GTGm@eY3v+qif5KmTLfZ5HQ/yG) /s~5Ffv}!<[C]k#ol(LǗw-,&4/&>3= vwf{ϡ{`pDGR2_j9j bql>apwUZeqMF3 aZ0؁~AT92R:Rhz$e2/ ׷|s-RJZ ݊u53lxtIhZ\p!L`"|/9VTN@ı l&lD/c6"1Y  no2Ϟ=#-OY+XNNyXƴ4UN@B[6uV̚O@hֻ-= Hoч_H*X ͗iH>mM.뺁ݾg}wMj.pEO?=>wPJwѲY vێ~wJTY~;l".D͖|9g=߾~dQc/L~;'a2sO7߽6WdB62ɴTeK< xrzʴw#@4_Ȫn-T ZD?"o2 Y ,Um0)&Ou Ǻ;9Q tt6I6q}s-lQlGat V 8Z⼤,%z߳p[ IDATs4gxRYخ7i,Kӳ֯{GdFT6ե( RU4EcF `fS caDQURe*&2۝vcN vu^;gﵾ3aQٌ88'Fh{XN %ц4W+NPT!մiInSoWy`6]5vndHnؠ"4L*g)ˑCQMOf'+ }Ͼ>PEΤ,NJ^S7psya|UN)ʌt8lwM>c2E8:~8՟AiëwDax%}/^k~뷾Ŵ\M;ơCKx:oX(5c2޼| ~TU-WW Bct: #/LuCM*%Oo]Wלr>\SL==zV9nk0t̪)UY!ㆡ(So/߾Ǒb͞R3[";I'[ %(t:;Q0ږ"0LD[F?g <l(!8_C p?aF)pɳ/{OR*z;ބ7Xx sⒹ:#O:|8%Y0 v#nǁ{'g?Ѓj^r8lood2<~ۑH>-D)waΗח_D Zh#2'2B┤Qx?]iTJ>)}82x<ҴGt'Cs*AC"|'r%+Kr-[ MݗFrw6|kIU a; d mrDI,29"9E qd2 II ՊcÛijg0&t qZ uvC;(#7s޿'?ŋSub;1Ru# hѦsahcns'%& (-8 nτjBV\g|y/~ J?iw_]35u0BhVs=xc+>{c|3tQkʲ(5uaV-U9Bj+ewr:&-8B1CgŒl8{EZKwdYQ%Z+ڶkL&A |_!%BădrCG[{6!Z+>=Ry"pDɂ˫UO C>6h1HjN2m"- 맜8?WOՄo|[گ}o]͛ӵ[Fg'}CRFvv#@;TU.5ḱ_~N:RZ~HQXU#}AX-pw ~'L^|RVt:w 2PILaUaFmK1Y,=ecRe}xpўNp[2#+s?*9_XL&SBW{c1egYyYtxRŝyyiF0R0=mۧa NE2U6džѧDi!3#P"d&'XGpwt!"I2Iɓk祐;D"zJ5rZrb*TO`q!XYqw;!5woyxxaݻ7FON\իWܾ{0X69O]}{TJ V ..o-dRy>s5[F U@c^ g,cR1 \] qUeyzmOr m/"{/<<<"eF{=WW&ཛ_gҵ.4bHcm c縼8h-GǿUvo߱^S-?Mk:)c2_$Maǻwo0F1N'@iu<}Wط =ztvǠ="TekI-v4i3 cRbP)w4.b %akp8rh{q%y3XG< }+Q E!Rm/::Tβ #>d,O(uz(f_hBtm}_|phcM\L|?!,d+Tw#/w_^_^9H3@Ҷ=g ygJ#%Lp6)78Rct4E J1U?bK.YarR\Jndc=wLk2+M *XX@?cLDt-aW:4 T%Dg\]HtSMM9L펴톲"1KmÏþgqN B$XvB5yv|1av@!TJ2 Ab~ bIOMX(͌l ;ABs#Ybt R4T4=Z dFOvEm꿗&LM,C 3dÞ9g~)W0E2-N˙bLm2|9gfI/>k$7~?cd(pGM)f݁60,f+ΗxK~wYIXж5u=rJ qX׀oR|8db . ޏ "ZS}CH&6dQD{}-J(Z*2#y3dO~ !ZUN )@vr5!?Ņ]$PbzE?m8nʜ,OL)mkSjl}jBPB}*gzHE=0Ev Q\woG UiX,AБ|d>xŧ~bc{KI==cN;#]?b]2˟%&˨{r~ן!{ψX5`Ύ)jfذ^Ιdޓ97OfxR 10ZH.ϩiǖO?}P=жK2<>~5EAH7HS72cO9L Cz&g #,sb0pw{2 Y,*^8՟pS~6<>LNIt5ݑE\#gy%{?gR7E.."h\pyq8JfEFY,ѣt$dJٱg>05=ms˧~Ooxx|;t-y^XL̖CXb*Q?2WuEFp`t U#xG`>Y1YIU1&O'F|H%N]_1ωa?ٔi:`AgY!钍1)}͓)"o^}lyrUp¨ T@(I91L "uit'ֆP<{&+'n`[zۢDLQb:W' T@(M49rSQ"æ)5yu)k%ל/N_}F7$&޳-R B$vx޲پeZ|+],np,Z{}^Ջ0ph$LVG)CfF'?+웎" df%z:I>!aPO`L_<1f\&$9C1Lz(3b=aI1;A@ .U"u0:PyJC1]}$Fb5zfKFْ;Cg; ?[P*Sx,=zstM2ڤ4^*El<,5xpOmo:RSusáf?\qw?`:+9lw8?HYݞ'Or</~뷷)q6f=Ŕta㑟C.oX;^ϱ?XPU3v=phhַ x)P":ׄ,qcKUUΩpv> YS);\ czc'T>Ru`mܠ 5݁4*/+!TJvY8X6 dh-I{~xduEGּykT> 0=dćtHkT%jJQL(Ld4vGl֥[M!˓ݠ)pA wF$nhZFpHF۴ieUi3-g POc(ښՊI9[ouKn|;g:?cۑ '3D"G0fDHrZ$]HU Q()qc$:^PVK8Z9Y>C Ւݱ{.g+G8"ZV8/x!˳,#39GĨȋ2#݆,z8H )5Jd2a<}EHMOCX8D>$|RbT8x cX-֜GY-/?cQM3֫qH R#F`dF@9l7D!)My^ϙL*m[lhRdrlRYE|{ÇqlA%1HLe2ʬkq4QH 4t",Di}J=&%0"o- }i5B_'#dF?X~Y-k-ݎ@)# IDAT;i<#+5m{`{38h[lpn`G)f5#x!DFPjݞo9EJx6|oEHLYs#]m9f&(L8@ΙMb} nGG˱MLa)[c(p!4 8&HS̠<>9 =ъ(֒,G!Z2c}9d|>gt#߾k:VgkJ[M=F 4u{14x%ʞHEnr)NC]KDʀ$B_ITL)cף 1ץG|Z2*=Zu1)JلnO TZ&g<iUbu=q[M=1tc$hxdX%q)8nw 7cr65] U ݘlQp<˂r+un#7:׌v`4IXPڨĮ>sɜl#_p嚶oQ0>ٜaGk{0P4}Clvu!DeA )s*w޾'TY=+͏iۆ>Jљv v]zL?u(Lr:Kaf)EU0=Á~Tm[+6t jC{PzEbME幡*KDD} -Bdy`p=90rw ۝/Sf)@Ȓ,^\oW7(v{~>G*Lj3?I]c9{˳gdwTeAddZN(LFS7oRO &wLSHsmB zR 1GrJLkA)SD 'NG$a'3,LjBhmG^( Ѣ͔Z !l@j`aT" TӤBCSDtRЏIDq8|zuIiBj˻;#AiإaЬV+JCcCZsX'p^&]+<3E[;IQ-=Ώ'I4ET PFF ! nhfל7g9>}b/(4Z tQ"%c4r*)&}È )})o{Arzʮs31M@nxx|M; dO="U,D"1`̉#b8i8cs7# +^=T/^>B30ZbLfśÎj6a:o>>PNrK0 0Z$+r.0$س l6#/B3m8qtHe]z, g(Of,6B-eJAtvQB#e; e1}#n9n_q8T)yt 'l2? jx':'Urq{uMm&UDqL dv#2dձckbJ%>IJI =)ݖRaDwMwD|Fv()PId{|b~^yn62圛r~{bp ]M۶fafcCNOCaR g3nGңu`0c]ShE=4}K O|$CTt,&儺n!#-&F hG..s`ݾ4tPMwź@9"]`Lv e$mloq0:vP:dJ!hbYćT{:B 2;-zϱ_vN&TBK)SLA )6Ǥѕeנd,1HI!A 1OG1$59%乡*=m3^Sǻ4zq-9l=*12CNV8< |9PBP+5&S웚m#Y:qF51D(dc )( -Zz}RU[5 xHwL0L }܈ 6i#Q2)Qޢ"-(Ҵ[;p!qb]`AU @ RŔ,/e{ۓe)"\*XB!u^ p#/yG,+@jaPZbrT41}yI [PXNm;ҧ>8E1JႥ"Y$RHKy)rѦ{Ez֫sٌ=pevMR!|)%M$^Q{3L6}.o( 3zæﱱg# Ky/߼ЌG~ُ9ذپq k۷$I.NɌI4nN /ʤ;w']޽{G}|,m(m*js-mwH iFQ4MM1ZIK> L(!+4)N"ާװTicY)5!" %)zAfʓmlzN ,T!s'6iWHĨ %%ZfdN!eAצz5kNYb[D#MU٧g-[|{iw3>^e6Cgw8d^LÑq%qG&L (CL~L](1Fo^v 8B@ʊapGCYu\]PW-01M4$Rjy<=x ϟKAL9r&v71:E.V(]qe‰"ʂɡquq9Zw3M#i[),5e) : bEpaqxtQ`]@+EL?fOa(Q$\ Xp!NJgW[)y'>]U"UnrEYW8kZAL %Zg:þOـyP "YD#50P0C85AFʂe>*k͂,8_YE% DY4$tg*H!PBFx0N,onB?c~ӟuw__W_`e{54M%Z&4uSvuvTZ\tZsׯ^qEZidx9}1\\-AzFbf)%)䖻m!9!PH!p1#mS$̪]sy~L(j1P  ljVus93fRZ+ USHm+U H!vι\(2O9s= 1RD8+RƌH e>LMՂl3GpAa`\^}C)F)!JO@#CYg=ਜ਼%$R6+d1s|!S5B2g#uJWu($3s3)Uvw6kH7}{]TEH3h[ ]QEfĐۮe#RX-1nb3=mWu-J6ՂRפ A!0fFdBT5b%r{dJD2g M!R>H#@HEp Ʈ$<&Bt (<3g_TmGݒׯ_#LV3)xVrTx~-!HJR$Bi.HBFh ݰX-2;i93bĝв+03 cn܅R'xHB ޒ]lDX.KL4W?qA$x1h J '@&ʲjdzHUeרc"b )Pm36 Ԛ(iȰvHq8U|MqOJH)Zx) Vsu$l$|>|54T8<{Vh-(誈JB]/s\ӇXX) V;ڦkj,K޼yveZ<;woXuarY#4Q%?Ox;ifGH9~ &r][dFt~B,:М(9q_gwzvrU0#!yv!`VB(TI۶p_nw:.׈'XCݵHOfn_G/K}5M۲^!Eo~G*%ST~TkW:}p<`E I .Cv ,bp3wyvbW5kiZUrAdЅ8G"u حO1ys$Z x~*TOفEϢmos?.`G$ϸ8sxᐅ*5Ks8`Lj"m̢[.Z?Ki2<>>ry~Awٕ< \_.L8x 򌏞ƷwkQXT7~mG^^<090k4&W,KZtqP*lG7s@U #7k scF3TEQ!U 1 RB$ $OpΊbES/qF+F5}`jcaJ҈]$|HL; IDATa^}8H"IJʱEM)CdcSs H V|r(DH%27=ɿ"ZhO"!!́"bmveխxxqvDH80[x _Q `G|4hp.p0gp~mv'abh01O)mVU1ov ֝.6<}3~\yDvN[.8;;Ck4:y5ݺ3͌avX3'ik>=o~GYjb,#!Yt !54Џaa 4M2u Tu "oU.JB"<`ѭ sšH\8bt*1#"<|?{~\\t75y.[}@u4ٱ;8pw@b7oɩ;qF ^>e h aRgC#$N#m1sLX%wJ@uhm{R8[e183]+޾"kdanv>!$Kxg8f7B./87x;, 9hy%H\?[9fmӲZ)Zu }q?#)ԉ3VEnaJ7O<N&Lt/n.|z dbw<>>/͢d|6_c`tKnoх?>C"4enhu݂p~OH B0n1-,ٹWUU5ϴ٠ ,4)'xzi2Xk1Ʊ;`bל__@U ~C777l6;W:aHT4mE],F JuV'7B 4Z!swD)ET, vwmKY뒥Z;l:?á'2aB- C$G cJn&2]xk&)Ԇ7$fY<}jbb0-VTfk-Xc0tKREMJ\"ڂ=D(A5 o|3wpa(a93O}oò׿w|cV?^?^|i?q:c&qG c$EѰ^e0sY.8UCdYxֺ)"R$AE92 \'-gJN8w Q3J@pP-eO{yG%UY|B4uISkC@iҚ'L"qFiǑ]y.F,<nPuD3PA:e*apR7˫4-H[7a uv NzI(]BǘHQ0\w]B%)δcGd@T.҆i Ey=so."Lcm}J׷4s dv!BhZsK@YHɱ$V H%(%` ZJ)Uq ZI> % ڶjV|`e04p}}ͳ=HqDׂղfgEcJSU Bk.;Nd=<9j!l\\.8;$8G%Hۉ=\\B4M]?nEÄ@$E\IM<T#a%KJ!b?< TqFPDIUX7lnJini%f&%VE݅\'D>󲲕(T&x$$$Rd*@"!bLi&{K>gL\_^jB/Շ\_?cd2#>bw[cenaJْ~f[JV8HL4!&*OMBc4,S0 f9"r8r{8D!>U. FHQ0Pm*Œ TUIW7T:S#CJI=R*`m>Th )q2sQٙO! N wo9 =|'7Z(RewY`u.D| ecD A<Lz`& U Q:CRpw_=,gOXE!: E3cb'kAQ+虍AoM:!7p$ \-/noi 6a=Or3Ug?޽cegaj8??Å\̰(5.}[Tn|Ȱ)X,[@j[1t憮kxh!dCV˒cr4hlFnsls6;X.;B| b)< %4m8ySϮ8& %nxH.ێ0"uasbM, UUPWU 41S3QD~1?yd ~f?mKQ<{W4$WAQy*ڦ|}FT9ʲfv17YU5e.|`zYu]ɱ`.{}>cyi,ڎdql6;dˊ0 )!*fy|z Ӟc~K0l0fObeQ2Ca}px!fuH%>ʱx*+Z4`2:3Rtv98. ((4M[2G"q>S xA1(0՚Plw{߳l.i]PLDH*qc X3SUł3pX 5'EEY4튪\4>rd1fYVψb瞡r5gkK>z ^2;AE{Aם =Au,Wۯ0fD{7 ul$MZ!0M#3br!%(~kHQ*9e.YPi4mGY̞y0n=;iԉq!g}q q4!s0A>IhN-iѝ|d8VD ghC9^hNV%Df7B*s=0iA dPLEQR755P\A)ULhRTTb8CBfDbXM` ۧ)E} \?5E*Kqdp)8g2+'e{GXlC6)UH  %"Lc5?yUnVSa:W<ĸi푪 ka^q90dVɖx-)樌ʹ-<_-K_)k6d0N~C, Ug'9_D >sXϬ+?+6="|/k$DUX7aSd #>#_w [/'? _|)_}woc UUQas~6+Z$ڶ$8:U[; .b,J03ZdL(j˂`3(~G5. )#.LlMux1JC8s)C YnF1i釁aYYW ӄBIP-'(ERF \R5K k8{PTUj@[y0}~V%OWa_s}sK.77f#BBNZ8121!u#pR]X{/9[P̳!b?g8쐺@ M RsuCȵ҅n!W/|ć gG&$Ռs Dʲ8EyҔEq%Rdui+~b&HSky!R%Ā3rF_ 5,\6{H4‡xuUs@C2)~s,JWX֊J+ osӺ|QZ0.;Ӓ袠,J1cC*AӴY0J ʂC3z{[5GiJR6%RI=>ǥ2Gfq !B sčK*7Bn}^|5o5~{U_S"ˊ(I1 ef&s5}p3%CXKzIS.JJ|J? 9?^?^آUfSIZ"JԵg8y1Nb8 @j$yRnQ P"M23qƹC;MT8H *ܺMQfq% ̭ "e21"&h!rZp4"F1+㖻;N5e(4]sA- REațjwՂ[22Wc\ D![...V\$$EUrٰ;.O18[b٭X]S|pͷԵcJ&HQr8 F?*_5Bi3Lø;\-Jȹ$E!df.OB'Q8hiۖޛZZߚx;YCVUWꮦ! 0qCy6EޫʪȌ&u2æQnZqCs^~;&u,xxx{\ۅd!9]sBidK+ʀn:4"Ո"04aP2ryN3F'CLL6A@d:/{etؓ#Fpvu%=y6# 1=_Q1f IDAT+DHQcG6Us<2Y 3B >lYmDo1-buswMc)@@$RDp,gs6x>thsO1honQ@"_RKz^G=JIr8 eJed DxI6HUMOf2ރbcHKo{!qAm"W|Gx~lG#Zr ^}E#.TeRFg),Oiҳ(Jx||x<".k,GK(%Qh0a5eUtg3v=wwrϟ1*K^CSLw> }á~-5R ;͓g R7 ][EUp}4FtAHi(J6/'/qk̙lC׌8?)%AFGi.r|!~( nLlswwdARsJ]l@ JB&K? CQzR'#UHV#%$Jh0&q֒,ӌD`Y87Ѷ-Q:R$Do!$b|L",FO&PO|qҴ;I k]nD)!qˆ1U":Íf;qm :(xm;D 4*Ez}h-aQ5s84DLz>Z{sh 3; YUclhb4%{` A=OO&<}E[sZ UnF3wb1Y.\kRdGnEM:-mo?ٳ|%-6޼Kɏ*ls XSU2<{~2׷{_Zpu9c6A<߳{8biXΡ4~Iynyt4^W/ˊ͋ QBȇ|Ls~hkO%\\lجkI;e1[/4r< F*g|rɪ:&U$P%&ϨTP3+d8~rQ>KGb:B@S"8) _ +Ԓ1&i]2[!hө5VN &EePZ2ÉҿOpkY7)I{Ё6=PtwݶMg6FU{(z9R\3MSTH=e8}rΊh~,;qs %@:<1)LVqhzO;vp~_ ޽!ǡ9еr|>v4Mxd[ðewC Atٜn<?%Y>?þc|uӂ\8w1!1:C bݞc]`NpN7~իY#c?ejAɒҢÅ;}<%B`+ME^Qs\L&*g׊ΦQ(Fk{Ni,`\ՌcF|I=$u %WE: pvwGR'Fnofsj1:O%8E"|:#%:۳gdz;p\rl@Dz"Ä1_bY-x:'峧cp/ݻn( Wxqj;!wǑVԳYR?G1M0(84-u5~09w7ndjEi;H66Fg\ba]\Gn)^>{~r8| "g)ɓĊ~bui@IlhdFKV*Jf cd-.|HTdAP@J 6x9 }>}D$ϲTi3 8<}߳nɴ$ )ժ5Z+Dd} x24TI:釁%B&ލeR Tm͎tCO0}`n*ItiГiDLv$1=]ݻ#9id}@t^YYfuiZQ%BmEǟ11LξimQ0Ϲcp!I=Lrb1h$$*p<c4Q YI;GH20#5XM=#3>/rb9KIʘ@>նGKN\^( vzrOO~Ɵ'\MȌ+qv9Iqy~7(i8\__m bȨNa(hN(#`U »F$LPe)vj ;oY1N-өFY , !踾CYv_>"e>+0Zww(Q9uKDƯA6K6"B8Q9ϟ&+J@Rd9C;R_Q-Y.-,~ xM&噼#+SeZ ɴ%gZA0!dZ. ~%ٌ5U}`{??9>Z.Wч_"Њh'ٚ1%QV9o}Wo?wu^~šogtg}8$&ÿ|;3Yۛ>ZS9.X>fz'pJU ::O?H)O^ 0"Kp *0alzJ0"&KެBv-?UC4 PgȌJi˓&tuB0O~w JHkZ{ϸߧIR( uJHrrwwlEdr#7worl &{]^(#Ϯp`m󈑉ABxL{*Y,hUBFel{O84#mbJD@""EL"u%dO)cyZdkPJN%R2ǟLJ*aBwo-\^; JVKn{e2ic0RP%F{@SW+ =CG{ Z%w4mZ.uҺƎ $AЊSMC%5}:QvDHT1GHNBGqy.J֜_XW|կ0ZFKQL@YXTeM7D)11JZ lB(vJ`Scb:&n$@KN,WD?ļ|Œz1;Z"K|B2MHyi)ul`Cg( xdH {Zz#E.黑cs$T{\.Ռ @7hG 1vHyJ݄4n94KC&SB`i}hW$xtl:31Dy*g1/"ӌȇ_+M~k'E~9X~7_ю-;&7 ?Gݳ;4cZ BH۶:GD\c1R3bF^̩oAF^0NG;@Q*C* K9J ?MM7%<Tum+h{HOVr?E`I#Uc2XbmDa) z,4#ERRV)\oH&;2ࣧzsǾq6l^`EM٣ty˟_RTfb^qإZ˧(18?plW^gLs?)0F8[S䚀`>_YYI7v3V4~xG-QDLQO)(˒`bj3|oAF\<"0فq醎zV'_~ H.6g:Ւۻk`tCGĸǞ*ج9HV5yghhɌ9}?YmV09wwI]VJ  Ze=uCRZ$&&K @BL㈋PWIK(F䪤Xdl6+-|oQP"2hU]H9kBi pJsڳ,fy1c0:n J23?Ato!׆\+mmg} /^o\\9o1*#p~񂺞1ZO7L]]NDOL~|JFG;f(Pr{ipKT,+fˌ^ӶqY<ꌓޓR;Z9A>`@DT":DY֓eNRr wQ! i-YmKǔ6(Hhm$e(g4] E1q׌ @DD޺NOAbdN ZFM "W{LnYr^U!3nE:! !魛Cg2g&vFb{i02zftyARs|Hh%%.adFi!;dx>~'L@83  IDAT2@$m)4y5Cs\\\apoCezL2g; DP"G9a--yQC Mi_WO,/'tMRYjԜldB(T- S-o)` #Ŋ g{(4FEӴCHҐӹ@ "O@rBJ|5NIyK觞”c"2е4 k'ɌFg"13 yE]D$FOzD[k!$-S(Zs21L"Fg>W%r/2DwټDrVP --k(9@<7 ]:?U.&k=u9g1_\;AȘO'Ӡp:]`ciD5:gr4/wza8?ېIžcۯ7|!^}@Q=s<ɇ6:+i1g8?J~_IEEVȡ9#HNΦTL|x7`4Eƺ;v-&?\U+!~^#71#H qKUMs'S{v;d)25D 4=fs3f k-M7=eϞ]aۉYeYnhf J*8Ѝb2Ի%RfbbhA *Q,8OߏhH`?#v L<-'6x"&SdZ&QLUd^nvxN5;(Q6Y\d\Lvb.(3 8:p0%3[ğyU8LCG}a}"iؕ+B vUNLyX+ɦMۑ dykA À >UE$0RhnNͲxbSTER+D=eY#< vLr6x/S}2 T ļKΙcM#1b,K:Ki~ =(E/V,5Y947,Mwю;ļ. !97:\LI; <*tItU>#0hq)FzNpp~~0t߾~ѷ^cȾٲ>y޳X^zœKfsl|gL> l..ͻ/7[Y91վTf&Kdgﮯ~%$g#9v-^N.Y?`6S"/e&-5}2`c =ZIq. Lw77&hHuc$0w J rV5QGidps7@PU)PO]ѷ .=Ǥ Qq84&lK%\2zM<É~9 S/IaZh 6 8FBH$snzMfU$ R")M1 #HذxLGTG1ErDa4MCQAM$]N#\'+nzYDz;~d6{KUhZ_)`G`"hX1[g%&{x uۛ#y)Q2,+0*9R|׷ kU MDWhUe{~|$+rTJg*# TUj>=iteZ 0!Ks C2Jۻwۆ>L-uI=+w#?k=7H W~Ϯ~F9.Ο__K~_!a1 I qsٖk~h=֦T8SjOq 0 O?5dDYQ:*d?l@ޒg!F>x!$ކ,UNqJrv$+VS>xkGm_gY=;lowryrG|~>_y=ONys|+~͆kھдdy|Ȼ|@9iW7/1-BW9;>1XC rHQvx!CRj.tơmûiW3R %E5Z Y*3v;OyL!&/ lݾ>UW kF v,Abb.=YBfZ~e톡7"h%d"fpX8?XuI]fK^E! Ubyk9ZgP"AQ 2HPOȈ1m;`lKV+1YzQ DlhlOϫZڐv(`Z"eF^)|0;53cyqDkbAUxgBH`~Pj,CHhێ2J<035yq@1=mc[ nH1#!ްh;v7"$pzz zu <ӋcNW5~GL?v隆  Kn$<PJM$/[\|X cBr9c6?@Ka&B=te! MQӯ~t.g\RVDjz"U@\u=fp{'ObmrVӳKB3{b1gaCzތK 9==ĹO3^A7O?8xNiچ~'#D0>)W/ 5 8&Si1U() 3XrUA(9MsvTUA)*'/Sr娫oU9hh;<#Ltdes" )IR3i;)!D>Ռ!,Jʪ"9ƻ@r e"xE It ;Xp3MΙ`\40 /dY !xȫ AQHZO9:^ޮ+\8>eVp^o99 004!bѴk; XnU׿ &`Qhzp{nOnMOVHMB-\3<~v{k}vi:*EL4 R ~Hi3S :ú*#˒%/$'я1dx3un(*jmKYY\r # Aьh] sWR{JBq87&(tL9߰wa( !eN5֌77>W Yn{Vbg1?tF ;"+8e!`=;r:L?{~R,'Ou q#uμ^RKdz@ }7g9Zdy5UhgT0ڎ*E: ގH{]9fTe٢f 175yLZgF!x!&1!(Bƻ|#>J)~W:?yCvWھúԄ],WeZ/ad7H5'U:N B ^Re'x9^<=RP*挦KQ:g p|dm6("u='й!+HHp6]f9BDڡe{yWjQ&~ )) 5@,S%EQ\d5F䙧kZc$ۆHchʲ"}wx*f,+V?,ASd\ZGZ&WI'Y2(5X7B߬.$NO (1uVaZO(#b܀1#֎Ɔg(Mr޸R1U3˒ՒTbHH=R&NU{bץtGR0A˜Bghqʼ{O])U5KuvDk~O&.E;)vp.`lLrR`IIRNΎw2A2BYf3H8ZK a!4)b&g$w#:wl !=>>}Gpu.E2w4' ׂ`##eYṔBpZaBwnsn{҂\(r"c8EnRe2 ƚeEAQUx,TUAQo7l}xzR,s f#%,yuQ_Bd>$o&e^ƞTC}A9I٬sѻ|s1PװXV :Zb@UXu<<. M?"|9}(rK0q{ɓ&X^Wnc,jF4YotS+\ ffv#y(9ۀ m.|Np{?JKQ(q|y/aFxp^u~(g%/Gŧ|ْgrzz: 3zaOYiBLwr~['[)WMXjr蜫WzR!8NVG-e - (2W19]YŸO-RO#ʊzYYtbZ3t<}򄼘S͠ #*x̻t,rδLl*r=v}ڳ(P1v tM O 9&wow̗Ub_լdz_ I{MT`'^74|`t!%e޻_a:E< UJpsiw CǎeFU v{#&lY h-I#=,rܳGyI]qROo`m #fU1]c4휫O~Bг]bG5?Gkm6H<Dޥl8$a. ,ۼOSkRI&HqBeHUL *.<8 85=.w~B _pubu9u;EoZv>YxC;wi'/Ðڝv'-jL4aI֓オ ^1taߌ(|vfqtnqsǏcX4}Z:qD))ٛKv#qrڊLIѣ,W, =U eI>/\%b^kI`!Q!|x8b2u"Rj6#/4$\ `]j]cڳD瀙~nZxZtfjnD NmkyGKS &8,IeAQ;:u?&Wݖm!5kR tKDf 89j!i4aRtFc.>w{^]8-8O.rfX!2[cL|bzT9>B״y Ry{|,[WD:Zҏvw߽`X,M Rsyy@ӵ4M]1c,ڇ_짼oSG,'+kzƾ'Oy Oxuu5U)B`-"J2-pc +ri9?2MQLxȢ F >ˊ&0wO m31rqL dCc<=z*s-(%8 h={b!7?쌛kvYV%\jndYi}D?c=h  %i#74 kx(Ќ|_ѣ ^aؓg3Zsn9PR7qvz//8;?A-Rh,zӷRf_Et|= R5'[;5:wUwЛ)'Gg g̪; +0n(,SE;;:ٶ 7LINs806`!`c[G,~G ]OU((ʊ[C4j:7Ms`XÄ'8l6>Tu1iHXO]89=ʢׯ˪X =7[a{I' !bT߳k6Ds"(G>-`p;%}7B,?ḺH165"^DD OKʩ%}X,V]BT!K<vXG(3384gA덿Xg~O~ǿu8/#,xEn||Q`'6ڣbb/ֹn:5ZIpؑC>!p8? ‡#MQ @Y\^>$(˚>͆g>a#4ϳǀ,<&&kL"YDSeS;{Kd"mH)Y.XrT6埳o,ME9VH!i=G\^(ἥm;ש+KWW/h۞'apبr3pf&9'''lven2B/>)ѓSOޢwܯ_1RAIs1=R(-Z4tx#2VyrNcZ@Kyhʦ!##/j?f,ntȓNLϮ#}SK=8cXɳSd9AT'$vktJ%{KIdF"KςIӵrAe'@7lVBJl68Ol$e1K,!k-!t&ڞ8ʪ&Wa<;ַ zNB[`r<'08؆BHizL7Лg<]1(FOD⃥Wsa (q||GOȋ0f$e;+UU23}, (ˌ"v;...x]vMo",oڌvBb@JjsMekE}gw3ZHuEQ uѶईNOXIXcTgGq @(|<\3,QY򘓳3mC\0J '$Ԏ㈨"rT1 bRcetIQcFYjk)t>C1RbQX.M bﱾg4.%aIti'6]DNn~8 ==ݡԒo|#ΏNXdJ|?H֏2!\>/$y躎3Isiow{JQdH/N+f9ݖvF)fa41 qHAG @)cRך,EA3VKNNϘfdYj>T2RqhY-8=;f\@ihz)MyT *ČXǫ=$SJLrtHp~FBrʲ@*#ht6)т,gtӷøz[stMKV,W36f EooZ$Ύu Q*]2{eG_"':|동-%ww7Hϗ\0͞nlfɌSET{' r|v)7H݃Ԩa€1>9LY݆?}朝^˲ d ~w`6Yš%FM1y.)(/.fc?F׬9/_g#I(E"=W_O A|ƙ_~R%|ID}OYkڶet !We : T`R3I 4TMQLxR2đt8Em3HR 9E,px~OmMEOަ <9ƁpI5zrbTR1UWK CRZ]=1X. ~L))j%8يXsYY 2K>a>82$vSODJ1=KIx9ÌY=325Ps b(bLW7<|ṫه\g>8qKYiTط1,5BD !X)Ϟ,S xR$xSW}*) BHA!)aX' X3uCru^d !ϗg:Aҏ B|LjwYlEtً>]De0IU2 EbHbjXBQsNo2e}-/>;nngˋ()6wkpnDE -ဉ3,jrsS RIuh%ȗEfgz8:8ZHcGG9U  >ckh]N3fal黴֗c~!0ZڶI;wBUH(茧3s^^t/yX,Ja$8G (To%B&"Ff~qE OĆGy ˪x~hzt48,)3EcJ'1(t&HME>]Ҕ.gU*z6њwI-Sg"Y"Ĩcid e$ƊW-nro|e[qyyɼ^mخ7l[=5(,CCXeY>XPf8>`^m8R̬>aҬ麎-@|@+ ZI0P {Y^lr䈗prDkkUZ@ onh0._Đ,+Ro^swonF0NO` Soﹻ[QBrzvĢC:T3\ݑ)8?C =@+ɣˊll`XЛ;/ü^n(HY4~OIK0&2P!=~%#(=~7 @5)$X#U0 _A*}y ]Kuh@HKǺQRy!,W >G[yA?@h<ŌHr o:WobyS[_1v`{Fk!d2ׯ]N-1#Т4#/kV'ǩ») ]Hơٚ"%thQb& >񜝝RKi!U;xtzFݦPlxjf@{%H>{ " \[34zi_I`7]A5m}}~_3(-%'/~?>AF);O%DQJ-D*b 9!&,fC.~Ș?Ao7ȿo)erBkR6 #͎q|tʮCF2)P"Kh)FcLp(R†HLSuͯ?MS>MEDaYI ‏Dik5Jh1|,+ʢ&2VG 2N&p( -3 HQ ak-y9J(zV G,>@R5eYN-]>*|-*K?aR QcH"ZKټd?{vk!a APqt/qrrJY|ElerqLYjvn8b,g \ɈT-Gqxۣ c,f048n Ұi## 1$ (|X#c-tb(%Pӄ)~eL3`@3'׷7dY' Fv=>IFĠcϤӴN0tZ gVPƉ㣇\rq5{{!S;<=g>S9yÅ"7W)b ?ئ)9Q(A)9=/M g\ɡj"}$xATI9>>,s6>'!#8B乘X!^DD?RWKNWu]WcB$rN9=>ysa|NTjʠP5J9!x@DIp.RKαwdEb)TxЏ=1 "9s4M:wMf9OKĻs9MSԦg5p$SX)/}-$у\^^o^xnZO'o,/qO~fI<}nah3h;\Uf'D 58eHUIZDɱBR,vddVYF\%Li#Ƥ,<~T(b2M] fk{L{gܼNbQN^(k,Y+h98NNɪ9ܦN;3hJjh\7_<j;R׊2/TZ?www+Ak<6}2O] zuMQ/nnK!sh6ߜ0e<|eY" 2jIQRafT!bd %%x\C:|˜zI7RzF|(M۷!az!̵ib$8 S 4hieNㄐe3D c>[~Bi DuXfS?$dmbwU>(v|o3oMKjVRȸϕjZp̚W@$S!3}L&̌=W{1}mVvU !zq"Tpb^BK BIŔ5ܿbnݻw g'[gǼϰY︺ɳHY{}=A'Oa Wk.yo0om-\ws㈔ c vl6w.rJD)9,W/ǁ&EZ4MRiAS+*(c/NNMS%)l^0OlԼz䓗,[TV^Q>$̫@]D޸sbƇގT&+|g1F#dūv[D0Η̣ǏuV@s`*VOi(FI:F0kt#;NvdRfÇϞ$ 烏>;\UW\_pu UxyG"jZeC暘@$.hՌ݄4MV.p~tB]׌K޸B J]1=}?R+O黉nǟpzrRah1GL]FMD̟@[-@ ý{8>>ۜs}10ٞsnK*VN_?C|K|8,c\mwXNl/?gi'AWxE!Ǩ!)E %4|NB˔HS ZFCK&IQ}+IMAQHQ~HSe+H=O>GRJ(" h꧿ksGNShPe 1dV4:Fα44`P!eС@̓\8s!P->uZ%y2 V%1M8\o_RT!" d8'qC@ɆVi@$L_0R UABAYΩ #kTg`*89;{w\մ.89iZnn6!e[k{( ,QJ1n"eX[;I 49r"D .俇d޶(!u],t`ZT 1ۯ<(4̶)44UAm4 )0q||Ϟ^(c?UT%mS1k (>X8QY,D^zI!55UӰ<:fͦ\+&Ĝá,=ΏET59$Xz}vF*Е֭[vkl-̐=ۏ>̩i"xueY >kC";ifo"QXH2r5JTg~͎l5]PWEI,)í[n2+Jmo{Y-X,-fuL'{WEd4MElv>3oEU$s% sN%~£sƃ{ӓs>[eԂg9)z(kID oZXA>f{52 24y>?k( $Qvݓ|?s\Zs}}nùWWf]Yk3̀#cO4M$Dh Ȃ, .zVd e8;]B8ױXrrdK="9&f\yX6x$ԯ.:@RT};4 t<HGLY۠cͫGbl *ծXT+vN]oZH1b'r #!)ohʆ~m`1g49lv7itU>&KLgn>]rrqBӫO!VrVVxR1Ci 'aP JɓHUW6E)ʒٳ߽b:K|oҧ_߽]~s4?'~ k]-$2eY81Jw@ŧ|ʈl~"jR9kQŎRYq8%TdtG-U s<'RT(:!A-~pVPBv(UTAL3Gy ?o~ w?- n.r3_.懏(P"h%k(Q:H݆Sh\sz 1M( 7ym ĄӡFVԌ$ID3=~n!&F\KGKL6M;1j <} ]"c=Wu۠furNY{W)!HHa zsX4R˪6Ru,+dLiHD| 0pQ5a<\0؁]W i|& t"M0%a՚{lr+VeyڐROk'D陦-"fA^wֹ]C%RVAH]hYbT -woL 6H) W,)٬[7>DEnxdw i;3|TIP'w@Jmw|jډ,Q:{UQb8>4)ODRI"Gx!VhlH#ُbHh-;p:2%&Ws23v`򤔫xm[#db{" E0D A* 8(mJR}PEɛ3VwgOQBdu>yϟPjNNsZ 6or~yYJ.cUy<\&k!B#2|=,B$$u1CQǘ2q6|dC", ha ں15"x %OƑ uCԌ.k`s!2k nhɓOl"C|]*AjxչOꈦFwYRnm0+s,ijLy1H&0:~4H"9U$"VR(Y@FlNAE&)4 |"JSj )5!U5C!IDh %nv[t*l|Py[NN| Sf %)m&RN1A=U"wП 4 HeTjwKvA3O!%?"Wh5[WB?RYg m*ңif4ӔBwn#,uNpܽ{٪&(˥l ں$pn ucݘ1+ctH;2'aSHUmd ժF+n"#@e$<z;U6)4ӑRTYpjl:K.-eݮ;AY5 AhW s.739ҶR) %m&da>mS܀,+=v˸wT%|7yx>GnM)rrRW-wbb+._uEE[749 Rկit3tdP]g7iWgRԎWH1Ҵ4",0+MrY]c"udŜ}eOU65E ѳ>iќe>cy67Y|drβ= W7dpz~d@S0+g1ET\_ z[og.٬9KI"00L=GqH!wV̪*hsAKL݆jbze}7Ze 4"E~C bb^P !,.q)'}>o3ZfM7ZnѲ-kpW))y拖%ٌc(vxB_u uSdM{&hv0yv <@vhZfq} O~+2?c_{Wk)s3f~Dz7;ewCʼ_;R, ~40Y>֦i4I"3t x+.(d)]EO qbt=*Q!7Rsʼ,s{tY R-4ZȒ"[H/szGHJbyp.FX,)aFwDѦ{x ޻|kϛݳ;_k߫\s_ӯO\i%5n$){И`8軉(bʼ̼9}Zч|K$"j8@Mh4I  '(Rpx0,4*u H1˵Ĥ(KI%+Le"Eְshݮù-R: S6hq֣RaK4TIE4mˋ%׏IBcW%7f-Vܾs«n6(*wD='s4bC$L[T2,8;bvK;gwy_YHq͛>Ͱ%8ђ)TuEh{%1|//_见VOLӀsS7 c?\Lʢ"7Tg9Z6wT껔TMU8Xo QC|iI>"Ia=mWh#n;W3뉾E 6R,̷f-~f2 naL)er2tP; :L./@Uֆ;|hFeNedfFÅAide""E*c3ډZ *ssZiz hi1'A0 <|s<{J0NGfmd\K)MV {jr9Gľ0N}w1y`Z39OnB.RSRD@JʢF8XȊ IDATqU|K=%nd\ P V8FRa@x6f+b\\\B˪F U 3gjJCDԯE>8JR4!eJ )B(NLJJzsmRZc, t;D\C3$Fٞ82eBmt]N ӔR!}VprRA)&sRfr( "C<0j_.{ e(4qd8F\__@*'ORggþ#\gJ)Ss9$%Dtk:E^ƁϼPI@]拆yS$!3qnI12M#vp|os3vn@n.{@3ÄT}a{ ,.Ko}x)Or}M,pbellBE"-) 9E;/PJFG7y1*/KSEICL)% OX,[F(8""Tf52DS\ٙDU+¡5 0?o񀣓S1c999&G3[1[(=3MhJ#ed빼AvhΨʒ 㰡cM[Q}NW<~ƥC['TuGp( *Sq]EUc+.#zM -?V3~󷳐cɿ 8)ųGXk{6g緘&ǫ7q <}1kbr.R(ƛ1Ogv2_P(5znq;ggH zt1E]듲}u v>v,3@C}`5.=>,^1)X&Ǩ  ^^3>_4@h2mجweXO9"s9tϞx4+3qM[qyy锓vI}!:])+y;v[U"US㝥 vr\\<!e R`Ս1ӿO9K?}+o_C/_'ʟ&ĿS?NJ>Ms?K礭Rx!'@>eF@f;y]Je^ V4 g-þcuÀ֚|/r* Lv;O%#m) s$z]4lwTen YDz1Wȳwp! sY2ujQ>  'Ă|NJvTA𒫋=UY7{g7G=pjgFGdsBS"%ISR-M#iZɮ{ū˧H5 0> g <1HBЯ`t&|JB$âI )kqXL!SC' hHBV1jUc =y|: ';k7'Xjv6% exNvrSՂ˫ekstEJ%a(U$MLnv\\<ⵞ=#a㛗2> 1vke%!FH@i M[1ւ?# >=k)LN$t]6L%)I CG \%@- 截:,Oh%Kn߽uq-H 5WG0o3q{nŌϾsV2 VH F'kOo1_ڼA1q;HW_gϞJes~{J>.;m~aD;38`d@MJdN%Q%)c 8*Tm% BLD2@֒G HQ$)Y$" ]%EUQh8(5@a DQ`T]jBLad y9o}n3S7袣;&)JM!)Bg5#PKTJ!UKZ6d(,99=e}Q5{財:5}gi,(QSsz |J!CB(b͢g/syu0UScĬ%nH89;eo[R %>ya- y!q?T>dͫŢ0 ɣt8Zm~,TE 0\k"%>dTUj]\`:E*t2$8eYR͆]Gj%$nE~8Pꋰ4B)bbtZɬ^RWLYBd`s'jʕ~O۶P~#JSlK#]7m*7EN<1y|a2?.½X2$tN,e֒RU\]c))穊C~nnZg) p$'Z1ik]ćqʋBSNh$)G vʵ@}9=@@)Calc*B|r=ό45W/GˬlHIf9軎zDl*2SÃs9\\7(&H5'9Ҭj-kf ͚~QEÇH ?fҴZ̏ m,GTUـ5Thfϳg4:GUj9Ň~ vkG7~{=w>w"$H0Wt4 S=`zxn=hi$53JSp /3">|#~q>[m>ߢr*?[wGx`|O#ā뛁o~w/P~o;:{2%0R鬅i!-fL+^vWiԉ\vTMlނ(]TY&H"9h >;hZ6?CV'G>sM5c~7h*+-1JzX̎~=J N"///IvRS6!v { f:&ă{)ʒ'/^2#]oz"DbBׂ>1dp!T^u)J,+fjœI)es49,G)v,W wg')MNLÁ%G!Q4zY@)=WO1LkT͌NӜY,Z:nfMԸK EJ+@H ZUGcb|#ZDUV592b7Ltԥ_'K|+I%w=6"CEV'Ef_dn4I 5MSQ$hIj4R:WDc)S;RZ0!>)duu~Dx ECH{jqCi68F,i(L6Z*c]3_ mbٺ1QPβ`?HLRg$"BBh)LњaYPF|Lh&=bȀi/Y-W;`ʄ,?wڷ6~/r~+޽ϟA 0cFʘ˂[( B +~yaϋO`мX(˂a2DI |!)d6r y ȼ4q! \JBB<`">XRdPRl<Kt:\>#M[ 0݆h=u&GE$=VB1}ͳ)4 㖾ߒD@rL%)/@TDb&0Z RbutÞ]AxD(iEF|=<} Rja=hpv'K$DqL4ה!z:qmf0Cxj!:V ?V&xGBA1%0QT,h昱O0S*5/S5SG z $RZ}3a?DDpbʺ{SX1&n}ֳ=cCC $ A"1Cb !8L !@( 'q.8{w|jg JUϮ}躞'''\_b-Ɣ邪\7Qʂ܀|§4Y1\.(RzWLo\@b`ް^-pv$*?sRƋ0;oƶh4,j[NO1U߃{jBiRƵ|RR z[EgֹH"Oʪk6'ǁJ"='y }|Mߜ3QY5=|iYTsfFѹUԿUAGp@s@4rIQ.1"G(T) a`%ĒA0l=ZH(d` } ɔieIYQRBiÁ8.)2};7 %Rdݮ%DC"XYe^d rGeޒ&nGJ0XX CaYe ڶ#i"ڹ -P`q1y.1l}VЄih ;ND 3lYn`CYSTz~ϓjg;Y.6Ǒ2(!2(5Aa2_̎aց(L2L ڶ(1L2D^4uz~xqNJ@;`hho`Uo-\48*0yK{$}]l1؂.hۉiL^,ko&톋 y iƗԹc>| DnoF&)e.\)Ef)֢Ĭ +2jtp30DOt@uf'Y $dx۳40BbAaN90ý'woIf׊f$F8;wo!ǑOp z1.EDSw1ry^@SW#H쎷(x w-?4!JY,5Jyl;9rusVq F_%N/_h;&?P/kB_}./CO? ^|I [u^pʒˋK.OGvcEٻ=YexfC{Z,dV}˝4JS<2 ftYVj8v-ݱGy#UfWfaw=Sԋe4lOM NNVl{ˡ} B#Ev{>:yM9?Ozs_?)e%,v<0 #1ahWOlK]Ibr7 71JG=m( ,YV8M8Q[G}~^I\m&! ھ윦|pBۿX,+Nk"^O,+V ˪O?OzGNy~ W*)ONkH>Ur7`GH$e"vtCbgǼN* .h "8|tɐ_=nle1ў'j ED.+">te&39*7h=rsfrVo>2EΉI!b'lsHpa>a@!gD!%^$"L <"F|L1Htʚek.'D <2&cR uh@HJy|I-Sw6 g~o_i__.%A{hh99=lEo0ҵX%ۓ%Kۏ㼝!f݄V="Bd/I- ƚfRy_NFe~LؐRh E͑RC>qU)5|!x/0:  ?ќxG\bЅ{ETYDB*-1)%/- ;(A> tޚ5d.lֹ1%=.#)?ZtO>oѢtWF!DPBk朮s<9t$! gﰬw-"jɱkYxǛyxw,U"H%%-eYbJMc$.._Yd> 2ysaT;R!;\HJ?bz&b3B\?|gbBHK !G kñu,IQʠ;q֛5bM 'Ye֏'%(M1'v9rX?Ў|ii̼jPɨ'y&?N=6H( a2h2 bH(_(Sȅ*4O$-c:|'SxmhEO(E yH}.JEw6f׺\Q&&L2C)Ab%1)"]$в>7s5'k|aH"c4X.=d#12M!?hہ^Ԥs7|g)-E S(!Z`*HO5ϯf )}߳Z~xrG{c)x39﾿!Xcfn}ϘD,PB)rd{z0X?|w{~R/,>a ,%ņRi$',O5'x57zj]rhBb-=fwP=kIS(A>#a~ %9=$C-9_Ϟ %P-,֞rq!.'HbcRW,ecp) CH2 ])ʨC5RJ겠,k{^YJ} 9 IDAT RF,%t@KC.>Kj]c'\6pqX|6+w{6W2_^pxC߰.XWh1?34U&ymN')]{LVɡ=y W Lf, Nζ ]7Џ~xuM h ,kʢ[*]aC@ƶ'!b=憴i0Fbෞʿ>|=S>zmNק; ~bRoxt1պb31E"sǡu)謜 )IY Lqt?T1U\^>9>'|N  !R蒢jڣeA"!GlhS$΍K1xBl6kL vz;H8RfU#}24:$(+8Y%k0eЌÁ:֋%r g+b=wGGߡWۉ{9[sgkLx}~[˂qړRnt ](kʲFJ0Zu'wcI88-˺ٳ{~>Iŷ gwK>RYYPeGDCSoj0#_|ݑ n=wW(c$]wd:tf:Ơu3oV qwXESnֲo\ c6Xnb>a^Ҷ=]wiix<`tm?lQybȼcg3X=Jp̔![|)`r<>P$4%@FI#\>fp{}a 6Y'lx(s$1|0JP%!F7ͺH; #C19Q 6C 浘 #8fÙs c* HJmp~iN89!Q@'XEYaLtC{˱]$AZye';F;ЏqptCⰇj* R4@ɬnpwcin\HAYTx7eccJHStZU3^$wׄ7L@%SJ/I~"z\"QTUEUa"P0ҵ=E2%BJJQ |_H6O| >b4 g-];3ޯ>}\sfl_>e`tI{<"-Da,V{0 ?ӯ_q}}o[( Ϟ}0kˆf99p1feH.PǠY-CoOŻ|k6 $(BFǎȉ~|Lox?{y.(0a]e1 4MYvhʒVX0"P3=?{R)\\__ݜXmfjzpX*;Kr]юLljfQqYD.$w8譣46%{6 63 h'F;qq'O2L=4[1Q$s ‡za1eQRg_`blr&>}9871Y6RCi|EM(hL}CpΣU8L<{ ,|<#Y}QUaF޼~8v@+S"<3η^__Mi?_O~ϟI?*&V:Ripc˪4 ;) Y-V8[DX'sszΌ3e4'+MNX9M 'g'}Oi\vmQ)1'RFiRp]bS`:bZcN:2#HX9=#E<%EQ N-8ΜF#."Zl4y8s Fv-Yʜ[TNٮffNF% mN8d<֌}+; CU5, Њs'ujSSju=eY,űmU+j*W$ ߦ(~NB?x&ucjzC.dEu;w7HRu#qZvDzMW_lP&qs~^G -Fi֤6!GВ(5 l҉*Y[8_bB;QGh!IIPh*'|H!r^Yܓɜh͜OJsw/9~v%.Q5 jZTNF1L:sJ4jYZ%i}bA^ v(sbeQgx[0!\\0O$JJ$ &M=RV(PaELEB8pؿ!vru{ɓjO~GǏwwi=tz[;<LJpn ;AՂU~)OJ"_pHyeɲn0HU䃏ǃK?r{{ZٮX 6 DҳZt3I"Eq[bQTECL.T5v0L{FX53D N$D:P0|@(˚jf!뜼> .˒r!$F< 1o{U ;Jh#^; h"Dhm.'ńPH R*y036"K-cin@4Z( ;:>HyYJX,4!:7PRe`R2MvX7}U8 $<$!OHIpH<']sɾݱk9ͨUU0\OH~VgvD`OUkg.l0նa8e~znG(xCC?x vkP6E=Wbzo;|i@\I!y0d)1Ei 6'[Rrxf$'sԂ咢 D5 zq2{8M(le7M;Ɯ4YC^%M:?4*S'J|R!=op<,V9 gzO6 vMe*bl0ԙ?Sk1*bPż &+F %c6hɠȫ1D^Y6.!PW5~ܑlB\ĐOs3 olGF:a~f!@MKD(CXЏAv{q~~ I$ڱw5N-T/YI)H(mCҀ<|C׸M 27*;-7nJ5n".39KBz1Pж<|<u5HU(35|H)#%_~ݘ0%,EfcۗB(84_pqg7a*@*8?}W/~Ȳٲ~y Ï8nUwܤBQ[. !ӭE!Fq^ sA)JGkѪf!)i˒28;pK޼Y.)ʆfO5Xʪ rS #B nHQtI <͍X,rC'>'#W%h e}SR )A*munbN.,lh899}q$HQ9=RQ5$7','KeRYp*k"[+>c>{|;ŋ?dj0Z{Z=w{|Zy5;JbFȃj#5> eEfف7Xq9xn.x\~n/',jWLmQrLŪ2wL"uUPh=CBqwͩ\b ^P/Psm'_srrƻO "b[8oHޏ~'}3~+#u(`U?hmu)'FQ4 m:'WtF]kc[2sq PbUP( `r_}N$UL$z cfM8@m e yP%3Xx;؁$<ғT,1.[l4M=QpzvAJ!CٌEQ!̲ $8HXcu Io E=Ds>ft,PՎۉIuCY5H"'eb{VMc'Ͳh[4 @D).fJ~_WCG~lM#uE]+BG0Âm1M9<ڑaOYU7C=Tؙ-d&#Re)ةo@93:yij"S>RJbf(s'& 3Qbύ 1_UE.0EvD@ٔRbb@̊(i5 9кM~Yzfi  N @umjͲiǁq8F?|'_xCD)[h)ʊ2&#h&RPVK e1R 0WXw Ǝcw0t`nO8ٞ#h'feAru5wW H(1 e,TC?lhtƢ^Rι\?y (XsʢzV>G4Upŗ_v49O}ZtHv.N4M3]̅d+6u) EYUD 1T"D0#Hy9|#NN\~p45ggD9Dz!WB"X3CTUNH% )tHJfby,# <%*tI -+ qHacII AYe9T OaJO>6HWRaTNH2kpY4k.5/^ݞLv)7#<#o#C].LV%ZLGO9uJ.,>f\1{Q3=xxc_rɷ?~NND<7Wy_~F8V$oFj~?<톫7/Fq>7cJ3)#5S%,y# y00d9v[!e Jj٠%wE2_,Hab-CE i@k؏m>\.! ;<#6o( {Gw2%cYE4:K,-l<)3&c$Z)MCU4-5nʟc-Z*RyU$&)kɝ̀lԫpҏ}Ly]VEU /9 %b^R0 sCF ?6:"`] Zs,@ ҄R(.x⎟?O?w~"khjEt>/\kNYuUqzvÎj̱94Õ G{f&n޳X/ )NO/!NN֔ijd:R<|}墦, UMU9%" I  zsGT  - EQ^xU]ѷݾK<O?sSyjfyBty>\orzk'qdt~ScS`}rJF?1-"M.lWiNCg.b4RaЏ p1d^K%^PKd4`xssh-JkQ $G? GhQpc6f2J+Bbn~w&łz)3wL6H !f5x]a]-HAxI1Chm<}f ID;,k&FNs>,<+$Bbu) 0!woqwhsCݎw=1MiR@IY/\Q(憮o2؁@%.D?{BHSm8>b%3bYS͏>no߰ٮ]oՒ_<}Ϟ? nGQQ.&u0)zd-nDp~e]q@r7x恵2ߛej`0n7TM4 (-9#1L zaZQ(& gg4Mfi*Vl={c.C2[E~4751xh{0"E)v87n!#ȦJ?r<ϥ) wH"ɻ,DG=)J%|'/_+}"烏u;>xzzwwS$65BI(65Td'zAU+vsdKD%>rA &OX|`@DH3G$){ fX雕 C F oLs38&/9W )O - "JtV&dO6qȔDI&=rxn0Sfp;PP jbY/YS8==gڲ,I)p}􆫫+P9m1 9,ln5&:)DR[(q9}C2l7 IDATc,\?"!( P, P@UXkiۑ믈riH@%7W|w9={L\=רR;OPFPc{.BHG%!I_ AϞI 9͠VGl1aM#_>CQ 7\\Y0}^a%hi"[GYkQvbB)V5F58?Yܼy pC`PBRB&TjN6'ŀsh9[֛TU REEy%B%m9@J1 ʢA8{K|"8E@L=u9's<,Vĉa8n1B;ϡ=Z0! t^A! R6 'Xkf BHqbhX3jSGqssC3NOOQW\\ٴUAy^0fu5Zq6PD]ޤ׶lMzF55WOy#nHBvvb$# &E@` Y р-D! DV"g*33o7ĩޫh>g=nw;0:iϖdEfg=Գm( "qL$k#ny9GjBi(Wqu|moLJ%?} _}wޡ <{~~K@x%f=Y^pd296 fSgZ*vCe<~z4M12Gӂ#AmrL1C`mN)n%EV`z<#$G1+(f7 -yQֱDZufR,Y e4"3Xǔ $8K]d`FGL&Q1#v2^V`Sj) q6R#iXNƈ`Ч ,8quSι吴"c{;zl|b5,fNyuG/p c#*SSE$?yLCOi*7̀BB$CUĐN,B(lZ'ڈp0/\0 -F)ci\ѝ.eURdM`GgoYVx ]5w)g'?%:~oO~|8Hiۮ90M"VE]sqq0g;'da(aHpZeV+_s$?Y<>v8۲?O_|-8l7ݱoXi>Sg,W ~I_}u˔**޿v8Vm۟*:q[>oZS?QK"oFluHTL f&nO{܂ԥ!vR5{^BߢjuIpT\__o]i\K Yy`yP !-vtz@^Zed{ qh(BGDtvPDȘ3+Jwkad;ps$=c(Kǖ%GS*e9申%qDSWa ,gX!u~E"e]f#B ɼɲ Z]?9pTTeN01ӂce>$ dhLMz5ݚ|ElV2f!WTc6%FuEi D>4YΖ\,挽E:Nv %4Tg>'`Fʏ+&7dD r*H&, "nџ,>m#XV&fةCd2$(v-hqAȌ⒢ d,bf#S'cW"4}qqvɯ~S86{ 1nȲ"/ CG%nqa>1&C(FuJZg!R˫kcC!ƒ/?'OwWD-IX.H=`]P02Gꥇ>BE 1MÁ2 3 )U:(N$|q.&BvPH!2I &؂fJFdJ7ѧF=S%zK]]ﻔcqѤ c ;1x"AL @?aJgM|I F+~إMs(˜m1{)FE/FOI Sl=t-2Kk6-j?l^4 ww<͑\pyqv"ϱc  Q l/{23 }+o4 _ +4@^j2a=Fgx>Ձux&T9*| 97EuQ cDfv`O ˗_RYVMQ8Kpܢ(躎L~xgYgx'p>uWx@5ve0hUE5m{|E{KbHU0;y4 Yϸ}gϞc&l\JeF !*xAe}i g<)Wm"??W痜]3?}n_|u="h&۳:s{{ o߿a(#qc:ўO$Z'5J!f6g\\\puq_uZ*i\Jc:mt''S \֩v6T+rŢ*Y.CG^h^}sXq]4MXfDѠv ӄ ;XOLRUj#h$4B48'pi 4+К@%|Qq1#2!}ΑFlX%RmYmrQzlSo^Ղo3N=>%K23^iG&pS( YuZJ2+e]eD=0F}pܱ<b:[R(0HKcDJX) 6 2g3&!,RՂd4]8:,{Rn@R{TL)01՗oJ}/w:?Yru};f -&)%>mCt4 P5Iq0%!y?zʯs~? &<>d;8ʤ—xkYSc~ϿR'wk>0N !"?Z˻7`V%UX^1ohʲq/#,Rd!0M<B G>{nw`dp}qb,}D H/x9}vxSfE|qKvˋwUƢ^q:*k{0MI/9;* F6Uj%lwUZT&m$ʉXs]1K9Zθ:%r<&A2ٞf2X2=!1:g}GzDf<}=کehh egZg-chG̬;Z1B"sz8%Wb2fiLӄۢŋO"ondmtf> 9)Q.D ͼZ|cxX{=o~R^5=qaSOi$1N MOf=T!'IәE %V~1aXB"XQ-e]RxzD+UE=v[4`,ǑWĘ))zMI Q$nI%#e9ϩd1@oEt1⼥?GNjҢې6#R Lor%',D bG@3 - G=^m醖'xRnp}"dGQqpIyJqw{r=&AU(jbpUIVS\U)M154b2臁i<@y8t F$@;)lfIcF;IbIrtM1J.o,0ٖ"d&Y%!е#j[)1YG-o}-!Ej%Z> %eNI:ط bRV^@A.2eZ|$Nt<Smq~*$XjX M]ט5z[uGZ(I \sjF^Lֲ?>mKV-<RmҤzM6_HYFL!bV4I@뜱LS$CUP)6XL3Nާ 'ǎuE;&P &&ɍl#=fOQ3LaDwĆ$!ƈj6CF]f >IaDU!7\;G*p6=ʙHN<ǔwhnn[G1% 94f-rVvqDPt9p{Ka$uURU3{ \n!EI 2L `;e>ҷ 6Bʁai9 c9 }h:8=eW,K5]SdC-ZHD7gfeɲsX%md^"]@Ɍ5LPS}am2]0F&K2{ޱ,/(5эÞ"Wm4XqbZ1Ϲ^{ñkqCbhpbe$ "U'd3ڦo>Bdwp8nqb莌&\].*0=ާ4v@") ,iXWJu2)qvBDxwCH.br"vZ,Y=SrcnnW^@`dspaLbV zI>\_]}Ӵ V\xdsjVjV࣠_Ӝn?}È3$֬V ?{޼ ֛w<~XX4qjȾswwGQUzd"1 k8984S5!.yqtD%W}ZtȨKOO Iɶw!݋yhG޹ K]_ދ)_ҬG//l8U}A1vEOv`"q?smhbA)T0AwE=?~HyYp/6v.W:fYJ;Β5!*<{ƓPUc˯^ݞYY0vY4Pѓ UYӶ#E^aT5y6,˸<_quyMBNܸ^oi;Mx)8&juM:O%k9vx$Ze',BQt{vϡMbzu9YĈ7_ r/:ֿιϒ&G-dv!z " ks̹w16E+@gX!x \mM IR_Dĩ6[m f(IGC)?-Jc)cI ~R֤覵(9.!΅L)qD,E\ql6(Mw;RS9=C7!EfdFߍtx'0 B԰!rS|XXoHLji(}.~1u E`].GRD+kcDb7I!D&߱ݼfVx0 k=:ZxR'>E ,j^eIU\QW,T*}%Ow]޼۟,YɓG=tk^ eaM^ҠNR#TG6HiHYeF >m<'ƖO^ٷxjJ7J)MJ!)Ҟ`4{\!+~ۢ?^/@K])0CwEp+D{fDDC].:è9=M+FO$Eb!X;bd6m&4SY0'i:AOaDVfP1}y9.N yqE4TPĕ $p80L="zLR #% qg#R˭ IDATfXhv̀֒s.//Y.E1~ûwt]@ >} o`!4pS y]q8cp}'/>%s=/h۞kg3=%/~Ҟ?y3vMfa^?5)uLIې=dE>2ڐ8A"]0t->iE( QFl)Oـޘkf$'NS2YRaOөPYQR>3v:"J 0i$y@T7z)$4 1ո<p8&|IxQuI]id"#~YI2(ɺTUI9޶GkчiR%MǔZ@,AR:!N>ጒ#eJ{H#0Yv=&+zɋ;W@^x.fk,₋ 65Ö#]JFg 1zO?ƝRVFf:uFabF*nĎYY^X,/f['*$VIՌ Au[Jtcfm)˚"+!-DrIU|ߏhcxMР|'4˭ɒ5 , m7H8AIb9%ĵ|8 D%(f .ire0L(%"%.Bv|9?|HH0&7ҷcbjDߧgQnuZ1 E9;h__RP8ifߴtm`r%&ǹx^$sf=/\ ؈Ԗim`lE|+>+V8?_1_7T;ԻOlSWϐjF^|I4M{6D!,ACUJW HifuN=;g!S7b1zX1*߆!q̒A))M0$0W;ɋo1#gV0 Ny}AQ,3၈W~P+~~_ V3vB=~ "[LH#)"ء R}ˬ.9[esCK(!=c߲>7uh@Qfx<2͸Il12G?ě7o˯O粲,B2N-Ök VZ_R9YlP)50QdRKڻl FV0 *_ES6Qs(2mGis?]K?(#YJbXeߢO öMfl~ױl:úT}'Ooh-$3ģNK΍<}?㶿fZ4tC@;Х`2J|}DLnXJ~?$*ʙFF.%Oix(0JaLxϊѵ%4'{b"9d={A`GG ۛk1!ݽgBjVg8;;qyQ怛S,I;dOV JbgH䘦QF9T40l~@=Q Pp#f7-`SOF{?:WCHEy@DB1 bj%Qڏ\F9Ph1=DE͸<`x~O|3m[hc:8xM0i[À2=p@J$*f2~OQzcrqv~;qOu41>=ҡ:J).RixC(8:"&[I.i}EeD2&"*auU3 HRb'?[D3+'T "]$E?%Ԇmɏꚲ87v*Gah#s . =Z)t>K#'0vS8f9}bG+N܀$DpD>0+,9[>*WEmƬg?9Qz{;A<~z0:hY$g#v`[,,5UUKv@%*KCJZtd^dp2L-!zJ*aHX^EGf"XB䫯~NpLG1]T{U,c?B!* ,lc$F Y/if0 \')#$FKmv6 OO(4MSd%((Rjlkd ĉqO)4!j2(sw,D4K}`DDEOUI͊fFd;颛Dy32M^ݞhcv}KA Ҍ@06n ybEv/Pf9_~Sp O?|un'?1Mmvl 7 ޾l Jڮcե7Y=ɔG$$OV!:,c. C(~%"~ D,Oax蹹~٧+v†O=8ڤ}weǏoꫯ]G,.2\](ۓA1=uz6y}'^SU^QiHZ04Vt#RŜ`^=mG)0*xb2Œ1(($ݑ '| >s6CЎϟ=a\k޾y`^iXB@\?w~OB)R1_ׯ_[Bf~±i۞g?lk~J3T5c'rqKE?y _x0NTU0y:)sk.A ^2,V33 mӳ<<{Rp~v|yؼR Yn1 =oLPShV=uN*qa]pv~wlWx&>ꚡ?*%~d*s"|OxϢ.rd#f!4y͍ N-!:V9R!qk"z2v, RDqLr~*^syqq$/zˬ->lP*}!MLJÇܲ#FmDTUl6cwX7r8R:ըfH7Y7T'"bF>1=Ԑ?yrh{.G72[VzQJ^2#?a(&xiJb@fy&2U"L]OP7oGL8BJ{/ ͈89Ҵ拊Ol-qd2 -jVTϾDi8|}sLGFgRJG|EQ"Zmt6 L_vc]29ksAxe 3;A>L",K<jY1ZI-ƍIRTR%ZcdƛW/Ͼ1UU`@󜢼A[maOmY0ؖ8HRB˚c guq2 =m{@0PEUܑɥ4FJzOV%eTd&GFҋhrH%N/F=;0$Ft:A%FGI,gwwNn\ A1!(VHV!`,BD<)$YϐFmJ=P uvLvhQyD;6HSBGcp#i]%]b1h1@pާnߌQAŜ!btJֱ\.ۇx ;~u[+_ޜ,{{:=wnv&) ar`@3?SGNY&%5nv w: {&kw١S3+έ:{w~sy%EgSa$ G 1%iIKVE^2_Q`-mf؎X Rh2Su;#EiX,szs8:sgE2XճY8Msb@pk)釁grv-J(.R{ciq|! ugŰ@BS.8MB)ufdE"g<Úal+\`GG6DgY.fDqӄbpږ֥alsxFa8{0$%#1L3""(q؏Y-o^۷D|{ϟț/駑nvľYě[82{6't2=ARzd'>}f?&NIFdLvypc>"ϳ&R/C$Msdb%f^xyckDw8gwiQ"$42ى(D TxNɘl1u;?ׂL\ДE4M(.?y^9wwwl׏Hl$Efx6i@*1 D0(ʤĝal(EFj&z^0LAĊiP<6iB2+<0 Nd9>g;bP,W3or55M?xo~' gɒ ;wG) rdf>_pe8w\.늦8mq`OikZ2 =߼-c=\LKC &sZRUmX7XY.lOlv' w"!\@C5F"!x9-yYg&=ЌA+~wYA9!HbG=y0^}OYg\^-?&)5"JN{Ou9CifI*p%2HETՌc3b8-咘b\^%}%k$!,O\Î ʲH[Vi %+.9;b|nK\L|^`^H؄o2U.f&;t^nGx/z-w/^ްYT9짿v,_\c_\] ,՛SrpQL̈́Kɍrb6=R؈@Zk-Y"[ylV!d1==qH)2CkMQLΑIT)Z>qd@4ʡ4(dq3U72:}idaDE{MK3(Yr8?2yf9cIUv;I!遾mSP::\_d};S#xܠYb bE (  %CK5ݐk:O*ZKwyv}ˬaJ)Yij^?bHQE )/^FB[NY.KY<#1V&+fTh95w =ZKSh84qY: ?1ia9EE^vEA?5"D4O1_(abvElw^nXqj' T2YTD:>NDQJJ)rv{O bN a:]$ɨZgKb;JQ2Ǻ,2z>c_r{#V/Qbd#h/9`JM Yr&2b t gKiM}Le,34s:5"b<+OS_+Q ~jh.E`Rþg?2NqGE\E4"Ć&k1*Ry!QZCG6!L~[Iekl=SM%4r@hu!8Ҡ @ 5_ (RÎ2o@UTYFOu }hYچ[CxGXْo^r==RY\t\\9 rlq@4x;"iTd%^?bZ᰼O1W[\觎dnc f 9a")obT:idZ8/J'͠Q]g^v#~F]H#iYY_eU1h-!tQj.Yb6Ce˔f.C ׏(&Fڶ=]uTfic9Ӡ&&}tRfi%sߐ)&2QBs[b8A0ڱg wBdMPyl^.U 'Du}wDL&mAd%# eYr}b61]HJxZdX! '۲X2xK(l60O`StVdH5M 傪R1p|&;L7RdX' -4&3y禳Bc\'t퟈3S29s>]͚)PWS۝SR{,8Z<8۬puuëoѲk=d\bc=ЏQF8ᝧdee7eO}bL IDATIIZ?훯xܣU@Ɓ(Iӝ,hjBeJռ|O&p3泒KǞS3[1يO[oπt(L.wxpd)6W9_XVw1=~[~񚲒qf^e|??bE6fTa}vvUݞ/^(k|No=1+r-O;$/@7Kھtq,J~SRj;:EI]+b L#2CU3..hqI{JttES↮Xq#FI|5EhȬ|ͯx|x _}/O,/ 6*>|zOth;;Dǔ_7<9g9uR0Y 36 C Z)=atMTeٓ V%J)Ȭɲ61ZZìlSMiAH$|F@DKf Mn$os.EU'?{S5 ": '#RB%YnQ2_8vCgȲd^?aDHQ&.8HRMTH.)(7o1* NZ)->X&gѪHKYSY^-ݖ1Zez(o52H?Ǭʯ'9O,DU\\[(!![҅i(KAIs\ I#%faFۡdTn0CEWdF6qvdDxo: %Fi7+n=g^7ZOFRфadhEU(%d)P8DItO愈HiJim{LVurۗ 2S ?wl{-B(iD$3%}gqcG1+)-F H`[Ԭ9醞ȔIiԩU8IP/$E&u^1i" .0-4J$r @iOLĩ9e!1Z` )ҟ?z'O$10|zp8Q+ v@ C\ŗ_sjG>* 2Ҟ:|"[qi~_|o_߱}Y.%EY\.1ylw<(%>Hϱ+yѳ\RJfuᘘzTpo;\nx8'rS&FKl#UFwOSú2C;o4!"(6dZY{K$YMziAE2cP,O":o{7Y<>9&1JJw8,/˷ CiBHkuMߋ3ab>#3)vꔴUS MuR's itZ0+R#VK59i$(DOJ.+N Nr*C<!4D{JPbN3bHXx\?טJd`ݳ=4޼\ޤEߩAnn3O;dn\N;LS=$iIQ4@гXJx2.ҍ-֏BHIr ّlyyzĹT[vX͈g OK<&K8?1K:x~>3?/sT^caDV$ˏ1HĹ /iSFȀwӘ*J*8 ee&' =BJ϶S&ŊCԈ,'f%MlN[L0#_r8Ln:)G_/Pڳa:' Lu-+tnoCҸabGrq!eR'GCiKY%a*54]:8 Jt-~^0]tBiy RddZ-VzuaboITy>@T+Y41 mrXk ފ4@ 1kقqϯ+vj.uUՌsS K9Qb!K g7shxvwX0l2ŝ s|SӀ6\]]Yp!@x+Q |8d0#e])# cG]iv7~tëW@I{=4"M gcC93`!FG?Iڃ?iO1yΛr}#x|2xK!14}MfD9{./j| G9n);ó6+"'j5 F7NAI_BKLoy@OC_,4,K{Ђi#"s?Xޤ,]?6N*ݟ6 %(r}}׷\-;;ҝӧ{%}~x7u-% SNSϧϟY^^s-*x pBc 1,m +c.$r)rzaȳ)zteh<=֛cBb n'rSiqYjxze3[\D13y0xol1heY;IiNn_8HqcR'нM yRF2-Wm@ * )d!2M0~LU"!:9wh!,ʜ,Xo!DF %еkN &|닚??`~ueU3JrٝcszV0+gE0N,W+ >|0Rh)4dZ'IZ0f.rSR0zn|6 >UE@gюNӔj^S9WoR i7=OU\ni<>=Ql Ղp.F\⚟ܾ1xppn(% d纙dKN͞qh{SBo.iN=byP~dURe*xN7'7o[Ol7Á #lF "5ClKyvy?gw gطX{n;W cZfljJJ@xzbS4bď8S QĐ@ZIbPD-mɲd Q2ùQ1d8X] 1߳1N{zI#,<%8%HuQu8ԕ`1"Fg(TS\.PJ ӷNGrtuU]hbɛ7oB$qGi.X,fkeͷM{J(XD8a B~y2I )Z'Z0,sa:sDyAP2aHQ92.֝< xAP.臁iJ㱅(Gu-Z+яgfOqE*] KTX`_G'; b,Čd 7RsُGoYx|Ŏɶhya ')PЏ]fSTTq2eRsF?1t=:CS2T,0i(%Fi!:m&~q,ɺSCa \G=(P̫#SPB2<ޖdEG7L>EuGL>hu SZ.$~"$OhgORb2O^3/bZminptq6Ҷ#"ŌJzSkrrT=R8W(qxm.om6em{"~2qj- zPpyRԾtM#!=[&$: g8 h"4Gi霧mF(H iLZ&&g|!1RWӶZV7 !-qG8h!MJ! c R%@Hg/|]kb4/.oO~Ln|x2ժiC$m=LJ-JHܰF8\,UJ.ʈ0 L'(x#׆Y`m`9\ZOYheq~B n=˂t܀TRo \.˟!!PдGڶtY.QJquuvDt yY^o9wup K"虗s#ZJ29my&3-ֺTPu5G }f[N-,L^ɸX-JJ8vG!`=,KtWbgZW짿ɬ8|xKnoz~iwzG`D&!ϯnYSJZKɺfLǏ%⒡\^dO_Lpus`IPREii82M x!7ɲ(fe:k4t`4aɍ#ynE33,SeKBͺ(8٣D \]]q}}{>}$"䈝F\R6ګw IDAT|^/}~2cL dB7dE?zu%|2RLM{drI.D2 es,e{$*q\\q8\0a=q~J t!Q*WO,q41[̑"-\]:4,WsEI%md<הyEkLJs圚t2|Ǩ܄hY.Z-!rZҒ=8dz|< 7M\\#).r[S蚮y"iذJ|_}.o~݇o\,gK%R+>}Dp{RV)r$,lҒ(2LeF m֎DMA ʒ娝qLé `=R:Ʊay:GBlBhsR4csJ 2֎t EHyWd9 ϹyFX7ʔ~Q -4͎S`tb5Ŋ2/9fY.Táq8tT 2LC72_]:V%v|z¨aEGp}qSKU|5/_0Mn\N)i#C?F".朎{~goy/ q]0_-oޤIL{tGcȱPt7V4.h&ȴF"DhfsP7(*猌ɧ;{-Wr)m]ZZXdxx|>07AX>`{AsU5DÏ#.%H6k9?PΗtHvR9Cx?D@ӵ3./Ǐm0h2g`I@ yҬ6pSmG0N^GNP $֊Zl IgVRM#żf-ǀcyf19SgoX/WI1m&heҌcJ&`=Ѧ)pGX  I '8 QZ8Ǥ np1#2a nS RZJ(L5҆5xR8*@Vd4@U ~n\!((Y3[]p~|Stf"RQ/( mGp2+CE?: ]Ɓi-f"Åd:p;A$iK_%6X섖*"% %R|=?`4?j-%TUF{J,&B@Kbӷ"@2tN4 03=O=eqKCH,$D2ie乢^LXR T "ݷ2)L5k\Hn'@dIć'p{CA8Te#R+џ]o,P[̫͛W#dmMw#?f3ʲlbctcgܧmfvRGT&+!RvΧZe,In~rfRdЬ6WxK⌫ m]WZg|h/IZgc SV 9Дe}OLӞ_W;~oy1 cn>rpH(/6Uvly!F`ۛp炋Ō/gpl&ƶ*g?jT!VZ&o|Ig~?c~Ϟ}' i;&je>,ֵ/Y+Y=RJp8`݄T2+P)!-0IAif3 c v]\}88vϯT{+Vg=fbs1GMFA!#mcsqLj#>|치`{#>c FgYoV(IVz&Os*eMn2gUJaƁ7 f!yelGEjB&TYKA2z;b8(SR(>|bZQV(:0yz,ɲo9c2Iy@0_ΐ%+q`a&#rfu]s{{Ks2T[ Պ"a淿^&A'.WG5j%|eGr1cUUZvw>|9n~]`J<}D#)LjlBk׷= =Rb1 g 3vfXi;䆳4݁r%&±h%ȋ"`&G2|K\\\f?@vTeJOLIǣ%`6٬*Rnس34*3yfh[pZԠ4, ù-as蜪^S;6 :0 cKkL(*LzZˣG/>%1Xk麁0bZo1**...R:4O HxgUDDx׉%gl6: -"a2:GFɓ(~s^~h2m{&{ =B%=(AHïW_|(˒myMjy.ER{Z?@2#>%l@(~ l'|Db:f .,2$dac#rv4lqOvO@#uՕڎc3㜪\#[ 81YCs]0Li$L?jT![w>JbtfPrl:)0 ݀+:rFKlBa' ,Qڑ |)-)Rz'*|P8a]]!TRk;lLa-=@UOp>1c%,<1??r"p}lq<t pd; PTw]b ̋9>H<(h{ڱA5A&~w8eQ6{2dn7@()5UHI>}l)( Ռ8S@(X'E@%ঢ়{J %+N掲̑*]6w87%4E 7M(R+R8t=f3 Aamd.?f= L|qJMT9yeғWYFUV$۳N@]'F6V)ޛX$8cj1 ce&LI g@A-2zb!tCi)dĘG GxB[ /^ma@4ΝYq u8oOJҡ[*>5A*ZQzO.%Qcۓezش ,.D<SŝbJ*tJOnL% :S /=c6?<-)x(hqL3#NI @ꜲN]& e_BDiЧ/&KP!p=ほ!TJ:$T ׊v@Bwn?ʌq @bs6}-FE0 Y2 4ta_ϸx5)'NWZӍ t-jv 4,y(u 4 Szs'9"Z|v'UEnv۶&C)dm.H>Jb%2,'L; \Oĭ3D YHb# dĠ&gTVٱ wo߾nw^(!cjuFUs/&O_q<Tm2D=x'OYWEnnŀ 7޿xNzdŌGK?3w'.ȌEkgZrV!*b;)x okl|QӃpssˋ膑R|^'~dѣGHG_v`"8atb3} >3/ֲ{xϿW7x& 2#ytu9xÇNZCͫoi{VKd&g*ضHY9[/Y,怣 c'DqW?OڤG<Ö]R=LjЊzQŏy~K9+i#_K)ȳ gU101X@}b]F%3*1-M)5Ze 2 DtM4y +5RȄǒP_d%&ȼ:ЍZ᧞Y>Hvw뗯XnRګܒeo1V›@ee֛qcJ#="JIkS3ز}(!N5+hb4Mm,dX\\\%)9uiq.et `bsuFU/i42'4M)eRB 8|yWϮ0Rn\O5-A:bX 3}ZĞCibq`i$ybR4 ޽fZ3/v˜y6vKfr-ZB)eIUfS[>h-zΓ˳OjAQ|o޿9 O{"!(k'CJz$/G*7)-L^! Zy޽JiΝ\zzՊ(0"7ɔn4tC.m[q:M10O\ed=~LV #Z9- ,ZDUhR"Nt/5~8HnKa:$Rf0N䎛ljPr,+2SBjEq8=~S$EY^ϙ,Q$JxGǹ7o_傧O.9Pp8(˜8N E͇=~`RW?ݟWKﹿ(3 y9G!G*UbH"H܌x$ӯHLNY:&7Þ|o҃vbtȘA$8(!Gdڠ, լ4R=tM%Q[Gyba2u]8p}w#MDz(0:5usF7Q Chs(HgɤFkۖa( UYNIlkV2E,ùU5cv#nnx 3pąBw/(;iC8N⑚H}xkLPe@߾VD#~',WPJ0=ލy;HY\TĠ*mU4bþ92M>]=dBeH Zt&q3us{! mGnr4Z,Y,jiVEU ^x΋-77׌v`K\!8{5٧f;VW n C\quuήwً߽g7+B;! 'A|TDʘ]1zd9چh3yJG:n1y/ JlɧϸuȳOy)ׂC'/J &N!@&{J|F,{"cđ@ПO5QZ՟4p|VЇ4{4y*OhSnAd )9|HӬ y9 1⧺->Aһ0N,J$ppCU)|H@_.ȔT,ɔNRCK>zB*#1\=nG|ptY'p -E*'+3^{pi 0tfg,WKv%s`{O'{sˋ 0u~rɧc"b1hvӽœvB BIQj#΂10v:RmP!ƀ;]_Z}?"1)A"A(Q*8!O$zt-rMBZ*rþ8[*qT$*j$'ḣi=?6tg4DGq8nG?E72~w~*cߴ%WO y7?G?zt`>_rwۤ8 ELZ[@U4 C _#'?`Ya{2)xtm~zfذ^Doyo˹/_Pm/<4=7GQzRU6 4n<`]O]ל_>YMZM0-nHI`n{1UQ3vwa2rX ق#<GJJ%AO:Y,APe g9[wcrݱq!@.pq~ӌ;^_fwc_pFJ ZA5kܔΈ.5`疪 1]/8%cQofX)뜮XK]JcG7"$J$ X/֌ ??蒿ow3|M5jƬҒ?\Wf&vDtZP#g@ pnDc ? wmh%X\,8`;2-ޥ=*{X<,K%N#z<G7mZ`n^'a.ٜ?(U"Wo_̰<+Ɍ&!NF"qFIӍE Jv; /c>sqqq.'YTɐWWizVc0Ps\tCu#R]vt}s.F7yÏ=n ȇR:@dCXMRB ` r2]GHт0yDPE't()HI8(iQ&zsb[J)1Ǖ,A IDATR(!v#أg,q#kt2=I@̙yU8Y:Cc>;gVoѤ`Q`Q%wFATWǻԞXs7z|N7_\^?x *{4j0 mOQ >RETui|ڤq:(DB'É0Ch% SZ$~a<%HJU)11=PmddF)DLI2OFK7 v Ղzv?E-hBw B@Y82z@\V|vIU%6*O5 h]зi$c"==yslw7H(%"Oʒ!MD댼HLa)R meHɳq;L^ < e C!u)ޥ.z$vgDGc,lcsqfW87KfŚPU5coQFjNQ҃ttH#ubv$@s4S6K`PL,O!0 拚:/=6nnlV'hhh(dҲ?"/j \1ۛ{@[I&O5"PQd[.(s4d<8"ZA7D<Ч렇i(ᩲkؤ!A : tI?yLV'POÖ+4y12U|$ PB0JRQF<78;s|z}!$1!~$3ED3b,Q=yc RkPbEftMcN$/ruhQs~aX-g%M$Z'0Xl6%/!B("c݂pzYQbG@6Qg%(b6?"ZR\-f̪LkY2LqǶk!8XTdZeԳI/-XK3n20M}Vh)L vD}F2ΑHDm<(3XɌx3q@gw4gEJS8gsMQ';B:)gg nw3S<x'A9{̏~4gwxl3ᘸ~csEn<-=MwOnSm#y%3;Q .PTe-(˒<(E {n[`)͊kH&;uͼ_#BdX#_{C RUi3~|IYv۔{ن5Y2O߿"(dz<nR4{űH uQ2{v~7,x# !<e:P&G@^֬7 6wDCTW!"Bj!9,jw-8Q Ah ȼ(1R3JOF޿fyadY`T`y RhKC \c{8XMhwt,s >XW+^4GTlhۏ-:fՌinWIY,MX#v0Zg)IbHX|DKq 49[aT^.vݦyfHO)6xɵ0WahQ:G@G|V(kd,gkO2OK CP*nv)U~.Mty5@rcx츺dVj6dLޥgJ*S*]wn\LQe9}rsY˓B=C{6U))ҵ{˒(= fi$.$}ex?Ѷ{BpT9+5BkT; qͧUİdZ%QyA](2wx5n(sZZ?'GIR! }!Ғm;Z?b*%M*J{&1 ɠHR" RA# Be(F)HVed BkU%#2mb (TO5 ĊgPU%6fuIul)rO DJi]t592NQFTTeE~@1!KٜŬ@l^h#yh=gk$ގ" ʰx~pBQRsl︸^X7<A^|%ق{4ƘIwCcm2L6 ℑd^H,q~mZV%gOf5 &x8yt2B.p rӘ@pZrMYsZXn_7IH*9A#+R( EG^}7atIsՏ~͇m?$ȤL hos;I& GoO JÀ{L?%M'k 'n$/R*$=#"3U1+*ͫUJᝣ.[v-AAH.mUA_5JϾC 9g5vw:H$B Ol )5d"ת=YV8=t&^zñk1:bH["+3%QD|2 QʌY9-CxQJ۪~O1 KD'@Ri=DRs[h#e nN_K>}zvXk6̗5J Ʊp Цk`%@"4GsAi4"Lb'{zH/$I 1bH*y5dɳ\exhsîIuDM8:mN}b9\^]1_np=}Ni[oVr-0BG ռ$Sr 6CZI?DBpQeR>HdHB $dZG MCr,RgR2) ".dHkqe4dS'DPdf5?3ck+Xl EEY̙seS!FPg+-}3amw)%O)uDF>|H>\]=ɓ'e4{޾}{2>{2/xtql^3 lm;YFkU5-& m$o>~1%g3]^ru᰿_vYH Y]2;޿ &PAd hP\xpHC\Re#J޲݁08K;}:g8MxHaH_{)9ӎ*S3k=el~o~k(@Ұ |ntk\)%U5YI;ھCdie8Jzٻ[J]k-x-1F#d"3AQ!|`yR34]Ęm1 cd\/{Tc ېg뫓1C*R\lf<~eq/2^vUy9}>Hn*X/޾#3Kvޱ9J?ڑ CsuuY5ii-߽|鹷,Hd`pa6{C&r.`# S`E0tMY cpA<]M s0=^^sQJz4RrPL|߲?<:ːH{/ Z|BvLs @O31spϬK#]\dNПLyUQ!z$4Mjaе@K84N ޞwM$żd\a]þ%"E>%h5' ?՟رc)ˌ6ص@ETTUEaM|]zFQ$.'{٢s%mGۏEK/~~$e1R$-pHZt3xl !puu~do8g?WWnni[o EYČg|K,mܾbB9Ax<2-gf@iY#NlmGǔ 6A:NMnjb'1)3R09*zB\8~J\,"@ E _ޥDz+={m_%9q+"YŒJXnh 6=?'7<0@nXaC K*KUbE2׈8{Tz5@2qbﵾ}G)!ѱ{8|3>X+[!d*äl.YVdIf-|FuN5?8\9Zc;loRHHRC] nb:%zQ4# * 2)BC ;0 4qzKKTHޔc~I5Y>Z0K]d{OFՃ 2Gy5Lc`'&iع<T5gmlQ0h77wdIʗ_-}d*m*lB"L?#49ǻ;Ea"h 5[-iێ}0M|Io4QcRa7"<bk#)CņǏϘ5/)mBǪA@9K%w _o)K|SteNQnnPr†ry ɉϑ`kQs֫KnXo9<%/y'_puu_՗^lwLv ) T"nZ *!V 9+Ζg\״M4t(В8( DN)BS}+vZb:=R|(qō j%Rz^ׯaBmu1_CyێQJ F#bސ91 RE=L^#eN͑dq &ぬi*a!#ь@f(KӌKQ*, & %F/۾@ߑ"D4}ǛLXyׯlK?m bL%HI^eN$iPu" BA׵ Bl@.g3<<#Ñ@Ӷh'ՆbMYhNQBei=Tc "x[zڡ IDATrz5gf}5|}E'?,7[Y 1e,(X'=1*.eSh%R #h|RMc.O󏸺|NY-]eAjZ8nw%"G(Ok!^iX#X18d6q S9Mdv;< >0D W<|i麖nhv8+r8Vl6Ede(ك݀#Կ:`YD㜥8L;a@iM^]ҵi6"3 .*_>0LݯJ|۲Xgi:1_F8@2M;SCT nJD8HZ$mఫw ҂,0Zsc16\p9HlR;G]ډƉy77|ϼ̙2fE44Ձ9"p89O?p>d}+F7д5m(pSV)6<1i>5v Lc@+Eb }2#jv84|FfzF@>Ĕb|^%];e2ygYnH E!Q*>#Z`b6+QR#b9S蜩 :vꥎ\#(HZ9l7L9Rihx7v{C|:(g -=|LҎ=cr1ۻ;7DJעƆGϟڬZ-Ms+|Ox1RK@74mwoۻ-u f g sOMu }{TE"K7ooht`_uj3gs~H3M^yeN08!݁6"&8b}[ wgGCiHrб.[7]F\Ee7掶1fƣqq@T'+H|E9#Wx7Q4VK>qS'lQ P+>0-qNp}G_+k7xя~||}cݾW_=79=1J❣>$*ŨҤDI9>}|Gu{aOUBp \S7{.:0 xwJB{(\Çk<'S+LR2N!KrDd& @S#]PtH9I768!jxb,R+H*ZuD78Bxv.wH:Q!=g%NRh;'?cWK\x^М-xS s'0@'nhqvbzg}?gس'+Ҙbd>^L i)nw$IƳ?F_=RI-M<錦6GHsBZdDksxbtF[L% L:SlF Ktݞ%5*Gq2#F%yVf%Y^R-4!Of%v~; #&Q']GɊ$1,+Nc4-PRR36KBP:4L8YBb`5&0FlJ/Q*"&tL_cnmeݑuh%J:r!>xnk-Н1#vb$ԑP.cc1$ZK&Xk;[؜]Y]D訫ShYΓ'8[S7YdrϷ]6)I0QJP)JiHU NHHN-;79*i$yI31qٜoBr}} '?L"g?>RċcpP#'nE05ؓqP'87G (eJрb eR'ZB,g+T0vR!& ބ ,97~Dž)bŢz>&u\E2yUSd04h1I"pqqnkfYi6 ɬ3m"{ (6%J(!u$؂;mF(0N#MP :{b`y$@ r$!5&R3I$Tůg9_6Ȑ&2,e(C"n5UmDJ%N58R $5CSe9c{wѧH`Vjűc}^v]wZ9a~{ϓ+.+޼~7u8S6q7_Op0 ' '} m3r9/mX.28nF)g doy%Af|?O 7 ~I4Bԇ=a< (ƉsGOM<|¬, -pb?lXǟF`RAD}uQclj4(c臎a3zuj}|(J>|0 {wИ$S9߱$$TS5GuaKhsyRk!AIE@sq!wJ$kG&7Gޛ?Ug;M ټ,(2$HVc/˲dYOmz"I2Q>@HAbR:;dzX@feq_Ĝ8U}o'FQ0R&а+\< "%b_ V Tk["[v I2VTBV4]-H$Lc|𔦩xU;Qop4쇏yC$U[k^}mX-2R%ƖH)6xTTuMy6#/)*﫺L<򟜋C%08|? 0?T]Ov4]Mxj"- 1p})N? *9 \ sktU=iz,A.KՐ4C7z]#e9NMS1+ feR ]'VE.躎ThCgyݖ?5OE[A%y B[F)=k=ybPJBβx#|Ż[|E?8cϫ7W<~\{7zc=k^|k1}jykqH@rV F3]Џ XQ5^x释g,ž',ikϡbTnk-b L5SD[f)ٌ g\\\KCadBu0cT< $cMʠړ9 =(0pWw8Ev}2:lF`ɓ08TG "a8*/nZa#zfM۷Y09JUۑg*&g?" چqDxƗ^"ւi,/y<~C5q ْkP՞ eʛ77̚ņrl,oxc;wĨ !\x\'n'I y㽥*|hQx"dLGh<c|@!IRر2ލ;AM1уWdޞwu !KDw)֔Œa!cL]8 0M%bAX-au8ytk#Դ<u)#L0iZ"EVʳG 1g{ 1X'@HN0I4®W{1 dL?ʼn6%aN um0(2 ć'0+㞡8\]~1)>8.щk*A/kL)_GNP˒6'KSPJ~Nt}u* ن;?'|G_ߐ/>u#2kýi;0z0ϫoofv7➧>#J{vyx\D7/~1os6H[6.R(7Z}͡[ bxQQ=aJ.H!`d)wſ >~Y< IG`RlP2tk96qލ (ηOu;bAh@"~v!:Cd008Y@YDu< >D8F19n`=y4Iq#SPl%;e[JYգqM_Cc~?f5qkTG#ȓ^u_!eS\<{=Xyoٶ[ɓ?/mxo۷ĦA"cb&$Mx,hE*PY*LщQtm͛WHӌ,9GJQΙ͋PnJ +߼:TGSf݁m.053gsF.ZpVcѤ*q<\{gߧ\DFi$<8B4ec<#@I!SȠPx+̓7brA1+IMb 3Id0T-SGpyd1 n`r-:IX5J 2K )s4xd d{1U$"/R8 "pь + 8𚜧G)-NfG=GhG@P7z\u074')J>FmG7vdFJw}G޽cXÏxjKG^\oȲ9'F d/9*б-ez,AgJIĩ)tځ‹|Og>~sl4ak &CL@(R@q HabV*c'/\BVHfYJ*edfQ&&gso@K|NӶqẘo{,V?ca8;[3 .,f%.xi1Xo@7t]nutntl{&25N(kY*0,hwl[$׿`ZF % GK-8[<$I "(2f!ю_k|CrAQ.4]Im F,6sHG|9xzv"OgKyY; ٌ1B(ab;+( y1nfb|+{_yL}0xhOIrF/觞0L%oVd9%sfƤ\hRG5Ya#1%/ݲ`5MӁH mT0.$C$h.mz|To^';>͗|շrhZ D0tfqƬ,rvCk˗/Lj`V,s4(;ɳYyƠ'1RR&3Ԕٌ"ٚ;Wg͒>ϐRD2T $0"sZEF膖n %CwƩ-u}l>a[wEQrymrww/~895hiѩFg '&^sS=CvK8/q'„HAꈴ0iA $ww;LVSd0w{5,3macyiV,M A&FL#m\H)rApΑ3Hu4| &Qx܄R2ONi*唾9AN@N?APj*D&^qqo[+D*9+w,>;9Y'O\Ix8Ko9؃+*G oΟ{Xnp~u4Mc3߲o)g3ȱe05]un6)fII)꺢/&krAGԏ ]OIQbàۿH]q(6:Zv"aXFi =e)F;1Y >MEȘ@1&E,ڀ#8%duZӈV3HGx,74Sdن ϟXP =tVl.OݾXE*I9_S@?@q6U&;2Ֆ\§~7k8g}FY\_o˷߽˗J#PI/)aCjF=ǣŤ͸,w;n$Q|I9\E,(%ewwW( g?mï~KHì! IDATY*w^s̳X>٨&01/s,G[8]d 98E |LҤIG(r5x\;p=KF簃M \$ tDvhǔH)1oF ,-NL'l C܀ ResCRji`juh i S{ِ$*! Ch%ZTu{-=]DK(;ۦ):2/c-Iu=( d,g&ښaC ^x~W@LӂipaW/!w ͱbXg\^,(+dYF_; 8SPo޼6={3FgGTJQ1ɾ};-iTk%r![] >HWuœXK$ZqwcbbPk0&ռm,EYXD14-˫+7i"O"p>6rfZ-1iǎ}%YDSgnEd^}΁X&Xi<20X8Skv%?zM-pvb{ޑ; ,K/:쨪xVeqӈ#c}ǿ$IX,;|1#r'ái/fA(Վ4%%,Zp$8Kqwة9ìH9[o~˗/|qƱni(E$wU[|19C2݋WO?#O>*t2̬Y zf )F֌bOp&Kv]iio-Hdt@*GhGLf2 eI)Rʬ EE# 6 o{ %Jϐc 瞽֪7d"uA+G>)m`%1O׿ه :ڶgs\3ځD 1q Pq~yT)=A|0')ͱblBeZDܓ)u\}/ooSEvS IfHMa* g2&񶧪zCDKAIoԺg!(!ƴ$ZJ&9%y69#]WQNǺ1p~B逑Q,Dh޲.rVB,EA/l|T JI2-Bθ|_rf1{I f 4uҍ=΍8ߓ% Y6E\U A38G?Z6hːs)՚Iyum*#IRQFq-"I5moL I6tÆy-ϙ V'̗3V+vn:>]ßb__0[|y<2z 9Jo=# d1)T.&,H 5/fRLFd_#y"@ja@ˌzG1PUБ%)FOCOy_[ywk~e b:e`IMʻw1:W/ Mk崶*^,qs('#͙$M30A2_\TJ+<^|(41N/W[>qdRK28`[K^`9zMWE@ih"+~$:|ޖyI}Ǝj`=*S)!&|H\4Mb*B%mOSw4vm3$Ts~↧O/?0 Cm}4My>{{?5̖&BWeTj; AFh2B|J}|R0ZX RhAkX%K ٿr},h <!-ㆶ921>#JKNi;L^5Y!Xoq#5iL0R04C?Fp6BcRAKq#HL6~h<*Ze4v 5噦gY$>JaD C84cٌ*~ Am(4lOg=Qs,M^Q4n s C}8zHa6IE$䩊P7X[Z6'պ+:IdUL&4Mh;T 2зxm$t2qұGFBm/mk^tB2!= ytQ{oLzt1#'(ݡUKr9ݞc$spww|w7&,(sb1C[Y2EaPCӍH꫋ f-ކP!Fp'ц$Mnz])Eظ$d"=٬o;z^Yq}}ڶg1_,G=ɜذk^|Ba}J҆vB8g9F ӧ?o9\5p`ح/ij/>gO8a KqZp'IB!E\v캆C{}`RH2d&( QJV l!lp $xw$ub\]\1.^tIӂnͱyzqyyI׿n|QJ0yvǤPENJ B % ,cG/~I]w M{ I% /<&WyaIr1?9oc5qna>hp>0mI<ɐ&.٭2;`.Ɋ1hrA](=eyvA[o=>TUM'̦gS[t@UEۙ6`m4Jd L&xa9nL S6TMÓ?NhnY*8v'IH 3MQ#u)1 8N~?!Aё:AxGFoiu]hhD+tAl1e*#Qw{zyrx;&-RI6,!'X![{?tmɻwoqcd C0@M2p8LLg,. Z'76{Fy(gK`@]W>2]=_?MĜ&'cRUxLJkGRd%@}l EI #zi9cZR5΍x7\eO&3)WC`>- A^SAjY1 >xX~D Cj? oGCژKX)ڮnn5Mݲ~xn ]s`:)ޣfaG+$M}}Ӣ:ϧevu#IBr^2d ZRbF$ D@"r͛ۗ EJ)J4A3MmKqh y*ItxLy8xFm+=ePq8TtH}^۴?o<^}C'hpi2$KK9I?u#$z \r~ų眝~8p~~C챣h{z9U˴Hs&î %9g޿yŋ7SWWXI';74!w?XQ!M8 <~kC P.b"g3 Fzk'+ $,ΎtCK7\^^Hءc91 j\?a5-&d|gLoo[ɤ\2byEH %Mms]C7U5JJ$-!=-V#k)'MSnDВgS?~WUͱa>9S_QWqIu>|?Sn߽g>[aǎP{\2|Ej\>D3:e2+e]faep̙'8ox§~߽Í/~9WWOnZi8Xy)! ,;Ec0RhT߲~x`:-ʐeȧ$m[՞8dORMjn.5WWqsk HӜϞыg\^\^l 8w,6*[LbpK ,iɒtqB"ň'!I3"dd:gZ/X/NJғf%}?w=ލ( ΞtDF) gy]E?2WytijpvA!u@HahCxyJA%~CJAT'YGJY.ʲ?D{'랇bIXBgmNMchTUxNmJ0X6 ĺ}W2 IN}bHvDj0dr ZNI%CO]i5qt<<>}< p-B1[f=Ha>=:4''On$0:OU7̗g=qcG_ Ww뫨+ B& tcBR"uv#CXQL?bubpcœ'?Y$>F24PJ 'Xo;R5q9  oޢds BB Hr< 05%Du][ qN`紺NYfdIc4<Ȝm@ɄR(޼y9.|/`J le(%=m} Fo8AuLjhmvE#g)YG }$1<h mh2]1?cGЉA'x}? AW ,A:;Z"c>_]d8ppQ6[2cVub[~b1c9K=J,e[Fg[<j$X[RbFu֓LJt!mDs7pz$II4NjB܌mM&z>ng %ET!"$[;YZ~45C!9L&%霫 n]m2V3IҍqҶcRDo6AB96 ōP+#|vLsgWUx˟pޏLHD8QQx7FpN8k@ɩ*$H!1Zb :}g1|4ቓSW($E} 8+ro]LȜ4RJ' iZd Hf$~ Mx|ŧhx)n +ٌ2Mád%5i>%x2\""SeJz6a˔"H~L{qBU`&Emޓ&){v}Yb5ZJs-~G.b;0 t}Ђ%6AѲ?~KݾD}|nX.JGn O howHﹾX`ߏH'(L'KX Vjpvv0zp믿f,&9Pq+gSU={J& 5vimxZ(%dzwrI]s--y&c;v0`>VD/M (EZB$&esquoOryu~'<O0-j a8p}}JRz~dʀ2,!IS~9X-mנ ;eȲțk;pc= H醑E>e5_(˔}OpyqF'CV E1> }z pxeI@g)EJ)srx<ȄCp$2}GGzwsxҌ. #~Ɣ˫g\]DlgP=ag1)](WBI9a:Zs~vfk#PE?8NfeBp>*qD JQd%yV4=}NU3X>Dh0oqn`,{М.E9Тe\"g2Ĵ5HA1F2 i)f2I/M5+r$]/;-HA*0)23[߽f6 1S904F[bb6zM0]]?ܳ<{h)Ͳ  b532"x;KW,gyJ^ lKzN+w8I{!4R*$ː*~x[? f]7@"w8>n$ I* L3J*9D6p%b8Y-R"GKӷ R0RTI;|űX4M1d@|rΡN0BW!4l!8=N$79\Pd MsDɔ$+PFG)7]kf),:8AԈHy얮OoVX븸dzv QDt(3"+%tBG =x7p<4\^-L&uE"Fn_p#9jDbݡXxqv nE,;xEΛy-M{՛?A~'3{oQi`#4H%Ղ7LɲIQFqGn<>֜=gbJ^dg>ׯyF︻{CLfnoY'D0t]m2gIۿ|d:_S|x #a$HA;ۻW4.& b'Kq [Gӟ*,gbA11-cnO6WJSinG 0xx-YL{ݿxx.Ev Uu攊֞9%vpvvF(OM4+"ä'Zޢ&Og˛8$mkr4 =uJ)'ͣ]>c <û }+ȏwxXLjjCsy§חD^9ѡ?}-~$$%MSqw|r:; Cci‘Ug_0Hx4G҂{r'7/Ug?yxx@KūWX]-OmIQD١ڒ$Cti:r3`%srTi2m><</os=}%MUrF l#R{@b$MߑAN]Khm))D3 &WaFRh- Diq@$&U7wc!x8#Jx6 XQLpxzD9R&yʋgg.{t[ ^#X F]j2 5>D0u#5cr@0#ͱik y0&TՑ( J FKl\h{saj..ϹI/ǖ9r8JN`<-F5EI -XyA1ϿctemjNSIa'/0J{"gFJHsw??))o[RUsE摶oߓ2S~iiqY-rӟ|MN!gkIu-JK)m)"&d^F9c0y9Ք7—-Бg &MgۏTyqqA#m1zc! PA@ͮe۟R5&^:dBONm42 kAPBK&@&'Xy5h/]ߐ,vmx @1-{}躁(2Hˋk ve9nTq\4:oycL ' ,oJҴe8I$f䩦:ޑQ[G5X.xO`oo>'\=}¡k}ÎaGulvGV4Áׯ_ͷHp1 w֊޺X,}gq8`9?_$?=SR YA}iڑCC[Ë 7W BhI a`:QFL*2%Ua{<0_` (@ReLdq~΃u24"Ɉ/ q8l St;4@:ꄋg8i[}CYa0}qxL4gggQKtM7P.2{$({ Pd<ܢRAێLFF0>r5>~S/>|okfB9X//`W ?2ZG_Z/Yj<ûnx~W/?c8EI8 UNzʐ%ZlN7v$?e}ϻw*c\qq~B ~#X7oǞJɏ~77oxxAIOq(m8(p6dsMQe53Oۘ.efY.*f!8i52%,k@A?\8ʓ=wLÌEUl3bɬ<RG# EN,WܾAJbc6ϕʩJ5>Mb*g$bS@@"ꄶh`ٜ}`tk^rYR)v9ݜ?)7$ۄbZ_kTlNɳj~4MT<{vɟ|%>ܳZUi8جV'?ri;aYӶ-vohf#*I{1M3xxFR2~ؓ4`Ods/FR13BYi$ 6'uM$LnZ:1 6yBEDiҏm6.V˒jAYzū/IJGqI!Cq9 G*@xIYP7q$i sGJSY#Y`#bl2ԤR\.ְn94eԀyY&͙s;o#CHV 'mcѲ4zE1;&IS geQ5Axn?J$gWUq{DܸjyiEi L!Uz H>ZpG'_bsyND{(>M<',5' $Y0'EiqQsaGqyvu ueacT8#w&%vSOW=>Ret]K=ROL?f]v{w7ƏEa=g+rp` nodž*dL\<;g/y> =Z=[,?9ϟTw3h+ GWWYԛL昄 f?g/`9;AǒI?xճ %а?l:&g˴`v3yIQdh-0u2y;ڼ侀DgAΣ˄ۛv*$ MvxFT&q;.RhY֛eLk=} Y*1vuAmj^R+Nֲyg6=bYrHJ"Ż 3tC{o3=8b 4M Z A7t\5V|{b{H{hX.VтՌeAY2VR#OO}qsvzeh#Y*Bꉧ-DnzDt#͊iM8D IDATجr2PRJap8q8GWiqIvU&M8X`0Ͳiw4=p)0yufB%pq~J?|O3ryE?No֧'bCg.[O>)Z.tcFMz|3)!>ιX34Q*/0w7i9!8 B \+%JǺaH8&f`Q-i$ݵoI&˲i=9])Xq}xxbpސe I ed.~ۿQӊg/NP4]sir iMM7py~π#TeBـc7M$Eșu$IBxw9!6S~=I쒋32;MZ6XǙZ-9 !A'k󒏏{<20?jՅ!=Yy ζL~SA3BQ/(m%V[yY^eTeP9y^-;M^XQ' >58aK7}RuXC$j^f$)ff6Q J6є+|{K#UNg%.XCU<{-f@`IҔ BxEEfwܲ\IduLaq_^+;v;Ώ\8gL"׫)S$͐J㜣E90t#eYh1fHAHI˫+ydbgۛO=R{vLLN,Ze"Бe evf}ZP5EAM|E"[u3.xTh]}9$jֻ83INB&KR )uQs>z$Ņa2 BJe8cR^r=ww COӞ&9Ն5BHaF& *UDrhn[D_<2=HX0{t+3R$̓  3]S1QC JX@YnX, !nTʪX1w-4!IbgZj*pqlPzd" >%2EʜșL9ewZSe$ pFxT }3O"Q%xsxʋoh/_RLa X*,KVMLvd=Z,%j }?2L4*dGH3$eNY&#13۞?*J҂De -5eV+Drj ,-ADԡki=]ױZԬ5#5S5G2p "+rRx~zF33DrќbFiBY,1,Ȥ` ́=^%n6C@ų<;2;*_'DHUnDsxČ{2-#>@eWg'dE XxIdNFDpPlTuNE!~a$/$]?v[y&#RmUg&+^F;Y+Blw;mjsB7FVNNN ,KhGOcFN(b̌J5s~zxmɲah=9D Rdy 4h۷EG բ,7M< pXkV3;NIdY#$kA_Wy=w7<}!/,jh)b<&./K M3yYk \ Cn3#@%Fӓ ub\r8xk<2W]秬788s帐elsR5eYpb`z1́4]LNX;̦|Gi :QHȋ77q"pfi!Vv1U\5 ~PV'UW,3 |~ZAV!a=ŞdMD%40AQf gx~QQ=(U0q gqh)PX8̱1Zh$ YPWI|^a-d G{w3R (!ebr%4,I"(3ESi>\l")Dd:咲.X/=ϯ&$ݖ GDe1fBG^XEWfi@ 2 ,*a{D8;3:p-%$4ϊqvٳ8@k^~wXKk8NH9UE;h./^ 5;sy-mQ*fXG[0s J3O$ETqؔx]̎_ fcSHM*X$IrfQL b76M>1jzkvxCQ0SWkyXzuAQ=k&f0@7Ƹau}:{,vKU1Go-Y"X,3cF+O]Gprԧx(PXoH o*hRҡHo~t*x~+Csa臆~h߃X,I~OQXH cXkE9Cy?o~3m|l/x;^ơE*q-wo1sgy4w>cUn=UU=<><ߊ,+>&fzڲv$doQryv ȋaBh$AxBz21)'LZNP|?-J$ =ȸV1":,iHE]Nvsk;>RE?VeAVYنqyٸO4M ر=bL$JQeTk9GX'%%L?9߱ߓ$% ]3WWѵ3=P'Yp}y΢.٬jNN޽ogqav0tsdNQ\U<<~aHl8Gvf; D-o!)E4~ Ϟ=H&v Yj$`xxՆ Z#'͂ʱ>R웖SOLX xYc]zƩ!4emۑ䚲L89;%^v8rIl]|g:)OUX/A@?IlXJ83BU$YBH?dG>2ќ>Yբ-&cU*D}ɲ0 ,$觑$ӔuudEez,iJ84"e1ex4% ||{1$`H:IYJyC 5iM^TivE0☢NRYm>S $u9 YQrp㖦ihXǑWg\_]#,cGx~~c^<<{xzsy7$EN0rh]ײ,YEdq֓$eeH32e2F*pvvud:*cߴG1JGYgḦ́iT cO^l's<' ,N*+e^P^~ۿQn)g咺jfZpZQW.*YJV3@=Ry$EQ&CRqQiEG Èu )Rq"fsN>O#']Wypsc=I-EU"g~"Z&k. nryj9fL.|8츻}d^R.3&3O-8&Ո(M+WHab"P/x 8 #YJZ8 q<5;uh^?)tQKf{iJ5>ZZHBTF.`6OH(Mf,+ßDg+hiP Luq2򇬝#G)ZGa<* h%"8rvyD)‘ :̄GAVYPh fO~<9]n o1  $i5ށ岎c" 3e71ݾgܰOхh"eiճhQp1O{P9ϯ`Q/QhT(Y|BUeQoJPT |qj j]'Z[dKN4힧cJhp!DNA Әu]9=e臉n@(@" MEd\)Ex' "X>zG^1}I"Yݱl20]b}fmp>.# R)^<W_@칼:' Ȼ3Ȃc?gP:-]`m~ =ww!(ʔȼ$K%ZO{0x 񎡏w;[]4ygd+3PV9?g()ͰN30Qdql#=Ma4}I7MCÄN<*K H 䜲fvZnxoHRuj;}s-IQ-*@qh-l ?mT/'I$'\?˜s&eyݘժwuꨲy W,$Jlӂ ؆ۆj  cAH&RRțs>\ܘށ d "k7Ffe)A(SХCֆ(@i{U|v=i4|tÐdщo6""%~C4ĨrI{8#JJQwlG,G@Eһ[RL> 1ZS}}z(cUq}d\;O8[p<ɳ}c.,%ZOo}CgחuM׏H4ڣMܖaHO[PDZ\ !8' Oar`@ n"ƐZl*bT|~zW0lu!]V^& m_3_7Mc/+x$5| =Ÿ0#17x'9:.8?dZBp1)im)F,Gc}F2O_O*C^\__ D 3N{Z ,w#0y qOZtSD 5S42Iwliȍ㹸}͊ϦMfn֚\+ldBJޟ[aIC/*4( *S80qQbtoT],dsY]T`*E7=Ǘ_3!cq9{OLcfo[~;v*mqqu; ޤĈ)8$,fs.ΙTQt$ nĹH )=1Oi JFEϓ& Η82r:Ly̘MJA(4Me2hh"fA;v;nu1'/z~;]1DU.!]x h͢Ib> )A(0n(RbN`v"6X2ྡྷiT!Yz̘ CqSW q>p#WJq8at21UEI ܄dywL*s8mە2Ռ=pwu4ܜߤwMZa@GjNel;7[THڑtwEդ:kH 873t?t%ͼy¼~FFE_giP m<Ŝvwϱ nGEGG!D H@g tvDh ٔ6 nҶ=]wh)\hG$%bg~_ב.!pq~G/++.W},yVPtYde] Lk)u꼤*~b_~GiXϞw|g7)l'6 ʩ3DkK zBB!`ƞpr/2B!4M5#˪R QFBb%O8{,dZ@@,Ɋ8)8F|F^$f H"@TJ BHU?ć:0a]$lY"'cS VA-Zg&G%2*ӌr8Xb8yrEZ@:qdH#ւ"ˈZ`TGm1:gFs`c cҮΉxbU餈ʜxB;v,%0}( Co<Ҩ9&8HMg 1e&!LPu`Y EZ.EA]u mBxi&}䓕WWL4:bfYA^,LYXq ︻c{LVQv۷|g\__= X1AonQH=Yv:,ꈌ(rA"l$ ؤatnĽQb&J?=? -BL$qmt!2W_nw8z&G?RVSB$ĺpS.ȳdU.(D@n$y>"Aͼa t!U(qM[m:TRaTQ:F~H!bdYEqَbI3Fk(@eٷGi%0$~ d7O?fPxS 7}ñqzݮgX!v;zIٮ`-1R NYO,/.81-ηi(%0L,Ϯhfn2Wx;k.y#S;D9^0 R}wӵ{2h9u]ϙ5ź4n(1ui0YOi!$ 2Sno F a;YJQeijYq/Z|H`8[!`/N[%Aa$vJqar1JR 0qGao;sU,EYSU5W))M-nJͪ*dY,j~G^d$N>FMpbĔtC{|zg>+nJUT7NGk(rmDgJC Hw޲Y]{Vd62[.Po躎;w,fs"xGrsyH<: 0>UT3Ơ3dڜ54RPkg+=;޲:0at1˳Jj޼{z02O=?*_W;v->LEpqd&l y.jЧ?T X؁9vh4u "pwT.5Z:i?< юG6 -ѣ)aTbvY49.D7VyMTUEU-M`5Ռf>.APT͂ xwS}i\O&ՓWF: c (4 VsfMZ7GڔNj|0F=<֥zB(_3@2"M#ÖvKRR-Wql9=ՒY&q@VT SK?ipyhQ0//) ƇqPO%?џ@ V˂}=why Of]SY@\B08[մHf3 R{lH-&aX\p뷯XL#cc2rUlt{Bp oJ"DZ$X:-Dd|oƊ>8iB49|Λ7@Fb]b)g 09V±o8;`quuG/>)߼0 39T%ǣ7R5C?b jًPݗ(YcTFR0JK1Ĩ1iYL)) &lvլ"'b'O˂_yy!H \+0ӁnGi&150C "b1o aҐg<ᑿo觞.RT9;;gÉOӥC?LC7MdKqv@jI.)I\$J4FrA3;gqvd{Bи Z<ɨ':I7(=Џ=M ̻&nzbWRϯO~TzA,~o 7oqY,j @-JY8L&BL! ҳ8_"g_S6̰C >)PyzC`6h%9O]aI 5O|9#G68˯ggK~_oOx5"DRŜgK"ec;0/;b,h8ƾGEwqAgHqp'IՂx$gUFVi qIGI躁΀hFKa$@a Gd 8.v4*VdRrxKpIEdTՌ,~OXFĮ( )Nf,p6-pGt J!bɞR8 Db8I,ↇG#@Y..!777l6zHYgSϾ[;%,}s8!/+eFr@REb HeY,Q N Y|q΋G,y{=F{$Y|vN3_<[ 1@4|)g$>p}]7UaL <5*1DUk=s3Fd#,ȱkeL4HAFZ돌>]J&kN5J㼧5/SW>- Gj.puvO>qn<ҵG"qJdLg]9;-/eLnT1\\w%fJU#>4QHƇvD9ϞErq9^Br~A~״S+x{Eb^G+8>V+B,u)\ϐA\1tJM alvdRe?ɺ[΋tQ*)4OϾl* pꜻe0!ɫ Q֧kǀ$yn^ӵOWfCh&9{u3d 3}'w[=ڤ5"#9AlwkQTJbog9IoO&+M~ g 2m#.O>LK܋̐5MSЩ?)BnG79b IDAT4%h|1_jͮD[zdS<0Ȱ4͂8m 8A 7 r=# аp#̮~Ad oGBXÇoϗ.uww5F}36;,gv;2YG(ea{|vNYDS5Lj݈ $hRe\]]C|M;tyjJ;IWzvMß?c$ϟ)5?Of9n_ݷoxXXo<#l|{2,Z1e0N}߁6`)dd6G ʢ$ gG|)w=bSySfAY&oy;KTق<1hU3oO>N)*vD&Q 5?Osy\_߰Df' ˤ3VgW̐eeôa<`J4J|k"ɳ*AE1OUit.0AQ^ 1ط=X$"WdPɢA"TkzD\cz[QQL'ۅ1fэi Gsv9~~`$d*iJ*U u$FЏ푲ipvKPŘ ||ɇ5ߡT hsn_۷t@U,-I) 3%(R}_I6)%eYhǁCwL>! z7".UA9ȱ.(ڡ%FOX nx'yvs<;>ƻgkԳ-Fo#sO#o VTIzl6-|<>Dn"+ Dq8vP69uQԆuOYgxK D@9<>|a*v!|ggV 9@zbrv~Y> ǎ)#}YY~vEfI\'7M,fsΗ+"+h($֎12P<ȴ[zɼ)lDZ5\]^њ#ق}ic`YxX2FrO?l!G V%8qF7"U)D4 1X;@t.d:gfpssZLf-*3\2 y*y(GacG-ǀT!fl6k-|_|gϰ~C:䨳@Q% )`lm˹G3_!0QZxe fO<]$qH,ؿo_/b pΡ#!SlN Hj !E&dnY. Qs? wT"ېFc$dʤO,DO/j J'}(ӶIt t82BVUɵFkb6G DO ÈBD k Oi--=͎9{?KYVԦ2V&2-_~; GLyD>⦈ IKÃioLuUYa%K,X"NvꙦ9g*~L`cy˿c>#q81Nɝ YEtlLQ"tAU-A5Ѓ.m! 0 lNt XE^șgؑg3~W"a* `ǡ#9?gL9~sK՜6 ļX! 4(̰!Dqnq$Ec8%)Bbw)TPGg"(qŸ.qdqtmOQW{6dyq})9Yp2_1]pyǎYq?޾ٴBm"]1GaJ Z!DG78%e12m0\I<@E 0L|9pرxxctwl߃zI՜quvG/``shabu"GÖfv8/:o~ n^'o;Qżbb(՛|hmҐkT4-io{ y6A@꼤 ezpӄPMR[G.5:IS",OB uy21|>#nC 1bݞybBsi.&5;F(`z)6MS\ A6-6x\pxٷ=G"C@[zFUкٳyc5#?-al'*O)̨<=Ǥ xIڡ*qHu1eCY IDFB**Ugp#`CHmNJ6=9| ,W+t&W[ay\#I̱>0Z^_}ū-W8 ٳOYP*)>{{HdYg8΢`a;2u| S*..WsuCzvbHZc%3B{w˫7m۔ڜ&ʲ`=S/rqk2COQdL"`Tt'0hjᆬjTߘR8lɬ(s/ A?.l֏: .feQ-黁NݑEA Z>CTX̖L<|{vTf1[2of &N94ZW` Z\Ϋd1Cɑ'*|yJ gTRv ]oqq䚬(ط[&TH)p26q}Ɩ1pZK?L/uUcT@@F[͗plc m@a+FknЩB0KbXy\?0$hgUUiq6R5%L=~JFTk{;޽y*i}jTu4z>᧿f ˻w'zg&醞/r.Nuql[..4~)g -ñE K~׼jP9gd~D8QBSȷ߾ __Ҏ;>YktP8=)íF`2IY=WGqO\s~8`]EVδ УD屙s̚O"74eDE7/YˌqbZuN yVJ:dk>UUq*~`=m;0>bL $EtzHtJ0GK]idZE"ΦwfJS"&ՀVS(\Ht' WxzYķ~陾i1JI a}%AJ(0Ș !6=*QH\e[>}|;޼i+߈? \UFI(%{w\T:B gsvKInA8,nS~Ws|6'Ǝq#I5":I2EBpi@ D\gBǟ,RNDfD&z^įG.S ;K7L*Tm!pصxג%B|˩!=Ñ1!הy0jf!Z\Z0[s{`NO]kEg[V%Y$:Ԏ=EQ)դI9u#7욑pՋ;ʋ}lqK[Gf@`.9Ӟ<=;m\ =5Ö~p/9ֻ8g>1зcSpyۼftj1BPVsfŌ?qh7隶 Vqqub9cK-*S3 ʺ\wGw-ͷ;?"UIp-ėjQޑGOAXHȉ4I|Iy3!HL$'ѱb#} eN@iCvaj%i;zeSnEDHfŠA Ie(Za"ClUmdx>:Xkۀ =8eZ*F(YFȜq,5|}b~ ̖7.ww_cҒwlv'no?˟DkO>F𳫯y~8`]w'^XV( > gKi9Yb1kq"5y q}{j0L(vӀ*]T7*4R$I$9YQ3̈́G$Co @-hgB>mZoLKQf`u?u|#m C JЊa HGa=R+7/MM6|>⌎ъ,KuB}7 ʐxc=Ij0Ԉއe\E !4Ię& $F-ϻ ݩGȣ$Й&3cLvc2NAa'A=BD(8((ކ3X#.@',!6g@0DdJIsǸһ?0 qj=Ur{{jkm(OQ<<|7# U3+.ozuM;SotaP%"5I)C@Hڱp|fGFK$bzyI. g&=?1iX; [USWʢ`9cݐ$}2gB`>\q[%Q ~mٷGn/$ ޓA,'I5hP!5?c(w- J)`EڑI$+~0i1Lc~?$q !`Xd :B$5Xf%W7<>oAr`{f#,A)u-FA5^]q<<=?|1mbmaVV,Q2"ⴏLTj42<=lhϬfbVן~9k򊼺B2؆wx1Gx.`>^$P*beDmkރ?wqAES1' (P V!$Ol#kϋJ;9 $adqOwȫ;nkƶ!7%MӐ)FSKorOHQQ*2W78\XnŽ;,_dBP:r$,憧gadb:oXΗ\_߰ngnnnPJp |nɳ(jIL׌Èю er6,eIY$77oQ:9Fۣ8]wdt &v$121 1RVsҤde%O."2Y2#e5g#Π빿 3Xy B0y`Ҕ&?$H!0JЏ!/ & g ik臆2?-ƺVg\\^ dE>? 8 "L'M4 8q8>|,ْItA#:RH'剬W9F4s#K`;Y0hOlyywXs9FeѯxK?cҞ$Udi"\X ,.^<8gς`;QC҆nl:H?2\^_ƘBcmic&~|*ϤY%Jt.N?ԁ/JJ%v0N#>ć"2YJ54p.=];p6Oi-xamh[!pk'B {F&KSF;0Csm,*YgYA 1^ "W8<f݈y`(fݱX-ɲ T@|j]SxrꎌS͖woS5hڶeY*vn"0Ks )ж#)֌K c4]("VKw'>jiI4] tJepY?!HTr"N,+2HŅM䇸nsͳǏ>:+F ,:4Iq63 I*~_q}{M4kveRr{ް;I" nL(#@{Jy22-G;ga> c IDAT˘N&228?F^xNmɒOY$$)y,gLN&~:"+ZEZ|bZ2N#a;o96TeN[SȤ $7/py}ǟϾGhM{ʜYU0ys}|b2s SNZA#G8xq`ӂY5K%nr4)J0qu<#YjUZFE?KSёSvnǦp\<,fs,%I 6f%b Q( \<͢TJ$nw.G &faQ\G"+I g}|Hh`,IRedrΏSDcdjxNqj=4Mͱ>^/y3g, %<>>#يww~7?Œ$IO-,͙x~;ǩP2ruj@"I='"肗qC{nL(_ I$i,};*Mzۗ1V9tmͩ9P7pulv=I!(f Px \,f(!9]8 r`sO<<}|_w3tМZ6's_| g?Lwg5/YgxKɐ7I~V$R$0I~nFpvDD-SB!|@"chZVQ@L[źk'ơho܄D0v29:֐K a9gӅh04W1;o:>d~4Hqnm?flޑ$-%vp  O=!$$JO GU ](SXu&ΐa ZZJ,ЄR1h"2;4La mщr1r<=AQ]K U0hs!(4LJ!nX5Ӯc:CtjJMI^#%{RXS<9ڮ !;8jN oOvy1HЂ, +-鉗_~v.6yb:ewxRvHKvž >|bQ]7 I p~DO @$ h=trj(h^byɟrfZ;(^X=(*ơzu1ff%yr.:V,pl."9MFV@H2;}͋/YCtpiklĻ#.h`׶X{ځ8߰\%?|G5 F1KY]_sbͩ;==2َөF%`itS?'Ynbۑ$c4Ƌy,fKŜt8N1;v񢍈g `cNti(apxLdhs}uGZ4ݑn'+ I^qqqĚ~GPr6cuyH$ ۏxɲ tf=(0Xۖ@KR4tuO? &ةcji#4Si y=:Bd`y C/^`!hDGSJY@aE{of9y!ID MSS۷t$ڐe1Wޠubylq/%uSGY߳nINE٢찮Xo(2UtnrLT\rK }sT2`Hf2&Y!H&4g^hUߏر/{ K2^d^Gv8WmG?nD v]̧Βz(2i`!ytӀRrR}z$9G/&3,bVs&=hI,B}oMYQơ|P >sn_b~Op扢LR'-R^+1EtϜڎc=|BC >ׯ_$ }[iaђX[*W Xޞ!b,0v176nD&i:MǑnsMYULFmRL"ֲXCN~`;v#kf Er<6t]cER)y9R(&Zc; ‡$(261L3̑1(BN FI* 6Bϳ`SgwHKzJhN!-MenbFtCKEhE`ǎvߐ&F0Z!Qq-HS(KI,! 4Ge/8Ā%=BLdaȂDﲳdyGC4K8dxO6Vȶ-EQVA'Ý$"w6_Xg{8;2ICs*ޛ,O8|=7/`9v__ٛ(Uzl^! f,!#!pR()Ň)uMGx4mE޸Ę87xk1lљsv(E}lIT9qǼ):;4즊a6Sy`:ǜ̠4$JpQ*nG*C͘-.Ҁ1@=mȋaT՜[WZǧf%779 #`1 5OsKH^e'\(U35HU嗿5v66q *!K+ɹ{y~z&R1Dl6rcRh7պ$Iz FY47\H)mvсb2feoۿ&;W~"NG8qyur L974'=Xߣet6)W ޼z؏|M{pc-HʮS4cZ?&nem9PRiovºi>Ґy^0y o>{C D.]u?W~&yym?9"'/2H@z[Q86&Z1,2 = 6'`LNrv-{dYSV3./9Hn85_~_} ymwo@?Lx&v#՜Ϋ3/?v@딲X_TGs)ul)vϴ݁ቀg6/YΗdiR)يoU9NyV{x<{bsLûw~ `a=H!*s Q,uI^3mG&֩/EV:k#Ic\&Mՙ58 ˋUu)yaȊ}}p쩏ׯ^q۞WԧMseJ'Teqj/_Y)Ŋp&(KR]1+. V#|«׼z>c^&W%*#1 c*U^'%vrѱOxyGFT !797q%O7)$J9g^N7%YQZ.UE/ȳ,b@?}desLZ!EBN놾9-RՊ$&iNl[~0y RT,Ť,cyr8/\]]}`wg6+*nڶti|W"SҜBz4#Ĝ8$0JǦ.&V̪_}3PŒmS?^ܭYPU ÁT38k$)W ]gPsf+Vk.6͎OX߃t4 ugFdž)?AE*9O>nԱ85 c8(JD8()~ZF-O?dD'Q $Zv=0F" DFQέ#8*Bǻe?\<"_%];Fע8{q0ayƔ;NIpg) >qa(=yF 2m ~¹12DEw>!xzH GǮήX($C~/| 7lMyBC91/$ΝQJ#LS' d-I0v#1軖*+GbFOFYZёe 8Y`ѱ*YGc >BHP`Cz O4~GnZ-y%gc쩎`Hc %wY c 䨛3K4y╒r&rgb$G],jsm3Ct#㠘U Tx XPDp2RMJuhuFY|QЏ e1ixM1؉ ,S7hIӎiI? I2 \ MU-1F MOSΕii2Aiq[MBH!.,.8FO$I@f !5t-Y2)%* ߽'.DQ sgs3zѶ-ϻG?Pw.¹-[W\|A6G>|<=Y%^/=q`N 8$*aVȓ2j0641:;҄C'Ԧ i$}p'H|`t` (9M0: M iR=%&iO#B4o|/?Y1cwo?2Cphv87f,gKy_}b{v=uة%0=N'BbFt퀳aiZTK+4i OW?G@hMUNQ9( -vHH2ۄhZ9[/<$ܺ8IYlsk!JPɐRt4' ueo%Idd*n lRHx' )#AZt*.%Wtkd|!%Hg=Øh x멛ĦS&5Od:b HpĦlf&٩RC44 <*]A$ns4 t=mo/.XzɇGFl'Pkwy͸wD,u;YN<\T0A6 ,B+tazbh9!ON@H)O>֎)ڦ$Q d(d2= *ZI ]3yfȔ *M:LJ1MN/*/=mc[1Ƥ(O6 c QJ"c<|1t2@YNZ3.ZcmME%@IO-ێDdh T88&b6pPd" ,+fgt r*E%uO|Ż~X.կ~ŧo>!Ƥl6ܾq'KXf*^^׼x¤gеoxdt UŬnmc~ hO5,;4웆 li0yL]2զ`23IYxlp6%/d 8)%~w?Ŕ-@t_,,W<y">rxꬠikvPL4]G? LfPdž|AY|6i.HIHҳwH b;PLREpG)YJRn"6[HCf;H(O& :4@n8fS:1srk9[-c{@DK #\.ٗ_bLcា@?Zܱ=8h9O - s~`ty"qvY-/̯zF"#FZFgQ*qR?vQ) #D%%T *gmJH)Lzf\'ʒ&y#%vRR5LfSt#8Ą3,_H{~x_ǧ[1#ÑBO; ٔni{P7HO])ޥafx2G)7pqq#-j.%Lq8_D a6xni<:gGv=m3],yl麎g.Y,f#C)/隖b1'?hPlv5}˗_)Z~..CsnE|1Cnĉ9;;:1 軡E*AO2#YZ'LrNl(nWƤYYcLȳatGN`:c:pvK߷,*E0xsHi9؞eTy2|8-wrÐ,%yb=m ]79ei#h;"!QT?zE C%QJbt1\11l>bhD 5c1Y#E!IT(l.,}_qth(2U c{T1WY積!@jd&q9$}f(˒uצDO#eYb L$RfHpOӯG045Zd 銪JJ{@ZOc҈5񜄀L; (sl*%-&q!!U^m bJn洍e2ɑBro(whUztQV+޾;T#RJ1ee;I˗cGLH/)4s("A^|vC߷(RLJmMV*q$X>.0tcIN\j#1EU5łrRptf@7$ݫuJeqf1={O ⧥IȡkSzK?(Nɬ.0DdZ Q@(ˌ4gF* !X)Î%e³ lj ~f;F#y!rւՄjdb8;ίP:Z+r~dh{T=<#|ū,+}x"H?>gǁ/|x#E|b{lΫ7baXN9x߲?@ <2 -GEM< #lG`#EN2ScGW@J4s^4:# # C(AI!F/V\=u{+GtOnȔfG2Ŕ(V5yE7=]mX/(zyRggDIi;]p~V Rfd\LdF5#[/_p:TgN3^[nnc#JӷO/nox޺t&O?Khi=lh1#Y^,Y/L"U13ZK^x AJ=8~dZ0>h4PXθ@N\9;(SQG6=_buvv=wCf3>\߳\_OIp<LJdO쏏G/sfAkMUe4HЏ5'7Hyq4G(JXL#U>Ջ|-CMD}2,tCa)2:IVMȆ@[<=OAUi=gvȼzЖG6SKUƾ`V`Yn-09 c0$!`[deuݱ_ S:G> :BCKOHGլ4 YV7o^S R<{d}62=M vL\Ͽ?ߥ]C53^| sԴc@[C?]=(0JHp#2v0J O .H)G<-ڕPL"_s#OATn\_I$HM:9W&G0). ]jy>G`Kj2K#?-ۣdiyB5eV&[f`V9bD% www#|Fj*5& !ڦ'FEpCȒO?tAscOx}|Qa̔"͑`HI S< D+{'Smzpx"6Xn_1A_39 d"օ\G?-Q''9Q SK x"D|xyց 2N>>O O&XG!?֋5"(xJ jY. Q`]n#Zi{Gr)!9o^&M8N) Ś@vsCVHb MZĔ) uQ)тdc?"HL)R9pcٔFShs#.i)S~)'{#Di)e4Y'_;xݎ|gl݈iz+%@ߍ 5"e!iDIj՚ׯp݆O^W?)^Gi92L1- mRk)}L2F*ʢ4X6`\B4mP)v̀':I) ]`HRɹ1UȊ|L6.ͱAkW<=CK5(9brlVҍ-펇l@Lx3.=,#'0xzdRdD8l7DEg9wwx}K"Ō~tl6głT>UWgKMJd!0?v\^~?k}#9?٧6ݞ,O[0-ͧlwO|[xJ1yt1w 书Bpe(]?ݘ`#ǞL TUϹwU}?5՚+ 9={/p!7ŗ掇(jͫ?߼I=WĘ.0z0)4e9xl(2CԈEuԿK?HΛO>W ÀT!u"; /Axt( 뜲ZAV p,L:NDC:'7y5!2}#qQ&{@HcũjbPJ"}lvHcJq98QT $!b1fMU.X,$n޲۴ ct#]1 fVi" sjF)'A5d6[Q6Db! +$EFp$Lsk}YTgIYMdX9|O; ,(3&ZMȵ%;h:W+")ɂgsnn3~ gu5~GWd&?3oN@w[k;N5dmKV =Ød_{ܰ*UvFߦ:1"(2h(D)愘R9"J$Xޏ( e9!x@I9bLKMmA&XQ ʼn 6JZ@ aCz`m0#tɚK x\j<鞪3A!2xH ܢ((ĶrCj\Fc+ѧzŸ#Ri)釐OBOt$Bgh5a1/W?+!a{dP3lд唈pQv lJ]m8:MA:E#-u#`Bjh+}Ia=2-L3^|N ##c㟧RG/} RJS !;[ǀt;,=tD1HiȒX ؠ+ͱclo;2C+`P~u$8Pi*̦Kx9H۵4لrqFY(^gtc]Ւ&9`{|=R Beő IƬ_"?y|8CQN|G51Y1#aDmK܏{-Bdciで=7R !;LO x&# csJG1}/l̋ve4"jHU4Dg(5vC6q6%hքRRYuAa2IeRjCdAE%yV, 6DD8Y (*g9e3-mgt0ZFO朝L- k$2Z"`e6w#>h\nGw Io#^ͱ{h)ODG,?aշ utX'uTUsҙGg%2]*!FG]T=&g$ ;o5e5[9zہ= y|˫7Kz_64MGdNg,sl[d~-%EQΘaw{4,m2l66'6dYZ*dBk:ɘL&V+{O;7_?0v=锶k31dfXfxxi;6ۑc29llZ%#ByvJ`!LOPCQdb8/F0Z.eɳY.7$Dw,KmicTpɈOܔ}Kb2$ @aP2^j=QE2e&9eFFJx0DZXEE1NjP) Q7{Й`tMt'8`C'fS16CHgY S?@BDŋGΙMW4Zhb,YW\]=w7Oh"i~_i8 V9!FUu/~s^c2>6}~J1Fچd'O[~xG#X?/Lz0͎Tsvv!u[`6;C3f2Xھ!*dY@^C Dކ2@4@D5HD\Ahk"PczuENzvDSRgac"'IHU(S,8:)"=gW5{r JvFcTJ@(R:`F>mN9ME~zp"K2Kf%YVv;~|=?墢($fj5U~V+\+_|mj]~PNSq:bLm)a- =k2=qY<{3F^4@Rrf2s_ z︹4bWbpb($Fd!b!S/rW_zAf,C@_/D<~59?[%|Q5 1도iEi PM%=bz2Y.<8`N!#u65% xY-<> iF?زL4RXBl>[S*Lv>HQIO8M]X̘意iU8/"jeYI csΡTBGL^mʊH^#[0wx=;ɋͦz|Aӵl7,k&ӊ>ٔ`簽=m$Fl"C MdN|ן7iSdYIɩGp3 cJ8Oe#Pm5P<68r0qȌL Bɲ j/- csu.e.Gpxxz䳳iξ9Y Bv-8S$T2}m?R cc$1aG.'LaGKUH)Z)bH#0&;8>ld( %C8[(dVdYf̧S,mﹼZoޒ9g3 e=*ыG5ʹ{H)*rEg=O|,|÷l3M9OOlIUЪ3KԻcف`1+1ʳlJj!sV)JH}R^\ 7摮S3Ԙ *D QCV TrTBiH0IEC Hu2񤎕~QZBT 4P2fv@!EVDE5 }d(<dVB2OGqܖ%icӞtG>ĔUYYU]A- 2PP  P2`%]tuuUVdDdfD~3Ѷ vJ}]uϹm>x"~ E8tg߾$ YfEu{^p#ع(~r8bŧ:ITJpQ/+꺦^00r<'[1"vvēUk1H"qeRdgAIA9 FjH.AE at QTe&luv`D2c0&1RtFcfTYYVdN iʊjAQ|Ż7Y-V=y.ח#sUt}iŏ^c̓'W\\]ӏ)ɿqExL0͛7iP|}}g_"_gF1J*̢z=vO~9 c48oi;`ʢLp'9O)@t~& bR|tJ_sw\WuCHU%diV\^^|_ݽph#ȋ bJˤɢLdFk2a;%~tꈝ}:ʼnᓊY \RJ"Ic)! Ò%EQ1}7h։%aS JgLOil֔cnwo@EqG"lFW%Ć]s/Km*̓K0YMv/@/xxbr=yx~L ~9!n3<2M ;M=Q.K}Hd" @L=\ঈ5sequTsa?|hwxo9&*lKSd,ӌnB(MYc4΁]עu!f{K +b(%ɌbL v;QҠU.AtFլ"cZhUry~w[B 4'N) |9UYPmJ5cߡtLCjB0jfhǑaY(#'@+HڶEϩO|of Џ`' RJ<,$e8eF;Li. .//yww ZIR~Ղ9eq3uYHdBk`xLh{f73Ǚ9qȨd *Yi1SRKzp1ɕY,TDʑ-3,R<l9vFłq4'`$Sڼh|1@ HE?b0"LxGw|xx8%)!gЬ7xaps`p#>R/Wx#yYC^Tl;F E!iVKvvwD^uN^dlB(OxҞyp97&Sl6>|dѬج ka?p8ɞIk*SWOLalIa4jCGSN%Ψ}gy ea/_qyu1% !1'vǖG?/>vz;P9`> L/y`-O?&>;c{Ϙci=apxuba=ot[?l]7⌷e!Q!0<mHC,覙#BS b8qHQ>ldN~»!}CԜ`]ɘQSm"1_7ըz<8=;{iu0Г皳ivTeA+F;3l7$7ivlh"G(qpyO?CY7+MFՕÞyH'1,&Ǝ#;H8r;p00f03OzCYט"Y*=QZ_/BU5|o˿߈^Z/[4sb4n=yz3f'1k%3nxb5H7Ltcm)iͩs{{*Qth; 0MkPjrm: B%Ăl5R!e{)8??g۔i :[pq~En$=1Θ r-mBVo}R, bT |/shvt8oPFtKD)U݋:P)4 "J-mcz 0!br3}'xQ%ŊZ'[L+Y,` B%2ə(ӐtK!.:4iKИ*( d !c*C·P` ;2YY!43Imd8{ gk../b,ᜣm#Rd2f72Y@9&"<>[7777 ڲ9=www`YVuv;Z/nޢ|?+˘AIT͂qte/~89>ʼnJK >+&;${ B$Y(Ooi fG]ק8( :D^dQl˗|ׄ`Y}}2E/QFH9=>%h2TAvH^fX\D~2Wdݬa<\S90̓E8vHhS<" șa,*63ȱѵG<Xb(5cӃ0˒zƤZwbOc ˺¹gg)-#M]\!F&&{8Z)B(IYک"ZheQctiQh |C?*tqF:3&p^YYq:A Ә)3)%cZ(B*L>|k|'nooAGX˳avU<e/fy{"xiVPȗDp} DL3FIܧڛ)1O ?%'*2)-օcÞ0&|i? |m^0ۀ95J,tnYA7|ݣfwp삫׼|w V%C~L a .t(#FWxl{{9Uh>2,Xh6, A 50^]PD!ƌ$C$ 1l2uE%G1W7BH<&܄Tgf E0;cyY EGV͢fkiӪKzO,4Z.]\'c]OVH#Zg\_]ᐪJ u@0lG{8tzDbH✦Yr;sNrz`&,JH8j&!\g)M Pu=<%R=TzN׾E,1}򒯿:+Xn.yy^yɵy͑ݶEy 2 ]n3N_#& D 0ctd'if#Т3Ŕ݁jbzyFue^POnhޱop"=dEvRD)Qdu]4K{CGR$#>8I-ŧrSZᔂH' IQۏc 0DLxOwʼn5kCQTX2Ok9WWOnLj2͑S ᙈrFp#f߇%ʀ #Rx O?x؝ePU3;-R,ٞbL-ݐ*P)fYL|BP$]ti-QQg|JS{wbt("yDTE͢~BY^\\/%7OrqydyJqZ@80E<ϧZ%6X..+{gkwlVo.g3!UE%^t>Fl{gCw`zجX7 4dKeY3[J^Զ>IhYY@@ wG|TH] cyg 1?ų(^1My) h&JiDLCoO(sM ԹA)\'։uDbE3O #a쌲ۻW 1( ':L#<ݻ7@nOZΟt(#!F ]8z>*mwYF[ϩ+V /2{u8O= -hZp)%E@? &?at7 =߿!gk!-0C8Y_WEhczUňc~Q2ӄ2 &.ǗLL -9ͪB)&im]g[ 0+'0nڳD0}ݏ_\_\e/otpnje(JY.ڡŻ&"E2nhTHm>]+9h5͢,3Bi?ly:Z\qfyN8C`$zQVKaG"m*³=SZg>Acy8ӷ?EEcH4HE ,O TLy)_gVkꢤ;~č]Q,gbCbE4h#0RSU9Je0gGdYOmT{ x(d/ @B4Ւu_^/oa^!2ͯ~+o曯Xp(J&Fh& (58ZS/ #NC!ڀ*F?8m\TU0[9t,rcl!#U#YKr٧ܣԹDMn8PyB #T1܌AƴdNQ%jLy<1 n5!ڔ;Lxϯx)vvk1Ec:T躞袤γT)28?FcH0§1IMb /JzAXpZL}Gf3S"0geEU4MêY=ZK<-h&81L-ZKBTTeD !\]X#?MƏp|IZϾ@?OսilGE [#s7I&PD1;~@r: f{L/}N*nw.8Z}:sngcv{dLl BvhKd>+pK,9"dA AI+He*)Z0MyibI:%w~b,E^!Y.oo4V09G]X0YAvHUd&RԆfUYv˻7oJ%ByG1a'Rחi۞mM/w-dydNap8g "YN{$ҡ.ɳe):%f3~A2HMD4Q* d&ooǻt8s욫3\n?~;RZ^ s3#ees:PZDڧȳuL~F(2Gf,J[Dp#J1UcSRۨI9^z 2OHwY&Y/kVg!Px||Iꌋka@H Oc;Gydʉ큢<M+ d;q,7 "ɐEFlT0!8?E21R 4Gt9c h!qs!ia@],0!U%#1h/)kIs#Yb zJ;?/,nbz`}=J?{d}yQevuu^qe)1&?c+qx>zCovډqA>3P5-uud;,~hrbz1TDG"@"gO1,Jʺ`nhvruuƫW?&Z %Mb9m[i8\#2ʧѦ3RdNz1&KP3kI-i) q2Ýo6a{T]Њjӛku44uɟيkxlRUAk0W4RڙLsN^Sy1MUԛ!p h8d <񔳳~->틢Aky51X$iqSMDgFdexeMX>qHbPW U1r}HMSGLuBmrʲO#,EC CZ /%emd dH,yL".ądǖ(#zѐe5F<7pmD ;WWW<}d7<<ܥ@p\J?PK!2BWDl-v CKt̶# c?wJ e>2ue&V\n, 7>- LG`XmX^>rp@01dqh;ˋkv[޼} ERfEٰ?35eswEwW_}W_}o[^MI]\?I*Gu)r&71M=*Ls%c72[OSSr ?^<9vefnEGYW]e^y2lԚb D!!s\s!LST9})`lSJ5wy&0.` QDLp2=%f[Y*|,o 8x-/ϩ3q"/N3!vV0=³ZV,zP͆U(t =#֛ h0g `v7&G JhYc_X_0dLJ7=Cw$,(Ma4)Tuʊ<7 }n'j3Fk(~8=^9/o)#gk}[Bt'a(Md-4qu~ mmxc:h SAUVHuJJ)2zɓpϾD~=m"T9w0DBb'W}sRI> qu҂i8GL2iLxRdj6<{'l{rŞ~j6yt Q9bJԙ-^#.S@kM^dKX7"DD0&oPTJLcl eM{1*úQ1 IDATcgZr;azEXXow"Ow!PB \_=%#]K?@(Ɍ;8c⊫+UISWҰjvL]H!ɳ)fb l !CDFE|\]_n(-O!SOLj} F%Ӎ=.3 sTUzY@dfU D/uӰ^oNZҖ;8M $GfX+)g_pvvъd0ƤHeP reٰXn3MUb09dF>||,BnEj$/sw8wD13Gyxhɲga )0; ɰ'3\/y|8p;82UaTg}͓g[n`m/ϒb} MYeia=ާ.8<윶M79ˇGJɧoxާ %<_}m *x8AiԂoٜ]PT`<yEJxy "Oggdw#CO!'-2=r*EVƐ1ޝh] N`!zG3V8ٞ0ޥěց&+rAUXgMR1ZK E`Q=64i~Hp#D %eYs%t}nϢ^av2M)B#Fˀwyp6 MBZK qt鬄KtY7hyxatO{cS^53'߂x~>lOϨʚ"ĎDYT OZ^\R5Gs!39gg./yqLgyNuh$uSd9>g8a;ڦ^׼~*PY ) ZkT񈒒X$PixPP/,˴خ p82.i`]C7ğnѳZ1ZSi8Qf>uvd\Bd:f<ǨΧE1ƩIC A؄z00UZD4$zT) E^X\&ӵ'݇؝P”IL!UH{"^O,2'8/# =.$Q0=x:-)$el%3 \d϶R`2=)=q`Dq! <:,aN@U%Ԃ c6iI:MӒiKoo8E]3t#Sے  Kӿ/&r¹)=HeL=MXىJX4:В OOwϼ~zŋ/9rdTDa})=$M" ZKYZrFbiᒮkR&XŊ꒬.{e),٠&]cWF^Vo=eY)8ΕLU>H !}Yfˤγx9e6OO;.Kn^|R5_ ]?qy} kLj"&K껑oCX-7Qg)<(K0d9=Q$fMO"79*!{v5o_cojL#HP`*U*I;ES%3t(><Oرgp8_Z_!w>`3ggrL0epqĘs:s޾:G3GDX..$azSw#xJTL@6] G,Tar=~/O/hN2>Q5?Kn>8vfղ<e"bmKz(sQL# ^rvvENte:0QQQ9>WIYdN'i67DœeY e8R))B "&|&RaH ?y a6 #eVKi|C^_e:`d=ぺBKƼ MKfpG+Ҙ+2^ڹcr&k1%qѱ$t7\Xb 2t4efۇ=" #r-h{b87_Ep҅"!8.8tGM@J(V&m5=M(3+G:/X,(œi!.hSP+NǞ't.U!BDaZ.(T'=ΉT8צ 41L#.f6TT !3eg5$t&-~Q HSApuuF爨)r(a0Rath'-_0mHӉ0 N6GvcrMYcRG0*OZt5/J: -kUM{ro'xӉqLM^8z7߰}|}GruiX+6XLqp^R lځ9OOO~xGY欗дCap}}I^}`a2 qW|o2YD) UtHQhD ) )ޏTewqYmZR5F6 5ѓ툣^\ ZE{#(x{ /8[fǀucl{./ּyi1U$ !ΧmBU...w819t0t\\|~*Rq<bXjJNJ'DFH? UrQ%晦9-uU`hARVG/+6ȏ?|d~~eD&2 qym9jEQ.S=tq^v4F$oY97;uz7mƤ僜SGҳEy)Q̦rxts,bĎ.҈U4[D(Ȳ&EUcGu .rlv<-yd %QVnǁ%G`n9G^8m TQy;o8oiSZ E3 -MpiAt=J4D<=VCmvˋW,K!ՋLj1Z'J%S >?YZsbHd;Z4UdtAe,qg|hԼ+ԋehnN_JJ%* 9{^zh)h4-玩sȐ#T}'{|8 e6y_| gW<=>;W Ś4=!-v(dE#5/.n5&P90&;iQJd$RLIs}Kn8<߲}ڣUϾԎ{q<9O,e2Bp.BOt !_-0͉O;4pqvQfɩkfi:*p.NV(ETM-Ӑ!#i[aT+"b]hD✔e0,'YTQ2*@z?/9;;/曯BS%X7(tƟW?}n^\1Mw{ǯ[,ck-EɌćgK19}?2t饭r2="HBHNL3yBC9 ͚4M(B`]oNpvv!|ؓi4zHŢb\ph>Yd?~S&ˋ ㉣YZTWyK3s" t[WK6 eQеRj RfsĬ">T GvKxcL+2S$Ryx"f a2<6$eQ!cHC?1ڀQkPhR#4Ca0(ӻ"Sx5W蚎nalc bhӻ/ps}Ų.a3a`q~h `<EVpHm@i\0"UU:o',kݖe!x(YH<(MFpOrr>>ox>}y%_^2|>c e^^\G?FFOL )5Ex+~ Vg\\Sk oGR`JF.sbTD?«ן_x /|p|dr9u"FLsvsg{&$2fTejyFV48tV wY.|7\_6-P[T庠ttlљR HYlx #@Vӡr*W]v[.ݶE ʜ1ZS٬sMGDK|wdYz/>GCY,~\]]aHV_ѿHUJy3ʺ|Kvho9wh)ϸ~EDe ggxox&˒}@͛ @U֘n1%jq d 8K^ECCt)i~dN&cJc2HHdQ14.x!wڔȨ( ñ84zNmd^򋗟o#6%aG\pj<*u~8a4M8JGV("#ipǪ: :Ɓ($CLCTUY S~lF67?}.xwnl{> _zw9 SiRi4mG QJcԋ<,rNm2*zA/6iG޳\t)^.Y,{;Yժv,E;Uط>׼A \!dž-~J]E+>}%~p ]$xIUKtditibȒ27X*A%S)F )JS !EAoOH1c $ayl3:ʬBC<jQtR`L>'r-vf 1 FOLb;`Iȑ)L(I*)UZM龡$R*VАA 䜞 "YeLG+*qoB3~! )R֨ CDቀ9\;KgND!L"oJ`OS.? "YƑ\P M7?~O3Yé#8~wv._7jQh"1`O~wOd8׌Sիk޿rqs JDNg>E%G ) `q'$AQ9D'>JR}ZsvvAUUHӟ!],dx8B̃ T"ÿsZ8 L.cR(ӚL J79 # _~3޼zI4o9 ,(!\\OQBp8pn~|nv(2"g*tJKd%KB̶cĉ SfH@G6g SR2T {pBf#* ƘY5)gFz >>S9Śင6i{xZm&#,@f9P<%%hpH iHH%0J%e*kgZJ&1lSK&2 LaHɴJED'C Y*3qxrqQ&A%6Y?iJzgęCژGΥ{ t"YD6d*gtC?q:dnl]( %B)T0?rxޢ!dNpżmri LVT %Ӕ8LҘr-;8~9u7r{ɲr|=pvKnejŋ 0 ϷLӞJ#_Ipx=6G6)hTuj_60Vdi<=?r8=O#4z$],xAdʛ<6=|OOyx ϾY6,%x!;\ޢ!+ʲS`xd@eKǀBHEf#_% IDAT#fKY1@`Lp^suu?m"c'ӀsO)K.'W cQTSۡpSˈI2-}p}q~Ң,YԊ`-adX,\_Q ,BH.OO|Ϲ+G/YV4c۰ܬB)h0/8?#t)*2g޾z~;5}OWޙqe=U] u]U9e'ud5BktT0oӄ:ʺB ;g}1̋@HL#)'H T;*%B">FO AHHQO$4neYr3mcTkSKg=_ejۿi44͎^adھGDN c_|F4{~?E*h\(15*HiXKv !uԆ|^ !OmG>%H-Z&\Mi*r$ 2i8̌YGDnTy41ٶ" 1n1Ma̅1g\B?$M9LU*񉒅Vp,`cJ EMZ˜pbŋ7=֠EyA?Z٫8mXo.PJ yū=Q>j! ǘFN5\"94.RuTuFQ䌃GtR_Y#cfgF)2F+ tW20ڑКI'&TUa6N <}5΂wF 4YffI7 EbUtGN~~t4H?:Fɂ!VU>RD ѴM#+%9|_Q-=e#UuG>&\ŏ%%:;L?[Y6DA @Ed?Z-9g@QtM{HɜZmxS>}spwQu=a} 6Hˊ+cS7o/?#l0(Y jizd)Y$+OqVXch%5FHrJ<1ք (|p;֋k/,){4}?)@⫺ŨԹ*ÅilRD=:"-2"xSrMH}qǩ;%D.pjZLcFz"FRJʼJx@T=M&q$g^hih] ;S^eQqu^KxwqjҦcg_ |xc{`w|^:ywwww쟏 cO eGVe1=.ѝ<Ҁ&2MnS.p8}De5sOYf~Ep8nL^pΒe:jB>byٰ#,R\ܜcpywdљmz$ֻ9"N4=oǝO5[t6Ch%I1 X C̼"OC"TPZ '.%/1U"b?$-> ,W%(hۆqN "3Df<qSfh"6mmp~< )E֣3[b8[&qVv͂ECǿZ\j _}T'tbe-|IYl%#R2B %az:PY0jQ'Ω.X/BɌ ~ہo .%s3UYG^xâ*yёU;XV%ӑDG ^z}5Ga8|OfG1b5ތE)ӡ, ֫īdo0M`d6[x0t|O ZFGsz.//DPcM4ˑfwxqqO V+kw>yf@sj9; sAt݉~[0v)%?~q8[_Pk6@ x#ɲb^tVHO*^m80t#.TIA%nU&#L  y a@:U"x.zB-yqW%膑"~%=Uac4jYr{{eBJ3 hʰXT;>>'K^՜!ɭe1q`Le'F 11 Mo*C*?츺+볏߳} YG$/oہ-춎Z@!eb!ӻT-+ vЎ'Fodk3|+ oB"T2H0q! ǎ2_aG#*ʼ>[S5c̰w{=g/xǧ;FU20 #ULLi.Dl٦)sGgW"㑩HA1>MGX?uE"J BHRR:A5`gBLE:[87YO6 5AI&aM mf͈ђBI)8qf*w&9TW紨K\JK]sb{!I<ȉBbB29K:xB+GKwpqF!9[.23q$/siQH~XxX,!F!]L,fY(D۷kv-Mpq:臎EHa"buiQO4vHUceDNS4M0H2'bt'9(Ӷfaq&(ycy''bMQv;H#Ӕ9JLVPUUМހڔ,9 ^@H (*$HW |2[te5W_(gQ}`> FԀB*LUF\PdݖRFH c J{e#kY,مdY L}Yem\^ްZ-z2ۻu|svOVj&C<階Ь+T(X˗DO~__KhS;wL\]q}D O,g1#S5e6C8]_ȒŢN)t[5U鹸(Z=gg8ہ@ϩuMቢ(P ŔIDg+U\,хaq LAbLS!l7p.?{Mtg!7ٜқ.2S&zج|-͆3ۃVdGFsNH#YU]v{y|CY#}}d=m2=u]rMj8,)4Mg˒sdܳD/VC۝xx:rssNahd.P5V -s^\]s:{c\}m,%)r\oS@$_Z:ibYӻ ;hqxSYzϳy6=?YD3(8$ O@JJ[E)!UӥqebKy.!U.n@ק>5!D-Nu)m$GMbg:URm,m)nJaoEe ҅P=(計HPl;6!Nӈ$`TXIAUɲFq<k2aTFߏQG $ĴBwj|}QljODF;&?`RBPVg.$(X,slw'//#: ޾|AQxz4|b^\}{ys&=my޳ӟhyRH& $}EdH!hGyʬ,[CG\KAUd|yY5qORbIX Ӊ㮥ێ|rcrX+9 Rt! \l&fUbqoy%CoQv#U^$y~}$bƖ= d>r~hq d88[oh裏oخ?KHZ#T#NgѪGA-ȴGʣPcB0tap!Z e9ERJt]]҂ jyq-]2+${Gs:QU,Px4E͐ ݄3qx_k ZH"CmsmNmGYdxOG)R'4;\-4eY3o^2(ӚO'FGFB{a FJS3fa@ÍDd?u#?w$˹^2s{`__2+嫷,71 ӟ%y  cGiJk$򳜱|!IOsko!SD ,C.hR}R7zlHHw$yg$ #ƌTeَCU<쌣_50 *(ʊ4]1m'+f|w>\]^?o8,yBNEWˀqqdA* qulޒ#B+,c1.2q.Y䙿Y"ALhLoQJU}y*K|9!B)($'2OF9agO8sFL#pxk$+zF^w]YAH(ᰶúa;3 y!U԰QU5Z%90XQLB(%sҤI Ak[8wtf$&K.lW8,mc2y1>d:v(r;ZKn?ܓ$)W|g~BrZ7DAi:w`R-UvgًO85q\_[ Nt5O~Ç Cǟ~NSq.9b,LCٌD/S(- SԨ4I Q9ۋ+QͶ,PɂiŜ+A>X~@M(U%ޖdɊ4b囦1@ㆨYLLQOr}EIOI$#3a}`5YNUQE8 I3xbډRCY1B4*V:N'؏xiUΙU Lӈ0 =Y& n҄ϮR#BY\l7\ldE/>gfs=I^WSMQwLa6_p}uq@G.&P.u,3X :Cv'Ʊ#I%BxDPHp78Y^`Gh%sfU4&i3H'ypbY-Wɩwl6K=A :d >%h.yQn7sa'P傲*2( x$ٌ"2ܶ~1ZHp0Mf1a՛O@` N;'IR &c <{ǧ'_; ԣ4j}@{9gg>U$KWHy/۫ xZJ쪪q= s?z$!J$"+"]%w9uنǧ~/H3aquyۏ>mZ JS 8K(>cV~{ @QgoX/k"n YMh뮉qN^_SV잎tHՔ? @3 !Q Αꊾw!R2twz6n .6-?tbbf{bD%}{W8q{{<쟞p2\^^Z\P@0RKf5EINt/~%"Ə\^ qzuÇw߃S]PeQ"˙/cO?f:wLS%DҔ g$ IDAT;V,Vφ_flVk1 ]8X+*E%ayK>QC ITyɲ A(P kih#x"8<.TdwBwNǻDF{$"A=9NnGB:=\B EӥiF:>pN:q7A /fhQ|(;gG+!xԎ oxyG/ O<SY./gLG06BJ1vY{3PQ-ƚȷE#in]שּׂA)R'HbOK) uhbBE':v@.ق C#_yB&"0{wGEpYJejD떱&5:'dR<=u,l/_%% i,L`"ÎzZ, >ezNUYoZoȊnzBzB !I pksn3R$%p v8RFƩcxqQQ4U (:%Wv Ox:&‘f icuEY[ڶ/X R*0MkK'<{_+|r}@rQzz~8yq4"O ƙR[%IRDC$B`Fʬ`]%h Y44y_~9]w@%"E )-Jyivx"kH&)msNr%%H Z$iA#M{[{"c&)*@$3H |OR9JI9V%{Y.E7%њ4ՌjT=әt-ӉoK p86tDx6Ն'G0HϔXJ`(X,K.+͑{8JiMYf䙦,s\2=C?@PiN=hI0LDUU1`-,%8;LHi_W{cs5G ūpV_ZR "n$g9R4.YDNY<{v*͞"xqJJϔ<)1x?vG85 }7qջI]f` Jf%t4-΅$)Ecqt:$D(,>`>n.oկ~:24',CkM]!S@ |廳Acȩy a'$0+K//X"xVYE2x޼|ͼ>y+q$K3 E^SF[˫6W< pM Mvf" soDl{4~O޾m;K4VHO#ˋ+2#M;?7TEIǔrdX͖4ɹF& ww9()خiN,K"c0*wSd[fy!#;ұe9EQќ:vE^j >`poEn ~Juk*u=R*uB]c"PSI1Q=<i_YLN`9Y^IRD*l0HH(6USے9OO{#ϖbjZdl`H3Ҵr2=!EPY^2y/itr~}k 8wBȁa<2M!|^&iZaLel@(td4]ǯQ)\c5b:KXrs?'~^%UUdb"Hʀq&?~,00= 87(.=Vp6`-.s@BHdF)&AWklXtRqmDJܟ+HO&rXofEKOJ,cG[ &TXIb?kG¡@aǩ}`Gli4PJiNVBNJp~xko :;%U] Sd0HC.P,6Kn!^"'MU"P*Ff*4#Z&(!"fl5Cdh9|4 d|ek>0f}zFH8N8;q{~HKf?17zj *jz\iwz%1'KH̋/?䣿dz~xd?FYvMsڱ{ccX!fi@<*ƨ<=tݑ'U={AG&gՖѶ-EQU!>P -N[hh'ڦ[As꾇JfH1`Xk`:vO8c6=ͳX#wOo&Cnv}C?bc !hvcuyEVV??ǯy8-]CY$Cy;Ss%ak{֛ eU3i$YF89ɲnɲ4rL&Ri贠-YV[MpbcO4=icWHb,k*AEcJ,K4Lruٳ D2 ={نYu*MhږÞ7⫯"?ǁ9 MZiz/O黎j`KR_ !Nq@+ΰy%Z(SGslӌ޾%Q۷;LΜ=Kno}"MkF ^kfh{iBjIh=7lF5@'(NPw$E]sJu^Ʌad@+#EEeάh<䩆pnid6+p8{:YnY.o9did2M)ԋi#}V+kbaN# o=cSf9!ĴƎeD-ch{D28`YCk_\$Zbm I&IphPTTd~Ssp|d4-f9NdiN3T<<> 큲Sy2/ zO~./pOu4RyFD0GuMUVTy;ʒ`=HbaVdIkj΢*i8Z@A#ތyA ǦEHA$9Ti:'" 褤(JHmʒ耱-J9J##0LG9$E@4 IFL@"Bcl bbp-ؠr]0bq8Ge c%2&㘦82/.w62V#Rzt4UkcMDy]!Vų̥93Ʀ:c^oX6zp%fdj|v%^~J=[|ύ4ӈ@ "D!=EdS2A47$bӚW/P3v[4M@jJsUh]IQ#ox|@lztHU-8:>ܟI|E5w{O'/? H 2~("o|]YDžp;@(5XkXQJ! JTc͇w=Bh6ZIɌCgQ0TV9yVr:xNjE|c,g, r ,f9y&]d*Ri19b݇/p%yj3='$x\AbAQl-*^ɒZ߼xყ*SX-Tey M3p-m\/XV9l-Dĭ^eD#Tf[{B\fB}geˊ"/)ʜn!XqdCE/OJR5:I⁅4$)ڮZHӂuɜ??M mݷqR*.^0 #lV3ՔU&G IYa2z`PKꢦinncl4<{r;8k E)X3aYSZ+r/)]K,s../E=wxQJ:'n`>1׌fr,uSEpx'l6|;޽{G xyŚqG? 4m5E' >'ъo߲Xןs{UUb:iێߡ,bzk;̊|N]4lk#xNeQ(ӌ_ ǎ4@ɂ˗i- 1gG1Ɠe9M#x!D-GHƘxD"8h8>̉ v+~5jFf}C%eJA)bg4 xc3?NQ3iW~ydɳmxa ]B`Ӝ1-[8d$iAJ3YkֱfDV/2eϠВ$;W{ ]ߠ$19+/_<cyvŸ{¹fYՑ-xbӎHifINX U3YIw$bK]7"3J EhdJQԔE wl%i3LI1/iۜo% WdIA]֜ whIuA1x([Ϯ>V;O_o sƉ"&IY% W /sFHPBS4VbG,Z(/GSL E FjA؎x +tb >s+fE5a: IDATDAZ}R tvT$Ojt44(k . Q\A $۾!^BMG)0 F(4!jRJ9iRktptÁ,WԵjLt9W_*87O7ѶKYԿ{k90PJ!u H.W*M!+Rsyf{;[v_34H"iBDΨ cutڳ\J0^m٬ 7P9o__RT+qϬҔbV/ztxH2(fć.<>ay:HZAb >g^_!E>puEϨ5:+e=&Ձa")fG9 D[v*~#oY9GyJƋW^XAhmWWs<{Di&~16(R)JMVf7Xoa+-^ZОjB>ь~=tцs~Dyړ傾)㫯fc4??'wGzsƻt=c>!l}) Agsd\Pv-RTQZƁ5 %ƴRJ-]jq="8ĉhS< )8FxG @SUK͖,G' 4jx6{{RR1-K]Ie8n4p2q4˸!bmk2`czV9" 뎮SN&e^q{{߉2>~g%$PYv)hm[dg?WK4@UE>~(8>PL iU]#>̧KRՁÁi;qϣG(٤B)5uc\0T9By,a*{;`[̊ۆLIU+$y,~3px3'O 5upsA2΢ZJd1{Ef44ڋKd>]O5Z&aoLYE^Q {I7`ǘnU2" (в>@-y|D;UG @g-2-> ) c@7:)%UHϹ<"9N)%J[,yy|:Dm|Oi0Ghu82R$yLfxwa^c>}{6Z2x7,K& O?شX+yIdJ"+ںi3X,h $!LEQѴ=Iݡ)5.oO-p{ỗ?'?Ͼǁ8PQ+ y9, |,>p5,FQ,L81:=-;V59Zkڑah룦YkLR2<~I#uΝ`)*qcnFt2қb-['%\T"Bd deAXv088?v |{ڶՙ$ǻ4S)5gaXpqqA<R 8AY՛@f ;޼ԇ I/Ogl|wdqФTU`#ۧ!S /n=6i|9c!2@k@ӵ}ᆲMOsz#0uw%e3wϗh=iicPMy`CiˣW|*ڿBLi (WL9lt}kLtdI״f3L#I+V XwRx)vU{DceyƟO;O_W\ݲ釖Ͷ(2>)I>КA'0cNt 4Eiԏ>B; a,&h1E-,`q R E^^N46ڡ'꾊[~6M8C+0SNbw8Ƙ醑zn`t5 ^T)'A dDE1gƑfT9GO(Ҍ\ YIyr9,s=c(H hGOozcT1ڎ,<%%0rQSFbm>GaOn-` nC{xW̧K&UIrΐxܲoq5| k#1UUqw{nw(w-E8I}0*$cRdZ C -N*uB(SM'L&%㞼0T7Qp~~_~:pT(*J$}_3Hp`ɤd7ǘT@'muySHU}M}ܒ+r[Og\d R-!Z n "2NA#ՎhhkʉȔYq1xOUX/8oIe4cfNy>#I+O~_3rq9aBcZ~Q'Ռb/_ZN j=z1&e_vXЉw8L'xC_316pyvVr;,aOY,pȋlcձ̦3o9;[4hD ޾fCL*udT9KQ$Y}d|.?TS|(on3 _~9EEuNJiߒ }s.Qw Y1)s|e9h 3p ;(%0zL&%wj: eI c-XL>&XNiE4Y]m( v)%Œ~h<p$MbRh:6iOÞ(@k~c`#xf^1LQB`AGT%<ΏƣԁI&*|^X cH)4%=#K'^_KT2"|T'{p8".w Aص1/\N m[v'!Qb;IDLۘ>%I<"Ɖq=";M9X X.ly&S=zŚ2/XӶ5ij16Btk-RJD5ϟjy'ҴstmY..IKOpAo,* dEES[n6-i }_g4H׎\o-|s~"3 j|bcOӘxqTF8ptDgERQL'KV33=ǁэ(>(t)i{BԊe-EK˩V?KT8dL)04.ZdN(fqM8I,0= bNBR(ӉCInpGz '-yJӘpt!O(-Z%H$Lhccݱ;-֬/pXr<64߿z^\TU5:!#dސٴx<&C MSlv yIU:(DJ4a9/'俠f7_i@l9d^$H-~##">BrV"C%@: qH'NT9 ~{ڡAu B%q HI68cIҨv'n08E|L2Mn^ҵ#E :LERR9Ţ"/R 832 :Dj,S((R;}RtU*$$RF!TK$8I׵`}SVYuA9#_QF%y:x?#>>2^1Áqi-u~<~D*7~kFN0I9L!M [Z@,(!(tJS~$8(J~Xqt=;#}D9s*V,3ߡ\\LN :w%vY k ]װ^xO&憾iqs>F~=YH!nTZ 8{"㓫s0H?l֫9*1r{@VMp^y~8s,]/p'xyq  UbiNSR8<:>+=EQୀ{wu4E8Z.գ5o_42X|Qҵ{v[>fsdII3z=2_SߑW%Yd?ZP%#]}-hCM-x4e";K8l؄UVq2$O)&$!DecPNhx&hdS{(HFh\8Bn;,(3Bh[=uZ)]`!*#*C2b.(g+&ɂ5F$8Q%n ]< e{SGxb,Rơ9n YA6q.noGGt:6{Oa\p{{ !.K H4R$łբd"|~{.3llV%IZ&9]0:3Xj2' cD,q Y"U4qH5bƆT<{o74uM ev5&'gKR9[,ӄX- tV3C UE]״]d]^i4=ŚlfGefƣf@$|yy Ȩ>H|PÞ$MBjA7L糏r|:'OloCg x7s}}֒PBK:TOR7ȣq^QBx1p霊`RX70tmdY$h[nQ5vfk-z=EȘq/a1!H&Iǎ=p8c(xqZZ?B|!v@W)YDp1T4JicLh> nBEՆBx:*CnDKG̦ iʣw7/膞ܼad>RҤ"Ks Ïc{;<ȧx ml9]7 iR=ÏS~8lJZkd:>/7|~0޼I-s^Əu#E?CZW84e}?_pooo\\Lɒ@1->b!w!94y B*^<\=!(hu_}$ 59rΛ?P7BŭTQ'FM$țdN*%1 .-V\^^ =i~?7kڶe:pEЪ\')թ'8 ]д5gy9iq 2IΗ8pO>/~͋pM7WWWL&O$z'Hj;m͡n+I-߾@g|Kq`7ײ,X-8;c%-4GZkH#n22-X F'$UQ1-gh2Q3q0cO'TUAJLplb?=wENPo^~Go4!_"3*r1Vhwg0Q(E(,ŏGWW/X.D384KRI.|4 M]my$X/,B!t6c1ཧk2%Кm9IAqְ@',a uX8*I#:,&$ھ#R GC7$ $)\\.IҀs#]MDӈ{Ѣuc=ww7t]GTt@5_'>t@G&p8|v-AU-_})'bƖ(AYFohd6pϻ߽e:ÞLd`Z~ԆׯITJYLfm)%N+z3pQ:$͆4X'3 7)#;ri&Uasb=Fo^RUqwj4MC%$rڟj#P'Fys IWKȓ) 'KLGqy9c?F&i.% .R-.# ) R='QK;wil6;9~߿j.ÌQ7#Ld%*` P*luÞf3I%& mcB}RD(^hR9{1=48Lg`:FFЏ|#f1;c>[! %)BظT$c:XJbpcR^Ĵ >Su}p&VWE I%ء/L^~}_"N06 RV3FkFˡi=V4# Ŀ׃_џcW+T*c$rɤZ>vcl<0i&)2:ćoXR"K(.plfs@`C7]J!JqcD߉:>&j#Mӥ1j NjS !CExFH7}GLQ"9;6wY!>~FzyNZ9~aTC,SSՊ<9,W2Y3L1_P$W'W,fiΓ˒iw (8_PT YPB;O`øUR3)1!= 8HMc`~?*Vp7Mf]ՒD <<{1,fkkÓO /*nCP:n__r=}ҳ?\^^q/hۿW|>X\jy` \/yF<%8CRGEn\8Y0CDVՔ$- X??&sc2!Q+$(NTJ4VXg)3Rqsnqvl1g^ѡ$ I;y8ın )v4dyiJoO>Ui^2']kۚ~dZ_lk*ޞ>$^}wu&jmcۂDB*̙fg?c7@9iFhfk_ӄz#YYrbp]32D0wUUH3tJobxh>4>F\If0`F8}國,o_ҙ` TiM>YrB Z2Mc ˲x1=&]ȓ,M)BzSR"y*#9oSV qXgP2C')ȶ$/Xt1F=)'IBUyݖn;g06>g$Z#iJx"i7ΐ)nf!a,)G&R3"%3b&ְLr Og\]34X/Kf(bFG,y, nODJYVop8qs艐iŧ_7wx8'5Dl^≗q1jV 9g}Hk=IS81,Q<~9`y%7oCdO7l|]₣x=ƁB|p7H5{c9.nǏ&c@wl6 ,yP!V f0 }b{N+rpϨD! (p4Uxlm@~0dIT>Vǎ'knrd$ŚZI*aRg,WfyG6w^{4ej_xhNCS8gZ#UES#O%~_ݫHJʲ 2p|?/xt,Ǒgn/{7L%Ʉt9l6=RvdEY{KV,]L7E*ϱ"E`Z/m)c:K@Q<o9;{5ao-{zZ?awFmԔG_rww0!ŀ ~h;j#k6 !=JDPxX5њ4E#70چ4;m1Av UJUMZ%I2p !8RDөua$U)ޏ)Ȳj:e\l{^[ %F @TT*8}m~u ֶCs<}قPG|jI[XntVRa]׀h n=5MSP%A׵;DS%Ɖyd(bzgq4m9fr$W+?}~r3T5̫9 6"ɳjQ7Q !Se\$H'fjEӧef'(Kz?"B@Hf:[nw X-ٌj6ȩcc;w%vnn!t:0Y15ʃtpz,uRf9Efw/8AH L"= EEd`ib絾XR54EP5Vh,[,y&CP0 2XG[g#bS9a Cq~P^]pMW+/jc0bOx bEQdn74-膑4M9u>GKAee :BVv %3RhZip;Nr !n{q0nlPi|Gp6YZ06]3dŽ-JHR-1n dc-Sém IS3$6=oց >9=y*~G'k|opӀCIQyx bI54.škQiзeL%40=8(˸Z,iTs=u9(˨F7Ԛz3' YFݧђ5EN)é᷿}KYѸ[ڶ'pyGx+~g}f᰿ev^tJdH]Vdi /V*Ƃq`\kPg4MeIicLEAd̪; 8x>r4tw?G|7NG3 擬de^q}}nmx/ cXV$*n\Wy>~ͻwZ:>~#+-!ӈ~=ciZƾc-2+ .0D;E "99&cM"c%MSTзH"sMk˚!-x 1R N0"rr$Ğ&#McRlgfH`}#)8'=U݄J9,IFs{w8~"?MS M8MLttB $1u1_T'|&)ˌiiշ8(3tDAZP?2ٸQvuBDd}RD.UEx^h޽{G׎$IM2Ú(Bsu7/ !0Ո VS.L 8|:c˜ pX//\|.jX;=wMR(fYJVff=&V74;Z"W;&kx-mXRi0f%|Ʈ믿f#;G# Dqtl16.X̑2M&9~9݋X]_|oZr!"~8J4M,>JOyQ1]ݎa2R%,Wii\vHY۽&NZ\Y,2R-I棥9+Lѣh{QUx#F8IR䮠-u@7ɀ<`"+'4EY0*@Q2 h~$i8Zbi4'+2d86#Yﰓ  x>?D<@qCGT?vIkI3V̛ySB8Un:g@j H,Z(XUX+Ff&ÌÞ?(g4mOgq. Avӏ_6.e?d&4Dԋ%iȤ3 3ZTHisB $SUu g9ϰNERޠ t"1g,2hQJ+ZzlCJE\^\0 o2?c?<9=M!UJ{9{&OKҷZ)-5(V@2g6ȊJADP ,^:p$iTc蚖.;KYB1=E^EDdy\)ʄJ~~?W_ټٳX_Cawd~󒯾5ozZh$P2E3)Iyxe$K | N{n^{l 6 MpTȣ{X3q:hY0]|?r5I&aͻ/1'9xӏ'gU~tbe 8E(E*iN޿{;pnIHi{ %&Z\d$IjPK()Vgy,:zTbd^hHt]JĘT1gX,׼{o c^5ZĴb\^-pviOq85LSHiOYUE"Ŝ@Yx-@U4M@w'a^Y_\O`vw86xg0fvfYf<>|ƓȲ%K5_3g<1STbk-'IAE$Z?3&p~~6\z¿W݁zrB"nfsXnln߰`|ؗ$в4c664M)yV{ R$dy}Es7h=m`!C4]@B$("D+mw[)R$uOg/n_ /ؿc>yy3s^|I^8IiRhMC|cdw3J㭠_Qd+x-ӑYR w2y \\Y^HYf:Mgn; N is`pX2/i5G9o,;Г8?pwmgV-(ҒȨӔ$Q3t;dys}eeYY0 C>: G18g-XDX#HY{ ˊPLkƞqivmW0+rW:`I,[7d\LZ ?EY"MC{4Գa\k 13xR|T06'R-HAhI7Xm6 zbŒ*X\ѵ J¼*UR3B^a;\A7yZǪ]v㣧=njWk,M)3)2,|Fwؽ4 ߒ(A 8ƾ! (z)8(:| EOslY_\)8rcPiV(nMyB+tFF}-Ά҄~Ӓ|h,iiN=z2&XIi4.DK7vdd(Jph&1Wɴ,W5nfֺ9A뚪rtͻ|~KYF&\N&,>M@I)4Hْe=O||B t-iSU Ijvd{ Vf(-X,qT 2'~NEG5/bs(D{q 3$SgB='S31"͸ZC]f {|kSlN8HͭJ&7ж^(DZ71w >;wp"\Cph"U1%~8r2$#Id A0 yZPU#۷cwct'6oqv]f-GmINÚ%Z 2f9:M.u]_!KfuNQ<1L//6B%9vj_]ˏ}aw=W<{qt 8EСΐ(hHǏ>C}tm<֍ Ӂ/_nhO;..f):\6PdIJ#I|vcTY΢RW$ p6B4gՊY=xԏ9WDpҨ @Ld%kxCD8#?9ϟ?N#߾o^`rqU0/!0yP(zsAĴ`yS Ŭvw@@\?f0=ۘnDf^fmD%!49e&i4`K$pCG[G M3Ȫ:MPY "֧)pbB#F$!; 4rgmhb%Ϙ@^O1>0N7-ߚ!ry|x`wqYxA&x%) C쩻#;kN)M=؉U=CV5b\߁pvYxW+qDJI6 kSk>~ʯ_*n9q QcjJXgyTUƃECX(lȲ?Á<-HÙ"7L)ūW2SHȳW_ q#yD*QRP$3:k:"&@gs|@y wkt]\3LL0A2}%M]pD>x~=P9yc~sb\W%͎o6oO$I&~w//7 my{~', h%K>x;v->x¬fvdQΐ2&9"D) YTƲ^X,d{; xp~>\s~ϬA|阌‘ _5B(4ŒQqEUXڞ3 b(98 :)f  K@ㅎP@kRbz~T ,4;&a\]ϩ3aHGY͙.;`} 2,~6L&^pH Z&Ӕ4fTu_~M"^I2ˬ.ЉsƾeH-IBhFgLhqu)atB%)2($aai U^312 SoY̗g3#Y:d=He=J)1n`0=UY$)F9U~4^4)x ?GX8P:Ȭj1#',P)eP9˚iN~FzbdM'wĦB4EJ3^!yIӂy'mHҜ:a Ju)29kL O ~"]<*ݎ~ϱiKV9krHxEzqw4푀c> ݹҪ)ʄih0I\L[k1t:@K.SŅ>` 3Nwl6["EVBDTthңܾ?/?ps MG`>е*D 3wX]h7!cv$dJO.mtXMR&E~3㝇hyQan4a44Q\?ǟU膞TVmǘ3Β G{$JcE3UpK QKn&55vCo|ɚ;jf'Y"ߝ%g|J?b3X~BG۲R2IL;j-#CR@lFnn5-%uYM`7_fVO_W {7fz4(%ȋxYYя~̧|£ǏYm1Ɛg99qq8 g[ OBTHc- Α/P2>P֨D):Q:0Бٽit@M4HjdVUv{=,'Ry~H0?!@K+9frl2E>eGgGa1ghY]3׼}iT(9.Azc=oWk߾xzM3#FAMسNRdiE |j6x˚nh)ʄ~w "g6y!U-˿!=u4c ,uLo~;^>>9=߿xs8wx71W(-h#>Xf$0| /JDruMqVħthbu1I^GQe'7X^'?S?yf7l} 4ZK4aaq|[\r-oz}k+\.rqt·As8h Gfg~@ f`Y-KYUg˩$ƎC^kugh U)eM%Tej9B||zMLC5?FQ9aY[l<TKm%,W9RzA0d죇Ox i1=}sm]A;b\ʚQRӜZLiߏzy'@eŗLOoVW@QM;QK 4ѷYZjpuq~CJGYJ| jٳs޼~v{LhkN$&R,A@R%Ŋh_0PiyN5pNfCݼ2Dt 3D/l2s?׬J]3;c1_D]1X?O`>3#巼7tjf>_2l6IgϹy̗__ZH$4^e;ܐnqwb!nuKSZk./E򊋋 ޾{;uv'V|<3AߍU]M%g%i Mg0߳ Yߙ$Rȸ0ڮn,.+Rq|iTvZbJh2>XB J7h/4O!BB^y'~O,.zpd>Nz _ *)#B :2FDġ<8ɓkbK)vg6[2ggy-NL3R%k(h=9? U&;Siif1έXu KsE"&Y/ZV$f9!/_d:'vDe=)ȳ_W>xә"ihq!0,&"ItɈy! pz`O7J37ƔIVbWWZr0Am/AFK[VՖVAꮮE|&/Ηps\ɌΉA!UR%mcډ2Yβ_>ĸ%Ҵy8M?H-v `=y4Ù>N 8w{-CC]GrF3Bŗ7߲>Ss{<'=>AjiN atJTݿ@u^p~v~"F9;ҷX+ fc!! 6)#@ԚiC@"EzIqCquM]w( !t-Z lׁ٤n=ل(eR DP?yS?sss7o/ɓ+;޼AjZMq8d xjg-`OKtQ28f@7Sl m۲XYxvp\1O*9?:>m;aT )$pDD7tFD ?XѹI=s1w+ .ues8쀎CIrEHKlH`<򴡫D#e2 łTH=0T{x90 mt->/\s~`.U'`'W\]]q}uMܾ/_; !7%t #{KE*T'ūCX-/8JFkk-1Zah'> 4lAY FkBt%&_P q;=un&:7[XTeӔR.m˪4 Á(< cOUx7qǕLɥ^FQ ”t>"E(C䗿6``߲\-Cc!i"J˚r6tmbt`d+A]f,l^qdžCF֚zM^کˀͦAud,Z ~ӟ^ל=mozf՜ 5c-}P٢}PJ JYc aV&ۡOU[ڡAk͇PJ`LNy0LTU'Bϒp%FLLܢA(:{0=3dp-cX.yw yz9=6Dƈ()˒Cېg٬º2 .6<"3G^X2KmF`|`ufbl"%oQ*|$_H p{bj!6Aw<~|ɫ7߱Ӗ+拌=Pᣣ%S L0Zs%E2U<{wG״tMlӻ݆QO&Luj 1D^i"1`G.fk@ߣuZ(vL<,llR'ffDL ʀ*Y/bxbL>H2!aRL6 }LH&;'2./P4G~Lo3`w[̪- N]3h ~.3&5 ?/mB`{8 A 0{3;(LPW;6)Q+dlOo@S_rL}UR}? >b[Y^21 >m#ybd eQI7%`vluA;SU}iJ bLV!E3E@MSM6CD,! rN lL\ \r}9>(Q7YrRXﹿC,˾?d_3ݣ&zQ+i#%Pɮôe|LDž GF2\< bL/8>KLJfѢ3@^̐ 9ggKkG9YMo5֍Á#e1GKjNn,HQ+wCʊ,ۆ!CEy;% v5WeƫW'/$,KǺ-=ES&!`,期I\9omE$ā(2K<͛7{?}OݷLn,y9 vFQWYO4A'iIH̊qfd[RScGV( ff D* xى>DH,+t@liۑaˋ<%F:!ЦinsPsD"J$v'd-X'V{Ybdz'ϰStDshzOVYͫٵZő"R+LnTQ@8SW+AVi~f CjΞ}lbH*ߴSK90BY{:ɠ'b0%6[iA2H)MdFICߏ &ۓ<#EgRk|NG'8k'O9Trubj~F|/x)ힻ[ni4Pe9ge2 'O8!{6wo-;K&\~_ta2#ݮKuCB8Mxgt2՛eEA%1'd*YPF3MCO8EÆsVG0tח]Fuw=E͛o_1?Cf,Vgᛗܼxt]Z%h>0,eY'VCӰlйf]q}6u]Ҷ _=UyQHqNoes;)/#VnhRb-X yH53!\__0K,S6eU{X,pw>\_]%0Dxn8.JUUaEF;|S9N-Mc[H{?Ti >?y~'p8ȋ"}w~yb~N !c9;6{=lbh;yEn$[6T,Lׯ_4M˯hcoAM@RhҢRʴ9gX9ѣaHXP.-U<0ڤ< H8mIx2 HMUȖL>cr%R +gehw,' `f+]94Gv?[>lt'8ۓgM%|wl:MUx[)u$%8?rhvdRe y %r#RIŽ*8?d?XcL0xs>Mi#r\>ہLڶcq`n]9t|/~cO2iw7~0M̪oۿ-2Wi 8ISgHmNwpJ4=l8O48y ɲ#V3,&ըүL 3#o6M0Ų " in"z'GIQ, Bcn{ 23IK-NKzi=QHnH@\crM c Ƥiir(' Zsy@߾xC3A~tUHF (i4|$ϖ.2鮵>p_}8~ 1 \zvGhP2 3>dacsf[dHe:1,ۿWSBf (+ :⧖”i+Ҍڠۗw0SDebzz⊇2=f @nrB8iK1T2HDd>G. }Q* rMm>2IrYvA@?Ng}9vhK1)OU@jA^(ޏ0u A)ڞ'O߃b or|׬Kom/W?1y?c/ #>h_ׯ%nHe& 26-q||Q2}s<{~=wtC'A"0X'1zJ]AT!AT\=Z[ˬyguW_ͿWEeTrAQUc2Di95n0zOY.nv"#m(+91H&!#0c zP@qrs%uAFRd:H:qWd~"d#QE<ֻ86M 暺1&EjE0/)9Es9_˜my-RE KL}cϗ8vhF (QY?}4l)D9լN! "B,Ïa\ < #\>+v$7&)$ZͶ9$w5+釞8Z9ْm9l[ԝ"HY3Ll!~d*щI^-&S9Ln1#R+Ĕ^΂+=Z*.RMSёB "Lxhɍ 3YԚKA-!`?B9+X?J71'0npuFd]_OClV qB'ON_FA%ی1RG1Y: ݄bEU( ,Ws{~󻿥鏠Uz$B uw^sy h-Fi~xxH͇lvZǬίX_H֧WoWLD>=}8 S;S4\$eU2Cɤyҙ)X/X_>idk^!L1' %0w#Ol;_ zl# ./?Lt)Viqsx"&1 sYf\9;5,f׏ZtmK:c1^oi"1j m`:[S4U(I:Yfu 1p@HKj8 A2/H)9 H-/l6X?erjݻw|Q4-E!l0 C'2C:,VyE3lyH Ɏ?cZPWQUW_aZ1_Q&k84>d`T?}|N 0~ )rʢ`|9?;fk|rD("nx5g%.xLUqM rY5Ǩ9 mrT(@kLE'NBN ʻ!qLN^8dɃ:hY<{L3v0̳4p80TD!ekљ=SWùH5Чž~H~xi*PUAypKYV0 GL]dƠuvb2ys{MUԥ 6{6 !zEb&:7] OudT)J/ ɬc",jl蹽ߡLO1] USYYzOp Cq! Cs C;"d0|$NK&PRD!$ΘD ?ޙL&Ájf>[T=9.RTsd,Vg;f ["۔~3tf@Z.Wg96m̉z* IDATk[bd&bL,}?cGu$0> ֺIe9O'M dQҜ>,PLSze0H`gg+%}/8$2hҠ$({ڰ%9պ8) .:;gE2 ڞqN~022}sw ΙlO) p xoQMpD !>ZVg5>|͞٬J$J),"OO,܀)r拊{=E]v[8uh-/Q%Qi^D'O~|ƎM5J;+xkN0MG8J咪Y86;gcáj⊡Nt`r6'Z)kGt)J=EnƎawGgE1: K5ϟ?ǻ}rUrᆮxחr͎~(3)eJ/,gK.ĠpV*q#vLm}Ryl=~ǧOpmyE?I+׌}ǫ,5& LȈ]J([~b Vvy&J/QxJ H%ZH#ٟyZ$ЊhĶx7_Tѡ5u2II~%lI=_X_c !4~sCקϻ4E1zOc,b7rHjwȱmawLD6 OӌJ 4:BpDM 8h)ggeah n hEOKȤ#]7x:G 1Q{/g|YuQ!$3yszicBBwLY)#G,4R9GE BFeV#e4NpJ'gElR-|C*S(B"HgGNj~3*_opGWSt^k9"zV5on!g9^Q"E6MKErEU{HV%e96j:QW%}{`8uQ# )"ޟlo' MoPҦwn d*)axS>J(^|vp80 =?/> ybq$ ~ t* 4 #SW NXX-ΩAN,"|MYp֡OX=yA0{]ϫoۼ/5|ɩW?1b9gzv0d`a\]]m6, ֦'&d`V,95y-Zgy2'dw8ѵCs)#Y'>P) [8KoZxxآb<'F> F~7-ZKE#Bݛwݑ|'O=1 n" ԑaEBJY?11@%5U?@ 3jn{SǛ7o(Ϩg:OUt0`m$SY}brJd!uLThbc1YF }|ݯ86z";-YT2; IvŮQ$R?y2"YV 2 Cd͌Q7ߢ꜇_LSo>uNT,ٝd)%(ꊺSdRhiiS%%ǎr6;O 738`J ynȥb1eN6-{L~kLiHI(:nnfeq2؞@e%Ö)Ɍ!]/:ÊJ<'2>|b7s8lzD ӐnO]EE44yn2ݹ7lI}穦mp84fD6EY.*%n)8+֫!8ڶY1{IC,+~{ylV%+ 3no,LU{*AוN~pXتBD?4Ah@am'C(R)E@{fDkt͑iBUvBY؅=#)B1xQ8G,=لIނyBlGWɳg Ce&HR$Ώ鳟iO4ѳX(V% Sd3ʲ&;I|P62Zˣk%]7Z'il7GzEY-8t-E%?7SO?59?'o9i*UD ¢3k[i|HLq7,1))LW uipOy=')Ut, '0TN((N'Y,874l%m~cɲ,e9͋N{X$ yO'r>Suں;V&ANL Z眭/z:mt֫ϟ?[_׿d+p~BH?t i I?FIDOhqmL͋. 4ժD~qLوfyrZZ]b2t[wp{C L QzÍ#Aaju99'gk6a}<dyeY ną)?4ł<˹yS~'>ןqqTlG3|׬W6|OytgCr'EjyT9d(gdyF?t Lhz2ʪD+Mi1i}SWLQk "*DT* }BɎL9a{F?0 !f.D"ɓъ(uj5ChGD H= HeF\8<; O"3t=M;! 9ƥCdVV<{ˋsl7 4Y>P%?W9. z>ܲl1T_O_3o!i9R%rl9yQ/gO?a\^,5ZJƾesGhJ g'͞wIإw=Zg46Ee,KbVSpg m8Qr@U|Jg vacqw?m*j>%?Vd<UU&gRhW( mG`Ǝn#쒼49u⒋gF=wlw|7m ȟelndyng'2#4}N#v) c$EZGPW9vy>}dqJKeK 횪Rm8t݀iÄRO>y0L@p"ԥ"zO]HVnϟt~ \?~~vC^j7ʼad{q`S@djV&4ph1Ozs9M Lt<~rMӥg8ԁ9=]ۧGi@9{._yx`Z..S9Gg]KӶ|?춼y{ûwwX /8?B=[agݱ. "ǎ7y$X..1YJG݇-Mr|(+:=Ou9e4&,W+YaGaDj3]$Ӏjtq`t-tOƑ7o ݑ&%Da-eΊRe.%EY%e]cz LɎֲ"K󚳳3ʲ(sL( Nu"+Y̗d&w*_lX;q{2PR%]!}'Vk-Чv39)i1_w;J9"gڡ~@YUeE^" x4d89-d, E劲.DT7<2uSY'qL0]qsMӄɴTp"ڞWB‰5GjҚY/VvGvCZs4LC:?EZ1\LY5CM!z<2_jRRV$RKdټdFGs]`ZZ4>-hDk8 '+$&D#HZ)h,y4X #Rf0sb qB-ȀP% o. f5J4mOpE`Γ<.@)Dd` D\/xx8"F*˚5.(!0L^\lyw>0ق~:iJ'8ߛD_o1%b~-M 8RvB"CpX7).JhUm(reYg&iI&SĘ^fB1ZhA|?{]۲nbk}o`C̔ n&2 ?M?nܲ$ NY)2IT"[jW.f\qpð"6.{Z{1((0w$ y X&B_SK4MYn 2:cOt툊с㣔ZL0*t e:g wa❣mk o~W_id-EMJTP!?SU06k=N*@F)(1/9ۤ~8oRҏvoZbʲ U0tPihEKASd|IxV+dF똏?;hpYXF~ƹ @<㘾;-(fl$LY|{ȯ?/#{޾я?(2[v"xzdߓg< yzFpH8TOsK7Ԍ(V]EUKWaDȓJQe9yqf}?p!5Ih搰B h[܌2 9Džwa6 Ȧܐ CGuj0[@B]GȘfC)vǞhN#]"G! )#)JBkgtBa\EGl" og,??g?O{@*@c~Az4 ] (Pރq` fRFih~RX .QxW4͘fAi6.bEx|||3=056/BuX+VMYZ|?sX6 (i-Y֜o7<>>P5m-U%!BɐlH=}8,}@X%˛7oX.XYw)חt}oL)d)~3no_##3OG302 U{YO猓YzEG1Y%&R\ˤ:$Lťm{6넋N` Dځ{U1fh8?c0]/!eb1#xiC}0Ŝay\jZ$c vc-vcH&!0$C8jDqx5A;g{~q~ E8Yf(YAko}?vc辳(Uo)^AHf&˖T|>LOeYy1!% >,a'gwxbTcRHii!Gk7e=`i@aq~QpL9U'pXI6 u[sX1wce#r7;rss$߾g8h+bD 4]˿'Nq3zQ1)l{ ˃[,LƓDg7KPu5]/֬PvYA$y)J yTta68=gHxڙ~ Ct"SHQ?ۄJh0sf̸=O;NOI8L3h}˫+N՞c]Cm8(q0HYYAVH/CH`LaG9(P $ih_rBAˠ}qu33P1Ikdl~ U8zvӲ}G^3wwXYV0y`FWW_?#Rc1=ajqy1bŦ<vXӁyjelbѡqf-0⬦-H55X8ֆ,K86{7)WR~tjH/;4 8!+3AKh'20$sU4a<fh'bry~E޾}7kp;r,}8,kɢ<%D81#_$0s<i;p!$`+NC:)$Nkv8 J "g?01#6׿5na .g}O#'YWO %R,iRvÀ4]s"IDD),' qKEqAm3?4NYYX##P~#vZQlθzjiN5EJcg%Nx]K4XG%mP<>4¢0iOBb]`bHfg""f-;K>(#RS51v=[XY ˪2O=Ws-/`&dL Z</CuCoa4VĚdHӈ )ٌK1H,R^Y%ƒ1#h(Q!7oIkkSu4MEVqѶ#d)-;!Nⸯ'M I!p( ~2TUBMw?²68&1ONJ`BǹIr8ܒwtCq`:";MfC<=Ibw7}.C#  vxcI4“<}~`(Vh9;b?? oI҈HKihtE*>$TUϹ鉶u06 A$R]x0qL,3./W_gAYя5uS%2"e&|DaXo22eRcӨ(˱.`2u^rgn./Y9V%쟞p8ୣXzy<5ur59p~p!RП`:4]*fg,ohɒMcx6g B+꺡|5.yK{4 h~^K4s;: Mc f5"i^xA7xfT#ŊGWNJzTRYtj)9U<<<k&;^|djiB`d1&PۥTK\HL$(q0'QjX(ðyw%`6` lHo Is4ME4.uCILżxvꌶ1|] [XǞqty(9;~o?0o1q.T[Hzw5|ͩӌsakN9|1N=z 37 d)r$|aFvO;HP%fEHzi< XWg)3>#/o܂طsO؅C~W04c~lLA1HU"eyI?:vY ) a5RcĻ N4i.ԫʲ|h2R1X ƒ 6y~ v,X/^t5uJ75 3o.8۬P۬΀3c{u/TYB݃]P@#8L$i~I+fko߾' bZm~~Yn]})f~Rhm[ :D7mC$X8{8TD`ԕV [VےjXVaN^(,D/Vۘ|oY!9*\߆~\x8f)PYuQQ!!8[*NӔȒCi{Gsj(wqE c"$LtQBݴ_q~ꍧ6xﹽ*oNJөFp>ԕ˻\g(Pd,]׆]K (҈j #4eI43ww'?17g8;q:> dd9},b-{XLGb MRR#)_k6?j@0̶Y zsnE'TH> 0& Ädž4M)7פqNq1Hg̮#B3*U_DHZPd%թ^_%?c kiZ0,4N:I׎ dLǬ޼":&kIq`%ҧq{}ɪ,{Nu,B]3JyԒ=PF 0F;~ІlW(1-vK$\]WiGNzLsC&$F80dINEMyѧEAt'K:39\^tʦĚ? $Ӊلm%c#&@75d7X.OR :8ųҴkz9$Βls<5TvǞo< 1=/=C xwsH36$\ yBGE3N ]WE^Ĕ߼yͳzY`ⰿS ?ӡS̤Ey`ձT Un|/oy9Zk:}X6C4#"+ | = ??'RS'"31ϟu-oxm+\Gi4̣FK ^`觥byͼTG IӘ9/5۷6Xsq~-tijH痿㾧,f>ǝ Ð4JtGlf3Ygu$iŕ¸Pl][)$"l/^>#R~W VhՆٌTU_cEKx+(JMU~'`GxcݻuJocsbc8bV.8ٓ(|Uߢ)蛑džiiۥ>K4e{7^kcb_sxdh2v6ˈ4v X)C`l6,ihpޠ!U1LXadtIlg б.J42t 0uuKǠFC=g+s~qG}^#UNYmt9ŊoIGhƑq2 *'K  /i r>św_ğ>˳j,4 YeB-Y@^C/R21}Cl/C=;Ñ)Qx?0-`0)a/oQD&\":!>Z_!fZ13b-cBn`b+bTRK-1l1"呋2\^]Z^`XBԊ$K;~9((5$8ˇ[dFDBb݄ 3 MS!X<W4 q$ZR"/Á>US08E Yg%pDJs=#N4q!Iûn6>{c۷_ czY傤,ΐ8@۝OE J^(xg?0#$f()ӂMqog>堤uu)3LC3 m8dxܲ\.|NB2qpg/y K9u?"E6-%B0}7M IDAT8dEA7 clqn?Qʫ7ށN=.˂،5eT +YGG%H \[?pui2<m~}Y !m#IgG +ˈ\c+h03 ;ne\h娪yv ؅!G 4 8r}{$yAuLf퇰(Zf75~aBu[T$hډ@j63 coqP(Sf9B:f e. Fnm[iXjsF5C$B43i0 t@s<%ɷxix:vġc;Hb4ဉara{s~0Z$ poB, /'J^ߝHL]qXԿFJE̻;9|| EZw$QLeet}R"-Is+qZ,*($l/(6k8x<YT1p:6elVXր!m݌/əny'BVJ乸~LEqd ӴTP R(vXx:ƞ4+@H8vH>ᏸ^;?oG֛ۛ"ŌRUy1 .0VHd k1X"$w]H;fJLc 'IQE777Oy5{VCrqaPBlsN$<==ҜE"T'nb I\**޾;gW\_}1fn“1)|ঁ_>$% +!48F1Գ{xBke& N00Njcw&VKZ+~H>"N4B Xnq2iPבd-HBΡc8vys<9HtT[CVgt9/$}'>I3X*Q$R d v$J4rArXC$ /^Π  kC X6(hD:a<7ٱ,KN{[=&3UJ!# HBsB  (,RefDVfdޙ`;T>tw3߷ց u }NYE5y+6C4 F7"t,s5 `{sODx;Y5FwuDfNLM)u5K0~b0NagS;=pyq[&h~ ۇSDj}%}Ï+EALFM6Հ "*f랜-ǑLLeYҴ62i(C Aa2w,3])O?nA&|) J`щdnM=0gN54톮#5Z+oҴGJ XQbYpz|,͈]G*pVxz?ñaۧCTuEx|D IxN~m7IE)h^,gszlDm{u`J g;|Lp!E{\@qbr9D]'SRJD-r)ޫd1xM@bzSfk#YQF(/^~ւ_|˛7JP]?RMi1M-=(3bN3nT1z&| V SՊgy1\tB\t]]\dh,_SsD*~MyW9^]ͰS:}7Ms褕$bJgAE3/k@2EJRAD֊ik=3nxg#Ĥm H i@K;r-Ӂ~ L} N5LV7 #Je-kqrYϿ *0hi`^H<^ tj<M(3JEn4Zg{_2MLMQO" 5O_s)(slNG?u6 3Ek-rVBdh4 <+"@ߥtpvp$C i4;]tjBX,8`Gb;qrlO J"nds}u#cWϟ &pnȴ wW=7욖O?>!=ctnPil1+!|M)s"hv CA^yֽ*.3Ԙ}`sܦSYHLCh2by#}D4DbΧk+Yb;#6%tT m$'16,U5Tby{ &{8,>tcӅ\&l=wPu\ߤs! (UGJSk_6TATHiB)8hyxuo~8RM6ir#]j8MireNۼiMuHpӾ%:c^IM-a}x|wj(d Z'F`0)gՌ ]_01LcFQdEUa CT{n%#_$vgeXeh>CmeIYR6H~w/f47m0L灇G!Y ޼|Û32/pԜX-^]ӝwLHTy&kقۦ:?> s"IY5Yh [/΂30&񧪲8 uwnɚ3=|Gي~XKMRRϮZT]2&d1tfr+7=%O>f:87䓏xC6ă%0Z~$tת*k/^}_Ry1H 0C.)5"QiV a m; ui iR\ ?1M!՞Dr+х!3rFe*=u](|qs^?޳;ؾړ„H}*9mJ,$^$D$4XEd~ E PH&p@3c"yQXC{#"=VkSzAd0tCOa*={rdNtxÑK>yQ%H\S$ R( ^\.SoL[8X+! `g$Si+} D<{ޡY.JCUV,sn p4:U2O[ 1RK4QW3N iSR:??+X/97<_ry}Aswm"!zǷH U0"rq`<2':ב)F@l7&&S4bGRȷ~G(8^Rf% ,ٳg쳟4 1 8lGlӰm\>2黖orcJs#$E=,t'>5޾oko+ax'`Tʧf &qdJѳs XwNU 9~I{n޲^Q1~{Ŝ =wKld~;H'8092=g/(]Kbq|\2(Y.h'Z򼄐,Oc hƁY9c6x /47w7%љbhyny)|q2pFsr((.}֛a5_[ǔF\Y4,}2Sq(qnM=d@!>\礘8.ĻrF*0YɓR<(= HKV4SOWkb,8BA>HYJ3a$,J)#7g$9!%(Rw}VCL 3'zu*xw̲;2%=y}GTJb>cE5#/2j nܿݡk3<9 1%|ZdKwTٹbqr{QY k-.pN+b/O DirHvtsxHL=U !&+..Mϲ4ѿSܰ;s0Nm6aXu#6X61E -m{2Ѩ.wṭeJɨ lS?$z4btm*AVK[Zp:\^1䄏jVnX.r3vཧϨ. >z!n5BB7" ` 4i+Z(Qq< ѳ7A(dbT*qbF2S`FllDs ]E2) 8!#%Y0/qqxdɓ5Ğ,לڷm/^ݽ&)|E|Y>#xRv#8"A4~dESz 1oҴ"t)pDQTLc<0hkBJ^>o1%DLP2#D8ɀ#mt]&MsmPgY#*t';1$x&+hxf4S*j~$)yآMq4{njuf2(HDzMeūD!/>壏>`{zt:uF@ DDTy{z>&aW/H-T5P) ` J5-Fk4/⇇Ws=v]P',.ZOIvdQF;hR Nd\U|Jd",RfaxB괽TsswE-8eyL{>b`$.S^,HjHU$g&m | h=@KCf+ D t;G] a@K(|j*2N=/ܒ{qq;CO%χ Lߤp춏l C1>+3KYLZweqH#в@˜W_ Bz|w_sJ];<0mlv=CQeee0YoϕKzdBntmԢdrzH 1G](f "a0 Eg. }턈Ӿ#bͧ {%2G=g9§*9z.Dv3w@Cc4eU&%2?n;h) >%oǘbaVxÉdy`633)AU%R\y^‡ I(bi#?>2[rYexuN)լ3CQTi~d# ʀ_Inռcr3?H Kbw8%XX&Ki7C..P)cGdfKdRSi,&ה&I5*{WEh1Eu%{VջJdr *{O߷15"<fMIB$3zQ#UtvpNue\ c?S@?t ubؑ9 "]'c2YԤ"/ Th\h2Y-JnPB o^ %uY0։5%]"t́*Rֳ&KiDD=!@|JUI*"3 qzERgS"8QsE=iOe#@ Ҥ{GB*BGTЎoX_| \hPb1Qk=z'|o޾>&4)gCdp+)<֋t^+d1cې:>cU'fx~xgG<+06pMJxdGp !"c=HPթآUF])~,~Bat =`yqH,;_$DBFw,3Y]GBG'j}09qdGFk6NwirZlFY>a3P8)K4:^xw?O1SySDf #m1H!gsSPي~OYzX.6%{ŌXȔfӍ=WfR]cз{Ooc\{\]pmOϹwOɦ9 ,[!0/h/5> s./c#9:Cp8v|[0 U'o9g5>{ǛWIg&8Z n͓'O8-łț7wd䣏?>Kɲ=y>0zrm,0MuI "7%Όsm%RȘϖ՜(210%#o<[m{v{G{4_ Cm7[R- EyN*a:aMbI^6[CoR,$0 jjlH۱!Pdys]G.(nftljY]Rhvdl;T٤򜡟)41NhcHqr@goDJAKnnn(o.]Iix*%Z /y5Jf#ҟ+*.$a%MwgpkVUnw.OZ[A2NT\__8%0r #e3zU`Og1"yN@^Äm|\^\ӜNt#MCYZ?9v x|Fȸ|{ώ6{p?%×DC{.׷=jdNr'8v0T<=iBJ@ ΥZa`G^me燗9 #l Ñ/_=|~G}I<ܾ0`>Woypr7?O_1/1e[ƾ\;O&xoUURk1HUU P0 1-}Ou{D95#(-.X23R˅# L3]?tD D&T=*Aj:3KCOUǦ= $~QJ) QNc q4M ʀXjf EnζN&)|>Ai<SVful'Cq1b':k >LMHat\.ѧ.. ."D2m5LC߰X |A'of kW`9::#Ӣ. {Ƿ뒢4 = 3cB 8G~,KN!ٌ,))u%E釖ZI(}da4(ee__=lq#N!7zDd4=1bL݅'&Hq)f-RO sVK)Q8~1&Ǟx=撢xЌ Þ)b?veriDU4lq%5U^~iH=I Z d> ӆS'1qQ'H#]͞^9L.{́#{L"Lp}}6ƁKd 0HժhqD|QяGqvhS17ǩ=rysIq7Z|A8ܧT88EEAvS %26owt]j=-i5!ĩ}`OrF e&!^شԕ缹?Paj^9{55 2h A`}q͓+Nt]Ǿ9^0 %w&?g,1h}_AGl_v<3G|DoJi!u6")}@h!i>agHFT2<'juqSO(-@^ʓ'dC d@Jc(-R\;dCꛉ<Y,eMUDL4KJ6Q^UN]\,.]*Bc4G yd5qu?nSDd>ڿACw`EI2tQI Y;?T$+"ShO^ezƥ͕Ya`Y( c7C&ŢVȨddh'tcCUe30+1O";:4;kyJ|ih!54`Ȭׯ955YU L4 BN#v> F9OV mq#DՊ>|bb8xAu,+<ۛ'(ξ9Lvb<>nzKQT\PffDDrB)ZIB3`2Cg΁s'eI۶}ܲ`Z˯95;~W`]{vCn>$R 8QB#LTά4Tv!=mo`r|2I*c8./-o_5!SxOpBdxqv,UVӵ ݐQHa@gJeUpn!tI7Y.V{۔*R"Rd&I|N۞0gЏ5 mIAz\۠ 3LZ2e$ԣ }סgX;qbTANb?‡|0t|o^//-4m(K!0Cr<\/Ys=>Lg#F͇}‹efuf ͗kc:_,_ MwLKEB1׼y}p[g|K8m!Loz{w7,/.clO=yn8{Pٟ'ȩwGNDYW 3: aЩ ӆռftɶ1MGBL|Gf%9FUt}AHG?5 Äxodq\$r홬c}yM{'f3O=[ ]J?!ebkiTE;ϩ8}p1&]|$L/hf81eL#([GFgSzgFǏ|`c>eo7,sLy[;WL>}2 1Zv}GT:='Ǔg퉇H(Kޟ7 ")k2Z.Y_, q<>2MoiO ;zuyގx* hy@b麁(K IfӬʨu !p:,)C<7S'Oyxai;.V#:٣o#nh_a[I 槈֎ ҭ~TH% aZK]f(Yx8.'<$6Ub{deEWt%%lz&Vy4RePWĩiϞ\Rd@s.?e{~g=sNĉ̬ʪmnm1b#&L @! v"A sAQĘ^1fKQhy rҴ%n2|k*ScݑƏh~vRtj8_w`|0UBKDUBQEAmV_# 0FPVP5>Jt"͗YC<_|+.\Z-x{T-pv~o>"d8EVȷ~͋/XCtUU- KYaŮ"2O{*Pr`u!!9'x}{ "&%Bf垏jI""d]'ßx=1$bNnReъD)^RJK-"wO8lwoFpܣNqCiy+Z6x3:٘BgƓOϓ !S%Ua@޾ԆBiTQk]tIm }\YA)ɵ"->jMg301x.) (\c67l;I1SO1Њ2c*Dߗ M#]03^0 <|*pRֆ _? Ŕy3"87S79OcVT$"M,0c8EHhd3[M YhkHl?!I}iW:+ke)9L{Y$p(dK$1 YݘhѦEw I`5 3?}MY䃧|ɏH1e?l=>Goo n:~McawĔ5ކg?Վ n' 78[TǼ@Jx{$ve>a}8+G:N8E\\MU۱ڈLij!r$0$# tR @ YH db}Ĵ^>%M?nKa28+.//yE~W߶slYy]G.1Us3QJs8nX,)+I5!8~1M^2"A<{I'3? s^/o}?D W1ں"p8OXIJW_ԋ%U!_x˩W V-J ,%<,Wg\_s\Zo?v?ŗ{WWWz?pqvg? }ۻ{풟/IBO>eX0G!DR yuD qiPT 5C@Yi(~ɺ3ډeU60H٬8&\ y' 6uձ\=7^QyX474eSzr,:>'T&0MR$}QVkLrl|&D3;Ԃ)+I&eNԛBS)<*`>r.\ʃOQJg8/?XR;'K+{ɓ'Gnnn!ZsuuN|uds9ٱSZ#f+BA/.Zq؏L10Oit5]לyR+2^隚j։gֹi:TYl0EdSy)N $˳;"թReUp<AD9˛7)˒jIx5{A3{LIژQlـP49$ GG3u}d0P5W6Ҵ&|@I R-!yfX^n(W)Gc?o_p IBdҙSukp=1 }28:l:cJ )QZ0 ȣbԒGWU+LLӘUMy@\ÂAǔWHA)xP9i`CfΞ[TݲL!!HC#Q:ᵀ0&`9XgTM<ϔMS'۩,Jad37ȹ|?Q IDATܼPmRf|JԦ@z#?hNM!QhBo|i:$ D'!0'q?pv>U^Hs|eNdSb")ač#+W5c?2#)i˂<3L,ۻ׌sUAU0T霣 1# k +dH M*p8\{̓gG7߲Z,jCE"#˳{t8;@XȔ7o^p7\Ci$h L<\]?Rr?}\swaL 4/_yݵ\Y/yѽ'_yX(Bph\rnc庺lAIB[qOQE޴Q-!j!2&+XwGjͲ"Ć$g"U۠fvZv{-˩PҠDb4J4kyiwU%~XпbhP[eYct6@( xbtJhqOIPW-EQ!+4Z~_!rD, "JUv[,s "ƀ#)Kd@] T%1xbH1WU,VZ^~g^49t6# =[9.RȬ(MpFɒieĦ e!ٜ  bcv=x])=g}wxH[EKq<>Ahab&b.Y.(F'Պ8?l9{R*MENGBc@$b=&!E%2E8 l8AXЪi3a x6?DIOÑ3.x9ht87#bjZbv(EֳǑi>2tI`W9>2G,)k>ё/?7"8>g{ag/_ j}?Lnbh}KnZC-(wH8n8op@5tgwv%JBcJi82LGFoI@ yx@Hta8m3sE8{?)8Ls}BV+޽)F;œ꽙sSrgmklo{HuEreІiM~KU1#*:2uCJS LX; 7-E8R,΅-=16o)E,LZk6hL)yP21 uP 7-!BE0uVlyx꺣Ga:c2LHS$n:fqسQ(Ԋsi.Mf G:'RuǞ?4Fs|f? ?!byբ k-qJَWӝ + r]P5<=XdqKe]Z13QB) M ïOX=Y#v4xͻx#woAJ~D CU灔w9f)&%+D29Q5RW[[ ˪4<)x}li[t!g%=TU(| i"R3:HBfff|p4]P3 :20M@SPr44=bL'8pyyɾy q<2nd״iJny8y{YҮqOQ4mh{}fŢaɷϿ_sw.8euJN$JgHTdP R"Dv *ݱltudN'gψTEx)uAlI@szi2cǓ(@q)?ƙ"!$*chek0MLszz2Lr 'Σ=Q$LQœ#if2b8Iж5O읣2%3޾}˛7o(K΁MpF †|wn&HTi!JD 40eS h9?bu-3G^y#uY,kVG !(>ɇxo>cy_PȂ;[OJg;$"<>wȳ"U^Vo_-6W Gv*C֌p3|<:ںg;dQBgOـ.#ݪ[ g5G;޾Oų%_׹Z_g_?SVkBfm6u;T!hU`9=(ib\Y՝bf `"1aHI)FI|y\rA%e`$͓'TI!.WgK>>^|ղ!gg,uc!W#@9NnnMj쌺}{K5[mg8(`L;So93%e!9ssulR"]X4< %݋gxiL4ԩa%~u+i"*"dd޼R_4uI[74Mӻ"W&Ɯ N-noqL1IB"H8ncϧ\zMEAUU6ɶ?\VOmWcǁG޽yMF]KQʺb(woe*EɱESs:;-1W?Sdr=EU5HS4Mׯ=OʋB8h&vUCeUȉ?)DL`L)b<{v<^ѵ-~`|=AHyylDtJg8[i:c% l7o臞uZgv;Y0{JcE0rNlu"J-"^a?|){v0#kczRg(2fK8HEtߗEQࣃpb|ȍ)ROt$#jα;9NZM|=N`iڂ,E=EKղCϿ?(p>Q896[C>z|y=]JPƈa'xԑ++£(!R9 6KJ.r<91 FSLj`#RŅHoa9_^Zc,cӶ5tYS$Γ))@JTe@)4Ɣ^9KcFJ]Ӕ 4>C! ɜ&Z!Eo3P*17_XS Hܳ(FM3mɰPBS|c?_1[ib%}S[%i)M9am"FEQds318*!0L 1RW9-h;d=|`v(%IȽҐds"KiUKQ/:<[JX;A-08V qgwxC[(epSu"ΠERRR [ʦf0ୠԏ ҂Uwɓ?c8ܼ!I8_ĕQEdjYaKH6Gw1`:CuӱGɼh&8r.C6$H^$ CS/䣏yp}g?0Q%Ix|H[t"Ŭ>d@%8lk=)$i!xp4W}c <ҞE4/ ){jF%+Ei!?/9eQN]@c$U#ۼdwO+h3l 1oiuM.XIru%)33D!Z8~ħLmʜȘ޿|p*E3=2I 7LZcGvw8FU|)$HRefH ÀHk=F))#i[r:G ӄugN .HQ#^}K@U5R@3urq&Ed=R3Tc6Nh&9ShN&úQQI h>q\-(ˊ8ssf|PW:W[5Wl6^xR1/H}J@Pך4Mu ]U&Wmg>"vTu^|E= cmm0HUjtF#[^~)eX4~sK V{bRLTU5=oCe@0rORc6'>Oŗ|C>%Q1 =j6/^݁kLmH"Ň6fhnp6թzxkIqDePe m(ʜ(7=7{I:be>S0ZUyx4UhΖKad$]]Џ1&_g}8cS%>8;;cs6MD"'UDBJlbe]°=$Y%ZhU ˅Ai$9[//ϳ{MEQl\C~Oe40,LCQTvoC)5pQȩ:%OPɡdVgJc4]۬7U5O?}J\]j\5ł^< 4{R d+1!%΍ DJoWWWT0, .߽mA` ub)#!9tO&' {Mf7yY⤭O0^_0{?y($0B\ (:)l6E}p nLfTnih"0$T RV{p`VHU!Ӏ.M!2HYl(9|<11жuy5[wXףԒ0VɱZ{$+Dž8%ȃ}D YQqٱ{>Ϲ\_Q5- CYTUK'ЧΠHH<.%T8Ee'b81=u~>>GQׄ金ՙ[߿8Ns<"YRj!YPasbɥ?ۃw[)q". ~/)o$f-N"݌3]uZܜ(.<ٿnv܏iFM'PD ͉[pB@!?se\s%c|7)"!@,x6yr?E?d.Fu5ycj]@kbOPl .l Γl4%Rh,KH,䙧%4lv B-/yJdBLeC%hJ~ӟ˯YmN&I!%OH))kdpYG@=gl )%)zTІ.# i(% i]iPq8NB惦ț -4e=_}3cܥnK5a`߿pcwt C6LaJ*e{ KJZfȚ#H.hF4Q駖v Ďq#ę5 >\GQA GJn ~Hz򀺑F2 v-K~/%=\ܼfy4q&&G״cN Zդ(Dl$;=l(Mꂳ91±ADBMWxOr5 J !aC$!aD3)Da#k1ula ҰT+PULo=s8ryWo[.s2wDg-egw\|F DyvuSbvyZ}?(~8`Zr2{= )ZQ(M#arNHPRntqWVw[b[-y!e]2Lٺs~iϵTHt>eFRE}ajrZryqr{L0aA $4ɫՊmTY #;f E6f^S|J#Á[8hZV%Áۜ R2#eY_p&~'<`ҞW7^q.$>|w";.?kC)r4$RQaBS7gGao[XWga_jۿ'O_oMUt]GTN3VmvĹ$)9˲dvH;m51Yb]5 J#yRPxhPb?Fv{,ꎛ,ή<#ȳg_s3uvquyF* nwJnf٭XT /_}\Za'ٲXlenGɂ"Vf\phYIi?<9vybr+noi8}b}EXlx%ӔUcPp<駘FJCݮ[ s^~ɗ~NQْdIY\KypB@ I25)q*3LdS/vXk}v݆3?*E?N20#@QjʦSnLB]qqqc0hLɣ\__n0Fed˓'pTe* .: Qg( zb)"%1H*xFaL$UvHD&ps:(C^T! QL ⧟+Iy)gcB1=0#ޓ|Tʓ}׏TӒ?+\hrs"(D 2cj]znŸDck ^NwiO{jȉe\-N׌L{8p6c򿸎E1AʴU%`=fd~擆W7իcײnz`td |9ɪ#3~^y@B${ooxڮLK&p^ct6xlynk@Y?g\]9va:O^%@'(s.g2a %+HD*.3>DLo&9.{zӂl69R*밝Ce !qplG&(5%y %W-9"eD.c{:Rs~ԓ%/F*x{vdyB}d%:4ub>th# dE>%ƵD؏xoQ2jˆlTGͳuƢ3xf2ghS vYYD͚qǤͷsczϝ-RLNȗ5]30?zz7߿ 3FSWWyAQ4LSi۴ sM;Kz*k0t1,@{7kz70xݖɪjhkbm\E"cRTeplO/i#cٌә(=Y*6c'=4"2l_-yKrt!maHJe I֟~+.Kla D&eu4K>}a#&ʤLT3r=]Bui0TdKW5M3;bG1czIpD/V8!k-JKzZ`:2Ԭ711s$ >{, }X;l/nV+ -CwdxBM=E+jV{ijg7TE1Ykk9:KI ULˊ"kԱP"}5;tȔ&7(ysVKn Nmb$ ,K|\ `6R5|NWC@ BŊ2 L'KtQ2cZrĈA@W%=7kȩ N)tmRd,+꺆*Q@+ql7d:W%7\8$]y:Tbv0,iP%mF62;W(#eF^$0S b# &SR<=GD(.8zb{4)] > ) eiB`ߥef"[6ox/X-JۮoѹS%|n3@ppEJ!ŌpHg0pИ~`QT !q|XqA7l1a3‚f=.Y맑i]kIS fu|2 #~/ɋ, bgJzfZOOHjӏ{:>#zGnu8R >X eb"Z?(.5wwTՔ !UǏwjJ|Ғb2]1-Xo7p!eBC8RsH/nG6POO-Buԑ ]f˗5mߑ%)rLIxˆ/|WivRbg1OqHvo%邻~|ORlR"Dbݭ״cO]'x)}`:"M=Y{DN; l[2uq#DPd30 V9Ljʢg;y}ix 3D6eՒr?~˗?}Η_>XP9e/kc݈GR`ES2ox!a:m(K`Nb9P1: (K|GrTVP!D2% 6ͼd*CF@$q~~D(EV&;BwߡF D8q:tʞ`O@,&V(#T2qt'\S[l>UBOh\NH38G7DhsV2oȋ,msgƁ`<Qo?>wB|HI3IYuq5_P7%6GDQSf*@O}4UJ3!R|XFjB 81 D@%XWLi4~aHL3q( e&# c uC%3bG(*STekQ|LF6 4Qr88V ~1 'ޟiC?tg@Z)uAQ(.|g9;B{)_2knxK9lk]]g'a}t9#1$ 'fGJ*UʤZ2XҔE"J@IZcߜi&cN % ꒺f5ųg_\s)˒=e)+Ao`l0L4R:ah:5yA&rkZf:{S4mr#Xi}&[$4RJ.p6yzRmy\pGlG*A~妋}DJm󞪬X()g:HXԺE3̘I% ne乢r"cQG% & D<'4HtOYdUId"#r.W'S`0o~Gڱ#DOUdy~#'>M0Z1c ]#DҤ " .sDREIs|+߼D޳8@y^2_b23_(a9GB Ō#.G3p}J9GcJ켸f:k>{$[ywjO]ˋjS^PUÚ[sځ,;TՔ9/^򒪬ɤd[Bc;L fMë, eJy#\]>G\<dYb-)sC6`9/F0$钫M5I kGGp)Y2h&32i4hW2ǘ@?8rJ&s}`Fh;aM&Xw)_3=,@*|qpv` 8(K3׬.e adu<1"咲,xw!3ΒQJ1eHͦf u<<{}bZK׎G֛5mwLf<'K&u1!D"M)> ]^}2`Y@*Ń v鎷P:xl9NJ tṭM0Zʲ$20#̧5W8?~G*AygY)tD qEQW%1ʔ|R% ȤNٺ1#lKּz;N!X<_~~[n=Ͽ"/JvbL/{\GB;?w57WV%DL%TY4MM @7l6[3ȔJgZ yjXVx06-ϕ((yBзJJ:!DG?tƚqT|j_DRp y ; Ip"Z0C⚅1yp=|E*<2)sYh5Kry6O[xoBP$n91*\E$H‹SOe ǛVyڀPmHj[R3GB#=3DysX+_^ҵ}wL)oN'vX!$VtW>&SdQN3.q͘5?O) ø=#7?a dlCby~ޖ 2KZ!f̦+j Dyb4atB e ~$H#&B WiB5.V/뫗UIvǬBKTǧ݁.ʜ{LdC=+iK'Ŝj2g9qId26 3uFz8w(?ie^gc$Aj>0v#PxD c8Q oF"O?~eEL&O[6#zvÛ7?b>WɴLP@ !{<@=J 𘱧(j1RFϫ_Uw[ J\4^L2>`eŹn; R`z1C)%uEß/xvkw߼]dÀvVhV;zIEU:Cٙ Æ0 Z<2ϐZc{ز>lq0Udy:TUlD1gX0Jf0daR8o5IVj f~o2vjijDQrj=Zϸ,onǤ=~L,; L5W.\ ˾7H2|b9c lq\Q/_XHNݑCx I8-Q2-Rjy9c:[q:9Qس&R *g9[g98D<\]]q<8$bxi@T; c1G06Z T)g}  Fǩ(@i2n;hMUv;޽o:;-|^_cSg(uwń+*hil:EBgH\M?QE&q`6_sFطk7#\ޔ8ݷWYA%w%)Hୣ,˔`#]x8:#A*H"GJO]f泗7uͻ "iX.K,+)o?0 |uN^Ul{?{<*0SbLtV`1=YAPh4eMU5XkB0 xkn7BbROP1p؟h"Șюc9 TW #>M$yqrF ({؟RI'2vW,V+e퇏7Od:iA3) ǔB0v=--Ӫ;}?0L0D^?b`FҥCwM=*Js 23c`MK"yҚ/+v g2bwH;6O;M a:Z؏"'C Jy^'gSֶ-u=r% *noBH:c\Q<gKN(1M(pˌU$ ךVSf92$Čd4MvHg3i Du{D&YB-5Q F,%ńl> E(MnxdY̐A(͂뫯4Wx_ ŔCfwEI3х#aS'/dR0y~1&, DƑ5 ޼}L'D]oyE"Rg s?󒛛/;{E9'bşS߳ٽ% CYxoA%,“tuu%J CR`eyң'M>Z'u-mz:r“\E33[Eq&A4ぶm:Ney+voK_½k-=x~l>C|:#AvC ) Qb(Ss1.seXoOl;)/OKnooͷW3> ˂хǘ.ƈ1 *ꌺNu}=W|~_ƇHPrBBz M3kS#ȡM u EU cę7TuyTgDܹ&eYR'@;XkPY2TL4Ʉ.Ǒ.Jҍ=B+c ,W5MS!gyn G f%g7ojIUBߏDp.oZ1tG>>bezN uj1GDvt:bL@zok)jj(hZڡe0-]g9+pA"&PDD8H  !8q6rڏLg)B{r-Ñg8'"m2/b>',3=bg;*ch#2-tr>g9Dt?~G&s_K./ѵ)3\[;Y.y}>l4L Qz4U^ =]wKs:j( I@cz#Xmjd:Oƺ!1q۴$tƲ;Pyumg_bu7/fRzF4٦TJM4 y|p8dy| \S%\k#λ3<2:guֈ6%%$)] 3Ђ\a4|X.x\?l 0 Gt'J]lw IFY%WWWlvktJ'FM鷦mteɵº'WE@2'\,t]Æy}`h-uq{tV2ix+ǃa3m. bƬYCt|㘜Bf)>Xh@E5?w?ݝy9 oo1,*ޯ^W ֏QU $B`uy HnpiQeU)DgK*"lkzu]LScq:~H#EO~k[>x/~v#mRݿ[>skBΧ3,ʲ&FɌ&t}vt.YF$ӑD3E{lU 9Eq~bF>7ZJ)"~`p':OYf E7oxkGf  fO*pSḭǞ}ŅgdZlp1w}2:ÏA,tBk&ME n*2?G6x#!iO5!ԅFFhc{rSm]׳o:zTu]< ]buMi`xc]ITgä͛w`y113Z^_-wyYэA&h#Rd9t6]|JtcҠB1?&#d2s2f̈uz.CJ,>~/(S"!&f`ir޾.o/"֍<׌ l"y!͑|DV =劲n(i3爘Q EQ0XTtڑvhqޥdY*W:Ɛ͔`MveNz(JR1-TMg獧@i5uHq`>dZ%ŢBOhҟ ɜ~bqëWxKg5}wDJISc5]uhMzFIT"2DM/ƥd[a0Wa/jF0{<з{n?M0t<]dĻ~gPU <=mpV)ytnD"{e5g9n-8weDD8H*gnP*]HYrKxU3]QE z]QQpyy͊7`Ȼ7( 2nX]kǻ QgH Gc 41j\08'~N÷@;z.W5 .q5ad:xl{&#h$y&t3B"ƥk;V  );qL67&8ovTjR+Αdz=(^9+S8SX˫~rg/x;>&Y9KEix9eGvG5#d9L\Fw Gi0'(χ2ѸΡbb15d(.mbH5JJrYg7/_K'8c"Aҝ>G~ʼnd;2xɏ?;ޝr3  [U&WkcҿkEh}@g#uT)ۧ5SIdBQͪ󹱤;~De9$){Lb{NՉ W 573>/ie.F*XUU34!Q.@ fdO]t,cޢ5EdT2զD :ːYٮeYf5fvwzk.#T%JPJ(餢JB\ \H%T Q@ mDEFxGwfnfNnV?;.v/ޡ%s7{5p>H)Q2 Z$ -<2yFG9/[8`.9& 7Z期DGHyr~Ƴ|0v#~7<4 (QFA]fȐR2z-7w}sͮcXkf%Jhn>w, yTv%lLi"7d!ɚxhsGAAFAA5bv춇$8e=c}C]@g kK IQ-,Wl$f5hGDXJU9>2o-3j2)LefV3 H\ח,yJdxC58K)%*I'Bƴ|;dYVеE}|vfvfd6{n p )5BLL 4OCI E2qdلh]![>I s !b*0)UFU(ADނIhxeA|V^a"TA6R硳;UU!tDzjr^D|ŧCjALJn6|L4&A bHFHqW-@拒rƩKSUDM] o:/>ٳgHЙan~iJC'vZRGP B7F_IR(kSD0A2LM2#ZD811 Cty,=g~9WO?f8eX,v@TdM).LN")uͲGx}O~XףEa{..! 7 :daSHvwxsqqBQ޼yEuE*B2. s࣫eFۍh#h-~HaL)3ǭc i|8FXwӓ<C=]D-qh1b]`"+* ;B>4h9Y_mzuwwXqɊ6Q8ufge?ѹM=]?`Ԝz^#Ϲ\M X !$ߩ0ybz12)m᏿bLzdH5J t*)܍ĺcHVرgpNg{2gHa8ZaN,iTʰp'M Di,Ks05]2c}Ňu \`#M+no2_/Mhr~[sRJ(ZL1=УF*/Ͽa@K^| xwl#!Zt1MCbxPFPuDS[|Be8 ,sɲh%DrӶ#I(dVΨ~H(:Kd)qX.8?]xి6T:rd0y ^qu'/3û7p}-=2O1gJS 9ۻ:Fg9u]Q$wh!,\H{oiۑ(:33gDF0M3e2A1HOHXs|:8`>0_^׆.w;~-%WWty9nDy.(g)ph:!={NQmVW9bGÎiPR2qHz6C5}T.3꺦o~.%Q`5ED˔_hSttX,įҶ1%z X(sCkvC!B2t%LiQl8*[omfԵ"@%anYRÁ;={掷on{'2xc>n?}Ώ~#)姿N IIsԳS!-)'X=v[[ @(E9Xr_~[PT,]$UU2ycZdduY.ҹ޶=],k-7Zp.sqbVV8' )fEGQ`f`Yԧϟ3Vo`gkx=/@42-` ndly!)2ɾ:K35,0h覑`s DJc}$FOC :0y&]b_] qK5LԳ%e6gPcK }7 P-90z^999;5&KߝOpu^\< Y^^]OfjzzY[g IDAT^2lOtvD(RUUves縸XNKͻo5yn?b W/.׈1;4$~1 ØegTg ft@%cU_Q%EqطhFGm $S}?Zqy-%y~4KG,O+Al6 .~qvAU$puUFQ /(Ik%tyժ1ĨFE`DQHfefsRMBJAQ$kK?z"]!Kྒྷ%͆a<0N C32L#yYrv2vGnޱ?LI g(Ƌ4@vq =Ӄf&b^X Lk4!҃..TGvB )؎Ǝ)};\]|氡ni"3 U 81jC_~q'|o+]㙰G29Dռ^o?wyQE8C7q#Cf(Ql?~~ 33HA1 ;{O}t[ LK)]E RdN9ċg<}!Q,ON@y:;#gKTh-Kɀ6Գ5uf笗O8<!3\P|i@<GY mfE77tHY %g'qӞO>jݣW8&LVM'1D`}[LP%i(D8x &S G`"#ڤ߳ZyA=_1t-}?b0(Zdt웑,!F Yh޿ۣd0NA>~zI7=a 0#NɃ#Ny]'ч?&dJ'M= (qtdq.~!ыՋ(`9A1&ϰ!2+_#c 2˺b Rw M"Ȣ(a(A %#ySxpJ2N.%>8?_n7%/ %Ɓg)d*''+~ ~+j'9՜'f\ mUصdD#A 6oikkܓ$~r=C19AJZ9;]SWf8U!)-Ptc dn LE00@qI7 Sʣb 8[Q%u$S9UUQJ^Wc*"*ɤdDuJEwyƖP$;|駉msnCocOdQHi( a 4\FƱǏ[fF~#fH% f5"''v!!}pm72ZR3>08 vH)GK||qT>g9`L#D=ppub=c1Rez>KIYθߤ43H4ybI:6qFz2 m2;J`-%CB TSKB ԩA)CKbTY( XGZWŜ< mme|"(*=4X.8;ģ7tV,OO #|EЁ #2:c77,?ߣg~5ׄ`*> P糚riH(Y,JCUehe9c'ψ!_aaevjZAHlyd()';u=$s;TJB,g'g/+| X7 #Dٷ{,E4R0Ltmetiho2 B!Tۇ ݾ'0J3ف=yfU*f˫l v]Jy (G# 3f#qCahYUGc8a;JELTw0:( )]Md4Y"MpP"p"_(PB2FLvqws$,NB0;b"d>hRwF!2f JdybIx"C"_7_, ^BIbG >$X )܇1ծ 0;!ѣN9TA4 At+Ą+'K4)/;ppEA)\db#$ B-RxơMKlDi- a"+ )A [qt!rz~;nsPF8ȕ"/Ks Cz)s> Hw%m`}lFqrd˓96_6yV"uLCx ==W/h'KYx,OyǒS R+}k:zA5+$諛'%\^\1#[Mɶ3@,۝bYA).Xd)ƙQ%sO<3;F͊r" h-mc3s{,gZk 6EVӴ[JpK%-g kt: ,%'6 ]ROaLNR/U&>&%J1P&YՂLCYqjEb4 #HZÐ@jR2`\P3Bl{fՊ]:Ai*C %s(!P1`F (>]%$ ލ04ޓg2K"K 7xإ nᡁ'5@6<Z"B^9)j67w#*h?ʬ!mzbGx7b![ SpzzyƢ^uCb;oDžzM9 |q~pw_OCr9g,luYU~Ŷ90ّ0'3y4%f%&3t]0vL6A斲P( ;M(}y5[:GoEÎ6${Ltd%A#xΥ!{K=[2(].=Bpûl&ɔ8%u7*1ENJڶ%R t19$*8~.j~j.;6bmEEUfc( #e\$:t t턐lJu'"AcKYiD;V7=Гl1jOg!AP9=]uةkR#OuD˒ pէTU#4uYa[Kqa'L&ԏlo) (҅GG(#Ø^<FvWÛ7ykOK5t|;?%l컖7׷~;r:Kj^!dcR&I*ɒeL}_~AUW-"{nnne4&7ml‰,3i.ebx:XDط|6}7Ͽ_cOB+PP?CK>2t=^WT3Ek#ea -^0Zl)5_u dJjLh˜,yF|iq SLQWUA۶q,g%t^`}o~ZkOZ }xΛO!o@T gĐ}_suyad>qMΏK~14} 2YX4{Px܀vtdS:On2D򈋇d:O%D<-qa^}秩4Kv> =E$ycW_}%Yэ) )͛w}Ӧ0`]L/ i-㘆HOӵ}ǷSc/Ә4BZW ( N~1ʬąHQͰOBMl-M5ŢbqPj,Kv;rcR!>"Aʼ@JwfsrtߦELƹ;4(~ [ "SҒĂ/*\"H H48ǦJNh1:٦@@@Z!$ " )Rr i q8A<ӯĐRyacVt!ϳ0C*##K)Grm %Iy4Ewhpȼ=LO. j)J94r=r~ m_A})?{^!>NHrx"[X>zR" 8M).tV -U^kˮFB F,i/\^>e1Kgm YA:F KMUrcPa{G &X+Z#w E#_'ϏI$8\YsDTw^~!k,"m@%V9FWHNxL-C\2{rF< A; (ł_|tRXı+ FHJbQ_'Ϲ:yunЦ퐐:m=֢d&2 U^,ʚ,.c9yn ƹ?ŤGBbdDM;Fby$ +Des}΢nfN(FZޱ]Pѻi(U D/&U,CF',,%J)v%<1lɍET&"bI5T9lޒMg')<#޽cҺD~4GD,!c1i6Yb3s2/xwGt48D~G[OSl3FL#38l7lw //>`}陊Y`*%Ƒo߾%jۉ"/qeV\qcd>܈5ռ[F74m CXy۴iY$C_irْa@'&ȷ[,~Ĥ%UY͛w<0"Hl<Ĺ9˳KtQ/}ܣ2XS"#*&0д:4 r~sX.֌=C$,!+?}(Su HihaдyfbW?+O=Nf 2 W'x?q{w#ZKxgAh'{[9=?K \0f~\ZV݈今(RP%ţT qGߥ$w99[Q%Ә :8x')EϿ^}4(#pM2P'rRDnʤ ;@߻<7UUZrG":3ۿ[<}z=o޼I\Qϗԋ-mz6;fd{)ˊ‡*2֫%UKbHL!IqQ28HFyY%@J 8穪v$3d@G}dX=M+Q<قo7ةtN=>vء?&JKf "霏B ! MqFC'>Hr25n+z`m@۔Ig >1p)"#1DQ5cJ^\}8,uTyh':=뜟p6邩o:Cǿo~s^|,!ڎ >X}8ѠfGѱZ!~O%x8V'IcJF\,5"TANg#*Cꂦ򨅛pAj^Ƭ#@x !PZ]L$#>X&.lH_oAJvCv,Կ<=eer\}rqoJ IDATgJpDc Ę"LU)%1(% 3֊< - sy<[P}O>_WX, X,j1<:pB48~g9{f#>&P76(ӑdGRyF$ Nq\'cٷ z!w˿)9/-E)GvwO-2QR)qc|Wՙ3K |zI^—_ۻwlo&L6iCN2#ii{1ߔ, dpւו%Z }C en0Y()xx0(󔸫咇[6&KwҘ&0*i1t}8XS}<=u5È&YAD Pk(9(" Xu5Kӆ'tkV )m;]`P/_v;faź *7L.29ӧ/Y-O1&hnHƆinabYocO72+9rǬ'"lqva,jg䒳)CY8޽?m1>Ly䔺C'vMO=#PE]g\_D6N wL~*fB%2!;1N )sb౽cxl*th[s&Ô^Dw[ڮ%c?`lj"S=֧:!%1&!2S8FҰX̱ndxvǍu2WĘSKb3IzZ,_k, &1Mr`j'|>z1E DF6RD$ vo8?Hs,v.WLW!`2 ʲ%H𘪆¦u8O0)K޼}y0fp89痼~7??>[鑉boߐԥ 0s M)5F\qn *(:Q1'1Yq ϟ<:6-y?9gG枛?G|r84{$36s]b#6 #XpBĊ BJ'{1RU)惹LL&5IN]|iLvmO{hI$)ZiU͋?qqq@'7S}EPeL|Y+LeVufeU(rʲp8pl&pB"UhlۗdE$"K.Nl-(3.α9'7gȺ It 8^c|$IFu"2 xPJP9EQǺ8p<iAUųf0tlfϞ dӟplA/ڎ1$=*h-Zk..Nn7>F&jIuG'K,8)ov}e9O@Jس(bzEL B嬖U\즂Dg |hCzr"Mz,Ë𞯪O4" CJ"uvjĹ/_я'<{vJVGe& , ;zq@i8hǁ$Ө(GDū9${W u`iidaܢBjA54Jľu=']񎧨BbŅP{dœ5]{$yrh:fjH 丟\%g +.b9,Wgd:/qˋg9Ui|IEmyR6Bo"Ydֻ '~xmV$$c7"rH;hb,,Kqbk0~ 2PsE qgG(h- NF{uLE^`'颤FE"w_ehq0Mz x'ѧ_(Vl{~qlAT&3K9;aq{ 5ns(X0<}Zx| ccL7٨D"i !PQ:Z8Fl]h˚I<,yL4];ba)#(/Xb%Kb %\KyU2^9vۦGHp#eI $hoqԅqTVx52j6Y+L'Yzӈhx3Z^Z,xv#%$Џ8Js삳8d:e2 t>۸At/~zv7`H yqv~V}{bŧ}3Ʃgci-9Hit$JP*A+77o.G84xS>\={1M-mѐ*. .O/894abXz E"J RWa"xB`#2 $$.TXNL&ARdx34GZˡP?b:ȗZnnh8+iy+cȓ̀~G6;N.?ٳ'|[Tk]i\ f[|,%(g2#YZҎe!MdN8j)%A'-Z ֈ`2 *&ޜ 0L^uJYuE$iFnP d"O$cg8>6NVqw7(fhtXUK2)Q M dY[..=yY`c{fj`^!.&k0>W(2=:T4Y31NmQFceRU9m#ym}=xz.!R2я0Z^!ͷo 6 )V~q9}sPZ]T$)wx7\}KZ/?棏>1idGd0h-8RIEj'uZ H-ywgR©">{Dq3e@1G9}sčtp6 n2X;Ō>*8e Kx!XԤ:%Is/y|#I$i Uc~0位@o!“9yiz3xߓewNyn{Ǘ/>} wf=7TQV }蝅1hY,⌋ 4=n:nKj"ul`Y0аJ &?T?o'8Ow8 ޓQ׿fw麆ILH!#:IT! YM\S3!;KN"c; J))E^ <!ֻ SH }KZH1 g=O\✡m/Iqʅ3.\`Q#E:o$ϟ}~)~o4{:$IFEM2u)!Nޝ@p87sbɿӡJh UeQwp6Fˊ;@(ɓ"jU8'p$jjdžO c`4KP:Џ 0̕hu"A&)bVm7yݎzy Ed$Z0֎Qmn:?!I~״}/<%Ϟ=# { (*Va` >xסɘNtaf~WF!gr<{<$!DjlUY.<2pnƸ&V(ҔPBS4 ztuɋg/?S6=޼%$Hp`OHp|pGzYquu/+y1Y)J/xUZ846֢$CK4z:&>(|4-Y\T(P^k' SG k!#L%dTY^c\Lޝ___~=qՂ,WT+nb`2 E8xY,g3>PTyɪډ f ~BxLQ) X/; dyN鑻M0x9 )0v,eH=<Tn7ߓ5^\dW#|Y.KP5~ w e0Q HGЊDLS`^q~~8LZ)g nv\+xO3Lg dJwbs8r{]}&yQ1NhMrfpdg&ǖ~B0J@yi??+NN,ki1:ZǴ[V5w<044q959rX2Oq-N&60f$Q 3yNOTEN7IA &lkc' o켠-aǁqj#6+u?9Y ggg)%eK>ݴ5~ ]ӲX,<4Mf&alq{K$ڬ`:<A$)f0idOYH";FXA ΣIEJ$cߓ$jF+T۶ICof V,K *-T6+-}mry ;~yrU6;Ef )%݆ Aܣd|QԒ mdGV5d9UF4MCv"+rk$I8|4k".+$zLS {AgkʢB 3`݈ YXW%=}gIq4'Vu ޲\G`{w RāɒzE*L@>RE"1s{f&הŚT/5u=q8hU1 #.1,K5< ?X)R{1&cSs IDATf$L1EuA*OZh LE+NAğ"9;?;nAjȒ/al{wz?yw'' i@O S)X&3!s] djIFu5Le=:ͨKä́AuPi+DX/aRȧS hW iVsrw{ t}P|ɒ>4>bC vMEP808`{qszRrzr_ `Xﹸɋ 6w-yHҜf0G+Zg4@UEyTU4;{rͯ/~􇼺$:.WTuP~EY)zC/Aw?C^t1V ")Hk}}/RJ7`h2 dzkZQ3r8>4OU $>Т!Ìu7yߛnN"rh_NZ$a-y!J\]`c@L̀ 2v$ML"[ey3jM7X(a~gg&|ͯ꫿cts5,֥/ xxooWN_2/$`ޑ$r>PBH~HYTyAty$Jpo{mIk隖iFf}rA8h }Nx\\~5 jĂ̢rZ"NꭝH !߱!h%C4.?=:?Nia?Z,Vh2 ft[2~8cxI u'wqfl[s8IBǍ*/,+`C`Q^jQτu1Lt ^H6 8P zF)^2[( P}9*xs ߽"FuqÓ.//p$Պ"K(99]GKIҚcCqx s14!ڎGXtd$:G8Z"xlys}˱9$qF69eGpC5#.X/ZUJƮE8dlہf!d:"(v[>'56ILGi~w>II >veg=mMÀ >q؝e`SU: LE ͢Hi:'<'9YrN䅊;,2NSe k tGLRr0nTuuq4)'ZRb{!h3Z55ށjXd:BQ}'=Ӭ#n8;ۿ_ۗL_|9?C듊1Nm|;H-`Wq(7\mE؈h'BdJfdec!MS5q U]Ng%¿KBvlZ?ٳl;G2B@ MQTӀ P/WMϱQձW_uUYP"K I3E_5yV"&G@W(pvy!1~Ǿ9vFc[rp_mۛxp >*c9Cwᭋ̌'$ 2-8?'HH;RCB%i?Yf>b|`q9Ԟ,>d邼Y. yd'Α9Z|y@$Oqz{]T՜.q,Kt"2MqiN rswӫ|X2#IZR mc̑Ǟo^2n 0#48TH!GؒD/8T-GnqwNk&&!lr99F`lV3^p#Q>ERSYdKnDg""j1a'+A1Nlv7#\)4KJ1ZbqF!gLv/xY<>ZO.ȹ ;)&ՒOs2LpƠӔ@y,si`AB !5 *j[sCGVdcOY8 c0:H蚖" [g%fr[0?}뿽\4RXDR g/"P((H) #RzyRv1i:r2EYf$)LCO׵BT2nQ"jLƻ hSBFV<4E@zN &;pd7AdtWq6 #ؿli9ZT*x.LV9u RȒ!8eE"_"uN׏ SNcMÈ!8bBcj#[޼jz{`*x|x}}ӫ ͯ/yzQaMOHD@''guŷ~/7!^P ցi)JNW%Ϟ؎'HYdEY`bϯSOiI X rxDzez^7[vcҖɪ R (DA#[dGzz T!p-헛V*^'"*"d{9"iKfyF.ج9 ݅ղeX##<>}:S$BH0ab.kw`#‰ %1P$x>e X-+%(Yd- 1Bt$?aL!$N@zIA1_"RUUZ1%NZw`}+ ?OkV1}S2Cl4d3&U*P@l3;\7YENɕ!RS;}>TeS塻wg}o~3}vz2G,ҙn%%X ۫5.N9Y"M6h|w!_ rkRDRvĺaЅF%GЮsV׮V+ں2u4"y=ÙCUMf%_|wݻ7m3AALl6k 1e@$kT?VɟIbQie::ƩXhn+w6hUQU34:b]D)v2ȇ7ivQlF 6@YeEYTi^i7M* )82 sw0BP7UCq$"7F>*MϮx=ad:&58Owg\lV+cFE $9F.)4R2#56 LPkq)-*({uQ f ^p>ډv誠&֛5Mr͢NezGBPRi4A||(҄5RF&7 ;TÇ=BZRqo~d-Z.4TUM$f9z2o-}GNwTJ|X7Ø5[.g-!%羬'0)3v4J Dh]C|c*%arx'8_Tg??|=%, R ϟsBpLSXY0v`9:F8MP~p/1eE])no֌ ? x6բzYsAk/~~9Z s |\bȩ8ΐ:?Rn Ej3k`rް\PIB)"{|`<ŚUBQ 1.xףdD+ l6CGڲ-~$ ń80 2w|xx`/Ⴓ^i[=wr-cіXV r>!F\\!DI?!s” H)a4j)H@<]#Kj@:+'G7~`{Rrh#q֢n*~?1v;oO?ptWWWPxvwy9~˟W_3 ]ʈr!̈ZGΗUdPL of3UN$AS8x}HUĘ&z=gr,kRJ?<ߟQ3DQjKY*vQܿݞGmC?Bt%g&yP?Y$ ""W')W5 Y:`q9]XW4|+ׯy߱nxxmI)onVKG[ EY-"EOgsf-Zк@paэ3ϱK1DUg@wLpusMT}O %︻;!Ϭ<]h^}׷8Pl[/\ܸgr!Ӆq1עBC%SPREDi0Y.DN,KF;qSxR"@hJ"j`,z )4d@*ǁ/nPKwj""1/Tl[0'qd^bfZ!;LU>{MDAg7m$VQ"C B MBI2 (tNT| vN O)E~㮷KRsR ΣtHUY3فatStޱ^5MD) eG k#FJ](‘T ƜRJAѶ@)A]@ jlq60J7O9eY_oĆu'% * Lւ+bÄ"Ad$QMOr~V+|<e; &{ą>iL@bY2={pr%E4YgWOoFGݬ7 2(O֪Wax'aw᷿_= R9 X)Jփ1bHkOhg0Dk5k$\.! d59b479S*d:Т#Ĉ6n4z*QY`s0UY=!R65@? 4MCU~Y 'O$"YUr&2G=)V< YLCڮ T]a5xW__?{8rs:!zISĨ@@av)e Iޮ)X?]\1J5@$>ø҄*Ej%RC`(( .݄4dwrTMCߏɱ^oJfbaѮ2W_s~yʲVIOYm7a'4HrUʋH Y-9%PHOWea8#I%Sz$&I4ܽGnh!Rf &bѐO6}Bq| X6ELvPY'޼ >LB-) Ck1(.24-d|Dэl~v@wv^/9%9o?$xzDBV/V4Uù0NzqrrE%mHǎU[JTlV-—WswC&Gm4M`$y*#L惛*6 t)@\D&AQ(SLf]4!E^jU4NM S5RaZO!$aX4 urț'H7 |I hJ@HP~t< &Dբ a' KLc`j8EKL#6bXIV AqdCU6?Ϣہ$6WtOYnk*{c(P:󹄉TO(onD zݰlxzEZtf{DJRR 6 /_|6=ӎrDRv774UI ÎLuw'VyQnUQ e`jK$oI2Zdzs:< U)ꖶ^d~dRPʆ맄q@%|t>F!jaQ2&y|fbɑ"88GjA2XTXߑd<~f{fLp)ZNz۷xk5o@x񂫫 l h+CS()9M]r8>{)aFOӴ, ?cw8P.4ZLrN*ѓBk]%|x~7R1LS2E1R#6we*Ys{V/>@HY.pN7r?^SHsyYewV+4Uorŕ)d;ɃBK[GZSMوT0L#~$eB{7MRʪɲӄ\ $I&. -062TuA(8\.iږiB3jnw`\_醑'knnn0Вoq:d0\jv#A*bdZ3_Ҷ?־'"'D'H eݎ=,7H*W}LO{\hǏ9$r0Pۛkp69.%Cg'F+Ų-)z PRwT,*Ϫdۖ۩87 2q=m@ȈI@ KDLiVry%#e$Hox_+oxxx%7!E\סGKe d~FKdڂLOl˵_2Ms:S4Ͽ?r 1}ɶi)Pya)3gT8iPtC4uMf\g8k%.86eE+{"bpS^&V˚ ]wR m.F .܉ ¹|ѥ2\lxIPn1iȑE %#pm/76y|dqbC6PUgBn(xtyoÈv|pޱXք80JKJ]shX,_ z”҂&ʢNowV+x8Q7|ᗚm2L#np?LY,+rLբj0Q:0yK$r`LJYoj="Tl=ՔӥgQF2UE9G嫼$m1Pl):34)SP:/ u)2ڼSb G>颮a@낦^Q% \ȇӹtr*f2I(9wh- Ѣu9穪X5 -| O.s㘸~RglX5K)П=Ŋk={FY8̜bתisØ˗Z&l|Ongأwv>r/Df|pjꊡqDaDr߇"8Ϻ-e0{ҞD1E";`!H SӘ)L%@L$?U`{\$vL8҄1ߍRLt0YCڙ_'黎~詚fZSnʰq:jt=r;Ηh8=|Ֆٟ[LMzOvx#Uf 40H40LeSS<{ ǽr6'fs Ù뫧,.qp ui=${`CD*;<+A~o;g /;VѴitώO,iDQC>oy8wg| p2M9q6>W|fԬKʲ (fu1ޑ1%YУHH]6*Soy<yy_>qsbL`<{~p)|D(248l~݇j2Y,|fR㇁iq9NX;dOzD &Bp>sJ^ʜ<)W6RP!RVe)kr55us_'ϟ_^oV-U#ޏgSNEMMwl^tB5C§H?P*jv$?hgZW?a!39kwt3i(;2gO6AN)(Rʖ*ռ|%k~w?oͼ:9 ۫+޽{LJ!dːY (QJnᔟ>3]ʦD .[ebX<N*sʾk=BzJլ'ɡ8K4O24n (X.W@*qSjR0+!c ۫7 ;Q$M'2ۏbD ;*Rc̔zfKXd<\Wk'R451MӰXc8aϻZkTcShES׈մ9\ٟh"nӀT4xCTHYPr0hےÅ`a[% F.NW???p9O.Q:bHCT R7bbD kq83]HV]vC@iJLDad34l6l[>/O~R6,b~OHӅ{G,5 sM 'E`ƹGޡu6²X-^\T%*|7(+ZxzL_s{Ĩx `] Q쏏SGUSAݛ?RO7Tgϟp'qT(eϓ>{Ƌ_ڑaxt(l7)Q*H%E .\mo,WTi#n6aN#ՂϿ*v'?;4pj'\/ e9{ɱ}=q>td3ztP9 *d/frS^D0iڒ8QD`mKh8h Zknb=ds~C|+j_7<}aЄ()"BDADys\{c1Z0 %|-8a&^xFmp0M5jwﱣ<\4!p Oxr9޾conYnxiT.s;;N!ܲba=}k IE '6Z\T5*>~/~ giiR4& Zte%ʲل.+W HElVa'Z4Z⥧]6g!@w_S憗/,͒_#}72=J,8i%z͹sh2/mfACoqfhEYp~rf{?]$H[/ŗx'EN`#Yrs<lj< `1E J0&8 X+dk\ x!y IRՙ 0MT~̃zIl0sr9< kgP9VR]$UY`TA 8UY/&?q|8b4@<8o%ZUSq>N'bp$ißє;c#י]49ҼzU߼]`qcF1! &I8\.1`Lb5qDpPRCgۆDMH uӰl(!9vuAQ4ɋrݭ&Q\m3Kg̩TFe<1GJE BlJz+?MxwFwj^bFNps{X73ekr`D"n]@%mdp>! Ӑ;v{RJ$-Q IDATUA].BgDYV(LH{?Ux'?&^bNuNF]<Đϗ&{^XS dZK$$Ւ>''Oy?|d/GL,W@%p.ϿV- !ci(xJSU 'ΧSJHTE:?̔º~0t}χFQ%>yBoH1 /~c+CYԤt80QnXYnjlW"|ZS%Z:_Zhd_C IhmʖUESHJ"_3ex]nIIl5=ąt@튺 S"0FuKr 8u)KCU.(˒)vnJDI>P'x/=^@XY,&„ȅZP KXq)ajM )Ԝ7tޡeI[?p.yx>A] @,kD,(ʆE⫟)uW%x~.qZ0LF;R:?tT[ 4+gl%?$,-E]\| wLQ6y\$CQh7=EyjAyʹE!q)2N @ ]iɫ3R+BJhc(D>T@(Ee$FJ)c2@ZkLv]o2Mپ͠(E!,Rf-H $2{n6i!Xڑ,s*IB!F/hYS+l!amz?ɲ^;>}.FZ4Hf36jHן8{&86,sxӟdw)u9W7%uP+ђ<Ynqa\-sSz9' 8UN{:r85̫ч3<#S:pbLgӥ>8zDH>vAH9ZVFH5LS: hY֚o>0gat|ϫO2:5݆"+(윶*CfsOpՋK y~{su?$?ST6c[4%< 3M "r~$q? bǝҗg%1D7~h1 {cVUI_dZ@Z4 n  đ ǚj^zDL06摢4,%"FJ2*y2 RƩF--ݸ}D+1Ε - y䳜Y(bwN9?|E#M~c3p\myIR9i%8~-?抺Ř93 jH Hi<66gLVsNMŒal"eZ _'/-Z[!EM{qk_v/Q(lr;UNG} 㞺=1#6i FH~I=BHy:XC1qLP8:8TӝvRhs| .D\AH.% B "!1b$p;TI(-䔜(THa 軑{h-ΊÑ'NWeFnP7<ŌiJ¹R0F'E;`31܄DUNLcS W*kND?II TeF> !4'FJZ,feYu3n`6׬/8q5 3k=_p}}۷yV!eb*\?t<,[4mԑ{f3(%.mT˹G+ӡFF޿OU%0fse~jݛ B9MC%{r=rC}S7Gй*-ÑV pR>0+Jlf}~+"DEx{5C\WhkxnxpD $Q@Q+1ҏ{#\]0nݗLx>Wo" h@Zȳ@6UOd*ϼ4 ? CQ}4p'`?O#T" m$-ATۇȫ.?vG޿g kikB?b<9Z]Z]0g/ivG#JaoSit͆c}bw$h%t=Z,RRmP%dF&0:CM=ZA85)E$1!=X11iDLc>/:'(z'-P؂EYe4aH&@e.B4xDN:9{xkoc.VK'躉XiJFLA`hk6ODߓY855Q&THBt[\\5#?W6_`,ܑs}~R,>kB/O Q Պrɯگ2N=|-.Â%}x8BIH,mRfWKdH< Iۗ2w({] 3&@(RF2qH1c@!R#r# %KϫTFt )2ibݾckO56 #ߑx97K1UB{|ʤi}.qZLJqt(Y Q(Qq}y92b]MOCftF x{OQ-ʑ< tM?"Uk7( R!`<17VW:Ϯ_!lw?o~[HZE&wOvNh40 i=o?b ;ŋkoPRg?V%U:9Bpa zz*ҷF:Y#ȩy%m{.c-+_ajL}K a0 us^חQR݄2Iܝ/'EQFY5Gl!!]t/_woc[V%ZHc\Z-85[nbrZdq8q>:%|glaW#Bc29y1#/qDY|=tl@& tuMSθ +V|˟s<4"=$ܦQV4TՑ(VN5ov˂xvc}b>daJjFg2\FۦC0L ,[&Ç>(d.٫4#a2P.*J/I(%utȍM[یm1RDMh# Wv'I@`e y^""iv#m{T D4]R byG/Uƛw}qiJ喟O>ɧxiK^hxzzbZ_{p:h# 0⠒LCۏx0YNM~bt֦z8)kSMлal)Ei;M!AM٬bM#{6 =j #^}+dEEulvL㑱ƚ~ђW}g~AmOyq~ΫW?$[&236 ݎȫY*>=ùӰ=j2meI \s'Gn5H ǻ@pKF49.01St@t*g@D]ñ;EN'55YnhmY,S:8NH)Kxk6NM$ Su||͛7 iCHic Ŋf~砮{p),-$OwtC8jpM;{BY״݁fC,/zqE7|7_K2DD&y0LM8F7s4H׷D2ӔqE*C˫f94Qij:(,cM>|@J Á?\tK652r{Y bp=B;$7ߠd61iȩIr!?0ư/=iJH2*n:HV4ˆqO h1 `aɳ"bJ?jѷc>[QNg3fyI<-ĞOawZݐSc+Ѹɳnk"11tCjJL/϶]D::Li:%aΆa*V*VHNv@^(i:Ow|4}GH-2#ğT ,:bIל8[ʭd>WfqjQBga (Ir90tO;\*(11"?ksLT*&ݷt8ǀy]e~B810"$e1Gd&CJץ r/{wvwJS#+eNe[O̫]^8H?%r|=4U(. %8׫QJa$xCi4iJZhf₲,)b1;li4 ХR O}^"*g> 2bb(.fF^6t;Ns}yA6QGɮ'x5O4A0pӄt(;5&zK}+~wO^|N5oSMs t#5lR]0ߏ4@sis@!BS.VL<+daXS1*5 j|jw{)]z|a$ 'ptHӶ|%E1f8y칼~> ,#)?ta1}6nF9~WesZ<=>g6QGd""C C7x1E#DEQ.p.R9`} :cJtr Bs 3 #yn0&Ŷ'S$Tl8K`Cwk%;ז&Q"ERXqu/>,?g5"˕e98v5Rrm &׼z}I7 HRh ̪92mLia &OOؾgjcHi( f+Ƣޠ{G)y6C20E0x|,gC?h 2p<}W2é&884[qLѩsl |/i7kW32!? ɿ怏I3~tr,ZHۆ@im,D$wwtpnGoFBӔIsuuMU(޽ߑZY݈bNY7tXO##,O>o:޾}O1R%JDRZQw(AeawDʉg'l&&i̋ %>Ӫ&Vt1< r˗tt8ҵ-)rF"#%#y-xI8 "d>[ZQ}C1'{?c/_v,[#n7tDo޿TTb JM^ձPFQV9UcO Rxrk<' @a5, y&39p>%jM#51$dY|e'#EQ1:1q;'Ap:ai`ee|¹95U9ʈ#??/}~ns}J}ͪE&XE"˙JiӰ? 67I SUTJ >}:civ݁@g6фБ\sgotuK0];PN1dkуJ͡O a<뵵Yxl@UhU%ݖ])~p^'0&cjĝQBcP6-fh efW(pȼfHbq28ArqH6FL'΢DF?DN^f(O/uѡL"sP8?y|0ZX*&tLWS״mGC> Tk"( 2c怿??%?:fDcćmu۶y|IYMC "cmYiOPiCZa3׹z~d|n̰FseAw)iе@YV乥)?"ň9`(knt:B\\0V ,ŪW| Cۡ38?<;z|dVcTGk n?cxm8[R cĐ:|QFG2t ySҷ=>^%푺nӥ 5-7dY2BɨҦ%xgƊF eA8` c|no6߽qZsGBH/2="ZS&Tٛ`DFiC|:<V|]/Bд'Xִw[~gnvrD'z[HKq6%.0FK}G5bss Z)ȩ`黑gWox7goƆ$[~"f̀6΃:81WdetE)a&,11)Ή V ",fhjyYc7Fr}L(V!bd*%jJk9a~B [Ϭ*X]ۏ ,c%jI&`5%Y31M`B,sHٌ/_]th}~Ï~C޽_U{ѴCt7MRKI7l[.M$>|dYzںkຖW@YV9*ާ!2%QR¹=Ö\ᰡiLXBf( O{;2LGx9>~lR`{b&ˊuZ4 OLcطab:8Iju4t-8EBOK%˄SO7oԶ\ryyQ"Hӥ:~LS0:qsi~7 d|JN[V<OW?Zwvl!=#;=e e1膖@Qj\t ܙ󠸽];d1 <ӰTdh)e20 FVugkQZk>dtpܱZ?#k:26-cM 84+V5wD1wMуՆQMqFax<2Ld1)"d(E>{ɭoz=ڈ3ga30paƑ*cKiXi'ā,S@߷dAF蛖"3LmcX`h;OUꫯBZ]+l<<=)fы5e&txOt8@Ҷ|~.k gSbN!]8TE1%˲)/)yT(y32| 1%F@)9nd)0y|HΐehLJ7;Lᒵ%ƈLc)mA͸Om@[ f^tnҊ2/2%#}|6/(lvPEt W . DWȨX6ǚ~FdB\Dc麎 T~ E|҂u Pd4ݝ6}O76\я!a&bbG/uё{poMg<<}Aȭam]48(k(ɏg M\Te>lG$%pHQ$4M=U;λe5 *Ĕj.En"'3Y 9ؒ d@FBod_dwmJ W  U|?cSUUJȎ!kiOG@5+O|VgsPFy34r`ȾO9=Ll ?eu@' )+Lu<3 6a}Z1E2}#!~8=F ⹮iMF*"x%Fj^ޮY+vNH LП")&Gx1N-c$WPBܔ-.!I T{H(tf ԙ9 }zν3V@<9 bzOf :^*y%a!Ɓ>" @ [j\گH&өiW~5 03g2R}|?7/h_{ؖbBcoM lIbڠS1 h9TH  TŒW>eZs8́~8]M̬@c”b002gVfL3-# @A E"+*,Yb?Q; cAq&r nraAyڡz58ASHU\P|71ۤ&Lcߠ@JA`>v 6' k'jSl#6ϸ^rSnsG .ߦo~e%DHH-%~qÑ&ru o2kvd #(mzͩʌp4eUZYJ YyQa so + >D~d^-隞Te6+i@p7$;̪H78@ hkǧt71;qSU3yB}Yɭf5i*heƐip>fN͑+ၶ I#4ǎo[TL`i̙491L'NM1/OX̗ kq Xr\"Fijy-zb "l"D6\no=m@f,k0NLmC[p1}.n,]8 #d1 >" Y;{\p!!ZFRXqécV-/W8ixƾN ]nq|{޼ mMV~y%o޾/>jv~3B(Yf̫o-X.RA 5rKs9bbD5TUE7s5$~r'&Vk֦mv\m3\Z&FTU rg( <,,<<`P%Q UI{"#b-ܙ :'"sZs~>]GTEL"$1YFq\p{O[ᛟ_j5Y3no^`hR;[TQW9.Y_3 8BuJТ,ȒJHp8p8H뷯n?{;|XV"OaG.KuV뚇2ݡc~rf=R>@U<{oUB*-Rtpf'!T )g~_KvGq P,W$sl ,b&⫝̸AASxh&8MJPZDJPE;"Ӕ,~j;̼9ԳXFu>(DиKJRaC0eE. HRFgcm\>2_w<;O9v3I56x,Kvx UN CO#,$V͂#UiW>̲dOY{#e`Ѭ8OJ>_LIr>uEMD3%idT XvGnnq&rˊHz@Ջq4ޝƑ, |<ጎh |"<_|"`DQEi›313q%~[q$EbQʯQ!A2.=e)T dDi"!M2TYM]pwԣg t-%2!_·ij%}OYfX8Cʹ}s^ eY~cG@\.1Fj`!Z2pGwg ֛8;dl"+cXoBIWR-bfs3elr"t8MHxId@h/6b'Q X+"I3l/EKGX1q<#x W rZ,yjl%Fޖek rFLL0lV$}T9Ȕr2aY˗ly٢A =?zus˩} Mܥ[?S)#i V8<i3vgM)0(bbjiTՒo|>l{LJ=ce/xR)6k{p`!<ܳ?bC%%Q*chH/O֏x BУ+OSxox5_x&. sϨG&=av<ꊺqjG#IR%sJ2sF1*D W4UdEO^S "\I̚; X9'<'O'C8; ?"%nҊu5#~-Y9L%)y] b,/(&$u,,#O6\](a=uuŚm:inS{G?C>#ѼfEd(RAAP G34a!M%^E=UĿC0X4n%e9gg%M כo_fy,y||x:0M1\8bM=i2޼z08O xEmX.nG<{'OoXطy Oėg]0(lLYld)eO0#o.-kjJ];pV\]?xﹻu*Hb+ࣉfbA2 1&)b똰"٬2ÖsߑetSJχ|h#Ry5soxŒ??ղ`XĿ I"%!U)UZһ3eґ)| 9w8DjHIeḀ̈̄PRU Yv`j=M!NcLzqwx RA{dJ ^%ImK^N-ZâΨ~αrETQ<sHEYwDDKRFYd,sK2j.(ǑX,\m6h7/_aӧO|ÏLxF[O>M}ylr5-r<=K%YA4W%8Ddfzf̙yK!m7$# K8l&^ɜL^_deg/k#3LJIߏ3_|Dx/g͢hʊ4h㩪"/жT8cgs*ryA,٬`gv=^8?{+,[4 t=]{9Z{భ"X%hZ޿?qY`mOYS`G&*ڳlLgiYI$cgz E0"dssGw,-zIϹ;RdU某%Zk>dlwwRs}]#~&MbBfl#v<'! FU q`ȧOI3q'D0陲,Ynָ84+-V g Cɂbp}|̨Gc-EKVWj㧋6/.Z cGY47W=hȹ?ky 4سX-X]՜ϱrY,*aF؀t (+x'K B8,Fy~ !"MxZ7xE8G,c#@rry*dIY_b͌!p dAȨ$ӨLUduQGXg$pr:qƐ,IqDC %6, :Fnٳ, k{iNo[i{64)-ZREe7.Kϟs}}13}{F u7ITyM]/X-R8aYJ$?9QzzDɂm\4xO*-IaNJFRdIAeНOm5bk X# o0v$U`yqz†0s6Wd!jjNm"TFu?Ym0z™?Ѷ';̘-X-% |?gnO^Zݽm@A%à1s4 Ղft`2HrIWWL60{ CwY 4YӍR$E)OTil6TUO[|HȲlATB>0M7,фf&n{QF-y`q:!̃!U)nq.p:ʼns{Bv3Tڔaҧ9^ Cam m'&,U%9?̖c{D[Gveeue4M}jTzFtˀ#m9t$\ >.Fy3{sOUҕd}CY^1ӱ%#0OJJt`pN#T N+iWxǞyxڰhTu+g eV$UY,%̧Ϗyk$5#tg\\7L*s?quuBWW*' =iP5C?m!qڞTi!)>Z|_[@if=!7 -Rp0q lj&\:\T󨱳iJ~O2𚪨cOhP`f-c'M$yei+,LV(!).D E )$xO C XV>d!"\ qxB2rI")u7M \4.L^>weՠ]n.m*yF&x9vxoLbBNJd2G'G2V$.kq81Ua~ـibFd|~Q)/)*6 #lV%Ӥ{ p=;a"IS͆s?J*L,F VJL%$YYbAl Hj@\6ؓ]R81eA!8=}g-3K)*zxb̉x<?bz,섴K{f3uAypBҬLGfe)iyy^22w#wO=BSMCt6k|phZ*gv-4;2Y!]7O[KJmK)4A ,%OcO< 8>ӏ-oC% >%uO#; Ì#!Vˊ4 LsL9z@ -M ci9U& k7WY a + Ak1֐t7by2,sFv@EER(,p2]g撔L `n1`&a B Id|>;IӜ6ZU4A% LF)cqg"gZ0#db sqdmߡUn=6j"zAG34CV1:N=)X[Fk1&.G֯_?'|w\lg9 Ҕmݪ;)KTYslDr}}Ct5n(@Rf3]RT Ra4McL;U*h;ES%:2^48~*ob"x7oq!!$_aA;ORP~njGW+TAqAJd+L YDf`HuT\ "_!ډ_rQ,4nЪ)Ƒ$H"xG8F ,7d?Cԅ$ sƩ$}MˡHkA9lu4OMA@dHQ UQgҗMmp0#/dE~9qx%.L<<|bF͂(@ݬx'h=qb]d]ėa##Eמ'!CpX)#|횲 IO41xyF \l6D6Lx.MM]q0x'/ GXц@'%k4-v8x[Dp4O'&XKN.*YG*k o=Ea&Ar7@?fpGN@JnoN'/],eCD$I'$0'fC c !ZT ɓ+^|~wzKU5xeY2#{L`MX<^|o^33?~$DqrVŲ$I9 5yr,8uuƙk Cw@%Pf%iVonnXǏ/@ބfQ"%Eβ*"`gMgX ǞfR><A =q~Df*X,e0^eY͛o.5Y3t=pf=s>`Bf, yY=ГEsCW,%$SFu $A2y "#1nss1LX Ni]'\ԟ^`] Q6 OtY,a775E_6+6 ݎ̻L37'|#ϣ6We4)9[vwGMᔣF Z!U`uSR6+ qhؕϲ,rl%$K % !<bAu#=kц\IHڑ#ݙzC/Y_Kr'/҄wϞݰr88*KȪ~gGARF8_`6VJeᗏ-im`ҐqVֱ{j#A&96 fʋIPώ2m_w>qw'?7?g4@7\KE^V̤gD`s& xS{B&DC:d%C?g0pl;HA[C, \_-0]!gz4^q"c%MxXJC>4m{ ~᷼wUhgd#djHCFYϩ iFQd&@ugVUS3l(ډq4Sq m)2e||{HH+ A_/>Ƣ0 !{2E RȨ; 4M՞][:×>gk)(ʄ\%qAx[!N nn7IUFzv^\ !"N/D%E-m{B/5cĩJXLƄ`9diI)ܓ?fBqb?L#eӐ'%{d嚠 E$-Fh8eyћ"#81DDU\`\~8b]6C48LRYO]T=NfHiMN(iqVW+ʲfz'#ڌ8gpl"E$ܾfv|-Å!RGڮԔJE>\?a8[j$û gQ?%ִ $؎adu"g %6VDo~SӞϴ݁Jb݊x q\y.$KJW{6=8TvGxrn=xŴmeo(%}2ϚZ٬EZґ ڃw4-Bx9#'fzfpmy8zN3^!NQ6tmOUÙg*oL|ݿe{'OT%|zFvwd8Xٓgps%Jlaj9..᷿Wq5c-Jk1*ϵ*x)+??U2)mfgd`pcV,Mém&/ɶo_aLE0H,8_9bu9RIT%Z4.»X-J,K6ƸPێy΢ TѶ-www$eαo~Ls89yɓ[믿;@hbF]m1]hD7RYZ4,Ω;M'8/jQ=,9'>Z(nik~󧟀$-׷lΗ 7L|brzRg{  KZlv1}%TZrƬY,i2PsHFǷ~պ>@baTP@IgEYҝ?#97\.<~Dt4SLtYłDn80 So{ ac 2Id4_:/1֓.IdB$.JGAYh)9weT˚C{ێ~ǯ~+Y_#TU)l@^YowmܽO5Z&Եt$\Y.|Ұq8n)!:zFcC?G|ͫWwYDy s!Ԋ-օ&J $"C4D%*b`QRbȆI"i S3س۞(˜<Әm&Gt'Zf"h%%cCQŜꊶz>c3fAIM(Aʼ KSnMyUi*-q!0MS 7_)|=cjH!Tt1*@HQIp~RDNDph k?k۬$0M;N8#QHNU4`}|sәR FT&y o*qKY gDL) 8ExW~‡ӷ]\l$f>ԃ?34W1 "Hn0Djb:% . B XyxKo1cO~gOc۴TS1a܄&kgxlVDk=wo`&4/@(>mЪO$I &tjO9Md4Uہ$KcJ$'M*383!eEZ{1QBi yjApu[N=ox3>s&|Ԋ13eA&|+}SOI7:{(5o }dR(S!dd圡wti1DHP*zvhywdUFնTEM kBAC1 H9HJFvQvwbaRkMQԌƓ3{ghapGv ZH%X.pE0N! z#J:bO=.F0E}#^zuG =pl >D*~6~جP RcExܙ|-%8 B8<J~"/{ C5iAR̪ 8xRK9"o8o0PAF~82 ;d)?%'t]ԭgyɻwW_" ֗+,8i$f$ij)wfVI4Zi`QV83PkthOT["[YR(k YQ1_mm@˚w{ø%DSVY0 )vdI쬣"zSsDɳ-vd6}GϹ:p jD`9ny2by|J&g쁼\;$ T"釖nl"D 85C7&9>D0RH) Ҥ"LƓEFH9"_Aėu@fD{tQ_ͷ$DNwg(.iCO7N-TPK\0ޡtL(s=(^P_xv{DJ"PE8u,s3X;`HQ1> &2C' DJv9Z޾__hZa?_\p#ϫ%.~d=B=۞ar ÄRH`96F$/Ax)2Yčoس %&RQb}uɇw1!$ DЬ"d7"H*lbZ 8Dx8B ‡We켧, z%1尳9eJdiJ*cJC3bm`-}g9`^ٟ9෿W~󏿥,"rFYK?f}իW;fe^}䊍 L(~DT/;nPIA~Wߐ5J_*L4IȳHXΗs *SӀT"ҘI|juK1Cs}aczʼ@Gn c?lrЂ;Ḡ<߼LGvF?#]3DRHM?M)0DК,KHU4&iݽbo٬<'_!ˢ<`,֍8rhw̕c85[.s~`ݡj?$Ms+ڦaa4>oGîw(!gsʲ({МZv!Wo*c}yAE=gQɓYJBTgUFeZy@%R#B:XC&wEȍ|0$JiRQ"!Yl ْnqq~jqMl#FN:2g2|ʛ7曷)ulIÆoxESZ"ʼn]Xc#e`FR/c:)YdIS>O"aVi>c4(PyWŵ2yKpGq&$~ژ*n{<g.P3#0$ YIU)E Iqޠe@)#i&*۾L3V5W?ryfvG_ǿ I*6z1gs||s9 "!<+<onO5+'8ƶD ,wdٚ|I8ZZ"AiFȋrqnÇ\\\UywXL83F( J=3+5ZVHY3_pSDؓ}B#U܀ c(g=f IDAT-6Fyd eQ|(ŊY9t[R$?u}rҏ)&D@Zk%!@ÆgƱ9B8ݓрmRdIU1H>g ɜʂLh&#irVhq)r  $AܙY4y~bݺ$Ip@@ :0quuE=[rj{N~8QT8Rq`?HKap{K>yFY%9}8`ƑtթYMxSƱnP ΍=sz/h%Jfˢt8M=ŲB3*K9#L8!9u^ȨH3 ]w'KJ;6x(Nhv ddgKN?YJӝbDTq~@\αn $n"V$sDVNy V 9st 4]RZHa}|k@PO\ ˛k޽~`:h8#f|X MA}<ܽ ųϱC}M#Ep|^(Dz k f cO۵TU䦨4e} Ҵ93H)iN{h=;Nj~F]_wQwG|Ƌbu>lm g^2_cGOKj44M:_J!ZBPR+4aL8'9F Ōqa*SC<|c;ۉGyVIZQ"瓛./+v[ËnXp>Do+Tx}ł_RU|Yealx|qC}㾣@jjR])yzkZ6{)cZ)Ep>0oFu] Q{}b\4=ÀV j|yRq8??g\|B4m*h/k޿~ɇw$Y8}ǧoC^hƩapG">XyN͞Iy*gO>ytb9 =On v`(7h1X{Ϟ__~{=ITE1=>8(#xTdn)%(k\p|xxAj՛ߓD΢s}?Fb2۴](%!,rZi$UP-RRUq{x覆H@:&q > hU='IS^|wG{ʪ`;H=&M$ A`#H;S:_.8'5K,0N5X`{ři3Xyk6&dYH)SEh2@ 02fIA2\ >Z'9d<+R]k-WX~0Zߐe I5AE;k\hg.!H~He$O'Ǫv ) I'q`FYK$stx9&hNァkz4/hp;bIU/94=ifvR/iyPK֋+LXl6?,b,~wE{U]lczn]`&j޼}GdT !C%rAU B!xi'\dEe\\_We8iŢxϞ`}qſwk...βo՚$S䚾;op#8`H,z*ǽ7<x~ml^)NԋEQ9vwaR+s-z6Ñ, $gs3u Q$HmhjY4fxT4W^OݻH)Hj!B{= qubN͉VBx"X]pśKSLJ}V*\kq4 G蹺݆aG]W|f9X,'f3gQ/G%?%%cvvhP)(/fEjG qĺgܱ\X+w\]Rg~oibpZ~Li"OY}mw2(3i`޾=["v8# RD q:+"kv{ʼ@' kN` Bal6ŋuQ}Z!K_q# ; ryB~b5IA\K)xDdeB?EPP L། b4h-rcDOhR<Ւۿ&%+{Lr01 Jl$ILOf?ZyaY>cs)77,KٌL{@?vC7mɳDyd3zcumiI܀yŬ.g37!D%v(Κ8o.*zipX׬/n^/IB YF3haO:!K42T'pjCL@uxiq8nf`L4%XZel` @@'&$Ѹ'XHR=CpBX-f,sۖa4$I&2-ʲ#%WW(kBҎG ]0G\^-@Xarap ٨a'SK5Sl6ú- 3 (yN#HIeeA1e=t%%C7O_-=Gsd' n2_wlR$i)3 4 |a /$E]P)s'!O%iaf}uc} 22GEӛ'8gcx I%:Α]b("'8RƇ͆bAQ4v0ȘF-$Lpy}ED{a^iPUg!Shv\ǁ~GQ<}~B֫4FႄnɊ<.Xs . g$*cUHZ ֍B=u'# 5Dj,ÏԣaMӜ$9hA` A*D(h&ѣ$RF?ILp|`P#',r+*Ønw_}l1*\H%B mC,cV{|S%ӆv,S$縠UQ>3xr ~K8=ISƮAjr9N z GJТtVB/4ΐ+bpz YCQ& δfdN . !#!M-"\p*jlԅ0ʑ4\\\"50 Ftc{ KvX/eRвZǡyZ [PəapQ;2'|X, ܴdD^1BzJs"T8V{= |S=a@Qf,9MD`ҸJ$c&4o#ghj-Aڡa0X֟/ `Xckks(0t-k~tKU.9zc'"&31~AR,M cITԪ8g%L傺h{i xVx& Q3_-t5#Q뜤DAၛKɬHͣkGlq"WslNi %0Y\WyT6~$!/X^?&o?p.*ӂGW~$K\_=!Cf1SL2HsdPyn`Oˌ,IcQ r]\o;?s7bߺ  e9EQqtl@e1],YQ !?t?2ޛZuckjqo2KR,$ 4tOa7 e Id2[dԧժ׬Ԙ| :pC $YM^-҄ ٥ 4<~ϑUsj8;iFIf*fΦ\~!-1t]O\p6=Mǫ<@Tq3* !*;|7 }qͻs8g^~vw=_%QF~ noomr~+G[TEB8;568>qy,E|948uYXnVl[&%ҷ{ϲD7Jzŧ/_p}y'Ӏ2#ww+FnpvrM{~c*0s80Yt$E >J\m OdQ`̊4v9\Ӏ!;K9LrܵYs)nƹST ӈ&^X.L;;vbFsϞjgFZg/5UU@Q8NH!(LIYa& "a'޿{}VZVġ<=swx"JkB9C@D)Q(k<#}?'@v^af$O,g|O躖a(@l6&YA`F+VnGc;VՆ?gMo/ mrqAQz|H t L/$ӄɷ|s-8K2UOyo#MmخW/j^-0N) P`gI-j`뜫O9'>_S%'UU,A`LAg*IGE<RSo,-U}CQ37-> %&tV%W7;2di;B -em\䜚DQx%ZnZIɧ_0/Yb!&8?569QïfxHlLk2ٿbyr;.\UL@"OӠY.V3^zr^w6)CӘR(>!BocbC+Zh8v=cϱt3DI$(*0tsO?/zpwsy%rvawdYk_ M۰^4kʲwCeoP,8o-y6GHJҮZ4@Λ+%MxGIե` nKp:=2N=ww'dF*Y6*s_NqT:Z%4ڤ". 4Lt>R0tGE[V-7ï~rSqYB]D`?& <OkmT))tF%F(rq1`UQYeI]['d 3c4ݑcW-X]x gJMӜ r9))spe@Ĉw Ci' yQ5iL0]$UQdzϋ3Zv99Z^e i7aONyE(}v<;\V" Gqz!=R9D\D77W~5`FToX_lq\]]^/ldYj`GopJJeX;s:lzyd (#u}X-/֜ t搂iǙ ~xv}ũm#vjYÐZyfhe}њ8O,5WW-;$78$lprQV)*IvX%ZWg4B&c#nOEw?<4*\l6gϞQ ƩigK;4H)W4m=OdfkBH*# )9֗X<ډ*7df{Q3<ߠ8ZRҳm[R:7i -(#U %go"1ƈLVS777s`2'v>qVvΦ8 XѪ"soWm`=HAZ3NyQ03ZkբgG皠 UσW_9?CG&yz:# l/[޼{~iXg$U)t0 >]T8+Z.x'G:DޗMZHtv ,  |QFt -dJp9W*4ya12\qlvO()Y// dj%CQ*' %@n =G ,2f%~)<=~<:W(.wR42J4C*p{}~vs?~S2t&ջfs0iHȪ`G_psDS9 O(3IL/ߪZ`} ;_cɔˋ\CAkqaCۛ5mDlWW̯ǎo5o|*kqtE8o;&^,!GjAs:ĺd8X-x-v'یEi'̱ ݇9I'5NCu #ÁG|J JAK<&ea6(>Wg(VB(5%M\_9M{0}D<7"A65Zk|L !+M?>)]o?෿G^h'6)r)^,uŽzD*zfFKt!ɋ,EO?i g.\ZV,*9XgliE)!҄ K&Se8|zcWcgբ†!"mxJzUdP4,,7^Mw?^A") &޾}G6%w */<ŬbaYp:<@srzRj{/)Ϯ1&ztĎՆqpZd{s{޼YDAIF85GBqq)ȋ 'ur,jJ;cd*R[4Ϯo !Si AyT:3dMq軎BgҰ\sv@ ut:v鲷*A9=nBtA2:j5TidwmJ"bĻ:?!"U<gܠty})o{H+B*z#x%/VO'=Q-닒~c,OQR_|ų/xp(%3SCB>ks%z.mn>k[v {* 4I=W(JMY&wQ,VK Gqf=ZQ:3FTvMcOU(+rUs88dr'4w=V Ð`{'!BdzܔM|$Rj"R%OL,;fJ;u&*A Lg['L,Ѝ-ye8Lnй… τw)">wG"ne#/EBPă 'EnrI5RdTLkb44̙-.Cs oVh#㸢kՒo)KC&%9 k,nd툟gƉԏt8)69a#/aLpbgAKw SBɊzi xE$0.hȜfXY>|(QPZYgJ Fue* SZh!ե%~Rf, \3t T ǢYVu\W ]ḧ94Lk:,2˒8aD_ $Wn}?0#E2e,Wihcg6 !"bLGMCt ,D]p.Y˪b*݇,4{#&ͩ%̉Y_`}$ kg'Ym+푮zygD?v0>CfS"߱\~jsqQ9a熦; NUœ?cQ_Y]R!v %j~גc33) c:ƻ;XX ¤Y$fps~sRKp6P1YN"EJ%] r#.d#os<,O,mk&#U 4023];k]bÇxzeQN0& 6u)TUvQJ`rmeJ! |{GzQ"d2 H𒼨}zwh0O ]z{4ziE&OCo~;rt4Yd qd. 65uA=a?Ć=7kJ GqO7RnZ =B^U\\ݰկ~ٮl߳?,+q~/'^iO{8Qm$HsB+k,W:gLbgu 7s.q8_U)HJI ,s@;>8/.VC. ,cHNh!0>%BL?#ٙˆeO0#%QJnO6(!ekD&(x:q&u?)%7׷-?qyuK[Osp\\>?G~oܿ#GQtCoy&U'~EP)i1BY76qb+/Q\ )٦e:u$1wD~rjK8"Y&?^Jq54M) LD#+@uwZ&85!,+213B&hgnOcs覠&+9 alY7<{> +L**A6'8, !x2=OlBĨ ;ϞW'l6`"Tl#ddF8 2 :6,ABʀ)"BL%75dpl QH*#Jw!Fnw~ֲ(@f#c$JzN :SS$&;X%ĘM?r_OO80{}Tݷ?М:>%eY/Q2oL>QMQ"+ԥb,]04eH*]ДQ:Pk! ~b|,v$"(v{qv O%1h] )Bc!5Bsc6Mws_002 Y.zZc`yx|R y ݱZP@ep#e4cD+޽}nl+Pph͟r9mV[d"&sIӶuML)aJ:?gZP Ӊ3OGyw|U&!m7Zy<=0H܊F ?+\,1X֫K*uw{͑(zRh4 \a]w%8rT:*m`>×%vvG1ޒp)ji B0e%rT lAR,{N]8:bW'bs+5woq4J 5=%u :12n0y"\Օ$&=SDp\t! z)ё"Us(Q͕F) bT@e1Xf]2G(Ľ!U"Sfv˩r|9n@H&!39ZȴAZ[֫ vm MIE% q:ήҏzɧ/,7كnop08%EN?N,uMe$0ssArAw)4'_p]'"hcpTV0TN8YKߐEȤCP. Z'USI)8 EVT4RI3fv7 bf DqsV! +Vv@E'Mjj `O=Þen(Mr|xׯ_mI )?Jaff^DyxzCϾv-R L?~h?KW7~R+jrA8 :)EC`Q/F11)Ɨ왧m$Bw i! 1rH̼#vi!=TE2cdf]e9Œ#o>e&r?{gܳ٬"no^>_ 4Ea<0&h*ubHuNt)) 9o#Sx_2ix4 4uX QSJ*plX`%4 (zfƎoaKt>ٮ" [NǞ7=j˟_EYS/̶@f,WX7XlkS4Z]ftk#7_rSRhھvږ2OBђ48`퀒adyhRUIr ΁L Q0 !T 5oߡd-i=rES!ĘitD?[usF# Sɴ*dkMŢ@ Y,dd; IDATf;23APIӉ4b! &xSUs^ 2>鍻>%jp8tS"uvPI+BS| t~qTX?8u-]2Z՘|M@1ϖ.-ŖYj O^""cdp8rx1 =f0\/ Y_y<40-JNxOLKy`^ai@]*aFeKnLE-&tmJTyYB0 6 i\\ӟ~O{:PT%*ycH0y4dihe8fgs3(j>HsV0|6<|mh$'kgU]yMG 'dJ%dU%z'"۱ZUD؟JU=Ef0bٰX|_&:~TD9\)g7 he9! /ML֟%vsr9=[{Loh'N/n-%&+?8!"Sg_}%ڀd:`;=Em[|x8c)n~bW$;|Քu8Ld&'x mj${ MGwn!)idU LStc Aw~uϱT9yq~DIQn"˒Rf:%9lt:PN#Jۙ̉^<{vQsww<:>}RtJ!E1#N`.+.aܰ@7ylSc$\arJΜMMsQMLa$Fic"WWKL@wO#BA^f110 v9J"|8bu !%$b) :לN'-5)p86'GdYI{&=dfdVuh9CbpC[~D0NsHTY"C O]ŵ^WhL <"̞]qu96 2,婢8Y?y)L'ggl;-<],3FkȔKЁ[̉>K󖌎hLO_:Îq\jB&Z-s4}ȳyPdJnhS)e:Ƕ{HYHω`ڔ, = }O)ckԺ4E^d+UZ!?V!ATQ dzԛkz;"P]wxƁnb-ݩɬ蒇կ7{դ#>&s)d:%gS<솼t,*.x,o?p<`Z7_#y^svbkGLj*Om>]J"Ue=>&F];(u 1zBDljq8 r2Aa]d٠^}BUTs;Ļk.\71 ~ۏY\RP֤5z1o/jηfd%HY(h{5UQ2+B4¹ǔ<}"]CLbQ\%g'eCTAᰥ :/yW$5fwh)y3s$?rww `0#{RtDB}H(I^'9k:5_%P('_eݚ,imͫ4p@^ BGʺ4Xk؛Y3EIx<"PL?@H-wX.;;R *\`awܲX,cн!rV>M[CjǁLg{s.ίzEܱ߱b~Ғ}zڶlIrh,;(v}`C/r!9"2U;˗ݎmtAL.HM'RlIDA۵"= P%^OS+ H J DBrw9j~Co,'ϸ~!sQJxCUfRE>30:9Sl6gp(Sb>޲~0D*D4Haú@38bBd"njRZGvBK ZbAd3fɊ<ٟҍCpE A:}@KQॡ(N`T9p>M//e4{|  '@`,kh ~_1ݷm[25Ti>+ʬូx23cLBٮ1W/Y5߼@&"g:gL9 *wL?r ,> ex`aTDG(Sd d, v-Kr1Yw;N;Zt 5Qt >@z E V+aOoFEJ%)9qϹ|f` z":9ݡC,Yay9 |qH(i2qp̚Be)>Yױ=*,GN5ZL!Bkɾݳ9lO19s:CtE^x("ՙ=CN܃'O3 f{3xP@=9Cfc@qMcY =EV ڎ G ˆaL \k#CetՒ|8>cXȻk/~?(YL"/+GfBSꜧKfq3r2m$*9oy?}s.//O"8W'bO[YFcaLes!6O$U1=U1Yol[; h88ky5ƀ,6l =$Jh&s "=h2+=wږ5߲?g7ߥ}{{vei'-yuI]Ar5rvc8; FoxwbZWL`lZ]'ENs9q8g> ѐBkJRb !ihDcQL&@!⊢O\s8X!7K.^e`yzn(RxIl GM0[^paNH/*OYVlᛯag`&?-Y"V8Jea>\-m7[$KvgQ:(sf摷RlNj7: ;~{7xkΞ膞axVUsY<Í,ø9_ZqH.`J)PEN!:k.18޳|sy3vہv0L sd1!lJl[ھǻtU"J8EkBDjRSW f. 3zBNtFLyF߶n[/.Y??if;5>:mحy /gV+|tCˡݳ;ҧKw7%zꂻۏl6;bGkH֢3zcMԖԲƧ~k=&7bS hHp~''̌WD?׿;nf/gl .% {fO)3~H2 GյŚQ"ELuO 2D΃rH:0Z024 } F#!/c=YM>t=aSD݌IɳkSñ!DJQf`0#: #BU@5zQEb:+ ?0?rs!n`g?f]af5c`ZTȬ"p`Ȧ)cr,'?l; SCc0I`{zvlP")$)ҁ%Whn:={B G6l' #_}}X&#jRқ4 劮5a4Le ݮ9Þg/TE\]1!MB'E7{̸a6@fk;l͚Wq'ۿ "^-u㘦=Œʱa2!?MŒ pwC Z buVKF6}OI5=*XA ,<~<@uirScHRMcNOM'Nדަ$UL -]?CD]EbzœKv=UU| $:3k~_[ˊs*&uN]eNoz ;8;"%אSzsd4 ʢ|uFI=FSrϞddv'x0 ( V; gidcDp\?7L*noZsu6H/-yCg{8"J߳s긪2pr4fI8&[UZ e\.+X]$' 8;Tf2)͗o8?0mZ.pzqȢ9[X7 &`M@ɒw3ggTeN?0'xz'2.K]AɴFg,>̉艨}7 #yS;R}jgi^`2i֟\iP5Y |6g4M{e!er(7 *56InO<'$VS|Y0]4L\'pHwK] #/%el0@+ШL{\?ռ|W43lvz$&uanJLYt%-2zmP9$M) `PBs{ '>`d-O^dUjB8zm }nR wm:5 ,D (i(71M| "*c<s ,}l(rq.(r@e%JDq]jY U=^|o??]S[qGLgQP{6s>Y*t HsB}k( 5O?QP_1iJ__\~P1ȤdyG:txt3(n޽G9.:>oya0Z92bAQdw1`G,c'+3gMwoD.A]U~Ζ y|艡pK/fd C`;j.KV%Eq@7xi1m"|z8R)Zf(! ֱk[Y]ļ bX!c83Ψ k-9en!Y˚I=ڀstJn,QR y1 *P@hڕୣ!t) Ӥ7"y.6.qd2Ei=ۏ\<&$`(u5eަltPUCƅfSN')ysű^0R :LBgo荥_Tj p(!~axt&<[u&Y&F)M9AglʹK0\%])v#dteYAM(k>dIX% $׿@44M "е{5p  c?I:K|J؃Gɂ*OgO1i*㞶[dLJla&Λ~h7G*B$"DEK52ycl ޵d:KCSfIw=c?b:YTSgth;5!Z"-1XIYQ |bm-,Wd yނ g,[00khQJҘq9EpxwU옞2*̦sbd;lJ1"DoX7DH]QWt#Ϧ5bHL%O/)g4x"RO*'3o~O|?+~ū  st^v=F):aތ!EkPy)-vdi\*YV\-)򊛛޶L*ːB"'B$Qa9gRLn%>ʉ}8KjmbƎ ȴ9W ۜ7_ǹ5~S 2kD^)Y][;x>X=_vu=ZjsBalp!S%JOo;a`緩i'ӡ3ʹ}3eY!zrqL]ØA|vKE]VtyF9rHndZ H"<1*9-CHgHj%SU.()Ri,Tb]x-6T=} x"_з3 }? #=OΙ4%Atg8c:Riff@ሒu~<w2$Q8za<^Dea;"wL 2Cg'qHIF"?UHq(t419O()`:-iq@KEYm-GIxkЍ,KQ,h<:euWXclK`,ݘI-wX, 1M՜t(S|:&<|f{'*H1$qkAkiEi}|ǟhۖ/psrUt@Gk"%gg_POg'/s~\q~~w}H._?YwL]NH!! !Ď)FiMUg}dpv4chDž\?!DM?!p~O]~jJMbjޭw|}s✟?K֊ 8[Nz~ٛC{$HQf18'$A`Ri>ThT:޽>2γ;Ueo荡6UbP"(&/`:eBA$C呀8Ke2Ǻhڈ@uZ֛;;Рi`3GgeLE!KE GbpHQR 'Zg HgaTLKq. jRR?$'x9@ ߟ[#RSuɲgSVqȐa 'Fጡ<ܷT '%8{޽իWh}lr=E"?W( rK__B޿~O^(\tޣXpH9H'r{wÚQ?t>~W43~o~֛{DO^%usr@([l`ƞ*2T,3JE^b9,N:<65Ӓ2Mt\8ӃHBdyV,Ot{S(K 77/Fٵ&77t# 1fK" $:g`0; ejy=f[)ÚfL˜;H* >v=G9@ig (ԐpOȊݯ8 yY"(̧Zf<Fgط{Դwn06HϮj=mA%<E*X[@)S|b;,M>{sl%U=I;@(=mw@HUg,az qLX*˳9{[Ufz6,`pt94~`+6h*2O1%{'B((,黑vЏ鯮J49 LJ~\STsǷ Y#(isf3dY 7f-c` J\,HNRr%DxEʙNATjJy_Ìn=?A+sCl iSS2#FmFqjaC2O5ObS dVf2=ճkӏ;iiʊW3<:n(JVggϼ;|O?Ťʸrn2I", Zy:$`u~l /s֏!=N@Sf8U~HQ#'˚1(w2<(3 Z!E{O%L3mj./̧%v7o6<MFYU y1HVg,sr-l@˓f,UI4-U xIʧU8ҌȐucp^&8rHp4Rl#ާ1IO{<_*=.(jJݜʈ dyoPiTY=ho/~)eG!D*|lZ1o&-%JUy%1z{WOx߽}Ǘ_'Ulo޾aiIp"Ũs16h1E) ,UOYά)4ft}ֿe=u=LeI3) fݳߜ"''X/(zBaJ&cT,}I3o1~ͷn/_2!|N\fqNX]Pg \\&Bm Jh-#.X~Cj"k?(3B}GD%qp%zXL5h*v1=|o(CL|s񈵩Fѡepvo8mR9z! 拚;Fۦ& 8b=D)F !SU۶UU$ Q5eQ!S ׄRI: MKYe`R=2ɥӶ-=:L9./g/_G#G%#hZX.$vpL ?Ҭ.Ws?2W=xS&)$QH.@Ĺ@ q> %N͘@g<(VJ'3OkI}TWZKx3cBKV&* uL `Hs˓h[$VŢ9եڡ?zƾ7Gڮ+v#1H1Iz:K1bLBLRUV!DfX)uE7R8Shs E<@1όlvkfP#\իkh-ZM=ӚTs8&!>6MCCz>kV'޽ B7>_,;42U/Vힲ 4MMY ]w*rg 秊|L>0g,(TˢPA3S݆z;mR8U#]b=TՔ͆f`8R9ͼw?w<{??g\s.\pmy[ft]|X.f[IIb:c4H`a} -wSOX [9Hj)*OVq,RKT8=.(Q0ڂ??y 77@YLgWAP9 \>f~J LJΘNg È)s|QG_"b3%]Hء9˳9g߬3:&ےeﹹ}rvh)Ewd`Rē/oqzIj f5Pq#uO^gO/ѢaOQ$C;2f; `).W9u=qf !4qqd:t#x@}~ FtL2)9K#N6luRd@Յk!ghtLV,ka xCggHoE0ֲ?P 6H Cw0XR)%1˴pe=hFE "Y*=(+drw:?V8,SX/[h114nx|S~u .!ͧޛXiZ߬~XMGĉe* (j p a0!HHBR>Qe<]޻5vڿcCr&er7{{9jPg4JN"T (*qϡ|YR/~^D ʇ`,y\8:r)9:8=@rhI&ORpEy:f%MYS^c'p;ZZ/+rQMTЧ)ip}G^|vf᠞C{@xGݔ|S>޿a4^8(Ҍn4! R0 J *EipJվ0n,ffE*ӎe!y⒲oS#v4UEQdLOirvɢZ*B!S&f$]FZg3 & ΂Y:i! a=LQȾ4H| 1&՜muz9RJF%RV!SRÌ5lh))x{R "W׿oo>@HҼL#iaa'UIiN,f%y&tBӔ,#? ~S~a~s=oo1₟Oh߽|͛7/Y,+|<1!~y}n7$0hڣ9uN U4KKY<~n0 gU5(Uc4M oD]^c4~xZ|F^圞-p!]_c$Kkz1˯mB^˟sh{Cg >}p5!cE 8-l0ȾzJ2IvYMJ Otg4zf GC_"8dn"L%!ˡ3vdw%$ Jh%X.Y]`?GH/buCc]-opvDTș5oޒU)Y2!^O Îðnт,&u0!hB)Htx3%)@5kv;#fO!rB<\^^pvv4MGD߷EAP%%c?0/ NNP*Z-GUSD1xvp܌GUs EN[g{ .k<[V; >y}yrɛׯ4~9< RIPڲ?lxmȧD$di͡[amԏD*H E`Hh fȒ`gVaáy#CZ;e'\ 7qlV 14Nt}L/yyE˄2Y,N8{ud:  x Zd8(O($8?jFUU?s0 ] 7PPUerURE\T|~k I5%=yN0>x0A2)!`@pqq9ՆulJ{]o.-ny˳sv3>3DxS40n6+lhsIE"V 2 eC> 8|QzC;XoI(0g`v<0uDHbٯ8?&"q֛LxLq fAwǖ)˒qjbG@[P?4s98*p1,/([&{q*Vp>#Hص&g/3<so*r>)o$=~ E@Q,}y?}{{ƾi;R azd4yY%qj.&oY}|!VcOY֤iNUx/(!dYJ/bbِ~#e;lӂF F*ahCJU7z 8FPqΏL4ytI! ~|V@c|{lIf<}vF^Lpq4M_ m)J)c4nP7*nvɲ(%5fl9k~/'?9A(Yf.'O4!K(Ҹ!c_%OSGSJ&| c E3էѬ Vw]d)R&ж-Mu;)g˂ˆ4niH@F|hw{R5'M+/9?{,NxǛ׬טg ͢,#ˢmLB\\p8b1BntC8r ђ,%yXmV^BptmO!JMOC3hS4%E fhF؁\ $2Q$:1m,R>E "e6` IDATo٬;v{@~K.IӜɍTUAߴT ަ7+uMY.:xa`[Yc8rm=IA?$ TPAkp:^p'H)Y.ud)JQ Y,.PĵQHbd,RTUX!u3ym6PX18N'St#U @*EB87XeCq8ȋc:L1M<~`7_U9"V-g)SMI=Jz@)> ~/k?=Nz_&@Ye_P7)߾|o5:a踼hk->9LE(}drtC?1 h) h=l[±#.$blă~^d8g8;ivz҂~c="z$痒vܠH25$ֺŕ(tBF>eٓe%}sۘq>ڠ^,3꺌eTk#'iF2>&>oƸ6F6b"b4Ҋ@ SYtNS7|~vq!MrSU\$FȘ̞oQbB D 9'(rAs%Q)wG`eԫkQ5JDIeL(0ǎ#M:C <*az 7"p<p#$Ȅc-''g=$+ 40 #{[HR)Rdcû@Uϐ2e8r{{nȲ,HSþCk@֐9Z'vajbISPzþG{yELθ D9:6^ HD8&L7QV6`.ɒrIwutJYeteQ_1]w@E[5 ޒ9 ݈8U<@1hwZ3z9f Zgdy.KNNN(˜~=8** 0E`yes6ZIry:˛ Cvi9Yv`8g%<&|L$ӱAb#`ؒZL6tD]hRBpgS|R"L>#_OqxR:5̛<;P2'bd "Bm({Zi#3qIҼBD%}7&H4+:6/FK99Xryy N\>"2^~4eEYL0|/r~dLJg&dEȖ[G!8'Ȋ"*\Mc臖DKeউTiBݛ O}R <ݲ^iOH[vGp%{Vw䥧8%nvXjlvYTI*Hh֘l $I:fD] "s}| 5&9eUfuKo qD*MYT X,yjvO5eLi.!U 9a]7Dd!;TH2M=݆iu";D IMd{~٬D`Vna⃡3vĺ' [&!KǛ[:OZT\^>ݵ-fI[ Hg9IUAS"Ӂ"_@hgYyҎLp$R("B\C|^s8Q)W4Hw}%?jwRtQ\̱66e g{4RNfQ{hǨMe,؋9x,fUl&ܚQ!<3#ƀSΚ9愲hb  Yc@\puqɓGSc>TUE^+,*E8Xp}#dJ,JqPuZf<+ BmxޢUJQՀm[uiF.ޅ }G?o~ǿ`)2A'E3|S94v(M}|dܽ٧Ϲ_5xtɒ嫯99MX.$IBY_;NO5Ōټg4ßN5S*{f6{~vG]*3KV8*'?>ƌ'smG0ZҚ'?/+-@T$I"INOӶ৿wJQf=wl_c]#pF6H; }rJsH FcD DQJNL<:=2:R)ډw7\\\1OL4Eѣ'M8Ag =m`,MdI }4$# %$X?;BKԱ`bǺ1.&T@@uBX,RN#=]rf`b](|gݭ=a?Vqp BCcvHp.0q}<5h"Hq$',Ub&4ŁsߩgD`uPL̈́R{H&.4Nfwc~kۘȐ(irJBDM0v]`Co'ʲDOdEζ=WVX+gyR[g/\7KzIwYr++b ̐"#ъ$hݷÄIUFzLztF{^\#e ճ cuV MP4J n8MTUF$06A@X,x; TEӧπ`Kmw uSQ4%= y샧.8+Ng S$5wknnsݱ1颈a "?5Fn=~iRҷ\\y|ge0t}O{o tC4YZ1777X(>SR@HHT4͜o+)ʌir{pݱJz HG32 })Gf  IT!4mٲ_~RBP룾0۷e@49www|_w!?݁fl61^xZG^JN,Y^Q yV I07alO|,:8oy|v¬9᝙aOSF~EcLc6/AXT4H<֠z4 @ Q;!I_Sp00Nqv<'m !U\,9?d`[B~`Q(FV"صoFV8ɬɓL{vfK6?;z)*"!Ф*(f5y6ɳK8"afr|̼| HPd `Y=x'ڑ $2 嫯!/,g p@)C"nj1*ktZqv;XOl10]a#KO6gh[$HUF\\[3"VMU`ET>@Y6$:.diƬW^r#'L *P9BL?1u#<.Hw[0}3y"P:gkm[k+b'؀N#G+@HH!*E2V|@yNX8:,'Qw<"Կ7H0y=Ŀ_Ou2`X4yIUG|5830-}0y%x̟_lVsuu~ _}uST~Svɓ4..'NFC,f<<$rCDGnAH 30qrrʼ }AKϖ<1 :K%e Ae,;noo9?7 FGM!+Kj̢T$?gUCQ4l̎c0c$ӳyQ$)]?ѵk޽ӧ ~o_}X!#P'Oʑ xPLѪ# di*)˜ʸ~~e6KELHI>>a`ELUQ%dcEG:0jXew8A,OC暛[36_.@'?9/-N`XX.h:軎Ddԋ(˜HQBáK?k]Q39I|?o6g~Z3g|~0)iI1Ơ*4N*{Bnm@h ta;EɌiq-$IԌHpT8$"ɀ@(J[ E!X g/>-~d:N/ zK?}ċszvvew0 e]ł =8t3&gIe1ELLB*UgщBIa*J>v7v,5vCiK9$KTtiy }O,A6„<|%I*LQHP*֭1j:͒[ӹ=0;FR4U3lay|5gn8#qE!gpMۮS'tBHg5ZASlVlnc ,gg Zqhwxo2HŢ.Ahr6;ek/Yo 0DdS%YޠҔ!,e,pvDg ZfBX ]Om{ e]1E~XGNjkL7.Rggh8-޼vRoV|xK?cOc뚪ilw/Gi|vݶEi*$#K4ТEvT&$IFYԨ4as;BkRV9Zk6]$KA,A+fEa 7ɬ*kV=޼e>o0l*u8"ˏVISTy-.T ¯z5{8"Sl7{dɳvױvx8 o03vGł~K׷̚y%9J9iN8K$ݶe g$iɓMWל.O&EGL-rFxuݻH\0b1?ߵlV[S.]Ŗ( S3Jn>R d,w>@a@l }K!F WX;Q"֢C^h)Q$㏞<ۻkać@yleO)金yU<v[~z"!݀7VOmo-Rz\wh) ݈ 4e%iR![B(aAFK^Un`ݮPQ541M#./0͓d%Ax?2BeQ$:.!C$MiZh' 8$͹lJnTPippSN,?T O !Le)ʘgs"F>^~5wlw瘢*aa{ó]wp/|ꆾz~hq8ҴggdIWQdmejƁ4QO̖3>p[N8=O*~-e!Zg6REy'Q{MT !?#n?(ItM]T\-[z7EOc\>91'Mr|imό"͊| kج^o{XA^$I9t#Iy44aCv0qbqϲ`1㝥kwT"`ᇡc˸hR>P$:%4a$va*Oh>|.3i8,P0|4Ae-cZ f}n@A1L i& A 0~" r8x }c7"%$ynl@vEF'zݼhB8JA\LS~`q*yk=mxxرx#nXd*p 6‡O$NߏT)NE8]CgF`R ?qǮ{%Ȅt! tC,vu"ϪiO8[\hmy$x;=s:vQ\(Q9Ya(rqqqs$B IFJͫWW|L[GoYVFR0hFI|jM5/K0IFv$Q: ,.R$YAW_;^~r>m{#ADhJTES-΅sEIKq'~L|>gZ̸p{GO~0hqCO@}8a]O>T\Ў#ٜ _Ɏ9g\|w(%Uj4OhMfsBԚnh҂rRbFC]{<;PP-J)Ӝj#]gI<-00:%Smþ;m951`bXJJ|ʴ}C1Fߩn9;bAM8u'ty>Ҟz924Eq'I9ЂȰvOL͜[\/Y/oF?!17%MS>~/#3~NRVn0XH`c,s1{t:e4-wHVx^-$\8!u`{es#>sQ"{$IJRJ޾S=U|v_؋XW vGPHd>e9 p|b:-w7{Td://YeAwuLdjV܈ 7XRTTklph(憶o糘Luq",'1)'I}qYgTy9ھyPh;X" <tHR4uж#e3d$2dewZ4m|&\\LO[ w `:o޲Zϩ#&QH`2)Ig3W6O?o^ryy-#cC7&F:f{lNc#5H7Y@I!.F);ֻȧl%=Qm>?V'!+rfR3ǎ$yZH"=MWJC[ PҒ \(Ѥp:fiZ`m)ѓ9Eb*ӎzibhmO"B]O׶4uT!JJrơj[[h۞W.|?~#ieȳ JIu @9 2<,CkI22&K\ :ڣ"5Mmi=m{P֢ 3Ü!ƤĈNbl9%vRgtCf)@iAH-A@$A,, l<y.!ܱDXx~E0(˨<:0g2Rlz4D|~QJ\.ɫTIL@$L/_\3=M{ ;$ $22Rc(-tdw.;8 $2n&qT$ƧӓI(Ρ#g˜=YE7@l,"hdDn7Z28B70c຀D;OOA֏#؀%ZH]2I5L c $Unt8 ;1:!RL'QuUN?|Ow|رb+W>E@g\]Ih޿{@jϯoxzL4 ..+[~{Hbf}bmIM.VhE@hEep񑋋k֗/a]As<IX;4戔m4ANѶT4rG^bA]=5Zk-r#d / NLJ[>0\,X/mC7v#8ͫU=qӬ[Gǩ96YZի7txtR94 !5wGTs%LkƱet -CPF Zͺe^yxjD6":6h'3S~D*MW7!LJ>H#$ д0Z!$/#]u}4afLv9,K-ǻiPcC~7oرa=X@?Y5OOO÷S-Mƺ\fΒ;7k%uᑧݞ,X#C$D^WV+Fᙦil X2p^HؾЁpLEF ^# C ݈I>>Q d嚗/_Ɵ"l9EjrdEꎰx0&A{O6*eoRӷBh$,Kf`9 erI$}ԋ&I|ٹ@$IZk|p87`-k)y Ew**$D@xGbx1*et;ģ%irDsu;nOI8F A۶ޓ#_MlWh M9>FfI2$e0FCH|>E)F㙐)i6g>MpcNN@d,W+.._r{v4g1]pyX?p< a,ELye3Ҝz1bLgmےToD&xײyeR-nnin<dOjx0ZU? rJ5K& 0Zx e\%IQR1J֫9,Yr<t~B'?Ά>PG~Wb},˫.Q=oyB'vx&S,vGǚbHb 7&f,](Whuvq|-TeJb" y{t"t*lK7C1ĴWhS )5>޲/Ƿ(BixRMGvEsOCog##˜D$ڈ3{%Aɘ82)R)Թ, "g!/3E1c?_fjq {ghsG#0DEqYi#v|vbq8~X7UPS&)ww}g-*|k_O@[N eL>N{6&d96&- t#U@\KxRFy9 Rh 1#JwkmC!.Aϼ7{JM< |>mᴧosa$Kg-m?0ZP={ *\ sF=/ ,#ђi9'PLKAj"wI'9.DLum4ZhRȔYq{h.L XYJF%I2_.NbCzJ6_ IDATmyrttrT1}=ij1~ڮF IVz,eZV1id>4=i?mZR8bmtjpmn7&Քe+#Rhtx["a2Ry||d"- ͱ۟0: #M^]_SJn}d=ilMSu !Z>@rFִ,RiY o-}j2d;fu# A4VÀs:_αOc85޶$ ,ź$c6).4Þ3xC<-) Z$>ruqC:9(N4nA8]=EA̬& %I^plO{}~MHD{G/S.W_CSU7_' n˧G=F0_RfgC)HMHz:p {ںm cr&x<"{PqPf𿘎D| uv皂8ۆ45TUJHӔ_}+{ٲ"59YQ?=Dv?4B0tu@`PƶxQpNCJ jy5ug[L%$"q(9(â<3nOb$ݎfCL9yp?S;$P22dIA C1[6OsˋW0j)3,?N#+>|z'mvx1z(]ݰi1nϑreH Au2X 'i8xxxd21Alkێ3!@ WWk+DqwCM=-vX˜b橠ΙK_!|nGKvozʲDed*i ޣ8H$ڠEJOMRpN LrFB7B U1! ;xgaD t<uk2紦"-R13M!`=gAXrخF+}|Vi7 y$M=PZf0uc;oL ?  ww "ԀL piwahQڳZOHKtBm9v{L i[޾,3$4]GS߭*e(?o78Щ95"CSݰݏIh{<8K;X|tN4qjdiL4`#p\F}T;% V8:I8?<|p;"PԳ&'Q%y5^>q{ө!Qi(KuuΑ$^t80 1ueGqHUъ$]˧[f3WgEYV9Z*m,]IZƔ`t1Q!I;ִ=$MyvGo{F3ǩo/gLs._yx|f:ݐSrʏ?͛Xn$2&ՔIG֑Y~$?bc6IW&(Pb$ mOGC1D\| $B$&AJJi`#/ R* Y,V,+MS3h ~tܓIVN UL $0ƅu$RxSC4/0&ct"( A&TvkSw\b!Ҝ{Lqh6$fyw?hݑW3j<5xo|62pZôx\92!`,䅈)JяyBEAf(ix9 NPkf) %!JPc'M4U9Ň4|+|p  Bƺx^5a#T{2Q~~D4&7y2Z:5?~3yz@ddd6/L&iL\xНP@Yo^QTWV+v=9^Xv~{$ꆮko*rf)RMuܟȊD2ֵm|?9G5ap \ ͦ'*㰯ѓ t:Ow|uq=bR:a9R?#>$SY"$I KCZ`9+ISOwx|,A+E9j3N.j]M'fL5]7|sElCKYl6%5_ ,At?ҶBF^CYVHs:T~2@@ 끮Q&seD$8E ̮qV&%ۧ޼/GX,#cJ DEP4'cT唢xz;s9yYfh(D%MW=^Z';n")ze2G3&[["?RO7X,f mNbv-(ɲ2nB-w7Q,lJ6D|A@׷dc%%3GP*nu49[$N&ӏ'4`!1%U|N?qK$RD!h~鐲``yyR~8(=mۑ(tC,c};Dے~i;'SFN'qJB/m[5W/왼/8-MY>025j푮w`()ꂱ~ڑ(O?z<ׯKl8t|R=7oޜM]%aREHr<.*%5y?x|>Yg&Sz]#nNӏėxӹsvK|mhIS ~w ޏ؝Z3LBdcpᄸ 0Y R3{pM-@^,KNxyېOriB;Z$@%eB;^!5bXov ޿WWHHzA9y!¨@$!S=$Fȟ64}M?Otx%YVa Zh7E-b@~ŋei|jPox~|d:~7;zs{˱9!bu'3v4&۹VA0x 4(#;_0Nto?u}d GN=IY׬W+l7q#/"J./({Npc+Rڪ>&-DL*%ckl2\GHt=vnD]GEOVӸz~`h[) g%u6h!K+C'yV*Id"2Exߞ9WҐ$ #n?hɘƜy8qp(DvKudYd:F>x ~i Y('3Fe>%)3żJybNuwd#iI*@ܽXo.+%3n2\^] Ub3$1-$&8|L"y8KXqGRSNu3Id|9OfƀL B+yܜ('Ad_HUd?NBdEviV( g:Yq^]Ar^aL܅(H41o (K(*j0ZM3 =]z9+أ˄lb9aEl1'x=}?gi>XM498m^wxĹݯ*Iro?y^\_oᛯcUxyе=e\x\axF遻0N~9 %2J  J4"z\`\ glz*;m= uiYcsD oYzՒrRlk~X6 Œ2+@t(2}870{v0x޳寎,f.v(8)UQҷ)T;|z7ooL~dź!T:磴xZNâǞ&J=dԳwG)(P>S&v@KzDwab{2,uYq=3ZHrml`2zƬ(*CqAJE*ȴb"YyͲ>Kf(躎qhQ:%o<#7APd5j 7À,Z%N,'ɓd ԕt k6]'UFajRMi8PLEWe cb{D"b"R l@f Wi׍XͱeRaya 1&m$g_3z@(6DȈGHU-K5Ujhe]PWy<|9_})mguVqO&tsmp~ē.Bi&bEYW<>>&Ьd#2ĒPhP`TUEYWlw|-M]8:TkqΒW2r}_~\Pe6X>KaDCSd\i#eeԺ 萱\Gbt:+X)8?P֒s9 '/!TΒgc~_r\]&n{O׵Dax!*h;|LMO&fuV,ZgvO^ʢN3(Y5Ci/x}L#o}?WhUP3TUe) 2bQՊa6hN -d*'Zq7R,dfwq>QV 0Dׯ?/e,BTBO}hDQ|&AqvvA;^XSJDHQU!8HQ|WXPՋKxd8c^Y7B%Hb|H:1*6HKCN#vXkU;(8 xHN!1yAMDY̫5A׶Xkq\9 D9eD*5yp0 X觉i:h)mml܊d%niO»L[)(]i@)~R5ՊRj-OpĦ d m'??-^@QxK:;ug2u!Uir|Q2EwY˗gl[_ך3ƩEiA({w<>|$OvK6kKy~8xP3BFF,MHYR%<2GTeA? *zkӐ`7﷪, :1\RZJI8Ocz({%ss<<RϹ}Ds0;&?MHIoCPзcw9i\;WJ3Cd2Kɣ0kHqtxwyz+nx)u ,CHp} ~}.E1Y};_-EQ$&`r8$+^RfԂ44 H#*FO5+GJfU}_pss>n߲XTT匇.7|H@Q9e$|/釖W/YFQz@8 g֎dZjfEJefA,K$8,=gKb=hiG !!:ܐg*Uf hLn,X/4RiX5X;*S f(ځjw>6_kz!Ymxwd=.ѣ[O~~k֛KHQcQ; rT cd#m&ϱ9_<['x4w-gyDܼeX1_-#ðcv CslH#?Z^U-Qhȑ1X $p ӆ%ۑd:m+ gkq/?TTICK6r_:Ɠi"{z1磏>D7 RJșUD?%b@LF黑;!FKIiSth2N)RvJl!8L]5!q`wvem R"0ar(]P)y8t<>dY8Z%!0^B`>[^o1@lw<=)./%xa9h]-MKRq7H)LGfub4Ǵ`:rs:ØpNQ-X,WmbS4(Il{ؓG|oW,"j5px_~B$| YYzw] #kA3>/.黆7q(:^޲>eCY nK̔~KYab1]|wޱ<[1_̹b8M4MԳ $D#9]=>$s"Y-1~O@|FMIj3 4HFkR0lІh2lL:ZB ~L߶yiA3 Bb1sٿjZeDF"O L*Mn$`5e{ 9VW/)ŌbFVi>W=e1]7t7U)#K~y{Ӱ9xk1:S"GJ'9@aOq! }Nh1jH%&bL$h!Ɍ >=2K_*V3w87#72խ%Z=?ϙ h>.rmېiI )q1 2UcSUMDDd0gMW ϬTm'6q4"eJkgJ'1/H3n;/͞|}~yAFSg[;+b&zGeXd~;zűmzGQit8ғȈ$ZGH dA(8>]З yDjvBՉ%E^;BnY.?pssK EnbQ??[>V2yj"A^֚nhyxeݢEOO稉q,s dAXO&r953ſ%E%x4vJCYlQpÑ`\,}diǁb1{\"9YQqo臖,EV=Rmb3E4j9[͈?Mެf<>? /,O_'frQ\0M}Qe:GdE5gyvNUUXbD*7Hi(% (Ǟc뗌8t۞\JI i(cd#OmƔfyn]?!EGnh<;YKz5ZtۑkOrdJLAF~o~}f ^?+|{z9t`ӰDizs DqRyg@g Oc?K^~}Z ߃dvPdv ۯi[޿2Y0EdEI9 =Zk//{쟞#[4e׈BD4l܈uoto~_%}7җP(y`{lB-slZ ,1͙ᤂK@Vȧi`\Tжm4T:?f^i~>uc-K bzcGϲneo wR[2#X$-Ð(n=GDTU NzH@Qi(TE`f17VEď'2fUu@X;v{J89[mlhF DY&s]J6? \].&2((2A،1PZб #$1hUU ]J hE:ٵziS],B1Z0ich҆#i9:@TU|*gϏ2=OT%k[,mKrx`X1&RCCi$9Ms87o0JT#Jb9nH*,REbS5:+?Z'Y~D ȌFal%(ߦ B&1&Wڈ+b}(9cmH() LR !S@CqՔՊ̔D!&GQĊHѮ=CO#YQ=GcXg8ajJ~;4Se Sd gR)~M-vlY,kL'sX7⣄8S' bLa>"c"ɥݮAg Srջ>tUHqb 8=EI=?,eEZg qZU1,9vŬxq%"#.ȋ%ڤϮbj>ThGi8=Bз<4*Uq(gLS,qlz}FK5\lxqǧ-Ze(8[m!\)"ۯ2`,qt'<@\l-DoyE?C $:ϸzb쒮#8BL  O㞝xdѺ]P- >;;CcKAXܣ<~@K DksC֥-16Lʙnp$%ݖG#^f1EKs]!כ*(봕޶Hm)Sr^",4 ttQyI^Tcu)Ȳ ;*pFM.kPEOcR+W PZ UV'qJ[de9$AH {Mpv(Q'!NT ov>]A8i:jR H-( 1%ŬLÎ @Sɟ?#v;(rR&ʴ(k(S9enʜY]9[0++_2N={-o3:uh˫Wc˿ ~rꊳ(͛du29˗_~}:on>TU:##]7\.9??g..Xysiy:8O~bra.$NҖ { LQdp,Y.Il0X́㮅@J=R_2z=>b$͗>V)˔Lb$Öu˫k0&g?)ݱaVVyYW01too9[ϹMرe%S ,9Y.Qy`q`r#K`}JTeyaC"Y~߱^+z;R9Zy#θSwrexzi)R&=.7퀔p^&7_8s'ք`-%{J g, tc(:SRw4HiJGK79eWza w H$Bd)Rm YjOBAr ɐ#EE6bF ϸ5X/^F&2{ZS/ ,2`n LsByIxjy^b: fcJ"H10t[a NYmmzQLzB!H|CVŴLmfzFptr|FtӨ'dE KK6-m瑢`V1"x6Jh~S>f>,4]-|'|O $c{g4vY=>(g'0lw7h,6LCC:&s|2Gw%Ǥl>~nxVӜm9:30, '␐2ɩh$kbaG.Q ;9&Bc'l6g9;!K+ʣtȻ7_k < :~O1U&f&Lh~sX|Y.c~w\_gGfYl9yVB%=EuГzxo(KhKO?{Ϋo~5݊=G^?tgdx|fޱ X\5tG` w8`bR9.qv+vpssdH#Pbb X3>1sSUQs#߮;>98qyy/>O`}mHZ& uYcTx7j?~4:l1t% OǎׯEJ8;9{|DJ=fhۑH w`&{u?p l*n¹8QPQ$J ^ GBo`?'v620td`Q:e:]ˬ:G*I5?&J޼*]cm4f%(GǧO¤{rAVTXk%h mi;AwX,+JG7x-yH$]\_9Ř<%K:<Y7N8k"$yJ48 qby*#o3 C<<@"<[H2ctf_@$ A%=$'ST/9^.U9IP: Y/g3~c|d5nr@'_#grzzJ|wl7 R@3DlFrua6KY.38RZ#04,8;;a^*TYb1#㖹z=neP:g<>fcEqz<'/R,G q`0YJQ5en(U3-EQ׼{wɷɳΌˊ3V73`Fb~?}x|XV?ݛnȲ Hv4 &;a u!BLX)u Zeݭ))Jd?|OҞۿ"M T]4CE-*cclHk޽{Eo?*B[Kg?*ShSA)41N2Cd( c+/puᅦN{RIS|!f&~%26g$8gyV$c̄c I\JR~bŞ`Z6;J%ø=, a״$Og˯~Kvvl!΍d:eSCpEQwgyua2vwug[cL7 3:>N)߽!泄x|Brqq{`7{HuvЩNjo7v1Q]fYvжx||L߳;`+̲ .nzcX<}3\.9;>%O5Т3 䳂fCTE9JQO(?s?ɒ(ȷk{ìJ9 )%߭tMscFB !qhBSVtW)nob0Hg;M7я =Y^rvqAiVw|pMYUN]׬W[,a^߀Lжh'l,d2t07|(]l2Al=34Vłpa-="Ey,1iR cˬ:#4p;%:u?1=.t?bl*Çq 21Ds !RF7oq\PW''tɋt8|˲C:VA,c hy?TygaG"D{nx (l\߼C %%RX7C*#{(&8%M$I*}I WBCd>[-L;NѪnhI\2:O" O+b)QeYuqI(QZwW0wɱd^Du-.֢h/Gg/Ǟ{V`!*/2e^?,uɌ݊vhDA1jD=(s~H4,G[#ֵ4m0tQ>"JLY.42}@;lp~Kuqf{ox!MN Am<8S mk\ |W 8c4R a6[mJfJQ;M4h@0~ci"ж u܂`(!aņ8#Ďu{]*tMZV9*Ufb&&PIJc*IcGӸR"T%4u%f3 (*~Bx{ x!IP&6%Sxɫb7BFH2ޯ:O8:9g&oF$I2!Q16kȫʑrbF!GJ)f@GcЉ w$0HiD̴'"ҸސcB{7qK:Jc $ޑ_C9&G'\}15gƆO8??l6={|ZKy%J )HҜEdE3隖7PʲHsHPIbYڦe*FkE4{HMZKBX}ӠҘ.HlIzYyY3(3{1K )H!:^X~pģk2EJ*]iZ$n:Hu8(O?Cu08%5goQiBWW7/W_8^b"% $2d( 1u-@VfTi|N%,~WC@'O^ 'W/߽$+RKv mpwf9͊EǨbm8hR ;jA^h͊q@Z#Yd Ⱦ fu~azYQ2#yizOE<@>1mҏY^SԆgORyIne2{To;T!SM PeQU`2Yg)Zfq`u{=Zvf;ɒ'Oqể[ce1_rl-u]p^TLS>8@y+@)vp %dV!BO$ ?o8"՚qzk#BIqG)dZ֜>f3Fѳg0!-ٮoKb^1|g\ S$=` ~j+nwy¬2~FaG#`,>:Dh("`߶q`=#{H i%~+H l0vcsϣOz"=Na} !cM7Aݱ^u}ţ"/Xo/yx ?S w-E}eۍEF)6-lɀ #5Z&$ykBXi2&ݕEHaHzÇKl ^@e9E5'kR;R|w{$~Ȋ{9M;s$Ox9o߼vK'(QK>zտ`x n=Ww7%O]+Oxtr_WdYɜ~ڣzýa1VhPʒjBG?3NGQUh=fjvf%N$*a9_ Ƈ4YaGD×_~/RӶQ?~uwKk .?0E]L,KTJR3K7|%4qz~Jr(4 m`aUѱ^u rAdݚnE"FX <<ɣG|$yAq]_$)}h-Rc&i%U* (ڝCXLaG%$ mdN-:MEq8o>ӴM=Ml{.{>~~W_}O#0ݭ;%;wAhNώ(aȊzH̨X=kzJO#V4ݖ>\szz,IUnG|' ? (Sen,N苏) A=Knd[aC;Fv &$Y1}?yM#'oz7_pvzfg^R3~e9vTl {A?O{0y!FC^(scOkm;£ٯ}?o{I%3f"OI0n?*  uzD*̃DS0HHa5)T@>{ɾ:~׳<.0-yY80`I<2-ց cǼ0.!!N9] "PdqO-3q%=Ep}0W/@bW_}zŷe2~t1U!"m]H4)! 9J8<ˣ',9MEe-ErQ6:ZdGOyg1N ?oY?FiK`ѱ[Y*9U$ɂ$HF)6F)O?ø;nҤeB?J"9nxi@ O=+l5WWW\]%6E;1=&hc2D+Nސ8/89^P  g'dU%x7(ʔgA5m'rx~gG~''K9hہlΔY뚶m#-plxgiw{liq]ĄIUGF/~D .9 IDATr>k%z6+fX6?<|+v&~KRe58 xB zͻog?JxRA(7[5!H3\$:'("f:aDqI,JO" d,yV3v#9\<:㗿@H{-8iݑfc{Dp[Tgxo_wԋ~ C7I5Y*h-u^ xYcpv{x.KXs]I|1裩1gnqfl hPB<"3}v{&| %8@!PXHUSZE M;1R${IU%gzPp|R$1ɻY_a1.mCV $7`lKg<}Ϟ~\{-y3~W~( "ϨmB(),.3HӌlƏ~|4 W)8:Z$ TUœ'O?s~~2M1umaYFx @U e;$[i#RjlV)K]̫~*3*YyX-`r5bm53BP 'M*{~1+4Cבe(˜4y cep.Z3l;)y!hC?1f5H;gbLo7{lǿÇ((:e! i1=aZssb%7o/yٗ2׿nK$_qcڻhC 0!o# +NNk,Vē ø/&4) .aptc01Ws,}d8^!/K%rNyX,Q"];珹-/_GK8}yOW_5 t >N$`2Uwk*0ZlGO] I2qn͞iWgA !9$RcBj$Og]#ϣmhِ#f!bla-I2MY-͖ ckA(<BA"eh P"HC@H#Xo:h-2Xci ~Ѫwo_*6L<@Z ZI%_1O3t) 0IDZCQVa]Oq5f48o SX.E+U5$Ht-& i,y@pX+ )y^#u0WxM@:d io^^~xMUU4hw}[|LOi{tTᦡK&+v4ȵ@`q;Ge :vCn#Q' eZc-VH%";@`%CQv8uQ}uJgHq&Z_mO?^5k`ų'҄k9?98SmCL}m|I5H4Wx7$#g85z^A;0  B ƩmTdhV넲^B>@-66j0ɱoh2p|dŒkx"QʔTg]|}ɒr]7 T1$ 80YGpHWJQ&su-dи-,;E }zt Qya(`tc%»zFRLfO9_.qY?~b^Ӵ ߽g&Œ IBVhؾA`A"3ꬦ5co{þ4nC"E(E]ee O"(Ja!YBBi(!I4.$ޑ `VI[d= "6ČzD(؃N; X7gHU%5_ooT%`ȋcF= ӘLL^fJ9/(RMK9MO<߾>~W~%g}G;E&Jm)L0vdg2L"05Q m UXkA& w<~|A߷\n(J|>Ǐ6Z,ʢ$In i#H5y9ݎf?pW$IE5ˣ.f|[V5\CaѪ'/&z{La J;[`-x^wt0p c|CJ52[Pf9;!IgdvlLɧgn;bdY,k%ugGiX|) iV `IGՂ˫knx`vmߡeK-Yg.>E\ dT:R`?ȭIT Pdx:?@EJ4q'i,x8$6>s*-P"A-bZc~?ׯ/^Spzj"CdBY8zDސ9vQK4!8='|ЍytpHQqfC7 )SNR#øebAFD:TR`m:[D(H(+P*E+/ibGHGnq?pB.?C&-NXiǁvC]-8>>Gx!"Z$h)89=pE #IVv| }zSE~!vN/haG UR)?y=f#U͡;"$*3A?m"._o6{?/1ZǛI{zQHE7L)Y2\ee?#Iw2<9&-Jf`ط BH~zKۏH79D`pN"s.Wd*i u5 `h>&bttS,xg/>dN<>>bEIM1mbe Md¸_3_EjJs3he1?iF :amU4]O&f<~EvǶkv64y"` 6xR)cƞhv-AGR554̀P~GYV,G8ov dYBYxM2tyG︺a&[0/?eViYt'@ A9/ΌѐcZHҌ<[}f1?۷|s>}A׼y|Gډmy%_u1gklhH?$ Q9a # InoDhtp xQQa"G Ue@*)jc"O&L5]T~g}cz)CtXGbprd,-v46isu;{5LC4w\߽xestt/ǩCњhOʒ"VHdziOߵ1YJQ%?W=}zûo:BpTeNYXdh:jg7"1ϩg%:*gӸ_fn0ΰ\d7e iFt3Y}LTH1UK%t< ifi4FZJDݮ3z$2+pCO!Rt"q!ĺq`=n[AJ[40 2eI eU lv[>uGO0vؙ֒NV-!ꄳdiR~t=BiN3oIt~'8ҁh23#Pq+5%m+./i.Ha4x40B"+dСS5CSʳȡIe| -rvW\ŗ_ɋ|-7q|Ts^y z)Y Way@jq0-{LfKYu}D{WX:!UIP2LG*Ѿ=C`AkfyMl;nBѶ=W':e<"gglkvd:", IFQx7э24/noO?E(Ra4H!ZFʹ{>m>V} ɲ|?WlzVEnO'.\*Edɲ@bˆ#BLx B `f#$Kr\{6ٝyF1X !#<{FfZCAҢ\:[R"YV߶ɥJx;7 --cv(5yVLsOSXklǔ)*Ŋn\}1%튢:.c26>՗ 5: uA 9A)jwۙ廓&g./?kt]A[Պ:pQ7c;X`Qp+D613LQ5͢bYf>u$<ј>p(\33Kr ;kjEYV,ٳGH'Cw8mim;OtCr^}so[Lkbgk>']waG<qz/š, '''hnJ;74Tf,uJ_ ݡ?xǻwHFMA;ؼk1(81{o8딾QZ뺤ֆyhwW8Y5tBZmp`F+fKQUj:.o=}?3wwi,*=NN%Txi:pu>]2Aكger.?┛k 2pq&J6R%UNˋtV ~s0Ω1ePv\s}}M@gT|u>'1*C(w!$fiY, HQ9Cʠ$/ڑ$qۉK0c~͖,@"ٵTpjj~x UQs{}G&Mɴ~sE{[\8-!d&!%}*ɐ*c2_QV5EŀTc?v=Ze`n #LZZT+8E1*躖E\3C "N/aQ_ +.dTlnh-+S R _Dy7HiV<0ad 1b2ţG=_Pj MAaDX+1m!DdpOw&)8ҔO_R-JwU'KCK4(!" iZR)g#}5J %v$7%E54{H)bpi#ãygiv@g)5)*T:#H="{ aF3}D2mGk0:~ >Y?Bxv9U!2! S (0|G(G} ݖjQy o=Bʈ2߾MLT [|PvO3۳(҅/D0F`抶mɳZwm\]i'k_&Hϼ~?TuNm48.4aV BcP9:+p#HXY*YQ>!03J "* Y L}V}vHq!+\H9Y7DTG_0 ȠY-4i%bT[@ajEU324F﷜\, aԕ* )MALCZBm 3ݖraX6kʼ` ;mPli*( I CO01#tܔU=!CH MJ1=YE˚hhQ\7gj932FS=1펶i-J iFil70FUq0WY#S}ߣa\s~~FQmd[*Ѧ$̄"M?wJMa' (ΩceY`Kh<<Sqr"KI> YDQ%&+gAѶ{8$r(WG[KQ@H6\J黉JS"3,ԗ^Z6ćČ꺎(+}?XFLaڐ%B ;l3ۻ)'-9[x5]w DGS/!UꈁCYƣ` xL38 X)MU.Ȋz6K3YB3@,.KD#5Z'CZe@cdQ! A~"|sl $n2l@Q n1 stV!U"v&N{vixiT눂呷>FOϦj aZ4dY,  iM1LGx72shwL:eΏ>g_){Ox {OMlqYhhXθ&q[T yR &73 iyFg"M^aGNNjV5y:]L9*A摴ǜ^`ߧ<ҍY D}ߑeN"C%G*lG*wuB|&B IIhAU@%)TvNjAS97#E.pc$xak-1ZVˊ¤vNpSEQiOXϞ=`24G8̶d<ɒU.m[C{|tʲ9il#QNO4P)aKgNPLb@xxAI2XnO/yQ~E*/OiŒ(3 QbSȸ>$l9H22ǥpx?ꠔfMY&Z\.R5bE~H]xvkg ep:u:CeLk C-0J!uѩ4 }8[RqX}b"ox~%}9秚~ؑig@XktLӈnj96nrI3?cG8ao~<|dF"di_| zMf N*]QryqIi^~wo2y~R 9%yZu0zہf`8)Fry~;ƫG T (mY=oX =UQo{%;84rsuųgh%J|(ˊ՚n踽M } uA|:FgKq,S"MJd#' WAoF@,R($Y2'D4~C''gNYKP|n G4Y)0{28?Gl[-~GD1d5:+q^$ԅ)p^.۞,+xsL^Jr!L];P%EK:bL$g< ymmrfYŎSzɞ;B x@!Z',2;{M)jog?%̔4hy>5q9*u9Z b7l7-砕aݢTqLMn4L9ҐeYDŽ"a~T>pr~).yW_C?m?D? '/AO6Qkqyb%R%hRdyy^c2hW}-Q$RICeB2)z#3#0 -y&WLL h-z2hΗy%;(EEq`G#U g;Er>:i ٖf:eqj1sZJTH##DMnҖ8J`(բN]@ߍ&N%gg|) 28hU5R(9?& e_ݟlQfǖ`[BL;;.X'QlHLR AZ9(3UR :N& K)wM\{K8ڡJ+=?'(ȋk=g@du#~Hθsw{w3MUY-3\hc("j%M]ᜣJIk BeIS/dGEEz%fԐs`swOg̱.q”XsŇ%5H#볚(h1m~mpY0t']LsldV$Yˢy/NLBj&ߧvh~JW]1 C21&dR挳 Hd%*W zeMQ]0usJ9G֗O9l8"U~dEa$%K.>&5OVap}cQw?ȗ| ]Y#ENv| M BHQP vhY5]uKoUq &~vxX,RR/JNG9Jb\kdzXΩ Ҥ*{^}7R-V,? %*>޾fg|w)tJe`ݬY,O8xnߧD!jbx9Zk& ]OTm;2M3֧*^UMT%yn8Svہa\Y)yG, L㞃4R0!wǘ'y@)Ǣ*#v"u֪TkYAnXieK7>(a: AD,*.z7)R>=9?Ox9>;O&T{VUGd<'WBoitdj{z9^)> Ţf@TY;BRf9ǖ6d-BFƑiEMy8k-7[NQlH[dn.>n"C,.0R00ӏÁq`: yRL!c0 ^)O|Q;>%rTpwwGpqw^HN/X]JB&Wi#s&y P^rmz$(8t-yP2&@uwXg)pT IճɎip{O1M`e 0C3;&_Qf@ssc1Pf#"Mя~LnJi ^<=ewho. /9;9 "y2Gpcsv,sta7płˋg|:O4.GiȦ4FISѲۦ-^3YbLK,}Ӕn!r>m>(cABL4;*u:{8H1){>FKCz]#`g2SbgI^|'c CKpaٜg{SmN7K\+)JD <~/Z>ݞE}AglHw gxٳX-n!f_} @Qˁnp Sd,=EM&_o3ԀV#Efg"O`zNj%G/%x_[~Վ5pwܼ3CW)QeOx %m_+܄3$.8;_:Y} /?,:݁a8ڍJ*zdz\\r{{nh'E8#]E)uB:9򲾯69 EbpTF4)ӐVeMp#4ױO B\_:8?`tDj0t q(2cT: eYz=E"JEaG\DV EĻњَ|/$i()q6r8 R&qGf۳h*nJo9U*'5WC()>!S&ʈ5в5C\vKYVH!Im9Kw0q|97wmۼkw3&Gm>`<9Y8` I#n4OnELÈZk,<^xJI'G|拯A7!b L)eJW~!DNv@-!$<}vg+kFS-UDH=>ezNJFa]&Tudv*v-eJ!Ȑr>bƋl ӖG%'lv{P I_1$ꢈv>BSHB"&2x@Ǘ )"hmfp"ս.bfQjBF;2"'g<:vb.KqߠTeNQfm~w J"{Kdaeߥ  ?T3!$Ȧ %U^ IXb1)|d,Qw#8t=<}ɓ^SW@3$Axo5ǟ-BdB]ڑA#N#atyt ,#Xs y5u|:} =9Fmynpq:_Č>#XǴXpHow[89Cl9mBL BH#ZeH $I>l#PmFŌ96̈́Ǐ??>]5EVrzQ(2ZbU ]qVc,+lTSٳ\ô=lrt)bOmggg4~"Ӕ!4m[1#yAf9au7_p}^w0gD3Zv$+&ddhPҁ̧%IZ"H4Kbw R?>DpsF39w:,,'|﷾~Oͮf2ZǡbN?jEHӔjNyή>[ܐiŇ[;V'hgLrtnj^~+.>`~YڡGʌPtCP v49 š@]$INٌq⨘tXLI)#/j-*UTyE?r"<2s=AFJD LRq,Qgk<$V,|^"Be#B*bMݢbZ 'Hnh_d!Č!2!lB:n&M*#${#Ms|@pq ;$R匶ź#,dI9*Ȑϙ̖ 2G9-[_#ED #C0 r?Ĺ\^|yvw|OxtgϾbnO3\O쀑< 'Iu cπ,g_Y3ĺ-ISE@ee v{OU:2=O3ֻ { y0'yJa`9|Xɋzvf3R~ǎj}kfH-7bG# .ă%8jR'9OQd$Jǘ? "b?Ԁ|@K3FT ĸ"'x Ʊ'3đy$yt]`RIL֚4$IlP2e1pTV+@ͮop8IAlpͫפB|3N&|x i$XkLn6e2TUkyƮ_s4q9vJW֢a01CddGkߗpQ/f93]/جka(2TdIUW!#p9/tjA n5$KVӃLs|_";7דVM7ݞJRbL+xzn^[3EN7( yG"ach;h YIdyBDYUշuM1*2]Po6=3P`ɋH׌@];/`){αXN1}fCL7x5,K <:GSHǔV `4GTuEbM2< ZkFoK)1я07̖m K9i&g{#X(UX̘LsO_`RGTQLS궧&9yv{C fAtV5X#USH% Zbf=wL3>7o^fߥ(RH Ѫu8m2M{5*LA+f Uzs>MUUqz{j_|Ki~_)Ŝ4Fg>(JG3kO:d2a60pwݿޓ*8$I0 8=]tMO(25ecb/IEL9E5C (| =?ܒS(*w+W4ݚieg̦S!I?7ߐd8+Zg).X '|~ew!]LuCI Ht,4eҎۻww*Lg3Ƒ$Be3: >8H9;=bj* mBy9c1R7;qg[dYpO7eNYjYkQIJ Xᓒ<+iIBxP$I5OѶ5f֒~L8=Y\L1s~=yTDp8=}O][<^p~YZd=`\8:=MG7:ȋ,-( >f5,St}<^qvzr~v{bRklfS&TJۏlTՌnچkd}d>_qwwzY2>޿{B҄#S Yniܮg9դ:`i[9zu$k/y;3~k&L'眝>WrI(JEX;cGWH4)"w^>8Ʊ⍴( paEecG$#%GKrd=6Xit-<'c4U5'+?d;޾{13d>b1̳O "gZdO q~0p Oc4(BހEY -`Z|Xi`c2t:(+nTՄ咦=0 3|n %&x[`/ .ξjyh7yc\1_ah꺎7,W眝͖IU1&dg6g g`awX$%F딪\1Efͤ9k>\Mg+nx16 mK/g*hbF ׉¡̛(@$ 8QJ,h+鱣cB7_fA&Rt(9a{4$,<"HB&g2a%䱣#MpgiJ' JI!:(1c|6`qXӠ#U-FȬ:*"GG^=č)2t>$ !AYȎH״CejD@7CJ!?_ 4&K<8+x<,MP&b8oGrZZ# ]/qҡҔ/5o;=ڮW߰X9}1jbyɔ:MRCz78FXH;˸q@} UEtÈJ +RTUu#`6(T y'szrǏwԇAJی4euñv#pS#l@ONh%Id2>C1 ;#p8`|'IB]#/XQ@=b8j@X,0?F:/M#p@e[q-8f,#Ms`so \e,KօB,2oG]4-E>gG_mPiuE>e>;i cXTj g,IYPf  .-]Ƙ:ǡCJu1gp{{?8K ·_I1]Oj oPIa"4#ё7t#IC3PNR0;FDdn3$&) q8V2xTmiNY(=>;xpp8ݬL&I?g>]c[]L9Eٙd9SZ Ѡ8!C@G=_z|v-mrqY"/V aGNof X.,&woa;5Rt|>'7E@{OO1[<`R=? 2v/UCA?%iR;lGͨ`=:aE:hi;f1@dL8Ȓtc`Di{BϳX?p RGkAʲЩøTgeӴk-?W4d-wgY-/HsybzoPw-YZ2=^#T`2k:m))Rn|>EtQiҲŚSL&s,6Aes>>ҷ1V,fGCD@J蚚;%aGB.xw]]SUŔJJv 9i^!EN tn_tf07vt ZQy("C: IDAT^QX1 D$XJPO8/qtV8Y`}cOV\>&R1fHHC5-iYb 4PEŭo*A|}PQu!F{lPH'+ 1˭X{<@CC %t}728Z;I9;!=#{=Eg+-_>g\p{{Ǣ@l5MӠtF&9ZgɄ…V9Y>a<;n>H*3$BDd#4Ox`Gq(&2tRvΡd I&R)?BdmĨFf)G<fB 8i\)B8k2CVc->j CLR,z/@ysmK$yơC :9#6؍YY \/sga~RJuIT ER>Gdfaݖ|\>82~GZHDYnc8ᓧv\]o~G5)躎O9(!f8$%MSXoȫ?~54݀,9Eh h$X?,8Pb6[Xx-MSsɌ,i۞"_ȁoc1INBQS,CL9#\},΁ ȫ<DoF*A XIdS `RD(!S^<:6=:rڑo^7zǺ5$!BG c%eR9۝v[贬m[hH8=9'K۲ߵl7 \_ݳn9{;C |󁪚Ěf{\clG)/V޿ǗEMyL`y&n@> R?fJȸ4 G_Է5Sg#Eh>B΃f3q >ާq]Zxq@%m7Y8BF l #-QEOJѵ&r/mR# |;z=iJ3XOyfE<  :NV9yMӒ(*I D'ifTqafmOUι ZG}"A5)Gpصl,iI\eU$( >}sNabRC:$ Q.8-_O?GZ"S ﯘ'g|_pzgmxT$)5M,":y,<9O(KINRT%(g%E@shHP;ʃS?2q RK&+T9yY[ΛXU:Iq%߼|˟gX g[oY,vzb"n1q⠯ `Q*r*dbB*v#Z ...٬{߰8Y1[Н"8P1Lɒr2Ti]֒ر\]$ g'+n?_ cLҍ]`,=<,l"xoX,f&~'T$BP 8cKHE\K!HhphTuݒ')Z* o_̦mɜjr17W c:Z-btZQ Teɇ7<:~}\n] Ź|IfތRLCHA!ErL%N%yNж=i6`ȲY 8`=lQt"Hdi,8=8YWx`uH^]d}[B-'}{C"ciLM&Q6QHS ZhٱZt]w 9^.i2m{35Ꝋ :{B;$J:9u ?ro?|W*Nϧ`E{IpG|2ӯ#80/ kB:"MhZG"M*EU'8HTyH2-2H"w53$ 0pwt`6]1va|+n4z a5k-i:K,C 7$>i"0}ʾ*ViHtAQLzI5'I ]Qw{.(&w9'U`=n4d6&L1aD [JyG |4@6̧StŔ)Fv[vOt`ITl>XvaJR6iSQϿqv5w7xc8="no$JdDC?n`9o?ܳZ9TYQt䠊K e ]Ī -IBHG?x7& `XcDrs_)yVDj4"Xm0JG>V1+c ^0@< mL(Ŋi=jQ(qdFh$mK=$ x9liá|; }`6}B]xd,3"Eeh*WFB:n}2˳qKIc1=y4IɎ uav#6DлN c!&SߡX5u_D$k_˳-KQ9'KNNc:\{o 0=I&QB4;88Z$ńtASh޷ey)ْ`y fh@XtE0dsrXǞ)dv;TJiUb.kdH+#=>4L{L*^ &)!`THoI3EayB~8mK40H"ތ @pg:,;O*Ty '_}PT RDPH+F7lg38؍RJIY|(Jo} ?+g(h=Z0`[SV? 斪JHSw{rs}m>`!*ҬPvA~ЬF>H xC0.ڬNs$a71}csL%a6Xpwa8eG >ė_ō@NdqSs$ 24ZcJz D^vE TWhXmIb9%:b0p3b ^"3QP2"br}G5@tc}ca:3 8d N p@˜x'iMR):)I-jT嬖L9"8n׼|+KXonY,)##(%拒8-j]אdu}yb5ahdi\}΃d-Y%"!Wh Igg:-΀u4c M$ }ی$Y y"} X^  pŇ}]vO^,I[m!HRͣIf[\CN%Avl(}aƸ{DpD 9u}rqR(hHBe:mkpT lW5w`'=p80i&9J8lZ޼݁eEhp7!f_\)ּxc-,O*.N/ڟ/+Ŝǟ<Çl_QT/~㿅&Yo%$εu0&'OLݎ4KNdفӳ$~K%BVÀ3p:?awx;l68gx!w94́$)97oO>x{g=.q./{bþ_=_V +:ʰ:I@X[|t}ɔ`݁2MJqm&l( ӧprvn߰Y7.C}rɭYG:E)Bp}}+):UL /ュ)Œ?0Ϲ][ٔczOD@v zw`6mR&tAt]f#΍xlTŜqBi)BX66+u#3r^=ӧN/w.m@(1Ʋw\^Z˫WXo($ ÀqhRgILO*,Mֳ?@)FJN "xǏ\__gs)k,[:F4PX!0+$g-U6CNS?}p>&|c& À7=o fj2HXtt2/(1\hi[?0 Ih5pU"9 ē&9J޳Z-8xu,ytr"IQi1iL e٠T Ղt(wﹹz# KG U5#͒HAYI4"mL656r@{!|l3)EQpzBK0y3lFߟд5Бqku {D>>j-AЂ`cD{p$}ߡŒvw{h&%F{^ݲ\ecs ҧH ٌ\! u{&f: o֧L&tCn(+lvuo)0=n҂*xH;H(ѧ*aLK7ctA8|.,K ]HKԴi=,[{~ :I]Uhb=;?/?Ͽ3v#Zä.X.TuJ-etPaow'(OQ'rDx%b{dGB){dTϞ}whPD,'!rRPOKgH%}Lf|G #J#tzu(DL,xȳtŹ;`? 7oQU UU;CӤh#UJZG\r}=G= k!S'F} Ou7x43Vd4&&Oi;ZҔ Ȟ 2ip]c~pcBQļW88;GWEzeyt>͊w)"1ٲatHQ0\3^"y9EPT[!ɵ$5vϸϿۯWg8*W\eJ-j ;FYWݿq6}?1? "ljtK!u&d3s*[Z?u4ѵ)\,@a` N0}Ǒ*]yЦ ;2 ʫK ^>+Fk_Q0`+j(`)bvOzRO^c̀|ww[u ]*rŌw5.x TM/{։5#͆}rNQ~dݒ%MSq<c2b L⊇ETVKQyL)Ͱ7iUssJe˩ epۻՁ?NgaawA$49:#WgQ(5˳D0?BHh&яܼ{E%JCE!8K'b#/oooG|p VLbr\, 4x*T'51ܭ0H+H#A.9EV bJE<@VdZ@0 ,Qzkf)Y(+2 QSWKC/_ Ev35B@p[=vmע*Nڽcb}CLq~9a- @*xa*lFh\TJrdC8L*LKZgy$b:iK;k v*CKb&1<{@O52pSeX,C4 sB<`{ s1)RtW&2+e>>B1鴕BɊLIN :BH* 8)P򤚇-Ru]t@~R>S.8GZ2J˼d>]+ƀuQ%rfSdJjھ%4>vdw?M qf ?J0!ۯ E֒< Æ"QX4 | U}@|Q3έ"8knnnBQ9Y! 0:PY%Bs /$8@wqg&@5ҵcNj9*ʲdF{*k.>|-77 ahሬ+EV줩O[=.&B(@mG\Y^?O~ ~7~nK+݊7{%}ϓ׌nğBA`Rk MQHuRgZhR"9cu{,sF; OpI=lRu)%3g?JX"Bi#`pnHCWE-C{C8c*mZ>=_ǜ/-2s?JQA-rv.n0&pq _wb:s{>q9LUR.|r쒛o>x8^+˫w:WΦ,m|OӛvC3O\Y3dT,3qT=YњOPy_|7)rASAC DMUjY) ϣGkiy,  V<9E8n`0XO\A@O>X;Ӓ0b1BFVU95͑w=ԵdH9ڤayQ*T@^ônP6U ٌ%O>&Y'ԃJjsc >FIjK @y^GL' vۖط#_~:YY|$F튾Bos~<ȫ2!k&Ӛlځ j +=v}`zbBGû '?dD1 fA59%Iaw`QjJQYJ)Yvx{S޼_~#L3gKʺA)ɔg$ގp|:j:p,kq{Tu qd_u=*WT#J݈Ìq4CV2TUA^h)Zx)YndG⒲Pǔ8AiOd)ԩ-%B$R‡q"A3d9Ye۾,ˋ Hٮ( )GIc2|6G"8`>?cwrU]F2 ~@c)s!Re 'o=;20o!i}4::h&sKF 1#sb"H\Gz?RLϟ^ҵ{BTNPq @<t ^" &@HkԂ#݊Ǐ^r|qױP5yh f8rńlAS Ba=~efBמw ]1>%/bZ^<}?>zFkR0ZΗ ;B`1L ~㝤iιxH;w __5y.D5Tu}ZXr䗿'8cl6-xwOg<5䒼D?__Aο/{'ӂzZ###Mt2I5#ǃIl xkp" -yLU#")b@T#U))%Y YgA#BernkVfK\V#܂;3(.Erx|T$) &hU!]0_^Ͽx1y۷|<0k#\dӰ!洙'$R*A't5D,eĤ!2m&\-9춿d6b0_1ijGݻw8ru='p{GY,usrYoLžLԍz1crqv6k$mM`ZཧyΌi c>EI&5C&sIFp鐝ۉy>zOQR0ɂ%b*2ڤ)-RH*Un;c ,餍tH&J&:U0}^fY:f1} "YnNWu2HJkuljT'R$UM*V5΃I+c?W"g4$F6 lvh>!RGW[|:0tIsCs#1)$Ufxw‰ȤRT*#(3h#E!8%/4;N(2JJm|R+`FpZŀ4\AFN (R+8x,ѥd:$t-DcFK z;dySs֌#?U#-vDj{'??__1g3952JxzI) ?B(` k :Afk~P#vbCH=M&uL91޾}MO/pq}w٬ּzs=*lYex!@g$z1d@ha,CR8iiB9˒lQU5Ryɛ8Z ]S vɲ, f:Gĥ A_#4M@ܯ!,9n}0#hxC^xv3;=8xXFzC c,}*,?5o6YQLxBRgؑ iz6RQp2xBU<{v) Y.a9O)zv{Ęr z?~cƀ8U nd1k8R0aG+b6?)r~vplwa9;~ӟ_vkfƭq70Ƥ ZʲNi9 |z  YRy&L|wцh("yQ!D\_14qcZhӳ <͋,{cǼ9v˫W"(GQD%ɊJf Ο\p鍧6 y}oDTYKq24Rx?޶HݞnAn4DP"; \b),2" OtTuIYj! 5#ZU|dwDܱX^T%eʣBD8#1 ALJk)fP]茦(b%R[%"зB۱;/^).id:0R|X\~m[>Ua=?\|;<}q/~s6{zA4b}ݶ[|[M~,zBt]?b>sf+!s"FXLfU B1}.3]_1NyHkRe8z\'Oxacy.9;[P%gd*GtqyxXso0*@;xtHcQsP)>\i\+ʢk./ʚ3LLOLUTtwTUCDrg/'X1=m{Č=O?Îk<{|qog<{] 6=[EJQD}qǟ~|y77|WCe:Ŀupz=s+e]kb4AzNU21~E g4lyܥ$nA*5EG RJ>#?} wwo:q<9;1(-c]2+%h{ $ +DGē%ʱH\QH(]0i0=ֲ"#ՕRaEkM+Lqv>c3 BJ[QURb7+&KX7v;~V[Ro1c|UU4 y^n72ZrmT@qa -e`*,; ›:!i'xN( Ds |OAgc/?Tktzw=J}y}0NOcEweI娦 DCptJ4/) o^}#!(#. &bls8/,/gHDJEٔIҙV8cMjI )͊ځF%-8&uyS B}v%2.YMR n:+ &a֜%ܤIM uKU+x{aF+̦ hMS7Hfۛ=Z0V7;rBhZ az!OgݜnLPɬf'W8BYIJ"@;ЧgŋJ2G2C G&hQO P8TCk:8rl[y-O>?J./)3A|:p9 t(k_K9?g;ɤŽ=ܢ+`[#=YhOEpXL%" RD O. 2mJ9ȑ6'']Q`Gv 4Oˌ""$hfYCr~$8ƴx78B<8Tsrjy$v֚QyVi#/4RDLl>]]قOlS'RId傠9vK׭i+d2q(q2x7<?+t1ݬ~-Ϛx{SFfVeWguWW HFX%H'y?%%6UUYU13yM@@*&I8 IL&M,ѷ;}0T&gbvk-FM%MYL=Uzqxew9yáU!ރd*:)iUtpJpA8GP0,zUaw=7~{gvlyp~8_/~l&>y˗_/~rskn7wXko)y&$d*Y:L%dYFQUFQmq`R26 H91 𧴦$ o咢8;;g'ڎϟ8y{vs}09J'v=Mj]Ȱ.%DH¹M}1\be ۑ),%,wipڑy-= P2I|`^"m@v ڀցb1c$S"$sV%cR_8/%-)sAōTUoBqhMU+ zl4D@)vIQsʢ (kEK!œ*JHyV'##}矟ZF.(ʜ[Ʊ?1w,ֺJ%^v?Dvdgw}z"L?%0|dfx00#M܄ Zct)%$,/2a4*ENQ$Q[n-xdU\\ ±AB1K׺*XhO99D #TBx@zy=E:թ2a!jUslTy&ޑiOwz]2 +ikɘKn`!:3!{Z8 gsG&Co(˜n%oq{͛ Eu~L9Y,KW5T Eyc7G֫5FοϿټ8 nX,ω8z>QT5?W?G_Zhvbˮ2{Lu,S`*W̫^5os~=ԋBx~$SbOk..Έq8=g6[0ځwo_s}=vjIf0 Z)@rgl7o^oh Cܼe:RRy\U!J| ރfwh&'fssLn0$?U2BDLR)(9l޾Az^}ba1'H5yM9="Lv CVϘlKOl^3 xJKh'f+޼gz|H , >9maM)IY}'/ *@QUm gw O{Ž,8HJԲ4&@׷LE-z}Bܜ9bO%*׬SvSԙf q*(RjP }Zh8 FoGyϞ?SioLAXt'Ͼwooq.-S^1m OywE"A",*Bp M%Sp cG˒B25MNcdYI!U4;m Q5 H'֋ rC \:RJBtX7t,zatd>+94Ms*KjrĹɌqt9Zy\pLֳX\?)MҘ>[SZ!Gsi(23Vg3 jb@KCZBy .ux'x0Fh#5 \l-H$.l)i"֙Nk'ھEjE>FGa AS&ԣ%p.P9 #< rHiSP(SnA܄([;@#8Aa1?`R}z~xF!DbF3JeN 9x:%&޾~CTe"H<1ag?=/aX4lV+ڶKg/9vD),ψBE~Nc2͓97xvCuvy!~1 h(2 $l6,h(%%g5r}s> H'x|E¤8{'.:՗RDT9҂ &v-M^%5EzkMߧ\ HQdLaDȐXޡuJL>}<ɻT8U2b$U=Ec E*v*WLc䗿xwpq9O80L\?:#C(%@QT@d}`eۛ;lFUWdfsrUҠ_ $IuY)Wd:gق0Ę2֒ 1l6H$DpX#%S-8KpH4ΦdeBi>hq*iS2jlDv('(g9iۖ<7<N6-h㑡HC b0y["+EXUJ,ZB҂:B\!i{. C~8?DR6]^RbM$CQ1yqrlqNi:(SVlnaJz9(O"gJ)y/Vi &Й8 %$Ms`GV )/~&r]a_[#ݱO QwGơMfgA8rw{O#2"ݻlFf*L!czɵ#Slv( wGÀ J5_D6uO;RrQiS+ $OVd4=Og8& iLNܡB?c./ͻ-ywd"ct mC*O q8 eVt}{ڶS/{YNp=}a"4yf RM>&Hg{^Wlxs?XdJƾ#S4Z ÇKzIRxtDOu22bfudE/D|HIad*I(GHy>e4QEӳ@a{y m39 \JCj^PV99e3חNlJI5+qD(1YEЏ^Y67̧UQg5kRwx:1p䅠GLR,d"c5_gf˦qy)XJZ{ʖP%8 4g{(m֑ۥdU^GƔ2Cte"qa2x~2aBkqd~ȵ"RDBpkqDX J3M=R)tqZٟ%1nr'frm 6𡪕18 aHJɼg]꜋^M~K`;%O?aLɠkFU&NSY-+#mK QZ|*lqŏx^Ȝ~it#Yb=x$RSy{ْY-pg"2 ywO>~@s~-1 C3iB!,ISeBEM]-hSH4@TbtF. f&%Ñ)Y#qFȋ_psuYD7Vs}*4yrUC0#a|\rg?9goy[6- (xssjm0`&a)o/.XUBʑ| }zEwH8)zIF5/Y˿oپT "HQzOPJ+~0b{?}EN.s)i9ҰZ^01b2N h׹~z9vzJ*(2ulj*(rQ IDAT #.3>h_gEr։Q55"a4NϿjvfՒG_}de9O1RcJ7j6)98{fHx⦑ ŞnK7VkJȈnHbI5/B{f> C@ pq~{f`'vR|^cLZ4́A5gL>r8luREJÈaaGGPU%BF\Gl l CCL Z/CBh4]2/ח)ICNՂ nniw >s]dJ8džCAYִǴ) Ś:&fCO>~1"xG7ugFƩM De$+(kVe⫯巯y9FX :h ̲:N? r>'ϰK0#T=`,@+ۿo麎:n8h씼 4%d,m :U64MR'Ƌ)EWB;)5R$f^#vJ}GFU(]Ѝ 3VW'ƞGD')LDtƇtbn%3*dYՌnB4 FaG'j -Hw}Q)B&J2$[Dsz|^PKvێ}N6Te4AEw)yZ8"D&gq#7KbwvMPy- j-r%@ 6yVC@?yB0N1 i#Wk2G]R֥%;9O ڌ(3J1F7g@~C,g4/38 A8ɴ%NI,='ƔF-$[wDbLp iB=Te71;%NKHEQ[190Ydr-uʙ JZ`qduR RHҸ&Ӊ 9hU1ZzGxxxJ&R4JM8va[^NZѶS)STIn V]ug+jo#R))qSI* D̫%؃ʐA!L2V[Nו)ԃ0u@4iURާz|b|1i^৑DŽd(S3C5A7NԳԝ*g8ێjV\.D2Ggq)diW/k`6 H&u9BD~ucR[TTFcp*}GϮ|N~xo޿X-x{Wg688#F7/WWWXȫo^~@(e8=vJL&o8kP!36ɟ7;Tу,u̳Y>1 ;]lbz-k޾p8c&7 by ]߃Hv햮o)˜*UƩmR DZxk%@ B* X&z2Pa@؎LTLJ]괒+~G&*EV92iP)1 sbh@/˚٬"4wp. ˚spCPu#֎ ٳ Hwh1FR0}qm;zmX0K"3k( xL{ Zet >$>%֍ #.jBd8{F|8D8G1UkZOS> }c0Md\tS@I6_+݉ub4u֡R&0H'YD  N!kUMdWbBƩc.r./lfpEˌi ">~0sq\/3c*?`}swly)О"9>Jْݡ%+f/ 0ʬ.BU a{btL'AZR:q~v4ޣPZgiX,]lQ38p6`|m->% S`dr[?V=lY6t=FE%v"yݐ;v r~w9fh,z>%T%m?Z^p< ln< p ~BPl6Jq~³gx{c*8v"WU8Uz~l5'7R ~4`I]*㽧'C?3*˯ҡM2-L<[PK.V'_1 J6z?M2 D|ةG ɋ/>(m#.@3fǹxZyVWk2 0/5"ͻ7ܼ5}@k"9t#vmY/4]s@d}5 ZC(v%Y#c!8o:5zǁټ* m?.edBHHxM.jBT-Sk57[tc`yd⛿/~_A8io'泤DFCN?JG=t<~4pa˟ \_%b5bˆ0W#Cԉ.`ZwRhȱEJxKx*'PB@HYQ9!"1JHK5YQcaϝdxZU@Cއ0}oE"(aȴaV'8)7ġ9z<-H S,ǹ &OLc{O3*20L{{'EJ~*+. ARf lXHlOzPق%CF!=]͌?#i&B&euj\?#,G,Wo߽x!%hWvlDe|@?F EFY(T6 A(bH`byh BlDgn@QՔgi+qv>,nѢD MT=ηx Ʊ ^Q?uZ3Uy4up fH1hqEHدl)c $;յR ;1)dROIã,1"uG1,g5$%LxfG S#炭\TIH A"@Ɛͩ6@)4rBIxjtӈ#PA;2B%uRJD+Iʼnc$`~$%<{&g)xK~774(vBT!iHlJ*0Zb`^̲2cFxv\]ӧw~o^}f5ղū|udo !Nzaj䳏y ݞw7wlWdEΛ7>\&ڶZX`6DL T%dA<%4*7HQY  {w=v7':\\\qsajh=yaڶ%'ҐkͬQ5d;@YJ"08B000Mݞ) :{,ZS57ﹿyE:fB+d C{htF'u$ϊdњϖ,KnBe ?yڮa4kBl#Ԟz*jVoӈ'Ti ,g'ӛ7b 2h,3w Ca{}J&4`>)Bd) U u8}l1h>#7=ٌsnoo3yNJd:TFkR&sk{hĔS*}fB8%AN\!QwHsۛ=_oW#54%@DvG^(/W(77aLWafs-}y~O ~뇿I7@3Fl,8ˏyE2Hґt(w`1xy99#ET'V(aY 2g#B,穢߷9_Ќ,%Mhp%PҢUDT,7T%޽b6%楦݌2,o,(X-Ȳ oGۆf wu?'0 (rRW^p&QV V9zM=bۗ}8 C 35#R(.RK~%77ѷ_?AgKr-/q+Ԝ?N F)i|$ח<~1%]0 yjFJ8MDsi^?]w;gK w700=UUg0)=:NVT3px~CUIv ?rKeG~ʎHѱX̎A9"M- CBUq,a^:n7>ϷłĻwohC`fU?a7T"|Kj|( 8#] IHFVE'?O)Ixl;(|Mn-6<|ƓOY;_;iVH؇@Zgl?k@H>^rIf(y[i$ ~#ƚ :63)6m//f4%>mHL}G=Je-z>RdQ2g]GgFկ{bFw;햌pNaGkMYG*/L{ d 111 ;ݚ( ,T(QPUvL芶t P)eq\Qe{x b.lw,Ǝ\߼GiDyJV,O8]=cV=fa4ԧݻ+ 7E@97t)-Z)Ʃ?y#e]P%uS2 XXn HQ Ӷu] $-U:FB(!n/* [*C61\[I:}sD9 sHMn`Ϳk \l%̍GJ}w Zgx&1_lQXDBfH m@LBfR `jAUZa" /Q"@2Wb"ƀ#J$ #!fRȔ̯QTcCMiރ( Fص|!EZv5?KQ(5UU)EaHCK)d%$_^Xi$:R5XFiN{2T匪\#Ň bd2X4 (RF΍4kjsAY-@yq1RDh.яZ5uYA5Q\$ %I O:BKPQ"iTE$*eиB h`^I.ru_51?>DP\U)%14"BD8?Y(-28 eO眝 C%ox5g'6޽CgO-f_ps{٣*4X/ _;/J2MC>7t炲+)\qux[k̔FR5s"( O|gJ1pt&MlE0tRY"⧀+y愈 r^ƈsQdߑR<aQ͆) rtv62ms+|~ZZaa:JUa5Bwbr9GttDR@Q(B5Ą--4mDY2W^[IxɧיIf`s?۷]QW}pZozS'bЮ3HEJ)lb16ߥ>ű eZk)2Bw7T¤|?%f,[Îp5$ri~}cv( )z #2% ?wQp a3_<}k}9u~8?/a13M w$$Z+RrlC 7D$PhX|ל=-){f~sbS(@zyrgy肻۞W]f-O-V' c/s<-\̐$Hd8u t Iύd ,VTlZ,f JBatGgOط]fa , Ł, INpzĬYR3^~ɡ;Ўl ^<{^`6F &Gǘ"/j!D%wwwzq@EISֈs^aww)ʚ 枪SH#Jc`}@aMj TMfYO~]_7&ϾXoh-XNy2By矿1ׯp~%J+QV%fN?q .g󂪐j~᫯@rG}G޾cm{no޼Vf1 .0Ռ([S3 ږ51F}m)|kDU98<  -L1C`Qani"ȓ'9?~}EԉEJjU ڶ4znscjvJZ{ VerQruw+/V'|̮SHcc>aܳ^Ňz4ƞp Ä@,4eU$h#x<`tW^o*3𚪦0McŌG]id2`6-M0ҶbJ9!.N]`!$9;|/?9bn[#Lûy##N8?2V5cGa t-0 =)ŜSG:1%(Z!y?]K )"(US [8vQ7 7ueh1B[XRLG|B禄 "tlgP )eؼ IDAT4$Æ5y$!# BQU ,"sv2>_‚;옆=U6"}y!<" s'0*i-nS(enż[RycZG+ 08pCWIhXK3CvP6>زqC@ Rt|#UBMH ʐ22.D6CnB'%I(a:VO/y C釈O`UʭQRi ]10b 8H[BmM2$p2&\DPk) OLɑ#DUpqZm4s੫ Z4\߾;B! -{l4T9|8dvE!P^ +1-,1buGQlGVg _!UE }Y!I8V @F")hMiJcebVi͌,W>O ~~'Y.w|{~|ǑS4Mw- # ޽eX, R6$RĔ>C0=) RE#Shf>dWQ\V EUsw{Ol/3v}`?=Wׯ=1MyQ`h:E)JV%(J[x RGB"dw*,nIX )gp`ߣqʑ[%senסOY9 TJQ3!x℧^-LdqS H}eQc!(Y{ֻ51E>(jvf)Kϗ(&%0zOSZM}QG>u=eBhrPvuk00;֛{k4!)k#?,Yg"`,(mq\+@,EvfG$ݐڢ b h9>M^c 6iA䈕1"BHHDJ%H ܫ!\pNO.!D7|b)JٜʮOM]QZE%k)-/i эl7g+ꪡ05Ei)Lc sҵ1RL<я|}*="w$ZRL>/2EuOw$2Yxz\LMrUu#'Klmb8GwvMQ95Yݲysł0QgO=]r=~7_=US!LJ& +sg͋o7֖0aKAH)x)W77ܬ7,ND햛~ߦ\` ;bH?owÆ(lЏGRʳ[ǹܸdd:!RNhX um =W!!J$ J%bqaԳ9)-mf%> Ri!7)&"g)6-RE JbAS[1g/~7oZN/4ϟ?oDrQAc>#t7rǏ)˒zK5]p *w{5ȄSf%0 },dC*(I;\5 N\\>,gݐ[82R)l!.R1iJl[6/aG:!s$<(EY)Q i1>"f0dy{(TeIQ@A*Dq !5s‡z춑>(T\Su&R#ncS0EҔ"nB|y:bȭ+PtuҦvXMHэLa$6U3V3MQyMezclp!R%b \{>ȔnJ 8n >&774Xǀ*7|?8x&Z#`8 :XB8V$.($zFQ9P:# wTTb }v#{׌n#ݮEJKYS]aEi.16⻞q"bn<1:"|( *r#xQvOBIHyM$t=T B uv$\~3(F""[2ei>m# 9.9 }v |{|6VGU)IAwPh0 7|Q墍j6,pe4MLpp]n>#CkC{jnm7>B41Mq麈1 UU1596GPFhf˯8==lҞj*">$D]6cMfcHEdעLG'>DTebJX)0(*F?1M K0OitH+@}y$Q?3t&HQYU ̚GQG$IR|,RMBQ\pSI$JH]xǘ|]v!1bv%շl6P֒j^#"'><߆a Cx$`"ytkMc06%y{N/N_~%\p~q9w fs_Iꊶmy1 o_1==ID.. YSv I RKs/Y, s>sN͐`Xa'\\>=_FI/ agut2?nbLX'tuֆ"e)TURo⑓E95BNwLIIT;bщf'#6 ^Kk+q쯾`\ѵJ a%wRaq.b Ɗ^Xprcsph"ƞ0׿fdˏg\$4ZlY[6 }m' Jc uv “' >c 򃈓ER9>SAcw!,U^r!?Ϭ)0ML0`6ss$0VaLE uSyu[i 1f7e$Qm$nsm|Kb@D\%WcX})~Jl6{DU6Z)0h%'}YLl)+D޽nh V9 UnlGO/y׼zLŒ)XfT+u@t> Ƃg!B G5 UUd̯В\}SRKʺ3B b íڂ9Uw#)dXWׯ\nb8%4:nnqyqNa-/Sا<2%m;N ݭ O?9CGqnfms]O)kL ULcs渌=0 ( R$n, .ΰuGpk$(7kF7P*ʦhc*rT[\/nȓj޼yM7\s&āGNNKv{7%EyvkʦV5X(aRg0"EϬNnG.qwwGl]inO>ɀnûw<~1M# wW,W3>,b[Ai )sf!k>ӘgB|^2 ݼ88RQ65m )}?ҍ=₣ B ,Qy.pS@,hYJF%>Ąi( MTLQ0PKO?zLZ C҂R]7{(eX*H8_|s6oh-U0UuT>7=Z J[p{,0V9/p>ѳgHmp!ä-#>'mŮ[Ad7m%gf^!/GgNr} =v;f˚ɂ@1%r Ǽ,N]c\ ~3\eV|'?FMm ~"7) )WZr)0Z3Sf=낕,Ŝђ)+ۑ֮ɴt=K9s9Bx)iBbDĈ=š#M$<}{@jE$I"Gg[ׯaX,x֠?SIMi+ SR֛ 'tHIHH18VFfQ%2) %0`9ڒ%Ðq!G! |N2"#\U=#=E-98\41 4=T*khY.-'-LEa ĩ4d1c$mЬ9 l/1бۿ%:g>+pcD ci>;X)+z]X-Dʀk99*IS-Eo 48JJ(e| QP1>p>8ȿ#IJ>} @Y G@JV&Z "Hv¹!n*7C#;6_gVL;Dˢ@4eA$xx_*2J E5DfAG& Rd~3+RsQ2G|lZ<;Ci]D4|ՠDb|]_RXS?tL.D"c : 8IX))SIod"BHM \[BB-R FR5`$Ŭ]`LB+AI*nÏv#40XzwMp%5uSv{=xfFUz(rP0! +whX!<8TB3[rs>dgR9ݜ]y1M0_T-[Ʊ(ůb&%+=\lwV48ж{v 횢ӊpeDdȵ[ŒR:/cFjc)dVNR̮2T;l~cJ1@`LG(0 ʣS*%iʢB(MJmn>1܀R5GKE"G0U!h[CQmbhȋO>ۗt04#@>, ﯩ6߿&mwI.h І^p89YPWKnn7~;.// ΍ݾ&8dO y5";w5MUc~>B~[]= RΏ ÀL% bPwUOM(~ɢ"0" %%M;2~Vh9ܚ!L0vw[>;yt1_7Q*p~Y6VA&n8U0=N!%.Oe2MCn\]\R"&n{G̱֢ŒwcXRK̺0y9r&Oc@JKznw L"0M5Z֊v-gg%æ}VYfvHjTi.眞#v<Ø0f7;.?7 .s-;0krݛ Ȇ, d>ˍ|("R q&f|VQq͛WB]ݒ 44%.xXn9Y)?_PY\/zMH;=?|KB|UnZ1۴Lo̥,BE#meM5[ٶ]{%đHᙦRĨxzsNW\1A"+ZȜV(<$4rwʢ}EP:< Kۣ$|3NOLnL(2zRu [J.憫wSqqꜷo_;BAiu1DVA!K޾on!d"Dh-1e^7iMP,g)0yvwm/EQ0MԕzȺҕe{l8!iʵ EbBEJ3<$E6 $4mE*moYFO9>Ne%;TUEt#ߢ`}l0Ƣ`{Hf ZȌ)x6~N~8Q 2ۇ\[e$c?1]37qsG+!!׎YG_ܢ<2TV;dgV%)J795|YHU tn qI]hX߿g9?]0 miۖiҰZDo)5=Y#L.WfP2_qz6'˧Xlga P؀ҁ0t=ݾ‡s# &7̠a^S4cآ:MOD?DNV#%_?@!=s[3ivN]ϙ %WoحQڔ`1 =sJ=DG0a 7_RR*/b>O N2ˋG%Ǣyfs;rrrz(HUckc nJca~"/JSߴ5o^^5}S=dy(rD*-?;;z=J{F2_<{'2s%rYcv=eYgvNY4PZnoۮYyQc&~?__/]brLK$y>y1g'l6oځ)Ξe} ~wlqd対̎UyI})FX*C |b8!b[֛)KaHTe'+B$no=mgSMډl)flr_.f|審of[^|Ţ!v޼e) ͓3_n;ASłf] RVJ]9Gf67nS81+J%$D6}Gp zd4ԓDPUIgeR")|ʎG#, fT(*x޿aG^8k6[Rp1 Rh'?wos[i=>efDFil&P\%1,$f ̐P 1bԠJ d8at:Ȍ~u ։k/xķ>kGHx%U2$83:ّ =L cCBU^)}-9H\ĿrqJ0/*D#IbZByx,AEL()y9(|BSVp{ ' RR Jq;sTm+<(c&LDp3C鸁%I)5DHt<7޳^o|@j0ސ)Ə‹LJIeZ),%ى.ƭ*%KXoc2 O%jfB*$ce}"ce ib,ލ$IrHl#umK)~}PqcL8!s=ݚlԤRp>MjR#Ts#]4/UUh{3p<.+6;z3aa6b}Y2PWg͌AZÉtt|8POS|5iS9L@Nq)dS9z@tDc'+g&R4`+.g_j ,SIo%JaCh"Fk %eT)glo^߭h#~''{^Q_/xK*;z3d@%~`2&}SL$ݎaYYoDM#=XNUQrrrf/"iR={lFf]#аhP3$cZ,'sqq[3!=m" Ù$1*M'cC]#?G8?/s=~ TdZӵSb" 33&EjPv#y*RQd O=%)H|Z `>RH,˨ ]er mLGiR$frw D O>+[pNITz0pyV AɌj`1?8?_]ĺfaŋ|%cB8e:,4B6T2 =ǁdbU8s0c1#bq$U)~uu>Lud?WJo9R*=i5q8?@*ŶAs..ђ3/,Ccl5mX -'⌼̨JEu4|'l;d gg^m_5~SG :ēw~k??yNclCij.g9y!9ݮ/qX)wwk.~5A 0HـpvZTxAQܮ.Yobnx=ϟ|<xż?x ]<˯> :Q%gǏ9ݮGM׎l7o8??'Vy& gzEB3)Os8gv|}lQc`v99=.+h\Aɷ;O+cO3ͳ/nw44Cۗ_)O(}vtnCRtpo}h ݎih";^oh(=*I)?$'x 87 8KzB(h<#8K=T/$Pߵwkpt|ݎwlwi,(Q< Y 6WRS%mkƺɌD緿1o/EyR3M+3l,^F)۞vۑfv;N/UEF ]Zd>_Ʈ~MH|-g!)1#[lV3NБ"%;Dk1`'+2f I)5hp Wdvg"V&kPI1MZ*q`$,"TL݈̲0Ús(nEDf16l;"ƣdy7;ʼ<NdY1 &Z#w$yN]͹?pbG42,ldy Ʉ8Bc Q}[idMTG]jT$>f#VB ߑi#'A4k7oQǼy5Z%c ggY,c"(XTIx`DV$yWtrVGG3v{^]r{3r}8ґkL,ݱٯlOz1,Y,;V+tr:,#cZY>zv ъfױ.Svo={^& 99;ډWºQMY\^ANnZ)@} MfX)@q :Ș3@1 !(ʣJf@gP-p3! Gi` YY l;6z7gÓ咬:1)NN997W(OOig\84K88O4 ^s|rO^hvKE1NW<kJ:`"T{f>{Hv۰oFbr#d'+nH Q0Cݮ<`l<]g)1~=}s=g=q`z˶ ,˸xݾ(-(º(ђCt;_/)r{{"7k&]0MG<8999 E_gѴ=.f;xM~G?/ Rp{E^Yhڑ,+xGxA?Y`krȒQё9$˯^u0bE@3lwC >b]quy ]jx/1RьSTxoج4h`ZsYI'mK4'_˔y􄳓'>]KV~CJd$:GA4 /HLrDj]Ki/1-Y%YR'G7l[ֻ5ټh~Ƨ|ƪf ;b-%IvEH^ÞfϞ~iz<(58u2횿?ӳ뗬7[ir1.`21Qx"G! U Bi`VfņNR=i#:5R#":iTFh$(r!x$-J)q<~iѴ-݆~"KZD8v@ȸ22%RHjHAiCW^h- !nI0AO2^̎~.^ǖ,+ѩwtCK";l5Vy3޾}-I֣tzHo8g"F"Ak{8 Kh%jCN>(fhCY8 Ir$AP1I#Ph05xn_,d3!Y(iޒSӆa-ubFCūWL~^D0:3Z>Vrs;!Q9mlZi gLD4Lcҕ̘Fn‘qT&YM@9Kj#Eyz8D]bloW[`v=Z9Pac(0%OKUGv?1-A#:2ATtL߀Ha, %=x/*vӻH;(n pj$Y)cBI%AjL֓jdE%BF3oPi-5SNRH)Kfu0=⽧\y qӪ*!Hc>ڦ pzS0B[C]/HaAGZ.*d'f5x9,Jt"T2UN%" Op -uvYD#>u7F=+>B$x.UL'Y\#ų|ЊgaYN-tHaxIaM Jd)U0/yej=e893_,%'s>Ox5?[Ǒr"`$o-XPE|#.xc1CFk"Bܯ4QKn}dk77dYƸi,cSJͮCUvM;|YyREEATՓG$J"xĪ@1LJx\]ήZ5$2DgGP)gggb& 8gO[ W&S9Yf!Vq_;x^J^i48;Le"2$$$mOc HZtI IDAT\FhۖnQJPUN5$/wfY6}-kBe21\(ygUGCEf]#S2Md$eY=kuim *8R#T|NB }ߓ$"FȲH>᭠(*ѣcu<A4TaJҨALacJb'f%ZdiV \dxPW t\U ^4 m3`|`8!j&yʲ_Y81NM,BDѴ`|*[ ŭg]DrttteN#EHLgl"AJMߏH-(˜Mk Q(? e͎@@(HSX -kKBr~!)~0v[ag=EEΈ?$?$K*|5! {1(-IkpϮlgł Z[k o Byz"=ܮy{|syUD4Z|T=mc Zܽ~4/kq>4MC5KIt4M1:M78=7iR ߼`k(9 Yrwwï>%O{/!p7,f"0L1l"(8psY`1[48<{n#I/l63ೆW^YRyI88H;ƩgFQT9B:LōfP +..aPR =xJ24PWs{{}O{B(@npLd ,ȃ;-2fkA@sk"؃xbܯTta@{'T0K(=K~AGꞶo(gY㭉N'ӄPW~n۰X,O;f4 d2xpn0X{:3|384`^Z(򊪚C'Ed~0%##իWC7FɌ<[aڸ:ik%"_ E.& ,HtȶwԻKŲ$@?1JьiRXQ?5B)899~[g2zi-yq^mb"-ġ˯_5: =XvrsNKV ]h$fxhELxz/u]Gy fT:wda[q}sE'Ubu{sjM׍` [6[x v΂聲&˃ł^~a"&|oh6V/%E ͆v߰XT| ҴD)MT,ffTUU͏~#*eSѦu=K"ṼPWѷszrWiGbBoq&ZCp*!ãU !##E^cF4@۷C؀`d.ay5V,|`Y6\ߑgpzrbqחz=SqѬ-(ҌqHf9?b~b8ch=A'91#ݖi˜(YYZ@%L޲߭gWtEim{<(': M!LC+Bҏ]b57+z/9;X.Hiۖz-*$iR&$3tD\ϳǟ9ML(Z8^ ky@x"PQBLDئi,!Hؠ:%-%k,6C6#2DÀ,%cB RF&1)!=ѧD Re*bj@@qx kh JEP#|ORNB8cum%"5jcD C]N;2I hb섛Fp)RHS!4BqT\C{%#KD"{Tzɣ!w#l:KtL ߏJ;StcnY,_<z+uy~?]c,R˘H q𔛻kv-}אd~IoX/k)2&5sy}R yI`‡q[RK8QsY-]TS8,Z >Z}BJ)ߥ &o-J~B%E#)~'Ukx Y̏"a׿zV✋&:gY1 ђ_I TS y*U #SS3=]LSíQ;9k`s#{V+Ҽ`LAXھcbߓR:H6nq0fLE;|[^|A߳ݮlV$IB]k=|@Xc)gH]'|c{^˲4MYnOw[vqsLABJ)D PL# 1bUUBVedѸ7k_uܒߐ[w{{q}s<"UX_\_ge,^wʤdZTn{ub),m2NMJdص )ʚ|E-ZQf3^w_syDb(2.%AM# C`^g-+cG)X,fyZ^xgg+={ts\;QPLsvLq#>$#+ IN('dM )_x?a8+{gyw~lg`9_qs~hyJ?t&"uNf >|G<%˲d2M)au]ZH+x}9Zk6#W|Jk<')3Qzlv7kS݁Hq85LI)&'h7^%zTDY(u{ Ǯcx?[9]Ϳӥp'7J*Oc=~r.#g۠Uƫ]W?)yl!KՓb]6 [FgHӥ$A}u>VaD=[u/) EYh1rDDb62gX-\]Ay)(s=Sry{CuqF$U#ɲ G}r& d2%uH 3)ED'tD`g\ ;[2&OHI&ڑZ8|Lq""d-HR#mĹ"Ҷ$1'jmQ'd=ĔRIO-d{ Й񥩴je'DHtgOcp.m<}B$9 GO6iӀp}qN])U܉4YJiHzڶEv\_\mLC>$bJa}f.(A7MHa0R`Csi)ɵJR!ĤFB H6D e2&:(Q}{)uH+Fp\#j>Bn>Cww{wl1J4;H5.( [| e2X?1M6~M:^\>&vK)!0Ijl69wl7 _}xo7<߳߁䅤6H 0*C7 c4 SO^}bezN?mQp6y[[tе}:P2ùa D"g|Ղ}9=1Yq0SU38i{,eK(y:haHZ$޳OI:|%Nf( !a)yn0yFw\ C[ÎY]`ݻ{2No[ T3Ŭ87:g M(PR6p: ݶ[iɖYB h;E !TH]m0l2aO':4ؔ*ℒ !=~:U$HJfL&)(,UGG)A1Mc t݄SE Y&G ozn=ewȘcѷG&gOg|w|=sEera$982d 컆" KþMUvs/*,KnZ~w |w ]@ 6TE[әZx(sļH .߿-YVItJ?qADLs)5nBɔ~X[R&ЫN5e,e8LNYf޲_0RhVp F-:YBX2kc=Mۧ yɺ4[(>;8P>ClS 8;;cVVwmGv{^zV` 7УDZޥeaxw4,qL͛uB:Cl[?lN:}O۶ CT!12I3r2U"%sHiF46a*Ϙ rݔVQʐg_= vGM O7fU5CHrQs~~IsHgO:/ w Ð.`t%m;ҵ&$>:no⛯@DǓKZ 1ۑ4,ajH<`] - D-]m{ʢFgfTrnȪ%+$h,˒NLxǑ?_orO?a>[6M[I~ ) e+2M>H 0F"R%E:3EMO4[""1(3 U3ҠL26i8Z]P%0QdSdg:O|tc "/'wd\`a3=1@Uku4L~Jh!tx-Mx!!ͳR COPm*p8l֢NjVyvg/OTygdyf4-w {t6Q:Sb@7y3Zy?[Jo/9<(K|ԌVp8u B@Bv<(,bA՜au>R~ }KbԳZt`J Mt]0٘sZ̫#_{Pa<|A) A8[ee%Cɴ컁 l+Tú?)6ա;&o:]H,膉ͮ#uΒ':}@D?%Pe NJӆ g$U=O5DX (H]Wh-n<4DJ(B &ЧnIICi F; Tkym> mC!3iҶ^i!:G >͸P֡MҎBdBi|W% 8)04dHҳN.iOz51N->Ԕɋ9߼5|s./Dt dd%[Nti1R ئۓ~?=gpyzyqJeSd6wk^E5^^r_}|%f5yYW9vB蘆tS]t!phTET5=Г*= &Ff*%ZGY3C=<~t4Mxx-k!OO߿f_f ?c>|$L?bg'|)J'Hv,}.H]ϙ-f}C7S#+L̝80՜Kn\ߢUD4is=Yr|XLJ)mg}Ji+NM ɉ6֥*[@IRq$]F黑 ԥ@E Tb\D Îwovby6s80 !DVg3b<']aO|ł/JѶud@ 6p3&Bt`("B@gyNDJ!VB9viw!C< R-xY&YGp `-N&Se{TRJN#n%oo'b#˒N]( ,O\# ݱp'ϣ( d0rmyz2|ӧLÑW6 aEɔcLop~wݫ=:!Wȳ9[; /$˫9ʗ ?2>MxĩfjyF=dyY#Dwu,KlFk!G4qn" ޿KK==4)sC@!TNϸ^5>9ZY.f.cRq-+j }d Ei.ӂ@Hű٬4݈R\'ÑaY,3ij'%R-=gRA$:eɌc#b d:?GFiϟ~ӧx4  ~I]bu9}hv<}zMf~Cf9M.x5qQp}y0t=vH6bQ씒7Mvx3ꗿƹT@a2"Kz(v@l&|Bc;pܵ\^ܰH-+|hri8 (њ<{-Y_1Wki e= a*oxzvˬ3J2%YV0膉ɥAb"xn=YqnO So`5 e90 eY|L7XG֧; H:4at%V zQq1Y@ӵF)mg:?c) R} zBSQ&򤴆 ;kTCNĽI)3sm R+H^sdYIbT m1-Y.:Y)ʢF0O8ܠD. iƆʖb81I3dyNrB?0 2rh2 TJADO fsT?4G/Ĥ2SpڴJB4͑恾oSJMGʙfi! `ߣM;"!&>8EHr2j?w~j#.5 @Vd`،t`'>yq ֻ5Ώho2[h 0#.}?9lwZkC1d/%H"B$NR`:}V33B\^]1/Wh2إ >CH5Sq~:ځL6"Y ?`Ǵ#Bh$~LPcLYZRaLS*%qICL \2;tHtǘvdRq\a$ә"GG =S('{@t`ljUs)B߷|uWWuJl6h(Ѧ,톧^/?'Hë{G>g<~3CJ< ͖qͬxr{fHKIvQTO8Ǩ)%]pb8#9bH*&bTwJ%DQѷ#h]Ҁ9 )"x R ,Z $"!J9Fpg#FqMO/hv{Zդ_}w 5&Kx0*h[>ۦl9?OPq!x?}pvcAX8m/1#ZK(hyű^h}iYeg(8-ggOh[p53\=%#7i:~Ѫ;( eƷ/.%(2*RU7/Z^~77xx|{{H6v5e:TLST;׻T!,k^Q C4 JLc82j]Kt g%3I:֣}o#Iټ=qn"1`G !U4#`=Uug#D4Fá#x Ap<el1aj]2qx:z0ÔeؐX'3EzNvc}R@%nwzFjAQ/x|];0 ^ܿGJ("WSR?>. WWW1-R 3:+qA$+T$`>*8??MZ2YyQU#U c(㎾0 {DXޏ'$"'G<>X} x{A?HmGˢ*Y.ϘS?ٯrӿeV'Rx. #2ZiTAh'"= 8ӁA&5VÆB?vD02c-vw uȨQG-O `ߤ1x1a,s1gjq232G|SU2g DyU}vH  a1`9/)g%(@JHC[fK~$EQ1 6ɮ"y9grDcqe nD46(Íx:cZVg5EbyXmkDLc77?lyCYY.Sz=]@=D?$-G|~ЈT(CK7tCr9OL02)%5ŒYo4$/ <8+r\#N)N18HupZ7)*X2~ ؏)-mpLnG(1N}i/77ܽhS Iu@!"ep.^ cjD*ã[fuh~ЎH> (C1 L[yGTK\t4m0x6K#RVYJb$飖3Y4pP J,,$9*Ec2cn 3鱭NjP`]t1(4&铄.pO\X,yŜk QT%r۷o9v-˳jIQ m]Ѻ !1Z!5dYdANm_Q4)¾g mbWU3;v=^s;MŋXΩÁvKtr4>g:ޜKG)|kGΦޘ{NEL0dNLgtMJب6Z2CDcY>"tEhټJI~L[1D #`!*2yDVS[Nl'yjacbe-+ʲD)nwuyoFٽú;BY>\^!ea,OyKrlvABPcBLFo\^.) NLKO .UY) R*d$: dT,B\d3Fk\'8'4 IVS./.Ǒc>' R2yd-˒1Zn xEY-Pu1XQRJQtGi L7ނQ Gai|*'ޱ:SW'V\?Z~fbR,12Ӽ{J<]TE9믿kGrて]#yӴaA <"Ӟ~Ksl,)@ '`?i^[=|O/fɘ7v1-euZ `+6R.ʏ~hU术FO CsL8J(Y[\]~Op}Cg81R3eꂼLW?úF}O? %ܛbs(rVK2{>0򰻣9>{_( Ϙ+=7{šٳ7ԵF|ȋ??_m29Y Ϙ*yhB`cpnve$A<~zM Ƥ[Lݻox%T[KU Q gr) lIx٢*;1s=Koa;!ՓZ8xRugV3OZԁ*=yUC@:#iM8e>۳GDP ckY?ɊБ~tG0N=''߿ch-O?y̋O?״$YQtOp0'O.n#BQh[l^$vkx{//5DEh-u5z=u8u Ĝ1C)ҩJ1zk\:/gH%يqd;=PKVˡmxY]=a>eY'?CK~S׼y. #yc'O]  Q y.IP:U&dB'Hi'OfDd-eYXqA!u|a?[lmc)̂O?}7l2yGۥ@Y\`r5coF0\^<qI% ~mY+˒KR$H'! I&mɤjVH)F$}^@;d["TJa{-mZ*"y!Yoz!!V?f4֒o2,T9x\kJw#Đ;ZOd 1ŇBb߸'er|;Ɔ %j=2zR3LVtz'DhۖnOUBUSw 0&N#1aN I)nݻ7HM"߿crdA>r&BH xb躎~.d9C86q51ՂBy钕+O .Dn ˎTp=)c"JJ`Ƥ놉NSs=K@n+|hije)0c^)E >/躎w:veNuY)!xs2)HuI;ynD=QHc,9-RkY"n "OⓔZexGOC0;L1VX$MQd CPxslrv}݀s(_%WiòmP1C̬"U!ʆGQdI6i(hdHead{SK=Bj4 5RiI1Be#1NMk"HH' mcH[+o^~/~W8P$^ }((zGi4&(bn6t,g ݟ'7݀:vs 7^})5%׏o-llvw8J_a\8Bg&AAE 4"/ *~R6o#yal6aϩo>d3'UhL_pp<myhNukt]-PRx2D \DeaAg9YX7ɦgRhaBPg .߾|&G4GR; թs|`p Rjnl0tV @sܲp}vCUIܐek~b\^^2yLJ\?a6?=)L٪$9 b`a@iPI '^w#L# k9RZS#@AK 1J2~B2˫TYZ7ypue]1؉ &B$Çmk'O`k",9y}|hRąl?6pɹɰc'Y?w?loy^8^C`z Oitn["gyv!&xRҴcB.) ֔grO˿f^S>90vo߼dۛ?i|mJr.y|%e=7H4=ݛXyϚxsʪbOI Ɇ% 3|{W6`2mP Pt9XU]cfUDdD8q=TʿT"8gﵾ}g_E MkZzFI>;}abt%I8)IetG( f%BH~ܾӤAWJCqz60u(9=-5EMUU0>qhْbJ۶.%\i;a m}# c(Lꌼig,ӳJV0Rgtl;%i),(2CdF<\`~-~`3Џ=3Oɼ~u8zPp~a}N0VУGG,Ôq (ʌlA>4 ~KqmGC;6bc)%dO$*0YćqO7lY')C~3L>D.XwH.7 K!!M$:qB R&R%4rYc /+ǀ"tjr?,| ?H^n`ݳ-f9ZG$ VGeT!@%bLL!#Zc"CB,VTF)5e֛ NOPZ1Op~7|5 N'T vg^O0'Ѭ12j;$erZ.)˂̔l7=t2 "_/й.gX9=FUMfv ^c ^Iɮ_=zP xx">ETaH5L)|l:G1ڞHII+طW#$!x0 EӁMlz 96ȘLY.Ohzo :0>g%l@N$+&Ddie# '%QFڡGд[>7hтp&cyzBYX77ʢ0bH##đѦi@NNk6G֛N);si3ՠ ">FYPi 0 ©H/ew\ΘL&<d*TLj..A=]G@&&PGlk=t)|dei3)Å4юRwչ<%,]J}h$/ Fg.aĎ؍{F]M~g MG?Z<{z=,lȧqk1[2 6ZCl~juG7XL+é.խH) G"ХԉAk |M<'?zɇG@fĶ;vbhh-YAn4{YŇ| ;rwv8X\ ?9t= zdIQL3atC/'v;-5IN7$qEv| & u]2ؑ~ffk!Cbzuav9PIEbZ-.8b݈PDH ER]&bTS&oyQ2YOUyt"lvHV!!㐆B2Cl9cZ!ur(!%2Z <榡dA!pN ɉ^!|ͯ7l6m t̠ST IuA)M <-EҤLKXG^e(esîc[|8Gg=1D(AB/N9]l78]3zh"˙.40R)Eśon9;kx{{`)d<~͡QC׼y}< L}{DH&Ge'@O 7Żo+?H(޳^xEJͤ*mރ }?m`)(02)1.f2i"NMjvKb Xrv~J,gw$=4289;ū~CY69_rz>#=Kv=Mar{{K5Ԭ7=.4w~ρ/-Æ2caYK4;f.Nqrr/C!r)#O(˒r2/i*䆢*x)%w+WyN#+ͷ_4{#}|//@X^~*TOx{%uX&HoL:L'9E2c"BQWxܡu.JYng0 c(˒'O~@;7D<)JNϗIs4}72&Sfsʩ{և;Мg%UmJ q!62y@8B54Lkf:e٦gvc(`R/ycϾÏrS <+7.Օ2BhU]de*c>~aty!1F%]5g/ /W/k iTG*Tj մ x&/~uEA~fӴu6̐2C)pt#U)&SqGAؙNf kՂ (]2[uF(~WC[n~G^YΡcKf4MY#;2rD錇{17PLHUKD0,soC_lPkCU/SL8tm4݀_s؏4m>~`CuDG'ƃLjɲxJII'%%taB<9JcW"{@H$0{vmÎ]2Hɞnɳ Qi|iMW8cDZ1">֔2|2|я~bdhy!Q2\3,GpҟG8&H#Zt;L*N'TyK<1K*?*U,exH0y({-Zyӧ2g:5_~BFbZhڞݡ4{ f}[uMw`'Fۻ4ɺv8tL/?xzҍ]8cUlh<-R00&ՊЏ*ж{Up8gid2.fAHc bK Z7FcHjeaa(MMF6!O=IlmQQ DZRfQʑsmmH iP;w% _/hU)p.0Orn6:?( ;L2-8=[{&ӂby\,Oj˚-UUQ>#J ZKrmvO~cXrEU》%YNUGƑd3ie؏tE)*d]2Fx|tZsh6 T2n$8re0{a.C( $=!Ld{ ,1bLd@YXNϭO^2}Za E 2Gwߠdq!$6<4|zP~!) 0:w{Dt}g6N{ < g-dγsaO7tK?=LsCR*~)+:6( wwv#\^O$W|{uG?˯Y0һ(3OYL/䜡l5blDekuA]W\-nZ0tv6l>̨g<?ݛ x_~w (GU)2}guJ$mKBYe@`>x7ryyT%;jtV+:}C<<5;+G+E@Q9qv> SL& γӏY_pssäzʫ,fs:>w2*))%-o8*G&E#mk%wm47_=Jr9K5]Spqqfbߢ :O^Te_|WQԓrӏ} ( :Hh=~آL 0@2rc}Of k$o ,#0@hٔ3p} UH=`)z&Rc#2o 2IjIrf3Lr_7=*󜜞sA{?  l įZ0Od=N9L!#"?b]QDi#5YQW3fslpa[>/b@ad e #-E9YNкΘL ſnu.iEA3Fā4L''\`mEfKcd~'{ןݭ(JT"cQB:'q4Z@%1((*jFWXAUqƨ O;d9Rh&VlUYK!EJ8 EVbTkY..!xXW cdZmxW}^Nqz'8Yqr{5ɒgz jy ;&h>ZeY wdl>:>qO2Ӂotٔx|\3ڞ"D?ru&]J=QfoL(rҡ32&s;0=eu{ۓҖT0"."2%s x6$`Jtc'SL2Nѡy ֣rX92yooyzEY0[i6]Đ.jJ*@^x]J#Y@h3t1ujah>wC; adWe_]ߣu$h,2L2Ѕ?cnJ,9VҠH<(ӧHnow8Ȋx+ IDAT("kfSBEcEjِe &#z|PPMgh%ct}-y^9<1q DLc)uƤ?#K~89Ώ5Tтt:&uAa2fՔ~k^ \\ҷ"+Ơ=cͮ9. ^)L88|G?L.W|{5O_P.ro7+v>8 &x)w|{}п廛;!PTyJMTms[[I% B˿7~}LHd.Z+^q{w0]~)>U U&Ex&?$CFYDDHz ل~DX "-7 cd^j,GD&m\UF3EG0ZLQ2E{puBJĨ2ex+֓AttKFNECz G)rô{@$bp6PW'( }~owe62)Α*2Y1ex'',z~跨架!UL'#M`G9}߿6Mg9t]匓%o\fMMGsrv"[fS8?ϨjAGOzocrrSpKc/8Mz9u99zM c?q/ADA@i2Ј`02%Î) ]0@Y1YJb) eZ%f.' pi)%^H>MfAk=y\zG ar^C Dyax9$0ؤYJ,馷pɓsa.>n膑<'++V[~ɲ #'Ox%͆~tnUAc=:a͛7dقs]uWdguXF~뷞 |`;n!ע9yb/3Dl7 MΡFH|IzK3?#ʢO?#Lba\ ÎPNg4?9^}4ٴlQж]暡\J|7ӟ-5m4M2t>z 7uupP%K.8;]2 |C`OΞ1O˯X>з)?/ ?鋧7tmHn@H!z9 \n1d,m\]CiS5 mޢ5YN/ ;D}:Q ڶG qϞm{@_~C4XV,*` ~;NUCCnr `( &Xe ˙i=yRU-U].35uMfJPG^DzNX"x8sJnHFN Z2Ŏ7#vHp'6\D@E3Bdti"7LSVh.X=O|e4xcIQ$1j{(mf^I۷(ʜa0FS%xryAlwjk;'Nx4RII,e(X67RWh\!JQ R%XKYᵖ f %#p|Ə~bð mH^z=Z\Nxv͎%*ɫܯvM.U]w `Gݎ͡azRo2򃏗`(2"v.wz̏(M۶AFZRҶcJD& =Bgfi(RMg)],HYd2IMx)d ReMǞ˗,N/Y?/ u;M|A ,Ҡ3AaJ2+$JV5JeA%X~fH 4m۴rl=y}>[3#o>4₲UAYܾaជ͊^$3!-JG!"(ҦdRUJuM$MK oϞ?>j)o>'FxeIt;!1G~kiAQuJAUv[=:Eޗ" ؕsLuj12Cʴ-6T4M:1DTHq(mJY^dѢG)ɔ2h'HiJW,S`ۥڲK߳ط (^ v4!bzJ E\Ei=yi)?Y=<͗GsDF00gg 3IͤmHîC˞xw4x)M{!,WHq.y!SM &JIΖ{LYgb:ϩql+Ϟ2f̀\:=hƎ鏂=#S=?9f+| W4큆dnO~/o99[brX׽cǡB|gsU3:#MG> 6ԥa1+~) '>/Ǒ9'O<E1!ju= Y~0 (}Sj2g:;!Ja/?5&*%A=}gO/pc7͖ffbe<8Fʲ&FfچS{R<{^>`Zz믿-eCoyv!¾lU,/ mó~Dɲ4,<%b\DjgDxLs?ƩuLի%C;tƔ7*v5":2#U%NgE087;BI+bX.5;#y57ͧq{C ~DA%v#;Bg ߤH0b$, x`F$VGQdH=ĠXobi,:oޮGNN>dY.r;PO*N !SpD d#>~-G4c|>Il:v@R,г<\{,cyk!(򚯿-YS'Enw`K*"_ OP\:P j/:J)MeU Z*O<c 6)І8Ken1ESʎ-g*{wq>c[Z3 cFrxO]o&i"t N"+M:p4bICR1*c>12]kw섷pR:Q"O/1:KCiL\>7&~Qx.һ :ט|$ı^#CR)mmer\QB2db\8fdiQ9b?El~@dl1g64}$8N* ;n8PI)`p-\@^ L,":bcB]Hu(IKEpIpt :39G5w\o-K~Ʉ> mcu0W|Sq.p] ͊L gԘlzRTaϳQT%߲=|gAV\xF@{ӷ%#X\:),"Ӗt2"\JN8"vlLHEAuQl R!-@p*4iWN|jj;B×%,Ug/%UbRUi5/k~KFL))A48^!XRdx1VǤUnێzRP3k;.""d0)g9F+J~aCg#EbmdZv=IJ%HX .Uٯwi LS9Yj4TRhwr*^ue+u;aIS!ADGf( c-ϟ]O''LmZ8ȳd|vH]RYl6}TJ``c SPy:YxUqRxo)u>4z);?Sˋ OmqC9 0\>9ewس5C$8C^T8?=C5-2U'˳ WWW/xrDiݥ2;5qlh{/绫7K G{,[gvk ~!2YU`@5|>ǤqahF3r؜ M6lB!EefqQ}+;1AZĵ{o[+V*3c~8?}dn޾RZ#>=32;DKHs0H0Xt:$T i}ΫX*J9{ ?J%Uc^p{e0aH34Z ?/Cn(zAut]1>B\V~n[cTBPb fH`QBa]9rș{GH00-xH@OpAk ,JO8O8c.ŕXӢ5,n^;y c |xg L.慄ig ђ,s Fdmf&D!n{nZaɑ"V$<\&$ew|ӷYR-ItFfg_msImA]x.ɒi3b/mt/S_\sȋ]l@Z)t"ûRi$M3֫-Ubfqfg6 (Ѻm&vb?@8@]di(1$2p~< Q$JMyQ঎Ւ^pl{>>~T%_&=|7E8Z3q6|6,+ o : )4Rx.WBĭQH95g" st́D{ƱÚD{W5}Ӝ 4JjDX$ϖ(z?&і~=䩖;oAD粒^R>=UHӜ4S$:(cx-u]r>nWuqYApBJ4/}#@/ct:1 b?=¢>8Œ4.ǻ %$Ւj`wLsNTZ{ĄDtČUUoGy Xơ!79IӜX`9drUĘ_(\_bR nv[UΛ g x#j%z d\S5O~z-O?pؿ ,U.vf[1MRK,aR%ied fja6r." _b(6(Is:prniOd4YX;G+ց>r:6LH\. tazN#Yq{{M۶4EU 躞<>c#]PUu]4Qt]dfo-4Ii IDATc_."x;XAɌ4`)ӜfËmw|z&Kk.'(Isg<Y a%;|M RvLg[,4928٬QŒ2 ȥ_11[YnAt`h0ł/]]D17ͻG^ܮR5RzE+$!4 ٠uN ]Il'[$Q咢x|xf Zeha>qyǑ,-9 t7׉ ",K3O*.|mF~߲^n_BLg9zndY[E0u〛=y9E,Y18[xhۑW;zJDue)Zk?޳R e39B@IL@=9O䅢܍߽!x֒:aOO)z\t#m:|*?my+3uůoo#Cϻ,ͳ̙1&kE1?9}38CL"4Zmty2yۏGS{:3WKʲde)Q3X3SVJ.^"֌bRnCOp;2I2d e%YldyND&"#t04g޽!*]`Є0')*/}jmfV/?o9?Q>ĪdՌrҼZF3 lɳy-E3DgJ!{ JgXk)59>d|Ě9>|j䫟L16&{wwGx}j&/+~DI6w?:<>4o"Z^ab,dX-<:>y4eZq>x=p HYМ|{g 2@sjy蹹5fsOK|*(pjZ=V$8];6' -Kd񕧮K<1&&} %"x3Mlq֫zU㍍ `\3JxCUU<==qx>Y)t⨋t IQĚxk@8" %&pv&KRm?_~l,Huj UT'<=~[v{az "aXR+Z?1u)Ydł5wwOl6;6:~3o߽cYՑ)b!/7'Ϗ(5X(Ӓ*hN{Sa'q¹qG9 ~-0aR]CRdgJ/I.#o-hr4ԋ0_6V̾rq³,q^Dl"dTk#I3tG0yIU@*#{xiF]UԚmQ-]H-ՖncaQq#ekMl$f%ySVR\F5nOG\Q.L詊-/ݖDg{ΐd`Ύu%ubDVi>T*0?3NEaM#Tiph5EQŗ=s )\qKoDp=N.`g!hP2vԓe ["poWKv5ڨU]O!KbQoȋE&O GX\$8GC !OrRWssmLeh E(8(?*|@GYt"-V5"QdE*=AO$l4O:K$ydvuȩ03c]XqE18jHSDK$% DPͦy&?~ms:4aP/J2PqmEJl;"LP70LY6'lȋ y Hd ;&k*a.Bbv )rw{G/*9f2a$‚tc.it~'&۱exIі#fzݛ. 8]?ARQ{i՚b9XW#W1%2ozwsIv;׿7/ќ=}gs!9ͷ/ 6%4ܒkrpzҒ$RrDWɲfos|EFH`Ʉt/O]Q-%EFY*95.ɪq'!9ETXE2Ah4cx/ISypjLRq,ײs&.vfзxAU\/v BK‚a"Ձ:==Lp؟%"^3ugL}`n<c)JI]&Z0,%K88XFŖ_}w?!3KY$Ɋq9&K۶N#[+  fd]gocY8 _:{M@Yl]_n*q[Fӑ8`X4Hx<46XVlW[h#Y IFC?{9>]s¡uўh4γ(Hܣ,srJtǿ7I+$;LoyaH=]\Q6'~DTJ"kOgr<7ζ8mh &D Ok&(˚ŢR1G'RO_4Qg+#@U,9OܽyK{h: ?2:XoW7vn8*qj xsC^"CC!D\>l˿˿"'Axp<2j4펛t<ӵ-y3୻OE-$OGuW_W5IaΈ" 9HX0a:2/O}owtF9Hc?u}ӡ,}i:\ᓮnB2PnQ%̂4Hh!vha&nvxZed? c$n+l'JJ Z$ #OETyrqb'Vd"@5fA;uT"BA`CK`kznpƺL$‡Ƒ&9yZ_Kݞ X"?.%)±q!\L  Y&.|$)BrF-f!/H'G۷1|:hi飞0\(P #)h$lh-=Ӟ,O(,zGVs{{ӞtKLR-xϋ軎 i(2DF޲k\˯aI?8w(М1:%s8aA!.\)YQ¥ٟhZb\P x'3;Z,gΞw &:*%MjH@㬊C`(4[ c2rAyLHkƮ;7JaĚq? G>2_b7yN\G!9H gp80 ,8Ӎ#E ?G}ޠ2ivHv# R(ARe>Zg(Ҍ,Jy@kn &3bQ|SxΆ.(9MB%wc҂lwk8ӷG֫xjGdEK!آ;cj UO<-{Q%Mӑ_ BdXӓE| ũbVxia'Si1H3xz՚{(a0ddJVۊբ7?"ab,)Ӕr#y1-Dbp$x%m=00ND_%Enttƒy4hf9x?|-:~פNqvBhkOh!8:3&<.ޝY,]~S3TbAUU|aԨxn)Q kLwg>2)gzrӁۗ;DxR6eu#fM[Ҷ 4M)'YTBH$8+ !2w /^GNMCn7o?hv\ tBP9%gɪfyz>D;Nㅠi:TY _@ *vfQ PGn@A89%5q5 !(> >4y6̳9LJ;ڶ%Q2~!LiV0nEAU Rf7~Yk-= ƹg:EL$E] i*1w6n< Ҳ`Q-qI11 ]=7/P5#ut0#~uC]iv,bPZ1R91͐:pz~<GI ` "XYjx\-S"CX Rfi{iyzY6Dٔbs}.2U`̲Q*&EQp:9{tQH$K޾c߾z5{oźD#Ś<+YZ{ q SlRZ7o?sz}0D9I85y4gzc_ IDAT".x7SU䑴gy>恀d/x3V%7ߒf7()rjpuW7H<#jp|/my_]=wwp"ٮn9!޿JL#b[on7i~ydŬ)#eM]f5 vd:0Q)M4 O#BzB*/1sO׶INU%̖u ^4 |pGz]٬x||DEon[<:zwY<9ƩgUՔyc<0 {CWknF *Y2:s:?8!["=BZ<=?UI^habpgQYnܾXP.r6Ds{/.Kd@%pBxnHӌ=I2CFvf\IɑedBSک|:~Ik䕤b'ƩChtә$ԣ3Op#6x?a\ yR)^ΐd<>}vj{HQ.Y׫i4z^Ҭ@iQ@h7J/Dj Pyr1 I$H 'k:(Ť8@y #ZO@4@HT.Q/,Aq3br@YԂ~hJAZר pxshqK$o?tdeBU V5Zka6Ztʽl KS֛-u]<><|Jf͓{yYm<>>]\~x<Hqxc/ubc-E&}ӌ,X,*43㙮IL86»d'Ds0Dr={mۨ߮j2E<1X#E9wsmk"4Cii&n|~V7r$:P%uE۞,? #Mz,K4;NvcΌ 4k||,%,lEw5EQ UVdufdDx >ٵ;XǽgB \fy<ǾYJtwQ/J1cCṳ@!5]xi]7_tmBaTANm<)ΰZcLjI8mfuZ 7lc\\l8[OonێDzM?|?=>:G*҉Qf22ڀARK}bu4\܈=T@DDAf y)^k:<&n0^PJ:B?8-Q "N1~Iք|AXoYf{;ӫDt.*|74OY.θzB۶юk"U5w^ -YB%ΑfOd|dW]@H7;S$|CK9|}k.>MHӏ"ץrZo5 ]L)f"E3€juT?2>Kn"AKlUmȲoGLRo-*Zp$M4yѓb˲*DO>d=B<~7Ͽtԡ g>JF9[^9Xk:ɵiahw`(JIf4Wkb#" &bO?Em_ Vs>>>ٳ+S5 o޼a=#:33vhXPF5Sey:KzgZW߳8#n/(˒۷w@Zv>!M X;q,$77W\_2m(?zV,WEôߦBOuYaGNͳ 2ơelbjtPRss}NkߡTk< SCLbGfo pAgQ8\𩐥{LҒƑ"SӬlQI> 7(e^PSdkDh |xgxV!c;~wQ3bLmeY,nvCӼ86 #QE 1Ub^k(˚v%5]Ԯ[S3C!Yx8osV5 Y7] :39'+ Fm7m%iSj Y djbGf4EȌzE7D`d-#eQe+O~o︿ˊ2]rmts8qCm-B#ͱŻgG6 Rʲ WWP/^x1fɡl"1,g8?X̨V N]D;[|y>I`q~H4469] GZɚD4 &?DڮFH_G1Һ+6%M<, peF^2!@}FdKU HIPFD@#g]4eEn#"#zFMu1F8f3!A웞2WH)t.ֹGT+BIC3ñi,=>}`Y_$oH|w˒bAX期-trN'c2"%*Vgȫ:)&)2B~!5",O0}c2v(@FOkBY?9tBaH2 *@Dc/uƎ."P.*JqNC"řJ#a94cpጳ3 F(ʜaOQ| nגr3.(3=6{ᢚD$9֎t]jB:^AqqHN3!R༧X͐Z<-MuV D)3=&&[1]},"txX+&s}s]׀ ʲĹS,yBgkRb0%kt|^0`*xLBOBpD`p.Ҝ"EIY,8DxxY% -3bh VK9cg܏fvT+k#!z2)H9a@Y,KM /~w'gg%9nb 3sԅzz]PщM"QZGJ$Utx&b "%&&f|:#$Oî(&91Çx%"yDR;QcrS.+!C@e&AЇDc7˿gX'Ȳ=%!FĘܿR۷=ӉuVH ݞ`3n\b^uT4\k_rjH~l`-_~ Msd:cJ-gGWv|aAP38;.X-j;˱aF%Gsa}vĔf>'4Î(JBU0&sCVW!}`dJ,ː2q̱~qن|1M{ocHf5_m߲*q{t$=LR7(ih:65"ڶypZϑJC B\f3[-e2#WWs6w߳Xyv=/^ᜧ=09:b ܽDȳ?.+B\aOo8eɳgK}׷oqGBKAcG$љR=JX@>z&9gJ1 ޷!$#I(3bu;GWh%(5K TtZ /JOy<0+ 0ɵyr9?\_@Y,}sЦLTUE Ml쓨2a.qd,UKWEr䘬J ee0 G=Sp#;P:E;DLYwjLFBB90؎kPJ 'w+` "ہvh86Fe,QiPcCt# H'HRiB&x:IyRTC2О0 ܿ})2)s3)9e~AY\ ~tD96ımiCLbX\+>'mtq1

    :.(ʢ[5yӶ=}.ń%a0G(xxx`>N7$k`{nSTFgt>CXSҹ̚wH@0nI  V10SCvΏ; Rw#(Ox=^C/-cX[>3 Vg ֍8;00 g+..<+| &pJ֡"HR ;v#;0(A='9ecAffr8X 3z9'&!?}YVJ&GA ;RE1ᖫ nnBM! =~?~ WO||$#E&|pq XMiVU*9Ҽewvmrrf6+Qه7T(I}>c|kc<[+v=j!9bNUdHnlBQIj߾lYs~. ΖuZ]h4-WOVdo8[&2>[cbPC;YVe#m۲\./,W%g|"fXR3Fi;KTe ?(LG3"G@k..W!_&Գ )>'ϸ +' @)ˌF0,7OX.tCr9*Ra@ يzY߿oyVut{@ўNK񢻻;GOi *@,C7/AX9Eq!Dv=_}#ؐ",o |4uHFyjŁĆI~>C##}CQ*Oܤq.C HsLάtB>/?ާ^ha:zpXk1Z8f!m,UfEM\]?_=ɚLaL'"}=];NcrTxbL S2[ IDATDv!Y6-%cG!F)bceM۞v* &<& xKђT"!]2 9thi=H+ǟLVKk%0z% zY! zt(cv{ d/@Vh=)Wψ2gy9磏?K#釐)#<˨BOd(YR{rI` 0VFFS@ГP3%|ox%j2(<.j:e6Yf퉗/_ DnVFb] z~Me,%ov2YUۻWFݓ}F,BK/fv]OSk^ kq2 (N>X\tԲt wGd&>=B"B̔Bc@Ԝ..ぢ8[մLZȋɭE1/H!%HM xZΤ@g VF<2GI$\Eϡ9`r5ƘTFhk .@i:ܢ˳e j޶{= BBYrt{B=/njcH҅uV(tRJ0#.tЏ1@| - 2W=mm{3.//Kg>_ϟ?GJxru/(˜p#;޼|5\]TI|֒ME;#M2m2bDK|'&Y!v4&#61)20&WlV]R#t_DR|(P؍i@F:!x奈\?)z&llVq84NlX;g EHsT2$[d쩊"}ad-j %,)}po7[D'KaC-w,VOϱ|,h-ZjϿɟqzQд;"a =6u6"@Mw^jg h,jM21vPKsBt,9M{H RF;M*QB4|2/%?O2\cHw`0/NǁΞh{KShF?Bz1Y]"9 4GKOԲ4tmvc&2i6)'G?oas3%uUo7Xֺb"1 f$]7Ɠe=8?aXpyfsmit6 @ќFOt͸z Ji>Yfe}($y,Gꈗ,\\Yg\}nLq'*8H) |'!#+ΖlwlOc'Y1- KWi-Dr;;'~p848U -^Ӵw}Ch9N|%y^y"d͜<͑ 1hOQG8?vNvd}>K۷iOf "4}g|[6\.h8t] JN. ,t]'|ē'OݎAju-FЃ4z,'5S3B.aH[1\@*@s6 MD"z={. CCx", > %r$ <8>"+)Ax"9 e fL.3Tfxxxxz f9m۳퐽KLȋ],ZEdĘ/Q2G !K) x>tR8IFS&PVq@)@O|T*:WRs(!YvtDAʒk2B F]%7gs:ҤSˏ(!jXT\O9>򂇇-!t-QB A;уIҼ* 3B(ʼSsB(Td`^߾aزZǁmi0k7D*]B# D|vĻH6U`j"UQB0TU$ '8&+M3\0/dfk~= pOEU͒[ō%E";P>]  Fp>M\^֩17t]G?$@zxob2'&@r &8h-CҔ{`RTUjɲr#xW8 K1UK)'GE REHfB&Fe)=dFIJ)ˌ"u#Q4MjN($R)2B"YQ8䅚DQOxtYLb@Bfd8i1ƤM(+>'"S{J "i( R邬C w6q}2T}o'kd*'lmΎ xT*l)zP:KnFCKZX+r8_#nlX=]C]g5^ϩf Φ(~wDiAo#QH9|jD885!|uIPYj8>"tr1 +:Ovd"Fg#2>$&k#+y ۳;l,Ws\S82K> }Gv{e"I272*HPh˸dmzG?!mĠtI;u]+1&^[LoBD0q4g,gNq~xm&0 Hiul~FQtxh89wا RD!/%E^o|x'ZOf$R:G=,7o5yn|}A4l6ddu6xSnE/*"7m-pA#:KCo,$]f^\#ي Eg :֨h-ӥ:F?1L< I͘>Sk-U5C %ST32I1T~ǹ ӓ)b: k&Ej"JJDX Xe뭒aV}B9X_T,*C?)]SUE[{8ru| ي9+xؼt'" n>(-r6gt}L@j&c}Ho!^S:;2bgx$9xmyjW;,+Fkpu!Ot]é=]|}5חgFp}}u=圸 ?4c˩kQJ^8[SV9Ux>plK}G.\\.ˌ/T-+Æ(l6}b"g]R*mrgy  }jmN{\_r|Qr8(JقܠEHQX,j :g> w4';E A?lD8eHH=1Ny>}ӧuMZT4$y=YEj=vܾe_Wq{x||1bl=eYbO*"cl'|d\_9%e^p~~h{ܾ"rNVx8  z8Dsũp|şUbZErf!:$i?a1FzrP`QH|h;LaMϞԊzQshZr_o^'Ҡ$SD tC $'[v9YRm=iN=ނy&9ψqcBdfN -mg1`9MYtH,tꩪ&[DO5/7%1qhմ,=:Faq|&J VmHifUM"Uy@GiIfAEw7u{B5a8ɯ~UZav9v,fZ>-4mrxjޒ ~25um;"Z<%K۝ի +?E!*3H )[fbwwH9cj2q5p4e6X̪[*'7ՠ%QJX͙+ؘF0/1dxx|O_YIפ6,gGsJ[T%$ˆV2=df~vga~ݾeE`$4G)r vwL ǀ =~8Gr!)E@PϗutJ2I`-* 6(ȵI sK;F1R,R3ǻ>rRԩF<>츿)40BC=0w7\_lȵ'Hibq3p'%e^c5mGdZÆ8ɔ@:qʼl}fUQƁ*_^pLk0D(JLfN}jhc`s⫵+ʺP3IYd@ $+r)4]{$ٌTU~w4!d=b _3(L_?5}b:2Z˱"q%˜;־Ȩg5U-Ed>'/yf*YH+tC0$ F!bsEY`%PrxpxR[]nRl3)b)N!fv^y܈I5LYJS:?BIO[sjF1O\#6Q( -Qg<E7Fswolap<9ZL Mb,CW\Hr184́4h5$y52Ipa̫֤o~WnnYf(fq>& (5 M ;?P%SV,w[f$6Ou]'5 T4}w Ǧ![{fUFTyX%#r$N/ 㟡Fɉ#-5];0jx_?%)_5YQRƩ&MR@Eqߐ)m{I#e. 4UO^HZɟxWPp7En=gpqT~`7‹s~s;w{ j_^yՊ~<5nȭ!3/ ~7,3{qy9n v(Y,v{wqSk%ORIWM'/VGvˡ6eT{<}}KgT;btBpnr$d8a,ٔxY 21&?Ǽ/1& ]д#nrLH]7BdY$Hx3]P:&%E&t@ahPAݔ(P 8y99G"sc;OO% .87 Nlx&>P \k2(5GB MܧTDZw- &vJ{m0L ,<1Ha5-Hb#5X h#1z"gSr?w4BZQʼn i@x.`u pu0o?HPv0MŌyUPtuCdENXLf9>0 b;ѡ BߟhO-Y.P^J_h{m##J%N{ơe90n>7gnNPD' $Gtx!*M7DR9D+c\zVh1 a Z#nL2T*fc9'xb0 uCnJS}Ye=ŌĘ ~{ڶ?oKjNiB` Q1MASaXp)Vn58NAdHYŒEI0Efq& Z Lvah=^GwX =;%IREFv{1E%ϕN<͒Z4'Xm-$̪a :Behcі#6HXAč8FC^Ie^^R[E1M#Aq9 IDAT|wM?ڶ='bTX1[^❧tѻGA2J |-})Ϟp<̶I醁jEs2 ӀMgLLnG٪H)I&4]K dZ6=uSf0pLL^1(+ [T=Q*i$ɬtUD!"/IN䀭gdɊt!K6zg{Q4(VQ2hKf2JSUE"+%-=E&J13HM QX 2TXDdv89\ EUL'žܽ$O>}Ҟw4 y lwTGqwJWWo︽QTӟeVՓ͚jhTC̛4 HYf cm[ꦑ|.y&'mP"-&䄨bAD|q7#EK7ye* Q5FcJ(Q,&3R&E3 @"*K#Qk&F&2 L~ hhÇH)9|alv4MsbGv-)҂v*!G:Ϲ5i ,ce|s,Vqp;/;0W I-xySo5Fa0/S֋ i*jrM>7|CX @YVdizm{݁gwwnSYʼn  ޽Qr~4zkx-՜f rv={zFu' *aI3/HnX,V)fsl E{(T`ea*Jk=w%(etoy씢FWs)H$DqO\X`.IӔ@ K-=dsyw^qՂ&y@-CGtR9nG޾op.Rz{r~N CKV GbTdiM4JI:N=JEQ[z YE #x;qE^h9[eb1{TOլ yxx`3^II35ul9y@)p>6cy ipyZ2_䤉'{AJqjJfCv+ө~[ΑhEUmdjʂ=I ޏ 1 yJOl{lRb2P $,\$B-??dtx,A鳍IF7JZ{Lhl7<<3:SI&S{b }}w Iw8ؒ"DF,X\g rdo(%֖Z\Z_qKn9rY❦*~fK+BY0rIɊ&0$gUgpUi޼"e[TAS@I*#C@|5DHTZOD6UBzD'ˆ Ch"mΰ$/1$l$VQ,Q("]bPy!l='bDGR5iqnR$Dndr\p O.M.ar5 HkPP)(vw(dx&m++(Pp6}ۡXn2nDž] D9I~$/g ,)p\ aq^|'v)8r<q~/ʊϟ!q\UQd9U%$Pq!D\] oq,P &\t@G #` C4D,1+JXt+OPshj^{vnO•)r9i)HGۑ4:ih-o}G]RMI3lCZ̊%eBx:-Iqq!f&I #||^1v=OHR6mב9EV@WypGq_\=,s2O skPaiN3'4SQ3:.X[$D]P3x C*Wx籉}hxɧEvA`ΈV=ʳmh;TyFVh%uI3K sԨid#<}U *:96fOoƁ7ݑ9IiƎqP1| YFJ$!d[5w8ߣ \__u5$&1E޿G'\\~3^|Cw %GOP;&<}m u˼*eG^yT7cOu襃<4}, L>K$#T$8utdF5O//IR h$b$Lj"#Irg0,:%Rn``m ar t0,7׏xꎺni#EцC}DMM'75[PU6qj<|{x8=p8#K,!jmZ e%Cww 7,Or88޾ioJsV%Ώ c8q-i:O z\Ϟ^?8ZZ6r"K;X.59p^3VJB6ZTS`;?&Y Ab $kSoþAaEpShKp؍4l/rnj|Z ԓYEiж}K Obzt/<*5M{8e)\,ͥ$AMM](e60&T尫J) 7okH_MdyBDJZ$]Q4uGX0 p;οc-hCy еۇimD@Ll"gHZtD%X{GI*ϮoA)+uj6 Q~WWsW}yH1'2g>,kVjhuu;a9,9՚m(Sϻ1-bya 2#Q*(H$=]_)(Ty44fFY|d^rq*(˜8JM,s:9$W<5?jу7'?gGWW;Ť(A mF痢ʂ5l/xcG77E@?4}䩔d۶%$2,Q%Ɖǃl̺ғԊ-c3AݡaXhV &j>Q*jTP<k!a** ~L3{Q*o(%(c,!xHbo$jO059tīH8oD'K^dHrg:Zՠb)+F 2hQ4o Zٴa0(2G9@|`|0r<#qo[#I3.68v4b2 zɓ  ϼPI`Tz>G)Pc"b/7hd^gY{OS sdyI? br=iUQW m'CqBY;R{)QtEMRf9;83GJTS@VP?`#rRX5}R%m?0EO<ߝ/N4RsY<=&GKww[Q e H5[`ew~;&p 3 r(* )f3$ep|u]~͋O>#υcd4#PӶځ,E e!IGCO2*ei,%E$(bKrA^|x{":UN4iR* L%߾EqSi֞ՂO>m[sww8E 38{UfI5$Z<8+Y7M2Pw#EE׶XeR7!aٰ?fČ CI4UYP3WR|p\\,IRhalhf|2l7DrWGϟ?g\_kyZ[^~ f|gx_ \?Ul23o]Kw:^He6/h 0r:qn"D(g"!FR(\p2$VIŬ\^n6c<5 C/&$q(m%XIiFbZaRWJΆ!\IznҠj/lF]F'T &QTA+9+*3&@1r=>[2"$ALgfQ(C:khۑ9(JoYW|'$)WM$N\`@^1YŐ+lZ](*=i23ڜzt:[aݡUNjs6'\?9vN'yv%m8j${f`[~W?`Z ϫׯ~)?/X-/ȊnǻwOrW(*`HS|=C+Edi1 \Mœ%Պ} RU>MYUMfXcpHWOْHڎ_Oh;$,/ q$:ʛ3O,Ż|r~+FqpߓP#w 7UQDU%If\^-mm? ъ,1dYњtk[(0fɿ?oOx1ݎE=˜lF|wlF./5ϟ?ZKY$yF`$Ɖ*zi)3|4\_= {}K]\^'?|_(ʅJIJKndX歰q:}>P94#)62|pS+np4u-619 (LJ~BKoqӛO7UFp t"IR|?1 E}ijGk $9b̹.dʈy@KE9vU!A1`%3 \b$Rc)ҌbV1FzYlVyORt:W9GkXat8I‡A>[ .:_wm/qOhpTRޣcyLnHT%-rUuwbrR Jb.D:1.B.RRqfHQ_ZCWak~{ ՊG@rNjXpttDx淒UkZlZw{PG5&Qj&MFO۶RA 4?GE9tP;t:М&Rj(3f=\pyHR:eI c 1HTsH\92fP856yŧWXSau'N-ooJaSCf ơ':qGťXVtO&/ϴ[N/I"I+J.؄fL>,d:w*ѕF2'dХt ɂԔ!e9!.s?\\.bO=EeO/R}xpO4$/NaAr0 }`Ħ\<&oóWL;qp~r͏%}=:VkKVXƓs6 g;N͟g0xgX-/dx"o^=@~M֛7Tjd8giΩHl—8˼\@d$2d= IDATN#e'7[+~KHU=DQȆ>*YEWdYذ}4Vjf n"My1<+WLCt츿}&f58'x(qbr:Mb+sf rjEYFBbH&M9S|ƾ~, 4Dsoa*fɜ/aN( %Fx<ҴÖH?bׯy6ě/Q:J*VeS9Бm4"Ό;Z-ie0:H 1UEfTkk+RR%][e2,#>P=J~-Z:|sjV`9՜}aˬ8&4p.FsC/, LT{u%]s<tzyEbKg6( ^Szagwr`#! :*\lRM[[\/xzon!@߷LSOe2^IӔܞﳁ 4}C5ϹzW%yb҄{/ ^yqq]qxE@V J>?8cBnÉXU+wE&aSZz$ Jq?j$('vκ{b8b(r 5iRZdۿ><_+~"6#,ˤeKnt -4NnDvwH5[RbVpNVs6$Hxt:r[yیQqBY̗%nଃ]a@h6uN t -U!AD κ"x5%ԖDl&{@( ˠF62MЈMi|уc\1<<<=$=B<[%LC# $yL@fYrkNݞ| ޽6GլĶ n?O,M~nN]V}X7Şh|{0ЍȇyU\RX3<ŋ'xqb5#ɢ8;;'áÆ͗*(Gd qE^FEo'NR=szZ)S5gE䣏~@=ss}ׯސ?0 # Olp䄳G;&ci-Yf| 2lIRU'Ț4]גeMӐ2EbMd"Z+iKnϻY>A֖pqz)XSJB !^$5$B'&1N?DC'*9(9q8;<  9yzݺ3?F 7XlH7)Mgt-FiMH ֎x&/D)l0 3 jA(x*g uuBH"geyK7L@25RiQQù@3z{lځ2+.FAu⌥d{@;[v$GCb}ϡy哗<{+Ac !My^/kc7Xېu4UsQMw5YEUg0yΗX\D3tiZ1fGxRc'< '&L16Yx1M۝EVTUvsOxt~NǘiR`ȲU}9ݗTEB8l$$IvcbEVwdDkO\ Q3?2Pǁc 9TEJdbl*][<)d5^ PZF :zEa^fLDgLlNOO99?#(1B@fЏ^6$4bH3ThTՌǏӄ'$ oBIʺDаzF`T*em3N> RV%IbD@*L v/O~BU,9q>9L8zz IZ65+x" xsn!e;iOZܭi⨪-)%yA'3v6j%4ֻw9Z,Aw "!7ӉC{fU m-EPeh;~B EVd8瘦$*։~;g^&iO. ,k;0q|@QA`asͬ*yi&t&Oijv{ } O}%Μ'/眝cO7*ogFFQv蘼kXnڎ/rqLF~3͐Zd*R øLSo߳l-k uI^hBHGG? hh(˚9|}7ֿKkb^#(Wj9R:u ~9" !HC 겢HKbV ۇIaCaYq3=eQJƮe2}۱}ؑe/>0SLh4lϟkqB& |Kvc7ZKʲ,K6tÞk^zE^|J,"s[޽{kE9w'O5i fUN/ҜnaS2.:LC\a`$RjEu Lc'2AS$:M^`pLFށg'"D D |8 [RA: Zšc-́#}*G ZˤVqD*~nFǡwYxD8;ENp^LLhȊ$HP(PiFZdX;!A%f/k&#0Ԇ2Zok2*2"~y[<%foPBX͹[ߒeer{yձXDyB$% _^pO8C.%"'-i²: `c?łmX,; M; ug'?巿钀r/>8k!H\D>XB3hCAM!$Tk]͹a^k@ce#Cr;od"^BpLf,fvx׼5uYJ(8`܈ SR!x$\\\|7P&n%r}}/Xo6IK'PGRʐDEdX$3-"5Äyc*:8|U~h\CY0ѵ[;NNNx/+nM?B\_ gK^9 t`pC]޼A?k>odawŒ\_}D;02<,2q`^#CHK]p~`#S(8i'KoF@Z =vGf>`y±F9<+@>]葢b0皦I31LDs͓ÁNϖ5o֜xxeqbEk1@{4"~HAY=ʢ~K4l7cca&}ݙt%)߼!%R×z\\.l{@׎> xtf{~3 ߼~| -}y!R^yMwh:zl@M./TUj1daOUsssaKa=AƊu7oo|:2YL\׳.9;;9M'+4Z/Uy\s^~+]e&,GHMQ u }d8  C?0_R4F$DQe; Hqc+BEC2SpطdyBnDJ8?? cz}GU1g@Q,i :G Oz.&tH؉=#Q!G)yB)B0X;d@)hC%4T"wRJ 1Yu/L]˓Y;p> ؾ *._ ҊH e`lw y#]gQ ZxswpLQUM7E5(Ia6dhI89A*qD{noZ"tLDpmǗ :u:üp84+88]̙m[&3Fʄ~qRяo߽[NdN(05\DGQ: *8>'g'1Yb w=t.&"KLX79gssq 4fz^]_%X汎6iFUUӚihsV˒z8 4W٨8q4TEI1w3 v v0/DϞ^"4~`"S,۷Ä񞾳}sf۱y8d<,׿ŇzUcDͺL^,6&ڶ%*&2 zdQicg 0gG sgKH4U|VՌ>Tf۷oY,,veio;~O/s4 ! ,ff Xڑ3+O鎷;"E@Q"68GRF/ZhIPFOvhF~#vF)o?ɓy ?ii-Hr2ciΒOl/_r}%ռ"g |1"ký;F/RTDR)EF6B .Pe No0xLT7Q┢(uAj#ViI[Rv{|ĕ5L!S)LuFUꆶ#OUx=WW!&V[%g'o~ -OAĥA$qGm2`ZrRd9Q"a}1ى6l#/O|>T"BJ5723n4=Sh_7*=Uvs@钧|ůۿwon:Bh=vG|\lSj_~w4)_>???c[fU$;JFY=~|3GZ #gS|s 77y&22a7|+>fuř=;~Gwoi{L`mLÈa[}'OϿd#k뗓ѣ<9A?W28;;c[OLFg) fwLI8(f  c@'d0Lr(포1ڜ&0 fU':~HJMm;A=Q)NH # 9Zd8aLEMAy v3¤!\;ي%'S޾m[͹xz{NGbEg,> ¹bu%˓oX?ܒ "8vՒDQeEe|Y߳;<ETZe28R4#20L)`þ٬ډof$2fE-3-S0{xzѧ|Oh3VgKFiN2ơc2\eFmw yh}4UI{h=EQf@QUTePO)rDRM?HE<;' w;pcbu"x׷\\\P3q@c` L[adhF au"v Snn]LiG5@0iA*"@=h9;;ډ0g%3q$Sqmfsf{lV!e`c1;ͻ-`0cOQe pX;1 !s&J22s_sB)*4JYYB5THPQl,]X-+>#]ljf-] ϱgYAϹ[7LSӧS0C9" t}&|NQJ ~jX%!XNB! IU_z}vFJ/i=RU ]vf>'DgLc#-RdGO(xǏo~z}#:M?i?򔛻-9 d"i=RFK]E]O.҂o=Mײ5uICUUufYIv GH88qQd(=4H=YiYYͣR^7}5q93~W1ђ "l~gv5ub&{G{>%2!x-09q?N=!yn麎oN)hYvFv8Sz79aB KNNjNVqS9,i?BdGn2290Q#<ӑ1pvr>gqB e@: ;lgl)iuzF7%Y^3ɰLu#@+6]期_phXmzR>"Wx?d"r6{޼e2 kswgyч̖ npG. /NPŎ9I1L0aI-sSkBEN@f9+Nqh='rSEby(X3=iQ/ SGٮmY~WO͉p6NP᪒@*-. x%@*! $&-e;93?[GXq̅ }֞s4~x,#GJP$--w }dy%nt:A;<w;V;C%-uS)W[ۖl #k`A)1r%,R-ɲc25a=˓+l=4$w]3?EAgZDRN(-8v*fË7o_q7!ȆIFRVw_ahCC* <#Osso9?߾mt݁4`DD4[@?둻x>"WTqOc חW&ٳgԇ{Vwkn66E'9]7 umϏzMU|S_R)`V1r2?*bDkΓU%@',Kھ:LӲۯtM~82a2=eRrz Y.xۡ| gsl]M4]7x|Uَ}"4͹#Ǵ#ل躑m b#3O3TaplT8?+@׍g?{b6iZnoo]`=Z~}_bL&Ɓ4 INyc~_ <}T+W!!x@*r2_#EgkIDd: d0 wΎQ9'yBIt)mku}Poͦ0V|8mX<7E"3uq"&U)ih!JdVqrvBNvxB+R釚bltRe$q\,O` i3y9oo 3[K$Js=(;hv]lI"+^a:]e ֍7|gEW)wwJ|.!E4>HQ2!):)p(t}CccT-F !K:ʪd_?0{G Ad/4Xobuҕlv#JpT-6L&yqTyzv IDAT@Ӎ$~1j6a֖t2NYL m1)Ť`:+|AۏXR)Mm9=' ./pc@ 5Y ),7#qOOFjq6*)%ǸD$$d0- M鴊0LH FJ hI8$vF'IL3LOD@')yBۑXዋ$x]w&\c~Z %9G^d->0 B@j/Yơdlq#hڞ[z0 9i"DhNo]Y -Q#72w`4AĬ/CQGȒ .DX%d YZ'Sr1$8-''ѵ= Z'G]l!sQut 0f` ,Ya<*29#!Zќ-Hɹ>¸1nWkktyT={b9p]tAR-&Hۈ#!AXnݱ=AF@x2Z4cgӴ=zclwӶBFV84Ad#0($ˣvL Q45޼Zd29Nb$k޼}w"$SmIGϾl;\^_psj2Pw{pɲaC$eVRM BP4CHE3Rh}T,&.Z{<y W9mӳۖ!H ^~d H9JM2hʼ#m,^hȕz͛ӏO s[7a}㫯on@Th=mcfdZq{%8)+@QdyGݢ.3B MAkA?AX͖0#f]s{"I ܼ<ʒf9/bUЩ%KRE8Lf B 7w'OO$KYNlw =Ud؆brd2'emkU)RTPo/u1o_淿+LK EJ4|ůy%?/mC7$G,J(=7-BBX(˂4͏-t~~N]l6!jK}\|)蜰mTZ "TyFf*Yt#|ߩ!@HU` mcrjX,OXN$NEMFj"D`RC08?zu+펖ci"Ox/RBQf3bB*֛ Y &}bV8cŸAqd¨,;Ҷ!.H҄uq m3ud:chZQ$ ۪o$ prB?t]됉F ctE"cLP!x{s3#£SB#~gA~N1<V_6Gow[fXokcBؔAʲbXe \{GhLp;8Y uEk 29 (@2f8 lw4v+68=4*",HĔfװ߷tMCǖ(yp. ߯,O8oIAOX,eIH]дMje:ss e`аߎ,O)S(\6ֲKH6eQɴ# B!($=F4[v e$IYEݶd]MGp#J <1nȡݘѴ~h"S{kюthێQHrѱf˫GE9fk=!1~Pk~󛿁cs/sv䧿|g;|9_yjepjlN3kD'BE'l7]̣SUlRI cbc|YG*s#"7o 2GSUc+d"F1tQZ2N c7Dd;KlĨkp#||*-UU1*DR]q0"ĒtTUAޤ!69:Hf_L)CJLlx3C 'LS'W>0ɓ',NN_˜?y_Ϲyç9=(ˑɤd:)8;,nTPIr;6Jۆa04dFɤ `n>IзN|{3t}CCfͶ 9X080aa2%I*{T2dXO30Ӝ97& O"`<{a\__<9ŋׯ؊C%qvvnazc'O..Lʊw7oW|yʹxK擂 5sxkUUUjF#l΋_w,KX^3kd iq >UUE۶V+$DAdTՄu%yӶ{XV3ج.NN12jV5Гt *]Mz@8@!-J{F!d`l{R-P7҆%B\_^syUd *!ɜpV`\`q^>%ط`];!?DϿ?_"5=p^Q cv`WItR%O( oB82ƝaEW4O>g;?17福YR(xwwwCGC T`pA`RF(-b\g{zôJI#d1M@~8 w ]5gt=k}Tl !Du1$p!ނH ]J$ZKK@I1Abm6.*CGG~g`6)Ӷ-BEt9CAsh1HU$f3f. zbReU;)([#Rq< 6ĉ1(\h::o.A#'֗VLom1_SU9U1 UgU5pQƟMif B8!wH)Ɓc2)emHqRNOH) .Xp{aҎD8.';.š!$UyBux/pÈNs)u TNҼItv_M);k-u]4 EQg3G'Q9Á7CGvG.+/˘%eY<= j,]ӳ^[OF!#;M 3 #EPi;4ڶEkbEY2-C;pz$ˊo4|<윮i?1e1#Ϧ3 m/ڂWohe2lJYJ<^h+}t8!!5MHS}XkEHfM1]-8-B0"2ZBѥ!|`#{+I{ἮEQ)0T>և$*`]1=_f @xGoEtiaq=)4i#Qc])s t]Gը$2Ls. yJSGZA69e2[m5C0vy"813kƁ9$hG`_fY,N/Kzբǁ>iي5GKI')7w]d2xp` ~؍*p2_`wB{ iZĔq!?1 Ց]B-C$g/)$nK*Il_|5}=~o_?%Ǧl,˄YVD\ JYE=ުr!մDqd"t((ʔ<7zݦO$9gg}`>Izl6c2?bgvc/!v=KӜ7T8ʼd1#rnooxnֳIӌ+޼yIۍ{???Ot#]Jr.Np&֤$źa RD&Tl'i<"I9F^9-1@,9wnuC5MOsh%:YC0YTGӣrMJ|a^pt4еzg:/Y. p'$]嫯pz`:2H_ơ$sB"B4)2>M1fƖCUF"Udv(u<5#Jff|PKSc4'%B**M. k-펮q>$Q@Ζzq86e 2U cO%4]+o}#I26wqAQTmdRTe#*xsw|HYrz~q^|G:az8?jEӵ )c.6'm͐(Kh܃̕R|{n[.G|ƪ!LDcLhPe1 mWQ$r*LuA0'hA.NX%Rxs8Yo!V$Mo!ZpzzJx>VGvYqvzl$s6-`9%$݆aGɉb:pVc< eMJ\Uv<­IAhYZ]cL ntuOZ$1El*F2iIIӴv;YBߙX{-{,FҌ`:b~l;~)Y!u?^Svml(+D"HHt9s(UB\錾nWhd5oȓ@|wm]{';g#3iɋ/(r(L)9 ^Nn0&WGfZ+>~~SwѵYUIѵڜA8#yG,‹ @dc;ٌDgs[Nf/b )y1aal IDATcQ4 !!(aqŇLp(hW2:C8i>ݱY0Zd*Q*Cp|á>2ꄈMVమGct9Gx32 T&t#]ס`\r}-il5/?O?H)RMZ+ڡF%3tVqGkڽ˷]x;E}۟ru} ε|՗ h=JFn1{{{Ï~);ڶ؁GO'rwÚզe>?XxkV{;|pdIʣkj!K ,XCtg YXz3v7fO>S^z_4T4QL&-8#yREptP3GUQh8v{ƶ31dZPexeg6oackz EwvٷZ#jDgQvHg|vӲݍmt"-Mw`G_d68[q̧Yap*2.=z@,8 (IRZ%UpzkjnnQ |Qc;zhնc:`״|?ⳏ~_t? PTybPDb`Gh މʾ@I1;vu]MQ圄woSzՂlFw3 [ܸ=hI?]P?s~oHxH@>BjBs2h; Ibx0 diUńbq[&&Mg c}XPx%DцzdE!) g=fcA[1^: ^L]\N gV8S"!U$f*fDNu-}6R"xu @#2@W[:xLMc[T=sJ ?l>n6IqRv"*럲qyȲ D1&YtG|+HR+]fe7xHRF!$/رe3JDDbCδY=8&ew˟_]CwYqn$I21l[DcL0n4OMtx.7mcmtH|H7\ x:Vc4,;qeGBSUeLI"$OdN5 w&sv[pҬ)軨o ,*ʪ[~itk * K?F0qh G Zk|٬nf vہn@GҴ#tPy2G,RD8'IJ5$L(+ۡ77/3rڶGRhL#U:~"¤F-fd*|">gSvʹ*6pzz/`-]uFkWoX̦it:S#Ewc#X4$7oA9f+"PN:" WkRN+N)'J)yҵ5:I#H$CIh(-o޼-OJ";1X#zGo& C 9{FbTE'\88'9j%cGYLx#ƌqTqy~|pV+ѝ佋y%{ǘ(ӓJh>A뇑&ScZB#jX^U82-rz !:"LJf<|t䥊bNYܯX﶑)%4%Kiqc̢P`I TƖ(()7F$'њ<6ӓSa3q=c@g nEVP,u% ݊럠uwf /^~a>P?ΙCہf ׏R={ī׿`u4P" 8[T8l q|odSVuM(i"c[J0Dpkv1j6cB+%NGL@)EnDɣC8 1\d3yhC 8f:c- (Hǽֽ1FN2)J ꆲ v5Rްx^[K)O&MVqNg4ޛ}oW~}w^J6rq/p<ҏ㺮X-KvC:.%|R(޿{aOf$}בe ;{ʬ H[)%surn|xakB|ZOx 0p#x۔/(%~L]HQ5v(̄%MU!dTۻHuGk~/h-߽G gtϓXۗ>Fg.]GfT 츹;0AxX-MH6JNO7, ]ב<33v XeM?|5%8a}nJ#J='cw64d zrBl(2q\Ri 2YfTeAfZ" C}+Î2;6deֆmM, < tI3 U^! =qљFӇ8HQWYNt3ʢ$+(ǎБ)kp~d'q"<& rvj(y;VR|># V"D~ɓ͎۫W_b2{y2xq^"e>ͫ=yHa!(ip,B*9:$H}cbf᰿(j~`SUJH( ;o~@eCXȮ풒İX.j>{qTl )MGO@j=ds0%Mډ&yUXF4ORaJ_yV|Yާp|Ej4HAS-KU%(r-08Yp~~~R&HQ&:'˔$;ꋣ\]4yCO۶6mbv9Xv-︓i ,=]mxT` )#I@L qiJ2]T1Н \0izi=R&HitX)8Osw3yx%Rb8==es4 . 4!I*Aж#W<}Q,dYb6 COTJ' T*4K0MYtccuMeHTSMiJ=9"uQ/rn膞,id 1VDŽCA)BWH5)H\xgyՒ5 ,놡#H??g};b紙2mRuW(iY?@&G+^Tgis UHOU:8zpcbѦ"JMpWoYе,Sÿg%?}'?O^?g:2ţ͆:/h{4՗_?@why=@W_~ğO*Ak0&qGtjL|٠p1bUFQ yA-ɵ:'Jn",tbyRl$o'@z/@8Cf91; \$qp )& D=sD%adilYu),o^zX,x ޾_%MӰ>i}2AMf˧猓~ۥϙLUy1e^yoY75&z6cG|*)墦 vB$Lx(S.ϒ,'Πy 2TPg2qswRg/B8P&oDO8GUlcJmg,"H ,NBI,7{qdUEHA GlA&#Sjim;Dh8(G4ME-7dl 0\C옧k'b'dr2!tG3CXst["fsz'ϩ%];Ǐ ^<57#~zͻ?)${no;B)ج6|{TEOmc Ӝ a{â1h.8X7{6'K%N(NJ]צtZ7tCDzG<2(ݜD!Q bXtJj`}w8[<mLc4;KxzprŇ_r5_o !#͂mEꗿ[~w8|;R%ux[ dES?_QT*KlzL,_Cofy H>D' uŔϠ$˦ZuCI U[nrMe1XyAQTi+ ,b0/,14u#أE]vKsm$ YQ҉<bF{;{=`)9nk[\>R3 30@ncݲ>{Bc1Uy:ZdL9͔Na>Ahۖn86E)5Y|J`G9$ХͺFT"K3֎g#X0ٱFTT̽M)J$`J28{&g)X?DH-UFf$wif RY~k~ecTTp~RcĨ1:afR!)0X/ڰY;8tòIl';dF1 2g\sssbGf*d:G<> Zg̓``|v@]$r0eUG_@dFw'2]pvӔk޽,kD0U`L62cx=ëWh%Y^0[a? ͒okC82^>zpϡ߲۷,%_3nnrww- u\Kƛ{vY*}- -RdU4`ZJY0 {:_險y-ڤL/4iҵ#Ds0O 2]V e$ZסKyv($eQ=dYJVya~Áf}8=sBr!KkGxܤWa$}{Ois=LX!zOo@bgj(]03fS'?W^qwc4͒s8t\P/ږ75m 7W! ?,W89#)?/xK^|=kY4"vriHRE|JQBb}Qv8cC#IO7{F,Fj!qvlm{ʼ!sLJ!"'"AFf Bw/~*ro{7>B5]σM9BJ>{Y+sڌA0d̓ڀu`!m8w4 RHt<3hii% JjȣMHAF4DM*P\h6hqaJ*!nB(MVfgޢK?(٬NjɢY`.еXz?W?#āyT#3 N e3ñRbxsS3NO/i9bys[~;&DdGD ]ڕb{LS%HB$nDi0E&?|Ptp<TQP ; b!4ೂV#fBL~$*v(FoΏ@[Vd`єKgDh#g p`BUfŖ7?jQM] QRKLV_"+ n91%?V5_~Ɇq Ѳ>闟|7ݖESψ8w@y|~7| e꿉W t;f ֬N"^ԑF4#űҰeXk29ՈN=EH(r40,q9N(9Lc iQTUwp?VXo麎,Z4p>@@?BȥbDh%tb(b`XB#s6-Vㄔ:U$U-io}ϡݱ^qZa푥H#]@P2MRI+NVN}G&27L>O@{6ˈUٜk2>ς 0R59.LD'}8dyc^p0s#vm( ZRWlv<MYL(uܼ=bEהE̓C+MӬ;SSS޼I1=!wu0hUя}:;a$TdF)P B+>qq|Z.?}UREuB;pCPle@FH=zSN7S\׏Vix,aeQڜ"u:y"$IKT[.߼úq~Fҵ0KC!Df;X*>3.]ps˷~GF O˟\__1M?x5g|W?)O埽o̯~Kn﮹|˧OJ#3AuNS?;HYʦf,@)[\P3Cs_~Ħ@7t~jɊ^|ͺB+flZPfѬYglw.|xtqa_K b-8RfG)!'Bj3hm~7#=Z6{BpHs~7Of2&c}` Q0͞a۲^/q20r[aZ,hc"Jnz_Iq e軙(2 !HnHifY,5&hSʧВ`*dA:GX#!dNDeYky  uRv†IFO4vmXV\Ӝ_i {ZnGVDH1Hi޹=QfdUMnz(%ݰǺ!/LsD)*|hbQC@xGI6U##23%P6])I}YEWI9J7fu(qꙝev3TUM.syȲg٬Ϲ8=_q~C]\Bd }g)|'iYk!)I-BD:&f&ɢg̳ExtqAn$G]|=ba?KIo)U3on2| (eS:, RT'89>Ruz% YqQ te$^K''Oed:2f;oaH岦mԆIE-4}7\0O,VgdJ|5~~P7xjI34Qnoo ۖz$rq|ާC52Uo Dw7N%R=7w03OlQUY0=':7ߣUdytAw~v'4ϟ+n'k=(>"lNɳ++CߢÅ )ZxBy.E#C W!:*e*EBQ()] -Rɤ2 '(arsJ9Ä3jSBJf: GBURH#'iF< #)ѥc=BKC Ҷ-ZǤ(X`_PT1ҰYz-J*C1(2(( Q(Li!ҍ?[b";i&/p2=혃_0Lm"Go_s~~ b77(ʚ<_prayx8\L3-.{[4\Z⣳Bt""ai(c@۫8g(b81C"0FqIv!MU@Y\^>a,kVKV+hSqzZz-evɆo\6lVKfIץ@TbL !(M)xۇ-egabF<Ű2{)ᝋS tmL>ryXO믿e)//?_?OPf5ȫ߼GOƟ__}E:Ì`=e4no^%>se !2>8 Ӗ.ٜmX,mq0d8|ͫ!#Q5gA Ο<壟|J?v/>{Îq츾y̰oQ()9(J@4\ߴL4k|rA\\#*iǁK>˴=1 6Urb;, ˺a>ۇٳj2"#Zx=nG8x& x oqE"m;٬'"ogw8,F(jNRf&bA7yjoݖ,h3?'θ>t}s-4ZG;rRRU997۞Es B S.TMJqz..Ήfog2DFҔJBPt]|?a#(*t@ ȴAlE:u~vԎ O 0ɕ.H~"XOQ 󌵞"HkuEfxٳݷ]2S_PҶI{f\RVkZTeV9FKp:B~P`Ec zilQ! !xg=B$ jF'* ۉaR'T#S<œ{t%Z B=-7.k6(eps#?Ƙ_OT|pŐHt81-JYf55mszZZԕѣ /_ r.9SyYn4UΡ=&A?N\}OSI7NYt1$:G\cPU5dyɕFEifY^U0Mi(F02-ZߎD Zf18 8C9Cpw w``1SPkQ( ͏cZNO7igH׸qk6g<eY}϶T$"|Tv!K$vn(#GI6b u)5eѬr3[LV EF[~p^,X.1 2#BRd:]MEY,V?{4˒,4 f>t/ZZB)/+ڮEyXL$dw$*`7- 8;~$R(JF7暴SjOoo>j]p<ǑnK(Ezt2L}uVdُdehV{qb4#9\^.( P~9p>|xD?-钡uߙ5Bn5+I#nt4`D\P$2z z8z42# ^ IDAT_o9ͯӟC!^,%<(U`0 [4#%J$4mG 2NnE̦AH&eN H$¢%c"|2\F@XV~5 !&~˖(^R .b>_<#A4RxcɒDL'_uk4"8T@M:utZ1RoHܧDGM]G]i-!H( (gJh!ǜYe0)<`#גvGHIuAv2f>sW$),jFӑ,+pq6?$K{"It917Mc5_=X0v K3 `z1*ӊjؿtxȲcL) 1P,+#@h2/$Zdu 999" Ʊ'ˊS&mɻw7<>>=lF-I_II@`LgG0%Iӌi3#R$Y¾I(NaWL9jNglm3L99:9F Rvn0H_.NhP iɳ~DHJv!rEXwa5OWHIӒ{]&čԿҿǿX)ZGU&s7q;w;ΌoR u~۸:0@O(q04]WZi|GD87~ryd:'/'/NiThg}~v, 'l5e`d4eyrʤvsʲ)")ah0nĚ`#$*BxҔXwz9Q!#/52'"jn$EdiBQEJpvGXޏx{wG,8_)!EjzqUn?qt@>yB:BpiMhhcji3 \_;&%^~K"C`ZViNkyH5,HJBIdE ZM1\ztI]{hUB=X^JZDX4QG\䛝sx\Rt*:0Op<)v ZIdum֓DD“g<|5~͖jn*$qh a$Ht~;08lϋ"6KU8< 1)7YLm]LTH&V E%3sѴ"^irxyf[=R(P(-4M#I3/(i=*>zu͟_CϤJiZh5,0%M;vvu16̖s;l%YN Ǯݲ*|)Y)hJs]XniURV9!֜ﰮ븾~ǾMqpb~ӳ&߮xoVL8BI,/ U `4dik^yîiǑn&:.B$I^ 'Q".ʴG1Njn߱, q<ܭi.|遱6|{Ӹ".I|C./y5|8.8::Î&Eh-gLSD"xIu\]="$<}%(&UUymQl*9Xr|rvOV$TcnFF>D֛{ӓe)wEASzC \<Xڦc{˄'<{rA?}8S1%\y0 =Zk;'Ŝq2$*jg k<.w U)kȱ>ڈeB`FO i3k ,:,fS֛{n zTXť1p.-EQ0 gKFӲ74}C;XLY,K /R(NJuH*K%G(`ǹfRl|JLӷ-E3fxyJR=H9={aSsz8j )f8=`>+CgS"~]=?W;FcBGt{٭7Xu7, h=YL7]5(-9;~ WX7>ꄪK'"Hۮ xo Y! AhO"_Cu$X!A}ѵ$G,KCGk<%C s1%@BL&b ^ q.y|kZ3 mǃRbt>T}^@> ">fh-Z\A[)@)aGǑ,| _%xk/)E0$pʠ(4ݞuC,ny#K BK}ױv?㬉Cmh5( @3zGg\\\'H,m6k!QW& R&hAsY90Ff iy8 G7|`$cJ u(F^D723Nj6m?oa],$c֝p{w0z!`MToϫL9(8\+0;lupn?FkA&s||9-M?T${nwkeEJ9!H^|MMdَ~o Lnj+߼= M %rv^| _KXoʜMYx\CҎ X75-TE7o:Aҷĭu1ye,<[mG[d ъcpZɇyaTeFִ?P@*჈VQ%9v VcCOo,|2A JX7\CjW<޽fy0{ۚ4o{|qs0hVuLQ2#x.7,*W3\,{P2W/YI Ayu{*z,bI}FZV,ǜ-1G9qbMS~Y"ͣU4vy1m-  moHs=f3Y,9;q#=I') a0ԵAI2R2ֽѐh 18=?!pl]rz{/~+|"pE*O/p~@琟i* ddk1-Ip@^ΞY.|2'))u]#4X=PTOϹ~{]ĥP`#2D~fyCGn0c9Q]_Ӷ4E*-tah,9 me)t!El6%z} s7"=vs$rs}cK:B*3q96裏hښo/b3v!:Do9=;Z~m{gg'/oç,Snh):%IGx?Aٌ~y2釁aﶈ!}\QKȓ_@#HbhhHZ2t5]ـ_O1:ibF~UȒϞ\"`ٔRs~QaMMM3~1''g+(RFx5tC)? Yl"7"MCY8p44Re$BqKi=rnV4IKƍg%Ev1{2*ìqc{rѨ"%΀)*Ù,q1Hi=ݎoO87~|$)'xaHR;񾥮o)v%=$EbFEݡTd(hդBG>~L> ńyn<0/"nEhv=B-E R$ ޽[rB!"rB .=$e0-s&9E9k#g<לE8vQj<9==%5;"3!nE^FkЉϰaD$01i? ^d{ه䅢5"꺆RR~Ʀe2-HGC%k,w}w5|9@cϹlD' ,;~):;9Rf>ZLlښB)n)y ͖}ry/~5^_k?e:,Q ֐kEp1!z%S{+A` $Z@i8Ou'K>{?e=1{n0S{|5 BJU.( Cߠdh;k'h-پ/}`E wTUcBٳ,n6|VEdn)h"L7븽v@bZ2Aʘ$ LdH8cѲdqv¼˿b')Yط-]H i!|dN|«7;1Ȑ!dB@/2B,v#yn?,ˌ2bCbn߰Zc>.8??o4 o^dY^&4ZmPۛc|V2*ώ4MǤ,XojMdmOR)NώHH`ϐ*ֆ,0Ɛ%98F83Ի~4#pu8:}k@SlAH!V?+B0jr8gh:dӧiju[5[,9hۆ(X.7 ]LH%*6RUbsKvO4U^qzzJUUg?W׫McC4[_>c6F9O?O?~WW]R=tKvۊ3'gfzoYNf,SDa|\$>O?~ݖnK Ի,Iӌ|A׍=E&SFsX_x|'|_)}FzIpƀ2,BZb>3"Odi Zcoi 8j8a+5IQY)秤"Q38os\]߰ZcH7&j~ϞN('uӑhBlDsx'>VƼc1\qzrğkGLKV`99=4$tݞkQJxkIY-ل4Mt` :EiytQO$*C^c-&KBHE 315G眝Ss^_5jVRe~h"eRVL)U9ù@S8+0Ʊ֬V;BsYf_o_r4_J2[5x=;;A*z(  7_' ޏdyFcRaHR[=c- c;0/8~y=FjEYH)HRS>./<=Tg<)2@-~;T*NWG{ ԧ$JF0mjTmP!"/B#V_ :\dJ3BtEɔ숶74"Ct"%S% 4sxʓgsrrF02z#Ba'X;3~p#|jX,xXcl}Ci͎~lǁ#*G%!k޽{vwKn@ xL`}OQiY,4 M E4z̰V,yTv{^4/^ %:b#K5gg1HdIsXz03h0 J(Rß$ZR׏q9* BI$9`A =~UI t8H" i)O/a28:Z G^Tjڦmk"tl^DO1` ev`QF S֓&ptt#E RqRv`=J]F;HPՔ m׳nɊ4Kpvi4CWݞk軆@8:9:q. IDATevW#uZBTxk("!01:x D\g߱#o! Bp7Mî)˒Ic͞~O]0hht!R&&jHW+֫5f4=]?P6ۚc,N"D3YJP) }'eY8Zf ADe:kM=?|~EZnPRDT/ϘT3v$MJ>sΟX gY,"O?)('C0xCݨGc]ӳ.NƖ}Gf ZGPV ƴxWԻwt),;`HV&=Z g ֶ(a~$M[f)'gGQ 4ݚ-blh[8#^}Kc=sr9}a4M|V{A w :4[a.0i~G Vwknogd>e1~W/ʶ6gC JSğk)Y4 DbG?zZP:TJ0ʘ|H")!H${CpS7mOZTg"@5Vx;2tmߺw!dSmxrO-fl_}5gH麞F`ߓJT/xhxIpRryqN'p#]sMYn6tmgG| 2[J&դ⇿=ŏbϹݻk֏7dSL'%Uqq~{OR)釁jF&jRuj7׏<> 8hjO5]70)&s I^o8l+vZh8^Q-~O۵ˉq8)4lvLYL*bųsNI wt]O۴8"米xxc^ekfٯkyzylZRN ]]t˗/y#O3hYvh3]όglgϸ}Gtxc3SD\|5Onu9 Jz0)е<)\]qz@j%Bxc!&FA Z:n.]L>ߣDfc;$AZv3|ElQ2 8£SC?vHK.{ J)v6Kmx'xCƳgY,O/J0 ‹/ښGZfUNU)X~ͽ҂7tm,R2YLY,HXklJUceI] Î⦯u1M&)%tm<ل7r˻[ǫ\>]Puu2*2}|'l[v6*ǎfՁˣ9yAY-x8vzœ'q4M0 bǟ|DUCGl?zgշu5-Q kywyq{{ͻ;2 b5<[~"/g>q}Ӡ4)}뛖=YZF>$BQIZ !SQt%pB ΰơ }gC脶4ȋe^&#"3ŪbIMS%vBjCM5P  @nhQH6hUY6;l2ȁ缓I9g}PS|G$d۽%~?$/ ^yo^!%@:{DU!i1",6G*Q|򫟲yuo_*zpd!Fő]0R0N('4&/{6vGyhvi7ZL`'8|NR Sv޾d1gg[yUQ_Ǝ#ӌ_hU=tdZp2_2֖$Y,C?FC;dY0MS0 #.hs#Æ)Vwl-4/Y<~1߿m! ;hYAh$x,a#E"f2Zb14nr$Z0 ֍SI1J0р i2 T1#EsswoX=KSst6()) q55Zv ]Hb0`Ɓ$ehG%}?Zh6CMy(eHD<ۜqrr it7WyZYţS<%1sO~@Q'>拂?pwwnWG޽ ųe^vݻ<+IZ7&޹0j;= OAYmOMO}xi^E#Fl )v \X YIYMG0'g\CgIB9q5agkX8eԀWRJ !ߟ(els|49V:4M\^eiezi4 &ߡԱJ69E1)hN L>vv8vhN^$錋 ]KH3V}=Öź ռ@iM ZG4^q~z'l[^~ˏ1ѡHQI|vn;2vɒ(ԉ!lku8dAFb`EVW_'Y^A 7$e)ӊnCǨ΋jQ noO0?s D†hH vCE'DFIGhL0NYUJU%iZh-A0'g )8{Bp,:$<^NDZgI:VaQ7uL,e\pK۶KDn~0\%JSL{?EGpu@ǡd=Dnþct=,V+$R8 !U:dJDГ(60ځͮm#%IYD^;¨G)8O,V?hX f3j=c^c ?^?:s< }@ݡ#hˋ~|f8^ cMN)AX) -o^}wwMzyBb P3t]%aǫo^;M|9Má}*xs{1\%)s`$=ISEc?}rd=_rْms,眝?B$I|ĻeHڦ'ϸyG?g>?#t)!ơPv&m' D ,*BqEbDȤ2vкXxvCQG=NS|c 䌦Pt["{"ZFk蘼@AgL~"Sǚ-4a-Y\.i%s6-=YgK{^y_4TY?Oy%]sp{i-l^"$/q^a ?K5o<~`R{heqz84Cpv`R1)B ht R2ILŢGˡip^b]AG[e(q. chxx#fh$BR1O{q>ÅBh `tgH֐טŚĤdi;F$G{Ud9i.1FҬ@6R"md=C{FSn&)9={PdW[ =@IЫK'yaa9[l_CYcɂ<{;޾|>9=خC z=˗@HYeRr-ޏ9?{ď~=j># qUA`݈$%M "s̊WoȾ[/WeFr JD .Fv~1-h鐢$m?&5BN g.bD#x $I5]QU}!%TUL1M=)Y޾}K?=D;D'Mӡ%%U)&g^c.//(%?y;#ir}fCaFd||Qd 3 /Î88O?ww~XSw໿zgk"ٱlhkpep%'''c>Է.&\r/f#~!YTO4wyzfRw=p /<<%/D)%y_E8v$Ejӑ&b{[3w:r!'|_r{u!Brr&r6=qmu=傓Ւ[ۑYsyqnwCYUeśݖkldEB`KA9CkGjR̩IX-W%}oPt1Џ`v ơ/)>:c1pGH/?_S ǎ-O?8d,ܶ]/sv=ʀ3 A?:O~1ieJJ`"3!GM*Dd-&t K+6K\4֜,msw$-Ɋ*64gk拊C!¡\_BKFOOء:Z $z24RG`!O3 YM ] e˳I"FJsBJg/S4g\atykdb)AH \.Btи/Z qmM4!b,Ms\4T4arMtg/#D$8uJkz5va)&,|OofHfd^]Q-IZd|Xc@Q) =y6c}rJc\Ө/4S"|qgHe9uސs1>t,Kx5Z8WieŻw D4#/@0ٚ$ Uqmqv`7Y$FCáB`_}|>n' M*wiAJsɺ8sc `B)-PdFԱ*%Aw R F')n蹾ځ3GHIZj0Yl;jA9̈[JѪ#>^T %*fġ9(vrI?VR6з}(ՂSh6΁ޛ޼} &̖$Ph Z&xJ,G)E QzƉ& G gZKȾ M\ I LҢMa UO' ybJERr)Z}bK)e.p>;vߡ޽n{&*N1ZR=+ Bq$f׏Y̖H!v5EQgx}Du4Ms$8G՜ձj#u" H!&RJm:TSqHZ̝cfGs@dB&7!!LK۰vfRH(V5^z ]MLvpzpv~֒.g܄4x]ׄxxx xR5d#őY9OZyvajݷw ]Ld*c$/iC8yPZ.OIz9cV}O_(S(E&<.-!rFRdI\Bq9[? )t K8ȓۛ 8Ѝ1fa $Ƞ(֜4 ^=MCj?ZIGJE,{-X]'4N>4ŋgIAUl.Pp\#GSUQ&yr2_ȵJ%eCd3~?aV])ݟ0Nw=D}0ɿj?r>i!_R*9c:i8?/,Hid(2&+iBvn{f#bNf|g|_ SUJ{ݮePd*Nj2IBq^28ֆf\Rm0vg'Xsqo޾Sj|aPŒi9z/26!/ aO= 8ocTD" h<tINc=+USLa8pKH)2 #N)U):;FbTHihZRco:Qǁ0ZQ&%OGaZz1NBpYÆ^T՜aP9<%/ 2"#K4QJ@RENVb&-5:12xy6IhH54P3Sp:ցFjIОp87L~BXC!*b߲4B )6&d$Q{攘8V貘!9֯,iRŋhD}x}dG4Zc6 3q÷端g"YaѶ#rSUGޢ4m<\szɫPocyngFyꄷW{g9.&P tmCݵ,V #@UU5ST C](!=Hń4z1# k"O&bzųgIRͻ+5]7a ,)W>\\XmOkf&Q\^^p~f:^2v? cdJXsLZTQ.jj@[42_e>ߵ Y1c-o߾g rZg >&.O]`*Gj$UHXOx |ŗ4m掷f>&p]?е# v1UtbH|Gʙ|嚶;[f8?ZWogϞknޡ,QR =C6 ՊkMQIR qVX׳?.> .űq"[`jfeNAT# 7 Ѭ5o^EQc341X,Knn隞i-YJcV 2N"g¬z O ߼FDƏ; L`}4"A754][/45|W`[X Tӷqf]lx'l:~Dw.CYɟCxW߼cK;&{ &5b 2 B|j~W_3 YU)`d'ljۖ;$H4 $1))=4[WW !=i9a)MX65Sq`q6 qZ Ʌhp yFet}=.HaBȔCc 8ĊXgy1OY-H!968,e0aT4A T'93GksͣZ91Z1iLAM2qё0J "oG%*Guo(XY⸑$xrYFL2*Q?X/q`~Ȥhsap~\^0M~3 r~oy@U9;[3_-L\.n1I88Q3S :G=;~B}L Ane`HGسBIuh"U¡>7 cb]c$Mblj*ٷ{ w|klNd^Z -2\__=,'k$~{l;B8,&fJ)a`r#&դIc4ޅYyx#<P"0Y1+*8BO/{olYi=kSxL5@Ww4f@c!X a"j(2*x&u[/G?wﵾ},SHK̝ғt82M`t"Nr\19J?^DddR-0(M?2Ԧi1ϮctN?D[Mg:Qs5 0*p{sk VWoq^PU5/_ĺ7> vwh ?K-"hBDvO eyJzjM;J#\ҿʴҧT1,&G¥IGia$yӶ!;H}#}<c%$t(ҩRZ&]GI);= v<2xGssdR嗟!t|FOifV;:OKòɲ2GǛ7o,.=g0 ~\V̖rT~l[ "P%F#s2yb,'k?3ʼk<>0pX2xX0#͈o.YӒLѲ.Q`Ӎ)臁7k 'SP '9n!Њ04 R IDAT)gϮX-~ϻ>pاso?Ǒdt}L'tRa /$&_ Ær sbeh5J Fv?(ɍf1@oUQUU1Lx:;-+.^ewox &鑢^PV9&l(8aC-:G2m'3&ʼn-9 {6'Vs`ޢdN^RU ]B V+o`?!ꜫ+^znY Qw#e"e4F)r]6=v<-Js"1%@6\z#FsnҶ-h)m46X5{:.J&u#1Xn?gtRqu~GՔ/^aS"SjJ( IϟRD9ΧءH_PO.Sd|o de:#Y OwC1'x|1wjD5TFb;6iyi1B ޣ%sfml:cƘ\C{_A?*V D@o&R:* 0|@H (P*JCd AnŒHPA2~3^}3(zHPၦiRX #2g%2d:0k__1K9R\/X,gs6-nʒ(=eaqǤ^28DH\GGdidg:-;Q˂qL*<f$<з/_1OQZۖ'8z";YEMG9?_r~~7ndG'YFPBQE`D'L'ѺL1`jn;KOqVе#1$mЏ;tiVdY Brqm:dX' rj Ȋuh& "޿Geb0ޑ!h.mnLf `u^c@~E5<<: g:-X?X7O>y-wG޼<\ȒUʅM[ZK e4霧.(P9'7FcĮ@E J ' OsFO ,"Çp2w1=ʪlu{8<"OUadG&uͳg/Ln KÃ.j 70:Or!Yo@$~d(O挮),1mOm'`=Rk`,O[[Ifm*&5YQ%4!OJ{s!U'g"U]pqþ)ޤ8ua :ɾiowDiޖAH kYځR m۞ x%sCIs:2)H]lRcg+R1&v["G6:{DLtvdRȋ M0:ӆY.,Wgl["d4dJ]N'mrUֱ\@>)(*sf" oFzV &}rCO#3JL*2MIHY6q,;GߥN91//(˒1TCs|4fYFtI9Dl6\#qL+Mj>+6-`Gˊ"sx' 83.ήx[Z..Kb Hn؍xE1Abm<=P)ݑq I?Sr+7nD&T;KIQqu)+nx!!h E>g<,s> f|_W3KK>Wp_oCx5z}G ^~/p4? %K H Gז2bY)ihJ T 6(#02gtpaR~L @u ##d!)d>x|z@iA)L<4%ޏ .IΞi֣:8B8\) d,R"(0t8<`;Dl=OO=M EUfGQjb(;xi23a\|aȇLgIY,. %#G'vy"|d)9xǺ̫24 I$`>3..Qqy_?҆H(3Xa쩫 LSdtVOҳY=^G Jj~O2=MxRfx辶3"~0FoJQV9%=MHfsDJ=",?]˄6H!Z"a6_uM҂ I DL%f@jZDFoΖ6@/Οq\X=86|㖻=~BFD~@ yJY/?m[65j-ZKDcb~|VatN4lwOÁHU z2R6%*ؤ YQ =q@x!&К~EUbG3Ԇa<"k{ 7MdąTꆑa$d^vzKv!9Z)%DDv, h *81n?%HF<'$vҳ:r-"åHIb0["-)`Z<}Pc3ڑLOH)?}7r8pt4Ϝz`2+1yw.%K6kzB\j~A~I#?Z AtmH" i)'xK.&kG$Քe>+iLYyL+y/ӯxy)?yTS.(,%a3SWF@h =ad%.ȋJ jRTi M5R9D!ǻ,ӐJ:2hxwu2vv ?9~S2Ri =C+nn:˗Y|eX]d/)n-.SQEHίxGGUiB "00 r^^c2#@|;"x>? vCPOk.W䕁`l<y9+2|~_~1( BK9028}OY&US2pu}áQ[Z~xOI|RO#cUp<c} ,Η-TUU5.t'׎%ʱlYd2x؟t<. ÅT]8RRdÖFBϋ3+70eA?__.SDQ*%t]!c B~$KI"cLPFsOdD_s k;d '-704 "֎tN*dZ[7'BKE$Gb1Rj 68U\Jge=JjTkHF;& thGHÍ5Nyr䫯~q~ R*f1F'T& <ؓ*/RI*i޽ɳ4m 4DYVo %Ɋ"[2?2QVSϗ|}s[~EL58<<(8!mz c-S= $CwHC#ƤD*9BTd(lf,|'т蜧pl{~ D~πg/O5C"es\QUYSWeɳfy|hxZoaS û!Ut؏}=\/Ɩ^u]~ēEJ&, 3[g)R%v)JX#0R&xh Gc y3ږ*Xf cvƺ;`b2fWe$'@wVr8m[| Z}|X?& 2Hӻm[Tj6'{<>܁D;|DqeSS`t#BsThͮVsl-9Fzr.l~ٔ$CN5}kMjr"`rA+l H2=a %k2!uɋ/ 6RkbO^7a̜(P|y6ES?+bS֦RQdo#Tl֒ǻz;-풪;-!>kn%Y#i8'.c}Jsyb:tJj$eߟ wIC!˒g$К\J1"%+ ;4`-ƤʴnLg R'IՈ§kLv0qȹ!iڤ'#M!$v߱ٓRjV+p:s>Gvj24Hoּ}"݆~zNSl2{M^NqOޒԐ8C"hId,6y) ˳ǦK@g2w0xD%يb%;6/dud菴.^@p;DF#G5=6:݄Tcҩz޶ ] > y6OcHjBnڒ,Oϸ) CGw_Ej4!7v34T!RS2U/e{$TMKW۷?6m:ի\kCi()PhW.vgp(e%W|k O;o{j+^ 77?0)W/.q$ F Ip%%lby6֦ʬF޿:%<#M∘ LAqذ\NDhiqlw-WWϸ8j 3qid32D$2tlZsuqj5x1*a9yFUPC`{(*G1C(G>ۯlhUu^5]۲\N'Z-x\4;7,K;&s'> Ba#T=,K~_2I)aW'<>>=uQ -}.\__SjdpjDkͻw8 RjI' +˜Da9\!ʜՒfStgS2@{ Nϊ(ҹQBd"5wOL&z8Uad CTm?z2c=:+ٷm7`?W9_/D>6e )2P:PY& B )PZOiɔb ØM 8;H6i[UtZITրth]O FM]Y=aq~|MsuYdD<8D / 4kiP!L? UdB87<<]$xH0}z<5[d:Ȋw%qpjEQz 1D2!Q1Ec:*,{g<ݐWy)MđgAjۗTu2i)I:!0XK, *ɓb7Ⱦ=F%F.+w(݀) EQ3NǞKֹGx@+Eik NhtczRjBˍNiR RBT)۔N .%'`T)#WP1`xㆾZcɬ`6.%ذw\\(2k _6T9gKVg3ٴGxzl;x1aXfGV'wxzBTO2D"M2p<=(5ގizV{mD oAdhZOxHt* -9B1G;ˌn˿KV3.WՄl5A 88HA"U% "е,CqΡD,$J*̧4iAMT@Y\^^"U8$Chf< FpL%(64Tѹ{Gxt=AJuB vTu%1n ;B 4;GqX]P90]JyKh%9匋3no?蚞!2V azbQeJ+<{篞wGR._c>Z8[+KM 5W~K&>"TJ FP2@$-T.#< vc?o2ZK_fon Ckm|ddU2~D,餠zVdJ⢣r`%Pd2xvۻmqAxDiW֟Rd2}ހHAeh E1"eL 8#`RJ7kVc,,sl[tJss!SO>k޼y!-T- sylʯKG˗$q1v#e@G:kq!2% <;vSbW"$#R~aGGwyFUcJεxxqa!},яR۹@~kֻ1}1c"~Z49軁Ψ76DiZpd11:4 "~"T?n5%J#DQ&PIi Cc2-XW&DH0x=qsvt݁1:z7遧eRzZ9eE4m{d;}L4J+akJg)9ed A!v=GO:}tI@gA+vG5 BRz&d!*0 :~b`x*s. RV%FN}guY96(cm{K!u8qU4YV"E 1'ƌ,\d,SϘKL`K$ om 5AfyꚋKy5oE`u6%JLpT]HqOq ,TNpT"EbOfy`tDuȒm#zIULj ' HAS28PJ % ($z {]{mҶ ֎(vI-N*{]ZY*]VDZK ]&-u]31قɤ9d:gXz^9\]׼[Ru]g!vr{݇oRkJ|V,V̦5>'$R{| nD A5xڥyi-bdɰalH\xhKgc]S&KǑ ՜?c#>@ׂ/wkELc}298'PD`<%R~IgZ2LHUsGP"4wѓSs84]_]^Rf3V1z؝얶!Y'NL=A44RteB{%75ռq#nd+s l!aK!1`w1Bb !,HL c]Me*2#on{ݿWuPL"t#}ʠy+#iRP5.ºCI 9EZAHD3<\"L0# ח5s]Xx?p8g 8"cy<-"kCsPBѵhsEIҒw2V~kE ,w8 Xg8q(I*S 1ˢ`\2 .'THP3 Gb1c^Q$!!KG~ȼN97[F#i p~VE%=J靖5Cxc>z1e@r<6HB5M3!͖ҏ#O>Ȗ|l׷9[]GÅ1=5!X2WgPn02Nx4;eÙJ~ʣ|޼%ˇ(yvOXo/e*|aw|\qyYp2/ AVHHCN?/u~ųLNCtZ(J@wFReB&O7|&4L6Ě^a}D mЌi<&y!&f& DQGꓕd $*@`8ևbr-+Z_s^yЍt`;0t%iRdaX:{s<ܼcGadKYxe\DvN@{ 5Rk$k ' 1"F Jt}҂([nM$O'tk#|go8+2FiDi?=,#ۑiO&&_$YV$p:H lFt]G"c-0O2D ճ,Cg EURXso'^{,b4H+P$d0p iQ?pH0/#]=L?ſ/? yJQkV TX,x= \$VXrɢW%_~'$E~قs>|H]!m[ϩ[<~rDHw}fsO|e"Z8;;߿oITi=mϱmXohf ΓjىtJ%H|,IIX=%:ш%|xcݚsR]0$73(m[6=eX(ʄ Ly\v_\#%yt}Ote̡d\vDk<~ȏ?f<]?߷xf9εÀfXI4|`ɑki08?u`Ɖ{=E]GJr<6) AYg?4bi8f*蛞~sʲ=v /?~y)p.B(;}|w}3E^Iʃ Q9Mi A,1I,C?냏ga#h!ޙ۶gg,=bY/S8d<Ѭ…~j^ =y ^H-9&g=駿J}d4!n X<Y>!U. kWY*:I"`mO) dc61 y&h|'UG&Ih^ql$<@jtf2>bTJáa,ZaT;w̫Yw}MEa EQPUeUe Ry<^TE[ %4i"~4 LC3fG%%EٷERR)?4Ug '/~Bn84>m\!x AX6 ֍ƗKxUUI"O`W `hώ0^R$IJg,mg-'xcO7=]x㪅"G5Y,j`&8+8g q BH./I3=;8nhJ9GjXX%2$l6kDQY5sbP 2APJᝍpI-eY(}z:EǏ٭7|= PfgZ9' ,e{;3dgMGj/,PH04_&B@fQzLD>oߝj?=#]۲Ro߾(rR)(V3LLē/x#\H{O9Ȳc'6[Hsؒ&'d }q, jǤŜC=;E6)[޾{nN;572%u.~ڱE%LD-;V<)7,3Evwd&y K>F4!8==:ip""BlpyuԱCnv&TY8lK 8~=I;=Z(7y8uHX0ahȳuY0VݖiDe8 T )Rsw[Of)pq}zc?~ɬ7VIP5_k{vS?@Y$\R44e"sv-˿`1a<9c<Ǝpf^L=z" N=zD=) \4-h;BT䊨@v:Ƹ Tʺ=%+@%Pudei#e5C!z%3>͛nzݳ(ղ#xws g!5 'T$AhXD!֟EFR%[!^`:Ag9>L -0_*xgɜoyi:#xٜ%$)6&Ce#iN\%h^^ =CTEA @(T"S f9NÈTg,y.+$}? =0"&A-ğGs=k7oFTUJ6ݮI,>C;ڣh4-αݟ1;ǟgoX]̩f J*/N(-`3cG&ŌբDKOsPDDpu}AUU'oþ+)_l6O;^~MQT\__3"w|e9|A4l޼yԂTa.#Ƙ;Mh I|ꩊ=0LF4㣧'4zv=y \^?~~$$Ϟ=u=clVR ߓV|$:)88q8츼ŋi㈙~)WMn4U͋O^Ͽ¥9Ob{bu'I }7iL {G@<~ry7_p@'(gϞqǑ 8kQ Lp8G12푦i#y.a_SQ FH2g\)RP̲h:;f HyAE2֣C Z˜*)uYH .B9߰ݴyNES1f"Wy\-Qbagc'98npƴPRp~vMw- "4C%ɠ`Yg<|vlw;3ƭNR$OSБV%Ov}CY#3 7|Ipz,gx5Zfd8fE}Ŭ v!K, fҳ#R 20M#-D4M1D)EeB[&/22!1̩/SUiPR 4?~~װ;x|h=1_LaD)}H|>8zqh0IqT7dYClY)I=nGq(PRѶٜKPvl;8fxxhۖ$kt!*Cxʄ;p..z(xmG"#3$#$fIw)8G.3g^Go-8R$: IDAT!n:iuJY%w[nGLY.k)nyjNQft]G Y$)!0R~gO9FnovTzzL4ȫ?媢޼AE~zym͗ dywo$-۽c2{6Z5^xmHP:&}Di¬Ҍc!v?)~2(b,5fKOȏƩE\,' ald-Y:C ɡbuIQ Dv{n}M v*Yj oS/_o)*$RhrqUS)wo#Am<{OY/7YyCUW_}w?|ü4btvr9/o"H4,5圲H =iyt}/qhQBG)E$i0LI7au:⍛y|r|d!Fsf >aw;޽*;pg:=&{m9;y{޼geJ m=8[-zI{i 8:w^$$JJìʹCh XkSY='}?E`$27iwo8;{g<vF]{{ 5n ;eF~Qk$~-KΗ$Ic|ȧiPgt}3-$U5i'1=ĉ2~cB\wgqY͍,0cdRi  psʇsC _՗߱^3 :GLZTM>bsʲӧObG[裧$*Vbv) E99;[p˗q{wvX&]0{{w`O9;S<|`2-]d{KT~:&&gI+v 684cQ/IFXyOsiMÄN5Lh |ߤ,K~=?s>z4A ckn ~$0S8x$+f)ϋ/xI?q81fM˺5g뗏 |ǻnpO >OWgǚ@ z<}5e3E"T x6K>~'y cO$iF{ϟ:CfH M&# [H2⦆}qy~U*J[X,"ÌdEu~.j<}Gx;w['lGc)Hd>~%T%%-O@(H!4Iqĸ5é>=lfض=rs.V[铇o︻ߐ4+|zݠlR$0eo;T7٬bo6P/Sr|H + DÚ@8 hH&ё% سY:e;wHs9يh tCKN% 8)dM.v<"x8B Ǟ|F5+@Fn׃!OO %4Y&+uӶ;.i( %BX#Ҋ#[$iI M> !̪g+Bp (!UO=J,XX-xGX񹹻e{_ݷ_wݼmv\^%UEP)`\kb4-{Rr8ܮ) Y&(q :v M0EqV  C0īA//bpʑ Yqq0gf 69ywo1f¸FhU0ـ  YL皻wlw`2xZS"D{^;h-Mqm(2>3^_qwo4F2Yr=yR-I08㇏lx[4'-|bgOh߿c,(P9noߣ/R8[/3\CӓY4Uȼ, ,Ht"eyp{H4\.ȲH{ 1+SʲƌLLbx=!oCiR92gU4-1ɞj|ݏ4n}?dd{XoA6'/$O~ȕHfłyTx{(qYT}q^4'̪i=g^/>'Ldn \$ۆn3apXRDp@x3Y()4!(il1^,Y߿?)ůmoCUu}4yNy&QȜ .)T|hb}|)޾n0\=|H3Dil6GfDh0CǤ{Mx!]>Chݡ{O^<~`<]a-yVuÇx-AYT q6ɑY94,:MX-IƯ?z ^ !^i92cOO'Η_%3Vt]êN(sMr6p4EGxnk(L7Pi;3fcۓe}7qQ4"XJ j&sJ Ƕ0ֲoZD" CW/M3UT 'Ihg-NaBS3~ UR~?qVW'vdpwD]xi#BAQ7TݰY(8Wg!H0 姟LU3Yqj9wsJZ3VL z m 2r,&! ȝ*YOߍS4%HO K?LK;@bUȤD QKi9L EV]'_\\Fs&u4md,Ȗ0eq\\\'l6|30GVT >|E,G1|ENn*@X{v9??kE}A^dܽ3\2[${ԤْiU4R>ZKv]Y,ȲNN[!ca62v{gILycV$)4Gxnr5# >~B׏4M~JU&/qHSI͸\߱{28EQp}O]ȫa݄s"*8)2nYCMLės)Ň :~dt8P!J$! )Ait0~4_eyS}B0xcc7O5II RA%,V+ıѸ8H P,RjE?v;q8l@8O.n{8(xBLD>ӆH5UZNihȋql)ʔ@M7kQr]?"M԰9t4`|9 ,O=A(Ï_$B&LJ1:˄։SҩXM!Jg*MH)(qx"kmR)`Oɚ߂h5 2NoSiH(g)æ"\r5Jk&ӒiO9+1@i5J@3l T06BBK4-vm"V ZQ$H,GgWo^1GaL1[fw$ȑzYЍ Z%x~1MwP*Z/qF/,o^Y-"f'\_S%ŻR;8Zi?3ǜ]f ?/y/LO^Lc=JP3hSNze(%4 P2c>K%Ex 0XIYCp\]DgT4MCs3L$T'-rLɞU+斾X-ϸ:/>ߓ:9K!BKAtܬz5Dj[Ɋacj1\ xJ%:MH9%mϘ IZFn޽&QOfe8qc޽a]%W/v;E;H޲dYy҂(UPf%u:w IpFP 5y(MGJ9;kbL/:&#-+ÀF&9H2zzHS&7-ǶŸqadeN$')QsDPҢo&4Z))/W-m7wTD/)+RB3LX먊)oM""-k >xdhԝI$,Kl6L?F+c S?OsA_Փo4 vĸZb@Q i"E+%fĎ#: uiR)RH&R) 1:TSdu5DWYF癜e1+ť?ҍ1;Es; %Jwgq┠EaؓW9I.=i)1Ļ""Ahɒ j=p}}|?@7ܩhCTyB6c2|J:j#Қ9rhwmgP^е-TR`vXaRth|#+RD)DcGc2cH1q} C?˯bה3f =9XGKh]ל]5,K1=u]2eQw[quA^sś[%ggKMT Ht..릸YQ_E eUpHtێiIA^h"e-90 g }W5J{TAyJ{b~N0"HyEYi. ̮w+S ȓYnXpb7@04L)֓LR$JhHɫ!F˨7qh ږ},j*exD)o0Кat iF1$ J!摵3a6~w]GtE_]aERCLIc+ɄgfO~'~_gzm8{`$o[?1 ͊n6Rx\7 ] q8v8-m;)8?čfYhJhntT(#8%oXgc88a25vlҡ&.*!ϋm $nEuɦSzVRe IDAT)>jm`v$u>0L6`#-9CKڡ#K*z3'ɠ"Q2@ZfxX̏"U,mmc<ܺsӧ_qu}A ל9 CCv -YAϮa`hoq4-/k^?'؆bZJ[fE< )BL麎v2ISƑE!kE֔;wYtzְ])??SMjI; Г'96\O!'y_s||G?`>[pq.1Vg%J(")iDZxV k=o0Lv ۧӔVxwysiDN6$h!}x inA 3+toeW7+,Mס4dILc8X̘LDrZm=`nqx|ݲųl_7,ZdYr oqz|ӯ`XY1<||Z ̦%}y 3=3n蚚kS#Ipw~iZO9=O j'鬊3Sv}m%E\^=Rӌ٤8;{qv* I2 |N@"DW7W(ۿ*NOozK?PRtƒ茦3Y\DLcgytt}r\ݼf[R)缾,s?:A޼z 2J%.,fGӏ?K./sz|SG =_~ќqڒI4P-h=rӷC*I?!Y B(l Q iz`:.VtD7rE^Pg;Щ`~5ѓ yB5v0}'Fge$dRhk("鼏$Ki$Kw_ndRvf5b$C70 ୠ:GGJmư^N欶 .wJ=>`x"U v;펣9nvE@HOތѢjg{J*).+,ӈ=wu%3 #x6I"\JRrDs| =RW Q-wZ1e:STRwmLAH-Kpov68oѽ#I3W,wxba Bp$I ] X0!O3s(i36Z|$Ym'Q Gbkiu/=pxxՒmH}N#Y\_`qtzI{愶Y_7#:xpm4< NN% M3K*d# ??b^j& ZKW$9qnma1"VhqDxU1[qudVJcY2hBO^5p~~dZ ~ɴj5ēhw\Vz >BLU@ Yq w&N2`=Z;*ф` 5D} ג9ux!ȋ J!.**G cbE#(;f7RTzrippkZ\{n I%iAbmħ"WM&lwk.Bt"ص5XZM4pǞT'H2rtIi nPL&޿H1}=MӒ)~UI!5}[%B߅`A~R3fh)^ q۬JMJqI.q{XѢ'`II&b'IEX::#Z*ʢDʄ`%(+@GܻdA.7l M鳯SɧDQ(B!MS^c]w= LK !GGs&U 2g?lݻ&Ւ iU` rtZ̧ۧGf+:CQڶaD3v,ق(0F"{T;m7C;h`>)=|}9_|%aH^x3bL MP_vz&qLy=$o ]Wet;s."|i?5߱uh2NXNOT y-BiiqGx.PS7+%ؓ9m'0?o ISZǧ@ ^~MSŽwY^ۗ4H;x1WߞEy'gg?vq w=fPM{mvH-pC'v@) R*$c #bzJS,B}R`ݚ$Y$<F,IL*YB$. }NNnޮo yF0QU@PA)}?b{Ty+BtcVl&>%ޕ|49HtH Qѓsb=UUgo^r~ GCҼBȑ˫yJL O>xX"57oof)|N ͚)v@˅L ZocIYuW CSW 2UǷ99>%W7[F+y7%=Sp17P%/| >7y\_Ypua:KQ֛+V7,&iJԀTL%db^h {55Mpyqu#Z3!q0=q6&0-3BsN\C\#yjJLaٴɄIy_ $&Y50fՔa{G6Iiڞ 'O]w`Zι}넃oxfI?: $3Kv[3fJ)^| Dwo_s~vѓ$-¯oPN*grSJhrƣp ߼8d2U4 9nK?B(yWO!d>-~_P'Hw6|jM?EmRłl8D>^UU;t]LROnV5G' &)Wk͊;"۳H)=z$Ii-yv[X?n39r: xt\jTڜs}u7InV;NNfG'\]E<}E*X̏yf}C?7wʳO/?M$DknnVY!z}-լ$)y#!b!D*CĈ4NRRӭzSA*9KQdtL)ĻO|Nj[gG mj!83f JM1c4O@s"P/b ~"YdQXY!i\"m7ysn)2f('j}l"0 'Ѳ4zufy^RUUe4xUmT*l-ȳsT23J.;-k 5YeCC3h5ɘ1-iRu#J֊A AD`k!Ifɂm]O>W=UuP-$GQܺg;..溧mYia$Պ9y橷={U`@z/x8s(&Jb.vɻSjF<0gYdMɋ)u;"O3`w5֍8vǶuhLhƞ,&9C?04Iɒ 3 !xay)*]M 8 Mgn )#/h$,SIƮ^st|@"㹳\FI7l)2O)dMqsC582!/ْ9ǧl6+2Q9cX5=D!BRRhvx@At*1!]|<+ 6 zBS.ISFDB} * M['!Ttvch;7s[t"?#/_8\__u PGdE2{D@Z,^ g/Iʄ$Qlwt{`"Y.?E(o,7$2#X%9e^f5=d0m˦0voʂ,˨-XF @i!CEyqK]_iJEqUVcl&:8Lhp]L/iM<!"Kq(Z/xGqh<!zݻwX.!S׵Y2[ίιY^Q)H!J:w R`ǁYv^)o=xoO$:I5xAf{ZC$Y)34%/fU;ҏ#*xHSX! Bԩ'zM<;`a`Zͩ[83I2yLL&%R'dEJ&\__+&dypqA#PbJ5Qt$Cl l4#f!A>`E Zr|r]1ugI ^R>g,MwfL(%).0抢(m;Og7Ӓ=&ӔP[+/l#kK>x )i>eis =w<?!GGGy??n-..(KDጅ(rJ'g3y /ft:E2RSڶZW>QpAaMGJb Se:8:=y%nkвXLƑ:VшwZ{"'GHDo.,q(X)"*"ǡfgۀr5!+YvӁ'eC׫׬+زm#9:UUŘ0#]0R$IҰ{m !4dȲ3:&[E 4HV R!)qZJB7zoQ(GY0 tw[t>KP0p}0-+M#5("nE/Ztnhq5H 8K/A+MEz0qiNtgHf'yR־K&R B 6 rI(ԑO=~)~?.x%B( ]_c]7"BL܌{CﭥQU!^{&d:P*#krԑaC`"v;aImƭ"0؆jVǮS(tVkXQeLx-:IP| )gaA-IyF&93l6Si11%"\k!2"}W3t}m, L E`$TӸiz횁˫5yٳ7X')ӇmvŒ ͶJ)/>#dd2E 錬PƱG)dR2Fv95~_XX16tjbE>"nN(//ٵ $͢DҼI<@H)C'p^3޽n,+ho^d4-vQBsrx g\^^+H t5;w臖1M6#ߣٶ|+fh;<<j;c(,K%H(3`iA#ޏX?l@L*_zq=s Ì51,!<8 X<EPNK-9877$$ui̙N } 'h#z3v6eiٸIgwiD(iJ'=`6Gh}ukV뚾m(' 8ȶFٱ5ryõ|aϤ|1dDZ0͑N%3IܫzjfZRV=2֫ABrPF\\eGёT>$} 8>=82#Jv#ͺ%'6\_ew)E1'h=Bh(r|"OݴCMaiB:%ZW JIi<՜sھahG$\]O|/_)r}#nxn#p]OǴlXJp0˸w(2~$4M C?6yvQ 򀋳%Վ(ϟrqyOnqzz'n<{X:S8:<&Ϟƌ1:O4ܽ{mOyO~O7op<5fJhErrrBܾ}ځw1Mxg5 C߲٬;:6-RJTGRk7{ΚD%)iaÎz [ow=-R$}zԶ3 IDATalyiʲ ΒܫGZtZppmkq߉a ϣ-u&5REmuZG} _ nHXLܻw/ 1n[nsV|.Vjm F }Ba|z~y{%}$˄T :LHĄxIf&(9lvY %@/!^r7"x1F]o#3O7;*&Pg* XH.(Jh",f^=ei۞k&5A|pg4]T؝ hPXs1B sa E@aD% Ds >3 *g8|KÀƘ>ր NkmGlRp1p|zvg!ώ)]ٞ-: ^\I>{aNj_7͆Ţ26cF.a6)ƞT щ{Alh;1&{ka0k7~HC\\;vۖ߾a'&|E Li8:&UB^`1Γ)HAuB F`:.Dk8$I;?uOviDfNy{dynqBxg)>>xM{|Fco.GI3ngի `)'y>#sT$\3%͢:OY Iq8 tRC0(ާ,U]#YG9GG'ÖrE1=@ŔÃ;O>0)Jlbi U`>DfBJzM?yEVtݎ$_c?/E]ceS|׿d4-֎LʄԻgW|W.Tto} 4UxkJaGRvܹ7Ȥe:-Pڱ]_|Yܹs˗/y=>1_9JA;chfK횼YBt$#dl\,H3A*!KBN$*sb͈%7kz5^y I QBP$9.8,$*aR&Hb?xE{Epl;jJ"0}40;S89-ye^kjk!Ō_? r_Qѯ?5AQz)u+lLSb߳i@BDU%A#Z{ B\^IJRJjRYMJD a떲,A*VAxf> |@xY! 7Qv!T^!HI EU..C)x ݎͶ% &Sv۞ďǍ/F$!S޳vuk<^i;IwTb2)M O(iyLJ}[7+xw|ńe)[Z!4H3bb{,-Q2T)Z#Dsg#MH%\ ]i\Ե C)2ոDxMFcAu.a3T`<4dힷ$ILxp6,MXB1e^&9yIw%%9 X-LYGN_լ=,3u v hpɌ>5"ޛ3KiZ}:Sĉ)##;^Z60Hh !`f`]meTuݪS"~|Wb5Z)08}ȫ)A'v44Md^ <˰'8ɓH<֌xC4i >놪fIV2_\~j̈JtN_ہރq*NA$eI5+ɲ t]ySO#i&dy#ƎL=kJ8D~zK F7MbXOщ<TՒR(8%YvZ$89jH3MH֘aZ .>'0"e c"-N8: f8p2ڔD[)& h麎*99}COLF)e8x414!)OgErvD}} r=4^Qui+8xXQJ_PN\]s8ږX,fHBr9_ULTeY[8 `##Sh8⁀: }T(uL58O5eY5WWWt">̪$U!(,CP yҵT4f_y|0#-KlwXHN4>x )O!HUEl0S` , vE:|yN4ĭ*z:[ VE$4tz.?T_5cɂLJ=Ҭwspzd2*zϯY,W=ْ%4qh3z;~dDH9jNyX}MWy>y"eAPJ1=ZkR0IEBĪu ޒ)%SkX<;g&%:coրwX\\f W ^,#8+EQ\m/9Ӟ$YpvyT\|C*/W nd^1gg@cf>䩋ӥoq"]-:i5Kw&+Sʄ0j %+*׿R\J)ʚf5_} ?Ofq~9?EG>[?Z%ieV,gs<,KVyzZp.X[N1h83ZDN8ZP5g3eK'2[diӖٙBnc|NJ|@5TyjIٳgiDR Mr$P I)ggKVgs8XhMwp<2M1>PL^?4t͑4_5gRʓO2\^\swu)])Ñy:&yY?tT ʼ$Osnoh!Yqj9 yI*i SG7yJ3 ̫De>Fq D1y1jm%lm'\Kʲ#|{,C:{OdiA݆$I~ 񗿊r/:dеlP*ڣgpw{H$=?C'&ӊj8blj"E~r~ <}d86y_q{OU'y7/XLÀL7̪:U.Yf4yTS:OФ"lA]vO eQXwYUꂧ#~ 2C)E^Qf%$iN^h` 8iwܯAjҴ48_( ( ,/HSf9N,!Ꙧ*re]g.3~cg+-#d#>09;{j0M|+RyTK9DCn"xQDNU"),)(X-/(9-_XgMfğOay#+$bF .R֕PdH8hGqB(4yd2'%|%WX]G<% *(ʄ`F|a|H1ʟuA EFjkc=NXD#E|ZBB) E`D$J"dF hƖ 3)&(#7jSJT+c8ؔ:U>X:R#\-M1|pI ,0Gl i0UNX`&ي"%I+fZ}?i~K8UY $ ;3,O*lA "Vc71,xFdC]VZ<=麎lI"=knDȃZZ^BBAh6EF#+|sY.8?Kn7Ir&5Reh H(&/x8Oq8`lPBpq1/.'v>~pGoL;)li:Dr80c_)'8c 2)1G8h2Ơ2d");jd8q~<(R tFUhQ&qGH%YV)7BUX..Il!Ҥ9@drfA-ɒXm[Os4E+MKkk;N:*Ʈij7xq88?ͩ3ꆮu_aH0SB)#ٌ"ȟ_E.h~1ނa 83P$%0`FOӏ&pl=K.^X΢Gv\<+lYiaf ÀzM+̘/j`Б^&cn}3j9uQR)S01 =BHPJš@X7jcrH&l˻YA =I8vx20ޒ%9Fʬ8?{RqqvvD7?o IQ1wxiɄ`2OO(<:U%)^5>|8 ł op38;(3c~_ß|;1tǘZ_<7| 0egwh=6(i3y {6-ggeLEoƓI|`]`bLb~! opck!E=Wg'o-}vb^NV k..x90@wV0x~w`?1o޼>| x8͇e)?~' ϼoH5-sly4; 2͡E*Zih֑%9CG sxCߎ83i1ua蔀@jngK| [3S>5]G6d(d(R HfTIҊJB#*!K+/4O4}'F(j*[8{-7GsŬLfߣ) T\` EPñJS\(r?ї_<ú@UL"uNY4crt9R;6QH#N8txkQ:G%d$Zf@.#'@/^ȳkwXډ!-D! F =$OC&7J)T%34Ò`lq3^"TA`t9Ϙ솼,>0OLBr:p̪302A ƩEP*E N16+ltϖQy8H)OZ9IzDs\ IDAT̟u>C1S4HFg$ A4v,dxŗ1WW]2'9?Z+YB*,'Tي:<,n-=cqhb:(bu1F MK(ь!.M'7T]`죩J 4+d$r鉾@ߛF vaiݑ~ST|=w}!xSq(b)x#*QBDM8#)Lc8d0ᅤz);uJJXK3r=0DZD.S^efGmH <]DȄoj"9` c0bΞD'*ꏵ`)87GE'dMp#ERJn1Z67q@*CFsƱ= X~}70P V:`( 1 y82&\l- K.j*aƁ[Iw(g<;{w |}H@ EDgsL ,~v;Fs`1B}w I"8w;2"c8(%5elx||DIH% ͈c Y1gXqx-IHxxX~`::q5ŻO¯ HBbΧ~Bȇ;L3 :96 {AOxvөEs\=Itl^)uQl>k$atGuߑ4d W6_B! qDX5̪BfggtpZ N)gW|Ev6)˚Öǻ{ hU㩮m12v#Cu]4?S-Ň6X ?ewCwoȲ %4#!Q YpQqJ0K;' >PqG1U,['k'Ŭ26-r&X<}-C۰/8krbѩ!AgHW9U^P/7 hEIB)6AUuu44(J4}G "ҺInnn@ IU\:CiG`ݚnl !t,n͇;2]Sy׼xK>yS:A? E-Y_#pLbI,a"lwk#GQ&Fi6$iuñ#ITf2GFߐ傢H1t|w3Zui /^2{֛"l{2+I:M#Z7u5NjȾpGnМ/.fO\?{G_\I.y}9A4ÁGa u(S %Opg:-cPdH5/޲Z\&"n5EJx2Ew٥=ł"Q2''-//H,kڡ /"(q>@K\1^-B@8q>VJdGO90ZGM Ơ$̗12-F.F?:L4 e yt40ñx ѦgBxt&(uY̖d^Wq%<vĄqAFNIYThwsjdZ~O88Z. D,d|lq%d1A"MU7pG |5E^; ݑ!Ma:s9>6DTŒB:fx4I|PS5՜Z=|fWw߱\.w-̪+~ß;W!_}zć_2Y%&q ~3 ͎MGYufg'+s0`@4uU1=OG'I%acbv_o!^8Dob5()74퀐-EUR \0g4zLZc;@3@ ܑnG%y|_4Ywy$+<ݴdž1i`R`1`uylud[btjK!.e&K ]<ƑjEDT)ck)g%}z!ZRmHSMfneM#܇w[1I\hEsquN9ǧ'i.ZPe㡥[t>D-NLPҳٶ(RgbX9>gԑ5v*K~/yoe,dq\^_cg;r8|rpJ ̸\~xdY͘9gs./V82Yݾ(ҘG,+VB꺢(Fsh}E^qssW},H|`rsvk'?Yoơ 1Z=}߰~=/l7h>L$EBY]y ,8"Dًg<>VF/߱wpo!Xw>ruşٯ>=wjV1!.Պ$1~gR_]$KH >`ebǏ|DS-"04-vr)$gsfTi-3.:[`|`'fi$Fv.(oe! hY͡  e`ŁvSobR|%")iKz[;0%yITشx=Y"eJ7M!y5l͎L؍sZ%r?7w7|9Ypvv{vgȶɋZѴ{&5Jڔ6Xv4݀Nrѐ)I*[bu7*Rc:Ñtˋ$x8? Ϯι_e+EoM2K`63MMfy a>Ziv |wc j~O~ dagyE]A7<'+1]J={7'0n0>2UE,OsWBN,NqL3rrqFiƱZkݩ@MM5:U3nh A:,#/S'&di9#!QUV23Di@! QYN0`Vi拜ۻt(kTH*(f -Θ|is@ʜݑ2UHgdɂTV6snn7ةEg)yC 7z3羅7mۣ8E#w7,VRwyauiTXJ_c0_80 njf }9CQ.~65|Ǜ}ԸΪ4Q|w֍'+&{ځEiNpzoKʙF8430:Ѥdٍ̫]aQYxhwXO?Dޓhe y -AJO6LvD+A4mۣ&j"Ku'DJqEJhq:AX.2 y8uAle0TEͳmvD+V+bV]8;TOi xgS;b^"o+~_RV u`#}?^0Ysnk"s麉푀d@ !dNWdUӖ^DI)I>1Mo?_,/_Ϟ]?misC 4{]GWWdnf1M#fC?v`yyufd RLv~f8H/*ьTGg%U5#Kk:q$Mrٌ"YwLɌT$V0}0`ٌa>w =N(39YsAYn3= OEKhT"E`!͓gr0ŊO޾gf1+~DZcjlE^Z/!o^_? bix~ub1Rmg}š<'K5c4 U#%=~Lᛯ麎ɇW/]?0=P@fVf;7]b(ڮGEpVZ1aXpOx}aP]U+Xò1#Ϙu~h'J"oLl[޿@va_gެ?buq`ْc&GL*rVTa"c&ڮGg)IDɏYhCUQn @]oa͒E/X=,ZF5WfeDdfvbjW&R#dv}' v4AFc7xJS qN 8ne) є׎td՚vCj")I#IA"f<{<]{ F zH!B% bGBI޼}氠64ݒfDpL3A"B* !{LJ׼y < \L)JǾivx4' U-}Ov(!qTf62CB(J(ThF80>>7|0RہCI,9a:,+u@!zwy1qxpl$D/ɒ1)EwYMExUTBS'XBg`rO[*"^jzV3E]|[NjZ@̘M.[FR`rMχ-﫨3..Ω-o߿cmISGZk,!9@5]_C*O&$z?ZDiJkꪥ R ң)PxBQxL>*w$eB!Aꂺq]?0LW8P vPult)+|Dyz?@3M4rovP&7HL }>?!ogZѬ*1IJ}h jQ"S' ]Ϛ@(GPv#`{ܧ'ںa n9?5ਪ=us4I8E#Nׅxㄌwŋ #$"7d8;v5ݎ#6U--iڹV! %ܻw 햯hKoRٳgd>m[>,Α}?0Xszrv>?}5O9[_?oO9;>^|ҘSNKPBU?ZkЧ~;=Y6RSR:vAKڀG[[Ҵ{vh2% : =/x'rM^)=G J>ZM闀Cp^qzqNp /)uuˡ^↊ qci Q J&G`fLXBXRE ^%BkA&ֵz9T=AлE Q"Z[2/X^#BFY0hi3vۚ2O?1&ŋ`]4L,xi9"Is1LG1J./>#SDܮ6,W /h2`Y1XO S"P(tXQ7[zې&Er(y𷞶:4C "xR>$^{uOKn[RH31G(9!yAXھLJ; J aǀ%(e;6햦cqqG7XP,WBxDSIݴG%Reo 􄓳SN]w%uݒ T@'gdInnw-IRw=m30{8/=h1p=2nyCUh}/n Bӟ|IuVw|/h-F[ L-]W 8pSqV}Ӷg_<Ϟ+لLnlwkE!ʒ< PDŽ3i`~4z2Vf[AzfRB$8>JOwhǔ{(!5u9h)77-O\quuŇ"iT2?=az%}EK/~At@ENmX]2 챎@) 7h t`D|gAxbɮk}87H W{J03Qߢzvj=݂!td qVv{OF 4]:%qbO}c=wů?_!վ7hipJGsp$k受V^)HQ-\! CE@o8]\ΐCCU#$)}%}OO>{CEB^Cu( "x-u]ʄ1޽XAv Cܠc H\'O@ɋ V"K[Pz` cE4ji#ä1/)zܱA a4Θ^-ݡfw!aq膀u(/mM[`TMmxm2\8[j2'Sd -y!_P7{@3t#0بB0M\{H^-ѐ%q -UC*ġbk='i6;`2:{a4ᆎ_}2/hnLG\{a_5Jz{Sw`nXmxIQTT}xo9O&߰ZErcLֆKXolz*}X_~œ'Oh#bT=gKV(h:O(d;2Z۲ .V\8]4ȵq>bIWwNֱ'hAbb1,B9<=^x(n:,i r`_/{O7 %aE0aRv~"kH!i1\^=iV튦Q5+k4ɹzvd>쐺M{6%c>3j u/R65Ʉ{޼}IQ웞)|՚ҷ|O?Pxkr_9߿ _s=O>{)˯YlHL 4@PY1S#IXm 0}@`JHȲdG^ռ_.C$8 K9?}ʋ~:(C4mO?X=Y]M}1)Qz.DD"~sDhB.m8VMTlnR$IE")'IDw~%t2w od)&En]\|%m;2?+'وI* gp̺ؓë194 򋯘OOheT4KK<PaLP8,#hk}Tanb2Zrg,{]/y7 պopy!~`ї<~jE_7 B1A؂ӳ){wzMYhIZ$jhTC|_)QmXZ;$ H㘟NvXm vzd#>K ϟBxWϾ'ƁUmvJMG_f85m;mLex\g%e^}& /b+7e-}mՆQ9 MOw9:5݊jJL*\w ww>Ϟ=c} =I֖BhyXӻ:|q? BQ`2*I3eqI홌Seni81йնO|BZ4/o{yb)~,]I IycCVt~h 2q넲>(Eyb`JFkcRjr%5}ǡ _i+ѱs]ۓ&Gkܘw >$DhRN3\VK'=Iq!N2Ac8JBP%r-C}Ͱ߱n^(2dafRsFbaq6ƴaTkG[oBYoV ڮBk/A4"*כ%}} -a݀%5 @))=4k+ tFK% ]j-޽7d"Ȗfp.8;Q`E HI՘g_ի7a[ߒd*h"aZd٘9䀣g<%'|a_\)\ `$$Il><*P$okTQ)b6zfb-GxЉb5-[u΁Z zmIB:nn=^v@d:tĽsnnnڎrhsQ^X.cp=b:E(FtIoiJ"$MSi:'SίN|͇\߼mQMC? ܭE-:^1Ox˂oHգf؀ 0߳=4 #ڶo% NFc~'quq%Ֆ͞L6ehv\x JHFemϟs^3nnvdG}hMLo8lk @8:_L篗(%]mi[GQiJ<|t68ۗjO{\dI~, A34) i+(@~`5RYH"z;eJb"_NJ\L o5C& Sl2bһbDžFG<|~6^nc'h%h^|ϫohڊS6 ա)Qj4{Dq-%X^\`1)S&AMߴgTC8K9;or4}N2":!aH81隖Y/8lL!6udC,F FEeǴ)2V腐El>ɓ7K2'͆hV {.ny2_qwUbjVg6-}g942?/\/8wHw]3iQJ9xpfI9Θ̎ȅ2Fl5 R 򗿈6Դ`27s~9o!h&%KX\B%m jnyCqӺa,U]:+dYP- T(-KѺiIsc LO8wūWoAr釘tRƣWkCtVݖ }{߳?e2t,Uk"s]Fi@sMa򎾋b%yӶ-yrz:Gkv Awb9uv'ٜrT%v ڮbXۮq:`ҰZ\LX,,7 1yKJ9Kb} Cgӌ0GpZ("!O$i35 ]ïeEeq$xϨ,_#?n CXRq)fZNs$gYߑuoICe,)@9m[aOf Y^gwwwlKD`DVa@pvG6lȳMNOJ]{,-m;-h4ä=mLR]2?xoxkFc$ ֱuo,!տů0F rQf%)c-X>2YN MDŽ"dN&G7X~g߰Tơ8~=$bb7R8VZ=Ǻ9-Z@s1)# j.I8<^pz2o} / 2D*@747)A(3mȲQ:C'4vv!~w]ӯx֛oɃϹH?qٔ$$Yn!UNw<Oݴmhp:/}ǡZl!r`O"Xaqq~O~|+nnWO.m]Ef,)*F:4%Q ucmW 5iR&%?dDhc@;BФFZPǔ%>P1>]ϻG6K?`.OrN')ndzL iKkV;޽spjyFM=&xbt§O>(LDMfuKg{ UՂWؾdNOew'oKwfא%19B^0ay-3g {<=N1eLYU"YU , !W<~ nUY9DFtgdSI5/f{ַyP*# d:#K Hm }M'b4[v3pأYcgsf%wV;LuQP#=YV&%߾"\~Q$q]$xm~g1}/-I1I1w ֠6H١rI& IdEB^dFiد;,e\?͛7q@o 89;E15$qd0K>}Ƥp^%#edGyN@`C`-'E7-E<6΃ qqW[d&Xޛ/9_5o߼`:S4h!DC;z*hfBã| M!e@:MwU#f,?hqA_˧7!%i޾@|Mo`[.4ȨJP-IVIL=Y~Cg:1;#Ey> “Q h$ndBsxK4->p|Ҥo?BpDg4TwW$dq 7ߠóZn8TC:c\beDT" cw~P}d(DTP{hф2Ng:SUm_ wsthJc5 4ux:e2ӵe3A˿dEJk.4SGo.V,KNNI4鳇xx=7ߐ٘v{>LQIA$QU7x1͙|u *+(霒mBtxr5R \pCsqv~Lo*ڶ[sv:64m`C:w4 8s{w%yG?؟lnۖ$UbgW[00c:w}T&'d$D mGQ*T`2H>Ⴧܿ*W.ON]fڿb(y9B%vwf0aq|L8Y3.A\α6hHeE(|F I2:x3z&hfv $ Y{f6E<\{1D#rS<| f38'5[BpE'IF"Fh l?PK>~?Ϲ]qv իLW9y9[H xXY.R"V= uHt558aO ^5y^dBlRߢU Q,Gq{{o>m]H9y9fhjx28l1O?} Z;FdhZC泒7rs;G|Çh:_|{=YkvDu-ζO#hJYPUU!HqD!c"'8Ҷ51옶myYZ$zuαYG+"!ɢ9-F 3.7lLYM4 CLz!Žooe8pt)Ruiv$*w;idI9o(!ia:9a2T-.QjlzL&$A)=u}Љ(QUE$YB@c9JTDk{z{uD8a&n}Y-^h`2|xuaA: o/c4Mq+2(Yw;|FY6v TD&hrsujh>Ov ݆78oX.S 3g'y<YF4U|PA}J4g6ʐ*`݀T)&=r6,,E@۷ |✡{IuvXsR,瑳Zn:ȋ1U$B(PqI,-od[Ռ(q{v-G2-Mӱo]8wgTNhj u%M"Ty|ʃ-p~~JQ칹^1.r<"so{dVbMO iSzPcjw^kz|L')>`6pqqYvhV=߽:Ɉ%qq x8ZbwyLZI)V'l[Ѩ'Lf2*%g(12_ܑ?, !4uI).x$"AiGJ%/5@:Kd_|rLqyCݭpf̼,MP"w>v펦c\dܭnT75Q9Hh v$:e<11yk!|Sb"#6"0M)w Rاn`~`pyufhi=77WՎ } +zVe!"Kmn=zҚ|L$~_qׯ_3PRf:QJaiznV^Ht yZvjFH Jz\'Kj|lrFl08I9>=&/Nqwқk!,ֶdb.mOP +uN4NƔ\`jy|v[ԱKJ\;$66*PfcZm*4%M5qIT.5p}?~xq}}JeFsϟݴ]ß }{|M}ߑ9u}yjC/㌣)~{d"cmPuQ- <cl5chs-M&MyIV38v̀ww+^yJ5@ kapwׯ^bc<3O8(5RMbX^jEVEIXQ8ް0 ю# r'QN& xXꎺh KBtăO'??%+c%ݖkQ k8*&qZq/c 8Go,dHkM單6yRl1.Xni%5};[ߟszrD)ھk~[c?xl;.' xr)6 IDATQ'GF6ւPR,s\Nhۊz_SW5O?~McG0Xv`=yB1xg 6=֌ʒ"/HӄjE5YFQF5wm;'KR..S~=$[F @7;pzzfJ)Uж5֬nN&̧ (U쪚ۻ4% i"̽v%膁"Ɯ?'gKfY3*k1û: ADB߻ȒRx)޾EL AKߤ'و^esp~ _Og.񏉜Od_6nƍ68zo̓ u%87@z!b?#x/x:6{Nd\ ae'lww\]~-Bug:ZD4kEDS=y:HTpR}Λ8H =rs{:jml΢h xz+Y.8p`B5ƐHr<$dn;^3tdix2Spr{{r,Kھc()ctD8t sŘuy<4edisA{] |%QEal !&͂r-bU!<1bAE tXšSJ%!bXA&GCş7gW%Yhټ!VyQ,=d@oI!C@ȇJ#`?&8>@Ѷ$:ɳQyZ;CczOp4}xŋ2S,CL0`RkMo D4'"#+(ĄӈɌ۫Vw5"[f}vX2)lI-SZg5Icyu$IۘO>4h^PUW7k0 8c#-47PPfI{8XLgy56sH,lI1SRfg$ ox믈 OP)RW֯EinFJfut:h~B]ܭn8ߧgO9=}"'OgރG4M÷oaŸx u΃[״MA>xO[|CӽA 9_|9%de<-9=]k.//FcƓuSuA1*q9e[FQf4hYbѷv;y)ww7ooP8c9;{&Wo7[kVq.*˜,6ڊ)I6n<-LGQTx H@7b]@;NCUԾ4eA9-@  a7 F0WDRrfqtlk0 2Ht* !nUi ggc)*@Iph$ǘʓb:C$+p~L'x `툩ơ!'1jO]b X7R Cu=,=ϫo/n 7;-F)$1}zl"g*A@"U߲bTEs#/AHMp6MdɈ"|`zPJy] f&@`x(7=>Llb;#]Ojt-书Y/$(AH8X,FYR7 ISX{0g)epRcDvQ3MخZ]9I"Q:EMT'h=vCYC( 8vW#&M#%8gU{xl6n}Gݵn o#$9~\q}jz[OӴuK7tgG zX xT)ev}u 8Db#C" `S4d@x|`4!ȠiTM8(c._S9}o(#%ˣ߽Ң H1i& uI8"SFEδ8Ʌ$4me:$uV>i^ĺ>j+,`lw=*t~l6CKI;֫4  t$wwx+~૯n10SU;֤YI],OOh4 c04$iR}⬢,fd8hpAGsb cZTrs!DS7tD[BHGhH5?d<K_(Ce{<|pBes} Qxyq A?#17w_ E⨶Ƨg? niTg6m߰^]c;L3~˗_\4؆Ͷ ]L9Z+iUoI%MoyDGS =M[ioLy}d`J%]I,Ǥ*>]l-5$ CV{)''gtM Rrug`:Kͺ߶X9X4d6?Xqon$JB"oDBR#%/=B$$IxY&Y6Vyt;$ʺ#O 6GQfu4 6@Ql;~xkKC-]Ʉ4M)TƧ~ۗŋk{]2ɳ1> CYBZp7kvi;"Rӂ| 5 g|gׄC/h: ͦ!{Rc~W_iznWoX,$eJ߷Xiyo:coS==5g ƹ>yz}G]0]v0v?ֱ\]t-m7P5;X`e9`>2Hwk^^e4ZThQ f[F{ݎڑ`23vaC@ ` D$D.NR9j.Ȓ2 JO;("m,W7/)˒{眜wqw,Y:&MKB˷ܭjQ)T|9r#J9Yy4|K\_e23.ƌ&c Ei;l?r:"-QefmUJI햮m.`zKia~n}A;xJ>bq\0t5gK Xoa4H35 ]Co$qqqs4lJ4mӷ+FEd4f߳60LQ!_wXyߧEY3޽7L&NNSq@im)'K 䣒t6#+@%cE3ߐ& $t@h(whg= h2>(>Bq2[6bC ۷ef2t]78F$I'H9i9VnУ)P55qqB+Ec87h-vM1Q87oAQu@Ʉ9,/ěh26DkP$:CS#=Q,|<]σp01Id"x;,J<#O !ڭ~ 6nxl4å[VJ:m(PAdo&M3F)ʃЁ%t֟&6 "'r$#p)d$M&mCUm=#Q)Dhmld(!I֮f1ƘLɤpH I) @:?܆ &HL&9}PUMU?:.nrtZTa6%չ;;2w'Iv(g;HRx4%-rn0W2xP= ^aOEhal2%s\Q};`bqc/!/ / fu=YxT!D֓-%QRD4hIfB\K,K}0]DP$IdLDwQ YzpH6 $7eDd $2ϩ` tM@w "F)5ZAnAJn#21h7Yx\bL0N ^3 b4f_5h2fh:>5dp=նFȨeZ"T2BAZSVf%'3#$"P777+מ7o0-xZӨL$߽oG)RN8^}x.$`])JJZX tu<{GOsrv=׼|O1I"E DJ汮釂(Z3}g[m 6 Dxk :5?_u $o-*9hY^<8m5Y:f<0'OLcokv(J&|j~_RIV^c}zlzwu}\gꎾfOr~z °[W8Sc,<1wW9ZHL oxt<# GSb[w)O|>q5CcHn7 ѽ| }mFc'squ+;P))ȞD)Bh^\3r}>5d&zry|pwt1o)a ض9! UU\ (Y, arfKX]^) eU "4 l  l_~]eĆQX["WoI⛛l*J[2_X &^zEuT:M~~m@46[b`*TXCɔZDHe3L~6D2f!L$l0; h&<K%[fy19u:¬9:.ǖa|^Y-h`p7F #f匇G}b*J'O~¿N ?BTt%yb ,flM#QP D rG郵JYJÐ~̜ cT>Ԏ)arBִmKa*:(RPx稫"@Iegi0%y*kpQ%"+a"D_# :btmG RPRR%˚(0ԙCBpPCar2gG*ھlRJDDQZy+>bZTel9yl7-8qFPѡ_^psEɆ/2u!XE8 VeGU*)M MH\/($Z0RPt^g]J|{G;1$9'GǼ{0ɜj[ (tDiQ&43]}'?|qv^?k-#atHZ!2C6 b#(ݮ%>(u!GXS\9~lUDf(--]/9=l4 c40= ۑO>:mKks$X-EKGGTePLc:d\]Ǽw09?z1]Ƿ߾z?9ql79Ϟ}0 W>q|Nrbal&+U9%9pPJ&UU!c$AVGC:BPh }/fKҖ8 SVeY ] 4j1dђi1jry-_|޿dP2ǎlŢ*0$$H%*|!.x\C8I>D(*?ǔ1Lc`te 4o cOtځu| A6."{|tv~jH!O00#1x͖If\~юhK"e\3Xklix %$~ݹID ,&Z.Wב-3m[n)LD? `Uqwdžmrjv~{ՓKxzd6/w[wÞ}Q \ 8cfXЎϮ9?;fzb9??g6Y.@_߷mvq5匏^>܁n~X9/?G{~~ c;:Zr舾ei5 " l;]u~r9%\.ru`6'=Bd{~8Z1_֜1[ta=[9%baGi\#6;Cm~ L#1|[6v{HMfº.02`l{ժ⣗Oq첁Z6L#X>3ovT5,rPM)iRx gHeA`:t]:@JTH17abrbv0ym@!d̉?Fibt=REN9>;w-|eQfZj1憪[BL8Lln\ɰP!$h0*/SȋR>D|pD0fK(Bd F]a0XSu C7a6K dDqBFP?p sx" HDAp7F2I>tzC@($B 1Z pHD:3r*O ,kR4@?L-1$B =cThnƪa) &q%Ň@%(f =!fxr9n'wF+CWxRbHmKU$51xQ%ZIʥfZ|"{|p$/~ ;%b)QB DV0)b>I)Y M։]Apㄵ}H(F`m7i&PgW\=)I"S=RSs*a"Ō%/G1k?` I )LLvwx?>[!Emi[ 0'12* ,Z >>Pi` K R`yC(֚cąΌn-71=wFg5 8 flEƐCyxU~`,e1 mGDTmK=9*i Ro;@L@wn<(e>g 2Ƭ 1sbd)'2̦" Lߏ9晡>t~b4LY&BWJ&7dOHLq$f/j ;:S h@$I灢L[Y )"ʌ1Gp#JXPya7o_Sڂ"~xC L 24F4!P1Ź [XISCoAdm\S0(7r~ʺ'xGg1 ) {&W< tVKp< l#!N&GrT2vLC]Lc`k )_sN))Ҭ5Rf&42 =Ui q(_%Lf$&b8}Ä1y [FIj>&/WZ0B|6igE&e%Lnh|"H;Ӵf'[9wM#B31TU9!@ak CHm#qL8=ʃi `X-t]0zRЈ$>OIpwU .-}suOQӸȤ}0߃5gK]~ScT7$)*7ŊoҴ$,G眜<(~]c'˟qÞ}ײX99;g8F߼ϖ-47W Izaq((x 747)UWtʿJ_<gOK#M)~V)2@iOk㓋(]RVȚ_c!}@(O@U,/iѡ8՚|vQgT]PV3b= SlSb9cS!J4 ɚ|7~uǟsW$Yalb|E.Vd5].,暢!d`:# 5[mϫ5$G];dа:|x9."M&蚖BG,kx=B#ň5أ`Xp\g\]]bÛ麆uɬ5F׷w\vb5xng:KrFJ7?t]j6G[vȥ?/z~RE% \i $<[SXE]ӟ{#Iеc^X9~R/y|0n+AaƆҁ)*i]pt n>\3 /.x9Zg9 ux|q]0熣Az'6&fTc'$ozGJF7 bQ95e:BtLB`m#e(e:>oZ.𼂶mQVnln2R%O9M8)`ّh}&}K,=[ܦH)/cq#Ϟ䄶m !u Ō37pMYpzb-j l.)*b%Da')#-J0>SWs"d^NI݃N[ߍt݀PcKD31 =ĆY3_?дcKn1hPJcE&XTm ,j0c^H2# ?B;>Kc^'1$ [*3C ѡ~O* h#rg?$^"f*q2K똯y8 1a见j^ga?R%2$+zsf^|K #Ȉ(bhH&ʄ*GFUV%1Gog1_SKಊT>JDB3%O:wv@0MMU/5!JzN]xM>!tyBjH-0$(Y2+ |Hd#!"gB㦉i um(l5"RBJE - >E[b8'_B`^2NdE7D?@$ڨC&cĘH m'j2Xص %GG8ط!y)(*TN'RXMTXͼ,R`"W@Þw csL1mc:nnqG)ւBr!$ idxC1W u@ u=ܑBN\^^]&\H VK^yw f^ K~4_~y3? 5(W{"%_|L/x쳏˿w=^~|Ɋ#7vaV-r%WʓkR#\5ƠEJ!Re6&BL$RfM1f6L0+k, "0M=XT1?3d\@H '㖛u'd\uL<)vDzwOLѵl9!trE3˾kCkͬnDꜦCіU"Dr3\RF&/^Yiy29Bpǚ35mM_}--8% u-!D^\ L}/ZYDtD1P斓|z{/o+~c;pM_wN8=[mr}|vvҒhw{55oqrRqtRST3q iBJ ? t9!< EK "z\RS絤4b5%u`U[qMDC1sCGQZ>OX.V_%9Z%D(!!'ƠÇSKQ.\Y.+ѳn%~G-*x+4VcfVzǻos}O8!cی$y`xͶ ~`ߠ:6-}<ĐgG7Mq4 @1_ٷ;&UsJB8m8>3[\5C߲ !B=gOO~#_b.f%Hi-C#`Q/QaLۇGk3IUJ$^V,sv fvZca&[U<DRT2 =E1cY[06ڡe1 YmpS&UQcTɦڝm R$N e IxHKѡ ~d׷er ǖYt-]qt|1_~kb~ Wwo>Pl1u];qj-d)$#@ ]1D݀g̗=)*ʕ}r:[JALbb*H F1 16Kk(DA`w c#R:bhq5@ I4G~h\yI-R"%'*/l*9ȱ?rUED}/eig0LcHB}[aF/Pk&܅O.WHeyʋghc aO~_Rs޼y_~hVӈsymNPBswgz2cߵL~d}*gێf[tԒΩJIH)$i5{ʊ>$oZuYx'F..cJ{1+*nG2+6S8bD_@~RǸ߄W=Ta5;j>Ѻ@`AY, 2\HBb ؠC0hi݄ƹ\;;=:y`mynr GǜNIIdRߝFf{NNNpc4|r峧S˾1[]DPB\_#H'l6QR঄&Bs\'M B S]4!҄9EBLފ6ìMX. I)\Rf^դ`f|R8U"ĉպE~S!=)"~BYU~p }6)cH>o|ʪbU`5DpSJ1[3ar|Hh+QRQ mK]UiUC1 Vke 9YU3|p"s<R+RH NV+&)"Ȭ"'W˔TuHRh3,,kp@5Œf't}+npo$fNa1N#y8bX#Tqbq2P Hk)B+t(U6'lop(к`^)mi)]}X SEXeHښS=y]b"pB(ʲ» e~A.Gɚ@T<<w49'))m ~Ғ nѪ`u|5|3 %+hUkk2~{Rlv-ct5H57_"UM;"Č ֫3VI#t⧟s>яꫯ IDAT7Fj蜋'i _fs~Y?3Ǽx_^9տE ߾ $1d4( >ڧqćH?/<]dfk0jAث2i TZprr%^z#RVK)'5=X!Ue՚]߲\xǁQc $/J28Sœc O}\srr#Oy? %w-=7߱m9ϟ98櫯~#5,#E#eIp|6$B|l6gVߍ$@ARbG~c~v(C?dHUZSkn !u1QFn:"Ɉf;l`'n2f)ZʲĘiX?r39; O"Т|EQXa Lv Zc 9P0yU].UY&!NHQF3MVgة=C [2MC~| !bkDl b2o^ ɍU%݀@iML!%СM¨U1!tAH m .Mop^Jy=-z6 dIP+l*i)t\9c3#'O֤zƲ^!f!LRgź@paP,Oepo,SВÆx_|w/]d>\a*5#HH(MXs%߿wdgf[b LS+-2dVkRҕD=+3~QxSM;NϏ<|IR3EE86{hQ Gl1[VGs1Zl-@x; UՖH*1F -FG\߼v41 }xGH4\Mg{V'r7 4K<'|p]dA I,ǙUY""dPY?jˊ|It>+ɧe2gXj $,k̕$%ӳcQqss?P%E u=czc!pnʆqdzG,6(|nae,W$l>슳 մ=Ҁ{X~YB V((8~z)Hj뒯5B&fUMM$ -7o}cʻ=~b>;fVfs]ʘ ",y"pwRq (4قO8[Αpt4\QG/IB ޿(FIzB )a}zf{G7%ckm ޾0M=<2!S7o^__=%=0/^~_'xo-ͯ1pt"YKt#xuL=<.\6i 'ʪ$Ɖ 6D EUeԞnh1Z N ~9Nj3ΎO D7!9.C'(LN =[޼ȄY$H106w7o1bu|0'R,cdr]u cV{}§K Ol_o~\iRjM>B~&V è}0`"QӍ0ucso{Su<֜Z#Evr0P 2dϞ䃏l6^}dIi)1D GꮊZ k=k=Ihu‡~5m;RUt-YivS1X膞|}wADŽg]<Ғln#M MMedi-y1~"+aM[ hw$'4www$i<&;˜'Oq[x$+G%%m=c&V1džr1 Qc?S\m*~޾~Eʼ n/89]rq뛷+G iRE۱mJIV׸1RU`Dg cT;xwH^(9U Aqw7 Ďqg<TIO9CnY:l;frd<G׌h7Hiϖ|\| 96=~&B8N$n(3&S5I/xABQ:x|ӵ,O黚4K a #RN6RTR3+}w"&+(QL} y6apa2p `H&1atkkf:GK>^ʆS+ٞbrP30\_ܒg3ڡhƹqHi!Fj|o891_{yw=&%-!8Ԙ4zb\ŭD.MYƖQ*>AdB#EN?T՞i軚2R@9ibDK֋9/_vw'?74j<ϣp5CͻLSɽRiJdFy /0IϬX31N^XPt"Q r9ι%戗iGMJD^a6QW//"GhiPw=^`61uH^J~ovs>#,c۰T5v@cVHMF]7LĬQ)^SB\\Ci0mͻ89kx"h3(1AirPш4#iB3HF㧘 Ux݀u`38L;[E0/#B&Z̐{4ȳ7Nq@z^ #9G??*dF.9JN ,/.rB`W5hP Vۻ;?jL5Bd8⦘-`1_m=e&q|hI4Hx#{Uv[1$S4nL~o{1Le:׫csuyG@ܖy;%=9TB xio1ى GUǍ5IȒ{NKvD#Go'ˮ= Wњ~;Gf2AYfd [CEyzquϣ{gs.o~ةhc=Ƥnas8Qq("M7&t7-$C vD@bue}`rD^^~0F 4-ܩrEQ߳DE$a%= 5mWQwt@V8cҔ˫=/0qrV矑)~o~x|3^=nLJ-N'O膞ayI'駟k+$}_}lQ5tq296D?vlw{%4dͼ4gu@ *v[6=]ײX,Ɔ<|ϻe^ptt8;E DN]UX7lAȘmkF IV̋9 ~ 5|* n؎&h`q{X.ѣ %ls#)Rh)f+df3n4`i/M9 M/Spbg >m؉[ m$S`ZqyVĶ!rI^X, agLfڵ-m}rjͮڳnKߏ8?`lj"0$m"co "xϸmzG8O<}yѷ;c_+o7L>& .:o}!yxo1vyz6㧏xyrs{sM='c-v4DFQLѣe9#7;R-֎aֽ dێnO(LUT%-C7D|w4mGQ 7DACSǿq P(, $AKID8,01>1ܿwL>tm[NV )k6)E>g8TlnJ-h,g''go^7X}KS;aYxk^{'y_LF'jGJYe4$m,4"=&fm$Th{K%MsB=$V-Z0m4z`:apdIn~ov?Ok~W!)@,S;1he4gX1t $ F4v% -adJ.MC4q$UO"0YvV9$H";i`1_3N= h%'H1N=eQ QAz8O㈝FİҔ uԳi R|Pw-.@Uﱶ' F:,;KZ \)_\bAׅxQ- F EO8eJ cA 4ZZBۚwrv1a;/)ɳݖx泜8Eᐪ;FS+>toWka 1a  yj,Q2hY"ivnK7bdBqyOߏ4YRD3{̃GǜߟG4g?[!Ms#l =3>=?#5B4Ih_su}AUU"D-: Ɂ$S(,~'B.5&Q}ܜ7s5{Xk+S8S%%J2r.b*&xznRZ`R!4Db6%4MIR ,MDliu;ѻi1i773yt &(b*h*ٛBIV>F(2&ہUݴtC11M5CIbd61 #w=.30,Ac0BٞDةezlI,N鷧 #}H c7FC2|)Nߵ$j_){?;'H|kuWfmE @]ҶГ%ӳ#>jk 4vr]rT%s&fŌ?,+xofzh" d&Hy=E2Ʊj~ Bz;#Jėsá-Ujb__xF?b %e[&W袑MGyz#Å~{-ø\1-CE7%}|bZm7_q{wѲ30 ڡ-]Ů%+StrU}*PDxZn(),x'!`a1g'H`"P>yDAlfOpq@u#M#t>=BX8|IJD?Z("(#5)J|ݯx|YQ׷[ -A-cm)g2Cz15=W7Tgp^^aKL5cQ,89:$e9dl^pys&fG F{e'"KDHX4]Cl/?m~&320\5[c'ڑݑ҂ zm-Zg$Y'- MӰ #5^8?]sss%E))f9޼HEg_1N4+X̗?]GDƚ >~t4zℳ{p=joAIY@C0N4Ay6#(JK,̲c~L˃P  Nj5@c[DyRsݖv]GO_2ȹw_KѼhhΎ6!$yfP*eN8==gpuuEF.7VsYJx1έl6 ]k1Z&67\\1LjQq~®aXoI\š$ZrMqVK.n hxɳ!xCŒŪd0Y&!a7/RtC'o6! q@c4!hQI',`b)V%|-]o1i4`UUGDZB sc 9!LFx(qA-Cqҵi(E˄ri<-CoJͳ#!>&i)&xN@ФFEKF~u}M@a9$Vx`1[ DȠj6ܤքՖ>CQÞ)nOiz.n|B\]ܠsJ8Y/y!uӢe@fq/d:<(J%(}9ϟQ}m͎fb1_P}KVi3d `p$-VH0,ffMc9; x-_}#;A6h YŃW8"k;~ooz* #MT`O=V+c*6W$MF?!@&hv}e5_ DLB 'B?4\__s! Z LJEb'ǔeNk6Hcz yY2/K$q`u[NNN$1T-m>3NOwË.9[Q jkG\\Dr. r"O!N=@PU\BJ~0&>ƱYJ9ϰ@,8R5@1Z2,IS"OW|<}noM~&5)+~"8Oݷ((%!@zXϘe9kc"%1nl7 &) xIj|6[n" 8jG%Su 2$iTy>cV<~tb+qG1S iZ0,C4sLH匽P7w€qCv; EN$Y`kT@1؆qjPh)r1C Arv~(omwZ]Jp4} ʲW9eѶn<2)p4ڑ%Y`tm>IhAC@ƍsmࡸNzhKڶ%$Q{: &x'sv> 8iZ3:S $i Iu{Rݦ:2帼ziZ1IUYB_Sed0S ld"Z҈fIA,-/ U"<9v]ZX,+nr6k$ER(#I{)@H]rÀypz"M$GG+,cG.Rw2WL.&v &<3*q]x0!D4%%, #ӄitB8 ʒD9|j:n"J;>| 1-X` 'DLSO!R @jR ;O^IӔżdU[1 m[x M} !#`DplrqB,9c\H혯CiMvT[fEB,csw6%Mu/kqBޢ|1݀Z1nEAad6!ĻX쇁I泔/W {ʅCkIEԾU\h<ف48`J2HZk!d7v]!O> ^{UdfH'FVG).Txo^!mP(6 J$4抾nD mR.zx _7 e^Dxba/_| @/48=;ڦhښ/~˾ȋq-Cג "Hk.S,OH@7 23Jhe 4t2 v]YڑWo誚AH\wt6dX]3t},חwDaE@Z(>cFJ &nie$:uۅ8t !V¬L~-C7<}sb3}GO>fRcZJ-wTt, |kC$CШ .؄^(t}=YA?vCGVCEשd]seqrr?&br$U (2{GyN;7GTUC_X ƽR,"֎iAen`* BD\JI f=@4!Evn];ǚNKGbÇY֑ye"Ѡ_ջ} . ŒF ڬ>:1Y&M<yEJ[ih|k}L#7Y iue28./q$*öï%rl^OqαlJsss859di||+(ʜg|KBp!e=,S,ʜ2hʲ9??nnv˔㣄2OXu iA?7ggha@˗_~bQ%_2tDiu5h%޽4噦XR@Ϩ͆',k C je*=II2Dbr !agͯy+<g!1ėa)h'7; ;*syơbYR+S3%c?b_W$Y˗6~(lIT<*%opp@t>e7 mΣihŻ(%YghcrÇyZ*zu|!?ׯ_s{n%XO(vpv2=޽~ÛoY,tJX6H,vb43iVq 01 %Lc[mi iD"Z}M;!,x3nNZuv (ל;E ޒ}onZO(5yf Mfoz֧s,4̀xwQ?>%.^?_qГ"ć P"tM5\\L2نkH# L3N=]{@*L^`h`Ǐ>჏uɋE F!nJ W8MEOdv%bili $[80-nâ8Z-d:NR"yH<|>:R#M1 ;T.2ƿYįuqq{*8*6!A`=JݿO8?{엄` DwϾů#uJ--JϏHD xW<-m4l.H3ٽ#&WXh}"|b)s#R9G't@ɒ cza_qt%ORnn?0е{%Z7/d>/QI二]OU,>|{|/_5݆aƁd>Ͽ`@&OmCfG'x<'IBZeO?çxx|7_cL=<ꘫ[>rtZh'-z>?5MSlT)Sm*c>f($GdIM=ж#KTx,%vr>p=,87d30*!(s޼y]- ǪT<8y8w! ژ7 $zp7ŴD T`LinP,ȶ겡i$IJYL4, AgEbXP\_U4F9Fq w1!lߢ*o. #1 ri)4iJȁmӍ@)$4Dc]z-;g]og[Ȫʬ*.cdh1w[H-` 0/_B v7]ҙYDuavd>RR _}ŻRwx/xXH3ED9Av[1%=JDݯ(HݻKT!#! )$)e1f\ivdI8IlJ ;tgQ( bOxy.T_s$f_^B;f$ID p~ʛ7o~~C>Kֻ% Q,)G)УZ`=Y=h̓'ߣg7wxoZPg<,oi۞jI^ĀSD:($I ^1 TBN߂OCۨ8#%OO$yjM4( 13xai('Oθ v^prv㧬V+:* ΢a1)'skù9Cbɋ$34uvGk滺F dTf^lI$QLgIO8!",yU7\4Anqpp@7TuHu"%Ώ0 uPP:$RR9Qwih{KƮi-]oJkH!]ƣ0ʟ`mΪY> Hw~Yw 4ٮ7=]kZҴ`T0t8*) Rv9hP1Ez×_S髿u*';?\[J$Ngue%‡C` ip Ӏ  mӡ`,I"ƚ< IDAT)$ځGN$XMZݒ̎BTUi^r~zt4ᖪ^azK6dfR6Lu£$I/wG̹{e^%[ŷ+8wL' ]ߢ^clEլ&+#G9fQ&l*o(a2nXlPǺc:4b!!#I4)mG^blKHFUKS5&)8Ag=uzs׷(*l>=E i+MREn?;u@ [vMYqUC¨(+z`p}=Fc0EII),Bx4Q4EJ~ˀ +ghDQ _b WmRyJu1CO{g=fYL!.D7K $IIV C$v:`|I /B%gp֠&RBx Ʊb ϳ ")5I`UB範K7gnW'VIXCV䡒!كaP G|2gL)D6nT\|RqQ0mWT)x\/h_y+1Y@e1%ў|Oc?o 3i OQI6JML \}`DQD #)jm p}DeNhw/Cm"W˞O1<,n8(麊nS#GG,\\  Cm30OyUM hc'sO"WŮnh)ʜoS./~s;vێG~O| C`/y?S#\2c ״ݎ( v7a@#(FsHQ5Th%p˔-hp; փxPR[mk^H^޻!8V:TɬdaaQWϞZX_{Yl !Izآ-g^-y?`Z#x#ub?){d7oޡb6=n>…VHVG!\ؤo CH{8Ӓ 'g̏>jج򎛻;ODYFh"4;xM4fcFg`Z2y]SB8(L%{wIl-~F8?P)Il2[1ՆݶzH"LU?vQ)qR ltz@BQSI 5߫f\f(s%%I"ɲ$sf3a6=ɳy5߿em\Uq$ V,+а{OsqqN鄗`uh(na0(۪9/CtRF9Bft]nt%5i_Bzu-EQ#] >:t]"Nx$ bӵc*d<8g!9"]1ء7{S%*l}\UMd:fTx5ׯf:ɋDi/~9uı& |Ż+npto˸8<<%U1Mղs){O+=>BJmW ,ZÐYVRY=opoq{~Q˩v-i0M8ɫpaAծ5Ɉ1 0M9<T " #I 68)D@guHZIƸ8`4h%8:=߿ŋlȁ Lz~H,';ݺ+OD:a6?A4-y=զ inZFKYLXޭ5/xwG=P39yOKX&0f׃U1jEnqZu=Iٮyp#<,A($Y!dJ]5^͒ח| ֢as)rĨ,ݖ8֤iHO^}Ƴ'|kźayC?t7 A)ɨ e1Fmk| %ɄÃ1i"ɋ9veQJr8?h=({B%Jf1` J;6B2G CԖvHfI&B{:Xsu}IUoh5RöZ)QAĤii 5]b 1P GE]u5dOQ(%]W-гZ=XEt8f^ IhѴMkI^ĬKzmARs{6/h"B0RáCބǘ ,B~w!Q ֑m!aJji+<~󎺩/F$Y3.Ujbo^2(mp ||[(¢(+A8j7h"D+qvxtaH18;PU6uP:kiLP7#k,q 4I8"BsI)x yca^Zݳ^v ީ$T@&HX0CւQdvdzd2cv0nk6 Jj65hzVJ^x>"ui,;zbkvY&IDZ!@"?_Q:lv8g0f`hA!GN7xIt:rC[w W8NB\Zht!D4k(_l(cnnny'M$Rqf?L}}2W =SO<stxbV%ˇzn c?#Nb޾v^wجVn@)jݗ/cpϟ?bqwf_|'9<<掼srzL~yd%= !%(UJI%y,ź6sbȡ w-amثxY,lw;$J] htuΨ<9qZm.ڶ9փBM."X}{ ,M'H)iڎmQNbCJ)6UR$ UKA6Db63M)˂HGTv_{f!00A:UL}]cs8n -V}H±^? ?qq8 P|!qDq(/cz꺡 yCz B2 hw>X7珞r0[@{C6YH$R߱fapWr9yζ}u4ULf#tM{( [Ϯqݵ87#KJҘ=z[E4UOg7iRdyIIBQ>:dh%8̏dyNYDFblϨ8>>DG'xtzBdYJ&yX bCzNWW8]+ 궢j⤥0v)I)EQdxBGhلS8cp|`vבk+2 &9/y1U풶Qk89sp0e:b ]/:B,7V74-e^$<бKf*^fCW:#eUusՎCW/,2 aGgnpuIninalC:[H%Jb -w 傺ڰZ.1&Zվw ÀspU)u۪"gCWHė IJ&CγDqF(a2 t5fOaзBqzP N06]`8R$NRѷ=mݣfuoy5X[sxxc=0(pQPc@08i>رTH6E:aRDD:#)qFP ɔGgGc4CKwbu5톦 U%MP`z"K=S+ ΣF y3Ě- ֡L 7c( (X .Bts(ʂa1ݖ0ocJiܰp3, J#;& } @g 4)%y"Z#GX ۜ%G? 5¡%!V4R$ X+E$y7 th'7Ma4ISI R L|p3c8,nZh`<xVKfSR0JiR1V!e$!Jivc rwOgń4hmFӅp{d1?>7=JYx)/>eV~ݛ_IxYQl6 /5>O>yţc]~[ʉDNVȨc[iswbq+u`eAeHa [Ura@5w`'˲PMstq#Cic"Sf(қ:7=u]!EXx8t)ŷ0N|\@xTZtst|jaR<O ^X%JjW!Q%!Rpp0~qc*kQ;5"M9|khD`+G9Gsn> 2RD{j>O^b27UKHA;3"4c\Nj g  vKc%I0@.|FBvZ(A0dZczf!f :XPt;zۡozDsє}]>ѧ<~vBYvAU[ 7cY.:nn-XޯY,B-3 Ɠ1*xa8=!cGmxXru.x>=?e2%OOLxdDQDID9.8}rFVCd6`>%* 'M ~`Ϧ̏݆ↇv < }O$qL2Nz\~D)so/a|` `{8>_!^avTc-q$8|dalKߵ,; ILuG ,VkuF3\^^\nҜ$IIG4ghɒIh躊XK [BŋO(1/;Ha0?'یFk+!M_MO? wlַ)$CyC9@)!'G'%G8==Dj;{:XowTuX0# P8pvpdqxyŏ~7xW{1g<0i) ! MϞ dC[FeBI(o;!mG)Zf;fSxx ֒8<>|k7t݆[$f<.H0 =}hd.y uD=>= ^}GviUw_p.yQXhFɈnP7=)5Yv"=rfvoY}n xbKR44(b:=Z~Q T 4J4 AoGa3C,ڞ~NٮO$H')∣S6Ւ2 G4UӲYH"I3N&XpaBʰq4Z轝A$7eAT@weR݀`(3j,RH3%o _7xuP5AǎnZbLuu 5 th38[vg5[Zô=ʹG'O0Oh\ WWWTUmwYkR~ @c$m1(Kŷkɋ<9sfC]W$McD)NbфC]r֤Nmj)J("]NK %S~k?ѣs>sXVDJs~~Γ37\}9Gme\NHh6=Ղzc>s4?e4\m(Fs1[~?d뷟s ),alVv?5H%@XSDkKo,quL_K`&4%xDVLG%7 _PE?ٟpc2a,L').zGg,e>ºzd 1XR9Yv&4wGhO|uMU5$Y2t'+60kun!zk{Oʽ}10ڶ$p=M)V @va̱#MRvƓx v@Bt{ۖʯ)J{& )fhnVLf<: o.>0@01f[cmq"d~om=q,Šߋ055 DYozoPtB>|=_}M^H㽥Yқ҂,LG(hň@p`t1` ]FW)B Ek8 z zp%H8-Cv YZ;/F[vmV1qBf9CiPvuFqH= S o/Oh8 5bd|trբqQb O`!jOrf[XoCXoh lMH5 &z& gsh`1Gvh"4C:K"tꪥo:&q3O~}..LWܷ.QfW?DTG)mݦ ՠP-R* MmRAeDq!5 ycXE,$^Z+<vZco mz<$f&XE$2i:lDKh[f eS7;vUU ֆx$H~!榳QJ!cBGI۳h`kJ$T`Ra%:!R%~㳣$I0LMJn<ac\0*`ja&(l{Lrd 1@Ȇ᜵>pTIa!-$IAF$}߇p_rˌDc<CƠPe<:9ŋWy-¡e2+ɢǼ悷!̧s14Kܮ8뱃k+4d̫:޼}_ a J~?Ͽ`WW,K~Eݮ fㄶ;fN.E}LH& dku!0w=EjI+ !,B8"om U <:{d<}~| :X=rBؚmе[ea5,wwrM84\$`ԁ#  vs9v1L@&E$E齪w?uɱ*[߽})YN$׫gՌrnOqч@ !cb)ʒteh"&P NOϙN4VR߱Zmh!%kH5ą5Xs"&%fo/n0Ax8:v[89)xwvnoR˄y]d|}} 'mj J kΏymL˄c&س٬yuuK51wdElA?:ۆrZEfqw|NOx-}~Gä,PEnCg) D˯>  tx9v~7LК/7D1UCS<@躁,-37tYdgqcä<n˟32|$)E9)Rv;..!ͯ8??Gȁ,eDscb #@Z՛MsEUŪJ41x[vkd"_w]HRM&d"MơCt_@"::C֌ 夢,&2Dj$#D p6u#B,K1Nu&@x !ۿKdHك}D%Lwr9+I)CN K VɚnZ4mͨ qAhlv8x>7/duPirMQ.2l"6 d 4XZQTS&Uqy 8 cMՓB og^U5TM!5 E9]DTXf ˣ-ՕMCٹ:REL8yssbf8|Bј%5Z%(GubI׵lw+k-DJI?-tp'MH> [,BȭC/ ig)֍lw{ھCD{l4HbG׈R" C<y1u&"!VEnYmkfkl|IU~PM4mOeH0LѤ !iQHrWŌf=}?2MqVeE+GȲ6?6x.$Q[-RE)ֻ84jZ$yo!cMk"#<|/_R{\__cŔM@UI cFrMσGoю $qF=]+?qszBX,ʵW+w?Mn\!\;vgR'9Z>񣷹/@E-S>}ˋk}єx>@)E[麆4l:JHu ?:ğٟW2M;g~l  zPL$*8z !CP1 ԞfC9TnFgH;ByR[]}JU $>&YFw;#^Īn]{ iE9S6cp1x难5[a[oq~ѷтȲ?ѠUn~}Tʰa$v8HM(0-ɂa:zLor * ,&Sd1PY&6#g,KebM4eՊϿ*be1DNh{8ق∮Ci5޶$Qb8⊘ Z6 ɇ"' 0blL]^5UY,x}̫_oQJqs{ͳ_t09EQd ]OXehl/ﺵC,ū'G|AׁkN)yJ^xɷ߾_7_ۜG ?M/iw c|dM(M8Bc/b|λj5B  #e{MB7S 0E`tyTRN6uG sMh)$iRf''St0%0#tBdm@k2o{@v'JǗ~$1㼍)$Am >;Ą\pP9Jqzv)zGK~d6ay\2[HV쀵#e6J PsE2|=ٹ4ŋ@^(dqDVg"ǠoAa(ȫ3m K!!z8R*7(f~};҂<0n$ƑPqViHG@QFM8oj iAx1&%R,)Y,f<:8k6{<;PwuJQd7M~/oY._RK"V&ӌ]sOp9F7Ɵq1\@匡hۖ F?t;ڡÎaH꺦j =K]MƊ$qh[n%%fcD sY&`}P9ǜ,$6lIY ]g]N@:8 `Vѩb~YcR)^X$2BUxOCdΪ2r|T[oƲX2N)5[VX$I r"iOtqP@?(6v*+~wndVx-YNϱf1=g5]' _ױ e@]>?ًH-b|YV!$Kxyꚶ&e#f%>9{/.̧Te{ơmrxߥd錗^[ g |{f%B,97=:l*g,:BVJJ3=IIЌ=DV"AXH$ O <5iǑ%)4EO{P Z$ufoY(M4niHRd!!a2M:e0| 0J),^?'ahAL?P5SZyTH"zӳT3Ǿm(i\\owb:E3XFygi]k#izHwhā* 9==.Bc#(ZjM^INMxjk?g?9?g|OD IDAT'/vByѡ)⬆Pe Y6;$iYWe#i"db$Y9#ELv#ʁ ("~ūoxx)/_S7}_2,Na,)f۲]Ѥ}|@*G&#T;o.̈GngX]RvK'o

    &|rE[:6c$O<ڑ-'t#<ͨ-]xY2LM ߉Ʉq싯x<9OsO~5AF rB]GW2!p `Oؑ @Q9D挣S%<%$ave:[2Nnoqΐ&޵5^PM',`tʤLH2j/_ߐɜv@ bq`=bƖ A*~>AQ:iF*uܒuJF\3SPSsd",Qk_Ñ1 $F q RgCԎæҋhB*L0mvxHL'l()JSGkߢBÇ|TTN(Q%h}=KHJ$`#*ffVw[!C8>wCHRSnﹽ8>]tF?%CyrDǟQ_gqFAKCss,fhrV" jլ7kGG';ob,Kj&ˣ7ϟSR}OgYFd,wTތ@JIGC4`+2d3%H=ðC tTFy#7 hcK)qP .Z yEߍxPAgIh[ncځ$-]zBL"cFG*~BE0xIB4`4 G?45T"-pr")~֥W0oϭv"/hrPm>#Bۜ7_G'F13M%t:ع`aj6-=YNM~sϮ9 nZq+c'>)˂bbqy$ ricHެ:I?nn/)%߾͚<%y2!r\c;fEJu!kOǃR$L:M@:*m[Xbȵ2$uҍ{R7 ]B0+M31c@(f[|އR 6]xP"ώ׷\|:qH8pwwOY8^33_We>_bF8>y#4=/^dź"SL&K$eـ/~b`ٰo:ûO ߼f'ÝG!4rʛoKUUlOokjO=y7_s{ox^;>fm9XecU@x upH}IdF낓ق3&y19NJ-BZ*[ڮ%U";b;C$3n!S<4[`d׀"PsQ7у9^T/IRI G # Go0=]5h+D0~-xvȸIb}7X%7#T^ue4xabRSHx[,2qgt{VIֻ-%I #.(d $h G0d! U[Zkv9(S={A8G躆̏3N˯ӟ?.q|{|>9v_3C<ـu&:o3@JqX1D )RL`}h<8eiscGM?֛@^MijOXv8,׊X'/x_G>xx͏ZrGߡbl-I*'ܾdɲHUP~ I%(fK@䌽A:,!'Q)e@F9G!= uvjl6E)dZO{$?M)yJ`6#4egh;LoH,V{XUr{}0t8ۣO8?[pwsA_o)rx),톺Ň"'Mb]1qI?8:f>q|2wr{w_]\}S#<~!!D*egn{6y,34=b:wo!:&8kSHy)q:M(IU^`1<<{n0#IQINv{p΢c^ӏe6_}z(J锣#NOOI *A4*՝ %xUlK<}QTHRltI;ÆiܲZs|$-*jDnv)*s5iHT F,a!I]*.(}w AGˋK> ?O2A7`ߴ & ٌLm4nAd\ 2x"qs%%%{ISdFH"CשDhfGBht&uEZ6[F#HdG%Z* :0#A;s6s$XJ A[]H$;)'iЉĚ@?:F,/=A:FdqJDTe{14KJBUJVx $'xH8И8$ZgqPԍP}$BL&gh9kCodz?")<1nU>*1~-35>Vx#ɲu I`QeQ}n,0Tg9X Y-:L'9m#3 .r&m6\d 3liBi=^@YM D%y?Fs$˙'XSGXpc8w=m4J㣖>,))C߲MaGt& K$zvf"RVvщ ,EN6$/q%F*IhUD+$GAGdC$@QD֕ns BAe0cՍ,I72SR㒄@h:PM$y/6A MexQR-5b46GP+Ae0=8jװ]o9\s|tьV.t"i;+ޕdE0  Q{HMˌ|84zuf[1!Xh`դ:17#~k{rkiPhL4I]9`iY'%JHu;:泯~u:J!dO@cL1jR > LA;Y,q졩GfvϦb0 dxT>rִMChT *C O#;ɺS5=c9O8kTy̳kWl;D~؏$:D 0(qGvp$E:x7=Eb-yQN @ 2Vʜnt&4M_[Osv ?u \o8=qzt}J][#Gk"я KhC6v4jA?hFAe,ՄSYoVt{KhqC4Qזz^)tPԛal[!rʚ1gF2q8xFْw}ϸ|ϴ:ݱ]K$H}\8 tӼjA"1ЏeWl4 k qqdr-waqiI=:KL*(t( c7l7\bsF8OB MUDu{Ϩ\_]P)Z9x&&vegf7yiŤb9,.C\N#…@_d'3O>5Oܯn@*&eA(4)w{z hc>>B$GG'&HE[G*Wǎk/^' eUz֛[0Ep6AAXRpu"& R`W9r„-V;65:qQ4$c,9?~d2@5)nvX)5JJ*ŎCpO_π))yֻ -TdEӓ#^zCMR<8cclnb=AhFͺe2LFh/<X F>]8$G :taȲ(nČ=nH_> £4Z/C@lx'6 AhITDD+}!sj,i큙=(Bn9~oP!H ]c9<3cx|`rE <m@'/DLA!W#2<)֌Hw$! tVmG*h+sSL'7 v Ih{!]d sZ/)L ƶ1E'9uXmz~2z.PCi/ȁμ6F`JiLoaȃm.qGk^9G PN*))Ji- Uw]bv`5 y4]0 vu4R =YX|i6T)8d22RZPG{6cqEQ2yP ~>K?y@UxOM͗diN%[k !]DI cM~,K<|'==2323**khn!HlA!!v-@l`@Bl@,$DP55ЕUYY9DzG`aM,^i5j{9~!LC;"9 FSd)3Yvh8I3#0  (dba1ka⻟9''7nwuB*n;ٷVw5/_ + @3<$I| rYsuz#eHA'̦Պoc3fb%M5&441,͚4;vsݒfKQ*l i fq~o{NS_~*l 4\]aؖf`W7CDzfޒ(O .]sw%S ZKjN< m~MH54Mxxq|rNUUNxWP)o|}mJ#H09X4ud2EN3ھCk*v﹌LpT`r寮_ ЊG@IZ>țJM?lVkjI9cnkR1jXIs̪'w9Pp}u/-Yd$Ut#cPJ|ISkfwwX4[ġ4snMx7L8<>qS0dw$BF IDAT^UYVxe9]~s|={`R/Nr/!Bʣ(O*0tq":٧@x QGgMrqj"{0%˧ԭ؀^Ƌ iȫ'KudiB~\ ʓu-c*ℶ2 tdza'K`*ŎNTB"]ƑDGpu&)`@y< /٬DR }CC=,a2_wgc`!$ ęRYZ!E5Iu ݡUHJGjHP ߢJ/˯9n%kVW(2gz.B}6dy5A`z7no9 )1Mb&r~;XںRdH)d5.!<ƏQ:,!Xpv4) !a8?;%LXUJ0XC]פF'q[:(˒o=@1%OҘtԠ<0٤dGƮzކSiyP [Z%Rf|2!-"x[tz8(}yQ7Ӭ QմT+!*d y܀=,~'"=(Ev.>7 w&~ v#es~JFp 2GZ-Tv|NXMSA%i>ѣx%p^қk9iyȇ>]a }Bm7jR )5Y;2UPUS$ IӶLTXcu{E[Jy+&%Fc|a[@&)<:wo^clЬj.) ϟGru/4Zä*~ݖb-]%/oo|o𿲼_E`xYRMG*@x웜,1,ɳ899\\oYmɳ$CYb6.;Ys0 HBe{D EQi҄ l0vRYu{,ۡKwb6z05Yc{CQy4u(*11hcv\|z#M8-(iQt R{0Q i)5}pΣx|1$$J|06ְH#IPj_$i0J3ֻʹX);<"p`~ J%hS3V rb`R-1ksȐ,CNRS;Q>`/yTyFOIꚾA 5Tg, Rspnq4{̿w/}/^~I]w\^ސE a} Y.uJ0( }❧vt(|u EL= J8I0s)WO|}akd1?k=ϟk-z-*Hj)Gn^,Q$-rN<~]s|rdV94-R='y__0_%r-BJ& 96jX>_q IvM,)˜c+?; øH'EѼdRhP8?p|rah{J%͆? Qz˦ ! $onUW3ʲ2*,*v.:#G2˧圛{~_1;!pp8#+*1LX]q m*BHt8o=tʮu.E4!h]*Y.9yv {hbz4J%q4vǤ*&SqD RkEvd1  ]"r&zɢ:Rf82mn$c?0=u=rt4g~pNӸPTɤd$"9=Bop$I\0afNxE1JE;s )PM4Ō>wo9>?ӟO>ayC߮!SGጤ;Su 8Fn&RX=Y HB1,H~p o x"OZ84`ZD٠E@%b ~)c 8PBz{&Ao1کU "Udt]R (  2YzN_d<~0Řzz~wP qK65ٌ4+Iga1G?SV; ~ Mp,y^r|t7AGY J i.B"cR.m(9g1i^3AssrD:8\{}d**gR: !rOЇXvuo@(_"Egh;RL)z8Z7Fȫu-axQ!U `;!3ҁR1) Cg+.cNG]8xHBc1Wk`$ 횶_jJQDP^$H"PkɊW EE;{g{#`8x DIV,ʵJP*'"'%.p=dn M8;;fQe%F1ґK5؅L4GJ3!Y>aD<~t@$ݎX3.AC_? 2!$!'Glv{ֆ4FuJZrJ]ouiA'dA I{Ǝ u|o^D]yzt.f(U@J,c/>{7~O>}ඨDK?DۋV ƚ} #C%(I"xpL jM\ diE[Jjhd\BMf3q!ж-B(r3fhm6ѰYXs|<ϱ&ZRTyr!TL]!0*ynaENZx36{*ocE?h~wG|g1䈉 f )*Xvm!f]essur;ơ'K@r< _\,'0gb$Mϟq8a(ҵ ,Âqw1=0yf`-țX̳Qh&Es^ŎYŽ{c֑O'x;rs{EYC14 a!3cjyvF)G8&-6+\x*c/LgSf)z喦)R(ʔ$ӌ KRul6,f8iv;qm[Fo7LgN2*EEY’jvwAxyGVlqr[$]0 4f]}oZEu7쭱5-]DMM7}bf㓇?4 L rq"Ń=:&s^o37^jfna7!#'熾z3jb%͒3Ɛ)}?č'&4g7fU6XkȜ RK1N{&Dbvl7дk~zb+.o|јヅL4*qz7kZUqHץ<wO~hG<|xЎެyJ[9n Q%Ck0DUoam`{\DtJMYv8o޾#,YIVJRU֫2e!.`]!uO^Ht"bb CAŌ%CU8I>ƱFX-g~>BFۖJEbL;Ń#m Zi %sr7]CZhm=yȻ[!/{^~ќIYEY.* kط͆4M*,D}@,֢tC:E4vOp%h2D+ZI.l+Ao.^aPJnWl(@%Lʒa7QG1`iu|V3\\f4 }߁2m}5\;J)d5%K t1&BLi L* %1iNULHuOE Li,n*'P*ݱnqpz$]k!4h%RV#Jw|}XI}2\^Ř"I)Jz|8E"0 ݰ$ŸdֵXGX֡i} 5.!Ajr& jBdYEPR3 n/DH臞w?\Ï[TSAdv]teٌnhZh2ŋXiu"`DϮB#dN6=jPIlq-7e,{Jh?FaɓFg-f_{69iWoۆO"pe FÛbr\<8*)SAlIH?8~ψNJVjJ[F"˱1nT\,熃C"ͻ7o v cHM|#~s=˥Xsrzx9cO5)'I&vi.rl1LY\HJ3~4e o7+㽤dyE>)P3$r%X _,& #$aʨ9yxJd3=8OR2y*)/iz:сhqCX ^NE<ø-w˷X1nmۑ0t81PᆽCѵCT*c#n%(cn.WlV;Jc\[KӞA kY_3I2`8i (4M>3Eє6+EoGP,}RoZRȞ` Y+ `FAn _X'""6xP BANEi5(HLNgnJ*4X&|ru{ M+hv{,W_d3 }9 :`W"/$ סOX;2FM(8c@+iiG,JXG!U B3{ i/k{jILGLfh]RU%uE)ҒqKBH0Éȯ(DiT!p!{c4կc2B4 { > Ŋ=#C)E]LS<b?{f?I˻%?|@ߵ+Iq }^-b 6!M$M|pD{`GW$.(ӂdF-xX#۲@/2 i;d!(#[ 4]ᜬH1nT#`ސei#fWJ4c S:ٺi6Lq`Ud7\_񺽠(JI XNl St]30noɲ< ,.@W4Z'}1n?mBR-4Y!vt}|CV[9<<("gy.IӜ٬c#)ybF1%X1!(ֻ-BHt>Ü PQ IDAT Dg{͖uxbځ)w9s`tݮe[q띔$ BA')RfPdF }d@fVC<@ddS1ocSe^azC׎Rdqdt4L2N|c#s80uUUo|‡?7o™!M0#Mݓ9g<8zfg?ۯpxJΙϧ\) )bE>4=MmKXrAM~B(ھpUnb7?\\z ~a$Wi`#)ƱGHӂTiH(ҫxȧL2^mOHY^=kƓUTkF;<y"ͮ?G.-~0eR-躆fG;1VTENm/<.({\"ÚHLo* 3Ddyjr}&ޛ䆣c"+Ӣ"-rr{ൎGbJ1<]q;9Akbcm"G(#Lgq(SIBb}Źi3;X06+FgѩF%cSGnJߏH떃CLwd4ۻ%B7:Ҍ~'?g?nuc\ z_M zO, $I MUv`%MY(9֐لGP-7֚#WޒJb5wW\\"\TK>plq.%˫{ްi"&xӘ5#%'./kB@Uf.JӇ͒>ߛ=C-2SҖXdYJU 5ybj}5y#$I5EHf :MD3UtZ=бqy"vp0ژ/hg;ʲd6ew-xAqV` X$&YX26vdbG>qG"k <#&/%us<ְLȔjC9]!C88n.x?#x'-Ѵ@$cU8 8w+b]P(N$64MJF%UY L$:  gƞD)dFD;TG IExw.ڎvC#~#ZguJ ~:\daYR`ͺ70al335Y9c;RS E.ѴUTٴ`ItNpuYmx`kcǰW^V<$ލ~cSߓsB㠡G6TIhGRq`nPZ!iFpTifGfB#Z8b#r,CX,Edf .D\Vdis8vu;G)l6~"?Hmskc}фݺW-X͋] DG[xþFtHB܎!_`Q *()SnnnGI1F펺QX?Dz"(˄o>@B PxgiTD*$Q:rR͆okE8O7vB"ee/M$dgGdiAWCM8蓖tl8OG߯<+Af2=g&!I#\V__:4Rt Kl;tcE!@"3}ck[9AUgR a23fպw7^FzӲqxF|y전 jZ@Ԁ%s({vk1VSTG%I^﫫BwP^Du"ZKBݪ4%ނCZ<,M@5y8u)<^@]"#ȏoJf"7?棏>bqwwC]en@ GGg>f[s30ưYA΋O9=9cc#I4mWGAPq?$Rθx9/?{CYN:CJܿ{'' ct:e:Q%G˯~Eny)>x?G?8 ,`HÆ{jz;2Dk#(Rݫd"^8JP$YBn1#Xt ;%xoΑr"t7,<Ɋ 3W):lֻjVFȫȄSJjxcQBR[ڮ{ #ߘJ'DBS4M).1㎢~0]a!Y܀~y>_UT!6*`t#`Lf|EQ,W-e#?gI{*`G3ڶ˗V-aY.A@^o}kzsZ)N X3E}rŝ`6]|_pv>n6n_aFH5hS-^ oF[`0h0FշL3IF>  c{5$)VOF' oZ<+RwwwCGJ$I)$cGcq<)GEӶ[ݒq~*0g2U~ILg*i9Yݱ;5XTܾmp>_qyyΓK-Y`m3b $Yf*CȨ <~YןF3gPIO5yszu]S5="4/h=夢 ʼ`>{E5TSï?)*|z y!ad4_~FEJ7YKH1c\+yZQ#ELU|v3`@5%3 @ӵ$*!NY,)ozwB&cL xfCW\݁ݦG*kF6-Ys?ںC,R>x_Gw_r9gLXvtZʓ9S>/C (r8cpX8B}E$,gK1tPCPB1ACYƌ!*фX%"`0+%GT14+5MHxqMKܚif\&SM+>woRŋj}<-p麎C2FbGש`GH^}c, GKLB#&F2l2{f4;lI9yLh{n'E)AEB7;Fې&rRћǗ -3v2%)7j0{dzH҂ӳ%YfM5O%S.9*ti`20 CGDe! }L R #$.XGj@6@,)4JOp6"PMʣK/"e&4N 'iO+n -UȺ?vDCxFk:K.;C1C@XUpAz|vb~BR}}62`G)xL%J$<%c}GR ܢr8''Trq#:$,8[꺎7V4uݐ&)e1e:eLá%xM JG{S\Q*AhV>^DL~im0Q:qPb90JJ":hϱG}9P]#?@VdIJn9Niۆ~d:w?l[bKΐɀ"Ҋ[aG۵l^.H25tu@*_rF,0CK;O) ak_IBGy ]w $$wX!ޡuV_t2)yYVyLl6v<84h-9;\oޠTiR=Lg)ak~[ww7opw[i4)֎ȈE+Ѯ6T<ϐ""䄾>PVq@"fXEWX#I"i` J4ٳZ_ǾHunl)x64Oщ@ɂɳ3F8xHʌOBHb ́c v4!x|$KU۸px-n F G0:kØ,IɲDiR`u=M ^8{so<鄋33\m_8\=tHYE?|;3U}{ͧ_[ݏr=YcޓG/@X̖ Ey''#ޮYuXi}p`h:HSx~woºŅ: (躎w/[t/(,Ҕa7_я=J@JfzXnRRoX_oN'$+X! Cy <5BHf:Yrv޳Z \{M ALYH6_2a\-9WcR9=9PIr1͛&(p[T1O*:\0J tZE&P2#h{Kusq .]̞hG\Qe1wgYy:9aΏva 7/߰\>@뜟|[??Dο˟$O `,uגhŃSĈ5ЇH#1Q-fNHc#{MTc̸cϤQ4 fm;jQ7df:&՚[AZ)}2o0c O5Zg8 JS˶!!~H=!ͣB{+°Y0;H $$0 MK Mw/Ȩ&SE?.0ddGxIPQܶ-M%4o^K4;ֻIL).Ya~KudDbf=.$H+\alînɒWoQ:e6YR%>ėoc]kNRbd#5^!$Lr,24Dx+*"exIi㽧rb: PE%9Nv ,+0ޱ.^D BLBH e2*q`$%X ̪!_g ?_†w>9w3t}RUEy8ZcR e\ibRi58o⿑Ȳ'K ~8-*8nn;[,a:f`:(R 8&mg,~z<e>%Ǝ};cX4MĹ +(%"xX(PGK@g9~'rz;b M!U4/R -JF3[8YRd uvlhXÀ {-m@-x#= a.ڗBpc["pc@#1KXE Σm: 3  RA^ d;ZjT830-adˋǔeL7wovǃKi)3RJA-vXqDVԓ rhZ rH%&<"O4_|_~..a Z9߳nISM1!h=&'< $&TEI5Yʴ CG}G>e~˿?Mw7w2Tꗿo~ gϟW?=W/pݎl)'˫#d`ݒe :4t͞.ĪL/圮BhG ;Jk{XLI;GQL:'O2jFRHݶZ'i l02Cg{Vfnl=YcFT_? %ֻ*T8ڸ3vĻJ3Ty9ya #Mxz@ -Sg7Ç4A+TdgLvX -Mw":rMbN;v֑%`@IOQMb,:D@1L2naL9TohuFO8s2[jZr7oDKƮ""*BJѩiHw,/4lB0B'i혳`1&+MXщbm>PM45T)J2N2/fgǸ1nF(w7[A!! Yl2œxi!FwKEU1z˃Ǐׇ߬;##UP4MCiH$* 0>>&-Rݦ!IUepz>kIG?f~/=CCbU!O 3ZY, ޢ"сG Ҋ<8;_ݯX,Kۑc"\pG;ԔLN!Et6(*1|]#NXv h`GiR58E)R&TO$Zbǁ!iFG L$ \'0@MwӓdIYVT%u>̀u]lhx*rǤ%*ȣQ'r&v8{eJjJZӓi ;Ϩd2tb/H"IR4,/|/dG -ݖ}2- ^Ҵ%jl $0-ib ؃(8yFN&SBp4|Zsn7\߼u!*Ri,Æ$5~뿡[B6OWg\"E~ǣ$EiEQΰRz5{s #,KX^!jir )$!.>@&$S4$@ˋ FiRh YZȒD:nCVHSqSэZ.@G~0ZqD8/01UL* /@HE? H*hr#u8ے2 nX+n B25=UU1[iۚ9uU!" +xѷ-RhH,hc]܌.yv2nWwh-Y;D1=Cs#3 U;NNKu-n_?}IM\߼l}GTl"'/͢c-ɇ-MQ<{vu˯cWQMQL88iLBvU7 kϮ:CÝ~ z _}x94#niGK :qh_ww,a6K9]Nx%L&9~%_9 ?\cBi2+3f *ĥ"9= ]޳'|g4=͆L2v Ϲ_DkA!?nqW\_O96Ihc!ϟ3.h;z`0#{3Ow9w4wo{AGh< GTD0&e#I$Yx%,y% ÀVTYBی=?2E|A!섳3h..bu(*r5M3\.$-Pnni4ei;=~H\=~D9-MH]ͫwYtNgHR#91qж5Mm $*<dz(хȾq$=m`1$`Iu`9,dY]B8ZRm\{O~vtvcldT,yVaFO+ۇo9?<} tdYR dzױo,sӑ4Y".I5pQd% t#"#ldѪ 1 9]e-UL+ls17oȳ)W-AhɂrnDgȼ٭I SlZB STdt}~EHKYe C ZHnޱ:߬ytn Er ]s7؛6l_Ձ$Ɋ1'(z3 3qv{t&JOOGɡ(Yuzl#Xx"$4$)ƌ ֠50Ƅ (PyNqtaqi/"ehz-Uqd)rRE/>yt`^gmoGI%BwxSL&3nnv:)Z3u|A6;Roj oH6|4cr:yjׯa7Lb K`1EݧzT$IE%Q[3T0aAUz|Ёq gp>E/>#{5=JlT/A\EXnw+!@1FzzJ]dicX**7-8ؑpؑn쏀QIGAXoj;ä5| :hbi8YV2/[aF7BTUJ,6$MC $X5S͚iG(/O pΐF2Z XkQң4w;sCfc%TtR0-]9B g1Mh-Vkz} G> &e9ÍI5p8ps~K^˳2xph$. *qΠU&I9GQơ%/~3Xf\ h9]$ %>hdG0 PQkIQQ#ڑ_۷ocG;D4~k~1t8[^qvrN M4w[޾e4q]d8/qV0?qJ F~ sr~óXY ]0O˷{do},>8֫}s}} ={Ⱦc-1v3#mq LS%!X{)e9%ϫȐ3,czZN& :KIMLɒ/ޑ^&5m4鑺TUC<0iZ&9ӪT{$2|.'eve rȊ,D&]WcGxpLK^~"%>eBxkhj!9pwwOOv+p>Ǐ+ۖ/?c6"/J%N]c=I|IэDM1α?R}"B{ LLֻ-oFȞq\#Ԁ0 9ƻV9; zHҜwPo9-pɌ4t]9$I2uS>3Ì c??9{AοOT2B&TZ,>MiEk b͑1pV'm_lvu4čIS̪ί.|_t gO#ɳ C'8MWH_ZJt>'UӸ##_h@% uG}@a_[ w&3v-$Co8ZFmISfR\~sd2FmLT8kǑ,OǞn|YhuSccy2[_bzbYg5}F$m3))C/Eh]_%!w8Pbp#ƌ $њ4OIhVF#o>BNjgiƎqJ=R{'woE(p0:6,K# DɃKv c?ҷ2l%-R6 :MȲseMא 0$$Rx):q>nM,9LRA+lw,%-5R97=l=Y# iYdÀ3Q'>"Ir9=6nTMZj0 |4KR)ámƱ\\r=v/^sz{=!Hqܐ)y6g6h;WO#& 0Ӆb:!/b\T {m32,X'('075p}CJVْy7wk}Mr{3ˋG,fs65: u뗟<=dņFQ=̀xp!i.~$C)tr:vttImI/~x0;Фy,IUh Z'GXq'ixQm[cƘ16ڤH8Q +\t/h-m#TiEX8\*-b2FkIMg][dAUU$m eeYh&% $JP{4Ygz?Y%!3 9\ѸqGjЌ! 1ZK}Ѯ[?a4ĪU7"q}zV |@ /AfqDg<ᜡi4|4Mc6{Aftqߵ !$E_]r{{P35Qc[tuQՖNynj~OY. 9K6[%;OZmßɟ_&|?D~7TuRf֚ɸ 4JFpsB 4tݚٚT x1k5aJ)988@ͺa$GkQ9 ܤ~hZ{͸1]p|'o=C>XoX_\WݴpBw+D˃hv56^\"sj=P2/%G&Sǧ%$lXrZ!D3Y󆀧{v Q"EemUb6s|3)Ǽ*%6q,wmKPNYovxr|mOy!,իWTUG^j\E<cMŸ(yb*iwP\ݡӄ,LFEHHIvwyVZ"{6ˋ[nPaK]Ur2]kLŔׯ^RݖйFj41vo!ۚ{DŽ4˘vP)!|x׀_n3Gfp`z`E8kTIUA{41HPB݊xx~?7KcG4'`vkn.xP2W_񵴎 IDAT!;~o? #S#HWſ.n{*`[EIU;?`0.:cC  *Y,3 a% Vmó/>)Ro-ٜj(k ^b0-  8De4-o?yo~!yԎFݖ{eqR#=z',D϶/Y~f))Ũe> {U,F. =Hձ-Y]RhI9X.eZn 8<Ѧ$E'*91A!D9")I2^e%ybsGQ*Vn}ԕ݀b)hk4n([<'83&Hsw\]ĺ<8aLO6h2B}$e2IIȳ1.<=tl )Uհ*$(FLfSV5WwqXņ0D~MӌhB &:X];DUbod u4UՎdTWrwˁ4HP`:!!gJR$D?=v]ӛfW㕠3Bt3HY!\H 2^ j)lpul\{h(/:єYA]Lt}GT}Prt&:$ZJ)xJIuUi`e2pzv|1G-ګ ,%S\,):C4@v{M}#HT,Kh\IpTd#./ M|()ȳdTTۖ: ^F}7u=mcp|6ECxGLg!4yPI]$Iƽ|oX,ږ]g B QɣGg\^oH`}{G$;}Vwx[r0?w?'\\^G e]452Q?%׷W -Q ^{\cL$$p(2h;mH< J-{ (sFHPIDZ&2_{SZ=ޒ0k@ n .|kJ)dIid߉Xve}?T+ɉR4(|ui16uph<3Z9ĥ VnC{Hj!`%Cr< I` !|L-2VU#/r"# i[6[(CsTk,VHaXOt4]pt2ꊮkz:c:9g|3 B$q6s\9X̨/^=y˻C3 LJ/& _J="s.dlP'P4+zSG=%]`8 rqtZR4xɂr3HIMa#/r,;֫hZvkm5>xƓukK9i@8230nT]!ٽc ,K& }͓ɘ,\/^r}}KwL&c1z`"SZd:a<Ń G Z@H8wN9.@D>L58oJcz׿.YAU1WXcW|J]{|| ?Ym g>+9\Lïs9 8#7Xckźo?y»}@mG4mK}OoL&9o<|jMS,czQV0Q:!@7fx˫W<{'|`w F%TL&)GSL#)Qdd4>tA]~98uW\\?c-Ȁ$H)9hMuh'V/_Ś4Nfg%Y0)JN#ּ^_\>$AX"Ç@|TE%^p{w{%C<,+B3bdSIVw\+pX_Qf{G!!VF)R2M"hjA`R_!#ssOcr_ c@~+$eclg,4gLuu8cL'8<1Lw~J1\߼ɤT3 }y-GGstt|:;福-ǏkKYK]!y1eqyDŊau)7l :1=Nli:]t2?^_' ~KgT?RLg ɢu@YEA307&M2G#83K%*I o-i&2/[G2A*0(M톦L)6x6[r`6K)28 L6LPS{ido#ڿ s@NXw$| Ghqsy=J t"q.>ɽ%~a }nIV48$"+22 :G󥋉`)T$*Vc,!l֌%HAkz14Xtt2k]uD$,GH)6 Ф`߿0X~__ AHʘ`%yӵI4$Mc/;dz_pqqYOA/ʢ@P @KK&B2s4 ]:}F1.b:vXH<ږh|1)j3<+᥄n֚$'T՚nXsj-Պgucx!e Ͽhڊd`j]^2g=eniO] =&)4Mͧ-m3!Ax>­)#ɳIq1\q)Wyuc Ō}n4S .x\pte[NNt䝷v 8ߐѺ;o^06="gC7"kf#R]0TGgg,2Vmb3R\e,Iiۖt]RP h8>8nK^skSqSNXH b6I(?o~Sw5B&\]R(5*mc:.5 g-,#)*IJ Hj;1ޣ6`HbD`H3QJFenװ\Ȩ3thNݭ)Cc)AASWݦc&u$i˛%2y:zcY.L%:).I:IWyN@wkp9pyWk2wA?W7eB 2Gq,+]kIj[pdYxZb]j{s=B%<ヂݦWxH)52ņblϙ@S\H*#td@e9J6=2j^?s/V|lk<0{-b<& 2 @bYm^\_}}V_1]@P=BL]윢<`:%mE^HuTijt=Zњ00]M7ĊK&x*s*.%褠8<|uL`}M9Jh5~NӔLvki$ xc,OѲ@1BeT.NsT RI#憾X>=;&dP%]C jq)6+T@Ɍ|d>U͊;H)6p4}yMIR ^M|4!y)}> CLp|{0s=Ιͦ"um1XKdN)$??{o?ßyADbT?pVxh3*ǔQZ4d~3W8am-"Zz\' AB་(D+2 'd`4E@]ג&pr||7,<} #041$@jMHQ#|8?b2mSQdЀi*'tE'$7nFHjcn IvY1?2J'VHpI`<.<g{ kd$!s(˄*]ե+D> J\3ƣYژnw1^Y979EQ U }?K]O;t"HAFrLz tR<, n!ah3xcu0* >3n/ߛb Bљ$(lWcxM͇f tCǮ%9$T5UOx(SD=kR]XLHӔ%.4H64Mw3[,!^/*3@2~ EyϹ`TĚp{{;LJ } /?W\of<{G$*E&8Gћ?O醆((co xx { )"(J'Hce%"^#ك;?dv|,#MsqREL\fAٲdX1.rv-Uh<ϙxcbH4D$V}ܮJĺq3hfS]7tB/]Xcb,ɋA(Ҭd,EfALL]=gV+'j7wb뽧{ޑe%^lq.4,Z9}2*(sx8`qj}q%b5!QJ|BOh?5mPϧ|<{Α(Ku=3/bub:VD0?8脛W|ɧ|TtmKE\ %Z/$hSN T!( )ݮgR܀C% ;t,+HuIdz"<`TUb~`<.qaM=IEk3˦r4Zް\r4f9}Yݾ`c65|#~??իg;ai#8;f(JM5iØ:+D-9>>F'5HӜDEvb?PY*Ls:3ӧ]!j,5łт57cl C47ж{3Kmcu͛o3Nt|ŏMxLqvvb/@kfJ6-n{ n1ݶ&ȈZ+;F,NXͷ7tz 777 v`>`dT=YaöO?ԕZHج;89ϋS;^z"v  m0*{ezߥ(t}$17 )5䛜E0!"؄OYWiӯo}량_?H'졜rxHdUQ cQ<–\L㴱#GqC$riv@]WԵc<=;AQɈmvJSg2/Y`0bʮ#K'Zvk-'g@ AG$nB`E.%M*b pPDx:=mW"3.2:no1KQj (} TB q,$:!"`:"2 EITYD;o-2!n.bq'ògWQMYSDj,Q2g1lzT-_G~J"H^u Ho*%x/q>[moSfbˌ"dxF£6ڹR'cȘDMyFEv-h$DnZd+ØoVS'd!]7P_V7DdE8+%JYDC[88dv0!D˂ȢXaI='9GcS>~!O<Ms飉J E{DsWS3L#oOYo+ڱm6E:E Du8)x"(ğ- LR/"!H$QQ4F(evdE[<1u=YyI9^ gQ~rє#Q!tr1v[pqsfvpLYLl+f7fV.*jA2 6>C'MG}OݵO M9X-KE'ju!v޴|7Wh NmM0pzz9?ϸx]o['g_,sTV'zwy fz %yXQfZ!R$_~)-f,єeNg"x\a gI!CC 5:LU LA`eW{FVuk1C('U "w:9dTb4E)D& 3/FMH{SG$9v yDd(2"L@ ^_^PW+&2#¾ b0!zT-2 N8: EYL& ٌ ._pqqLf$ڰ=iZ2=*\P4iM8o"psvoX$ӱXL@xEoh & r;1ytYpusGq0;dC~'bzay4MC׵$b1HYbq+:kӬM W[vkSUw͎4/ܗ^Sd%ڒx i$~ ''' g"AKp ߠUv/?Ya]-1CMݮ)r,qw,ׯ#W)Ӄi(MA4cc;=h::ɋ/*nǬ%h3XҼ 9:I;{S3sfYH]z6uEb/< lwkt0izڮG% vwqgzv-}RD6"+r,'̦K%JI}'ucZObuxxӍ5Ê$"/&EFZ h#rp{G0fa}`wڠD+31"w B9*!BI2C`0culV@<mg {9\ӷw}P,BtL+jNSH݌b |7ܻoYVl ?Ͽcj~6;5ymczwz?wlc r>r87ҍcXxэWR/D˱L#}oF*!`H1A2붤ubyAZPU眞\޾:2!=UX4o0)f̦ݖ$-hT3[F#YX,Sz/b`y~A9$Iu]Vz RCkg`]J{pmf8n U('$Q8R`0FL"{ Ju !$lz4Cbl8e쑝!HFT;|Dg)uZҤdHk!E?@|7tN B >bvXz@'۬1TE  ĭ[T,g,qV;5A:t2 kB#KF!ୣ[5n~щG"(*r(IJ5UU $m3Wo0v8"9Awqۺ[泊{(&~!7?gPBaFcSf ~ EBߵHxx>(\xeYkmO~7ٳg[+RR#Ʊnj-"~Vm*޽ 'G?O?1MӮiZ!&F/7L'38cbR :\\^[}/͙ML3Zv_#ETvjuGQ1\L&%*jN23ҍ>8g~vbߒjO'+ں&U;J$;)8]x1)$it}ChDR ;NK YRDNh>5fd=Ϡ4K)k8#;0G>3DNa{'hm3H*@q0P){IF$hio,kÏ?#V[޲" I G4ͻ|'ҏ]^$yNn>.,s>:<w<=jffa:R:]Lu,a"/S$ U1d܈ *} )^sr:cXg4MKX'{Гg|h;V !XlftfCk[<2cHXOoYۑG!x,S>Ʋ8 .-BnnٮoY+šADިHX/YO'h9e,* Pep(%Mwɴ|vȷP7|{s0_^\^k*;rrRH9ڲ^a8޽Ss~WY8?PT dk2w?~'_𣡜&,ܶy7Zp*BɌ ^lt1)[AAHs4.utlw;ڮyPJs}zҬ"uQ b0: .H1QBƔQ1H w[tdnc= B>==) e~p}sh['hHx$"Ht5Cd)F} 6%!MD$6*Ld e0$d}1}Ѹqਲ"$r$ݛ+&U* 3Ɓ,# ZXtʤRl`2d߅s׼eqgGZ6nT秱ʡ-b,:BG5tq1!zG<~8FEǘH2NN1EϮ!Q;Drr>>,>yNe:zkr^I7Z5Ɍ~#0Ǫ"n'IjjlV]q]Ww,w,.X^! O4YTD(&[䢜P7mݢ ;^5"Q uR 9Y8lVX;F9B2>io64ZDxt@'#xw%[,y[-L>Pa0Gю3Bht5øGðR)ŊuMqz6\٭1bTeE()S&VBy-:Ӝ]"Dz#KP*BT=B% Ee*9=?no8F3[Hni쌮F/ZX qd;{̃x6KhEdzY./'-͆ab=ȴ`O?>][mxɶqr~z?)ʒ~[,neC AS<|zs-o_s{[l3]>c\]]q{b}cy#>8#nN7[+~C$b36t9؁LHY!"8Qɚ_2-Z Y3H/Rlim3ml* |ˈ>@/mדf i@X1e(&Ťb]I%k,Ip6֍"{KX0c" L4E)ͽG\ fv ^A F@?r57n12gٮ'[Sf4U;$ $S L-;ó]ܿ6hs.1TT*Ӎ@VO g70v8fKN5Raan IDATt$iy7A)IUL-:e{^=a>3-H gNϿKd~ E  V12@[hk v:)2nҵ5+DDK!,0ak6dy0ZnPR00,)DFKX \<ԇk[bZ2,H򌛛?GPm9 o;v4a{hPA2^qC.5 2(zm J|vKA [6+ IM+v5mISP9+EV{ o!Mr@K6-&Te,pAPUi_^C=P!e=}?d]J^|ʴkw g~o.S)}]v:ȶѤ%HUBU()1a GRfXL'*G 15#H-Iv}o,o|1nE IbE{ ZsDom+N/Σ/Otu-}"2m (Y }dh2! xTLxGOp0)@R =I&޼_qs! U2F2,H+6=hY,ٷ yYIr88sHPF>ydwzwջx- y0ڑ$MXk>|%޽3 t_ WŌfXl{R=i{4؍,NѢ=1o-;G$ \Re((rt":g21ΰ/1ްXT@^VY/uW]K; 1!&G;>[-n% ()5b4\\puua2w<}򌳳 vr$g9.ݕ(3B=d@ ǜg cC?mBfGhdN'1Ai")u»6k&Ev XAJbIV.Xo^mƆn9I4tg\1vX/q^;l%ՊɬBM?6'1If` i Q:9"]glnw@',Y_{˷LV%y!U\*Yo7s#K6ٴilϢF~h{:ȓC)+vm70&tCc,Z,DxIbWt3`:V~FJvb6deℓ޼$R`$)P邽, Dv[>| rǯ˛/X/GW moSb!Ę`-i2x#)H򤤪&II8tCɌ~vӰc.MCCwF؅B뱃!SD :2dBM^G`$rAYT"&H%'2?cqZ:8%T ЪnV%i&i tq)akTmqZۀ-Jb`kF%&$IF}RkXуl?@H_/x Ze#S5k!o4BW$I=c0 pvMM]ϱFEɧ*yeLy>Rhv[gϞ2LRڬZ6"'j|8c)׷=$MK?qIr\̉>z1¹8i :UFэ1˜l=I:)9zbd9c>)ݚvЀ10[̸b>=Em$Bi5( .ߣLٲx>({CS4kw*e1KTRQKܭ8DŽ)á=9<[^w*e0o'XS%m;0 PeH220rY!b(Xސs qz~I0D_sh^60K*Z AQӘz1[DHM26( Ue6Ѷ'|+Ϟ аL=+N3&EdI*N/ydӊo^=@L RNhuL @~D%<ϐ LߑWMm]Ȳ N`:]8wq,t9Ǜw|J^).$e{ JuѢ* ZIadG&COk#7/O5;YE Ikq°XSʓь06\O "CSLZ#޶(4n*CB9TtH[(RG@)±Z -بT*c=hCᝢ()Y3;J|BU`m-prSMǺڣ2\JEL ƒs7,ޏ3d)eQP1!M|*$:^TPQGF?LJ *~)TLR$B{ȓ<$XP ߼!±wl1\iN ur>#hid!P7-*qYR!R$gT!x';7)JS9B08!dz9UQSΒSf^{~h,xPce5\Ru0Z4SXyJ4M^u8?z'|k{V 7ol"Jh@~;XnooY6ym"uIKb_| Q; rA!3'g\/_*̦{,˸!'g|/Ͽ))UUɓ>{Ҷ-ͷW?\>pŽGHf)9֨b X?WB"B`h3 Yh$m[0foQL8 DL1.mI uH!@&46 DR*Q8Fn g2]2Ux/'TeAJFZ ="~UӷdN*44ǽˏQ1FilI5e^ѵ ތ\^N!E4AId Q>`aZ;^V"՛-&&y<2u-91mAz޾y-IEQ1q0X8ZvCu!ITyV;N9==eZ;fY,Dܻwʿ׿mk _q{{  o޼`ٱ( Xg$v;ʼ`-/{%9vR2ɌjYŪꮪL 1ywzAOf}%ÿ;9٬zz?k&1""1]NRD|CӕYj,+ǹ%`:spnSa}?0pVTؐq_/!,f?Qmۻd|ã{`&jU Jeh%JP7ɁF>ÏX/kv! % G*}M1[L- ܵdy⡅w6=!`S3lbÐaY]] }m;*cLH4^8/JÔ!He@`ۦ؄ T^5(\dM[Pԉу)I:TVHw=RFrLX*"0 Gh6Anost8F;Jm'RZ]ָȤP&GY]!f-o=H 0W`]2 uJSV4L5JSo8>a@x 3M^)ǯ+߶4[ͳg%e^!$&9O\g#DLSe 2 (aeI`&D$Xy!'/0`Tf1:bdA;n]д5Sf9.Rw^q8/CIEl ^/ !K~C?2kJہX''JYJB釈mhUa<%׽` klȜL~C JJNj@=F #Pz`q`}ruT{Kq'%4U^ BhLZzzgV_-#y^H- ?8  5%1J M U& pqbwoxLBrJkZ) JJ_ tyOm6& dHCp(B$]QSw]w>ݖ;m!YrJ'!{6\^Q21L4Ӝ|o~Ejҳ[ I%9Ϟ2&W|ɮΈ{Ay:nZ^0o^q}shTPVh[|2$'G'dńg]%]AxF3Y(=fqcԞw-^DLG'6-?]%~䄷o+JNhAE?ap`͛WKd<\r}Ggks!Ek:}<er a 28 d~Tki12gSQ"XokWg<;1H|AD`}d,J*V(3U!2]Ag1ɊQN[\o-Rxa҂H"A(.n5E0?:fv5NNN0yvy^dV%E9f<9@]Ag %G-1:'$yb!i}Lkd"=kC!ҹ0}L5-'fq/Ap18~(c2ֵ(m"{yEut^MkO>ON(JxwZ1 HxcM]'gd1ՒqqbvfM .oNdO0.9aVM'tIfF;dx헨wo\!@g;R2SQa RDe%o.vDe('ea;6͎h Uio軖LGc=~|2 Gcsf)Kcu|/E Ry.cȃ#֛%߼ML+p#~v!c`\vV,y8Z:;uMzD7W["fՒ }$xEf4uTkجlH!(E9(I7 E NRde ԉ5Tb:5nHp~d2a[U0tu<|2tu?l>==f6^_E: % U*QZ bDBPD)pgzr#)S*/Pلȁs2c|>"~w隖_Ud -w%>vH鱡c-! !GۀB|rKC*>yBF %HoaI2Ew×.ym{\Py t85@cT!QIm qCO$dLDRPp.<(҅RdhFL]O͔ e4*!rf6umZr͆1"d*'4) PɖՇIRa@`>"BN%W;F1]Lk"$H¬#fF451 =7)1DB8>`>;&{^~{= Eb ѢB ӆdL5=l bFnMۧ˺R}R[b亠&ɵҧ RI.)C B4-Y "DGf2a\OM!8дkigd"cJē(;0phhn3V^ek@IH!ΐ*ޭ65Y^e,4MC"qDv Z$DzT񌀦zl!ق_端ly^Pt`iP:0̑*.fʲ3cE[>654]]kbbNN!56vK79tzVHt*N(7u BBMl\aBTH41zUUu;ھv٬6(F =㧷Rs$UP2h%):C m CB!UDˊܷƅ#bEfd^k72`xM7߾fKQ,-rx+nqG(rM߷kԈ!$> n;`Cd#DK.˃cg:1T,﮸]Okiz[UaICqUg[BOiqsdDȌLg [nhۖn Z L>-yVዀTYܭirww`RsVw_u R%F5駟7%Q1f-LJxS0Wpyso1c2Ӕ9YFT-R yC{O+DcܷQ(҃wdymiqvM5FiV .$\Q<ߟ'D#sF8 !&QBd(eRPd;1m[v ![1I9FeSlL4Mji.?ѳ8<1݀P"^ajsēgCLs{f[op!{~Oy1r[l39(61ҴHabxrd|@ͨͪG s{ۍ`ߎ-A,soOZ'\[b m|r`$'H!ø 9Lp_hLᱮ{'LgG;s6mm#,s=Rc>s~7˟SwE`oV2{|{/x_}rS&\6K (3pg 91`<1N9>9av0ǺMTHGbس!̈؋e)Xa^Vf= ݎb<ǹ9' FU(LgCk/Zݚ1;,.5lW;>} gn#r!,q!C 1DR\t!{T,/YHrc|lR75Ղ{NY.ݯهvUcԾFr/eZo*7=uPhł#./ޱYݥ$KNX|7RSdYr_eat{Wmpgd|T;vn|ǧ'\{(4MQ䆾|O8c2p|z҂,0еKT^Vgg1&gݱ!6R&3F ދ;JWS+\켷x)4h߮7mtver=xKy y^XaT)Cc=]1P#fO?_t ϟ?/,WɟPݐgnMj<8AEm:oΩ1}c1&}L>d"KlLCHUv[!,R+(Pa4:;G, Dl?{S@,oה'S\]]%t}wv .aqmd{:s! |̨hj8Z9wjN{,"RI<׷w"C^;| >(auˌj4hW_59 }&5!ަ02R~i @(U $5vk[!mn0z1SUQ"9rڶg4*0Y:TF89>acH65%Ð-j?؊BJ˜@G?I42'xCPbDOٮc?>w_ߴ5CJ.o-E^CpvoW<{1CGk&X4;5)T7\MAT\^l1暋)nFbɋ MӤiɫ STrʠUw*t&RpvF#\p4mS&J ` E9C/mBc@xvnX#QtCGn,WeNI2Cɔl6Cе\{=l %%˻8ߢrC84:e_}`PIp#Z"1%B 91߰V,+x u˻1Y bħ=dy{/=fwC _u6BUEA!an"j@c2Ae(-q=][BbrysD_*J#ݻOf*l= k7L K\w}|5TEIn)6FGcŔljږw2X'Ҷ!5󨒃ьQ2LJ3Rq=wۛ4}rQ>4h°m>S5]<ͮK-19^| >aTMg7F)OYMlv=>ނ{=:[ UE ]M19]k1~G?/!Gj[G^ik)rxxVpnwWmu}=pwa2;|#/_r&O?'?o& =vpqƓGyc֫qssEgkG$otn)Bsdyn{yId6*G*%= 5Z F) ]L:Sdq9K 1J4hLsѵCbe9)Rj`s! %gꐲ|%r1fb~̸o_3tלw36=@a-eV:rJ5&/&4M)Edԭ#ӊj6J/,)&2z- yɲ$w]ݴ(evx+-Ry. )=ٔnjF=RgatA;#<8{6+9v_Տa`6=_-GGЪ$xp>9oDW3im_hNϨ1^/tή[stt19u]^o)a>3 nX߭&D)04 拖#[ILGhgE!Ұ~u?}?R8*5'9:X07_1999+NNfIT]< imװSGmhoRyGfqH5 %}i2=ú #qmJ>FT&@ Qbyc 2 yq1g<3^k& y# M2 bq' YB' !zO*9@7x2EifcِdQ9&]'/{!Oèr0EsFUW"Em'w;Bt-ZA\@E5U-ObڶJF,b״k4_N&d:GJVw( G5UrLeI|Ӵ=4B} %ޅt%J7K!|}3Hu C!%Ϗ  [s-ei a`]sz<+MoX_|r,)rIo7.EplOVK(TQctB[.|앣I-DMf}[FbZTň =T%(j}M)Ům$u=0ؖ,WYvrۼ*D,h:n&!OMw]f0X "VB)#ܤ o,?nIwHɊ >)e9 !IM1mgb7stWj^99%9ボuqvAbpCR%dafGet]CoYo݆z!1a:3*O(sjEz )JN`j "pxBLJKF529?Ȉ"f1&HV|TJ 2f8'Ѻ@2INS_YܭoqҹfZ%)FB ɔ{s֫= =vtTyxr}KP'  ^|)ցx IDAT=ׯJ%7m ]L1?!1wK~fYcZ!ѣG\\hk'/⋯=Y1! Ki{߼B8Fl6+.[2lZU+G{A6"T21X$ ^`tjջޢLI J?ɓ'tMKS'fm;Iű_r fMXF'?kbzl1g2V22븽k{°Y[e4q75`:Y7oIH9ikF|ߞ }:f]b<-N۲^1*Jr3P-xa5o^Y-YoDzf\CDjIVcmbS5>FvMs'4þH%hjg,Ybz½/^v`Qd aE@~nR.QfZ)-Qm"u%$ֆRT."L#,#,t]}JC4Ap>i~*ob;s"e,r)R}12u5S]jWx7 ELOYLكHmug}s6K]Ļ >6 * :T^ܼc:w10~ݮ%qu{##bԻ&̼aY6P*CjK Q|BUEŰ[s)GGjwv}M3l醖q~ lJ c Ÿ GY.8>;BApG[|3Kv8vl Ѥ}0%WFX\iEDI0HDP)G8F~%A2Lɔ&7B zݚݶcrP <m 2lgk77 )ǂ]*%&G-f$CӤlW<{ '/MBpÀ5Mӓ]q!w>S޼;gZ5LȵaaG #^9->Cv-Mpvg|L&3^5Ѵ[%"nd6ww9;;c,ydB۴KmY~G/Z/loa .rrrS޿]q'Os_˟p|t1.lTP 2Y$(!2N`/RUA]ש} ;r 'Cff7%`HBh!FFU1Bnܮ,p΢ ,uryA?D݆,[^%~bxt@v|CP@ʂ$y./qɴiCCۯ(w W7ow? ,xx6E{#ޒ%bySO ֱmZ@KEYj'FDT8CjBA0t B",XlZ&9#uݾ:[3vn ~vO>W$۝X,c r}}1xCI@Cb,n|hX0&?۷|HUUpt3n5Ͷ[w ?=֛kkn -9G@iFňo XءaTPTEEBSw-y5gm(J"G"!Dɯ[kw{>>VVYrڸ\%D5`!J@HL,H` %4cʅq'Nvav^[[ BЉ8}H2KXWo붎fE[6eQJƀ*jfB 8no^/`wt_{YWKl욢Hrx~mSS;Q0W}ͻ}p|$E#V9NG8D|ES.ĉ)Yд!BE m=DfkZiN .̛6qJ8d7a?_PVka01_,PQLg1+jȲϞRxyݼnukns=nV J%aiŦ-SLrrr'?8 jw@/nϰm$YiDh9h^1eX{n@GaՌudٓgd)_ʢf\`:(ʚ{)˫9u[r1l׾=ƣ=n眿y.LG͌4t]bIp)^-0֓1lI+kT!%ecP!R׸T-Z( E-#ج`(˚~>挥:\ YoC8fj e`&qNIM#/KzmUs}N?ރwѡJtJaA5W8B%q0,Rf ֚bY6AeQvr~os!R¦ZdYvZu1>6eP_11qSu]X#&]gKc loDvkqt`-xC[;>c֫Efeg,m]Mk9=}-*XuEFH# #.@uDmMfwNkaCT` o-FCnTM͌(딋s^]ofCE$"NRl); "c4#/UY^ݱZoxpgqL˃7ݭ)kPZ%qW R:Vǚ$q\Xmj:'0m>Fе%$yl戓Ϯ((2<ڶŴ^?{Nj`#52a-:&BDg $5)щjsRbkQ5-8!EJ 7IHb58ԊT%ZāC"@)An@h vFHQ* <", I8圪lGMX.3K=,c 0;M| Zy~>E5g`O՚=>Ĺ$J!g0h[tk;^xI]g|eCipV'(řf?)*TkcGQUWPV {{0?KIEY9!'''\5dD]wH!u ScH`iێo6oۛ2嚦icHMR4]xDyLDxa][C{r!ibLm׌KAdl!jf!yG佌RU]6XZqxGH7b{888$2^~{Z'XHD~y"w~?~"G mNNTLk0O~>kEj"6%T+6"^(Md` ꧅#V1tΣGe=nnnLvIn|@0X,K:ob[8 ?k ȶ).,fi5mP*˒$I7}FPꆺY1_-7(5FIG~ 8Rq{{|qз]1%ZK..OQ*0nŸQP_Pޣpe,Zj"PdYN9H1ejLC4kT0TI>"=0TbuúeSIM#$DZR+fs?fCi`jc[,b8#ธ͹ d8$o\"ac+…_;QۛEIr FCvv̶J6D#VŪC%EuX/6U Ή,i}G i( uXXMM'JH;B٬6l:! ~yӫx/}N01Ʋ^f3&3 lV_^cݛ0麆k ziB|H6݂ GO8: ?/Y-(%RsԬ8Nby?z`'Olkp!.Bo+=K8l?,yޔtLIƓul6E+]~k HDE(SqE4Ukc}IYnX)g A%@!`?g8o~~S^"`,kʲe(h,ǯ_ۛ[>^99v_/;_g4Sox9eYwpp0e] _~9wSNm{ )ҁm0XgCdwߧZS-y:OM]uIԶAv9˵CރqPזz|qG$$i|6Zk]^9WTW |HSVwT:ԨW-{DZY-pa>5XSa],ٙ4JG2nQ>yRiSbÃǸ\#$D+MiֳKX1M~#ί^bEMgP3n{Qel+ڝ )uT-xl]C 4~Rg cGg ސpDY4U/uKgD*mZ\RנUa 9fE5mӰѵ^OSܻjf.X$i3"0Y7XX,<{1bFD ng+孷̸[;W iNHE[G鐨RP4MgjD%(n!Cd\Qց^6&Җ~OfRiNƒzyN͙ϊP:RTGTwG Dϻïwza{ƽ]5Yqqqd'e4X.aPx- 9<_۲ IDATq ̎oepa*F 8&äG-Ycg8`oW4UEXh9E\pssM׶}Y7쳻?%soYm4nqI5\߄٫SH TCQh'HeE<!Ն٬fgO2ݭq@ueʒypDk:N^ruuu]Г umXQT ebd I#qqqCf<ק/2h'8ll4^/F8ںAŁ[ @Zg[bS (Vp.48IX 4RZCQFy>9  /#AĬ45k)uT&V]Ւe=ʢج=<22(sqq {M1,Ig1i~Oޗ,W7V$gҐbҸhi`mR[Jyt'M{PF2J64-Ɣh~޳^PVEm`4ʈ`Sւ8v[PJQ:yMX$kYQ1HW cL14#IWNRX3v{f9m#X,nc=}t:%MF|K>cNO_|ϓGO[0 麎ל/ɳw1/^2-=_O0c٠l V$B: 6 pRҤOX > I4ZG4>oC|G!hꂪ茡nj z(T_mqluQӴ) ysWMY"Tz*Fjr6iF=9&J3)k;G8?1M>z;6 g5RUN"92Plnz٦x}:X:l'Yl 5ræX1 |%qp=ކC tA) MWaGxkr"EGø *tn;t p:*XH]H-GQD ށڤX#,ûŻorfXX&ڿ}?κ()g%˕W<{ :JYh%wy1Or|Q`(PML"  { /^1wͮ_QޡB+MI gZަ?,MUU%(Uu tQrS\E1mk,RoM/d6+qnŻ0 5IQUŦb2eouy3cI,cr؊&"] bՒ^oÃ>e^'1L'!^cȲt3nglVk4Ur1 p-PE $ɲx9ggx⌲,IӔË.ݚl@ v&ul6(*֣ ;?()=iRQ i: BŔeA7 !J4)lG9)57 2" Iӄ\1b0t3._X,b6\B;("lA׺PEhãGČFSFIH"nh ~ח"`.z_d{wzlASVuuK^,t:ʰD ]p 媢-8`11Bgh*V9=%!Vxߐr-Rux_0n{>u0NO0qus->f|1(8_RW%<ƙtp 81a|x(#EXC M1tJ]uRDeܻ.1|9p-֡\&Dw&#Y̖w q\rvq0"5" 8Zu#H~LkDe/^/iXxnhSU5Rx^]ͿL]2_qvq$qNnM; 7A- Ok%Ri;YRrxxDg.=Z ^1MNC IEd)$m /O?5J;?8n D:A"؝di`,X, \J[-ؙdyFgiIbpmnu%,2Ivwve)=uYUxI"[e;Lw;C$ܾ$Y U|tjƪT `?")uh͊Ϟ_G~n30g6 \LjEV+X6[66F9E,< Nq3L֢f7Jk;ڮ5 m5pXGaBG)]H_eiN ? MGenVV+6f #x'(/@N0x^Jg*ʺn X%`4m>tc.8<9f0R-)7jUUh*UJ]סe[`bvJgj0ANH~( 8cxH_`Y1VEmҋŒ€`=m"K6+>qkְ\l( Rht=t14t?fFI:05$|N# y(6K^b^ Xf5P]m}39?{ŧ?j,#Y̗ԕc08_ >?Ÿjc:PVˈ,>be^X-gȲzY>':{yrQo/Lܾ٧s3ź#~JGPA0T8T1qp݌րEtOdH3Ew nnϾJbhH/NP?kC" CmkCE%INl!!t{{uBx_,jf1;g4i֎GlX(-!Fo:^1i|W#):/H4O> I~[pSܚN8q#\0mZ֡k4Fʐ]0uk5Lh<އCN+<,Znڑg}XkMHf0❷6_Q5oe4Uema`@\|Ig!JH i,WT岣{O?/_-hJx(7~7^O_k_Ƈ~vw\,NZ7˗'<5[Xt9Ï_qm]1!SBzkKRP-:nȲ`RʓBI1E`Sr$=ʪko_嗟3O@4em۰.-є8ڡkVGx^sxt)JVί^ԿZ1ۊs{9d;ޜI O\_ܒJHem4Qpyyu,F.@Jrc&e(s _ݣ?Hgx E\UG)a?p[ԖxRm(*w89~9M *t$.Oyq/h jKH UA׶H'M(\oqp|g!1A8#>~D%TeruUr{3#>|FdyB'Yb,!%w1+2jkDT Uw`&%dYBWVӰ><!Bw\^bl+R&\]*W׌}V Rh$ h҄7TXo.WDM]-fM]X #w(ux4^:0xG`Uւ |`w+⌢l:z4P|ﵷ,{{i%Q4 N;E 3i\M]qlMc҄4M@:v!Xt]7DVDZQWkl]+ɈYm, fQ,&)MV(6k$Z$kLEtmI5 iz{(lQ*h8q}Yіa{PV74 /׼|}zsA3Hḽ=%K"1YtJS*1Ɖ-n ngw9^!}lvj9G7`C/ l !~oKtMëW/O~rrrqswɟ)g)Teӑ҉ ZR8ϱ g+дUCD1ʨn dQ6MКl3!BjNbt8V%6R東-m5XS]o6ް73S%ᅠ,Lv_& vtr@ƴuHH}e8PxBqnV8EpNhAeVq+ XTu^f81g[)\|q uYXQVk^tL0A:pNHpеvkh[CBJs6(B}5N`r3vk!!)kpxgHUs[(jƴt׃xz9GE)" G > %U HRl~r9ikܿϟŧ/جA ַxggxko?'1 , EiioP-h5|ٞo;3+q0ŗE_i{7ؖ 8;%Z oxQ-IbY*zA̝s/Cn޼Ӄ O^q~JGc\kh4mI8::bl[i5DNm1}5xD)Hf.ɲj|GnPqd2L7lv:@j xBa!Xj4}khʞpĭӛTuSHb??L}1zwC|g ` m:"3S;RYmd(N҆ #ٛa>{N *w' m2Ɯl;m[NOoeZx+V5Ub{GfXa |P0r#& IDATj.{z-؞jzxtxp4Ixy)CkEkv a1G%+=X@JZCkI7MR87%Nlio,¶@B-M M&@8E x7D)N39upvY]Hľ HRͮ\͙Rm=5n3zL.ꆪHIHIZә-BED'mJg,RJ4Bo` 5}yozH75((Y@n7,g2R{ VdِH^范kvD%Nr` HbzCʈfH,+HipPW!3;"HAWw .:;l:b2-3^=a[W]1KGxq=R W7Jlk" ,'AtQ1Ȳz|~?qmGmZh\p=#y ah:Jnz1o3 zɮ\'xL4Mݑ&Cq8Cqč{5x~A[H]Yr9y2FO:?%2D^X@$4%gr/ǟqhHcd8IIӔFs|2ٓ9|xXnhHwɍ[#ⓟӧFSܻ/_#^"4k>ـ GM'!q`Q 1xg>5$!eVm`6C$ =F-NXzc)G~# 4'b)B+S\4K90"*K6%/^`< N2(j?{};qCfltm,\JMO]o@VtHic<Q M"`xk݆( q ]Ź5U{N]/1qgۢN1@5t&`;PR] +PB#a2981{HMoA4`@J pYv{~wHh/C!I2y1ݎv$qLSu4uРi^_p}#JJ!˯з5>mz{?)}ObYoe2~mɀ y%V9sXCjg Y~.ٞp@[5L'9]_!CIKz1;V]i ]R-i0,K_q-n޺EMyzC[0ηY,v<1z͋1a0KFc)x5|]^ e7_ i ISm*/6+Hn)KpfM+c{|yIq!I6{WqF[}|?_>*|Ηk?C:yI1"g2=KF^w4MO)|B]rrzV CሪiQyD1;2MxY zRfnwnrzƇg{t0q4+°Zoi929?G,Kb1 CRP)/ar0A+vuKsV8)Ӝь3l7 TC)2I;{STZc4=tN\hNNcbtD',um!)$EZGcj}Ek"RxVe۟lg1bʫWgToh g{;_ O=+VkTaM!b٭w8g0C)"Kr&6%}oQP& r&:l{"d8,R[;$a1CM&#)j֫{$k^~̣޻ߤqQV $5}ћriZӴ8Jɢ(l֛9 ]߆ &X4SWlw+}APXQn ynQ8Ƽ\i=,}m ԠiDQۓ#9IHз:lQux9uG]A"t]5d44CNۆO>9ǧ3`:;fy!wWGGCv_}ܹAksvBTȒ1qAYn0^" +cXV͎"|G׵o6g"@x>`-Mmɲ4+@ uxR L!MPEmffdi̯m J].6F༧i閂$I(*0R4MjhL0]ںm#GOSm"۽$Nrʭk]d38zEUHY\`~BJwCa;Oۑe5 ibۃBu,78Ήch6ŠFn-ma\=Xڹ= ^JY5U s-2<'꺣CW[Py{.ٌ~_]%U#pN,zOf>]?g_wv5O~Iѷn2;'~&Ρk+Jwk04eו\_-9?`40Mnv5ゲ2%K%w R4*Ä4Ҵm gO,.бK >ŗϜl gؾf60>_Z,x%trr?_|1`~g(i{瀶]Xw2'N8c{S}G*È_/$󌻷orW-BF4ŘjW^B':,~7A%)N)w~s"jgOzG g'U6u<>)Z4䅣 0$MM&]`LKX.<{~F{|d8ɓB,Q:0E>(Ȋ!GGGtZrg M >AYV ovQL%q`*){O蚒zE OXXqG!4u\ϯyuѾ-\ܹ͗sGGE]()uͽ7>Dkj}>'yDg?4XאƆedE _|^mQ* +ܻϽh˚rx8(%|>N/^뚥ZЖ;$4[ҡ"|3u@n%Z+NO][ܮ٬0}8j-Bx4a9])"MG5ht6|n{I%:v ]ɢ`ʓ%ӵ_~9? ~=;_ɿ'35N:RmP B:,&I&clo }t6\j/9;ƍIƮZqŗ;◯hv5]7]KBj<6;Jijdy=F gl}t@uQH2Ucںm[vr}!fvuHش+i,*aBxDZll1 J#d4P=R@ʖYă-/ - ]-qZU)R:8zHO_! mlHF1v,wm -$IBXz= $"/(Pl6(a6= Hx5]Ev;Kc:=}7q];p*Nn98{qqeI$"x83/üiK#6]HG;AoCo͛ܿw?(B`-|!<9UhFWi{wa\T>uAt- 9Ia0g[Wx'X-[\ o5z) Y,ExqxbX%l6i[<7a9|m?Pk~7s-ec]ǚjOTuED_'`6ىfυLI%S4ʎl9(5̘IH`۰+yCq}zsrXN`)HxXbAJdZ0 p!a뙌rl߰^lW,Q%:D=Zu mUS)yu Ba T!2dʒ{b1RgÏ]}/zw̎o1;1;rpxL y ΅Uoњ\wzڃwT'tW ,-M9:GE[}]ϫHE%my=Q79$RslBڎ]UeIh2d2٧dqrM*83;Mby~%RR 5,J~KF;ZC͵{|Mg(&fρZw }Q qo#4]3Ggwa3}ش9oё'"/O̎Fm͓ J7H,ִ4Β'Hpǚ󎪺 r~  o랾saC*JR3 Yt6FA"#MP  ֣$@e;O: AD$o8u]f ,{ $i{iHtNҚzMݔ} %c^MGT3{wx¶7H{gYFՔeEؐtz[5 +LrRV*i)#DRa}X©X3,%}rzzLY74ʉt 4.|=M=6, w$Iw %1rn2h>Cmlrlv z8?kڡ+r m2 a61)(F^AjT  )`0ņ꼡)Gxzf1 шI"a5wU#CKV<ְԍe-3LhSUI)!#=d8p ׶ MSsH$,9:H}]C])`pg{t"l4b7br9g2> I5bBŔ;jUV ^Z8!mpX( }0__az8=GoA{x]V9/^>r^L{޵̯Y,.Yk}oՎCn9?d6aWr}uEUUǣ>Cwl;P H+b6=d~r2#]Hibo99<嫧[6%W9MkY^OqzrHi>S^|A" O>cPd9ѣG:r+QÇ_6,)b8b\/8cܚm@M E(NɋikF)gkt$H3z먚\Gi\`zvq,qfh%[k X,#RLڶ;z 5[7ǿYvрǏ  7g ),Bhۓ$q23v0}zGhڒ7|ȱEV5X-t;99%Vʢ]Lh}5O+ "٣]>z_~OO֑M$RAAQ 6IqUYR+ #Cjf4YVЗ5ںuÊN5-X{e'oޜ.Kokptze1٣m ];$Rh`Zcf`{;8n;5''',nhk_ʢqoV/W̯! a{g`X͙^p~Z)ޖ)HӜ;$qr]oϗ$t'yK:?O kVC*ڶe2`S-՚s؞MQMKU  Jկ}j=a͹ vF۳#XDNowP=63`Q8Z5ײ!B3dԶ֞;jVFLP=i֖բB0IӔͭ KJ0~YK }Al7>OB) ,_к'ıO,7gl1_RK!p 뤯0N::q7WHưuR8NiۆcI) :B^Ѯn@!L-E1}k1&x1?W C۞ξ` #T'mAW\]gg[r3Լzyj~nK)|}m[Qg_bohTC #D`Tŝ;%OckOOxk8Aتx}f)US1LHRu@QtmOzR;h;C&LS$'r߫0j,=m۲Z]4s;M^4"s8J(W+,G[:|rh_1(cl#i,ק[fC# hA`"Ksvޱ7 xJԨ9PU }gCd@e}=C uM[a{'zpaPH₭m{=vvx)j -NNZZ @ RdCR9{;*E#0 ,K(&Mcڶ9A$sm[{o1x h:?_zPkd'p_?%gg'Tm,臟rG߾zO1SN0h{Gg<>CkV5[IӔ[?ux'|>/^7Os<">9$tЃ̥LʚdiN7#\߀PaOId }ւ0qB$% -rc2kr(%c`yqLP[ʪS "RbKiU&'.}ӯCXwi! #&mdu3*LFer` 4`ץyh4 B!NwFK"V :z A1Ji.Dd{w XsDq $`8F/^- CEuEgwu)kK",Xz׫^)@a0HG4MtNu6>췷7' OxAY֞{8G]kF}߳X,y>g?,AQ0a)WW7 ~pC>眞rx4akgBմ5o(#~GǪ着S?8=dp#XQ=T!Y"-&=o t[ۻ5+u[!{wG9'W4]QWu1,8Y.+04X0( 8:gϞ/y\^^!es^ZC v!7S^| rADIrʺjz.<ϳj >#w8۷Uj&BPX. 6ϼ,"kA[w}㙌I庥oP`DgmNR?@b6oB8Xـ7glwxp]y/2{Aί^^;Ǹ ./n;pu6ISd`TR>㕼oD!-BJ;( "sZwCiK{etAVXЪm[H߾$If}sF#Bj"Ֆ`-2k '|7ܴDI n48|:%@ u,)"" e~i xj[z)IXQDׇv?2e2v+|}Kݖ {ө8 Bz^RF8P]?#,2m<$@~P>| 4hm:Ga`D\__{ ]=|8aBdIJ)?BL Cw7 g`&=> 砮+pt<# ғe78A~+X)f)\фqحʦ&tq9TfdRvK)F[l(rh QX%j c\Irvzs<͋/XWoWLG@lmE)ϸiAouGOA $a3וg;liu&VOřC7KVcD( lmmKABk$}tkѽ6 (^C,IpպFPg gopFDQD FK8# α{L&LG{w /P˿](˒@X( %URqP{zy3G f[c/uU~7s"-& )Y:ih.jvrN~M:,i*$Ae)`C5Q=d:^]7^|m"$$Y: р$:7OPzjqCU/ =Kђ\a?|ȣрϾ B<cUh?߿jۓ_)᤿*zYd4;/MUnk>%)2o>|cXK^|JSzt웗 ~_e<%_9Ox!|o&~0^/9=?j~6ys¿Ͽ|CɯƇX*5h$ƸS; uݲ,W ' AS%%u{p2蚎lxL[78c퍊 R,Ί" ^;Rѵ9zx$75jGDQD$3#14yJYk}KJ,.+V1MXWdi _=924pnZT_>U8oGC(-ӂ옭clTcLna^4I?Z0x% 2KѮW5Qg6;Cc-)l ,\AۜsR@[3V qru~ASW ]g7fo McEn+VS@2}ӟ|<}zA,aw7pC޼yCYX}ZMEdZQ5I w3y7/ߒmWW8 /;C隼Hr*Ҍz|j Wm5-2H==Jyūo%c2߶mc À)֤ݖDȎZ#$cC}ʪe8y7 IDAT!=f[oXO ~rd2YdR$7$=-=~Hi*b61 _t;@bLGgW4-T7\_XV;=fN!We%Q\˚P£Gﰿ9_~9A X-WuK%U;[10>=jruM*U S^<&lK1'CXhe'9 ^9[=)2 }n5umi:M:wy]/ix̀8HQR=jc 3!aЪ:ZsV3$[w-@)ͳ_lm\Q+}I!Y a0E рa![nM càpYSse|aB}QLzK )s!l<M>;EdqbU^w3F_| w}lWW&qu~C$ao^/Q=J^XcH~#ݶ$҃m^l`<_]Ј@r{{M]U͟<Q@`L 6 U0A=/!s=7Q{"k7<ɿ5)F[h]DVRia>Z ($/BVڦ$DAHD(aD!~`b o9Z[&چ@:zMEl`0@[q8Nu=MQ 2"nw,!vPyBg%N[Tu8L^9|@:.ĩDi}0X,KEԕL$7(Փ2HG: yM296 8!;[DaBDQB4֖73 )5eb\ 4d= Pa{6fsqQ!ʼn4D63npTu; )QN;ۿ9'oxgߜӫEZc!E@]]/!M3|ɇ>$\]SoqVquuB' )pBcºɊ O v]!uMS FQsaQlI'":m"sk0^#w5E D V&ޠ{㜺q}HRFEt]2`<0'p~uJۖ(]ZPZnoa$-?$>?u&RjExYU$!ūa.NX,q/p6`8#~G4͚[^xr9Gk5k$,Oq?f^ďhg,mPZc9:pz'-"c ꫧ(C ֫hjbq;'#~GF\_S3jx0$S#d?#.NCdO_0فwwL^Ɩi>[ڦnҩc|Ji*^1 uwIL|C[=  '06& H;w{}{rN aBAA5JBqdyAkMaLΫȵ`….l%'hqM9[o -ɀm[π"I2T򂪾e<ɑQHۭIB>ILUjlQqcfHNt`Df0gٷ CoZt(F {7$D9YXR7+9>M |9w,ﹽ] /ik,q$ (f8$ C8;˚5B[Mˁh6rmEX/t~G<{ ߼b<$lmOLsi8U/[[3v)hۆ;V9uAmtuy>6<#fn_ܢ;twRX-8?c!H^aBH3Ey~NY!DQLڱXUpİ"kՄ?< 7n4yQBZЭoو4+.D)A 3X)D ?1amGtV!޺ ul: rے+>j׿ptȸ_(k 2t2 ; M u `18dnjbGiHGD9VګD$Q կ\@$  #rAXhQkqR0S m ?H~"[NV G *Cm?`>{wf@Z Ojbrw[ꦥwPPF$Q̆Q R71\ݼŘ5QIӘl8%%aeoמ ϩaep5} 4@xR"d^p}{63D} 3((}a߼ I2( T3/Ip̠m*E=أ8"Lc777\^^on%gtG %.(eZ rI{넡蚬5!HfbsP2TxS3^/XuW|>g /_\_˖{1g$Yp<`wo"m=ggoy_|W8QCVޞo&ɿD@eSU a=cV;MS5a H<&eg>f4(T -c5uW^ӵkzn0q.-=z`4 ~t"r`tWϟw?ϘͶ ~ٗu5mqܿ{?!9W/ι8k[ѐ]~9?9]2nW7O+G>ΐ꒲Β$mS 0L}Od%-$р$1F]OorEx[\\\pssMVdÂ$Y+5}oJe|S~g!;;IHk%qҷ HEBl@ŋ[Z@"UX+Gnx5噇ᦒHKvqI@P^"4 9Mۣu}no5oOα.m՜rN%19=d~SSF}G[WiA(UW\]$ CЪa<6Fd\P< ?]K٫=SS+K8c-Jal ZqhH s%Kmi%i2X.dyHUy-1l3p8d]XB8ޝ;Pw \^_y̶fXڜ흂dƺR\^ߠuH F{G9¹􈃃G䒓ӷ$m!Ur 4 %٘(g^=afw?xg'~jZ[ #{1}M&$y|>M>4X,{֫/>}#s.nY.)$" "IDatvohϘ T՚uu|zOIYFEyWV\_]c9}Cl'dk3 2Ibv&3.,OjcW_s]Y_=<2J%~ja0&bVa>c 䈺8lH(].wq0HM[czF!ȑ#Ic8bMXNK0DJzNt$(L:r;[SB4a)G+jO~׾,9/u<|2Xz? RM³V ĺ+քǀqkDaHE@ A1`\oޙ Fd 4"YDRwh<C۶> f-Jkd( ^g-wfo߾}Z9oK) {h=p"*e6#I"Ohj?71bo[Mܹ."P,Vg 8xCW8W`;&w,Q}McZ"%|@Q_QU=QJ'8o>A$mdO*cb%=%Z ٛ.oYo9_MU͐M'D;r]*TU&PzEemy[mkVa`cLZVˆ` .Ohۖ;N'Jv8'|}dh/Nx_pCADYXP=Qƃskvs CF|c9* Hd1jdYAU RmXnם%0pVE4c..2W;]BJz;y[YƴڐCzQ(,(^74˫jABg>`yi{ŵ(d\i1VUW|ބXm֐ V-qi2@7o^S^`0_$˞Vh(i|tb0Vx~gӳ =}K#I=;./FC xOT?jk2@RWн`2ܡm{s1}l~_ mznNαFIx;qsu_(X\^\^.{>Ox쉯Ib20My]/_b9b૯qu (oO Jo_Zx˜ehEeh%i* YiCsssCTxvfE3 2 c4GDe a |mW8?<l.] #uD-Tl RJ @rc3 'C e@ d DbE9ǻwH!ZMuNe|g,K`AU07 #N|*٘TMK> (8H0hyk_+noYW Yab JwBZ9q<"|*vkgWo^t2ngݚHq.Br$,7 1.pސtz)U֑%9],x;|p ԋ",($IWo8?:L}Ͷ8={ӧO{?Y;'zK6p1ϟ:4ٚaM@=9A—q388yސ(dfqx\RopOs1ZfC.o_AR@Lg@Hn){˲3=Y};"NDdDFF2+bl٢Z--`47< ȰepCjmn[$6i*feVeV^"2nϾﵖOA q8'z}g|q||D(ʺB`;j8nI8TuCKYE 5RHaYoc6Ioĝ{s<5[{#"ٟSwY>#.8| %IE)4`vk0٫34 lsnq<3gMe!{d~㷶:! ?Ƙ~:#Ømo/^q3[&)5mr-A:O' a'v8m.*T0-miNT>ZB-)Y4HDV!7lAJMyd/bg$. Z`N؂K]okv&P7] iG6Xg0sQH)EY/xHBd[pOUU<3,M}pnHX/cJV%/qL:I=v=AJԸ`ڶ +wh_HLFX@dZUM?*lvҎYKBmx{!YFR V>iAҋs@9=1YyHjN v0x~t~7Z"îP1P>6CQ7EI ^olvM$ZT )"uTeCT9AxUUsqbFYb]MYR8Fɐa0 i4]q˫3DTė_7W8Q$ UP  h1q4`g@'Y}?N@eMm yurS-2 x_}; *kҾ^FH/ S48Ԑ[>[zJٌ/?cS:\zU!u£q|.??痿<_w>݇㏙LvY.ߒ-/Tl6EINO!EG#/hx9|p| xo|qѴގkVX.-q$hdKIcgo`@n1U-y`Ci%a,@Xߵ5BH0A AIec q G$.w9-^zbt{m1[%1kZZ# tHWū78a(8.^svvzAkw٧Oy[| /e1N~ƻwq,v%4msC*G׵8%p0]\Ӷ-'w)MFT',Ż-KAkV!US|S0e Ơ!οIhMF`4jS+$ee|2ޝ?ٳg|+}%1p1)7u飛fz5*jMd0~WL -i~<$igh) z8 u?upz[X~Fw~Ś/ˆw> .OVJ( ~B[dU0 qo3uG/2ؖ80Lhe"K`4!I|eەLM2]\yj uQ?fO_pqqM[( d%ftzeIp a95jEQT-0}>_}E^S׎$yQf0HyGW[f#88賿7>׃ƀV :A7C:ءJƻ&Eѣ隴ݝC>}EˀMs..n%@埌#00%B2(7F}7~j>m%ܾsBܺM<ӟ?㧟Px`ܧh6e {)Rخ-jJtLh{O:#ںa؏Cpڢ* EFԀ!+:#W!yW#ۇGd%:sIGrtO8ZG$ĽI<* ?o,: r Gw#Wl yAE>.0 PaL[qE'r<Lg+ `MLбH*Ty n9)mh:vuNZA [E" P0f3cvXouX @^ms-iL[Ӵ-aJ g%%|x(s#J1NSȾ`0V\^OA0mCm}~۴њ 'rjJ_ٌ,w!PqP<{Ѹ`]GyQeEM mqܣJu1ZŬ9$$P}_ hM #Gc;j,rh` 1݆GTsx$#d uCk;$)m #!!GYzPJcDAHH_o<:M$aV9uҕ~;E K `;?:bI Z d)%uFE!tM0=(oZ﬩0[AYalKYtV;F!7WJ> ηaI0(K`}6u|9Lure:+^C*bι-5?R VMxQZڮ`Ȯ kThhA|uH긵 Z#Exa" ݊)(w 5.n\^_QׅKњnut7?e5uBu!s;wutޭ-$2f7+R|݈X:** JD8oǏ>b3Gdggs7gOZx1k}M z!q ]n:':k:'8@X4;Cv̦K 9U`:Kt6( &ܻdgO"pr`4{p9/z=֬5Ŋw=޽w7fÓ'Oo}{󏹸:g~@\r׶pǷOx)O>69BĤ]WWl69HsBxWrr!_] CjHpdypukt`hh a@X0|5/1cg2f> dq^ !aя~ AӶo-DәP{1khEm,S./|l?7|ѣw(#>}J~sܹsQo"Ȳ$bCڋX/~%^`Hka1M;9qL4uN逪5ܼd<8䇿y?O3Tg{g/ϟ0Fg'jGy]w(Y^0ج3PuKA]/>g\8Z<_Z1El~-~e{9U]}oO/>!wwzLX,X矓Ye CG!7sʪ@a [W\ohjHSx8Ϟ|JĨ(!Vjt% #.oX,a0 @+b(NIjn1*nFD>5Ǻ+Ђ 3.G0O8?b6[0]l`6kLP%Mx֍:n0bN%{wOߦ;,x¿Wɦ$ÐNA蛂[xRu!"x8a,$.r ǧ ْ-yNj6:a?"X!QZg_a\0taQ,8{"hjIZsnnnҗm}:IvG3_1r~s_kꢣ.[XkH!q麆̱A'uAh!It ER(`;_&o9pa#Zw#(jJ趶kSJ(Gj0e:p«D! P!T"}$Zj7v HaN +jghI/ai-ur5(K>{i:E T$bM T5 =W:Pj6c/] EK0 %m+VW5MEHqۿ+Ђ@G~Q7D(upLvpmʑGYN{TuIjbn .`^1o۹?8Ccj+YVyNZ~pKeK8apmȞ"6D8޶Mͳ"v'cʪ!@HMU8O;ÔۼxH-i|XY:M7CZ[gg,#u mgJѵJ %RʲrpgK xmĐDp@l|Tk-BHkM@]54qǠo9> d goj^⒓1;$>alЁRBlX`P?-+!#(B= Tu͌P˯9KieAH`D/!aI}f)q*143._Sk|Jrr$4t0"!*Mpt 1_g_ֶ-~.n<+fe8}|­#D@Б<})yQ9 Պ[czW`1_3!?vo:!LZ_eg-6-(&CF}zIn sq̡j M0hᡋʿXĖy`I8着NTM"ʡ8`. :lC$#f^cE',$I0MŶvcg6/naf5_Q%t=`8Sa1TCu(1^apMVꈲ,)+r!n%"cL{r%c[5N՚Y"EBt6vжuHY{tep8i{3Ԓ8麜4DnjF?F}{&sq>O(J:!^ YSR:d`Y EQpS: {@D1Y8k t;;=[1{}-3BHGڇpB uYK ֡\a8ÏKF B+Lk޼nج!N~BWL t8Z_5,-u! c$Ւx ?awgBV|ӢcG_ td4!*;찮![g_>mZ/ڂ(8@;vwGpiڒ8At:ͪ䓟 [2 CbgO?tb|:#%Av%Ŝrkt@7L&}Mŝ# Yn03`%ܹ{,d%cgc$$StDJFZa4%]踇ѴMKOh#Ԛ(0$AGի!:`m5Iܿ} 真{{ јji ԆbY?&)/s?vzo#GM[$aD|T8 $,KO4Mvѵn[}-`;dzt ~h+pPl8B" P̞8CL33 BC(6uh!¡`?@w e,i'Ou*Fi/ _Wu BY߾ + T"yK6ƢpVE聐mKYX!h;4ŹoJ@;¸AkR|[^`0(QJok((RaNy{`1 mk0"d)^b13 Qǡ H iZdz +NN ɐR00"S P)ZA}//)VB1 ]-iB:HÐ^/"JCT Yq1) Bq[e1iP7 4mbK^$^z#TUbؚu1(W\\F̖QEC $Z0-bӱo?L(g ./[6EƦ,(˒Jmyclh&:t0"&If+/"z嚢Ψ@[$ZEmMeôhH*iH}] IDAT;=Lؔ9zzQkA{>NvՆ,[R^ Œ(/xSr~-Fc( ooq)oG|^y^ps=SscNx挫7Ҙ̨$[{8ײx{jEUL׬ 81Ϟp32|? Ic~~??AF>fS5Ŧ,j/RU{apm &(O4* MfuAQ/i,oi1tйzZC,Pt %mK6[XPwBFAZq ;H۽EE1-R  )Ba8QpHZ6{w9|pBi5c޹"oO>0D`# 隚 Y.d+s1Z(7g=&}%dKH6k,][CAYl I THUoXofpaԁm2m7Ji:֛>w'MSvTUAIMQp8hD۶LS6뜶(l[*3փ- nګ뒦-"Ht"4JVu`KXU]\^_ӔO쌦 }(d3/.%|+461Гf~!G|}AEŔoۂ6 'O>ź!oa[5 EB3=`7Qw5IO{Q5CrgKn6(i,o^1Qz[]]aÊ:,v 4l%QTJ %;p?;wE0/ td΋C4誆bBn] obmVp@洍%}خ*6AL=sH$(-g'eђhQ̦[ jdRo Q0{C'[2D+ݦm+,Z;03Ac([]׌crQj06LuMhG/_|N( bKĹ Qp8Zȫҗ(8A5B]Xxs{Ðp|S1WKs8>H!i_1կwy^|A|rz̭ _W'FPnjϟ5ECo0$ ֭]v'oy/ɲ@p~~jQrr: (y^coz{FZO?:m+1Y kKeuƜLYs" U(!b.Z6/i[EYFn *#2cXf5q"I}Ɓ CrΫW3$ 5 D-uyF YfkHxq`j1f븖Ӂt5$Qǯ>{ƳW??k'F+g<: h) NbÈpo t-E]Z E$8Y/(4tx#"Zt2ums h$,5!ORRZK6+H cp$ YU✣Jl*'RS )M0_Q6 (g|xsf7UNnt2ihf!}EkMgZ2ɿZ :IPAo zY T8$aYmV4]l JRlGׄ8Ec,.888&MSzQLΨbS2YӶszI`05FC.s0Qt77Hu_4M2n,"7(e2`r@?plG۵ ,|ҖٱקdJ ZfSb: DH1uRLg3**i3(3:Ҷncy8[0}JXo3^:e4pxk@kvLٲCGմm e^:t!Z$Qaΐ9/^~lvm7-U!lGt]zb6+Q8ʲ[nn2F푤1xÛ4B,[-B$ Y~ɛ7o, a2`?uc X.y',sG%قl1'ws|~͗_|J0$a?^W7/^n<}v988J_)6ƭpANIRp8f<إm`*aJYNs%Zoi U$ 3 !{]I:߰*- dk!@ s\z‹g8:Ab* (4чdYϞ2]і^/OH*wP2`>hr :k#9?`1Ҷ{_()0xHsU w7+^DQeh%Y”hzrr'%ː$;!ٖ{Yɝ޶MND\BBޠbfKa;Gt[wM.R&%zF:( ][!_I+Yr-hijg$qQmgX.|w1Q1;u-͆[?~L$<{bCJLoIOi |;wNy9=yޛJiZZkۆcg>gw3*n5*Ew HTjq7 . ꮮ@VU֐1dDdxg>m{Z*Jj$s1[{{QQF|<)?)򆳋+)Q EW5 [[W ʂ1.Yř&/pRbӶ6UsmH2:K89旿%{1ăC8M,V+t 4FDv'46Պׯt{żrA4Ӯ:ʼέ#^qYD,M!<83$/u%LΪnή8r 4"Hy?3cz5*qB咫Ã1iId2!{ oޝ~]luP}qzFX%}Wc~cX(w,K,g\a-r\_4k9JE=|/3$///.J@̗-R*0cz39ي[ H>Ϲ`g{3iBܹ},+{ó/?۷\Ѷ=;|cM ][q~~xͧ<|lR9Ϟ&=w!ipyu?sv2~w :;;"bwk$^ fӊjM)[hʲdpBI8PMJ݅0E HbA"%vj3u. ''bʼ)k>.jd=, J!N"T$0NxX&HXیV ;(el)(ʉ %2^xGyQ$g;+@#g>*¹pH`4H6؎rI[+鴣ZiLQy%WoIESO40 xwr/~iQ2No [|phl6e4,VK1uKhmS[cڦG)k1ZblC> ]qP@eX{;|vKrXp퐨\ͱF(^/i+Ҥ&k)=Ixղ .+X#e eVo~'2_s|pH uEDH_GQ`7z)I1A)t:R ʔ0(Zw6q>_/ u|~TE*)ɈsyN1u{5k (&svwweL$]f'gTUETFVADӀpttĭ[\__SuM:``{G}GUugY, ә}uo_۷4cw֝c.iQ;ZbegiʢjHbp='Nu=mZ_;>m[^ِղ ?7KڶFlɲZ6Lؚ %2b[eqƑ%"黆oGnA ErדE1W眜c4qAgahY:u0ŤirY1:CU7pNνGY)YVP`< \T9WKi_H)i?uS$GGz1ӓs..fApx#ꫯIS>`jfw{<- oż-|8f0TPηe]t֐tmMӵ!_gCF Y\.@0H IDATvd@hA bж+kC835|?H4)v",hibPj88 q]ɀŢ \%y9{;D*gUrs5z<`{k-7ݔQ9Ě`{k;l?7 C1.s&"$@w8U- O8}NQtYLYMȶ0~iV38Bp>Mϗ_|A[7}sr8g8(c2SV]ѷ [cE#iywz':vkh㹺\p!m.9>15le8ׂܥX.kn48iP/!p< +3n,f͒8ak;c4,'1ixwvNڦ^CUY./I >fAz l]j= 8YjUZyܾs.2t}Ë/yt1eړe>xu=m[ΧLo NxnOnSS! i3lK i-y6 F;f:( U9<'qA"SxI ,1QTҬ[% SbOUA%)):XszgEENc:d55==qGkMDZ741(ZGO?WzA|h货iMIMpX#zuu5h  x*QEQ3apnBmo^F;V5Qp8fnz7A ($MXP[|D,cJjU$z5J(6#6H} ^=>:@O5\MW` MJrL?8 [5Q²TZ U1%P$RYټ9fꯡI >bteszCkt0@)Axc]ЙFQq@W+0ޱ39dkxALn-iLՄ;KVXrzźn$ky#s4]0ÁEI;CIzlŐUhM$0X@c"I#ՆA" "zQ*FE p U$2& p&$am[1ȤH-ye>]'9: 驝1M1_-D8lhcJAHp\ynH)FG[_08;KZxet%1;pk(F0ٍY ɽO>yΔodW\܄skn9w~wAjNf vv({V͒LQJXx<{Kw$y€Cze0 FjᜱZ uوLɳR3,Цƙd˪EP+i" RP2fX1pe){{{|I,WW4킽=mQBp3|E ƃ BdI!;)os|)a<(8;;-c-xXY؜)1BnoX۠cyD SD$y`S[XŚa9R ;ܹ}cUP3Ϲ:_b:rxa~!Sv+yZKdH!,IjQ;;G?{v'f7eZ2rDlηo~dAQ2/Y,nOxouϷ[w0qqy=c\&IXOy仼~wGGyrȺǪq':)M@*Mk8@-Я"bFob IiYT層9 0 ~dmaZP,>d(3Dm2"45`rSV"@FAI  qmi۞8bǹ e`nvAo4m[#"K &9:"F6C=E^еk`4*ICSJJsu}gG K< Պ 1<2SnW +͌wDc<,HP݊l DHƂ[p~FqX`E?^vx2!D,@ Ȟ;bYf(hb^Q{̐0h Ib8}|A^cź U_q鱮2\PӆcUUqȳ"m)r~D1bfnMHP ^$qBVul9ߤ"8ʦNjpO\*U01:>/󏸼 ]C Oqto^`KO߶ı]ܺO+`NBpu=j|Q%\__0_LrM0-VH ;DqgO/yWA3ȹuHxuS\Pd0 IX;{(0ʲnɺbe`-@ϗ̧K޺͏~}dz/{MU7QN]IakE 'oOsqvy $)*X쪪G)ؙjkG_Q>Wx ltHt+$Ï?jca658}͜|ͷ?&w?/_4LBlwV %3ޝ^2->pO$"0%NjR82NjգmkHT٦ ,k=I9Dwh=GRcSG,MGv7MXΓi0z(pZ^#$9 Hb0Ƌb8?%fJm?7_)Z4i5m̑fRmUCp!,dI ,S(Xw퍐Qj0h1Rx##EDJ9R,-Q(jȪa:`$H8~ $ G%qq~q삺v_whm-/وz1rheOmuć[^dZw4Eyj `4:@*T"EY"3 ZB=S~z3d1>dEkwg'Fi2»@*7u(;Ly z+B59|N dO%ySWEPP74}q eyWtC0A 8C߄K\E IMlֿ86x  g:L'zt'ƓeL鐮o*p~F`H0ȳ QCuͶ8q}s0;`8]vbL6lLnl`#NRivk8 A鉤„N r31TM&[4uG M$T}0)pjQO~翮8oI2eO~A&{ѐ|]MG,`x+4!RL9ֶh>bwo*jJU-M8iYU=EU LoX.*zo}4MX7M1cML[5ܺunjtMScӚIn†v8p<RAx`9]a'ȷ>Ͽ[f7T%r@1'=_̖ ޼~GRbIɇ|3Gxl1?ruі:7Y loztCP,Sa73P$5>}ЃcDGUbcoo,1*wh0}˨̈EB⬢74H)˔( 1 z$Yh)5!mxkڐ$|4P1 &!٢ =yI(gՙ5Nf8bPa NR8 3 찢(#bHYW <a…hW||デ)en,[q5^)TdP¦/!ԭpz\ g=oV b i)HO^DT9ZhC61/8u1@#RvF=tG{mQ"ށ-5JXW\^3/ISD0qCgS:51q&Юpi%}9/^dn0 ՖrG6x><$X'hAw^u2V[I"bӚ"챳;aw+Tk:q7Lg V5j8*ڞ^G9mo͗]6j[C8E@l+ 778uh˺jY:VK"xh;wnqqqE+u2软gu~lz]bgkϯQEE^hKr>%u[v5q*#"7X.p}"7x>K?MZB Wku;e۞,O1Ŷ=MKirYDxbeuHWA^>έG;HF#y% 4Bkax%mnl7veKIp0Y,ViIZD%E1&IX,$G?7߼?ɿt՟"{Xڻ}`I>[+vv1pvvJTy0Z"d $I8`l7j P!H$J>\e08#I9IRcp,1NoA>(!RCLi ȱ'i#Ҳn=6tF5H TTk=}gYw$"L:5HѴ^QakKve4)H*G $&FȌ,%ǬV(H3pxC^aBADP#cbqĻ 5IT mp0 ,zC)u&Rl߁Pnޢ7H4˂;zҶk (BlƘ0$&$xB@A:{uZ-"d.PBѷr8Go0 1']YظnXeDa#;HṞs~uI$s>7?>O}D%|՗O,7hxD?3Kd9;jczg6UӘЮmὦ# nf/_\rvSs+C~u+5]nxź/m3_h(G#&1i`k_1I˂u[aEd2AE69dk,*LɳvݑD*{WKNYk}|DDfD9VVT*ɅAdw;_};`}a06467 -m ԚJ%*爌Dy߼&_? y9{߷{\__?#"XrhHMZ-F;tŊ$ *kZpH^΃:JIc.Ux UK EwE1Z֛LL&P ].)+N_BcF}<2Xx{IbO)ya~,(g^c'QW+L3'H>=+n:fwgp3<~/_ba4Ӛ4e`x c7.i{eIg ; > E\\.tH[7X<W,7k3w9>`SyI荥 ]o!TB8'(75dYN%8Ƴ^mZ[X9Ż bi+W_ fַdYHXE|1U}& #d2ɓ'\,f 0D$ɚ/-f1(%MS+HIwv10*6Jޣ$0(ritC50Y IDAT.Fyg5Vꓳ"Ns(& J)vؙ;NHotKKnW,nӔd8=рH?e5/-y*$"SA;)A*;iCDNv\gh!MǼ8}yPd";^Vg)8cKO|;?$RV5MV0BwJD'DB)BmaH DkUٓ61 < 59$WׯfSV8o'˸)KjY )ᘷdg;xZ.劮/i ] (M`@mtXqi{4UI69:9䭷ptG*N_~Y3ۙPwHʰ{8& g7G3~{̃fqsͦ* uq4`~U{Etrțw; W/n+>,oX.ކA[nqs`47dYIz>e33c9ڎ(Ql^$)خP֑$E-UIXkL'dńqA[.hb4}/ It.i{0V$Cvvn1o=}a9wL'|w5mQ=O=mU(s4JP,VKV%Z=rӳw0bow8kQ2DXv77:}I__szY.!!H!T7rsB!d2awwOrq5gpϞ=3Cܽ7LʲjLAxP5HLK!iǻ0JӔ5ez] sl۳Ztb$zI,:=ydIY.4yh[xT0*`wG<|m6Kk(C mqVizW8>b\\Ʌq:3X1~Vr|]2Hw;3=B:к߇T$DQDu̗%q03̰V1ͦA!^_ͪhX^$IN# FXVMu4M }myƃ!pF Q,tJD͚x4uW4£l($v6 `چMuèRCܹwhzu-ƶ4Ųj-W KMi;4E LJ4}Οsu}ʺSs ZZ bJT1eDR"EfSRX:&]Ï>Dj~|}Mߐ$i#Kʲf1Xd}OY\\diFSC ̮-Qd4惇Ɲ7P]~)?0t?dojٳTMG[ռz 7߼ϭ[G2h+^|r2fi!FFOC}#z!$Y«gx0*~o$YnȨc<ٔp G0 q$qjj"T/:#V dz暟h@GG9Y`f8ag:}bbF$_3ߴm!3hWpߩ*K 3:M$\@-?{mm)Yx;~P ^FQշ)75MTκ*_,Tt_{;ο )J) Ax:.p1F94MhcbS8 װmICvg'Lfs~A1eggfq=uKY "z-X=gCfOoH LJ߻)mɋ)΅jXu#\^$ɲzk;wMe-0h RDXkfsW۪9`vuHgjN %OyJ۶L& o޹ͽ{o2_\3D= p`gNzt"|"[kh0bo/{mӄ"7riP&Dp8x0Z`d$Ά|P ̀j tvi ZKjO^mY9*MrΡm:G$V B"<:j `r΅m{/8}p髗TumuSÆntwj~;o/D2dܚLPkZ:$ODXX&ś0`]ШH֓8b5! T8xAH G ;0ahgkSV VMH()9;'tJ'q}}P$U EbAH898Ň};DI8w횛9U&$US"dYl~3&ԕP`0ksW$DCE!e|Z!嶊XH"зW bܻw} 6*i#k})ז4΃9; GGG8cx<~ ьd3!"S.qq}= 7)y.7|KV%W7TU֖%]yۤO|ߧ[zc~?a]v}?|,7 5Qulcz{ `ULJWye]b $ɶv)$ƒu#ElZ{K!yL{9,K@R M@[(ie$6uj$!(x0Z'M2Zse JBT .Xdl6TQ=k)G$'tbU.fqA۷wt7ah%"!M[2}\GVW納GxѴaogxB j4918ːighZ4a0eXT pӵ!78[1u˪lBVad4\];G]ik, xz33N⒋ i,TEnsrgx,yP 꺦c,{P #$G%BkH3x Ws7%M9{J#_t%jPPBK Fӈ(f9Y.󎶁da{+%皶)L9w߸og8/<}b<[R}͜gO.yhmWT5אdk=m! dkQBP+:pZHJp}}MoIbH"]@-XOJ Vj(H0LTރ*=:U-+I)w Ws|E>ruvY?wq~Aߒc?hwc1Ncz *#"L "εDq@JmP'1T"e{PP 2H3Q%e]auF7S +נDD$'\]$yB$bbgCFsxɈ3n veXi5L35(H!2cxqHz"m\IaZcf}֫WWHRWKKkpa2eX<=Xs˚<"A)iYmFFӷD*szMQ()''RW}ghʖ`i=IEɶiILDđyO =]2 P6yh&WKC9æ  m-! +$(4&|m'h"*Td0bƣ)q;B*@% #6TQV"hīpj-' ;d<1O/8ci`\Cv Dak-<[02MS^4†Ou0IoM(W b1/5!|9{x'ZgBb⣏>ߣmkrO@o9;;#NbȻ|マa~S?: a^X,7988g([X,?x񔯾7ORxj5+4h IDATru$*z+DP) gq6k8KJ n; }A@n8lp%4\W`$ ˆbspęD o{Gad5=z;PuDqD7`G sU)`0H)D@l:Z`tIuB2 K' b{8+8;֠ !:/_>c3fgwzA*Nn:v ٙazvssP1"+c/Ci2Exh-*_Zͦxg4ߦ'N3I3i8EZꪥm{R-D6$ WWWU5.ȭǘN2~bf10(xL]\^^Tp0Fʐe2ck4`oA#g!VC_'%] u5G'=82MݑZuy=]S5O,kY,86p-!iG('<ڛPs^F W1Whd$W*@nWh裷;KSwst&d\FJ2;On$Ц7-iS sX.oZxt]mCX`?`zfu EH|fZ aggs[hmmCzP$P"UHh #kMYo6'%2FTM5V;ipt}K[R%|[jၾ Hq;o&u]^*,g3͆/<ǽggX y-Wqv'Ͼ JVw([C!UQCŊ"ss@(M DCn>cϨ2?mrHRE1['C7<~K; 2 &JbLH;!_W"TNnMuk_qui ?ʯ~u5Usy|E|;||JϟZz KƓMۃPm,i$UaqAj 'qaH.U8Vo@mS $Y"1}zYeCfp+PIHw}^zjJA^ Rufk+ 8lMUMHy-B("cqn.TDRn%i4NǴ} 2}#cXo~C:FFm3/ٴe(ME4tep/$^ X?҇*x̳ڜsr!mQ{w(TEH*c*jax: k]٠`tCȓ8G9#-Pl]Su-2XZ  ER `mXǹs&r$@o+H(V94d3II^=%en6x1$IB[ʔ \/ؔԆ_R0Y0_??7ܻC?yWx (ཤ=+lh_]G%9di=1|s1 XPJrYh( -qu C.R>T @(b0vAJʲd4 Rux4m%Qa0-kH?pd[ 5Upsu8vfz½^vx9 wf %[к I^,7%qT`# blk6K>HZOfS W1\A-бXB߹dG1&$QauD(ڄ`awC6!Ҽ8ʍ^㤡dFagwĴrmxmh˖T 'q}ū*{ ^d b2r||HU6tZRl6 I//̿?FE'R6)zC&D"dWeXpqvEx ɄpH1p|tpXpvyǏ9M4MxϞ@7,ﴞ5p"3"2r.r5r[4m$ZjqB!7p-1m\\s9<ӞXo?B[޻HE(R'{{snnnBc(U2V DjY.wºuz=UX~wxؙk/~UMqO>}Donyx8a2q}Tc,hڊkzkB\__>w!X'1q-9v| md4Nb NЈ0 ,K4[[[6cGYK%lHcBt!QTB/Oe#HɲRB`W!ViDe}4[0:H֫C8`0qx{;]͛7\^ m[ĚFii,H&Sx iy>`הM"ԬVdeǓ/Nc,W/Y\rqjcڶ,K0L1;3<"eSX }$Wg_RUKtq;U?Y..L&ܾs+v&:}Lg¥˧W//ys>zD F㘲9ճ=͙LLOydYF] [alZZGцKTy${$2c궢 R&#}HjZ_}R}pb0][Dbm 7!Mb]ѐ3X bt8$EjV0>xk 8"H AB)nEDY"T{jS(m`I4"RseC>sqqrh<,ę`U$MM-Jy {Q2!u`ij)`Z$eb(g'M#Ak<}qYBZ " Y!wBmN|$qi!YV<}vi* < 10a.Ң8a\svwi jEk,hGO? WR7~c=b4Nmx3 f3pGKSʢg~m Z㠭[t1 Ehf`B"$3o0n"EKTG:tF,R]!fij]jH%1H}gz H o)1jBR ;wyC_p~rCx4qto?W~=_/?:ӭ1"JpV5gI퉳sR3_Oip] &(uqԤQHXBhXb;O& [[[YZƣ)8C]e[u1e1{H盘b`ThѵItĺ.%ĝb]q-H8OIޣuL)J9<u;6Ozg?`<+k9b41MIF4X\]MbeE1"'E^ ٹb>S+8Eg:MiA!gyqz]GqNizh]^3( ڦ"N8{ _|;wI7}C]uTe?!saᙋ6xۇ8aCР&it0u5mcAۛ`=Rѹ֏GqMrP2 HxAԥc265hpI tNBJuڤo@=M)]ݱGpi]OXS%$eho@е!4$j8)+$h8kd0͘(!UjoZ]o,ƈ +L#`t!"N*3|`(7+ ZA>T$?8`kk7,W.8Pk")xj`4 eaPqD֋'F,(-6uII[z8j^uGbmV1pޒ$ -8cɒbg|(;d> 8%^^i7CZ&:zӆNq0'k<}װŲ RXrIedV r䃘qK^2dzm*j8Eg0Lx-b ;mi  mYwhha4(+zkGn|uMk 6Ha;C"ӏ?ewv';\F{AENXѴiW ^s,p NO47 d`8˛7oÐaiZ5ILz5;*2:g8ai*(,j,b0`LG\ٚf=ܦ 'g/ޅ?~DS55:IMOU$>ah?ł[w>޵5 FQT-Q<`{gLվa1?g8cg/^c=d,-kFu qlIuX>{5Mc Irn8<wuk}<{|7QIؚrq3")XՊժ޹?3W/Ѵ4OmUI$IфŪd 6W:H>ʣ_N IDATb\]ip|A4eC[{֥e4\~@n.?8oAqB"&ލ-ig;zٌlְhB*MxĊ$B dwgr l?=ź #57 wo/X.*^8r՚W ;ܾ}w޽KϘsڶ0 ڙZ0β.~28g6 ߓ 2 v[bLOTU9, pGtDb4Nd$D74M:,lh4a4;1d6p'zf.nX.IRp.r2 ۞rxX%i6n''xwnӏɣGPQDUxo^rzvFYZm;l6VTeO]&Ctm}6Jg_r|zq4is i>"ҸR ޹O>G3ݚ4 /_>j>6[ng:ݢ;g723 $1[C\_puyz됄86u4]SHۻ|t!x~ѻ'Oc;t{GtuM0~Bi5ւ%]"5=8QTUKW7&IRtQqR0_躰imCGtD0 !ٹߴDI"b>o0]H0"Tk$75ޅpAxwUeGc]nKM9~srp4aDؤFZe /H 9?i!$i!=uX]#m oZ"XH%jHS)FQ Hah#̆?CJpOӔ4ҵ6 ʒD lMHחt5ueA$)hqGGG~g6P5mSRWA%[nn*&! 1lf1۳-bi%ak 11(5&0F + zm$3WTub;޾Ûל^#48"v;꺤5=B+*fiV놺c~c0#{{ܺ3eyMCqXH!Hz꺦,% (ꎾw;mK(㏏O~E2iI%O>} q"W_R+{GGx:˒O>a:٥Zxk)$"͂)nTa6d\Q ʪx˟*$g)2 /v8O`XI|[6$&p|OUlcׁ0YTmOc$'KGtrx4f@Rp ;?? #F-F#W ,qF2qq9j Jd2dgw)IS|#2&$.Y8E<пLGYb84l071aN-Qբ ctl %䠷ßu.*YjV7!R[Ȳ9 Afܽ;wo=B/.z|qM݄b)xbF{˚jh;Kv?,m_3N^zc}GE%UAY-Y.j޽?oc]zHcMbrr~q.cźf1/P:c:уǼ;D2\/O~)}_s~qBuMQTx$q1\9==!φmz]X?c.xfP8zpg/?狧?$J:c0" iۂ(ZTuEYהEgYx6B*1 [1~2 N5J+uT /ZCۆqC_P}.,úu:TJ$I$ pbqp$ {poz69(5YG|=t}O5qL' ZAԆ ){ KUG"&XgQDhT[DaMH(C(p6i N՚=M۳^^umBG!o3$<eD],x&ctHYt}EU/1ź9ubu ZyXo-ey[rM]/ G mWpvvtl`8xj]SKv liY.\]{"e'x!lӯ$IT A|oUQa|H;Ï~bL6 "=OgQ2nX,X}ﹺ\ry` ܹsvN Kx.g2,,R 0)`}߅ :C , 9B:ΖtM`RG8e;U"Y:C Y(O~%s>,V hF8x&NV+C֎hR=m·i kX-{Wlms v,+t$0Cmrb#X,'C:qVg]8!SvfD/ s'Hgx} ܽ{O_Pv)_萦i\0_0LU˗HyNoxꌫKGoJ`?`4)uE0P+.N5XwLٝ7%knnU v![!5,o[0wGw?/iI$HWg I8S 2(ȣO8\3I,,M0RDt2LDK=} ,$/p#yOGuADx밈%~ux/ y>Oz}w>į9y%$:!$I"\zch)1}OD`DF`ObL   J V$ILys5KIhQuEJ-ZEMx5u]|ml&Ō_)!)UtC߆Ȳdc DJx$`I.l=`BEaj,2c%J(NOONq#[3vᗿe0nq7'O0 a2Ih_Rᄢ FYR*Qڐ{M LE;]R-Jy,aզj]k`ps\i/< TD)CC0;T(!HӘꆮa5B8''QR:zbq%w/ MkޒƠeH #5I3sueey'J2fq>Opr|z]Q(N߽\oQ?'ݘ1iN(ÌbYbmt2& QXi%#/$iGQB1I7,KjAABa8Pb20B0uDe>'T6pG$ܘxM32m-V )#eDZ1&p"Nώ9;y< Jez"B=Nc{wwz㘢|8S^&!0Ƒ茾pGXχ?f:r } .XiBu8(iHF~ۻ`L{}nQ ~2f5MSS4B 6Grk6[;{ S+n/YknnnXWtMKQVsxp~[?3Tg<}w@@X)=]ֵyI8%wX1TZ7&[h9M:HU8-P6&k-mW%sܦ#;<0xIH7ad2"MVIؼj]E[\xz}簶 qEf]Yt>aG[mƣmh8ܟ}8>$@&X(c3EeIA/ɒBTXhj xӴRHmVE8i.D#BXofqŜ_!bw}jz#IHDFaL9*R 4֖F\/.@Z(V]M] &]s9O_<#8Bз G7*vwwYo_Ƃ€#'`\R0X1^$xݛ#%>6 kv> )H8by3˯O+ÜzV_!<5Y4 |ɻ@ux_71!{'so,JElo9q< ծ 4mM׮2")V*HFnZAk%fρ5f5!=J{)b^l-h1⚑w"7]VKp!`4]kuhIatHn{\Wt;rț7 ? ^nO`T~ t}%4xO[ۑ9I `4aNs˂rе;Gxi/^,.5~8}5Ԧ 'Ϲkm C,1xg()K'9(㧿)`<ԅQ@jsssC"B#TE= ӭ4u$QXdDZE%EN!!V k$ukpޛRk(Ih6&&3$J.0ZBtB߁7`:ApV1xxW̯׼ˇDί?>moGIG [;S' lߢtI0 cA%2xӛXJ**DIDғ q+fxox{HH(JZ#%R$ ڏ9ip@[TQTMr:7H-q)%,@ t6*< {Lf|՜1hsFUq攮3b5-iZ6),uw[Ūb")ɢE[nǎ:in$de6A M, ÛIA @/ݶd[IšXӝ3w=,Oh#Q;~+Uh4b22q3"ckz7pѐC+%笫Yn{qmgogƣ7&/<&dbd9:0EmQvT108?DH_1!ΧáRǻL&3Ɠ=bχϻpkXrs/&M'3͒S|pZ!$J'ޖ2 Btdqan:F!ӐeѠ²jrA% EQ0M"+Va]ŨT)@AɗSpy =Y6b]lQe S*Ձ26H5=0.˳/|JGlF3 ieZu; b@xN5Pm6ִM ?NbH繾"!4,i53u,+hoтai躆)ZKPWݒJbR$ӯtwyx3NO?cS^sͷNDt:K͊MSo,Up$d#`Znb|>k,рoܹ 5h鋧8cplpW_;B^<ӏ<{j /Am)mGItȔFt'#|hȲ@Q 2 GI=J{rc( H>p+H[ET)qE]UAٞLy͢m2&ђL :v5go?_39~G_!/>G '?ۥ)x2>Ϟ|OLLS¤ [-Ͼvh2KȱXN8,3dY dSEtH>xG 6(ipdox\ߜam\R D6E^kҥz8@+RX"ĀQbS%LζD9=y1+!,W=)3;SÜ(Zvqk M":2S$u%S ENY(/N"$>O[uYӧϙ{|/1,ONNxNgђtx2pBɈ[wl6xَ[FtQ÷ iP[|pn[ i(*,wH)L&80ObMSv5wX,iJ\ (uC6YbgYNve4 ײ,#3zPX0YiJ!rd&-/BFBt.] z!b}$sYF\p3@ tkrum-kӴP514dY`3ꊫ[EΔ!.w;59‡q_=aGw0L}YAo;zxxyGq4HP辧k*82 WWg\/.8;Km^7iK988bggp1JHs?!ZhX軿XFi4"w#zy&6Dfi;"JЈ(iS[`0łV> (%c6;;!Kڪn<@ݤZz R!!oo t]G]Cf-mQF#wd-Mƻ-{MVRPd%E4}KZ1ϧ욋g[DZ-oABY(ܐރ}& gXWѴP|[W^{>}no,(+lPs|4eo?cq}BH%;!?}<Idz$dy>l_Slq,i0!"y Áe& ߒidT @`DĆL cGp} ? =CѢTN ASܱ^grؚ\#8IpLem{w7σ9jKJQɊK]iէ0xc򌰝pQ**H0}yAnk@Jik$ vEEB g0.89{|gIRi)pvoSq!jh4`6Idl%5eo~;yMU\4OcFKoTc6ۮY,9sp^Y.oFQFss[cZ)sB\^Ҕp q:S(δ(҅֔T,mzLLr!YQ,"u:stl6{ygϰ֒d(Eo 6b;;%m[su}W#dmiHP aӓ |9Պ_wF@'5xgd]]ƝG޽HY8??g\s}s*5AW~Swcr] eZ٬syⒺߐ9i044OY^lQd;twiU  v&%e.imbwi8[K*:=ʒ\f[>fpY,[,s|o5JHdhlՌ1d i]k%mg "[#\&&HmC'~d3YbK9kvITl6CKuCvMݭRDVJM A- HYpqd<ܿ@QhVR$>V%1*VEQHFrl M]^HfB&#HlW"vEE( A; `x[nga$)!.YV]Op۬ |QdYu mKYXo */$ɌIkR^{ f=vqð,^ՄuzMC<~8FfG, 2-Xז0R+>=;$=߾_b2?b(↾sh^nhh*88|s\!|dtgGr}k2b]ΐ&<)W# yz_Lh< ȶ&΢%Ogm QISl߂ aLkWޡMIybg3^# = c"(5g(]R!=Yf(!ƣmw5MqvvBBѻĶsʹ]x>{yA__o0*6+N?d눢'^TMmYZY/֜2 qMbH*h6-Hbw|H!K >#ٟ隚|XW7+6mJixg'Qz&dl6SNOXZcd2WWWyx^9KUܬoi zT4]˦nP@4V>" (M=NN3b2^Z"#Y^`mFtnUZ4-7W-]qYqz"a |[wM{刲,a:~7^_{gls_;&ӂzfi6c X1=ًud:"mrçAcH/4<BJ$A:(=b@k'RhQ,/~PdLX-wh5`0'jAFSW7ihV_e&w `t%rdz?,7<[/?w~OC3MUC^{*NquyVdbJ#c8qDH9*3>M,1Juc4-%02ToBb=.$#REk)!{c&79qJaemExk;d[zb`xq.@x"( ʲd682Y@d>p}ݽppfO~_|SB e|:GJK@`hpzޒ)=Jrl-u$@&2k)sF1GMY/|L'{)lWTg0(5RVR"bGo;q@xQـuMP+" gņd8an!*,oVl5`Ã= #IIYдK"K5+W;EQN&RXzzz Mݬ'lͿ_yd Պ>H ;ݛ d͓^ }ߒZz"tf&@{{\ c\('1i:vFg]^uv $Ҥ^O&*C%֚9zuQCg!qhT E iܲj3٣r.J’`M/WWH=L6AI&XzUq{}00cX3(G\RdS6U &>BsYLc\r~udOY}: JDQjlva'3P2C}(Q:7u!sTB!:v>,U19xx.Y77 2nk V7d*`}078SW8x+o/Rql8ڟsxxȏ{],cn.y"LiM-{;{\xo׾Όw!}1!^\]\snMcYys-P2F9{G: )[!eTF: i!hKݭ"霷Z%~S_5<=ƣ\]]Q7-U6go=ƣW7o}sV+V5:8yqJZB3?SEIp0|>{KӬ.U}9ʊ𗆸l IDATUa1DGm#_>}jΧ3vw|'3&&LAk Q t}و 19~)uspL[QO>M"Q7q;,'lַ~k7K\}&0-;rܹh4nݲ>qʢN8l)xæxL!Y"Cr af40*S+3@Ԥ B>2x[GtVۖѰ\lЌGe l-m=&6E>b/?~o}W|O~wo[kzL9:<&xx&~YtCe>p"}SnRU3H,2 6$pޫ*M\L/% ;l^q;x?` gt])r1A{91Fj0)IWC IRE79iWj0&C!(!hGȫby{ryIbPS (]?F" CK{5Hd&7AI^irLf\yxuڪgv;$@ h=ffAU-'7"C1RhK BI:|GI)ZTɄ`5Y"]Bdbkiڎj".!Qlh7B MSch-fMVxoBHm uji{OTL#\>zK^f85H<'*P` MW-g.0"_G\ |HzrV:+m=mp֒קEQPRxp}*A{nk*umW=TjYl@iUGgV+| En'#L Tg_\uF{8edrp, ?|BtO߇߸ᨠެ9RjtJrXÊR]\)Qɖg`8& JHlQUM CV¢lg=wsf(crMt[ryuJ\S57t]dfLW7^u#QVIYAl4F9ݝcwZ[6:Bt]2=73XRx“l5W+ GM޽KnٳǬVWT-͚v)|>Kί2xQk<s >NNNhfe X!fLf[IS#gy)?,70߇vvnj'% $&{ඃd 3Ll :%%Q"=CĈQ i\tx\h!mH.Re;X+MDO m]SdIPDգBс)2D'nc=hBO>fuJW]S* CF:񄢘Pͺ(Ӌ7@RK.&cV[vwwٙﳩqV&(`d2cj٬1`LFKƃ!uW#V y17 ڱwĝC>'\$6f[ɍL X?y?"7;MzcX`PpqqE>c:赇MwImT_b<O۶LNJÃ9enx 錾mAHpɓ[>F+BCoރ!9.gfJ&Sf=MgۛsrvGjmZ'ˤe޼dHL='brB@un 6ze<, s4ucYUј2h|8u'+%Y  QMR+y ڳ*j83e4H,1v^\zK~zCZ'QHk`gٻ?/9g'3(']iM7o!z 7X/NY-oW2()[!Zƣn;Ԩj>Ç Jtj`8BxlŊKTLo>c>338!چRkZ$Zdvp( ^A*ٝpvO?}AY()BtŽ1ԭc8J@զ[%%ZKJ=o:5 D$Tɠd:Yzrqكy߿h\ޥk+j-}g) I~M#u oҥMwV5@URmP3* 5 ׷-i Ϩ6G) $q]"/(6ͶRv40$#Ǝ68)>2nR"J[8;}NdgwJnZ!crǯVׄh]iJ϶V f6١״] BB6 KFC1|HAYn'Q"7I=Bm?ٸ6MVmABJ!IE-9?:ʷ褩 !]#E h9Bɗ+/A:A=t}_^Ho)A6M6@4[A ,CpȐ,9b;Vʐg16YȔhcT[ݰG~LYiҶi[HfL)sk=:i:kYU Jm+K]=z=Ȁ=x ^ux<`0Hi~A:f]J^{Mي5u |ʋ8:;A5pbS׉Q%HZ>LkCݵDyiQ4h(̈ALStlcшd͆A1$8ҎR֛(ޑ"RJzNTdimhs& ,h6+zjCpw}LTZGWT2jqtx#dS?{Blج/"{~{4h=`4:@J|wh5D4r;MSٴX{08o]&Cw~?P/o6[Fh'88?dq[1m^{ˎ4M13;# sά*TuK O鍶ZIZ ZjPݝ]J%$cdvo(D`x=y/޳!IŇ˷CKdղ섲,(}o̦Gل/? FKNO/0 `Cߢ Khb 'Q%J!4]KkDX|>Xk:&g";klIBlk~Rd0ETut&4L&pCYY!EdsfK-l[QIU9R,-mtLz >M]#@5I~ =(0=J(D5ׯ__9<< )2f3T!DG].ģ ]f"L;=ƳL[zjmvKߓD\mQ?s>>fdqw=y#UEhnAX%XU*躎jjuOYWbDI}.-Kg3>;#@0 8=>k/np&rV[\^!I t[B%|mwIݴh'Qc,,@;S$EG`=tm o^/Xo"WzC9!^ !n|YIވhڃ#2V὏ SJƳQEn"s6[PN%RąukBB`,Tlm I3N9884Mׂ2DyE_~A?Kw_5 h'LQYo囗7LqHEdvCx!T2./jQ3EBd $J vθ{O0ɑ2ZUU5( BL**,U޸ɒXRRF5f1c:g^ Y@QfZA!UH,G4MXnRy\ U:"{6@]U(hc`2`tmKVĵQƨ*YFT#(IӜ<,^kz7`( ڶ{1:d:+i) l6Ic*)?dv4͖zpeNpP"0$CP1ɒhʱ6"|p}`xթ~&^@^J[wx#ZhQ1P$yAZD}!3mT"5aadѰƤIUN߷mBDͲH<&kL,mt7]7YV0tBeRD^>78*黁Q]PUc6-yZ6U]ĴjPH%AեIY` ]~,M1ưk/v@V*"PhGnn{ !z޳4uH_2q[g<2j$8\Eh*y|zu-ÇrɨJI 톲NB`<iM F2 "'<ZD\}rz|?S}%*2mm[ow, L!p0IcHXw=Jf k"𘲬8>:)IEY%4{qs۱mFѤE.^"bJg5*g|9=a1 ฻x1CO߭ 0G.(bBYNӟ82n-6 &ZGkè99}]ס75W4͖ w}@x>lEm9`MLI3 XibG{OEq > }ĊwK?&Nb0 m}y4DnR IP4ÉZ|}g CDh*2I}xBddW~mVtf68?l7j<e" v.%9@7h6뎾Ã٤p xy9.ž(lܼ+K[?fVJ@ yX+Vۂ#.o(C(?glӆjf#͠֌CmO;nRc2~qI9GGsE%> ,Z@^T!}(G3 ځ͓'TEjݲ}wlhWG<v~HX.y!)`Wt1-Hd KC6.9G=mi軁&,W fG7Y_3?(Ç `_;OϘNkw ]\ZdZk^[r\ $b~tm̦St1&RTLJ9z+zz9]i&M8 \Ǔ{+k8Bd\4ݖth\o`F;m@ xMIAXc*JEGɨyHBE;Nɳ ~OoLVQkzc\dz2WocDz4XM]N0ځ> b$#9BSJJHF~ !>H{N9Z;prrg=1<fe 햾m()N0~ HpͷR5]KÈ X}J ߧ:@ƍTH%KkZ}AP%I^t*@x.LƇӶ~JYv+buJB9L8::m.=_.ʜPR"B<8g{! cLY9U~5]deh5S*%/bh.hu  { B췶q>LdY"IGrαb:PIB55E@h,31J%JT$ $BCg)ZHλfb`ޡE;<0fلN:db1@d U $~YmMjT F!d._!$Ed6 o=ްٮ<|` bձn(,-O%2¼4I!dt0P#(SJPB8 u’)~hX5ߵ/\^ϟO*bcIc>YAU5yV!4AH-j^~"n} BB®N!% z[|0ԣxzJ|2`em#IcMB(c$I9=9$Jf:c1y(ǞMg# 6Yl[JCPYIx#9u5fW숾PIsZ.d̏y#<3jc܃o MGNȰ^#"!K4vatgQzqyfj}1Ͱe6j*ՠ,<!B &2̩{7 IDATCH;B7ƢM4A%hh vՀ(XIhkhڎ|l*Io^GVV}k$ 򴈟Y-ȲDHvӛ2v)ʌÃY;Di||UU6G}GaW$hldgeIkcJŅɥ}" MVPjgģ|(|"y0~hfx2g> iټ[뼴h Hy;޼}ɔ]'lkTqz/Y'˯"=*݆Kt> 5>X$EQ%ֺȅ+{TX*()RQy:>>}7`l6[:kAyF{۞$%ٜw`MbJ`>%I>=Q9Kԣ $J Te)>֎眜P_}mۓfcZ-o6qTc89W||trus݊{M!QY?oSIVkfjx9] )ywGFȿvC$UUagےP 5v:/bZ4Yh;<'"`6˲d64ŋg?a:n>ܷI'c4eH!#.-8=9A=go7ݖ??7 ;Nai?8O.8GfՋKogOT<_|C1#Jp^TH" ^kȲ'/)X$q8 nуeϸXoZ3+:'OJV뎶 󂋇.V٘nѭZK9ۿ c_7tj,wXRՁ4W͒xOͻK-g"I! XAC#dؖOpROd# .Pl )gHS~w6EsF7 nŃaZsX ,8mgLGc ͊~YGBLUoAP"vg@J4'I%D;PDIKTIw)Ŏ9U9a0@7R5g u9e겤yM888!I*}HӔc|$mK-E߃#fڴtݒx+JPizBx=MiJY^4 Q2'U&n|EdT9EzZLKEmAChǭ0#8D*!cuhTa݀sV\dg.}a ]d c Vi U1B*R I/9FPNY@xf1j0@3ƁXlK!B!n-<,s82pA E_q%E I2iEM2D$yBQD 0XmlV}ܤizP)!m=->8Fm} t̎j7 7$m nrk!u=`6;nozfSAUT$(fUT&7ш^3sss/ӯbURGJpy}}ٳgrrtJQL8o߾k6~XclKQ&|c&G81{@x@%qh$IEiTAJωw /3[aN2dYl|:^dF6IDT[;Gad1n#=C{( Ɉ*/nv-elmg9c֐HEO{PdeI߭IUoC2RJFIh;Mibi TH%%T |TWkJsREEm´ 7/1CՎ_Y_szsx4fKT^qwͶm5 "#G; aq_(&՜Q=gW|*9<8aTxp~/~9F#"cCMSf;ϹZpuI+fΠ&/*nc3$*' |(V1 41^Y4aK  {5=CG/8x~B=LӜfQW)TyAYE!pZ2'`A899ozz) 8?/,N0V]_w",Em?3c]K,6.(4`BX}"HOs*~?ł:ӂ1j~3UTuy|f_7O{&#)TCg hwk3*`D`MGi :Ƴ~h1Ns^1+ #:vlBh4s& R*6pVbhO?:c:Ⴆ:n.Yſ$#+sOHSAZH޾}U!.>'ӏAr{OyG{\u1ǹ#O0i޼yGY֔eӧO quzM"R ?|L]h}߲,8<q}yOEh*%xgBHI=MRGtM#\@nf =8.Yr0 ww[fe 0}C1}'S"D2% A=I*&L"+N8׼wG78iAdR$h>v>u5hk0Ab8/Bb\@wDfv<>[-鞟 dF y__?=I C8N3uP%~ J)>\]te}#/kۓfB[ ǑʔD ER1V"\ǚXщ'}Cy~Ÿ_ eo5KڶGYH>y#NOOH/^;.o.͆5"@Sc;-׌,G$*Dx "]Ƚ&(/IeW1U Sz)K~对|>z(!+qv ϩF5iV(3PQ=iznѸ0TlBkIZ#Q̂b%bI3&RE6*ȳ<;4jgPJ/Xk|H7~Ddz$)LG3н!Kc&M YJY {ڶzȎL%iFx!y,Z] b:HRu=JR*ȳ:μ`>7II l`q N (Ҭfa;&BS]ԻODV-) 8\GMseJYT]F]Lԣm=݆$I8;;&kW3*HP**yIZlVKMaYn:܋,#QFr|qwד[o.u:n2O8R%ɳiL-.4#/!;;"I@;6EF9X3srɻwAjy֤͠*T"ضQ.P̙9[5zE{!G1OTUt:gyA刼v|z/~m4f:dsF1mPwvqūW/y7h|6>zqq/'$ 41>DA~_]3 ~ۥ^HDP1cS%yU2hjdH6j]*V')˜HXC1u:aKkmg=2IW}ͭhGUd4Zho"JW4M"Ͱ2-Fw8¡R! GQK#eRgНcq`jT 8% p!&ۦGʜ5 =䅠2Bp0ĩsEJhJl!C^+ndL45uM+݂@$ݭ c-Y^PP){5-CJ'x'8:<윯u碬%U%!U.i5BB[HҜ:3W{8>=e; X.[l\$Jv͖c&/89]J)...xbMU'u Z0rXѬ6et3(i]3ċ{Nm4=Ʉ ,jv]v1h }68t$>z)'/K\W]kmf㯃C?)4,,W-]g= Q8$i>:7l90#-bj%Mźͷ;<ɤm[^wxp~a ޽mL(mFM x}ߓE NC(?1Ϩ&%1մ]`)Rk1`%b/ɒYx[14>qwe6hxff!<>5y_rlDDع쓏{s%BRU-td ݲ Yŷo;\hqb@*!Wb{ug8^$KrTV=:'"Se9i _.Aw2Ğqbp~grtt$ɘϏi- PRA>e"T@<2 CW"e4XaуSo>Z/PKoH2E%K7k<|xA^Z:pxj}M=*AdIt~B\]SHQow_3;G3N&RF-x}p}=M Q )2 g%i2b<: r9 O89xʂ6dxsaI8=f0#[we!_wo_PJ[vt|HVV^8&ィ͆-z54 4| 8S ғgI 0-(2I$He{@`k)pV BwĆ1,k15NiBYdY,&Zڞhv% ʴf\CpI=o; H%ҌY.-y1кg^ݭuTUEeݞќ<…&tRjIL8wt}J(u;Ҥ&MFxAx"v/} KG$(1AY&q!SIA@IUD !+,5%}r%ޑe[59>=o؋hv&I!!"u)Ϟg5_#byf. xVyxe]/?o" O=HJyMb;t]&hFʺf4r|tJQTHÇޢMAl6 iR4GGGܯw$eiZ,e裲{Lec~_p~~1tW/~o/Q 4⻊svvgqrrD5h(BHI/#ZklJkbDT#nutNU-[Hz/րt'" MȗyIW( Q>ʲ{Kۛ8<`cV%~ԉV"a2Ӓ[vIHۯYcŨ8A螡Hd%>Dx2F-,Ypg/x)^i֜?,︽d4iz@gX͋g de:=4u$yw/w<ɘTNNAզ'ST9 , Q"y _/^Y?D(>1ir=X#<} |c-d`4S ihOj.K!5w/o_o*e~r~|GϏ˔-YhŇ$$xJ)nhH@1x<'W lK͆4z-|xÇ\%q]2D Y=Β$\` 2p֓+F&|?p{R}_?gt^&(]JIk{DBy|rfketAVHҌhZǻwbMmIӄٟx ͆'O988ÊLD>PÊi":F8!/RRY1OSNwxr^oynݠTdEC!L$G椈!@P8T@]H AbC07U۽|TB+%B7`\|vɯfiy3}72#+#*+ XHXr/؁$#dbaY[B d2"Ng>X<'\č{}ML8+>9ҁL IDAT-޿_sv~Zկ9>?_oA7Ԩ0Os0!NMb9匓woFu({"PTdUIp($t?~қ4cG)6<<"e#J[DIFgh ~0Zq\?뻷II#ɴ΅/& yDSL(3ZJQ2k=C. ۿ%Ŋl1ơEc4c{-4uK?t]rtCUuR$JjZk%ӥPuj*K UQ0]mK5IE/;8x,\Q,Y,V踝GhMYwDCʖmRk(6hϥ /`xKGo hJs%rvN[vSݔA%1,gZ8j~>> \b@N)%I>s4@f4HYwhSX0˃++24[V#a-y^01-X-躎<:<m=4 ]XOOJ85'gHYVMUa-qTeaE q'Ms|E1Hׯ_gSXWQ55qj9T7hMJ$T$"H%I(#(|]Tȱ5<[b d r~G3xC5pzv{/g: p{NN}ah}'b~햷oVG9Ϟ=_z)Rzg,+r7qgg =H"kN_|gB"j2񓜋˂PX|Oo[m@zFu=dYD(ѵ5'G9f{ =kCJOjkšjb B w"}jպ@(I7 n{R 3ZpxcpqfZ<:zP\hT#ylC@}9[]VEE14 8j3ш=δ kK#j˗J9&I"xIڦ%D1!Q6,j2mxSNbzE׏DQ/>xyp}oqٳy8[^yE׏Qpg(:La]ë/?o T /dEEi޼ `6 c |Ο)4f/PFZCEa'CVE1ދ ӐἼ<9"IBi麊f$Yhlnhascb@E,!R}X8fuzT9 !5YuB2 o|]cW,OGZ^~ >\%#=bAWWHy%Qi{a]GK ,Q2c8 ߾!P%X"tm6CK։{Z+=h;Ac!#:V8T@]4MC|3/\#\&I ҍ8*9{/I ia @ZBJg< w/_$MrN"f{GI޼ K<9i ,bl1#N b4u3'-27vUW\߶,`t5 2/1=Yƌ}kzF uu#}Ү!Nk_G<&3aƓ'O8={c-em/ 9'geDӴ4},0&G2ô͞E1g}r/om t kώW?3`RcG:)CȲlM7 > S?IW8}o{yơp*B1)+2c1+$I/ @ pz ( ݲȓD1|k?m?]AV2RxpH-yrLL'klzkMR8d1dG$&lu#vtcX p띟G;ק̊5[N"p6jӏI}_SV yJ0#Qb1 VqN4ll AaeYbǯ#!ʟ ‡7C RHGD+`ue[ 6ls(eCCJt C?h"DZfiNg{T 쀗Q#T`XD(Exg&N16\`(q0KE)KKc^ht8<:xszStjYψ3*Ғ f Jiմmo}j*麖Yb%UYrڞ(NfQù &3<ъ,h놮)JDQ|}ac a)A PQrw](08(89>a5[ ĎU5N3F7b0Ux7r4dža0tCs#"ŽQP{wp:PhCF*QAJG:8Bx'${WgGAn4ٳ [pMٔene}|JQ4UMx@&,48n (֐&Vw(%Q("R4M&5\"c )$G'2-%ƌuM7Nb$FEyv Z v,s겢(2^Eބ'Xya7] jn 3DYo$oOYXsnOWw_|cͶ|MY\^^/jSAQD)ZdYA/lRd)I?Zҵ%yqswf$*F*D+'H$E6C"M躞8*x,ϙgu.a}0Q=P(뒺kipƁkæg_$5y{_p|rA/85o߼ Ia|wMi6[CZD  (mH8ञo#BkkG}a0X xQ"=!($$E2чgc#0蘆&Mb(fz;&1,p8s I(RtVZ,9)-ipؽelrp3 MS$ Y4A#lL^GTHDH* z.辭-7Z3օvojYsz|FQd̋|0n$6)i^Џ#XPZA/*iWtAjɛȋPn8cE|2GH='1I>#Ma#۷O8˗Ӎ:AWl75\r} {+|>^!@GDQJ m픶HIU8TDRu; qh*F<(܈ G+=SO>W$iķ,6 CB|ᰣmo"w|"K` ]ds|}4|gz>^fہp|=^~39:3>vl(;Xh $7?˗?D H7ZGg 6-_^߄:~ַv;F0.Vwm ip0K$!(0VjJ1|oDſjk{1o>a<t2AjET Ci0ÀCR)P{Bz*[t$e)piB`-m[cЯϳFw!Bi-YTb+(\ha 3Dx8S~^d8dbC같T%PJ`Hr$IZEB0A$q|-\\LSTpkcG7[J!(ulE,d^rt;^ЏgWxk$e{h>!7Mc oon֣&"seI+w#oLPZQ0w%e Bb&d 9 nb:ahQa5ݦwǰ0Y+ 1#:&0]ihlz0,'I@S ɲfO(gǤ"/0Ɛ9T%ey`W(˒~OU0Ԏ 4 yS3y3ww(g0{v-B0=ޙi*k#p&"%8CGCqLeDF }?2C@ R(54e|QPgX;``G Wph53H.75WOH%www ]={xt3 ()YΖtQ'lɣȲnKj걢TF~J0஫4MB ?D:kNSN(=#IYNLȄ$Kw7xorH%6BB!c1mwL&:^aFO]UԥcYDwHȳ%E@zXΏX;v{h N<#ZAkò ȵGّak:47Xp>c[CG yV:2_v#6\/ym(oolqr~fDIB2(ͧNzgX%朞2qpp @w4}|>D]a{+" s8T]x4MX_K됫@ IDATJώ8=4X9>^SeCC#38S'!*SdPW7XsD3EF OP& Q+>;sHi =E㵦m V(ul&hږRٽ~k:GKA(a8cSZ'sm9XwF<777VOQ񍏾ΔS5u[!d"M5yf8nB9ڮCD1?X"|avoh9'YЊb_)'GCJbX?>~Cw&I$,dh m_=MGexϟ? Yibh mw`waH ry~1-\6>zs+s^ö /~qh ,G(˚77y-:k<<:WgYϦiTU5T XC5Yū?G<{+pFf=B:vxvCY~}t]fWL!=ZBI ~4a#d0DizUM(@| &YD5޺eNo |G+p1sm@8ھº4 z?y{sC6 |bl,Ib^RU<-Sί.}bзm1Q3U3|hq~w=U=`1bDIuE8rDht-k;N`Ҋ*e:zNdŒ1҃8 0~`ELeA%>1$ϑ8PQF:(!\ɳzI uWB So ,I麁ɲ8+BU{]Li5l&)%q$ǖFΓg85DZ4R%]r"`,BmLE2 DQZG-RX鉙fUN!o" b%9"8Ƈ}䝜,r K4cf.X@7YEܲ}x@Ok,$Ғ(#N@gHx- W u8Jѵz <i\3tbY3`/mj ,/& sO?Xv °ZRU :~08!0v Q,䔛/7TUM{ ~{l4t k1UT%^vhdl1~yAQiQ2miPR#@Ju|OV,RQ6'x"=(l{](GiAjL`bIQ',W'H)^(~Ӕ;c|c#Io J,KfGU08ZB)$8ꊳ+~UdsEHb9#G,>hHOdEN$n) y끾*T.c.#NRܣH co7-cs g8ѳZQ #g!T),,"?PgG "ȳޢu`QiA;@ u8@R)U Q e07P .K*1xK[o=T֍{g)?ٿpomQҴg8n9jOHK&F_Gs<-{|yŎrG+y)ü%%ecG"蓞,9$t@{IAv4M:SL#:y{ën`4 o|e'Dq0z@I<(Y|G\=zDƘc+lLG^HBlRorţK~7#ad+y)<(=6V'+V? MSe Hz|ɓ'_|vSW8 0fd^(H҈gaF-IG|k^/^0 a( Ur3I3%OIf/̒1dI,9%KS| ]oҗa, A[@8BT0=C:[qvrnov G˄ ̞Gjl ,o<\`l08lqa01=]ۓÛ)a6+8Z1 Zy>T%b]C?7|&(G"T &A-l >#E x t|4&fHqF+w='fFXAɼy u"*w\UIsn'mw-yBf/щDFcst͎.78gx{%E]a45~Ȳk$Ea2{G|dYAuD"f5?*Hu~gȲ58LgZu@(˛ up#J&(ْ`((`P\n$ o@.|KB A#(W ڶCdi:MyXkyd`*$&#5M=`g8?X3;p(CT,*/t00 K¦k6h#pփWi F〰m~7Q2fip mےhMhxHVkeOCݎHJColS\?e1RBׂ;,;Rww N&]-Ix<ޙKXِ:A4%q$1m[3GBwG/9{|³oG5U;U=uۿc4E2o7[Wx_掻g !MSτ׳:Uhڶ Xstt*;8ڶB`=D|RJ-^TҞmHӌbVPfQUMc-؅DBPw=Yl6 5o)B:3)G12xֶ[E9MgGDq `r%°ZR"&a@fyJa ~:,+=8YǁsV{6"ӔHjPkDGތHmf.=y"U`wI.LĉX2 J=:GQU!e=p`'󑕡0:"?, "ڱ+GW9 野 ! ^o;lvKyA4ANJnhu]хॳvҡChTv;qb5!=}7 bdžX!TUvwˇ~L:e74Ӽ~g{o-#232k_z1=# ga&0FB 1R5UGfd,7"o{׳a7Zaa|4Ҹ7~=nn)g%M{?nW9Gysn;5חo Z~5~Sxib`Diٴ-G' vdv0 ?я+uKvMR,|;/^u-֎ "es)?v'DEň0[T-N4,9W|[_{bhrfwY1 yP*)K6[8:Q̊c7wBtnڀQLeڮÍptT9,KƠXܮ,S 0 C9w(˒ϟs}{EjNfgbjޥ[5|9;VвUZѴ#&ps-99DO?-Ɣ*Jk@ȤB JVxR 2jK,b@LaT(yG0x7pN<";uvD^}(JYrjfݲm$j,7!Η/?3+za(3"6bHPh$ U6mk>ŞHԳmZ2 I2Db*ĘaGvp}S`&/L">! E0D8WJ=w 20D[1s hɏ8p]EhgILvcc +a2QKE~@0&6[;7\\jL#zV!YelSxGr~S@ƃw"7 SQ/g>,%ÞmP8>]! z$)TJfC$h!]];P \߼ƍ7 X7`$"2u)ݯʂzV`]]C&شFS, ,9''g̺g/dd%`Ҍ}:2) dۉ)"5u]O, I b^byB\OÒt1z"bd>;rM ?͏Sa {Nr΃tC Cc]DM|4?<;ykEҐ{PjRTDk-)@$NUd)OR:+Q #dƠ.ُR%u-cLpg9J7A}R/WY4ݞq&f[<BKCy:ӣWyI?6ؐ 10YSuR' P/2ЬO3q|zg8߻:|*]R!B{Y_Ia,K VU X,(s!`2֒5R QkB3 8j?gNJ{Bm*ÎdH,)*DX/XF$]oAV7n0y9Q?yܻg!o~YaRU.E]GQ8k_?Eto}#% ~ER! 7潻w a cG5H=&* ]CCMi,c=F I7ģggTEg>#~@+G, z a4M~v LlT{kɲ*,<}Bft`uJC<'7)`ElxMtV躞YY0j"M7Y^uǏٯM>)ޥ»|QzRy1)2frz.ɳyhȍN؋9 d2:N$\'`6#Gp#m Վ~Cff(YppX3AΗ_}S{zy5cc?$cBKڦE늱wxPOtCŇ@Vi!>h{֐&'䤈4Þ+8:9 e,k 00z7EKMۡ,ȍ4tBJ|(HS#zIQ0F,{-Lݞ ׷TF zY2I_3ȱ6R'<xyū+u]F5;вoF5m"5~Jrt8gC`ݡTkcqx@K|5󥤜EJ>rpTRVUsSq]CddRC@D%RhNwffE|*rf3LpNEaRbi1;{"E%-4Smp1g\.TMzf=*(NLjB. &麁zKuݞRVo6[X,+FPYmJe$FP'{9 N$w E+3i YJIffl*m;Fei)R_<9ˤHK -#*YsM<"$ ct9<:dO=ĺe>)΢,JOLBHCg;nT ^{t8CN7K#M `)Й!00[98Z7O~AIB(9,#X.g23cpd[^crk=ǣG4 ΍Sgd>dY-;k>{eub~Wz6Z߹{ nn~-/^HYi<"tR6f*xRb^0b B!DRLzdU~̖d&È}ߐe50`]2 Y,O[D7 [9k֡eHr:Tv"h2ZQמ-%tmd>h!ه1-w;^ 70Jq8X=YQbtc%]; dm M8*X̸,''q}㏹.חE]5"vێ{ʪfvX,ص[xEی<]gtmjd:ACH.zBH$y fPLPׁT|gMFAxQB3@M\'("&- Y`aIcӢ"R_S˃3|(//9_>;k|?B !u5gZn z|BE7ZI@cP@Oa蒬ʧdj,khJ*,K l;,CqzjAjH ۮMjJAFmGs>!0cv=膌>,kmDiu 'xq]7bGi>x+x/899IÞJ.Yw)y7s~爇}vO8yAͶ ZT1U,U,l%DCA]sr:"i~uuE7.5msFw=x1 0i`BLwCRބAHT;Q2}P%$$vl)rCn49==C2EbA$ ~H3 }jTKH(q R( #ǧrvvF^pf> E"z'@&6E*tF2U+wif1BhGSҗAg b?' *O?H-%Yv5&/EAi$:^e#bmb$C N& MEǚTbRhOXOl"bYn8:eqddjwFTRK|PJRU)i( 0'c:=i%[a] xI 0hv7pO˦G}'ÿOɌ{ !-֎y;[ %ii!dbLL a2zBR ErS!&@p$ t > Iz;X;tL2 F#2sO(#tdk!z670b$DW"GXp}f,FzMp;4!-w@*KVt}CHQ,ܿwkhvkf˼TDףH+-}EH]K>y)y^<8f|-Ek"TTalƖ~l8M)dD!bb2`d$46UY"ed^1L* 8vqlӀp*nTk?bCp \U ! ."b1FA0]?pssh}׳'@]8;;sdy@0c*X% ,Xp˃{_E{;O?ԳH)zQdmJ~$qv$/sL 3A$+<6$H~ܔTŌQUsTJi~i&W }g/.yvãе{E nDQХVe)r-H@p6)H >!Bdy0zԘ)f9,J(Q2oORd,>:HNR%Ku.sL,jzo=D8 O.>tT*w4 2mP!/rPɒZݰݮZ\=vE)}JlqpޒM[M1Y{ x|hc@ȤQ" FK,NfKQH-!TVi>$qbL8ew6 2C):7-oX"C8|H H~&NR$X.L[ AS C4LJKfuI 6 sd8nh!$Ύw#u >)$,3tc~zw$m@"R$O]C1*G Rt$AĻ6)घSH۴T>EVE;rztW< 7;tc`{#@V ) %2ޣamegs0-ɳ iֻ*\qFH'%dP%fE wg~ o.$NR1":X,x4P+24d{%MG*R棏 Bs{% cfs8g9;?e6+'fb5%cuHჇ<{?8DZ^yv@1Zr;6Ϟ2Y<8=;׿'f~<:@m;дC/|[vېrKxs{4XS(#N9=9Ya@d+L ]3f՜cm;[ h)5-!_WG:NtH%v1JMv||z#Ο6X,qRGBD @frOvEoai(C).qb:FbҲ} >x~y=Qw.")(4Mg6("R e J#*J9fy㤐@A;8iTl{>+~K~ 9+~<}5M3R%|=~69=?[ne{W9/=moo|wl7+$"~=S=ƺ HJϓSxut7z2O]+l7<~Ay~?+#8{wy! 3e鑺#;hUF^wY] ͰYAFNwwo_cȘ8v\r!1ZNYmi+LFtg=1H>d$+ nV7+51&s<( (cjNY.HI)% ZѶes|zbVs}}XEA7k_W/PU)ZgOl6E=z;ӟ@R<)CJh (ąZ?2o*ueY見r{wY_#Fgr&XN'dn' 2c2bqB4 b~3:!D9:(RiyOBxx/8[QvD%e~wO_q}y~3?RayލBP88wƇ~h89;G_з\N ټLvl )26]g" HtCW<|wϩfo._g\y#u]+6 xGsvfoWG9}cʳĦ)%!Eh m #cf35p`-F+>x!BI"/9X0[,9=ˏcnVW,KBHViҝZLfEVd csLZlzǼ8B1d(lгs;%A\`CyqgߥP\h 2_fHhbP䖼5ZFv1[@Y~\o;e=GjA I>}V1 2'xM8wvo lֿ|=_w?/1Gdñ=븸!/\j q]GIC] gIDL !h Rs}H2?iiJ;)2N c}DLM?=Q2|b05a`[|oG똗ꄮAqcD"5Q㣔NЏ Xט<` #RYL.K$*a*d< NQ]nh#3rcś8+\$f^7,%fl)9*56@="&;2I>3X/ IDATSEBPyFg 0$I|PI%Ԕn6+n``B g~ZkѹF s mdGwMkDһ嬦f4e&ˆJDL~7XBY:m D/ DQF*fURJ0)I)()nf}2@ w~sOOq:N482ZK<[1 3 vsZQ͢aO8Iٓd>]RӶ Þ٥ 8P+rFg2~abgr(+%^vou~B(loe) (љ*gl)*D$0J36lk SxPD`#h1'Kh,f!$m?Y]""F &i~nmfdSUeWab`BHHH0-3`ĠXUSYY{vگcQء B޽^{y3!P< ,2Ѱ`P% ''9m=www@u9Sdp'D\P ]g ezX[Է|} ,IݱY!U儅h+Rg{()᯵G4mZy- 8%xee( ʢMQ&}0c*l1C' $)0F"Em4=1h!CeM-*Ȁ}!tڐPQrwp~Z/@,1|Rnf],o/|XEg c4{@#L斘\Ë؜ ~'@ >/_^R%aCIì81 UD!8ow_ v),&b5 BI^~=l(-PFSY/XUBn#+,<vѷJ\R&lqcT w Fi-%Br<:{He+n{޽{ՂP51W%EQq{^!P0nB+4=) VM\͚8}>G>7_ZCV'+nFs65UUB/~o02 |O>:Ixd=L\h5EE]XY Irwc躆!Ƈy]{T`lꖥEʊ|a(X%!\9;U.֡xSHH 8d~XU!`RlCN%1{jMEJ|~%GB ubyirR[n7/۶68S%jlƵlC@(9 rz+1 !3r#m2ql|bǚł3SW@ !4!Y1Dqw9wݞqkfFikismf|:GIJڢB~C>>z^/5T -zb,?3|:ͫyuy˛#(f5^7 C E$:O9??eXC'Ʀu-})>9gY!\qs%`KKL#Ŭt] _P6WZFd2tͪ&zw dعs7xP(+Y/yhl2EIu#xvWxŻl5V!\c+l-/Jޖ zBeBĘ9rdL;G{ûrԒyey˓%34þmS^!jDOD'ʿ|{?y]Gi߲ IF%Җۻ-E24͜#'(Ybć9RyML)A$7B5!va0Fr_4x#Q !%M#1¬RDwd{wֆӕ%7LkvE҂=Zj[zOYVtM>dF{Fd[ axi{R2̧ͶƧ12P]I:"zBAT,Y9;}H=_#HЍC3]yw)rrbP,ágH! IRE*Dp|BHlR.oё˫KnooYV٪SZ|ҤJ1cTk ) z Sv9 ^$ `H)@7Þapt@?$Z67%ZΓPhk Ifӓ4rC<T\{~>Ç͡EDp#FiH2S$}yfJY}3B"`|LĿ64v>5¶Hf3NN7DjAy%$0g;Q23({UDBH&փLhm8?0_.X[_OX'ŷ~JN {>p fḣHi`i{Ғ3Vh%BmF'HIEKLS>x|!Y⿻1%P;|!zVmfyC>)Cw0:щS"[䬺Yd?dTXIY^TKr³gf}5JwfcD)3٢MF/InDWhc"޽}a+Ztm\\\.//ѶZ~'AYho=c1_I>\^~Ţd^lx5ϟof3?{ׯf>n_wFI1W"87`H567zcT^˯W| ZJRgH101F|6)ɩQBj<1'<)QG~(ʕc >*j-;͖wo86'D A;&fmO|s?c#Hmiȩ1uu`|LР"jQV'.ru,6YXÑP<@\t={nn|{n %c7/ݻwr^ ud! ͪs6M?9dσۆo.In) ]Pfsc%,s,k.ҰmszXITyhUN7s=MRDNK˼!. >a~J +)9m߱Xf ~؜HL!(*8)F (lm36VA0={(FO1I]D /jj[зG'7Fq}d6O/w^_\<>gZieQsw{eUW;NN`VqlͭȀ;)$KaQ:SNĨIa'n (I 8leFcpPhHlVZ^>`<ǖ ;-+ åtH]լV+=}ht~CY Ee[捁{z7QF`Y+2y꺤3R/( -jV,?^lr{ew<0 -.Y ;kP"m:c(]rw{lٺb Ŏf !@L~mg";CLk.l*{ 1HJ=k)2l5 F"s:{O) HFhA bqD)˦4 Ǭ̵DvNW)M.+s2% CVU^0_.8[/HoÑduH'%#14ϏS[Ʊs櫁W7NAlAbm=~uQh PJa&DOv㴅""fm3 Q!R`ِdmkL²^d0 % U0b"tSDUrWkRϪӋ\O|%}נ!Lx)XLy15}S |dn 9&gV5wqȵDDR0RURP@w=ͱ˵CKJԞPbGGagBhQxqx)M qY>"ǀh]cjli](bdNE+Eq8'I"!xEmg<1ﹻSW?/?liCDRgC7Ѝ#ڀ0+ ( `$>)3K%A fbJtIjҖ2$Zoy9}3WUᇟT\|ǧ 6S;vR96jJEnﮦԘUAQ*T$Yk4KE> -WQz M ϫl}0y{gK67:G IU\8ۜ;}꺦n l2!ud8QaߒtsRGA#-2B;5ǖa37nh=}?K)0ҠE@d>+Xo6g\P>DIO%=m9}`V ~/jr.qr1gwy`Γ'On\^"Ʉ\X5UNm{(ǣGo/^!ԅB&X͗&a0FpzFJ/K~y8K~w|7lf) >`Ê2vG2_4i T匪96{g (OQ|KmH5 D?ri];RycQ%fDj1ql;ТEiŻ{6 }=㱙&MfJ c M GO(88 c{RRH*₧ qA7^3l2 @!wÍ-ɉ\%QdfLL:EIl݊#Cɼ!MN&=BZl UO0eA7|8}_9?/W3[o5~&c)$yV[u]b^.p.qsъY5'%EU*NOjV%Zw$!Ym֔&pp8dJE1wcOD5+q.ҍ`+"%9Ǧ! t}j3ga4ϵk1d`!{6ha;J8FR(4 \Fϵ10PUNPGAw?G/xtNY踸 ՛wl5Ypuuj5" $Z@`.h)E^+j('\qA/=)aM`QY_X<~)W_>o^r>G ВC0 19cOt-FfpFе 9Ol-7M!S(ˆF(cY/8}QOP]#Z*ʒQēGcJIjnn3{f^դ͚ar%h,9;W/M=L!-,֖QPn0>^^>՛/)1 HaBtַU~ò,n'lRic;dRDAV.H*?D(lTLZަ9(1%;Y"%MD?pte4BF*7@b g%7/Us9'1Ɩ ɧW-O)4}=N\8e10H%䬲}HY`lIsAI]C7t`JVQ:+#FGL(2ڹ>bD&IYD¹.!J(|֤\=㼛?9TTy'|ī/QJQقHJ0( jGUͳO8Y/?3Oθ8`{{䛯_/\>0o+c>)99,7uUeUHcPR\ #!פEE)[=Z-r%STTF%FiiŜ湮]nfgP0$|hADz<Ȏq_󼺮ceɺl:NC!/}QuJ]60VA kϿ9ŗps[S>u aKv[c{I]u wn9FY^ ~$/BdXRt88rb08=.פUaCH]J1P _[d] , 5b9806#]svqNYZv{@a-Rj@2Y^\Ck8d1Z-r}sRl>S2/" ]I\OK.@kP8Q CC IDATE@c hf#E-Kcbp(iyfx]'?7g7noow_qe>H٧G?@kMi,/_"E~{)3ċyFM-Eb2@Ȉ )̎B|F)DlB@ P N68;F7o[ ;p٬ Y?#uCСyU#RҜϾ~}ݼfF9rl5!$B %g/pcxQ3R93[\o&OjqƱyk\]2zf99?a^͂{=]OGmYh'hGF׀@|LS6'9=}#~$?nhvG/ǑqȺfIfHO jJR/ CNx1I'ՉFҔeVNΓ5!N# 9&&%)唃=G'RE!ʙE#RO;& )s}. !df^uۭ{$>z|!Dޡ",2EpDG(gιlݒ)`2tYFG%JN8}2yH:N8SKKespw7o~gmҶ=8oJju<ĬkTޥ5ZK!Y#q0E){D"! OD`:׻{^s~w+}ٌ ?pm!JvǬr~Wk) F́$]Ӑj'~ă(%h#ms`9[Us3^Pb5B4)f@ftu ed,ZKDaJ̀k#%n $.sV51th& nEMy4 cGh]еj-wwwyH!gKV/!+}C"p%/ X/J>|XUlSrszNY$ #JO>yGٌi $1dkS!2OW%r|n8;d9D:q=.~)5oߐ؇~X.f$4̋Ţ,BNKQAi۞ټ`Q k~GH"ږq` 0(LFP*['a:f2WeD |=fhqc$:s=|'t74yIl$'M'a m%IjDPDX+VL"aauTUB` Zvo_V3bLx'<}__9ǃӏ?Ͽ;p77z#7ohH!AfuG?y7l6*!0ʇ.z@B9b>4hMDSjbnHSPNrA$.5JgTHŗ|ͫԋ9' d|Wf{Aд#E k*1|;/Iq kKDz J$M.!kCR`tyAu9XoH1&?(Y@2Hi) (%ۻ RRXk g6/AzlnxwWIHa"Z "4A8,Dt gfR-d&2\4y~RBH2*5cQEM)HQ n08csG)LeN cRX2O D_uGR h-3b3 !({7䚖w4Fs$9%(BӏDrUj^G6.1=2hytM%g[] 8_g$EbxDJ–聜^IS( BS5 BJ8#R"Gٌe*JBS(,J ;sMi gҡmvo~ƱH !\_f\G?h_p<w6lB+(JGC˂ u0u˒@&G]X[f:- Uz,$ggglVyAG݈9[4VcOHb88bO >{H"_}ᘇ'zAwVkd(VSUU@2J"Z-yw{{}1o޼$Vx5Ͽ0;;|<~AKЏl4 aϵ4COu >R(س[$k~ӟӳ%5Z݁6b9)#L0x))Kݔ2HA.䊐VO$@7[Tk ;i<2F% M|EwdÒ!q?|7\ݼF&Ҷ=_B|OH=g vOf#^"p@Dpz>R 4͞^|1釀%<эA,ʂ(QPk;vہ\}ByF)j` HxFǩ,\g6l!D%c~ӟٜ+./_zF[Y͏~c~EŢaoq~ ٬Yf߲?lIpmy30$x^|cX-73 P }B)rU3K>:ׯ4 eQoxPRՆ Zy1꺞RbVXYS(KQTe arfT`>F?WR%kf7Ggl[%FGO)6;իWtc@i"1+HI1lW6P5ZՂ8Y-,Vv#ن.II5-Rhk] ]&H21ni@tHydph5\@1 )#&#i^G?_~59ei;% A{Ͳw;~C|SeUuUM" 0$k%Z+À lh%ư ذ``$sDRʌ̘NgJ0Dpyy~Ϟ.bYnb" S̍i ,pdM9AHJQ;JVQV$$hU\$1H g-J%R&ZAvp1G.kJ29G9rJ6f(H2IYb8ls_Q#pH*RV+1E˪^O#G=Ж?£ E) F瘆\zuu5(wNrOBIrHΟrkȵ a#!B! uBk|@e8dn-ZcSW11 q8R5 ʪV'7|4J{ \NH 1IH!4JA1Ϧxca>&=eUƜȕ)1zf3)+ɽ̈,h'Vuٰ\b:) NB(b$+LJn89RƲcO]7(e.ZmzTUxBȶQ*"d` 'bl;TM\\ (U H!30Tx(LL٩ $Q+KI0M 7ʅc\w٩4;2+"wᢨj҇I 0%BY/E,ۻX# Z%DYTsĔrJkQ"MHm8J{Ed̊2!Ut1U ;yÁ"S?ǖ$!4T֢`f˼6GÄ$JMٔo wg yrlI`w/INn/x |Gb*FL$Әyb^X;g%ZTyB»)G]5jELnQB{xȢCwd\"xB{Z㝦aengDU2r6Iz~{"s^|9woJ\nO4,f?}GWlo?lN]TEAr #jid[>vs:󛲦9:V oo ww'jCL%O?G?ƏYNRz3>; +uP arvx1zH(gycfAQG"DƉɍ6|4%E=<,ȐJ²]1ډ@( (" NҒP 方7FaL]WZ#RdOW`dvV@mU⼝c[X_$_L㭋 tO~c^r-a,r6G_Ŕl…edoPefԒaTv$(* - _O]v@<1x>`wn8 uԍ$X]|O*o1;"I`ec9_#E0M,aYB|?Hp,ׁrOw|y~#BTS:ڥ'6E=|s.J*ѓ3oS5S-}]f3N' ͜2B JlR^`hVi4v(Н)zkt&O>aٴl #2_RVyY uy>sGgd7mW|䧟p;ZkP4QX}s0Ѯ@KY)Vu#l3_R/Id beh>0M`` Cv) pk+Jr=>r8MD+?GW__ 9___Wߥ?C~8DLœϼ bܼX/|;D?xGʈE5d=2Gڐ(YѶ-ZHv;ɡΣM BU,(M{`,V+s?Mxٸ%,DH0ag=Z'fdvho싟% ’RDB8qALCwi z(R2ԕEհh/Hv//ss}Nj x8q9( u#Bd JD;lu<}{M8 G&qwղdzd48Gq!oH) iж4MD *P ÀUِRb&+\gUvjKv3e`m}NkG$`OTeg-㖮? .f&Ґ3PD7[us4ՆgϞ?\Ř/ߢDCU.Q24BX//Fn>:ڶ;J;q2ަ*!}4MSLd])aCn#4!%@64Ns.Ebp:S@өGȀrrK} $:WWL!2#Rkq8B$TOp*E&?& $1\S**5) 3ZI!W^"$JVS?p:CGXbcjJM8 TIYn)YG/{f`rc!C6uDSJ U°K;(ggRWUlˎ yGXژwΥDv& DYcvaI>dR>+WI dB3!G$8'޲(2gMs4 &P3q TM)g!S( a"Ĉy; 3'C)DJDvu摚qRfq E,jR$bn WՔ`t9fO]I z'RܼlxMYlZg1N;:tǀO>(^f4U>ǀg 1=T%&. + 7o^+M,Vg\]]wc ^Jqyya80 , TY#Oqqq4MS?qtqH ՂhY,\z4x#"IƓŔ<*joH"RU_/s~On2K{VEO](y< =o߾eοχ/>7;AtH8q>o.yy¹Om9 c򟿊`9H#sش) (d2H׏iq% ٓIHH7(KꪅiVɳh5j@],3Fg)LEJmJB~ǑF 9/fY-RiSxrh"EF+B8xUd4HE> G?23C ȭB3bʮadЖ0,3 YjAQh~GHX,VbunZZ/6 c6vQ0ڎ7[޾#u%n9Q S$4MËsyZv#jYO] bq'^F*%:~$Nvd4O&c vuwe41$7p{˩e<ǔ㮕)}ڳ<۰X0px8;6s|==IAv(K4(=O=mkNCx$)Т %`c4<{>Q%DIO( pp꛹!BN.$ErkW`T٭6knoveV ?M}=\-|}}{ TO>sW'΃a&?lEa \lVxyhãK`x|Zh,։EuafGAɒJySUfF7J=L" .% |6Q:̈́47|97^+%u&h)gsz8R֫\A^*|1WCkXDp)cj֋ Z*vK4J cB'.lw${|p{@H=ر(eX 2Ydz\DuIa]pӜ`}V#ĉFirlW)?GT>IUb:N!2!p䢟˟my3l$CO9/Vq=r{}fsR&{FrMC"T@<8e0!2#uڊ!䶋&$:Ic RO?#*I?@UTzhTTPTŋ̎..ݖnfQ#lZ0r곈UUw;0dL)o:}~@\abWPTvS)71@\^)$iY-/pNMYD2dZHV$rGV ǎatycfxRG)":@Q+ S?1LR+\"ۈH2Cbt%"L%Rvݑ0t}sJ Lȼf wYk;N#n) ga qd}~B2!VA:bUi|(Ś\#DہN8o>G'C")MGڶRbtAUTUB %*)%EYpiA !SvFe6@~.lIFY]䆻ɓwbXiĺ,E+C"Qo'B̜-gQ3&x7bDR#DD),#U'}H*+0@ JR)H9Mb_ ޳%u>zvR=vb%N, 4d2 *ZgaH1hVn,Vknb?(uѮ_ңA| l'Ȏ7;,Ke  Bc=C,YG`Tp81N3=y/_⣠9Z9wO<ɓ,-%[RFFBQn$Bxk?ׯoHJ~闾 sHc7KZ ]o*UPn{~Նׯ_rs u) ivh[z*y/ Uc a*8LVjWgtIP 1H[G"7;YP('QV5F6 => v =. QR2 Z]=z1%SnC޽~%y^h ꂡl.ƔuNjHH % ͗Yp޹Bs!Ɂ-ISfF&("}ǧ~o-./(Q.|1f}U{#MRh[5[e4wo=)P[vIU8zkqz 3b#ыK#FK=|}ݎ7.]qssoos5]?w;b u:(dwqD K(-Ͽz\}ͧ_ptċ5'痗[^}t~Ø+H]8z9_,',hٚY^oիkfOUxo!Eu#!p9EfAnA at7 2E~L:Y+ŎGsg ߼_esFSlkHi0J% p;4Sfˊ41>u8>;(H!0im__ 9__ׇ~p8a@ I.~xwRt%!x!8ΕNZ"›HrV gqB"v|NDh4bu1;ijAS{ cJ%mjK\l.9|T7w|g ,m㑮qAS5hFНzc R+b()J.re~PBBQR#SuU`Lh B%l8?qnp]kt@|$AL*1Cܠ)8ɜ+ͼ#Og=h⌮m-d!2C̱1Z Rq{~{ВZfѩ(0CBliDj5FLN]b=Y÷m./'?xlg݄__ŋ?˗@j?~L,~_w#eYu}vM}߱\ wI m1ϯ?_|}$,+qRŒ>^Yjm(ko\^=4cs<2\jrAJ:pZ+st@Ֆ9;=BDBEe,i70L.{}`=R)v' dN ~D]q?P5ݍ8P-4mKebyԃt@bl*}˫UCp"[sy˛7yu>h,Q2f1Xʦk*\B!DX8u~㒧&l8P5.1 #]TU_N'ꪥL6g.797zw~#6y{L6a#6<~&$ǣUpé&Lv*V%(iDOR+ʦ!r 蠮 BOx9ذ\涼7~%"'4R{r Mɛo~}֊ԍ/މ.eY# ]b٬X,V(떮>hV"DbQ%UUP5Rը`tCJMU㵡 Da(ʖgW87db}IoEC"IM۴uK;yF?|L! 1kK71([#RCURRTiϱ;m>RV #M]l9mO4f~?0!,(ւh9n77?#Hb4_۷~KHJȚ-SlFK"I4e1~2My>?ŵ.,EbL1Css˥@?h!NK{@X;Q%l )Iv3OJ x)vwHմU &SUż1 s~*Ky P:WOc92nƈ1eN")8O9jH1)#†i[꺦)9np̜0*;a{O]bnŊ>'Ɗ Śa ѱ^_VuUQ ǡg q&L!ieJL%S1 6Yfps)J<[#Պr0%c@):'Am9R3jDeaf,`T"\e.f23,gY4eCS/E$"Lv&FTˊt(Ţaܔ:dP"݀s+(y<~>ony{v+iFCQ*"G(T2Ê)^i )3 <&;#RvJ[)Oaa jNlj/?Ax1vqrA$qOߟ:6Y@0@(\y#DJ4RRvѠǧOĤFqv]/g*ݛaJD4ac<A.{"ĩx{evJC`s<}Wׯ~#x`o L(,)2q!*uAߏU9Pv`ʲcrܹ( >-R+F!m|nLhi ljrCYX)1 ,uv)cZώ8$v_ )x"eZm<(+ %,zBP@M[C M e 7~sO~c><`QIŎ(pyQZ?DP$I?{1bj <0"032yF..iiqqJ҄~cڻ[dQ0YϡO_;u?&|G(me7pt12۰sݦD6}$P1bR9nZGUȆu!$G7o>回iXY..X.1X(s5͚R<ܿ0EyT=;Xܲ! ͎(*S7oo|4W\=>u6UM4U~)5eBdq1b'(0͂7\fL9l<}r-C6!ǻ1=K{>ƁP%Bgg@53s'Fw0vD:S1$3 lݶ6P%}dec@V6?&ɔyG!p80= ,KR L~l~J%s!FB̎ f1&dhqH -fJ(%1E H]ĎyH/FL&5R' HѷGrOX4KRL w5tDS"21Qeb2a'4b]i׸H>(n-dV{w9EAQTuifpp-FbPTm8!Uພ\b%gGDfCHvxgp~D48xY"RVL4UͲ]`dW<>Xb']NaȪl|9I&rt'+ņ/|;N媥`n@LL!Rj=MSB8j3oJV;Mxa "ڌ isX!UMQnh ݰSڶO__R Özswo#:q2Ϯ/'$By`岥 H>TU!/sXV(% W/? CJ^џLSb/ϹRUIi",yKstc#FR5Պ'Oh?3Nao|~!_|&G9DAM4͒NTGbӧOZp EP5'Y*c24v G\>.(j eOȮÉRk\mh} s k,Y%U7wY,>έx{RvM)0ztf# IDATQK9n08Mbd^g;PU Fj۷'TTE!dJq.` o~ b|m 3< tU(NEHKYUTЬ"B=\ygs,!JJkǑBjP4PTi"˥Ӻ :yNpb@DUɕ0c]Ǐ ɲ<"h'>pO"yts )_n'_ԋcn"FP1n vJTFDɸ 23%zN=A v10>REMexvϞqyw;?}//D`ɫ79tcCˋK.]n{գg4MÖq"'W<}dwl6G#PqOHs4,j=~6-Ӟs?sJJVp|4s)4ӘѻYdcDt/AUZS%41˺K F Tk=>%itCfyjn"!F;`~KRj3'@S>rM^M~Οa%VyebOcMzm;g5oވMFtd JF H < !@!jT7`ʄo,$(%(1Aig:3#3#9V`q #J7"{~Sedfp8fSQ 4i) ! ѥ$]눱><zڦP$3K.#-V7l7H. 8O&t3l"ALc<$$fCRMj?9%JޓRUH8C*A4Yl2_4Ce5.fULXܺr6C%E򊯞=M";pq^۽Fnx?BUh#5t6 JPe%Oeh5{ :O!R$#ؓI$1,:vmR*% O鉴N_˒YHEȀƾ@U#E d6$-(ȴfp7!яѣU"[;t%Hr(K%]bW|[ >86:]LlIU9Co96=RdL'y^?tVz`D4`:#CK,O@%#$R1V[ "4<kf5t >&D-X}=J!BLC*4mSU"1RT2`SDLURMa]$WQtal)&-!ʱ.QIE6=2*xAd63Jø۷) v#f(JI]Uxo"`G72N5P21t.%ɾ'#hگwɬH̤.}|DZ)Р G*bd)2(4e 3CX%9oh-m3 x1C#C*B;8hXHS+-,F_xlvp]*ɋ)w}/bې#2LJW0F(sV%zU>n#:g۱#9Kd7nƻWdUҞ%O/*d/;X5L,gmHf:]r}}{IZg Ƣ+CQhcjN Ss`-E1kwD~K4c2~'~7ܬ)ѓ̳=Jl;?oZ$1bj??ۿJߛ\`tʑw|᧼x&C`YFAq0&M+,WXy52X(Yu5!@hcR 49HA'Ϟ<1U9O)%ʲd(KO=TFUHطL\W'h(|NUɮy3)mgMl'kN]NY0)hȱ?ݚ yQ{CUO+<5B|36-g]O|^>BYZx`!ׂ,@XHh`Zf`&J,tFbH4Øǫ%tq'²޼cp80Ge>?Ν{HYs%?|R%BnnMnnnobO#!"b (ZSO*ϸu6لsBB FlHuF Ȏ Z-քɔFIPZyEh]BI 㟍t1T_,J*mû&٦D',IdLJgv;t&2IL!egY>Q@iBzY,>FJ@"ޅTQ۷nq]ӳYs= (g ɜ@@߷Ravf%zntFۑm]yuUl$dgqSɌGb8{Few[Z&;xi UذQkbS .ӚDg9J\\o5h#m{@g(ܘ P21F!=`RL BfA2C>|C~BQ-||BE"NYdy YQ ;.P5:g>_Aힺ<ɿw?1^T|F({`L) fT,wMFA^LL%'skh=oy>|\zKi&8-GbHUa3xbFˌ<+p1CU30X g}$- K5!1`)KRg:"EG齚`a?&+&Fs[/~f29??jͳg/ꜟ+~7 ֞aٱ(*@{;|gֱ?y͏()7;,g1גV}59)bd:[TՔ,&8k9w f4PP*#'YͤӶט&~96J#eq)p<94{\xގEZ(IۤAc7(,Ryc#CfKKt1s< \qq~)%eY$f+bM@LK\TEx{Ȕgɢ4u=kb9*̓8/H~82;dLC =U=e2] }?`-o>gbX G7(U]`] %Tju CVbRWH[NV \SWZ ۠d&_O8k= e &rs8·>oC_(ΝK~{wo6{+<' dy~ "mC֦1i"DEZZH Ĺր"g~3fߗ'Ok8ν "/^}(hچo}Vs4B("/)AcK&3)qv@*O[%U;d%N' e9=q,+I|:tEULg"bsHV2N;GJF-]a$|LX݁<53'3>x􈪬": j>[{v͆Ӌپbwx~cL1=H^IOIkzZ+&J)*}ٌ͚vñ㣧[31aG#قtA&rb:̵(N( PH]'"PRUS{oA">ֲgYt6Z)<;57i[EIQg |4j]EDJ =:C']1Ra!ʐ23K !`}n<JbB]~Ar9'۫;7z3x"Y('儬(qacZ邭K @Yebq}Q&<PZmH1+gf.ձ %T@=/ʢ0 Tuŭw{`NrfgLۛ5vә#&Hѹ)SC)31 *"C>qNVXJTc)6+<*"o޽fɌ|l>e\!j푾oqn( :BXbH)Fb@ĔRCLp1.b1|я7= DiM7XSXRחܽm'X,1[=`y[>[%ĞAd5Бٲ|7l7ozK'cVpL(! i~L)uY4MJc@)F,(ΈL.b5xhۖ*Cds!@ cMkX.OY-Oh#$Dw] 3CϷ>ܥx6X!teY2Eۥ}SRjNNzZb5┪y Wޒ'}v7c%s'~+t.t'goŎ{K|V26~ /y:9޺c`e}z-plih3 ФI]#ӒeH!2!d\_)/.=cw-FIp6sLfv. \]pab}c yk:KǴ\ l6J}}72R! њ -U9Y`:8|p%%L*(˜E`2+ʂc{>x|lG|̃GPdF-!:?!/޴EG^d ưnȋJd2X-88zt2AJ˗ox fcngX_l|鷸~o豲lgRW(`Qi1e}.a=}r" ;XyGV3NVgFv7;?>G( EoZ޼]3gd=lw׼xIbŕ:-@HMϘN>s ٜLW KXpc}WɂIZv]<7Ͽ|3#Y?/ /. ޾Ft2a !CTtK5^FBhxLkȫ$E,BD4Y`P%)):/p޲p8nq#/$*KPT=e }@eQd9Ǐ?]Q50`!je>-) R0Kb,KfzݘCM*ْꌦI6Rei$U8v;v=W7Lgwɳ)]1`5=GhE$LC`\rh7WK>6 o߼$  9Ŝ[@ACHI~R-b4 % ZI,L&K˜m3XGSB EJn!tgdJ:d)Ugi ENMqWfKJL&,OQ9r8AR1<}D`n@ơ"h3fgsz+~f, |۟2M0}6_&f8?\c/_~A_~P0P5֏uҶk0{怵Xc4Ev9uQA)l:zRQIC3Xΐ>R(!lS&)EU1͙/hq0t Iy1L&<uѥ4ØTB,c$0X`M*[Z x"iNyb1*{N g6\4HD"7wrqyFiCfprrT&EHtt6c }.: L\+ (\6 }-CQfXq8lӥz׌ ;  I+nH,`Sk2U g qfz,scIPO`cm7PΨ2#Kݮ9:9eYu-eh8R#DIrllcc,`oQ:6:+츹9POj,+>}&}DhisCbWh-Yf,;Gӵ_}ي6|̰ST7J9I Lx>=9=r94LcOqhǺ(,=(,D2ZT2tT8&&3RZGC?t87 uŎc{`2 q51dd"X*JŘcf"C阀lH=K{Ͼ1'#Cb5ֽܺ{x_=}Jw3)>z/|s.o%&8 77;BsX.)\ל2 ]EEY_* n߾D7J(mWWܿw۷.YgHn8vlwtLPiXxw}|Rk^8bxǏ" -WcSOr-ߦ(s_yPɤLUȋ Kt6a29#f w?$DR*-Et=f\ZN Ee1ß_|ssߣ/1䟋W-''K=n bc2(Q"F`['/M~mvnІ!P)Lce{(󒼜")xYl⨈99Y q勖c L4iˊîZrb>t%.(#Ե-r;C6n6 R D~a6qy?*Ł:UKb$vKK#rϤ<ȦH.U9˱mk$%e|Io.?tءx3z7osM&%LtB,$Oc=.$Bٳgܹ_=&+d:$qs..Lè-Ret}_OeDd$F sSՊk^rRҶJZn:!+rt6TT"@gyo-Y#"-ńI0e|DMW|3.?? ??gZ1-/N9\\^ ov7ܬ8RWQ:ҏv0|]2pc5Kr(˚ B<~OVHU=!hs8ѧZV )yu-Wo2Bq>"uJ 8B@K,W R!ZN (o-h+B (P;JiP,Kz{"pGbt 4 /d!(Gwzm`#&:Y&QJ#b@ ICQLyLPiBHCLr j2Ad9mcf`>[CnwxXӪ# jF߷8WڦrP xC5h"LJ=2իdOЫ"DTcBLOcH(;ޥd28Rpt'hu˔YdȬ,4YIQ]_3D`M:p}Jl1}uT0.U}@)wgw9WMuo`@TA 8|:B0Ҍ1"b)5 rB͑-.N'yyZD}LwG 'Y,kjzs͓/7,W78 DaTJc4t͖۷oyM!d+Ơ,fsqQd3H C[C6)͆`:.1O"(p fB0, 6{h%JE9zn4cm{oSOgfs8@*n]^2/~ëk .>ℯw}*s޿|1W9Ozt*999mn ]v|OK%B$x .n Z=Zp M * 5)JJNV+\3AsEi@5J6O2"11CKEk;mCJ(I`G ޿mȫ -JFRoCkfid0LJb4&-TRfc#Jd :bl7GWϟ3)g%ƶD`c]T1cYŮluR O;QEHOQ*9.~G3#C֜,,BR<ـAHp\b cfd월U6qbx/QR t~AZvɬrlKģ`4Þ^|&Ei \_<o=`1u)h!Ylbq:7-w/~MDf)4Ǧ!| sH8c>p`wmk2uyrD&yS%o޼m3Ď՜o8=Msܳ~wtvd<`8a6_0i@"> Fm{C)X,W džׯ_n}a70‡ϸ}cؾxl9lwL'%n4zǶj4݁{kZM9:+)S! `<^q.XhZsOټ ـ5 7o>~ݹuGw`g6hYWt]^-3I+4f鰠ֺtTڢ$F֑<+98ɤDW)BibIkI Rz [rvzЂ輀G{G~ӢUPo;E8?dHC|I4{.X偪L5f<{&طkĺ!E&^a#] u!T>:̙f vd*iBMoQd~)!2r47xs2[fW_0 t&ѳ%S ަ$_`>[b6]T9J(S(j7fqs'nmPzmj9e߲\UHRJ@ocA%I*!٧>H=y^1ԵB*5!r_|=mwę!uǺ#Z#URIyG @|)Ϟ_'Hql6B!#x#8?? -u=eReu)" kRD)bH!I~L)*i؎ConPC*tpQ#x2MY-tbmGF3@eEuL<1EN\!Z 9ݎkN69M<Q@9.dq"ȋ3&L0,zP];#)u$>J\-EJ@LqzzveqrrjuJYjIѮ=McYjIw Kx zOUL##` Zm:^>ŋ/),qQdDhIs1(Dh)9&,Nasbp?U)JEaGIQTMlv[.//sm9X:,)7uTBD|u"7 $Z 8>iXjE&}r˫K*-ٜ!:)?яy_% E?'_~ES=L-y-o޼;tZ}V߼7Ljͭۘ![vy#T 7?;ۿuWozmp8d %Jdα1O+I߷ekAYjI` MУED1=,fKT&i#ZELh1HU)EI1U{2mzIg0V2Dz1c=RrM"ן3g-ɌL8+ 3i;n( > 4$3 HE+M"$G1>҂a&fk~ֳn6UeW]C{Y0`!13 K #BnL@fbٲ-#[v*3t/n VA 2{zy$Lp'zt]˚O>~[ :- ,(T9=ya۷{'h]k lNV;>E, #{='*Q1uҫ_onCQ=1a'Rp'a˺i<įio~vK2Y*p}}e]P `]׌Dw8LMGM 'gs=YqqLG+G}<\݄z</Drꖡ|ʢ;0[GG)J֪\nCI) vc''lrusG]׼9}'/.x+~5`g!I )+"E"@})6&$ۻO?~$B.!zĠ9?~_n%!L͇̓[!BDp1[hLc~ʃ;z .3܌Gˢu9aژ\-v3 0&!Ǹw=cEȢ1MI-Ύ!qzEl גZgن{=u!st)ogg?ah1] 0Hak./{˷KbBHKEB*=>4='K0L^REfRN̤JA(~DKI3=w1me RY+em'G ,#E82NNNݮcxԧ$ՂŢEX!f >29N='BCZBLLsv{a$6]R-\?K2L̫bfn(¹ zB'>;rG.seK]\2ϖWo_S8QfKNj}K>Y#>\b[,DirkSךR"|: ZPC3%DY.NHƃe"2ryr҉a8gHOnVm>B3\>'.jں@"ψHyr#rU[M) "`&v@YgW*9YP}6Q_?}?o!>*cm\#d"ڏ,2 0GYmOrz:nrΞˊvNHU#uɋ2;c.O.ˋ O p85Cܱ|8WZD@!Kf66X@#JInGYBH]lNΩjC3n"ZL@Pj \Já,I4>Z*J1eb8'(wܼaV"ADJ]0C\H׻#E1Nhh?qs@|4JGR:,r)Ɯ+ʊ{-ZӴK3.ZQe[{aKκInb?}v4!;$D6 83N5i؜~b:=MkÁ*YV, PĻ@,8xo}~h QBU"&Vq}$I uBk0 2\8?u9YzSnG 9͚np852{8]oO铖ӳ ϟ?a{×_~݋gUQ1geY13h+:?[s~v \RC w7Woyp޴(|9 pz0vhqLf21SՌv?ӳ%Mkqw,P2ι{ۊ]?t\_=P=B

    $ڔHmO" M]SU Zepw%FXF<~ DN<_xyg'"= ^!+V5eY\l TAi̤ĕb^ \S'%{H$FAaF>k=A6 n X, وݮgBg~Qp?pSƻ)q>mX[vv{x9RW~îcuAju׷N6T䫯?9|qwϞ0enBJV'$aiywnԕ`mfmn-!R'beXO)]TIlČN0ylc~[ߧq|܉_0+WWgქmpw)ˇ٪( H)amK!zKF)Ide2d͢YޜDEJK$ ?DQ*s# SSn -G}}LPO7e/aOUkEIea/2IفIxyY- }7l"#bNiJf}fq-E\"Ø{ZD%}f$OpirnYϫ)?J!Bj]@.E k'LQRV s! dĨl2 1B !_Ԗm: ffYpvշ#qw?-no%VGd.dNd1Q쾓Mdԙ@֐?J*hƎGow873=øj.dq]/;|V"F@)ϺLc hUG!%>X>2 6~̽leESII , x˯ǞB|7{.';R zOSB3һlR%JVUq=>3tbn5O<%hx ̕ RI%7=^^kl^dև"1BH*9,XJH ]9$ O VY}sgP$gұᮻힺvOpfewYk-;=M.ˊܹUZGvC׾EǚHZdܔl\  fRjRaC-Jb H!H-!&|LցR9Z1f@`? % 1ldCV31$^8\ ]֎yvbmq(H>ENxz%G)IuwGst(Rbte?OC"p3o| !9 %U%Ŝ 1i&XOʢ KH;X+Pl>~O+,\)7|LE[ɷ?/~Cgq _4L#xOe ibG 1fd,%dٜu;v4M(!G"FކС ŀ@eNP5Kg~@] a&kKS܂pT˯jFM۶<}fRWί{)TG*/[(2s=HX.[faoo)˒LvD]1rY^/RRn?ސ|_ ׊i9=``,mjmm{ Y23_bw>Fcuz8 \/l*MY(HF*^H,'E8;<^3%SVhg>qd 8(tGn^il1b>٣@Q8lAIH)RDy yj>0XѦBo m$߼dEbN+4Q)6jMr&:Q7 (S*mOfn9sB+sZe-ЃxJ4M3fx]w@yb,  8ּHYbrz-PxkiI䚔R =~X|zg_|I{iSM˪(;#JDB m kvt0~cfdA+Q@Q&{E 'u @zV' Ðe k[65af<U<J]H*xnys4v; ӚWn0б5eQcd:zյeѣ'N4̫;'ڦkBLD/H!?__;adrdv;h+"ˢEaDv.fBfmEYgY#y; P&ef"mY@Y#C2rzc%? E@bvf!oB#<$Z*$1JE KH({ׇHY $$OQV(ST(.ȃ*;;JSZ c}P(iƨ=ӏ89BM ,3 ՚ykvm ="0xg}T cH uaαX M B* VxY'AqPJc`L40Ph\38L`ٶUWxf "EPǗ?ţ_凔%%F\^٬x9's ]?~~-EQ>O% NONy,pvF\#w~{s)8wTH慏?8&LN4 g'g0N(wABhM *R:.M]T <IڃOǿO~򇲶=1%8SŢm)QU pJ&8Vӣ%!t-6XKa矿9zQ yÏٜܜR vu#Mz_ ؜G?~'ԋ%[q'XxAQy +J ? 캎i\lϯ$GO}͛cCr€֛v!Q2T]Hf;PݷmqnVq}3 ŸӿrCY.\@"3c?/GlNQD?=>? 3m(UI[ǼZ믿/-;NO >O 5hܜղf3e2 ubEνX- ߼蒺)y?&ϟ7\KM!qO*j"r{;ɡ) SV( \H9+Dx9YqKPD21D jV3mB87#QiǏHp6aYPٽ(&oy/>K8;_RԒ}wG"CRRhP*|̇K柇֊HV+j! ,uɔC,\qOUU8牺uV:?$zc$Da*z 1yJR=\1N{NOl6 o^ H,Kfhμ̕; Y3Hz47-Cwe D;J$dYd֓ !%5z[?=B?2'Rø5/_ IDAT.-EɗP Mqz|>uİbCt-g'/x R:(d]-f3#D<#!X,Ŕ0Q%˲*!4YrAJdTcyek$1|3,Kgfvێ R#&mHZ"u§' ёREYtҸ_#ei%>D, )heKbs$ez؁bYqLF=gzCK$DbK8Xp.WҼMuEfSB:lVg##FȣGgx]5457ao$VI 4q)SvA(IM&+(rPa@O"m!đfH>CC^Ę1wLhUm' ysxosU(.*) =E03eil֧4(󳧬'^ppOU`hmpspx4BXM# Bbq:|O((pvr,r2nۇ)&fP'n# 2q[4c%"v{~"ޛںY~vkԩ*SƖQA )\ |$n(*!Ŗ7 "'q~GŘgπϼ<:[k~ϳ2yV ͆,n: !rѳǎP%Ǭlu9 t#Q_ԖN~:*V@s?|̏S/ י/|{!0=Uˇ~7K,Kn Z 1| EQul1C^\-t>%KXYÌdHmG>Xdi^]֛[lw[f ;ۑyټB{8${i儮=޼(*Bȳ$BoysrBLjg<5Hj Շ<~X`H,-q>aD>I5#5n d¦✣,R`tnCa$h!h2}8\~ TUEvEb%6BSvmL*t4£QRxB{嫍Q㭏 +/nUܙa i[,c6qssdBbI=MBT5IŤ<qVh2׼c6u5قf+ w7ghj4 >F'/Nr5] [rY=UUEE[SŚT"Ȳq,lFBSf%ށ,51b'{":LLK 0bWט$aZVc۴4vxl,>Dg-\!X Md=(ɤlޏjuo/†qND.AD)+VixE1Yx+ A0u] I4YLf }ͻ?ct%F!0ˆ-jL nSEk>&v)'X_ޒ5 :j]^`dEFHp,ԍk M5Ŵc tM=EC0uL7wmGLȲ?J"a?}&yI$KWޒ%Nj3^zd!q]!$A?t"i^՞2;nq%-N$c@2ɘDXV vD+C7x6ۆ옢H_&9Gcn7%%d0"'M(yH6g]еR&^CL-"Difv1ZLJ;c[YVZ\"O, ڮr]3Xh: Ja7(LAdY% ƴ .jW[rWW IMAY$,ӌ/_q}}*x'\\\lBbcVrzDi26;65O^2(RVǷ|~ۨ "$d%T ӯMC;F7HHE@Rl6 jJuBu"k-%RJfu:zYm4HLq}|" s$Ei Hp/x4JD6O&E`.HLɊ$r~6ѶPS"FH(ޢ(~:!iڀ l|@Kiα^"L+*28.vh$O j;X6޳883J*f+W[PS ''^ CH2͚Fh i^p8mL;ۋ8J %Fg:thQ - -x7?C:?%G9|ͤ,pe6-nzuUJn ;`Fh} ..|lV-];b' yDih54@ńе$Fw; Ũ@j025ȯ׸Q%9}穪1]" q dGS8"k I1M~a:F2R==W\7Ns|3j»wWӟIU|Da]l l g44횦L3AW?K/?h1eyq9>>F{n.̦$In[4*06ѵfsao5! tH9YC$I1g*Gl=^CI/ߛØIZxRVteW{!䔡E;!G˔ĀHRN$MžU]ӑ%F%XN5Ӊ'\RX\)&UTU guvivv;F00!!QHv!@kIr R%cT}+4>"%+cGk , 1!ꏥ+B(Wx`bG-'4cC{PI^a %#TBH HaR}^-]bH;pn m,Ƈhp#!TIrq{nd>g~-EbmGo )(Yʲ$OsKDi8pyyyTUFd] Y,%n\- }cG笖-Ɍ$RO1zMs}sO>5Z%rjnb"#gجRDbU'xmK"cFVMFGkƘ rnO" ѵ^Se%`Ƞ:A w5Cv-BIGIY"md0Jv2)YlVW7/lV+Nnh%8g\h:ZkDqhhE1hǘ,\lH͂gO`o/qyx|4-`zb2ꓣ#Ҵ$%jFg-[!|с!:tD@I-i0Z&RB?")qIREǤ2>]FyQ#3zR[L1ȏJs FC`t#y%=WX Xɲ*kD!bh#S-GO׾v7Z#Mbr\RoksdBol(RMbq20ۛ+Lzjь2哟?XFf̀ z& xIY vf{O? $DK^QÞ !5>aBuBhazI >n$I>uĩC\JY7!$ZE.8z …IcW;Q{u;PhJLKk"2pt"B3k#/<^sy}׾RD,@$-cMnĤaH(ITdʒkxKcwT-֍8>2%5c@f$|<`q+z÷~锢qx|Fy޾~#ӦޑcoOsu})-Mѷ6&܀0K_]j|zx;??.?_Љ:x-MʃY8ͩfҀZ~uprr8t^L ?F gdLfRpvzDp=̫*s%sIE1Ew$idlW5e|$EfIS:ùPDI3SfkmB!d/zU"0NH0H0ZtZeՊGF?0h 1oGY.2Mȳ f˼|0XQJG x!쫶>@h)F̰8 7)y6n #d+NNYo ~@bağ}[q I4G4@Ywpgorv |S.o`V tnC=#(K7t1eh;NΙs.^_[D%ںn`I&3=zٟsuyK?XF1 ~;ݓY*(Ʊ,UJ8tX,+X;PN+&Yh~ifnGTba4 m$J?s.|U5a]b- >VV2GY.{voDTUN5rvzwqp8iZ.֫P)o޼k;gfZ3ǧM+ݖ$^RUW>6ۻ .OucGGfsuחL&%4IЭ ^'mGMC׎ I4FW# j0:ڮExGk! Ax޾nYJ2NNӶ7vl60BO`d_?5j? _?)_z'_cʉf:-5 ܭCF, ޡd=Ip~'Ϟ9/߼f^^_nY.{UuJޡ?aGN &ɩDf=K)lNpҷeY,KY\"MEt2ɳQ D(aH<6nH*X[%IG.V'^jC֨D8=fg2QQ*u9ľZEx{ƊEI? D4cpɃ)}Uk!&mdiM4xoF+Ke"SaCԑ,@9\PHl+(c m4R!UVnn0[WWq81uAz)z$ ѓ R )wk.m[!/R]׍Au8::eRIx)örp\Q zlB%)99=[2)F 6 Pfp1u- jR=Z"%:4MXm!SMd@EzwxI19F =**FkCUeem"&pRh#Fem;m)HAU4i2AɌqqCO,q}4!M8ۓ&Ɂ;T;Ê7e {^X5EE۶}wh#`@lϹFnf_OeﵨamO"~Çh; {} Ii4F0-KWWO_Qy"\*_O僯+\;V}hIV|]sC ֵxb]¡TT*/SRNPFFrr#>|xpmJ=qpox!)*!Gq ΧV1,24G1 _|J]guRQ L,~`m{ʲ`;4܌G(RQO%! ~*%MSֻ*)&Vm $>1/ b۷W,5i!IRTėOwwVlgώ9<<9G$$gཏ靬m{6TEsq`rb״1U ,IW7뺦TqѢFf-RƟUSo>H=}3cR-?~|P)KbD7=N0-uG7/Ŕԗ=Óc]nf''s-ZKN_I?bMݏ}yrGߍl5WY8d 1٢(縭[)'7[5 onpc$ޫfmJb>8|` ž?w-,3 R0IFxG9F#v0 b-KI?ÀoqΓD34YvBKMVe{G`hRW҈.za!1q~~CVnqtrnakLHUخ#<ĤȨZvWeѦ'9O|ӧA0 =ֵ ㎦*4ݒI&#0wIeӌ$Y1Wh` SL',fewqKC?)^ PzT= $*,w/ lL,aܡb챮c=P[:b-2C:^,| *NSEȱ`TI!2?}&),mdjAG,'1!ErM?ZPy~ $FdCkl5EYfU^9JBH$ 2ფQ ŗ <&P2&8:;Ż OiFu|Y܊҃,͛[ƾyqp8Lf춷l}״5LJg?#޽vXC;"+Rl;..EIN-N)˒[52H ,moIuJys03E3?[''GxHnXow,NΎFr }?ҏՒ]3bfq|Je=AW^_/`h;McL)i脚DKƲv FV{ڶE%6;& IX) B:LAUi-yHz IyQTkAٰܓei- SFsqqc.;R$dI~hUsd:GiO99zs5mfDp-Qʒ̧Sb-=Z*noHt׶(ɲn_4mrJQ2T$nԴvpQ`$jzZz#II0v%ioJL#&uJ4H ΓxDd:A7/zqiYݯH/Q%y!^d)/ڈ^<5لi#`?Kk-L:)RT5}Y<(RVѬefJXcQd m8x]Q%RJ$(2fk,G4;쾪-~[9yay]܂9i^BJS? 1i# ]50c}æ췻ݚb{K7xtBa2w-mqx(2ij1]6 > GȌ0g1;`Zxs>5eo+./^SS9ƌt ﶸ!Ki,!Awi;zvُ_swwdZ2RfYmTJ* b @#ɾo}r?'p}bR] =E~_H /?~O7ٮ#UJOZvVյޛYv}]FDfeV)SVy@H0@ `3L" #!#f DcɆBeUCY9t_1X_FIbOnstwv{%@k@^j]GY cܷ5YR%thh-Yud@C&,c2Mat%3jfB k͊%j뗗o7iPO~d{alVSFE@."=eSU5-E{^]^_2a N Q*ȝ x̔I]s{a#ݯbTQ`ǀ2ūe_ YVԓ%m1ñ0z 8YL'TUNpO?>hQguR2ƞ^gf~q1͜t]ЀL@8mʤQYƤQJqwfڦvC9Bz"n;:Fж À${-%B#/'3Ez|5J)CYwqO}w1$cTUH`6z1 P:"9}wp'O0#=͚a,Rj& ͡c쓰(K?=to^(8=oFn/[]3 !RLp<:Q;J`[sX"Emv`3N PzR1o=CUM9n3(ZMá9Q 6-U9C1jrN))ܲɊ.9qG#Km"PRP%؄v{OulVqr@k|1 o߾NkCMGQ]7')}5 Oj& Az?Ќ (3^ߣpwFvo+13^oozCY6KΦp`24:XxOgι"?WLgdZbIe=w[6aBܭ>}f/_ZlpH~,O+?VL12#JEk=nt9;ywom ߽7#?i-1tf2-S-dRald#&Kwޭ' vv7PէՌS;,+fӜ0 g[nč=&]z2-jF4<{ w#Z6mE*1aܑ)_}}6e겢i3- Ʊۓgex>"ig-@2ێZoAvh3a"u˽ڏ~o3L`kG<Ĉ Glރ&^BriEKs{\h0Ŗ}{EVd)dOx)=ŐGIVSOH&0 cmKu. ((Bstd:b4 i[%qDpÑA&Hֱۆ`Xmt=Y>{atcG40vGvdznEm `;ڑI,+6ݮ #D(Ʉqj.//YxxƆA 4#u y=!Jv@kjU=fLf'9fp}(eƖ;t8:l xomg师#Xo7|Ku1ZSd9DeN9;;nGih3q 06 Obl6cc=]ׁkSSiqZdIdctyUeu b 7z4YM wTbPSLnЦf2(7QjA,Nyɧ8pyOCp1ZKI*RUxz=@O?dlsl9=</BXyqǟa4?| ϽaQHѷIVwGRrS .PDUUVt|ZeNQ1uH?ǀdgFqc˕"^;Fk ,q_ޮ+P&mIǟ|)9nln X|sϸAx>3~?)ْ8W|%ʀ%` ay_c#ӂ !;"(oeh-J6b>ct[qu{8vN-k֛--%R·#AL־mPGXm4V 6]2ovcITΑu#'3жc4'Bfl6t!U>FRvXW"#7($nH" G('quuuA.u `YVhF`P2CA#Et2}; ӂjdW4?j*GSCCJIPc"JșO@)V M}'%ZI!EآxЦ /_*c$ <FKJ; -@HӴ "֦6(Iܭ[,3 _jcĻ@ 82ܘfEMng_p{{p)rpҏ=:Sxq31{g,NKMbǨ7 /) IDAT9p=~W3C}#:`?ի#DD3+S8dΧMr\5"77Ɏ=1_.jfӊ|JIrP*$%,cEy!Fa }?@C`Ï@Y-Um-﮾k,Ua(mܿwgOPU RFHYeMWb!H5y^{g<|}Cf% pv (᧟O>!Xϛ0FK6Bi\D*=OS 0f,"'Kc2~˾i~X!)R3%=6Rd|H?1DI 5I)ᢥ:65RG\L!$p9l>(;ΘNLebɐB0jbR&ѤPyeN1YsBʴR ,H>k.xSsLaOwmCC;64톻[vP8߰_^t},I1GQQd3dfʣ3\^X&Wju0֌@ﹻLb%!ڦKJ˜K̩#8ҷ)EQsc=i -KqeZr94;R)gRŮ)r`:3\ް7H-eل#1 *&G"1&bzs!}Oi3de}0 Cw,, nnnVhk xFaED|:G"蛖|tlt_7Y|Dg&-8Z%Hq x'1,zAYd>13Wlw#YVQs.՟{èXǖ"|6 9 h}OQH@CSU%ٜ3ΐff="{kV ]oy#/q~ޒ5`x|z# .~-!XbD"k;n֑)۷ ۪[)cAxR$\@MF:] k~i-&/QJ0|1d6rzvy5~#"GQd֛-_o^}iX?2h J焠Ru8-Rf|?xE{u+6.0/-["1=sCptcdt] RG) ׮KypIY7,)Y6x˜"]'~_g8vYy!秧{~/{O8=ެ~w Boxc1#Fv=|{\<)"ء'50e9(NNEFi2HCwt*%pw|@( l`GV,NN89=Ak0qn:~HF=DCWʬXq=F]0Ln'pqhnoVѥO5>v`; 38i `a?MN9BcC`}6MEAMkBMr69T(RIxݮG|Z@#ȴd91&|`:_PU5gLf3ގH v b'POgޭAYm(\(m:EѧHڤJ#]ߑe;v"/ H{1prbxgd:_vhQJ$J;G::‡qzY~vd~a]w6˓3>Ϟ}ܠiUёGQ2ed2Sك)وsq eMN43!b3ɳ )k{|B6|x9/_&tۭwAzz1ܠ|Ƀ ~_ێ7&G#G*%B BĘ2j)q"t;k#)ΘS޽mk99KMT])]H|HbHZxdYQGW62̫̔lC m@uY#D2UFgR3Z DĨ*M%P星O 2,G 6Bd$fSEig-}d,;tn؏} | !>6d }"@mm2 iRlzѶ cK4Hjf1e>eMQ֜ﹺڢm=u}f}jUmP"+yE9+Q v;2SoDY{L 8=dힷ/JYHc˛Dpzv.Kq2FOi]"#ɳΒ>tR+vG=;Æ1Ǝ"Dn TɆ0 cz;4,adhʲ.:Ghw eRvFuԵcG+h}r2MgbD#noڞ3tVS6A2,(y%`,ath-wfصdBƞBTqx{waߑ%''ϓ`E6RPFK:`߭齥iV4&U;>G\|Ëo4=L98x%z^8a:J PĐb =-8*<׈"O,KҠ#(Uj?ʲj 0̊74`qXqen.obJ&+*3a697V6- '''m!.s&)nQS՜g9{@kmخW<̦%nm,s=S }lSRsw=\\̸8;gܯ N4XhqҞ'l=N,Fo*޾]l{NNK<׬6Bd!zMwRo!4W  _c:I{S?iV9=y~󟳺Ɍȩ)gH'q=)3mt *b3S?'9 NR%1(Aze74PQx%B@Ue8+lh9Yޣ*Bܤ@$g;>CStإ} CV5 vH6 Ʉak::APd)miÑŖHRH8~$"٬R{]]0[O2CK+И2 d|d}8Pqda-BJ/L}.}8X$%JF;J%S6 Xc2Iq{qղo_bn m .@YieY1_^9z$6ec b=hvmG&tfe!y&QZr;xJx<Lށ ~r Η&Z_qBvH-J'0_{5e^PCcȤbVzqG Yn#p~`6-V0)戠S~oV79`G:4&soM(ֲQe2!$DtؓٓyrLx ]izRiӷ{¤뒺,膞p<R+DeTIsD>cR/yp~dYE]1;vG&U&¥A)C/*"H!љZb:Y=)H%)  |RJ!Ҵ=3J|o ! U^K$E|D)йĘZ|9N<-΍ɅWA cR^tV1ZO9OPjJ&7'<{)~(Sw#qd\`|v6b %;KDSu1"i-y6; t}.E&>qS&EI]hiVw7\a IQF߮tyZ1N>`1*s!%"vY*HD@Y$jvw#jZ 1F& ن dZ1:=xo=~>Ϟе ]n2 i9ts6*'|/h1`2 D$A?(.Ym[\eo.ph:lI=́҆T)긑O1R57R${&7#PO]tMn* qdM70 d@ ƶNsLv "h{N+)l614qĎa{O!c6m~GG R&˳'y)݆/dZ ~q8'? > d8ZTՊ˷LfX:t0[h|Ndl)HY*4}&wƴHp" HЪm=8Xɲ c0Y CI7P:QS> bre10(3ILʊs8YNvphLX7$LMs [6&ćhJq$~.]u#KJ͈ TbUIը(*3SR"J"xJ Xa9jiiMjR C3 Ĉu`@hQ3s2?:JE/ əiXw! g tr:i$p>a/JF[`%!j ?|ʏ4X'w&/Q&+Kt,JBijOyzy >zsd2[yNUgo߾&)ZM@>rg:S%!*޺c%]C^dx{r6[67=DŽ חPOrlVbtEU̪ IAafD0X>2[T,N˗"*swTe{Gq2d>Gcc{O5Uz f6y{@Ȋ5ݾ>g}>ƣU84GwwBww]?/?nwg2-/_~vѧzi)AtNoy;)Zem.+w=!$[B1-fsW$Xtb#~DcF,(ͧ@ba(gH1Y$8˟T;݆@0E`t< RhtQb0dCGq~zwӳ_߷(qADO^M겘i8d(p1>AJ"# +D/%3~DgIR:# m9lm2H#γ=4L)Lp>",hT='΄LVGMf8RF,|Xc4EǑ<2Gdrdi#1}'̧x78[$DZMztAU+|lpqhw q!Eef$G!cX,ϩ4q8;g>s cE~容\ۯ1M(d Б q@`) GVO/rCd˗%4' *2!˒X?ل{qpW5mGtHeF nBD\):n>2 &9{6w-&ϙVmYiӵX>₲ٵ-w-< 6|gTU"PM+T>W?=2#}GU.b>y':o._7w;tRDс 633X8]>yG◿?(k0TUz1:O֡|G,'%?闌##Yrv~ Ls,Z`tUb &*_ūw(^gT /|M]L%y3Aph}j IDATdvJnH ZK>8Ͽx E9)ʜfx1o~۷Y̖eз g9l{>x1~רg"g[Id8s~>'|n6 @VƳiZ8 9ǿ;7?];$u-ͱ)&e2=Y8ց,Ov{CdF+2ɲIg=BX\蒍{'JD"0 Rd8[ca`yw\0F_oh?[r|~0@ c bB Yjzξxg\_߲ٵD0y5^dFY( .z6ΐDh Q )"tmfs/!O>F;tѺ&(tIYLٷym{ghS[ѝgQ5e=ZO[E 'H7#OǏ;;{ (,N7&ǹ2SB3v~t)1Z !,O| y&UBIqU KbAURKEJ*mw/Ȫ@5oPڡMJ>%'<5Jf4=a6ٮOLߌ^լGEcIw+ݳZs{癒$)d`hG)B dEJ"$ݎEXda[.SU FrRń1xHD߭٬o֑90bxIB A;q6_ivZ ,ܭ: B%O0_hmَaMEkbȰVs|Ҷ-笖G߳osyyujNiՖ6xrYƑqs8p7_!bB s]|k6}ӢClfkƱL Y'EE^ivYTgƘO9??g2/~ ̗9dJ߯GXq1!i)oТuRHȊ,o!8+946[Cx}X/ՌDg~pHh[E?c IRt?FIyu5LIb4+TȨ%,^0M{9՜7ع1*nOiZgt]ԋ'*K҈h >飡 |L7W1a`,JH\\0}1 w7KFbe~׀H荥m"H~gy?ɿg=,?/>O?eIjR:Z\ͦ>pG*={?__HΏs)_ l+޼ 9:FcQZ -ӋѮy: H0c9tQVwH&:djZ_ 5G99MxUc9lOߙ!88D5Hr2)qJ4.XG)hi,kH~ޣdD͈&GBɘXI$EM0#Eᭋ+H$ ğ@<$RXeY/n8pX U9pcQȀzQ])*zǏuv7XF Z,kG׉3i2e?*ibZ2=)Q!gs{ Ew$]d"?1}n)Edz߭xY蓢dtkv`jJ,)EmV@x-/^ F* cO"=Z|v W4mOӴtCV1=NI3tcuѦ$c/%s6AV7NZGW^ͻkb59=Y1gd3J\'[nw?sfQң@np=&TUޯkN/HtzS#d0ZF.,}[eFŋGgx/h[uDJDTQ;.`̘R)'`X8!b`8Y,8pM!E6u'OuDHF/EEM;:K}VyvB8ZG|٭{& ھLV~]ky9U[ׄ w ,к">^;M"HJ>WS$[CS:0xR CL E o qj97/!MK4?{*01I4.؋ ?x P=~/>~h$}?D`_y%>t\_d_s{[& ˳s>x\\|wv-,!s(Ҍq̐sVsoxpbׯ$CCf z泂*b[h9w:Q'0h2wo^rsobG?e>b2) FHC0W)R>2i#Щ@DD!|AKkA?I%BFxۏx0k)y>t׷E2$! q stV2N&)~Co%Z4a $&H⋌P(|>kk/;B2R0 a6F)Rvflj5-@usf/@˜5vNĔ3h.V;XtV"EFUl :IP<~~hȠ*@4M"si VXq}uwo[z j:&~$s!.&d'|g|/0,)>+Q''|7|;e4={QSR$}vHܱC,b*Kuy٣ b!f<G븻Gc G R߼n D2abb%ͺ1~Z-2Q1-CqE#FJNz9vgTISIж&.d&qX? Jf\4Ih!iN; Kd16#~Ɓ % I$;Mb!꺥CI"Yo"âѻ.N@^J R)|˟pzNSU+Ὃ}՚(Df(Q239DPx(ywP\{Oi`$=bQU8p*=e9a[oHDբ``GdzPo{&gr.rzK^y2_|Պӟp<}{G޾c{lҷ^{ YJUT }ohLV ѹ_pss%$e3]#/O9R (7k˨Ui~ex儮s{{2N3HoygO89="0_tHuX;$jN;ꆀ 5+hJ A:O^iе6kuk-XY,Йv.nMPcI([Rs삣#dJ@c&u>$ =$o1 mHhPJX=ZɌ<ϑ֙ !:w-v~Py *q2|(V$%$JZO"<{~77L2Vdل!q@$JeL~ 7RX|;Xߚ0rtV0DZpm=m?h8tΗ_~IZ%edz  IӧOy/_)v_s}sCnHdloKnbk*#hqt?Zʼn$I{p䩌b%T|[..'~~N5}/ޡdBtO~5>~M;ki;3`.$ciۚ/vu_HQc#I5V$ĄI4BxqPJ"tT'H, '#tV蔢 y8a0FN0 8\"sMw$ JR2]Bj`MO$x'"{A V|''R DPϞ0\y%777gPA1DCדf2x+H+č{ߧP"gRōN<"H݀w~=xG(ʜo_˯ds2 d2%l݆]M9bj4ԾmL+ĺwXqtr)ΏO~‹8ZsuuONw7|Wpib6a1(uqss~#YoFbR۱&5MSc YŒ98+VrCY@ "z .!fCUxc fe]0:ђ8noCMR?{w=GV\]]q~O-y/9CV(3 G9?uJ 4m6 @% >h gu}Η\B&S>zy!~sEpۛ=]Zyxӥf4ힾ$3NN?GIq"-%ǏOyxŧw0C1dZz!)zIO ڶn&`w~M cG$INUL/hϱ`6[prv3U399z sS4R6T',s=^p>U(E% &ټ쯼ŹE'ْR"BU \EwPUVYP"5"Hl'2_ 5G_="Uq{|s̏xC<׬44,B,PxGw'JnC9ua}N[6ߠb2E(H b*3)&T OHTT }Jtm1v KxGy~wZy1eYq}sG*mz k uڑ}y (Gz{x|ټd>)lv:CS ltyK1uc4#'' ݻwӏ#:ixcTwhJ`\Gs,c?N!{" qsas?7lJC~i|MXoPs^ȃG'd9XcNVlopWB`ڂ t {,1V,g,I3k#MĤЎ| ͞v I^޾}5ޏtmK۵$ߵ1Fȑ=m3=I,Lf?6b]C^I}\W"1X?ҏ-]\9u$˸oaM\ޅӻ2; ,(gԷ$:Y_n(3N#w܌P|',\tqL=CU?o|w;n7}$Q$DK>+o&.pws#(&ӊahq^7-uxdFsR37gWz|vURmێ4cR]PrCB֠RAQ*VGs}2t{sҴnn8dJa,Kf9HUFV( 'g8a(t^&ʘ(Lb G%"ZƁIV%/)ZfLVZ5*.RFi"^i14븽cRjF\~CBv϶~tH@ po)-4Ip.čAY$!ӷ=ۦu)eYogOY bT;x$ K4w[Fr( ON( lI !$o ֣ΙLV>TJi0(u$(D8ԿMno<N+4MM# U+rzD |X(m* $i0j.`“O(V Úy,rc^OF,IP(2WM~7 ?+l5??ngl%TaKȘG6yL9e>;!9y7ox5o~KK櫂АDuOUuC$ϟp{g{W[޽qwkq~웆R/ŔL+vuKRB(ѩ pt<*5V9C7@ v4?D1ͨwk_wmG9-Ȳ ˣϻw*3ƾ递rlZDλ$?-iA5IrǶY3#RE+#E)NHF #钬2⁸iw4# V^:BH %JȲl7*;샏u*c^|~cpdR1dϾrqJ1&Z1CЩ8et#гȵn6oCɔDR]LJI%` k5vfjR T"i?z*%.4#IY.3X,x=m'M3"MǺYChq;OytZq|Fݤ`4znƎ-cb1:{mzrݮW6g.߿DERh}s˷$>o'е;^;W-)>9Yɴ ,42h'ӂzCgdk$i:ì`a:,1m6yh :rT ~cIDh2 1&p& %V*AX\P7ky0Mwّ4Hev$@JEz{P%,<~l 77wZ2]q6_#>PUe-$?W_p*'xPKq1-u6RIQL8<(HrHS\ܰrɾąA0B^0D 4}gIuI^e38<%I<栏p U+):\v$8(mW!h2 "n}"hR<.曟#v{fsry-ݖq(2P>tG54MC4KM ,9=vs.Y/חT_џxKzӣ O5F6xu76K$ 8!DYXiU6pS%B$[QR:=Z( cH8A G~11xŃ +nnnS ]kc)x"RBgTYIMdT+\$n(J ɔ$$*G%W+T* c Β8HpMvԐOYBaYfGg^ e" 8)4Kp45tH [3vM׵d%,8_}J3Ax1dH:ZWw9K99q}%kAѪVu-A%aĻnn߲^S>زNJ\Ev<8qbhEbVoٴk5tC8IABM2D2 loJ'iEQdEdKyI"{PK=Oy_;vD'4OfZVD4EQс:āEH)U%Yy3o{=MĎfws{K<(f/Ztmˣ0ڎ<;2,XWqDzKۓ(ғi3ґmQ7864]ܔ8l hF)Sc9YQ L&)P Z&ٔiqĸ@?PY^PMSt cSeIxܓ͊kB|֤I *L"M& + 5Xk 6Zg<`ɒm\,N;}͗_?6u0[$~A2fG?nF a:s uHiEHTxk!&C~ۃXHg,E^az'>dYJӵ1ZExKKs꧅ć} Ɍ>/>6,=ŜDZ8nXCVh 8IӔ,8;RSj`VYcGL yԼ`hbF;x0c#`˜fՒ1ad{WOo9*ptehFf@H'xQLSȩ%1fэ RwL%tJ5lFMyssATʳ>F |Ưſ§L92À :-HVU n8QUiquVL3 Lu@HBBϻ[f>͑ƁKV9ȣR*I-c 2-3pVQD>Re1' û"WoonS C`<~9rhN<Vyhd8k$JM.}R%v:!=G8'w@*@J= 5.//Ѫ,"]{!Ȓiɲ$ƖL2дӅƎyJ$% '{t]{JF#N{Dҁ,#  ]2-RTQ}ʛ7o~sMWCUDuxwLf+KA!P̣#1}qʦYoa%rxZhq('dYl}O۶'\/NYāQHRHP`;).r8"e:*l;\8gG1ɜbFÀ CЪ"r_\r=6 :GG M f!8oN0koEgBhMӄ@ l6[%˳YL Pј8 $"\B(W(q͎24!c2Cw`2[DHjHrp8JtG\_S7YE5xx~Pqk21 {էh-kL;,.n86k&ӌ4PLs{ z?Lg%I"Hh y>-JUzaQU9i±M)nW]Ce8uߡXxd|;ҙ׻ R.щdRV=شQ<.33X3f$S }[nooɲ|G3Z|99kL5etZpq6Gx50~Ҷ-ݞirL&9O|)>O~?٭w iAG}6/Ku7_^|qwB\Q%|-?~8<~xb^r8lY^QV3BM؎|#4 ڦA6|XeF~+o73kР1GfC@ vXƶ 9;?c߱n)'%Y2d1g4=`]0vw`Ù)=E#DE+U"|!,f!@MȒ)h6{ww\}ɮGj^ dqH`O[D'8k*Eb8h,8[>ĎjF#mxww?gk-YP!KpIHmdƴD)H BH3Qc,K$E(SER`l4yI1~P)~8GN:bGt"Uepw;͆k&Lf WG=}3C0D%g )A]7]OUU\>dX% 5'ﱣk{"}2a:1=2&yw󒗯?ûJpОs-³O1v|s^^}ΗW_Q7,3?2E)P JkL8"cIJs1>`2)QZp8y%i*³(y6=Iγ~˫Ϲ^3u+n]k)AC$ m3P)?yUrx9=" KM[k"qèA'!ލ8fxa2In%̱ݽipfb^Iy*x;0wwy]0_,*ic$I2&gg\^>`p8 [)Mt@U&bʃi=]_C+d2cZ-ɳ xT0Ts"PZ./.@v-Cb̀'6H0u4,( p_򴎃cUG)l(cFknWt-6yx'g<}_{$GʜgO?g8Iu͚fCjސb`Xo-c1qt VIXO#t 'fágU5#KK޼y~'dKmwz-" +#Bj\tMi5A986s !O0=wwv#MG3p.0fLsf9O{i5*K:M?bgcO9LbsnF; ZK2IZ3<~rɇ=~{+~Hzl[s8gޑ$VX9s}G׍x$ibM@ VȧHP:a:"eX9VdƎ4Y~=AHZDThItTLhGA 帷ti`:{_XTwޑg)lɓ<hiC0X#Fn0[J{^_~;Ord{/)`a1[a9΀Fz142c6fS usdߐ*rʤ@#б݁Nv7&$qP%)EQpX/M=YN 4t NRphH$:a;6-MF F5B)4RRm@u Z*WvTbzP$"N]t1:\80VFݤy >߻dϿ(+>wRq{{DZYo 2v;]1/_^Ӹ+~ ]=`Gm7o~s&roj3=*'L> Ղ}ٌ1?,u{$^"놱6+cF e~"e:%[ eG9T"T.2 ,=Y"P7iJ?(T*ȫ y3_tÎ4M2p8qΟlIH kzC=ٔiq~6S k4+E͸$45&HWׯbǑYz52)'TRN1#!&vT|9-c~^W>3bbxxI^ dL@401"$%ISN+Fs/Z 4#OcV)i1EpQc|V e_IEpwyN^NxAԤu&/$i: O4 t]4mdIJ̦Kl[NyC՛W,QLTg1fČ)uAb~N>(3fx2HfGYax-[dbmw~9[ԂHrhYE?g?޽a>]"*Fkkysv{Jel>Ypg>;Lƶ$D'ps/lPeNK | M|m,eQvkh#5df%Q`G.փB$I)^4ތsm֤Egt5m'{I3>ázrB,\3U{9]"&Tr`d,K3O~=C'֎i57/QʠQw{8YQT nnƖoÎ=4l nIӜDEEe]qwu=YNϨ9ug:Pg 8RRT}L ѐeEd2$A hztum8 `_a݈J4eR=n8&)fp}G%md]|>g\vk@|LTe񌃏Yc-‡>2+w}}MslXV(%zɮ0; %Jk6m;zs@_ዏ⋯̧\vv5m=p~>o;ڦg|yŻwTUdswa'_|nw{//;Ǐ?|w0Ux( 4j }kǟO Mi"%K5o=ZHВ-!XHe BS. [np1-MA .˜C':e19JF֝tCжGtS3BfOqyJaTDbO;k{oJ K qxOS%YM3 88C,Yf?CIhԒ$O()Qbڞ{@hkS*ڬH! o\v(L4dXx%lAQ4M8Z4czԎ,)w0 aqPt$+)_]zLSTV+COC@:>b:ַ%O^lǸq釆jl6f`9`b:M(M4X%$IXGvm"Q^p4cu2c.&RP+B2A(ECӐ&1]MZLc@JiRa'IDHʁmB)Y#FkMeEݵlwL+)tɬ:uq$X C7`1&= A*b5LoL03*<3ެ`ODۓ^Gh.DP3i4ZǞm𮣪4ÎJb:;:Vg O# &i6.yB?ZBp(6R!GF` ]0f2ڱfl47Z...tŶo0}`>t H$Pˇ\X-fs}vAI[ljE?B‹'~ $=0DHKYBSbI{l=9x~3[KտO]b~cGnnIufih=FAP!wAFְi@^8;R<+q@؅:4>p-i*pnDDKc%E*P*#I!9UQ#llu=U8!n`2F婢$9YPUr~hoLSͰ8wzGaC)"xLc0h4IGhn !)ؓv"DCwsbu@&hHUt΃|[?d6DdJv\JMy3k ҞT=ֹ;>]KGL$XUEQ fXr*뾭*GkJʘdB|2;;v 8wwy&y9By12A 7A2;3409[e%rbLR4eA.V[^߼d:[1ڀmɳ'n4<ͫcߟɂnIub 'HMuw$I25Yj9k sHY,V vDӒ4mU[Ų]À6TS 좤Z>}5o@ &V\rH6ju"I!}"xCj"p"?DˤcGLAx !58k'}34aZ%m->PlȳR&3&LfKn#i#9 _\}ǰ\ I$ϸy3Y$"ȗ_]s{K9[1 ,R}&K1X3*7i!J{^&Im;=/.VOHc4iÍK '5AgAƚhHR?|@ ΁tF8T!׬VK/qJ%/ %Þx-]7=(!vFiG,%V9>u]_<eL(v0z>^;~)!1Ϟ=嗯!OɟܣJN,8axf+x$ᓜ9Ź=kIh[S,"J!';:$BD)=ms %$VӰ-pJ 15:Fnin BI $IF$4vu+Px\dg4@>oGEcHY4Ƙk"hۀybE򂘤H2$9*M)ggg05Y b`=I&i{@o@Y(15 Θ8W ,RapLIX"DLĚa? $Viƞ eYrq~IcZ߭ح_S7C\2)8l?sqbtG%Ѐf:SU)m72t=Ei UU!hgUFf$z#W3)J.XC0L%H?T\19G%yAe'S;AAOݴ$ $1OYfYVfwSG1\%<| Sα^ۼ;MpR+db D&"~)NHȥD% BdH\RV9͖|>: 66{R}K}r84L&EL/@dYA7=~hy -~AQ!/&?BM}l,+k,*-2]cq<5#ZJK={o{3;M$A77ן7?O-]g7c e2 4;ؓHI4̋Hƒ54xn(m8:Kљk$( Rl2%=;zsDe2͘.4Rua#JC˄bFNcE8b/RMp< CGYUL')ᔎL&xDȑR JIF͌F,"KCɔo,) %VQak E9etܥ;%i"IMNZ30x f0-C}=C^1$ DHT"p'Xo?)8cpk8D fwpBd 6=J{n;|hp~OQim%IJ&5RF)hA^uJVC͖@d`e=J!$jwpnj<8|˗km˄ͺo%|ȣ R>'O~Oyx<D35{f^s{}rEY/nsz4[p~|'O)'s3pyHr?~}런X/c3Zچ6$IHێ/~ՄiY  zgoXN CˡяdʃK%R'NQ7qeL?IHE@R}2#yV,A@ftn` UN5/!< AtFsb8yso `+(x{uO-|l?`IL& SABiRJQуfw4e#^g;tƚ4/Gj77loG!Vx'K#y{QMfG48{%Y:/z; DHޛZw^gz3;ŔseU`=#7Ȣ[LE#Ro$B@P/^CKRKvLUv9++|9`]RՍ{<k1838 0I r-BXFEBg.M RiXm &!-2Xl#@4őyUUQU] ǑD!}ߣ\~FLe!`{Kl~`"A-)@kH;n:Ֆht2&<lnEQ*AӓS+=]9JJQ 0(`k}/~f]q{S1,({zٰlqqr4#qwY*\ t@BVtm3F췖*0Eoد={䦠a覥k{-H9;:ڞ- !Z\ H'1>2"[G%tuQI> dfGS65Rx0gWm;^J:l2e6=gq]y]]}>^~ۛr#!&f'{d/g__EwCL#ػI^PR2{w!=Z}%C(^^a@/y{٘7WEbdD4MӰkk>C-KP; -\Gph{ͧ\^ĹwX[߻=7 oБ$gwCJ9ZwM&;NrssNe$3/b"y jIz཈lb@!o/: U-`Qqim7s>@_=R'7yjAP:GkMHB) 5&&BOAIǮFĘ`^[I{ɝOlP:fQ] s nCpα^o |FI w÷W WW7VAj$g4ғx<{ˮDjE^F>1=R9s234=y6ij]i*b %#8ؔr`]>WX,pD%{Ɠ!YbmwAu6T[Hg`GoֿEEbaaԔ=~񻧔_~9͚4GғhPZ\,J3(9J m4ob5(MI˫!$d $]K>>͂ZRV)$K#ޯ=7Kg8R]V.l7k5'wƘry$5&PI ,`H2o;1R!DK"` =RcDyਗ਼=U(Unb!]#z^]+.a\<_дHfx$$ھ;)CQ=쪚dJZdu<+n.qe8JIDO$(M[w9=[ꦥw Mj%h,Vi<"3iedx^f)c4m6t; =4Mɲ*>9AJֻ-B&tֱ_e~)ݖeƳZռkcȳjxw>`2W_Yo.<>q977o{/./i*ASiV7ϾZӷtg \LH hLa:7I=(g\`mGZh$6D:!DhJBX{cHʠkC>c~,wΎd2EВ|̓/,aLf76rZK,cw4mڣD!\`>;!EB^mzQdx7!Oi}H$I5KQFstpcc,r;חkhaFQ|x`pBU;o2(4UүR wNMXp}}E+}}//]I3zÒˋug[qνpz^ r^=˗_;/ '?'֚9t3Mge(J$%4Rh[6MH3ɳg/x$-bW5I  f/loQʂYsHBƪExW :CO)SL"Bc]:Fhpxl[#Fp4?5\]k<&_<^ 'Jciz rP%޶$: +ޢEdŹ@RxDSmٮo1 (a\sgqgn6|ޜ3 Ϩb{OpxҁAiɐnlo$H =$`V2`1 ٬;)g]& }`C)Ha9"I ;qvK^8:99ח;PW+t"1ڨJ@4鐾뱾 ɓSސ$3&uY\QǜuHA&\Q6qYIylBKΒ2- iyv5펲bLIzL2f<4XnYI&Ro! F4&McR5݁sq`jOh w14@`Q5US2#ISVe4(Z0Xy2&_Xa _WBdy%$Jģyi&㒮h¡T#:X/j-lx4b ևTVmPzmR|L)ta%3QꠕZð;V-Ymkt&~-.ākTus"!#&.<QTd"PZm%iB%뚭:!M3JXmcQ|YMD Q Dvɧ}/⯠S-֬7G9 Q-2*,snwA1GSڈ0:Ea)M< OpCr#)3C_o87IM_g &mwq@㌦].!PZrl,M _'Oh&֜ܡ>ikXY΋&hw?^\doHSPjI`4jriWWϫAΫ/ ;8‹?)_- gX\CA^`Ⱦ>Biv;ekIf:1\ߜOPC؟z6jj4w]kIŨgUF]<q#A׍kc( p@ Z #1iRɊc!xM"3MH`Y"ȀlGv5BBmK4)F(Qx 1$ , =mנ QhPk;LjJFXr6E1gPInږۧ\>f}4{LK:EITL4)fנM)-]""M57 dw$iԂ)Ȋ#3BERж-UUQ-y3jKvRHSXҳ0Mc۴4mE%t]zdI#˪VGEnȋi4>FtH@UixwXZд{&1F{D]g lva4!HAp Et)} ||vFp2E9:e[an68aRߛ $yduqpjºg6?EI_>e]`ח+$0dܔwcb_m2uKMO1&(m%8e4*Yon٬!8={%} 0W{={3`<,cBC%T&gdf2esu]sr:e: d@`) F zNaT{^^> Z3d4:]6lVSDc{b1 k[RhjQvǜF=x[?D%if rP$n 6Tm#EBpzeLQC4G|ѻ)j.Dx%//08p"RokS,Y2#ysW7 M'\/> giR@]#d8D ޱn !0(s-[hh-v$Q7+֣#nn.9.i;x6c2>z6SQRb- &9v~"B vMMFةẙ~F)I=0mA'*Wl6Vc:c@궳X;+kǧ_|G}LׂR>;z}G36-/_TQlמu 1J l9k^t w9RwTʡ& $iAӷ'M8UñAm6{1h uU-AVpCG"B m*6#KkhCƁxW}#D]GW74[hMV Ȋhm[D) c(8G8 惏b}O)1トGPEDZ҈v_ 8Du4{P @511F+a[oܮ麎D[YQـ騪wŀD0(&+\,]pz^ r^=5# Mg_w{x W/K @Zb//hz11E*-햙|5YP7kff}(h<9+ G$-8AMBFjPZ"ĎIq;m A$iQ삍[+Ϟ= HҌ__ٳgxyo7G? vkB@j<D=k| S*@ICDVjLHdU`./nq.2G+HLFC/ 6^!]S-1(sf˛ K^ LR!쎲t4]GWP:g:myxom'?cW]ݿd[#CЂ0c0%<ɲ傺Om-ZgmTpB(˜uAwDBLJϾ^%mbOgXiqnKYD#Fo@Á&Xfm| h5}cBJW0FhUdh4ΝSF!?hZt>7LӢW%gsc^6wO_'I2DS>\\jٲH4P%eG^o;_q<{>oJ!c5L3&M :.//طk=wfHX\qzt;ǧ !Zq%Ԏqˢi;lz!7{Ư ~/Ο6[,bF IDAT!uv[N)!]Xv{]T ш'r4z"1(!Yv~W6/[z2+.A-mߢU{mTe+wr}QG?gd&G?!MGłoȊk !^4#CBX J8v*JUmGYHڶc2*IۛUdۤSk8'͆ FSvkf) 0ʰ'sd\N@  >D5)IQ%)hvUـtg? e9:LXn;ַ_~^Us>gmy>x~Ç~Ho|{ѣ7>}wj>Sڽjf2St#/ 62Ԡ,}Q \H &{f!Git5}[Ѻ=I3M[6[Ox?! mL]xFet>c:9O{Y'vSvvep;hJb2M\^^'xx|ŷ&S+^>%57W/m+'O3_#8Q:+ʂ7|vMJ |ogPe afpemO\nB{Eowl;wfH$Z8V_z? R4u]>D5MXB0F3(…ā#Lv#XADp8d<)Yn<%B%(%yIK)WUC"%ۆ,-#EQńۧ ֱFR\9OttM Pd9i%F D<]7z: N"7ADEm(00PJ:{ZhG{Vb2)]EF!WO]׼/#ՖJNa~a[UeeV1liA QTՎ ⶱb!0$II ںBi6("iw(-*#%׷sv-i]JE>۔ 3 >%Bvt풦[2V4ݒٓf &zJp4kGP ɳ|P͘ s{{ 4,O#3*(=NO.?͒,K2zk\_=FXp2{'oX6<|8'{siB9Y,BP;&6!Û"Gh: &w |O$U#I2)y>E>bW䃜KNN1Blk C1O2&t(eBl@I1xJB[RQpݖ죟^@X-Qf]>Ij朮4)ld|Ie"EB (SՎ?G ɼ@g-Zl74M62 b2RźC%dp.a\o&) سٝs/4Ř@G[:6 9k1";6zZWBGmrcܳvd =)A)CQ8/9:KS=$:>mq;pBniۖ,+Nh3zQm)3bPE9fZ-b $悺譇6a+5eed4&Ms."WE~ rH()hz)}}H!)_Fw%?i`L2NOmxڜo}}[zh;Up?g o*xzy/.ٮ;ַZ\i wdUjʉ=mM6]K0x?f(2.j6~ Ӝ Z+:-*B|:0)1~cny2ֳ19KCJ-6hLvH!ɊPBU5Z.PiRP ͞A@6EK>|8ԻRzm″##oZKo[#dL<R"~RE˝BҔ0hh3Zh#QR㼥x' 3A!&W$Ba /S (u=]"I,=5Bvd]Ni\_\b۞ ̓nǠ0fPW[LYyuT wx7l64uOۊ}$V8< γ/e4GF`~5  c2Al^A1G[=Ifh:!ݲDR;wOF<~)

    LAKqpzY;< ~>ѺCYDv@C-}㨱nO5!ܽ{&-Qj \ gqzj3V79jE@ NݎfE[(eWSf%ރLtBTv!xx79J)>B,m EN`̶m F1O(C(7Hg ]0cۚǟ"'F̎r.KnEVMۖp6c<3 ƻģVoLFxzzy6^]x:bXHRJXsFP9ͱN0r2KFx#0& ۡjfdž`znW9I+?ZO= bQ+;4c6?d~poCݴ[B`%JF k SGQ*k}/,TXmcG 9E#}^ZAkgG<\4> s\<p?dpqvь=bz,/_1<{xȻWWyuuKG$A,uEF(qfp8ORҢF&5&$vXSLF`v ҠTHqBs&8f>5k ",I2ɧhkꁡHz2W#{_F5clH4IPQ Vz\$0^ZkmO^i":6T ]`E >Tt&@NH^Pn0v G"|iheTly)Q;K&l(/o Cz;GjJp( uߦh۞(J(I8!]У": {Ұz oߴTU(bEtJd鈓/Xowd"/FXWSWmH%)V G%R1V(hDHF1qX;v|Z3? hm+? :fO}gϿoe~2!eX\ܒ'0eLmWɌ|LYi-yéwhcͦOj ΎHWx=$iL*GPGRZsu Qb hYm35X(1m3U8aTLNPer.c<PÊgw$Q("E_8~Gc> Q(NLG{ZͷiqBFC͸3"0߽fysxBecBmڎi.lǔ []%&I<"Nj~os~MdƘi{ dd>% |dTtyG'Q@q\"f6;`~PKr,kp#X!uWR8c2"eLBa{Hnh[î!C:2$Ihc@xwmM$>qq^#I`zswwuvMl"Sn)q 5?pKݬAr2+ 0KN0>>)#2Xw{by$[7]ryOhldDx|#~?H`1<,xX/1CJVٔx39?"c՛_ ] Bw튪1c bL /X9:<[fwCE'T~|pjsjb@ƴXUw(]' ]̷onp[T4pp^}o ɜۄ^\1M988ZHtsAm%7Wd<*;^<;cـ軎 5$Rlږf:Kq6!I'(Htzj*(H&(yȲ݄*'!gMu!"w[ keD*(#m)O2.8:8'R ͮHodj/nwxqtXoh !<p[kCKCZVTeOV[ےv$1q~,;SS1LW9C(bzE ߰,k}N $mW(9]_ i4b><8HqHo$@j&kyÀPcvv3f oٖ5qew~}Oլ5b/cZ,Lgs%HC*WTE\^al b n`||wn4_r%_ǫgbeZ$ Tܵ\}@Q̘g ]G7$i;-!JdYl:x=]1A%`iβ$c_:PDΘ ETgdY5}_! ,PSvciz M$xhnQv?;3<%ĉnqGVd#2{~PnC1yztN11Qf(˒軆3x q4b:MbGs8')Q]_t5m[6/cIH㛠A;Xtc܀` !h譣 DQ"N 45u7DLSLfw_+)) e#0a[+˸8"S6?}cq!egU *e].ԝ@Iz9Fڊۻ|okO޴{FF9T IDAT 6;~?'(c^q6<7tx1p0q|6r"cg$EʽŢڱ<0 4 oW4Mqwi-iamAh ,I]Gc#nB9n :l+8>f:= 3,jvTd$xM@hYӗL1-{^%E>)á_@5Xu<0c]<M?ќ' 7\ݼ lvk&cnPVkkwVÆ4wx J~7x#4%NhN[7;д;"^`ṫpOvl"4e%u[utףuDU$ڭ^5vn`b2QћH (eIД!ɤ Q:,64u5id?W߼gKӳ\W4tdZ|WߩݶBZ/_gy[>\( F#^\}CEg?/կ~jatnx?.Ptt^P( NQӌ9ë-22X℠Z"Ȋym!C.+sӽ^Q3c-QQ7mV}f[iEZl`i' : `(*Wfx^Dp}}KJ Rb<@qo;zc@6cmBG"2txa(B!Ak$u)bfY"UGoz=bFؾt} E7Ywÿ[|hNh4bkŗ?t]MӠܫOJ$aWv iv)WQ4i:%H'Thj0:C#R($%3X@"a9Fy“'O8VI\byCED}|Z+LH4ЃXGJ 0٬0Âh`zx2| Wݛ /ꒋLJlwͯWD1ㄪ!ۛ%/cveHOȳ醖ڑQzb6<{9<pؕ-UA[|E)BtZ0 ~<4~cօN)8 M)%RsUOqPp-޿fې>1%[xadYtzȶ*? 8 ;՚08xtƦ0Ħ#?FswS2 ͒p~fW0*ըkIZ...ώx=W7w&q<,b3U&HO?S)='OA Kwl 1ܽA"H<?S$DQ'iX,oi*G$=e]RW :DgibB F8!$JSBt`|M{cp8k@8mM7޽vx%bS5WYn9>=%,IxDıkbB9FqƼ9; }0CM"6t]R'qB]Rz)R\}nqzqEI"K%iYV tf$Ή4G<m+xӷnWa ~zd[Ɉf,ﹼ pϞ޽{ׯqސ&sN_wo/{(\^?9_5UCɘQ>%97 ~s⡢^ z\"iQ1$b4=q&j I(JHӻPہ|~O!f'6JF%R$1DJhoR@Hsl|hM!m[qSwL DD:ۡB(uta MZ{1ޅAc*@]8EndRuP ҆ |8V\SM)eIՌ|it}a=$24ZjX384Ii!)G$YAYn UYbdVAꚄJsExo^=iIrM)Z`]G(5TՎ4 vLR4$q4FnۖѷG_vP=Nt43+z섃#si{#T =8S3YΨM|rÂ'wC-Iȁ^wk}/bZSX|rq6%^kTdL08A8vױ5'̦Ɉ(J!t]2;ǏW<<,2Bxb bдZk" mSVQF)&Sxtvl>/ lS#֫),3ʲCfj< la(ƊOYnlL?\"eEݮ'I .\<~vWraq5YU8/_r\g/??yOG d矜^)+"~+֋5eSSUU)YZ/Ymal8o2'NR02J2 INt|0#XNqrNwq2?2XѳniJ0ø|8H}Т ID1IȲCYncj?6%J挲F?[s[RBz("Dämk :H<#ˤq"l"ř}{A>c x4I8CKt=Bg޷xih0eRJWxA k$: l7Wd6;;Tf1Ϟ=A+ѵK#[T#NG?\.-ҍP{81fɇ-yzdD]Ӷ5PU-(JPS<$f~hCZ[&0 JP -:[Q,؁nYXl'HW$IR5JhWפ`6=nCfSc~_1y2l^z'J4n+>JƘ-ta\@}sCt2Awk@!cEHtýEuۑD: 3HӈmC*CQ䌋3T\a~|̀*贀aP+PH*lzoQ^h JIޢUxw$q=^(im$89<$R3eKEDEL'($boX.4}GYohbQ1u`I1 RJ1B&0SyNW7ێz-%npB~4vY!{I:a4 pj$iC]nTW%DR[Why~KWꮣzmC''GP9޿)$u<{v︹(6H? իWvOŧw׿g 5ղz6ղk@)Jn@ MDBŞwe5mx _2J4{]& |B(!"EU1*&tf "&4Y22Obtmq82:XJt,mS%Y;Ni>.ېBb]eY [^,P)]cn`R$yu9P(1etZP=g𖡇nI1&ܯZڮ5U{ˮ&d`傲ns]cSn^#,7\<~9ê&/Fx.+,w[~ GKo601섛U] <} gv6,Z6$Ht9#~f[-Y3҈]]QuPa0􆡇([Z)bD)]URW=yQ$1kn|)''GցՔ)ٌ^.g^p{{ X`,q;MӅCA\icIGXkK@1)lV5R((C00 g~H4aTGHnIh Oј4=y6 f^3Dꚻ{YnIdDTtAn,Ck0XzmWӘ6XjMO& β+kzؐPQһ&{a$ ]v5mWlP *P*B)Ej< ߫C7i*.2>{)w]s|t%,~iT2 LGGj¯`:>ݎM(=cŖqwW+C]`JU-YDJab"&=V48L ^8۫Q{,4J`ùdB5TUEg :Bb}Gb!U%(-點4ɉе}xwav;cHQ:Bn|C`F1 tCKG-u" b[,Ղ%c1ɄGlk~A7lѝFGno- 1(H iZSE_<c+\e%Isp8,(1tl~Hڶe2 DT0>||MY-臒$ȓHaHg}O;!OΰbC)d2/a2Af7p>?( ^yIYE'Ǥ#IUG(=F kƳD LX+"JUHX K CY]9JfV֐yHZT4Do;a 08"" Oog4mE]5yB4%KGD2j\ߣDLQvNW17-c0CJ4nt q`}=Gx#DB7t,vdfH,I##C]͚dDE>" ۏR~x>~@tCBnjG'ΧmMY[yNے'9f3 Y*0"X뉕BK1M |^Ċ> AvOt]Gu;fxvm,ԭgi8=+4%nN$b4t+\]-JRk\J1HӔkXWh=U}GW/ fEk:dbX{aXB<< <($(_7K$GG,{?~K&G减8cY:Trx8'J6n_1)="@Q(&32oVtE~WoV>{`#jk&1O<co%^hՁ|zcY9?g<;cAWo0w_*tGe`zOOOy%g'fC1 ?O?#o޼=m~q`RXc!$؃$f0$Q>@-Mj>@e<D0`щCG ڄRh{I7L~$zk=ƒLQHV>y]?c:Ҷ'78 8+[HKalkdp=QF2x)p ޛLyZϙhצw9"<PFb-   !-zh]]UdVFdd{h ,eE܍/^nkvy #11:[).K22^ŝNMU$Qm>A$I@w1PZYR#Ñ)JJՄO>-77W\p8&Ubĺ"pI5I "'w-c?b{?#40믹mxIi}l nV "gH$Z.`7 IDATOYmpKJɳ?z¤ߨʛVbatFݭH#qc?f9iT|yFԬvkz p@@S8bWdWK$ sYIe2Jł~Ȯ+0& }vI5͙VnF$ ZhtNS| E0xJ\7GfI԰߳9RzW/>cR<`V>D9Ȥ=:M8` 8H?lv9 @8ذޝ;%srwMLl`<'6jz X g V LElBz-dUAYN6|}Kbbv!EbzT";Fc#)Jz~8RwP;f{JIBp>| oBFoR$r \67mr#Y1{vОe O)6Z.cMی@{O?Ge_?EMSZ,aIM .B=8>XSCkzGG۶<M3%eYG3Nd>]2ڎ,ɋh=Z8l7+z5Q,O5ѓ 8ڶ&x={juGOF;#ΝҤH_nk71"H/{di-^s8diŗ~Gsq1\>drs{ϡi}g3~|͉IV-$\_QT)޿?jvqw`s3g)Ov\ a9b2/=͙-J6~bmJ#&yKM%qE[whѾA*爟9A| }didW8 >֟NĶm黁?5ݾXVUs+ʹ=ACf \ݱQf%Jh F}槚ď\T|v/Ah"GJIߵy6yL0%RIfjZP{BoX9c~G9O>W| >b3\pL8L~bzD:$r|۞vt=4tlOshrFB t>(< J$#˶ *^]bR[u};)8Sج@jƣ'&F* ]% -@;snCM|P4y_\>X0On;mА*M9F8IQLf$-_kxMpGҡU5 7/HZ8Xgq8d@躎9Mᰦi:<9e(+RC~Ȼoz5u6H= HJ|XBL1)[*2**ɔe;ܼZO%̀ lʟc{H'd䫯>W9?(Fw%rj>nVkEe`}_Z0= ccR!r~Lk> $YLjŅm-3^V*%/+oCs# 8Á=@U7k̦Kα,֐)7'0@M#E", cLٔ|#|]) ?{J^f◟5e i ˿)|!];4cgϞQN?7/~->>ĠhQ 8? cRGORGO+~e =Cϗ{>#~/Ų$ђqBF R"2v7z$4_?`pdZ2 ́0BCgpT(C_^\gv`>rv.r39aR!0iuGiDL!: 2$X=)ȘR ^#TL[ii܄K"D|9w Adء渳<gg͞7W_1;#$ˋ%EEK^9ouYҞHG.|,eVPE\wBg9~TT>P5x6V"&x{F IZf)ÞqZ"Xpܽ#+UNRni<|}7P!_?t݊76<|xd:_2@0].T EYN?8B0(̌O7׼u+Sٷ?\eݖvCV4MSY1&YR,2/w4(J1^iȜgcMg]Hz-_xIi(9m%/&-̫9՚o,fsRBp~`y1e:NI8G\;^}CZ$%-2\#:)-CjHH51uqa'x{ՒG# ڶF. ?BCU1zW/ٮ6Gno5CfAwߎ?O?qDxA-kʲD$RhYG~ILLK&R>胨%\cݑ&_\96๸ f&VS#5)1\mT>(E`{X6("q"(rump1 ؁aIZ*!$XߒSxk)X.ϙOk5ms$gAK0vY! ݈7Zsh:!/']) 1(BaQZ+# Zcp<֘:mDJ1Xv1?1(չ{O4FOs0B8uNt*c Jz;2 \i`FO5YUn[sX$’aV)$d? [(1:|r91rm2sc1ϘΖ,|\K |xzqCC7vq`6mIQ*&d5#H @)VKɔg[n$204~=m-B)p1i<Bl7{[@z.T$ipFIɳ)qwX.hh,[L+^cܴvGW83ڦ'/rJDmEw1&d7}~-ImCHILZt钳%MCK q`{d etmTfGbjgڑtc9Ĵz4+Iry>!??gݒ]f=0ɲap|?o%}_ ȃg<}ؑo^ݞ /gmc\zsz$+ OKv[8ȒD(qJxC1eJ3rMԢK=%$&8uCw98(d!ᔀYe$ ֵܾa~f!#h}Di'cKQ&ENay&ͮ gA*tcdFL4OYo]GQ%Þii1STL3VwfAjJ$mz5mw{ˤ48gQRqȯ,yd|QquuE]&$y޶ ~DkЍvP?|O>o^dgm ۆ_/ےgCyλk??vD%P+.I_sK3ȋ~h.J6;V//)?P:?woWl#Z[F @,.[B#hk19 CW##1UF7< zB3hKTW/_|6%O2C˝u8/(Ɓgmף,r@-+V9%Mh>f"NjZB%dYJ$0 d,R+ڡ%3 ֍1t7  tD3.IO~oz(#QF G ~p\F~ 7,#Yf${f$ g%8'k96w V4=h1JPkf$E O T41Z:'jJ5=~cTF+cKu$P@h4ISu+IF/V}C^z*oIRE֊FSʼ`]H P쉉1G3 l[-y#ٔ$OAc=hGOߍLˌ|r૯ih驖3Fu b }Gu\^V\^^DЂ (%H4p;u%B[G<w86 c5˳9O>e1`GJ[޼yQ^/3"1ݚmK.x-JD{O2Xvެq~1 Wn9b};qt$F%X#WZ\)8 \=ȧlv[zC~2#Y6ن4M o]zلc-"%n0iA4 go1 (B{0uB*cP Ea"3vF,yLxdJ5ڮA((}e=Ӕ4T<^b͘ E`tP\,iպiz&Ղyv4-Rzw9];/ډ?#?| %}lw5A1))'9/^ M ˳0pېIDig`Z=cMX; ^m2\ K B !Yl ཌྷ ^0#V.cMčjAƐ$m&e *Ui{Ba9& X߲^WomJ-^FZ+FSGi3!SJn,+ӧ¿aJ Zm߬ls%]/-H?beHs~~C2D ̦!%sh G@Y9#1qSZM$za#M$ZkfU 0T{ G^|Fh 9YL8_ж-Nt4L+w_֛ ucp[.e`m֐e9qgT8{☠(&,1iWw$z0c8Va$!-t:C߽7H)4=䓏p|R#ۛ;u_O[}9^{sHO>D3S=.hɂj~~suFj?P|{nj#isטm[yE>3w=e5Ad: ^aL`Ve+̰$I՞eIۏu6J;ϒAf"#O$1$51>*8kOXE(!ulqjȳ rtu=QHcX$:(\Lnm;a لwrRFLMJvH,O[߱Y.iF6z?w QXKrSh< ;OgLc =JbED0\L4ڞN@pB$E\mA\EM;>I0XG׍Lfv"<~Bx< % tn%䖡stcM @64#oDp"( r$&Є8XL4lvM}3X{FŵG<!iT-e\3"NQ[= ҂kNI`K[wEN+rjuEiGr-2mUS XoыKP2H!Xoxh/8?[W4G&/Br)F޼}9 b==M,ypq~|n[xwuq MSyta0ڐB&MoOɱc.߾k)g3a.RAc y "F1Cۓ*Mڡ7=gǬ:4!viy\R}CuLS,c븿˜g Ϟ=,߿i) hjlv|^@ׅC^y~o_)2t@Yqz|7&f<{T|\] o"rIYhLN988Ó }ۣUI%<8{u=_~9]#&2mk@iIJ̙(99IVv=߼ŋo2!QE C]Q"Ddd>ȋ wkYα.;"K$|tiJo{]Kn]DrDDYlx+LS݆nKUqFC~ Zf8۱]߳\hŎQF?l5ZiԞTx|%Cؠ(cm[$RJ/LLGSz =2mؒhA,Ib|_O~<<닯Y$賏9:==( GRbbb] Jv ʄxg*A1w3<`2E8G& V5w@O)|3M %"B,_]3\`eu2ih-RA YZ2ɧf3Nځh{GkՖ\\ft MC8a%>,]'RaV#BH-MJkEX4˰&{:.޾m :ILfI[-$I2LEHW<==O?eowL%]1AR8ny.S ^5-bleh͎=C<'AsX0Jf8#H8:88ZЩSv]RgWͯ~C멷Tl2ѣGx|/8>8cRLDwch]H$B: ОN< \#tH|\F;ߗYGC4n,k2~XtM]Yء^kv8gHZK4tԐY:XkwߣH> Z+"gp$Dï=-%AYsF՜e=#Hx$htFX.lc%OƱ޼zEQcnV ..ENDFn*kMtDᡳ)NIR.v6=0i:c`=e%ޱ\v[FHSNOIM31`F^hn/.8 욖s1(w8>>x:^=Qȓ!-ED :u)RIS#.K%%bqG $ۚHy!&)I3&ZL$IUc{"#F!E>c>cU%/_|%t} Bc &Gf^m%ꀾ[qyuϧ!*xn *e6מL+Ek,h+f5Л>H3M= I l?3TkC/w=΂sa@gBXJ1ti>DؾGO'ۮc 5*9R`-7?a]Ҷ ]ߐ$(#nSw;vJ)NOp_|Npf\4}B%}g.$Q`3^!x"AȰJ JpYs5+Xi '$@zvL&#tDkt}M=I/REQa\`%M 9yPU9@cZ }`2=`dJM[.L&Z(<#9Rjam^͛t}nӏ9:W_ zl@h#NLSNNy jƮ{ڶ@&圇gE~C]+60E:q;nXojURxLt̀z1X#yxso\#M74]ͮ^ŲJ1BiucɓkJ&q՚zî^l55 (`.:*xa )St Ф:FetA pl-]Ai7Z#BR\ .4MQZѴk$وдkdQc>?,cX.6 T]5|KUcY5/ص;=~tQXӳӵ׺55?|LY$lw+ےdc{ھً=}(E;+3fGNOp^aMc`T&<Ï9'G YrbHR!Xn/x}dTj,yDxlKidtMv{ABGq=SDa\Ǜ$KICI!mP2⹌hc$L>97:65w \$Z)Rؙ|!y7>##IEuH3EU<@۶@UJTG:P*F4>>{W")RYS U5WJjH3Ȳ؍uX8Xn: ȓoQG: f@ؒDaeq,S$*:TSU%ɘzb'zߝt"4฻[p}uK=ͶGJ*Fk3X7ydaR;A4} Q "ve!ð mƚgOqzwkЛ  LqV00ڠc+vyM% krTD̻Tň4k }B Eq=Mx[.X.5a2Q9s[{A?)˫W CC?,̖n&RTBDˮH7KNH*A0 Nz*t?Ekzɦޠ3 !S)}Wx/ȋs=Dʔ(ʥ*:Ӽ'x g Z ! @!:ŘB;;=7TU3]t"YG?HM {e>r||F"S&RŞ c鄪*":K$Ytg?ozg1VX m۲^ٮ6ϟwߢ`4*ɲx{Ɨkb(d9<. t2:; 9O?q|қR0ҵ6G(wچ}F ѴQQOF#v7d::j*% ~C#DځnOơH  hwF4MHB0n(бs)Ĩ!?Ӕ*C3z3FXkmt3"R $N$~9e^l춖mݑg6gPG$J$:p8eT/;nT.W GD1%E-k-D.$j#[,RHe"mT^RNHDiY0 Ƹsq}`U$DTH Je }A +giqjB:Q }T !{$K*d7HJf$*Ϲ@">sc悾gj7,9o>\!/f=Dˌׯ_cA ȴ 14Jv_/NO9 WU㬡:d"q s~~ʸ|'1߿ 57ZϓǤV|n:E&h;AiFDJI^ C"t;P-]zgI0L&dYNZĨLL8\lO)&vYe9"IR,c; ~>9f|8qxxw|oY5wwM`vT1V+T#eK<a-$d6YLIv- !g's?~BڰZ]sVc/߽ߟB{w~ɟ(x)6lwkwLgqN aZPGx+h7 J!'G]ߒjbOk"%Qɳ%3ھgO  >xPD R.^jyI xhˀєi Co(6ɐP#TdElv7Z.bYu[BRZ <Jg=IZ猪o78gs@Bx|lvxHtAf1ck-2M:Ebl[ʊL%v\=^dctF}/qApxr o߾o"cbFVNNZ2UovLSb̫oӳCY7`Z#łm&P23^~C v]O0K84fbkZ֛>L $VLf3 J+Q@kI5Iyc0LBk_[&Sfuj>q&*i I*3d(x'|S'lavEUdŘYPw[bGw/Xȋh08C%4PJŁNwete (aA&2$XMQ=n/DeE2vzDĆӊE$nEWhCh+i'UUf"UnZ=nDrr8ibyMBU/К|΃7dBUkv !dhޢ sNO57 ]}4[@Ho($XftRIQz#$Io:k89{sxJ]SDzVff}zs ItqvA2= <~̇?Kmޯ$ެ1a<)ֶa q$-V$Y:*J Y)v/n cu0iIvCe 8pPW L'O9:s{{EtHp{*x=c+>=d6= H;CYrr2*gQ2;r[ɏ?Ⓩ>旿5B F2(31\^޲Zh=os|ղ=u #}$5ICHR Xo+Ofkns5iﷱ0:U(sق&Jn(˒4Ix!$`0ޒe I>yCvN,X脼!ZCo@vASN:Ѹ`ސ$2F-RDEZ5 !cO|wKPx;IҵYSӒmJh5@ӷ &eS6nCw;6Vx#JŞ@A@( "j1CJV!Jr> $BiO`c; ;|vhAxQ.zNOOJxﴎqlݲ6Y<9{pJukєw@"B{y!@ӵ)=!]g9<ȦG&4 XߟBypp8g|4~[(󄧏ϸ/ :ͱ. O*Ύ軆P"I%,zO6!xH$nCu `H]1oo Hq 6ҭŌ R#D`&#ҤB%%wCj5IJ[L&vb(H$!|$d%:w`|vÇ)~h9wCX,!'dmkIcTMREpӰ[X.YA>yNozD"2!UH]_o蛚*9ьz$#fvF-77H4y5M9v` * pZpڲk[lw @Oy!Mӛk+QA&3cơdZEZ3 ΐ8soYL!h_8kSmGf2c<#c /diϣG8<.yx]FxIYS!<g_c |n͛ oWfF21;QƸrB3HD Fstt `|⠙.4 V A ڡ#+Mq%{n*&^i9O8:yF +1][tϟ0?{Ÿ_'#6__s}>Ԩ#+{,nKv16$4ہjl624KШPLDu+iB2nɮL*8Pbu/8FgLJBO$h+޼5ZN hVtIUW^΢+Lz2/(ݩw%qCkQq`Ȕ$xr9GTՔoyɫWQ2G KS6dO3?-Ŋa'Q~oKg;8=y̳O︼,Kxկ/'Ϙ׼}GS@t/F$DBKpr@)d `<qGS5X<{/HuwaRu B2DSm ָwc;! BLjD4)4E@@P}cI{ )4Z16f }<6v\?Ī "O)t)C" .4tТd1f<k[%({ zŧ{"amID\y'BxUyA@IEn`gM<&wBh%BӁpHR%1}@&-bGPK4)0bhCcT$ԝ( "* v mnqˏ~c,~#9㋒$ɻv G+E;)~O2 7xp~HHP(a`{YöD-k֔0\\, ;-JB%yݚ'p%ut*X\Eb@>"Ϗ 10=~@ܭ'KxeG\`1yf=F䉠 KLү/ʔL@!F a6uKg A"fKb?R&`4a\$82%plx"eTf[f}f@kFՌ3O~g Ig%iVa<$fYDd`lsdc6#nooYt] ;4$A܀Ґ':,c~0#IzzCZh` H51QCHz#&x̸6fE<),6[D6FՔQiF,nxVwJTCO~Ÿ*ԛ-m-_lHʞ"58ߡq!HO5JB!BjHLFy0jp"^;Dsi,BJ?8?˔R?CXQ,‘$#];0Y R"@)³Z-hv"K &՜m]3-#W˄n0X&Uۻ26)5eH^~O߱_%u]~}dɓ'|G|͗}"e\` |O# >KR0ι_0t~-/^A8::O?vqw֚QstW_Q$~$qGhZ3t94a<#h%e 4DJH D">AQ_G(NzaB Q>cL&9q,W(F8P1&GJ&AGQe$v2%O4B@ {H+I2Q|&B^NX.-mGG}HKZ6yu}MfD@b/&{ݜID`BD8HE,co-fdYGUkcAHBDVo;ɳ覶"Fׂ%KHGÁ *P4ZË<ܓ18coi1؆(RN3@p)E>Cmځ)q>ǹk,Z%U)vhS"O5Ҡ Wb7+:gTL8=,Kx;n.x7PJZՋoQŘ~Acc29sdJ?*a05r}ӓ9O>¸ \4Zv.:ޛږ}~sxeFfVVV]H'=0xm mOle V y`$ AQ*onݯ΃u"/ 6$r9L+rqwʓ7y4=^j!ȱ @ qQ1){k~v)3G9w7dEH@锣3}1?>;|g?qvo~(%P*QHl#WWg%?Ij4]Ն씓{}W/Tw?%ʬٶşJ9>>?A7ֿ-#Aη_w05A>Cܭ75q䄬nVt}Ke`T TG 2yEkz0?xI=`&TRf%fAkwkW7/yA5\|^RV9.9Wf"Og8ox񚪜Ÿq9mc@2:B0ьbld7 !u䮻Y3eOҔP3:[Id8UUhyYmnx:UTU< R$PXLq݀CRj_sxx+6,}5x-[vfڦC考=_>1Fnf^4Ӎ^GpkxhpZ IDATRx5BIھa6(~K ?F7Zl R(~{Ǐd9?h{ ) H>:vpdWǸBY Re5Xr|t w}uw>1~\\/lRd)s(rQ\TdYb`_ $G &ߓ V!d *"*9o)V7lW\\=mom̖9}?4 GGdiJJ'`|ߠ(>%RL[̤h=cߐ I:qr~@Nч)7wј5t.2u-ؑcNL5[nґ R>s #MVgvjΡUBZTu9)JO<}*h[H3ҳ۽ OJxz#>mRSKf<|9Pc^}Kv}64(A$)Gv0MTeɬiv҆yE0MS1&m@8DיP;D`4!"L~Ӧ#Ȋfv @ia"U5i:|>gV/,+0H 8!8;fUJ M2&$FGfUoƯC./x O8M }O-)_Ͽ)/_=6Vt?xrƯگp}sɋW^wdY_>'? ?S6_~)oج. ΢D~˟ٗonZA)1$/:.xD"Щ« yqr/!,ueI ["3bh/Ӫ&zHr!D1ّ&cp{ Nċ4ZqdGƲI c&FhI$(u@iNdO(܄Zh ZD ,QJ@ Jѱ%B%mϗ=AD =JJPI</0Hv%7k&I}XEZFbS+E1Tk S ( L=D gcj_9s zM#ds#`Q߻ݶe 62׼h鹾yO5/r'qvBg-mbk f)LD văԕdJ]1flC^,8_|'<|˷sztVDvŮx,G3n|{տHR>ItW?F4|TBfb19Ōy?OX1H'7m{6XDPX3(24@q" g}$yu鮐TE9!8n5BQV9Y^"Lh@ P ^hP*ȋY5#2KG@u>J9Ȳv,9ǧ0\]ȳm(jG X< )ښq~1a݀ z쩫%R]֚݀Me^][4'DCRf\o/h]L38:\T!CoPʑel6-"kԶ-84gyFVTL}J4>Ff]#Ir]<˯"jg!p@`"yA߯C %Q9z %}?EM88=O=+넴|n/5>($'/+(z0L/ٳW9ʂy{޽`t:a&T:,SQ3TqkYΎyc,Oϸ2&3p}{5ecLưx71bq.NOD)k^[]<'Iy0X%E>G邃GG$\Ǭ|ćOصk~q_RU|7%6 :I5;fufa1U$HU(.#@^lw@ysWH:e}wlilq3C`?%Ώ$Jn̅_(l#4i̪y<6]{~Ȑp>Sq(hJfRr{{ˢ*mwTUn )g59"T4ێo~h,o߾˧nXvGxGġ5Z4}4M8 :Lm:1 IJItFU $}?2͊#FD0{'2` s4qaZwd ƁOg e8͆qdpa':x77kzAʨS>_'Zŗ_uDxiXpvV?!|_[ c5(899ˋkN@H.~w>>۷<}6?|Ǐfݲ[wv5օs$Zii*94 Q,F:D(q9LW58Za#HJA*֫`x-o4Mΐ u]L`Ň8pR*>-^ 9:X26 i~@+nItCNZg'ƾ͹˵/_xBd %P : s(gs;@z}_guTηoow_W?{ML" _W9ˣcnWwfyX0-iviqαv89DS<<<ꊫK@ ENB3~1%hv)`%JOe8hIހ@Dx 8Y,#O"Ppq1$dY’!b~R$,ͽ<~n1o^pyyIcdY`{&!xl-b)늲ԱuLfOAx:$F]5JՂvאy<6#nYV`4m4ǜ z}ONΒ%vE5#޿ %Ơ"sPi00B3XbN%#yVжw/Du6B2 FV.uJy&0A O%yZ0v#o"cG~_T4+&bπif`<~zer`SA]߂0yF5੫Fy6?ȳ=~Xl}J7h5Qb8?"@3Pz35TS~Lm)Jb0, β4cG,OP:G EfΎyxsb"c>IEN28\Irvs<ζ6).+qKXp G'gT9îN6Z O5Zyq68;2 w:F%uUrd6{ˮlH %SKXo $!RF'z.~Yg<8w>a2f_IcV;6ؚۘfd~ 0e t])iƀՌ篾/[ jn v!2>aa{V" ʲfq"D#HMo@Ɍ I|{k>t,v!!8QQ Uѳf)Cc z5v L#xR :#I?th;Rԋ9]ׁܬ|~4L.˒=1Y5[nbԳO>>nݺa mbrb~قDp18W wx/ժAj8>Qe|w.]q{RT5 yy}M:,M$L{"WhoB/%Y +`~,8RBQ՚,wIB\y|8 ORUKBH64TP䊡_{ =T=OJJ !*" 4/(˒:)87[)Vm˲$Goa DU$Z(P{h0bCS%} A$8@)R9K7$Fj00zTg;bgo{3|>.\ RS;tF {4x;ҷ1ӏ6'_@HH5v1镒qq WS )48$zxYJ<3v Qf"[xKhQdF B@dEjqmC9 Hc&Ӹa$:e~c\Wn)y)ЩR m72a0]GJK&yyYp~d^G;֜>8g6Ö%YXα^(Ͳ>dw#c{}KUbuwI i {`}b-y^&( 9?}n×OBXoEYUP}5`-)EږW M4: Ck]9>UARd\|i 8L^ v4pr6GksxZJ*gMOf2i u|f}G0f}h__kv%???DW4펱l0[X,ff[s}>bsttH'La M3Գ'}'~o!}u#!KB1;IR .j7X!!E>BT&}v_) kB'B]BB!RIt#E5k׊44a-Ra!RsyqE2Mmې$ ƌiN,;,&kYżЉhP{@k=R$ŌȨ^kc=-9<1~IE@k R#sۑl-!rib2sꖷo_2L Z a2=Rz-H'ƣD3֎|paAO8o~GY9e~4ZgL雑i|Q@͖ fOi#/:lVkC k$coHub~` ĦаXj,H?{ J`&ɬ:*o5mqY]R ֛kiuy! <*hȲDg$jNuw~LzsG𱲗fDTS9Z$a'9?y[޽Dd߻֚o_kV'I&ZH8t@ M4"h"Q u@ ɶmȮ}CS "erev-8ԏ8vIRIY&xoPVIM^}ˮ[ӍBJT#tw`'M kqcya}@qO9<gGeM0N:io*3M7ye.gCH"!(]1"-RP@Ӷ Sqχznn ||@1ppx)EQ!kz^zj-<棏?,39Bëo˯x|8ws=|_0~K7~7lW'?ꆶ uʬ)k6{g9<|W׼ $I#P({q :fp Ï ^:PVcvv"mq"mjz34#yQc,%Ղfb6FiY>!$%V( hۆqL#WJ+ D6}uOk0 k mPB?IdiɘtTS$"™(Ch;>S!t9gudLxza 'G-L^1-78Z<۫oow&r_Bo_=/8~pskfSOiS2-ISu]S.flv;qW$ ~%e7;xGYFcYr0?`a1fƀ$GjM?4ΖUFjg,>BRPHZF6 d~d%quXx ׶[R,e2-*K Qno ĚSY๼}ÇgZ3xxgzsI1!u~šDifegYR[#Ixt#v|s4)-M%dYT|= IDATUA`lqX$k}/[lͮoO89*7\S37Hsʣvӯ)iS. ˈ8H4R+TR9C7aepr|nb2/Wsv>Ǽ{7eZT2L  $ZkH%cJ33i!F@!KBD[ ϲX3C Pn a Aф=d'QdD~hr,"B9#,}OÖRDIp~JpS!s"+黆xI4 u/A'qzoN34coȊt4&f!@S\4-@j \xB&Bex񳐔ra5We)8H|L8;;19JA"z:r xMDLPOn=*¸ ƐekknE]x@7C+{{:gIeJ*=G3~ $,ynGVC۬Y-Ib^8,eѓ _?S&d:#HnUvr>a{ >AFnL:@JlϮde' ~wUc=6nTJXm'V[+6%L =NXq}ժcVKiBYIhc՚F35BL3홦tÄT~/+ezV;蓏y9o(]#EVt?7[:,Ւ CHˮYѴ+ƀ&.m@j!Z&i`׮4 o6;NOI ethW<~!*)Z$eVc_}WRM{䶈d5P2fU"1G WÃsʣS0YostRFhM 3I;Эx+6' eˁ4]J,ق] zbLxFZ X%0?d <h&,E14p0;Cj$Ϩms-RH1Ř,RdȕG+X]KfUn}ͫO錇 uJ?ZvێV5͆ˋ D-89]r|T޴v=I`6hspxL$,[ 3kWeM?tl6IEԌeVT5#mHE$ҒY}@״ݖ5Ão$Mu}ÓCEL!e~Bsh9 5iXk{+޿ߢ3Tc;,[\HI*%2݃\8\Dq,dG!jq$`7; h$ZI($Q8o,2ւ=(gI>nr߿gGPUv 5a ''{h˿xʳQiLov%ADcXi:T{Y=!4>x!g@'HE@'NxN.2axFMzY|H^hۡ.=wA|"@HyNN9H@ˀÀQ&g2 Py% O=)`BT+%{D%H7)KJaQ:.${w%~߉}%7,Af YptA;*0q׏LM}#2Q34G]EŸ"M~XYDUL#}*)d!Cp,U$iEY̑*\{] 2NZ8 HdkfPgZ-3g4_όlN>#$d;uY7e/ c0q1,C![%T3"sǎݯfst+ 1/ c{5@hh˯!PT`*JڡI$hC],W)7lsfHY-xS/J&ӑ (7-|juwQd5_DfL1~ z^_$K2tAz“`0U Aȗ,ny$)ɣt}ß7')Jg8!hZ1͞g{8ӂH%Y,"d^#AF泂JZ/A[\$c>qt|/iȧS*54KHF~1_}r>s^?o5e` $HSL&[ҡ8tJk=w/ln"ߢV .czOn8먼yǚA)TGD %9G rk]p?JVDF< zH˜nISI'Ѥbd!-na1л2g'w˿W|<}}%p~b5{$3Eq G,["I!?o"w:ܑffsE"UUQ)MvɌy:g) _ iLZ-'%$8)˒4+B1_ŗ߰ߵ,X}c!c4zdʓ)b'CHõ-JdEFIa#ic`YΐirEUDyR3 L-_+ Gʅ"$wwl :*V90)5%wۄjVfT5{;D8#erۋ/Y-X;yB3\o@7  j&`1řfGSlv ZU'hu͈ enMdލQ ΎYj6'KRr,U`nHv_cC؃FV$:WI#a0|@& Ntӑg۱E89=hc;nN LAPfc߀X4i6#Twc\zn0dّf١lEY̘UTKRU$ILYIZ4{.^s;94Ӕsv|JQT<{闟pcc쾢y5:( 2D6B, HUÀY6!ڶ}xNhZ"BxAe 6&Є`AR5żedb <$P2(~t~ HHMDf]O13 q$ԸSwU5 bx\__Gadڑcwyu45E4 aDkՊ;>;! Wȯ'e__4(e!MMÈ Y6c/H)z\qAX ^e?$ђ/ 4 >Ix4a}{p:cu>d}7lL[n S;s̗%A(<;/x Вd)S 4[3!cKA(0%Z~s<Ͱ{N0|m4ZFi."Ź7ET&HI?5h4C7U.l#?OWIrz1$EEALơFa'KhRĴ{n)qdYLI[iJah  ^`]/$RCJ#dxR9ցHWk8GJIVh2뱂qtX;BȀI8s/_pun|1xxl9CHT@pdٜXRc77/;\4}7#a-8G lrcXq}{Ϻabcsג$-ZKgF "eBhC2!kFڶ8g_f1i:;N*$PIf+hxCMY2ʲb2 ,E9.X,1Ȅ2-3\-dE8Tb}T!᫯ŋ[&:8Ʊ), $LjDnQHHNݮA6 !JHv;YNu<ɓ'#D*E, d8gH5dŜ3d"84jyB>D%& lv[\Gg}r>{=y.~[UHRb;*Cn/q"+3T#߾:CjU<麁cl0f}it]`Є( Rh8}aKH:ybZra軑jAU.9;RsGR1ުdƼL^gsBP?4{Ctzu4B*t'xg"p79hGy4hfs%8w,h ^eOOȳ& bH&X;FEp0Bw!nѴ!ZU)us{c>fV D-PrB04ÏPoȓq[&iNxza'`lF)ݞ'gp}-dn{~9'1cBO|{%_Mg"a2&)aҞHq-Yt$,.aqv1=Aݱv2_Hnش"9pƢTP2%M53㊦ DQ?ear#ҒGxi`yStY RBX7M;m;RmHZ<ùX3:V' @2"D^(L!11b͛TZs,KgB4UzY&)wM0XK̫z%M}C=y/E&,SH"B@g5eV CO+vCRjs{tIS cyqNqEjiLv}uͧ >xCS~㟑c|Tc4*?C?Cv{="/?oBiﶨdF31N ^yt$:>ZFkZhl8Z;bET*rXiTx+uKל=h}8X$(J4ctu*!O VwxAJA ˒,e`2#]#IY D@k E|? HPR'M5 "ncGK 9$U¡b4>$؀3D%$y|]y9r/˃r](Ȣ:W|o%\=Ny8{6jŕBX tFEAh%Vp ֎~B5g,iZ"4#|z 7Ob Ui%XT&$G I8pt!њD XIP:L4:1ơ8!6J.sv縸*}x2w월IӔ4ӔyFir:k^xMaYN`y4g6 }ӲXλ%6<,'X8Q|A08??k'm>TzGIސ`eqL-8*g:\\^X%.H4>x!d{a,WׯRV)/w)-K>.'g!ΑHd B'ȩ _}9!dŌg` f햦6 ]зH8Y.9TRmj..-g<D 7WW\]bL3_T4[bve<ȗS1Yid5+8*+zJ47w[$ђ1zwGHu =M*fw{my3kwd0 M{~hP m};C CU nzQ!LITQsvuK-ȉ媢(FԍLe>4(PKB wDZϘƀԞ,d%~O?칾9gr I o#GD!J:%\ue1-_s'fMߐft`-% !rIfȨI,%`4m7٩&۲љG \},+``& HlI RW_~d S%"O8^~rQVK$#JUy,A%K/I#(آʲ`~vuY*X,V8]3*C vJ9ZK|pl҅ IDAT(ۯ/iwO]}C$>hPr07@ZZο/? "( *Ǿ'Ҵ x f1 o-EkF먲H-9:ZѴۢ:h #9T==zF5Zf2\Ͽg?+(ח$GV9R#&hwǜ?`c%t8 Dsvd43X<}Wmc=E8|MsM 'K枩7Qf9`"aFBS<}7/Ohʲ>^WxK[>?O/>NJ43/|Ӝgon7 W;67fl3D#C>Xkh_+cG$yaƒ#A5"GipHDX781NE@ h-PZDc;|p8`4!36M;,!>c"DJePndi *VTrHXLIq 5^E^L0R]HXyG* öNTqAhr.0eV>:e;(Ej%Xg "/$L4BJs82E$#k!BڅaaGДEVĝ -*MHRjES ];F=8 kgRdAI ѵ5E!Gw SY2ti*tHP2yty51]4a‘f0{5J˜];0LJPjȷ/_&㣜ai=Œk:{jƋobٳ^AĤfa-caQIo;2)"(_qu~A9;iۖORV B\l=1 =w v[/?c?ayTTUܴWGuMSOe]*itI`嬂i* 0q=f"e5Q%JT*jud IBY'!@J% <f(=İ'O0Qv/?mkei1>4`"Dϖio3G'?g^GLc`>㬠)5ͪm[6 4еuMrH"K'MS14uKYU(U2 0Da 4NbW>pDmuaC1n]Hinv5 P-x0R<^RZv[r~/gt]E G3>tÀ#e:ͨ%WJ >-:/OȋqZ݄uc<~p[1SHcJ];R4(M`QAfs\RixsK^]~\f|"#%4Z&ڦ&2,E|vOP눬SaD~w uUx! ^C M<|$ɳ3l7 R*6{no7%41A3Z5@`BmY% }n+jUrvv{.// 4; `<Sy8ӏv QK$Y4}eO<;W}?bMd8[E O>l͗_~ez/wqX7~_7mMQ$Hcڡ_5w~{ꭡZPJHPRRPUBZNa| o;9-բ"p^-"ո&m˾{OU"^__PIC Z' q|pM C8 x~I*dt~ӠJbY|T /99^2mG{{Ͽ Ϟbf1W ,];g_"@^$|G+F3Ѝ{-w{TBǶ0 YNQJ2L4<9,R_Z>W}85,9UC77̉$I޼mnw~˷n5Be{X?JRt04YL~fC4ة8 ;1cBCJ?9#KTʤfz'*2äb2G)MӒn55I5lAǣAtt|>oZf: J ûx.(sX.wAW1]oU{xӣc.d&O>a,!O>o`E \<~O^r}}4XAxx|}9!{ѣG<}??ϮY}#7-]k3?F8pH7V)pXCÄO+NddǺxaF *I(7>#Æ?0f I/pzG;(Ʉx,:gDAq_TUAݎ0@Gp%#>Ӓ" 3\@4 :ȲLU?XnMxH2xf(swdw` q!Q-n?qI;JLɁ&dL9ƥBd`yixBRoBn`$+0N`n;F̪ PiI+Q0!:+\{t"X4H >a,~d,Ί,EO}⫗ l6fw%,K vjŚGTsyfg[kMYTYI}M^NVKܓ\ۃy:d Ƕp{Kݵ(DTq}Ӓy+@G+V#dN2#SX=:eſ˛sfysHP ʪ$q٘,44Hstp{{KZJct0I)UX)uGPeǿ MJ>:k4)+O? Ō<ՊۗwvVkr۞~dio `O~-//iTk)B`,?vxG?^ 4+H;9vpux0T,-1aꦥ*b9P+M\䤢F꺦m.s$ER5:VM+>vؽt 갹3a1(IbGD@?)bc>[E4<s`ni=8ZC Ypss%˒noYI39}M>b[7LfK-HtɃG) h>&H1M}B`6Q*Xq M$GDI}f4 Rq1S0#8X@JLCܴ&%eYtNQP:gW Sa:0=ZO6E)&ںKNT av$K"IIL؁vhú& ZiHBU00C c@1Y0LxwPj5$k'|I5}ӏ,ƶX- ~DY>^ t݀K"ELdcs&"fg{\0X;a%AP"4ɋC4?` 8 EH-dnp{@Z2N∳gz6*csoAĈ{>znn/W?1@(j$#*a"M*O?;uR{zSs5E%s9gءf{ͿI圶mwvsN=^SyE|(%E3NP"{cKYšl$ iG @넼hip0xa Tx1<(pxﹹƸy#(X!HuXP(4#zGox`)fc {ΎO*}O$lDx/h?z@Sh2qYEp./c{яv{ݶi ]LKy!U~w]4vo˂N%m4My)OyƟ_WS{}Ŋzss͋kZFC;H2<|^:̖l5`EQ=̫,vh";.UGBZ88a]xWAiq2 ;a#ߘ@`y 7SԔ{{"|&cĵ֢ o!r|8TY&)L,i[nB,o,I"*& !~~YJPdTMHp␐< آ<S ܅D?Ku\ߵT$Hd,W77YFQde,sxkNtr5K^}2jձL iocmc.G+.VҔy[.^]\|ꛘ$ Rںvw|^1.JSҴ?mKyjh)h{X,4ggߧka_߳G1=O#ApB_}ΏՌ|14M ML+IpeiHt`szORmn~M;T~J^۲0Ɛ R%YVR Ͼ(Nc*Gb_dm?i+x{=qb9kTzj-b $,!^Bx˂e %KHrw}[̘֭̌ߑ{A;MJ'N>IQԪ(6"f,cVeM?_1~n25I \b5x 2^g 齥8ẉGx9"Jw;#+saLu6Rmu(D ((9K|K5G yrD9EY{v xK5 +^0AUqt~}vmh]+vF) "0 "Nfdo|@Iȋ{ >x> XC^pxXHǟ )tя="oÖ\{:Cg`HY)A;4RJQ{z`Ð y4tdEA]dEIQflw69g ?ZMH<1K.ƯkLW[{1]ۧzg|#=oEAߏ)g;b࣏FB s%S;##)a$ϦX>Mw*8:=䘪PHh f }o5M˫Dxoq{ݎi:p㖚 IDATv;(K89;*EЫ)gknH6H$QD E%ܣ gpa tNzN'NL9.sjJۏ]ȐxBDRY'+V[%PB(I$!UĒәjs8hiL*أ29L=ZA^A5J3mjs%5" $>Fw j_@j oF}JҚ BH˞w vǤ*Mtf2JQ\i‹i咉FTeI -$<!-@'6&N;wHnDmp^r L%g痘1PU5eV=CzVJX1d,k`{˗ؾ#(F^ᬠkflF&4*&G"5ȉ圣+\7ϰ`@eH6(D@BET i %ȵ2O<<δЍJX"PD8%y^zw} K B]EYFo6\c豎(!,' vG9w-Mpf \65dBM"9<:g>;,g_dŜvHI۹L^]SBM2_垦쓟pq} 3en?$w8swsU8t!< ȼkBpΓ*Dv`K#-% 11H- NuN Ylj7n *Ayh;ZwOnh(-\ )$r 1"zB"Be!ƦOjJ4XCg(r f,?d_Q*( EtJYteq{aqf c-* h)1 :Lq(HKHjq$bM1-4DLcgk<54; @He^caZyF2|ݖK5772';!b@ %,pQ0q4;k$V%s?ǏN?c__pyF=d2+ы).^R#~wy|ɳg..>cQdGJNtbޒ׷ט!5Ry1PO }∵;ق\CUj"ZE|8`:_ 3&%ˏۆWxSN!4v ƊX[CDɤg\0-d4wӴw[.}Pj32ɲoflh52# mE)ÏIoGCoFn+~`u8bu+n8> svsj!Ent !NX.ȶ@k`R|X*XkKUfe {'|Ӵ!ݎ#/g9в:U݆Wv;`9Y y#9w?_%Csv wwܿw}>sWlwklYa|eYFveя&[UBG J1t=};`Ɓ٤J"`t#-!*ަbJYe CBr"=Q03?oK,=flo~ucڼ* Dmɤ$Ykt)"* UĺES QhU1sb`uT@7 AV:Q<\aCrorg%xO+:Q9J(]aIj8䫳_ɔ Y^qxtzw'eY5-0} #u-dEFU; *uKorX1- b ز٦]r1gkiF!d#X]r3JbTI))yԲt10IUftGJMKʲ*R:֑I5Uu醎f: u6<$0ܳ"K-)ѣ5Pydw4%gRj?h ,am7a Y bF^Txe5$9}g1CVNO1@kRG%kFvGuCf<ϘPCN1J%7,+{q*g'HI5XK%뮹cѶ[V's"pDeN@ABO]:Б+__a[d&-4vh rdyp%RL+;zҏ[P08,ȐDO-%COX.e톛 ) bb49{w#*>P9eY}uo@5uGh|xɳ%a0l 2s2J BĤ:g[Ƿ>Ͼbt|JVlv;zq*;~/i6EtM{?v՞S|`ub9/~ɮAl }?R|67g˗QGv"ۍEizr}ehvIrAg"͎Wk&S~ȣ_٧?GƜi=gRCƗ<_dhr0}K3! 8ZX $'Grй@W{ƾ[FI jLmÈ='Lg5mbw.!E ?Ę`)Z0a;Iy6DIMtM 7uU1͝NC*3k>YRJQ(qa@露(ˊ;obw^H4!,Ic,!0$wduAMKrD$ OU.Y&-yve3+YNr-=yϿ w!~#_I lrD)9ZNc )p{w:gRh^ˌEeE-el mGqmv s1`wBt]`6 #zE]OЪk-`?çN8w|>oq 55Ø O~?b9ɃY'T Чf,2vCrdJU,g&)3U|ۼ//H?LȫwUL8{S~'E 'Q/_mnйZQ "#披n"uI6QbD4RH9f8>YnsŶ1XîmK숲,PJ'X躁*P&BUK/=9P BeY:Od)R5c=_|ZgdJEFc]js}֡B*-$xN}wtl#'Ņ" jAl膞kDYA"%QQ(5Gs$faZ}ŤOAŜ.._pv]5 =7EM.&zݎ"vDi0E02 vku̦lbk D (-GGKʱmpqes%Q*MBRR]0^iZXo7R C:6)22* ismM2SSQKG)2%+w^`Ư??!X;;N`q0 rgJzGe䊒t,)J{b*w)%Պ{_qvv%"֌#S~x}}ˋk;g%]HQ~e%08n那Ohp%ZJ[|'|/@ -W^|y\nE^8ڽ,󐪷@ay$yd ,#'ja K6x1BQEYKbHďK:!DbqH+LOBJ8hKn:h4])#t@jJ$yۢD*WrQPU=X6zN0h֥X"2 =1 =R&)bZ~ &(|Hkub2n,~o޵(% .qS65i EEG^˒'sEpoFpHbr~R&V~@jN,/ fxZՌq{{K& ZFL}8J9`0ft*($,m0*EG5fM.4Bd, m3rwٔz/ʷ>2^f:M v yfy@)v{ڥ@Ye\_s}Qqr8?!UΧ`c=OW-Œۻ-Id(b À33X5z}:R Ǣl9N95;) %vɋ9Un0nVrE8:XQ57774 89~)!Z|U6Ŵ;א)o.ȋ[Fcɋ9@U;27=YQWF `S 5qج{o mӡ2ΡhɓTUŇO9sTŤB JP@}mA._#<|tRK|%izMٺmw]1#C߱ޜQoނ7r|_$R_/H.BrLgsz{uĘrBČ#l7㾾ZB7X,(ruuw ^)Mv}l"Cgvt8Yy}c)O6w jQb:0)k$mΡ|d6e -B H *#"Ŵv*TG,:+TIYVYݷP)1ޓ8 @eI "+Q"#EY b29euST +JHYf f :bft͈i8'S",/u@Č%}? ; ][М]Edu9/d98JmO}еcwk}}bBQXc ffFV=Gr{{̗y!p~o;֛5~KmLdV!gmpq7m}YV`E!D̈N0-5;GԌc  pLӠ1Zd2cX0WW"-!FrI)Ԗ(8Xqtt5M-e!<+)2[Vwyz^xJ. Y3(Y@oGC*ʊlb>g:PW5a قfӟ~ʯxƁs8:<~ՒyJ=[={ͫW]sw'Ӛsf; \^4<ާ(*^⊫;2~__/IZ/_rsb H% b:ӒAS)Q#zwwӁʣtD-TDH?;\hR$/:f#Dd:-<sD)B Ig)~圾. Rbqo#D҉%| <Σ$V(ȋ<+L)JV ɻ DHM5Dk"|rch*%TH\*pb@o?w/*J(T$)"Ujrj|ZRfi]?SWд-7{"FPdA͠DƐxDRf(2#/x#)f*qڴ;) 3CzbL#9:1!ɻ$*=Tf IDAT@U$mױ 0 Bv=0tB3iT>&MӲ۶>AUִMϮmw+ӱݭ UY`'+Yjv8Kkkn_\B(WϿ@~q.Yg2Wy˳)HU0'fޱl[1ay`Fn6kk7~ @-$O䐾1vܾZ n^)89`@_ՒqFa!:AƶDC UJ9#' >~" zC1(ni;8PU)ѿa,"g:0]DU5zB+B/0gT< z T;Cp&H9yYݶCD0xa&̗+:a\jHU>DIUUEM|>޽{e1F6;z3[nnhVJ'!%!:cT9[tUvHpͬTR"eV*G"L'5΅F.JF2Lz*>ŋ[VxuÇ!wWӟp|Tr| ڶEzbz<<}Ic͗,)vwu=Bu=sCʳZ2f{CYHqqD 0cv>0,YLW&`H̍~!eox9˃Cm8gI5Z,m#/Nܑe?IҶˢ  K|ѦaO¸)wtt3K&.qa.J8r]bTۿvfKTeDv5k#7W7ersVKxaLQͲy9''8?W4;G#}],Xdb۱Y#7#?!O>9?ɗ8ZӇs@MxuY !$Dv{G9 Dk'+Y|5# xq Bܷ6)Ffv㝥p2b툵iRS'ޑ;I|dޥO2FHCȢܻ|D ĦS$C(E E?RdE" M d[ +&@7]8X"]\`!/,E%RDZ&V!b@ M$L B@$sJnرM`i!QFBKj[L&0ݚmpc{z1q *'mOp3Zd{wh2B5F[0vq0vԋo\Int-J6tg`oop&Xx[ 8?"$Lg]ǟٟQV9>(5CJ拚 ~lۜybT9E&ltnduht'4]-<|RT)N?!@XXﹺca T=YQwP\lݤy<9_ G3YU'_`udXΟ?øÄz"ʒ垟siGgZ,&}o!2ӣC)]34XzϽ{[3eG'#ԋszb) i7ka:0tkuj[o7[6?涡5>@3 my#9fcO-3E]bi뚪*Ȋ#y!)"}l>8;ݧ(ޢ&|_pvvFvcAHZ2[j{;h-9q/G xR5(-y!#5jI=)~h e^TBZ[%h>ZnZR=sȘ\72$'kH@B!WgLY)HѺčČ mZɔ"!)c$@^L'@%BRzw{M6lwh3Aog n4 BE3DJi\hnp)3"C)b`/oX.PYb~洍`~M0ZFe-ca,3Y~{bˈZ&r6! FŐiQF@o$CIY)5IH4YlRͮZ33x]&J9![yt@Dw%X0$*VjD%iv3!F!LeYa`D(I0ro4yD&I2rl1H֚ +7-8ɜz~կ"PQh!Fo` ovY0Dn|#YbMgoٛ\]]'Q2CkG5Yh{pa`~8g`{]`RϹrFJɗ_\ŋ{\{oӯ_6:u;嗷ge`>9::蜪:/ ?S%C.sl#C&ހ#J%9x=9?׉̏JʹoNȫm:WD].Bį)tƸP,^0z2ҬD+JE0)5fx+ߕ(ʊD׎Q"yv܃x J 5(ۢdsaY`,HDT_, nm؇Qf\ ivc,JA$qƣBdHyW.Ka_JYV*ogV8. cϹmeHd+ A:}v0!Eh7)Ij1Â]`6m mnEIdlQڵ:Xo̧whwrwL)7C,6I4pxxfٵ.lw@e1n)αyt7h:/K gL.p~ Lfno |m=(1gg<9MǑ2Ӽ~by8;;l5UL*? /o?[s\\^F+m;I2AGQSS2=oجLl*>ɽ^o '~RۻKll22+ Ӷ;a dVҴ[4wy»< 7|75nWt@^TH0i,+ɽsTzZ=MY=D*S!oDž}{ľs̢c(RƦFhCD1>e*Gi` jRdRل1rPO xhv}ߡS0;Bqy O3T჎^3PhnhIyAEH3AOZE`튪d3Ly17Czz|lQ"ϣa^Te5;/iVl!9>~c ctRخA4%I2TTUoѺ;DN$J@p&<,G5x:&h0#EQ?$JzK ێuQSUѹ4)+ 48RIRMjh&oc13HZob]=)3lJE&snh;|lR@*):;rvc5*jʬF"9_2E>|B?lV+|4)Zɽ0ۓ&)!-IR%::6˽vK/rMU8I}J*g|!\$rF ޭɳW{S L !X.x~ وw#]!M֣tN$0t% +cs:cR}8/֓ $n41+3ZH)iv-YZ3rp|Z{w:]+EQ%#hn;,WCl_g UUkc&ӚKFc@;֑%.oS>_X, tMۖs 'I$\1Oy1/_>gR+Q\]nl{v!={ʳ茳83 -~ iMQThmLfhہO|%!i:<14tͺG4P1a&ZUp #ўQ?9{RHIc!~.ϐ |<7a-,֑9!)3C`{(KPXPRp'":h!B@ @+&fXc}w=Y`mnW.a:RrAicU4h,ɋ)Ze.@9IUJW1m!M˗S}aI c hGد xK0}`6 =L&a%/RJ),_|!5o񄛫tBDP2-@^v 4˨JG"C^('BO &:' ,yHnK&Us,0v+ eO󒪨ٮ7k9 8 bZgnxO+lndcy9M]Pf%v{)נ̎ۆ53Bp}ǯ7ɋ(O?sfppx]kh:O{ˎsuM%Cgخ[Apw}r~ gO^Ǫ^Bz_?ƣg?t߂{*8,d_\<"Ҧ)秜<8bZ5|򆃃VjF~6Qd%O<?L$f93'4`P[SF Т`Rפ{G7۩$`Q q&et8" b?}JX;vEkP.`~gɲIQUʲĎȾ}l12@t!I#%G e1e4=8M`U^hDQ2 6@%՚,xa٣eEM@+\$\ncMɌF%iJJQLCӻ5%_eEe5mw4È!V3uUA7a΁K0#Rz%"`UѾ/UJB[2CJ&^CEE(r&,2[BPΏcb\bi:pW_{[<:a/n...HӔo򷨋<쐾i۞$xczv9|w6/g<}S@[C [Y$RW q :=:ߨH6@`` BFnoGRcQ{F֋ !f x4B'h`N\l3gΣČ;|/ zzZ$g|t2B A LŹ\|`D LsN1ÈTq")L3I"Aج?w-^j_7Ixx'<<}4|I}B-RxLY,TUNib9E]7 q\:ci^_eSNOOlߐ$Q +lJSs"v/@#v@(1+ Xc+ 1FdYzf''sfׯG> <.Z=st4[ftuoTp|\QV98Z-iyvm.JɳgOyXkMk=iK6?AwL55ϟ)!ok\>{~Ep (X-6W#{pXsUTd FB=:Kp8 !߅(VBAo0tL&fߣ":1躍8w ^✧Y Qf:ΐ(v F@<>Ĉ#mPm1#K nD 2Dd I:A4I/H.֍K3v (>**ڎb#GwY#=X}ȳ$-bSRnײXlX-QhRN;KDp "# #ZKf^ynQSU]7_a@" l.pYwa:MǓ GLu4[K%5/_\l "]pttjos~v͌uty$Ay(m]_|yɃO>~gx~ Nғ /8{[fg_`HgFO5kn)U1ڑ:*'l׌ <1H%ѢD-'ṵBږ'xgZxt~tz@븿gRk6ٜtҔ録u ˫MH(ejjBP vV)o6?Okz-^[?ÿ?_O蟋oo>U`Kno)˜Jd ,yzMPXݮ"oِ,1À$Џ-5wqY.7 r,whkREq`DЉ!%xŶ辎 s~8?.9O)4QL$e C=}:t<>{4ۮKi^r5Gj3v,=vLP:1^w# Vk<~$:'Xooos^Bz?ޫ?.Ư}䄟|x,^Z*gq&/3D:4QlHaXd,ː2( n=1ɢ%ގQ08lq#y)2oIBn)B6u=/YkDfDl@I5eEeMq<69`ȼ#Md (#Ls-iZ)C#Ep 6{,!ӷfiK<|rҁ Eu1ڷXn7t. Xb;'5!M@۶R#%UD)yQ3"BiV}ߒjI臞2˙ΎtzF׎lמHDtc=V͙Lsg}IZ\T&9RMOFi1\_?"/^2sf8ǎҬrm,JnC$,;Dh hLgQҳ'? *>Sa;͋>ZSf3H9='8<< #&Ҕ4(O`]pyn6!_G[Oyw$F)?@&sLGB̲,AJ1($rpHYܑHLOu$:ַ+AY_\`Q&bS06Yv͂+pzzʙ>PXI`;`AY1xHRJb20l662b4 66|JN%E?v}OyڋHh6Z2d;/) #U"eRE1E( !ȳD2t#DtF"wJ98ƟxOuj΃tM c< [LvWWkR10 4b'~ߊ PM *KI޻<ۏ yB4f,ҏQFHC=Lyz jpvqp0;d-Me{G|0&3SH(;zb@$(\FbMLjbBO0ֱ\m{~Čs } Q)BOYjLmu)Clؘ'B,y{Pxb]Ӳ]mY/byF܏'<8=W\\pxPIqi^&̦5}rs3%ҷ E" 'o0 LfG?:m[}V 4۵@&i7$ Tq8x<|)ŧs{DZT I׬IF|)͎ /Yp!pG&xұi̦ : c$mwk- %[?yvqwK߮@QMxe^sCk{dp_Rx Ӯ.:3t-<89e2g}d'M;oֻw =ތz2at0za_*V a?@D#QqX7]5H ecl,1z#/!=mߑ ءd®[;L3; CN(%vD&xihb\KJސHn-JC*uJq6r+@O͗xoDqnxpFsSX)*HU|Ji κZv ZLT!*G M3h+g!JXaؑ"˰Eʔ0c;/z~&e\^}I 3_`/9V(_.]pWXkOnwW-mk󄪨9>:1ƺtlk a;IL4̈ucpjrLg(屣Me3N<"+ v=I_ޮIuT)Cqޒ;ptt $/雖2Iɪ)o$ ?mZ$=WIHRfc*V{|դfvcϮ^pxYRWS2ϟӴ-C+)Θ'Lcf<8}w#/^~x G=,VtC 0z3%H,|_p^^,Y̆4 K{*3Z^߱mwDu;Q@etCîUIiv uYvAix$8%J!DFu# ntw= (hFuDޖR8cBbCȄa1v O&$Y 1EI0th#u$4tKt@]C+&5Ir2CJ/ײ-fm܈"^^eV:*]PH @b<B̙`$ĐB%&H\vYUYټ>۞~we P49~~ٌ:r~l_zK𞡇9? Ð@ƤqpcTjDxtV1Udb?V$%t"dR1ju-&qh´HQO~G|~?bh@ ☲_-r..5/_eZ(QL 0& E%>uR%AMC]@>j ˇB ZM٣FgEbeE\9JC:=_*^<&Șd}LkEB#TBqQiu 2rl?eRfP~MUUn'S_oq5L"q kxN Jl9i\E% P*^t=}W;A1Op#"1SMÁ=Qn,/3th =(:dX۫L&fDYcz~rQ}> =ط{9O}@R ;\Tf~~0LQ2C!Q*9t9ZyWoЗf7^~soޯB~ofj|lHvNw!ELD茮(%EV9V;sG̑1`]jb6+9;l{D'FkR&D`;"4J2LF+"J)bLzCӥS,)%Yȋ2lŨBkU:!i86 woFiĹ5ͭK J)~P2"Jcuq{BgӓAp IDATG7 xT,ղeEQ$_U#E{W[s5Epvc|#WJ 2QcG E>IYEʘӒh=0}&ȴ T*ӧ`ƺcz6]jBK)u1 ȴ&F0rhlRd=`>C*x%Wׯ)+Ek@`ْiI]ƇJb3pr7_W4 V(r5MH 9UuBU(_7[NX.Nq2kvi3=lu A Ͽ ~_uV'&\e2;^rv#Y5m8 gGOg(QD41$|!}:ų+c{f 0[:7@9 |W<9]נyAN:yQ Edd&JeP(|>E4U:H;T⍘{Dx6a~E1[DXG I.9rFnBBd05Q*CDȲ:GĐ*Eat1Ut Ys80TDciY'1}ߏΗpQ09H AH>>f93$]3@(z?<GBmw"jEzHEhUXˌK?Ͼϯ~ ~JG!X.ۯ{|6hy6sR>R9Mzl|b%ZØńɉAyF5q" QxlvOE$lD (l$V,9!BHY)2XM`0";GJ.Br"Iy 2=Y) UBH B ÿW{-u3C'p3n YjA|q>bcd8BT1 s J)LS,/ɤ@ 8Jbi@08hF#brR֥!K┬8>šOEj?͛WLf [UExS eLL!<(V5AGbqwzGQLC;fO]T%"`[Vc\"Pl }O:ux' >xƽ 8K8Q4oǯ^ۗ<͕a톬kCkG5zN0lFe7iRSWSɪ ~OBT qV(`2躎eM]"X-mPnXLg@;Db[Yg6'Ff XXpt\^o7[zi5d?!1h~EYąж~z Ȋlƃ9<{n}iY]#Y6йPBSKuP D1CqjWfɵpCM0tA-R&^ juN1}:c]""($ZH J"$ N$ZgȻ 5re(@ d-MsxWj8qɕ)CiPs^zS5ۦnbgi;qrz6:՝|ӧEq=];uk%fpv~|1emy> 0.>/ ]HNCK%o޾nu!sB1v-uw a , KPj G%' N( aRYFeY$w! !CVPU".pR(tV 1r;`tĐTHDbEFTHR!25y&9:czuapCH"(*$)340VNJ/^Dݏ P\CH!Qblhamĺm@Yit&RIHf8d.8>Lѣ2G "HQa#r^B_X~gc6~mYZnnW(]g_<T&XPhP)2=tM(Y!"^|vY>- #%}izAX}Qnw`}@{0> bYl0咈 =LR:ǯӴsdY1u-J)9[R!r|zBY8k(g,ٴ(=dy3ҢҷZQ(.2M(#yiO*EQg١U)]g[>\>bRUM~uZa}؈1ɋⷎ 6tAQp^ٷH]m|vY.+Eln%YxWWWX9;@^w;^~m @o=]z0Csml6㥯gU{,LkqCӒW/.̎hݡka^FdA9ێ7V%}:#nGF`oٮ,OyC}~?_9/˯yR25Y%_5ww7 WztVD Gg3Ƞq./6K>xzL=7 β(Ӎ'դNm-&ٵ8JiL1鄻iC$"C">1R5}sh :tCXO5 # XBj l? xC4BFLHqmHHYAQLLErq(jvg9Z+$$_ 34z<<$u5V!ǀa%j5YY_| nn_s$g%B%ǃsf{Mgz|@I%+TmXNRs_"$JX3(jP5-޼޶,5uQ14EJe%R&f;TVaaV1,K'Jx7 }rt5\~˗YD<56;tV2=e>[ʰ}=Msl%I/QqBxZd9DGe"㹸b;^^߳_y{-S31OW7|9K{ڮ=8Ib!FNzh0kר#Аzc\*\*3Rnu@gc@RD}j' e%`džkJ8ۧ&)MNv`: C{猈!}1A+1R Zgc*}moA$h"$ˣ͊[`2٬RwXsl6kz^"Dd2#Jcdy4ѣ|'Xnv\]x ́RD<}O"x% ΰڀ'98Jnש* G->4ֱ\^r|t|nHUdzu`nTL1d#kĢ17al"5Sל>,-"ES<#!RL29IRJ \7¿H,:GYdcV >5RDd "T1RyiR}X8VbǨ CA$h wQ)={X,N@QI($ : cJ .LILYZ4Bu{T[8GwMgB%Bĭ3#=B{WGsl9=OMmdRT2Ejt7MXL f3oXi=BxLI)2cu=)!:no6<{wV%WVwhy pΏzA"Rd'jȊ U*g:[b[?>9EEk:RM&t uu-M%ԓ) ]w}gZ${78AXbg9+a)jCqttFpfw=798` ȼ$ Ϸ =0tC=t, Bl?4(8>„F` |y|ׯ_{6^yޯ/wRw7=^<~􌪜^ѣG-Xl6E٪LALHe={ƴg+ܰ*r)EY"dQ K"ph8.6,c9l;2OUxWm(eĘ bzJ .]UK.mcF\82E=nlĻ{zamMO)b2J!@#Bd8'*anGU3jzuڈ_ggRH%G%:ǃsY}IYPNjOq#!ݎr|g`٬,>}ryg8q*9DLC$Xnob6/&*U=RZtv(9fw`܁,);(nt) ;Qxs}EV X )q G!yDz,cێHO!1иG0†ED3 ..$9 J(4EJ ]81 ].ތnCt]\SV50ew 儷o1<"x2gEdU>U +zoxÐS%㇜?8f7o))yV]7V){<||Tnh&e t|ٟ_52OYCf_~ _|@+OY(X6k,SϘ'w_H 'g9SdXKYyMdŞCӏ Yr6<͡ۀV#>&|pEWB@GsTR4@g wECq:ֽ,Wx{"CHbQJm-N'TZO4 ?߉dZ,S c4'XN!5GF,Y%3\QVk]㕎L/ %4y!1Cx~Z뫷{@@Q!UOQP# c>b0m7 L+" .0}ß_?x|J4z;]C۶k2 $(:8{=ue\ڦc54M (<ϩ 1n|Vt j(U<6brWw3BŬH-ER'霦i*PPeFg,g3iO7l9ta ɌvE^t-,"*@ѲB$]__S51F9$ fh6[.vMCYGK )KI]fTSjZǼ$"m`=3EfL"v?Gg\]]t)l%Jn{O-Ơ)[8t:sEdo˂_oXm[\o-n)8fai}[8rѳ?Ь 1H*GkEUVeEӷJkه 뽐~_|o}@Qb>G:zٳ\. |ůx{ b4MC] }f3r-}ۻudyj2#Un7YIE(@Y&lqD*'ƈ-B vAHIQfyPnC ʢDHAHFXh)S;gHt)%Q$=nz 4U1 „ i$T f!W-݁7on(^Cf)BPϙ e aQam Ͽs^x.F2[.ZS9ݐ>ZA$YJ^D.ՠW_ _un9y?X躆Ŭ W} -]5壏y)h㍤ϒ_d}x{*U`#0 c_1LmkqcDWLQRLXHH%"{+#䌲b!&@)&ӂvzݰoJIDP ^ DFwi,Q)kT N E'3AX...xaaa|$6{C)999>A׃cXJLinZg7oYL2LED׷wl=y:K5(59UWBDpd$S4) Ʊm:R )"Bx*bRQ8aqtI]cw}ĔcyU2O)3l)JIbvKlZBeddǤ^1WohvoXt@{H1@]M &rd;zEt(N$&I'Ϗ͞2rq|n}I,EBNiZ g}ׯ&EYc  .JETL/0>rh#yL"ޓ)壏y3*&%w߾euꊾmң,2S:zk8Șb[]Hi'yD<2ZТMRuFzQ dB{GNάH;lY>h%>Wn袤&<|Wx0]%_O,\<4[,]d{Hg+eNu/YGYj۱Zw8 'lfǀa8:9ݳ>mӴn3ă gI ^bbzEH2tlzR2ɲgO>o7?}?_8zG6BT&G8 ">^R"\f:C)eSOodY֎,1tkĚ=J$W=ϖX빺f}|>cR\c{*V" D'@krUe1ݚټ"/ DL`2vNUW fcRyKn_rq|Y12^z~{R2?ɱMwEYlJ&5JVzC]qd2ڞ/%-eahN^D=8]8'?dϘMɲyzSrH ټ9J4]d^abgƳ](ǧ<7>DR$H~Eovu(_M\5$$bSzXڅ@(uBa DlKNOq4 b$ 9DBa <8Kvo޼!0td2_BDF9l6{\NUmu(XRMoƧLsܯW<|gܼ|saY_?'=ΦhZǏy!1ڡݙ=O ۵^5 Ň@AYqVG7i|%SmUeꝃEK=,rzqqQ< fӈ͊̈2E D QUQRDr%1Í)%zlt׎N%aCII;Bc4Lu2EW_?l1CZR׷)"m<'8ypT CmoX"PLѻT!ZZJ\08O.3Ӓq@`1W|,^yޯ닟ۿ븽Gxrt4Cs>c-~K}BD{g=ww+i ַ _Q -J)=Ï.޻X]uۗs[3ǒ$@ $$xH # ~@!!ly1,Oϴ3]ʪ̈ۺvdlrIP22.g[> Zyr|w taRQ.Z:UU+ BJYQ# I *2trHHrB& QRֳ EM>o8GrRSdE"zO9Q(MŔ0l4h]RDHM. u UEuu3LJr{ȡ]; EE]\bDze%G(xu 1S%PW:W:6v c`R,T*;-;i ey;3/_0 !)JAۿă{_]xr !sn뇻a:I$ 陔KJ`k(+fst^5wr! ͤ^l#uKnn.6K4p: b>:O]8>z}A=Zizί>'\\5!"uӶu#;3?g3w]{H|Å "[inP. Lape61MZcdi0ѩ \5#RJ^aHMv;f&?\->~M 41WxJ4ͭ4Rv;`" )e\ y)ݓ%K zFQV̧ 6 bL#v:iJ9C︾@߻<@)0q`.<]^| Zx}d6[2sl~/?O~``=*.IjH<ϻ_2^b0G?ndRc <|xtpqq0hݶݶ bj*6-vH;5IlA "$ͺ2U0B8p*+cI˄dپfYDl(D=JfQg-ÐTǍ_orEI"Ȇ|vqIR"ƀ" z\jsH)GBn@ PFd#3,CLjEPFHNBbiV(Y1El2%h#gm B$IIyM㆔F PT%$5Dt9!C8PV:4:zf *(#q6K8?~ߛsXai.EgyqzJ7boc08s5+-z",r% Ӳ\#.H)Cf@x `("}@0v}7mS+q3 =EwH*fJ㷟 duf:G8`BJ\RLL/5#fBU zqvq[ 1,g)ݯ~?S^n9> s[f̼+ h#!Z)Y.dZ ,Jʔ())\(dr$zB۞]{*&K 4r΢Z9)J.8՝7NfP^l>BH=Eз}~.=JEiAL 0HBD ),Mh;G]zMc~<8yã9'X3IHvUPhBF(aQ 7rP HH%8WrƋO/ \o!ziw/7ų\nZx?a:hzR ̗3կSֻ5=O_<۠$ٌlƝ<(rszŋahOfwL_o7lvkN_zR'(M! vMս P eJv7n~`-U]MXmQR?}[0K>gLY+B0\n #xj=!E\d&OL$@̑pzEg0=]Q5BgP.+|=SܹsDYLQEI%׫WL Dh[겢Wx;,4\\=g2ж;inIT ~1z. m-eQaT&2oJtOt4WDY6\\3mu?>/NFYJRV+4ܣwY_+oDABMc;\Red[ !$ٜWS u\Y|?/1n\prr¤Z5lr2NzwMپ$*/OϹ{'euMRhH7:l1r!'a V?`7l~kt88F;J=;rRVE!CGHG,0 !%|ѝ;\_p#BQ e]\nQ& sTn ln) ](+%(H+g3Lc!J+!9ƕo;)( IH%邋W\_dA% O˜n<<%s" a"|睷8ytW4YDp\mNhcJ͖`#wai9{Zxy9Dsw\.cm\uPw,su)_@߬7BΛf^у'7')cV/[ DSkJdRSOJn={8zsEAU(%իW8yuqL&+9ftVFEuònn]2LIgseP7BBE]eE4m&%0|\>m(D$F(QT%+qnT$2ds2*j!/6_s}ߢuAs-[)",fxbV ucu(!q 򿥔@$҂i$rCi$E)hs AIv%FH&hyq3[Αs}s)DK!GeRJ2)ȰH! >1h:4}¼ޣH IV !Ð(TEUU$cnP*o^~2д+Vge!Bsv~vKUNTՄ÷LSc&F7{r*"\|v@QT_涊TPkܫ5+Zcχ9?;tm@S^Xu޴HrhF1,O>vEQ\^]p@?li&H1s!Zde-ߠ O-7D)JPD#Vt0X(ShjtCI`6moٵM*ZKl&Mg{$-rX\KX?ж m2CsTeԄqseBж=ӧl-!&#DTn6ƚkvC)n#D hY]m-}Z'L!җln־3B g\ <|8??E H\fb$iœ'Y/xqO?43YFGD`DML!iqqyFYARV{ʯ Oӟ}D6=Oϱ7_CNO/ڰcO٬[r)^ru#EL!G1kP+d=I|ZD$Ď`=&Gt9>=MYG0_ d.x )|$]DL^cUHxsG&BC9K-4J|]DH>G 2^ud-ktr?dDfO[[|"il’/s}y0FD[|"RKO $u0rmiJ]$|x6"L1"d'M4ys-,!D4A낪ۛ#Db7eo<}&P)GHlK ! V+.o>g:S%R%t[&ࣥ?W1~0AF+B%eحlv-ZMfvS~<V*p޷)J*qVp63&wSwBSqfusv%5ќ^jG8:2)+b;p=-nvM>`e1#=|!e!pÌI^>BQ5]d#8v;_}݇,B5+]@IY̵fsÇKS9ԵQ׬Ez"_ L;Ggg۸Մhm*d;2ÃG87^ )"k$I‡)D|t ;# ["d`s(K39B)+)-|bl$FEd!HBO?̿'&R [vmQ5/N>ϓ *m4;.3q|@qbtCpx| M&o[vہnB#adgKJrsCʔdrӎ 1OZ&>H=UTD- HeYQ6͗)jR4펾o)JEQzVDȄ)E#ȃ2 @@9c>ߣ&LShnψ I64}#rtϤc;w%Rꊟ~S#?<)zNNNkxf3<@\]vk!= ,7HP%Z>ί&5ٜ+Xs}sIQ< bamv1iɴCL+}BEAYcpءc2`a4SXwzCESf`LA }*㪮ځ@">1Ƅ!HXR216aƇ,vN5Z">]DSVtmE];AiJ/Yo"ע)kSsMuo>]Q5Ca&Ę(L2W?|vH gY=ܹ3fΜ<,nEIZʲązRa裏4%4MCB|_,)xK ݚjn}$ ^Ov|oc^|ry^(7~ß}?O^\]>|ȧ~'>YI!5A`ʊz+!یuh)zT38[x֜;]aݎ{vzB@jR"vנu|Oy6EyL]`osSJ8^ UZE{ ص4][ІR ],'j3 ?c/2pJdN $J!|$̺ Pu>@{ȧ:.nFܺʎ$6Hmh,qNjG am+VQ}zɏz-[zP*Ao{R@ć-ζ%)irʤq}e^N TJ8+642Mz>w6jCm{v03rw)idA9,ۦbfo|/żKRgPr" 1?4:ΑpnlayxrT0p~;l9xA=gZہ)TWŒ; u|%)> -{)(34HF~װ+R;#k>J\88|`u|4 mCJIfgaRe!L' :pﶄY_cm Z;!hpЇ,21'"m1sbDȪ`M?77덐fY_yͿ7_O?`23p``3@.+RHYa]ζ;bE,YŤb<$ISEU!DDJ&9BԲ9v@4Y$pCTH)4L):Xn(b!-ϒ$ZewEbtvG]k_ UV~ƀf"G9mxG\.FcJta DR[ B XYL(+SIM5LC:[拂i9#TztQz\g{^7ą)/_RWbN.*!En#5Q}*PP%ZHV/xuv{jʉ бڞ9..XoIx:chh!Ҧ 5םۈH̝"ʔȮv8{|tbh+پiv.mO=1Dn툁oRf!$e'N>hH-9ٳS).QFbQʐ)) !QSv]  Av9kR:HB#ajdvaD Q0l-]FW<7sL#aHE Ït8>>gn6#SvS4K{=mgsu{O[]N|_fӏ3TW<4Xq2`62MYXonr ج74unzT-'LMfþ i U+;4]rWo}a\@IX>FOɐFZY9"a ]7g ݶ' U E=KOYBӲ6Vz#Yo~M<uL!#wWşon >} 2PUBhbptU{DĘзK5)!7p{6VII`tiEZtCTv%DЅQPFb- r%˧A &QB`L(c|u0 JmvmԘc  DM AL]E#%@kCw=zgGA[<5FB7FNEZ1Tѻ-IH'D>BFum:&]. kXӀs)#ZW,3dr<}]kL$qq\^ ] "@Le Zk'WnK921xcZû4Mw̯b5!fQZ]GarG?1!ffv38>ϔ}suuنI`I!eA(q<EYO1qqJIRh)Iȑ>$2 SLl;Q'\IN$Bfw; HeZgƒ"hwc7ѭ7:;^ 2_"&C<H$q)IT! BfsQjRTXЦ !;bƃw^`gk1rtjG%jFeG pC?S^>0GYD.\4h'woqz"sp[0tv^Чo#}F["G[n {+p.1Fn4J%^ 96mԊQ,"v,}) 4T-|{ܻ㏞rzz֒|7 ?~tClRqu9l7=޹OY$33BHU$UixZъ@aN@ -n^%!HDȑ9kFW*&_F'pT F'M! kPN4)LΐdM$ MA  mG۶`X"޽\eo"=foQ@ssR3h*=H9ڟنz=>ƔfޛY3ԩ܃݉cvI0$R\A.H.P#H0J) " 2XE&nvUL{w\#ݻΒѩ:_h #FӤHpu3԰dg#y#XK 83Mي|Apwq^o^׃z?c?~Vǿ2/^>Eg`:Oտd^So_zC BHþ[l6AI[ek-I;/ꖾ7xuIW!ztdJ[2i>[&Thr2Q C0|RB) e2Ǿ:!H$.QÅ>%HIU0Nt= RhdJA%yb. L9eE9h[K۷}T3AX;buEZ(Z9lg 2r:*R]xuRUQR.+I&%ZfhBǺ|kJq}ʉtPaM{c1X3ϱruuE Rۢ7C(٬k d`p&uŎ7)MMlo#:8>>`[оjכĩP +ʸ/vj oMה9EUw5m{C g+-܀BEFY)2=kwD# !W(@ߧm e̙ֈrZhn7ŔPYΦћF#3z"h (R '9*#P)Ȑ"1mO5!rt|GOgwx&DH IDATځuJiSl6#<7Wzu _tmO=Co<{E]|.hL˂ $wy|b;Qz7<-,g{G|>-~ihtF?XTHCg)S21%_FXJCiAVFБ"\ ʩA),һޡjmڐ8b)A2啦(XkѓegjB#O< !q"mEAa'HҠ4 M̹dX1 Z]h`'JCX;0%%bʶ^\*"6ĈHR'RA:c*X 24S2/xFR+N2dr U1A}HV""1-WEIY5HM 58 oS5z< c.EH X gM;I5LD(E77),'`EoɣGk4ۈ"[Iu@@ɂ,SN-`<[N"|6It%DpG%~H-Vȇ8sNe.SM%J)>f%WW~gtU?g8?;wM6l6k?VKV3yn^ gQIHxDi`֦:  G7>K1 \yOY(&C!yy dyxDQ2"df_K|W7V FH N2|/#]*~dJRhp2+Mc:L FKOKLDubU(hn-meFUdδ4mpFO?} EguAw4Kj# cHtn 8~äZpZpsx yxSE [ֻ d5? arD$ A GP,ٛ#dT\\>-XÞ~֧'o{$S9;}H={#noo6{i*#ixHOɠ#T6+"t!tt݀3"sQCH6zLYk<Oc)!Sٷ2 w= CH(~ +Bk!%f48X(3nnn}hˁ+fӊߠft3M}m:j mJ||W/S:) w!жLJ6_X/2Dvo![MuI5SƇL:(y>1HR Gd"EDqr^\iR5L \pΣe*$0T"DDϔX=5D(lR`1>KsReLeYj4!Fʴ ]C#_$Z wF*MckɊ"8n9gҠSecFbhX!S&P13EuhAIŮ݂oa"۴AuƳ{$RMPz',f6 \bAT# M"J([`tJYTt}GI7 4r] py>Z$rCۦ+8 ':8!eFp~D\{5uqJ}>ZN$LN\ZM|*=)}-th.EuU~$lk?%~s`y _ڗ<}7\_]pqy+rtjnt*8>8<98P Y0E Irm=K!v*jB<*]0tM:'MKoR3儮f Ypz>C^d~ʥg:8;kE)ph5C۲ܲA,dz)g-]l O2 JK)\ nZzg ܘMK([Ky|t 5U%"H/w(  te@93Xoz./6l.yッU^9i q__7~ĿiI?4/R7kc`z#RsYx_# I "H&QLHl7􃡚0%a2ӳXV65Cl춐eLg )~a6`69mM ַQ'LKh5)5X J/L^E蜀"-8;?bz5O?d`5%rj5Ug-ƶD6ΙL!+rj.f:Zkxpm#NO`5 X,"m`!ENQBK$񑀦m#Уgg54 = nXP*qq4е2XCUUҶ5WWMvK6R[G}%EJ u$=6]*1u `"B/~+ڦei]"Au<@'Ώ3m~ 0 v놑1!ObEZj ONb#W_Ǿgcz#SOywի M*D:4\ڄ&S7wUF glb/'G^Њ̗ӌd:Lf3 ':p0h!:U9\ ީ"*~פO,t1)@QԐ$H`!x]XFͼ"( {_EA4Ho-Yh*O|JHR) =EQ0%yL>Jd B(]븾@$Oulx/1}pJ/.^!C@ak RzӰYXΧd*CKuL*M&LL tQ-BHca>k8Fo=ޤMHc[WhU4}* [Cp`v3XWc]m)94 &lb @0;Q)+qjGpE*v,hI@aPqȓnZ7LeL&ꞻC1C#sj[EEk{?C?L&%ł/|x+>qqk:N MAXԷx;]r%HtV1?s~b5d%-o<|o:_=էvPD8]VViZܣCAC; )9kZi]Rv BÃCʜY( m2T唛z! я'}=r`i,K"W>Y8glnyyyA"tfM ] 3SHX\>d} S)ԴRi aZb-#Zkn`19poI=9V . ~xd:]r{{ {rJYUIvCJ'㖏x~sCE+!*1CH "ϟ7WϰCnnA 7@v0eSӊGwNKEQYUrsfs%489Y9￿,+N99:OyS\g)3LS)oX/}|G0>uWF@2ͺ'l88l-I7dYr՚k66zGܧYTD9H֜1%F}gZYM$E&@ԖUzG q#g|*GJ3R}ݿ[w*g}vE^thN<\q!/Y؏It XI@!PJJe^2k HT z߮wTu`'o<;lNUTJn\>wG,>bhPǛI,p;&ӜBgMuAB`C0 8mp>2G'tg0c%CϪ2KV>}v ֔$/QIRD|De:56XJ-<(rXg ZDR*b"R)DEY899g6=`ky#rer!jA5/v,3d`5C8K|@dE``6-چ"HNPdz7!NPRiIU CiM 3t}z}ˮ'w ηD*'t_"<.HlȨ:gLm0.⍁Leb~x:& [v׳:`!4x1+2ͺƆȤN/ڰ4pic-Ir]vJeHn_MȾmȋ).x5Y}WvGt,+zLb4-Lz(T@9³<*8}PX a2S*f;v IMw4ILCp1nKGFج.d_c"3d`7eVR`|]zM5haD$+8)=2)B*v(DĚ3o &%@bhB$Ψ%:#6ӹOWi-"tg8;HGDT,%ށIQ۵\__'|]czNT9kM*0G@^hRR_#sB *2tNb\Gtw5UAk9=>cwzXo:';POUdI]D7=08= jXF˜U*ACz,>9|GcI~6In/q|6IϽFTsupjwϗi@$IF;\~7LX#x'w ^l/s>&< MOYeR@T @o@ijD Xv|Kg"=?,ck@ L'G'sdن.Xov,W[)6 fG38#J#zFKn>a;r,>rBKIUM1a0‘ҵhJ:PֲxVRd+b`v랶Moiw=BB:N3\ܲYon4uwvmJmT??L/20EQ2!HdZWկbѱ7\^]%+Bl4U8ڞ u=v)tunnecSm6^|Euwb?8*4_yw88Xlh?i;!w,*<萯뜜 /ۍ#bGh!fbh]k ^dFc<"q;&ƍgqI /ʣKTIdGPMDvEG?ԩNTUޅG $l(GIjbq9G HuYAp\_Y7ɔ,eYnYߦ$^SB*RCIR'Mۦߕh!!X%rLS&eWж) ROkI5Z L4`S2 [38f`}~C$ľBC)$YB!Qx/T y!GGT"DK4 ޞeolҞ{ .@GI|67)ESַ1o:$C`* sȰIY2.C%l>?v# v; %yWo:+1>[../O8w]c4 +*PS=zoPN qQﶴdSfm RM'YR5])[.gJ L]Z$B DI,MH-.@QUX75H`Ew}[o0itt]Mߤ4"Ի5:KYɖE;oFvΐ!E Tsu%ǜ?xuϫ-uNNɛ_ūk 4vϴ; FA]7(Xo cd6x!4z^'r^<<{~~ᔃ'<{Ӵ5O>9993y@ v0Ƙ5/_B8c}>s<Ռ|,yQ㔾7WzW8 QV9Z 6cr|;noZv` eFY% XR+wiAO -K2R:fd9]W&0 ܬ7d$I+Ə #ZkSH$Ay`#)9J r:#tf]߳o7*K\\𩮣b Y: Ğkٹ[5eRLVi%y&t]~:]|HUUzwLTZhAY ӧC*AU:Gʌ gC)YEMcHd@Mo8%'''\$~ÐRMZ Xv"+X,<yn,*+&)mP7%IVLΗdEIw`\15!$=fÎ\W)pn ʩfp fǮtwZTUS.Uc->"EEy<&BqR?Kdp}ӳs!n FDzax%mH5%iZËi eZ R1XnX VdLGpWJ>T4(8dg5K`*9VJ +܉* ,#ȤdFo}ݱ: ̉1R7l')9?'7WGѴٜrŸ@ (Z`%YZ Ex2QBloZ~wxGoeĆ )sNxAz[z3wBEv{zD" vrB60oɕ,4)7AӧcҪaxZ6l[tfp@lbOsAPH4apP7 U ||)%^lN_SzzN^sOG١+??ɋ/l6ka`<`;u:ݯ-WoobG.B8n\\4 fH'u0þs}svFg7<,(Il:K]ŲwH7G>AlJƤdRڦk P2O8#ա!5d!%b Hi\emi%&xJh5Kd"F bUҧY%/_]/'3"K̆FDj {G`A,+uNH(Yu(bx/R1.Q J+fO]Qix8F&P: UkKe1X 1o膆LV!I&InUe$䬵l6khmYu5LyyN}5Ro} 8r-"Fpi@89>R''{ocYyp7CUjAw  $ĴcBH,Zb B,X$!$Zlꦪ̌<t3_=?RM!7{߽}e5EISτ1t85}{I^+ߐR+#2VFa;pGJ  R"DJ%2S>{ƑB%^TASq}vj#N麁WVlrzeN/ys,~C~ӟb KY4 5;-x)G<~򘳋s^xI 5W+R,جgk/E ?/tݖݎ񂪬t"+.p>v@ݚ %6Ӷ=ۈ@Ycy>z"3Bj7W׼|קmOPUeY.h9W+,9w3~yk7i/9T^A kI,Ok;EYqے@($B3J%0R^N$4͸hqbL"e)k> JZQkq7Te%mj6(ʲjoaBVJo$B"*ʺ.K2kqn 3>űOG[ݺ6dƯDLž(PVaaLRo]ml6c>qyqɦߎmE{2C DBt!a k*ssoolV~ǴzJ 7ɧYm;"ׯ"Gʎ8 X56lLJSAfu=&쳻Oi*&͂ C/Âjs(NCX~y`Gw.oŪn]yd⌮~םc1`]~ ;]%8fA:t)ID@.֡z[ފN)+P8??۶֔pg}Y\M>&bF넭1VI[\ƚo1HhQI3 6cİb63.QɐB>h ]O<&K/n.ui E0\ewp|ɫ/Y/OUsDR3\,9=_Z{C~t0:FQ,6[){G|h oǿO~ѻSϻΑn[8 :?Ϳo8$<>eis=m-CqpKSoj])dڬ( 77 gozO E,~SZT)Q90[)MO }W2u>|ئ@2n壸R[|̬ڞe)ͤѣhXԵ49w,f;VP#''_L,)GI*ႴXmQ&g1F\2:H ¯p'y}_/h USԥ.pg{*-֐LjF ZktIcQl۞0 =Je\-oh@sU-l kE)mA+)cEAi5UU20f{uN߷#: Ft6|rO/!wv 蒪6=eǣ{ϙOw9_^ncQ3t-'0,Mmm%rffZ\m)}[x>o^~uCh&JBSS776|Ϲ!J Vʓ~9xֽLKi9{>S~YOY|{sRtf$DN4ɩOe=g8Š1m#U=df9YTa6roLeA( eYL;X)+KL1J*g.RB(Bu[iK"{QF.ft!")p$tUy3 +y=s&(D0AL3ȣ@T5։%F)HuΖn9V#Lk# bQꄪ*hꊺewfrKV뎧O>Z7/TKè8JNx|Tt= Wn{v/iʒikv%em8.%=Cicq]֍c DVyJD 7) F) [DE4߂[pEc()T10dt*IPHo>۟Z) ։;1̈́,!$Ը.g -lͬ!b[+Bt\^_5儓kR0Ti*&Ke B(" XcuDeRSJ[`& U!5ՖjS.Q1psyC)tApuu֊ЗTci)^R5LJ]0ʆtݖ(+iL}WGeSy)cYl>aZt`Jn"?!I 5}Fvl)ٌGO?)7玿>ϟLnrޭw/w]e><ܧh.WK6|*@/_/88`lmf})bhn.Jsp_rquAXl)dFΑk@qzQq>yx&%e]QT%)|p#@{ )2t.H>{.̒ɔ5m߱=1z9\ҧ#-ku-Sl`ygGG'F dx,q1܉8[x֚F[KJl)@?ʲ81 O$Y+-H8 yE##| fE;>3٥( OO? NJ)ik..Ok4puM#1LJL&v1yK~?#zF'.6^Zۺ7a`9!n6c$.V%ױgr;g;e`>5_9д[G֫cɓ'u.ْ+Ox!'A;}?rW ֫ 43 !PEQ}FDC.PwO2])#La|/=$/,[OA9cYQ žZ3mWyHKYw眸 D:QڂPlcKbY87b$ޗU"9eՊ1J[1XUŀxZq9c CUV8\-1c:vX,9.PPz(IJ$H e )QJt]22UޮŶuhSb  :b..^rtOQhBtt]TTM)k LL%qPDj;GLzO>ɚu0圲g\Y^^kʪ!%q$$ Fσs 5S[wѪwF6BYSC` $M]4n;1*!g~!0CQwVl6-n][)Ma5''mB*&29l"eP-V34ls )Q]!if,y#>{<7۰ݬlVnx|ݷ[ș4mX|!Lo˺Bk-,'2JxRꆁ,B*Zb sC"2rJ2My@ BRxp}O n}mM}aK;6 [ ( 0`1;X2c dӘDa Fi S75O>,ݚw`9&щ,%C]Vg6@yz~q|>_rss #a=:a,DؔIdZct U'SS519bt4?-/,P8[J6u@q?x@3qcu@Lt@{SR >|f鹸ΣA7xB`3 s=1n1#0֊+1XPW3=g,oVtC0tBDkyrRRj0D>yʓ'Oݣ*JO/ٮV}>}z..֛FYdgwG<{鏾3_k^~5ЎFnn:./޼l6LJ|{"ѵ+r }O d]Os10l-??(Ik:-iP)?#COmmT0t?%fT»7l~TkL*|)\eU+HkiLTEE]/( l[oj^>)=yus M$2Cӭ[|7ǶѲzdpm/-EaQ6[Q@@T m4b Sd&3;c`fC7wޓxsxpÇpv}bеkl1-ȴn(%HSU㣂|J{f2މc&H6(5g2Eة*)^^`i* - ueǁqhĝ !-LiCa*%q = FhN\i=cQau99?_X{cYxikJ軖d1O{LSUk6 $vR5 v+1{)u]fmXb,o6 $C"I4 g`Aׯ9{+!^;) ̓ln1g/^B%ra|o&/^|7߼`+Ns/?7/_uvQ~[߉IkYC1Ƒ6O&߉9S-.Hg.QVgˣ$BFFgJc0&h?x"В)D:Kue~oHvmGO|{;3c |#Y 1/_}MLif:mD1k&Xߜe:=5q`.9%u=F$W|%1s8FFB߮;ROR`ʂ~XiWrMoY# .QMFf"clK T1;\/ohN@̑,Jc 1g͏W[99|˽|0lB3MEΙ+RnўҔƇ@hS1RW jSO7Kp~uƯJ{at¨8 *sg2t-}Pt;z1&OGM"oN@QT| }xn۱3ӳs7,W7}ω~a9,p||O88\0)W?端9Hw[\owt9{{\q]q>n=C[ȑOzf^-])ɋ8ӟ|_w<C83nOmc2(9-ifw`Y|(Di%SX-(l+J!$ UcĒ 9*b6dm 㟄ۛ($gDf<EQH|he1eAΩb2)hhk\!Qbã5Tus=H^W/~)+ff9::Ȅۨ>n6Fh)'m+pV<)AäET@xc9e$uFp~:\Dރ?~8?5Z/(kG[ĐQidA1bLCY`mMf VDe[5X?hI*RϰɈΘH PJuJN[D!1/ضuK_ΖKShfFn6"┕+4W+@)|pvzޟ1̸Bψhvw|W|dZ|O I XޭwBλn[]O{vWo*?/>_QPg0F,MW,{T儔͆TU [UR[ 11|y`hW ;S@Y] &Ϭ=:;G|{\^^uF) :S5,~ 7+ Jy"b?n˲C1.] wxQ&h.Cȉ;N&4R{H BS1 !FOS zB#tXĜ=eXǎUڎхȫ#$ur;4VW]rS*߽~ X+Nc dZAUU٬l66kVg^B$u@&PV$mcUA=98e>?r-8f"Ls?8BQh3Y-@Tu]X}k&!"JG-Z1rOIjY֢}aw''',W Sr%1cRWK|hݻ"8G򣨚oiGXNݻf-upvf:mã\/ *dy|.ۮ"uKHJzFn AS࣏o|k~.=m$*˯>gEI(s[?}f3fvv甕ŋb>!xq5Mz500 e~)0Co8?Ѣ1G;xr6̶M(%G|0lHɳZzVt%aUA- 춅ǚRCŷ;[UVnj"cL3=awao0Bk2f#CR7r=bʴ9n&9,+ 9;j9+ʲ& ^XLQFCPKҩITqb|h ДtaH1i^RJa(CcPU->:k-ݽ>OD4)%gFuX4ư]Č8P ^2d(8/1muMm>L?Mfu85ihNѵDDvNa­ QIe֖)Ƶ\)AxAY͆V#$!q1FN-xoUUKk6t˧y 6X0E "yZ%Ͼ23ZlJy6f:V[jNKn+~PҖAΆ@ɐ H%g߹E@:p|^1F ڶ=wg/A㝡2٬)mGW\Ȗߧkخ. M.eEMfzJYN͔Eǁʣ)s^ܻI%ZlY0 hV"ث V, bT&1 Dޱi[.650Hmk)if3dC ,>osS.;u[wzoY?_[wv'O~.rmٚ!'ʲ$.7X]v'̚ ^~sdRRk"kklj}frzͫW/Y$P ߎM"6wU3 n;Ͷ{F0edf-R Նⲙ66ގuFlXHv[a"yN)"^);Yhdc\jCSsx0%{ FZ7ki 1sb^:P&x~Yr I,1|9Qʑ:I$̚ n@I uVY4Hݱ:j56hbvo)q(u(G@t̀ɂKBeQLX-Y˛kv(2424sD b,0**S,\]|7?ާ+=Icy+\ VgRjQ3 b2Mr"s/]숃+vՖˎ݅aw{ﱷ77c#P Zb\&S EyEקc -?;;\__3 K9JGbmIeU3Mh kkqdK|{Cc>,7t q.+yzsx=Nߜ'+E3Ҕ =Vrqr7/^w=van$EaxC{|+..N9.mo588'O9}󒋋 |(:+V qZ{f|7`bo=Jw e1腖xTFQ5 ()GR,jo9b6R"`j2zꙡ*R2{,G:MOʙˆF”|Ϝ82`Ģbg&`K{羉*V9&i:K>u--0k!Έql#+5"QRJD9eUtthiʲ`{NNoLjIEf0 rUQ`kQhG"'EHRB )$"4  t.brQSwRI )0t}O:}=~z9Gj1!3uCf!yEEN^+MJtY2DA\2fzG MEJ[4 K3nq;faj仪[Bac[wsC^{ FxRf2V35rx2F! IDATO3k0ư|`t> Jqq,V6GIZ2$mXt'`&ao#Y>kY3,"fΑap#Ҷ-_|9};{L3q|J\_@ N> ?69?=\p|{f7" ۍdT1VS73ҔƲ^ed*9d`d} CI1!#Z01"$,deV1,plHnV5(&l5c(CI[޻Żz{ޭo'"j]R*By#g}[(Ե(Ha^.x>HnAd8PF$FL!ud3tv8+ur;hٔ$`<1jsM߯ h(E^FF$V}iX6g>a]l՚,؛ΘL#V%|T`T뢀$b ̝"kgIȌ䄻_9͆ F^sdslBUmX/W\/fDc rz^­[0˹{7zOGNU̶_z1@i$Zҗďط8;;s%^*Iٟs 9ZIzbyru54oA]ޘ;|dJە<}#MGtcY`),aTCVhNNL&\__qyyEL'RdU@l~dd7Ǔw7m8n+'H&cA|ߠ =):L@fzˀ@莑"2xӶNapH}GוpQ앃C¹lGٔ:eSW4#ɢHqUj@0T2 VMl [јX@zivizV45XSrB?W=R*猯f0!v Ƅ |-O3rt|UuHk-{{{cp":7%UU1f3Mk·2|fֽY7unֿq{ *?W_u+޽>][quu', BoQ|GUܾuOÏ~?Mγ{6=5Z*9U%#|Xѩ㽯 RhT˳gOb^rq M |UO F1e+b[Oэ!XMhh(adE,ɣ@"? [)fa85Tg#46]E ȸt燈Kh$w$RF~FXBlHr^6s>9=Y*W$$ф|jE:lg<(-RE]՜_,ڀw"TR۷oCfE'xEi u!Vk6RRJtbE9BTux!Uઌo[;|ج+<}||]e黊wvd&Oìg7Fz92Z}=!0ih,9<3ޓ$%I(F9K4$0`'ZE5<n }c pR aq#j뎐c~Yb`m}n71$1"@Uh?bӚ,MPC`-aREuZ$z`+EG*C@RU-y,KX\/Y6໖/VY^ d@( ^?1L ..;j>][ x;GF 'p66WTw.DRcYdbqFdtDx˲x_ Ed1^ڻڤQ {PacdعAceÄ޽Cqmbx?`Cb-J c B:Da3x/M:\ )S4F@Z&\dLZzW!EV2`4cyZxʝx)W1,#K8]O&ňWm͸sxx@113Ɠ-t|Og޹$e6hc0FҮ:H3HҀ=&.J b*,K;H%%V5wm͆8d:;F֞zlLg u|g/s~?z#ܬ!fݬ/vn"_U|LlFUAٓ$ZDpo=zM>\\\7i+?c=Cz41= ,%:yJJj $5:жMĒl0(׌9LJ)Ƿx_68?dYP$#ʾfnɲEt#M_ywy1jb!߫jdV&oÃ#NO=g2䐫kV eY'S&bё$PW0L c GGGl6.|4gkf Cji:zMG@tHvqXŚWY ~YbmexoJD8A&':Aik}KpiD/+XԞDlu]g26r29+v+:; Yr!:zZi*(R bLQd$YBYʒ޹ᔑ9&D}ۿ:uݒcZzO?c2A&I.V· tZSCYy1gkRNMK6(04uwcyO4nqttjb^ǘ m<(p)`{p. stYP 4Mh1)pYTewL'\.V }oدdycp>FJ{so7FyMYF_uDvdm]:/Y;ËdlRoy`/`L1Vd`2qLq1n-7H)q_ث"GJ^ ,g%X#ZIO="s1^;k%4J+ڶH1 G0 /..wa:#=>Ƈf:˧`Z_V+ܞq"в7mY֠m[A\Z$Ju4!?|Y@%IMJQr-6ԕuѴF Hݰwh)努lh:Ǘ5~4_~݈87źo=~Wqx!BŸ?'@)Ц'M5a61MR`ijbkbG&3dyέ[c尹ytּxAo[1ɘ,8Ņ%n4m&Ibt¨((?yu &$r$ڦCi9Tnc q =s\^l2IGHVۘh򔪍M Exm<hUttQHuPoRFϰA6!6F}I4B!cL:)!XTܽ}dB5hh53F1f܁1qD8XaM[t6`CA mh41g1 о6 J ,⼍Q"-bn_'n2<^t#E7m<Fdy|>Źjɜcں(\_O?a67f>QS?z珞1;d:ߣڔTM`O?m+*v]GT<}zk3MHmXVt6F2zg-uQ(RD MU6MGgt}EFwDqR98&W75Zk6~櫼]>(˒7Ȧy^n!0L8?s>~>"w\G#UOO8BB\C %#dgH) ʓ 琉 &38:1S6l̄8hAq]O߻ACLf#FItk k1F1Y_pDnv6V%XĘ$I=/Ur*t*{``oocdYS%>urx}Cey1+z <~uE2FH^;!pzO8\Gqpt] osqqA]1V)Ml)Dاt &]JӔt|3ږkb4%x`m6{ܽGԛղb\wCx9 zt9]GE6^O1RnEDvV`> )ṍIEA!j`j˿?sG`GIАgDg1G98\P(5Ej;c=ҴKz[Ҵk)*{MGK^ԁr]$0:ىS=wSDtD%h5 -^yI|6@K>cq;F,+xK|~f97fݬ;ӇF#)yasxxs5I%V%O14!3ƳR$?)łG7TUŋg(%sEč.)F.mmLt]ղ20a6@xBmMI,|&hϾZZ9Uƨh!O c3B0"%c:3 h|a\:iaB:}Q4nSuuUYYCCg y&8uhGTUytzM+;1P^(k*o"6"$uCŨkRU,V4U'°4(Rl9N[DK!pc\zɝ{{><{v Brs}RkOQν{xdqzi#h{8MFE ɜ#~Oy_{ Ow߮$Jf,6/>/.99 [dĹXTI@#6|,Gl,0YG^3FSh,1'D6r'O$.>wt}|3zb (صm6%)v x;{&60e+@ˆ6)#MA h[ uTUEӵ;Q`"s !OO.%Y)5pڔ1iEK׉,=ue6D%5@Kv;Ώ{k-A 4-_DZJ]4ܾ{z<ϙLBAԊ<] IDATTʲ8/Y x(Z)鄺if's3ެ!fݬ{w ]0.RL Sܽg_%"AoW`҄lO݌(I^Lc>m~Spt4!XK^b$!5ybq !Ф Fujmy )Р0IK7:ѓcLxʲxJQXk9??򒪪(s"{K'"V aiQRidBbK$8zhУ&Ip>֡{'jɺy|k-/N)BQ7٬Vж-42qM\Q:R7yҴ186=cR$+b9ڪux90)65h7mčuui̓1!ƣw5UOxpXlUe^1i?]FfJBL&FK=l1i%ink-eiQ }5n:fw^qz=Y#Ni9::bogxtc[jC˖z=Ny{ .Njg/Fh[TI@`#|?h)"vyw(Qp;:<ߥZ7X\,ūbݵDSWnzTw_hњd50uPA:RԒI&go5/Ӷ-8g2rX\nP $䭵H"^Zb!B}tgXKZsk+]DDj =MSc"[I%C Xtmta;LJ'Ħk٣ @kjx^{t"_aIv""ƒ> c-Xa:"&A}]X>rl"F]#8BI\ŁP|+<|G?4{۵l*\zϤ;,O6e TU}Ob @,uGt]CUU\/Vb1bhX=]?uzzmoXbD<}ۡd+*+%?e+zK*B0$NZSɔ1y %U:p|?؈&BZL ,V+M=6C*}j*%0J C#ck:$m9 5)&Qۣ&n7Mgc\+ƶXVJs%$Ix&C)W EEҮoc]FVtB{O8Ѩxu[Y7mM.\m 8/02#/hmXmE>F,cE0:zAiug.dX7urh%8W^ZK(vo̤ё8 ťbs.ց ] %B>r!>aG)TcG12At!3$Lrla$]\brOpA!A $ $el{Ϟ-xvz`{EUgE~ah[!-/mC}ṱ2Y'd̝;w88:!%?u=WWk4dIKuAI m #"ѣG<} C-eth!>֎O&#qEi+SSlV+FAbTFH7Ud4wk7*I"Z (Ob4wRk=JLc\RLY]%Yi"(ѥTUe9Z'Sd@7}ÇPT'ѹJ65Zk$!51zaC8}?4)Dѽd ]!SԪįDi2\+aȧk#ߠuͰ#ЖgO>#Պr!Hd~-[J?IxՊbAVj>]ˊs!:aHTjںh;'y98*颵\+&:;-˒r,K,̜!eM|Vkf_{5ΞGEp;k8jhEAit]s0O9<wy4|ruUq5l z O!$rzU;jèh{3)LS~گ}EHCw/{E՜<{zJS(21SϮjO][$:Eb:aC+mDF&}5z)HH#0'`:WLD! 3ѭ)bS^l΅,2j:;n$JH`>qqyFg[ XHA^ 퀮0xQahH Q4 [I')#R)zgigLڶ&bDTDmڤ!"BR&ƭYI`:Fp)^t)jPTQQ K[ һ(X.₧nzX5}N ;N[hJKAxT5M |w3*}IG+9x'HӜ+ASwD"PvMx]xvQ/(/[1c+"Z" 'jc[Ci!GLhSb:WfT[-f)ј,sfSa=B &Ӷ=s\/+<5 Oh@S3%;@ķ{W*Խ+mto_Ocs&j.) ;Wt6&M 8@P\^^<[/}fnY7FȹY7zT5"ĊMh\ ڟ}po~ $(t}s}}ccT8":8CMٺ|J8rݻdzgng W33`:QKd$ ȐxY#tտB?ohCR$"r8l^l>A'XW Q@!p8yt(-yT GXXqH)%(ꂪ.&EƦZc]{ؓ34jTJFqn{O4hk@,!h}wunxuSAH%xE]Yʲ4ItI3o~ɭY^Z_3D v 'l i/XcfUn(1JE1$̀uXm!/cf8F9u]s}}PT r i[Kf2iIa2'ѽSVM%c}/!{a:y|G(%h$IhcTxד(zIw)E6x?8G3ʪ(hs}^^ ޹kX^_NO1}ND9}q^w\S5Uٰ.Y)ņ tc<1Lg#FYq=>kkmIͺa>2&m[c...b; /N/y,MJ@4qV{?Q!6lFp+l+J(B ,BT X2˙4D1KڢMh{#T@[@-0 Mձ>I$%5yX\!GwZ/BgQ2YD@iv‹AV!YR}-h94E8gSF= crU B($a'<ġWU|Ō'3s/S εXZDcm2']߿o9/?wMYGSдMtS-uB߄{IFN@5]LU!#$9KBPel.r4kKE=$;s' 9=1D#|}`'"vC*CHP>+c1xߣ3$&d, (eɍ"ђE>¹@Yu4M0ƺu2f;K~`&w}yZ7\-:W-UhAQWQs> 1c1'vEvv_!o\F(϶ΑVpCk;yy::zP<'MMMĘ6%}@ .>WC+(F|?1ZLꞦnurIDGu.Ms>x}3ެu#ܬ{,A\e+%³7?ɟ'_'_#ڀ =8G98>lJruur:$֖YJKĨ4MҗpzM]\^mM<)˒l>瑯bUUj4% q N5ڻMF)B q.Bi;dEB۵}~*z 4 dEf @ L&}umC?n%w'X Jhһ0޻X]us[/UUuaA`lQ3٘A0l!@ < $@ 4fc <` 3]]Uq9qn<}NDdeXR*O8q;[8uD;F1g/#M{(*0X-r]G1kb7x Jxɛ4M ղP }{Tzd4`ZrQQU`wnOcruYue$2a:sp8a4i5Y_s!OS:kx1}ϟ?wx!UQ5qQVH7y_Q249=]CjE۶.mBu>7%I'ǧ<ړdCVz.`0`0ٳg,KriI"elP*(Jz5.r$-ݓdFv߯ YY2:dbP ǂ!{IxA[C(Yo.¶"X Tۚ(+ͨcU`zڶ  \tH[~IąށfZPeƻ@f,wbrQFӿFq<u*H.JWwr={ lW\Bo%vqRk Z'hPU cv!orβZmbSHΒ9y6tlNڀ@46Fec tơeB1ҵ.@]uqVDV/^T-R&UK۶dYCmݺ~ks/Zw kPdlswo]ߠ&}#RP vU2$cQ"$G+Ϡ eC8KIClZ7K#ky۶t]5]c >Aɔ`ueim UY,;6y:cgw%^ȹl񳢀섴g[If"[Yk{^;G{.)vY,FSM]#VN~ZE,P^@Q X\)<(SG:[Y(4of՛unun//ƿ|۬x;d^,=VENM(m)y3O>kcKϠp0um()?}yj S9>>,H)za9O>G̗0^;M@DJFs :QQɊ(Ќ'CFo<,~<6xqlNzjǣZ!E>njEU~ǸYX7"Sٮ{³ZmX0VOe*kUZVx>7pvzDATa1Yr쌦%P*$CuTBC?䁓W}t$IF]di IDAT7 3\Oi:w-!\8"cuUεNd\i’.<*ct. MKR1 )J{]oIt2`d4HDyKIN | ^EX7Xa}twml So>$x8SׁMGׂ ΆK}lϽ2ֻKrq.n=sC,r'n[6cE|_zA%}ϩtj"/R-_ɤt(] q !B!"mڙg A; Ik D To𵏾q3ެu#ܬuA;?%gԿ_ ?/ r9gQ&/b?7ɄۛpttDgggNa:*)˒D'0%K5ֵimqM]GCq EKe蘝9jTuoUqZl M3Zks$ƿV%GLthGMt4,5uoc0&2bL*!I*ob_'Y_ORV o<|Gx~cb!Dv{0]ER7lw4i ȳr x'|z+ i ΂t.VKz,ͦ!Mr !6 (M{)%mgzot#EWEhʲ$8"$o[8lZx'д[.0{{)44uj,YAPϿ:tDY> -Ddxw|ڦùi-IT뚳{3w<~>JIs%ETܭ4 hNO<~wp?k}iDl`ՆMײjw3JbUmw]'+C8/C,=`я>d4~:a9BjC$>:BkVlZKdm0~;Hzpv`ל ,V Wo3YA0&T!j1E·X~u<ā4xvMJ[w66$tug]-_$ƒt&26 .5éf %/RL!;KƓfض\ bk@sk!rj!յQ`!~k-1>2QO)/60ơtPQi8#h.Drfb v0(P;>06DIb֚Y@0LP8/ @dlht<&Q]BͦZ`i8̒c.c){7˝@Y7Xt`mt8+.@r ڡeJdE)URW Ƹv6A`LG:Sx a+8<~#$.ʳ=>B/bkN 2>E/_腻S3 9YQY`0LrI AxRrh4UX\>e)I EKIOF4c.ƵB"XvbXh9u5KWX#i 4U`2TM:C\mĩgY^_Kok+mxx jRFsH\G+#C)BpDfU]qZ좀s?SoͺrnͺYo?XXۡ'op>{ŦpzzJݵlEҙ SɤE5l H&Iƹ~NX-Dd&s OWޫew0( ThۚfC[f-Z(B9N"KŚQ97ߚ0&/ofry߷bXm,kPjpoȗ'w޹bu|~FUx|tHpqDF, 6k fg4MϾDJzCpжIsW5V dYw&[|;?cNN8~Q3$2lI>]&!/OzmbLök8JqR 3bkv|1 s}nw PmI3JP1H& e +4QtfC-bqfS!D:o0K[D/gI.Vzwyr y?jPJtdeh4B(I5޵xיށXBȳ$^<@i.^:CSVj`0R$NNN9?S#?]_XxgXl:K)qxxX][?3M!Kt n M{YNLSf몡iM699vh;6xx^f==eu8bojl1ѝHHT҃PwXkB4R(X,83q#\B^IΝ;c<{igs|$K"#p!˲4-I0(]:k--y"AxtZ5 ױ~t8Þ7"mG(ddYutE8UJ̓ߎM1圪jX 4Q)D^ƍ6 USZwFtG{,S\Ohlm<]C3}%$byǏyc_,/h놪ޠ8:2`6F6!ᣯ;Ob"vwZ\;vQKC2$L2J(g4x%YHRNbؖMڨcO ¡glDJEe2*1"荏@q]<<_ 4 FQt$:B_uu9[(1ʺaB\iRдuv[ xs'ﱷn4ԛ"Op&2]so.#&ٞ5 4k]q=*OweױZn8==#WG6D ^yeH.CCvuZ{q)քkٲm1闬xcα&8F',I*:8!/$ihE)ʄɨ$$8n(`0`2dgHcJh6Œbj5AxnKo"Ztuc[$61xN4 4mkMcۡBL'!:Wʹü[*mMuN윴W]6]S_jcH}Bnk]luddžp hOTlK%_fݬ!fݬ~NN^pqqm&pրɋbP4Ք{zm<%gSڶF`۟24MܑR1s&-rT!J%٧Os<#ӊbլ1>d]P tϊ%SH<!{.@@r JC 8C%k2-7B2lqP\@ȴي'zb@v޽)JBQH98ΡخÅ@X^zESˆxAAkdIw(uPT`S)R UGEA$ܽ^XjxfXSFA݉J$;7@g-AZzGk{woфr'~}]4IZ?7?O~>~Ƴ^ŧ/hwD;3 {n󖥴c+OdnY[!gbC+EAл&IG5I }~B>2 ǂP Cc@%U{ghܯF:p;BPc`3A!* /!~ rnmGJ{&IR$FO$ eejj @Ȭ 6rrJ|HRi]_WG]<^ cuC:puΑs}X#.& ב6;ooa2n)YORR$&@eeyD tJ qxh:˲Z kYmΨmSVg2b } YIB)MeHMUe[Oy]1N/\ o 8WlwW8`\ګU[Fku_ W-0{db: d  #IG{El~r`S03fƃx3e,âDKEg궦fexT"^ ߹w<%I:qc/;H)GAL Y Y.[I0ANhڮfZ0[T"#,\DN䝄}YPWEQQ*k-o˷pbEMc{f8*ֶًxSmt!{'F #욿txNP2%)tQF3$ {h0 caRmg2zcۖYCnw4MD)lgجk)"Fl.`4R"&rJE!Ze|qI$Bc}F*{ND / ׆qW\ 9r Dq0.lnE-5ofʣ]@i$ 90C$ A$&6FA(p-+$Z8.I P5l[:[,c0ut4EQ϶ך󬫖 &:#:׋8k|N`~y013v~뮛cnkrU? t"~+^)'ֻk7n qYe~fY7FȹY7f_wB5`:bmTLƣY"^Yq2ip6j&{tBl645HeрDKD *U6LcS<pwD |w0؊άS>Xi#hSV gg|ŢAJv4^ G.(AW}l@P^[o۲\^Z/mRk6riMf=s.IG0Mp.Ny,'B~.ixR;ѩb\^ޜ!w`CLa;q*r`vȑڲ0T^:f8P$7}wa@)ER$Uٌ<簮Kfg,s!xÜc+V 2ZӭC/ Da IDATECI|1.͜r8lR Rxł,X.6MqSCڶfT `YD7ѥ$kbkS#Msx{t]OOɊ!&^$OJL9?9O&]w~ĘUBJh.G-֜ϡwчrv~Ōr⌮UAcMm#5h4!ӽ!!|?fnh*u/u_ 9|wijϰLN뚯}'o,{ǯ ,&n!ôߊ1]ð#GFUkEkB)|JDqSk7˄@ $9?[qrd["M'nЛunun/okCkpXFhmpytpX)y1_^0zc3,˞w#t0p`? 9<)'fxq }"/,O"u9фGqttDӬ)=jɦZ\Tkmm4Irv:g63tcAI3}(N0 qG| >綁e0sxxHzIH6Ut>H$JXgD\y$Ա2( ѶBp]"P^Too9>>jݰ%i %|?ӶcqvzN$  D 46Eƀo[& wwi/!v8+I{˛*Ҍ4h)#ݰ\awC$GGܺuwyc֫Ϟ}|1=]穪3|[oX,gԕG M̗>ZۿbͫWkxDY FF!u A `:26E^寸i#mSAtrI.O>MB)pP0N؟QJ\cŜ,AI=ȜjߵE0mwYm|<Y#,r ላ ^|Ip`Lm mk F"Hz64ϣٶ[$!x`1M-EWhk *9^\k2V WN@Ei֠Ň@QFk`2]^y3?D ARSsF$y.FL^diAn{JUR#c֋2Ś4MTy:U!+RV#`r/_PG -xc9:N'ܹswZ_訛5B:*t]8ANRWWgTP,K<Tl GAJP%yI2;:6eI]Xup1$9F5;U RJ{h Z^`vqR*%|Ӣ֑c J&| "r## H45EQݿ矱^0uEh)MӐ Ղ%"ci:.6W~Cq`\$.a[HTs5WB•*$ mʢSNBz@^L4&X,'ۡX$ՋGQ<6udxt"}J!b#N6֏ezݡ<|xᔪ^kdu[N`:$+uy#BA, 1^c=:6eb<Vu!{N ߺb̠e0B(Ogt]jbX\7e23 xwX,|3nb\GY.֪-u<miO FcJzFMs֫ !uB`/c5Zk IQ"˱\xA:.]k*q-R7+>o]gcK7 mY-"F08Ӝ$e0L)AG6xJ^@pa\j}N^Fn]lj"^%h! ($EOkb vo/).H&.Z  4UGSq&`ƶqxTAB1ᚐ#cv_ (E}nڬ•㏝t{F:.ֹ,Ȳ~x~3wެu#ܬuQ[r?-y"i6eh4b`ʯEZ2mwB 2of~02iּ<~J$˲Q2h;2 u ,>HTl}cic01p.rWꪡ"fi(X0^m;wG !Y1LH2_|lAy>xEG %UDQ c+TBZCQUKU5d(ry>j`\"UUk&X.tvp:#\Yk1B ȔiiL|ۇTU#BIIhX;]@Υxl n8w6X4 ܻE+?~CXѼTM UpʫsN_Z4kJւ;wn18pb]kX.#+ĭu(4z m##$]/kGpg]:mChALL Xf'Mg H9GH,-o}GSx jŊ2+1 )|qzrL!|fE'1w{h9;;nYjpul,K}]>|֚ } >f+*b6ISa1_&A2RGCI9}jZw,i&IN i T29H2CVx#t?#/B:H%ֻfƆlEz]\>L1BL!@ ,vmw[\\| 2DV+=_u(T+& 9R'M2: A{mAZ{/ȲkGl ֒肮 x}$IFedYTw]FY-wFu&"q~uX@]a6X! UTtmT9isrrJ6ta*ʖͺ(*q!̎&uлG  Kן$<7qRAH߾wB͵}\wA~ÝyM k7{l/hٮL(R :jrQt0f!1z!0)i:bǟ|FQ-@YlT]l5ޅx7mHQa ! :$Qu!:epdy\~$!E7iY͖JU^,:GS[+JpVYW%u g8{* UXB3{+Nڵ{a%~sbM6H~["pp]9vNGց||{NSJ!T>^ ~w@wn 9wnݭw>6XCIFZסvU>y4mAYeTQj8 m|_b:9Hǒ$W\^ٮg`4ɲiHxF]&pB $uruuPprol>hp'8iږαٮ[CًKͪ2ғ^ȓ|}xDnX'ٝB`Cװs xL_yNخ/Y,oTQƄxHS{dci`,Vmבe!vԵ9ÉnI4<_NEX(Lfmv[&8[c[(^D9^y-?bsgbmM6UeMQ b&Zk>s5:ZABSk Qb&³o& ̡Kv ps%ڄ>fswxTx>:GqSM~[ȹ}~߳n -! o\Hpc(v rd3 #pb0I!${'ȳ)/.x9׫k ֛+-.lՊ"SAbz 3t."p]Uh j}sd  ]mhjKFQV-Ŷkmem) 嚦ԭmlKy[Xމ:oܯyHo7M )\}ە1sb!>v΃cn޼[wNȹ[wne={)$>GO6?D)7{o?_!5Q ZAm1re84/7 0',l6+`z${ϟ٧DF#RgܘZpNM 4O'3Et:cBHZP7[ڶfd q8|t6c2\\,iۖs9J&H!`ƚ,݆?CFc:@F#IrR5R(>A{޽{ȇ#5_`-^P mEU6EA%iq~q>c><}9uհZXMHurxx:|}S./uC c[SEQQUAYٜ xo ߨtbUAއv1;fwMАd[ Ce'}s-=S{9w>$MW"5hIsd41qL  #Cbcʺ9۲FPkkz˙eZ)GGG(%Ɣ՚\cmT7r\`= uu)M88'1kRdVLj;ASNYgMC[ P!ýkd7Prnn(%tڷAԱtp-%{J I8 ??r7kޭu'ܭu$IDz@xVRmcPlTe6$8Kw)00D`n\/.N/yc,k B@p=8,[aCB{?d~4d/^=7=CFJC^>Gmh;bhβݖbma<szã~<ӟ_ǚٟiLՔMऱtBmB# #}]:r~vEU8oC/I躎43!"#MU /ˇ~v&\^]駤yFEtV0Y-K1F|g{ IDAT(8>:9W/)%[ʦYur,M6mkISx:_2EQ?f+( {#aF&|xA.74MVuCgV%Y1%yO?(^ MAQ!:8G|H)bڶYى^ `(}Ž+-!'8 nۃGʾ؇piP=8&iA{c$TdmW#5J$t6 gANg4Ms3ةa-NGA3+lY^` qx4; lwIGնNbd[ZKۂ{G;t6l֖,\5Q1[Ek4xF0H1/_S>#-0,? ͈2 #ls(ubj"5BRF`pxW!gl6ۊ%JfxM婪6ԊEvѾ$aU7]Tq/AUn/vVT{zo97љ!|K7Ӂ^ZEc<Ş4, F8vD%I%ajǚ, HA_ooeYQՆ(ILfAM1Q/8h$3TerNkkWnpR,Қ,J㑻oJ:gDIhZ,Pt b:86DP$Ŷm~DMLhׯ^pv9Bv GNt,7KQ7%MIQEy> $舳KV5EIU.7[bʲeE!JA*899Oˎf%8 V#xΘ4gz0⣏>?1eYRT%iUċu*YMb&)B mWr~* 64'EGkEu1s~ӏqWf~03KFıo = =F)B0!&vz'p.mc:[:0n-@YF4tqߊ71){O'V ?}"R {+??~x#2ވަw* y~~z7_ޭu'ܭum?oSUmmk:$4 ,y k=mAh<_QNْ$sbh<5ie#VMU#D8]+azS .onil6Ťi-8 IDD$qQ~SHpőbKk"a'&)Nא,q(Wx ]4jK$TUfz~5DZPA.B耪4z5 'Ӟ'dzԂvϟta캎ɒHӶhq !5JE)4Zy ONǀK4mi@Eys3vaSZTuQ eie~YT5)ggK..I)S9s]15MN6<~r޿O>Pl%Ea^)PaO]TmC(bl7$O],t!Mc^"`,QHÇ8_)+>}`Br}B0Vaێ?8g ,4gS4\_m_6Ee<{?߾5[ywCubMS^\"D ]_eWwqwf԰wS8j-OolDvX%Hݡ#N}<$'zJ ;_!E^"w(\i;.ptB,3bry蛠%=+D9ˮ)P ][(@TsHжD5"NDDDNMIul6զ켇:JIq4DA>|HUUTUAg,xWŧ,TyMbNP2!G0 ?=-MciĵimxmC[R$XhuꝖ;mv^`;ƌyv7,j/}9rnwI*oGpLwBqkR"JdCtj$BXr(MfcPŞ(v"dhrqe BlKXkɇ5yw.pt${CtX:JAjtΒ rbC6TՖP9zҌpD~7@jpxz'``ڶ8*:68 :tݶl vF⬢Z\rϳc7oט{wߏ7%}\ƒ(k/_ƿy7Wޭu'ܭuU2,Oa<2iڂ8HB9<Q*8xuK0EY2ـlNyu;89%WLsݻD\޿JEx'"!Ih&c9Wg,Wm˶Xg2[jPi)rÀ4E1‰^|x5mx{w<lU٢TDט[UnM[Baov睰-.{#\g}a$q#g/QB)$z&8n N&$i!ʼnx,5d)x2 $2 }dhDkzJzH$5{+hE' Jj14u׷'Y,zEg.iMQ mcEMGbtp(DlۅϬ6] ixR`[hi{t]^_ǝx{,p>ʷv:֩3 rzaX eY&ߋ{ޡbGwuuڭ?ڿ{G<#I"4t ѐ84mC=g:c\?bsLC,CjA=o8'Xp^WB:zP.HXCX87 NO# UW< kX8QZc|8,&Qu6@mk6p:ֱ`6XZbKKu 1-Rh0%: 0ξvmS$yZAk 2f 0Pp]_\\g)cɐ(C !J) m]a]l2EB$)YF)eQӶcbq]`f/Hc#Ri*j7"S]j8+7u[| [햩mh}[ŽvQ*Th3=s[ `+-"@T8U䃈4 ,LCXl-vTMŶXSUU㠏5Iı&B$,Mo46/q/P$I6| Xh"ҡKE\.CT*M8>:(8B8N"i:6 uzN!Ѷ5mSaLCgኵB$Y8"H.3>q>0V`:OS;RU)#DvBsۅyۻvPggހxݎ'w!#K)/"[{7Cޭu'ܭu?7ߡn7y B@J|b=xXG V QsWk!89::q(OIŚvEuɿzGSw )%cLx ?ŋW,K^]Q z"4lV b[tLg%ܻwDS6xF h YpDx9nց!Yp%I#(P!z^Ue8U==> Zg0Avd PQP1x496#Qp)?..l7W/dR^F)FEXGLt,Fc?Ǘ$; Y# 2tP*NȲ50ȓ%%]`LGijfsHXtSn-|''XMQPUUGk R U0NX\^qqFaX*@{f3I`ғwh"e/^K '8d2ɻc^>瓟~rbI[Y!_+}w˺bž(5#`‘{J7[@k+I/{@R J{,BGGM>[NSZ_&qEj(ىmkz7Zx{8޽{D"t9m(m[v{tφ Y;yOc `0 MrJ𾯄t+ϾG6(˒YYڮ:l@cI)S{,ʢ zO]u4kcmpu k),EFoݺ7gB{ǟ. @8vqp)H zç?^͏wn 9wnݭ^;5ִjJ\/I?[J=0 8<qY,LGcӁƃISU2unkZKW[&JCwDX=C!bprrXiDqJqt2puk#,YE}]UX6ѻSyl69u]!īͦ:fk~W~4($?ǜ_2 idjzrjQἠ +(ʗy)?)MJE۪o|?>Mm SLwsrn8~;2T 57BoV~P7 uMaH}*D+a4CAZSP%id{g#VjIkmMh!XG{,N&cã58,;x!p!=(o{ .up'x$M4٘6ے(ٖ2| (e<"fqՋEC)Ș{̏i/XiryE^ܽ ʲ%$qp ! |4C[ˈvڬրs\G IDATn:9Ǵmv_^5mc0;nF q=^kwBnߎ3774_`c{o߸z܍k6H[x{9Rͽu'(?;1&s7;ޭu'ܭuKqq_`L]bYqLݒ)Al>&֒)_t T8ۡtFGl+^Z-!(޽cд&9Vk-I><rH18>>d6S m vTk=σGl[S1U= IQ,W뒋-I"0mEQ`-iB$* Zb]QVprfG!r~qAY֜_ 2<'g_,62V՜O t@jί^q)*Fb8&G38'1R|3/y;!EjfўTk...[V1ib 8h*f;AQ!wzsׅߥ,Ckh:Na<ѵ-q,yt{_z`'Qm M5KY8 M2֫?zh4wd>ڎ LkW+^:ag-0MjUrx2Czj BÎ@y𜟟\$0x8B|kW]ݑe~`JBeZ !-xye7!$rs nGSlhQ!AjX3+NYsp4CdY^,iLG:DOr>?!gx LcV`JbZ8 U;a*DbscUubB#, Ef]}UqJ%Ȳ /_r599z|vׯvy&8?\3z18˥cήhj1eQsu}fBE (!D2tHeig=ӧj*Cw;|I[R<. 9zB/TB kkz["/8s?NcN{; x~'#E: ,JyFCMx3Ed@iKIt5ı$I5R9\t"%p>\th2c8ϭ*U5 oT WwZu-žMk.,hE !|}KAg]K4Qq$2tQ=/_qyrIYTtӶ6LZk){q=E?ع]xvsHP7pO qN%B*CKˣG?ݺrݺ[w?og=X1^SISU%'s$'O?%2ڢvIcL *t2jtΫsVٮV U 9w`ә&g0̱úaљ,cNNN0j;ڮ@ꜳW}&wC)C$Ieٰݔ[k:B!01K 4Dºpr_Vj I2(XwcY; VpuT~]2̰2aΣGc5uw)"$qΰZU|/c|mvSSԞ ba`H]$Z*(EJ4 uѷ; s( SB'QXΊ֠.x5}E>cB#6 AGz'n=L=j,(#%PdD1;I2GXG$0?s5R `C (;ވxQ&sXS%uՆ5}l0:2_imi~{5=Uuުܞ'D%(HHX A@;1+ GNIpb%N۸ۍnw׭{g>{\;]{}D[s>{?y~O`"C30YxR{og4M "8ƚVj놶m{!w{xA'pu}BoVX[]$ <NO.:%2޲^<{jAXtM57LGD:Gg\aG1Ǽxk?0,&Ēq/*[VYo'DmF)^r|m-m]M޷ۤ~'!g7Q#mUrSSvꡃZq7uB6Bcn!KvF㔃IF;tlrIH<" N$i@7HcBR!Z3NOFX۳X,ӷ%W,V+T: n4H$UIVcG,jgB6qQClXYkEۈFR˥I/NX+q U;WAUIbMm!K)as. "{7|wMk-!GJ}mP9t((xz?x+g?{!g?~ߛ'ߥ >|>GSRA0(Y,<{_ҷFoCӃa6E^j]ޡOM{}vuɌCyқiL&,34FGgIk7pp0a<.^{%׳ 1G i ,0QRqtz,kڪÇN7~˳K$źwy|]<-Sfg]mz.(IB| FŘ$K^p||,-}hz)F#^cƓ_>e6b6[˞"ٺMOj̦eEQf!`a{єtzUv5JijH9nL*vIӘrR7yV+VjXҨDɄժ',g G%/}Rpzƶ2TϮ[7Kxt9ZwmTEC]p_{C /\U%vX8dYs&M`3@s "+YZ`63ܡ8ցCb\w C7{pF͞+> +voL[P5w8(E !#itb,:#kV$4}Cݬb#ΆRy?9"OHPgE6"MS u]ZuCg2B rث$Iv0Dƙ"Ó'bz|mv3xu"GLJ!( ]gsxx'?ir<@\<)zƺatY=UUuyx?Qj:^߿'/O( ԧ眼7N'竡QkE]srUmozdL[*7F N 孈v-Ho/eوm#w[N܎bpP6IIuVSJ(c9cDq;#MG;,"N$4mhXˋ ΔriO !I4E yO'(^88MEA%dyqt:E)E6|5$u{-i"}u8y#_/')l ^=(7Xtv8>R[wHtAb>_^5#W,5}_{w` <}}5$nbKoǎ砯UJԟ~rog{yc4,q yr||DYd*mUUYYfY`T͚[ݻSt#`BEL&lӵ&Zr5ŋq8ǘX rYºkQ xh4]mֻ-[p: /sӜIo]Me?mƺ .(2]l@B } RD)xڮ)1ق5$A }%Pӽۺiڶc~m(PJq} , Prx\p"i *hAJOc./[sP-BF?7B ]XŁxN"8Ddg={~rW؏u]??Rס0c2*EaIm'y#g|KRWDe,]`CcOOڞ?|5yźkQֆֱqhoq"}!J)\]ۺog[76(Gd*Ydv q)7Ρ&_xQځ@\RIst'gTJRg4)bͲZ⽥3"Y nMw7B"U$L:K8Ȳ8qBm8I4-FeI찴 7La %on,!v 1~w[pqlJ l7T7 9Ѭzs!GV-yɊpL')&1wܻ{dS YD(V4՚rg'\ft]ЌD"E8Q`Ls$/$@`AK R6N:I$QJLLGK/J$r5 `q~m9#nN$Y8OL@wx(醟'$Y-RE8!H%RJD20 l&*hwzHgؿ#@JZn]!%-0nKcM:cPw~[]p? 9~|O_K_{?Lc,~tZo:XS7ш( c!c-z`Xs}VWpwwa]f3./_*fUuD2:2}Ӕe۟xz+_ +dII]u  w=ZH˔@tmx8$8zth͎ a|l9!.!n]`"YkEr2&8t,6[3 jc< ,3'%iT YMfWy R4 Cq.T''RKqp\XʝX'qZTD54]ؤD)YVdho -sY ,Ŝ+gnYC֐erR9IT-C1r`g4^(A$K"9"#e 2/XίIRM-η!ڧmSaBaX,$qLAyH%,)oj;^) 4 .H]5l@]kȔ c#AJ ?}/4 ":/ٸ7pn?V*Ŀ6%_AE-h<rg?{!g?~ѝgϟ?_9..ζA b-i_mx\'4)Q㽥לb^Ś$8*$FLQ*,4nv(r,%Vw#6S#e[Պ 4-GeΫS 5 ch=+Kzh{dݙrp8p:!RՒlF]4*<.$]}P2REeV\.j&fMӰXlZ :Q ά7= _<^wjVˊ3\__֙tttDXگs-# |qN?!.U mw"LEwI6υ֨|gQU7 n+/w?omxh#X3ġ*pNSEI" ?nnqh8!y,!I%Zd9 +`:I)F4 Hq=ieaĥ)Qbgj^0-Y5EQ0p.Tu[b I7Yz$2g1֣1H[T%,]G42O8?9Q,0}'IV>4Mf.Hb9bƣ)Yua rt5:!̮k9<9CG 1Npikq )֫8HC |1?OŘN}2:ȬaΞ,D1ݶ\s¡#4FHϏ7Pg?{!g?~q//cLϗ7OsttQp(Apdć/SUk phml7,)!-Qj۶% В1N1stt$ z]6Vws &Gj MSQ-m#z۶5eqij(evyEDiHaA%&xxJE<~:G fI`hnvNnlnP%|Srn 2N!o:nXݎ`Yw[h $% dB#4)‘|,OS4euBXp*Y뉣p o— C"s6Q(yQ%BFHcFA{74u '@1Oַ]YҒ!<}[# \-%SLq>n,miquGP뚺2\]uܿ_$b<su5c qj 9d-f#(nڥvEx6#󍐳+ A WJ!gwK#7ѭ0J J)BNoZ8Fk&fpxm2I>4U)h4&!2&,@ oŷ]gc$' F!RŬVW<}Yzu8<=v۠hzlSYQR-Y6BG vHDʔ{_G˫3._^ΈbG _e8:&)iZc8{֋%mS4=PjEg պng4ʙN־} YKrxwN}O_[orqqȝ qRZ4EmÖ9&%(>~Exg?{!g?~gnQTTɧ[,GK!bY "^[G 6,XZ%wzg>i~~^`lrqlvA^u-5A4ZHɲqT%e9"/Ǽxjyu"=}ߓ$ Ʉj  ̧ȋdz_mk640EQjx 'sQ1hLYCӧ[EJ9ʨ|`Xpӷ&-m.|{bт8[UOCýO|ĝ{w蚎_bv5l*{s~~͋3 ⚗pto~\¡dNS bˎs*ܹ kα~n܉]!JsQ{2o'rqAh qPD { k\Wv=z#UQFKFeZ27Caa'2$Cɔ(*áp,W52!6%=Cđk޴QqᆀiVbzv@ O(2ǺIJFG#k._;Q$"Dû;$5=b1i8kX]Xm^:U75}Rk KJҶu"D=mi{6?—ww =>拿 O ..CtlX ;D60ǗsR!n(gx{<+O;~rg?O?>?XUk4!,k,b7s9q=qHS 4cUH҈ŢGpzi؎XVH 7E/cuZ)(&Kmz,s&16>S>}"dZҶNrXcpCp|<  I`lXe 3g (ڂ!8]8 V YDऑQ̽{∇x `/NNˊ"f>[ ^zmN/NyjbUͨQAWwh2GGG|'O NOOXל_X#:Kl+!X 8CK-FnZmKfݺhPfƜmXnV"\v*-FNr zpr;P}}%$݂R '!Y <B#u;)Q2BHt1lr}\NG87$q>~٭m[usJ9??EJI V%}WE9&`;Cy1V|D$S\ϖLw'poqu~b =]hhBiX,V(s2<ꜹq^nX :|Ƭp]<~1Vumq; 9Z[U~'"X)yjTH#QL&/G1IA6EhM &\\quuAQHOA Dhq}K%R _DRsI)Jtk-:Ή㇜]\vʸczX|H-gisjۖI9ĝZ'h#3/_r~~A "[[uH靧;"C`j&FGԍ?Ox)<ט^`{N*7 4z=&zuo3m1q|6:mA)Coht꣏mq}6}Ʃk{9e ,B zqh#AƞÜAN=q'|r|V1фqrr|~2*ct c *P*B JfѺjX.j$/IAo"* ]uKtMEUϹ:`44YrP% xX"PQBh]bF4ɇMÛ *+n=vHa3"BJmx^~e6A%4#Mkl6c6qv92s爺nw e!fomkiG8?G_y./ ]#d8ۡZ}#+-p;"T8k?oƿg?:~o{ڦ/|bAQh\7|(ViD,LKfE9pΐ 1=[zիpdzjfႰcV!%CKSuΑDPay<qx8E*фqvW^2͹8TV[;T\c/ nMۢc11ӣ);\^_#~Ut,EѶ]T:9WPUi g]q~`6 Eh "u9I}{3S~󷾃m%npA`؁ 9Ι3IHϗ*T]6z۰zeH70"^RJ1bJۺa;ݺa6*xٞޚmNۜ grn[~ wCL6O}ׄ$ ʑ (a|1:O5ίIbGQ()@ B\R(AG!ԶN ˔5BⱦqA IB@QL{!Y^0Ϲ>g8CJGUUD*-w8;A|XczlKh%XX#\b b! ^ bbD߅ \bIי jE ֶV4a*"u4͇LPh&ݘ&x#9Z:ov<Q6GR *gx K떐=t&*R(ё'V41B"37)eͤUJ#պa6[K_ѵ9RY\ls=jxgx:8s,'Lں.g4tG[H^5pqyJ߮yS9"MD9,MZ<=Z)S<nGZ*h))DهzM$O$EgIx< r ո%*\85MԚdn(9]1/AI"JFC2UU= L}kZ荧!Ϗ1=\]^wKQt&82 w,KRᆛ-?#n? 9~?:ʯ2ZXѴkkH7XE^ y$IH|7{zr84B+Jjޔ5|Sx½{w(62eAu[ VW5eÈToj|ւHCeEZQ]ppp@^tjf9qW&hquk-y^r] k-gg8 i}u&ӒjW\].8yf yXj5O;KFg/y gCWnTA(X.CWAl.8"V]^o#SM6PobJ,˶nmY7&+gMھ|?xQ%R8>@Y2JFPYв1Nʜ^K_:߃5x鐢߾Rre-Sj<))X1w4>D79UǺF\kMry6umn7i?{_gOyWsж(cxEQ/o_كg?}~K_MS!?oװ6b8yB6j$ *tsw\ l'?wU@C$K /^n+ ,K(Ep7:4a4Gc%g5O=D3[\5=b^SUmoţdBP%Blb'HӔd)B՜14MOh(,E?|zǏ|ͳ:ed$qe:"Rgéf KGsecLX~5Bd.(K i0g>ԺghDNFlŠΈ#VlfZWRYvPK.PBnCca|7Vړ1iwNZqtoEY旜w IDAT|ƽhp%I'qFk..f떺Ȋ4Έ,iI85 FxBÚ,njz)vGj-a\gt K^XlqA[uC]CQDMOYm?^ %JPDQJ(rrHj5RX!pj;C'7{Sx"UB[fj+9S-@ 4}/h-?#:ӼupnfS5><k+])CITdIRLS&Ӕ ZLQh C)NSb mۇiF|d:B)r5cajI %]!yb1 1K{G(Ux6%'{o4Izw~+:޳{gF,0!0l,#@\k@%@l Xm"̵{X %X, Ih}}z9'3+w;»^p~#&z}22߷G$\v<9N?Vhp~vDYAȊ,P\@D;XUbH<L988DK7ӳ#-PY#CmYm|*hQLcuy ^2wHҜ+Ɵ {0oB)jY,u]cKE _>}OO*~ilbҺRh/G/pPi/L Z$T$Rɦv@)\$5y 1>.Zk: }K=F)4@0Iԍ鈖fȷ㬕}rm)R w$IR5$v.L".b6X;ds1LrϽ{5)&"`t@Hj(XbG \BmWqvr,$Zc C6Q[^G\ZRbLmZӒ[Ng|+VY/M#2M8??)ZgriWZgvw9=3?_2SUgRBlymsז52~ɦMJ\$=ҤQKLqAlփ}H)6e}*g-M$hm:0FcCj I!ItP9JTh[NB(<'4JƢUR h#IӔdNc[OpmtbxZ ׫@LȒ]꠨KA JԵAK`6!P<WyW]dY-mmT,Zۂl噟:l  淾>?3?^F)y>tځpU1CBrxe}}?oeyahZlYAڸb6&&7&fh->XG|$<oQ*:$RhV[n"ItUH6{:R !&Ϥ}f:,c(hT(iy 7~BfH;nӄYȀ@u$PJ$vv`#rB ͠9qnFAh%dL9.ئR7 OҢt`4Og<1\OQF\lv988d:2?_RV+rƖ l$aXpz )i!\G[۪ZaΗ,^!BM#|mDZ"ICTbIٝ]ʢ쌺n;J+T[;湰RΩkI&цq닢@ɔ<0?/xqS8 S18yY6nO%rV5j4ۖ5Yc6yl'#rb{߸o.o$N~ [-Hĉ,&L&#QBS=u5 /0HGQJG]+YB]uR& j;C)S|~FU1Eo*:Rc)G&`Q6AYO33xfw9{\Z,*#A항CL'X.*D\Pj9wkX1_rvvlk=bԧ_o=~YT6 {9|'y3`0`exч<b6J y;W9doo4ߵs'''%up6/. t.$ΖX_\V(iJX( 籚7ߨ66 qQ?(l? i]o8eFu-CRy3n\Bz`.7:P6wڡR ̊CrͰ'(-/min{vc!V6Cpsyք ՝ [UK(%f!֯Ӻ( a6_b=ؿp}?pH]$*1hBX#$h2PEx&Q$&UT5!OQFɭ[7W"1JQpMSRkR(j \z/ܽ{ݳHTi`^@]tEw,e*fx EsXy []Su +n+c>-qh]gO\Pz&6k}YwwO(.&3r\f"ȦoX[%{]Rҽ&YdRJjŤ)*yMr%3Ӕ#`4FLFꥄćqZױՒ4KX,OX.8_bz >㕳b%,2Lш<EEŝ'2_XV%g3\r@]XWv_$~ѬU\ձ6$!8B#bR͎)YTk@]WXW!L&1#2hRY򼠪ZH԰X,9;yiD geE\Te!e{x}3rv:CgbeN]0n[xl^hqmZD͚f޴vs.4m+ۤG􉜆K8\@iUFB a>3zh.kB%rǷDΚp"t}5QA 0')ݜtI3<,2THD3\9lr7oGt[Fc2f8i# "̑UG!f!1" H,~łjR41 jV#Hcб2jj`4:YFFe=\TU8OXh(9UݽB*J./<&M 5iؖG GU;e7nvG_/b8tE7HB2GݿWZGbhL] S%˥˾w>x?6D9o 09 0~K})u]]fZKb-nۜÙFSVRUUxUTF#@l͇rE8TFqnyHtTvM5qk-EuC2*̳18n&MLKjGRAe뎰;nAA]j9wn3˼ֶ&ZtA**xM&3(Iʳ(ٻ2W)rCr3ޱV$2~Vրǻ!u]lMع@]9$(|iO{*W6bA]hkX,bO #Pg@ *|LQ̚uըf24 cεk9<<Ν'xX,ܹs eѮ$6TgcT$bV**uf,\$]$t|$Izmv-Ԇ[{+3k;g_IiS6Z#.Ւ8u*HIHcXTu̜O`4Lw2iB L"rQ%BF;b]VXg<dž5mժ P/c5i %#HC|UV$I y=2NZ EAgh?`2pXZ(|)e`x(!x 03=@ɘTU3,ù)d$IevV+8=9& -X[w>9B)(>aSwvJFą7nQ,b=xPu|?KS2N46FF#YNUEblRjׄWKy}O~7~+y|9I6a  n /oySGSeS'b%hOYRyRE3Ez8S=ko7MpPq4׃] 8vkENEn !M ۲l)Aҷ*M$ uJfЈw֫h[֘Md ?@׼n߅@GذʹC^<5&ed ܻǽv9gG?#9jNNZs5irIpM-:UUH8I]ަ.N995)\3)%7-"3g*l l%BT3erU 'zb^l7&1ł7n"Nqc#"&D-a!nŵp>]Rgs4֩(S(t˯IO!RnZZkS ]~RXoLtYZwiQmxHHtEBYP $)L){#vwRFmr%eB‘`2Tq! bH:"L򤳱{*u]sX:T4uDr$}pӄ("fLw'q,Fd1.!>f6;,uF&%<AIFI3#xO1e]_ޭQsXWPKfgܽ{Jma2i%=.e\O1K,nZ1 OӜx[l@岤\Ԭs|>Q#E|c;o*sf Ū|}֗Cok3`-oko7o۝ĚiS%iQiu @m80 SRvo[w+CQֹYC_jcmYi^f6HشtJ6l7 /!PAO:1cbٷCn:ZH8Z!e;v(bݳa^TS &%׮r>,>v9:X)QFamlٱ> Ra+)ӝm$X@(sKeY"BT[|:P,c2if PjإUԵYEhB X7ߑ"ԥ[[,0@\!p+%ЇΆx!`G,IsjZWhS2ZR[+ޮtG6Hj,3wO89RU*"I~e~Wsg ʛ D΀ y;w 5CzK4hA7LZFR%)coyoۡ5:/qF73ֻpVQAo h_ݺ9*Dm4\Vռ9L7v ;kR#n9mqMq8%0:h +%P cތ<%I2sLw]?D*I<{ׯݓ#nݺEQ[pI#k-Y2[̨q3j$,QY\.ʨdV4qz:;>1kʕew7LJ!G- u3xzޱ\.m$&ML)[ZyMoCtQԕ4J6J['&I(˺Enz-k_j7(rpi(-1*Zեk*Vܭцiɞˈ~6ϓBʧOtv/IJEOtk <&š& Xc26|#:bowdKYLY,,899*gY.WdifPJE## c@(vvvc2k=7|16t9Sx\`6dH!:+L;o}xݤ0 uorn7RPM[4{ɀԐ*ƚt&D1&dD*(L'JCUU%Ue Rj`i2ƠBkm,+L1&eYTEC^5a{c˝#Rמ W[j$&m´U,k&,󜝝2$b4;9>\m~(Nɔ],Zn5vV)SFm#5Igu4)˒)Ʉ;wnjjU`SU`$ di zޛF{ґxj[ o-UUak1x%TwR"Z<oykir7m<5`0`s'|Goyoxp^XkZDoTDG@Csdaǡɺi6ζhd *kg79փpT'.{OmݺeU ,Z^ͱiAɃ nk/æg33iRf#"'YVԚȹ iqv)lWV-W_Uʭ_cs+d~%mxL&YIy&ߛpsOr6eQ0K̠U K"bbE!TpI6G[$Ad:…|1눙*XUхT+dGBdYkHUYt6Iu+dYFN2{NUUpM|9579JZ-%MuR;QQ [Q4f4M9;9svwelӌ$Ip'O#y[>QbPMS:ϲdTUQ@byٽ7H$ sр"g o^ /x[b1p㜋)75Z+zp7ѳA:8Q*]+J_%n`GzKUӇ6U<cP1$IňP#Gkb0lQ* ,GK/I5(Sձ\j)f4ʸFEjlj= Tʢ,-rTe#\lęN\~V5yST+>`+Om3S߃`%0I%/ZyB)svzfy2K[s~Smqjȭ6Yc;.QArsuM RҬUVm.ӪiqITg| $QT$ k"GG`g$FcwM9ڵkf3f3fgز,k$)H*-"cH!r=hrb} {}ܹ{LYZ A22 uXք-U]4eҨRD¶8s|bEj}Iv6! /,K:%VUYzXT%B_{04VyV˚,R+ W??  0x ~}w5ۭDNKl"lҵ &STm,l{q7rc@[[c&zAZn ?^vҠ>tbiv=Rxd!¡MAmQ  ?@kWrppiı1ߢK gUUSW!y' ZkܹlN.kG![ζױQX`BX *H:ڶʥZzXM굏i~MH);"/G^9\Eh7ɧɭ%OAhvx2Cs% JIt v}JҞ4'iP7-Gֳ*-$8c1?9J r}׹u nx(8}דUnZylU,AiR H#FjXH~ MjQpz:,,7o*-*iD($1%_RՁ+ٙxu]t s7F E`ggL4DiIW;)dPU󤎎(%IQQ3; yOR7qBQ3>i{~{*_a0`@ 0` oyF!Ax R /:EͶRRvJ kS(ol# ۑPUƠ=K>< dH)A(PW8eI$l;,6։GF>J)J 9::"xA6=).жKuU ; n].BvC٢u#r:Gs:zƘFq㚼6FhSOJҬלHkG8n+Atϕ?/6p}2K_w޷ڮU9mϏ'6tzqߴmmXgHH(ǰmm!ZkFyڑZ(-8c<3{i>!?G>Gqvv|QpAH&ӌ41g9RȘnd&AK89%5Is E[Y,J %bnQ8Gobа'OhkM|VPཤ,l$TEWi%<3+}98!'''<3?qȷ0`@ 0`.{/FQ75ʹBEns=ơ!%rdWo6B_Yu0ؾ{dC4.9jFjVCɈ5ZD gZKRH)cHh ؔqHh42ls$rxb0!ta qld 6 `]G5 (e] 36FQױŨnkNi3dm*9gM^UGn@k z:z!A Րʖm2 clQ].Um;}N{3h!Dg+ c. 4$s%W {{SG)aPՒSV2Vի;( 9SB)}{''8WXH͵\hJQE0ͥ%dAHGfsppd;.NgKgg3EjI-XQ̨ !0R\$QAWifH3NAJF)RT5WXj%>(ou*HxPgK]U8#9XY\|֭!B,i1L(*dEQk-⁏${u^~i3`0`7 ;λΎi+AUtojɍ~X2 ]]ex:De%t7o>9)٘3!7Q=J #*wҭ(>פ2*68$E)tY}Rݒ&Jzv;۵J޾:4iv}R_/m]lZK6p'ŵ6 ѵLuϕ˜*[*:_j8bQzKӪlT7_+&o_f  0ƫ_{j~e?aZ$ 5o[ZbG 6VG8:"g3lv;fˢ(?DVz$.۶@[ ~(/wY0Q+; !q{MVLX[6HKF[Ǭɘ6ׯo=&>vP_numkԅa/O|]l[wŅ5/>AӞ' }ע%em;^ܷ$lN5Uq5ik(#1&H%BhifAJd2B&# ŪĹ[Vƕ],!MA6h) +L'T`< F )u8?3>!l+>|Mpq:F2k$H{Me؈)wW֣UM#Jʲ&fȔ|շ<| ^~?ͷ|Kg*  0?o|Ww+fGbgM8+aWrJuk; &cqdDRI۰s(/l^Z4LIYn]=l粰M=϶%B'ӯv}Hga8%-/X6`2*4N! D,x}.Ϝq^|ٲ/m7EE$*A}#2%?W7OBtk0)} oVFYXWP*,YN=deUH i.MrQH]?G\8-X,+f 2 \4$%;-Q5Y6!5}{OHRC1csxq]9Vm(>vM\hmmj9!9el1a]HnhlPfvO;RvDNw湒 EL]Vo Q|z=lMOF\Fl75Ͼ9}be{:haSYԇzVa? 6IU?z>Y+W{|mvLLi붓D;}L"r0`\`\1>u-\*dU>]@O؏{ S& d{~ޝ1/X-R GcznZo||W=3V'$3wdrA6J9>|-c}]V]URRJPQ.0B~d2[F#*^ϲY5w[pU7d ~/xE̯C9<+PEf"g oc/ ?l֑ Ms:?~|dQŠ誰.9}j;\&'@J0=* ~kir(]s.}{N-㸌,i-kkAnY6aS .D6G*9D~֮vI2^Po/*^`H|9C[ b:Ƃ^[a#rlʮCk&yI$W(*⟋9(8_.-7?w/x><|g??us~?V e :IҜ;Xƶ5|g^*~GjY&)Wݨ/__tVјDi$lNY|ȇ|$/qI8UT.7iyֳo|FKP{|;{1|W?yͣ=~-_?d 0`@ 0`^¯MR&i;[UD  дe .YvvL;mgtʕp5knMI[QqL竭gHDW~ŷ o/ xчy -g/|*}4`3`E/ y{:&S|oq; tPI+oZ^i#LP l]iȹبtٶCť޻f?.hȴMՍH4m[Fm*vrDPJyqa?k֖Inm IDAT\]ܹ|n$> q{MVTc]@ju׷Q.mVBnۀlQhI3C ЉaZP,K#UTjIɢ(2_zo}'g~|rH`CCIfЄ=[P_;a$2{YqK[϶C~Rím]n{;UV}KH);vQ{gb)IIӴ#Es9F1[V,yw6~Tu̯ Iq%K&w$$. M항;b+IKXдP.VB$o@$ܙs.˜sfJL0(|f̙M{y< 6cwu^y$^ U(ǡxɗ!(gq/cyp|)< >O7O<@kguˎ4~3qN!B!9w=yx饿䩿a͚5!M9)uEk"E<QT'6uSu'JZlsZf(&ymQTjA[TqtH>Bx(p'c-V݀a-4tKnwmM:li*ja$-Ikf򒓔݊l=Q;VJSuxRW}>6&ys}vL}}IB-Vb񡊧wbab厄u۩|Z BDX,gZ,o7?أ-snL!B!QcGTxv<۰Xu x .{i<.hny`m]g!bb ܀3-\O)D 9YRQ]5*vR&3\޻I{h.A%CWSֱ֧fhB'y2$mJ!f.=nk= ' JWJ,}6).DҸ8_/=X9$^b;Vn&&VWC-`뮽{bejRoq._k׭b0#B!B!wO~.]‘#1DOڏR Ԥ n Q ']}THh uw)TFd6HcEkO{m꜑dRh89ۆҽ3T(SVVkMOM㛊F~Ym~k-vU{o)&y*A |zn Q կĶm(B!LB!:}RϞ= /ᇿ'V`0$쨋dsܶVǢ9mIJBθB4-4 )J;L#ΛgqS (e\T0b꤬FAsZk݌6bIrZq*ŽGsZOOxo+WPQXI^:Zjy,-m*& 2BB!:[~mH>O4Yir!LqrmY8R-NJK{0UX2:&Jn͒El^;mePH2/vmL|؞=!Bp!?Ey:tg;7x`( a@u(*x!^`"-`+bhR+ȱ@KT,qKڥTKۂu&Ǿ$f4*X1oС?yV?EiӦMؽ{7iB !Bޕ䥒S G + >-WgfZ6%9IGt s~@B@I !B><}硪x'qnC!5! :WIENDB`pytango-9.2.2/doc/_static/ipython_db.html000066400000000000000000000053151316324373100204340ustar00rootroot00000000000000

    ITango 8.1.1 -- An interactive Tango client.


    Running on top of Python 2.6.6, IPython 1.1 and PyTango 8.1.1


    ITango [1]: db

    Result [1]:


    bcu01ctrl.esrf.fr:10000

    TANGO Database sys/database/2

    Running since 2013-11-12 17:08:54

    Devices defined = 32
    Devices exported = 4
    Device servers defined = 10
    Device servers exported = 2

    Device properties defined = 17 [History lgth = 30]
    Class properties defined = 63 [History lgth = 56]
    Device attribute properties defined = 4 [History lgth = 7]
    Class attribute properties defined = 0 [History lgth = 0]
    Object properties defined = 0 [History lgth = 0]



    ITango [2]:

    pytango-9.2.2/doc/_static/ipython_motor.html000066400000000000000000000073651316324373100212160ustar00rootroot00000000000000

    ITango 8.1.1 -- An interactive Tango client.


    Running on top of Python 2.6.6, IPython 1.1 and PyTango 8.1.1


    ITango [1]: energy = Motor("energy")


    ITango [2]: energy

    Result [2]:


    Name:

    motor/icepapctrl/34

    Alias:

    energy

    Database:

    bcu01ctrl.esrf.fr:10000

    Type:

    Motor

    Server:

    Sardana/energy

    Server host:

    bcu01ctrl.esrf.fr

    Documentation:

    www.sardana-controls.org



    ITango [3]:

    pytango-9.2.2/doc/_static/ipython_serial.html000066400000000000000000000075051316324373100213310ustar00rootroot00000000000000

    ITango 8.1.1 -- An interactive Tango client.


    Running on top of Python 2.6.6, IPython 1.1 and PyTango 8.1.1


    ITango [1]: serial_line = Serial("pc01/tty/1")


    ITango [2]: serial_line

    Result [2]:


    Name:

    pc01/tty/1

    Alias:

    -----

    Database:

    bcu01ctrl.esrf.fr:10000

    Type:

    Serial

    Server:

    Serial/pc01

    Server host:

    pc01.esrf.fr

    Documentation:

    www.tango-controls.org/devices/Serial



    ITango [3]:

    pytango-9.2.2/doc/_static/ipython_tango.html000066400000000000000000000500031316324373100211510ustar00rootroot00000000000000

    ITango 8.1.1 -- An interactive Tango client.


    Running on top of Python 2.6.6, IPython 1.1 and PyTango 8.1.1


    ITango [1]: tango_test = TangoTest("sys/tg_test/1")


    ITango [2]: wave, img = tango_test.wave, tango_test.short_image_ro


    ITango [3]: fig, (axis1, axis2) = plt.subplots(1, 2, figsize=(9, 3));

           ...: axis2.imshow(img); axis2.set_title("Beam");

           ...: axis1.plot(wave); axis1.set_title("Sine");

           ...:

    ITango [4]:

    pytango-9.2.2/doc/_static/itango00.png000066400000000000000000000737221316324373100175450ustar00rootroot00000000000000PNG  IHDR&9ųsBIT|dtEXtSoftwaregnome-screenshot> IDATxw|SUl&颃V{=- < TǃG  K!P(]g6]mJC_Ƚ9sϹD@D=c1;uA0}LV?cv|F BLf?c1c Rʥ8p{w]H@FfF}ԗ1cGBRFAdt bSq9( ؇D#zuA^n628Pg1cubDشI)1_ @_PTtgpc1?0px7vXq㏣ԳaAJ_sHIKX1c2gOZ,{HL#ZL"H"*&~qdd@_`(dRh*(UXaw?\OЍïҝ/IŬX?{D˳D(e++#$&ެxknnw2Ya>H A"A0ޖeA* 7'ʧg@iÿx<'r2UhrFB8À*wKFQ9{ FEv"XLW\hı7̫2SK߮eV[H+\HTu}~Z#lFlӪIK\&3v/T[?d%#G3?\_^8Y~KW~l:ፕTWw,ܽr;?/NeK]}A@-\>_lrwŅI`j<=@#3"/5~w6":dp6Oz |5ȎQ ۇ1j@%5,@0(0T?դcowWėQwQ8IWbg#T\ةTHʁ֮|ca/"(<%줁B[ws xv9HT.hӧ5|(D)RxuK P??ג$P{W[$ǥ Pֻ,腩z.cY!#QL1_^QEpV*̵Jfn>нU3l^\v.fQw @dDlLT2)R3 ̚cVLvZG-vjؔyT g;%pbcX;,O?̀{랠ݸ.,^4?f83ksiWxO?}<WÌHkt!'#qo]Gm~󾾉ϞlQ# cQSb/|JL߾.eE'ljJLue32w{zEeJa_##*h4B_`6zF^JH*y{0ƿwx3\f𪘏lFvҮhyuAK QE ir wl{q-M!hӴ;tpKg9OgY|A_`DrF$ }o1ĔacUi%_: =R2*O_L2ʮ c}J` R+.2d@~6 (@^66 /894j66 c\R&g1kSYx׬7qu4_Կ낹e&r_FfޕCeeBaFBc>2F#!3'u0Ƙ_WiH@M !@K@wNxvN?\t&(󍠼[ lϿ<[hQ u1f̈́2QQ[Zfޕʥ*(Vڔck ^([v[WxL̜NM-z6J(d<1Tx/>7nݛm)?Gvy~O1$A=Sl^!{?ڋ8Z2Ƙ(w'W|.$UFo ,Tk /7_YFfaJ Y9y)TL-2^Α1@Tp=:)bqwaY%y鉈xLS,w/+XO F[75r!sq,' 91kjEFiU|{*ldI` o#yŏeUmIޮJvmT򸽭Ye.3(r(|'1րew?a 9>cZj /)FxTܩ9\"ы*UӱpH=@ X2v2sg$RvZت |1f*9+""" :;u˯/1VT&n@pYƒdK?k!@B.)2H_XI%q띈0,H  YJF'v*8(8XgY cq?e]1 f?.p)XIvS:9pܸ_݈8 |=2=dR)^e \cAѵwX4uQF-}iƘ| c1&Yw :k3ADȊWM@ u9qQX vW6/̙hcS\7M9!5~㛩y磁awAX$_=ϴxX/ CV/?Mg/"6pعtOPD?@=+CTv`IYfc1ӣ+ŅIH)CK%i㩴V z{uq$[4}w Oi%]MNn0 HBvTU_^ĘY)Ȑy64{9֑MΧQ^DgBF/'$ضQ߱~1rDd m S+7-i\hMt5>b&;(;m&&m]z""2U4ChW)zD\Mvm9VIIkcKu49I+L)&pjaOF-܍'"8qĉ'N֜Cc?Q]`OC7PΤ@+M9H^#Em _rLjʫ=~y L)ۯk_Pw=^;8G{Fo^3=fK˯(rD1mJk>g&Ф=Zn=IZۈϯ>Uxa'0`$I=ЩVDih5)MDjF8qĉ:MBnر r}[cl#b.$ oeka!u$&ŞG?\"3xwHƕ@S`xk7]zzN(-_d8>J>H[5]FljRb/᧏#*+_L{M{O~TU8oq2& DH{>za{;H,Yi͹EHSoǟ8h 9Ot!wﯲS===+Y)vMa&k#"hBs07cR] 4ͯz?|J6 G{QH5S}W $f?n,Idɜ=^y#t(j?;8&lLa(7bKGeɈN.t柏ZGO?V!h:ɞX99t$l,лm9l… pkk6/o>x`[qy|>uP= ^ߝ:H WّL ~pC*%? 1c|}"Buh,8ʀ;e i@a(@V[zD0x2(29.#,s&5 wbT٦g^s{s{GV3:ӖppEXv .VĺjeX=i|R|(=;S]n}hԩ;zñ871cQvCA&yoLFk^4N5Ƥ(M'=ML#)&Zdꋹ[l'*}qykUm ]$"=%UV(SѦ7()rƉIK%ضDDJ>Ygr+rTSH*Rj_sW9+%NQڅWpY)+?oD72J{?Ci"cZ ])#.oyF/*ܷ60fN7:$2%M|?.'gKӈ֓jد@6(&HK7OmF΢cwh38qĉ'N֛[֑S'aM}4' 9=l] YA_N8qĉeeudF)q@}0C;xv86ۅ3TN>^7 ݀Pc5H+#ϭF`'KIX 2 s~sIۏ uoH2᭕9k HFvJv*x 6c11c=Cpv4-~3c1f8`g1c̊qc1Ƙg-1u$4)`4܂ʶ*C!ӧz7Gc-GV-܌'bEⳔ4Eк;tHCK8 Dm,_}"(6c#kԠkBਅԧX[=+sK9ov~cQP:4uQsy5kEC9Z0huhD2dvܑLϴ$</ jך*R=ȿ,Z0tyנ--_}'~QL&t=gʶ e}x1aH\>ʼn'Nuxg?w!H{= G'Hd(@Ӹ?g+W=~lލ`~-FXw8}F̓*QX".q5l|~c{@ k6YX˦@SG?:bP5U, G< ` K-Я,<7f)?2 Csy^lbd12,A'BK imȲSi( Qqpn;1t~Mip6.8aѸ.] SbƐ4!+ ~\5-58X] @D F})8}h?RW%Nzb.Ș;3ʽ"8%RB]@[<40،rscaT4K?,p!2`<$^-`+*0gWn]>:(k.WEGRÜ[VG12%F֌FH)rRzQ/ܮI%}F7kFT*KUuhKќvHThyXlcC #y>F[FݯL7dOZm"vRdWq;>BkGʚH\D_1縚:ou08qĩSbuDӇ?FҢ<$/-ILjҬ] lȷˋ3I'%iq`Q=[ﵐjT~h~Vd+6huqʤy]ےV"2/YS7ijGg!ݕ$ AَH>z̯nk.yIAzܿiH}t\C<݄d&U_I¿_m. AGC6PG=M`Hɟ)4rg$Vے$C7R[{)BVv&UmD6ꭍ`MmuQ֬]ĤGz%9Ugy8ĉJcizgVhlĕ;q'ܴCN䝡=rpF eBjU$7-DLܸ\~Pd4Vr Ye7VA f0Q"Nفw!GPѹuf v.ή A`YASθw $ kZC}w#q(0yv^y$υ@NTϒ*bXdl~<2!~نRrn`ȫ,O3zxϟ~^pRf< CbW꤉wlOk|$> ӶホEGUvpkyo2CrO$ϻ8ue7~O } @ӆtVYTAF@'Q5}W.c~r_=ez嗟unЧ"O 1|p6[*!1k1S'4(4 =_\WZ RSQS<[M)F33QߍDS)\)n~C3*hpcH"/hX\+}:`bNt~#@0 2Oe2[ΣۻmzO!OGg l7Qr+鼪,q IDAT9]cƇ!eҳ3<ЩODh͜@R>]߽w__ (IMί獿0cM AɫwЊ]T<MVBp;K?wmV,EęNQnܮ )&?@dt*]AocD#Sv Wo8x?WA(JW<pMauiX 1-+?{7'sc1پߴQgoX]PWb/!8/. ߚ, yJc Gŷ+L~ʻeOwS.mo~C0o8+e1wawУѕ6f_ޣ9~&B#AJj.9]a,zaO ƶh; OsHI q!UE?M+[pŪGHvlzuOc]ί^X h;32>կ8crGHw _ =Պ 2$Ì^0XFq#q.|ʫ#CDyd`(1m]U `ɉy˽ :ab-wW_qʴCt#jF~s|p,% ]Ԣbsy~ İ ʜ7׬&6h7c1XC&ѕ>hܬM}קVx81cąh[CE|YS7|HeTC[ YLMa+cp~ ǯ(LEFf,BoOMa:+cp(@ZiJ:]aj1XTV1wC2l<fN#axp適Fc膣rT#qy ?jNFl?'}zd%Xj|^[ʌTk%h`='Ů^}s=h>ZCUODDB@ N @ _rb/-%6NV*=aҍ_u9BCmu~ _#Ymӵ,3m{WQE/ZzhϪ~UI׳)drz\JS~Mf7!ie7RIzĺi+?Vpu5>Gb/~r~ѥVLu<9OԧGW1zWĺvU *M1d6> :k3ADȊWM@ P9fWou{,V?4Ha9Fwp1$ :p# Vq@ؗZU[7M9!5~㛩Pf[X0ı?vޫ=  ֱ=+(Zq>ܾܱ[hWng s~롿 3>Ÿw mʽu uR\I2uO _Uxz`3Mq_d,1 ~ugDV'?_/㻈Zf&@lBkGJuԎ-Ъ#;imlɹ=b&'kb>S`38<&D;??{K>v ]_lZ_F؎oBpDӐjn_~U RSPbr#1׀//a{*bXdl^wTz?!y?=؞6_.I}m7E2g .DnS bנtŋus~=^1k|X]̍,DwA ]h ['t.. ^/F_.-=.8UJ @ ecǘS H0XW,Y~J.a(HFWM_m οt]S87OFt%1/bʵ4l]Ь3efC9cګ@}._s~v]'?aEGe s1)Nƹ1ep{nکDgqY_gֵrbx获>w)P rj\yXpO-V0pDCd>";@VYLdФᮘv)ߤZ'Ė c1.2ǎ.A~%d:Q_ #W$[s[mwKr(\ 2_u}s|pQl,`ISt,7G;p6zwuw79]Ƈ{""1yEAҳ/{ubNŌM-p<4e/DŽkA3â㲾#'ƻuBS|d',)&ڲ3y#&MjiS/],EDXcL ljzN{ DOQ2 :4q O叩'ϘtwD5Qb|Mk07յ]lM\.)^G-'+3lOYfg+m.= KW 뎰uW*ѯJ^~c}M W+~]ȋ/g0i9c{9?vc蟴f>$*9H6YFD!fFd~o]1w|Tbf|`JS> K-&?K8QvRQw<39WGߘ j=` Pb(-} ch롃ss{]p; y突7lvi+ZRn6 (Y+` 嬨KK xn .K㞘v1 Wt=pm׫v6zj=z~7> yZjUw+pG߆TbV~"X:?zD h?>p鎩ZݷPu[UR2~}}d.vи㊶Keo"yɏظh4:z;AvW۾s6( ػ9}1EɝKs#,5j;=bx9HisΘ9>cv13>x˻ucrPCea۶;^^G5ȏ՞:&̀{]j hLJjb6eŅOit*1&c~7]E.f;?pbҍDt6zi,:v֊fz6AIDD7Nl@3lQm"~'h 1f 2Ń*jg3ʵX;&56Q~ tj΢o=*yzKZs(KG]Eߏr_dt(gS>S]ܯL2Ξ3f$cZ{9 s-#]ṷ j6V8OѩF"cEKoOG'L:t_ wIOD{tfχ4'٘%keRj9Zج61# 4 PKyItyrzUkQ}ޖoso%:G[f 4O}D;VRT~j^֑SIR{5E9= &WiRQߤmHWudn(󾤵ȚNܯ8q<=\㑎8=T:`DB;_{_@.A \zbB)X~X(Ģ_1V9k0kwAT;|m3 HBDLJ5=w؋5*z=Z]ZN&qx"Z2qĩdNJaJOE|U4n1c;1o[u<1c11c1+;c1cVvc1Ƭ1cY1c1b3c1f8`g1c̊qc1Ƙ〝1c1+;c1cVvc1Ƭ1cY1c1b3c1f8`g1c̊qc1Ƙ〝1c1+;c1cVvc1Ƭ1cY1c1bv] SP @Jfuܹ39nb,c1~H;q# ؟f-ޯ8ڟսGu=7Y1Sbc1b8tONLUB-S%Nzb.Ș;3bk Dp ̛&BJ ?~TѦ'ॲld\|v"b3 lĝΥ(G1ļ!*E_;$p,sU H!PNBün5{ǪˆW˷Sx7pzJ@ƛ9h7.apre[h_}h?R Tmu KB~nv< yMڅ1>=Q\x )Ȑy64{9֑MΧQ^@Hx*OWMnގdE]&HǓBhUua~.S)PF VnZҸyj+/NMZ eޤCͧHb:obӎ-Ъ#;imlɹ=b&''I?ݤ^Nm=IӨBDd_Ut5:m@nZҺS-t# Q%-$7š94;٫dcߘZvH}Elյi5fvx JJaGЕ7[&lF5tOݓ}{J47͏Gq^'?uN8qHOԧGW$`ϡ_nB2'*ۯk_PwlЏzIJOL1Ĥp3K`$\|bE}Ԓ¥ M^5QE^ӊtZk$C7R[1Ϟo ZҤ-Hd(ĕJsWKSOMjf?C5Z5&]$S4[V1"R^v~@ N;C4}&Yt4'N8q2#+]GP`p^y$U@z$cȠ{p,Ǧ+{#/A{p (+y+O:uh>d>~H\Eꍯ׶+'sFٟ B4f_ɤ v zzz@'+7k vC^ځܢ*ѷE_hd:EQsb#y- hz.آGpxIcw"`#-\D2 '3;Y~<,w6Qɦ0/ 麦pOy$^~ aPg~FDt aj {(gA{cV?^5 ψCTocH*]cjJ>WoRKJ!8 v9?#G2GohRp|l0]B.1oc6ph1dz|KbtV{=t 7)=;S'$\OѢiѶ%("5qڴHٯb⽩=᭖@qGd IDAT+N^?GfC0ĴKnZ|:{j!zbVh;[Ss]c1<`ϋ/g0iʇB0f#9W u%Ū1({cO1̭i M[*wgsUVp 9h3, /9"9GLec}|_U_4N5Ƥ@~@Ip?sjD d&_}?e\-nfFGo"_Ѭ,-ޮ!ӠCJZ&cY씎DqhtvZ}1~m5yNNₑUW]ogc#|_2aF%l}FrMǂ GcT~J(-} ch롃ss{]޾`LDв~3k W? ߰ڥAHHq(67czo#[(lpo3Oc1x|n#o7zښ˞{-0m\8+8֚^<]1XpV0&uJ1c=Cpv4-~3c1f8`g1c̊qc1Ƙy DpJ ~(ϫK #^Ŧa'Pn.xIf` >5 ^ OO5ڲ15K]7K^kԗἛ;>ahsV+ 9X_mOF/{8ί#h1@qd6E[bwxTU?̤w= - @D@Dw׵꺫ZVׯl+"X@A@遐$&ޯ9ϓds9;\N$4=sz{PJDJJUlrώ'߅8q|!#{U"C{NUomp3V}?ĩ `5֢,/_?ߎoVBܽgS<qAJi~yLYvA 8]b{DY`h7kW_yǣ%#U!g'xp_j^W5EG6ᙅ#\psy1+x6V:Wu^8k7;/R?u sVtnδ/|}5 `aU>b!|$*[{8|ժ8#Aͯe8;_pHqUH=; odœ;Y]8:YnX(v.{o+a.;uND`;cNt{~w;>J@RE2V, 6A}q܏g%}-(I?wߛv#T-DuU7bDs8ӏ\nA H;I90?0o#&S͘'"")- g$:]>X/%_įPW/i I6eG͑Q;V;Z_k,OɏnI2D&.y^vȪm̓o@&F_@}mo(C.##z״Q1o_Y)W uwIy_O9LuG|pYuU29zIel_{ƝS?8.ߺKf/: #'̒%.NCÉ~_vt^F8t/JklJvb|QR\iٶ,s@_F  JBn[o0%HR]:|H>^b"$|l/I|}]$p⃲t_>BY/ahݏJ@DZ D{Y=+=$"b=*<0_ _PtrJ2^&*'-(ALh\Kl\#coݙ.=Oܕ 2SȌ)q>`WGM?ƈu)fD 4? ~9ur :Okj-ySC +ϓg&\Zy 9VކN(K_ٞoG&r\?WL'k+[W`"zx?>}pYd]Og_BD _)m7gh1`.JϬ` S DPOHW]_7%YN`kzC5DI'0+c-~ WݡsUARڐg5(Qk{|v +6ifh%wyo#sWXQ|^ m4^HGqN z',i\">i*"+0>R3i:.RAGbUl]ⶈc2sŎhzQ PnJ[xymx}*~ ƼS髆W@N}Q.yYaΜr~_`u0j"h.]:8h|q=;#1op̻ބOƉ`z! pZWX`0wYY \WoeݡsUAH 6-KknEr,Nz: MXmn'Koh/=#ǘ°xך4f_BֱQ]1! JD|@yŃh?>}*MA}7^2. }j^e֬P ȫ6c1lգܠGoWB݊u/?W<ӝrq(GJǪ"0aj/F?T'+\㋻t6=8řPV_Ecl.”6J~s,؅S(N[O?Ŋ؎qݡsU~䅨ko@ؑ SqGn>O_*1c5Y^&Nm3_'d%6"@Y-)o4 cڇ#7T) U#ߴ"f2X/mL ;cZ̵_5@?VJ]/U8_J5K"}8N%!WB˗I?X(G^/}Z=Ī:Tx'W>!W+>2xܕr)RP%6QnhwO_uDڕt|r'ywɵJt?yJ1W֥^~UΒp)ꗪjT,"!>${AޝCDDDDDNaNDDDDy0DDDDD;cNDDDDy0DDDDD;cNDDDDy0DDDDD;cNDDDDy0DDDDD;cNDDDDy0DDDDD;cNDDDDA HIIH<'v'_C{ }!;Z ?Sdqv:v:'"""r{U"U*`[6,VAGb]TPi|6jY]kƧG;{ĀyC/.WAuh9zdO""":ϹnJ̀)`#w((['ҍ`zMaT0@@7 O)+a?KwwH3S0*7bV,ll*!Ŀ7FکZNWn֥h2e%ۑ4wbRșJ@RO})dXl؟ Z_=|AEs{[qsaw"[vRq4oKR sC/2%ә\swګ_<(E 9>#Csv̙E߃W>| 0Hj{NȩBL<9ѢSZfkQ2nHJ[U4 P5INVKl@ ++?G}ޞ I6eG͑Q% "Ffx_R {+]KLD-%p8C X*.W$"C?dL\,NU}oODD: ΫdrL auْ S;wh,o Nȧ])cK@r=kIDlgvcbbbbbbbjJ)odƔ8q]K2+e]ՔWgl:,Djϼ? ^?tv75g$-?2umcL $R͒ SZMbە;}j?ɱw=L2*HΓcjwa"yI?Gכ{I3.ٝF.sK̂M.?Cሙ;=g4ú/ڗi.( DZvqm˙pP̛R=i7KmZ{i6|梴70u@i):⊸ <îL ~v#""sWQaSn_קÎ5~@q)NёXf䔷/\ꠡk^Fa.Gny Ky.jCb\%\8s@lx#h &G@wqO/ xkWueU9ӾJ]_Ą m3z7C;xr!VN *h*wow7eeȮ@TK^aQFY7n+ј64(/W,0]~=%LH=Мpk4s;WhUT cŤ~ r. DK{z90gWO s0lrm|}L1!s1 m&,Fd:>Xz}^ن;=B|.,l,[{:_>j@yp}x9O1#6!:&4v#""B/.gC/c'S9Z#xF`ق1:j+¦ ߿l;+f!&"1]$>wq.2/޽#qoGܣQt| $]he.?=?<| . o(o~ײ |?5[7գ']VOzAG*BD}Jwo⋿k|Lv#""UbZVhn%n+SsJ.QTH_FmF¦AO>.15rߴ0tk{ RP$U$ʛăyReUɡE}Dխ2m:lڸ%$`⃲)|8r0TL"b*˒;foe1]JLW *ߑMr1X$M^|<ȳ_jTNH^\vcbbbbbbbjJʗuty },,~e /H\q2ܼ?DDtV֛ΦHV,O7:ҐTYK>Iz>=_4i!j }/@pMnڏ5])@l_ V,GM^x?=[?$o5*h౳>޾zN&s[jm.`n"bn eOg۳=~))Mw-[}Bnj6Gd2~wbeU8z4X(OIEŎ_0~¡1pNխ*ڕD?/DXHu0QϚž@71)" BDI@>FTx'J!ԟTPaΫ4:]*шc0FQaMc!xC(r ʭF矢^[̙(/}4٥ǫk.'}'/F(ia8_>6vܿ袯o&duE=Va B7C~ۮ#pLFDGGˈ x ;@Ņ6Z̽H>X+L/CÞOqxW2֋Ma2'Fږ,% ahò;|Q_ Y0Ys:v]վj=ѥs"XDjR7M0؜?9q<0#~Co@ۡǙPNЅ6}do<aO@_pXO<˄1P .3)U6X˳`  v,8^w.X e: F/"|5/xנ"a8&극7*ǸKpQ\, vyʇlg bpbMhok4t-u4ء3pɲ*9;;SHQ//kd*IF7N=7A )'!;S^”-&^`}(m>"|`ʾ.D|:Wnþ}o I}ڿbP!8JnWH;|)_=jA|K! Ka¯0X c8xi+?M#z `\/&-ω/Y!ͻ`3 aryƌVPKRf(UpH;rpzOhO5eENWeUA<#.]``H\m83){Q۰~% Q%232QmFhCΫ6c>B~ %-yu{ %UL7c_a QSؙbXr\s+"5# &хqn9S>.fx9b)DYV'Nނ!#Pz1(QPӃKk)@yv[`Kh4`qf7/ԡ`r0uY` lg~?4(?m֢b}a %cE`:}Ui'@Qfz }}Ps#д ǫpJo iU:^M_Gů';_Ɖ}U3 /(5`+;MH=NuBa76ṅ_LjA~g^ oQ>x%F{0?گPk5Ǒ %NĈcY ],>JDVv1lv'6@qWolU0v:ŀߣkQg_L,?\[/Rt#L}55r-=Z/[?ԟ:8K&Cx5:ܥ3"Td!&f4 _Ō%#gRR ;^M j3Rf#,f#,Yݵs{;;uжy[+O@o?x{f16WDDu<'`uk؆cwJ#<%?t'UAwUpME=<[a04о\]֟7tz5FP9k UiYwa?QTc8Wl ڔ54C4Ҹx0}n^lG ;y׿H|x9F^g]qa".k+RwnB==)<}m(,6 a Bvo`/5-o("'_13ȡa:OOߡw׉v3l6O)c+F";cW֋r ZH#-cOj*u=Ph7tURTc@d.;i4+FUI E/ԤfOM)1^$m|]k-Fu }9Oqr`)FMmFAYY\h ,RE0nb\4q& I$ߋVz(D EvJ9 QvPNe+0n yo"ŷ,C_QcA㔝Wh ԠdƇN,<r!4r RaNVQD`qK09U׋r}B]de`E]!(*U}`^ ZV 3Fk|5a0 ڿ-Uբz&KQ58 *qIUbPo=,=hOQ )oUd+AEF%"F] p0ƽ:h|·o"d󅖻5{?af[S0ЅFEߦUb?ԡ.#K5m!uL S?Nq#O j{1`)@A!qq,h@J1wØY!@eEh2&nC l0¡?(岎DD]v%DeOiڳۖPQ1(9m9S/]k:G8nָ{lZh_݆~FL#(]̚ӅGTnɑ 0tʙ| Xňԟދ=g)4”承`Oԇn7Ptܶkx8n2@ɋ04a}TAmU'߄w +ߵkN ssjRV?> 9܃G0eGE~?e+)_ Ka+> OcGZ47c#"0cJd&&N~QϾH4Rn]7g*tYX|4G%ySVt* 2SȌ)q!t CFQ~$ݩEWrЍCu>30`N?1\Ի6.`͌kxAT2N|w ƀK@eH=L(?23ByzQ-H{{w.pSd}8t"""""Q)(9zV%(L"e 4a M+hw JPۊ_Pꦾ:LK5PgC6]Vw5X*!d 7:j|rK5=U]z'`׫1j eLVa-f &VJ●ltvSf^`@4lbZ*M`Zm-jh05"M P!x8+G:-9םB;Sb 6xނ6T ,fT" p5tg~dm.F!džVx5T@4'Yp;j*K' nlu\B԰}m$jK4ɂ"oPCꕣ'""""޹îBk0"^~*Z&6x5_6ǤT[諆^ * jCuMQ | !3UF7?KDDDD罞U@u^6Ips f 5M1nFm w5*g LVUK>_o|54Υ.8={0d m_RXu?u~*4g_? ɲzYaWf*< |cAI0`Wÿy{XX AɲzYa7ِ[5ĆRWi0P?i1dű64XWw -5p1kqъά3 _6{b]ǀ`ܝ2&ඔ;]*1DDDD{K`( Q-8rB@ Pbž?M~)瀘LV۸{ 9oXԐ@ܚkci0s:4~ٌlG#"""":8؝]]wr$(‚/ڿTN`R^!5R#L+'T!̶J wut5o5&^d 5%5ެ1ɂޘYo3lr ؍6Fk4[-h8mCV ~usn5'"""N)VLẌDDDDt~ey0DDDDD;cNDDDDy̔63`'"""":#8 DDDDDnQ0`'""""r+`=?# ؉ѝ ΀ :S_]wDDDDD4[cNDDDDԋc?%F{DDDDDDM"""""7jLSbD74vSbz=j pv 9; ݔ'nXx#tz}!ʙi0./.O?jnXzk{ݝc?%FÕ/OE@HT*>JDDDDԛʋ =4C `ezyA$""""P=?nelZ[DDDDD?.>hr0)IENDB`pytango-9.2.2/doc/_static/jive_powersupply.png000066400000000000000000000333071316324373100215450ustar00rootroot00000000000000PNG  IHDR2HQsBITOtEXtSoftwaregnome-screenshot> IDATxw\w?]zPѠH슀(`Kl E|=+}0^ĚY͏D.jP1 HX) He٬체E}=1|{Ɲ3 "zqAu㉈g0ӻE4@ks~%yz/s,1WKKK/\`0x<^'" gla|P( ޟ׫mV~YyfWa'<ut)j/d&$"O7ǫ%̙3:t"у ,//h0t:`(,,(AT`AtR|_DBFa偁2|>]vwޕH$?Vbq@@@mmmEENI4J~XPC$wFO,\xZ]R={gϞNNNW^%"F# ۷oϞ_3^Bg#4obWD"\.?|aa>hZ-XP(R)t:@̠{=#2ԫU/)&ub,kT+: $ssmL( e$SChTW,+8$v?yF^旿lvuޤPG#iKT84rڪ^gҰe}ᝋ+7ۍ(biExK #F 7oJ۷o;88zRe2@ĵ~:4Ź9Sf??t{cȄҮ=' ;ը/yVUKq_Z@V̴ۙ i/ v[6s-GX'ng/x mZ x@Tj!CB@ j59;;j^BMY8{UeMx`8|~jI:F-/h0 UeU=5j4V;O{UW|$|RD3TTTݨ-@d"G>b+?O^$o޻}릥~H̗zj/ʬ15W6ϦuZg"R߻Q\VA^~Hj\/}#Bi|͗pYjӖTPuAD:]c J^TӓS>O](-#;@_ԸN5:vc-n,x<[{K}z-rtGzx'xW,rɧ@(i Gd/_ LOOjzUTT:991|>cblǠP:,@Div{Y]=Hb~CJ ?y~s_yy?\DԾOw}H˼>,#T%c+nx8R9{șRGD.47.\FT*4H$tEEEvvv@Ã6qş zM̥GDtH@X@DbC9웿VN/861?߾̺?Au8:jS W]-+T_ڥFNKnwe nZN#7O˫{*vL̥㠃tlEme\^ۙy?8L5[Co\q=-Uҫtv=vwP.q;r*rhc|!/\|w)=p"*͸gw8k,^'W{x; ş~Ϸ"J,;wݻFsvvV*bKVg^^+?^5X^UNv*KIb^Mp_(~.<}ա; t yU:rxu֝Kdpol=s;{~*>ϩ۳R~/Ν9F|kEv^=<0*w. _߼, svXp5byw/\=zW'm,QTi{Ov-}7K="'ws5vͧW^k""JZ[OQg6{U/Ŀrn%xĿd"rlAkd8Fӕ\;;;XW VmdOQUSNԁBE'ZgG jKN?|{jh=eD -~x *ݶg|UUJY@D< jόHVz/9?r;@o+n݆#v%O*%"Nt-?tw/'5E7WC}-g9"8Xd}YRRr̙ݻ:99zzz1`0zPkuE .({Cm;w!OP[+jOD|^hp@_Wf#e H̝Kn=f&GsDNm[ud.泉L_PUQ˷k^QŒOڷIDqgTn!;gW8d >_^^}mWW:xs@f^ q.-_wb?}pjgs";_ZARuٟ׷S|#l n[Ckijܺ2IߝVx>㬩g83JM.@Dne^t~F Z |KH}<8YM;H]d.s{&L$gʜ]B{c}9_G{pM+:}4R&=== pΝ &H$V+ uO@+[z@e.VSTxv$U u{p]F,uM+tpkT{Ztwg"WRj,;?ؑ ;7+J_-#)wr-?%ӫ:ӂy|BWs0Nv2yeʹsu5"x6U$0|z,Ut2;f H)~ vj` +W $򄂦 7Rl kYñ[ݯzBVXXxu777OOg}ã/..ѣGϞ={ڙۭ)}*++ۤ*\l2c+#""z뭳gϚ6fddL4iԨQɉ͵\&##cСCǏ2̞={ԨQ÷jZm󂂂kךʕ+7oV;vpºu/_rM˳/d-/^ܷofR;ܵk޽{͛wqփyG޿RZ(Ց~Snch 8p Ź;;0k„ D4hР̱cfddL8ټmRUDD<سg#F)k׮DT^^n8i$P^?qD6]\\l}W\DVo&y@ 7Κ5ʼK5D4x`k fө?-`.K 3_@ x6lp˗oڴ߿ODDT]]mBaFFZtR>}X{3ZQQQADlnfΜyuuؘiӦ׳2܌ cǧfdd,\pȐ!毱R9te;X;5= Of^^^DtŽ=l>@ 4hVO;C3Zh4m[Ւ%K-~͛7ۤձ~Mw޻w~ ŪU,I;_AߺuӉx&[2N:N8jlľy(##mrtt\bmRd?6Hru:ݍ7ى)L6yd"pdMDGTjdd$]xJ4FsN-눈XfMnn}TTΦJW_޽L%bVMϒ3c}&Mtĉ~m[ŋ}c\\wzW^yeq}^i]tttqq^ѣٳY… 7oSק4hB{KII1/8))ѱ9_GSٓƏ;v:㑥ߔ?8NjNټys`` /օ4쫯ڳg|Я_NȰ+',؏OWgoz"5YϐbЪlpjرuںV}.܉#2x dy2<p 8Aע@zB{̙cPQQի/]jX.<<ތ ݻGGGY/f„ [nurrU-! ,0g[-裏̇iUl4Mrrŋ׮]h+W5jѣGfm׷MWVVFEEƲqbbb8lR6ZNHH1cq𜜜ӧGDDիNp>et3gL:5**jҥUUUۉHPL:U[4=D"ѣ~={bCřmqSSS_uuf&M/a;v JJJ ڵk 6,--|>dzzh׮]=zmی}&%%\+VXdə3gSIzzƍ"##M{]&!&%%/gX)l:CH[\UVmڴ8-v___TʞZtȑԙ8by9+믿N2ѰsGGG* Dׯ_& ~Pvӓ rcƌi4ҭ3ǏgLLL7nO8T_1N6l-Mbcc׬YS*@#>Ȇ~!"JLLdd#vM,/7r`'Oiww:h6l`LNN6=D L=_P(JVVtY@~iҤ26rgyb=5&$$XBT.Xĉϟ/;wۍ"##7olHrCBB>#:IOOg,Y׷OOw}|h'jnprQZZZrr1>?~|ux"=QGdtB52u4[Z8C!dy2<֭;v?^umҤIZm,??֬Yvvvk֬ u֞={ZJu&ۧP(ΝHD~~~qqq/cGg|>w &[^z̎,6Xd c#?RG7nx׮]{"w7|j|FdungDеkW"2߸qY<<<4QO~9Gdːdr\V[NIDAT+''gӦMqqq񩩩 .2dH}vӠ,",,N=x\Tυd'O&"{?,6~__ߏ?x۶m-j'N4Y\\wzW^y%%%%::Xc, ~~~+V0o7sbȐ!j]ve"|E 8A C!dy2<p 8EwB̙3[iHآիW_tIՒMY ǐO~EA~~ }zDDDQQQ^;3~̙SFEE-]z;)SzcFV(62&JKK\ر#((()))((}{ذaiiiDϞ=KD$]v>|x۶m3rǏXbɒ%gΜiL%7nLJJ4vLbz4bL֒ RK,<<|ܸq3gknjCD* t"JOO9r15cǎ Dg.\خ];" wܹxbf3g;4k'1c$%%i^߲+lsTuu5;H$l,k׮To6**JRegga***Ǝ5=d3NNN7n5hl:((_NNNnL'Og ٰaCII %'':v&88877 jJ@[FӦM߷o_@@ŋYcHHX, %"vilРAW'O޾}Iz*PR,wٲe1!!aŊJrW&N8|\޷o߹s6ny;vh3Ӣ x7ڵk4ÇiLV2&&݊agKڵn:Oin:c k>/<枨[L{m2w~7l]< ?lCGmR-[غxD#2x:!sj~Sn:Až5h6Z!dy2<pm7x}{ԩzj^?x 4-^x l޼{MSƧ|>? &&FղHP,[lԨQ111lPz+<<6lk<|jYIogt:#6$,zРAcǎȘ8qy'l2ӇC qYfyxxX, j5hёx}" " Œ Z}ҥ>}wžqi8~(...>>>555##c…C iLjƁHZY{{_  4H~'!2o1퇈z{^{MPZ5Vs .|gSLa-j/cg'N`$/[,CD2lDdo1-o`:tڴi+V111*ꫯڽ{7=|KV[OD S*l.DGG=z̞=ۆ\9_GSц ;v-2mL)(h=uBٙLMk4upG|E 8A C!dy2!dyEM0h-914Ou<9ZdFj,W\qrrjI%Vg ٳm[4?jpCV'$$DFFΘ1 %FDq:0C5y̙SFEE-]xRYYYID111eeeDT^^^XXHDllńL>aNi"k'"B1uT^olh4[uѣ?4?ѥwѣGo۶5ZjӦMƱœ>}3qƤHc?;v JJJ b6lXZZgϲyX_1DrǏ[\:ZaH+2,$$Ĵۤ#Fd2-Mekdǎ{(22 HD?Zo׮];c˜9sAAA=3f ꫬ1,,,==Gi 264bh…˪jiƌĦz~| @S,***ƎΘ(>>_2e siiigUUT*6]]]^$ k vJʚ;wnvvJׯ_}קjiW*ё#Gb} @#,<== )))s7__M6ƮY\N6liq!F#Hش##Bal ^~}XXH$2dCCC٨ijMӰ"3ǏOLL$qշZdRx)_6_Y EYY7lYl|C(11q1,,,%%/q:kV^XXX^^`Vڙ.00-jPK,h۷o4iRYY^gW'},'N8|\޷o߹siӦ۷/ `Ŭ1$$D,!٥1iy +VP* ,0fH+F7o6>) Р>?~|@@%<kΘW+ cU._~~=ӯZO뜽&V޾igc:JDiiiZ!A!t[mdxr_hs[Y:_o-|k߾=wY^SlOnos\#c"#=M_Ǔπ ":w65JF%Hͫb6[=c\vWb+'>od?sΌ8ʪYY:@c䝣R):וL_h6q?X":}5f_kz[;TV\tG^`U爨5ޯ$"PR'M,\v:oM9ſ;lB 꿗.ٷyKF'[K!RJ~{7]J}eD7𚽯ܖ]Nw_$"7w򲯓DbRPk|>߼*6lۿ{}a=1_[YM}6ހs?d〠:}3-y)%c#_u?([忿yqTUk6mg< .,, ÆG7_5}qaDqvgH IcO!";;MDef-vn}5lL'/'4،d#Ó%1He=;`ݻw7K`$Z-tzċDtV}[m%o=kEdA(;o'K:IDM~ڤh"D?曥%n=͜ߙ7\^ߗtbVVbW߁DV{dll%oWj:w;u8+vѶOmB3Ƽk61ZQX@TQ3㍜w#g)h{߭zp[c͓ +4x.kZ8`1BqC[y9,؏ 3OhGޜ>1 a_`Բ6Ǥ*6}X=i 1=ae 2<ư280LAv(Ɩx|aenxa5€y2<p 8܂#2 /// Tells compiler the method is abstract, it should be implemented by subclass. /// throw new Error("The method is abstract, it should be implemented by subclass."); }; function C_AbstractClass (instance) { /// /// Tells compiler the class is abstract, it should be implemented by subclass. /// if(instance.constructor === C_AbstractClass.caller) throw new Error("Cannot create instance of an abstract class."); } this.$C_AbstractClass = C_AbstractClass; }; //$JssorPoint$ var $JssorPoint$ = function (x, y) { var _ThisPoint = this; // Properties _ThisPoint.x = x; _ThisPoint.y = y; _ThisPoint.$Plus = function (point) { return new $JssorPoint$(x + point.x, y + point.y); }; _ThisPoint.$Minus = function (point) { return new $JssorPoint$(x - point.x, y - point.y); }; _ThisPoint.$Times = function (factor) { return new $JssorPoint$(x * factor, y * factor); }; _ThisPoint.$Divide = function (factor) { return new $JssorPoint$(x / factor, y / factor); }; _ThisPoint.$Negate = function () { return new $JssorPoint$(-x, -y); }; _ThisPoint.$DistanceTo = function (point) { return Math.sqrt(Math.pow(x - point.x, 2) + Math.pow(y - point.y, 2)); }; _ThisPoint.$Apply = function (func) { return new $JssorPoint$(func(x), func(y)); }; _ThisPoint.$Equals = function (point) { return (point instanceof $JssorPoint$) && (x === point.x) && (y === point.y); }; _ThisPoint.$ToString = function () { return "(" + x + "," + y + ")"; }; }; //$JssorEasing$ var $JssorEasing$ = window.$JssorEasing$ = { $EaseLinear: function (t) { return t; }, $EaseGoBack: function (t) { return 1 - Math.abs((t *= 2) - 1); }, $EaseSwing: function (t) { return -Math.cos(t * Math.PI) / 2 + .5; }, $EaseInQuad: function (t) { return t * t; }, $EaseOutQuad: function (t) { return -t * (t - 2); }, $EaseInOutQuad: function (t) { return (t *= 2) < 1 ? 1 / 2 * t * t : -1 / 2 * (--t * (t - 2) - 1); }, $EaseInCubic: function (t) { return t * t * t; }, $EaseOutCubic: function (t) { return (t -= 1) * t * t + 1; }, $EaseInOutCubic: function (t) { return (t *= 2) < 1 ? 1 / 2 * t * t * t : 1 / 2 * ((t -= 2) * t * t + 2); }, $EaseInQuart: function (t) { return t * t * t * t; }, $EaseOutQuart: function (t) { return -((t -= 1) * t * t * t - 1); }, $EaseInOutQuart: function (t) { return (t *= 2) < 1 ? 1 / 2 * t * t * t * t : -1 / 2 * ((t -= 2) * t * t * t - 2); }, $EaseInQuint: function (t) { return t * t * t * t * t; }, $EaseOutQuint: function (t) { return (t -= 1) * t * t * t * t + 1; }, $EaseInOutQuint: function (t) { return (t *= 2) < 1 ? 1 / 2 * t * t * t * t * t : 1 / 2 * ((t -= 2) * t * t * t * t + 2); }, $EaseInSine: function (t) { return 1 - Math.cos(t * Math.PI / 2); }, $EaseOutSine: function (t) { return Math.sin(t * Math.PI / 2); }, $EaseInOutSine: function (t) { return -1 / 2 * (Math.cos(Math.PI * t) - 1); }, $EaseInExpo: function (t) { return t == 0 ? 0 : Math.pow(2, 10 * (t - 1)); }, $EaseOutExpo: function (t) { return t == 1 ? 1 : -Math.pow(2, -10 * t) + 1; }, $EaseInOutExpo: function (t) { return t == 0 || t == 1 ? t : (t *= 2) < 1 ? 1 / 2 * Math.pow(2, 10 * (t - 1)) : 1 / 2 * (-Math.pow(2, -10 * --t) + 2); }, $EaseInCirc: function (t) { return -(Math.sqrt(1 - t * t) - 1); }, $EaseOutCirc: function (t) { return Math.sqrt(1 - (t -= 1) * t); }, $EaseInOutCirc: function (t) { return (t *= 2) < 1 ? -1 / 2 * (Math.sqrt(1 - t * t) - 1) : 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1); }, $EaseInElastic: function (t) { if (!t || t == 1) return t; var p = .3, s = .075; return -(Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * 2 * Math.PI / p)); }, $EaseOutElastic: function (t) { if (!t || t == 1) return t; var p = .3, s = .075; return Math.pow(2, -10 * t) * Math.sin((t - s) * 2 * Math.PI / p) + 1; }, $EaseInOutElastic: function (t) { if (!t || t == 1) return t; var p = .45, s = .1125; return (t *= 2) < 1 ? -.5 * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * 2 * Math.PI / p) : Math.pow(2, -10 * (t -= 1)) * Math.sin((t - s) * 2 * Math.PI / p) * .5 + 1; }, $EaseInBack: function (t) { var s = 1.70158; return t * t * ((s + 1) * t - s); }, $EaseOutBack: function (t) { var s = 1.70158; return (t -= 1) * t * ((s + 1) * t + s) + 1; }, $EaseInOutBack: function (t) { var s = 1.70158; return (t *= 2) < 1 ? 1 / 2 * t * t * (((s *= 1.525) + 1) * t - s) : 1 / 2 * ((t -= 2) * t * (((s *= 1.525) + 1) * t + s) + 2); }, $EaseInBounce: function (t) { return 1 - $JssorEasing$.$EaseOutBounce(1 - t) }, $EaseOutBounce: function (t) { return t < 1 / 2.75 ? 7.5625 * t * t : t < 2 / 2.75 ? 7.5625 * (t -= 1.5 / 2.75) * t + .75 : t < 2.5 / 2.75 ? 7.5625 * (t -= 2.25 / 2.75) * t + .9375 : 7.5625 * (t -= 2.625 / 2.75) * t + .984375; }, $EaseInOutBounce: function (t) { return t < 1 / 2 ? $JssorEasing$.$EaseInBounce(t * 2) * .5 : $JssorEasing$.$EaseOutBounce(t * 2 - 1) * .5 + .5; }, $EaseInWave: function (t) { return 1 - Math.cos(t * Math.PI * 2) }, $EaseOutWave: function (t) { return Math.sin(t * Math.PI * 2); }, $EaseOutJump: function (t) { return 1 - (((t *= 2) < 1) ? (t = 1 - t) * t * t : (t -= 1) * t * t); }, $EaseInJump: function (t) { return ((t *= 2) < 1) ? t * t * t : (t = 2 - t) * t * t; } }; var $JssorDirection$ = window.$JssorDirection$ = { $TO_LEFT: 0x0001, $TO_RIGHT: 0x0002, $TO_TOP: 0x0004, $TO_BOTTOM: 0x0008, $HORIZONTAL: 0x0003, $VERTICAL: 0x000C, $LEFTRIGHT: 0x0003, $TOPBOTOM: 0x000C, $TOPLEFT: 0x0005, $TOPRIGHT: 0x0006, $BOTTOMLEFT: 0x0009, $BOTTOMRIGHT: 0x000A, $AROUND: 0x000F, $GetDirectionHorizontal: function (direction) { return direction & 0x0003; }, $GetDirectionVertical: function (direction) { return direction & 0x000C; }, $ChessHorizontal: function (direction) { return (~direction & 0x0003) + (direction & 0x000C); }, $ChessVertical: function (direction) { return (~direction & 0x000C) + (direction & 0x0003); }, $IsToLeft: function (direction) { return (direction & 0x0003) == 0x0001; }, $IsToRight: function (direction) { return (direction & 0x0003) == 0x0002; }, $IsToTop: function (direction) { return (direction & 0x000C) == 0x0004; }, $IsToBottom: function (direction) { return (direction & 0x000C) == 0x0008; }, $IsHorizontal: function (direction) { return (direction & 0x0003) > 0; }, $IsVertical: function (direction) { return (direction & 0x000C) > 0; } }; var $JssorKeyCode$ = { $BACKSPACE: 8, $COMMA: 188, $DELETE: 46, $DOWN: 40, $END: 35, $ENTER: 13, $ESCAPE: 27, $HOME: 36, $LEFT: 37, $NUMPAD_ADD: 107, $NUMPAD_DECIMAL: 110, $NUMPAD_DIVIDE: 111, $NUMPAD_ENTER: 108, $NUMPAD_MULTIPLY: 106, $NUMPAD_SUBTRACT: 109, $PAGE_DOWN: 34, $PAGE_UP: 33, $PERIOD: 190, $RIGHT: 39, $SPACE: 32, $TAB: 9, $UP: 38 }; var $JssorAlignment$ = { $TopLeft: 0x11, $TopCenter: 0x12, $TopRight: 0x14, $MiddleLeft: 0x21, $MiddleCenter: 0x22, $MiddleRight: 0x24, $BottomLeft: 0x41, $BottomCenter: 0x42, $BottomRight: 0x44, $IsTop: function (aligment) { return aligment & 0x10 > 0; }, $IsMiddle: function (alignment) { return alignment & 0x20 > 0; }, $IsBottom: function (alignment) { return alignment & 0x40 > 0; }, $IsLeft: function (alignment) { return alignment & 0x01 > 0; }, $IsCenter: function (alignment) { return alignment & 0x02 > 0; }, $IsRight: function (alignment) { return alignment & 0x04 > 0; } }; var $JssorMatrix$; var $JssorAnimator$; // $Jssor$ is a static class, so make it singleton instance var $Jssor$ = window.$Jssor$ = new function () { // Fields var _This = this; var REGEX_WHITESPACE_GLOBAL = /\S+/g; var ROWSER_UNKNOWN = 0; var BROWSER_IE = 1; var BROWSER_FIREFOX = 2; var BROWSER_FIREFOX = 3; var BROWSER_CHROME = 4; var BROWSER_OPERA = 5; //var arrActiveX = ["Msxml2.XMLHTTP", "Msxml3.XMLHTTP", "Microsoft.XMLHTTP"]; var browser = 0; var browserRuntimeVersion = 0; var browserEngineVersion = 0; var browserJavascriptVersion = 0; var webkitVersion = 0; var app = navigator.appName; var ver = navigator.appVersion; var ua = navigator.userAgent; var _DocElmt = document.documentElement; var _TransformProperty; function DetectBrowser() { if (!browser) { if (app == "Microsoft Internet Explorer" && !!window.attachEvent && !!window.ActiveXObject) { var ieOffset = ua.indexOf("MSIE"); browser = BROWSER_IE; browserEngineVersion = ParseFloat(ua.substring(ieOffset + 5, ua.indexOf(";", ieOffset))); //check IE javascript version /*@cc_on browserJavascriptVersion = @_jscript_version; @*/ // update: for intranet sites and compat view list sites, IE sends // an IE7 User-Agent to the server to be interoperable, and even if // the page requests a later IE version, IE will still report the // IE7 UA to JS. we should be robust to self //var docMode = document.documentMode; //if (typeof docMode !== "undefined") { // browserRuntimeVersion = docMode; //} browserRuntimeVersion = document.documentMode || browserEngineVersion; } else if (app == "Netscape" && !!window.addEventListener) { var ffOffset = ua.indexOf("Firefox"); var saOffset = ua.indexOf("Safari"); var chOffset = ua.indexOf("Chrome"); var webkitOffset = ua.indexOf("AppleWebKit"); if (ffOffset >= 0) { browser = BROWSER_FIREFOX; browserRuntimeVersion = ParseFloat(ua.substring(ffOffset + 8)); } else if (saOffset >= 0) { var slash = ua.substring(0, saOffset).lastIndexOf("/"); browser = (chOffset >= 0) ? BROWSER_CHROME : BROWSER_FIREFOX; browserRuntimeVersion = ParseFloat(ua.substring(slash + 1, saOffset)); } if (webkitOffset >= 0) webkitVersion = ParseFloat(ua.substring(webkitOffset + 12)); } else { var match = /(opera)(?:.*version|)[ \/]([\w.]+)/i.exec(ua); if (match) { browser = BROWSER_OPERA; browserRuntimeVersion = ParseFloat(match[2]); } } } } function IsBrowserIE() { DetectBrowser(); return browser == BROWSER_IE; } function IsBrowserIeQuirks() { return IsBrowserIE() && (browserRuntimeVersion < 6 || document.compatMode == "BackCompat"); //Composite to "CSS1Compat" } function IsBrowserFireFox() { DetectBrowser(); return browser == BROWSER_FIREFOX; } function IsBrowserSafari() { DetectBrowser(); return browser == BROWSER_FIREFOX; } function IsBrowserChrome() { DetectBrowser(); return browser == BROWSER_CHROME; } function IsBrowserOpera() { DetectBrowser(); return browser == BROWSER_OPERA; } function IsBrowserBadTransform() { return IsBrowserSafari() && (webkitVersion > 534) && (webkitVersion < 535); } function IsBrowserSafeHWA() { return IsBrowserSafari() && (webkitVersion < 535); } function IsBrowserIe9Earlier() { return IsBrowserIE() && browserRuntimeVersion < 9; } function GetTransformProperty(elmt) { if (!_TransformProperty) { // Note that in some versions of IE9 it is critical that // msTransform appear in this list before MozTransform each(['transform', 'WebkitTransform', 'msTransform', 'MozTransform', 'OTransform'], function (property) { if (elmt.style[property] != undefined) { _TransformProperty = property; return true; } }); _TransformProperty = _TransformProperty || "transform"; } return _TransformProperty; } // Helpers function getOffsetParent(elmt, isFixed) { // IE and Opera "fixed" position elements don't have offset parents. // regardless, if it's fixed, its offset parent is the body. if (isFixed && elmt != document.body) { return document.body; } else { return elmt.offsetParent; } } function toString(obj) { return Object.prototype.toString.call(obj); } // [[Class]] -> type pairs var class2type; function each(object, callback) { if (toString(object) == "[object Array]") { for (var i = 0; i < object.length; i++) { if (callback(object[i], i, object)) { return true; } } } else { for (var name in object) { if (callback(object[name], name, object)) { return true; } } } } function GetClass2Type() { if (!class2type) { class2type = {}; each(["Boolean", "Number", "String", "Function", "Array", "Date", "RegExp", "Object"], function (name) { class2type["[object " + name + "]"] = name.toLowerCase(); }); } return class2type; } function type(obj) { return obj == null ? String(obj) : GetClass2Type()[toString(obj)] || "object"; } function isPlainObject(obj) { // Must be an Object. // Because of IE, we also have to check the presence of the constructor property. // Make sure that DOM nodes and window objects don't pass through, as well if (!obj || type(obj) !== "object" || obj.nodeType || _This.$IsWindow(obj)) { return false; } var hasOwn = Object.prototype.hasOwnProperty; try { // Not own constructor property must be Object if (obj.constructor && !hasOwn.call(obj, "constructor") && !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) { return false; } } catch (e) { // IE8,9 Will throw exceptions on certain host objects #9897 return false; } // Own properties are enumerated firstly, so to speed up, // if last one is own, then all properties are own. var key; for (key in obj) { } return key === undefined || hasOwn.call(obj, key); } function Delay(code, delay) { setTimeout(code, delay || 0); } function RemoveByReg(str, reg) { var m = reg.exec(str); if (m) { var header = str.substr(0, m.index); var tailer = str.substr(m.lastIndex + 1, str.length - (m.lastIndex + 1)); str = header + tailer; } return str; } function BuildNewCss(oldCss, removeRegs, replaceValue) { var css = (!oldCss || oldCss == "inherit") ? "" : oldCss; each(removeRegs, function (removeReg) { var m = removeReg.exec(css); if (m) { var header = css.substr(0, m.index); var tailer = css.substr(m.lastIndex + 1, css.length - (m.lastIndex + 1)); css = header + tailer; } }); css = replaceValue + (css.indexOf(" ") != 0 ? " " : "") + css; return css; } function SetStyleFilterIE(elmt, value) { if (browserRuntimeVersion < 9) { elmt.style.filter = value; } } function SetStyleMatrixIE(elmt, matrix, offset) { //matrix is not for ie9+ running in ie8- mode if (browserJavascriptVersion < 9) { var oldFilterValue = elmt.style.filter; var matrixReg = new RegExp(/[\s]*progid:DXImageTransform\.Microsoft\.Matrix\([^\)]*\)/g); var matrixValue = matrix ? "progid:DXImageTransform.Microsoft.Matrix(" + "M11=" + matrix[0][0] + ", M12=" + matrix[0][1] + ", M21=" + matrix[1][0] + ", M22=" + matrix[1][1] + ", SizingMethod='auto expand')" : ""; var newFilterValue = BuildNewCss(oldFilterValue, [matrixReg], matrixValue); SetStyleFilterIE(elmt, newFilterValue); _This.$CssMarginTop(elmt, offset.y); _This.$CssMarginLeft(elmt, offset.x); } } // Methods _This.$IsBrowserIE = IsBrowserIE; _This.$IsBrowserIeQuirks = IsBrowserIeQuirks; _This.$IsBrowserFireFox = IsBrowserFireFox; _This.$IsBrowserSafari = IsBrowserSafari; _This.$IsBrowserChrome = IsBrowserChrome; _This.$IsBrowserOpera = IsBrowserOpera; _This.$IsBrowserBadTransform = IsBrowserBadTransform; _This.$IsBrowserSafeHWA = IsBrowserSafeHWA; _This.$IsBrowserIe9Earlier = IsBrowserIe9Earlier; _This.$BrowserVersion = function () { return browserRuntimeVersion; }; _This.$BrowserEngineVersion = function () { return browserEngineVersion || browserRuntimeVersion; }; _This.$WebKitVersion = function () { return webkitVersion; }; _This.$Delay = Delay; _This.$Inherit = function (instance, baseClass) { baseClass.apply(instance, [].slice.call(arguments, 2)); return Extend({}, instance); }; function Construct(instance, constructor) { instance.constructor === Construct.caller && instance.$Construct && instance.$Construct(); } _This.$Construct = Construct; _This.$GetElement = function (elmt) { if (_This.$IsString(elmt)) { elmt = document.getElementById(elmt); } return elmt; }; function GetEvent(event) { return event || window.event; } GetEvent = GetEvent; _This.$EventSrc = function (event) { event = GetEvent(event); return event.target || event.srcElement || document; }; _This.$EventDst = function (event) { event = GetEvent(event); return event.relatedTarget || event.toElement; }; _This.$MousePosition = function (event) { event = GetEvent(event); var body = document.body; return { x: event.pageX || event.clientX + (_DocElmt.scrollLeft || body.scrollLeft || 0) - (_DocElmt.clientLeft || body.clientLeft || 0) || 0, y: event.pageY || event.clientY + (_DocElmt.scrollTop || body.scrollTop || 0) - (_DocElmt.clientTop || body.clientTop || 0) || 0 }; }; _This.$PageScroll = function () { var body = document.body; return { x: (window.pageXOffset || _DocElmt.scrollLeft || body.scrollLeft || 0) - (_DocElmt.clientLeft || body.clientLeft || 0), y: (window.pageYOffset || _DocElmt.scrollTop || body.scrollTop || 0) - (_DocElmt.clientTop || body.clientTop || 0) }; }; _This.$WindowSize = function () { var body = document.body; return { x: body.clientWidth || _DocElmt.clientWidth, y: body.clientHeight || _DocElmt.clientHeight }; }; //_This.$GetElementPosition = function (elmt) { // elmt = _This.$GetElement(elmt); // var result = new $JssorPoint$(); // // technique from: // // http://www.quirksmode.org/js/findpos.html // // with special check for "fixed" elements. // while (elmt) { // result.x += elmt.offsetLeft; // result.y += elmt.offsetTop; // var isFixed = _This.$GetElementStyle(elmt).position == "fixed"; // if (isFixed) { // result = result.$Plus(_This.$PageScroll(window)); // } // elmt = getOffsetParent(elmt, isFixed); // } // return result; //}; //_This.$GetMouseScroll = function (event) { // event = GetEvent(event); // var delta = 0; // default value // // technique from: // // http://blog.paranoidferret.com/index.php/2007/10/31/javascript-tutorial-the-scroll-wheel/ // if (typeof (event.wheelDelta) == "number") { // delta = event.wheelDelta; // } else if (typeof (event.detail) == "number") { // delta = event.detail * -1; // } else { // $JssorDebug$.$Fail("Unknown event mouse scroll, no known technique."); // } // // normalize value to [-1, 1] // return delta ? delta / Math.abs(delta) : 0; //}; //_This.$MakeAjaxRequest = function (url, callback) { // var async = typeof (callback) == "function"; // var req = null; // if (async) { // var actual = callback; // var callback = function () { // Delay($Jssor$.$CreateCallback(null, actual, req), 1); // }; // } // if (window.ActiveXObject) { // for (var i = 0; i < arrActiveX.length; i++) { // try { // req = new ActiveXObject(arrActiveX[i]); // break; // } catch (e) { // continue; // } // } // } else if (window.XMLHttpRequest) { // req = new XMLHttpRequest(); // } // if (!req) { // $JssorDebug$.$Fail("Browser doesn't support XMLHttpRequest."); // } // if (async) { // req.onreadystatechange = function () { // if (req.readyState == 4) { // // prevent memory leaks by breaking circular reference now // req.onreadystatechange = new Function(); // callback(); // } // }; // } // try { // req.open("GET", url, async); // req.send(null); // } catch (e) { // $JssorDebug$.$Log(e.name + " while making AJAX request: " + e.message); // req.onreadystatechange = null; // req = null; // if (async) { // callback(); // } // } // return async ? null : req; //}; //_This.$ParseXml = function (string) { // var xmlDoc = null; // if (window.ActiveXObject) { // try { // xmlDoc = new ActiveXObject("Microsoft.XMLDOM"); // xmlDoc.async = false; // xmlDoc.loadXML(string); // } catch (e) { // $JssorDebug$.$Log(e.name + " while parsing XML (ActiveX): " + e.message); // } // } else if (window.DOMParser) { // try { // var parser = new DOMParser(); // xmlDoc = parser.parseFromString(string, "text/xml"); // } catch (e) { // $JssorDebug$.$Log(e.name + " while parsing XML (DOMParser): " + e.message); // } // } else { // $JssorDebug$.$Fail("Browser doesn't support XML DOM."); // } // return xmlDoc; //}; function Css(elmt, name, value) { /// /// access css /// $Jssor$.$Css(elmt, name); //get css value /// $Jssor$.$Css(elmt, name, value); //set css value /// /// /// the element to access css /// /// /// the name of css property /// /// /// the value to set /// if (value != undefined) { elmt.style[name] = value; } else { var style = elmt.currentStyle || elmt.style; value = style[name]; if (value == "" && window.getComputedStyle) { style = elmt.ownerDocument.defaultView.getComputedStyle(elmt, null); style && (value = style.getPropertyValue(name) || style[name]); } return value; } } function CssN(elmt, name, value, isDimensional) { /// /// access css as numeric /// $Jssor$.$CssN(elmt, name); //get css value /// $Jssor$.$CssN(elmt, name, value); //set css value /// /// /// the element to access css /// /// /// the name of css property /// /// /// the value to set /// if (value != undefined) { isDimensional && (value += "px"); Css(elmt, name, value); } else { return ParseFloat(Css(elmt, name)); } } function CssP(elmt, name, value) { /// /// access css in pixel as numeric, like 'top', 'left', 'width', 'height' /// $Jssor$.$CssP(elmt, name); //get css value /// $Jssor$.$CssP(elmt, name, value); //set css value /// /// /// the element to access css /// /// /// the name of css property /// /// /// the value to set /// return CssN(elmt, name, value, true); } function CssProxy(name, numericOrDimension) { /// /// create proxy to access css, CssProxy(name[, numericOrDimension]); /// /// /// the element to access css /// /// /// not set: access original css, 1: access css as numeric, 2: access css in pixel as numeric /// var isDimensional = numericOrDimension & 2; var cssAccessor = numericOrDimension ? CssN : Css; return function (elmt, value) { return cssAccessor(elmt, name, value, isDimensional); }; } function GetStyleOpacity(elmt) { if (IsBrowserIE() && browserEngineVersion < 9) { var match = /opacity=([^)]*)/.exec(elmt.style.filter || ""); return match ? (ParseFloat(match[1]) / 100) : 1; } else return ParseFloat(elmt.style.opacity || "1"); } function SetStyleOpacity(elmt, opacity, ie9EarlierForce) { if (IsBrowserIE() && browserEngineVersion < 9) { //var filterName = "filter"; // browserEngineVersion < 8 ? "filter" : "-ms-filter"; var finalFilter = elmt.style.filter || ""; // for CSS filter browsers (IE), remove alpha filter if it's unnecessary. // update: doing _This always since IE9 beta seems to have broken the // behavior if we rely on the programmatic filters collection. var alphaReg = new RegExp(/[\s]*alpha\([^\)]*\)/g); // important: note the lazy star! _This protects against // multiple filters; we don't want to delete the other ones. // update: also trimming extra whitespace around filter. var ieOpacity = Math.round(100 * opacity); var alphaFilter = ""; if (ieOpacity < 100 || ie9EarlierForce) { alphaFilter = "alpha(opacity=" + ieOpacity + ") "; //elmt.style["-ms-filter"] = "progid:DXImageTransform.Microsoft.Alpha(opacity=" + ieOpacity + ") "; } var newFilterValue = BuildNewCss(finalFilter, [alphaReg], alphaFilter); SetStyleFilterIE(elmt, newFilterValue); } //if (!IsBrowserIE() || browserEngineVersion >= 9) else { elmt.style.opacity = opacity == 1 ? "" : Math.round(opacity * 100) / 100; } } function SetStyleTransformInternal(elmt, transform) { var rotate = transform.$Rotate || 0; var scale = transform.$Scale == undefined ? 1 : transform.$Scale; if (IsBrowserIe9Earlier()) { var matrix = _This.$CreateMatrix(rotate / 180 * Math.PI, scale, scale); SetStyleMatrixIE(elmt, (!rotate && scale == 1) ? null : matrix, _This.$GetMatrixOffset(matrix, transform.$OriginalWidth, transform.$OriginalHeight)); } else { //rotate(15deg) scale(.5) translateZ(0) var transformProperty = GetTransformProperty(elmt); if (transformProperty) { var transformValue = "rotate(" + rotate % 360 + "deg) scale(" + scale + ")"; if (IsBrowserChrome() && webkitVersion > 535) transformValue += " perspective(2000px)"; elmt.style[transformProperty] = transformValue; } } } _This.$SetStyleTransform = function (elmt, transform) { if (IsBrowserBadTransform()) { Delay(_This.$CreateCallback(null, SetStyleTransformInternal, elmt, transform)); } else { SetStyleTransformInternal(elmt, transform); } }; _This.$SetStyleTransformOrigin = function (elmt, transformOrigin) { var transformProperty = GetTransformProperty(elmt); if (transformProperty) elmt.style[transformProperty + "Origin"] = transformOrigin; }; _This.$CssScale = function (elmt, scale) { if (IsBrowserIE() && browserEngineVersion < 9 || (browserEngineVersion < 10 && IsBrowserIeQuirks())) { elmt.style.zoom = (scale == 1) ? "" : scale; } else { var transformProperty = GetTransformProperty(elmt); if (transformProperty) { //rotate(15deg) scale(.5) var transformValue = "scale(" + scale + ")"; var oldTransformValue = elmt.style[transformProperty]; var scaleReg = new RegExp(/[\s]*scale\(.*?\)/g); var newTransformValue = BuildNewCss(oldTransformValue, [scaleReg], transformValue); elmt.style[transformProperty] = newTransformValue; } } }; _This.$EnableHWA = function (elmt) { if (!elmt.style[GetTransformProperty(elmt)] || elmt.style[GetTransformProperty(elmt)] == "none") elmt.style[GetTransformProperty(elmt)] = "perspective(2000px)"; }; _This.$DisableHWA = function (elmt) { //if (force || elmt.style[GetTransformProperty(elmt)] == "perspective(2000px)") elmt.style[GetTransformProperty(elmt)] = "none"; }; var ie8OffsetWidth = 0; var ie8OffsetHeight = 0; //var ie8WindowResizeCallbackHandlers; //var ie8LastVerticalScrollbar; //var toggleInfo = ""; //function Ie8WindowResizeFilter(window, handler) { // var trigger = true; // var checkElement = (IsBrowserIeQuirks() ? window.document.body : window.document.documentElement); // if (checkElement) { // //check vertical bar // //var hasVerticalBar = checkElement.scrollHeight > checkElement.clientHeight; // //var verticalBarToggle = hasVerticalBar != ie8LastVerticalScrollbar; // //ie8LastVerticalScrollbar = hasVerticalBar; // var widthChange = checkElement.offsetWidth - ie8OffsetWidth; // var heightChange = checkElement.offsetHeight - ie8OffsetHeight; // if (widthChange || heightChange) { // ie8OffsetWidth += widthChange; // ie8OffsetHeight += heightChange; // } // else // trigger = false; // } // trigger && handler(); //} //_This.$OnWindowResize = function (window, handler) { // if (IsBrowserIE() && browserEngineVersion < 9) { // if (!ie8WindowResizeCallbackHandlers) { // ie8WindowResizeCallbackHandlers = [handler]; // handler = _This.$CreateCallback(null, Ie8WindowResizeFilter, window); // } // else { // ie8WindowResizeCallbackHandlers.push(handler); // return; // } // } // _This.$AddEvent(window, "resize", handler); //}; _This.$WindowResizeFilter = function (window, handler) { return IsBrowserIe9Earlier() ? function () { var trigger = true; var checkElement = (IsBrowserIeQuirks() ? window.document.body : window.document.documentElement); if (checkElement) { //check vertical bar //var hasVerticalBar = checkElement.scrollHeight > checkElement.clientHeight; //var verticalBarToggle = hasVerticalBar != ie8LastVerticalScrollbar; //ie8LastVerticalScrollbar = hasVerticalBar; var widthChange = checkElement.offsetWidth - ie8OffsetWidth; var heightChange = checkElement.offsetHeight - ie8OffsetHeight; if (widthChange || heightChange) { ie8OffsetWidth += widthChange; ie8OffsetHeight += heightChange; } else trigger = false; } trigger && handler(); } : handler; }; _This.$MouseOverOutFilter = function (handler, target) { /// /// The target element to detect mouse over/out events. (for ie < 9 compatibility) /// $JssorDebug$.$Execute(function () { if (!target) { throw new Error("Null reference, parameter \"target\"."); } }); return function (event) { event = GetEvent(event); var eventName = event.type; var related = event.relatedTarget || (eventName == "mouseout" ? event.toElement : event.fromElement); if (!related || (related !== target && !_This.$IsChild(target, related))) { handler(event); } }; }; _This.$AddEvent = function (elmt, eventName, handler, useCapture) { elmt = _This.$GetElement(elmt); // technique from: // http://blog.paranoidferret.com/index.php/2007/08/10/javascript-working-with-events/ if (elmt.addEventListener) { if (eventName == "mousewheel") { elmt.addEventListener("DOMMouseScroll", handler, useCapture); } // we are still going to add the mousewheel -- not a mistake! // _This is for opera, since it uses onmousewheel but needs addEventListener. elmt.addEventListener(eventName, handler, useCapture); } else if (elmt.attachEvent) { elmt.attachEvent("on" + eventName, handler); if (useCapture && elmt.setCapture) { elmt.setCapture(); } } $JssorDebug$.$Execute(function () { if (!elmt.addEventListener && !elmt.attachEvent) { $JssorDebug$.$Fail("Unable to attach event handler, no known technique."); } }); }; _This.$RemoveEvent = function (elmt, eventName, handler, useCapture) { elmt = _This.$GetElement(elmt); // technique from: // http://blog.paranoidferret.com/index.php/2007/08/10/javascript-working-with-events/ if (elmt.removeEventListener) { if (eventName == "mousewheel") { elmt.removeEventListener("DOMMouseScroll", handler, useCapture); } // we are still going to remove the mousewheel -- not a mistake! // _This is for opera, since it uses onmousewheel but needs removeEventListener. elmt.removeEventListener(eventName, handler, useCapture); } else if (elmt.detachEvent) { elmt.detachEvent("on" + eventName, handler); if (useCapture && elmt.releaseCapture) { elmt.releaseCapture(); } } }; _This.$FireEvent = function (elmt, eventName) { //var document = elmt.document; $JssorDebug$.$Execute(function () { if (!document.createEvent && !document.createEventObject) { $JssorDebug$.$Fail("Unable to fire event, no known technique."); } if (!elmt.dispatchEvent && !elmt.fireEvent) { $JssorDebug$.$Fail("Unable to fire event, no known technique."); } }); var evento; if (document.createEvent) { evento = document.createEvent("HTMLEvents"); evento.initEvent(eventName, false, false); elmt.dispatchEvent(evento); } else { var ieEventName = "on" + eventName; evento = document.createEventObject(); //event.eventType = ieEventName; //event.eventName = ieEventName; elmt.fireEvent(ieEventName, evento); } }; _This.$AddEventBrowserMouseUp = function (handler, userCapture) { _This.$AddEvent((IsBrowserIe9Earlier()) ? document : window, "mouseup", handler, userCapture); }; _This.$RemoveEventBrowserMouseUp = function (handler, userCapture) { _This.$RemoveEvent((IsBrowserIe9Earlier()) ? document : window, "mouseup", handler, userCapture); }; //_This.$AddEventBrowserMouseDown = function (handler, userCapture) { // _This.$AddEvent((IsBrowserIe9Earlier()) ? document : window, "mousedown", handler, userCapture); //}; //_This.$RemoveEventBrowserMouseDown = function (handler, userCapture) { // _This.$RemoveEvent((IsBrowserIe9Earlier()) ? document : window, "mousedown", handler, userCapture); //}; _This.$CancelEvent = function (event) { event = GetEvent(event); // technique from: // http://blog.paranoidferret.com/index.php/2007/08/10/javascript-working-with-events/ if (event.preventDefault) { event.preventDefault(); // W3C for preventing default } event.cancel = true; // legacy for preventing default event.returnValue = false; // IE for preventing default }; _This.$StopEvent = function (event) { event = GetEvent(event); // technique from: // http://blog.paranoidferret.com/index.php/2007/08/10/javascript-working-with-events/ if (event.stopPropagation) { event.stopPropagation(); // W3C for stopping propagation } event.cancelBubble = true; // IE for stopping propagation }; _This.$CreateCallback = function (object, method) { // create callback args var initialArgs = [].slice.call(arguments, 2); // create closure to apply method var callback = function () { // concatenate new args, but make a copy of initialArgs first var args = initialArgs.concat([].slice.call(arguments, 0)); return method.apply(object, args); }; //$JssorDebug$.$LiveStamp(callback, "callback_" + ($Jssor$.$GetNow() & 0xFFFFFF)); return callback; }; var _Freeer; _This.$FreeElement = function (elmt) { if (!_Freeer) _Freeer = _This.$CreateDiv(); if (elmt) { $Jssor$.$AppendChild(_Freeer, elmt); $Jssor$.$ClearInnerHtml(_Freeer); } }; _This.$InnerText = function (elmt, text) { if (text == undefined) return elmt.textContent || elmt.innerText; var textNode = document.createTextNode(text); _This.$ClearInnerHtml(elmt); elmt.appendChild(textNode); }; _This.$InnerHtml = function (elmt, html) { if (html == undefined) return elmt.innerHTML; elmt.innerHTML = html; }; _This.$GetClientRect = function (elmt) { var rect = elmt.getBoundingClientRect(); return { x: rect.left, y: rect.top, w: rect.right - rect.left, h: rect.bottom - rect.top }; }; _This.$ClearInnerHtml = function (elmt) { elmt.innerHTML = ""; }; _This.$EncodeHtml = function (text) { var div = _This.$CreateDiv(); _This.$InnerText(div, text); return _This.$InnerHtml(div); }; _This.$DecodeHtml = function (html) { var div = _This.$CreateDiv(); _This.$InnerHtml(div, html); return _This.$InnerText(div); }; _This.$SelectElement = function (elmt) { var userSelection; if (window.getSelection) { //W3C default userSelection = window.getSelection(); } var theRange = null; if (document.createRange) { theRange = document.createRange(); theRange.selectNode(elmt); } else { theRange = document.body.createTextRange(); theRange.moveToElementText(elmt); theRange.select(); } //set user selection if (userSelection) userSelection.addRange(theRange); }; _This.$DeselectElements = function () { if (document.selection) { document.selection.empty(); } else if (window.getSelection) { window.getSelection().removeAllRanges(); } }; _This.$Children = function (elmt) { var children = []; for (var tmpEl = elmt.firstChild; tmpEl; tmpEl = tmpEl.nextSibling) { if (tmpEl.nodeType == 1) { children.push(tmpEl); } } return children; }; function FindFirstChild(elmt, attrValue, attrName, deep) { if (!attrName) attrName = "u"; for (elmt = elmt ? elmt.firstChild : null; elmt; elmt = elmt.nextSibling) { if (elmt.nodeType == 1) { if (AttributeEx(elmt, attrName) == attrValue) return elmt; if (deep) { var childRet = FindFirstChild(elmt, attrValue, attrName, deep); if (childRet) return childRet; } } } } _This.$FindFirstChild = FindFirstChild; function FindChildren(elmt, attrValue, attrName, deep) { if (!attrName) attrName = "u"; var ret = []; for (elmt = elmt ? elmt.firstChild : null; elmt; elmt = elmt.nextSibling) { if (elmt.nodeType == 1) { if (AttributeEx(elmt, attrName) == attrValue) ret.push(elmt); if (deep) { var childRet = FindChildren(elmt, attrValue, attrName, deep); if (childRet.length) ret = ret.concat(childRet); } } } return ret; } _This.$FindChildren = FindChildren; function FindFirstChildByTag(elmt, tagName, deep) { for (elmt = elmt ? elmt.firstChild : null; elmt; elmt = elmt.nextSibling) { if (elmt.nodeType == 1) { if (elmt.tagName == tagName) return elmt; if (deep) { var childRet = FindFirstChildByTag(elmt, tagName, deep); if (childRet) return childRet; } } } } _This.$FindFirstChildByTag = FindFirstChildByTag; function FindChildrenByTag(elmt, tagName, deep) { var ret = []; for (elmt = elmt ? elmt.firstChild : null; elmt; elmt = elmt.nextSibling) { if (elmt.nodeType == 1) { if (!tagName || elmt.tagName == tagName) ret.push(elmt); if (deep) { var childRet = FindChildrenByTag(elmt, tagName, true); if (childRet.length) ret = ret.concat(childRet); } } } return ret; } _This.$FindChildrenByTag = FindChildrenByTag; _This.$GetElementsByTag = function (elmt, tagName) { return elmt.getElementsByTagName(tagName); }; function Extend(target) { for (var i = 1; i < arguments.length; i++) { var options = arguments[i]; // Only deal with non-null/undefined values if (options) { // Extend the base object for (var name in options) { target[name] = options[name]; } } } // Return the modified object return target; } _This.$Extend = Extend; function Unextend(target, options) { $JssorDebug$.$Assert(options); var unextended = {}; // Extend the base object for (var name in target) { if (target[name] != options[name]) { unextended[name] = target[name]; } } // Return the modified object return unextended; } _This.$Unextend = Unextend; _This.$IsUndefined = function (obj) { return type(obj) == "undefined"; }; _This.$IsFunction = function (obj) { return type(obj) == "function"; }; _This.$IsArray = function (obj) { return type(obj) == "array"; }; _This.$IsString = function (obj) { return type(obj) == "string"; }; _This.$IsNumeric = function (obj) { return !isNaN(ParseFloat(obj)) && isFinite(obj); }; _This.$IsWindow = function (obj) { return obj && obj == obj.window; }; _This.$Type = type; // args is for internal usage only _This.$Each = each; _This.$IsPlainObject = isPlainObject; function CreateElement(tagName) { return document.createElement(tagName); } _This.$CreateElement = CreateElement; _This.$CreateDiv = function () { return CreateElement("DIV", document); }; _This.$CreateSpan = function () { return CreateElement("SPAN", document); }; _This.$EmptyFunction = function () { }; function Attribute(elmt, name, value) { if (value == undefined) return elmt.getAttribute(name); elmt.setAttribute(name, value); } function AttributeEx(elmt, name) { return Attribute(elmt, name) || Attribute(elmt, "data-" + name); } _This.$Attribute = Attribute; _This.$AttributeEx = AttributeEx; function ClassName(elmt, className) { if (className == undefined) return elmt.className; elmt.className = className; } _This.$ClassName = ClassName; function ToHash(array) { var hash = {}; each(array, function (item) { hash[item] = item; }); return hash; } _This.$ToHash = ToHash; function Join(separator, strings) { /// /// The element to show the dialog around /// /// /// The element to show the dialog around /// var joined = ""; each(strings, function (str) { joined && (joined += separator); joined += str; }); return joined; } _This.$Join = Join; _This.$AddClass = function (elmt, className) { var newClassName = ClassName(elmt) + " " + className; ClassName(elmt, Join(" ", ToHash(newClassName.match(REGEX_WHITESPACE_GLOBAL)))); }; _This.$RemoveClass = function (elmt, className) { ClassName(elmt, Join(" ", _This.$Unextend(ToHash(ClassName(elmt).match(REGEX_WHITESPACE_GLOBAL)), ToHash(className.match(REGEX_WHITESPACE_GLOBAL))))); }; _This.$ParentNode = function (elmt) { return elmt.parentNode; }; _This.$HideElement = function (elmt) { _This.$CssDisplay(elmt, "none"); }; _This.$HideElements = function (elmts) { for (var i = 0; i < elmts.length; i++) { _This.$HideElement(elmts[i]); } }; _This.$ShowElement = function (elmt, show) { _This.$CssDisplay(elmt, show == false ? "none" : ""); }; _This.$ShowElements = function (elmts) { for (var i = 0; i < elmts.length; i++) { _This.$ShowElement(elmts[i]); } }; _This.$RemoveAttribute = function (elmt, attrbuteName) { elmt.removeAttribute(attrbuteName); }; _This.$CanClearClip = function () { return IsBrowserIE() && browserRuntimeVersion < 10; }; _This.$SetStyleClip = function (elmt, clip) { if (clip) { elmt.style.clip = "rect(" + Math.round(clip.$Top) + "px " + Math.round(clip.$Right) + "px " + Math.round(clip.$Bottom) + "px " + Math.round(clip.$Left) + "px)"; } else { var cssText = elmt.style.cssText; var clipRegs = [ new RegExp(/[\s]*clip: rect\(.*?\)[;]?/i), new RegExp(/[\s]*cliptop: .*?[;]?/i), new RegExp(/[\s]*clipright: .*?[;]?/i), new RegExp(/[\s]*clipbottom: .*?[;]?/i), new RegExp(/[\s]*clipleft: .*?[;]?/i) ]; var newCssText = BuildNewCss(cssText, clipRegs, ""); $Jssor$.$CssCssText(elmt, newCssText); } }; _This.$GetNow = function () { return new Date().getTime(); }; _This.$AppendChild = function (elmt, child) { elmt.appendChild(child); }; _This.$AppendChildren = function (elmt, children) { each(children, function (child) { _This.$AppendChild(elmt, child); }); }; _This.$InsertBefore = function (elmt, child, refObject) { elmt.insertBefore(child, refObject); }; _This.$InsertAdjacentHtml = function (elmt, where, text) { elmt.insertAdjacentHTML(where, text); }; _This.$RemoveChild = function (elmt, child) { elmt.removeChild(child); }; _This.$RemoveChildren = function (elmt, children) { each(children, function (child) { _This.$RemoveChild(elmt, child); }); }; _This.$ClearChildren = function (elmt) { _This.$RemoveChildren(elmt, _This.$Children(elmt)); }; _This.$ParseInt = function (str, radix) { return parseInt(str, radix || 10); }; function ParseFloat(str) { return parseFloat(str); } _This.$ParseFloat = ParseFloat; _This.$IsChild = function (elmtA, elmtB) { var body = document.body; while (elmtB && elmtA != elmtB && body != elmtB) { try { elmtB = elmtB.parentNode; } catch (e) { // Firefox sometimes fires events for XUL elements, which throws // a "permission denied" error. so this is not a child. return false; } } return elmtA == elmtB; }; function CloneNode(elmt, deep) { return elmt.cloneNode(deep); } _This.$CloneNode = CloneNode; function TranslateTransition(transition) { if (transition) { var flyDirection = transition.$FlyDirection; if (flyDirection & 1) { transition.x = transition.$ScaleHorizontal || 1; } if (flyDirection & 2) { transition.x = -transition.$ScaleHorizontal || -1; } if (flyDirection & 4) { transition.y = transition.$ScaleVertical || 1; } if (flyDirection & 8) { transition.y = -transition.$ScaleVertical || -1; } TranslateTransition(transition.$Brother); } } _This.$TranslateTransitions = function (transitions) { /// /// For backward compatibility only. /// if (transitions) { for (var i = 0; i < transitions.length; i++) { TranslateTransition(transitions[i]); } for (var name in transitions) { TranslateTransition(transitions[name]); } } }; function LoadImageCallback(callback, image, abort) { image.onload = null; image.abort = null; if (callback) callback(image, abort); } _This.$LoadImage = function (src, callback) { if (IsBrowserOpera() && browserRuntimeVersion < 11.6 || !src) { LoadImageCallback(callback, null); } else { var image = new Image(); image.onload = _This.$CreateCallback(null, LoadImageCallback, callback, image); image.onabort = _This.$CreateCallback(null, LoadImageCallback, callback, image, true); image.src = src; } }; _This.$LoadImages = function (imageElmts, mainImageElmt, callback) { var _ImageLoading = imageElmts.length + 1; function LoadImageCompleteEventHandler(image, abort) { _ImageLoading--; if (mainImageElmt && image && image.src == mainImageElmt.src) mainImageElmt = image; !_ImageLoading && callback && callback(mainImageElmt); } each(imageElmts, function (imageElmt) { $Jssor$.$LoadImage(imageElmt.src, LoadImageCompleteEventHandler); }); LoadImageCompleteEventHandler(); }; _This.$BuildElement = function (template, tagName, replacer, createCopy) { if (createCopy) template = CloneNode(template, true); var templateHolders = $Jssor$.$GetElementsByTag(template, tagName); for (var j = templateHolders.length - 1; j > -1; j--) { var templateHolder = templateHolders[j]; var replaceItem = CloneNode(replacer, true); ClassName(replaceItem, ClassName(templateHolder)); $Jssor$.$CssCssText(replaceItem, templateHolder.style.cssText); var thumbnailPlaceHolderParent = $Jssor$.$ParentNode(templateHolder); $Jssor$.$InsertBefore(thumbnailPlaceHolderParent, replaceItem, templateHolder); $Jssor$.$RemoveChild(thumbnailPlaceHolderParent, templateHolder); } return template; }; var _MouseDownButtons; function JssorButtonEx(elmt) { var _Self = this; var _OriginClassName; var _IsMouseDown; //class name 'dn' var _IsActive; //class name 'av' var _IsDisabled; //class name 'ds' function Highlight() { var className = _OriginClassName; if (_IsDisabled) { className += 'ds'; } else if (_IsMouseDown) { className += 'dn'; } else if (_IsActive) { className += "av"; } ClassName(elmt, className); } function OnMouseDown(event) { if (_IsDisabled) { _This.$CancelEvent(event); } else { _MouseDownButtons.push(_Self); _IsMouseDown = true; Highlight(); } } _Self.$MouseUp = function () { /// /// Internal member function, do not use it. /// /// _IsMouseDown = false; Highlight(); }; _Self.$Activate = function (activate) { if (activate != undefined) { _IsActive = activate; Highlight(); } else { return _IsActive; } }; _Self.$Enable = function (enable) { if (enable != undefined) { _IsDisabled = !enable; Highlight(); } else { return !_IsDisabled; } }; //JssorButtonEx Constructor { elmt = _This.$GetElement(elmt); if (!_MouseDownButtons) { _This.$AddEventBrowserMouseUp(function () { var oldMouseDownButtons = _MouseDownButtons; _MouseDownButtons = []; each(oldMouseDownButtons, function (button) { button.$MouseUp(); }); }); _MouseDownButtons = []; } _OriginClassName = ClassName(elmt); $Jssor$.$AddEvent(elmt, "mousedown", OnMouseDown); } } _This.$Buttonize = function (elmt) { return new JssorButtonEx(elmt); }; _This.$Css = Css; _This.$CssN = CssN; _This.$CssP = CssP; _This.$CssOverflow = CssProxy("overflow"); _This.$CssTop = CssProxy("top", 2); _This.$CssLeft = CssProxy("left", 2); _This.$CssWidth = CssProxy("width", 2); _This.$CssHeight = CssProxy("height", 2); _This.$CssMarginLeft = CssProxy("marginLeft", 2); _This.$CssMarginTop = CssProxy("marginTop", 2); _This.$CssPosition = CssProxy("position"); _This.$CssDisplay = CssProxy("display"); _This.$CssZIndex = CssProxy("zIndex", 1); _This.$CssFloat = function (elmt, float) { return Css(elmt, IsBrowserIE() ? "styleFloat" : "cssFloat", float); }; _This.$CssOpacity = function (elmt, opacity, ie9EarlierForce) { if (opacity != undefined) { SetStyleOpacity(elmt, opacity, ie9EarlierForce); } else { return GetStyleOpacity(elmt); } }; _This.$CssCssText = function (elmt, text) { if (text != undefined) { elmt.style.cssText = text; } else { return elmt.style.cssText; } }; var _StyleGetter = { $Opacity: _This.$CssOpacity, $Top: _This.$CssTop, $Left: _This.$CssLeft, $Width: _This.$CssWidth, $Height: _This.$CssHeight, $Position: _This.$CssPosition, $Display: _This.$CssDisplay, $ZIndex: _This.$CssZIndex }; //var _StyleGetter = { // $Opacity: _This.$GetStyleOpacity, // $Top: _This.$GetStyleTop, // $Left: _This.$GetStyleLeft, // $Width: _This.$GetStyleWidth, // $Height: _This.$GetStyleHeight, // $Position: _This.$GetStylePosition, // $Display: _This.$GetStyleDisplay, // $ZIndex: _This.$GetStyleZIndex //}; var _StyleSetterReserved; //var _StyleSetterReserved = { // $Opacity: _This.$SetStyleOpacity, // $Top: _This.$SetStyleTop, // $Left: _This.$SetStyleLeft, // $Width: _This.$SetStyleWidth, // $Height: _This.$SetStyleHeight, // $Display: _This.$SetStyleDisplay, // $Clip: _This.$SetStyleClip, // $MarginLeft: _This.$SetStyleMarginLeft, // $MarginTop: _This.$SetStyleMarginTop, // $Transform: _This.$SetStyleTransform, // $Position: _This.$SetStylePosition, // $ZIndex: _This.$SetStyleZIndex //}; function StyleSetter() { if (!_StyleSetterReserved) { _StyleSetterReserved = Extend({ $MarginTop: _This.$CssMarginTop, $MarginLeft: _This.$CssMarginLeft, $Clip: _This.$SetStyleClip, $Transform: _This.$SetStyleTransform }, _StyleGetter); } return _StyleSetterReserved; } function StyleSetterEx() { StyleSetter(); //For Compression Only _StyleSetterReserved.$Transform = _StyleSetterReserved.$Transform; return _StyleSetterReserved; } _This.$StyleSetter = StyleSetter; _This.$StyleSetterEx = StyleSetterEx; _This.$GetStyles = function (elmt, originStyles) { StyleSetter(); var styles = {}; each(originStyles, function (value, key) { if (_StyleGetter[key]) { styles[key] = _StyleGetter[key](elmt); } }); return styles; }; _This.$SetStyles = function (elmt, styles) { var styleSetter = StyleSetter(); each(styles, function (value, key) { styleSetter[key] && styleSetter[key](elmt, value); }); }; _This.$SetStylesEx = function (elmt, styles) { StyleSetterEx(); _This.$SetStyles(elmt, styles); }; $JssorMatrix$ = new function () { var _ThisMatrix = this; function Multiply(ma, mb) { var acs = ma[0].length; var rows = ma.length; var cols = mb[0].length; var matrix = []; for (var r = 0; r < rows; r++) { var row = matrix[r] = []; for (var c = 0; c < cols; c++) { var unitValue = 0; for (var ac = 0; ac < acs; ac++) { unitValue += ma[r][ac] * mb[ac][c]; } row[c] = unitValue; } } return matrix; } _ThisMatrix.$ScaleX = function (matrix, sx) { return _ThisMatrix.$ScaleXY(matrix, sx, 0); }; _ThisMatrix.$ScaleY = function (matrix, sy) { return _ThisMatrix.$ScaleXY(matrix, 0, sy); }; _ThisMatrix.$ScaleXY = function (matrix, sx, sy) { return Multiply(matrix, [[sx, 0], [0, sy]]); }; _ThisMatrix.$TransformPoint = function (matrix, p) { var pMatrix = Multiply(matrix, [[p.x], [p.y]]); return new $JssorPoint$(pMatrix[0][0], pMatrix[1][0]); }; }; _This.$CreateMatrix = function (alpha, scaleX, scaleY) { var cos = Math.cos(alpha); var sin = Math.sin(alpha); //var r11 = cos; //var r21 = sin; //var r12 = -sin; //var r22 = cos; //var m11 = cos * scaleX; //var m12 = -sin * scaleY; //var m21 = sin * scaleX; //var m22 = cos * scaleY; return [[cos * scaleX, -sin * scaleY], [sin * scaleX, cos * scaleY]]; }; _This.$GetMatrixOffset = function (matrix, width, height) { var p1 = $JssorMatrix$.$TransformPoint(matrix, new $JssorPoint$(-width / 2, -height / 2)); var p2 = $JssorMatrix$.$TransformPoint(matrix, new $JssorPoint$(width / 2, -height / 2)); var p3 = $JssorMatrix$.$TransformPoint(matrix, new $JssorPoint$(width / 2, height / 2)); var p4 = $JssorMatrix$.$TransformPoint(matrix, new $JssorPoint$(-width / 2, height / 2)); return new $JssorPoint$(Math.min(p1.x, p2.x, p3.x, p4.x) + width / 2, Math.min(p1.y, p2.y, p3.y, p4.y) + height / 2); }; }; //for backward compatibility //var $JssorUtils$ = window.$JssorUtils$ = $Jssor$; //$JssorObject$ var $JssorObject$ = window.$JssorObject$ = function () { var _ThisObject = this; // Fields var _Listeners = []; // dictionary of eventName --> array of handlers var _Listenees = []; // Private Methods function AddListener(eventName, handler) { $JssorDebug$.$Execute(function () { if (eventName == undefined || eventName == null) throw new Error("param 'eventName' is null or empty."); if (typeof (handler) != "function") { throw "param 'handler' must be a function."; } $Jssor$.$Each(_Listeners, function (listener) { if (listener.$EventName == eventName && listener.$Handler === handler) { throw new Error("The handler listened to the event already, cannot listen to the same event of the same object with the same handler twice."); } }); }); _Listeners.push({ $EventName: eventName, $Handler: handler }); } function RemoveListener(eventName, handler) { $JssorDebug$.$Execute(function () { if (eventName == undefined || eventName == null) throw new Error("param 'eventName' is null or empty."); if (typeof (handler) != "function") { throw "param 'handler' must be a function."; } }); $Jssor$.$Each(_Listeners, function (listener, index) { if (listener.$EventName == eventName && listener.$Handler === handler) { _Listeners.splice(index, 1); } }); } function ClearListeners() { _Listeners = []; } function ClearListenees() { $Jssor$.$Each(_Listenees, function (listenee) { $Jssor$.$RemoveEvent(listenee.$Obj, listenee.$EventName, listenee.$Handler); }); _Listenees = []; } //Protected Methods _ThisObject.$Listen = function (obj, eventName, handler, useCapture) { $JssorDebug$.$Execute(function () { if (!obj) throw new Error("param 'obj' is null or empty."); if (eventName == undefined || eventName == null) throw new Error("param 'eventName' is null or empty."); if (typeof (handler) != "function") { throw "param 'handler' must be a function."; } $Jssor$.$Each(_Listenees, function (listenee) { if (listenee.$Obj === obj && listenee.$EventName == eventName && listenee.$Handler === handler) { throw new Error("The handler listened to the event already, cannot listen to the same event of the same object with the same handler twice."); } }); }); $Jssor$.$AddEvent(obj, eventName, handler, useCapture); _Listenees.push({ $Obj: obj, $EventName: eventName, $Handler: handler }); }; _ThisObject.$Unlisten = function (obj, eventName, handler) { $JssorDebug$.$Execute(function () { if (!obj) throw new Error("param 'obj' is null or empty."); if (eventName == undefined || eventName == null) throw new Error("param 'eventName' is null or empty."); if (typeof (handler) != "function") { throw "param 'handler' must be a function."; } }); $Jssor$.$Each(_Listenees, function (listenee, index) { if (listenee.$Obj === obj && listenee.$EventName == eventName && listenee.$Handler === handler) { $Jssor$.$RemoveEvent(obj, eventName, handler); _Listenees.splice(index, 1); } }); }; _ThisObject.$UnlistenAll = ClearListenees; // Public Methods _ThisObject.$On = _ThisObject.addEventListener = AddListener; _ThisObject.$Off = _ThisObject.removeEventListener = RemoveListener; _ThisObject.$TriggerEvent = function (eventName) { var args = [].slice.call(arguments, 1); $Jssor$.$Each(_Listeners, function (listener) { try { listener.$EventName == eventName && listener.$Handler.apply(window, args); } catch (e) { // handler threw an error, ignore, go on to next one $JssorDebug$.$Error(e.name + " while executing " + eventName + " handler: " + e.message, e); } }); }; _ThisObject.$Destroy = function () { ClearListenees(); ClearListeners(); for (var name in _ThisObject) delete _ThisObject[name]; }; $JssorDebug$.$C_AbstractClass(_ThisObject); }; $JssorAnimator$ = function (delay, duration, options, elmt, fromStyles, toStyles) { delay = delay || 0; var _ThisAnimator = this; var _AutoPlay; var _Hiden; var _CombineMode; var _PlayToPosition; var _PlayDirection; var _NoStop; var _TimeStampLastFrame = 0; var _SubEasings; var _SubRounds; var _SubDurings; var _Callback; var _Position_Current = 0; var _Position_Display = 0; var _Hooked; var _Position_InnerBegin = delay; var _Position_InnerEnd = delay + duration; var _Position_OuterBegin; var _Position_OuterEnd; var _LoopLength; var _NestedAnimators = []; var _StyleSetter; function GetPositionRange(position, begin, end) { var range = 0; if (position < begin) range = -1; else if (position > end) range = 1; return range; } function GetInnerPositionRange(position) { return GetPositionRange(position, _Position_InnerBegin, _Position_InnerEnd); } function GetOuterPositionRange(position) { return GetPositionRange(position, _Position_OuterBegin, _Position_OuterEnd); } function Shift(offset) { _Position_OuterBegin += offset; _Position_OuterEnd += offset; _Position_InnerBegin += offset; _Position_InnerEnd += offset; _Position_Current += offset; _Position_Display += offset; $Jssor$.$Each(_NestedAnimators, function (animator) { animator, animator.$Shift(offset); }); } function Locate(position, relative) { var offset = position - _Position_OuterBegin + delay * relative; Shift(offset); //$JssorDebug$.$Execute(function () { // _ThisAnimator.$Position_InnerBegin = _Position_InnerBegin; // _ThisAnimator.$Position_InnerEnd = _Position_InnerEnd; // _ThisAnimator.$Position_OuterBegin = _Position_OuterBegin; // _ThisAnimator.$Position_OuterEnd = _Position_OuterEnd; //}); return _Position_OuterEnd; } function GoToPosition(positionOuter, force) { var trimedPositionOuter = positionOuter; if (_LoopLength && (trimedPositionOuter >= _Position_OuterEnd || trimedPositionOuter <= _Position_OuterBegin)) { trimedPositionOuter = ((trimedPositionOuter - _Position_OuterBegin) % _LoopLength + _LoopLength) % _LoopLength + _Position_OuterBegin; } if (!_Hooked || _NoStop || force || _Position_Current != trimedPositionOuter) { var positionToDisplay = Math.min(trimedPositionOuter, _Position_OuterEnd); positionToDisplay = Math.max(positionToDisplay, _Position_OuterBegin); if (!_Hooked || _NoStop || force || positionToDisplay != _Position_Display) { if (toStyles) { var currentStyles = toStyles; if (fromStyles) { var interPosition = (positionToDisplay - _Position_InnerBegin) / (duration || 1); if (options.$Optimize && $Jssor$.$IsBrowserChrome() && duration) { interPosition = Math.round(interPosition / 16 * duration) * 16 / duration; } if (options.$Reverse) interPosition = 1 - interPosition; currentStyles = {}; for (var key in toStyles) { var round = _SubRounds[key] || 1; var during = _SubDurings[key] || [0, 1]; var propertyInterPosition = (interPosition - during[0]) / during[1]; propertyInterPosition = Math.min(Math.max(propertyInterPosition, 0), 1); propertyInterPosition = propertyInterPosition * round; var floorPosition = Math.floor(propertyInterPosition); if (propertyInterPosition != floorPosition) propertyInterPosition -= floorPosition; var easing = _SubEasings[key] || _SubEasings.$Default; var easingValue = easing(propertyInterPosition); var currentPropertyValue; var value = fromStyles[key]; var toValue = toStyles[key]; if ($Jssor$.$IsNumeric(toValue)) { currentPropertyValue = value + (toValue - value) * easingValue; } else { currentPropertyValue = $Jssor$.$Extend({ $Offset: {} }, fromStyles[key]); $Jssor$.$Each(toValue.$Offset, function (rectX, n) { var offsetValue = rectX * easingValue; currentPropertyValue.$Offset[n] = offsetValue; currentPropertyValue[n] += offsetValue; }); } currentStyles[key] = currentPropertyValue; } } if (fromStyles.$Zoom) { currentStyles.$Transform = { $Rotate: currentStyles.$Rotate || 0, $Scale: currentStyles.$Zoom, $OriginalWidth: options.$OriginalWidth, $OriginalHeight: options.$OriginalHeight }; } if (toStyles.$Clip && options.$Move) { var styleFrameNClipOffset = currentStyles.$Clip.$Offset; var offsetY = (styleFrameNClipOffset.$Top || 0) + (styleFrameNClipOffset.$Bottom || 0); var offsetX = (styleFrameNClipOffset.$Left || 0) + (styleFrameNClipOffset.$Right || 0); currentStyles.$Left = (currentStyles.$Left || 0) + offsetX; currentStyles.$Top = (currentStyles.$Top || 0) + offsetY; currentStyles.$Clip.$Left -= offsetX; currentStyles.$Clip.$Right -= offsetX; currentStyles.$Clip.$Top -= offsetY; currentStyles.$Clip.$Bottom -= offsetY; } if (currentStyles.$Clip && $Jssor$.$CanClearClip() && !currentStyles.$Clip.$Top && !currentStyles.$Clip.$Left && (currentStyles.$Clip.$Right == options.$OriginalWidth) && (currentStyles.$Clip.$Bottom == options.$OriginalHeight)) currentStyles.$Clip = null; $Jssor$.$Each(currentStyles, function (value, key) { _StyleSetter[key] && _StyleSetter[key](elmt, value); }); } _ThisAnimator.$OnInnerOffsetChange(_Position_Display - _Position_InnerBegin, positionToDisplay - _Position_InnerBegin); } _Position_Display = positionToDisplay; $Jssor$.$Each(_NestedAnimators, function (animator, i) { var nestedAnimator = positionOuter < _Position_Current ? _NestedAnimators[_NestedAnimators.length - i - 1] : animator; nestedAnimator.$GoToPosition(positionOuter, force); }); var positionOld = _Position_Current; var positionNew = positionOuter; _Position_Current = trimedPositionOuter; _Hooked = true; _ThisAnimator.$OnPositionChange(positionOld, positionNew); } } function Join(animator, combineMode) { /// /// Combine another animator as nested animator /// /// /// An instance of $JssorAnimator$ /// /// /// 0: parallel - place the animator parallel to this animator. /// 1: chain - chain the animator at the _Position_InnerEnd of this animator. /// $JssorDebug$.$Execute(function () { if (combineMode !== 0 && combineMode !== 1) $JssorDebug$.$Fail("Argument out of range, the value of 'combineMode' should be either 0 or 1."); }); if (combineMode) animator.$Locate(_Position_OuterEnd, 1); _Position_OuterEnd = Math.max(_Position_OuterEnd, animator.$GetPosition_OuterEnd()); _NestedAnimators.push(animator); } function PlayFrame() { if (_AutoPlay) { var now = $Jssor$.$GetNow(); //var timeOffset = Math.min(now - _TimeStampLastFrame, $Jssor$.$IsBrowserOpera() ? 80 : 20); var timeOffset = Math.min(now - _TimeStampLastFrame, 80); var timePosition = _Position_Current + timeOffset * _PlayDirection; _TimeStampLastFrame = now; if (timePosition * _PlayDirection >= _PlayToPosition * _PlayDirection) timePosition = _PlayToPosition; GoToPosition(timePosition); if (!_NoStop && timePosition * _PlayDirection >= _PlayToPosition * _PlayDirection) { Stop(_Callback); } else { $Jssor$.$Delay(PlayFrame, options.$Interval); } } } function PlayToPosition(toPosition, callback, noStop) { if (!_AutoPlay) { _AutoPlay = true; _NoStop = noStop _Callback = callback; toPosition = Math.max(toPosition, _Position_OuterBegin); toPosition = Math.min(toPosition, _Position_OuterEnd); _PlayToPosition = toPosition; _PlayDirection = _PlayToPosition < _Position_Current ? -1 : 1; _ThisAnimator.$OnStart(); _TimeStampLastFrame = $Jssor$.$GetNow(); PlayFrame(); } } function Stop(callback) { if (_AutoPlay) { _NoStop = _AutoPlay = _Callback = false; _ThisAnimator.$OnStop(); if (callback) callback(); } } _ThisAnimator.$Play = function (positionLength, callback, noStop) { PlayToPosition(positionLength ? _Position_Current + positionLength : _Position_OuterEnd, callback, noStop); }; _ThisAnimator.$PlayToPosition = PlayToPosition; _ThisAnimator.$PlayToBegin = function (callback, noStop) { PlayToPosition(_Position_OuterBegin, callback, noStop); }; _ThisAnimator.$PlayToEnd = function (callback, noStop) { PlayToPosition(_Position_OuterEnd, callback, noStop); }; _ThisAnimator.$Stop = Stop; _ThisAnimator.$Continue = function (toPosition) { PlayToPosition(toPosition); }; _ThisAnimator.$GetPosition = function () { return _Position_Current; }; _ThisAnimator.$GetPlayToPosition = function () { return _PlayToPosition; }; _ThisAnimator.$GetPosition_Display = function () { return _Position_Display; }; _ThisAnimator.$GoToPosition = GoToPosition; _ThisAnimator.$GoToBegin = function () { GoToPosition(_Position_OuterBegin, true); }; _ThisAnimator.$GoToEnd = function () { GoToPosition(_Position_OuterEnd, true); }; _ThisAnimator.$Move = function (offset) { GoToPosition(_Position_Current + offset); }; _ThisAnimator.$CombineMode = function () { return _CombineMode; }; _ThisAnimator.$GetDuration = function () { return duration; }; _ThisAnimator.$IsPlaying = function () { return _AutoPlay; }; _ThisAnimator.$IsOnTheWay = function () { return _Position_Current > _Position_InnerBegin && _Position_Current <= _Position_InnerEnd; }; _ThisAnimator.$SetLoopLength = function (length) { _LoopLength = length; }; _ThisAnimator.$Locate = Locate; _ThisAnimator.$Shift = Shift; _ThisAnimator.$Join = Join; _ThisAnimator.$Combine = function (animator) { /// /// Combine another animator parallel to this animator /// /// /// An instance of $JssorAnimator$ /// Join(animator, 0); }; _ThisAnimator.$Chain = function (animator) { /// /// Chain another animator at the _Position_InnerEnd of this animator /// /// /// An instance of $JssorAnimator$ /// Join(animator, 1); }; _ThisAnimator.$GetPosition_InnerBegin = function () { /// /// Internal member function, do not use it. /// /// /// return _Position_InnerBegin; }; _ThisAnimator.$GetPosition_InnerEnd = function () { /// /// Internal member function, do not use it. /// /// /// return _Position_InnerEnd; }; _ThisAnimator.$GetPosition_OuterBegin = function () { /// /// Internal member function, do not use it. /// /// /// return _Position_OuterBegin; }; _ThisAnimator.$GetPosition_OuterEnd = function () { /// /// Internal member function, do not use it. /// /// /// return _Position_OuterEnd; }; _ThisAnimator.$OnPositionChange = _ThisAnimator.$OnStart = _ThisAnimator.$OnStop = _ThisAnimator.$OnInnerOffsetChange = $Jssor$.$EmptyFunction; _ThisAnimator.$Version = $Jssor$.$GetNow(); //Constructor 1 { options = $Jssor$.$Extend({ $Interval: 16 }, options); //Sodo statement, for development time intellisence only $JssorDebug$.$Execute(function () { options = $Jssor$.$Extend({ $LoopLength: undefined, $Setter: undefined, $Easing: undefined }, options); }); _LoopLength = options.$LoopLength; _StyleSetter = $Jssor$.$Extend({}, $Jssor$.$StyleSetter(), options.$Setter); _Position_OuterBegin = _Position_InnerBegin = delay; _Position_OuterEnd = _Position_InnerEnd = delay + duration; var _SubRounds = options.$Round || {}; var _SubDurings = options.$During || {}; _SubEasings = $Jssor$.$Extend({ $Default: $Jssor$.$IsFunction(options.$Easing) && options.$Easing || $JssorEasing$.$EaseSwing }, options.$Easing); } }; function $JssorPlayerClass$() { var _ThisPlayer = this; var _PlayerControllers = []; function PlayerController(playerElement) { var _SelfPlayerController = this; var _PlayerInstance; var _PlayerInstantces = []; function OnPlayerInstanceDataAvailable(event) { var srcElement = $Jssor$.$EventSrc(event); _PlayerInstance = srcElement.pInstance; $Jssor$.$RemoveEvent(srcElement, "dataavailable", OnPlayerInstanceDataAvailable); $Jssor$.$Each(_PlayerInstantces, function (playerInstance) { if (playerInstance != _PlayerInstance) { playerInstance.$Remove(); } }); playerElement.pTagName = _PlayerInstance.tagName; _PlayerInstantces = null; } function HandlePlayerInstance(playerInstanceElement) { var playerHandler; if (!playerInstanceElement.pInstance) { var playerHandlerAttribute = $Jssor$.$AttributeEx(playerInstanceElement, "pHandler"); if ($JssorPlayer$[playerHandlerAttribute]) { $Jssor$.$AddEvent(playerInstanceElement, "dataavailable", OnPlayerInstanceDataAvailable); playerHandler = new $JssorPlayer$[playerHandlerAttribute](playerElement, playerInstanceElement); _PlayerInstantces.push(playerHandler); $JssorDebug$.$Execute(function () { if ($Jssor$.$Type(playerHandler.$Remove) != "function") { $JssorDebug$.$Fail("'pRemove' interface not implemented for player handler '" + playerHandlerAttribute + "'."); } }); } } return playerHandler; } _SelfPlayerController.$InitPlayerController = function () { if (!playerElement.pInstance && !HandlePlayerInstance(playerElement)) { var playerInstanceElements = $Jssor$.$Children(playerElement); $Jssor$.$Each(playerInstanceElements, function (playerInstanceElement) { HandlePlayerInstance(playerInstanceElement); }); } }; } _ThisPlayer.$EVT_SWITCH = 21; _ThisPlayer.$FetchPlayers = function (elmt) { elmt = elmt || document.body; var playerElements = $Jssor$.$FindChildren(elmt, "player", null, true); $Jssor$.$Each(playerElements, function (playerElement) { if (!_PlayerControllers[playerElement.pId]) { playerElement.pId = _PlayerControllers.length; _PlayerControllers.push(new PlayerController(playerElement)); } var playerController = _PlayerControllers[playerElement.pId]; playerController.$InitPlayerController(); }); }; }pytango-9.2.2/doc/_static/jssor.slider.js000066400000000000000000005146411316324373100203750ustar00rootroot00000000000000/// /* * Jssor.Slider 18.0 * http://www.jssor.com/ * * TERMS OF USE - Jssor.Slider * * Copyright 2014 Jssor * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ var $JssorSlider$; var $JssorSlideshowFormations$ = window.$JssorSlideshowFormations$ = {}; var $JssorSlideshowRunner$; new function () { //Constants +++++++ var COLUMN_INCREASE = 0; var COLUMN_DECREASE = 1; var ROW_INCREASE = 2; var ROW_DECREASE = 3; var DIRECTION_HORIZONTAL = 0x0003; var DIRECTION_VERTICAL = 0x000C; var TO_LEFT = 0x0001; var TO_RIGHT = 0x0002; var TO_TOP = 0x0004; var TO_BOTTOM = 0x0008; var FROM_LEFT = 0x0100; var FROM_TOP = 0x0200; var FROM_RIGHT = 0x0400; var FROM_BOTTOM = 0x0800; var ASSEMBLY_BOTTOM_LEFT = FROM_BOTTOM + TO_LEFT; var ASSEMBLY_BOTTOM_RIGHT = FROM_BOTTOM + TO_RIGHT; var ASSEMBLY_TOP_LEFT = FROM_TOP + TO_LEFT; var ASSEMBLY_TOP_RIGHT = FROM_TOP + TO_RIGHT; var ASSEMBLY_LEFT_TOP = FROM_LEFT + TO_TOP; var ASSEMBLY_LEFT_BOTTOM = FROM_LEFT + TO_BOTTOM; var ASSEMBLY_RIGHT_TOP = FROM_RIGHT + TO_TOP; var ASSEMBLY_RIGHT_BOTTOM = FROM_RIGHT + TO_BOTTOM; //Constants ------- //Formation Definition +++++++ function isToLeft(roadValue) { return (roadValue & TO_LEFT) == TO_LEFT; } function isToRight(roadValue) { return (roadValue & TO_RIGHT) == TO_RIGHT; } function isToTop(roadValue) { return (roadValue & TO_TOP) == TO_TOP; } function isToBottom(roadValue) { return (roadValue & TO_BOTTOM) == TO_BOTTOM; } function PushFormationOrder(arr, order, formationItem) { formationItem.push(order); arr[order] = arr[order] || []; arr[order].push(formationItem); } $JssorSlideshowFormations$.$FormationStraight = function (transition) { var cols = transition.$Cols; var rows = transition.$Rows; var formationDirection = transition.$Assembly; var count = transition.$Count; var a = []; var i = 0; var col = 0; var r = 0; var cl = cols - 1; var rl = rows - 1; var il = count - 1; var cr; var order; for (r = 0; r < rows; r++) { for (col = 0; col < cols; col++) { cr = r + ',' + col; switch (formationDirection) { case ASSEMBLY_BOTTOM_LEFT: order = il - (col * rows + (rl - r)); break; case ASSEMBLY_RIGHT_TOP: order = il - (r * cols + (cl - col)); break; case ASSEMBLY_TOP_LEFT: order = il - (col * rows + r); case ASSEMBLY_LEFT_TOP: order = il - (r * cols + col); break; case ASSEMBLY_BOTTOM_RIGHT: order = col * rows + r; break; case ASSEMBLY_LEFT_BOTTOM: order = r * cols + (cl - col); break; case ASSEMBLY_TOP_RIGHT: order = col * rows + (rl - r); break; default: order = r * cols + col; break; //ASSEMBLY_RIGHT_BOTTOM } PushFormationOrder(a, order, [r, col]); } } return a; }; $JssorSlideshowFormations$.$FormationSwirl = function (transition) { var cols = transition.$Cols; var rows = transition.$Rows; var formationDirection = transition.$Assembly; var count = transition.$Count; var a = []; var hit = []; var i = 0; var col = 0; var r = 0; var cl = cols - 1; var rl = rows - 1; var il = count - 1; var cr; var courses; var course = 0; switch (formationDirection) { case ASSEMBLY_BOTTOM_LEFT: col = cl; r = 0; courses = [ROW_INCREASE, COLUMN_DECREASE, ROW_DECREASE, COLUMN_INCREASE]; break; case ASSEMBLY_RIGHT_TOP: col = 0; r = rl; courses = [COLUMN_INCREASE, ROW_DECREASE, COLUMN_DECREASE, ROW_INCREASE]; break; case ASSEMBLY_TOP_LEFT: col = cl; r = rl; courses = [ROW_DECREASE, COLUMN_DECREASE, ROW_INCREASE, COLUMN_INCREASE]; break; case ASSEMBLY_LEFT_TOP: col = cl; r = rl; courses = [COLUMN_DECREASE, ROW_DECREASE, COLUMN_INCREASE, ROW_INCREASE]; break; case ASSEMBLY_BOTTOM_RIGHT: col = 0; r = 0; courses = [ROW_INCREASE, COLUMN_INCREASE, ROW_DECREASE, COLUMN_DECREASE]; break; case ASSEMBLY_LEFT_BOTTOM: col = cl; r = 0; courses = [COLUMN_DECREASE, ROW_INCREASE, COLUMN_INCREASE, ROW_DECREASE]; break; case ASSEMBLY_TOP_RIGHT: col = 0; r = rl; courses = [ROW_DECREASE, COLUMN_INCREASE, ROW_INCREASE, COLUMN_DECREASE]; break; default: col = 0; r = 0; courses = [COLUMN_INCREASE, ROW_INCREASE, COLUMN_DECREASE, ROW_DECREASE]; break; //ASSEMBLY_RIGHT_BOTTOM } i = 0; while (i < count) { cr = r + ',' + col; if (col >= 0 && col < cols && r >= 0 && r < rows && !hit[cr]) { //a[cr] = i++; hit[cr] = true; PushFormationOrder(a, i++, [r, col]); } else { switch (courses[course++ % courses.length]) { case COLUMN_INCREASE: col--; break; case ROW_INCREASE: r--; break; case COLUMN_DECREASE: col++; break; case ROW_DECREASE: r++; break; } } switch (courses[course % courses.length]) { case COLUMN_INCREASE: col++; break; case ROW_INCREASE: r++; break; case COLUMN_DECREASE: col--; break; case ROW_DECREASE: r--; break; } } return a; }; $JssorSlideshowFormations$.$FormationZigZag = function (transition) { var cols = transition.$Cols; var rows = transition.$Rows; var formationDirection = transition.$Assembly; var count = transition.$Count; var a = []; var i = 0; var col = 0; var r = 0; var cl = cols - 1; var rl = rows - 1; var il = count - 1; var cr; var courses; var course = 0; switch (formationDirection) { case ASSEMBLY_BOTTOM_LEFT: col = cl; r = 0; courses = [ROW_INCREASE, COLUMN_DECREASE, ROW_DECREASE, COLUMN_DECREASE]; break; case ASSEMBLY_RIGHT_TOP: col = 0; r = rl; courses = [COLUMN_INCREASE, ROW_DECREASE, COLUMN_DECREASE, ROW_DECREASE]; break; case ASSEMBLY_TOP_LEFT: col = cl; r = rl; courses = [ROW_DECREASE, COLUMN_DECREASE, ROW_INCREASE, COLUMN_DECREASE]; break; case ASSEMBLY_LEFT_TOP: col = cl; r = rl; courses = [COLUMN_DECREASE, ROW_DECREASE, COLUMN_INCREASE, ROW_DECREASE]; break; case ASSEMBLY_BOTTOM_RIGHT: col = 0; r = 0; courses = [ROW_INCREASE, COLUMN_INCREASE, ROW_DECREASE, COLUMN_INCREASE]; break; case ASSEMBLY_LEFT_BOTTOM: col = cl; r = 0; courses = [COLUMN_DECREASE, ROW_INCREASE, COLUMN_INCREASE, ROW_INCREASE]; break; case ASSEMBLY_TOP_RIGHT: col = 0; r = rl; courses = [ROW_DECREASE, COLUMN_INCREASE, ROW_INCREASE, COLUMN_INCREASE]; break; default: col = 0; r = 0; courses = [COLUMN_INCREASE, ROW_INCREASE, COLUMN_DECREASE, ROW_INCREASE]; break; //ASSEMBLY_RIGHT_BOTTOM } i = 0; while (i < count) { cr = r + ',' + col; if (col >= 0 && col < cols && r >= 0 && r < rows && typeof (a[cr]) == 'undefined') { PushFormationOrder(a, i++, [r, col]); //a[cr] = i++; switch (courses[course % courses.length]) { case COLUMN_INCREASE: col++; break; case ROW_INCREASE: r++; break; case COLUMN_DECREASE: col--; break; case ROW_DECREASE: r--; break; } } else { switch (courses[course++ % courses.length]) { case COLUMN_INCREASE: col--; break; case ROW_INCREASE: r--; break; case COLUMN_DECREASE: col++; break; case ROW_DECREASE: r++; break; } switch (courses[course++ % courses.length]) { case COLUMN_INCREASE: col++; break; case ROW_INCREASE: r++; break; case COLUMN_DECREASE: col--; break; case ROW_DECREASE: r--; break; } } } return a; }; $JssorSlideshowFormations$.$FormationStraightStairs = function (transition) { var cols = transition.$Cols; var rows = transition.$Rows; var formationDirection = transition.$Assembly; var count = transition.$Count; var a = []; var i = 0; var col = 0; var r = 0; var cl = cols - 1; var rl = rows - 1; var il = count - 1; var cr; switch (formationDirection) { case ASSEMBLY_BOTTOM_LEFT: case ASSEMBLY_TOP_RIGHT: case ASSEMBLY_TOP_LEFT: case ASSEMBLY_BOTTOM_RIGHT: var C = 0; var R = 0; break; case ASSEMBLY_LEFT_BOTTOM: case ASSEMBLY_RIGHT_TOP: case ASSEMBLY_LEFT_TOP: case ASSEMBLY_RIGHT_BOTTOM: var C = cl; var R = 0; break; default: formationDirection = ASSEMBLY_RIGHT_BOTTOM; var C = cl; var R = 0; break; } col = C; r = R; while (i < count) { cr = r + ',' + col; if (isToTop(formationDirection) || isToRight(formationDirection)) { PushFormationOrder(a, il - i++, [r, col]); //a[cr] = il - i++; } else { PushFormationOrder(a, i++, [r, col]); //a[cr] = i++; } switch (formationDirection) { case ASSEMBLY_BOTTOM_LEFT: case ASSEMBLY_TOP_RIGHT: col--; r++; break; case ASSEMBLY_TOP_LEFT: case ASSEMBLY_BOTTOM_RIGHT: col++; r--; break; case ASSEMBLY_LEFT_BOTTOM: case ASSEMBLY_RIGHT_TOP: col--; r--; break; case ASSEMBLY_RIGHT_BOTTOM: case ASSEMBLY_LEFT_TOP: default: col++; r++; break; } if (col < 0 || r < 0 || col > cl || r > rl) { switch (formationDirection) { case ASSEMBLY_BOTTOM_LEFT: case ASSEMBLY_TOP_RIGHT: C++; break; case ASSEMBLY_LEFT_BOTTOM: case ASSEMBLY_RIGHT_TOP: case ASSEMBLY_TOP_LEFT: case ASSEMBLY_BOTTOM_RIGHT: R++; break; case ASSEMBLY_RIGHT_BOTTOM: case ASSEMBLY_LEFT_TOP: default: C--; break; } if (C < 0 || R < 0 || C > cl || R > rl) { switch (formationDirection) { case ASSEMBLY_BOTTOM_LEFT: case ASSEMBLY_TOP_RIGHT: C = cl; R++; break; case ASSEMBLY_TOP_LEFT: case ASSEMBLY_BOTTOM_RIGHT: R = rl; C++; break; case ASSEMBLY_LEFT_BOTTOM: case ASSEMBLY_RIGHT_TOP: R = rl; C--; break; case ASSEMBLY_RIGHT_BOTTOM: case ASSEMBLY_LEFT_TOP: default: C = 0; R++; break; } if (R > rl) R = rl; else if (R < 0) R = 0; else if (C > cl) C = cl; else if (C < 0) C = 0; } r = R; col = C; } } return a; }; $JssorSlideshowFormations$.$FormationSquare = function (transition) { var cols = transition.$Cols || 1; var rows = transition.$Rows || 1; var arr = []; var i = 0; var col; var r; var dc; var dr; var cr; dc = cols < rows ? (rows - cols) / 2 : 0; dr = cols > rows ? (cols - rows) / 2 : 0; cr = Math.round(Math.max(cols / 2, rows / 2)) + 1; for (col = 0; col < cols; col++) { for (r = 0; r < rows; r++) PushFormationOrder(arr, cr - Math.min(col + 1 + dc, r + 1 + dr, cols - col + dc, rows - r + dr), [r, col]); } return arr; }; $JssorSlideshowFormations$.$FormationRectangle = function (transition) { var cols = transition.$Cols || 1; var rows = transition.$Rows || 1; var arr = []; var i = 0; var col; var r; var cr; cr = Math.round(Math.min(cols / 2, rows / 2)) + 1; for (col = 0; col < cols; col++) { for (r = 0; r < rows; r++) PushFormationOrder(arr, cr - Math.min(col + 1, r + 1, cols - col, rows - r), [r, col]); } return arr; }; $JssorSlideshowFormations$.$FormationRandom = function (transition) { var a = []; var r, col, i; for (r = 0; r < transition.$Rows; r++) { for (col = 0; col < transition.$Cols; col++) PushFormationOrder(a, Math.ceil(100000 * Math.random()) % 13, [r, col]); } return a; }; $JssorSlideshowFormations$.$FormationCircle = function (transition) { var cols = transition.$Cols || 1; var rows = transition.$Rows || 1; var arr = []; var i = 0; var col; var r; var hc = cols / 2 - 0.5; var hr = rows / 2 - 0.5; for (col = 0; col < cols; col++) { for (r = 0; r < rows; r++) PushFormationOrder(arr, Math.round(Math.sqrt(Math.pow(col - hc, 2) + Math.pow(r - hr, 2))), [r, col]); } return arr; }; $JssorSlideshowFormations$.$FormationCross = function (transition) { var cols = transition.$Cols || 1; var rows = transition.$Rows || 1; var arr = []; var i = 0; var col; var r; var hc = cols / 2 - 0.5; var hr = rows / 2 - 0.5; for (col = 0; col < cols; col++) { for (r = 0; r < rows; r++) PushFormationOrder(arr, Math.round(Math.min(Math.abs(col - hc), Math.abs(r - hr))), [r, col]); } return arr; }; $JssorSlideshowFormations$.$FormationRectangleCross = function (transition) { var cols = transition.$Cols || 1; var rows = transition.$Rows || 1; var arr = []; var i = 0; var col; var r; var hc = cols / 2 - 0.5; var hr = rows / 2 - 0.5; var cr = Math.max(hc, hr) + 1; for (col = 0; col < cols; col++) { for (r = 0; r < rows; r++) PushFormationOrder(arr, Math.round(cr - Math.max(hc - Math.abs(col - hc), hr - Math.abs(r - hr))) - 1, [r, col]); } return arr; }; function GetFormation(transition) { var formationInstance = transition.$Formation(transition); return transition.$Reverse ? formationInstance.reverse() : formationInstance; } //GetFormation //var _PrototypeTransitions = []; function EnsureTransitionInstance(options, slideshowInterval) { var _SlideshowTransition = { $Interval: slideshowInterval, //Delay to play next frame $Duration: 1, //Duration to finish the entire transition $Delay: 0, //Delay to assembly blocks $Cols: 1, //Number of columns $Rows: 1, //Number of rows $Opacity: 0, //Fade block or not $Zoom: 0, //Zoom block or not $Clip: 0, //Clip block or not $Move: false, //Move block or not $SlideOut: false, //Slide the previous slide out to display next slide instead //$FlyDirection: 0, //Specify fly transform with direction $Reverse: false, //Reverse the assembly or not $Formation: $JssorSlideshowFormations$.$FormationRandom, //Shape that assembly blocks as $Assembly: ASSEMBLY_RIGHT_BOTTOM, //The way to assembly blocks $ChessMode: { $Column: 0, $Row: 0 }, //Chess move or fly direction $Easing: $JssorEasing$.$EaseSwing, //Specify variation of speed during transition $Round: {}, $Blocks: [], $During: {} }; $Jssor$.$Extend(_SlideshowTransition, options); _SlideshowTransition.$Count = _SlideshowTransition.$Cols * _SlideshowTransition.$Rows; if ($Jssor$.$IsFunction(_SlideshowTransition.$Easing)) _SlideshowTransition.$Easing = { $Default: _SlideshowTransition.$Easing }; _SlideshowTransition.$FramesCount = Math.ceil(_SlideshowTransition.$Duration / _SlideshowTransition.$Interval); _SlideshowTransition.$EasingInstance = GetEasing(_SlideshowTransition); _SlideshowTransition.$GetBlocks = function (width, height) { width /= _SlideshowTransition.$Cols; height /= _SlideshowTransition.$Rows; var wh = width + 'x' + height; if (!_SlideshowTransition.$Blocks[wh]) { _SlideshowTransition.$Blocks[wh] = { $Width: width, $Height: height }; for (var col = 0; col < _SlideshowTransition.$Cols; col++) { for (var r = 0; r < _SlideshowTransition.$Rows; r++) _SlideshowTransition.$Blocks[wh][r + ',' + col] = { $Top: r * height, $Right: col * width + width, $Bottom: r * height + height, $Left: col * width }; } } return _SlideshowTransition.$Blocks[wh]; }; if (_SlideshowTransition.$Brother) { _SlideshowTransition.$Brother = EnsureTransitionInstance(_SlideshowTransition.$Brother, slideshowInterval); _SlideshowTransition.$SlideOut = true; } return _SlideshowTransition; } function GetEasing(transition) { var easing = transition.$Easing; if (!easing.$Default) easing.$Default = $JssorEasing$.$EaseSwing; var duration = transition.$FramesCount; var cache = easing.$Cache; if (!cache) { var enumerator = $Jssor$.$Extend({}, transition.$Easing, transition.$Round); cache = easing.$Cache = {}; $Jssor$.$Each(enumerator, function (v, easingName) { var easingFunction = easing[easingName] || easing.$Default; var round = transition.$Round[easingName] || 1; if (!$Jssor$.$IsArray(easingFunction.$Cache)) easingFunction.$Cache = []; var easingFunctionCache = easingFunction.$Cache[duration] = easingFunction.$Cache[duration] || []; if (!easingFunctionCache[round]) { easingFunctionCache[round] = [0]; for (var t = 1; t <= duration; t++) { var tRound = t / duration * round; var tRoundFloor = Math.floor(tRound); if (tRound != tRoundFloor) tRound -= tRoundFloor; easingFunctionCache[round][t] = easingFunction(tRound); } } cache[easingName] = easingFunctionCache; }); } return cache; } //GetEasing //Formation Definition ------- function JssorSlideshowPlayer(slideContainer, slideElement, slideTransition, beginTime, slideContainerWidth, slideContainerHeight) { var _Self = this; var _Block; var _StartStylesArr = {}; var _AnimationStylesArrs = {}; var _AnimationBlockItems = []; var _StyleStart; var _StyleEnd; var _StyleDif; var _ChessModeColumn = slideTransition.$ChessMode.$Column || 0; var _ChessModeRow = slideTransition.$ChessMode.$Row || 0; var _Blocks = slideTransition.$GetBlocks(slideContainerWidth, slideContainerHeight); var _FormationInstance = GetFormation(slideTransition); var _MaxOrder = _FormationInstance.length - 1; var _Period = slideTransition.$Duration + slideTransition.$Delay * _MaxOrder; var _EndTime = beginTime + _Period; var _SlideOut = slideTransition.$SlideOut; var _IsIn; _EndTime += $Jssor$.$IsBrowserChrome() ? 260 : 50; _Self.$EndTime = _EndTime; _Self.$ShowFrame = function (time) { time -= beginTime; var isIn = time < _Period; if (isIn || _IsIn) { _IsIn = isIn; if (!_SlideOut) time = _Period - time; var frameIndex = Math.ceil(time / slideTransition.$Interval); $Jssor$.$Each(_AnimationStylesArrs, function (value, index) { var itemFrameIndex = Math.max(frameIndex, value.$Min); itemFrameIndex = Math.min(itemFrameIndex, value.length - 1); if (value.$LastFrameIndex != itemFrameIndex) { if (!value.$LastFrameIndex && !_SlideOut) { $Jssor$.$ShowElement(_AnimationBlockItems[index]); } else if (itemFrameIndex == value.$Max && _SlideOut) { $Jssor$.$HideElement(_AnimationBlockItems[index]); } value.$LastFrameIndex = itemFrameIndex; $Jssor$.$SetStylesEx(_AnimationBlockItems[index], value[itemFrameIndex]); } }); } }; function DisableHWA(elmt) { $Jssor$.$DisableHWA(elmt); var children = $Jssor$.$Children(elmt); $Jssor$.$Each(children, function (child) { DisableHWA(child); }); } //constructor { slideElement = $Jssor$.$CloneNode(slideElement, true); DisableHWA(slideElement); if ($Jssor$.$IsBrowserIe9Earlier()) { var hasImage = !slideElement["no-image"]; var slideChildElements = $Jssor$.$FindChildrenByTag(slideElement, null, true); $Jssor$.$Each(slideChildElements, function (slideChildElement) { if (hasImage || slideChildElement["jssor-slider"]) $Jssor$.$CssOpacity(slideChildElement, $Jssor$.$CssOpacity(slideChildElement), true); }); } $Jssor$.$Each(_FormationInstance, function (formationItems, order) { $Jssor$.$Each(formationItems, function (formationItem) { var row = formationItem[0]; var col = formationItem[1]; { var columnRow = row + ',' + col; var chessHorizontal = false; var chessVertical = false; var chessRotate = false; if (_ChessModeColumn && col % 2) { if ($JssorDirection$.$IsHorizontal(_ChessModeColumn)) { chessHorizontal = !chessHorizontal; } if ($JssorDirection$.$IsVertical(_ChessModeColumn)) { chessVertical = !chessVertical; } if (_ChessModeColumn & 16) chessRotate = !chessRotate; } if (_ChessModeRow && row % 2) { if ($JssorDirection$.$IsHorizontal(_ChessModeRow)) { chessHorizontal = !chessHorizontal; } if ($JssorDirection$.$IsVertical(_ChessModeRow)) { chessVertical = !chessVertical; } if (_ChessModeRow & 16) chessRotate = !chessRotate; } slideTransition.$Top = slideTransition.$Top || (slideTransition.$Clip & 4); slideTransition.$Bottom = slideTransition.$Bottom || (slideTransition.$Clip & 8); slideTransition.$Left = slideTransition.$Left || (slideTransition.$Clip & 1); slideTransition.$Right = slideTransition.$Right || (slideTransition.$Clip & 2); var topBenchmark = chessVertical ? slideTransition.$Bottom : slideTransition.$Top; var bottomBenchmark = chessVertical ? slideTransition.$Top : slideTransition.$Bottom; var leftBenchmark = chessHorizontal ? slideTransition.$Right : slideTransition.$Left; var rightBenchmark = chessHorizontal ? slideTransition.$Left : slideTransition.$Right; //$JssorDebug$.$Execute(function () { // topBenchmark = bottomBenchmark = leftBenchmark = rightBenchmark = false; //}); slideTransition.$Clip = topBenchmark || bottomBenchmark || leftBenchmark || rightBenchmark; _StyleDif = {}; _StyleEnd = { $Top: 0, $Left: 0, $Opacity: 1, $Width: slideContainerWidth, $Height: slideContainerHeight }; _StyleStart = $Jssor$.$Extend({}, _StyleEnd); _Block = $Jssor$.$Extend({}, _Blocks[columnRow]); if (slideTransition.$Opacity) { _StyleEnd.$Opacity = 2 - slideTransition.$Opacity; } if (slideTransition.$ZIndex) { _StyleEnd.$ZIndex = slideTransition.$ZIndex; _StyleStart.$ZIndex = 0; } var allowClip = slideTransition.$Cols * slideTransition.$Rows > 1 || slideTransition.$Clip; if (slideTransition.$Zoom || slideTransition.$Rotate) { var allowRotate = true; if ($Jssor$.$IsBrowserIE() && $Jssor$.$BrowserEngineVersion() < 9) { if (slideTransition.$Cols * slideTransition.$Rows > 1) allowRotate = false; else allowClip = false; } if (allowRotate) { _StyleEnd.$Zoom = slideTransition.$Zoom ? slideTransition.$Zoom - 1 : 1; _StyleStart.$Zoom = 1; if ($Jssor$.$IsBrowserIe9Earlier() || $Jssor$.$IsBrowserOpera()) _StyleEnd.$Zoom = Math.min(_StyleEnd.$Zoom, 2); var rotate = slideTransition.$Rotate; //if (rotate == true) // rotate = 1; _StyleEnd.$Rotate = rotate * 360 * ((chessRotate) ? -1 : 1); _StyleStart.$Rotate = 0; } } if (allowClip) { if (slideTransition.$Clip) { var clipScale = slideTransition.$ScaleClip || 1; var blockOffset = _Block.$Offset = {}; if (topBenchmark && bottomBenchmark) { blockOffset.$Top = _Blocks.$Height / 2 * clipScale; blockOffset.$Bottom = -blockOffset.$Top; } else if (topBenchmark) { blockOffset.$Bottom = -_Blocks.$Height * clipScale; } else if (bottomBenchmark) { blockOffset.$Top = _Blocks.$Height * clipScale; } if (leftBenchmark && rightBenchmark) { blockOffset.$Left = _Blocks.$Width / 2 * clipScale; blockOffset.$Right = -blockOffset.$Left; } else if (leftBenchmark) { blockOffset.$Right = -_Blocks.$Width * clipScale; } else if (rightBenchmark) { blockOffset.$Left = _Blocks.$Width * clipScale; } } _StyleDif.$Clip = _Block; _StyleStart.$Clip = _Blocks[columnRow]; } //fly { var chessHor = chessHorizontal ? 1 : -1; var chessVer = chessVertical ? 1 : -1; if (slideTransition.x) _StyleEnd.$Left += slideContainerWidth * slideTransition.x * chessHor; if (slideTransition.y) _StyleEnd.$Top += slideContainerHeight * slideTransition.y * chessVer; } $Jssor$.$Each(_StyleEnd, function (propertyEnd, property) { if ($Jssor$.$IsNumeric(propertyEnd)) { if (propertyEnd != _StyleStart[property]) { _StyleDif[property] = propertyEnd - _StyleStart[property]; } } }); _StartStylesArr[columnRow] = _SlideOut ? _StyleStart : _StyleEnd; var animationStylesArr = []; var virtualFrameCount = Math.round(order * slideTransition.$Delay / slideTransition.$Interval); _AnimationStylesArrs[columnRow] = new Array(virtualFrameCount); _AnimationStylesArrs[columnRow].$Min = virtualFrameCount; var framesCount = slideTransition.$FramesCount; for (var frameN = 0; frameN <= framesCount; frameN++) { var styleFrameN = {}; $Jssor$.$Each(_StyleDif, function (propertyDiff, property) { var propertyEasings = slideTransition.$EasingInstance[property] || slideTransition.$EasingInstance.$Default; var propertyEasingArray = propertyEasings[slideTransition.$Round[property] || 1]; var propertyDuring = slideTransition.$During[property] || [0, 1]; var propertyFrameN = (frameN / framesCount - propertyDuring[0]) / propertyDuring[1] * framesCount; propertyFrameN = Math.round(Math.min(framesCount, Math.max(propertyFrameN, 0))); var propertyEasingValue = propertyEasingArray[propertyFrameN]; if ($Jssor$.$IsNumeric(propertyDiff)) { styleFrameN[property] = _StyleStart[property] + propertyDiff * propertyEasingValue; } else { var value = styleFrameN[property] = $Jssor$.$Extend({}, _StyleStart[property]); value.$Offset = []; $Jssor$.$Each(propertyDiff.$Offset, function (rectX, n) { var offsetValue = rectX * propertyEasingValue; value.$Offset[n] = offsetValue; value[n] += offsetValue; }); } }); if (_StyleStart.$Zoom) { styleFrameN.$Transform = { $Rotate: styleFrameN.$Rotate || 0, $Scale: styleFrameN.$Zoom, $OriginalWidth: slideContainerWidth, $OriginalHeight: slideContainerHeight }; } if (styleFrameN.$Clip && slideTransition.$Move) { var styleFrameNClipOffset = styleFrameN.$Clip.$Offset; var offsetY = (styleFrameNClipOffset.$Top || 0) + (styleFrameNClipOffset.$Bottom || 0); var offsetX = (styleFrameNClipOffset.$Left || 0) + (styleFrameNClipOffset.$Right || 0); styleFrameN.$Left = (styleFrameN.$Left || 0) + offsetX; styleFrameN.$Top = (styleFrameN.$Top || 0) + offsetY; styleFrameN.$Clip.$Left -= offsetX; styleFrameN.$Clip.$Right -= offsetX; styleFrameN.$Clip.$Top -= offsetY; styleFrameN.$Clip.$Bottom -= offsetY; } styleFrameN.$ZIndex = styleFrameN.$ZIndex || 1; _AnimationStylesArrs[columnRow].push(styleFrameN); } } //for }); }); _FormationInstance.reverse(); $Jssor$.$Each(_FormationInstance, function (formationItems) { $Jssor$.$Each(formationItems, function (formationItem) { var row = formationItem[0]; var col = formationItem[1]; var columnRow = row + ',' + col; var image = slideElement; if (col || row) image = $Jssor$.$CloneNode(slideElement, true); $Jssor$.$SetStyles(image, _StartStylesArr[columnRow]); $Jssor$.$CssOverflow(image, "hidden"); $Jssor$.$CssPosition(image, "absolute"); slideContainer.$AddClipElement(image); _AnimationBlockItems[columnRow] = image; $Jssor$.$ShowElement(image, _SlideOut); }); }); } } //JssorSlideshowRunner++++++++ var _SlideshowRunnerCount = 1; $JssorSlideshowRunner$ = window.$JssorSlideshowRunner$ = function (slideContainer, slideContainerWidth, slideContainerHeight, slideshowOptions, handleTouchEventOnly) { var _SelfSlideshowRunner = this; //var _State = 0; //-1 fullfill, 0 clean, 1 initializing, 2 stay, 3 playing var _EndTime; var _SliderFrameCount; var _SlideshowPlayerBelow; var _SlideshowPlayerAbove; var _PrevItem; var _SlideItem; var _TransitionIndex = 0; var _TransitionsOrder = slideshowOptions.$TransitionsOrder; var _SlideshowTransition; var _SlideshowPerformance = 16; function SlideshowProcessor() { var _SelfSlideshowProcessor = this; var _CurrentTime = 0; $JssorAnimator$.call(_SelfSlideshowProcessor, 0, _EndTime); _SelfSlideshowProcessor.$OnPositionChange = function (oldPosition, newPosition) { if ((newPosition - _CurrentTime) > _SlideshowPerformance) { _CurrentTime = newPosition; _SlideshowPlayerAbove && _SlideshowPlayerAbove.$ShowFrame(newPosition); _SlideshowPlayerBelow && _SlideshowPlayerBelow.$ShowFrame(newPosition); } }; _SelfSlideshowProcessor.$Transition = _SlideshowTransition; } //member functions _SelfSlideshowRunner.$GetTransition = function (slideCount) { var n = 0; var transitions = slideshowOptions.$Transitions; var transitionCount = transitions.length; if (_TransitionsOrder) { /*Sequence*/ if (transitionCount > slideCount && ($Jssor$.$IsBrowserChrome() || $Jssor$.$IsBrowserSafari() || $Jssor$.$IsBrowserFireFox())) { transitionCount -= transitionCount % slideCount; } n = _TransitionIndex++ % transitionCount; } else { /*Random*/ n = Math.floor(Math.random() * transitionCount); } transitions[n] && (transitions[n].$Index = n); return transitions[n]; }; _SelfSlideshowRunner.$Initialize = function (slideIndex, prevIndex, slideItem, prevItem, slideshowTransition) { $JssorDebug$.$Execute(function () { if (_SlideshowPlayerBelow) { $JssorDebug$.$Fail("slideshow runner has not been cleared."); } }); _SlideshowTransition = slideshowTransition; slideshowTransition = EnsureTransitionInstance(slideshowTransition, _SlideshowPerformance); _SlideItem = slideItem; _PrevItem = prevItem; var prevSlideElement = prevItem.$Item; var currentSlideElement = slideItem.$Item; prevSlideElement["no-image"] = !prevItem.$Image; currentSlideElement["no-image"] = !slideItem.$Image; var slideElementAbove = prevSlideElement; var slideElementBelow = currentSlideElement; var slideTransitionAbove = slideshowTransition; var slideTransitionBelow = slideshowTransition.$Brother || EnsureTransitionInstance({}, _SlideshowPerformance); if (!slideshowTransition.$SlideOut) { slideElementAbove = currentSlideElement; slideElementBelow = prevSlideElement; } var shift = slideTransitionBelow.$Shift || 0; _SlideshowPlayerBelow = new JssorSlideshowPlayer(slideContainer, slideElementBelow, slideTransitionBelow, Math.max(shift - slideTransitionBelow.$Interval, 0), slideContainerWidth, slideContainerHeight); _SlideshowPlayerAbove = new JssorSlideshowPlayer(slideContainer, slideElementAbove, slideTransitionAbove, Math.max(slideTransitionBelow.$Interval - shift, 0), slideContainerWidth, slideContainerHeight); _SlideshowPlayerBelow.$ShowFrame(0); _SlideshowPlayerAbove.$ShowFrame(0); _EndTime = Math.max(_SlideshowPlayerBelow.$EndTime, _SlideshowPlayerAbove.$EndTime); _SelfSlideshowRunner.$Index = slideIndex; }; _SelfSlideshowRunner.$Clear = function () { slideContainer.$Clear(); _SlideshowPlayerBelow = null; _SlideshowPlayerAbove = null; }; _SelfSlideshowRunner.$GetProcessor = function () { var slideshowProcessor = null; if (_SlideshowPlayerAbove) slideshowProcessor = new SlideshowProcessor(); return slideshowProcessor; }; //Constructor { if ($Jssor$.$IsBrowserIe9Earlier() || $Jssor$.$IsBrowserOpera() || (handleTouchEventOnly && $Jssor$.$WebKitVersion() < 537)) { _SlideshowPerformance = 32; } $JssorObject$.call(_SelfSlideshowRunner); $JssorAnimator$.call(_SelfSlideshowRunner, -10000000, 10000000); $JssorDebug$.$LiveStamp(_SelfSlideshowRunner, "slideshow_runner_" + _SlideshowRunnerCount++); } }; //JssorSlideshowRunner-------- //JssorSlider function JssorSlider(elmt, options) { var _SelfSlider = this; //private classes function Conveyor() { var _SelfConveyor = this; $JssorAnimator$.call(_SelfConveyor, -100000000, 200000000); _SelfConveyor.$GetCurrentSlideInfo = function () { var positionDisplay = _SelfConveyor.$GetPosition_Display(); var virtualIndex = Math.floor(positionDisplay); var slideIndex = GetRealIndex(virtualIndex); var slidePosition = positionDisplay - Math.floor(positionDisplay); return { $Index: slideIndex, $VirtualIndex: virtualIndex, $Position: slidePosition }; }; _SelfConveyor.$OnPositionChange = function (oldPosition, newPosition) { var index = Math.floor(newPosition); if (index != newPosition && newPosition > oldPosition) index++; ResetNavigator(index, true); _SelfSlider.$TriggerEvent(JssorSlider.$EVT_POSITION_CHANGE, GetRealIndex(newPosition), GetRealIndex(oldPosition), newPosition, oldPosition); }; } //Carousel function Carousel() { var _SelfCarousel = this; $JssorAnimator$.call(_SelfCarousel, 0, 0, { $LoopLength: _SlideCount }); //Carousel Constructor { $Jssor$.$Each(_SlideItems, function (slideItem) { (_Loop & 1) && slideItem.$SetLoopLength(_SlideCount); _SelfCarousel.$Chain(slideItem); slideItem.$Shift(_ParkingPosition / _StepLength); }); } } //Carousel //Slideshow function Slideshow() { var _SelfSlideshow = this; var _Wrapper = _SlideContainer.$Elmt; $JssorAnimator$.call(_SelfSlideshow, -1, 2, { $Easing: $JssorEasing$.$EaseLinear, $Setter: { $Position: SetPosition }, $LoopLength: _SlideCount }, _Wrapper, { $Position: 1 }, { $Position: -1 }); _SelfSlideshow.$Wrapper = _Wrapper; //Slideshow Constructor { $JssorDebug$.$Execute(function () { $Jssor$.$Attribute(_SlideContainer.$Elmt, "debug-id", "slide_container"); }); } } //Slideshow //CarouselPlayer function CarouselPlayer(carousel, slideshow) { var _SelfCarouselPlayer = this; var _FromPosition; var _ToPosition; var _Duration; var _StandBy; var _StandByPosition; $JssorAnimator$.call(_SelfCarouselPlayer, -100000000, 200000000); _SelfCarouselPlayer.$OnStart = function () { _IsSliding = true; _LoadingTicket = null; //EVT_SWIPE_START _SelfSlider.$TriggerEvent(JssorSlider.$EVT_SWIPE_START, GetRealIndex(_Conveyor.$GetPosition()), _Conveyor.$GetPosition()); }; _SelfCarouselPlayer.$OnStop = function () { _IsSliding = false; _StandBy = false; var currentSlideInfo = _Conveyor.$GetCurrentSlideInfo(); //EVT_SWIPE_END _SelfSlider.$TriggerEvent(JssorSlider.$EVT_SWIPE_END, GetRealIndex(_Conveyor.$GetPosition()), _Conveyor.$GetPosition()); if (!currentSlideInfo.$Position) { OnPark(currentSlideInfo.$VirtualIndex, _CurrentSlideIndex); } }; _SelfCarouselPlayer.$OnPositionChange = function (oldPosition, newPosition) { var toPosition; if (_StandBy) toPosition = _StandByPosition; else { toPosition = _ToPosition; if (_Duration) { var interPosition = newPosition / _Duration; if ($Jssor$.$IsBrowserChrome() || $Jssor$.$IsBrowserFireFox()) { Math.round(interPosition * 16 / _Duration) / 16 * _Duration; interPosition = parseFloat(interPosition.toFixed(4)); } toPosition = _Options.$SlideEasing(interPosition) * (_ToPosition - _FromPosition) + _FromPosition; } } _Conveyor.$GoToPosition(toPosition); }; _SelfCarouselPlayer.$PlayCarousel = function (fromPosition, toPosition, duration, callback) { $JssorDebug$.$Execute(function () { if (_SelfCarouselPlayer.$IsPlaying()) $JssorDebug$.$Fail("The carousel is already playing."); }); _FromPosition = fromPosition; _ToPosition = toPosition; _Duration = duration; _Conveyor.$GoToPosition(fromPosition); _SelfCarouselPlayer.$GoToPosition(0); _SelfCarouselPlayer.$PlayToPosition(duration, callback); }; _SelfCarouselPlayer.$StandBy = function (standByPosition) { _StandBy = true; _StandByPosition = standByPosition; _SelfCarouselPlayer.$Play(standByPosition, null, true); }; _SelfCarouselPlayer.$SetStandByPosition = function (standByPosition) { _StandByPosition = standByPosition; }; _SelfCarouselPlayer.$MoveCarouselTo = function (position) { _Conveyor.$GoToPosition(position); }; //CarouselPlayer Constructor { _Conveyor = new Conveyor(); _Conveyor.$Combine(carousel); _Conveyor.$Combine(slideshow); } } //CarouselPlayer //SlideContainer function SlideContainer() { var _Self = this; var elmt = CreatePanel(); $Jssor$.$CssZIndex(elmt, 0); _Self.$Elmt = elmt; _Self.$AddClipElement = function (clipElement) { $Jssor$.$AppendChild(elmt, clipElement); $Jssor$.$ShowElement(elmt); }; _Self.$Clear = function () { $Jssor$.$HideElement(elmt); $Jssor$.$ClearInnerHtml(elmt); }; } //SlideContainer //SlideItem function SlideItem(slideElmt, slideIndex) { var _SelfSlideItem = this; var _CaptionSliderIn; var _CaptionSliderOut; var _CaptionSliderCurrent; var _IsCaptionSliderPlayingWhenDragStart; var _Wrapper; var _BaseElement = slideElmt; var _LoadingScreen; var _ImageItem; var _ImageElmts = []; var _LinkItemOrigin; var _LinkItem; var _ImageLoading; var _ImageLoaded; var _ImageLazyLoading; var _ContentRefreshed; var _Processor; var _PlayerInstanceElement; var _PlayerInstance; var _SequenceNumber; //for debug only $JssorAnimator$.call(_SelfSlideItem, -_DisplayPieces, _DisplayPieces + 1, { $SlideItemAnimator: true }); function ResetCaptionSlider(fresh) { _CaptionSliderOut && _CaptionSliderOut.$Revert(); _CaptionSliderIn && _CaptionSliderIn.$Revert(); RefreshContent(slideElmt, fresh); _ContentRefreshed = true; _CaptionSliderIn = new _CaptionSliderOptions.$Class(slideElmt, _CaptionSliderOptions, 1); $JssorDebug$.$LiveStamp(_CaptionSliderIn, "caption_slider_" + _CaptionSliderCount + "_in"); _CaptionSliderOut = new _CaptionSliderOptions.$Class(slideElmt, _CaptionSliderOptions); $JssorDebug$.$LiveStamp(_CaptionSliderOut, "caption_slider_" + _CaptionSliderCount + "_out"); $JssorDebug$.$Execute(function () { _CaptionSliderCount++; }); _CaptionSliderOut.$GoToBegin(); _CaptionSliderIn.$GoToBegin(); } function EnsureCaptionSliderVersion() { if (_CaptionSliderIn.$Version < _CaptionSliderOptions.$Version) { ResetCaptionSlider(); } } //event handling begin function LoadImageCompleteEventHandler(completeCallback, loadingScreen, image) { if (!_ImageLoaded) { _ImageLoaded = true; if (_ImageItem && image) { var imageWidth = image.width; var imageHeight = image.height; var fillWidth = imageWidth; var fillHeight = imageHeight; if (imageWidth && imageHeight && _Options.$FillMode) { //0 stretch, 1 contain (keep aspect ratio and put all inside slide), 2 cover (keep aspect ratio and cover whole slide), 4 actual size, 5 contain for large image, actual size for small image, default value is 0 if (_Options.$FillMode & 3 && (!(_Options.$FillMode & 4) || imageWidth > _SlideWidth || imageHeight > _SlideHeight)) { var fitHeight = false; var ratio = _SlideWidth / _SlideHeight * imageHeight / imageWidth; if (_Options.$FillMode & 1) { fitHeight = (ratio > 1); } else if (_Options.$FillMode & 2) { fitHeight = (ratio < 1); } fillWidth = fitHeight ? imageWidth * _SlideHeight / imageHeight : _SlideWidth; fillHeight = fitHeight ? _SlideHeight : imageHeight * _SlideWidth / imageWidth; } $Jssor$.$CssWidth(_ImageItem, fillWidth); $Jssor$.$CssHeight(_ImageItem, fillHeight); $Jssor$.$CssTop(_ImageItem, (_SlideHeight - fillHeight) / 2); $Jssor$.$CssLeft(_ImageItem, (_SlideWidth - fillWidth) / 2); } $Jssor$.$CssPosition(_ImageItem, "absolute"); _SelfSlider.$TriggerEvent(JssorSlider.$EVT_LOAD_END, slideItem); } } $Jssor$.$HideElement(loadingScreen); completeCallback && completeCallback(_SelfSlideItem); } function LoadSlideshowImageCompleteEventHandler(nextIndex, nextItem, slideshowTransition, loadingTicket) { if (loadingTicket == _LoadingTicket && _CurrentSlideIndex == slideIndex && _AutoPlay) { if (!_Frozen) { var nextRealIndex = GetRealIndex(nextIndex); _SlideshowRunner.$Initialize(nextRealIndex, slideIndex, nextItem, _SelfSlideItem, slideshowTransition); nextItem.$HideContentForSlideshow(); _Slideshow.$Locate(nextRealIndex, 1); _Slideshow.$GoToPosition(nextRealIndex); _CarouselPlayer.$PlayCarousel(nextIndex, nextIndex, 0); } } } function SlideReadyEventHandler(loadingTicket) { if (loadingTicket == _LoadingTicket && _CurrentSlideIndex == slideIndex) { if (!_Processor) { var slideshowProcessor = null; if (_SlideshowRunner) { if (_SlideshowRunner.$Index == slideIndex) slideshowProcessor = _SlideshowRunner.$GetProcessor(); else _SlideshowRunner.$Clear(); } EnsureCaptionSliderVersion(); _Processor = new Processor(slideIndex, slideshowProcessor, _SelfSlideItem.$GetCaptionSliderIn(), _SelfSlideItem.$GetCaptionSliderOut()); _Processor.$SetPlayer(_PlayerInstance); } !_Processor.$IsPlaying() && _Processor.$Replay(); } } function ParkEventHandler(currentIndex, previousIndex) { if (currentIndex == slideIndex) { if (currentIndex != previousIndex) _SlideItems[previousIndex] && _SlideItems[previousIndex].$ParkOut(); else _Processor && _Processor.$AdjustIdleOnPark(); _PlayerInstance && _PlayerInstance.$Enable(); //park in var loadingTicket = _LoadingTicket = $Jssor$.$GetNow(); _SelfSlideItem.$LoadImage($Jssor$.$CreateCallback(null, SlideReadyEventHandler, loadingTicket)); } else { var distance = Math.abs(slideIndex - currentIndex); var loadRange = _DisplayPieces + _Options.$LazyLoading; if (!_ImageLazyLoading || distance <= loadRange || _SlideCount - distance <= loadRange) { _SelfSlideItem.$LoadImage(); } } } function SwipeStartEventHandler() { if (_CurrentSlideIndex == slideIndex && _Processor) { _Processor.$Stop(); _PlayerInstance && _PlayerInstance.$Quit(); _PlayerInstance && _PlayerInstance.$Disable(); _Processor.$OpenSlideshowPanel(); } } function FreezeEventHandler() { if (_CurrentSlideIndex == slideIndex && _Processor) { _Processor.$Stop(); } } function LinkClickEventHandler(event) { if (_LastDragSucceded) { $Jssor$.$CancelEvent(event); } else { _SelfSlider.$TriggerEvent(JssorSlider.$EVT_CLICK, slideIndex, event); } } function PlayerAvailableEventHandler() { _PlayerInstance = _PlayerInstanceElement.pInstance; _Processor && _Processor.$SetPlayer(_PlayerInstance); } _SelfSlideItem.$LoadImage = function (completeCallback, loadingScreen) { loadingScreen = loadingScreen || _LoadingScreen; if (_ImageElmts.length && !_ImageLoaded) { $Jssor$.$ShowElement(loadingScreen); if (!_ImageLoading) { _ImageLoading = true; _SelfSlider.$TriggerEvent(JssorSlider.$EVT_LOAD_START); $Jssor$.$Each(_ImageElmts, function (imageElmt) { if (!imageElmt.src) { imageElmt.src = $Jssor$.$AttributeEx(imageElmt, "src2"); $Jssor$.$CssDisplay(imageElmt, imageElmt["display-origin"]); } }); } $Jssor$.$LoadImages(_ImageElmts, _ImageItem, $Jssor$.$CreateCallback(null, LoadImageCompleteEventHandler, completeCallback, loadingScreen)); } else { LoadImageCompleteEventHandler(completeCallback, loadingScreen); } }; _SelfSlideItem.$GoForNextSlide = function () { if (_SlideshowRunner) { var slideshowTransition = _SlideshowRunner.$GetTransition(_SlideCount); if (slideshowTransition) { var loadingTicket = _LoadingTicket = $Jssor$.$GetNow(); var nextIndex = slideIndex + _PlayReverse; var nextItem = _SlideItems[GetRealIndex(nextIndex)]; return nextItem.$LoadImage($Jssor$.$CreateCallback(null, LoadSlideshowImageCompleteEventHandler, nextIndex, nextItem, slideshowTransition, loadingTicket), _LoadingScreen); } } PlayTo(_CurrentSlideIndex + _Options.$AutoPlaySteps * _PlayReverse); }; _SelfSlideItem.$TryActivate = function () { ParkEventHandler(slideIndex, slideIndex); }; _SelfSlideItem.$ParkOut = function () { //park out _PlayerInstance && _PlayerInstance.$Quit(); _PlayerInstance && _PlayerInstance.$Disable(); _SelfSlideItem.$UnhideContentForSlideshow(); _Processor && _Processor.$Abort(); _Processor = null; ResetCaptionSlider(); }; //for debug only _SelfSlideItem.$StampSlideItemElements = function (stamp) { stamp = _SequenceNumber + "_" + stamp; $JssorDebug$.$Execute(function () { if (_ImageItem) $Jssor$.$Attribute(_ImageItem, "debug-id", stamp + "_slide_item_image_id"); $Jssor$.$Attribute(slideElmt, "debug-id", stamp + "_slide_item_item_id"); }); $JssorDebug$.$Execute(function () { $Jssor$.$Attribute(_Wrapper, "debug-id", stamp + "_slide_item_wrapper_id"); }); $JssorDebug$.$Execute(function () { $Jssor$.$Attribute(_LoadingScreen, "debug-id", stamp + "_loading_container_id"); }); }; _SelfSlideItem.$HideContentForSlideshow = function () { $Jssor$.$HideElement(slideElmt); }; _SelfSlideItem.$UnhideContentForSlideshow = function () { $Jssor$.$ShowElement(slideElmt); }; _SelfSlideItem.$EnablePlayer = function () { _PlayerInstance && _PlayerInstance.$Enable(); }; function RefreshContent(elmt, fresh, level) { if (elmt["jssor-slider"]) return; level = level || 0; if (!_ContentRefreshed) { if (elmt.tagName == "IMG") { _ImageElmts.push(elmt); if (!elmt.src) { _ImageLazyLoading = true; elmt["display-origin"] = $Jssor$.$CssDisplay(elmt); $Jssor$.$HideElement(elmt); } } if ($Jssor$.$IsBrowserIe9Earlier()) { $Jssor$.$CssZIndex(elmt, ($Jssor$.$CssZIndex(elmt) || 0) + 1); } if (_Options.$HWA && $Jssor$.$WebKitVersion() > 0) { //if ((_HandleTouchEventOnly && ($Jssor$.$WebKitVersion() < 534 || !_SlideshowEnabled)) || (!_HandleTouchEventOnly && $Jssor$.$WebKitVersion() < 535)) { // $Jssor$.$EnableHWA(elmt); //} if (!_HandleTouchEventOnly || ($Jssor$.$WebKitVersion() < 534 || !_SlideshowEnabled)) { $Jssor$.$EnableHWA(elmt); } } } var childElements = $Jssor$.$Children(elmt); $Jssor$.$Each(childElements, function (childElement, i) { var uAttribute = $Jssor$.$AttributeEx(childElement, "u"); if (uAttribute == "player" && !_PlayerInstanceElement) { _PlayerInstanceElement = childElement; if (_PlayerInstanceElement.pInstance) { PlayerAvailableEventHandler(); } else { $Jssor$.$AddEvent(_PlayerInstanceElement, "dataavailable", PlayerAvailableEventHandler); } } if (uAttribute == "caption") { if (!$Jssor$.$IsBrowserIE() && !fresh) { var captionElement = $Jssor$.$CloneNode(childElement, true); $Jssor$.$InsertBefore(elmt, captionElement, childElement); $Jssor$.$RemoveChild(elmt, childElement); childElement = captionElement; fresh = true; } } else if (!_ContentRefreshed && !level && !_ImageItem && $Jssor$.$AttributeEx(childElement, "u") == "image") { _ImageItem = childElement; if (_ImageItem) { if (_ImageItem.tagName == "A") { _LinkItemOrigin = _ImageItem; $Jssor$.$SetStyles(_LinkItemOrigin, _StyleDef); _LinkItem = $Jssor$.$CloneNode(_ImageItem, false); //cancel click event on
    element when a drag of slide succeeded $Jssor$.$AddEvent(_LinkItem, "click", LinkClickEventHandler); $Jssor$.$SetStyles(_LinkItem, _StyleDef); $Jssor$.$CssDisplay(_LinkItem, "block"); $Jssor$.$CssOpacity(_LinkItem, 0); $Jssor$.$Css(_LinkItem, "backgroundColor", "#000"); _ImageItem = $Jssor$.$FindFirstChildByTag(_ImageItem, "IMG"); $JssorDebug$.$Execute(function () { if (!_ImageItem) { $JssorDebug$.$Error("slide html code definition error, no 'IMG' found in a 'image with link' slide.\r\n" + elmt.outerHTML); } }); } _ImageItem.border = 0; $Jssor$.$SetStyles(_ImageItem, _StyleDef); } } RefreshContent(childElement, fresh, level + 1); }); } _SelfSlideItem.$OnInnerOffsetChange = function (oldOffset, newOffset) { var slidePosition = _DisplayPieces - newOffset; SetPosition(_Wrapper, slidePosition); //following lines are for future usage, not ready yet //if (!_IsDragging || !_IsCaptionSliderPlayingWhenDragStart) { // var _DealWithParallax; // if (IsCurrentSlideIndex(slideIndex)) { // if (_CaptionSliderOptions.$PlayOutMode == 2) // _DealWithParallax = true; // } // else { // if (!_CaptionSliderOptions.$PlayInMode) { // //PlayInMode: 0 none // _CaptionSliderIn.$GoToEnd(); // } // //else if (_CaptionSliderOptions.$PlayInMode == 1) { // // //PlayInMode: 1 chain // // _CaptionSliderIn.$GoToBegin(); // //} // else if (_CaptionSliderOptions.$PlayInMode == 2) { // //PlayInMode: 2 parallel // _DealWithParallax = true; // } // } // if (_DealWithParallax) { // _CaptionSliderIn.$GoToPosition((_CaptionSliderIn.$GetPosition_OuterEnd() - _CaptionSliderIn.$GetPosition_OuterBegin()) * Math.abs(newOffset - 1) * .8 + _CaptionSliderIn.$GetPosition_OuterBegin()); // } //} }; _SelfSlideItem.$GetCaptionSliderIn = function () { return _CaptionSliderIn; }; _SelfSlideItem.$GetCaptionSliderOut = function () { return _CaptionSliderOut; }; _SelfSlideItem.$Index = slideIndex; $JssorObject$.call(_SelfSlideItem); //SlideItem Constructor { var thumb = $Jssor$.$FindFirstChild(slideElmt, "thumb"); if (thumb) { _SelfSlideItem.$Thumb = $Jssor$.$CloneNode(thumb, true); $Jssor$.$RemoveAttribute(thumb, "id"); $Jssor$.$HideElement(thumb); } $Jssor$.$ShowElement(slideElmt); _LoadingScreen = $Jssor$.$CloneNode(_LoadingContainer, true); $Jssor$.$CssZIndex(_LoadingScreen, 1000); //cancel click event on element when a drag of slide succeeded $Jssor$.$AddEvent(slideElmt, "click", LinkClickEventHandler); ResetCaptionSlider(true); _SelfSlideItem.$Image = _ImageItem; _SelfSlideItem.$Link = _LinkItem; _SelfSlideItem.$Item = slideElmt; _SelfSlideItem.$Wrapper = _Wrapper = slideElmt; $Jssor$.$AppendChild(_Wrapper, _LoadingScreen); _SelfSlider.$On(203, ParkEventHandler); _SelfSlider.$On(28, FreezeEventHandler); _SelfSlider.$On(24, SwipeStartEventHandler); $JssorDebug$.$Execute(function () { _SequenceNumber = _SlideItemCreatedCount++; }); $JssorDebug$.$Execute(function () { $Jssor$.$Attribute(_Wrapper, "debug-id", "slide-" + slideIndex); }); } } //SlideItem //Processor function Processor(slideIndex, slideshowProcessor, captionSliderIn, captionSliderOut) { var _SelfProcessor = this; var _ProgressBegin = 0; var _SlideshowBegin = 0; var _SlideshowEnd; var _CaptionInBegin; var _IdleBegin; var _IdleEnd; var _ProgressEnd; var _IsSlideshowRunning; var _IsRollingBack; var _PlayerInstance; var _IsPlayerOnService; var slideItem = _SlideItems[slideIndex]; $JssorAnimator$.call(_SelfProcessor, 0, 0); function UpdateLink() { $Jssor$.$ClearChildren(_LinkContainer); if (_ShowLink && _IsSlideshowRunning && slideItem.$Link) { $Jssor$.$AppendChild(_LinkContainer, slideItem.$Link); } $Jssor$.$ShowElement(_LinkContainer, _IsSlideshowRunning || !slideItem.$Image); } function ProcessCompleteEventHandler() { if (_IsRollingBack) { _IsRollingBack = false; _SelfSlider.$TriggerEvent(JssorSlider.$EVT_ROLLBACK_END, slideIndex, _IdleEnd, _ProgressBegin, _IdleBegin, _IdleEnd, _ProgressEnd); _SelfProcessor.$GoToPosition(_IdleBegin); } _SelfProcessor.$Replay(); } function PlayerSwitchEventHandler(isOnService) { _IsPlayerOnService = isOnService; _SelfProcessor.$Stop(); _SelfProcessor.$Replay(); } _SelfProcessor.$Replay = function () { var currentPosition = _SelfProcessor.$GetPosition_Display(); if (!_IsDragging && !_IsSliding && !_IsPlayerOnService && _CurrentSlideIndex == slideIndex) { if (!currentPosition) { if (_SlideshowEnd && !_IsSlideshowRunning) { _IsSlideshowRunning = true; _SelfProcessor.$OpenSlideshowPanel(true); _SelfSlider.$TriggerEvent(JssorSlider.$EVT_SLIDESHOW_START, slideIndex, _ProgressBegin, _SlideshowBegin, _SlideshowEnd, _ProgressEnd); } UpdateLink(); } var toPosition; var stateEvent = JssorSlider.$EVT_STATE_CHANGE; if (currentPosition != _ProgressEnd) { if (currentPosition == _IdleEnd) { toPosition = _ProgressEnd; } else if (currentPosition == _IdleBegin) { toPosition = _IdleEnd; } else if (!currentPosition) { toPosition = _IdleBegin; } else if (currentPosition > _IdleEnd) { _IsRollingBack = true; toPosition = _IdleEnd; stateEvent = JssorSlider.$EVT_ROLLBACK_START; } else { //continue from break (by drag or lock) toPosition = _SelfProcessor.$GetPlayToPosition(); } } _SelfSlider.$TriggerEvent(stateEvent, slideIndex, currentPosition, _ProgressBegin, _IdleBegin, _IdleEnd, _ProgressEnd); var allowAutoPlay = _AutoPlay && (!_HoverToPause || _HoverStatus); if (currentPosition == _ProgressEnd) { allowAutoPlay && slideItem.$GoForNextSlide(); } else if (allowAutoPlay || currentPosition != _IdleEnd) { _SelfProcessor.$PlayToPosition(toPosition, ProcessCompleteEventHandler); } } }; _SelfProcessor.$AdjustIdleOnPark = function () { if (_IdleEnd == _ProgressEnd && _IdleEnd == _SelfProcessor.$GetPosition_Display()) _SelfProcessor.$GoToPosition(_IdleBegin); }; _SelfProcessor.$Abort = function () { _SlideshowRunner && _SlideshowRunner.$Index == slideIndex && _SlideshowRunner.$Clear(); var currentPosition = _SelfProcessor.$GetPosition_Display(); if (currentPosition < _ProgressEnd) { _SelfSlider.$TriggerEvent(JssorSlider.$EVT_STATE_CHANGE, slideIndex, -currentPosition -1, _ProgressBegin, _IdleBegin, _IdleEnd, _ProgressEnd); } }; _SelfProcessor.$OpenSlideshowPanel = function (open) { if (slideshowProcessor) { $Jssor$.$CssOverflow(_SlideshowPanel, open && slideshowProcessor.$Transition.$Outside ? "" : "hidden"); } }; _SelfProcessor.$OnInnerOffsetChange = function (oldPosition, newPosition) { if (_IsSlideshowRunning && newPosition >= _SlideshowEnd) { _IsSlideshowRunning = false; UpdateLink(); slideItem.$UnhideContentForSlideshow(); _SlideshowRunner.$Clear(); _SelfSlider.$TriggerEvent(JssorSlider.$EVT_SLIDESHOW_END, slideIndex, _ProgressBegin, _SlideshowBegin, _SlideshowEnd, _ProgressEnd); } _SelfSlider.$TriggerEvent(JssorSlider.$EVT_PROGRESS_CHANGE, slideIndex, newPosition, _ProgressBegin, _IdleBegin, _IdleEnd, _ProgressEnd); }; _SelfProcessor.$SetPlayer = function (playerInstance) { if (playerInstance && !_PlayerInstance) { _PlayerInstance = playerInstance; playerInstance.$On($JssorPlayer$.$EVT_SWITCH, PlayerSwitchEventHandler); } }; //Processor Constructor { if (slideshowProcessor) { _SelfProcessor.$Chain(slideshowProcessor); } _SlideshowEnd = _SelfProcessor.$GetPosition_OuterEnd(); _CaptionInBegin = _SelfProcessor.$GetPosition_OuterEnd(); _SelfProcessor.$Chain(captionSliderIn); _IdleBegin = captionSliderIn.$GetPosition_OuterEnd(); _IdleEnd = _IdleBegin + _Options.$AutoPlayInterval; captionSliderOut.$Shift(_IdleEnd); _SelfProcessor.$Combine(captionSliderOut); _ProgressEnd = _SelfProcessor.$GetPosition_OuterEnd(); } } //Processor //private classes function SetPosition(elmt, position) { var orientation = _DragOrientation > 0 ? _DragOrientation : _PlayOrientation; var x = _StepLengthX * position * (orientation & 1); var y = _StepLengthY * position * ((orientation >> 1) & 1); if ($Jssor$.$IsBrowserChrome()) { x = x.toFixed(3); y = y.toFixed(3); } else { x = Math.round(x); y = Math.round(y); } if ($Jssor$.$IsBrowserIE() && $Jssor$.$BrowserVersion() >= 10 && $Jssor$.$BrowserVersion() < 11) { elmt.style.msTransform = "translate(" + x + "px, " + y + "px)"; } else if ($Jssor$.$IsBrowserChrome() && $Jssor$.$BrowserVersion() >= 30 && $Jssor$.$BrowserVersion() < 34) { elmt.style.WebkitTransition = "transform 0s"; elmt.style.WebkitTransform = "translate3d(" + x + "px, " + y + "px, 0px) perspective(2000px)"; } else { $Jssor$.$CssLeft(elmt, x); $Jssor$.$CssTop(elmt, y); } } //Event handling begin function OnMouseDown(event) { var tagName = $Jssor$.$EventSrc(event).tagName; if (!_DragOrientationRegistered && tagName != "INPUT" && tagName != "TEXTAREA" && tagName != "SELECT" && RegisterDrag()) { OnDragStart(event); } } function Freeze() { _CarouselPlaying_OnFreeze = _IsSliding; _PlayToPosition_OnFreeze = _CarouselPlayer.$GetPlayToPosition(); _Position_OnFreeze = _Conveyor.$GetPosition(); if (_IsDragging || !_HoverStatus && (_HoverToPause & 12)) { _CarouselPlayer.$Stop(); _SelfSlider.$TriggerEvent(JssorSlider.$EVT_FREEZE); } } function Unfreeze(byDrag) { if (!_IsDragging && (_HoverStatus || !(_HoverToPause & 12)) && !_CarouselPlayer.$IsPlaying()) { var currentPosition = _Conveyor.$GetPosition(); var toPosition = Math.ceil(_Position_OnFreeze); if (byDrag && Math.abs(_DragOffsetTotal) >= _Options.$MinDragOffsetToSlide) { toPosition = Math.ceil(currentPosition); toPosition += _DragIndexAdjust; } if (!(_Loop & 1)) { toPosition = Math.min(_SlideCount - _DisplayPieces, Math.max(toPosition, 0)); } var t = Math.abs(toPosition - currentPosition); t = 1 - Math.pow(1 - t, 5); if (!_LastDragSucceded && _CarouselPlaying_OnFreeze) { _CarouselPlayer.$Continue(_PlayToPosition_OnFreeze); } else if (currentPosition == toPosition) { _CurrentSlideItem.$EnablePlayer(); _CurrentSlideItem.$TryActivate(); } else { _CarouselPlayer.$PlayCarousel(currentPosition, toPosition, t * _SlideDuration); } } } function OnDragStart(event) { _IsDragging = true; _DragInvalid = false; _LoadingTicket = null; $Jssor$.$AddEvent(document, _MoveEvent, OnDragMove); _LastTimeMoveByDrag = $Jssor$.$GetNow() - 50; _LastDragSucceded = 0; Freeze(); if (!_CarouselPlaying_OnFreeze) _DragOrientation = 0; if (_HandleTouchEventOnly) { var touchPoint = event.touches[0]; _DragStartMouseX = touchPoint.clientX; _DragStartMouseY = touchPoint.clientY; } else { var mousePoint = $Jssor$.$MousePosition(event); _DragStartMouseX = mousePoint.x; _DragStartMouseY = mousePoint.y; $Jssor$.$CancelEvent(event); } _DragOffsetTotal = 0; _DragOffsetLastTime = 0; _DragIndexAdjust = 0; //Trigger EVT_DRAGSTART _SelfSlider.$TriggerEvent(JssorSlider.$EVT_DRAG_START, GetRealIndex(_Position_OnFreeze), _Position_OnFreeze, event); } function OnDragMove(event) { if (_IsDragging && (!$Jssor$.$IsBrowserIe9Earlier() || event.button)) { var actionPoint; if (_HandleTouchEventOnly) { var touches = event.touches; if (touches && touches.length > 0) { actionPoint = new $JssorPoint$(touches[0].clientX, touches[0].clientY); } } else { actionPoint = $Jssor$.$MousePosition(event); } if (actionPoint) { var distanceX = actionPoint.x - _DragStartMouseX; var distanceY = actionPoint.y - _DragStartMouseY; if (Math.floor(_Position_OnFreeze) != _Position_OnFreeze) _DragOrientation = _DragOrientation || (_PlayOrientation & _DragOrientationRegistered); if ((distanceX || distanceY) && !_DragOrientation) { if (_DragOrientationRegistered == 3) { if (Math.abs(distanceY) > Math.abs(distanceX)) { _DragOrientation = 2; } else _DragOrientation = 1; } else { _DragOrientation = _DragOrientationRegistered; } if (_HandleTouchEventOnly && _DragOrientation == 1 && Math.abs(distanceY) - Math.abs(distanceX) > 3) { _DragInvalid = true; } } if (_DragOrientation) { var distance = distanceY; var stepLength = _StepLengthY; if (_DragOrientation == 1) { distance = distanceX; stepLength = _StepLengthX; } if (!(_Loop & 1)) { if (distance > 0) { var normalDistance = stepLength * _CurrentSlideIndex; var sqrtDistance = distance - normalDistance; if (sqrtDistance > 0) { distance = normalDistance + Math.sqrt(sqrtDistance) * 5; } } if (distance < 0) { var normalDistance = stepLength * (_SlideCount - _DisplayPieces - _CurrentSlideIndex); var sqrtDistance = -distance - normalDistance; if (sqrtDistance > 0) { distance = -normalDistance - Math.sqrt(sqrtDistance) * 5; } } } if (_DragOffsetTotal - _DragOffsetLastTime < -2) { _DragIndexAdjust = 0; } else if (_DragOffsetTotal - _DragOffsetLastTime > 2) { _DragIndexAdjust = -1; } _DragOffsetLastTime = _DragOffsetTotal; _DragOffsetTotal = distance; _PositionToGoByDrag = _Position_OnFreeze - _DragOffsetTotal / stepLength / (_ScaleRatio || 1); if (_DragOffsetTotal && _DragOrientation && !_DragInvalid) { $Jssor$.$CancelEvent(event); if (!_IsSliding) { _CarouselPlayer.$StandBy(_PositionToGoByDrag); } else _CarouselPlayer.$SetStandByPosition(_PositionToGoByDrag); } else if ($Jssor$.$IsBrowserIe9Earlier()) { $Jssor$.$CancelEvent(event); } } } } else { OnDragEnd(event); } } function OnDragEnd(event) { UnregisterDrag(); if (_IsDragging) { _IsDragging = false; _LastTimeMoveByDrag = $Jssor$.$GetNow(); $Jssor$.$RemoveEvent(document, _MoveEvent, OnDragMove); _LastDragSucceded = _DragOffsetTotal; _LastDragSucceded && $Jssor$.$CancelEvent(event); _CarouselPlayer.$Stop(); var currentPosition = _Conveyor.$GetPosition(); //Trigger EVT_DRAG_END _SelfSlider.$TriggerEvent(JssorSlider.$EVT_DRAG_END, GetRealIndex(currentPosition), currentPosition, GetRealIndex(_Position_OnFreeze), _Position_OnFreeze, event); Unfreeze(true); Freeze(); } } //Event handling end function SetCurrentSlideIndex(index) { _PrevSlideItem = _SlideItems[_CurrentSlideIndex]; _PreviousSlideIndex = _CurrentSlideIndex; _CurrentSlideIndex = GetRealIndex(index); _CurrentSlideItem = _SlideItems[_CurrentSlideIndex]; ResetNavigator(index); return _CurrentSlideIndex; } function OnPark(slideIndex, prevIndex) { _DragOrientation = 0; SetCurrentSlideIndex(slideIndex); //Trigger EVT_PARK _SelfSlider.$TriggerEvent(JssorSlider.$EVT_PARK, GetRealIndex(slideIndex), prevIndex); } function ResetNavigator(index, temp) { _TempSlideIndex = index; $Jssor$.$Each(_Navigators, function (navigator) { navigator.$SetCurrentIndex(GetRealIndex(index), index, temp); }); } function RegisterDrag() { var dragRegistry = JssorSlider.$DragRegistry || 0; var dragOrientation = _DragEnabled; if (_HandleTouchEventOnly) (dragOrientation & 1) && (dragOrientation &= 1); JssorSlider.$DragRegistry |= dragOrientation; return (_DragOrientationRegistered = dragOrientation & ~dragRegistry); } function UnregisterDrag() { if (_DragOrientationRegistered) { JssorSlider.$DragRegistry &= ~_DragEnabled; _DragOrientationRegistered = 0; } } function CreatePanel() { var div = $Jssor$.$CreateDiv(); $Jssor$.$SetStyles(div, _StyleDef); $Jssor$.$CssPosition(div, "absolute"); return div; } function GetRealIndex(index) { return (index % _SlideCount + _SlideCount) % _SlideCount; } function IsCurrentSlideIndex(index) { return GetRealIndex(index) == _CurrentSlideIndex; } function IsPreviousSlideIndex(index) { return GetRealIndex(index) == _PreviousSlideIndex; } //Navigation Request Handler function NavigationClickHandler(index, relative) { if (relative) { if (!_Loop) { //Stop at threshold index = Math.min(Math.max(index + _TempSlideIndex, 0), _SlideCount - _DisplayPieces); relative = false; } else if (_Loop & 2) { //Rewind index = GetRealIndex(index + _TempSlideIndex); relative = false; } } PlayTo(index, _Options.$SlideDuration, relative); } function ShowNavigators() { $Jssor$.$Each(_Navigators, function (navigator) { navigator.$Show(navigator.$Options.$ChanceToShow > _HoverStatus); }); } function MainContainerMouseLeaveEventHandler() { if (!_HoverStatus) { //$JssorDebug$.$Log("mouseleave"); _HoverStatus = 1; ShowNavigators(); if (!_IsDragging) { (_HoverToPause & 12) && Unfreeze(); (_HoverToPause & 3) && _SlideItems[_CurrentSlideIndex].$TryActivate(); } } } function MainContainerMouseEnterEventHandler() { if (_HoverStatus) { //$JssorDebug$.$Log("mouseenter"); _HoverStatus = 0; ShowNavigators(); _IsDragging || !(_HoverToPause & 12) || Freeze(); } } function AdjustSlidesContainerSize() { _StyleDef = { $Width: _SlideWidth, $Height: _SlideHeight, $Top: 0, $Left: 0 }; $Jssor$.$Each(_SlideElmts, function (slideElmt, i) { $Jssor$.$SetStyles(slideElmt, _StyleDef); $Jssor$.$CssPosition(slideElmt, "absolute"); $Jssor$.$CssOverflow(slideElmt, "hidden"); $Jssor$.$HideElement(slideElmt); }); $Jssor$.$SetStyles(_LoadingContainer, _StyleDef); } function PlayToOffset(offset, slideDuration) { PlayTo(offset, slideDuration, true); } function PlayTo(slideIndex, slideDuration, relative) { /// /// PlayTo( slideIndex [, slideDuration] ); //Play slider to position 'slideIndex' within a period calculated base on 'slideDuration'. /// /// /// slide slideIndex or position will be playing to /// /// /// base slide duration in milliseconds to calculate the whole duration to complete this play request. /// default value is '$SlideDuration' value which is specified when initialize the slider. /// /// http://msdn.microsoft.com/en-us/library/vstudio/bb385682.aspx /// http://msdn.microsoft.com/en-us/library/vstudio/hh542720.aspx if (_CarouselEnabled && (!_IsDragging || _Options.$NaviQuitDrag)) { _IsSliding = true; _IsDragging = false; _CarouselPlayer.$Stop(); { //Slide Duration if (slideDuration == undefined) slideDuration = _SlideDuration; var positionDisplay = _Carousel.$GetPosition_Display(); var positionTo = slideIndex; if (relative) { positionTo = positionDisplay + slideIndex; if (slideIndex > 0) positionTo = Math.ceil(positionTo); else positionTo = Math.floor(positionTo); } if (!(_Loop & 1)) { positionTo = GetRealIndex(positionTo); positionTo = Math.max(0, Math.min(positionTo, _SlideCount - _DisplayPieces)); } var positionOffset = (positionTo - positionDisplay) % _SlideCount; positionTo = positionDisplay + positionOffset; var duration = positionDisplay == positionTo ? 0 : slideDuration * Math.abs(positionOffset); duration = Math.min(duration, slideDuration * _DisplayPieces * 1.5); _CarouselPlayer.$PlayCarousel(positionDisplay, positionTo, duration || 1); } } } //private functions //member functions _SelfSlider.$PlayTo = PlayTo; _SelfSlider.$GoTo = function (slideIndex) { /// /// instance.$GoTo( slideIndex ); //Go to the specifed slide immediately with no play. /// PlayTo(slideIndex, 1); }; _SelfSlider.$Next = function () { /// /// instance.$Next(); //Play the slider to next slide. /// PlayToOffset(1); }; _SelfSlider.$Prev = function () { /// /// instance.$Prev(); //Play the slider to previous slide. /// PlayToOffset(-1); }; _SelfSlider.$Pause = function () { /// /// instance.$Pause(); //Pause the slider, prevent it from auto playing. /// _AutoPlay = false; }; _SelfSlider.$Play = function () { /// /// instance.$Play(); //Start auto play if the slider is currently paused. /// if (!_AutoPlay) { _AutoPlay = true; _SlideItems[_CurrentSlideIndex] && _SlideItems[_CurrentSlideIndex].$TryActivate(); } }; _SelfSlider.$SetSlideshowTransitions = function (transitions) { /// /// instance.$SetSlideshowTransitions( transitions ); //Reset slideshow transitions for the slider. /// $JssorDebug$.$Execute(function () { if (!transitions || !transitions.length) { $JssorDebug$.$Error("Can not set slideshow transitions, no transitions specified."); } }); $Jssor$.$TranslateTransitions(transitions); //for old transition compatibility _Options.$SlideshowOptions.$Transitions = transitions; }; _SelfSlider.$SetCaptionTransitions = function (transitions) { /// /// instance.$SetCaptionTransitions( transitions ); //Reset caption transitions for the slider. /// $JssorDebug$.$Execute(function () { if (!transitions || !transitions.length) { $JssorDebug$.$Error("Can not set caption transitions, no transitions specified"); } }); $Jssor$.$TranslateTransitions(transitions); //for old transition compatibility _CaptionSliderOptions.$CaptionTransitions = transitions; _CaptionSliderOptions.$Version = $Jssor$.$GetNow(); }; _SelfSlider.$SlidesCount = function () { /// /// instance.$SlidesCount(); //Retrieve slides count of the slider. /// return _SlideElmts.length; }; _SelfSlider.$CurrentIndex = function () { /// /// instance.$CurrentIndex(); //Retrieve current slide index of the slider. /// return _CurrentSlideIndex; }; _SelfSlider.$IsAutoPlaying = function () { /// /// instance.$IsAutoPlaying(); //Retrieve auto play status of the slider. /// return _AutoPlay; }; _SelfSlider.$IsDragging = function () { /// /// instance.$IsDragging(); //Retrieve drag status of the slider. /// return _IsDragging; }; _SelfSlider.$IsSliding = function () { /// /// instance.$IsSliding(); //Retrieve right<-->left sliding status of the slider. /// return _IsSliding; }; _SelfSlider.$IsMouseOver = function () { /// /// instance.$IsMouseOver(); //Retrieve mouse over status of the slider. /// return !_HoverStatus; }; _SelfSlider.$LastDragSucceded = function () { /// /// instance.$IsLastDragSucceded(); //Retrieve last drag succeded status, returns 0 if failed, returns drag offset if succeded /// return _LastDragSucceded; }; function OriginalWidth() { /// /// instance.$OriginalWidth(); //Retrieve original width of the slider. /// return $Jssor$.$CssWidth(_ScaleWrapper || elmt); } function OriginalHeight() { /// /// instance.$OriginalHeight(); //Retrieve original height of the slider. /// return $Jssor$.$CssHeight(_ScaleWrapper || elmt); } _SelfSlider.$OriginalWidth = _SelfSlider.$GetOriginalWidth = OriginalWidth; _SelfSlider.$OriginalHeight = _SelfSlider.$GetOriginalHeight = OriginalHeight; function Scale(dimension, isHeight) { /// /// instance.$ScaleWidth(); //Retrieve scaled dimension the slider currently displays. /// instance.$ScaleWidth( dimension ); //Scale the slider to new width and keep aspect ratio. /// if (dimension == undefined) return $Jssor$.$CssWidth(elmt); $JssorDebug$.$Execute(function () { if (!dimension || dimension < 0) { $JssorDebug$.$Fail("'$ScaleWidth' error, 'dimension' should be positive value."); } }); if (!_ScaleWrapper) { $JssorDebug$.$Execute(function () { var originalWidthStr = $Jssor$.$Css(elmt, "width"); var originalHeightStr = $Jssor$.$Css(elmt, "height"); var originalWidth = $Jssor$.$CssP(elmt, "width"); var originalHeight = $Jssor$.$CssP(elmt, "height"); if (!originalWidthStr) { $JssorDebug$.$Fail("Cannot scale jssor slider, 'dimension' of 'outer container' not specified. Please specify 'dimension' in pixel. e.g. 'dimension: 600px;'"); } if (!originalHeightStr) { $JssorDebug$.$Fail("Cannot scale jssor slider, 'height' of 'outer container' not specified. Please specify 'height' in pixel. e.g. 'height: 300px;'"); } if (originalWidthStr.indexOf('%') != -1) { $JssorDebug$.$Fail("Cannot scale jssor slider, 'dimension' of 'outer container' not valid. Please specify 'dimension' in pixel. e.g. 'dimension: 600px;'"); } if (originalHeightStr.indexOf('%') != -1) { $JssorDebug$.$Fail("Cannot scale jssor slider, 'height' of 'outer container' not valid. Please specify 'height' in pixel. e.g. 'height: 300px;'"); } if (!originalWidth) { $JssorDebug$.$Fail("Cannot scale jssor slider, 'dimension' of 'outer container' not valid. 'dimension' of 'outer container' should be positive number. e.g. 'dimension: 600px;'"); } if (!originalHeight) { $JssorDebug$.$Fail("Cannot scale jssor slider, 'height' of 'outer container' not valid. 'height' of 'outer container' should be positive number. e.g. 'height: 300px;'"); } }); //var innerWrapper = $Jssor$.$CloneNode(elmt, false); //$Jssor$.$RemoveAttribute(innerWrapper, "id"); var innerWrapper = $Jssor$.$CreateDiv(document); $Jssor$.$CssCssText(innerWrapper, $Jssor$.$CssCssText(elmt)); $Jssor$.$ClassName(innerWrapper, $Jssor$.$ClassName(elmt)); $Jssor$.$CssPosition(innerWrapper, "relative"); $Jssor$.$CssTop(innerWrapper, 0); $Jssor$.$CssLeft(innerWrapper, 0); $Jssor$.$CssOverflow(innerWrapper, "visible"); //_ScaleWrapper = $Jssor$.$CloneNode(elmt, false); //$Jssor$.$RemoveAttribute(_ScaleWrapper, "id"); //$Jssor$.$CssCssText(_ScaleWrapper, ""); _ScaleWrapper = $Jssor$.$CreateDiv(document); $Jssor$.$CssPosition(_ScaleWrapper, "absolute"); $Jssor$.$CssTop(_ScaleWrapper, 0); $Jssor$.$CssLeft(_ScaleWrapper, 0); $Jssor$.$CssWidth(_ScaleWrapper, $Jssor$.$CssWidth(elmt)); $Jssor$.$CssHeight(_ScaleWrapper, $Jssor$.$CssHeight(elmt)); $Jssor$.$SetStyleTransformOrigin(_ScaleWrapper, "0 0"); $Jssor$.$AppendChild(_ScaleWrapper, innerWrapper); var children = $Jssor$.$Children(elmt); $Jssor$.$AppendChild(elmt, _ScaleWrapper); $Jssor$.$Css(elmt, "backgroundImage", ""); var noMoveElmts = { "navigator": _BulletNavigatorOptions && _BulletNavigatorOptions.$Scale == false, "arrowleft": _ArrowNavigatorOptions && _ArrowNavigatorOptions.$Scale == false, "arrowright": _ArrowNavigatorOptions && _ArrowNavigatorOptions.$Scale == false, "thumbnavigator": _ThumbnailNavigatorOptions && _ThumbnailNavigatorOptions.$Scale == false, "thumbwrapper": _ThumbnailNavigatorOptions && _ThumbnailNavigatorOptions.$Scale == false }; $Jssor$.$Each(children, function (child) { $Jssor$.$AppendChild(noMoveElmts[$Jssor$.$AttributeEx(child, "u")] ? elmt : innerWrapper, child); }); $Jssor$.$ShowElement(innerWrapper); $Jssor$.$ShowElement(_ScaleWrapper); } $JssorDebug$.$Execute(function () { if (!_InitialScrollWidth) { _InitialScrollWidth = _SelfSlider.$Elmt.scrollWidth; } }); _ScaleRatio = dimension / (isHeight? $Jssor$.$CssHeight : $Jssor$.$CssWidth)(_ScaleWrapper); $Jssor$.$CssScale(_ScaleWrapper, _ScaleRatio); $Jssor$.$CssWidth(elmt, isHeight ? (_ScaleRatio * OriginalWidth()) : dimension); $Jssor$.$CssHeight(elmt, isHeight ? dimension : (_ScaleRatio * OriginalHeight())); $Jssor$.$Each(_Navigators, function (navigator) { navigator.$Relocate(); }); } _SelfSlider.$ScaleHeight = _SelfSlider.$GetScaleHeight = function (height) { /// /// instance.$ScaleHeight(); //Retrieve scaled height the slider currently displays. /// instance.$ScaleHeight( dimension ); //Scale the slider to new height and keep aspect ratio. /// if (height == undefined) return $Jssor$.$CssHeight(elmt); Scale(height, true); }; _SelfSlider.$ScaleWidth = _SelfSlider.$SetScaleWidth = _SelfSlider.$GetScaleWidth = Scale; _SelfSlider.$GetVirtualIndex = function (index) { var parkingIndex = Math.ceil(GetRealIndex(_ParkingPosition / _StepLength)); var displayIndex = GetRealIndex(index - _CurrentSlideIndex + parkingIndex); if (displayIndex > _DisplayPieces) { if (index - _CurrentSlideIndex > _SlideCount / 2) index -= _SlideCount; else if (index - _CurrentSlideIndex <= -_SlideCount / 2) index += _SlideCount; } else { index = _CurrentSlideIndex + displayIndex - parkingIndex; } return index; }; //member functions $JssorObject$.call(this); //initialize member variables _SelfSlider.$Elmt = elmt = $Jssor$.$GetElement(elmt); //initialize member variables var _InitialScrollWidth; //for debug only var _CaptionSliderCount = 1; //for debug only $JssorDebug$.$Execute(function () { var outerContainerElmt = $Jssor$.$GetElement(elmt); if (!outerContainerElmt) $JssorDebug$.$Fail("Outer container '" + elmt + "' not found."); }); var _Options = $Jssor$.$Extend({ $FillMode: 0, //[Optional] The way to fill image in slide, 0 stretch, 1 contain (keep aspect ratio and put all inside slide), 2 cover (keep aspect ratio and cover whole slide), 4 actual size, 5 contain for large image, actual size for small image, default value is 0 $LazyLoading: 1, //[Optional] For image with lazy loading format (), by default it will be loaded only when the slide comes. //But an integer value (maybe 0, 1, 2 or 3) indicates that how far of nearby slides should be loaded immediately as well, default value is 1. $StartIndex: 0, //[Optional] Index of slide to display when initialize, default value is 0 $AutoPlay: false, //[Optional] Whether to auto play, default value is false $Loop: 1, //[Optional] Enable loop(circular) of carousel or not, 0: stop, 1: loop, 2 rewind, default value is 1 $HWA: true, //[Optional] Enable hardware acceleration or not, default value is true $NaviQuitDrag: true, $AutoPlaySteps: 1, //[Optional] Steps to go of every play (this options applys only when slideshow disabled), default value is 1 $AutoPlayInterval: 3000, //[Optional] Interval to play next slide since the previous stopped if a slideshow is auto playing, default value is 3000 $PauseOnHover: 1, //[Optional] Whether to pause when mouse over if a slider is auto playing, 0 no pause, 1 pause for desktop, 2 pause for touch device, 3 pause for desktop and touch device, 4 freeze for desktop, 8 freeze for touch device, 12 freeze for desktop and touch device, default value is 1 $SlideDuration: 500, //[Optional] Specifies default duration (swipe) for slide in milliseconds, default value is 400 $SlideEasing: $JssorEasing$.$EaseOutQuad, //[Optional] Specifies easing for right to left animation, default value is $JssorEasing$.$EaseOutQuad $MinDragOffsetToSlide: 20, //[Optional] Minimum drag offset that trigger slide, default value is 20 $SlideSpacing: 0, //[Optional] Space between each slide in pixels, default value is 0 $DisplayPieces: 1, //[Optional] Number of pieces to display (the slideshow would be disabled if the value is set to greater than 1), default value is 1 $ParkingPosition: 0, //[Optional] The offset position to park slide (this options applys only when slideshow disabled), default value is 0. $UISearchMode: 1, //[Optional] The way (0 parellel, 1 recursive, default value is recursive) to search UI components (slides container, loading screen, navigator container, arrow navigator container, thumbnail navigator container etc. $PlayOrientation: 1, //[Optional] Orientation to play slide (for auto play, navigation), 1 horizental, 2 vertical, 5 horizental reverse, 6 vertical reverse, default value is 1 $DragOrientation: 1 //[Optional] Orientation to drag slide, 0 no drag, 1 horizental, 2 vertical, 3 both, default value is 1 (Note that the $DragOrientation should be the same as $PlayOrientation when $DisplayPieces is greater than 1, or parking position is not 0) }, options); //Sodo statement for development time intellisence only $JssorDebug$.$Execute(function () { _Options = $Jssor$.$Extend({ $ArrowKeyNavigation: undefined, $SlideWidth: undefined, $SlideHeight: undefined, $SlideshowOptions: undefined, $CaptionSliderOptions: undefined, $BulletNavigatorOptions: undefined, $ArrowNavigatorOptions: undefined, $ThumbnailNavigatorOptions: undefined }, _Options); }); var _PlayOrientation = _Options.$PlayOrientation & 3; var _PlayReverse = (_Options.$PlayOrientation & 4) / -4 || 1; var _SlideshowOptions = _Options.$SlideshowOptions; var _CaptionSliderOptions = $Jssor$.$Extend({ $Class: $JssorCaptionSliderBase$, $PlayInMode: 1, $PlayOutMode: 1 }, _Options.$CaptionSliderOptions); $Jssor$.$TranslateTransitions(_CaptionSliderOptions.$CaptionTransitions); //for old transition compatibility var _BulletNavigatorOptions = _Options.$BulletNavigatorOptions; var _ArrowNavigatorOptions = _Options.$ArrowNavigatorOptions; var _ThumbnailNavigatorOptions = _Options.$ThumbnailNavigatorOptions; $JssorDebug$.$Execute(function () { if (_SlideshowOptions && !_SlideshowOptions.$Class) { $JssorDebug$.$Fail("Option $SlideshowOptions error, class not specified."); } }); $JssorDebug$.$Execute(function () { if (_Options.$CaptionSliderOptions && !_Options.$CaptionSliderOptions.$Class) { $JssorDebug$.$Fail("Option $CaptionSliderOptions error, class not specified."); } }); $JssorDebug$.$Execute(function () { if (_BulletNavigatorOptions && !_BulletNavigatorOptions.$Class) { $JssorDebug$.$Fail("Option $BulletNavigatorOptions error, class not specified."); } }); $JssorDebug$.$Execute(function () { if (_ArrowNavigatorOptions && !_ArrowNavigatorOptions.$Class) { $JssorDebug$.$Fail("Option $ArrowNavigatorOptions error, class not specified."); } }); $JssorDebug$.$Execute(function () { if (_ThumbnailNavigatorOptions && !_ThumbnailNavigatorOptions.$Class) { $JssorDebug$.$Fail("Option $ThumbnailNavigatorOptions error, class not specified."); } }); var _UISearchMode = _Options.$UISearchMode; var _ScaleWrapper; var _SlidesContainer = $Jssor$.$FindFirstChild(elmt, "slides", null, _UISearchMode); var _LoadingContainer = $Jssor$.$FindFirstChild(elmt, "loading", null, _UISearchMode) || $Jssor$.$CreateDiv(document); var _BulletNavigatorContainer = $Jssor$.$FindFirstChild(elmt, "navigator", null, _UISearchMode); var _ArrowLeft = $Jssor$.$FindFirstChild(elmt, "arrowleft", null, _UISearchMode); var _ArrowRight = $Jssor$.$FindFirstChild(elmt, "arrowright", null, _UISearchMode); var _ThumbnailNavigatorContainer = $Jssor$.$FindFirstChild(elmt, "thumbnavigator", null, _UISearchMode); $JssorDebug$.$Execute(function () { //if (_BulletNavigatorOptions && !_BulletNavigatorContainer) { // throw new Error("$BulletNavigatorOptions specified but bullet navigator container (
    1 && _Options.$DragOrientation && _Options.$DragOrientation != _PlayOrientation) $JssorDebug$.$Fail("Option $DragOrientation error, it should be 0 or the same of $PlayOrientation when $DisplayPieces is greater than 1."); if (!$Jssor$.$IsNumeric(_Options.$ParkingPosition)) $JssorDebug$.$Fail("Option $ParkingPosition error, it should be a numeric value."); if (_Options.$ParkingPosition && _Options.$DragOrientation && _Options.$DragOrientation != _PlayOrientation) $JssorDebug$.$Fail("Option $DragOrientation error, it should be 0 or the same of $PlayOrientation when $ParkingPosition is not equal to 0."); }); var _StyleDef; var _SlideElmts = []; { var slideElmts = $Jssor$.$Children(_SlidesContainer); $Jssor$.$Each(slideElmts, function (slideElmt) { if (slideElmt.tagName == "DIV" && !$Jssor$.$AttributeEx(slideElmt, "u")) { _SlideElmts.push(slideElmt); } }); } $JssorDebug$.$Execute(function () { if (_SlideElmts.length < 1) { $JssorDebug$.$Error("Slides html code definition error, there must be at least 1 slide to initialize a slider."); } }); var _SlideItemCreatedCount = 0; //for debug only var _SlideItemReleasedCount = 0; //for debug only var _PreviousSlideIndex; var _CurrentSlideIndex = -1; var _TempSlideIndex; var _PrevSlideItem; var _CurrentSlideItem; var _SlideCount = _SlideElmts.length; var _SlideWidth = _Options.$SlideWidth || _SlidesContainerWidth; var _SlideHeight = _Options.$SlideHeight || _SlidesContainerHeight; var _SlideSpacing = _Options.$SlideSpacing; var _StepLengthX = _SlideWidth + _SlideSpacing; var _StepLengthY = _SlideHeight + _SlideSpacing; var _StepLength = (_PlayOrientation & 1) ? _StepLengthX : _StepLengthY; var _DisplayPieces = Math.min(_Options.$DisplayPieces, _SlideCount); var _SlideshowPanel; var _CurrentBoardIndex = 0; var _DragOrientation; var _DragOrientationRegistered; var _DragInvalid; var _HandleTouchEventOnly; var _Navigators = []; var _BulletNavigator; var _ArrowNavigator; var _ThumbnailNavigator; var _ShowLink; var _Frozen; var _AutoPlay; var _AutoPlaySteps = _Options.$AutoPlaySteps; var _HoverToPause = _Options.$PauseOnHover; var _AutoPlayInterval = _Options.$AutoPlayInterval; var _SlideDuration = _Options.$SlideDuration; var _SlideshowRunnerClass; var _TransitionsOrder; var _SlideshowEnabled; var _ParkingPosition; var _CarouselEnabled = _DisplayPieces < _SlideCount; var _Loop = _CarouselEnabled ? _Options.$Loop : 0; var _DragEnabled; var _LastDragSucceded; var _HoverStatus = 1; //0 Hovering, 1 Not hovering //Variable Definition var _IsSliding; var _IsDragging; var _LoadingTicket; //The X position of mouse/touch when a drag start var _DragStartMouseX = 0; //The Y position of mouse/touch when a drag start var _DragStartMouseY = 0; var _DragOffsetTotal; var _DragOffsetLastTime; var _DragIndexAdjust; var _Carousel; var _Conveyor; var _Slideshow; var _CarouselPlayer; var _SlideContainer = new SlideContainer(); var _ScaleRatio; //$JssorSlider$ Constructor { _AutoPlay = _Options.$AutoPlay; _SelfSlider.$Options = options; AdjustSlidesContainerSize(); elmt["jssor-slider"] = true; //_SlideshowPanel = CreatePanel(); //$Jssor$.$CssZIndex(elmt, $Jssor$.$CssZIndex(elmt)); //$Jssor$.$CssLeft(_SlideshowPanel, $Jssor$.$CssLeft(_SlidesContainer)); //$Jssor$.$CssZIndex(_SlidesContainer, $Jssor$.$CssZIndex(_SlidesContainer)); //$Jssor$.$CssTop(_SlideshowPanel, $Jssor$.$CssTop(_SlidesContainer)); $Jssor$.$CssZIndex(_SlidesContainer, $Jssor$.$CssZIndex(_SlidesContainer) || 0); $Jssor$.$CssPosition(_SlidesContainer, "absolute"); _SlideshowPanel = $Jssor$.$CloneNode(_SlidesContainer); $Jssor$.$InsertBefore($Jssor$.$ParentNode(_SlidesContainer), _SlideshowPanel, _SlidesContainer); if (_SlideshowOptions) { _ShowLink = _SlideshowOptions.$ShowLink; _SlideshowRunnerClass = _SlideshowOptions.$Class; $JssorDebug$.$Execute(function () { if (!_SlideshowOptions.$Transitions || !_SlideshowOptions.$Transitions.length) { $JssorDebug$.$Error("Invalid '$SlideshowOptions', no '$Transitions' specified."); } }); $Jssor$.$TranslateTransitions(_SlideshowOptions.$Transitions); //for old transition compatibility _SlideshowEnabled = _DisplayPieces == 1 && _SlideCount > 1 && _SlideshowRunnerClass && (!$Jssor$.$IsBrowserIE() || $Jssor$.$BrowserVersion() >= 8); } _ParkingPosition = (_SlideshowEnabled || _DisplayPieces >= _SlideCount || !(_Loop & 1)) ? 0 : _Options.$ParkingPosition; _DragEnabled = ((_DisplayPieces > 1 || _ParkingPosition) ? _PlayOrientation : -1) & _Options.$DragOrientation; //SlideBoard var _SlideboardElmt = _SlidesContainer; var _SlideItems = []; var _SlideshowRunner; var _LinkContainer; var _DownEvent = "mousedown"; var _MoveEvent = "mousemove"; var _UpEvent = "mouseup"; var _CancelEvent; var _LastTimeMoveByDrag; var _Position_OnFreeze; var _CarouselPlaying_OnFreeze; var _PlayToPosition_OnFreeze; var _PositionToGoByDrag; //SlideBoard Constructor { var msPrefix; if (window.navigator.pointerEnabled || (msPrefix = window.navigator.msPointerEnabled)) { _DownEvent = msPrefix ? "MSPointerDown" : "pointerdown"; _MoveEvent = msPrefix ? "MSPointerMove" : "pointermove"; _UpEvent = msPrefix ? "MSPointerUp" : "pointerup"; _CancelEvent = msPrefix ? "MSPointerCancel" : "pointercancel"; if (_DragEnabled) { var touchAction = "none"; if (_DragEnabled == 1) { touchAction = "pan-y"; } else if (_DragEnabled == 2) { touchAction = "pan-x"; } $Jssor$.$Css(_SlideboardElmt, msPrefix ? "msTouchAction" : "touchAction", touchAction); } } else if ("ontouchstart" in window || "createTouch" in document) { _HandleTouchEventOnly = true; _DownEvent = "touchstart"; _MoveEvent = "touchmove"; _UpEvent = "touchend"; _CancelEvent = "touchcancel"; } _Slideshow = new Slideshow(); if (_SlideshowEnabled) _SlideshowRunner = new _SlideshowRunnerClass(_SlideContainer, _SlideWidth, _SlideHeight, _SlideshowOptions, _HandleTouchEventOnly); $Jssor$.$AppendChild(_SlideshowPanel, _Slideshow.$Wrapper); $Jssor$.$CssOverflow(_SlidesContainer, "hidden"); //link container { _LinkContainer = CreatePanel(); $Jssor$.$Css(_LinkContainer, "backgroundColor", "#000"); $Jssor$.$CssOpacity(_LinkContainer, 0); $Jssor$.$InsertBefore(_SlideboardElmt, _LinkContainer, _SlideboardElmt.firstChild); } for (var i = 0; i < _SlideElmts.length; i++) { var slideElmt = _SlideElmts[i]; var slideItem = new SlideItem(slideElmt, i); _SlideItems.push(slideItem); } $Jssor$.$HideElement(_LoadingContainer); $JssorDebug$.$Execute(function () { $Jssor$.$Attribute(_LoadingContainer, "debug-id", "loading-container"); }); _Carousel = new Carousel() _CarouselPlayer = new CarouselPlayer(_Carousel, _Slideshow); $JssorDebug$.$Execute(function () { $Jssor$.$Attribute(_SlideboardElmt, "debug-id", "slide-board"); }); if (_DragEnabled) { $Jssor$.$AddEvent(_SlidesContainer, _DownEvent, OnMouseDown); $Jssor$.$AddEvent(document, _UpEvent, OnDragEnd); _CancelEvent && $Jssor$.$AddEvent(document, _CancelEvent, OnDragEnd); } } //SlideBoard _HoverToPause &= (_HandleTouchEventOnly ? 10 : 5); //Bullet Navigator if (_BulletNavigatorContainer && _BulletNavigatorOptions) { _BulletNavigator = new _BulletNavigatorOptions.$Class(_BulletNavigatorContainer, _BulletNavigatorOptions, OriginalWidth(), OriginalHeight()); _Navigators.push(_BulletNavigator); } //Arrow Navigator if (_ArrowNavigatorOptions && _ArrowLeft && _ArrowRight) { _ArrowNavigator = new _ArrowNavigatorOptions.$Class(_ArrowLeft, _ArrowRight, _ArrowNavigatorOptions, OriginalWidth(), OriginalHeight()); _Navigators.push(_ArrowNavigator); } //Thumbnail Navigator if (_ThumbnailNavigatorContainer && _ThumbnailNavigatorOptions) { _ThumbnailNavigatorOptions.$StartIndex = _Options.$StartIndex; _ThumbnailNavigator = new _ThumbnailNavigatorOptions.$Class(_ThumbnailNavigatorContainer, _ThumbnailNavigatorOptions); _Navigators.push(_ThumbnailNavigator); } $Jssor$.$Each(_Navigators, function (navigator) { navigator.$Reset(_SlideCount, _SlideItems, _LoadingContainer); navigator.$On($JssorNavigatorEvents$.$NAVIGATIONREQUEST, NavigationClickHandler); }); Scale(OriginalWidth()); $Jssor$.$AddEvent(elmt, "mouseout", $Jssor$.$MouseOverOutFilter(MainContainerMouseLeaveEventHandler, elmt)); $Jssor$.$AddEvent(elmt, "mouseover", $Jssor$.$MouseOverOutFilter(MainContainerMouseEnterEventHandler, elmt)); ShowNavigators(); //Keyboard Navigation if (_Options.$ArrowKeyNavigation) { $Jssor$.$AddEvent(document, "keydown", function (e) { if (e.keyCode == $JssorKeyCode$.$LEFT) { //Arrow Left PlayToOffset(-1); } else if (e.keyCode == $JssorKeyCode$.$RIGHT) { //Arrow Right PlayToOffset(1); } }); } var startPosition = _Options.$StartIndex; if (!(_Loop & 1)) { startPosition = Math.max(0, Math.min(startPosition, _SlideCount - _DisplayPieces)); } _CarouselPlayer.$PlayCarousel(startPosition, startPosition, 0); } } //Jssor Slider //JssorSlider.$ASSEMBLY_BOTTOM_LEFT = ASSEMBLY_BOTTOM_LEFT; //JssorSlider.$ASSEMBLY_BOTTOM_RIGHT = ASSEMBLY_BOTTOM_RIGHT; //JssorSlider.$ASSEMBLY_TOP_LEFT = ASSEMBLY_TOP_LEFT; //JssorSlider.$ASSEMBLY_TOP_RIGHT = ASSEMBLY_TOP_RIGHT; //JssorSlider.$ASSEMBLY_LEFT_TOP = ASSEMBLY_LEFT_TOP; //JssorSlider.$ASSEMBLY_LEFT_BOTTOM = ASSEMBLY_LEFT_BOTTOM; //JssorSlider.$ASSEMBLY_RIGHT_TOP = ASSEMBLY_RIGHT_TOP; //JssorSlider.$ASSEMBLY_RIGHT_BOTTOM = ASSEMBLY_RIGHT_BOTTOM; JssorSlider.$EVT_CLICK = 21; JssorSlider.$EVT_DRAG_START = 22; JssorSlider.$EVT_DRAG_END = 23; JssorSlider.$EVT_SWIPE_START = 24; JssorSlider.$EVT_SWIPE_END = 25; JssorSlider.$EVT_LOAD_START = 26; JssorSlider.$EVT_LOAD_END = 27; JssorSlider.$EVT_FREEZE = 28; JssorSlider.$EVT_POSITION_CHANGE = 202; JssorSlider.$EVT_PARK = 203; JssorSlider.$EVT_SLIDESHOW_START = 206; JssorSlider.$EVT_SLIDESHOW_END = 207; JssorSlider.$EVT_PROGRESS_CHANGE = 208; JssorSlider.$EVT_STATE_CHANGE = 209; JssorSlider.$EVT_ROLLBACK_START = 210; JssorSlider.$EVT_ROLLBACK_END = 211; window.$JssorSlider$ = $JssorSlider$ = JssorSlider; //(function ($) { // jQuery.fn.jssorSlider = function (options) { // return this.each(function () { // return $(this).data('jssorSlider') || $(this).data('jssorSlider', new JssorSlider(this, options)); // }); // }; //})(jQuery); //window.jQuery && (jQuery.fn.jssorSlider = function (options) { // return this.each(function () { // return jQuery(this).data('jssorSlider') || jQuery(this).data('jssorSlider', new JssorSlider(this, options)); // }); //}); }; //$JssorBulletNavigator$ var $JssorNavigatorEvents$ = { $NAVIGATIONREQUEST: 1, $INDEXCHANGE: 2, $RESET: 3 }; var $JssorBulletNavigator$ = window.$JssorBulletNavigator$ = function (elmt, options, containerWidth, containerHeight) { var self = this; $JssorObject$.call(self); elmt = $Jssor$.$GetElement(elmt); var _Count; var _Length; var _Width; var _Height; var _CurrentIndex; var _CurrentInnerIndex = 0; var _Options; var _Steps; var _Lanes; var _SpacingX; var _SpacingY; var _Orientation; var _ItemPrototype; var _PrototypeWidth; var _PrototypeHeight; var _ButtonElements = []; var _Buttons = []; function Highlight(index) { if (index != -1) _Buttons[index].$Activate(index == _CurrentInnerIndex); } function OnNavigationRequest(index) { self.$TriggerEvent($JssorNavigatorEvents$.$NAVIGATIONREQUEST, index * _Steps); } self.$Elmt = elmt; self.$GetCurrentIndex = function () { return _CurrentIndex; }; self.$SetCurrentIndex = function (index) { if (index != _CurrentIndex) { var lastInnerIndex = _CurrentInnerIndex; var innerIndex = Math.floor(index / _Steps); _CurrentInnerIndex = innerIndex; _CurrentIndex = index; Highlight(lastInnerIndex); Highlight(innerIndex); //self.$TriggerEvent($JssorNavigatorEvents$.$INDEXCHANGE, index); } }; self.$Show = function (show) { $Jssor$.$ShowElement(elmt, show); }; var _Located; self.$Relocate = function (force) { if (!_Located || _Options.$Scale == false) { if (_Options.$AutoCenter & 1) { $Jssor$.$CssLeft(elmt, (containerWidth - _Width) / 2); } if (_Options.$AutoCenter & 2) { $Jssor$.$CssTop(elmt, (containerHeight - _Height) / 2); } _Located = true; } }; var _Initialized; self.$Reset = function (length) { if (!_Initialized) { _Length = length; _Count = Math.ceil(length / _Steps); _CurrentInnerIndex = 0; var itemOffsetX = _PrototypeWidth + _SpacingX; var itemOffsetY = _PrototypeHeight + _SpacingY; var maxIndex = Math.ceil(_Count / _Lanes) - 1; _Width = _PrototypeWidth + itemOffsetX * (!_Orientation ? maxIndex : _Lanes - 1); _Height = _PrototypeHeight + itemOffsetY * (_Orientation ? maxIndex : _Lanes - 1); $Jssor$.$CssWidth(elmt, _Width); $Jssor$.$CssHeight(elmt, _Height); //self.$Relocate(true); for (var buttonIndex = 0; buttonIndex < _Count; buttonIndex++) { var numberDiv = $Jssor$.$CreateSpan(); $Jssor$.$InnerText(numberDiv, buttonIndex + 1); var div = $Jssor$.$BuildElement(_ItemPrototype, "NumberTemplate", numberDiv, true); $Jssor$.$CssPosition(div, "absolute"); var columnIndex = buttonIndex % (maxIndex + 1); $Jssor$.$CssLeft(div, !_Orientation ? itemOffsetX * columnIndex : buttonIndex % _Lanes * itemOffsetX); $Jssor$.$CssTop(div, _Orientation ? itemOffsetY * columnIndex : Math.floor(buttonIndex / (maxIndex + 1)) * itemOffsetY); $Jssor$.$AppendChild(elmt, div); _ButtonElements[buttonIndex] = div; if (_Options.$ActionMode & 1) $Jssor$.$AddEvent(div, "click", $Jssor$.$CreateCallback(null, OnNavigationRequest, buttonIndex)); if (_Options.$ActionMode & 2) $Jssor$.$AddEvent(div, "mouseover", $Jssor$.$MouseOverOutFilter($Jssor$.$CreateCallback(null, OnNavigationRequest, buttonIndex), div)); _Buttons[buttonIndex] = $Jssor$.$Buttonize(div); } //self.$TriggerEvent($JssorNavigatorEvents$.$RESET); _Initialized = true; } }; //JssorBulletNavigator Constructor { self.$Options = _Options = $Jssor$.$Extend({ $SpacingX: 0, $SpacingY: 0, $Orientation: 1, $ActionMode: 1 }, options); //Sodo statement for development time intellisence only $JssorDebug$.$Execute(function () { _Options = $Jssor$.$Extend({ $Steps: undefined, $Lanes: undefined }, _Options); }); _ItemPrototype = $Jssor$.$FindFirstChild(elmt, "prototype"); $JssorDebug$.$Execute(function () { if (!_ItemPrototype) $JssorDebug$.$Fail("Navigator item prototype not defined."); if (isNaN($Jssor$.$CssWidth(_ItemPrototype))) { $JssorDebug$.$Fail("Width of 'navigator item prototype' not specified."); } if (isNaN($Jssor$.$CssHeight(_ItemPrototype))) { $JssorDebug$.$Fail("Height of 'navigator item prototype' not specified."); } }); _PrototypeWidth = $Jssor$.$CssWidth(_ItemPrototype); _PrototypeHeight = $Jssor$.$CssHeight(_ItemPrototype); $Jssor$.$RemoveChild(elmt, _ItemPrototype); _Steps = _Options.$Steps || 1; _Lanes = _Options.$Lanes || 1; _SpacingX = _Options.$SpacingX; _SpacingY = _Options.$SpacingY; _Orientation = _Options.$Orientation - 1; } }; var $JssorArrowNavigator$ = window.$JssorArrowNavigator$ = function (arrowLeft, arrowRight, options, containerWidth, containerHeight) { var self = this; $JssorObject$.call(self); $JssorDebug$.$Execute(function () { //var arrowLeft = $Jssor$.$FindFirstChild(elmt, "arrowleft", null, uiSearchMode); //var arrowRight = $Jssor$.$FindFirstChild(elmt, "arrowright", null, uiSearchMode); if (!arrowLeft) $JssorDebug$.$Fail("Option '$ArrowNavigatorOptions' spepcified, but UI 'arrowleft' not defined. Define 'arrowleft' to enable direct navigation, or remove option '$ArrowNavigatorOptions' to disable direct navigation."); if (!arrowRight) $JssorDebug$.$Fail("Option '$ArrowNavigatorOptions' spepcified, but UI 'arrowright' not defined. Define 'arrowright' to enable direct navigation, or remove option '$ArrowNavigatorOptions' to disable direct navigation."); if (isNaN($Jssor$.$CssWidth(arrowLeft))) { $JssorDebug$.$Fail("Width of 'arrow left' not specified."); } if (isNaN($Jssor$.$CssWidth(arrowRight))) { $JssorDebug$.$Fail("Width of 'arrow right' not specified."); } if (isNaN($Jssor$.$CssHeight(arrowLeft))) { $JssorDebug$.$Fail("Height of 'arrow left' not specified."); } if (isNaN($Jssor$.$CssHeight(arrowRight))) { $JssorDebug$.$Fail("Height of 'arrow right' not specified."); } }); //var arrowLeft = $Jssor$.$FindFirstChild(elmt, "arrowleft", null, uiSearchMode); //var arrowRight = $Jssor$.$FindFirstChild(elmt, "arrowright", null, uiSearchMode); var _Length; var _CurrentIndex; var _Options; var _Steps; var _ArrowWidth = $Jssor$.$CssWidth(arrowLeft); var _ArrowHeight = $Jssor$.$CssHeight(arrowLeft); function OnNavigationRequest(steps) { self.$TriggerEvent($JssorNavigatorEvents$.$NAVIGATIONREQUEST, steps, true); } self.$GetCurrentIndex = function () { return _CurrentIndex; }; self.$SetCurrentIndex = function (index, virtualIndex, temp) { if (temp) { _CurrentIndex = virtualIndex; } else { _CurrentIndex = index; } //self.$TriggerEvent($JssorNavigatorEvents$.$INDEXCHANGE, index); }; self.$Show = function (show) { $Jssor$.$ShowElement(arrowLeft, show); $Jssor$.$ShowElement(arrowRight, show); }; var _Located; self.$Relocate = function (force) { if (!_Located || _Options.$Scale == false) { if (_Options.$AutoCenter & 1) { $Jssor$.$CssLeft(arrowLeft, (containerWidth - _ArrowWidth) / 2); $Jssor$.$CssLeft(arrowRight, (containerWidth - _ArrowWidth) / 2); } if (_Options.$AutoCenter & 2) { $Jssor$.$CssTop(arrowLeft, (containerHeight - _ArrowHeight) / 2); $Jssor$.$CssTop(arrowRight, (containerHeight - _ArrowHeight) / 2); } _Located = true; } }; var _Initialized; self.$Reset = function (length) { _Length = length; _CurrentIndex = 0; if (!_Initialized) { //self.$Relocate(true); $Jssor$.$AddEvent(arrowLeft, "click", $Jssor$.$CreateCallback(null, OnNavigationRequest, -_Steps)); $Jssor$.$AddEvent(arrowRight, "click", $Jssor$.$CreateCallback(null, OnNavigationRequest, _Steps)); $Jssor$.$Buttonize(arrowLeft); $Jssor$.$Buttonize(arrowRight); _Initialized = true; } //self.$TriggerEvent($JssorNavigatorEvents$.$RESET); }; //JssorArrowNavigator Constructor { self.$Options = _Options = $Jssor$.$Extend({ $Steps: 1 }, options); _Steps = _Options.$Steps; } }; //$JssorThumbnailNavigator$ var $JssorThumbnailNavigator$ = window.$JssorThumbnailNavigator$ = function (elmt, options) { var _Self = this; var _Length; var _Count; var _CurrentIndex; var _Options; var _NavigationItems = []; var _Width; var _Height; var _Lanes; var _SpacingX; var _SpacingY; var _PrototypeWidth; var _PrototypeHeight; var _DisplayPieces; var _Slider; var _CurrentMouseOverIndex = -1; var _SlidesContainer; var _ThumbnailPrototype; $JssorObject$.call(_Self); elmt = $Jssor$.$GetElement(elmt); function NavigationItem(item, index) { var self = this; var _Wrapper; var _Button; var _Thumbnail; function Highlight(mouseStatus) { _Button.$Activate(_CurrentIndex == index); } function OnNavigationRequest(event) { if (!_Slider.$LastDragSucceded()) { var tail = _Lanes - index % _Lanes; var slideVirtualIndex = _Slider.$GetVirtualIndex((index + tail) / _Lanes - 1); var itemVirtualIndex = slideVirtualIndex * _Lanes + _Lanes - tail; _Self.$TriggerEvent($JssorNavigatorEvents$.$NAVIGATIONREQUEST, itemVirtualIndex); } //$JssorDebug$.$Log("navigation request"); } $JssorDebug$.$Execute(function () { self.$Wrapper = undefined; }); self.$Index = index; self.$Highlight = Highlight; //NavigationItem Constructor { _Thumbnail = item.$Thumb || item.$Image || $Jssor$.$CreateDiv(); self.$Wrapper = _Wrapper = $Jssor$.$BuildElement(_ThumbnailPrototype, "ThumbnailTemplate", _Thumbnail, true); _Button = $Jssor$.$Buttonize(_Wrapper); if (_Options.$ActionMode & 1) $Jssor$.$AddEvent(_Wrapper, "click", OnNavigationRequest); if (_Options.$ActionMode & 2) $Jssor$.$AddEvent(_Wrapper, "mouseover", $Jssor$.$MouseOverOutFilter(OnNavigationRequest, _Wrapper)); } } _Self.$GetCurrentIndex = function () { return _CurrentIndex; }; _Self.$SetCurrentIndex = function (index, virtualIndex, temp) { var oldIndex = _CurrentIndex; _CurrentIndex = index; if (oldIndex != -1) _NavigationItems[oldIndex].$Highlight(); _NavigationItems[index].$Highlight(); if (!temp) { _Slider.$PlayTo(_Slider.$GetVirtualIndex(Math.floor(virtualIndex / _Lanes))); } }; _Self.$Show = function (show) { $Jssor$.$ShowElement(elmt, show); }; _Self.$Relocate = $Jssor$.$EmptyFunction; var _Initialized; _Self.$Reset = function (length, items, loadingContainer) { if (!_Initialized) { _Length = length; _Count = Math.ceil(_Length / _Lanes); _CurrentIndex = -1; _DisplayPieces = Math.min(_DisplayPieces, items.length); var horizontal = _Options.$Orientation & 1; var slideWidth = _PrototypeWidth + (_PrototypeWidth + _SpacingX) * (_Lanes - 1) * (1 - horizontal); var slideHeight = _PrototypeHeight + (_PrototypeHeight + _SpacingY) * (_Lanes - 1) * horizontal; var slidesContainerWidth = slideWidth + (slideWidth + _SpacingX) * (_DisplayPieces - 1) * horizontal; var slidesContainerHeight = slideHeight + (slideHeight + _SpacingY) * (_DisplayPieces - 1) * (1 - horizontal); $Jssor$.$CssPosition(_SlidesContainer, "absolute"); $Jssor$.$CssOverflow(_SlidesContainer, "hidden"); if (_Options.$AutoCenter & 1) { $Jssor$.$CssLeft(_SlidesContainer, (_Width - slidesContainerWidth) / 2); } if (_Options.$AutoCenter & 2) { $Jssor$.$CssTop(_SlidesContainer, (_Height - slidesContainerHeight) / 2); } //$JssorDebug$.$Execute(function () { // if (!_Options.$AutoCenter) { // var slidesContainerTop = $Jssor$.$CssTop(_SlidesContainer); // var slidesContainerLeft = $Jssor$.$CssLeft(_SlidesContainer); // if (isNaN(slidesContainerTop)) { // $JssorDebug$.$Fail("Position 'top' wrong specification of thumbnail navigator slides container (
    ...
    ), \r\nwhen option $ThumbnailNavigatorOptions.$AutoCenter set to 0, it should be specified by inline style in pixels (like
    )"); // } // if (isNaN(slidesContainerLeft)) { // $JssorDebug$.$Fail("Position 'left' wrong specification of thumbnail navigator slides container (
    ...
    ), \r\nwhen option $ThumbnailNavigatorOptions.$AutoCenter set to 0, it should be specified by inline style in pixels (like
    )"); // } // } //}); $Jssor$.$CssWidth(_SlidesContainer, slidesContainerWidth); $Jssor$.$CssHeight(_SlidesContainer, slidesContainerHeight); var slideItemElmts = []; $Jssor$.$Each(items, function (item, index) { var navigationItem = new NavigationItem(item, index); var navigationItemWrapper = navigationItem.$Wrapper; var columnIndex = Math.floor(index / _Lanes); var laneIndex = index % _Lanes; $Jssor$.$CssLeft(navigationItemWrapper, (_PrototypeWidth + _SpacingX) * laneIndex * (1 - horizontal)); $Jssor$.$CssTop(navigationItemWrapper, (_PrototypeHeight + _SpacingY) * laneIndex * horizontal); if (!slideItemElmts[columnIndex]) { slideItemElmts[columnIndex] = $Jssor$.$CreateDiv(); $Jssor$.$AppendChild(_SlidesContainer, slideItemElmts[columnIndex]); } $Jssor$.$AppendChild(slideItemElmts[columnIndex], navigationItemWrapper); _NavigationItems.push(navigationItem); }); var thumbnailSliderOptions = $Jssor$.$Extend({ $AutoPlay: false, $NaviQuitDrag: false, $SlideWidth: slideWidth, $SlideHeight: slideHeight, $SlideSpacing: _SpacingX * horizontal + _SpacingY * (1 - horizontal), $MinDragOffsetToSlide: 12, $SlideDuration: 200, $PauseOnHover: 1, $PlayOrientation: _Options.$Orientation, $DragOrientation: _Options.$DisableDrag ? 0 : _Options.$Orientation }, _Options); _Slider = new $JssorSlider$(elmt, thumbnailSliderOptions); _Initialized = true; } //_Self.$TriggerEvent($JssorNavigatorEvents$.$RESET); }; //JssorThumbnailNavigator Constructor { _Self.$Options = _Options = $Jssor$.$Extend({ $SpacingX: 3, $SpacingY: 3, $DisplayPieces: 1, $Orientation: 1, $AutoCenter: 3, $ActionMode: 1 }, options); //Sodo statement for development time intellisence only $JssorDebug$.$Execute(function () { _Options = $Jssor$.$Extend({ $Lanes: undefined, $Width: undefined, $Height: undefined }, _Options); }); _Width = $Jssor$.$CssWidth(elmt); _Height = $Jssor$.$CssHeight(elmt); $JssorDebug$.$Execute(function () { if (!_Width) $JssorDebug$.$Fail("width of 'thumbnavigator' container not specified."); if (!_Height) $JssorDebug$.$Fail("height of 'thumbnavigator' container not specified."); }); _SlidesContainer = $Jssor$.$FindFirstChild(elmt, "slides"); _ThumbnailPrototype = $Jssor$.$FindFirstChild(_SlidesContainer, "prototype"); $JssorDebug$.$Execute(function () { if (!_ThumbnailPrototype) $JssorDebug$.$Fail("prototype of 'thumbnavigator' not defined."); }); _PrototypeWidth = $Jssor$.$CssWidth(_ThumbnailPrototype); _PrototypeHeight = $Jssor$.$CssHeight(_ThumbnailPrototype); $Jssor$.$RemoveChild(_SlidesContainer, _ThumbnailPrototype); _Lanes = _Options.$Lanes || 1; _SpacingX = _Options.$SpacingX; _SpacingY = _Options.$SpacingY; _DisplayPieces = _Options.$DisplayPieces; } }; //$JssorCaptionSlider$ function $JssorCaptionSliderBase$() { $JssorAnimator$.call(this, 0, 0); this.$Revert = $Jssor$.$EmptyFunction; } var $JssorCaptionSlider$ = window.$JssorCaptionSlider$ = function (container, captionSlideOptions, playIn) { $JssorDebug$.$Execute(function () { if (!captionSlideOptions.$CaptionTransitions) { $JssorDebug$.$Error("'$CaptionSliderOptions' option error, '$CaptionSliderOptions.$CaptionTransitions' not specified."); } //else if (!$Jssor$.$IsArray(captionSlideOptions.$CaptionTransitions)) { // $JssorDebug$.$Error("'$CaptionSliderOptions' option error, '$CaptionSliderOptions.$CaptionTransitions' is not an array."); //} }); var _Self = this; var _ImmediateOutCaptionHanger; var _PlayMode = playIn ? captionSlideOptions.$PlayInMode : captionSlideOptions.$PlayOutMode; var _CaptionTransitions = captionSlideOptions.$CaptionTransitions; var _CaptionTuningFetcher = { $Transition: "t", $Delay: "d", $Duration: "du", x: "x", y: "y", $Rotate: "r", $Zoom: "z", $Opacity: "f", $BeginTime: "b" }; var _CaptionTuningTransfer = { $Default: function (value, tuningValue) { if (!isNaN(tuningValue.$Value)) value = tuningValue.$Value; else value *= tuningValue.$Percent; return value; }, $Opacity: function (value, tuningValue) { return this.$Default(value - 1, tuningValue); } }; _CaptionTuningTransfer.$Zoom = _CaptionTuningTransfer.$Opacity; $JssorAnimator$.call(_Self, 0, 0); function GetCaptionItems(element, level) { var itemsToPlay = []; var lastTransitionName; var namedTransitions = []; var namedTransitionOrders = []; //$JssorDebug$.$Execute(function () { // var debugInfoElement = $Jssor$.$GetElement("debugInfo"); // if (debugInfoElement && playIn) { // var text = $Jssor.$InnerHtml(debugInfoElement) + "
    "; // $Jssor$.$InnerHtml(debugInfoElement, text); // } //}); function FetchRawTransition(captionElmt, index) { var rawTransition = {}; $Jssor$.$Each(_CaptionTuningFetcher, function (fetchAttribute, fetchProperty) { var attributeValue = $Jssor$.$AttributeEx(captionElmt, fetchAttribute + (index || "")); if (attributeValue) { var propertyValue = {}; if (fetchAttribute == "t") { //if (($Jssor$.$IsBrowserChrome() || $Jssor$.$IsBrowserSafari() || $Jssor$.$IsBrowserFireFox()) && attributeValue == "*") { // attributeValue = Math.floor(Math.random() * captionSlideOptions.$CaptionTransitions.length); // $Jssor$.$Attribute(captionElmt, fetchAttribute + (index || ""), attributeValue); //} propertyValue.$Value = attributeValue; } else if (attributeValue.indexOf("%") + 1) propertyValue.$Percent = $Jssor$.$ParseFloat(attributeValue) / 100; else propertyValue.$Value = $Jssor$.$ParseFloat(attributeValue); rawTransition[fetchProperty] = propertyValue; } }); return rawTransition; } function GetRandomTransition() { //return _CaptionTransitions.length && _CaptionTransitions[Math.floor(Math.random() * 42737 / (i + 1)) % _CaptionTransitions.length]; return _CaptionTransitions[Math.floor(Math.random() * _CaptionTransitions.length)]; } function EvaluateCaptionTransition(transitionName) { var transition; if (transitionName == "*") { transition = GetRandomTransition(); } else if (transitionName) { //indexed transition allowed, just the same as named transition var tempTransition = _CaptionTransitions[$Jssor$.$ParseInt(transitionName)] || _CaptionTransitions[transitionName]; if ($Jssor$.$IsArray(tempTransition)) { if (transitionName != lastTransitionName) { lastTransitionName = transitionName; namedTransitionOrders[transitionName] = 0; namedTransitions[transitionName] = tempTransition[Math.floor(Math.random() * tempTransition.length)]; } else { namedTransitionOrders[transitionName]++; } tempTransition = namedTransitions[transitionName]; if ($Jssor$.$IsArray(tempTransition)) { tempTransition = tempTransition.length && tempTransition[namedTransitionOrders[transitionName] % tempTransition.length]; if ($Jssor$.$IsArray(tempTransition)) { //got transition from array level 3, random for all captions tempTransition = tempTransition[Math.floor(Math.random() * tempTransition.length)]; } //else { // //got transition from array level 2, in sequence for all adjacent captions with same name specified // transition = tempTransition; //} } //else { // //got transition from array level 1, random but same for all adjacent captions with same name specified // transition = tempTransition; //} } //else { // //got transition directly from a simple transition object // transition = tempTransition; //} transition = tempTransition; if ($Jssor$.$IsString(transition)) transition = EvaluateCaptionTransition(transition); } return transition; } var captionElmts = $Jssor$.$Children(element); $Jssor$.$Each(captionElmts, function (captionElmt, i) { var transitionsWithTuning = []; transitionsWithTuning.$Elmt = captionElmt; var isCaption = $Jssor$.$AttributeEx(captionElmt, "u") == "caption"; $Jssor$.$Each(playIn ? [0, 3] : [2], function (j, k) { if (isCaption) { var transition; var rawTransition; if (j != 2 || !$Jssor$.$AttributeEx(captionElmt, "t3")) { rawTransition = FetchRawTransition(captionElmt, j); if (j == 2 && !rawTransition.$Transition) { rawTransition.$Delay = rawTransition.$Delay || { $Value: 0 }; rawTransition = $Jssor$.$Extend(FetchRawTransition(captionElmt, 0), rawTransition); } } if (rawTransition && rawTransition.$Transition) { transition = EvaluateCaptionTransition(rawTransition.$Transition.$Value); if (transition) { //var transitionWithTuning = $Jssor$.$Extend({ $Delay: 0, $ScaleHorizontal: 1, $ScaleVertical: 1 }, transition); var transitionWithTuning = $Jssor$.$Extend({ $Delay: 0 }, transition); $Jssor$.$Each(rawTransition, function (rawPropertyValue, propertyName) { var tuningPropertyValue = (_CaptionTuningTransfer[propertyName] || _CaptionTuningTransfer.$Default).apply(_CaptionTuningTransfer, [transitionWithTuning[propertyName], rawTransition[propertyName]]); if (!isNaN(tuningPropertyValue)) transitionWithTuning[propertyName] = tuningPropertyValue; }); if (!k) { if (rawTransition.$BeginTime) transitionWithTuning.$BeginTime = rawTransition.$BeginTime.$Value || 0; else if ((_PlayMode) & 2) transitionWithTuning.$BeginTime = 0; } } } transitionsWithTuning.push(transitionWithTuning); } if ((level % 2) && !k) { //transitionsWithTuning.$Children = GetCaptionItems(captionElmt, lastTransitionName, [].concat(namedTransitions), [].concat(namedTransitionOrders), level + 1); transitionsWithTuning.$Children = GetCaptionItems(captionElmt, level + 1); } }); itemsToPlay.push(transitionsWithTuning); }); return itemsToPlay; } function CreateAnimator(item, transition, immediateOut) { var animatorOptions = { $Easing: transition.$Easing, $Round: transition.$Round, $During: transition.$During, $Reverse: playIn && !immediateOut, $Optimize: true }; $JssorDebug$.$Execute(function () { animatorOptions.$CaptionAnimator = true; }); var captionItem = item; var captionParent = $Jssor$.$ParentNode(item); var captionItemWidth = $Jssor$.$CssWidth(captionItem); var captionItemHeight = $Jssor$.$CssHeight(captionItem); var captionParentWidth = $Jssor$.$CssWidth(captionParent); var captionParentHeight = $Jssor$.$CssHeight(captionParent); var toStyles = {}; var fromStyles = {}; var scaleClip = transition.$ScaleClip || 1; //Opacity if (transition.$Opacity) { toStyles.$Opacity = 2 - transition.$Opacity; } animatorOptions.$OriginalWidth = captionItemWidth; animatorOptions.$OriginalHeight = captionItemHeight; //Transform if (transition.$Zoom || transition.$Rotate) { toStyles.$Zoom = transition.$Zoom ? transition.$Zoom - 1 : 1; if ($Jssor$.$IsBrowserIe9Earlier() || $Jssor$.$IsBrowserOpera()) toStyles.$Zoom = Math.min(toStyles.$Zoom, 2); fromStyles.$Zoom = 1; var rotate = transition.$Rotate || 0; toStyles.$Rotate = rotate * 360; fromStyles.$Rotate = 0; } //Clip else if (transition.$Clip) { var fromStyleClip = { $Top: 0, $Right: captionItemWidth, $Bottom: captionItemHeight, $Left: 0 }; var toStyleClip = $Jssor$.$Extend({}, fromStyleClip); var blockOffset = toStyleClip.$Offset = {}; var topBenchmark = transition.$Clip & 4; var bottomBenchmark = transition.$Clip & 8; var leftBenchmark = transition.$Clip & 1; var rightBenchmark = transition.$Clip & 2; if (topBenchmark && bottomBenchmark) { blockOffset.$Top = captionItemHeight / 2 * scaleClip; blockOffset.$Bottom = -blockOffset.$Top; } else if (topBenchmark) blockOffset.$Bottom = -captionItemHeight * scaleClip; else if (bottomBenchmark) blockOffset.$Top = captionItemHeight * scaleClip; if (leftBenchmark && rightBenchmark) { blockOffset.$Left = captionItemWidth / 2 * scaleClip; blockOffset.$Right = -blockOffset.$Left; } else if (leftBenchmark) blockOffset.$Right = -captionItemWidth * scaleClip; else if (rightBenchmark) blockOffset.$Left = captionItemWidth * scaleClip; animatorOptions.$Move = transition.$Move; toStyles.$Clip = toStyleClip; fromStyles.$Clip = fromStyleClip; } //Fly { var toLeft = 0; var toTop = 0; if (transition.x) toLeft -= captionParentWidth * transition.x; if (transition.y) toTop -= captionParentHeight * transition.y; if (toLeft || toTop || animatorOptions.$Move) { toStyles.$Left = toLeft + $Jssor$.$CssLeft(captionItem); toStyles.$Top = toTop + $Jssor$.$CssTop(captionItem); } } //duration var duration = transition.$Duration; fromStyles = $Jssor$.$Extend(fromStyles, $Jssor$.$GetStyles(captionItem, toStyles)); animatorOptions.$Setter = $Jssor$.$StyleSetterEx(); return new $JssorAnimator$(transition.$Delay, duration, animatorOptions, captionItem, fromStyles, toStyles); } function CreateAnimators(streamLineLength, captionItems) { $Jssor$.$Each(captionItems, function (captionItem, i) { $JssorDebug$.$Execute(function () { if (captionItem.length) { var top = $Jssor$.$CssTop(captionItem.$Elmt); var left = $Jssor$.$CssLeft(captionItem.$Elmt); var width = $Jssor$.$CssWidth(captionItem.$Elmt); var height = $Jssor$.$CssHeight(captionItem.$Elmt); var error = null; if (isNaN(top)) error = "Style 'top' for caption not specified. Please always specify caption like 'position: absolute; top: ...px; left: ...px; width: ...px; height: ...px;'."; else if (isNaN(left)) error = "Style 'left' not specified. Please always specify caption like 'position: absolute; top: ...px; left: ...px; width: ...px; height: ...px;'."; else if (isNaN(width)) error = "Style 'width' not specified. Please always specify caption like 'position: absolute; top: ...px; left: ...px; width: ...px; height: ...px;'."; else if (isNaN(height)) error = "Style 'height' not specified. Please always specify caption like 'position: absolute; top: ...px; left: ...px; width: ...px; height: ...px;'."; if (error) $JssorDebug$.$Error("Caption " + (i + 1) + " definition error, \r\n" + error + "\r\n" + captionItem.$Elmt.outerHTML); } }); var animator; var captionElmt = captionItem.$Elmt; var transition = captionItem[0]; var transition3 = captionItem[1]; if (transition) { animator = CreateAnimator(captionElmt, transition); streamLineLength = animator.$Locate(transition.$BeginTime == undefined ? streamLineLength : transition.$BeginTime, 1); } streamLineLength = CreateAnimators(streamLineLength, captionItem.$Children); if (transition3) { var animator3 = CreateAnimator(captionElmt, transition3, 1); animator3.$Locate(streamLineLength, 1); _Self.$Combine(animator3); _ImmediateOutCaptionHanger.$Combine(animator3); } if (animator) _Self.$Combine(animator); }); return streamLineLength; } _Self.$Revert = function () { _Self.$GoToPosition(_Self.$GetPosition_OuterEnd() * (playIn || 0)); _ImmediateOutCaptionHanger.$GoToBegin(); }; //Constructor { _ImmediateOutCaptionHanger = new $JssorAnimator$(0, 0); //var streamLineLength = 0; //var captionItems = GetCaptionItems(container, null, [], [], 1); CreateAnimators(0, _PlayMode ? GetCaptionItems(container, 1) : []); } }; //Event Table //$EVT_CLICK = 21; function(slideIndex[, event]) //$EVT_DRAG_START = 22; function(position[, virtualPosition, event]) //$EVT_DRAG_END = 23; function(position, startPosition[, virtualPosition, virtualStartPosition, event]) //$EVT_SWIPE_START = 24; function(position[, virtualPosition]) //$EVT_SWIPE_END = 25; function(position[, virtualPosition]) //$EVT_LOAD_START = 26; function(slideIndex) //$EVT_LOAD_END = 27; function(slideIndex) //$EVT_POSITION_CHANGE = 202; function(position, fromPosition[, virtualPosition, virtualFromPosition]) //$EVT_PARK = 203; function(slideIndex, fromIndex) //$EVT_PROGRESS_CHANGE = 208; function(slideIndex, progress[, progressBegin, idleBegin, idleEnd, progressEnd]) //$EVT_STATE_CHANGE = 209; function(slideIndex, progress[, progressBegin, idleBegin, idleEnd, progressEnd]) //$EVT_ROLLBACK_START = 210; function(slideIndex, progress[, progressBegin, idleBegin, idleEnd, progressEnd]) //$EVT_ROLLBACK_END = 211; function(slideIndex, progress[, progressBegin, idleBegin, idleEnd, progressEnd]) //$EVT_SLIDESHOW_START = 206; function(slideIndex[, progressBegin, slideshowBegin, slideshowEnd, progressEnd]) //$EVT_SLIDESHOW_END = 207; function(slideIndex[, progressBegin, slideshowBegin, slideshowEnd, progressEnd]) //http://www.jssor.com/development/reference-api.html pytango-9.2.2/doc/_static/logo-medium.png000066400000000000000000005211741316324373100203410ustar00rootroot00000000000000PNG  IHDR_sRGBbKGD pHYs\F\FCAtIME  '3CwtEXtCommentCreated with GIMPW IDATxI IDATM.F"[@OPT,W !  V6CA=#U'7< % V:>=!!W6L|q? I &  %%&ZEztdz&NXHz1)5 69:AE4GLآ7/1  bQwV>~h.-    .40Z@ 5 ƍfn3 &$*H F +8# H  @! 'G'  k IDAT  /  4;$6> A"I&%  6"%%4 <   !##,(,"  !F*,+H2ESX^@" C A; 6:& =(()#"$ ! ( I%XNxWFO7*;(ҋ^df    3VLW  < }=:iWQ5( 5::   8%&'$ !   j68cL ()*    +&+:;+AJ 6`Gc!*]QDOK2H$M'f IDATx,% "p9s$  ,8@*,+-   ܴAMS1H G$< 3ס2'! '(̼ػGQO5 颐A&FµL%I3::# " &$ : %'  8   , **4;/*ڥQ&! K ʺ#Gz{$&+B%' f C!!6  މ?? *-)"$  d  2Yg  [=*x , X   DJ+' k.*(25:ս0zf ),)G6=< '* q I€VIZ2   DF- !*) P(0 I31&##`  0 ځ@R&'ݺ ? /d(4C=@'-.'* #C38D<=a GL,Q5(L  : # M1| )`2<>6:B;=Bw$z7 ,#A-C IDATH  #",.>=M 51 $ @+$053v6BCI?IHN A 4 $QC#<  +'".A- 7 + A%(7Evz\L ! =8;D@C o '  &$ A"#*P}ZG 9 RKQ&%02$$D k45'+CIq4B $'i D'*!+. %# 5 Gr$%,j`+69$% @ - NWP  !$1)Q!%()*7;   %, %KC"##'컸Y&(&v+8< (, -  5(! dBH AZ IDAT }#((0>C oGA  :Y4:5,-G$*  ^8KP&031Qv5#'   ƽ    xA^%032=AͿqĝI $    ';MQ ښ q< ` !    ' 5BF t ") 7 "  j =JN st   8CG҇z~[Oa 5' wM;k(//N2?E~s0  ŷ I- "f%'&f0:>ߘrr  $$+*.  MHQS+56  VĽ  7 IDATE#4l$*-'5;+} Wn j$C% r#&$Q-8;$2:.Ҽ  s 6 -;+a+59!' 7 O^UODT[MAC=  6     ,wF3 8 #)  AC 2wfP A燼95a '%"uF=Jg_vj" '>$(4Dt M& T h!  33X _  G W#$ %5< (+6 *22D8 RC aF,9>" $,."'3J:Y;$ܟ   ;4>`gm "$@NS:OU*.EL"-? >rw/   *  !!  >@Z B]AlT)r`R 248.mt:  #NPS' !!!6=5 T"|!$! ;I/9:  4& t U|9>4B &>s %(2:6 O6">)9*GF9!(C4+ . / H}   9,69 *Cx(D&+' 5 IDAT1   5%&xR҃$ *J>L)13  4GɆ8;* !uaU*)=P7$W>1!((!4&<3  L #(),65"+#CJ'/ ]@1A-$ 0o ?j !   4W<-$ 9+ gF3D/$ .   )^Ybo$&37  H  #V)!"&;0    $mu Q   M$' =׹84  &ǟ   $RSZhs$,.') ()- dP.щ /:8=4  @+  "8 9 ([ !  :+ B ׷@J E@"  $ /!  9/O>3>M Gk{ $3"8@ '( | IDAT[ȼ̪6 7"# %W@ Dt|!"2&5<'+ # 3 ")  88   ! =< L&)4&5:#'#*(Z  EBF<7 )) B(,#6:>=%( '#     5l.-ݻ,69   )0    ҿ    M  (&)(-(1-@T;  #(  #$2CZжB   L@Nbfc$D歳Z[ ` Z ] XJ4   E '  ơ G   X?A!u } J ) "!   ,k#. -ᅴ)(*K $ V?@ h  #  :2 /2+2/   0 &5| ."  ! ?)06 * U ?"  &:*VR>,)# !   B,!     &  QںT?, IDAT>  .!VO;    $!3'A     ;̜-#    18(4,!  1.$59-     # /)  츘  $"(. 1:: $ @#=rnp      %4;  # #     =m ڂ##!=   !--!   "D> E1  *NV?670     X1С5 IDAT*$,` !    , +  3=+*  %a'U&$ %)'  + !IAI  ;   (      G}2  "    Z      9|Q!   &H 8,   *# "ƿu6Y "H.  B     /+   "o05u%* #   PJOK !      خܚ oᬸc4: ,  % |7     2&*  ) {#4^Ԫ  ? 4%Hn#< 7     ̖1)2-      ͶX28 #  %ߕ& '9   b(> $    00ϕH     r@ׅF8    J2<٦O]O5 % )  2RKVYU=x7* h(E O&v-" ( ľR@+ `"+`D6 IDAT   ̗XB Tl*599)-."     ` .  & "n=Uz$02^*48%.1        `#"""&! * H@f>טAIQ x&)h'25:%/2 #    i(0e'-   Й~pg ʪ?I$(E"%&5 (    mz"  8ze6):(D@(0y9C6 8&*''"#3   $&   )4/Ǿts )0/H+M ;'+." [73ۼ ! "  }~x  : `p_W ں:?8 0   ":/ +@@. -*px98*E}JzI1 4#$#% a F"3 5'WQ9#  p͠raT L2 ɋ$37+  $> < !-!ZR:$8-" 7B6Q+68/  H-)᳻* $4YE1$6."f[ŀ IDAT7PO".!. 1($    /& D;.)ٽ%CSOy'68c)36B$)+&)   (! <μH$=K ((`.:=?#*-#&   :% $%< Ӻ`HM ow-;?@(,.%)    4%$'; ֻK+"ߟ%59U/67 (,   %!.;ۺK* .B    7& 45L4& ?PT    /$ 24^GD Z.9<   )  47 #   !$%$v  =h IDAT 3^B1#%t  j+ +3#"#!v $u=3+"U   ;$& w"5͵ 'F򲔜'.    mG6 "#+jsB  ?KM& "  զBzkS<=# K  -  w89R<,̫tvH.!G$/3   pٻ:>>15    ) )4&!G)>(+   %@GK(Q    3  15#F$B  ν& J    8)! 3ֺG']  =*"-*)*) ,$  0ɽ952?  HokC  +  : ʽA+#G" > Ǹ'V  IDAT  3$2B2(G%'' A    1" .Ⱦ?605"$585%0  ) CʸE- :#%?׷+N  #/>>;˼>%? $U    + 0ҼA-$@ /*)'&    ,!  &=ɹ963M 5<>& M   !Eȷ2JSM"#׸( N   . (J;7ĵ832G!# .()&)  * .1.>*?%'< ȸaD    %HF;0*ѾA)8%' *W       Oz IDAT "9:@"ܺC+1%( >'ø&1   #G@G=9ιC+ 3"*, +" 8*   !6:C ԹB&-!#$ >#Rzw)=      !MA]M5ҮD"K  õ"R      DG?≠ cR B1&(O    !9C$ź׼:30G&)*J    /2<%ļA* 9!*, =l* ;     $7AùD#9  -< 3++@'     v8  7<ƻE R   *ACNN07  #Ã|43  7@ȹ1EHO$&  +?Ϛ+E   ȎA#7   9@ IDAT:+%<), +A #'O ȸE%7  2@"ŹD%6$$   5@ -NTV'*R  -++*߶E$8 5BøF!\  )Ǹ?K+(-S    :(%#صC$3 7@ ¹5HOR"$  ED0eJ<Ũ&,S    G'#"̷=+"!&  9>ú>-"=$& (7ہ(1Q  C%&8ECʊ9:72   a  .&W      **$$ʗD *   A> ĺb8&dyhXP?/73($&    ^d0!"  1  z%  7?=7Vc̾0  GH-( sYH  -^%''{ø 0 i%bDjqʱ2(   & .-+1343/" FrG  Iz0ߟ¼pX>1(tbff kmi+-պ  hP.% KRYYG A( 9  F#=-% D- ߭#'&l @ Jٯ @+ . ;BAets}{7        - xmi[ (*nͻº .( n 5z  .!(,+BEDi{ (   &/'!335ӡ'$3G:;    l8E0Anq0:*0H 07 P9Zil\xu}ƽH ) % BFE--/@k<       \ݹ1#27C$TFq-%# v2/08   u¢lB IDAT HY:0'G^O  t6993jþTbf9C@;  o  ! ILLSM('CII255vlIDAT L!IENDB`pytango-9.2.2/doc/_static/logo.ico000077500000000000000000000076761316324373100170620ustar00rootroot00000000000000  (@ ,+2-,306&' 2 @&$KYx)-y+)06694;X,\)1i,&K"3z'M#2g+2i,)U%* _y""913K.S+W2`/4d1:s69r6:s7,])2l.'N ~W|k^ydg;9 _y7>^!; yq!aM)ܲ(G&w=u:'G%kw్u f4Q|SjnsW++S)?v<!3 Bi")U&*V'0s+T)@w=#4X~bjӴU|g.\-$B"t@x='C&Uw߫zp8wGl($'"@92 4w/QB'-}'7GD"E 4 pM沽Q$$-44] {:$ 2<>>%(  KRP-r a}+Z6fEF@\nrM,24   0 $& p;7;FG9??4<>PE9GR # I T6'  V  0: ;GH/8:+Ǧx-%&ػ- AQU09< ľ-P[[ 1 #((A5EJ ȿez  R   yȽϽ.:> Žcez'.  ?KOfh۩E&+,)@QV ǾUsS vOМ IDAT  /89;!s<  H%?+22e0FPwT{G1O`@=<:2     +36ʽ$6= "*,O|8-zt'ӯŲ|0-2/P c6@P(k )+  1/U* 08:сR(+]49\",  *.-ԏp0&.........../8P{,  >ѫ .108{. S*hK"lGZ*6:0+   5I:(|%G lwH&+(4??ͽ!),H799J!$#!17@@?+ m5G$F < u&,.*->~6Q)Hf]@+) tP=  F. 'gJ˜  ul{Hs96Ye @'kN>)'MQQQQPQX   EU4Vו*36#@ K.+ !,)8-ֺgkȿ !+.'(   HN IDATg(   NuA!" 4ܵ 9AvXNKN(, 1jB#&'4??a8?XAO@&SJ I.i   \kؿh078 /8: ߎY{=l%*Z  )!9+&r051gۢn!# >PU1 %ANSk%.0"(* &  84;3SEEJ./R-/W.3" .57      d)NY6:tU57G! 3>Bʽ<ϫ  $' cQc4\2y 5V57KV(&/2dg[.- $   6!|"*t۪$ -  .ooǼdpj3F)~ʫ O ! Tvrdn7e`I 7CHɿPPEdzZ_fK  :ySQ 6 IDAT$  ' #;6+J,bNK J[8H9,7,P"  !  0 >(`Sm"C061; B 1-  AP>#)) -pD1560 #"$;'    '  ## &#!q`0H0   5 4 XnO31.  _~=FWi51'(3(i^>* &01# $( t+  ͻ< wZ3   $(, ټ;s&l5"v %"  3" 9   CA.ȾowRnԾ{  $m#yRLJ2 IDAT'$'i ' %)  긴b2.; 2#$5!! 8 & _3`r,6|<4 1 ;= u.  -4 .5#tװ ,.s#,,,*J5Ȗ+ 34oNg) N3e  ); 3d/V ځ uo:]3;@OTOq < B옳nj5g0icN(k  M3 A; iLWa* 16C 4BF1$  jw**0!h7 %-7G".3M4&* -     & ˃'9 <<' 8<X{'9@ pI=Ϧ%%!>-@+"5 49Ӻ1/2Ŀ5}|`tœ IDAT]t5#)ae+11  $!; 6U=VJ5v /5>L$,-II ! #<%!%J SG3ukPA(A(2 V)46.79  #I!D ; mbK@O* 0=A:#)+3 +85" FEd+// #*+@5  < 17B<8EJ5jQ4  4W׉|cbs8  =J+" GUY-–8- IDAT8 V"% $($-hWҸſϣ)I .    @ %'02 U"$  2"J  B!  9 Ӱn͂  !07S8* ,)/"%&B$eđ(.b ) ;FJ( k  '- ,݈& = @! * l -!15 <;:j'   G)+<+ K14 +X  -L( PBak?CBi ! Vk- 22// :FHe]q s0*,   #)d   3 '"%% ^9! )!$'2446' #w.+)\5@HIKD;Di IDATN34(7 '  l;+3HD ļgm\V% [&RcP-e}X"Z EMN5"$3')5(  F5D1%// #$$DȖ{jr >Y&LA@Q,HvZ.XGL+ j&,-)A0!     L(r o:@@Q'i/*E;b(9 '4 <   :1,` *&1mu&/3'03Em> ׼APSa*4 :RLt7|EfM R)m#7Ĺ.220>FK%\! ?^#nBPT\Er [5@@?B=< )O(":n&+-RA%.%&b#  ڂ6 M  "/3"+(.r o@@G HF`>BH`PU <V#&O .Qԭ+ c 5&ׇ:sF  -#-bO10n޾k)+*9FIP+++ $ = q[$ϱ3ʷ>LObMܹ#Z5'"UHɷ {+=.轼 ȲK`dDR+wM: hP; ҍ IDAT×I4*: EQP)c>R+ %7 ƸF   =>e!-5Z!=  CTYOo9 o0  GImj "U/B"N67R'e7 8(#=1/.'-oqӨ4"+/4&,-ALOe&A" " :p(0,D &  H-+  "V}5Gb̌6HM  HUV>SlB0Vy .d@r=o0=d5f3#3 B/ 'S*/  #!K"L=)܊̦lޞ  HRT4))'՝P)i{(IRHA; 2w#:$ # 2 _ $Q׳ 1'  0 $ , kf044 8AC %1fKAxDF AvsshUWW }   ":  ( egՐt dQ3&_H '.(sA/u& xbc $ն/Wބ}dUWnuTFY^ATY[@%'(o{9<\ s8J^=+ [YY qv|_ƷJŽ 3.+hQ:(N;e/ un fE4a 4((zn@@ZlO=MZ"$ 'KO J;īEHd,B   O :   {bfCAv# A 3 @0+,^Ԣk: IDAT^,_2LV T :D*S|3Hӣh #& 1 K @    42,ƷM&*|%,3 l/..kK"-(;bWsm=[ixv ; &%n5=7`"(# FS*  # "T5 o$5= $" mZf Z9JN z!i Ii Kc6c- 1--L B>Tuj͵mZ < n@ 611H /,į2KQ0  %hjkVG\! '+&,?DD9&[uGIDATY"{l[PIENDB`pytango-9.2.2/doc/_static/motor.png000066400000000000000000000317631316324373100172630ustar00rootroot00000000000000PNG  IHDR>asRGBbKGD pHYs  tIME 4d IDATx}yTsNUUo(  KĘLLnԘq2ɝ8cƉsu2QA'5Fu۩?̩s#]U|.-? ۷o>0ׯR0Uӧ*//gx={"dťK:s^{$Ȝ{)bٵko``m>}jfD"BE( me& ,d2% ?8oڵǩpQ\qB&,Y~j([A!\A˲ 0d2>(? ! (&il$F|V\MUUU|>f͢.I6,{XVpX,B :%4%<} _} (BDQ!$%AazAxb&2%8+ ?1~_BQǓO>[-Kj8NeYB *2f3L*)EQ_CBe,"N&AƇ7o%8k=z?Dl6q*~8N|>X,fjl6b@e*=TeYWP]!C/<̜9sRXX3D/18nG):;;ѡ~ 2Q ,a8Npe˖kО~,$ BiӦIo6.]z&¸SwY:t+GE*RdY&ZNBGOwHӰX,3gQUUb @CA$b_8rK 0ݍJK/4׿z;rH!DaYh%tf J-)y$I̙31c L<&M$I~0 <B O~EQjͮ]Bfs!:[2dS$Á aݺu2e ;-aEEJ$Ir~z ˖-F׿5n կ~70(D(Ҽp+>9s\bFtuuyZ? v]$h-p=h;z#3 x<غu GP/!\QQ1\ޱcn0??斖ﴷ[,l6b~$]wmF60 Q!2nF\ v}>*T0 ̂ n??ٔGtq X,'{F ŷ#d>M&%@"W\n+WĐ7^R))oݺu /^?8ܼ2aUFuQ Iy\.466;V=#DJT*uߊ+/^wW o>?jnnv,k|z=M4#?z 23fPH$0 F`Y >ڊ)SLٳV8pol6jkkQ[[1s %: ڳgԧ~?SL&²,"5abϗxz*}dZ@\|9n ŒAl )p8L^-fzf4!hѦ'NpwΘ1|n8{xq`)wG?CO>`߾}=>1]OVNrFAG>=]dwy'6lؐa4cBgV\9yL*@ <rh6 v;V"IV* Ef&,'X繍7[ؼy3sϭtcǎ8#AXzH!8 9g%*u[A-[e$a{H<Ƶ^諯5k4Aq۶meYeD"X,ib3[YB(b N˲n`w*++wX`'-[( 駟^,˰wb,F9 h4F-2:Mկ~X,'F1tP(`ƍQIӧOtuu|$IRGH8{,z{{<v{em( 7d2X,nYf~?ǎ(iSh 5~s$gl!п,U&gOID[j]`޽.em"0f34Qa` rI.*QA x ü\UU>bϞ=˩(cYPxf۷VP\\b|eJ0YL&jkka000ӧO~%%%q 7rA$̛\U sV՚G@nǛoφ  fx<B! ٳ8s JKKqcŢR)0 J̛7gFEE*H$=Y!f3JJJPQQӉP(N~_7nDqq9mFޅXZMMMX,^YaZDAK]V3 cT*T*ɯGeҚtZHӈby^UNTQUUzx<,h4h4z2۹xt~?jp7 s.;ڽ:qx p Øp(8d_~) p 9PZ0^/heVUqEГٮ%0N%%%ꪫ I~$IVHI>hhh&z2WÅ> zϵJ-d2 ́zZU{f," ȑ#ؿ?Μ9QOذ/ĽP"G?BCCChC& m ,XV^^1Lnz*Ri6.K룁zBV (X,j  jbҥXj(ZZZpQ9shH<몝{3Z#bhiiԩSQ}rO/0k9|curTDҍ4ijjj< bAPITn vf¼y .DCCL&@i{ٜjŸTp&5F#崏!OXx6."2 ĐT*/]&_1}ELÁiӦaٲeG<Pj@R<6n܈yhdif:Ʉ cl*0T-KM*їع_M^<{.Ui8N\~8Ch)i |G)FHN>bE*} &(]Z[[q_$Y/{fp/YYbQc J!}-.mO٬ Pbt"'n4 /~T*g x>x/8믿O="ڜb@k!x,p XVDQ477>jSJ{4K1FXǎD"8x8PPHRDZdwL2b@"Dww7^*PHX,D XbA4;#npXs\HRX,x<8s !(,,Duu d%zbv;hnnF,Cyy9l6Z[[AAaa!:::(& [+ՊI&a`` HYIWZuFU@___n8s 6O (bXx1^1JUQ"#G?ϖ$!H JN6MmeaQUU>jn%%%UC٣ށdKa$- F?gʘ|>L&phNy?8ED]VmďnSJhH eeep8eY =ǂPY,l65gQEi5f3o va|s @%aħD"qjΜ9' F4zHOhT/v4 nGii)Dp8H$>?hNEh4mmmp:=0J$ԑ`lT Di\hd2DfFcEHi吞Fx<|bjhKKKF4n[=<3gΠHӸaZU{!^$ak/0j(X,v?]v @˷("re &2k8W@xDbLKOT:!L?3А|>8"}ƃd ǃp8 ׫V % 7 6z'NX,t: ىQq^ݫb[$ NvE m؈h4 AP[[۴u]#2k- {/IxF'4e{ \ө^g:F"f8͛7l =QZդ<=wq!3@* +=TnG<-#L&nqU 4b=["@(R3^+Ƣ]P m ZM;]Ŭg3, ˲䦛nCm!DhQDŽrx]wd"huh3s0 DQDWW^xx^tMARz9  EEE&vfHJJ#3!bŋۿ!ɖ yLeYΒ@Vam.uVmmm8|0, jkkՁǞŻ߿VAtuuaɘ>}al hԿivH3APn7?o}ѻ !SNM82cܨ(Y µ̾}с2x^adX @XrrDc䎦GvN'Zq8ꫯ~^7cǎsB}8h +**c=׋*v_{"MiJ*(ۋ[nEk2@iA`M088R4 dΜ9/n߾o !{7t9DTdR);˲D$Q{PAw*b׾ L|,>:g`0hYfy饗n%$}YlذᜆeD{{OWp8rOF:FQ yR)$ ~ s=W''4J ܌Zag~eΝ_ $oߎ7\x$nuڿY, P]сP(c޼y8[L$+UG10˶#Bаwꫯ~zΝ!g}?/!$kǦ7T)H2AQQtb`jq7IڎTl1yz$Ijx6].?oӟ5!$SOaӦM8_kDPYYIO=YoT,=١[YrёaE"b̙j_rewՠh;wkGiU(bF|YI͛׿{3QA)8Ft1IBfϞgΝ !۷[oŅXyI,ԏy'ODkk+~֕B&^XV]V2#"RZ;8Q h7$IX,d޼y^y !m۶7o\ P]]}===8qBMS7S$|><$I裏P[[%Kt@?+;v"cM>2P<ǩS(bڵk3!$SO]?j-IaZTWW6 mmm!M& }OVs/^@ юv>c%M$={555jz} RW?瞭?/˸+/D"N Eg9;1o:n(--lFQQU[a<1_2eJFn,~ohMMM&so%Kz)qV^ PZZ 3gΎ@ ժju )uAqq1kJ-6,I&6 fZٌ_ Ðٳgz}.O^'* &MzO lFQ/PH;g5:bFޑL&ϫHN,Css3i\b>CL,ٳgٵkBH'ߎiػw/DQ| S"z{{ۋwgyy9b hf%@/ߎ{̙3v3F#MwnQٳ{n-[pQ)+V@QF+ gA +psoQDYQVVADQqmbDN l8p*++qWjhG& )d(n à3#dW#?ڑk*玎|>̛7cjĘѨ/AA`ZE\k @Bo>,Z%Ka~|ԩ?' I&72'VǏG__.qr\.R.N'mc8`ϋ-Rϝ;~t!^d^x꠽~^/bzzzAN4M/ӺCutQH$ 3"G_좋hhh%K/^v\e&i:q!PK375A'ՊZȲ`0`02>-93b hhhPXf}݆d29nobk 3r%zQPPkfbLpvB*x.}9' ]ږ .<xSSkdҙJ߱pPE$4YS mߌʬai9uB\:/jkk,Y$pv-C_vb12d+F'G0ӓڊ|@,,c,T9 j׵^+HEQnjjYTTlNMF24Z7'0WH&Z?ӌ"-H|h:u 4Ν;olsݗY,l:Qj9~t(2g K Nwիkl0fժȲLK.eĈ`yЮիW,X*WyXvǿ@˲Υk g:q`׮]j2 3sT*q>`Xriۛ[Xre7^ve"ȿ8Bz3d,.{c-[LN<ʾŋ߻b ["뮮X,eYc\QzL&+.1S?a|򲞞GT*Q T>=wG%^nOjllTxM4555- B ].ټ8=!x}h =D RF 3kPH%8ǫn>vqWx[(1QzZ[M<JKKq%Ng`t:O]Rxi7|Wn[tsh]]]yfYF=zikAиEAA$y\˗/W744$*++o? \.tOhl<9ijGٌ`02z~Z|( `{bʕl2L&moo?#W&sX,8Nu-V@PHTj%H֊+-ZUV- wl4E T50 NQ5I$6m}gd͜9(a޽ Xf]'($7x/>-{f4w( ٳgp8T+,ˊ$IzjałW^yE.))MW^yk׮d\իW'Gf200pO{{{"r (ݴiӽ>,|*iXMMMZѻk׮-{9|cǎ ǎS?>|8?<ȧ>%R^ØnbVk-0l2|q͚5ر㜶t.{}Ϟ=6Һ.OX?&IENDB`pytango-9.2.2/doc/_static/project-config.jam000066400000000000000000000015731316324373100210130ustar00rootroot00000000000000import option ; using msvc : 9.0 : "C:/Program Files (x86)/Microsoft Visual Studio 9.0/VC/bin/cl.exe" ; using msvc : 10.0 : "C:/Program Files (x86)/Microsoft Visual Studio 10.0/VC/bin/cl.exe" ; using python : 2.6 : "C:/Python/win32/26" : : : 32 2.6 ; using python : 2.6 : "C:/Python/x64/26" : : : 64 2.6 ; using python : 2.7 : "C:/Python/win32/27" : : : 32 2.7 ; using python : 2.7 : "C:/Python/x64/27" : : : 64 2.7 ; using python : 3.2 : "C:/Python/win32/32" : : : 32 3.2 ; using python : 3.2 : "C:/Python/x64/32" : : : 64 3.2 ; using python : 3.3 : "C:/Python/win32/33" : : : 32 3.3 ; using python : 3.3 : "C:/Python/x64/33" : : : 64 3.3 ; option.set keep-going : false ; pytango-9.2.2/doc/_static/pytango.css000066400000000000000000000140711316324373100176010ustar00rootroot00000000000000/* * sphinxdoc.css_t * ~~~~~~~~~~~~~~~ * * Sphinx stylesheet -- sphinxdoc theme. Originally created by * Armin Ronacher for Werkzeug. * * :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ @import url("basic.css"); /* -- page layout ----------------------------------------------------------- */ body { font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 'Verdana', sans-serif; font-size: 14px; letter-spacing: -0.01em; line-height: 150%; text-align: center; background-color: #BFD1D4; color: black; padding: 0; border: 1px solid #aaa; margin: 0px 20px 0px 20px; min-width: 740px; } div.document { background-color: white; text-align: left; background-image: url(contents.png); background-repeat: repeat-x; } div.bodywrapper { margin: 0 240px 0 0; border-right: 1px solid #ccc; } div.body { margin: 0; padding: 0.5em 20px 20px 20px; } div.related { font-size: 1em; } div.related ul { background-image: url(navigation.png); height: 2em; border-top: 1px solid #ddd; border-bottom: 1px solid #ddd; } div.related ul li { margin: 0; padding: 0; height: 2em; float: left; } div.related ul li.right { float: right; margin-right: 5px; } div.related ul li a { margin: 0; padding: 0 5px 0 5px; line-height: 1.75em; color: #EE9816; } div.related ul li a:hover { color: #3CA8E7; } div.sphinxsidebarwrapper { padding: 0; } div.sphinxsidebar { margin: 0; padding: 0.5em 15px 15px 0; width: 210px; float: right; font-size: 1em; text-align: left; } div.sphinxsidebar h3, div.sphinxsidebar h4 { margin: 1em 0 0.5em 0; font-size: 1em; padding: 0.1em 0 0.1em 0.5em; color: white; border: 1px solid #86989B; background-color: #AFC1C4; } div.sphinxsidebar h3 a { color: white; } div.sphinxsidebar ul { padding-left: 1.5em; margin-top: 7px; padding: 0; line-height: 130%; } div.sphinxsidebar ul ul { margin-left: 20px; } div.footer { background-color: #E3EFF1; color: #86989B; padding: 3px 8px 3px 0; clear: both; font-size: 0.8em; text-align: right; } div.footer a { color: #86989B; text-decoration: underline; } /* -- body styles ----------------------------------------------------------- */ p { margin: 0.8em 0 0.5em 0; } a { color: #CA7900; text-decoration: none; } a:hover { color: #2491CF; } div.body a { text-decoration: underline; } h1 { margin: 0; padding: 0.7em 0 0.3em 0; font-size: 1.5em; color: #11557C; } h2 { margin: 1.3em 0 0.2em 0; font-size: 1.35em; padding: 0; } h3 { margin: 1em 0 -0.3em 0; font-size: 1.2em; } div.body h1 a, div.body h2 a, div.body h3 a, div.body h4 a, div.body h5 a, div.body h6 a { color: black!important; } h1 a.anchor, h2 a.anchor, h3 a.anchor, h4 a.anchor, h5 a.anchor, h6 a.anchor { display: none; margin: 0 0 0 0.3em; padding: 0 0.2em 0 0.2em; color: #aaa!important; } h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor, h5:hover a.anchor, h6:hover a.anchor { display: inline; } h1 a.anchor:hover, h2 a.anchor:hover, h3 a.anchor:hover, h4 a.anchor:hover, h5 a.anchor:hover, h6 a.anchor:hover { color: #777; background-color: #eee; } a.headerlink { color: #c60f0f!important; font-size: 1em; margin-left: 6px; padding: 0 4px 0 4px; text-decoration: none!important; } a.headerlink:hover { background-color: #ccc; color: white!important; } cite, code, code { font-family: 'Consolas', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; font-size: 0.95em; letter-spacing: 0.01em; } code { background-color: #f2f2f2; border-bottom: 1px solid #ddd; color: #333; } code.descname, code.descclassname, code.xref { border: 0; } hr { border: 1px solid #abc; margin: 2em; } a code { border: 0; color: #CA7900; } a code:hover { color: #2491CF; } pre { font-family: 'Consolas', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; font-size: 0.95em; letter-spacing: 0.015em; line-height: 120%; padding: 0.5em; border: 1px solid #ccc; background-color: #f8f8f8; } pre a { color: inherit; text-decoration: underline; } td.linenos pre { padding: 0.5em 0; } div.quotebar { background-color: #f8f8f8; max-width: 250px; float: right; padding: 2px 7px; border: 1px solid #ccc; } div.topic { background-color: #f8f8f8; } table { border-collapse: collapse; margin: 0 -0.5em 0 -0.5em; } table td, table th { padding: 0.2em 0.5em 0.2em 0.5em; } div.admonition, div.warning { font-size: 0.9em; margin: 1em 0 1em 0; border: 1px solid #86989B; background-color: #f7f7f7; padding: 0; } div.admonition p, div.warning p { margin: 0.5em 1em 0.5em 1em; padding: 0; } div.admonition pre, div.warning pre { margin: 0.4em 1em 0.4em 1em; } div.admonition p.admonition-title, div.warning p.admonition-title { margin: 0; padding: 0.1em 0 0.1em 0.5em; color: white; border-bottom: 1px solid #86989B; font-weight: bold; background-color: #AFC1C4; } div.warning { border: 1px solid #940000; } div.warning p.admonition-title { background-color: #CF0000; border-bottom-color: #940000; } div.admonition ul, div.admonition ol, div.warning ul, div.warning ol { margin: 0.1em 0.5em 0.5em 3em; padding: 0; } div.versioninfo { margin: 1em 0 0 0; border: 1px solid #ccc; background-color: #DDEAF0; padding: 8px; line-height: 1.3em; font-size: 0.9em; } .viewcode-back { font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 'Verdana', sans-serif; } div.viewcode-block:target { background-color: #f4debf; border-top: 1px solid #ac9; border-bottom: 1px solid #ac9; } div.code-block-caption { background-color: #ddd; color: #222; border: 1px solid #ccc; }pytango-9.2.2/doc/_static/serial.png000066400000000000000000000142631316324373100173760ustar00rootroot00000000000000PNG  IHDRsRGBPLTE'!!"!&>&+, ,41Y9/00*<4%";H::6><0Hs:?CHj,HSKfJG;NG6GID XWxogfargS$|-ykor`qnB0}YvwC||q]rTQ{utlxtgtMtvs /xFGxac8<{Ы FRk@pPH@b<B佖, 3QknwUzM/c(ӠiR+֪5G! Ȑ n׈1G> AթYghY'4aQc9xqY4D{͝F!jVnHI $`A`%.^~YKT=C 4՘X%NtP D!ˈN Y߻Vժ@$АbW .82tՔ&$բ?D-^z+s[#NRu Bwe,PCd"A =NKӱ,yAQ{Xŀ# hi9hVKGp(Ԑ=s 6ۡiu* hDщﰼ 4j;;8«!AgIC1((84~B@0C@ǩA2䛐9<y4@bPI1K80;gACxa!+2azP1<4zv@&x^b / 0- ; 'Ȗ7jOrNx$Y/"h`ED74,͖)Iy"'ktP| _%ZtՍey2 qLyŸCx ;"8HDg--,ǒ+ Xwv9ćYO- E'8--; 8@Yw :^M\ S*C!`IqwN~[#GEx7 [H rDculpds!ɇvLC 90+F4 mC\Ы"T#С`CWvTĿOop2O( y H@;mDݟH?FS*l8J.clܽ 2v sanC>s qOy 6 |o J\I@@)J.Ez$PtHASʕ30B=J( G Wfxw`2 Ę H@I쀗DzCpD4z@At|:Vp"MmxqCڟcg&ެPPjOwǧNw |z/J8/H(JQ[Y 0 Hn 3x^?EB !Oʪ*Dq K,C2U?EЍf;6 tH,U_Ujnny6G}~$]d4&`0m%OsN\~իWz gVdvOA2 @;u8ϛqP>Ǐͫ:raGܠlJ0񱏢"fYq'auuu]x7nh %%e'1P]]]yiر3B:#`dM掆3v=χ we^N?~Jʳ#ǭ .j:vႄcP#ݝz/Hw;GǴ۩?XXʔ>*_Є a$[ i O6/\r`PU{}mIJhq;WW_VnȄl, J^G-|>ca;f͟2(5Xz; ׶@ һIw GtyΊ_Zv雚c4V+^]~Б3(% |qwȜ-0g}]+V>\6ALSyB [z3#`` 9(2|u yD6"{ov6eAUVT,,\"M\j18C4S }xOn@j*q6o[w(vak3džyOz瓒 %Z*-XD}щV2u:WVv+9׷9RQd0b~zly|}bÆ{2֭ Dxa,۲]iǑ| #ǥ`mmlZi-}ty'dJæO?g%{/^n˖ߜv`۪qb@r:=#F@;]N;$ΥBCm?s(G" 2?O̗JR򝯿=qrmY i0o@i< \.Y:󩧞շY^z=}Y$- nONK3sR=yr? u3}swcn7) `a=NB V1* R-??cEgכZka .<ٕo.xg$335<,JR[#-,9--҈HiI.,ֲ3! Ȋpyg=;~35LR\Y/),,,DGDgm]b\٥Ņi`n>emF @v_l]X} 0X3O|n㞰9{e~T(5YRrҒ$e}ѰCOvCu3zuҨ`ON?P_\\רbiږES×dKl(++)IJ$hƅ;MÙ۫'aÆTJR㣢kIDD}}('mQbg%+,~CB34guiUn>'UWsYɁBgY(3!.|NYtф%1I߽4cFLLl\W#13N>3 5X]vMV?=]:b^1cv(9&2-33S9kd3<]DBb`oXh]3 ،/|tV=1<8,KKb# IIq&O~zɑSSz! 4\`u ؐ>-~ɓ'1ٛ9&-,LK22%"nϵ"f9 \]]׮ hL-/yzV3"7HDϊJ%̈Wn=rˊ;]kMM._vL~zʒI2%Dǧ"yt_$kvx<{@wHV[Ĥ I#"ccbb"#\!!J1xVCuu{ ;](̇YjьȤ KܡHcLA>ql")22gȁYs6}㭐AJ{qncN|iv?xa^ʁC8qJDtYq׆ZH\/bS yd^,HN@ϣl0|`]:/( 0) { elements.eq(0).show(); } var slideshow_timer = setInterval(switch_image, IMAGE_TIME); function switch_image() { old_index = curr_index; if (curr_index < (elements.length-1)) { curr_index += 1; } else { curr_index = 0; } show_hide(curr_index, old_index); } function show_hide(show_index, hide_index) { elements.eq(hide_index).fadeOut(FADE_TIME); elements.eq(show_index).delay(FADE_TIME+100).fadeIn(FADE_TIME); } }); pytango-9.2.2/doc/_static/starter.png000066400000000000000000000366231316324373100176070ustar00rootroot00000000000000PNG  IHDR>asRGBbKGD pHYs  tIME % tEXtCommentCreated with GIMPW IDATxyeUmwyK˵ꥺ[RK%Z$8`1a a#g&dȒ""F-  HVwmYUToeVXBbQQ/߻o,=ȁȁȁȁȁȁȁȁȁȁȁȁȁȁȁȁȁȁȁȁȁȁȁm/|cu<07?+O<$J)R|?woq]]>/O=&pc" #!9F)N:IEO|mqw 7X>jgO3y-®E!Bc!r-#GpٳO~_9uv9w$<\[*1( !xޣ6Z`e?PJ@JR<BZ)wE1ܹ8}Ϝ;k,Ń(mGxmlaF_ݷBߵ2~khmEt'O8wna;P|}CDWWW+߽sCH)Gw=6C?{7{B.vu3iJQgrG6111'x񓽟k߫ kޡҎoc=#ګ,!&Ǐc|]wu7qg-p3; ,PJ2L{7fx?|6m\iq/.CZ *?>m2}<BgϞ;~ ǺBlo v+!Bُ/@mqnf_ms7q?eZߢTA h@V#MӯOɧiovsB!lv Ohw`5%.B@as;{Ӹu[wC?,^E=0qK%s5&1w_M?{G/wWe,hY\u*(3]}ER0w{g( jZ8T7=vܡbgcf6RE{cPveY{ިft94Ek͵h6 *xQwjd.ނ8 O~ٹ9k,Bկzvi 8{Qv8RҨ7ejjZV:G))d>x4 }Cld7 hwm8-Q8~?"=uwr';W~2 }rdyDE++؛Ώ2'G.YT;&s]ۛk)!x$#r=?~JlLpufg0Q4=WVVtJ) #TGBnTe!B(I r  -rJjKp/RfOARʑ>J "O5 [Uc|%!+SdH 45,':L&א~vllBH \fZJL1S "#ȲkV119I "HC@E֊.9RQ= @K!TQe ΃׉*J25=4λl|+4ZWgG=;+<$"` ,2 5qhO?WpeYJ{H\ޘ=d_EUʂ%իӧ\Ƶי#MS~Acբi }xr6t̩3wWw<8yEE =? ݼÛ_6xÛ&2 ,owz{RF)%NȰA3wfD^Z$9KPLBe pȠDQBosc(S[Y,bB)a {N~u` xphm}6.\G >fbX^]ŘҢ ox[бVlnQIҕK(S2Q,+a/>y+7 Y#@.2U0x;`kc>R*uPYꍓ\y2Bؓ[LNN2739'1Z@Ky$5&vY+"]eWY +5NҊaq~wP*|TR~ qjmX]]&mH /]G(QRb"CÈ7V6EΟ$IWrygXZij ')2_Qo!M "xW "E*O#Xg_!EFwEx70ƌ@#5UPv'kXkiM֙iՉ(6`XYYqDL/DIdmt\G** .(+jX4b戝[xQA(ҸVET*ː/w;j&*?{q*c +d6ńGXZcFSi6zy[[{W?4O$ܼJ!DI(%'!H i I"OG"LRl$qHB(mVF'$RIkY.lzN(/}I>ArErWLb@2J,XȷSEk\^ȕk>y^017=Y\\EJA$H!0QL[T%Ź%|qp;WA"hLx8zd/|\]N?YfyFZ(MZjLJ $bsk͛w߃™QG?w..ҨՑRiMWѴDӈS8 iC'b"mh֚lc9iZ#Ҟ"/yhHRc-!c)8YF' !+=|'gx)z}Z,-Q P0j6ڌާ+ҔDCtYȗ>%/pΓLf8~Yq%F@ !e AF ãSKFUC=AH8UW] 6ksuiZ{C @͉&k+EYO7^WMY[yn™嵧PR@h DQ9zyN?g.MbbrDZ+\[z ަiG1qcmFGʰ @i)ADƕ2I6e}D`m\T r-V' tI"MMЂ4mR'8z-N?}fB{ _!mn)y&iMQH!8Qef-]IB Keqw%ѣ@8;xRRDBR:Nbɑ) Dz2aTRk ޣsRJ.^G="׮.-c94y :YiJa7x\ ~Z#C'X"r %?+W0_DhĐ){/zEޠ^squ#Ο]i>=1R/ô^wJ3pًpj`TbsC()|)}1!PZeWqD畫`Y6[k#w)&;HW!z! RJDnݮ"uON8wNݦ2r67H8vͩ^7TrLUV((rG~& iq=wb'8.]̩S'Ffej5 Y^}`q23u;1B'THg6J82lNpsDiuX0&V(kI?MZCVNYFQ88tO>$zVAN.!M4bBnw8s6,+R"UD!b7oCe!u!ʾf_*xY*QEh-(+ugfgɋem7t:#Kz Z-n鵙5IзVNq'j5Yq N3O055\48/L[஻^~ݿd=L$ Y6 ISR]fM}Na5QuDp}c ~<ωh@a`@$ VVVn' y:++Q1ȻH[d TRJ87iYUN2bwJ"}`8aF'ApqsuzӧlWreNf=IiPdg00S>K4'2PX×LAF/SǏQ7iN4J)DLbVQDYzT(SEx(Sd)ihSf'd-^w x@IA,&Tե!8V QQaK]Z0m۬|[^r7#p<}2#GS-jn_N I:҂xBur/Ԕ pԩQ^|2J5HMܗtigCA'xY[۠-A 5Fm zQӅ`k#keӨ+XC˨hـ"(SkL W7h,IfgguR\FZ[n6 e\fl0VWp/G%^ [=xUlԣpLPl'u!tgRY!7n.3gHtTBq4QR玻ν;(8v eifNwsvZeϹZ l1 JjH_FĵZ~T#sfgB*S3-v"+,3??OElmm'-IӔ LdhDIGu: "M>ISA BQPH7rue?IzA3p$An?8:xԣ_0? WWdyGJc_ڵ+(х'2/}2d}s^+ϩg@@,P +4z_Ыz ss"285?({'fɝrMRg|?xᇩOqduz㼧뱺Lg@#uBnkoU@$٠|@xO9Q6GHTFsD<&z9[HQrB;G5Ia{{(iP@(i6L6" HtzKI٩)+ȲN_t3sV%tD;੧.WBW.P]N=}+03=Qn }M(4SC rkQFñ-?̰ D f$| MV@JJ(c\›VN:ο>X\Il'֖tJ$A`ssdZ?s;|7 lPH(Qk)ڽ-Nï8R@G]V74LKH4PbkyAߗ|sZ2yS#E(;x MjH{=?y]dbI `d@4zZGhRYLJD MQBќh2ٚШ(*x'9s~?q7tzyy9lllM|tӨm̥KrHPe:#.'2jZ40H(-,~%KQC\NYN$j(BJVPI/ gdvzz*kiBT܇W>5 Ow/=|So??{^TGa9|h)z["xgYtBprn|y?Vaú2E[]ѣG)VMjD1s<5BmtՕSf{F(2IA!^qnZ#5Wh#M~-~z} fPH+Ep2шPh=U6C%@ z$&I7Wnpv433CVR. e U92\N&[[eLƵ1(ɤED5(V-fkis^QM(2GN R + 0lt!"Z5 &'kDQ̣~C 㳇0JWaf6W})X*S?⣯sɹ ǎganvpCJiBPrHqaC+O.!(:EILMM90j54~>Օ/Xq{9G۝K'ٜX}77A'llCcr QNgYu&Ͳ0B, 0 K?[8?N<r-0AF8$ W~k _'?C6}h=~"{_p!#Q3`+N/[POlabYz(,Qufgg^:uzLX/\ѣGiZ%;Zz.Yo-Q 1k5t#iL}0:thN49vd%yl2(bvv͍XXB(YEs&LNXY^)AyeۼњHH&''iM9u,S%@ц7~}Mm5-?}}H|3~^+.C)c"!b-qtYi$L"`21Ct6+"1111NˡI211 `m,W\(TcDkKyYhMFQ`B^y$7qbM- RyGq)*u '^/Q!tBor1eYv8oivΆ<OA-nnu0escs4%dXZ˗Bk ZFđ.3'$JS4 ffg|҈е+7i KaK*{qr$/A)gQ ;I5, gmx%/_}-Hu!]}˷_yw~Q|GXP Җ>5]hfGD{f0!U<ϩ;땱B\5"Ո꣓ECJyDa1REW^WR?kw~4&,Ε .}(EւPDVk$ΏM=z/x_k#6]>͛>SB;~WA-zZ ökϩ'lnn<`4kzPױE"ܱEf=%M^!TPՒƊ$l[2nM&jRGH)bK,i-R8:ȍDؓV+ȻwoW?|u%`p^{A eUDX竔P2hFBL|, Z3;bb9Qj.Umx,^I5rgKx5c0 z-)RRNR!Bɖ)pqnpB09sL o{'oSqy{??yҊtRU#>"S[kRFC@g*\Uqy=WdeKb;iZbÇѺ"j333Kd BHY^hm@WnCS%:HdL]B"4&A%]_umWo·!LZk~󦨈Jk0U@cwѪDo}no333CC,pfjWnr)h4t;]NvRJ7Dr (w9QT ӭr!CQ&%ԉcLw7!'!0??7b :QB+E$UKcD3>eɈz’ x9vtl_ :k[(??rx'IE٧/4i!p y/t8P3"(-F)/ D"Pҫ8I.ؔ];-NEl[lTE9tw(K--Fj"!$PCHy0"h4$MDD7@"J*E_K@ j KiKc{;9g|bbBBZ+{ڗMvg̞3ĩ:bZ E[yL,5tmphT&LB#:+5.mo PfB ,ˆa0׊7躆 lzjxj ҇Xy*KūY9{z䛯yww W)z\=cԀACsm:ePQx%ݶkiz*r<q LUxi= m;܎f ϿHRCceX(%̿Nhjz/_ Le;1ij~#J)r'`ӓ16>JG&=2+e)hTӐR0ŚVawqדlŊ1?tt{OUu8̅˱*WDAEE>nnQzLEI)#/8 .31?FP A#XvMsg^o:o 3dd]]]Ys(DtSقh2ta^Y)!hظkϬ*5M7o>qxdLp-<`xdj8uXUU%R()ZTV&`.<ҁhILU#B lghLFc4Y>o.@(6F>H,] 3[?k-e.+TUbLAޱA F xw{m-Ӑez#̂J4_YCg3ku 9k[7mhmo^<nͦR9Z$I$I$I$I$I$I$I$I$I$I$I$ݭeCIENDB`pytango-9.2.2/doc/_templates/000077500000000000000000000000001316324373100161125ustar00rootroot00000000000000pytango-9.2.2/doc/_templates/index.html000066400000000000000000000357151316324373100201220ustar00rootroot00000000000000{% extends "layout.html" %} {% set title = 'PyTango documentation' %} {% set script_files = script_files + ["http://code.jquery.com/jquery-1.9.1.min.js", "_static/jssor.js", "_static/jssor.slider.js"] %} {% block body %}

    Welcome to PyTango documentation!

    PyTango is a python module that exposes to Python the complete Tango C++ API. This means that you can write not only tango applications (scripts, CLIs, GUIs) that access tango device servers but also tango device servers themselves, all of this in pure python.

    Check out the getting started guide to learn how to build and/or install PyTango and after that the quick tour can help you with the first steps in the PyTango world. If you need help understanding what Tango itself really is, you can check the Tango homepage where you will find plenty of documentation, FAQ and tutorials.

    {% endblock %} pytango-9.2.2/doc/_templates/indexsidebar.html000066400000000000000000000011661316324373100214450ustar00rootroot00000000000000

    Download

    Current version: {{ release }}

    Get PyTango from PyPi
    or install it with:

    pip install PyTango

    PDF

    A PDF version here.

    Other versions

    Development
    Latest stable
    8.1.8
    8.1.6 pytango-9.2.2/doc/_templates/layout.html000066400000000000000000000010241316324373100203120ustar00rootroot00000000000000{% extends "sphinxdoc/layout.html" %} {% block extrahead %} {% endblock %} {% block rootrellink %}
  • home
  • getting started
  • quick tour
  • how to
  • FAQ
  • documentation (v{{ release }}) »
  • {% endblock %} pytango-9.2.2/doc/api.rst000066400000000000000000000003661316324373100152650ustar00rootroot00000000000000 .. currentmodule:: tango .. _pytango-api: =========== PyTango API =========== .. automodule:: tango .. toctree:: :maxdepth: 1 data_types client_api/index server_api/index database encoded utilities exception pytango-9.2.2/doc/client_api/000077500000000000000000000000001316324373100160645ustar00rootroot00000000000000pytango-9.2.2/doc/client_api/attribute_proxy.rst000066400000000000000000000001571316324373100220650ustar00rootroot00000000000000AttributeProxy -------------- .. currentmodule:: tango .. autoclass:: tango.AttributeProxy :members: pytango-9.2.2/doc/client_api/device_proxy.rst000066400000000000000000000002221316324373100213120ustar00rootroot00000000000000.. currentmodule:: tango DeviceProxy ----------- .. autoclass:: tango.DeviceProxy :show-inheritance: :members: :inherited-members: pytango-9.2.2/doc/client_api/green.rst000066400000000000000000000006051316324373100177170ustar00rootroot00000000000000 .. pytango-client-green-api: ========= Green API ========= Summary: * :func:`tango.get_green_mode` * :func:`tango.set_green_mode` * :func:`tango.futures.DeviceProxy` * :func:`tango.gevent.DeviceProxy` .. autofunction:: tango.get_green_mode .. autofunction:: tango.set_green_mode .. autofunction:: tango.futures.DeviceProxy .. autofunction:: tango.gevent.DeviceProxy pytango-9.2.2/doc/client_api/group.rst000066400000000000000000000022051316324373100177510ustar00rootroot00000000000000 Group ----- .. currentmodule:: tango .. GroupElement is the base class of Group, but is not the base of anything else. So, I don't include it in the documentation but just add its functions into Group by using :inherited-members: Group class ~~~~~~~~~~~ .. autoclass:: tango.Group :show-inheritance: :inherited-members: :members: GroupReply classes ~~~~~~~~~~~~~~~~~~ Group member functions do not return the same as their DeviceProxy counterparts, but objects that contain them. This is: - *write attribute* family returns tango.GroupReplyList - *read attribute* family returns tango.GroupAttrReplyList - *command inout* family returns tango.GroupCmdReplyList The Group*ReplyList objects are just list-like objects containing :class:`~tango.GroupReply`, :class:`~tango.GroupAttrReply` and :class:`~GroupCmdReply` elements that will be described now. Note also that GroupReply is the base of GroupCmdReply and GroupAttrReply. .. autoclass:: tango.GroupReply :members: .. autoclass:: tango.GroupAttrReply :show-inheritance: :members: .. autoclass:: GroupCmdReply :show-inheritance: :members: pytango-9.2.2/doc/client_api/index.rst000066400000000000000000000002131316324373100177210ustar00rootroot00000000000000Client API ========== .. toctree:: :maxdepth: 2 device_proxy attribute_proxy group green miscellaneous other pytango-9.2.2/doc/client_api/miscellaneous.rst000066400000000000000000000045521316324373100214670ustar00rootroot00000000000000.. currentmodule:: tango API util -------- .. autoclass:: ApiUtil :members: Information classes ------------------- See also `Event configuration information`_ Attribute ~~~~~~~~~ .. autoclass:: AttributeAlarmInfo :members: .. autoclass:: AttributeDimension :members: .. autoclass:: AttributeInfo :members: .. autoclass:: AttributeInfoEx :members: see also :class:`AttributeInfo` .. autoclass:: DeviceAttributeConfig :members: Command ~~~~~~~ .. autoclass:: DevCommandInfo :members: .. autoclass:: CommandInfo :members: Other ~~~~~ .. autoclass:: DeviceInfo :members: .. autoclass:: LockerInfo :members: .. autoclass:: PollDevice :members: Storage classes --------------- Attribute: DeviceAttribute ~~~~~~~~~~~~~~~~~~~~~~~~~~ .. autoclass:: DeviceAttribute :members: Command: DeviceData ~~~~~~~~~~~~~~~~~~~ Device data is the type used internally by Tango to deal with command parameters and return values. You don't usually need to deal with it, as command_inout will automatically convert the parameters from any other type and the result value to another type. You can still use them, using command_inout_raw to get the result in a DeviceData. You also may deal with it when reading command history. .. autoclass:: DeviceData :members: Callback related classes ------------------------ If you subscribe a callback in a DeviceProxy, it will be run with a parameter. This parameter depends will be of one of the following classes depending on the callback type. .. autoclass:: AttrReadEvent :members: .. autoclass:: AttrWrittenEvent :members: .. autoclass:: CmdDoneEvent :members: Event related classes --------------------- Event configuration information ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. autoclass:: AttributeEventInfo :members: .. autoclass:: ArchiveEventInfo :members: .. autoclass:: ChangeEventInfo :members: .. autoclass:: PeriodicEventInfo :members: Event arrived structures ~~~~~~~~~~~~~~~~~~~~~~~~ .. autoclass:: EventData :members: .. autoclass:: AttrConfEventData :members: .. autoclass:: DataReadyEventData :members: History classes --------------- .. autoclass:: DeviceAttributeHistory :show-inheritance: :members: See :class:`DeviceAttribute`. .. autoclass:: DeviceDataHistory :show-inheritance: :members: See :class:`DeviceData`. pytango-9.2.2/doc/client_api/other.rst000066400000000000000000000020401316324373100177330ustar00rootroot00000000000000.. currentmodule:: tango Enumerations & other classes ---------------------------- Enumerations ~~~~~~~~~~~~ .. autoclass:: tango.LockerLanguage .. autoclass:: tango.CmdArgType .. autoclass:: tango.MessBoxType .. autoclass:: tango.PollObjType .. autoclass:: tango.PollCmdCode .. autoclass:: tango..SerialModel .. autoclass:: tango.AttReqType .. autoclass:: tango.LockCmdCode .. autoclass:: tango.LogLevel .. autoclass:: tango.LogTarget .. autoclass:: tango.EventType .. autoclass:: tango.KeepAliveCmdCode .. autoclass:: tango.AccessControlType .. autoclass:: tango.asyn_req_type .. autoclass:: tango.cb_sub_model .. autoclass:: tango.AttrQuality .. autoclass:: tango.AttrWriteType .. autoclass:: tango.AttrDataFormat .. autoclass:: tango.PipeWriteType .. autoclass:: tango.DevSource .. autoclass:: tango.ErrSeverity .. autoclass:: tango.DevState .. autoclass:: tango.DispLevel .. autoclass:: tango.GreenMode Other classes ~~~~~~~~~~~~~ .. autoclass:: tango.Release :members: .. autoclass:: tango.TimeVal :members: pytango-9.2.2/doc/conf.py000066400000000000000000000430341316324373100152600ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ import sys import os import re # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.append(os.path.abspath('../')) sys.path.append(os.path.abspath('./')) # Import tango try: import tango except ImportError: from mock_tango_extension import tango from tango import Release print("Building documentation for PyTango {0}".format(Release.version_long)) print("Using PyTango from: {0}".format(os.path.dirname(tango.__file__))) needs_sphinx = "1.0" # -- General configuration ----------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.pngmath', 'sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.graphviz'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8' # The master toctree document. master_doc = 'contents' # General information about the project. project = u'PyTango' copyright = u"""Except where otherwise noted, content on this site is licensed under a Creative Commons Attribution 3.0 License""" #Ideally we would like to put the following html code for copyright... but how? '''\ Creative Commons License
    PyTango Documentation by ESRF is licensed under a Creative Commons Attribution-Share Alike 3.0 Spain License.''' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '.'.join(Release.version.split('.')[:2]) # The full version, including alpha/beta/rc tags. release = Release.version_long # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. #unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. exclude_trees = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. html_theme = 'sphinxdoc' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] html_style = 'pytango.css' # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = "PyTango documentation" # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = "PyTango" # The name of an image file (relative to this directory) to place at the top # of the sidebar. html_logo = '_static/logo.png' # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. html_favicon = '_static/logo.ico' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. html_sidebars = {'index': ['indexsidebar.html']} # Additional templates that should be rendered to pages, maps page names to # template names. html_additional_pages = { 'index' : 'index.html' } # If false, no module index is generated. #html_use_modindex = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'PyTangodoc' # -- Options for LaTeX output -------------------------------------------------- # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('contents', 'PyTango.tex', u'PyTango Documentation', u'PyTango team', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. latex_logo = '_static/logo.png' latex_elements = { 'fontpkg': '\\usepackage{palatino}', 'papersize': 'a4paper', 'pointsize': '10pt', } latex_show_urls = 'no' # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_use_modindex = True # -- Options for RST ----------------------------------------------------------- rst_epilog = """\ .. _Tango: http://www.tango-controls.org .. _Python: http://python.org .. _IPython: http://ipython.org .. _numpy: http://www.numpy.org .. _gevent: http://www.gevent.org .. _asyncio: https://asyncio.readthedocs.io/en/latest/ .. _boost-python: http://www.boost.org/libs/python .. _PyPi: https://pypi.python.org/pypi/pytango .. _issues: https://github.com/tango-controls/pytango/issues .. _PRs: https://github.com/tango-controls/pytango/pulls """ # -- Options for reference to other documentation ------------------------------ intersphinx_mapping = { 'http://docs.python.org/dev': None, 'http://docs.scipy.org/doc/scipy/reference': None, 'http://docs.scipy.org/doc/numpy': None, 'http://ipython.org/ipython-doc/stable': None, 'http://api.mongodb.org/python/current': None, 'http://packages.python.org/CouchDB': None, 'http://pycassa.github.com/pycassa': None, 'http://docs.sqlalchemy.org/en/latest': None, } todo_include_todos = True def copy_spaces(origin): r = '' for x in range(len(origin)): if origin[x] in (' ', '\t'): r += origin[x] else: return r return r def type_to_link(tipus): if tipus[:9] == 'sequence<' and tipus[-1:] == '>': return 'sequence<' + type_to_link(tipus[9:-1]) + '>' #elif tipus in dir(PyTango): else: return ':class:`' + tipus + "`" #else: # return tipus def type_to_pytango_link(tipus): if tipus[:9] == 'sequence<' and tipus[-1:] == '>': return 'sequence<' + type_to_link(tipus[9:-1]) + '>' elif tipus in dir(tango): return ':class:`' + tipus + "`" else: return tipus def possible_type_to_link(text): if len(text) and text[0] == '(' and text[-1] == ')': return '(' + type_to_link(text[1:-1]) +')' return text def parse_typed_line(line): spacesSplit = line.strip().split(' ') first = spacesSplit[0].strip() return possible_type_to_link(first) + ' ' + ' '.join(spacesSplit[1:]) def parse_parameters(line): spaces = copy_spaces(line) miniLine = line.strip() if miniLine[:2] != '- ': return line spl = miniLine[2:].split(':', 1) assert(len(spl) == 2) return spaces + ':' + spl[0].strip() + ': ' + parse_typed_line(spl[1]) def parse_bullet_with_type(line): spaces = copy_spaces(line) miniLine = line.strip() if miniLine[:2] not in ['- ', '* ']: return line spl = miniLine.split(':', 1) if len(spl) != 2: return line return spaces + spl[0] + ': ' + parse_typed_line(spl[1]) def parse_throws(line): words = re.split('(\W+)', line) assert(line == ''.join(words)) return ''.join(map(type_to_pytango_link, words)) # http://codedump.tumblr.com/post/94712647/handling-python-docstring-indentation def docstring_to_lines(docstring): if not docstring: return [] lines = docstring.expandtabs().splitlines() # Determine minimum indentation (first line doesn't count): indent = sys.maxint for line in lines[1:]: stripped = line.lstrip() if stripped: indent = min(indent, len(line) - len(stripped)) # Remove indentation (first line is special): trimmed = [lines[0].strip()] if indent < sys.maxint: for line in lines[1:]: trimmed.append(line[indent:].rstrip()) # Strip off trailing and leading blank lines: while trimmed and not trimmed[-1]: trimmed.pop() while trimmed and not trimmed[0]: trimmed.pop(0) return trimmed def search_ONLY_signature(name, text): lines = docstring_to_lines(text) # There should be ONE signature and must be the FIRST text # Signature is the ONLY starting at position 0 signatureLine = None for ln in range(len(lines)): line = lines[ln] if len(line.strip()) and line[0] != ' ': parentesis = line.split('(', 1) fname = parentesis[0].strip() if len(parentesis)==2 and fname == name.rsplit('.',1)[1]: if signatureLine is not None: # More than one signature! return None signatureLine = ln else: return None # There's a text as FIRST text that's NOT the signature! if signatureLine is None: return None return lines[signatureLine] def split_signature(text): if text is None: return None # split "fname(params)", "returntype" ops = text.split('->') if len(ops) != 2: return None # get rid of "fname" params = ops[0].strip() ret_type = ops[1].strip() p = params.find('(') if p < 0: return None params = params[p:] return params, ret_type _with_only_one_signature_methods = {} def __reformat_lines(app, what, name, obj, options, lines): global _with_only_one_signature_methods if what != 'method': for ln in range(len(lines)): lines[ln] = parse_bullet_with_type(lines[ln]) return toinsert = [] parsingParameters = False parsingThrows = False toinsert.append((0, "")) for ln in range(len(lines)): line = lines[ln] if len(line) and line[0] != ' ': if name in _with_only_one_signature_methods: # This method has one and only one signature. So it will # be displayed by sphinx, there's no need for us to fake # it here... lines[ln] = "" else: parentesis = line.split('(', 1) fname = parentesis[0].strip() if len(parentesis)==2 and fname == name.rsplit('.',1)[1]: sg = split_signature(line) if sg is not None: # Main lines are like small titles (**bold**): lines[ln] = '**' + fname +'** *' + sg[0] + '* **->** ' + type_to_link(sg[1]) # Add an ENTER after the title, to make a different # paragraph. So if I have 2 signatures, there's no problem # with it... toinsert.append((ln+1, "")) ## Main lines are like small titles (**bold**): #lines[ln]='**' + line.strip() + '**' ## Add an ENTER after the title, to make a different ## paragraph. So if I have 2 signatures, there's no problem ## with it... #toinsert.append((ln+1, "")) # Mark the "New in this version" lines... if line.strip()[:14] == "New in PyTango": lines[ln] = copy_spaces(lines[ln]) + "*" + line.strip() + "*" parsingParameters = False parsingThrows = False # Look for special control_words # To replace the actual syntax: "Return : something" # with the one understood by reStructuredText ":Return: something" spl = line.strip().split(':', 1) control_word = spl[0].strip() if ((len(spl) != 2) or (control_word not in ["Parameters", "Return", "Throws", "Example", "See Also" ]) ): if parsingParameters: lines[ln] = parse_parameters(line) elif parsingThrows: lines[ln] = parse_throws(line) continue parsingParameters = False parsingThrows = False spaces = copy_spaces(line) # The Example control word is even more special. I will put # the contents from the following line into a code tag (::) if control_word == 'Example': lines[ln] = spaces + ":" + control_word + ": " + spl[1] toinsert.append((ln+1, "")) toinsert.append((ln+1, spaces + ' ::')) toinsert.append((ln+1, "")) elif control_word == 'Parameters': lines[ln] = spaces + ":Parameters:" + parse_parameters(spl[1]) parsingParameters = True elif control_word == 'Return': lines[ln] = spaces + ":Return: " + parse_typed_line(spl[1]) elif control_word == "Throws": lines[ln] = spaces + ":Throws:" + parse_throws(spl[1]) parsingThrows = True else: lines[ln] = spaces + ":" + control_word + ": " + spl[1] for x in range(len(toinsert)-1, -1, -1): pos, txt = toinsert[x] lines.insert(pos, txt) def __process_signature(app, what, name, obj, options, signature, return_annotation): global _with_only_one_signature_methods if what != 'method': return sg = split_signature(search_ONLY_signature(name, obj.__doc__)) if sg is not None: _with_only_one_signature_methods[name] = True return sg return (signature, return_annotation) def setup(app): # sphinx will call these methods when he finds an object to document. # I want to edit the docstring to adapt its format to something more # beautiful. # I also want to edit the signature because boost methods have no # signature. I will read the signature from the docstring. # The order sphinx will call it is __process_signature, __reformat_lines. # And it is important because I keep some information between the two # processes # Problem is __process_signature works great with python methods... # but is not even called for methods defined by boost. So, as it is, # is useless now. #app.connect('autodoc-process-signature', __process_signature) app.connect('autodoc-process-docstring', __reformat_lines) pytango-9.2.2/doc/contents.rst000066400000000000000000000005411316324373100163440ustar00rootroot00000000000000 .. currentmodule:: tango .. _contents: ======== Contents ======== .. toctree:: :maxdepth: 2 :titlesonly: start quicktour ITango green_modes/green API How to How to contribute FAQ TEP History of changes **Last update:** |today| pytango-9.2.2/doc/data_types.rst000066400000000000000000001111351316324373100166460ustar00rootroot00000000000000.. currentmodule:: tango .. _pytango-data-types: Data types ========== This chapter describes the mapping of data types between Python and Tango. Tango has more data types than Python which is more dynamic. The input and output values of the commands are translated according to the array below. Note that if PyTango is compiled with :py:mod:`numpy` support the numpy type will be the used for the input arguments. Also, it is recomended to use numpy arrays of the appropiate type for output arguments as well, as they tend to be much more efficient. **For scalar types (SCALAR)** +-------------------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | Tango data type | Python 2.x type | Python 3.x type (*New in PyTango 8.0*) | +=========================+===========================================================================+===========================================================================+ | DEV_VOID | No data | No data | +-------------------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | DEV_BOOLEAN | :py:obj:`bool` | :py:obj:`bool` | +-------------------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | DEV_SHORT | :py:obj:`int` | :py:obj:`int` | +-------------------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | DEV_LONG | :py:obj:`int` | :py:obj:`int` | +-------------------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | DEV_LONG64 | - :py:obj:`long` (on a 32 bits computer) | :py:obj:`int` | | | - :py:obj:`int` (on a 64 bits computer) | | +-------------------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | DEV_FLOAT | :py:obj:`float` | :py:obj:`float` | +-------------------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | DEV_DOUBLE | :py:obj:`float` | :py:obj:`float` | +-------------------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | DEV_USHORT | :py:obj:`int` | :py:obj:`int` | +-------------------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | DEV_ULONG | :py:obj:`int` | :py:obj:`int` | +-------------------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | DEV_ULONG64 | * :py:obj:`long` (on a 32 bits computer) | :py:obj:`int` | | | * :py:obj:`int` (on a 64 bits computer) | | +-------------------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | DEV_STRING | :py:obj:`str` | :py:obj:`str` (decoded with *latin-1*, aka *ISO-8859-1*) | +-------------------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | | sequence of two elements: | sequence of two elements: | | DEV_ENCODED | | | | (*New in PyTango 8.0*) | 0. :py:obj:`str` | 0. :py:obj:`str` (decoded with *latin-1*, aka *ISO-8859-1*) | | | 1. :py:obj:`bytes` (for any value of *extract_as*) | 1. :py:obj:`bytes` (for any value of *extract_as*, except String. | | | | In this case it is :py:obj:`str` (decoded with default python | | | | encoding *utf-8*)) | +-------------------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ **For array types (SPECTRUM/IMAGE)** +-------------------------+-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | Tango data type | ExtractAs | Data type (Python 2.x) | Data type (Python 3.x) (*New in PyTango 8.0*) | +=========================+=================+===========================================================================+===========================================================================+ | DEVVAR_CHARARRAY | Numpy | :py:class:`numpy.ndarray` (dtype= :py:obj:`numpy.uint8`) | :py:class:`numpy.ndarray` (dtype= :py:obj:`numpy.uint8`) | | +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | | Bytes | :py:obj:`bytes` (which is in fact equal to :py:obj:`str`) | :py:obj:`bytes` | | +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | | ByteArray | :py:obj:`bytearray` | :py:obj:`bytearray` | | +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | | String | :py:obj:`str` | String :py:obj:`str` (decoded with default python encoding *utf-8*!!!) | | +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | | List | :py:class:`list` <:py:obj:`int`> | :py:class:`list` <:py:obj:`int`> | | +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | | Tuple | :py:class:`tuple` <:py:obj:`int`> | :py:class:`tuple` <:py:obj:`int`> | +-------------------------+-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | DEVVAR_SHORTARRAY | Numpy | :py:class:`numpy.ndarray` (dtype= :py:obj:`numpy.uint16`) | :py:class:`numpy.ndarray` (dtype= :py:obj:`numpy.uint16`) | | or +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | (DEV_SHORT + SPECTRUM) | Bytes | :py:obj:`bytes` (which is in fact equal to :py:obj:`str`) | :py:obj:`bytes` | | or +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | (DEV_SHORT + IMAGE) | ByteArray | :py:obj:`bytearray` | :py:obj:`bytearray` | | +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | | String | :py:obj:`str` | String :py:obj:`str` (decoded with default python encoding *utf-8*!!!) | | +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | | List | :py:class:`list` <:py:obj:`int`> | :py:class:`list` <:py:obj:`int`> | | +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | | Tuple | :py:class:`tuple` <:py:obj:`int`> | :py:class:`tuple` <:py:obj:`int`> | +-------------------------+-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | DEVVAR_LONGARRAY | Numpy | :py:class:`numpy.ndarray` (dtype= :py:obj:`numpy.uint32`) | :py:class:`numpy.ndarray` (dtype= :py:obj:`numpy.uint32`) | | or +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | (DEV_LONG + SPECTRUM) | Bytes | :py:obj:`bytes` (which is in fact equal to :py:obj:`str`) | :py:obj:`bytes` | | or +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | (DEV_LONG + IMAGE) | ByteArray | :py:obj:`bytearray` | :py:obj:`bytearray` | | +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | | String | :py:obj:`str` | String :py:obj:`str` (decoded with default python encoding *utf-8*!!!) | | +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | | List | :py:class:`list` <:py:obj:`int`> | :py:class:`list` <:py:obj:`int`> | | +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | | Tuple | :py:class:`tuple` <:py:obj:`int`> | :py:class:`tuple` <:py:obj:`int`> | +-------------------------+-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | DEVVAR_LONG64ARRAY | Numpy | :py:class:`numpy.ndarray` (dtype= :py:obj:`numpy.uint64`) | :py:class:`numpy.ndarray` (dtype= :py:obj:`numpy.uint64`) | | or +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | (DEV_LONG64 + SPECTRUM) | Bytes | :py:obj:`bytes` (which is in fact equal to :py:obj:`str`) | :py:obj:`bytes` | | or +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | (DEV_LONG64 + IMAGE) | ByteArray | :py:obj:`bytearray` | :py:obj:`bytearray` | | +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | | String | :py:obj:`str` | String :py:obj:`str` (decoded with default python encoding *utf-8*!!!) | | +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | | List | :py:class:`list` | :py:class:`list` <:py:obj:`int`> | | +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | | Tuple | :py:class:`tuple` | :py:class:`tuple` <:py:obj:`int`> | +-------------------------+-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | DEVVAR_FLOATARRAY | Numpy | :py:class:`numpy.ndarray` (dtype= :py:obj:`numpy.float32`) | :py:class:`numpy.ndarray` (dtype= :py:obj:`numpy.float32`) | | or +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | (DEV_FLOAT + SPECTRUM) | Bytes | :py:obj:`bytes` (which is in fact equal to :py:obj:`str`) | :py:obj:`bytes` | | or +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | (DEV_FLOAT + IMAGE) | ByteArray | :py:obj:`bytearray` | :py:obj:`bytearray` | | +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | | String | :py:obj:`str` | String :py:obj:`str` (decoded with default python encoding *utf-8*!!!) | | +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | | List | :py:class:`list` <:py:obj:`float`> | :py:class:`list` <:py:obj:`float`> | | +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | | Tuple | :py:class:`tuple` <:py:obj:`float`> | :py:class:`tuple` <:py:obj:`float`> | +-------------------------+-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | DEVVAR_DOUBLEARRAY | Numpy | :py:class:`numpy.ndarray` (dtype= :py:obj:`numpy.float64`) | :py:class:`numpy.ndarray` (dtype= :py:obj:`numpy.float64`) | | or +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | (DEV_DOUBLE + SPECTRUM) | Bytes | :py:obj:`bytes` (which is in fact equal to :py:obj:`str`) | :py:obj:`bytes` | | or +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | (DEV_DOUBLE + IMAGE) | ByteArray | :py:obj:`bytearray` | :py:obj:`bytearray` | | +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | | String | :py:obj:`str` | String :py:obj:`str` (decoded with default python encoding *utf-8*!!!) | | +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | | List | :py:class:`list` <:py:obj:`float`> | :py:class:`list` <:py:obj:`float`> | | +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | | Tuple | :py:class:`tuple` <:py:obj:`float`> | :py:class:`tuple` <:py:obj:`float`> | +-------------------------+-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | DEVVAR_USHORTARRAY | Numpy | :py:class:`numpy.ndarray` (dtype= :py:obj:`numpy.uint16`) | :py:class:`numpy.ndarray` (dtype= :py:obj:`numpy.uint16`) | | or +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | (DEV_USHORT + SPECTRUM) | Bytes | :py:obj:`bytes` (which is in fact equal to :py:obj:`str`) | :py:obj:`bytes` | | or +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | (DEV_USHORT + IMAGE) | ByteArray | :py:obj:`bytearray` | :py:obj:`bytearray` | | +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | | String | :py:obj:`str` | String :py:obj:`str` (decoded with default python encoding *utf-8*!!!) | | +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | | List | :py:class:`list` <:py:obj:`int`> | :py:class:`list` <:py:obj:`int`> | | +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | | Tuple | :py:class:`tuple` <:py:obj:`int`> | :py:class:`tuple` <:py:obj:`int`> | +-------------------------+-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | DEVVAR_ULONGARRAY | Numpy | :py:class:`numpy.ndarray` (dtype= :py:obj:`numpy.uint32`) | :py:class:`numpy.ndarray` (dtype= :py:obj:`numpy.uint32`) | | or +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | (DEV_ULONG + SPECTRUM) | Bytes | :py:obj:`bytes` (which is in fact equal to :py:obj:`str`) | :py:obj:`bytes` | | or +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | (DEV_ULONG + IMAGE) | ByteArray | :py:obj:`bytearray` | :py:obj:`bytearray` | | +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | | String | :py:obj:`str` | String :py:obj:`str` (decoded with default python encoding *utf-8*!!!) | | +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | | List | :py:class:`list` <:py:obj:`int`> | :py:class:`list` <:py:obj:`int`> | | +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | | Tuple | :py:class:`tuple` <:py:obj:`int`> | :py:class:`tuple` <:py:obj:`int`> | +-------------------------+-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | DEVVAR_ULONG64ARRAY | Numpy | :py:class:`numpy.ndarray` (dtype= :py:obj:`numpy.uint64`) | :py:class:`numpy.ndarray` (dtype= :py:obj:`numpy.uint64`) | | or +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | (DEV_ULONG64 + SPECTRUM)| Bytes | :py:obj:`bytes` (which is in fact equal to :py:obj:`str`) | :py:obj:`bytes` | | or +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | (DEV_ULONG64 + IMAGE) | ByteArray | :py:obj:`bytearray` | :py:obj:`bytearray` | | +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | | String | :py:obj:`str` | String :py:obj:`str` (decoded with default python encoding *utf-8*!!!) | | +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | | List | :py:class:`list` | :py:class:`list` <:py:obj:`int`> | | +-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | | Tuple | :py:class:`tuple` | :py:class:`tuple` <:py:obj:`int`> | +-------------------------+-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | DEVVAR_STRINGARRAY | | sequence<:py:obj:`str`> | sequence<:py:obj:`str`> | | or | | | (decoded with *latin-1*, aka *ISO-8859-1*) | | (DEV_STRING + SPECTRUM) | | | | | or | | | | | (DEV_STRING + IMAGE) | | | | +-------------------------+-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | | | sequence of two elements: | sequence of two elements: | | DEV_LONGSTRINGARRAY | | | | | | | 0. :py:class:`numpy.ndarray` (dtype= :py:obj:`numpy.int32`) or | 0. :py:class:`numpy.ndarray` (dtype= :py:obj:`numpy.int32`) or | | | | sequence<:py:obj:`int`> | sequence<:py:obj:`int`> | | | | 1. sequence<:py:obj:`str`> | 1. sequence<:py:obj:`str`> (decoded with *latin-1*, aka *ISO-8859-1*) | +-------------------------+-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | | | sequence of two elements: | sequence of two elements: | | DEV_DOUBLESTRINGARRAY | | | | | | | 0. :py:class:`numpy.ndarray` (dtype= :py:obj:`numpy.float64`) or | 0. :py:class:`numpy.ndarray` (dtype= :py:obj:`numpy.float64`) or | | | | sequence<:py:obj:`int`> | sequence<:py:obj:`int`> | | | | 1. sequence<:py:obj:`str`> | 1. sequence<:py:obj:`str`> (decoded with *latin-1*, aka *ISO-8859-1*) | +-------------------------+-----------------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ For SPECTRUM and IMAGES the actual sequence object used depends on the context where the tango data is used, and the availability of :py:mod:`numpy`. 1. for properties the sequence is always a :py:class:`list`. Example:: >>> import tango >>> db = tango.Database() >>> s = db.get_property(["TangoSynchrotrons"]) >>> print type(s) 2. for attribute/command values - :py:class:`numpy.ndarray` if PyTango was compiled with :py:mod:`numpy` support (default) and :py:mod:`numpy` is installed. - :py:class:`list` otherwise .. _pytango-pipe-data-types: Pipe data types --------------- Pipes require different data types. You can think of them as a structured type. A pipe transports data which is called a *blob*. A *blob* consists of name and a list of fields. Each field is called *data element*. Each *data element* consists of a name and a value. *Data element* names must be unique in the same blob. The value can be of any of the SCALAR or SPECTRUM tango data types (except DevEnum). Additionally, the value can be a *blob* itself. In PyTango, a *blob* is represented by a sequence of two elements: * blob name (str) * data is either: * sequence (:py:class:`list`, :py:class:`tuple`, or other) of data elements where each element is a :py:class:`dict` with the following keys: * *name* (mandatory): (str) data element name * *value* (mandatory): data (compatible with any of the SCALAR or SPECTRUM data types except DevEnum). If value is to be a sub-*blob* then it should be sequence of [*blob name*, sequence of data elements] (see above) * *dtype* (optional, mandatory if a DevEncoded is required): see :ref:`Data type equivalence `. If dtype key is not given, PyTango will try to find the proper tango type by inspecting the value. * a :py:class:`dict` where key is the data element name and value is the data element value (compact version) When using the compact dictionary version note that the order of the data elements is lost. If the order is important for you, consider using :py:class:`collections.OrderedDict` instead (if you have python >=2.7. If not you can use ``ordereddict`` backport module available on pypi). Also, in compact mode it is not possible to enforce a specific type. As a consequence, DevEncoded is not supported in compact mode. The description sounds more complicated that it actually is. Here are some practical examples of what you can return in a server as a read request from a pipe:: import numpy as np # plain (one level) blob showing different tango data types # (explicity and implicit): PIPE0 = \ ('BlobCase0', ({'name': 'DE1', 'value': 123,}, # converts to DevLong64 {'name': 'DE2', 'value': np.int32(456),}, # converts to DevLong {'name': 'DE3', 'value': 789, 'dtype': 'int32'}, # converts to DevLong {'name': 'DE4', 'value': np.uint32(123)}, # converts to DevULong {'name': 'DE5', 'value': range(5), 'dtype': ('uint16',)}, # converts to DevVarUShortArray {'name': 'DE6', 'value': [1.11, 2.22], 'dtype': ('float64',)}, # converts to DevVarDoubleArray {'name': 'DE7', 'value': numpy.zeros((100,))}, # converts to DevVarDoubleArray {'name': 'DE8', 'value': True}, # converts to DevBoolean ) ) # similar as above but in compact version (implicit data type conversion): PIPE1 = \ ('BlobCase1', dict(DE1=123, DE2=np.int32(456), DE3=np.int32(789), DE4=np.uint32(123), DE5=np.arange(5, dtype='uint16'), DE6=[1.11, 2.22], DE7=numpy.zeros((100,)), DE8=True) ) # similar as above but order matters so we use an ordered dict: import collections data = collections.OrderedDict() data['DE1'] = 123 data['DE2'] = np.int32(456) data['DE3'] = np.int32(789) data['DE4'] = np.uint32(123) data['DE5'] = np.arange(5, dtype='uint16') data['DE6'] = [1.11, 2.22] data['DE7'] = numpy.zeros((100,)) data['DE8'] = True PIPE2 = 'BlobCase2', data # another plain blob showing string, string array and encoded data types: PIPE3 = \ ('BlobCase3', ({'name': 'stringDE', 'value': 'Hello'}, {'name': 'VectorStringDE', 'value': ('bonjour', 'le', 'monde')}, {'name': 'DevEncodedDE', 'value': ('json', '"isn\'t it?"'), 'dtype': 'bytes'}, ) ) # blob with sub-blob which in turn has a sub-blob PIPE4 = \ ('BlobCase4', ({'name': '1DE', 'value': ('Inner', ({'name': '1_1DE', 'value': 'Grenoble'}, {'name': '1_2DE', 'value': ('InnerInner', ({'name': '1_1_1DE', 'value': np.int32(111)}, {'name': '1_1_2DE', 'value': [3.33]})) }) )}, {'name': '2DE', 'value': (3,4,5,6), 'dtype': ('int32',) }, ) ) pytango-9.2.2/doc/database.rst000066400000000000000000000006671316324373100162640ustar00rootroot00000000000000Database API ============ .. currentmodule:: tango .. autoclass:: tango.Database :members: .. autoclass:: tango.DbDatum :members: .. autoclass:: tango.DbDevExportInfo :members: .. autoclass:: tango.DbDevExportInfo :members: .. autoclass:: tango.DbDevImportInfo :members: .. autoclass:: tango.DbDevInfo :members: .. autoclass:: tango.DbHistory :members: .. autoclass:: tango.DbServerInfo :members: pytango-9.2.2/doc/encoded.rst000066400000000000000000000002531316324373100161100ustar00rootroot00000000000000 .. _encoded: Encoded API =========== *This feature is only possible since PyTango 7.1.4* .. currentmodule:: tango .. autoclass:: tango.EncodedAttribute :members: pytango-9.2.2/doc/environment.yml000066400000000000000000000001011316324373100170340ustar00rootroot00000000000000name: py35 dependencies: - python=3.5 - numpy - gevent - graphvizpytango-9.2.2/doc/exception.rst000066400000000000000000000306401316324373100165100ustar00rootroot00000000000000.. currentmodule:: tango .. highlight:: python :linenothreshold: 4 .. _pytango-exception-api: Exception API ============= Exception definition -------------------- All the exceptions that can be thrown by the underlying Tango C++ API are available in the PyTango python module. Hence a user can catch one of the following exceptions: - :class:`DevFailed` - :class:`ConnectionFailed` - :class:`CommunicationFailed` - :class:`WrongNameSyntax` - :class:`NonDbDevice` - :class:`WrongData` - :class:`NonSupportedFeature` - :class:`AsynCall` - :class:`AsynReplyNotArrived` - :class:`EventSystemFailed` - :class:`NamedDevFailedList` - :class:`DeviceUnlocked` When an exception is caught, the sys.exc_info() function returns a tuple of three values that give information about the exception that is currently being handled. The values returned are (type, value, traceback). Since most functions don't need access to the traceback, the best solution is to use something like exctype, value = sys.exc_info()[:2] to extract only the exception type and value. If one of the Tango exceptions is caught, the exctype will be class name of the exception (DevFailed, .. etc) and the value a tuple of dictionary objects all of which containing the following kind of key-value pairs: - **reason**: a string describing the error type (more readable than the associated error code) - **desc**: a string describing in plain text the reason of the error. - **origin**: a string giving the name of the (C++ API) method which thrown the exception - **severity**: one of the strings WARN, ERR, PANIC giving severity level of the error. :: import tango # How to protect the script from exceptions raised by the Tango try: # Get proxy on a non existing device should throw an exception device = tango.DeviceProxy("non/existing/device") except DevFailed as df: print("Failed to create proxy to non/existing/device:\n%s" % df) Throwing exception in a device server ------------------------------------- The C++ :class:`~tango::Except` class with its most important methods have been wrapped to Python. Therefore, in a Python device server, you have the following methods to throw, re-throw or print a Tango::DevFailed exception : - :meth:`~tango.Except.throw_exception` which is a static method - :meth:`~tango.Except.re_throw_exception` which is also a static method - :meth:`~tango.Except.print_exception` which is also a static method The following code is an example of a command method requesting a command on a sub-device and re-throwing the exception in case of:: try: dev.command_inout("SubDevCommand") except tango.DevFailed as df: tango.Except.re_throw_exception(df, "MyClass_CommandFailed", "Sub device command SubdevCommand failed", "Command()") :line 2: Send the command to the sub device in a try/catch block :line 4-6: Re-throw the exception and add a new level of information in the exception stack Exception API ------------- .. autoclass:: tango.Except :show-inheritance: :members: .. autoclass:: tango.DevError :show-inheritance: :members: .. autoexception:: tango.DevFailed :show-inheritance: :members: .. autoexception:: tango.ConnectionFailed :show-inheritance: This exception is thrown when a problem occurs during the connection establishment between the application and the device. The API is stateless. This means that DeviceProxy constructors filter most of the exception except for cases described in the following table. The desc DevError structure field allows a user to get more precise information. These informations are : **DB_DeviceNotDefined** The name of the device not defined in the database **API_CommandFailed** The device and command name **API_CantConnectToDevice** The device name **API_CorbaException** The name of the CORBA exception, its reason, its locality, its completed flag and its minor code **API_CantConnectToDatabase** The database server host and its port number **API_DeviceNotExported** The device name .. autoexception:: tango.CommunicationFailed :show-inheritance: This exception is thrown when a communication problem is detected during the communication between the client application and the device server. It is a two levels Tango::DevError structure. In case of time-out, the DevError structures fields are: +-------+--------------------+-------------------------------------------------+----------+ | Level | Reason | Desc | Severity | +=======+====================+=================================================+==========+ | 0 | API_CorbaException | CORBA exception fields translated into a string | ERR | +-------+--------------------+-------------------------------------------------+----------+ | 1 | API_DeviceTimedOut | String with time-out value and device name | ERR | +-------+--------------------+-------------------------------------------------+----------+ For all other communication errors, the DevError structures fields are: +-------+-------------------------+----------------------------------------------------+----------+ | Level | Reason | Desc | Severity | +=======+=========================+====================================================+==========+ | 0 | API_CorbaException | CORBA exception fields translated into a string | ERR | +-------+-------------------------+----------------------------------------------------+----------+ | 1 | API_CommunicationFailed | String with device, method, command/attribute name | ERR | +-------+-------------------------+----------------------------------------------------+----------+ .. autoexception:: tango.WrongNameSyntax :show-inheritance: This exception has only one level of Tango::DevError structure. The possible value for the reason field are : **API_UnsupportedProtocol** This error occurs when trying to build a DeviceProxy or an AttributeProxy instance for a device with an unsupported protocol. Refer to the appendix on device naming syntax to get the list of supported database modifier **API_UnsupportedDBaseModifier** This error occurs when trying to build a DeviceProxy or an AttributeProxy instance for a device/attribute with a database modifier unsupported. Refer to the appendix on device naming syntax to get the list of supported database modifier **API_WrongDeviceNameSyntax** This error occurs for all the other error in device name syntax. It is thrown by the DeviceProxy class constructor. **API_WrongAttributeNameSyntax** This error occurs for all the other error in attribute name syntax. It is thrown by the AttributeProxy class constructor. **API_WrongWildcardUsage** This error occurs if there is a bad usage of the wildcard character .. autoexception:: tango.NonDbDevice :show-inheritance: This exception has only one level of Tango::DevError structure. The reason field is set to API_NonDatabaseDevice. This exception is thrown by the API when using the DeviceProxy or AttributeProxy class database access for non-database device. .. autoexception:: tango.WrongData :show-inheritance: This exception has only one level of Tango::DevError structure. The possible value for the reason field are : **API_EmptyDbDatum** This error occurs when trying to extract data from an empty DbDatum object **API_IncompatibleArgumentType** This error occurs when trying to extract data with a type different than the type used to send the data **API_EmptyDeviceAttribute** This error occurs when trying to extract data from an empty DeviceAttribute object **API_IncompatibleAttrArgumentType** This error occurs when trying to extract attribute data with a type different than the type used to send the data **API_EmptyDeviceData** This error occurs when trying to extract data from an empty DeviceData object **API_IncompatibleCmdArgumentType** This error occurs when trying to extract command data with a type different than the type used to send the data .. autoexception:: tango.NonSupportedFeature :show-inheritance: This exception is thrown by the API layer when a request to a feature implemented in Tango device interface release n is requested for a device implementing Tango device interface n-x. There is one possible value for the reason field which is API_UnsupportedFeature. .. autoexception:: tango.AsynCall :show-inheritance: This exception is thrown by the API layer when a the asynchronous model id badly used. This exception has only one level of Tango::DevError structure. The possible value for the reason field are : **API_BadAsynPollId** This error occurs when using an asynchronous request identifier which is not valid any more. **API_BadAsyn** This error occurs when trying to fire callback when no callback has been previously registered **API_BadAsynReqType** This error occurs when trying to get result of an asynchronous request with an asynchronous request identifier returned by a non-coherent asynchronous request (For instance, using the asynchronous request identifier returned by a command_inout_asynch() method with a read_attribute_reply() attribute). .. autoexception:: tango.AsynReplyNotArrived :show-inheritance: This exception is thrown by the API layer when: - a request to get asynchronous reply is made and the reply is not yet arrived - a blocking wait with timeout for asynchronous reply is made and the timeout expired. There is one possible value for the reason field which is API_AsynReplyNotArrived. .. autoexception:: tango.EventSystemFailed :show-inheritance: This exception is thrown by the API layer when subscribing or unsubscribing from an event failed. This exception has only one level of Tango::DevError structure. The possible value for the reason field are : **API_NotificationServiceFailed** This error occurs when the subscribe_event() method failed trying to access the CORBA notification service **API_EventNotFound** This error occurs when you are using an incorrect event_id in the unsubscribe_event() method **API_InvalidArgs** This error occurs when NULL pointers are passed to the subscribe or unsubscribe event methods **API_MethodArgument** This error occurs when trying to subscribe to an event which has already been subsribed to **API_DSFailedRegisteringEvent** This error means that the device server to which the device belongs to failed when it tries to register the event. Most likely, it means that there is no event property defined **API_EventNotFound** Occurs when using a wrong event identifier in the unsubscribe_event method .. autoexception:: tango.DeviceUnlocked :show-inheritance: This exception is thrown by the API layer when a device locked by the process has been unlocked by an admin client. This exception has two levels of Tango::DevError structure. There is only possible value for the reason field which is **API_DeviceUnlocked** The device has been unlocked by another client (administration client) The first level is the message reported by the Tango kernel from the server side. The second layer is added by the client API layer with informations on which API call generates the exception and device name. .. autoexception:: tango.NotAllowed :show-inheritance: .. autoexception:: tango.NamedDevFailedList :show-inheritance: This exception is only thrown by the DeviceProxy::write_attributes() method. In this case, it is necessary to have a new class of exception to transfer the error stack for several attribute(s) which failed during the writing. Therefore, this exception class contains for each attributes which failed : - The name of the attribute - Its index in the vector passed as argumen tof the write_attributes() method - The error stack pytango-9.2.2/doc/faq.rst000066400000000000000000000027431316324373100152640ustar00rootroot00000000000000.. currentmodule:: tango .. _pytango-faq: FAQ === Answers to general Tango questions can be found in the `general tango tutorial `_. Please also check the `general tango how to `_. **How can I report an issue?** Bug reports are very valuable for the community. Please open a new issue on the GitHub issues_ page. **How can I contribute to PyTango and the documentation?** Contribution are always welcome! You can open pull requests on the GitHub PRs_ page. **I got a libbost_python error when I try to import tango module...** For instance:: >>> import tango ImportError: libboost_python.so.1.53.0: cannot open shared object file: No such file or directory You must check that you have the correct boost python installed on your computer. To see which boost python file PyTango needs, type: .. sourcecode:: console $ ldd /usr/lib64/python2.7/site-packages/tango/_tango.so linux-vdso.so.1 => (0x00007ffea7562000) libtango.so.9 => /lib64/libtango.so.9 (0x00007fac04011000) libomniORB4.so.1 => /lib64/libomniORB4.so.1 (0x00007fac03c62000) libboost_python.so.1.53.0 => not found [...] **I have more questions, where can I ask?** The `Tango forum `_ is a good place to get some support. Meet us in the `Python section `_. pytango-9.2.2/doc/green_modes/000077500000000000000000000000001316324373100162445ustar00rootroot00000000000000pytango-9.2.2/doc/green_modes/client_asyncio_mode.rst000066400000000000000000000010151316324373100230020ustar00rootroot00000000000000asyncio mode ~~~~~~~~~~~~ Asyncio_ mode is similar to gevent but it uses explicit coroutines. You can compare gevent and asyncio examples. .. literalinclude:: ../../examples/asyncio_green_mode/asyncio_simple_example.py :linenos: Below you can find a TCP server example, which runs in an asynchronous mode and waits for a device's attribute name from a TCP client, then asks the device for a value and replies to the TCP client. .. literalinclude:: ../../examples/asyncio_green_mode/tcp_server_example.py :linenos:pytango-9.2.2/doc/green_modes/client_futures_mode.rst000066400000000000000000000067261316324373100230500ustar00rootroot00000000000000futures mode ~~~~~~~~~~~~ Using :mod:`concurrent.futures` cooperative mode in PyTango is relatively easy:: >>> from tango.futures import DeviceProxy >>> dev = DeviceProxy("sys/tg_test/1") >>> dev.get_green_mode() tango.GreenMode.Futures >>> print(dev.state()) RUNNING The :func:`tango.futures.DeviceProxy` API is exactly the same as the standard :class:`~tango.DeviceProxy`. The difference is in the semantics of the methods that involve synchronous network calls (constructor included) which may block the execution for a relatively big amount of time. The list of methods that have been modified to accept *futures* semantics are, on the :func:`tango.futures.DeviceProxy`: * Constructor * :meth:`~DeviceProxy.state` * :meth:`~DeviceProxy.status` * :meth:`~DeviceProxy.read_attribute` * :meth:`~DeviceProxy.write_attribute` * :meth:`~DeviceProxy.write_read_attribute` * :meth:`~DeviceProxy.read_attributes` * :meth:`~DeviceProxy.write_attributes` * :meth:`~DeviceProxy.ping` So how does this work in fact? I see no difference from using the *standard* :class:`~tango.DeviceProxy`. Well, this is, in fact, one of the goals: be able to use a *futures* cooperation without changing the API. Behind the scenes the methods mentioned before have been modified to be able to work cooperatively. All of the above methods have been boosted with two extra keyword arguments *wait* and *timeout* which allow to fine tune the behaviour. The *wait* parameter is by default set to `True` meaning wait for the request to finish (the default semantics when not using green mode). If *wait* is set to `True`, the timeout determines the maximum time to wait for the method to execute. The default is `None` which means wait forever. If *wait* is set to `False`, the *timeout* is ignored. If *wait* is set to `True`, the result is the same as executing the *standard* method on a :class:`~tango.DeviceProxy`. If, *wait* is set to `False`, the result will be a :class:`concurrent.futures.Future`. In this case, to get the actual value you will need to do something like:: >>> from tango.futures import DeviceProxy >>> dev = DeviceProxy("sys/tg_test/1") >>> result = dev.state(wait=False) >>> result >>> # this will be the blocking code >>> state = result.result() >>> print(state) RUNNING Here is another example using :meth:`~DeviceProxy.read_attribute`:: >>> from tango.futures import DeviceProxy >>> dev = DeviceProxy("sys/tg_test/1") >>> result = dev.read_attribute('wave', wait=False) >>> result >>> dev_attr = result.result() >>> print(dev_attr) DeviceAttribute[ data_format = tango.AttrDataFormat.SPECTRUM dim_x = 256 dim_y = 0 has_failed = False is_empty = False name = 'wave' nb_read = 256 nb_written = 0 quality = tango.AttrQuality.ATTR_VALID r_dimension = AttributeDimension(dim_x = 256, dim_y = 0) time = TimeVal(tv_nsec = 0, tv_sec = 1383923329, tv_usec = 451821) type = tango.CmdArgType.DevDouble value = array([ -9.61260664e-01, -9.65924853e-01, -9.70294813e-01, -9.74369212e-01, -9.78146810e-01, -9.81626455e-01, -9.84807087e-01, -9.87687739e-01, -9.90267531e-01, ... 5.15044507e-1]) w_dim_x = 0 w_dim_y = 0 w_dimension = AttributeDimension(dim_x = 0, dim_y = 0) w_value = None] pytango-9.2.2/doc/green_modes/client_gevent_mode.rst000066400000000000000000000077041316324373100226400ustar00rootroot00000000000000gevent mode ~~~~~~~~~~~ .. warning:: Before using gevent mode please note that at the time of writing this documentation, *tango.gevent* requires the latest version 1.0 of gevent (which has been released the day before :-P). Using gevent_ cooperative mode in PyTango is relatively easy:: >>> from tango.gevent import DeviceProxy >>> dev = DeviceProxy("sys/tg_test/1") >>> dev.get_green_mode() tango.GreenMode.Gevent >>> print(dev.state()) RUNNING The :func:`tango.gevent.DeviceProxy` API is exactly the same as the standard :class:`~tango.DeviceProxy`. The difference is in the semantics of the methods that involve synchronous network calls (constructor included) which may block the execution for a relatively big amount of time. The list of methods that have been modified to accept *gevent* semantics are, on the :func:`tango.gevent.DeviceProxy`: * Constructor * :meth:`~DeviceProxy.state` * :meth:`~DeviceProxy.status` * :meth:`~DeviceProxy.read_attribute` * :meth:`~DeviceProxy.write_attribute` * :meth:`~DeviceProxy.write_read_attribute` * :meth:`~DeviceProxy.read_attributes` * :meth:`~DeviceProxy.write_attributes` * :meth:`~DeviceProxy.ping` So how does this work in fact? I see no difference from using the *standard* :class:`~tango.DeviceProxy`. Well, this is, in fact, one of the goals: be able to use a gevent cooperation without changing the API. Behind the scenes the methods mentioned before have been modified to be able to work cooperatively with other greenlets. All of the above methods have been boosted with two extra keyword arguments *wait* and *timeout* which allow to fine tune the behaviour. The *wait* parameter is by default set to `True` meaning wait for the request to finish (the default semantics when not using green mode). If *wait* is set to `True`, the timeout determines the maximum time to wait for the method to execute. The default timeout is `None` which means wait forever. If *wait* is set to `False`, the *timeout* is ignored. If *wait* is set to `True`, the result is the same as executing the *standard* method on a :class:`~tango.DeviceProxy`. If, *wait* is set to `False`, the result will be a :class:`gevent.event.AsyncResult`. In this case, to get the actual value you will need to do something like:: >>> from tango.gevent import DeviceProxy >>> dev = DeviceProxy("sys/tg_test/1") >>> result = dev.state(wait=False) >>> result >>> # this will be the blocking code >>> state = result.get() >>> print(state) RUNNING Here is another example using :meth:`~DeviceProxy.read_attribute`:: >>> from tango.gevent import DeviceProxy >>> dev = DeviceProxy("sys/tg_test/1") >>> result = dev.read_attribute('wave', wait=False) >>> result >>> dev_attr = result.get() >>> print(dev_attr) DeviceAttribute[ data_format = tango.AttrDataFormat.SPECTRUM dim_x = 256 dim_y = 0 has_failed = False is_empty = False name = 'wave' nb_read = 256 nb_written = 0 quality = tango.AttrQuality.ATTR_VALID r_dimension = AttributeDimension(dim_x = 256, dim_y = 0) time = TimeVal(tv_nsec = 0, tv_sec = 1383923292, tv_usec = 886720) type = tango.CmdArgType.DevDouble value = array([ -9.61260664e-01, -9.65924853e-01, -9.70294813e-01, -9.74369212e-01, -9.78146810e-01, -9.81626455e-01, -9.84807087e-01, -9.87687739e-01, -9.90267531e-01, ... 5.15044507e-1]) w_dim_x = 0 w_dim_y = 0 w_dimension = AttributeDimension(dim_x = 0, dim_y = 0) w_value = None] .. note:: due to the internal workings of gevent, setting the *wait* flag to `True` (default) doesn't prevent other greenlets from running in *parallel*. This is, in fact, one of the major bonus of working with :mod:`gevent` when compared with :mod:`concurrent.futures` pytango-9.2.2/doc/green_modes/green.rst000066400000000000000000000020461316324373100201000ustar00rootroot00000000000000.. currentmodule:: tango Green mode ========== PyTango supports cooperative green Tango objects. Since version 8.1 two *green* modes have been added: :obj:`~tango.GreenMode.Futures` and :obj:`~tango.GreenMode.Gevent`. In version 9.2.0 another one has been added: :obj:`~tango.GreenMode.Asyncio`. The :obj:`~tango.GreenMode.Futures` uses the standard python module :mod:`concurrent.futures`. The :obj:`~tango.GreenMode.Gevent` mode uses the well known gevent_ library. The newest, :obj:`~tango.GreenMode.Asyncio` mode, uses asyncio_ - a Python library for asynchronous programming (it's featured as a part of a standard Python distribution since version 3.5 of Python; it's available on PyPI for older ones). You can set the PyTango green mode at a global level. Set the environment variable :envvar:`PYTANGO_GREEN_MODE` to either *futures*, *gevent* or *asyncio* (case insensitive). If this environment variable is not defined the PyTango global green mode defaults to *Synchronous*. .. include:: green_modes_client.rst .. include:: green_modes_server.rst pytango-9.2.2/doc/green_modes/green_modes_client.rst000066400000000000000000000022641316324373100226270ustar00rootroot00000000000000Client green modes ------------------ You can also change the active global green mode at any time in your program:: >>> from tango import DeviceProxy, GreenMode >>> from tango import set_green_mode, get_green_mode >>> get_green_mode() tango.GreenMode.Synchronous >>> dev = DeviceProxy("sys/tg_test/1") >>> dev.get_green_mode() tango.GreenMode.Synchronous >>> set_green_mode(GreenMode.Futures) >>> get_green_mode() tango.GreenMode.Futures >>> dev.get_green_mode() tango.GreenMode.Futures As you can see by the example, the global green mode will affect any previously created :class:`DeviceProxy` using the default *DeviceProxy* constructor parameters. You can specificy green mode on a :class:`DeviceProxy` at creation time. You can also change the green mode at any time:: >>> from tango.futures import DeviceProxy >>> dev = DeviceProxy("sys/tg_test/1") >>> dev.get_green_mode() tango.GreenMode.Futures >>> dev.set_green_mode(GreenMode.Synchronous) >>> dev.get_green_mode() tango.GreenMode.Synchronous .. include:: client_futures_mode.rst .. include:: client_gevent_mode.rst .. include:: client_asyncio_mode.rstpytango-9.2.2/doc/green_modes/green_modes_server.rst000066400000000000000000000051061316324373100226550ustar00rootroot00000000000000Server green modes ------------------ .. warning:: Green modes for the server side are still very much experimental. If you encounter any issues, please report them on the GitHub issues_ page. PyTango server API from version 9.2.0 supports two green modes: :obj:`~tango.GreenMode.Gevent` and :obj:`~tango.GreenMode.Asyncio`. Both can be used in writing new device servers in an asynchronous way. gevent mode ~~~~~~~~~~~ This mode lets you convert your existing devices to asynchronous devices easily. You just add `green_mode = tango.GreenMode.Gevent` line to your device class. Consider this example:: class GeventDevice(Device): green_mode = tango.GreenMode.Gevent Every method in your device class will be treated as a coroutine implicitly. This can be beneficial, but also potentially dangerous as it is a lot harder to debug. You should use this green mode with care. :obj:`~tango.GreenMode.Gevent` green mode is useful when you don't want to change too much in your existing code (or you don't feel comfortable with writing syntax of asynchronous calls). Another thing to have in mind is that the Tango monitor lock is present - you can't have two read operations happening concurrently. Any subsequent ones will always have to wait for the first one to finish. Greenlets (a task in a background, but handled within the event loop) can be used. asyncio mode ~~~~~~~~~~~~ The way asyncio green mode on the server side works is it redirects all user code to an event loop. This means that all user methods become coroutines, so in Python > 3.5 you should define them with `async` keyword. In Python < 3.5, you should use a `@coroutine` decorator. This also means that in order to convert existing code of your devices to :obj:`~tango.GreenMode.Asyncio` green mode you will have to introduce at least those changes. But, of course, to truly benefit from this green mode (and asynchronous approach in general), you should introduce more far-fetched changes! The main benefit of asynchronous programing approach is that it lets you control precisely when code is run sequentially without interruptions and when control can be given back to the event loop. It's especially useful if you want to perform some long operations and don't want to prevent clients from accessing other parts of your device (attributes, in particular). This means that in :obj:`~tango.GreenMode.Asyncio` green mode there is no monitor lock! The example below shows how asyncio can be used to write an asynchronous Tango device: .. literalinclude:: ../../examples/asyncio_green_mode/asyncio_device_example.py :linenos: pytango-9.2.2/doc/how-to-contribute.rst000066400000000000000000000041171316324373100201030ustar00rootroot00000000000000.. currentmodule:: tango .. highlight:: python :linenothreshold: 3 .. _how-to-contribute: ================= How to Contribute ================= Everyone is welcome to contribute to PyTango project. If you don't feel comfortable with writing core PyTango we are looking for contributors to documentation or/and tests. Workflow -------- A normal Git workflow is used. You can find how to automate your git branching workflow example_. Good practices: * There is no special policy regarding commit messages. They should be short (50 chars or less) and contain summary of all changes, * A CONTRIBUTING file is required, * Pull requests should be ALWAYS made to develop branch, not to a master branch. reStructuredText and Sphinx --------------------------- Documentation is written in reStructuredText_ and built with Sphinx_ - it's easy to contribute. It also uses autodoc_ importing docstrings from tango package. Theme is not important, a theme prepared for Tango Community can be also used. Source code standard -------------------- All code should be PEP8_ compatible. We have set up checking code quality with Codacy_ which uses PyLint_ under the hood. You can see how well your code is rated on your PR's page. .. note:: The accepted policy is that your code **cannot** introduce more issues than it solves! You can also use other tools for checking PEP8_ compliance for your personal use. One good example of such a tool is Flake8_ which combines PEP8_ and PyFlakes_. There are plugins_ for various IDEs so that you can use your favourite tool easily. .. _example: http://jeffkreeftmeijer.com/2010/why-arent-you-using-git-flow .. _autodoc: https://pypi.python.org/pypi/autodoc .. _PEP8: https://www.python.org/dev/peps/pep-0008 .. _Flake8: https://gitlab.com/pycqa/flake8 .. _PyFlakes: https://github.com/PyCQA/pyflakes .. _plugins: https://gitlab.com/pycqa/flake8/issues/286 .. _reStructuredText: http://docutils.sourceforge.net/rst.html .. _Sphinx: http://www.sphinx-doc.org/en/stable .. _PyLint: https://www.pylint.org .. _Codacy: https://www.codacy.com/app/tango-controls/pytango/dashboard pytango-9.2.2/doc/howto.rst000066400000000000000000001365471316324373100156670ustar00rootroot00000000000000.. currentmodule:: tango .. highlight:: python :linenothreshold: 3 .. _pytango-howto: ====== How to ====== This is a small list of how-tos specific to PyTango. A more general Tango how-to list can be found `here `_. How to contribute ----------------- Everyone is welcome to contribute to PyTango project. If you don't feel comfortable with writing core PyTango we are looking for contributors to documentation or/and tests. It refers to the next section, see :ref:`how-to-contribute`. Check the default TANGO host ---------------------------- The default TANGO host can be defined using the environment variable :envvar:`TANGO_HOST` or in a `tangorc` file (see `Tango environment variables `_ for complete information) To check what is the current value that TANGO uses for the default configuration simple do:: >>> import tango >>> tango.ApiUtil.get_env_var("TANGO_HOST") 'homer.simpson.com:10000' Check TANGO version ------------------- There are two library versions you might be interested in checking: The PyTango version:: >>> import tango >>> tango.__version__ '9.2.2' >>> tango.__version_info__ (9, 2, 2) and the Tango C++ library version that PyTango was compiled with:: >>> import tango >>> tango.constants.TgLibVers '9.2.2' Report a bug ------------ Bugs can be reported as tickets in `TANGO Source forge `_. When making a bug report don't forget to select *PyTango* in **Category**. It is also helpfull if you can put in the ticket description the PyTango information. It can be a dump of: .. sourcecode:: console $ python -c "from tango.utils import info; print(info())" Test the connection to the Device and get it's current state ------------------------------------------------------------ One of the most basic examples is to get a reference to a device and determine if it is running or not:: from tango import DeviceProxy # Get proxy on the tango_test1 device print("Creating proxy to TangoTest device...") tango_test = DeviceProxy("sys/tg_test/1") # ping it print(tango_test.ping()) # get the state print(tango_test.state()) Read and write attributes ------------------------- Basic read/write attribute operations:: from tango import DeviceProxy # Get proxy on the tango_test1 device print("Creating proxy to TangoTest device...") tango_test = DeviceProxy("sys/tg_test/1") # Read a scalar attribute. This will return a tango.DeviceAttribute # Member 'value' contains the attribute value scalar = tango_test.read_attribute("long_scalar") print("Long_scalar value = {0}".format(scalar.value)) # PyTango provides a shorter way: scalar = tango_test.long_scalar.value print("Long_scalar value = {0}".format(scalar)) # Read a spectrum attribute spectrum = tango_test.read_attribute("double_spectrum") # ... or, the shorter version: spectrum = tango_test.double_spectrum # Write a scalar attribute scalar_value = 18 tango_test.write_attribute("long_scalar", scalar_value) # PyTango provides a shorter way: tango_test.long_scalar = scalar_value # Write a spectrum attribute spectrum_value = [1.2, 3.2, 12.3] tango_test.write_attribute("double_spectrum", spectrum_value) # ... or, the shorter version: tango_test.double_spectrum = spectrum_value # Write an image attribute image_value = [ [1, 2], [3, 4] ] tango_test.write_attribute("long_image", image_value) # ... or, the shorter version: tango_test.long_image = image_value Note that if PyTango is compiled with numpy support the values got when reading a spectrum or an image will be numpy arrays. This results in a faster and more memory efficient PyTango. You can also use numpy to specify the values when writing attributes, especially if you know the exact attribute type:: import numpy from tango import DeviceProxy # Get proxy on the tango_test1 device print("Creating proxy to TangoTest device...") tango_test = DeviceProxy("sys/tg_test/1") data_1d_long = numpy.arange(0, 100, dtype=numpy.int32) tango_test.long_spectrum = data_1d_long data_2d_float = numpy.zeros((10,20), dtype=numpy.float64) tango_test.double_image = data_2d_float Execute commands ---------------- As you can see in the following example, when scalar types are used, the Tango binding automagically manages the data types, and writing scripts is quite easy:: from tango import DeviceProxy # Get proxy on the tango_test1 device print("Creating proxy to TangoTest device...") tango_test = DeviceProxy("sys/tg_test/1") # First use the classical command_inout way to execute the DevString command # (DevString in this case is a command of the Tango_Test device) result = tango_test.command_inout("DevString", "First hello to device") print("Result of execution of DevString command = {0}".format(result)) # the same can be achieved with a helper method result = tango_test.DevString("Second Hello to device") print("Result of execution of DevString command = {0}".format(result)) # Please note that argin argument type is automatically managed by python result = tango_test.DevULong(12456) print("Result of execution of DevULong command = {0}".format(result)) Execute commands with more complex types ---------------------------------------- In this case you have to use put your arguments data in the correct python structures:: from tango import DeviceProxy # Get proxy on the tango_test1 device print("Creating proxy to TangoTest device...") tango_test = DeviceProxy("sys/tg_test/1") # The input argument is a DevVarLongStringArray so create the argin # variable containing an array of longs and an array of strings argin = ([1,2,3], ["Hello", "TangoTest device"]) result = tango_test.DevVarLongArray(argin) print("Result of execution of DevVarLongArray command = {0}".format(result)) Work with Groups ---------------- .. todo:: write this how to Handle errors ------------- .. todo:: write this how to .. _pytango-howto-server: For now check :ref:`pytango-exception-api`. Registering devices ------------------- Here is how to define devices in the Tango DataBase:: from tango import Database, DbDevInfo # A reference on the DataBase db = Database() # The 3 devices name we want to create # Note: these 3 devices will be served by the same DServer new_device_name1 = "px1/tdl/mouse1" new_device_name2 = "px1/tdl/mouse2" new_device_name3 = "px1/tdl/mouse3" # Define the Tango Class served by this DServer new_device_info_mouse = DbDevInfo() new_device_info_mouse._class = "Mouse" new_device_info_mouse.server = "ds_Mouse/server_mouse" # add the first device print("Creating device: %s" % new_device_name1) new_device_info_mouse.name = new_device_name1 db.add_device(new_device_info_mouse) # add the next device print("Creating device: %s" % new_device_name2) new_device_info_mouse.name = new_device_name2 db.add_device(new_device_info_mouse) # add the third device print("Creating device: %s" % new_device_name3) new_device_info_mouse.name = new_device_name3 db.add_device(new_device_info_mouse) Setting up device properties ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A more complex example using python subtilities. The following python script example (containing some functions and instructions manipulating a Galil motor axis device server) gives an idea of how the Tango API should be accessed from Python:: from tango import DeviceProxy # connecting to the motor axis device axis1 = DeviceProxy("microxas/motorisation/galilbox") # Getting Device Properties property_names = ["AxisBoxAttachement", "AxisEncoderType", "AxisNumber", "CurrentAcceleration", "CurrentAccuracy", "CurrentBacklash", "CurrentDeceleration", "CurrentDirection", "CurrentMotionAccuracy", "CurrentOvershoot", "CurrentRetry", "CurrentScale", "CurrentSpeed", "CurrentVelocity", "EncoderMotorRatio", "logging_level", "logging_target", "UserEncoderRatio", "UserOffset"] axis_properties = axis1.get_property(property_names) for prop in axis_properties.keys(): print("%s: %s" % (prop, axis_properties[prop][0])) # Changing Properties axis_properties["AxisBoxAttachement"] = ["microxas/motorisation/galilbox"] axis_properties["AxisEncoderType"] = ["1"] axis_properties["AxisNumber"] = ["6"] axis1.put_property(axis_properties) Write a server -------------- Before reading this chapter you should be aware of the TANGO basic concepts. This chapter does not explain what a Tango device or a device server is. This is explained in details in the `Tango control system manual `_ Since version 8.1, PyTango provides a helper module which simplifies the development of a Tango device server. This helper is provided through the :mod:`tango.server` module. Here is a simple example on how to write a *Clock* device server using the high level API .. code-block:: python :linenos: import time from tango.server import Device, attribute, command, pipe class Clock(Device): @attribute def time(self): return time.time() @command(dtype_in=str, dtype_out=str) def strftime(self, format): return time.strftime(format) @pipe def info(self): return ('Information', dict(manufacturer='Tango', model='PS2000', version_number=123)) if __name__ == "__main__": Clock.run_server() **line 2** import the necessary symbols **line 5** tango device class definition. A Tango device must inherit from :class:`tango.server.Device` **line 7-9** definition of the *time* attribute. By default, attributes are double, scalar, read-only. Check the :class:`~tango.server.attribute` for the complete list of attribute options. **line 11-13** the method *strftime* is exported as a Tango command. In receives a string as argument and it returns a string. If a method is to be exported as a Tango command, it must be decorated as such with the :func:`~tango.server.command` decorator **line 15-20** definition of the *info* pipe. Check the :class:`~tango.server.pipe` for the complete list of pipe options. **line 24** start the Tango run loop. The mandatory argument is a list of python classes that are to be exported as Tango classes. Check :func:`~tango.server.run` for the complete list of options Here is a more complete example on how to write a *PowerSupply* device server using the high level API. The example contains: #. a read-only double scalar attribute called *voltage* #. a read/write double scalar expert attribute *current* #. a read-only double image attribute called *noise* #. a *ramp* command #. a *host* device property #. a *port* class property .. code-block:: python :linenos: from time import time from numpy.random import random_sample from tango import AttrQuality, AttrWriteType, DispLevel from tango.server import Device, attribute, command from tango.server import class_property, device_property class PowerSupply(Device): current = attribute(label="Current", dtype=float, display_level=DispLevel.EXPERT, access=AttrWriteType.READ_WRITE, unit="A", format="8.4f", min_value=0.0, max_value=8.5, min_alarm=0.1, max_alarm=8.4, min_warning=0.5, max_warning=8.0, fget="get_current", fset="set_current", doc="the power supply current") noise = attribute(label="Noise", dtype=((float,),), max_dim_x=1024, max_dim_y=1024, fget="get_noise") host = device_property(dtype=str) port = class_property(dtype=int, default_value=9788) @attribute def voltage(self): self.info_stream("get voltage(%s, %d)" % (self.host, self.port)) return 10.0 def get_current(self): return 2.3456, time(), AttrQuality.ATTR_WARNING def set_current(self, current): print("Current set to %f" % current) def get_noise(self): return random_sample((1024, 1024)) @command(dtype_in=float) def ramp(self, value): print("Ramping up...") if __name__ == "__main__": PowerSupply.run_server() .. _logging: Server logging -------------- This chapter instructs you on how to use the tango logging API (log4tango) to create tango log messages on your device server. The logging system explained here is the Tango Logging Service (TLS). For detailed information on how this logging system works please check: * `3.5 The tango logging service `_ * `9.3 The tango logging service `_ The easiest way to start seeing log messages on your device server console is by starting it with the verbose option. Example:: python PyDsExp.py PyDs1 -v4 This activates the console tango logging target and filters messages with importance level DEBUG or more. The links above provided detailed information on how to configure log levels and log targets. In this document we will focus on how to write log messages on your device server. Basic logging ~~~~~~~~~~~~~ The most basic way to write a log message on your device is to use the :class:`~tango.server.Device` logging related methods: * :meth:`~tango.server.Device.debug_stream` * :meth:`~tango.server.Device.info_stream` * :meth:`~tango.server.Device.warn_stream` * :meth:`~tango.server.Device.error_stream` * :meth:`~tango.server.Device.fatal_stream` Example:: def read_voltage(self): self.info_stream("read voltage attribute") # ... return voltage_value This will print a message like:: 1282206864 [-1215867200] INFO test/power_supply/1 read voltage attribute every time a client asks to read the *voltage* attribute value. The logging methods support argument list feature (since PyTango 8.1). Example:: def read_voltage(self): self.info_stream("read_voltage(%s, %d)", self.host, self.port) # ... return voltage_value Logging with print statement ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *This feature is only possible since PyTango 7.1.3* It is possible to use the print statement to log messages into the tango logging system. This is achieved by using the python's print extend form sometimes refered to as *print chevron*. Same example as above, but now using *print chevron*:: def read_voltage(self, the_att): print >>self.log_info, "read voltage attribute" # ... return voltage_value Or using the python 3k print function:: def read_Long_attr(self, the_att): print("read voltage attribute", file=self.log_info) # ... return voltage_value Logging with decorators ~~~~~~~~~~~~~~~~~~~~~~~ *This feature is only possible since PyTango 7.1.3* PyTango provides a set of decorators that place automatic log messages when you enter and when you leave a python method. For example:: @tango.DebugIt() def read_Long_attr(self, the_att): the_att.set_value(self.attr_long) will generate a pair of log messages each time a client asks for the 'Long_attr' value. Your output would look something like:: 1282208997 [-1215965504] DEBUG test/pydsexp/1 -> read_Long_attr() 1282208997 [-1215965504] DEBUG test/pydsexp/1 <- read_Long_attr() Decorators exist for all tango log levels: * :class:`tango.DebugIt` * :class:`tango.InfoIt` * :class:`tango.WarnIt` * :class:`tango.ErrorIt` * :class:`tango.FatalIt` The decorators receive three optional arguments: * show_args - shows method arguments in log message (defaults to False) * show_kwargs shows keyword method arguments in log message (defaults to False) * show_ret - shows return value in log message (defaults to False) Example:: @tango.DebugIt(show_args=True, show_ret=True) def IOLong(self, in_data): return in_data * 2 will output something like:: 1282221947 [-1261438096] DEBUG test/pydsexp/1 -> IOLong(23) 1282221947 [-1261438096] DEBUG test/pydsexp/1 46 <- IOLong() Multiple device classes (Python and C++) in a server ---------------------------------------------------- Within the same python interpreter, it is possible to mix several Tango classes. Let's say two of your colleagues programmed two separate Tango classes in two separated python files: A :class:`PLC` class in a :file:`PLC.py`:: # PLC.py from tango.server import Device class PLC(Device): # bla, bla my PLC code if __name__ == "__main__": PLC.run_server() ... and a :class:`IRMirror` in a :file:`IRMirror.py`:: # IRMirror.py from tango.server import Device class IRMirror(Device): # bla, bla my IRMirror code if __name__ == "__main__": IRMirror.run_server() You want to create a Tango server called `PLCMirror` that is able to contain devices from both PLC and IRMirror classes. All you have to do is write a :file:`PLCMirror.py` containing the code:: # PLCMirror.py from tango.server import run from PLC import PLC from IRMirror import IRMirror run([PLC, IRMirror]) It is also possible to add C++ Tango class in a Python device server as soon as: 1. The Tango class is in a shared library 2. It exist a C function to create the Tango class For a Tango class called MyTgClass, the shared library has to be called MyTgClass.so and has to be in a directory listed in the LD_LIBRARY_PATH environment variable. The C function creating the Tango class has to be called _create_MyTgClass_class() and has to take one parameter of type "char \*" which is the Tango class name. Here is an example of the main function of the same device server than before but with one C++ Tango class called SerialLine:: import tango import sys if __name__ == '__main__': py = tango.Util(sys.argv) util.add_class('SerialLine', 'SerialLine', language="c++") util.add_class(PLCClass, PLC, 'PLC') util.add_class(IRMirrorClass, IRMirror, 'IRMirror') U = tango.Util.instance() U.server_init() U.server_run() :Line 6: The C++ class is registered in the device server :Line 7 and 8: The two Python classes are registered in the device server Create attributes dynamically ----------------------------- It is also possible to create dynamic attributes within a Python device server. There are several ways to create dynamic attributes. One of the way, is to create all the devices within a loop, then to create the dynamic attributes and finally to make all the devices available for the external world. In C++ device server, this is typically done within the Class::device_factory() method. In Python device server, this method is generic and the user does not have one. Nevertheless, this generic device_factory method calls a method named dyn_attr() allowing the user to create his dynamic attributes. It is simply necessary to re-define this method within your Class and to create the dynamic attribute within this method: ``dyn_attr(self, dev_list)`` where dev_list is a list containing all the devices created by the generic device_factory() method. There is another point to be noted regarding dynamic attribute within Python device server. The Tango Python device server core checks that for each attribute it exists methods named _read and/or _write and/or is__allowed. Using dynamic attribute, it is not possible to define these methods because attributes name and number are known only at run-time. To address this issue, the Device_3Impl::add_attribute() method has a diferent signature for Python device server which is: ``add_attribute(self, attr, r_meth = None, w_meth = None, is_allo_meth = None)`` attr is an instance of the Attr class, r_meth is the method which has to be executed with the attribute is read, w_meth is the method to be executed when the attribute is written and is_allo_meth is the method to be executed to implement the attribute state machine. The method passed here as argument as to be class method and not object method. Which argument you have to use depends on the type of the attribute (A WRITE attribute does not need a read method). Note, that depending on the number of argument you pass to this method, you may have to use Python keyword argument. The necessary methods required by the Tango Python device server core will be created automatically as a forward to the methods given as arguments. Here is an example of a device which has a TANGO command called *createFloatAttribute*. When called, this command creates a new scalar floating point attribute with the specified name:: from tango import Util, Attr from tango.server import Device, command class MyDevice(Device): @command(dtype_in=str) def CreateFloatAttribute(self, attr_name): attr = Attr(attr_name, tango.DevDouble) self.add_attribute(attr, self.read_General, self.write_General) def read_General(self, attr): self.info_stream("Reading attribute %s", attr.get_name()) attr.set_value(99.99) def write_General(self, attr): self.info_stream("Writting attribute %s", attr.get_name()) Create/Delete devices dynamically --------------------------------- *This feature is only possible since PyTango 7.1.2* Starting from PyTango 7.1.2 it is possible to create devices in a device server "en caliente". This means that you can create a command in your "management device" of a device server that creates devices of (possibly) several other tango classes. There are two ways to create a new device which are described below. Tango imposes a limitation: the tango class(es) of the device(s) that is(are) to be created must have been registered before the server starts. If you use the high level API, the tango class(es) must be listed in the call to :func:`~tango.server.run`. If you use the lower level server API, it must be done using individual calls to :meth:`~tango.Util.add_class`. Dynamic device from a known tango class name ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you know the tango class name but you don't have access to the :class:`tango.DeviceClass` (or you are too lazy to search how to get it ;-) the way to do it is call :meth:`~tango.Util.create_device` / :meth:`~tango.Util.delete_device`. Here is an example of implementing a tango command on one of your devices that creates a device of some arbitrary class (the example assumes the tango commands 'CreateDevice' and 'DeleteDevice' receive a parameter of type DevVarStringArray with two strings. No error processing was done on the code for simplicity sake):: from tango import Util from tango.server import Device, command class MyDevice(Device): @command(dtype_in=[str]) def CreateDevice(self, pars): klass_name, dev_name = pars util = Util.instance() util.create_device(klass_name, dev_name, alias=None, cb=None) @command(dtype_in=[str]) def DeleteDevice(self, pars): klass_name, dev_name = pars util = Util.instance() util.delete_device(klass_name, dev_name) An optional callback can be registered that will be executed after the device is registed in the tango database but before the actual device object is created and its init_device method is called. It can be used, for example, to initialize some device properties. Dynamic device from a known tango class ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you already have access to the :class:`~tango.DeviceClass` object that corresponds to the tango class of the device to be created you can call directly the :meth:`~tango.DeviceClass.create_device` / :meth:`~tango.DeviceClass.delete_device`. For example, if you wish to create a clone of your device, you can create a tango command called *Clone*:: class MyDevice(tango.Device_4Impl): def fill_new_device_properties(self, dev_name): prop_names = db.get_device_property_list(self.get_name(), "*") prop_values = db.get_device_property(self.get_name(), prop_names.value_string) db.put_device_property(dev_name, prop_values) # do the same for attributes... ... def Clone(self, dev_name): klass = self.get_device_class() klass.create_device(dev_name, alias=None, cb=self.fill_new_device_properties) def DeleteSibling(self, dev_name): klass = self.get_device_class() klass.delete_device(dev_name) Note that the cb parameter is optional. In the example it is given for demonstration purposes only. .. _server: Write a server (original API) ----------------------------- This chapter describes how to develop a PyTango device server using the original PyTango server API. This API mimics the C++ API and is considered low level. You should write a server using this API if you are using code generated by `Pogo tool `_ or if for some reason the high level API helper doesn't provide a feature you need (in that case think of writing a mail to tango mailing list explaining what you cannot do). The main part of a Python device server ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The rule of this part of a Tango device server is to: - Create the :class:`Util` object passing it the Python interpreter command line arguments - Add to this object the list of Tango class(es) which have to be hosted by this interpreter - Initialize the device server - Run the device server loop The following is a typical code for this main function:: if __name__ == '__main__': util = tango.Util(sys.argv) util.add_class(PyDsExpClass, PyDsExp) U = tango.Util.instance() U.server_init() U.server_run() **Line 2** Create the Util object passing it the interpreter command line arguments **Line 3** Add the Tango class *PyDsExp* to the device server. The :meth:`Util.add_class` method of the Util class has two arguments which are the Tango class PyDsExpClass instance and the Tango PyDsExp instance. This :meth:`Util.add_class` method is only available since version 7.1.2. If you are using an older version please use :meth:`Util.add_TgClass` instead. **Line 7** Initialize the Tango device server **Line 8** Run the device server loop The PyDsExpClass class in Python ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The rule of this class is to : - Host and manage data you have only once for the Tango class whatever devices of this class will be created - Define Tango class command(s) - Define Tango class attribute(s) In our example, the code of this Python class looks like:: class PyDsExpClass(tango.DeviceClass): cmd_list = { 'IOLong' : [ [ tango.ArgType.DevLong, "Number" ], [ tango.ArgType.DevLong, "Number * 2" ] ], 'IOStringArray' : [ [ tango.ArgType.DevVarStringArray, "Array of string" ], [ tango.ArgType.DevVarStringArray, "This reversed array"] ], } attr_list = { 'Long_attr' : [ [ tango.ArgType.DevLong , tango.AttrDataFormat.SCALAR , tango.AttrWriteType.READ], { 'min alarm' : 1000, 'max alarm' : 1500 } ], 'Short_attr_rw' : [ [ tango.ArgType.DevShort, tango.AttrDataFormat.SCALAR, tango.AttrWriteType.READ_WRITE ] ] } **Line 1** The PyDsExpClass class has to inherit from the :class:`DeviceClass` class **Line 3 to 7** Definition of the cmd_list :class:`dict` defining commands. The *IOLong* command is defined at lines 3 and 4. The *IOStringArray* command is defined in lines 5 and 6 **Line 9 to 17** Definition of the attr_list :class:`dict` defining attributes. The *Long_attr* attribute is defined at lines 9 to 12 and the *Short_attr_rw* attribute is defined at lines 14 to 16 If you have something specific to do in the class constructor like initializing some specific data member, you will have to code a class constructor. An example of such a contructor is :: def __init__(self, name): tango.DeviceClass.__init__(self, name) self.set_type("TestDevice") The device type is set at line 3. Defining commands ~~~~~~~~~~~~~~~~~ As shown in the previous example, commands have to be defined in a :class:`dict` called *cmd_list* as a data member of the xxxClass class of the Tango class. This :class:`dict` has one element per command. The element key is the command name. The element value is a python list which defines the command. The generic form of a command definition is: ``'cmd_name' : [ [in_type, <"In desc">], [out_type, <"Out desc">], <{opt parameters}>]`` The first element of the value list is itself a list with the command input data type (one of the :class:`tango.ArgType` pseudo enumeration value) and optionally a string describing this input argument. The second element of the value list is also a list with the command output data type (one of the :class:`tango.ArgType` pseudo enumeration value) and optionaly a string describing it. These two elements are mandatory. The third list element is optional and allows additional command definition. The authorized element for this :class:`dict` are summarized in the following array: +-------------------+----------------------+------------------------------------------+ | key | Value | Definition | +===================+======================+==========================================+ | "display level" | DispLevel enum value | The command display level | +-------------------+----------------------+------------------------------------------+ | "polling period" | Any number | The command polling period (mS) | +-------------------+----------------------+------------------------------------------+ | "default command" | True or False | To define that it is the default command | +-------------------+----------------------+------------------------------------------+ Defining attributes ~~~~~~~~~~~~~~~~~~~ As shown in the previous example, attributes have to be defined in a :class:`dict` called **attr_list** as a data member of the xxxClass class of the Tango class. This :class:`dict` has one element per attribute. The element key is the attribute name. The element value is a python :class:`list` which defines the attribute. The generic form of an attribute definition is: ``'attr_name' : [ [mandatory parameters], <{opt parameters}>]`` For any kind of attributes, the mandatory parameters are: ``[attr data type, attr data format, attr data R/W type]`` The attribute data type is one of the possible value for attributes of the :class:`tango.ArgType` pseudo enunmeration. The attribute data format is one of the possible value of the :class:`tango.AttrDataFormat` pseudo enumeration and the attribute R/W type is one of the possible value of the :class:`tango.AttrWriteType` pseudo enumeration. For spectrum attribute, you have to add the maximum X size (a number). For image attribute, you have to add the maximun X and Y dimension (two numbers). The authorized elements for the :class:`dict` defining optional parameters are summarized in the following array: +-------------------+-----------------------------------+------------------------------------------+ | key | value | definition | +===================+===================================+==========================================+ | "display level" | tango.DispLevel enum value | The attribute display level | +-------------------+-----------------------------------+------------------------------------------+ |"polling period" | Any number | The attribute polling period (mS) | +-------------------+-----------------------------------+------------------------------------------+ | "memorized" | "true" or | Define if and how the att. is memorized | | | "true_without_hard_applied" | | +-------------------+-----------------------------------+------------------------------------------+ | "label" | A string | The attribute label | +-------------------+-----------------------------------+------------------------------------------+ | "description" | A string | The attribute description | +-------------------+-----------------------------------+------------------------------------------+ | "unit" | A string | The attribute unit | +-------------------+-----------------------------------+------------------------------------------+ |"standard unit" | A number | The attribute standard unit | +-------------------+-----------------------------------+------------------------------------------+ | "display unit" | A string | The attribute display unit | +-------------------+-----------------------------------+------------------------------------------+ | "format" | A string | The attribute display format | +-------------------+-----------------------------------+------------------------------------------+ | "max value" | A number | The attribute max value | +-------------------+-----------------------------------+------------------------------------------+ | "min value" | A number | The attribute min value | +-------------------+-----------------------------------+------------------------------------------+ | "max alarm" | A number | The attribute max alarm | +-------------------+-----------------------------------+------------------------------------------+ | "min alarm" | A number | The attribute min alarm | +-------------------+-----------------------------------+------------------------------------------+ | "min warning" | A number | The attribute min warning | +-------------------+-----------------------------------+------------------------------------------+ |"max warning" | A number | The attribute max warning | +-------------------+-----------------------------------+------------------------------------------+ | "delta time" | A number | The attribute RDS alarm delta time | +-------------------+-----------------------------------+------------------------------------------+ | "delta val" | A number | The attribute RDS alarm delta val | +-------------------+-----------------------------------+------------------------------------------+ The PyDsExp class in Python ~~~~~~~~~~~~~~~~~~~~~~~~~~~ The rule of this class is to implement methods executed by commands and attributes. In our example, the code of this class looks like:: class PyDsExp(tango.Device_4Impl): def __init__(self,cl,name): tango.Device_4Impl.__init__(self, cl, name) self.info_stream('In PyDsExp.__init__') PyDsExp.init_device(self) def init_device(self): self.info_stream('In Python init_device method') self.set_state(tango.DevState.ON) self.attr_short_rw = 66 self.attr_long = 1246 #------------------------------------------------------------------ def delete_device(self): self.info_stream('PyDsExp.delete_device') #------------------------------------------------------------------ # COMMANDS #------------------------------------------------------------------ def is_IOLong_allowed(self): return self.get_state() == tango.DevState.ON def IOLong(self, in_data): self.info_stream('IOLong', in_data) in_data = in_data * 2 self.info_stream('IOLong returns', in_data) return in_data #------------------------------------------------------------------ def is_IOStringArray_allowed(self): return self.get_state() == tango.DevState.ON def IOStringArray(self, in_data): l = range(len(in_data)-1, -1, -1) out_index=0 out_data=[] for i in l: self.info_stream('IOStringArray <-', in_data[out_index]) out_data.append(in_data[i]) self.info_stream('IOStringArray ->',out_data[out_index]) out_index += 1 self.y = out_data return out_data #------------------------------------------------------------------ # ATTRIBUTES #------------------------------------------------------------------ def read_attr_hardware(self, data): self.info_stream('In read_attr_hardware') def read_Long_attr(self, the_att): self.info_stream("read_Long_attr") the_att.set_value(self.attr_long) def is_Long_attr_allowed(self, req_type): return self.get_state() in (tango.DevState.ON,) def read_Short_attr_rw(self, the_att): self.info_stream("read_Short_attr_rw") the_att.set_value(self.attr_short_rw) def write_Short_attr_rw(self, the_att): self.info_stream("write_Short_attr_rw") self.attr_short_rw = the_att.get_write_value() def is_Short_attr_rw_allowed(self, req_type): return self.get_state() in (tango.DevState.ON,) **Line 1** The PyDsExp class has to inherit from the tango.Device_4Impl **Line 3 to 6** PyDsExp class constructor. Note that at line 6, it calls the *init_device()* method **Line 8 to 12** The init_device() method. It sets the device state (line 9) and initialises some data members **Line 16 to 17** The delete_device() method. This method is not mandatory. You define it only if you have to do something specific before the device is destroyed **Line 23 to 30** The two methods for the *IOLong* command. The first method is called *is_IOLong_allowed()* and it is the command is_allowed method (line 23 to 24). The second method has the same name than the command name. It is the method which executes the command. The command input data type is a Tango long and therefore, this method receives a python integer. **Line 34 to 47** The two methods for the *IOStringArray* command. The first method is its is_allowed method (Line 34 to 35). The second one is the command execution method (Line 37 to 47). The command input data type is a string array. Therefore, the method receives the array in a python list of python strings. **Line 53 to 54** The *read_attr_hardware()* method. Its argument is a Python sequence of Python integer. **Line 56 to 59** The method executed when the *Long_attr* attribute is read. Note that before PyTango 7 it sets the attribute value with the tango.set_attribute_value function. Now the same can be done using the set_value of the attribute object **Line 61 to 62** The is_allowed method for the *Long_attr* attribute. This is an optional method that is called when the attribute is read or written. Not defining it has the same effect as always returning True. The parameter req_type is of type :class:`AttReqtype` which tells if the method is called due to a read or write request. Since this is a read-only attribute, the method will only be called for read requests, obviously. **Line 64 to 67** The method executed when the *Short_attr_rw* attribute is read. **Line 69 to 72** The method executed when the Short_attr_rw attribute is written. Note that before PyTango 7 it gets the attribute value with a call to the Attribute method *get_write_value* with a list as argument. Now the write value can be obtained as the return value of the *get_write_value* call. And in case it is a scalar there is no more the need to extract it from the list. **Line 74 to 75** The is_allowed method for the *Short_attr_rw* attribute. This is an optional method that is called when the attribute is read or written. Not defining it has the same effect as always returning True. The parameter req_type is of type :class:`AttReqtype` which tells if the method is called due to a read or write request. General methods ############### The following array summarizes how the general methods we have in a Tango device server are implemented in Python. +----------------------+-------------------------+-------------+-----------+ | Name | Input par (with "self") |return value | mandatory | +======================+=========================+=============+===========+ | init_device | None | None | Yes | +----------------------+-------------------------+-------------+-----------+ | delete_device | None | None | No | +----------------------+-------------------------+-------------+-----------+ | always_executed_hook | None | None | No | +----------------------+-------------------------+-------------+-----------+ | signal_handler | :py:obj:`int` | None | No | +----------------------+-------------------------+-------------+-----------+ | read_attr_hardware | sequence<:py:obj:`int`> | None | No | +----------------------+-------------------------+-------------+-----------+ Implementing a command ###################### Commands are defined as described above. Nevertheless, some methods implementing them have to be written. These methods names are fixed and depend on command name. They have to be called: - ``is__allowed(self)`` - ``(self, arg)`` For instance, with a command called *MyCmd*, its is_allowed method has to be called `is_MyCmd_allowed` and its execution method has to be called simply *MyCmd*. The following array gives some more info on these methods. +-----------------------+-------------------------+--------------------+-----------+ | Name | Input par (with "self") | return value | mandatory | +=======================+=========================+====================+===========+ | is__allowed | None | Python boolean | No | +-----------------------+-------------------------+--------------------+-----------+ | Cmd_name | Depends on cmd type |Depends on cmd type | Yes | +-----------------------+-------------------------+--------------------+-----------+ Please check :ref:`pytango-data-types` chapter to understand the data types that can be used in command parameters and return values. The following code is an example of how you write code executed when a client calls a command named IOLong:: def is_IOLong_allowed(self): self.debug_stream("in is_IOLong_allowed") return self.get_state() == tango.DevState.ON def IOLong(self, in_data): self.info_stream('IOLong', in_data) in_data = in_data * 2 self.info_stream('IOLong returns', in_data) return in_data **Line 1-3** the is_IOLong_allowed method determines in which conditions the command 'IOLong' can be executed. In this case, the command can only be executed if the device is in 'ON' state. **Line 6** write a log message to the tango INFO stream (click :ref:`here ` for more information about PyTango log system). **Line 7** does something with the input parameter **Line 8** write another log message to the tango INFO stream (click :ref:`here ` for more information about PyTango log system). **Line 9** return the output of executing the tango command Implementing an attribute ######################### Attributes are defined as described in chapter 5.3.2. Nevertheless, some methods implementing them have to be written. These methods names are fixed and depend on attribute name. They have to be called: - ``is__allowed(self, req_type)`` - ``read_(self, attr)`` - ``write_(self, attr)`` For instance, with an attribute called *MyAttr*, its is_allowed method has to be called *is_MyAttr_allowed*, its read method has to be called *read_MyAttr* and its write method has to be called *write_MyAttr*. The *attr* parameter is an instance of :class:`Attr`. Unlike the commands, the is_allowed method for attributes receives a parameter of type :class:`AttReqtype`. Please check :ref:`pytango-data-types` chapter to understand the data types that can be used in attribute. The following code is an example of how you write code executed when a client read an attribute which is called *Long_attr*:: def read_Long_attr(self, the_att): self.info_stream("read attribute name Long_attr") the_att.set_value(self.attr_long) **Line 1** Method declaration with "the_att" being an instance of the Attribute class representing the Long_attr attribute **Line 2** write a log message to the tango INFO stream (click :ref:`here ` for more information about PyTango log system). **Line 3** Set the attribute value using the method set_value() with the attribute value as parameter. The following code is an example of how you write code executed when a client write the Short_attr_rw attribute:: def write_Short_attr_rw(self,the_att): self.info_stream("In write_Short_attr_rw for attribute ",the_att.get_name()) self.attr_short_rw = the_att.get_write_value(data) **Line 1** Method declaration with "the_att" being an instance of the Attribute class representing the Short_attr_rw attribute **Line 2** write a log message to the tango INFO stream (click :ref:`here ` for more information about PyTango log system). **Line 3** Get the value sent by the client using the method get_write_value() and store the value written in the device object. Our attribute is a scalar short attribute so the return value is an int pytango-9.2.2/doc/itango.rst000066400000000000000000000010061316324373100157650ustar00rootroot00000000000000.. highlight:: python :linenothreshold: 4 .. _itango: ====== ITango ====== ITango is a PyTango CLI based on IPython_. It is designed to be used as an IPython profile. .. image:: _static/itango00.png ITango is available since PyTango 7.1.2 and has been moved to a separate project since PyTango 9.2.0: * `package and instructions on PyPI `_ * `sources on GitHub `_ * `documentation on pythonhosted `_ pytango-9.2.2/doc/logo-medium.bmp000066400000000000000000006223261316324373100167060ustar00rootroot00000000000000BM$6(: $F\F\tsw" 03" 6206_c^fjf(-&  9A D"H< %,%(PYyO[y #/0,ttx)>mrx26@,23.]\\w{w/(T$2l,1j,.c) B#9?8Y^Y";&Q"0h+3n.3l.3l-3l-2k-3m.3n.,['iliBeFiev3-0'pFP`,1231>9B8!D3l,2j+2i+2i,2i,3l-2j+'Q!-2:2ZaY$.#'@*X%4m-3l-2j,2i+1i,1i,2j,2i,2i,2i,2j-2k--`(&n3Ztk$$;-,XW\_[m%243>!6.`)3l-2i,2j-2j-2j-2i,1h+2h*3j+2j+'Q"-"GOFPWO$)%N!+^&3m-3m.2j,1g*1g*2g*2h*2h+2i+2j,2i,2g*2h*1i,2i,1i,%N"8:87S^ey3 -*e-041KJO(D'0g,2l.1k-1k.2l/2l/2k.2k.2j-2i,1h+2h+3j,4o.-a("G3$%""#!/ C/b)5o.3l.2k-2j-1i,2h+2h+2h+2h+2h*2h+2h+2i-2j,2h+2g*2g*1h+2j,-a).;-Yy#.P]~ #,^7>I39P7/e-2m/1l/2k/2l/1k.1k.1l/2l/2l/2l/2k.2j-1i,1h+2j+3l,4p.2j,,]&-`(+\'+[&1h+4n-3k,2i,1i,1i,1i,1j-2j-2i-2j-2j-2j-2i,2h,2h+2i-2j-2j-2h+2g+2h+2i+.`(1F0Qr=dWw.CK_6:P)bagj&&. 7]lZ.`+2m/1k.2l/3l04m13l02l/2l/1k.1k.1k.1l/2l/2k.2j-2j-2i,1i+2h+2i+2j,2k,2k-2j,1i,2i,1j-1j-1j-2j.2k.1k.1k.1k.2l/2l/2k.2j-2i,2i,2i,2j-2i,2i,1i,2i+.`(,>+\yBiky5ZhKWkmlnjpu#$!9.W,0j-1l.3l06n36o37o46o35n24m13l02l/2l/2l/1k.1l.2l/2k.2k.2k.2j-2j-2j-2j-2k.2k.2k.1k.1k.3l/4m14m14m13m03m02l/1k.2k.2l/2k.2j-2i,2j-2i,2i,2i,2k.2i,+['5<4cFkbo3apLXl)*(^ztNZl -%#9PPPXqX.d+2m/1k.6o4=s:;r8;r8;r8:r79q77o45n24m13m03l02l/1k.1l.1l/2l/2l/2l/1k.1l.1l.1k.1k.1k.4n18p5:q79q69q68p56o33l01k.2l/2k.2l/2k.2j-2i,2i,2j-1j-2k.0g,%C":`ao6O\r}veDO`GFDZyz9=A1$"7cccvvu7_57q47o46o3;s8Bw?Ax?@x=?w<>u;>t<=s9;r89p67o46n34m13l02k/1k.1k.1k.1k.2k/2l/2l/3l04m15n2;r8=t:=s:=t:=s:;r87o43l01l.2l/2l/1l/2l/2k.2j-2i,2i,2i,1k.,](AG@7\zr2"#1XtClKq[{__~_w^etb3463&$:776rr/_-w;A{>7e5.V-0[/Ay>Ay>@w=@u=>t;t;=t;=s:@Gsz{h=BJ2(&9VVW+)4HdG1i.7o4:t7:v7(P&,+7*KSJ5?5'&E$:n8C|@Bw?Av>?v=?u<>t;t:>t;>t;?ut<@v=@u=@u=?uu<>t;=s:>s;t;=s:x;=v;=t:YcXEPE}}[_\CF@     ad`KVH#J3k,2i,1j-4m16o35n24m12l/2m/'R#)!\acc`;7g)R]\{gtYjGUkf%#I,,KmmSRt#U\4A3m.1i,2k.5m28o57p45n22l/2j--a(puuuunNJv'OLqPsz)0D'&.3344)387Eml&#S#W#W#X#W#W [lvuuuuuvuspib^!\#X"X ^lrJ34p.1j,1k.4n19q69q66o34m12k.3k,)_ouuup2-f.T>dSuCAG0%'>@IbVi_tf|d{Te24F$.@?I][yptqsussuspqttstutuuttvvvvsL&0g+2k-1k.3l18q5:r78p55m22l/2l-$K9iquur(#h;_FkXz#,=g>J^"OOSYWunqoqompspmmrropsqprporuuu]$1h+2k-1j-2l/7o4;r8;r88o53l03m/,^&(elruq% gtq`}>c{LoMlh/d0.2ZYumpoplknnnmlpqmnpljmnmqttb"-_'2m/1k.1k.4m1:r7t;=t:8p55q2'V!0bij!\kHmBfFiz~AdyWUtgihimjjopnnmmlkmljkikZ.1h.2l/1k.1l.3l07o5s;;r87p4/f),%Whk]cVxCgRrlt<=t:8q54o0 @#Khhg96cRq`>cm]?dKHpikjjjlkjnonlikmlijik:'Q$2m/1k.1k.1l.3l09q6>t;>t;;r88s5'U 9fil!YPo^@dFn>b?;jillmjlmjlnnjilmjjio5#G"2k.2l/2l/1k.2l/5o2(Omkt Xg@cnst<9r5'M#9ljni!c+NYy}d?cNneaj(#ijmnnjkmnkiillilkn:)T(1k.2l/1l/2k.2l/3l0;r8@v=@v==s:(P#%ckhs7Nox`SrgV|Loki!ekmnnljlolgimkjloM"A*0g+2l.1l/2k.2l/1l.8o5>t;@v>?v=3e/$Pll-Gh~V{f;dftVS{hnnnpokkmjgikklm^&*,^'2l/2l/1l/2l/1k.3l0;s8@u=?v<9n5!A% ?q#y5Vx?fj-ULoB>ulomnnolijiihhkmp6&N#2m/2l/2l/2l/2l/1k.8p5=t;?u<=s9+U)*q 3]|n6[%Ojl94rnljmlmmjhjkihjnP8(/e)2l/1l/2l/2l/1k.4m1;r8?u<>t;1`."k.KtKpHg&P~Tv%fokimnlmihjkihkc ,'W 3n01k/2k.2l/1k.2k/7p4=t:>t;8k4.$-tFfs@d-U|qfa]hqlkmokjhikjhii>$K"3n/2l/2l.1k.1k.2k/5n2;r8>u<;q8%F&*YTwzChQq2YtWy~IEwlolmlnkiijjiikN8$0i*2l/1l/1k.2l/2l/4m19q6=t;=u:/Z,"*7iz.TCfq}f)$donmomljhjiiilc#5)X!3n/1l/1k.2l/3m04m17p4=t:>u;7j4#1,BfPqeqjqoeqmnokjihiihilM>!3n-2k.1l/1k.3l05n27o4t;v<2c/'62\cNqqzXv|zgplkmkhhjjijgM:"2k,2l/1k.1k.4m18p5:r7=t:>u<9o6#8$nrGjJnrljVRmqllnlhhhiki[''.e&2l/1l/1k.3l09p6ut;@z>@I]nSxQqjqTr fvpjloliikljD B2m-2l/1k.2l/7o4t;>t;@x=,Y'2BCkeNooQrspjvqjkonjlnm[21h*2l.1k.2l/7o4=s:?ut;?uuv<9o5%="jv[y&O{]:a2YG"K]z]Znuutnimr6>2i+1h+1j-2l/4m16o38p5;s9?u<>w;$CZqNqDlqUyAg(QI(OSqzURnuutokpF>2i,1j-2j-1k.2l/4m16o3:q7>t;?v<,Z'PkoOsSy{kVv?c'Q$N'P&N%N)P-S:]IkuMMnssuple)0i-2k.2l/2l/1l/2l/5m27o4b!K K#@trqsqn4-^+2k-2l/1k.1k.1k.2l/5n2:q7@u=?u;1R0^uTy{JqlIl'G!.rkJ"A$0g+2k-2k.1k.2l/2l/6n38p5>s;Av=8a5Wp|X}z9h4;SK{Ty|+Sgs,(e8%L%1g*2j-1k.2l/5n2;r8;r8;r8>uu;?t<6[6VhQvZxU{RZV2*X'2l.2k.2k.4n19p6s:>t;Aw>At=4M;rzGkZzOvJPC8(/g*2m/1k.2l/6o39q6/P/`twGkRtX~i{L$*+](3n/1k/1k.5m28q59e8=MWkMoMpKr}9;l)!G2m/2l/1k.2l/6o3:r7=s:>t;Av>Bx?-|i=b[z;b_et:40i,2m/1k.1k.3m08p5t;Av>Bx@.T,$w+?wEh1Y\CJr,_(2n/1k.1l.2k/5n2:q7=t:@v>Bw?>q;) eENy8;j!!NNo5\bs#H3m.2l/2l/1k.4m17o4;r8?uCzA.O*Euuq$aQ]FMv"a[Tr,Te>QS'W2l.2l/1k.2l/5n28p5;r8?tDz@-!juutt v+%}-(u'#b'"a)$d)"p%vud~~}~g6\nv)0j,2l/1k.2l/5n28p59q6@BA;GNLjLp>LU<4q11k.2l/5m27o49q6;r8>t;Bw?E|B&@#$ nutttu#w%vnln qh8  5)X$3l,3l,2i+2h*2h*1g*2g)1f)2g*/b'":!M;AE| vV;_gK\r, 4q11k.2l/5n27n49q6;r8=s:Aw>F|B7b4CrutuutqmlmkR * ,[%4l,2i+2h*1g*2h*2h+2h+2h+2g+2f*2g(1f(%I!ede<;7-A]N-)QpNpCXy 3m/2l/2k/4m17p48p5:q7F}C2Z-!FquutromkmX%0,[%4k+2g*1g*2h*2g*2h*2h*2h+2h+2i-2j-2h+2h*2i+2h+2h**X#9G8+]CHYX&C\XWYH}3*uuu=3o/1k.2k/3l06o37p4:q7t;Aw?E}B0T,!#Ptutqomkk]  <1g*2i+1g*2h+2i,2i,2i-2j-2k.2l/1l/2l/3n/4p04o/3m.2i+1h*2g*-_&6v?6{\BWXWWXWXXXWX[\=08/+Y&2l.1k.2k/4m16o38p5:q7=s:@v>DyA9i4 /.#g!ytqopnlb"<1g+2j,1h+2h+2i,2i,2j-2k.2l/2l/1l/2m/0i-%M"47(T$1j,2h+2g)0c(:jij3V9/CVXWXXXXXXXWXYO[RmOPO$I 2k-2k.2l/4m15n27p4:q71i+2i,2h+/b(6ggh:JR#"PYWXXXXXXXXXXZB+>70e+2k.1k.4m15n27o49p6;r8=t:Av>Cy@5d1,Qvrppqog <1g*2h+1h+2j-2j-2j-2k.2k.2l/2l/1l/2l.1g,$83(U".a(.^'-\&1g*2h+2k,+Z%#/"6]?=YXXXXXXXXXXXWZ; w(T0=/,](2j-1k.4l15n26o38p5:q7?v<2d/A!2+" ]qoppb =0e*2i+1h*2h+2j-2k.2l/1k.2k-2l-2n-2n-3r+,J2 Wmna"6 Q6-b!3i&2g(&O"+!t:[XTTXXXXXXXXXXXXXXXUS1&7$,_(2k-1k.6n38p58p59p69q6;r8?u<@v=:s7'P$B.R2"!Yopqa !D0e*2i,1h+2i,2i,2k.1l/2k.2l-2h*.T,+@5*:;(2=#"GgnnRbq^}#L}EZK,'VUQ !XXWXXXWXXXXXXXXXXXWWZHMXZW&O#2j-1k.4m18p58q59q69q6:r7s;=t:;q89q6:p7:r7=s:8n5-\)*Y%0d*&Q"2*W$3k,1i,1k.2l/2k.2l/1k.2l.1j.-O3\oosutt v"w$x!wt9=oHj/i0d>WXXXWXXXXXXXXXXXXXXXXXXXYEzK[I0`.Bx?@v<=t;=u<=t;>s;;r89p69q6;r8;q86m23k-3l-,]'0$J3k,1h+2j-2k.2l/2l/2l/2l/2l.1g/&,Blpstuttv#w' z)"{ yQz0cD|!WXXXXXXXXXXXXXXXXXXXXXXXXYG:kok*U(@w=Cx@@u=>u<>u<=t;u<+,^(3k-1j-2k.2l/2k.2k.1l/2l/2l-0^.+A6_ptuuv!v"w&y*$|.(-'~$xmAI~Ho{So8)@WXXXXXXXXXXXXXXXXXXXXXXXWXR:joj)O(Av>EyBBw?Av>?t<=t;:r78o56n35m23k/$L!()T$4n.1k.2k.2l/2k.2k.1l/2l.1h,,N2&.C!VZstuv!w#x%y)"{-'}1+/)(!z x_g}/X^{;"QWXXXXXXXXXXXXXXXXXXXXXXXXYEMQ\Q+Q)?u@v=>t;;r99q69q6-Y*)#J 4n.1k.2l/2l/2k.2l/2l/1k.2m,+E2!Pfk[gwv"w& y(!{*$|.'~1+3-3-0)(!{v)&maxX@hHgB(TXWXXXXXXXXXXXXXXXXXXXXXWXT7bka)I'8k5@y=Bz?Bx?@v=%3$*Z(3n/1k.2l/2k.2l/2l/1k.1k/2p,*>5`opststl23h_pZhS_PX;E|JVXdOY!)F:y$0B [XXXXXXXXXXXXXXXXU='2m/2l/1k.2l/1k.2l/8p59p63l08s5,X* !U("y-'~/).(~,'},%}-&~0)1*.'w@D{W~>g|b1LFI{% aA;WXZ:}$1>D$.="[XXXXXXXXXXXXXXWXS3-c+2l/1k.1k.2l/3m05n29q68p55p2,Y* <7j("x-&}0*1*/)-'~,&}-'~.'~-&~(#oDIzq8`qn/u"fmWCFq%s9WZ:&5=Q8q9ZXXXXXXXXXXXXXXXXQ,fif"C 3m01l.1k.2l/4m18o58o5;q88q5,\)(sq)"o+%{-'~0)0*/)-'}+$|)"{+$|/)0)z&&g9fOs)}lh35ii~6,TA(89yU!&";ZXXXXXXXXXXXXXXXWD{6o36o33l01k.3l08o59p69p6:s73j0-b`~-(k+%z-'}0)0*/)/)/(0*/)~,$z"yHg~-S&vc"dIS^y$h7uW=9ZXXXXXXXXXXXXXXXV?#(#)W'8p58o53m01k.4m19q67p47p4:r7$H"ksk?+/g,2l/4m19p67o41k.2l/6o3:s72c/->,|Js1ZpGeJo`|VvMqUzR<=OY[ZYXWXXXXWR-QQQ(T&1k.2l/5n2:p76n31k.2l/7o4:q7+R)\_\GoEmPv?h~e=bEiW{mQC;7=HVXWXXXWRA4C3,`)3m04m18p5:q75n21k.2l/8p55i2/H.yAge>fmBd=dktk\E>WXXXXWPSono&L$0i-6o38p5;r8:q73m03l04m19r6.^,>E=Bhy7_v(RUuzf3PYWXXW=rUbS+\)2l/7o4:r7;r89q66n36o38o59q6*R'__^3Y.MXWZ2Z02n/3m05m26n39p6;r8:r78p58p54g206/'NvCidQyl5`*+,Xf|0OYXG0\.2m/4l15o27o4:q7cDjLqStnhnsxmxYsfvd/^,=u;;s9;r8w;@v=>u<>u<=s:;r8;r89r6:r7#?!v\Lr[}[/ozo'P%?x=Av>@u<>u<=s:#UdT*X(>w>u<=s:t;u<=s;Bx@Av>?u<>u<u;=t;>t;.X,/Xk*Sz1WWcU)W$2j,2i+1i,2k+2j-1j-2k.2j,2h*3i+-_(iki:5r28p5>t;Av>Aw@@v=>t;>t;>u?u<>t;>u"5q18p5=s:?uu<>t;1Z.T|;cHo[(Q$K3k,1h,2k-(_0,X{wy1_U-HOH9f372k-<@;#=!3p08p5=t:>t;Aw?Aw??u<>u<>t:/W-rurIjyq>Ci4]Xv8I:1k/.c1)]<"SN7`zOw.dB(*K)=q83h,1i,LPK 83l08p5Bw?Av>>u<=t:1]/ada8]fBfgi&P5ZFd*VmBhZol+[a)_,$Av>?u<=t;4b1LNLHowVsjTwwrs*Vh'_,0e+3]0As>:o61i,+[&]m[*[(8q5=t:>t;?uAv>?u<>t;;p8+,+b[Wwj|9fk*b)6m/Bv=Dx@;r87p35tt%Q"7r4=s:?u<>t;@u=Bv??u<>s;>v;sOpk`|Hn'[9:o/Fw?IzDDyA6i4@F@"H 7r4t;@u=Bw?@u=>t;>u:@ad{ijur9du+\DAq?L|BF|A!; )H'4o1;r8>t;@u=?t<@v=@v=>t;=s:":!CgHjklesMr;eo3cP/V-KQF?V=.c+:r7=t;@u=?ut;=s:#?!baC_Ww{W|(Ee[m[g[%R#9s6=s:?utt;>t;?ut;?uu<=r:%2$Wtvl~ikh+2k-1j.([FCj_>f_&@Gesc.\+=u:=t:>t;=t;>t;>t;>u<=r:#8![|2X._(2j-1k-/i.)]E@iX|8_lLvOE[Vt/V->w;t;=t;=u<=t;u<=t;>s;>t;>t;>s;s;=s:=s:=s:=t:=t::o70P-lvQpmmm/0f*2i+1j-1k.2l/3l/0f8+VzNw!V4ItPtz{Fmvxv2Y0>v<>t;=s:=s:u;>t;u;>s:=t:s:>t;=t::r63h0>Wt;>u<=t:u=t::p7/R-knk2@1:l8@v==t;=t: K^pGn'0',(V%2k-2j,1i,2j-2k.1k-2j-5m1=t;Aw?@u<;r8.Q,NONedd/V->s;>u<>t;=t:;r89q68p47n36m07m03e+;5HFh~fVZV**$M!2k-3m.2j-1j-2k.1l/1k.2k.5m2=t:Aw?@w>=t:*S(!)!0u<>s:Bx@Ax?(N&$"=!Av=?t<=t;t;@v=Aw?Au?'I&!*!glg*K(Bx?>u<=s:Bw?Cy@@t>!:/4/OUN1W.Cz@@u=>u;?u<>t;Bw?Bw@@u=*ilj  ;l8F~CCx@DyBBw?>t;9q66n34l13l02m/2l/1l.1l.1k.1k.1k.1k.1k.1k.2k/2k/4m15n27p49p69p6:r8?t@v=@u=@v=@v=@v=?v<@v=Bw?EyBI}FKH6\4'!"+ /9d7H~ECzA@v=>t;=t:=t:=t:=t:?v=Cw@DxAEyBDyAEyBCyABw?Av>Av>Bw?G|DIG9`6&z|y"4N '! -M+DwAF|DDyADxADxADxADyAEyBEyBBw?@v=?u<>t;EyB6]5*  gjg0X^I_<<50Q.GyDI}FH{EH{FI|GI|GI|GEzCDxAFzCFzCDyA8d52uwt=eYp0530U.DxAH{EH|FH|FH{FH{EH{E>h;'C'  Z^Z]zJc$)":!%B#)H(%@#"7 FHEyx^qmprHJG7978;8qrp=cpytango-9.2.2/doc/man/000077500000000000000000000000001316324373100145305ustar00rootroot00000000000000pytango-9.2.2/doc/man/itango.1000066400000000000000000000007031316324373100160730ustar00rootroot00000000000000.TH ITANGO "1" "September 2012" "ITango 8.0.0" "User Commands" .SH NAME PyTango \- manual page for ITango 8.0.0 .SH SYNOPSIS .B usage: \fIitango \fR[\fIIPython options\fR] .SH "SEE ALSO" The documentation for .B IPython The full documentation for .B ITango is maintained as a Texinfo manual. If the .B info and .B ITango programs are properly installed at your site, the command .IP .B info itango .PP should give you access to the complete manual. pytango-9.2.2/doc/mock_tango_extension.py000066400000000000000000000100521316324373100205420ustar00rootroot00000000000000"""Mock the tango._tango extension module. This is useful to build the documentation without building the extension. However this is a bit tricky since the python side relies on what the extension exposes. Here is the list of the mocking aspects that require special attention: - __doc__ should not contain the mock documentation - __mro__ is required for autodoc - __name__ attribute is required - Device_6Impl class should not be accessible - the __base__ attribute for Device_[X]Impl is required - it shoud be possible to set __init__, __getattr__ and __setattr__ methods - tango.base_types.__document_enum needs to be patched before it is called - tango.base_types.__document_method needs to be patched before it is called - the mocks should not have any public methods such as assert_[...] - _tango.constants.TgLibVers is required (e.g. '9.2.2') - _tango._get_tango_lib_release function is required (e.g. lambda: 922) - tango._tango AND tango.constants modules have to be patched - autodoc requires a proper inheritance for the device impl classes Patching tango._tango using sys.modules does not seem to work for python version older than 3.5 (failed with 2.7 and 3.4) """ # Imports import sys from mock import MagicMock __all__ = ['tango'] # Constants TANGO_VERSION = '9.2.2' TANGO_VERSION_INT = int(TANGO_VERSION[::2]) # Extension mock class class ExtensionMock(MagicMock): # Remove the mock documentation __doc__ = None # The method resolution order is required for autodoc __mro__ = object, @property def __name__(self): # __name__ is used for some objects if self._mock_name is None: return '' return self._mock_name.split('.')[-1] def __getattr__(self, name): # Limit device class discovery if name == 'Device_6Impl': raise AttributeError # Regular mock behavior return MagicMock.__getattr__(self, name) def __setattr__(self, name, value): # Ignore unsupported magic methods if name in ["__init__", "__getattr__", "__setattr__", "__str__", "__repr__"]: return # Hook as soon as possible and patch the documentation methods if name == 'side_effect' and self.__name__ == 'AccessControlType': import tango.utils import tango.base_types import tango.device_server tango.utils.__dict__['document_enum'] = document_enum tango.utils.__dict__['document_method'] = document_method tango.base_types.__dict__['__document_enum'] = document_enum tango.device_server.__dict__['__document_method'] = document_method MagicMock.__setattr__(self, name, value) # Remove all public methods for name in dir(ExtensionMock): if not name.startswith('_') and \ callable(getattr(ExtensionMock, name)): setattr(ExtensionMock, name, None) # Patched version of document_enum def document_enum(klass, enum_name, desc, append=True): getattr(klass, enum_name).__doc__ = desc # Patched version of document_enum def document_method(klass, name, doc, add=True): method = lambda self: None method.__doc__ = doc method.__name__ = name setattr(klass, name, method) # Use empty classes for device impl inheritance scheme def set_device_implementations(module): module.DeviceImpl = type('DeviceImpl', (object,), {}) module.Device_2Impl = type('Device_2Impl', (module.DeviceImpl,), {}) module.Device_3Impl = type('Device_3Impl', (module.Device_2Impl,), {}) module.Device_4Impl = type('Device_4Impl', (module.Device_3Impl,), {}) module.Device_5Impl = type('Device_5Impl', (module.Device_4Impl,), {}) # Patch the extension module _tango = ExtensionMock(name='_tango') _tango.constants.TgLibVers = TANGO_VERSION _tango._get_tango_lib_release.return_value = TANGO_VERSION_INT set_device_implementations(_tango) # Patch modules sys.modules['tango._tango'] = _tango sys.modules['tango.constants'] = _tango.constants print('Mocking tango._tango extension module') # Try to import import tango pytango-9.2.2/doc/quicktour.rst000066400000000000000000000214051316324373100165370ustar00rootroot00000000000000.. _pytango-quick-tour: Quick tour ========== This quick tour will guide you through the first steps on using PyTango. Fundamental TANGO concepts -------------------------- Before you begin there are some fundamental TANGO concepts you should be aware of. Tango consists basically of a set of *devices* running somewhere on the network. A device is identified by a unique case insensitive name in the format *//*. Examples: `LAB-01/PowerSupply/01`, `ID21/OpticsHutch/energy`. Each device has a series of *attributes*, *pipes*, *properties* and *commands*. An attribute is identified by a name in a device. It has a value that can be read. Some attributes can also be changed (read-write attributes). Each attribute has a well known, fixed data type. A pipe is a kind of attribute. Unlike attributes, the pipe data type is strucured (in the sence of C struct) and it is dynamic. A property is identified by a name in a device. Usually, devices properties are used to provide a way to configure a device. A command is also identified by a name. A command may or not receive a parameter and may or not return a value when it is executed. Any device has **at least** a *State* and *Status* attributes and *State*, *Status* and *Init* commands. Reading the *State* or *Status* attributes has the same effect as executing the *State* or *Status* commands. Each device as an associated *TANGO Class*. Most of the times the TANGO class has the same name as the object oriented programming class which implements it but that is not mandatory. TANGO devices *live* inside a operating system process called *TANGO Device Server*. This server acts as a container of devices. A device server can host multiple devices of multiple TANGO classes. Devices are, therefore, only accessible when the corresponding TANGO Device Server is running. A special TANGO device server called the *TANGO Database Server* will act as a naming service between TANGO servers and clients. This server has a known address where it can be reached. The machines that run TANGO Device Servers and/or TANGO clients, should export an environment variable called :envvar:`TANGO_HOST` that points to the TANGO Database server address. Example: ``TANGO_HOST=homer.lab.eu:10000`` Minimum setup ------------- This chapter assumes you have already installed PyTango. To explore PyTango you should have a running Tango system. If you are working in a facility/institute that uses Tango, this has probably already been prepared for you. You need to ask your facility/institute tango contact for the :envvar:`TANGO_HOST` variable where Tango system is running. If you are working in an isolate machine you first need to make sure the Tango system is installed and running (see `tango how to `_). Most examples here connect to a device called *sys/tg_test/1* that runs in a TANGO server called *TangoTest* with the instance name *test*. This server comes with the TANGO installation. The TANGO installation also registers the *test* instance. All you have to do is start the TangoTest server on a console:: $ TangoTest test Ready to accept request .. note:: if you receive a message saying that the server is already running, it just means that somebody has already started the test server so you don't need to do anything. Client ------ Finally you can get your hands dirty. The first thing to do is start a python console and import the :mod:`tango` module. The following example shows how to create a proxy to an existing TANGO device, how to read and write attributes and execute commands from a python console:: >>> import tango >>> # create a device object >>> test_device = tango.DeviceProxy("sys/tg_test/1") >>> # every device has a state and status which can be checked with: >>> print(test_device.state()) RUNNING >>> print(test_device.status()) The device is in RUNNING state. >>> # this device has an attribute called "long_scalar". Let's see which value it has... >>> data = test_device.read_attribute("long_scalar") >>> # ...PyTango provides a shortcut to do the same: >>> data = test_device["long_scalar"] >>> # the result of reading an attribute is a DeviceAttribute python object. >>> # It has a member called "value" which contains the value of the attribute >>> data.value 136 >>> # Check the complete DeviceAttribute members: >>> print(data) DeviceAttribute[ data_format = SCALAR dim_x = 1 dim_y = 0 has_failed = False is_empty = False name = 'long_scalar' nb_read = 1 nb_written = 1 quality = ATTR_VALID r_dimension = AttributeDimension(dim_x = 1, dim_y = 0) time = TimeVal(tv_nsec = 0, tv_sec = 1399450183, tv_usec = 323990) type = DevLong value = 136 w_dim_x = 1 w_dim_y = 0 w_dimension = AttributeDimension(dim_x = 1, dim_y = 0) w_value = 0] >>> # PyTango provides a handy pythonic shortcut to read the attribute value: >>> test_device.long_scalar 136 >>> # Setting an attribute value is equally easy: >>> test_device.write_attribute("long_scalar", 8776) >>> # ... and a handy shortcut to do the same exists as well: >>> test_device.long_scalar = 8776 >>> # TangoTest has a command called "DevDouble" which receives a number >>> # as parameter and returns the same number as a result. Let's >>> # execute this command: >>> test_device.command_inout("DevDouble", 45.67) 45.67 >>> # PyTango provides a handy shortcut: it exports commands as device methods: >>> test_device.DevDouble(45.67) 45.67 >>> # Introspection: check the list of attributes: >>> test_device.get_attribute_list() ['ampli', 'boolean_scalar', 'double_scalar', '...', 'State', 'Status'] >>> This is just the tip of the iceberg. Check the :class:`~tango.DeviceProxy` for the complete API. PyTango used to come with an integrated IPython_ based console called :ref:`itango`, now moved to a separate project. It provides helpers to simplify console usage. You can use this console instead of the traditional python console. Be aware, though, that many of the *tricks* you can do in an :ref:`itango` console cannot be done in a python program. Server ------ Since PyTango 8.1 it has become much easier to program a Tango device server. PyTango provides some helpers that allow developers to simplify the programming of a Tango device server. Before creating a server you need to decide: 1. The name of the device server (example: `PowerSupplyDS`). This will be the mandatory name of your python file. 2. The Tango Class name of your device (example: `PowerSupply`). In our example we will use the same name as the python class name. 3. the list of attributes of the device, their data type, access (read-only vs read-write), data_format (scalar, 1D, 2D) 4. the list of commands, their parameters and their result In our example we will write a fake power supply device server. The server will be called `PowerSupplyDS`. There will be a class called `PowerSupply` which will have attributes: * *voltage* (scalar, read-only, numeric) * *current* (scalar, read_write, numeric, expert mode) * *noise* (2D, read-only, numeric) pipes: * *info* (read-only) commands: * *TurnOn* (argument: None, result: None) * *TurnOff* (argument: None, result: None) * *Ramp* (param: scalar, numeric; result: bool) properties: * *host* (string representing the host name of the actual power supply) * *port* (port number in the host with default value = 9788) Here is the code for the :file:`PowerSupplyDS.py` .. literalinclude:: _static/PowerSupplyDS.py :linenos: Check the :ref:`high level server API ` for the complete reference API. The :ref:`write a server how to ` can help as well. Before running this brand new server we need to register it in the Tango system. You can do it with Jive (`Jive->Edit->Create server`): .. image:: _static/jive_powersupply.png ... or in a python script:: >>> import tango >>> dev_info = tango.DbDevInfo() >>> dev_info.server = "PowerSupplyDS/test" >>> dev_info._class = "PowerSupply" >>> dev_info.name = "test/power_supply/1" >>> db = tango.Database() >>> db.add_device(dev_info) After, you can run the server on a console with:: $ python PowerSupplyDS.py test Ready to accept request Now you can access it from a python console:: >>> import tango >>> power_supply = tango.DeviceProxy("test/power/supply/1") >>> power_supply.state() STANDBY >>> power_supply.current = 2.3 >>> power_supply.current 2.3 >>> power_supply.PowerOn() >>> power_supply.Ramp(2.1) True >>> power_supply.state() ON Next steps: Check out the :ref:`pytango-api`. pytango-9.2.2/doc/revision.rst000066400000000000000000002672201316324373100163560ustar00rootroot00000000000000.. _pytango-history-changes: ================== History of changes ================== :Contributers: T\. Coutinho :Last Update: |today| .. _pytango-revisions: Document revisions ------------------- +----------+----------------------------------------------------------------------------------+-----------------------------------------------------+--------------------------------+ | Date | Revision | Description | Author | +==========+==================================================================================+=====================================================+================================+ | 18/07/03 | 1.0 | Initial Version | M\. Ounsy | +----------+----------------------------------------------------------------------------------+-----------------------------------------------------+--------------------------------+ | 06/10/03 | 2.0 | Extension of the "Getting Started" paragraph | A\. Buteau/M\. Ounsy | +----------+----------------------------------------------------------------------------------+-----------------------------------------------------+--------------------------------+ | 14/10/03 | 3.0 | Added Exception Handling paragraph | M\. Ounsy | +----------+----------------------------------------------------------------------------------+-----------------------------------------------------+--------------------------------+ | 13/06/05 | 4.0 | Ported to Latex, added events, AttributeProxy | V\. Forchì | | | | and ApiUtil | | +----------+----------------------------------------------------------------------------------+-----------------------------------------------------+--------------------------------+ | | | fixed bug with python 2.5 and and state events | | | 13/06/05 | 4.1 | new Database constructor | V\. Forchì | +----------+----------------------------------------------------------------------------------+-----------------------------------------------------+--------------------------------+ | 15/01/06 | 5.0 | Added Device Server classes | E\.Taurel | +----------+----------------------------------------------------------------------------------+-----------------------------------------------------+--------------------------------+ | 15/03/07 | 6.0 | Added AttrInfoEx, AttributeConfig events, 64bits, | T\. Coutinho | | | | write_attribute | | +----------+----------------------------------------------------------------------------------+-----------------------------------------------------+--------------------------------+ | 21/03/07 | 6.1 | Added groups | T\. Coutinho | +----------+----------------------------------------------------------------------------------+-----------------------------------------------------+--------------------------------+ | 15/06/07 | `6.2 `_ | Added dynamic attributes doc | E\. Taurel | +----------+----------------------------------------------------------------------------------+-----------------------------------------------------+--------------------------------+ | 06/05/08 | `7.0 `_ | Update to Tango 6.1. Added DB methods, version info | T\. Coutinho | +----------+----------------------------------------------------------------------------------+-----------------------------------------------------+--------------------------------+ | 10/07/09 | `8.0 `_ | Update to Tango 7. Major refactoring. Migrated doc | T\. Coutinho/R\. Suñe | +----------+----------------------------------------------------------------------------------+-----------------------------------------------------+--------------------------------+ | 24/07/09 | `8.1 `_ | Added migration info, added missing API doc | T\. Coutinho/R\. Suñe | +----------+----------------------------------------------------------------------------------+-----------------------------------------------------+--------------------------------+ | 21/09/09 | `8.2 `_ | Added migration info, release of 7.0.0beta2 | T\. Coutinho/R\. Suñe | +----------+----------------------------------------------------------------------------------+-----------------------------------------------------+--------------------------------+ | 12/11/09 | `8.3 `_ | Update to Tango 7.1. | T\. Coutinho/R\. Suñe | +----------+----------------------------------------------------------------------------------+-----------------------------------------------------+--------------------------------+ | ??/12/09 | `8.4 `_ | Update to PyTango 7.1.0 rc1 | T\. Coutinho/R\. Suñe | +----------+----------------------------------------------------------------------------------+-----------------------------------------------------+--------------------------------+ | 19/02/10 | `8.5 `_ | Update to PyTango 7.1.1 | T\. Coutinho/R\. Suñe | +----------+----------------------------------------------------------------------------------+-----------------------------------------------------+--------------------------------+ | 06/08/10 | `8.6 `_ | Update to PyTango 7.1.2 | T\. Coutinho | +----------+----------------------------------------------------------------------------------+-----------------------------------------------------+--------------------------------+ | 05/11/10 | `8.7 `_ | Update to PyTango 7.1.3 | T\. Coutinho | +----------+----------------------------------------------------------------------------------+-----------------------------------------------------+--------------------------------+ | 08/04/11 | `8.8 `_ | Update to PyTango 7.1.4 | T\. Coutinho | +----------+----------------------------------------------------------------------------------+-----------------------------------------------------+--------------------------------+ | 13/04/11 | `8.9 `_ | Update to PyTango 7.1.5 | T\. Coutinho | +----------+----------------------------------------------------------------------------------+-----------------------------------------------------+--------------------------------+ | 14/04/11 | `8.10 `_ | Update to PyTango 7.1.6 | T\. Coutinho | +----------+----------------------------------------------------------------------------------+-----------------------------------------------------+--------------------------------+ | 15/04/11 | `8.11 `_ | Update to PyTango 7.2.0 | T\. Coutinho | +----------+----------------------------------------------------------------------------------+-----------------------------------------------------+--------------------------------+ | 12/12/11 | `8.12 `_ | Update to PyTango 7.2.2 | T\. Coutinho | +----------+----------------------------------------------------------------------------------+-----------------------------------------------------+--------------------------------+ | 24/04/12 | `8.13 `_ | Update to PyTango 7.2.3 | T\. Coutinho | +----------+----------------------------------------------------------------------------------+-----------------------------------------------------+--------------------------------+ | 21/09/12 | `8.14 `_ | Update to PyTango 8.0.0 | T\. Coutinho | +----------+----------------------------------------------------------------------------------+-----------------------------------------------------+--------------------------------+ | 10/10/12 | `8.15 `_ | Update to PyTango 8.0.2 | T\. Coutinho | +----------+----------------------------------------------------------------------------------+-----------------------------------------------------+--------------------------------+ | 20/05/13 | `8.16 `_ | Update to PyTango 8.0.3 | T\. Coutinho | +----------+----------------------------------------------------------------------------------+-----------------------------------------------------+--------------------------------+ | 28/08/13 | `8.13 `_ | Update to PyTango 7.2.4 | T\. Coutinho | +----------+----------------------------------------------------------------------------------+-----------------------------------------------------+--------------------------------+ | 27/11/13 | `8.18 `_ | Update to PyTango 8.1.1 | T\. Coutinho | +----------+----------------------------------------------------------------------------------+-----------------------------------------------------+--------------------------------+ | 16/05/14 | `8.19 `_ | Update to PyTango 8.1.2 | T\. Coutinho | +----------+----------------------------------------------------------------------------------+-----------------------------------------------------+--------------------------------+ | 30/09/14 | `8.20 `_ | Update to PyTango 8.1.4 | T\. Coutinho | +----------+----------------------------------------------------------------------------------+-----------------------------------------------------+--------------------------------+ | 01/10/14 | `8.21 `_ | Update to PyTango 8.1.5 | T\. Coutinho | +----------+----------------------------------------------------------------------------------+-----------------------------------------------------+--------------------------------+ | 05/02/15 | `8.22 `_ | Update to PyTango 8.1.6 | T\. Coutinho | +----------+----------------------------------------------------------------------------------+-----------------------------------------------------+--------------------------------+ | 03/02/16 | `8.23 `_ | Update to PyTango 8.1.8 | T\. Coutinho | +----------+----------------------------------------------------------------------------------+-----------------------------------------------------+--------------------------------+ | 12/08/16 | 8.24 | Update to PyTango 8.1.9 | V\. Michel | +----------+----------------------------------------------------------------------------------+-----------------------------------------------------+--------------------------------+ | 26/02/16 | `9.2.0a `_ | Update to PyTango 9.2.0a | T\. Coutinho | +----------+----------------------------------------------------------------------------------+-----------------------------------------------------+--------------------------------+ | 15/08/16 | `9.2.0 `_ | 9.2.0 Release | V\. Michel | +----------+----------------------------------------------------------------------------------+-----------------------------------------------------+--------------------------------+ | 23/01/17 | `9.2.1 `_ | 9.2.1 Release | V\. Michel | +----------+----------------------------------------------------------------------------------+-----------------------------------------------------+--------------------------------+ | 27/09/17 | `9.2.2 `_ | 9.2.2 Release | G\. Cuni/V\. Michel/J\. Moldes | +----------+----------------------------------------------------------------------------------+-----------------------------------------------------+--------------------------------+ .. _pytango-version-history: Version history --------------- +----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Version | Changes | +==========+=======================================================================================================================================================================+ | 9.2.2 | 9.2.2 release. | | | | | | Features: | | | - `Pull Request #104: Pipe Events `_ | | | - `Pull Request #106: Implement pipe write (client and server, issue #9) `_ | | | - `Pull Request #122: Dynamic commands `_ | | | - `Pull Request #124: Add forward attribute `_ | | | - `Pull Request #129: Implement mandatory property (issue #30) `_ | | | | | | Changes: | | | - `Pull Request #109: Device Interface Change Events `_ | | | - `Pull Request #113: Adding asyncio green mode documentation and a how-to on contributing `_ | | | - `Pull Request #114: Added PEP8-ified files in tango module. `_ | | | - `Pull Request #115: Commands polling tests (client and server) `_ | | | - `Pull Request #116: Attribute polling tests (client and server) `_ | | | - `Pull Request #117: Use official tango-controls conda channel `_ | | | - `Pull Request #125: Forward attribute example `_ | | | - `Pull Request #134: Linting pytango (with pylint + flake8) `_ | | | - `Pull Request #137: Codacy badge in README and code quality policy in How to Contribute `_ | | | - `Pull Request #143: Added missing PipeEventData & DevIntrChangeEventData `_ | | | | | | Bug fixes: | | | - `Pull Request #85 (issue #84): Fix Gevent ThreadPool exceptions `_ | | | - `Pull Request #94 (issue #93): Fix issues in setup file (GCC-7 build) `_ | | | - `Pull Request #96: Filter badges from the long description `_ | | | - `Pull Request #97: Fix/linker options `_ | | | - `Pull Request #98: Refactor green mode for client and server APIs `_ | | | - `Pull Request #101 (issue #100) check for None and return null string `_ | | | - `Pull Request #102: Update server tests `_ | | | - `Pull Request #103: Cache build objects to optimize travis builds `_ | | | - `Pull Request #112 (issue #111): Use _DeviceClass as tango device class constructor `_ | | | - `Pull Request #128 (issue #127): Set default worker in server.py `_ | | | - `Pull Request #135: Better exception handling in server.run and test context (issue #131) `_ | | | - `Pull Request #142 (issue #142): Added missing PipeEventData & DevIntrChangeEventData `_ | | | - `Pull Request #148 (issue #144): Expose utils helpers `_ | | | - `Pull Request #149: Fix return value of proxy.subscribe_event `_ | | | - `Pull Request #158 (issue #155): Fix timestamp and casing in utils.EventCallback `_ | | | | +----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | 9.2.1 | 9.2.1 release. | | | | | | Features: | | | - `Pull Requests #70: Add test_context and test_utils modules, used for pytango unit-testing `_ | | | | | | Changes: | | | - `Issue #51: Refactor platform specific code in setup file `_ | | | - `Issue #67: Comply with PEP 440 for pre-releases `_ | | | - `Pull Request #70: Add unit-testing for the server API `_ | | | - `Pull Request #70: Configure Travis CI for continuous integration `_ | | | - `Pull Request #76: Add unit-testing for the client API `_ | | | - `Pull Request #78: Update the python version classifiers `_ | | | - `Pull Request #80: Move tango object server to its own module `_ | | | - `Pull Request #90: The metaclass definition for tango devices is no longer mandatory `_ | | | | | | Bug fixes: | | | - `Issue #24: Fix dev_status dangling pointer bug `_ | | | - `Issue #57: Fix dev_state/status to be gevent safe `_ | | | - `Issue #58: Server gevent mode internal call hangs `_ | | | - `Pull Request #62: Several fixes in tango.databaseds `_ | | | - `Pull Request #63: Follow up on issue #21 (Fix Group.get_device method) `_ | | | - `Issue #64: Fix AttributeProxy.__dev_proxy to be initialized with python internals `_ | | | - `Issue #74: Fix hanging with an asynchronous tango server fails to start `_ | | | - `Pull Request #81: Fix DeviceImpl documentation `_ | | | - `Issue #82: Fix attribute completion for device proxies with IPython >= 4 `_ | | | - `Issue #84: Fix gevent threadpool exceptions `_ | | | | +----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | 9.2.0 | 9.2.0 release. | | | | | | Features: | | | | | | - `Issue #37: Add display_level and polling_period as optional arguments to command decorator `_ | | | | | | Bug fixes: | | | | | | - Fix cache problem when using `DeviceProxy` through an `AttributeProxy` | | | - Fix compilation on several platforms | | | - `Issue #19: Defining new members in DeviceProxy has side effects `_ | | | - Fixed bug in `beacon.add_device` | | | - Fix for `get_device_list` if server_name is '*' | | | - Fix `get_device_attribute_property2` if `prop_attr` is not `None` | | | - Accept `StdStringVector` in `put_device_property` | | | - Map 'int' to DevLong64 and 'uint' to DevULong64 | | | - `Issue #22: Fix push_data_ready_event() deadlock `_ | | | - `Issue #28: Fix compilation error for constants.cpp `_ | | | - `Issue #21: Fix Group.get_device method `_ | | | - `Issue #33: Fix internal server documentation `_ | | | | | | Changes: | | | - Move ITango to another project | | | - Use `setuptools` instead of `distutils` | | | - Add `six` as a requirement | | | - Refactor directory structure | | | - Rename `PyTango` module to `tango` (`import PyTango` still works for backward compatibility) | | | - Add a ReST readme for GitHub and PyPI | | | | | | ITango changes (moved to another project): | | | - Fix itango event logger for python 3 | | | - Avoid deprecation warning with IPython 4.x | | | - Use entry points instead of scripts | | | | +----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | 9.2.0a | 9.2 alpha release. Missing: | | | | | | - writtable pipes (client and server) | | | - dynamic commands (server) | | | - device interface change event (client and server) | | | - pipe event (client and server) | | | | | | Bug fixes: | | | | | | - `776: [pytango][8.1.8] SyntaxError: invalid syntax `_ | +----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | 8.1.9 | Features: | | | | | | - `PR #2: asyncio support for both client and server API `_ | | | - `PR #6: Expose AutoTangoMonitor and AutoTangoAllowThreads `_ | | | | | | Bug fixes: | | | | | | - `PR #31: Get -l flags from pkg-config `_ | | | - `PR #15: Rename itango script to itango3 for python3 `_ | | | - `PR #14: Avoid deprecation warning with IPython 4.x `_ | +----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | 8.1.8 | Features: | | | | | | - `PR #3: Add a run_server class method to Device `_ | | | - `PR #4: Add device inheritance `_ | | | - `110: device property with auto update in database `_ | | | | | | Bug fixes: | | | | | | - `690: Description attribute property `_ | | | - `700: [pytango] useless files in the source distribution `_ | | | - `701: Memory leak in command with list argument `_ | | | - `704: Assertion failure when calling command with string array input type `_ | | | - `705: Support boost_python lib name on Gentoo `_ | | | - `714: Memory leak in PyTango for direct server command calls `_ | | | - `718: OverflowErrors with float types in 8.1.6 `_ | | | - `724: PyTango DeviceProxy.command_inout() memory leaks `_ | | | - `736: pytango FTBFS with python 3.4 `_ | | | - `747: PyTango event callback in gevent mode gets called in non main thread `_ | +----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | 8.1.6 | Bug fixes: | | | | | | - `698: PyTango.Util discrepancy `_ | | | - `697: PyTango.server.run does not accept old Device style classes `_ | +----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | 8.1.5 | Bug fixes: | | | | | | - `687: [pytango] 8.1.4 unexpected files in the source package `_ | | | - `688: PyTango 8.1.4 new style server commands don't work `_ | +----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | 8.1.4 | Features: | | | | | | - `107: Nice to check Tango/PyTango version at runtime `_ | | | | | | Bug fixes: | | | | | | - `659: segmentation fault when unsubscribing from events `_ | | | - `664: problem while installing PyTango 8.1.1 with pip (using pip 1.4.1) `_ | | | - `678: [pytango] 8.1.2 unexpected files in the source package `_ | | | - `679: PyTango.server tries to import missing __builtin__ module on Python 3 `_ | | | - `680: Cannot import PyTango.server.run `_ | | | - `686: Device property substitution for a multi-device server `_ | +----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | 8.1.3 | *SKIPPED* | +----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | 8.1.2 | Features: | | | | | | - `98: PyTango.server.server_run needs additional post_init_callback parameter `_ | | | - `102: DevEncoded attribute should support a python buffer object `_ | | | - `103: Make creation of *EventData objects possible in PyTango `_ | | | | | | Bug fixes: | | | | | | - `641: python3 error handling issue `_ | | | - `648: PyTango unicode method parameters fail `_ | | | - `649: write_attribute of spectrum/image fails in PyTango without numpy `_ | | | - `650: [pytango] 8.1.1 not compatible with ipyton 1.2.0-rc1 `_ | | | - `651: PyTango segmentation fault when run a DS that use attr_data.py `_ | | | - `660: command_inout_asynch (polling mode) fails `_ | | | - `666: PyTango shutdown sometimes blocks. `_ | +----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | 8.1.1 | Features: | | | | | | - Implemented tango C++ 8.1 API | | | | | | Bug fixes: | | | | | | - `527: set_value() for ULong64 `_ | | | - `573: [pytango] python3 error with unregistered device `_ | | | - `611: URGENT fail to write attribute with PyTango 8.0.3 `_ | | | - `612: [pytango][8.0.3] failed to build from source on s390 `_ | | | - `615: Threading problem when setting a DevULong64 attribute `_ | | | - `622: PyTango broken when running on Ubuntu 13 `_ | | | - `626: attribute_history extraction can raised an exception `_ | | | - `628: Problem in installing PyTango 8.0.3 on Scientific Linux 6 `_ | | | - `635: Reading of ULong64 attributes does not work `_ | | | - `636: PyTango log messages are not filtered by level `_ | | | - `637: [pytango] segfault doing write_attribute on Group `_ | +----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | 8.1.0 | *SKIPPED* | +----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | 8.0.3 | Features: | | | - `88: Implement Util::server_set_event_loop method in python `_ | | | | | | Bug fixes: | | | | | | - `3576353: [pytango] segfault on 'RestartServer' `_ | | | - `3579062: [pytango] Attribute missing methods `_ | | | - `3586337: [pytango] Some DeviceClass methods are not python safe `_ | | | - `3598514: DeviceProxy.__setattr__ break python's descriptors `_ | | | - `3607779: [pytango] IPython 0.10 error `_ | | | - `598: Import DLL by PyTango failed on windows `_ | | | - `605: [pytango] use distutils.version module `_ | +----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | 8.0.2 | Bug fixes: | | | | | | - `3570970: [pytango] problem during the python3 building `_ | | | - `3570971: [pytango] itango does not work without qtconsole `_ | | | - `3570972: [pytango] warning/error when building 8.0.0 `_ | | | - `3570975: [pytango] problem during use of python3 version `_ | | | - `3574099: [pytango] compile error with gcc < 4.5 `_ | +----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | 8.0.1 | *SKIPPED* | +----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | 8.0.0 | Features: | | | | | | - Implemented tango C++ 8.0 API | | | - Python 3k compatible | | | | | | Bug fixes: | | | | | | - `3023857: DevEncoded write attribute not supported `_ | | | - `3521545: [pytango] problem with tango profile `_ | | | - `3530535: PyTango group writting fails `_ | | | - `3564959: EncodedAttribute.encode_xxx() methods don't accept bytearray `_ | +----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | 7.2.4 | Bug fixes: | | | | | | - `551: [pytango] Some DeviceClass methods are not python safe `_ | +----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | 7.2.3 | Features: | | | | | | - `3495607: DeviceClass.device_name_factory is missing `_ | | | | | | Bug fixes: | | | | | | - `3103588: documentation of PyTango.Attribute.Group `_ | | | - `3458336: Problem with pytango 7.2.2 `_ | | | - `3463377: PyTango memory leak in read encoded attribute `_ | | | - `3487930: [pytango] wrong python dependency `_ | | | - `3511509: Attribute.set_value_date_quality for encoded does not work `_ | | | - `3514457: [pytango] TANGO_HOST multi-host support `_ | | | - `3520739: command_history(...) in PyTango `_ | +----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | 7.2.2 | Features: | | | | | | - `3305251: DS dynamic attributes discards some Attr properties `_ | | | - `3365792: DeviceProxy. could be documented `_ | | | - `3386079: add support for ipython 0.11 `_ | | | - `3437654: throw python exception as tango exception `_ | | | - `3447477: spock profile installation `_ | | | | | | Bug fixes: | | | | | | - `3372371: write attribute of DevEncoded doesn't work `_ | | | - `3374026: [pytango] pyflakes warning `_ | | | - `3404771: PyTango.MultiAttribute.get_attribute_list missing `_ | | | - `3405580: PyTango.MultiClassAttribute missing `_ | +----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | 7.2.1 | *SKIPPED* | +----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | 7.2.0 | Features: | | | | | | - `3286678: Add missing EncodedAttribute JPEG methods `_ | +----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | 7.1.6 | Bug fixes: | | | | | | - 7.1.5 distribution is missing some files | +----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | 7.1.5 | Bug fixes: | | | | | | - `3284174: 7.1.4 does not build with gcc 4.5 and tango 7.2.6 `_ | | | - `3284265: [pytango][7.1.4] a few files without licence and copyright `_ | | | - `3284318: copyleft vs copyright `_ | | | - `3284434: [pytango][doc] few ERROR during the doc generation `_ | | | - `3284435: [pytango][doc] few warning during the doc generation `_ | | | - `3284440: [pytango][spock] the profile can't be installed `_ | | | - `3285185: PyTango Device Server does not load Class Properties values `_ | | | - `3286055: PyTango 7.1.x DS using Tango C++ 7.2.x seg faults on exit `_ | +----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | 7.1.4 | Features: | | | | | | - `3274309: Generic Callback for events `_ | | | | | | Bug fixes: | | | | | | - `3011775: Seg Faults due to removed dynamic attributes `_ | | | - `3105169: PyTango 7.1.3 does not compile with Tango 7.2.X `_ | | | - `3107243: spock profile does not work with python 2.5 `_ | | | - `3124427: PyTango.WAttribute.set_max_value() changes min value `_ | | | - `3170399: Missing documentation about is__allowed method `_ | | | - `3189082: Missing get_properties() for Attribute class `_ | | | - `3196068: delete_device() not called after server_admin.Kill() `_ | | | - `3257286: Binding crashes when reading a WRITE string attribute `_ | | | - `3267628: DP.read_attribute(, extract=List/tuple) write value is wrong `_ | | | - `3274262: Database.is_multi_tango_host missing `_ | | | - `3274319: EncodedAttribute is missing in PyTango (<= 7.1.3) `_ | | | - `3277269: read_attribute(DevEncoded) is not numpy as expected `_ | | | - `3278946: DeviceAttribute copy constructor is not working `_ | | | | | | Documentation: | | | | | | - Added :ref:`utilities` chapter | | | - Added :ref:`encoded` chapter | | | - Improved :ref:`server` chapter | +----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | 7.1.3 | Features: | | | | | | - tango logging with print statement | | | - tango logging with decorators | | | - from sourceforge: | | | - `3060380: ApiUtil should be exported to PyTango `_ | | | | | | Bug fixes: | | | | | | - added licence header to all source code files | | | - spock didn't work without TANGO_HOST env. variable (it didn't recognize tangorc) | | | - spock should give a proper message if it tries to be initialized outside ipython | | | | | | - `3048798: licence issue GPL != LGPL `_ | | | - `3073378: DeviceImpl.signal_handler raising exception crashes DS `_ | | | - `3088031: Python DS unable to read DevVarBooleanArray property `_ | | | - `3102776: PyTango 7.1.2 does not work with python 2.4 & boost 1.33.0 `_ | | | - `3102778: Fix compilation warnings in linux `_ | +----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | 7.1.2 | Features: | | | | | | - `2995964: Dynamic device creation `_ | | | - `3010399: The DeviceClass.get_device_list that exists in C++ is missing `_ | | | - `3023686: Missing DeviceProxy. `_ | | | - `3025396: DeviceImpl is missing some CORBA methods `_ | | | - `3032005: IPython extension for PyTango `_ | | | - `3033476: Make client objects pickable `_ | | | - `3039902: PyTango.Util.add_class would be useful `_ | | | | | | Bug fixes: | | | | | | - `2975940: DS command with DevVarCharArray return type fails `_ | | | - `3000467: DeviceProxy.unlock is LOCKING instead of unlocking! `_ | | | - `3010395: Util.get_device_* methods don't work `_ | | | - `3010425: Database.dev_name does not work `_ | | | - `3016949: command_inout_asynch callback does not work `_ | | | - `3020300: PyTango does not compile with gcc 4.1.x `_ | | | - `3030399: Database put(delete)_attribute_alias generates segfault `_ | +----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | 7.1.1 | Features: | | | | | | - Improved setup script | | | - Interfaced with PyPI | | | - Cleaned build script warnings due to unclean python C++ macro definitions | | | - `2985993: PyTango numpy command support `_ | | | - `2971217: PyTango.GroupAttrReplyList slicing `_ | | | | | | Bug fixes: | | | | | | - `2983299: Database.put_property() deletes the property `_ | | | - `2953689: can not write_attribute scalar/spectrum/image `_ | | | - `2953030: PyTango doc installation `_ | +----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | 7.1.0 | Features: | | | | | | - `2908176: read_*, write_* and is_*_allowed() methods can now be defined `_ | | | - `2941036: TimeVal conversion to time and datetime `_ | | | - added str representation on Attr, Attribute, DeviceImpl and DeviceClass | | | | | | Bug fixes: | | | | | | - `2903755: get_device_properties() bug reading DevString properties `_ | | | - `2909927: PyTango.Group.read_attribute() return values `_ | | | - `2914194: DevEncoded does not work `_ | | | - `2916397: PyTango.DeviceAttribute copy constructor does not work `_ | | | - `2936173: PyTango.Group.read_attributes() fails `_ | | | - `2949099: Missing PyTango.Except.print_error_stack `_ | +----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | 7.1.0rc1 | Features: | | | | | | - v = image_attribute.get_write_value() returns square sequences (arrays of | | | arrays, or numpy objects) now instead of flat lists. Also for spectrum | | | attributes a numpy is returned by default now instead. | | | - image_attribute.set_value(v) accepts numpy arrays now or square sequences | | | instead of just flat lists. So, dim_x and dim_y are useless now. Also the | | | numpy path is faster. | | | - new enum AttrSerialModel | | | - Attribute new methods: set(get)_attr_serial_model, set_change_event, | | | set_archive_event, is_change_event, is_check_change_event, | | | is_archive_criteria, is_check_archive_criteria, remove_configuration | | | - added support for numpy scalars in tango operations like write_attribute | | | (ex: now a DEV_LONG attribute can receive a numpy.int32 argument in a | | | write_attribute method call) | | | | | | Bug fixes: | | | | | | - DeviceImpl.set_value for scalar attributes | | | - DeviceImpl.push_***_event | | | - server commands with DevVar***StringArray as parameter or as return type | | | - in windows,a bug in PyTango.Util prevented servers from starting up | | | - DeviceImpl.get_device_properties for string properties assigns only first | | | character of string to object member instead of entire string | | | - added missing methods to Util | | | - exported SubDevDiag class | | | - error in read/events of attributes of type DevBoolean READ_WRITE | | | - error in automatic unsubscribe events of DeviceProxy when the object | | | disapears (happens only on some compilers with some optimization flags) | | | - fix possible bug when comparing attribute names in DeviceProxy | | | - pretty print of DevFailed -> fix deprecation warning in python 2.6 | | | - device class properties where not properly fetched when there is no | | | property value defined | | | - memory leak when converting DevFailed exceptions from C++ to python | | | - python device server file without extension does not start | | | | | | Documentation: | | | | | | - Improved FAQ | | | - Improved compilation chapter | | | - Improved migration information | +----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+ pytango-9.2.2/doc/server_api/000077500000000000000000000000001316324373100161145ustar00rootroot00000000000000pytango-9.2.2/doc/server_api/attribute.rst000066400000000000000000000006621316324373100206550ustar00rootroot00000000000000Attribute classes ================= .. currentmodule:: tango Attr ---- .. autoclass:: tango.Attr :members: Attribute --------- .. autoclass:: tango.Attribute :members: WAttribute ---------- .. autoclass:: tango.WAttribute :members: MultiAttribute -------------- .. autoclass:: tango.MultiAttribute :members: UserDefaultAttrProp ------------------- .. autoclass:: tango.UserDefaultAttrProp :members: pytango-9.2.2/doc/server_api/device.rst000066400000000000000000000002151316324373100201030ustar00rootroot00000000000000Device ====== .. currentmodule:: tango DeviceImpl ---------- .. autoclass:: tango.LatestDeviceImpl :members: :inherited-members: pytango-9.2.2/doc/server_api/device_class.rst000066400000000000000000000001421316324373100212670ustar00rootroot00000000000000DeviceClass =========== .. currentmodule:: tango .. autoclass:: tango.DeviceClass :members: pytango-9.2.2/doc/server_api/index.rst000066400000000000000000000002361316324373100177560ustar00rootroot00000000000000.. currentmodule:: tango Server API ---------- .. toctree:: :maxdepth: 2 server device device_class logging attribute util pytango-9.2.2/doc/server_api/logging.rst000066400000000000000000000006431316324373100202770ustar00rootroot00000000000000Logging decorators ================== .. currentmodule:: tango LogIt ----- .. autoclass:: tango.LogIt :members: DebugIt ------- .. autoclass:: tango.DebugIt :members: InfoIt ------ .. autoclass:: tango.InfoIt :members: WarnIt ------- .. autoclass:: tango.WarnIt :members: ErrorIt ------- .. autoclass:: tango.ErrorIt :members: FatalIt ------- .. autoclass:: tango.FatalIt :members: pytango-9.2.2/doc/server_api/server.rst000066400000000000000000000246001316324373100201560ustar00rootroot00000000000000 .. currentmodule:: tango.server .. _pytango-hlapi: High level server API ===================== .. automodule:: tango.server .. hlist:: * :class:`~tango.server.Device` * :class:`~tango.server.attribute` * :class:`~tango.server.command` * :class:`~tango.server.pipe` * :class:`~tango.server.device_property` * :class:`~tango.server.class_property` * :func:`~tango.server.run` * :func:`~tango.server.server_run` This module provides a high level device server API. It implements :ref:`TEP1 `. It exposes an easier API for developing a Tango device server. Here is a simple example on how to write a *Clock* device server using the high level API:: import time from tango.server import run from tango.server import Device from tango.server import attribute, command class Clock(Device): time = attribute() def read_time(self): return time.time() @command(din_type=str, dout_type=str) def strftime(self, format): return time.strftime(format) if __name__ == "__main__": run((Clock,)) Here is a more complete example on how to write a *PowerSupply* device server using the high level API. The example contains: #. a read-only double scalar attribute called *voltage* #. a read/write double scalar expert attribute *current* #. a read-only double image attribute called *noise* #. a *ramp* command #. a *host* device property #. a *port* class property .. code-block:: python :linenos: from time import time from numpy.random import random_sample from tango import AttrQuality, AttrWriteType, DispLevel from tango.server import Device, attribute, command from tango.server import class_property, device_property class PowerSupply(Device): voltage = attribute() current = attribute(label="Current", dtype=float, display_level=DispLevel.EXPERT, access=AttrWriteType.READ_WRITE, unit="A", format="8.4f", min_value=0.0, max_value=8.5, min_alarm=0.1, max_alarm=8.4, min_warning=0.5, max_warning=8.0, fget="get_current", fset="set_current", doc="the power supply current") noise = attribute(label="Noise", dtype=((float,),), max_dim_x=1024, max_dim_y=1024, fget="get_noise") host = device_property(dtype=str) port = class_property(dtype=int, default_value=9788) def read_voltage(self): self.info_stream("get voltage(%s, %d)" % (self.host, self.port)) return 10.0 def get_current(self): return 2.3456, time(), AttrQuality.ATTR_WARNING def set_current(self, current): print("Current set to %f" % current) def get_noise(self): return random_sample((1024, 1024)) @command(dtype_in=float) def ramp(self, value): print("Ramping up...") if __name__ == "__main__": PowerSupply.run_server() *Pretty cool, uh?* .. _pytango-hlapi-datatypes: .. rubric:: Data types When declaring attributes, properties or commands, one of the most important information is the data type. It is given by the keyword argument *dtype*. In order to provide a more *pythonic* interface, this argument is not restricted to the :obj:`~tango.CmdArgType` options. For example, to define a *SCALAR* :obj:`~tango.CmdArgType.DevLong` attribute you have several possibilities: #. :obj:`int` #. 'int' #. 'int32' #. 'integer' #. :obj:`tango.CmdArgType.DevLong` #. 'DevLong' #. :obj:`numpy.int32` To define a *SPECTRUM* attribute simply wrap the scalar data type in any python sequence: * using a *tuple*: ``(:obj:`int`,)`` or * using a *list*: ``[:obj:`int`]`` or * any other sequence type To define an *IMAGE* attribute simply wrap the scalar data type in any python sequence of sequences: * using a *tuple*: ``((:obj:`int`,),)`` or * using a *list*: ``[[:obj:`int`]]`` or * any other sequence type Below is the complete table of equivalences. ======================================== ======================================== dtype argument converts to tango type ======================================== ======================================== ``None`` ``DevVoid`` ``'None'`` ``DevVoid`` ``DevVoid`` ``DevVoid`` ``'DevVoid'`` ``DevVoid`` ``DevState`` ``DevState`` ``'DevState'`` ``DevState`` :py:obj:`bool` ``DevBoolean`` ``'bool'`` ``DevBoolean`` ``'boolean'`` ``DevBoolean`` ``DevBoolean`` ``DevBoolean`` ``'DevBoolean'`` ``DevBoolean`` :py:obj:`numpy.bool_` ``DevBoolean`` ``'char'`` ``DevUChar`` ``'chr'`` ``DevUChar`` ``'byte'`` ``DevUChar`` ``chr`` ``DevUChar`` ``DevUChar`` ``DevUChar`` ``'DevUChar'`` ``DevUChar`` :py:obj:`numpy.uint8` ``DevUChar`` ``'int16'`` ``DevShort`` ``DevShort`` ``DevShort`` ``'DevShort'`` ``DevShort`` :py:obj:`numpy.int16` ``DevShort`` ``'uint16'`` ``DevUShort`` ``DevUShort`` ``DevUShort`` ``'DevUShort'`` ``DevUShort`` :py:obj:`numpy.uint16` ``DevUShort`` :py:obj:`int` ``DevLong`` ``'int'`` ``DevLong`` ``'int32'`` ``DevLong`` ``DevLong`` ``DevLong`` ``'DevLong'`` ``DevLong`` :py:obj:`numpy.int32` ``DevLong`` ``'uint'`` ``DevULong`` ``'uint32'`` ``DevULong`` ``DevULong`` ``DevULong`` ``'DevULong'`` ``DevULong`` :py:obj:`numpy.uint32` ``DevULong`` ``'int64'`` ``DevLong64`` ``DevLong64`` ``DevLong64`` ``'DevLong64'`` ``DevLong64`` :py:obj:`numpy.int64` ``DevLong64`` ``'uint64'`` ``DevULong64`` ``DevULong64`` ``DevULong64`` ``'DevULong64'`` ``DevULong64`` :py:obj:`numpy.uint64` ``DevULong64`` ``DevInt`` ``DevInt`` ``'DevInt'`` ``DevInt`` ``'float32'`` ``DevFloat`` ``DevFloat`` ``DevFloat`` ``'DevFloat'`` ``DevFloat`` :py:obj:`numpy.float32` ``DevFloat`` :py:obj:`float` ``DevDouble`` ``'double'`` ``DevDouble`` ``'float'`` ``DevDouble`` ``'float64'`` ``DevDouble`` ``DevDouble`` ``DevDouble`` ``'DevDouble'`` ``DevDouble`` :py:obj:`numpy.float64` ``DevDouble`` :py:obj:`str` ``DevString`` ``'str'`` ``DevString`` ``'string'`` ``DevString`` ``'text'`` ``DevString`` ``DevString`` ``DevString`` ``'DevString'`` ``DevString`` :py:obj:`bytearray` ``DevEncoded`` ``'bytearray'`` ``DevEncoded`` ``'bytes'`` ``DevEncoded`` ``DevEncoded`` ``DevEncoded`` ``'DevEncoded'`` ``DevEncoded`` ``DevVarBooleanArray`` ``DevVarBooleanArray`` ``'DevVarBooleanArray'`` ``DevVarBooleanArray`` ``DevVarCharArray`` ``DevVarCharArray`` ``'DevVarCharArray'`` ``DevVarCharArray`` ``DevVarShortArray`` ``DevVarShortArray`` ``'DevVarShortArray'`` ``DevVarShortArray`` ``DevVarLongArray`` ``DevVarLongArray`` ``'DevVarLongArray'`` ``DevVarLongArray`` ``DevVarLong64Array`` ``DevVarLong64Array`` ``'DevVarLong64Array'`` ``DevVarLong64Array`` ``DevVarULong64Array`` ``DevVarULong64Array`` ``'DevVarULong64Array'`` ``DevVarULong64Array`` ``DevVarFloatArray`` ``DevVarFloatArray`` ``'DevVarFloatArray'`` ``DevVarFloatArray`` ``DevVarDoubleArray`` ``DevVarDoubleArray`` ``'DevVarDoubleArray'`` ``DevVarDoubleArray`` ``DevVarUShortArray`` ``DevVarUShortArray`` ``'DevVarUShortArray'`` ``DevVarUShortArray`` ``DevVarULongArray`` ``DevVarULongArray`` ``'DevVarULongArray'`` ``DevVarULongArray`` ``DevVarStringArray`` ``DevVarStringArray`` ``'DevVarStringArray'`` ``DevVarStringArray`` ``DevVarLongStringArray`` ``DevVarLongStringArray`` ``'DevVarLongStringArray'`` ``DevVarLongStringArray`` ``DevVarDoubleStringArray`` ``DevVarDoubleStringArray`` ``'DevVarDoubleStringArray'`` ``DevVarDoubleStringArray`` ``DevPipeBlob`` ``DevPipeBlob`` ``'DevPipeBlob'`` ``DevPipeBlob`` ======================================== ======================================== .. autoclass:: Device :show-inheritance: :inherited-members: :members: .. autoclass:: attribute .. autofunction:: command .. autoclass:: pipe .. autoclass:: device_property .. autoclass:: class_property .. autofunction:: run .. autofunction:: server_run pytango-9.2.2/doc/server_api/util.rst000066400000000000000000000001451316324373100176230ustar00rootroot00000000000000Util ==== .. currentmodule:: tango .. autoclass:: tango.Util :members: :inherited-members: pytango-9.2.2/doc/start.rst000066400000000000000000000056771316324373100156630ustar00rootroot00000000000000.. highlight:: python :linenothreshold: 5 .. _getting-started: Getting started =============== Installing ---------- Linux ~~~~~ PyTango is available on linux as an official debian/ubuntu package: .. sourcecode:: console $ sudo apt-get install python-pytango RPM packages are also available for RHEL & CentOS: .. hlist:: :columns: 2 * `CentOS 6 32bits `_ * `CentOS 6 64bits `_ * `CentOS 7 64bits `_ * `Fedora 23 32bits `_ * `Fedora 23 64bits `_ PyPi ~~~~ You can also install the latest version from `PyPi`_. First, make sure you have the following packages already installed (all of them are available from the major official distribution repositories): * `boost-python`_ (including boost-python-dev) * `numpy`_ Then install PyTango either from pip: .. sourcecode:: console $ pip install PyTango or easy_install: .. sourcecode:: console $ easy_install -U PyTango Windows ~~~~~~~ First, make sure `Python`_ and `numpy`_ are installed. PyTango team provides a limited set of binary PyTango distributables for Windows XP/Vista/7/8. The complete list of binaries can be downloaded from `PyPI`_. Select the proper windows package, download it and finally execute the installion wizard. Compiling --------- Linux ~~~~~ Since PyTango 9 the build system used to compile PyTango is the standard python setuptools. Besides the binaries for the three dependencies mentioned above, you also need the development files for the respective libraries. You can get the latest ``.tar.gz`` from `PyPI`_ or directly the latest SVN checkout: .. sourcecode:: console $ git clone https://github.com/tango-cs/pytango.git $ cd pytango $ python setup.py build $ sudo python setup.py install This will install PyTango in the system python installation directory. (Since PyTango9, :ref:`itango` has been removed to a separate project and it will not be installed with PyTango.) If you whish to install in a different directory, replace the last line with: .. sourcecode:: console $ # private installation to your user (usually ~/.local/lib/python./site-packages) $ python setup.py install --user $ # or specific installation directory $ python setup.py install --prefix=/home/homer/local Windows ~~~~~~~ On windows, PyTango must be built using MS VC++. Since it is rarely needed and the instructions are so complicated, I have choosen to place the how-to in a separate text file. You can find it in the source package under :file:`doc/windows_notes.txt`. Testing ------- To test the installation, import ``tango`` and check ``tango.Release.version``: .. sourcecode:: console $ python -c "import tango; print(tango.Release.version)" 9.2.2 Next steps: Check out the :ref:`pytango-quick-tour`. pytango-9.2.2/doc/tep.rst000066400000000000000000000003361316324373100153010ustar00rootroot00000000000000.. currentmodule:: tango .. _pytango_enhancement_proposals: ============================= PyTango Enhancement Proposals ============================= .. toctree:: :maxdepth: 1 tep/tep-0001 tep/tep-0002 pytango-9.2.2/doc/tep/000077500000000000000000000000001316324373100145455ustar00rootroot00000000000000pytango-9.2.2/doc/tep/DataBase.xmi000066400000000000000000001567531316324373100167510ustar00rootroot00000000000000 pytango-9.2.2/doc/tep/database.py000066400000000000000000003001231316324373100166620ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding:utf-8 -*- ############################################################################## ## license : ##============================================================================ ## ## File : DataBase.py ## ## Project : TANGO ## ## $Author : controls$ ## ## $Revision : $ ## ## $Date : $ ## ## $HeadUrl : $ ##============================================================================ ## This file is generated by POGO ## (Program Obviously used to Generate tango Object) ## ## (c) - Software Engineering Group - ESRF ############################################################################## """This class manage the TANGO database.""" __all__ = ["DataBase", "DataBaseClass", "main"] __docformat__ = 'restructuredtext' import PyTango import sys # Add additional import #----- PROTECTED REGION ID(DataBase.additionnal_import) ENABLED START -----# try: from __future__ import print_function except ImportError: pass #----- PROTECTED REGION END -----# // DataBase.additionnal_import ############################################################################## ## Device States Description ## ## No states for this device ############################################################################## class DataBase (PyTango.Device_4Impl): #--------- Add you global variables here -------------------------- #----- PROTECTED REGION ID(DataBase.global_variables) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.global_variables #------------------------------------------------------------------ # Device constructor #------------------------------------------------------------------ def __init__(self,cl, name): PyTango.Device_4Impl.__init__(self,cl,name) self.debug_stream("In " + self.get_name() + ".__init__()") DataBase.init_device(self) #------------------------------------------------------------------ # Device destructor #------------------------------------------------------------------ def delete_device(self): self.debug_stream("In " + self.get_name() + ".delete_device()") #----- PROTECTED REGION ID(DataBase.delete_device) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.delete_device #------------------------------------------------------------------ # Device initialization #------------------------------------------------------------------ def init_device(self): self.debug_stream("In " + self.get_name() + ".init_device()") self.get_device_properties(self.get_device_class()) self.attr_StoredProcedureRelease_read = '' self.attr_Timing_average_read = [0.0] self.attr_Timing_minimum_read = [0.0] self.attr_Timing_maximum_read = [0.0] self.attr_Timing_calls_read = [0.0] self.attr_Timing_index_read = [''] self.attr_Timing_info_read = [''] #----- PROTECTED REGION ID(DataBase.init_device) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.init_device #------------------------------------------------------------------ # Always excuted hook method #------------------------------------------------------------------ def always_executed_hook(self): self.debug_stream("In " + self.get_name() + ".always_excuted_hook()") #----- PROTECTED REGION ID(DataBase.always_executed_hook) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.always_executed_hook #================================================================== # # DataBase read/write attribute methods # #================================================================== #------------------------------------------------------------------ # Read StoredProcedureRelease attribute #------------------------------------------------------------------ def read_StoredProcedureRelease(self, attr): self.debug_stream("In " + self.get_name() + ".read_StoredProcedureRelease()") #----- PROTECTED REGION ID(DataBase.StoredProcedureRelease_read) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.StoredProcedureRelease_read attr.set_value(self.attr_StoredProcedureRelease_read) #------------------------------------------------------------------ # Read Timing_average attribute #------------------------------------------------------------------ def read_Timing_average(self, attr): self.debug_stream("In " + self.get_name() + ".read_Timing_average()") #----- PROTECTED REGION ID(DataBase.Timing_average_read) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.Timing_average_read attr.set_value(self.attr_Timing_average_read) #------------------------------------------------------------------ # Read Timing_minimum attribute #------------------------------------------------------------------ def read_Timing_minimum(self, attr): self.debug_stream("In " + self.get_name() + ".read_Timing_minimum()") #----- PROTECTED REGION ID(DataBase.Timing_minimum_read) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.Timing_minimum_read attr.set_value(self.attr_Timing_minimum_read) #------------------------------------------------------------------ # Read Timing_maximum attribute #------------------------------------------------------------------ def read_Timing_maximum(self, attr): self.debug_stream("In " + self.get_name() + ".read_Timing_maximum()") #----- PROTECTED REGION ID(DataBase.Timing_maximum_read) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.Timing_maximum_read attr.set_value(self.attr_Timing_maximum_read) #------------------------------------------------------------------ # Read Timing_calls attribute #------------------------------------------------------------------ def read_Timing_calls(self, attr): self.debug_stream("In " + self.get_name() + ".read_Timing_calls()") #----- PROTECTED REGION ID(DataBase.Timing_calls_read) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.Timing_calls_read attr.set_value(self.attr_Timing_calls_read) #------------------------------------------------------------------ # Read Timing_index attribute #------------------------------------------------------------------ def read_Timing_index(self, attr): self.debug_stream("In " + self.get_name() + ".read_Timing_index()") #----- PROTECTED REGION ID(DataBase.Timing_index_read) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.Timing_index_read attr.set_value(self.attr_Timing_index_read) #------------------------------------------------------------------ # Read Timing_info attribute #------------------------------------------------------------------ def read_Timing_info(self, attr): self.debug_stream("In " + self.get_name() + ".read_Timing_info()") #----- PROTECTED REGION ID(DataBase.Timing_info_read) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.Timing_info_read attr.set_value(self.attr_Timing_info_read) #------------------------------------------------------------------ # Read Attribute Hardware #------------------------------------------------------------------ def read_attr_hardware(self, data): self.debug_stream("In " + self.get_name() + ".read_attr_hardware()") #----- PROTECTED REGION ID(DataBase.read_attr_hardware) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.read_attr_hardware #================================================================== # # DataBase command methods # #================================================================== #------------------------------------------------------------------ # DbAddDevice command: #------------------------------------------------------------------ def DbAddDevice(self, argin): """ Add a Tango class device to a specific device server :param argin: Str[0] = Full device server process name Str[1] = Device name Str[2] = Tango class name :type: PyTango.DevVarStringArray :return: :rtype: PyTango.DevVoid """ self.debug_stream("In " + self.get_name() + ".DbAddDevice()") #----- PROTECTED REGION ID(DataBase.DbAddDevice) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbAddDevice #------------------------------------------------------------------ # DbAddServer command: #------------------------------------------------------------------ def DbAddServer(self, argin): """ Create a device server process entry in database :param argin: Str[0] = Full device server name Str[1] = Device(s) name Str[2] = Tango class name Str[n] = Device name Str[n + 1] = Tango class name :type: PyTango.DevVarStringArray :return: :rtype: PyTango.DevVoid """ self.debug_stream("In " + self.get_name() + ".DbAddServer()") #----- PROTECTED REGION ID(DataBase.DbAddServer) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbAddServer #------------------------------------------------------------------ # DbDeleteAttributeAlias command: #------------------------------------------------------------------ def DbDeleteAttributeAlias(self, argin): """ Delete an attribute alias. :param argin: Attriibute alias name. :type: PyTango.DevString :return: :rtype: PyTango.DevVoid """ self.debug_stream("In " + self.get_name() + ".DbDeleteAttributeAlias()") #----- PROTECTED REGION ID(DataBase.DbDeleteAttributeAlias) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbDeleteAttributeAlias #------------------------------------------------------------------ # DbDeleteClassAttribute command: #------------------------------------------------------------------ def DbDeleteClassAttribute(self, argin): """ delete a class attribute and all its properties from database :param argin: Str[0] = Tango class name Str[1] = Attribute name :type: PyTango.DevVarStringArray :return: :rtype: PyTango.DevVoid """ self.debug_stream("In " + self.get_name() + ".DbDeleteClassAttribute()") #----- PROTECTED REGION ID(DataBase.DbDeleteClassAttribute) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbDeleteClassAttribute #------------------------------------------------------------------ # DbDeleteClassAttributeProperty command: #------------------------------------------------------------------ def DbDeleteClassAttributeProperty(self, argin): """ delete class attribute properties from database :param argin: Str[0] = Tango class name Str[1] = Attribute name Str[2] = Property name Str[n] = Property name :type: PyTango.DevVarStringArray :return: :rtype: PyTango.DevVoid """ self.debug_stream("In " + self.get_name() + ".DbDeleteClassAttributeProperty()") #----- PROTECTED REGION ID(DataBase.DbDeleteClassAttributeProperty) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbDeleteClassAttributeProperty #------------------------------------------------------------------ # DbDeleteClassProperty command: #------------------------------------------------------------------ def DbDeleteClassProperty(self, argin): """ Delete class properties from database :param argin: Str[0] = Tango class name Str[1] = Property name Str[n] = Property name :type: PyTango.DevVarStringArray :return: :rtype: PyTango.DevVoid """ self.debug_stream("In " + self.get_name() + ".DbDeleteClassProperty()") #----- PROTECTED REGION ID(DataBase.DbDeleteClassProperty) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbDeleteClassProperty #------------------------------------------------------------------ # DbDeleteDevice command: #------------------------------------------------------------------ def DbDeleteDevice(self, argin): """ Delete a devcie from database :param argin: device name :type: PyTango.DevString :return: :rtype: PyTango.DevVoid """ self.debug_stream("In " + self.get_name() + ".DbDeleteDevice()") #----- PROTECTED REGION ID(DataBase.DbDeleteDevice) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbDeleteDevice #------------------------------------------------------------------ # DbDeleteDeviceAlias command: #------------------------------------------------------------------ def DbDeleteDeviceAlias(self, argin): """ Delete a device alias. :param argin: device alias name :type: PyTango.DevString :return: :rtype: PyTango.DevVoid """ self.debug_stream("In " + self.get_name() + ".DbDeleteDeviceAlias()") #----- PROTECTED REGION ID(DataBase.DbDeleteDeviceAlias) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbDeleteDeviceAlias #------------------------------------------------------------------ # DbDeleteDeviceAttribute command: #------------------------------------------------------------------ def DbDeleteDeviceAttribute(self, argin): """ Delete device attribute properties from database :param argin: Str[0] = Device name Str[1] = Attribute name :type: PyTango.DevVarStringArray :return: :rtype: PyTango.DevVoid """ self.debug_stream("In " + self.get_name() + ".DbDeleteDeviceAttribute()") #----- PROTECTED REGION ID(DataBase.DbDeleteDeviceAttribute) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbDeleteDeviceAttribute #------------------------------------------------------------------ # DbDeleteDeviceAttributeProperty command: #------------------------------------------------------------------ def DbDeleteDeviceAttributeProperty(self, argin): """ delete a device attribute property from the database :param argin: Str[0] = Device name Str[1] = Attribute name Str[2] = Property name Str[n] = Property name :type: PyTango.DevVarStringArray :return: :rtype: PyTango.DevVoid """ self.debug_stream("In " + self.get_name() + ".DbDeleteDeviceAttributeProperty()") #----- PROTECTED REGION ID(DataBase.DbDeleteDeviceAttributeProperty) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbDeleteDeviceAttributeProperty #------------------------------------------------------------------ # DbDeleteDeviceProperty command: #------------------------------------------------------------------ def DbDeleteDeviceProperty(self, argin): """ Delete device property(ies) :param argin: Str[0] = Device name Str[1] = Property name Str[n] = Property name :type: PyTango.DevVarStringArray :return: :rtype: PyTango.DevVoid """ self.debug_stream("In " + self.get_name() + ".DbDeleteDeviceProperty()") #----- PROTECTED REGION ID(DataBase.DbDeleteDeviceProperty) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbDeleteDeviceProperty #------------------------------------------------------------------ # DbDeleteProperty command: #------------------------------------------------------------------ def DbDeleteProperty(self, argin): """ Delete free property from database :param argin: Str[0] = Object name Str[1] = Property name Str[n] = Property name :type: PyTango.DevVarStringArray :return: :rtype: PyTango.DevVoid """ self.debug_stream("In " + self.get_name() + ".DbDeleteProperty()") #----- PROTECTED REGION ID(DataBase.DbDeleteProperty) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbDeleteProperty #------------------------------------------------------------------ # DbDeleteServer command: #------------------------------------------------------------------ def DbDeleteServer(self, argin): """ Delete server from the database but dont delete device properties :param argin: Device server name :type: PyTango.DevString :return: :rtype: PyTango.DevVoid """ self.debug_stream("In " + self.get_name() + ".DbDeleteServer()") #----- PROTECTED REGION ID(DataBase.DbDeleteServer) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbDeleteServer #------------------------------------------------------------------ # DbDeleteServerInfo command: #------------------------------------------------------------------ def DbDeleteServerInfo(self, argin): """ delete info related to a Tango devvice server process :param argin: Device server name :type: PyTango.DevString :return: :rtype: PyTango.DevVoid """ self.debug_stream("In " + self.get_name() + ".DbDeleteServerInfo()") #----- PROTECTED REGION ID(DataBase.DbDeleteServerInfo) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbDeleteServerInfo #------------------------------------------------------------------ # DbExportDevice command: #------------------------------------------------------------------ def DbExportDevice(self, argin): """ Export a device to the database :param argin: Str[0] = Device name Str[1] = CORBA IOR Str[2] = Device server process host name Str[3] = Device server process PID or string ``null`` Str[4] = Device server process version :type: PyTango.DevVarStringArray :return: :rtype: PyTango.DevVoid """ self.debug_stream("In " + self.get_name() + ".DbExportDevice()") #----- PROTECTED REGION ID(DataBase.DbExportDevice) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbExportDevice #------------------------------------------------------------------ # DbExportEvent command: #------------------------------------------------------------------ def DbExportEvent(self, argin): """ Export Event channel to database :param argin: Str[0] = event channel name (or factory name) Str[1] = CORBA IOR Str[2] = Notifd host name Str[3] = Notifd pid Str[4] = Notifd version :type: PyTango.DevVarStringArray :return: :rtype: PyTango.DevVoid """ self.debug_stream("In " + self.get_name() + ".DbExportEvent()") #----- PROTECTED REGION ID(DataBase.DbExportEvent) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbExportEvent #------------------------------------------------------------------ # DbGetAliasDevice command: #------------------------------------------------------------------ def DbGetAliasDevice(self, argin): """ Get device name from its alias. :param argin: Alias name :type: PyTango.DevString :return: Device name :rtype: PyTango.DevString """ self.debug_stream("In " + self.get_name() + ".DbGetAliasDevice()") argout = '' #----- PROTECTED REGION ID(DataBase.DbGetAliasDevice) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetAliasDevice return argout #------------------------------------------------------------------ # DbGetAttributeAlias command: #------------------------------------------------------------------ def DbGetAttributeAlias(self, argin): """ Get the attribute name for the given alias. If alias not found in database, returns an empty string. :param argin: The attribute alias name :type: PyTango.DevString :return: The attribute name (device/attribute) :rtype: PyTango.DevString """ self.debug_stream("In " + self.get_name() + ".DbGetAttributeAlias()") argout = '' #----- PROTECTED REGION ID(DataBase.DbGetAttributeAlias) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetAttributeAlias return argout #------------------------------------------------------------------ # DbGetAttributeAliasList command: #------------------------------------------------------------------ def DbGetAttributeAliasList(self, argin): """ Get attribute alias list for a specified filter :param argin: attribute alias filter string (eg: att*) :type: PyTango.DevString :return: attribute aliases :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetAttributeAliasList()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbGetAttributeAliasList) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetAttributeAliasList return argout #------------------------------------------------------------------ # DbGetClassAttributeList command: #------------------------------------------------------------------ def DbGetClassAttributeList(self, argin): """ Get attrilute list for a given Tango class with a specified filter :param argin: Str[0] = Tango class name Str[1] = Attribute name filter (eg: att*) :type: PyTango.DevVarStringArray :return: Str[0] = Class attribute name Str[n] = Class attribute name :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetClassAttributeList()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbGetClassAttributeList) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetClassAttributeList return argout #------------------------------------------------------------------ # DbGetClassAttributeProperty command: #------------------------------------------------------------------ def DbGetClassAttributeProperty(self, argin): """ Get Tango class property(ies) value :param argin: Str[0] = Tango class name Str[1] = Attribute name Str[n] = Attribute name :type: PyTango.DevVarStringArray :return: Str[0] = Tango class name Str[1] = Attribute property number Str[2] = Attribute property 1 name Str[3] = Attribute property 1 value Str[n + 1] = Attribute property 2 name Str[n + 2] = Attribute property 2 value :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetClassAttributeProperty()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbGetClassAttributeProperty) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetClassAttributeProperty return argout #------------------------------------------------------------------ # DbGetClassAttributeProperty2 command: #------------------------------------------------------------------ def DbGetClassAttributeProperty2(self, argin): """ This command supports array property compared to the old command called DbGetClassAttributeProperty. The old command has not been deleted from the server for compatibility reasons. :param argin: Str[0] = Tango class name Str[1] = Attribute name Str[n] = Attribute name :type: PyTango.DevVarStringArray :return: Str[0] = Tango class name Str[1] = Attribute property number Str[2] = Attribute property 1 name Str[3] = Attribute property 1 value number (array case) Str[4] = Attribute property 1 value Str[n] = Attribute property 1 value (array case) Str[n + 1] = Attribute property 2 name Str[n + 2] = Attribute property 2 value number (array case) Str[n + 3] = Attribute property 2 value Str[n + m] = Attribute property 2 value (array case) :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetClassAttributeProperty2()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbGetClassAttributeProperty2) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetClassAttributeProperty2 return argout #------------------------------------------------------------------ # DbGetClassAttributePropertyHist command: #------------------------------------------------------------------ def DbGetClassAttributePropertyHist(self, argin): """ Retrieve Tango class attribute property history :param argin: Str[0] = Tango class Str[1] = Attribute name Str[2] = Property name :type: PyTango.DevVarStringArray :return: Str[0] = Attribute name Str[1] = Property name Str[2] = date Str[3] = Property value number (array case) Str[4] = Property value 1 Str[n] = Property value n :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetClassAttributePropertyHist()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbGetClassAttributePropertyHist) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetClassAttributePropertyHist return argout #------------------------------------------------------------------ # DbGetClassForDevice command: #------------------------------------------------------------------ def DbGetClassForDevice(self, argin): """ Get Tango class for the specified device. :param argin: Device name :type: PyTango.DevString :return: Device Tango class :rtype: PyTango.DevString """ self.debug_stream("In " + self.get_name() + ".DbGetClassForDevice()") argout = '' #----- PROTECTED REGION ID(DataBase.DbGetClassForDevice) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetClassForDevice return argout #------------------------------------------------------------------ # DbGetClassInheritanceForDevice command: #------------------------------------------------------------------ def DbGetClassInheritanceForDevice(self, argin): """ Get class inheritance for the specified device. :param argin: Device name :type: PyTango.DevString :return: Classes off the specified device. [0] - is the class of the device. [1] - is the class from the device class is inherited. ........and so on :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetClassInheritanceForDevice()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbGetClassInheritanceForDevice) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetClassInheritanceForDevice return argout #------------------------------------------------------------------ # DbGetClassList command: #------------------------------------------------------------------ def DbGetClassList(self, argin): """ Get Tango class list with a specified filter :param argin: Filter :type: PyTango.DevString :return: Class list :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetClassList()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbGetClassList) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetClassList return argout #------------------------------------------------------------------ # DbGetClassProperty command: #------------------------------------------------------------------ def DbGetClassProperty(self, argin): """ :param argin: Str[0] = Tango class Str[1] = Property name Str[2] = Property name :type: PyTango.DevVarStringArray :return: Str[0] = Tango class Str[1] = Property number Str[2] = Property name Str[3] = Property value number (array case) Str[4] = Property value Str[n] = Propery value (array case) .... :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetClassProperty()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbGetClassProperty) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetClassProperty return argout #------------------------------------------------------------------ # DbGetClassPropertyHist command: #------------------------------------------------------------------ def DbGetClassPropertyHist(self, argin): """ Retrieve Tango class property history :param argin: Str[0] = Tango class Str[1] = Property name :type: PyTango.DevVarStringArray :return: Str[0] = Property name Str[1] = date Str[2] = Property value number (array case) Str[3] = Property value 1 Str[n] = Property value n :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetClassPropertyHist()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbGetClassPropertyHist) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetClassPropertyHist return argout #------------------------------------------------------------------ # DbGetClassPropertyList command: #------------------------------------------------------------------ def DbGetClassPropertyList(self, argin): """ Get property list for a given Tango class with a specified filter :param argin: The filter :type: PyTango.DevString :return: Property name list :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetClassPropertyList()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbGetClassPropertyList) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetClassPropertyList return argout #------------------------------------------------------------------ # DbGetDeviceAlias command: #------------------------------------------------------------------ def DbGetDeviceAlias(self, argin): """ Return alias for device name if found. :param argin: The device name :type: PyTango.DevString :return: The alias found :rtype: PyTango.DevString """ self.debug_stream("In " + self.get_name() + ".DbGetDeviceAlias()") argout = '' #----- PROTECTED REGION ID(DataBase.DbGetDeviceAlias) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetDeviceAlias return argout #------------------------------------------------------------------ # DbGetDeviceAliasList command: #------------------------------------------------------------------ def DbGetDeviceAliasList(self, argin): """ Get device alias name with a specific filter :param argin: The filter :type: PyTango.DevString :return: Device alias list :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetDeviceAliasList()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbGetDeviceAliasList) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetDeviceAliasList return argout #------------------------------------------------------------------ # DbGetDeviceAttributeList command: #------------------------------------------------------------------ def DbGetDeviceAttributeList(self, argin): """ Return list of attributes matching the wildcard for the specified device :param argin: Str[0] = Device name Str[1] = Wildcard :type: PyTango.DevVarStringArray :return: attribute name list :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetDeviceAttributeList()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbGetDeviceAttributeList) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetDeviceAttributeList return argout #------------------------------------------------------------------ # DbGetDeviceAttributeProperty command: #------------------------------------------------------------------ def DbGetDeviceAttributeProperty(self, argin): """ Get device attribute property(ies) value :param argin: Str[0] = Device name Str[1] = Attribute name Str[n] = Attribute name :type: PyTango.DevVarStringArray :return: Str[0] = Device name Str[1] = Attribute property number Str[2] = Attribute property 1 name Str[3] = Attribute property 1 value Str[n + 1] = Attribute property 2 name Str[n + 2] = Attribute property 2 value :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetDeviceAttributeProperty()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbGetDeviceAttributeProperty) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetDeviceAttributeProperty return argout #------------------------------------------------------------------ # DbGetDeviceAttributeProperty2 command: #------------------------------------------------------------------ def DbGetDeviceAttributeProperty2(self, argin): """ Retrieve device attribute properties. This command has the possibility to retrieve device attribute properties which are arrays. It is not possible with the old DbGetDeviceAttributeProperty command. Nevertheless, the old command has not been deleted for compatibility reason :param argin: Str[0] = Device name Str[1] = Attribute name Str[n] = Attribute name :type: PyTango.DevVarStringArray :return: Str[0] = Device name Str[1] = Attribute property number Str[2] = Attribute property 1 name Str[3] = Attribute property 1 value number (array case) Str[4] = Attribute property 1 value Str[n] = Attribute property 1 value (array case) Str[n + 1] = Attribute property 2 name Str[n + 2] = Attribute property 2 value number (array case) Str[n + 3] = Attribute property 2 value Str[n + m] = Attribute property 2 value (array case) :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetDeviceAttributeProperty2()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbGetDeviceAttributeProperty2) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetDeviceAttributeProperty2 return argout #------------------------------------------------------------------ # DbGetDeviceAttributePropertyHist command: #------------------------------------------------------------------ def DbGetDeviceAttributePropertyHist(self, argin): """ Retrieve device attribute property history :param argin: Str[0] = Device name Str[1] = Attribute name Str[2] = Property name :type: PyTango.DevVarStringArray :return: Str[0] = Attribute name Str[1] = Property name Str[2] = date Str[3] = Property value number (array case) Str[4] = Property value 1 Str[n] = Property value n :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetDeviceAttributePropertyHist()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbGetDeviceAttributePropertyHist) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetDeviceAttributePropertyHist return argout #------------------------------------------------------------------ # DbGetDeviceClassList command: #------------------------------------------------------------------ def DbGetDeviceClassList(self, argin): """ Get Tango classes/device list embedded in a specific device server :param argin: Device server process name :type: PyTango.DevString :return: Str[0] = Device name Str[1] = Tango class Str[n] = Device name Str[n + 1] = Tango class :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetDeviceClassList()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbGetDeviceClassList) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetDeviceClassList return argout #------------------------------------------------------------------ # DbGetDeviceDomainList command: #------------------------------------------------------------------ def DbGetDeviceDomainList(self, argin): """ Get list of device domain name matching the specified :param argin: The wildcard :type: PyTango.DevString :return: Device name domain list :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetDeviceDomainList()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbGetDeviceDomainList) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetDeviceDomainList return argout #------------------------------------------------------------------ # DbGetDeviceExportedList command: #------------------------------------------------------------------ def DbGetDeviceExportedList(self, argin): """ Get a list of exported devices whose names satisfy the filter (wildcard is :param argin: filter :type: PyTango.DevString :return: list of exported devices :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetDeviceExportedList()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbGetDeviceExportedList) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetDeviceExportedList return argout #------------------------------------------------------------------ # DbGetDeviceFamilyList command: #------------------------------------------------------------------ def DbGetDeviceFamilyList(self, argin): """ Get a list of device name families for device name matching the specified wildcard :param argin: The wildcard :type: PyTango.DevString :return: Family list :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetDeviceFamilyList()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbGetDeviceFamilyList) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetDeviceFamilyList return argout #------------------------------------------------------------------ # DbGetDeviceInfo command: #------------------------------------------------------------------ def DbGetDeviceInfo(self, argin): """ Returns info from DbImportDevice and started/stopped dates. :param argin: Device name :type: PyTango.DevString :return: Str[0] = Device name Str[1] = CORBA IOR Str[2] = Device version Str[3] = Device Server name Str[4] = Device Server process host name Str[5] = Started date (or ? if not set) Str[6] = Stopped date (or ? if not set) Str[7] = Device class Lg[0] = Device exported flag Lg[1] = Device Server process PID (or -1 if not set) :rtype: PyTango.DevVarLongStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetDeviceInfo()") argout = [0],[''] #----- PROTECTED REGION ID(DataBase.DbGetDeviceInfo) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetDeviceInfo return argout #------------------------------------------------------------------ # DbGetDeviceList command: #------------------------------------------------------------------ def DbGetDeviceList(self, argin): """ Get a list of devices for specified server and class. :param argin: argin[0] : server name argin[1] : class name :type: PyTango.DevVarStringArray :return: The list of devices for specified server and class. :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetDeviceList()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbGetDeviceList) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetDeviceList return argout #------------------------------------------------------------------ # DbGetDeviceWideList command: #------------------------------------------------------------------ def DbGetDeviceWideList(self, argin): """ Get a list of devices whose names satisfy the filter. :param argin: filter :type: PyTango.DevString :return: list of exported devices :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetDeviceWideList()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbGetDeviceWideList) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetDeviceWideList return argout #------------------------------------------------------------------ # DbGetDeviceMemberList command: #------------------------------------------------------------------ def DbGetDeviceMemberList(self, argin): """ Get a list of device name members for device name matching the specified filter :param argin: The filter :type: PyTango.DevString :return: Device names member list :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetDeviceMemberList()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbGetDeviceMemberList) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetDeviceMemberList return argout #------------------------------------------------------------------ # DbGetDeviceProperty command: #------------------------------------------------------------------ def DbGetDeviceProperty(self, argin): """ :param argin: Str[0] = Device name Str[1] = Property name Str[n] = Property name :type: PyTango.DevVarStringArray :return: Str[0] = Device name Str[1] = Property number Str[2] = Property name Str[3] = Property value number (array case) Str[4] = Property value 1 Str[n] = Property value n (array case) Str[n + 1] = Property name Str[n + 2] = Property value number (array case) Str[n + 3] = Property value 1 Str[n + m] = Property value m :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetDeviceProperty()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbGetDeviceProperty) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetDeviceProperty return argout #------------------------------------------------------------------ # DbGetDevicePropertyHist command: #------------------------------------------------------------------ def DbGetDevicePropertyHist(self, argin): """ Retrieve device property history :param argin: Str[0] = Device name Str[2] = Property name :type: PyTango.DevVarStringArray :return: Str[0] = Property name Str[1] = date Str[2] = Property value number (array case) Str[3] = Property value 1 Str[n] = Property value n :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetDevicePropertyHist()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbGetDevicePropertyHist) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetDevicePropertyHist return argout #------------------------------------------------------------------ # DbGetDevicePropertyList command: #------------------------------------------------------------------ def DbGetDevicePropertyList(self, argin): """ Get property list belonging to the specified device and with name matching the specified filter :param argin: Str[0] = device name Str[1] = Filter :type: PyTango.DevVarStringArray :return: Property name list :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetDevicePropertyList()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbGetDevicePropertyList) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetDevicePropertyList return argout #------------------------------------------------------------------ # DbGetDeviceServerClassList command: #------------------------------------------------------------------ def DbGetDeviceServerClassList(self, argin): """ Get list of Tango classes for a device server :param argin: device server process name :type: PyTango.DevString :return: list of classes for this device server :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetDeviceServerClassList()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbGetDeviceServerClassList) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetDeviceServerClassList return argout #------------------------------------------------------------------ # DbGetExportdDeviceListForClass command: #------------------------------------------------------------------ def DbGetExportdDeviceListForClass(self, argin): """ Query the database for device exported for the specified class. :param argin: Class name :type: PyTango.DevString :return: Device exported list :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetExportdDeviceListForClass()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbGetExportdDeviceListForClass) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetExportdDeviceListForClass return argout #------------------------------------------------------------------ # DbGetHostList command: #------------------------------------------------------------------ def DbGetHostList(self, argin): """ Get host list with name matching the specified filter :param argin: The filter :type: PyTango.DevString :return: Host name list :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetHostList()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbGetHostList) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetHostList return argout #------------------------------------------------------------------ # DbGetHostServerList command: #------------------------------------------------------------------ def DbGetHostServerList(self, argin): """ Get list of device server process name running on host with name matching the specified filter :param argin: The filter :type: PyTango.DevString :return: Device server process name list :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetHostServerList()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbGetHostServerList) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetHostServerList return argout #------------------------------------------------------------------ # DbGetHostServersInfo command: #------------------------------------------------------------------ def DbGetHostServersInfo(self, argin): """ Get info about all servers running on specified host, name, mode and level :param argin: Host name :type: PyTango.DevString :return: Server info for all servers running on specified host :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetHostServersInfo()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbGetHostServersInfo) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetHostServersInfo return argout #------------------------------------------------------------------ # DbGetInstanceNameList command: #------------------------------------------------------------------ def DbGetInstanceNameList(self, argin): """ Returns the instance names found for specified server. :param argin: Server name :type: PyTango.DevString :return: The instance names found for specified server. :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetInstanceNameList()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbGetInstanceNameList) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetInstanceNameList return argout #------------------------------------------------------------------ # DbGetObjectList command: #------------------------------------------------------------------ def DbGetObjectList(self, argin): """ Get list of free object defined in database with name matching the specified filter :param argin: The filter :type: PyTango.DevString :return: Object name list :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetObjectList()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbGetObjectList) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetObjectList return argout #------------------------------------------------------------------ # DbGetProperty command: #------------------------------------------------------------------ def DbGetProperty(self, argin): """ Get free object property :param argin: Str[0] = Object name Str[1] = Property name Str[n] = Property name :type: PyTango.DevVarStringArray :return: Str[0] = Object name Str[1] = Property number Str[2] = Property name Str[3] = Property value number (array case) Str[4] = Property value 1 Str[n] = Property value n (array case) Str[n + 1] = Property name Str[n + 2] = Property value number (array case) Str[n + 3] = Property value 1 Str[n + m] = Property value m :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetProperty()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbGetProperty) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetProperty return argout #------------------------------------------------------------------ # DbGetPropertyHist command: #------------------------------------------------------------------ def DbGetPropertyHist(self, argin): """ Retrieve object property history :param argin: Str[0] = Object name Str[2] = Property name :type: PyTango.DevVarStringArray :return: Str[0] = Property name Str[1] = date Str[2] = Property value number (array case) Str[3] = Property value 1 Str[n] = Property value n :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetPropertyHist()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbGetPropertyHist) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetPropertyHist return argout #------------------------------------------------------------------ # DbGetPropertyList command: #------------------------------------------------------------------ def DbGetPropertyList(self, argin): """ Get list of property defined for a free object and matching the specified filter :param argin: Str[0] = Object name Str[1] = filter :type: PyTango.DevVarStringArray :return: Property name list :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetPropertyList()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbGetPropertyList) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetPropertyList return argout #------------------------------------------------------------------ # DbGetServerInfo command: #------------------------------------------------------------------ def DbGetServerInfo(self, argin): """ Get info about host, mode and level for specified server :param argin: server name :type: PyTango.DevString :return: server info :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetServerInfo()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbGetServerInfo) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetServerInfo return argout #------------------------------------------------------------------ # DbGetServerList command: #------------------------------------------------------------------ def DbGetServerList(self, argin): """ Get list of device server process defined in database with name matching the specified filter :param argin: The filter :type: PyTango.DevString :return: Device server process name list :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetServerList()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbGetServerList) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetServerList return argout #------------------------------------------------------------------ # DbGetServerNameList command: #------------------------------------------------------------------ def DbGetServerNameList(self, argin): """ Returns the list of server names found for the wildcard specified. It returns only the server executable name without instance name as DbGetServerList. :param argin: wildcard for server names. :type: PyTango.DevString :return: server names found. :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetServerNameList()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbGetServerNameList) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetServerNameList return argout #------------------------------------------------------------------ # DbImportDevice command: #------------------------------------------------------------------ def DbImportDevice(self, argin): """ Import a device from the database :param argin: Device name (or alias) :type: PyTango.DevString :return: Str[0] = device name Str[1] = CORBA IOR Str[2] = device version Str[3] = device server process name Str[4] = host name Str[5] = Tango class name Lg[0] = Exported flag Lg[1] = Device server process PID :rtype: PyTango.DevVarLongStringArray """ self.debug_stream("In " + self.get_name() + ".DbImportDevice()") argout = [0],[''] #----- PROTECTED REGION ID(DataBase.DbImportDevice) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbImportDevice return argout #------------------------------------------------------------------ # DbImportEvent command: #------------------------------------------------------------------ def DbImportEvent(self, argin): """ Get event channel info from database :param argin: name of event channel or factory :type: PyTango.DevString :return: export information e.g. IOR :rtype: PyTango.DevVarLongStringArray """ self.debug_stream("In " + self.get_name() + ".DbImportEvent()") argout = [0],[''] #----- PROTECTED REGION ID(DataBase.DbImportEvent) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbImportEvent return argout #------------------------------------------------------------------ # DbInfo command: #------------------------------------------------------------------ def DbInfo(self): """ Get miscellaneous numbers on information stored in database :param : :type: PyTango.DevVoid :return: Miscellaneous info like: - Device defined in database - Device marked as exported in database - Device server process defined in database - Device server process marked as exported in database - Device properties defined in database - Class properties defined in database - Device attribute properties defined in database - Class attribute properties defined in database - Object properties defined in database :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbInfo()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbInfo) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbInfo return argout #------------------------------------------------------------------ # DbPutAttributeAlias command: #------------------------------------------------------------------ def DbPutAttributeAlias(self, argin): """ Define an alias for an attribute :param argin: Str[0] = attribute name Str[1] = attribute alias :type: PyTango.DevVarStringArray :return: :rtype: PyTango.DevVoid """ self.debug_stream("In " + self.get_name() + ".DbPutAttributeAlias()") #----- PROTECTED REGION ID(DataBase.DbPutAttributeAlias) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbPutAttributeAlias #------------------------------------------------------------------ # DbPutClassAttributeProperty command: #------------------------------------------------------------------ def DbPutClassAttributeProperty(self, argin): """ Create/Update class attribute property(ies) in database :param argin: Str[0] = Tango class name Str[1] = Attribute number Str[2] = Attribute name Str[3] = Property number Str[4] = Property name Str[5] = Property value ..... :type: PyTango.DevVarStringArray :return: :rtype: PyTango.DevVoid """ self.debug_stream("In " + self.get_name() + ".DbPutClassAttributeProperty()") #----- PROTECTED REGION ID(DataBase.DbPutClassAttributeProperty) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbPutClassAttributeProperty #------------------------------------------------------------------ # DbPutClassAttributeProperty2 command: #------------------------------------------------------------------ def DbPutClassAttributeProperty2(self, argin): """ This command adds support for array properties compared to the previous one called DbPutClassAttributeProperty. The old comman is still there for compatibility reason :param argin: Str[0] = Tango class name Str[1] = Attribute number Str[2] = Attribute name Str[3] = Property number Str[4] = Property name Str[5] = Property value number (array case) Str[5] = Property value 1 Str[n] = Property value n (array case) ..... :type: PyTango.DevVarStringArray :return: :rtype: PyTango.DevVoid """ self.debug_stream("In " + self.get_name() + ".DbPutClassAttributeProperty2()") #----- PROTECTED REGION ID(DataBase.DbPutClassAttributeProperty2) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbPutClassAttributeProperty2 #------------------------------------------------------------------ # DbPutClassProperty command: #------------------------------------------------------------------ def DbPutClassProperty(self, argin): """ Create / Update class property(ies) :param argin: Str[0] = Tango class name Str[1] = Property number Str[2] = Property name Str[3] = Property value number Str[4] = Property value 1 Str[n] = Property value n .... :type: PyTango.DevVarStringArray :return: :rtype: PyTango.DevVoid """ self.debug_stream("In " + self.get_name() + ".DbPutClassProperty()") #----- PROTECTED REGION ID(DataBase.DbPutClassProperty) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbPutClassProperty #------------------------------------------------------------------ # DbPutDeviceAlias command: #------------------------------------------------------------------ def DbPutDeviceAlias(self, argin): """ Define alias for a given device name :param argin: Str[0] = device name Str[1] = alias name :type: PyTango.DevVarStringArray :return: :rtype: PyTango.DevVoid """ self.debug_stream("In " + self.get_name() + ".DbPutDeviceAlias()") #----- PROTECTED REGION ID(DataBase.DbPutDeviceAlias) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbPutDeviceAlias #------------------------------------------------------------------ # DbPutDeviceAttributeProperty command: #------------------------------------------------------------------ def DbPutDeviceAttributeProperty(self, argin): """ Create/Update device attribute property(ies) in database :param argin: Str[0] = Device name Str[1] = Attribute number Str[2] = Attribute name Str[3] = Property number Str[4] = Property name Str[5] = Property value ..... :type: PyTango.DevVarStringArray :return: :rtype: PyTango.DevVoid """ self.debug_stream("In " + self.get_name() + ".DbPutDeviceAttributeProperty()") #----- PROTECTED REGION ID(DataBase.DbPutDeviceAttributeProperty) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbPutDeviceAttributeProperty #------------------------------------------------------------------ # DbPutDeviceAttributeProperty2 command: #------------------------------------------------------------------ def DbPutDeviceAttributeProperty2(self, argin): """ Put device attribute property. This command adds the possibility to have attribute property which are arrays. Not possible with the old DbPutDeviceAttributeProperty command. This old command is not deleted for compatibility reasons. :param argin: Str[0] = Device name Str[1] = Attribute number Str[2] = Attribute name Str[3] = Property number Str[4] = Property name Str[5] = Property value number (array case) Str[5] = Property value 1 Str[n] = Property value n (array case) ..... :type: PyTango.DevVarStringArray :return: :rtype: PyTango.DevVoid """ self.debug_stream("In " + self.get_name() + ".DbPutDeviceAttributeProperty2()") #----- PROTECTED REGION ID(DataBase.DbPutDeviceAttributeProperty2) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbPutDeviceAttributeProperty2 #------------------------------------------------------------------ # DbPutDeviceProperty command: #------------------------------------------------------------------ def DbPutDeviceProperty(self, argin): """ Create / Update device property(ies) :param argin: Str[0] = Tango device name Str[1] = Property number Str[2] = Property name Str[3] = Property value number Str[4] = Property value 1 Str[n] = Property value n .... :type: PyTango.DevVarStringArray :return: :rtype: PyTango.DevVoid """ self.debug_stream("In " + self.get_name() + ".DbPutDeviceProperty()") #----- PROTECTED REGION ID(DataBase.DbPutDeviceProperty) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbPutDeviceProperty #------------------------------------------------------------------ # DbPutProperty command: #------------------------------------------------------------------ def DbPutProperty(self, argin): """ Create / Update free object property(ies) :param argin: Str[0] = Object name Str[1] = Property number Str[2] = Property name Str[3] = Property value number Str[4] = Property value 1 Str[n] = Property value n .... :type: PyTango.DevVarStringArray :return: :rtype: PyTango.DevVoid """ self.debug_stream("In " + self.get_name() + ".DbPutProperty()") #----- PROTECTED REGION ID(DataBase.DbPutProperty) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbPutProperty #------------------------------------------------------------------ # DbPutServerInfo command: #------------------------------------------------------------------ def DbPutServerInfo(self, argin): """ Update server info including host, mode and level :param argin: server info :type: PyTango.DevVarStringArray :return: :rtype: PyTango.DevVoid """ self.debug_stream("In " + self.get_name() + ".DbPutServerInfo()") #----- PROTECTED REGION ID(DataBase.DbPutServerInfo) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbPutServerInfo #------------------------------------------------------------------ # DbUnExportDevice command: #------------------------------------------------------------------ def DbUnExportDevice(self, argin): """ Mark a device as non exported in database :param argin: Device name :type: PyTango.DevString :return: :rtype: PyTango.DevVoid """ self.debug_stream("In " + self.get_name() + ".DbUnExportDevice()") #----- PROTECTED REGION ID(DataBase.DbUnExportDevice) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbUnExportDevice #------------------------------------------------------------------ # DbUnExportEvent command: #------------------------------------------------------------------ def DbUnExportEvent(self, argin): """ Mark one event channel as non exported in database :param argin: name of event channel or factory to unexport :type: PyTango.DevString :return: none :rtype: PyTango.DevVoid """ self.debug_stream("In " + self.get_name() + ".DbUnExportEvent()") #----- PROTECTED REGION ID(DataBase.DbUnExportEvent) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbUnExportEvent #------------------------------------------------------------------ # DbUnExportServer command: #------------------------------------------------------------------ def DbUnExportServer(self, argin): """ Mark all devices belonging to a specified device server process as non exported :param argin: Device server name (executable/instance) :type: PyTango.DevString :return: :rtype: PyTango.DevVoid """ self.debug_stream("In " + self.get_name() + ".DbUnExportServer()") #----- PROTECTED REGION ID(DataBase.DbUnExportServer) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbUnExportServer #------------------------------------------------------------------ # ResetTimingValues command: #------------------------------------------------------------------ def ResetTimingValues(self): """ Reset the timing attribute values. :param : :type: PyTango.DevVoid :return: :rtype: PyTango.DevVoid """ self.debug_stream("In " + self.get_name() + ".ResetTimingValues()") #----- PROTECTED REGION ID(DataBase.ResetTimingValues) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.ResetTimingValues #------------------------------------------------------------------ # DbGetDataForServerCache command: #------------------------------------------------------------------ def DbGetDataForServerCache(self, argin): """ This command returns all the data needed by a device server process during its startup sequence. The aim of this command is to minimize database access during device server startup sequence. :param argin: Elt[0] = DS name (exec_name/inst_name), Elt[1] = Host name :type: PyTango.DevVarStringArray :return: All the data needed by the device server during its startup sequence. Precise list depend on the device server :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetDataForServerCache()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbGetDataForServerCache) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetDataForServerCache return argout #------------------------------------------------------------------ # DbDeleteAllDeviceAttributeProperty command: #------------------------------------------------------------------ def DbDeleteAllDeviceAttributeProperty(self, argin): """ Delete all attribute properties for the specified device attribute(s) :param argin: str[0] = device name Str[1]...str[n] = attribute name(s) :type: PyTango.DevVarStringArray :return: :rtype: PyTango.DevVoid """ self.debug_stream("In " + self.get_name() + ".DbDeleteAllDeviceAttributeProperty()") #----- PROTECTED REGION ID(DataBase.DbDeleteAllDeviceAttributeProperty) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbDeleteAllDeviceAttributeProperty #------------------------------------------------------------------ # DbMySqlSelect command: #------------------------------------------------------------------ def DbMySqlSelect(self, argin): """ This is a very low level command. It executes the specified SELECT command on TANGO database and returns its result without filter. :param argin: MySql Select command :type: PyTango.DevString :return: MySql Select command result - svalues : select results - lvalue[n] : =0 if svalue[n] is null else =1 (last lvalue -1) is number of rows, (last lvalue) is number of fields :rtype: PyTango.DevVarLongStringArray """ self.debug_stream("In " + self.get_name() + ".DbMySqlSelect()") argout = [0],[''] #----- PROTECTED REGION ID(DataBase.DbMySqlSelect) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbMySqlSelect return argout #------------------------------------------------------------------ # DbGetCSDbServerList command: #------------------------------------------------------------------ def DbGetCSDbServerList(self): """ Get a list of host:port for all database server defined in the control system :param : :type: PyTango.DevVoid :return: List of host:port with one element for each database server :rtype: PyTango.DevVarStringArray """ self.debug_stream("In " + self.get_name() + ".DbGetCSDbServerList()") argout = [''] #----- PROTECTED REGION ID(DataBase.DbGetCSDbServerList) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetCSDbServerList return argout #------------------------------------------------------------------ # DbGetAttributeAlias2 command: #------------------------------------------------------------------ def DbGetAttributeAlias2(self, argin): """ Get the attribute alias from the attribute name. Returns one empty string if nothing found in database :param argin: The attribute name (dev_name/att_name) :type: PyTango.DevString :return: The attribute alias name (or empty string) :rtype: PyTango.DevString """ self.debug_stream("In " + self.get_name() + ".DbGetAttributeAlias2()") argout = '' #----- PROTECTED REGION ID(DataBase.DbGetAttributeAlias2) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetAttributeAlias2 return argout #------------------------------------------------------------------ # DbGetAliasAttribute command: #------------------------------------------------------------------ def DbGetAliasAttribute(self, argin): """ Get the attribute name from the given alias. If the given alias is not found in database, returns an empty string :param argin: The attribute alias :type: PyTango.DevString :return: The attribute name (dev_name/att_name) :rtype: PyTango.DevString """ self.debug_stream("In " + self.get_name() + ".DbGetAliasAttribute()") argout = '' #----- PROTECTED REGION ID(DataBase.DbGetAliasAttribute) ENABLED START -----# #----- PROTECTED REGION END -----# // DataBase.DbGetAliasAttribute return argout #================================================================== # # DataBaseClass class definition # #================================================================== class DataBaseClass(PyTango.DeviceClass): # Class Properties class_property_list = { } # Device Properties device_property_list = { } # Command definitions cmd_list = { 'DbAddDevice': [[PyTango.DevVarStringArray, "Str[0] = Full device server process name\nStr[1] = Device name\nStr[2] = Tango class name"], [PyTango.DevVoid, "none"]], 'DbAddServer': [[PyTango.DevVarStringArray, "Str[0] = Full device server name\nStr[1] = Device(s) name\nStr[2] = Tango class name\nStr[n] = Device name\nStr[n + 1] = Tango class name"], [PyTango.DevVoid, "none"]], 'DbDeleteAttributeAlias': [[PyTango.DevString, "Attriibute alias name."], [PyTango.DevVoid, "none"]], 'DbDeleteClassAttribute': [[PyTango.DevVarStringArray, "Str[0] = Tango class name\nStr[1] = Attribute name"], [PyTango.DevVoid, "none"]], 'DbDeleteClassAttributeProperty': [[PyTango.DevVarStringArray, "Str[0] = Tango class name\nStr[1] = Attribute name\nStr[2] = Property name\nStr[n] = Property name"], [PyTango.DevVoid, "none"]], 'DbDeleteClassProperty': [[PyTango.DevVarStringArray, "Str[0] = Tango class name\nStr[1] = Property name\nStr[n] = Property name"], [PyTango.DevVoid, "none"]], 'DbDeleteDevice': [[PyTango.DevString, "device name"], [PyTango.DevVoid, "none"]], 'DbDeleteDeviceAlias': [[PyTango.DevString, "device alias name"], [PyTango.DevVoid, "none"]], 'DbDeleteDeviceAttribute': [[PyTango.DevVarStringArray, "Str[0] = Device name\nStr[1] = Attribute name"], [PyTango.DevVoid, "none"]], 'DbDeleteDeviceAttributeProperty': [[PyTango.DevVarStringArray, "Str[0] = Device name\nStr[1] = Attribute name\nStr[2] = Property name\nStr[n] = Property name"], [PyTango.DevVoid, "none"]], 'DbDeleteDeviceProperty': [[PyTango.DevVarStringArray, "Str[0] = Device name\nStr[1] = Property name\nStr[n] = Property name"], [PyTango.DevVoid, "none"]], 'DbDeleteProperty': [[PyTango.DevVarStringArray, "Str[0] = Object name\nStr[1] = Property name\nStr[n] = Property name"], [PyTango.DevVoid, "none"]], 'DbDeleteServer': [[PyTango.DevString, "Device server name"], [PyTango.DevVoid, "none"]], 'DbDeleteServerInfo': [[PyTango.DevString, "Device server name"], [PyTango.DevVoid, "none"]], 'DbExportDevice': [[PyTango.DevVarStringArray, "Str[0] = Device name\nStr[1] = CORBA IOR\nStr[2] = Device server process host name\nStr[3] = Device server process PID or string ``null``\nStr[4] = Device server process version"], [PyTango.DevVoid, "none"]], 'DbExportEvent': [[PyTango.DevVarStringArray, "Str[0] = event channel name (or factory name)\nStr[1] = CORBA IOR\nStr[2] = Notifd host name\nStr[3] = Notifd pid\nStr[4] = Notifd version"], [PyTango.DevVoid, "none"]], 'DbGetAliasDevice': [[PyTango.DevString, "Alias name"], [PyTango.DevString, "Device name"]], 'DbGetAttributeAlias': [[PyTango.DevString, "The attribute alias name"], [PyTango.DevString, "The attribute name (device/attribute)"]], 'DbGetAttributeAliasList': [[PyTango.DevString, "attribute alias filter string (eg: att*)"], [PyTango.DevVarStringArray, "attribute aliases"]], 'DbGetClassAttributeList': [[PyTango.DevVarStringArray, "Str[0] = Tango class name\nStr[1] = Attribute name filter (eg: att*)"], [PyTango.DevVarStringArray, "Str[0] = Class attribute name\nStr[n] = Class attribute name"]], 'DbGetClassAttributeProperty': [[PyTango.DevVarStringArray, "Str[0] = Tango class name\nStr[1] = Attribute name\nStr[n] = Attribute name"], [PyTango.DevVarStringArray, "Str[0] = Tango class name\nStr[1] = Attribute property number\nStr[2] = Attribute property 1 name\nStr[3] = Attribute property 1 value\nStr[n + 1] = Attribute property 2 name\nStr[n + 2] = Attribute property 2 value"]], 'DbGetClassAttributeProperty2': [[PyTango.DevVarStringArray, "Str[0] = Tango class name\nStr[1] = Attribute name\nStr[n] = Attribute name"], [PyTango.DevVarStringArray, "Str[0] = Tango class name\nStr[1] = Attribute property number\nStr[2] = Attribute property 1 name\nStr[3] = Attribute property 1 value number (array case)\nStr[4] = Attribute property 1 value\nStr[n] = Attribute property 1 value (array case)\nStr[n + 1] = Attribute property 2 name\nStr[n + 2] = Attribute property 2 value number (array case)\nStr[n + 3] = Attribute property 2 value\nStr[n + m] = Attribute property 2 value (array case)"]], 'DbGetClassAttributePropertyHist': [[PyTango.DevVarStringArray, "Str[0] = Tango class\nStr[1] = Attribute name\nStr[2] = Property name"], [PyTango.DevVarStringArray, "Str[0] = Attribute name\nStr[1] = Property name\nStr[2] = date\nStr[3] = Property value number (array case)\nStr[4] = Property value 1\nStr[n] = Property value n"]], 'DbGetClassForDevice': [[PyTango.DevString, "Device name"], [PyTango.DevString, "Device Tango class"]], 'DbGetClassInheritanceForDevice': [[PyTango.DevString, "Device name"], [PyTango.DevVarStringArray, "Classes off the specified device.\n[0] - is the class of the device.\n[1] - is the class from the device class is inherited.\n........and so on"]], 'DbGetClassList': [[PyTango.DevString, "Filter"], [PyTango.DevVarStringArray, "Class list"]], 'DbGetClassProperty': [[PyTango.DevVarStringArray, "Str[0] = Tango class\nStr[1] = Property name\nStr[2] = Property name"], [PyTango.DevVarStringArray, "Str[0] = Tango class\nStr[1] = Property number\nStr[2] = Property name\nStr[3] = Property value number (array case)\nStr[4] = Property value\nStr[n] = Propery value (array case)\n...."]], 'DbGetClassPropertyHist': [[PyTango.DevVarStringArray, "Str[0] = Tango class\nStr[1] = Property name"], [PyTango.DevVarStringArray, "Str[0] = Property name\nStr[1] = date\nStr[2] = Property value number (array case)\nStr[3] = Property value 1\nStr[n] = Property value n"]], 'DbGetClassPropertyList': [[PyTango.DevString, "The filter"], [PyTango.DevVarStringArray, "Property name list"]], 'DbGetDeviceAlias': [[PyTango.DevString, "The device name"], [PyTango.DevString, "The alias found"]], 'DbGetDeviceAliasList': [[PyTango.DevString, "The filter"], [PyTango.DevVarStringArray, "Device alias list"]], 'DbGetDeviceAttributeList': [[PyTango.DevVarStringArray, "Str[0] = Device name\nStr[1] = Wildcard"], [PyTango.DevVarStringArray, "attribute name list"]], 'DbGetDeviceAttributeProperty': [[PyTango.DevVarStringArray, "Str[0] = Device name\nStr[1] = Attribute name\nStr[n] = Attribute name"], [PyTango.DevVarStringArray, "Str[0] = Device name\nStr[1] = Attribute property number\nStr[2] = Attribute property 1 name\nStr[3] = Attribute property 1 value\nStr[n + 1] = Attribute property 2 name\nStr[n + 2] = Attribute property 2 value"]], 'DbGetDeviceAttributeProperty2': [[PyTango.DevVarStringArray, "Str[0] = Device name\nStr[1] = Attribute name\nStr[n] = Attribute name"], [PyTango.DevVarStringArray, "Str[0] = Device name\nStr[1] = Attribute property number\nStr[2] = Attribute property 1 name\nStr[3] = Attribute property 1 value number (array case)\nStr[4] = Attribute property 1 value\nStr[n] = Attribute property 1 value (array case)\nStr[n + 1] = Attribute property 2 name\nStr[n + 2] = Attribute property 2 value number (array case)\nStr[n + 3] = Attribute property 2 value\nStr[n + m] = Attribute property 2 value (array case)"]], 'DbGetDeviceAttributePropertyHist': [[PyTango.DevVarStringArray, "Str[0] = Device name\nStr[1] = Attribute name\nStr[2] = Property name"], [PyTango.DevVarStringArray, "Str[0] = Attribute name\nStr[1] = Property name\nStr[2] = date\nStr[3] = Property value number (array case)\nStr[4] = Property value 1\nStr[n] = Property value n"]], 'DbGetDeviceClassList': [[PyTango.DevString, "Device server process name"], [PyTango.DevVarStringArray, "Str[0] = Device name\nStr[1] = Tango class\nStr[n] = Device name\nStr[n + 1] = Tango class"]], 'DbGetDeviceDomainList': [[PyTango.DevString, "The wildcard"], [PyTango.DevVarStringArray, "Device name domain list"]], 'DbGetDeviceExportedList': [[PyTango.DevString, "filter"], [PyTango.DevVarStringArray, "list of exported devices"]], 'DbGetDeviceFamilyList': [[PyTango.DevString, "The wildcard"], [PyTango.DevVarStringArray, "Family list"]], 'DbGetDeviceInfo': [[PyTango.DevString, "Device name"], [PyTango.DevVarLongStringArray, "Str[0] = Device name\nStr[1] = CORBA IOR\nStr[2] = Device version\nStr[3] = Device Server name\nStr[4] = Device Server process host name\nStr[5] = Started date (or ? if not set)\nStr[6] = Stopped date (or ? if not set)\nStr[7] = Device class\n\nLg[0] = Device exported flag\nLg[1] = Device Server process PID (or -1 if not set)"]], 'DbGetDeviceList': [[PyTango.DevVarStringArray, "argin[0] : server name\nargin[1] : class name"], [PyTango.DevVarStringArray, "The list of devices for specified server and class."]], 'DbGetDeviceWideList': [[PyTango.DevString, "filter"], [PyTango.DevVarStringArray, "list of exported devices"]], 'DbGetDeviceMemberList': [[PyTango.DevString, "The filter"], [PyTango.DevVarStringArray, "Device names member list"]], 'DbGetDeviceProperty': [[PyTango.DevVarStringArray, "Str[0] = Device name\nStr[1] = Property name\nStr[n] = Property name"], [PyTango.DevVarStringArray, "Str[0] = Device name\nStr[1] = Property number\nStr[2] = Property name\nStr[3] = Property value number (array case)\nStr[4] = Property value 1\nStr[n] = Property value n (array case)\nStr[n + 1] = Property name\nStr[n + 2] = Property value number (array case)\nStr[n + 3] = Property value 1\nStr[n + m] = Property value m"]], 'DbGetDevicePropertyHist': [[PyTango.DevVarStringArray, "Str[0] = Device name\nStr[2] = Property name"], [PyTango.DevVarStringArray, "Str[0] = Property name\nStr[1] = date\nStr[2] = Property value number (array case)\nStr[3] = Property value 1\nStr[n] = Property value n"]], 'DbGetDevicePropertyList': [[PyTango.DevVarStringArray, "Str[0] = device name\nStr[1] = Filter"], [PyTango.DevVarStringArray, "Property name list"]], 'DbGetDeviceServerClassList': [[PyTango.DevString, "device server process name"], [PyTango.DevVarStringArray, "list of classes for this device server"]], 'DbGetExportdDeviceListForClass': [[PyTango.DevString, "Class name"], [PyTango.DevVarStringArray, "Device exported list"]], 'DbGetHostList': [[PyTango.DevString, "The filter"], [PyTango.DevVarStringArray, "Host name list"]], 'DbGetHostServerList': [[PyTango.DevString, "The filter"], [PyTango.DevVarStringArray, "Device server process name list"]], 'DbGetHostServersInfo': [[PyTango.DevString, "Host name"], [PyTango.DevVarStringArray, "Server info for all servers running on specified host"]], 'DbGetInstanceNameList': [[PyTango.DevString, "Server name"], [PyTango.DevVarStringArray, "The instance names found for specified server."]], 'DbGetObjectList': [[PyTango.DevString, "The filter"], [PyTango.DevVarStringArray, "Object name list"]], 'DbGetProperty': [[PyTango.DevVarStringArray, "Str[0] = Object name\nStr[1] = Property name\nStr[n] = Property name"], [PyTango.DevVarStringArray, "Str[0] = Object name\nStr[1] = Property number\nStr[2] = Property name\nStr[3] = Property value number (array case)\nStr[4] = Property value 1\nStr[n] = Property value n (array case)\nStr[n + 1] = Property name\nStr[n + 2] = Property value number (array case)\nStr[n + 3] = Property value 1\nStr[n + m] = Property value m"]], 'DbGetPropertyHist': [[PyTango.DevVarStringArray, "Str[0] = Object name\nStr[2] = Property name"], [PyTango.DevVarStringArray, "Str[0] = Property name\nStr[1] = date\nStr[2] = Property value number (array case)\nStr[3] = Property value 1\nStr[n] = Property value n"]], 'DbGetPropertyList': [[PyTango.DevVarStringArray, "Str[0] = Object name\nStr[1] = filter"], [PyTango.DevVarStringArray, "Property name list"]], 'DbGetServerInfo': [[PyTango.DevString, "server name"], [PyTango.DevVarStringArray, "server info"]], 'DbGetServerList': [[PyTango.DevString, "The filter"], [PyTango.DevVarStringArray, "Device server process name list"]], 'DbGetServerNameList': [[PyTango.DevString, "wildcard for server names."], [PyTango.DevVarStringArray, "server names found."]], 'DbImportDevice': [[PyTango.DevString, "Device name (or alias)"], [PyTango.DevVarLongStringArray, "Str[0] = device name\nStr[1] = CORBA IOR\nStr[2] = device version\nStr[3] = device server process name\nStr[4] = host name\nStr[5] = Tango class name\n\nLg[0] = Exported flag\nLg[1] = Device server process PID"]], 'DbImportEvent': [[PyTango.DevString, "name of event channel or factory"], [PyTango.DevVarLongStringArray, "export information e.g. IOR"]], 'DbInfo': [[PyTango.DevVoid, "none"], [PyTango.DevVarStringArray, "Miscellaneous info like:\n- Device defined in database\n- Device marked as exported in database\n- Device server process defined in database\n- Device server process marked as exported in database\n- Device properties defined in database\n- Class properties defined in database\n- Device attribute properties defined in database\n- Class attribute properties defined in database\n- Object properties defined in database"]], 'DbPutAttributeAlias': [[PyTango.DevVarStringArray, "Str[0] = attribute name\nStr[1] = attribute alias"], [PyTango.DevVoid, "none"]], 'DbPutClassAttributeProperty': [[PyTango.DevVarStringArray, "Str[0] = Tango class name\nStr[1] = Attribute number\nStr[2] = Attribute name\nStr[3] = Property number\nStr[4] = Property name\nStr[5] = Property value\n....."], [PyTango.DevVoid, "none"]], 'DbPutClassAttributeProperty2': [[PyTango.DevVarStringArray, "Str[0] = Tango class name\nStr[1] = Attribute number\nStr[2] = Attribute name\nStr[3] = Property number\nStr[4] = Property name\nStr[5] = Property value number (array case)\nStr[5] = Property value 1\nStr[n] = Property value n (array case)\n....."], [PyTango.DevVoid, "none"]], 'DbPutClassProperty': [[PyTango.DevVarStringArray, "Str[0] = Tango class name\nStr[1] = Property number\nStr[2] = Property name\nStr[3] = Property value number\nStr[4] = Property value 1\nStr[n] = Property value n\n...."], [PyTango.DevVoid, "none"]], 'DbPutDeviceAlias': [[PyTango.DevVarStringArray, "Str[0] = device name\nStr[1] = alias name"], [PyTango.DevVoid, "none"]], 'DbPutDeviceAttributeProperty': [[PyTango.DevVarStringArray, "Str[0] = Device name\nStr[1] = Attribute number\nStr[2] = Attribute name\nStr[3] = Property number\nStr[4] = Property name\nStr[5] = Property value\n....."], [PyTango.DevVoid, "none"]], 'DbPutDeviceAttributeProperty2': [[PyTango.DevVarStringArray, "Str[0] = Device name\nStr[1] = Attribute number\nStr[2] = Attribute name\nStr[3] = Property number\nStr[4] = Property name\nStr[5] = Property value number (array case)\nStr[5] = Property value 1\nStr[n] = Property value n (array case)\n....."], [PyTango.DevVoid, "none"]], 'DbPutDeviceProperty': [[PyTango.DevVarStringArray, "Str[0] = Tango device name\nStr[1] = Property number\nStr[2] = Property name\nStr[3] = Property value number\nStr[4] = Property value 1\nStr[n] = Property value n\n...."], [PyTango.DevVoid, "none"]], 'DbPutProperty': [[PyTango.DevVarStringArray, "Str[0] = Object name\nStr[1] = Property number\nStr[2] = Property name\nStr[3] = Property value number\nStr[4] = Property value 1\nStr[n] = Property value n\n...."], [PyTango.DevVoid, "none"]], 'DbPutServerInfo': [[PyTango.DevVarStringArray, "server info"], [PyTango.DevVoid, "none"]], 'DbUnExportDevice': [[PyTango.DevString, "Device name"], [PyTango.DevVoid, "none"]], 'DbUnExportEvent': [[PyTango.DevString, "name of event channel or factory to unexport"], [PyTango.DevVoid, "none"]], 'DbUnExportServer': [[PyTango.DevString, "Device server name (executable/instance)"], [PyTango.DevVoid, "none"]], 'ResetTimingValues': [[PyTango.DevVoid, "none"], [PyTango.DevVoid, "none"]], 'DbGetDataForServerCache': [[PyTango.DevVarStringArray, "Elt[0] = DS name (exec_name/inst_name), Elt[1] = Host name"], [PyTango.DevVarStringArray, "All the data needed by the device server during its startup sequence. Precise list depend on the device server"]], 'DbDeleteAllDeviceAttributeProperty': [[PyTango.DevVarStringArray, "str[0] = device name\nStr[1]...str[n] = attribute name(s)"], [PyTango.DevVoid, "none"]], 'DbMySqlSelect': [[PyTango.DevString, "MySql Select command"], [PyTango.DevVarLongStringArray, "MySql Select command result\n - svalues : select results\n - lvalue[n] : =0 if svalue[n] is null else =1\n (last lvalue -1) is number of rows, (last lvalue) is number of fields"]], 'DbGetCSDbServerList': [[PyTango.DevVoid, "none"], [PyTango.DevVarStringArray, "List of host:port with one element for each database server"]], 'DbGetAttributeAlias2': [[PyTango.DevString, "The attribute name (dev_name/att_name)"], [PyTango.DevString, "The attribute alias name (or empty string)"]], 'DbGetAliasAttribute': [[PyTango.DevString, "The attribute alias"], [PyTango.DevString, "The attribute name (dev_name/att_name)"]], } # Attribute definitions attr_list = { 'StoredProcedureRelease': [[PyTango.DevString, PyTango.SCALAR, PyTango.READ]], 'Timing_average': [[PyTango.DevDouble, PyTango.SPECTRUM, PyTango.READ, 64]], 'Timing_minimum': [[PyTango.DevDouble, PyTango.SPECTRUM, PyTango.READ, 64]], 'Timing_maximum': [[PyTango.DevDouble, PyTango.SPECTRUM, PyTango.READ, 64]], 'Timing_calls': [[PyTango.DevDouble, PyTango.SPECTRUM, PyTango.READ, 64]], 'Timing_index': [[PyTango.DevString, PyTango.SPECTRUM, PyTango.READ, 64]], 'Timing_info': [[PyTango.DevString, PyTango.SPECTRUM, PyTango.READ, 64]], } #------------------------------------------------------------------ # DataBaseClass Constructor #------------------------------------------------------------------ def __init__(self, name): PyTango.DeviceClass.__init__(self, name) self.set_type(name); print("In DataBase Class constructor") #================================================================== # # DataBase class main method # #================================================================== def main(): try: py = PyTango.Util(sys.argv) py.add_class(DataBaseClass,DataBase,'DataBase') U = PyTango.Util.instance() U.server_init() U.server_run() except PyTango.DevFailed as e: print('-------> Received a DevFailed exception:', e) except Exception as e: print('-------> An unforeseen exception occured....', e) if __name__ == '__main__': main() pytango-9.2.2/doc/tep/tep-0001.rst000066400000000000000000000455761316324373100164660ustar00rootroot00000000000000 .. currentmodule:: tango.server .. _pytango-TEP1: ==================================== TEP 1 - Device Server High Level API ==================================== ================== ==================================================== TEP: 1 ================== ==================================================== Title: Device Server High Level API Version: 2.2.0 Last-Modified: 10-Sep-2014 Author: Tiago Coutinho Status: Active Type: Standards Track Content-Type: text/x-rst Created: 17-Oct-2012 ================== ==================================================== Abstract ======== This TEP aims to define a new high level API for writting device servers. Rationale ========= The code for Tango device servers written in Python often obey a pattern. It would be nice if non tango experts could create tango device servers without having to code some obscure tango related code. It would also be nice if the tango programming interface would be more pythonic. The final goal is to make writting tango device servers as easy as:: class Motor(Device): __metaclass__ = DeviceMeta position = attribute() def read_position(self): return 2.3 @command() def move(self, position): pass if __name__ == "__main__": server_run((Motor,)) Places to simplify =================== After looking at most python device servers one can see some patterns: At `` class level: #. always inherits from latest available DeviceImpl from pogo version #. constructor always does the same: #. calls super constructor #. debug message #. calls init_device #. all methods have debug_stream as first instruction #. init_device does additionaly get_device_properties() #. *read attribute* methods follow the pattern:: def read_Attr(self, attr): self.debug_stream() value = get_value_from_hardware() attr.set_value(value) #. *write attribute* methods follow the pattern:: def write_Attr(self, attr): self.debug_stream() w_value = attr.get_write_value() apply_value_to_hardware(w_value) At `Class` class level: #. A Class class exists for every class #. The Class class only contains attributes, commands and properties descriptions (no logic) #. The attr_list description always follows the same (non explicit) pattern (and so does cmd_list, class_property_list, device_property_list) #. the syntax for attr_list, cmd_list, etc is far from understandable At `main()` level: #. The main() method always does the same: #. create `Util` #. register tango class #. when registering a python class to become a tango class, 99.9% of times the python class name is the same as the tango class name (example: Motor is registered as tango class "Motor") #. call `server_init()` #. call `server_run()` High level API =============== The goals of the high level API are: Maintain all features of low-level API available from high-level API -------------------------------------------------------------------------------- Everything that was done with the low-level API must also be possible to do with the new API. All tango features should be available by direct usage of the new simplified, cleaner high-level API and through direct access to the low-level API. Automatic inheritance from the latest** :class:`~tango.DeviceImpl` -------------------------------------------------------------------------------- Currently Devices need to inherit from a direct Tango device implementation (:class:`~tango.DeviceImpl`, or :class:`~tango.Device_2Impl`, :class:`~tango.Device_3Impl`, :class:`~tango.Device_4Impl`, etc) according to the tango version being used during the development. In order to keep the code up to date with tango, every time a new Tango IDL is released, the code of **every** device server needs to be manually updated to ihnerit from the newest tango version. By inheriting from a new high-level :class:`~tango.server.Device` (which itself automatically *decides* from which DeviceImpl version it should inherit), the device servers are always up to date with the latest tango release without need for manual intervention (see :mod:`tango.server`). Low-level way:: class Motor(PyTango.Device_4Impl): pass High-level way:: class Motor(PyTango.server.Device): pass Default implementation of :class:`~tango.server.Device` constructor -------------------------------------------------------------------------------- 99% of the different device classes which inherit from low level :class:`~tango.DeviceImpl` only implement `__init__` to call their `init_device` (see :mod:`tango.server`). :class:`~tango.server.Device` already calls init_device. Low-level way:: class Motor(PyTango.Device_4Impl): def __init__(self, dev_class, name): PyTango.Device_4Impl.__init__(self, dev_class, name) self.init_device() High-level way:: class Motor(PyTango.server.Device): # Nothing to be done! pass Default implementation of :meth:`~tango.server.Device.init_device` -------------------------------------------------------------------------------- 99% of different device classes which inherit from low level :class:`~tango.DeviceImpl` have an implementation of `init_device` which *at least* calls :meth:`~tango.DeviceImpl.get_device_properties` (see :mod:`tango.server`). :meth:`~tango.server.Device.init_device` already calls :meth:`~tango.server.Device.get_device_properties`. Low-level way:: class Motor(PyTango.Device_4Impl): def init_device(self): self.get_device_properties() High-level way:: class Motor(PyTango.server.Device): # Nothing to be done! pass Remove the need to code :class:`~tango.DeviceClass` -------------------------------------------------------------------------------- 99% of different device servers only need to implement their own subclass of :class:`~tango.DeviceClass` to register the attribute, commands, device and class properties by using the corresponding :obj:`~tango.DeviceClass.attr_list`, :obj:`~tango.DeviceClass.cmd_list`, :obj:`~tango.DeviceClass.device_property_list` and :obj:`~tango.DeviceClass.class_property_list`. With the high-level API we completely remove the need to code the :class:`~tango.DeviceClass` by registering attribute, commands, device and class properties in the :class:`~tango.server.Device` with a more pythonic API (see :mod:`tango.server`) #. Hide `Class` class completely #. simplify `main()` Low-level way:: class Motor(PyTango.Device_4Impl): def read_Position(self, attr): pass class MotorClass(PyTango.DeviceClass): class_property_list = { } device_property_list = { } cmd_list = { } attr_list = { 'Position': [[PyTango.DevDouble, PyTango.SCALAR, PyTango.READ]], } def __init__(self, name): PyTango.DeviceClass.__init__(self, name) self.set_type(name) High-level way:: class Motor(PyTango.server.Device): position = PyTango.server.attribute(dtype=float, ) def read_position(self): pass Pythonic read/write attribute -------------------------------------------------------------------------------- With the low level API, it feels strange for a non tango programmer to have to write:: def read_Position(self, attr): # ... attr.set_value(new_position) def read_Position(self, attr): # ... attr.set_value_date_quality(new_position, time.time(), AttrQuality.CHANGING) A more pythonic away would be:: def read_position(self): # ... self.position = new_position def read_position(self): # ... self.position = new_position, time.time(), AttrQuality.CHANGING Or even:: def read_position(self): # ... return new_position def read_position(self): # ... return new_position, time.time(), AttrQuality.CHANGING Simplify `main()` -------------------------------------------------------------------------------- the typical `main()` method could be greatly simplified. initializing tango, registering tango classes, initializing and running the server loop and managing errors could all be done with the single function call to :func:`~tango.server_run` Low-level way:: def main(): try: py = PyTango.Util(sys.argv) py.add_class(MotorClass,Motor,'Motor') U = PyTango.Util.instance() U.server_init() U.server_run() except PyTango.DevFailed,e: print '-------> Received a DevFailed exception:',e except Exception,e: print '-------> An unforeseen exception occured....',e High-level way:: def main(): classes = Motor, PyTango.server_run(classes) In practice =========== Currently, a pogo generated device server code for a Motor having a double attribute `position` would look like this:: #!/usr/bin/env python # -*- coding:utf-8 -*- ############################################################################## ## license : ##============================================================================ ## ## File : Motor.py ## ## Project : ## ## $Author : t$ ## ## $Revision : $ ## ## $Date : $ ## ## $HeadUrl : $ ##============================================================================ ## This file is generated by POGO ## (Program Obviously used to Generate tango Object) ## ## (c) - Software Engineering Group - ESRF ############################################################################## """""" __all__ = ["Motor", "MotorClass", "main"] __docformat__ = 'restructuredtext' import PyTango import sys # Add additional import #----- PROTECTED REGION ID(Motor.additionnal_import) ENABLED START -----# #----- PROTECTED REGION END -----# // Motor.additionnal_import ############################################################################## ## Device States Description ## ## No states for this device ############################################################################## class Motor (PyTango.Device_4Impl): #--------- Add you global variables here -------------------------- #----- PROTECTED REGION ID(Motor.global_variables) ENABLED START -----# #----- PROTECTED REGION END -----# // Motor.global_variables #------------------------------------------------------------------ # Device constructor #------------------------------------------------------------------ def __init__(self,cl, name): PyTango.Device_4Impl.__init__(self,cl,name) self.debug_stream("In " + self.get_name() + ".__init__()") Motor.init_device(self) #------------------------------------------------------------------ # Device destructor #------------------------------------------------------------------ def delete_device(self): self.debug_stream("In " + self.get_name() + ".delete_device()") #----- PROTECTED REGION ID(Motor.delete_device) ENABLED START -----# #----- PROTECTED REGION END -----# // Motor.delete_device #------------------------------------------------------------------ # Device initialization #------------------------------------------------------------------ def init_device(self): self.debug_stream("In " + self.get_name() + ".init_device()") self.get_device_properties(self.get_device_class()) self.attr_Position_read = 0.0 #----- PROTECTED REGION ID(Motor.init_device) ENABLED START -----# #----- PROTECTED REGION END -----# // Motor.init_device #------------------------------------------------------------------ # Always excuted hook method #------------------------------------------------------------------ def always_executed_hook(self): self.debug_stream("In " + self.get_name() + ".always_excuted_hook()") #----- PROTECTED REGION ID(Motor.always_executed_hook) ENABLED START -----# #----- PROTECTED REGION END -----# // Motor.always_executed_hook #================================================================== # # Motor read/write attribute methods # #================================================================== #------------------------------------------------------------------ # Read Position attribute #------------------------------------------------------------------ def read_Position(self, attr): self.debug_stream("In " + self.get_name() + ".read_Position()") #----- PROTECTED REGION ID(Motor.Position_read) ENABLED START -----# self.attr_Position_read = 1.0 #----- PROTECTED REGION END -----# // Motor.Position_read attr.set_value(self.attr_Position_read) #------------------------------------------------------------------ # Read Attribute Hardware #------------------------------------------------------------------ def read_attr_hardware(self, data): self.debug_stream("In " + self.get_name() + ".read_attr_hardware()") #----- PROTECTED REGION ID(Motor.read_attr_hardware) ENABLED START -----# #----- PROTECTED REGION END -----# // Motor.read_attr_hardware #================================================================== # # Motor command methods # #================================================================== #================================================================== # # MotorClass class definition # #================================================================== class MotorClass(PyTango.DeviceClass): # Class Properties class_property_list = { } # Device Properties device_property_list = { } # Command definitions cmd_list = { } # Attribute definitions attr_list = { 'Position': [[PyTango.DevDouble, PyTango.SCALAR, PyTango.READ]], } #------------------------------------------------------------------ # MotorClass Constructor #------------------------------------------------------------------ def __init__(self, name): PyTango.DeviceClass.__init__(self, name) self.set_type(name); print "In Motor Class constructor" #================================================================== # # Motor class main method # #================================================================== def main(): try: py = PyTango.Util(sys.argv) py.add_class(MotorClass,Motor,'Motor') U = PyTango.Util.instance() U.server_init() U.server_run() except PyTango.DevFailed,e: print '-------> Received a DevFailed exception:',e except Exception,e: print '-------> An unforeseen exception occured....',e if __name__ == '__main__': main() To make things more fair, let's analyse the stripified version of the code instead:: import PyTango import sys class Motor (PyTango.Device_4Impl): def __init__(self,cl, name): PyTango.Device_4Impl.__init__(self,cl,name) self.debug_stream("In " + self.get_name() + ".__init__()") Motor.init_device(self) def delete_device(self): self.debug_stream("In " + self.get_name() + ".delete_device()") def init_device(self): self.debug_stream("In " + self.get_name() + ".init_device()") self.get_device_properties(self.get_device_class()) self.attr_Position_read = 0.0 def always_executed_hook(self): self.debug_stream("In " + self.get_name() + ".always_excuted_hook()") def read_Position(self, attr): self.debug_stream("In " + self.get_name() + ".read_Position()") self.attr_Position_read = 1.0 attr.set_value(self.attr_Position_read) def read_attr_hardware(self, data): self.debug_stream("In " + self.get_name() + ".read_attr_hardware()") class MotorClass(PyTango.DeviceClass): class_property_list = { } device_property_list = { } cmd_list = { } attr_list = { 'Position': [[PyTango.DevDouble, PyTango.SCALAR, PyTango.READ]], } def __init__(self, name): PyTango.DeviceClass.__init__(self, name) self.set_type(name); print "In Motor Class constructor" def main(): try: py = PyTango.Util(sys.argv) py.add_class(MotorClass,Motor,'Motor') U = PyTango.Util.instance() U.server_init() U.server_run() except PyTango.DevFailed,e: print '-------> Received a DevFailed exception:',e except Exception,e: print '-------> An unforeseen exception occured....',e if __name__ == '__main__': main() And the equivalent HLAPI version of the code would be:: #!/usr/bin/env python from PyTango import DebugIt, server_run from PyTango.server import Device, DeviceMeta, attribute class Motor(Device): __metaclass__ = DeviceMeta position = attribute() @DebugIt() def read_position(self): return 1.0 def main(): server_run((Motor,)) if __name__ == "__main__": main() References ========== :mod:`tango.server` Changes ======= from 2.1.0 to 2.2.0 ------------------- Changed module name from *hlapi* to *server* from 2.0.0 to 2.1.0 ------------------- Changed module name from *api2* to *hlapi* (High Level API) From 1.0.0 to 2.0.0 ------------------- * API Changes * changed Attr to attribute * changed Cmd to command * changed Prop to device_property * changed ClassProp to class_property * Included command and properties in the example * Added references to API documentation Copyright ========= This document has been placed in the public domain. pytango-9.2.2/doc/tep/tep-0002.rst000066400000000000000000000414651316324373100164600ustar00rootroot00000000000000 .. currentmodule:: tango.databaseds .. _pytango-TEP2: =================================================== TEP 2 - Tango database serverless =================================================== ================== ==================================================== TEP: 2 ================== ==================================================== Title: Tango database serverless Version: 1.0.0 Last-Modified: 17-Oct-2012 Author: Tiago Coutinho Status: Active Type: Standards Track Content-Type: text/x-rst Created: 17-Oct-2012 Post-History: 17-Oct-2012 ================== ==================================================== Abstract ======== This TEP aims to define a python DataBaseds which doesn't need a database server behind. It would make tango easier to try out by anyone and it could greatly simplify tango installation on small environments (like small, independent laboratories). Motivation ========== I was given a openSUSE laptop so that I could do the presentation for the tango meeting held in FRMII on October 2012. Since I planned to do a demonstration as part of the presentation I installed all mysql libraries, omniorb, tango and pytango on this laptop. During the flight to Munich I realized tango was not working because of a strange mysql server configuration done by the openSUSE distribution. I am not a mysql expert and I couldn't google for a solution. Also it made me angry to have to install all the mysql crap (libmysqlclient, mysqld, mysql-administrator, bla, bla) just to have a demo running. At the time of writting the first version of this TEP I still didn't solve the problem! Shame on me! Also at the same tango meetting during the tango archiving discussions I heard fake whispers or changing the tango archiving from MySQL/Oracle to NoSQL. I started thinking if it could be possible to have an alternative implementation of DataBaseds without the need for a mysql server. Requisites ========== * no dependencies on external packages * no need for a separate database server process (at least, by default) * no need to execute post install scripts to fill database Step 1 - Gather database information ===================================== It turns out that python has a Database API specification (:pep:`249`). Python distribution comes natively (>= 2.6) with not one but several persistency options (:ref:`persistence`): +-----------------+--------+---------------+------------+---------------------+-------------------------------------------------------------------------+ | module | Native | Platforms | API | Database | Description | +=================+========+===============+============+=====================+=========================================================================+ | **Native python 2.x** | +-----------------+--------+---------------+------------+---------------------+-------------------------------------------------------------------------+ | :mod:`pickle` | Yes | all | dump/load | file | python serialization/marchalling module | +-----------------+--------+---------------+------------+---------------------+-------------------------------------------------------------------------+ | :mod:`shelve` | Yes | all | dict | file | high level persistent, dictionary-like object | +-----------------+--------+---------------+------------+---------------------+-------------------------------------------------------------------------+ | :mod:`marshal` | Yes | all | dump/load | file | Internal Python object serialization | +-----------------+--------+---------------+------------+---------------------+-------------------------------------------------------------------------+ | :mod:`anydbm` | Yes | all | dict | file | Generic access to DBM-style databases. Wrapper for :mod:`dbhash`, | | | | | | | :mod:`gdbm`, :mod:`dbm` or :mod:`dumbdbm` | +-----------------+--------+---------------+------------+---------------------+-------------------------------------------------------------------------+ | :mod:`dbm` | Yes | all | dict | file | Simple "database" interface | +-----------------+--------+---------------+------------+---------------------+-------------------------------------------------------------------------+ | :mod:`gdbm` | Yes | unix | dict | file | GNU's reinterpretation of dbm | +-----------------+--------+---------------+------------+---------------------+-------------------------------------------------------------------------+ | :mod:`dbhash` | Yes | unix? | dict | file | DBM-style interface to the BSD database library (needs :mod:`bsddb`). | | | | | | | **Removed in python 3** | +-----------------+--------+---------------+------------+---------------------+-------------------------------------------------------------------------+ | :mod:`bsddb` | Yes | unix? | dict | file | Interface to Berkeley DB library. **Removed in python 3** | +-----------------+--------+---------------+------------+---------------------+-------------------------------------------------------------------------+ | :mod:`dumbdbm` | Yes | all | dict | file | Portable DBM implementation | +-----------------+--------+---------------+------------+---------------------+-------------------------------------------------------------------------+ | :mod:`sqlite3` | Yes | all | DBAPI2 | file, memory | DB-API 2.0 interface for SQLite databases | +-----------------+--------+---------------+------------+---------------------+-------------------------------------------------------------------------+ | **Native Python 3.x** | +-----------------+--------+---------------+------------+---------------------+-------------------------------------------------------------------------+ | :mod:`pickle` | Yes | all | dump/load | file | python serialization/marchalling module | +-----------------+--------+---------------+------------+---------------------+-------------------------------------------------------------------------+ | :mod:`shelve` | Yes | all | dict | file | high level persistent, dictionary-like object | +-----------------+--------+---------------+------------+---------------------+-------------------------------------------------------------------------+ | :mod:`marshal` | Yes | all | dump/load | file | Internal Python object serialization | +-----------------+--------+---------------+------------+---------------------+-------------------------------------------------------------------------+ | :mod:`dbm` | Yes | all | dict | file | Interfaces to Unix "databases". Wrapper for :mod:`dbm.gnu`, | | | | | | | :mod:`dbm.ndbm`, :mod:`dbm.dumb` | +-----------------+--------+---------------+------------+---------------------+-------------------------------------------------------------------------+ | :mod:`dbm.gnu` | Yes | unix | dict | file | GNU's reinterpretation of dbm | +-----------------+--------+---------------+------------+---------------------+-------------------------------------------------------------------------+ | :mod:`dbm.ndbm` | Yes | unix | dict | file | Interface based on ndbm | +-----------------+--------+---------------+------------+---------------------+-------------------------------------------------------------------------+ | :mod:`dbm.dumb` | Yes | all | dict | file | Portable DBM implementation | +-----------------+--------+---------------+------------+---------------------+-------------------------------------------------------------------------+ | :mod:`sqlite3` | Yes | all | DBAPI2 | file, memory | DB-API 2.0 interface for SQLite databases | +-----------------+--------+---------------+------------+---------------------+-------------------------------------------------------------------------+ **third-party DBAPI2** .. hlist:: :columns: 1 * `pyodbc `_ * `mxODBC `_ * `kinterbasdb `_ * `mxODBC Connect `_ * `MySQLdb `_ * `psycopg `_ * `pyPgSQL `_ * `PySQLite `_ * `adodbapi `_ * `pymssql `_ * `sapdbapi `_ * `ibm_db `_ * `InformixDB `_ **third-party NOSQL** *(these may or not have python DBAPI2 interface)* * `CouchDB `_ - :mod:`couchdb.client` * `MongoDB `_ - :mod:`pymongo` - NoSQL database * `Cassandra `_ - :mod:`pycassa` **third-party database abstraction layer** * `SQLAlchemy `_ - :mod:`sqlalchemy` - Python SQL toolkit and Object Relational Mapper Step 2 - Which module to use? ===================================== *herrrr... wrong question!* The first decision I thought it should made is which python module better suites the needs of this TEP. Then I realized I would fall into the same trap as the C++ DataBaseds: hard link the server to a specific database implementation (in their case MySQL). I took a closer look at the tables above and I noticed that python persistent modules come in two flavors: dict and DBAPI2. So naturally the decision I thought it had to be made was: *which flavor to use?* But then I realized both flavors could be used if we properly design the python DataBaseds. Step 3 - Architecture ===================================== If you step back for a moment and look at the big picture you will see that what we need is really just a mapping between the Tango DataBase set of attributes and commands (I will call this `Tango Device DataBase API`) and the python database API oriented to tango (I will call this TDB interface). The TDB interface should be represented by the :class:`ITangoDB`. Concrete databases should implement this interface (example, DBAPI2 interface should be represented by a class :class:`TangoDBAPI2` implementing :class:`ITangoDB`). Connection to a concrete ITangoDB should be done through a factory: :class:`TangoDBFactory` The Tango DataBase device should have no logic. Through basic configuration it should be able to ask the :class:`TangoDBFactory` for a concrete :class:`ITangoDB`. The code of every command and attribute should be simple forward to the :class:`ITangoDB` object (a part of some parameter translation and error handling). .. graphviz:: digraph uml { fontname = "Bitstream Vera Sans" fontsize = 8 node [ fontname = "Bitstream Vera Sans" fontsize = 8 shape = "record" ] edge [ fontname = "Bitstream Vera Sans" fontsize = 8 ] subgraph tangodbPackage { label = "Package tangodb" ITangoDB [ label = "{ITangoDB|+ add_device()=0\l+delete_device()=0\l+export_device()=0\l...}" ] DBAPI2 [ label = "{TangoDBAPI2}" ] Dict [ label = "{TangoDBDict}" ] DBSqlite3 [ label = "{TangoDBSqlite3}" ] mxODBC [ label = "{TangoDBmxODBC}" ] MySQLdb [ label = "{TangoDBMySQLdb}" ] Shelve [ label = "{TangoDBShelve}" ] TangoDBFactory [ label = "{TangoDBFactory|+get_tango_db(): ITangoDB}" ] DBAPI2 -> ITangoDB Dict -> ITangoDB DBSqlite3 -> DBAPI2 mxODBC -> DBAPI2 MySQLdb -> DBAPI2 Shelve -> Dict } DeviceImpl [ label = "{Tango::DeviceImpl}" ] DataBase [ label = "{DataBase|+DbAddDevice()\l+DbDeleteDevice()\l+DbExportDevice()\l...}" ] DataBase -> DeviceImpl } Step 4 - The python DataBaseds ===================================== If we can make a python device server which has the same set of attributes and commands has the existing C++ DataBase (and of course the same semantic behavior), the tango DS and tango clients will never know the difference (BTW, that's one of the beauties of tango). The C++ DataBase consists of around 80 commands and 1 mandatory attribute (the others are used for profiling) so making a python Tango DataBase device from scratch is out of the question. Fortunately, C++ DataBase is one of the few device servers that were developed since the beginning with pogo and were successfully adapted to pogo 8. This means there is a precious :download:`DataBase.xmi` available which can be loaded to pogo and saved as a python version. The result of doing this can be found here :download:`here ` (this file was generated with a beta version of the pogo 8.1 python code generator so it may contain errors). Step 5 - Default database implementation =========================================== The decision to which database implementation should be used should obey the following rules: #. should not require an extra database server process #. should be a native python module #. should implement python DBAPI2 It came to my attention the :mod:`sqlite3` module would be perfect as a default database implementation. This module comes with python since version 2.5 and is available in all platforms. It implements the DBAPI2 interface and can store persistently in a common OS file or even in memory. There are many free scripts on the web to translate a mysql database to sqlite3 so one can use an existing mysql tango database and directly use it with the python DataBaseds with sqlite3 implementation. Development ================= The development is being done in PyTango SVN trunk in the :mod:`tango.databaseds` module. You can checkout with:: $ svn co https://tango-cs.svn.sourceforge.net/svnroot/tango-cs/bindings/PyTango/trunk PyTango-trunk Disadvantages =============== A serverless, file based, database has some disadvantages when compared to the mysql solution: * Not possible to distribute load between Tango DataBase DS and database server (example: run the Tango DS in one machine and the database server in another) * Not possible to have two Tango DataBase DS pointing to the same database * Harder to upgrade to newer version of sql tables (specially if using dict based database) Bare in mind the purpose of this TED is to simplify the process of trying tango and to ease installation and configuration on small environments (like small, independent laboratories). References ============ * http://wiki.python.org/moin/DbApiCheatSheet * http://wiki.python.org/moin/DbApiModuleComparison * http://wiki.python.org/moin/DatabaseProgramming * http://wiki.python.org/moin/DbApiFaq * :pep:`249` * http://wiki.python.org/moin/ExtendingTheDbApi * http://wiki.python.org/moin/DbApi3 pytango-9.2.2/doc/utilities.rst000066400000000000000000000020211316324373100165150ustar00rootroot00000000000000.. _utilities: The Utilities API ================= .. currentmodule:: tango.utils .. autoclass:: tango.utils.EventCallback :members: :undoc-members: .. autofunction:: tango.utils.is_pure_str .. autofunction:: tango.utils.is_seq .. autofunction:: tango.utils.is_non_str_seq .. autofunction:: tango.utils.is_integer .. autofunction:: tango.utils.is_number .. autofunction:: tango.utils.is_bool .. autofunction:: tango.utils.is_scalar_type .. autofunction:: tango.utils.is_array_type .. autofunction:: tango.utils.is_numerical_type .. autofunction:: tango.utils.is_int_type .. autofunction:: tango.utils.is_float_type .. autofunction:: tango.utils.is_bool_type .. autofunction:: tango.utils.is_binary_type .. autofunction:: tango.utils.is_str_type .. autofunction:: tango.utils.obj_2_str .. autofunction:: tango.utils.seqStr_2_obj .. autofunction:: tango.utils.scalar_to_array_type .. autofunction:: tango.utils.get_home .. autofunction:: tango.utils.requires_pytango .. autofunction:: tango.utils.requires_tango pytango-9.2.2/doc/windows_notes.txt000066400000000000000000000151211316324373100174200ustar00rootroot00000000000000Windows ~~~~~~~ On windows, PyTango must be built using MS VC++. ... which is good fun specially if you only have express edition and multiple python versions to build with! .. warning:: The next chapters are internal notes I have gathered along the years to overcome windows limitations/problems in order to be able to compile PyTango. They are not for the weak of heart, seriously! Pre-requisites ############## **Python** Python must be installed in the following directory structure: \\\\ Where: - is a python base directory choosen by you (ex: :file:`C:\\Python`) - **must** be either *win32* or *x64* - **must** be *26*, *27*, *31*, *32*, *33* Example: Assuming you choose *C:\\Python* as PythonBaseDir, if you want to build PyTango for python 2.7 on 64 bits you must install python in :file:`C:\\python\\x64\\27`. **Visual C++** Python recommends compiling any python libraries using the same compiler version. So, depending on the python version(s) you want PyTango to be build, you need VC++ 9.0 (2008) or/and VC++ 10.0 (2010). Here is the table of compilers and corresponding Visual C++ version used by CPython: +----------------------+--------------+ | Visual C++ version | Compiler | +======================+==============+ | Visual C++ 4.x | MSC_VER=1000 | +----------------------+--------------+ | Visual C++ 5 | MSC_VER=1100 | +----------------------+--------------+ | Visual C++ 6 | MSC_VER=1200 | +----------------------+--------------+ | Visual C++ .NET | MSC_VER=1300 | +----------------------+--------------+ | Visual C++ .NET 2003 | MSC_VER=1310 | +----------------------+--------------+ | Visual C++ 2005 | MSC_VER=1400 | +----------------------+--------------+ | Visual C++ 2008 | MSC_VER=1500 | +----------------------+--------------+ | Visual C++ 2010 | MSC_VER=1600 | +----------------------+--------------+ | Visual C++ 2011 | MSC_VER=1700 | +----------------------+--------------+ +----------+--------------+-----------------------------+ | version | architecture | VC++ | +==========+==============+=============================+ | 2.6.6 | 32 bits | MSC 1500 (Visual C++ 2008) | +----------+--------------+-----------------------------+ | 2.6.6 | 64 bits | MSC 1500 (Visual C++ 2008) | +----------+--------------+-----------------------------+ | 2.7.3 | 32 bits | MSC 1500 (Visual C++ 2008) | +----------+--------------+-----------------------------+ | 2.7.3 | 64 bits | MSC 1500 (Visual C++ 2008) | +----------+--------------+-----------------------------+ | 3.1.4 | 32 bits | MSC 1500 (Visual C++ 2008) | +----------+--------------+-----------------------------+ | 3.1.4 | 64 bits | MSC 1500 (Visual C++ 2008) | +----------+--------------+-----------------------------+ | 3.2.3 | 32 bits | MSC 1500 (Visual C++ 2008) | +----------+--------------+-----------------------------+ | 3.2.3 | 64 bits | MSC 1500 (Visual C++ 2008) | +----------+--------------+-----------------------------+ | 3.3.0 | 32 bits | MSC 1600 (Visual C++ 2010) | +----------+--------------+-----------------------------+ | 3.3.0 | 64 bits | MSC 1600 (Visual C++ 2010) | +----------+--------------+-----------------------------+ **Visual C++ 9.0 (2008) express quick install guide** 1. Download and install VC++ 9.0 (2008) Express Edition 2. If you need to compile in 64 bits platform 2.1. Download and install Windows 7 SDK for .NET Framework 3.5 SP1 [a.k.a. Windows SDK 7.0] (**not** Windows SDK 7.1!) 2.2. The vcvarsall.bat in VC++ 2008 Express looks for the x86_amd64 :file:`vcvarsx86_amd64.bat` in all the wrong places. The easiest way to work around that is to navigate to the :file:`VC\\bin` directory of your VC++ 2008 installation (in my case :file:`C:\\Program Files (x86)\\Microsoft Visual Studio 9.0\\VC\\bin`). Copy :file:`vcvarsx86_amd64.bat`, and paste into the :file:`VC\\bin\\x86_amd64 subdirectory`. **Visual C++ 10.0 (2010) express quick install guide** 1. Download and install VC++ 10.0 (2010) Express Edition 2. If you need to compile in 64 bits platform follow the instructions **in the order they appear** (not doing so may lead to `KB2519277 `_ problem) 2.1. Visual Studio 2010 SP1 2.2. Download and install Windows 7 SDK for .NET Framework 4.0 [a.k.a. Windows SDK 7.1] 2.3. VC++ 2010 doesn't come with vcvarsx86_amd64.bat. But in this case, since the environment setting tool is different than in VC++ 2008, all you have to do is create a file called vcvarsx86_amd64.bat in VC\\bin\\x86_amd64 directory of your VC++ 2010 installation (in my case C:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\bin\\x86_amd64) with the following content: @CALL "C:\\Program Files\\Microsoft SDKs\\Windows\\v7.1\\Bin\\SetEnv.Cmd" /Release /x64 (adapt to your Windows SDK installation directory) **Boost python** Boost python DLL, lib and header files must be installed for the specific platform(s) and python version(s) you which to build PyTango on. The directory structure for the boost headers: \\include The directory structure for the boost libraries: \\multi\\release\\\\\\\\\\ Where: - the boost base directory (ex: C:\Boost-1.53.0) - may be either *msvc-9.0* or *msvc-10.0* - **must** be either *win32* or *x64* - **must** be either *static* or *shared* - **must** be either *static* or *shared* (if Link==static, RuntimeLink can only be *static*) - **must** be *26*, *27*, *31*, *32*, *33* **Boost python multi platform compilation quick build guide** - Download boost source code from http://wwww.boost.org - Extract boost to a directory (ex: c:\\workspace\\boost-1.53.0) - Download and place `boost_python_install.py <>` in your boost extract directory (ex: c:\\workspace\\boost-1.53.0\\boost_python_install.py) (adapt python versions you which to build) - Place the user-config.jam file in %HOMEPATH%%HOMEDIR% (adapt paths and python versions to your system) - Open a console - Switch to the boost directory - Execute this script using python (ex: C:\\Python\\win32\\26\\python.exe boost_python_install.py) **Tango** TODO pytango-9.2.2/examples/000077500000000000000000000000001316324373100150265ustar00rootroot00000000000000pytango-9.2.2/examples/Clock/000077500000000000000000000000001316324373100160615ustar00rootroot00000000000000pytango-9.2.2/examples/Clock/Clock.py000066400000000000000000000004741316324373100174730ustar00rootroot00000000000000import time from PyTango.server import Device, attribute, command class Clock(Device): @attribute def time(self): return time.time() @command(dtype_in=str, dtype_out=str) def strftime(self, format): return time.strftime(format) if __name__ == "__main__": Clock.run_server() pytango-9.2.2/examples/Clock/ClockDS.py000066400000000000000000000021671316324373100177230ustar00rootroot00000000000000#!/usr/bin/env python """ Clock Device server showing how to write a TANGO server with a Clock device which has attributes: - time: read-only scalar float - gmtime: read-only sequence (spectrum) of integers commands: - ctime: in: float parameter; returns a string - mktime: in: sequence (spectrum) of 9 integers; returns a float """ import time from PyTango.server import Device, attribute, command class Clock(Device): @attribute(dtype=float) def time(self): return time.time() gmtime = attribute(dtype=(int,), max_dim_x=9) def read_gmtime(self): return time.gmtime() @command(dtype_in=float, dtype_out=str) def ctime(self, seconds): """ Convert a time in seconds since the Epoch to a string in local time. This is equivalent to asctime(localtime(seconds)). When the time tuple is not present, current time as returned by localtime() is used. """ return time.ctime(seconds) @command(dtype_in=(int,), dtype_out=float) def mktime(self, tupl): return time.mktime(tupl) if __name__ == "__main__": Clock.run_server() pytango-9.2.2/examples/Clock/client.py000066400000000000000000000016141316324373100177130ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2013-2015 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ """ Simple client to show how to connect to a Clock device from ClockDS usage: client clock_dev_name """ import sys import PyTango if len(sys.argv) != 2: print "must provide one and only one clock device name" sys.exit(1) clock = PyTango.DeviceProxy(sys.argv[1]) t = clock.time gmt = clock.gmtime print(t) print(gmt) print(clock.ctime(t)) print(clock.mktime(gmt)) pytango-9.2.2/examples/TuringMachine/000077500000000000000000000000001316324373100175635ustar00rootroot00000000000000pytango-9.2.2/examples/TuringMachine/TuringMachine.py000066400000000000000000000040171316324373100226740ustar00rootroot00000000000000import json from PyTango import DevState from PyTango.server import Device from PyTango.server import attribute, command, device_property class TuringMachine(Device): blank_symbol = device_property(dtype=str, default_value=" ") initial_state = device_property(dtype=str, default_value="init") def init_device(self): Device.init_device(self) self.__tape = {} self.__head = 0 self.__state = self.initial_state self.__final_states = [] self.__transition_function = None self.set_state(DevState.RUNNING) @attribute(dtype=(str,)) def final_states(self): return self.__final_states @final_states.write def final_states(self, final_states): self.__final_states = final_states @attribute(dtype=str) def transition_function(self): return self.__transition_function @transition_function.write def transition_function(self, func_str): self.__transition_function = tf = {} for k, v in json.loads(func_str).items(): tf[tuple(str(k).split(","))] = map(str, v) print(tf) @attribute(dtype=str) def tape(self): s, keys = "", self.__tape.keys() min_used, max_used = min(keys), max(keys) for i in range(min_used, max_used): s += self.__tape.get(i, self.__blank_symbol) return s @command def step(self): char_under_head = self.__tape.get(self.__head, self.blank_symbol) x = self.__state, char_under_head if x in self.__transition_function: y = self.__transition_function[x] self.__tape[self.__head] = y[1] if y[2] == "R": self.__head += 1 elif y[2] == "L": self.__head -= 1 self.__state = y[0] print(self.__state) def dev_state(self): if self.__state in self.__final_states: return DevState.ON else: return DevState.RUNNING if __name__ == "__main__": TuringMachine.run_server() pytango-9.2.2/examples/TuringMachine/turing_client.py000066400000000000000000000010001316324373100227720ustar00rootroot00000000000000from turing_machine import TuringMachine transition_function = {("init","0"):("init", "1", "R"), ("init","1"):("init", "0", "R"), ("init"," "):("final"," ", "N"), } t = TuringMachine("010011 ", final_states=["final"], transition_function=transition_function) print("Input on Tape:") print(t.get_tape_str()) while not t.final(): t.step() print("Result of the Turing machine calculation:") print(t.get_tape_str()) pytango-9.2.2/examples/asyncio_green_mode/000077500000000000000000000000001316324373100206575ustar00rootroot00000000000000pytango-9.2.2/examples/asyncio_green_mode/__init__.py000066400000000000000000000000451316324373100227670ustar00rootroot00000000000000__all__ = ['asyncio_device_example'] pytango-9.2.2/examples/asyncio_green_mode/asyncio_device_example.py000066400000000000000000000014421316324373100257310ustar00rootroot00000000000000"""Demo Tango Device Server using asyncio green mode""" import asyncio from tango import DevState, GreenMode from tango.server import Device, command, attribute class AsyncioDevice(Device): green_mode = GreenMode.Asyncio async def init_device(self): await super().init_device() self.set_state(DevState.ON) @command async def long_running_command(self): loop = asyncio.get_event_loop() future = loop.create_task(self.coroutine_target()) async def coroutine_target(self): self.set_state(DevState.INSERT) await asyncio.sleep(15) self.set_state(DevState.EXTRACT) @attribute async def test_attribute(self): await asyncio.sleep(2) return 42 if __name__ == '__main__': AsyncioDevice.run_server() pytango-9.2.2/examples/asyncio_green_mode/asyncio_simple_example.py000066400000000000000000000004271316324373100257650ustar00rootroot00000000000000import asyncio from tango.asyncio import DeviceProxy async def asyncio_example(): dev = await DeviceProxy("sys/tg_test/1") print(dev.get_green_mode()) print(await dev.state()) loop = asyncio.get_event_loop() loop.run_until_complete(asyncio_example()) loop.close() pytango-9.2.2/examples/asyncio_green_mode/tcp_server_example.py000066400000000000000000000035361316324373100251270ustar00rootroot00000000000000"""A simple TCP server for Tango attributes. It runs on all interfaces on port 8888: $ python tango_tcp_server.py Serving on 0.0.0.0 port 8888 It can be accessed using netcat: $ ncat localhost 8888 >>> sys/tg_test/1/ampli 0.0 >>> sys/tg_test/1/state RUNNING >>> sys/tg_test/1/nope DevFailed[ DevError[ desc = Attribute nope is not supported by device sys/tg_test/1 origin = AttributeProxy::real_constructor() reason = API_UnsupportedAttribute severity = ERR] ] >>> ... """ import asyncio from tango.asyncio import AttributeProxy async def handle_echo(reader, writer): # Write the cursor writer.write(b'>>> ') # Loop over client request async for line in reader: request = line.decode().strip() # Get attribute value using asyncio green mode try: proxy = await AttributeProxy(request) attr_value = await proxy.read() reply = str(attr_value.value) # Catch exception if something goes wrong except Exception as exc: reply = str(exc) # Reply to client writer.write(reply.encode() + b'\n' + b'>>> ') # Close communication writer.close() async def start_serving(): server = await asyncio.start_server(handle_echo, '0.0.0.0', 8888) print('Serving on {} port {}'.format(*server.sockets[0].getsockname())) return server async def stop_serving(server): server.close() await server.wait_closed() def main(): # Start the server loop = asyncio.get_event_loop() server = loop.run_until_complete(start_serving()) # Serve requests until Ctrl+C is pressed try: loop.run_forever() except KeyboardInterrupt: pass # Close the server loop.run_until_complete(stop_serving(server)) loop.close() if __name__ == '__main__': main() pytango-9.2.2/examples/dynamic/000077500000000000000000000000001316324373100164525ustar00rootroot00000000000000pytango-9.2.2/examples/dynamic/common/000077500000000000000000000000001316324373100177425ustar00rootroot00000000000000pytango-9.2.2/examples/dynamic/common/__init__.py000066400000000000000000000000001316324373100220410ustar00rootroot00000000000000pytango-9.2.2/examples/dynamic/common/roi.py000066400000000000000000000004371316324373100211110ustar00rootroot00000000000000class ROI: def __init__(self, x, y, w, h): self.x = x self.y = y self.w = w self.h = h def __repr__(self): return str(self) def __str__(self): txt = "ROI(x={o.x}, y={o.y}, w={o.w}, h={o.h})".format(o=self) return txt pytango-9.2.2/examples/dynamic/dynamic_client.py000066400000000000000000000013171316324373100220100ustar00rootroot00000000000000import PyTango.client my_object = PyTango.client.Object("my_object") print("my_object.bla = {0}".format(my_object.bla)) print("my_object.ble = {0}".format(my_object.ble)) print("my_object.bli = {0}".format(my_object.bli)) print("my_object.array = {0}".format(my_object.array)) r1 = my_object.func1() print("my_object.func1() = {0}".format(r1)) r2 = my_object.func2(96.44) print("my_object.func2(96.44) = {0}".format(r2)) r3 = my_object.func3(45.86, 'hello', d=False, c='world') print("my_object.func3(45.86, 'hello', d=False, c='world') = {0}".format(r3)) r4 = my_object.func4() print("my_object.func4() = {0}".format(r4)) r5 = my_object.zeros((500, 1000)) print("my_object.zeros((500, 1000)) = {0}".format(r5)) pytango-9.2.2/examples/dynamic/dynamic_server.py000066400000000000000000000016641316324373100220450ustar00rootroot00000000000000import numpy from PyTango.server import Server from common.roi import ROI class MyClass: def __init__(self): self.bla = 55.6 self.ble = 11 self.bli = False self.array = numpy.ones((1000,1000)) self.buff = bytearray(100000*"Hello ") self.__rois = {} def func1(self): return "executed func1" def func2(self, v): return 2*v def func3(self, a, b, c=1, d=3): """Just some documentation""" return "done func3" def add_roi(self, name, roi): self.__rois[name] = roi server.register_object(roi, name) def remove_roi(self, name): del self.__rois[name] # no need to unregister object import logging logging.basicConfig(level=logging.DEBUG) my_object = MyClass() a_roi = ROI(0,0,0,0) server = Server("Dynamic") server.register_object(my_object, "dynamic_object") server.register_object(a_roi, "dummy_roi") server.run() pytango-9.2.2/examples/fwdAttr/000077500000000000000000000000001316324373100164415ustar00rootroot00000000000000pytango-9.2.2/examples/fwdAttr/FwdServer.py000066400000000000000000000015621316324373100207260ustar00rootroot00000000000000import tango from tango.server import Device from tango.server import attribute __all__ = ["FwdServer", "main"] class FwdServer(Device): """ Start this server: python FwdServer.py myFwdServer Start the server containing the root attribute. Then using jive select the Attribute properties from myFwdServer Select you forwarded attribute and add the value to __root_att e.g. __root_att -> x/y/z/root_attribute_name Now restart the FwdServer """ def init_device(self): Device.init_device(self) self._current = 0.0 self.set_state(tango.DevState.ON) voltage = attribute(name="voltage", label='Voltage', forwarded=True) @attribute(label='Current', dtype='float') def current(self): return self._current # ---------- # Run server # ---------- if __name__ == '__main__': FwdServer.run_server() pytango-9.2.2/examples/interfacechangeEvents/000077500000000000000000000000001316324373100213215ustar00rootroot00000000000000pytango-9.2.2/examples/interfacechangeEvents/ClassFactory.cpp000066400000000000000000000036341316324373100244300ustar00rootroot00000000000000/*----- PROTECTED REGION ID(IfchangeServer::ClassFactory.cpp) ENABLED START -----*/ static const char *RcsId = "$Id: $"; //============================================================================= // // file : ClassFactory.cpp // // description : C++ source for the class_factory method of the DServer // device class. This method is responsible for the creation of // all class singleton for a device server. It is called // at device server startup. // // project : // // This file is part of Tango device class. // // Tango 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. // // Tango 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 Tango. If not, see . // // $Author: $ // // $Revision: $ // $Date: $ // // $HeadURL: $ // //============================================================================= // This file is generated by POGO // (Program Obviously used to Generate tango Object) //============================================================================= #include #include // Add class header files if needed /** * Create IfchangeServer Class singleton and store it in DServer object. */ void Tango::DServer::class_factory() { // Add method class init if needed add_class(IfchangeServer_ns::IfchangeServerClass::init("IfchangeServer")); } /*----- PROTECTED REGION END -----*/ // IfchangeServer::ClassFactory.cpp pytango-9.2.2/examples/interfacechangeEvents/IfchangeClient.py000066400000000000000000000035161316324373100245430ustar00rootroot00000000000000from __future__ import print_function import time import tango class EventManager(): def __init__(self, dp): self._deviceProxy = dp if dp is not None: print("Subscribed to Interface Change Events") self._event_id = dp.subscribe_event( tango.EventType.INTERFACE_CHANGE_EVENT, self) def unsubscribe(self): self._deviceProxy.unsubscribe_event(self._event_id) def push_event(self, ev): print("Event -----push_event-----------------") print("Timestamp: ", ev.reception_date) print("Event type: ", ev.event) print("Device server: ", ev.device) print("Event error: ", ev.err) if ev.err: print("Caught pipe exception") err = ev.errors[0] print("Error desc: ", err.desc) print("Error origin: ", err.origin) print("Error reason: ", err.reason) print("Error severity: ", err.severity) else: if ev.cmd_list is not None: print("Number of commands ", len(ev.cmd_list)) for i in range(len(ev.cmd_list)): cmdInfo = ev.cmd_list[i] print("cmd -----> ", cmdInfo.cmd_name) else: print("Number of commands 0") if ev.att_list is not None: print("Number of attributes ", len(ev.att_list)) for i in range(len(ev.att_list)): attInfo = ev.att_list[i] print("att -----> ", attInfo.name) else: print("Number of attributes 0") print("Device started ", ev.dev_started) def main(): dev = tango.DeviceProxy('ifchangeServer/tango/1') EventManager(dev) time.sleep(3000.0) if __name__ == '__main__': main() pytango-9.2.2/examples/interfacechangeEvents/IfchangeServer.cpp000066400000000000000000000261041316324373100247230ustar00rootroot00000000000000/*----- PROTECTED REGION ID(IfchangeServer.cpp) ENABLED START -----*/ static const char *RcsId = "$Id: $"; //============================================================================= // // file : IfchangeServer.cpp // // description : C++ source for the IfchangeServer class and its commands. // The class is derived from Device. It represents the // CORBA servant object which will be accessed from the // network. All commands which can be executed on the // IfchangeServer are implemented in this file. // // project : // // This file is part of Tango device class. // // Tango 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. // // Tango 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 Tango. If not, see . // // $Author: $ // // $Revision: $ // $Date: $ // // $HeadURL: $ // //============================================================================= // This file is generated by POGO // (Program Obviously used to Generate tango Object) //============================================================================= #include #include /*----- PROTECTED REGION END -----*/ // IfchangeServer.cpp /** * IfchangeServer class description: * */ //================================================================ // The following table gives the correspondence // between command and method names. // // Command name | Method name //================================================================ // State | Inherited (no method) // Status | Inherited (no method) // Add_dynamic | add_dynamic // Delete_Dynamic | delete__dynamic //================================================================ //================================================================ // Attributes managed is: //================================================================ // busy | Tango::DevBoolean Scalar //================================================================ namespace IfchangeServer_ns { /*----- PROTECTED REGION ID(IfchangeServer::namespace_starting) ENABLED START -----*/ // static initializations /*----- PROTECTED REGION END -----*/ // IfchangeServer::namespace_starting //-------------------------------------------------------- /** * Method : IfchangeServer::IfchangeServer() * Description : Constructors for a Tango device * implementing the classIfchangeServer */ //-------------------------------------------------------- IfchangeServer::IfchangeServer(Tango::DeviceClass *cl, string &s) : TANGO_BASE_CLASS(cl, s.c_str()) { /*----- PROTECTED REGION ID(IfchangeServer::constructor_1) ENABLED START -----*/ init_device(); /*----- PROTECTED REGION END -----*/ // IfchangeServer::constructor_1 } //-------------------------------------------------------- IfchangeServer::IfchangeServer(Tango::DeviceClass *cl, const char *s) : TANGO_BASE_CLASS(cl, s) { /*----- PROTECTED REGION ID(IfchangeServer::constructor_2) ENABLED START -----*/ init_device(); /*----- PROTECTED REGION END -----*/ // IfchangeServer::constructor_2 } //-------------------------------------------------------- IfchangeServer::IfchangeServer(Tango::DeviceClass *cl, const char *s, const char *d) : TANGO_BASE_CLASS(cl, s, d) { /*----- PROTECTED REGION ID(IfchangeServer::constructor_3) ENABLED START -----*/ init_device(); /*----- PROTECTED REGION END -----*/ // IfchangeServer::constructor_3 } //-------------------------------------------------------- /** * Method : IfchangeServer::delete_device() * Description : will be called at device destruction or at init command */ //-------------------------------------------------------- void IfchangeServer::delete_device() { DEBUG_STREAM << "IfchangeServer::delete_device() " << device_name << endl; /*----- PROTECTED REGION ID(IfchangeServer::delete_device) ENABLED START -----*/ // Delete device allocated objects /*----- PROTECTED REGION END -----*/ // IfchangeServer::delete_device delete[] attr_busy_read; } //-------------------------------------------------------- /** * Method : IfchangeServer::init_device() * Description : will be called at device initialization. */ //-------------------------------------------------------- void IfchangeServer::init_device() { DEBUG_STREAM << "IfchangeServer::init_device() create device " << device_name << endl; /*----- PROTECTED REGION ID(IfchangeServer::init_device_before) ENABLED START -----*/ // Initialization before get_device_property() call /*----- PROTECTED REGION END -----*/ // IfchangeServer::init_device_before // No device property to be read from database attr_busy_read = new Tango::DevBoolean[1]; /*----- PROTECTED REGION ID(IfchangeServer::init_device) ENABLED START -----*/ set_status("ON"); set_state(Tango::ON); // Initialize device /*----- PROTECTED REGION END -----*/ // IfchangeServer::init_device } //-------------------------------------------------------- /** * Method : IfchangeServer::always_executed_hook() * Description : method always executed before any command is executed */ //-------------------------------------------------------- void IfchangeServer::always_executed_hook() { DEBUG_STREAM << "IfchangeServer::always_executed_hook() " << device_name << endl; /*----- PROTECTED REGION ID(IfchangeServer::always_executed_hook) ENABLED START -----*/ // code always executed before all requests /*----- PROTECTED REGION END -----*/ // IfchangeServer::always_executed_hook } //-------------------------------------------------------- /** * Method : IfchangeServer::read_attr_hardware() * Description : Hardware acquisition for attributes */ //-------------------------------------------------------- void IfchangeServer::read_attr_hardware(TANGO_UNUSED(vector &attr_list)) { DEBUG_STREAM << "IfchangeServer::read_attr_hardware(vector &attr_list) entering... " << endl; /*----- PROTECTED REGION ID(IfchangeServer::read_attr_hardware) ENABLED START -----*/ // Add your own code /*----- PROTECTED REGION END -----*/ // IfchangeServer::read_attr_hardware } //-------------------------------------------------------- /** * Read attribute busy related method * Description: * * Data type: Tango::DevBoolean * Attr type: Scalar */ //-------------------------------------------------------- void IfchangeServer::read_busy(Tango::Attribute &attr) { DEBUG_STREAM << "IfchangeServer::read_busy(Tango::Attribute &attr) entering... " << endl; /*----- PROTECTED REGION ID(IfchangeServer::read_busy) ENABLED START -----*/ // Set the attribute value *attr_busy_read = true; attr.set_value(attr_busy_read); /*----- PROTECTED REGION END -----*/ // IfchangeServer::read_busy } //-------------------------------------------------------- /** * Read attribute ioattr related method * Description: * * Data type: Tango::DevDouble * Attr type: Scalar */ //-------------------------------------------------------- void IfchangeServer::read_ioattr(Tango::Attribute &attr) { DEBUG_STREAM << "IfchangeServer::read_ioattr(Tango::Attribute &attr) entering... " << endl; Tango::DevDouble *att_value = get_ioattr_data_ptr(attr.get_name()); /*----- PROTECTED REGION ID(IfchangeServer::read_ioattr) ENABLED START -----*/ // Set the attribute value attr.set_value(att_value); /*----- PROTECTED REGION END -----*/ // IfchangeServer::read_ioattr } //-------------------------------------------------------- /** * Method : IfchangeServer::add_dynamic_attributes() * Description : Create the dynamic attributes if any * for specified device. */ //-------------------------------------------------------- void IfchangeServer::add_dynamic_attributes() { // Example to add dynamic attribute: // Copy inside the following protected area to create instance(s) at startup. // add_ioattr_dynamic_attribute("MyioattrAttribute"); /*----- PROTECTED REGION ID(IfchangeServer::add_dynamic_attributes) ENABLED START -----*/ // Add your own code to create and add dynamic attributes if any /*----- PROTECTED REGION END -----*/ // IfchangeServer::add_dynamic_attributes } //-------------------------------------------------------- /** * Command Add_dynamic related method * Description: * */ //-------------------------------------------------------- void IfchangeServer::add_dynamic() { DEBUG_STREAM << "IfchangeServer::Add_dynamic() - " << device_name << endl; /*----- PROTECTED REGION ID(IfchangeServer::add_dynamic) ENABLED START -----*/ add_iocmd_dynamic_command("test_dynamic_Command", true); add_ioattr_dynamic_attribute("test_dynamic_Attribute"); // Add your own code /*----- PROTECTED REGION END -----*/ // IfchangeServer::add_dynamic } //-------------------------------------------------------- /** * Command Delete_Dynamic related method * Description: * */ //-------------------------------------------------------- void IfchangeServer::delete__dynamic() { DEBUG_STREAM << "IfchangeServer::Delete_Dynamic() - " << device_name << endl; /*----- PROTECTED REGION ID(IfchangeServer::delete__dynamic) ENABLED START -----*/ remove_iocmd_dynamic_command("test_dynamic_Command"); remove_ioattr_dynamic_attribute("test_dynamic_Attribute"); // Add your own code /*----- PROTECTED REGION END -----*/ // IfchangeServer::delete__dynamic } //-------------------------------------------------------- /** * Command iocmd related method * Description: * */ //-------------------------------------------------------- void IfchangeServer::iocmd(Tango::Command &command) { DEBUG_STREAM << "IfchangeServer::" << command.get_name() << " - " << device_name << endl; /*----- PROTECTED REGION ID(IfchangeServer::iocmd) ENABLED START -----*/ // Add your own code /*----- PROTECTED REGION END -----*/ // IfchangeServer::iocmd } //-------------------------------------------------------- /** * Method : IfchangeServer::add_dynamic_commands() * Description : Create the dynamic commands if any * for specified device. */ //-------------------------------------------------------- void IfchangeServer::add_dynamic_commands() { // Example to add dynamic command: // Copy inside the folowing protected area to instanciate at startup. // add_iocmd_dynamic_command("MyiocmdCommand", true); /*----- PROTECTED REGION ID(IfchangeServer::add_dynamic_commands) ENABLED START -----*/ // Add your own code to create and add dynamic commands if any /*----- PROTECTED REGION END -----*/ // IfchangeServer::add_dynamic_commands } /*----- PROTECTED REGION ID(IfchangeServer::namespace_ending) ENABLED START -----*/ // Additional Methods /*----- PROTECTED REGION END -----*/ // IfchangeServer::namespace_ending } // namespace pytango-9.2.2/examples/interfacechangeEvents/IfchangeServer.h000066400000000000000000000131021316324373100243620ustar00rootroot00000000000000/*----- PROTECTED REGION ID(IfchangeServer.h) ENABLED START -----*/ //============================================================================= // // file : IfchangeServer.h // // description : Include file for the IfchangeServer class // // project : // // This file is part of Tango device class. // // Tango 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. // // Tango 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 Tango. If not, see . // // $Author: $ // // $Revision: $ // $Date: $ // // $HeadURL: $ // //============================================================================= // This file is generated by POGO // (Program Obviously used to Generate tango Object) //============================================================================= #ifndef IfchangeServer_H #define IfchangeServer_H #include /*----- PROTECTED REGION END -----*/ // IfchangeServer.h /** * IfchangeServer class description: * */ namespace IfchangeServer_ns { /*----- PROTECTED REGION ID(IfchangeServer::Additional Class Declarations) ENABLED START -----*/ // Additional Class Declarations /*----- PROTECTED REGION END -----*/ // IfchangeServer::Additional Class Declarations class IfchangeServer : public TANGO_BASE_CLASS { /*----- PROTECTED REGION ID(IfchangeServer::Data Members) ENABLED START -----*/ // Add your own data members /*----- PROTECTED REGION END -----*/ // IfchangeServer::Data Members // Attribute data members public: Tango::DevBoolean *attr_busy_read; // Constructors and destructors public: /** * Constructs a newly device object. * * @param cl Class. * @param s Device Name */ IfchangeServer(Tango::DeviceClass *cl,string &s); /** * Constructs a newly device object. * * @param cl Class. * @param s Device Name */ IfchangeServer(Tango::DeviceClass *cl,const char *s); /** * Constructs a newly device object. * * @param cl Class. * @param s Device name * @param d Device description. */ IfchangeServer(Tango::DeviceClass *cl,const char *s,const char *d); /** * The device object destructor. */ ~IfchangeServer() {delete_device();}; // Miscellaneous methods public: /* * will be called at device destruction or at init command. */ void delete_device(); /* * Initialize the device */ virtual void init_device(); /* * Always executed method before execution command method. */ virtual void always_executed_hook(); // Attribute methods public: //-------------------------------------------------------- /* * Method : IfchangeServer::read_attr_hardware() * Description : Hardware acquisition for attributes. */ //-------------------------------------------------------- virtual void read_attr_hardware(vector &attr_list); /** * Attribute busy related methods * Description: * * Data type: Tango::DevBoolean * Attr type: Scalar */ virtual void read_busy(Tango::Attribute &attr); virtual bool is_busy_allowed(Tango::AttReqType type); // Dynamic attribute methods public: /** * Attribute ioattr related methods * Description: * * Data type: Tango::DevDouble * Attr type: Scalar */ virtual void read_ioattr(Tango::Attribute &attr); virtual bool is_ioattr_allowed(Tango::AttReqType type); void add_ioattr_dynamic_attribute(string attname); void remove_ioattr_dynamic_attribute(string attname); Tango::DevDouble *get_ioattr_data_ptr(string &name); map ioattr_data; //-------------------------------------------------------- /** * Method : IfchangeServer::add_dynamic_attributes() * Description : Add dynamic attributes if any. */ //-------------------------------------------------------- void add_dynamic_attributes(); // Command related methods public: /** * Command Add_dynamic related method * Description: * */ virtual void add_dynamic(); virtual bool is_Add_dynamic_allowed(const CORBA::Any &any); /** * Command Delete_Dynamic related method * Description: * */ virtual void delete__dynamic(); virtual bool is_Delete_Dynamic_allowed(const CORBA::Any &any); // Dynamic commands methods public: /** * Command iocmd related method * Description: * */ virtual void iocmd(Tango::Command &command); virtual bool is_iocmd_allowed(const CORBA::Any &any); void add_iocmd_dynamic_command(string cmdname, bool device); void remove_iocmd_dynamic_command(string cmdname); //-------------------------------------------------------- /** * Method : IfchangeServer::add_dynamic_commands() * Description : Add dynamic commands if any. */ //-------------------------------------------------------- void add_dynamic_commands(); /*----- PROTECTED REGION ID(IfchangeServer::Additional Method prototypes) ENABLED START -----*/ // Additional Method prototypes /*----- PROTECTED REGION END -----*/ // IfchangeServer::Additional Method prototypes }; /*----- PROTECTED REGION ID(IfchangeServer::Additional Classes Definitions) ENABLED START -----*/ // Additional Classes Definitions /*----- PROTECTED REGION END -----*/ // IfchangeServer::Additional Classes Definitions } // End of namespace #endif // IfchangeServer_H pytango-9.2.2/examples/interfacechangeEvents/IfchangeServer.py000066400000000000000000000036521316324373100245740ustar00rootroot00000000000000import logging import tango from tango.server import Device from tango.server import DispLevel from tango.server import attribute, command class IfchangeServer(Device): def init_device(self): Device.init_device(self) logging.basicConfig(level=logging.DEBUG) self._current = 0.0 self.set_state(tango.DevState.ON) @attribute(label='Sequence Counter', dtype='int', description="Sequence counter") def seq_counter(self): return 456 @attribute(label='Voltage', dtype='float', description="voltage") def volts(self): return 3.142 def read_current(self): print ("read current method ", self._current) return self._current def write_current(self, curr): self._current = curr print ("set current to ", self._current) def start(self, argin): print ("start method") return 3142 @command(dtype_in=str, doc_in='name of dynamic attribute to add') def add_dyn_attr(self, name): attr = attribute(name=name, dtype='float', fget=self.read_current, fset=self.write_current) self.add_attribute(attr) @command(dtype_in=str, doc_in='name of dynamic attribute to delete') def delete_dyn_attr(self, name): self._remove_attribute(name) @command def add_dyn_cmd(self): device_level = True cmd = command(f=self.start, dtype_in=str, dtype_out=int, doc_in='description of input', doc_out='description of output', display_level=DispLevel.EXPERT, polling_period=5.1) self.add_command(cmd, device_level) @command(dtype_in=str, doc_in='name of dynamic command to delete') def delete_dyn_cmd(self, name): self._remove_command(name, False, True) # ---------- # Run server # ---------- if __name__ == '__main__': IfchangeServer.run_server() pytango-9.2.2/examples/interfacechangeEvents/IfchangeServer.xmi000066400000000000000000000110431316324373100247320ustar00rootroot00000000000000 pytango-9.2.2/examples/interfacechangeEvents/IfchangeServerClass.cpp000066400000000000000000000515211316324373100257120ustar00rootroot00000000000000/*----- PROTECTED REGION ID(IfchangeServerClass.cpp) ENABLED START -----*/ static const char *RcsId = "$Id: $"; static const char *TagName = "$Name: $"; static const char *CvsPath = "$Source: $"; static const char *SvnPath = "$HeadURL: $"; static const char *HttpServer = "http://www.esrf.eu/computing/cs/tango/tango_doc/ds_doc/"; //============================================================================= // // file : IfchangeServerClass.cpp // // description : C++ source for the IfchangeServerClass. // A singleton class derived from DeviceClass. // It implements the command and attribute list // and all properties and methods required // by the IfchangeServer once per process. // // project : // // This file is part of Tango device class. // // Tango 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. // // Tango 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 Tango. If not, see . // // $Author: $ // // $Revision: $ // $Date: $ // // $HeadURL: $ // //============================================================================= // This file is generated by POGO // (Program Obviously used to Generate tango Object) //============================================================================= #include /*----- PROTECTED REGION END -----*/ // IfchangeServerClass.cpp //------------------------------------------------------------------- /** * Create IfchangeServerClass singleton and * return it in a C function for Python usage */ //------------------------------------------------------------------- extern "C" { #ifdef _TG_WINDOWS_ __declspec(dllexport) #endif Tango::DeviceClass *_create_IfchangeServer_class(const char *name) { return IfchangeServer_ns::IfchangeServerClass::init(name); } } namespace IfchangeServer_ns { //=================================================================== // Initialize pointer for singleton pattern //=================================================================== IfchangeServerClass *IfchangeServerClass::_instance = NULL; //-------------------------------------------------------- /** * method : IfchangeServerClass::IfchangeServerClass(string &s) * description : constructor for the IfchangeServerClass * * @param s The class name */ //-------------------------------------------------------- IfchangeServerClass::IfchangeServerClass(string &s):Tango::DeviceClass(s) { cout2 << "Entering IfchangeServerClass constructor" << endl; set_default_property(); write_class_property(); /*----- PROTECTED REGION ID(IfchangeServerClass::constructor) ENABLED START -----*/ /*----- PROTECTED REGION END -----*/ // IfchangeServerClass::constructor cout2 << "Leaving IfchangeServerClass constructor" << endl; } //-------------------------------------------------------- /** * method : IfchangeServerClass::~IfchangeServerClass() * description : destructor for the IfchangeServerClass */ //-------------------------------------------------------- IfchangeServerClass::~IfchangeServerClass() { /*----- PROTECTED REGION ID(IfchangeServerClass::destructor) ENABLED START -----*/ /*----- PROTECTED REGION END -----*/ // IfchangeServerClass::destructor _instance = NULL; } //-------------------------------------------------------- /** * method : IfchangeServerClass::init * description : Create the object if not already done. * Otherwise, just return a pointer to the object * * @param name The class name */ //-------------------------------------------------------- IfchangeServerClass *IfchangeServerClass::init(const char *name) { if (_instance == NULL) { try { string s(name); _instance = new IfchangeServerClass(s); } catch (bad_alloc &) { throw; } } return _instance; } //-------------------------------------------------------- /** * method : IfchangeServerClass::instance * description : Check if object already created, * and return a pointer to the object */ //-------------------------------------------------------- IfchangeServerClass *IfchangeServerClass::instance() { if (_instance == NULL) { cerr << "Class is not initialised !!" << endl; exit(-1); } return _instance; } //=================================================================== // Command execution method calls //=================================================================== //-------------------------------------------------------- /** * method : Add_dynamicClass::execute() * description : method to trigger the execution of the command. * * @param device The device on which the command must be executed * @param in_any The command input data * * returns The command output data (packed in the Any object) */ //-------------------------------------------------------- CORBA::Any *Add_dynamicClass::execute(Tango::DeviceImpl *device, TANGO_UNUSED(const CORBA::Any &in_any)) { cout2 << "Add_dynamicClass::execute(): arrived" << endl; ((static_cast(device))->add_dynamic()); return new CORBA::Any(); } //-------------------------------------------------------- /** * method : Delete_DynamicClass::execute() * description : method to trigger the execution of the command. * * @param device The device on which the command must be executed * @param in_any The command input data * * returns The command output data (packed in the Any object) */ //-------------------------------------------------------- CORBA::Any *Delete_DynamicClass::execute(Tango::DeviceImpl *device, TANGO_UNUSED(const CORBA::Any &in_any)) { cout2 << "Delete_DynamicClass::execute(): arrived" << endl; ((static_cast(device))->delete__dynamic()); return new CORBA::Any(); } //-------------------------------------------------------- /** * method : iocmdClass::execute() * description : method to trigger the execution of the command. * * @param device The device on which the command must be executed * @param in_any The command input data * * returns The command output data (packed in the Any object) */ //-------------------------------------------------------- CORBA::Any *iocmdClass::execute(Tango::DeviceImpl *device, TANGO_UNUSED(const CORBA::Any &in_any)) { cout2 << "iocmdClass::execute(): arrived" << endl; ((static_cast(device))->iocmd(*this)); return new CORBA::Any(); } //=================================================================== // Properties management //=================================================================== //-------------------------------------------------------- /** * Method : IfchangeServerClass::get_class_property() * Description : Get the class property for specified name. */ //-------------------------------------------------------- Tango::DbDatum IfchangeServerClass::get_class_property(string &prop_name) { for (unsigned int i=0 ; i vect_data; // Set Default Class Properties // Set Default device Properties } //-------------------------------------------------------- /** * Method : IfchangeServerClass::write_class_property() * Description : Set class description fields as property in database */ //-------------------------------------------------------- void IfchangeServerClass::write_class_property() { // First time, check if database used if (Tango::Util::_UseDb == false) return; Tango::DbData data; string classname = get_name(); string header; string::size_type start, end; // Put title Tango::DbDatum title("ProjectTitle"); string str_title(""); title << str_title; data.push_back(title); // Put Description Tango::DbDatum description("Description"); vector str_desc; str_desc.push_back(""); description << str_desc; data.push_back(description); // put cvs or svn location string filename("IfchangeServer"); filename += "Class.cpp"; // check for cvs information string src_path(CvsPath); start = src_path.find("/"); if (start!=string::npos) { end = src_path.find(filename); if (end>start) { string strloc = src_path.substr(start, end-start); // Check if specific repository start = strloc.find("/cvsroot/"); if (start!=string::npos && start>0) { string repository = strloc.substr(0, start); if (repository.find("/segfs/")!=string::npos) strloc = "ESRF:" + strloc.substr(start, strloc.length()-start); } Tango::DbDatum cvs_loc("cvs_location"); cvs_loc << strloc; data.push_back(cvs_loc); } } // check for svn information else { string src_path(SvnPath); start = src_path.find("://"); if (start!=string::npos) { end = src_path.find(filename); if (end>start) { header = "$HeadURL: "; start = header.length(); string strloc = src_path.substr(start, (end-start)); Tango::DbDatum svn_loc("svn_location"); svn_loc << strloc; data.push_back(svn_loc); } } } // Get CVS or SVN revision tag // CVS tag string tagname(TagName); header = "$Name: "; start = header.length(); string endstr(" $"); end = tagname.find(endstr); if (end!=string::npos && end>start) { string strtag = tagname.substr(start, end-start); Tango::DbDatum cvs_tag("cvs_tag"); cvs_tag << strtag; data.push_back(cvs_tag); } // SVN tag string svnpath(SvnPath); header = "$HeadURL: "; start = header.length(); end = svnpath.find(endstr); if (end!=string::npos && end>start) { string strloc = svnpath.substr(start, end-start); string tagstr ("/tags/"); start = strloc.find(tagstr); if ( start!=string::npos ) { start = start + tagstr.length(); end = strloc.find(filename); string strtag = strloc.substr(start, end-start-1); Tango::DbDatum svn_tag("svn_tag"); svn_tag << strtag; data.push_back(svn_tag); } } // Get URL location string httpServ(HttpServer); if (httpServ.length()>0) { Tango::DbDatum db_doc_url("doc_url"); db_doc_url << httpServ; data.push_back(db_doc_url); } // Put inheritance Tango::DbDatum inher_datum("InheritedFrom"); vector inheritance; inheritance.push_back("TANGO_BASE_CLASS"); inher_datum << inheritance; data.push_back(inher_datum); // Call database and and values get_db_class()->put_property(data); } //=================================================================== // Factory methods //=================================================================== //-------------------------------------------------------- /** * Method : IfchangeServerClass::device_factory() * Description : Create the device object(s) * and store them in the device list */ //-------------------------------------------------------- void IfchangeServerClass::device_factory(const Tango::DevVarStringArray *devlist_ptr) { /*----- PROTECTED REGION ID(IfchangeServerClass::device_factory_before) ENABLED START -----*/ // Add your own code /*----- PROTECTED REGION END -----*/ // IfchangeServerClass::device_factory_before // Create devices and add it into the device list for (unsigned long i=0 ; ilength() ; i++) { cout4 << "Device name : " << (*devlist_ptr)[i].in() << endl; device_list.push_back(new IfchangeServer(this, (*devlist_ptr)[i])); } // Manage dynamic attributes if any erase_dynamic_attributes(devlist_ptr, get_class_attr()->get_attr_list()); // Export devices to the outside world for (unsigned long i=1 ; i<=devlist_ptr->length() ; i++) { // Add dynamic attributes if any IfchangeServer *dev = static_cast(device_list[device_list.size()-i]); dev->add_dynamic_attributes(); dev->add_dynamic_commands(); // Check before if database used. if ((Tango::Util::_UseDb == true) && (Tango::Util::_FileDb == false)) export_device(dev); else export_device(dev, dev->get_name().c_str()); } /*----- PROTECTED REGION ID(IfchangeServerClass::device_factory_after) ENABLED START -----*/ // Add your own code /*----- PROTECTED REGION END -----*/ // IfchangeServerClass::device_factory_after } //-------------------------------------------------------- /** * Method : IfchangeServerClass::attribute_factory() * Description : Create the attribute object(s) * and store them in the attribute list */ //-------------------------------------------------------- void IfchangeServerClass::attribute_factory(vector &att_list) { /*----- PROTECTED REGION ID(IfchangeServerClass::attribute_factory_before) ENABLED START -----*/ // Add your own code /*----- PROTECTED REGION END -----*/ // IfchangeServerClass::attribute_factory_before // Attribute : busy busyAttrib *busy = new busyAttrib(); Tango::UserDefaultAttrProp busy_prop; // description not set for busy // label not set for busy // unit not set for busy // standard_unit not set for busy // display_unit not set for busy // format not set for busy // max_value not set for busy // min_value not set for busy // max_alarm not set for busy // min_alarm not set for busy // max_warning not set for busy // min_warning not set for busy // delta_t not set for busy // delta_val not set for busy busy->set_default_properties(busy_prop); // Not Polled busy->set_disp_level(Tango::OPERATOR); // Not Memorized att_list.push_back(busy); // Create a list of static attributes create_static_attribute_list(get_class_attr()->get_attr_list()); /*----- PROTECTED REGION ID(IfchangeServerClass::attribute_factory_after) ENABLED START -----*/ // Add your own code /*----- PROTECTED REGION END -----*/ // IfchangeServerClass::attribute_factory_after } //-------------------------------------------------------- /** * Method : IfchangeServerClass::pipe_factory() * Description : Create the pipe object(s) * and store them in the pipe list */ //-------------------------------------------------------- void IfchangeServerClass::pipe_factory() { /*----- PROTECTED REGION ID(IfchangeServerClass::pipe_factory_before) ENABLED START -----*/ // Add your own code /*----- PROTECTED REGION END -----*/ // IfchangeServerClass::pipe_factory_before /*----- PROTECTED REGION ID(IfchangeServerClass::pipe_factory_after) ENABLED START -----*/ // Add your own code /*----- PROTECTED REGION END -----*/ // IfchangeServerClass::pipe_factory_after } //-------------------------------------------------------- /** * Method : IfchangeServerClass::command_factory() * Description : Create the command object(s) * and store them in the command list */ //-------------------------------------------------------- void IfchangeServerClass::command_factory() { /*----- PROTECTED REGION ID(IfchangeServerClass::command_factory_before) ENABLED START -----*/ // Add your own code /*----- PROTECTED REGION END -----*/ // IfchangeServerClass::command_factory_before // Command Add_dynamic Add_dynamicClass *pAdd_dynamicCmd = new Add_dynamicClass("Add_dynamic", Tango::DEV_VOID, Tango::DEV_VOID, "", "", Tango::OPERATOR); command_list.push_back(pAdd_dynamicCmd); // Command Delete_Dynamic Delete_DynamicClass *pDelete_DynamicCmd = new Delete_DynamicClass("Delete_Dynamic", Tango::DEV_VOID, Tango::DEV_VOID, "", "", Tango::OPERATOR); command_list.push_back(pDelete_DynamicCmd); /*----- PROTECTED REGION ID(IfchangeServerClass::command_factory_after) ENABLED START -----*/ // Add your own code /*----- PROTECTED REGION END -----*/ // IfchangeServerClass::command_factory_after } //=================================================================== // Dynamic attributes related methods //=================================================================== //-------------------------------------------------------- /** * method : IfchangeServerClass::create_static_attribute_list * description : Create the a list of static attributes * * @param att_list the ceated attribute list */ //-------------------------------------------------------- void IfchangeServerClass::create_static_attribute_list(vector &att_list) { for (unsigned long i=0 ; iget_name()); transform(att_name.begin(), att_name.end(), att_name.begin(), ::tolower); defaultAttList.push_back(att_name); } cout2 << defaultAttList.size() << " attributes in default list" << endl; /*----- PROTECTED REGION ID(IfchangeServerClass::create_static_att_list) ENABLED START -----*/ /*----- PROTECTED REGION END -----*/ // IfchangeServerClass::create_static_att_list } //-------------------------------------------------------- /** * method : IfchangeServerClass::erase_dynamic_attributes * description : delete the dynamic attributes if any. * * @param devlist_ptr the device list pointer * @param list of all attributes */ //-------------------------------------------------------- void IfchangeServerClass::erase_dynamic_attributes(const Tango::DevVarStringArray *devlist_ptr, vector &att_list) { Tango::Util *tg = Tango::Util::instance(); for (unsigned long i=0 ; ilength() ; i++) { Tango::DeviceImpl *dev_impl = tg->get_device_by_name(((string)(*devlist_ptr)[i]).c_str()); IfchangeServer *dev = static_cast (dev_impl); vector &dev_att_list = dev->get_device_attr()->get_attribute_list(); vector::iterator ite_att; for (ite_att=dev_att_list.begin() ; ite_att != dev_att_list.end() ; ++ite_att) { string att_name((*ite_att)->get_name_lower()); if ((att_name == "state") || (att_name == "status")) continue; vector::iterator ite_str = find(defaultAttList.begin(), defaultAttList.end(), att_name); if (ite_str == defaultAttList.end()) { cout2 << att_name << " is a UNWANTED dynamic attribute for device " << (*devlist_ptr)[i] << endl; Tango::Attribute &att = dev->get_device_attr()->get_attr_by_name(att_name.c_str()); dev->remove_attribute(att_list[att.get_attr_idx()], true, false); --ite_att; } } } /*----- PROTECTED REGION ID(IfchangeServerClass::erase_dynamic_attributes) ENABLED START -----*/ /*----- PROTECTED REGION END -----*/ // IfchangeServerClass::erase_dynamic_attributes } //-------------------------------------------------------- /** * Method : IfchangeServerClass::get_attr_by_name() * Description : returns Tango::Attr * object found by name */ //-------------------------------------------------------- Tango::Attr *IfchangeServerClass::get_attr_object_by_name(vector &att_list, string attname) { vector::iterator it; for (it=att_list.begin() ; itget_name()==attname) return (*it); // Attr does not exist return NULL; } /*----- PROTECTED REGION ID(IfchangeServerClass::Additional Methods) ENABLED START -----*/ /*----- PROTECTED REGION END -----*/ // IfchangeServerClass::Additional Methods } // namespace pytango-9.2.2/examples/interfacechangeEvents/IfchangeServerClass.h000066400000000000000000000160131316324373100253540ustar00rootroot00000000000000/*----- PROTECTED REGION ID(IfchangeServerClass.h) ENABLED START -----*/ //============================================================================= // // file : IfchangeServerClass.h // // description : Include for the IfchangeServer root class. // This class is the singleton class for // the IfchangeServer device class. // It contains all properties and methods which the // IfchangeServer requires only once e.g. the commands. // // project : // // This file is part of Tango device class. // // Tango 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. // // Tango 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 Tango. If not, see . // // $Author: $ // // $Revision: $ // $Date: $ // // $HeadURL: $ // //============================================================================= // This file is generated by POGO // (Program Obviously used to Generate tango Object) //============================================================================= #ifndef IfchangeServerClass_H #define IfchangeServerClass_H #include #include /*----- PROTECTED REGION END -----*/ // IfchangeServerClass.h namespace IfchangeServer_ns { /*----- PROTECTED REGION ID(IfchangeServerClass::classes for dynamic creation) ENABLED START -----*/ /*----- PROTECTED REGION END -----*/ // IfchangeServerClass::classes for dynamic creation //========================================= // Define classes for attributes //========================================= // Attribute busy class definition class busyAttrib: public Tango::Attr { public: busyAttrib():Attr("busy", Tango::DEV_BOOLEAN, Tango::READ) {}; ~busyAttrib() {}; virtual void read(Tango::DeviceImpl *dev,Tango::Attribute &att) {(static_cast(dev))->read_busy(att);} virtual bool is_allowed(Tango::DeviceImpl *dev,Tango::AttReqType ty) {return (static_cast(dev))->is_busy_allowed(ty);} }; //========================================= // Define classes for dynamic attributes //========================================= // Attribute ioattr class definition class ioattrAttrib: public Tango::Attr { public: ioattrAttrib(const string &att_name):Attr(att_name.c_str(), Tango::DEV_DOUBLE, Tango::READ) {}; ~ioattrAttrib() {}; virtual void read(Tango::DeviceImpl *dev,Tango::Attribute &att) {(static_cast(dev))->read_ioattr(att);} virtual bool is_allowed(Tango::DeviceImpl *dev,Tango::AttReqType ty) {return (static_cast(dev))->is_ioattr_allowed(ty);} }; //========================================= // Define classes for commands //========================================= // Command Add_dynamic class definition class Add_dynamicClass : public Tango::Command { public: Add_dynamicClass(const char *name, Tango::CmdArgType in, Tango::CmdArgType out, const char *in_desc, const char *out_desc, Tango::DispLevel level) :Command(name,in,out,in_desc,out_desc, level) {}; Add_dynamicClass(const char *name, Tango::CmdArgType in, Tango::CmdArgType out) :Command(name,in,out) {}; ~Add_dynamicClass() {}; virtual CORBA::Any *execute (Tango::DeviceImpl *dev, const CORBA::Any &any); virtual bool is_allowed (Tango::DeviceImpl *dev, const CORBA::Any &any) {return (static_cast(dev))->is_Add_dynamic_allowed(any);} }; // Command Delete_Dynamic class definition class Delete_DynamicClass : public Tango::Command { public: Delete_DynamicClass(const char *name, Tango::CmdArgType in, Tango::CmdArgType out, const char *in_desc, const char *out_desc, Tango::DispLevel level) :Command(name,in,out,in_desc,out_desc, level) {}; Delete_DynamicClass(const char *name, Tango::CmdArgType in, Tango::CmdArgType out) :Command(name,in,out) {}; ~Delete_DynamicClass() {}; virtual CORBA::Any *execute (Tango::DeviceImpl *dev, const CORBA::Any &any); virtual bool is_allowed (Tango::DeviceImpl *dev, const CORBA::Any &any) {return (static_cast(dev))->is_Delete_Dynamic_allowed(any);} }; //========================================= // Define classes for dynamic commands //========================================= // Command iocmd class definition class iocmdClass : public Tango::Command { public: iocmdClass(const char *name, Tango::CmdArgType in, Tango::CmdArgType out, const char *in_desc, const char *out_desc, Tango::DispLevel level) :Command(name,in,out,in_desc,out_desc, level) {}; iocmdClass(const char *name, Tango::CmdArgType in, Tango::CmdArgType out) :Command(name,in,out) {}; ~iocmdClass() {}; virtual CORBA::Any *execute (Tango::DeviceImpl *dev, const CORBA::Any &any); virtual bool is_allowed (Tango::DeviceImpl *dev, const CORBA::Any &any) {return (static_cast(dev))->is_iocmd_allowed(any);} }; /** * The IfchangeServerClass singleton definition */ #ifdef _TG_WINDOWS_ class __declspec(dllexport) IfchangeServerClass : public Tango::DeviceClass #else class IfchangeServerClass : public Tango::DeviceClass #endif { /*----- PROTECTED REGION ID(IfchangeServerClass::Additionnal DServer data members) ENABLED START -----*/ /*----- PROTECTED REGION END -----*/ // IfchangeServerClass::Additionnal DServer data members public: // write class properties data members Tango::DbData cl_prop; Tango::DbData cl_def_prop; Tango::DbData dev_def_prop; // Method prototypes static IfchangeServerClass *init(const char *); static IfchangeServerClass *instance(); ~IfchangeServerClass(); Tango::DbDatum get_class_property(string &); Tango::DbDatum get_default_device_property(string &); Tango::DbDatum get_default_class_property(string &); protected: IfchangeServerClass(string &); static IfchangeServerClass *_instance; void command_factory(); void attribute_factory(vector &); void pipe_factory(); void write_class_property(); void set_default_property(); void get_class_property(); string get_cvstag(); string get_cvsroot(); private: void device_factory(const Tango::DevVarStringArray *); void create_static_attribute_list(vector &); void erase_dynamic_attributes(const Tango::DevVarStringArray *,vector &); vector defaultAttList; Tango::Attr *get_attr_object_by_name(vector &att_list, string attname); }; } // End of namespace #endif // IfchangeServer_H pytango-9.2.2/examples/interfacechangeEvents/IfchangeServerDynAttrUtils.cpp000066400000000000000000000142421316324373100272520ustar00rootroot00000000000000/*----- PROTECTED REGION ID(IfchangeServer::DynAttrUtils.cpp) ENABLED START -----*/ static const char *RcsId = "$Id: $"; //============================================================================= // // file : IfchangeServerDynAttrUtils.cpp // // description : Dynamic attributes utilities file for the IfchangeServer class // // project : // // This file is part of Tango device class. // // Tango 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. // // Tango 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 Tango. If not, see . // // $Author: $ // // $Revision: $ // $Date: $ // // $HeadURL: $ // //============================================================================= // This file is generated by POGO // (Program Obviously used to Generate tango Object) //============================================================================= #include #include /*----- PROTECTED REGION END -----*/ // IfchangeServer::DynAttrUtils.cpp //================================================================ // Attributes managed is: //================================================================ // ioattr | Tango::DevDouble Scalar //================================================================ // For compatibility reason, this file (IfchangeServerDynAttrUtils) // manage also the dynamic command utilities. //================================================================ // The following table gives the correspondence // between command and method names. // // Command name | Method name //================================================================ // iocmd | iocmd //================================================================ namespace IfchangeServer_ns { //============================================================= // Add/Remove dynamic attribute methods //============================================================= //-------------------------------------------------------- /** * Add a ioattr dynamic attribute. * * parameter attname: attribute name to be cretated and added. */ //-------------------------------------------------------- void IfchangeServer::add_ioattr_dynamic_attribute(string attname) { // Attribute : ioattr ioattrAttrib *ioattr = new ioattrAttrib(attname); Tango::UserDefaultAttrProp ioattr_prop; // description not set for ioattr // label not set for ioattr // unit not set for ioattr // standard_unit not set for ioattr // display_unit not set for ioattr // format not set for ioattr // max_value not set for ioattr // min_value not set for ioattr // max_alarm not set for ioattr // min_alarm not set for ioattr // max_warning not set for ioattr // min_warning not set for ioattr // delta_t not set for ioattr // delta_val not set for ioattr /*----- PROTECTED REGION ID(IfchangeServer::att_ioattr_dynamic_attribute) ENABLED START -----*/ /*----- PROTECTED REGION END -----*/ // IfchangeServer::att_ioattr_dynamic_attribute ioattr->set_default_properties(ioattr_prop); // Not Polled ioattr->set_disp_level(Tango::OPERATOR); // Not Memorized ioattr_data.insert(make_pair(attname, 0.0)); add_attribute(ioattr); } //-------------------------------------------------------- /** * remove a ioattr dynamic attribute. * * parameter attname: attribute name to be removed. */ //-------------------------------------------------------- void IfchangeServer::remove_ioattr_dynamic_attribute(string attname) { remove_attribute(attname, true); map::iterator ite; if ((ite=ioattr_data.find(attname))!=ioattr_data.end()) { /*----- PROTECTED REGION ID(IfchangeServer::remove_ioattr_dynamic_attribute) ENABLED START -----*/ /*----- PROTECTED REGION END -----*/ // IfchangeServer::remove_ioattr_dynamic_attribute ioattr_data.erase(ite); } } //============================================================ // Tool methods to get pointer on attribute data buffer //============================================================ //-------------------------------------------------------- /** * Return a pointer on ioattr data. * * parameter attname: the specified attribute name. */ //-------------------------------------------------------- Tango::DevDouble *IfchangeServer::get_ioattr_data_ptr(string &name) { map::iterator ite; if ((ite=ioattr_data.find(name))==ioattr_data.end()) { TangoSys_OMemStream tms; tms << "Dynamic attribute " << name << " has not been created"; Tango::Except::throw_exception( (const char *)"ATTRIBUTE_NOT_FOUND", tms.str().c_str(), (const char *)"IfchangeServer::get_ioattr_data_ptr()"); } return &(ite->second); } //============================================================= // Add/Remove dynamic command methods //============================================================= //-------------------------------------------------------- /** * Add a iocmd dynamic command. * * parameter cmdname: command name to be cretated and added. * parameter device: Set this flag to true if the command must be added for only this device. */ //-------------------------------------------------------- void IfchangeServer::add_iocmd_dynamic_command(string cmdname, bool device) { iocmdClass *piocmdCmd = new iocmdClass(cmdname.c_str(), Tango::DEV_VOID, Tango::DEV_VOID, "", "", Tango::OPERATOR); add_command(piocmdCmd, device); } //-------------------------------------------------------- /** * remove a iocmd dynamic command. * * parameter cmdname: command name to be removed. */ //-------------------------------------------------------- void IfchangeServer::remove_iocmd_dynamic_command(string cmdname) { remove_command(cmdname, true); } } // namespace pytango-9.2.2/examples/interfacechangeEvents/IfchangeServerStateMachine.cpp000066400000000000000000000116551316324373100272160ustar00rootroot00000000000000/*----- PROTECTED REGION ID(IfchangeServerStateMachine.cpp) ENABLED START -----*/ static const char *RcsId = "$Id: $"; //============================================================================= // // file : IfchangeServerStateMachine.cpp // // description : State machine file for the IfchangeServer class // // project : // // This file is part of Tango device class. // // Tango 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. // // Tango 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 Tango. If not, see . // // $Author: $ // // $Revision: $ // $Date: $ // // $HeadURL: $ // //============================================================================= // This file is generated by POGO // (Program Obviously used to Generate tango Object) //============================================================================= #include /*----- PROTECTED REGION END -----*/ // IfchangeServer::IfchangeServerStateMachine.cpp //================================================================ // States | Description //================================================================ namespace IfchangeServer_ns { //================================================= // Attributes Allowed Methods //================================================= //-------------------------------------------------------- /** * Method : IfchangeServer::is_busy_allowed() * Description : Execution allowed for busy attribute */ //-------------------------------------------------------- bool IfchangeServer::is_busy_allowed(TANGO_UNUSED(Tango::AttReqType type)) { // Not any excluded states for busy attribute in read access. /*----- PROTECTED REGION ID(IfchangeServer::busyStateAllowed_READ) ENABLED START -----*/ /*----- PROTECTED REGION END -----*/ // IfchangeServer::busyStateAllowed_READ return true; } //-------------------------------------------------------- /** * Method : IfchangeServer::is_ioattr_allowed() * Description : Execution allowed for ioattr attribute */ //-------------------------------------------------------- bool IfchangeServer::is_ioattr_allowed(TANGO_UNUSED(Tango::AttReqType type)) { // Not any excluded states for ioattr attribute in read access. /*----- PROTECTED REGION ID(IfchangeServer::ioattrStateAllowed_READ) ENABLED START -----*/ /*----- PROTECTED REGION END -----*/ // IfchangeServer::ioattrStateAllowed_READ return true; } //================================================= // Commands Allowed Methods //================================================= //-------------------------------------------------------- /** * Method : IfchangeServer::is_Add_dynamic_allowed() * Description : Execution allowed for Add_dynamic attribute */ //-------------------------------------------------------- bool IfchangeServer::is_Add_dynamic_allowed(TANGO_UNUSED(const CORBA::Any &any)) { // Not any excluded states for Add_dynamic command. /*----- PROTECTED REGION ID(IfchangeServer::Add_dynamicStateAllowed) ENABLED START -----*/ /*----- PROTECTED REGION END -----*/ // IfchangeServer::Add_dynamicStateAllowed return true; } //-------------------------------------------------------- /** * Method : IfchangeServer::is_Delete_Dynamic_allowed() * Description : Execution allowed for Delete_Dynamic attribute */ //-------------------------------------------------------- bool IfchangeServer::is_Delete_Dynamic_allowed(TANGO_UNUSED(const CORBA::Any &any)) { // Not any excluded states for Delete_Dynamic command. /*----- PROTECTED REGION ID(IfchangeServer::Delete_DynamicStateAllowed) ENABLED START -----*/ /*----- PROTECTED REGION END -----*/ // IfchangeServer::Delete_DynamicStateAllowed return true; } //-------------------------------------------------------- /** * Method : IfchangeServer::is_iocmd_allowed() * Description : Execution allowed for iocmd attribute */ //-------------------------------------------------------- bool IfchangeServer::is_iocmd_allowed(TANGO_UNUSED(const CORBA::Any &any)) { // Not any excluded states for iocmd command. /*----- PROTECTED REGION ID(IfchangeServer::iocmdStateAllowed) ENABLED START -----*/ /*----- PROTECTED REGION END -----*/ // IfchangeServer::iocmdStateAllowed return true; } /*----- PROTECTED REGION ID(IfchangeServer::IfchangeServerStateAllowed.AdditionalMethods) ENABLED START -----*/ // Additional Methods /*----- PROTECTED REGION END -----*/ // IfchangeServer::IfchangeServerStateAllowed.AdditionalMethods } // End of namespace pytango-9.2.2/examples/interfacechangeEvents/Makefile000066400000000000000000000117471316324373100227730ustar00rootroot00000000000000#============================================================================= # # file : Makefile # # description : Makefile to generate a TANGO device server. # # project : IfchangeServer # # $Author: $ # # $Revision: $ # $Date: $ # #============================================================================= # This file is generated by POGO # (Program Obviously used to Generate tango Object) #============================================================================= # # #============================================================================= # MAKE_ENV is the path to find common environment to buil project # MAKE_ENV = /usr/local/share/pogo/preferences #============================================================================= # PACKAGE_NAME is the name of the library/device/exe you want to build # PACKAGE_NAME = IfchangeServer MAJOR_VERS = 1 MINOR_VERS = 0 RELEASE = Release_$(MAJOR_VERS)_$(MINOR_VERS) # #============================================================================= # # RELEASE_TYPE # # - DEBUG : debug symbols - no optimization # # - OPTIMIZED : no debug symbols - optimization level set to O2 # #----------------------------------------------------------------------------- RELEASE_TYPE = DEBUG #============================================================================= # OUTPUT_TYPE can be one of the following : # - 'STATIC_LIB' for a static library (.a) # - 'SHARED_LIB' for a dynamic library (.so) # - 'DEVICE' for a device server (will automatically include and link # with Tango dependencies) # - 'SIMPLE_EXE' for an executable with no dependency (for exemple the test tool # of a library with no Tango dependencies) # OUTPUT_TYPE = DEVICE #============================================================================= # OUTPUT_DIR is the directory which contains the build result. # if not set, the standard location is : # - ./shlib if OUTPUT_TYPE is SHARED_LIB # - ./lib if OUTPUT_TYPE is STATIC_LIB # - ./bin for others # #OUTPUT_DIR = #============================================================================= # INC_DIR_USER is the list of all include path needed by your sources # - for a device server, tango dependencies are automatically appended # - '-I ../include' and '-I .' are automatically appended in all cases # INC_DIR_USER= -I . #============================================================================= # LIB_DIR_USER is the list of user library directories # - for a device server, tango libraries directories are automatically appended # - '-L ../lib' is automatically appended in all cases # LIB_DIR_USER= #============================================================================= # LFLAGS_USR is the list of user link flags # - for a device server, tango libraries directories are automatically appended # - '-ldl -lpthread' is automatically appended in all cases # # !!! ATTENTION !!! # Be aware that the order matters. # For example if you must link with libA, and if libA depends itself on libB # you must use '-lA -lB' in this order as link flags, otherwise you will get # 'undefined reference' errors # #LFLAGS_USR+= #============================================================================= # CXXFLAGS_USR lists the compilation flags specific for your library/device/exe # This is the place where to put your compile-time macros using '-Dmy_macro' # # -DACE_HAS_EXCEPTIONS -D__ACE_INLINE__ for ACE # #CXXFLAGS_USR+= -Wall #============================================================================= # TANGO_REQUIRED # - TRUE : your project depends on TANGO # - FALSE : your project does not depend on TANGO #----------------------------------------------------------------------------- # - NOTE : if PROJECT_TYPE is set to DEVICE, TANGO will be auto. added #----------------------------------------------------------------------------- TANGO_REQUIRED = TRUE #============================================================================= # include Standard TANGO compilation options # include $(MAKE_ENV)/tango.opt #============================================================================= # POST_PROCESSING: action to be done after normal make. # e.g.: change executable file name, ..... #POST_PROCESSING = \ # mv bin/$(BIN_DIR)/$(PACKAGE_NAME) bin/$(BIN_DIR)/$(PACKAGE_NAME)_DS #============================================================================= # SVC_OBJS is the list of all objects needed to make the output # SVC_INCL = $(PACKAGE_NAME).h $(PACKAGE_NAME)Class.h SVC_OBJS = \ $(LIB_OBJS) \ $(OBJDIR)/ClassFactory.o \ $(OBJDIR)/main.o LIB_OBJS = \ $(OBJDIR)/$(PACKAGE_NAME).o \ $(OBJDIR)/$(PACKAGE_NAME)Class.o \ $(OBJDIR)/$(PACKAGE_NAME)StateMachine.o \ $(OBJDIR)/$(PACKAGE_NAME)DynAttrUtils.o \ $(ADDITIONAL_OBJS) SVC_INHERITANCE_OBJ = \ #============================================================================= # include common targets # include $(MAKE_ENV)/common_target.opt pytango-9.2.2/examples/interfacechangeEvents/Makefile.bck000066400000000000000000000117471316324373100235310ustar00rootroot00000000000000#============================================================================= # # file : Makefile # # description : Makefile to generate a TANGO device server. # # project : IfchangeServer # # $Author: $ # # $Revision: $ # $Date: $ # #============================================================================= # This file is generated by POGO # (Program Obviously used to Generate tango Object) #============================================================================= # # #============================================================================= # MAKE_ENV is the path to find common environment to buil project # MAKE_ENV = /usr/local/share/pogo/preferences #============================================================================= # PACKAGE_NAME is the name of the library/device/exe you want to build # PACKAGE_NAME = IfchangeServer MAJOR_VERS = 1 MINOR_VERS = 0 RELEASE = Release_$(MAJOR_VERS)_$(MINOR_VERS) # #============================================================================= # # RELEASE_TYPE # # - DEBUG : debug symbols - no optimization # # - OPTIMIZED : no debug symbols - optimization level set to O2 # #----------------------------------------------------------------------------- RELEASE_TYPE = DEBUG #============================================================================= # OUTPUT_TYPE can be one of the following : # - 'STATIC_LIB' for a static library (.a) # - 'SHARED_LIB' for a dynamic library (.so) # - 'DEVICE' for a device server (will automatically include and link # with Tango dependencies) # - 'SIMPLE_EXE' for an executable with no dependency (for exemple the test tool # of a library with no Tango dependencies) # OUTPUT_TYPE = DEVICE #============================================================================= # OUTPUT_DIR is the directory which contains the build result. # if not set, the standard location is : # - ./shlib if OUTPUT_TYPE is SHARED_LIB # - ./lib if OUTPUT_TYPE is STATIC_LIB # - ./bin for others # #OUTPUT_DIR = #============================================================================= # INC_DIR_USER is the list of all include path needed by your sources # - for a device server, tango dependencies are automatically appended # - '-I ../include' and '-I .' are automatically appended in all cases # INC_DIR_USER= -I . #============================================================================= # LIB_DIR_USER is the list of user library directories # - for a device server, tango libraries directories are automatically appended # - '-L ../lib' is automatically appended in all cases # LIB_DIR_USER= #============================================================================= # LFLAGS_USR is the list of user link flags # - for a device server, tango libraries directories are automatically appended # - '-ldl -lpthread' is automatically appended in all cases # # !!! ATTENTION !!! # Be aware that the order matters. # For example if you must link with libA, and if libA depends itself on libB # you must use '-lA -lB' in this order as link flags, otherwise you will get # 'undefined reference' errors # #LFLAGS_USR+= #============================================================================= # CXXFLAGS_USR lists the compilation flags specific for your library/device/exe # This is the place where to put your compile-time macros using '-Dmy_macro' # # -DACE_HAS_EXCEPTIONS -D__ACE_INLINE__ for ACE # #CXXFLAGS_USR+= -Wall #============================================================================= # TANGO_REQUIRED # - TRUE : your project depends on TANGO # - FALSE : your project does not depend on TANGO #----------------------------------------------------------------------------- # - NOTE : if PROJECT_TYPE is set to DEVICE, TANGO will be auto. added #----------------------------------------------------------------------------- TANGO_REQUIRED = TRUE #============================================================================= # include Standard TANGO compilation options # include $(MAKE_ENV)/tango.opt #============================================================================= # POST_PROCESSING: action to be done after normal make. # e.g.: change executable file name, ..... #POST_PROCESSING = \ # mv bin/$(BIN_DIR)/$(PACKAGE_NAME) bin/$(BIN_DIR)/$(PACKAGE_NAME)_DS #============================================================================= # SVC_OBJS is the list of all objects needed to make the output # SVC_INCL = $(PACKAGE_NAME).h $(PACKAGE_NAME)Class.h SVC_OBJS = \ $(LIB_OBJS) \ $(OBJDIR)/ClassFactory.o \ $(OBJDIR)/main.o LIB_OBJS = \ $(OBJDIR)/$(PACKAGE_NAME).o \ $(OBJDIR)/$(PACKAGE_NAME)Class.o \ $(OBJDIR)/$(PACKAGE_NAME)StateMachine.o \ $(OBJDIR)/$(PACKAGE_NAME)DynAttrUtils.o \ $(ADDITIONAL_OBJS) SVC_INHERITANCE_OBJ = \ #============================================================================= # include common targets # include $(MAKE_ENV)/common_target.opt pytango-9.2.2/examples/interfacechangeEvents/main.cpp000066400000000000000000000051561316324373100227600ustar00rootroot00000000000000/*----- PROTECTED REGION ID(IfchangeServer::main.cpp) ENABLED START -----*/ static const char *RcsId = "$Id: $"; //============================================================================= // // file : main.cpp // // description : C++ source for the IfchangeServer device server main. // The main rule is to initialise (and create) the Tango // system and to create the DServerClass singleton. // The main should be the same for every Tango device server. // // project : // // This file is part of Tango device class. // // Tango 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. // // Tango 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 Tango. If not, see . // // $Author: $ // // $Revision: $ // $Date: $ // // $HeadURL: $ // //============================================================================= // This file is generated by POGO // (Program Obviously used to Generate tango Object) //============================================================================= #include // Check if crash reporting is used. #if defined(ENABLE_CRASH_REPORT) # include #else # define DECLARE_CRASH_HANDLER # define INSTALL_CRASH_HANDLER #endif DECLARE_CRASH_HANDLER; int main(int argc,char *argv[]) { INSTALL_CRASH_HANDLER try { // Initialise the device server //---------------------------------------- Tango::Util *tg = Tango::Util::init(argc,argv); // Create the device server singleton // which will create everything //---------------------------------------- tg->server_init(false); // Run the endless loop //---------------------------------------- cout << "Ready to accept request" << endl; tg->server_run(); } catch (bad_alloc &) { cout << "Can't allocate memory to store device object !!!" << endl; cout << "Exiting" << endl; } catch (CORBA::Exception &e) { Tango::Except::print_exception(e); cout << "Received a CORBA_Exception" << endl; cout << "Exiting" << endl; } Tango::Util::instance()->server_cleanup(); return(0); } /*----- PROTECTED REGION END -----*/ // IfchangeServer::main.cpp pytango-9.2.2/examples/mandatory/000077500000000000000000000000001316324373100170245ustar00rootroot00000000000000pytango-9.2.2/examples/mandatory/MandatoryPropertyServer.py000066400000000000000000000011561316324373100242730ustar00rootroot00000000000000from __future__ import print_function from tango import DevState from tango.server import Device, device_property class MandatoryPropertyServer(Device): Hostname = device_property( dtype='str', mandatory=True, doc='The controller host address') Port = device_property( dtype='int', default_value=3456, doc='The controller port number') def init_device(self): Device.init_device(self) print('Port: ', self.Port) print('Host: ', self.Hostname) self.set_state(DevState.ON) if __name__ == '__main__': MandatoryPropertyServer.run_server() pytango-9.2.2/examples/many/000077500000000000000000000000001316324373100157725ustar00rootroot00000000000000pytango-9.2.2/examples/many/many_client.py000066400000000000000000000006241316324373100206500ustar00rootroot00000000000000from time import time import tango.client N = 100 obj_names = ["obj%04d" % i for i in range(N)] start = time() objs = map(tango.client.Object, obj_names) dt = time() - start print "Took %fs to create %d objects (avg %fs/object)" % (dt, N, dt/N) start = time() res = [ obj.func3(1,2) for obj in objs ] dt = time() - start print "Took %fs to call func3 on %d objects (avg %fs/object)" % (dt, N, dt/N) pytango-9.2.2/examples/many/many_server.py000066400000000000000000000012621316324373100206770ustar00rootroot00000000000000from PyTango.server import Server class MyClass: def __init__(self): self.bla = 55.6 self.ble = 11 self.bli = False def func1(self): return "executed func1" def func2(self, v): return 2*v def func3(self, a, b, c=1, d=3): """Just some documentation""" return "done func3" def func(self, nap_time): import time time.sleep(nap_time) return "Finished sleep for {0}s".format(nap_time) server = Server("many", server_type="Server") N = 100 objs = [] for i in range(N): name = "obj%04d" % i obj = MyClass() objs.append(obj) server.register_object(obj, name) server.run() pytango-9.2.2/examples/multi/000077500000000000000000000000001316324373100161605ustar00rootroot00000000000000pytango-9.2.2/examples/multi/multi_client.py000066400000000000000000000014041316324373100212210ustar00rootroot00000000000000import PyTango.client my_object = PyTango.client.Object("multi_my_object") print("my_object.bla = {0}".format(my_object.bla)) print("my_object.ble = {0}".format(my_object.ble)) print("my_object.bli = {0}".format(my_object.bli)) r1 = my_object.func1() print("my_object.func1() = {0}".format(r1)) r2 = my_object.func2(96.44) print("my_object.func2(96.44) = {0}".format(r2)) r3 = my_object.func3(45.86, 'hello', d=False, c='world') print("my_object.func3(45.86, 'hello', d=False, c='world') = {0}".format(r3)) another_object = PyTango.client.Object("multi_another_object") r1 = another_object.is_valid() print("another_object.is_valid() = {0}".format(r1)) r2 = another_object.lets_go("hello, world!") print("another_object.lets_go('hello, world!') = {0}".format(r2)) pytango-9.2.2/examples/multi/multi_server.py000066400000000000000000000017001316324373100212500ustar00rootroot00000000000000from PyTango.server import Server class MyClass: def __init__(self): self.bla = 55.6 self.ble = 11 self.bli = False def func1(self): return "executed func1" def func2(self, v): return 2*v def func3(self, a, b, c=1, d=3): """Just some documentation""" return "done func3" class AnotherClass: def __init__(self, valid=True): self.__valid = valid def is_valid(self): return self.__valid def lets_go(self, p1): return "lets_go done!", p1 def fft(self, a, n=None, axis=-1): import numpy.fft return numpy.fft.fft(a, n=n, axis=axis) def array(self, a): return a my_object = MyClass() another_object = AnotherClass(valid=False) server = Server("multi", server_type="Server") server.register_object(my_object, "multi_my_object") server.register_object(another_object, "multi_another_object") server.run() pytango-9.2.2/examples/pipeEvents/000077500000000000000000000000001316324373100171505ustar00rootroot00000000000000pytango-9.2.2/examples/pipeEvents/ClassFactory.cpp000066400000000000000000000036001316324373100222500ustar00rootroot00000000000000/*----- PROTECTED REGION ID(PipeServer::ClassFactory.cpp) ENABLED START -----*/ static const char *RcsId = "$Id: $"; //============================================================================= // // file : ClassFactory.cpp // // description : C++ source for the class_factory method of the DServer // device class. This method is responsible for the creation of // all class singleton for a device server. It is called // at device server startup. // // project : // // This file is part of Tango device class. // // Tango 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. // // Tango 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 Tango. If not, see . // // $Author: $ // // $Revision: $ // $Date: $ // // $HeadURL: $ // //============================================================================= // This file is generated by POGO // (Program Obviously used to Generate tango Object) //============================================================================= #include #include // Add class header files if needed /** * Create PipeServer Class singleton and store it in DServer object. */ void Tango::DServer::class_factory() { // Add method class init if needed add_class(PipeServer_ns::PipeServerClass::init("PipeServer")); } /*----- PROTECTED REGION END -----*/ // PipeServer::ClassFactory.cpp pytango-9.2.2/examples/pipeEvents/Makefile000066400000000000000000000116671316324373100206230ustar00rootroot00000000000000#============================================================================= # # file : Makefile # # description : Makefile to generate a TANGO device server. # # project : PipeServer # # $Author: $ # # $Revision: $ # $Date: $ # #============================================================================= # This file is generated by POGO # (Program Obviously used to Generate tango Object) #============================================================================= # # #============================================================================= # MAKE_ENV is the path to find common environment to buil project # MAKE_ENV = /usr/local/share/pogo/preferences #============================================================================= # PACKAGE_NAME is the name of the library/device/exe you want to build # PACKAGE_NAME = PipeServer MAJOR_VERS = 1 MINOR_VERS = 0 RELEASE = Release_$(MAJOR_VERS)_$(MINOR_VERS) # #============================================================================= # # RELEASE_TYPE # # - DEBUG : debug symbols - no optimization # # - OPTIMIZED : no debug symbols - optimization level set to O2 # #----------------------------------------------------------------------------- RELEASE_TYPE = DEBUG #============================================================================= # OUTPUT_TYPE can be one of the following : # - 'STATIC_LIB' for a static library (.a) # - 'SHARED_LIB' for a dynamic library (.so) # - 'DEVICE' for a device server (will automatically include and link # with Tango dependencies) # - 'SIMPLE_EXE' for an executable with no dependency (for exemple the test tool # of a library with no Tango dependencies) # OUTPUT_TYPE = DEVICE #============================================================================= # OUTPUT_DIR is the directory which contains the build result. # if not set, the standard location is : # - ./shlib if OUTPUT_TYPE is SHARED_LIB # - ./lib if OUTPUT_TYPE is STATIC_LIB # - ./bin for others # #OUTPUT_DIR = #============================================================================= # INC_DIR_USER is the list of all include path needed by your sources # - for a device server, tango dependencies are automatically appended # - '-I ../include' and '-I .' are automatically appended in all cases # INC_DIR_USER= -I . #============================================================================= # LIB_DIR_USER is the list of user library directories # - for a device server, tango libraries directories are automatically appended # - '-L ../lib' is automatically appended in all cases # #LIB_DIR_USER= #============================================================================= # LFLAGS_USR is the list of user link flags # - for a device server, tango libraries directories are automatically appended # - '-ldl -lpthread' is automatically appended in all cases # # !!! ATTENTION !!! # Be aware that the order matters. # For example if you must link with libA, and if libA depends itself on libB # you must use '-lA -lB' in this order as link flags, otherwise you will get # 'undefined reference' errors # #LFLAGS_USR+= #============================================================================= # CXXFLAGS_USR lists the compilation flags specific for your library/device/exe # This is the place where to put your compile-time macros using '-Dmy_macro' # # -DACE_HAS_EXCEPTIONS -D__ACE_INLINE__ for ACE # CXXFLAGS_USR+= -std=c++11 #============================================================================= # TANGO_REQUIRED # - TRUE : your project depends on TANGO # - FALSE : your project does not depend on TANGO #----------------------------------------------------------------------------- # - NOTE : if PROJECT_TYPE is set to DEVICE, TANGO will be auto. added #----------------------------------------------------------------------------- TANGO_REQUIRED = TRUE #============================================================================= # include Standard TANGO compilation options # include $(MAKE_ENV)/tango.opt #============================================================================= # POST_PROCESSING: action to be done after normal make. # e.g.: change executable file name, ..... #POST_PROCESSING = \ # mv bin/$(BIN_DIR)/$(PACKAGE_NAME) bin/$(BIN_DIR)/$(PACKAGE_NAME)_DS #============================================================================= # SVC_OBJS is the list of all objects needed to make the output # SVC_INCL = $(PACKAGE_NAME).h $(PACKAGE_NAME)Class.h SVC_OBJS = \ $(LIB_OBJS) \ $(OBJDIR)/ClassFactory.o \ $(OBJDIR)/main.o LIB_OBJS = \ $(OBJDIR)/$(PACKAGE_NAME).o \ $(OBJDIR)/$(PACKAGE_NAME)Class.o \ $(OBJDIR)/$(PACKAGE_NAME)StateMachine.o \ $(ADDITIONAL_OBJS) SVC_INHERITANCE_OBJ = \ #============================================================================= # include common targets # include $(MAKE_ENV)/common_target.opt pytango-9.2.2/examples/pipeEvents/PipeEventClient.py000066400000000000000000000031741316324373100225650ustar00rootroot00000000000000import time import tango from tango import EventType class EventManager(): def __init__(self, dp): self._deviceProxy = dp if dp is not None: print "Subscribed to TestPipe" self._event_id = dp.subscribe_event("TestPipe", EventType.PIPE_EVENT, self) def unsubscribe(self): self._deviceProxy.unsubscribe_event(self._event_id) def push_event(self, ev): print "Event -----push_event-----------------" print "Timestamp: ", ev.reception_date print "Pipe name: ", ev.pipe_name print "Event type: ", ev.event print "Device server: ", ev.device print "Event error: ", ev.err if ev.err: print "Caught pipe exception" err = ev.errors[0] print "Error desc: ", err.desc print "Error origin: ", err.origin print "Error reason: ", err.reason print "Error severity: ", err.severity else: print "Nb data elts: ", ev.pipe_value.data_elt_nb print "Event name: ", ev.pipe_value.name print "Root blob name: ", ev.pipe_value.root_blob_name print "Elements names: ", ev.pipe_value.data_elt_names nb_elt = ev.pipe_value.data_elt_nb for i in range(nb_elt): print "Elements: ", ev.pipe_value.data[i] def main(): dev = tango.DeviceProxy('pipeServer/tango/1') print dev EventManager(dev) time.sleep(3000.0) if __name__ == '__main__': main() pytango-9.2.2/examples/pipeEvents/PipeServer.cpp000066400000000000000000000347241316324373100217520ustar00rootroot00000000000000/*----- PROTECTED REGION ID(PipeServer.cpp) ENABLED START -----*/ static const char *RcsId = "$Id: $"; //============================================================================= // // file : PipeServer.cpp // // description : C++ source for the PipeServer class and its commands. // The class is derived from Device. It represents the // CORBA servant object which will be accessed from the // network. All commands which can be executed on the // PipeServer are implemented in this file. // // project : // // This file is part of Tango device class. // // Tango 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. // // Tango 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 Tango. If not, see . // // $Author: $ // // $Revision: $ // $Date: $ // // $HeadURL: $ // //============================================================================= // This file is generated by POGO // (Program Obviously used to Generate tango Object) //============================================================================= #include #include /*----- PROTECTED REGION END -----*/ // PipeServer.cpp /** * PipeServer class description: * */ //================================================================ // The following table gives the correspondence // between command and method names. // // Command name | Method name //================================================================ // State | Inherited (no method) // Status | Inherited (no method) // cmd_push_pipe_event | cmd_push_pipe_event //================================================================ //================================================================ // Attributes managed is: //================================================================ //================================================================ namespace PipeServer_ns { /*----- PROTECTED REGION ID(PipeServer::namespace_starting) ENABLED START -----*/ // static initializations /*----- PROTECTED REGION END -----*/ // PipeServer::namespace_starting //-------------------------------------------------------- /** * Method : PipeServer::PipeServer() * Description : Constructors for a Tango device * implementing the classPipeServer */ //-------------------------------------------------------- PipeServer::PipeServer(Tango::DeviceClass *cl, string &s) : TANGO_BASE_CLASS(cl, s.c_str()) { /*----- PROTECTED REGION ID(PipeServer::constructor_1) ENABLED START -----*/ init_device(); /*----- PROTECTED REGION END -----*/ // PipeServer::constructor_1 } //-------------------------------------------------------- PipeServer::PipeServer(Tango::DeviceClass *cl, const char *s) : TANGO_BASE_CLASS(cl, s) { /*----- PROTECTED REGION ID(PipeServer::constructor_2) ENABLED START -----*/ init_device(); /*----- PROTECTED REGION END -----*/ // PipeServer::constructor_2 } //-------------------------------------------------------- PipeServer::PipeServer(Tango::DeviceClass *cl, const char *s, const char *d) : TANGO_BASE_CLASS(cl, s, d) { /*----- PROTECTED REGION ID(PipeServer::constructor_3) ENABLED START -----*/ init_device(); /*----- PROTECTED REGION END -----*/ // PipeServer::constructor_3 } //-------------------------------------------------------- /** * Method : PipeServer::delete_device() * Description : will be called at device destruction or at init command */ //-------------------------------------------------------- void PipeServer::delete_device() { DEBUG_STREAM << "PipeServer::delete_device() " << device_name << endl; /*----- PROTECTED REGION ID(PipeServer::delete_device) ENABLED START -----*/ // Delete device allocated objects /*----- PROTECTED REGION END -----*/ // PipeServer::delete_device } //-------------------------------------------------------- /** * Method : PipeServer::init_device() * Description : will be called at device initialization. */ //-------------------------------------------------------- void PipeServer::init_device() { DEBUG_STREAM << "PipeServer::init_device() create device " << device_name << endl; /*----- PROTECTED REGION ID(PipeServer::init_device_before) ENABLED START -----*/ // Initialization before get_device_property() call /*----- PROTECTED REGION END -----*/ // PipeServer::init_device_before // No device property to be read from database /*----- PROTECTED REGION ID(PipeServer::init_device) ENABLED START -----*/ // Initialize device /*----- PROTECTED REGION END -----*/ // PipeServer::init_device } //-------------------------------------------------------- /** * Method : PipeServer::always_executed_hook() * Description : method always executed before any command is executed */ //-------------------------------------------------------- void PipeServer::always_executed_hook() { DEBUG_STREAM << "PipeServer::always_executed_hook() " << device_name << endl; /*----- PROTECTED REGION ID(PipeServer::always_executed_hook) ENABLED START -----*/ // code always executed before all requests /*----- PROTECTED REGION END -----*/ // PipeServer::always_executed_hook } //-------------------------------------------------------- /** * Method : PipeServer::read_attr_hardware() * Description : Hardware acquisition for attributes */ //-------------------------------------------------------- void PipeServer::read_attr_hardware(TANGO_UNUSED(vector &attr_list)) { DEBUG_STREAM << "PipeServer::read_attr_hardware(vector &attr_list) entering... " << endl; /*----- PROTECTED REGION ID(PipeServer::read_attr_hardware) ENABLED START -----*/ // Add your own code /*----- PROTECTED REGION END -----*/ // PipeServer::read_attr_hardware } //-------------------------------------------------------- /** * Method : PipeServer::add_dynamic_attributes() * Description : Create the dynamic attributes if any * for specified device. */ //-------------------------------------------------------- void PipeServer::add_dynamic_attributes() { /*----- PROTECTED REGION ID(PipeServer::add_dynamic_attributes) ENABLED START -----*/ // Add your own code to create and add dynamic attributes if any /*----- PROTECTED REGION END -----*/ // PipeServer::add_dynamic_attributes } //-------------------------------------------------------- /** * Read pipe TestPipe related method * Description: */ //-------------------------------------------------------- void PipeServer::read_TestPipe(Tango::Pipe &pipe) { DEBUG_STREAM << "PipeServer::read_TestPipe(Tango::Pipe &pipe) entering... " << endl; /*----- PROTECTED REGION ID(PipeServer::read_TestPipe) ENABLED START -----*/ vector de_names; de_names.push_back("x"); de_names.push_back("y"); de_names.push_back("width"); de_names.push_back("height"); pipe.set_data_elt_names(de_names); Tango::DevFloat y,width,height; Tango::DevFloat x = 5.9; y=6.0; width=30.0; height=45.0; string root_name = "theBlob"; pipe.set_root_blob_name(root_name); pipe << x << y << width << height; /*----- PROTECTED REGION END -----*/ // PipeServer::read_TestPipe } //-------------------------------------------------------- /** * Write pipe TestPipe related method * Description: */ //-------------------------------------------------------- void PipeServer::write_TestPipe(Tango::WPipe &pipe) { DEBUG_STREAM << "PipeServer::write_TestPipe(Tango::WPipe &pipe) entering... " << endl; /*----- PROTECTED REGION ID(PipeServer::write_TestPipe) ENABLED START -----*/ cout << "root blob name " << pipe.get_root_blob_name() << endl; cout << "nb of data elements " << pipe.get_data_elt_nb() << endl; try { for (auto i=0; i data; pipe >> data; cout << "value " << data.value << endl; } else if (data_type == Tango::DEV_BOOLEAN) { Tango::DataElement data; pipe >> data; cout << "value " << data.value << endl; } else if (data_type == Tango::DEV_STRING) { Tango::DataElement data; pipe >> data; cout << "value " << data.value << endl; } else if (data_type == Tango::DEV_LONG64) { Tango::DataElement data; pipe >> data; cout << "value " << data.value << endl; } else if (data_type == Tango::DEV_STATE) { Tango::DataElement data; pipe >> data; cout << "value " << data.value << endl; } else if (data_type == Tango::DEVVAR_DOUBLEARRAY) { std::vector data; pipe >> data; for (std::vector::iterator it = data.begin() ; it != data.end(); ++it) cout << *it << " "; cout << endl; } else if (data_type == Tango::DEVVAR_LONG64ARRAY) { std::vector data; pipe >> data; for (std::vector::iterator it = data.begin() ; it != data.end(); ++it) cout << *it << " "; cout << endl; } else if (data_type == Tango::DEVVAR_STATEARRAY) { std::vector data; pipe >> data; for (std::vector::iterator it = data.begin() ; it != data.end(); ++it) cout << *it << " "; cout << endl; } else if (data_type == Tango::DEVVAR_STRINGARRAY) { std::vector data; pipe >> data; for (std::vector::iterator it = data.begin() ; it != data.end(); ++it) cout << *it << " "; cout << endl; } else if (data_type == Tango::DEVVAR_BOOLEANARRAY) { std::vector data; pipe >> data; for (std::vector::iterator it = data.begin() ; it != data.end(); ++it) cout << *it << " "; cout << endl; } } } catch (exception &e) { cout << "Exception: " << e.what() << endl; } // Add your own code here /*----- PROTECTED REGION END -----*/ // PipeServer::write_TestPipe } //-------------------------------------------------------- /** * Command cmd_push_pipe_event related method * Description: * * @param argin */ //-------------------------------------------------------- void PipeServer::cmd_push_pipe_event(Tango::DevShort argin) { DEBUG_STREAM << "PipeServer::cmd_push_pipe_event() - " << device_name << endl; /*----- PROTECTED REGION ID(PipeServer::cmd_push_pipe_event) ENABLED START -----*/ if (argin == 0) { Tango::DevicePipeBlob dpb("PipeEventCase0"); vector de_inner_inner_names; de_inner_inner_names.push_back("InnerInnerFirstDE"); de_inner_inner_names.push_back("InneraaaaaaaInnerSecondDE"); inner_inner_blob.set_data_elt_names(de_inner_inner_names); inner_inner_blob.set_name("InnerInner"); dl = 111; v_db.clear(); v_db.push_back(3.33); v_db.push_back(3.33); inner_inner_blob["InneraaaaaaaInnerSecondDE"] << v_db; inner_inner_blob["InnerInnerFirstDE"] << dl; vector de_inner_names; de_inner_names.push_back("InnerFirstDE"); de_inner_names.push_back("InnerSecondDE"); de_inner_names.push_back("InnerThirdDE"); inner_blob.set_data_elt_names(de_inner_names); inner_blob.set_name("Inner"); inner_str = "Grenoble"; inner_bool = true; inner_blob << inner_str << inner_inner_blob << inner_bool; vector de_names; de_names.push_back("1DE"); de_names.push_back("2DE"); dpb.set_data_elt_names(de_names); v_dl.clear(); v_dl.push_back(3); v_dl.push_back(4); v_dl.push_back(5); v_dl.push_back(6); dpb << inner_blob << v_dl; this->push_pipe_event("TestPipe",&dpb); } else if (argin == 1) { Tango::DevicePipeBlob dpb("PipeEventCase1"); vector de_names; de_names.push_back("Another_1DE"); de_names.push_back("Another_2DE"); dpb.set_data_elt_names(de_names); v_dl.clear(); v_dl.push_back(2); string str("Barcelona"); dpb << v_dl << str; this->push_pipe_event("TestPipe",&dpb); } else if (argin == 2) { Tango::DevicePipeBlob dpb("PipeEventCase2"); vector de_names; de_names.push_back("Qwerty_1DE"); de_names.push_back("Azerty_2DE"); dpb.set_data_elt_names(de_names); std::vector v_df; v_df.clear(); v_df.push_back(3.142); v_df.push_back(6.284); v_df.push_back(12.568); v_df.push_back(25.136); string str("Barcelona"); dpb << str << v_df; #ifdef WIN32 struct _timeb tv; tv.time = 10; tv.millitm = 0; this->push_pipe_event("TestPipe",&dpb,tv); #else struct timeval tv; tv.tv_sec = 10; tv.tv_usec = 0; this->push_pipe_event("TestPipe",&dpb,tv); #endif } else if (argin == 3) { Tango::DevErrorList del; del.length(1); del[0].reason = CORBA::string_dup("aaa"); del[0].desc = CORBA::string_dup("bbb"); del[0].origin = CORBA::string_dup("ccc"); Tango::DevFailed df(del); this->push_pipe_event("TestPipe",&df); } else if (argin == 4) { Tango::DevicePipeBlob dpb("PipeEventCase4"); vector de_names; de_names.push_back("Lunes"); de_names.push_back("Martes"); dpb.set_data_elt_names(de_names); string str("Girona"); v_dl.clear(); for (int loop = 0;loop < 3000;loop++) v_dl.push_back(loop); dpb << str << v_dl; this->push_pipe_event("TestPipe",&dpb); } /*----- PROTECTED REGION END -----*/ // PipeServer::cmd_push_pipe_event } //-------------------------------------------------------- /** * Method : PipeServer::add_dynamic_commands() * Description : Create the dynamic commands if any * for specified device. */ //-------------------------------------------------------- void PipeServer::add_dynamic_commands() { /*----- PROTECTED REGION ID(PipeServer::add_dynamic_commands) ENABLED START -----*/ // Add your own code to create and add dynamic commands if any /*----- PROTECTED REGION END -----*/ // PipeServer::add_dynamic_commands } /*----- PROTECTED REGION ID(PipeServer::namespace_ending) ENABLED START -----*/ // Additional Methods /*----- PROTECTED REGION END -----*/ // PipeServer::namespace_ending } // namespace pytango-9.2.2/examples/pipeEvents/PipeServer.h000066400000000000000000000113021316324373100214020ustar00rootroot00000000000000/*----- PROTECTED REGION ID(PipeServer.h) ENABLED START -----*/ //============================================================================= // // file : PipeServer.h // // description : Include file for the PipeServer class // // project : // // This file is part of Tango device class. // // Tango 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. // // Tango 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 Tango. If not, see . // // $Author: $ // // $Revision: $ // $Date: $ // // $HeadURL: $ // //============================================================================= // This file is generated by POGO // (Program Obviously used to Generate tango Object) //============================================================================= #ifndef PipeServer_H #define PipeServer_H #include /*----- PROTECTED REGION END -----*/ // PipeServer.h /** * PipeServer class description: * */ namespace PipeServer_ns { /*----- PROTECTED REGION ID(PipeServer::Additional Class Declarations) ENABLED START -----*/ // Additional Class Declarations /*----- PROTECTED REGION END -----*/ // PipeServer::Additional Class Declarations class PipeServer : public TANGO_BASE_CLASS { /*----- PROTECTED REGION ID(PipeServer::Data Members) ENABLED START -----*/ // Add your own data members Tango::DevLong dl; vector v_db; Tango::DevicePipeBlob inner_inner_blob; Tango::DevicePipeBlob inner_blob; string inner_str; Tango::DevBoolean inner_bool; vector v_dl; /*----- PROTECTED REGION END -----*/ // PipeServer::Data Members // Constructors and destructors public: /** * Constructs a newly device object. * * @param cl Class. * @param s Device Name */ PipeServer(Tango::DeviceClass *cl,string &s); /** * Constructs a newly device object. * * @param cl Class. * @param s Device Name */ PipeServer(Tango::DeviceClass *cl,const char *s); /** * Constructs a newly device object. * * @param cl Class. * @param s Device name * @param d Device description. */ PipeServer(Tango::DeviceClass *cl,const char *s,const char *d); /** * The device object destructor. */ ~PipeServer() {delete_device();}; // Miscellaneous methods public: /* * will be called at device destruction or at init command. */ void delete_device(); /* * Initialize the device */ virtual void init_device(); /* * Always executed method before execution command method. */ virtual void always_executed_hook(); // Attribute methods public: //-------------------------------------------------------- /* * Method : PipeServer::read_attr_hardware() * Description : Hardware acquisition for attributes. */ //-------------------------------------------------------- virtual void read_attr_hardware(vector &attr_list); //-------------------------------------------------------- /** * Method : PipeServer::add_dynamic_attributes() * Description : Add dynamic attributes if any. */ //-------------------------------------------------------- void add_dynamic_attributes(); // pipe related methods public: // Pipe TestPipe bool is_TestPipe_allowed(Tango::PipeReqType); void read_TestPipe(Tango::Pipe &); void write_TestPipe(Tango::WPipe &); // Command related methods public: /** * Command cmd_push_pipe_event related method * Description: * * @param argin */ virtual void cmd_push_pipe_event(Tango::DevShort argin); virtual bool is_cmd_push_pipe_event_allowed(const CORBA::Any &any); //-------------------------------------------------------- /** * Method : PipeServer::add_dynamic_commands() * Description : Add dynamic commands if any. */ //-------------------------------------------------------- void add_dynamic_commands(); /*----- PROTECTED REGION ID(PipeServer::Additional Method prototypes) ENABLED START -----*/ // Additional Method prototypes /*----- PROTECTED REGION END -----*/ // PipeServer::Additional Method prototypes }; /*----- PROTECTED REGION ID(PipeServer::Additional Classes Definitions) ENABLED START -----*/ // Additional Classes Definitions /*----- PROTECTED REGION END -----*/ // PipeServer::Additional Classes Definitions } // End of namespace #endif // PipeServer_H pytango-9.2.2/examples/pipeEvents/PipeServer.py000066400000000000000000000056161316324373100216160ustar00rootroot00000000000000 import tango from tango import DevState from tango import GreenMode from tango.server import Device from tango.server import command, pipe class PipeServer(Device): def init_device(self): Device.init_device(self) self.rootBlobName = 'theBlob' self.__blob = self.rootBlobName, dict(x=5, y=7, width=101, height=230) self._send_task = None @pipe(label="Test pipe", fisallowed="is_TestPipe_allowed") def TestPipe(self): return self.__blob def is_TestPipe_allowed(self, pipeReqType): """ pipeReqType is either READ_REQ or WRITE_REQ.""" if pipeReqType == tango.AttReqType.READ_REQ: return self.get_state() not in [DevState.FAULT, DevState.OFF] else: return self.get_state() not in [DevState.FAULT, DevState.OFF, DevState.MOVING] @command(dtype_in=('int'), doc_in="Pipe event test case 0 - 4") def cmd_push_pipe_event(self, argin): if argin == 0: float_list = [3.33, 3.34, 3.35, 3.36] inner_int_list = [11, 12, 13, 14, 15] inner_inner_data = [("InnerInnerFirstDE", 111), ("InnerInnerSecondDE", float_list), ("InnerInnerThirdDE", inner_int_list)] inner_inner_blob = ("InnerInner", dict(inner_inner_data)) inner_data = [("InnerFirstDE", "Grenoble"), ("InnerSecondDE", inner_inner_blob), ("InnerThirdDE", True)] inner_blob = ("Inner", dict(inner_data)) int_list = [3, 4, 5, 6] pipe_data = [("1DE", inner_blob), ("2DE", int_list)] blob = ("PipeEvent0", dict(pipe_data)) self.push_pipe_event("TestPipe", blob) elif argin == 1: pipeData = [("Another_1DE", 2), ("Another_2DE", "Barcelona"), ("Another_3DE", 45.67)] blob = "PipeEventCase1", dict(pipeData) self.push_pipe_event("TestPipe", blob) elif argin == 2: float_list = [3.142, 6.284, 12.568] string_list = ["ESRF", "Alba", "MAXIV"] pipeData = [("Qwerty_1DE", "Barcelona"), ("Azerty_2DE", float_list), ("Xserty", string_list)] blob = "PipeEventCase2", dict(pipeData) self.push_pipe_event("TestPipe", blob) elif argin == 3: print "not coded yet" elif argin == 4: alist = [k for k in range(30)] pipeData = [("Lunes", "Girona"), ("Martes", alist)] blob = "PipeEventCase4", dict(pipeData) self.push_pipe_event("TestPipe", blob) else: print "Invalid test case: Use 0-4" # # Run server # if __name__ == '__main__': PipeServer.run_server(green_mode=GreenMode.Gevent) pytango-9.2.2/examples/pipeEvents/PipeServer.xmi000066400000000000000000000047201316324373100217560ustar00rootroot00000000000000 pytango-9.2.2/examples/pipeEvents/PipeServerClass.cpp000066400000000000000000000452001316324373100227270ustar00rootroot00000000000000/*----- PROTECTED REGION ID(PipeServerClass.cpp) ENABLED START -----*/ static const char *RcsId = "$Id: $"; static const char *TagName = "$Name: $"; static const char *CvsPath = "$Source: $"; static const char *SvnPath = "$HeadURL: $"; static const char *HttpServer = "http://www.esrf.eu/computing/cs/tango/tango_doc/ds_doc/"; //============================================================================= // // file : PipeServerClass.cpp // // description : C++ source for the PipeServerClass. // A singleton class derived from DeviceClass. // It implements the command and attribute list // and all properties and methods required // by the PipeServer once per process. // // project : // // This file is part of Tango device class. // // Tango 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. // // Tango 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 Tango. If not, see . // // $Author: $ // // $Revision: $ // $Date: $ // // $HeadURL: $ // //============================================================================= // This file is generated by POGO // (Program Obviously used to Generate tango Object) //============================================================================= #include /*----- PROTECTED REGION END -----*/ // PipeServerClass.cpp //------------------------------------------------------------------- /** * Create PipeServerClass singleton and * return it in a C function for Python usage */ //------------------------------------------------------------------- extern "C" { #ifdef _TG_WINDOWS_ __declspec(dllexport) #endif Tango::DeviceClass *_create_PipeServer_class(const char *name) { return PipeServer_ns::PipeServerClass::init(name); } } namespace PipeServer_ns { //=================================================================== // Initialize pointer for singleton pattern //=================================================================== PipeServerClass *PipeServerClass::_instance = NULL; //-------------------------------------------------------- /** * method : PipeServerClass::PipeServerClass(string &s) * description : constructor for the PipeServerClass * * @param s The class name */ //-------------------------------------------------------- PipeServerClass::PipeServerClass(string &s):Tango::DeviceClass(s) { cout2 << "Entering PipeServerClass constructor" << endl; set_default_property(); write_class_property(); /*----- PROTECTED REGION ID(PipeServerClass::constructor) ENABLED START -----*/ /*----- PROTECTED REGION END -----*/ // PipeServerClass::constructor cout2 << "Leaving PipeServerClass constructor" << endl; } //-------------------------------------------------------- /** * method : PipeServerClass::~PipeServerClass() * description : destructor for the PipeServerClass */ //-------------------------------------------------------- PipeServerClass::~PipeServerClass() { /*----- PROTECTED REGION ID(PipeServerClass::destructor) ENABLED START -----*/ /*----- PROTECTED REGION END -----*/ // PipeServerClass::destructor _instance = NULL; } //-------------------------------------------------------- /** * method : PipeServerClass::init * description : Create the object if not already done. * Otherwise, just return a pointer to the object * * @param name The class name */ //-------------------------------------------------------- PipeServerClass *PipeServerClass::init(const char *name) { if (_instance == NULL) { try { string s(name); _instance = new PipeServerClass(s); } catch (bad_alloc &) { throw; } } return _instance; } //-------------------------------------------------------- /** * method : PipeServerClass::instance * description : Check if object already created, * and return a pointer to the object */ //-------------------------------------------------------- PipeServerClass *PipeServerClass::instance() { if (_instance == NULL) { cerr << "Class is not initialised !!" << endl; exit(-1); } return _instance; } //=================================================================== // Command execution method calls //=================================================================== //-------------------------------------------------------- /** * method : cmd_push_pipe_eventClass::execute() * description : method to trigger the execution of the command. * * @param device The device on which the command must be executed * @param in_any The command input data * * returns The command output data (packed in the Any object) */ //-------------------------------------------------------- CORBA::Any *cmd_push_pipe_eventClass::execute(Tango::DeviceImpl *device, const CORBA::Any &in_any) { cout2 << "cmd_push_pipe_eventClass::execute(): arrived" << endl; Tango::DevShort argin; extract(in_any, argin); ((static_cast(device))->cmd_push_pipe_event(argin)); return new CORBA::Any(); } //=================================================================== // Properties management //=================================================================== //-------------------------------------------------------- /** * Method : PipeServerClass::get_class_property() * Description : Get the class property for specified name. */ //-------------------------------------------------------- Tango::DbDatum PipeServerClass::get_class_property(string &prop_name) { for (unsigned int i=0 ; i vect_data; // Set Default Class Properties // Set Default device Properties } //-------------------------------------------------------- /** * Method : PipeServerClass::write_class_property() * Description : Set class description fields as property in database */ //-------------------------------------------------------- void PipeServerClass::write_class_property() { // First time, check if database used if (Tango::Util::_UseDb == false) return; Tango::DbData data; string classname = get_name(); string header; string::size_type start, end; // Put title Tango::DbDatum title("ProjectTitle"); string str_title(""); title << str_title; data.push_back(title); // Put Description Tango::DbDatum description("Description"); vector str_desc; str_desc.push_back(""); description << str_desc; data.push_back(description); // put cvs or svn location string filename("PipeServer"); filename += "Class.cpp"; // check for cvs information string src_path(CvsPath); start = src_path.find("/"); if (start!=string::npos) { end = src_path.find(filename); if (end>start) { string strloc = src_path.substr(start, end-start); // Check if specific repository start = strloc.find("/cvsroot/"); if (start!=string::npos && start>0) { string repository = strloc.substr(0, start); if (repository.find("/segfs/")!=string::npos) strloc = "ESRF:" + strloc.substr(start, strloc.length()-start); } Tango::DbDatum cvs_loc("cvs_location"); cvs_loc << strloc; data.push_back(cvs_loc); } } // check for svn information else { string src_path(SvnPath); start = src_path.find("://"); if (start!=string::npos) { end = src_path.find(filename); if (end>start) { header = "$HeadURL: "; start = header.length(); string strloc = src_path.substr(start, (end-start)); Tango::DbDatum svn_loc("svn_location"); svn_loc << strloc; data.push_back(svn_loc); } } } // Get CVS or SVN revision tag // CVS tag string tagname(TagName); header = "$Name: "; start = header.length(); string endstr(" $"); end = tagname.find(endstr); if (end!=string::npos && end>start) { string strtag = tagname.substr(start, end-start); Tango::DbDatum cvs_tag("cvs_tag"); cvs_tag << strtag; data.push_back(cvs_tag); } // SVN tag string svnpath(SvnPath); header = "$HeadURL: "; start = header.length(); end = svnpath.find(endstr); if (end!=string::npos && end>start) { string strloc = svnpath.substr(start, end-start); string tagstr ("/tags/"); start = strloc.find(tagstr); if ( start!=string::npos ) { start = start + tagstr.length(); end = strloc.find(filename); string strtag = strloc.substr(start, end-start-1); Tango::DbDatum svn_tag("svn_tag"); svn_tag << strtag; data.push_back(svn_tag); } } // Get URL location string httpServ(HttpServer); if (httpServ.length()>0) { Tango::DbDatum db_doc_url("doc_url"); db_doc_url << httpServ; data.push_back(db_doc_url); } // Put inheritance Tango::DbDatum inher_datum("InheritedFrom"); vector inheritance; inheritance.push_back("TANGO_BASE_CLASS"); inher_datum << inheritance; data.push_back(inher_datum); // Call database and and values get_db_class()->put_property(data); } //=================================================================== // Factory methods //=================================================================== //-------------------------------------------------------- /** * Method : PipeServerClass::device_factory() * Description : Create the device object(s) * and store them in the device list */ //-------------------------------------------------------- void PipeServerClass::device_factory(const Tango::DevVarStringArray *devlist_ptr) { /*----- PROTECTED REGION ID(PipeServerClass::device_factory_before) ENABLED START -----*/ // Add your own code /*----- PROTECTED REGION END -----*/ // PipeServerClass::device_factory_before // Create devices and add it into the device list for (unsigned long i=0 ; ilength() ; i++) { cout4 << "Device name : " << (*devlist_ptr)[i].in() << endl; device_list.push_back(new PipeServer(this, (*devlist_ptr)[i])); } // Manage dynamic attributes if any erase_dynamic_attributes(devlist_ptr, get_class_attr()->get_attr_list()); // Export devices to the outside world for (unsigned long i=1 ; i<=devlist_ptr->length() ; i++) { // Add dynamic attributes if any PipeServer *dev = static_cast(device_list[device_list.size()-i]); dev->add_dynamic_attributes(); // Check before if database used. if ((Tango::Util::_UseDb == true) && (Tango::Util::_FileDb == false)) export_device(dev); else export_device(dev, dev->get_name().c_str()); } /*----- PROTECTED REGION ID(PipeServerClass::device_factory_after) ENABLED START -----*/ // Add your own code /*----- PROTECTED REGION END -----*/ // PipeServerClass::device_factory_after } //-------------------------------------------------------- /** * Method : PipeServerClass::attribute_factory() * Description : Create the attribute object(s) * and store them in the attribute list */ //-------------------------------------------------------- void PipeServerClass::attribute_factory(vector &att_list) { /*----- PROTECTED REGION ID(PipeServerClass::attribute_factory_before) ENABLED START -----*/ // Add your own code /*----- PROTECTED REGION END -----*/ // PipeServerClass::attribute_factory_before // Create a list of static attributes create_static_attribute_list(get_class_attr()->get_attr_list()); /*----- PROTECTED REGION ID(PipeServerClass::attribute_factory_after) ENABLED START -----*/ // Add your own code /*----- PROTECTED REGION END -----*/ // PipeServerClass::attribute_factory_after } //-------------------------------------------------------- /** * Method : PipeServerClass::pipe_factory() * Description : Create the pipe object(s) * and store them in the pipe list */ //-------------------------------------------------------- void PipeServerClass::pipe_factory() { /*----- PROTECTED REGION ID(PipeServerClass::pipe_factory_before) ENABLED START -----*/ // Add your own code /*----- PROTECTED REGION END -----*/ // PipeServerClass::pipe_factory_before Tango::UserDefaultPipeProp udpp; TestPipeClass *pTestPipe = new TestPipeClass("TestPipe",Tango::OPERATOR); udpp.set_description(""); udpp.set_label(""); pTestPipe->set_default_properties(udpp); pipe_list.push_back(pTestPipe); /*----- PROTECTED REGION ID(PipeServerClass::pipe_factory_after) ENABLED START -----*/ // Add your own code /*----- PROTECTED REGION END -----*/ // PipeServerClass::pipe_factory_after } //-------------------------------------------------------- /** * Method : PipeServerClass::command_factory() * Description : Create the command object(s) * and store them in the command list */ //-------------------------------------------------------- void PipeServerClass::command_factory() { /*----- PROTECTED REGION ID(PipeServerClass::command_factory_before) ENABLED START -----*/ // Add your own code /*----- PROTECTED REGION END -----*/ // PipeServerClass::command_factory_before // Command cmd_push_pipe_event cmd_push_pipe_eventClass *pcmd_push_pipe_eventCmd = new cmd_push_pipe_eventClass("cmd_push_pipe_event", Tango::DEV_SHORT, Tango::DEV_VOID, "", "", Tango::OPERATOR); command_list.push_back(pcmd_push_pipe_eventCmd); /*----- PROTECTED REGION ID(PipeServerClass::command_factory_after) ENABLED START -----*/ // Add your own code /*----- PROTECTED REGION END -----*/ // PipeServerClass::command_factory_after } //=================================================================== // Dynamic attributes related methods //=================================================================== //-------------------------------------------------------- /** * method : PipeServerClass::create_static_attribute_list * description : Create the a list of static attributes * * @param att_list the ceated attribute list */ //-------------------------------------------------------- void PipeServerClass::create_static_attribute_list(vector &att_list) { for (unsigned long i=0 ; iget_name()); transform(att_name.begin(), att_name.end(), att_name.begin(), ::tolower); defaultAttList.push_back(att_name); } cout2 << defaultAttList.size() << " attributes in default list" << endl; /*----- PROTECTED REGION ID(PipeServerClass::create_static_att_list) ENABLED START -----*/ /*----- PROTECTED REGION END -----*/ // PipeServerClass::create_static_att_list } //-------------------------------------------------------- /** * method : PipeServerClass::erase_dynamic_attributes * description : delete the dynamic attributes if any. * * @param devlist_ptr the device list pointer * @param list of all attributes */ //-------------------------------------------------------- void PipeServerClass::erase_dynamic_attributes(const Tango::DevVarStringArray *devlist_ptr, vector &att_list) { Tango::Util *tg = Tango::Util::instance(); for (unsigned long i=0 ; ilength() ; i++) { Tango::DeviceImpl *dev_impl = tg->get_device_by_name(((string)(*devlist_ptr)[i]).c_str()); PipeServer *dev = static_cast (dev_impl); vector &dev_att_list = dev->get_device_attr()->get_attribute_list(); vector::iterator ite_att; for (ite_att=dev_att_list.begin() ; ite_att != dev_att_list.end() ; ++ite_att) { string att_name((*ite_att)->get_name_lower()); if ((att_name == "state") || (att_name == "status")) continue; vector::iterator ite_str = find(defaultAttList.begin(), defaultAttList.end(), att_name); if (ite_str == defaultAttList.end()) { cout2 << att_name << " is a UNWANTED dynamic attribute for device " << (*devlist_ptr)[i] << endl; Tango::Attribute &att = dev->get_device_attr()->get_attr_by_name(att_name.c_str()); dev->remove_attribute(att_list[att.get_attr_idx()], true, false); --ite_att; } } } /*----- PROTECTED REGION ID(PipeServerClass::erase_dynamic_attributes) ENABLED START -----*/ /*----- PROTECTED REGION END -----*/ // PipeServerClass::erase_dynamic_attributes } //-------------------------------------------------------- /** * Method : PipeServerClass::get_attr_by_name() * Description : returns Tango::Attr * object found by name */ //-------------------------------------------------------- Tango::Attr *PipeServerClass::get_attr_object_by_name(vector &att_list, string attname) { vector::iterator it; for (it=att_list.begin() ; itget_name()==attname) return (*it); // Attr does not exist return NULL; } /*----- PROTECTED REGION ID(PipeServerClass::Additional Methods) ENABLED START -----*/ /*----- PROTECTED REGION END -----*/ // PipeServerClass::Additional Methods } // namespace pytango-9.2.2/examples/pipeEvents/PipeServerClass.h000066400000000000000000000116401316324373100223750ustar00rootroot00000000000000/*----- PROTECTED REGION ID(PipeServerClass.h) ENABLED START -----*/ //============================================================================= // // file : PipeServerClass.h // // description : Include for the PipeServer root class. // This class is the singleton class for // the PipeServer device class. // It contains all properties and methods which the // PipeServer requires only once e.g. the commands. // // project : // // This file is part of Tango device class. // // Tango 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. // // Tango 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 Tango. If not, see . // // $Author: $ // // $Revision: $ // $Date: $ // // $HeadURL: $ // //============================================================================= // This file is generated by POGO // (Program Obviously used to Generate tango Object) //============================================================================= #ifndef PipeServerClass_H #define PipeServerClass_H #include #include /*----- PROTECTED REGION END -----*/ // PipeServerClass.h namespace PipeServer_ns { /*----- PROTECTED REGION ID(PipeServerClass::classes for dynamic creation) ENABLED START -----*/ /*----- PROTECTED REGION END -----*/ // PipeServerClass::classes for dynamic creation //========================================= // Define classes for pipes //========================================= // Pipe TestPipe class definition class TestPipeClass: public Tango::WPipe { public: TestPipeClass(const string &name, Tango::DispLevel level) :WPipe(name, level) {}; ~TestPipeClass() {}; virtual bool is_allowed (Tango::DeviceImpl *dev,Tango::PipeReqType _prt) {return (static_cast(dev))->is_TestPipe_allowed(_prt);} virtual void read(Tango::DeviceImpl *dev) {(static_cast(dev))->read_TestPipe(*this);} virtual void write(Tango::DeviceImpl *dev) {(static_cast(dev))->write_TestPipe(*this);} }; //========================================= // Define classes for commands //========================================= // Command cmd_push_pipe_event class definition class cmd_push_pipe_eventClass : public Tango::Command { public: cmd_push_pipe_eventClass(const char *name, Tango::CmdArgType in, Tango::CmdArgType out, const char *in_desc, const char *out_desc, Tango::DispLevel level) :Command(name,in,out,in_desc,out_desc, level) {}; cmd_push_pipe_eventClass(const char *name, Tango::CmdArgType in, Tango::CmdArgType out) :Command(name,in,out) {}; ~cmd_push_pipe_eventClass() {}; virtual CORBA::Any *execute (Tango::DeviceImpl *dev, const CORBA::Any &any); virtual bool is_allowed (Tango::DeviceImpl *dev, const CORBA::Any &any) {return (static_cast(dev))->is_cmd_push_pipe_event_allowed(any);} }; /** * The PipeServerClass singleton definition */ #ifdef _TG_WINDOWS_ class __declspec(dllexport) PipeServerClass : public Tango::DeviceClass #else class PipeServerClass : public Tango::DeviceClass #endif { /*----- PROTECTED REGION ID(PipeServerClass::Additionnal DServer data members) ENABLED START -----*/ /*----- PROTECTED REGION END -----*/ // PipeServerClass::Additionnal DServer data members public: // write class properties data members Tango::DbData cl_prop; Tango::DbData cl_def_prop; Tango::DbData dev_def_prop; // Method prototypes static PipeServerClass *init(const char *); static PipeServerClass *instance(); ~PipeServerClass(); Tango::DbDatum get_class_property(string &); Tango::DbDatum get_default_device_property(string &); Tango::DbDatum get_default_class_property(string &); protected: PipeServerClass(string &); static PipeServerClass *_instance; void command_factory(); void attribute_factory(vector &); void pipe_factory(); void write_class_property(); void set_default_property(); void get_class_property(); string get_cvstag(); string get_cvsroot(); private: void device_factory(const Tango::DevVarStringArray *); void create_static_attribute_list(vector &); void erase_dynamic_attributes(const Tango::DevVarStringArray *,vector &); vector defaultAttList; Tango::Attr *get_attr_object_by_name(vector &att_list, string attname); }; } // End of namespace #endif // PipeServer_H pytango-9.2.2/examples/pipeEvents/PipeServerStateMachine.cpp000066400000000000000000000071471316324373100242370ustar00rootroot00000000000000/*----- PROTECTED REGION ID(PipeServerStateMachine.cpp) ENABLED START -----*/ static const char *RcsId = "$Id: $"; //============================================================================= // // file : PipeServerStateMachine.cpp // // description : State machine file for the PipeServer class // // project : // // This file is part of Tango device class. // // Tango 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. // // Tango 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 Tango. If not, see . // // $Author: $ // // $Revision: $ // $Date: $ // // $HeadURL: $ // //============================================================================= // This file is generated by POGO // (Program Obviously used to Generate tango Object) //============================================================================= #include /*----- PROTECTED REGION END -----*/ // PipeServer::PipeServerStateMachine.cpp //================================================================ // States | Description //================================================================ namespace PipeServer_ns { //================================================= // Attributes Allowed Methods //================================================= //================================================= // pipe Allowed Methods //================================================= //-------------------------------------------------------- /** * Method : PipeServer::is_TestPipe_allowed() * Description : Execution allowed for TestPipe pipe */ //-------------------------------------------------------- bool PipeServer::is_TestPipe_allowed(TANGO_UNUSED(Tango::PipeReqType type)) { // Not any excluded states for TestPipe pipe in Write access. /*----- PROTECTED REGION ID(PipeServer::TestPipeStateAllowed_WRITE) ENABLED START -----*/ /*----- PROTECTED REGION END -----*/ // PipeServer::TestPipeStateAllowed_WRITE // Not any excluded states for TestPipe pipe in read access. /*----- PROTECTED REGION ID(PipeServer::TestPipeStateAllowed_READ) ENABLED START -----*/ /*----- PROTECTED REGION END -----*/ // PipeServer::TestPipeStateAllowed_READ return true; } //================================================= // Commands Allowed Methods //================================================= //-------------------------------------------------------- /** * Method : PipeServer::is_cmd_push_pipe_event_allowed() * Description : Execution allowed for cmd_push_pipe_event attribute */ //-------------------------------------------------------- bool PipeServer::is_cmd_push_pipe_event_allowed(TANGO_UNUSED(const CORBA::Any &any)) { // Not any excluded states for cmd_push_pipe_event command. /*----- PROTECTED REGION ID(PipeServer::cmd_push_pipe_eventStateAllowed) ENABLED START -----*/ /*----- PROTECTED REGION END -----*/ // PipeServer::cmd_push_pipe_eventStateAllowed return true; } /*----- PROTECTED REGION ID(PipeServer::PipeServerStateAllowed.AdditionalMethods) ENABLED START -----*/ // Additional Methods /*----- PROTECTED REGION END -----*/ // PipeServer::PipeServerStateAllowed.AdditionalMethods } // End of namespace pytango-9.2.2/examples/pipeEvents/main.cpp000066400000000000000000000051421316324373100206020ustar00rootroot00000000000000/*----- PROTECTED REGION ID(PipeServer::main.cpp) ENABLED START -----*/ static const char *RcsId = "$Id: $"; //============================================================================= // // file : main.cpp // // description : C++ source for the PipeServer device server main. // The main rule is to initialise (and create) the Tango // system and to create the DServerClass singleton. // The main should be the same for every Tango device server. // // project : // // This file is part of Tango device class. // // Tango 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. // // Tango 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 Tango. If not, see . // // $Author: $ // // $Revision: $ // $Date: $ // // $HeadURL: $ // //============================================================================= // This file is generated by POGO // (Program Obviously used to Generate tango Object) //============================================================================= #include // Check if crash reporting is used. #if defined(ENABLE_CRASH_REPORT) # include #else # define DECLARE_CRASH_HANDLER # define INSTALL_CRASH_HANDLER #endif DECLARE_CRASH_HANDLER; int main(int argc,char *argv[]) { INSTALL_CRASH_HANDLER try { // Initialise the device server //---------------------------------------- Tango::Util *tg = Tango::Util::init(argc,argv); // Create the device server singleton // which will create everything //---------------------------------------- tg->server_init(false); // Run the endless loop //---------------------------------------- cout << "Ready to accept request" << endl; tg->server_run(); } catch (bad_alloc &) { cout << "Can't allocate memory to store device object !!!" << endl; cout << "Exiting" << endl; } catch (CORBA::Exception &e) { Tango::Except::print_exception(e); cout << "Received a CORBA_Exception" << endl; cout << "Exiting" << endl; } Tango::Util::instance()->server_cleanup(); return(0); } /*----- PROTECTED REGION END -----*/ // PipeServer::main.cpp pytango-9.2.2/examples/pipes/000077500000000000000000000000001316324373100161465ustar00rootroot00000000000000pytango-9.2.2/examples/pipes/ClassFactory.cpp000066400000000000000000000036001316324373100212460ustar00rootroot00000000000000/*----- PROTECTED REGION ID(PipeServer::ClassFactory.cpp) ENABLED START -----*/ static const char *RcsId = "$Id: $"; //============================================================================= // // file : ClassFactory.cpp // // description : C++ source for the class_factory method of the DServer // device class. This method is responsible for the creation of // all class singleton for a device server. It is called // at device server startup. // // project : // // This file is part of Tango device class. // // Tango 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. // // Tango 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 Tango. If not, see . // // $Author: $ // // $Revision: $ // $Date: $ // // $HeadURL: $ // //============================================================================= // This file is generated by POGO // (Program Obviously used to Generate tango Object) //============================================================================= #include #include // Add class header files if needed /** * Create PipeServer Class singleton and store it in DServer object. */ void Tango::DServer::class_factory() { // Add method class init if needed add_class(PipeServer_ns::PipeServerClass::init("PipeServer")); } /*----- PROTECTED REGION END -----*/ // PipeServer::ClassFactory.cpp pytango-9.2.2/examples/pipes/Makefile000066400000000000000000000116671316324373100176210ustar00rootroot00000000000000#============================================================================= # # file : Makefile # # description : Makefile to generate a TANGO device server. # # project : PipeServer # # $Author: $ # # $Revision: $ # $Date: $ # #============================================================================= # This file is generated by POGO # (Program Obviously used to Generate tango Object) #============================================================================= # # #============================================================================= # MAKE_ENV is the path to find common environment to buil project # MAKE_ENV = /usr/local/share/pogo/preferences #============================================================================= # PACKAGE_NAME is the name of the library/device/exe you want to build # PACKAGE_NAME = PipeServer MAJOR_VERS = 1 MINOR_VERS = 0 RELEASE = Release_$(MAJOR_VERS)_$(MINOR_VERS) # #============================================================================= # # RELEASE_TYPE # # - DEBUG : debug symbols - no optimization # # - OPTIMIZED : no debug symbols - optimization level set to O2 # #----------------------------------------------------------------------------- RELEASE_TYPE = DEBUG #============================================================================= # OUTPUT_TYPE can be one of the following : # - 'STATIC_LIB' for a static library (.a) # - 'SHARED_LIB' for a dynamic library (.so) # - 'DEVICE' for a device server (will automatically include and link # with Tango dependencies) # - 'SIMPLE_EXE' for an executable with no dependency (for exemple the test tool # of a library with no Tango dependencies) # OUTPUT_TYPE = DEVICE #============================================================================= # OUTPUT_DIR is the directory which contains the build result. # if not set, the standard location is : # - ./shlib if OUTPUT_TYPE is SHARED_LIB # - ./lib if OUTPUT_TYPE is STATIC_LIB # - ./bin for others # #OUTPUT_DIR = #============================================================================= # INC_DIR_USER is the list of all include path needed by your sources # - for a device server, tango dependencies are automatically appended # - '-I ../include' and '-I .' are automatically appended in all cases # INC_DIR_USER= -I . #============================================================================= # LIB_DIR_USER is the list of user library directories # - for a device server, tango libraries directories are automatically appended # - '-L ../lib' is automatically appended in all cases # #LIB_DIR_USER= #============================================================================= # LFLAGS_USR is the list of user link flags # - for a device server, tango libraries directories are automatically appended # - '-ldl -lpthread' is automatically appended in all cases # # !!! ATTENTION !!! # Be aware that the order matters. # For example if you must link with libA, and if libA depends itself on libB # you must use '-lA -lB' in this order as link flags, otherwise you will get # 'undefined reference' errors # #LFLAGS_USR+= #============================================================================= # CXXFLAGS_USR lists the compilation flags specific for your library/device/exe # This is the place where to put your compile-time macros using '-Dmy_macro' # # -DACE_HAS_EXCEPTIONS -D__ACE_INLINE__ for ACE # CXXFLAGS_USR+= -std=c++11 #============================================================================= # TANGO_REQUIRED # - TRUE : your project depends on TANGO # - FALSE : your project does not depend on TANGO #----------------------------------------------------------------------------- # - NOTE : if PROJECT_TYPE is set to DEVICE, TANGO will be auto. added #----------------------------------------------------------------------------- TANGO_REQUIRED = TRUE #============================================================================= # include Standard TANGO compilation options # include $(MAKE_ENV)/tango.opt #============================================================================= # POST_PROCESSING: action to be done after normal make. # e.g.: change executable file name, ..... #POST_PROCESSING = \ # mv bin/$(BIN_DIR)/$(PACKAGE_NAME) bin/$(BIN_DIR)/$(PACKAGE_NAME)_DS #============================================================================= # SVC_OBJS is the list of all objects needed to make the output # SVC_INCL = $(PACKAGE_NAME).h $(PACKAGE_NAME)Class.h SVC_OBJS = \ $(LIB_OBJS) \ $(OBJDIR)/ClassFactory.o \ $(OBJDIR)/main.o LIB_OBJS = \ $(OBJDIR)/$(PACKAGE_NAME).o \ $(OBJDIR)/$(PACKAGE_NAME)Class.o \ $(OBJDIR)/$(PACKAGE_NAME)StateMachine.o \ $(ADDITIONAL_OBJS) SVC_INHERITANCE_OBJ = \ #============================================================================= # include common targets # include $(MAKE_ENV)/common_target.opt pytango-9.2.2/examples/pipes/PipeClient.py000066400000000000000000000016661316324373100205650ustar00rootroot00000000000000import tango from tango import DevState dev = tango.DeviceProxy('pipeServer/tango/1') blob = dev.read_pipe('TestPipe') print 'PipeClient.py: ', blob blob = ('pipeWriteTest0', dict(x=5.9, y=15.1, anInt=-169, str="the test", truth=False, state=DevState.FAULT)) dev.write_pipe('TestPipe', blob) float_list = [3.33, 3.34, 3.35, 3.36] inner_int_list = [11, 12, 13, 14, 15] inner_inner_data = [("InnerInnerFirstDE", 111), ("InnerInnerSecondDE", float_list), ("InnerInnerThirdDE", inner_int_list)] inner_inner_blob = ("InnerInner", dict(inner_inner_data)) inner_data = [("InnerFirstDE", "Grenoble"), ("InnerSecondDE", inner_inner_blob), ("InnerThirdDE", True)] inner_blob = ("Inner", dict(inner_data)) int_list = [3, 4, 5, 6] pipe_data = [("1DE", inner_blob), ("2DE", int_list)] blob = ("PipeWriteTest1", dict(pipe_data)) dev.write_pipe('TestPipe', blob) pytango-9.2.2/examples/pipes/PipeServer.cpp000066400000000000000000000265241316324373100207470ustar00rootroot00000000000000/*----- PROTECTED REGION ID(PipeServer.cpp) ENABLED START -----*/ static const char *RcsId = "$Id: $"; //============================================================================= // // file : PipeServer.cpp // // description : C++ source for the PipeServer class and its commands. // The class is derived from Device. It represents the // CORBA servant object which will be accessed from the // network. All commands which can be executed on the // PipeServer are implemented in this file. // // project : // // This file is part of Tango device class. // // Tango 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. // // Tango 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 Tango. If not, see . // // $Author: $ // // $Revision: $ // $Date: $ // // $HeadURL: $ // //============================================================================= // This file is generated by POGO // (Program Obviously used to Generate tango Object) //============================================================================= #include #include /*----- PROTECTED REGION END -----*/ // PipeServer.cpp /** * PipeServer class description: * */ //================================================================ // The following table gives the correspondence // between command and method names. // // Command name | Method name //================================================================ // State | Inherited (no method) // Status | Inherited (no method) //================================================================ //================================================================ // Attributes managed is: //================================================================ //================================================================ namespace PipeServer_ns { /*----- PROTECTED REGION ID(PipeServer::namespace_starting) ENABLED START -----*/ // static initializations /*----- PROTECTED REGION END -----*/ // PipeServer::namespace_starting //-------------------------------------------------------- /** * Method : PipeServer::PipeServer() * Description : Constructors for a Tango device * implementing the classPipeServer */ //-------------------------------------------------------- PipeServer::PipeServer(Tango::DeviceClass *cl, string &s) : TANGO_BASE_CLASS(cl, s.c_str()) { /*----- PROTECTED REGION ID(PipeServer::constructor_1) ENABLED START -----*/ init_device(); /*----- PROTECTED REGION END -----*/ // PipeServer::constructor_1 } //-------------------------------------------------------- PipeServer::PipeServer(Tango::DeviceClass *cl, const char *s) : TANGO_BASE_CLASS(cl, s) { /*----- PROTECTED REGION ID(PipeServer::constructor_2) ENABLED START -----*/ init_device(); /*----- PROTECTED REGION END -----*/ // PipeServer::constructor_2 } //-------------------------------------------------------- PipeServer::PipeServer(Tango::DeviceClass *cl, const char *s, const char *d) : TANGO_BASE_CLASS(cl, s, d) { /*----- PROTECTED REGION ID(PipeServer::constructor_3) ENABLED START -----*/ init_device(); /*----- PROTECTED REGION END -----*/ // PipeServer::constructor_3 } //-------------------------------------------------------- /** * Method : PipeServer::delete_device() * Description : will be called at device destruction or at init command */ //-------------------------------------------------------- void PipeServer::delete_device() { DEBUG_STREAM << "PipeServer::delete_device() " << device_name << endl; /*----- PROTECTED REGION ID(PipeServer::delete_device) ENABLED START -----*/ // Delete device allocated objects /*----- PROTECTED REGION END -----*/ // PipeServer::delete_device } //-------------------------------------------------------- /** * Method : PipeServer::init_device() * Description : will be called at device initialization. */ //-------------------------------------------------------- void PipeServer::init_device() { DEBUG_STREAM << "PipeServer::init_device() create device " << device_name << endl; /*----- PROTECTED REGION ID(PipeServer::init_device_before) ENABLED START -----*/ // Initialization before get_device_property() call /*----- PROTECTED REGION END -----*/ // PipeServer::init_device_before // No device property to be read from database /*----- PROTECTED REGION ID(PipeServer::init_device) ENABLED START -----*/ // Initialize device /*----- PROTECTED REGION END -----*/ // PipeServer::init_device } //-------------------------------------------------------- /** * Method : PipeServer::always_executed_hook() * Description : method always executed before any command is executed */ //-------------------------------------------------------- void PipeServer::always_executed_hook() { DEBUG_STREAM << "PipeServer::always_executed_hook() " << device_name << endl; /*----- PROTECTED REGION ID(PipeServer::always_executed_hook) ENABLED START -----*/ // code always executed before all requests /*----- PROTECTED REGION END -----*/ // PipeServer::always_executed_hook } //-------------------------------------------------------- /** * Method : PipeServer::read_attr_hardware() * Description : Hardware acquisition for attributes */ //-------------------------------------------------------- void PipeServer::read_attr_hardware(TANGO_UNUSED(vector &attr_list)) { DEBUG_STREAM << "PipeServer::read_attr_hardware(vector &attr_list) entering... " << endl; /*----- PROTECTED REGION ID(PipeServer::read_attr_hardware) ENABLED START -----*/ // Add your own code /*----- PROTECTED REGION END -----*/ // PipeServer::read_attr_hardware } //-------------------------------------------------------- /** * Method : PipeServer::add_dynamic_attributes() * Description : Create the dynamic attributes if any * for specified device. */ //-------------------------------------------------------- void PipeServer::add_dynamic_attributes() { /*----- PROTECTED REGION ID(PipeServer::add_dynamic_attributes) ENABLED START -----*/ // Add your own code to create and add dynamic attributes if any /*----- PROTECTED REGION END -----*/ // PipeServer::add_dynamic_attributes } //-------------------------------------------------------- /** * Read pipe TestPipe related method * Description: */ //-------------------------------------------------------- void PipeServer::read_TestPipe(Tango::Pipe &pipe) { DEBUG_STREAM << "PipeServer::read_TestPipe(Tango::Pipe &pipe) entering... " << endl; /*----- PROTECTED REGION ID(PipeServer::read_TestPipe) ENABLED START -----*/ vector de_names; de_names.push_back("x"); de_names.push_back("y"); de_names.push_back("width"); de_names.push_back("height"); pipe.set_data_elt_names(de_names); Tango::DevFloat y,width,height; Tango::DevFloat x = 5.9; y=6.0; width=30.0; height=45.0; string root_name = "theBlob"; pipe.set_root_blob_name(root_name); pipe << x << y << width << height; /*----- PROTECTED REGION END -----*/ // PipeServer::read_TestPipe } //-------------------------------------------------------- /** * Write pipe TestPipe related method * Description: */ //-------------------------------------------------------- void PipeServer::write_TestPipe(Tango::WPipe &pipe) { DEBUG_STREAM << "PipeServer::write_TestPipe(Tango::WPipe &pipe) entering... " << endl; /*----- PROTECTED REGION ID(PipeServer::write_TestPipe) ENABLED START -----*/ cout << "root blob name " << pipe.get_root_blob_name() << endl; cout << "nb of data elements " << pipe.get_data_elt_nb() << endl; try { extract(pipe); } catch (exception &e) { cout << "Exception: " << e.what() << endl; } // Add your own code here /*----- PROTECTED REGION END -----*/ // PipeServer::write_TestPipe } //-------------------------------------------------------- /** * Method : PipeServer::add_dynamic_commands() * Description : Create the dynamic commands if any * for specified device. */ //-------------------------------------------------------- void PipeServer::add_dynamic_commands() { /*----- PROTECTED REGION ID(PipeServer::add_dynamic_commands) ENABLED START -----*/ // Add your own code to create and add dynamic commands if any /*----- PROTECTED REGION END -----*/ // PipeServer::add_dynamic_commands } /*----- PROTECTED REGION ID(PipeServer::namespace_ending) ENABLED START -----*/ template void PipeServer::extract(T& obj) { for (unsigned i = 0; i < obj.get_data_elt_nb(); i++) { cout << "name " << obj.get_data_elt_name(i) << endl; } for (unsigned i = 0; i < obj.get_data_elt_nb(); i++) { cout << "name " << obj.get_data_elt_name(i) << endl; int data_type = obj.get_data_elt_type(i); cout << "data_type " << data_type << endl; if (data_type == Tango::DEV_DOUBLE) { Tango::DataElement data; obj >> data; cout << "value " << data.value << endl; } else if (data_type == Tango::DEV_BOOLEAN) { Tango::DataElement data; obj >> data; cout << "value " << data.value << endl; } else if (data_type == Tango::DEV_STRING) { Tango::DataElement < std::string > data; obj >> data; cout << "value " << data.value << endl; } else if (data_type == Tango::DEV_LONG64) { Tango::DataElement < int64_t > data; obj >> data; cout << "value " << data.value << endl; } else if (data_type == Tango::DEV_STATE) { Tango::DataElement < Tango::DevState > data; obj >> data; cout << "value " << data.value << endl; } else if (data_type == Tango::DEVVAR_DOUBLEARRAY) { std::vector data; obj >> data; for (std::vector::iterator it = data.begin(); it != data.end(); ++it) cout << *it << " "; cout << endl; } else if (data_type == Tango::DEVVAR_LONG64ARRAY) { std::vector < int64_t > data; obj >> data; for (std::vector::iterator it = data.begin(); it != data.end(); ++it) cout << *it << " "; cout << endl; } else if (data_type == Tango::DEVVAR_STATEARRAY) { std::vector < Tango::DevState > data; obj >> data; for (std::vector::iterator it = data.begin(); it != data.end(); ++it) cout << *it << " "; cout << endl; } else if (data_type == Tango::DEVVAR_STRINGARRAY) { std::vector < std::string > data; obj >> data; for (std::vector::iterator it = data.begin(); it != data.end(); ++it) cout << *it << " "; cout << endl; } else if (data_type == Tango::DEVVAR_BOOLEANARRAY) { std::vector data; obj >> data; for (std::vector::iterator it = data.begin(); it != data.end(); ++it) cout << *it << " "; cout << endl; } else if (data_type == Tango::DEV_PIPE_BLOB) { cout << "Found inner blob" << endl; Tango::DevicePipeBlob blob; obj >> blob; extract (blob); } } } // Additional Methods /*----- PROTECTED REGION END -----*/ // PipeServer::namespace_ending } // namespace pytango-9.2.2/examples/pipes/PipeServer.h000066400000000000000000000104601316324373100204040ustar00rootroot00000000000000/*----- PROTECTED REGION ID(PipeServer.h) ENABLED START -----*/ //============================================================================= // // file : PipeServer.h // // description : Include file for the PipeServer class // // project : // // This file is part of Tango device class. // // Tango 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. // // Tango 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 Tango. If not, see . // // $Author: $ // // $Revision: $ // $Date: $ // // $HeadURL: $ // //============================================================================= // This file is generated by POGO // (Program Obviously used to Generate tango Object) //============================================================================= #ifndef PipeServer_H #define PipeServer_H #include /*----- PROTECTED REGION END -----*/ // PipeServer.h /** * PipeServer class description: * */ namespace PipeServer_ns { /*----- PROTECTED REGION ID(PipeServer::Additional Class Declarations) ENABLED START -----*/ // Additional Class Declarations /*----- PROTECTED REGION END -----*/ // PipeServer::Additional Class Declarations class PipeServer : public TANGO_BASE_CLASS { /*----- PROTECTED REGION ID(PipeServer::Data Members) ENABLED START -----*/ // Add your own data members /*----- PROTECTED REGION END -----*/ // PipeServer::Data Members // Constructors and destructors public: /** * Constructs a newly device object. * * @param cl Class. * @param s Device Name */ PipeServer(Tango::DeviceClass *cl,string &s); /** * Constructs a newly device object. * * @param cl Class. * @param s Device Name */ PipeServer(Tango::DeviceClass *cl,const char *s); /** * Constructs a newly device object. * * @param cl Class. * @param s Device name * @param d Device description. */ PipeServer(Tango::DeviceClass *cl,const char *s,const char *d); /** * The device object destructor. */ ~PipeServer() {delete_device();}; // Miscellaneous methods public: /* * will be called at device destruction or at init command. */ void delete_device(); /* * Initialize the device */ virtual void init_device(); /* * Always executed method before execution command method. */ virtual void always_executed_hook(); // Attribute methods public: //-------------------------------------------------------- /* * Method : PipeServer::read_attr_hardware() * Description : Hardware acquisition for attributes. */ //-------------------------------------------------------- virtual void read_attr_hardware(vector &attr_list); //-------------------------------------------------------- /** * Method : PipeServer::add_dynamic_attributes() * Description : Add dynamic attributes if any. */ //-------------------------------------------------------- void add_dynamic_attributes(); // pipe related methods public: // Pipe TestPipe bool is_TestPipe_allowed(Tango::PipeReqType); void read_TestPipe(Tango::Pipe &); void write_TestPipe(Tango::WPipe &); // Command related methods public: //-------------------------------------------------------- /** * Method : PipeServer::add_dynamic_commands() * Description : Add dynamic commands if any. */ //-------------------------------------------------------- void add_dynamic_commands(); /*----- PROTECTED REGION ID(PipeServer::Additional Method prototypes) ENABLED START -----*/ // Additional Method prototypes template void extract(T& obj); /*----- PROTECTED REGION END -----*/ // PipeServer::Additional Method prototypes }; /*----- PROTECTED REGION ID(PipeServer::Additional Classes Definitions) ENABLED START -----*/ // Additional Classes Definitions /*----- PROTECTED REGION END -----*/ // PipeServer::Additional Classes Definitions } // End of namespace #endif // PipeServer_H pytango-9.2.2/examples/pipes/PipeServer.py000066400000000000000000000021721316324373100206060ustar00rootroot00000000000000import tango from tango.server import Device from tango.server import pipe from tango import DevState class PipeServer(Device): def init_device(self): Device.init_device(self) self.rootBlobName = 'theBlob' self.__blob = self.rootBlobName, dict(x=0.3, y=10.22, width=105.1, height=206.6) @pipe(label="Test pipe", fisallowed="is_TestPipe_allowed") def TestPipe(self): return self.__blob @TestPipe.write def TestPipe(self, blob): self.__blob = blob print blob def is_TestPipe_allowed(self, pipeReqType): """ pipeReqType is either READ_REQ or WRITE_REQ.""" if pipeReqType == tango.AttReqType.READ_REQ: return self.get_state() not in [DevState.FAULT, DevState.OFF] else: return self.get_state() not in [DevState.FAULT, DevState.OFF, DevState.MOVING] # ---------- # Run server # ---------- if __name__ == '__main__': PipeServer.run_server() pytango-9.2.2/examples/pipes/PipeServer.xmi000066400000000000000000000040511316324373100207510ustar00rootroot00000000000000 pytango-9.2.2/examples/pipes/PipeServerClass.cpp000066400000000000000000000432401316324373100217270ustar00rootroot00000000000000/*----- PROTECTED REGION ID(PipeServerClass.cpp) ENABLED START -----*/ static const char *RcsId = "$Id: $"; static const char *TagName = "$Name: $"; static const char *CvsPath = "$Source: $"; static const char *SvnPath = "$HeadURL: $"; static const char *HttpServer = "http://www.esrf.eu/computing/cs/tango/tango_doc/ds_doc/"; //============================================================================= // // file : PipeServerClass.cpp // // description : C++ source for the PipeServerClass. // A singleton class derived from DeviceClass. // It implements the command and attribute list // and all properties and methods required // by the PipeServer once per process. // // project : // // This file is part of Tango device class. // // Tango 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. // // Tango 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 Tango. If not, see . // // $Author: $ // // $Revision: $ // $Date: $ // // $HeadURL: $ // //============================================================================= // This file is generated by POGO // (Program Obviously used to Generate tango Object) //============================================================================= #include /*----- PROTECTED REGION END -----*/ // PipeServerClass.cpp //------------------------------------------------------------------- /** * Create PipeServerClass singleton and * return it in a C function for Python usage */ //------------------------------------------------------------------- extern "C" { #ifdef _TG_WINDOWS_ __declspec(dllexport) #endif Tango::DeviceClass *_create_PipeServer_class(const char *name) { return PipeServer_ns::PipeServerClass::init(name); } } namespace PipeServer_ns { //=================================================================== // Initialize pointer for singleton pattern //=================================================================== PipeServerClass *PipeServerClass::_instance = NULL; //-------------------------------------------------------- /** * method : PipeServerClass::PipeServerClass(string &s) * description : constructor for the PipeServerClass * * @param s The class name */ //-------------------------------------------------------- PipeServerClass::PipeServerClass(string &s):Tango::DeviceClass(s) { cout2 << "Entering PipeServerClass constructor" << endl; set_default_property(); write_class_property(); /*----- PROTECTED REGION ID(PipeServerClass::constructor) ENABLED START -----*/ /*----- PROTECTED REGION END -----*/ // PipeServerClass::constructor cout2 << "Leaving PipeServerClass constructor" << endl; } //-------------------------------------------------------- /** * method : PipeServerClass::~PipeServerClass() * description : destructor for the PipeServerClass */ //-------------------------------------------------------- PipeServerClass::~PipeServerClass() { /*----- PROTECTED REGION ID(PipeServerClass::destructor) ENABLED START -----*/ /*----- PROTECTED REGION END -----*/ // PipeServerClass::destructor _instance = NULL; } //-------------------------------------------------------- /** * method : PipeServerClass::init * description : Create the object if not already done. * Otherwise, just return a pointer to the object * * @param name The class name */ //-------------------------------------------------------- PipeServerClass *PipeServerClass::init(const char *name) { if (_instance == NULL) { try { string s(name); _instance = new PipeServerClass(s); } catch (bad_alloc &) { throw; } } return _instance; } //-------------------------------------------------------- /** * method : PipeServerClass::instance * description : Check if object already created, * and return a pointer to the object */ //-------------------------------------------------------- PipeServerClass *PipeServerClass::instance() { if (_instance == NULL) { cerr << "Class is not initialised !!" << endl; exit(-1); } return _instance; } //=================================================================== // Command execution method calls //=================================================================== //=================================================================== // Properties management //=================================================================== //-------------------------------------------------------- /** * Method : PipeServerClass::get_class_property() * Description : Get the class property for specified name. */ //-------------------------------------------------------- Tango::DbDatum PipeServerClass::get_class_property(string &prop_name) { for (unsigned int i=0 ; i vect_data; // Set Default Class Properties // Set Default device Properties } //-------------------------------------------------------- /** * Method : PipeServerClass::write_class_property() * Description : Set class description fields as property in database */ //-------------------------------------------------------- void PipeServerClass::write_class_property() { // First time, check if database used if (Tango::Util::_UseDb == false) return; Tango::DbData data; string classname = get_name(); string header; string::size_type start, end; // Put title Tango::DbDatum title("ProjectTitle"); string str_title(""); title << str_title; data.push_back(title); // Put Description Tango::DbDatum description("Description"); vector str_desc; str_desc.push_back(""); description << str_desc; data.push_back(description); // put cvs or svn location string filename("PipeServer"); filename += "Class.cpp"; // check for cvs information string src_path(CvsPath); start = src_path.find("/"); if (start!=string::npos) { end = src_path.find(filename); if (end>start) { string strloc = src_path.substr(start, end-start); // Check if specific repository start = strloc.find("/cvsroot/"); if (start!=string::npos && start>0) { string repository = strloc.substr(0, start); if (repository.find("/segfs/")!=string::npos) strloc = "ESRF:" + strloc.substr(start, strloc.length()-start); } Tango::DbDatum cvs_loc("cvs_location"); cvs_loc << strloc; data.push_back(cvs_loc); } } // check for svn information else { string src_path(SvnPath); start = src_path.find("://"); if (start!=string::npos) { end = src_path.find(filename); if (end>start) { header = "$HeadURL: "; start = header.length(); string strloc = src_path.substr(start, (end-start)); Tango::DbDatum svn_loc("svn_location"); svn_loc << strloc; data.push_back(svn_loc); } } } // Get CVS or SVN revision tag // CVS tag string tagname(TagName); header = "$Name: "; start = header.length(); string endstr(" $"); end = tagname.find(endstr); if (end!=string::npos && end>start) { string strtag = tagname.substr(start, end-start); Tango::DbDatum cvs_tag("cvs_tag"); cvs_tag << strtag; data.push_back(cvs_tag); } // SVN tag string svnpath(SvnPath); header = "$HeadURL: "; start = header.length(); end = svnpath.find(endstr); if (end!=string::npos && end>start) { string strloc = svnpath.substr(start, end-start); string tagstr ("/tags/"); start = strloc.find(tagstr); if ( start!=string::npos ) { start = start + tagstr.length(); end = strloc.find(filename); string strtag = strloc.substr(start, end-start-1); Tango::DbDatum svn_tag("svn_tag"); svn_tag << strtag; data.push_back(svn_tag); } } // Get URL location string httpServ(HttpServer); if (httpServ.length()>0) { Tango::DbDatum db_doc_url("doc_url"); db_doc_url << httpServ; data.push_back(db_doc_url); } // Put inheritance Tango::DbDatum inher_datum("InheritedFrom"); vector inheritance; inheritance.push_back("TANGO_BASE_CLASS"); inher_datum << inheritance; data.push_back(inher_datum); // Call database and and values get_db_class()->put_property(data); } //=================================================================== // Factory methods //=================================================================== //-------------------------------------------------------- /** * Method : PipeServerClass::device_factory() * Description : Create the device object(s) * and store them in the device list */ //-------------------------------------------------------- void PipeServerClass::device_factory(const Tango::DevVarStringArray *devlist_ptr) { /*----- PROTECTED REGION ID(PipeServerClass::device_factory_before) ENABLED START -----*/ // Add your own code /*----- PROTECTED REGION END -----*/ // PipeServerClass::device_factory_before // Create devices and add it into the device list for (unsigned long i=0 ; ilength() ; i++) { cout4 << "Device name : " << (*devlist_ptr)[i].in() << endl; device_list.push_back(new PipeServer(this, (*devlist_ptr)[i])); } // Manage dynamic attributes if any erase_dynamic_attributes(devlist_ptr, get_class_attr()->get_attr_list()); // Export devices to the outside world for (unsigned long i=1 ; i<=devlist_ptr->length() ; i++) { // Add dynamic attributes if any PipeServer *dev = static_cast(device_list[device_list.size()-i]); dev->add_dynamic_attributes(); // Check before if database used. if ((Tango::Util::_UseDb == true) && (Tango::Util::_FileDb == false)) export_device(dev); else export_device(dev, dev->get_name().c_str()); } /*----- PROTECTED REGION ID(PipeServerClass::device_factory_after) ENABLED START -----*/ // Add your own code /*----- PROTECTED REGION END -----*/ // PipeServerClass::device_factory_after } //-------------------------------------------------------- /** * Method : PipeServerClass::attribute_factory() * Description : Create the attribute object(s) * and store them in the attribute list */ //-------------------------------------------------------- void PipeServerClass::attribute_factory(vector &att_list) { /*----- PROTECTED REGION ID(PipeServerClass::attribute_factory_before) ENABLED START -----*/ // Add your own code /*----- PROTECTED REGION END -----*/ // PipeServerClass::attribute_factory_before // Create a list of static attributes create_static_attribute_list(get_class_attr()->get_attr_list()); /*----- PROTECTED REGION ID(PipeServerClass::attribute_factory_after) ENABLED START -----*/ // Add your own code /*----- PROTECTED REGION END -----*/ // PipeServerClass::attribute_factory_after } //-------------------------------------------------------- /** * Method : PipeServerClass::pipe_factory() * Description : Create the pipe object(s) * and store them in the pipe list */ //-------------------------------------------------------- void PipeServerClass::pipe_factory() { /*----- PROTECTED REGION ID(PipeServerClass::pipe_factory_before) ENABLED START -----*/ // Add your own code /*----- PROTECTED REGION END -----*/ // PipeServerClass::pipe_factory_before Tango::UserDefaultPipeProp udpp; TestPipeClass *pTestPipe = new TestPipeClass("TestPipe",Tango::OPERATOR); udpp.set_description(""); udpp.set_label(""); pTestPipe->set_default_properties(udpp); pipe_list.push_back(pTestPipe); /*----- PROTECTED REGION ID(PipeServerClass::pipe_factory_after) ENABLED START -----*/ // Add your own code /*----- PROTECTED REGION END -----*/ // PipeServerClass::pipe_factory_after } //-------------------------------------------------------- /** * Method : PipeServerClass::command_factory() * Description : Create the command object(s) * and store them in the command list */ //-------------------------------------------------------- void PipeServerClass::command_factory() { /*----- PROTECTED REGION ID(PipeServerClass::command_factory_before) ENABLED START -----*/ // Add your own code /*----- PROTECTED REGION END -----*/ // PipeServerClass::command_factory_before /*----- PROTECTED REGION ID(PipeServerClass::command_factory_after) ENABLED START -----*/ // Add your own code /*----- PROTECTED REGION END -----*/ // PipeServerClass::command_factory_after } //=================================================================== // Dynamic attributes related methods //=================================================================== //-------------------------------------------------------- /** * method : PipeServerClass::create_static_attribute_list * description : Create the a list of static attributes * * @param att_list the ceated attribute list */ //-------------------------------------------------------- void PipeServerClass::create_static_attribute_list(vector &att_list) { for (unsigned long i=0 ; iget_name()); transform(att_name.begin(), att_name.end(), att_name.begin(), ::tolower); defaultAttList.push_back(att_name); } cout2 << defaultAttList.size() << " attributes in default list" << endl; /*----- PROTECTED REGION ID(PipeServerClass::create_static_att_list) ENABLED START -----*/ /*----- PROTECTED REGION END -----*/ // PipeServerClass::create_static_att_list } //-------------------------------------------------------- /** * method : PipeServerClass::erase_dynamic_attributes * description : delete the dynamic attributes if any. * * @param devlist_ptr the device list pointer * @param list of all attributes */ //-------------------------------------------------------- void PipeServerClass::erase_dynamic_attributes(const Tango::DevVarStringArray *devlist_ptr, vector &att_list) { Tango::Util *tg = Tango::Util::instance(); for (unsigned long i=0 ; ilength() ; i++) { Tango::DeviceImpl *dev_impl = tg->get_device_by_name(((string)(*devlist_ptr)[i]).c_str()); PipeServer *dev = static_cast (dev_impl); vector &dev_att_list = dev->get_device_attr()->get_attribute_list(); vector::iterator ite_att; for (ite_att=dev_att_list.begin() ; ite_att != dev_att_list.end() ; ++ite_att) { string att_name((*ite_att)->get_name_lower()); if ((att_name == "state") || (att_name == "status")) continue; vector::iterator ite_str = find(defaultAttList.begin(), defaultAttList.end(), att_name); if (ite_str == defaultAttList.end()) { cout2 << att_name << " is a UNWANTED dynamic attribute for device " << (*devlist_ptr)[i] << endl; Tango::Attribute &att = dev->get_device_attr()->get_attr_by_name(att_name.c_str()); dev->remove_attribute(att_list[att.get_attr_idx()], true, false); --ite_att; } } } /*----- PROTECTED REGION ID(PipeServerClass::erase_dynamic_attributes) ENABLED START -----*/ /*----- PROTECTED REGION END -----*/ // PipeServerClass::erase_dynamic_attributes } //-------------------------------------------------------- /** * Method : PipeServerClass::get_attr_by_name() * Description : returns Tango::Attr * object found by name */ //-------------------------------------------------------- Tango::Attr *PipeServerClass::get_attr_object_by_name(vector &att_list, string attname) { vector::iterator it; for (it=att_list.begin() ; itget_name()==attname) return (*it); // Attr does not exist return NULL; } /*----- PROTECTED REGION ID(PipeServerClass::Additional Methods) ENABLED START -----*/ /*----- PROTECTED REGION END -----*/ // PipeServerClass::Additional Methods } // namespace pytango-9.2.2/examples/pipes/PipeServerClass.h000066400000000000000000000100161316324373100213670ustar00rootroot00000000000000/*----- PROTECTED REGION ID(PipeServerClass.h) ENABLED START -----*/ //============================================================================= // // file : PipeServerClass.h // // description : Include for the PipeServer root class. // This class is the singleton class for // the PipeServer device class. // It contains all properties and methods which the // PipeServer requires only once e.g. the commands. // // project : // // This file is part of Tango device class. // // Tango 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. // // Tango 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 Tango. If not, see . // // $Author: $ // // $Revision: $ // $Date: $ // // $HeadURL: $ // //============================================================================= // This file is generated by POGO // (Program Obviously used to Generate tango Object) //============================================================================= #ifndef PipeServerClass_H #define PipeServerClass_H #include #include /*----- PROTECTED REGION END -----*/ // PipeServerClass.h namespace PipeServer_ns { /*----- PROTECTED REGION ID(PipeServerClass::classes for dynamic creation) ENABLED START -----*/ /*----- PROTECTED REGION END -----*/ // PipeServerClass::classes for dynamic creation //========================================= // Define classes for pipes //========================================= // Pipe TestPipe class definition class TestPipeClass: public Tango::WPipe { public: TestPipeClass(const string &name, Tango::DispLevel level) :WPipe(name, level) {}; ~TestPipeClass() {}; virtual bool is_allowed (Tango::DeviceImpl *dev,Tango::PipeReqType _prt) {return (static_cast(dev))->is_TestPipe_allowed(_prt);} virtual void read(Tango::DeviceImpl *dev) {(static_cast(dev))->read_TestPipe(*this);} virtual void write(Tango::DeviceImpl *dev) {(static_cast(dev))->write_TestPipe(*this);} }; /** * The PipeServerClass singleton definition */ #ifdef _TG_WINDOWS_ class __declspec(dllexport) PipeServerClass : public Tango::DeviceClass #else class PipeServerClass : public Tango::DeviceClass #endif { /*----- PROTECTED REGION ID(PipeServerClass::Additionnal DServer data members) ENABLED START -----*/ /*----- PROTECTED REGION END -----*/ // PipeServerClass::Additionnal DServer data members public: // write class properties data members Tango::DbData cl_prop; Tango::DbData cl_def_prop; Tango::DbData dev_def_prop; // Method prototypes static PipeServerClass *init(const char *); static PipeServerClass *instance(); ~PipeServerClass(); Tango::DbDatum get_class_property(string &); Tango::DbDatum get_default_device_property(string &); Tango::DbDatum get_default_class_property(string &); protected: PipeServerClass(string &); static PipeServerClass *_instance; void command_factory(); void attribute_factory(vector &); void pipe_factory(); void write_class_property(); void set_default_property(); void get_class_property(); string get_cvstag(); string get_cvsroot(); private: void device_factory(const Tango::DevVarStringArray *); void create_static_attribute_list(vector &); void erase_dynamic_attributes(const Tango::DevVarStringArray *,vector &); vector defaultAttList; Tango::Attr *get_attr_object_by_name(vector &att_list, string attname); }; } // End of namespace #endif // PipeServer_H pytango-9.2.2/examples/pipes/PipeServerStateMachine.cpp000066400000000000000000000060121316324373100232230ustar00rootroot00000000000000/*----- PROTECTED REGION ID(PipeServerStateMachine.cpp) ENABLED START -----*/ static const char *RcsId = "$Id: $"; //============================================================================= // // file : PipeServerStateMachine.cpp // // description : State machine file for the PipeServer class // // project : // // This file is part of Tango device class. // // Tango 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. // // Tango 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 Tango. If not, see . // // $Author: $ // // $Revision: $ // $Date: $ // // $HeadURL: $ // //============================================================================= // This file is generated by POGO // (Program Obviously used to Generate tango Object) //============================================================================= #include /*----- PROTECTED REGION END -----*/ // PipeServer::PipeServerStateMachine.cpp //================================================================ // States | Description //================================================================ namespace PipeServer_ns { //================================================= // Attributes Allowed Methods //================================================= //================================================= // pipe Allowed Methods //================================================= //-------------------------------------------------------- /** * Method : PipeServer::is_TestPipe_allowed() * Description : Execution allowed for TestPipe pipe */ //-------------------------------------------------------- bool PipeServer::is_TestPipe_allowed(TANGO_UNUSED(Tango::PipeReqType type)) { // Not any excluded states for TestPipe pipe in Write access. /*----- PROTECTED REGION ID(PipeServer::TestPipeStateAllowed_WRITE) ENABLED START -----*/ /*----- PROTECTED REGION END -----*/ // PipeServer::TestPipeStateAllowed_WRITE // Not any excluded states for TestPipe pipe in read access. /*----- PROTECTED REGION ID(PipeServer::TestPipeStateAllowed_READ) ENABLED START -----*/ /*----- PROTECTED REGION END -----*/ // PipeServer::TestPipeStateAllowed_READ return true; } //================================================= // Commands Allowed Methods //================================================= /*----- PROTECTED REGION ID(PipeServer::PipeServerStateAllowed.AdditionalMethods) ENABLED START -----*/ // Additional Methods /*----- PROTECTED REGION END -----*/ // PipeServer::PipeServerStateAllowed.AdditionalMethods } // End of namespace pytango-9.2.2/examples/pipes/__init__.py000066400000000000000000000000001316324373100202450ustar00rootroot00000000000000pytango-9.2.2/examples/pipes/main.cpp000066400000000000000000000051421316324373100176000ustar00rootroot00000000000000/*----- PROTECTED REGION ID(PipeServer::main.cpp) ENABLED START -----*/ static const char *RcsId = "$Id: $"; //============================================================================= // // file : main.cpp // // description : C++ source for the PipeServer device server main. // The main rule is to initialise (and create) the Tango // system and to create the DServerClass singleton. // The main should be the same for every Tango device server. // // project : // // This file is part of Tango device class. // // Tango 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. // // Tango 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 Tango. If not, see . // // $Author: $ // // $Revision: $ // $Date: $ // // $HeadURL: $ // //============================================================================= // This file is generated by POGO // (Program Obviously used to Generate tango Object) //============================================================================= #include // Check if crash reporting is used. #if defined(ENABLE_CRASH_REPORT) # include #else # define DECLARE_CRASH_HANDLER # define INSTALL_CRASH_HANDLER #endif DECLARE_CRASH_HANDLER; int main(int argc,char *argv[]) { INSTALL_CRASH_HANDLER try { // Initialise the device server //---------------------------------------- Tango::Util *tg = Tango::Util::init(argc,argv); // Create the device server singleton // which will create everything //---------------------------------------- tg->server_init(false); // Run the endless loop //---------------------------------------- cout << "Ready to accept request" << endl; tg->server_run(); } catch (bad_alloc &) { cout << "Can't allocate memory to store device object !!!" << endl; cout << "Exiting" << endl; } catch (CORBA::Exception &e) { Tango::Except::print_exception(e); cout << "Received a CORBA_Exception" << endl; cout << "Exiting" << endl; } Tango::Util::instance()->server_cleanup(); return(0); } /*----- PROTECTED REGION END -----*/ // PipeServer::main.cpp pytango-9.2.2/examples/simple/000077500000000000000000000000001316324373100163175ustar00rootroot00000000000000pytango-9.2.2/examples/simple/common/000077500000000000000000000000001316324373100176075ustar00rootroot00000000000000pytango-9.2.2/examples/simple/common/__init__.py000066400000000000000000000000001316324373100217060ustar00rootroot00000000000000pytango-9.2.2/examples/simple/common/roi.py000066400000000000000000000004371316324373100207560ustar00rootroot00000000000000class ROI: def __init__(self, x, y, w, h): self.x = x self.y = y self.w = w self.h = h def __repr__(self): return str(self) def __str__(self): txt = "ROI(x={o.x}, y={o.y}, w={o.w}, h={o.h})".format(o=self) return txt pytango-9.2.2/examples/simple/simple_client.py000066400000000000000000000013171316324373100215220ustar00rootroot00000000000000import PyTango.client my_object = PyTango.client.Object("my_object") print("my_object.bla = {0}".format(my_object.bla)) print("my_object.ble = {0}".format(my_object.ble)) print("my_object.bli = {0}".format(my_object.bli)) print("my_object.array = {0}".format(my_object.array)) r1 = my_object.func1() print("my_object.func1() = {0}".format(r1)) r2 = my_object.func2(96.44) print("my_object.func2(96.44) = {0}".format(r2)) r3 = my_object.func3(45.86, 'hello', d=False, c='world') print("my_object.func3(45.86, 'hello', d=False, c='world') = {0}".format(r3)) r4 = my_object.func4() print("my_object.func4() = {0}".format(r4)) r5 = my_object.zeros((500, 1000)) print("my_object.zeros((500, 1000)) = {0}".format(r5)) pytango-9.2.2/examples/simple/simple_server.py000066400000000000000000000017051316324373100215530ustar00rootroot00000000000000import numpy from PyTango.server import Server from common.roi import ROI class MyClass: def __init__(self): self.bla = 55.6 self.ble = 11 self.bli = False self.array = numpy.ones((1000,1000)) self.buff = bytearray(100000*"Hello ") def func1(self): return "executed func1" def func2(self, v): return 2*v def func3(self, a, b, c=1, d=3): """Just some documentation""" return "done func3" def func4(self): roi1 = ROI(10, 20, 640, 480) roi2 = ROI(0, 0, 1024, 768) return roi1.__dict__ def zeros(self, shape, dtype='float'): import numpy return numpy.zeros(shape, dtype=dtype) def func5(self, nap_time): import time time.sleep(nap_time) return "Finished sleep for {0}s".format(nap_time) my_object = MyClass() server = Server("Bla") server.register_object(my_object, "my_object") server.run() pytango-9.2.2/ext/000077500000000000000000000000001316324373100140105ustar00rootroot00000000000000pytango-9.2.2/ext/api_util.cpp000066400000000000000000000044021316324373100163220ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include #include "pyutils.h" using namespace boost::python; namespace PyApiUtil { inline object get_env_var(const char *name) { std::string value; if (Tango::ApiUtil::get_env_var(name, value) == 0) { return str(value); } return object(); } }; void (Tango::ApiUtil::*get_asynch_replies1)() = &Tango::ApiUtil::get_asynch_replies; void (Tango::ApiUtil::*get_asynch_replies2)(long) = &Tango::ApiUtil::get_asynch_replies; bool (Tango::ApiUtil::*in_server1)() = &Tango::ApiUtil::in_server; void (Tango::ApiUtil::*in_server2)(bool) = &Tango::ApiUtil::in_server; void export_api_util() { class_("ApiUtil", no_init) .def("instance", &Tango::ApiUtil::instance, return_value_policy()) .staticmethod("instance") .def("pending_asynch_call", &Tango::ApiUtil::pending_asynch_call) .def("get_asynch_replies", get_asynch_replies1) .def("get_asynch_replies", get_asynch_replies2) .def("set_asynch_cb_sub_model", &Tango::ApiUtil::set_asynch_cb_sub_model) .def("get_asynch_cb_sub_model", &Tango::ApiUtil::get_asynch_cb_sub_model) .def("get_env_var", &PyApiUtil::get_env_var) .staticmethod("get_env_var") .def("is_notifd_event_consumer_created", &Tango::ApiUtil::is_notifd_event_consumer_created) .def("is_zmq_event_consumer_created", &Tango::ApiUtil::is_zmq_event_consumer_created) .def("get_user_connect_timeout", &Tango::ApiUtil::get_user_connect_timeout) .def("get_ip_from_if", &Tango::ApiUtil::get_ip_from_if) ; } pytango-9.2.2/ext/archive_event_info.cpp000066400000000000000000000021101316324373100203430ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include using namespace boost::python; void export_archive_event_info() { class_("ArchiveEventInfo") .enable_pickling() .def_readwrite("archive_rel_change", &Tango::ArchiveEventInfo::archive_rel_change) .def_readwrite("archive_abs_change", &Tango::ArchiveEventInfo::archive_abs_change) .def_readwrite("archive_period", &Tango::ArchiveEventInfo::archive_period) .def_readwrite("extensions", &Tango::ArchiveEventInfo::extensions) ; } pytango-9.2.2/ext/attr_conf_event_data.cpp000066400000000000000000000050641316324373100206720ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include #include "exception.h" using namespace boost::python; extern boost::python::object PyTango_DevFailed; namespace PyAttrConfEventData { static boost::shared_ptr makeAttrConfEventData() { Tango::AttrConfEventData *result = new Tango::AttrConfEventData; return boost::shared_ptr(result); } static void set_errors(Tango::AttrConfEventData &event_data, boost::python::object &dev_failed) { Tango::DevFailed df; boost::python::object errors = dev_failed.attr("args"); sequencePyDevError_2_DevErrorList(errors.ptr(), event_data.errors); } }; void export_attr_conf_event_data() { class_("AttrConfEventData", init()) .def("__init__", boost::python::make_constructor(PyAttrConfEventData::makeAttrConfEventData)) // The original Tango::AttrConfEventData structure has a 'device' field. // However, if we returned this directly we would get a different // python device each time. So we are doing our weird things to make // sure the device returned is the same where the read action was // performed. So we don't return Tango::AttrConfEventData::device directly. // See callback.cpp .setattr("device",object()) .def_readwrite("attr_name", &Tango::AttrConfEventData::attr_name) .def_readwrite("event", &Tango::AttrConfEventData::event) .setattr("attr_conf",object()) .def_readwrite("err", &Tango::AttrConfEventData::err) .def_readwrite("reception_date", &Tango::AttrConfEventData::reception_date) .add_property("errors", make_getter(&Tango::AttrConfEventData::errors, return_value_policy()), &PyAttrConfEventData::set_errors) .def("get_date", &Tango::AttrConfEventData::get_date, return_internal_reference<>()) ; } pytango-9.2.2/ext/attribute_alarm_info.cpp000066400000000000000000000024151316324373100207100ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include using namespace boost::python; void export_attribute_alarm_info() { class_("AttributeAlarmInfo") .enable_pickling() .def_readwrite("min_alarm", &Tango::AttributeAlarmInfo::min_alarm) .def_readwrite("max_alarm", &Tango::AttributeAlarmInfo::max_alarm) .def_readwrite("min_warning", &Tango::AttributeAlarmInfo::min_warning) .def_readwrite("max_warning", &Tango::AttributeAlarmInfo::max_warning) .def_readwrite("delta_t", &Tango::AttributeAlarmInfo::delta_t) .def_readwrite("delta_val", &Tango::AttributeAlarmInfo::delta_val) .def_readwrite("extensions", &Tango::AttributeAlarmInfo::extensions) ; } pytango-9.2.2/ext/attribute_dimension.cpp000066400000000000000000000015421316324373100205660ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include using namespace boost::python; void export_attribute_dimension() { class_("AttributeDimension") .def_readonly("dim_x", &Tango::AttributeDimension::dim_x) .def_readonly("dim_y", &Tango::AttributeDimension::dim_y) ; } pytango-9.2.2/ext/attribute_event_info.cpp000066400000000000000000000017271316324373100207420ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include using namespace boost::python; void export_attribute_event_info() { class_("AttributeEventInfo") .enable_pickling() .def_readwrite("ch_event", &Tango::AttributeEventInfo::ch_event) .def_readwrite("per_event", &Tango::AttributeEventInfo::per_event) .def_readwrite("arch_event", &Tango::AttributeEventInfo::arch_event) ; } pytango-9.2.2/ext/attribute_info.cpp000066400000000000000000000016231316324373100175340ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include using namespace boost::python; void export_attribute_info() { class_ > ("AttributeInfo") .def(init()) .enable_pickling() .def_readwrite("disp_level", &Tango::AttributeInfo::disp_level) ; } pytango-9.2.2/ext/attribute_info_ex.cpp000066400000000000000000000023661316324373100202350ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include using namespace boost::python; void export_attribute_info_ex() { class_ > ("AttributeInfoEx") .def(init()) .enable_pickling() .def_readwrite("root_attr_name", &Tango::AttributeInfoEx::root_attr_name) .def_readwrite("memorized", &Tango::AttributeInfoEx::memorized) .def_readwrite("enum_labels", &Tango::AttributeInfoEx::enum_labels) .def_readwrite("alarms", &Tango::AttributeInfoEx::alarms) .def_readwrite("events", &Tango::AttributeInfoEx::events) .def_readwrite("sys_extensions", &Tango::AttributeInfoEx::sys_extensions) ; } pytango-9.2.2/ext/attribute_proxy.cpp000066400000000000000000000102271316324373100177620ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include "defs.h" #include "pytgutils.h" namespace bopy = boost::python; extern const char *param_must_be_seq; extern const char *unreachable_code; extern const char *non_string_seq; namespace PyAttributeProxy { struct PickleSuite : bopy::pickle_suite { static bopy::tuple getinitargs(Tango::AttributeProxy& self) { Tango::DeviceProxy* dev = self.get_device_proxy(); std::string ret = dev->get_db_host() + ":" + dev->get_db_port() + "/" + dev->dev_name() + "/" + self.name(); return bopy::make_tuple(ret); } }; static boost::shared_ptr makeAttributeProxy1(const std::string& name) { return boost::shared_ptr(new Tango::AttributeProxy(name.c_str())); } static boost::shared_ptr makeAttributeProxy2(const Tango::DeviceProxy *dev, const std::string& name) { return boost::shared_ptr(new Tango::AttributeProxy(dev, name.c_str())); } } void export_attribute_proxy() { // The following function declarations are necessary to be able to cast // the function parameters from string& to const string&, otherwise python // will not recognize the method calls void (Tango::AttributeProxy::*get_property_)(std::string &, Tango::DbData &) = &Tango::AttributeProxy::get_property; void (Tango::AttributeProxy::*delete_property_)(std::string &) = &Tango::AttributeProxy::delete_property; bopy::class_ AttributeProxy("__AttributeProxy", bopy::init()) ; AttributeProxy .def("__init__", boost::python::make_constructor(PyAttributeProxy::makeAttributeProxy1)) .def("__init__", boost::python::make_constructor(PyAttributeProxy::makeAttributeProxy2)) // // Pickle // .def_pickle(PyAttributeProxy::PickleSuite()) // // general methods // .def("name", &Tango::AttributeProxy::name, ( arg_("self") )) .def("get_device_proxy", &Tango::AttributeProxy::get_device_proxy, ( arg_("self") ), bopy::return_internal_reference<1>()) // // property methods // .def("_get_property", (void (Tango::AttributeProxy::*) (const std::string &, Tango::DbData &)) get_property_, ( arg_("self"), arg_("propname"), arg_("propdata") ) ) .def("_get_property", (void (Tango::AttributeProxy::*) (std::vector&, Tango::DbData &)) &Tango::AttributeProxy::get_property, ( arg_("self"), arg_("propnames"), arg_("propdata") ) ) .def("_get_property", (void (Tango::AttributeProxy::*) (Tango::DbData &)) &Tango::AttributeProxy::get_property, ( arg_("self"), arg_("propdata") ) ) .def("_put_property", &Tango::AttributeProxy::put_property, ( arg_("self"), arg_("propdata") ) ) .def("_delete_property", (void (Tango::AttributeProxy::*) (const std::string &)) delete_property_, ( arg_("self"), arg_("propname") ) ) .def("_delete_property", (void (Tango::AttributeProxy::*) (StdStringVector &)) &Tango::AttributeProxy::delete_property, ( arg_("self"), arg_("propnames") ) ) .def("_delete_property", (void (Tango::AttributeProxy::*) (Tango::DbData &)) &Tango::AttributeProxy::delete_property, ( arg_("self"), arg_("propdata") ) ) ; } pytango-9.2.2/ext/base_types.cpp000066400000000000000000000372131316324373100166600ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include "defs.h" #include "pytgutils.h" #include "fast_from_py.h" #include "base_types_numpy.hpp" using namespace boost::python; // from tango_const.h void export_poll_device(); // from devapi.h void export_locker_info(); //TODO void export_locking_thread(); void export_dev_command_info(); void export_attribute_dimension(); void export_command_info(); void export_device_info(); void export_device_attribute_config(); void export_attribute_info(); void export_attribute_alarm_info(); void export_change_event_info(); void export_periodic_event_info(); void export_archive_event_info(); void export_attribute_event_info(); void export_attribute_info_ex(); void export_device_data(); void export_device_attribute(); void export_device_data_history(); void export_device_attribute_history(); void export_device_pipe(); void export_pipe_info(); void export_dev_error(); void export_time_val(); // // Necessary equality operators for having vectors exported to python // namespace Tango { inline bool operator==(const Tango::DbDatum& dd1, const Tango::DbDatum& dd2) { return dd1.name == dd2.name && dd1.value_string == dd2.value_string; } inline bool operator==(const Tango::DbDevInfo& di1, const Tango::DbDevInfo& di2) { return di1.name == di2.name && di1._class == di2._class && di1.server == di2.server; } inline bool operator==(const Tango::DbDevImportInfo& dii1, const Tango::DbDevImportInfo& dii2) { return dii1.name == dii2.name && dii1.exported == dii2.exported && dii1.ior == dii2.ior && dii1.version == dii2.version; } inline bool operator==(const Tango::DbDevExportInfo& dei1, const Tango::DbDevExportInfo& dei2) { return dei1.name == dei2.name && dei1.ior == dei2.ior && dei1.host == dei2.host && dei1.version == dei2.version && dei1.pid == dei2.pid; } inline bool operator==(const Tango::DbHistory& dh1_, const Tango::DbHistory& dh2_) { Tango::DbHistory &dh1 = const_cast(dh1_); Tango::DbHistory &dh2 = const_cast(dh2_); return dh1.get_name() == dh2.get_name() && dh1.get_attribute_name() == dh2.get_attribute_name() && dh1.is_deleted() == dh2.is_deleted(); } inline bool operator==(const Tango::GroupReply& dh1_, const Tango::GroupReply& dh2_) { /// @todo ? return false; } inline bool operator==(const Tango::TimeVal& tv1, const Tango::TimeVal& tv2) { return tv1.tv_sec == tv2.tv_sec && tv1.tv_usec == tv2.tv_usec && tv1.tv_nsec == tv2.tv_nsec; } inline bool operator==(const Tango::DeviceData& dd1_, const Tango::DeviceData& dd2_) { Tango::DeviceData &dd1 = const_cast(dd1_); Tango::DeviceData &dd2 = const_cast(dd2_); return //dh1.any == dh2.any && dd1.exceptions() == dd2.exceptions(); } inline bool operator==(const Tango::DeviceDataHistory& ddh1_, const Tango::DeviceDataHistory& ddh2_) { Tango::DeviceDataHistory &ddh1 = const_cast(ddh1_); Tango::DeviceDataHistory &ddh2 = const_cast(ddh2_); return operator==((Tango::DeviceData)ddh1, (Tango::DeviceData)ddh2) && ddh1.failed() == ddh2.failed() && ddh1.date() == ddh2.date(); //&& //ddh1.errors() == ddh2.errors(); } inline bool operator==(const Tango::PipeInfo& pi1, const Tango::PipeInfo& pi2) { return pi1.name == pi2.name && pi1.description == pi2.description && pi1.label == pi2.label && pi1.disp_level == pi2.disp_level && pi1.writable == pi2.writable && pi1.extensions == pi2.extensions; } } /** * Converter from python sequence to CORBA sequence */ template struct convert_PySequence_to_CORBA_Sequence { convert_PySequence_to_CORBA_Sequence() { // Register converter from python sequence to CorbaSequence boost::python::converter::registry::push_back( &convertible, &construct, boost::python::type_id()); } // Check if given Python object is convertible to a sequence. // If so, return obj, otherwise return 0 static void* convertible(PyObject* obj) { return (PySequence_Check(obj)) ? obj : NULL; } static void construct(PyObject* obj, boost::python::converter::rvalue_from_python_stage1_data* data) { typedef boost::python::converter::rvalue_from_python_storage CorbaSequence_storage; void* const storage = reinterpret_cast(data)->storage.bytes; CorbaSequence *ptr = new (storage) CorbaSequence(); convert2array(object(handle<>(obj)), *ptr); data->convertible = storage; } }; #if PY_VERSION_HEX < 0x03000000 bool is_str(PyObject* obj) { return PyString_Check(obj) || PyUnicode_Check(obj); } struct StdString_from_python_str_unicode { StdString_from_python_str_unicode() { boost::python::converter::registry::push_back( &convertible, &construct, boost::python::type_id()); } // Determine if obj_ptr can be converted in a std::string static void* convertible(PyObject* obj) { if (!is_str(obj)) { return 0; } return obj; } // Convert obj_ptr into a std::string static void construct(PyObject* obj, boost::python::converter::rvalue_from_python_stage1_data* data) { bool decref = false; if (PyUnicode_Check(obj)) { decref = true; obj = PyUnicode_AsLatin1String(obj); } const char* value = PyBytes_AsString(obj); // Grab pointer to memory into which to construct the new std::string void* storage = ( (boost::python::converter::rvalue_from_python_storage*) data)->storage.bytes; // in-place construct the new std::string using the character data // extraced from the python object new (storage) std::string(value); // Stash the memory chunk pointer for later use by boost.python data->convertible = storage; if (decref) Py_DECREF(obj); } }; #endif // PY_VERSION_HEX < 0x03000000 int raise_asynch_exception(long thread_id, boost::python::object exp_klass) { return PyThreadState_SetAsyncExc(thread_id, exp_klass.ptr()); } void export_base_types() { enum_("ExtractAs") .value("Numpy", PyTango::ExtractAsNumpy) .value("ByteArray", PyTango::ExtractAsByteArray) .value("Bytes", PyTango::ExtractAsBytes) .value("Tuple", PyTango::ExtractAsTuple) .value("List", PyTango::ExtractAsList) .value("String", PyTango::ExtractAsString) .value("Nothing", PyTango::ExtractAsNothing) ; enum_("GreenMode") .value("Synchronous", PyTango::GreenModeSynchronous) .value("Futures", PyTango::GreenModeFutures) .value("Gevent", PyTango::GreenModeGevent) .value("Asyncio", PyTango::GreenModeAsyncio) ; enum_("_ImageFormat") .value("RawImage", PyTango::RawImage) .value("JpegImage", PyTango::JpegImage) ; // Export some std types // vector_indexing_suite<*, true | false>: // - true: Make a copy of the original value each time the vector // is accessed. We have a struct like this: // struct V { int value; } // and the object is: // std::vector vec = { 69 }; // wrapped in python and we do: // vec[0].value = 3 // vec[0].unexisting = 7 // print vec[0].value // >> 69 ( 3 is stored in the obj created the first vec[0]) // print vec[0].unexisting // >> exception (unexisting is stored in the other obj) // If the C struct has a 'value' field, it will // - false: Make a new proxy object of the original value each time // the vector is accessed. With the same example: // vec[0].value = 3 // vec[0].unexisting = 7 // print vec[0].value // >> 3 (It's another proxy obj, but modifiyes the same // internal C object) // print vec[0].unexisting // >> exception (unexisting is stored in the other obj) class_("StdStringVector") .def(vector_indexing_suite()); class_("StdLongVector") .def(vector_indexing_suite()); class_("StdDoubleVector") .def(vector_indexing_suite()); class_("CommandInfoList") .def(vector_indexing_suite()); class_("AttributeInfoList") .def(vector_indexing_suite()); class_("AttributeInfoListEx") .def(vector_indexing_suite()); class_("PipeInfoList") .def(vector_indexing_suite()); class_ >("AttrList") .def(vector_indexing_suite, true>()); class_ >("AttributeList") .def(vector_indexing_suite, true>()); class_ >("PipeList") .def(vector_indexing_suite, true>()); //class_("EventDataList") // .def(vector_indexing_suite()); class_("DbData") .def(vector_indexing_suite()); class_("DbDevInfos") .def(vector_indexing_suite()); class_("DbDevExportInfos") .def(vector_indexing_suite()); class_("DbDevImportInfos") .def(vector_indexing_suite()); class_ >("DbHistoryList") .def(vector_indexing_suite, true>()); class_ >("DeviceDataList") .def(vector_indexing_suite, true>()); class_("DeviceDataHistoryList") .def(vector_indexing_suite()); typedef std::vector StdGroupReplyVector_; class_< StdGroupReplyVector_ >("StdGroupReplyVector") .def(vector_indexing_suite()); typedef std::vector StdGroupCmdReplyVector_; class_< StdGroupCmdReplyVector_ >("StdGroupCmdReplyVector") .def(vector_indexing_suite()); typedef std::vector StdGroupAttrReplyVector_; class_< StdGroupAttrReplyVector_ >("StdGroupAttrReplyVector") .def(vector_indexing_suite()); to_python_converter(); //to_python_converter<_CORBA_String_member, CORBA_String_member_to_str2>(); to_python_converter<_CORBA_String_element, CORBA_String_element_to_str>(); to_python_converter >(); to_python_converter >(); to_python_converter >(); to_python_converter >(); to_python_converter >(); to_python_converter >(); to_python_converter >(); to_python_converter >(); to_python_converter >(); to_python_converter >(); to_python_converter >(); to_python_converter >(); to_python_converter >(); to_python_converter(); //to_python_converter(); convert_PySequence_to_CORBA_Sequence(); convert_PySequence_to_CORBA_Sequence(); convert_PySequence_to_CORBA_Sequence(); convert_PySequence_to_CORBA_Sequence(); convert_PySequence_to_CORBA_Sequence(); convert_PySequence_to_CORBA_Sequence(); convert_PySequence_to_CORBA_Sequence(); convert_PySequence_to_CORBA_Sequence(); convert_PySequence_to_CORBA_Sequence(); convert_PySequence_to_CORBA_Sequence(); convert_PySequence_to_CORBA_Sequence(); convert_PySequence_to_CORBA_Sequence(); convert_numpy_to_integer(); convert_numpy_to_integer(); convert_numpy_to_integer(); convert_numpy_to_float(); convert_numpy_to_float(); convert_numpy_to_integer(); convert_numpy_to_integer(); convert_numpy_to_integer(); convert_numpy_to_integer(); #if PY_VERSION_HEX < 0x03000000 StdString_from_python_str_unicode(); #endif // from tango_const.h export_poll_device(); // from devapi.h export_locker_info(); //TODO export_locking_thread(); export_dev_command_info(); export_attribute_dimension(); export_command_info(); export_device_info(); export_device_attribute_config(); export_attribute_info(); export_attribute_alarm_info(); export_change_event_info(); export_periodic_event_info(); export_archive_event_info(); export_attribute_event_info(); export_attribute_info_ex(); export_device_data(); export_device_attribute(); export_device_data_history(); export_device_attribute_history(); export_device_pipe(); export_pipe_info(); export_dev_error(); export_time_val(); def("raise_asynch_exception", &raise_asynch_exception); def("_get_tango_lib_release", &Tango::_convert_tango_lib_release); } pytango-9.2.2/ext/base_types_numpy.hpp000066400000000000000000000070611316324373100201130ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #pragma once #ifdef DISABLE_PYTANGO_NUMPY template struct convert_numpy_to_integer { convert_numpy_to_integer() {} }; template struct convert_numpy_to_float { convert_numpy_to_float() {} }; #else template struct convert_numpy_to_integer { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; static const long NumpyType = TANGO_const2numpy(tangoTypeConst); convert_numpy_to_integer() { boost::python::converter::registry::push_back( &convertible, &construct, boost::python::type_id()); } static void* convertible(PyObject* obj) { if (!PyArray_CheckScalar(obj)) return 0; PyArray_Descr* type = PyArray_DescrFromScalar(obj); if (PyDataType_ISINTEGER(type)) { return obj; } return 0; } static void construct(PyObject* obj, boost::python::converter::rvalue_from_python_stage1_data* data) { typedef boost::python::converter::rvalue_from_python_storage tango_storage; void* const storage = reinterpret_cast(data)->storage.bytes; TangoScalarType *ptr = new (storage) TangoScalarType(); PyObject* native_obj = PyObject_CallMethod(obj, const_cast("__int__"), NULL); if (native_obj == NULL) { boost::python::throw_error_already_set(); } from_py::convert(native_obj, *ptr); Py_DECREF(native_obj); data->convertible = storage; } }; template struct convert_numpy_to_float { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; static const long NumpyType = TANGO_const2numpy(tangoTypeConst); convert_numpy_to_float() { boost::python::converter::registry::push_back( &convertible, &construct, boost::python::type_id()); } static void* convertible(PyObject* obj) { if (!PyArray_CheckScalar(obj)) return 0; PyArray_Descr* type = PyArray_DescrFromScalar(obj); if (PyDataType_ISINTEGER(type) || PyDataType_ISFLOAT(type)) { return obj; } return 0; } static void construct(PyObject* obj, boost::python::converter::rvalue_from_python_stage1_data* data) { typedef boost::python::converter::rvalue_from_python_storage tango_storage; void* const storage = reinterpret_cast(data)->storage.bytes; TangoScalarType *ptr = new (storage) TangoScalarType(); PyObject* native_obj = PyObject_CallMethod(obj, const_cast("__float__"), NULL); if (native_obj == NULL) { boost::python::throw_error_already_set(); } from_py::convert(native_obj, *ptr); Py_DECREF(native_obj); data->convertible = storage; } }; #endif pytango-9.2.2/ext/callback.cpp000066400000000000000000000356451316324373100162650ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include "pytgutils.h" #include "callback.h" #include "device_attribute.h" #include "exception.h" #include "device_pipe.h" using namespace boost::python; struct PyCmdDoneEvent { object device; object cmd_name; object argout; object argout_raw; object err; object errors; object ext; }; struct PyAttrReadEvent { object device; object attr_names; object argout; object err; object errors; object ext; }; struct PyAttrWrittenEvent { object device; object attr_names; object err; object errors; object ext; }; static void copy_most_fields(PyCallBackAutoDie* self, const Tango::CmdDoneEvent* ev, PyCmdDoneEvent* py_ev) { // py_ev->device py_ev->cmd_name = object(ev->cmd_name); py_ev->argout_raw = object(ev->argout); py_ev->err = object(ev->err); py_ev->errors = object(ev->errors); // py_ev->ext = object(ev->ext); } static void copy_most_fields(PyCallBackAutoDie* self, const Tango::AttrReadEvent* ev, PyAttrReadEvent* py_ev) { // py_ev->device py_ev->attr_names = object(ev->attr_names); PyDeviceAttribute::AutoDevAttrVector dev_attr_vec(ev->argout); py_ev->argout = PyDeviceAttribute::convert_to_python( \ dev_attr_vec, *ev->device, self->m_extract_as); py_ev->err = object(ev->err); py_ev->errors = object(ev->errors); // py_ev->ext = object(ev->ext); } static void copy_most_fields(PyCallBackAutoDie* self, const Tango::AttrWrittenEvent* ev, PyAttrWrittenEvent* py_ev) { // py_ev->device py_ev->attr_names = object(ev->attr_names); py_ev->err = object(ev->err); py_ev->errors = object(ev->errors); // py_ev->ext = object(ev->ext); } /*static*/ object PyCallBackAutoDie::py_on_callback_parent_fades; /*static*/ std::map PyCallBackAutoDie::s_weak2ob; PyCallBackAutoDie::~PyCallBackAutoDie() { if (this->m_weak_parent) { PyCallBackAutoDie::s_weak2ob.erase(this->m_weak_parent); boost::python::xdecref(this->m_weak_parent); } } /*static*/ void PyCallBackAutoDie::init() { object py_scope = boost::python::scope(); def ("__on_callback_parent_fades", on_callback_parent_fades); PyCallBackAutoDie::py_on_callback_parent_fades = py_scope.attr("__on_callback_parent_fades"); } void PyCallBackAutoDie::on_callback_parent_fades(PyObject* weakobj) { PyObject* ob = PyCallBackAutoDie::s_weak2ob[weakobj]; if (!ob) return; // while (ob->ob_refcnt) boost::python::xdecref(ob); } void PyCallBackAutoDie::set_autokill_references(object &py_self, object &py_parent) { if (m_self == 0) m_self = py_self.ptr(); assert(m_self == py_self.ptr()); PyObject* recb = PyCallBackAutoDie::py_on_callback_parent_fades.ptr(); this->m_weak_parent = PyWeakref_NewRef(py_parent.ptr(), recb); if (!this->m_weak_parent) throw_error_already_set(); boost::python::incref(this->m_self); PyCallBackAutoDie::s_weak2ob[this->m_weak_parent] = py_self.ptr(); } void PyCallBackAutoDie::unset_autokill_references() { boost::python::decref(m_self); } template static void _run_virtual_once(PyCallBackAutoDie* self, OriginalT * ev, const char* virt_fn_name) { AutoPythonGIL gil; try { CopyT* py_ev = new CopyT(); object py_value = object( handle<>( to_python_indirect< CopyT*, detail::make_owning_holder>()(py_ev) ) ); // - py_ev->device = object(ev->device); No, we use m_weak_parent // so we get exactly the same python object... if (self->m_weak_parent) { PyObject* parent = PyWeakref_GET_OBJECT(self->m_weak_parent); if (parent && parent != Py_None) { py_ev->device = object(handle<>(borrowed(parent))); } } copy_most_fields(self, ev, py_ev); self->get_override(virt_fn_name)(py_value); } catch (...) { self->unset_autokill_references(); /// @todo yes, I want the exception to go to Tango and then wathever. But it will make a memory leak bcos tangoc++ is not handling exceptions properly!! (proxy_asyn_cb.cpp, void Connection::Cb_ReadAttr_Request(CORBA::Request_ptr req,Tango::CallBack *cb_ptr)) /// and the same for cmd_ended, attr_read & attr_written /// @bug See previous todo. If TangoC++ is fixed, it'll become a bug: delete ev; /// @todo or maybe it's just that I am not supposed to re-throw the exception? (still a bug in tangoc++). Then also get rid of the "delete ev" line! throw; } self->unset_autokill_references(); }; /*virtual*/ void PyCallBackAutoDie::cmd_ended(Tango::CmdDoneEvent * ev) { _run_virtual_once(this, ev, "cmd_ended"); }; /*virtual*/ void PyCallBackAutoDie::attr_read(Tango::AttrReadEvent *ev) { _run_virtual_once(this, ev, "attr_read"); }; /*virtual*/ void PyCallBackAutoDie::attr_written(Tango::AttrWrittenEvent *ev) { _run_virtual_once(this, ev, "attr_written"); }; PyCallBackPushEvent::~PyCallBackPushEvent() { boost::python::xdecref(this->m_weak_device); } void PyCallBackPushEvent::set_device(object &py_device) { this->m_weak_device = PyWeakref_NewRef(py_device.ptr(), 0); if (!this->m_weak_device) throw_error_already_set(); } namespace { template void copy_device(OriginalT* ev, object py_ev, object py_device) { if (py_device.ptr() != Py_None) py_ev.attr("device") = py_device; else py_ev.attr("device") = object(ev->device); } template static void _push_event(PyCallBackPushEvent* self, OriginalT * ev) { // If the event is received after python dies but before the process // finishes then discard the event if (!Py_IsInitialized()) { cout4 << "Tango event (" << ev->event << ") received for after python shutdown. " << "Event will be ignored" << std::endl; return; } AutoPythonGIL gil; // Make a copy of ev in python // (the original will be deleted by TangoC++ on return) object py_ev(ev); OriginalT* ev_copy = extract(py_ev); // If possible, reuse the original DeviceProxy object py_device; if (self->m_weak_device) { PyObject* py_c_device = PyWeakref_GET_OBJECT(self->m_weak_device); if (py_c_device && py_c_device != Py_None) { py_device = object(handle<>(borrowed(py_c_device))); } } try { PyCallBackPushEvent::fill_py_event(ev_copy, py_ev, py_device, self->m_extract_as); } SAFE_CATCH_REPORT("PyCallBackPushEvent::fill_py_event") try { self->get_override("push_event")(py_ev); } SAFE_CATCH_INFORM("push_event") }; } boost::python::object PyCallBackPushEvent::get_override(const char* name) { return boost::python::wrapper::get_override(name); } void PyCallBackPushEvent::fill_py_event(Tango::EventData* ev, object & py_ev, object py_device, PyTango::ExtractAs extract_as) { copy_device(ev, py_ev, py_device); /// @todo on error extracting, we could save the error in DeviceData /// instead of throwing it...? // Save a copy of attr_value, so we can still access it after // the execution of the callback (Tango will delete the original!) // I originally was 'stealing' the reference to TangoC++: I got // attr_value and set it to 0... But now TangoC++ is not deleting // attr_value pointer but its own copy, so my efforts are useless. if (ev->attr_value) { #ifdef PYTANGO_HAS_UNIQUE_PTR Tango::DeviceAttribute *attr = new Tango::DeviceAttribute; (*attr) = std::move(*ev->attr_value); #else Tango::DeviceAttribute *attr = new Tango::DeviceAttribute(*ev->attr_value); #endif py_ev.attr("attr_value") = PyDeviceAttribute::convert_to_python(attr, *ev->device, extract_as); } // ev->attr_value = 0; // Do not delete, python will. } void PyCallBackPushEvent::fill_py_event(Tango::AttrConfEventData* ev, object & py_ev, object py_device, PyTango::ExtractAs extract_as) { copy_device(ev, py_ev, py_device); if (ev->attr_conf) { py_ev.attr("attr_conf") = *ev->attr_conf; } } void PyCallBackPushEvent::fill_py_event(Tango::DataReadyEventData* ev, object & py_ev, object py_device, PyTango::ExtractAs extract_as) { copy_device(ev, py_ev, py_device); } void PyCallBackPushEvent::fill_py_event(Tango::PipeEventData* ev, object & py_ev, object py_device, PyTango::ExtractAs extract_as) { copy_device(ev, py_ev, py_device); if (ev->pipe_value) { #ifdef PYTANGO_HAS_UNIQUE_PTR Tango::DevicePipe *pipe_value = new Tango::DevicePipe; (*pipe_value) = std::move(*ev->pipe_value); #else Tango::DevicePipe *pipe_value = new Tango::DevicePipe(*ev->pipe_value); #endif py_ev.attr("pipe_value") = PyTango::DevicePipe::convert_to_python(pipe_value, extract_as); } } void PyCallBackPushEvent::fill_py_event(Tango::DevIntrChangeEventData* ev, object & py_ev, object py_device, PyTango::ExtractAs extract_as) { copy_device(ev, py_ev, py_device); py_ev.attr("cmd_list") = ev->cmd_list; py_ev.attr("att_list") = ev->att_list; } /*virtual*/ void PyCallBackPushEvent::push_event(Tango::EventData *ev) { _push_event(this, ev); } /*virtual*/ void PyCallBackPushEvent::push_event(Tango::AttrConfEventData *ev) { _push_event(this, ev); } /*virtual*/ void PyCallBackPushEvent::push_event(Tango::DataReadyEventData *ev) { _push_event(this, ev); } /*virtual*/ void PyCallBackPushEvent::push_event(Tango::PipeEventData *ev) { _push_event(this, ev); } /*virtual*/ void PyCallBackPushEvent::push_event(Tango::DevIntrChangeEventData *ev) { _push_event(this, ev); } void export_callback() { PyCallBackAutoDie::init(); /// @todo move somewhere else, another file i tal... class_ CmdDoneEvent("CmdDoneEvent", no_init); CmdDoneEvent .def_readonly("device", &PyCmdDoneEvent::device) .def_readonly("cmd_name", &PyCmdDoneEvent::cmd_name) .def_readonly("argout_raw", &PyCmdDoneEvent::argout_raw) .def_readonly("argout", &PyCmdDoneEvent::argout) .def_readonly("err", &PyCmdDoneEvent::err) .def_readonly("errors", &PyCmdDoneEvent::errors) .def_readonly("ext", &PyCmdDoneEvent::ext) ; class_ AttrReadEvent("AttrReadEvent", no_init); AttrReadEvent .def_readonly("device", &PyAttrReadEvent::device) .def_readonly("attr_names", &PyAttrReadEvent::attr_names) .def_readonly("argout", &PyAttrReadEvent::argout) .def_readonly("err", &PyAttrReadEvent::err) .def_readonly("errors", &PyAttrReadEvent::errors) .def_readonly("ext", &PyAttrReadEvent::ext) ; class_ AttrWrittenEvent( "AttrWrittenEvent", no_init); AttrWrittenEvent .def_readonly("device", &PyAttrWrittenEvent::device) .def_readonly("attr_names", &PyAttrWrittenEvent::attr_names) .def_readonly("err", &PyAttrWrittenEvent::err) .def_readonly("errors", &PyAttrWrittenEvent::errors) .def_readonly("ext", &PyAttrWrittenEvent::ext) ; class_ CallBackAutoDie( "__CallBackAutoDie", "INTERNAL CLASS - DO NOT USE IT", init<>()) ; CallBackAutoDie .def("cmd_ended", &PyCallBackAutoDie::cmd_ended, "This method is defined as being empty and must be overloaded by the user when the asynchronous callback model is used. This is the method which will be executed when the server reply from a command_inout is received in both push and pull sub-mode.") .def("attr_read", &PyCallBackAutoDie::attr_read, "This method is defined as being empty and must be overloaded by the user when the asynchronous callback model is used. This is the method which will be executed when the server reply from a read_attribute(s) is received in both push and pull sub-mode.") .def("attr_written", &PyCallBackAutoDie::attr_written, "This method is defined as being empty and must be overloaded by the user when the asynchronous callback model is used. This is the method which will be executed when the server reply from a write_attribute(s) is received in both push and pull sub-mode. ") ; class_ CallBackPushEvent( "__CallBackPushEvent", "INTERNAL CLASS - DO NOT USE IT", init<>()) ; CallBackPushEvent .def("push_event", (void (PyCallBackAutoDie::*)(Tango::EventData*))&PyCallBackAutoDie::push_event, "This method is defined as being empty and must be overloaded by the user when events are used. This is the method which will be executed when the server send event(s) to the client. ") .def("push_event", (void (PyCallBackAutoDie::*)(Tango::AttrConfEventData*))&PyCallBackAutoDie::push_event, "This method is defined as being empty and must be overloaded by the user when events are used. This is the method which will be executed when the server send attribute configuration change event(s) to the client. ") .def("push_event", (void (PyCallBackAutoDie::*)(Tango::DataReadyEventData*))&PyCallBackAutoDie::push_event, "This method is defined as being empty and must be overloaded by the user when events are used. This is the method which will be executed when the server send attribute data ready event(s) to the client. ") .def("push_event", (void (PyCallBackAutoDie::*)(Tango::PipeEventData*))&PyCallBackAutoDie::push_event, "This method is defined as being empty and must be overloaded by the user when events are used. This is the method which will be executed when the server send pipe event(s) to the client. ") .def("push_event", (void (PyCallBackAutoDie::*)(Tango::DevIntrChangeEventData*))&PyCallBackAutoDie::push_event, "This method is defined as being empty and must be overloaded by the user when events are used. This is the method which will be executed when the server send device interface change event(s) to the client. ") ; } pytango-9.2.2/ext/callback.h000066400000000000000000000111121316324373100157110ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #pragma once #include #include "defs.h" /// Tango expects an object for callbacks derived from Tango::CallBack. /// For read_attribute_asynch, write_attribute_asynch, the callback object /// should be available until the callback is run. Then it can disappear. /// Also if we forget about a DeviceProxy we don't need the callback anymore. /// For event subscription however, the requirements are different. The C++ /// callback can be called way after the original DeviceProxy has disappered. /// So for this case, the callback should live forever. As we don't want it, /// we implemented the deletion of the callback in the DeviceProxy destructor /// itself, after performing an unsubscribe. /// @todo this is for cmd_ended, attr_read and attr_written. push_event are not done! class PyCallBackAutoDie : public Tango::CallBack , public boost::python::wrapper { public: PyCallBackAutoDie() : m_self(0), m_weak_parent(0), m_extract_as(PyTango::ExtractAsNumpy) {} virtual ~PyCallBackAutoDie(); //! It is the PyCallBackAutoDie object itself, as seen from python PyObject* m_self; //! The object that will call this callback, so we can //! monitor if it disappears, we are not needed anymore. PyObject* m_weak_parent; PyTango::ExtractAs m_extract_as; static std::map s_weak2ob; static boost::python::object py_on_callback_parent_fades; static void on_callback_parent_fades(PyObject* weakobj); static void init(); void set_autokill_references(boost::python::object &py_self, boost::python::object &py_parent); void unset_autokill_references(); void set_extract_as(PyTango::ExtractAs extract_as) { this->m_extract_as = extract_as; } boost::python::object get_override(const char* name) { return boost::python::wrapper::get_override(name); } virtual void cmd_ended(Tango::CmdDoneEvent * ev); virtual void attr_read(Tango::AttrReadEvent *ev); virtual void attr_written(Tango::AttrWrittenEvent *ev); // virtual void push_event(Tango::EventData *ev); // virtual void push_event(Tango::AttrConfEventData *ev); // virtual void push_event(Tango::DataReadyEventData *ev); }; class PyCallBackPushEvent : public Tango::CallBack , public boost::python::wrapper { public: PyCallBackPushEvent() : m_weak_device(0), m_extract_as(PyTango::ExtractAsNumpy) {} virtual ~PyCallBackPushEvent(); //! The object that will call this callback (DeviceProxy), so we can //! monitor if it disappears, we are not needed anymore. PyObject* m_weak_device; PyTango::ExtractAs m_extract_as; void set_device(boost::python::object &py_device); void set_extract_as(PyTango::ExtractAs extract_as) { this->m_extract_as = extract_as; } boost::python::object get_override(const char* name); // virtual void cmd_ended(Tango::CmdDoneEvent * ev); // virtual void attr_read(Tango::AttrReadEvent *ev); // virtual void attr_written(Tango::AttrWrittenEvent *ev); virtual void push_event(Tango::EventData *ev); virtual void push_event(Tango::AttrConfEventData *ev); virtual void push_event(Tango::DataReadyEventData *ev); virtual void push_event(Tango::PipeEventData *ev); virtual void push_event(Tango::DevIntrChangeEventData *ev); static void fill_py_event(Tango::EventData* ev, boost::python::object & py_ev, boost::python::object py_device, PyTango::ExtractAs extract_as); static void fill_py_event(Tango::AttrConfEventData* ev, boost::python::object & py_ev, boost::python::object py_device, PyTango::ExtractAs extract_as); static void fill_py_event(Tango::DataReadyEventData* ev, boost::python::object & py_ev, boost::python::object py_device, PyTango::ExtractAs extract_as); static void fill_py_event(Tango::PipeEventData* ev, boost::python::object & py_ev, boost::python::object py_device, PyTango::ExtractAs extract_as); static void fill_py_event(Tango::DevIntrChangeEventData* ev, boost::python::object & py_ev, boost::python::object py_device, PyTango::ExtractAs extract_as); }; pytango-9.2.2/ext/change_event_info.cpp000066400000000000000000000017171316324373100201630ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include using namespace boost::python; void export_change_event_info() { class_("ChangeEventInfo") .enable_pickling() .def_readwrite("rel_change", &Tango::ChangeEventInfo::rel_change) .def_readwrite("abs_change", &Tango::ChangeEventInfo::abs_change) .def_readwrite("extensions", &Tango::ChangeEventInfo::extensions) ; } pytango-9.2.2/ext/command_info.cpp000066400000000000000000000014621316324373100171500ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include using namespace boost::python; void export_command_info() { class_ >("CommandInfo") .def_readonly("disp_level", &Tango::CommandInfo::disp_level) ; } pytango-9.2.2/ext/connection.cpp000066400000000000000000000131671316324373100166630ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include "pytgutils.h" #include "callback.h" namespace PyConnection { using namespace boost::python; static Tango::DeviceData command_inout(Tango::Connection& self, const string & cmd_name, const Tango::DeviceData &argin) { AutoPythonAllowThreads guard; return self.command_inout(const_cast(cmd_name), const_cast(argin)); } static long command_inout_asynch_id(Tango::Connection& self, const string &cmd_name, const Tango::DeviceData &argin, bool forget) { AutoPythonAllowThreads guard; return self.command_inout_asynch(const_cast(cmd_name), const_cast(argin), forget); } static Tango::DeviceData command_inout_reply(Tango::Connection& self, long id) { AutoPythonAllowThreads guard; return self.command_inout_reply(id); } static Tango::DeviceData command_inout_reply(Tango::Connection& self, long id, long timeout) { AutoPythonAllowThreads guard; return self.command_inout_reply(id, timeout); } static void command_inout_asynch_cb(object py_self, const string & cmd_name, const Tango::DeviceData &argin, object py_cb) { Tango::Connection* self = extract(py_self); PyCallBackAutoDie* cb = extract(py_cb); cb->set_autokill_references(py_cb, py_self); try { AutoPythonAllowThreads guard; self->command_inout_asynch(const_cast(cmd_name), const_cast(argin), *cb); } catch (...) { cb->unset_autokill_references(); throw; } } static void get_asynch_replies(Tango::Connection& self) { AutoPythonAllowThreads guard; self.get_asynch_replies(); } static void get_asynch_replies(Tango::Connection& self, long call_timeout) { AutoPythonAllowThreads guard; self.get_asynch_replies(call_timeout); } str get_fqdn() { string fqdn; Tango::Connection::get_fqdn(fqdn); return str(fqdn.c_str()); } } void export_connection() { using namespace boost::python; class_ Connection("Connection", no_init) ; Connection .def("dev_name", pure_virtual(&Tango::Connection::dev_name)) .def("get_db_host", &Tango::Connection::get_db_host, return_value_policy()) .def("get_db_port", &Tango::Connection::get_db_port, return_value_policy()) .def("get_db_port_num", &Tango::Connection::get_db_port_num) .def("get_from_env_var", &Tango::Connection::get_from_env_var) .def("get_fqdn", &PyConnection::get_fqdn) .staticmethod("get_fqdn") .def("is_dbase_used", &Tango::Connection::is_dbase_used) .def("get_dev_host", &Tango::Connection::get_dev_host, return_value_policy()) .def("get_dev_port", &Tango::Connection::get_dev_port, return_value_policy()) .def("connect", &Tango::Connection::connect) .def("reconnect", &Tango::Connection::reconnect) .def("get_idl_version", &Tango::Connection::get_idl_version) .def("set_timeout_millis", &Tango::Connection::set_timeout_millis) .def("get_timeout_millis", &Tango::Connection::get_timeout_millis) .def("get_source", &Tango::Connection::get_source) .def("set_source", &Tango::Connection::set_source) .def("get_transparency_reconnection", &Tango::Connection::get_transparency_reconnection) .def("set_transparency_reconnection", &Tango::Connection::set_transparency_reconnection) .def("__command_inout", &PyConnection::command_inout) .def("__command_inout_asynch_id", &PyConnection::command_inout_asynch_id) .def("__command_inout_asynch_cb", &PyConnection::command_inout_asynch_cb) .def("command_inout_reply_raw", (Tango::DeviceData (*)(Tango::Connection&, long)) &PyConnection::command_inout_reply) .def("command_inout_reply_raw", (Tango::DeviceData (*)(Tango::Connection&, long, long)) &PyConnection::command_inout_reply) // // Asynchronous methods // .def("get_asynch_replies", (void (*) (Tango::Connection&)) &PyConnection::get_asynch_replies) .def("get_asynch_replies", (void (*) (Tango::Connection&,long)) &PyConnection::get_asynch_replies) .def("cancel_asynch_request", &Tango::Connection::cancel_asynch_request) .def("cancel_all_polling_asynch_request", &Tango::Connection::cancel_all_polling_asynch_request) // // Control access related methods // .def("get_access_control", &Tango::Connection::get_access_control) .def("set_access_control", &Tango::Connection::set_access_control) .def("get_access_right", &Tango::Connection::get_access_right) ; } pytango-9.2.2/ext/constants.cpp000066400000000000000000000400401316324373100165260ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include #include "tango_numpy.h" #include "tgutils.h" using namespace boost::python; long TANGO_VERSION_HEX; void export_constants() { object consts_module(handle<>(borrowed(PyImport_AddModule("tango.constants")))); scope().attr("constants") = consts_module; scope consts_scope = consts_module; consts_scope.attr("__doc__") = "module containing several Tango constants.\n" "\nNew in PyTango 7.0.0"; #ifdef DISABLE_PYTANGO_NUMPY consts_scope.attr("NUMPY_SUPPORT") = false; consts_scope.attr("NUMPY_VERSION") = "0.0.0"; #else consts_scope.attr("NUMPY_SUPPORT") = true; #ifdef PYTANGO_NUMPY_VERSION consts_scope.attr("NUMPY_VERSION") = PYTANGO_NUMPY_VERSION; #else consts_scope.attr("NUMPY_VERSION") = "0.0.0"; #endif #endif consts_scope.attr("PY_MAJOR_VERSION") = PY_MAJOR_VERSION; consts_scope.attr("PY_MINOR_VERSION") = PY_MINOR_VERSION; consts_scope.attr("PY_MICRO_VERSION") = PY_MICRO_VERSION; consts_scope.attr("PY_VERSION") = PY_VERSION; consts_scope.attr("PY_VERSION_HEX") = PY_VERSION_HEX; consts_scope.attr("BOOST_MAJOR_VERSION") = BOOST_VERSION / 100000; consts_scope.attr("BOOST_MINOR_VERSION") = BOOST_VERSION / 100 % 1000; consts_scope.attr("BOOST_PATCH_VERSION") = BOOST_VERSION % 100; // missing BOOST_VERSION => do it in python // // From tango_const.h // // // Some general interest define // consts_scope.attr("TANGO_VERSION_MAJOR") = TANGO_VERSION_MAJOR; consts_scope.attr("TANGO_VERSION_MINOR") = TANGO_VERSION_MINOR; consts_scope.attr("TANGO_VERSION_PATCH") = TANGO_VERSION_PATCH; consts_scope.attr("TANGO_VERSION_NB") = TANGO_VERSION_NB; consts_scope.attr("TANGO_VERSION") = Tango::TgLibVers; consts_scope.attr("TgLibVers") = Tango::TgLibVers; consts_scope.attr("TgLibMajorVers") = Tango::TgLibMajorVers; consts_scope.attr("TgLibVersNb") = Tango::TgLibVersNb; consts_scope.attr("DevVersion") = Tango::DevVersion; consts_scope.attr("DefaultMaxSeq") = Tango::DefaultMaxSeq; consts_scope.attr("DefaultBlackBoxDepth") = Tango::DefaultBlackBoxDepth; consts_scope.attr("DefaultPollRingDepth") = Tango::DefaultPollRingDepth; consts_scope.attr("InitialOutput") =Tango:: InitialOutput; consts_scope.attr("DSDeviceDomain") = Tango::DSDeviceDomain; consts_scope.attr("DefaultDocUrl") = Tango::DefaultDocUrl; consts_scope.attr("EnvVariable") = Tango::EnvVariable; consts_scope.attr("WindowsEnvVariable") = Tango::WindowsEnvVariable; consts_scope.attr("DbObjName") = Tango::DbObjName; // Changed in tango 8 from DescNotSet to NotSet. We keep the old constant // to try to maintain backward compatibility consts_scope.attr("DescNotSet") = Tango::NotSet; consts_scope.attr("NotSet") = Tango::NotSet; consts_scope.attr("ResNotDefined") = Tango::ResNotDefined; consts_scope.attr("MessBoxTitle") = Tango::MessBoxTitle; consts_scope.attr("StatusNotSet") = Tango::StatusNotSet; consts_scope.attr("TangoHostNotSet") = Tango::TangoHostNotSet; consts_scope.attr("RootAttNotDef") = Tango::RootAttNotDef; consts_scope.attr("DefaultWritAttrProp") = Tango::DefaultWritAttrProp; consts_scope.attr("AllAttr") = Tango::AllAttr; consts_scope.attr("AllAttr_3") = Tango::AllAttr_3; consts_scope.attr("AllPipe") = Tango::AllPipe; consts_scope.attr("AllCmd") = Tango::AllCmd; consts_scope.attr("PollCommand") = Tango::PollCommand; consts_scope.attr("PollAttribute") = Tango::PollAttribute; consts_scope.attr("LOCAL_POLL_REQUEST") = Tango::LOCAL_POLL_REQUEST; consts_scope.attr("LOCAL_REQUEST_STR_SIZE") = Tango::LOCAL_REQUEST_STR_SIZE; consts_scope.attr("MIN_POLL_PERIOD") = Tango::MIN_POLL_PERIOD; consts_scope.attr("DELTA_T") = Tango::DELTA_T; consts_scope.attr("MIN_DELTA_WORK") = Tango::MIN_DELTA_WORK; consts_scope.attr("TIME_HEARTBEAT") = Tango::TIME_HEARTBEAT; consts_scope.attr("POLL_LOOP_NB") = Tango::POLL_LOOP_NB; consts_scope.attr("ONE_SECOND") = Tango::ONE_SECOND; consts_scope.attr("DISCARD_THRESHOLD") = Tango::DISCARD_THRESHOLD; consts_scope.attr("DEFAULT_TIMEOUT") = Tango::DEFAULT_TIMEOUT; consts_scope.attr("DEFAULT_POLL_OLD_FACTOR") = Tango::DEFAULT_POLL_OLD_FACTOR; consts_scope.attr("TG_IMP_MINOR_TO") = Tango::TG_IMP_MINOR_TO; consts_scope.attr("TG_IMP_MINOR_DEVFAILED") = Tango::TG_IMP_MINOR_DEVFAILED; consts_scope.attr("TG_IMP_MINOR_NON_DEVFAILED") = Tango::TG_IMP_MINOR_NON_DEVFAILED; consts_scope.attr("TANGO_PY_MOD_NAME") = Tango::TANGO_PY_MOD_NAME; consts_scope.attr("DATABASE_CLASS") = Tango::DATABASE_CLASS; consts_scope.attr("TANGO_FLOAT_PRECISION") = Tango::TANGO_FLOAT_PRECISION; consts_scope.attr("NoClass") = Tango::NoClass; // // Event related define // consts_scope.attr("EVENT_HEARTBEAT_PERIOD") = Tango::EVENT_HEARTBEAT_PERIOD; consts_scope.attr("EVENT_RESUBSCRIBE_PERIOD") = Tango::EVENT_RESUBSCRIBE_PERIOD; consts_scope.attr("DEFAULT_EVENT_PERIOD") = Tango::DEFAULT_EVENT_PERIOD; consts_scope.attr("DELTA_PERIODIC") = Tango::DELTA_PERIODIC; consts_scope.attr("DELTA_PERIODIC_LONG") = Tango::DELTA_PERIODIC_LONG; consts_scope.attr("HEARTBEAT") = Tango::HEARTBEAT; // // ZMQ event system related define // consts_scope.attr("ZMQ_EVENT_PROT_VERSION") = Tango::ZMQ_EVENT_PROT_VERSION; consts_scope.attr("HEARTBEAT_METHOD_NAME") = Tango::HEARTBEAT_METHOD_NAME; consts_scope.attr("EVENT_METHOD_NAME") = Tango::EVENT_METHOD_NAME; consts_scope.attr("HEARTBEAT_EVENT_NAME") = Tango::HEARTBEAT_EVENT_NAME; consts_scope.attr("CTRL_SOCK_ENDPOINT") = Tango::CTRL_SOCK_ENDPOINT; consts_scope.attr("MCAST_PROT") = Tango::MCAST_PROT; consts_scope.attr("MCAST_HOPS") = Tango::MCAST_HOPS; consts_scope.attr("PGM_RATE") = Tango::PGM_RATE; consts_scope.attr("PGM_IVL") = Tango::PGM_IVL; consts_scope.attr("MAX_SOCKET_SUB") = Tango::MAX_SOCKET_SUB; consts_scope.attr("PUB_HWM") = Tango::PUB_HWM; consts_scope.attr("SUB_HWM") = Tango::SUB_HWM; consts_scope.attr("SUB_SEND_HWM") = Tango::SUB_SEND_HWM; consts_scope.attr("NOTIFD_CHANNEL") = Tango::NOTIFD_CHANNEL; // // Locking feature related defines // consts_scope.attr("DEFAULT_LOCK_VALIDITY") = Tango::DEFAULT_LOCK_VALIDITY; consts_scope.attr("DEVICE_UNLOCKED_REASON") = Tango::DEVICE_UNLOCKED_REASON; consts_scope.attr("MIN_LOCK_VALIDITY") = Tango::MIN_LOCK_VALIDITY; consts_scope.attr("TG_LOCAL_HOST") = Tango::TG_LOCAL_HOST; // // Client timeout as defined by omniORB4.0.0 // consts_scope.attr("CLNT_TIMEOUT_STR") = Tango::CLNT_TIMEOUT_STR; consts_scope.attr("CLNT_TIMEOUT") = Tango::CLNT_TIMEOUT; consts_scope.attr("NARROW_CLNT_TIMEOUT") = Tango::NARROW_CLNT_TIMEOUT; // // Connection and call timeout for database device // consts_scope.attr("DB_CONNECT_TIMEOUT") = Tango::DB_CONNECT_TIMEOUT; consts_scope.attr("DB_RECONNECT_TIMEOUT") = Tango::DB_RECONNECT_TIMEOUT; consts_scope.attr("DB_TIMEOUT") = Tango::DB_TIMEOUT; consts_scope.attr("DB_START_PHASE_RETRIES") = Tango::DB_START_PHASE_RETRIES; // // Time to wait before trying to reconnect after // a connevtion failure // consts_scope.attr("RECONNECTION_DELAY") = Tango::RECONNECTION_DELAY; // // Access Control related defines // WARNING: these string are also used within the Db stored procedure // introduced in Tango V6.1. If you chang eit here, don't forget to // also update the stored procedure // consts_scope.attr("CONTROL_SYSTEM") = Tango::CONTROL_SYSTEM; consts_scope.attr("SERVICE_PROP_NAME") = Tango::SERVICE_PROP_NAME; consts_scope.attr("ACCESS_SERVICE") = Tango::ACCESS_SERVICE; // // Polling threads pool related defines // consts_scope.attr("DEFAULT_POLLING_THREADS_POOL_SIZE") = Tango::DEFAULT_POLLING_THREADS_POOL_SIZE; // // Max transfer size 256 MBytes (in byte). Needed by omniORB // consts_scope.attr("MAX_TRANSFER_SIZE") = Tango::MAX_TRANSFER_SIZE; // // Max GIOP connection per server . Needed by omniORB // consts_scope.attr("MAX_GIOP_PER_SERVER") = Tango::MAX_GIOP_PER_SERVER; // // Tango name length // consts_scope.attr("MaxServerNameLength") = Tango::MaxServerNameLength; consts_scope.attr("MaxDevPropLength") = Tango::MaxDevPropLength; // // For forwarded attribute implementation // consts_scope.attr("MIN_IDL_CONF5") = Tango::MIN_IDL_CONF5; consts_scope.attr("MIN_IDL_DEV_INTR") = Tango::MIN_IDL_DEV_INTR; consts_scope.attr("ALL_EVENTS") = Tango::ALL_EVENTS; // -------------------------------------------------------- // // Files used to retrieve env. variables // consts_scope.attr("USER_ENV_VAR_FILE") = Tango::USER_ENV_VAR_FILE; consts_scope.attr("kLogTargetConsole") = Tango::kLogTargetConsole; consts_scope.attr("kLogTargetFile") = Tango::kLogTargetFile; consts_scope.attr("kLogTargetDevice") = Tango::kLogTargetDevice; consts_scope.attr("kLogTargetSep") = Tango::kLogTargetSep; consts_scope.attr("AlrmValueNotSpec") = Tango::AlrmValueNotSpec; consts_scope.attr("AssocWritNotSpec") = Tango::AssocWritNotSpec; consts_scope.attr("LabelNotSpec") = Tango::LabelNotSpec; consts_scope.attr("DescNotSpec") = Tango::DescNotSpec; consts_scope.attr("UnitNotSpec") = Tango::UnitNotSpec; consts_scope.attr("StdUnitNotSpec") = Tango::StdUnitNotSpec; consts_scope.attr("DispUnitNotSpec") = Tango::DispUnitNotSpec; #ifdef FormatNotSpec consts_scope.attr("FormatNotSpec") = Tango::FormatNotSpec; #else consts_scope.attr("FormatNotSpec") = Tango::FormatNotSpec_FL; #endif consts_scope.attr("FormatNotSpec_FL") = Tango::FormatNotSpec_FL; consts_scope.attr("FormatNotSpec_INT") = Tango::FormatNotSpec_INT; consts_scope.attr("FormatNotSpec_STR") = Tango::FormatNotSpec_STR; consts_scope.attr("NotANumber") = Tango::NotANumber; consts_scope.attr("MemNotUsed") = Tango::MemNotUsed; consts_scope.attr("MemAttrPropName") = Tango::MemAttrPropName; #ifdef TANGO_LONG64 consts_scope.attr("TANGO_LONG32") = false; consts_scope.attr("TANGO_LONG64") = true; #else consts_scope.attr("TANGO_LONG32") = true; consts_scope.attr("TANGO_LONG64") = false; #endif consts_scope.attr("API_AttrConfig") = Tango::API_AttrConfig; consts_scope.attr("API_AttrEventProp") = Tango::API_AttrEventProp; consts_scope.attr("API_AttrIncorrectDataNumber") = Tango::API_AttrIncorrectDataNumber; consts_scope.attr("API_AttrNoAlarm") = Tango::API_AttrNoAlarm; consts_scope.attr("API_AttrNotAllowed") = Tango::API_AttrNotAllowed; consts_scope.attr("API_AttrNotFound") = Tango::API_AttrNotFound; consts_scope.attr("API_AttrNotWritable") = Tango::API_AttrNotWritable; consts_scope.attr("API_AttrOptProp") = Tango::API_AttrOptProp; consts_scope.attr("API_AttrPropValueNotSet") = Tango::API_AttrPropValueNotSet; consts_scope.attr("API_AttrValueNotSet") = Tango::API_AttrValueNotSet; consts_scope.attr("API_AttrWrongDefined") = Tango::API_AttrWrongDefined; consts_scope.attr("API_AttrWrongMemValue") = Tango::API_AttrWrongMemValue; consts_scope.attr("API_BadConfigurationProperty") = Tango::API_BadConfigurationProperty; consts_scope.attr("API_BlackBoxArgument") = Tango::API_BlackBoxArgument; consts_scope.attr("API_BlackBoxEmpty") = Tango::API_BlackBoxEmpty; consts_scope.attr("API_CannotCheckAccessControl") = Tango::API_CannotCheckAccessControl; consts_scope.attr("API_CannotOpenFile") = Tango::API_CannotOpenFile; consts_scope.attr("API_CantActivatePOAManager") = Tango::API_CantActivatePOAManager; consts_scope.attr("API_CantCreateClassPoa") = Tango::API_CantCreateClassPoa; consts_scope.attr("API_CantCreateLockingThread") = Tango::API_CantCreateLockingThread; consts_scope.attr("API_CantFindLockingThread") = Tango::API_CantFindLockingThread; consts_scope.attr("API_CantGetClientIdent") = Tango::API_CantGetClientIdent; consts_scope.attr("API_CantGetDevObjectId") = Tango::API_CantGetDevObjectId; consts_scope.attr("API_CantInstallSignal") = Tango::API_CantInstallSignal; consts_scope.attr("API_CantRetrieveClass") = Tango::API_CantRetrieveClass; consts_scope.attr("API_CantRetrieveClassList") = Tango::API_CantRetrieveClassList; consts_scope.attr("API_CantStoreDeviceClass") = Tango::API_CantStoreDeviceClass; consts_scope.attr("API_ClassNotFound") = Tango::API_ClassNotFound; consts_scope.attr("API_CmdArgumentTypeNotSupported") = Tango::API_CmdArgumentTypeNotSupported; consts_scope.attr("API_CommandNotAllowed") = Tango::API_CommandNotAllowed; consts_scope.attr("API_CommandNotFound") = Tango::API_CommandNotFound; consts_scope.attr("API_CorbaSysException") = Tango::API_CorbaSysException; consts_scope.attr("API_CorruptedDatabase") = Tango::API_CorruptedDatabase; consts_scope.attr("API_DatabaseAccess") = Tango::API_DatabaseAccess; consts_scope.attr("API_DeviceLocked") = Tango::API_DeviceLocked; consts_scope.attr("API_DeviceNotFound") = Tango::API_DeviceNotFound; consts_scope.attr("API_DeviceNotLocked") = Tango::API_DeviceNotLocked; consts_scope.attr("API_DeviceUnlockable") = Tango::API_DeviceUnlockable; consts_scope.attr("API_DeviceUnlocked") = Tango::API_DeviceUnlocked; consts_scope.attr("API_EventSupplierNotConstructed") = Tango::API_EventSupplierNotConstructed; consts_scope.attr("API_IncoherentDbData") = Tango::API_IncoherentDbData; consts_scope.attr("API_IncoherentDevData") = Tango::API_IncoherentDevData; consts_scope.attr("API_IncoherentValues") = Tango::API_IncoherentValues; consts_scope.attr("API_IncompatibleAttrDataType") = Tango::API_IncompatibleAttrDataType; consts_scope.attr("API_IncompatibleCmdArgumentType") = Tango::API_IncompatibleCmdArgumentType; consts_scope.attr("API_InitMethodNotFound") = Tango::API_InitMethodNotFound; consts_scope.attr("API_InitNotPublic") = Tango::API_InitNotPublic; consts_scope.attr("API_InitThrowsException") = Tango::API_InitThrowsException; consts_scope.attr("API_JavaRuntimeSecurityException") = Tango::API_JavaRuntimeSecurityException; consts_scope.attr("API_MemoryAllocation") = Tango::API_MemoryAllocation; consts_scope.attr("API_MethodArgument") = Tango::API_MethodArgument; consts_scope.attr("API_MethodNotFound") = Tango::API_MethodNotFound; consts_scope.attr("API_MissedEvents") = Tango::API_MissedEvents; consts_scope.attr("API_NotSupportedFeature") = Tango::API_NotSupportedFeature; consts_scope.attr("API_NtDebugWindowError") = Tango::API_NtDebugWindowError; consts_scope.attr("API_OverloadingNotSupported") = Tango::API_OverloadingNotSupported; consts_scope.attr("API_PolledDeviceNotInPoolConf") = Tango::API_PolledDeviceNotInPoolConf; consts_scope.attr("API_PolledDeviceNotInPoolMap") = Tango::API_PolledDeviceNotInPoolMap; consts_scope.attr("API_PollingThreadNotFound") = Tango::API_PollingThreadNotFound; consts_scope.attr("API_ReadOnlyMode") = Tango::API_ReadOnlyMode; consts_scope.attr("API_SignalOutOfRange") = Tango::API_SignalOutOfRange; consts_scope.attr("API_SystemCallFailed") = Tango::API_SystemCallFailed; consts_scope.attr("API_WAttrOutsideLimit") = Tango::API_WAttrOutsideLimit; consts_scope.attr("API_WizardConfError") = Tango::API_WizardConfError; consts_scope.attr("API_WrongEventData") = Tango::API_WrongEventData; consts_scope.attr("API_WrongHistoryDataBuffer") = Tango::API_WrongHistoryDataBuffer; consts_scope.attr("API_WrongLockingStatus") = Tango::API_WrongLockingStatus; consts_scope.attr("API_ZmqInitFailed") = Tango::API_ZmqInitFailed; } pytango-9.2.2/ext/data_ready_event_data.cpp000066400000000000000000000054241316324373100210100ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include #include "exception.h" using namespace boost::python; extern boost::python::object PyTango_DevFailed; struct PyDataReadyEventData { static inline Tango::DeviceProxy* get_device(Tango::DataReadyEventData &self) { return self.device; } static boost::shared_ptr makeDataReadyEventData() { Tango::DataReadyEventData *result = new Tango::DataReadyEventData; return boost::shared_ptr(result); } static void set_errors(Tango::DataReadyEventData &event_data, boost::python::object &dev_failed) { Tango::DevFailed df; boost::python::object errors = dev_failed.attr("args"); sequencePyDevError_2_DevErrorList(errors.ptr(), event_data.errors); } }; void export_data_ready_event_data() { class_("DataReadyEventData", init()) .def("__init__", boost::python::make_constructor(PyDataReadyEventData::makeDataReadyEventData)) // The original Tango::EventData structure has a 'device' field. // However, if we returned this directly we would get a different // python device each time. So we are doing our weird things to make // sure the device returned is the same where the read action was // performed. So we don't return Tango::EventData::device directly. // See callback.cpp .setattr("device",object()) .def_readwrite("attr_name", &Tango::DataReadyEventData::attr_name) .def_readwrite("event", &Tango::DataReadyEventData::event) .def_readwrite("attr_data_type", &Tango::DataReadyEventData::attr_data_type) .def_readwrite("ctr", &Tango::DataReadyEventData::ctr) .def_readwrite("err", &Tango::DataReadyEventData::err) .def_readwrite("reception_date", &Tango::DataReadyEventData::reception_date) .add_property("errors", make_getter(&Tango::DataReadyEventData::errors, return_value_policy()), &PyDataReadyEventData::set_errors) .def("get_date", &Tango::DataReadyEventData::get_date, return_internal_reference<>()) ; } pytango-9.2.2/ext/database.cpp000066400000000000000000000536171316324373100162740ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include "defs.h" #include "pytgutils.h" extern const char *param_must_be_seq; extern const char *unreachable_code; extern const char *non_string_seq; const char *param_numb_or_str_numb = "Second parameter must be an int or a " "string representing an int"; struct PyDatabase { struct PickleSuite : bopy::pickle_suite { static bopy::tuple getinitargs(Tango::Database& self) { std::string& host = self.get_db_host(); std::string& port = self.get_db_port(); if (host.size() > 0 && port.size() > 0) { return bopy::make_tuple(host, port); } else return bopy::make_tuple(); } }; static inline boost::shared_ptr makeDatabase_host_port1(const std::string &host, int port) { return boost::shared_ptr (new Tango::Database(const_cast(host), port)); } static inline boost::shared_ptr makeDatabase_host_port2(const std::string &host, const std::string &port_str) { std::istringstream port_stream(port_str); int port = 0; if(!(port_stream >> port)) { raise_(PyExc_TypeError, param_numb_or_str_numb); } return boost::shared_ptr (new Tango::Database(const_cast(host), port)); } static inline boost::shared_ptr makeDatabase_file(const std::string &filename) { return boost::shared_ptr (new Tango::Database(const_cast(filename))); } static inline boost::python::str get_device_alias(Tango::Database& self, const std::string &alias) { std::string devname; self.get_device_alias(alias, devname); return boost::python::str(devname); } static inline boost::python::str get_alias(Tango::Database& self, const std::string &devname) { std::string alias; self.get_alias(devname, alias); return boost::python::str(alias); } static inline void get_device_property_list2(Tango::Database& self, const std::string &devname, const std::string &wildcard, StdStringVector &d) { self.get_device_property_list(const_cast(devname), wildcard, d); } static inline boost::python::str get_attribute_alias(Tango::Database& self, const std::string &alias) { std::string attrname; self.get_attribute_alias(alias, attrname); return boost::python::str(attrname); } static inline void export_event(Tango::Database& self, const boost::python::object &obj) { Tango::DevVarStringArray par; convert2array(obj, par); self.export_event(&par); } static inline boost::python::str dev_name(Tango::Database& self) { Tango::Connection *conn = static_cast(&self); return boost::python::str(conn->dev_name()); } //static inline boost::python::str get_file_name(Tango::Database& self) //{ // return boost::python::str(self.get_file_name()); //} static inline boost::python::str get_device_from_alias(Tango::Database& self, const std::string &input) { std::string output; self.get_device_from_alias(input, output); return boost::python::str(output); } static inline boost::python::str get_alias_from_device(Tango::Database& self, const std::string &input) { std::string output; self.get_alias_from_device(input, output); return boost::python::str(output); } static inline boost::python::str get_attribute_from_alias(Tango::Database& self, const std::string &input) { std::string output; self.get_attribute_from_alias(input, output); return boost::python::str(output); } static inline boost::python::str get_alias_from_attribute(Tango::Database& self, const std::string &input) { std::string output; self.get_alias_from_attribute(input, output); return boost::python::str(output); } }; void export_database() { // The following function declarations are necessary to be able to cast // the function parameters from string& to const string&, otherwise python // will not recognize the method calls Tango::DbDatum (Tango::Database::*get_host_list_)(std::string &) = &Tango::Database::get_host_list; Tango::DbDatum (Tango::Database::*get_services_)(std::string &, std::string &) = &Tango::Database::get_services; Tango::DbDatum (Tango::Database::*get_device_service_list_)(std::string &) = &Tango::Database::get_device_service_list; void (Tango::Database::*register_service_)(std::string &, std::string &, std::string &) = &Tango::Database::register_service; void (Tango::Database::*unregister_service_)(std::string &, std::string &) = &Tango::Database::unregister_service; Tango::DbDatum (Tango::Database::*get_device_name_)(std::string &, std::string &) = &Tango::Database::get_device_name; Tango::DbDatum (Tango::Database::*get_device_exported_)(std::string &) = &Tango::Database::get_device_exported; Tango::DbDatum (Tango::Database::*get_device_domain_)(std::string &) = &Tango::Database::get_device_domain; Tango::DbDatum (Tango::Database::*get_device_family_)(std::string &) = &Tango::Database::get_device_family; Tango::DbDatum (Tango::Database::*get_device_member_)(std::string &) = &Tango::Database::get_device_member; Tango::DbDatum (Tango::Database::*get_device_alias_list_)(std::string &) = &Tango::Database::get_device_alias_list; std::string (Tango::Database::*get_class_for_device_)(std::string &) = &Tango::Database::get_class_for_device; Tango::DbDatum (Tango::Database::*get_class_inheritance_for_device_)(std::string &) = &Tango::Database::get_class_inheritance_for_device; Tango::DbDatum (Tango::Database::*get_device_exported_for_class_)(std::string &) = &Tango::Database::get_device_exported_for_class; void (Tango::Database::*put_device_alias_)(std::string &, std::string &) = &Tango::Database::put_device_alias; void (Tango::Database::*delete_device_alias_)(std::string &) = &Tango::Database::delete_device_alias; void (Tango::Database::*add_server_)(std::string &, Tango::DbDevInfos &) = &Tango::Database::add_server; void (Tango::Database::*delete_server_)(std::string &) = &Tango::Database::delete_server; void (Tango::Database::*unexport_server_)(std::string &) = &Tango::Database::unexport_server; Tango::DbServerInfo (Tango::Database::*get_server_info_)(std::string &) = &Tango::Database::get_server_info; void (Tango::Database::*delete_server_info_)(std::string &) = &Tango::Database::delete_server_info; Tango::DbDatum (Tango::Database::*get_server_class_list_)(std::string &) = &Tango::Database::get_server_class_list; Tango::DbDatum (Tango::Database::*get_instance_name_list_)(std::string &) = &Tango::Database::get_instance_name_list; Tango::DbDatum (Tango::Database::*get_server_list_)(std::string &) = &Tango::Database::get_server_list; Tango::DbDatum (Tango::Database::*get_host_server_list_)(std::string &) = &Tango::Database::get_host_server_list; Tango::DbDatum (Tango::Database::*get_device_class_list_)(std::string &) = &Tango::Database::get_device_class_list; Tango::DbHistoryList (Tango::Database::*get_property_history_)(std::string &, std::string &) = &Tango::Database::get_property_history; Tango::DbDatum (Tango::Database::*get_object_list_)(std::string &) = &Tango::Database::get_object_list; Tango::DbDatum (Tango::Database::*get_object_property_list_)(std::string &, std::string &) = &Tango::Database::get_object_property_list; Tango::DbHistoryList (Tango::Database::*get_device_property_history_)(std::string &, std::string &) = &Tango::Database::get_device_property_history; Tango::DbDatum (Tango::Database::*get_device_property_list1_)(std::string &, std::string &) = &Tango::Database::get_device_property_list; Tango::DbHistoryList (Tango::Database::*get_device_attribute_property_history_)(std::string &, std::string &, std::string &) = &Tango::Database::get_device_attribute_property_history; Tango::DbHistoryList (Tango::Database::*get_class_property_history_)(std::string &, std::string &) = &Tango::Database::get_class_property_history; Tango::DbDatum (Tango::Database::*get_class_list_)(std::string &) = &Tango::Database::get_class_list; Tango::DbDatum (Tango::Database::*get_class_property_list_)(std::string &) = &Tango::Database::get_class_property_list; Tango::DbHistoryList (Tango::Database::*get_class_attribute_property_history_)(std::string &, std::string &, std::string &) = &Tango::Database::get_class_attribute_property_history; Tango::DbDatum (Tango::Database::*get_class_attribute_list_)(std::string &, std::string &) = &Tango::Database::get_class_attribute_list; Tango::DbDevImportInfo (Tango::Database::*import_device_)(std::string &) = &Tango::Database::import_device; Tango::DbDevFullInfo (Tango::Database::*get_device_info_)(std::string &) = &Tango::Database::get_device_info; Tango::DbDatum (Tango::Database::*get_attribute_alias_list_)(std::string &) = &Tango::Database::get_attribute_alias_list; void (Tango::Database::*put_attribute_alias_)(std::string &, std::string &) = &Tango::Database::put_attribute_alias; void (Tango::Database::*delete_attribute_alias_)(std::string &) = &Tango::Database::delete_attribute_alias; bopy::class_ > Database("Database", bopy::init<>()) ; Database .def(bopy::init()) .def("__init__", bopy::make_constructor(PyDatabase::makeDatabase_host_port1)) .def("__init__", bopy::make_constructor(PyDatabase::makeDatabase_host_port2)) .def("__init__", bopy::make_constructor(PyDatabase::makeDatabase_file)) // // Pickle // .def_pickle(PyDatabase::PickleSuite()) // // general methods // .def("dev_name", &PyDatabase::dev_name) .def("write_filedatabase", &Tango::Database::write_filedatabase) .def("reread_filedatabase", &Tango::Database::write_filedatabase) .def("build_connection", &Tango::Database::write_filedatabase) .def("check_tango_host", &Tango::Database::check_tango_host) .def("check_access_control", &Tango::Database::check_access_control) .def("is_control_access_checked", &Tango::Database::is_control_access_checked) .def("set_access_checked", &Tango::Database::set_access_checked) .def("get_access_except_errors", &Tango::Database::get_access_except_errors, bopy::return_internal_reference<1>()) .def("is_multi_tango_host", &Tango::Database::is_multi_tango_host) .def("get_file_name", &Tango::Database::get_file_name, bopy::return_value_policy()) // // General methods // .def("get_info",&Tango::Database::get_info) .def("get_host_list", (Tango::DbDatum (Tango::Database::*) ()) &Tango::Database::get_host_list) .def("get_host_list", (Tango::DbDatum (Tango::Database::*) (const std::string &)) get_host_list_) .def("get_services", (Tango::DbDatum (Tango::Database::*) (const std::string &, const std::string &)) get_services_) .def("get_device_service_list", (Tango::DbDatum (Tango::Database::*) (const std::string &)) get_device_service_list_) .def("register_service", (void (Tango::Database::*) (const std::string &, const std::string &, const std::string &)) register_service_) .def("unregister_service", (void (Tango::Database::*) (const std::string &, const std::string &)) unregister_service_) // // Device methods // .def("add_device", &Tango::Database::add_device) .def("delete_device", &Tango::Database::delete_device) .def("import_device", (Tango::DbDevImportInfo (Tango::Database::*) (const std::string &)) import_device_) .def("export_device", &Tango::Database::export_device) .def("unexport_device", &Tango::Database::unexport_device) .def("get_device_info", (Tango::DbDevFullInfo (Tango::Database::*) (const std::string &)) get_device_info_) .def("get_device_name", (Tango::DbDatum (Tango::Database::*) (const string &, const string &)) get_device_name_) .def("get_device_exported", (Tango::DbDatum (Tango::Database::*) (const string &)) get_device_exported_) .def("get_device_domain", (Tango::DbDatum (Tango::Database::*) (const string &)) get_device_domain_) .def("get_device_family", (Tango::DbDatum (Tango::Database::*) (const string &)) get_device_family_) .def("get_device_member", (Tango::DbDatum (Tango::Database::*) (const string &)) get_device_member_) .def("get_device_alias", &PyDatabase::get_device_alias) .def("get_alias", &PyDatabase::get_alias) .def("get_device_alias_list", (Tango::DbDatum (Tango::Database::*) (const std::string &)) get_device_alias_list_) .def("get_class_for_device", (std::string (Tango::Database::*) (const std::string &)) get_class_for_device_) .def("get_class_inheritance_for_device", (Tango::DbDatum (Tango::Database::*) (const std::string &)) get_class_inheritance_for_device_) .def("get_device_exported_for_class", (Tango::DbDatum (Tango::Database::*) (const std::string &)) get_device_exported_for_class_) .def("put_device_alias", (void (Tango::Database::*) (const std::string &, const std::string &)) put_device_alias_) .def("delete_device_alias", (void (Tango::Database::*) (const std::string &)) delete_device_alias_) // // server methods // .def("_add_server", (void (Tango::Database::*) (const std::string &, Tango::DbDevInfos &)) add_server_) .def("delete_server", (void (Tango::Database::*) (const std::string &)) delete_server_) .def("_export_server", &Tango::Database::export_server) .def("unexport_server", (void (Tango::Database::*) (const std::string &)) unexport_server_) .def("rename_server", &Tango::Database::rename_server, ( arg_("self"), arg_("old_ds_name"), arg_("new_ds_name") )) .def("get_server_info", (Tango::DbServerInfo (Tango::Database::*) (const std::string &)) get_server_info_) .def("put_server_info", &Tango::Database::put_server_info, ( arg_("self"), arg_("info") )) .def("delete_server_info", (void (Tango::Database::*) (const std::string &)) delete_server_info_) .def("get_server_class_list", (Tango::DbDatum (Tango::Database::*) (const std::string &)) get_server_class_list_) .def("get_server_name_list", &Tango::Database::get_server_name_list) .def("get_instance_name_list", (Tango::DbDatum (Tango::Database::*) (const std::string &)) get_instance_name_list_) .def("get_server_list", (Tango::DbDatum (Tango::Database::*) ()) &Tango::Database::get_server_list) .def("get_server_list", (Tango::DbDatum (Tango::Database::*) (const std::string &)) get_server_list_) .def("get_host_server_list", (Tango::DbDatum (Tango::Database::*) (const std::string &)) get_host_server_list_) .def("get_device_class_list", (Tango::DbDatum (Tango::Database::*) (const std::string &)) get_device_class_list_) .def("get_server_release", &Tango::Database::get_server_release) // // property methods // .def("_get_property", (void (Tango::Database::*) (std::string, Tango::DbData &)) &Tango::Database::get_property) .def("_get_property_forced", &Tango::Database::get_property_forced) .def("_put_property", &Tango::Database::put_property) .def("_delete_property", &Tango::Database::delete_property) .def("get_property_history", (Tango::DbHistoryList (Tango::Database::*) (const std::string &, const std::string &)) get_property_history_) .def("get_object_list", (Tango::DbDatum (Tango::Database::*) (const std::string &)) get_object_list_) .def("get_object_property_list", (Tango::DbDatum (Tango::Database::*) (const std::string &, const std::string &)) get_object_property_list_) .def("_get_device_property", (void (Tango::Database::*) (std::string, Tango::DbData &)) &Tango::Database::get_device_property) .def("_put_device_property", &Tango::Database::put_device_property) .def("_delete_device_property", &Tango::Database::delete_device_property) .def("get_device_property_history", (Tango::DbHistoryList (Tango::Database::*) (const std::string &, const std::string &)) get_device_property_history_) .def("_get_device_property_list", (Tango::DbDatum (Tango::Database::*) (const std::string &, const std::string &)) get_device_property_list1_) .def("_get_device_property_list", &PyDatabase::get_device_property_list2) .def("_get_device_attribute_property", (void (Tango::Database::*) (std::string, Tango::DbData &)) &Tango::Database::get_device_attribute_property) .def("_put_device_attribute_property", &Tango::Database::put_device_attribute_property) .def("_delete_device_attribute_property", &Tango::Database::delete_device_attribute_property) .def("get_device_attribute_property_history", (Tango::DbHistoryList (Tango::Database::*) (const std::string &, const std::string &, const std::string &)) get_device_attribute_property_history_) .def("_get_class_property", (void (Tango::Database::*) (std::string, Tango::DbData &)) &Tango::Database::get_class_property) .def("_put_class_property", &Tango::Database::put_class_property) .def("_delete_class_property", &Tango::Database::delete_class_property) .def("get_class_property_history", (Tango::DbHistoryList (Tango::Database::*) (const std::string &, const std::string &)) get_class_property_history_) .def("get_class_list", (Tango::DbDatum (Tango::Database::*) (const std::string &)) get_class_list_) .def("get_class_property_list", (Tango::DbDatum (Tango::Database::*) (const std::string &)) get_class_property_list_) .def("_get_class_attribute_property", (void (Tango::Database::*) (std::string, Tango::DbData &)) &Tango::Database::get_class_attribute_property) .def("_put_class_attribute_property", &Tango::Database::put_class_attribute_property) .def("_delete_class_attribute_property", &Tango::Database::delete_class_attribute_property) .def("get_class_attribute_property_history", (Tango::DbHistoryList (Tango::Database::*) (const std::string &, const std::string &, const std::string &)) get_class_attribute_property_history_) .def("get_class_attribute_list", (Tango::DbDatum (Tango::Database::*) (const std::string &, const std::string &)) get_class_attribute_list_) // // Attribute methods // .def("get_attribute_alias", &PyDatabase::get_attribute_alias) .def("get_attribute_alias_list", (Tango::DbDatum (Tango::Database::*) (const std::string &)) get_attribute_alias_list_) .def("put_attribute_alias", (void (Tango::Database::*) (const std::string &, const std::string &)) put_attribute_alias_) .def("delete_attribute_alias", (void (Tango::Database::*) (const std::string &)) delete_attribute_alias_) // // event methods // .def("export_event", &PyDatabase::export_event) .def("unexport_event", (void (Tango::Database::*) (const std::string &)) &Tango::Database::unexport_event) // alias methods .def("get_device_from_alias", &PyDatabase::get_device_from_alias) .def("get_alias_from_device", &PyDatabase::get_alias_from_device) .def("get_attribute_from_alias", &PyDatabase::get_attribute_from_alias) .def("get_alias_from_attribute", &PyDatabase::get_alias_from_attribute) ; } pytango-9.2.2/ext/db.cpp000066400000000000000000000103101316324373100150740ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include "defs.h" #include "pytgutils.h" using namespace boost::python; void export_database(); namespace PyDbServerData { static inline boost::python::str get_name(Tango::DbServerData &self) { return boost::python::str(self.get_name()); } }; void export_db() { // Note: DbDatum in python is extended to support the python sequence API // in the file ../PyTango/db.py. This way the DbDatum behaves like a // sequence of strings. This allows the user to work with a DbDatum as // if it was working with the old list of strings class_("DbDatum", init<>()) .def(init()) .def(init()) .def_readwrite("name", &Tango::DbDatum::name) .def_readwrite("value_string", &Tango::DbDatum::value_string) .def("size", &Tango::DbDatum::size) .def("is_empty", &Tango::DbDatum::is_empty) ; class_("DbDevExportInfo") .def_readwrite("name", &Tango::DbDevExportInfo::name) .def_readwrite("ior", &Tango::DbDevExportInfo::ior) .def_readwrite("host", &Tango::DbDevExportInfo::host) .def_readwrite("version", &Tango::DbDevExportInfo::version) .def_readwrite("pid", &Tango::DbDevExportInfo::pid) ; class_("DbDevImportInfo") .def_readonly("name", &Tango::DbDevImportInfo::name) .def_readonly("exported", &Tango::DbDevImportInfo::exported) .def_readonly("ior", &Tango::DbDevImportInfo::ior) .def_readonly("version", &Tango::DbDevImportInfo::version) ; class_ >("DbDevFullInfo") .def_readonly("class_name", &Tango::DbDevFullInfo::class_name) .def_readonly("ds_full_name", &Tango::DbDevFullInfo::ds_full_name) .def_readonly("started_date", &Tango::DbDevFullInfo::started_date) .def_readonly("stopped_date", &Tango::DbDevFullInfo::stopped_date) .def_readonly("pid", &Tango::DbDevFullInfo::pid) ; class_("DbDevInfo") .def_readwrite("name", &Tango::DbDevInfo::name) .def_readwrite("_class", &Tango::DbDevInfo::_class) .def_readwrite("klass", &Tango::DbDevInfo::_class) .def_readwrite("server", &Tango::DbDevInfo::server) ; class_("DbHistory", init()) .def(init()) .def("get_name", &Tango::DbHistory::get_name) .def("get_attribute_name", &Tango::DbHistory::get_attribute_name) .def("get_date", &Tango::DbHistory::get_date) .def("get_value", &Tango::DbHistory::get_value) .def("is_deleted", &Tango::DbHistory::is_deleted) ; class_("DbServerInfo") .def_readwrite("name", &Tango::DbServerInfo::name) .def_readwrite("host", &Tango::DbServerInfo::host) .def_readwrite("mode", &Tango::DbServerInfo::mode) .def_readwrite("level", &Tango::DbServerInfo::level) ; class_("DbServerData", init()) .def("get_name", &PyDbServerData::get_name) .def("put_in_database", &Tango::DbServerData::put_in_database) .def("already_exist", &Tango::DbServerData::already_exist) .def("remove", (void (Tango::DbServerData::*) ()) &Tango::DbServerData::remove) .def("remove", (void (Tango::DbServerData::*) (const std::string &)) &Tango::DbServerData::remove) ; export_database(); } pytango-9.2.2/ext/defs.h000066400000000000000000000031301316324373100150770ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #pragma once #include #include typedef std::vector StdStringVector; typedef std::vector StdLongVector; typedef std::vector StdDoubleVector; /* HAS_UNIQUE_PTR definition comes from tango. It tells PyTango if Tango itself is using the new C++0x unique_ptr or not. In PyTango we try to use the same as TangoC++ whenever possible. */ #ifdef PYTANGO_HAS_UNIQUE_PTR #define unique_pointer std::unique_ptr #else #define unique_pointer std::auto_ptr #endif /* #ifdef HAS_UNIQUE_PTR typedef std::unique_ptr unique_pointer; #else typedef std::auto_ptr unique_pointer; #endif */ namespace PyTango { enum ExtractAs { ExtractAsNumpy, ExtractAsByteArray, ExtractAsBytes, ExtractAsTuple, ExtractAsList, ExtractAsString, ExtractAsPyTango3, ExtractAsNothing }; enum ImageFormat { RawImage, JpegImage }; enum GreenMode { GreenModeSynchronous, GreenModeFutures, GreenModeGevent, GreenModeAsyncio }; } pytango-9.2.2/ext/dev_command_info.cpp000066400000000000000000000024431316324373100200060ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include using namespace boost::python; void export_dev_command_info() { typedef Tango::CmdArgType Tango::_DevCommandInfo::* MemCmdArgType; class_("DevCommandInfo") .def_readonly("cmd_name", &Tango::DevCommandInfo::cmd_name) .def_readonly("cmd_tag", &Tango::DevCommandInfo::cmd_tag) .def_readonly("in_type", reinterpret_cast(&Tango::DevCommandInfo::in_type)) .def_readonly("out_type", reinterpret_cast(&Tango::DevCommandInfo::out_type)) .def_readonly("in_type_desc", &Tango::DevCommandInfo::in_type_desc) .def_readonly("out_type_desc", &Tango::DevCommandInfo::out_type_desc) ; } pytango-9.2.2/ext/dev_error.cpp000066400000000000000000000041531316324373100165060ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include "pyutils.h" #include namespace PyDevError { static void from_str_to_char(PyObject* in, CORBA::String_member& out) { if (PyUnicode_Check(in)) { PyObject *bytes_in = PyUnicode_AsLatin1String(in); out = CORBA::string_dup(PyBytes_AsString(bytes_in)); Py_DECREF(bytes_in); } else { out = CORBA::string_dup(PyBytes_AsString(in)); } } static inline PyObject* get_reason(Tango::DevError &self) { return from_char_to_str(self.reason); } static inline void set_reason(Tango::DevError &self, PyObject *str) { PyDevError::from_str_to_char(str, self.reason); } static inline PyObject* get_desc(Tango::DevError &self) { return from_char_to_str(self.desc); } static inline void set_desc(Tango::DevError &self, PyObject *str) { PyDevError::from_str_to_char(str, self.desc); } static inline PyObject* get_origin(Tango::DevError &self) { return from_char_to_str(self.origin); } static inline void set_origin(Tango::DevError &self, PyObject *str) { PyDevError::from_str_to_char(str, self.origin); } }; void export_dev_error() { bopy::class_("DevError") .enable_pickling() .add_property("reason", &PyDevError::get_reason, &PyDevError::set_reason) .def_readwrite("severity", &Tango::DevError::severity) .add_property("desc", &PyDevError::get_desc, &PyDevError::set_desc) .add_property("origin", &PyDevError::get_origin, &PyDevError::set_origin) ; } pytango-9.2.2/ext/device_attribute.cpp000066400000000000000000000746371316324373100200570ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include "device_attribute.h" #include "pytgutils.h" #include "tango_numpy.h" #include "fast_from_py.h" using namespace boost::python; extern const char *non_valid_image; extern const char *non_valid_spectrum; // Why am I storing 'type' as a python attribute with object::attr // instead of as a property calling DeviceAttribute::get_type here? // Because after 'extract'ing, any call to get_type() will fail. Same // for "value" and "w_value". And for has_failed and is_empty... static const char* value_attr_name = "value"; static const char* w_value_attr_name = "w_value"; static const char* type_attr_name = "type"; static const char* is_empty_attr_name = "is_empty"; static const char* has_failed_attr_name = "has_failed"; template struct python_tangocpp { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; static inline void to_cpp(const bopy::object & py_value, TangoScalarType & result) { result = bopy::extract(py_value); } static inline bopy::object to_python(const TangoScalarType & value) { return bopy::object(value); } }; template<> struct python_tangocpp { static const long tangoTypeConst = Tango::DEV_STRING; typedef TANGO_const2type(tangoTypeConst) TangoScalarType; static inline void to_cpp(const bopy::object & py_value, TangoScalarType & result) { result = CORBA::string_dup(bopy::extract(py_value)); } static inline bopy::object to_python(const TangoScalarType & value) { return bopy::object(std::string(value)); } }; #ifndef DISABLE_PYTANGO_NUMPY # include "device_attribute_numpy.hpp" #endif #define EXTRACT_VALUE(self, value_ptr) \ try { \ self >> value_ptr; \ } catch (Tango::DevFailed &e ) { \ if (strcmp(e.errors[0].reason.in(),"API_EmptyDeviceAttribute") != 0) \ throw; \ } namespace PyDeviceAttribute { template static inline void _update_value_as_bin(Tango::DeviceAttribute &self, bopy::object py_value, bool read_only) { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; typedef typename TANGO_const2arraytype(tangoTypeConst) TangoArrayType; // Extract the actual data from Tango::DeviceAttribute (self) TangoArrayType* value_ptr = 0; EXTRACT_VALUE(self, value_ptr) unique_pointer guard_value_ptr(value_ptr); py_value.attr(w_value_attr_name) = bopy::object(); if (value_ptr == 0) { if (read_only) { py_value.attr(value_attr_name) = bopy::object(bopy::handle<>(_PyObject_New(&PyBytes_Type))); } else { py_value.attr(value_attr_name) = bopy::object(bopy::handle<>(_PyObject_New(&PyByteArray_Type))); } return; } TangoScalarType* buffer = value_ptr->get_buffer(); const char *ch_ptr = reinterpret_cast(buffer); Py_ssize_t nb_bytes = (Py_ssize_t)value_ptr->length() * sizeof(TangoScalarType); PyObject* data_ptr = NULL; if (read_only) { data_ptr = PyBytes_FromStringAndSize(ch_ptr, nb_bytes); } else { data_ptr = PyByteArray_FromStringAndSize(ch_ptr, nb_bytes); } py_value.attr(value_attr_name) = bopy::object(bopy::handle<>(data_ptr)); } template<> inline void _update_value_as_bin(Tango::DeviceAttribute &self, bopy::object py_value, bool read_only) { Tango::DevVarEncodedArray* value_ptr; EXTRACT_VALUE(self, value_ptr) unique_pointer guard(value_ptr); Tango::DevEncoded* buffer = value_ptr->get_buffer(); Tango::DevEncoded& r_buffer = buffer[0]; bopy::str r_encoded_format(r_buffer.encoded_format); Tango::DevVarCharArray& r_encoded_data_array = r_buffer.encoded_data; char* r_ch_ptr = (char*) r_encoded_data_array.get_buffer(); Py_ssize_t r_size = r_encoded_data_array.length(); PyObject* r_encoded_data_ptr = NULL; if (read_only) { r_encoded_data_ptr = PyBytes_FromStringAndSize(r_ch_ptr, r_size); } else { r_encoded_data_ptr = PyByteArray_FromStringAndSize(r_ch_ptr, r_size); } bopy::object r_encoded_data = bopy::object(bopy::handle<>(r_encoded_data_ptr)); py_value.attr(value_attr_name) = bopy::make_tuple(r_encoded_format, r_encoded_data); if (self.get_written_dim_x() > 0) { bool is_write_type = self.get_written_dim_x() && (value_ptr->length() < 2); if (is_write_type) { bopy::object w_encoded_format(r_encoded_format); bopy::object w_encoded_data(r_encoded_data); py_value.attr(w_value_attr_name) = bopy::make_tuple(w_encoded_format, w_encoded_data); } else { Tango::DevEncoded& w_buffer = buffer[1]; bopy::str w_encoded_format(w_buffer.encoded_format); Tango::DevVarCharArray& w_encoded_data_array = w_buffer.encoded_data; char* w_ch_ptr = (char*) w_encoded_data_array.get_buffer(); PyObject* w_encoded_data_ptr = NULL; PyByteArray_FromStringAndSize(w_ch_ptr, w_encoded_data_array.length()); Py_ssize_t w_size = w_encoded_data_array.length(); if (read_only) { w_encoded_data_ptr = PyBytes_FromStringAndSize(w_ch_ptr, w_size); } else { w_encoded_data_ptr = PyByteArray_FromStringAndSize(w_ch_ptr, w_size); } bopy::object w_encoded_data = bopy::object(bopy::handle<>(w_encoded_data_ptr)); py_value.attr(value_attr_name) = bopy::make_tuple(w_encoded_format, w_encoded_data); } } else { py_value.attr(w_value_attr_name) = bopy::object(); } } template<> inline void _update_value_as_bin(Tango::DeviceAttribute &self, bopy::object py_value, bool read_only) { assert(false); } template static inline void _update_value_as_string(Tango::DeviceAttribute &self, bopy::object py_value) { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; typedef typename TANGO_const2arraytype(tangoTypeConst) TangoArrayType; // Extract the actual data from Tango::DeviceAttribute (self) TangoArrayType* value_ptr = 0; EXTRACT_VALUE(self, value_ptr) unique_pointer guard_value_ptr(value_ptr); if (value_ptr == 0) { py_value.attr(value_attr_name) = bopy::str(); py_value.attr(w_value_attr_name) = bopy::object(); return; } TangoScalarType* buffer = value_ptr->get_buffer(); const char* ch_ptr = reinterpret_cast(buffer); size_t nb_bytes = value_ptr->length() * sizeof(TangoScalarType); py_value.attr(value_attr_name) = bopy::str(ch_ptr, (size_t)nb_bytes); py_value.attr(w_value_attr_name) = bopy::object(); } template<> inline void _update_value_as_string(Tango::DeviceAttribute &self, bopy::object py_value) { Tango::DevVarEncodedArray* value_ptr; EXTRACT_VALUE(self, value_ptr) unique_pointer guard(value_ptr); Tango::DevEncoded* buffer = value_ptr->get_buffer(); Tango::DevEncoded& r_buffer = buffer[0]; bopy::str r_encoded_format(r_buffer.encoded_format); Tango::DevVarCharArray& r_encoded_data_array = r_buffer.encoded_data; char* r_ch_ptr = (char*)r_encoded_data_array.get_buffer(); bopy::str r_encoded_data(r_ch_ptr, r_encoded_data_array.length()); py_value.attr(value_attr_name) = bopy::make_tuple(r_encoded_format, r_encoded_data); if (self.get_written_dim_x() > 0) { bool is_write_type = self.get_written_dim_x() && (value_ptr->length() < 2); if (is_write_type) { bopy::object w_encoded_format(r_encoded_format); bopy::object w_encoded_data(r_encoded_data); py_value.attr(w_value_attr_name) = bopy::make_tuple(w_encoded_format, w_encoded_data); } else { Tango::DevEncoded& w_buffer = buffer[1]; bopy::str w_encoded_format(w_buffer.encoded_format); Tango::DevVarCharArray& w_encoded_data_array = w_buffer.encoded_data; char* w_ch_ptr = (char*)w_encoded_data_array.get_buffer(); bopy::str w_encoded_data(w_ch_ptr, w_encoded_data_array.length()); py_value.attr(w_value_attr_name) = bopy::make_tuple(w_encoded_format, w_encoded_data); } } else { py_value.attr(w_value_attr_name) = bopy::object(); } } template<> inline void _update_value_as_string(Tango::DeviceAttribute &self, bopy::object py_value) { assert(false); } template static inline void _update_scalar_values(Tango::DeviceAttribute &self, bopy::object py_value) { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; if (self.get_written_dim_x() > 0) { std::vector val; self.extract_read(val); // In the following lines, the cast is absolutely necessary because // vector may not be a vector at // compile time. For example, for vector, the compiler // may create a std::_Bit_reference type. py_value.attr(value_attr_name) = bopy::object((TangoScalarType)val[0]); self.extract_set(val); py_value.attr(w_value_attr_name) = bopy::object((TangoScalarType)val[0]); } else { TangoScalarType rvalue; EXTRACT_VALUE(self, rvalue) py_value.attr(value_attr_name) = bopy::object(rvalue); py_value.attr(w_value_attr_name) = bopy::object(); } } template<> inline void _update_scalar_values(Tango::DeviceAttribute &self, bopy::object py_value) { _update_value_as_string(self, py_value); } template<> inline void _update_scalar_values(Tango::DeviceAttribute &self, bopy::object py_value) { if (self.get_written_dim_x() > 0) { std::vector r_val, w_val; self.extract_read(r_val); py_value.attr(value_attr_name) = object(r_val[0]); self.extract_set(w_val); py_value.attr(w_value_attr_name) = object(w_val[0]); } else { std::string rvalue; EXTRACT_VALUE(self, rvalue) py_value.attr(value_attr_name) = object(rvalue); py_value.attr(w_value_attr_name) = object(); } } template<> inline void _update_scalar_values(Tango::DeviceAttribute &self, bopy::object py_value) { assert(false); } template static inline void _update_array_values_as_lists(Tango::DeviceAttribute &self, bool isImage, bopy::object py_value) { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; typedef typename TANGO_const2arraytype(tangoTypeConst) TangoArrayType; // Extract the actual data from Tango::DeviceAttribute (self) TangoArrayType* value_ptr = 0; EXTRACT_VALUE(self, value_ptr) unique_pointer guard_value_ptr(value_ptr); if (value_ptr == 0) { // Empty device attribute py_value.attr(value_attr_name) = bopy::list(); py_value.attr(w_value_attr_name) = object(); return; } TangoScalarType* buffer = value_ptr->get_buffer(); int total_length = value_ptr->length(); // Determine if the attribute is AttrWriteType.WRITE int read_size =0, write_size = 0; if (isImage) { read_size = self.get_dim_x() * self.get_dim_y(); write_size = self.get_written_dim_x() * self.get_written_dim_y(); } else { read_size = self.get_dim_x(); write_size = self.get_written_dim_x(); } bool is_write_type = (read_size + write_size) > total_length; // Convert to a list of lists long offset = 0; for(int it=1; it>=0; --it) { // 2 iterations: read part/write part if ((!it) && is_write_type) { py_value.attr(w_value_attr_name) = py_value.attr(value_attr_name); continue; } bopy::list result; if (isImage) { const int dim_x = it? self.get_dim_x() : self.get_written_dim_x(); const int dim_y = it? self.get_dim_y() : self.get_written_dim_y(); for (int y=0; y < dim_y; ++y) { bopy::list row; for (int x=0; x < dim_x; ++x) row.append(python_tangocpp::to_python(buffer[offset + x + y*dim_x])); result.append(row); } offset += dim_x*dim_y; } else { const int dim_x = it? self.get_dim_x() : self.get_written_dim_x(); for (int x=0; x < dim_x; ++x) result.append(python_tangocpp::to_python(buffer[offset + x])); offset += dim_x; } py_value.attr(it? value_attr_name : w_value_attr_name) = result; } } template<> inline void _update_array_values_as_lists(Tango::DeviceAttribute &self, bool isImage, bopy::object py_value) { assert(false); } template static void _update_array_values_as_tuples(Tango::DeviceAttribute &self, bool isImage, bopy::object py_value) { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; typedef typename TANGO_const2arraytype(tangoTypeConst) TangoArrayType; // Extract the actual data from Tango::DeviceAttribute (self) TangoArrayType* value_ptr = 0; EXTRACT_VALUE(self, value_ptr) unique_pointer guard_value_ptr(value_ptr); if (value_ptr == 0) { // Empty device attribute py_value.attr(value_attr_name) = bopy::tuple(); py_value.attr(w_value_attr_name) = object(); return; } TangoScalarType* buffer = value_ptr->get_buffer(); int total_length = value_ptr->length(); // Determine if the attribute is AttrWriteType.WRITE int read_size =0, write_size = 0; if (isImage) { read_size = self.get_dim_x() * self.get_dim_y(); write_size = self.get_written_dim_x() * self.get_written_dim_y(); } else { read_size = self.get_dim_x(); write_size = self.get_written_dim_x(); } bool is_write_type = (read_size + write_size) > total_length; // Convert to a tuple of tuples long offset = 0; for(int it=1; it>=0; --it) { // 2 iterations: read part/write part if ((!it) && is_write_type) { py_value.attr(w_value_attr_name) = py_value.attr(value_attr_name); continue; } object result_guard; if (isImage) { const int dim_x = it? self.get_dim_x() : self.get_written_dim_x(); const int dim_y = it? self.get_dim_y() : self.get_written_dim_y(); PyObject * result = PyTuple_New(dim_y); if (!result) bopy::throw_error_already_set(); result_guard = object(handle<>(result)); for (int y=0; y < dim_y; ++y) { PyObject * row = PyTuple_New(dim_x); if (!row) bopy::throw_error_already_set(); object row_guard = object(handle<>(row)); for (int x=0; x < dim_x; ++x) { object el = python_tangocpp::to_python(buffer[offset + x + y*dim_x]); PyTuple_SetItem(row, x, el.ptr()); incref(el.ptr()); } PyTuple_SetItem(result, y, row); incref(row); } offset += dim_x*dim_y; } else { const int dim_x = it? self.get_dim_x() : self.get_written_dim_x(); PyObject * result = PyTuple_New(dim_x); if (!result) bopy::throw_error_already_set(); result_guard = object(handle<>(result)); for (int x=0; x < dim_x; ++x) { object el = python_tangocpp::to_python(buffer[offset +x]); PyTuple_SetItem(result, x, el.ptr()); incref(el.ptr()); } offset += dim_x; } py_value.attr(it? value_attr_name : w_value_attr_name) = result_guard; } } template<> inline void _update_array_values_as_tuples(Tango::DeviceAttribute &self, bool isImage, bopy::object py_value) { assert(false); } void update_values(Tango::DeviceAttribute &self, bopy::object& py_value, PyTango::ExtractAs extract_as/*=ExtractAsNumpy*/) { // We do not want is_empty to launch an exception!! self.reset_exceptions(Tango::DeviceAttribute::isempty_flag); // self.get_type() already does self.is_empty() const int data_type = self.get_type(); const bool is_empty = data_type < 0; const bool has_failed = self.has_failed(); Tango::AttrDataFormat data_format = self.get_data_format(); py_value.attr(is_empty_attr_name) = object(is_empty); py_value.attr(has_failed_attr_name) = object(has_failed); py_value.attr(type_attr_name) = object(static_cast(data_type)); if (has_failed || is_empty) { // In none of this cases 'data_type' is valid so we cannot extract py_value.attr(value_attr_name) = object(); py_value.attr(w_value_attr_name) = object(); return; } bool is_image = false; switch (data_format) { case Tango::SCALAR: if (data_type == Tango::DEV_ENCODED) { switch (extract_as) { default: case PyTango::ExtractAsNumpy: case PyTango::ExtractAsTuple: case PyTango::ExtractAsList: TANGO_CALL_ON_ATTRIBUTE_DATA_TYPE_ID(data_type, _update_scalar_values, self, py_value); break; case PyTango::ExtractAsBytes: TANGO_CALL_ON_ATTRIBUTE_DATA_TYPE_ID(data_type, _update_value_as_bin, self, py_value, true); break; case PyTango::ExtractAsByteArray: TANGO_CALL_ON_ATTRIBUTE_DATA_TYPE_ID(data_type, _update_value_as_bin, self, py_value, false); break; case PyTango::ExtractAsString: TANGO_CALL_ON_ATTRIBUTE_DATA_TYPE_ID(data_type, _update_value_as_string, self, py_value); break; case PyTango::ExtractAsNothing: break; } } else { if (extract_as != PyTango::ExtractAsNothing) { TANGO_CALL_ON_ATTRIBUTE_DATA_TYPE_ID(data_type, _update_scalar_values, self, py_value); } } break; case Tango::IMAGE: is_image = true; case Tango::SPECTRUM: switch (extract_as) { default: case PyTango::ExtractAsNumpy: # ifndef DISABLE_PYTANGO_NUMPY TANGO_CALL_ON_ATTRIBUTE_DATA_TYPE_ID(data_type, _update_array_values, self, is_image, py_value); break; # endif case PyTango::ExtractAsTuple: TANGO_CALL_ON_ATTRIBUTE_DATA_TYPE_ID(data_type, _update_array_values_as_tuples, self, is_image, py_value); break; case PyTango::ExtractAsList: TANGO_CALL_ON_ATTRIBUTE_DATA_TYPE_ID(data_type, _update_array_values_as_lists, self, is_image, py_value); break; case PyTango::ExtractAsBytes: TANGO_CALL_ON_ATTRIBUTE_DATA_TYPE_ID(data_type, _update_value_as_bin, self, py_value, true); break; case PyTango::ExtractAsByteArray: TANGO_CALL_ON_ATTRIBUTE_DATA_TYPE_ID(data_type, _update_value_as_bin, self, py_value, false); break; case PyTango::ExtractAsString: TANGO_CALL_ON_ATTRIBUTE_DATA_TYPE_ID(data_type, _update_value_as_string, self, py_value); break; case PyTango::ExtractAsNothing: break; } break; case Tango::FMT_UNKNOWN: default: raise_(PyExc_ValueError, "Can't extract data because: self.get_data_format()=FMT_UNKNOWN"); assert(false); } } template static inline void _fill_list_attribute(Tango::DeviceAttribute & dev_attr, const bool isImage, const bopy::object & py_value) { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; typedef typename TANGO_const2arraytype(tangoTypeConst) TangoArrayType; CORBA::ULong dim_x, dim_y, nelems; // -- Check the dimensions if (isImage) { dim_y = static_cast(boost::python::len(py_value)); dim_x = static_cast(boost::python::len(py_value[0])); nelems = dim_x * dim_y; } else { dim_x = static_cast(boost::python::len(py_value)); dim_y = 0; nelems = dim_x; } // -- Allocate memory unique_pointer value; TangoScalarType* buffer = TangoArrayType::allocbuf(nelems); try { value.reset(new TangoArrayType(nelems, nelems, buffer, true)); } catch(...) { TangoArrayType::freebuf(buffer); throw; } // -- Copy the sequence to the newly created buffer if (isImage) { for(CORBA::ULong y = 0; y < dim_y; ++y) { object py_sub = py_value[y]; CORBA::ULong len_py_sub = static_cast(boost::python::len(py_sub)); if (len_py_sub != dim_x) raise_(PyExc_TypeError, non_valid_image); for(CORBA::ULong x = 0; x < dim_x; ++x) { python_tangocpp::to_cpp(py_sub[x], buffer[x + y*dim_x]); } } } else { for(CORBA::ULong x = 0; x < dim_x; ++x) { python_tangocpp::to_cpp(py_value[x], buffer[x]); } } // -- Insert it into the dev_attr dev_attr.insert(value.get(), dim_x, dim_y); // -- Final cleaning value.release(); // Do not delete value, it is handled by dev_attr now! } template<> inline void _fill_list_attribute(Tango::DeviceAttribute & dev_attr, const bool isImage, const bopy::object & py_value) { /// @todo really? This is really not gonna happen? // Unsupported assert(false); } static inline bopy::object undefined_attribute(Tango::DeviceAttribute* self) { return object(); // None } void reset_values(Tango::DeviceAttribute & self, int data_type, Tango::AttrDataFormat data_format, bopy::object py_value) { bool isImage = false; switch(data_format) { case Tango::SCALAR: TANGO_CALL_ON_ATTRIBUTE_DATA_TYPE_ID( data_type, _fill_scalar_attribute, self, py_value ); break; case Tango::IMAGE: isImage = true; case Tango::SPECTRUM: // Why on earth? Why do we define _fill_numpy_attribute instead // of just using _fill_list_attribute, if the latter accepts // anything with operators "[]" and "len" defined? // Well it seems that PyArray_GETITEM does something diferent // and I get a value transformed into a python basic type, // while py_value[y][x] gives me a numpy type (ej: numpy.bool_). // Then the conversions between numpy types to c++ are not // defined by boost while the conversions between python // standard types and C++ are. # ifdef DISABLE_PYTANGO_NUMPY { TANGO_CALL_ON_ATTRIBUTE_DATA_TYPE_ID( data_type, _fill_list_attribute, self, isImage, py_value ); } # else { if (PyArray_Check(py_value.ptr())) TANGO_CALL_ON_ATTRIBUTE_DATA_TYPE_ID( data_type, _fill_numpy_attribute, self, isImage, py_value ); else TANGO_CALL_ON_ATTRIBUTE_DATA_TYPE_ID( data_type, _fill_list_attribute, self, isImage, py_value ); } # endif break; default: raise_(PyExc_TypeError, "unsupported data_format."); } } void reset(Tango::DeviceAttribute & self, const Tango::AttributeInfo &attr_info, bopy::object py_value) { self.set_name(attr_info.name.c_str()); reset_values(self, attr_info.data_type, attr_info.data_format, py_value); } void reset(Tango::DeviceAttribute & self, const std::string &attr_name, Tango::DeviceProxy &dev_proxy, bopy::object py_value) { self.set_name(attr_name.c_str()); Tango::AttributeInfoEx attr_info; { AutoPythonAllowThreads guard; try { attr_info = dev_proxy.get_attribute_config(attr_name); } catch(...) {} } reset_values(self, attr_info.data_type, attr_info.data_format, py_value); } }; void export_device_attribute() { class_ DeviceAttribute("DeviceAttribute", init<>()) ; scope da_scope = DeviceAttribute; enum_("except_flags") .value("isempty_flag", Tango::DeviceAttribute::isempty_flag) .value("wrongtype_flag", Tango::DeviceAttribute::wrongtype_flag) .value("failed_flag", Tango::DeviceAttribute::failed_flag) .value("numFlags", Tango::DeviceAttribute::numFlags) ; DeviceAttribute .def(init()) .def_readwrite("name", &Tango::DeviceAttribute::name) .def_readwrite("quality", &Tango::DeviceAttribute::quality) .def_readwrite("time", &Tango::DeviceAttribute::time) .add_property("dim_x", &Tango::DeviceAttribute::get_dim_x) .add_property("dim_y", &Tango::DeviceAttribute::get_dim_y) .add_property("w_dim_x", &Tango::DeviceAttribute::get_written_dim_x) .add_property("w_dim_y", &Tango::DeviceAttribute::get_written_dim_y) .add_property("r_dimension", &Tango::DeviceAttribute::get_r_dimension) .add_property("w_dimension", &Tango::DeviceAttribute::get_w_dimension) .add_property("nb_read", &Tango::DeviceAttribute::get_nb_read) .add_property("nb_written", &Tango::DeviceAttribute::get_nb_written) .add_property("data_format", &Tango::DeviceAttribute::get_data_format) .def("get_date", &Tango::DeviceAttribute::get_date, return_internal_reference<>()) .def("get_err_stack", &Tango::DeviceAttribute::get_err_stack, return_value_policy()) .def("set_w_dim_x", &Tango::DeviceAttribute::set_w_dim_x) .def("set_w_dim_y", &Tango::DeviceAttribute::set_w_dim_y) ; } pytango-9.2.2/ext/device_attribute.h000066400000000000000000000212221316324373100175020ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #pragma once #include #include #include #include #include "pyutils.h" #include "defs.h" #include "pytgutils.h" #include "tango_numpy.h" #include "fast_from_py.h" namespace PyDeviceAttribute { /// @name Types /// @{ typedef unique_pointer > AutoDevAttrVector; /// @} template static void _update_array_values_as_tuples(Tango::DeviceAttribute &self, bool isImage, boost::python::object py_value); /// Set the value of a DeviceAttribute from python (useful for write*) void reset(Tango::DeviceAttribute& self, const Tango::AttributeInfo &attr_info, boost::python::object py_value); void reset(Tango::DeviceAttribute & self, const std::string &attr_name, Tango::DeviceProxy &dev_proxy, boost::python::object py_value); void update_values(Tango::DeviceAttribute &self, boost::python::object& py_value, PyTango::ExtractAs extract_as=PyTango::ExtractAsNumpy); template void update_data_format(Tango::DeviceProxy & dev_proxy, TDeviceAttribute* first, size_t nelems) { // Older devices do not send arg_format. So we try to discover it from // dim_x and dim_y. It is not perfect, sometimes we will get SCALAR for // SPECTRUM with size 1 for example. In that case, we must ask tango // for the actual value. TDeviceAttribute* p = first; std::vector attr_names; for (size_t n =0; n < nelems; ++n, ++p) { if ( (p->data_format != Tango::FMT_UNKNOWN) || (p->has_failed()) ) continue; if ( (p->get_dim_x() == 1) && (p->get_dim_y() == 0 ) ) { attr_names.push_back(p->name); } else if (p->get_dim_y() == 0) { p->data_format = Tango::SPECTRUM; } else { p->data_format = Tango::IMAGE; } } if (attr_names.size()) { unique_pointer attr_infos; { AutoPythonAllowThreads guard; p = first; try { attr_infos.reset(dev_proxy.get_attribute_config_ex(attr_names)); for (size_t n=0, m=0; n < nelems; ++n, ++p) { if ((p->data_format == Tango::FMT_UNKNOWN) && (!p->has_failed())) { p->data_format = (*attr_infos)[m++].data_format; } } } catch(Tango::DevFailed &) { // if we fail to get info about the missing attributes from // the server (because it as shutdown, for example) we assume // that they are SCALAR since dim_x is 1 for (size_t n=0; n < nelems; ++n, ++p) { if ((p->data_format == Tango::FMT_UNKNOWN) && (!p->has_failed())) { p->data_format = Tango::SCALAR; } } } } } return; } /// @param self The DeviceAttribute instance that the new python object /// will represent. It must be allocated with new. The new python object /// will handle it's destruction. There's never any reason to delete it /// manually after a call to this: Even if this function fails, the /// responsibility of destroying it will already be in py_value side or /// the object will already be destroyed. template boost::python::object convert_to_python(TDeviceAttribute* self, PyTango::ExtractAs extract_as) { using namespace boost::python; object py_value; try { py_value = object( handle<>( to_python_indirect< TDeviceAttribute*, detail::make_owning_holder>()(self))); } catch (...) { delete self; throw; } update_values(*self, py_value, extract_as); return py_value; } /// See the other convert_to_python version. Here we get a vector of /// DeviceAttributes. The responsibility to deallocate it is always from /// the caller (we will make a copy). This responsibility is unavoidable /// as we get a reference to auto_ptr -> the caller must use an auto_ptr, /// so the memory will finally be deleted. template boost::python::object convert_to_python(const unique_pointer >& dev_attr_vec, Tango::DeviceProxy & dev_proxy, PyTango::ExtractAs extract_as) { if (dev_attr_vec->empty()) { boost::python::list ls; return ls; } update_data_format(dev_proxy, &(*dev_attr_vec)[0], dev_attr_vec->size()); // Convert the c++ vector of DeviceAttribute into a pythonic list boost::python::list ls; typename std::vector::const_iterator i, e = dev_attr_vec->end(); for (i = dev_attr_vec->begin(); i != e; ++i) ls.append( convert_to_python(new TDeviceAttribute(*i), extract_as) ); return ls; } /// Convert a DeviceAttribute to python (useful for read*) /// @param dev_attr Should be a pointer allocated with new. You can forget /// about deallocating this object (python will do it) even /// if the call to convert_to_python fails. /// @param dev_proxy Device proxy where the value was got. DeviceAttribute /// sent by older tango versions do not have all the /// necessary information to extract the values, so we /// may need to ask. /// @param extract_as See ExtractAs enum. template boost::python::object convert_to_python(TDeviceAttribute* dev_attr, Tango::DeviceProxy & dev_proxy, PyTango::ExtractAs extract_as) { update_data_format(dev_proxy, dev_attr, 1); return convert_to_python(dev_attr, extract_as); } template static inline void _fill_scalar_attribute(Tango::DeviceAttribute & dev_attr, const boost::python::object & py_value) { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; TangoScalarType value; from_py::convert(py_value.ptr(), value); dev_attr << const_cast(value); } template<> inline void _fill_scalar_attribute(Tango::DeviceAttribute & dev_attr, const boost::python::object & py_value) { std::string value = boost::python::extract(py_value); dev_attr << value; } template<> inline void _fill_scalar_attribute(Tango::DeviceAttribute & dev_attr, const boost::python::object & py_value) { /// @todo test it!! /// @todo Now I am accepting 2 strings: encoded_format, encoded_data. This /// is far from a good solution, but its something... if (boost::python::len(py_value) != 2) { raise_(PyExc_TypeError, "Expecting a tuple of strings: encoded_format, encoded_data"); } boost::python::object encoded_format_str = py_value[0]; boost::python::object encoded_data_str = py_value[1]; /// @todo not sure... second parameter of insert is a reference, does it /// mean anything? Does he pretend to take ownership of the pointer or /// is he making another copy? what should I do? char* encoded_format = const_cast((boost::python::extract(encoded_format_str)())); Py_ssize_t encoded_data_len = boost::python::len(encoded_data_str); unsigned char* encoded_data = reinterpret_cast(const_cast((boost::python::extract(encoded_data_str)()))); // void insert(char *&,unsigned char *&,unsigned int); dev_attr.insert(encoded_format, encoded_data, static_cast(encoded_data_len)); //std::string value = boost::python::extract(py_value); //dev_attr << value; } } pytango-9.2.2/ext/device_attribute_config.cpp000066400000000000000000000051531316324373100213670ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include "pytgutils.h" void export_device_attribute_config() { bopy::class_("DeviceAttributeConfig") .def(bopy::init()) .enable_pickling() // .def_pickle(PyDeviceAttributeConfig::PickleSuite()) .def_readwrite("name", &Tango::DeviceAttributeConfig::name) .def_readwrite("writable", &Tango::DeviceAttributeConfig::writable) .def_readwrite("data_format", &Tango::DeviceAttributeConfig::data_format) .def_readwrite("data_type", &Tango::DeviceAttributeConfig::data_type) .def_readwrite("max_dim_x", &Tango::DeviceAttributeConfig::max_dim_x) .def_readwrite("max_dim_y", &Tango::DeviceAttributeConfig::max_dim_y) .def_readwrite("description", &Tango::DeviceAttributeConfig::description) //.def_readwrite("label", &Tango::DeviceAttributeConfig::label) .add_property("label", bopy::make_getter(&Tango::DeviceAttributeConfig::label, bopy::return_value_policy()), bopy::make_setter(&Tango::DeviceAttributeConfig::label, bopy::return_value_policy())) .def_readwrite("unit", &Tango::DeviceAttributeConfig::unit) .def_readwrite("standard_unit", &Tango::DeviceAttributeConfig::standard_unit) .def_readwrite("display_unit", &Tango::DeviceAttributeConfig::display_unit) .def_readwrite("format", &Tango::DeviceAttributeConfig::format) .def_readwrite("min_value", &Tango::DeviceAttributeConfig::min_value) .def_readwrite("max_value", &Tango::DeviceAttributeConfig::max_value) .def_readwrite("min_alarm", &Tango::DeviceAttributeConfig::min_alarm) .def_readwrite("max_alarm", &Tango::DeviceAttributeConfig::max_alarm) .def_readwrite("writable_attr_name", &Tango::DeviceAttributeConfig::writable_attr_name) .def_readwrite("extensions", &Tango::DeviceAttributeConfig::extensions) ; } pytango-9.2.2/ext/device_attribute_history.cpp000066400000000000000000000017321316324373100216220ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include using namespace boost::python; void export_device_attribute_history() { class_ > DeviceAttributeHistory("DeviceAttributeHistory", init<>()); DeviceAttributeHistory .def(init()) .def("has_failed", &Tango::DeviceAttributeHistory::has_failed) ; } pytango-9.2.2/ext/device_attribute_numpy.hpp000066400000000000000000000243031316324373100212750ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ // This header file is just some template functions moved apart from // device_attribute.cpp, and should only be included there. #pragma once namespace PyDeviceAttribute { /// This callback is run to delete Tango::DevVarXArray* objects. /// It is called by python. The array was associated with an attribute /// value object that is not being used anymore. /// @param ptr_ The array object. /// @param type_ The type of the array objects. We need it to convert ptr_ /// to the proper type before deleting it. ex: Tango::DEV_SHORT. #ifdef PYCAPSULE_OLD template static void _dev_var_x_array_deleter(void * ptr_) { TANGO_DO_ON_ATTRIBUTE_DATA_TYPE_ID(type, delete static_cast(ptr_); ); } #else template static void _dev_var_x_array_deleter(PyObject* obj) { void * ptr_ = PyCapsule_GetPointer(obj, NULL); TANGO_DO_ON_ATTRIBUTE_DATA_TYPE_ID(type, delete static_cast(ptr_); ); } template<> inline void _dev_var_x_array_deleter(PyObject* obj) { // Unsupported assert(false); } #endif template static inline void _update_array_values(Tango::DeviceAttribute &self, bool isImage, object py_value) { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; typedef typename TANGO_const2arraytype(tangoTypeConst) TangoArrayType; // Extract the actual data from Tango::DeviceAttribute (self) TangoArrayType* value_ptr = 0; try { self >> value_ptr; } catch (Tango::DevFailed &e ) { if (strcmp(e.errors[0].reason.in(),"API_EmptyDeviceAttribute") != 0) throw; } static const int typenum = TANGO_const2numpy(tangoTypeConst); if (value_ptr == 0) { // Empty device attribute PyObject* value = PyArray_SimpleNew(0, 0, typenum); if (!value) throw_error_already_set(); py_value.attr(value_attr_name) = object(handle<>(value)); py_value.attr(w_value_attr_name) = object(); return; } TangoScalarType* buffer = value_ptr->get_buffer(); npy_intp dims[2]; int nd = 1; size_t write_part_offset = 0; if (isImage) { nd = 2; dims[1] = self.get_dim_x(); dims[0] = self.get_dim_y(); write_part_offset = dims[1] * dims[0]; } else { nd = 1; dims[0] = self.get_dim_x(); write_part_offset = dims[0]; } // Create a new numpy.ndarray() object. It uses ch_ptr as the data, // so no costy memory copies when handling big images. char *ch_ptr = reinterpret_cast(buffer); PyObject* array = PyArray_SimpleNewFromData(nd, dims, typenum, ch_ptr); if (!array) { delete value_ptr; throw_error_already_set(); } // Create the numpy array for the write part. It will be stored in // another place. PyObject* warray = 0; if (self.get_written_dim_x() != 0) { if (isImage) { nd = 2; dims[1] = self.get_written_dim_x(); dims[0] = self.get_written_dim_y(); } else { nd = 1; dims[0] = self.get_written_dim_x(); } char* w_ch_ptr = reinterpret_cast( buffer + write_part_offset ); warray = PyArray_SimpleNewFromData(nd, dims, typenum, w_ch_ptr); if (!warray) { Py_XDECREF(array); delete value_ptr; throw_error_already_set(); } } // numpy.ndarray() does not own it's memory, so we need to manage it. // We can assign a 'base' object that will be informed (decref'd) when // the last copy of numpy.ndarray() disappears. // PyCObject is intended for that kind of things. It's seen as a // black box object from python. We assign him a function to be called // when it is deleted -> the function deletes the data. PyObject* guard = PyCapsule_New( static_cast(value_ptr), NULL, _dev_var_x_array_deleter); if (!guard ) { Py_XDECREF(array); Py_XDECREF(warray); delete value_ptr; throw_error_already_set(); } PyArray_BASE(array) = guard; py_value.attr(value_attr_name) = boost::python::object(boost::python::handle<>(array)); // The original C api object storing the data is the same for the // read data and the write data. so, both array and warray share // the same 'base' (guard). Thus, the data will not be deleted until // neither is accessed anymore. if (warray) { Py_INCREF(guard); PyArray_BASE(warray) = guard; py_value.attr(w_value_attr_name) = boost::python::object(boost::python::handle<>(warray)); } else py_value.attr(w_value_attr_name) = object(); // py_value.attr("__internal_data") = object(handle<>(borrowed(guard))); } template<> inline void _update_array_values(Tango::DeviceAttribute &self, bool isImage, object py_value) { _update_array_values_as_tuples(self, isImage, py_value); } template<> inline void _update_array_values(Tango::DeviceAttribute &self, bool isImage, object py_value) { /// @todo Sure, it is not necessary? assert(false); } // template // static inline void _update_array_values(PythonDeviceAttribute &self, bool isImage) // { // return _update_array_values_numpy(self, isImage); // } template static inline void _fill_numpy_attribute(Tango::DeviceAttribute & dev_attr, const bool isImage, const object & py_value) { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; typedef typename TANGO_const2arraytype(tangoTypeConst) TangoArrayType; // -- Check dimensions Py_ssize_t dim_x=0, dim_y=0, nelems=0; bool ok; switch (PyArray_NDIM(py_value.ptr())) { case 2: // -- Image ok = isImage; dim_x = PyArray_DIM(py_value.ptr(), 1); dim_y = PyArray_DIM(py_value.ptr(), 0); nelems = dim_x*dim_y; break; case 1: // -- Spectrum ok = !isImage; dim_x = PyArray_DIM(py_value.ptr(), 0); dim_y = 0; nelems = dim_x; break; default: // -- WTF?!!? ok = false; break; } if (!ok) raise_(PyExc_TypeError, isImage? non_valid_image : non_valid_spectrum); // -- Allocate memory for the new data object unique_pointer value; CORBA::ULong unelems = static_cast(nelems); TangoScalarType* buffer = TangoArrayType::allocbuf(unelems); try { value.reset(new TangoArrayType(unelems, unelems, buffer, true)); } catch(...) { TangoArrayType::freebuf(buffer); throw; } // -- Copy from numpy.array to TangoArrayType... PyArrayIterObject *iter; PyObject *array = py_value.ptr(); iter = (PyArrayIterObject *)PyArray_IterNew(array); if (!iter) throw_error_already_set(); handle<> _h((PyObject *)iter); object iter_guard(_h); if (isImage) { // Why not use PyArray_ITER_NEXT() instead of PyArray_ITER_GOTO()? // We could do a single while(iter->index < iter->size) instead // of the double "for". // I did this and it worked in the sense that it went across // the correct number of elements but... I did not know the // x and y position it corresponded! Yes, 'iter' has a coordinates // field, but it was always [0,0], never updated!! npy_intp coordinates[2]; npy_intp &x = coordinates[1]; npy_intp &y = coordinates[0]; npy_intp ndim_x = static_cast(dim_x); npy_intp ndim_y = static_cast(dim_y); for (y=0; y < ndim_y; ++y) { for (x=0; x < ndim_x; ++x) { PyArray_ITER_GOTO(iter, coordinates); PyObject* dataObj = PyArray_GETITEM(array, iter->dataptr); const object py_data = object( handle<>( dataObj ) ); buffer[y*ndim_x + x] = extract(py_data); } } } else { for (Py_ssize_t x=0; x < dim_x; ++x) { PyObject* dataObj = PyArray_GETITEM(array, iter->dataptr); const object py_data = object( handle<>( dataObj ) ); buffer[x] = extract(py_data); PyArray_ITER_NEXT(iter); } } // -- Insert into device attribute dev_attr.insert( value.get(), static_cast(dim_x), static_cast(dim_y) ); // -- Final cleaning... value.release(); // Do not delete value, it is handled by dev_attr now! } template<> inline void _fill_numpy_attribute(Tango::DeviceAttribute & dev_attr, const bool isImage, const object & py_value) { // Unsupported assert(false); } } pytango-9.2.2/ext/device_data.cpp000066400000000000000000000206461316324373100167540ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include "pytgutils.h" #include "fast_from_py.h" using namespace boost::python; #ifndef DISABLE_PYTANGO_NUMPY # include "to_py_numpy.hpp" #endif namespace PyDeviceData { Tango::CmdArgType get_type(Tango::DeviceData &self) { /// @todo This should change in Tango itself, get_type should not return int!! return static_cast(self.get_type()); } /// @name Scalar Insertion /// @{ template void insert_scalar(Tango::DeviceData &self, object py_value) { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; TangoScalarType value; from_py::convert(py_value.ptr(), value); self << value; } template <> void insert_scalar(Tango::DeviceData &self, object py_value) { PyObject* py_value_ptr = py_value.ptr(); if(PyUnicode_Check(py_value_ptr)) { PyObject* obj_bytes_ptr = PyUnicode_AsLatin1String(py_value_ptr); Tango::DevString value = PyBytes_AsString(obj_bytes_ptr); self << value; Py_DECREF(obj_bytes_ptr); } else { Tango::DevString value = PyBytes_AsString(py_value_ptr); self << value; } } template <> void insert_scalar(Tango::DeviceData &self, object py_value) { object p0 = py_value[0]; object p1 = py_value[1]; const char* encoded_format = extract (p0.ptr()); const char* encoded_data = extract (p1.ptr()); CORBA::ULong nb = static_cast(boost::python::len(p1)); Tango::DevVarCharArray arr(nb, nb, (CORBA::Octet*)encoded_data, false); Tango::DevEncoded val; val.encoded_format = CORBA::string_dup(encoded_format); val.encoded_data = arr; self << val; } template <> void insert_scalar(Tango::DeviceData &self, object py_value) { raise_(PyExc_TypeError, "Trying to insert a value in a DEV_VOID DeviceData!"); } template <> void insert_scalar(Tango::DeviceData &self, object py_value) { assert(false); } /// @} // ~Scalar Insertion // ----------------------------------------------------------------------- /// @name Array Insertion /// @{ template void insert_array(Tango::DeviceData &self, object py_value) { typedef typename TANGO_const2type(tangoArrayTypeConst) TangoArrayType; // self << val; -->> This ends up doing: // inline void operator << (DevVarUShortArray* datum) // { any.inout() <<= datum;} // So: // - We loose ownership of the pointer, should not remove it // - it's a CORBA object who gets ownership, not a buggy Tango // thing. So the last parameter to fast_convert2array is false TangoArrayType* val = fast_convert2array(py_value); self << val; } /// @} // ~Array Insertion // ----------------------------------------------------------------------- /// @name Scalar Extraction /// @{ template object extract_scalar(Tango::DeviceData &self) { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; /// @todo CONST_DEV_STRING ell el tracta com DEV_STRING TangoScalarType val; self >> val; return boost::python::object(val); } template <> object extract_scalar(Tango::DeviceData &self) { return boost::python::object(); } template <> object extract_scalar(Tango::DeviceData &self) { typedef std::string TangoScalarType; TangoScalarType val; self >> val; return boost::python::object(val); } template <> object extract_scalar(Tango::DeviceData &self) { assert(false); return bopy::object(); } /// @} // ~Scalar Extraction // ----------------------------------------------------------------------- /// @name Array extraction /// @{ template object extract_array(Tango::DeviceData &self, object &py_self, PyTango::ExtractAs extract_as) { typedef typename TANGO_const2type(tangoArrayTypeConst) TangoArrayType; // const is the pointed, not the pointer. So cannot modify the data. // And that's because the data is still inside "self" after extracting. // This also means that we are not supposed to "delete" tmp_ptr. const TangoArrayType* tmp_ptr; self >> tmp_ptr; switch (extract_as) { default: case PyTango::ExtractAsNumpy: # ifndef DISABLE_PYTANGO_NUMPY return to_py_numpy(tmp_ptr, py_self); # endif case PyTango::ExtractAsList: case PyTango::ExtractAsPyTango3: return to_py_list(tmp_ptr); case PyTango::ExtractAsTuple: return to_py_tuple(tmp_ptr); case PyTango::ExtractAsString: /// @todo case PyTango::ExtractAsNothing: return object(); } } template <> object extract_array(Tango::DeviceData &self, object &py_self, PyTango::ExtractAs extract_as) { assert(False); return object(); } /// @} // ~Array Extraction // ----------------------------------------------------------------------- object extract(object py_self, PyTango::ExtractAs extract_as) { Tango::DeviceData &self = boost::python::extract(py_self); TANGO_DO_ON_DEVICE_DATA_TYPE_ID(self.get_type(), return extract_scalar(self); , return extract_array(self, py_self, extract_as); ); return object(); } void insert(Tango::DeviceData &self, long data_type, object py_value) { TANGO_DO_ON_DEVICE_DATA_TYPE_ID(data_type, insert_scalar(self, py_value); , insert_array(self, py_value); ); } } void export_device_data() { class_ DeviceData("DeviceData", init<>()) ; scope scope_dd = DeviceData; /// @todo get rid of except_flags everywhere... or really use and export them everywhere! enum_("except_flags") .value("isempty_flag", Tango::DeviceData::isempty_flag) .value("wrongtype_flag", Tango::DeviceData::wrongtype_flag) .value("numFlags", Tango::DeviceData::numFlags) ; DeviceData .def(init()) .def("extract", &PyDeviceData::extract, ( arg_("self"), arg_("extract_as")=PyTango::ExtractAsNumpy )) .def("insert", &PyDeviceData::insert, (arg_("self"), arg_("data_type"), arg_("value"))) /// @todo do not throw exceptions!! .def("is_empty", &Tango::DeviceData::is_empty) // TODO // void exceptions(bitset fl) {exceptions_flags = fl;} // bitset exceptions() {return exceptions_flags;} // void reset_exceptions(except_flags fl) {exceptions_flags.reset((size_t)fl);} // void set_exceptions(except_flags fl) {exceptions_flags.set((size_t)fl);} .def("get_type", &PyDeviceData::get_type) ; } pytango-9.2.2/ext/device_data_history.cpp000066400000000000000000000022331316324373100205250ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include using namespace boost::python; void export_device_data_history() { class_ > DeviceDataHistory("DeviceDataHistory", init<>()); DeviceDataHistory .def(init()) .def("has_failed", &Tango::DeviceDataHistory::has_failed) .def("get_date", &Tango::DeviceDataHistory::get_date, return_internal_reference<>()) .def("get_err_stack", &Tango::DeviceDataHistory::get_err_stack, return_value_policy()) ; } pytango-9.2.2/ext/device_info.cpp000066400000000000000000000021321316324373100167640ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include using namespace boost::python; void export_device_info() { class_("DeviceInfo") .def_readonly("dev_class", &Tango::DeviceInfo::dev_class) .def_readonly("server_id", &Tango::DeviceInfo::server_id) .def_readonly("server_host", &Tango::DeviceInfo::server_host) .def_readonly("server_version", &Tango::DeviceInfo::server_version) .def_readonly("doc_url", &Tango::DeviceInfo::doc_url) .def_readonly("dev_type", &Tango::DeviceInfo::dev_type) ; } pytango-9.2.2/ext/device_pipe.cpp000066400000000000000000000341351316324373100167760ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include "device_pipe.h" #include "tgutils.h" #include "pytgutils.h" #include "tango_numpy.h" #include "fast_from_py.h" #ifndef DISABLE_PYTANGO_NUMPY # include "to_py_numpy.hpp" #endif namespace PyTango { namespace DevicePipe { template bopy::object __update_scalar_values(Tango::DevicePipe& self, size_t elt_idx) { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; TangoScalarType val; bopy::str name(self.get_data_elt_name(elt_idx)); self >> val; bopy::object data(val); return bopy::make_tuple(name, data); } template<> bopy::object __update_scalar_values(Tango::DevicePipe& self, size_t elt_idx) { bopy::str name(self.get_data_elt_name(elt_idx)); return bopy::make_tuple(name, bopy::object()); } template<> bopy::object __update_scalar_values(Tango::DevicePipe& self, size_t elt_idx) { typedef std::string TangoScalarType; TangoScalarType val; bopy::str name(self.get_data_elt_name(elt_idx)); self >> val; bopy::object data(val); return bopy::make_tuple(name, data); } template<> bopy::object __update_scalar_values(Tango::DevicePipe& self, size_t elt_idx) { Tango::DevicePipeBlob val; bopy::str name(self.get_data_elt_name(elt_idx)); self >> val; bopy::object data = extract(val, PyTango::ExtractAsNumpy); return bopy::make_tuple(name, data); } template bopy::object __update_array_values(Tango::DevicePipe &self, bopy::object &py_self, size_t elt_idx, PyTango::ExtractAs extract_as) { typedef typename TANGO_const2type(tangoArrayTypeConst) TangoArrayType; TangoArrayType tmp_arr; self >> (&tmp_arr); bopy::object data; switch (extract_as) { default: case PyTango::ExtractAsNumpy: # ifndef DISABLE_PYTANGO_NUMPY data = to_py_numpy(&tmp_arr, py_self); tmp_arr.get_buffer(1); break; # endif case PyTango::ExtractAsList: case PyTango::ExtractAsPyTango3: data = to_py_list(&tmp_arr); break; case PyTango::ExtractAsTuple: data = to_py_tuple(&tmp_arr); break; case PyTango::ExtractAsString: /// @todo case PyTango::ExtractAsNothing: data = bopy::object(); break; } bopy::str name(self.get_data_elt_name(elt_idx)); return bopy::make_tuple(name, data); } template <> bopy::object __update_array_values(Tango::DevicePipe &self, bopy::object &py_self, size_t elt_idx, PyTango::ExtractAs extract_as) { assert(false); return bopy::object(); } template <> bopy::object __update_array_values(Tango::DevicePipe &self, bopy::object &py_self, size_t elt_idx, PyTango::ExtractAs extract_as) { assert(false); return bopy::object(); } bopy::object update_value(Tango::DevicePipe &self, bopy::object& py_self, size_t elt_idx, PyTango::ExtractAs extract_as) { const int elt_type = self.get_data_elt_type(elt_idx); TANGO_DO_ON_DEVICE_DATA_TYPE_ID(elt_type, return __update_scalar_values(self, elt_idx); , return __update_array_values(self, py_self, elt_idx, extract_as); ); return bopy::object(); } void update_values(Tango::DevicePipe& self, bopy::object& py_self, PyTango::ExtractAs extract_as /*=PyTango::ExtractAsNumpy*/) { // We do not want is_empty to launch an exception!! //self.reset_exceptions(Tango::DevicePipe::isempty_flag); //py_self.attr("name") = self.get_name(); bopy::list data; py_self.attr("data") = data; size_t elt_nb = self.get_data_elt_nb(); for(size_t elt_idx = 0; elt_idx < elt_nb; ++elt_idx) { data.append(update_value(self, py_self, elt_idx, extract_as)); } } /////////////////////////////////////////////////////////////////////////////////////////// template bopy::object __extract_scalar(T& obj, size_t elt_idx) { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; TangoScalarType val; obj >> val; return bopy::object(val); } template<> bopy::object __extract_scalar(Tango::DevicePipe& obj, size_t elt_idx) { return bopy::object(); } template<> bopy::object __extract_scalar(Tango::DevicePipe& obj, size_t elt_idx) { std::string val; obj >> val; return bopy::object(val); } template<> bopy::object __extract_scalar(Tango::DevicePipe& obj, size_t elt_idx) { Tango::DevicePipeBlob val; obj >> val; // TODO: propagate extract_as return extract(val, PyTango::ExtractAsNumpy); } template<> bopy::object __extract_scalar(Tango::DevicePipeBlob& obj, size_t elt_idx) { return bopy::object(); } template<> bopy::object __extract_scalar(Tango::DevicePipeBlob& obj, size_t elt_idx) { std::string val; obj >> val; return bopy::object(val); } template<> bopy::object __extract_scalar(Tango::DevicePipeBlob& obj, size_t elt_idx) { Tango::DevicePipeBlob val; obj >> val; // TODO: propagate extract_as return extract(val, PyTango::ExtractAsNumpy); } template bopy::object extract_scalar(Tango::DevicePipe& self, size_t elt_idx) { return __extract_scalar(self, elt_idx); } template bopy::object extract_scalar(Tango::DevicePipeBlob& self, size_t elt_idx) { return __extract_scalar(self, elt_idx); } template bopy::object __extract_array(T& obj, size_t elt_idx, PyTango::ExtractAs extract_as) { typedef typename TANGO_const2type(tangoArrayTypeConst) TangoArrayType; TangoArrayType tmp_arr; obj >> (&tmp_arr); bopy::object data; switch (extract_as) { default: case PyTango::ExtractAsNumpy: # ifndef DISABLE_PYTANGO_NUMPY data = to_py_numpy(&tmp_arr, 1); break; # endif case PyTango::ExtractAsList: case PyTango::ExtractAsPyTango3: data = to_py_list(&tmp_arr); break; case PyTango::ExtractAsTuple: data = to_py_tuple(&tmp_arr); break; case PyTango::ExtractAsString: /// @todo case PyTango::ExtractAsNothing: data = bopy::object(); break; } return data; } template <> bopy::object __extract_array (Tango::DevicePipe& pipe, size_t elt_idx, PyTango::ExtractAs extract_as) { assert(false); return bopy::object(); } template <> bopy::object __extract_array (Tango::DevicePipe& pipe, size_t elt_idx, PyTango::ExtractAs extract_as) { assert(false); return bopy::object(); } template <> bopy::object __extract_array (Tango::DevicePipeBlob& blob, size_t elt_idx, PyTango::ExtractAs extract_as) { assert(false); return bopy::object(); } template <> bopy::object __extract_array (Tango::DevicePipeBlob& blob, size_t elt_idx, PyTango::ExtractAs extract_as) { assert(false); return bopy::object(); } template bopy::object extract_array(Tango::DevicePipe& self, size_t elt_idx, PyTango::ExtractAs extract_as) { return __extract_array(self, elt_idx, extract_as); } template bopy::object extract_array(Tango::DevicePipeBlob& self, size_t elt_idx, PyTango::ExtractAs extract_as) { return __extract_array(self, elt_idx, extract_as); } template bopy::object __extract_item(T& obj, size_t elt_idx, PyTango::ExtractAs extract_as) { const int elt_type = obj.get_data_elt_type(elt_idx); TANGO_DO_ON_DEVICE_DATA_TYPE_ID(elt_type, return extract_scalar(obj, elt_idx); , return extract_array(obj, elt_idx, extract_as); ); return bopy::object(); } template bopy::object __extract(T& obj, PyTango::ExtractAs extract_as) { bopy::list data; size_t elt_nb = obj.get_data_elt_nb(); for(size_t elt_idx = 0; elt_idx < elt_nb; ++elt_idx) { bopy::dict elem; elem["name"] = obj.get_data_elt_name(elt_idx); elem["dtype"] = static_cast(obj.get_data_elt_type(elt_idx)); elem["value"] = __extract_item(obj, elt_idx, extract_as); data.append(elem); } return data; } bopy::object extract(Tango::DevicePipeBlob& blob, PyTango::ExtractAs extract_as) { bopy::object name = bopy::str(blob.get_name()); bopy::object value = __extract(blob, extract_as); return bopy::make_tuple(name, value); } bopy::object extract(Tango::DevicePipe& device_pipe, PyTango::ExtractAs extract_as=PyTango::ExtractAsNumpy) { bopy::object name = bopy::str(device_pipe.get_root_blob_name()); bopy::object value = __extract(device_pipe, extract_as); return bopy::make_tuple(name, value); } } } void export_device_pipe() { bopy::class_ DevicePipe("DevicePipe"); bopy::scope dp_scope = DevicePipe; DevicePipe .def(bopy::init<>()) .def(bopy::init()) .def(bopy::init()) .def(bopy::init()) .add_property("name", bopy::make_function(&Tango::DevicePipe::get_name, bopy::return_value_policy ()), &Tango::DevicePipe::set_name) .add_property("root_blob_name", bopy::make_function(&Tango::DevicePipe::get_root_blob_name, bopy::return_value_policy ()), &Tango::DevicePipe::set_root_blob_name) .add_property("data_elt_nb", &Tango::DevicePipe::get_data_elt_nb, &Tango::DevicePipe::set_data_elt_nb) .add_property("data_elt_names", &Tango::DevicePipe::get_data_elt_names, &Tango::DevicePipe::set_data_elt_names) .def("get_data_elt_name", &Tango::DevicePipe::get_data_elt_name) .def("get_data_elt_type", &Tango::DevicePipe::get_data_elt_type) .def("extract", (bopy::object (*) (Tango::DevicePipe &, PyTango::ExtractAs)) PyTango::DevicePipe::extract) .def("extract", (bopy::object (*) (Tango::DevicePipeBlob &, PyTango::ExtractAs)) PyTango::DevicePipe::extract) ; } pytango-9.2.2/ext/device_pipe.h000066400000000000000000000033111316324373100164330ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #pragma once #include #include #include "defs.h" #include "pyutils.h" namespace PyTango { namespace DevicePipe { bopy::object extract(Tango::DevicePipeBlob& blob, PyTango::ExtractAs extract_as=PyTango::ExtractAsNumpy); void update_values(Tango::DevicePipe& self, bopy::object& py_value, PyTango::ExtractAs extract_as=PyTango::ExtractAsNumpy); template bopy::object convert_to_python(TDevicePipe* self, PyTango::ExtractAs extract_as) { bopy::object py_value; try { py_value = bopy::object( bopy::handle<>( bopy::to_python_indirect< TDevicePipe*, bopy::detail::make_owning_holder>() (self))); } catch (...) { delete self; throw; } update_values(*self, py_value, extract_as); return py_value; } } } pytango-9.2.2/ext/device_proxy.cpp000066400000000000000000001241121316324373100172150ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include "device_attribute.h" #include "device_pipe.h" #include "callback.h" #include "defs.h" #include "pytgutils.h" #include #include "pipe.h" #include "fast_from_py.h" extern const char *param_must_be_seq; extern const char *unreachable_code; extern const char *non_string_seq; BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(lock_overloads, Tango::DeviceProxy::lock, 0, 1); BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(unlock_overloads, Tango::DeviceProxy::unlock, 0, 1); namespace PyDeviceProxy { struct PickleSuite : bopy::pickle_suite { static bopy::tuple getinitargs(Tango::DeviceProxy& self) { std::string ret = self.get_db_host() + ":" + self.get_db_port() + "/" + self.dev_name(); return bopy::make_tuple(ret); } }; static inline Tango::DevState state(Tango::DeviceProxy& self) { AutoPythonAllowThreads guard; return self.state(); } static inline std::string status(Tango::DeviceProxy& self) { AutoPythonAllowThreads guard; return self.status(); } static inline int ping(Tango::DeviceProxy& self) { AutoPythonAllowThreads guard; return self.ping(); } static inline void pylist_to_devattrs(Tango::DeviceProxy& self, bopy::object &py_list, std::vector &dev_attrs) { std::vector attr_names; std::vector py_values; Py_ssize_t size = len(py_list); // Fill attr_names and py_values for (Py_ssize_t n = 0; n < size; ++n) { bopy::object tup = py_list[n]; std::string attr_name = bopy::extract(tup[0]); attr_names.push_back(attr_name); py_values.push_back(tup[1]); } // Get attr_info for all the attr_names unique_pointer attr_infos; { AutoPythonAllowThreads guard; attr_infos.reset(self.get_attribute_config_ex(attr_names)); } // Now prepare dev_attrs with attr_infos and py_values dev_attrs.resize(size); for (long n = 0; n < size; ++n) { PyDeviceAttribute::reset(dev_attrs[n], (*attr_infos)[n], py_values[n]); } } Tango::DevicePipe read_pipe(Tango::DeviceProxy& self, const std::string & pipe_name) { AutoPythonAllowThreads guard; return self.read_pipe(pipe_name); } //------------------------- copy from pipe.cpp ------------------------------------------------------- static void throw_wrong_python_data_type(const std::string &name, const char *method) { TangoSys_OMemStream o; o << "Wrong Python type for pipe " << name << ends; Tango::Except::throw_exception("PyDs_WrongPythonDataTypeForPipe", o.str(), method); } template void append_scalar_encoded(T& obj, const std::string &name, bopy::object& py_value) { bopy::object p0 = py_value[0]; bopy::object p1 = py_value[1]; const char* encoded_format = bopy::extract (p0.ptr()); PyObject* data_ptr = p1.ptr(); Py_buffer view; if (PyObject_GetBuffer(data_ptr, &view, PyBUF_FULL_RO) < 0) { throw_wrong_python_data_type(obj.get_name(), "append_scalar_encoded"); } CORBA::ULong nb = static_cast(view.len); Tango::DevVarCharArray arr(nb, nb, (CORBA::Octet*)view.buf, false); Tango::DevEncoded value; value.encoded_format = CORBA::string_dup(encoded_format); value.encoded_data = arr; obj << value; PyBuffer_Release(&view); } template void __append_scalar(T &obj, const std::string &name, bopy::object& py_value) { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; TangoScalarType tg_value; from_py::convert(py_value, tg_value); // if you use this line of code you will get an unresolved // reference for T = don't know why! // obj << tg_value; Tango::DataElement data_elt(name, tg_value); obj << data_elt; } template void append_scalar(Tango::DevicePipe& pipe, const std::string &name, bopy::object& py_value) { __append_scalar(pipe, name, py_value); } template<> void append_scalar(Tango::DevicePipe& pipe, const std::string &name, bopy::object& py_value) { throw_wrong_python_data_type(pipe.get_name(), "append_scalar"); } template<> void append_scalar(Tango::DevicePipe& pipe, const std::string &name, bopy::object& py_value) { throw_wrong_python_data_type(pipe.get_name(), "append_scalar"); } template<> void append_scalar(Tango::DevicePipe& pipe, const std::string &name, bopy::object& py_value) { append_scalar_encoded(pipe, name, py_value); } template void append_scalar(Tango::DevicePipeBlob& blob, const std::string &name, bopy::object& py_value) { __append_scalar(blob, name, py_value); } template<> void append_scalar(Tango::DevicePipeBlob& blob, const std::string &name, bopy::object& py_value) { throw_wrong_python_data_type(blob.get_name(), "append_scalar"); } template<> void append_scalar(Tango::DevicePipeBlob& blob, const std::string &name, bopy::object& py_value) { throw_wrong_python_data_type(blob.get_name(), "append_scalar"); } template<> void append_scalar(Tango::DevicePipeBlob& blob, const std::string &name, bopy::object& py_value) { append_scalar_encoded(blob, name, py_value); } // ------------- // Array version // ------------- template void __append_array(T& obj, const std::string &name, bopy::object& py_value) { typedef typename TANGO_const2type(tangoArrayTypeConst) TangoArrayType; TangoArrayType* value = fast_convert2array(py_value); obj << value; } template void append_array(Tango::DevicePipe& pipe, const std::string &name, bopy::object& py_value) { __append_array(pipe, name, py_value); } template<> void append_array(Tango::DevicePipe& pipe, const std::string &name, bopy::object& py_value) { throw_wrong_python_data_type(pipe.get_name(), "append_array"); } template<> void append_array(Tango::DevicePipe& pipe, const std::string &name, bopy::object& py_value) { throw_wrong_python_data_type(pipe.get_name(), "append_array"); } template<> void append_array(Tango::DevicePipe& pipe, const std::string &name, bopy::object& py_value) { throw_wrong_python_data_type(pipe.get_name(), "append_array"); } template<> void append_array(Tango::DevicePipe& pipe, const std::string &name, bopy::object& py_value) { throw_wrong_python_data_type(pipe.get_name(), "append_array"); } template void append_array(Tango::DevicePipeBlob& blob, const std::string &name, bopy::object& py_value) { __append_array(blob, name, py_value); } template<> void append_array(Tango::DevicePipeBlob& blob, const std::string &name, bopy::object& py_value) { throw_wrong_python_data_type(blob.get_name(), "append_array"); } template<> void append_array(Tango::DevicePipeBlob& blob, const std::string &name, bopy::object& py_value) { throw_wrong_python_data_type(blob.get_name(), "append_array"); } template<> void append_array(Tango::DevicePipeBlob& blob, const std::string &name, bopy::object& py_value) { throw_wrong_python_data_type(blob.get_name(), "append_array"); } template<> void append_array(Tango::DevicePipeBlob& blob, const std::string &name, bopy::object& py_value) { throw_wrong_python_data_type(blob.get_name(), "append_array"); } template void __append(T& obj, const std::string& name, bopy::object& py_value, const Tango::CmdArgType dtype) { TANGO_DO_ON_DEVICE_DATA_TYPE_ID(dtype, append_scalar(obj, name, py_value); , append_array(obj, name, py_value); ); } template void __set_value(T& obj, bopy::object& py_value) { // need to fill item names first because in case it is a sub-blob, // the Tango C++ API doesnt't provide a way to do it bopy::ssize_t n = bopy::len(py_value); std::vector < std::string > elem_names; for (size_t i = 0; i < n; ++i) { string s = bopy::extract < std::string > (py_value[i]["name"]); elem_names.push_back(bopy::extract(py_value[i]["name"])); } obj.set_data_elt_names(elem_names); for (size_t i = 0; i < n; ++i) { bopy::object item = py_value[i]; std::string item_name = bopy::extract(item["name"]); bopy::object py_item_data = item["value"]; Tango::CmdArgType item_dtype = bopy::extract(item["dtype"]); if (item_dtype == Tango::DEV_PIPE_BLOB) // a sub-blob { std::string blob_name = bopy::extract < std::string > (py_item_data[0]); bopy::object py_blob_data = py_item_data[1]; Tango::DevicePipeBlob blob(blob_name); __set_value(blob, py_blob_data); obj << blob; } else { __append(obj, item_name, py_item_data, item_dtype); } } } void set_value(Tango::DevicePipe& pipe, bopy::object& py_value) { __set_value(pipe, py_value); } //---------------------- end of copy from pipe.cpp ------------------------------------------------- static void write_pipe(Tango::DeviceProxy& self, const std::string & pipe_name, const std::string & root_blob_name, bopy::object py_value) { Tango::DevicePipe device_pipe(pipe_name, root_blob_name); set_value(device_pipe, py_value); // interface to c++ method AutoPythonAllowThreads guard; self.write_pipe(device_pipe); } static bopy::object read_attribute(Tango::DeviceProxy& self, const std::string & attr_name, PyTango::ExtractAs extract_as) { // Even if there's an exception in convert_to_python, the // DeviceAttribute will be deleted there, so we don't need to worry. Tango::DeviceAttribute* dev_attr = 0; { AutoPythonAllowThreads guard; dev_attr = \ new Tango::DeviceAttribute(self.read_attribute(attr_name.c_str())); } return PyDeviceAttribute::convert_to_python(dev_attr, self, extract_as); } static inline bopy::object read_attributes(Tango::DeviceProxy& self, bopy::object py_attr_names, PyTango::ExtractAs extract_as) { CSequenceFromPython attr_names(py_attr_names); PyDeviceAttribute::AutoDevAttrVector dev_attr_vec; { AutoPythonAllowThreads guard; dev_attr_vec.reset(self.read_attributes(*attr_names)); } return PyDeviceAttribute::convert_to_python(dev_attr_vec, self, extract_as); } static inline void write_attribute(Tango::DeviceProxy& self, const Tango::AttributeInfo & attr_info, bopy::object py_value) { Tango::DeviceAttribute da; PyDeviceAttribute::reset(da, attr_info, py_value); AutoPythonAllowThreads guard; self.write_attribute(da); } static inline void write_attribute(Tango::DeviceProxy& self, const string & attr_name, bopy::object py_value) { Tango::DeviceAttribute dev_attr; PyDeviceAttribute::reset(dev_attr, attr_name, self, py_value); { AutoPythonAllowThreads guard; self.write_attribute(dev_attr); } } static inline void write_attributes(Tango::DeviceProxy& self, bopy::object py_list) { std::vector dev_attrs; pylist_to_devattrs(self, py_list, dev_attrs); AutoPythonAllowThreads guard; self.write_attributes(dev_attrs); } static inline bopy::object write_read_attribute(Tango::DeviceProxy& self, const string & attr_name, bopy::object py_value, PyTango::ExtractAs extract_as) { Tango::DeviceAttribute w_dev_attr; unique_pointer r_dev_attr; // Prepare dev_attr structure PyDeviceAttribute::reset(w_dev_attr, attr_name, self, py_value); // Do the actual write_read_attribute thing... { AutoPythonAllowThreads guard; Tango::DeviceAttribute da = self.write_read_attribute(w_dev_attr); r_dev_attr.reset(new Tango::DeviceAttribute(da)); } // Convert the result back to python return PyDeviceAttribute::convert_to_python(r_dev_attr.release(), self, extract_as); } static bopy::object write_read_attributes(Tango::DeviceProxy& self, bopy::object py_name_val_list, bopy::object py_attr_names, PyTango::ExtractAs extract_as) { // Convert write std::vector dev_attrs; pylist_to_devattrs(self, py_name_val_list, dev_attrs); // Convert read CSequenceFromPython attr_names(py_attr_names); PyDeviceAttribute::AutoDevAttrVector dev_attr_vec; // Do the actual write_read_attributes thing... { AutoPythonAllowThreads guard; dev_attr_vec.reset(self.write_read_attributes(dev_attrs, *attr_names)); } // Convert the result back to python return PyDeviceAttribute::convert_to_python(dev_attr_vec, self, extract_as); } static bopy::object command_history(Tango::DeviceProxy& self, const std::string& cmd_name, int depth) { std::vector* device_data_hist = NULL; bopy::list ret; { AutoPythonAllowThreads guard; device_data_hist = self.command_history(const_cast(cmd_name), depth); } vector::iterator it = device_data_hist->begin(); for(;it != device_data_hist->end(); ++it) { Tango::DeviceDataHistory& hist = *it; ret.append(hist); } delete device_data_hist; return ret; } static inline bopy::object attribute_history(Tango::DeviceProxy& self, const std::string & attr_name, int depth, PyTango::ExtractAs extract_as) { unique_pointer< vector > att_hist; { AutoPythonAllowThreads guard; att_hist.reset(self.attribute_history(const_cast(attr_name), depth)); } return PyDeviceAttribute::convert_to_python(att_hist, self, extract_as); } static inline long read_attributes_asynch(Tango::DeviceProxy& self, bopy::object py_attr_names) { CSequenceFromPython attr_names(py_attr_names); AutoPythonAllowThreads guard; return self.read_attributes_asynch(*attr_names); } static inline bopy::object read_attributes_reply(Tango::DeviceProxy& self, long id, PyTango::ExtractAs extract_as) { PyDeviceAttribute::AutoDevAttrVector dev_attr_vec; { AutoPythonAllowThreads guard; dev_attr_vec.reset(self.read_attributes_reply(id)); } return PyDeviceAttribute::convert_to_python(dev_attr_vec, self, extract_as); } static inline bopy::object read_attributes_reply(Tango::DeviceProxy& self, long id, long timeout, PyTango::ExtractAs extract_as) { PyDeviceAttribute::AutoDevAttrVector dev_attr_vec; { AutoPythonAllowThreads guard; dev_attr_vec.reset(self.read_attributes_reply(id, timeout)); } return PyDeviceAttribute::convert_to_python(dev_attr_vec, self, extract_as); } static inline long write_attributes_asynch(Tango::DeviceProxy& self, bopy::object py_list) { std::vector dev_attrs; pylist_to_devattrs(self, py_list, dev_attrs); AutoPythonAllowThreads guard; return self.write_attributes_asynch(dev_attrs); } static inline void write_attributes_reply(Tango::DeviceProxy& self, long id, long timestamp) { AutoPythonAllowThreads guard; self.write_attributes_reply(id, timestamp); } static inline void write_attributes_reply(Tango::DeviceProxy& self, long id) { AutoPythonAllowThreads guard; self.write_attributes_reply(id); } static inline void read_attributes_asynch(bopy::object py_self, bopy::object py_attr_names, bopy::object py_cb, PyTango::ExtractAs extract_as) { Tango::DeviceProxy* self = bopy::extract(py_self); CSequenceFromPython attr_names(py_attr_names); PyCallBackAutoDie* cb = bopy::extract(py_cb); cb->set_autokill_references(py_cb, py_self); cb->set_extract_as(extract_as); try { AutoPythonAllowThreads guard; self->read_attributes_asynch(*attr_names, *cb); } catch (...) { cb->unset_autokill_references(); throw; } } static inline void write_attributes_asynch(bopy::object py_self, bopy::object py_list, bopy::object py_cb) { Tango::DeviceProxy* self = bopy::extract(py_self); std::vector dev_attrs; pylist_to_devattrs(*self, py_list, dev_attrs); PyCallBackAutoDie* cb = bopy::extract(py_cb); cb->set_autokill_references(py_cb, py_self); try { AutoPythonAllowThreads guard; self->write_attributes_asynch(dev_attrs, *cb); } catch (...) { cb->unset_autokill_references(); throw; } } static int subscribe_event_global( bopy::object py_self, Tango::EventType event, bopy::object py_cb, bool stateless) { Tango::DeviceProxy& self = bopy::extract(py_self); PyCallBackPushEvent* cb = 0; if (bopy::extract(py_cb).check()) { cb = bopy::extract(py_cb); cb->set_device(py_self); AutoPythonAllowThreads guard; return self.subscribe_event(event, cb, stateless); } } static int subscribe_event_attrib( bopy::object py_self, const string &attr_name, Tango::EventType event, bopy::object py_cb_or_queuesize, bopy::object &py_filters, bool stateless, PyTango::ExtractAs extract_as ) { Tango::DeviceProxy& self = bopy::extract(py_self); CSequenceFromPython filters(py_filters); PyCallBackPushEvent* cb = 0; int event_queue_size = 0; if (bopy::extract(py_cb_or_queuesize).check()) { cb = bopy::extract(py_cb_or_queuesize); cb->set_device(py_self); cb->set_extract_as(extract_as); AutoPythonAllowThreads guard; return self.subscribe_event(attr_name, event, cb, *filters, stateless); } else { event_queue_size = bopy::extract(py_cb_or_queuesize); AutoPythonAllowThreads guard; return self.subscribe_event(attr_name, event, event_queue_size, *filters, stateless); } } static void unsubscribe_event(Tango::DeviceProxy& self, int event) { // If the callback is running, unsubscribe_event will lock // until it finishes. So we MUST release GIL to avoid a deadlock AutoPythonAllowThreads guard; self.unsubscribe_event(event); } template static bopy::object get_events__aux(bopy::object py_self, int event_id, PyTango::ExtractAs extract_as=PyTango::ExtractAsNumpy) { Tango::DeviceProxy &self = bopy::extract(py_self); EDList event_list; self.get_events(event_id, event_list); bopy::list r; for (size_t i=0; i < event_list.size(); ++i) { ED* event_data = event_list[i]; bopy::object py_ev(bopy::handle<>( bopy::to_python_indirect< ED*, bopy::detail::make_owning_holder>()(event_data))); // EventDataList deletes EventData's on destructor, so once // we are handling it somewhere else (as an bopy::object) we must // unset the reference event_list[i] = 0; PyCallBackPushEvent::fill_py_event(event_data, py_ev, py_self, extract_as); r.append(py_ev); } return r; } static void get_events__callback(bopy::object py_self, int event_id, PyCallBackPushEvent *cb, PyTango::ExtractAs extract_as) { Tango::DeviceProxy& self = bopy::extract(py_self); cb->set_device(py_self); cb->set_extract_as(extract_as); self.get_events(event_id, cb); } static bopy::object get_events__attr_conf(bopy::object py_self, int event_id) { return get_events__aux (py_self, event_id); } static bopy::object get_events__data(bopy::object py_self, int event_id, PyTango::ExtractAs extract_as) { return get_events__aux (py_self, event_id, extract_as); } static bopy::object get_events__data_ready(bopy::object py_self, int event_id) { return get_events__aux (py_self, event_id); } static bopy::object get_events__pipe_data(bopy::object py_self, int event_id, PyTango::ExtractAs extract_as) { return get_events__aux(py_self, event_id, extract_as); } static bopy::object get_events__devintr_change_data(bopy::object py_self, int event_id, PyTango::ExtractAs extract_as) { return get_events__aux(py_self, event_id, extract_as); } static boost::shared_ptr makeDeviceProxy1(const std::string& name) { Tango::DeviceProxy* dp = NULL; { AutoPythonAllowThreads guard; dp = new Tango::DeviceProxy(name.c_str()); } return boost::shared_ptr(dp); } static boost::shared_ptr makeDeviceProxy2(const std::string& name, bool b) { Tango::DeviceProxy* dp = NULL; { AutoPythonAllowThreads guard; dp = new Tango::DeviceProxy(name.c_str(), b); } return boost::shared_ptr(dp); } }; void export_device_proxy() { // The following function declarations are necessary to be able to cast // the function parameters from string& to const string&, otherwise python // will not recognize the method calls void (Tango::DeviceProxy::*get_property_)(std::string &, Tango::DbData &) = &Tango::DeviceProxy::get_property; void (Tango::DeviceProxy::*delete_property_)(std::string &) = &Tango::DeviceProxy::delete_property; bopy::class_ > DeviceProxy("DeviceProxy", bopy::init<>()) ; DeviceProxy .def(bopy::init()) .def(bopy::init()) .def(bopy::init()) .def("__init__", boost::python::make_constructor(PyDeviceProxy::makeDeviceProxy1)) .def("__init__", boost::python::make_constructor(PyDeviceProxy::makeDeviceProxy2)) // // Pickle // .def_pickle(PyDeviceProxy::PickleSuite()) // // general methods // .def("dev_name", &Tango::DeviceProxy::dev_name) .def("info", &Tango::DeviceProxy::info, ( arg_("self") ), bopy::return_internal_reference<1>() ) .def("get_device_db", &Tango::DeviceProxy::get_device_db, bopy::return_value_policy()) .def("_status", &PyDeviceProxy::status, ( arg_("self") ) ) .def("_state", &PyDeviceProxy::state, ( arg_("self") ) ) .def("adm_name", &Tango::DeviceProxy::adm_name, ( arg_("self") ) ) .def("description", &Tango::DeviceProxy::description, ( arg_("self") ) ) .def("name", &Tango::DeviceProxy::name, ( arg_("self") ) ) .def("alias", &Tango::DeviceProxy::alias, ( arg_("self") ) ) .def("get_tango_lib_version", &Tango::DeviceProxy::get_tango_lib_version, ( arg_("self") ) ) .def("_ping", &PyDeviceProxy::ping, ( arg_("self") ) ) .def("black_box", &Tango::DeviceProxy::black_box, ( arg_("self"), arg_("n") ), bopy::return_value_policy() ) // // command methods // .def("get_command_list", &Tango::DeviceProxy::get_command_list, ( arg_("self") ), bopy::return_value_policy() ) .def("_get_command_config", (Tango::CommandInfoList* (Tango::DeviceProxy::*)(StdStringVector &)) &Tango::DeviceProxy::get_command_config, ( arg_("self"), arg_("attr_names") ), bopy::return_value_policy() ) .def("_get_command_config", (Tango::CommandInfo (Tango::DeviceProxy::*)(const std::string&)) &Tango::DeviceProxy::get_command_config, ( arg_("self"), arg_("attr_name") ) ) .def("command_query", &Tango::DeviceProxy::command_query, ( arg_("self"), arg_("command") ) ) .def("command_list_query", &Tango::DeviceProxy::command_list_query, ( arg_("self") ), bopy::return_value_policy() ) .def("import_info", &Tango::DeviceProxy::import_info, ( arg_("self") ) ) // // property methods // .def("_get_property", (void (Tango::DeviceProxy::*) (const std::string &, Tango::DbData &)) get_property_, ( arg_("self"), arg_("propname"), arg_("propdata") ) ) .def("_get_property", (void (Tango::DeviceProxy::*) (std::vector&, Tango::DbData &)) &Tango::DeviceProxy::get_property, ( arg_("self"), arg_("propnames"), arg_("propdata") ) ) .def("_get_property", (void (Tango::DeviceProxy::*) (Tango::DbData &)) &Tango::DeviceProxy::get_property, ( arg_("self"), arg_("propdata") ) ) .def("_put_property", &Tango::DeviceProxy::put_property, ( arg_("self"), arg_("propdata") ) ) .def("_delete_property", (void (Tango::DeviceProxy::*) (const std::string &)) delete_property_, ( arg_("self"), arg_("propname") ) ) .def("_delete_property", (void (Tango::DeviceProxy::*) (StdStringVector &)) &Tango::DeviceProxy::delete_property, ( arg_("self"), arg_("propnames") ) ) .def("_delete_property", (void (Tango::DeviceProxy::*) (Tango::DbData &)) &Tango::DeviceProxy::delete_property, ( arg_("self"), arg_("propdata") ) ) .def("_get_property_list", &Tango::DeviceProxy::get_property_list, ( arg_("self"), arg_("filter"), arg_("array") ) ) // // pipe methods // .def("get_pipe_list", &Tango::DeviceProxy::get_pipe_list, ( arg_("self") ), bopy::return_value_policy() ) .def("_get_pipe_config", (Tango::PipeInfoList* (Tango::DeviceProxy::*)(StdStringVector &)) &Tango::DeviceProxy::get_pipe_config, bopy::return_value_policy() ) .def("_get_pipe_config", (Tango::PipeInfo (Tango::DeviceProxy::*)(const std::string&)) &Tango::DeviceProxy::get_pipe_config, ( arg_("self"), arg_("pipe_name") ) ) .def("_set_pipe_config", (void (Tango::DeviceProxy::*)(Tango::PipeInfoList &)) &Tango::DeviceProxy::set_pipe_config, ( arg_("self"), arg_("seq") ) ) // // this should define the c++ signature Tango::DevicePipe read_pipe(Tango::DeviceProxy& self, const std::string & pipe_name) // .def("__read_pipe", &PyDeviceProxy::read_pipe, ( arg_("self"), arg_("pipe_name") ) ) // // this should define the c++ signature write_pipe(Tango::DeviceProxy& self, Tango::DevicePipe& pipe_data) // .def("__write_pipe", &PyDeviceProxy::write_pipe, ( arg_("self"), arg_("pipe_data") ) ) // // attribute methods // .def("get_attribute_list", &Tango::DeviceProxy::get_attribute_list, ( arg_("self") ), bopy::return_value_policy() ) .def("_get_attribute_config", (Tango::AttributeInfoList* (Tango::DeviceProxy::*)(StdStringVector &)) &Tango::DeviceProxy::get_attribute_config, ( arg_("self"), arg_("attr_names") ), bopy::return_value_policy() ) .def("_get_attribute_config", (Tango::AttributeInfoEx (Tango::DeviceProxy::*)(const std::string&)) &Tango::DeviceProxy::get_attribute_config, ( arg_("self"), arg_("attr_name") ) ) .def("_get_attribute_config_ex", &Tango::DeviceProxy::get_attribute_config_ex, ( arg_("self"), arg_("attr_names") ), bopy::return_value_policy() ) .def("attribute_query", &Tango::DeviceProxy::attribute_query, ( arg_("self"), arg_("attr_name") ) ) .def("attribute_list_query", &Tango::DeviceProxy::attribute_list_query, ( arg_("self") ), bopy::return_value_policy() ) .def("attribute_list_query_ex", &Tango::DeviceProxy::attribute_list_query_ex, ( arg_("self") ), bopy::return_value_policy() ) .def("_set_attribute_config", (void (Tango::DeviceProxy::*)(Tango::AttributeInfoList &)) &Tango::DeviceProxy::set_attribute_config, ( arg_("self"), arg_("seq") ) ) .def("_set_attribute_config", (void (Tango::DeviceProxy::*)(Tango::AttributeInfoListEx &)) &Tango::DeviceProxy::set_attribute_config, ( arg_("self"), arg_("seq") ) ) .def("_read_attribute", &PyDeviceProxy::read_attribute, ( arg_("self"), arg_("attr_name"), arg_("extract_as")=PyTango::ExtractAsNumpy ) ) .def("_read_attributes", &PyDeviceProxy::read_attributes, ( arg_("self"), arg_("attr_names"), arg_("extract_as")=PyTango::ExtractAsNumpy ) ) .def("_write_attribute", (void (*)(Tango::DeviceProxy&, const string &, bopy::object )) &PyDeviceProxy::write_attribute, ( arg_("self"), arg_("attr_name"), arg_("value") ) ) .def("_write_attribute", (void (*)(Tango::DeviceProxy&, const Tango::AttributeInfo &, bopy::object )) &PyDeviceProxy::write_attribute, ( arg_("self"), arg_("attr_info"), arg_("value") ) ) .def("_write_attributes", &PyDeviceProxy::write_attributes, ( arg_("self"), arg_("name_val") ) ) .def("_write_read_attribute", &PyDeviceProxy::write_read_attribute, ( arg_("self"), arg_("attr_name"), arg_("value"), arg_("extract_as")=PyTango::ExtractAsNumpy ) ) .def("_write_read_attributes", &PyDeviceProxy::write_read_attributes, ( arg_("self"), arg_("attr_in"), arg_("attr_read_names"), arg_("extract_as")=PyTango::ExtractAsNumpy ) ) // // history methods // .def("command_history", &PyDeviceProxy::command_history, (arg_("self"), arg_("cmd_name"), arg_("depth"))) .def("attribute_history", &PyDeviceProxy::attribute_history, ( arg_("self"), arg_("attr_name"), arg_("depth"), arg_("extract_as")=PyTango::ExtractAsNumpy ) ) // // Polling administration methods // .def("polling_status", &Tango::DeviceProxy::polling_status, ( arg_("self") ), bopy::return_value_policy() ) .def("poll_command", (void (Tango::DeviceProxy::*)(const char *, int)) &Tango::DeviceProxy::poll_command, ( arg_("self"), arg_("cmd_name"), arg_("period") ) ) .def("poll_attribute", (void (Tango::DeviceProxy::*)(const char *, int)) &Tango::DeviceProxy::poll_attribute, ( arg_("self"), arg_("attr_name"), arg_("period") ) ) .def("get_command_poll_period", (int (Tango::DeviceProxy::*)(const char *)) &Tango::DeviceProxy::get_command_poll_period, ( arg_("self"), arg_("cmd_name") ) ) .def("get_attribute_poll_period", (int (Tango::DeviceProxy::*)(const char *)) &Tango::DeviceProxy::get_attribute_poll_period, ( arg_("self"), arg_("attr_name") ) ) .def("is_command_polled", (bool (Tango::DeviceProxy::*)(const char *)) &Tango::DeviceProxy::is_command_polled, ( arg_("self"), arg_("cmd_name") ) ) .def("is_attribute_polled", (bool (Tango::DeviceProxy::*)(const char *)) &Tango::DeviceProxy::is_attribute_polled, ( arg_("self"), arg_("attr_name") ) ) .def("stop_poll_command", (void (Tango::DeviceProxy::*)(const char *)) &Tango::DeviceProxy::stop_poll_command, ( arg_("self"), arg_("cmd_name") ) ) .def("stop_poll_attribute", (void (Tango::DeviceProxy::*)(const char *)) &Tango::DeviceProxy::stop_poll_attribute, ( arg_("self"), arg_("attr_name") ) ) // // Asynchronous methods // .def("__read_attributes_asynch", (long (*) (Tango::DeviceProxy &, bopy::object)) &PyDeviceProxy::read_attributes_asynch, ( arg_("self"), arg_("attr_names") ) ) .def("read_attributes_reply", (bopy::object (*) (Tango::DeviceProxy &, long, PyTango::ExtractAs)) &PyDeviceProxy::read_attributes_reply, ( arg_("self"), arg_("id"), arg_("extract_as")=PyTango::ExtractAsNumpy ) ) .def("read_attributes_reply", (bopy::object (*) (Tango::DeviceProxy &, long, long, PyTango::ExtractAs)) &PyDeviceProxy::read_attributes_reply, ( arg_("self"), arg_("id"), arg_("timeout"), arg_("extract_as")=PyTango::ExtractAsNumpy ) ) .def("pending_asynch_call", &Tango::DeviceProxy::pending_asynch_call, ( arg_("self"), arg_("req_type") ) ) .def("__write_attributes_asynch", (long (*) (Tango::DeviceProxy &, bopy::object)) &PyDeviceProxy::write_attributes_asynch, ( arg_("self"), arg_("values") ) ) .def("write_attributes_reply", (void (*) (Tango::DeviceProxy &, long)) &PyDeviceProxy::write_attributes_reply, ( arg_("self"), arg_("id") ) ) .def("write_attributes_reply", (void (*) (Tango::DeviceProxy &, long, long)) &PyDeviceProxy::write_attributes_reply, ( arg_("self"), arg_("id"), arg_("timeout") ) ) .def("__read_attributes_asynch", (void (*) (bopy::object, bopy::object, bopy::object, PyTango::ExtractAs)) &PyDeviceProxy::read_attributes_asynch, ( arg_("self"), arg_("attr_names"), arg_("callback"), arg_("extract_as")=PyTango::ExtractAsNumpy ) ) .def("__write_attributes_asynch", (void (*) (bopy::object, bopy::object, bopy::object)) &PyDeviceProxy::write_attributes_asynch, ( arg_("self"), arg_("values"), arg_("callback") ) ) // // Logging administration methods // .def("add_logging_target", (void (Tango::DeviceProxy::*)(const std::string &)) &Tango::DeviceProxy::add_logging_target, ( arg_("self"), arg_("target_type_target_name") ) ) .def("remove_logging_target", (void (Tango::DeviceProxy::*)(const std::string &)) &Tango::DeviceProxy::remove_logging_target, ( arg_("self"), arg_("target_type_target_name") ) ) .def("get_logging_target", &Tango::DeviceProxy::get_logging_target, ( arg_("self") ) ) .def("get_logging_level", &Tango::DeviceProxy::get_logging_level, ( arg_("self") ) ) .def("set_logging_level", &Tango::DeviceProxy::set_logging_level, ( arg_("self"), arg_("level") )) // // Event methods // .def("__subscribe_event", &PyDeviceProxy::subscribe_event_global, ( arg_("self"), arg_("event"), arg_("cb_or_queuesize") )) .def("__subscribe_event", &PyDeviceProxy::subscribe_event_attrib, ( arg_("self"), arg_("attr_name"), arg_("event"), arg_("cb_or_queuesize"), arg_("filters")=bopy::list(), arg_("stateless")=false, arg_("extract_as")=PyTango::ExtractAsNumpy ) ) .def("__unsubscribe_event", &PyDeviceProxy::unsubscribe_event ) .def("__get_callback_events", PyDeviceProxy::get_events__callback, ( arg_("self"), arg_("event_id"), arg_("callback"), arg_("extract_as")=PyTango::ExtractAsNumpy) ) .def("__get_attr_conf_events", PyDeviceProxy::get_events__attr_conf, ( arg_("self"), arg_("event_id")) ) .def("__get_data_events", PyDeviceProxy::get_events__data, ( arg_("self"), arg_("event_id"), arg_("extract_as")=PyTango::ExtractAsNumpy )) .def("__get_data_ready_events", PyDeviceProxy::get_events__data_ready, ( arg_("self"), arg_("event_id")) ) .def("__get_pipe_events", PyDeviceProxy::get_events__pipe_data, ( arg_("self"), arg_("event_id"), arg_("extract_as")=PyTango::ExtractAsNumpy )) .def("__get_devintr_change_events", PyDeviceProxy::get_events__devintr_change_data, ( arg_("self"), arg_("event_id")) ) // methods to access data in event queues .def("event_queue_size", &Tango::DeviceProxy::event_queue_size, ( arg_("self"), arg_("event_id") ) ) .def("get_last_event_date", &Tango::DeviceProxy::get_last_event_date, ( arg_("self"), arg_("event_id") ) ) .def("is_event_queue_empty", &Tango::DeviceProxy::is_event_queue_empty, ( arg_("self"), arg_("event_id") ) ) // // Locking methods // .def("lock", &Tango::DeviceProxy::lock, lock_overloads( ( arg_("lock_validity") ))) .def("unlock", &Tango::DeviceProxy::unlock, unlock_overloads( arg_("force"))) .def("locking_status", &Tango::DeviceProxy::locking_status, ( arg_("self") )) .def("is_locked", &Tango::DeviceProxy::is_locked, ( arg_("self") )) .def("is_locked_by_me", &Tango::DeviceProxy::is_locked_by_me, ( arg_("self") )) .def("get_locker", &Tango::DeviceProxy::get_locker, ( arg_("self"), arg_("lockinfo") )) /// This is to be used by the python layer of this api... //.setattr("__subscribed_events", bopy::dict()) ; } pytango-9.2.2/ext/devintr_change_event_data.cpp000066400000000000000000000055141316324373100216730ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include #include "exception.h" using namespace boost::python; extern boost::python::object PyTango_DevFailed; namespace PyDevIntrChangeEventData { static boost::shared_ptr makeDevIntrChangeEventData() { Tango::DevIntrChangeEventData *result = new Tango::DevIntrChangeEventData; return boost::shared_ptr(result); } static void set_errors(Tango::DevIntrChangeEventData &event_data, boost::python::object &dev_failed) { Tango::DevFailed df; boost::python::object errors = dev_failed.attr("args"); sequencePyDevError_2_DevErrorList(errors.ptr(), event_data.errors); } }; // end PyDevIntrChangeEventData void export_devintr_change_event_data() { class_("DevIntrChangeEventData", init()) .def("__init__", boost::python::make_constructor(PyDevIntrChangeEventData::makeDevIntrChangeEventData)) // The original Tango::DevIntrChangeEventData structure has a 'device' field. // However, if we returned this directly we would get a different // python device each time. So we are doing our weird things to make // sure the device returned is the same where the read action was // performed. So we don't return Tango::DevIntrChangeEventData::device directly. // See callback.cpp .setattr("device",object()) .def_readwrite("event", &Tango::DevIntrChangeEventData::event) .def_readwrite("device_name", &Tango::DevIntrChangeEventData::device_name) .setattr("cmd_list", object()) .setattr("att_list", object()) .def_readwrite("dev_started", &Tango::DevIntrChangeEventData::dev_started) .def_readwrite("err", &Tango::DevIntrChangeEventData::err) .def_readwrite("reception_date", &Tango::DevIntrChangeEventData::reception_date) .def_readwrite("err", &Tango::DevIntrChangeEventData::err) .add_property("errors", make_getter(&Tango::DevIntrChangeEventData::errors, return_value_policy()), &PyDevIntrChangeEventData::set_errors) .def("get_date", &Tango::DevIntrChangeEventData::get_date, return_internal_reference<>()) ; } pytango-9.2.2/ext/enums.cpp000066400000000000000000000237471316324373100156600ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include using namespace boost::python; void export_enums() { enum_("LockerLanguage") .value("CPP", Tango::CPP) .value("JAVA", Tango::JAVA) ; enum_("CmdArgType") .value(Tango::CmdArgTypeName[Tango::DEV_VOID], Tango::DEV_VOID) .value(Tango::CmdArgTypeName[Tango::DEV_BOOLEAN], Tango::DEV_BOOLEAN) .value(Tango::CmdArgTypeName[Tango::DEV_SHORT], Tango::DEV_SHORT) .value(Tango::CmdArgTypeName[Tango::DEV_LONG], Tango::DEV_LONG) .value(Tango::CmdArgTypeName[Tango::DEV_FLOAT], Tango::DEV_FLOAT) .value(Tango::CmdArgTypeName[Tango::DEV_DOUBLE], Tango::DEV_DOUBLE) .value(Tango::CmdArgTypeName[Tango::DEV_USHORT], Tango::DEV_USHORT) .value(Tango::CmdArgTypeName[Tango::DEV_ULONG], Tango::DEV_ULONG) .value(Tango::CmdArgTypeName[Tango::DEV_STRING], Tango::DEV_STRING) .value(Tango::CmdArgTypeName[Tango::DEVVAR_CHARARRAY], Tango::DEVVAR_CHARARRAY) .value(Tango::CmdArgTypeName[Tango::DEVVAR_SHORTARRAY], Tango::DEVVAR_SHORTARRAY) .value(Tango::CmdArgTypeName[Tango::DEVVAR_LONGARRAY], Tango::DEVVAR_LONGARRAY) .value(Tango::CmdArgTypeName[Tango::DEVVAR_FLOATARRAY], Tango::DEVVAR_FLOATARRAY) .value(Tango::CmdArgTypeName[Tango::DEVVAR_DOUBLEARRAY], Tango::DEVVAR_DOUBLEARRAY) .value(Tango::CmdArgTypeName[Tango::DEVVAR_USHORTARRAY], Tango::DEVVAR_USHORTARRAY) .value(Tango::CmdArgTypeName[Tango::DEVVAR_ULONGARRAY], Tango::DEVVAR_ULONGARRAY) .value(Tango::CmdArgTypeName[Tango::DEVVAR_STRINGARRAY], Tango::DEVVAR_STRINGARRAY) .value(Tango::CmdArgTypeName[Tango::DEVVAR_LONGSTRINGARRAY], Tango::DEVVAR_LONGSTRINGARRAY) .value(Tango::CmdArgTypeName[Tango::DEVVAR_DOUBLESTRINGARRAY], Tango::DEVVAR_DOUBLESTRINGARRAY) .value(Tango::CmdArgTypeName[Tango::DEV_STATE], Tango::DEV_STATE) .value(Tango::CmdArgTypeName[Tango::CONST_DEV_STRING], Tango::CONST_DEV_STRING) .value(Tango::CmdArgTypeName[Tango::DEVVAR_BOOLEANARRAY], Tango::DEVVAR_BOOLEANARRAY) .value(Tango::CmdArgTypeName[Tango::DEV_UCHAR], Tango::DEV_UCHAR) .value(Tango::CmdArgTypeName[Tango::DEV_LONG64], Tango::DEV_LONG64) .value(Tango::CmdArgTypeName[Tango::DEV_ULONG64], Tango::DEV_ULONG64) .value(Tango::CmdArgTypeName[Tango::DEVVAR_LONG64ARRAY], Tango::DEVVAR_LONG64ARRAY) .value(Tango::CmdArgTypeName[Tango::DEVVAR_ULONG64ARRAY], Tango::DEVVAR_ULONG64ARRAY) .value(Tango::CmdArgTypeName[Tango::DEV_INT], Tango::DEV_INT) .value(Tango::CmdArgTypeName[Tango::DEV_ENCODED], Tango::DEV_ENCODED) .value(Tango::CmdArgTypeName[Tango::DEV_ENUM], Tango::DEV_ENUM) .value(Tango::CmdArgTypeName[Tango::DEV_PIPE_BLOB], Tango::DEV_PIPE_BLOB) .value(Tango::CmdArgTypeName[Tango::DEVVAR_STATEARRAY], Tango::DEVVAR_STATEARRAY) .export_values() ; enum_("MessBoxType") .value("STOP", Tango::STOP) .value("INFO", Tango::INFO) ; enum_("PollObjType") .value("POLL_CMD", Tango::POLL_CMD) .value("POLL_ATTR", Tango::POLL_ATTR) .value("EVENT_HEARTBEAT", Tango::EVENT_HEARTBEAT) .value("STORE_SUBDEV", Tango::STORE_SUBDEV) ; enum_("PollCmdCode") .value("POLL_ADD_OBJ", Tango::POLL_ADD_OBJ) .value("POLL_REM_OBJ", Tango::POLL_REM_OBJ) .value("POLL_START", Tango::POLL_START) .value("POLL_STOP", Tango::POLL_STOP) .value("POLL_UPD_PERIOD", Tango::POLL_UPD_PERIOD) .value("POLL_REM_DEV", Tango::POLL_REM_DEV) .value("POLL_EXIT", Tango::POLL_EXIT) .value("POLL_REM_EXT_TRIG_OBJ", Tango::POLL_REM_EXT_TRIG_OBJ) .value("POLL_ADD_HEARTBEAT", Tango::POLL_ADD_HEARTBEAT) .value("POLL_REM_HEARTBEAT", Tango::POLL_REM_HEARTBEAT) ; enum_("SerialModel") .value("BY_DEVICE",Tango::BY_DEVICE) .value("BY_CLASS",Tango::BY_CLASS) .value("BY_PROCESS",Tango::BY_PROCESS) .value("NO_SYNC",Tango::NO_SYNC) ; enum_("AttReqType") .value("READ_REQ",Tango::READ_REQ) .value("WRITE_REQ",Tango::WRITE_REQ) ; enum_("LockCmdCode") .value("LOCK_ADD_DEV", Tango::LOCK_ADD_DEV) .value("LOCK_REM_DEV", Tango::LOCK_REM_DEV) .value("LOCK_UNLOCK_ALL_EXIT", Tango::LOCK_UNLOCK_ALL_EXIT) .value("LOCK_EXIT", Tango::LOCK_EXIT) ; #ifdef TANGO_HAS_LOG4TANGO enum_("LogLevel") .value("LOG_OFF", Tango::LOG_OFF) .value("LOG_FATAL", Tango::LOG_FATAL) .value("LOG_ERROR", Tango::LOG_ERROR) .value("LOG_WARN", Tango::LOG_WARN) .value("LOG_INFO", Tango::LOG_INFO) .value("LOG_DEBUG", Tango::LOG_DEBUG) ; enum_("LogTarget") .value("LOG_CONSOLE", Tango::LOG_CONSOLE) .value("LOG_FILE", Tango::LOG_FILE) .value("LOG_DEVICE", Tango::LOG_DEVICE) ; #endif // TANGO_HAS_LOG4TANGO enum_("EventType") .value("CHANGE_EVENT", Tango::CHANGE_EVENT) .value("QUALITY_EVENT", Tango::QUALITY_EVENT) .value("PERIODIC_EVENT", Tango::PERIODIC_EVENT) .value("ARCHIVE_EVENT", Tango::ARCHIVE_EVENT) .value("USER_EVENT", Tango::USER_EVENT) .value("ATTR_CONF_EVENT", Tango::ATTR_CONF_EVENT) .value("DATA_READY_EVENT", Tango::DATA_READY_EVENT) .value("INTERFACE_CHANGE_EVENT", Tango::INTERFACE_CHANGE_EVENT) .value("PIPE_EVENT", Tango::PIPE_EVENT) ; enum_("AttrSerialModel") .value("ATTR_NO_SYNC", Tango::ATTR_NO_SYNC) .value("ATTR_BY_KERNEL", Tango::ATTR_BY_KERNEL) .value("ATTR_BY_USER", Tango::ATTR_BY_USER) ; enum_("KeepAliveCmdCode") .value("EXIT_TH", Tango::EXIT_TH) ; enum_("AccessControlType") .value("ACCESS_READ", Tango::ACCESS_READ) .value("ACCESS_WRITE", Tango::ACCESS_WRITE) ; enum_("asyn_req_type") .value("POLLING", Tango::POLLING) .value("CALLBACK", Tango::CALL_BACK) .value("ALL_ASYNCH", Tango::ALL_ASYNCH) ; enum_("cb_sub_model") .value("PUSH_CALLBACK", Tango::PUSH_CALLBACK) .value("PULL_CALLBACK", Tango::PULL_CALLBACK) ; // // Tango IDL // enum_("AttrQuality") .value("ATTR_VALID", Tango::ATTR_VALID) .value("ATTR_INVALID", Tango::ATTR_INVALID) .value("ATTR_ALARM", Tango::ATTR_ALARM) .value("ATTR_CHANGING", Tango::ATTR_CHANGING) .value("ATTR_WARNING", Tango::ATTR_WARNING) ; enum_("AttrWriteType") .value("READ", Tango::READ) .value("READ_WITH_WRITE", Tango::READ_WITH_WRITE) .value("WRITE", Tango::WRITE) .value("READ_WRITE", Tango::READ_WRITE) .value("WT_UNKNOWN", Tango::READ_WRITE) .export_values() ; enum_("AttrDataFormat") .value("SCALAR", Tango::SCALAR) .value("SPECTRUM", Tango::SPECTRUM) .value("IMAGE", Tango::IMAGE) .value("FMT_UNKNOWN", Tango::FMT_UNKNOWN) .export_values() ; enum_("DevSource") .value("DEV", Tango::DEV) .value("CACHE", Tango::CACHE) .value("CACHE_DEV", Tango::CACHE_DEV) ; enum_("ErrSeverity") .value("WARN", Tango::WARN) .value("ERR", Tango::ERR) .value("PANIC", Tango::PANIC) ; enum_("DevState") .value(Tango::DevStateName[Tango::ON], Tango::ON) .value(Tango::DevStateName[Tango::OFF], Tango::OFF) .value(Tango::DevStateName[Tango::CLOSE], Tango::CLOSE) .value(Tango::DevStateName[Tango::OPEN], Tango::OPEN) .value(Tango::DevStateName[Tango::INSERT], Tango::INSERT) .value(Tango::DevStateName[Tango::EXTRACT], Tango::EXTRACT) .value(Tango::DevStateName[Tango::MOVING], Tango::MOVING) .value(Tango::DevStateName[Tango::STANDBY], Tango::STANDBY) .value(Tango::DevStateName[Tango::FAULT], Tango::FAULT) .value(Tango::DevStateName[Tango::INIT], Tango::INIT) .value(Tango::DevStateName[Tango::RUNNING], Tango::RUNNING) .value(Tango::DevStateName[Tango::ALARM], Tango::ALARM) .value(Tango::DevStateName[Tango::DISABLE], Tango::DISABLE) .value(Tango::DevStateName[Tango::UNKNOWN], Tango::UNKNOWN) ; enum_("DispLevel") .value("OPERATOR", Tango::OPERATOR) .value("EXPERT", Tango::EXPERT) .value("DL_UNKNOWN", Tango::DL_UNKNOWN) ; enum_("PipeWriteType") .value("PIPE_READ", Tango::PIPE_READ) .value("PIPE_READ_WRITE", Tango::PIPE_READ_WRITE) .value("PIPE_WT_UNKNOWN", Tango::PIPE_WT_UNKNOWN) ; enum_("PipeSerialModel") .value("PIPE_NO_SYNC", Tango::PIPE_NO_SYNC) .value("PIPE_BY_KERNEL", Tango::PIPE_BY_KERNEL) .value("PIPE_BY_USER", Tango::PIPE_BY_USER) ; scope().attr("PipeReqType") = scope().attr("AttReqType"); enum_("AttrMemorizedType") .value("NOT_KNOWN", Tango::NOT_KNOWN) .value("NONE", Tango::NONE) .value("MEMORIZED", Tango::MEMORIZED) .value("MEMORIZED_WRITE_INIT", Tango::MEMORIZED_WRITE_INIT) ; } pytango-9.2.2/ext/event_data.cpp000066400000000000000000000056011316324373100166300ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include #include "exception.h" using namespace boost::python; extern boost::python::object PyTango_DevFailed; namespace PyEventData { static boost::shared_ptr makeEventData() { Tango::EventData *result = new Tango::EventData; result->attr_value = new Tango::DeviceAttribute(); return boost::shared_ptr(result); } static void set_errors(Tango::EventData &event_data, boost::python::object &error) { PyObject* error_ptr = error.ptr(); if (PyObject_IsInstance(error_ptr, PyTango_DevFailed.ptr())) { Tango::DevFailed df; boost::python::object error_list = error.attr("args"); sequencePyDevError_2_DevErrorList(error_list.ptr(), event_data.errors); } else { sequencePyDevError_2_DevErrorList(error_ptr, event_data.errors); } } }; void export_event_data() { class_("EventData", init()) .def("__init__", boost::python::make_constructor(PyEventData::makeEventData)) // The original Tango::EventData structure has a 'device' field. // However, if we returned this directly we would get a different // python device each time. So we are doing our weird things to make // sure the device returned is the same where the read action was // performed. So we don't return Tango::EventData::device directly. // See callback.cpp .setattr("device", object()) .def_readwrite("attr_name", &Tango::EventData::attr_name) .def_readwrite("event", &Tango::EventData::event) // The original Tango::EventData structure has "get_attr_value" but // we can't refer it directly here because we have to extract value // and so on. // See callback.cpp .setattr("attr_value", object()) .def_readwrite("err", &Tango::EventData::err) .def_readwrite("reception_date", &Tango::EventData::reception_date) .add_property("errors", make_getter(&Tango::EventData::errors, return_value_policy()), &PyEventData::set_errors) .def("get_date", &Tango::EventData::get_date, return_internal_reference<>()) ; } pytango-9.2.2/ext/exception.cpp000066400000000000000000000440651316324373100165230ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include "pytgutils.h" #include "exception.h" using namespace boost::python; // Useful constants for exceptions const char *param_must_be_seq = "Parameter must be a string or a python " "sequence (e.x.: a tuple or a list)"; const char *unreachable_code = "Code should be unreachable"; const char *non_string_seq = "Parameter must be a non string sequence " "(e.x.: a tuple or a list)"; const char *non_valid_image = "Parameter must be an IMAGE. This is a sequence" " of sequences (with all the sub-sequences having" " the same length) or a bidimensional numpy.array"; const char *non_valid_spectrum = "Parameter must be an SPECTRUM. This is a" " sequence of scalar values or a unidimensional" " numpy.array"; boost::python::object PyTango_DevFailed, PyTango_ConnectionFailed, PyTango_CommunicationFailed, PyTango_WrongNameSyntax, PyTango_NonDbDevice, PyTango_WrongData, PyTango_NonSupportedFeature, PyTango_AsynCall, PyTango_AsynReplyNotArrived, PyTango_EventSystemFailed, PyTango_DeviceUnlocked, PyTango_NotAllowed; namespace Tango { inline bool operator==(const Tango::NamedDevFailed& df1, const Tango::NamedDevFailed& df2) { /// @todo ? err_stack ? return (df1.name == df2.name) && (df1.idx_in_call == df2.idx_in_call); } } void sequencePyDevError_2_DevErrorList(PyObject *value, Tango::DevErrorList &del) { long len = max((int)PySequence_Size(value), 0); del.length(len); for (long loop = 0; loop < len; ++loop) { PyObject *item = PySequence_GetItem(value, loop); Tango::DevError &dev_error = extract(item); del[loop].desc = CORBA::string_dup(dev_error.desc); del[loop].reason = CORBA::string_dup(dev_error.reason); del[loop].origin = CORBA::string_dup(dev_error.origin); del[loop].severity = dev_error.severity; Py_XDECREF(item); } } void PyDevFailed_2_DevFailed(PyObject *value, Tango::DevFailed &df) { if (PyObject_IsInstance(value, PyTango_DevFailed.ptr())) { PyObject *args = PyObject_GetAttrString(value, "args"); if (PySequence_Check(args) == 0) { Py_XDECREF(args); Tango::Except::throw_exception( (const char *)"PyDs_BadDevFailedException", (const char *)"A badly formed exception has been received", (const char *)"PyDevFailed_2_DevFailed"); } else { sequencePyDevError_2_DevErrorList(args, df.errors); Py_DECREF(args); } } else { sequencePyDevError_2_DevErrorList(value, df.errors); } } void throw_python_dev_failed() { PyObject *type, *value, *traceback; PyErr_Fetch(&type, &value, &traceback); if (value == NULL) { Py_XDECREF(type); Py_XDECREF(traceback); Tango::Except::throw_exception( (const char *)"PyDs_BadDevFailedException", (const char *)"A badly formed exception has been received", (const char *)"throw_python_dev_failed"); } Tango::DevFailed df; try { PyDevFailed_2_DevFailed(value, df); } catch(...) { Py_XDECREF(type); Py_XDECREF(value); Py_XDECREF(traceback); throw; } Py_XDECREF(type); Py_XDECREF(value); Py_XDECREF(traceback); throw df; } Tango::DevFailed to_dev_failed(PyObject *type, PyObject *value, PyObject *traceback) { bool from_fetch = false; if ((type == NULL) || (value == NULL) || (traceback == NULL) || (type == Py_None) || (value == Py_None) || (traceback == Py_None)) { PyErr_Fetch(&type, &value, &traceback); from_fetch = true; } Tango::DevErrorList dev_err; dev_err.length(1); if (value == NULL) { // // Send a default exception in case Python does not send us information // dev_err[0].origin = CORBA::string_dup("Py_to_dev_failed"); dev_err[0].desc = CORBA::string_dup("A badly formed exception has been received"); dev_err[0].reason = CORBA::string_dup("PyDs_BadPythonException"); dev_err[0].severity = Tango::ERR; } else { // // Populate a one level DevFailed exception // PyObject *tracebackModule = PyImport_ImportModule("traceback"); if (tracebackModule != NULL) { // // Format the traceback part of the Python exception // and store it in the origin part of the Tango exception // PyObject *tbList_ptr = PyObject_CallMethod( tracebackModule, (char *)"format_tb", (char *)"O", traceback == NULL ? Py_None : traceback); boost::python::object tbList = object(handle<>(tbList_ptr)); boost::python::str origin = str("").join(tbList); char const* origin_ptr = boost::python::extract(origin); dev_err[0].origin = CORBA::string_dup(origin_ptr); // // Format the exec and value part of the Python exception // and store it in the desc part of the Tango exception // tbList_ptr = PyObject_CallMethod( tracebackModule, (char *)"format_exception_only", (char *)"OO", type, value == NULL ? Py_None : value); tbList = object(handle<>(tbList_ptr)); boost::python::str desc = str("").join(tbList); char const* desc_ptr = boost::python::extract(desc); dev_err[0].desc = CORBA::string_dup(desc_ptr); Py_DECREF(tracebackModule); dev_err[0].reason = CORBA::string_dup("PyDs_PythonError"); dev_err[0].severity = Tango::ERR; } else { // // Send a default exception because we can't format the // different parts of the Python's one ! // dev_err[0].origin = CORBA::string_dup("Py_to_dev_failed"); dev_err[0].desc = CORBA::string_dup("Can't import Python traceback module. Can't extract info from Python exception"); dev_err[0].reason = CORBA::string_dup("PyDs_PythonError"); dev_err[0].severity = Tango::ERR; } } if(from_fetch) { Py_XDECREF(type); Py_XDECREF(value); Py_XDECREF(traceback); } return Tango::DevFailed(dev_err); } void throw_python_generic_exception(PyObject *type, PyObject *value, PyObject *traceback) { throw to_dev_failed(type, value, traceback); } void handle_python_exception(boost::python::error_already_set &eas) { if (PyErr_ExceptionMatches(PyTango_DevFailed.ptr())) { throw_python_dev_failed(); } else { throw_python_generic_exception(); } } struct convert_PyDevFailed_to_DevFailed { convert_PyDevFailed_to_DevFailed() { boost::python::converter::registry::push_back( &convertible, &construct, boost::python::type_id()); } // Check if given Python object is convertible to a DevFailed. // If so, return obj, otherwise return 0 static void* convertible(PyObject* obj) { if (PyObject_IsInstance(obj, PyTango_DevFailed.ptr())) return obj; return 0; } // Construct a vec3f object from the given Python object, and // store it in the stage1 (?) data. static void construct(PyObject* obj, boost::python::converter::rvalue_from_python_stage1_data* data) { typedef boost::python::converter::rvalue_from_python_storage DevFailed_storage; void* const storage = reinterpret_cast(data)->storage.bytes; Tango::DevFailed *df_ptr = new (storage) Tango::DevFailed(); PyDevFailed_2_DevFailed(obj, *df_ptr); data->convertible = storage; } }; void _translate_dev_failed(const Tango::DevFailed &dev_failed, boost::python::object py_dev_failed) { boost::python::object py_errors(dev_failed.errors); PyErr_SetObject(py_dev_failed.ptr(), py_errors.ptr()); } void translate_dev_failed(const Tango::DevFailed &dev_failed) { _translate_dev_failed(dev_failed, PyTango_DevFailed); } void translate_connection_failed(const Tango::ConnectionFailed &dev_failed) { _translate_dev_failed(dev_failed, PyTango_ConnectionFailed); } void translate_communication_failed(const Tango::CommunicationFailed &dev_failed) { _translate_dev_failed(dev_failed, PyTango_CommunicationFailed); } void translate_wrong_name_syntax(const Tango::WrongNameSyntax &dev_failed) { _translate_dev_failed(dev_failed, PyTango_WrongNameSyntax); } void translate_non_db_device(const Tango::NonDbDevice &dev_failed) { _translate_dev_failed(dev_failed, PyTango_NonDbDevice); } void translate_wrong_data(const Tango::WrongData &dev_failed) { _translate_dev_failed(dev_failed, PyTango_WrongData); } void translate_non_supported_feature(const Tango::NonSupportedFeature &dev_failed) { _translate_dev_failed(dev_failed, PyTango_NonSupportedFeature); } void translate_asyn_call(const Tango::AsynCall &dev_failed) { _translate_dev_failed(dev_failed, PyTango_AsynCall); } void translate_asyn_reply_not_arrived(const Tango::AsynReplyNotArrived &dev_failed) { _translate_dev_failed(dev_failed, PyTango_AsynReplyNotArrived); } void translate_event_system_failed(const Tango::EventSystemFailed &dev_failed) { _translate_dev_failed(dev_failed, PyTango_EventSystemFailed); } void translate_device_unlocked(const Tango::DeviceUnlocked &dev_failed) { _translate_dev_failed(dev_failed, PyTango_DeviceUnlocked); } void translate_not_allowed(const Tango::NotAllowed &dev_failed) { _translate_dev_failed(dev_failed, PyTango_NotAllowed); } namespace PyExcept { inline void throw_exception(const char *a, const char *b, const char *c) { Tango::Except::throw_exception(a, b, c); } inline void throw_exception_severity(const char *a, const char *b, const char *c, Tango::ErrSeverity d) { Tango::Except::throw_exception(a ,b, c, d); } inline void re_throw_exception(const Tango::DevFailed &df, const char *a, const char *b, const char *c) { Tango::Except::re_throw_exception(const_cast(df), a, b, c); } inline void re_throw_exception_severity(const Tango::DevFailed &df, const char *a, const char *b, const char *c, Tango::ErrSeverity d) { Tango::Except::re_throw_exception(const_cast(df), a, b, c, d); } inline void print_exception(const Tango::DevFailed &df) { Tango::Except::print_exception(df); } } namespace PyNamedDevFailed { Tango::DevErrorList get_err_stack(Tango::NamedDevFailed & self) { return self.err_stack; } } //BOOST_PYTHON_FUNCTION_OVERLOADS(to_dev_failed_overloads, to_dev_failed, 0, 3) BOOST_PYTHON_FUNCTION_OVERLOADS(throw_python_generic_exception_overloads, throw_python_generic_exception, 0, 3) void export_exceptions() { bool (*compare_exception_) (Tango::DevFailed &, Tango::DevFailed &) = &Tango::Except::compare_exception; PyTango_DevFailed = boost::python::object( boost::python::handle<>(PyErr_NewException( (char *)"PyTango.DevFailed", NULL, NULL))); PyObject *df_ptr = PyTango_DevFailed.ptr(); PyTango_ConnectionFailed = boost::python::object( boost::python::handle<>(PyErr_NewException( const_cast("PyTango.ConnectionFailed"), df_ptr, NULL))); PyTango_CommunicationFailed = boost::python::object( boost::python::handle<>(PyErr_NewException( const_cast("PyTango.CommunicationFailed"), df_ptr, NULL))); PyTango_WrongNameSyntax = boost::python::object( boost::python::handle<>(PyErr_NewException( const_cast("PyTango.WrongNameSyntax"), df_ptr, NULL))); PyTango_NonDbDevice = boost::python::object( boost::python::handle<>(PyErr_NewException( const_cast("PyTango.NonDbDevice"), df_ptr, NULL))); PyTango_WrongData = boost::python::object( boost::python::handle<>(PyErr_NewException( const_cast("PyTango.WrongData"), df_ptr, NULL))); PyTango_NonSupportedFeature = boost::python::object( boost::python::handle<>(PyErr_NewException( const_cast("PyTango.NonSupportedFeature"), df_ptr, NULL))); PyTango_AsynCall = boost::python::object( boost::python::handle<>(PyErr_NewException( const_cast("PyTango.AsynCall"), df_ptr, NULL))); PyTango_AsynReplyNotArrived = boost::python::object( boost::python::handle<>(PyErr_NewException( const_cast("PyTango.AsynReplyNotArrived"), df_ptr, NULL))); PyTango_EventSystemFailed = boost::python::object( boost::python::handle<>(PyErr_NewException( const_cast("PyTango.EventSystemFailed"), df_ptr, NULL))); PyTango_DeviceUnlocked = boost::python::object( boost::python::handle<>(PyErr_NewException( const_cast("PyTango.DeviceUnlocked"), df_ptr, NULL))); PyTango_NotAllowed = boost::python::object( boost::python::handle<>(PyErr_NewException( const_cast("PyTango.NotAllowed"), df_ptr, NULL))); scope().attr("DevFailed") = PyTango_DevFailed; scope().attr("ConnectionFailed") = PyTango_ConnectionFailed; scope().attr("CommunicationFailed") = PyTango_CommunicationFailed; scope().attr("WrongNameSyntax") = PyTango_WrongNameSyntax; scope().attr("NonDbDevice") = PyTango_NonDbDevice; scope().attr("WrongData") = PyTango_WrongData; scope().attr("NonSupportedFeature") = PyTango_NonSupportedFeature; scope().attr("AsynCall") = PyTango_AsynCall; scope().attr("AsynReplyNotArrived") = PyTango_AsynReplyNotArrived; scope().attr("EventSystemFailed") = PyTango_EventSystemFailed; scope().attr("DeviceUnlocked") = PyTango_DeviceUnlocked; scope().attr("NotAllowed") = PyTango_NotAllowed; register_exception_translator(&translate_dev_failed); register_exception_translator(&translate_connection_failed); register_exception_translator(&translate_communication_failed); register_exception_translator(&translate_wrong_name_syntax); register_exception_translator(&translate_non_db_device); register_exception_translator(&translate_wrong_data); register_exception_translator(&translate_non_supported_feature); register_exception_translator(&translate_asyn_call); register_exception_translator(&translate_asyn_reply_not_arrived); register_exception_translator(&translate_event_system_failed); register_exception_translator(&translate_device_unlocked); register_exception_translator(&translate_not_allowed); class_("Except", no_init) .def("throw_exception", &PyExcept::throw_exception) .def("throw_exception", &PyExcept::throw_exception_severity) .def("re_throw_exception", &PyExcept::re_throw_exception) .def("re_throw_exception", &PyExcept::re_throw_exception_severity) .def("print_exception", &PyExcept::print_exception) .def("print_error_stack", &Tango::Except::print_error_stack) .def("compare_exception", (bool (*) (const Tango::DevFailed &, const Tango::DevFailed &)) compare_exception_) //.def("to_dev_failed", &to_dev_failed, to_dev_failed_overloads()) .def("throw_python_exception", &throw_python_generic_exception, throw_python_generic_exception_overloads()) .staticmethod("throw_exception") .staticmethod("re_throw_exception") .staticmethod("print_exception") .staticmethod("print_error_stack") //.staticmethod("to_dev_failed") .staticmethod("throw_python_exception") ; convert_PyDevFailed_to_DevFailed pydevfailed_2_devfailed; /// NamedDevFailed & family: class_ NamedDevFailed( "NamedDevFailed", "", no_init) ; NamedDevFailed .def_readonly("name", &Tango::NamedDevFailed::name) // string .def_readonly("idx_in_call", &Tango::NamedDevFailed::idx_in_call) // long .add_property("err_stack", PyNamedDevFailed::get_err_stack) // DevErrorList ; typedef std::vector StdNamedDevFailedVector_; class_< StdNamedDevFailedVector_ >("StdNamedDevFailedVector") .def(vector_indexing_suite()); // DevFailed is not really exported but just translated, so we can't // derivate. class_*/ > NamedDevFailedList( "NamedDevFailedList", "", no_init) ; NamedDevFailedList .def("get_faulty_attr_nb", &Tango::NamedDevFailedList::get_faulty_attr_nb) // size_t .def("call_failed", &Tango::NamedDevFailedList::call_failed) // bool .def_readonly("err_list", &Tango::NamedDevFailedList::err_list) // vector ; } pytango-9.2.2/ext/exception.h000066400000000000000000000070041316324373100161600ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #pragma once #include #include /** * Translates a seq into a C++ DevErrorList * * @param [in] seq a python sequence of DevError * @param [out] err_list the object to be filled with the several DevError */ void sequencePyDevError_2_DevErrorList(PyObject *seq, Tango::DevErrorList &err_list); /** * Translates a python DevFailed or a seq into a C++ DevFailed * * @param [in] obj a python sequence of DevError or a DevFailed * @param [out] df the object to be filled with the information extracted from obj */ void PyDevFailed_2_DevFailed(PyObject *obj, Tango::DevFailed &df); /** * Throws the current python exception. Assumes the python err is set and contains * a DevFailed exception */ void throw_python_dev_failed(); /** * Transforms a python exception into a Tango::DevFailed */ Tango::DevFailed to_dev_failed(PyObject *type=NULL, PyObject *value=NULL, PyObject *traceback=NULL); /** * Throws the current python exception as a DevFailed exception. */ void throw_python_generic_exception(PyObject *type=NULL, PyObject *value=NULL, PyObject *traceback=NULL); /** * Handles the current python exception: * If a PyTango DevFaild -> translates it to C++ and throws the DevFailed * If a generic python exception -> translates it to C++ DevFailed and throws the DevFailed * * @param[in] eas the boost python exception description (currently not used) */ void handle_python_exception(boost::python::error_already_set &eas); #define SAFE_CATCH_REPORT(meth_name) \ catch(boost::python::error_already_set &) \ { \ std::cerr << "PyTango generated an unexpected python exception in " \ << meth_name << "." << std::endl \ << "Please report this bug to PyTango with the following report:" \ << std::endl; \ PyErr_Print(); \ } \ catch(Tango::DevFailed &df) \ { \ std::cerr << "PyTango generated a DevFailed exception in " \ << meth_name << "." << std::endl \ << "Please report this bug to PyTango with the following report:" \ << std::endl; \ Tango::Except::print_exception(df); \ } \ catch(...) \ { \ std::cerr << "PyTango generated an unknown exception in " \ << meth_name << "." << std::endl \ << "Please report this bug to PyTango." << std::endl; \ } #define SAFE_CATCH_INFORM(meth_name) \ catch(boost::python::error_already_set &) \ { \ std::cerr << meth_name << " generated the following python exception:" << std::endl; \ PyErr_Print(); \ } \ catch(Tango::DevFailed &df) \ { \ std::cerr << meth_name << " generated the following DevFailed exception:" << std::endl; \ Tango::Except::print_exception(df); \ } \ catch(...) \ { \ std::cerr << meth_name << " generated an unknown exception." << std::endl; \ } pytango-9.2.2/ext/fast_from_py.h000066400000000000000000000507261316324373100166630ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #pragma once #include "from_py.h" /** * Translation between python object to Tango data type. * * Example: * Tango::DevLong tg_value; * try * { * from_py::convert(py_obj, tg_value); * } * catch(boost::python::error_already_set &eas) * { * handle_error(eas); * } */ template struct from_py { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; static inline void convert(const boost::python::object &o, TangoScalarType &tg) { convert(o.ptr(), tg); } static inline void convert(PyObject *o, TangoScalarType &tg) { // boost::python::object tmp(boost::python::handle<>(o)); // tg = boost::python::extract(tmp); Tango::Except::throw_exception( \ "PyDs_WrongPythonDataTypeForAttribute", "Unsupported attribute type translation", "from_py::convert()"); } }; #define DEFINE_FAST_TANGO_FROMPY(tangoTypeConst, FN) \ template<> \ struct from_py \ { \ typedef TANGO_const2type(tangoTypeConst) TangoScalarType; \ \ static inline void convert(const boost::python::object &o, TangoScalarType &tg) \ { \ convert(o.ptr(), tg); \ } \ \ static inline void convert(PyObject *o, TangoScalarType &tg) \ { \ tg = static_cast(FN(o)); \ if(PyErr_Occurred()) \ boost::python::throw_error_already_set(); \ } \ }; #undef max #undef min // DEFINE_FAST_TANGO_FROMPY should be enough. However, as python does not // provide conversion from python integers to all the data types accepted // by tango we must check the ranges manually. Also now we can add numpy // support to some extent... #ifdef DISABLE_PYTANGO_NUMPY # define DEFINE_FAST_TANGO_FROMPY_NUM(tangoTypeConst, cpy_type, FN) \ template<> \ struct from_py \ { \ typedef TANGO_const2type(tangoTypeConst) TangoScalarType; \ typedef numeric_limits TangoScalarTypeLimits; \ \ static inline void convert(const boost::python::object &o, TangoScalarType &tg) \ { \ convert(o.ptr(), tg); \ } \ \ static inline void convert(PyObject *o, TangoScalarType &tg) \ { \ cpy_type cpy_value = FN(o); \ if(PyErr_Occurred()) { \ PyErr_Clear(); \ PyErr_SetString(PyExc_TypeError, "Expecting a numeric type, it is not."); \ boost::python::throw_error_already_set(); \ } \ if (TangoScalarTypeLimits::is_integer) { \ if (cpy_value > TangoScalarTypeLimits::max()) { \ PyErr_SetString(PyExc_OverflowError, "Value is too large."); \ boost::python::throw_error_already_set(); \ } \ if (cpy_value < TangoScalarTypeLimits::min()) { \ PyErr_SetString(PyExc_OverflowError, "Value is too small."); \ boost::python::throw_error_already_set(); \ } \ } \ tg = static_cast(cpy_value); \ } \ }; #else // DISABLE_PYTANGO_NUMPY # define DEFINE_FAST_TANGO_FROMPY_NUM(tangoTypeConst, cpy_type, FN) \ template<> \ struct from_py \ { \ typedef TANGO_const2type(tangoTypeConst) TangoScalarType; \ typedef numeric_limits TangoScalarTypeLimits; \ \ static inline void convert(const boost::python::object &o, TangoScalarType &tg) \ { \ convert(o.ptr(), tg); \ } \ \ static inline void convert(PyObject *o, TangoScalarType &tg) \ { \ cpy_type cpy_value = FN(o); \ if(PyErr_Occurred()) { \ PyErr_Clear(); \ if(PyArray_CheckScalar(o) && \ ( PyArray_DescrFromScalar(o) \ == PyArray_DescrFromType(TANGO_const2numpy(tangoTypeConst)))) \ { \ PyArray_ScalarAsCtype(o, reinterpret_cast(&tg)); \ return; \ } else { \ PyErr_SetString(PyExc_TypeError, "Expecting a numeric type," \ " but it is not. If you use a numpy type instead of" \ " python core types, then it must exactly match (ex:" \ " numpy.int32 for PyTango.DevLong)"); \ boost::python::throw_error_already_set(); \ } \ } \ if (TangoScalarTypeLimits::is_integer) { \ if (cpy_value > (cpy_type)TangoScalarTypeLimits::max()) { \ PyErr_SetString(PyExc_OverflowError, "Value is too large."); \ boost::python::throw_error_already_set(); \ } \ if (cpy_value < (cpy_type)TangoScalarTypeLimits::min()) { \ PyErr_SetString(PyExc_OverflowError, "Value is too small."); \ boost::python::throw_error_already_set(); \ } \ } \ tg = static_cast(cpy_value); \ } \ }; #endif // !DISABLE_PYTANGO_NUMPY /* Allow for downcast */ inline unsigned PY_LONG_LONG PyLong_AsUnsignedLongLong_2(PyObject *pylong) { unsigned PY_LONG_LONG result = PyLong_AsUnsignedLongLong(pylong); if(PyErr_Occurred()) { PyErr_Clear(); result = PyLong_AsUnsignedLong(pylong); } return result; } DEFINE_FAST_TANGO_FROMPY_NUM(Tango::DEV_BOOLEAN, long, PyLong_AsLong) DEFINE_FAST_TANGO_FROMPY_NUM(Tango::DEV_UCHAR, unsigned long, PyLong_AsUnsignedLong) DEFINE_FAST_TANGO_FROMPY_NUM(Tango::DEV_SHORT, long, PyLong_AsLong) DEFINE_FAST_TANGO_FROMPY_NUM(Tango::DEV_USHORT, unsigned long, PyLong_AsUnsignedLong) DEFINE_FAST_TANGO_FROMPY_NUM(Tango::DEV_LONG, long, PyLong_AsLong) DEFINE_FAST_TANGO_FROMPY_NUM(Tango::DEV_ULONG, unsigned long, PyLong_AsUnsignedLong) DEFINE_FAST_TANGO_FROMPY(Tango::DEV_STATE, PyLong_AsLong) DEFINE_FAST_TANGO_FROMPY_NUM(Tango::DEV_LONG64, Tango::DevLong64, PyLong_AsLongLong) DEFINE_FAST_TANGO_FROMPY_NUM(Tango::DEV_ULONG64, Tango::DevULong64, PyLong_AsUnsignedLongLong_2) DEFINE_FAST_TANGO_FROMPY_NUM(Tango::DEV_FLOAT, double, PyFloat_AsDouble) DEFINE_FAST_TANGO_FROMPY_NUM(Tango::DEV_DOUBLE, double, PyFloat_AsDouble) // DEFINE_FAST_TANGO_FROMPY(Tango::DEV_STRING, PyString_AsString) DEFINE_FAST_TANGO_FROMPY(Tango::DEV_STRING, PyString_AsCorbaString) DEFINE_FAST_TANGO_FROMPY(Tango::DEV_ENUM, PyLong_AsUnsignedLong) template struct array_element_from_py : public from_py { }; template<> struct array_element_from_py { static const long tangoArrayTypeConst = Tango::DEVVAR_CHARARRAY; typedef TANGO_const2scalartype(tangoArrayTypeConst) TangoScalarType; typedef numeric_limits TangoScalarTypeLimits; static inline void convert(const boost::python::object &o, TangoScalarType &tg) { convert(o.ptr(), tg); } #ifdef DISABLE_PYTANGO_NUMPY static inline void convert(PyObject *o, TangoScalarType &tg) { long cpy_value = PyLong_AsLong(o); if(PyErr_Occurred()) { PyErr_Clear(); PyErr_SetString(PyExc_TypeError, "Expecting a numeric type," " but it is not"); boost::python::throw_error_already_set(); } if (TangoScalarTypeLimits::is_integer) { if (cpy_value > TangoScalarTypeLimits::max()) { PyErr_SetString(PyExc_OverflowError, "Value is too large."); boost::python::throw_error_already_set(); } if (cpy_value < TangoScalarTypeLimits::min()) { PyErr_SetString(PyExc_OverflowError, "Value is too small."); boost::python::throw_error_already_set(); } } tg = static_cast(cpy_value); } #else static inline void convert(PyObject *o, TangoScalarType &tg) { long cpy_value = PyLong_AsLong(o); if(PyErr_Occurred()) { PyErr_Clear(); if(PyArray_CheckScalar(o) && ( PyArray_DescrFromScalar(o) == PyArray_DescrFromType(TANGO_const2scalarnumpy(tangoArrayTypeConst)))) { PyArray_ScalarAsCtype(o, reinterpret_cast(&tg)); return; } else { PyErr_SetString(PyExc_TypeError, "Expecting a numeric type," " but it is not. If you use a numpy type instead of" " python core types, then it must exactly match (ex:" " numpy.int32 for PyTango.DevLong)"); boost::python::throw_error_already_set(); } } if (TangoScalarTypeLimits::is_integer) { if (cpy_value > TangoScalarTypeLimits::max()) { PyErr_SetString(PyExc_OverflowError, "Value is too large."); boost::python::throw_error_already_set(); } if (cpy_value < TangoScalarTypeLimits::min()) { PyErr_SetString(PyExc_OverflowError, "Value is too small."); boost::python::throw_error_already_set(); } } tg = static_cast(cpy_value); } #endif // DISABLE_PYTANGO_NUMPY }; template inline void fast_python_to_tango_buffer_deleter__(typename TANGO_const2type(tangoTypeConst)* data_buffer, long processedElements) { delete [] data_buffer; } template<> inline void fast_python_to_tango_buffer_deleter__(Tango::DevString* data_buffer, long processedElements) { for (long i =0; i < processedElements; ++i) { delete [] data_buffer[i]; } delete [] data_buffer; } template inline typename TANGO_const2type(tangoTypeConst)* fast_python_to_tango_buffer_sequence(PyObject* py_val, long* pdim_x, long *pdim_y, const std::string &fname, bool isImage, long& res_dim_x, long& res_dim_y) { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; long dim_x; long dim_y = 0; Py_ssize_t len = PySequence_Size(py_val); bool expectFlatSource; if (isImage) { if (pdim_y) { expectFlatSource = true; dim_x = *pdim_x; dim_y = *pdim_y; long len2 = dim_x*dim_y; if (len2 < len) len = len2; } else { expectFlatSource = false; if (len > 0) { PyObject* py_row0 = PySequence_ITEM(py_val, 0); if (!py_row0 || !PySequence_Check(py_row0)) { Py_XDECREF(py_row0); Tango::Except::throw_exception( "PyDs_WrongParameters", "Expecting a sequence of sequences.", fname + "()"); } dim_y = static_cast(len); dim_x = static_cast(PySequence_Size(py_row0)); Py_XDECREF(py_row0); } else { dim_x = 0; } } len = dim_x*dim_y; } else { expectFlatSource = true; if (pdim_x) { if (*pdim_x > len) Tango::Except::throw_exception( "PyDs_WrongParameters", "Specified dim_x is larger than the sequence size", fname + "()"); len = *pdim_x; } if (pdim_y && (*pdim_y!=0)) Tango::Except::throw_exception( "PyDs_WrongParameters", "You should not specify dim_y for an spectrum attribute!", fname + "()"); dim_x = static_cast(len); } res_dim_x = dim_x; res_dim_y = dim_y; if (!PySequence_Check(py_val)) Tango::Except::throw_exception( "PyDs_WrongParameters", "Expecting a sequence!", fname + "()"); /// @bug Why not TangoArrayType::allocbuf(len)? Because /// I will use it in set_value(tg_ptr,...,release=true). /// Tango API makes delete[] tg_ptr instead of freebuf(tg_ptr). /// This is usually the same, but for Tango::DevStringArray the /// behaviour seems different and causes weirdtroubles.. TangoScalarType *tg_ptr; tg_ptr = new TangoScalarType[len]; // The boost extract could be used: // TangoScalarType val = boost::python::extract(elt_ptr); // instead of the code below. // the problem is that extract is considerably slower than our // convert function which only has to deal with the specific tango // data types PyObject * py_el = 0; PyObject * py_row = 0; TangoScalarType tg_scalar; long idx = 0; try { if (expectFlatSource) { for (idx = 0; idx < len; ++idx) { py_el = PySequence_ITEM(py_val, idx); if (!py_el) boost::python::throw_error_already_set(); from_py::convert(py_el, tg_scalar); tg_ptr[idx] = tg_scalar; Py_DECREF(py_el); py_el = 0; } } else { for (long y=0; y < dim_y; ++y) { py_row = PySequence_ITEM(py_val, y); if (!py_row) boost::python::throw_error_already_set(); if (!PySequence_Check(py_row)) { Tango::Except::throw_exception( "PyDs_WrongParameters", "Expecting a sequence of sequences!", fname + "()"); } for (long x=0; x < dim_x; ++x, ++idx) { py_el = PySequence_ITEM(py_row, x); if (!py_el) boost::python::throw_error_already_set(); from_py::convert(py_el, tg_scalar); tg_ptr[x + y*dim_x] = tg_scalar; Py_DECREF(py_el); py_el = 0; } Py_DECREF(py_row); py_row = 0; } } } catch(...) { Py_XDECREF(py_el); Py_XDECREF(py_row); fast_python_to_tango_buffer_deleter__(tg_ptr, idx); throw; } return tg_ptr; } template inline typename TANGO_const2scalartype(tangoArrayTypeConst)* fast_python_to_corba_buffer_sequence(PyObject* py_val, long* pdim_x, const std::string &fname, long& res_dim_x) { typedef typename TANGO_const2type(tangoArrayTypeConst) TangoArrayType; typedef typename TANGO_const2scalartype(tangoArrayTypeConst) TangoScalarType; long dim_x; Py_ssize_t len = PySequence_Size(py_val); if (pdim_x) { if (*pdim_x > len) Tango::Except::throw_exception( "PyDs_WrongParameters", "Specified dim_x is larger than the sequence size", fname + "()"); len = *pdim_x; } dim_x = static_cast(len); res_dim_x = dim_x; if (!PySequence_Check(py_val)) Tango::Except::throw_exception( "PyDs_WrongParameters", "Expecting a sequence!", fname + "()"); TangoScalarType* tg_ptr = TangoArrayType::allocbuf(static_cast(len)); // The boost extract could be used: // TangoScalarType val = boost::python::extract(elt_ptr); // instead of the code below. // the problem is that extract is considerably slower than our // convert function which only has to deal with the specific tango // data types PyObject * py_el = 0; TangoScalarType tg_scalar; long idx = 0; try { for (idx = 0; idx < len; ++idx) { py_el = PySequence_ITEM(py_val, idx); if (!py_el) boost::python::throw_error_already_set(); array_element_from_py::convert(py_el, tg_scalar); tg_ptr[idx] = tg_scalar; Py_DECREF(py_el); py_el = 0; } } catch(...) { Py_XDECREF(py_el); TangoArrayType::freebuf(tg_ptr); throw; } return tg_ptr; } template<> inline TANGO_const2type(Tango::DEV_ENCODED)* fast_python_to_tango_buffer_sequence(PyObject*, long*, long*, const std::string & fname, bool isImage, long& res_dim_x, long& res_dim_y) { TangoSys_OMemStream o; o << "DevEncoded is only supported for SCALAR attributes." << ends; Tango::Except::throw_exception( "PyDs_WrongPythonDataTypeForAttribute", o.str(), fname + "()"); return 0; } # ifndef DISABLE_PYTANGO_NUMPY # include "fast_from_py_numpy.hpp" # define fast_python_to_tango_buffer fast_python_to_tango_buffer_numpy # define fast_python_to_corba_buffer fast_python_to_corba_buffer_numpy # else # define fast_python_to_tango_buffer fast_python_to_tango_buffer_sequence # define fast_python_to_corba_buffer fast_python_to_corba_buffer_sequence # endif template inline typename TANGO_const2type(tangoArrayTypeConst)* fast_convert2array(boost::python::object o) { typedef typename TANGO_const2type(tangoArrayTypeConst) TangoArrayType; typedef typename TANGO_const2scalartype(tangoArrayTypeConst) TangoScalarType; long res_dim_x; // Last parameter false: destruction will be handled by CORBA, not by // Tango. So, when we destroy it manually later, we also have to use the // CORBA style (TangoArrayType are defines by CORBA idl) TangoScalarType* array = fast_python_to_corba_buffer(o.ptr(), 0, "insert_array", res_dim_x); try { // not a bug: res_dim_y means nothing to us, we are unidimensional // here we have max_len and currebt_len = res_dim_x return new TangoArrayType(res_dim_x, res_dim_x, array, true); } catch(...) { TangoArrayType::freebuf(array); throw; } return 0; } template<> inline TANGO_const2type(Tango::DEVVAR_LONGSTRINGARRAY)* fast_convert2array(boost::python::object py_value) { const long tangoArrayTypeConst = Tango::DEVVAR_LONGSTRINGARRAY; typedef TANGO_const2type(tangoArrayTypeConst) TangoArrayType; if (!PySequence_Check(py_value.ptr())) { raise_convert2array_DevVarLongStringArray(); } size_t size = boost::python::len(py_value); if (size != 2) { raise_convert2array_DevVarLongStringArray(); } const boost::python::object &py_lng = py_value[0], &py_str = py_value[1]; unique_pointer a_lng( fast_convert2array(py_lng)); unique_pointer a_str( fast_convert2array(py_str)); unique_pointer result(new TangoArrayType()); result->lvalue = *a_lng; result->svalue = *a_str; return result.release(); } template<> inline TANGO_const2type(Tango::DEVVAR_DOUBLESTRINGARRAY)* fast_convert2array(boost::python::object py_value) { const long tangoArrayTypeConst = Tango::DEVVAR_DOUBLESTRINGARRAY; typedef TANGO_const2type(tangoArrayTypeConst) TangoArrayType; if (!PySequence_Check(py_value.ptr())) { raise_convert2array_DevVarDoubleStringArray(); } size_t size = boost::python::len(py_value); if (size != 2) { raise_convert2array_DevVarDoubleStringArray(); } const boost::python::object &py_dbl = py_value[0], &py_str = py_value[1]; unique_pointer a_dbl( fast_convert2array(py_dbl)); unique_pointer a_str( fast_convert2array(py_str)); unique_pointer result(new TangoArrayType()); result->dvalue = *a_dbl; result->svalue = *a_str; return result.release(); } pytango-9.2.2/ext/fast_from_py_numpy.hpp000066400000000000000000000220401316324373100204370ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ // This header file is just some template functions moved apart from // attribute.cpp, and should only be included there. #pragma once #include "tango_numpy.h" #define fast_python_to_tango_buffer_fallback__() \ fast_python_to_tango_buffer_sequence( \ py_val, \ pdim_x, \ pdim_y, \ fname, \ isImage, \ res_dim_x, \ res_dim_y \ ) template inline typename TANGO_const2type(tangoScalarTypeConst)* fast_python_to_tango_buffer_numpy(PyObject* py_val, long* pdim_x, long* pdim_y, const std::string &fname, bool isImage, long& res_dim_x, long& res_dim_y) { typedef typename TANGO_const2type(tangoScalarTypeConst) TangoScalarType; static const int typenum = TANGO_const2numpy(tangoScalarTypeConst); if (!PyArray_Check(py_val)) { return fast_python_to_tango_buffer_fallback__(); } int nd = PyArray_NDIM(py_val); npy_intp* dims = PyArray_DIMS(py_val); long len = 0; // Is the array exactly what we need? I mean: The type we need and with // continuous aligned memory? const bool exact_array = ( PyArray_CHKFLAGS(py_val, NPY_C_CONTIGUOUS | NPY_ALIGNED) && (PyArray_TYPE(py_val) == typenum) ); if (isImage) { // If dimensions are manually specified (nd must be 1), I don't // know how to handle from numpy if (nd == 1) return fast_python_to_tango_buffer_fallback__(); // Check: This is an image! if (nd != 2) Tango::Except::throw_exception( "PyDs_WrongNumpyArrayDimensions", "Expecting a 2 dimensional numpy array (IMAGE attribute).", fname + "()"); // If dimensions are manually limited and it's an image, I just // know that if the limit is the real size, then I can safely // ignore it, else let the default path take care... bool dims_ok = true; if (pdim_x) dims_ok = dims_ok && (*pdim_x == dims[1]); if (pdim_y) dims_ok = dims_ok && (*pdim_y == dims[0]); if (!dims_ok) return fast_python_to_tango_buffer_fallback__(); len = static_cast(dims[0]*dims[1]); res_dim_x = static_cast(dims[1]); res_dim_y = static_cast(dims[0]); } else { // Check: This is an spectrum! if (nd != 1) Tango::Except::throw_exception( "PyDs_WrongNumpyArrayDimensions", "Expecting a 1 dimensional numpy array (SPECTRUM attribute).", fname + "()"); // If x dimension is limited then I only know how to behave // if the array is exact if (pdim_x) { // if x_dim is wrong, instead of throwing an exception I will let // fast_python_to_tgbuffer_sequence throw it: const bool dims_ok = (*pdim_x <= dims[0]); if (!exact_array || !dims_ok) return fast_python_to_tango_buffer_fallback__(); len = *pdim_x; } else len = static_cast(dims[0]); res_dim_x = len; res_dim_y = 0; } TangoScalarType *tg_data = new TangoScalarType[len]; void *vd_data = tg_data; if (exact_array) { // The array is exactly what we need, so a plain memcpy is // enough! /// @todo If it is read only we need the copy, but if the /// attribute is read/write, Tango will do the copy himself on /// the calll to set_value(...), so there's no need for us /// to make an extra one... memcpy(vd_data, PyArray_DATA(py_val), len*sizeof(TangoScalarType)); } else { // We use numpy to create a copy of the array into the continuous // memory location that we specify. PyObject* py_cont; py_cont = PyArray_SimpleNewFromData(nd, dims, typenum, vd_data); if (!py_cont) { fast_python_to_tango_buffer_deleter__(tg_data, len); boost::python::throw_error_already_set(); } if (PyArray_CopyInto( (PyArrayObject*)py_cont, (PyArrayObject*)py_val) < 0) { Py_DECREF(py_cont); fast_python_to_tango_buffer_deleter__(tg_data, len); boost::python::throw_error_already_set(); } Py_DECREF(py_cont); } return tg_data; } template<> inline TANGO_const2type(Tango::DEV_STRING)* fast_python_to_tango_buffer_numpy(PyObject* py_val, long* pdim_x, long* pdim_y, const std::string &fname, bool isImage, long& res_dim_x, long& res_dim_y) { static const long tangoScalarTypeConst = Tango::DEV_STRING; return fast_python_to_tango_buffer_fallback__(); } template<> inline TANGO_const2type(Tango::DEV_ENCODED)* fast_python_to_tango_buffer_numpy(PyObject* py_val, long* pdim_x, long* pdim_y, const std::string &fname, bool isImage, long& res_dim_x, long& res_dim_y) { static const long tangoScalarTypeConst = Tango::DEV_ENCODED; return fast_python_to_tango_buffer_fallback__(); } #define fast_python_to_corba_buffer_fallback__() \ fast_python_to_corba_buffer_sequence( \ py_val, \ pdim_x, \ fname, \ res_dim_x \ ) template inline typename TANGO_const2scalartype(tangoArrayTypeConst)* fast_python_to_corba_buffer_numpy(PyObject* py_val, long* pdim_x, const std::string &fname, long& res_dim_x) { typedef typename TANGO_const2type(tangoArrayTypeConst) TangoArrayType; typedef typename TANGO_const2scalartype(tangoArrayTypeConst) TangoScalarType; static const int typenum = TANGO_const2scalarnumpy(tangoArrayTypeConst); if (!PyArray_Check(py_val)) { return fast_python_to_corba_buffer_fallback__(); } int nd = PyArray_NDIM(py_val); npy_intp* dims = PyArray_DIMS(py_val); long len = 0; // Is the array exactly what we need? I mean: The type we need and with // continuous aligned memory? const bool exact_array = ( PyArray_CHKFLAGS(py_val, NPY_C_CONTIGUOUS | NPY_ALIGNED) && (PyArray_TYPE(py_val) == typenum) ); // Check: This is an spectrum! if (nd != 1) Tango::Except::throw_exception( "PyDs_WrongNumpyArrayDimensions", "Expecting a 1 dimensional numpy array (SPECTRUM attribute).", fname + "()"); // If x dimension is limited then I only know how to behave // if the array is exact if (pdim_x) { // if x_dim is wrong, instead of throwing an exception I will let // fast_python_to_tgbuffer_sequence throw it: const bool dims_ok = (*pdim_x <= dims[0]); if (!exact_array || !dims_ok) return fast_python_to_corba_buffer_fallback__(); len = *pdim_x; } else len = static_cast(dims[0]); res_dim_x = len; TangoScalarType *tg_data = TangoArrayType::allocbuf(len); void *vd_data = tg_data; if (exact_array) { // The array is exactly what we need, so a plain memcpy is // enough! /// @todo If it is read only we need the copy, but if the /// attribute is read/write, Tango will do the copy himself on /// the calll to set_value(...), so there's no need for us /// to make an extra one... memcpy(vd_data, PyArray_DATA(py_val), len*sizeof(TangoScalarType)); } else { // We use numpy to create a copy of the array into the continuous // memory location that we specify. PyObject* py_cont; py_cont = PyArray_SimpleNewFromData(nd, dims, typenum, vd_data); if (!py_cont) { TangoArrayType::freebuf(tg_data); boost::python::throw_error_already_set(); } if (PyArray_CopyInto( (PyArrayObject*)py_cont, (PyArrayObject*)py_val) < 0) { Py_DECREF(py_cont); TangoArrayType::freebuf(tg_data); boost::python::throw_error_already_set(); } Py_DECREF(py_cont); } return tg_data; } template<> inline TANGO_const2scalartype(Tango::DEVVAR_STRINGARRAY)* fast_python_to_corba_buffer_numpy(PyObject* py_val, long* pdim_x, const std::string &fname, long& res_dim_x) { static const long tangoArrayTypeConst = Tango::DEVVAR_STRINGARRAY; return fast_python_to_corba_buffer_fallback__(); } pytango-9.2.2/ext/from_py.cpp000066400000000000000000000407411316324373100161750ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include "from_py.h" using namespace boost::python; char* obj_to_new_char(PyObject* obj_ptr) { Tango::DevString ret = NULL; if(PyUnicode_Check(obj_ptr)) { PyObject* obj_bytes_ptr = PyUnicode_AsLatin1String(obj_ptr); ret = CORBA::string_dup(PyBytes_AsString(obj_bytes_ptr)); Py_DECREF(obj_bytes_ptr); } else { ret = CORBA::string_dup(PyBytes_AsString(obj_ptr)); } return ret; } char* obj_to_new_char(bopy::object obj) { return obj_to_new_char(obj.ptr()); } void obj_to_string(PyObject* obj_ptr, std::string& result) { if(PyUnicode_Check(obj_ptr)) { PyObject* obj_bytes_ptr = PyUnicode_AsLatin1String(obj_ptr); result = PyBytes_AsString(obj_bytes_ptr); Py_DECREF(obj_bytes_ptr); } else { result = PyBytes_AsString(obj_ptr); } } void obj_to_string(bopy::object obj, std::string& result) { return obj_to_string(obj.ptr(), result); } /// @bug Not a bug per se, but you should keep in mind: It returns a new /// string, so if you pass it to Tango with a release flag there will be /// no problems, but if you have to use it yourself then you must remember /// to delete[] it! Tango::DevString PyString_AsCorbaString(PyObject* obj_ptr) { return obj_to_new_char(obj_ptr); } void convert2array(const boost::python::object &py_value, Tango::DevVarCharArray & result) { PyObject *py_value_ptr = py_value.ptr(); if(PySequence_Check(py_value_ptr) == 0) { raise_(PyExc_TypeError, param_must_be_seq); } CORBA::ULong size = static_cast(boost::python::len(py_value)); result.length(size); if (PyBytes_Check(py_value_ptr)) { char *ch = PyBytes_AS_STRING(py_value_ptr); for (CORBA::ULong i=0; i < size; ++i) { result[i] = ch[i]; } } else { for (CORBA::ULong i=0; i < size; ++i) { unsigned char *ch = boost::python::extract(py_value[i]); result[i] = ch[0]; } } } void convert2array(const object &py_value, StdStringVector & result) { PyObject *py_value_ptr = py_value.ptr(); if(PySequence_Check(py_value_ptr) == 0) { raise_(PyExc_TypeError, param_must_be_seq); } if (PyBytes_Check(py_value_ptr)) { result.push_back(PyBytes_AS_STRING(py_value_ptr)); } else if(PyUnicode_Check(py_value_ptr)) { PyObject* py_bytes_value_ptr = PyUnicode_AsLatin1String(py_value_ptr); result.push_back(PyBytes_AS_STRING(py_bytes_value_ptr)); Py_DECREF(py_bytes_value_ptr); } else { size_t size = boost::python::len(py_value); result.reserve(size); for (size_t i=0; i < size; ++i) { char *vi = boost::python::extract(py_value[i]); result.push_back(vi); } } } void convert2array(const object &py_value, Tango::DevVarStringArray & result) { PyObject *py_value_ptr = py_value.ptr(); if(PySequence_Check(py_value_ptr) == 0) { raise_(PyExc_TypeError, param_must_be_seq); } if (PyBytes_Check(py_value_ptr)) { result.length(1); result[0] = CORBA::string_dup(PyBytes_AS_STRING(py_value_ptr)); } else if(PyUnicode_Check(py_value_ptr)) { PyObject* py_bytes_value_ptr = PyUnicode_AsLatin1String(py_value_ptr); result[0] = CORBA::string_dup(PyBytes_AS_STRING(py_bytes_value_ptr)); Py_DECREF(py_bytes_value_ptr); } else { CORBA::ULong size = static_cast(boost::python::len(py_value)); result.length(size); for (CORBA::ULong i=0; i < size; ++i) { result[i] = CORBA::string_dup(boost::python::extract(py_value[i])); } } } void convert2array(const boost::python::object &py_value, Tango::DevVarDoubleStringArray & result) { if (!PySequence_Check(py_value.ptr())) { raise_convert2array_DevVarDoubleStringArray(); } CORBA::ULong size = static_cast(boost::python::len(py_value)); if (size != 2) { raise_convert2array_DevVarDoubleStringArray(); } const boost::python::object &py_double = py_value[0], &py_str = py_value[1]; convert2array(py_double, result.dvalue); convert2array(py_str, result.svalue); } void convert2array(const boost::python::object &py_value, Tango::DevVarLongStringArray & result) { if (!PySequence_Check(py_value.ptr())) { raise_convert2array_DevVarLongStringArray(); } CORBA::ULong size = static_cast(boost::python::len(py_value)); if (size != 2) { raise_convert2array_DevVarLongStringArray(); } const boost::python::object py_long = py_value[0], py_str = py_value[1]; convert2array(py_long, result.lvalue); convert2array(py_str, result.svalue); } void from_py_object(bopy::object &py_obj, Tango::AttributeAlarm &attr_alarm) { attr_alarm.min_alarm = obj_to_new_char(py_obj.attr("min_alarm")); attr_alarm.max_alarm = obj_to_new_char(py_obj.attr("max_alarm")); attr_alarm.min_warning = obj_to_new_char(py_obj.attr("min_warning")); attr_alarm.max_warning = obj_to_new_char(py_obj.attr("max_warning")); attr_alarm.delta_t = obj_to_new_char(py_obj.attr("delta_t")); attr_alarm.delta_val = obj_to_new_char(py_obj.attr("delta_val")); convert2array(py_obj.attr("extensions"), attr_alarm.extensions); } void from_py_object(object &py_obj, Tango::ChangeEventProp &change_evt_prop) { change_evt_prop.rel_change = obj_to_new_char(py_obj.attr("rel_change")); change_evt_prop.abs_change = obj_to_new_char(py_obj.attr("abs_change")); convert2array(py_obj.attr("extensions"), change_evt_prop.extensions); } void from_py_object(object &py_obj, Tango::PeriodicEventProp &periodic_evt_prop) { periodic_evt_prop.period = obj_to_new_char(py_obj.attr("period")); convert2array(py_obj.attr("extensions"), periodic_evt_prop.extensions); } void from_py_object(object &py_obj, Tango::ArchiveEventProp &archive_evt_prop) { archive_evt_prop.rel_change = obj_to_new_char(py_obj.attr("rel_change")); archive_evt_prop.abs_change = obj_to_new_char(py_obj.attr("abs_change")); archive_evt_prop.period = obj_to_new_char(py_obj.attr("period")); convert2array(py_obj.attr("extensions"), archive_evt_prop.extensions); } void from_py_object(object &py_obj, Tango::EventProperties &evt_props) { object py_ch_event = py_obj.attr("ch_event"); object py_per_event = py_obj.attr("per_event"); object py_arch_event = py_obj.attr("arch_event"); from_py_object(py_ch_event, evt_props.ch_event); from_py_object(py_per_event, evt_props.per_event); from_py_object(py_arch_event, evt_props.arch_event); } void from_py_object(object &py_obj, Tango::AttributeConfig &attr_conf) { attr_conf.name = obj_to_new_char(py_obj.attr("name")); attr_conf.writable = extract(py_obj.attr("writable")); attr_conf.data_format = extract(py_obj.attr("data_format")); attr_conf.data_type = extract(py_obj.attr("data_type")); attr_conf.max_dim_x = extract(py_obj.attr("max_dim_x")); attr_conf.max_dim_y = extract(py_obj.attr("max_dim_y")); attr_conf.description = obj_to_new_char(py_obj.attr("description")); attr_conf.label = obj_to_new_char(py_obj.attr("label")); attr_conf.unit = obj_to_new_char(py_obj.attr("unit")); attr_conf.standard_unit = obj_to_new_char(py_obj.attr("standard_unit")); attr_conf.display_unit = obj_to_new_char(py_obj.attr("display_unit")); attr_conf.format = obj_to_new_char(py_obj.attr("format")); attr_conf.min_value = obj_to_new_char(py_obj.attr("min_value")); attr_conf.max_value = obj_to_new_char(py_obj.attr("max_value")); attr_conf.min_alarm = obj_to_new_char(py_obj.attr("min_alarm")); attr_conf.max_alarm = obj_to_new_char(py_obj.attr("max_alarm")); attr_conf.writable_attr_name = obj_to_new_char(py_obj.attr("writable_attr_name")); convert2array(py_obj.attr("extensions"), attr_conf.extensions); } void from_py_object(object &py_obj, Tango::AttributeConfig_2 &attr_conf) { attr_conf.name = obj_to_new_char(py_obj.attr("name")); attr_conf.writable = extract(py_obj.attr("writable")); attr_conf.data_format = extract(py_obj.attr("data_format")); attr_conf.data_type = extract(py_obj.attr("data_type")); attr_conf.max_dim_x = extract(py_obj.attr("max_dim_x")); attr_conf.max_dim_y = extract(py_obj.attr("max_dim_y")); attr_conf.description = obj_to_new_char(py_obj.attr("description")); attr_conf.label = obj_to_new_char(py_obj.attr("label")); attr_conf.unit = obj_to_new_char(py_obj.attr("unit")); attr_conf.standard_unit = obj_to_new_char(py_obj.attr("standard_unit")); attr_conf.display_unit = obj_to_new_char(py_obj.attr("display_unit")); attr_conf.format = obj_to_new_char(py_obj.attr("format")); attr_conf.min_value = obj_to_new_char(py_obj.attr("min_value")); attr_conf.max_value = obj_to_new_char(py_obj.attr("max_value")); attr_conf.min_alarm = obj_to_new_char(py_obj.attr("min_alarm")); attr_conf.max_alarm = obj_to_new_char(py_obj.attr("max_alarm")); attr_conf.writable_attr_name = obj_to_new_char(py_obj.attr("writable_attr_name")); attr_conf.level = extract(py_obj.attr("level")); convert2array(py_obj.attr("extensions"), attr_conf.extensions); } void from_py_object(object &py_obj, Tango::AttributeConfig_3 &attr_conf) { attr_conf.name = obj_to_new_char(py_obj.attr("name")); attr_conf.writable = extract(py_obj.attr("writable")); attr_conf.data_format = extract(py_obj.attr("data_format")); attr_conf.data_type = extract(py_obj.attr("data_type")); attr_conf.max_dim_x = extract(py_obj.attr("max_dim_x")); attr_conf.max_dim_y = extract(py_obj.attr("max_dim_y")); attr_conf.description = obj_to_new_char(py_obj.attr("description")); attr_conf.label = obj_to_new_char(py_obj.attr("label")); attr_conf.unit = obj_to_new_char(py_obj.attr("unit")); attr_conf.standard_unit = obj_to_new_char(py_obj.attr("standard_unit")); attr_conf.display_unit = obj_to_new_char(py_obj.attr("display_unit")); attr_conf.format = obj_to_new_char(py_obj.attr("format")); attr_conf.min_value = obj_to_new_char(py_obj.attr("min_value")); attr_conf.max_value = obj_to_new_char(py_obj.attr("max_value")); attr_conf.writable_attr_name = obj_to_new_char(py_obj.attr("writable_attr_name")); attr_conf.level = extract(py_obj.attr("level")); object py_att_alarm = py_obj.attr("att_alarm"); object py_event_prop = py_obj.attr("event_prop"); from_py_object(py_att_alarm, attr_conf.att_alarm); from_py_object(py_event_prop, attr_conf.event_prop); convert2array(py_obj.attr("extensions"), attr_conf.extensions); convert2array(py_obj.attr("sys_extensions"), attr_conf.sys_extensions); } void from_py_object(object &py_obj, Tango::AttributeConfig_5 &attr_conf) { attr_conf.name = obj_to_new_char(py_obj.attr("name")); attr_conf.writable = extract(py_obj.attr("writable")); attr_conf.data_format = extract(py_obj.attr("data_format")); attr_conf.data_type = extract(py_obj.attr("data_type")); attr_conf.memorized = extract(py_obj.attr("memorized")); attr_conf.mem_init = extract(py_obj.attr("mem_init")); attr_conf.max_dim_x = extract(py_obj.attr("max_dim_x")); attr_conf.max_dim_y = extract(py_obj.attr("max_dim_y")); attr_conf.description = obj_to_new_char(py_obj.attr("description")); attr_conf.label = obj_to_new_char(py_obj.attr("label")); attr_conf.unit = obj_to_new_char(py_obj.attr("unit")); attr_conf.standard_unit = obj_to_new_char(py_obj.attr("standard_unit")); attr_conf.display_unit = obj_to_new_char(py_obj.attr("display_unit")); attr_conf.format = obj_to_new_char(py_obj.attr("format")); attr_conf.min_value = obj_to_new_char(py_obj.attr("min_value")); attr_conf.max_value = obj_to_new_char(py_obj.attr("max_value")); attr_conf.writable_attr_name = obj_to_new_char(py_obj.attr("writable_attr_name")); attr_conf.level = extract(py_obj.attr("level")); attr_conf.root_attr_name = obj_to_new_char(py_obj.attr("root_attr_name")); convert2array(py_obj.attr("enum_labels"), attr_conf.enum_labels); object py_att_alarm = py_obj.attr("att_alarm"); object py_event_prop = py_obj.attr("event_prop"); from_py_object(py_att_alarm, attr_conf.att_alarm); from_py_object(py_event_prop, attr_conf.event_prop); convert2array(py_obj.attr("extensions"), attr_conf.extensions); convert2array(py_obj.attr("sys_extensions"), attr_conf.sys_extensions); } void from_py_object(object &py_obj, Tango::AttributeConfigList &attr_conf_list) { PyObject* py_obj_ptr = py_obj.ptr(); if (!PySequence_Check(py_obj_ptr)) { attr_conf_list.length(1); from_py_object(py_obj, attr_conf_list[0]); return; } CORBA::ULong size = static_cast(boost::python::len(py_obj)); attr_conf_list.length(size); for (CORBA::ULong i=0; i < size; ++i) { object tmp = py_obj[i]; from_py_object(tmp, attr_conf_list[i]); } } void from_py_object(object &py_obj, Tango::AttributeConfigList_2 &attr_conf_list) { PyObject* py_obj_ptr = py_obj.ptr(); if (!PySequence_Check(py_obj_ptr)) { attr_conf_list.length(1); from_py_object(py_obj, attr_conf_list[0]); return; } CORBA::ULong size = static_cast(boost::python::len(py_obj)); attr_conf_list.length(size); for (CORBA::ULong i=0; i < size; ++i) { object tmp = py_obj[i]; from_py_object(tmp, attr_conf_list[i]); } } void from_py_object(object &py_obj, Tango::AttributeConfigList_3 &attr_conf_list) { PyObject* py_obj_ptr = py_obj.ptr(); if (!PySequence_Check(py_obj_ptr)) { attr_conf_list.length(1); from_py_object(py_obj, attr_conf_list[0]); return; } CORBA::ULong size = static_cast(boost::python::len(py_obj)); attr_conf_list.length(size); for (CORBA::ULong i=0; i < size; ++i) { object tmp = py_obj[i]; from_py_object(tmp, attr_conf_list[i]); } } void from_py_object(object &py_obj, Tango::AttributeConfigList_5 &attr_conf_list) { PyObject* py_obj_ptr = py_obj.ptr(); if (!PySequence_Check(py_obj_ptr)) { attr_conf_list.length(1); from_py_object(py_obj, attr_conf_list[0]); return; } CORBA::ULong size = static_cast(boost::python::len(py_obj)); attr_conf_list.length(size); for (CORBA::ULong i=0; i < size; ++i) { object tmp = py_obj[i]; from_py_object(tmp, attr_conf_list[i]); } } void from_py_object(object &py_obj, Tango::PipeConfig &pipe_conf) { pipe_conf.name = obj_to_new_char(py_obj.attr("name")); pipe_conf.description = obj_to_new_char(py_obj.attr("description")); pipe_conf.label = obj_to_new_char(py_obj.attr("label")); pipe_conf.level = extract(py_obj.attr("level")); pipe_conf.writable = extract(py_obj.attr("writable")); convert2array(py_obj.attr("extensions"), pipe_conf.extensions); } void from_py_object(object &py_obj, Tango::PipeConfigList &pipe_conf_list) { PyObject* py_obj_ptr = py_obj.ptr(); if (!PySequence_Check(py_obj_ptr)) { pipe_conf_list.length(1); from_py_object(py_obj, pipe_conf_list[0]); return; } CORBA::ULong size = static_cast(boost::python::len(py_obj)); pipe_conf_list.length(size); for (CORBA::ULong i=0; i < size; ++i) { object tmp = py_obj[i]; from_py_object(tmp, pipe_conf_list[i]); } } pytango-9.2.2/ext/from_py.h000066400000000000000000000640371316324373100156460ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #pragma once #include #include #if BOOST_VERSION < 103400 #include #endif #include #include "defs.h" #include "tgutils.h" #include "pyutils.h" #include "tango_numpy.h" #include "exception.h" extern const char *param_must_be_seq; char* obj_to_new_char(PyObject* obj_ptr); char* obj_to_new_char(bopy::object obj); void obj_to_string(PyObject* obj_ptr, std::string& result); void obj_to_string(bopy::object obj, std::string& result); /// @bug Not a bug per se, but you should keep in mind: It returns a new /// string, so if you pass it to Tango with a release flag there will be /// no problems, but if you have to use it yourself then you must remember /// to delete[] it! Tango::DevString PyString_AsCorbaString(PyObject* obj_ptr); /** * Converter from python sequence of strings to a std::vector * * @param[in] py_value python sequence object or a single string * @param[out] result std string vector to be filled */ void convert2array(const bopy::object &py_value, StdStringVector & result); /** * Converter from python sequence of characters to a Tango::DevVarCharArray * * @param[in] py_value python sequence object or a single string * @param[out] result Tango char array to be filled */ void convert2array(const bopy::object &py_value, Tango::DevVarCharArray & result); /** * Converter from python sequence to a Tango CORBA sequence * * @param[in] py_value python sequence object * @param[out] result CORBA sequence to be filled */ template void convert2array(const bopy::object &py_value, _CORBA_Sequence & result) { Py_ssize_t size = bopy::len(py_value); result.length(static_cast(size)); for (Py_ssize_t i=0; i < size; ++i) { TangoElementType ch = bopy::extract(py_value[i]); result[static_cast(i)] = ch; } } /** * Converter from python sequence of strings to a Tango DevVarStringArray * * @param[in] py_value python sequence object or a single string * @param[out] result Tango string array to be filled */ void convert2array(const bopy::object &py_value, Tango::DevVarStringArray & result); inline void raise_convert2array_DevVarDoubleStringArray() { Tango::Except::throw_exception( "PyDs_WrongPythonDataTypeForDoubleStringArray", "Converter from python object to DevVarDoubleStringArray needs a python sequence, sequence>", "convert2array()"); } /** * Converter from python sequence, sequence> to a Tango DevVarDoubleStringArray * * @param[in] py_value python sequence object * @param[out] result Tango array to be filled */ void convert2array(const bopy::object &py_value, Tango::DevVarDoubleStringArray & result); inline void raise_convert2array_DevVarLongStringArray() { Tango::Except::throw_exception( "PyDs_WrongPythonDataTypeForLongStringArray", "Converter from python object to DevVarLongStringArray needs a python sequence, sequence>", "convert2array()"); } /** * Converter from python sequence, sequence> to a Tango DevVarLongStringArray * * @param[in] py_value python sequence object * @param[out] result Tango array to be filled */ void convert2array(const bopy::object &py_value, Tango::DevVarLongStringArray & result); /** * Convert a python sequence into a C++ container * The C++ container must have the push_back method */ template struct from_sequence { static inline void convert(bopy::object seq, ContainerType& a) { typedef typename ContainerType::value_type T; PyObject *seq_ptr = seq.ptr(); Py_ssize_t len = PySequence_Length(seq_ptr); for(Py_ssize_t i = 0; i < len; ++i) { PyObject *o_ptr = PySequence_GetItem(seq_ptr, i); T s = bopy::extract(o_ptr); a.push_back(s); bopy::decref(o_ptr); } } static inline void convert(bopy::object seq, Tango::DbData& a) { PyObject *seq_ptr = seq.ptr(); Py_ssize_t len = PySequence_Length(seq_ptr); for(Py_ssize_t i = 0; i < len; ++i) { PyObject *o_ptr = PySequence_GetItem(seq_ptr, i); if (PyBytes_Check(o_ptr)) { a.push_back(Tango::DbDatum(PyBytes_AS_STRING(o_ptr))); } else if(PyUnicode_Check(o_ptr)) { PyObject* o_bytes_ptr = PyUnicode_AsLatin1String(o_ptr); a.push_back(Tango::DbDatum(PyBytes_AS_STRING(o_bytes_ptr))); Py_DECREF(o_bytes_ptr); } } } /** * Convert a python dictionary to a Tango::DbData. The dictionary keys must * be strings representing the DbDatum name. The dictionary value can be * be one of the following: * - Tango::DbDatum : in this case the key is not used, and the * item inserted in DbData will be a copy of the value * - sequence : it is translated into an array of strings and * the DbDatum inserted in DbData will have name as the dict key and value * the sequence of strings * - python object : its string representation is used * as a DbDatum to be inserted * * @param[in] d the python dictionary to be translated * @param[out] db_data the array of DbDatum to be filled */ static inline void convert(bopy::dict d, Tango::DbData& db_data) { bopy::object it = d.iteritems(); Py_ssize_t len = bopy::len(d); for(Py_ssize_t i = 0 ; i < len; ++i) { bopy::tuple pair = (bopy::tuple)it.attr("next")(); bopy::object key = pair[0]; bopy::object value = pair[1]; bopy::extract ext(value); if(ext.check()) { db_data.push_back(ext()); continue; } char const* key_str = bopy::extract(key); Tango::DbDatum db_datum(key_str); bopy::extract value_str(value); if(value_str.check()) { db_datum.value_string.push_back(value_str()); } else { if(PySequence_Check(value.ptr())) { from_sequence::convert(value, db_datum.value_string); } else { bopy::object str_value = value.attr("__str__")(); bopy::extract str_value_str(str_value); db_datum.value_string.push_back(str_value_str()); } } db_data.push_back(db_datum); } } }; extern const char *param_must_be_seq; /// This class is useful when you need a sequence like C++ type for /// a function argument, and you have exported this type to python. /// This will try to convert the parameter directly to the C++ object /// (valid if the argument passed was an instance of the exported type). /// If it fails, it will use from_sequence::convert to get a copy /// of the sequence in the expected format. /// So for example we can get a function that accepts an object of /// type StdStringVector, or a list of strings, or a tuple of strings... template class CSequenceFromPython { SequenceT* m_seq; bool m_own; public: CSequenceFromPython(bopy::object &py_obj) { bopy::extract ext(py_obj); if (ext.check()) { m_seq = ext(); m_own = false; } else { if (PySequence_Check(py_obj.ptr()) == 0) raise_(PyExc_TypeError, param_must_be_seq); if (PyUnicode_Check(py_obj.ptr()) != 0) raise_(PyExc_TypeError, param_must_be_seq); if (PyUnicode_Check(py_obj.ptr()) != 0) raise_(PyExc_TypeError, param_must_be_seq); m_own = true; //m_seq = new SequenceT(PySequence_Length(Py_obj.ptr())); m_seq = new SequenceT(); unique_pointer guard(m_seq); from_sequence::convert(py_obj, *m_seq); guard.release(); } } ~CSequenceFromPython() { if (m_own) delete m_seq; } SequenceT & operator*() { return *m_seq; } const SequenceT & operator*() const { return *m_seq; } }; void from_py_object(bopy::object &, Tango::AttributeAlarm &); void from_py_object(bopy::object &, Tango::ChangeEventProp &); void from_py_object(bopy::object &, Tango::PeriodicEventProp &); void from_py_object(bopy::object &, Tango::ArchiveEventProp &); void from_py_object(bopy::object &, Tango::EventProperties &); void from_py_object(bopy::object &, Tango::AttributeConfig &); void from_py_object(bopy::object &, Tango::AttributeConfig_2 &); void from_py_object(bopy::object &, Tango::AttributeConfig_3 &); void from_py_object(bopy::object &, Tango::AttributeConfig_5 &); template void from_py_object(bopy::object &py_obj, Tango::MultiAttrProp &multi_attr_prop) { multi_attr_prop.label = bopy::extract(bopy::str(py_obj.attr("label"))); multi_attr_prop.description = bopy::extract(bopy::str(py_obj.attr("description"))); multi_attr_prop.unit = bopy::extract(bopy::str(py_obj.attr("unit"))); multi_attr_prop.standard_unit = bopy::extract(bopy::str(py_obj.attr("standard_unit"))); multi_attr_prop.display_unit = bopy::extract(bopy::str(py_obj.attr("display_unit"))); multi_attr_prop.format = bopy::extract(bopy::str(py_obj.attr("format"))); bopy::extract min_value(py_obj.attr("min_value")); if(min_value.check()) multi_attr_prop.min_value = min_value(); else multi_attr_prop.min_value = bopy::extract(py_obj.attr("min_value")); bopy::extract max_value(py_obj.attr("max_value")); if(max_value.check()) multi_attr_prop.max_value = max_value(); else multi_attr_prop.max_value = bopy::extract(py_obj.attr("max_value")); bopy::extract min_alarm(py_obj.attr("min_alarm")); if(min_alarm.check()) multi_attr_prop.min_alarm = min_alarm(); else multi_attr_prop.min_alarm = bopy::extract(py_obj.attr("min_alarm")); bopy::extract max_alarm(py_obj.attr("max_alarm")); if(max_alarm.check()) multi_attr_prop.max_alarm = max_alarm(); else multi_attr_prop.max_alarm = bopy::extract(py_obj.attr("max_alarm")); bopy::extract min_warning(py_obj.attr("min_warning")); if(min_warning.check()) multi_attr_prop.min_warning = min_warning(); else multi_attr_prop.min_warning = bopy::extract(py_obj.attr("min_warning")); bopy::extract max_warning(py_obj.attr("max_warning")); if(max_warning.check()) multi_attr_prop.max_warning = max_warning(); else multi_attr_prop.max_warning = bopy::extract(py_obj.attr("max_warning")); bopy::extract delta_t(py_obj.attr("delta_t")); if(delta_t.check()) multi_attr_prop.delta_t = delta_t(); else multi_attr_prop.delta_t = bopy::extract(py_obj.attr("delta_t")); // Property type is Tango::DevLong! bopy::extract delta_val(py_obj.attr("delta_val")); if(delta_val.check()) multi_attr_prop.delta_val = delta_val(); else multi_attr_prop.delta_val = bopy::extract(py_obj.attr("delta_val")); bopy::extract event_period(py_obj.attr("event_period")); if(event_period.check()) multi_attr_prop.event_period = event_period(); else multi_attr_prop.event_period = bopy::extract(py_obj.attr("event_period")); // Property type is Tango::DevLong! bopy::extract archive_period(py_obj.attr("archive_period")); if(archive_period.check()) multi_attr_prop.archive_period = archive_period(); else multi_attr_prop.archive_period = bopy::extract(py_obj.attr("archive_period")); // Property type is Tango::DevLong! bopy::extract rel_change(py_obj.attr("rel_change")); if(rel_change.check()) multi_attr_prop.rel_change = rel_change(); else { bopy::object prop_py_obj = bopy::object(py_obj.attr("rel_change")); if(PySequence_Check(prop_py_obj.ptr())) { vector change_vec; for(long i = 0; i < bopy::len(prop_py_obj); i++) change_vec.push_back(bopy::extract(prop_py_obj[i])); multi_attr_prop.rel_change = change_vec; } else multi_attr_prop.rel_change = bopy::extract(py_obj.attr("rel_change")); // Property type is Tango::DevDouble! } bopy::extract abs_change(py_obj.attr("abs_change")); if(abs_change.check()) multi_attr_prop.abs_change = abs_change(); else { bopy::object prop_py_obj = bopy::object(py_obj.attr("abs_change")); if(PySequence_Check(prop_py_obj.ptr())) { vector change_vec; for(long i = 0; i < bopy::len(prop_py_obj); i++) change_vec.push_back(bopy::extract(prop_py_obj[i])); multi_attr_prop.abs_change = change_vec; } else multi_attr_prop.abs_change = bopy::extract(py_obj.attr("abs_change")); // Property type is Tango::DevDouble! } bopy::extract archive_rel_change(py_obj.attr("archive_rel_change")); if(archive_rel_change.check()) multi_attr_prop.archive_rel_change = archive_rel_change(); else { bopy::object prop_py_obj = bopy::object(py_obj.attr("archive_rel_change")); if(PySequence_Check(prop_py_obj.ptr())) { vector change_vec; for(long i = 0; i < bopy::len(prop_py_obj); i++) change_vec.push_back(bopy::extract(prop_py_obj[i])); multi_attr_prop.archive_rel_change = change_vec; } else multi_attr_prop.archive_rel_change = bopy::extract(py_obj.attr("archive_rel_change")); // Property type is Tango::DevDouble! } bopy::extract archive_abs_change(py_obj.attr("archive_abs_change")); if(archive_abs_change.check()) multi_attr_prop.archive_abs_change = archive_abs_change(); else { bopy::object prop_py_obj = bopy::object(py_obj.attr("archive_abs_change")); if(PySequence_Check(prop_py_obj.ptr())) { vector change_vec; for(long i = 0; i < bopy::len(prop_py_obj); i++) change_vec.push_back(bopy::extract(prop_py_obj[i])); multi_attr_prop.archive_abs_change = change_vec; } else multi_attr_prop.archive_abs_change = bopy::extract(py_obj.attr("archive_abs_change")); // Property type is Tango::DevDouble! } } template<> inline void from_py_object(bopy::object &py_obj, Tango::MultiAttrProp &multi_attr_prop) { multi_attr_prop.label = bopy::extract(bopy::str(py_obj.attr("label"))); multi_attr_prop.description = bopy::extract(bopy::str(py_obj.attr("description"))); multi_attr_prop.unit = bopy::extract(bopy::str(py_obj.attr("unit"))); multi_attr_prop.standard_unit = bopy::extract(bopy::str(py_obj.attr("standard_unit"))); multi_attr_prop.display_unit = bopy::extract(bopy::str(py_obj.attr("display_unit"))); multi_attr_prop.format = bopy::extract(bopy::str(py_obj.attr("format"))); bopy::extract min_value(py_obj.attr("min_value")); if(min_value.check()) multi_attr_prop.min_value = min_value(); else multi_attr_prop.min_value = bopy::extract(py_obj.attr("min_value")); bopy::extract max_value(py_obj.attr("max_value")); if(max_value.check()) multi_attr_prop.max_value = max_value(); else multi_attr_prop.max_value = bopy::extract(py_obj.attr("max_value")); bopy::extract min_alarm(py_obj.attr("min_alarm")); if(min_alarm.check()) multi_attr_prop.min_alarm = min_alarm(); else multi_attr_prop.min_alarm = bopy::extract(py_obj.attr("min_alarm")); bopy::extract max_alarm(py_obj.attr("max_alarm")); if(max_alarm.check()) multi_attr_prop.max_alarm = max_alarm(); else multi_attr_prop.max_alarm = bopy::extract(py_obj.attr("max_alarm")); bopy::extract min_warning(py_obj.attr("min_warning")); if(min_warning.check()) multi_attr_prop.min_warning = min_warning(); else multi_attr_prop.min_warning = bopy::extract(py_obj.attr("min_warning")); bopy::extract max_warning(py_obj.attr("max_warning")); if(max_warning.check()) multi_attr_prop.max_warning = max_warning(); else multi_attr_prop.max_warning = bopy::extract(py_obj.attr("max_warning")); bopy::extract delta_t(py_obj.attr("delta_t")); if(delta_t.check()) multi_attr_prop.delta_t = delta_t(); else multi_attr_prop.delta_t = bopy::extract(py_obj.attr("delta_t")); // Property type is Tango::DevLong! bopy::extract delta_val(py_obj.attr("delta_val")); if(delta_val.check()) multi_attr_prop.delta_val = delta_val(); else multi_attr_prop.delta_val = bopy::extract(py_obj.attr("delta_val")); bopy::extract event_period(py_obj.attr("event_period")); if(event_period.check()) multi_attr_prop.event_period = event_period(); else multi_attr_prop.event_period = bopy::extract(py_obj.attr("event_period")); // Property type is Tango::DevLong! bopy::extract archive_period(py_obj.attr("archive_period")); if(archive_period.check()) multi_attr_prop.archive_period = archive_period(); else multi_attr_prop.archive_period = bopy::extract(py_obj.attr("archive_period")); // Property type is Tango::DevLong! bopy::extract rel_change(py_obj.attr("rel_change")); if(rel_change.check()) multi_attr_prop.rel_change = rel_change(); else { bopy::object prop_py_obj = bopy::object(py_obj.attr("rel_change")); if(PySequence_Check(prop_py_obj.ptr())) { vector change_vec; for(long i = 0; i < bopy::len(prop_py_obj); i++) change_vec.push_back(bopy::extract(prop_py_obj[i])); multi_attr_prop.rel_change = change_vec; } else multi_attr_prop.rel_change = bopy::extract(py_obj.attr("rel_change")); // Property type is Tango::DevDouble! } bopy::extract abs_change(py_obj.attr("abs_change")); if(abs_change.check()) multi_attr_prop.abs_change = abs_change(); else { bopy::object prop_py_obj = bopy::object(py_obj.attr("abs_change")); if(PySequence_Check(prop_py_obj.ptr())) { vector change_vec; for(long i = 0; i < bopy::len(prop_py_obj); i++) change_vec.push_back(bopy::extract(prop_py_obj[i])); multi_attr_prop.abs_change = change_vec; } else multi_attr_prop.abs_change = bopy::extract(py_obj.attr("abs_change")); // Property type is Tango::DevDouble! } bopy::extract archive_rel_change(py_obj.attr("archive_rel_change")); if(archive_rel_change.check()) multi_attr_prop.archive_rel_change = archive_rel_change(); else { bopy::object prop_py_obj = bopy::object(py_obj.attr("archive_rel_change")); if(PySequence_Check(prop_py_obj.ptr())) { vector change_vec; for(long i = 0; i < bopy::len(prop_py_obj); i++) change_vec.push_back(bopy::extract(prop_py_obj[i])); multi_attr_prop.archive_rel_change = change_vec; } else multi_attr_prop.archive_rel_change = bopy::extract(py_obj.attr("archive_rel_change")); // Property type is Tango::DevDouble! } bopy::extract archive_abs_change(py_obj.attr("archive_abs_change")); if(archive_abs_change.check()) multi_attr_prop.archive_abs_change = archive_abs_change(); else { bopy::object prop_py_obj = bopy::object(py_obj.attr("archive_abs_change")); if(PySequence_Check(prop_py_obj.ptr())) { vector change_vec; for(long i = 0; i < bopy::len(prop_py_obj); i++) change_vec.push_back(bopy::extract(prop_py_obj[i])); multi_attr_prop.archive_abs_change = change_vec; } else multi_attr_prop.archive_abs_change = bopy::extract(py_obj.attr("archive_abs_change")); // Property type is Tango::DevDouble! } } template<> inline void from_py_object(bopy::object &py_obj, Tango::MultiAttrProp &multi_attr_prop) { string empty_str(""); multi_attr_prop.label = bopy::extract(bopy::str(py_obj.attr("label"))); multi_attr_prop.description = bopy::extract(bopy::str(py_obj.attr("description"))); multi_attr_prop.unit = bopy::extract(bopy::str(py_obj.attr("unit"))); multi_attr_prop.standard_unit = bopy::extract(bopy::str(py_obj.attr("standard_unit"))); multi_attr_prop.display_unit = bopy::extract(bopy::str(py_obj.attr("display_unit"))); multi_attr_prop.format = bopy::extract(bopy::str(py_obj.attr("format"))); bopy::extract min_value(py_obj.attr("min_value")); if(min_value.check()) multi_attr_prop.min_value = min_value(); else multi_attr_prop.min_value = empty_str; bopy::extract max_value(py_obj.attr("max_value")); if(max_value.check()) multi_attr_prop.max_value = max_value(); else multi_attr_prop.max_value = empty_str; bopy::extract min_alarm(py_obj.attr("min_alarm")); if(min_alarm.check()) multi_attr_prop.min_alarm = min_alarm(); else multi_attr_prop.min_alarm = empty_str; bopy::extract max_alarm(py_obj.attr("max_alarm")); if(max_alarm.check()) multi_attr_prop.max_alarm = max_alarm(); else multi_attr_prop.max_alarm = empty_str; bopy::extract min_warning(py_obj.attr("min_warning")); if(min_warning.check()) multi_attr_prop.min_warning = min_warning(); else multi_attr_prop.min_warning = empty_str; bopy::extract max_warning(py_obj.attr("max_warning")); if(max_warning.check()) multi_attr_prop.max_warning = max_warning(); else multi_attr_prop.max_warning = empty_str; bopy::extract delta_t(py_obj.attr("delta_t")); if(delta_t.check()) multi_attr_prop.delta_t = delta_t(); else multi_attr_prop.delta_t = bopy::extract(py_obj.attr("delta_t")); // Property type is Tango::DevLong! bopy::extract delta_val(py_obj.attr("delta_val")); if(delta_val.check()) multi_attr_prop.delta_val = delta_val(); else multi_attr_prop.delta_val = empty_str; bopy::extract event_period(py_obj.attr("event_period")); if(event_period.check()) multi_attr_prop.event_period = event_period(); else multi_attr_prop.event_period = bopy::extract(py_obj.attr("event_period")); // Property type is Tango::DevLong! bopy::extract archive_period(py_obj.attr("archive_period")); if(archive_period.check()) multi_attr_prop.archive_period = archive_period(); else multi_attr_prop.archive_period = bopy::extract(py_obj.attr("archive_period")); // Property type is Tango::DevLong! bopy::extract rel_change(py_obj.attr("rel_change")); if(rel_change.check()) multi_attr_prop.rel_change = rel_change(); else { bopy::object prop_py_obj = bopy::object(py_obj.attr("rel_change")); if(PySequence_Check(prop_py_obj.ptr())) { vector change_vec; for(long i = 0; i < bopy::len(prop_py_obj); i++) change_vec.push_back(bopy::extract(prop_py_obj[i])); multi_attr_prop.rel_change = change_vec; } else multi_attr_prop.rel_change = bopy::extract(py_obj.attr("rel_change")); // Property type is Tango::DevDouble! } bopy::extract abs_change(py_obj.attr("abs_change")); if(abs_change.check()) multi_attr_prop.abs_change = abs_change(); else { bopy::object prop_py_obj = bopy::object(py_obj.attr("abs_change")); if(PySequence_Check(prop_py_obj.ptr())) { vector change_vec; for(long i = 0; i < bopy::len(prop_py_obj); i++) change_vec.push_back(bopy::extract(prop_py_obj[i])); multi_attr_prop.abs_change = change_vec; } else multi_attr_prop.abs_change = bopy::extract(py_obj.attr("abs_change")); // Property type is Tango::DevDouble! } bopy::extract archive_rel_change(py_obj.attr("archive_rel_change")); if(archive_rel_change.check()) multi_attr_prop.archive_rel_change = archive_rel_change(); else { bopy::object prop_py_obj = bopy::object(py_obj.attr("archive_rel_change")); if(PySequence_Check(prop_py_obj.ptr())) { vector change_vec; for(long i = 0; i < bopy::len(prop_py_obj); i++) change_vec.push_back(bopy::extract(prop_py_obj[i])); multi_attr_prop.archive_rel_change = change_vec; } else multi_attr_prop.archive_rel_change = bopy::extract(py_obj.attr("archive_rel_change")); // Property type is Tango::DevDouble! } bopy::extract archive_abs_change(py_obj.attr("archive_abs_change")); if(archive_abs_change.check()) multi_attr_prop.archive_abs_change = archive_abs_change(); else { bopy::object prop_py_obj = bopy::object(py_obj.attr("archive_abs_change")); if(PySequence_Check(prop_py_obj.ptr())) { vector change_vec; for(long i = 0; i < bopy::len(prop_py_obj); i++) change_vec.push_back(bopy::extract(prop_py_obj[i])); multi_attr_prop.archive_abs_change = change_vec; } else multi_attr_prop.archive_abs_change = bopy::extract(py_obj.attr("archive_abs_change")); // Property type is Tango::DevDouble! } } void from_py_object(bopy::object &, Tango::AttributeConfigList &); void from_py_object(bopy::object &, Tango::AttributeConfigList_2 &); void from_py_object(bopy::object &, Tango::AttributeConfigList_3 &); void from_py_object(bopy::object &, Tango::AttributeConfigList_5 &); void from_py_object(bopy::object &, Tango::PipeConfig &); void from_py_object(bopy::object &, Tango::PipeConfigList &); pytango-9.2.2/ext/group.cpp000066400000000000000000000322361316324373100156560ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include "pytgutils.h" #include "device_attribute.h" void export_group_reply_list(); void export_group_reply(); namespace PyGroup { using namespace boost::python; void add(Tango::Group& self, std::auto_ptr grp, int timeout_ms) { Tango::Group* grp_ptr = grp.get(); if (grp_ptr) { // After adding grp_ptr into self, self is the responsible of // deleting grp_ptr, so we "nullify" the grp object. It's python // counterpart will still be available, but any method call will // return an exception. self.add(grp_ptr, timeout_ms); grp.release(); } else { raise_(PyExc_TypeError, "Param \"group\" is null. It probably means that it has" " already been inserted in another group." ); } } Tango::GroupCmdReplyList command_inout_reply(Tango::Group &self, long req_id, long timeout_ms) { AutoPythonAllowThreads guard; return self.command_inout_reply(req_id, timeout_ms); } static void __update_data_format(Tango::Group &self, Tango::GroupAttrReplyList& r) { // Usually we pass a device_proxy to "convert_to_python" in order to // get the data_format of the DeviceAttribute for Tango versions // older than 7.0. However, GroupAttrReply has no device_proxy to use! // So, we are using update_data_format() in here. // The conver_to_python method is called, without the usual // device_proxy argument, in PyGroupAttrReply::get_data(). Tango::GroupAttrReplyList::iterator i, e = r.end(); for (i=r.begin(); i != e; ++i) { Tango::DeviceProxy* dev_proxy = self.get_device(i->dev_name()); if (!dev_proxy) continue; PyDeviceAttribute::update_data_format( *dev_proxy, &(i->get_data()), 1 ); } } Tango::GroupAttrReplyList read_attribute_reply (Tango::Group &self, long req_id, long timeout_ms = 0 ) { Tango::GroupAttrReplyList r; { AutoPythonAllowThreads guard; r = self.read_attribute_reply(req_id, timeout_ms); } __update_data_format(self, r); return r; } Tango::GroupAttrReplyList read_attributes_reply (Tango::Group &self, long req_id, long timeout_ms = 0) { Tango::GroupAttrReplyList r; { AutoPythonAllowThreads guard; r = self.read_attributes_reply(req_id, timeout_ms); } __update_data_format(self, r); return r; } long read_attributes_asynch (Tango::Group &self, object py_value, bool forward = true) { StdStringVector r; convert2array(py_value, r); return self.read_attributes_asynch(r, forward); } long write_attribute_asynch(Tango::Group &self, const std::string &attr_name, bopy::object py_value, bool forward = true, bool multi = false) { Tango::DeviceProxy* dev_proxy = self.get_device(1); // If !dev_proxy (no device added in self or his children) then we // don't initialize dev_attr. As a result, the reply will be empty. /// @todo or should we raise an exception instead? if(!dev_proxy) { Tango::DeviceAttribute dev_attr; dev_attr.set_name(attr_name.c_str()); AutoPythonAllowThreads guard; return self.write_attribute_asynch(dev_attr, forward); } // Try to see if we can get attribute information from any device in // the group Tango::AttributeInfoEx attr_info; bool has_attr_info = false; { AutoPythonAllowThreads guard; for(long dev_idx = 1; dev_idx <= self.get_size(); ++dev_idx) { try { attr_info = self[dev_idx]->get_attribute_config(attr_name); has_attr_info = true; break; } catch(...) {} } } if(multi) { if(!PySequence_Check(py_value.ptr())) { raise_(PyExc_TypeError, "When multi is set, value must be a python sequence " "(ex: list or tuple)" ); } Py_ssize_t attr_nb = bopy::len(py_value); std::vector dev_attr(attr_nb); if (has_attr_info) { for(Py_ssize_t i = 0; i < attr_nb; ++i) { PyDeviceAttribute::reset(dev_attr[i], attr_info, py_value[i]); } } else { for(Py_ssize_t i = 0; i < attr_nb; ++i) { dev_attr[i].set_name(attr_name.c_str()); } } AutoPythonAllowThreads guard; return self.write_attribute_asynch(dev_attr, forward); } else { Tango::DeviceAttribute dev_attr; if (has_attr_info) { PyDeviceAttribute::reset(dev_attr, attr_info, py_value); } else { dev_attr.set_name(attr_name.c_str()); } // If !dev_proxy (no device added in self or his children) then we // don't initialize dev_attr. As a result, the reply will be empty. /// @todo or should we raise an exception instead? AutoPythonAllowThreads guard; return self.write_attribute_asynch(dev_attr, forward); } } Tango::GroupReplyList write_attribute_reply (Tango::Group &self, long req_id, long timeout_ms = 0) { AutoPythonAllowThreads guard; return self.write_attribute_reply(req_id, timeout_ms); } } void export_group() { using namespace boost::python; export_group_reply(); export_group_reply_list(); // export_group_element(); // class_, // unique_pointer, boost::noncopyable > Group( // "__Group", // init()) // ; class_, boost::noncopyable > Group("__Group", init()) ; Group .def("_add", (void (Tango::Group::*) (const std::string &, int)) &Tango::Group::add, (arg_("self"), arg_("pattern"), arg_("timeout_ms")=-1) ) .def("_add", (void (Tango::Group::*) (const std::vector &, int)) &Tango::Group::add, (arg_("self"), arg_("patterns"), arg_("timeout_ms")=-1)) .def("_add", PyGroup::add, (arg_("self"), arg_("group"), arg_("timeout_ms")=-1) ) .def("_remove", (void (Tango::Group::*) (const std::string &, bool)) &Tango::Group::remove, (arg_("self"), arg_("pattern"), arg_("forward")=true)) .def("_remove", (void (Tango::Group::*) (const std::vector &, bool)) &Tango::Group::remove, (arg_("self"), arg_("patterns"), arg_("forward")=true)) .def("get_group", &Tango::Group::get_group, (arg_("self"), arg_("group_name")), return_internal_reference<1>() ) .def("get_size", &Tango::Group::get_size, (arg_("self"), arg_("forward")=true) ) .def("remove_all", &Tango::Group::remove_all) // GroupElement redefinitions of enable/disable. If I didn't // redefine them, the later Group only definitions would // hide the ones defined in GroupElement. .def("enable", &Tango::GroupElement::enable, (arg_("self")) ) .def("disable", &Tango::GroupElement::disable, (arg_("self")) ) .def("enable", &Tango::Group::enable, (arg_("self"), arg_("dev_name"), arg_("forward")=true) ) .def("disable", &Tango::Group::disable, (arg_("self"), arg_("dev_name"), arg_("forward")=true) ) .def("get_device_list", &Tango::Group::get_device_list, (arg_("self"), arg_("forward")=true) ) .def("command_inout_asynch", (long (Tango::Group::*) (const std::string&, bool, bool)) &Tango::Group::command_inout_asynch, ( arg_("self"), arg_("cmd_name"), arg_("forget")=false, arg_("forward")=true) ) .def("command_inout_asynch", (long (Tango::Group::*) (const std::string&, const Tango::DeviceData&, bool, bool)) &Tango::Group::command_inout_asynch, ( arg_("self"), arg_("cmd_name"), arg_("param"), arg_("forget")=false, arg_("forward")=true) ) .def("command_inout_asynch", (long (Tango::Group::*) (const std::string&, const std::vector&, bool, bool)) &Tango::Group::command_inout_asynch, ( arg_("self"), arg_("cmd_name"), arg_("param"), arg_("forget")=false, arg_("forward")=true) ) .def("command_inout_reply", PyGroup::command_inout_reply, ( arg_("self"), arg_("req_id"), arg_("timeout_ms")=0 ) ) .def("read_attribute_asynch", &Tango::Group::read_attribute_asynch, ( arg_("self"), arg_("attr_name"), arg_("forward")=true) ) .def("read_attribute_reply", PyGroup::read_attribute_reply, ( arg_("self"), arg_("req_id"), arg_("timeout_ms")=0 ) ) .def("read_attributes_asynch", PyGroup::read_attributes_asynch, ( arg_("self"), arg_("attr_names"), arg_("forward")=true) ) .def("read_attributes_reply", PyGroup::read_attributes_reply, ( arg_("self"), arg_("req_id"), arg_("timeout_ms")=0 ) ) .def("write_attribute_asynch", PyGroup::write_attribute_asynch, ( arg_("self"), arg_("attr_name"), arg_("value"), arg_("forward")=true, arg_("multi")=false) ) .def("write_attribute_reply", PyGroup::write_attribute_reply, ( arg_("self"), arg_("req_id"), arg_("timeout_ms")=0 ) ) .def("get_parent", &Tango::Group::get_parent, (arg_("self")), return_internal_reference<1>() ) .def("contains", &Tango::Group::contains, (arg_("self"), arg_("pattern"), arg_("forward")=true) ) .def("get_device", (Tango::DeviceProxy* (Tango::Group::*) (const std::string &)) &Tango::Group::get_device, (arg_("self"), arg_("dev_name")), return_internal_reference<1>() ) .def("get_device", (Tango::DeviceProxy* (Tango::Group::*) (long)) &Tango::Group::get_device, (arg_("self"), arg_("idx")), return_internal_reference<1>() ) .def("ping", &Tango::Group::ping, (arg_("self"), arg_("forward")=true) ) .def("set_timeout_millis", &Tango::Group::set_timeout_millis, (arg_("self"), arg_("timeout_ms")) ) .def("get_name", &Tango::Group::get_name, (arg_("self")), return_value_policy() ) .def("get_fully_qualified_name", &Tango::Group::get_fully_qualified_name, (arg_("self")) ) .def("enable", &Tango::Group::enable, (arg_("self")) ) .def("disable", &Tango::Group::disable, (arg_("self")) ) .def("is_enabled", &Tango::Group::is_enabled, (arg_("self")) ) .def("name_equals", &Tango::Group::name_equals, (arg_("self")) ) .def("name_matches", &Tango::Group::name_matches, (arg_("self")) ) ; // I am not exporting "find", so all the GroupElemens will be // Groups (there's no way to access a GroupDeviceElement) // class_, boost::noncopyable > GroupDeviceElement( // "GroupDeviceElement", // no_init) // ; } pytango-9.2.2/ext/group_reply.cpp000066400000000000000000000052351316324373100170700ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include "pytgutils.h" #include "device_attribute.h" namespace PyGroupAttrReply { using namespace boost::python; object get_data(Tango::GroupAttrReply& self, PyTango::ExtractAs extract_as) { // Usually we pass a device_proxy to "convert_to_python" in order to // get the data_format of the DeviceAttribute for Tango versions // older than 7.0. However, GroupAttrReply has no device_proxy to use! // So, we are using update_data_format() in: // GroupElement::read_attribute_reply/read_attributes_reply return PyDeviceAttribute::convert_to_python( new Tango::DeviceAttribute(self.get_data()), extract_as ); } } void export_group_reply() { using namespace boost::python; class_ GroupReply("GroupReply", "", no_init); GroupReply // .def(init<>()) // .def(init()) // .def(init()) /// @todo args? // .def(init()) .def("has_failed", &Tango::GroupReply::has_failed) .def("group_element_enabled", &Tango::GroupReply::group_element_enabled) .def("dev_name", &Tango::GroupReply::dev_name, return_value_policy()) .def("obj_name", &Tango::GroupReply::obj_name, return_value_policy()) .def("get_err_stack", &Tango::GroupReply::get_err_stack, return_value_policy()) ; class_ > GroupCmdReply( "GroupCmdReply", no_init) ; GroupCmdReply .def("get_data_raw", &Tango::GroupCmdReply::get_data, return_internal_reference<1>() ) ; class_ > GroupAttrReply( "GroupAttrReply", no_init) ; GroupAttrReply .def("__get_data", &PyGroupAttrReply::get_data, ( arg_("self"), arg_("extract_as")=PyTango::ExtractAsNumpy) ) ; } pytango-9.2.2/ext/group_reply_list.cpp000066400000000000000000000036631316324373100201260ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include "pytgutils.h" #include "device_attribute.h" void export_group_reply_list() { using namespace boost::python; typedef std::vector StdGroupReplyVector_; typedef std::vector StdGroupCmdReplyVector_; typedef std::vector StdGroupAttrReplyVector_; class_ > GroupReplyList( "GroupReplyList", init<>()) ; GroupReplyList .def("has_failed", &Tango::GroupReplyList::has_failed) .def("reset", &Tango::GroupReplyList::reset) .def("push_back", &Tango::GroupReplyList::push_back) ; class_ > GroupCmdReplyList( "GroupCmdReplyList", init<>()) ; GroupCmdReplyList .def("has_failed", &Tango::GroupCmdReplyList::has_failed) .def("reset", &Tango::GroupCmdReplyList::reset) .def("push_back", &Tango::GroupCmdReplyList::push_back) ; class_ > GroupAttrReplyList( "GroupAttrReplyList", init<>()) ; GroupAttrReplyList .def("has_failed", &Tango::GroupAttrReplyList::has_failed) .def("reset", &Tango::GroupAttrReplyList::reset) .def("push_back", &Tango::GroupAttrReplyList::push_back) ; } pytango-9.2.2/ext/locker_info.cpp000066400000000000000000000022561316324373100170130ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include struct PyLockerInfo { static inline boost::python::object get_locker_id(Tango::LockerInfo &li) { return (li.ll == Tango::CPP) ? boost::python::object(li.li.LockerPid) : boost::python::tuple(li.li.UUID); } }; void export_locker_info() { boost::python::class_("LockerInfo") .def_readonly("ll", &Tango::LockerInfo::ll) .add_property("li", &PyLockerInfo::get_locker_id) .def_readonly("locker_host", &Tango::LockerInfo::locker_host) .def_readonly("locker_class", &Tango::LockerInfo::locker_class) ; } pytango-9.2.2/ext/locking_thread.cpp000066400000000000000000000013171316324373100174730ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include using namespace boost::python; void export_locking_thread() { class_("LockingThread") ; } pytango-9.2.2/ext/periodic_event_info.cpp000066400000000000000000000016071316324373100205320ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include using namespace boost::python; void export_periodic_event_info() { class_("PeriodicEventInfo") .enable_pickling() .def_readwrite("period", &Tango::PeriodicEventInfo::period) .def_readwrite("extensions", &Tango::PeriodicEventInfo::extensions) ; } pytango-9.2.2/ext/pipe_event_data.cpp000066400000000000000000000047011316324373100176450ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include #include "exception.h" using namespace boost::python; extern boost::python::object PyTango_DevFailed; namespace PyPipeEventData { static boost::shared_ptr makePipeEventData() { Tango::PipeEventData *result = new Tango::PipeEventData; return boost::shared_ptr(result); } static void set_errors(Tango::PipeEventData &event_data, boost::python::object &dev_failed) { Tango::DevFailed df; boost::python::object errors = dev_failed.attr("args"); sequencePyDevError_2_DevErrorList(errors.ptr(), event_data.errors); } }; // end PyPipeEventData void export_pipe_event_data() { class_("PipeEventData", init()) .def("__init__", boost::python::make_constructor(PyPipeEventData::makePipeEventData)) // The original Tango::PipeEventData structure has a 'device' field. // However, if we returned this directly we would get a different // python device each time. So we are doing our weird things to make // sure the device returned is the same where the read action was // performed. So we don't return Tango::PipeEventData::device directly. // See callback.cpp .setattr("device",object()) .def_readwrite("pipe_name", &Tango::PipeEventData::pipe_name) .def_readwrite("event", &Tango::PipeEventData::event) .setattr("pipe_value",object()) .def_readwrite("err", &Tango::PipeEventData::err) .def_readwrite("reception_date", &Tango::PipeEventData::reception_date) .add_property("errors", make_getter(&Tango::PipeEventData::errors, return_value_policy()), &PyPipeEventData::set_errors) .def("get_date", &Tango::PipeEventData::get_date, return_internal_reference<>()) ; } pytango-9.2.2/ext/pipe_info.cpp000066400000000000000000000021711316324373100164650ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include using namespace boost::python; void export_pipe_info() { class_ ("PipeInfo") .def(init()) .enable_pickling() .def_readwrite("name", &Tango::PipeInfo::name) .def_readwrite("description", &Tango::PipeInfo::description) .def_readwrite("label", &Tango::PipeInfo::label) .def_readwrite("disp_level", &Tango::PipeInfo::disp_level) .def_readwrite("writable", &Tango::PipeInfo::writable) .def_readwrite("extensions", &Tango::PipeInfo::extensions) ; } pytango-9.2.2/ext/poll_device.cpp000066400000000000000000000020211316324373100167740ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include using namespace boost::python; void export_poll_device() { class_("PollDevice", "A structure containing PollDevice information\n" "the following members,\n" " - dev_name : string\n" " - ind_list : sequence\n" "\nNew in PyTango 7.0.0") .def_readwrite("dev_name", &Tango::PollDevice::dev_name) .def_readwrite("ind_list", &Tango::PollDevice::ind_list) ; } pytango-9.2.2/ext/precompiled_header.cpp000066400000000000000000000011011316324373100203200ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp"pytango-9.2.2/ext/precompiled_header.hpp000066400000000000000000000023561316324373100203420ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ // These files are really basic, used everywere within the project // but they take a while (seconds!) to process. // We don't want to waste those seconds for each cpp file, so we // use this precompiled header. //#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION #include #include #include #include #include #include #include #include #include #include #include #include #include //#include pytango-9.2.2/ext/pytango.cpp000066400000000000000000000070361316324373100162030ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #if BOOST_VERSION < 103400 #define DISABLE_BOOST_DOCSTRING_OPTIONS #endif #ifndef DISABLE_PYTANGO_NUMPY # define PY_ARRAY_UNIQUE_SYMBOL pytango_ARRAY_API # include #endif #include using namespace boost::python; void export_version(); void export_enums(); void export_constants(); void export_base_types(); void export_event_data(); void export_attr_conf_event_data(); void export_data_ready_event_data(); void export_pipe_event_data(); void export_exceptions(); void export_api_util(); void export_connection(); void export_device_proxy(); void export_devintr_change_event_data(); void export_attribute_proxy(); void export_db(); void export_callback(); /// @todo not sure were to put it... void export_util(); void export_pipe(); void export_attr(); void export_fwdattr(); void export_attribute(); void export_encoded_attribute(); void export_wattribute(); void export_multi_attribute(); void export_multi_class_attribute(); void export_user_default_attr_prop(); void export_user_default_fwdattr_prop(); void export_user_default_pipe_prop(); void export_sub_dev_diag(); void export_dserver(); void export_device_class(); void export_device_impl(); void export_group(); void export_log4tango(); void export_auto_tango_monitor(); #ifdef DISABLE_PYTANGO_NUMPY void init_numpy(void) {} #elif PY_MAJOR_VERSION >= 3 void* init_numpy(void) { import_array(); return NULL; } #else void init_numpy(void) { import_array(); return; } #endif BOOST_PYTHON_MODULE(_tango) { #ifndef DISABLE_BOOST_DOCSTRING_OPTIONS // Configure generated docstrings const bool show_user_defined = false; const bool show_py_signatures = false; docstring_options doc_opts(show_user_defined, show_py_signatures); #endif // specify that this module is actually a package boost::python::object package = boost::python::scope(); package.attr("__path__") = "PyTango"; PyEval_InitThreads(); init_numpy(); export_callback(); /// @todo not sure were to put it... export_version(); export_enums(); export_constants(); export_base_types(); export_event_data(); export_attr_conf_event_data(); export_data_ready_event_data(); export_pipe_event_data(); export_devintr_change_event_data(); export_exceptions(); export_api_util(); export_connection(); export_device_proxy(); export_attribute_proxy(); export_db(); export_util(); export_pipe(); export_attr(); export_fwdattr(); export_attribute(); export_encoded_attribute(); export_wattribute(); export_multi_attribute(); export_multi_class_attribute(); export_user_default_attr_prop(); export_user_default_fwdattr_prop(); export_user_default_pipe_prop(); export_sub_dev_diag(); export_device_class(); export_device_impl(); //@warning export_dserver must be made after export_device_impl export_dserver(); export_group(); export_log4tango(); export_auto_tango_monitor(); } pytango-9.2.2/ext/pytgutils.cpp000066400000000000000000000012141316324373100165560ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include "defs.h" #include "pytgutils.h" using namespace boost::python; pytango-9.2.2/ext/pytgutils.h000066400000000000000000000037371316324373100162370ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #pragma once #include #include #include "defs.h" #include "pyutils.h" #include "from_py.h" #include "to_py.h" #include "tgutils.h" /// Get the python Global Interpret Lock class AutoPythonGIL { PyGILState_STATE m_gstate; /** * Check python. Before acquiring python GIL check if python as not been * shutdown. If this is the case then the best we can do here is throw an * exception to try to prevent the PyTango from calling python code **/ inline void check_python() { if(!Py_IsInitialized()) { Tango::Except::throw_exception( "AutoPythonGIL_PythonShutdown", "Trying to execute python code when python interpreter as shutdown.", "AutoPythonGIL::check_python"); } } public: inline AutoPythonGIL(bool safe=true) { if (safe) check_python(); m_gstate = PyGILState_Ensure(); } inline ~AutoPythonGIL() { PyGILState_Release(m_gstate); } }; /** * Translate a double into a timeval structure * * @param[out] tv timeval structure to be filled with the time * @param[in] t a double representing the time */ inline void double2timeval(struct timeval &tv, double t) { double sec = floor(t); #ifdef WIN32 tv.tv_usec = (long)((t-sec)*1.0E6); tv.tv_sec = (long)(sec); #else tv.tv_usec = (time_t)((t-sec)*1.0E6); tv.tv_sec = (suseconds_t)(sec); #endif } pytango-9.2.2/ext/pyutils.cpp000066400000000000000000000120321316324373100162230ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include "defs.h" #include "pyutils.h" using namespace boost::python; bopy::object from_char_to_str2(const std::string& in, const char* encoding /*=NULL defaults to latin-1 */, const char* errors /*="strict" */) { return from_char_to_str2(in.c_str(), in.size(), encoding, errors); } bopy::object from_char_to_str2(const char* in, Py_ssize_t size /* =-1 */, const char* encoding /*=NULL defaults to latin-1 */, const char* errors /*="strict" */) { return bopy::object(bopy::handle<>(from_char_to_str(in, size, encoding, errors))); } PyObject* from_char_to_str(const std::string& in, const char* encoding /*=NULL defaults to latin-1 */, const char* errors /*="strict" */) { return from_char_to_str(in.c_str(), in.size(), encoding, errors); } PyObject* from_char_to_str(const char* in, Py_ssize_t size /* =-1 */, const char* encoding /*=NULL defaults to latin-1 */, const char* errors /*="strict" */) { if (size < 0) { size = strlen(in); } #ifdef PYTANGO_PY3K if (!encoding) { return PyUnicode_DecodeLatin1(in, size, errors); } else { return PyUnicode_Decode(in, size, encoding, errors); } #else return PyString_FromStringAndSize(in, size); #endif } void from_str_to_char(PyObject* in, std::string& out) { if (PyUnicode_Check(in)) { PyObject *bytes_in = PyUnicode_AsLatin1String(in); out = PyBytes_AsString(bytes_in); Py_DECREF(bytes_in); } else { out = std::string(PyBytes_AsString(in), PyBytes_Size(in)); } } bool is_method_defined(object &obj, const std::string &method_name) { return is_method_defined(obj.ptr(), method_name); } bool is_method_defined(PyObject *obj, const std::string &method_name) { bool exists, is_method; is_method_defined(obj, method_name, exists, is_method); return exists && is_method; } void is_method_defined(object &obj, const std::string &method_name, bool &exists, bool &is_method) { is_method_defined(obj.ptr(), method_name, exists, is_method); } void is_method_defined(PyObject *obj, const std::string &method_name, bool &exists, bool &is_method) { exists = is_method = false; PyObject *meth = PyObject_GetAttrString_(obj, method_name.c_str()); exists = NULL != meth; if (!exists) { PyErr_Clear(); return; } is_method = (1 == PyCallable_Check(meth)); Py_DECREF(meth); } #ifdef PYCAPSULE_OLD int PyCapsule_SetName(PyObject *capsule, const char *unused) { unused = unused; PyErr_SetString(PyExc_NotImplementedError, "can't use PyCapsule_SetName with CObjects"); return 1; } void *PyCapsule_Import(const char *name, int no_block) { PyObject *object = NULL; void *return_value = NULL; char *trace; size_t name_length = (strlen(name) + 1) * sizeof(char); char *name_dup = (char *)PyMem_MALLOC(name_length); if (!name_dup) { return NULL; } memcpy(name_dup, name, name_length); trace = name_dup; while (trace) { char *dot = strchr(trace, '.'); if (dot) { *dot++ = '\0'; } if (object == NULL) { if (no_block) { object = PyImport_ImportModuleNoBlock(trace); } else { object = PyImport_ImportModule(trace); if (!object) { PyErr_Format(PyExc_ImportError, "PyCapsule_Import could not " "import module \"%s\"", trace); } } } else { PyObject *object2 = PyObject_GetAttrString(object, trace); Py_DECREF(object); object = object2; } if (!object) { goto EXIT; } trace = dot; } if (PyCObject_Check(object)) { PyCObject *cobject = (PyCObject *)object; return_value = cobject->cobject; } else { PyErr_Format(PyExc_AttributeError, "PyCapsule_Import \"%s\" is not valid", name); } EXIT: Py_XDECREF(object); if (name_dup) { PyMem_FREE(name_dup); } return return_value; } #endif bool hasattr(boost::python::object& obj, const std::string& name) { return PyObject_HasAttrString(obj.ptr(), name.c_str()); } pytango-9.2.2/ext/pyutils.h000066400000000000000000000205141316324373100156740ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #pragma once #include namespace bopy = boost::python; #define arg_(a) boost::python::arg(a) #if PY_MAJOR_VERSION >= 3 #define PYTANGO_PY3K #endif #if PY_VERSION_HEX < 0x02050000 typedef int Py_ssize_t; #endif // ----------------------------------------------------------------------------- // The following section contains functions that changed signature from <=2.4 // using char* to >=2.5 using const char*. Basically we defined them here using // const std::string inline PyObject *PyObject_GetAttrString_(PyObject *o, const std::string &attr_name) { #if PY_VERSION_HEX < 0x02050000 char *attr = const_cast(attr_name.c_str()); #else const char *attr = attr_name.c_str(); #endif return PyObject_GetAttrString(o, attr); } inline PyObject *PyImport_ImportModule_(const std::string &name) { #if PY_VERSION_HEX < 0x02050000 char *attr = const_cast(name.c_str()); #else const char *attr = name.c_str(); #endif return PyImport_ImportModule(attr); } // Bytes interface #if PY_VERSION_HEX < 0x02060000 #define PyBytesObject PyStringObject #define PyBytes_Type PyString_Type #define PyBytes_Check PyString_Check #define PyBytes_CheckExact PyString_CheckExact #define PyBytes_CHECK_INTERNED PyString_CHECK_INTERNED #define PyBytes_AS_STRING PyString_AS_STRING #define PyBytes_GET_SIZE PyString_GET_SIZE #define Py_TPFLAGS_BYTES_SUBCLASS Py_TPFLAGS_STRING_SUBCLASS #define PyBytes_FromStringAndSize PyString_FromStringAndSize #define PyBytes_FromString PyString_FromString #define PyBytes_FromFormatV PyString_FromFormatV #define PyBytes_FromFormat PyString_FromFormat #define PyBytes_Size PyString_Size #define PyBytes_AsString PyString_AsString #define PyBytes_Repr PyString_Repr #define PyBytes_Concat PyString_Concat #define PyBytes_ConcatAndDel PyString_ConcatAndDel #define _PyBytes_Resize _PyString_Resize #define _PyBytes_Eq _PyString_Eq #define PyBytes_Format PyString_Format #define _PyBytes_FormatLong _PyString_FormatLong #define PyBytes_DecodeEscape PyString_DecodeEscape #define _PyBytes_Join _PyString_Join #define PyBytes_Decode PyString_Decode #define PyBytes_Encode PyString_Encode #define PyBytes_AsEncodedObject PyString_AsEncodedObject #define PyBytes_AsEncodedString PyString_AsEncodedString #define PyBytes_AsDecodedObject PyString_AsDecodedObject #define PyBytes_AsDecodedString PyString_AsDecodedString #define PyBytes_AsStringAndSize PyString_AsStringAndSize #define _PyBytes_InsertThousandsGrouping _PyString_InsertThousandsGrouping #else #include #endif /* PyCapsule definitions for old python */ #if ( (PY_VERSION_HEX < 0x02070000) \ || ((PY_VERSION_HEX >= 0x03000000) \ && (PY_VERSION_HEX < 0x03010000)) ) #define PYCAPSULE_OLD #define __PyCapsule_GetField(capsule, field, default_value) \ ( PyCapsule_CheckExact(capsule) \ ? (((PyCObject *)capsule)->field) \ : (default_value) \ ) \ #define __PyCapsule_SetField(capsule, field, value) \ ( PyCapsule_CheckExact(capsule) \ ? (((PyCObject *)capsule)->field = value), 1 \ : 0 \ ) \ #define PyCapsule_Type PyCObject_Type #define PyCapsule_CheckExact(capsule) (PyCObject_Check(capsule)) #define PyCapsule_IsValid(capsule, name) (PyCObject_Check(capsule)) #define PyCapsule_New(pointer, name, destructor) \ (PyCObject_FromVoidPtr(pointer, destructor)) #define PyCapsule_GetPointer(capsule, name) \ (PyCObject_AsVoidPtr(capsule)) /* Don't call PyCObject_SetPointer here, it fails if there's a destructor */ #define PyCapsule_SetPointer(capsule, pointer) \ __PyCapsule_SetField(capsule, cobject, pointer) #define PyCapsule_GetDestructor(capsule) \ __PyCapsule_GetField(capsule, destructor) #define PyCapsule_SetDestructor(capsule, dtor) \ __PyCapsule_SetField(capsule, destructor, dtor) /* * Sorry, there's simply no place * to store a Capsule "name" in a CObject. */ #define PyCapsule_GetName(capsule) NULL int PyCapsule_SetName(PyObject *capsule, const char *unused); #define PyCapsule_GetContext(capsule) \ __PyCapsule_GetField(capsule, descr) #define PyCapsule_SetContext(capsule, context) \ __PyCapsule_SetField(capsule, descr, context) void * PyCapsule_Import(const char *name, int no_block); #endif /* #if PY_VERSION_HEX < 0x02070000 */ PyObject* from_char_to_str(const char* in, Py_ssize_t size=-1, const char* encoding=NULL, /* defaults to latin-1 */ const char* errors="strict"); PyObject* from_char_to_str(const std::string& in, const char* encoding=NULL, /* defaults to latin-1 */ const char* errors="strict"); bopy::object from_char_to_str2(const char* in, Py_ssize_t size=-1, const char* encoding=NULL, /* defaults to latin-1 */ const char* errors="strict"); bopy::object from_char_to_str2(const std::string& in, const char* encoding=NULL, /* defaults to latin-1 */ const char* errors="strict"); void from_str_to_char(PyObject* in, std::string& out); inline void raise_(PyObject *type, const char *message) { PyErr_SetString(type, message); boost::python::throw_error_already_set(); } /// You should run any I/O intensive operations (like requesting data through /// the network) in the context of an object like this. class AutoPythonAllowThreads { PyThreadState *m_save; public: inline void giveup() { if (m_save) { PyEval_RestoreThread(m_save); m_save = 0; } } inline AutoPythonAllowThreads() { m_save = PyEval_SaveThread(); } inline ~AutoPythonAllowThreads() { giveup(); } }; /** * Determines if the given method name exists and is callable * within the python class * * @param[in] obj object to search for the method * @param[in] method_name the name of the method * * @return returns true is the method exists or false otherwise */ bool is_method_defined(boost::python::object &obj, const std::string &method_name); /** * Determines if the given method name exists and is callable * within the python class * * @param[in] obj object to search for the method * @param[in] method_name the name of the method * * @return returns true is the method exists or false otherwise */ bool is_method_defined(PyObject *obj, const std::string &method_name); /** * Determines if the given method name exists and is callable * within the python class * * @param[in] obj object to search for the method * @param[in] method_name the name of the method * @param[out] exists set to true if the symbol exists or false otherwise * @param[out] is_method set to true if the symbol exists and is a method * or false otherwise */ void is_method_defined(PyObject *obj, const std::string &method_name, bool &exists, bool &is_method); /** * Determines if the given method name exists and is callable * within the python class * * @param[in] obj object to search for the method * @param[in] method_name the name of the method * @param[out] exists set to true if the symbol exists or false otherwise * @param[out] is_method set to true if the symbol exists and is a method * or false otherwise */ void is_method_defined(boost::python::object &obj, const std::string &method_name, bool &exists, bool &is_method); #define PYTANGO_MOD \ boost::python::object pytango((boost::python::handle<>(boost::python::borrowed(PyImport_AddModule("tango"))))); #define CALL_METHOD(retType, self, name, ...) \ boost::python::call_method(self, name , __VA_ARGS__); bool hasattr(boost::python::object &, const std::string &); pytango-9.2.2/ext/server/000077500000000000000000000000001316324373100153165ustar00rootroot00000000000000pytango-9.2.2/ext/server/attr.cpp000066400000000000000000000231201316324373100167720ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include "server/attr.h" using namespace boost::python; #define __AUX_DECL_CALL_ATTR_METHOD \ PyDeviceImplBase *__dev_ptr = dynamic_cast(dev); \ AutoPythonGIL __py_lock; #define __AUX_CATCH_PY_EXCEPTION \ catch(boost::python::error_already_set &eas) \ { handle_python_exception(eas); } #define CALL_ATTR_METHOD(dev, name) \ __AUX_DECL_CALL_ATTR_METHOD \ try { boost::python::call_method(__dev_ptr->the_self, name); } \ __AUX_CATCH_PY_EXCEPTION #define CALL_ATTR_METHOD_VARGS(dev, name, ...) \ __AUX_DECL_CALL_ATTR_METHOD \ try { boost::python::call_method(__dev_ptr->the_self, name, __VA_ARGS__); } \ __AUX_CATCH_PY_EXCEPTION #define CALL_ATTR_METHOD_RET(retType, ret, dev, name) \ __AUX_DECL_CALL_ATTR_METHOD \ try { ret = boost::python::call_method(__dev_ptr->the_self, name); } \ __AUX_CATCH_PY_EXCEPTION #define CALL_ATTR_METHOD_VARGS_RET(retType, ret, dev, name, ...) \ __AUX_DECL_CALL_ATTR_METHOD \ try { ret = boost::python::call_method(__dev_ptr->the_self, name, __VA_ARGS__); } \ __AUX_CATCH_PY_EXCEPTION #define RET_CALL_ATTR_METHOD(retType, dev, name) \ __AUX_DECL_CALL_ATTR_METHOD \ try { return boost::python::call_method(__dev_ptr->the_self, name); } \ __AUX_CATCH_PY_EXCEPTION #define RET_CALL_ATTR_METHOD_VARGS(retType, dev, name, ...) \ __AUX_DECL_CALL_ATTR_METHOD \ try { return boost::python::call_method(__dev_ptr->the_self, name, __VA_ARGS__); } \ __AUX_CATCH_PY_EXCEPTION void PyAttr::read(Tango::DeviceImpl *dev, Tango::Attribute &att) { if (!_is_method(dev, read_name)) { TangoSys_OMemStream o; o << read_name << " method not found for " << att.get_name(); Tango::Except::throw_exception("PyTango_ReadAttributeMethodNotFound", o.str(), "PyTango::Attr::read"); } CALL_ATTR_METHOD_VARGS(dev, read_name.c_str(), boost::ref(att)) } void PyAttr::write(Tango::DeviceImpl *dev, Tango::WAttribute &att) { if (!_is_method(dev, write_name)) { TangoSys_OMemStream o; o << write_name << " method not found for " << att.get_name(); Tango::Except::throw_exception("PyTango_WriteAttributeMethodNotFound", o.str(), "PyTango::Attr::write"); } CALL_ATTR_METHOD_VARGS(dev, write_name.c_str(), boost::ref(att)) } bool PyAttr::is_allowed(Tango::DeviceImpl *dev, Tango::AttReqType ty) { if (_is_method(dev, py_allowed_name)) { RET_CALL_ATTR_METHOD_VARGS(bool, dev, py_allowed_name.c_str(), ty) } // keep compiler quiet return true; } bool PyAttr::_is_method(Tango::DeviceImpl *dev, const std::string &name) { AutoPythonGIL __py_lock; PyDeviceImplBase *__dev_ptr = dynamic_cast(dev); PyObject *__dev_py = __dev_ptr->the_self; return is_method_defined(__dev_py, name); } void PyAttr::set_user_prop(vector &user_prop, Tango::UserDefaultAttrProp &def_prop) { // // Is there any user defined prop. defined ? // size_t nb_prop = user_prop.size(); if (nb_prop == 0) return; for (size_t loop = 0;loop < nb_prop;loop++) { Tango::AttrProperty prop = user_prop[loop]; string &prop_name = prop.get_name(); const char *prop_value = prop.get_value().c_str(); if (prop_name == "label") def_prop.set_label(prop_value); else if (prop_name == "description") def_prop.set_description(prop_value); else if (prop_name == "unit") def_prop.set_unit(prop_value); else if (prop_name == "standard_unit") def_prop.set_standard_unit(prop_value); else if (prop_name == "display_unit") def_prop.set_display_unit(prop_value); else if (prop_name == "format") def_prop.set_format(prop_value); else if (prop_name == "min_value") def_prop.set_min_value(prop_value); else if (prop_name == "max_value") def_prop.set_max_value(prop_value); else if (prop_name == "min_alarm") def_prop.set_min_alarm(prop_value); else if (prop_name == "max_alarm") def_prop.set_max_alarm(prop_value); else if (prop_name == "min_warning") def_prop.set_min_warning(prop_value); else if (prop_name == "max_warning") def_prop.set_max_warning(prop_value); else if (prop_name == "delta_val") def_prop.set_delta_val(prop_value); else if (prop_name == "delta_t") def_prop.set_delta_t(prop_value); else if (prop_name == "abs_change") def_prop.set_event_abs_change(prop_value); else if (prop_name == "rel_change") def_prop.set_event_rel_change(prop_value); else if (prop_name == "period") def_prop.set_event_period(prop_value); else if (prop_name == "archive_abs_change") def_prop.set_archive_event_abs_change(prop_value); else if (prop_name == "archive_rel_change") def_prop.set_archive_event_rel_change(prop_value); else if (prop_name == "archive_period") def_prop.set_archive_event_period(prop_value); } } void export_attr() { class_("Attr", init >()) .def("set_default_properties", &Tango::Attr::set_default_properties) .def("set_disp_level", &Tango::Attr::set_disp_level) .def("set_polling_period", &Tango::Attr::set_polling_period) .def("set_memorized", &Tango::Attr::set_memorized) .def("set_memorized_init", &Tango::Attr::set_memorized_init) .def("set_change_event", &Tango::Attr::set_change_event) .def("is_change_event", &Tango::Attr::is_change_event) .def("is_check_change_criteria", &Tango::Attr::is_check_change_criteria) .def("set_archive_event", &Tango::Attr::set_archive_event) .def("is_archive_event", &Tango::Attr::is_archive_event) .def("is_check_archive_criteria", &Tango::Attr::is_check_archive_criteria) .def("set_data_ready_event", &Tango::Attr::set_data_ready_event) .def("is_data_ready_event", &Tango::Attr::is_data_ready_event) .def("get_name", &Tango::Attr::get_name, return_value_policy()) .def("get_format", &Tango::Attr::get_format) .def("get_writable", &Tango::Attr::get_writable) .def("get_type", &Tango::Attr::get_type) .def("get_disp_level", &Tango::Attr::get_disp_level) .def("get_polling_period", &Tango::Attr::get_polling_period) .def("get_memorized", &Tango::Attr::get_memorized) .def("get_memorized_init", &Tango::Attr::get_memorized_init) .def("get_assoc", &Tango::Attr::get_assoc, return_value_policy()) .def("is_assoc", &Tango::Attr::is_assoc) .def("get_cl_name", &Tango::Attr::get_cl_name, return_value_policy()) .def("set_cl_name", &Tango::Attr::set_cl_name) .def("get_class_properties", &Tango::Attr::get_class_properties, return_internal_reference<>()) .def("get_user_default_properties", &Tango::Attr::get_user_default_properties, return_internal_reference<>()) .def("set_class_properties", &Tango::Attr::set_class_properties) .def("check_type", &Tango::Attr::check_type) .def("read", &Tango::Attr::read) .def("write", &Tango::Attr::write) .def("is_allowed", &Tango::Attr::is_allowed) ; class_, boost::noncopyable>("SpectrumAttr", init()) ; class_, boost::noncopyable>("ImageAttr", init()) ; // class_("PyAttr", no_init) // .def("set_allowed", &PyAttr::set_allowed) // .def("set_read_name", &PyAttr::set_read_name) // .def("set_write_name", &PyAttr::set_write_name) // ; // // class_, boost::noncopyable>("PyScaAttr", // init()) // ; // // class_, boost::noncopyable>("PySpecAttr", // init()) // ; // // class_, boost::noncopyable>("PyImaAttr", // init()) // ; class_("AttrProperty", init()) .def(init()) .def("get_value", &Tango::AttrProperty::get_value, return_value_policy()) .def("get_lg_value", &Tango::AttrProperty::get_lg_value) .def("get_name", &Tango::AttrProperty::get_name, return_value_policy()) ; } pytango-9.2.2/ext/server/attr.h000066400000000000000000000250341316324373100164450ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #ifndef _ATTR_H_ #define _ATTR_H_ #include #include #include "exception.h" #include "pytgutils.h" #include "server/device_impl.h" class PyAttr { public: /** * Constructor */ PyAttr() {} /** * Desctructor */ virtual ~PyAttr() {} /** * Read one attribute. This method forward the action to the python method. * * @param[in] dev The device on which the attribute has to be read * @param[in, out] att the attribute */ void read(Tango::DeviceImpl *dev,Tango::Attribute &att); /** * Write one attribute. This method forward the action to the python method. * * @param[in] dev The device on which the attribute has to be written * @param[in, out] att the attribute */ void write(Tango::DeviceImpl *dev,Tango::WAttribute &att); /** * Decide if it is allowed to read/write the attribute * * @param[in] dev The device on which the attribute has to be read/written * @param[in] ty The requets type (read or write) * * @return a boolean set to true if it is allowed to execute * the command. Otherwise, returns false */ bool is_allowed(Tango::DeviceImpl *dev,Tango::AttReqType ty); /** * Sets the is_allowed method name for this attribute * * @param[in] name the is_allowed method name */ inline void set_allowed_name(const std::string &name) { py_allowed_name = name; } /** * Sets the read method name for this attribute * * @param[in] name the read method name */ inline void set_read_name(const std::string &name) { read_name = name; } /** * Sets the write method name for this attribute * * @param[in] name the write method name */ inline void set_write_name(const std::string &name) { write_name = name; } /** * Transfer user property from a vector of AttrProperty * to a UserDefaultAttrProp * * @param[in] user_prop the AttrProperty vector * @param[out] def_prop the UserDefaultAttrProp instance */ void set_user_prop(std::vector &user_prop, Tango::UserDefaultAttrProp &def_prop); bool _is_method(Tango::DeviceImpl *dev, const std::string &name); private: /** the name of the is allowed python method */ std::string py_allowed_name; /** the name of the read attribute python method */ std::string read_name; /** the name of the write attribute python method */ std::string write_name; }; /** * The python class representing a scalar attribute */ class PyScaAttr: public Tango::Attr, public PyAttr { public: /** * Python Scalar Attribute constructor * * @param[in] na The attribute name * @param[in] type The attribute data type * @param[in] w The attribute writable type */ PyScaAttr(const std::string &na, long type, Tango::AttrWriteType w) : Tango::Attr(na.c_str(), type, w) {} /** * Python Scalar Attribute constructor * * @param[in] na The attribute name * @param[in] type The attribute data type * @param[in] w The attribute writable type * @param[in] ww The attribute max dim x * @param[in] user_prop The attribute user default properties */ PyScaAttr(const std::string &na, long type, Tango::AttrWriteType w, std::vector &user_prop) : Tango::Attr(na.c_str(), type, w) { if (user_prop.size() == 0) return; Tango::UserDefaultAttrProp def_prop; set_user_prop(user_prop,def_prop); set_default_properties(def_prop); } /** * Python Scalar Attribute destructor */ ~PyScaAttr() {}; /** * Decide if it is allowed to read/write the attribute * * @param[in] dev The device on which the attribute has to be read/written * @param[in] ty The requets type (read or write) * * @return a boolean set to true if it is allowed to execute * the command. Otherwise, returns false */ inline virtual bool is_allowed(Tango::DeviceImpl *dev, Tango::AttReqType ty) { return PyAttr::is_allowed(dev, ty); } /** * Read one attribute. This method forward the action to the python method. * * @param[in] dev The device on which the attribute has to be read * @param[in, out] att the attribute */ inline virtual void read(Tango::DeviceImpl *dev, Tango::Attribute &att) { return PyAttr::read(dev, att); } /** * Write one attribute. This method forward the action to the python method. * * @param[in] dev The device on which the attribute has to be written * @param[in, out] att the attribute */ virtual void write(Tango::DeviceImpl *dev, Tango::WAttribute &att) { return PyAttr::write(dev, att); } }; //------------------------------------------------------------------------------------------------ class PySpecAttr: public Tango::SpectrumAttr, public PyAttr { public: /** * Python Spectrum Attribute constructor * * @param[in] na The attribute name * @param[in] type The attribute data type * @param[in] w The attribute writable type * @param[in] xx The attribute max dim x */ PySpecAttr(const std::string &na, long type, Tango::AttrWriteType w, long xx) : Tango::SpectrumAttr(na.c_str(), type, w, xx) {} /** * Python Spectrum Attribute constructor * * @param[in] na The attribute name * @param[in] type The attribute data type * @param[in] w The attribute writable type * @param[in] xx The attribute max dim x * @param[in] user_prop The attribute user default properties */ PySpecAttr(const std::string &na, long type, Tango::AttrWriteType w, long xx, std::vector &user_prop) : Tango::SpectrumAttr(na.c_str(), type, w, xx) { if (user_prop.size() == 0) return; Tango::UserDefaultAttrProp def_prop; set_user_prop(user_prop,def_prop); set_default_properties(def_prop); } /** * Python Spectrum Attribute destructor */ ~PySpecAttr() {} /** * Decide if it is allowed to read/write the attribute * * @param[in] dev The device on which the attribute has to be read/written * @param[in] ty The requets type (read or write) * * @return a boolean set to true if it is allowed to execute * the command. Otherwise, returns false */ inline virtual bool is_allowed(Tango::DeviceImpl *dev, Tango::AttReqType ty) { return PyAttr::is_allowed(dev, ty); } /** * Read one attribute. This method forward the action to the python method. * * @param[in] dev The device on which the attribute has to be read * @param[in, out] att the attribute */ inline virtual void read(Tango::DeviceImpl *dev, Tango::Attribute &att) { return PyAttr::read(dev, att); } /** * Write one attribute. This method forward the action to the python method. * * @param[in] dev The device on which the attribute has to be written * @param[in, out] att the attribute */ virtual void write(Tango::DeviceImpl *dev, Tango::WAttribute &att) { return PyAttr::write(dev, att); } }; //------------------------------------------------------------------------------------------------ class PyImaAttr: public Tango::ImageAttr, public PyAttr { public: /** * Python Image Attribute constructor * * @param[in] na The attribute name * @param[in] type The attribute data type * @param[in] w The attribute writable type * @param[in] xx The attribute max dim x * @param[in] yy The attribute max dim y */ PyImaAttr(const std::string &na, long type, Tango::AttrWriteType w, long xx, long yy) : Tango::ImageAttr(na.c_str(), type, w, xx, yy) {} /** * Python Image Attribute constructor * * @param[in] na The attribute name * @param[in] type The attribute data type * @param[in] w The attribute writable type * @param[in] xx The attribute max dim x * @param[in] yy The attribute max dim y * @param[in] user_prop The attribute user default properties */ PyImaAttr(const std::string &na, long type, Tango::AttrWriteType w, long xx, long yy, std::vector &user_prop) : Tango::ImageAttr(na.c_str(), type, w, xx, yy) { if (user_prop.size() == 0) return; Tango::UserDefaultAttrProp def_prop; set_user_prop(user_prop,def_prop); set_default_properties(def_prop); } /** * Python Image Attribute destructor */ ~PyImaAttr() {} /** * Decide if it is allowed to read/write the attribute * * @param[in] dev The device on which the attribute has to be read/written * @param[in] ty The requets type (read or write) * * @return a boolean set to true if it is allowed to execute * the command. Otherwise, returns false */ inline virtual bool is_allowed(Tango::DeviceImpl *dev, Tango::AttReqType ty) { return PyAttr::is_allowed(dev, ty); } /** * Read one attribute. This method forward the action to the python method. * * @param[in] dev The device on which the attribute has to be read * @param[in, out] att the attribute */ inline virtual void read(Tango::DeviceImpl *dev, Tango::Attribute &att) { return PyAttr::read(dev, att); } /** * Write one attribute. This method forward the action to the python method. * * @param[in] dev The device on which the attribute has to be written * @param[in, out] att the attribute */ virtual void write(Tango::DeviceImpl *dev, Tango::WAttribute &att) { return PyAttr::write(dev, att); } }; #endif pytango-9.2.2/ext/server/attribute.cpp000066400000000000000000001041441316324373100200310ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include "defs.h" #include "pytgutils.h" #include "attribute.h" #include "fast_from_py.h" using namespace boost::python; # ifdef WIN32 # define PYTG_TIME_FROM_DOUBLE(dbl, tv) \ if (true) { \ tv.time = (time_t)floor(dbl); \ tv.millitm = (unsigned short)((dbl - tv.time)*1.0e3); \ } else (void)0 # define PYTG_NEW_TIME_FROM_DOUBLE(dbl, tv) \ struct _timeb tv; PYTG_TIME_FROM_DOUBLE(dbl, tv) # else # define PYTG_TIME_FROM_DOUBLE(dbl, tv) double2timeval(tv, dbl); # define PYTG_NEW_TIME_FROM_DOUBLE(dbl, tv) \ struct timeval tv; PYTG_TIME_FROM_DOUBLE(dbl, tv) #endif inline static void throw_wrong_python_data_type(const std::string &att_name, const char *method) { TangoSys_OMemStream o; o << "Wrong Python type for attribute " << att_name << ends; Tango::Except::throw_exception( (const char *)"PyDs_WrongPythonDataTypeForAttribute", o.str(), method); } inline static void throw_wrong_python_data_type_in_array(const std::string &att_name, long idx, const char *method) { TangoSys_OMemStream o; o << "Wrong Python type for attribute " << att_name << ".\nElement with index " << idx << " in sequence does not " << "have a correct type." << ends; Tango::Except::throw_exception( (const char *)"PyDs_WrongPythonDataTypeForAttribute", o.str(), method); } extern long TANGO_VERSION_HEX; namespace PyAttribute { /** * Tango Attribute set_value wrapper for scalar attributes * * @param att attribute reference * @param value new attribute value */ template inline void __set_value_scalar(Tango::Attribute &att, bopy::object &value) { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; /* I hate doing this because tango inside is doing a new again when set_value_date_quality is invoked with the release flag to true the other option would be to use per thread tango data like it was done in v3.0.4 I prefer this one since it decouples TangoC++ from PyTango and creating a scalar is not so expensive after all */ unique_pointer cpp_val(new TangoScalarType); from_py::convert(value.ptr(), *cpp_val); att.set_value(cpp_val.release(), 1, 0, true); } /** * Tango Attribute set_value wrapper for DevEncoded attributes * * @param att attribute reference * @param data_str new attribute data string * @param data new attribute data */ inline void __set_value(Tango::Attribute &att, bopy::str &data_str, bopy::str &data) { extract val_str(data_str.ptr()); if (!val_str.check()) { throw_wrong_python_data_type(att.get_name(), "set_value()"); } extract val(data.ptr()); if (!val.check()) { throw_wrong_python_data_type(att.get_name(), "set_value()"); } Tango::DevString val_str_real = val_str; Tango::DevString val_real = val; att.set_value(&val_str_real, (Tango::DevUChar*)val_real, (long)len(data)); } /** * Tango Attribute set_value wrapper for DevEncoded attributes * * @param att attribute reference * @param data_str new attribute data string * @param data new attribute data */ inline void __set_value(Tango::Attribute &att, bopy::str &data_str, bopy::object &data) { extract val_str(data_str.ptr()); if (!val_str.check()) { throw_wrong_python_data_type(att.get_name(), "set_value()"); } PyObject* data_ptr = data.ptr(); Py_buffer view; if (PyObject_GetBuffer(data_ptr, &view, PyBUF_FULL_RO) < 0) { throw_wrong_python_data_type(att.get_name(), "set_value()"); } Tango::DevString val_str_real = val_str; att.set_value(&val_str_real, (Tango::DevUChar*)view.buf, (long)view.len); PyBuffer_Release(&view); } /** * Tango Attribute set_value_date_quality wrapper for scalar attributes * * @param att attribute reference * @param value new attribute value * @param t timestamp * @param quality attribute quality */ template inline void __set_value_date_quality_scalar(Tango::Attribute &att, bopy::object &value, double t, Tango::AttrQuality quality) { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; PYTG_NEW_TIME_FROM_DOUBLE(t, tv); /* I hate doing because tango inside is doing a new again when set_value_date_quality is invoked with the release flag to true the other option would be to use per thread tango data like it was done in v3.0.4 I prefer this one since it decouples TangoC++ from PyTango and creating a scalar is not so expensive after all */ unique_pointer cpp_val(new TangoScalarType); from_py::convert(value, *cpp_val); att.set_value_date_quality(cpp_val.release(), tv, quality, 1, 0, true); } /** * Tango Attribute set_value_date_quality wrapper for DevEncoded attributes * * @param att attribute reference * @param data_str new attribute data string * @param data new attribute data * @param t timestamp * @param quality attribute quality */ inline void __set_value_date_quality(Tango::Attribute &att, bopy::str &data_str, bopy::str &data, double t, Tango::AttrQuality quality) { extract val_str(data_str.ptr()); if (!val_str.check()) { throw_wrong_python_data_type(att.get_name(), "set_value1()"); } extract val(data.ptr()); if (!val.check()) { throw_wrong_python_data_type(att.get_name(), "set_value2()"); } PYTG_NEW_TIME_FROM_DOUBLE(t, tv); Tango::DevString val_str_real = val_str; Tango::DevString val_real = val; att.set_value_date_quality(&val_str_real, (Tango::DevUChar*)val_real, (long)len(data), tv, quality); } /** * Tango Attribute set_value_date_quality wrapper for DevEncoded attributes * * @param att attribute reference * @param data_str new attribute data string * @param data new attribute data * @param t timestamp * @param quality attribute quality */ inline void __set_value_date_quality(Tango::Attribute &att, bopy::str &data_str, bopy::object &data, double t, Tango::AttrQuality quality) { extract val_str(data_str.ptr()); if (!val_str.check()) { throw_wrong_python_data_type(att.get_name(), "set_value1()"); } PyObject* data_ptr = data.ptr(); Py_buffer view; if (PyObject_GetBuffer(data_ptr, &view, PyBUF_FULL_RO) < 0) { throw_wrong_python_data_type(att.get_name(), "set_value()"); } PYTG_NEW_TIME_FROM_DOUBLE(t, tv); Tango::DevString val_str_real = val_str; att.set_value(&val_str_real, (Tango::DevUChar*)view.buf, (long)view.len); att.set_value_date_quality(&val_str_real, (Tango::DevUChar*)view.buf, (long)view.len, tv, quality); PyBuffer_Release(&view); } template void __set_value_date_quality_array( Tango::Attribute& att, bopy::object &value, double time, Tango::AttrQuality* quality, long* x, long* y, const std::string &fname, bool isImage) { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; if (!PySequence_Check(value.ptr())) { // avoid bug in tango 7.0 to 7.1.1: DevEncoded is not defined in CmdArgTypeName const char *arg_type = tangoTypeConst == Tango::DEV_ENCODED ? "DevEncoded" : Tango::CmdArgTypeName[tangoTypeConst]; TangoSys_OMemStream o; o << "Wrong Python type for attribute " << att.get_name() << " of type " << arg_type << ". Expected a sequence." << ends; Tango::Except::throw_exception( "PyDs_WrongPythonDataTypeForAttribute", o.str(), fname + "()"); } TangoScalarType* data_buffer; long res_dim_x=0, res_dim_y=0; data_buffer = fast_python_to_tango_buffer( value.ptr(), x, y, fname, isImage, res_dim_x, res_dim_y); static const bool release = true; if (quality) { PYTG_NEW_TIME_FROM_DOUBLE(time, tv); att.set_value_date_quality( data_buffer, tv, *quality, res_dim_x, res_dim_y, release); } else { att.set_value(data_buffer, res_dim_x, res_dim_y, release); } } inline void __set_value(const std::string & fname, Tango::Attribute &att, bopy::object &value, long* x, long *y, double t = 0.0, Tango::AttrQuality* quality = 0) { long type = att.get_data_type(); Tango::AttrDataFormat format = att.get_data_format(); const bool isScalar = (format == Tango::SCALAR); const bool isImage = (format == Tango::IMAGE); if (isScalar) { if ((x && ((*x) > 1)) || (y && (*y) > 0)) { TangoSys_OMemStream o; o << "Cannot call " << fname; if (y) o << "(data, dim_x, dim_y) on scalar attribute "; else o << "(data, dim_x) on scalar attribute "; if (quality) o << att.get_name() << ". Use set_value_date_quality(data) instead" << ends; else o << att.get_name() << ". Use set_value(data) instead" << ends; Tango::Except::throw_exception( "PyDs_WrongPythonDataTypeForAttribute", o.str(), fname + "()"); } else { if (quality) TANGO_CALL_ON_ATTRIBUTE_DATA_TYPE_ID(type, __set_value_date_quality_scalar, att, value, t, *quality); else TANGO_CALL_ON_ATTRIBUTE_DATA_TYPE_ID(type, __set_value_scalar, att, value); } } else { TANGO_CALL_ON_ATTRIBUTE_DATA_TYPE_ID(type, __set_value_date_quality_array, att, value, t, quality, x, y, fname, isImage); } } inline void __set_value(const std::string & fname, Tango::Attribute &att, bopy::str &data_str, bopy::str &data, double t = 0.0, Tango::AttrQuality* quality = 0) { if (quality) __set_value_date_quality(att, data_str, data, t, *quality); else __set_value(att, data_str, data); } inline void __set_value(const std::string & fname, Tango::Attribute &att, bopy::str &data_str, bopy::object &data, double t = 0.0, Tango::AttrQuality* quality = 0) { if (quality) __set_value_date_quality(att, data_str, data, t, *quality); else __set_value(att, data_str, data); } inline void set_value(Tango::Attribute &att, bopy::object &value) { __set_value("set_value", att, value, 0, 0); } inline void set_value(Tango::Attribute &att, Tango::EncodedAttribute *data) { att.set_value(data); } inline void set_value(Tango::Attribute &att, bopy::str &data_str, bopy::str &data) { __set_value("set_value", att, data_str, data); } inline void set_value(Tango::Attribute &att, bopy::str &data_str, bopy::object &data) { __set_value("set_value", att, data_str, data); } inline void set_value(Tango::Attribute &att, bopy::object &value, long x) { __set_value("set_value", att, value, &x, 0); } inline void set_value(Tango::Attribute &att, bopy::object &value, long x, long y) { __set_value("set_value", att, value, &x, &y); } inline void set_value_date_quality(Tango::Attribute &att, bopy::object &value, double t, Tango::AttrQuality quality) { __set_value("set_value_date_quality", att, value, 0, 0, t, &quality); } inline void set_value_date_quality(Tango::Attribute &att, bopy::str &data_str, bopy::str &data, double t, Tango::AttrQuality quality) { __set_value("set_value_date_quality", att, data_str, data, t, &quality); } inline void set_value_date_quality(Tango::Attribute &att, bopy::str &data_str, bopy::object &data, double t, Tango::AttrQuality quality) { __set_value("set_value_date_quality", att, data_str, data, t, &quality); } inline void set_value_date_quality(Tango::Attribute &att, bopy::object &value, double t, Tango::AttrQuality quality, long x) { __set_value("set_value_date_quality", att, value, &x, 0, t, &quality); } inline void set_value_date_quality(Tango::Attribute &att, bopy::object &value, double t, Tango::AttrQuality quality, long x, long y) { __set_value("set_value_date_quality", att, value, &x, &y, t, &quality); } template inline void _get_properties_multi_attr_prop(Tango::Attribute &att, bopy::object &multi_attr_prop) { Tango::MultiAttrProp tg_multi_attr_prop; att.get_properties(tg_multi_attr_prop); to_py(tg_multi_attr_prop,multi_attr_prop); } inline bopy::object get_properties_multi_attr_prop(Tango::Attribute &att, bopy::object &multi_attr_prop) { long tangoTypeConst = att.get_data_type(); TANGO_CALL_ON_ATTRIBUTE_DATA_TYPE_NAME(tangoTypeConst, _get_properties_multi_attr_prop, att, multi_attr_prop); return multi_attr_prop; } template inline void _set_properties_multi_attr_prop(Tango::Attribute &att, bopy::object &multi_attr_prop) { Tango::MultiAttrProp tg_multi_attr_prop; from_py_object(multi_attr_prop,tg_multi_attr_prop); att.set_properties(tg_multi_attr_prop); } void set_properties_multi_attr_prop(Tango::Attribute &att, bopy::object &multi_attr_prop) { long tangoTypeConst = att.get_data_type(); TANGO_CALL_ON_ATTRIBUTE_DATA_TYPE_NAME(tangoTypeConst, _set_properties_multi_attr_prop, att, multi_attr_prop); } void set_upd_properties(Tango::Attribute &att, bopy::object &attr_cfg) { Tango::AttributeConfig_3 tg_attr_cfg; from_py_object(attr_cfg, tg_attr_cfg); att.set_upd_properties(tg_attr_cfg); } void set_upd_properties(Tango::Attribute &att, bopy::object &attr_cfg, bopy::object &dev_name) { Tango::AttributeConfig_3 tg_attr_cfg; from_py_object(attr_cfg, tg_attr_cfg); string tg_dev_name = bopy::extract(dev_name); att.set_upd_properties(tg_attr_cfg,tg_dev_name); } inline void fire_change_event(Tango::Attribute &self) { self.fire_change_event(); } inline void fire_change_event(Tango::Attribute &self, object &data) { bopy::extract except_convert(data); if (except_convert.check()) { self.fire_change_event( const_cast( &except_convert() )); return; } TangoSys_OMemStream o; o << "Wrong Python argument type for attribute " << self.get_name() << ". Expected a DevFailed." << ends; Tango::Except::throw_exception( "PyDs_WrongPythonDataTypeForAttribute", o.str(), "fire_change_event()"); } // usually not necessary to rewrite but with direct declaration the compiler // gives an error. It seems to be because the tango method definition is not // in the header file. inline bool is_polled(Tango::Attribute &self) { return self.is_polled(); } template inline void _set_min_alarm(Tango::Attribute &self, bopy::object value) { TangoScalarType c_value = bopy::extract(value); self.set_min_alarm(c_value); } #if TANGO_VERSION_NB < 80100 // set_min_alarm template<> inline void _set_min_alarm(Tango::Attribute &self, bopy::object value) { string err_msg = "Attribute properties cannot be set with Tango::DevEncoded data type"; Tango::Except::throw_exception((const char *)"API_MethodArgument", (const char *)err_msg.c_str(), (const char *)"Attribute::set_min_alarm()"); } #endif // set_min_alarm inline void set_min_alarm(Tango::Attribute &self, bopy::object value) { bopy::extract value_convert(value); if (value_convert.check()) { self.set_min_alarm(value_convert()); } else { long tangoTypeConst = self.get_data_type(); // TODO: the below line is a neat trick to properly raise a Tango exception if a property is set // for one of the forbidden attribute data types; code dependent on Tango C++ implementation if(tangoTypeConst == Tango::DEV_STRING || tangoTypeConst == Tango::DEV_BOOLEAN || tangoTypeConst == Tango::DEV_STATE) tangoTypeConst = Tango::DEV_DOUBLE; else if(tangoTypeConst == Tango::DEV_ENCODED) tangoTypeConst = Tango::DEV_UCHAR; TANGO_CALL_ON_ATTRIBUTE_DATA_TYPE_NAME(tangoTypeConst, _set_min_alarm, self, value); } } template inline void _set_max_alarm(Tango::Attribute &self, bopy::object value) { TangoScalarType c_value = bopy::extract(value); self.set_max_alarm(c_value); } #if TANGO_VERSION_NB < 80100 // set_max_alarm template<> inline void _set_max_alarm(Tango::Attribute &self, bopy::object value) { string err_msg = "Attribute properties cannot be set with Tango::DevEncoded data type"; Tango::Except::throw_exception((const char *)"API_MethodArgument", (const char *)err_msg.c_str(), (const char *)"Attribute::set_max_alarm()"); } #endif // set_max_alarm inline void set_max_alarm(Tango::Attribute &self, bopy::object value) { bopy::extract value_convert(value); if (value_convert.check()) { self.set_max_alarm(value_convert()); } else { long tangoTypeConst = self.get_data_type(); // TODO: the below line is a neat trick to properly raise a Tango exception if a property is set // for one of the forbidden attribute data types; code dependent on Tango C++ implementation if(tangoTypeConst == Tango::DEV_STRING || tangoTypeConst == Tango::DEV_BOOLEAN || tangoTypeConst == Tango::DEV_STATE) tangoTypeConst = Tango::DEV_DOUBLE; else if(tangoTypeConst == Tango::DEV_ENCODED) tangoTypeConst = Tango::DEV_UCHAR; TANGO_CALL_ON_ATTRIBUTE_DATA_TYPE_NAME(tangoTypeConst, _set_max_alarm, self, value); } } template inline void _set_min_warning(Tango::Attribute &self, bopy::object value) { TangoScalarType c_value = bopy::extract(value); self.set_min_warning(c_value); } #if TANGO_VERSION_NB < 80100 // set_min_warning template<> inline void _set_min_warning(Tango::Attribute &self, bopy::object value) { string err_msg = "Attribute properties cannot be set with Tango::DevEncoded data type"; Tango::Except::throw_exception((const char *)"API_MethodArgument", (const char *)err_msg.c_str(), (const char *)"Attribute::set_min_warning()"); } #endif // set_min_warning inline void set_min_warning(Tango::Attribute &self, bopy::object value) { bopy::extract value_convert(value); if (value_convert.check()) { self.set_min_warning(value_convert()); } else { long tangoTypeConst = self.get_data_type(); // TODO: the below line is a neat trick to properly raise a Tango exception if a property is set // for one of the forbidden attribute data types; code dependent on Tango C++ implementation if(tangoTypeConst == Tango::DEV_STRING || tangoTypeConst == Tango::DEV_BOOLEAN || tangoTypeConst == Tango::DEV_STATE) tangoTypeConst = Tango::DEV_DOUBLE; else if(tangoTypeConst == Tango::DEV_ENCODED) tangoTypeConst = Tango::DEV_UCHAR; TANGO_CALL_ON_ATTRIBUTE_DATA_TYPE_NAME(tangoTypeConst, _set_min_warning, self, value); } } template inline void _set_max_warning(Tango::Attribute &self, bopy::object value) { TangoScalarType c_value = bopy::extract(value); self.set_max_warning(c_value); } #if TANGO_VERSION_NB < 80100 // set_max_warning template<> inline void _set_max_warning(Tango::Attribute &self, bopy::object value) { string err_msg = "Attribute properties cannot be set with Tango::DevEncoded data type"; Tango::Except::throw_exception((const char *)"API_MethodArgument", (const char *)err_msg.c_str(), (const char *)"Attribute::set_max_warning()"); } #endif // set_max_warning inline void set_max_warning(Tango::Attribute &self, bopy::object value) { bopy::extract value_convert(value); if (value_convert.check()) { self.set_max_warning(value_convert()); } else { long tangoTypeConst = self.get_data_type(); // TODO: the below line is a neat trick to properly raise a Tango exception if a property is set // for one of the forbidden attribute data types; code dependent on Tango C++ implementation if(tangoTypeConst == Tango::DEV_STRING || tangoTypeConst == Tango::DEV_BOOLEAN || tangoTypeConst == Tango::DEV_STATE) tangoTypeConst = Tango::DEV_DOUBLE; else if(tangoTypeConst == Tango::DEV_ENCODED) tangoTypeConst = Tango::DEV_UCHAR; TANGO_CALL_ON_ATTRIBUTE_DATA_TYPE_NAME(tangoTypeConst, _set_max_warning, self, value); } } template PyObject* __get_min_alarm(Tango::Attribute &att) { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; TangoScalarType tg_val; att.get_min_alarm(tg_val); bopy::object py_value(tg_val); return bopy::incref(py_value.ptr()); } PyObject *get_min_alarm(Tango::Attribute &att) { long tangoTypeConst = att.get_data_type(); if(tangoTypeConst == Tango::DEV_ENCODED) tangoTypeConst = Tango::DEV_UCHAR; TANGO_CALL_ON_ATTRIBUTE_DATA_TYPE_ID(tangoTypeConst, return __get_min_alarm, att); return 0; } template PyObject* __get_max_alarm(Tango::Attribute &att) { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; TangoScalarType tg_val; att.get_max_alarm(tg_val); bopy::object py_value(tg_val); return bopy::incref(py_value.ptr()); } PyObject *get_max_alarm(Tango::Attribute &att) { long tangoTypeConst = att.get_data_type(); if(tangoTypeConst == Tango::DEV_ENCODED) tangoTypeConst = Tango::DEV_UCHAR; TANGO_CALL_ON_ATTRIBUTE_DATA_TYPE_ID(tangoTypeConst, return __get_max_alarm, att); return 0; } template PyObject* __get_min_warning(Tango::Attribute &att) { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; TangoScalarType tg_val; att.get_min_warning(tg_val); bopy::object py_value(tg_val); return bopy::incref(py_value.ptr()); } PyObject *get_min_warning(Tango::Attribute &att) { long tangoTypeConst = att.get_data_type(); if(tangoTypeConst == Tango::DEV_ENCODED) tangoTypeConst = Tango::DEV_UCHAR; TANGO_CALL_ON_ATTRIBUTE_DATA_TYPE_ID(tangoTypeConst, return __get_min_warning, att); return 0; } template PyObject* __get_max_warning(Tango::Attribute &att) { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; TangoScalarType tg_val; att.get_max_warning(tg_val); bopy::object py_value(tg_val); return bopy::incref(py_value.ptr()); } PyObject *get_max_warning(Tango::Attribute &att) { long tangoTypeConst = att.get_data_type(); if(tangoTypeConst == Tango::DEV_ENCODED) tangoTypeConst = Tango::DEV_UCHAR; TANGO_CALL_ON_ATTRIBUTE_DATA_TYPE_ID(tangoTypeConst, return __get_max_warning, att); return 0; } }; BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(set_quality_overloads, Tango::Attribute::set_quality, 1, 2); BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(set_change_event_overloads, Tango::Attribute::set_change_event, 1, 2); BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(set_archive_event_overloads, Tango::Attribute::set_archive_event, 1, 2); void export_attribute() { enum_("alarm_flags") .value("min_level", Tango::Attribute::min_level) .value("max_level", Tango::Attribute::max_level) .value("rds", Tango::Attribute::rds) .value("min_warn", Tango::Attribute::min_warn) .value("max_warn", Tango::Attribute::max_warn) .value("numFlags", Tango::Attribute::numFlags) ; class_("Attribute", no_init) .def("is_write_associated", &Tango::Attribute::is_writ_associated) .def("is_min_alarm", &Tango::Attribute::is_min_alarm) .def("is_max_alarm", &Tango::Attribute::is_max_alarm) .def("is_min_warning", &Tango::Attribute::is_min_warning) .def("is_max_warning", &Tango::Attribute::is_max_warning) .def("is_rds_alarm", &Tango::Attribute::is_rds_alarm) .def("is_polled", &PyAttribute::is_polled) .def("check_alarm", &Tango::Attribute::check_alarm) .def("get_writable", &Tango::Attribute::get_writable) .def("get_name", &Tango::Attribute::get_name, return_value_policy()) .def("get_data_type", &Tango::Attribute::get_data_type) .def("get_data_format", &Tango::Attribute::get_data_format) .def("get_assoc_name", &Tango::Attribute::get_assoc_name, return_value_policy()) .def("get_assoc_ind", &Tango::Attribute::get_assoc_ind) .def("set_assoc_ind", &Tango::Attribute::set_assoc_ind) .def("get_date", &Tango::Attribute::get_date, return_internal_reference<>()) .def("set_date", (void (Tango::Attribute::*) (Tango::TimeVal &)) &Tango::Attribute::set_date) .def("get_label", &Tango::Attribute::get_label, return_value_policy()) .def("get_quality", &Tango::Attribute::get_quality, return_value_policy()) .def("set_quality", &Tango::Attribute::set_quality, set_quality_overloads()) .def("get_data_size", &Tango::Attribute::get_data_size) .def("get_x", &Tango::Attribute::get_x) .def("get_max_dim_x", &Tango::Attribute::get_max_dim_x) .def("get_y", &Tango::Attribute::get_y) .def("get_max_dim_y", &Tango::Attribute::get_max_dim_y) .def("get_polling_period", &Tango::Attribute::get_polling_period) .def("set_attr_serial_model", &Tango::Attribute::set_attr_serial_model) .def("get_attr_serial_model", &Tango::Attribute::get_attr_serial_model) .def("set_min_alarm", &PyAttribute::set_min_alarm) .def("set_max_alarm", &PyAttribute::set_max_alarm) .def("set_min_warning", &PyAttribute::set_min_warning) .def("set_max_warning", &PyAttribute::set_max_warning) .def("get_value_flag", &Tango::Attribute::get_value_flag) .def("set_value_flag", &Tango::Attribute::set_value_flag) .def("get_disp_level", &Tango::Attribute::get_disp_level) .def("change_event_subscribed", &Tango::Attribute::change_event_subscribed) .def("periodic_event_subscribed", &Tango::Attribute::periodic_event_subscribed) .def("archive_event_subscribed", &Tango::Attribute::archive_event_subscribed) .def("quality_event_subscribed", &Tango::Attribute::quality_event_subscribed) .def("user_event_subscribed", &Tango::Attribute::user_event_subscribed) .def("use_notifd_event", &Tango::Attribute::use_notifd_event) .def("use_zmq_event", &Tango::Attribute::use_zmq_event) .def("get_min_alarm", (PyObject* (*) (Tango::Attribute &)) &PyAttribute::get_min_alarm) .def("get_max_alarm", (PyObject* (*) (Tango::Attribute &)) &PyAttribute::get_max_alarm) .def("get_min_warning", (PyObject* (*) (Tango::Attribute &)) &PyAttribute::get_min_warning) .def("get_max_warning", (PyObject* (*) (Tango::Attribute &)) &PyAttribute::get_max_warning) .def("set_value", (void (*) (Tango::Attribute &, bopy::object &)) &PyAttribute::set_value) .def("set_value", (void (*) (Tango::Attribute &, bopy::str &, bopy::object &)) &PyAttribute::set_value) .def("set_value", (void (*) (Tango::Attribute &, bopy::str &, bopy::str &)) &PyAttribute::set_value) .def("set_value", (void (*) (Tango::Attribute &, Tango::EncodedAttribute *)) &PyAttribute::set_value) .def("set_value", (void (*) (Tango::Attribute &, bopy::object &, long)) &PyAttribute::set_value) .def("set_value", (void (*) (Tango::Attribute &, bopy::object &, long, long)) &PyAttribute::set_value) .def("set_value_date_quality", (void (*) (Tango::Attribute &, bopy::object &, double t, Tango::AttrQuality quality)) &PyAttribute::set_value_date_quality) .def("set_value_date_quality", (void (*) (Tango::Attribute &, bopy::str &, bopy::str &, double t, Tango::AttrQuality quality)) &PyAttribute::set_value_date_quality) .def("set_value_date_quality", (void (*) (Tango::Attribute &, bopy::str &, bopy::object &, double t, Tango::AttrQuality quality)) &PyAttribute::set_value_date_quality) .def("set_value_date_quality", (void (*) (Tango::Attribute &, bopy::object &, double t, Tango::AttrQuality quality, long)) &PyAttribute::set_value_date_quality) .def("set_value_date_quality", (void (*) (Tango::Attribute &, bopy::object &, double t, Tango::AttrQuality quality, long, long)) &PyAttribute::set_value_date_quality) .def("set_change_event", &Tango::Attribute::set_change_event, set_change_event_overloads()) .def("set_archive_event", &Tango::Attribute::set_archive_event, set_archive_event_overloads()) .def("is_change_event", &Tango::Attribute::is_change_event) .def("is_check_change_criteria", &Tango::Attribute::is_check_change_criteria) .def("is_archive_event", &Tango::Attribute::is_archive_event) .def("is_check_archive_criteria", &Tango::Attribute::is_check_archive_criteria) .def("set_data_ready_event", &Tango::Attribute::set_data_ready_event) .def("is_data_ready_event", &Tango::Attribute::is_data_ready_event) .def("remove_configuration", &Tango::Attribute::remove_configuration) /* .def("_get_properties", &PyAttribute::get_properties) .def("_get_properties_2", &PyAttribute::get_properties_2) .def("_get_properties_3", &PyAttribute::get_properties_3) */ .def("_get_properties_multi_attr_prop", &PyAttribute::get_properties_multi_attr_prop) /* .def("_set_properties", &PyAttribute::set_properties) .def("_set_properties_3", &PyAttribute::set_properties_3) */ .def("_set_properties_multi_attr_prop", &PyAttribute::set_properties_multi_attr_prop) .def("set_upd_properties", (void (*) (Tango::Attribute &, bopy::object &)) &PyAttribute::set_upd_properties) .def("set_upd_properties", (void (*) (Tango::Attribute &, bopy::object &, bopy::object &)) &PyAttribute::set_upd_properties) .def("fire_change_event", (void (*) (Tango::Attribute &)) &PyAttribute::fire_change_event) .def("fire_change_event", (void (*) (Tango::Attribute &, bopy::object &)) &PyAttribute::fire_change_event) ; } pytango-9.2.2/ext/server/attribute.h000066400000000000000000000054461316324373100175030ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #ifndef _ATTRIBUTE_H_ #define _ATTRIBUTE_H_ #include #include namespace PyAttribute { void set_value(Tango::Attribute &, boost::python::object &); void set_value(Tango::Attribute &, boost::python::str &, boost::python::str &); void set_value(Tango::Attribute &, boost::python::str &, boost::python::object &); void set_value(Tango::Attribute &, boost::python::object &, long); void set_value(Tango::Attribute &, boost::python::object &, long, long); void set_value_date_quality(Tango::Attribute &, boost::python::object &, double, Tango::AttrQuality); void set_value_date_quality(Tango::Attribute &, boost::python::str &, boost::python::str &, double, Tango::AttrQuality); void set_value_date_quality(Tango::Attribute &, boost::python::str &, boost::python::object &, double, Tango::AttrQuality); void set_value_date_quality(Tango::Attribute &, boost::python::object &, double, Tango::AttrQuality , long); void set_value_date_quality(Tango::Attribute &, boost::python::object &, double, Tango::AttrQuality , long, long); boost::python::object get_properties(Tango::Attribute &, boost::python::object &); boost::python::object get_properties_2(Tango::Attribute &, boost::python::object &); boost::python::object get_properties_3(Tango::Attribute &, boost::python::object &); boost::python::object get_properties_multi_attr_prop(Tango::Attribute &, boost::python::object &); void set_properties(Tango::Attribute &, boost::python::object &, boost::python::object &); void set_properties_3(Tango::Attribute &, boost::python::object &, boost::python::object &); void set_properties_multi_attr_prop(Tango::Attribute &, boost::python::object &); }; #endif // _ATTRIBUTE_H_ pytango-9.2.2/ext/server/auto_monitor.cpp000066400000000000000000000064261316324373100205510ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include "defs.h" #include "pytgutils.h" namespace PyTango { class AutoTangoMonitor { Tango::AutoTangoMonitor *mon; Tango::DeviceImpl *dev; Tango::DeviceClass *klass; public: AutoTangoMonitor(Tango::DeviceImpl *dev_arg) : mon(), dev(), klass() { dev = dev_arg; } AutoTangoMonitor(Tango::DeviceClass *klass_arg) : mon(), dev(), klass() { klass = klass_arg; } void acquire() { if (mon != NULL) { return; } if (dev != NULL) { AutoPythonAllowThreads no_gil; mon = new Tango::AutoTangoMonitor(dev); } else if (klass != NULL) { AutoPythonAllowThreads no_gil; mon = new Tango::AutoTangoMonitor(klass); } } void release() { if (mon != NULL) { delete mon; mon = NULL; } } ~AutoTangoMonitor() { release(); } }; class AutoTangoAllowThreads { public: AutoTangoAllowThreads(Tango::DeviceImpl *dev): count(0) { Tango::Util* util = Tango::Util::instance(); Tango::SerialModel ser = util->get_serial_model(); switch(ser) { case Tango::BY_DEVICE: mon = &(dev->get_dev_monitor()); break; case Tango::BY_CLASS: //mon = &(dev->device_class->ext->only_one); break; case Tango::BY_PROCESS: //mon = &(util->ext->only_one); break; default: mon = NULL; } release(); } void acquire() { if (mon == NULL) return; AutoPythonAllowThreads no_gil; for(int i=0; i < count; ++i) { mon->get_monitor(); } } protected: void release() { if (mon == NULL) return; int cur_thread = omni_thread::self()->id(); int mon_thread = mon->get_locking_thread_id(); int lock_count = mon->get_locking_ctr(); // do something only if the monitor was taken by the current thread if ((mon_thread == cur_thread) && lock_count) { while (lock_count > 0) { mon->rel_monitor(); lock_count = mon->get_locking_ctr(); count++; } } else { mon = NULL; } } private: Tango::TangoMonitor *mon; int count; omni_thread::ensure_self auto_self; }; } // namespace PyTango void export_auto_tango_monitor() { bopy::class_( "AutoTangoMonitor", bopy::init()) .def(bopy::init()) .def("_acquire", &PyTango::AutoTangoMonitor::acquire) .def("_release", &PyTango::AutoTangoMonitor::release) ; bopy::class_( "AutoTangoAllowThreads", bopy::init()) .def("_acquire", &PyTango::AutoTangoAllowThreads::acquire); ; } pytango-9.2.2/ext/server/command.cpp000066400000000000000000000236521316324373100174500ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include "exception.h" #include "pytgutils.h" #include "fast_from_py.h" #include "server/device_impl.h" #include "server/command.h" #ifndef DISABLE_PYTANGO_NUMPY # include "to_py_numpy.hpp" #endif #include using namespace boost::python; //+------------------------------------------------------------------------- // // method : PyCmd::is_allowed // // description : Decide if it is allowed to execute the command // // argin : - dev : The device on which the command has to be excuted // - any : The input data // // This method returns a boolean set to True if it is allowed to execute // the command. Otherwise, returns false // //-------------------------------------------------------------------------- bool PyCmd::is_allowed(Tango::DeviceImpl *dev, const CORBA::Any &any) { if (py_allowed_defined == true) { PyDeviceImplBase *dev_ptr = dynamic_cast(dev); //Device_4ImplWrap *dev_ptr = static_cast(dev); AutoPythonGIL __py_lock; bool returned_value = true; try { returned_value = call_method(dev_ptr->the_self, py_allowed_name.c_str()); } catch(error_already_set &eas) { handle_python_exception(eas); } return returned_value; } else return true; } void allocate_any(CORBA::Any *&any_ptr) { try { any_ptr = new CORBA::Any(); } catch (bad_alloc) { Tango::Except::throw_exception( "API_MemoryAllocation", "Can't allocate memory in server", "PyCmd::allocate_any()"); } } void throw_bad_type(const char *type) { TangoSys_OMemStream o; o << "Incompatible command argument type, expected type is : Tango::" << type << ends; Tango::Except::throw_exception( "API_IncompatibleCmdArgumentType", o.str(), "PyCmd::extract()"); } template void insert_scalar(boost::python::object &o, CORBA::Any &any) { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; any <<= extract(o); } template<> void insert_scalar(boost::python::object &o, CORBA::Any &any) {} template<> void insert_scalar(boost::python::object &o, CORBA::Any &any) { Tango::DevBoolean value = extract(o); CORBA::Any::from_boolean any_value(value); any <<= any_value; } template<> void insert_scalar(boost::python::object &o, CORBA::Any &any) { bopy::object p0 = o[0]; bopy::object p1 = o[1]; const char* encoded_format = bopy::extract (p0.ptr()); PyObject* data_ptr = p1.ptr(); Py_buffer view; if (PyObject_GetBuffer(data_ptr, &view, PyBUF_FULL_RO) < 0) { throw_bad_type(Tango::CmdArgTypeName[Tango::DEV_ENCODED]); } CORBA::ULong nb = static_cast(view.len); Tango::DevVarCharArray arr(nb, nb, (CORBA::Octet*)view.buf, false); Tango::DevEncoded *data = new Tango::DevEncoded; data->encoded_format = CORBA::string_dup(encoded_format); data->encoded_data = arr; any <<= data; PyBuffer_Release(&view); } template<> void insert_scalar(boost::python::object &o, CORBA::Any &any) { assert(false); } template void insert_array(boost::python::object &o, CORBA::Any &any) { typedef typename TANGO_const2type(tangoArrayTypeConst) TangoArrayType; // Destruction will be handled by CORBA, not by Tango. TangoArrayType* data = fast_convert2array(o); // By giving a pointer to <<= we are giving ownership of the data // buffer to CORBA any <<= data; } template<> void insert_array(boost::python::object &o, CORBA::Any &any) { assert(false); } template void extract_scalar(const CORBA::Any &any, boost::python::object &o) { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; TangoScalarType data; if ((any >>= data) == false) throw_bad_type(Tango::CmdArgTypeName[tangoTypeConst]); o = object(data); } template<> void extract_scalar(const CORBA::Any &any, boost::python::object &o) { Tango::ConstDevString data; if ((any >>= data) == false) throw_bad_type(Tango::CmdArgTypeName[Tango::DEV_STRING]); o = object(data); } template<> void extract_scalar(const CORBA::Any &any, boost::python::object &o) {} template<> void extract_scalar(const CORBA::Any &any, boost::python::object &o) { assert(false); } template<> void extract_scalar(const CORBA::Any &any, boost::python::object &o) { Tango::DevEncoded* data; if ((any >>= data) == false) throw_bad_type(Tango::CmdArgTypeName[Tango::DEV_ENCODED]); bopy::str encoded_format(data[0].encoded_format); bopy::str encoded_data((const char*)data[0].encoded_data.get_buffer(), data[0].encoded_data.length()); o = boost::python::make_tuple(encoded_format, encoded_data); } #ifndef DISABLE_PYTANGO_NUMPY /// This callback is run to delete Tango::DevVarXArray* objects. /// It is called by python. The array was associated with an attribute /// value object that is not being used anymore. /// @param ptr_ The array object. /// @param type_ The type of the array objects. We need it to convert ptr_ /// to the proper type before deleting it. /// ex: Tango::DEVVAR_SHORTARRAY. # ifdef PYCAPSULE_OLD template static void dev_var_x_array_deleter__(void * ptr_) { TANGO_DO_ON_DEVICE_ARRAY_DATA_TYPE_ID(type, delete static_cast(ptr_); ); } # else template static void dev_var_x_array_deleter__(PyObject* obj) { void * ptr_ = PyCapsule_GetPointer(obj, NULL); TANGO_DO_ON_DEVICE_ARRAY_DATA_TYPE_ID(type, delete static_cast(ptr_); ); } #endif #endif template void extract_array(const CORBA::Any &any, boost::python::object &py_result) { typedef typename TANGO_const2type(tangoArrayTypeConst) TangoArrayType; TangoArrayType *tmp_ptr; if ((any >>= tmp_ptr) == false) throw_bad_type(Tango::CmdArgTypeName[tangoArrayTypeConst]); #ifndef DISABLE_PYTANGO_NUMPY // For numpy we need a 'guard' object that handles the memory used // by the numpy object (releases it). // But I cannot manage memory inside our 'any' object, because it is // const and handles it's memory itself. So I need a copy before // creating the object. TangoArrayType* copy_ptr = new TangoArrayType(*tmp_ptr); // numpy.ndarray() does not own it's memory, so we need to manage it. // We can assign a 'base' object that will be informed (decref'd) when // the last copy of numpy.ndarray() disappears. // PyCObject is intended for that kind of things. It's seen as a // black box object from python. We assign him a function to be called // when it is deleted -> the function deletes de data. PyObject* guard = PyCapsule_New( static_cast(copy_ptr), NULL, dev_var_x_array_deleter__); if (!guard ) { delete copy_ptr; throw_error_already_set(); } py_result = to_py_numpy(copy_ptr, object(handle<>(guard))); #else py_result = to_py_list(tmp_ptr); #endif } template<> void extract_array(const CORBA::Any &any, boost::python::object &py_result) { assert(false); } CORBA::Any *PyCmd::execute(Tango::DeviceImpl *dev, const CORBA::Any ¶m_any) { PyDeviceImplBase *dev_ptr = dynamic_cast(dev); AutoPythonGIL python_guard; try { // This call extracts the CORBA any into a python object. // So, the result is that param_py = param_any. // It is done with some template magic. boost::python::object param_py; TANGO_DO_ON_DEVICE_DATA_TYPE_ID(in_type, extract_scalar(param_any, param_py); , extract_array(param_any, param_py); ); // Execute the python call for the command object ret_py_obj; if (in_type == Tango::DEV_VOID) { ret_py_obj = call_method(dev_ptr->the_self, name.c_str()); } else { ret_py_obj = call_method(dev_ptr->the_self, name.c_str(), param_py); } CORBA::Any *ret_any; allocate_any(ret_any); unique_pointer ret_any_guard(ret_any); // It does: ret_any = ret_py_obj TANGO_DO_ON_DEVICE_DATA_TYPE_ID(out_type, insert_scalar(ret_py_obj, *ret_any); , insert_array(ret_py_obj, *ret_any); ); return ret_any_guard.release(); } catch(error_already_set &eas) { handle_python_exception(eas); return 0; // Should not happen, handle_python_exception rethrows in // a Tango friendly manner } } pytango-9.2.2/ext/server/command.h000066400000000000000000000031421316324373100171050ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #ifndef _COMMAND_H_ #define _COMMAND_H_ #include #include class PyCmd : public Tango::Command { public: PyCmd(string &name, Tango::CmdArgType in, Tango::CmdArgType out, string &in_desc, string &out_desc, Tango::DispLevel level) :Tango::Command(name,in,out,in_desc,out_desc, level),py_allowed_defined(false) {}; PyCmd(const char *name, Tango::CmdArgType in, Tango::CmdArgType out) :Tango::Command(name,in,out),py_allowed_defined(false) {}; PyCmd(const char *name, Tango::CmdArgType in, Tango::CmdArgType out, const char *in_desc, const char *out_desc, Tango::DispLevel level) :Tango::Command(name,in,out,in_desc,out_desc, level),py_allowed_defined(false) {}; virtual ~PyCmd() {}; virtual CORBA::Any *execute (Tango::DeviceImpl *dev, const CORBA::Any &any); virtual bool is_allowed (Tango::DeviceImpl *dev, const CORBA::Any &any); void set_allowed(const string &name) {py_allowed_defined=true;py_allowed_name=name;} private: bool py_allowed_defined; string py_allowed_name; }; #endif pytango-9.2.2/ext/server/device_class.cpp000066400000000000000000000372211316324373100204530ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include "pytgutils.h" #include "exception.h" #include "server/device_class.h" #include "server/attr.h" #include "server/command.h" #include "server/pipe.h" using namespace boost::python; #define __AUX_DECL_CALL_DEVCLASS_METHOD \ AutoPythonGIL __py_lock; #define __AUX_CATCH_PY_EXCEPTION \ catch(boost::python::error_already_set &eas) \ { handle_python_exception(eas); } #define CALL_DEVCLASS_METHOD(name) \ __AUX_DECL_CALL_DEVCLASS_METHOD \ try { boost::python::call_method(m_self, #name); } \ __AUX_CATCH_PY_EXCEPTION #define CALL_DEVCLASS_METHOD_VARGS(name, ...) \ __AUX_DECL_CALL_DEVCLASS_METHOD \ try { boost::python::call_method(m_self, #name, __VA_ARGS__); } \ __AUX_CATCH_PY_EXCEPTION CppDeviceClass::CppDeviceClass(const string &name) :Tango::DeviceClass(const_cast(name)) {} CppDeviceClass::~CppDeviceClass() {} void CppDeviceClass::create_command(const std::string &cmd_name, Tango::CmdArgType param_type, Tango::CmdArgType result_type, const std::string ¶m_desc, const std::string &result_desc, Tango::DispLevel display_level, bool default_command, long polling_period, const std::string &is_allowed) { PyCmd *cmd_ptr = new PyCmd(cmd_name.c_str(), param_type, result_type, param_desc.c_str(), result_desc.c_str(), display_level); if (!is_allowed.empty()) { cmd_ptr->set_allowed(is_allowed); } if (polling_period > 0) cmd_ptr->set_polling_period(polling_period); if (default_command) set_default_command(cmd_ptr); else command_list.push_back(cmd_ptr); } void CppDeviceClass::create_fwd_attribute(vector &att_list, const std::string &attr_name, Tango::UserDefaultFwdAttrProp *att_prop) { Tango::FwdAttr *attr_ptr = new Tango::FwdAttr(attr_name); attr_ptr->set_default_properties(*att_prop); att_list.push_back(attr_ptr); } void CppDeviceClass::create_attribute(vector &att_list, const std::string &attr_name, Tango::CmdArgType attr_type, Tango::AttrDataFormat attr_format, Tango::AttrWriteType attr_write, long dim_x, long dim_y, Tango::DispLevel display_level, long polling_period, bool memorized, bool hw_memorized, const std::string &read_method_name, const std::string &write_method_name, const std::string &is_allowed_name, Tango::UserDefaultAttrProp *att_prop) { // // Create the attribute objet according to attribute format // PyScaAttr *sca_attr_ptr = NULL; PySpecAttr *spec_attr_ptr = NULL; PyImaAttr *ima_attr_ptr= NULL; PyAttr *py_attr_ptr = NULL; Tango::Attr *attr_ptr = NULL; switch (attr_format) { case Tango::SCALAR: sca_attr_ptr = new PyScaAttr(attr_name, attr_type, attr_write); py_attr_ptr = sca_attr_ptr; attr_ptr = sca_attr_ptr; break; case Tango::SPECTRUM: spec_attr_ptr = new PySpecAttr(attr_name.c_str(), attr_type, attr_write, dim_x); py_attr_ptr = spec_attr_ptr; attr_ptr = spec_attr_ptr; break; case Tango::IMAGE: ima_attr_ptr = new PyImaAttr(attr_name.c_str(), attr_type, attr_write, dim_x, dim_y); py_attr_ptr = ima_attr_ptr; attr_ptr = ima_attr_ptr; break; default: TangoSys_OMemStream o; o << "Attribute " << attr_name << " has an unexpected data format\n" << "Please report this bug to the PyTango development team" << ends; Tango::Except::throw_exception( (const char *)"PyDs_UnexpectedAttributeFormat", o.str(), (const char *)"create_attribute"); break; } py_attr_ptr->set_read_name(read_method_name); py_attr_ptr->set_write_name(write_method_name); py_attr_ptr->set_allowed_name(is_allowed_name); if (att_prop) attr_ptr->set_default_properties(*att_prop); attr_ptr->set_disp_level(display_level); if (memorized) { attr_ptr->set_memorized(); attr_ptr->set_memorized_init(hw_memorized); } if (polling_period > 0) attr_ptr->set_polling_period(polling_period); att_list.push_back(attr_ptr); } void CppDeviceClass::create_pipe(vector &pipe_list, const std::string &name, Tango::PipeWriteType access, Tango::DispLevel display_level, const std::string &read_method_name, const std::string &write_method_name, const std::string &is_allowed_name, Tango::UserDefaultPipeProp *prop) { Tango::Pipe *pipe_ptr = NULL; if(access == Tango::PIPE_READ) { PyTango::Pipe::PyPipe* py_pipe_ptr = new PyTango::Pipe::PyPipe(name, display_level, access); py_pipe_ptr->set_read_name(read_method_name); py_pipe_ptr->set_allowed_name(is_allowed_name); pipe_ptr = py_pipe_ptr; } else { PyTango::Pipe::PyWPipe* py_pipe_ptr = new PyTango::Pipe::PyWPipe(name, display_level); py_pipe_ptr->set_read_name(read_method_name); py_pipe_ptr->set_allowed_name(is_allowed_name); py_pipe_ptr->set_write_name(write_method_name); pipe_ptr = py_pipe_ptr; } if (prop) pipe_ptr->set_default_properties(*prop); pipe_list.push_back(pipe_ptr); } CppDeviceClassWrap::CppDeviceClassWrap(PyObject *self, const std::string &name) : CppDeviceClass(name), m_self(self) { init_class(); } /** * Destructor */ CppDeviceClassWrap::~CppDeviceClassWrap() {} void CppDeviceClassWrap::init_class() { AutoPythonGIL python_guard; signal_handler_defined = is_method_defined(m_self, "signal_handler"); } void CppDeviceClassWrap::attribute_factory(std::vector &att_list) { // // make sure we pass the same vector object to the python method // AutoPythonGIL python_guard; object py_att_list( handle<>( to_python_indirect< std::vector, detail::make_reference_holder>()(att_list))); try { boost::python::call_method(m_self, "_attribute_factory", py_att_list); } catch(boost::python::error_already_set &eas) { handle_python_exception(eas); } } void CppDeviceClassWrap::pipe_factory() { // // make sure we pass the same vector object to the python method // AutoPythonGIL python_guard; object py_pipe_list( handle<>( to_python_indirect< std::vector, detail::make_reference_holder>()(pipe_list))); try { boost::python::call_method(m_self, "_pipe_factory", py_pipe_list); } catch(boost::python::error_already_set &eas) { handle_python_exception(eas); } } void CppDeviceClassWrap::command_factory() { CALL_DEVCLASS_METHOD(_command_factory) } void CppDeviceClassWrap::device_name_factory(std::vector &dev_list) { // // make sure we pass the same vector object to the python method // AutoPythonGIL python_guard; object py_dev_list( handle<>( to_python_indirect< std::vector, detail::make_reference_holder>()(dev_list))); try { boost::python::call_method(m_self, "device_name_factory", py_dev_list); } catch(boost::python::error_already_set &eas) { handle_python_exception(eas); } } void CppDeviceClassWrap::device_factory(const Tango::DevVarStringArray *dev_list) { CALL_DEVCLASS_METHOD_VARGS(device_factory, dev_list) } void CppDeviceClassWrap::signal_handler(long signo) { if (signal_handler_defined == true) { CALL_DEVCLASS_METHOD_VARGS(signal_handler, signo) } else { Tango::DeviceClass::signal_handler(signo); } } void CppDeviceClassWrap::default_signal_handler(long signo) { this->Tango::DeviceClass::signal_handler(signo); } void CppDeviceClassWrap::delete_class() { AutoPythonGIL __py_lock; try { // // Call the delete_class_list function in order to clear the global // constructed class Python list. It is MANDATORY to destroy these objects // from Python. Otherwise, there are "seg fault" when Python exit. // It tooks me quite a long time to find this... // PYTANGO_MOD pytango.attr("delete_class_list")(); } catch(error_already_set &eas) { handle_python_exception(eas); } } namespace PyDeviceClass { object get_device_list(CppDeviceClass &self) { boost::python::list py_dev_list; vector dev_list = self.get_device_list(); for(vector::iterator it = dev_list.begin(); it != dev_list.end(); ++it) { object py_value = object( handle<>( to_python_indirect< Tango::DeviceImpl*, detail::make_reference_holder>()(*it))); py_dev_list.append(py_value); } return py_dev_list; } object get_command_list(CppDeviceClass &self) { boost::python::list py_cmd_list; vector cmd_list = self.get_command_list(); for(vector::iterator it = cmd_list.begin(); it != cmd_list.end(); ++it) { object py_value = object( handle<>( to_python_indirect< Tango::Command*, detail::make_reference_holder>()(*it))); py_cmd_list.append(py_value); } return py_cmd_list; } object get_pipe_list(CppDeviceClass &self, const std::string& dev_name) { boost::python::list py_pipe_list; vector pipe_list = self.get_pipe_list(dev_name); // vector pipe_list = self.get_pipe_list(); for(vector::iterator it = pipe_list.begin(); it != pipe_list.end(); ++it) { object py_value = object( handle<>( to_python_indirect< Tango::Pipe*, detail::make_reference_holder>()(*it))); py_pipe_list.append(py_value); } return py_pipe_list; } void register_signal(CppDeviceClass &self, long signo) { self.register_signal(signo); } #if (defined __linux) void register_signal(CppDeviceClass &self, long signo, bool own_handler) { self.register_signal(signo, own_handler); } #endif } BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS (export_device_overload, CppDeviceClass::export_device, 1, 2) void export_device_class() { void (Tango::DeviceClass::*add_wiz_dev_prop_)(string &,string &) = &Tango::DeviceClass::add_wiz_dev_prop; void (Tango::DeviceClass::*add_wiz_dev_prop__)(string &,string &,string &) = &Tango::DeviceClass::add_wiz_dev_prop; void (Tango::DeviceClass::*add_wiz_class_prop_)(string &,string &) = &Tango::DeviceClass::add_wiz_class_prop; void (Tango::DeviceClass::*add_wiz_class_prop__)(string &,string &,string &) = &Tango::DeviceClass::add_wiz_class_prop; class_, boost::noncopyable>("DeviceClass", init()) .def("device_factory", &CppDeviceClassWrap::device_factory) .def("device_name_factory", &CppDeviceClassWrap::device_name_factory) .def("export_device", &CppDeviceClass::export_device, export_device_overload()) .def("_add_device", &CppDeviceClass::add_device) .def("register_signal", (void (*) (CppDeviceClass &, long)) &PyDeviceClass::register_signal) #if defined __linux .def("register_signal", (void (*) (CppDeviceClass &, long, bool)) &PyDeviceClass::register_signal) #endif .def("unregister_signal", &Tango::DeviceClass::unregister_signal) .def("signal_handler", &Tango::DeviceClass::signal_handler, &CppDeviceClassWrap::default_signal_handler) .def("get_name", &Tango::DeviceClass::get_name, return_value_policy()) .def("get_type", &Tango::DeviceClass::get_type, return_value_policy()) .def("get_doc_url", &Tango::DeviceClass::get_doc_url, return_value_policy()) .def("get_cvs_tag", &Tango::DeviceClass::get_cvs_tag, return_value_policy()) .def("get_cvs_location",&Tango::DeviceClass::get_cvs_location, return_value_policy()) .def("get_device_list",&PyDeviceClass::get_device_list) .def("get_command_list",&PyDeviceClass::get_command_list) .def("get_pipe_list",&PyDeviceClass::get_pipe_list) .def("get_cmd_by_name",&Tango::DeviceClass::get_cmd_by_name, return_internal_reference<>()) .def("get_pipe_by_name",&Tango::DeviceClass::get_pipe_by_name, return_internal_reference<>()) .def("set_type", (void (Tango::DeviceClass::*) (const char *)) &Tango::DeviceClass::set_type) .def("add_wiz_dev_prop", (void (Tango::DeviceClass::*) (const std::string &, const std::string &)) add_wiz_dev_prop_) .def("add_wiz_dev_prop", (void (Tango::DeviceClass::*) (const std::string &, const std::string &, const std::string &)) add_wiz_dev_prop__) .def("add_wiz_class_prop", (void (Tango::DeviceClass::*) (const std::string &, const std::string &)) add_wiz_class_prop_) .def("add_wiz_class_prop", (void (Tango::DeviceClass::*) (const std::string &, const std::string &, const std::string &)) add_wiz_class_prop__) .def("_device_destroyer", (void (Tango::DeviceClass::*) (const char *)) &Tango::DeviceClass::device_destroyer) .def("_create_attribute", &CppDeviceClass::create_attribute) .def("_create_fwd_attribute", &CppDeviceClass::create_fwd_attribute) .def("_create_pipe", &CppDeviceClass::create_pipe) .def("_create_command", &CppDeviceClass::create_command) .def("get_class_attr", &Tango::DeviceClass::get_class_attr, return_value_policy()) ; implicitly_convertible, auto_ptr >(); } pytango-9.2.2/ext/server/device_class.h000066400000000000000000000147141316324373100201220ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #ifndef _DEVICE_CLASS_H_ #define _DEVICE_CLASS_H_ #include #include class CppDeviceClass: public Tango::DeviceClass { public: CppDeviceClass(const string &); virtual ~CppDeviceClass(); /** * Export a device. * Associate the servant to a CORBA object and send device network parameter * to TANGO database. * The main parameter sent to database is the CORBA object stringified device IOR. * * @param[in] dev The device to be exported (CORBA servant) * @param[in] corba_dev_name The name to be used in the CORBA object key. * This parameter does not need to be set in most of * cases and has a default value. It is used for special * device server like the database device server. */ inline void export_device(Tango::DeviceImpl *dev, const char *corba_dev_nam = "Unused") { Tango::DeviceClass::export_device(dev, corba_dev_nam); } /** * Returns the python interpreter state * * @return python interpreter state pointer */ inline PyInterpreterState *get_py_interp() { return interp; } /** * Sets the python interpreter state * * @param[in] in python interpreter state */ inline void set_py_interp(PyInterpreterState *in) { interp = in; } /** * Creates an attribute and adds it to the att_list. * This method is intended to be called by python to register a new * attribute. */ void create_attribute(vector &att_list, const std::string &attr_name, Tango::CmdArgType attr_type, Tango::AttrDataFormat attr_format, Tango::AttrWriteType attr_write, long dim_x, long dim_y, Tango::DispLevel display_level, long polling_period, bool memorized, bool hw_memorized, const std::string &read_method_name, const std::string &write_method_name, const std::string &is_allowed_name, Tango::UserDefaultAttrProp *att_prop); void create_fwd_attribute(vector &att_list, const std::string &attr_name, Tango::UserDefaultFwdAttrProp *att_prop); /** * Creates an pipe and adds it to the att_list. * This method is intended to be called by python to register a new * pipe. */ void create_pipe(vector &pipe_list, const std::string &name, Tango::PipeWriteType access, Tango::DispLevel display_level, const std::string &read_method_name, const std::string &write_method_name, const std::string &is_allowed_name, Tango::UserDefaultPipeProp *prop); /** * Creates a command. * This method is intended to be called by python to register a new * command. */ void create_command(const std::string &cmd_name, Tango::CmdArgType param_type, Tango::CmdArgType result_type, const std::string ¶m_desc, const std::string &result_desc, Tango::DispLevel display_level, bool default_command, long polling_period, const std::string &is_allowed); protected: PyInterpreterState *interp; }; class CppDeviceClassWrap : public CppDeviceClass { public: /** a reference to itself */ PyObject *m_self; /** * Constructor * * @param[in] self A reference to the python device class object * @param[in] name the class name */ CppDeviceClassWrap(PyObject *self, const std::string &name); /** * Destructor */ virtual ~CppDeviceClassWrap(); /** * This method forward a C++ call to the device_factory method to the * Python method * * @param[in] dev_list The device name list */ virtual void device_factory(const Tango::DevVarStringArray *dev_list); /** * This method forward a C++ call to the attribute_factory method to the * Python method * * @param[in] att_list attribute list */ virtual void attribute_factory(std::vector &att_list); /** * This method forward a C++ call to the pipe_factory method to the * Python method * * @param[in] pipe_list pipe list */ virtual void pipe_factory(); /** * This method forward a C++ call to the command_factory method to the * Python method */ virtual void command_factory(); /** * This method forward a C++ call to the device_name_factory method to the * Python method */ virtual void device_name_factory(std::vector &dev_list); /** * This method is called to ask Python to delete a class by decrementing * the Python ref count */ virtual void delete_class(); /** * This method forward a C++ call to the signal_handler method to the * Python method or executes default signal handler if no signal handler * is defined in python * * @param[in] signo signal identifier */ virtual void signal_handler(long signo); /** * Default signal handler implementation * * @param[in] signo signal identifier */ void default_signal_handler(long signo); protected: /** * Initializes the class. Registers as a python DeviceClass to tango, * determines existence of a signal handler among other things */ void init_class(); /** * flag containing the information about the existence of a signal_handler * method in the python class */ bool signal_handler_defined; }; #endif // _DEVICE_CLASS_H_ pytango-9.2.2/ext/server/device_impl.cpp000066400000000000000000001774751316324373100203270ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include "defs.h" #include "pytgutils.h" #include "exception.h" #include "server/device_impl.h" #include "server/attr.h" #include "server/attribute.h" #include "server/command.h" #include "to_py.h" #include "pipe.h" extern const char *param_must_be_seq; using namespace boost::python; #define __AUX_DECL_CALL_DEVICE_METHOD \ AutoPythonGIL __py_lock; #define __AUX_CATCH_PY_EXCEPTION \ catch(boost::python::error_already_set &eas) \ { handle_python_exception(eas); } #define __AUX_CATCH_EXCEPTION(name) \ catch(...) \ { Tango::Except::throw_exception("CppException", \ "An unexpected C++ exception occurred", \ #name ); \ } #define CALL_DEVICE_METHOD(cls, name) \ __AUX_DECL_CALL_DEVICE_METHOD \ try { \ if (override name = this->get_override( #name) ) { name (); } \ else { cls :: name (); } \ } \ __AUX_CATCH_PY_EXCEPTION \ __AUX_CATCH_EXCEPTION(name) #define CALL_DEVICE_METHOD_VARGS(cls, name, ...) \ __AUX_DECL_CALL_DEVICE_METHOD \ try { \ if (override name = this->get_override( #name) ) \ { name (__VA_ARGS__); } \ else { cls :: name (__VA_ARGS__); } \ } \ __AUX_CATCH_PY_EXCEPTION \ __AUX_CATCH_EXCEPTION(name) #define CALL_DEVICE_METHOD_RET(cls, name) \ __AUX_DECL_CALL_DEVICE_METHOD \ try { \ if (override name = this->get_override( #name) ) { return name (); } \ else { return cls :: name (); } \ } \ __AUX_CATCH_PY_EXCEPTION \ __AUX_CATCH_EXCEPTION(name) #define CALL_DEVICE_METHOD_VARGS_RET(cls, name, ...) \ __AUX_DECL_CALL_DEVICE_METHOD \ try { \ if (override name = this->get_override( #name) ) \ { return name (__VA_ARGS__); } \ else { return cls :: name (__VA_ARGS__); } \ } \ __AUX_CATCH_PY_EXCEPTION \ __AUX_CATCH_EXCEPTION(name) // we don't use extract<> from boost bellow to get attribute name because it is // considerably slow #define SAFE_PUSH(dev, attr, attr_name) \ std::string __att_name; \ from_str_to_char(attr_name.ptr(), __att_name); \ AutoPythonAllowThreads python_guard_ptr; \ Tango::AutoTangoMonitor tango_guard(&dev); \ Tango::Attribute & attr = dev.get_device_attr()->get_attr_by_name(__att_name.c_str()); \ python_guard_ptr.giveup(); #define SAFE_PUSH_CHANGE_EVENT(dev, attr_name, data) \ { \ SAFE_PUSH(dev, attr, attr_name) \ PyAttribute::set_value(attr, data); \ attr.fire_change_event(); \ } #define SAFE_PUSH_CHANGE_EVENT_VARGS(dev, attr_name, data, ...) \ { \ SAFE_PUSH(dev, attr, attr_name) \ PyAttribute::set_value(attr, data, __VA_ARGS__); \ attr.fire_change_event(); \ } #define SAFE_PUSH_CHANGE_EVENT_DATE_QUALITY(dev, attr_name, data, date, quality) \ { \ SAFE_PUSH(dev, attr, attr_name) \ PyAttribute::set_value_date_quality(attr, data, date, quality); \ attr.fire_change_event(); \ } #define SAFE_PUSH_CHANGE_EVENT_DATE_QUALITY_VARGS(dev, attr_name, data, date, quality, ...) \ { \ SAFE_PUSH(dev, attr, attr_name) \ PyAttribute::set_value_date_quality(attr, data, date, quality, __VA_ARGS__); \ attr.fire_change_event(); \ } #define SAFE_PUSH_ARCHIVE_EVENT(dev, attr_name, data) \ { \ SAFE_PUSH(dev, attr, attr_name) \ PyAttribute::set_value(attr, data); \ attr.fire_archive_event(); \ } #define SAFE_PUSH_ARCHIVE_EVENT_VARGS(dev, attr_name, data, ...) \ { \ SAFE_PUSH(dev, attr, attr_name) \ PyAttribute::set_value(attr, data, __VA_ARGS__); \ attr.fire_archive_event(); \ } #define SAFE_PUSH_ARCHIVE_EVENT_DATE_QUALITY(dev, attr_name, data, date, quality) \ { \ SAFE_PUSH(dev, attr, attr_name) \ PyAttribute::set_value_date_quality(attr, data, date, quality); \ attr.fire_archive_event(); \ } #define SAFE_PUSH_ARCHIVE_EVENT_DATE_QUALITY_VARGS(dev, attr_name, data, date, quality, ...) \ { \ SAFE_PUSH(dev, attr, attr_name) \ PyAttribute::set_value_date_quality(attr, data, date, quality, __VA_ARGS__); \ attr.fire_archive_event(); \ } #define AUX_SAFE_PUSH_EVENT(dev, attr_name, filt_names, filt_vals) \ StdStringVector filt_names_; \ StdDoubleVector filt_vals_; \ from_sequence::convert(filt_names, filt_names_); \ from_sequence::convert(filt_vals, filt_vals_); \ SAFE_PUSH(dev, attr, attr_name) #define SAFE_PUSH_EVENT(dev, attr_name, filt_names, filt_vals, data) \ { \ AUX_SAFE_PUSH_EVENT(dev, attr_name, filt_names, filt_vals) \ PyAttribute::set_value(attr, data); \ attr.fire_event(filt_names_, filt_vals_); \ } #define SAFE_PUSH_EVENT_VARGS(dev, attr_name, filt_names, filt_vals, data, ...) \ { \ AUX_SAFE_PUSH_EVENT(dev,attr_name, filt_names, filt_vals) \ PyAttribute::set_value(attr, data, __VA_ARGS__); \ attr.fire_event(filt_names_, filt_vals_); \ } #define SAFE_PUSH_EVENT_DATE_QUALITY(dev, attr_name, filt_names, filt_vals, data, date, quality) \ { \ AUX_SAFE_PUSH_EVENT(dev, attr_name, filt_names, filt_vals) \ PyAttribute::set_value_date_quality(attr, data, date, quality); \ attr.fire_event(filt_names_, filt_vals_); \ } #define SAFE_PUSH_EVENT_DATE_QUALITY_VARGS(dev, attr_name, filt_names, filt_vals, data, date, quality, ...) \ { \ AUX_SAFE_PUSH_EVENT(dev,attr_name, filt_names, filt_vals) \ PyAttribute::set_value_date_quality(attr, data, date, quality, __VA_ARGS__); \ attr.fire_event(filt_names_, filt_vals_); \ } namespace PyDeviceImpl { inline PyObject* get_polled_cmd(Tango::DeviceImpl &self) { return to_list >::convert(self.get_polled_cmd()); } inline PyObject* get_polled_attr(Tango::DeviceImpl &self) { return to_list >::convert(self.get_polled_attr()); } inline PyObject* get_non_auto_polled_cmd(Tango::DeviceImpl &self) { return to_list >::convert(self.get_non_auto_polled_cmd()); } inline PyObject* get_non_auto_polled_attr(Tango::DeviceImpl &self) { return to_list >::convert(self.get_non_auto_polled_attr()); } /* ********************************** * change event USING set_value * **********************************/ inline void push_change_event(Tango::DeviceImpl &self, str &name) { str name_lower = name.lower(); if ("state" != name_lower && "status" != name_lower) { Tango::Except::throw_exception( "PyDs_InvalidCall", "push_change_event without data parameter is only allowed for " "state and status attributes.", "DeviceImpl::push_change_event"); } SAFE_PUSH(self, attr, name) attr.fire_change_event(); } inline void push_change_event(Tango::DeviceImpl &self, str &name, object &data) { boost::python::extract except_convert(data); if (except_convert.check()) { SAFE_PUSH(self, attr, name); attr.fire_change_event( const_cast( &except_convert() )); return; } SAFE_PUSH_CHANGE_EVENT(self, name, data); } // Special variantion for encoded data type inline void push_change_event(Tango::DeviceImpl &self, str &name, str &str_data, str &data) { SAFE_PUSH(self, attr, name) PyAttribute::set_value(attr, str_data, data); attr.fire_change_event(); } // Special variantion for encoded data type inline void push_change_event(Tango::DeviceImpl &self, str &name, str &str_data, object &data) { SAFE_PUSH(self, attr, name) PyAttribute::set_value(attr, str_data, data); attr.fire_change_event(); } inline void push_change_event(Tango::DeviceImpl &self, str &name, object &data, long x) { SAFE_PUSH_CHANGE_EVENT_VARGS(self, name, data, x); } inline void push_change_event(Tango::DeviceImpl &self, str &name, object &data, long x, long y) { SAFE_PUSH_CHANGE_EVENT_VARGS(self, name, data, x, y); } /* ********************************** * change event USING set_value_date_quality * **********************************/ inline void push_change_event(Tango::DeviceImpl &self, str &name, object &data, double t, Tango::AttrQuality quality) { SAFE_PUSH_CHANGE_EVENT_DATE_QUALITY(self, name, data, t, quality) } // Special variantion for encoded data type inline void push_change_event(Tango::DeviceImpl &self, str &name, str &str_data, str &data, double t, Tango::AttrQuality quality) { SAFE_PUSH(self, attr, name) PyAttribute::set_value_date_quality(attr, str_data, data, t, quality); attr.fire_change_event(); } // Special variantion for encoded data type inline void push_change_event(Tango::DeviceImpl &self, str &name, str &str_data, object &data, double t, Tango::AttrQuality quality) { SAFE_PUSH(self, attr, name) PyAttribute::set_value_date_quality(attr, str_data, data, t, quality); attr.fire_change_event(); } inline void push_change_event(Tango::DeviceImpl &self, str &name, object &data, double t, Tango::AttrQuality quality, long x) { SAFE_PUSH_CHANGE_EVENT_DATE_QUALITY_VARGS(self, name, data, t, quality, x) } inline void push_change_event(Tango::DeviceImpl &self, str &name, object &data, double t, Tango::AttrQuality quality, long x, long y) { SAFE_PUSH_CHANGE_EVENT_DATE_QUALITY_VARGS(self, name, data, t, quality, x, y) } /* ********************************** * archive event USING set_value * **********************************/ inline void push_archive_event(Tango::DeviceImpl &self, str &name) { SAFE_PUSH(self, attr, name) attr.fire_archive_event(); } inline void push_archive_event(Tango::DeviceImpl &self, str &name, object &data) { boost::python::extract except_convert(data); if (except_convert.check()) { SAFE_PUSH(self, attr, name); attr.fire_archive_event( const_cast( &except_convert() )); return; } SAFE_PUSH_ARCHIVE_EVENT(self, name, data); } // Special variation for encoded data type inline void push_archive_event(Tango::DeviceImpl &self, str &name, str &str_data, str &data) { SAFE_PUSH(self, attr, name) PyAttribute::set_value(attr, str_data, data); attr.fire_archive_event(); } // Special variation for encoded data type inline void push_archive_event(Tango::DeviceImpl &self, str &name, str &str_data, object &data) { SAFE_PUSH(self, attr, name) PyAttribute::set_value(attr, str_data, data); attr.fire_archive_event(); } inline void push_archive_event(Tango::DeviceImpl &self, str &name, object &data, long x) { SAFE_PUSH_ARCHIVE_EVENT_VARGS(self, name, data, x); } inline void push_archive_event(Tango::DeviceImpl &self, str &name, object &data, long x, long y) { SAFE_PUSH_ARCHIVE_EVENT_VARGS(self, name, data, x, y); } /* ********************************** * archive event USING set_value_date_quality * **********************************/ inline void push_archive_event(Tango::DeviceImpl &self, str &name, object &data, double t, Tango::AttrQuality quality) { SAFE_PUSH_ARCHIVE_EVENT_DATE_QUALITY(self, name, data, t, quality) } // Special variantion for encoded data type inline void push_archive_event(Tango::DeviceImpl &self, str &name, str &str_data, str &data, double t, Tango::AttrQuality quality) { SAFE_PUSH(self, attr, name) PyAttribute::set_value_date_quality(attr, str_data, data, t, quality); attr.fire_archive_event(); } // Special variantion for encoded data type inline void push_archive_event(Tango::DeviceImpl &self, str &name, str &str_data, object &data, double t, Tango::AttrQuality quality) { SAFE_PUSH(self, attr, name) PyAttribute::set_value_date_quality(attr, str_data, data, t, quality); attr.fire_archive_event(); } inline void push_archive_event(Tango::DeviceImpl &self, str &name, object &data, double t, Tango::AttrQuality quality, long x) { SAFE_PUSH_ARCHIVE_EVENT_DATE_QUALITY_VARGS(self, name, data, t, quality, x) } inline void push_archive_event(Tango::DeviceImpl &self, str &name, object &data, double t, Tango::AttrQuality quality, long x, long y) { SAFE_PUSH_ARCHIVE_EVENT_DATE_QUALITY_VARGS(self, name, data, t, quality, x, y) } /* ********************************** * user event USING set_value * **********************************/ inline void push_event(Tango::DeviceImpl &self, str &name, object &filt_names, object &filt_vals) { AUX_SAFE_PUSH_EVENT(self, name, filt_names, filt_vals) attr.fire_event(filt_names_, filt_vals_); } inline void push_event(Tango::DeviceImpl &self, str &name, object &filt_names, object &filt_vals, object &data) { SAFE_PUSH_EVENT(self, name, filt_names, filt_vals, data) } // Special variantion for encoded data type inline void push_event(Tango::DeviceImpl &self, str &name, object &filt_names, object &filt_vals, str &str_data, str &data) { AUX_SAFE_PUSH_EVENT(self, name, filt_names, filt_vals) PyAttribute::set_value(attr, str_data, data); attr.fire_event(filt_names_, filt_vals_); } // Special variantion for encoded data type inline void push_event(Tango::DeviceImpl &self, str &name, object &filt_names, object &filt_vals, str &str_data, object &data) { AUX_SAFE_PUSH_EVENT(self, name, filt_names, filt_vals) PyAttribute::set_value(attr, str_data, data); attr.fire_event(filt_names_, filt_vals_); } inline void push_event(Tango::DeviceImpl &self, str &name, object &filt_names, object &filt_vals, object &data, long x) { SAFE_PUSH_EVENT_VARGS(self, name, filt_names, filt_vals, data, x) } inline void push_event(Tango::DeviceImpl &self, str &name, object &filt_names, object &filt_vals, object &data, long x, long y) { SAFE_PUSH_EVENT_VARGS(self, name, filt_names, filt_vals, data, x, y) } /* *************************************** * user event USING set_value_date_quality * **************************************/ inline void push_event(Tango::DeviceImpl &self, str &name, object &filt_names, object &filt_vals, object &data, double t, Tango::AttrQuality quality) { SAFE_PUSH_EVENT_DATE_QUALITY(self, name, filt_names, filt_vals, data, t, quality) } // Special variantion for encoded data type inline void push_event(Tango::DeviceImpl &self, str &name, object &filt_names, object &filt_vals, str &str_data, str &data, double t, Tango::AttrQuality quality) { AUX_SAFE_PUSH_EVENT(self, name, filt_names, filt_vals) PyAttribute::set_value_date_quality(attr, str_data, data, t, quality); attr.fire_event(filt_names_, filt_vals_); } // Special variantion for encoded data type inline void push_event(Tango::DeviceImpl &self, str &name, object &filt_names, object &filt_vals, str &str_data, object &data, double t, Tango::AttrQuality quality) { AUX_SAFE_PUSH_EVENT(self, name, filt_names, filt_vals) PyAttribute::set_value_date_quality(attr, str_data, data, t, quality); attr.fire_event(filt_names_, filt_vals_); } inline void push_event(Tango::DeviceImpl &self, str &name, object &filt_names, object &filt_vals, object &data, double t, Tango::AttrQuality quality, long x) { SAFE_PUSH_EVENT_DATE_QUALITY_VARGS(self, name, filt_names, filt_vals, data, t, quality, x) } inline void push_event(Tango::DeviceImpl &self, str &name, object &filt_names, object &filt_vals, object &data, double t, Tango::AttrQuality quality, long x, long y) { SAFE_PUSH_EVENT_DATE_QUALITY_VARGS(self, name, filt_names, filt_vals, data, t, quality, x, y) } /* ********************************** * data ready event * **********************************/ inline void push_data_ready_event(Tango::DeviceImpl &self, const str &name, long ctr) { SAFE_PUSH(self, attr, name) self.push_data_ready_event(__att_name, ctr); //__att_name from SAFE_PUSH } /* ********************************** * pipe event * **********************************/ inline void push_pipe_event(Tango::DeviceImpl &self, str &pipe_name, object& pipe_data) { std::string __pipe_name; from_str_to_char(pipe_name.ptr(), __pipe_name); boost::python::extract except_convert(pipe_data); if (except_convert.check()) { self.push_pipe_event(__pipe_name, const_cast(&except_convert())); return; } Tango::DevicePipeBlob dpb; bool reuse = false; PyDevicePipe::set_value(dpb, pipe_data); self.push_pipe_event(__pipe_name, &dpb, reuse); } void check_attribute_method_defined(PyObject *self, const std::string &attr_name, const std::string &method_name) { bool exists, is_method; is_method_defined(self, method_name, exists, is_method); if (!exists) { TangoSys_OMemStream o; o << "Wrong definition of attribute " << attr_name << "\nThe attribute method " << method_name << " does not exist in your class!" << ends; Tango::Except::throw_exception( (const char *)"PyDs_WrongCommandDefinition", o.str(), (const char *)"check_attribute_method_defined"); } if(!is_method) { TangoSys_OMemStream o; o << "Wrong definition of attribute " << attr_name << "\nThe object " << method_name << " exists in your class but is not a Python method" << ends; Tango::Except::throw_exception( (const char *)"PyDs_WrongCommandDefinition", o.str(), (const char *)"check_attribute_method_defined"); } } void add_attribute(Tango::DeviceImpl &self, const Tango::Attr &c_new_attr, boost::python::object read_meth_name, boost::python::object write_meth_name, boost::python::object is_allowed_meth_name) { Tango::Attr &new_attr = const_cast(c_new_attr); std::string attr_name = new_attr.get_name(), read_name_met, write_name_met, is_allowed_method; if (read_meth_name.ptr() == Py_None) { read_name_met = "read_" + attr_name; } else { read_name_met = boost::python::extract(read_meth_name); } if (write_meth_name.ptr() == Py_None) { write_name_met = "write_" + attr_name; } else { write_name_met = boost::python::extract(write_meth_name); } if (is_allowed_meth_name.ptr() == Py_None) { is_allowed_method = "is_" + attr_name + "_allowed"; } else { is_allowed_method = boost::python::extract(is_allowed_meth_name); } Tango::AttrWriteType attr_write = new_attr.get_writable(); // // Create the attribute object according to attribute format // PyScaAttr *sca_attr_ptr = NULL; PySpecAttr *spec_attr_ptr = NULL; PyImaAttr *ima_attr_ptr= NULL; PyAttr *py_attr_ptr = NULL; Tango::Attr *attr_ptr = NULL; long x, y; vector &def_prop = new_attr.get_user_default_properties(); Tango::AttrDataFormat attr_format = new_attr.get_format(); long attr_type = new_attr.get_type(); switch (attr_format) { case Tango::SCALAR: sca_attr_ptr = new PyScaAttr(attr_name, attr_type, attr_write, def_prop); py_attr_ptr = sca_attr_ptr; attr_ptr = sca_attr_ptr; break; case Tango::SPECTRUM: x = (static_cast(new_attr)).get_max_x(); spec_attr_ptr = new PySpecAttr(attr_name, attr_type, attr_write, x, def_prop); py_attr_ptr = spec_attr_ptr; attr_ptr = spec_attr_ptr; break; case Tango::IMAGE: x = (static_cast(new_attr)).get_max_x(); y = (static_cast(new_attr)).get_max_y(); ima_attr_ptr = new PyImaAttr(attr_name, attr_type, attr_write, x, y, def_prop); py_attr_ptr = ima_attr_ptr; attr_ptr = ima_attr_ptr; break; default: TangoSys_OMemStream o; o << "Attribute " << attr_name << " has an unexpected data format\n" << "Please report this bug to the PyTango development team" << ends; Tango::Except::throw_exception( (const char *)"PyDs_UnexpectedAttributeFormat", o.str(), (const char *)"cpp_add_attribute"); break; } py_attr_ptr->set_read_name(read_name_met); py_attr_ptr->set_write_name(write_name_met); py_attr_ptr->set_allowed_name(is_allowed_method); if (new_attr.get_memorized()) attr_ptr->set_memorized(); attr_ptr->set_memorized_init(new_attr.get_memorized_init()); attr_ptr->set_disp_level(new_attr.get_disp_level()); attr_ptr->set_polling_period(new_attr.get_polling_period()); attr_ptr->set_change_event(new_attr.is_change_event(), new_attr.is_check_change_criteria()); attr_ptr->set_archive_event(new_attr.is_archive_event(), new_attr.is_check_archive_criteria()); attr_ptr->set_data_ready_event(new_attr.is_data_ready_event()); // // Install attribute in Tango. // self.add_attribute(attr_ptr); } void remove_attribute(Tango::DeviceImpl &self, const char *att_name, bool clean_db = true) { string str(att_name); self.remove_attribute(str, false, clean_db); } void add_command(Tango::DeviceImpl &self, boost::python::object cmd_name, boost::python::object cmd_data, boost::python::object disp_level, bool device_level = false) { // PyCmd *py_cmd_ptr = nullptr; Tango::Command *cmd_ptr = nullptr; std::string name = boost::python::extract(cmd_name); std::string in_desc = boost::python::extract(cmd_data[0][1]); std::string out_desc = boost::python::extract(cmd_data[1][1]); Tango::CmdArgType argtype_in = boost::python::extract(cmd_data[0][0]); Tango::CmdArgType argtype_out = boost::python::extract(cmd_data[1][0]); Tango::DispLevel display_level = boost::python::extract(disp_level); cmd_ptr = new PyCmd(name, argtype_in, argtype_out, in_desc, out_desc, display_level); // py_cmd_ptr = new PyCmd(name, argtype_in, argtype_out, in_desc, out_desc, display_level); // cmd_ptr = py_cmd_ptr; // // Install the command in Tango. // self.add_command(cmd_ptr, device_level); } void remove_command(Tango::DeviceImpl &self, boost::python::object cmd_name, bool free_it = false, bool clean_db = true) { std::string name = boost::python::extract(cmd_name); self.remove_command(name, free_it, clean_db); } inline void debug(Tango::DeviceImpl &self, const string &msg) { if (self.get_logger()->is_debug_enabled()) { self.get_logger()->debug_stream() << log4tango::LogInitiator::_begin_log << msg; } } inline void info(Tango::DeviceImpl &self, const string &msg) { if (self.get_logger()->is_info_enabled()) { self.get_logger()->info_stream() << log4tango::LogInitiator::_begin_log << msg; } } inline void warn(Tango::DeviceImpl &self, const string &msg) { if (self.get_logger()->is_warn_enabled()) { self.get_logger()->warn_stream() << log4tango::LogInitiator::_begin_log << msg; } } inline void error(Tango::DeviceImpl &self, const string &msg) { if (self.get_logger()->is_error_enabled()) { self.get_logger()->error_stream() << log4tango::LogInitiator::_begin_log << msg; } } inline void fatal(Tango::DeviceImpl &self, const string &msg) { if (self.get_logger()->is_fatal_enabled()) { self.get_logger()->fatal_stream() << log4tango::LogInitiator::_begin_log << msg; } } PyObject* get_attribute_config(Tango::DeviceImpl &self, object &py_attr_name_seq) { Tango::DevVarStringArray par; convert2array(py_attr_name_seq, par); Tango::AttributeConfigList *attr_conf_list_ptr = self.get_attribute_config(par); boost::python::list ret = to_py(*attr_conf_list_ptr); delete attr_conf_list_ptr; return boost::python::incref(ret.ptr()); } void set_attribute_config(Tango::DeviceImpl &self, object &py_attr_conf_list) { Tango::AttributeConfigList attr_conf_list; from_py_object(py_attr_conf_list, attr_conf_list); self.set_attribute_config(attr_conf_list); } bool is_attribute_polled(Tango::DeviceImpl &self, const std::string &att_name) { DeviceImplWrap *self_w = (DeviceImplWrap*)(&self); return self_w->_is_attribute_polled(att_name); } bool is_command_polled(Tango::DeviceImpl &self, const std::string &cmd_name) { DeviceImplWrap *self_w = (DeviceImplWrap*)(&self); return self_w->_is_command_polled(cmd_name); } int get_attribute_poll_period(Tango::DeviceImpl &self, const std::string &att_name) { DeviceImplWrap *self_w = (DeviceImplWrap*)(&self); return self_w->_get_attribute_poll_period(att_name); } int get_command_poll_period(Tango::DeviceImpl &self, const std::string &cmd_name) { DeviceImplWrap *self_w = (DeviceImplWrap*)(&self); return self_w->_get_command_poll_period(cmd_name); } void poll_attribute(Tango::DeviceImpl &self, const std::string &att_name, int period) { DeviceImplWrap *self_w = (DeviceImplWrap*)(&self); self_w->_poll_attribute(att_name, period); } void poll_command(Tango::DeviceImpl &self, const std::string &cmd_name, int period) { DeviceImplWrap *self_w = (DeviceImplWrap*)(&self); self_w->_poll_command(cmd_name, period); } void stop_poll_attribute(Tango::DeviceImpl &self, const std::string &att_name) { DeviceImplWrap *self_w = (DeviceImplWrap*)(&self); self_w->_stop_poll_attribute(att_name); } void stop_poll_command(Tango::DeviceImpl &self, const std::string &cmd_name) { DeviceImplWrap *self_w = (DeviceImplWrap*)(&self); self_w->_stop_poll_command(cmd_name); } } DeviceImplWrap::DeviceImplWrap(PyObject *self, CppDeviceClass *cl, std::string &st) :Tango::DeviceImpl(cl,st), m_self(self) { Py_INCREF(m_self); } DeviceImplWrap::DeviceImplWrap(PyObject *self, CppDeviceClass *cl, const char *name, const char *desc /* = "A Tango device" */, Tango::DevState sta /* = Tango::UNKNOWN */, const char *status /* = StatusNotSet */) :Tango::DeviceImpl(cl, name, desc, sta, status), m_self(self) { Py_INCREF(m_self); } void DeviceImplWrap::init_device() { this->get_override("init_device")(); } bool DeviceImplWrap::_is_attribute_polled(const std::string &att_name) { return this->is_attribute_polled(att_name); } bool DeviceImplWrap::_is_command_polled(const std::string &cmd_name) { return this->is_command_polled(cmd_name); } int DeviceImplWrap::_get_attribute_poll_period(const std::string &att_name) { return this->get_attribute_poll_period(att_name); } int DeviceImplWrap::_get_command_poll_period(const std::string &cmd_name) { return this->get_command_poll_period(cmd_name); } void DeviceImplWrap::_poll_attribute(const std::string &att_name, int period) { this->poll_attribute(att_name, period); } void DeviceImplWrap::_poll_command(const std::string &cmd_name, int period) { this->poll_command(cmd_name, period); } void DeviceImplWrap::_stop_poll_attribute(const std::string &att_name) { this->stop_poll_attribute(att_name); } void DeviceImplWrap::_stop_poll_command(const std::string &cmd_name) { this->stop_poll_command(cmd_name); } Device_2ImplWrap::Device_2ImplWrap(PyObject *self, CppDeviceClass *cl, std::string &st) :Tango::Device_2Impl(cl,st),m_self(self) { Py_INCREF(m_self); } Device_2ImplWrap::Device_2ImplWrap(PyObject *self, CppDeviceClass *cl, const char *name, const char *desc /* = "A Tango device" */, Tango::DevState sta /* = Tango::UNKNOWN */, const char *status /* = StatusNotSet */) :Tango::Device_2Impl(cl, name, desc, sta, status), m_self(self) { Py_INCREF(m_self); } void Device_2ImplWrap::init_device() { this->get_override("init_device")(); } PyDeviceImplBase::PyDeviceImplBase(PyObject *self):the_self(self) { Py_INCREF(the_self); } namespace PyDevice_2Impl { PyObject* get_attribute_config_2(Tango::Device_2Impl &self, object &attr_name_seq) { Tango::DevVarStringArray par; convert2array(attr_name_seq, par); Tango::AttributeConfigList_2 *attr_conf_list_ptr = self.get_attribute_config_2(par); boost::python::list ret = to_py(*attr_conf_list_ptr); delete attr_conf_list_ptr; return boost::python::incref(ret.ptr()); } /* Postponed: Tango (7.1.1) has no set_attribute_config_2 !!! void set_attribute_config_2(Tango::Device_2Impl &self, object &py_attr_conf_list) { Tango::AttributeConfigList_2 attr_conf_list; from_py_object(py_attr_conf_list, attr_conf_list); self.set_attribute_config_2(attr_conf_list); } */ } PyDeviceImplBase::~PyDeviceImplBase() {} void PyDeviceImplBase::py_delete_dev() {} Device_3ImplWrap::Device_3ImplWrap(PyObject *self, CppDeviceClass *cl, std::string &st) :Tango::Device_3Impl(cl,st), PyDeviceImplBase(self) { _init(); } Device_3ImplWrap::Device_3ImplWrap(PyObject *self, CppDeviceClass *cl, const char *name, const char *desc /* = "A Tango device" */, Tango::DevState sta /* = Tango::UNKNOWN */, const char *status /* = StatusNotSet */) :Tango::Device_3Impl(cl, name, desc, sta, status), PyDeviceImplBase(self) { _init(); } Device_3ImplWrap::~Device_3ImplWrap() { delete_device(); } void Device_3ImplWrap::_init() { // Make sure the wrapper contains a valid pointer to the self // I found out this is needed by inspecting the boost wrapper_base.hpp code initialize_wrapper(the_self, this); } void Device_3ImplWrap::init_device() { AutoPythonGIL __py_lock; try { this->get_override("init_device")(); } catch(boost::python::error_already_set &eas) { handle_python_exception(eas); } } void Device_3ImplWrap::delete_device() { CALL_DEVICE_METHOD(Device_3Impl, delete_device) } void Device_3ImplWrap::default_delete_device() { this->Tango::Device_3Impl::delete_device(); } void Device_3ImplWrap::delete_dev() { // Call here the delete_device method. It is defined in Device_3ImplWrap // class which is already destroyed when the Tango kernel call the // delete_device method try { delete_device(); } catch (Tango::DevFailed &e) { Tango::Except::print_exception(e); } } void Device_3ImplWrap::py_delete_dev() { Device_3ImplWrap::delete_dev(); PyDeviceImplBase::py_delete_dev(); } void Device_3ImplWrap::always_executed_hook() { CALL_DEVICE_METHOD(Device_3Impl, always_executed_hook) } void Device_3ImplWrap::default_always_executed_hook() { this->Tango::Device_3Impl::always_executed_hook(); } void Device_3ImplWrap::read_attr_hardware(vector &attr_list) { CALL_DEVICE_METHOD_VARGS(Device_3Impl, read_attr_hardware, attr_list) } void Device_3ImplWrap::default_read_attr_hardware(vector &attr_list) { this->Tango::Device_3Impl::read_attr_hardware(attr_list); } void Device_3ImplWrap::write_attr_hardware(vector &attr_list) { CALL_DEVICE_METHOD_VARGS(Device_3Impl, write_attr_hardware, attr_list) } void Device_3ImplWrap::default_write_attr_hardware(vector &attr_list) { this->Tango::Device_3Impl::write_attr_hardware(attr_list); } Tango::DevState Device_3ImplWrap::dev_state() { CALL_DEVICE_METHOD_RET(Device_3Impl, dev_state) // Keep the compiler quiet return Tango::UNKNOWN; } Tango::DevState Device_3ImplWrap::default_dev_state() { return this->Tango::Device_3Impl::dev_state(); } Tango::ConstDevString Device_3ImplWrap::dev_status() { __AUX_DECL_CALL_DEVICE_METHOD try { if (override dev_status = this->get_override("dev_status") ) { std::string status = dev_status(); this->the_status = status; } else { this->the_status = Device_3Impl::dev_status(); } } __AUX_CATCH_PY_EXCEPTION \ __AUX_CATCH_EXCEPTION(dev_status) return this->the_status.c_str(); } Tango::ConstDevString Device_3ImplWrap::default_dev_status() { return this->Tango::Device_3Impl::dev_status(); } void Device_3ImplWrap::signal_handler(long signo) { CALL_DEVICE_METHOD_VARGS(Device_3Impl, signal_handler, signo) } void Device_3ImplWrap::default_signal_handler(long signo) { this->Tango::Device_3Impl::signal_handler(signo); } namespace PyDevice_3Impl { PyObject* get_attribute_config_3(Tango::Device_3Impl &self, object &attr_name_seq) { Tango::DevVarStringArray par; convert2array(attr_name_seq, par); Tango::AttributeConfigList_3 *attr_conf_list_ptr = self.get_attribute_config_3(par); boost::python::list ret = to_py(*attr_conf_list_ptr); delete attr_conf_list_ptr; return boost::python::incref(ret.ptr()); } void set_attribute_config_3(Tango::Device_3Impl &self, object &py_attr_conf_list) { Tango::AttributeConfigList_3 attr_conf_list; from_py_object(py_attr_conf_list, attr_conf_list); self.set_attribute_config_3(attr_conf_list); } } Device_4ImplWrap::Device_4ImplWrap(PyObject *self, CppDeviceClass *cl, std::string &st) :Tango::Device_4Impl(cl,st), PyDeviceImplBase(self) { _init(); } Device_4ImplWrap::Device_4ImplWrap(PyObject *self, CppDeviceClass *cl, const char *name, const char *desc /* = "A Tango device" */, Tango::DevState sta /* = Tango::UNKNOWN */, const char *status /* = StatusNotSet */) :Tango::Device_4Impl(cl, name, desc, sta, status), PyDeviceImplBase(self) { _init(); } Device_4ImplWrap::~Device_4ImplWrap() { delete_device(); } void Device_4ImplWrap::_init() { // Make sure the wrapper contains a valid pointer to the self // I found out this is needed by inspecting the boost wrapper_base.hpp code initialize_wrapper(the_self, this); } void Device_4ImplWrap::init_device() { AutoPythonGIL __py_lock; try { this->get_override("init_device")(); } catch(boost::python::error_already_set &eas) { handle_python_exception(eas); } } void Device_4ImplWrap::delete_device() { CALL_DEVICE_METHOD(Device_4Impl, delete_device) } void Device_4ImplWrap::default_delete_device() { this->Tango::Device_4Impl::delete_device(); } void Device_4ImplWrap::delete_dev() { // Call here the delete_device method. It is defined in Device_4ImplWrap // class which is already destroyed when the Tango kernel call the // delete_device method try { delete_device(); } catch (Tango::DevFailed &e) { Tango::Except::print_exception(e); } } void Device_4ImplWrap::py_delete_dev() { Device_4ImplWrap::delete_dev(); PyDeviceImplBase::py_delete_dev(); } void Device_4ImplWrap::always_executed_hook() { CALL_DEVICE_METHOD(Device_4Impl, always_executed_hook) } void Device_4ImplWrap::default_always_executed_hook() { this->Tango::Device_4Impl::always_executed_hook(); } void Device_4ImplWrap::read_attr_hardware(vector &attr_list) { CALL_DEVICE_METHOD_VARGS(Device_4Impl, read_attr_hardware, attr_list) } void Device_4ImplWrap::default_read_attr_hardware(vector &attr_list) { this->Tango::Device_4Impl::read_attr_hardware(attr_list); } void Device_4ImplWrap::write_attr_hardware(vector &attr_list) { CALL_DEVICE_METHOD_VARGS(Device_4Impl, write_attr_hardware, attr_list) } void Device_4ImplWrap::default_write_attr_hardware(vector &attr_list) { this->Tango::Device_4Impl::write_attr_hardware(attr_list); } Tango::DevState Device_4ImplWrap::dev_state() { CALL_DEVICE_METHOD_RET(Device_4Impl, dev_state) // Keep the compiler quiet return Tango::UNKNOWN; } Tango::DevState Device_4ImplWrap::default_dev_state() { return this->Tango::Device_4Impl::dev_state(); } Tango::ConstDevString Device_4ImplWrap::dev_status() { __AUX_DECL_CALL_DEVICE_METHOD try { if (override dev_status = this->get_override("dev_status") ) { std::string status = dev_status(); this->the_status = status; } else { this->the_status = Device_4Impl::dev_status(); } } __AUX_CATCH_PY_EXCEPTION \ __AUX_CATCH_EXCEPTION(dev_status) return this->the_status.c_str(); } Tango::ConstDevString Device_4ImplWrap::default_dev_status() { return this->Tango::Device_4Impl::dev_status(); } void Device_4ImplWrap::signal_handler(long signo) { try { CALL_DEVICE_METHOD_VARGS(Device_4Impl, signal_handler, signo) } catch(Tango::DevFailed &df) { long nb_err = df.errors.length(); df.errors.length(nb_err + 1); df.errors[nb_err].reason = CORBA::string_dup( "PyDs_UnmanagedSignalHandlerException"); df.errors[nb_err].desc = CORBA::string_dup( "An unmanaged Tango::DevFailed exception occurred in signal_handler"); df.errors[nb_err].origin = CORBA::string_dup("Device_4Impl.signal_handler"); df.errors[nb_err].severity = Tango::ERR; Tango::Except::print_exception(df); } } void Device_4ImplWrap::default_signal_handler(long signo) { this->Tango::Device_4Impl::signal_handler(signo); } Device_5ImplWrap::Device_5ImplWrap(PyObject *self, CppDeviceClass *cl, std::string &st) :Tango::Device_5Impl(cl,st), PyDeviceImplBase(self) { _init(); } Device_5ImplWrap::Device_5ImplWrap(PyObject *self, CppDeviceClass *cl, const char *name, const char *desc /* = "A Tango device" */, Tango::DevState sta /* = Tango::UNKNOWN */, const char *status /* = StatusNotSet */) :Tango::Device_5Impl(cl, name, desc, sta, status), PyDeviceImplBase(self) { _init(); } Device_5ImplWrap::~Device_5ImplWrap() { delete_device(); } void Device_5ImplWrap::_init() { // Make sure the wrapper contains a valid pointer to the self // I found out this is needed by inspecting the boost wrapper_base.hpp code initialize_wrapper(the_self, this); } void Device_5ImplWrap::init_device() { AutoPythonGIL __py_lock; try { this->get_override("init_device")(); } catch(boost::python::error_already_set &eas) { handle_python_exception(eas); } } void Device_5ImplWrap::delete_device() { CALL_DEVICE_METHOD(Device_5Impl, delete_device) } void Device_5ImplWrap::default_delete_device() { this->Tango::Device_5Impl::delete_device(); } void Device_5ImplWrap::delete_dev() { // Call here the delete_device method. It is defined in Device_5ImplWrap // class which is already destroyed when the Tango kernel call the // delete_device method try { delete_device(); } catch (Tango::DevFailed &e) { Tango::Except::print_exception(e); } } void Device_5ImplWrap::py_delete_dev() { Device_5ImplWrap::delete_dev(); PyDeviceImplBase::py_delete_dev(); } void Device_5ImplWrap::always_executed_hook() { CALL_DEVICE_METHOD(Device_5Impl, always_executed_hook) } void Device_5ImplWrap::default_always_executed_hook() { this->Tango::Device_5Impl::always_executed_hook(); } void Device_5ImplWrap::read_attr_hardware(vector &attr_list) { CALL_DEVICE_METHOD_VARGS(Device_5Impl, read_attr_hardware, attr_list) } void Device_5ImplWrap::default_read_attr_hardware(vector &attr_list) { this->Tango::Device_5Impl::read_attr_hardware(attr_list); } void Device_5ImplWrap::write_attr_hardware(vector &attr_list) { CALL_DEVICE_METHOD_VARGS(Device_5Impl, write_attr_hardware, attr_list) } void Device_5ImplWrap::default_write_attr_hardware(vector &attr_list) { this->Tango::Device_5Impl::write_attr_hardware(attr_list); } Tango::DevState Device_5ImplWrap::dev_state() { CALL_DEVICE_METHOD_RET(Device_5Impl, dev_state) // Keep the compiler quiet return Tango::UNKNOWN; } Tango::DevState Device_5ImplWrap::default_dev_state() { return this->Tango::Device_5Impl::dev_state(); } Tango::ConstDevString Device_5ImplWrap::dev_status() { __AUX_DECL_CALL_DEVICE_METHOD try { if (override dev_status = this->get_override("dev_status") ) { std::string status = dev_status(); this->the_status = status; } else { this->the_status = Device_5Impl::dev_status(); } } __AUX_CATCH_PY_EXCEPTION \ __AUX_CATCH_EXCEPTION(dev_status) return this->the_status.c_str(); } Tango::ConstDevString Device_5ImplWrap::default_dev_status() { return this->Tango::Device_5Impl::dev_status(); } void Device_5ImplWrap::signal_handler(long signo) { try { CALL_DEVICE_METHOD_VARGS(Device_5Impl, signal_handler, signo) } catch(Tango::DevFailed &df) { long nb_err = df.errors.length(); df.errors.length(nb_err + 1); df.errors[nb_err].reason = CORBA::string_dup( "PyDs_UnmanagedSignalHandlerException"); df.errors[nb_err].desc = CORBA::string_dup( "An unmanaged Tango::DevFailed exception occurred in signal_handler"); df.errors[nb_err].origin = CORBA::string_dup("Device_5Impl.signal_handler"); df.errors[nb_err].severity = Tango::ERR; Tango::Except::print_exception(df); } } void Device_5ImplWrap::default_signal_handler(long signo) { this->Tango::Device_5Impl::signal_handler(signo); } #if ((defined sun) || (defined WIN32)) BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(register_signal_overload, Tango::DeviceImpl::register_signal, 1, 1) #else BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(register_signal_overload, Tango::DeviceImpl::register_signal, 1, 2) #endif BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(append_status_overload, Tango::DeviceImpl::append_status, 1, 2) BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(set_change_event_overload, Tango::DeviceImpl::set_change_event, 2, 3) BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(set_archive_event_overload, Tango::DeviceImpl::set_archive_event, 2, 3) BOOST_PYTHON_FUNCTION_OVERLOADS(remove_attribute_overload, PyDeviceImpl::remove_attribute, 2, 3) void export_device_impl() { // The following function declarations are necessary to be able to cast // the function parameters from string& to const string&, otherwise python // will not recognize the method calls long (Tango::DeviceImpl::*get_cmd_poll_ring_depth_)(std::string &) = &Tango::DeviceImpl::get_cmd_poll_ring_depth; long (Tango::DeviceImpl::*get_attr_poll_ring_depth_)(std::string &) = &Tango::DeviceImpl::get_attr_poll_ring_depth; void (Tango::DeviceImpl::*stop_polling1)() = &Tango::DeviceImpl::stop_polling; void (Tango::DeviceImpl::*stop_polling2)(bool) = &Tango::DeviceImpl::stop_polling; class_, boost::noncopyable>("DeviceImpl", init >()) .def("init_device", pure_virtual(&Tango::DeviceImpl::init_device)) .def("set_state", &Tango::DeviceImpl::set_state) .def("get_state", &Tango::DeviceImpl::get_state, return_value_policy()) .def("get_prev_state", &Tango::DeviceImpl::get_prev_state, return_value_policy()) .def("get_name", &Tango::DeviceImpl::get_name, return_value_policy()) .def("get_device_attr", &Tango::DeviceImpl::get_device_attr, return_value_policy()) .def("register_signal", &Tango::DeviceImpl::register_signal, register_signal_overload()) .def("unregister_signal", &Tango::DeviceImpl::unregister_signal) .def("get_status", &Tango::DeviceImpl::get_status, return_value_policy()) .def("set_status", &Tango::DeviceImpl::set_status) .def("append_status", &Tango::DeviceImpl::append_status, append_status_overload()) .def("dev_state", &Tango::DeviceImpl::dev_state) .def("dev_status", &Tango::DeviceImpl::dev_status) .def("get_attribute_config", &PyDeviceImpl::get_attribute_config) .def("set_change_event", &Tango::DeviceImpl::set_change_event, set_change_event_overload()) .def("set_archive_event", &Tango::DeviceImpl::set_archive_event, set_archive_event_overload()) .def("_add_attribute", &PyDeviceImpl::add_attribute) .def("_remove_attribute", &PyDeviceImpl::remove_attribute, remove_attribute_overload()) .def("_add_command", &PyDeviceImpl::add_command) .def("_remove_command", &PyDeviceImpl::remove_command) //@TODO .def("get_device_class") //@TODO .def("get_db_device") .def("is_attribute_polled", &PyDeviceImpl::is_attribute_polled) .def("is_command_polled", &PyDeviceImpl::is_command_polled) .def("get_attribute_poll_period", &PyDeviceImpl::get_attribute_poll_period) .def("get_command_poll_period", &PyDeviceImpl::get_command_poll_period) .def("poll_attribute", &PyDeviceImpl::poll_attribute) .def("poll_command", &PyDeviceImpl::poll_command) .def("stop_poll_attribute", &PyDeviceImpl::stop_poll_attribute) .def("stop_poll_command", &PyDeviceImpl::stop_poll_command) .def("get_exported_flag", &Tango::DeviceImpl::get_exported_flag) .def("get_poll_ring_depth", &Tango::DeviceImpl::get_poll_ring_depth) .def("get_poll_old_factor", &Tango::DeviceImpl::get_poll_old_factor) .def("is_polled", (bool (Tango::DeviceImpl::*) ())&Tango::DeviceImpl::is_polled) .def("get_polled_cmd", &PyDeviceImpl::get_polled_cmd) .def("get_polled_attr", &PyDeviceImpl::get_polled_attr) .def("get_non_auto_polled_cmd", &PyDeviceImpl::get_non_auto_polled_cmd) .def("get_non_auto_polled_attr", &PyDeviceImpl::get_non_auto_polled_attr) //@TODO .def("get_poll_obj_list", &PyDeviceImpl::get_poll_obj_list) .def("stop_polling", stop_polling1) .def("stop_polling", stop_polling2) .def("check_command_exists", &Tango::DeviceImpl::check_command_exists) //@TODO .def("get_command", &PyDeviceImpl::get_command) .def("get_dev_idl_version", &Tango::DeviceImpl::get_dev_idl_version) .def("get_cmd_poll_ring_depth", (long (Tango::DeviceImpl::*) (const std::string &)) get_cmd_poll_ring_depth_) .def("get_attr_poll_ring_depth", (long (Tango::DeviceImpl::*) (const std::string &)) get_attr_poll_ring_depth_) .def("is_device_locked", &Tango::DeviceImpl::is_device_locked) .def("init_logger", &Tango::DeviceImpl::init_logger) .def("start_logging", &Tango::DeviceImpl::start_logging) .def("stop_logging", &Tango::DeviceImpl::stop_logging) //.def("set_exported_flag", &Tango::DeviceImpl::set_exported_flag) //.def("set_poll_ring_depth", &Tango::DeviceImpl::set_poll_ring_depth) .def("push_change_event", (void (*) (Tango::DeviceImpl &, str &)) &PyDeviceImpl::push_change_event, (arg_("self"), arg_("attr_name"))) .def("push_change_event", (void (*) (Tango::DeviceImpl &, str &, object &)) &PyDeviceImpl::push_change_event) .def("push_change_event", (void (*) (Tango::DeviceImpl &, str &, str &, str &)) &PyDeviceImpl::push_change_event) .def("push_change_event", (void (*) (Tango::DeviceImpl &, str &, str &, object &)) &PyDeviceImpl::push_change_event) .def("push_change_event", (void (*) (Tango::DeviceImpl &, str &, object &, long)) &PyDeviceImpl::push_change_event) .def("push_change_event", (void (*) (Tango::DeviceImpl &, str &, object &, long, long)) &PyDeviceImpl::push_change_event) .def("push_change_event", (void (*) (Tango::DeviceImpl &, str &, object &, double, Tango::AttrQuality)) &PyDeviceImpl::push_change_event) .def("push_change_event", (void (*) (Tango::DeviceImpl &, str &, str &, str &, double, Tango::AttrQuality)) &PyDeviceImpl::push_change_event) .def("push_change_event", (void (*) (Tango::DeviceImpl &, str &, str &, object &, double, Tango::AttrQuality)) &PyDeviceImpl::push_change_event) .def("push_change_event", (void (*) (Tango::DeviceImpl &, str &, object &, double, Tango::AttrQuality, long)) &PyDeviceImpl::push_change_event) .def("push_change_event", (void (*) (Tango::DeviceImpl &, str &, object &, double, Tango::AttrQuality, long, long)) &PyDeviceImpl::push_change_event) .def("push_archive_event", (void (*) (Tango::DeviceImpl &, str &)) &PyDeviceImpl::push_archive_event, (arg_("self"), arg_("attr_name"))) .def("push_archive_event", (void (*) (Tango::DeviceImpl &, str &, object &)) &PyDeviceImpl::push_archive_event) .def("push_archive_event", (void (*) (Tango::DeviceImpl &, str &, str &, str &)) &PyDeviceImpl::push_archive_event) .def("push_archive_event", (void (*) (Tango::DeviceImpl &, str &, str &, object &)) &PyDeviceImpl::push_archive_event) .def("push_archive_event", (void (*) (Tango::DeviceImpl &, str &, object &, long)) &PyDeviceImpl::push_archive_event) .def("push_archive_event", (void (*) (Tango::DeviceImpl &, str &, object &, long, long)) &PyDeviceImpl::push_archive_event) .def("push_archive_event", (void (*) (Tango::DeviceImpl &, str &, object &, double, Tango::AttrQuality)) &PyDeviceImpl::push_archive_event) .def("push_archive_event", (void (*) (Tango::DeviceImpl &, str &, str &, str &, double, Tango::AttrQuality)) &PyDeviceImpl::push_archive_event) .def("push_archive_event", (void (*) (Tango::DeviceImpl &, str &, str &, object &, double, Tango::AttrQuality)) &PyDeviceImpl::push_archive_event) .def("push_archive_event", (void (*) (Tango::DeviceImpl &, str &, object &, double, Tango::AttrQuality, long)) &PyDeviceImpl::push_archive_event) .def("push_archive_event", (void (*) (Tango::DeviceImpl &, str &, object &, double, Tango::AttrQuality, long, long)) &PyDeviceImpl::push_archive_event) .def("push_event", (void (*) (Tango::DeviceImpl &, str &, object &, object &)) &PyDeviceImpl::push_event) .def("push_event", (void (*) (Tango::DeviceImpl &, str &, object &, object &, object &)) &PyDeviceImpl::push_event) .def("push_event", (void (*) (Tango::DeviceImpl &, str &, object &, object &, str &, str &)) &PyDeviceImpl::push_event) .def("push_event", (void (*) (Tango::DeviceImpl &, str &, object &, object &, str &, object &)) &PyDeviceImpl::push_event) .def("push_event", (void (*) (Tango::DeviceImpl &, str &, object &, object &, object &, long)) &PyDeviceImpl::push_event) .def("push_event", (void (*) (Tango::DeviceImpl &, str &, object &, object &, object &, long, long)) &PyDeviceImpl::push_event) .def("push_event", (void (*) (Tango::DeviceImpl &, str &, object &, object &, object &, double, Tango::AttrQuality)) &PyDeviceImpl::push_event) .def("push_event", (void (*) (Tango::DeviceImpl &, str &, object &, object &, str &, str &, double, Tango::AttrQuality)) &PyDeviceImpl::push_event) .def("push_event", (void (*) (Tango::DeviceImpl &, str &, object &, object &, str &, object &, double, Tango::AttrQuality)) &PyDeviceImpl::push_event) .def("push_event", (void (*) (Tango::DeviceImpl &, str &, object &, object &, object &, double, Tango::AttrQuality, long)) &PyDeviceImpl::push_event) .def("push_event", (void (*) (Tango::DeviceImpl &, str &, object &, object &, object &, double, Tango::AttrQuality, long, long)) &PyDeviceImpl::push_event) .def("push_data_ready_event", (void (*) (Tango::DeviceImpl &, str &, long)) &PyDeviceImpl::push_data_ready_event, (arg_("self"), arg_("attr_name"), arg_("ctr"))) .def("push_att_conf_event", &Tango::DeviceImpl::push_att_conf_event) .def("push_pipe_event", (void (*) (Tango::DeviceImpl &, str &, object&)) &PyDeviceImpl::push_pipe_event, (arg_("self"), arg_("pipe_name"), arg_("pipe_data"))) .def("get_logger", &Tango::DeviceImpl::get_logger, return_internal_reference<>()) .def("__debug_stream", &PyDeviceImpl::debug) .def("__info_stream", &PyDeviceImpl::info) .def("__warn_stream", &PyDeviceImpl::warn) .def("__error_stream", &PyDeviceImpl::error) .def("__fatal_stream", &PyDeviceImpl::fatal) .def("get_min_poll_period", &Tango::DeviceImpl::get_min_poll_period) .def("get_cmd_min_poll_period", &Tango::DeviceImpl::get_cmd_min_poll_period, return_internal_reference<>()) .def("get_attr_min_poll_period", &Tango::DeviceImpl::get_attr_min_poll_period, return_internal_reference<>()) .def("is_there_subscriber", &Tango::DeviceImpl::is_there_subscriber) ; implicitly_convertible, auto_ptr >(); class_, boost::noncopyable> ("Device_2Impl", init >()) .def("get_attribute_config_2", &PyDevice_2Impl::get_attribute_config_2) //@TODO .def("read_attribute_history_2", &PyDevice_2Impl::read_attribute_history_2) ; class_, boost::noncopyable> ("Device_3Impl", init >()) .def("init_device", pure_virtual(&Tango::Device_3Impl::init_device)) .def("delete_device", &Tango::Device_3Impl::delete_device, &Device_3ImplWrap::default_delete_device) .def("always_executed_hook", &Tango::Device_3Impl::always_executed_hook, &Device_3ImplWrap::default_always_executed_hook) .def("read_attr_hardware", &Tango::Device_3Impl::read_attr_hardware, &Device_3ImplWrap::default_read_attr_hardware) .def("write_attr_hardware", &Tango::Device_3Impl::write_attr_hardware, &Device_3ImplWrap::default_write_attr_hardware) .def("dev_state", &Tango::Device_3Impl::dev_state, &Device_3ImplWrap::default_dev_state) .def("dev_status", &Tango::Device_3Impl::dev_status, &Device_3ImplWrap::default_dev_status) .def("signal_handler", &Tango::Device_3Impl::signal_handler, &Device_3ImplWrap::default_signal_handler) .def("get_attribute_config_3", &PyDevice_3Impl::get_attribute_config_3) .def("set_attribute_config_3", &PyDevice_3Impl::set_attribute_config_3) ; class_, bases, boost::noncopyable> ("Device_4Impl", init >()) .def("init_device", pure_virtual(&Tango::Device_4Impl::init_device)) .def("delete_device", &Tango::Device_4Impl::delete_device, &Device_4ImplWrap::default_delete_device) .def("always_executed_hook", &Tango::Device_4Impl::always_executed_hook, &Device_4ImplWrap::default_always_executed_hook) .def("read_attr_hardware", &Tango::Device_4Impl::read_attr_hardware, &Device_4ImplWrap::default_read_attr_hardware) .def("write_attr_hardware", &Tango::Device_4Impl::write_attr_hardware, &Device_4ImplWrap::default_write_attr_hardware) .def("dev_state", &Tango::Device_4Impl::dev_state, &Device_4ImplWrap::default_dev_state) .def("dev_status", &Tango::Device_4Impl::dev_status, &Device_4ImplWrap::default_dev_status) .def("signal_handler", &Tango::Device_4Impl::signal_handler, &Device_4ImplWrap::default_signal_handler) ; implicitly_convertible, auto_ptr >(); class_, bases, boost::noncopyable> ("Device_5Impl", init >()) .def("init_device", pure_virtual(&Tango::Device_5Impl::init_device)) .def("delete_device", &Tango::Device_5Impl::delete_device, &Device_5ImplWrap::default_delete_device) .def("always_executed_hook", &Tango::Device_5Impl::always_executed_hook, &Device_5ImplWrap::default_always_executed_hook) .def("read_attr_hardware", &Tango::Device_5Impl::read_attr_hardware, &Device_5ImplWrap::default_read_attr_hardware) .def("write_attr_hardware", &Tango::Device_5Impl::write_attr_hardware, &Device_5ImplWrap::default_write_attr_hardware) .def("dev_state", &Tango::Device_5Impl::dev_state, &Device_5ImplWrap::default_dev_state) .def("dev_status", &Tango::Device_5Impl::dev_status, &Device_5ImplWrap::default_dev_status) .def("signal_handler", &Tango::Device_5Impl::signal_handler, &Device_5ImplWrap::default_signal_handler) ; implicitly_convertible, auto_ptr >(); } //namespace PyDevIntrThread //{ // /* ********************************** // * interface change event // * **********************************/ // inline void push_devintr_change_event(Tango::DevIntrThread &self) // { // self.push_event(); // } //} // //void export_device_intrthread() //{ // class_("DeviceImpl", // init >()) // // .def("push_event", // (void (*) (Tango::DevIntrThread &)) // &PyDeviceIntrThread::push_devintr_change_event, // (arg_("self"))) //} pytango-9.2.2/ext/server/device_impl.h000066400000000000000000000307201316324373100177510ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #ifndef _DEVICE_IMPL_H #define _DEVICE_IMPL_H #include #include #include "server/device_class.h" /** * A wrapper around the Tango::DeviceImpl class */ class DeviceImplWrap : public Tango::DeviceImpl, public boost::python::wrapper { public: /** a reference to itself */ PyObject *m_self; /** * Constructor * * @param[in] self * @param[in] cl * @param[in] st */ DeviceImplWrap(PyObject *self, CppDeviceClass *cl, std::string &st); /** * Constructor * * @param[in] self * @param[in] cl * @param[in] name * @param[in] desc * @param[in] sta * @param[in] status */ DeviceImplWrap(PyObject *self, CppDeviceClass *cl, const char *name, const char *desc = "A Tango device", Tango::DevState sta = Tango::UNKNOWN, const char *status = Tango::StatusNotSet); /** * Destructor */ virtual ~DeviceImplWrap() {} /** * Invokes the actual init_device */ void init_device(); bool _is_attribute_polled(const std::string &att_name); bool _is_command_polled(const std::string &cmd_name); int _get_attribute_poll_period(const std::string &att_name); int _get_command_poll_period(const std::string &cmd_name); void _poll_attribute(const std::string &att_name, int period); void _poll_command(const std::string &cmd_name, int period); void _stop_poll_attribute(const std::string &att_name); void _stop_poll_command(const std::string &cmd_name); }; /** * A wrapper around the Tango::Device_2Impl class */ class Device_2ImplWrap : public Tango::Device_2Impl, public boost::python::wrapper { public: /** a reference to itself */ PyObject *m_self; /** * Constructor * * @param[in] self * @param[in] cl * @param[in] st */ Device_2ImplWrap(PyObject *self, CppDeviceClass *cl, std::string &st); /** * Constructor * * @param[in] self * @param[in] cl * @param[in] name * @param[in] desc * @param[in] sta * @param[in] status */ Device_2ImplWrap(PyObject *self, CppDeviceClass *cl, const char *name, const char *desc = "A Tango device", Tango::DevState sta = Tango::UNKNOWN, const char *status = Tango::StatusNotSet); /** * Destructor */ virtual ~Device_2ImplWrap() {} /** * Invokes the actual init_device */ void init_device(); }; class PyDeviceImplBase { public: /** a reference to itself */ PyObject *the_self; std::string the_status; PyDeviceImplBase(PyObject *self); virtual ~PyDeviceImplBase(); virtual void py_delete_dev(); }; /** * A wrapper around the Tango::Device_3Impl class */ class Device_3ImplWrap : public Tango::Device_3Impl, public PyDeviceImplBase, public boost::python::wrapper { public: /** * Constructor * * @param[in] self * @param[in] cl * @param[in] st */ Device_3ImplWrap(PyObject *self, CppDeviceClass *cl, std::string &st); /** * Constructor * * @param[in] self * @param[in] cl * @param[in] name * @param[in] desc * @param[in] sta * @param[in] status */ Device_3ImplWrap(PyObject *self, CppDeviceClass *cl, const char *name, const char *desc = "A Tango device", Tango::DevState sta = Tango::UNKNOWN, const char *status = Tango::StatusNotSet); /** * Destructor */ virtual ~Device_3ImplWrap(); /** * Necessary init_device implementation to call python */ virtual void init_device(); /** * Necessary delete_device implementation to call python */ virtual void delete_device(); /** * Executes default delete_device implementation */ void default_delete_device(); /** * called to ask Python to delete a device by decrementing the Python * reference count */ virtual void delete_dev(); /** * Necessary always_executed_hook implementation to call python */ virtual void always_executed_hook(); /** * Executes default always_executed_hook implementation */ void default_always_executed_hook(); /** * Necessary read_attr_hardware implementation to call python */ virtual void read_attr_hardware(vector &attr_list); /** * Executes default read_attr_hardware implementation */ void default_read_attr_hardware(vector &attr_list); /** * Necessary write_attr_hardware implementation to call python */ virtual void write_attr_hardware(vector &attr_list); /** * Executes default write_attr_hardware implementation */ void default_write_attr_hardware(vector &attr_list); /** * Necessary dev_state implementation to call python */ virtual Tango::DevState dev_state(); /** * Executes default dev_state implementation */ Tango::DevState default_dev_state(); /** * Necessary dev_status implementation to call python */ virtual Tango::ConstDevString dev_status(); /** * Executes default dev_status implementation */ Tango::ConstDevString default_dev_status(); /** * Necessary signal_handler implementation to call python */ virtual void signal_handler(long signo); /** * Executes default signal_handler implementation */ void default_signal_handler(long signo); virtual void py_delete_dev(); protected: /** * internal method used to initialize the class. Called by the constructors */ void _init(); }; /** * Device_4ImplWrap is the class used to represent a Python Tango device. */ class Device_4ImplWrap : public Tango::Device_4Impl, public PyDeviceImplBase, public boost::python::wrapper { public: /** * Constructor * * @param[in] self * @param[in] cl * @param[in] st */ Device_4ImplWrap(PyObject *self, CppDeviceClass *cl, std::string &st); /** * Constructor * * @param[in] self * @param[in] cl * @param[in] name * @param[in] desc * @param[in] sta * @param[in] status */ Device_4ImplWrap(PyObject *self, CppDeviceClass *cl, const char *name, const char *desc = "A Tango device", Tango::DevState sta = Tango::UNKNOWN, const char *status = Tango::StatusNotSet); /** * Destructor */ virtual ~Device_4ImplWrap(); /** * Necessary init_device implementation to call python */ virtual void init_device(); /** * Necessary delete_device implementation to call python */ virtual void delete_device(); /** * Executes default delete_device implementation */ void default_delete_device(); /** * called to ask Python to delete a device by decrementing the Python * reference count */ virtual void delete_dev(); /** * Necessary always_executed_hook implementation to call python */ virtual void always_executed_hook(); /** * Executes default always_executed_hook implementation */ void default_always_executed_hook(); /** * Necessary read_attr_hardware implementation to call python */ virtual void read_attr_hardware(vector &attr_list); /** * Executes default read_attr_hardware implementation */ void default_read_attr_hardware(vector &attr_list); /** * Necessary write_attr_hardware implementation to call python */ virtual void write_attr_hardware(vector &attr_list); /** * Executes default write_attr_hardware implementation */ void default_write_attr_hardware(vector &attr_list); /** * Necessary dev_state implementation to call python */ virtual Tango::DevState dev_state(); /** * Executes default dev_state implementation */ Tango::DevState default_dev_state(); /** * Necessary dev_status implementation to call python */ virtual Tango::ConstDevString dev_status(); /** * Executes default dev_status implementation */ Tango::ConstDevString default_dev_status(); /** * Necessary signal_handler implementation to call python */ virtual void signal_handler(long signo); /** * Executes default signal_handler implementation */ void default_signal_handler(long signo); virtual void py_delete_dev(); protected: /** * internal method used to initialize the class. Called by the constructors */ void _init(); }; /** * Device_5ImplWrap is the class used to represent a Python Tango device. */ class Device_5ImplWrap : public Tango::Device_5Impl, public PyDeviceImplBase, public boost::python::wrapper { public: /** * Constructor * * @param[in] self * @param[in] cl * @param[in] st */ Device_5ImplWrap(PyObject *self, CppDeviceClass *cl, std::string &st); /** * Constructor * * @param[in] self * @param[in] cl * @param[in] name * @param[in] desc * @param[in] sta * @param[in] status */ Device_5ImplWrap(PyObject *self, CppDeviceClass *cl, const char *name, const char *desc = "A Tango device", Tango::DevState sta = Tango::UNKNOWN, const char *status = Tango::StatusNotSet); /** * Destructor */ virtual ~Device_5ImplWrap(); /** * Necessary init_device implementation to call python */ virtual void init_device(); /** * Necessary delete_device implementation to call python */ virtual void delete_device(); /** * Executes default delete_device implementation */ void default_delete_device(); /** * called to ask Python to delete a device by decrementing the Python * reference count */ virtual void delete_dev(); /** * Necessary always_executed_hook implementation to call python */ virtual void always_executed_hook(); /** * Executes default always_executed_hook implementation */ void default_always_executed_hook(); /** * Necessary read_attr_hardware implementation to call python */ virtual void read_attr_hardware(vector &attr_list); /** * Executes default read_attr_hardware implementation */ void default_read_attr_hardware(vector &attr_list); /** * Necessary write_attr_hardware implementation to call python */ virtual void write_attr_hardware(vector &attr_list); /** * Executes default write_attr_hardware implementation */ void default_write_attr_hardware(vector &attr_list); /** * Necessary dev_state implementation to call python */ virtual Tango::DevState dev_state(); /** * Executes default dev_state implementation */ Tango::DevState default_dev_state(); /** * Necessary dev_status implementation to call python */ virtual Tango::ConstDevString dev_status(); /** * Executes default dev_status implementation */ Tango::ConstDevString default_dev_status(); /** * Necessary signal_handler implementation to call python */ virtual void signal_handler(long signo); /** * Executes default signal_handler implementation */ void default_signal_handler(long signo); virtual void py_delete_dev(); protected: /** * internal method used to initialize the class. Called by the constructors */ void _init(); }; #endif // _DEVICE_IMPL_H pytango-9.2.2/ext/server/dserver.cpp000066400000000000000000000174331316324373100175040ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include #include "to_py.h" #include "from_py.h" using namespace boost::python; namespace PyDServer { PyObject* query_class(Tango::DServer &self) { Tango::DevVarStringArray *res = self.query_class(); PyObject *py_res = CORBA_sequence_to_list::convert(*res); delete res; return py_res; } PyObject* query_device(Tango::DServer &self) { Tango::DevVarStringArray *res = self.query_device(); PyObject *py_res = CORBA_sequence_to_list::convert(*res); delete res; return py_res; } PyObject* query_sub_device(Tango::DServer &self) { Tango::DevVarStringArray *res = self.query_sub_device(); PyObject *py_res = CORBA_sequence_to_list::convert(*res); delete res; return py_res; } PyObject* query_class_prop(Tango::DServer &self, const std::string &class_name) { std::string class_name2 = class_name; Tango::DevVarStringArray *res = self.query_class_prop(class_name2); PyObject *py_res = CORBA_sequence_to_list::convert(*res); delete res; return py_res; } PyObject* query_dev_prop(Tango::DServer &self, const std::string &dev_name) { std::string dev_name2 = dev_name; Tango::DevVarStringArray *res = self.query_dev_prop(dev_name2); PyObject *py_res = CORBA_sequence_to_list::convert(*res); delete res; return py_res; } PyObject* polled_device(Tango::DServer &self) { Tango::DevVarStringArray *res = self.polled_device(); PyObject *py_res = CORBA_sequence_to_list::convert(*res); delete res; return py_res; } PyObject* dev_poll_status(Tango::DServer &self, const std::string &dev_name) { std::string dev_name2 = dev_name; Tango::DevVarStringArray *res = self.dev_poll_status(dev_name2); PyObject *py_res = CORBA_sequence_to_list::convert(*res); delete res; return py_res; } void add_obj_polling(Tango::DServer &self, object &py_long_str_array, bool with_db_upd = true, int delta_ms = 0) { Tango::DevVarLongStringArray long_str_array; convert2array(py_long_str_array, long_str_array); self.add_obj_polling(&long_str_array, with_db_upd, delta_ms); } void upd_obj_polling_period(Tango::DServer &self, object &py_long_str_array, bool with_db_upd = true) { Tango::DevVarLongStringArray long_str_array; convert2array(py_long_str_array, long_str_array); self.upd_obj_polling_period(&long_str_array, with_db_upd); } void rem_obj_polling(Tango::DServer &self, object &py_str_array, bool with_db_upd = true) { Tango::DevVarStringArray str_array; convert2array(py_str_array, str_array); self.rem_obj_polling(&str_array, with_db_upd); } void lock_device(Tango::DServer &self, object &py_long_str_array) { Tango::DevVarLongStringArray long_str_array; convert2array(py_long_str_array, long_str_array); self.lock_device(&long_str_array); } Tango::DevLong un_lock_device(Tango::DServer &self, object &py_long_str_array) { Tango::DevVarLongStringArray long_str_array; convert2array(py_long_str_array, long_str_array); return self.un_lock_device(&long_str_array); } void re_lock_devices(Tango::DServer &self, object &py_str_array) { Tango::DevVarStringArray str_array; convert2array(py_str_array, str_array); self.re_lock_devices(&str_array); } PyObject* dev_lock_status(Tango::DServer &self, Tango::ConstDevString dev_name) { Tango::DevVarLongStringArray* ret = self.dev_lock_status(dev_name); PyObject* py_ret = CORBA_sequence_to_list::convert(*ret); delete ret; return py_ret; } } BOOST_PYTHON_FUNCTION_OVERLOADS(add_obj_polling_overload, PyDServer::add_obj_polling, 2, 4) BOOST_PYTHON_FUNCTION_OVERLOADS(upd_obj_polling_period_overload, PyDServer::upd_obj_polling_period, 2, 3) BOOST_PYTHON_FUNCTION_OVERLOADS(rem_obj_polling_overload, PyDServer::rem_obj_polling, 2, 3) void export_dserver() { // The following function declarations are necessary to be able to cast // the function parameters from string& to const string&, otherwise python // will not recognize the method calls void (Tango::DServer::*restart_)(std::string &) = &Tango::DServer::restart; class_, boost::noncopyable> ("DServer", no_init) .def("query_class", &PyDServer::query_class) .def("query_device", &PyDServer::query_device) .def("query_sub_device", &PyDServer::query_sub_device) .def("kill", &Tango::DServer::kill) .def("restart", (void (Tango::DServer::*) (const std::string &)) restart_) .def("restart_server", &Tango::DServer::restart_server) .def("query_class_prop", &PyDServer::query_class_prop) .def("query_dev_prop", &PyDServer::query_dev_prop) .def("polled_device", &PyDServer::polled_device) .def("dev_poll_status", &PyDServer::polled_device) .def("add_obj_polling", &PyDServer::add_obj_polling, add_obj_polling_overload()) .def("upd_obj_polling_period", &PyDServer::upd_obj_polling_period, upd_obj_polling_period_overload()) .def("rem_obj_polling", &PyDServer::rem_obj_polling, rem_obj_polling_overload()) .def("stop_polling", &Tango::DServer::stop_polling) .def("start_polling", (void (Tango::DServer::*)() ) &Tango::DServer::start_polling) .def("add_event_heartbeat", &Tango::DServer::add_event_heartbeat) .def("rem_event_heartbeat", &Tango::DServer::rem_event_heartbeat) .def("lock_device", &PyDServer::lock_device) .def("un_lock_device", &PyDServer::un_lock_device) .def("re_lock_devices", &PyDServer::re_lock_devices) .def("dev_lock_status", &PyDServer::dev_lock_status) .def("delete_devices", &Tango::DServer::delete_devices) .def("start_logging", &Tango::DServer::start_logging) .def("stop_logging", &Tango::DServer::stop_logging) .def("get_process_name", &Tango::DServer::get_process_name, return_value_policy()) .def("get_personal_name", &Tango::DServer::get_personal_name, return_value_policy()) .def("get_instance_name", &Tango::DServer::get_instance_name, return_value_policy()) .def("get_full_name", &Tango::DServer::get_full_name, return_value_policy()) .def("get_fqdn", &Tango::DServer::get_fqdn, return_value_policy()) .def("get_poll_th_pool_size", &Tango::DServer::get_poll_th_pool_size) .def("get_opt_pool_usage", &Tango::DServer::get_opt_pool_usage) .def("get_poll_th_conf", &Tango::DServer::get_poll_th_conf) ; } pytango-9.2.2/ext/server/encoded_attribute.cpp000066400000000000000000001330631316324373100215140ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include #include "tango_numpy.h" #include "device_attribute.h" using namespace boost::python; const int i = 1; #define IS_BIGENDIAN() ( (*(char*)&i) == 0 ) namespace PyEncodedAttribute { /// This callback is run to delete char* objects. /// It is called by python. The array was associated with an attribute /// value object that is not being used anymore. /// @param ptr_ The array object. /// @param type_ The type of data. We don't need it for now # ifdef PYCAPSULE_OLD template static void __ptr_deleter(void * ptr_) { if (1 == type) delete [] (static_cast(ptr_)); else if (2 == type) delete [] (static_cast(ptr_)); else if (4 == type) delete [] (static_cast(ptr_)); } # else template static void __ptr_deleter(PyObject* obj) { void * ptr_ = PyCapsule_GetPointer(obj, NULL); if (1 == type) delete [] (static_cast(ptr_)); else if (2 == type) delete [] (static_cast(ptr_)); else if (4 == type) delete [] (static_cast(ptr_)); } # endif void encode_gray8(Tango::EncodedAttribute &self, object py_value, int w, int h) { PyObject *py_value_ptr = py_value.ptr(); unsigned char *buffer = NULL; if (PyBytes_Check(py_value_ptr)) { buffer = reinterpret_cast(PyBytes_AsString(py_value_ptr)); self.encode_gray8(buffer, w, h); return; } #ifndef DISABLE_PYTANGO_NUMPY else if (PyArray_Check(py_value_ptr)) { w = static_cast(PyArray_DIM(py_value_ptr, 1)); h = static_cast(PyArray_DIM(py_value_ptr, 0)); buffer = (unsigned char*)(PyArray_DATA(py_value_ptr)); self.encode_gray8(buffer, w, h); return; } #endif // It must be a py sequence // we are sure that w and h are given by python (see encoded_attribute.py) const int length = w*h; unsigned char *raw_b = new unsigned char[length]; unique_pointer b(raw_b); buffer = raw_b; unsigned char *p = raw_b; int w_bytes = w; for (long y=0; y 255) { Py_DECREF(row); Py_DECREF(cell); PyErr_SetString(PyExc_TypeError, "int item not in range(256)"); boost::python::throw_error_already_set(); } *p = (unsigned char)byte; } Py_DECREF(cell); p++; } } Py_DECREF(row); } self.encode_gray8(buffer, w, h); } void encode_jpeg_gray8(Tango::EncodedAttribute &self, object py_value, int w, int h, double quality) { PyObject *py_value_ptr = py_value.ptr(); unsigned char *buffer = NULL; if (PyBytes_Check(py_value_ptr)) { buffer = reinterpret_cast(PyBytes_AsString(py_value_ptr)); self.encode_jpeg_gray8(buffer, w, h, quality); return; } #ifndef DISABLE_PYTANGO_NUMPY else if (PyArray_Check(py_value_ptr)) { w = static_cast(PyArray_DIM(py_value_ptr, 1)); h = static_cast(PyArray_DIM(py_value_ptr, 0)); buffer = (unsigned char*)(PyArray_DATA(py_value_ptr)); self.encode_jpeg_gray8(buffer, w, h, quality); return; } #endif // It must be a py sequence // we are sure that w and h are given by python (see encoded_attribute.py) const int length = w*h; unsigned char *raw_b = new unsigned char[length]; unique_pointer b(raw_b); buffer = raw_b; unsigned char *p = raw_b; int w_bytes = w; for (long y=0; y 255) { Py_DECREF(row); Py_DECREF(cell); PyErr_SetString(PyExc_TypeError, "int item not in range(256)"); boost::python::throw_error_already_set(); } *p = (unsigned char)byte; } Py_DECREF(cell); p++; } } Py_DECREF(row); } self.encode_jpeg_gray8(buffer, w, h, quality); } void encode_gray16(Tango::EncodedAttribute &self, object py_value, int w, int h) { PyObject *py_value_ptr = py_value.ptr(); unsigned short *buffer = NULL; if (PyBytes_Check(py_value_ptr)) { buffer = reinterpret_cast(PyBytes_AsString(py_value_ptr)); self.encode_gray16(buffer, w, h); return; } #ifndef DISABLE_PYTANGO_NUMPY else if (PyArray_Check(py_value_ptr)) { w = static_cast(PyArray_DIM(py_value_ptr, 1)); h = static_cast(PyArray_DIM(py_value_ptr, 0)); buffer = (unsigned short*)(PyArray_DATA(py_value_ptr)); self.encode_gray16(buffer, w, h); return; } #endif // It must be a py sequence // we are sure that w and h are given by python (see encoded_attribute.py) const int length = w*h; unsigned short *raw_b = new unsigned short[length]; unique_pointer b(raw_b); buffer = raw_b; unsigned short *p = raw_b; int w_bytes = 2*w; for (long y=0; y(PyBytes_AsString(cell)); *p = *word; } else if (PyLong_Check(cell)) { unsigned short word = (unsigned short)PyLong_AsUnsignedLong(cell); if (PyErr_Occurred()) { Py_DECREF(row); Py_DECREF(cell); boost::python::throw_error_already_set(); } *p = word; } else { Py_DECREF(row); Py_DECREF(cell); PyErr_SetString(PyExc_TypeError, "Unsupported data type in array element"); boost::python::throw_error_already_set(); } Py_DECREF(cell); p++; } } Py_DECREF(row); } self.encode_gray16(buffer, w, h); } void encode_rgb24(Tango::EncodedAttribute &self, object py_value, int w, int h) { PyObject *py_value_ptr = py_value.ptr(); unsigned char *buffer = NULL; if (PyBytes_Check(py_value_ptr)) { buffer = reinterpret_cast(PyBytes_AsString(py_value_ptr)); self.encode_rgb24(buffer, w, h); return; } #ifndef DISABLE_PYTANGO_NUMPY else if (PyArray_Check(py_value_ptr)) { buffer = (unsigned char*)(PyArray_DATA(py_value_ptr)); self.encode_rgb24(buffer, w, h); return; } #endif // It must be a py sequence // we are sure that w and h are given by python (see encoded_attribute.py) const int length = w*h; unsigned char *raw_b = new unsigned char[length]; unique_pointer b(raw_b); buffer = raw_b; unsigned char *p = raw_b; int w_bytes = 3*w; for (long y=0; y> 16) & 0xFF; p++; *p = (unsigned char)(byte >> 8) & 0xFF; p++; *p = (unsigned char)(byte) & 0xFF; p++; } else { *p = (unsigned char)(byte) & 0xFF; p++; *p = (unsigned char)(byte >> 8) & 0xFF; p++; *p = (unsigned char)(byte >> 16) & 0xFF; p++; } } Py_DECREF(cell); } } Py_DECREF(row); } self.encode_rgb24(buffer, w, h); } void encode_jpeg_rgb24(Tango::EncodedAttribute &self, object py_value, int w, int h, double quality) { PyObject *py_value_ptr = py_value.ptr(); unsigned char *buffer = NULL; if (PyBytes_Check(py_value_ptr)) { buffer = reinterpret_cast(PyBytes_AsString(py_value_ptr)); self.encode_jpeg_rgb24(buffer, w, h, quality); return; } #ifndef DISABLE_PYTANGO_NUMPY else if (PyArray_Check(py_value_ptr)) { buffer = (unsigned char*)(PyArray_DATA(py_value_ptr)); self.encode_jpeg_rgb24(buffer, w, h, quality); return; } #endif // It must be a py sequence // we are sure that w and h are given by python (see encoded_attribute.py) const int length = w*h; unsigned char *raw_b = new unsigned char[length]; unique_pointer b(raw_b); buffer = raw_b; unsigned char *p = raw_b; int w_bytes = 3*w; for (long y=0; y> 16) & 0xFF; p++; *p = (unsigned char)(byte >> 8) & 0xFF; p++; *p = (unsigned char)(byte) & 0xFF; p++; } else { *p = (unsigned char)(byte) & 0xFF; p++; *p = (unsigned char)(byte >> 8) & 0xFF; p++; *p = (unsigned char)(byte >> 16) & 0xFF; p++; } } Py_DECREF(cell); } } Py_DECREF(row); } self.encode_jpeg_rgb24(buffer, w, h, quality); } void encode_jpeg_rgb32(Tango::EncodedAttribute &self, object py_value, int w, int h, double quality) { PyObject *py_value_ptr = py_value.ptr(); unsigned char *buffer = NULL; if (PyBytes_Check(py_value_ptr)) { buffer = reinterpret_cast(PyBytes_AsString(py_value_ptr)); self.encode_jpeg_rgb32(buffer, w, h, quality); return; } #ifndef DISABLE_PYTANGO_NUMPY else if (PyArray_Check(py_value_ptr)) { buffer = (unsigned char*)(PyArray_DATA(py_value_ptr)); self.encode_jpeg_rgb32(buffer, w, h, quality); return; } #endif // It must be a py sequence // we are sure that w and h are given by python (see encoded_attribute.py) const int length = w*h; unsigned char *raw_b = new unsigned char[length]; unique_pointer b(raw_b); buffer = raw_b; unsigned char *p = raw_b; int w_bytes = 4*w; for (long y=0; y> 24) & 0xFF; p++; *p = (unsigned char)(byte >> 16) & 0xFF; p++; *p = (unsigned char)(byte >> 8) & 0xFF; p++; *p = (unsigned char)(byte) & 0xFF; p++; } else { *p = (unsigned char)(byte) & 0xFF; p++; *p = (unsigned char)(byte >> 8) & 0xFF; p++; *p = (unsigned char)(byte >> 16) & 0xFF; p++; *p = (unsigned char)(byte >> 24) & 0xFF; p++; } } Py_DECREF(cell); } } Py_DECREF(row); } self.encode_jpeg_rgb32(buffer, w, h, quality); } PyObject *decode_gray8(Tango::EncodedAttribute &self, Tango::DeviceAttribute *attr, PyTango::ExtractAs extract_as) { unsigned char *buffer; int width, height; self.decode_gray8(attr, &width, &height, &buffer); char *ch_ptr = reinterpret_cast(buffer); PyObject *ret = NULL; switch (extract_as) { case PyTango::ExtractAsNumpy: #ifndef DISABLE_PYTANGO_NUMPY { npy_intp dims[2] = { height, width }; ret = PyArray_SimpleNewFromData(2, dims, NPY_UBYTE, ch_ptr); if (!ret) { delete [] buffer; throw_error_already_set(); } // numpy.ndarray() does not own it's memory, so we need to manage it. // We can assign a 'base' object that will be informed (decref'd) when // the last copy of numpy.ndarray() disappears. // PyCObject is intended for that kind of things. It's seen as a // black box object from python. We assign him a function to be called // when it is deleted -> the function deletes de data. PyObject* guard = PyCapsule_New( static_cast(ch_ptr), NULL, __ptr_deleter<1>); if (!guard) { Py_XDECREF(ret); delete [] buffer; throw_error_already_set(); } PyArray_BASE(ret) = guard; break; } #endif case PyTango::ExtractAsString: { ret = PyTuple_New(3); if (!ret) { delete [] buffer; throw_error_already_set(); } size_t nb_bytes = width*height*sizeof(char); PyObject *buffer_str = PyBytes_FromStringAndSize(ch_ptr, nb_bytes); if (!buffer_str) { Py_XDECREF(ret); delete [] buffer; throw_error_already_set(); } PyTuple_SetItem(ret, 0, PyLong_FromLong(width)); PyTuple_SetItem(ret, 1, PyLong_FromLong(height)); PyTuple_SetItem(ret, 2, buffer_str); delete [] buffer; break; } case PyTango::ExtractAsTuple: { ret = PyTuple_New(height); if (!ret) { delete [] buffer; throw_error_already_set(); } for (long y=0; y < height; ++y) { PyObject *row = PyTuple_New(width); if (!row) { Py_XDECREF(ret); delete [] buffer; throw_error_already_set(); } for (long x=0; x < width; ++x) { PyTuple_SetItem(row, x, PyBytes_FromStringAndSize(ch_ptr + y*width+x, 1)); } PyTuple_SetItem(ret, y, row); } delete [] buffer; break; } case PyTango::ExtractAsPyTango3: case PyTango::ExtractAsList: { ret = PyList_New(height); if (!ret) { delete [] buffer; throw_error_already_set(); } for (long y=0; y < height; ++y) { PyObject *row = PyList_New(width); if (!row) { Py_XDECREF(ret); delete [] buffer; throw_error_already_set(); } for (long x=0; x < width; ++x) { PyList_SetItem(row, x, PyBytes_FromStringAndSize(ch_ptr + y*width+x, 1)); } PyList_SetItem(ret, y, row); } delete [] buffer; break; } default: { delete [] buffer; PyErr_SetString(PyExc_TypeError, "decode only supports " "ExtractAs Numpy, String, Tuple and List"); boost::python::throw_error_already_set(); break; } } return ret; } PyObject *decode_gray16(Tango::EncodedAttribute &self, Tango::DeviceAttribute *attr, PyTango::ExtractAs extract_as) { unsigned short *buffer; int width, height; self.decode_gray16(attr, &width, &height, &buffer); unsigned short *ch_ptr = buffer; PyObject *ret = NULL; switch (extract_as) { case PyTango::ExtractAsNumpy: #ifndef DISABLE_PYTANGO_NUMPY { npy_intp dims[2] = { height, width }; ret = PyArray_SimpleNewFromData(2, dims, NPY_USHORT, ch_ptr); if (!ret) { delete [] buffer; throw_error_already_set(); } // numpy.ndarray() does not own it's memory, so we need to manage it. // We can assign a 'base' object that will be informed (decref'd) when // the last copy of numpy.ndarray() disappears. // PyCObject is intended for that kind of things. It's seen as a // black box object from python. We assign him a function to be called // when it is deleted -> the function deletes de data. PyObject* guard = PyCapsule_New( static_cast(ch_ptr), NULL, __ptr_deleter<2>); if (!guard) { Py_XDECREF(ret); delete [] buffer; throw_error_already_set(); } PyArray_BASE(ret) = guard; break; } #endif case PyTango::ExtractAsString: { ret = PyTuple_New(3); if (!ret) { delete [] buffer; throw_error_already_set(); } size_t nb_bytes = width*height*sizeof(unsigned short); PyObject *buffer_str = PyBytes_FromStringAndSize( reinterpret_cast(ch_ptr), nb_bytes); delete [] buffer; if (!buffer_str) { Py_XDECREF(ret); throw_error_already_set(); } PyTuple_SetItem(ret, 0, PyLong_FromLong(width)); PyTuple_SetItem(ret, 1, PyLong_FromLong(height)); PyTuple_SetItem(ret, 2, buffer_str); break; } case PyTango::ExtractAsTuple: { ret = PyTuple_New(height); if (!ret) { delete [] buffer; throw_error_already_set(); } for (long y=0; y < height; ++y) { PyObject *row = PyTuple_New(width); if (!row) { Py_XDECREF(ret); delete [] buffer; throw_error_already_set(); } for (long x=0; x < width; ++x) { PyTuple_SetItem(row, x, PyLong_FromUnsignedLong(ch_ptr[y*width+x])); } PyTuple_SetItem(ret, y, row); } delete [] buffer; break; } case PyTango::ExtractAsPyTango3: case PyTango::ExtractAsList: { ret = PyList_New(height); if (!ret) { delete [] buffer; throw_error_already_set(); } for (long y=0; y < height; ++y) { PyObject *row = PyList_New(width); if (!row) { Py_XDECREF(ret); delete [] buffer; throw_error_already_set(); } for (long x=0; x < width; ++x) { PyList_SetItem(row, x, PyLong_FromUnsignedLong(ch_ptr[y*width+x])); } PyList_SetItem(ret, y, row); } delete [] buffer; break; } default: { delete [] buffer; PyErr_SetString(PyExc_TypeError, "decode only supports " "ExtractAs Numpy, String, Tuple and List"); boost::python::throw_error_already_set(); break; } } return ret; } PyObject *decode_rgb32(Tango::EncodedAttribute &self, Tango::DeviceAttribute *attr, PyTango::ExtractAs extract_as) { unsigned char *buffer; int width, height; self.decode_rgb32(attr, &width, &height, &buffer); unsigned char *ch_ptr = buffer; PyObject *ret = NULL; switch (extract_as) { case PyTango::ExtractAsNumpy: #ifndef DISABLE_PYTANGO_NUMPY { npy_intp dims[2] = { height, width }; ret = PyArray_SimpleNewFromData(2, dims, NPY_UINT32, ch_ptr); if (!ret) { delete [] buffer; throw_error_already_set(); } // numpy.ndarray() does not own it's memory, so we need to manage it. // We can assign a 'base' object that will be informed (decref'd) when // the last copy of numpy.ndarray() disappears. // PyCObject is intended for that kind of things. It's seen as a // black box object from python. We assign him a function to be called // when it is deleted -> the function deletes de data. PyObject* guard = PyCapsule_New( static_cast(ch_ptr), NULL, __ptr_deleter<4>); if (!guard) { Py_XDECREF(ret); delete [] buffer; throw_error_already_set(); } PyArray_BASE(ret) = guard; break; } #endif case PyTango::ExtractAsString: { ret = PyTuple_New(3); if (!ret) { delete [] buffer; throw_error_already_set(); } size_t nb_bytes = width*height*4; PyObject *buffer_str = PyBytes_FromStringAndSize( reinterpret_cast(ch_ptr), nb_bytes); delete [] buffer; if (!buffer_str) { Py_XDECREF(ret); throw_error_already_set(); } PyTuple_SetItem(ret, 0, PyLong_FromLong(width)); PyTuple_SetItem(ret, 1, PyLong_FromLong(height)); PyTuple_SetItem(ret, 2, buffer_str); break; } case PyTango::ExtractAsTuple: { ret = PyTuple_New(height); if (!ret) { delete [] buffer; throw_error_already_set(); } for (long y=0; y < height; ++y) { PyObject *row = PyTuple_New(width); if (!row) { Py_XDECREF(ret); delete [] buffer; throw_error_already_set(); } for (long x=0; x < width; ++x) { long idx = 4*(y*width+x); // data comes in in big endian format Tango::DevULong data; if (IS_BIGENDIAN()) { char *p = reinterpret_cast(&data); *p = ch_ptr[idx++]; ++p; *p = ch_ptr[idx++]; ++p; *p = ch_ptr[idx++]; ++p; *p = ch_ptr[idx]; } else { idx +=3; char *p = reinterpret_cast(&data); *p = ch_ptr[idx--]; ++p; *p = ch_ptr[idx--]; ++p; *p = ch_ptr[idx--]; ++p; *p = ch_ptr[idx]; } PyTuple_SetItem(row, x, PyLong_FromUnsignedLong(data)); } PyTuple_SetItem(ret, y, row); } delete [] buffer; break; } case PyTango::ExtractAsPyTango3: case PyTango::ExtractAsList: { ret = PyList_New(height); if (!ret) { delete [] buffer; throw_error_already_set(); } for (long y=0; y < height; ++y) { PyObject *row = PyList_New(width); if (!row) { Py_XDECREF(ret); delete [] buffer; throw_error_already_set(); } for (long x=0; x < width; ++x) { long idx = 4*(y*width+x); // data comes in in big endian format Tango::DevULong data; if (IS_BIGENDIAN()) { char *p = reinterpret_cast(&data); *p = ch_ptr[idx++]; ++p; *p = ch_ptr[idx++]; ++p; *p = ch_ptr[idx++]; ++p; *p = ch_ptr[idx]; } else { idx +=3; char *p = reinterpret_cast(&data); *p = ch_ptr[idx--]; ++p; *p = ch_ptr[idx--]; ++p; *p = ch_ptr[idx--]; ++p; *p = ch_ptr[idx]; } PyList_SetItem(row, x, PyLong_FromUnsignedLong(data)); } PyList_SetItem(ret, y, row); } delete [] buffer; break; } default: { delete [] buffer; PyErr_SetString(PyExc_TypeError, "decode only supports " "ExtractAs Numpy, String, Tuple and List"); boost::python::throw_error_already_set(); break; } } return ret; } } void export_encoded_attribute() { class_ EncodedAttribute( "EncodedAttribute", init<>()); EncodedAttribute .def(init >()) .def("_encode_gray8", &PyEncodedAttribute::encode_gray8) .def("_encode_gray16", &PyEncodedAttribute::encode_gray16) .def("_encode_rgb24", &PyEncodedAttribute::encode_rgb24) .def("_encode_jpeg_gray8", &PyEncodedAttribute::encode_jpeg_gray8) .def("_encode_jpeg_rgb24", &PyEncodedAttribute::encode_jpeg_rgb24) .def("_encode_jpeg_rgb32", &PyEncodedAttribute::encode_jpeg_rgb32) .def("_decode_gray8", &PyEncodedAttribute::decode_gray8) .def("_decode_gray16", &PyEncodedAttribute::decode_gray16) .def("_decode_rgb32", &PyEncodedAttribute::decode_rgb32) ; } pytango-9.2.2/ext/server/fwdAttr.cpp000066400000000000000000000020661316324373100174410ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include void export_user_default_fwdattr_prop() { boost::python::class_("UserDefaultFwdAttrProp") .def("set_label", &Tango::UserDefaultFwdAttrProp::set_label) ; } void export_fwdattr() { boost::python::class_("FwdAttr", boost::python::init()) .def("set_default_properties", &Tango::FwdAttr::set_default_properties) ; } pytango-9.2.2/ext/server/log4tango.cpp000066400000000000000000000121031316324373100177150ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include "defs.h" #include "pytgutils.h" using namespace boost::python; extern const char *param_must_be_seq; extern const char *non_string_seq; namespace PyLogging { void add_logging_target(object &obj) { PyObject *obj_ptr = obj.ptr(); if(PySequence_Check(obj_ptr) == 0) { raise_(PyExc_TypeError, param_must_be_seq); } Tango::DevVarStringArray par; int len = (int) PySequence_Length(obj_ptr); par.length(len); for(int i = 0; i < len; ++i) { PyObject* item_ptr = PySequence_GetItem(obj_ptr, i); str item = str(handle<>(item_ptr)); par[i] = CORBA::string_dup(extract(item)); } Tango::Logging::add_logging_target(&par); } void remove_logging_target(object &obj) { PyObject *obj_ptr = obj.ptr(); if(PySequence_Check(obj_ptr) == 0) { raise_(PyExc_TypeError, param_must_be_seq); } Tango::DevVarStringArray par; int len = (int) PySequence_Length(obj_ptr); par.length(len); for(int i = 0; i < len; ++i) { PyObject* item_ptr = PySequence_GetItem(obj_ptr, i); str item = str(handle<>(item_ptr)); par[i] = CORBA::string_dup(extract(item)); } Tango::Logging::remove_logging_target(&par); } } void export_log4tango() { { scope level_scope = class_("Level", no_init) .def("get_name", &log4tango::Level::get_name, return_value_policy()) .def("get_value", &log4tango::Level::get_value) .staticmethod("get_name") .staticmethod("get_value") ; enum_("LevelLevel") .value("OFF", log4tango::Level::OFF) .value("FATAL", log4tango::Level::FATAL) .value("ERROR", log4tango::Level::ERROR) .value("WARN", log4tango::Level::WARN) .value("INFO", log4tango::Level::INFO) .value("DEBUG", log4tango::Level::DEBUG) ; } class_("Logger", init >()) .def("get_name", &log4tango::Logger::get_name, return_value_policy()) .def("set_level", &log4tango::Logger::set_level) .def("get_level", &log4tango::Logger::get_level) .def("is_level_enabled", &log4tango::Logger::is_level_enabled) .def("__log", (void (log4tango::Logger::*)(log4tango::Level::Value, const std::string &)) &log4tango::Logger::log) .def("__log_unconditionally", (void (log4tango::Logger::*)(log4tango::Level::Value, const std::string &)) &log4tango::Logger::log_unconditionally) .def("__debug", (void (log4tango::Logger::*)(const std::string &)) &log4tango::Logger::debug) .def("__info", (void (log4tango::Logger::*)(const std::string &)) &log4tango::Logger::info) .def("__warn", (void (log4tango::Logger::*)(const std::string &)) &log4tango::Logger::warn) .def("__error", (void (log4tango::Logger::*)(const std::string &)) &log4tango::Logger::error) .def("__fatal", (void (log4tango::Logger::*)(const std::string &)) &log4tango::Logger::fatal) .def("is_debug_enabled", &log4tango::Logger::is_debug_enabled) .def("is_info_enabled", &log4tango::Logger::is_info_enabled) .def("is_warn_enabled", &log4tango::Logger::is_warn_enabled) .def("is_error_enabled", &log4tango::Logger::is_error_enabled) .def("is_fatal_enabled", &log4tango::Logger::is_fatal_enabled) ; class_("Logging", no_init) .def("get_core_logger", &Tango::Logging::get_core_logger, return_value_policy()) .def("add_logging_target", &PyLogging::add_logging_target) .def("remove_logging_target", &PyLogging::remove_logging_target) .def("start_logging", &Tango::Logging::start_logging) .def("stop_logging", &Tango::Logging::stop_logging) .staticmethod("get_core_logger") .staticmethod("add_logging_target") .staticmethod("remove_logging_target") .staticmethod("start_logging") .staticmethod("stop_logging") ; } pytango-9.2.2/ext/server/multi_attribute.cpp000066400000000000000000000044711316324373100212450ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include using namespace boost::python; void export_multi_attribute() { class_("MultiAttribute", no_init) .def("get_attr_by_name", &Tango::MultiAttribute::get_attr_by_name, return_value_policy()) .def("get_attr_by_ind", &Tango::MultiAttribute::get_attr_by_ind, return_value_policy()) .def("get_w_attr_by_name", &Tango::MultiAttribute::get_w_attr_by_name, return_value_policy()) .def("get_w_attr_by_ind", &Tango::MultiAttribute::get_w_attr_by_ind, return_value_policy()) .def("get_attr_ind_by_name", &Tango::MultiAttribute::get_attr_ind_by_name) // New in 7.0.0 .def("get_alarm_list", &Tango::MultiAttribute::get_alarm_list, return_internal_reference<>()) // New in 7.0.0 .def("get_attr_nb", &Tango::MultiAttribute::get_attr_nb) // New in 7.0.0 .def("check_alarm", (bool (Tango::MultiAttribute::*) ()) &Tango::MultiAttribute::check_alarm) // New in 7.0.0 .def("check_alarm", (bool (Tango::MultiAttribute::*) (const long)) &Tango::MultiAttribute::check_alarm) // New in 7.0.0 .def("check_alarm", (bool (Tango::MultiAttribute::*) (const char *)) &Tango::MultiAttribute::check_alarm) // New in 7.0.0 .def("read_alarm", (void (Tango::MultiAttribute::*) (const std::string &)) &Tango::MultiAttribute::read_alarm) // New in 7.0.0 .def("get_attribute_list", &Tango::MultiAttribute::get_attribute_list, return_value_policy()) ; } pytango-9.2.2/ext/server/multi_class_attribute.cpp000066400000000000000000000024041316324373100224240ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include "pytgutils.h" using namespace boost::python; void export_multi_class_attribute() { Tango::Attr& (Tango::MultiClassAttribute::*get_attr_)(std::string &) = &Tango::MultiClassAttribute::get_attr; class_("MultiClassAttribute", no_init) .def("get_attr", (Tango::Attr& (Tango::MultiClassAttribute::*) (const std::string &)) get_attr_, return_value_policy()) .def("remove_attr", &Tango::MultiClassAttribute::remove_attr) .def("get_attr_list", &Tango::MultiClassAttribute::get_attr_list, return_value_policy()) ; } pytango-9.2.2/ext/server/pipe.cpp000066400000000000000000000447131316324373100167700ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include "defs.h" #include "pytgutils.h" #include "pipe.h" #include "fast_from_py.h" #include #include "device_pipe.h" #define __AUX_DECL_CALL_PIPE_METHOD \ PyDeviceImplBase *__dev_ptr = dynamic_cast(dev); \ AutoPythonGIL __py_lock; #define __AUX_CATCH_PY_EXCEPTION \ catch(bopy::error_already_set &eas) \ { handle_python_exception(eas); } #define CALL_PIPE_METHOD(dev, name) \ __AUX_DECL_CALL_PIPE_METHOD \ try { bopy::call_method(__dev_ptr->the_self, name); } \ __AUX_CATCH_PY_EXCEPTION #define CALL_PIPE_METHOD_VARGS(dev, name, ...) \ __AUX_DECL_CALL_PIPE_METHOD \ try { bopy::call_method(__dev_ptr->the_self, name, __VA_ARGS__); } \ __AUX_CATCH_PY_EXCEPTION #define CALL_PIPE_METHOD_RET(retType, ret, dev, name) \ __AUX_DECL_CALL_PIPE_METHOD \ try { ret = bopy::call_method(__dev_ptr->the_self, name); } \ __AUX_CATCH_PY_EXCEPTION #define CALL_PIPE_METHOD_VARGS_RET(retType, ret, dev, name, ...) \ __AUX_DECL_CALL_PIPE_METHOD \ try { ret = bopy::call_method(__dev_ptr->the_self, name, __VA_ARGS__); } \ __AUX_CATCH_PY_EXCEPTION #define RET_CALL_PIPE_METHOD(retType, dev, name) \ __AUX_DECL_CALL_PIPE_METHOD \ try { return bopy::call_method(__dev_ptr->the_self, name); } \ __AUX_CATCH_PY_EXCEPTION #define RET_CALL_PIPE_METHOD_VARGS(retType, dev, name, ...) \ __AUX_DECL_CALL_PIPE_METHOD \ try { return bopy::call_method(__dev_ptr->the_self, name, __VA_ARGS__); } \ __AUX_CATCH_PY_EXCEPTION namespace PyTango { namespace Pipe { void _Pipe::read(Tango::DeviceImpl *dev, Tango::Pipe &pipe) { if (!_is_method(dev, read_name)) { TangoSys_OMemStream o; o << read_name << " method " << " not found for " << pipe.get_name(); Tango::Except::throw_exception("PyTango_ReadPipeMethodNotFound", o.str(), "PyTango::Pipe::read"); } CALL_PIPE_METHOD_VARGS(dev, read_name.c_str(), boost::ref(pipe)) } void _Pipe::write(Tango::DeviceImpl *dev, Tango::WPipe &pipe) { if (!_is_method(dev, write_name)) { TangoSys_OMemStream o; o << write_name << " method not found for " << pipe.get_name(); Tango::Except::throw_exception("PyTango_WritePipeMethodNotFound", o.str(), "PyTango::Pipe::write"); } PyDeviceImplBase *__dev_ptr = dynamic_cast(dev); AutoPythonGIL __py_lock; try { bopy::call_method(__dev_ptr->the_self, write_name.c_str(), boost::ref(pipe)); } catch(bopy::error_already_set &eas) { handle_python_exception(eas); } } bool _Pipe::is_allowed(Tango::DeviceImpl *dev, Tango::PipeReqType ty) { if (_is_method(dev, py_allowed_name)) { RET_CALL_PIPE_METHOD_VARGS(bool, dev, py_allowed_name.c_str(), ty) } // keep compiler quiet return true; } bool _Pipe::_is_method(Tango::DeviceImpl *dev, const std::string &name) { AutoPythonGIL __py_lock; PyDeviceImplBase *__dev_ptr = dynamic_cast(dev); PyObject *__dev_py = __dev_ptr->the_self; return is_method_defined(__dev_py, name); } static void throw_wrong_python_data_type(const std::string &name, const char *method) { TangoSys_OMemStream o; o << "Wrong Python type for pipe " << name << ends; Tango::Except::throw_exception("PyDs_WrongPythonDataTypeForPipe", o.str(), method); } template void append_scalar_encoded(T& obj, const std::string &name, bopy::object& py_value) { bopy::object p0 = py_value[0]; bopy::object p1 = py_value[1]; const char* encoded_format = bopy::extract (p0.ptr()); PyObject* data_ptr = p1.ptr(); Py_buffer view; if (PyObject_GetBuffer(data_ptr, &view, PyBUF_FULL_RO) < 0) { throw_wrong_python_data_type(obj.get_name(), "append_scalar_encoded"); } CORBA::ULong nb = static_cast(view.len); Tango::DevVarCharArray arr(nb, nb, (CORBA::Octet*)view.buf, false); Tango::DevEncoded value; value.encoded_format = CORBA::string_dup(encoded_format); value.encoded_data = arr; obj << value; PyBuffer_Release(&view); } template void __append_scalar(T &obj, const std::string &name, bopy::object& py_value) { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; TangoScalarType value; from_py::convert(py_value, value); obj << value; } template void append_scalar(Tango::Pipe& pipe, const std::string &name, bopy::object& py_value) { __append_scalar(pipe, name, py_value); } template<> void append_scalar(Tango::Pipe& pipe, const std::string &name, bopy::object& py_value) { throw_wrong_python_data_type(pipe.get_name(), "append_scalar"); } template<> void append_scalar(Tango::Pipe& pipe, const std::string &name, bopy::object& py_value) { throw_wrong_python_data_type(pipe.get_name(), "append_scalar"); } template<> void append_scalar(Tango::Pipe& pipe, const std::string &name, bopy::object& py_value) { append_scalar_encoded(pipe, name, py_value); } template void append_scalar(Tango::DevicePipeBlob& blob, const std::string &name, bopy::object& py_value) { __append_scalar(blob, name, py_value); } template<> void append_scalar(Tango::DevicePipeBlob& blob, const std::string &name, bopy::object& py_value) { throw_wrong_python_data_type(blob.get_name(), "append_scalar"); } template<> void append_scalar(Tango::DevicePipeBlob& blob, const std::string &name, bopy::object& py_value) { throw_wrong_python_data_type(blob.get_name(), "append_scalar"); } template<> void append_scalar(Tango::DevicePipeBlob& blob, const std::string &name, bopy::object& py_value) { append_scalar_encoded(blob, name, py_value); } // ------------- // Array version // ------------- template void __append_array(T& obj, const std::string &name, bopy::object& py_value) { typedef typename TANGO_const2type(tangoArrayTypeConst) TangoArrayType; TangoArrayType* value = fast_convert2array(py_value); obj << value; } template void append_array(Tango::Pipe& pipe, const std::string &name, bopy::object& py_value) { __append_array(pipe, name, py_value); } template<> void append_array(Tango::Pipe& pipe, const std::string &name, bopy::object& py_value) { throw_wrong_python_data_type(pipe.get_name(), "append_array"); } template<> void append_array(Tango::Pipe& pipe, const std::string &name, bopy::object& py_value) { throw_wrong_python_data_type(pipe.get_name(), "append_array"); } template<> void append_array(Tango::Pipe& pipe, const std::string &name, bopy::object& py_value) { throw_wrong_python_data_type(pipe.get_name(), "append_array"); } template<> void append_array(Tango::Pipe& pipe, const std::string &name, bopy::object& py_value) { throw_wrong_python_data_type(pipe.get_name(), "append_array"); } template void append_array(Tango::DevicePipeBlob& blob, const std::string &name, bopy::object& py_value) { __append_array(blob, name, py_value); } template<> void append_array(Tango::DevicePipeBlob& blob, const std::string &name, bopy::object& py_value) { throw_wrong_python_data_type(blob.get_name(), "append_array"); } template<> void append_array(Tango::DevicePipeBlob& blob, const std::string &name, bopy::object& py_value) { throw_wrong_python_data_type(blob.get_name(), "append_array"); } template<> void append_array(Tango::DevicePipeBlob& blob, const std::string &name, bopy::object& py_value) { throw_wrong_python_data_type(blob.get_name(), "append_array"); } template<> void append_array(Tango::DevicePipeBlob& blob, const std::string &name, bopy::object& py_value) { throw_wrong_python_data_type(blob.get_name(), "append_array"); } template void __append(T& obj, const std::string& name, bopy::object& py_value, const Tango::CmdArgType dtype) { TANGO_DO_ON_DEVICE_DATA_TYPE_ID(dtype, append_scalar(obj, name, py_value); , append_array(obj, name, py_value); ); } /* template void __set_value(T& obj, bopy::object& py_value) { bopy::object items = py_value.attr("items")(); Py_ssize_t size = bopy::len(items); obj.set_data_elt_nb(size); for(size_t i = 0; i < size; ++i) { std::string item_name = bopy::extract(items[i][0]); bopy::object py_item_data = items[i][1]; if (PyDict_Check(py_item_data.ptr())) // data element { bopy::object py_item_value = py_item_data["value"]; bopy::object py_item_dtype = py_item_data["dtype"]; Tango::CmdArgType item_dtype = bopy::extract(py_item_dtype); __append(obj, item_name, py_item_value, item_dtype); } else { std::string blob_name = bopy::extract(py_item_data[0]); bopy::object py_blob_data = py_item_data[1]; Tango::DevicePipeBlob blob(blob_name); __set_value(blob, py_blob_data); obj << blob; } } } */ /* template void __set_value(T& obj, bopy::object& py_value) { bopy::str name_key("name"); Py_ssize_t size = bopy::len(py_value); std::vector elem_names; for(size_t i = 0; i < size; ++i) { elem_names.push_back(bopy::extract(py_value[i][0])); } obj.set_data_elt_names(elem_names); for(size_t i = 0; i < size; ++i) { std::string item_name = bopy::extract(py_value[i][0]); bopy::dict py_item_data = bopy::extract(py_value[i][1]); if (py_item_data.has_key(name_key)) // a sub-blob { std::string blob_name = bopy::extract(py_item_data["name"]); bopy::object py_blob_data = py_item_data["data"]; Tango::DevicePipeBlob blob(blob_name); __set_value(blob, py_blob_data); obj << blob; } else { bopy::object py_item_value = py_item_data["value"]; bopy::object py_item_dtype = py_item_data["dtype"]; Tango::CmdArgType item_dtype = bopy::extract(py_item_dtype); __append(obj, item_name, py_item_value, item_dtype); } } } */ template void __set_value(T& obj, bopy::object& py_value) { // need to fill item names first because in case it is a sub-blob, // the Tango C++ API doesnt't provide a way to do it Py_ssize_t size = bopy::len(py_value); std::vector elem_names; for(ssize_t i = 0; i < size; ++i) { elem_names.push_back(bopy::extract(py_value[i]["name"])); } obj.set_data_elt_names(elem_names); for(ssize_t i = 0; i < size; ++i) { bopy::object item = py_value[i]; std::string item_name = bopy::extract(item["name"]); bopy::object py_item_data = item["value"]; Tango::CmdArgType item_dtype = bopy::extract(item["dtype"]); if (item_dtype == Tango::DEV_PIPE_BLOB) // a sub-blob { std::string blob_name = bopy::extract(py_item_data[0]); bopy::object py_blob_data = py_item_data[1]; Tango::DevicePipeBlob blob(blob_name); __set_value(blob, py_blob_data); obj << blob; } else { __append(obj, item_name, py_item_data, item_dtype); } } } void set_value(Tango::Pipe& pipe, bopy::object& py_value) { __set_value(pipe, py_value); } bopy::object get_value(Tango::WPipe& pipe) { bopy::object py_value; Tango::DevicePipeBlob blob = pipe.get_blob(); py_value = PyTango::DevicePipe::extract(blob); return py_value; } }} // namespace PyTango::Pipe void export_pipe() { bopy::class_("Pipe", bopy::init >()) .def("get_name", &Tango::Pipe::get_name, bopy::return_value_policy()) .def("set_name", &Tango::Pipe::set_name) .def("set_default_properties", &Tango::Pipe::set_default_properties) .def("get_root_blob_name", &Tango::Pipe::get_root_blob_name, bopy::return_value_policy()) .def("set_root_blob_name", &Tango::Pipe::set_root_blob_name) .def("get_desc", &Tango::Pipe::get_desc, bopy::return_value_policy()) .def("get_label", &Tango::Pipe::get_label, bopy::return_value_policy()) .def("get_disp_level", &Tango::Pipe::get_disp_level) .def("get_writable", &Tango::Pipe::get_writable) .def("get_pipe_serial_model", &Tango::Pipe::get_pipe_serial_model) .def("set_pipe_serial_model", &Tango::Pipe::set_pipe_serial_model) .def("has_failed", &Tango::Pipe::has_failed) .def("_set_value", (void (*) (Tango::Pipe &, bopy::object &)) &PyTango::Pipe::set_value) .def("get_value", (bopy::object (*) (Tango::WPipe &)) &PyTango::Pipe::get_value) ; bopy::class_, boost::noncopyable >("WPipe", bopy::init()) ; } namespace PyDevicePipe { static void throw_wrong_python_data_type(const std::string &name, const char *method) { TangoSys_OMemStream o; o << "Wrong Python type for pipe " << name << ends; Tango::Except::throw_exception("PyDs_WrongPythonDataTypeForPipe", o.str(), method); } template void __append_scalar(T &obj, const std::string &name, bopy::object& py_value) { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; TangoScalarType value; from_py::convert(py_value, value); obj << value; } template void __append_array(T& obj, const std::string &name, bopy::object& py_value) { typedef typename TANGO_const2type(tangoArrayTypeConst) TangoArrayType; TangoArrayType* value = fast_convert2array(py_value); obj << value; } template bool __check_type(const bopy::object& value) { bopy::extract item(value); return item.check(); } template bool __convert(const bopy::object& value, T& py_item_data) { bopy::extract item(value); if (item.check()) { py_item_data = item(); return true; } return false; } void __append(Tango::DevicePipeBlob& dpb, const std::string& name, bopy::object& value) { if (__check_type(value)) { __append_scalar(dpb, name, value); } else if (__check_type(value)) { __append_scalar(dpb, name, value); } else if (__check_type(value)) { __append_scalar(dpb, name, value); } else if (__check_type(value)) { __append_scalar(dpb, name, value); } else if (__check_type(value)) { if (__check_type(value[0])) { __append_array(dpb, name, value); } else if (__check_type(value[0])) { __append_array(dpb, name, value); } else if (__check_type(value[0])) { __append_array(dpb, name, value); } else { throw_wrong_python_data_type(name, "__append"); } } else { throw_wrong_python_data_type(name, "__append"); } } void __set_value(Tango::DevicePipeBlob& dpb, bopy::dict& dict) { int nitems = len(dict); std::vector elem_names; for (auto i=0; i(dict.keys()[i])); } dpb.set_data_elt_names(elem_names); bopy::list values = dict.values(); for (auto i=0; i (py_data[0]); dpb.set_name(name); bopy::dict data = bopy::extract(py_data[1]); __set_value(dpb, data); } } // namespace PyDevicePipe pytango-9.2.2/ext/server/pipe.h000066400000000000000000000050021316324373100164210ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #pragma once #include #include #include "exception.h" #include "pytgutils.h" #include "server/device_impl.h" namespace PyTango { namespace Pipe { class _Pipe { public: _Pipe() {} virtual ~_Pipe() {} void read(Tango::DeviceImpl *, Tango::Pipe &); void write(Tango::DeviceImpl *dev, Tango::WPipe &); bool is_allowed(Tango::DeviceImpl *, Tango::PipeReqType); void set_allowed_name(const std::string &name) { py_allowed_name = name; } void set_read_name(const std::string &name) { read_name = name; } void set_write_name(const std::string &name) { write_name = name; } bool _is_method(Tango::DeviceImpl *, const std::string &); private: std::string py_allowed_name; std::string read_name; std::string write_name; }; class PyPipe: public Tango::Pipe, public _Pipe { public: PyPipe(const std::string &name, const Tango::DispLevel level, const Tango::PipeWriteType write=Tango::PIPE_READ): Tango::Pipe(name, level, write) {} ~PyPipe() {} virtual void read(Tango::DeviceImpl *dev) { _Pipe::read(dev, *this); } virtual bool is_allowed(Tango::DeviceImpl *dev, Tango::PipeReqType rt) { return _Pipe::is_allowed(dev, rt); } }; class PyWPipe: public Tango::WPipe, public _Pipe { public: PyWPipe(const std::string &name, const Tango::DispLevel level): Tango::WPipe(name, level) {} ~PyWPipe() {} virtual void read(Tango::DeviceImpl *dev) { _Pipe::read(dev, *this); } virtual void write(Tango::DeviceImpl *dev) { _Pipe::write(dev, *this); } virtual bool is_allowed(Tango::DeviceImpl *dev, Tango::PipeReqType rt) { return _Pipe::is_allowed(dev, rt); } }; }} // namespace PyTango::Pipe namespace PyDevicePipe { void set_value(Tango::DevicePipeBlob &, boost::python::object &); } // namespace PyDevicePipe pytango-9.2.2/ext/server/subdev.cpp000066400000000000000000000035521316324373100173170ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include using namespace boost::python; namespace PySubDevDiag { PyObject *get_sub_devices(Tango::SubDevDiag &self) { Tango::DevVarStringArray *sub_devs = self.get_sub_devices(); boost::python::list py_sub_devs; for(unsigned long i = 0; i < sub_devs->length(); ++i) { py_sub_devs.append((*sub_devs)[i].in()); } delete sub_devs; return boost::python::incref(py_sub_devs.ptr()); } } void export_sub_dev_diag() { class_ ("SubDevDiag", no_init) .def("set_associated_device", &Tango::SubDevDiag::set_associated_device) .def("get_associated_device", &Tango::SubDevDiag::get_associated_device) .def("register_sub_device", &Tango::SubDevDiag::register_sub_device) .def("remove_sub_devices", (void (Tango::SubDevDiag::*) ()) &Tango::SubDevDiag::remove_sub_devices) .def("remove_sub_devices", (void (Tango::SubDevDiag::*) (std::string)) &Tango::SubDevDiag::remove_sub_devices) .def("get_sub_devices", &PySubDevDiag::get_sub_devices) .def("store_sub_devices", &Tango::SubDevDiag::store_sub_devices) .def("get_sub_devices_from_cache", &Tango::SubDevDiag::get_sub_devices_from_cache) ; } pytango-9.2.2/ext/server/tango_util.cpp000066400000000000000000000261051316324373100201730ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include "defs.h" #include "pytgutils.h" #include "exception.h" #include "server/device_class.h" using namespace boost::python; namespace PyUtil { void _class_factory(Tango::DServer* dserver) { AutoPythonGIL guard; PYTANGO_MOD // // First, create CPP class if any. Their names are defined in a Python list // boost::python::list cpp_class_list = extract(pytango.attr("get_cpp_classes")()); Py_ssize_t cl_len = boost::python::len(cpp_class_list); for(Py_ssize_t i = 0; i < cl_len; ++i) { bopy::tuple class_info = extract(cpp_class_list[i]); char *class_name = extract(class_info[0]); char *par_name = extract(class_info[1]); dserver->_create_cpp_class(class_name, par_name); } // // Create Python classes with a call to the class_factory Python function // pytango.attr("class_factory")(); // // Make all Python tango class(es) known to C++ and set the PyInterpreter state // boost::python::list constructed_classes(pytango.attr("get_constructed_classes")()); Py_ssize_t cc_len = boost::python::len(constructed_classes); for(Py_ssize_t i = 0; i < cc_len; ++i) { CppDeviceClass *cpp_dc = extract (constructed_classes[i])(); dserver->_add_class(cpp_dc); } } void server_init(Tango::Util & instance, bool with_window = false) { AutoPythonAllowThreads guard; Tango::DServer::register_class_factory(_class_factory); instance.server_init(with_window); } void server_run(Tango::Util & instance) { AutoPythonAllowThreads guard; instance.server_run(); } inline Tango::Util* init(boost::python::object &obj) { PyObject *obj_ptr = obj.ptr(); if(PySequence_Check(obj_ptr) == 0) { raise_(PyExc_TypeError, param_must_be_seq); } int argc = (int) PySequence_Length(obj_ptr); char** argv = new char*[argc]; Tango::Util* res = 0; try { for(int i = 0; i < argc; ++i) { PyObject* item_ptr = PySequence_GetItem(obj_ptr, i); str item = str(handle<>(item_ptr)); argv[i] = extract(item); } res = Tango::Util::init(argc, argv); } catch (...) { delete [] argv; throw; } delete [] argv; if (PyEval_ThreadsInitialized() == 0) { PyEval_InitThreads(); } return res; } inline Tango::Util* instance1() { return Tango::Util::instance(); } inline Tango::Util* instance2(bool b) { return Tango::Util::instance(b); } inline object get_device_list_by_class(Tango::Util &self, const string &class_name) { boost::python::list py_dev_list; vector &dev_list = self.get_device_list_by_class(class_name); for(vector::iterator it = dev_list.begin(); it != dev_list.end(); ++it) { object py_value = object( handle<>( to_python_indirect< Tango::DeviceImpl*, detail::make_reference_holder>()(*it))); py_dev_list.append(py_value); } return py_dev_list; } inline object get_device_by_name(Tango::Util &self, const string &dev_name) { Tango::DeviceImpl *value = self.get_device_by_name(dev_name); object py_value = object( handle<>( to_python_indirect< Tango::DeviceImpl*, detail::make_reference_holder>()(value))); return py_value; } inline object get_device_list(Tango::Util &self, const string &name) { boost::python::list py_dev_list; vector dev_list = self.get_device_list(name); for(vector::iterator it = dev_list.begin(); it != dev_list.end(); ++it) { object py_value = object( handle<>( to_python_indirect< Tango::DeviceImpl*, detail::make_reference_holder>()(*it))); py_dev_list.append(py_value); } return py_dev_list; } inline bool event_loop() { AutoPythonGIL guard; PYTANGO_MOD boost::python::object py_event_loop = pytango.attr("_server_event_loop"); boost::python::object py_ret = py_event_loop(); bool ret = boost::python::extract(py_ret); return ret; } inline void server_set_event_loop(Tango::Util& self, boost::python::object& py_event_loop) { PYTANGO_MOD if (py_event_loop.ptr() == Py_None) { self.server_set_event_loop(NULL); pytango.attr("_server_event_loop") = py_event_loop; } else { pytango.attr("_server_event_loop") = py_event_loop; self.server_set_event_loop(event_loop); } } void set_use_db(bool use_db) { Tango::Util::_UseDb = use_db; } boost::python::str get_dserver_ior(Tango::Util& self, Tango::DServer* dserver) { Tango::Device_var d = dserver->_this(); dserver->set_d_var(Tango::Device::_duplicate(d)); const char *dserver_ior = self.get_orb()->object_to_string(d); boost::python::str ret = dserver_ior; delete [] dserver_ior; return ret; } boost::python::str get_device_ior(Tango::Util& self, Tango::DeviceImpl* device) { const char *ior = self.get_orb()->object_to_string(device->get_d_var()); boost::python::str ret = ior; delete [] ior; return ret; } void orb_run(Tango::Util& self) { AutoPythonAllowThreads guard; self.get_orb()->run(); } boost::python::str get_pid_str(Tango::Util& self) { boost::python::str ret = self.get_pid_str().c_str(); return ret; } boost::python::str get_version_str(Tango::Util& self) { boost::python::str ret = self.get_version_str().c_str(); return ret; } static boost::shared_ptr makeUtil(boost::python::object& args) { Tango::Util* util = PyUtil::init(args); return boost::shared_ptr(util); } } BOOST_PYTHON_FUNCTION_OVERLOADS (server_init_overload, PyUtil::server_init, 1, 2) void export_util() { class_("Interceptors") .def("create_thread", &Tango::Interceptors::create_thread) .def("delete_thread", &Tango::Interceptors::delete_thread) ; class_("Util", no_init) .def("__init__", boost::python::make_constructor(PyUtil::makeUtil)) .def("init", PyUtil::init, return_value_policy()) .staticmethod("init") .def("instance", &PyUtil::instance1, return_value_policy()) .def("instance", &PyUtil::instance2, return_value_policy()) .staticmethod("instance") .def("set_trace_level", &Tango::Util::set_trace_level) .def("get_trace_level", &Tango::Util::get_trace_level) .def("get_ds_inst_name", &Tango::Util::get_ds_inst_name, return_value_policy()) .def("get_ds_exec_name", &Tango::Util::get_ds_exec_name, return_value_policy()) .def("get_ds_name", &Tango::Util::get_ds_name, return_value_policy()) .def("get_host_name", &Tango::Util::get_host_name, return_value_policy()) .def("get_pid_str", &PyUtil::get_pid_str) .def("get_pid", &Tango::Util::get_pid) .def("get_tango_lib_release", &Tango::Util::get_tango_lib_release) .def("get_version_str", &PyUtil::get_version_str) .def("get_server_version", &Tango::Util::get_server_version, return_value_policy()) .def("set_server_version", &Tango::Util::set_server_version) .def("set_serial_model", &Tango::Util::set_serial_model) .def("get_serial_model", &Tango::Util::get_serial_model) .def("reset_filedatabase", &Tango::Util::reset_filedatabase) .def("unregister_server", &Tango::Util::unregister_server) .def("get_dserver_device", &Tango::Util::get_dserver_device, return_value_policy()) .def("server_init", &PyUtil::server_init, server_init_overload()) .def("server_run", &PyUtil::server_run) .def("server_cleanup", &Tango::Util::server_cleanup) .def("trigger_cmd_polling", &Tango::Util::trigger_cmd_polling) .def("trigger_attr_polling", &Tango::Util::trigger_attr_polling) .def("set_polling_threads_pool_size", &Tango::Util::set_polling_threads_pool_size) .def("get_polling_threads_pool_size", &Tango::Util::get_polling_threads_pool_size) .def("is_svr_starting", &Tango::Util::is_svr_starting) .def("is_svr_shutting_down", &Tango::Util::is_svr_shutting_down) .def("is_device_restarting", &Tango::Util::is_device_restarting) .def("get_sub_dev_diag", &Tango::Util::get_sub_dev_diag, return_internal_reference<>()) .def("connect_db", &Tango::Util::connect_db) .def("reset_filedatabase", &Tango::Util::reset_filedatabase) .def("get_database", &Tango::Util::get_database, return_internal_reference<>()) .def("unregister_server", &Tango::Util::unregister_server) .def("get_device_list_by_class", &PyUtil::get_device_list_by_class) .def("get_device_by_name", &PyUtil::get_device_by_name) .def("get_device_list", &PyUtil::get_device_list) .def("server_set_event_loop", &PyUtil::server_set_event_loop) .def("set_interceptors", &Tango::Util::set_interceptors) .def_readonly("_UseDb", &Tango::Util::_UseDb) .def_readonly("_FileDb", &Tango::Util::_FileDb) .def("set_use_db", &PyUtil::set_use_db) .staticmethod("set_use_db") .def("get_dserver_ior", &PyUtil::get_dserver_ior) .def("get_device_ior", &PyUtil::get_device_ior) .def("orb_run", &PyUtil::orb_run) ; } pytango-9.2.2/ext/server/user_default_attr_prop.cpp000066400000000000000000000107721316324373100226050ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include using namespace boost::python; void export_user_default_attr_prop() { class_("UserDefaultAttrProp") .def("set_label", &Tango::UserDefaultAttrProp::set_label) .def("set_description", &Tango::UserDefaultAttrProp::set_description) .def("set_format", &Tango::UserDefaultAttrProp::set_format) .def("set_unit", &Tango::UserDefaultAttrProp::set_unit) .def("set_standard_unit", &Tango::UserDefaultAttrProp::set_standard_unit) .def("set_display_unit", &Tango::UserDefaultAttrProp::set_display_unit) .def("set_min_value", &Tango::UserDefaultAttrProp::set_min_value) .def("set_max_value", &Tango::UserDefaultAttrProp::set_max_value) .def("set_min_alarm", &Tango::UserDefaultAttrProp::set_min_alarm) .def("set_max_alarm", &Tango::UserDefaultAttrProp::set_max_alarm) .def("set_min_warning", &Tango::UserDefaultAttrProp::set_min_warning) .def("set_max_warning", &Tango::UserDefaultAttrProp::set_max_warning) .def("set_delta_t", &Tango::UserDefaultAttrProp::set_delta_t) .def("set_delta_val", &Tango::UserDefaultAttrProp::set_delta_val) .def("set_abs_change", &Tango::UserDefaultAttrProp::set_event_abs_change) .def("set_rel_change", &Tango::UserDefaultAttrProp::set_event_rel_change) .def("set_period", &Tango::UserDefaultAttrProp::set_event_period) .def("set_archive_abs_change", &Tango::UserDefaultAttrProp::set_archive_event_abs_change) .def("set_archive_rel_change", &Tango::UserDefaultAttrProp::set_archive_event_rel_change) .def("set_archive_period", &Tango::UserDefaultAttrProp::set_archive_event_period) .def("set_event_abs_change", &Tango::UserDefaultAttrProp::set_event_abs_change) .def("set_event_rel_change", &Tango::UserDefaultAttrProp::set_event_rel_change) .def("set_event_period", &Tango::UserDefaultAttrProp::set_event_period) .def("set_archive_event_abs_change", &Tango::UserDefaultAttrProp::set_archive_event_abs_change) .def("set_archive_event_rel_change", &Tango::UserDefaultAttrProp::set_archive_event_rel_change) .def("set_archive_event_period", &Tango::UserDefaultAttrProp::set_archive_event_period) .def("_set_enum_labels", &Tango::UserDefaultAttrProp::set_enum_labels) .def_readwrite("label", &Tango::UserDefaultAttrProp::label) .def_readwrite("description", &Tango::UserDefaultAttrProp::description) .def_readwrite("unit", &Tango::UserDefaultAttrProp::unit) .def_readwrite("standard_unit", &Tango::UserDefaultAttrProp::standard_unit) .def_readwrite("display_unit", &Tango::UserDefaultAttrProp::display_unit) .def_readwrite("format", &Tango::UserDefaultAttrProp::format) .def_readwrite("min_value", &Tango::UserDefaultAttrProp::min_value) .def_readwrite("max_value", &Tango::UserDefaultAttrProp::max_value) .def_readwrite("min_alarm", &Tango::UserDefaultAttrProp::min_alarm) .def_readwrite("max_alarm", &Tango::UserDefaultAttrProp::max_alarm) .def_readwrite("min_warning", &Tango::UserDefaultAttrProp::min_warning) .def_readwrite("max_warning", &Tango::UserDefaultAttrProp::max_warning) .def_readwrite("delta_val", &Tango::UserDefaultAttrProp::delta_val) .def_readwrite("delta_t", &Tango::UserDefaultAttrProp::delta_t) .def_readwrite("abs_change", &Tango::UserDefaultAttrProp::abs_change) .def_readwrite("rel_change", &Tango::UserDefaultAttrProp::rel_change) .def_readwrite("period", &Tango::UserDefaultAttrProp::period) .def_readwrite("archive_abs_change", &Tango::UserDefaultAttrProp::archive_abs_change) .def_readwrite("archive_rel_change", &Tango::UserDefaultAttrProp::archive_rel_change) .def_readwrite("archive_period", &Tango::UserDefaultAttrProp::archive_period) .def_readwrite("enum_labels", &Tango::UserDefaultAttrProp::enum_labels) ; } pytango-9.2.2/ext/server/user_default_pipe_prop.cpp000066400000000000000000000016111316324373100225600ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include using namespace boost::python; void export_user_default_pipe_prop() { class_("UserDefaultPipeProp") .def("set_label", &Tango::UserDefaultPipeProp::set_label) .def("set_description", &Tango::UserDefaultPipeProp::set_description) ; } pytango-9.2.2/ext/server/wattribute.cpp000066400000000000000000000564351316324373100202310ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include "defs.h" #include "pytgutils.h" #include "fast_from_py.h" using namespace boost::python; #ifndef TgLibVersNb # define TgLibVersNb 80005 #endif /** * Helper method to Limit the max number of element to send to C++ * * @param[in,out] len the length. if x*y is lower the len, the len is updated to x*y * @param[in] x the x dimension * @param[in] y the y dimension */ static inline void twod2oned(long &len, long x, long y) { if (y <= 0) { if (x < len) { len = x; } } else { long max_elt = x * y; if (max_elt < len) len = max_elt; } } inline static void throw_wrong_python_data_type(const std::string &att_name, const char *method) { TangoSys_OMemStream o; o << "Wrong Python type for attribute " << att_name << ends; Tango::Except::throw_exception( "PyDs_WrongPythonDataTypeForAttribute", o.str(), method); } namespace PyWAttribute { /// @name Min/Max value /// @{ template PyObject* __get_min_value(Tango::WAttribute &att) { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; TangoScalarType tg_val; att.get_min_value(tg_val); boost::python::object py_value(tg_val); return boost::python::incref(py_value.ptr()); } PyObject *get_min_value(Tango::WAttribute &att) { long type = att.get_data_type(); if(type == Tango::DEV_ENCODED) type = Tango::DEV_UCHAR; TANGO_CALL_ON_ATTRIBUTE_DATA_TYPE_ID(type, return __get_min_value, att); return 0; } template PyObject* __get_max_value(Tango::WAttribute &att) { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; TangoScalarType tg_val; att.get_max_value(tg_val); boost::python::object py_value(tg_val); return boost::python::incref(py_value.ptr()); } PyObject *get_max_value(Tango::WAttribute &att) { long type = att.get_data_type(); if(type == Tango::DEV_ENCODED) type = Tango::DEV_UCHAR; TANGO_CALL_ON_ATTRIBUTE_DATA_TYPE_ID(type, return __get_max_value, att); return 0; } #if TgLibVersNb >= 80100 // set_min_value template inline void _set_min_value(Tango::WAttribute &self, boost::python::object value) { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; TangoScalarType c_value = boost::python::extract(value); self.set_min_value(c_value); } #else // set_min_value template inline void __set_min_value(Tango::WAttribute &self, boost::python::object value) { TangoScalarType c_value = boost::python::extract(value); self.set_min_value(c_value); } template<> inline void __set_min_value(Tango::WAttribute &self, boost::python::object value) { string err_msg = "Attribute properties cannot be set with Tango::DevEncoded data type"; Tango::Except::throw_exception((const char *)"API_MethodArgument", (const char *)err_msg.c_str(), (const char *)"WAttribute::set_min_value()"); } template inline void _set_min_value(Tango::WAttribute &self, boost::python::object value) { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; __set_min_value(self,value); } #endif // set_min_value inline void set_min_value(Tango::WAttribute &self, boost::python::object value) { bopy::extract value_convert(value); if (value_convert.check()) { self.set_min_value(value_convert()); } else { long tangoTypeConst = self.get_data_type(); // TODO: the below line is a neat trick to properly raise a Tango exception if a property is set // for one of the forbidden attribute data types; code dependent on Tango C++ implementation if(tangoTypeConst == Tango::DEV_STRING || tangoTypeConst == Tango::DEV_BOOLEAN || tangoTypeConst == Tango::DEV_STATE) tangoTypeConst = Tango::DEV_DOUBLE; else if(tangoTypeConst == Tango::DEV_ENCODED) tangoTypeConst = Tango::DEV_UCHAR; TANGO_CALL_ON_ATTRIBUTE_DATA_TYPE_ID(tangoTypeConst, _set_min_value, self, value); } } #if TgLibVersNb >= 80100 // set_max_value template inline void _set_max_value(Tango::WAttribute &self, boost::python::object value) { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; TangoScalarType c_value = boost::python::extract(value); self.set_max_value(c_value); } #else // set_max_value template inline void __set_max_value(Tango::WAttribute &self, boost::python::object value) { TangoScalarType c_value = boost::python::extract(value); self.set_max_value(c_value); } template<> inline void __set_max_value(Tango::WAttribute &self, boost::python::object value) { string err_msg = "Attribute properties cannot be set with Tango::DevEncoded data type"; Tango::Except::throw_exception((const char *)"API_MethodArgument", (const char *)err_msg.c_str(), (const char *)"WAttribute::set_max_value()"); } template inline void _set_max_value(Tango::WAttribute &self, boost::python::object value) { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; __set_max_value(self,value); } #endif // set_max_value inline void set_max_value(Tango::WAttribute &self, boost::python::object value) { bopy::extract value_convert(value); if (value_convert.check()) { self.set_max_value(value_convert()); } else { long tangoTypeConst = self.get_data_type(); // TODO: the below line is a neat trick to properly raise a Tango exception if a property is set // for one of the forbidden attribute data types; code dependent on Tango C++ implementation if(tangoTypeConst == Tango::DEV_STRING || tangoTypeConst == Tango::DEV_BOOLEAN || tangoTypeConst == Tango::DEV_STATE) tangoTypeConst = Tango::DEV_DOUBLE; else if(tangoTypeConst == Tango::DEV_ENCODED) tangoTypeConst = Tango::DEV_UCHAR; TANGO_CALL_ON_ATTRIBUTE_DATA_TYPE_ID(tangoTypeConst, _set_max_value, self, value); } } /// @} /// @name set_write_value /// @{ template inline void __set_write_value_scalar(Tango::WAttribute &att, boost::python::object &value) { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; /*extract val(value.ptr()); if (!val.check()) { throw_wrong_python_data_type(att.get_name(), "set_write_value()"); } TangoScalarType cpp_value = val; */ TangoScalarType cpp_value; from_py::convert(value.ptr(), cpp_value); att.set_write_value(cpp_value); } template<> inline void __set_write_value_scalar(Tango::WAttribute &att, boost::python::object &value) { Tango::Except::throw_exception( "PyDs_WrongPythonDataTypeForAttribute", "set_write_value is not supported for DEV_ENCODED attributes.", "set_write_value()"); } template inline void __set_write_value_array(Tango::WAttribute &att, boost::python::object &seq, long x_dim, long y_dim) { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; typedef typename TANGO_const2arraytype(tangoTypeConst) TangoArrayType; PyObject *seq_ptr = seq.ptr(); long len = (long) PySequence_Size(seq_ptr); twod2oned(len, x_dim, y_dim); TangoScalarType *tg_ptr = TangoArrayType::allocbuf(len); for (long idx = 0; idx < len; ++idx) { PyObject *elt_ptr = PySequence_GetItem(seq_ptr, idx); // The boost extract could be used: // TangoScalarType val = boost::python::extract(elt_ptr); // instead of the code below. // the problem is that extract is considerably slower than our // convert function which only has to deal with the specific tango // data types try { TangoScalarType tg_scalar; from_py::convert(elt_ptr, tg_scalar); tg_ptr[idx] = tg_scalar; Py_DECREF(elt_ptr); } catch(...) { Py_DECREF(elt_ptr); delete [] tg_ptr; throw; } } try { att.set_write_value(tg_ptr, x_dim, y_dim); delete [] tg_ptr; } catch(...) { delete [] tg_ptr; throw; } } template<> inline void __set_write_value_array(Tango::WAttribute &att, boost::python::object &seq, long x_dim, long y_dim) { PyObject *seq_ptr = seq.ptr(); long len = (long) PySequence_Size(seq_ptr); twod2oned(len, x_dim, y_dim); Tango::DevString* tg_ptr = Tango::DevVarStringArray::allocbuf(len); for (long idx = 0; idx < len; ++idx) { PyObject *elt_ptr = PySequence_GetItem(seq_ptr, idx); // The boost extract could be used: // TangoScalarType val = boost::python::extract(elt_ptr); // instead of the code below. // the problem is that extract is considerably slower than our // convert function which only has to deal with the specific tango // data types try { Tango::DevString tg_scalar; from_py::convert(elt_ptr, tg_scalar); tg_ptr[idx] = Tango::string_dup(tg_scalar); Py_DECREF(elt_ptr); } catch(...) { Py_DECREF(elt_ptr); delete [] tg_ptr; throw; } } try { att.set_write_value(tg_ptr, x_dim, y_dim); // delete [] tg_ptr; } catch(...) { delete [] tg_ptr; throw; } } template<> inline void __set_write_value_array(Tango::WAttribute &att, boost::python::object &seq, long x_dim, long y_dim) { Tango::Except::throw_exception( "PyDs_WrongPythonDataTypeForAttribute", "set_write_value is not supported for DEV_ENCODED attributes.", "set_write_value()"); } inline void set_write_value(Tango::WAttribute &att, boost::python::object &value) { long type = att.get_data_type(); Tango::AttrDataFormat format = att.get_data_format(); if (format == Tango::SCALAR) { TANGO_CALL_ON_ATTRIBUTE_DATA_TYPE_ID(type, __set_write_value_scalar, att, value); } else { if (!PySequence_Check(value.ptr())) { TangoSys_OMemStream o; o << "Wrong Python type for attribute " << att.get_name() << "of type " << Tango::CmdArgTypeName[type] << ". Expected a sequence." << ends; Tango::Except::throw_exception( "PyDs_WrongPythonDataTypeForAttribute", o.str(), "set_value()"); } long size = static_cast(PySequence_Size(value.ptr())); TANGO_CALL_ON_ATTRIBUTE_DATA_TYPE_ID(type, __set_write_value_array, att, value, size, 0); } } inline void set_write_value(Tango::WAttribute &att, boost::python::object &value, long x) { long type = att.get_data_type(); Tango::AttrDataFormat format = att.get_data_format(); if (format == Tango::SCALAR) { TangoSys_OMemStream o; o << "Cannot call set_value(data, dim_x) on scalar attribute " << att.get_name() << ". Use set_write_value(data) instead" << ends; Tango::Except::throw_exception( "PyDs_WrongPythonDataTypeForAttribute", o.str(), "set_write_value()"); } else { if (!PySequence_Check(value.ptr())) { TangoSys_OMemStream o; o << "Wrong Python type for attribute " << att.get_name() << "of type " << Tango::CmdArgTypeName[type] << ". Expected a sequence" << ends; Tango::Except::throw_exception( "PyDs_WrongPythonDataTypeForAttribute", o.str(), "set_write_value()"); } TANGO_CALL_ON_ATTRIBUTE_DATA_TYPE_ID(type, __set_write_value_array, att, value, x, 0); } } inline void set_write_value(Tango::WAttribute &att, boost::python::object &value, long x, long y) { long type = att.get_data_type(); Tango::AttrDataFormat format = att.get_data_format(); if (format == Tango::SCALAR) { TangoSys_OMemStream o; o << "Cannot call set_write_value(data, dim_x, dim_y) " << "on scalar attribute " << att.get_name() << ". Use set_write_value(data) instead" << ends; Tango::Except::throw_exception( (const char *)"PyDs_WrongPythonDataTypeForAttribute", o.str(), (const char *)"set_write_value()"); } else { if (!PySequence_Check(value.ptr())) { TangoSys_OMemStream o; o << "Wrong Python type for attribute " << att.get_name() << "of type " << Tango::CmdArgTypeName[type] << ". Expected a sequence" << ends; Tango::Except::throw_exception( (const char *)"PyDs_WrongPythonDataTypeForAttribute", o.str(), (const char *)"set_write_value()"); } TANGO_CALL_ON_ATTRIBUTE_DATA_TYPE_ID(type, __set_write_value_array, att, value, x, y); } } /// @} /// @name get_write_value /// @{ // // PyTango 3 compatibility // template void __get_write_value_pytango3(Tango::WAttribute &att, boost::python::list &seq) { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; const TangoScalarType *ptr; long length = att.get_write_value_length(); att.get_write_value(ptr); for (long l = 0; l < length; ++l) { seq.append(ptr[l]); } } template<> void __get_write_value_pytango3(Tango::WAttribute &att, boost::python::list &seq) { const Tango::ConstDevString *ptr = NULL; att.get_write_value(ptr); if (ptr == NULL) { return; } long length = att.get_write_value_length(); for (long l = 0; l < length; ++l) { seq.append(ptr[l]); } } inline void get_write_value_pytango3(Tango::WAttribute &att, boost::python::list &value) { long type = att.get_data_type(); TANGO_CALL_ON_ATTRIBUTE_DATA_TYPE_ID(type, __get_write_value_pytango3, att, value); } template void __get_write_value_scalar(Tango::WAttribute &att, boost::python::object* obj) { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; TangoScalarType v; att.get_write_value(v); *obj = boost::python::object(v); } template<> void __get_write_value_scalar(Tango::WAttribute &att, boost::python::object* obj) { Tango::DevString v = NULL; att.get_write_value(v); if(v == NULL) { *obj = boost::python::object(); } else { *obj = boost::python::object((const char*)v); } } template void __get_write_value_array_pytango3(Tango::WAttribute &att, boost::python::object* obj) { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; const TangoScalarType *buffer = NULL; att.get_write_value(buffer); if (buffer == NULL) { *obj = boost::python::object(); return; } size_t length = att.get_write_value_length(); boost::python::list o; for (size_t n = 0; n < length; ++n) { o.append(buffer[n]); } *obj = o; } template<> void __get_write_value_array_pytango3(Tango::WAttribute &att, boost::python::object* obj) { const Tango::ConstDevString *ptr = NULL; if (ptr == NULL) { *obj = boost::python::object(); return; } long length = att.get_write_value_length(); att.get_write_value(ptr); boost::python::list o; for (long l = 0; l < length; ++l) { o.append(ptr[l]); } } template void __get_write_value_array_lists(Tango::WAttribute &att, boost::python::object* obj) { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; const TangoScalarType *buffer = NULL; att.get_write_value(buffer); if (buffer == NULL) { *obj = boost::python::object(); return; } size_t dim_x = att.get_w_dim_x(); size_t dim_y = att.get_w_dim_y(); boost::python::list result; if (att.get_data_format() == Tango::SPECTRUM) { for (size_t x=0; x void __get_write_value_array_lists(Tango::WAttribute &att, boost::python::object* obj) { const Tango::ConstDevString* buffer= NULL; att.get_write_value(buffer); if (buffer == NULL) { *obj = boost::python::object(); return; } size_t dim_x = att.get_w_dim_x(); size_t dim_y = att.get_w_dim_y(); boost::python::list result; if (att.get_data_format() == Tango::SPECTRUM) { for (size_t x=0; x, boost::noncopyable> ("WAttribute", no_init) .def("get_min_value", (PyObject* (*) (Tango::WAttribute &)) &PyWAttribute::get_min_value) .def("get_max_value", (PyObject* (*) (Tango::WAttribute &)) &PyWAttribute::get_max_value) .def("set_min_value", &PyWAttribute::set_min_value) .def("set_max_value", &PyWAttribute::set_max_value) .def("is_min_value", &Tango::WAttribute::is_min_value) .def("is_max_value", &Tango::WAttribute::is_max_value) .def("get_write_value_length", &Tango::WAttribute::get_write_value_length) .def("set_write_value", (void (*) (Tango::WAttribute &, boost::python::object &)) &PyWAttribute::set_write_value) .def("set_write_value", (void (*) (Tango::WAttribute &, boost::python::object &, long)) &PyWAttribute::set_write_value) .def("set_write_value", (void (*) (Tango::WAttribute &, boost::python::object &, long, long)) &PyWAttribute::set_write_value) // old style get_write_value .def("get_write_value", &PyWAttribute::get_write_value_pytango3, ( arg_("self"), arg_("empty_list"))) // new style get_write_value .def("get_write_value", &PyWAttribute::get_write_value, ( arg_("self"), arg_("extract_as")=PyTango::ExtractAsNumpy )) ; } pytango-9.2.2/ext/server/wattribute_numpy.hpp000066400000000000000000000050171316324373100214540ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ // This header file is just some template functions moved apart from // wattribute.cpp, and should only be included there. #pragma once #include "tango_numpy.h" namespace PyWAttribute { template void __get_write_value_array_numpy(Tango::WAttribute &att, boost::python::object* obj) { typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType; const TangoScalarType *buffer; att.get_write_value(buffer); size_t length = att.get_write_value_length(); // Copy buffer in a python raw buffer const char *original_ch_buffer = reinterpret_cast(buffer); PyObject* str_guard = PyBytes_FromStringAndSize(original_ch_buffer, length*sizeof(TangoScalarType)); if (!str_guard) { throw_error_already_set(); } // Create a numpy object based on it... static const int typenum = TANGO_const2numpy(tangoTypeConst); npy_intp dims[2]; int nd = 1; char* ch_buffer = PyBytes_AsString(str_guard); if (att.get_data_format() == Tango::IMAGE) { nd = 2; dims[1] = att.get_w_dim_x(); dims[0] = att.get_w_dim_y(); } else { nd = 1; dims[0] = att.get_w_dim_x(); } PyObject* array = PyArray_SimpleNewFromData(nd, dims, typenum, ch_buffer); if (!array) { Py_XDECREF(str_guard); throw_error_already_set(); } PyArray_BASE(array) = str_guard; *obj = boost::python::object(boost::python::handle<>(array)); } template<> void __get_write_value_array_numpy(Tango::WAttribute &att, boost::python::object* obj) { __get_write_value_array_lists(att, obj); } template<> void __get_write_value_array_numpy(Tango::WAttribute &att, boost::python::object* obj) { __get_write_value_array_lists(att, obj); } } pytango-9.2.2/ext/tango_numpy.h000066400000000000000000000060251316324373100165240ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #pragma once #ifndef DISABLE_PYTANGO_NUMPY #include //#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION // See "Importing the API" for the why of these weird defines before // the inclusion of numpy. They are needed so that you can do import_array // in just one file while using numpy in all the project files. // http://docs.scipy.org/doc/numpy/reference/c-api.array.html#miscellaneous // - { # define PY_ARRAY_UNIQUE_SYMBOL pytango_ARRAY_API # define NO_IMPORT_ARRAY # include // - } #include "tgutils.h" /// @name Conversion from a Tango scalar type name to the numpy equivalent name /// @{ # define TANGO_const2numpy(tangoid) tango_name2numpy::value template struct tango_name2numpy { enum { }; }; # define DEF_TANGO2NUMPY(tangoid, numpyid) \ template<> \ struct tango_name2numpy \ { \ enum {value = numpyid}; \ } DEF_TANGO2NUMPY(Tango::DEV_STATE, NPY_UINT32 ); DEF_TANGO2NUMPY(Tango::DEV_SHORT, NPY_INT16 ); DEF_TANGO2NUMPY(Tango::DEV_LONG, NPY_INT32 ); DEF_TANGO2NUMPY(Tango::DEV_DOUBLE, NPY_FLOAT64 ); DEF_TANGO2NUMPY(Tango::DEV_FLOAT, NPY_FLOAT32 ); DEF_TANGO2NUMPY(Tango::DEV_BOOLEAN, NPY_BOOL ); DEF_TANGO2NUMPY(Tango::DEV_USHORT, NPY_UINT16 ); DEF_TANGO2NUMPY(Tango::DEV_ULONG, NPY_UINT32 ); // Unassigned Tango::DEV_STRING, mapping to NPY_STRING is not copy-free DEF_TANGO2NUMPY(Tango::DEV_UCHAR, NPY_UBYTE ); // DEF_TANGO2NUMPY(Tango::DEV_CHAR, NPY_BYTE ); //Unassigned: Tango::DEV_ENCODED DEF_TANGO2NUMPY(Tango::DEV_LONG64, NPY_INT64 ); DEF_TANGO2NUMPY(Tango::DEV_ULONG64, NPY_UINT64 ); DEF_TANGO2NUMPY(Tango::DEV_ENUM, NPY_INT16 ); /// @name Conversion from a Tango array type name to the scalar numpy name /// For types like DEVVAR_DOUBLEARRAY. This is ended with ARRAY, except /// DEVVAR_LONGSTRINGARRAY, DEVVAR_DOUBLESTRINGARRAY and DEVVAR_STRINGARRAY /// @{ # define TANGO_const2scalarnumpy(tangoid) tango_name2scalarnumpy::value // We can use TANGO_const2scalarconst which gives us the equivalence // except for DEVVAR_CHARARRAY, that does not have any Scalar type // equivalent. template struct tango_name2scalarnumpy { enum {value = TANGO_const2numpy(TANGO_const2scalarconst(N)) }; }; template<> struct tango_name2scalarnumpy { enum {value = NPY_UBYTE }; }; /// @} #endif // #ifndef DISABLE_PYTANGO_NUMPY pytango-9.2.2/ext/tgutils.h000066400000000000000000000426151316324373100156640ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #pragma once #include #include #ifndef TANGO_VERSION_NB #define TANGO_VERSION_NB \ TANGO_VERSION_MAJOR*10000 + \ TANGO_VERSION_MINOR*100 + \ TANGO_VERSION_MINOR #endif namespace Tango { typedef std::vector DbHistoryList; } template struct tango_name2type { }; template struct tango_type2name { enum { }; }; template struct tango_name2arraytype { }; template struct tango_name2arrayname { enum { }; }; template struct tango_name2scalarname { enum { }; }; #define DEF_TANGO_SCALAR_ARRAY_NAMES(scalarname, arrayname) \ template<> \ struct tango_name2arrayname \ { \ enum {value = Tango:: arrayname}; \ }; \ template<> \ struct tango_name2scalarname \ { \ enum {value = Tango:: scalarname}; \ }; #define DEF_TANGO_NAME2TYPE__(tangoname, tangotype) \ template<> \ struct tango_name2type \ { \ typedef tangotype Type; \ }; #define DEF_TANGO_TYPE2NAME__(tangotype, tangoname) \ template<> \ struct tango_type2name \ { \ enum {value = Tango:: tangoname}; \ }; #define DEF_TANGO_NAME2TYPE(tangoname, tangotype) \ DEF_TANGO_NAME2TYPE__(tangoname, tangotype) \ DEF_TANGO_TYPE2NAME__(tangotype, tangoname) #define DEF_TANGO_NAME2ARRAY(tangoname, tangotype, simple) \ template<> \ struct tango_name2arraytype \ { \ typedef tangotype Type; \ typedef simple ElementsType; \ }; #define TSD_SIMPLE__(tangoname, eltangotype, arraytangotype) \ DEF_TANGO_NAME2TYPE(tangoname, eltangotype) \ DEF_TANGO_NAME2ARRAY(tangoname, arraytangotype, eltangotype) #define TSD_ARRAY__(tangoname, eltangotype, arraytangotype) \ DEF_TANGO_NAME2TYPE(tangoname, arraytangotype) \ DEF_TANGO_NAME2ARRAY(tangoname, void, eltangotype) TSD_SIMPLE__( DEV_SHORT, Tango::DevShort , Tango::DevVarShortArray ); TSD_SIMPLE__( DEV_LONG, Tango::DevLong , Tango::DevVarLongArray ); TSD_SIMPLE__( DEV_DOUBLE, Tango::DevDouble , Tango::DevVarDoubleArray ); TSD_SIMPLE__( DEV_STRING, Tango::DevString , Tango::DevVarStringArray ); TSD_SIMPLE__( DEV_FLOAT, Tango::DevFloat , Tango::DevVarFloatArray ); TSD_SIMPLE__( DEV_BOOLEAN, Tango::DevBoolean, Tango::DevVarBooleanArray ); TSD_SIMPLE__( DEV_USHORT, Tango::DevUShort , Tango::DevVarUShortArray ); TSD_SIMPLE__( DEV_ULONG, Tango::DevULong , Tango::DevVarULongArray ); TSD_SIMPLE__( DEV_UCHAR, Tango::DevUChar , Tango::DevVarUCharArray ); TSD_SIMPLE__( DEV_LONG64, Tango::DevLong64 , Tango::DevVarLong64Array ); TSD_SIMPLE__( DEV_ULONG64, Tango::DevULong64, Tango::DevVarULong64Array ); TSD_SIMPLE__( DEV_STATE, Tango::DevState , Tango::DevVarStateArray ); TSD_SIMPLE__( DEV_ENCODED, Tango::DevEncoded, Tango::DevVarEncodedArray ); TSD_SIMPLE__( DEV_PIPE_BLOB, Tango::DevPipeBlob, void ); // since enum type is implemented as a short we cannot use tango_type2name because // it will conflict with the DevShort template declaration DEF_TANGO_NAME2TYPE__( DEV_ENUM, Tango::DevEnum); DEF_TANGO_NAME2ARRAY( DEV_ENUM, Tango::DevVarShortArray, Tango::DevEnum); TSD_SIMPLE__( DEV_VOID, void , void); TSD_ARRAY__( DEVVAR_CHARARRAY, _CORBA_Octet , Tango::DevVarCharArray); TSD_ARRAY__( DEVVAR_SHORTARRAY, Tango::DevShort , Tango::DevVarShortArray); TSD_ARRAY__( DEVVAR_LONGARRAY, Tango::DevLong , Tango::DevVarLongArray); TSD_ARRAY__( DEVVAR_FLOATARRAY, Tango::DevFloat , Tango::DevVarFloatArray); TSD_ARRAY__( DEVVAR_DOUBLEARRAY, Tango::DevDouble , Tango::DevVarDoubleArray); TSD_ARRAY__( DEVVAR_USHORTARRAY, Tango::DevUShort , Tango::DevVarUShortArray); TSD_ARRAY__( DEVVAR_ULONGARRAY, Tango::DevULong , Tango::DevVarULongArray); TSD_ARRAY__( DEVVAR_STRINGARRAY, Tango::DevString , Tango::DevVarStringArray); TSD_ARRAY__( DEVVAR_LONGSTRINGARRAY, void , Tango::DevVarLongStringArray); TSD_ARRAY__( DEVVAR_DOUBLESTRINGARRAY, void , Tango::DevVarDoubleStringArray); TSD_ARRAY__( DEVVAR_BOOLEANARRAY, Tango::DevBoolean, Tango::DevVarBooleanArray); TSD_ARRAY__( DEVVAR_LONG64ARRAY, Tango::DevLong64 , Tango::DevVarLong64Array); TSD_ARRAY__( DEVVAR_ULONG64ARRAY, Tango::DevULong64, Tango::DevVarULong64Array); TSD_ARRAY__( DEVVAR_STATEARRAY, Tango::DevState , Tango::DevVarStateArray); DEF_TANGO_SCALAR_ARRAY_NAMES( DEV_SHORT, DEVVAR_SHORTARRAY ); DEF_TANGO_SCALAR_ARRAY_NAMES( DEV_LONG, DEVVAR_LONGARRAY ); DEF_TANGO_SCALAR_ARRAY_NAMES( DEV_DOUBLE, DEVVAR_DOUBLEARRAY ); DEF_TANGO_SCALAR_ARRAY_NAMES( DEV_STRING, DEVVAR_STRINGARRAY ); DEF_TANGO_SCALAR_ARRAY_NAMES( DEV_FLOAT, DEVVAR_FLOATARRAY ); DEF_TANGO_SCALAR_ARRAY_NAMES( DEV_BOOLEAN, DEVVAR_BOOLEANARRAY ); DEF_TANGO_SCALAR_ARRAY_NAMES( DEV_USHORT, DEVVAR_USHORTARRAY ); DEF_TANGO_SCALAR_ARRAY_NAMES( DEV_ULONG, DEVVAR_ULONGARRAY ); //DEF_TANGO_SCALAR_ARRAY_NAMES( DEV_UCHAR, DEVVAR_CHARARRAY ); DEF_TANGO_SCALAR_ARRAY_NAMES( DEV_LONG64, DEVVAR_LONG64ARRAY ); DEF_TANGO_SCALAR_ARRAY_NAMES( DEV_ULONG64, DEVVAR_ULONG64ARRAY ); DEF_TANGO_SCALAR_ARRAY_NAMES( DEV_STATE, DEVVAR_STATEARRAY ); // DEF_TANGO_SCALAR_ARRAY_NAMES( DEV_ENCODED, DEVVAR_ENCODEDARRAY ); //DEF_TANGO_SCALAR_ARRAY_NAMES( DEV_, DEVVAR_LONGSTRINGARRAY ); //DEF_TANGO_SCALAR_ARRAY_NAMES( DEV_, DEVVAR_DOUBLESTRINGARRAY ); #define TANGO_type2const(type) tango_type2name::value #define TANGO_const2type(name) tango_name2type::Type #define TANGO_const2arraytype(name) tango_name2arraytype::Type #define TANGO_const2arrayelementstype(name) tango_name2arraytype::ElementsType #define TANGO_type2arraytype(type) TANGO_const2arraytype(TANGO_type2const(type)) #define TANGO_const2string(name) (Tango::CmdArgTypeName[name]) #define TANGO_const2arrayconst(scalarconst) tango_name2arrayname::value #define TANGO_const2scalarconst(arrayconst) tango_name2scalarname::value #define TANGO_const2scalartype TANGO_const2arrayelementstype #define __TANGO_DEPEND_ON_TYPE_AUX_ID(typename_, DOIT) \ case Tango:: typename_: { \ static const long tangoTypeConst = Tango:: typename_; \ DOIT; \ break; \ } #define __TANGO_DEPEND_ON_TYPE_AUX_NAME(tid_, DOIT) \ case Tango:: tid_: { \ typedef TANGO_const2type(Tango:: tid_) TangoType; \ DOIT; \ break; \ } #define TANGO_DO_ON_ATTRIBUTE_DATA_TYPE_ID(tid, DOIT) if (true) { \ switch(tid) { \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_SHORT, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_LONG, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_DOUBLE, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_STRING, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_FLOAT, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_BOOLEAN, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_USHORT, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_ULONG, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_UCHAR, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_LONG64, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_ULONG64, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_STATE, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_ENCODED, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_ENUM, DOIT) \ default: \ assert(false); \ } } else (void)0 #define TANGO_DO_ON_ATTRIBUTE_DATA_TYPE_NAME(tid, DOIT) if (true) { \ switch(tid) { \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_SHORT, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_LONG, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_DOUBLE, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_STRING, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_FLOAT, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_BOOLEAN, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_USHORT, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_ULONG, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_UCHAR, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_LONG64, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_ULONG64, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_STATE, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_ENCODED, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_ENUM, DOIT) \ default: \ assert(false); \ } } else (void)0 #define TANGO_CALL_ON_ATTRIBUTE_DATA_TYPE_ID(tid, fn, ...) \ TANGO_DO_ON_ATTRIBUTE_DATA_TYPE_ID(tid, fn(__VA_ARGS__)) #define TANGO_CALL_ON_ATTRIBUTE_DATA_TYPE_NAME(tid, fn, ...) \ TANGO_DO_ON_ATTRIBUTE_DATA_TYPE_NAME(tid, fn(__VA_ARGS__)) #if TANGO_VERSION_NB > 90205 #define __TANGO_DVBA __TANGO_DEPEND_ON_TYPE_AUX_ID(DEVVAR_BOOLEANARRAY, DOIT_ARRAY) #else #define __TANGO_DVBA #endif /// @todo Not sure about who I choosed to comment out from here... #define TANGO_DO_ON_DEVICE_DATA_TYPE_ID(tid, DOIT_SIMPLE, DOIT_ARRAY) if (true) { \ switch(tid) { \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_VOID, DOIT_SIMPLE) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_BOOLEAN, DOIT_SIMPLE) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_SHORT, DOIT_SIMPLE) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_LONG, DOIT_SIMPLE) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_FLOAT, DOIT_SIMPLE) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_DOUBLE, DOIT_SIMPLE) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_USHORT, DOIT_SIMPLE) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_ULONG, DOIT_SIMPLE) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_STRING, DOIT_SIMPLE) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_STATE, DOIT_SIMPLE) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEVVAR_CHARARRAY, DOIT_ARRAY) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEVVAR_SHORTARRAY, DOIT_ARRAY) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEVVAR_LONGARRAY, DOIT_ARRAY) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEVVAR_FLOATARRAY, DOIT_ARRAY) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEVVAR_DOUBLEARRAY, DOIT_ARRAY) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEVVAR_USHORTARRAY, DOIT_ARRAY) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEVVAR_ULONGARRAY, DOIT_ARRAY) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEVVAR_STRINGARRAY, DOIT_ARRAY) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEVVAR_LONGSTRINGARRAY, DOIT_ARRAY) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEVVAR_DOUBLESTRINGARRAY, DOIT_ARRAY) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEVVAR_STATEARRAY, DOIT_ARRAY) \ /* __TANGO_DEPEND_ON_TYPE_AUX_ID(CONST_DEV_STRING, DOIT_SIMPLE) */\ __TANGO_DVBA \ /* __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_UCHAR, DOIT_SIMPLE)*/ \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_LONG64, DOIT_SIMPLE) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_ULONG64, DOIT_SIMPLE) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEVVAR_LONG64ARRAY, DOIT_ARRAY) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEVVAR_ULONG64ARRAY, DOIT_ARRAY) \ /* __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_INT, DOIT_SIMPLE) */\ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_ENCODED, DOIT_SIMPLE) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_PIPE_BLOB, DOIT_SIMPLE) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_ENUM, DOIT_SIMPLE) \ default: \ assert(false); \ } } else (void)0 #define TANGO_DO_ON_DEVICE_ARRAY_DATA_TYPE_ID(tid, DOIT_ARRAY) if (true) { \ switch(tid) { \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEVVAR_CHARARRAY, DOIT_ARRAY) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEVVAR_SHORTARRAY, DOIT_ARRAY) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEVVAR_LONGARRAY, DOIT_ARRAY) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEVVAR_FLOATARRAY, DOIT_ARRAY) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEVVAR_DOUBLEARRAY, DOIT_ARRAY) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEVVAR_USHORTARRAY, DOIT_ARRAY) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEVVAR_ULONGARRAY, DOIT_ARRAY) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEVVAR_STRINGARRAY, DOIT_ARRAY) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEVVAR_LONGSTRINGARRAY, DOIT_ARRAY) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEVVAR_DOUBLESTRINGARRAY, DOIT_ARRAY) \ __TANGO_DVBA \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEVVAR_LONG64ARRAY, DOIT_ARRAY) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEVVAR_ULONG64ARRAY, DOIT_ARRAY) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEVVAR_STATEARRAY, DOIT_ARRAY) \ default: \ assert(false); \ } } else (void)0 #define TANGO_DO_ON_DEVICE_DATA_TYPE_NAME(tid, DOIT_SIMPLE, DOIT_ARRAY) if (true) { \ switch(tid) { \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_VOID, DOIT_SIMPLE) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_BOOLEAN, DOIT_SIMPLE) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_SHORT, DOIT_SIMPLE) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_LONG, DOIT_SIMPLE) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_FLOAT, DOIT_SIMPLE) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_DOUBLE, DOIT_SIMPLE) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_USHORT, DOIT_SIMPLE) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_ULONG, DOIT_SIMPLE) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_STRING, DOIT_SIMPLE) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEVVAR_CHARARRAY, DOIT_ARRAY) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEVVAR_SHORTARRAY, DOIT_ARRAY) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEVVAR_LONGARRAY, DOIT_ARRAY) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEVVAR_FLOATARRAY, DOIT_ARRAY) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEVVAR_DOUBLEARRAY, DOIT_ARRAY) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEVVAR_USHORTARRAY, DOIT_ARRAY) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEVVAR_ULONGARRAY, DOIT_ARRAY) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEVVAR_STRINGARRAY, DOIT_ARRAY) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEVVAR_LONGSTRINGARRAY, DOIT_ARRAY) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEVVAR_DOUBLESTRINGARRAY, DOIT_ARRAY) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_STATE, DOIT_SIMPLE) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEVVAR_STATEARRAY, DOIT_ARRAY) \ /* __TANGO_DEPEND_ON_TYPE_AUX_NAME(CONST_DEV_STRING, DOIT_SIMPLE) */\ __TANGO_DVBA \ /* __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_UCHAR, DOIT_SIMPLE)*/ \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_LONG64, DOIT_SIMPLE) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_ULONG64, DOIT_SIMPLE) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEVVAR_LONG64ARRAY, DOIT_ARRAY) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEVVAR_ULONG64ARRAY, DOIT_ARRAY) \ /* __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_INT, DOIT_SIMPLE) */\ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_ENCODED, DOIT_SIMPLE) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_PIPE_BLOB, DOIT_SIMPLE) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_ENUM, DOIT_SIMPLE) \ default: \ assert(false); \ } } else (void)0 #define TANGO_CALL_ON_DEVICE_DATA_TYPE_ID(tid, fn_simple, fn_array, ...) \ TANGO_DO_ON_DEVICE_DATA_TYPE_ID(tid, fn_simple(__VA_ARGS__), fn_array(__VA_ARGS__)) #define TANGO_CALL_ON_DEVICE_DATA_TYPE_NAME(tid, fn_simple, fn_array, ...) \ TANGO_DO_ON_DEVICE_DATA_TYPE_NAME(tid, fn_simple(__VA_ARGS__), fn_array(__VA_ARGS__)) #define TANGO_DO_ON_NUMERICAL_ATTRIBUTE_DATA_TYPE_ID(tid, DOIT) if (true) { \ switch(tid) { \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_SHORT, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_LONG, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_DOUBLE, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_FLOAT, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_USHORT, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_ULONG, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_UCHAR, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_LONG64, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_ULONG64, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_ID(DEV_ENUM, DOIT) \ default: \ assert(false); \ } } else (void)0 #define TANGO_DO_ON_NUMERICAL_ATTRIBUTE_DATA_TYPE_NAME(tid, DOIT) if (true) { \ switch(tid) { \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_SHORT, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_LONG, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_DOUBLE, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_FLOAT, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_USHORT, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_ULONG, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_UCHAR, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_LONG64, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_ULONG64, DOIT) \ __TANGO_DEPEND_ON_TYPE_AUX_NAME(DEV_ENUM, DOIT) \ default: \ assert(false); \ } } else (void)0 #define TANGO_CALL_ON_NUMERICAL_ATTRIBUTE_DATA_TYPE_ID(tid, fn, ...) \ TANGO_DO_ON_NUMERICAL_ATTRIBUTE_DATA_TYPE_ID(tid, fn(__VA_ARGS__)) #define TANGO_CALL_ON_NUMERICAL_ATTRIBUTE_DATA_TYPE_NAME(tid, fn, ...) \ TANGO_DO_ON_NUMERICAL_ATTRIBUTE_DATA_TYPE_NAME(tid, fn(__VA_ARGS__)) pytango-9.2.2/ext/time_val.cpp000066400000000000000000000015571316324373100163240ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include using namespace boost::python; void export_time_val() { class_("TimeVal") .def_readwrite("tv_sec", &Tango::TimeVal::tv_sec) .def_readwrite("tv_usec", &Tango::TimeVal::tv_usec) .def_readwrite("tv_nsec", &Tango::TimeVal::tv_nsec) ; } pytango-9.2.2/ext/to_py.cpp000066400000000000000000000304511316324373100156510ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include "defs.h" #include "to_py.h" #include "pyutils.h" using namespace boost::python; object to_py(const Tango::AttributeAlarm &attr_alarm) { PYTANGO_MOD object py_attr_alarm = pytango.attr("AttributeAlarm")(); py_attr_alarm.attr("min_alarm") = str(attr_alarm.min_alarm.in()); py_attr_alarm.attr("max_alarm") = str(attr_alarm.max_alarm.in()); py_attr_alarm.attr("min_warning") = str(attr_alarm.min_warning.in()); py_attr_alarm.attr("max_warning") = str(attr_alarm.max_warning.in()); py_attr_alarm.attr("delta_t") = str(attr_alarm.delta_t.in()); py_attr_alarm.attr("delta_val") = str(attr_alarm.delta_val.in()); py_attr_alarm.attr("extensions") = CORBA_sequence_to_list::to_list(attr_alarm.extensions); return py_attr_alarm; } object to_py(const Tango::ChangeEventProp &change_event_prop) { PYTANGO_MOD object py_change_event_prop = pytango.attr("ChangeEventProp")(); py_change_event_prop.attr("rel_change") = str(change_event_prop.rel_change.in()); py_change_event_prop.attr("abs_change") = str(change_event_prop.abs_change.in()); py_change_event_prop.attr("extensions") = CORBA_sequence_to_list::to_list(change_event_prop.extensions); return py_change_event_prop; } object to_py(const Tango::PeriodicEventProp &periodic_event_prop) { PYTANGO_MOD object py_periodic_event_prop = pytango.attr("PeriodicEventProp")(); py_periodic_event_prop.attr("period") = str(periodic_event_prop.period.in()); py_periodic_event_prop.attr("extensions") = CORBA_sequence_to_list::to_list(periodic_event_prop.extensions); return py_periodic_event_prop; } object to_py(const Tango::ArchiveEventProp &archive_event_prop) { PYTANGO_MOD object py_archive_event_prop = pytango.attr("ArchiveEventProp")(); py_archive_event_prop.attr("rel_change") = str(archive_event_prop.rel_change.in()); py_archive_event_prop.attr("abs_change") = str(archive_event_prop.abs_change.in()); py_archive_event_prop.attr("period") = str(archive_event_prop.period.in()); py_archive_event_prop.attr("extensions") = CORBA_sequence_to_list::to_list(archive_event_prop.extensions); return py_archive_event_prop; } object to_py(const Tango::EventProperties &event_props) { PYTANGO_MOD object py_event_props = pytango.attr("EventProperties")(); py_event_props.attr("ch_event") = to_py(event_props.ch_event); py_event_props.attr("per_event") = to_py(event_props.per_event); py_event_props.attr("arch_event") = to_py(event_props.arch_event); return py_event_props; } object to_py(const Tango::AttributeConfig &attr_conf, object py_attr_conf) { if(py_attr_conf.ptr() == Py_None) { PYTANGO_MOD py_attr_conf = pytango.attr("AttributeConfig")(); } py_attr_conf.attr("name") = str(attr_conf.name.in()); py_attr_conf.attr("writable") = attr_conf.writable; py_attr_conf.attr("data_format") = attr_conf.data_format; py_attr_conf.attr("data_type") = attr_conf.data_type; py_attr_conf.attr("max_dim_x") = attr_conf.max_dim_x; py_attr_conf.attr("max_dim_y") = attr_conf.max_dim_y; py_attr_conf.attr("description") = str(attr_conf.description.in()); py_attr_conf.attr("label") = str(attr_conf.label.in()); py_attr_conf.attr("unit") = str(attr_conf.unit.in()); py_attr_conf.attr("standard_unit") = str(attr_conf.standard_unit.in()); py_attr_conf.attr("display_unit") = str(attr_conf.display_unit.in()); py_attr_conf.attr("format") = str(attr_conf.format.in()); py_attr_conf.attr("min_value") = str(attr_conf.min_value.in()); py_attr_conf.attr("max_value") = str(attr_conf.max_value.in()); py_attr_conf.attr("min_alarm") = str(attr_conf.min_alarm.in()); py_attr_conf.attr("max_alarm") = str(attr_conf.max_alarm.in()); py_attr_conf.attr("writable_attr_name") = str(attr_conf.writable_attr_name.in()); py_attr_conf.attr("extensions") = CORBA_sequence_to_list::to_list(attr_conf.extensions); return py_attr_conf; } object to_py(const Tango::AttributeConfig_2 &attr_conf, object py_attr_conf) { if(py_attr_conf.ptr() == Py_None) { PYTANGO_MOD py_attr_conf = pytango.attr("AttributeConfig_2")(); } py_attr_conf.attr("name") = str(attr_conf.name.in()); py_attr_conf.attr("writable") = attr_conf.writable; py_attr_conf.attr("data_format") = attr_conf.data_format; py_attr_conf.attr("data_type") = attr_conf.data_type; py_attr_conf.attr("max_dim_x") = attr_conf.max_dim_x; py_attr_conf.attr("max_dim_y") = attr_conf.max_dim_y; py_attr_conf.attr("description") = str(attr_conf.description.in()); py_attr_conf.attr("label") = str(attr_conf.label.in()); py_attr_conf.attr("unit") = str(attr_conf.unit.in()); py_attr_conf.attr("standard_unit") = str(attr_conf.standard_unit.in()); py_attr_conf.attr("display_unit") = str(attr_conf.display_unit.in()); py_attr_conf.attr("format") = str(attr_conf.format.in()); py_attr_conf.attr("min_value") = str(attr_conf.min_value.in()); py_attr_conf.attr("max_value") = str(attr_conf.max_value.in()); py_attr_conf.attr("min_alarm") = str(attr_conf.min_alarm.in()); py_attr_conf.attr("max_alarm") = str(attr_conf.max_alarm.in()); py_attr_conf.attr("writable_attr_name") = str(attr_conf.writable_attr_name.in()); py_attr_conf.attr("level") = attr_conf.level; py_attr_conf.attr("extensions") = CORBA_sequence_to_list::to_list(attr_conf.extensions); return py_attr_conf; } object to_py(const Tango::AttributeConfig_3 &attr_conf, object py_attr_conf) { if(py_attr_conf.ptr() == Py_None) { PYTANGO_MOD py_attr_conf = pytango.attr("AttributeConfig_3")(); } py_attr_conf.attr("name") = str(attr_conf.name.in()); py_attr_conf.attr("writable") = attr_conf.writable; py_attr_conf.attr("data_format") = attr_conf.data_format; py_attr_conf.attr("data_type") = attr_conf.data_type; py_attr_conf.attr("max_dim_x") = attr_conf.max_dim_x; py_attr_conf.attr("max_dim_y") = attr_conf.max_dim_y; py_attr_conf.attr("description") = str(attr_conf.description.in()); py_attr_conf.attr("label") = str(attr_conf.label.in()); py_attr_conf.attr("unit") = str(attr_conf.unit.in()); py_attr_conf.attr("standard_unit") = str(attr_conf.standard_unit.in()); py_attr_conf.attr("display_unit") = str(attr_conf.display_unit.in()); py_attr_conf.attr("format") = str(attr_conf.format.in()); py_attr_conf.attr("min_value") = str(attr_conf.min_value.in()); py_attr_conf.attr("max_value") = str(attr_conf.max_value.in()); py_attr_conf.attr("writable_attr_name") = str(attr_conf.writable_attr_name.in()); py_attr_conf.attr("level") = attr_conf.level; py_attr_conf.attr("att_alarm") = to_py(attr_conf.att_alarm); py_attr_conf.attr("event_prop") = to_py(attr_conf.event_prop); py_attr_conf.attr("extensions") = CORBA_sequence_to_list::to_list(attr_conf.extensions); py_attr_conf.attr("sys_extensions") = CORBA_sequence_to_list::to_list(attr_conf.sys_extensions); return py_attr_conf; } object to_py(const Tango::AttributeConfig_5 &attr_conf, object py_attr_conf) { if(py_attr_conf.ptr() == Py_None) { PYTANGO_MOD py_attr_conf = pytango.attr("AttributeConfig_5")(); } py_attr_conf.attr("name") = str(attr_conf.name.in()); py_attr_conf.attr("writable") = attr_conf.writable; py_attr_conf.attr("data_format") = attr_conf.data_format; py_attr_conf.attr("data_type") = attr_conf.data_type; py_attr_conf.attr("memorized") = attr_conf.memorized; py_attr_conf.attr("mem_init") = attr_conf.mem_init; py_attr_conf.attr("max_dim_x") = attr_conf.max_dim_x; py_attr_conf.attr("max_dim_y") = attr_conf.max_dim_y; py_attr_conf.attr("description") = str(attr_conf.description.in()); py_attr_conf.attr("label") = str(attr_conf.label.in()); py_attr_conf.attr("unit") = str(attr_conf.unit.in()); py_attr_conf.attr("standard_unit") = str(attr_conf.standard_unit.in()); py_attr_conf.attr("display_unit") = str(attr_conf.display_unit.in()); py_attr_conf.attr("format") = str(attr_conf.format.in()); py_attr_conf.attr("min_value") = str(attr_conf.min_value.in()); py_attr_conf.attr("max_value") = str(attr_conf.max_value.in()); py_attr_conf.attr("writable_attr_name") = str(attr_conf.writable_attr_name.in()); py_attr_conf.attr("level") = attr_conf.level; py_attr_conf.attr("root_attr_name") = str(attr_conf.root_attr_name.in()); py_attr_conf.attr("enum_labels") = CORBA_sequence_to_list::to_list(attr_conf.enum_labels); py_attr_conf.attr("att_alarm") = to_py(attr_conf.att_alarm); py_attr_conf.attr("event_prop") = to_py(attr_conf.event_prop); py_attr_conf.attr("extensions") = CORBA_sequence_to_list::to_list(attr_conf.extensions); py_attr_conf.attr("sys_extensions") = CORBA_sequence_to_list::to_list(attr_conf.sys_extensions); return py_attr_conf; } boost::python::list to_py(const Tango::AttributeConfigList &attr_conf_list) { boost::python::list py_attr_conf_list; boost::python::object none; for(unsigned long index = 0; index < attr_conf_list.length(); ++index) { const Tango::AttributeConfig &attr_conf = attr_conf_list[index]; py_attr_conf_list.append(to_py(attr_conf, none)); } return py_attr_conf_list; } boost::python::list to_py(const Tango::AttributeConfigList_2 &attr_conf_list) { boost::python::list py_attr_conf_list; boost::python::object none; for(unsigned long index = 0; index < attr_conf_list.length(); ++index) { const Tango::AttributeConfig_2 &attr_conf = attr_conf_list[index]; py_attr_conf_list.append(to_py(attr_conf, none)); } return py_attr_conf_list; } boost::python::list to_py(const Tango::AttributeConfigList_3 &attr_conf_list) { boost::python::list py_attr_conf_list; boost::python::object none; for(unsigned long index = 0; index < attr_conf_list.length(); ++index) { const Tango::AttributeConfig_3 &attr_conf = attr_conf_list[index]; py_attr_conf_list.append(to_py(attr_conf, none)); } return py_attr_conf_list; } boost::python::list to_py(const Tango::AttributeConfigList_5 &attr_conf_list) { boost::python::list py_attr_conf_list; boost::python::object none; for(unsigned long index = 0; index < attr_conf_list.length(); ++index) { const Tango::AttributeConfig_5 &attr_conf = attr_conf_list[index]; py_attr_conf_list.append(to_py(attr_conf, none)); } return py_attr_conf_list; } object to_py(const Tango::PipeConfig &pipe_conf, object py_pipe_conf) { if(py_pipe_conf.ptr() == Py_None) { PYTANGO_MOD py_pipe_conf = pytango.attr("PipeConfig")(); } py_pipe_conf.attr("name") = str(pipe_conf.name.in()); py_pipe_conf.attr("description") = str(pipe_conf.description.in()); py_pipe_conf.attr("label") = str(pipe_conf.label.in()); py_pipe_conf.attr("level") = pipe_conf.level; py_pipe_conf.attr("writable") = pipe_conf.writable; py_pipe_conf.attr("extensions") = CORBA_sequence_to_list::to_list(pipe_conf.extensions); return py_pipe_conf; } boost::python::list to_py(const Tango::PipeConfigList &pipe_conf_list) { boost::python::list py_pipe_conf_list; boost::python::object none; for(unsigned long index = 0; index < pipe_conf_list.length(); ++index) { const Tango::PipeConfig &pipe_conf = pipe_conf_list[index]; py_pipe_conf_list.append(to_py(pipe_conf, none)); } return py_pipe_conf_list; } pytango-9.2.2/ext/to_py.h000066400000000000000000000275621316324373100153270ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #pragma once #include #include #include "defs.h" #include "pyutils.h" struct DevEncoded_to_tuple { static inline PyObject* convert(Tango::DevEncoded const& a) { boost::python::str encoded_format(a.encoded_format); bopy::object encoded_data = bopy::object( bopy::handle<>(PyBytes_FromStringAndSize( (const char*)a.encoded_data.get_buffer(), (Py_ssize_t)a.encoded_data.length()))); boost::python::object result = boost::python::make_tuple(encoded_format, encoded_data); return boost::python::incref(result.ptr()); } static const PyTypeObject* get_pytype() { return &PyTuple_Type; } }; template struct to_list { static inline PyObject* convert(ContainerType const& a) { boost::python::list result; typedef typename ContainerType::const_iterator const_iter; for(const_iter it = a.begin(); it != a.end(); it++) { result.append(boost::python::object(*it)); } return boost::python::incref(result.ptr()); } static const PyTypeObject* get_pytype() { return &PyList_Type; } }; template struct to_tuple { static inline PyObject* convert(ContainerType const& a) { typedef typename ContainerType::const_iterator const_iter; PyObject *t = PyTuple_New(a.size()); int32_t i = 0; for(const_iter it = a.begin(); it != a.end(); ++it, ++i) { PyTuple_SetItem(t, i, boost::python::incref(it->ptr())); } return t; } static const PyTypeObject* get_pytype() { return &PyTuple_Type; } }; template struct CORBA_sequence_to_tuple { static PyObject* convert(CorbaContainerType const& a) { unsigned long size = a.length(); PyObject *t = PyTuple_New(size); for(unsigned long i=0; i < size; ++i) { boost::python::object x(a[i]); PyTuple_SetItem(t, i, boost::python::incref(x.ptr())); } return t; } static const PyTypeObject* get_pytype() { return &PyTuple_Type; } }; template<> struct CORBA_sequence_to_tuple { static PyObject* convert(Tango::DevVarStringArray const& a) { unsigned long size = a.length(); PyObject *t = PyTuple_New(size); for(unsigned long i=0; i < size; ++i) { boost::python::str x(a[i].in()); PyTuple_SetItem(t, i, boost::python::incref(x.ptr())); } return t; } static const PyTypeObject* get_pytype() { return &PyTuple_Type; } }; template<> struct CORBA_sequence_to_tuple { static PyObject* convert(Tango::DevVarLongStringArray const& a) { unsigned long lsize = a.lvalue.length(); unsigned long ssize = a.svalue.length(); PyObject *lt = PyTuple_New(lsize); PyObject *st = PyTuple_New(ssize); for(unsigned long i=0; i < lsize; ++i) { boost::python::object x(a.lvalue[i]); PyTuple_SetItem(lt, i, boost::python::incref(x.ptr())); } for(unsigned long i=0; i < ssize; ++i) { boost::python::str x(a.svalue[i].in()); PyTuple_SetItem(st, i, boost::python::incref(x.ptr())); } PyObject *t = PyTuple_New(2); PyTuple_SetItem(t, 0, lt); PyTuple_SetItem(t, 1, st); return t; } static const PyTypeObject* get_pytype() { return &PyTuple_Type; } }; template<> struct CORBA_sequence_to_tuple { static PyObject* convert(Tango::DevVarDoubleStringArray const& a) { unsigned long dsize = a.dvalue.length(); unsigned long ssize = a.svalue.length(); PyObject *dt = PyTuple_New(dsize); PyObject *st = PyTuple_New(ssize); for(unsigned long i=0; i < dsize; ++i) { boost::python::object x(a.dvalue[i]); PyTuple_SetItem(dt, i, boost::python::incref(x.ptr())); } for(unsigned long i=0; i < ssize; ++i) { boost::python::str x(a.svalue[i].in()); PyTuple_SetItem(st, i, boost::python::incref(x.ptr())); } PyObject *t = PyTuple_New(2); PyTuple_SetItem(t, 0, dt); PyTuple_SetItem(t, 1, st); return t; } static const PyTypeObject* get_pytype() { return &PyTuple_Type; } }; template struct CORBA_sequence_to_list { static PyObject* convert(CorbaContainerType const& a) { unsigned long size = a.length(); boost::python::list ret; for(unsigned long i=0; i < size; ++i) { ret.append(a[i]); } return boost::python::incref(ret.ptr()); } static const PyTypeObject* get_pytype() { return &PyList_Type; } }; template<> struct CORBA_sequence_to_list { static boost::python::list to_list(Tango::DevVarStringArray const& a) { unsigned long size = a.length(); boost::python::list ret; for(unsigned long i=0; i < size; ++i) { ret.append(a[i].in()); } return ret; } static PyObject* convert(Tango::DevVarStringArray const& a) { return boost::python::incref(to_list(a).ptr()); } static const PyTypeObject* get_pytype() { return &PyList_Type; } }; template<> struct CORBA_sequence_to_list { static PyObject* convert(Tango::DevVarLongStringArray const& a) { unsigned long lsize = a.lvalue.length(); unsigned long ssize = a.svalue.length(); boost::python::list ret, lt, st; for(unsigned long i=0; i < lsize; ++i) { lt.append(a.lvalue[i]); } for(unsigned long i=0; i < ssize; ++i) { st.append(a.svalue[i]); } ret.append(lt); ret.append(st); return boost::python::incref(ret.ptr()); } static const PyTypeObject* get_pytype() { return &PyList_Type; } }; template<> struct CORBA_sequence_to_list { static PyObject* convert(Tango::DevVarDoubleStringArray const& a) { unsigned long dsize = a.dvalue.length(); unsigned long ssize = a.svalue.length(); boost::python::list ret, dt, st; for(unsigned long i=0; i < dsize; ++i) { dt.append(a.dvalue[i]); } for(unsigned long i=0; i < ssize; ++i) { st.append(a.svalue[i]); } ret.append(dt); ret.append(st); return boost::python::incref(ret.ptr()); } static const PyTypeObject* get_pytype() { return &PyList_Type; } }; struct CORBA_String_member_to_str { static inline PyObject* convert(CORBA::String_member const& cstr) { return from_char_to_str(cstr.in()); } //static const PyTypeObject* get_pytype() { return &PyBytes_Type; } }; struct CORBA_String_member_to_str2 { static inline PyObject* convert(_CORBA_String_member const& cstr) { return from_char_to_str(cstr.in()); } //static const PyTypeObject* get_pytype() { return &PyBytes_Type; } }; struct CORBA_String_element_to_str { static inline PyObject* convert(_CORBA_String_element const& cstr) { return from_char_to_str(cstr.in()); } //static const PyTypeObject* get_pytype() { return &PyBytes_Type; } }; struct String_to_str { static inline PyObject* convert(std::string const& cstr) { return from_char_to_str(cstr); } //static const PyTypeObject* get_pytype() { return &PyBytes_Type; } }; struct char_ptr_to_str { static inline PyObject* convert(const char *cstr) { return from_char_to_str(cstr); } //static const PyTypeObject* get_pytype() { return &PyBytes_Type; } }; boost::python::object to_py(const Tango::AttributeAlarm &); boost::python::object to_py(const Tango::ChangeEventProp &); boost::python::object to_py(const Tango::PeriodicEventProp &); boost::python::object to_py(const Tango::ArchiveEventProp &); boost::python::object to_py(const Tango::EventProperties &); template void to_py(Tango::MultiAttrProp &multi_attr_prop, boost::python::object &py_multi_attr_prop) { if(py_multi_attr_prop.ptr() == Py_None) { PYTANGO_MOD py_multi_attr_prop = pytango.attr("MultiAttrProp")(); } py_multi_attr_prop.attr("label") = multi_attr_prop.label; py_multi_attr_prop.attr("description") = multi_attr_prop.description; py_multi_attr_prop.attr("unit") = multi_attr_prop.unit; py_multi_attr_prop.attr("standard_unit") = multi_attr_prop.standard_unit; py_multi_attr_prop.attr("display_unit") = multi_attr_prop.display_unit; py_multi_attr_prop.attr("format") = multi_attr_prop.format; py_multi_attr_prop.attr("min_value") = multi_attr_prop.min_value.get_str(); py_multi_attr_prop.attr("max_value") = multi_attr_prop.max_value.get_str(); py_multi_attr_prop.attr("min_alarm") = multi_attr_prop.min_alarm.get_str(); py_multi_attr_prop.attr("max_alarm") = multi_attr_prop.max_alarm.get_str(); py_multi_attr_prop.attr("min_warning") = multi_attr_prop.min_warning.get_str(); py_multi_attr_prop.attr("max_warning") = multi_attr_prop.max_warning.get_str(); py_multi_attr_prop.attr("delta_t") = multi_attr_prop.delta_t.get_str(); py_multi_attr_prop.attr("delta_val") = multi_attr_prop.delta_val.get_str(); py_multi_attr_prop.attr("event_period") = multi_attr_prop.event_period.get_str(); py_multi_attr_prop.attr("archive_period") = multi_attr_prop.archive_period.get_str(); py_multi_attr_prop.attr("rel_change") = multi_attr_prop.rel_change.get_str(); py_multi_attr_prop.attr("abs_change") = multi_attr_prop.abs_change.get_str(); py_multi_attr_prop.attr("archive_rel_change") = multi_attr_prop.archive_rel_change.get_str(); py_multi_attr_prop.attr("archive_abs_change") = multi_attr_prop.archive_abs_change.get_str(); } boost::python::object to_py(const Tango::AttributeConfig &, boost::python::object py_attr_conf); boost::python::object to_py(const Tango::AttributeConfig_2 &, boost::python::object py_attr_conf); boost::python::object to_py(const Tango::AttributeConfig_3 &, boost::python::object py_attr_conf); boost::python::object to_py(const Tango::AttributeConfig_5 &, boost::python::object py_attr_conf); boost::python::list to_py(const Tango::AttributeConfigList &); boost::python::list to_py(const Tango::AttributeConfigList_2 &); boost::python::list to_py(const Tango::AttributeConfigList_3 &); boost::python::list to_py(const Tango::AttributeConfigList_5 &); boost::python::object to_py(const Tango::PipeConfig &, boost::python::object); boost::python::object to_py(const Tango::PipeConfigList &, boost::python::object); template inline boost::python::object to_py_list(const T *seq) { using namespace boost::python; return object(handle<>(CORBA_sequence_to_list::convert(*seq))); } template inline boost::python::object to_py_tuple(const T *seq) { using namespace boost::python; return object(handle<>(CORBA_sequence_to_tuple::convert(*seq))); } pytango-9.2.2/ext/to_py_numpy.hpp000066400000000000000000000124071316324373100171070ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #pragma once /// @name Array extraction /// @{ template inline boost::python::object to_py_numpy(const typename TANGO_const2type(tangoArrayTypeConst)* tg_array, boost::python::object parent) { static const int typenum = TANGO_const2scalarnumpy(tangoArrayTypeConst); if (tg_array == 0) { // Empty PyObject* value = PyArray_SimpleNew(0, 0, typenum); if (!value) boost::python::throw_error_already_set(); return boost::python::object(boost::python::handle<>(value)); } // Create a new numpy.ndarray() object. It uses ch_ptr as the data, // so no costy memory copies when handling big images. const void *ch_ptr = reinterpret_cast(tg_array->get_buffer()); int nd = 1; npy_intp dims[1]; dims[0]= tg_array->length(); PyObject* py_array = PyArray_SimpleNewFromData(nd, dims, typenum, const_cast(ch_ptr)); if (!py_array) { boost::python::throw_error_already_set(); } // numpy.ndarray() does not own it's memory, so we need to manage it. // We can assign a 'parent' object that will be informed (decref'd) // when the last copy of numpy.ndarray() disappears. That should // actually destroy the memory in its destructor PyObject* guard = parent.ptr(); Py_INCREF(guard); PyArray_BASE(py_array) = guard; return boost::python::object(boost::python::handle<>(py_array)); } template <> inline boost::python::object to_py_numpy(const Tango::DevVarStringArray* tg_array, boost::python::object parent) { return to_py_list(tg_array); } template <> inline boost::python::object to_py_numpy(const Tango::DevVarStateArray* tg_array, boost::python::object parent) { return to_py_list(tg_array); } template <> inline boost::python::object to_py_numpy(const Tango::DevVarLongStringArray* tg_array, boost::python::object parent) { boost::python::list result; result.append(to_py_numpy(&tg_array->lvalue, parent)); result.append(to_py_numpy(&tg_array->svalue, parent)); return result; } template <> inline boost::python::object to_py_numpy(const Tango::DevVarDoubleStringArray* tg_array, boost::python::object parent) { boost::python::list result; result.append(to_py_numpy(&tg_array->dvalue, parent)); result.append(to_py_numpy(&tg_array->svalue, parent)); return result; } /// @} // ~Array Extraction // ----------------------------------------------------------------------- template inline boost::python::object to_py_numpy(typename TANGO_const2type(tangoArrayTypeConst)* tg_array, int orphan) { static const int typenum = TANGO_const2scalarnumpy(tangoArrayTypeConst); if (tg_array == 0) { // Empty PyObject* value = PyArray_SimpleNew(0, 0, typenum); if (!value) boost::python::throw_error_already_set(); return boost::python::object(boost::python::handle<>(value)); } // Create a new numpy.ndarray() object. It uses ch_ptr as the data, // so no costy memory copies when handling big images. int nd = 1; npy_intp dims[1]; dims[0]= tg_array->length(); void *ch_ptr = (void *)(tg_array->get_buffer(orphan)); PyObject* py_array = PyArray_New(&PyArray_Type, nd, dims, typenum, NULL, ch_ptr, -1, 0, NULL); if (!py_array) { boost::python::throw_error_already_set(); } return boost::python::object(boost::python::handle<>(py_array)); } template <> inline boost::python::object to_py_numpy(Tango::DevVarStringArray* tg_array, int orphan) { return to_py_list(tg_array); } template <> inline boost::python::object to_py_numpy(Tango::DevVarStateArray* tg_array, int orphan) { return to_py_list(tg_array); } template <> inline boost::python::object to_py_numpy(Tango::DevVarLongStringArray* tg_array, int orphan) { boost::python::list result; result.append(to_py_numpy(&tg_array->lvalue, orphan)); result.append(to_py_numpy(&tg_array->svalue, orphan)); return result; } template <> inline boost::python::object to_py_numpy(Tango::DevVarDoubleStringArray* tg_array, int orphan) { boost::python::list result; result.append(to_py_numpy(&tg_array->dvalue, orphan)); result.append(to_py_numpy(&tg_array->svalue, orphan)); return result; } pytango-9.2.2/ext/version.cpp000066400000000000000000000013151316324373100162010ustar00rootroot00000000000000/****************************************************************************** This file is part of PyTango (http://pytango.rtfd.io) Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France Distributed under the terms of the GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version. See LICENSE.txt for more info. ******************************************************************************/ #include "precompiled_header.hpp" #include using namespace boost::python; void export_version() { scope().attr("__tangolib_version__") = Tango::TgLibVers; } pytango-9.2.2/readthedocs.yml000066400000000000000000000000441316324373100162160ustar00rootroot00000000000000conda: file: doc/environment.ymlpytango-9.2.2/setup.cfg000066400000000000000000000004001316324373100150230ustar00rootroot00000000000000[bdist_msi] skip_build=True [bdist_wininst] skip_build=True title=PyTango 9 bitmap=doc\logo-medium.bmp [tool:pytest] addopts = -v --boxed tests [aliases] test=pytest [flake8] ignore = E501, E731, E402 exclude = databaseds, examples, doc, .eggs, build pytango-9.2.2/setup.py000066400000000000000000000407761316324373100147400ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ import os import imp import sys import struct import platform import subprocess from setuptools import setup, Extension from setuptools import Command from setuptools.command.build_ext import build_ext as dftbuild_ext from setuptools.command.install import install as dftinstall from distutils.command.build import build as dftbuild from distutils.unixccompiler import UnixCCompiler from distutils.version import LooseVersion as V # Sphinx imports try: import sphinx import sphinx.util.console sphinx.util.console.color_terminal = lambda: False from sphinx.setup_command import BuildDoc except ImportError: sphinx = None # Detect numpy try: import numpy except ImportError: numpy = None # Platform constants POSIX = 'posix' in os.name WINDOWS = 'nt' in os.name IS64 = 8 * struct.calcsize("P") == 64 PYTHON_VERSION = platform.python_version_tuple() PYTHON2 = ('2',) <= PYTHON_VERSION < ('3',) PYTHON3 = ('3',) <= PYTHON_VERSION < ('4',) # Linux distribution distribution = platform.linux_distribution()[0].lower() if POSIX else "" distribution_match = lambda names: any(x in distribution for x in names) DEBIAN = distribution_match(['debian', 'ubuntu', 'mint']) REDHAT = distribution_match(['redhat', 'fedora', 'centos']) GENTOO = distribution_match(['gentoo']) # Arguments TESTING = any(x in sys.argv for x in ['test', 'pytest']) def get_readme(name='README.rst'): """Get readme file contents without the badges.""" with open(name) as f: return '\n'.join( line for line in f.read().splitlines() if not line.startswith('|') or not line.endswith('|')) def pkg_config(*packages, **config): config_map = { "-I": "include_dirs", "-L": "library_dirs", "-l": "libraries", } cmd = ["pkg-config", "--cflags-only-I", "--libs-only-L", "--libs-only-l", " ".join(packages)] proc = subprocess.Popen(cmd, stdout=subprocess.PIPE) result = proc.wait() result = str(proc.communicate()[0].decode("utf-8")) for elem in result.split(): flag, value = elem[:2], elem[2:] config_values = config.setdefault(config_map.get(flag), []) if value not in config_values: config_values.append(value) return config def abspath(*path): """A method to determine absolute path for a given relative path to the directory where this setup.py script is located""" setup_dir = os.path.dirname(os.path.abspath(__file__)) return os.path.join(setup_dir, *path) def get_release_info(): name = "release" release_dir = abspath('tango') data = imp.find_module(name, [release_dir]) release = imp.load_module(name, *data) return release.Release def uniquify(seq): no_dups = [] for elem in seq: if elem not in no_dups: no_dups.append(elem) return no_dups def get_c_numpy(): if numpy is None: return else: get_include = getattr(numpy, "get_include", None) if get_include is None: get_include = getattr(numpy, "get_numpy_include", None) if get_include is None: return inc = get_include() if os.path.isdir(inc): return inc def has_c_numpy(): return get_c_numpy() is not None def has_numpy(with_src=True): ret = numpy is not None if with_src: ret &= has_c_numpy() return ret def add_lib(name, dirs, sys_libs, env_name=None, lib_name=None, inc_suffix=None): if env_name is None: env_name = name.upper() + '_ROOT' ENV = os.environ.get(env_name) if lib_name is None: lib_name = name if ENV is None: sys_libs.append(lib_name) return else: inc_dir = os.path.join(ENV, 'include') dirs['include_dirs'].append(inc_dir) if inc_suffix is not None: inc_dir = os.path.join(inc_dir, inc_suffix) dirs['include_dirs'].append(inc_dir) lib_dirs = [os.path.join(ENV, 'lib')] if IS64: lib64_dir = os.path.join(ENV, 'lib64') if os.path.isdir(lib64_dir): lib_dirs.insert(0, lib64_dir) dirs['library_dirs'].extend(lib_dirs) if lib_name.startswith('lib'): lib_name = lib_name[3:] dirs['libraries'].append(lib_name) class build(dftbuild): user_options = list(dftbuild.user_options) # Strip library option user_options.append(( 'strip-lib', None, "strips the shared library of debugging symbols" " (Unix like systems only)")) # No documentation option user_options.append(( 'no-doc', None, "do not build documentation")) boolean_options = dftbuild.boolean_options + ['strip-lib', 'no-doc'] def initialize_options(self): dftbuild.initialize_options(self) self.strip_lib = None self.no_doc = None def finalize_options(self): dftbuild.finalize_options(self) def run(self): if numpy is None: self.warn('NOT using numpy: it is not available') elif get_c_numpy() is None: self.warn("NOT using numpy: numpy available but C source is not") dftbuild.run(self) if self.strip_lib: self.strip_debug_symbols() def strip_debug_symbols(self): if not POSIX: return if os.system("type objcopy") != 0: return d = abspath(self.build_lib, "tango") orig_dir = os.path.abspath(os.curdir) so = "_tango.so" dbg = so + ".dbg" try: os.chdir(d) stripped_cmd = 'file %s | grep -q "not stripped" || exit 1' % so not_stripped = os.system(stripped_cmd) == 0 if not_stripped: os.system("objcopy --only-keep-debug %s %s" % (so, dbg)) os.system("objcopy --strip-debug --strip-unneeded %s" % (so,)) os.system("objcopy --add-gnu-debuglink=%s %s" % (dbg, so)) os.system("chmod -x %s" % (dbg,)) finally: os.chdir(orig_dir) def has_doc(self): if self.no_doc: return False if sphinx is None: return False if V(sphinx.__version__) <= V("0.6.5"): print("Documentation will not be generated:" " sphinx version (%s) too low." " Needs 0.6.6" % sphinx.__version__) return False setup_dir = os.path.dirname(os.path.abspath(__file__)) return os.path.isdir(os.path.join(setup_dir, 'doc')) sub_commands = dftbuild.sub_commands + [('build_doc', has_doc), ] class build_ext(dftbuild_ext): def build_extensions(self): self.use_cpp_0x = False if isinstance(self.compiler, UnixCCompiler): compiler_pars = self.compiler.compiler_so while '-Wstrict-prototypes' in compiler_pars: del compiler_pars[compiler_pars.index('-Wstrict-prototypes')] # self.compiler.compiler_so = " ".join(compiler_pars) # mimic tango check to activate C++0x extension compiler = self.compiler.compiler proc = subprocess.Popen( compiler + ["-dumpversion"], stdout=subprocess.PIPE) pipe = proc.stdout proc.wait() gcc_ver = pipe.readlines()[0].decode().strip() if V(gcc_ver) >= V("4.3.3"): self.use_cpp_0x = True dftbuild_ext.build_extensions(self) def build_extension(self, ext): if self.use_cpp_0x: ext.extra_compile_args += ['-std=c++0x'] ext.define_macros += [('PYTANGO_HAS_UNIQUE_PTR', '1')] dftbuild_ext.build_extension(self, ext) if sphinx: class build_doc(BuildDoc): def run(self): # make sure the python path is pointing to the newly built # code so that the documentation is built on this and not a # previously installed version build_cmd = self.get_finalized_command('build') sys.path.insert(0, os.path.abspath(build_cmd.build_lib)) sphinx.setup_command.BuildDoc.run(self) sys.path.pop(0) class install_html(Command): user_options = [] # Install directory option user_options.append(( 'install-dir=', 'd', 'base directory for installing HTML documentation files')) def initialize_options(self): self.install_dir = None def finalize_options(self): self.set_undefined_options( 'install', ('install_html', 'install_dir')) def run(self): build_doc_cmd = self.get_finalized_command('build_doc') src_html_dir = abspath(build_doc_cmd.build_dir, 'html') self.copy_tree(src_html_dir, self.install_dir) class install(dftinstall): user_options = list(dftinstall.user_options) # HTML directory option user_options.append(( 'install-html=', None, "installation directory for HTML documentation")) def initialize_options(self): dftinstall.initialize_options(self) self.install_html = None def finalize_options(self): dftinstall.finalize_options(self) # We do a hack here. We cannot trust the 'install_base' value because # it is not always the final target. For example, in unix, the # install_base is '/usr' and all other install_* are directly relative # to it. However,in unix-local (like ubuntu) install_base is still # '/usr' but, for example, install_data, is '$install_base/local' # which breaks everything. # The hack consists in using install_data instead of install_base # since install_data seems to be, in practice, the proper install_base # on all different systems. if self.install_html is None: self.install_html = os.path.join(self.install_data, 'share', 'doc', 'pytango', 'html') def has_html(self): return sphinx is not None sub_commands = list(dftinstall.sub_commands) sub_commands.append(('install_html', has_html)) def setup_args(): directories = { 'include_dirs': [], 'library_dirs': [], 'libraries': [], } sys_libs = [] # Link specifically to libtango version 9 tangolib = ':libtango.so.9' if POSIX else 'tango' directories['libraries'].append(tangolib) add_lib('omni', directories, sys_libs, lib_name='omniORB4') add_lib('zmq', directories, sys_libs, lib_name='libzmq') add_lib('tango', directories, sys_libs, inc_suffix='tango') # special boost-python configuration BOOST_ROOT = os.environ.get('BOOST_ROOT') boost_library_name = 'boost_python' if BOOST_ROOT is None: if DEBIAN: suffix = "-py{v[0]}{v[1]}".format(v=PYTHON_VERSION) boost_library_name += suffix elif REDHAT: if PYTHON3: boost_library_name += '3' elif GENTOO: suffix = "-{v[0]}.{v[1]}".format(v=PYTHON_VERSION) boost_library_name += suffix else: inc_dir = os.path.join(BOOST_ROOT, 'include') lib_dirs = [os.path.join(BOOST_ROOT, 'lib')] if IS64: lib64_dir = os.path.join(BOOST_ROOT, 'lib64') if os.path.isdir(lib64_dir): lib_dirs.insert(0, lib64_dir) directories['include_dirs'].append(inc_dir) directories['library_dirs'].extend(lib_dirs) directories['libraries'].append(boost_library_name) # special numpy configuration numpy_c_include = get_c_numpy() if numpy_c_include is not None: directories['include_dirs'].append(numpy_c_include) macros = [] if not has_numpy(): macros.append(('DISABLE_PYTANGO_NUMPY', None)) else: macros.append(('PYTANGO_NUMPY_VERSION', '"%s"' % numpy.__version__)) if POSIX: directories = pkg_config(*sys_libs, **directories) Release = get_release_info() author = Release.authors['Coutinho'] please_debug = False packages = [ 'tango', 'tango.databaseds', 'tango.databaseds.db_access', ] py_modules = [ 'PyTango', # Backward compatibilty ] provides = [ 'tango', 'PyTango', # Backward compatibilty ] requires = [ 'boost_python (>=1.33)', 'numpy (>=1.1)', 'six', ] install_requires = [ 'six', ] setup_requires = [] if TESTING: setup_requires += ['pytest-runner'] tests_require = [ 'pytest-xdist', 'gevent', 'psutil', ] if PYTHON2: tests_require += ['trollius'] package_data = { 'PyTango': [], } data_files = [] classifiers = [ 'Development Status :: 5 - Production/Stable', 'Environment :: Other Environment', 'Intended Audience :: Developers', 'License :: OSI Approved ::' ' GNU Library or Lesser General Public License (LGPL)', 'Natural Language :: English', 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX', 'Operating System :: POSIX :: Linux', 'Operating System :: Unix', 'Programming Language :: C', 'Programming Language :: Python', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Topic :: Scientific/Engineering', 'Topic :: Software Development :: Libraries', ] # Note for PyTango developers: # Compilation time can be greatly reduced by compiling the file # src/precompiled_header.hpp as src/precompiled_header.hpp.gch # and then uncommenting this line. Someday maybe this will be # automated... extra_compile_args = [ # '-include ext/precompiled_header.hpp', ] extra_link_args = [ ] if please_debug: extra_compile_args += ['-g', '-O0'] extra_link_args += ['-g', '-O0'] src_dir = abspath('ext') client_dir = src_dir server_dir = os.path.join(src_dir, 'server') clientfiles = sorted( os.path.join(client_dir, fname) for fname in os.listdir(client_dir) if fname.endswith('.cpp')) serverfiles = sorted( os.path.join(server_dir, fname) for fname in os.listdir(server_dir) if fname.endswith('.cpp')) cppfiles = clientfiles + serverfiles directories['include_dirs'].extend([client_dir, server_dir]) include_dirs = uniquify(directories['include_dirs']) library_dirs = uniquify(directories['library_dirs']) libraries = uniquify(directories['libraries']) pytango_ext = Extension( name='_tango', sources=cppfiles, include_dirs=include_dirs, library_dirs=library_dirs, libraries=libraries, define_macros=macros, extra_compile_args=extra_compile_args, extra_link_args=extra_link_args, language='c++', depends=[]) cmdclass = { 'build': build, 'build_ext': build_ext, 'install_html': install_html, 'install': install} if sphinx: cmdclass['build_doc'] = build_doc long_description = get_readme() opts = dict( name='pytango', version=Release.version_long, description=Release.description, long_description=long_description, author=author[0], author_email=author[1], url=Release.url, download_url=Release.download_url, platforms=Release.platform, license=Release.license, packages=packages, py_modules=py_modules, classifiers=classifiers, package_data=package_data, data_files=data_files, provides=provides, keywords=Release.keywords, requires=requires, install_requires=install_requires, setup_requires=setup_requires, tests_require=tests_require, ext_package='tango', ext_modules=[pytango_ext], cmdclass=cmdclass) return opts def main(): return setup(**setup_args()) if __name__ == "__main__": main() pytango-9.2.2/tango/000077500000000000000000000000001316324373100143205ustar00rootroot00000000000000pytango-9.2.2/tango/__init__.py000066400000000000000000000242171316324373100164370ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ """ This is the main PyTango package file. Documentation for this package can be found online: http://pytango.readthedocs.io """ from __future__ import print_function __all__ = [ 'AccessControlType', 'ApiUtil', 'ArchiveEventInfo', 'ArchiveEventProp', 'ArgType', 'AsynCall', 'AsynReplyNotArrived', 'AttReqType', 'Attr', 'AttrConfEventData', 'AttrData', 'AttrDataFormat', 'AttrList', 'AttrProperty', 'AttrQuality', 'AttrReadEvent', 'AttrSerialModel', 'AttrWriteType', 'AttrWrittenEvent', 'Attribute', 'AttributeAlarm', 'AttributeAlarmInfo', 'AttributeConfig', 'AttributeConfig_2', 'AttributeConfig_3', 'AttributeDimension', 'AttributeEventInfo', 'AttributeInfo', 'AttributeInfoEx', 'AttributeInfoList', 'AttributeInfoListEx', 'AttributeList', 'AttributeProxy', 'ChangeEventInfo', 'ChangeEventProp', 'Pipe', 'PipeConfig', 'PipeWriteType', 'PipeEventData', 'DevIntrChangeEventData', 'CmdArgType', 'CmdDoneEvent', 'CommandInfo', 'CommandInfoList', 'CommunicationFailed', 'Connection', 'ConnectionFailed', 'ConstDevString', 'DServer', 'DataReadyEventData', 'Database', 'DbData', 'DbDatum', 'DbDevExportInfo', 'DbDevExportInfos', 'DbDevImportInfo', 'DbDevImportInfos', 'DbDevFullInfo', 'DbDevInfo', 'DbDevInfos', 'DbHistory', 'DbHistoryList', 'DbServerInfo', 'DbServerData', 'DebugIt', 'DevBoolean', 'DevCommandInfo', 'DevDouble', 'DevEncoded', 'DevError', 'DevFailed', 'DevFloat', 'DevInt', 'DevLong', 'DevLong64', 'DevShort', 'DevSource', 'DevState', 'DevString', 'DevUChar', 'DevULong', 'DevULong64', 'DevUShort', 'DevEnum', 'DevVarBooleanArray', 'DevVarCharArray', 'DevVarDoubleArray', 'DevVarDoubleStringArray', 'DevVarFloatArray', 'DevVarLong64Array', 'DevVarLongArray', 'DevVarLongStringArray', 'DevVarShortArray', 'DevVarStringArray', 'DevVarULong64Array', 'DevVarULongArray', 'DevVarUShortArray', 'DevVoid', 'DeviceAttribute', 'DeviceAttributeConfig', 'DeviceAttributeHistory', 'DeviceClass', 'DeviceData', 'DeviceDataList', 'DeviceDataHistory', 'DeviceDataHistoryList', 'DeviceImpl', 'DeviceInfo', 'DeviceProxy', 'DeviceUnlocked', 'Device_2Impl', 'Device_3Impl', 'Device_4Impl', 'Device_5Impl', 'DispLevel', 'EncodedAttribute', 'ErrSeverity', 'ErrorIt', 'EventData', 'EventProperties', 'EventSystemFailed', 'EventType', 'Except', 'ExtractAs', 'FMT_UNKNOWN', 'FatalIt', 'GreenMode', 'Group', 'GroupAttrReply', 'GroupAttrReplyList', 'GroupCmdReply', 'GroupCmdReplyList', 'GroupReply', 'GroupReplyList', 'IMAGE', 'ImageAttr', 'InfoIt', 'KeepAliveCmdCode', 'Level', 'LockCmdCode', 'LockerInfo', 'LockerLanguage', 'LogIt', 'LogLevel', 'LogTarget', 'Logger', 'Logging', 'MessBoxType', 'MultiAttribute', 'MultiAttrProp', 'MultiClassAttribute', 'NamedDevFailed', 'NamedDevFailedList', 'NonDbDevice', 'NonSupportedFeature', 'NotAllowed', 'PeriodicEventInfo', 'PeriodicEventProp', 'PollCmdCode', 'PollDevice', 'PollObjType', 'READ', 'READ_WITH_WRITE', 'READ_WRITE', 'Release', 'SCALAR', 'SPECTRUM', 'SerialModel', 'SpectrumAttr', 'StdDoubleVector', 'StdGroupAttrReplyVector', 'StdGroupCmdReplyVector', 'StdGroupReplyVector', 'StdLongVector', 'StdNamedDevFailedVector', 'StdStringVector', 'SubDevDiag', 'TangoStream', 'TimeVal', 'UserDefaultAttrProp', 'UserDefaultPipeProp', 'Util', 'WAttribute', 'WRITE', 'WarnIt', 'WrongData', 'WrongNameSyntax', '__version__', '__version_description__', '__version_info__', '__version_long__', '__version_number__', 'alarm_flags', 'asyn_req_type', 'cb_sub_model', 'class_factory', 'class_list', 'constants', 'constructed_class', 'cpp_class_list', 'delete_class_list', 'get_class', 'get_classes', 'get_constructed_class', 'get_constructed_classes', 'get_cpp_class', 'get_cpp_classes', 'raise_asynch_exception', 'AutoTangoMonitor', 'AutoTangoAllowThreads', 'LatestDeviceImpl', 'Interceptors', 'get_attribute_proxy', 'requires_tango', 'requires_pytango', 'set_green_mode', 'get_green_mode', 'get_device_proxy', 'is_scalar_type', 'is_array_type', 'is_numerical_type', 'is_int_type', 'is_float_type', 'is_bool_type', 'is_str_type', 'obj_2_str', 'str_2_obj', 'seqStr_2_obj'] __docformat__ = "restructuredtext" # Prepare windows import def __prepare_nt(): import os import sys import struct if os.name != 'nt': return try: from . import _tango # noqa: F401 except ImportError: pass else: return PATH = os.environ.get('PATH') if PATH is None: os.environ["PATH"] = PATH = "" tango_root = os.environ.get("TANGO_ROOT") if tango_root is None: tango_root = os.path.join(os.environ["ProgramFiles"], "tango") tango_root = tango_root.lower() if sys.hexversion < 0x03030000: vc = "vc9_dll" else: vc = "vc10_dll" is64 = 8 * struct.calcsize("P") == 64 if is64: arch = "win64" else: arch = "win32" tango_dll_path = os.path.join(tango_root, arch, "lib", vc) tango_dll_path = tango_dll_path.lower() if os.path.exists(tango_dll_path) and \ tango_dll_path not in PATH.lower(): os.environ['PATH'] += ";" + tango_dll_path else: # Tango C++ could not be found on the system... # ... use PyTango's private Tango C++ library tango_dll_path = os.path.dirname(os.path.abspath(__file__)) tango_dll_path = os.path.join(tango_dll_path, "_tango_dll_") if os.path.exists(tango_dll_path): os.environ['PATH'] += ";" + tango_dll_path __prepare_nt() # Boost imports from ._tango import ( AccessControlType, ApiUtil, ArchiveEventInfo, AsynCall, AsynReplyNotArrived, AttReqType, Attr, AttrConfEventData, AttrDataFormat, AttrList, AttrProperty, AttrQuality, AttrReadEvent, AttrSerialModel, AttrWriteType, AttrWrittenEvent, Attribute, AttributeAlarmInfo, AttributeDimension, AttributeEventInfo, AttributeInfo, AttributeInfoEx, AttributeInfoList, AttributeInfoListEx, AttributeList, ChangeEventInfo, CmdArgType, Pipe, PipeWriteType, DevIntrChangeEventData, CmdDoneEvent, CommandInfo, CommandInfoList, CommunicationFailed, Connection, ConnectionFailed, ConstDevString, DServer, DataReadyEventData, Database, DbData, DbDatum, DbDevExportInfo, DbDevExportInfos, DbDevImportInfo, DbDevImportInfos, DbDevFullInfo, DbDevInfo, DbDevInfos, DbHistory, DbHistoryList, DbServerInfo, DbServerData, DevBoolean, DevCommandInfo, DevDouble, DevEncoded, DevError, DevFailed, DevFloat, DevInt, DevLong, DevLong64, DevShort, DevSource, DevState, DevString, DevUChar, DevULong, DevULong64, DevUShort, DevEnum, DevVarBooleanArray, DevVarCharArray, DevVarDoubleArray, DevVarDoubleStringArray, DevVarFloatArray, DevVarLong64Array, DevVarLongArray, DevVarLongStringArray, DevVarShortArray, DevVarStringArray, DevVarULong64Array, DevVarULongArray, DevVarUShortArray, DevVoid, DeviceAttribute, DeviceAttributeConfig, DeviceAttributeHistory, DeviceData, DeviceDataList, DeviceDataHistory, DeviceDataHistoryList, DeviceImpl, DeviceInfo, DeviceProxy, DeviceUnlocked, Device_2Impl, Device_3Impl, Device_4Impl, Device_5Impl, DispLevel, EncodedAttribute, ErrSeverity, EventData, EventSystemFailed, EventType, PipeEventData, Except, ExtractAs, GreenMode, FMT_UNKNOWN, GroupAttrReply, GroupAttrReplyList, GroupCmdReply, GroupCmdReplyList, GroupReply, GroupReplyList, IMAGE, ImageAttr, KeepAliveCmdCode, Level, LockCmdCode, LockerInfo, LockerLanguage, LogLevel, LogTarget, Logger, Logging, MessBoxType, MultiAttribute, MultiClassAttribute, NamedDevFailed, NamedDevFailedList, NonDbDevice, NonSupportedFeature, NotAllowed, PeriodicEventInfo, PollCmdCode, PollDevice, PollObjType, READ, READ_WITH_WRITE, READ_WRITE, SCALAR, SPECTRUM, SerialModel, SpectrumAttr, StdDoubleVector, StdGroupAttrReplyVector, StdGroupCmdReplyVector, StdGroupReplyVector, StdLongVector, StdNamedDevFailedVector, StdStringVector, SubDevDiag, TimeVal, UserDefaultAttrProp, UserDefaultPipeProp, WAttribute, WRITE, WrongData, WrongNameSyntax, alarm_flags, asyn_req_type, cb_sub_model, constants, raise_asynch_exception, Interceptors, AutoTangoMonitor, AutoTangoAllowThreads) # Aliases ArgType = CmdArgType # Release from .release import Release __author__ = Release.author_lines __version_info__ = Release.version_info __version__ = Release.version __version_long__ = Release.version_long __version_number__ = Release.version_number __version_description__ = Release.version_description __doc__ = Release.long_description # Pytango imports from .attr_data import AttrData from .log4tango import ( TangoStream, LogIt, DebugIt, InfoIt, WarnIt, ErrorIt, FatalIt) from .device_server import ( ChangeEventProp, PeriodicEventProp, ArchiveEventProp, AttributeAlarm, EventProperties, AttributeConfig, AttributeConfig_2, AttributeConfig_3, MultiAttrProp, LatestDeviceImpl) from .pipe import PipeConfig from .attribute_proxy import AttributeProxy, get_attribute_proxy from .group import Group from .pyutil import Util from .device_class import DeviceClass from .globals import ( get_class, get_classes, get_cpp_class, get_cpp_classes, get_constructed_class, get_constructed_classes, class_factory, delete_class_list, class_list, cpp_class_list, constructed_class) from .utils import ( requires_pytango, requires_tango, is_scalar_type, is_array_type, is_numerical_type, is_int_type, is_float_type, is_bool_type, is_str_type, obj_2_str, str_2_obj, seqStr_2_obj) from .green import set_green_mode, get_green_mode from .device_proxy import get_device_proxy # Pytango initialization from .pytango_init import init as __init __init() pytango-9.2.2/tango/api_util.py000066400000000000000000000107161316324373100165050ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ """ This is an internal PyTango module. """ __all__ = ["api_util_init"] __docformat__ = "restructuredtext" from ._tango import ApiUtil from .utils import document_method, document_static_method, _get_env_var def __init_api_util(): if not hasattr(ApiUtil, "get_env_var"): ApiUtil.get_env_var = staticmethod(_get_env_var) def __doc_api_util(): ApiUtil.__doc__ = """ This class allows you to access the tango syncronization model API. It is designed as a singleton. To get a reference to the singleton object you must do:: import tango apiutil = tango.ApiUtil.instance() New in PyTango 7.1.3 """ document_static_method(ApiUtil, "instance", """ instance() -> ApiUtil Returns the ApiUtil singleton instance. Parameters : None Return : (ApiUtil) a reference to the ApiUtil singleton object. New in PyTango 7.1.3 """) document_method(ApiUtil, "pending_asynch_call", """ pending_asynch_call(self, req) -> int Return number of asynchronous pending requests (any device). The input parameter is an enumeration with three values which are: - POLLING: Return only polling model asynchronous request number - CALL_BACK: Return only callback model asynchronous request number - ALL_ASYNCH: Return all asynchronous request number Parameters : - req : (asyn_req_type) asynchronous request type Return : (int) the number of pending requests for the given type New in PyTango 7.1.3 """) document_method(ApiUtil, "get_asynch_replies", """ get_asynch_replies(self) -> None Fire callback methods for all (any device) asynchronous requests (command and attribute) with already arrived replied. Returns immediately if there is no replies already arrived or if there is no asynchronous requests. Parameters : None Return : None Throws : None, all errors are reported using the err and errors fields of the parameter passed to the callback method. New in PyTango 7.1.3 get_asynch_replies(self) -> None Fire callback methods for all (any device) asynchronous requests (command and attributes) with already arrived replied. Wait and block the caller for timeout milliseconds if they are some device asynchronous requests which are not yet arrived. Returns immediately if there is no asynchronous request. If timeout is set to 0, the call waits until all the asynchronous requests sent has received a reply. Parameters : - timeout : (int) timeout (milliseconds) Return : None Throws : AsynReplyNotArrived. All other errors are reported using the err and errors fields of the object passed to the callback methods. New in PyTango 7.1.3 """) document_method(ApiUtil, "set_asynch_cb_sub_model", """ set_asynch_cb_sub_model(self, model) -> None Set the asynchronous callback sub-model between the pull and push sub-model. The cb_sub_model data type is an enumeration with two values which are: - PUSH_CALLBACK: The push sub-model - PULL_CALLBACK: The pull sub-model Parameters : - model : (cb_sub_model) the callback sub-model Return : None New in PyTango 7.1.3 """) document_method(ApiUtil, "get_asynch_cb_sub_model", """ get_asynch_cb_sub_model(self) -> cb_sub_model Get the asynchronous callback sub-model. Parameters : None Return : (cb_sub_model) the active asynchronous callback sub-model. New in PyTango 7.1.3 """) def api_util_init(doc=True): __init_api_util() if doc: __doc_api_util() pytango-9.2.2/tango/asyncio.py000066400000000000000000000120471316324373100163430ustar00rootroot00000000000000# ----------------------------------------------------------------------------- # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ----------------------------------------------------------------------------- """This module exposes a asyncio version of :class:`PyTango.DeviceProxy` and :class:`PyTango.AttributeProxy""" from __future__ import absolute_import from functools import partial from ._tango import GreenMode from .device_proxy import get_device_proxy from .attribute_proxy import get_attribute_proxy __all__ = ["DeviceProxy", "AttributeProxy", "check_requirements"] def check_requirements(): try: import asyncio except ImportError: try: import trollius as asyncio except ImportError: raise ImportError( "Not able to import asyncio or its backport trollius") try: import concurrent.futures except ImportError: raise ImportError( "No module named concurrent. You need to " "install the futures backport module to have " "access to PyTango futures green mode") return asyncio, concurrent.futures check_requirements() DeviceProxy = partial(get_device_proxy, green_mode=GreenMode.Asyncio) DeviceProxy.__doc__ = """ DeviceProxy(self, dev_name, wait=False, timeout=None) -> DeviceProxy DeviceProxy(self, dev_name, need_check_acc, wait=False, timeout=None) -> DeviceProxy Creates a *asyncio* enabled :class:`~PyTango.DeviceProxy`. The DeviceProxy constructor internally makes some network calls which makes it *slow*. By using the asyncio *green mode* you may give the control back to the asyncio event loop using the *yield from* or *await* synthax. .. note:: The timeout parameter has no relation with the tango device client side timeout (gettable by :meth:`~PyTango.DeviceProxy.get_timeout_millis` and settable through :meth:`~PyTango.DeviceProxy.set_timeout_millis`) :param dev_name: the device name or alias :type dev_name: str :param need_check_acc: in first version of the function it defaults to True Determines if at creation time of DeviceProxy it should check for channel access (rarely used) :type need_check_acc: bool :param wait: whether or not to wait for result of creating a DeviceProxy. :type wait: bool :param timeout: The number of seconds to wait for the result. If None, then there is no limit on the wait time. Ignored when wait is False. :type timeout: float :returns: if wait is True: :class:`~PyTango.DeviceProxy` else: :class:`concurrent.futures.Future` :throws: * a *DevFailed* if wait is True and there is an error creating the device. * an *asyncio.TimeoutError* if wait is False, timeout is not None and the time to create the device has expired. New in PyTango 8.1.0 """ AttributeProxy = partial(get_attribute_proxy, green_mode=GreenMode.Asyncio) AttributeProxy.__doc__ = """ AttributeProxy(self, full_attr_name, wait=False, timeout=False) -> AttributeProxy AttributeProxy(self, device_proxy, attr_name, wait=False, timeout=False) -> AttributeProxy Creates a *futures* enabled :class:`~PyTango.AttributeProxy`. The AttributeProxy constructor internally makes some network calls which makes it *slow*. By using the asyncio *green mode* you may give the control back to the asyncio event loop using the *yield from* or *await* synthax. :param full_attr_name: the full name of the attribute :type full_attr_name: str :param device_proxy: the :class:`~PyTango.DeviceProxy` :type device_proxy: DeviceProxy :param attr_name: attribute name for the given device proxy :type attr_name: str :param wait: whether or not to wait for result of creating an AttributeProxy. :type wait: bool :param timeout: The number of seconds to wait for the result. If None, then there is no limit on the wait time. Ignored when wait is False. :type timeout: float :returns: if wait is True: :class:`~PyTango.AttributeProxy` else: :class:`concurrent.futures.Future` :throws: * a *DevFailed* if wait is True and there is an error creating the attribute. * a *asyncio.TimeoutError* if wait is False, timeout is not None and the time to create the attribute has expired. New in PyTango 8.1.0 """ Device = DeviceProxy Attribute = AttributeProxy del GreenMode del get_device_proxy del get_attribute_proxy pytango-9.2.2/tango/asyncio_executor.py000066400000000000000000000057331316324373100202650ustar00rootroot00000000000000# ----------------------------------------------------------------------------- # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ----------------------------------------------------------------------------- # Future imports from __future__ import absolute_import # Imports import functools try: from threading import get_ident except: from threading import _get_ident as get_ident # Asyncio imports try: import asyncio except ImportError: import trollius as asyncio try: from asyncio import run_coroutine_threadsafe except ImportError: from .asyncio_tools import run_coroutine_threadsafe # Tango imports from .green import AbstractExecutor __all__ = ["AsyncioExecutor", "get_global_executor", "set_global_executor"] # Asyncio compatibility ensure_future = getattr(asyncio, 'ensure_future', getattr(asyncio, 'async')) # Global executor _EXECUTOR = None def get_global_executor(): global _EXECUTOR if _EXECUTOR is None: _EXECUTOR = AsyncioExecutor() return _EXECUTOR def set_global_executor(executor): global _EXECUTOR _EXECUTOR = executor # Asyncio executor class AsyncioExecutor(AbstractExecutor): """Asyncio tango executor""" asynchronous = True default_wait = False def __init__(self, loop=None, subexecutor=None): if loop is None: try: loop = asyncio.get_event_loop() except RuntimeError: loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) self.loop = loop self.subexecutor = subexecutor def delegate(self, fn, *args, **kwargs): """Return the given operation as an asyncio future.""" callback = functools.partial(fn, *args, **kwargs) coro = self.loop.run_in_executor(self.subexecutor, callback) return ensure_future(coro) def access(self, accessor, timeout=None): """Return a result from an asyncio future.""" if self.loop.is_running(): raise RuntimeError("Loop is already running") coro = asyncio.wait_for(accessor, timeout, loop=self.loop) return self.loop.run_until_complete(coro) def submit(self, fn, *args, **kwargs): """Submit an operation""" corofn = asyncio.coroutine(lambda: fn(*args, **kwargs)) return run_coroutine_threadsafe(corofn(), self.loop) def execute(self, fn, *args, **kwargs): """Execute an operation and return the result.""" if self.loop._thread_id == get_ident(): corofn = asyncio.coroutine(lambda: fn(*args, **kwargs)) return corofn() future = self.submit(fn, *args, **kwargs) return future.result() pytango-9.2.2/tango/asyncio_tools.py000066400000000000000000000064031316324373100175620ustar00rootroot00000000000000"""Backport some asyncio features.""" from __future__ import absolute_import import concurrent.futures try: import asyncio except ImportError: import trollius as asyncio __all__ = ["run_coroutine_threadsafe"] def _set_concurrent_future_state(concurrent, source): """Copy state from a future to a concurrent.futures.Future.""" assert source.done() if source.cancelled(): concurrent.cancel() if not concurrent.set_running_or_notify_cancel(): return exception = source.exception() if exception is not None: concurrent.set_exception(exception) else: result = source.result() concurrent.set_result(result) def _copy_future_state(source, dest): """Internal helper to copy state from another Future. The other Future may be a concurrent.futures.Future. """ assert source.done() if dest.cancelled(): return assert not dest.done() if source.cancelled(): dest.cancel() else: exception = source.exception() if exception is not None: dest.set_exception(exception) else: result = source.result() dest.set_result(result) def _chain_future(source, dest): """Chain two futures so that when one completes, so does the other. The result (or exception) of source will be copied to destination. If destination is cancelled, source gets cancelled too. Compatible with both asyncio.Future and concurrent.futures.Future. """ if not isinstance(source, (asyncio.Future, concurrent.futures.Future)): raise TypeError('A future is required for source argument') if not isinstance(dest, (asyncio.Future, concurrent.futures.Future)): raise TypeError('A future is required for destination argument') source_loop = source._loop if isinstance(source, asyncio.Future) else None dest_loop = dest._loop if isinstance(dest, asyncio.Future) else None def _set_state(future, other): if isinstance(future, asyncio.Future): _copy_future_state(other, future) else: _set_concurrent_future_state(future, other) def _call_check_cancel(destination): if destination.cancelled(): if source_loop is None or source_loop is dest_loop: source.cancel() else: source_loop.call_soon_threadsafe(source.cancel) def _call_set_state(source): if dest_loop is None or dest_loop is source_loop: _set_state(dest, source) else: dest_loop.call_soon_threadsafe(_set_state, dest, source) dest.add_done_callback(_call_check_cancel) source.add_done_callback(_call_set_state) def run_coroutine_threadsafe(coro, loop): """Submit a coroutine object to a given event loop. Return a concurrent.futures.Future to access the result. """ if not asyncio.iscoroutine(coro): raise TypeError('A coroutine object is required') future = concurrent.futures.Future() def callback(): try: _chain_future(asyncio.async(coro, loop=loop), future) except Exception as exc: if future.set_running_or_notify_cancel(): future.set_exception(exc) raise loop.call_soon_threadsafe(callback) return future pytango-9.2.2/tango/attr_data.py000066400000000000000000000354061316324373100166450ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ """ This is an internal PyTango module. """ from __future__ import with_statement from __future__ import print_function __all__ = ["AttrData"] __docformat__ = "restructuredtext" import inspect from ._tango import Except, CmdArgType, AttrDataFormat, AttrWriteType from ._tango import DispLevel, UserDefaultAttrProp, UserDefaultFwdAttrProp from ._tango import Attr, SpectrumAttr, ImageAttr, FwdAttr from .utils import is_non_str_seq, is_pure_str class AttrData(object): """A helper class that contains the same information one of the items in DeviceClass.attr_list but in object form""" def __init__(self, name, class_name, attr_info=None): self.class_name = class_name self.attr_name = name self.attr_type = CmdArgType.DevVoid self.attr_format = AttrDataFormat.SCALAR self.attr_write = AttrWriteType.READ self.dim_x = 1 self.dim_y = 0 self.display_level = DispLevel.OPERATOR self.polling_period = -1 self.memorized = False self.hw_memorized = False if name is None: self.read_method_name = None self.write_method_name = None self.is_allowed_name = None else: self.read_method_name = "read_" + name self.write_method_name = "write_" + name self.is_allowed_name = "is_" + name + "_allowed" self.attr_class = None self.attr_args = [] self.att_prop = None self.forward = False if attr_info is not None: self.from_attr_info(attr_info) @classmethod def from_dict(cls, attr_dict): attr_dict = dict(attr_dict) name = attr_dict.pop('name', None) class_name = attr_dict.pop('class_name', None) self = cls(name, class_name) self.build_from_dict(attr_dict) return self def build_from_dict(self, attr_dict): self.forward = attr_dict.pop("forwarded", False) if not self.forward: self.attr_type = attr_dict.pop('dtype', CmdArgType.DevDouble) self.attr_format = attr_dict.pop('dformat', AttrDataFormat.SCALAR) self.dim_x = attr_dict.pop('max_dim_x', 1) self.dim_y = attr_dict.pop('max_dim_y', 0) self.display_level = attr_dict.pop('display_level', DispLevel.OPERATOR) self.polling_period = attr_dict.pop('polling_period', -1) self.memorized = attr_dict.pop('memorized', False) self.hw_memorized = attr_dict.pop('hw_memorized', False) is_access_explicit = "access" in attr_dict if is_access_explicit: self.attr_write = attr_dict.pop('access') else: # access is defined by which methods were defined r_explicit = "fread" in attr_dict or "fget" in attr_dict w_explicit = "fwrite" in attr_dict or "fset" in attr_dict if r_explicit and w_explicit: self.attr_write = AttrWriteType.READ_WRITE elif r_explicit: self.attr_write = AttrWriteType.READ elif w_explicit: self.attr_write = AttrWriteType.WRITE else: self.attr_write = AttrWriteType.READ fread = attr_dict.pop('fget', attr_dict.pop('fread', None)) if fread is not None: if is_pure_str(fread): self.read_method_name = fread elif inspect.isroutine(fread): self.read_method_name = fread.__name__ fwrite = attr_dict.pop('fset', attr_dict.pop('fwrite', None)) if fwrite is not None: if is_pure_str(fwrite): self.write_method_name = fwrite elif inspect.isroutine(fwrite): self.write_method_name = fwrite.__name__ fisallowed = attr_dict.pop('fisallowed', None) if fisallowed is not None: if is_pure_str(fisallowed): self.is_allowed_name = fisallowed elif inspect.isroutine(fisallowed): self.is_allowed_name = fisallowed.__name__ self.attr_class = attr_dict.pop("klass", self.DftAttrClassMap[self.attr_format]) self.attr_args.extend((self.attr_name, self.attr_type, self.attr_write)) if not self.attr_format == AttrDataFormat.SCALAR: self.attr_args.append(self.dim_x) if not self.attr_format == AttrDataFormat.SPECTRUM: self.attr_args.append(self.dim_y) else: self.attr_class = FwdAttr self.attr_args = [self.name] if len(attr_dict): if self.forward: self.att_prop = self.__create_user_default_fwdattr_prop(attr_dict) else: self.att_prop = self.__create_user_default_attr_prop(attr_dict) return self def _set_name(self, name): old_name = self.attr_name self.attr_name = name self.attr_args[0] = name if old_name is None: if self.read_method_name is None: self.read_method_name = "read_" + name if self.write_method_name is None: self.write_method_name = "write_" + name if self.is_allowed_name is None: self.is_allowed_name = "is_" + name + "_allowed" def __throw_exception(self, msg, meth="create_attribute()"): Except.throw_exception("PyDs_WrongAttributeDefinition", msg, meth) def __create_user_default_fwdattr_prop(self, extra_info): """for internal usage only""" p = UserDefaultFwdAttrProp() p.set_label(extra_info["label"]) return p def __create_user_default_attr_prop(self, extra_info): """for internal usage only""" p = UserDefaultAttrProp() doc = extra_info.pop('doc', None) if doc is not None: extra_info['description'] = doc for k, v in extra_info.items(): k_lower = k.lower() method_name = "set_%s" % k_lower.replace(' ', '_') if hasattr(p, method_name): method = getattr(p, method_name) if method_name == 'set_enum_labels': method(v) else: method(str(v)) elif k == 'delta_time': p.set_delta_t(str(v)) elif k_lower not in ('display level', 'polling period', 'memorized'): msg = "Wrong definition of attribute. " \ "The object extra information '%s' " \ "is not recognized!" % (k,) Except.throw_exception("PyDs_WrongAttributeDefinition", msg, "create_user_default_attr_prop()") return p def from_attr_info(self, attr_info): name = self.class_name attr_name = self.attr_name throw_ex = self.__throw_exception # check for well defined attribute info # check parameter if not is_non_str_seq(attr_info): throw_ex("Wrong data type for value for describing attribute %s in " "class %s\nMust be a sequence with 1 or 2 elements" % (attr_name, name)) if len(attr_info) < 1 or len(attr_info) > 2: throw_ex("Wrong number of argument for describing attribute %s in " "class %s\nMust be a sequence with 1 or 2 elements" % (attr_name, name)) extra_info = {} if len(attr_info) == 2: # attr_info[1] must be a dictionary # extra_info = attr_info[1], with all the keys lowercase for k, v in attr_info[1].items(): extra_info[k.lower()] = v attr_info = attr_info[0] attr_info_len = len(attr_info) # check parameter if not is_non_str_seq(attr_info) or \ attr_info_len < 3 or attr_info_len > 5: throw_ex("Wrong data type for describing mandatory information for " "attribute %s in class %s\nMust be a sequence with 3, 4 " "or 5 elements" % (attr_name, name)) # get data type try: self.attr_type = CmdArgType(attr_info[0]) except: throw_ex("Wrong data type in attribute argument for attribute %s " "in class %s\nAttribute data type (first element in first " "sequence) must be a tango.CmdArgType" % (attr_name, name)) # get format try: self.attr_format = AttrDataFormat(attr_info[1]) except: throw_ex("Wrong data format in attribute argument for attribute %s " "in class %s\nAttribute data format (second element in " "first sequence) must be a tango.AttrDataFormat" % (attr_name, name)) if self.attr_format == AttrDataFormat.SCALAR: if attr_info_len != 3: throw_ex("Wrong data type in attribute argument for attribute " "%s in class %s\nSequence describing mandatory " "attribute parameters for scalar attribute must have " "3 elements" % (attr_name, name)) elif self.attr_format == AttrDataFormat.SPECTRUM: if attr_info_len != 4: throw_ex("Wrong data type in attribute argument for attribute " "%s in class %s\nSequence describing mandatory " "attribute parameters for spectrum attribute must " "have 4 elements" % (attr_name, name)) try: self.dim_x = int(attr_info[3]) except: throw_ex("Wrong data type in attribute argument for attribute " "%s in class %s\n4th element in sequence describing " "mandatory dim_x attribute parameter for spectrum " "attribute must be an integer" % (attr_name, name)) elif self.attr_format == AttrDataFormat.IMAGE: if attr_info_len != 5: throw_ex("Wrong data type in attribute argument for attribute " "%s in class %s\nSequence describing mandatory " "attribute parameters for image attribute must have " "5 elements" % (attr_name, name)) try: self.dim_x = int(attr_info[3]) except: throw_ex("Wrong data type in attribute argument for attribute " "%s in class %s\n4th element in sequence describing " "mandatory dim_x attribute parameter for image " "attribute must be an integer" % (attr_name, name)) try: self.dim_y = int(attr_info[4]) except: throw_ex("Wrong data type in attribute argument for attribute " "%s in class %s\n5th element in sequence desribing " "mandatory dim_y attribute parameter for image " "attribute must be an integer" % (attr_name, name)) # get write type try: self.attr_write = AttrWriteType(attr_info[2]) except: throw_ex("Wrong data write type in attribute argument for " "attribute %s in class %s\nAttribute write type (third " "element in first sequence) must be a " "tango.AttrWriteType" % (attr_name, name)) try: self.display_level = DispLevel(extra_info.get("display level", DispLevel.OPERATOR)) except: throw_ex("Wrong display level in attribute information for " "attribute %s in class %s\nAttribute information for " "display level is not a tango.DispLevel" % (attr_name, name)) try: self.polling_period = int(extra_info.get("polling period", -1)) except: throw_ex("Wrong polling period in attribute information for " "attribute %s in class %s\nAttribute information for " "polling period is not an integer" % (attr_name, name)) try: memorized = extra_info.get("memorized", "false").lower() except: throw_ex("Wrong memorized value. for attribute %s in class %s." "Allowed valued are the strings \"true\", \"false\" and " "\"true_without_hard_applied\" (case incensitive)") if memorized == "true": self.memorized = True self.hw_memorized = True elif memorized == "true_without_hard_applied": self.memorized = True else: self.memorized = False if self.attr_type == CmdArgType.DevEnum: if 'enum_labels' not in extra_info: throw_ex("Missing 'enum_labels' key in attr_list definition " "for enum attribute %s in class %s" % (attr_name, name)) self.enum_labels = extra_info["enum_labels"] self.attr_class = extra_info.get("klass", self.DftAttrClassMap[self.attr_format]) self.attr_args.extend((self.attr_name, self.attr_type, self.attr_write)) if not self.attr_format == AttrDataFormat.SCALAR: self.attr_args.append(self.dim_x) if not self.attr_format == AttrDataFormat.SPECTRUM: self.attr_args.append(self.dim_y) att_prop = None if extra_info: att_prop = self.__create_user_default_attr_prop(extra_info) self.att_prop = att_prop def to_attr(self): attr = self.attr_class(*self.attr_args) if self.att_prop is not None: attr.set_default_properties(self.att_prop) attr.set_disp_level(self.display_level) if self.memorized: attr.set_memorized() attr.set_memorized_init(self.hw_memorized) if self.polling_period > 0: attr.set_polling_period(self.polling_period) return attr DftAttrClassMap = {AttrDataFormat.SCALAR: Attr, AttrDataFormat.SPECTRUM: SpectrumAttr, AttrDataFormat.IMAGE: ImageAttr} pytango-9.2.2/tango/attribute_proxy.py000066400000000000000000000433501316324373100201430ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ """ This is an internal PyTango module. It completes the binding of :class:`tango.AttributeProxy`. To access these members use directly :mod:`tango` module and NOT tango.attribute_proxy. """ import collections from ._tango import StdStringVector, DbData, DbDatum, DeviceProxy from ._tango import __AttributeProxy as _AttributeProxy from .utils import seq_2_StdStringVector, seq_2_DbData, DbData_2_dict from .utils import is_pure_str, is_non_str_seq from .green import green, get_green_mode from .device_proxy import __init_device_proxy_internals as init_device_proxy __all__ = ["AttributeProxy", "attribute_proxy_init", "get_attribute_proxy"] @green(consume_green_mode=True) def get_attribute_proxy(*args, **kwargs): """ get_attribute_proxy(self, full_attr_name, green_mode=None, wait=True, timeout=True) -> AttributeProxy get_attribute_proxy(self, device_proxy, attr_name, green_mode=None, wait=True, timeout=True) -> AttributeProxy Returns a new :class:`~tango.AttributeProxy`. There is no difference between using this function and the direct :class:`~tango.AttributeProxy` constructor if you use the default kwargs. The added value of this function becomes evident when you choose a green_mode to be *Futures* or *Gevent*. The AttributeProxy constructor internally makes some network calls which makes it *slow*. By using one of the *green modes* as green_mode you are allowing other python code to be executed in a cooperative way. :param full_attr_name: the full name of the attribute :type full_attr_name: str :param device_proxy: the :class:`~tango.DeviceProxy` :type device_proxy: DeviceProxy :param attr_name: attribute name for the given device proxy :type attr_name: str :param green_mode: determines the mode of execution of the device (including the way it is created). Defaults to the current global green_mode (check :func:`~tango.get_green_mode` and :func:`~tango.set_green_mode`) :type green_mode: :obj:`~tango.GreenMode` :param wait: whether or not to wait for result. If green_mode Ignored when green_mode is Synchronous (always waits). :type wait: bool :param timeout: The number of seconds to wait for the result. If None, then there is no limit on the wait time. Ignored when green_mode is Synchronous or wait is False. :type timeout: float :returns: if green_mode is Synchronous or wait is True: :class:`~tango.AttributeProxy` else if green_mode is Futures: :class:`concurrent.futures.Future` else if green_mode is Gevent: :class:`gevent.event.AsynchResult` :throws: * a *DevFailed* if green_mode is Synchronous or wait is True and there is an error creating the attribute. * a *concurrent.futures.TimeoutError* if green_mode is Futures, wait is False, timeout is not None and the time to create the attribute has expired. * a *gevent.timeout.Timeout* if green_mode is Gevent, wait is False, timeout is not None and the time to create the attribute has expired. New in PyTango 8.1.0 """ return AttributeProxy(*args, **kwargs) def __AttributeProxy__get_property(self, propname, value=None): """ get_property(self, propname, value) -> DbData Get a (list) property(ies) for an attribute. This method accepts the following types as propname parameter: 1. string [in] - single property data to be fetched 2. sequence [in] - several property data to be fetched 3. tango.DbDatum [in] - single property data to be fetched 4. tango.DbData [in,out] - several property data to be fetched. 5. sequence - several property data to be feteched Note: for cases 3, 4 and 5 the 'value' parameter if given, is IGNORED. If value is given it must be a tango.DbData that will be filled with the property values Parameters : - propname : (str) property(ies) name(s) - value : (tango.DbData) (optional, default is None meaning that the method will create internally a tango.DbData and return it filled with the property values Return : (DbData) containing the property(ies) value(s). If a tango.DbData is given as parameter, it returns the same object otherwise a new tango.DbData is returned Throws : NonDbDevice, ConnectionFailed (with database), CommunicationFailed (with database), DevFailed from database device """ if is_pure_str(propname) or isinstance(propname, StdStringVector): new_value = value if new_value is None: new_value = DbData() self._get_property(propname, new_value) return DbData_2_dict(new_value) elif isinstance(propname, DbDatum): new_value = DbData() new_value.append(propname) self._get_property(new_value) return DbData_2_dict(new_value) elif isinstance(propname, collections.Sequence): if isinstance(propname, DbData): self._get_property(propname) return DbData_2_dict(propname) if is_pure_str(propname[0]): new_propname = StdStringVector() for i in propname: new_propname.append(i) new_value = value if new_value is None: new_value = DbData() self._get_property(new_propname, new_value) return DbData_2_dict(new_value) elif isinstance(propname[0], DbDatum): new_value = DbData() for i in propname: new_value.append(i) self._get_property(new_value) return DbData_2_dict(new_value) def __AttributeProxy__put_property(self, value): """ put_property(self, value) -> None Insert or update a list of properties for this attribute. This method accepts the following types as value parameter: 1. tango.DbDatum - single property data to be inserted 2. tango.DbData - several property data to be inserted 3. sequence - several property data to be inserted 4. dict - keys are property names and value has data to be inserted 5. dict> - keys are property names and value has data to be inserted 6. dict - keys are property names and str(obj) is property value Parameters : - value : can be one of the following: 1. tango.DbDatum - single property data to be inserted 2. tango.DbData - several property data to be inserted 3. sequence - several property data to be inserted 4. dict - keys are property names and value has data to be inserted 5. dict> - keys are property names and value has data to be inserted 6. dict - keys are property names and str(obj) is property value Return : None Throws : ConnectionFailed, CommunicationFailed DevFailed from device (DB_SQLError) """ if isinstance(value, DbData): pass elif isinstance(value, DbDatum): new_value = DbData() new_value.append(value) value = new_value elif is_non_str_seq(value): new_value = seq_2_DbData(value) elif isinstance(value, collections.Mapping): new_value = DbData() for k, v in value.items(): if isinstance(v, DbDatum): new_value.append(v) continue db_datum = DbDatum(k) if is_non_str_seq(v): seq_2_StdStringVector(v, db_datum.value_string) else: db_datum.value_string.append(str(v)) new_value.append(db_datum) value = new_value else: raise TypeError( 'Value must be a tango.DbDatum, tango.DbData, ' 'a sequence or a dictionary') return self._put_property(value) def __AttributeProxy__delete_property(self, value): """ delete_property(self, value) -> None Delete a the given of properties for this attribute. This method accepts the following types as value parameter: 1. string [in] - single property to be deleted 2. tango.DbDatum [in] - single property data to be deleted 3. tango.DbData [in] - several property data to be deleted 4. sequence [in]- several property data to be deleted 5. sequence [in] - several property data to be deleted 6. dict [in] - keys are property names to be deleted (values are ignored) 7. dict [in] - several DbDatum.name are property names to be deleted (keys are ignored) Parameters : - value : can be one of the following: 1. string [in] - single property data to be deleted 2. tango.DbDatum [in] - single property data to be deleted 3. tango.DbData [in] - several property data to be deleted 4. sequence [in]- several property data to be deleted 5. sequence [in] - several property data to be deleted 6. dict [in] - keys are property names to be deleted (values are ignored) 7. dict [in] - several DbDatum.name are property names to be deleted (keys are ignored) Return : None Throws : ConnectionFailed, CommunicationFailed DevFailed from device (DB_SQLError) """ if isinstance(value, DbData) or isinstance(value, StdStringVector) or \ is_pure_str(value): new_value = value elif isinstance(value, DbDatum): new_value = DbData() new_value.append(value) elif isinstance(value, collections.Sequence): new_value = DbData() for e in value: if isinstance(e, DbDatum): new_value.append(e) else: new_value.append(DbDatum(str(e))) elif isinstance(value, collections.Mapping): new_value = DbData() for k, v in value.items(): if isinstance(v, DbDatum): new_value.append(v) else: new_value.append(DbDatum(k)) else: raise TypeError( 'Value must be a string, tango.DbDatum, ' 'tango.DbData, a sequence or a dictionary') return self._delete_property(new_value) # It is easier to reimplement AttributeProxy in python using DeviceProxy than # wrapping C++ AttributeProxy. However I still rely in the original # AttributeProxy for the constructor (parsing strings if necessary) and some # other things. With the _method_* functions defined later it is really easy. # One reason to do it this way: get_device_proxy() will always return the # same tango.DeviceProxy with this implementation. And then we can trust # it's automatic event unsubscription to handle events. class AttributeProxy(object): """ AttributeProxy is the high level Tango object which provides the client with an easy-to-use interface to TANGO attributes. To create an AttributeProxy, a complete attribute name must be set in the object constructor. Example: att = AttributeProxy("tango/tangotest/1/long_scalar") Note: PyTango implementation of AttributeProxy is in part a python reimplementation of the AttributeProxy found on the C++ API. """ def __init__(self, *args, **kwds): green_mode = kwds.pop('green_mode', get_green_mode()) self.__attr_proxy = _AttributeProxy(*args, **kwds) # get_device_proxy() returns a different python object each time # we don't want a different object, so we save the current one. self.__dev_proxy = dp = self.__attr_proxy.get_device_proxy() init_device_proxy(dp) dp.set_green_mode(green_mode) def get_device_proxy(self): """ get_device_proxy(self) -> DeviceProxy A method which returns the device associated to the attribute Parameters : None Return : (DeviceProxy) """ return self.__dev_proxy def name(self): """ name(self) -> str Returns the attribute name Parameters : None Return : (str) with the attribute name """ return self.__attr_proxy.name() def __str__(self): return "AttributeProxy(%s)" % self.name() def __repr__(self): return "AttributeProxy(%s)" % self.name() def _method_dev_and_name(dp_fn_name, doc=True): def __new_fn(self, *args, **kwds): return getattr(self._AttributeProxy__dev_proxy, dp_fn_name)(self.name(), *args, **kwds) if doc: __new_fn.__doc__ = "This method is a simple way to do:\n" + \ "\tself.get_device_proxy()." + dp_fn_name + \ "(self.name(), ...)\n\n" + \ "For convenience, here is the documentation of DeviceProxy." + \ dp_fn_name + "(...):\n" + \ str(getattr(DeviceProxy, dp_fn_name).__doc__) __new_fn.__name__ = dp_fn_name return __new_fn def _method_device(dp_fn_name, doc=True): def __new_fn(self, *args, **kwds): return getattr(self._AttributeProxy__dev_proxy, dp_fn_name)(*args, **kwds) if doc: __new_fn.__doc__ = "This method is a simple way to do:\n" + \ "\tself.get_device_proxy()." + dp_fn_name + \ "(...)\n\n" + \ "For convenience, here is the documentation of DeviceProxy." + \ dp_fn_name + "(...):\n" + \ str(getattr(DeviceProxy, dp_fn_name).__doc__) __new_fn.__name__ = dp_fn_name return __new_fn def _method_attribute(dp_fn_name, doc=True): def __new_fn(self, *args, **kwds): return getattr(self._AttributeProxy__attr_proxy, dp_fn_name)(*args, **kwds) if doc: __new_fn.__doc__ = getattr(_AttributeProxy, dp_fn_name).__doc__ __new_fn.__name__ = dp_fn_name return __new_fn def __init_AttributeProxy(doc=True): _AttributeProxy.get_property = __AttributeProxy__get_property _AttributeProxy.put_property = __AttributeProxy__put_property _AttributeProxy.delete_property = __AttributeProxy__delete_property # General methods # AttributeProxy.name manually defined AttributeProxy.status = _method_device('status', doc=doc) AttributeProxy.state = _method_device('state', doc=doc) AttributeProxy.ping = _method_device('ping', doc=doc) AttributeProxy.get_transparency_reconnection = _method_device('get_transparency_reconnection', doc=doc) AttributeProxy.set_transparency_reconnection = _method_device('set_transparency_reconnection', doc=doc) # Property methods AttributeProxy.get_property = _method_attribute('get_property', doc=doc) AttributeProxy.put_property = _method_attribute('put_property', doc=doc) AttributeProxy.delete_property = _method_attribute('delete_property', doc=doc) # Attribute methods AttributeProxy.get_config = _method_dev_and_name('get_attribute_config', doc=doc) AttributeProxy.set_config = _method_device('set_attribute_config', doc=doc) AttributeProxy.write = _method_dev_and_name('write_attribute', doc=doc) AttributeProxy.read = _method_dev_and_name('read_attribute', doc=doc) AttributeProxy.write_read = _method_dev_and_name('write_read_attribute', doc=doc) # History methods... AttributeProxy.history = _method_dev_and_name('attribute_history', doc=doc) # Polling administration methods AttributeProxy.poll = _method_dev_and_name('poll_attribute', doc=doc) AttributeProxy.get_poll_period = _method_dev_and_name('get_attribute_poll_period', doc=doc) AttributeProxy.is_polled = _method_dev_and_name('is_attribute_polled', doc=doc) AttributeProxy.stop_poll = _method_dev_and_name('stop_poll_attribute', doc=doc) # Asynchronous methods AttributeProxy.read_asynch = _method_dev_and_name('read_attribute_asynch', doc=doc) AttributeProxy.read_reply = _method_device('read_attribute_reply', doc=doc) AttributeProxy.write_asynch = _method_device('write_attribute_asynch', doc=doc) AttributeProxy.write_reply = _method_device('write_attribute_reply', doc=doc) # Event methods AttributeProxy.subscribe_event = _method_dev_and_name('subscribe_event', doc=doc) AttributeProxy.unsubscribe_event = _method_device('unsubscribe_event', doc=doc) AttributeProxy.get_events = _method_device('get_events', doc=doc) AttributeProxy.event_queue_size = _method_device('event_queue_size', doc=doc) AttributeProxy.get_last_event_date = _method_device('get_last_event_date', doc=doc) AttributeProxy.is_event_queue_empty = _method_device('is_event_queue_empty', doc=doc) def attribute_proxy_init(doc=True): __init_AttributeProxy(doc=doc) pytango-9.2.2/tango/auto_monitor.py000066400000000000000000000040531316324373100174130ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ """ This is an internal PyTango module. """ __all__ = ["auto_monitor_init"] __docformat__ = "restructuredtext" from ._tango import AutoTangoMonitor, AutoTangoAllowThreads def __AutoTangoMonitor__enter__(self): self._acquire() return self def __AutoTangoMonitor__exit__(self, *args, **kwargs): self._release() def __init_AutoTangoMonitor(): AutoTangoMonitor.__enter__ = __AutoTangoMonitor__enter__ AutoTangoMonitor.__exit__ = __AutoTangoMonitor__exit__ def __doc_AutoTangoMonitor(): AutoTangoMonitor.__doc__ = """\ In a tango server, guard the tango monitor within a python context:: with AutoTangoMonitor(dev): # code here is protected by the tango device monitor do something """ def __AutoTangoAllowThreads__enter__(self): return self def __AutoTangoAllowThreads__exit__(self, *args, **kwargs): self._acquire() def __init_AutoTangoAllowThreads(): AutoTangoAllowThreads.__enter__ = __AutoTangoAllowThreads__enter__ AutoTangoAllowThreads.__exit__ = __AutoTangoAllowThreads__exit__ def __doc_AutoTangoAllowThreads(): AutoTangoAllowThreads.__doc__ = """\ In a tango server, free the tango monitor within a context: with AutoTangoAllowThreads(dev): # code here is not under the tango device monitor do something """ def auto_monitor_init(doc=True): __init_AutoTangoMonitor() __init_AutoTangoAllowThreads() if doc: __doc_AutoTangoMonitor() __doc_AutoTangoAllowThreads() pytango-9.2.2/tango/base_types.py000066400000000000000000000704541316324373100170420ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ """ This is an internal PyTango module. """ __all__ = ["base_types_init"] __docformat__ = "restructuredtext" from ._tango import (StdStringVector, StdLongVector, StdDoubleVector, CommandInfoList, AttributeInfoList, AttributeInfoListEx, DbData, DbDevInfos, DbDevExportInfos, DbDevImportInfos, DbHistoryList, DeviceDataHistoryList, StdGroupReplyVector, StdGroupCmdReplyVector, StdGroupAttrReplyVector, ArchiveEventInfo, EventData, AttrConfEventData, AttributeAlarmInfo, AttributeDimension, AttributeEventInfo, DeviceAttributeConfig, AttributeInfo, AttributeInfoEx, ChangeEventInfo, PeriodicEventInfo, DevCommandInfo, CommandInfo, DataReadyEventData, DeviceInfo, LockerInfo, PollDevice, TimeVal, AttrWriteType, AttrDataFormat, DispLevel) from .utils import document_method, is_integer from .utils import document_enum as __document_enum from .utils import seq_2_StdStringVector, StdStringVector_2_seq def __StdVector__add(self, seq): ret = seq.__class__(self) ret.extend(seq) return ret def __StdVector__mul(self, n): ret = self.__class__() for _ in range(n): ret.extend(self) return ret def __StdVector__imul(self, n): ret = self.__class__() for _ in range(n): ret.extend(self) return ret def __StdVector__getitem(self, key): if is_integer(key) or key.step is None: return self.__original_getitem(key) res = self.__class__() nb = len(self) start = key.start or 0 stop = key.stop or nb if start >= nb: return res if stop > nb: stop = nb for i in range(start, stop, key.step or 1): res.append(self[i]) return res def __fillVectorClass(klass): klass.__add__ = __StdVector__add klass.__mul__ = __StdVector__mul klass.__imul__ = __StdVector__imul klass.__original_getitem = klass.__getitem__ klass.__getitem__ = __StdVector__getitem # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ # DeviceAttributeConfig pickle # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ def __DeviceAttributeConfig__getinitargs__(self): return () def __DeviceAttributeConfig__getstate__(self): return ( self.name, int(self.writable), int(self.data_format), self.data_type, self.max_dim_x, self.max_dim_y, self.description, self.label, self.unit, self.standard_unit, self.display_unit, self.format, self.min_value, self.max_value, self.min_alarm, self.max_alarm, self.writable_attr_name, StdStringVector_2_seq(self.extensions)) def __DeviceAttributeConfig__setstate__(self, state): self.name = state[0] self.writable = AttrWriteType(state[1]) self.data_format = AttrDataFormat(state[2]) self.data_type = state[3] self.max_dim_x = state[4] self.max_dim_y = state[5] self.description = state[6] self.label = state[7] self.unit = state[8] self.standard_unit = state[9] self.display_unit = state[10] self.format = state[11] self.min_value = state[12] self.max_value = state[13] self.min_alarm = state[14] self.max_alarm = state[15] self.writable_attr_name = state[16] self.extensions = seq_2_StdStringVector(state[17]) # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ # AttributeInfo pickle # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ def __AttributeInfo__getinitargs__(self): return () def __AttributeInfo__getstate__(self): ret = list(__DeviceAttributeConfig__getstate__(self)) ret.append(int(self.disp_level)) return tuple(ret) def __AttributeInfo__setstate__(self, state): __DeviceAttributeConfig__setstate__(self, state) self.disp_level = DispLevel(state[18]) # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ # AttributeAlarmInfo pickle # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ def __AttributeAlarmInfo__getinitargs__(self): return () def __AttributeAlarmInfo__getstate__(self): return ( self.min_alarm, self.max_alarm, self.min_warning, self.max_warning, self.delta_t, self.delta_val, StdStringVector_2_seq(self.extensions)) def __AttributeAlarmInfo__setstate__(self, state): self.min_alarm = state[0] self.max_alarm = state[1] self.min_warning = state[2] self.max_warning = state[3] self.delta_t = state[4] self.delta_val = state[5] self.extensions = seq_2_StdStringVector(state[6]) # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ # ChangeEventInfo pickle # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ def __ChangeEventInfo__getinitargs__(self): return () def __ChangeEventInfo__getstate__(self): return ( self.rel_change, self.abs_change, StdStringVector_2_seq(self.extensions)) def __ChangeEventInfo__setstate__(self, state): self.rel_change = state[0] self.abs_change = state[1] self.extensions = seq_2_StdStringVector(state[2]) # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ # PeriodicEventInfo pickle # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ def __PeriodicEventInfo__getinitargs__(self): return () def __PeriodicEventInfo__getstate__(self): return ( self.period, StdStringVector_2_seq(self.extensions)) def __PeriodicEventInfo__setstate__(self, state): self.period = state[0] self.extensions = seq_2_StdStringVector(state[1]) # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ # ArchiveEventInfo pickle # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ def __ArchiveEventInfo__getinitargs__(self): return () def __ArchiveEventInfo__getstate__(self): return ( self.archive_rel_change, self.archive_abs_change, self.archive_period, StdStringVector_2_seq(self.extensions)) def __ArchiveEventInfo__setstate__(self, state): self.archive_rel_change = state[0] self.archive_abs_change = state[1] self.archive_period = state[2] self.extensions = seq_2_StdStringVector(state[3]) # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ # AttributeEventInfo pickle # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ def __AttributeEventInfo__getinitargs__(self): return () def __AttributeEventInfo__getstate__(self): return ( self.ch_event, self.per_event, self.arch_event) def __AttributeEventInfo__setstate__(self, state): self.ch_event = state[0] self.per_event = state[1] self.arch_event = state[2] # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ # AttributeInfoEx pickle # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ def __AttributeInfoEx__getinitargs__(self): return () def __AttributeInfoEx__getstate__(self): ret = list(__AttributeInfo__getstate__(self)) ret.append(self.alarms) ret.append(self.events) ret.append(StdStringVector_2_seq(self.sys_extensions)) return tuple(ret) def __AttributeInfoEx__setstate__(self, state): __AttributeInfo__setstate__(self, state) self.alarms = state[19] self.events = state[20] self.sys_extensions = seq_2_StdStringVector(state[21]) def __init_base_types(): v_klasses = ( StdStringVector, StdLongVector, StdDoubleVector, CommandInfoList, AttributeInfoList, AttributeInfoListEx, DbData, DbDevInfos, DbDevExportInfos, DbDevImportInfos, DbHistoryList, DeviceDataHistoryList, StdGroupReplyVector, StdGroupCmdReplyVector, StdGroupAttrReplyVector) for v_klass in v_klasses: __fillVectorClass(v_klass) DeviceAttributeConfig.__getinitargs__ = __DeviceAttributeConfig__getinitargs__ DeviceAttributeConfig.__getstate__ = __DeviceAttributeConfig__getstate__ DeviceAttributeConfig.__setstate__ = __DeviceAttributeConfig__setstate__ AttributeInfo.__getinitargs__ = __AttributeInfo__getinitargs__ AttributeInfo.__getstate__ = __AttributeInfo__getstate__ AttributeInfo.__setstate__ = __AttributeInfo__setstate__ AttributeAlarmInfo.__getinitargs__ = __AttributeAlarmInfo__getinitargs__ AttributeAlarmInfo.__getstate__ = __AttributeAlarmInfo__getstate__ AttributeAlarmInfo.__setstate__ = __AttributeAlarmInfo__setstate__ ChangeEventInfo.__getinitargs__ = __ChangeEventInfo__getinitargs__ ChangeEventInfo.__getstate__ = __ChangeEventInfo__getstate__ ChangeEventInfo.__setstate__ = __ChangeEventInfo__setstate__ PeriodicEventInfo.__getinitargs__ = __PeriodicEventInfo__getinitargs__ PeriodicEventInfo.__getstate__ = __PeriodicEventInfo__getstate__ PeriodicEventInfo.__setstate__ = __PeriodicEventInfo__setstate__ ArchiveEventInfo.__getinitargs__ = __ArchiveEventInfo__getinitargs__ ArchiveEventInfo.__getstate__ = __ArchiveEventInfo__getstate__ ArchiveEventInfo.__setstate__ = __ArchiveEventInfo__setstate__ AttributeEventInfo.__getinitargs__ = __AttributeEventInfo__getinitargs__ AttributeEventInfo.__getstate__ = __AttributeEventInfo__getstate__ AttributeEventInfo.__setstate__ = __AttributeEventInfo__setstate__ AttributeInfoEx.__getinitargs__ = __AttributeInfoEx__getinitargs__ AttributeInfoEx.__getstate__ = __AttributeInfoEx__getstate__ AttributeInfoEx.__setstate__ = __AttributeInfoEx__setstate__ def __doc_base_types(): def document_enum(enum_name, desc): import tango __document_enum(tango, enum_name, desc) document_enum("ExtractAs", """ Defines what will go into value field of DeviceAttribute, or what will Attribute.get_write_value() return... Not all the possible values are valid in all the cases. Valid possible values are: - Numpy : Value will be stored in [value, w_value]. If the attribute is an scalar, they will contain a value. If it's an SPECTRUM or IMAGE it will be exported as a numpy array. - Tuple : Value will be stored in [value, w_value]. If the attribute is an scalar, they will contain a value. If it's an SPECTRUM or IMAGE it will be exported as a tuple or tuple of tuples. - List : Value will be stored in [value, w_value]. If the attribute is an scalar, they will contain a value. If it's an SPECTRUM or IMAGE it will be exported as a list or list of lists - String : The data will be stored 'as is', the binary data as it comes from TangoC++ in 'value'. - Nothing : The value will not be extracted from DeviceAttribute """) document_enum("CmdArgType", """ An enumeration representing the command argument type. - DevVoid - DevBoolean - DevShort - DevLong - DevFloat - DevDouble - DevUShort - DevULong - DevString - DevVarCharArray - DevVarShortArray - DevVarLongArray - DevVarFloatArray - DevVarDoubleArray - DevVarUShortArray - DevVarULongArray - DevVarStringArray - DevVarLongStringArray - DevVarDoubleStringArray - DevState - ConstDevString - DevVarBooleanArray - DevUChar - DevLong64 - DevULong64 - DevVarLong64Array - DevVarULong64Array - DevInt - DevEncoded - DevEnum - DevPipeBlob """) document_enum("LockerLanguage", """ An enumeration representing the programming language in which the client application who locked is written. - CPP : C++/Python language - JAVA : Java language New in PyTango 7.0.0 """) document_enum("MessBoxType", """ An enumeration representing the MessBoxType - STOP - INFO New in PyTango 7.0.0 """) document_enum("PollObjType", """ An enumeration representing the PollObjType - POLL_CMD - POLL_ATTR - EVENT_HEARTBEAT - STORE_SUBDEV New in PyTango 7.0.0 """) document_enum("PollCmdCode", """ An enumeration representing the PollCmdCode - POLL_ADD_OBJ - POLL_REM_OBJ - POLL_START - POLL_STOP - POLL_UPD_PERIOD - POLL_REM_DEV - POLL_EXIT - POLL_REM_EXT_TRIG_OBJ - POLL_ADD_HEARTBEAT - POLL_REM_HEARTBEAT New in PyTango 7.0.0 """) document_enum("SerialModel", """ An enumeration representing the type of serialization performed by the device server - BY_DEVICE - BY_CLASS - BY_PROCESS - NO_SYNC """) document_enum("AttReqType", """ An enumeration representing the type of attribute request - READ_REQ - WRITE_REQ """) document_enum("LockCmdCode", """ An enumeration representing the LockCmdCode - LOCK_ADD_DEV - LOCK_REM_DEV - LOCK_UNLOCK_ALL_EXIT - LOCK_EXIT New in PyTango 7.0.0 """) document_enum("LogLevel", """ An enumeration representing the LogLevel - LOG_OFF - LOG_FATAL - LOG_ERROR - LOG_WARN - LOG_INFO - LOG_DEBUG New in PyTango 7.0.0 """) document_enum("LogTarget", """ An enumeration representing the LogTarget - LOG_CONSOLE - LOG_FILE - LOG_DEVICE New in PyTango 7.0.0 """) document_enum("EventType", """ An enumeration representing event type - CHANGE_EVENT - QUALITY_EVENT - PERIODIC_EVENT - ARCHIVE_EVENT - USER_EVENT - ATTR_CONF_EVENT - DATA_READY_EVENT - INTERFACE_CHANGE_EVENT - PIPE_EVENT *DATA_READY_EVENT - New in PyTango 7.0.0* *INTERFACE_CHANGE_EVENT - New in PyTango 9.2.2* *PIPE_EVENT - New in PyTango 9.2.2* """) document_enum("AttrSerialModel", """ An enumeration representing the AttrSerialModel - ATTR_NO_SYNC - ATTR_BY_KERNEL - ATTR_BY_USER New in PyTango 7.1.0 """) document_enum("KeepAliveCmdCode", """ An enumeration representing the KeepAliveCmdCode - EXIT_TH New in PyTango 7.0.0 """) document_enum("AccessControlType", """ An enumeration representing the AccessControlType - ACCESS_READ - ACCESS_WRITE New in PyTango 7.0.0 """) document_enum("asyn_req_type", """ An enumeration representing the asynchronous request type - POLLING - CALLBACK - ALL_ASYNCH """) document_enum("cb_sub_model", """ An enumeration representing callback sub model - PUSH_CALLBACK - PULL_CALLBACK """) document_enum("AttrQuality", """ An enumeration representing the attribute quality - ATTR_VALID - ATTR_INVALID - ATTR_ALARM - ATTR_CHANGING - ATTR_WARNING """) document_enum("AttrWriteType", """ An enumeration representing the attribute type - READ - READ_WITH_WRITE - WRITE - READ_WRITE """) document_enum("AttrDataFormat", """ An enumeration representing the attribute format - SCALAR - SPECTRUM - IMAGE - FMT_UNKNOWN """) document_enum("PipeWriteType", """ An enumeration representing the pipe type - PIPE_READ - PIPE_READ_WRITE """) document_enum("DevSource", """ An enumeration representing the device source for data - DEV - CACHE - CACHE_DEV """) document_enum("ErrSeverity", """ An enumeration representing the error severity - WARN - ERR - PANIC """) document_enum("DevState", """ An enumeration representing the device state - ON - OFF - CLOSE - OPEN - INSERT - EXTRACT - MOVING - STANDBY - FAULT - INIT - RUNNING - ALARM - DISABLE - UNKNOWN """) document_enum("DispLevel", """ An enumeration representing the display level - OPERATOR - EXPERT """) document_enum("GreenMode", """ An enumeration representing the GreenMode - Synchronous - Futures - Gevent New in PyTango 8.1.0 """) ArchiveEventInfo.__doc__ = """ A structure containing available archiving event information for an attribute with the folowing members: - archive_rel_change : (str) relative change that will generate an event - archive_abs_change : (str) absolute change that will generate an event - archive_period : (str) archive period - extensions : (sequence) extensions (currently not used)""" EventData.__doc__ = """ This class is used to pass data to the callback method when an event is sent to the client. It contains the following public fields: - device : (DeviceProxy) The DeviceProxy object on which the call was executed. - attr_name : (str) The attribute name - event : (str) The event name - attr_value : (DeviceAttribute) The attribute data (DeviceAttribute) - err : (bool) A boolean flag set to true if the request failed. False otherwise - errors : (sequence) The error stack - reception_date: (TimeVal) """ document_method(EventData, "get_date", """ get_date(self) -> TimeVal Returns the timestamp of the event. Parameters : None Return : (TimeVal) the timestamp of the event New in PyTango 7.0.0 """) AttrConfEventData.__doc__ = """ This class is used to pass data to the callback method when a configuration event is sent to the client. It contains the following public fields: - device : (DeviceProxy) The DeviceProxy object on which the call was executed - attr_name : (str) The attribute name - event : (str) The event name - attr_conf : (AttributeInfoEx) The attribute data - err : (bool) A boolean flag set to true if the request failed. False otherwise - errors : (sequence) The error stack - reception_date: (TimeVal) """ document_method(AttrConfEventData, "get_date", """ get_date(self) -> TimeVal Returns the timestamp of the event. Parameters : None Return : (TimeVal) the timestamp of the event New in PyTango 7.0.0 """) AttributeAlarmInfo.__doc__ = """ A structure containing available alarm information for an attribute with the folowing members: - min_alarm : (str) low alarm level - max_alarm : (str) high alarm level - min_warning : (str) low warning level - max_warning : (str) high warning level - delta_t : (str) time delta - delta_val : (str) value delta - extensions : (StdStringVector) extensions (currently not used)""" AttributeDimension.__doc__ = """ A structure containing x and y attribute data dimensions with the following members: - dim_x : (int) x dimension - dim_y : (int) y dimension""" AttributeEventInfo.__doc__ = """ A structure containing available event information for an attribute with the folowing members: - ch_event : (ChangeEventInfo) change event information - per_event : (PeriodicEventInfo) periodic event information - arch_event : (ArchiveEventInfo) archiving event information""" DeviceAttributeConfig.__doc__ = """ A base structure containing available information for an attribute with the following members: - name : (str) attribute name - writable : (AttrWriteType) write type (R, W, RW, R with W) - data_format : (AttrDataFormat) data format (SCALAR, SPECTRUM, IMAGE) - data_type : (int) attribute type (float, string,..) - max_dim_x : (int) first dimension of attribute (spectrum or image attributes) - max_dim_y : (int) second dimension of attribute(image attribute) - description : (int) attribute description - label : (str) attribute label (Voltage, time, ...) - unit : (str) attribute unit (V, ms, ...) - standard_unit : (str) standard unit - display_unit : (str) display unit - format : (str) how to display the attribute value (ex: for floats could be '%6.2f') - min_value : (str) minimum allowed value - max_value : (str) maximum allowed value - min_alarm : (str) low alarm level - max_alarm : (str) high alarm level - writable_attr_name : (str) name of the writable attribute - extensions : (StdStringVector) extensions (currently not used)""" AttributeInfo.__doc__ = """ A structure (inheriting from :class:`DeviceAttributeConfig`) containing available information for an attribute with the following members: - disp_level : (DispLevel) display level (OPERATOR, EXPERT) Inherited members are: - name : (str) attribute name - writable : (AttrWriteType) write type (R, W, RW, R with W) - data_format : (AttrDataFormat) data format (SCALAR, SPECTRUM, IMAGE) - data_type : (int) attribute type (float, string,..) - max_dim_x : (int) first dimension of attribute (spectrum or image attributes) - max_dim_y : (int) second dimension of attribute(image attribute) - description : (int) attribute description - label : (str) attribute label (Voltage, time, ...) - unit : (str) attribute unit (V, ms, ...) - standard_unit : (str) standard unit - display_unit : (str) display unit - format : (str) how to display the attribute value (ex: for floats could be '%6.2f') - min_value : (str) minimum allowed value - max_value : (str) maximum allowed value - min_alarm : (str) low alarm level - max_alarm : (str) high alarm level - writable_attr_name : (str) name of the writable attribute - extensions : (StdStringVector) extensions (currently not used)""" AttributeInfoEx.__doc__ = """ A structure (inheriting from :class:`AttributeInfo`) containing available information for an attribute with the following members: - alarms : object containing alarm information (see AttributeAlarmInfo). - events : object containing event information (see AttributeEventInfo). - sys_extensions : StdStringVector Inherited members are: - name : (str) attribute name - writable : (AttrWriteType) write type (R, W, RW, R with W) - data_format : (AttrDataFormat) data format (SCALAR, SPECTRUM, IMAGE) - data_type : (int) attribute type (float, string,..) - max_dim_x : (int) first dimension of attribute (spectrum or image attributes) - max_dim_y : (int) second dimension of attribute(image attribute) - description : (int) attribute description - label : (str) attribute label (Voltage, time, ...) - unit : (str) attribute unit (V, ms, ...) - standard_unit : (str) standard unit - display_unit : (str) display unit - format : (str) how to display the attribute value (ex: for floats could be '%6.2f') - min_value : (str) minimum allowed value - max_value : (str) maximum allowed value - min_alarm : (str) low alarm level - max_alarm : (str) high alarm level - writable_attr_name : (str) name of the writable attribute - extensions : (StdStringVector) extensions (currently not used) - disp_level : (DispLevel) display level (OPERATOR, EXPERT)""" ChangeEventInfo.__doc__ = """ A structure containing available change event information for an attribute with the folowing members: - rel_change : (str) relative change that will generate an event - abs_change : (str) absolute change that will generate an event - extensions : (StdStringVector) extensions (currently not used)""" PeriodicEventInfo.__doc__ = """ A structure containing available periodic event information for an attribute with the folowing members: - period : (str) event period - extensions : (StdStringVector) extensions (currently not used)""" DevCommandInfo.__doc__ = """ A device command info with the following members: - cmd_name : (str) command name - cmd_tag : command as binary value (for TACO) - in_type : (CmdArgType) input type - out_type : (CmdArgType) output type - in_type_desc : (str) description of input type - out_type_desc : (str) description of output type New in PyTango 7.0.0""" CommandInfo.__doc__ = """ A device command info (inheriting from :class:`DevCommandInfo`) with the following members: - disp_level : (DispLevel) command display level Inherited members are (from :class:`DevCommandInfo`): - cmd_name : (str) command name - cmd_tag : (str) command as binary value (for TACO) - in_type : (CmdArgType) input type - out_type : (CmdArgType) output type - in_type_desc : (str) description of input type - out_type_desc : (str) description of output type""" DataReadyEventData.__doc__ = """ This class is used to pass data to the callback method when an attribute data ready event is sent to the clien. It contains the following public fields: - device : (DeviceProxy) The DeviceProxy object on which the call was executed - attr_name : (str) The attribute name - event : (str) The event name - attr_data_type : (int) The attribute data type - ctr : (int) The user counter. Set to 0 if not defined when sent by the server - err : (bool) A boolean flag set to true if the request failed. False otherwise - errors : (sequence) The error stack - reception_date: (TimeVal) New in PyTango 7.0.0""" DeviceInfo.__doc__ = """ A structure containing available information for a device with the" following members: - dev_class : (str) device class - server_id : (str) server ID - server_host : (str) host name - server_version : (str) server version - doc_url : (str) document url""" LockerInfo.__doc__ = """ A structure with information about the locker with the folowing members: - ll : (tango.LockerLanguage) the locker language - li : (pid_t / UUID) the locker id - locker_host : (str) the host - locker_class : (str) the class pid_t should be an int, UUID should be a tuple of four numbers. New in PyTango 7.0.0""" PollDevice.__doc__ = """ A structure containing PollDevice information with the folowing members: - dev_name : (str) device name - ind_list : (sequence) index list New in PyTango 7.0.0""" document_method(DataReadyEventData, "get_date", """ get_date(self) -> TimeVal Returns the timestamp of the event. Parameters : None Return : (TimeVal) the timestamp of the event New in PyTango 7.0.0 """) TimeVal.__doc__ = """ Time value structure with the following members: - tv_sec : seconds - tv_usec : microseconds - tv_nsec : nanoseconds""" def base_types_init(doc=True): __init_base_types() if doc: __doc_base_types() pytango-9.2.2/tango/callback.py000066400000000000000000000052411316324373100164300ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ """ This is an internal PyTango module. """ __all__ = ["callback_init"] __docformat__ = "restructuredtext" from ._tango import CmdDoneEvent, AttrReadEvent, AttrWrittenEvent def __init_Callback(): pass def __doc_Callback(): CmdDoneEvent.__doc__ = """ This class is used to pass data to the callback method in asynchronous callback model for command execution. It has the following members: - device : (DeviceProxy) The DeviceProxy object on which the call was executed. - cmd_name : (str) The command name - argout_raw : (DeviceData) The command argout - argout : The command argout - err : (bool) A boolean flag set to true if the command failed. False otherwise - errors : (sequence) The error stack - ext : """ AttrReadEvent.__doc__ = """ This class is used to pass data to the callback method in asynchronous callback model for read_attribute(s) execution. It has the following members: - device : (DeviceProxy) The DeviceProxy object on which the call was executed - attr_names : (sequence) The attribute name list - argout : (DeviceAttribute) The attribute value - err : (bool) A boolean flag set to true if the command failed. False otherwise - errors : (sequence) The error stack - ext : """ AttrWrittenEvent.__doc__ = """ This class is used to pass data to the callback method in asynchronous callback model for write_attribute(s) execution It has the following members: - device : (DeviceProxy) The DeviceProxy object on which the call was executed - attr_names : (sequence) The attribute name list - err : (bool) A boolean flag set to true if the command failed. False otherwise - errors : (NamedDevFailedList) The error stack - ext : """ def callback_init(doc=True): __init_Callback() if doc: __doc_Callback() pytango-9.2.2/tango/client.py000066400000000000000000000176411316324373100161610ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ """ High Level API for writting Tango clients This is an experimental module. Not part of the official API. """ import weakref import functools import six import tango from tango import DeviceProxy as Device from tango import CmdArgType from tango.codec import loads from tango.codec import dumps as _dumps _FMT = "pickle" dumps = functools.partial(_dumps, _FMT) def _command(device, cmd_info, *args, **kwargs): name = cmd_info.cmd_name if cmd_info.in_type == CmdArgType.DevEncoded: result = device.command_inout(name, dumps((args, kwargs))) else: result = device.command_inout(name, *args, **kwargs) if cmd_info.out_type == CmdArgType.DevEncoded: result = loads(*result) return result class _DeviceHelper(object): __CMD_FILTER = set(("init", "state", "status")) __ATTR_FILTER = set(("state", "status")) __attr_cache = None __cmd_cache = None def __init__(self, dev_name, *args, **kwargs): self.dev_name = dev_name self.device = Device(dev_name, *args, **kwargs) self.slots = weakref.WeakKeyDictionary() def connect(self, signal, slot, event_type=tango.EventType.CHANGE_EVENT): i = self.device.subscribe_event(signal, event_type, slot) self.slots[slot] = i return i def disconnect(self, signal, slot): i = self.slots.pop(slot) self.device.unsubscribe_event(i) def get_attr_cache(self, refresh=False): cache = self.__attr_cache if not cache: refresh = True if refresh: cache = {} dev = self.device try: for attr_info in dev.attribute_list_query_ex(): attr_name = attr_info.name if attr_name.lower() in self.__ATTR_FILTER: continue cache[attr_name] = attr_info except tango.DevFailed: pass self.__attr_cache = cache return cache def get_attr_info(self, name): cache = self.get_attr_cache() result = cache.get(name) if result: return result else: cache = self.get_attr_cache(refresh=True) return cache.get(name) def get_cmd_cache(self, refresh=False): cache = self.__cmd_cache if not cache: refresh = True if refresh: cache = {} dev = self.device try: for cmd_info in dev.command_list_query(): cmd_name = cmd_info.cmd_name if cmd_name.lower() in self.__CMD_FILTER: continue cmd_func = functools.partial(_command, dev, cmd_info) cmd_func.__name__ = cmd_name cmd_func.__doc__ = cmd_info.in_type_desc cmd_info.func = cmd_func cache[cmd_name] = cmd_info except tango.DevFailed: pass self.__cmd_cache = cache return cache def get_cmd_info(self, name): cache = self.get_cmd_cache() result = cache.get(name) if result: return result else: cache = self.get_cmd_cache(refresh=True) return cache.get(name) def is_cmd(self, name): return name.lower() in self.get_cmd_cache() def members(self): result = self.get_attr_cache().keys() result.extend(self.get_cmd_cache().keys()) return result def get(self, name): dev = self.device result = self.get_attr_info(name) if result: result = dev.read_attribute(name) value = result.value if result.type == tango.DevEncoded: result = loads(*value) else: result = value return result result = self.get_cmd_info(name) if result is None: raise KeyError("Unknown %s" % name) return result def set(self, name, value): result = self.get_attr_info(name) if result is None: raise KeyError("Unknown attribute %s" % name) if result.data_type == tango.DevEncoded: self.device.write_attribute(name, dumps(value)) else: self.device.write_attribute(name, value) def get_info(self): try: return self.__info except AttributeError: pass try: info = self.device.info() self.__dict__["__info"] = info return info except tango.DevFailed: return None def __getitem__(self, name): if self.get_attr_info(name) is None: raise KeyError("Unknown attribute %s" % name) return self.device[name] def __setitem__(self, name, value): if self.get_attr_info(name) is None: raise KeyError("Unknown attribute %s" % name) self.device[name] = value def __str__(self): return self.dstr() def __repr__(self): return str(self) def dstr(self): info = self.get_info() klass = "Device" if info: klass = info.dev_class return "{0}({1})".format(klass, self.dev_name) class Object(object): """Tango object""" def __init__(self, dev_name, *args, **kwargs): helper = _DeviceHelper(dev_name, *args, **kwargs) self.__dict__["_helper"] = helper def __getattr__(self, name): try: r = self._helper.get(name) except KeyError as ke: six.raise_from(AttributeError('Unknown {0}'.format(name)), ke) if isinstance(r, tango.CommandInfo): self.__dict__[name] = r.func return r.func return r def __setattr__(self, name, value): try: return self._helper.set(name, value) except KeyError as ke: six.raise_from(AttributeError('Unknown {0}'.format(name)), ke) def __getitem__(self, name): return self._helper[name] def __setitem__(self, name, value): self._helper[name] = value def __str__(self): return str(self._helper) def __repr__(self): return repr(self._helper) def __dir__(self): return self._helper.members() def get_object_proxy(obj): """Experimental function. Not part of the official API""" return obj._helper.device def get_object_db(obj): """Experimental function. Not part of the official API""" return get_object_proxy(obj).get_device_db() def get_object_name(obj): """Experimental function. Not part of the official API""" return get_object_proxy(obj).get_name() def get_object_info(obj): """Experimental function. Not part of the official API""" return get_object_proxy(obj).info() def get_attributes_config(obj, refresh=False): """Experimental function. Not part of the official API""" return obj._helper.get_attr_cache(refresh=refresh) def get_commands_config(obj, refresh=False): """Experimental function. Not part of the official API""" return obj._helper.get_cmd_cache(refresh=refresh) def connect(obj, signal, slot, event_type=tango.EventType.CHANGE_EVENT): """Experimental function. Not part of the official API""" return obj._helper.connect(signal, slot, event_type=event_type) def disconnect(obj, signal, slot): """Experimental function. Not part of the official API""" return obj._helper.disconnect(signal, slot) pytango-9.2.2/tango/codec.py000066400000000000000000000011731316324373100157510ustar00rootroot00000000000000__all__ = ["loads", "dumps"] def loads(fmt, data): if fmt.startswith("pickle"): import pickle loads = pickle.loads elif fmt.startswith("json"): import json loads = json.loads else: raise TypeError("Format '{0}' not supported".format(fmt)) return loads(data) def dumps(fmt, obj): if fmt.startswith("pickle"): import pickle ret = fmt, pickle.dumps(obj, protocol=pickle.HIGHEST_PROTOCOL) return ret elif fmt.startswith("json"): import json return fmt, json.dumps(obj) raise TypeError("Format '{0}' not supported".format(fmt)) pytango-9.2.2/tango/connection.py000066400000000000000000000545431316324373100170440ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ """ This is an internal PyTango module. """ __all__ = ["connection_init"] __docformat__ = "restructuredtext" import collections from ._tango import Connection, DeviceData, __CallBackAutoDie, CmdArgType, \ DeviceProxy, Database, ExtractAs from .utils import document_method as __document_method from .utils import document_static_method as __document_static_method from .green import green def __CallBackAutoDie__cmd_ended_aux(self, fn): def __new_fn(cmd_done_event): try: cmd_done_event.argout = cmd_done_event.argout_raw.extract( self.defaultCommandExtractAs) except Exception: pass return fn(cmd_done_event) return __new_fn def __get_command_inout_param(self, cmd_name, cmd_param=None): if cmd_param is None: return DeviceData() if isinstance(cmd_param, DeviceData): return cmd_param if isinstance(self, DeviceProxy): # This is not part of 'Connection' interface, but # DeviceProxy only. info = self.command_query(cmd_name) param = DeviceData() param.insert(info.in_type, cmd_param) return param elif isinstance(self, Database): # I just try to guess types DevString and DevVarStringArray # as they are used for Database param = DeviceData() if isinstance(cmd_param, str): param.insert(CmdArgType.DevString, cmd_param) return param elif isinstance(cmd_param, collections.Sequence) and all([isinstance(x, str) for x in cmd_param]): param.insert(CmdArgType.DevVarStringArray, cmd_param) return param else: raise TypeError( "command_inout() parameter must be a DeviceData object or a string or a sequence of strings") else: raise TypeError("command_inout() parameter must be a DeviceData object.") def __Connection__command_inout(self, name, *args, **kwds): """ command_inout( self, cmd_name, cmd_param=None, green_mode=None, wait=True, timeout=None) -> any Execute a command on a device. Parameters : - cmd_name : (str) Command name. - cmd_param : (any) It should be a value of the type expected by the command or a DeviceData object with this value inserted. It can be ommited if the command should not get any argument. - green_mode : (GreenMode) Defaults to the current DeviceProxy GreenMode. (see :meth:`~tango.DeviceProxy.get_green_mode` and :meth:`~tango.DeviceProxy.set_green_mode`). - wait : (bool) whether or not to wait for result. If green_mode is *Synchronous*, this parameter is ignored as it always waits for the result. Ignored when green_mode is Synchronous (always waits). - timeout : (float) The number of seconds to wait for the result. If None, then there is no limit on the wait time. Ignored when green_mode is Synchronous or wait is False. Return : The result of the command. The type depends on the command. It may be None. Throws : ConnectionFailed, CommunicationFailed, DeviceUnlocked, DevFailed from device TimeoutError (green_mode == Futures) If the future didn't finish executing before the given timeout. Timeout (green_mode == Gevent) If the async result didn't finish executing before the given timeout. .. versionadded:: 8.1.0 *green_mode* parameter. *wait* parameter. *timeout* parameter. """ r = Connection.command_inout_raw(self, name, *args, **kwds) if isinstance(r, DeviceData): try: return r.extract(self.defaultCommandExtractAs) except Exception: return None else: return r __Connection__command_inout.__name__ = "command_inout" def __Connection__command_inout_raw(self, cmd_name, cmd_param=None): """ command_inout_raw( self, cmd_name, cmd_param=None) -> DeviceData Execute a command on a device. Parameters : - cmd_name : (str) Command name. - cmd_param : (any) It should be a value of the type expected by the command or a DeviceData object with this value inserted. It can be ommited if the command should not get any argument. Return : A DeviceData object. Throws : ConnectionFailed, CommunicationFailed, DeviceUnlocked, DevFailed from device """ param = __get_command_inout_param(self, cmd_name, cmd_param) return self.__command_inout(cmd_name, param) def __Connection__command_inout_asynch(self, cmd_name, *args): """ command_inout_asynch(self, cmd_name) -> id command_inout_asynch(self, cmd_name, cmd_param) -> id command_inout_asynch(self, cmd_name, cmd_param, forget) -> id Execute asynchronously (polling model) a command on a device Parameters : - cmd_name : (str) Command name. - cmd_param : (any) It should be a value of the type expected by the command or a DeviceData object with this value inserted. It can be ommited if the command should not get any argument. If the command should get no argument and you want to set the 'forget' param, use None for cmd_param. - forget : (bool) If this flag is set to true, this means that the client does not care at all about the server answer and will even not try to get it. Default value is False. Please, note that device re-connection will not take place (in case it is needed) if the fire and forget mode is used. Therefore, an application using only fire and forget requests is not able to automatically re-connnect to device. Return : (int) This call returns an asynchronous call identifier which is needed to get the command result (see command_inout_reply) Throws : ConnectionFailed, TypeError, anything thrown by command_query command_inout_asynch( self, cmd_name, callback) -> None command_inout_asynch( self, cmd_name, cmd_param, callback) -> None Execute asynchronously (callback model) a command on a device. Parameters : - cmd_name : (str) Command name. - cmd_param : (any)It should be a value of the type expected by the command or a DeviceData object with this value inserted. It can be ommited if the command should not get any argument. - callback : Any callable object (function, lambda...) or any oject with a method named "cmd_ended". Return : None Throws : ConnectionFailed, TypeError, anything thrown by command_query .. important:: by default, TANGO is initialized with the **polling** model. If you want to use the **push** model (the one with the callback parameter), you need to change the global TANGO model to PUSH_CALLBACK. You can do this with the :meth:`tango.ApiUtil.set_asynch_cb_sub_model` """ if len(args) == 0: # command_inout_asynch() argin = DeviceData() forget = False return self.__command_inout_asynch_id(cmd_name, argin, forget) elif len(args) == 1: if isinstance(args[0], collections.Callable): # command_inout_asynch(lambda) cb = __CallBackAutoDie() cb.cmd_ended = __CallBackAutoDie__cmd_ended_aux(self, args[0]) argin = __get_command_inout_param(self, cmd_name) return self.__command_inout_asynch_cb(cmd_name, argin, cb) elif hasattr(args[0], 'cmd_ended'): # command_inout_asynch(Cbclass) cb = __CallBackAutoDie() cb.cmd_ended = __CallBackAutoDie__cmd_ended_aux(self, args[0].cmd_ended) argin = __get_command_inout_param(self, cmd_name) return self.__command_inout_asynch_cb(cmd_name, argin, cb) else: # command_inout_asynch(value) argin = __get_command_inout_param(self, cmd_name, args[0]) forget = False return self.__command_inout_asynch_id(cmd_name, argin, forget) elif len(args) == 2: if isinstance(args[1], collections.Callable): # command_inout_asynch( value, lambda) cb = __CallBackAutoDie() cb.cmd_ended = __CallBackAutoDie__cmd_ended_aux(self, args[1]) argin = __get_command_inout_param(self, cmd_name, args[0]) return self.__command_inout_asynch_cb(cmd_name, argin, cb) elif hasattr(args[1], 'cmd_ended'): # command_inout_asynch(value, cbClass) cb = __CallBackAutoDie() cb.cmd_ended = __CallBackAutoDie__cmd_ended_aux(self, args[1].cmd_ended) argin = __get_command_inout_param(self, cmd_name, args[0]) return self.__command_inout_asynch_cb(cmd_name, argin, cb) else: # command_inout_asynch(value, forget) argin = __get_command_inout_param(self, cmd_name, args[0]) forget = bool(args[1]) return self.__command_inout_asynch_id(cmd_name, argin, forget) else: raise TypeError("Wrong number of attributes!") __Connection__command_inout_asynch.__name__ = "command_inout_asynch" def __Connection__command_inout_reply(self, idx, timeout=None): """ command_inout_reply(self, id) -> DeviceData Check if the answer of an asynchronous command_inout is arrived (polling model). If the reply is arrived and if it is a valid reply, it is returned to the caller in a DeviceData object. If the reply is an exception, it is re-thrown by this call. An exception is also thrown in case of the reply is not yet arrived. Parameters : - id : (int) Asynchronous call identifier. Return : (DeviceData) Throws : AsynCall, AsynReplyNotArrived, CommunicationFailed, DevFailed from device command_inout_reply(self, id, timeout) -> DeviceData Check if the answer of an asynchronous command_inout is arrived (polling model). id is the asynchronous call identifier. If the reply is arrived and if it is a valid reply, it is returned to the caller in a DeviceData object. If the reply is an exception, it is re-thrown by this call. If the reply is not yet arrived, the call will wait (blocking the process) for the time specified in timeout. If after timeout milliseconds, the reply is still not there, an exception is thrown. If timeout is set to 0, the call waits until the reply arrived. Parameters : - id : (int) Asynchronous call identifier. - timeout : (int) Return : (DeviceData) Throws : AsynCall, AsynReplyNotArrived, CommunicationFailed, DevFailed from device """ if timeout is None: r = self.command_inout_reply_raw(idx) else: r = self.command_inout_reply_raw(idx, timeout) if isinstance(r, DeviceData): try: return r.extract(self.defaultCommandExtractAs) except Exception: return None else: return r __Connection__command_inout_reply.__name__ = "command_inout_reply" def __init_Connection(): Connection.defaultCommandExtractAs = ExtractAs.Numpy Connection.command_inout_raw = __Connection__command_inout_raw Connection.command_inout = green(__Connection__command_inout) Connection.command_inout_asynch = __Connection__command_inout_asynch Connection.command_inout_reply = __Connection__command_inout_reply def __doc_Connection(): def document_method(method_name, desc, append=True): return __document_method(Connection, method_name, desc, append) def document_static_method(method_name, desc, append=True): return __document_static_method(Connection, method_name, desc, append) Connection.__doc__ = """ The abstract Connection class for DeviceProxy. Not to be initialized directly. """ document_method("dev_name", """ dev_name(self) -> str Return the device name as it is stored locally Parameters : None Return : (str) """) document_method("get_db_host", """ get_db_host(self) -> str Returns a string with the database host. Parameters : None Return : (str) New in PyTango 7.0.0 """) document_method("get_db_port", """ get_db_port(self) -> str Returns a string with the database port. Parameters : None Return : (str) New in PyTango 7.0.0 """) document_method("get_db_port_num", """ get_db_port_num(self) -> int Returns an integer with the database port. Parameters : None Return : (int) New in PyTango 7.0.0 """) document_method("get_from_env_var", """ get_from_env_var(self) -> bool Returns True if determined by environment variable or False otherwise Parameters : None Return : (bool) New in PyTango 7.0.0 """) document_method("connect", """ connect(self, corba_name) -> None Creates a connection to a TANGO device using it's stringified CORBA reference i.e. IOR or corbaloc. Parameters : - corba_name : (str) Name of the CORBA object Return : None New in PyTango 7.0.0 """) document_method("reconnect", """ reconnect(self, db_used) -> None Reconnecto to a CORBA object. Parameters : - db_used : (bool) Use thatabase Return : None New in PyTango 7.0.0 """) document_method("get_idl_version", """ get_idl_version(self) -> int Get the version of the Tango Device interface implemented by the device Parameters : None Return : (int) """) document_method("set_timeout_millis", """ set_timeout_millis(self, timeout) -> None Set client side timeout for device in milliseconds. Any method which takes longer than this time to execute will throw an exception Parameters : - timeout : integer value of timeout in milliseconds Return : None Example : dev.set_timeout_millis(1000) """) document_method("get_timeout_millis", """ get_timeout_millis(self) -> int Get the client side timeout in milliseconds Parameters : None Return : (int) """) document_method("get_source", """ get_source(self) -> DevSource Get the data source(device, polling buffer, polling buffer then device) used by command_inout or read_attribute methods Parameters : None Return : (DevSource) Example : source = dev.get_source() if source == DevSource.CACHE_DEV : ... """) document_method("set_source", """ set_source(self, source) -> None Set the data source(device, polling buffer, polling buffer then device) for command_inout and read_attribute methods. Parameters : - source: (DevSource) constant. Return : None Example : dev.set_source(DevSource.CACHE_DEV) """) document_method("get_transparency_reconnection", """ get_transparency_reconnection(self) -> bool Returns the device transparency reconnection flag. Parameters : None Return : (bool) True if transparency reconnection is set or False otherwise """) document_method("set_transparency_reconnection", """ set_transparency_reconnection(self, yesno) -> None Set the device transparency reconnection flag Parameters : " - val : (bool) True to set transparency reconnection " or False otherwise Return : None """) document_method("command_inout_reply_raw", """ command_inout_reply_raw(self, id) -> DeviceData Check if the answer of an asynchronous command_inout is arrived (polling model). If the reply is arrived and if it is a valid reply, it is returned to the caller in a DeviceData object. If the reply is an exception, it is re-thrown by this call. An exception is also thrown in case of the reply is not yet arrived. Parameters : - id : (int) Asynchronous call identifier. Return : (DeviceData) Throws : AsynCall, AsynReplyNotArrived, CommunicationFailed, DevFailed from device """) document_method("command_inout_reply_raw", """ command_inout_reply_raw(self, id, timeout) -> DeviceData Check if the answer of an asynchronous command_inout is arrived (polling model). id is the asynchronous call identifier. If the reply is arrived and if it is a valid reply, it is returned to the caller in a DeviceData object. If the reply is an exception, it is re-thrown by this call. If the reply is not yet arrived, the call will wait (blocking the process) for the time specified in timeout. If after timeout milliseconds, the reply is still not there, an exception is thrown. If timeout is set to 0, the call waits until the reply arrived. Parameters : - id : (int) Asynchronous call identifier. - timeout : (int) Return : (DeviceData) Throws : AsynCall, AsynReplyNotArrived, CommunicationFailed, DevFailed from device """) # // # // Asynchronous methods # // document_method("get_asynch_replies", """ get_asynch_replies(self) -> None Try to obtain data returned by a command asynchronously requested. This method does not block if the reply has not yet arrived. It fires callback for already arrived replies. Parameters : None Return : None New in PyTango 7.0.0 """) document_method("get_asynch_replies", """ get_asynch_replies(self, call_timeout) -> None Try to obtain data returned by a command asynchronously requested. This method blocks for the specified timeout if the reply is not yet arrived. This method fires callback when the reply arrived. If the timeout is set to 0, the call waits undefinitely for the reply Parameters : - call_timeout : (int) timeout in miliseconds Return : None New in PyTango 7.0.0 """) document_method("cancel_asynch_request", """ cancel_asynch_request(self, id ) -> None Cancel a running asynchronous request This is a client side call. Obviously, the call cannot be aborted while it is running in the device. Parameters : - id : The asynchronous call identifier Return : None New in PyTango 7.0.0 """) document_method("cancel_all_polling_asynch_request", """ cancel_all_polling_asynch_request(self) -> None Cancel all running asynchronous request This is a client side call. Obviously, the calls cannot be aborted while it is running in the device. Parameters : None Return : None New in PyTango 7.0.0 """) # // # // Control access related methods # // document_method("get_access_control", """ get_access_control(self) -> AccessControlType Returns the current access control type Parameters : None Return : (AccessControlType) The current access control type New in PyTango 7.0.0 """) document_method("set_access_control", """ set_access_control(self, acc) -> None Sets the current access control type Parameters : - acc: (AccessControlType) the type of access control to set Return : None New in PyTango 7.0.0 """) document_method("get_access_right", """ get_access_right(self) -> AccessControlType Returns the current access control type Parameters : None Return : (AccessControlType) The current access control type New in PyTango 8.0.0 """) document_static_method("get_fqdn", """ get_fqdn(self) -> str Returns the fully qualified domain name Parameters : None Return : (str) the fully qualified domain name New in PyTango 7.2.0 """) document_method("is_dbase_used", """ is_dbase_used(self) -> bool Returns if the database is being used Parameters : None Return : (bool) True if the database is being used New in PyTango 7.2.0 """) document_method("get_dev_host", """ get_dev_host(self) -> str Returns the current host Parameters : None Return : (str) the current host New in PyTango 7.2.0 """) document_method("get_dev_port", """ get_dev_port(self) -> str Returns the current port Parameters : None Return : (str) the current port New in PyTango 7.2.0 """) def connection_init(doc=True): __init_Connection() if doc: __doc_Connection() pytango-9.2.2/tango/databaseds/000077500000000000000000000000001316324373100164135ustar00rootroot00000000000000pytango-9.2.2/tango/databaseds/DataBase.xmi000066400000000000000000001567631316324373100206200ustar00rootroot00000000000000 pytango-9.2.2/tango/databaseds/DataBaseds000077500000000000000000000001341316324373100203320ustar00rootroot00000000000000#!/usr/bin/env python from database import main if __name__ == '__main__': main() pytango-9.2.2/tango/databaseds/__init__.py000066400000000000000000000000001316324373100205120ustar00rootroot00000000000000pytango-9.2.2/tango/databaseds/create_db.sql000066400000000000000000000215341316324373100210510ustar00rootroot00000000000000# # Create all database tables # source create_db_tables.sql # # Init the history identifiers # INSERT INTO device_history_id VALUES (0); INSERT INTO device_attribute_history_id VALUES (0); INSERT INTO class_history_id VALUES (0); INSERT INTO class_attribute_history_id VALUES (0); INSERT INTO object_history_id VALUES (0); # # Create entry for database device server in device table # INSERT INTO device VALUES ('sys/database/2',NULL,'sys','database','2','nada','nada','nada','DataBaseds/2','nada','DataBase','nada','nada','nada','nada'); INSERT INTO device VALUES ('dserver/DataBaseds/2',NULL,'dserver','DataBaseds','2','nada','nada','nada','DataBaseds/2','nada','DServer','nada','nada','nada','nada'); INSERT INTO server VALUES ('databaseds/2','',0,0); INSERT INTO device VALUES ('sys/database/pydb-test',NULL,'sys','database','pydb-test','nada','nada','nada','DataBaseds/pydb-test','nada','DataBase','nada','nada','nada','nada'); INSERT INTO device VALUES ('dserver/DataBaseds/pydb-test',NULL,'dserver','DataBaseds','pydb-test','nada','nada','nada','DataBaseds/pydb-test','nada','DServer','nada','nada','nada','nada'); INSERT INTO server VALUES ('databaseds/pydb-test','',0,0); # # Create entry for test device server in device table # INSERT INTO device VALUES ('sys/tg_test/1',NULL,'sys','tg_test','1','nada','nada','nada','TangoTest/test','nada','TangoTest','nada','nada','nada','nada'); INSERT INTO device VALUES ('dserver/TangoTest/test',NULL,'dserver','TangoTest','test','nada','nada','nada','TangoTest/test','nada','DServer','nada','nada','nada','nada'); INSERT INTO server VALUES ('tangotest/test','',0,0); # # Create entry for Tango Control Access in device table # INSERT INTO device VALUES ('sys/access_control/1',NULL,'sys','access_control','1','nada','nada','nada','TangoAccessControl/1','nada','TangoAccessControl','nada','nada','nada','nada'); INSERT INTO device VALUES ('dserver/TangoAccessControl/1',NULL,'dserver','TangoAccessControl','1','nada','nada','nada','TangoAccessControl/1','nada','DServer','nada','nada','nada','nada'); INSERT INTO server VALUES ('tangoaccesscontrol/1','',0,0); # # Create default user access # INSERT INTO access_address VALUES ('*','*.*.*.*','FF.FF.FF.FF',20060824131221,00000000000000); INSERT INTO access_device VALUES ('*','*/*/*','write',20060824131221,00000000000000); # # Create entries in the property_class tables for controlled access service # INSERT INTO property_class VALUES('Database','AllowedAccessCmd',1,'DbGetServerInfo','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('Database','AllowedAccessCmd',2,'DbGetServerNameList','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('Database','AllowedAccessCmd',3,'DbGetInstanceNameList','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('Database','AllowedAccessCmd',4,'DbGetDeviceServerClassList','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('Database','AllowedAccessCmd',5,'DbGetDeviceList','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('Database','AllowedAccessCmd',6,'DbGetDeviceDomainList','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('Database','AllowedAccessCmd',7,'DbGetDeviceFamilyList','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('Database','AllowedAccessCmd',8,'DbGetDeviceMemberList','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('Database','AllowedAccessCmd',9,'DbGetClassList','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('Database','AllowedAccessCmd',10,'DbGetDeviceAliasList','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('Database','AllowedAccessCmd',11,'DbGetObjectList','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('Database','AllowedAccessCmd',12,'DbGetPropertyList','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('Database','AllowedAccessCmd',13,'DbGetProperty','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('Database','AllowedAccessCmd',14,'DbGetClassPropertyList','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('Database','AllowedAccessCmd',15,'DbGetClassProperty','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('Database','AllowedAccessCmd',16,'DbGetDevicePropertyList','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('Database','AllowedAccessCmd',17,'DbGetDeviceProperty','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('Database','AllowedAccessCmd',18,'DbGetClassAttributeList','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('Database','AllowedAccessCmd',19,'DbGetDeviceAttributeProperty','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('Database','AllowedAccessCmd',20,'DbGetDeviceAttributeProperty2','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('Database','AllowedAccessCmd',21,'DbGetLoggingLevel','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('Database','AllowedAccessCmd',22,'DbGetAliasDevice','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('Database','AllowedAccessCmd',23,'DbGetClassForDevice','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('Database','AllowedAccessCmd',24,'DbGetClassInheritanceForDevice','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('Database','AllowedAccessCmd',25,'DbGetDataForServerCache','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('Database','AllowedAccessCmd',26,'DbInfo','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('Database','AllowedAccessCmd',27,'DbGetClassAttributeProperty','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('Database','AllowedAccessCmd',28,'DbGetClassAttributeProperty2','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('Database','AllowedAccessCmd',29,'DbMysqlSelect','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('Database','AllowedAccessCmd',30,'DbGetDeviceInfo','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('Database','AllowedAccessCmd',31,'DbGetDeviceWideList','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('Database','AllowedAccessCmd',32,'DbImportEvent','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('Database','AllowedAccessCmd',33,'DbGetDeviceAlias','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('Database','AllowedAccessCmd',34,'DbGetCSDbServerList','1980-01-01 ','1980-01-01 ',NULL); # # # INSERT INTO property_class VALUES('DServer','AllowedAccessCmd',1,'QueryClass','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('DServer','AllowedAccessCmd',2,'QueryDevice','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('DServer','AllowedAccessCmd',3,'EventSubscriptionChange','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('DServer','AllowedAccessCmd',4,'DevPollStatus','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('DServer','AllowedAccessCmd',5,'GetLoggingLevel','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('DServer','AllowedAccessCmd',6,'GetLoggingTarget','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('DServer','AllowedAccessCmd',7,'QueryWizardDevProperty','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('DServer','AllowedAccessCmd',8,'QueryWizardClassProperty','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES('DServer','AllowedAccessCmd',9,'QuerySubDevice','1980-01-01 ','1980-01-01 ',NULL); # # # INSERT INTO property_class VALUES ('Starter','AllowedAccessCmd',1,'DevReadLog','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES ('Starter','AllowedAccessCmd',2,'DevStart','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES ('Starter','AllowedAccessCmd',3,'DevGetRunningServers','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES ('Starter','AllowedAccessCmd',4,'DevGetStopServers','1980-01-01 ','1980-01-01 ',NULL); # # # INSERT INTO property_class VALUES ('TangoAccessControl','AllowedAccessCmd',1,'GetUsers','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES ('TangoAccessControl','AllowedAccessCmd',2,'GetAddressByUser','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES ('TangoAccessControl','AllowedAccessCmd',3,'GetDeviceByUser','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES ('TangoAccessControl','AllowedAccessCmd',4,'GetAccess','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES ('TangoAccessControl','AllowedAccessCmd',5,'GetAllowedCommands','1980-01-01 ','1980-01-01 ',NULL); INSERT INTO property_class VALUES ('TangoAccessControl','AllowedAccessCmd',6,'GetAllowedCommandClassList','1980-01-01 ','1980-01-01 ',NULL); # # Load the stored procedures # source stored_proc.sql pytango-9.2.2/tango/databaseds/create_db_tables.sql000066400000000000000000000137441316324373100224070ustar00rootroot00000000000000CREATE TABLE IF NOT EXISTS access_address ( user varchar(255) default NULL, address varchar(255) default NULL, netmask varchar(255) default 'FF.FF.FF.FF', updated timestamp NOT NULL, accessed timestamp NOT NULL default '00000000000000' ) ; CREATE TABLE IF NOT EXISTS access_device ( user varchar(255) default NULL, device varchar(255) default NULL, rights varchar(255) default NULL, updated timestamp NOT NULL, accessed timestamp NOT NULL default '00000000000000' ) ; CREATE TABLE IF NOT EXISTS attribute_alias ( alias varchar(255) NOT NULL default '', name varchar(255) NOT NULL default '', device varchar(255) NOT NULL default '', attribute varchar(255) NOT NULL default '', updated timestamp NOT NULL, accessed timestamp NOT NULL, comment text ) ; CREATE TABLE IF NOT EXISTS attribute_class ( class varchar(255) NOT NULL default '', name varchar(255) NOT NULL default '', updated timestamp NOT NULL, accessed timestamp NOT NULL, comment text ) ; CREATE TABLE IF NOT EXISTS device ( name varchar(255) NOT NULL default 'nada', alias varchar(255) default NULL, domain varchar(85) NOT NULL default 'nada', family varchar(85) NOT NULL default 'nada', member varchar(85) NOT NULL default 'nada', exported int(11) default 0, ior text, host varchar(255) NOT NULL default 'nada', server varchar(255) NOT NULL default 'nada', pid int(11) default 0, class varchar(255) NOT NULL default 'nada', version varchar(8) NOT NULL default 'nada', started datetime default 0, stopped datetime default 0, comment text ) ; # # Table structure for table 'event' # CREATE TABLE IF NOT EXISTS event ( name varchar(255) default NULL, exported int(11) default NULL, ior text, host varchar(255) default NULL, server varchar(255) default NULL, pid int(11) default NULL, version varchar(8) default NULL, started datetime default NULL, stopped datetime default NULL ) ; # # Table structure for table 'property' # CREATE TABLE IF NOT EXISTS property ( object varchar(255) default NULL, name varchar(255) default NULL, count int(11) default NULL, value text default NULL, updated timestamp NOT NULL, accessed timestamp NOT NULL, comment text ) ; # # Table structure for table 'property_attribute_class' # CREATE TABLE IF NOT EXISTS property_attribute_class ( class varchar(255) NOT NULL default '', attribute varchar(255) NOT NULL default '', name varchar(255) NOT NULL default '', count int(11) NOT NULL default '0', value text default NULL, updated timestamp NOT NULL, accessed timestamp NOT NULL, comment text ) ; # # Table structure for table 'property_attribute_device' # CREATE TABLE IF NOT EXISTS property_attribute_device ( device varchar(255) NOT NULL default '', attribute varchar(255) NOT NULL default '', name varchar(255) NOT NULL default '', count int(11) NOT NULL default '0', value text default NULL, updated timestamp NOT NULL, accessed timestamp NOT NULL, comment text ) ; # # Table structure for table 'property_class' # CREATE TABLE IF NOT EXISTS property_class ( class varchar(255) NOT NULL default '', name varchar(255) NOT NULL default '', count int(11) NOT NULL default '0', value text default NULL, updated timestamp NOT NULL, accessed timestamp NOT NULL, comment text ) ; # # Table structure for table 'property_device' # CREATE TABLE IF NOT EXISTS property_device ( device varchar(255) NOT NULL default '', name varchar(255) NOT NULL default '', domain varchar(255) NOT NULL default '', family varchar(255) NOT NULL default '', member varchar(255) NOT NULL default '', count int(11) NOT NULL default '0', value text default NULL, updated timestamp NOT NULL, accessed timestamp NOT NULL, comment text ) ; # # Table structure for table 'server' # CREATE TABLE IF NOT EXISTS server ( name varchar(255) NOT NULL default '', host varchar(255) NOT NULL default '', mode int(11) default '0', level int(11) default '0' ) ; # # Tables for history identifiers # CREATE TABLE IF NOT EXISTS device_history_id ( id int(11) NOT NULL default '0' ) ; CREATE TABLE IF NOT EXISTS device_attribute_history_id ( id int(11) NOT NULL default '0' ) ; CREATE TABLE IF NOT EXISTS class_history_id ( id int(11) NOT NULL default '0' ) ; CREATE TABLE IF NOT EXISTS class_attribute_history_id ( id int(11) NOT NULL default '0' ) ; CREATE TABLE IF NOT EXISTS object_history_id ( id int(11) NOT NULL default '0' ) ; # # Tables for history # CREATE TABLE IF NOT EXISTS property_hist ( id int(10) NOT NULL default '0', date timestamp NOT NULL, object varchar(255) NOT NULL default '', name varchar(255) NOT NULL default '', count int(11) NOT NULL default '0', value text ) ; CREATE TABLE IF NOT EXISTS property_device_hist ( id int(10) NOT NULL default '0', date timestamp NOT NULL, device varchar(255) NOT NULL default '', name varchar(255) NOT NULL default '', count int(11) NOT NULL default '0', value text ) ; CREATE TABLE IF NOT EXISTS property_class_hist ( id int(10) NOT NULL default '0', date timestamp NOT NULL, class varchar(255) NOT NULL default '', name varchar(255) NOT NULL default '', count int(11) NOT NULL default '0', value text ) ; CREATE TABLE IF NOT EXISTS property_attribute_class_hist ( id int(10) NOT NULL default '0', date timestamp NOT NULL, class varchar(255) NOT NULL default '', attribute varchar(255) NOT NULL default '', name varchar(255) NOT NULL default '', count int(11) NOT NULL default '0', value text ) ; CREATE TABLE IF NOT EXISTS property_attribute_device_hist ( id int(10) NOT NULL default '0', date timestamp NOT NULL, device varchar(255) NOT NULL default '', attribute varchar(255) NOT NULL default '', name varchar(255) NOT NULL default '', count int(11) NOT NULL default '0', value text ) ; pytango-9.2.2/tango/databaseds/database.py000066400000000000000000002342041316324373100205360ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """This class manage the TANGO database.""" import sys import time import logging import functools try: import argparse except ImportError: argparse = None from optparse import OptionParser import tango from tango import AttrWriteType, GreenMode from tango.server import Device, DeviceMeta from tango.server import attribute, command from tango.server import class_property from tango.server import device_property from tango.server import run from tango.globals import get_class, get_class_by_class, \ get_constructed_class_by_class READ_ONLY = AttrWriteType.READ WRITE_ONLY = AttrWriteType.WRITE READ_WRITE = AttrWriteType.READ_WRITE READ_WITH_WRITE = AttrWriteType.READ_WITH_WRITE #Argument Options global options global WILDCARD_REPLACEMENT WILDCARD_REPLACEMENT = True class DbInter(tango.Interceptors): def create_thread(self): pass def delete_thread(self): pass DB_NAME = None def set_db_name(db_name): global DB_NAME DB_NAME = db_name def get_db_name(): return DB_NAME from . import db_access th_exc = tango.Except.throw_exception from .db_errors import * def check_device_name(dev_name): if '*' in dev_name: return False, None, None if dev_name.startswith("tango:"): dev_name = dev_name[6:] elif dev_name.startswith("taco:"): dev_name = dev_name[5:] if dev_name.startswith("//"): dev_name = dev_name[2:] if not '/' in dev_name or dev_name.startswith("/"): return False, None, None dfm = dev_name.split("/") if len(dfm) != 3: return False, None, None # check that each element has at least one character if not all(map(len, dfm)): return False, None, None return True, dev_name, dfm def replace_wildcard(text): if not WILDCARD_REPLACEMENT: return text # escape '%' with '\' text = text.replace("%", "\\%") # escape '_' with '\' text = text.replace("_", "\\_") # escape '"' with '\' text = text.replace('"', '\\"') # escape ''' with '\' text = text.replace("'", "\\'") # replace '*' with '%' text = text.replace("*", "%") return text class TimeStructure: def __init__(self): self.average = 0 self.minimum = 0 self.maximum = 0 self.maximum = 0 self.total_elapsed = 0 self.calls = 0 self.index = '' def stats(f): fname = f.__name__ @functools.wraps(f) def wrapper(self, *args, **kwargs): start = time.time() try: return f(self, *args, **kwargs) finally: end = time.time() update_timing_stats(self, start, end, fname) return wrapper def update_timing_stats(dev, time_before, time_after, cmd_name): tmp_time = dev.timing_maps[cmd_name] time_elapsed = (time_after - time_before) * 1000. tmp_time.total_elapsed = tmp_time.total_elapsed + time_elapsed if time_elapsed > tmp_time.maximum: tmp_time.maximum = time_elapsed if time_elapsed < tmp_time.minimum or tmp_time.minimum == 0: tmp_time.minimum = time_elapsed tmp_time.calls = tmp_time.calls + 1 tmp_time.average = tmp_time.total_elapsed/tmp_time.calls class DataBase(Device): """ DataBase TANGO device class """ # --- attributes --------------------------------------- Timing_maximum = attribute(dtype=('float64',),max_dim_x=128, access=READ_ONLY) Timing_average = attribute(dtype=('float64',),max_dim_x=128, access=READ_ONLY) Timing_index = attribute(dtype=('str',),max_dim_x=128, access=READ_ONLY) Timing_calls = attribute(dtype=('float64',),max_dim_x=128, access=READ_ONLY) Timing_info = attribute(dtype=('str',),max_dim_x=128, access=READ_ONLY) StoredProcedureRelease = attribute(dtype='str', access=READ_ONLY) Timing_minimum = attribute(dtype=('float64',),max_dim_x=128, access=READ_ONLY) def init_device(self): self._log = log = logging.getLogger(self.get_name()) self._log.debug("In init_device()") self.attr_StoredProcedureRelease_read = '' self.init_timing_stats() m = __import__('%s.%s' % (db_access.__package__,options.db_access),None,None, '%s.%s' % (db_access.__package__,options.db_access)) self.db = m.get_db(personal_name = options.argv[1]) try: global WILDCARD_REPLACEMENT WILDCARD_REPLACEMENT = m.get_wildcard_replacement() except AttributeError: pass self.set_state(tango.DevState.ON) def init_timing_stats(self): self.timing_maps = {} for cmd in dir(self): if cmd.startswith('Db'): self.timing_maps[cmd] = TimeStructure() self.timing_maps[cmd].index = cmd # --- attribute methods -------------------------------- def read_Timing_maximum(self): self._log.debug("In read_Timing_maximum()") return [x.maximum for x in self.timing_maps.values()] def read_Timing_average(self): self._log.debug("In read_Timing_average()") return [x.average for x in self.timing_maps.values()] def read_Timing_index(self): self._log.debug("In read_Timing_index()") return [x.index for x in self.timing_maps.values()] def read_Timing_calls(self): self._log.debug("In read_Timing_calls()") return [x.calls for x in self.timing_maps.values()] def read_Timing_info(self): self._log.debug("In read_Timing_info()") util = tango.Util.instance() attr_Timing_info_read = [] attr_Timing_info_read.append("TANGO Database Timing info on host " + util.get_host_name()) attr_Timing_info_read.append(" ") attr_Timing_info_read.append("command average minimum maximum calls") attr_Timing_info_read.append(" ") for tmp_name in sorted(self.timing_maps.keys()): tmp_info = "%41s\t%6.3f\t%6.3f\t%6.3f\t%.0f"%(tmp_name, self.timing_maps[tmp_name].average, self.timing_maps[tmp_name].minimum, self.timing_maps[tmp_name].maximum, self.timing_maps[tmp_name].calls) attr_Timing_info_read.append(tmp_info) return attr_Timing_info_read def read_StoredProcedureRelease(self): self._log.debug("In read_StoredProcedureRelease()") self.attr_StoredProcedureRelease_read = self.db.get_stored_procedure_release() return self.attr_StoredProcedureRelease_read def read_Timing_minimum(self): self._log.debug("In read_Timing_minimum()") return [x.minimum for x in self.timing_maps.values()] # --- commands ----------------------------------------- @stats @command(dtype_in='str', doc_in='The wildcard', dtype_out=('str',), doc_out='Device name domain list') def DbGetDeviceDomainList(self, argin): """ Get list of device domain name matching the specified :param argin: The wildcard :type: tango.DevString :return: Device name domain list :rtype: tango.DevVarStringArray """ self._log.debug("In DbGetDeviceDomainList()") return self.db.get_device_domain_list(replace_wildcard(argin)) @stats @command(dtype_in='str', doc_in='Device server name (executable/instance)', doc_out='none') def DbUnExportServer(self, argin): """ Mark all devices belonging to a specified device server process as non exported :param argin: Device server name (executable/instance) :type: tango.DevString :return: :rtype: tango.DevVoid """ self._log.debug("In DbUnExportServer()") self.db.unexport_server(argin) @command(dtype_in=('str',), doc_in='str[0] = device name\nStr[1]...str[n] = attribute name(s)', doc_out='none') def DbDeleteAllDeviceAttributeProperty(self, argin): """ Delete all attribute properties for the specified device attribute(s) :param argin: str[0] = device name Str[1]...str[n] = attribute name(s) :type: tango.DevVarStringArray :return: :rtype: tango.DevVoid """ self._log.debug("In DbDeleteAllDeviceAttributeProperty()") if len(argin) < 2: self.warn_stream("DataBase::DbDeleteAllDeviceAttributeProperty(): insufficient number of arguments ") th_exc(DB_IncorrectArguments, "insufficient number of arguments to delete all device attribute(s) property", "DataBase::DbDeleteAllDeviceAttributeProperty()") dev_name = argin[0] ret, d_name, dfm = check_device_name(dev_name) if not ret: th_exc(DB_IncorrectDeviceName, "device name (" + argin + ") syntax error (should be [tango:][//instance/]domain/family/member)", "DataBase::DbDeleteAllDeviceAttributeProperty()") self.db.delete_all_device_attribute_property(dev_name, argin[1:]) @command(dtype_in='str', doc_in='Attriibute alias name.', doc_out='none') def DbDeleteAttributeAlias(self, argin): """ Delete an attribute alias. :param argin: Attriibute alias name. :type: tango.DevString :return: :rtype: tango.DevVoid """ self._log.debug("In DbDeleteAttributeAlias()") self.db.delete_attribute_alias(argin) @command(dtype_in=('str',), doc_in='Str[0] = Tango class\nStr[1] = Attribute name\nStr[2] = Property name', dtype_out=('str',), doc_out='Str[0] = Attribute name\nStr[1] = Property name\nStr[2] = date\nStr[3] = Property value number (array case)\nStr[4] = Property value 1\nStr[n] = Property value n') def DbGetClassAttributePropertyHist(self, argin): """ Retrieve Tango class attribute property history :param argin: Str[0] = Tango class Str[1] = Attribute name Str[2] = Property name :type: tango.DevVarStringArray :return: Str[0] = Attribute name Str[1] = Property name Str[2] = date Str[3] = Property value number (array case) Str[4] = Property value 1 Str[n] = Property value n :rtype: tango.DevVarStringArray """ self._log.debug("In DbGetClassAttributePropertyHist()") class_name = argin[0] attribute = replace_wildcard(argin[1]) prop_name = replace_wildcard(argin[2]) return self.db.get_class_attribute_property_hist(class_name, attribute, prop_name) @stats @command(dtype_in=('str',), doc_in='Str[0] = Device name\nStr[1] = Attribute number\nStr[2] = Attribute name\nStr[3] = Property number\nStr[4] = Property name\nStr[5] = Property value number (array case)\nStr[5] = Property value 1\nStr[n] = Property value n (array case)\n.....', doc_out='none') def DbPutDeviceAttributeProperty2(self, argin): """ Put device attribute property. This command adds the possibility to have attribute property which are arrays. Not possible with the old DbPutDeviceAttributeProperty command. This old command is not deleted for compatibility reasons. :param argin: Str[0] = Device name Str[1] = Attribute number Str[2] = Attribute name Str[3] = Property number Str[4] = Property name Str[5] = Property value number (array case) Str[5] = Property value 1 Str[n] = Property value n (array case) ..... :type: tango.DevVarStringArray :return: :rtype: tango.DevVoid """ self._log.debug("In DbPutDeviceAttributeProperty2()") device_name = argin[0] nb_attributes = int(argin[1]) self.db.put_device_attribute_property2(device_name, nb_attributes, argin[2:]) @command(dtype_in='str', doc_in='attribute alias filter string (eg: att*)', dtype_out=('str',), doc_out='attribute aliases') def DbGetAttributeAliasList(self, argin): """ Get attribute alias list for a specified filter :param argin: attribute alias filter string (eg: att*) :type: tango.DevString :return: attribute aliases :rtype: tango.DevVarStringArray """ self._log.debug("In DbGetAttributeAliasList()") if not argin: argin = "%" else: argin = replace_wildcard(argin) return self.db.get_attribute_alias_list(argin) @command(dtype_in='str', doc_in='Class name', dtype_out=('str',), doc_out='Device exported list') def DbGetExportdDeviceListForClass(self, argin): """ Query the database for device exported for the specified class. :param argin: Class name :type: tango.DevString :return: Device exported list :rtype: tango.DevVarStringArray """ self._log.debug("In DbGetExportdDeviceListForClass()") argin = replace_wildcard(argin) return self.db.get_exported_device_list_for_class(argin) @command(dtype_in=('str',), doc_in='Str[0] = attribute name\nStr[1] = attribute alias', doc_out='none') def DbPutAttributeAlias(self, argin): """ Define an alias for an attribute :param argin: Str[0] = attribute name Str[1] = attribute alias :type: tango.DevVarStringArray :return: :rtype: tango.DevVoid """ self._log.debug("In DbPutAttributeAlias()") if len(argin) < 2: self.warn_stream("DataBase::DbPutAttributeAlias(): insufficient number of arguments ") th_exc(DB_IncorrectArguments, "insufficient number of arguments to put attribute alias", "DataBase::DbPutAttributeAlias()") attribute_name = argin[0] attribute_alias = argin[1] self.db.put_attribute_alias(attribute_name, attribute_alias) @stats @command(dtype_in='str', doc_in='The filter', dtype_out=('str',), doc_out='Device server process name list') def DbGetServerList(self, argin): """ Get list of device server process defined in database with name matching the specified filter :param argin: The filter :type: tango.DevString :return: Device server process name list :rtype: tango.DevVarStringArray """ self._log.debug("In DbGetServerList()") argin = replace_wildcard(argin) return self.db.get_server_list(argin) @stats @command(dtype_in=('str',), doc_in='Str[0] = Device name\nStr[1] = CORBA IOR\nStr[2] = Device server process host name\nStr[3] = Device server process PID or string ``null``\nStr[4] = Device server process version', doc_out='none') def DbExportDevice(self, argin): """ Export a device to the database :param argin: Str[0] = Device name Str[1] = CORBA IOR Str[2] = Device server process host name Str[3] = Device server process PID or string ``null`` Str[4] = Device server process version :type: tango.DevVarStringArray :return: :rtype: tango.DevVoid """ DbExportDevice(self, argin) @command(dtype_in=('str',), doc_in='Str[0] = Device name\nStr[1] = Attribute name\nStr[2] = Property name\nStr[n] = Property name', doc_out='none') def DbDeleteDeviceAttributeProperty(self, argin): """ delete a device attribute property from the database :param argin: Str[0] = Device name Str[1] = Attribute name Str[2] = Property name Str[n] = Property name :type: tango.DevVarStringArray :return: :rtype: tango.DevVoid """ self._log.debug("In DbDeleteDeviceAttributeProperty()") if len(argin) < 3: self.warn_stream("DataBase::db_delete_device_attribute_property(): insufficient number of arguments ") th_exc(DB_IncorrectArguments, "insufficient number of arguments to delete device attribute property", "DataBase::DeleteDeviceAttributeProperty()") dev_name, attr_name = argin[:2] ret, dev_name, dfm = check_device_name(argin) if not ret: self.warn_stream("DataBase::db_delete_device_attribute_property(): device name " + argin + " incorrect ") th_exc(DB_IncorrectDeviceName, "failed to delete device attribute property, device name incorrect", "DataBase::DeleteDeviceAttributeProperty()") for prop_name in argin[2:]: self.db.delete_device_attribute_property(dev_name, attr_name, prop_name) @stats @command(dtype_in='str', doc_in='The wildcard', dtype_out=('str',), doc_out='Family list') def DbGetDeviceFamilyList(self, argin): """ Get a list of device name families for device name matching the specified wildcard :param argin: The wildcard :type: tango.DevString :return: Family list :rtype: tango.DevVarStringArray """ self._log.debug("In DbGetDeviceFamilyList()") argin = replace_wildcard(argin) return self.db.get_device_family_list(argin) @command(dtype_in='str', doc_in='filter', dtype_out=('str',), doc_out='list of exported devices') def DbGetDeviceWideList(self, argin): """ Get a list of devices whose names satisfy the filter. :param argin: filter :type: tango.DevString :return: list of exported devices :rtype: tango.DevVarStringArray """ self._log.debug("In DbGetDeviceWideList()") argin = replace_wildcard(argin) return self.db.get_device_wide_list(argin) @command(dtype_in=('str',), doc_in='Str[0] = Object name\nStr[1] = Property number\nStr[2] = Property name\nStr[3] = Property value number\nStr[4] = Property value 1\nStr[n] = Property value n\n....', doc_out='none') def DbPutProperty(self, argin): """ Create / Update free object property(ies) :param argin: Str[0] = Object name Str[1] = Property number Str[2] = Property name Str[3] = Property value number Str[4] = Property value 1 Str[n] = Property value n .... :type: tango.DevVarStringArray :return: :rtype: tango.DevVoid """ self._log.debug("In DbPutProperty()") object_name = argin[0] nb_properties = int(argin[1]) self.db.put_property(object_name, properties, argin[2:]) @command(dtype_in=('str',), doc_in='Str[0] = Object name\nStr[1] = Property name\nStr[n] = Property name', doc_out='none') def DbDeleteProperty(self, argin): """ Delete free property from database :param argin: Str[0] = Object name Str[1] = Property name Str[n] = Property name :type: tango.DevVarStringArray :return: :rtype: tango.DevVoid """ self._log.debug("In DbDeleteProperty()") obj_name = argin[0] for prop_name in argin[1:]: self.db.delete_property(obj_name, prop_name) @command(dtype_in=('str',), doc_in='Str[0] = Tango class name\nStr[1] = Attribute name\nStr[n] = Attribute name', dtype_out=('str',), doc_out='Str[0] = Tango class name\nStr[1] = Attribute property number\nStr[2] = Attribute property 1 name\nStr[3] = Attribute property 1 value number (array case)\nStr[4] = Attribute property 1 value\nStr[n] = Attribute property 1 value (array case)\nStr[n + 1] = Attribute property 2 name\nStr[n + 2] = Attribute property 2 value number (array case)\nStr[n + 3] = Attribute property 2 value\nStr[n + m] = Attribute property 2 value (array case)') def DbGetClassAttributeProperty2(self, argin): """ This command supports array property compared to the old command called DbGetClassAttributeProperty. The old command has not been deleted from the server for compatibility reasons. :param argin: Str[0] = Tango class name Str[1] = Attribute name Str[n] = Attribute name :type: tango.DevVarStringArray :return: Str[0] = Tango class name Str[1] = Attribute property number Str[2] = Attribute property 1 name Str[3] = Attribute property 1 value number (array case) Str[4] = Attribute property 1 value Str[n] = Attribute property 1 value (array case) Str[n + 1] = Attribute property 2 name Str[n + 2] = Attribute property 2 value number (array case) Str[n + 3] = Attribute property 2 value Str[n + m] = Attribute property 2 value (array case) :rtype: tango.DevVarStringArray """ self._log.debug("In DbGetClassAttributeProperty2()") class_name = argin[0] return self.db.get_class_attribute_property2(class_name, argin[1:]) @stats @command(dtype_in='str', doc_in='filter', dtype_out=('str',), doc_out='list of exported devices') def DbGetDeviceExportedList(self, argin): """ Get a list of exported devices whose names satisfy the filter (wildcard is :param argin: filter :type: tango.DevString :return: list of exported devices :rtype: tango.DevVarStringArray """ self._log.debug("In DbGetDeviceExportedList()") argin = replace_wildcard(argin) return self.db.get_device_exported_list(argin) @command(dtype_in='str', doc_in='The device name', dtype_out='str', doc_out='The alias found') def DbGetDeviceAlias(self, argin): """ Return alias for device name if found. :param argin: The device name :type: tango.DevString :return: The alias found :rtype: tango.DevString """ self._log.debug("In DbGetDeviceAlias()") ret, dev_name, dfm = check_device_name(argin) if not ret: th_exc(DB_IncorrectDeviceName, "device name (" + argin + ") syntax error (should be [tango:][//instance/]domain/family/member)", "DataBase::DbGetDeviceAlias()") return self.db.get_device_alias(dev_name) @command(dtype_in=('str',), doc_in='Str[0] = Tango class name\nStr[1] = Attribute number\nStr[2] = Attribute name\nStr[3] = Property number\nStr[4] = Property name\nStr[5] = Property value\n.....', doc_out='none') def DbPutClassAttributeProperty(self, argin): """ Create/Update class attribute property(ies) in database :param argin: Str[0] = Tango class name Str[1] = Attribute number Str[2] = Attribute name Str[3] = Property number Str[4] = Property name Str[5] = Property value ..... :type: tango.DevVarStringArray :return: :rtype: tango.DevVoid """ self._log.debug("In DbPutClassAttributeProperty()") class_name = argin[0] nb_attributes = int(argin[1]) self.db.put_class_attribute_property(class_name, nb_attributes, argin[2:]) @stats @command(dtype_in='str', doc_in='The filter', dtype_out=('str',), doc_out='Property name list') def DbGetClassPropertyList(self, argin): """ Get property list for a given Tango class with a specified filter :param argin: The filter :type: tango.DevString :return: Property name list :rtype: tango.DevVarStringArray """ self._log.debug("In DbGetClassPropertyList()") if not argin: argin = "%" else: argin = replace_wildcard(argin) return self.db.get_class_property_list(argin) @command(dtype_in='str', doc_in='The filter', dtype_out=('str',), doc_out='Device alias list') def DbGetDeviceAliasList(self, argin): """ Get device alias name with a specific filter :param argin: The filter :type: tango.DevString :return: Device alias list :rtype: tango.DevVarStringArray """ self._log.debug("In DbGetDeviceAliasList()") if not argin: argin = "%" else: argin = replace_wildcard(argin) return self.db.get_device_alias_list(argin) @command(dtype_in=('str',), doc_in='Str[0] = Tango class name\nStr[1] = Attribute name', doc_out='none') def DbDeleteClassAttribute(self, argin): """ delete a class attribute and all its properties from database :param argin: Str[0] = Tango class name Str[1] = Attribute name :type: tango.DevVarStringArray :return: :rtype: tango.DevVoid """ self._log.debug("In DbDeleteClassAttribute()") if len(argin) < 2: self.warn_stream("DataBase::db_delete_class_attribute(): insufficient number of arguments ") th_exc(DB_IncorrectArguments, "insufficient number of arguments to delete class attribute", "DataBase::DeleteClassAttribute()") klass_name, attr_name = argin[:2] self.db.delete_class_attribute(klass_name, attr_name) @command(dtype_in=('str',), doc_in='Str[0] = Tango class\nStr[1] = Property name', dtype_out=('str',), doc_out='Str[0] = Property name\nStr[1] = date\nStr[2] = Property value number (array case)\nStr[3] = Property value 1\nStr[n] = Property value n') def DbGetClassPropertyHist(self, argin): """ Retrieve Tango class property history :param argin: Str[0] = Tango class Str[1] = Property name :type: tango.DevVarStringArray :return: Str[0] = Property name Str[1] = date Str[2] = Property value number (array case) Str[3] = Property value 1 Str[n] = Property value n :rtype: tango.DevVarStringArray """ self._log.debug("In DbGetClassPropertyHist()") class_name = argin[0] prop_name = argin[1] return self.db.get_class_property_hist(class_name, prop_name) @command(dtype_in=('str',), doc_in='Str[0] = Device name\nStr[1] = Attribute name', doc_out='none') def DbDeleteDeviceAttribute(self, argin): """ Delete device attribute properties from database :param argin: Str[0] = Device name Str[1] = Attribute name :type: tango.DevVarStringArray :return: :rtype: tango.DevVoid """ self._log.debug("In DbDeleteDeviceAttribute()") if len(argin) < 2: self.warn_stream("DataBase::db_delete_device_attribute(): insufficient number of arguments ") th_exc(DB_IncorrectArguments, "insufficient number of arguments to delete device attribute", "DataBase::DeleteDeviceAttribute()") dev_name, attr_name = argin[:2] ret, dev_name, dfm = check_device_name(argin) if not ret: self.warn_stream("DataBase::db_delete_device_attribute(): device name " + argin + " incorrect ") th_exc(DB_IncorrectDeviceName, "failed to delete device attribute, device name incorrect", "DataBase::DeleteDeviceAttribute()") self.db.delete_device_attribute(dev_name, attr_name) @stats @command(dtype_in='str', doc_in='MySql Select command', dtype_out='DevVarLongStringArray', doc_out='MySql Select command result\n - svalues : select results\n - lvalue[n] : =0 if svalue[n] is null else =1\n (last lvalue -1) is number of rows, (last lvalue) is number of fields') def DbMySqlSelect(self, argin): """ This is a very low level command. It executes the specified SELECT command on TANGO database and returns its result without filter. :param argin: MySql Select command :type: tango.DevString :return: MySql Select command result - svalues : select results - lvalue[n] : =0 if svalue[n] is null else =1 (last lvalue -1) is number of rows, (last lvalue) is number of fields :rtype: tango.DevVarLongStringArray """ self._log.debug("In DbMySqlSelect()") tmp_argin = argin.lower() # Check if SELECT key is alread inside command cmd = argin tmp_argin = argin.lower() pos = tmp_argin.find('select') if pos == -1: cmd = "SELECT " + cmd pos = tmp_argin.find(';') if pos != -1 and len(tmp_argin) > (pos + 1): th_exc(DB_IncorrectArguments, "SQL command not valid: " + argin, "DataBase::ExportDevice()") return self.db.my_sql_select(cmd) @stats @command(dtype_in=('str',), doc_in='Str[0] = Device name\nStr[1] = Attribute number\nStr[2] = Attribute name\nStr[3] = Property number\nStr[4] = Property name\nStr[5] = Property value\n.....', doc_out='none') def DbPutDeviceAttributeProperty(self, argin): """ Create/Update device attribute property(ies) in database :param argin: Str[0] = Device name Str[1] = Attribute number Str[2] = Attribute name Str[3] = Property number Str[4] = Property name Str[5] = Property value ..... :type: tango.DevVarStringArray :return: :rtype: tango.DevVoid """ self._log.debug("In DbPutDeviceAttributeProperty()") device_name = argin[0] nb_attributes = int(argin[1]) self.db.put_device_attribute_property(device_name, nb_attributes, argin[2:]) @stats @command(dtype_in=('str',), doc_in='Str[0] = Device name\nStr[1] = Attribute name\nStr[n] = Attribute name', dtype_out=('str',), doc_out='Str[0] = Device name\nStr[1] = Attribute property number\nStr[2] = Attribute property 1 name\nStr[3] = Attribute property 1 value\nStr[n + 1] = Attribute property 2 name\nStr[n + 2] = Attribute property 2 value') def DbGetDeviceAttributeProperty(self, argin): """ Get device attribute property(ies) value :param argin: Str[0] = Device name Str[1] = Attribute name Str[n] = Attribute name :type: tango.DevVarStringArray :return: Str[0] = Device name Str[1] = Attribute property number Str[2] = Attribute property 1 name Str[3] = Attribute property 1 value Str[n + 1] = Attribute property 2 name Str[n + 2] = Attribute property 2 value :rtype: tango.DevVarStringArray """ self._log.debug("In DbGetDeviceAttributeProperty()") dev_name = argin[0] return self.db.get_device_attribute_property(dev_name, argin[1:]) @stats @command(dtype_in=('str',), doc_in='Str[0] = Object name\nStr[1] = filter', dtype_out=('str',), doc_out='Property name list') def DbGetPropertyList(self, argin): """ Get list of property defined for a free object and matching the specified filter :param argin: Str[0] = Object name Str[1] = filter :type: tango.DevVarStringArray :return: Property name list :rtype: tango.DevVarStringArray """ self._log.debug("In DbGetPropertyList()") object_name = argin[0] wildcard = replace_wildcard(argin[1]) return self.db.get_property_list(object_name, wildcard) @stats @command(dtype_in='str', doc_in='Device server process name', dtype_out=('str',), doc_out='Str[0] = Device name\nStr[1] = Tango class\nStr[n] = Device name\nStr[n + 1] = Tango class') def DbGetDeviceClassList(self, argin): """ Get Tango classes/device list embedded in a specific device server :param argin: Device server process name :type: tango.DevString :return: Str[0] = Device name Str[1] = Tango class Str[n] = Device name Str[n + 1] = Tango class :rtype: tango.DevVarStringArray """ self._log.debug("In DbGetDeviceClassList()") return self.db.get_device_class_list(argin) @command(dtype_in='str', doc_in='Device name', doc_out='none') def DbUnExportDevice(self, argin): """ Mark a device as non exported in database :param argin: Device name :type: tango.DevString :return: :rtype: tango.DevVoid """ self._log.debug("In DbUnExportDevice()") dev_name = argin[0].lower() self.db.unexport_device(dev_name) @command(dtype_in='str', doc_in='Alias name', dtype_out='str', doc_out='Device name') def DbGetAliasDevice(self, argin): """ Get device name from its alias. :param argin: Alias name :type: tango.DevString :return: Device name :rtype: tango.DevString """ self._log.debug("In DbGetAliasDevice()") if not argin: argin = "%" else: argin = replace_wildcard(argin) return self.db.get_alias_device(argin) @command(dtype_in='str', doc_in='device name', doc_out='none') def DbDeleteDevice(self, argin): """ Delete a devcie from database :param argin: device name :type: tango.DevString :return: :rtype: tango.DevVoid """ self._log.debug("In DbDeleteDevice()") ret, dev_name, dfm = check_device_name(argin) if not ret: self.warn_stream("DataBase::db_delete_device(): device name " + argin + " incorrect ") th_exc(DB_IncorrectDeviceName, "failed to delete device, device name incorrect", "DataBase::DeleteDevice()") self.db.delete_device(dev_name) @command(dtype_in=('str',), doc_in='Str[0] = Device name\nStr[1] = Wildcard', dtype_out=('str',), doc_out='attribute name list') def DbGetDeviceAttributeList(self, argin): """ Return list of attributes matching the wildcard for the specified device :param argin: Str[0] = Device name Str[1] = Wildcard :type: tango.DevVarStringArray :return: attribute name list :rtype: tango.DevVarStringArray """ self._log.debug("In DbGetDeviceAttributeList()") dev_name = argin[0] wildcard = argin[1] if not wildcard: wildcard = "%" else: wildcard = replace_wildcard(wildcard) return self.db.get_device_attribute_list(dev_name, wildcard) @command(dtype_in='str', doc_in='Host name', dtype_out=('str',), doc_out='Server info for all servers running on specified host') def DbGetHostServersInfo(self, argin): """ Get info about all servers running on specified host, name, mode and level :param argin: Host name :type: tango.DevString :return: Server info for all servers running on specified host :rtype: tango.DevVarStringArray """ self._log.debug("In DbGetHostServersInfo()") argin = replace_wildcard(argin) return self.db.get_host_servers_info(argin) @command(dtype_in=('str',), doc_in='s[0] = old device server name (exec/instance)\ns[1] = new device server name (exec/instance)') def DbRenameServer(self, argin): """ Rename a device server process :param argin: str[0] = old device server name (exec/instance) str[1] = new device server name (exec/instance) :type: tango.DevVarStringArray :return: :rtype: tango.DevVoid """ self._log.debug("In DbRenameServer()") if len(argin) < 2: self.warn_stream("DataBase::DbRenameServer(): insufficient number of arguments ") th_exc(DB_IncorrectArguments, "insufficient number of arguments (two required: old name and new name", "DataBase::DbRenameServer") old_name = argin[0] new_name = argin[1] if ('/' not in argin[0]) or ('/' not in argin[1]): self.warn_stream("DataBase::DbRenameServer(): wrong syntax in command args ") th_exc(DB_IncorrectArguments, "Wrong syntax in command args (ds_exec_name/inst_name)", "DataBase::DbRenameServer") self.db.rename_server(old_name, new_name) @stats @command(dtype_in='str', doc_in='The filter', dtype_out=('str',), doc_out='Host name list') def DbGetHostList(self, argin): """ Get host list with name matching the specified filter :param argin: The filter :type: tango.DevString :return: Host name list :rtype: tango.DevVarStringArray """ self._log.debug("In DbGetHostList()") argin = replace_wildcard(argin) return self.db.get_host_list(argin) @command(dtype_in='str', doc_in='Device name', dtype_out=('str',), doc_out='Classes off the specified device.\n[0] - is the class of the device.\n[1] - is the class from the device class is inherited.\n........and so on') def DbGetClassInheritanceForDevice(self, argin): """ Get class inheritance for the specified device. :param argin: Device name :type: tango.DevString :return: Classes off the specified device. [0] - is the class of the device. [1] - is the class from the device class is inherited. ........and so on :rtype: tango.DevVarStringArray """ self._log.debug("In DbGetClassInheritanceForDevice()") return self.db.get_class_inheritance_for_device(argin) @stats @command(dtype_in='str', doc_in='Device server name', doc_out='none') def DbDeleteServer(self, argin): """ Delete server from the database but dont delete device properties :param argin: Device server name :type: tango.DevString :return: :rtype: tango.DevVoid """ self._log.debug("In DbDeleteServer()") if '*' in argin or '%' in argin or not '/' in argin: self.warn_stream("DataBase::db_delete_server(): server name " + argin + " incorrect ") th_exc(DB_IncorrectServerName, "failed to delete server, server name incorrect", "DataBase::DeleteServer()") self.db.delete_server(argin) @command(dtype_in='str', doc_in='The attribute alias name', dtype_out='str', doc_out='The attribute name (device/attribute)') def DbGetAttributeAlias(self, argin): """ Get the attribute name for the given alias. If alias not found in database, returns an empty string. :param argin: The attribute alias name :type: tango.DevString :return: The attribute name (device/attribute) :rtype: tango.DevString """ self._log.debug("In DbGetAttributeAlias()") return self.db.get_attribute_alias(argin) @command(dtype_in=('str',), doc_in='Elt[0] = DS name (exec_name/inst_name), Elt[1] = Host name', dtype_out=('str',), doc_out='All the data needed by the device server during its startup sequence. Precise list depend on the device server') def DbGetDataForServerCache(self, argin): """ This command returns all the data needed by a device server process during its startup sequence. The aim of this command is to minimize database access during device server startup sequence. :param argin: Elt[0] = DS name (exec_name/inst_name), Elt[1] = Host name :type: tango.DevVarStringArray :return: All the data needed by the device server during its startup sequence. Precise list depend on the device server :rtype: tango.DevVarStringArray """ self._log.debug("In DbGetDataForServerCache()") ## TODO return [''] @command(dtype_in=('str',), doc_in='Str[0] = Object name\nStr[1] = Property name\nStr[n] = Property name', dtype_out=('str',), doc_out='Str[0] = Object name\nStr[1] = Property number\nStr[2] = Property name\nStr[3] = Property value number (array case)\nStr[4] = Property value 1\nStr[n] = Property value n (array case)\nStr[n + 1] = Property name\nStr[n + 2] = Property value number (array case)\nStr[n + 3] = Property value 1\nStr[n + m] = Property value m') def DbGetProperty(self, argin): """ Get free object property :param argin: Str[0] = Object name Str[1] = Property name Str[n] = Property name :type: tango.DevVarStringArray :return: Str[0] = Object name Str[1] = Property number Str[2] = Property name Str[3] = Property value number (array case) Str[4] = Property value 1 Str[n] = Property value n (array case) Str[n + 1] = Property name Str[n + 2] = Property value number (array case) Str[n + 3] = Property value 1 Str[n + m] = Property value m :rtype: tango.DevVarStringArray """ self._log.debug("In DbGetProperty()") object_name = argin[0] return self.db.get_property(object_name, argin[1:]) @command(dtype_in='str', doc_in='device server process name', dtype_out=('str',), doc_out='list of classes for this device server') def DbGetDeviceServerClassList(self, argin): """ Get list of Tango classes for a device server :param argin: device server process name :type: tango.DevString :return: list of classes for this device server :rtype: tango.DevVarStringArray """ self._log.debug("In DbGetDeviceServerClassList()") argin = replace_wildcard(argin) return self.db.get_server_class_list(argin) @stats @command(dtype_in=('str',), doc_in='Str[0] = Tango device name\nStr[1] = Property number\nStr[2] = Property name\nStr[3] = Property value number\nStr[4] = Property value 1\nStr[n] = Property value n\n....', doc_out='none') def DbPutDeviceProperty(self, argin): """ Create / Update device property(ies) :param argin: Str[0] = Tango device name Str[1] = Property number Str[2] = Property name Str[3] = Property value number Str[4] = Property value 1 Str[n] = Property value n .... :type: tango.DevVarStringArray :return: :rtype: tango.DevVoid """ self._log.debug("In DbPutDeviceProperty()") device_name = argin[0] nb_properties = int(argin[1]) self.db.put_device_property(device_name, nb_properties, argin[2:]) @command(doc_in='none', doc_out='none') def ResetTimingValues(self): """ Reset the timing attribute values. :param : :type: tango.DevVoid :return: :rtype: tango.DevVoid """ self._log.debug("In ResetTimingValues()") for tmp_timing in self.timing_maps.itervalues(): tmp_timing.average = 0. tmp_timing.minimum = 0. tmp_timing.maximum = 0. tmp_timing.total_elapsed = 0. tmp_timing.calls = 0. @command(doc_in='none', dtype_out=('str',), doc_out='List of host:port with one element for each database server') def DbGetCSDbServerList(self): """ Get a list of host:port for all database server defined in the control system :param : :type: tango.DevVoid :return: List of host:port with one element for each database server :rtype: tango.DevVarStringArray """ self._log.debug("In DbGetCSDbServerList()") return self.db.get_csdb_server_list() @stats @command(dtype_in=('str',), doc_in='Str[0] = Tango class name\nStr[1] = Property number\nStr[2] = Property name\nStr[3] = Property value number\nStr[4] = Property value 1\nStr[n] = Property value n\n....', doc_out='none') def DbPutClassProperty(self, argin): """ Create / Update class property(ies) :param argin: Str[0] = Tango class name Str[1] = Property number Str[2] = Property name Str[3] = Property value number Str[4] = Property value 1 Str[n] = Property value n .... :type: tango.DevVarStringArray :return: :rtype: tango.DevVoid """ self._log.debug("In DbPutClassProperty()") class_name = argin[0] nb_properties = int(argin[1]) self.db.put_class_property(class_name, nb_properties, argin[2:]) @stats @command(dtype_in='str', doc_in='Device name (or alias)', dtype_out='DevVarLongStringArray', doc_out='Str[0] = device name\nStr[1] = CORBA IOR\nStr[2] = device version\nStr[3] = device server process name\nStr[4] = host name\nStr[5] = Tango class name\n\nLg[0] = Exported flag\nLg[1] = Device server process PID') def DbImportDevice(self, argin): """ Import a device from the database :param argin: Device name (or alias) :type: tango.DevString :return: Str[0] = device name Str[1] = CORBA IOR Str[2] = device version Str[3] = device server process name Str[4] = host name Str[5] = Tango class name Lg[0] = Exported flag Lg[1] = Device server process PID :rtype: tango.DevVarLongStringArray """ self._log.debug("In DbImportDevice()") return self.db.import_device(argin.lower()) @stats @command(dtype_in=('str',), doc_in='Str[0] = Device name\nStr[1] = Property name\nStr[n] = Property name', doc_out='none') def DbDeleteDeviceProperty(self, argin): """ Delete device property(ies) :param argin: Str[0] = Device name Str[1] = Property name Str[n] = Property name :type: tango.DevVarStringArray :return: :rtype: tango.DevVoid """ self._log.debug("In DbDeleteDeviceProperty()") dev_name = argin[0] for prop_name in argin[1:]: self.db.delete_device_property(dev_name, prop_name) @command(dtype_in='str', doc_in='Device name', dtype_out='str', doc_out='Device Tango class') def DbGetClassForDevice(self, argin): """ Get Tango class for the specified device. :param argin: Device name :type: tango.DevString :return: Device Tango class :rtype: tango.DevString """ self._log.debug("In DbGetClassForDevice()") return self.db.get_class_for_device(argin) @command(dtype_in=('str',), doc_in='Str[0] = Device name\nStr[1] = Attribute name\nStr[2] = Property name', dtype_out=('str',), doc_out='Str[0] = Attribute name\nStr[1] = Property name\nStr[2] = date\nStr[3] = Property value number (array case)\nStr[4] = Property value 1\nStr[n] = Property value n') def DbGetDeviceAttributePropertyHist(self, argin): """ Retrieve device attribute property history :param argin: Str[0] = Device name Str[1] = Attribute name Str[2] = Property name :type: tango.DevVarStringArray :return: Str[0] = Attribute name Str[1] = Property name Str[2] = date Str[3] = Property value number (array case) Str[4] = Property value 1 Str[n] = Property value n :rtype: tango.DevVarStringArray """ self._log.debug("In DbGetDeviceAttributePropertyHist()") dev_name = argin[0] attribute = replace_wildcard(argin[1]) prop_name = replace_wildcard(argin[2]) return self.db.get_device_attribute_property_hist(dev_name, attribute, prop_name) @command(dtype_in='str', doc_in='server name', dtype_out=('str',), doc_out='server info') def DbGetServerInfo(self, argin): """ Get info about host, mode and level for specified server :param argin: server name :type: tango.DevString :return: server info :rtype: tango.DevVarStringArray """ self._log.debug("In DbGetServerInfo()") return self.db.get_server_info(argin) @command(dtype_in=('str',), doc_in='Str[0] = device name\nStr[1] = alias name', doc_out='none') def DbPutDeviceAlias(self, argin): """ Define alias for a given device name :param argin: Str[0] = device name Str[1] = alias name :type: tango.DevVarStringArray :return: :rtype: tango.DevVoid """ self._log.debug("In DbPutDeviceAlias()") if len(argin) < 2: self.warn_stream("DataBase::DbPutDeviceAlias(): insufficient number of arguments ") th_exc(DB_IncorrectArguments, "insufficient number of arguments to put device alias", "DataBase::DbPutDeviceAlias()") device_name = argin[0] device_alias = argin[1] self.db.put_device_alias(device_name, device_alias) @stats @command(dtype_in=('str',), doc_in='Str[0] = device name\nStr[1] = Filter', dtype_out=('str',), doc_out='Property name list') def DbGetDevicePropertyList(self, argin): """ Get property list belonging to the specified device and with name matching the specified filter :param argin: Str[0] = device name Str[1] = Filter :type: tango.DevVarStringArray :return: Property name list :rtype: tango.DevVarStringArray """ self._log.debug("In DbGetDevicePropertyList()") device_name = argin[0] prop_filter = argin[1] prop_filter = replace_wildcard(prop_filter) return self.db.get_device_property_list(device_name, prop_filter) @stats @command(dtype_in='str', doc_in='The filter', dtype_out=('str',), doc_out='Device server process name list') def DbGetHostServerList(self, argin): """ Get list of device server process name running on host with name matching the specified filter :param argin: The filter :type: tango.DevString :return: Device server process name list :rtype: tango.DevVarStringArray """ self._log.debug("In DbGetHostServerList()") argin = replace_wildcard(argin) return self.db.get_host_server_list(argin) @command(dtype_in=('str',), doc_in='Str[0] = Tango class\nStr[1] = Property name\nStr[2] = Property name', dtype_out=('str',), doc_out='Str[0] = Tango class\nStr[1] = Property number\nStr[2] = Property name\nStr[3] = Property value number (array case)\nStr[4] = Property value\nStr[n] = Propery value (array case)\n....') def DbGetClassProperty(self, argin): """ :param argin: Str[0] = Tango class Str[1] = Property name Str[2] = Property name :type: tango.DevVarStringArray :return: Str[0] = Tango class Str[1] = Property number Str[2] = Property name Str[3] = Property value number (array case) Str[4] = Property value Str[n] = Propery value (array case) .... :rtype: tango.DevVarStringArray """ self._log.debug("In DbGetClassProperty()") class_name = argin[0] return self.db.get_class_property(class_name,argin[1:]) @command(dtype_in='str', doc_in='The filter', dtype_out=('str',), doc_out='Object name list') def DbGetObjectList(self, argin): """ Get list of free object defined in database with name matching the specified filter :param argin: The filter :type: tango.DevString :return: Object name list :rtype: tango.DevVarStringArray """ self._log.debug("In DbGetObjectList()") argin = replace_wildcard(argin) return self.db.get_object_list(argin) @command(dtype_in=('str',), doc_in='Str[0] = Tango class name\nStr[1] = Attribute name\nStr[2] = Property name\nStr[n] = Property name', doc_out='none') def DbDeleteClassAttributeProperty(self, argin): """ delete class attribute properties from database :param argin: Str[0] = Tango class name Str[1] = Attribute name Str[2] = Property name Str[n] = Property name :type: tango.DevVarStringArray :return: :rtype: tango.DevVoid """ self._log.debug("In DbDeleteClassAttributeProperty()") if len(argin) < 3: self.warn_stream("DataBase::db_delete_class_attribute_property(): insufficient number of arguments ") th_exc(DB_IncorrectArguments, "insufficient number of arguments to delete class attribute property", "DataBase::DeleteClassAttributeProperty()") klass_name, attr_name = argin[:2] for prop_name in argin[2:]: self.db.delete_class_attribute_property(klass_name, attr_name, prop_name) @command(dtype_in='str', doc_in='Server name', dtype_out=('str',), doc_out='The instance names found for specified server.') def DbGetInstanceNameList(self, argin): """ Returns the instance names found for specified server. :param argin: Server name :type: tango.DevString :return: The instance names found for specified server. :rtype: tango.DevVarStringArray """ self._log.debug("In DbGetInstanceNameList()") return self.db.get_instance_name_list(argin) @command(dtype_in='str', doc_in='The attribute name (dev_name/att_name)', dtype_out='str', doc_out='The attribute alias name (or empty string)') def DbGetAttributeAlias2(self, argin): """ Get the attribute alias from the attribute name. Returns one empty string if nothing found in database :param argin: The attribute name (dev_name/att_name) :type: tango.DevString :return: The attribute alias name (or empty string) :rtype: tango.DevString """ self._log.debug("In DbGetAttributeAlias2()") attr_name = argin[0] return self.db.get_attribute_alias2(attr_name) @command(dtype_in=('str',), doc_in='Str[0] = Full device server name\nStr[1] = Device(s) name\nStr[2] = Tango class name\nStr[n] = Device name\nStr[n + 1] = Tango class name', doc_out='none') def DbAddServer(self, argin): """ Create a device server process entry in database :param argin: Str[0] = Full device server name Str[1] = Device(s) name Str[2] = Tango class name Str[n] = Device name Str[n + 1] = Tango class name :type: tango.DevVarStringArray :return: :rtype: tango.DevVoid """ self._log.debug("In DbAddServer()") if len(argin) < 3 or not len(argin) % 2: self.warn_stream("DataBase::AddServer(): incorrect number of input arguments ") th_exc(DB_IncorrectArguments, "incorrect no. of input arguments, needs at least 3 (server,device,class)", "DataBase::AddServer()") server_name = argin[0] for i in range((len(argin) - 1) / 2): d_name, klass_name = argin[i * 2 + 1], argin[i * 2 + 2] ret, dev_name, dfm = check_device_name(d_name) if not ret: th_exc(DB_IncorrectDeviceName, "device name (" + d_name + ") syntax error (should be [tango:][//instance/]domain/family/member)", "DataBase::AddServer()") self.db.add_device(server_name, (dev_name, dfm) , klass_name) @stats @command(dtype_in='str', doc_in='name of event channel or factory', dtype_out='DevVarLongStringArray', doc_out='export information e.g. IOR') def DbImportEvent(self, argin): """ Get event channel info from database :param argin: name of event channel or factory :type: tango.DevString :return: export information e.g. IOR :rtype: tango.DevVarLongStringArray """ self._log.debug("In DbImportEvent()") argin = replace_wildcard(argin.lower()) return self.db.import_event(argin) @command(dtype_in=('str',), doc_in='Str[0] = Device name\nStr[2] = Property name', dtype_out=('str',), doc_out='Str[0] = Property name\nStr[1] = date\nStr[2] = Property value number (array case)\nStr[3] = Property value 1\nStr[n] = Property value n') def DbGetDevicePropertyHist(self, argin): """ Retrieve device property history :param argin: Str[0] = Device name Str[1] = Property name :type: tango.DevVarStringArray :return: Str[0] = Property name Str[1] = date Str[2] = Property value number (array case) Str[3] = Property value 1 Str[n] = Property value n :rtype: tango.DevVarStringArray """ self._log.debug("In DbGetDevicePropertyHist()") device_name = argin[0] prop_name = argin[1] return self.db.get_device_property_hist(device_name, prop_name) @command(dtype_in='str', doc_in='wildcard for server names.', dtype_out=('str',), doc_out='server names found.') def DbGetServerNameList(self, argin): """ Returns the list of server names found for the wildcard specified. It returns only the server executable name without instance name as DbGetServerList. :param argin: wildcard for server names. :type: tango.DevString :return: server names found. :rtype: tango.DevVarStringArray """ self._log.debug("In DbGetServerNameList()") argin = replace_wildcard(argin) return self.db.get_server_name_list(argin) @stats @command(dtype_in=('str',), doc_in='Str[0] = Device name\nStr[1] = Attribute name\nStr[n] = Attribute name', dtype_out=('str',), doc_out='Str[0] = Device name\nStr[1] = Attribute property number\nStr[2] = Attribute property 1 name\nStr[3] = Attribute property 1 value number (array case)\nStr[4] = Attribute property 1 value\nStr[n] = Attribute property 1 value (array case)\nStr[n + 1] = Attribute property 2 name\nStr[n + 2] = Attribute property 2 value number (array case)\nStr[n + 3] = Attribute property 2 value\nStr[n + m] = Attribute property 2 value (array case)') def DbGetDeviceAttributeProperty2(self, argin): """ Retrieve device attribute properties. This command has the possibility to retrieve device attribute properties which are arrays. It is not possible with the old DbGetDeviceAttributeProperty command. Nevertheless, the old command has not been deleted for compatibility reason :param argin: Str[0] = Device name Str[1] = Attribute name Str[n] = Attribute name :type: tango.DevVarStringArray :return: Str[0] = Device name Str[1] = Attribute property number Str[2] = Attribute property 1 name Str[3] = Attribute property 1 value number (array case) Str[4] = Attribute property 1 value Str[n] = Attribute property 1 value (array case) Str[n + 1] = Attribute property 2 name Str[n + 2] = Attribute property 2 value number (array case) Str[n + 3] = Attribute property 2 value Str[n + m] = Attribute property 2 value (array case) :rtype: tango.DevVarStringArray """ self._log.debug("In DbGetDeviceAttributeProperty2()") dev_name = argin[0] return self.db.get_device_attribute_property2(dev_name, argin[1:]) @command(dtype_in=('str',), doc_in='Str[0] = Tango class name\nStr[1] = Property name\nStr[n] = Property name', doc_out='none') def DbDeleteClassProperty(self, argin): """ Delete class properties from database :param argin: Str[0] = Tango class name Str[1] = Property name Str[n] = Property name :type: tango.DevVarStringArray :return: :rtype: tango.DevVoid """ self._log.debug("In DbDeleteClassProperty()") klass_name = argin[0] for prop_name in argin[1:]: self.db.delete_class_property(prop_name) @command(dtype_in='str', doc_in='name of event channel or factory to unexport', doc_out='none') def DbUnExportEvent(self, argin): """ Mark one event channel as non exported in database :param argin: name of event channel or factory to unexport :type: tango.DevString :return: none :rtype: tango.DevVoid """ self._log.debug("In DbUnExportEvent()") event_name = argin[0].lower() self.db.unexport_event(event_name) @stats @command(doc_in='none', dtype_out=('str',), doc_out='Miscellaneous info like:\n- Device defined in database\n- Device marked as exported in database\n- Device server process defined in database\n- Device server process marked as exported in database\n- Device properties defined in database\n- Class properties defined in database\n- Device attribute properties defined in database\n- Class attribute properties defined in database\n- Object properties defined in database') def DbInfo(self): """ Get miscellaneous numbers on information stored in database :param : :type: tango.DevVoid :return: Miscellaneous info like: - Device defined in database - Device marked as exported in database - Device server process defined in database - Device server process marked as exported in database - Device properties defined in database - Class properties defined in database - Device attribute properties defined in database - Class attribute properties defined in database - Object properties defined in database :rtype: tango.DevVarStringArray """ self._log.debug("In DbInfo()") return self.db.info() @command(dtype_in=('str',), doc_in='Str[0] = Tango class name\nStr[1] = Attribute name\nStr[n] = Attribute name', dtype_out=('str',), doc_out='Str[0] = Tango class name\nStr[1] = Attribute property number\nStr[2] = Attribute property 1 name\nStr[3] = Attribute property 1 value\nStr[n + 1] = Attribute property 2 name\nStr[n + 2] = Attribute property 2 value') def DbGetClassAttributeProperty(self, argin): """ Get Tango class property(ies) value :param argin: Str[0] = Tango class name Str[1] = Attribute name Str[n] = Attribute name :type: tango.DevVarStringArray :return: Str[0] = Tango class name Str[1] = Attribute property number Str[2] = Attribute property 1 name Str[3] = Attribute property 1 value Str[n + 1] = Attribute property 2 name Str[n + 2] = Attribute property 2 value :rtype: tango.DevVarStringArray """ self._log.debug("In DbGetClassAttributeProperty()") class_name = argin[0] return self.db.get_class_attribute_property(class_name, argin[1:]) @command(dtype_in=('str',), doc_in='Str[0] = Tango class name\nStr[1] = Attribute number\nStr[2] = Attribute name\nStr[3] = Property number\nStr[4] = Property name\nStr[5] = Property value number (array case)\nStr[5] = Property value 1\nStr[n] = Property value n (array case)\n.....', doc_out='none') def DbPutClassAttributeProperty2(self, argin): """ This command adds support for array properties compared to the previous one called DbPutClassAttributeProperty. The old comman is still there for compatibility reason :param argin: Str[0] = Tango class name Str[1] = Attribute number Str[2] = Attribute name Str[3] = Property number Str[4] = Property name Str[5] = Property value number (array case) Str[5] = Property value 1 Str[n] = Property value n (array case) ..... :type: tango.DevVarStringArray :return: :rtype: tango.DevVoid """ self._log.debug("In DbPutClassAttributeProperty2()") class_name = argin[0] nb_attributes = int(argin[1]) self.db.put_class_attribute_property2(class_name, nb_attributes, argin[2:]) @command(dtype_in=('str',), doc_in='server info', doc_out='none') def DbPutServerInfo(self, argin): """ Update server info including host, mode and level :param argin: server info :type: tango.DevVarStringArray :return: :rtype: tango.DevVoid """ self._log.debug("In DbPutServerInfo()") if len(argin) < 4: self.warn_stream("DataBase::DbPutServerInfo(): insufficient number of arguments ") th_exc(DB_IncorrectArguments, "insufficient server info", "DataBase::DbPutServerInfo()") tmp_server = argin[0].lower() tmp_host = argin[1] tmp_mode = argin[2] tmp_level = argin[3] tmp_extra = [] if len(argin) > 4: tmp_extra = argin[4:] tmp_len = len(argin) - 1 self.db.put_server_info(tmp_server, tmp_host, tmp_mode, tmp_level, tmp_extra) @command(dtype_in='str', doc_in='device alias name', doc_out='none') def DbDeleteDeviceAlias(self, argin): """ Delete a device alias. :param argin: device alias name :type: tango.DevString :return: :rtype: tango.DevVoid """ self._log.debug("In DbDeleteDeviceAlias()") self.db.delete_device_alias(argin) @stats @command(dtype_in=('str',), doc_in='Str[0] = event channel name (or factory name)\nStr[1] = CORBA IOR\nStr[2] = Notifd host name\nStr[3] = Notifd pid\nStr[4] = Notifd version', doc_out='none') def DbExportEvent(self, argin): """ Export Event channel to database :param argin: Str[0] = event channel name (or factory name) Str[1] = CORBA IOR Str[2] = Notifd host name Str[3] = Notifd pid Str[4] = Notifd version :type: tango.DevVarStringArray :return: :rtype: tango.DevVoid """ self._log.debug("In DbExportEvent()") if len(argin) < 5: self.warn_stream("DataBase::db_export_event(): insufficient export info for event ") th_exc(DB_IncorrectArguments, "insufficient export info for event", "DataBase::ExportEvent()") event, IOR, host, pid, version = argin[:5] event = replace_wildcard(event.lower()) self.db.export_event(event, IOR, host, pid, version) @stats @command(dtype_in=('str',), doc_in='Str[0] = Device name\nStr[1] = Property name\nStr[n] = Property name', dtype_out=('str',), doc_out='Str[0] = Device name\nStr[1] = Property number\nStr[2] = Property name\nStr[3] = Property value number (array case)\nStr[4] = Property value 1\nStr[n] = Property value n (array case)\nStr[n + 1] = Property name\nStr[n + 2] = Property value number (array case)\nStr[n + 3] = Property value 1\nStr[n + m] = Property value m') def DbGetDeviceProperty(self, argin): """ :param argin: Str[0] = Device name Str[1] = Property name Str[n] = Property name :type: tango.DevVarStringArray :return: Str[0] = Device name Str[1] = Property number Str[2] = Property name Str[3] = Property value number (array case) Str[4] = Property value 1 Str[n] = Property value n (array case) Str[n + 1] = Property name Str[n + 2] = Property value number (array case) Str[n + 3] = Property value 1 Str[n + m] = Property value m :rtype: tango.DevVarStringArray """ self._log.debug("In DbGetDeviceProperty()") device_name = argin[0] return self.db.get_device_property(device_name, argin[1:]) @command(dtype_in='str', doc_in='Device name', dtype_out='DevVarLongStringArray', doc_out='Str[0] = Device name\nStr[1] = CORBA IOR\nStr[2] = Device version\nStr[3] = Device Server name\nStr[4] = Device Server process host name\nStr[5] = Started date (or ? if not set)\nStr[6] = Stopped date (or ? if not set)\nStr[7] = Device class\n\nLg[0] = Device exported flag\nLg[1] = Device Server process PID (or -1 if not set)') def DbGetDeviceInfo(self, argin): """ Returns info from DbImportDevice and started/stopped dates. :param argin: Device name :type: tango.DevString :return: Str[0] = Device name Str[1] = CORBA IOR Str[2] = Device version Str[3] = Device Server name Str[4] = Device Server process host name Str[5] = Started date (or ? if not set) Str[6] = Stopped date (or ? if not set) Str[7] = Device class Lg[0] = Device exported flag Lg[1] = Device Server process PID (or -1 if not set) :rtype: tango.DevVarLongStringArray """ self._log.debug("In DbGetDeviceInfo()") ret, dev_name, dfm = check_device_name(argin) if not ret: th_exc(DB_IncorrectDeviceName, "device name (" + argin + ") syntax error (should be [tango:][//instance/]domain/family/member)", "DataBase::DbGetDeviceAlias()") return self.db.get_device_info(dev_name) @command(dtype_in=('str',), doc_in='Str[0] = Object name\nStr[2] = Property name', dtype_out=('str',), doc_out='Str[0] = Property name\nStr[1] = date\nStr[2] = Property value number (array case)\nStr[3] = Property value 1\nStr[n] = Property value n') def DbGetPropertyHist(self, argin): """ Retrieve object property history :param argin: Str[0] = Object name Str[2] = Property name :type: tango.DevVarStringArray :return: Str[0] = Property name Str[1] = date Str[2] = Property value number (array case) Str[3] = Property value 1 Str[n] = Property value n :rtype: tango.DevVarStringArray """ self._log.debug("In DbGetPropertyHist()") object_name = argin[0] prop_name = argin[1] return self.db.get_property_hist(object_name, prop_name) @stats @command(dtype_in='str', doc_in='The filter', dtype_out=('str',), doc_out='Device names member list') def DbGetDeviceMemberList(self, argin): """ Get a list of device name members for device name matching the specified filter :param argin: The filter :type: tango.DevString :return: Device names member list :rtype: tango.DevVarStringArray """ self._log.debug("In DbGetDeviceMemberList()") argin = replace_wildcard(argin) return self.db.get_device_member_list(argin) @command(dtype_in='str', doc_in='Filter', dtype_out=('str',), doc_out='Class list') def DbGetClassList(self, argin): """ Get Tango class list with a specified filter :param argin: Filter :type: tango.DevString :return: Class list :rtype: tango.DevVarStringArray """ self._log.debug("In DbGetClassList()") server = replace_wildcard(argin) return self.db.get_class_list(server) @command(dtype_in='str', doc_in='The attribute alias', dtype_out='str', doc_out='The attribute name (dev_name/att_name)') def DbGetAliasAttribute(self, argin): """ Get the attribute name from the given alias. If the given alias is not found in database, returns an empty string :param argin: The attribute alias :type: tango.DevString :return: The attribute name (dev_name/att_name) :rtype: tango.DevString """ self._log.debug("In DbGetAliasAttribute()") alias_name = argin[0] return self.db.get_alias_attribute(alias_name) @command(dtype_in='str', doc_in='Device server name', doc_out='none') def DbDeleteServerInfo(self, argin): """ delete info related to a Tango devvice server process :param argin: Device server name :type: tango.DevString :return: :rtype: tango.DevVoid """ self._log.debug("In DbDeleteServerInfo()") self.db.delete_server_info(argin) @command(dtype_in=('str',), doc_in='Str[0] = Tango class name\nStr[1] = Attribute name filter (eg: att*)', dtype_out=('str',), doc_out='Str[0] = Class attribute name\nStr[n] = Class attribute name') def DbGetClassAttributeList(self, argin): """ Get attrilute list for a given Tango class with a specified filter :param argin: Str[0] = Tango class name Str[1] = Attribute name filter (eg: att*) :type: tango.DevVarStringArray :return: Str[0] = Class attribute name Str[n] = Class attribute name :rtype: tango.DevVarStringArray """ self._log.debug("In DbGetClassAttributeList()") class_name = argin[0] wildcard = replace_wildcard(argin[1]) return self.db.get_class_attribute_list(class_name, wildcard) @command(dtype_in=('str',), doc_in='Str[0] = Full device server process name\nStr[1] = Device name\nStr[2] = Tango class name', doc_out='none') def DbAddDevice(self, argin): """ Add a Tango class device to a specific device server :param argin: Str[0] = Full device server process name Str[1] = Device name Str[2] = Tango class name :type: tango.DevVarStringArray :return: :rtype: tango.DevVoid """ self._log.debug("In DbAddDevice()") if len(argin) < 3: self.warn_stream("DataBase::AddDevice(): incorrect number of input arguments ") th_exc(DB_IncorrectArguments, "incorrect no. of input arguments, needs at least 3 (server,device,class)", "DataBase::AddDevice()") self.info_stream("DataBase::AddDevice(): insert %s server with device %s",argin[0],argin[1]) server_name, d_name, klass_name = argin[:3] if len(argin) > 3: alias = argin[3] else: alias = None ret, dev_name, dfm = check_device_name(d_name) if not ret: th_exc(DB_IncorrectDeviceName, "device name (" + d_name + ") syntax error (should be [tango:][//instance/]domain/family/member)", "DataBase::AddDevice()") # Lock table self.db.add_device(server_name, (dev_name, dfm) , klass_name, alias=alias) @command(dtype_in=('str',), doc_in='argin[0] : server name\nargin[1] : class name', dtype_out=('str',), doc_out='The list of devices for specified server and class.') def DbGetDeviceList(self, argin): """ Get a list of devices for specified server and class. :param argin: argin[0] : server name argin[1] : class name :type: tango.DevVarStringArray :return: The list of devices for specified server and class. :rtype: tango.DevVarStringArray """ self._log.debug("In DbGetDeviceList()") server_name = replace_wildcard(argin[0]) class_name = replace_wildcard(argin[1]) return self.db.get_device_list(server_name, class_name) # DbExportDevice is executed in the post_init_cb function below. # It needs to be separated from the actual device to prevent the device in # gevent mode to queue the request to the gevent thread and waitting for it. def DbExportDevice(self, argin): """ Export a device to the database :param argin: Str[0] = Device name Str[1] = CORBA IOR Str[2] = Device server process host name Str[3] = Device server process PID or string ``null`` Str[4] = Device server process version :type: tango.DevVarStringArray :return: :rtype: tango.DevVoid """ self._log.debug("In DbExportDevice()") if len(argin) < 5: self.warn_stream("DataBase::DbExportDevice(): insufficient export info for device ") th_exc(DB_IncorrectArguments, "insufficient export info for device", "DataBase::ExportDevice()") dev_name, IOR, host, pid, version = argin[:5] dev_name = dev_name.lower() if pid.lower() == 'null': pid = "-1" self.db.export_device(dev_name, IOR, host, pid, version) def main(argv = None): #Parameters management global options if argparse: parser = argparse.ArgumentParser() parser.add_argument("--db_access",dest="db_access",default="sqlite3", help="database type") parser.add_argument("-e", "--embedded",dest="embedded",default=False, action="store_true") parser.add_argument("--logging_level","-l",dest="logging_level",type=int, default=0,help="logging_level 0:WARNING,1:INFO,2:DEBUG") parser.add_argument("--port",dest="port",default=None, type=int, help="database port") parser.add_argument('argv',nargs=argparse.REMAINDER) options = parser.parse_args(argv) options.argv = ["DataBaseds"] + options.argv else: parser = OptionParser() parser.add_option("--db_access",dest="db_access",default="sqlite3", help="database type") parser.add_option("-l", "--logging_level",dest="logging_level",default=0, help="logging_level 0:WARNING,1:INFO,2:DEBUG") parser.add_option("-e","--embedded",dest="embedded",default=False, action="store_true") parser.add_option("--port",dest="port",default=10000, type=int, help="database port") (options,args) = parser.parse_args(argv) options.argv = ["DataBaseds"] + args port = options.port if port is None: try: _, port = tango.ApiUtil.get_env_var("TANGO_HOST").split(":") except: port = 10000 options.argv += ["-ORBendPoint", "giop:tcp::{0}".format(port)] log_fmt = '%(threadName)-14s %(levelname)-8s %(asctime)s %(name)s: %(message)s' if options.logging_level == 1: logging_level = logging.INFO elif options.logging_level == 2: logging_level = logging.DEBUG else: logging_level = logging.WARNING logging.basicConfig(format=log_fmt, stream=sys.stdout,level=logging_level) try: db_name = "sys/database/" + options.argv[1] set_db_name(db_name) if options.embedded: __run_embedded(db_name, options.argv) else: __run(db_name, options.argv) except Exception as e: import traceback traceback.print_exc() def __monkey_patch_database_class(): DataBaseClass = DataBase.TangoClassClass def device_factory(self, device_list): """for internal usage only""" dev_name = get_db_name() klass = self.__class__ klass_name = klass.__name__ info = get_class_by_class(klass) klass = get_constructed_class_by_class(klass) if info is None: raise RuntimeError("Device class '%s' is not " \ "registered" % klass_name) if klass is None: raise RuntimeError("Device class '%s' as not been " \ "constructed" % klass_name) deviceClassClass, deviceImplClass, deviceImplName = info deviceImplClass._device_class_instance = klass device = self._new_device(deviceImplClass, klass, dev_name) self._add_device(device) tmp_dev_list = [device] self.dyn_attr(tmp_dev_list) self.export_device(device, "database") self.py_dev_list += tmp_dev_list DataBaseClass.device_factory = device_factory def __monkey_patch_util(util): # trick util to execute orb_run instead of the usual server_run util._original_server_run = util.server_run util.server_run = util.orb_run def __run(db_name,argv): """ Runs the Database DS as a standalone database. Run it with:: ./DataBaseds pydb-test -ORBendPoint giop:tcp::11000 """ tango.Util.set_use_db(False) util = tango.Util(argv) __monkey_patch_util(util) __monkey_patch_database_class() dbi = DbInter() util.set_interceptors(dbi) def post_init_cb(): logging.debug("post_init_cb()") util = tango.Util.instance() dserver = util.get_dserver_device() dserver_name = dserver.get_name() dserver_ior = util.get_dserver_ior(dserver) dbase = util.get_device_by_name(db_name) dbase_name = dbase.get_name() dbase_ior = util.get_device_ior(dbase) host = util.get_host_name() pid = util.get_pid_str() version = util.get_version_str() DbExportDevice(dbase, [dserver_name, dserver_ior, host, pid, version]) DbExportDevice(dbase, [dbase_name, dbase_ior, host, pid, version]) run((DataBase,), args=argv, util=util, post_init_callback=post_init_cb, green_mode=GreenMode.Gevent, verbose=True) def __run_embedded(db_name,argv): """Runs the Database device server embeded in another TANGO Database (just like any other TANGO device server)""" __monkey_patch_database_class() run((DataBase,), args=argv, util=util, green_mode=GreenMode.Gevent) if __name__ == '__main__': main() pytango-9.2.2/tango/databaseds/db_access/000077500000000000000000000000001316324373100203215ustar00rootroot00000000000000pytango-9.2.2/tango/databaseds/db_access/__init__.py000066400000000000000000000007011316324373100224300ustar00rootroot00000000000000__all__ = [] def _init_module() : import os for root,dirs,files in os.walk(__path__[0]) : for file_name in files : if file_name.startswith('__') : continue base,ext = os.path.splitext(file_name) if ext == '.py' : subdir = root[len(__path__[0]) + 1:] if subdir: base = '%s.%s' % (subdir,base) __all__.append(base) _init_module() pytango-9.2.2/tango/databaseds/db_access/beacon.py000066400000000000000000001132631316324373100221300ustar00rootroot00000000000000from __future__ import print_function from __future__ import absolute_import import os import logging import functools import threading import Queue import re import weakref import datetime import tango th_exc = tango.Except.throw_exception from ..db_errors import * from bliss.config import static,settings import itertools if logging.getLogger().isEnabledFor(logging.INFO): def _info(funct) : def f(self,*args,**kwargs) : self._info("%s: %s %s", funct.__name__,args,kwargs) returnVal = funct(self,*args,**kwargs) if returnVal is not None: self._info("return %s : %s",funct.__name__,returnVal) else: self._info("return %s",funct.__name__) return returnVal return f else: def _info(funct): return funct def _filter(wildcard,l) : wildcard = wildcard.replace('*','.*') m = re.compile(wildcard,re.IGNORECASE) return [x for x in l if x is not None and m.match(x)] class beacon(object): DB_API_NAME = 'beacon' TANGO_ATTR_ALIAS = 'tango.attr.alias' def __init__(self, personal_name = "",**keys): self._config = static.Config('',3.) self._logger = logging.getLogger(self.__class__.__name__) self._debug = self._logger.debug self._info = self._logger.info self._warn = self._logger.warn self._error = self._logger.error self._critical = self._logger.critical self._index() #Trick to start self._beacon_dserver_node = static.Node(self._config) self._beacon_dserver_node['server'] = 'DataBaseds' self._beacon_dserver_node['personal_name'] = personal_name tango_name = 'sys/database/%s' % personal_name databse_device_node = static.Node(self._config,self._beacon_dserver_node) databse_device_node['class'] = 'DataBase' databse_device_node['tango_name'] = tango_name self._beacon_dserver_node['device'] = [databse_device_node] self._beacon_dserver_node['tango_name'] = tango_name self._tango_name_2_node[tango_name] = databse_device_node tango_name = 'dserver/databaseds/%s' % personal_name self._tango_name_2_node[tango_name] = self._beacon_dserver_node server_name = 'DataBaseds/%s' % personal_name self._personal_2_node[server_name] = self._beacon_dserver_node def _index(self): #Tango indexing self._strong_node_ref = set() self._personal_2_node = weakref.WeakValueDictionary() self._tango_name_2_node = weakref.WeakValueDictionary() self._class_name_2_node = weakref.WeakValueDictionary() for key,values in self._config.root.iteritems(): indexing_flag = key == 'tango' if isinstance(values,list) : self._parse_list(values,indexing_flag) elif isinstance(values,dict): self._parse_dict(values,indexing_flag) self._index_tango(values) def _parse_list(self,l,indexing_flag) : for v in l: if isinstance(v,list): self._parse_list(v,indexing_flag) elif isinstance(v,dict): if indexing_flag: self._index_tango(v) self._parse_dict(v,indexing_flag) def _parse_dict(self,d,indexing_flag): for k,v in d.iteritems(): if isinstance(v,dict): if not indexing_flag: indexing_flag = k == 'tango' if indexing_flag: self._index_tango(v) self._parse_dict(v,indexing_flag) elif isinstance(v,list) : self._parse_list(v,indexing_flag) def _index_tango(self,v) : klass = v.get('class') if klass is not None and v.parent.get('device') is None: self._class_name_2_node[klass] = v personal_name = v.get('personal_name') if personal_name is not None : server = v.get('server') if server is None: self._error("_index_tango(personal_name=%) didn't specify server key (executable name)", personal_name) return personal_name = personal_name.lower() dserver_name = '%s/%s' % (server,personal_name) self._personal_2_node[dserver_name] = v self._tango_name_2_node['dserver/%s' % dserver_name.lower()] = v tango_name = v.get('tango_name') if tango_name is not None: tango_name = tango_name.lower() self._tango_name_2_node[tango_name.lower()] = v alias = v.get('alias') if alias is not None: self._tango_name_2_node[alias] = v # TANGO API @_info def get_stored_procedure_release(self): return 'release 0.0' @_info def add_device(self, server_name, dev_info, klass_name, alias=None): self._info("add_device(server_name=%s, dev_info=%s, klass_name=%s, alias=%s)", server_name, dev_info, klass_name, alias) tango_name, _ = dev_info tango_name = tango_name.lower() device_node = self._tango_name_2_node.get(tango_name) if device_node is not None: # There is a problem? return server_exe_name,personal_name = server_name.split('/') personal_name = personal_name.lower() server_name = '%s/%s' % (server_exe_name,personal_name) server_node = self._personal_2_node.get(server_name) if server_node is None: server_node = static.Node(self._config,filename = 'tango/%s.yml' % server_name.replace('/','_')) server_node['server'] = server_exe_name server_node['personal_name'] = personal_name self._personal_2_node[server_name] = server_node self._tango_name_2_node['dserver/%s' % server_name.lower()] = server_node self._strong_node_ref.add(server_node) device_node = static.Node(self._config,parent=server_node) self._strong_node_ref.add(device_node) device_node['tango_name'] = tango_name device_node['class'] = klass_name if alias is not None: device_node['alias'] = alias device_node_list = server_node.get('device',[]) device_node_list.append(device_node) server_node['device'] = device_node_list self._tango_name_2_node[tango_name] = device_node server_node.save() @_info def delete_attribute_alias(self, alias): self._info("delete_attribute_alias(alias=%s)",alias) attr_alias = settings.HashObjSetting(self.TANGO_ATTR_ALIAS) del attr_alias[alias] def _get_class_attribute(self,klass_name, attr_name) : self._info("_get_class_attribute(klass_name=%s,attr_name=%s)", klass_name, attr_name) key_name = 'tango.class.attribute.%s.%s' % (klass_name,attr_name) return settings.HashObjSetting(key_name) @_info def delete_class_attribute(self, klass_name, attr_name): class_attribute = self._get_class_attribute(klass_name,attr_name) class_attribute.clear() @_info def delete_class_attribute_property(self, klass_name, attr_name, prop_name): class_attribute = self._get_class_attribute(klass_name,attr_name) del class_attribute[prop_name] def _get_class_properties(self,klass_name,prop_name): #key_name = 'tango.class.properties.%s.%s' % (klass_name,prop_name) #return settings.QueueSetting(key_name) return self._class_name_2_node.get(klass_name,dict()).get('properties',dict()).get(prop_name,'') @_info def delete_class_property(self, klass_name, prop_name): #class_property = self._get_class_properties(klass_name,prop_name) #class_property.clear() pass def _get_property_attr_device(self,dev_name) : key_name = 'tango.%s' % dev_name.lower().replace('/','.') return settings.HashObjSetting(key_name) @_info def delete_device(self, dev_name): dev_name = dev_name.lower() self._info("delete_device(dev_name=%s)", dev_name) device_node = self._tango_name_2_node.pop(dev_name,None) if device_node is None: return server_node = device_node.parent if server_node is None: # weird return device_list = server_node.get('device',[]) device_list.remove(device_node) server_node.save() prop_attr_device = self._get_property_attr_device(dev_name) prop_attr_device.clear() @_info def delete_device_alias(self, dev_alias): self._info("delete_device_alias(dev_alias=%s)", dev_alias) device_node = self._tango_name_2_node.pop(dev_alias) if device_node is None: return server_node = device_node.parent if server_node is None: # weird return del device_node['alias'] server_node.save() @_info def delete_device_attribute(self, dev_name, attr_name): prop_attr_device = self._get_property_attr_device(dev_name) del prop_attr_device[attr_name] @_info def delete_device_attribute_property(self, dev_name, attr_name, prop_name): prop_attr_device = self._get_property_attr_device(dev_name) d = prop_attr_device.get(attr_name) if d is not None: del d[prop_name] prop_attr_device[attr_name] = d @_info def delete_device_property(self, dev_name, prop_name): properties = self._get_property_node(dev_name) if properties is None: return try: del properties[prop_name] properties.save() except KeyError: pass @_info def delete_property(self, obj_name, prop_name): self._warn("Not implemented delete_property(obj_name=%s, prop_name=%s)", (obj_name,prop_name)) @_info def delete_server(self, server_name): server_node = self._personal_2_node.get(server_name) if server_node is None: return server_node.clear() server_node.save() @_info def delete_server_info(self, server_instance): self._warn("Not implemented delete_server_info(server_instance=%s)", (server_instance)) def _get_export_device_info(self,dev_name): key_name = 'tango.info.%s' % dev_name return settings.HashSetting(key_name) @_info def export_device(self, dev_name, IOR, host, pid, version): self._info("export_device(dev_name=%s, host=%s, pid=%s, version=%s)", dev_name, host, pid, version) dev_name = dev_name.lower() device_node = self._tango_name_2_node.get(dev_name) if device_node is None: th_exc(DB_DeviceNotDefined, "device " + dev_name + " not defined in the database !", "DataBase::ExportDevice()") self._info("export_device(IOR=%s)", IOR) export_device_info = self._get_export_device_info(dev_name) start_time = datetime.datetime.now() export_device_info.set({'IOR':IOR,'host':host,'pid':pid,'version':version, 'start-time': '%s' % start_time}) @_info def export_event(self, event, IOR, host, pid, version): self._warn("Not implemented export_event(event=%s, IOR=%s, host=%s, pid=%s, version=%s)", (event, IOR, host, pid, version)) @_info def get_alias_device(self, dev_alias): device_node = self._tango_name_2_node.get(dev_alias) if device_node is None: th_exc(DB_DeviceNotDefined, "No device found for alias '" + dev_alias + "'", "DataBase::GetAliasDevice()") return device_node.get('tango_name') @_info def get_attribute_alias(self, attr_alias_name): attr_alias = settings.HashObjSetting(self.TANGO_ATTR_ALIAS) attr_alias_info = attr_alias.get(attr_alias_name) if attr_alias_info is None: th_exc(DB_SQLError, "No attribute found for alias '" + attr_alias + "'", "DataBase::GetAttributeAlias()") return attr_alias_info.get('name') @_info def get_attribute_alias_list(self, attr_alias_name): attr_alias = settings.HashObjSetting(self.TANGO_ATTR_ALIAS) attr_alias_info = attr_alias.get(attr_alias_name) if attr_alias_info is None: return [] else: return [attr_alias_info.get('name')] @_info def get_class_attribute_list(self, class_name, wildcard): redis = settings.get_cache() attributes = [x for x in redis.scan_iter(match='tango.class.attribute.%s' % class_name)] return _filter(wildcard,attributes) @_info def get_class_attribute_property(self, class_name, attributes): result = [class_name, str(len(attributes))] for att_name in attributes: class_attribute_properties = self._get_class_attribute(class_name,att_name) attr_property = [x for p in class_attribute_properties.iteritems() for x in p] result.extend([att_name,str(len(attr_property)/2)] + attr_property) return result @_info def get_class_attribute_property2(self, class_name, attributes): result = [class_name, str(len(attributes))] for attr_name in attributes: class_properties = self._get_class_attribute(class_name,attr_name) attr_property = [] for nb,(name,values) in enumerate(class_properties.iteritems()): if isinstance(values,list) : attr_property.extend([name,str(len(values))] + [str(x) for x in values]) else: attr_property.extend([name,'1',str(values)]) if(attr_property): result.extend([attr_name,str(nb)] + attr_property) else: result.extend([attr_name,'0']) return result @_info def get_class_attribute_property_hist(self, class_name, attribute, prop_name): return [] @_info def get_class_for_device(self, dev_name): dev_name = dev_name.lower() device_node = self._tango_name_2_node.get(dev_name) if device_node is None: th_exc(DB_IncorrectArguments, "Device not found for " + dev_name, "Database.GetClassForDevice") class_name = device_node.get('class') if class_name is None: th_exc(DB_IncorrectArguments, "Class not found for " + dev_name, "Database.GetClassForDevice") return class_name @_info def get_class_inheritance_for_device(self, dev_name): class_name = self.get_class_for_device(dev_name) props = self.get_class_property(class_name, "InheritedFrom") return [class_name] + props[4:] @_info def get_class_list(self, server): server_name_list = [x.get('server') for x in self._personal_2_node.values() if x.has_key('server')] res = _filter(server,set(server_name_list)) res.sort() return res @_info def get_class_property(self, class_name, properties): result = [class_name,str(len(properties))] for prop_name in properties: properties_array = [] values = self._get_class_properties(class_name,prop_name) if isinstance(values,list): values = [str(x) for x in values] properties_array.extend([prop_name,str(len(values))] + values) elif values: properties_array.extend([prop_name,'1',str(values)]) else: properties_array.extend([prop_name,'0']) result.extend(properties_array) return result @_info def get_class_property_hist(self, class_name, prop_name): return [] @_info def get_class_property_list(self, class_name): properties = self._class_name_2_node.get(class_name,dict()).get("properties", dict()) return [k for k,v in properties.iteritems() if not isinstance(v,dict)] #cache = settings.get_cache() #return cache.keys('tango.class.properties.%s*' % class_name) @_info def get_device_alias(self, dev_name): dev_name = dev_name.lower() device_node = self._tango_name_2_node.get(dev_name) if device_node is None: th_exc(DB_DeviceNotDefined, "No alias found for device '" + dev_name + "'", "DataBase::GetDeviceAlias()") alias = device_node.get('alias') if alias is None: th_exc(DB_DeviceNotDefined, "No alias found for device '" + dev_name + "'", "DataBase::GetDeviceAlias()") return alias @_info def get_device_alias_list(self, alias): alias_list = [node.get('alias') for node in self._tango_name_2_node.values()] return _filter(alias,alias_list) @_info def get_device_attribute_list(self, dev_name, attribute): prop_attr_device = self._get_property_attr_device(dev_name) return _filter(attribute,prop_attr_device.keys()) @_info def get_device_attribute_property(self, dev_name, attributes): prop_attr_device = self._get_property_attr_device(dev_name) result = [dev_name,str(len(attributes))] for attr_name in attributes: prop_attr = prop_attr_device.get(attr_name) if prop_attr is None: result.extend([attr_name,'0']) else: result.extend([attr_name,str(len(prop_attr))] + [str(x) for p in prop_attr.iteritems() for x in p]) return result @_info def get_device_attribute_property2(self, dev_name, attributes): prop_attr_device_handler = self._get_property_attr_device(dev_name) result = [dev_name, str(len(attributes))] prop_attr_device = prop_attr_device_handler.get_all() for attr_name in attributes: prop_attr = prop_attr_device.get(attr_name) if prop_attr is None: result.extend((attr_name,'0')) else: result.extend((attr_name,str(len(prop_attr)))) for name,values in prop_attr.iteritems(): if isinstance(values,list): result.extend([name,len(values)] + [str(x) for x in values]) else: result.extend((name,'1',str(values))) return result @_info def get_device_attribute_property_hist(self, dev_name, attribute, prop_name): return [] @_info def get_device_class_list(self, server_name): server_node = self._personal_2_node.get(server_name) if server_node is None: return [] devices = server_node.get('device') if isinstance(devices,list): name_class = [(n.get('tango_name'),n.get('class')) for n in devices] else: name_class = [(devices.get('tango_name'),devices.get('class'))] return [x for p in name_class for x in p] @_info def get_device_domain_list(self, wildcard): filtered_names = _filter(wildcard, [n.get('tango_name') for n in self._tango_name_2_node.values()]) res = list(set([x.split('/')[0] for x in filtered_names])) res.sort() return res @_info def get_device_exported_list(self, wildcard): cache = settings.get_cache() return [x.replace('tango.','') for x in cache.keys('tango.%s' % wildcard)] @_info def get_device_family_list(self, wildcard): filtered_names = _filter(wildcard, [n.get('tango_name') for n in self._tango_name_2_node.values()]) return list(set([x.split('/')[1] for x in filtered_names])) def get_device_info(self, dev_name): dev_name = dev_name.lower() device_info = self._get_export_device_info(dev_name) device_node = self._tango_name_2_node.get(dev_name) result_long = [] result_str = [] info = device_info.get_all() if device_node: if dev_name.startswith('dserver'): server_node = device_node else: server_node = device_node.parent result_str.extend((dev_name, info.get('IOR',''), str(info.get('version','0')), server_node.get('server','') + '/' + server_node.get('personal_name',''), info.get('host','?'),info.get('start-time','?'),'?', device_node.get('class','DServer'))) result_long.extend((info and 1 or 0,info.get('pid',-1))) return (result_long,result_str) @_info def get_device_list(self,server_name, class_name): if server_name == '*': r_list = list() for server_node in self._personal_2_node.values(): device_list = server_node.get('device') r_list.extend(self._tango_name_from_class(device_list,class_name)) return r_list server_node = self._personal_2_node.get(server_name) if server_node is None: return [] device_list = server_node.get('device') return self._tango_name_from_class(device_list,class_name) def _tango_name_from_class(self,device_list,class_name): m = re.compile(class_name.replace('*','.*'),re.IGNORECASE) if isinstance(device_list,list) : return [x.get('tango_name') for x in device_list if m.match(x.get('class',''))] elif isinstance(device_list,dict) and m.match(device_list.get('class','')) : return [device_list.get('tango_name')] else: return [] @_info def get_device_wide_list(self, wildcard): return _filter(wildcard,self._tango_name_2_node.keys()) @_info def get_device_member_list(self, wildcard): wildcard = wildcard.lower() filtered_names = _filter(wildcard,self._tango_name_2_node.keys()) return list(set([x.split('/')[-1] for x in filtered_names])) def _get_property_node(self,dev_name) : dev_name = dev_name.lower() device_node = self._tango_name_2_node.get(dev_name) if device_node is None: properties = {} else: properties = device_node.get('properties') if isinstance(properties,str) : # reference properties_key = properties.split('/') node_refname = properties_key[0] property_node = self._config.get_config(node_refname) if properties_key == node_refname: properties = property_node else: for key in properties_key[1:]: property_node = property_node.get(key) if property_node is None: break properties = property_node return properties @_info def get_device_property(self, dev_name, properties_query_list): properties = self._get_property_node(dev_name) if properties is None: result = [dev_name,str(len(properties_query_list))] for p_name in properties_query_list: result.extend([p_name,'0','']) return result else: nb_properties = 0 properties_array = [] properties_key = properties.keys() for property_name in properties_query_list: ask_keys = _filter(property_name,properties_key) if not ask_keys: properties_array.extend([property_name,'0','']) nb_properties += 1 for key in ask_keys: values = properties.get(key,'') if isinstance(values,list): values = [str(x) for x in values] properties_array.extend([property_name,str(len(values))] + values) else: properties_array.extend([property_name,'1',str(values)]) nb_properties += len(ask_keys) return [dev_name,str(nb_properties)] + properties_array @_info def get_device_property_list(self,device_name,prop_filter) : properties = self._get_property_node(device_name) if properties is None: return [] else: return _filter(prop_filter,[k for k,v in properties.iteritems() if not isinstance(v,dict)]) @_info def get_device_property_hist(self, device_name, prop_name): return [] @_info def get_device_server_class_list(self, server_name): server_name = server_name.lower() server_node = self._personal_2_node.get(server_name) if server_node is None: return [] else: devices = server_node.get('device') if isinstance(devices,list): return [x.get('class') for x in devices] else: return [devices.get('class')] @_info def get_exported_device_list_for_class(self, class_name): result = [] cache = settings.get_cache() exported_devices = cache.keys('tango.info.*') for exp_dev in exported_devices: dev_name = exp_dev.replace('tango.info.','') dev_node = self._tango_name_2_node.get(dev_name) if dev_node: dev_class_name = dev_node.get('class') if dev_class_name == class_name: result.append(dev_name) return result @_info def get_host_list(self, host_name): cache = settings.get_cache() host_list = [settings.HashSetting(key_name).get('host') for key_name in cache.keys('tango.info.*')] return _filter(host_name,host_list) @_info def get_host_server_list(self, host_name): result = [] wildcard = host_name.replace('*','.*') m = re.compile(wildcard) cache = settings.get_cache() for key_name in cache.keys('tango.info.*'): host = settings.HashSetting(key_name).get('host') if not m.match(host): continue dev_name = key_name.replace('tango.info.','') dev_node = self._tango_name_2_node.get(dev_name) if dev_node is None: continue server_node = dev_node.parent result.append('%s/%s' % (server_node.get('server',''),server_node.get('personal_name'))) return result @_info def get_host_servers_info(self, host_name): #Don't know what it is? return [] @_info def get_instance_name_list(self, server_name): server_name = server_name + "\*" server_list = self.get_server_list(server_name) result = [] for server in server_list: names = server.split("/") result.append(names[1]) return result @_info def get_object_list(self, name): return [] @_info def get_property(self, object_name, properties): result = [object_name,str(len(properties))] for prop in properties: result.extend([prop,'0','']) return result @_info def get_property_hist(self, object_name, prop_name): return [] @_info def get_property_list(self, object_name, wildcard): return [] @_info def get_server_info(self, server_name): return ["","",""] @_info def get_server_list(self, wildcard): return _filter(wildcard,self._personal_2_node.keys()) @_info def get_server_name_list(self,wildcard) : res = list(set(_filter(wildcard,[x.split('/')[0] for x in self._personal_2_node.keys()]))) res.sort() return res @_info def get_server_class_list(self,wildcard): server_names = _filter(wildcard,self._personal_2_node.keys()) result = set() for ser_name in server_names: server_node = self._personal_2_node.get(ser_name) for device_node in server_node.get('device',[]) : class_name = device_node.get('class') if class_name is not None: result.add(class_name) result.add('DServer') result = list(result) result.sort() return result def import_device(self, dev_name): dev_node = self._tango_name_2_node.get(dev_name) if dev_node is not None: return self.get_device_info(dev_name) else: th_exc(DB_DeviceNotDefined, "device " + dev_name + " not defined in the database !", "DataBase::ImportDevice()") @_info def import_event(self, event_name): th_exc(DB_DeviceNotDefined, "event " + event_name + " not defined in the database !", "DataBase::ImportEvent()") @_info def info(self): return ["Beacon Beacon files"] @_info def put_attribute_alias(self, attribute_name, attr_alias_name): attr_alias = settings.HashObjSetting(self.TANGO_ATTR_ALIAS) attr_alias_info = attr_alias.get(attr_alias_name) if attr_alias_info is not None: self.warn_stream("DataBase::DbPutAttributeAlias(): this alias exists already ") th_exc(DB_SQLError, "alias " + attribute_alias + " already exists !", "DataBase::DbPutAttributeAlias()") attr_alias[attr_alias_name] = attribute_name @_info def put_class_attribute_property(self, class_name, nb_attributes, attr_prop_list): attr_id = 0 for k in range(nb_attributes): attr_name,nb_properties = attr_prop_list[attr_id],int(attr_prop_list[attr_id + 1]) attr_id += 2 class_properties = self._get_class_attribute(class_name,att_name) new_values = {} for prop_id in range(attr_id,attr_id + nb_properties * 2,2) : prop_name,prop_val = attr_prop_list[prop_id],attr_prop_list[prop_id + 1] new_values[prop_name] = prop_val attr_id += nb_properties * 2 class_properties.set(new_values) @_info def put_class_attribute_property2(self, class_name, nb_attributes, attr_prop_list): attr_id = 0 for j in range(nb_attributes) : attr_name,nb_properties = attr_prop_list[attr_id],attr_prop_list[attr_id + 1] attr_id += 2 class_properties = self._get_class_attribute(class_name,att_name) new_values = {} for prop_id in range(nb_properties) : prop_name,prop_number = attr_prop_list[attr_id],int(attr_prop_list[attr_id + 1]) attr_id += 2 prop_values = [] for prop_sub_id in range(prop_number): prop_values.append(attr_prop_list[attr_id]) attr_id += 1 if len(prop_values) == 1: prop_values = prop_values[0] new_values[prop_name] = prop_values class_properties.set(new_values) @_info def put_class_property(self, class_name, nb_properties, attr_prop_list): attr_id = 0 class_node = self._class_name_2_node.get(class_name) if class_node is None: class_node = static.Node(self._config,parent=self._config.root, filename = 'tango/%s.yml' % class_name.replace('/','_')) class_node['class'] = class_name self._strong_node_ref.add(class_node) self._class_name_2_node[class_name] = class_node properties = class_node.get('properties',dict()) for k in range(nb_properties): prop_name,nb_values = attr_prop_list[attr_id],int(attr_prop_list[attr_id + 1]) attr_id += 2 if nb_values == 1: properties[prop_name] = attr_prop_list[attr_id] else: properties[prop_name] = list(attr_prop_list[attr_id:]) class_node['properties'] = properties class_node.save() @_info def put_device_alias(self, device_name, device_alias): device_node = self._tango_name_2_node.get(device_name) device_node['alias'] = device_alias device_node.save() @_info def put_device_attribute_property(self, device_name, nb_attributes, attr_prop_list): attr_id = 0 prop_attr_device = self._get_property_attr_device(device_name) for k in range(nb_attributes): attr_name,prop_nb = attr_prop_list[attr_id],int(attr_prop_list[attr_id + 1]) attr_id += 2 new_values = {} for prop_id in range(attr_id,attr_id + prop_nb * 2,2) : prop_name,prop_val = attr_prop_list[prop_id],attr_prop_list[prop_id + 1] new_values[prop_name] = prop_val prop_attr_device[attr_name] = new_values attr_id += prop_nb * 2 @_info def put_device_attribute_property2(self, device_name, nb_attributes, attr_prop_list): attr_id = 0 prop_attr_device = self._get_property_attr_device(device_name) for k in range(nb_attributes): attr_name,prop_nb = attr_prop_list[attr_id],int(attr_prop_list[attr_id + 1]) attr_id += 2 new_values = {} for prop_id in range(prop_nb) : prop_name,prop_nb = attr_prop_list[attr_id],int(attr_prop_list[attr_id + 1]) attr_id += 2 prop_values = [] for prop_sub_id in range(prop_nb): prop_values.append(attr_prop_list[attr_id]) attr_id += 1 if len(prop_values) == 1: prop_values = prop_values[0] new_values[prop_name] = prop_values prop_attr_device[attr_name] = new_values @_info def put_device_property(self, device_name, nb_properties, attr_prop_list): device_name = device_name.lower() device_node = self._tango_name_2_node.get(device_name) old_properties = device_node.get('properties') if isinstance(old_properties,str): #reference properties_key = old_properties.split('/') node_refname = properties_key[0] property_node = self._config.get_config(node_refname) if properties_key == node_refname: old_properties = property_node else: for key in properties_key[1:]: property_node = property_node.get(key) if property_node is None: break old_properties = property_node if old_properties is None: properties = static.Node(self._config,parent=device_node) device_node['properties'] = properties else: properties = old_properties id_prop = 0 for i in range(nb_properties) : prop_name,prop_nb_values = attr_prop_list[id_prop],int(attr_prop_list[id_prop + 1]) id_prop += 2 if prop_nb_values == 1: properties[prop_name] = attr_prop_list[id_prop] else: properties[prop_name] = attr_prop_list[id_prop:id_prop + prop_nb_values] id_prop += prop_nb_values properties.save() @_info def put_property(self, object_name, nb_properties, attr_prop_list): #Not use in our case pass @_info def put_server_info(self, tmp_server, tmp_host, tmp_mode, tmp_level, tmp_extra): #Not use in our case pass def unexport_device(self, dev_name): device_info = self._get_export_device_info(dev_name) device_info.clear() @_info def unexport_event(self, event_name): #Not use in our case pass @_info def unexport_server(self, server_name): server_node = self._personal_2_node.get(server_name) if server_node is None: return for device in server_node.get('device'): tango_name = device.get('tango_name') if tango_name: self.unexport_device(tango_name) @_info def delete_all_device_attribute_property(self, dev_name, attr_list): prop_attr_device = self._get_property_attr_device(dev_name) for attr_name in attr_list: del prop_attr_device[attr_name] @_info def my_sql_select(self, cmd): #todo see if it's really needed return ([0,0],[]) @_info def get_csdb_server_list(self): cache = settings.get_cache() exported_devices = cache.keys('tango.info.sys/database*') result = [] for key_name in exported_devices: info = settings.HashSetting(key_name) result.append(info.get('IOR')) return result @_info def get_attribute_alias2(self, attr_name): attr_alias = settings.HashObjSetting(self.TANGO_ATTR_ALIAS) result = [] for alias,name in attr_alias.iteritems(): if name == attr_name: result.append(alias) return result @_info def get_alias_attribute(self, alias_name): attr_alias = settings.HashObjSetting(self.TANGO_ATTR_ALIAS) attr_name = attr_alias.get(alias_name) return attr_name and [attr_name] or [] @_info def rename_server(self, old_name, new_name): device_node = self._tango_name_2_node.get(new_name) if device_node is not None: th_exc(DB_SQLError, "Device server process name " + attribute_alias + "is already used !", "DataBase::DbRenameServer()") device_node = self._tango_name_2_node.pop(old_name) device_node['tango_name'] = new_name self._tango_name_2_node[new_name] = device_node device_node.save() def get_db(personal_name = "",**keys): return beacon(personal_name = personal_name) def get_wildcard_replacement(): return False pytango-9.2.2/tango/databaseds/db_access/sqlite3.py000066400000000000000000002002521316324373100222600ustar00rootroot00000000000000from __future__ import print_function import os import logging import functools import threading import Queue import tango th_exc = tango.Except.throw_exception from db_errors import * from concurrent.futures import ThreadPoolExecutor Executor = ThreadPoolExecutor(1) def get_create_db_statements(): statements = [] with open("create_db_tables.sql") as f: lines = f.readlines() # strip comments lines = (line for line in lines if not line.startswith('#')) lines = (line for line in lines if not line.lower().strip().startswith('key')) lines = (line for line in lines if not line.lower().strip().startswith('key')) lines = "".join(lines) lines = lines.replace("ENGINE=MyISAM", "") statements += lines.split(";") with open("create_db.sql") as f: lines = f.readlines() # strip comments lines = (line for line in lines if not line.lower().startswith('#')) lines = (line for line in lines if not line.lower().startswith('create database')) lines = (line for line in lines if not line.lower().startswith('use')) lines = (line for line in lines if not line.lower().startswith('source')) lines = "".join(lines) statements += lines.split(";") return statements def replace_wildcard(text): # escape '%' with '\' text = text.replace("%", "\\%") # escape '_' with '\' text = text.replace("_", "\\_") # escape '"' with '\' text = text.replace('"', '\\"') # escape ''' with '\' text = text.replace("'", "\\'") # replace '*' with '%' text = text.replace("*", "%") return text def use_cursor(f): @functools.wraps(f) def wrap(*args, **kwargs): self = args[0] has_cursor = 'cursor' in kwargs cursor = kwargs.pop('cursor', None) if not has_cursor: cursor = Executor.submit(self.get_cursor).result() self.cursor = cursor try: ret = Executor.submit(f, *args, **kwargs).result() if not has_cursor: Executor.submit(cursor.connection.commit).result() return ret finally: if not has_cursor: Executor.submit(cursor.close).result() del self.cursor return wrap class Tango_dbapi2(object): DB_API_NAME = 'sqlite3' def __init__(self, db_name="tango_database.db", history_depth=10, fire_to_starter=True): self._db_api = None self._db_conn = None self.db_name = db_name self.history_depth = history_depth self.fire_to_starter = fire_to_starter self._logger = logging.getLogger(self.__class__.__name__) self._debug = self._logger.debug self._info = self._logger.info self._warn = self._logger.warn self._error = self._logger.error self._critical = self._logger.critical self.initialize() def close_db(self): if self._db_conn is not None: self._db_conn.commit() self._db_conn.close() self._db_api = None self._db_conn = None def get_db_api(self): if self._db_api is None: self._db_api = __import__(self.DB_API_NAME) return self._db_api @property def db_api(self): return self.get_db_api() @property def db_conn(self): if self._db_conn is None: self._db_conn = self.db_api.connect(self.db_name) return self._db_conn def get_cursor(self): return self.db_conn.cursor() def initialize(self): self._info("Initializing database...") if not os.path.isfile(self.db_name): self.create_db() else: # trigger connection self.db_conn @use_cursor def create_db(self): self._info("Creating database...") statements = get_create_db_statements() cursor = self.cursor for statement in statements: cursor.execute(statement) @use_cursor def get_id(self, name): cursor = self.cursor name += '"_history_id' _id = cursor.execute('SELECT id FROM ?', (name,)).fetchone()[0] + 1 cursor.execute('UPDATE ? SET id=?', (name, _id)) return _id @use_cursor def purge_att_property(self, table, field, obj, attr, name): cursor = self.cursor cursor.execute(\ 'SELECT DISTINCT id FROM ? WHERE ? = ? AND name = ? AND ' \ 'attribute = ? ORDER BY date', (table, field, obj, name, attr)) rows = cursor.fetchall() to_del = len(rows) - self.history_depth if to_del > 0: for row in rows[:to_del]: cursor.execute('DELETE FROM ? WHERE id=?', (table, row[0])) @use_cursor def purge_property(self, table, field, obj, name): cursor = self.cursor cursor.execute(\ 'SELECT DISTINCT id FROM ? WHERE ? = ? AND name = ? ' \ 'ORDER BY date', (table, field, obj, name)) rows = cursor.fetchall() to_del = len(rows) - self.history_depth if to_del > 0: for row in rows[:to_del]: cursor.execute('DELETE FROM ? WHERE id=?', (table, row[0])) @use_cursor def get_device_host(self, name): cursor = self.cursor name = replace_wildcard(name) cursor.execute('SELECT host FROM device WHERE name LIKE ?', (name,)) row = cursor.fetchone() if row is None: raise Exception("No host for device '" + name + "'") else: return row[0] def send_starter_cmd(self, starter_dev_names): for name in starter_dev_names: pos = name.find('.') if pos != -1: name = name[0:pos] dev = tango.DeviceProxy(name) dev.UpdateServersInfo() # TANGO API def get_stored_procedure_release(self): return 'release 1.8' @use_cursor def add_device(self, server_name, dev_info, klass_name, alias=None): self._info("delete_attribute_alias(server_name=%s, dev_info=%s, klass_name=%s, alias=%s)", server_name, dev_info, klass_name, alias) dev_name, (domain, family, member) = dev_info cursor = self.cursor # first delete the tuple (device,name) from the device table cursor.execute('DELETE FROM device WHERE name LIKE ?', (dev_name,)) # then insert the new value for this tuple cursor.execute(\ 'INSERT INTO device (name, alias, domain, family, member, exported, ' \ 'ior, host, server, pid, class, version, started, stopped) ' \ 'VALUES (?, ?, ?, ?, ?, 0, "nada", "nada", ?, 0, ?, "0", NULL, NULL)', (dev_name, alias, domain, family, member, server_name, klass_name)) # Check if a DServer device entry for the process already exists cursor.execute('SELECT name FROM device WHERE server LIKE ? AND class LIKE "DServer"', (server_name,)) if cursor.fetchone() is None: dev_name = "dserver/" + server_name domain, family, member = dev_name.split("/", 2) cursor.execute(\ 'INSERT INTO device (name, domain, family, member, exported, ior, ' \ 'host, server, pid, class, version, started, stopped) ' \ 'VALUES (?, ?, ?, ?, 0, "nada", "nada", ?, 0, "DServer", "0", NULL, NULL)', (dev_name, domain, family, member, server_name)) @use_cursor def delete_attribute_alias(self, alias): self._info("delete_attribute_alias(alias=%s)", alias) self.cursor.execute('DELETE FROM attribute_alias WHERE alias=?', (alias,)) @use_cursor def delete_class_attribute(self, klass_name, attr_name): self.cursor.execute(\ 'DELETE FROM property_attribute_class WHERE class LIKE ? AND ' \ 'attribute LIKE ?', (klass_name, attr_name)) @use_cursor def delete_class_attribute_property(self, klass_name, attr_name, prop_name): cursor = self.cursor # Is there something to delete ? cursor.execute(\ 'SELECT count(*) FROM property_attribute_class WHERE class = ? ' \ 'AND attribute = ? AND name = ?', (klass_name, attr_name, prop_name)) if cursor.fetchone()[0] > 0: # then delete property from the property_attribute_class table cursor.execute(\ 'DELETE FROM property_attribute_class WHERE class = ? AND ' \ 'attribute = ? and name = ?', (klass_name, attr_name, prop_name)) # mark this property as deleted hist_id = self.get_id('class_attribute', cursor=cursor) cursor.execute(\ 'INSERT INTO property_attribute_class_hist (class, attribute, ' \ 'name, id, count, value) VALUES ' \ '(?, ?, ?, ?, "0", "DELETED")', (klass_name, attr_name, prop_name, hist_id)) self.purge_att_property("property_attribute_class_hist", "class", klass_name, attr_name, prop_name, cursor=cursor) @use_cursor def delete_class_property(self, klass_name, prop_name): cursor = self.cursor prop_name = replace_wildcard(prop_name) # Is there something to delete ? cursor.execute(\ 'SELECT DISTINCT name FROM property_class WHERE class=? AND ' \ 'name LIKE ?', (klass_name, prop_name)) for row in cursor.fetchall(): # delete the tuple (device,name,count) from the property table name = row[0] cursor.execute(\ 'DELETE FROM property_class WHERE class=? AND name=?', (klass_name, name)) # Mark this property as deleted hist_id = self.get_id("class", cursor=cursor) cursor.execute(\ 'INSERT INTO property_class_hist (class, name, id, count, value) ' \ 'VALUES (?, ?, ?, "0", "DELETED")', (klass_name, name, hist_id)) self.purge_property("property_class_hist", "class", klass_name, name, cursor=cursor) @use_cursor def delete_device(self, dev_name): self._info("delete_device(dev_name=%s)", dev_name) cursor = self.cursor dev_name = replace_wildcard(dev_name) # delete the device from the device table cursor.execute('DELETE FROM device WHERE name LIKE ?', (dev_name,)) # delete device from the property_device table cursor.execute('DELETE FROM property_device WHERE device LIKE ?', (dev_name,)) # delete device from the property_attribute_device table cursor.execute('DELETE FROM property_attribute_device WHERE device LIKE ?', (dev_name,)) @use_cursor def delete_device_alias(self, dev_alias): self._info("delete_device_alias(dev_alias=%s)", dev_alias) self.cursor.execute('UPDATE device SET alias=NULL WHERE alias=?', (dev_alias,)) @use_cursor def delete_device_attribute(self, dev_name, attr_name): dev_name = replace_wildcard(dev_name) self.cursor.execute(\ 'DELETE FROM property_attribute_device WHERE device LIKE ? AND ' \ 'attribute LIKE ?', (dev_name, attr_name)) @use_cursor def delete_device_attribute_property(self, dev_name, attr_name, prop_name): cursor = self.cursor # Is there something to delete ? cursor.execute(\ 'SELECT count(*) FROM property_attribute_device WHERE device = ?' \ 'AND attribute = ? AND name = ?', (dev_name, attr_name, prop_name)) if cursor.fetchone()[0] > 0: # delete property from the property_attribute_device table cursor.execute(\ 'DELETE FROM property_attribute_device WHERE device = ? AND ' 'attribute = ? AND name = ?', (dev_name, attr_name, prop_name)) # Mark this property as deleted hist_id = self.get_id("device_attribute", cursor=cursor) cursor.execute(\ 'INSERT INTO property_attribute_device_hist ' \ '(device, attribute, name, id, count, value) VALUES ' \ '(?, ?, ?, ?, "0", "DELETED")', (dev_name, attr_name, prop_name, hist_id)) self.purge_att_property("property_attribute_device_hist", "device", dev_name, attr_name, prop_name, cursor=cursor) @use_cursor def delete_device_property(self, dev_name, prop_name): cursor = self.cursor prop_name = replace_wildcard(prop_name) # Is there something to delete ? cursor.execute(\ 'SELECT DISTINCT name FROM property_device WHERE device=? AND ' \ 'name LIKE ?', (dev_name, prop_name)) for row in cursor.fetchall(): # delete the tuple (device,name,count) from the property table cursor.execute(\ 'DELETE FROM property_device WHERE device=? AND name LIKE ?', (dev_name, prop_name)) # Mark this property as deleted hist_id = self.get_id("device", cursor=cursor) cursor.execute(\ 'INSERT INTO property_device_hist (device, id, name, count, value) ' \ 'VALUES (?, ?, ?, "0", "DELETED")', (dev_name, hist_id, row[0])) self.purge_property("property_device_hist", "device", dev_name, row[0]) @use_cursor def delete_property(self, obj_name, prop_name): cursor = self.cursor prop_name = replace_wildcard(prop_name) # Is there something to delete ? cursor.execute(\ 'SELECT DISTINCT name FROM property WHERE object=? AND ' \ 'name LIKE ?', (obj_name, prop_name)) for row in cursor.fetchall(): # delete the tuple (object,name,count) from the property table cursor.execute(\ 'DELETE FROM property_device WHERE device=? AND name LIKE ?', (obj_name, prop_name)) # Mark this property as deleted hist_id = self.get_id("object", cursor=cursor) cursor.execute(\ 'INSERT INTO property_hist (object, name, id, count, value) ' \ 'VALUES (?, ?, ?, "0", "DELETED")', (obj_name, row[0], hist_id)) self.purge_property("property_hist", "object", obj_name, row[0]) @use_cursor def delete_server(self, server_instance): cursor = self.cursor server_instance = replace_wildcard(server_instance) previous_host = None # get host where running if self.fire_to_starter: adm_dev_name = "dserver/" + server_instance previous_host = self.get_device_host(adm_dev_name) # then delete the device from the device table cursor.execute('DELETE FROM device WHERE server LIKE ?', (server_instance,)) # Update host's starter to update controlled servers list if self.fire_to_starter and previous_host: self.send_starter_cmd(previous_host) pass @use_cursor def delete_server_info(self, server_instance): self.cursor.execute('DELETE FROM server WHERE name=?', (server_instance,)) @use_cursor def export_device(self, dev_name, IOR, host, pid, version): self._info("export_device(dev_name=%s, host=%s, pid=%s, version=%s)", dev_name, host, pid, version) self._info("export_device(IOR=%s)", IOR) cursor = self.cursor do_fire = False previous_host = None if self.fire_to_starter: if dev_name[0:8] == "dserver/": # Get database server name tango_util = tango.Util.instance() db_serv = tango_util.get_ds_name() adm_dev_name = "dserver/" + db_serv.lower() if dev_name != adm_dev_name and dev_name[0:16] != "dserver/starter/": do_fire = True previous_host = self.get_device_host(dev_name) cursor.execute('SELECT server FROM device WHERE name LIKE ?', (dev_name,)) row = cursor.fetchone() if row is None: th_exc(DB_DeviceNotDefined, "device " + dev_name + " not defined in the database !", "DataBase::ExportDevice()") server = row[0] # update the new value for this tuple cursor.execute(\ 'UPDATE device SET exported=1, ior=?, host=?, pid=?, version=?, ' \ 'started=datetime("now") WHERE name LIKE ?', (IOR, host, pid, version, dev_name)) # update host name in server table cursor.execute('UPDATE server SET host=? WHERE name LIKE ?', (host, server)) if do_fire: hosts = [] hosts.append(host) if previous_host != "" and previous_host != "nada" and previous_host != host: hosts.append(previous_host) self.send_starter_cmd(hosts) @use_cursor def export_event(self, event, IOR, host, pid, version): cursor = self.cursor cursor.execute(\ 'INSERT event (name,exported,ior,host,server,pid,version,started) ' \ 'VALUES (?, 1, ?, ?, ?, ?, ?, datetime("now")', (event, IOR, host, event, pid, version)) @use_cursor def get_alias_device(self, dev_alias): cursor = self.cursor cursor.execute('SELECT name FROM device WHERE alias LIKE ?', (dev_alias,)) row = cursor.fetchone() if row is None: th_exc(DB_DeviceNotDefined, "No device found for alias '" + dev_alias + "'", "DataBase::GetAliasDevice()") return row[0] @use_cursor def get_attribute_alias(self, attr_alias): cursor = self.cursor cursor.execute('SELECT name from attribute_alias WHERE alias LIKE ?', (attr_alias,)) row = cursor.fetchone() if row is None: th_exc(DB_SQLError, "No attribute found for alias '" + attr_alias + "'", "DataBase::GetAttributeAlias()") return row[0] @use_cursor def get_attribute_alias_list(self, attr_alias): cursor = self.cursor cursor.execute('SELECT DISTINCT alias FROM attribute_alias WHERE alias LIKE ? ORDER BY attribute', (attr_alias,)) return [ row[0] for row in cursor.fetchall() ] @use_cursor def get_class_attribute_list(self, class_name, wildcard): cursor = self.cursor cursor.execute('SELECT DISTINCT attribute FROM property_attribute_class WHERE class=? and attribute like ?', (class_name, wildcard)) return [ row[0] for row in cursor.fetchall() ] @use_cursor def get_class_attribute_property(self, class_name, attributes): cursor = self.cursor stmt = 'SELECT name,value FROM property_attribute_class WHERE class=? AND attribute LIKE ?' result = [class_name, str(len(attributes))] for attribute in attributes: cursor.execute(stmt, (class_name, attribute)) rows = cursor.fetchall() result.append(attribute) result.append(str(len(rows))) for row in rows: result.append(row[0]) result.append(row[1]) return result @use_cursor def get_class_attribute_property2(self, class_name, attributes): cursor = self.cursor stmt = 'SELECT name,value FROM property_attribute_class WHERE class=? AND attribute LIKE ? ORDER BY name,count' result = [class_name, str(len(attributes))] for attribute in attributes: cursor.execute(stmt, (class_name, attribute)) rows = cursor.fetchall() result.append(attribute) j = 0 new_prop = True nb_props = 0 prop_size = 0 prop_names = [] prop_sizes = [] prop_values = [] for row in rows: prop_values.append(row[1]) if j == 0: old_name = row[0] else: name = row[0] if name != old_name: new_prop = True old_name = name else: new_prop = False j = j + 1 if new_prop == True: nb_props = nb_props + 1 prop_names.append(row[0]) if prop_size != 0: prop_sizes.append(prop_size) prop_size = 1 else: prop_size = prop_size + 1 result.append(str(nb_props)) j = 0 k = 0 for name in prop_names: result.append(name) result.append(prop_sizes[j]) for i in range(0, prop_sizes[j]): result.append(prop_values[k]) k = k + 1 j = j + 1 return result @use_cursor def get_class_attribute_property_hist(self, class_name, attribute, prop_name): cursor = self.cursor stmt = 'SELECT DISTINCT id FROM property_attribute_class_hist WHERE class=? AND attribute LIKE ? AND name LIKE ? ORDER by date ASC' result = [] cursor.execute(stmt, (class_name, attribute, prop_name)) for row in cursor.fetchall(): idr = row[0] stmt = 'SELECT DATE_FORMAT(date,\'%Y-%m-%d %H:%i:%s\'),value,attribute,name,count FROM property_attribute_class_hist WHERE id =? AND class =?' cursor.execute(stmt, (idr, class_name)) rows = cursor.fetchall() result.append(rows[2]) result.append(rows[3]) result.append(rows[0]) result.append(str(rows[4])) for value in rows[1]: result.append(value) return result @use_cursor def get_class_for_device(self, dev_name): cursor = self.cursor cursor.execute('SELECT DISTINCT class FROM device WHERE name=?', (dev_name,)) row = cursor.fetchone() if row is None: th_exc(DB_IncorrectArguments, "Class not found for " + dev_name, "Database.GetClassForDevice") return row @use_cursor def get_class_inheritance_for_device(self, dev_name): cursor = self.cursor class_name = self.get_class_for_device(dev_name, cursor=cursor) props = self.get_class_property(class_name, "InheritedFrom", cursor=cursor) return [class_name] + props[4:] @use_cursor def get_class_list(self, server): cursor = self.cursor cursor.execute('SELECT DISTINCT class FROM device WHERE class LIKE ? ORDER BY class', (server,)) return [ row[0] for row in cursor.fetchall() ] @use_cursor def get_class_property(self, class_name, properties): cursor = self.cursor stmt = 'SELECT count,value FROM property_class WHERE class=? AND name LIKE ? ORDER BY count' result.append(class_name) result.append(len(properties)) for prop_name in properties: cursor.execute(stmt, (class_name, prop_name)) rows = cursor.fetchall() result.append(prop_name) result.append(str(len(rows))) for row in rows: result.append(row[1]) return result @use_cursor def get_class_property_hist(self, class_name, prop_name): cursor = self.cursor stmt = 'SELECT DISTINCT id FROM property_class_hist WHERE class=? AND AND name LIKE ? ORDER by date ASC' result = [] cursor.execute(stmt, (class_name, prop_name)) for row in cursor.fetchall(): idr = row[0] stmt = 'SELECT DATE_FORMAT(date,\'%Y-%m-%d %H:%i:%s\'),value,name,count FROM property_class_hist WHERE id =? AND class =?' cursor.execute(stmt, (idr, class_name)) rows = cursor.fetchall() result.append(rows[2]) result.append(rows[0]) result.append(str(rows[3])) for value in rows[1]: result.append(value) return result @use_cursor def get_class_property_list(self, class_name): cursor = self.cursor cursor.execute('SELECT DISTINCT name FROM property_class WHERE class LIKE ? order by NAME', (class_name,)) return [ row[0] for row in cursor.fetchall() ] @use_cursor def get_device_alias(self, dev_name): cursor = self.cursor cursor.execute('SELECT DISTINCT alias FROM device WHERE name LIKE ?', (dev_name,)) row = cursor.fetchone() if row is None: th_exc(DB_DeviceNotDefined, "No alias found for device '" + dev_name + "'", "DataBase::GetDeviceAlias()") return row[0] @use_cursor def get_device_alias_list(self, alias): cursor = self.cursor cursor.execute('SELECT DISTINCT alias FROM device WHERE alias LIKE ? ORDER BY alias', (alias,)) return [ row[0] for row in cursor.fetchall() ] @use_cursor def get_device_attribute_list(self, dev_name, attribute): cursor = self.cursor cursor.execute('SELECT DISTINCT attribute FROM property_attribute_device WHERE device=? AND attribute LIKE ? ORDER BY attribute', (dev_name, attribute,)) return [ row[0] for row in cursor.fetchall() ] @use_cursor def get_device_attribute_property(self, dev_name, attributes): cursor = self.cursor stmt = 'SELECT name,value FROM property_attribute_device WHERE device=? AND attribute LIKE ?' result = [dev_name, str(len(attributes))] for attribute in attributes: cursor.execute(stmt, (dev_name, attribute)) rows = cursor.fetchall() result.append(attribute) result.append(str(len(rows))) for row in rows: result.append(row[0]) result.append(row[1]) return result @use_cursor def get_device_attribute_property2(self, dev_name, attributes): cursor = self.cursor stmt = 'SELECT name,value FROM property_attribute_device WHERE device=? AND attribute LIKE ? ORDER BY name,count' result = [dev_name, str(len(attributes))] for attribute in attributes: cursor.execute(stmt, (dev_name, attribute)) rows = cursor.fetchall() result.append(attribute) j = 0 new_prop = True nb_props = 0 prop_size = 0 prop_names = [] prop_sizes = [] prop_values = [] for row in rows: prop_values.append(row[1]) if j == 0: old_name = row[0] else: name = row[0] if name != old_name: new_prop = True old_name = name else: new_prop = False j = j + 1 if new_prop == True: nb_props = nb_props + 1 prop_names.append(row[0]) if prop_size != 0: prop_sizes.append(prop_size) prop_size = 1 else: prop_size = prop_size + 1 result.append(str(nb_props)) j = 0 k = 0 for name in prop_names: result.append(name) result.append(prop_sizes[j]) for i in range(0, prop_sizes[j]): result.append(prop_values[k]) k = k + 1 j = j + 1 return result @use_cursor def get_device_attribute_property_hist(self, dev_name, attribute, prop_name): cursor = self.cursor stmt = 'SELECT DISTINCT id FROM property_attribute_device_hist WHERE device=? AND attribute LIKE ? AND name LIKE ? ORDER by date ASC' result = [] cursor.execute(stmt, (dev_name, attribute, prop_name)) for row in cursor.fetchall(): idr = row[0] stmt = 'SELECT DATE_FORMAT(date,\'%Y-%m-%d %H:%i:%s\'),value,attribute,name,count FROM property_attribute_device_hist WHERE id =? AND device =? ORDER BY count ASC' cursor.execute(stmt, (idr, class_name)) rows = cursor.fetchall() result.append(rows[2]) result.append(rows[3]) result.append(rows[0]) result.append(str(rows[4])) for value in rows[1]: result.append(value) return result @use_cursor def get_device_class_list(self, server_name): cursor = self.cursor result = [] cursor.execute('SELECT name,class FROM device WHERE server =? ORDER BY name', (server_name,)) for row in cursor.fetchall(): result.append(row[0]) result.append(row[1]) return result @use_cursor def get_device_domain_list(self, wildcard): cursor = self.cursor cursor.execute('SELECT DISTINCT domain FROM device WHERE name LIKE ? OR alias LIKE ? ORDER BY domain', (wildcard,wildcard)) return [ row[0] for row in cursor.fetchall() ] @use_cursor def get_device_exported_list(self, wildcard): cursor = self.cursor cursor.execute('SELECT DISTINCT name FROM device WHERE (name LIKE ? OR alias LIKE ?) AND exported=1 ORDER BY name', (wildcard,wildcard)) return [ row[0] for row in cursor.fetchall() ] @use_cursor def get_device_family_list(self, wildcard): cursor = self.cursor cursor.execute('SELECT DISTINCT family FROM device WHERE name LIKE ? OR alias LIKE ? ORDER BY family', (wildcard,wildcard)) return [ row[0] for row in cursor.fetchall() ] @use_cursor def get_device_info(self, dev_name): cursor = self.cursor cursor.execute('SELECT exported,ior,version,pid,server,host,started,stopped,class FROM device WHERE name =? or alias =?', (dev_name,dev_name)) result_long = [] result_str = [] for row in cursor.fetchall(): if ((row[4] == None) or (row[5] == None)): th_exc(DB_SQLError, "Wrong info in database for device '" + dev_name + "'", "DataBase::GetDeviceInfo()") result_str.append(dev_name) if raw[1] != None: result_str.append(str(raw[1])) else: result_str.append("") result_str.append(str(raw[2])) result_str.append(str(raw[4])) result_str.append(str(raw[5])) for i in range(0,2): cursor.execute('SELECT DATE_FORMAT(?,\'%D-%M-%Y at %H:%i:%s\')', raw[6 + i]) tmp_date = cursor.fetchone() if tmp_date == None: result_str.append("?") else: result_str.append(str(tmp_date)) for i in range(0,2): if raw[i] != None: result_long.append(raw[i]) result = (result_long, result_str) return result @use_cursor def get_device_list(self,server_name, class_name ): cursor = self.cursor cursor.execute('SELECT DISTINCT name FROM device WHERE server LIKE ? AND class LIKE ? ORDER BY name', (server_name, class_name)) return [ row[0] for row in cursor.fetchall() ] @use_cursor def get_device_wide_list(self, wildcard): cursor = self.cursor cursor.execute('SELECT DISTINCT name FROM device WHERE name LIKE ? ORDER BY name', (wildcard,)) return [ row[0] for row in cursor.fetchall() ] @use_cursor def get_device_member_list(self, wildcard): cursor = self.cursor cursor.execute('SELECT DISTINCT member FROM device WHERE name LIKE ? ORDER BY member', (wildcard,)) return [ row[0] for row in cursor.fetchall() ] @use_cursor def get_device_property(self, dev_name, properties): cursor = self.cursor stmt = 'SELECT count,value,name FROM property_device WHERE device = ? AND name LIKE ? ORDER BY count' result = [] result.append(dev_name) result.append(str(len(properties))) for prop in properties: result.append(prop) tmp_name = replace_wildcard(prop) cursor.execute(stmt, (dev_name, tmp_name)) rows = cursor.fetchall() result.append(attribute) result.append(str(len(rows))) for row in rows: result.append(row[1]) return result @use_cursor def get_device_property_hist(self, device_name, prop_name): cursor = self.cursor stmt = 'SELECT DISTINCT id FROM property_device_hist WHERE device=? AND name LIKE ? ORDER by date ASC' result = [] tmp_name = replace_wildcard(prop_name); cursor.execute(stmt, (class_name, device_name, tmp_name)) stmt = 'SELECT DATE_FORMAT(date,\'%Y-%m-%d %H:%i:%s\'),value,name,count FROM property_device_hist WHERE id =? AND device =? ORDER BY count ASC' for row in cursor.fetchall(): idr = row[0] cursor.execute(stmt, (idr, device_name)) rows = cursor.fetchall() result.append(rows[2]) result.append(rows[0]) result.append(str(rows[3])) for value in rows[1]: result.append(value) return result @use_cursor def get_device_server_class_list(self, server_name): cursor = self.cursor cursor.execute('SELECT DISTINCT class FROM device WHERE server LIKE ? ORDER BY class', (sever_name,)) return [ row[0] for row in cursor.fetchall() ] @use_cursor def get_exported_device_list_for_class(self, class_name): cursor = self.cursor cursor.execute('SELECT DISTINCT name FROM device WHERE class LIKE ? AND exported=1 ORDER BY name', (class_name,)) return [ row[0] for row in cursor.fetchall() ] @use_cursor def get_host_list(self, host_name): cursor = self.cursor cursor.execute('SELECT DISTINCT host FROM device WHERE host LIKE ? ORDER BY host', (host_name,)) return [ row[0] for row in cursor.fetchall() ] @use_cursor def get_host_server_list(self, host_name): cursor = self.cursor cursor.execute('SELECT DISTINCT server FROM device WHERE host LIKE ? ORDER BY server', (host_name,)) return [ row[0] for row in cursor.fetchall() ] def get_host_servers_info(self, host_name): servers = self.get_host_server_list(host_name) result = [] for server in servers: result.append(server) info = self.get_server_info(server) result.append(info[2]) result.append(info[3]) return result def get_instance_name_list(self, server_name): server_name = server_name + "\*" server_list = self.get_server_list(server_name) result = [] for server in server_list: names = server.split("/") result.append(names[1]) return result @use_cursor def get_object_list(self, name): cursor = self.cursor cursor.execute('SELECT DISTINCT object FROM property WHERE object LIKE ? ORDER BY object', (name,)) return [ row[0] for row in cursor.fetchall() ] @use_cursor def get_property(self, object_name, properties): cursor = self.cursor result = [] result.append(object_name) result.append(str(len(properties))) stmt = 'SELECT count,value,name FROM property WHERE object LIKE ? AND name LIKE ? ORDER BY count' for prop_name in properties: result.append(prop_name) prop_name = replace_wildcard(prop_name) cursor.execute(stmt, (object_name,prop_name)) rows = cursor.fetchall() n_rows = len(rows) result.append(n_rows) if n_rows: for row in rows: result.append(row[1]) else: result.append(" ") return result @use_cursor def get_property_hist(self, object_name, prop_name): cursor = self.cursor result = [] stmt = 'SELECT DISTINCT id FROM property_hist WHERE object=? AND name LIKE ? ORDER by date' prop_name = replace_wildcard(prop_name) cursor.execute(stmt, (object_name, prop_name)) stmt = 'SELECT DATE_FORMAT(date,\'%Y-%m-%d %H:%i:%s\'),value,name,count FROM property_hist WHERE id =? AND object =?' for row in cursor.fetchall(): idr = row[0] cursor.execute(stmt, (idr, object_name)) rows = cursor.fetchall() count = len(rows) if rows[3] == 0: count = 0 result.append(rows[2]) result.append(rows[0]) result.append(str(count)) for tmp_row in rows: result.append(tmp_row[1]) return result @use_cursor def get_property_list(self, object_name, wildcard): cursor = self.cursor cursor.execute('SELECT DISTINCT name FROM property WHERE object LIKE ? AND name LIKE ? ORDER BY name', (object_name,wildcard)) return [ row[0] for row in cursor.fetchall() ] @use_cursor def get_server_info(self, server_name): cursor = self.cursor cursor.execute('SELECT host,mode,level FROM server WHERE name =?', (server_name,)) result = [] result.append(server_name) row = cursor.fetchone() if row is None: result.append(" ") result.append(" ") result.append(" ") else: result.append(row[0]) result.append(row[1]) result.append(row[2]) return result @use_cursor def get_server_list(self, wildcard): cursor = self.cursor cursor.execute('SELECT DISTINCT server FROM device WHERE server LIKE ? ORDER BY server', (wildcard,)) return [ row[0] for row in cursor.fetchall() ] def get_server_list(self, wildcard): result = [] server_list = self.get_server_list(wildcard) for server in server_list: found = 0 server_name = server.split("/")[0] for res in result: if server_name.lower() == res.lower(): found = 1 if not found: result.append(server_name) return result @use_cursor def import_device(self, dev_name): cursor = self.cursor result_long = [] result_str = [] # Search first by server name and if nothing found by alias # Using OR takes much more time cursor.execute('"SELECT exported,ior,version,pid,server,host,class FROM device WHERE name =?', (dev_name,)) rows = cursor.fetchall() if len(rows) == 0: cursor.execute('"SELECT exported,ior,version,pid,server,host,class FROM device WHERE alias =?', (dev_name,)) rows = cursor.fetchall() if len(rows) == 0: th_exc(DB_DeviceNotDefined, "device " + dev_name + " not defined in the database !", "DataBase::ImportDevice()") for row in rows: result_str.append(dev_name) result_str.append(row[2]) result_str.append(row[4]) result_str.append(row[5]) result_str.append(row[6]) if row[1] != None: result_str.append(row[1]) else: result_str.append("") result_long.append(row[0]) result_long.append(row[3]) result = (result_long, result_str) return result @use_cursor def import_event(self, event_name): cursor = self.cursor result_long = [] result_str = [] cursor.execute('"SELECT exported,ior,version,pid,host FROM event WHERE name =?', (event_name,)) rows = cursor.fetchall() if len(rows) == 0: th_exc(DB_DeviceNotDefined, "event " + event_name + " not defined in the database !", "DataBase::ImportEvent()") for row in rows: result_str.append(event_name) result_str.append(row[1]) result_str.append(row[2]) result_str.append(row[4]) exported = -1 if row[0] != None: exported = row[0] result_long.append(exported) result_long.append(row[3]) result = (result_long, result_str) return result @use_cursor def info(self): cursor = self.cursor result = [] # db name info_str = "TANGO Database " + self.db_name result.append(info_str) # new line result.append("") # get start time of database cursor.execute('SELECT started FROM device WHERE name =?', (self.db_name,)) row = cursor.fetchone() info_str = "Running since ..." + str(row[0]) result.append(info_str) # new line result.append("") # get number of devices defined cursor.execute('SELECT COUNT(*) FROM device') row = cursor.fetchone() info_str = "Devices defined = " + str(row[0]) result.append(info_str) # get number of devices exported cursor.execute('SELECT COUNT(*) FROM device WHERE exported = 1') row = cursor.fetchone() info_str = "Devices exported = " + str(row[0]) result.append(info_str) # get number of device servers defined cursor.execute('SELECT COUNT(*) FROM device WHERE class = \"DServer\" ') row = cursor.fetchone() info_str = "Device servers defined = " + str(row[0]) result.append(info_str) # get number of device servers exported cursor.execute('SELECT COUNT(*) FROM device WHERE class = \"DServer\" AND exported = 1') row = cursor.fetchone() info_str = "Device servers exported = " + str(row[0]) result.append(info_str) # new line result.append("") # get number of device properties cursor.execute('SELECT COUNT(*) FROM property_device') row = cursor.fetchone() info_str = "Device properties defineed = " + str(row[0]) cursor.execute('SELECT COUNT(*) FROM property_device_hist') row = cursor.fetchone() info_str = info_str + " [History lgth = " + str(row[0]) + "]" result.append(info_str) # get number of class properties cursor.execute('SELECT COUNT(*) FROM property_class') row = cursor.fetchone() info_str = "Class properties defined = " + str(row[0]) cursor.execute('SELECT COUNT(*) FROM property_class_hist') row = cursor.fetchone() info_str = info_str + " [History lgth = " + str(row[0]) + "]" result.append(info_str) # get number of device attribute properties cursor.execute('SELECT COUNT(*) FROM property_attribute_device') row = cursor.fetchone() info_str = "Device attribute properties defined = " + str(row[0]) cursor.execute('SELECT COUNT(*) FROM property_attribute_device_hist') row = cursor.fetchone() info_str = info_str + " [History lgth = " + str(row[0]) + "]" result.append(info_str) # get number of class attribute properties cursor.execute('SELECT COUNT(*) FROM property_attribute_class') row = cursor.fetchone() info_str = "Class attribute properties defined = " + str(row[0]) cursor.execute('SELECT COUNT(*) FROM property_attribute_class_hist') row = cursor.fetchone() info_str = info_str + " [History lgth = " + str(row[0]) + "]" result.append(info_str) # get number of object properties cursor.execute('SELECT COUNT(*) FROM property') row = cursor.fetchone() info_str = "Object properties defined = " + str(row[0]) cursor.execute('SELECT COUNT(*) FROM property_hist') row = cursor.fetchone() info_str = info_str + " [History lgth = " + str(row[0]) + "]" result.append(info_str) return result @use_cursor def put_attribute_alias(self, attribute_name, attribute_alias): cursor = self.cursor attribute_name = attribute_name.lower() # first check if this alias exists cursor.execute('SELECT alias from attribute_alias WHERE alias=? AND name <> ? ', (attribute_alias,attribute_name)) rows = cursor.fetchall() if len(rows) > 0: self.warn_stream("DataBase::DbPutAttributeAlias(): this alias exists already ") th_exc(DB_SQLError, "alias " + attribute_alias + " already exists !", "DataBase::DbPutAttributeAlias()") tmp_names = attribute_name.split("/") if len(tmp_names) != 4: self.warn_stream("DataBase::DbPutAttributeAlias(): attribute name has bad syntax, must have 3 / in it") th_exc(DB_SQLError, "attribute name " + attribute_name + " has bad syntax, must have 3 / in it", "DataBase::DbPutAttributeAlias()") # first delete the current entry (if any) cursor.execute('DELETE FROM attribute_alias WHERE name=?', (attribute_name,)) # update the new value for this tuple tmp_device = tmp_names[0] + "/" + tmp_names[1] + "/" + tmp_names[2] tmp_attribute = tmp_names[3] cursor.execute('INSERT attribute_alias SET alias=? ,name=?, device=?,updated=NOW()', (attribute_alias, tmp_device, tmp_attribute)) @use_cursor def put_class_attribute_property(self, class_name, nb_attributes, attr_prop_list): cursor = self.cursor k = 0 for i in range(0,nb_attributes): tmp_attribute = attr_prop_list[k] nb_properties = int(attr_prop_list[k+1]) for j in range(k+2,k+nb_properties*2+2,2): tmp_name = attr_prop_list[j] tmp_value = attr_prop_list[j+1] # first delete the tuple (device,name,count) from the property table cursor.execute('DELETE FROM property_attribute_class WHERE class LIKE ? AND attribute LIKE ? AND name LIKE ?', (class_name, tmp_attribute, tmp_name)) # then insert the new value for this tuple cursor.execute('INSERT INTO property_attribute_class SET class=? ,attribute=?,name=?,count=\'1\',value=?,updated=NULL,accessed=NULL', (class_name, tmp_attribute, tmp_name, tmp_value)) # then insert the new value into the history table hist_id = self.get_id("class_attribute", cursor=cursor) cursor.execute('INSERT INTO property_attribute_class_hist SET class=?,attribute=?,name=?,id=?,count=\'1\',value=?', (class_name, tmp_attribute, tmp_name,hist_id,tmp_value)) self.purge_att_property("property_attribute_class_hist", "class", class_name, tmp_attribute, tmp_name, cursor=cursor) k = k + nb_properties*2+2 @use_cursor def put_class_attribute_property2(self, class_name, nb_attributes, attr_prop_list): cursor = self.cursor k = 0 for i in range(0,nb_attributes): tmp_attribute = attr_prop_list[k] nb_properties = int(attr_prop_list[k+1]) for jj in range(0,nb_properties,1): j = k + 2 tmp_name = attr_prop_list[j] # first delete the tuple (device,name,count) from the property table cursor.execute('DELETE FROM property_attribute_class WHERE class LIKE ? AND attribute LIKE ? AND name LIKE ?', (class_name, tmp_attribute, tmp_name)) n_rows = attr_prop_list[j+1] tmp_count = 0 for l in range(j+1,j+n_rows+1,1): tmp_value = attr_prop_list[l+1] tmp_count = tmp_count + 1 # then insert the new value for this tuple cursor.execute('INSERT INTO property_attribute_class SET class=? ,attribute=?,name=?,count=?,value=?,updated=NULL,accessed=NULL', (class_name, tmp_attribute, tmp_name, str(tmp_count), tmp_value)) # then insert the new value into the history table hist_id = self.get_id("class_attribute", cursor=cursor) cursor.execute('INSERT INTO property_attribute_class_hist SET class=?,attribute=?,name=?,id=?,count=?,value=?', (class_name, tmp_attribute, tmp_name,hist_id, str(tmp_count),tmp_value)) self.purge_att_property("property_attribute_class_hist", "class", class_name, tmp_attribute, tmp_name, cursor=cursor) k = k + n_rows + 2 k = k + 2 @use_cursor def put_class_property(self, class_name, nb_properties, attr_prop_list): cursor = self.cursor k = 0 for i in range(0,nb_properties): tmp_count = 0 tmp_name = attr_prop_list[k] n_rows = attr_prop_list[k+1] # first delete all tuples (device,name) from the property table cursor.execute('DELETE FROM property_class WHERE class LIKE ? AND name LIKE ?', (class_name, tmp_name)) for j in range(k+2,k+n_rows+2,1): tmp_value = attr_prop_list[j] tmp_count = tmp_count+1 # then insert the new value for this tuple cursor.execute('INSERT INTO property_class SET class=? ,name=?,count=?,value=?,updated=NULL,accessed=NULL', (class_name, tmp_name, str(tmp_count), tmp_value)) # then insert the new value into the history table hist_id = self.get_id("class", cursor=cursor) cursor.execute('INSERT INTO property_class_hist SET class=?,name=?,id=?,count=?,value=?', (class_name, tmp_name,hist_id, str(tmp_count),tmp_value)) self.purge_att_property("property_class_hist", "class", class_name, tmp_name, cursor=cursor) k = k + n_rows + 2 @use_cursor def put_device_alias(self, device_name, device_alias): cursor = self.cursor device_name = device_name.lower() # first check if this alias exists cursor.execute('SELECT alias from device WHERE alias=? AND name <>?', (device_alias, device_name)) rows = cursor.fetchall() if len(rows) > 0: self.warn_stream("DataBase::DbPutDeviceAlias(): this alias exists already ") th_exc(DB_SQLError, "alias " + device_alias + " already exists !", "DataBase::DbPutDeviceAlias()") # update the new value for this tuple cursor.execute('UPDATE device SET alias=? ,started=NOW() where name LIKE ?', (device_alias, device_name)) @use_cursor def put_device_attribute_property(self, device_name, nb_attributes, attr_prop_list): cursor = self.cursor k = 0 for i in range(0,nb_attributes): tmp_attribute = attr_prop_list[k] nb_properties = int(attr_prop_list[k+1]) for j in range(k+2,k+nb_properties*2+2,2): tmp_name = attr_prop_list[j] tmp_value = attr_prop_list[j+1] # first delete the tuple (device,name,count) from the property table cursor.execute('DELETE FROM property_attribute_device WHERE device LIKE ? AND attribute LIKE ? AND name LIKE ?', (device_name, tmp_attribute, tmp_name)) # then insert the new value for this tuple cursor.execute('INSERT INTO property_attribute_device SET device=? ,attribute=?,name=?,count=\'1\',value=?,updated=NULL,accessed=NULL', (device_name, tmp_attribute, tmp_name, tmp_value)) # then insert the new value into the history table hist_id = self.get_id("device_attribute", cursor=cursor) cursor.execute('INSERT INTO property_attribute_device_hist SET device=?,attribute=?,name=?,id=?,count=\'1\',value=?', (device_name, tmp_attribute, tmp_name,hist_id,tmp_value)) self.purge_att_property("property_attribute_device_hist", "device", device_name, tmp_attribute, tmp_name, cursor=cursor) k = k + nb_properties*2+2 @use_cursor def put_device_attribute_property2(self, device_name, nb_attributes, attr_prop_list): cursor = self.cursor k = 0 for i in range(0,nb_attributes): tmp_attribute = attr_prop_list[k] nb_properties = int(attr_prop_list[k+1]) for jj in range(0,nb_properties,1): j = k + 2 tmp_name = attr_prop_list[j] # first delete the tuple (device,name,count) from the property table cursor.execute('DELETE FROM property_attribute_device WHERE device LIKE ? AND attribute LIKE ? AND name LIKE ?', (device_name, tmp_attribute, tmp_name)) n_rows = attr_prop_list[j+1] tmp_count = 0 for l in range(j+1,j+n_rows+1,1): tmp_value = attr_prop_list[l+1] tmp_count = tmp_count + 1 # then insert the new value for this tuple cursor.execute('INSERT INTO property_attribute_device SET device=? ,attribute=?,name=?,count=?,value=?,updated=NULL,accessed=NULL', (device_name, tmp_attribute, tmp_name, str(tmp_count), tmp_value)) # then insert the new value into the history table hist_id = self.get_id("device_attribute", cursor=cursor) cursor.execute('INSERT INTO property_attribute_device_hist SET device=?,attribute=?,name=?,id=?,count=?,value=?', (device_name, tmp_attribute, tmp_name,hist_id, str(tmp_count),tmp_value)) self.purge_att_property("property_attribute_device_hist", "device", device_name, tmp_attribute, tmp_name, cursor=cursor) k = k + n_rows + 2 k = k + 2 @use_cursor def put_device_property(self, device_name, nb_properties, attr_prop_list): cursor = self.cursor k = 0 hist_id = self.get_id("device", cursor=cursor) for i in range(0,nb_properties): tmp_count = 0 tmp_name = attr_prop_list[k] n_rows = attr_prop_list[k+1] # first delete all tuples (device,name) from the property table cursor.execute('DELETE FROM property_device WHERE device LIKE ? AND name LIKE ?', (device_name, tmp_name)) for j in range(k+2,k+n_rows+2,1): tmp_value = attr_prop_list[j] tmp_count = tmp_count+1 # then insert the new value for this tuple cursor.execute('INSERT INTO property_device SET device=? ,name=?,count=?,value=?,updated=NULL,accessed=NULL', (device_name, tmp_name, str(tmp_count), tmp_value)) # then insert the new value into the history table cursor.execute('INSERT INTO property_device_hist SET device=?,name=?,id=?,count=?,value=?', (device_name, tmp_name,hist_id, str(tmp_count),tmp_value)) self.purge_att_property("property_device_hist", "device", device_name, tmp_name, cursor=cursor) k = k + n_rows + 2 @use_cursor def put_property(self, object_name, nb_properties, attr_prop_list): cursor = self.cursor k = 0 hist_id = self.get_id("object", cursor=cursor) for i in range(0,nb_properties): tmp_count = 0 tmp_name = attr_prop_list[k] n_rows = attr_prop_list[k+1] # first delete the property from the property table cursor.execute('DELETE FROM property WHERE object =? AND name =?', (object_name, tmp_name)) for j in range(k+2,k+n_rows+2,1): tmp_value = attr_prop_list[j] tmp_count = tmp_count+1 # then insert the new value for this tuple cursor.execute('INSERT INTO property SET object=? ,name=?,count=?,value=?,updated=NULL,accessed=NULL', (object_name, tmp_name, str(tmp_count), tmp_value)) # then insert the new value into the history table cursor.execute('INSERT INTO property_hist SET object=?,name=?,id=?,count=?,value=?', (object_name, tmp_name,hist_id, str(tmp_count),tmp_value)) self.purge_att_property("property_hist", "object", object_name, tmp_name, cursor=cursor) k = k + n_rows + 2 @use_cursor def put_server_info(self, tmp_server, tmp_host, tmp_mode, tmp_level, tmp_extra): cursor = self.cursor # If it is an empty host name -> get previous host where running previous_host = "" if self.fire_to_starter: if tmp_host == "": adm_dev_name = "dserver/" + tmp_server previous_host = self.get_device_host(adm_dev_name) # first delete the server from the server table cursor.execute('DELETE FROM server WHERE name=?', (tmp_server,)) # insert the new info for this server cursor.execute('INSERT INTO server SET name=? ,host=? ,mode=? ,level=?', ( tmp_server, tmp_host, tmp_mode, tmp_level)) # Update host's starter to update controlled servers list if self.fire_to_starter: hosts = [] if previous_host == "": hosts.append(tmp_host) else: hosts.append(previous_host) self.send_starter_cmd(hosts) @use_cursor def uexport_device(self, dev_name): cursor = self.cursor self._info("un-export device(dev_name=%s)", dev_name) cursor.execute('UPDATE device SET exported=0,stopped=NOW() WHERE name LIKE ?', (dev_name,)) @use_cursor def uexport_event(self, event_name): cursor = self.cursor self._info("un-export event (event_name=%s)", event_name) cursor.execute('UPDATE event SET exported=0,stopped=NOW() WHERE name LIKE ?', (event_name,)) @use_cursor def uexport_server(self, server_name): cursor = self.cursor self._info("un-export all devices from server ", server_name) cursor.execute('UPDATE device SET exported=0,stopped=NOW() WHERE server LIKE ?', (server_name,)) @use_cursor def delete_all_device_attribute_property(self, dev_name, attr_list): cursor = self.cursor for attr_name in attr_list: self._info("_delete_all_device_attribute_property(): delete device %s attribute %s property(ies) from database", dev_name, attr_name) #Is there something to delete ? cursor.execute('SELECT DISTINCT name FROM property_attribute_device WHERE device =? AND attribute = ?', (dev_name,attr_name)) rows = cursor.fetchall() if len(rows) != 0: cursor.execute('DELETE FROM property_attribute_device WHERE device = ? AND attribute = ?', (dev_name,attr_name)) # Mark this property as deleted for row in rows: hist_id = self.get_id('device_attribute', cursor=cursor) cursor.execute('INSERT INTO property_attribute_device_hist SET device=?,attribute=?,name=?,id=?,count=\'0\',value=\'DELETED\'', (dev_name,attr_name,row[0], hist_id)) self.purge_att_property("property_attribute_device_hist", "device", dev_name, attr_name, row[0], cursor=cursor) @use_cursor def my_sql_select(self, cmd): cursor = self.cursor cursor.execute(cmd) result_long = [] result_str = [] rows = cursor.fetchall() nb_fields = 0 for row in rows: if row == None: result_str.append("") result_long.append(0) else: for field in row: nb_fields = nb_fields + 1 if field != None: result_str.append(str(field)) result_long.append(1) else: result_str.append("") result_long.append(0) result_long.append(len(rows)) result_long.append(nb_fields) result = (result_long, result_str) return result @use_cursor def get_csdb_server_list(self): cursor = self.cursor cursor.execute('SELECT DISTINCT ior FROM device WHERE exported=1 AND domain=\'sys\' AND family=\'database\'') return [ row[0] for row in cursor.fetchall() ] @use_cursor def get_attribute_alias2(self, attr_name): cursor = self.cursor cursor.execute('SELECT alias from attribute_alias WHERE name LIKE ? ',(attr_name,)) return [ row[0] for row in cursor.fetchall() ] @use_cursor def get_alias_attribute(self, alias_name): cursor = self.cursor cursor.execute('SELECT name from attribute_alias WHERE alias LIKE ? ',(alias_name,)) return [ row[0] for row in cursor.fetchall() ] @use_cursor def rename_server(self, old_name, new_name): cursor = self.cursor # Check that the new name is not already used new_adm_name = "dserver/" + new_name cursor.execute('SELECT name from device WHERE name = ? ',(new_adm_name,)) rows = cursor.fetchall() if len(rows) != 0: th_exc(DB_SQLError, "Device server process name " + attribute_alias + "is already used !", "DataBase::DbRenameServer()") # get host where running previous_host = "" if self.fire_to_starter: try: adm_dev = "dserver/" + old_name previous_host = self.get_device_host(adm_dev) except: th_exc(DB_IncorrectServerName, "Server " + old_name + "not defined in database!", "DataBase::DbRenameServer()") # Change ds exec name. This means # 1 - Update the device's server column # 2 - Change the ds admin device name # 3 - Change admin device property (if any) # 4 - Change admin device attribute property (if any) old_adm_name = "dserver/" + old_name tmp_new = new_name.split('/') new_exec = tmp_new[0] new_inst = tmp_new[1] new_adm_name = "dserver/" + new_name cursor.execute('UPDATE device SET name =?, family =?, mamber =? WHERE name =?', (new_adm_name, new_exec, new_inst, old_adm_name)) cursor.execute('UPDATE property_device set device=? WHERE device=?', (new_adm_name, old_adm_name)) cursor.execute('UPDATE property_attribute_device set device=? WHERE device=?', (new_adm_name, old_adm_name)) # Update host's starter to update controlled servers list if self.fire_to_starter: hosts = [] if previous_host == "": hosts.append(tmp_host) else: hosts.append(previous_host) self.send_starter_cmd(hosts) class sqlite3(Tango_dbapi2): DB_API_NAME = 'sqlite3' def main(): db = Tango_sqlite3() db.add_device("MyServer/my1", ("a/b/c", ("a", "b", "c")), "MyClass") db.close_db() def get_db(**keys): return Executor.submit(sqlite3).result() if __name__ == "__main__": main() pytango-9.2.2/tango/databaseds/db_errors.py000066400000000000000000000005621316324373100207510ustar00rootroot00000000000000DB_SQLError = "DB_SQLError" DB_IncorrectArguments = "DB_IncorrectArguments" DB_IncorrectDeviceName = "DB_IncorrectDeviceName" DB_IncorrectServerName = "DB_IncorrectServerName" DB_DeviceNotDefined = "DB_DeviceNotDefined" DB_AliasNotDefined = "DB_AliasNotDefined" DB_NoFreeMySQLConnection = "DB_NoFreeMySQLConnection" DB_MySQLLibNotThreadSafe = "DB_MySQLLibNotThreadSafe" pytango-9.2.2/tango/databaseds/mysql2sqlite.sh000077500000000000000000000060071316324373100214260ustar00rootroot00000000000000#!/bin/sh # Converts a mysqldump file into a Sqlite 3 compatible file. It also extracts the MySQL `KEY xxxxx` from the # CREATE block and create them in separate commands _after_ all the INSERTs. # Awk is chosen because it's fast and portable. You can use gawk, original awk or even the lightning fast mawk. # The mysqldump file is traversed only once. # Usage: $ ./mysql2sqlite mysqldump-opts db-name | sqlite3 database.sqlite # Example: $ ./mysql2sqlite --no-data -u root -pMySecretPassWord myDbase | sqlite3 database.sqlite # Thanks to and @artemyk and @gkuenning for their nice tweaks. mysqldump --compatible=ansi --skip-extended-insert --compact "$@" | \ awk ' BEGIN { FS=",$" print "PRAGMA synchronous = OFF;" print "PRAGMA journal_mode = MEMORY;" print "BEGIN TRANSACTION;" } # CREATE TRIGGER statements have funny commenting. Remember we are in trigger. /^\/\*.*CREATE.*TRIGGER/ { gsub( /^.*TRIGGER/, "CREATE TRIGGER" ) print inTrigger = 1 next } # The end of CREATE TRIGGER has a stray comment terminator /END \*\/;;/ { gsub( /\*\//, "" ); print; inTrigger = 0; next } # The rest of triggers just get passed through inTrigger != 0 { print; next } # Skip other comments /^\/\*/ { next } # Print all `INSERT` lines. The single quotes are protected by another single quote. /INSERT/ { gsub( /\\\047/, "\047\047" ) gsub(/\\n/, "\n") gsub(/\\r/, "\r") gsub(/\\"/, "\"") gsub(/\\\\/, "\\") gsub(/\\\032/, "\032") print next } # Print the `CREATE` line as is and capture the table name. /^CREATE/ { print if ( match( $0, /\"[^\"]+/ ) ) tableName = substr( $0, RSTART+1, RLENGTH-1 ) } # Replace `FULLTEXT KEY` or any other `XXXXX KEY` except PRIMARY by `KEY` /^ [^"]+KEY/ && !/^ PRIMARY KEY/ { gsub( /.+KEY/, " KEY" ) } # Get rid of field lengths in KEY lines / KEY/ { gsub(/\([0-9]+\)/, "") } # Print all fields definition lines except the `KEY` lines. /^ / && !/^( KEY|\);)/ { gsub( /AUTO_INCREMENT|auto_increment/, "" ) gsub( /(CHARACTER SET|character set) [^ ]+ /, "" ) gsub( /DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP|default current_timestamp on update current_timestamp/, "" ) gsub( /(COLLATE|collate) [^ ]+ /, "" ) gsub(/(ENUM|enum)[^)]+\)/, "text ") gsub(/(SET|set)\([^)]+\)/, "text ") gsub(/UNSIGNED|unsigned/, "") gsub(/" [^ ]*(INT|int)[^ ]*/, "\" integer") if (prev) print prev "," prev = $1 } # `KEY` lines are extracted from the `CREATE` block and stored in array for later print # in a separate `CREATE KEY` command. The index name is prefixed by the table name to # avoid a sqlite error for duplicate index name. /^( KEY|\);)/ { if (prev) print prev prev="" if ($0 == ");"){ print } else { if ( match( $0, /\"[^"]+/ ) ) indexName = substr( $0, RSTART+1, RLENGTH-1 ) if ( match( $0, /\([^()]+/ ) ) indexKey = substr( $0, RSTART+1, RLENGTH-1 ) key[tableName]=key[tableName] "CREATE INDEX \"" tableName "_" indexName "\" ON \"" tableName "\" (" indexKey ");\n" } } # Print all `KEY` creation lines. END { for (table in key) printf key[table] print "END TRANSACTION;" } ' exit 0 pytango-9.2.2/tango/db.py000066400000000000000000002445701316324373100152730ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ """ This is an internal PyTango module. """ __all__ = ["db_init"] __docformat__ = "restructuredtext" import collections from ._tango import StdStringVector, Database, DbDatum, DbData, \ DbDevInfo, DbDevInfos, DbDevImportInfo, DbDevExportInfo, DbDevExportInfos, \ DbHistory, DbServerInfo, DbServerData from .utils import is_pure_str, is_non_str_seq, seq_2_StdStringVector, \ seq_2_DbDevInfos, seq_2_DbDevExportInfos, seq_2_DbData, DbData_2_dict from .utils import document_method as __document_method # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- # DbDatum extension # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- def __DbDatum___setitem(self, k, v): self.value_string[k] = v def __DbDatum___delitem(self, k): self.value_string.__delitem__(k) def __DbDatum_append(self, v): self.value_string.append(v) def __DbDatum_extend(self, v): self.value_string.extend(v) def __DbDatum___imul(self, n): self.value_string *= n def __init_DbDatum(): DbDatum.__len__ = lambda self: len(self.value_string) DbDatum.__getitem__ = lambda self, k: self.value_string[k] DbDatum.__setitem__ = __DbDatum___setitem DbDatum.__delitem__ = __DbDatum___delitem DbDatum.__iter__ = lambda self: self.value_string.__iter__() DbDatum.__contains__ = lambda self, v: self.value_string.__contains__(v) DbDatum.__add__ = lambda self, seq: self.value_string + seq DbDatum.__mul__ = lambda self, n: self.value_string * n DbDatum.__imul__ = __DbDatum___imul DbDatum.append = __DbDatum_append DbDatum.extend = __DbDatum_extend # DbDatum.__str__ = __DbDatum___str__ # DbDatum.__repr__ = __DbDatum___repr__ # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- # Database extension # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- def __Database__add_server(self, servname, dev_info, with_dserver=False): """ add_server( self, servname, dev_info, with_dserver=False) -> None Add a (group of) devices to the database. This is considered as a low level call because it may render the database inconsistent if it is not used properly. If *with_dserver* parameter is set to False (default), this call will only register the given dev_info(s). You should include in the list of dev_info an entry to the usually hidden **DServer** device. If *with_dserver* parameter is set to True, the call will add an additional **DServer** device if it is not included in the *dev_info* parameter. Example using *with_dserver=True*:: dev_info1 = DbDevInfo() dev_info1.name = 'my/own/device' dev_info1._class = 'MyDevice' dev_info1.server = 'MyServer/test' db.add_server(dev_info1.server, dev_info, with_dserver=True) Same example using *with_dserver=False*:: dev_info1 = DbDevInfo() dev_info1.name = 'my/own/device' dev_info1._class = 'MyDevice' dev_info1.server = 'MyServer/test' dev_info2 = DbDevInfo() dev_info1.name = 'dserver/' + dev_info1.server dev_info1._class = 'DServer dev_info1.server = dev_info1.server dev_info = dev_info1, dev_info2 db.add_server(dev_info1.server, dev_info) .. versionadded:: 8.1.7 added *with_dserver* parameter Parameters : - servname : (str) server name - dev_info : (sequence | DbDevInfos | DbDevInfo) containing the server device(s) information - with_dserver: (bool) whether or not to auto create **DServer** device in server Return : None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) """ if not isinstance(dev_info, collections.Sequence) and \ not isinstance(dev_info, DbDevInfo): raise TypeError( 'Value must be a DbDevInfos, a seq or a DbDevInfo') if isinstance(dev_info, DbDevInfos): pass elif isinstance(dev_info, DbDevInfo): dev_info = seq_2_DbDevInfos((dev_info,)) else: dev_info = seq_2_DbDevInfos(dev_info) if with_dserver: has_dserver = False for i in dev_info: if i._class == "DServer": has_dserver = True break if not has_dserver: dserver_info = DbDevInfo() dserver_info.name = 'dserver/' + dev_info[0].server dserver_info._class = 'DServer' dserver_info.server = dev_info[0].server dev_info.append(dserver_info) self._add_server(servname, dev_info) def __Database__export_server(self, dev_info): """ export_server(self, dev_info) -> None Export a group of devices to the database. Parameters : - devinfo : (sequence | DbDevExportInfos | DbDevExportInfo) containing the device(s) to export information Return : None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) """ if not isinstance(dev_info, collections.Sequence) and \ not isinstance(dev_info, DbDevExportInfo): raise TypeError( 'Value must be a DbDevExportInfos, a seq or ' 'a DbDevExportInfo') if isinstance(dev_info, DbDevExportInfos): pass elif isinstance(dev_info, DbDevExportInfo): dev_info = seq_2_DbDevExportInfos((dev_info), ) else: dev_info = seq_2_DbDevExportInfos(dev_info) self._export_server(dev_info) def __Database__generic_get_property(self, obj_name, value, f): """internal usage""" ret = None if isinstance(value, DbData): new_value = value elif isinstance(value, DbDatum): new_value = DbData() new_value.append(value) elif is_pure_str(value): new_value = DbData() new_value.append(DbDatum(value)) elif isinstance(value, collections.Sequence): new_value = DbData() for e in value: if isinstance(e, DbDatum): new_value.append(e) else: new_value.append(DbDatum(str(e))) elif isinstance(value, collections.Mapping): new_value = DbData() for k, v in value.items(): if isinstance(v, DbDatum): new_value.append(v) else: new_value.append(DbDatum(k)) ret = value else: raise TypeError( 'Value must be a string, tango.DbDatum, ' 'tango.DbData, a sequence or a dictionary') f(obj_name, new_value) if ret is None: ret = {} return DbData_2_dict(new_value, ret) def __Database__generic_put_property(self, obj_name, value, f): """internal usage""" if isinstance(value, DbData): pass elif isinstance(value, DbDatum): new_value = DbData() new_value.append(value) value = new_value elif is_non_str_seq(value): new_value = seq_2_DbData(value) elif isinstance(value, collections.Mapping): new_value = DbData() for k, v in value.items(): if isinstance(v, DbDatum): new_value.append(v) continue db_datum = DbDatum(k) if is_non_str_seq(v): seq_2_StdStringVector(v, db_datum.value_string) else: db_datum.value_string.append(str(v)) new_value.append(db_datum) value = new_value else: raise TypeError( 'Value must be a tango.DbDatum, tango.DbData, ' 'a sequence or a dictionary') return f(obj_name, value) def __Database__generic_delete_property(self, obj_name, value, f): """internal usage""" if isinstance(value, DbData): new_value = value elif isinstance(value, DbDatum): new_value = DbData() new_value.append(value) elif is_pure_str(value): new_value = DbData() new_value.append(DbDatum(value)) elif isinstance(value, collections.Sequence): new_value = DbData() for e in value: if isinstance(e, DbDatum): new_value.append(e) else: new_value.append(DbDatum(str(e))) elif isinstance(value, collections.Mapping): new_value = DbData() for k, v in value.items(): if isinstance(v, DbDatum): new_value.append(v) else: new_value.append(DbDatum(k)) else: raise TypeError( 'Value must be a string, tango.DbDatum, ' 'tango.DbData, a sequence or a dictionary') return f(obj_name, new_value) def __Database__put_property(self, obj_name, value): """ put_property(self, obj_name, value) -> None Insert or update a list of properties for the specified object. Parameters : - obj_name : (str) object name - value : can be one of the following: 1. DbDatum - single property data to be inserted 2. DbData - several property data to be inserted 3. sequence - several property data to be inserted 4. dict - keys are property names and value has data to be inserted 5. dict - keys are property names and str(obj) is property value 6. dict> - keys are property names and value has data to be inserted Return : None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError)""" return __Database__generic_put_property(self, obj_name, value, self._put_property) def __Database__get_property(self, obj_name, value): """ get_property(self, obj_name, value) -> dict> Query the database for a list of object (i.e non-device) properties. Parameters : - obj_name : (str) object name - value : can be one of the following: 1. str [in] - single property data to be fetched 2. DbDatum [in] - single property data to be fetched 3. DbData [in,out] - several property data to be fetched In this case (direct C++ API) the DbData will be filled with the property values 4. sequence [in] - several property data to be fetched 5. sequence [in] - several property data to be fetched 6. dict [in,out] - keys are property names In this case the given dict values will be changed to contain the several property values Return : a dictionary which keys are the property names the value associated with each key being a a sequence of strings being the property value. Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError)""" return __Database__generic_get_property(self, obj_name, value, self._get_property) def __Database__get_property_forced(self, obj_name, value): return __Database__generic_get_property(self, obj_name, value, self._get_property_forced) __Database__get_property_forced.__doc__ = __Database__get_property.__doc__ def __Database__delete_property(self, obj_name, value): """ delete_property(self, obj_name, value) -> None Delete a the given of properties for the specified object. Parameters : - obj_name : (str) object name - value : can be one of the following: 1. str [in] - single property data to be deleted 2. DbDatum [in] - single property data to be deleted 3. DbData [in] - several property data to be deleted 4. sequence [in]- several property data to be deleted 5. sequence [in] - several property data to be deleted 6. dict [in] - keys are property names to be deleted (values are ignored) 7. dict [in] - several DbDatum.name are property names to be deleted (keys are ignored) Return : None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError)""" return __Database__generic_delete_property(self, obj_name, value, self._delete_property) def __Database__get_device_property(self, dev_name, value): """ get_device_property(self, dev_name, value) -> dict> Query the database for a list of device properties. Parameters : - dev_name : (str) object name - value : can be one of the following: 1. str [in] - single property data to be fetched 2. DbDatum [in] - single property data to be fetched 3. DbData [in,out] - several property data to be fetched In this case (direct C++ API) the DbData will be filled with the property values 4. sequence [in] - several property data to be fetched 5. sequence [in] - several property data to be fetched 6. dict [in,out] - keys are property names In this case the given dict values will be changed to contain the several property values Return : a dictionary which keys are the property names the value associated with each key being a a sequence of strings being the property value. Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError)""" return __Database__generic_get_property(self, dev_name, value, self._get_device_property) def __Database__put_device_property(self, dev_name, value): """ put_device_property(self, dev_name, value) -> None Insert or update a list of properties for the specified device. Parameters : - dev_name : (str) object name - value : can be one of the following: 1. DbDatum - single property data to be inserted 2. DbData - several property data to be inserted 3. sequence - several property data to be inserted 4. dict - keys are property names and value has data to be inserted 5. dict - keys are property names and str(obj) is property value 6. dict> - keys are property names and value has data to be inserted Return : None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError)""" return __Database__generic_put_property(self, dev_name, value, self._put_device_property) def __Database__delete_device_property(self, dev_name, value): """ delete_device_property(self, dev_name, value) -> None Delete a the given of properties for the specified device. Parameters : - dev_name : (str) object name - value : can be one of the following: 1. str [in] - single property data to be deleted 2. DbDatum [in] - single property data to be deleted 3. DbData [in] - several property data to be deleted 4. sequence [in]- several property data to be deleted 5. sequence [in] - several property data to be deleted 6. dict [in] - keys are property names to be deleted (values are ignored) 7. dict [in] - several DbDatum.name are property names to be deleted (keys are ignored) Return : None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError)""" return __Database__generic_delete_property(self, dev_name, value, self._delete_device_property) def __Database__get_device_property_list(self, dev_name, wildcard, array=None): """ get_device_property_list(self, dev_name, wildcard, array=None) -> DbData Query the database for a list of properties defined for the specified device and which match the specified wildcard. If array parameter is given, it must be an object implementing de 'append' method. If given, it is filled with the matching property names. If not given the method returns a new DbDatum containing the matching property names. New in PyTango 7.0.0 Parameters : - dev_name : (str) device name - wildcard : (str) property name wildcard - array : [out] (sequence) (optional) array that will contain the matching property names. Return : if container is None, return is a new DbDatum containing the matching property names. Otherwise returns the given array filled with the property names Throws : ConnectionFailed, CommunicationFailed, DevFailed from device""" if array is None: return self._get_device_property_list(dev_name, wildcard) elif isinstance(array, StdStringVector): return self._get_device_property_list(dev_name, wildcard, array) elif is_non_str_seq(array): res = self._get_device_property_list(dev_name, wildcard) for e in res: array.append(e) return array def __Database__get_device_attribute_property(self, dev_name, value): """ get_device_attribute_property(self, dev_name, value) -> dict>> Query the database for a list of device attribute properties for the specified device. The method returns all the properties for the specified attributes. Parameters : - dev_name : (string) device name - value : can be one of the following: 1. str [in] - single attribute properties to be fetched 2. DbDatum [in] - single attribute properties to be fetched 3. DbData [in,out] - several attribute properties to be fetched In this case (direct C++ API) the DbData will be filled with the property values 4. sequence [in] - several attribute properties to be fetched 5. sequence [in] - several attribute properties to be fetched 6. dict [in,out] - keys are attribute names In this case the given dict values will be changed to contain the several attribute property values Return : a dictionary which keys are the attribute names the value associated with each key being a another dictionary where keys are property names and value is a DbDatum containing the property value. Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError)""" ret = None if isinstance(value, DbData): new_value = value elif isinstance(value, DbDatum): new_value = DbData() new_value.append(value) elif is_pure_str(value): new_value = DbData() new_value.append(DbDatum(value)) elif isinstance(value, collections.Sequence): new_value = DbData() for e in value: if isinstance(e, DbDatum): new_value.append(e) else: new_value.append(DbDatum(str(e))) elif isinstance(value, collections.Mapping): new_value = DbData() for k, v in value.items(): if isinstance(v, DbDatum): new_value.append(v) else: new_value.append(DbDatum(k)) else: raise TypeError( 'Value must be a string, tango.DbDatum, ' 'tango.DbData, a sequence or a dictionary') if ret is None: ret = {} self._get_device_attribute_property(dev_name, new_value) nb_items = len(new_value) i = 0 while i < nb_items: db_datum = new_value[i] curr_dict = {} ret[db_datum.name] = curr_dict nb_props = int(db_datum[0]) i += 1 for k in range(nb_props): db_datum = new_value[i] curr_dict[db_datum.name] = db_datum.value_string i += 1 return ret def __Database__put_device_attribute_property(self, dev_name, value): """ put_device_attribute_property( self, dev_name, value) -> None Insert or update a list of properties for the specified device. Parameters : - dev_name : (str) device name - value : can be one of the following: 1. DbData - several property data to be inserted 2. sequence - several property data to be inserted 3. dict> keys are attribute names and value being another dictionary which keys are the attribute property names and the value associated with each key being: 3.1 seq 3.2 tango.DbDatum Return : None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError)""" if isinstance(value, DbData): pass elif is_non_str_seq(value): new_value = seq_2_DbData(value) elif isinstance(value, collections.Mapping): new_value = DbData() for k1, v1 in value.items(): attr = DbDatum(k1) attr.append(str(len(v1))) new_value.append(attr) for k2, v2 in v1.items(): if isinstance(v2, DbDatum): new_value.append(v2) continue db_datum = DbDatum(k2) if is_non_str_seq(v2): seq_2_StdStringVector(v2, db_datum.value_string) else: if not is_pure_str(v2): v2 = repr(v2) db_datum.value_string.append(v2) new_value.append(db_datum) value = new_value else: raise TypeError( 'Value must be a tango.DbData,' 'a sequence or a dictionary') return self._put_device_attribute_property(dev_name, value) def __Database__delete_device_attribute_property(self, dev_name, value): """ delete_device_attribute_property(self, dev_name, value) -> None Delete a list of attribute properties for the specified device. Parameters : - devname : (string) device name - propnames : can be one of the following: 1. DbData [in] - several property data to be deleted 2. sequence [in]- several property data to be deleted 3. sequence [in] - several property data to be deleted 3. dict> keys are attribute names and value being a list of attribute property names Return : None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError)""" if isinstance(value, DbData): new_value = value elif is_non_str_seq(value): new_value = seq_2_DbData(value) elif isinstance(value, collections.Mapping): new_value = DbData() for k1, v1 in value.items(): attr = DbDatum(k1) attr.append(str(len(v1))) new_value.append(attr) for k2 in v1: new_value.append(DbDatum(k2)) else: raise TypeError( 'Value must be a string, tango.DbDatum, ' 'tango.DbData, a sequence or a dictionary') return self._delete_device_attribute_property(dev_name, new_value) def __Database__get_class_property(self, class_name, value): """ get_class_property(self, class_name, value) -> dict> Query the database for a list of class properties. Parameters : - class_name : (str) class name - value : can be one of the following: 1. str [in] - single property data to be fetched 2. tango.DbDatum [in] - single property data to be fetched 3. tango.DbData [in,out] - several property data to be fetched In this case (direct C++ API) the DbData will be filled with the property values 4. sequence [in] - several property data to be fetched 5. sequence [in] - several property data to be fetched 6. dict [in,out] - keys are property names In this case the given dict values will be changed to contain the several property values Return : a dictionary which keys are the property names the value associated with each key being a a sequence of strings being the property value. Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError)""" return __Database__generic_get_property(self, class_name, value, self._get_class_property) def __Database__put_class_property(self, class_name, value): """ put_class_property(self, class_name, value) -> None Insert or update a list of properties for the specified class. Parameters : - class_name : (str) class name - value : can be one of the following: 1. DbDatum - single property data to be inserted 2. DbData - several property data to be inserted 3. sequence - several property data to be inserted 4. dict - keys are property names and value has data to be inserted 5. dict - keys are property names and str(obj) is property value 6. dict> - keys are property names and value has data to be inserted Return : None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError)""" return __Database__generic_put_property(self, class_name, value, self._put_class_property) def __Database__delete_class_property(self, class_name, value): """ delete_class_property(self, class_name, value) -> None Delete a the given of properties for the specified class. Parameters : - class_name : (str) class name - value : can be one of the following: 1. str [in] - single property data to be deleted 2. DbDatum [in] - single property data to be deleted 3. DbData [in] - several property data to be deleted 4. sequence [in]- several property data to be deleted 5. sequence [in] - several property data to be deleted 6. dict [in] - keys are property names to be deleted (values are ignored) 7. dict [in] - several DbDatum.name are property names to be deleted (keys are ignored) Return : None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError)""" return __Database__generic_delete_property(self, class_name, value, self._delete_class_property) def __Database__get_class_attribute_property(self, class_name, value): """ get_class_attribute_property( self, class_name, value) -> dict> Query the database for a list of class attribute properties for the specified class. The method returns all the properties for the specified attributes. Parameters : - class_name : (str) class name - propnames : can be one of the following: 1. str [in] - single attribute properties to be fetched 2. DbDatum [in] - single attribute properties to be fetched 3. DbData [in,out] - several attribute properties to be fetched In this case (direct C++ API) the DbData will be filled with the property values 4. sequence [in] - several attribute properties to be fetched 5. sequence [in] - several attribute properties to be fetched 6. dict [in,out] - keys are attribute names In this case the given dict values will be changed to contain the several attribute property values Return : a dictionary which keys are the attribute names the value associated with each key being a another dictionary where keys are property names and value is a sequence of strings being the property value. Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError)""" ret = None if isinstance(value, DbData): new_value = value elif isinstance(value, DbDatum): new_value = DbData() new_value.append(value) elif is_pure_str(value): new_value = DbData() new_value.append(DbDatum(value)) elif isinstance(value, collections.Sequence): new_value = DbData() for e in value: if isinstance(e, DbDatum): new_value.append(e) else: new_value.append(DbDatum(str(e))) elif isinstance(value, collections.Mapping): new_value = DbData() for k, v in value.items(): if isinstance(v, DbDatum): new_value.append(v) else: new_value.append(DbDatum(k)) else: raise TypeError( 'Value must be a string, tango.DbDatum, ' 'tango.DbData, a sequence or a dictionary') self._get_class_attribute_property(class_name, new_value) if ret is None: ret = {} nb_items = len(new_value) i = 0 while i < nb_items: db_datum = new_value[i] curr_dict = {} ret[db_datum.name] = curr_dict nb_props = int(db_datum[0]) i += 1 for k in range(nb_props): db_datum = new_value[i] curr_dict[db_datum.name] = db_datum.value_string i += 1 return ret def __Database__put_class_attribute_property(self, class_name, value): """ put_class_attribute_property(self, class_name, value) -> None Insert or update a list of properties for the specified class. Parameters : - class_name : (str) class name - propdata : can be one of the following: 1. tango.DbData - several property data to be inserted 2. sequence - several property data to be inserted 3. dict> keys are attribute names and value being another dictionary which keys are the attribute property names and the value associated with each key being: 3.1 seq 3.2 tango.DbDatum Return : None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError)""" if isinstance(value, DbData): pass elif is_non_str_seq(value): new_value = seq_2_DbData(value) elif isinstance(value, collections.Mapping): new_value = DbData() for k1, v1 in value.items(): attr = DbDatum(k1) attr.append(str(len(v1))) new_value.append(attr) for k2, v2 in v1.items(): if isinstance(v2, DbDatum): new_value.append(v2) continue db_datum = DbDatum(k2) if is_non_str_seq(v2): seq_2_StdStringVector(v2, db_datum.value_string) else: db_datum.value_string.append(str(v2)) new_value.append(db_datum) value = new_value else: raise TypeError( 'Value must be a tango.DbData, ' 'a sequence or a dictionary') return self._put_class_attribute_property(class_name, value) def __Database__delete_class_attribute_property(self, class_name, value): """ delete_class_attribute_property(self, class_name, value) -> None Delete a list of attribute properties for the specified class. Parameters : - class_name : (str) class name - propnames : can be one of the following: 1. DbData [in] - several property data to be deleted 2. sequence [in]- several property data to be deleted 3. sequence [in] - several property data to be deleted 4. dict> keys are attribute names and value being a list of attribute property names Return : None Throws : ConnectionFailed, CommunicationFailed DevFailed from device (DB_SQLError)""" if isinstance(value, DbData): new_value = value elif is_non_str_seq(value): new_value = seq_2_DbData(value) elif isinstance(value, collections.Mapping): new_value = DbData() for k1, v1 in value.items(): attr = DbDatum(k1) attr.append(str(len(v1))) new_value.append(attr) for k2 in v1: new_value.append(DbDatum(k2)) else: raise TypeError( 'Value must be a DbDatum, DbData, a sequence or a dictionary') return self._delete_class_attribute_property(class_name, new_value) def __Database__get_service_list(self, filter='.*'): import re data = self.get_property('CtrlSystem', 'Services') res = {} filter_re = re.compile(filter) for service in data['Services']: service_name, service_value = service.split(':') if not filter_re.match(service_name) is None: res[service_name] = service_value return res def __Database__str(self): return "Database(%s, %s)" % (self.get_db_host(), self.get_db_port()) def __init_Database(): Database.add_server = __Database__add_server Database.export_server = __Database__export_server Database.put_property = __Database__put_property Database.get_property = __Database__get_property Database.get_property_forced = __Database__get_property_forced Database.delete_property = __Database__delete_property Database.get_device_property = __Database__get_device_property Database.put_device_property = __Database__put_device_property Database.delete_device_property = __Database__delete_device_property Database.get_device_property_list = __Database__get_device_property_list Database.get_device_attribute_property = __Database__get_device_attribute_property Database.put_device_attribute_property = __Database__put_device_attribute_property Database.delete_device_attribute_property = __Database__delete_device_attribute_property Database.get_class_property = __Database__get_class_property Database.put_class_property = __Database__put_class_property Database.delete_class_property = __Database__delete_class_property Database.get_class_attribute_property = __Database__get_class_attribute_property Database.put_class_attribute_property = __Database__put_class_attribute_property Database.delete_class_attribute_property = __Database__delete_class_attribute_property Database.get_service_list = __Database__get_service_list Database.__str__ = __Database__str Database.__repr__ = __Database__str def __doc_Database(): def document_method(method_name, desc, append=True): return __document_method(Database, method_name, desc, append) Database.__doc__ = """ Database is the high level Tango object which contains the link to the static database. Database provides methods for all database commands : get_device_property(), put_device_property(), info(), etc.. To create a Database, use the default constructor. Example:: db = Database() The constructor uses the TANGO_HOST env. variable to determine which instance of the Database to connect to.""" document_method("write_filedatabase", """ write_filedatabase(self) -> None Force a write to the file if using a file based database. Parameters : None Return : None New in PyTango 7.0.0 """) document_method("reread_filedatabase", """ reread_filedatabase(self) -> None Force a complete refresh over the database if using a file based database. Parameters : None Return : None New in PyTango 7.0.0 """) document_method("build_connection", """ build_connection(self) -> None Tries to build a connection to the Database server. Parameters : None Return : None New in PyTango 7.0.0 """) document_method("check_tango_host", """ check_tango_host(self, tango_host_env) -> None Check the TANGO_HOST environment variable syntax and extract database server host(s) and port(s) from it. Parameters : - tango_host_env : (str) The TANGO_HOST env. variable value Return : None New in PyTango 7.0.0 """) document_method("check_access_control", """ check_access_control(self, dev_name) -> AccessControlType Check the access for the given device for this client. Parameters : - dev_name : (str) device name Return : the access control type as a AccessControlType object New in PyTango 7.0.0 """) document_method("is_control_access_checked", """ is_control_access_checked(self) -> bool Returns True if control access is checked or False otherwise. Parameters : None Return : (bool) True if control access is checked or False New in PyTango 7.0.0 """) document_method("set_access_checked", """ set_access_checked(self, val) -> None Sets or unsets the control access check. Parameters : - val : (bool) True to set or False to unset the access control Return : None New in PyTango 7.0.0 """) document_method("get_access_except_errors", """ get_access_except_errors(self) -> DevErrorList Returns a reference to the control access exceptions. Parameters : None Return : DevErrorList New in PyTango 7.0.0 """) document_method("is_multi_tango_host", """ is_multi_tango_host(self) -> bool Returns if in multi tango host. Parameters : None Return : True if multi tango host or False otherwise New in PyTango 7.1.4 """) document_method("get_file_name", """ get_file_name(self) -> str Returns the database file name or throws an exception if not using a file database Parameters : None Return : a string containing the database file name Throws : DevFailed New in PyTango 7.2.0 """) document_method("get_info", """ get_info(self) -> str Query the database for some general info about the tables. Parameters : None Return : a multiline string """) document_method("get_host_list", """ get_host_list(self) -> DbDatum get_host_list(self, wildcard) -> DbDatum Returns the list of all host names registered in the database. Parameters : - wildcard : (str) (optional) wildcard (eg: 'l-c0*') Return : DbDatum with the list of registered host names """) document_method("get_services", """ get_services(self, serv_name, inst_name) -> DbDatum Query database for specified services. Parameters : - serv_name : (str) service name - inst_name : (str) instance name (can be a wildcard character ('*')) Return : DbDatum with the list of available services New in PyTango 3.0.4 """) document_method("get_device_service_list", """ get_device_service_list(self, dev_name) -> DbDatum Query database for the list of services provided by the given device. Parameters : - dev_name : (str) device name Return : DbDatum with the list of services New in PyTango 8.1.0 """) document_method("register_service", """ register_service(self, serv_name, inst_name, dev_name) -> None Register the specified service wihtin the database. Parameters : - serv_name : (str) service name - inst_name : (str) instance name - dev_name : (str) device name Return : None New in PyTango 3.0.4 """) document_method("unregister_service", """ unregister_service(self, serv_name, inst_name) -> None Unregister the specified service from the database. Parameters : - serv_name : (str) service name - inst_name : (str) instance name Return : None New in PyTango 3.0.4 """) document_method("add_device", """ add_device(self, dev_info) -> None Add a device to the database. The device name, server and class are specified in the DbDevInfo structure Example : dev_info = DbDevInfo() dev_info.name = 'my/own/device' dev_info._class = 'MyDevice' dev_info.server = 'MyServer/test' db.add_device(dev_info) Parameters : - dev_info : (DbDevInfo) device information Return : None """) document_method("delete_device", """ delete_device(self, dev_name) -> None Delete the device of the specified name from the database. Parameters : - dev_name : (str) device name Return : None """) document_method("import_device", """ import_device(self, dev_name) -> DbDevImportInfo Query the databse for the export info of the specified device. Example : dev_imp_info = db.import_device('my/own/device') print(dev_imp_info.name) print(dev_imp_info.exported) print(dev_imp_info.ior) print(dev_imp_info.version) Parameters : - dev_name : (str) device name Return : DbDevImportInfo """) document_method("get_device_info", """ get_device_info(self, dev_name) -> DbDevFullInfo Query the databse for the full info of the specified device. Example : dev_info = db.get_device_info('my/own/device') print(dev_info.name) print(dev_info.class_name) print(dev_info.ds_full_name) print(dev_info.exported) print(dev_info.ior) print(dev_info.version) print(dev_info.pid) print(dev_info.started_date) print(dev_info.stopped_date) Parameters : - dev_name : (str) device name Return : DbDevFullInfo New in PyTango 8.1.0 """) document_method("export_device", """ export_device(self, dev_export) -> None Update the export info for this device in the database. Example : dev_export = DbDevExportInfo() dev_export.name = 'my/own/device' dev_export.ior = dev_export.host = dev_export.version = '3.0' dev_export.pid = '....' db.export_device(dev_export) Parameters : - dev_export : (DbDevExportInfo) export information Return : None """) document_method("unexport_device", """ unexport_device(self, dev_name) -> None Mark the specified device as unexported in the database Example : db.unexport_device('my/own/device') Parameters : - dev_name : (str) device name Return : None """) document_method("get_device_name", """ get_device_name(self, serv_name, class_name) -> DbDatum Query the database for a list of devices served by a server for a given device class Parameters : - serv_name : (str) server name - class_name : (str) device class name Return : DbDatum with the list of device names """) document_method("get_device_exported", """ get_device_exported(self, filter) -> DbDatum Query the database for a list of exported devices whose names satisfy the supplied filter (* is wildcard for any character(s)) Parameters : - filter : (str) device name filter (wildcard) Return : DbDatum with the list of exported devices """) document_method("get_device_domain", """ get_device_domain(self, wildcard) -> DbDatum Query the database for a list of of device domain names which match the wildcard provided (* is wildcard for any character(s)). Domain names are case insensitive. Parameters : - wildcard : (str) domain filter Return : DbDatum with the list of device domain names """) document_method("get_device_family", """ get_device_family(self, wildcard) -> DbDatum Query the database for a list of of device family names which match the wildcard provided (* is wildcard for any character(s)). Family names are case insensitive. Parameters : - wildcard : (str) family filter Return : DbDatum with the list of device family names """) document_method("get_device_member", """ get_device_member(self, wildcard) -> DbDatum Query the database for a list of of device member names which match the wildcard provided (* is wildcard for any character(s)). Member names are case insensitive. Parameters : - wildcard : (str) member filter Return : DbDatum with the list of device member names """) document_method("get_device_alias", """ get_device_alias(self, alias) -> str Get the device name from an alias. Parameters : - alias : (str) alias Return : device name .. deprecated:: 8.1.0 Use :meth:`~tango.Database.get_device_from_alias` instead """) document_method("get_alias", """ get_alias(self, alias) -> str Get the device alias name from its name. Parameters : - alias : (str) device name Return : alias New in PyTango 3.0.4 .. deprecated:: 8.1.0 Use :meth:`~tango.Database.get_alias_from_device` instead """) document_method("get_device_from_alias", """ get_device_from_alias(self, alias) -> str Get the device name from an alias. Parameters : - alias : (str) alias Return : device name New in PyTango 8.1.0 """) document_method("get_alias_from_device", """ get_alias_from_device(self, alias) -> str Get the device alias name from its name. Parameters : - alias : (str) device name Return : alias New in PyTango 8.1.0 """) document_method("get_device_alias_list", """ get_device_alias_list(self, filter) -> DbDatum Get device alias list. The parameter alias is a string to filter the alias list returned. Wildcard (*) is supported. Parameters : - filter : (str) a string with the alias filter (wildcard (*) is supported) Return : DbDatum with the list of device names New in PyTango 7.0.0 """) document_method("get_class_for_device", """ get_class_for_device(self, dev_name) -> str Return the class of the specified device. Parameters : - dev_name : (str) device name Return : a string containing the device class """) document_method("get_class_inheritance_for_device", """ get_class_inheritance_for_device(self, dev_name) -> DbDatum Return the class inheritance scheme of the specified device. Parameters : - devn_ame : (str) device name Return : DbDatum with the inheritance class list New in PyTango 7.0.0 """) document_method("get_class_for_device", """ get_class_for_device(self, dev_name) -> str Return the class of the specified device. Parameters : - dev_name : (str) device name Return : a string containing the device class """) document_method("get_device_exported_for_class", """ get_device_exported_for_class(self, class_name) -> DbDatum Query database for list of exported devices for the specified class. Parameters : - class_name : (str) class name Return : DbDatum with the list of exported devices for the New in PyTango 7.0.0 """) document_method("put_device_alias", """ put_device_alias(self, dev_name, alias) -> None Query database for list of exported devices for the specified class. Parameters : - dev_name : (str) device name - alias : (str) alias name Return : None """) document_method("delete_device_alias", """ delete_device_alias(self, alias) -> void Delete a device alias Parameters : - alias : (str) alias name Return : None """) document_method("_add_server", """ _add_server(self, serv_name, dev_info) -> None Add a a group of devices to the database. This corresponds to the pure C++ API call. Parameters : - serv_name : (str) server name - dev_info : (DbDevInfos) server device(s) information Return : None """) document_method("delete_server", """ delete_server(self, server) -> None Delete the device server and its associated devices from database. Parameters : - server : (str) name of the server to be deleted with format: / Return : None """) document_method("_export_server", """ _export_server(self, dev_info) -> None Export a group of devices to the database. This corresponds to the pure C++ API call. Parameters : - dev_info : (DbDevExportInfos) device(s) to export information Return : None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) """) document_method("unexport_server", """ unexport_server(self, server) -> None Mark all devices exported for this server as unexported. Parameters : - server : (str) name of the server to be unexported with format: / Return : None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) """) document_method("rename_server", """ rename_server(self, old_ds_name, new_ds_name) -> None Rename a device server process. Parameters : - old_ds_name : (str) old name - new_ds_name : (str) new name Return : None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) New in PyTango 8.1.0 """) document_method("get_server_info", """ get_server_info(self, server) -> DbServerInfo Query the database for server information. Parameters : - server : (str) name of the server to be unexported with format: / Return : DbServerInfo with server information Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) New in PyTango 3.0.4 """) document_method("put_server_info", """ put_server_info(self, info) -> None Add/update server information in the database. Parameters : - info : (DbServerInfo) new server information Return : None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) New in PyTango 3.0.4 """) document_method("delete_server_info", """ delete_server_info(self, server) -> None Delete server information of the specifed server from the database. Parameters : - server : (str) name of the server to be deleted with format: / Return : None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) New in PyTango 3.0.4 """) document_method("get_server_class_list", """ get_server_class_list(self, server) -> DbDatum Query the database for a list of classes instancied by the specified server. The DServer class exists in all TANGO servers and for this reason this class is removed from the returned list. Parameters : - server : (str) name of the server to be deleted with format: / Return : DbDatum containing list of class names instanciated by the specified server Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) New in PyTango 3.0.4 """) document_method("get_server_name_list", """ get_server_name_list(self) -> DbDatum Return the list of all server names registered in the database. Parameters : None Return : DbDatum containing list of server names Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) New in PyTango 3.0.4 """) document_method("get_instance_name_list", """ get_instance_name_list(self, serv_name) -> DbDatum Return the list of all instance names existing in the database for the specifed server. Parameters : - serv_name : (str) server name with format Return : DbDatum containing list of instance names for the specified server Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) New in PyTango 3.0.4 """) document_method("get_server_list", """ get_server_list(self) -> DbDatum get_server_list(self, wildcard) -> DbDatum Return the list of all servers registered in the database. If wildcard parameter is given, then the the list matching servers will be returned (ex: Serial/\*) Parameters : - wildcard : (str) host wildcard (ex: Serial/\*) Return : DbDatum containing list of registered servers """) document_method("get_host_server_list", """ get_host_server_list(self, host_name) -> DbDatum Query the database for a list of servers registred on the specified host. Parameters : - host_name : (str) host name Return : DbDatum containing list of servers for the specified host Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) New in PyTango 3.0.4 """) document_method("get_device_class_list", """ get_device_class_list(self, server) -> DbDatum Query the database for a list of devices and classes served by the specified server. Return a list with the following structure: [device name, class name, device name, class name, ...] Parameters : - server : (str) name of the server with format: / Return : DbDatum containing list with the following structure: [device_name, class name] Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) New in PyTango 3.0.4 """) document_method("_get_property", """ _get_property(self, obj_name, props) -> None Query the database for a list of object (i.e non-device) properties. The property names are specified by the DbData (seq) structures. The method returns the properties in the same DbDatum structures This corresponds to the pure C++ API call. Parameters : - obj_name : (str) object name - props [in, out] : (DbData) property names Return : None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) """) document_method("_get_property_forced", """ _get_property_forced(self, obj_name, props) -> None Query the database for a list of object (i.e non-device) properties. The property names are specified by the DbData (seq) structures. The method returns the properties in the same DbDatum structures This corresponds to the pure C++ API call. Parameters : - obj_name : (str) object name - props [in, out] : (DbData) property names Return : None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) New in PyTango 7.0.0 """) document_method("_delete_property", """ _delete_property(self, obj_name, props) -> None Delete a list of properties for the specified object. This corresponds to the pure C++ API call. Parameters : - obj_name : (str) object name - props : (DbData) property names Return : None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) """) document_method("get_property_history", """ get_property_history(self, obj_name, prop_name) -> DbHistoryList Get the list of the last 10 modifications of the specifed object property. Note that propname can contain a wildcard character (eg: 'prop*') Parameters : - serv_name : (str) server name - prop_name : (str) property name Return : DbHistoryList containing the list of modifications Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) New in PyTango 7.0.0 """) document_method("get_object_list", """ get_object_list(self, wildcard) -> DbDatum Query the database for a list of object (free properties) for which properties are defined and which match the specified wildcard. Parameters : - wildcard : (str) object wildcard Return : DbDatum containing the list of object names matching the given wildcard Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) New in PyTango 7.0.0 """) document_method("get_object_property_list", """ get_object_property_list(self, obj_name, wildcard) -> DbDatum Query the database for a list of properties defined for the specified object and which match the specified wildcard. Parameters : - obj_name : (str) object name - wildcard : (str) property name wildcard Return : DbDatum with list of properties defined for the specified object and which match the specified wildcard Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) New in PyTango 7.0.0 """) document_method("_get_device_property", """ _get_device_property(self, dev_name, props) -> None Query the database for a list of device properties for the specified device. The property names are specified by the DbData (seq) structures. The method returns the properties in the same DbDatum structures This corresponds to the pure C++ API call. Parameters : - dev_name : (str) device name - props : (DbData) property names Return : None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) """) document_method("_put_device_property", """ _put_device_property(self, dev_name, props) -> None Insert or update a list of properties for the specified device. This corresponds to the pure C++ API call. Parameters : - dev_name : (str) device name - props : (DbData) property data Return : None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) """) document_method("_delete_device_property", """ _delete_device_property(self, dev_name, props) -> None Delete a list of properties for the specified device. This corresponds to the pure C++ API call. Parameters : - dev_name : (str) device name - props : (DbData) property names to be deleted Return : None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) """) document_method("get_device_property_history", """ get_device_property_history(self, dev_name, prop_name) -> DbHistoryList Get the list of the last 10 modifications of the specified device property. Note that propname can contain a wildcard character (eg: 'prop*'). This corresponds to the pure C++ API call. Parameters : - serv_name : (str) server name - prop_name : (str) property name Return : DbHistoryList containing the list of modifications Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) New in PyTango 7.0.0 """) document_method("_get_device_property_list", """ _get_device_property_list(self, dev_name, wildcard) -> DbDatum _get_device_property_list(self, dev_name, wildcard, container) -> None Query the database for a list of properties defined for the specified device and which match the specified wildcard. This corresponds to the pure C++ API call. Parameters : - dev_name : (str) device name - wildcard : (str) property name wildcard - container [out] : (StdStringVector) array that will contain the matching property names Return : DbDatum containing the list of property names or None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) New in PyTango 7.0.0 """) document_method("_get_device_attribute_property", """ _get_device_attribute_property(self, dev_name, props) -> None Query the database for a list of device attribute properties for the specified device. The attribute names are specified by the DbData (seq) structures. The method returns all the properties for the specified attributes in the same DbDatum structures. This corresponds to the pure C++ API call. Parameters : - dev_name : (str) device name - props [in, out] : (DbData) attribute names Return : None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) """) document_method("_put_device_attribute_property", """ _put_device_attribute_property(self, dev_name, props) -> None Insert or update a list of attribute properties for the specified device. This corresponds to the pure C++ API call. Parameters : - dev_name : (str) device name - props : (DbData) property data Return : None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) """) document_method("_delete_device_attribute_property", """ _delete_device_attribute_property(self, dev_name, props) -> None Delete a list of attribute properties for the specified device. The attribute names are specified by the vector of DbDatum structures. Here is an example of how to delete the unit property of the velocity attribute of the id11/motor/1 device using this method : db_data = tango.DbData(); db_data.append(DbDatum("velocity")); db_data.append(DbDatum("unit")); db.delete_device_attribute_property("id11/motor/1", db_data); This corresponds to the pure C++ API call. Parameters : - serv_name : (str) server name - props : (DbData) attribute property data Return : None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) """) document_method("get_device_attribute_property_history", """ get_device_attribute_property_history(self, dev_name, att_name, prop_name) -> DbHistoryList Get the list of the last 10 modifications of the specified device attribute property. Note that propname and devname can contain a wildcard character (eg: 'prop*'). Parameters : - dev_name : (str) device name - attn_ame : (str) attribute name - prop_name : (str) property name Return : DbHistoryList containing the list of modifications Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) New in PyTango 7.0.0 """) document_method("_get_class_property", """ _get_class_property(self, class_name, props) -> None Query the database for a list of class properties. The property names are specified by the DbData (seq) structures. The method returns the properties in the same DbDatum structures. This corresponds to the pure C++ API call. Parameters : - class_name : (str) class name - props [in, out] : (DbData) property names Return : None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) """) document_method("_put_class_property", """ _put_class_property(self, class_name, props) -> None Insert or update a list of properties for the specified class. This corresponds to the pure C++ API call. Parameters : - class_name : (str) class name - props : (DbData) property data to be inserted Return : None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) """) document_method("_delete_class_property", """ _delete_class_property(self, class_name, props) -> None Delete a list of properties for the specified class. This corresponds to the pure C++ API call. Parameters : - class_name : (str) class name - props : (DbData) property names Return : None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) """) document_method("get_class_property_history", """ get_class_property_history(self, class_name, prop_name) -> DbHistoryList Get the list of the last 10 modifications of the specified class property. Note that propname can contain a wildcard character (eg: 'prop*'). Parameters : - class_name : (str) class name - prop_name : (str) property name Return : DbHistoryList containing the list of modifications Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) New in PyTango 7.0.0 """) document_method("get_class_list", """ get_class_list(self, wildcard) -> DbDatum Query the database for a list of classes which match the specified wildcard Parameters : - wildcard : (str) class wildcard Return : DbDatum containing the list of matching classes Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) New in PyTango 7.0.0 """) document_method("get_class_property_list", """ get_class_property_list(self, class_name) -> DbDatum Query the database for a list of properties defined for the specified class. Parameters : - class_name : (str) class name Return : DbDatum containing the list of properties for the specified class Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) """) document_method("_get_class_attribute_property", """ _get_class_attribute_property(self, class_name, props) -> None Query the database for a list of class attribute properties for the specified objec. The attribute names are returned with the number of properties specified as their value. The first DbDatum element of the returned DbData vector contains the first attribute name and the first attribute property number. The following DbDatum element contains the first attribute property name and property values. This corresponds to the pure C++ API call. Parameters : - class_name : (str) class name - props [in,out] : (DbData) property names Return : None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) """) document_method("_put_class_attribute_property", """ _put_class_attribute_property(self, class_name, props) -> None Insert or update a list of attribute properties for the specified class. This corresponds to the pure C++ API call. Parameters : - class_name : (str) class name - props : (DbData) property data Return : None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) """) document_method("get_class_attribute_property_history", """ get_class_attribute_property_history(self, dev_name, attr_name, prop_name) -> DbHistoryList Delete a list of properties for the specified class. This corresponds to the pure C++ API call. Parameters : - dev_name : (str) device name - attr_name : (str) attribute name - prop_name : (str) property name Return : DbHistoryList containing the list of modifications Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) New in PyTango 7.0.0 """) document_method("get_class_attribute_list", """ get_class_attribute_list(self, class_name, wildcard) -> DbDatum Query the database for a list of attributes defined for the specified class which match the specified wildcard. Parameters : - class_name : (str) class name - wildcard : (str) attribute name Return : DbDatum containing the list of matching attributes for the given class Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) New in PyTango 7.0.0 """) document_method("get_attribute_alias", """ get_attribute_alias(self, alias) -> str Get the full attribute name from an alias. Parameters : - alias : (str) attribute alias Return : full attribute name Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) .. deprecated:: 8.1.0 Use :meth:`~tango.Database.get_attribute_from_alias` instead """) document_method("get_attribute_from_alias", """ get_attribute_from_alias(self, alias) -> str Get the full attribute name from an alias. Parameters : - alias : (str) attribute alias Return : full attribute name Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) New in PyTango 8.1.0 """) document_method("get_alias_from_attribute", """ get_alias_from_attribute(self, attr_name) -> str Get the attribute alias from the full attribute name. Parameters : - attr_name : (str) full attribute name Return : attribute alias Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) New in PyTango 8.1.0 """) document_method("get_attribute_alias_list", """ get_attribute_alias_list(self, filter) -> DbDatum Get attribute alias list. The parameter alias is a string to filter the alias list returned. Wildcard (*) is supported. For instance, if the string alias passed as the method parameter is initialised with only the * character, all the defined attribute alias will be returned. If there is no alias with the given filter, the returned array will have a 0 size. Parameters : - filter : (str) attribute alias filter Return : DbDatum containing the list of matching attribute alias Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) """) document_method("put_attribute_alias", """ put_attribute_alias(self, attr_name, alias) -> None Set an alias for an attribute name. The attribute alias is specified by aliasname and the attribute name is specifed by attname. If the given alias already exists, a DevFailed exception is thrown. Parameters : - attr_name : (str) full attribute name - alias : (str) alias Return : None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) """) document_method("delete_attribute_alias", """ delete_attribute_alias(self, alias) -> None Remove the alias associated to an attribute name. Parameters : - alias : (str) alias Return : None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) """) document_method("export_event", """ export_event(self, event_data) -> None Export an event to the database. Parameters : - eventdata : (sequence) event data (same as DbExportEvent Database command) Return : None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) New in PyTango 7.0.0 """) document_method("unexport_event", """ unexport_event(self, event) -> None Un-export an event from the database. Parameters : - event : (str) event Return : None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device (DB_SQLError) New in PyTango 7.0.0 """) def __doc_DbDatum(): def document_method(method_name, desc, append=True): return __document_method(DbDatum, method_name, desc, append) DbDatum.__doc__ = """ A single database value which has a name, type, address and value and methods for inserting and extracting C++ native types. This is the fundamental type for specifying database properties. Every property has a name and has one or more values associated with it. A status flag indicates if there is data in the DbDatum object or not. An additional flag allows the user to activate exceptions. Note: DbDatum is extended to support the python sequence API. This way the DbDatum behaves like a sequence of strings. This allows the user to work with a DbDatum as if it was working with the old list of strings. New in PyTango 7.0.0""" document_method("size", """ size(self) -> int Returns the number of separate elements in the value. Parameters : None Return : the number of separate elements in the value. New in PyTango 7.0.0 """) document_method("is_empty", """ is_empty(self) -> bool Returns True or False depending on whether the DbDatum object contains data or not. It can be used to test whether a property is defined in the database or not. Parameters : None Return : (bool) True if no data or False otherwise. New in PyTango 7.0.0 """) def __doc_DbDevExportInfo(): DbDevExportInfo.__doc__ = """ import info for a device (should be retrived from the database) with the following members: - name : (str) device name - ior : (str) CORBA reference of the device - host : name of the computer hosting the server - version : (str) version - pid : process identifier""" def __doc_DbDevImportInfo(): DbDevImportInfo.__doc__ = """ import info for a device (should be retrived from the database) with the following members: - name : (str) device name - exported : 1 if device is running, 0 else - ior : (str)CORBA reference of the device - version : (str) version""" def __doc_DbDevInfo(): DbDevInfo.__doc__ = """ A structure containing available information for a device with the following members: - name : (str) name - _class : (str) device class - server : (str) server""" def __doc_DbHistory(): def document_method(method_name, desc, append=True): return __document_method(DbHistory, method_name, desc, append) DbHistory.__doc__ = """ A structure containing the modifications of a property. No public members.""" document_method("get_name", """ get_name(self) -> str Returns the property name. Parameters : None Return : (str) property name """) document_method("get_attribute_name", """ get_attribute_name(self) -> str Returns the attribute name (empty for object properties or device properties) Parameters : None Return : (str) attribute name """) document_method("get_date", """ get_date(self) -> str Returns the update date Parameters : None Return : (str) update date """) document_method("get_value", """ get_value(self) -> DbDatum Returns a COPY of the property value Parameters : None Return : (DbDatum) a COPY of the property value """) document_method("is_deleted", """ is_deleted(self) -> bool Returns True if the property has been deleted or False otherwise Parameters : None Return : (bool) True if the property has been deleted or False otherwise """) def __doc_DbServerInfo(): DbServerInfo.__doc__ = """ A structure containing available information for a device server with the following members: - name : (str) name - host : (str) host - mode : (str) mode - level : (str) level""" def __doc_DbServerData(): def document_method(method_name, desc, append=True): return __document_method(DbServerData, method_name, desc, append) DbServerData.__doc__ = """\ A structure used for moving DS from one tango host to another. Create a new instance by: DbServerData(, )""" document_method("get_name", """ get_name(self) -> str Returns the full server name Parameters : None Return : (str) the full server name """) document_method("put_in_database", """ put_in_database(self, tg_host) -> None Store all the data related to the device server process in the database specified by the input arg. Parameters : - tg_host : (str) The tango host for the new database Return : None """) document_method("already_exist", """ already_exist(self, tg_host) -> bool Check if any of the device server process device(s) is already defined in the database specified by the tango host given as input arg Parameters : - tg_host : (str) The tango host for the new database Return : (str) True in case any of the device is already known. False otherwise """) document_method("remove", """ remove(self) -> None remove(self, tg_host) -> None Remove device server process from a database. Parameters : - tg_host : (str) The tango host for the new database Return : None """) def db_init(doc=True): __init_DbDatum() if doc: __doc_DbDatum() __init_Database() if doc: __doc_Database() __doc_DbDevExportInfo() __doc_DbDevImportInfo() __doc_DbDevInfo() __doc_DbHistory() __doc_DbServerInfo() __doc_DbServerData() pytango-9.2.2/tango/device_attribute.py000066400000000000000000000102021316324373100202070ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ """ This is an internal PyTango module. """ __all__ = ["device_attribute_init"] __docformat__ = "restructuredtext" import copy from .utils import document_method as __document_method from ._tango import DeviceAttribute, ExtractAs def __DeviceAttribute__get_data(self): return self.get_data_raw().extract() def __DeviceAttribute__init(self, da=None): if da is None: DeviceAttribute.__init_orig(self) else: DeviceAttribute.__init_orig(self, da) try: self.value = copy.deepcopy(da.value) except: pass try: self.w_value = copy.deepcopy(da.w_value) except: pass try: self.scalar_w_value = da.scalar_w_value except: pass self.type = da.type self.is_empty = da.is_empty self.has_failed = da.has_failed def __doc_DeviceAttribute(): def document_method(method_name, desc, append=True): return __document_method(DeviceAttribute, method_name, desc, append) DeviceAttribute.__doc__ = """ This is the fundamental type for RECEIVING data from device attributes. It contains several fields. The most important ones depend on the ExtractAs method used to get the value. Normally they are: - value : Normal scalar value or numpy array of values. - w_value : The write part of the attribute. See other ExtractAs for different possibilities. There are some more fields, these really fixed: - name : (str) - data_format : (AttrDataFormat) Attribute format - quality : (AttrQuality) - time : (TimeVal) - dim_x : (int) attribute dimension x - dim_y : (int) attribute dimension y - w_dim_x : (int) attribute written dimension x - w_dim_y : (int) attribute written dimension y - r_rimension : (tuple) Attribute read dimensions. - w_dimension : (tuple) Attribute written dimensions. - nb_read : (int) attribute read total length - nb_written : (int) attribute written total length And two methods: - get_date - get_err_stack """ document_method("get_date", """ get_date(self) -> TimeVal Get the time at which the attribute was read by the server. Note: It's the same as reading the "time" attribute. Parameters : None Return : (TimeVal) The attribute read timestamp. """) document_method("get_err_stack", """ get_err_stack(self) -> sequence Returns the error stack reported by the server when the attribute was read. Parameters : None Return : (sequence) """) document_method("set_w_dim_x", """ set_w_dim_x(self, val) -> None Sets the write value dim x. Parameters : - val : (int) new write dim x Return : None New in PyTango 8.0.0 """) document_method("set_w_dim_y", """ set_w_dim_y(self, val) -> None Sets the write value dim y. Parameters : - val : (int) new write dim y Return : None New in PyTango 8.0.0 """) def __init_DeviceAttribute(): DeviceAttribute.__init_orig = DeviceAttribute.__init__ DeviceAttribute.__init__ = __DeviceAttribute__init DeviceAttribute.ExtractAs = ExtractAs def device_attribute_init(doc=True): __init_DeviceAttribute() if doc: __doc_DeviceAttribute() pytango-9.2.2/tango/device_class.py000066400000000000000000001014441316324373100173220ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ """ This is an internal PyTango module. """ from __future__ import print_function __all__ = ["DeviceClass", "device_class_init"] __docformat__ = "restructuredtext" import collections from ._tango import Except, DevFailed, DeviceClass, CmdArgType, \ DispLevel, UserDefaultAttrProp from .pyutil import Util from .utils import is_pure_str, is_non_str_seq, seqStr_2_obj, obj_2_str, \ is_array from .utils import document_method as __document_method from .globals import get_class, get_class_by_class, \ get_constructed_class_by_class from .attr_data import AttrData from .pipe_data import PipeData class PropUtil: """An internal Property util class""" scalar_int_types = (CmdArgType.DevShort, CmdArgType.DevUShort, CmdArgType.DevInt, CmdArgType.DevLong, CmdArgType.DevULong,) scalar_long_types = (CmdArgType.DevLong64, CmdArgType.DevULong64) scalar_float_types = (CmdArgType.DevFloat, CmdArgType.DevDouble,) scalar_numerical_types = scalar_int_types + scalar_long_types + scalar_float_types scalar_str_types = (CmdArgType.DevString, CmdArgType.ConstDevString,) scalar_types = scalar_numerical_types + scalar_str_types + \ (CmdArgType.DevBoolean, CmdArgType.DevEncoded, CmdArgType.DevUChar, CmdArgType.DevVoid) def __init__(self): self.db = None if Util._UseDb: self.db = Util.instance().get_database() def set_default_property_values(self, dev_class, class_prop, dev_prop): """ set_default_property_values(self, dev_class, class_prop, dev_prop) -> None Sets the default property values Parameters : - dev_class : (DeviceClass) device class object - class_prop : (dict) class properties - dev_prop : (dict) device properties Return : None """ for name in class_prop: type = self.get_property_type(name, class_prop) val = self.get_property_values(name, class_prop) val = self.values2string(val, type) desc = self.get_property_description(name, class_prop) dev_class.add_wiz_class_prop(name, desc, val) for name in dev_prop: type = self.get_property_type(name, dev_prop) val = self.get_property_values(name, dev_prop) val = self.values2string(val, type) desc = self.get_property_description(name, dev_prop) dev_class.add_wiz_dev_prop(name, desc, val) def get_class_properties(self, dev_class, class_prop): """ get_class_properties(self, dev_class, class_prop) -> None Returns the class properties Parameters : - dev_class : (DeviceClass) the DeviceClass object - class_prop : [in, out] (dict) the property names. Will be filled with property values Return : None""" # initialize default values if class_prop == {} or not Util._UseDb: return # call database to get properties props = self.db.get_class_property(dev_class.get_name(), list(class_prop.keys())) # if value defined in database, store it for name in class_prop: if props[name]: type = self.get_property_type(name, class_prop) values = self.stringArray2values(props[name], type) self.set_property_values(name, class_prop, values) else: print(name + " property NOT found in database") def get_device_properties(self, dev, class_prop, dev_prop): """ get_device_properties(self, dev, class_prop, dev_prop) -> None Returns the device properties Parameters : - dev : (DeviceImpl) the device object - class_prop : (dict) the class properties - dev_prop : [in,out] (dict) the device property names Return : None""" # initialize default properties if dev_prop == {} or not Util._UseDb: return # Call database to get properties props = self.db.get_device_property(dev.get_name(), list(dev_prop.keys())) # if value defined in database, store it for name in dev_prop: prop_value = props[name] if len(prop_value): data_type = self.get_property_type(name, dev_prop) values = self.stringArray2values(prop_value, data_type) if not self.is_empty_seq(values): self.set_property_values(name, dev_prop, values) else: # Try to get it from class property values = self.get_property_values(name, class_prop) if not self.is_empty_seq(values): if not self.is_seq(values): values = [values] data_type = self.get_property_type(name, class_prop) values = self.stringArray2values(values, data_type) if not self.is_empty_seq(values): self.set_property_values(name, dev_prop, values) else: # Try to get it from class property values = self.get_property_values(name, class_prop) if not self.is_empty_seq(values): if not self.is_seq(values): values = [values] data_type = self.get_property_type(name, class_prop) values = self.stringArray2values(values, data_type) if not self.is_empty_seq(values): self.set_property_values(name, dev_prop, values) def is_seq(self, v): """ is_seq(self, v) -> bool Helper method. Determines if the object is a sequence Parameters : - v : (object) the object to be analysed Return : (bool) True if the object is a sequence or False otherwise""" return isinstance(v, collections.Sequence) def is_empty_seq(self, v): """ is_empty_seq(self, v) -> bool Helper method. Determines if the object is an empty sequence Parameters : - v : (object) the object to be analysed Return : (bool) True if the object is a sequence which is empty or False otherwise""" return self.is_seq(v) and not len(v) def get_property_type(self, prop_name, properties): """ get_property_type(self, prop_name, properties) -> CmdArgType Gets the property type for the given property name using the information given in properties Parameters : - prop_name : (str) property name - properties : (dict) property data Return : (CmdArgType) the tango type for the given property""" try: tg_type = properties[prop_name][0] except: tg_type = CmdArgType.DevVoid return tg_type def set_property_values(self, prop_name, properties, values): """ set_property_values(self, prop_name, properties, values) -> None Sets the property value in the properties Parameters : - prop_name : (str) property name - properties : (dict) [in,out] dict which will contain the value - values : (seq) the new property value Return : None""" properties[prop_name][2] = values def get_property_values(self, prop_name, properties): """ get_property_values(self, prop_name, properties) -> obj Gets the property value Parameters : - prop_name : (str) property name - properties : (dict) properties Return : (obj) the value for the given property name""" try: tg_type = self.get_property_type(prop_name, properties) val = properties[prop_name][2] except: val = [] if is_array(tg_type) or (isinstance(val, collections.Sequence) and not len(val)): return val else: if is_non_str_seq(val): return val[0] else: return val def get_property_description(self, prop_name, properties): """ get_property_description(self, prop_name, properties) -> obj Gets the property description Parameters : - prop_name : (str) property name - properties : (dict) properties Return : (str) the description for the given property name""" return properties[prop_name][1] def stringArray2values(self, argin, argout_type): """internal helper method""" return seqStr_2_obj(argin, argout_type) def values2string(self, argin, argout_type): """internal helper method""" return obj_2_str(argin, argout_type) def __DeviceClass__init__(self, name): DeviceClass.__init_orig__(self, name) self.dyn_att_added_methods = [] self.dyn_cmd_added_methods = [] try: pu = self.prop_util = PropUtil() self.py_dev_list = [] pu.set_default_property_values(self, self.class_property_list, self.device_property_list) pu.get_class_properties(self, self.class_property_list) for prop_name in self.class_property_list: if not hasattr(self, prop_name): setattr(self, prop_name, pu.get_property_values(prop_name, self.class_property_list)) except DevFailed as df: print("PyDS: %s: A Tango error occured in the constructor:" % name) Except.print_exception(df) except Exception as e: print("PyDS: %s: An error occured in the constructor:" % name) print(str(e)) def __DeviceClass__str__(self): return '%s(%s)' % (self.__class__.__name__, self.get_name()) def __DeviceClass__repr__(self): return '%s(%s)' % (self.__class__.__name__, self.get_name()) def __throw_create_attribute_exception(msg): """ Helper method to throw DevFailed exception when inside create_attribute """ Except.throw_exception("PyDs_WrongAttributeDefinition", msg, "create_attribute()") def __throw_create_command_exception(msg): """ Helper method to throw DevFailed exception when inside create_command """ Except.throw_exception("PyDs_WrongCommandDefinition", msg, "create_command()") def __DeviceClass__create_user_default_attr_prop(self, attr_name, extra_info): """for internal usage only""" p = UserDefaultAttrProp() for k, v in extra_info.items(): k_lower = k.lower() method_name = "set_%s" % k_lower.replace(' ', '_') if hasattr(p, method_name): method = getattr(p, method_name) method(str(v)) elif k == 'delta_time': p.set_delta_t(str(v)) elif k_lower not in ('display level', 'polling period', 'memorized'): name = self.get_name() msg = "Wrong definition of attribute %s in " \ "class %s\nThe object extra information '%s' " \ "is not recognized!" % (attr_name, name, k) self.__throw_create_attribute_exception(msg) return p def __DeviceClass__attribute_factory(self, attr_list): """for internal usage only""" for attr_name, attr_info in self.attr_list.items(): if isinstance(attr_info, AttrData): attr_data = attr_info else: attr_data = AttrData(attr_name, self.get_name(), attr_info) if attr_data.forward: self._create_fwd_attribute(attr_list, attr_data.name, attr_data.att_prop) else: self._create_attribute(attr_list, attr_data.attr_name, attr_data.attr_type, attr_data.attr_format, attr_data.attr_write, attr_data.dim_x, attr_data.dim_y, attr_data.display_level, attr_data.polling_period, attr_data.memorized, attr_data.hw_memorized, attr_data.read_method_name, attr_data.write_method_name, attr_data.is_allowed_name, attr_data.att_prop) def __DeviceClass__pipe_factory(self, pipe_list): """for internal usage only""" for pipe_name, pipe_info in self.pipe_list.items(): if isinstance(pipe_info, PipeData): pipe_data = pipe_info else: pipe_data = PipeData(pipe_name, self.get_name(), pipe_info) self._create_pipe(pipe_list, pipe_data.pipe_name, pipe_data.pipe_write, pipe_data.display_level, pipe_data.read_method_name, pipe_data.write_method_name, pipe_data.is_allowed_name, pipe_data.pipe_prop) def __DeviceClass__command_factory(self): """for internal usage only""" name = self.get_name() class_info = get_class(name) deviceimpl_class = class_info[1] if not hasattr(deviceimpl_class, "init_device"): msg = "Wrong definition of class %s\n" \ "The init_device() method does not exist!" % name Except.throw_exception("PyDs_WrongCommandDefinition", msg, "command_factory()") for cmd_name, cmd_info in self.cmd_list.items(): __create_command(self, deviceimpl_class, cmd_name, cmd_info) def __create_command(self, deviceimpl_class, cmd_name, cmd_info): """for internal usage only""" name = self.get_name() # check for well defined command info # check parameter if not isinstance(cmd_info, collections.Sequence): msg = "Wrong data type for value for describing command %s in " \ "class %s\nMust be a sequence with 2 or 3 elements" % (cmd_name, name) __throw_create_command_exception(msg) if len(cmd_info) < 2 or len(cmd_info) > 3: msg = "Wrong number of argument for describing command %s in " \ "class %s\nMust be a sequence with 2 or 3 elements" % (cmd_name, name) __throw_create_command_exception(msg) param_info, result_info = cmd_info[0], cmd_info[1] if not isinstance(param_info, collections.Sequence): msg = "Wrong data type in command argument for command %s in " \ "class %s\nCommand parameter (first element) must be a sequence" % (cmd_name, name) __throw_create_command_exception(msg) if len(param_info) < 1 or len(param_info) > 2: msg = "Wrong data type in command argument for command %s in " \ "class %s\nSequence describing command parameters must contain " \ "1 or 2 elements" __throw_create_command_exception(msg) param_type = CmdArgType.DevVoid try: param_type = CmdArgType(param_info[0]) except: msg = "Wrong data type in command argument for command %s in " \ "class %s\nCommand parameter type (first element in first " \ "sequence) must be a tango.CmdArgType" __throw_create_command_exception(msg) param_desc = "" if len(param_info) > 1: param_desc = param_info[1] if not is_pure_str(param_desc): msg = "Wrong data type in command parameter for command %s in " \ "class %s\nCommand parameter description (second element " \ "in first sequence), when given, must be a string" __throw_create_command_exception(msg) # Check result if not isinstance(result_info, collections.Sequence): msg = "Wrong data type in command result for command %s in " \ "class %s\nCommand result (second element) must be a sequence" % (cmd_name, name) __throw_create_command_exception(msg) if len(result_info) < 1 or len(result_info) > 2: msg = "Wrong data type in command result for command %s in " \ "class %s\nSequence describing command result must contain " \ "1 or 2 elements" % (cmd_name, name) __throw_create_command_exception(msg) result_type = CmdArgType.DevVoid try: result_type = CmdArgType(result_info[0]) except: msg = "Wrong data type in command result for command %s in " \ "class %s\nCommand result type (first element in second " \ "sequence) must be a tango.CmdArgType" % (cmd_name, name) __throw_create_command_exception(msg) result_desc = "" if len(result_info) > 1: result_desc = result_info[1] if not is_pure_str(result_desc): msg = "Wrong data type in command result for command %s in " \ "class %s\nCommand parameter description (second element " \ "in second sequence), when given, must be a string" % (cmd_name, name) __throw_create_command_exception(msg) # If it is defined, get addictional dictionnary used for optional parameters display_level, default_command, polling_period = DispLevel.OPERATOR, False, -1 if len(cmd_info) == 3: extra_info = cmd_info[2] if not isinstance(extra_info, collections.Mapping): msg = "Wrong data type in command information for command %s in " \ "class %s\nCommand information (third element in sequence), " \ "when given, must be a dictionary" % (cmd_name, name) __throw_create_command_exception(msg) if len(extra_info) > 3: msg = "Wrong data type in command information for command %s in " \ "class %s\nThe optional dictionary can not have more than " \ "three elements" % (cmd_name, name) __throw_create_command_exception(msg) for info_name, info_value in extra_info.items(): info_name_lower = info_name.lower() if info_name_lower == "display level": try: display_level = DispLevel(info_value) except: msg = "Wrong data type in command information for command %s in " \ "class %s\nCommand information for display level is not a " \ "tango.DispLevel" % (cmd_name, name) __throw_create_command_exception(msg) elif info_name_lower == "default command": if not is_pure_str(info_value): msg = "Wrong data type in command information for command %s in " \ "class %s\nCommand information for default command is not a " \ "string" % (cmd_name, name) __throw_create_command_exception(msg) v = info_value.lower() default_command = v == 'true' elif info_name_lower == "polling period": try: polling_period = int(info_value) except: msg = "Wrong data type in command information for command %s in " \ "class %s\nCommand information for polling period is not an " \ "integer" % (cmd_name, name) __throw_create_command_exception(msg) else: msg = "Wrong data type in command information for command %s in " \ "class %s\nCommand information has unknown key " \ "%s" % (cmd_name, name, info_name) __throw_create_command_exception(msg) # check that the method to be executed exists try: cmd = getattr(deviceimpl_class, cmd_name) if not isinstance(cmd, collections.Callable): msg = "Wrong definition of command %s in " \ "class %s\nThe object exists in class but is not " \ "a method!" % (cmd_name, name) __throw_create_command_exception(msg) except AttributeError: msg = "Wrong definition of command %s in " \ "class %s\nThe command method does not exist!" % (cmd_name, name) __throw_create_command_exception(msg) is_allowed_name = "is_%s_allowed" % cmd_name try: is_allowed = getattr(deviceimpl_class, is_allowed_name) if not isinstance(is_allowed, collections.Callable): msg = "Wrong definition of command %s in " \ "class %s\nThe object '%s' exists in class but is " \ "not a method!" % (cmd_name, name, is_allowed_name) __throw_create_command_exception(msg) except: is_allowed_name = "" self._create_command(cmd_name, param_type, result_type, param_desc, result_desc, display_level, default_command, polling_period, is_allowed_name) def __DeviceClass__new_device(self, klass, dev_class, dev_name): return klass(dev_class, dev_name) def __DeviceClass__device_factory(self, device_list): """for internal usage only""" klass = self.__class__ klass_name = klass.__name__ info, klass = get_class_by_class(klass), get_constructed_class_by_class(klass) if info is None: raise RuntimeError("Device class '%s' is not registered" % klass_name) if klass is None: raise RuntimeError("Device class '%s' as not been constructed" % klass_name) deviceClassClass, deviceImplClass, deviceImplName = info deviceImplClass._device_class_instance = klass tmp_dev_list = [] for dev_name in device_list: device = self._new_device(deviceImplClass, klass, dev_name) self._add_device(device) tmp_dev_list.append(device) self.dyn_attr(tmp_dev_list) for dev in tmp_dev_list: if Util._UseDb and not Util._FileDb: self.export_device(dev) else: self.export_device(dev, dev.get_name()) self.py_dev_list += tmp_dev_list def __DeviceClass__create_device(self, device_name, alias=None, cb=None): """ create_device(self, device_name, alias=None, cb=None) -> None Creates a new device of the given class in the database, creates a new DeviceImpl for it and calls init_device (just like it is done for existing devices when the DS starts up) An optional parameter callback is called AFTER the device is registered in the database and BEFORE the init_device for the newly created device is called Throws tango.DevFailed: - the device name exists already or - the given class is not registered for this DS. - the cb is not a callable New in PyTango 7.1.2 Parameters : - device_name : (str) the device name - alias : (str) optional alias. Default value is None meaning do not create device alias - cb : (callable) a callback that is called AFTER the device is registered in the database and BEFORE the init_device for the newly created device is called. Typically you may want to put device and/or attribute properties in the database here. The callback must receive a parameter: device name (str). Default value is None meaning no callback Return : None""" util = Util.instance() util.create_device(self.get_name(), device_name, alias=alias, cb=cb) def __DeviceClass__delete_device(self, device_name): """ delete_device(self, klass_name, device_name) -> None Deletes an existing device from the database and from this running server Throws tango.DevFailed: - the device name doesn't exist in the database - the device name doesn't exist in this DS. New in PyTango 7.1.2 Parameters : - klass_name : (str) the device class name - device_name : (str) the device name Return : None""" util = Util.instance() util.delete_device(self.get_name(), device_name) def __DeviceClass__dyn_attr(self, device_list): """ dyn_attr(self,device_list) -> None Default implementation does not do anything Overwrite in order to provide dynamic attributes Parameters : - device_list : (seq) sequence of devices of this class Return : None""" pass def __DeviceClass__device_destroyer(self, name): """for internal usage only""" name = name.lower() for d in self.py_dev_list: dname = d.get_name().lower() if dname == name: dev_cl = d.get_device_class() # the internal C++ device_destroyer isn't case sensitive so we # use the internal DeviceImpl name to make sure the DeviceClass # finds it dev_cl._device_destroyer(d.get_name()) self.py_dev_list.remove(d) return err_mess = "Device " + name + " not in Tango class device list!" Except.throw_exception("PyAPI_CantDestroyDevice", err_mess, "DeviceClass.device_destroyer") def __DeviceClass__device_name_factory(self, dev_name_list): """ device_name_factory(self, dev_name_list) -> None Create device(s) name list (for no database device server). This method can be re-defined in DeviceClass sub-class for device server started without database. Its rule is to initialise class device name. The default method does nothing. Parameters : - dev_name_list : (seq) sequence of devices to be filled Return : None""" pass def __init_DeviceClass(): DeviceClass.class_property_list = {} DeviceClass.device_property_list = {} DeviceClass.cmd_list = {} DeviceClass.attr_list = {} DeviceClass.pipe_list = {} DeviceClass.__init_orig__ = DeviceClass.__init__ DeviceClass.__init__ = __DeviceClass__init__ DeviceClass.__str__ = __DeviceClass__str__ DeviceClass.__repr__ = __DeviceClass__repr__ DeviceClass._create_user_default_attr_prop = __DeviceClass__create_user_default_attr_prop DeviceClass._attribute_factory = __DeviceClass__attribute_factory DeviceClass._pipe_factory = __DeviceClass__pipe_factory DeviceClass._command_factory = __DeviceClass__command_factory DeviceClass._new_device = __DeviceClass__new_device DeviceClass.device_factory = __DeviceClass__device_factory DeviceClass.create_device = __DeviceClass__create_device DeviceClass.delete_device = __DeviceClass__delete_device DeviceClass.dyn_attr = __DeviceClass__dyn_attr DeviceClass.device_destroyer = __DeviceClass__device_destroyer DeviceClass.device_name_factory = __DeviceClass__device_name_factory def __doc_DeviceClass(): DeviceClass.__doc__ = """ Base class for all TANGO device-class class. A TANGO device-class class is a class where is stored all data/method common to all devices of a TANGO device class """ def document_method(method_name, desc, append=True): return __document_method(DeviceClass, method_name, desc, append) document_method("export_device", """ export_device(self, dev, corba_dev_name = 'Unused') -> None For internal usage only Parameters : - dev : (DeviceImpl) device object - corba_dev_name : (str) CORBA device name. Default value is 'Unused' Return : None """) document_method("register_signal", """ register_signal(self, signo) -> None register_signal(self, signo, own_handler=false) -> None Register a signal. Register this class as class to be informed when signal signo is sent to to the device server process. The second version of the method is available only under Linux. Throws tango.DevFailed: - if the signal number is out of range - if the operating system failed to register a signal for the process. Parameters : - signo : (int) signal identifier - own_handler : (bool) true if you want the device signal handler to be executed in its own handler instead of being executed by the signal thread. If this parameter is set to true, care should be taken on how the handler is written. A default false value is provided Return : None """) document_method("unregister_signal", """ unregister_signal(self, signo) -> None Unregister a signal. Unregister this class as class to be informed when signal signo is sent to to the device server process Parameters : - signo : (int) signal identifier Return : None """) document_method("signal_handler", """ signal_handler(self, signo) -> None Signal handler. The method executed when the signal arrived in the device server process. This method is defined as virtual and then, can be redefined following device class needs. Parameters : - signo : (int) signal identifier Return : None """) document_method("get_name", """ get_name(self) -> str Get the TANGO device class name. Parameters : None Return : (str) the TANGO device class name. """) document_method("get_type", """ get_type(self) -> str Gets the TANGO device type name. Parameters : None Return : (str) the TANGO device type name """) document_method("get_doc_url", """ get_doc_url(self) -> str Get the TANGO device class documentation URL. Parameters : None Return : (str) the TANGO device type name """) document_method("set_type", """ set_type(self, dev_type) -> None Set the TANGO device type name. Parameters : - dev_type : (str) the new TANGO device type name Return : None """) document_method("get_cvs_tag", """ get_cvs_tag(self) -> str Gets the cvs tag Parameters : None Return : (str) cvs tag """) document_method("get_cvs_location", """ get_cvs_location(self) -> None Gets the cvs localtion Parameters : None Return : (str) cvs location """) document_method("get_device_list", """ get_device_list(self) -> sequence Gets the list of tango.DeviceImpl objects for this class Parameters : None Return : (sequence) list of tango.DeviceImpl objects for this class """) document_method("get_command_list", """ get_command_list(self) -> sequence Gets the list of tango.Command objects for this class Parameters : None Return : (sequence) list of tango.Command objects for this class New in PyTango 8.0.0 """) document_method("get_cmd_by_name", """ get_cmd_by_name(self, (str)cmd_name) -> tango.Command Get a reference to a command object. Parameters : - cmd_name : (str) command name Return : (tango.Command) tango.Command object New in PyTango 8.0.0 """) document_method("add_wiz_dev_prop", """ add_wiz_dev_prop(self, str, str) -> None add_wiz_dev_prop(self, str, str, str) -> None For internal usage only Parameters : None Return : None """) document_method("add_wiz_class_prop", """ add_wiz_class_prop(self, str, str) -> None add_wiz_class_prop(self, str, str, str) -> None For internal usage only Parameters : None Return : None """) def device_class_init(doc=True): __init_DeviceClass() if doc: __doc_DeviceClass() pytango-9.2.2/tango/device_data.py000066400000000000000000000046031316324373100171250ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ """ This is an internal PyTango module. """ __all__ = ["device_data_init"] __docformat__ = "restructuredtext" from .utils import document_method as __document_method from ._tango import DeviceData def __DeviceData__get_data(self): return self.get_data_raw().extract() def __init_DeviceData(): pass def __doc_DeviceData(): def document_method(method_name, desc, append=True): return __document_method(DeviceData, method_name, desc, append) DeviceData.__doc__ = """ This is the fundamental type for sending and receiving data from device commands. The values can be inserted and extracted using the insert() and extract() methods. """ document_method("extract", """ extract(self) -> any Get the actual value stored in the DeviceData. Parameters : None Return : Whatever is stored there, or None. """) document_method("insert", """ insert(self, data_type, value) -> None Inserts a value in the DeviceData. Parameters : - data_type : - value : (any) The value to insert Return : Whatever is stored there, or None. """) document_method("is_empty", """ is_empty(self) -> bool It can be used to test whether the DeviceData object has been initialized or not. Parameters : None Return : True or False depending on whether the DeviceData object contains data or not. """) document_method("get_type", """ get_type(self) -> CmdArgType This method returns the Tango data type of the data inside the DeviceData object. Parameters : None Return : The content arg type. """) def device_data_init(doc=True): __init_DeviceData() if doc: __doc_DeviceData() pytango-9.2.2/tango/device_proxy.py000066400000000000000000003143731316324373100174050ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ """Define python methods for DeviceProxy object.""" import time import textwrap import threading import collections from ._tango import StdStringVector, DbData, DbDatum, AttributeInfo from ._tango import AttributeInfoEx, AttributeInfoList, AttributeInfoListEx from ._tango import DeviceProxy, __CallBackAutoDie, __CallBackPushEvent from ._tango import EventType, DevFailed, Except, ExtractAs, GreenMode from ._tango import PipeInfo, PipeInfoList, constants from ._tango import CmdArgType, DevState from .utils import TO_TANGO_TYPE, scalar_to_array_type from .utils import is_pure_str, is_non_str_seq, is_integer, is_number from .utils import seq_2_StdStringVector, StdStringVector_2_seq from .utils import seq_2_DbData, DbData_2_dict from .utils import document_method as __document_method from .utils import dir2 from .green import green, green_callback from .green import get_green_mode __all__ = ["device_proxy_init", "get_device_proxy"] __docformat__ = "restructuredtext" _UNSUBSCRIBE_LIFETIME = 60 @green(consume_green_mode=False) def get_device_proxy(*args, **kwargs): """get_device_proxy(self, dev_name, green_mode=None, wait=True, timeout=True) -> DeviceProxy get_device_proxy(self, dev_name, need_check_acc, green_mode=None, wait=True, timeout=None) -> DeviceProxy Returns a new :class:`~tango.DeviceProxy`. There is no difference between using this function and the direct :class:`~tango.DeviceProxy` constructor if you use the default kwargs. The added value of this function becomes evident when you choose a green_mode to be *Futures* or *Gevent*. The DeviceProxy constructor internally makes some network calls which makes it *slow*. By using one of the *green modes* as green_mode you are allowing other python code to be executed in a cooperative way. .. note:: The timeout parameter has no relation with the tango device client side timeout (gettable by :meth:`~tango.DeviceProxy.get_timeout_millis` and settable through :meth:`~tango.DeviceProxy.set_timeout_millis`) :param dev_name: the device name or alias :type dev_name: str :param need_check_acc: in first version of the function it defaults to True. Determines if at creation time of DeviceProxy it should check for channel access (rarely used) :type need_check_acc: bool :param green_mode: determines the mode of execution of the device (including the way it is created). Defaults to the current global green_mode (check :func:`~tango.get_green_mode` and :func:`~tango.set_green_mode`) :type green_mode: :obj:`~tango.GreenMode` :param wait: whether or not to wait for result. If green_mode Ignored when green_mode is Synchronous (always waits). :type wait: bool :param timeout: The number of seconds to wait for the result. If None, then there is no limit on the wait time. Ignored when green_mode is Synchronous or wait is False. :type timeout: float :returns: if green_mode is Synchronous or wait is True: :class:`~tango.DeviceProxy` else if green_mode is Futures: :class:`concurrent.futures.Future` else if green_mode is Gevent: :class:`gevent.event.AsynchResult` :throws: * a *DevFailed* if green_mode is Synchronous or wait is True and there is an error creating the device. * a *concurrent.futures.TimeoutError* if green_mode is Futures, wait is False, timeout is not None and the time to create the device has expired. * a *gevent.timeout.Timeout* if green_mode is Gevent, wait is False, timeout is not None and the time to create the device has expired. New in PyTango 8.1.0 """ return DeviceProxy(*args, **kwargs) class __TangoInfo(object): """Helper class for when DeviceProxy.info() is not available""" def __init__(self): self.dev_class = self.dev_type = 'Device' self.doc_url = 'http://www.esrf.fr/computing/cs/tango/tango_doc/ds_doc/' self.server_host = 'Unknown' self.server_id = 'Unknown' self.server_version = 1 # ------------------------------------------------------------------------------- # Pythonic API: transform tango commands into methods and tango attributes into # class members # ------------------------------------------------------------------------------- def __check_read_attribute(dev_attr): if dev_attr.has_failed: raise DevFailed(*dev_attr.get_err_stack()) return dev_attr def __check_read_pipe(dev_pipe): if dev_pipe.has_failed: raise DevFailed(*dev_pipe.get_err_stack()) return dev_pipe def __init_device_proxy_internals(proxy): if proxy.__dict__.get('_initialized', False): return executors = dict((key, None) for key in GreenMode.names) proxy.__dict__['_green_mode'] = None proxy.__dict__['_initialized'] = True proxy.__dict__['_executors'] = executors proxy.__dict__['_pending_unsubscribe'] = {} def __DeviceProxy__get_cmd_cache(self): try: ret = self.__dict__['__cmd_cache'] except KeyError: self.__dict__['__cmd_cache'] = ret = {} return ret def __DeviceProxy__get_attr_cache(self): try: ret = self.__dict__['__attr_cache'] except KeyError: self.__dict__['__attr_cache'] = ret = () return ret def __DeviceProxy__get_pipe_cache(self): try: ret = self.__dict__['__pipe_cache'] except KeyError: self.__dict__['__pipe_cache'] = ret = () return ret def __DeviceProxy__init__(self, *args, **kwargs): __init_device_proxy_internals(self) self._green_mode = kwargs.pop('green_mode', None) self._executors[GreenMode.Futures] = kwargs.pop('executor', None) self._executors[GreenMode.Gevent] = kwargs.pop('threadpool', None) self._executors[GreenMode.Asyncio] = kwargs.pop('asyncio_executor', None) return DeviceProxy.__init_orig__(self, *args, **kwargs) def __DeviceProxy__get_green_mode(self): """Returns the green mode in use by this DeviceProxy. :returns: the green mode in use by this DeviceProxy. :rtype: GreenMode .. seealso:: :func:`tango.get_green_mode` :func:`tango.set_green_mode` New in PyTango 8.1.0 """ gm = self._green_mode if gm is None: gm = get_green_mode() return gm def __DeviceProxy__set_green_mode(self, green_mode=None): """Sets the green mode to be used by this DeviceProxy Setting it to None means use the global PyTango green mode (see :func:`tango.get_green_mode`). :param green_mode: the new green mode :type green_mode: GreenMode New in PyTango 8.1.0 """ self._green_mode = green_mode def __DeviceProxy__refresh_cmd_cache(self): cmd_list = self.command_list_query() cmd_cache = {} for cmd in cmd_list: n = cmd.cmd_name.lower() doc = "%s(%s) -> %s\n\n" % (cmd.cmd_name, cmd.in_type, cmd.out_type) doc += " - in (%s): %s\n" % (cmd.in_type, cmd.in_type_desc) doc += " - out (%s): %s\n" % (cmd.out_type, cmd.out_type_desc) cmd_cache[n] = cmd, doc self.__dict__['__cmd_cache'] = cmd_cache def __DeviceProxy__refresh_attr_cache(self): attr_cache = [attr_name.lower() for attr_name in self.get_attribute_list()] self.__dict__['__attr_cache'] = attr_cache def __DeviceProxy__refresh_pipe_cache(self): pipe_cache = [pipe_name.lower() for pipe_name in self.get_pipe_list()] self.__dict__['__pipe_cache'] = pipe_cache def __get_command_func(dp, cmd_info, name): _, doc = cmd_info def f(*args, **kwds): return dp.command_inout(name, *args, **kwds) f.__doc__ = doc return f def __DeviceProxy__getattr(self, name): # trait_names is a feature of IPython. Hopefully they will solve # ticket http://ipython.scipy.org/ipython/ipython/ticket/229 someday # and the ugly trait_names could be removed. if name.startswith("_") or name == 'trait_names': raise AttributeError(name) name_l = name.lower() cmd_info = self.__get_cmd_cache().get(name_l) if cmd_info: return __get_command_func(self, cmd_info, name) if name_l in self.__get_attr_cache(): return self.read_attribute(name).value if name_l in self.__get_pipe_cache(): return self.read_pipe(name) try: self.__refresh_cmd_cache() except: pass cmd_info = self.__get_cmd_cache().get(name_l) if cmd_info: return __get_command_func(self, cmd_info, name) try: self.__refresh_attr_cache() except: pass if name_l in self.__get_attr_cache(): return self.read_attribute(name).value try: self.__refresh_pipe_cache() except Exception: pass if name_l in self.__get_pipe_cache(): return self.read_pipe(name) raise AttributeError(name) def __DeviceProxy__setattr(self, name, value): name_l = name.lower() if name_l in self.__get_cmd_cache(): raise TypeError('Cannot set the value of a command') if name_l in self.__get_attr_cache(): return self.write_attribute(name, value) if name_l in self.__get_pipe_cache(): return self.write_pipe(name, value) try: self.__refresh_cmd_cache() except: pass if name_l in self.__get_cmd_cache(): raise TypeError('Cannot set the value of a command') try: self.__refresh_attr_cache() except: pass if name_l in self.__get_attr_cache(): return self.write_attribute(name, value) try: self.__refresh_pipe_cache() except: pass if name_l in self.__get_pipe_cache(): return self.write_pipe(name, value) return super(DeviceProxy, self).__setattr__(name, value) def __DeviceProxy__dir(self): """Return the attribute list including tango objects.""" extra_entries = set() # Add commands try: extra_entries.update(self.get_command_list()) except Exception: pass # Add attributes try: extra_entries.update(self.get_attribute_list()) except Exception: pass # Add pipes try: extra_entries.update(self.get_pipe_list()) except Exception: pass # Merge with default dir implementation extra_entries.update([x.lower() for x in extra_entries]) entries = extra_entries.union(dir2(self)) return sorted(entries) def __DeviceProxy__getitem(self, key): return self.read_attribute(key) def __DeviceProxy__setitem(self, key, value): return self.write_attribute(key, value) def __DeviceProxy__contains(self, key): return key.lower() in map(str.lower, self.get_attribute_list()) def __DeviceProxy__read_attribute(self, value, extract_as=ExtractAs.Numpy): return __check_read_attribute(self._read_attribute(value, extract_as)) def __DeviceProxy__read_attributes_asynch(self, attr_names, cb=None, extract_as=ExtractAs.Numpy): """ read_attributes_asynch( self, attr_names) -> int Read asynchronously (polling model) the list of specified attributes. Parameters : - attr_names : (sequence) A list of attributes to read. It should be a StdStringVector or a sequence of str. Return : an asynchronous call identifier which is needed to get attributes value. Throws : ConnectionFailed New in PyTango 7.0.0 read_attributes_asynch( self, attr_names, callback, extract_as=Numpy) -> None Read asynchronously (push model) an attribute list. Parameters : - attr_names : (sequence) A list of attributes to read. See read_attributes. - callback : (callable) This callback object should be an instance of a user class with an attr_read() method. It can also be any callable object. - extract_as : (ExtractAs) Defaults to numpy. Return : None Throws : ConnectionFailed New in PyTango 7.0.0 .. important:: by default, TANGO is initialized with the **polling** model. If you want to use the **push** model (the one with the callback parameter), you need to change the global TANGO model to PUSH_CALLBACK. You can do this with the :meth:`tango.ApiUtil.set_asynch_cb_sub_model` """ if cb is None: return self.__read_attributes_asynch(attr_names) cb2 = __CallBackAutoDie() if isinstance(cb, collections.Callable): cb2.attr_read = cb else: cb2.attr_read = cb.attr_read return self.__read_attributes_asynch(attr_names, cb2, extract_as) def __DeviceProxy__read_attribute_asynch(self, attr_name, cb=None): """ read_attribute_asynch( self, attr_name) -> int read_attribute_asynch( self, attr_name, callback) -> None Shortcut to self.read_attributes_asynch([attr_name], cb) New in PyTango 7.0.0 """ return self.read_attributes_asynch([attr_name], cb) def __DeviceProxy__read_attribute_reply(self, *args, **kwds): """ read_attribute_reply( self, id, extract_as) -> int read_attribute_reply( self, id, timeout, extract_as) -> None Shortcut to self.read_attributes_reply()[0] New in PyTango 7.0.0 """ return __check_read_attribute(self.read_attributes_reply(*args, **kwds)[0]) def __DeviceProxy__write_attributes_asynch(self, attr_values, cb=None): """ write_attributes_asynch( self, values) -> int Write asynchronously (polling model) the specified attributes. Parameters : - values : (any) See write_attributes. Return : An asynchronous call identifier which is needed to get the server reply Throws : ConnectionFailed New in PyTango 7.0.0 write_attributes_asynch( self, values, callback) -> None Write asynchronously (callback model) a single attribute. Parameters : - values : (any) See write_attributes. - callback : (callable) This callback object should be an instance of a user class with an attr_written() method . It can also be any callable object. Return : None Throws : ConnectionFailed New in PyTango 7.0.0 .. important:: by default, TANGO is initialized with the **polling** model. If you want to use the **push** model (the one with the callback parameter), you need to change the global TANGO model to PUSH_CALLBACK. You can do this with the :meth:`tango.ApiUtil.set_asynch_cb_sub_model` """ if cb is None: return self.__write_attributes_asynch(attr_values) cb2 = __CallBackAutoDie() if isinstance(cb, collections.Callable): cb2.attr_write = cb else: cb2.attr_write = cb.attr_write return self.__write_attributes_asynch(attr_values, cb2) def __DeviceProxy__write_attribute_asynch(self, attr_name, value, cb=None): """ write_attributes_asynch( self, values) -> int write_attributes_asynch( self, values, callback) -> None Shortcut to self.write_attributes_asynch([attr_name, value], cb) New in PyTango 7.0.0 """ return self.write_attributes_asynch([(attr_name, value)], cb) def __DeviceProxy__write_read_attribute(self, attr_name, value, extract_as=ExtractAs.Numpy): result = self._write_read_attribute(attr_name, value, extract_as) return __check_read_attribute(result) def __DeviceProxy__write_read_attributes(self, name_val, attr_read_names, extract_as=ExtractAs.Numpy): return self._write_read_attributes(name_val, attr_read_names, extract_as) def __DeviceProxy__get_property(self, propname, value=None): """ get_property(propname, value=None) -> tango.DbData Get a (list) property(ies) for a device. This method accepts the following types as propname parameter: 1. string [in] - single property data to be fetched 2. sequence [in] - several property data to be fetched 3. tango.DbDatum [in] - single property data to be fetched 4. tango.DbData [in,out] - several property data to be fetched. 5. sequence - several property data to be feteched Note: for cases 3, 4 and 5 the 'value' parameter if given, is IGNORED. If value is given it must be a tango.DbData that will be filled with the property values Parameters : - propname : (any) property(ies) name(s) - value : (DbData) (optional, default is None meaning that the method will create internally a tango.DbData and return it filled with the property values Return : (DbData) object containing the property(ies) value(s). If a tango.DbData is given as parameter, it returns the same object otherwise a new tango.DbData is returned Throws : NonDbDevice, ConnectionFailed (with database), CommunicationFailed (with database), DevFailed from database device """ if is_pure_str(propname) or isinstance(propname, StdStringVector): new_value = value if new_value is None: new_value = DbData() self._get_property(propname, new_value) return DbData_2_dict(new_value) elif isinstance(propname, DbDatum): new_value = DbData() new_value.append(propname) self._get_property(new_value) return DbData_2_dict(new_value) elif isinstance(propname, collections.Sequence): if isinstance(propname, DbData): self._get_property(propname) return DbData_2_dict(propname) if is_pure_str(propname[0]): new_propname = StdStringVector() for i in propname: new_propname.append(i) new_value = value if new_value is None: new_value = DbData() self._get_property(new_propname, new_value) return DbData_2_dict(new_value) elif isinstance(propname[0], DbDatum): new_value = DbData() for i in propname: new_value.append(i) self._get_property(new_value) return DbData_2_dict(new_value) def __DeviceProxy__put_property(self, value): """ put_property(self, value) -> None Insert or update a list of properties for this device. This method accepts the following types as value parameter: 1. tango.DbDatum - single property data to be inserted 2. tango.DbData - several property data to be inserted 3. sequence - several property data to be inserted 4. dict - keys are property names and value has data to be inserted 5. dict> - keys are property names and value has data to be inserted 6. dict - keys are property names and str(obj) is property value Parameters : - value : can be one of the following: 1. tango.DbDatum - single property data to be inserted 2. tango.DbData - several property data to be inserted 3. sequence - several property data to be inserted 4. dict - keys are property names and value has data to be inserted 5. dict> - keys are property names and value has data to be inserted 6. dict - keys are property names and str(obj) is property value Return : None Throws : ConnectionFailed, CommunicationFailed DevFailed from device (DB_SQLError) """ if isinstance(value, DbData): pass elif isinstance(value, DbDatum): new_value = DbData() new_value.append(value) value = new_value elif is_non_str_seq(value): new_value = seq_2_DbData(value) elif isinstance(value, collections.Mapping): new_value = DbData() for k, v in value.items(): if isinstance(v, DbDatum): new_value.append(v) continue db_datum = DbDatum(k) if is_non_str_seq(v): seq_2_StdStringVector(v, db_datum.value_string) else: db_datum.value_string.append(str(v)) new_value.append(db_datum) value = new_value else: raise TypeError( 'Value must be a tango.DbDatum, tango.DbData, ' 'a sequence or a dictionary') return self._put_property(value) def __DeviceProxy__delete_property(self, value): """ delete_property(self, value) Delete a the given of properties for this device. This method accepts the following types as value parameter: 1. string [in] - single property to be deleted 2. tango.DbDatum [in] - single property data to be deleted 3. tango.DbData [in] - several property data to be deleted 4. sequence [in]- several property data to be deleted 5. sequence [in] - several property data to be deleted 6. dict [in] - keys are property names to be deleted (values are ignored) 7. dict [in] - several DbDatum.name are property names to be deleted (keys are ignored) Parameters : - value : can be one of the following: 1. string [in] - single property data to be deleted 2. tango.DbDatum [in] - single property data to be deleted 3. tango.DbData [in] - several property data to be deleted 4. sequence [in]- several property data to be deleted 5. sequence [in] - several property data to be deleted 6. dict [in] - keys are property names to be deleted (values are ignored) 7. dict [in] - several DbDatum.name are property names to be deleted (keys are ignored) Return : None Throws : ConnectionFailed, CommunicationFailed DevFailed from device (DB_SQLError) """ if isinstance(value, DbData) or isinstance(value, StdStringVector) or \ is_pure_str(value): new_value = value elif isinstance(value, DbDatum): new_value = DbData() new_value.append(value) elif isinstance(value, collections.Sequence): new_value = DbData() for e in value: if isinstance(e, DbDatum): new_value.append(e) else: new_value.append(DbDatum(str(e))) elif isinstance(value, collections.Mapping): new_value = DbData() for k, v in value.items(): if isinstance(v, DbDatum): new_value.append(v) else: new_value.append(DbDatum(k)) else: raise TypeError( 'Value must be a string, tango.DbDatum, ' 'tango.DbData, a sequence or a dictionary') return self._delete_property(new_value) def __DeviceProxy__get_property_list(self, filter, array=None): """ get_property_list(self, filter, array=None) -> obj Get the list of property names for the device. The parameter filter allows the user to filter the returned name list. The wildcard character is '*'. Only one wildcard character is allowed in the filter parameter. Parameters : - filter[in] : (str) the filter wildcard - array[out] : (sequence obj or None) (optional, default is None) an array to be filled with the property names. If None a new list will be created internally with the values. Return : the given array filled with the property names (or a new list if array is None) Throws : NonDbDevice, WrongNameSyntax, ConnectionFailed (with database), CommunicationFailed (with database) DevFailed from database device New in PyTango 7.0.0 """ if array is None: new_array = StdStringVector() self._get_property_list(filter, new_array) return new_array if isinstance(array, StdStringVector): self._get_property_list(filter, array) return array elif isinstance(array, collections.Sequence): new_array = StdStringVector() self._get_property_list(filter, new_array) StdStringVector_2_seq(new_array, array) return array raise TypeError('array must be a mutable sequence') def __DeviceProxy__get_attribute_config(self, value): """ get_attribute_config( self, name) -> AttributeInfoEx Return the attribute configuration for a single attribute. Parameters : - name : (str) attribute name Return : (AttributeInfoEx) Object containing the attribute information Throws : ConnectionFailed, CommunicationFailed, DevFailed from device Deprecated: use get_attribute_config_ex instead get_attribute_config( self, names) -> AttributeInfoList Return the attribute configuration for the list of specified attributes. To get all the attributes pass a sequence containing the constant tango.constants.AllAttr Parameters : - names : (sequence) attribute names Return : (AttributeInfoList) Object containing the attributes information Throws : ConnectionFailed, CommunicationFailed, DevFailed from device Deprecated: use get_attribute_config_ex instead """ if isinstance(value, StdStringVector) or is_pure_str(value): return self._get_attribute_config(value) elif isinstance(value, collections.Sequence): v = seq_2_StdStringVector(value) return self._get_attribute_config(v) raise TypeError('value must be a string or a sequence') def __DeviceProxy__get_attribute_config_ex(self, value): """ get_attribute_config_ex( self, name) -> AttributeInfoListEx : Return the extended attribute configuration for a single attribute. Parameters : - name : (str) attribute name Return : (AttributeInfoEx) Object containing the attribute information Throws : ConnectionFailed, CommunicationFailed, DevFailed from device get_attribute_config( self, names) -> AttributeInfoListEx : Return the extended attribute configuration for the list of specified attributes. To get all the attributes pass a sequence containing the constant tango.constants.AllAttr Parameters : - names : (sequence) attribute names Return : (AttributeInfoList) Object containing the attributes information Throws : ConnectionFailed, CommunicationFailed, DevFailed from device """ if isinstance(value, StdStringVector): return self._get_attribute_config_ex(value) elif is_pure_str(value): v = StdStringVector() v.append(value) return self._get_attribute_config_ex(v) elif isinstance(value, collections.Sequence): v = seq_2_StdStringVector(value) return self._get_attribute_config_ex(v) raise TypeError('value must be a string or a sequence') def __DeviceProxy__get_command_config(self, value=(constants.AllCmd,)): """ get_command_config( self) -> CommandInfoList Return the command configuration for all commands. Return : (CommandInfoList) Object containing the commands information Throws : ConnectionFailed, CommunicationFailed, DevFailed from device get_command_config( self, name) -> CommandInfo Return the command configuration for a single command. Parameters : - name : (str) command name Return : (CommandInfo) Object containing the command information Throws : ConnectionFailed, CommunicationFailed, DevFailed from device get_command_config( self, names) -> CommandInfoList Return the command configuration for the list of specified commands. Parameters : - names : (sequence) command names Return : (CommandInfoList) Object containing the commands information Throws : ConnectionFailed, CommunicationFailed, DevFailed from device """ if isinstance(value, StdStringVector) or is_pure_str(value): return self._get_command_config(value) elif isinstance(value, collections.Sequence): v = seq_2_StdStringVector(value) return self._get_command_config(v) raise TypeError('value must be a string or a sequence') def __DeviceProxy__get_pipe_config(self, value=None): """ get_pipe_config( self) -> PipeInfoList Return the pipe configuration for all pipes. Return : (PipeInfoList) Object containing the pipes information Throws : ConnectionFailed, CommunicationFailed, DevFailed from device get_pipe_config( self, name) -> PipeInfo Return the pipe configuration for a single pipe. Parameters : - name : (str) pipe name Return : (PipeInfo) Object containing the pipe information Throws : ConnectionFailed, CommunicationFailed, DevFailed from device get_pipe_config( self, names) -> PipeInfoList Return the pipe configuration for the list of specified pipes. To get all the pipes pass a sequence containing the constant tango.constants.AllPipe Parameters : - names : (sequence) pipe names Return : (PipeInfoList) Object containing the pipes information Throws : ConnectionFailed, CommunicationFailed, DevFailed from device New in PyTango 9.2.0 """ if value is None: value = [constants.AllPipe] if isinstance(value, StdStringVector) or is_pure_str(value): return self._get_pipe_config(value) elif isinstance(value, collections.Sequence): v = seq_2_StdStringVector(value) return self._get_pipe_config(v) raise TypeError('value must be a string or a sequence') def __DeviceProxy__set_attribute_config(self, value): """ set_attribute_config( self, attr_info) -> None Change the attribute configuration for the specified attribute Parameters : - attr_info : (AttributeInfo) attribute information Return : None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device set_attribute_config( self, attr_info_ex) -> None Change the extended attribute configuration for the specified attribute Parameters : - attr_info_ex : (AttributeInfoEx) extended attribute information Return : None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device set_attribute_config( self, attr_info) -> None Change the attributes configuration for the specified attributes Parameters : - attr_info : (sequence) attributes information Return : None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device set_attribute_config( self, attr_info_ex) -> None Change the extended attributes configuration for the specified attributes Parameters : - attr_info_ex : (sequence) extended attributes information Return : None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device """ if isinstance(value, AttributeInfoEx): v = AttributeInfoListEx() v.append(value) elif isinstance(value, AttributeInfo): v = AttributeInfoList() v.append(value) elif isinstance(value, AttributeInfoList): v = value elif isinstance(value, AttributeInfoListEx): v = value elif isinstance(value, collections.Sequence): if not len(value): return if isinstance(value[0], AttributeInfoEx): v = AttributeInfoListEx() elif isinstance(value[0], AttributeInfo): v = AttributeInfoList() else: raise TypeError( 'Value must be a AttributeInfo, AttributeInfoEx, ' 'sequence or sequence or sequence None Change the pipe configuration for the specified pipe Parameters : - pipe_info : (PipeInfo) pipe information Return : None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device set_pipe_config( self, pipe_info) -> None Change the pipes configuration for the specified pipes Parameters : - pipe_info : (sequence) pipes information Return : None Throws : ConnectionFailed, CommunicationFailed, DevFailed from device """ if isinstance(value, PipeInfo): v = PipeInfoList() v.append(value) elif isinstance(value, PipeInfoList): v = value elif isinstance(value, collections.Sequence): if not len(value): return if isinstance(value[0], PipeInfo): v = PipeInfoList() else: raise TypeError( 'Value must be a PipeInfo or a sequence') for i in value: v.append(i) else: raise TypeError( 'Value must be a PipeInfo or a sequence') return self._set_pipe_config(v) def __DeviceProxy__get_event_map_lock(self): """ Internal helper method""" if not hasattr(self, '_subscribed_events_lock'): # do it like this instead of self._subscribed_events = dict() to avoid # calling __setattr__ which requests list of tango attributes from device self.__dict__['_subscribed_events_lock'] = threading.Lock() return self._subscribed_events_lock def __DeviceProxy__get_event_map(self): """ Internal helper method""" if not hasattr(self, '_subscribed_events'): # do it like this instead of self._subscribed_events = dict() to avoid # calling __setattr__ which requests list of tango attributes from device self.__dict__['_subscribed_events'] = dict() return self._subscribed_events def __DeviceProxy__subscribe_event(self, *args, **kwargs): """ subscribe_event(event_type, cb, stateless=False, green_mode=None) -> int The client call to subscribe for event reception in the push model. The client implements a callback method which is triggered when the event is received. This method is currently used device interface change events only. Parameters : - event_type: (EventType) Is the event reason and must be on the enumerated values: * EventType.INTERFACE_CHANGE_EVENT - callback : (callable) Is any callable object or an object with a callable "push_event" method. - stateless : (bool) When the this flag is set to false, an exception will be thrown when the event subscription encounters a problem. With the stateless flag set to true, the event subscription will always succeed, even if the corresponding device server is not running. The keep alive thread will try every 10 seconds to subscribe for the specified event. At every subscription retry, a callback is executed which contains the corresponding exception - green_mode : the corresponding green mode (default is GreenMode.Synchronous) Return : An event id which has to be specified when unsubscribing from this event. Throws : EventSystemFailed subscribe_event(self, attr_name, event, callback, filters=[], stateless=False, extract_as=Numpy, green_mode=None) -> int The client call to subscribe for event reception in the push model. The client implements a callback method which is triggered when the event is received. Filtering is done based on the reason specified and the event type. For example when reading the state and the reason specified is "change" the event will be fired only when the state changes. Events consist of an attribute name and the event reason. A standard set of reasons are implemented by the system, additional device specific reasons can be implemented by device servers programmers. Parameters : - attr_name : (str) The device attribute name which will be sent as an event e.g. "current". - event_type: (EventType) Is the event reason and must be on the enumerated values: * EventType.CHANGE_EVENT * EventType.PERIODIC_EVENT * EventType.ARCHIVE_EVENT * EventType.ATTR_CONF_EVENT * EventType.DATA_READY_EVENT * EventType.USER_EVENT - callback : (callable) Is any callable object or an object with a callable "push_event" method. - filters : (sequence) A variable list of name,value pairs which define additional filters for events. - stateless : (bool) When the this flag is set to false, an exception will be thrown when the event subscription encounters a problem. With the stateless flag set to true, the event subscription will always succeed, even if the corresponding device server is not running. The keep alive thread will try every 10 seconds to subscribe for the specified event. At every subscription retry, a callback is executed which contains the corresponding exception - extract_as : (ExtractAs) - green_mode : the corresponding green mode (default is GreenMode.Synchronous) Return : An event id which has to be specified when unsubscribing from this event. Throws : EventSystemFailed subscribe_event(self, attr_name, event, queuesize, filters=[], stateless=False, green_mode=None) -> int The client call to subscribe for event reception in the pull model. Instead of a callback method the client has to specify the size of the event reception buffer. The event reception buffer is implemented as a round robin buffer. This way the client can set-up different ways to receive events: * Event reception buffer size = 1 : The client is interested only in the value of the last event received. All other events that have been received since the last reading are discarded. * Event reception buffer size > 1 : The client has chosen to keep an event history of a given size. When more events arrive since the last reading, older events will be discarded. * Event reception buffer size = ALL_EVENTS : The client buffers all received events. The buffer size is unlimited and only restricted by the available memory for the client. All other parameters are similar to the descriptions given in the other subscribe_event() version. """ # First argument is the event type if args and isinstance(args[0], int): return __DeviceProxy__subscribe_event_global(self, *args, **kwargs) # First argument is the attribute name else: return __DeviceProxy__subscribe_event_attrib(self, *args, **kwargs) def __DeviceProxy__subscribe_event_global(self, event_type, cb, stateless=False, green_mode=None): if event_type != EventType.INTERFACE_CHANGE_EVENT: raise TypeError("This method is only for Interface Change Events") else: if isinstance(cb, collections.Callable): cbfn = __CallBackPushEvent() cbfn.push_event = green_callback( cb, obj=self, green_mode=green_mode) elif hasattr(cb, "push_event") and isinstance( cb.push_event, collections.Callable): cbfn = __CallBackPushEvent() cbfn.push_event = green_callback( cb.push_event, obj=self, green_mode=green_mode) else: raise TypeError( "Parameter cb should be a callable object or " "an object with a 'push_event' method.") event_id = self.__subscribe_event(event_type, cbfn, stateless) with self.__get_event_map_lock(): se = self.__get_event_map() evt_data = se.get(event_id) if evt_data is not None: # Raise exception desc = textwrap.dedent("""\ Internal PyTango error: %s.subscribe_event(%s) already has key %d assigned to (%s, %s) Please report error to PyTango""") desc %= self, event_type, event_id, evt_data[2], evt_data[1] Except.throw_exception( "Py_InternalError", desc, "DeviceProxy.subscribe_event") se[event_id] = (cbfn, event_type, "dummy") return event_id def __DeviceProxy__subscribe_event_attrib(self, attr_name, event_type, cb_or_queuesize, filters=[], stateless=False, extract_as=ExtractAs.Numpy, green_mode=None): if isinstance(cb_or_queuesize, collections.Callable): cb = __CallBackPushEvent() cb.push_event = green_callback( cb_or_queuesize, obj=self, green_mode=green_mode) elif hasattr(cb_or_queuesize, "push_event") and \ isinstance(cb_or_queuesize.push_event, collections.Callable): cb = __CallBackPushEvent() cb.push_event = green_callback( cb_or_queuesize.push_event, obj=self, green_mode=green_mode) elif is_integer(cb_or_queuesize): cb = cb_or_queuesize # queuesize else: raise TypeError( "Parameter cb_or_queuesize should be a number, a" " callable object or an object with a 'push_event' method.") event_id = self.__subscribe_event( attr_name, event_type, cb, filters, stateless, extract_as) with self.__get_event_map_lock(): se = self.__get_event_map() evt_data = se.get(event_id) if evt_data is None: se[event_id] = (cb, event_type, attr_name) return event_id # Raise exception desc = textwrap.dedent("""\ Internal PyTango error: %s.subscribe_event(%s, %s) already has key %d assigned to (%s, %s) Please report error to PyTango""") desc %= self, attr_name, event_type, event_id, evt_data[2], evt_data[1] Except.throw_exception( "Py_InternalError", desc, "DeviceProxy.subscribe_event") def __DeviceProxy__unsubscribe_event(self, event_id): """ unsubscribe_event(self, event_id) -> None Unsubscribes a client from receiving the event specified by event_id. Parameters : - event_id : (int) is the event identifier returned by the DeviceProxy::subscribe_event(). Unlike in TangoC++ we chech that the event_id has been subscribed in this DeviceProxy. Return : None Throws : EventSystemFailed """ events_del = set() timestamp = time.time() se = self.__get_event_map() with self.__get_event_map_lock(): # first delete event callbacks that have expire for evt_id, (_, expire_time) in self._pending_unsubscribe.items(): if expire_time <= timestamp: events_del.add(evt_id) for evt_id in events_del: del self._pending_unsubscribe[evt_id] # unsubscribe and put the callback in the pending unsubscribe callbacks try: evt_info = se[event_id] except KeyError: raise KeyError("This device proxy does not own this subscription " + str(event_id)) del se[event_id] self._pending_unsubscribe[event_id] = evt_info[0], timestamp + _UNSUBSCRIBE_LIFETIME self.__unsubscribe_event(event_id) def __DeviceProxy__unsubscribe_event_all(self): with self.__get_event_map_lock(): se = self.__get_event_map() event_ids = list(se.keys()) se.clear() for event_id in event_ids: self.__unsubscribe_event(event_id) def __DeviceProxy__get_events(self, event_id, callback=None, extract_as=ExtractAs.Numpy): """ get_events( event_id, callback=None, extract_as=Numpy) -> None The method extracts all waiting events from the event reception buffer. If callback is not None, it is executed for every event. During event subscription the client must have chosen the pull model for this event. The callback will receive a parameter of type EventData, AttrConfEventData or DataReadyEventData depending on the type of the event (event_type parameter of subscribe_event). If callback is None, the method extracts all waiting events from the event reception buffer. The returned event_list is a vector of EventData, AttrConfEventData or DataReadyEventData pointers, just the same data the callback would have received. Parameters : - event_id : (int) is the event identifier returned by the DeviceProxy.subscribe_event() method. - callback : (callable) Any callable object or any object with a "push_event" method. - extract_as: (ExtractAs) Return : None Throws : EventSystemFailed See Also : subscribe_event New in PyTango 7.0.0 """ if callback is None: queuesize, event_type, attr_name = self.__get_event_map().get(event_id, (None, None, None)) if event_type is None: raise ValueError("Invalid event_id. You are not subscribed to event %s." % str(event_id)) if event_type in (EventType.CHANGE_EVENT, EventType.QUALITY_EVENT, EventType.PERIODIC_EVENT, EventType.ARCHIVE_EVENT, EventType.USER_EVENT): return self.__get_data_events(event_id, extract_as) elif event_type in (EventType.ATTR_CONF_EVENT,): return self.__get_attr_conf_events(event_id, extract_as) elif event_type in (EventType.DATA_READY_EVENT,): return self.__get_data_ready_events(event_id, extract_as) elif event_type in (EventType.PIPE_EVENT,): return self.__get_pipe_events(event_id, extract_as) else: assert (False) raise ValueError("Unknown event_type: " + str(event_type)) elif isinstance(callback, collections.Callable): cb = __CallBackPushEvent() cb.push_event = callback return self.__get_callback_events(event_id, cb, extract_as) elif hasattr(callback, 'push_event') and isinstance(callback.push_event, collections.Callable): cb = __CallBackPushEvent() cb.push_event = callback.push_event return self.__get_callback_events(event_id, cb, extract_as) else: raise TypeError( "Parameter 'callback' should be None, a callable object or an object with a 'push_event' method.") def __DeviceProxy___get_info_(self): """Protected method that gets device info once and stores it in cache""" if not hasattr(self, '_dev_info'): try: self.__dict__["_dev_info"] = self.info() except: return __TangoInfo() return self._dev_info def __DeviceProxy__str(self): info = self._get_info_() return "%s(%s)" % (info.dev_class, self.dev_name()) def __DeviceProxy__read_pipe(self, pipe_name, extract_as=ExtractAs.Numpy): r = self.__read_pipe(pipe_name) return r.extract(extract_as) def __get_pipe_type_simple(obj): if is_non_str_seq(obj): if len(obj) == 2 and \ is_pure_str(obj[0]) and \ (is_non_str_seq(obj[1]) or isinstance(obj[1], dict)): tg_type = CmdArgType.DevPipeBlob else: tg_type = __get_pipe_type(obj[0]) tg_type = scalar_to_array_type(tg_type) elif is_pure_str(obj): tg_type = CmdArgType.DevString elif isinstance(obj, DevState): tg_type = CmdArgType.DevState elif isinstance(obj, bool): tg_type = CmdArgType.DevBoolean elif is_integer(obj): tg_type = CmdArgType.DevLong64 elif is_number(obj): tg_type = CmdArgType.DevDouble else: raise ValueError('Cannot determine object tango type') return tg_type def __get_pipe_type_numpy_support(obj): try: ndim, dtype = obj.ndim, str(obj.dtype) except AttributeError: return __get_pipe_type_simple(obj) if ndim > 1: raise TypeError('cannot translate numpy array with {0} ' 'dimensions to tango type'.format(obj.ndim)) tg_type = TO_TANGO_TYPE[dtype] if ndim > 0: tg_type = scalar_to_array_type(dtype) return tg_type def __get_tango_type(dtype): if is_non_str_seq(dtype): tg_type = dtype[0] if is_non_str_seq(tg_type): raise TypeError("Pipe doesn't support 2D data") tg_type = TO_TANGO_TYPE[tg_type] tg_type = scalar_to_array_type(tg_type) else: tg_type = TO_TANGO_TYPE[dtype] return tg_type def __get_pipe_type(obj, dtype=None): if dtype is not None: return __get_tango_type(dtype) if constants.NUMPY_SUPPORT: return __get_pipe_type_numpy_support(obj) return __get_pipe_type_simple(obj) def __sanatize_pipe_element(elem): if isinstance(elem, dict): result = dict(elem) else: result = dict(name=elem[0], value=elem[1]) result['value'] = value = result.get('value', result.pop('blob', None)) result['dtype'] = dtype = __get_pipe_type(value, dtype=result.get('dtype')) if dtype == CmdArgType.DevPipeBlob: result['value'] = value[0], __sanatize_pipe_blob(value[1]) return result def __sanatize_pipe_blob(blob): if isinstance(blob, dict): return [__sanatize_pipe_element((k, v)) for k, v in blob.items()] else: return [__sanatize_pipe_element(elem) for elem in blob] def __DeviceProxy__write_pipe(self, *args, **kwargs): pipe_name, (blob_name, blob_data) = args sani_blob_data = __sanatize_pipe_blob(blob_data) self.__write_pipe(pipe_name, blob_name, sani_blob_data) def __DeviceProxy__read_attributes(self, *args, **kwargs): return self._read_attributes(*args, **kwargs) def __DeviceProxy__write_attribute(self, *args, **kwargs): return self._write_attribute(*args, **kwargs) def __DeviceProxy__write_attributes(self, *args, **kwargs): return self._write_attributes(*args, **kwargs) def __DeviceProxy__ping(self, *args, **kwargs): return self._ping(*args, **kwargs) def __DeviceProxy__state(self, *args, **kwargs): """state(self) -> DevState A method which returns the state of the device. Parameters : None Return : (DevState) constant Example : dev_st = dev.state() if dev_st == DevState.ON : ... """ return self._state(*args, **kwargs) def __DeviceProxy__status(self, *args, **kwargs): """status(self) -> str A method which returns the status of the device as a string. Parameters : None Return : (str) describing the device status """ return self._status(*args, **kwargs) def __DeviceProxy__write_attribute_reply(self, *args, **kwargs): """ write_attribute_reply(self, id) -> None Check if the answer of an asynchronous write_attribute is arrived (polling model). If the reply is arrived and if it is a valid reply, the call returned. If the reply is an exception, it is re-thrown by this call. An exception is also thrown in case of the reply is not yet arrived. Parameters : - id : (int) the asynchronous call identifier. Return : None Throws : AsynCall, AsynReplyNotArrived, CommunicationFailed, DevFailed from device. New in PyTango 7.0.0 write_attribute_reply(self, id, timeout) -> None Check if the answer of an asynchronous write_attribute is arrived (polling model). id is the asynchronous call identifier. If the reply is arrived and if it is a valid reply, the call returned. If the reply is an exception, it is re-thrown by this call. If the reply is not yet arrived, the call will wait (blocking the process) for the time specified in timeout. If after timeout milliseconds, the reply is still not there, an exception is thrown. If timeout is set to 0, the call waits until the reply arrived. Parameters : - id : (int) the asynchronous call identifier. - timeout : (int) the timeout Return : None Throws : AsynCall, AsynReplyNotArrived, CommunicationFailed, DevFailed from device. New in PyTango 7.0.0 """ return self.write_attributes_reply(*args, **kwargs) def __init_DeviceProxy(): DeviceProxy.__init_orig__ = DeviceProxy.__init__ DeviceProxy.__init__ = __DeviceProxy__init__ DeviceProxy.get_green_mode = __DeviceProxy__get_green_mode DeviceProxy.set_green_mode = __DeviceProxy__set_green_mode DeviceProxy.__getattr__ = __DeviceProxy__getattr DeviceProxy.__setattr__ = __DeviceProxy__setattr DeviceProxy.__getitem__ = __DeviceProxy__getitem DeviceProxy.__setitem__ = __DeviceProxy__setitem DeviceProxy.__contains__ = __DeviceProxy__contains DeviceProxy.__dir__ = __DeviceProxy__dir DeviceProxy.__get_cmd_cache = __DeviceProxy__get_cmd_cache DeviceProxy.__get_attr_cache = __DeviceProxy__get_attr_cache DeviceProxy.__get_pipe_cache = __DeviceProxy__get_pipe_cache DeviceProxy.__refresh_cmd_cache = __DeviceProxy__refresh_cmd_cache DeviceProxy.__refresh_attr_cache = __DeviceProxy__refresh_attr_cache DeviceProxy.__refresh_pipe_cache = __DeviceProxy__refresh_pipe_cache DeviceProxy.ping = green(__DeviceProxy__ping) DeviceProxy.state = green(__DeviceProxy__state) DeviceProxy.status = green(__DeviceProxy__status) DeviceProxy.read_attribute = green(__DeviceProxy__read_attribute) DeviceProxy.read_attributes = green(__DeviceProxy__read_attributes) DeviceProxy.write_attribute = green(__DeviceProxy__write_attribute) DeviceProxy.write_attributes = green(__DeviceProxy__write_attributes) DeviceProxy.write_read_attribute = green(__DeviceProxy__write_read_attribute) DeviceProxy.write_read_attributes = green(__DeviceProxy__write_read_attributes) DeviceProxy.read_attributes_asynch = __DeviceProxy__read_attributes_asynch DeviceProxy.read_attribute_asynch = __DeviceProxy__read_attribute_asynch DeviceProxy.read_attribute_reply = __DeviceProxy__read_attribute_reply DeviceProxy.write_attributes_asynch = __DeviceProxy__write_attributes_asynch DeviceProxy.write_attribute_asynch = __DeviceProxy__write_attribute_asynch DeviceProxy.write_attribute_reply = __DeviceProxy__write_attribute_reply DeviceProxy.read_pipe = green(__DeviceProxy__read_pipe) DeviceProxy.write_pipe = green(__DeviceProxy__write_pipe) DeviceProxy.get_property = __DeviceProxy__get_property DeviceProxy.put_property = __DeviceProxy__put_property DeviceProxy.delete_property = __DeviceProxy__delete_property DeviceProxy.get_property_list = __DeviceProxy__get_property_list DeviceProxy.get_attribute_config = __DeviceProxy__get_attribute_config DeviceProxy.get_attribute_config_ex = __DeviceProxy__get_attribute_config_ex DeviceProxy.set_attribute_config = __DeviceProxy__set_attribute_config DeviceProxy.get_command_config = __DeviceProxy__get_command_config DeviceProxy.get_pipe_config = __DeviceProxy__get_pipe_config DeviceProxy.set_pipe_config = __DeviceProxy__set_pipe_config DeviceProxy.__get_event_map = __DeviceProxy__get_event_map DeviceProxy.__get_event_map_lock = __DeviceProxy__get_event_map_lock DeviceProxy.subscribe_event = green( __DeviceProxy__subscribe_event, consume_green_mode=False) DeviceProxy.unsubscribe_event = green(__DeviceProxy__unsubscribe_event) DeviceProxy.__unsubscribe_event_all = __DeviceProxy__unsubscribe_event_all DeviceProxy.get_events = __DeviceProxy__get_events DeviceProxy.__str__ = __DeviceProxy__str DeviceProxy.__repr__ = __DeviceProxy__str DeviceProxy._get_info_ = __DeviceProxy___get_info_ def __doc_DeviceProxy(): def document_method(method_name, desc, append=True): return __document_method(DeviceProxy, method_name, desc, append) DeviceProxy.__doc__ = """\ DeviceProxy is the high level Tango object which provides the client with an easy-to-use interface to TANGO devices. DeviceProxy provides interfaces to all TANGO Device interfaces.The DeviceProxy manages timeouts, stateless connections and reconnection if the device server is restarted. To create a DeviceProxy, a Tango Device name must be set in the object constructor. Example : dev = tango.DeviceProxy("sys/tg_test/1") DeviceProxy(dev_name, green_mode=None, wait=True, timeout=True) -> DeviceProxy DeviceProxy(self, dev_name, need_check_acc, green_mode=None, wait=True, timeout=True) -> DeviceProxy Creates a new :class:`~tango.DeviceProxy`. :param dev_name: the device name or alias :type dev_name: str :param need_check_acc: in first version of the function it defaults to True. Determines if at creation time of DeviceProxy it should check for channel access (rarely used) :type need_check_acc: bool :param green_mode: determines the mode of execution of the device (including. the way it is created). Defaults to the current global green_mode (check :func:`~tango.get_green_mode` and :func:`~tango.set_green_mode`) :type green_mode: :obj:`~tango.GreenMode` :param wait: whether or not to wait for result. If green_mode Ignored when green_mode is Synchronous (always waits). :type wait: bool :param timeout: The number of seconds to wait for the result. If None, then there is no limit on the wait time. Ignored when green_mode is Synchronous or wait is False. :type timeout: float :returns: if green_mode is Synchronous or wait is True: :class:`~tango.DeviceProxy` elif green_mode is Futures: :class:`concurrent.futures.Future` elif green_mode is Gevent: :class:`gevent.event.AsynchResult` :throws: * :class:`~tango.DevFailed` if green_mode is Synchronous or wait is True and there is an error creating the device. * :class:`concurrent.futures.TimeoutError` if green_mode is Futures, wait is False, timeout is not None and the time to create the device has expired. * :class:`gevent.timeout.Timeout` if green_mode is Gevent, wait is False, timeout is not None and the time to create the device has expired. .. versionadded:: 8.1.0 *green_mode* parameter. *wait* parameter. *timeout* parameter. """ # ------------------------------------ # General methods # ------------------------------------ document_method("info", """ info(self) -> DeviceInfo A method which returns information on the device Parameters : None Return : (DeviceInfo) object Example : dev_info = dev.info() print(dev_info.dev_class) print(dev_info.server_id) print(dev_info.server_host) print(dev_info.server_version) print(dev_info.doc_url) print(dev_info.dev_type) All DeviceInfo fields are strings except for the server_version which is an integer" """) document_method("get_device_db", """ get_device_db(self) -> Database Returns the internal database reference Parameters : None Return : (Database) object New in PyTango 7.0.0 """) document_method("adm_name", """ adm_name(self) -> str Return the name of the corresponding administrator device. This is useful if you need to send an administration command to the device server, e.g restart it New in PyTango 3.0.4 """) document_method("description", """ description(self) -> str Get device description. Parameters : None Return : (str) describing the device """) document_method("name", """ name(self) -> str Return the device name from the device itself. """) document_method("alias", """ alias(self) -> str Return the device alias if one is defined. Otherwise, throws exception. Return : (str) device alias """) document_method("get_tango_lib_version", """ get_tango_lib_version(self) -> int Returns the Tango lib version number used by the remote device Otherwise, throws exception. Return : (int) The device Tango lib version as a 3 or 4 digits number. Possible return value are: 100,200,500,520,700,800,810,... New in PyTango 8.1.0 """) document_method("ping", """ ping(self) -> int A method which sends a ping to the device Parameters : None Return : (int) time elapsed in microseconds Throws : exception if device is not alive """) document_method("black_box", """ black_box(self, n) -> sequence Get the last commands executed on the device server Parameters : - n : n number of commands to get Return : (sequence) sequence of strings containing the date, time, command and from which client computer the command was executed Example : print(black_box(4)) """) # ------------------------------------- # Device methods # ------------------------------------- document_method("get_command_list", """ get_command_list(self) -> sequence Return the names of all commands implemented for this device. Parameters : None Return : sequence Throws : ConnectionFailed, CommunicationFailed, DevFailed from device """) document_method("command_query", """ command_query(self, command) -> CommandInfo Query the device for information about a single command. Parameters : - command : (str) command name Return : (CommandInfo) object Throws : ConnectionFailed, CommunicationFailed, DevFailed from device Example : com_info = dev.command_query(""DevString"") print(com_info.cmd_name) print(com_info.cmd_tag) print(com_info.in_type) print(com_info.out_type) print(com_info.in_type_desc) print(com_info.out_type_desc) print(com_info.disp_level) See CommandInfo documentation string form more detail """) document_method("command_list_query", """ command_list_query(self) -> sequence Query the device for information on all commands. Parameters : None Return : (CommandInfoList) Sequence of CommandInfo objects """) document_method("import_info", """ import_info(self) -> DbDevImportInfo Query the device for import info from the database. Parameters : None Return : (DbDevImportInfo) Example : dev_import = dev.import_info() print(dev_import.name) print(dev_import.exported) print(dev_ior.ior) print(dev_version.version) All DbDevImportInfo fields are strings except for exported which is an integer" """) # ------------------------------------ # Property methods # ------------------------------------ # get_property -> in code # put_property -> in code # delete_property -> in code # get_property_list -> in code # ------------------------------------ # Attribute methods # ------------------------------------ document_method("get_attribute_list", """ get_attribute_list(self) -> sequence Return the names of all attributes implemented for this device. Parameters : None Return : sequence Throws : ConnectionFailed, CommunicationFailed, DevFailed from device """) # get_attribute_config -> in code # get_attribute_config_ex -> in code document_method("attribute_query", """ attribute_query(self, attr_name) -> AttributeInfoEx Query the device for information about a single attribute. Parameters : - attr_name :(str) the attribute name Return : (AttributeInfoEx) containing the attribute configuration Throws : ConnectionFailed, CommunicationFailed, DevFailed from device """) document_method("attribute_list_query", """ attribute_list_query(self) -> sequence Query the device for info on all attributes. This method returns a sequence of tango.AttributeInfo. Parameters : None Return : (sequence) containing the attributes configuration Throws : ConnectionFailed, CommunicationFailed, DevFailed from device """) document_method("attribute_list_query_ex", """ attribute_list_query_ex(self) -> sequence Query the device for info on all attributes. This method returns a sequence of tango.AttributeInfoEx. Parameters : None Return : (sequence) containing the attributes configuration Throws : ConnectionFailed, CommunicationFailed, DevFailed from device """) # set_attribute_config -> in code document_method("read_attribute", """ read_attribute(self, attr_name, extract_as=ExtractAs.Numpy, green_mode=None, wait=True, timeout=None) -> DeviceAttribute Read a single attribute. Parameters : - attr_name : (str) The name of the attribute to read. - extract_as : (ExtractAs) Defaults to numpy. - green_mode : (GreenMode) Defaults to the current DeviceProxy GreenMode. (see :meth:`~tango.DeviceProxy.get_green_mode` and :meth:`~tango.DeviceProxy.set_green_mode`). - wait : (bool) whether or not to wait for result. If green_mode is *Synchronous*, this parameter is ignored as it always waits for the result. Ignored when green_mode is Synchronous (always waits). - timeout : (float) The number of seconds to wait for the result. If None, then there is no limit on the wait time. Ignored when green_mode is Synchronous or wait is False. Return : (DeviceAttribute) Throws : ConnectionFailed, CommunicationFailed, DevFailed from device TimeoutError (green_mode == Futures) If the future didn't finish executing before the given timeout. Timeout (green_mode == Gevent) If the async result didn't finish executing before the given timeout. .. versionchanged:: 7.1.4 For DevEncoded attributes, before it was returning a DeviceAttribute.value as a tuple **(format, data)** no matter what was the *extract_as* value was. Since 7.1.4, it returns a **(format, data)** unless *extract_as* is String, in which case it returns **(format, data)**. .. versionchanged:: 8.0.0 For DevEncoded attributes, now returns a DeviceAttribute.value as a tuple **(format, data)** unless *extract_as* is String, in which case it returns **(format, data)**. Carefull, if using python >= 3 data is decoded using default python *utf-8* encoding. This means that PyTango assumes tango DS was written encapsulating string into *utf-8* which is the default python encoding. .. versionadded:: 8.1.0 *green_mode* parameter. *wait* parameter. *timeout* parameter. """) document_method("read_attributes", """ read_attributes(self, attr_names, extract_as=ExtractAs.Numpy, green_mode=None, wait=True, timeout=None) -> sequence Read the list of specified attributes. Parameters : - attr_names : (sequence) A list of attributes to read. - extract_as : (ExtractAs) Defaults to numpy. - green_mode : (GreenMode) Defaults to the current DeviceProxy GreenMode. (see :meth:`~tango.DeviceProxy.get_green_mode` and :meth:`~tango.DeviceProxy.set_green_mode`). - wait : (bool) whether or not to wait for result. If green_mode is *Synchronous*, this parameter is ignored as it always waits for the result. Ignored when green_mode is Synchronous (always waits). - timeout : (float) The number of seconds to wait for the result. If None, then there is no limit on the wait time. Ignored when green_mode is Synchronous or wait is False. Return : (sequence) Throws : ConnectionFailed, CommunicationFailed, DevFailed from device TimeoutError (green_mode == Futures) If the future didn't finish executing before the given timeout. Timeout (green_mode == Gevent) If the async result didn't finish executing before the given timeout. .. versionadded:: 8.1.0 *green_mode* parameter. *wait* parameter. *timeout* parameter. """) document_method("write_attribute", """ write_attribute(self, attr_name, value, green_mode=None, wait=True, timeout=None) -> None write_attribute(self, attr_info, value, green_mode=None, wait=True, timeout=None) -> None Write a single attribute. Parameters : - attr_name : (str) The name of the attribute to write. - attr_info : (AttributeInfo) - value : The value. For non SCALAR attributes it may be any sequence of sequences. - green_mode : (GreenMode) Defaults to the current DeviceProxy GreenMode. (see :meth:`~tango.DeviceProxy.get_green_mode` and :meth:`~tango.DeviceProxy.set_green_mode`). - wait : (bool) whether or not to wait for result. If green_mode is *Synchronous*, this parameter is ignored as it always waits for the result. Ignored when green_mode is Synchronous (always waits). - timeout : (float) The number of seconds to wait for the result. If None, then there is no limit on the wait time. Ignored when green_mode is Synchronous or wait is False. Throws : ConnectionFailed, CommunicationFailed, DeviceUnlocked, DevFailed from device TimeoutError (green_mode == Futures) If the future didn't finish executing before the given timeout. Timeout (green_mode == Gevent) If the async result didn't finish executing before the given timeout. .. versionadded:: 8.1.0 *green_mode* parameter. *wait* parameter. *timeout* parameter. """) document_method("write_attributes", """ write_attributes(self, name_val, green_mode=None, wait=True, timeout=None) -> None Write the specified attributes. Parameters : - name_val: A list of pairs (attr_name, value). See write_attribute - green_mode : (GreenMode) Defaults to the current DeviceProxy GreenMode. (see :meth:`~tango.DeviceProxy.get_green_mode` and :meth:`~tango.DeviceProxy.set_green_mode`). - wait : (bool) whether or not to wait for result. If green_mode is *Synchronous*, this parameter is ignored as it always waits for the result. Ignored when green_mode is Synchronous (always waits). - timeout : (float) The number of seconds to wait for the result. If None, then there is no limit on the wait time. Ignored when green_mode is Synchronous or wait is False. Throws : ConnectionFailed, CommunicationFailed, DeviceUnlocked, DevFailed or NamedDevFailedList from device TimeoutError (green_mode == Futures) If the future didn't finish executing before the given timeout. Timeout (green_mode == Gevent) If the async result didn't finish executing before the given timeout. .. versionadded:: 8.1.0 *green_mode* parameter. *wait* parameter. *timeout* parameter. """) document_method("write_read_attribute", """ write_read_attribute(self, attr_name, value, extract_as=ExtractAs.Numpy, green_mode=None, wait=True, timeout=None) -> DeviceAttribute Write then read a single attribute in a single network call. By default (serialisation by device), the execution of this call in the server can't be interrupted by other clients. Parameters : see write_attribute(attr_name, value) Return : A tango.DeviceAttribute object. Throws : ConnectionFailed, CommunicationFailed, DeviceUnlocked, DevFailed from device, WrongData TimeoutError (green_mode == Futures) If the future didn't finish executing before the given timeout. Timeout (green_mode == Gevent) If the async result didn't finish executing before the given timeout. New in PyTango 7.0.0 .. versionadded:: 8.1.0 *green_mode* parameter. *wait* parameter. *timeout* parameter. """) document_method("write_read_attributes", """ write_read_attributes(self, name_val, attr_names, extract_as=ExtractAs.Numpy, green_mode=None, wait=True, timeout=None) -> DeviceAttribute Write then read attribute(s) in a single network call. By default (serialisation by device), the execution of this call in the server can't be interrupted by other clients. On the server side, attribute(s) are first written and if no exception has been thrown during the write phase, attributes will be read. Parameters : - name_val: A list of pairs (attr_name, value). See write_attribute - attr_names : (sequence) A list of attributes to read. - extract_as : (ExtractAs) Defaults to numpy. - green_mode : (GreenMode) Defaults to the current DeviceProxy GreenMode. (see :meth:`~tango.DeviceProxy.get_green_mode` and :meth:`~tango.DeviceProxy.set_green_mode`). - wait : (bool) whether or not to wait for result. If green_mode is *Synchronous*, this parameter is ignored as it always waits for the result. Ignored when green_mode is Synchronous (always waits). - timeout : (float) The number of seconds to wait for the result. If None, then there is no limit on the wait time. Ignored when green_mode is Synchronous or wait is False. Return : (sequence) Throws : ConnectionFailed, CommunicationFailed, DeviceUnlocked, DevFailed from device, WrongData TimeoutError (green_mode == Futures) If the future didn't finish executing before the given timeout. Timeout (green_mode == Gevent) If the async result didn't finish executing before the given timeout. New in PyTango 9.2.0 """) # ------------------------------------- # Pipe methods # ------------------------------------- document_method("read_pipe", """ read_pipe(self, pipe_name, extract_as=ExtractAs.Numpy, green_mode=None, wait=True, timeout=None) -> tuple Read a single pipe. The result is a *blob*: a tuple with two elements: blob name (string) and blob data (sequence). The blob data consists of a sequence where each element is a dictionary with the following keys: - name: blob element name - dtype: tango data type - value: blob element data (str for DevString, etc) In case dtype is ``DevPipeBlob``, value is again a *blob*. Parameters : - pipe_name : (str) The name of the pipe to read. - extract_as : (ExtractAs) Defaults to numpy. - green_mode : (GreenMode) Defaults to the current DeviceProxy GreenMode. (see :meth:`~tango.DeviceProxy.get_green_mode` and :meth:`~tango.DeviceProxy.set_green_mode`). - wait : (bool) whether or not to wait for result. If green_mode is *Synchronous*, this parameter is ignored as it always waits for the result. Ignored when green_mode is Synchronous (always waits). - timeout : (float) The number of seconds to wait for the result. If None, then there is no limit on the wait time. Ignored when green_mode is Synchronous or wait is False. Return : tuple Throws : ConnectionFailed, CommunicationFailed, DevFailed from device TimeoutError (green_mode == Futures) If the future didn't finish executing before the given timeout. Timeout (green_mode == Gevent) If the async result didn't finish executing before the given timeout. New in PyTango 9.2.0 """) document_method("write_pipe", """ write_pipe(self, blob, green_mode=None, wait=True, timeout=None) Write a *blob* to a single pipe. The *blob* comprises: a tuple with two elements: blob name (string) and blob data (sequence). The blob data consists of a sequence where each element is a dictionary with the following keys: - name: blob element name - dtype: tango data type - value: blob element data (str for DevString, etc) In case dtype is ``DevPipeBlob``, value is also a *blob*. Parameters : - blob : a tuple with two elements: blob name (string) and blob data (sequence). - green_mode : (GreenMode) Defaults to the current DeviceProxy GreenMode. (see :meth:`~tango.DeviceProxy.get_green_mode` and :meth:`~tango.DeviceProxy.set_green_mode`). - wait : (bool) whether or not to wait for result. If green_mode is *Synchronous*, this parameter is ignored as it always waits for the result. Ignored when green_mode is Synchronous (always waits). - timeout : (float) The number of seconds to wait for the result. If None, then there is no limit on the wait time. Ignored when green_mode is Synchronous or wait is False. Throws : ConnectionFailed, CommunicationFailed, DevFailed from device TimeoutError (green_mode == Futures) If the future didn't finish executing before the given timeout. Timeout (green_mode == Gevent) If the async result didn't finish executing before the given timeout. New in PyTango 9.2.1 """) # ------------------------------------- # History methods # ------------------------------------- document_method("command_history", """ command_history(self, cmd_name, depth) -> sequence Retrieve command history from the command polling buffer. See chapter on Advanced Feature for all details regarding polling Parameters : - cmd_name : (str) Command name. - depth : (int) The wanted history depth. Return : This method returns a vector of DeviceDataHistory types. Throws : NonSupportedFeature, ConnectionFailed, CommunicationFailed, DevFailed from device """) document_method("attribute_history", """ attribute_history(self, attr_name, depth, extract_as=ExtractAs.Numpy) -> sequence Retrieve attribute history from the attribute polling buffer. See chapter on Advanced Feature for all details regarding polling Parameters : - attr_name : (str) Attribute name. - depth : (int) The wanted history depth. - extract_as : (ExtractAs) Return : This method returns a vector of DeviceAttributeHistory types. Throws : NonSupportedFeature, ConnectionFailed, CommunicationFailed, DevFailed from device """) # ------------------------------------- # Polling administration methods # ------------------------------------- document_method("polling_status", """ polling_status(self) -> sequence Return the device polling status. Parameters : None Return : (sequence) One string for each polled command/attribute. Each string is multi-line string with: - attribute/command name - attribute/command polling period in milliseconds - attribute/command polling ring buffer - time needed for last attribute/command execution in milliseconds - time since data in the ring buffer has not been updated - delta time between the last records in the ring buffer - exception parameters in case of the last execution failed """) document_method("poll_command", """ poll_command(self, cmd_name, period) -> None Add a command to the list of polled commands. Parameters : - cmd_name : (str) command name - period : (int) polling period in milliseconds Return : None """) document_method("poll_attribute", """ poll_attribute(self, attr_name, period) -> None Add an attribute to the list of polled attributes. Parameters : - attr_name : (str) attribute name - period : (int) polling period in milliseconds Return : None """) document_method("get_command_poll_period", """ get_command_poll_period(self, cmd_name) -> int Return the command polling period. Parameters : - cmd_name : (str) command name Return : polling period in milliseconds """) document_method("get_attribute_poll_period", """ get_attribute_poll_period(self, attr_name) -> int Return the attribute polling period. Parameters : - attr_name : (str) attribute name Return : polling period in milliseconds """) document_method("is_command_polled", """ is_command_polled(self, cmd_name) -> bool True if the command is polled. Parameters : - cmd_name : (str) command name Return : boolean value """) document_method("is_attribute_polled", """ is_attribute_polled(self, attr_name) -> bool True if the attribute is polled. Parameters : - attr_name : (str) attribute name Return : boolean value """) document_method("stop_poll_command", """ stop_poll_command(self, cmd_name) -> None Remove a command from the list of polled commands. Parameters : - cmd_name : (str) command name Return : None """) document_method("stop_poll_attribute", """ stop_poll_attribute(self, attr_name) -> None Remove an attribute from the list of polled attributes. Parameters : - attr_name : (str) attribute name Return : None """) # ------------------------------------- # Asynchronous methods # ------------------------------------- # read_attribute_asynch -> in code # read_attributes_asynch -> in code # read_attribute_reply -> in code document_method("read_attributes_reply", """ read_attributes_reply(self, id, extract_as=ExtractAs.Numpy) -> DeviceAttribute Check if the answer of an asynchronous read_attribute is arrived (polling model). Parameters : - id : (int) is the asynchronous call identifier. - extract_as : (ExtractAs) Return : If the reply is arrived and if it is a valid reply, it is returned to the caller in a list of DeviceAttribute. If the reply is an exception, it is re-thrown by this call. An exception is also thrown in case of the reply is not yet arrived. Throws : AsynCall, AsynReplyNotArrived, ConnectionFailed, CommunicationFailed, DevFailed from device New in PyTango 7.0.0 read_attributes_reply(self, id, timeout, extract_as=ExtractAs.Numpy) -> DeviceAttribute Check if the answer of an asynchronous read_attributes is arrived (polling model). Parameters : - id : (int) is the asynchronous call identifier. - timeout : (int) - extract_as : (ExtractAs) Return : If the reply is arrived and if it is a valid reply, it is returned to the caller in a list of DeviceAttribute. If the reply is an exception, it is re-thrown by this call. If the reply is not yet arrived, the call will wait (blocking the process) for the time specified in timeout. If after timeout milliseconds, the reply is still not there, an exception is thrown. If timeout is set to 0, the call waits until the reply arrived. Throws : AsynCall, AsynReplyNotArrived, ConnectionFailed, CommunicationFailed, DevFailed from device New in PyTango 7.0.0 """) document_method("pending_asynch_call", """ pending_asynch_call(self) -> int Return number of device asynchronous pending requests" New in PyTango 7.0.0 """) # write_attributes_asynch -> in code document_method("write_attributes_reply", """ write_attributes_reply(self, id) -> None Check if the answer of an asynchronous write_attributes is arrived (polling model). If the reply is arrived and if it is a valid reply, the call returned. If the reply is an exception, it is re-thrown by this call. An exception is also thrown in case of the reply is not yet arrived. Parameters : - id : (int) the asynchronous call identifier. Return : None Throws : AsynCall, AsynReplyNotArrived, CommunicationFailed, DevFailed from device. New in PyTango 7.0.0 write_attributes_reply(self, id, timeout) -> None Check if the answer of an asynchronous write_attributes is arrived (polling model). id is the asynchronous call identifier. If the reply is arrived and if it is a valid reply, the call returned. If the reply is an exception, it is re-thrown by this call. If the reply is not yet arrived, the call will wait (blocking the process) for the time specified in timeout. If after timeout milliseconds, the reply is still not there, an exception is thrown. If timeout is set to 0, the call waits until the reply arrived. Parameters : - id : (int) the asynchronous call identifier. - timeout : (int) the timeout Return : None Throws : AsynCall, AsynReplyNotArrived, CommunicationFailed, DevFailed from device. New in PyTango 7.0.0 """) # ------------------------------------ # Logging administration methods # ------------------------------------ document_method("add_logging_target", """ add_logging_target(self, target_type_target_name) -> None Adds a new logging target to the device. The target_type_target_name input parameter must follow the format: target_type::target_name. Supported target types are: console, file and device. For a device target, the target_name part of the target_type_target_name parameter must contain the name of a log consumer device (as defined in A.8). For a file target, target_name is the full path to the file to log to. If omitted, the device's name is used to build the file name (which is something like domain_family_member.log). Finally, the target_name part of the target_type_target_name input parameter is ignored in case of a console target and can be omitted. Parameters : - target_type_target_name : (str) logging target Return : None Throws : DevFailed from device New in PyTango 7.0.0 """) document_method("remove_logging_target", """ remove_logging_target(self, target_type_target_name) -> None Removes a logging target from the device's target list. The target_type_target_name input parameter must follow the format: target_type::target_name. Supported target types are: console, file and device. For a device target, the target_name part of the target_type_target_name parameter must contain the name of a log consumer device (as defined in ). For a file target, target_name is the full path to the file to remove. If omitted, the default log file is removed. Finally, the target_name part of the target_type_target_name input parameter is ignored in case of a console target and can be omitted. If target_name is set to '*', all targets of the specified target_type are removed. Parameters : - target_type_target_name : (str) logging target Return : None New in PyTango 7.0.0 """) document_method("get_logging_target", """ get_logging_target(self) -> sequence Returns a sequence of string containing the current device's logging targets. Each vector element has the following format: target_type::target_name. An empty sequence is returned is the device has no logging targets. Parameters : None Return : a squence with the logging targets New in PyTango 7.0.0 """) document_method("get_logging_level", """ get_logging_level(self) -> int Returns the current device's logging level, where: - 0=OFF - 1=FATAL - 2=ERROR - 3=WARNING - 4=INFO - 5=DEBUG Parameters :None Return : (int) representing the current logging level New in PyTango 7.0.0 """) document_method("set_logging_level", """ set_logging_level(self, (int)level) -> None Changes the device's logging level, where: - 0=OFF - 1=FATAL - 2=ERROR - 3=WARNING - 4=INFO - 5=DEBUG Parameters : - level : (int) logging level Return : None New in PyTango 7.0.0 """) # ------------------------------------ # Event methods # ------------------------------------ # subscribe_event -> in code # unsubscribe_event -> in code # get_events -> in code document_method("event_queue_size", """ event_queue_size(self, event_id) -> int Returns the number of stored events in the event reception buffer. After every call to DeviceProxy.get_events(), the event queue size is 0. During event subscription the client must have chosen the 'pull model' for this event. event_id is the event identifier returned by the DeviceProxy.subscribe_event() method. Parameters : - event_id : (int) event identifier Return : an integer with the queue size Throws : EventSystemFailed New in PyTango 7.0.0 """) document_method("get_last_event_date", """ get_last_event_date(self, event_id) -> TimeVal Returns the arrival time of the last event stored in the event reception buffer. After every call to DeviceProxy:get_events(), the event reception buffer is empty. In this case an exception will be returned. During event subscription the client must have chosen the 'pull model' for this event. event_id is the event identifier returned by the DeviceProxy.subscribe_event() method. Parameters : - event_id : (int) event identifier Return : (tango.TimeVal) representing the arrival time Throws : EventSystemFailed New in PyTango 7.0.0 """) document_method("is_event_queue_empty", """ is_event_queue_empty(self, event_id) -> bool Returns true when the event reception buffer is empty. During event subscription the client must have chosen the 'pull model' for this event. event_id is the event identifier returned by the DeviceProxy.subscribe_event() method. Parameters : - event_id : (int) event identifier Return : (bool) True if queue is empty or False otherwise Throws : EventSystemFailed New in PyTango 7.0.0 """) # ------------------------------------ # Locking methods # ------------------------------------ document_method("lock", """ lock(self, (int)lock_validity) -> None Lock a device. The lock_validity is the time (in seconds) the lock is kept valid after the previous lock call. A default value of 10 seconds is provided and should be fine in most cases. In case it is necessary to change the lock validity, it's not possible to ask for a validity less than a minimum value set to 2 seconds. The library provided an automatic system to periodically re lock the device until an unlock call. No code is needed to start/stop this automatic re-locking system. The locking system is re-entrant. It is then allowed to call this method on a device already locked by the same process. The locking system has the following features: * It is impossible to lock the database device or any device server process admin device * Destroying a locked DeviceProxy unlocks the device * Restarting a locked device keeps the lock * It is impossible to restart a device locked by someone else * Restarting a server breaks the lock A locked device is protected against the following calls when executed by another client: * command_inout call except for device state and status requested via command and for the set of commands defined as allowed following the definition of allowed command in the Tango control access schema. * write_attribute call * write_read_attribute call * set_attribute_config call Parameters : - lock_validity : (int) lock validity time in seconds (optional, default value is tango.constants.DEFAULT_LOCK_VALIDITY) Return : None New in PyTango 7.0.0 """) document_method("unlock", """ unlock(self, (bool)force) -> None Unlock a device. If used, the method argument provides a back door on the locking system. If this argument is set to true, the device will be unlocked even if the caller is not the locker. This feature is provided for administration purpopse and should be used very carefully. If this feature is used, the locker will receive a DeviceUnlocked during the next call which is normally protected by the locking Tango system. Parameters : - force : (bool) force unlocking even if we are not the locker (optional, default value is False) Return : None New in PyTango 7.0.0 """) document_method("locking_status", """ locking_status(self) -> str This method returns a plain string describing the device locking status. This string can be: * 'Device is not locked' in case the device is not locked * 'Device is locked by CPP or Python client with PID from host ' in case the device is locked by a CPP client * 'Device is locked by JAVA client class
    from host ' in case the device is locked by a JAVA client Parameters : None Return : a string representing the current locking status New in PyTango 7.0.0" """) document_method("is_locked", """ is_locked(self) -> bool Returns True if the device is locked. Otherwise, returns False. Parameters : None Return : (bool) True if the device is locked. Otherwise, False New in PyTango 7.0.0 """) document_method("is_locked_by_me", """ is_locked_by_me(self) -> bool Returns True if the device is locked by the caller. Otherwise, returns False (device not locked or locked by someone else) Parameters : None Return : (bool) True if the device is locked by us. Otherwise, False New in PyTango 7.0.0 """) document_method("get_locker", """ get_locker(self, lockinfo) -> bool If the device is locked, this method returns True an set some locker process informations in the structure passed as argument. If the device is not locked, the method returns False. Parameters : - lockinfo [out] : (tango.LockInfo) object that will be filled with lock informantion Return : (bool) True if the device is locked by us. Otherwise, False New in PyTango 7.0.0 """) def device_proxy_init(doc=True): __init_DeviceProxy() if doc: __doc_DeviceProxy() pytango-9.2.2/tango/device_server.py000066400000000000000000002542771316324373100175400ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ """ This is an internal PyTango module. """ from __future__ import print_function import copy from ._tango import ( DeviceImpl, Device_3Impl, Device_4Impl, Device_5Impl, DevFailed, Attribute, WAttribute, MultiAttribute, MultiClassAttribute, Attr, Logger, AttrWriteType, AttrDataFormat, DispLevel, UserDefaultAttrProp, StdStringVector) from .utils import document_method as __document_method from .utils import copy_doc, get_latest_device_class from .attr_data import AttrData from .log4tango import TangoStream __docformat__ = "restructuredtext" __all__ = ["ChangeEventProp", "PeriodicEventProp", "ArchiveEventProp", "AttributeAlarm", "EventProperties", "AttributeConfig", "AttributeConfig_2", "AttributeConfig_3", "AttributeConfig_5", "MultiAttrProp", "device_server_init"] class LatestDeviceImpl(get_latest_device_class()): __doc__ = """\ Latest implementation of the TANGO device base class (alias for {0}). It inherits from CORBA classes where all the network layer is implemented. """.format(get_latest_device_class().__name__) class AttributeAlarm(object): """This class represents the python interface for the Tango IDL object AttributeAlarm.""" def __init__(self): self.min_alarm = '' self.max_alarm = '' self.min_warning = '' self.max_warning = '' self.delta_t = '' self.delta_val = '' self.extensions = [] class ChangeEventProp(object): """This class represents the python interface for the Tango IDL object ChangeEventProp.""" def __init__(self): self.rel_change = '' self.abs_change = '' self.extensions = [] class PeriodicEventProp(object): """This class represents the python interface for the Tango IDL object PeriodicEventProp.""" def __init__(self): self.period = '' self.extensions = [] class ArchiveEventProp(object): """This class represents the python interface for the Tango IDL object ArchiveEventProp.""" def __init__(self): self.rel_change = '' self.abs_change = '' self.period = '' self.extensions = [] class EventProperties(object): """This class represents the python interface for the Tango IDL object EventProperties.""" def __init__(self): self.ch_event = ChangeEventProp() self.per_event = PeriodicEventProp() self.arch_event = ArchiveEventProp() class MultiAttrProp(object): """This class represents the python interface for the Tango IDL object MultiAttrProp.""" def __init__(self): self.label = '' self.description = '' self.unit = '' self.standard_unit = '' self.display_unit = '' self.format = '' self.min_value = '' self.max_value = '' self.min_alarm = '' self.max_alarm = '' self.min_warning = '' self.max_warning = '' self.delta_t = '' self.delta_val = '' self.event_period = '' self.archive_period = '' self.rel_change = '' self.abs_change = '' self.archive_rel_change = '' self.archive_abs_change = '' def _init_attr_config(attr_cfg): """Helper function to initialize attribute config objects""" attr_cfg.name = '' attr_cfg.writable = AttrWriteType.READ attr_cfg.data_format = AttrDataFormat.SCALAR attr_cfg.data_type = 0 attr_cfg.max_dim_x = 0 attr_cfg.max_dim_y = 0 attr_cfg.description = '' attr_cfg.label = '' attr_cfg.unit = '' attr_cfg.standard_unit = '' attr_cfg.display_unit = '' attr_cfg.format = '' attr_cfg.min_value = '' attr_cfg.max_value = '' attr_cfg.writable_attr_name = '' attr_cfg.extensions = [] class AttributeConfig(object): """This class represents the python interface for the Tango IDL object AttributeConfig.""" def __init__(self): _init_attr_config(self) self.min_alarm = '' self.max_alarm = '' class AttributeConfig_2(object): """This class represents the python interface for the Tango IDL object AttributeConfig_2.""" def __init__(self): _init_attr_config(self) self.level = DispLevel.OPERATOR self.min_alarm = '' self.max_alarm = '' class AttributeConfig_3(object): """This class represents the python interface for the Tango IDL object AttributeConfig_3.""" def __init__(self): _init_attr_config(self) self.level = -1 self.att_alarm = AttributeAlarm() self.event_prop = EventProperties() self.sys_extensions = [] class AttributeConfig_5(object): """This class represents the python interface for the Tango IDL object AttributeConfig_5.""" def __init__(self): _init_attr_config(self) self.memorized = False self.mem_init = False self.level = -1 self.root_attr_name = '' self.enum_labels = [] self.att_alarm = AttributeAlarm() self.event_prop = EventProperties() self.sys_extensions = [] def __Attribute__get_properties(self, attr_cfg=None): """get_properties(self, attr_cfg = None) -> AttributeConfig Get attribute properties. Parameters : - conf : the config object to be filled with the attribute configuration. Default is None meaning the method will create internally a new AttributeConfig_5 and return it. Can be AttributeConfig, AttributeConfig_2, AttributeConfig_3, AttributeConfig_5 or MultiAttrProp Return : (AttributeConfig) the config object filled with attribute configuration information New in PyTango 7.1.4 """ if attr_cfg is None: attr_cfg = MultiAttrProp() if not isinstance(attr_cfg, MultiAttrProp): raise TypeError("attr_cfg must be an instance of MultiAttrProp") return self._get_properties_multi_attr_prop(attr_cfg) def __Attribute__set_properties(self, attr_cfg, dev=None): """set_properties(self, attr_cfg, dev) -> None Set attribute properties. This method sets the attribute properties value with the content of the fileds in the AttributeConfig/ AttributeConfig_3 object Parameters : - conf : (AttributeConfig or AttributeConfig_3) the config object. - dev : (DeviceImpl) the device (not used, maintained for backward compatibility) New in PyTango 7.1.4 """ if not isinstance(attr_cfg, MultiAttrProp): raise TypeError("attr_cfg must be an instance of MultiAttrProp") return self._set_properties_multi_attr_prop(attr_cfg) def __Attribute__str(self): return '%s(%s)' % (self.__class__.__name__, self.get_name()) def __init_Attribute(): Attribute.__str__ = __Attribute__str Attribute.__repr__ = __Attribute__str Attribute.get_properties = __Attribute__get_properties Attribute.set_properties = __Attribute__set_properties def __DeviceImpl__get_device_class(self): try: return self._device_class_instance except AttributeError: return None def __DeviceImpl__get_device_properties(self, ds_class=None): """get_device_properties(self, ds_class = None) -> None Utility method that fetches all the device properties from the database and converts them into members of this DeviceImpl. Parameters : - ds_class : (DeviceClass) the DeviceClass object. Optional. Default value is None meaning that the corresponding DeviceClass object for this DeviceImpl will be used Return : None Throws : DevFailed """ if ds_class is None: try: # Call this method in a try/except in case this is called during the DS shutdown sequence ds_class = self.get_device_class() except: return try: pu = self.prop_util = ds_class.prop_util self.device_property_list = copy.deepcopy(ds_class.device_property_list) class_prop = ds_class.class_property_list pu.get_device_properties(self, class_prop, self.device_property_list) for prop_name in class_prop: setattr(self, prop_name, pu.get_property_values(prop_name, class_prop)) for prop_name in self.device_property_list: setattr(self, prop_name, self.prop_util.get_property_values(prop_name, self.device_property_list)) except DevFailed as df: print(80 * "-") print(df) raise df def __DeviceImpl__add_attribute(self, attr, r_meth=None, w_meth=None, is_allo_meth=None): """add_attribute(self, attr, r_meth=None, w_meth=None, is_allo_meth=None) -> Attr Add a new attribute to the device attribute list. Please, note that if you add an attribute to a device at device creation time, this attribute will be added to the device class attribute list. Therefore, all devices belonging to the same class created after this attribute addition will also have this attribute. Parameters : - attr : (Attr or AttrData) the new attribute to be added to the list. - r_meth : (callable) the read method to be called on a read request - w_meth : (callable) the write method to be called on a write request (if attr is writable) - is_allo_meth: (callable) the method that is called to check if it is possible to access the attribute or not Return : (Attr) the newly created attribute. Throws : DevFailed """ attr_data = None if isinstance(attr, AttrData): attr_data = attr attr = attr.to_attr() att_name = attr.get_name() add_name_in_list = False r_name = 'read_%s' % att_name if r_meth is None: if attr_data is not None: r_name = attr_data.read_method_name else: r_name = r_meth.__name__ w_name = 'write_%s' % att_name if w_meth is None: if attr_data is not None: w_name = attr_data.write_method_name else: w_name = w_meth.__name__ ia_name = 'is_%s_allowed' % att_name if is_allo_meth is None: if attr_data is not None: ia_name = attr_data.is_allowed_name else: ia_name = is_allo_meth.__name__ try: self._add_attribute(attr, r_name, w_name, ia_name) if add_name_in_list: cl = self.get_device_class() cl.dyn_att_added_methods.append(att_name) except: if add_name_in_list: self._remove_attr_meth(att_name) raise return attr def __DeviceImpl__remove_attribute(self, attr_name): """remove_attribute(self, attr_name) -> None Remove one attribute from the device attribute list. Parameters : - attr_name : (str) attribute name Return : None Throws : DevFailed """ try: # Call this method in a try/except in case remove_attribute # is called during the DS shutdown sequence cl = self.get_device_class() except: return dev_list = cl.get_device_list() nb_dev = len(dev_list) if nb_dev == 1: self._remove_attr_meth(attr_name) else: nb_except = 0 for dev in dev_list: try: dev.get_device_attr().get_attr_by_name(attr_name) except: nb_except += 1 if nb_except == nb_dev - 1: self._remove_attr_meth(attr_name) self._remove_attribute(attr_name) def __DeviceImpl___remove_attr_meth(self, attr_name): """for internal usage only""" cl = self.get_device_class() if cl.dyn_att_added_methods.count(attr_name) != 0: r_meth_name = 'read_%s' % attr_name if hasattr(self.__class__, r_meth_name): delattr(self.__class__, r_meth_name) w_meth_name = 'write_%s' % attr_name if hasattr(self.__class__, w_meth_name): delattr(self.__class__, w_meth_name) allo_meth_name = 'is_%s_allowed' % attr_name if hasattr(self.__class__, allo_meth_name): delattr(self.__class__, allo_meth_name) cl.dyn_att_added_methods.remove(attr_name) def __DeviceImpl__add_command(self, cmd, device_level=True): """add_command(self, cmd, level=TANGO::OPERATOR) -> cmd Add a new command to the device command list. Parameters : - cmd : the new command to be added to the list - device_level : Set this flag to true if the command must be added for only this device Return : Command Throws : DevFailed """ add_name_in_list = False # This flag is always False, what use is it? try: config = dict(cmd.__tango_command__[1][2]) if config and ("Display level" in config): disp_level = config["Display level"] else: disp_level = DispLevel.OPERATOR self._add_command(cmd.__name__, cmd.__tango_command__[1], disp_level, device_level) if add_name_in_list: cl = self.get_device_class() cl.dyn_cmd_added_methods.append(cmd.__name__) except: if add_name_in_list: self._remove_cmd(cmd.__name__) raise return cmd def __DeviceImpl__remove_command(self, cmd_name, free_it=False, clean_db=True): """ remove_command(self, attr_name) -> None Remove one command from the device command list. Parameters : - cmd_name : (str) command name to be removed from the list - free_it : Boolean set to true if the command object must be freed. - clean_db : Clean command related information (included polling info if the command is polled) from database. Return : None Throws : DevFailed """ try: # Call this method in a try/except in case remove # is called during the DS shutdown sequence cl = self.get_device_class() except: return if cl.dyn_cmd_added_methods.count(cmd_name) != 0: cl.dyn_cmd_added_methods.remove(cmd_name) self._remove_command(cmd_name, free_it, clean_db) def __DeviceImpl__debug_stream(self, msg, *args): """ debug_stream(self, msg, *args) -> None Sends the given message to the tango debug stream. Since PyTango 7.1.3, the same can be achieved with:: print(msg, file=self.log_debug) Parameters : - msg : (str) the message to be sent to the debug stream Return : None """ self.__debug_stream(msg % args) def __DeviceImpl__info_stream(self, msg, *args): """ info_stream(self, msg, *args) -> None Sends the given message to the tango info stream. Since PyTango 7.1.3, the same can be achieved with:: print(msg, file=self.log_info) Parameters : - msg : (str) the message to be sent to the info stream Return : None """ self.__info_stream(msg % args) def __DeviceImpl__warn_stream(self, msg, *args): """ warn_stream(self, msg, *args) -> None Sends the given message to the tango warn stream. Since PyTango 7.1.3, the same can be achieved with:: print(msg, file=self.log_warn) Parameters : - msg : (str) the message to be sent to the warn stream Return : None """ self.__warn_stream(msg % args) def __DeviceImpl__error_stream(self, msg, *args): """ error_stream(self, msg, *args) -> None Sends the given message to the tango error stream. Since PyTango 7.1.3, the same can be achieved with:: print(msg, file=self.log_error) Parameters : - msg : (str) the message to be sent to the error stream Return : None """ self.__error_stream(msg % args) def __DeviceImpl__fatal_stream(self, msg, *args): """ fatal_stream(self, msg, *args) -> None Sends the given message to the tango fatal stream. Since PyTango 7.1.3, the same can be achieved with:: print(msg, file=self.log_fatal) Parameters : - msg : (str) the message to be sent to the fatal stream Return : None """ self.__fatal_stream(msg % args) @property def __DeviceImpl__debug(self): if not hasattr(self, "_debug_s"): self._debug_s = TangoStream(self.debug_stream) return self._debug_s @property def __DeviceImpl__info(self): if not hasattr(self, "_info_s"): self._info_s = TangoStream(self.info_stream) return self._info_s @property def __DeviceImpl__warn(self): if not hasattr(self, "_warn_s"): self._warn_s = TangoStream(self.warn_stream) return self._warn_s @property def __DeviceImpl__error(self): if not hasattr(self, "_error_s"): self._error_s = TangoStream(self.error_stream) return self._error_s @property def __DeviceImpl__fatal(self): if not hasattr(self, "_fatal_s"): self._fatal_s = TangoStream(self.fatal_stream) return self._fatal_s def __DeviceImpl__str(self): return '%s(%s)' % (self.__class__.__name__, self.get_name()) def __init_DeviceImpl(): DeviceImpl._device_class_instance = None DeviceImpl.get_device_class = __DeviceImpl__get_device_class DeviceImpl.get_device_properties = __DeviceImpl__get_device_properties DeviceImpl.add_attribute = __DeviceImpl__add_attribute DeviceImpl.remove_attribute = __DeviceImpl__remove_attribute DeviceImpl._remove_attr_meth = __DeviceImpl___remove_attr_meth DeviceImpl.add_command = __DeviceImpl__add_command DeviceImpl.remove_command = __DeviceImpl__remove_command DeviceImpl.__str__ = __DeviceImpl__str DeviceImpl.__repr__ = __DeviceImpl__str DeviceImpl.debug_stream = __DeviceImpl__debug_stream DeviceImpl.info_stream = __DeviceImpl__info_stream DeviceImpl.warn_stream = __DeviceImpl__warn_stream DeviceImpl.error_stream = __DeviceImpl__error_stream DeviceImpl.fatal_stream = __DeviceImpl__fatal_stream DeviceImpl.log_debug = __DeviceImpl__debug DeviceImpl.log_info = __DeviceImpl__info DeviceImpl.log_warn = __DeviceImpl__warn DeviceImpl.log_error = __DeviceImpl__error DeviceImpl.log_fatal = __DeviceImpl__fatal def __Logger__log(self, level, msg, *args): """ log(self, level, msg, *args) -> None Sends the given message to the tango the selected stream. Parameters : - level: (Level.LevelLevel) Log level - msg : (str) the message to be sent to the stream - args: (seq) list of optional message arguments Return : None .. versionchanged: """ self.__log(level, msg % args) def __Logger__log_unconditionally(self, level, msg, *args): """ log_unconditionally(self, level, msg, *args) -> None Sends the given message to the tango the selected stream, without checking the level. Parameters : - level: (Level.LevelLevel) Log level - msg : (str) the message to be sent to the stream - args: (seq) list of optional message arguments Return : None """ self.__log_unconditionally(level, msg % args) def __Logger__debug(self, msg, *args): """ debug(self, msg, *args) -> None Sends the given message to the tango debug stream. Parameters : - msg : (str) the message to be sent to the debug stream - args: (seq) list of optional message arguments Return : None """ self.__debug(msg % args) def __Logger__info(self, msg, *args): """ info(self, msg, *args) -> None Sends the given message to the tango info stream. Parameters : - msg : (str) the message to be sent to the info stream - args: (seq) list of optional message arguments Return : None """ self.__info(msg % args) def __Logger__warn(self, msg, *args): """ warn(self, msg, *args) -> None Sends the given message to the tango warn stream. Parameters : - msg : (str) the message to be sent to the warn stream - args: (seq) list of optional message arguments Return : None """ self.__warn(msg % args) def __Logger__error(self, msg, *args): """ error(self, msg, *args) -> None Sends the given message to the tango error stream. Parameters : - msg : (str) the message to be sent to the error stream - args: (seq) list of optional message arguments Return : None """ self.__error(msg % args) def __Logger__fatal(self, msg, *args): """ fatal(self, msg, *args) -> None Sends the given message to the tango fatal stream. Parameters : - msg : (str) the message to be sent to the fatal stream - args: (seq) list of optional message arguments Return : None """ self.__fatal(msg % args) def __UserDefaultAttrProp_set_enum_labels(self, enum_labels): """ set_enum_labels(self, enum_labels) -> None Set default enumeration labels. Parameters : - enum_labels : (seq) list of enumeration labels New in PyTango 9.2.0 """ elbls = StdStringVector() for enu in enum_labels: elbls.append(enu) return self._set_enum_labels(elbls) def __Attr__str(self): return '%s(%s)' % (self.__class__.__name__, self.get_name()) def __init_Attr(): Attr.__str__ = __Attr__str Attr.__repr__ = __Attr__str def __init_UserDefaultAttrProp(): UserDefaultAttrProp.set_enum_labels = __UserDefaultAttrProp_set_enum_labels def __init_Logger(): Logger.log = __Logger__log Logger.log_unconditionally = __Logger__log_unconditionally Logger.debug = __Logger__debug Logger.info = __Logger__info Logger.warn = __Logger__warn Logger.error = __Logger__error Logger.fatal = __Logger__fatal def __doc_DeviceImpl(): def document_method(method_name, desc, append=True): return __document_method(DeviceImpl, method_name, desc, append) DeviceImpl.__doc__ = """ Base class for all TANGO device. This class inherits from CORBA classes where all the network layer is implemented. """ document_method("init_device", """ init_device(self) -> None Intialize the device. Parameters : None Return : None """) document_method("set_state", """ set_state(self, new_state) -> None Set device state. Parameters : - new_state : (DevState) the new device state Return : None """) document_method("get_state", """ get_state(self) -> DevState Get a COPY of the device state. Parameters : None Return : (DevState) Current device state """) document_method("get_prev_state", """ get_prev_state(self) -> DevState Get a COPY of the device's previous state. Parameters : None Return : (DevState) the device's previous state """) document_method("get_name", """ get_name(self) -> (str) Get a COPY of the device name. Parameters : None Return : (str) the device name """) document_method("get_device_attr", """ get_device_attr(self) -> MultiAttribute Get device multi attribute object. Parameters : None Return : (MultiAttribute) the device's MultiAttribute object """) document_method("register_signal", """ register_signal(self, signo) -> None Register a signal. Register this device as device to be informed when signal signo is sent to to the device server process Parameters : - signo : (int) signal identifier Return : None """) document_method("unregister_signal", """ unregister_signal(self, signo) -> None Unregister a signal. Unregister this device as device to be informed when signal signo is sent to to the device server process Parameters : - signo : (int) signal identifier Return : None """) document_method("get_status", """ get_status(self, ) -> str Get a COPY of the device status. Parameters : None Return : (str) the device status """) document_method("set_status", """ set_status(self, new_status) -> None Set device status. Parameters : - new_status : (str) the new device status Return : None """) document_method("append_status", """ append_status(self, status, new_line=False) -> None Appends a string to the device status. Parameters : status : (str) the string to be appened to the device status new_line : (bool) If true, appends a new line character before the string. Default is False Return : None """) document_method("dev_state", """ dev_state(self) -> DevState Get device state. Default method to get device state. The behaviour of this method depends on the device state. If the device state is ON or ALARM, it reads the attribute(s) with an alarm level defined, check if the read value is above/below the alarm and eventually change the state to ALARM, return the device state. For all th other device state, this method simply returns the state This method can be redefined in sub-classes in case of the default behaviour does not fullfill the needs. Parameters : None Return : (DevState) the device state Throws : DevFailed - If it is necessary to read attribute(s) and a problem occurs during the reading """) document_method("dev_status", """ dev_status(self) -> str Get device status. Default method to get device status. It returns the contents of the device dev_status field. If the device state is ALARM, alarm messages are added to the device status. This method can be redefined in sub-classes in case of the default behaviour does not fullfill the needs. Parameters : None Return : (str) the device status Throws : DevFailed - If it is necessary to read attribute(s) and a problem occurs during the reading """) document_method("set_change_event", """ set_change_event(self, attr_name, implemented, detect=True) -> None Set an implemented flag for the attribute to indicate that the server fires change events manually, without the polling to be started. If the detect parameter is set to true, the criteria specified for the change event are verified and the event is only pushed if they are fullfilled. If detect is set to false the event is fired without any value checking! Parameters : - attr_name : (str) attribute name - implemented : (bool) True when the server fires change events manually. - detect : (bool) Triggers the verification of the change event properties when set to true. Default value is true. Return : None """) document_method("set_archive_event", """ set_archive_event(self, attr_name, implemented, detect=True) -> None Set an implemented flag for the attribute to indicate that the server fires archive events manually, without the polling to be started. If the detect parameter is set to true, the criteria specified for the archive event are verified and the event is only pushed if they are fullfilled. If detect is set to false the event is fired without any value checking! Parameters : - attr_name : (str) attribute name - implemented : (bool) True when the server fires change events manually. - detect : (bool) Triggers the verification of the change event properties when set to true. Default value is true. Return : None """) document_method("push_change_event", """ push_change_event(self, attr_name) -> None push_change_event(self, attr_name, except) -> None push_change_event(self, attr_name, data, dim_x = 1, dim_y = 0) -> None push_change_event(self, attr_name, str_data, data) -> None push_change_event(self, attr_name, data, time_stamp, quality, dim_x = 1, dim_y = 0) -> None push_change_event(self, attr_name, str_data, data, time_stamp, quality) -> None Push a change event for the given attribute name. The event is pushed to the notification daemon. Parameters: - attr_name : (str) attribute name - data : the data to be sent as attribute event data. Data must be compatible with the attribute type and format. for SPECTRUM and IMAGE attributes, data can be any type of sequence of elements compatible with the attribute type - str_data : (str) special variation for DevEncoded data type. In this case 'data' must be a str or an object with the buffer interface. - except: (DevFailed) Instead of data, you may want to send an exception. - dim_x : (int) the attribute x length. Default value is 1 - dim_y : (int) the attribute y length. Default value is 0 - time_stamp : (double) the time stamp - quality : (AttrQuality) the attribute quality factor Throws : DevFailed If the attribute data type is not coherent. """) document_method("push_archive_event", """ push_archive_event(self, attr_name) -> None push_archive_event(self, attr_name, except) -> None push_archive_event(self, attr_name, data, dim_x = 1, dim_y = 0) -> None push_archive_event(self, attr_name, str_data, data) -> None push_archive_event(self, attr_name, data, time_stamp, quality, dim_x = 1, dim_y = 0) -> None push_archive_event(self, attr_name, str_data, data, time_stamp, quality) -> None Push an archive event for the given attribute name. The event is pushed to the notification daemon. Parameters: - attr_name : (str) attribute name - data : the data to be sent as attribute event data. Data must be compatible with the attribute type and format. for SPECTRUM and IMAGE attributes, data can be any type of sequence of elements compatible with the attribute type - str_data : (str) special variation for DevEncoded data type. In this case 'data' must be a str or an object with the buffer interface. - except: (DevFailed) Instead of data, you may want to send an exception. - dim_x : (int) the attribute x length. Default value is 1 - dim_y : (int) the attribute y length. Default value is 0 - time_stamp : (double) the time stamp - quality : (AttrQuality) the attribute quality factor Throws : DevFailed If the attribute data type is not coherent. """) document_method("push_event", """ push_event(self, attr_name, filt_names, filt_vals) -> None push_event(self, attr_name, filt_names, filt_vals, data, dim_x = 1, dim_y = 0) -> None push_event(self, attr_name, filt_names, filt_vals, str_data, data) -> None push_event(self, attr_name, filt_names, filt_vals, data, time_stamp, quality, dim_x = 1, dim_y = 0) -> None push_event(self, attr_name, filt_names, filt_vals, str_data, data, time_stamp, quality) -> None Push a user event for the given attribute name. The event is pushed to the notification daemon. Parameters: - attr_name : (str) attribute name - filt_names : (sequence) the filterable fields name - filt_vals : (sequence) the filterable fields value - data : the data to be sent as attribute event data. Data must be compatible with the attribute type and format. for SPECTRUM and IMAGE attributes, data can be any type of sequence of elements compatible with the attribute type - str_data : (str) special variation for DevEncoded data type. In this case 'data' must be a str or an object with the buffer interface. - dim_x : (int) the attribute x length. Default value is 1 - dim_y : (int) the attribute y length. Default value is 0 - time_stamp : (double) the time stamp - quality : (AttrQuality) the attribute quality factor Throws : DevFailed If the attribute data type is not coherent. """) document_method("push_data_ready_event", """ push_data_ready_event(self, attr_name, counter = 0) -> None Push a data ready event for the given attribute name. The event is pushed to the notification daemon. The method needs only the attribue name and an optional "counter" which will be passed unchanged within the event Parameters : - attr_name : (str) attribute name - counter : (int) the user counter Return : None Throws : DevFailed If the attribute name is unknown. """) document_method("push_pipe_event", """ push_pipe_event(self, pipe_name, except) -> None push_pipe_event(self, pipe_name, blob, reuse_it) -> None push_pipe_event(self, pipe_name, blob, timeval, reuse_it) -> None Push a pipe event for the given blob. Parameters : - pipe_name : (str) pipe name - blob : (DevicePipeBlob) the blob data Return : None Throws : DevFailed If the pipe name is unknown. New in PyTango 9.2.2 """) document_method("get_logger", """ get_logger(self) -> Logger Returns the Logger object for this device Parameters : None Return : (Logger) the Logger object for this device """) document_method("get_exported_flag", """ get_exported_flag(self) -> bool Returns the state of the exported flag Parameters : None Return : (bool) the state of the exported flag New in PyTango 7.1.2 """) document_method("get_poll_ring_depth", """ get_poll_ring_depth(self) -> int Returns the poll ring depth Parameters : None Return : (int) the poll ring depth New in PyTango 7.1.2 """) document_method("get_poll_old_factor", """ get_poll_old_factor(self) -> int Returns the poll old factor Parameters : None Return : (int) the poll old factor New in PyTango 7.1.2 """) document_method("is_polled", """ is_polled(self) -> bool Returns if it is polled Parameters : None Return : (bool) True if it is polled or False otherwise New in PyTango 7.1.2 """) document_method("get_polled_cmd", """ get_polled_cmd(self) -> sequence Returns a COPY of the list of polled commands Parameters : None Return : (sequence) a COPY of the list of polled commands New in PyTango 7.1.2 """) document_method("get_polled_attr", """ get_polled_attr(self) -> sequence Returns a COPY of the list of polled attributes Parameters : None Return : (sequence) a COPY of the list of polled attributes New in PyTango 7.1.2 """) document_method("get_non_auto_polled_cmd", """ get_non_auto_polled_cmd(self) -> sequence Returns a COPY of the list of non automatic polled commands Parameters : None Return : (sequence) a COPY of the list of non automatic polled commands New in PyTango 7.1.2 """) document_method("get_non_auto_polled_attr", """ get_non_auto_polled_attr(self) -> sequence Returns a COPY of the list of non automatic polled attributes Parameters : None Return : (sequence) a COPY of the list of non automatic polled attributes New in PyTango 7.1.2 """) document_method("stop_polling", """ stop_polling(self) -> None stop_polling(self, with_db_upd) -> None Stop all polling for a device. if the device is polled, call this method before deleting it. Parameters : - with_db_upd : (bool) Is it necessary to update db ? Return : None New in PyTango 7.1.2 """) document_method("get_attribute_poll_period", """ get_attribute_poll_period(self, attr_name) -> int Returns the attribute polling period (ms) or 0 if the attribute is not polled. Parameters : - attr_name : (str) attribute name Return : (int) attribute polling period (ms) or 0 if it is not polled New in PyTango 8.0.0 """) document_method("get_command_poll_period", """ get_command_poll_period(self, cmd_name) -> int Returns the command polling period (ms) or 0 if the command is not polled. Parameters : - cmd_name : (str) command name Return : (int) command polling period (ms) or 0 if it is not polled New in PyTango 8.0.0 """) document_method("check_command_exists", """ check_command_exists(self) -> None This method check that a command is supported by the device and does not need input value. The method throws an exception if the command is not defined or needs an input value Parameters : - cmd_name: (str) the command name Return : None Throws : DevFailed API_IncompatibleCmdArgumentType, API_CommandNotFound New in PyTango 7.1.2 """) document_method("get_dev_idl_version", """ get_dev_idl_version(self) -> int Returns the IDL version Parameters : None Return : (int) the IDL version New in PyTango 7.1.2 """) document_method("get_cmd_poll_ring_depth", """ get_cmd_poll_ring_depth(self, cmd_name) -> int Returns the command poll ring depth Parameters : - cmd_name: (str) the command name Return : (int) the command poll ring depth New in PyTango 7.1.2 """) document_method("get_attr_poll_ring_depth", """ get_attr_poll_ring_depth(self, attr_name) -> int Returns the attribute poll ring depth Parameters : - attr_name: (str) the attribute name Return : (int) the attribute poll ring depth New in PyTango 7.1.2 """) document_method("is_device_locked", """ is_device_locked(self) -> bool Returns if this device is locked by a client Parameters : None Return : (bool) True if it is locked or False otherwise New in PyTango 7.1.2 """) document_method("get_min_poll_period", """ get_min_poll_period(self) -> int Returns the min poll period Parameters : None Return : (int) the min poll period New in PyTango 7.2.0 """) document_method("get_cmd_min_poll_period", """ get_cmd_min_poll_period(self) -> seq Returns the min command poll period Parameters : None Return : (seq) the min command poll period New in PyTango 7.2.0 """) document_method("get_attr_min_poll_period", """ get_attr_min_poll_period(self) -> seq Returns the min attribute poll period Parameters : None Return : (seq) the min attribute poll period New in PyTango 7.2.0 """) document_method("push_att_conf_event", """ push_att_conf_event(self, attr) -> None Push an attribute configuration event. Parameters : (Attribute) the attribute for which the configuration event will be sent. Return : None New in PyTango 7.2.1 """) document_method("push_pipe_event", """ push_pipe_event(self, blob) -> None Push an pipe event. Parameters : the blob which pipe event will be send. Return : None New in PyTango 9.2.2 """) document_method("is_there_subscriber", """ is_there_subscriber(self, att_name, event_type) -> bool Check if there is subscriber(s) listening for the event. This method returns a boolean set to true if there are some subscriber(s) listening on the event specified by the two method arguments. Be aware that there is some delay (up to 600 sec) between this method returning false and the last subscriber unsubscription or crash... The device interface change event is not supported by this method. Parameters : - att_name: (str) the attribute name - event_type (EventType): the event type Return : True if there is at least one listener or False otherwise """) def __doc_extra_DeviceImpl(cls): def document_method(method_name, desc, append=True): return __document_method(cls, method_name, desc, append) document_method("delete_device", """ delete_device(self) -> None Delete the device. Parameters : None Return : None """) document_method("always_executed_hook", """ always_executed_hook(self) -> None Hook method. Default method to implement an action necessary on a device before any command is executed. This method can be redefined in sub-classes in case of the default behaviour does not fullfill the needs Parameters : None Return : None Throws : DevFailed This method does not throw exception but a redefined method can. """) document_method("read_attr_hardware", """ read_attr_hardware(self, attr_list) -> None Read the hardware to return attribute value(s). Default method to implement an action necessary on a device to read the hardware involved in a a read attribute CORBA call. This method must be redefined in sub-classes in order to support attribute reading Parameters : attr_list : (sequence) list of indices in the device object attribute vector of an attribute to be read. Return : None Throws : DevFailed This method does not throw exception but a redefined method can. """) document_method("write_attr_hardware", """ write_attr_hardware(self) -> None Write the hardware for attributes. Default method to implement an action necessary on a device to write the hardware involved in a a write attribute. This method must be redefined in sub-classes in order to support writable attribute Parameters : attr_list : (sequence) list of indices in the device object attribute vector of an attribute to be written. Return : None Throws : DevFailed This method does not throw exception but a redefined method can. """) document_method("signal_handler", """ signal_handler(self, signo) -> None Signal handler. The method executed when the signal arrived in the device server process. This method is defined as virtual and then, can be redefined following device needs. Parameters : - signo : (int) the signal number Return : None Throws : DevFailed This method does not throw exception but a redefined method can. """) copy_doc(cls, "dev_state") copy_doc(cls, "dev_status") def __doc_Attribute(): def document_method(method_name, desc, append=True): return __document_method(Attribute, method_name, desc, append) Attribute.__doc__ = """ This class represents a Tango attribute. """ document_method("is_write_associated", """ is_write_associated(self) -> bool Check if the attribute has an associated writable attribute. Parameters : None Return : (bool) True if there is an associated writable attribute """) document_method("is_min_alarm", """ is_min_alarm(self) -> bool Check if the attribute is in minimum alarm condition. Parameters : None Return : (bool) true if the attribute is in alarm condition (read value below the min. alarm). """) document_method("is_max_alarm", """ is_max_alarm(self) -> bool Check if the attribute is in maximum alarm condition. Parameters : None Return : (bool) true if the attribute is in alarm condition (read value above the max. alarm). """) document_method("is_min_warning", """ is_min_warning(self) -> bool Check if the attribute is in minimum warning condition. Parameters : None Return : (bool) true if the attribute is in warning condition (read value below the min. warning). """) document_method("is_max_warning", """ is_max_warning(self) -> bool Check if the attribute is in maximum warning condition. Parameters : None Return : (bool) true if the attribute is in warning condition (read value above the max. warning). """) document_method("is_rds_alarm", """ is_rds_alarm(self) -> bool Check if the attribute is in RDS alarm condition. Parameters : None Return : (bool) true if the attribute is in RDS condition (Read Different than Set). """) document_method("is_polled", """ is_polled(self) -> bool Check if the attribute is polled. Parameters : None Return : (bool) true if the attribute is polled. """) document_method("check_alarm", """ check_alarm(self) -> bool Check if the attribute read value is below/above the alarm level. Parameters : None Return : (bool) true if the attribute is in alarm condition. Throws : DevFailed If no alarm level is defined. """) document_method("get_writable", """ get_writable(self) -> AttrWriteType Get the attribute writable type (RO/WO/RW). Parameters : None Return : (AttrWriteType) The attribute write type. """) document_method("get_name", """ get_name(self) -> str Get attribute name. Parameters : None Return : (str) The attribute name """) document_method("get_data_type", """ get_data_type(self) -> int Get attribute data type. Parameters : None Return : (int) the attribute data type """) document_method("get_data_format", """ get_data_format(self) -> AttrDataFormat Get attribute data format. Parameters : None Return : (AttrDataFormat) the attribute data format """) document_method("get_assoc_name", """ get_assoc_name(self) -> str Get name of the associated writable attribute. Parameters : None Return : (str) the associated writable attribute name """) document_method("get_assoc_ind", """ get_assoc_ind(self) -> int Get index of the associated writable attribute. Parameters : None Return : (int) the index in the main attribute vector of the associated writable attribute """) document_method("set_assoc_ind", """ set_assoc_ind(self, index) -> None Set index of the associated writable attribute. Parameters : - index : (int) The new index in the main attribute vector of the associated writable attribute Return : None """) document_method("get_date", """ get_date(self) -> TimeVal Get a COPY of the attribute date. Parameters : None Return : (TimeVal) the attribute date """) document_method("set_date", """ set_date(self, new_date) -> None Set attribute date. Parameters : - new_date : (TimeVal) the attribute date Return : None """) document_method("get_label", """ get_label(self, ) -> str Get attribute label property. Parameters : None Return : (str) he attribute label """) document_method("get_quality", """ get_quality(self) -> AttrQuality Get a COPY of the attribute data quality. Parameters : None Return : (AttrQuality) the attribute data quality """) document_method("set_quality", """ set_quality(self, quality, send_event=False) -> None Set attribute data quality. Parameters : - quality : (AttrQuality) the new attribute data quality - send_event : (bool) true if a change event should be sent. Default is false. Return : None """) document_method("get_data_size", """ get_data_size(self) -> None Get attribute data size. Parameters : None Return : (int) the attribute data size """) document_method("get_x", """ get_x(self) -> int Get attribute data size in x dimension. Parameters : None Return : (int) the attribute data size in x dimension. Set to 1 for scalar attribute """) document_method("get_max_dim_x", """ get_max_dim_x(self) -> int Get attribute maximum data size in x dimension. Parameters : None Return : (int) the attribute maximum data size in x dimension. Set to 1 for scalar attribute """) document_method("get_y", """ get_y(self) -> int Get attribute data size in y dimension. Parameters : None Return : (int) the attribute data size in y dimension. Set to 1 for scalar attribute """) document_method("get_max_dim_y", """ get_max_dim_y(self) -> int Get attribute maximum data size in y dimension. Parameters : None Return : (int) the attribute maximum data size in y dimension. Set to 0 for scalar attribute """) document_method("get_polling_period", """ get_polling_period(self) -> int Get attribute polling period. Parameters : None Return : (int) The attribute polling period in mS. Set to 0 when the attribute is not polled """) document_method("set_attr_serial_model", """ set_attr_serial_model(self, ser_model) -> void Set attribute serialization model. This method allows the user to choose the attribute serialization model. Parameters : - ser_model : (AttrSerialModel) The new serialisation model. The serialization model must be one of ATTR_BY_KERNEL, ATTR_BY_USER or ATTR_NO_SYNC Return : None New in PyTango 7.1.0 """) document_method("get_attr_serial_model", """ get_attr_serial_model(self) -> AttrSerialModel Get attribute serialization model. Parameters : None Return : (AttrSerialModel) The attribute serialization model New in PyTango 7.1.0 """) document_method("set_value", """ set_value(self, data, dim_x = 1, dim_y = 0) -> None <= DEPRECATED set_value(self, data) -> None set_value(self, str_data, data) -> None Set internal attribute value. This method stores the attribute read value inside the object. This method also stores the date when it is called and initializes the attribute quality factor. Parameters : - data : the data to be set. Data must be compatible with the attribute type and format. In the DEPRECATED form for SPECTRUM and IMAGE attributes, data can be any type of FLAT sequence of elements compatible with the attribute type. In the new form (without dim_x or dim_y) data should be any sequence for SPECTRUM and a SEQUENCE of equal-length SEQUENCES for IMAGE attributes. The recommended sequence is a C continuous and aligned numpy array, as it can be optimized. - str_data : (str) special variation for DevEncoded data type. In this case 'data' must be a str or an object with the buffer interface. - dim_x : (int) [DEPRECATED] the attribute x length. Default value is 1 - dim_y : (int) [DEPRECATED] the attribute y length. Default value is 0 Return : None """) document_method("set_value_date_quality", """ set_value_date_quality(self, data, time_stamp, quality, dim_x = 1, dim_y = 0) -> None <= DEPRECATED set_value_date_quality(self, data, time_stamp, quality) -> None set_value_date_quality(self, str_data, data, time_stamp, quality) -> None Set internal attribute value, date and quality factor. This method stores the attribute read value, the date and the attribute quality factor inside the object. Parameters : - data : the data to be set. Data must be compatible with the attribute type and format. In the DEPRECATED form for SPECTRUM and IMAGE attributes, data can be any type of FLAT sequence of elements compatible with the attribute type. In the new form (without dim_x or dim_y) data should be any sequence for SPECTRUM and a SEQUENCE of equal-length SEQUENCES for IMAGE attributes. The recommended sequence is a C continuous and aligned numpy array, as it can be optimized. - str_data : (str) special variation for DevEncoded data type. In this case 'data' must be a str or an object with the buffer interface. - dim_x : (int) [DEPRECATED] the attribute x length. Default value is 1 - dim_y : (int) [DEPRECATED] the attribute y length. Default value is 0 - time_stamp : (double) the time stamp - quality : (AttrQuality) the attribute quality factor Return : None """) document_method("set_change_event", """ set_change_event(self, implemented, detect = True) -> None Set a flag to indicate that the server fires change events manually, without the polling to be started for the attribute. If the detect parameter is set to true, the criteria specified for the change event are verified and the event is only pushed if they are fullfilled. If detect is set to false the event is fired without any value checking! Parameters : - implemented : (bool) True when the server fires change events manually. - detect : (bool) (optional, default is True) Triggers the verification of the change event properties when set to true. Return : None New in PyTango 7.1.0 """) document_method("set_archive_event", """ set_archive_event(self, implemented, detect = True) -> None Set a flag to indicate that the server fires archive events manually, without the polling to be started for the attribute If the detect parameter is set to true, the criteria specified for the archive event are verified and the event is only pushed if they are fullfilled. Parameters : - implemented : (bool) True when the server fires archive events manually. - detect : (bool) (optional, default is True) Triggers the verification of the archive event properties when set to true. Return : None New in PyTango 7.1.0 """) document_method("is_change_event", """ is_change_event(self) -> bool Check if the change event is fired manually (without polling) for this attribute. Parameters : None Return : (bool) True if a manual fire change event is implemented. New in PyTango 7.1.0 """) document_method("is_check_change_criteria", """ is_check_change_criteria(self) -> bool Check if the change event criteria should be checked when firing the event manually. Parameters : None Return : (bool) True if a change event criteria will be checked. New in PyTango 7.1.0 """) document_method("is_archive_event", """ is_archive_event(self) -> bool Check if the archive event is fired manually (without polling) for this attribute. Parameters : None Return : (bool) True if a manual fire archive event is implemented. New in PyTango 7.1.0 """) document_method("is_check_archive_criteria", """ is_check_archive_criteria(self) -> bool Check if the archive event criteria should be checked when firing the event manually. Parameters : None Return : (bool) True if a archive event criteria will be checked. New in PyTango 7.1.0 """) document_method("set_data_ready_event", """ set_data_ready_event(self, implemented) -> None Set a flag to indicate that the server fires data ready events. Parameters : - implemented : (bool) True when the server fires data ready events manually. Return : None New in PyTango 7.2.0 """) document_method("is_data_ready_event", """ is_data_ready_event(self) -> bool Check if the data ready event is fired manually (without polling) for this attribute. Parameters : None Return : (bool) True if a manual fire data ready event is implemented. New in PyTango 7.2.0 """) document_method("remove_configuration", """ remove_configuration(self) -> None Remove the attribute configuration from the database. This method can be used to clean-up all the configuration of an attribute to come back to its default values or the remove all configuration of a dynamic attribute before deleting it. The method removes all configured attribute properties and removes the attribute from the list of polled attributes. Parameters : None Return : None New in PyTango 7.1.0 """) def __doc_WAttribute(): def document_method(method_name, desc, append=True): return __document_method(WAttribute, method_name, desc, append) WAttribute.__doc__ = """ This class represents a Tango writable attribute. """ document_method("get_min_value", """ get_min_value(self) -> obj Get attribute minimum value or throws an exception if the attribute does not have a minimum value. Parameters : None Return : (obj) an object with the python minimum value """) document_method("get_max_value", """ get_max_value(self) -> obj Get attribute maximum value or throws an exception if the attribute does not have a maximum value. Parameters : None Return : (obj) an object with the python maximum value """) document_method("set_min_value", """ set_min_value(self, data) -> None Set attribute minimum value. Parameters : - data : the attribute minimum value. python data type must be compatible with the attribute data format and type. Return : None """) document_method("set_max_value", """ set_max_value(self, data) -> None Set attribute maximum value. Parameters : - data : the attribute maximum value. python data type must be compatible with the attribute data format and type. Return : None """) document_method("is_min_value", """ is_min_value(self) -> bool Check if the attribute has a minimum value. Parameters : None Return : (bool) true if the attribute has a minimum value defined """) document_method("is_max_value", """ is_max_value(self, ) -> bool Check if the attribute has a maximum value. Parameters : None Return : (bool) true if the attribute has a maximum value defined """) document_method("get_write_value_length", """ get_write_value_length(self) -> int Retrieve the new value length (data number) for writable attribute. Parameters : None Return : (int) the new value data length """) # document_method("set_write_value", """ # set_write_value(self, data, dim_x = 1, dim_y = 0) -> None # # Set the writable attribute value. # # Parameters : # - data : the data to be set. Data must be compatible with the attribute type and format. # for SPECTRUM and IMAGE attributes, data can be any type of sequence of elements # compatible with the attribute type # - dim_x : (int) the attribute set value x length. Default value is 1 # - dim_y : (int) the attribute set value y length. Default value is 0 # Return : None # """) document_method("get_write_value", """ get_write_value(self, lst) -> None <= DEPRECATED get_write_value(self, extract_as=ExtractAs.Numpy) -> obj Retrieve the new value for writable attribute. Parameters : - extract_as: (ExtractAs) - lst : [out] (list) a list object that will be filled with the attribute write value (DEPRECATED) Return : (obj) the attribute write value. """) def __doc_MultiClassAttribute(): def document_method(method_name, desc, append=True): return __document_method(MultiClassAttribute, method_name, desc, append) MultiClassAttribute.__doc__ = """ There is one instance of this class for each device class. This class is mainly an aggregate of :class:`~tango.Attr` objects. It eases management of multiple attributes New in PyTango 7.2.1""" document_method("get_attr", """ get_attr(self, attr_name) -> Attr Get the :class:`~tango.Attr` object for the attribute with name passed as parameter Parameters : - attr_name : (str) attribute name Return : (Attr) the attribute object Throws : DevFailed If the attribute is not defined. New in PyTango 7.2.1 """) document_method("remove_attr", """ remove_attr(self, attr_name, cl_name) -> None Remove the :class:`~tango.Attr` object for the attribute with name passed as parameter. Does nothing if the attribute does not exist. Parameters : - attr_name : (str) attribute name - cl_name : (str) the attribute class name New in PyTango 7.2.1 """) document_method("get_attr_list", """ get_attr_list(self) -> seq Get the list of :class:`~tango.Attr` for this device class. Return : (seq) the list of attribute objects New in PyTango 7.2.1 """) def __doc_MultiAttribute(): def document_method(method_name, desc, append=True): return __document_method(MultiAttribute, method_name, desc, append) MultiAttribute.__doc__ = """ There is one instance of this class for each device. This class is mainly an aggregate of :class:`~tango.Attribute` or :class:`~tango.WAttribute` objects. It eases management of multiple attributes""" document_method("get_attr_by_name", """ get_attr_by_name(self, attr_name) -> Attribute Get :class:`~tango.Attribute` object from its name. This method returns an :class:`~tango.Attribute` object with a name passed as parameter. The equality on attribute name is case independant. Parameters : - attr_name : (str) attribute name Return : (Attribute) the attribute object Throws : DevFailed If the attribute is not defined. """) document_method("get_attr_by_ind", """ get_attr_by_ind(self, ind) -> Attribute Get :class:`~tango.Attribute` object from its index. This method returns an :class:`~tango.Attribute` object from the index in the main attribute vector. Parameters : - ind : (int) the attribute index Return : (Attribute) the attribute object """) document_method("get_w_attr_by_name", """ get_w_attr_by_name(self, attr_name) -> WAttribute Get a writable attribute object from its name. This method returns an :class:`~tango.WAttribute` object with a name passed as parameter. The equality on attribute name is case independant. Parameters : - attr_name : (str) attribute name Return : (WAttribute) the attribute object Throws : DevFailed If the attribute is not defined. """) document_method("get_w_attr_by_ind", """ get_w_attr_by_ind(self, ind) -> WAttribute Get a writable attribute object from its index. This method returns an :class:`~tango.WAttribute` object from the index in the main attribute vector. Parameters : - ind : (int) the attribute index Return : (WAttribute) the attribute object """) document_method("get_attr_ind_by_name", """ get_attr_ind_by_name(self, attr_name) -> int Get Attribute index into the main attribute vector from its name. This method returns the index in the Attribute vector (stored in the :class:`~tango.MultiAttribute` object) of an attribute with a given name. The name equality is case independant. Parameters : - attr_name : (str) attribute name Return : (int) the attribute index Throws : DevFailed If the attribute is not found in the vector. New in PyTango 7.0.0 """) document_method("get_attr_nb", """ get_attr_nb(self) -> int Get attribute number. Parameters : None Return : (int) the number of attributes New in PyTango 7.0.0 """) document_method("check_alarm", """ check_alarm(self) -> bool check_alarm(self, attr_name) -> bool check_alarm(self, ind) -> bool - The 1st version of the method checks alarm on all attribute(s) with an alarm defined. - The 2nd version of the method checks alarm for one attribute with a given name. - The 3rd version of the method checks alarm for one attribute from its index in the main attributes vector. Parameters : - attr_name : (str) attribute name - ind : (int) the attribute index Return : (bool) True if at least one attribute is in alarm condition Throws : DevFailed If at least one attribute does not have any alarm level defined New in PyTango 7.0.0 """) document_method("read_alarm", """ read_alarm(self, status) -> None Add alarm message to device status. This method add alarm mesage to the string passed as parameter. A message is added for each attribute which is in alarm condition Parameters : - status : (str) a string (should be the device status) Return : None New in PyTango 7.0.0 """) document_method("get_attribute_list", """ get_attribute_list(self) -> seq Get the list of attribute objects. Return : (seq) list of attribute objects New in PyTango 7.2.1 """) def __doc_Attr(): def document_method(method_name, desc, append=True): return __document_method(Attr, method_name, desc, append) Attr.__doc__ = """ This class represents a Tango writable attribute. """ document_method("set_default_properties", """ set_default_properties(self) -> None Set default attribute properties. Parameters : - attr_prop : (UserDefaultAttrProp) the user default property class Return : None """) document_method("set_disp_level", """ set_disp_level(self, disp_lelel) -> None Set the attribute display level. Parameters : - disp_level : (DispLevel) the new display level Return : None """) document_method("set_polling_period", """ set_polling_period(self, period) -> None Set the attribute polling update period. Parameters : - period : (int) the attribute polling period (in mS) Return : None """) document_method("set_memorized", """ set_memorized(self) -> None Set the attribute as memorized in database (only for scalar and writable attribute) With no argument the setpoint will be written to the attribute during initialisation! Parameters : None Return : None """) document_method("set_memorized_init", """ set_memorized_init(self, write_on_init) -> None Set the initialisation flag for memorized attributes true = the setpoint value will be written to the attribute on initialisation false = only the attribute setpoint is initialised. No action is taken on the attribute Parameters : - write_on_init : (bool) if true the setpoint value will be written to the attribute on initialisation Return : None """) document_method("set_change_event", """ set_change_event(self, implemented, detect) -> None Set a flag to indicate that the server fires change events manually without the polling to be started for the attribute. If the detect parameter is set to true, the criteria specified for the change event are verified and the event is only pushed if they are fullfilled. If detect is set to false the event is fired without checking! Parameters : - implemented : (bool) True when the server fires change events manually. - detect : (bool) Triggers the verification of the change event properties when set to true. Return : None """) document_method("is_change_event", """ is_change_event(self) -> bool Check if the change event is fired manually for this attribute. Parameters : None Return : (bool) true if a manual fire change event is implemented. """) document_method("is_check_change_criteria", """ is_check_change_criteria(self) -> bool Check if the change event criteria should be checked when firing the event manually. Parameters : None Return : (bool) true if a change event criteria will be checked. """) document_method("set_archive_event", """ set_archive_event(self) -> None Set a flag to indicate that the server fires archive events manually without the polling to be started for the attribute If the detect parameter is set to true, the criteria specified for the archive event are verified and the event is only pushed if they are fullfilled. If detect is set to false the event is fired without checking! Parameters : - implemented : (bool) True when the server fires change events manually. - detect : (bool) Triggers the verification of the archive event properties when set to true. Return : None """) document_method("is_archive_event", """ is_archive_event(self) -> bool Check if the archive event is fired manually for this attribute. Parameters : None Return : (bool) true if a manual fire archive event is implemented. """) document_method("is_check_archive_criteria", """ is_check_archive_criteria(self) -> bool Check if the archive event criteria should be checked when firing the event manually. Parameters : None Return : (bool) true if a archive event criteria will be checked. """) document_method("set_data_ready_event", """ set_data_ready_event(self, implemented) -> None Set a flag to indicate that the server fires data ready events. Parameters : - implemented : (bool) True when the server fires data ready events Return : None New in PyTango 7.2.0 """) document_method("is_data_ready_event", """ is_data_ready_event(self) -> bool Check if the data ready event is fired for this attribute. Parameters : None Return : (bool) true if firing data ready event is implemented. New in PyTango 7.2.0 """) document_method("get_name", """ get_name(self) -> str Get the attribute name. Parameters : None Return : (str) the attribute name """) document_method("get_format", """ get_format(self) -> AttrDataFormat Get the attribute format Parameters : None Return : (AttrDataFormat) the attribute format """) document_method("get_writable", """ get_writable(self) -> AttrWriteType Get the attribute write type Parameters : None Return : (AttrWriteType) the attribute write type """) document_method("get_type", """ get_type(self) -> int Get the attribute data type Parameters : None Return : (int) the attribute data type """) document_method("get_disp_level", """ get_disp_level(self) -> DispLevel Get the attribute display level Parameters : None Return : (DispLevel) the attribute display level """) document_method("get_polling_period", """ get_polling_period(self) -> int Get the polling period (mS) Parameters : None Return : (int) the polling period (mS) """) document_method("get_memorized", """ get_memorized(self) -> bool Determine if the attribute is memorized or not. Parameters : None Return : (bool) True if the attribute is memorized """) document_method("get_memorized_init", """ get_memorized_init(self) -> bool Determine if the attribute is written at startup from the memorized value if it is memorized Parameters : None Return : (bool) True if initialized with memorized value or not """) document_method("get_assoc", """ get_assoc(self) -> str Get the associated name. Parameters : None Return : (bool) the associated name """) document_method("is_assoc", """ is_assoc(self) -> bool Determine if it is assoc. Parameters : None Return : (bool) if it is assoc """) document_method("get_cl_name", """ get_cl_name(self) -> str Returns the class name Parameters : None Return : (str) the class name New in PyTango 7.2.0 """) document_method("set_cl_name", """ set_cl_name(self, cl) -> None Sets the class name Parameters : - cl : (str) new class name Return : None New in PyTango 7.2.0 """) document_method("get_class_properties", """ get_class_properties(self) -> sequence Get the class level attribute properties Parameters : None Return : (sequence) the class attribute properties """) document_method("get_user_default_properties", """ get_user_default_properties(self) -> sequence Get the user default attribute properties Parameters : None Return : (sequence) the user default attribute properties """) document_method("set_class_properties", """ set_class_properties(self, props) -> None Set the class level attribute properties Parameters : - props : (StdAttrPropertyVector) new class level attribute properties Return : None """) def __doc_UserDefaultAttrProp(): def document_method(method_name, desc, append=True): return __document_method(UserDefaultAttrProp, method_name, desc, append) UserDefaultAttrProp.__doc__ = """ User class to set attribute default properties. This class is used to set attribute default properties. Three levels of attributes properties setting are implemented within Tango. The highest property setting level is the database. Then the user default (set using this UserDefaultAttrProp class) and finally a Tango library default value """ document_method("set_label", """ set_label(self, def_label) -> None Set default label property. Parameters : - def_label : (str) the user default label property Return : None """) document_method("set_description", """ set_description(self, def_description) -> None Set default description property. Parameters : - def_description : (str) the user default description property Return : None """) document_method("set_format", """ set_format(self, def_format) -> None Set default format property. Parameters : - def_format : (str) the user default format property Return : None """) document_method("set_unit", """ set_unit(self, def_unit) -> None Set default unit property. Parameters : - def_unit : (str) te user default unit property Return : None """) document_method("set_standard_unit", """ set_standard_unit(self, def_standard_unit) -> None Set default standard unit property. Parameters : - def_standard_unit : (str) the user default standard unit property Return : None """) document_method("set_display_unit", """ set_display_unit(self, def_display_unit) -> None Set default display unit property. Parameters : - def_display_unit : (str) the user default display unit property Return : None """) document_method("set_min_value", """ set_min_value(self, def_min_value) -> None Set default min_value property. Parameters : - def_min_value : (str) the user default min_value property Return : None """) document_method("set_max_value", """ set_max_value(self, def_max_value) -> None Set default max_value property. Parameters : - def_max_value : (str) the user default max_value property Return : None """) document_method("set_min_alarm", """ set_min_alarm(self, def_min_alarm) -> None Set default min_alarm property. Parameters : - def_min_alarm : (str) the user default min_alarm property Return : None """) document_method("set_max_alarm", """ set_max_alarm(self, def_max_alarm) -> None Set default max_alarm property. Parameters : - def_max_alarm : (str) the user default max_alarm property Return : None """) document_method("set_min_warning", """ set_min_warning(self, def_min_warning) -> None Set default min_warning property. Parameters : - def_min_warning : (str) the user default min_warning property Return : None """) document_method("set_max_warning", """ set_max_warning(self, def_max_warning) -> None Set default max_warning property. Parameters : - def_max_warning : (str) the user default max_warning property Return : None """) document_method("set_delta_t", """ set_delta_t(self, def_delta_t) -> None Set default RDS alarm delta_t property. Parameters : - def_delta_t : (str) the user default RDS alarm delta_t property Return : None """) document_method("set_delta_val", """ set_delta_val(self, def_delta_val) -> None Set default RDS alarm delta_val property. Parameters : - def_delta_val : (str) the user default RDS alarm delta_val property Return : None """) document_method("set_abs_change", """ set_abs_change(self, def_abs_change) -> None <= DEPRECATED Set default change event abs_change property. Parameters : - def_abs_change : (str) the user default change event abs_change property Return : None Deprecated since PyTango 8.0. Please use set_event_abs_change instead. """) document_method("set_event_abs_change", """ set_event_abs_change(self, def_abs_change) -> None Set default change event abs_change property. Parameters : - def_abs_change : (str) the user default change event abs_change property Return : None New in PyTango 8.0 """) document_method("set_rel_change", """ set_rel_change(self, def_rel_change) -> None <= DEPRECATED Set default change event rel_change property. Parameters : - def_rel_change : (str) the user default change event rel_change property Return : None Deprecated since PyTango 8.0. Please use set_event_rel_change instead. """) document_method("set_event_rel_change", """ set_event_rel_change(self, def_rel_change) -> None Set default change event rel_change property. Parameters : - def_rel_change : (str) the user default change event rel_change property Return : None New in PyTango 8.0 """) document_method("set_period", """ set_period(self, def_period) -> None <= DEPRECATED Set default periodic event period property. Parameters : - def_period : (str) the user default periodic event period property Return : None Deprecated since PyTango 8.0. Please use set_event_period instead. """) document_method("set_event_period", """ set_event_period(self, def_period) -> None Set default periodic event period property. Parameters : - def_period : (str) the user default periodic event period property Return : None New in PyTango 8.0 """) document_method("set_archive_abs_change", """ set_archive_abs_change(self, def_archive_abs_change) -> None <= DEPRECATED Set default archive event abs_change property. Parameters : - def_archive_abs_change : (str) the user default archive event abs_change property Return : None Deprecated since PyTango 8.0. Please use set_archive_event_abs_change instead. """) document_method("set_archive_event_abs_change", """ set_archive_event_abs_change(self, def_archive_abs_change) -> None Set default archive event abs_change property. Parameters : - def_archive_abs_change : (str) the user default archive event abs_change property Return : None New in PyTango 8.0 """) document_method("set_archive_rel_change", """ set_archive_rel_change(self, def_archive_rel_change) -> None <= DEPRECATED Set default archive event rel_change property. Parameters : - def_archive_rel_change : (str) the user default archive event rel_change property Return : None Deprecated since PyTango 8.0. Please use set_archive_event_rel_change instead. """) document_method("set_archive_event_rel_change", """ set_archive_event_rel_change(self, def_archive_rel_change) -> None Set default archive event rel_change property. Parameters : - def_archive_rel_change : (str) the user default archive event rel_change property Return : None New in PyTango 8.0 """) document_method("set_archive_period", """ set_archive_period(self, def_archive_period) -> None <= DEPRECATED Set default archive event period property. Parameters : - def_archive_period : (str) t Return : None Deprecated since PyTango 8.0. Please use set_archive_event_period instead. """) document_method("set_archive_event_period", """ set_archive_event_period(self, def_archive_period) -> None Set default archive event period property. Parameters : - def_archive_period : (str) t Return : None New in PyTango 8.0 """) def device_server_init(doc=True): __init_DeviceImpl() __init_Attribute() __init_Attr() __init_UserDefaultAttrProp() __init_Logger() if doc: __doc_DeviceImpl() __doc_extra_DeviceImpl(Device_3Impl) __doc_extra_DeviceImpl(Device_4Impl) __doc_extra_DeviceImpl(Device_5Impl) __doc_Attribute() __doc_WAttribute() __doc_MultiAttribute() __doc_MultiClassAttribute() __doc_UserDefaultAttrProp() __doc_Attr() pytango-9.2.2/tango/encoded_attribute.py000066400000000000000000000611201316324373100203560ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ """ This is an internal PyTango module. """ __all__ = ["encoded_attribute_init"] __docformat__ = "restructuredtext" from ._tango import EncodedAttribute, ExtractAs, _ImageFormat from ._tango import constants from .utils import is_pure_str, is_seq if constants.NUMPY_SUPPORT: try: import numpy np = numpy except: np = None else: np = None _allowed_extract = ( ExtractAs.Numpy, ExtractAs.String, ExtractAs.Tuple, ExtractAs.List) def __EncodedAttribute_encode_jpeg_gray8(self, gray8, width=0, height=0, quality=100.0): """Encode a 8 bit grayscale image as JPEG format :param gray8: an object containning image information :type gray8: :py:obj:`str` or :class:`numpy.ndarray` or seq< seq > :param width: image width. **MUST** be given if gray8 is a string or if it is a :class:`numpy.ndarray` with ndims != 2. Otherwise it is calculated internally. :type width: :py:obj:`int` :param height: image height. **MUST** be given if gray8 is a string or if it is a :class:`numpy.ndarray` with ndims != 2. Otherwise it is calculated internally. :type height: :py:obj:`int` :param quality: Quality of JPEG (0=poor quality 100=max quality) (default is 100.0) :type quality: :py:obj:`float` .. note:: When :class:`numpy.ndarray` is given: - gray8 **MUST** be CONTIGUOUS, ALIGNED - if gray8.ndims != 2, width and height **MUST** be given and gray8.nbytes **MUST** match width*height - if gray8.ndims == 2, gray8.itemsize **MUST** be 1 (typically, gray8.dtype is one of `numpy.dtype.byte`, `numpy.dtype.ubyte`, `numpy.dtype.int8` or `numpy.dtype.uint8`) Example:: def read_myattr(self, attr): enc = tango.EncodedAttribute() data = numpy.arange(100, dtype=numpy.byte) data = numpy.array((data,data,data)) enc.encode_jpeg_gray8(data) attr.set_value(enc) """ self._generic_encode_gray8(gray8, width=width, height=height, quality=quality, format=_ImageFormat.JpegImage) def __EncodedAttribute_encode_gray8(self, gray8, width=0, height=0): """Encode a 8 bit grayscale image (no compression) :param gray8: an object containning image information :type gray8: :py:obj:`str` or :class:`numpy.ndarray` or seq< seq > :param width: image width. **MUST** be given if gray8 is a string or if it is a :class:`numpy.ndarray` with ndims != 2. Otherwise it is calculated internally. :type width: :py:obj:`int` :param height: image height. **MUST** be given if gray8 is a string or if it is a :class:`numpy.ndarray` with ndims != 2. Otherwise it is calculated internally. :type height: :py:obj:`int` .. note:: When :class:`numpy.ndarray` is given: - gray8 **MUST** be CONTIGUOUS, ALIGNED - if gray8.ndims != 2, width and height **MUST** be given and gray8.nbytes **MUST** match width*height - if gray8.ndims == 2, gray8.itemsize **MUST** be 1 (typically, gray8.dtype is one of `numpy.dtype.byte`, `numpy.dtype.ubyte`, `numpy.dtype.int8` or `numpy.dtype.uint8`) Example:: def read_myattr(self, attr): enc = tango.EncodedAttribute() data = numpy.arange(100, dtype=numpy.byte) data = numpy.array((data,data,data)) enc.encode_gray8(data) attr.set_value(enc) """ self._generic_encode_gray8(gray8, width=width, height=height, format=_ImageFormat.RawImage) def __EncodedAttribute_generic_encode_gray8(self, gray8, width=0, height=0, quality=0, format=_ImageFormat.RawImage): """Internal usage only""" if not is_seq(gray8): raise TypeError("Expected sequence (str, numpy.ndarray, list, tuple " "or bytearray) as first argument") is_str = is_pure_str(gray8) if is_str: if not width or not height: raise ValueError("When giving a string as data, you must also " "supply width and height") if np and isinstance(gray8, np.ndarray): if gray8.ndim != 2: if not width or not height: raise ValueError("When giving a non 2D numpy array, width and " "height must be supplied") if gray8.nbytes != width * height: raise ValueError("numpy array size mismatch") else: if gray8.itemsize != 1: raise TypeError("Expected numpy array with itemsize == 1") if not gray8.flags.c_contiguous: raise TypeError("Currently, only contiguous, aligned numpy arrays " "are supported") if not gray8.flags.aligned: raise TypeError("Currently, only contiguous, aligned numpy arrays " "are supported") if not is_str and (not width or not height): height = len(gray8) if height < 1: raise IndexError("Expected sequence with at least one row") row0 = gray8[0] if not is_seq(row0): raise IndexError("Expected sequence (str, numpy.ndarray, list, tuple or " "bytearray) inside a sequence") width = len(row0) if format == _ImageFormat.RawImage: self._encode_gray8(gray8, width, height) elif format == _ImageFormat.JpegImage: self._encode_jpeg_gray8(gray8, width, height, quality) def __EncodedAttribute_encode_gray16(self, gray16, width=0, height=0): """Encode a 16 bit grayscale image (no compression) :param gray16: an object containning image information :type gray16: :py:obj:`str` or :py:obj:`buffer` or :class:`numpy.ndarray` or seq< seq > :param width: image width. **MUST** be given if gray16 is a string or if it is a :class:`numpy.ndarray` with ndims != 2. Otherwise it is calculated internally. :type width: :py:obj:`int` :param height: image height. **MUST** be given if gray16 is a string or if it is a :class:`numpy.ndarray` with ndims != 2. Otherwise it is calculated internally. :type height: :py:obj:`int` .. note:: When :class:`numpy.ndarray` is given: - gray16 **MUST** be CONTIGUOUS, ALIGNED - if gray16.ndims != 2, width and height **MUST** be given and gray16.nbytes/2 **MUST** match width*height - if gray16.ndims == 2, gray16.itemsize **MUST** be 2 (typically, gray16.dtype is one of `numpy.dtype.int16`, `numpy.dtype.uint16`, `numpy.dtype.short` or `numpy.dtype.ushort`) Example:: def read_myattr(self, attr): enc = tango.EncodedAttribute() data = numpy.arange(100, dtype=numpy.int16) data = numpy.array((data,data,data)) enc.encode_gray16(data) attr.set_value(enc) """ if not is_seq(gray16): raise TypeError("Expected sequence (str, numpy.ndarray, list, tuple " "or bytearray) as first argument") is_str = is_pure_str(gray16) if is_str: if not width or not height: raise ValueError("When giving a string as data, you must also " "supply width and height") if np and isinstance(gray16, np.ndarray): if gray16.ndim != 2: if not width or not height: raise ValueError("When giving a non 2D numpy array, width and " "height must be supplied") if gray16.nbytes / 2 != width * height: raise ValueError("numpy array size mismatch") else: if gray16.itemsize != 2: raise TypeError("Expected numpy array with itemsize == 2") if not gray16.flags.c_contiguous: raise TypeError("Currently, only contiguous, aligned numpy arrays " "are supported") if not gray16.flags.aligned: raise TypeError("Currently, only contiguous, aligned numpy arrays " "are supported") if not is_str and (not width or not height): height = len(gray16) if height < 1: raise IndexError("Expected sequence with at least one row") row0 = gray16[0] if not is_seq(row0): raise IndexError("Expected sequence (str, numpy.ndarray, list, tuple or " "bytearray) inside a sequence") width = len(row0) if is_pure_str(row0) or type(row0) == bytearray: width /= 2 self._encode_gray16(gray16, width, height) def __EncodedAttribute_encode_jpeg_rgb24(self, rgb24, width=0, height=0, quality=100.0): """Encode a 24 bit rgb color image as JPEG format. :param rgb24: an object containning image information :type rgb24: :py:obj:`str` or :class:`numpy.ndarray` or seq< seq > :param width: image width. **MUST** be given if rgb24 is a string or if it is a :class:`numpy.ndarray` with ndims != 3. Otherwise it is calculated internally. :type width: :py:obj:`int` :param height: image height. **MUST** be given if rgb24 is a string or if it is a :class:`numpy.ndarray` with ndims != 3. Otherwise it is calculated internally. :type height: :py:obj:`int` :param quality: Quality of JPEG (0=poor quality 100=max quality) (default is 100.0) :type quality: :py:obj:`float` .. note:: When :class:`numpy.ndarray` is given: - rgb24 **MUST** be CONTIGUOUS, ALIGNED - if rgb24.ndims != 3, width and height **MUST** be given and rgb24.nbytes/3 **MUST** match width*height - if rgb24.ndims == 3, rgb24.itemsize **MUST** be 1 (typically, rgb24.dtype is one of `numpy.dtype.byte`, `numpy.dtype.ubyte`, `numpy.dtype.int8` or `numpy.dtype.uint8`) and shape **MUST** be (height, width, 3) Example:: def read_myattr(self, attr): enc = tango.EncodedAttribute() # create an 'image' where each pixel is R=0x01, G=0x01, B=0x01 arr = numpy.ones((10,10,3), dtype=numpy.uint8) enc.encode_jpeg_rgb24(data) attr.set_value(enc) """ self._generic_encode_rgb24(rgb24, width=width, height=height, quality=quality, format=_ImageFormat.JpegImage) def __EncodedAttribute_encode_rgb24(self, rgb24, width=0, height=0): """Encode a 24 bit color image (no compression) :param rgb24: an object containning image information :type rgb24: :py:obj:`str` or :class:`numpy.ndarray` or seq< seq > :param width: image width. **MUST** be given if rgb24 is a string or if it is a :class:`numpy.ndarray` with ndims != 3. Otherwise it is calculated internally. :type width: :py:obj:`int` :param height: image height. **MUST** be given if rgb24 is a string or if it is a :class:`numpy.ndarray` with ndims != 3. Otherwise it is calculated internally. :type height: :py:obj:`int` .. note:: When :class:`numpy.ndarray` is given: - rgb24 **MUST** be CONTIGUOUS, ALIGNED - if rgb24.ndims != 3, width and height **MUST** be given and rgb24.nbytes/3 **MUST** match width*height - if rgb24.ndims == 3, rgb24.itemsize **MUST** be 1 (typically, rgb24.dtype is one of `numpy.dtype.byte`, `numpy.dtype.ubyte`, `numpy.dtype.int8` or `numpy.dtype.uint8`) and shape **MUST** be (height, width, 3) Example:: def read_myattr(self, attr): enc = tango.EncodedAttribute() # create an 'image' where each pixel is R=0x01, G=0x01, B=0x01 arr = numpy.ones((10,10,3), dtype=numpy.uint8) enc.encode_rgb24(data) attr.set_value(enc) """ self._generic_encode_rgb24(rgb24, width=width, height=height, format=_ImageFormat.RawImage) def __EncodedAttribute_generic_encode_rgb24(self, rgb24, width=0, height=0, quality=0, format=_ImageFormat.RawImage): """Internal usage only""" if not is_seq(rgb24): raise TypeError("Expected sequence (str, numpy.ndarray, list, tuple " "or bytearray) as first argument") is_str = is_pure_str(rgb24) if is_str: if not width or not height: raise ValueError("When giving a string as data, you must also " "supply width and height") if np and isinstance(rgb24, np.ndarray): if rgb24.ndim != 3: if not width or not height: raise ValueError("When giving a non 2D numpy array, width and " "height must be supplied") if rgb24.nbytes / 3 != width * height: raise ValueError("numpy array size mismatch") else: if rgb24.itemsize != 1: raise TypeError("Expected numpy array with itemsize == 1") if not rgb24.flags.c_contiguous: raise TypeError("Currently, only contiguous, aligned numpy arrays " "are supported") if not rgb24.flags.aligned: raise TypeError("Currently, only contiguous, aligned numpy arrays " "are supported") if not is_str and (not width or not height): height = len(rgb24) if height < 1: raise IndexError("Expected sequence with at least one row") row0 = rgb24[0] if not is_seq(row0): raise IndexError("Expected sequence (str, numpy.ndarray, list, tuple or " "bytearray) inside a sequence") width = len(row0) if is_pure_str(row0) or type(row0) == bytearray: width /= 3 if format == _ImageFormat.RawImage: self._encode_rgb24(rgb24, width, height) elif format == _ImageFormat.JpegImage: self._encode_jpeg_rgb24(rgb24, width, height, quality) def __EncodedAttribute_encode_jpeg_rgb32(self, rgb32, width=0, height=0, quality=100.0): """Encode a 32 bit rgb color image as JPEG format. :param rgb32: an object containning image information :type rgb32: :py:obj:`str` or :class:`numpy.ndarray` or seq< seq > :param width: image width. **MUST** be given if rgb32 is a string or if it is a :class:`numpy.ndarray` with ndims != 2. Otherwise it is calculated internally. :type width: :py:obj:`int` :param height: image height. **MUST** be given if rgb32 is a string or if it is a :class:`numpy.ndarray` with ndims != 2. Otherwise it is calculated internally. :type height: :py:obj:`int` .. note:: When :class:`numpy.ndarray` is given: - rgb32 **MUST** be CONTIGUOUS, ALIGNED - if rgb32.ndims != 2, width and height **MUST** be given and rgb32.nbytes/4 **MUST** match width*height - if rgb32.ndims == 2, rgb32.itemsize **MUST** be 4 (typically, rgb32.dtype is one of `numpy.dtype.int32`, `numpy.dtype.uint32`) Example:: def read_myattr(self, attr): enc = tango.EncodedAttribute() data = numpy.arange(100, dtype=numpy.int32) data = numpy.array((data,data,data)) enc.encode_jpeg_rgb32(data) attr.set_value(enc) """ if not is_seq(rgb32): raise TypeError("Expected sequence (str, numpy.ndarray, list, tuple " "or bytearray) as first argument") is_str = is_pure_str(rgb32) if is_str: if not width or not height: raise ValueError("When giving a string as data, you must also " "supply width and height") if np and isinstance(rgb32, np.ndarray): if rgb32.ndim != 2: if not width or not height: raise ValueError("When giving a non 2D numpy array, width and " "height must be supplied") if rgb32.nbytes / 4 != width * height: raise ValueError("numpy array size mismatch") else: if rgb32.itemsize != 4: raise TypeError("Expected numpy array with itemsize == 4") if not rgb32.flags.c_contiguous: raise TypeError("Currently, only contiguous, aligned numpy arrays " "are supported") if not rgb32.flags.aligned: raise TypeError("Currently, only contiguous, aligned numpy arrays " "are supported") if not is_str and (not width or not height): height = len(rgb32) if height < 1: raise IndexError("Expected sequence with at least one row") row0 = rgb32[0] if not is_seq(row0): raise IndexError("Expected sequence (str, numpy.ndarray, list, tuple or " "bytearray) inside a sequence") width = len(row0) if is_pure_str(row0) or type(row0) == bytearray: width /= 4 self._encode_jpeg_rgb32(rgb32, width, height, quality) def __EncodedAttribute_decode_gray8(self, da, extract_as=ExtractAs.Numpy): """Decode a 8 bits grayscale image (JPEG_GRAY8 or GRAY8) and returns a 8 bits gray scale image. :param da: :class:`DeviceAttribute` that contains the image :type da: :class:`DeviceAttribute` :param extract_as: defaults to ExtractAs.Numpy :type extract_as: ExtractAs :return: the decoded data - In case String string is choosen as extract method, a tuple is returned: width, height, buffer - In case Numpy is choosen as extract method, a :class:`numpy.ndarray` is returned with ndim=2, shape=(height, width) and dtype=numpy.uint8. - In case Tuple or List are choosen, a tuple> or list> is returned. .. warning:: The PyTango calls that return a :class:`DeviceAttribute` (like :meth:`DeviceProxy.read_attribute` or :meth:`DeviceProxy.command_inout`) automatically extract the contents by default. This method requires that the given :class:`DeviceAttribute` is obtained from a call which **DOESN'T** extract the contents. Example:: dev = tango.DeviceProxy("a/b/c") da = dev.read_attribute("my_attr", extract_as=tango.ExtractAs.Nothing) enc = tango.EncodedAttribute() data = enc.decode_gray8(da) """ if hasattr(da, 'value'): raise TypeError("DeviceAttribute argument must have been obtained from " "a call which doesn't extract the contents") if extract_as not in _allowed_extract: raise TypeError("extract_as must be one of Numpy, String, Tuple, List") return self._decode_gray8(da, extract_as) def __EncodedAttribute_decode_gray16(self, da, extract_as=ExtractAs.Numpy): """Decode a 16 bits grayscale image (GRAY16) and returns a 16 bits gray scale image. :param da: :class:`DeviceAttribute` that contains the image :type da: :class:`DeviceAttribute` :param extract_as: defaults to ExtractAs.Numpy :type extract_as: ExtractAs :return: the decoded data - In case String string is choosen as extract method, a tuple is returned: width, height, buffer - In case Numpy is choosen as extract method, a :class:`numpy.ndarray` is returned with ndim=2, shape=(height, width) and dtype=numpy.uint16. - In case Tuple or List are choosen, a tuple> or list> is returned. .. warning:: The PyTango calls that return a :class:`DeviceAttribute` (like :meth:`DeviceProxy.read_attribute` or :meth:`DeviceProxy.command_inout`) automatically extract the contents by default. This method requires that the given :class:`DeviceAttribute` is obtained from a call which **DOESN'T** extract the contents. Example:: dev = tango.DeviceProxy("a/b/c") da = dev.read_attribute("my_attr", extract_as=tango.ExtractAs.Nothing) enc = tango.EncodedAttribute() data = enc.decode_gray16(da) """ if hasattr(da, 'value'): raise TypeError("DeviceAttribute argument must have been obtained from " "a call which doesn't extract the contents") if extract_as not in _allowed_extract: raise TypeError("extract_as must be one of Numpy, String, Tuple, List") return self._decode_gray16(da, extract_as) def __EncodedAttribute_decode_rgb32(self, da, extract_as=ExtractAs.Numpy): """Decode a color image (JPEG_RGB or RGB24) and returns a 32 bits RGB image. :param da: :class:`DeviceAttribute` that contains the image :type da: :class:`DeviceAttribute` :param extract_as: defaults to ExtractAs.Numpy :type extract_as: ExtractAs :return: the decoded data - In case String string is choosen as extract method, a tuple is returned: width, height, buffer - In case Numpy is choosen as extract method, a :class:`numpy.ndarray` is returned with ndim=2, shape=(height, width) and dtype=numpy.uint32. - In case Tuple or List are choosen, a tuple> or list> is returned. .. warning:: The PyTango calls that return a :class:`DeviceAttribute` (like :meth:`DeviceProxy.read_attribute` or :meth:`DeviceProxy.command_inout`) automatically extract the contents by default. This method requires that the given :class:`DeviceAttribute` is obtained from a call which **DOESN'T** extract the contents. Example:: dev = tango.DeviceProxy("a/b/c") da = dev.read_attribute("my_attr", extract_as=tango.ExtractAs.Nothing) enc = tango.EncodedAttribute() data = enc.decode_rgb32(da) """ if hasattr(da, 'value'): raise TypeError("DeviceAttribute argument must have been obtained from " "a call which doesn't extract the contents") if extract_as not in _allowed_extract: raise TypeError("extract_as must be one of Numpy, String, Tuple, List") return self._decode_rgb32(da, extract_as) def __init_EncodedAttribute(): EncodedAttribute._generic_encode_gray8 = __EncodedAttribute_generic_encode_gray8 EncodedAttribute.encode_gray8 = __EncodedAttribute_encode_gray8 EncodedAttribute.encode_jpeg_gray8 = __EncodedAttribute_encode_jpeg_gray8 EncodedAttribute.encode_gray16 = __EncodedAttribute_encode_gray16 EncodedAttribute._generic_encode_rgb24 = __EncodedAttribute_generic_encode_rgb24 EncodedAttribute.encode_rgb24 = __EncodedAttribute_encode_rgb24 EncodedAttribute.encode_jpeg_rgb24 = __EncodedAttribute_encode_jpeg_rgb24 EncodedAttribute.encode_jpeg_rgb32 = __EncodedAttribute_encode_jpeg_rgb32 EncodedAttribute.decode_gray8 = __EncodedAttribute_decode_gray8 EncodedAttribute.decode_gray16 = __EncodedAttribute_decode_gray16 EncodedAttribute.decode_rgb32 = __EncodedAttribute_decode_rgb32 def __doc_EncodedAttribute(): pass def encoded_attribute_init(doc=True): __init_EncodedAttribute() if doc: __doc_EncodedAttribute() pytango-9.2.2/tango/exception.py000066400000000000000000000150751316324373100167000ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ """ This is an internal PyTango module. """ __all__ = ["exception_init"] __docformat__ = "restructuredtext" from .utils import document_static_method as __document_static_method from ._tango import Except, DevError, ErrSeverity def __to_dev_failed(exc_type=None, exc_value=None, traceback=None): """to_dev_failed(exc_type, exc_value, traceback) -> tango.DevFailed Generate a TANGO DevFailed exception. The exception is created with a single :class:`~tango.DevError` object. A default value *tango.ErrSeverity.ERR* is defined for the :class:`~tango.DevError` severity field. The parameters are the same as the ones generates by a call to :func:`sys.exc_info`. Parameters : - type : (class) the exception type of the exception being handled - value : (object) exception parameter (its associated value or the second argument to raise, which is always a class instance if the exception type is a class object) - traceback : (traceback) traceback object Return : (tango.DevFailed) a tango exception object New in PyTango 7.2.1""" try: Except.throw_python_exception(exc_type, exc_value, traceback) except Exception as e: return e # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ # DevError pickle # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ def __DevError__getinitargs__(self): return () def __DevError__getstate__(self): return self.reason, self.desc, self.origin, int(self.severity) def __DevError__setstate__(self, state): self.reason = state[0] self.desc = state[1] self.origin = state[2] self.severity = ErrSeverity(state[3]) def __init_DevError(): DevError.__getinitargs__ = __DevError__getinitargs__ DevError.__getstate__ = __DevError__getstate__ DevError.__setstate__ = __DevError__setstate__ def __init_Except(): Except.to_dev_failed = staticmethod(__to_dev_failed) def __doc_Except(): def document_static_method(method_name, desc, append=True): return __document_static_method(Except, method_name, desc, append) Except.__doc__ = """ A containner for the static methods: - throw_exception - re_throw_exception - print_exception - compare_exception""" document_static_method("throw_exception", """ throw_exception(reason, desc, origin, sever=tango.ErrSeverity.ERR) -> None Generate and throw a TANGO DevFailed exception. The exception is created with a single :class:`~tango.DevError` object. A default value *tango.ErrSeverity.ERR* is defined for the :class:`~tango.DevError` severity field. Parameters : - reason : (str) The exception :class:`~tango.DevError` object reason field - desc : (str) The exception :class:`~tango.DevError` object desc field - origin : (str) The exception :class:`~tango.DevError` object origin field - sever : (tango.ErrSeverity) The exception DevError object severity field Throws : DevFailed """) document_static_method("re_throw_exception", """ re_throw_exception(ex, reason, desc, origin, sever=tango.ErrSeverity.ERR) -> None Re-throw a TANGO :class:`~tango.DevFailed` exception with one more error. The exception is re-thrown with one more :class:`~tango.DevError` object. A default value *tango.ErrSeverity.ERR* is defined for the new :class:`~tango.DevError` severity field. Parameters : - ex : (tango.DevFailed) The :class:`~tango.DevFailed` exception - reason : (str) The exception :class:`~tango.DevError` object reason field - desc : (str) The exception :class:`~tango.DevError` object desc field - origin : (str) The exception :class:`~tango.DevError` object origin field - sever : (tango.ErrSeverity) The exception DevError object severity field Throws : DevFailed """) document_static_method("print_error_stack", """ print_error_stack(ex) -> None Print all the details of a TANGO error stack. Parameters : - ex : (tango.DevErrorList) The error stack reference """) document_static_method("print_exception", """ print_exception(ex) -> None Print all the details of a TANGO exception. Parameters : - ex : (tango.DevFailed) The :class:`~tango.DevFailed` exception """) document_static_method("throw_python_exception", """ throw_python_exception(type, value, traceback) -> None Generate and throw a TANGO DevFailed exception. The exception is created with a single :class:`~tango.DevError` object. A default value *tango.ErrSeverity.ERR* is defined for the :class:`~tango.DevError` severity field. The parameters are the same as the ones generates by a call to :func:`sys.exc_info`. Parameters : - type : (class) the exception type of the exception being handled - value : (object) exception parameter (its associated value or the second argument to raise, which is always a class instance if the exception type is a class object) - traceback : (traceback) traceback object Throws : DevFailed New in PyTango 7.2.1 """) def __doc_DevError(): DevError.__doc__ = """ Structure describing any error resulting from a command execution, or an attribute query, with following members: - reason : (str) reason - severity : (ErrSeverity) error severty (WARN, ERR, PANIC) - desc : (str) error description - origin : (str) Tango server method in which the error happened""" def exception_init(doc=True): __init_Except() __init_DevError() if doc: __doc_Except() __doc_DevError() pytango-9.2.2/tango/futures.py000066400000000000000000000112721316324373100163720ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ """This module exposes a futures version of :class:`tango.DeviceProxy` and :class:`tango.AttributeProxy""" __all__ = ["DeviceProxy", "AttributeProxy", "check_requirements"] from functools import partial from tango import GreenMode from tango.device_proxy import get_device_proxy from tango.attribute_proxy import get_attribute_proxy def check_requirements(): try: import concurrent.futures # noqa: F401 except ImportError: import sys if sys.version_info[0] < 3: raise ImportError( "No module named concurrent. You need to " "install the futures backport module to have " "access to PyTango futures green mode") check_requirements() DeviceProxy = partial(get_device_proxy, green_mode=GreenMode.Futures) DeviceProxy.__doc__ = """ DeviceProxy(self, dev_name, wait=True, timeout=True) -> DeviceProxy DeviceProxy(self, dev_name, need_check_acc, wait=True, timeout=True) -> DeviceProxy Creates a *futures* enabled :class:`~tango.DeviceProxy`. The DeviceProxy constructor internally makes some network calls which makes it *slow*. By using the futures *green mode* you are allowing other python code to be executed in a cooperative way. .. note:: The timeout parameter has no relation with the tango device client side timeout (gettable by :meth:`~tango.DeviceProxy.get_timeout_millis` and settable through :meth:`~tango.DeviceProxy.set_timeout_millis`) :param dev_name: the device name or alias :type dev_name: str :param need_check_acc: in first version of the function it defaults to True. Determines if at creation time of DeviceProxy it should check for channel access (rarely used) :type need_check_acc: bool :param wait: whether or not to wait for result of creating a DeviceProxy. :type wait: bool :param timeout: The number of seconds to wait for the result. If None, then there is no limit on the wait time. Ignored when wait is False. :type timeout: float :returns: if wait is True: :class:`~tango.DeviceProxy` else: :class:`concurrent.futures.Future` :throws: * a *DevFailed* if wait is True and there is an error creating the device. * a *concurrent.futures.TimeoutError* if wait is False, timeout is not None and the time to create the device has expired. New in PyTango 8.1.0 """ AttributeProxy = partial(get_attribute_proxy, green_mode=GreenMode.Futures) AttributeProxy.__doc__ = """ AttributeProxy(self, full_attr_name, wait=True, timeout=True) -> AttributeProxy AttributeProxy(self, device_proxy, attr_name, wait=True, timeout=True) -> AttributeProxy Creates a *futures* enabled :class:`~tango.AttributeProxy`. The AttributeProxy constructor internally makes some network calls which makes it *slow*. By using the *gevent mode* you are allowing other python code to be executed in a cooperative way. :param full_attr_name: the full name of the attribute :type full_attr_name: str :param device_proxy: the :class:`~tango.DeviceProxy` :type device_proxy: DeviceProxy :param attr_name: attribute name for the given device proxy :type attr_name: str :param wait: whether or not to wait for result of creating an AttributeProxy. :type wait: bool :param timeout: The number of seconds to wait for the result. If None, then there is no limit on the wait time. Ignored when wait is False. :type timeout: float :returns: if wait is True: :class:`~tango.AttributeProxy` else: :class:`concurrent.futures.Future` :throws: * a *DevFailed* if wait is True and there is an error creating the attribute. * a *concurrent.futures.TimeoutError* if wait is False, timeout is not None and the time to create the attribute has expired. New in PyTango 8.1.0 """ Device = DeviceProxy Attribute = AttributeProxy del GreenMode del get_device_proxy del get_attribute_proxy pytango-9.2.2/tango/futures_executor.py000066400000000000000000000036731316324373100203160ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ # Future imports from __future__ import absolute_import # Concurrent imports from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor # Tango imports from .green import AbstractExecutor __all__ = ["FuturesExecutor", "get_global_executor", "set_global_executor"] # Global executor _EXECUTOR = None def get_global_executor(): global _EXECUTOR if _EXECUTOR is None: _EXECUTOR = FuturesExecutor() return _EXECUTOR def set_global_executor(executor): global _EXECUTOR _EXECUTOR = executor # Futures executor class FuturesExecutor(AbstractExecutor): """Futures tango executor""" asynchronous = True default_wait = True def __init__(self, process=False, max_workers=20): cls = ProcessPoolExecutor if process else ThreadPoolExecutor self.subexecutor = cls(max_workers=max_workers) def delegate(self, fn, *args, **kwargs): """Return the given operation as a concurrent future.""" return self.subexecutor.submit(fn, *args, **kwargs) def access(self, accessor, timeout=None): """Return a result from a single callable.""" return accessor.result(timeout=timeout) def submit(self, fn, *args, **kwargs): """Submit an operation""" return fn(*args, **kwargs) def execute(self, fn, *args, **kwargs): """Execute an operation and return the result.""" return fn(*args, **kwargs) pytango-9.2.2/tango/gevent.py000066400000000000000000000120041316324373100161570ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ """This module exposes a gevent version of :class:`tango.DeviceProxy` and :class:`tango.AttributeProxy""" from __future__ import absolute_import from functools import partial from ._tango import GreenMode from .device_proxy import get_device_proxy from .attribute_proxy import get_attribute_proxy __all__ = ["DeviceProxy", "AttributeProxy", "check_requirements"] def check_requirements(): try: import gevent except ImportError: raise ImportError("No module named gevent. You need to install " "gevent module to have access to PyTango gevent " "green mode. Consider using the futures green mode " "instead") import distutils.version gevent_version = ".".join(map(str, gevent.version_info[:3])) if distutils.version.StrictVersion(gevent_version) < "1.0": raise ImportError("You need gevent >= 1.0. You are using %s. " "Consider using the futures green mode instead" % gevent_version) check_requirements() DeviceProxy = partial(get_device_proxy, green_mode=GreenMode.Gevent) DeviceProxy.__doc__ = """ DeviceProxy(self, dev_name, wait=True, timeout=True) -> DeviceProxy DeviceProxy(self, dev_name, need_check_acc, wait=True, timeout=True) -> DeviceProxy Creates a *gevent* enabled :class:`~tango.DeviceProxy`. The DeviceProxy constructor internally makes some network calls which makes it *slow*. By using the gevent *green mode* you are allowing other python code to be executed in a cooperative way. .. note:: The timeout parameter has no relation with the tango device client side timeout (gettable by :meth:`~tango.DeviceProxy.get_timeout_millis` and settable through :meth:`~tango.DeviceProxy.set_timeout_millis`) :param dev_name: the device name or alias :type dev_name: str :param need_check_acc: in first version of the function it defaults to True. Determines if at creation time of DeviceProxy it should check for channel access (rarely used) :type need_check_acc: bool :param wait: whether or not to wait for result of creating a DeviceProxy. :type wait: bool :param timeout: The number of seconds to wait for the result. If None, then there is no limit on the wait time. Ignored when wait is False. :type timeout: float :returns: if wait is True: :class:`~tango.DeviceProxy` else: :class:`gevent.event.AsynchResult` :throws: * a *DevFailed* if wait is True and there is an error creating the device. * a *gevent.timeout.Timeout* if wait is False, timeout is not None and the time to create the device has expired. New in PyTango 8.1.0 """ AttributeProxy = partial(get_attribute_proxy, green_mode=GreenMode.Gevent) AttributeProxy.__doc__ = """ AttributeProxy(self, full_attr_name, wait=True, timeout=True) -> AttributeProxy AttributeProxy(self, device_proxy, attr_name, wait=True, timeout=True) -> AttributeProxy Creates a *gevent* enabled :class:`~tango.AttributeProxy`. The AttributeProxy constructor internally makes some network calls which makes it *slow*. By using the *gevent mode* you are allowing other python code to be executed in a cooperative way. :param full_attr_name: the full name of the attribute :type full_attr_name: str :param device_proxy: the :class:`~tango.DeviceProxy` :type device_proxy: DeviceProxy :param attr_name: attribute name for the given device proxy :type attr_name: str :param wait: whether or not to wait for result of creating an AttributeProxy. :type wait: bool :param timeout: The number of seconds to wait for the result. If None, then there is no limit on the wait time. Ignored when wait is False. :type timeout: float :returns: if wait is True: :class:`~tango.AttributeProxy` else: :class:`gevent.event.AsynchResult` :throws: * a *DevFailed* if wait is True and there is an error creating the attribute. * a *gevent.timeout.Timeout* if wait is False, timeout is not None and the time to create the attribute has expired. New in PyTango 8.1.0 """ Device = DeviceProxy Attribute = AttributeProxy del GreenMode del get_device_proxy del get_attribute_proxy pytango-9.2.2/tango/gevent_executor.py000066400000000000000000000116311316324373100201020ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ # Future imports from __future__ import absolute_import # Imports import sys import six import types import functools # Combatibility imports try: from threading import get_ident except: from threading import _get_ident as get_ident # Gevent imports import gevent.queue # Tango imports from .green import AbstractExecutor __all__ = ["get_global_executor", "set_global_executor", "GeventExecutor"] # Global executor _EXECUTOR = None def get_global_executor(): global _EXECUTOR if _EXECUTOR is None: _EXECUTOR = GeventExecutor() return _EXECUTOR def set_global_executor(executor): global _EXECUTOR _EXECUTOR = executor # Patch for gevent threadpool def get_global_threadpool(): """Before gevent-1.1.0, patch the spawn method to propagate exception raised in the loop to the AsyncResult. """ threadpool = gevent.get_hub().threadpool if gevent.version_info < (1, 1) and not hasattr(threadpool, '_spawn'): threadpool._spawn = threadpool.spawn threadpool.spawn = types.MethodType( spawn, threadpool, type(threadpool)) return threadpool class ExceptionWrapper: def __init__(self, exception, error_string, tb): self.exception = exception self.error_string = error_string self.tb = tb def wrap_errors(func): @functools.wraps(func) def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except: return ExceptionWrapper(*sys.exc_info()) return wrapper def get_with_exception(result, block=True, timeout=None): result = result._get(block, timeout) if isinstance(result, ExceptionWrapper): # Raise the exception using the caller context six.reraise(result.exception, result.error_string, result.tb) return result def spawn(threadpool, fn, *args, **kwargs): # The gevent threadpool do not raise exception with async results, # we have to wrap it fn = wrap_errors(fn) result = threadpool._spawn(fn, *args, **kwargs) result._get = result.get result.get = types.MethodType(get_with_exception, result, type(result)) return result # Gevent task and event loop class GeventTask: def __init__(self, event, func, *args, **kwargs): self.event = event self.func = func self.args = args self.kwargs = kwargs self.value = None self.exception = None def run(self): try: self.value = self.func(*self.args, **self.kwargs) except: self.exception = sys.exc_info() finally: self.event.set() def spawn(self): return gevent.spawn(self.run) def result(self): self.event.wait() if self.exception: six.reraise(*self.exception) return self.value class GeventLoop: def __init__(self): self.thread_id = get_ident() self.tasks = gevent.queue.Queue() self.loop = gevent.spawn(self.run) def run(self): while True: self.tasks.get().spawn() def is_gevent_thread(self): return self.thread_id == get_ident() def submit(self, func, *args, **kwargs): event = gevent._threading.Event() task = GeventTask(event, func, *args, **kwargs) self.tasks.put_nowait(task) self.tasks.hub.loop.async().send() return task # Gevent executor class GeventExecutor(AbstractExecutor): """Gevent tango executor""" asynchronous = True default_wait = True def __init__(self, loop=None, subexecutor=None): if loop is None: loop = GeventLoop() if subexecutor is None: subexecutor = get_global_threadpool() self.loop = loop self.subexecutor = subexecutor def delegate(self, fn, *args, **kwargs): """Return the given operation as a gevent future.""" return self.subexecutor.spawn(fn, *args, **kwargs) def access(self, accessor, timeout=None): """Return a result from an gevent future.""" return accessor.get(timeout=timeout) def submit(self, fn, *args, **kwargs): return self.loop.submit(fn, *args, **kwargs) def execute(self, fn, *args, **kwargs): """Execute an operation and return the result.""" if self.loop.is_gevent_thread(): return fn(*args, **kwargs) task = self.submit(fn, *args, **kwargs) return task.result() pytango-9.2.2/tango/globals.py000066400000000000000000000061451316324373100163230ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ """ This is an internal PyTango module. """ __all__ = ["get_class", "get_classes", "get_cpp_class", "get_cpp_classes", "get_constructed_class", "get_constructed_classes", "class_factory", "delete_class_list", "class_list", "cpp_class_list", "constructed_class"] __docformat__ = "restructuredtext" # list of tuple class_list = [] # list of tuple cpp_class_list = [] # list of DeviceClass objects, one for each registered device class constructed_class = [] def get_classes(): global class_list return class_list def get_class(name): for klass_info in get_classes(): if klass_info[2] == name: return klass_info return None def get_class_by_class(klass): for klass_info in get_classes(): if klass_info[0] == klass: return klass_info return None def get_cpp_classes(): global cpp_class_list return cpp_class_list def get_cpp_class(name): for klass_info in get_cpp_classes(): if klass_info[1] == name: return klass_info return None def get_constructed_classes(): global constructed_class return constructed_class def get_constructed_class(name): for klass in get_constructed_classes(): if klass.get_name() == name: return klass return None def get_constructed_class_by_class(klass): for k in get_constructed_classes(): if k.__class__ == klass: return k return None # # A method to delete Tango classes from Python # def delete_class_list(): global constructed_class if len(constructed_class) != 0: del (constructed_class[:]) # # A generic class_factory method # def class_factory(): local_class_list = get_classes() local_cpp_class_list = get_cpp_classes() if ((len(local_class_list) + len(local_cpp_class_list)) == 0): print('Oups, no Tango class defined within this device server !!!') print('Sorry, but I exit') import sys sys.exit() # Call the delete_class_list function in order to clear the global # constructed class Python list. This is necessary only in case of # ServerRestart command delete_class_list() local_constructed_class = get_constructed_classes() for class_info in local_class_list: device_class_class = class_info[0] tango_device_class_name = class_info[2] device_class = device_class_class(tango_device_class_name) local_constructed_class.append(device_class) pytango-9.2.2/tango/green.py000066400000000000000000000136501316324373100157770ustar00rootroot00000000000000# ----------------------------------------------------------------------------- # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ----------------------------------------------------------------------------- # Imports import os from functools import wraps # Tango imports from ._tango import GreenMode __all__ = ['get_green_mode', 'set_green_mode', 'green', 'green_callback', 'get_executor', 'get_object_executor'] # Handle current green mode try: _CURRENT_GREEN_MODE = getattr( GreenMode, os.environ["PYTANGO_GREEN_MODE"].capitalize()) except Exception: _CURRENT_GREEN_MODE = GreenMode.Synchronous def set_green_mode(green_mode=None): """Sets the global default PyTango green mode. Advice: Use only in your final application. Don't use this in a python library in order not to interfere with the beavior of other libraries and/or application where your library is being. :param green_mode: the new global default PyTango green mode :type green_mode: GreenMode """ global _CURRENT_GREEN_MODE # Make sure the green mode is available get_executor(green_mode) # Set the green mode _CURRENT_GREEN_MODE = green_mode def get_green_mode(): """Returns the current global default PyTango green mode. :returns: the current global default PyTango green mode :rtype: GreenMode """ return _CURRENT_GREEN_MODE # Abstract executor class class AbstractExecutor(object): asynchronous = NotImplemented default_wait = NotImplemented def delegate(self, fn, *args, **kwargs): """Delegate an operation and return an accessor.""" if not self.asynchronous: raise ValueError('Not supported in synchronous mode') raise NotImplementedError def access(self, accessor, timeout=None): """Return a result from an accessor.""" if not self.asynchronous: raise ValueError('Not supported in synchronous mode') raise NotImplementedError def submit(self, fn, *args, **kwargs): """Submit an operation""" if not self.asynchronous: return fn(*args, **kwargs) raise NotImplementedError def execute(self, fn, *args, **kwargs): """Execute an operation and return the result.""" if not self.asynchronous: return fn(*args, **kwargs) raise NotImplementedError def run(self, fn, args=(), kwargs={}, wait=None, timeout=None): if wait is None: wait = self.default_wait # Sychronous (no delegation) if not self.asynchronous: if not wait or timeout: raise ValueError('Not supported in synchronous mode') return fn(*args, **kwargs) # Asynchronous delegation accessor = self.delegate(fn, *args, **kwargs) if not wait: return accessor return self.access(accessor, timeout=timeout) class SynchronousExecutor(AbstractExecutor): asynchronous = False default_wait = True # Default synchronous executor def get_synchronous_executor(): return _SYNCHRONOUS_EXECUTOR _SYNCHRONOUS_EXECUTOR = SynchronousExecutor() # Getters def get_object_green_mode(obj): if hasattr(obj, "get_green_mode"): return obj.get_green_mode() return get_green_mode() def get_executor(green_mode=None): if green_mode is None: green_mode = get_green_mode() # Valid green modes if green_mode == GreenMode.Synchronous: return get_synchronous_executor() if green_mode == GreenMode.Gevent: from . import gevent_executor return gevent_executor.get_global_executor() if green_mode == GreenMode.Futures: from . import futures_executor return futures_executor.get_global_executor() if green_mode == GreenMode.Asyncio: from . import asyncio_executor return asyncio_executor.get_global_executor() # Invalid green mode raise TypeError("Not a valid green mode") def get_object_executor(obj, green_mode=None): """Returns the proper executor for the given object. If the object has *_executors* and *_green_mode* members it returns the submit callable for the executor corresponding to the green_mode. Otherwise it returns the global executor for the given green_mode. Note: *None* is a valid object. :returns: submit callable""" # Get green mode if green_mode is None: green_mode = get_object_green_mode(obj) # Get executor executor = None if hasattr(obj, '_executors'): executor = obj._executors.get(green_mode, None) if executor is None: executor = get_executor(green_mode) # Get submitter return executor # Green modifiers def green(fn=None, consume_green_mode=True): """Make a function green. Can be used as a decorator.""" def decorator(fn): @wraps(fn) def greener(obj, *args, **kwargs): args = (obj,) + args wait = kwargs.pop('wait', None) timeout = kwargs.pop('timeout', None) access = kwargs.pop if consume_green_mode else kwargs.get green_mode = access('green_mode', None) executor = get_object_executor(obj, green_mode) return executor.run(fn, args, kwargs, wait=wait, timeout=timeout) return greener if fn is None: return decorator return decorator(fn) def green_callback(fn, obj=None, green_mode=None): """Return a green verion of the given callback.""" executor = get_object_executor(obj, green_mode) @wraps(fn) def greener(*args, **kwargs): return executor.submit(fn, *args, **kwargs) return greener pytango-9.2.2/tango/group.py000066400000000000000000000715301316324373100160340ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ """ This is an internal PyTango module. """ __all__ = ["Group", "group_init"] __docformat__ = "restructuredtext" import collections from ._tango import __Group as _RealGroup, StdStringVector from .utils import seq_2_StdStringVector, is_pure_str from .utils import document_method as __document_method from .device_proxy import __init_device_proxy_internals as init_device_proxy def _apply_to(fn, key): if isinstance(key, slice): if key.step: return [fn(x) for x in range(key.start, key.stop, key.step)] else: return [fn(x) for x in range(key.start, key.stop)] else: return fn(key) def _get_one_item(group, key): x = group.get_group(key) if x is not None: return x return group.get_device(key) # I define Group as a proxy to __Group, where group is the actual # C++ Tango::Group object. Most functions just call the __group object # and are defined dynamically in __init_proxy_Group, also copying it's # documentation strings. # The proxy is useful for add(group). In this case the parameter 'group' # becomes useless. With the proxy we make that parameter come to live # again before returning. # Another function that needs to be adapted to this is get_group because # we want to return a Group, not a __Group! # The get_device method also needs to be adapted in order to properly # initialize the returned proxy with its python attributes. class Group: """A Tango Group represents a hierarchy of tango devices. The hierarchy may have more than one level. The main goal is to group devices with same attribute(s)/command(s) to be able to do parallel requests.""" def __init__(self, name): if is_pure_str(name): name = _RealGroup(name) if not isinstance(name, _RealGroup): raise TypeError("Constructor expected receives a str") self.__group = name def add(self, pattern_subgroup, timeout_ms=-1): if isinstance(pattern_subgroup, Group): name = pattern_subgroup.__group.get_name() self._add(pattern_subgroup.__group, timeout_ms) pattern_subgroup.__group = self.get_group(name) else: self._add(pattern_subgroup, timeout_ms) def _add(self, patterns_or_group, timeout_ms=-1): if isinstance(patterns_or_group, _RealGroup): return self.__group._add(patterns_or_group, timeout_ms) elif isinstance(patterns_or_group, StdStringVector): return self.__group._add(patterns_or_group, timeout_ms) elif isinstance(patterns_or_group, str): return self.__group._add(patterns_or_group, timeout_ms) elif isinstance(patterns_or_group, collections.Sequence): patterns = seq_2_StdStringVector(patterns_or_group) return self.__group._add(patterns, timeout_ms) else: raise TypeError('Parameter patterns_or_group: Should be Group, str or a sequence of strings.') def remove(self, patterns, forward=True): if isinstance(patterns, str): return self.__group._remove(patterns, forward) elif isinstance(patterns, collections.Sequence): std_patterns = seq_2_StdStringVector(patterns) return self.__group._remove(std_patterns, forward) else: raise TypeError('Parameter patterns: Should be a str or a sequence of str.') def get_device(self, name_or_index): proxy = self.__group.get_device(name_or_index) init_device_proxy(proxy) return proxy def get_group(self, group_name): internal = self.__group.get_group(group_name) if internal is None: return None return Group(internal) def __contains__(self, pattern): return self.contains(pattern) def __getitem__(self, key): fn = lambda x: _get_one_item(self, x) return _apply_to(fn, key) def __delitem__(self, key): fn = lambda x: self.remove(x) return _apply_to(fn, key) def __len__(self): return self.get_size() def command_inout(self, cmd_name, param=None, forward=True): if param is None: idx = self.command_inout_asynch(cmd_name, forget=False, forward=forward) else: idx = self.command_inout_asynch(cmd_name, param, forget=False, forward=forward) return self.command_inout_reply(idx) def read_attribute(self, attr_name, forward=True): idx = self.__group.read_attribute_asynch(attr_name, forward) return self.__group.read_attribute_reply(idx) def read_attributes(self, attr_names, forward=True): idx = self.__group.read_attributes_asynch(attr_names, forward) return self.__group.read_attributes_reply(idx) def write_attribute(self, attr_name, value, forward=True, multi=False): idx = self.__group.write_attribute_asynch(attr_name, value, forward=forward, multi=multi) return self.__group.write_attribute_reply(idx) def __init_proxy_Group(): proxy_methods = [ # 'add', # Needs to be adapted 'command_inout_asynch', 'command_inout_reply', 'contains', 'disable', 'enable', # 'get_device', # Needs to be adapted 'get_device_list', 'get_fully_qualified_name', # 'get_group', # Needs to be adapted 'get_name', 'get_size', 'is_enabled', 'name_equals', 'name_matches', 'ping', 'read_attribute_asynch', 'read_attribute_reply', 'read_attributes_asynch', 'read_attributes_reply', 'remove_all', 'set_timeout_millis', 'write_attribute_asynch', 'write_attribute_reply'] def proxy_call_define(fname): def fn(self, *args, **kwds): return getattr(self._Group__group, fname)(*args, **kwds) fn.__doc__ = getattr(_RealGroup, fname).__doc__ setattr(Group, fname, fn) for fname in proxy_methods: proxy_call_define(fname) # Group.add.__func__.__doc__ = _RealGroup.add.__doc__ # Group.get_group.__func__.__doc__ = _RealGroup.get_group.__doc__ # Group.__doc__ = _RealGroup.__doc__ def __doc_Group(): def document_method(method_name, desc, append=True): return __document_method(_RealGroup, method_name, desc, append) document_method("_add", """ add(self, patterns, timeout_ms=-1) -> None Attaches any device which name matches one of the specified patterns. This method first asks to the Tango database the list of device names matching one the patterns. Devices are then attached to the group in the order in which they are returned by the database. Any device already present in the hierarchy (i.e. a device belonging to the group or to one of its subgroups), is silently ignored but its client side timeout is set to timeout_ms milliseconds if timeout_ms is different from -1. Parameters : - patterns : (str | sequence) can be a simple device name or a device name pattern (e.g. domain_*/ family/member_*), or a sequence of these. - timeout_ms : (int) If timeout_ms is different from -1, the client side timeouts of all devices matching the specified patterns are set to timeout_ms milliseconds. Return : None Throws : TypeError, ArgumentError """) document_method("_remove", """ remove(self, patterns, forward=True) -> None Removes any group or device which name matches the specified pattern. The pattern parameter can be a group name, a device name or a device name pattern (e.g domain_*/family/member_*). Since we can have groups with the same name in the hierarchy, a group name can be fully qualified to specify which group should be removed. Considering the following group: :: -> gauges | -> cell-01 | |-> penning | | |-> ... | |-> pirani | |-> ... | -> cell-02 | |-> penning | | |-> ... | |-> pirani | |-> ... | -> cell-03 | |-> ... | | -> ... A call to gauges->remove("penning") will remove any group named "penning" in the hierarchy while gauges->remove("gauges.cell-02.penning") will only remove the specified group. Parameters : - patterns : (str | sequence) A string with the pattern or a list of patterns. - forward : (bool) If fwd is set to true (the default), the remove request is also forwarded to subgroups. Otherwise, it is only applied to the local set of elements. For instance, the following code remove any stepper motor in the hierarchy: root_group->remove("*/stepper_motor/*"); Return : None Throws : """) document_method("remove_all", """ remove_all(self) -> None Removes all elements in the _RealGroup. After such a call, the _RealGroup is empty. """) document_method("contains", """ contains(self, pattern, forward=True) -> bool Parameters : - pattern : (str) The pattern can be a fully qualified or simple group name, a device name or a device name pattern. - forward : (bool) If fwd is set to true (the default), the remove request is also forwarded to subgroups. Otherwise, it is only applied to the local set of elements. Return : (bool) Returns true if the hierarchy contains groups and/or devices which name matches the specified pattern. Returns false otherwise. Throws : """) document_method("get_device", """ get_device(self, dev_name) -> DeviceProxy get_device(self, idx) -> DeviceProxy Returns a reference to the specified device or None if there is no device by that name in the group. Or, returns a reference to the "idx-th" device in the hierarchy or NULL if the hierarchy contains less than "idx" devices. This method may throw an exception in case the specified device belongs to the group but can't be reached (not registered, down...). See example below: :: try: dp = g.get_device("my/device/01") if dp is None: # my/device/01 does not belong to the group pass except DevFailed, f: # my/device/01 belongs to the group but can't be reached pass The request is systematically forwarded to subgroups (i.e. if no device named device_name could be found in the local set of devices, the request is forwarded to subgroups). Parameters : - dev_name : (str) Device name. - idx : (int) Device number. Return : (DeviceProxy) Be aware that this method returns a different DeviceProxy referring to the same device each time. So, do not use it directly for permanent things. Example: # WRONG: The DeviceProxy will quickly go out of scope # and disappear (thus, the event will be automatically # unsubscribed) g.get_device("my/device/01").subscribe_events('attr', callback) # GOOD: dp = g.get_device("my/device/01") dp.subscribe_events('attr', callback) Throws : DevFailed """) document_method("get_device_list", """ get_device_list(self, forward=True) -> sequence Considering the following hierarchy: :: g2.add("my/device/04") g2.add("my/device/05") g4.add("my/device/08") g4.add("my/device/09") g3.add("my/device/06") g3.add(g4) g3.add("my/device/07") g1.add("my/device/01") g1.add(g2) g1.add("my/device/03") g1.add(g3) g1.add("my/device/02") The returned vector content depends on the value of the forward option. If set to true, the results will be organized as follows: :: dl = g1.get_device_list(True) dl[0] contains "my/device/01" which belongs to g1 dl[1] contains "my/device/04" which belongs to g1.g2 dl[2] contains "my/device/05" which belongs to g1.g2 dl[3] contains "my/device/03" which belongs to g1 dl[4] contains "my/device/06" which belongs to g1.g3 dl[5] contains "my/device/08" which belongs to g1.g3.g4 dl[6] contains "my/device/09" which belongs to g1.g3.g4 dl[7] contains "my/device/07" which belongs to g1.g3 dl[8] contains "my/device/02" which belongs to g1 If the forward option is set to false, the results are: :: dl = g1.get_device_list(False); dl[0] contains "my/device/01" which belongs to g1 dl[1] contains "my/device/03" which belongs to g1 dl[2] contains "my/device/02" which belongs to g1 Parameters : - forward : (bool) If it is set to true (the default), the request is forwarded to sub-groups. Otherwise, it is only applied to the local set of devices. Return : (sequence) The list of devices currently in the hierarchy. Throws : """) document_method("get_group", """ get_group(self, group_name ) -> Group Returns a reference to the specified group or None if there is no group by that name. The group_name can be a fully qualified name. Considering the following group: :: -> gauges |-> cell-01 | |-> penning | | |-> ... | |-> pirani | |-> ... |-> cell-02 | |-> penning | | |-> ... | |-> pirani | |-> ... | -> cell-03 | |-> ... | | -> ... A call to gauges.get_group("penning") returns the first group named "penning" in the hierarchy (i.e. gauges.cell-01.penning) while gauges.get_group("gauges.cell-02.penning'') returns the specified group. The request is systematically forwarded to subgroups (i.e. if no group named group_name could be found in the local set of elements, the request is forwarded to subgroups). Parameters : - group_name : (str) Return : (Group) Throws : New in PyTango 7.0.0 """) # Tango methods (~ DeviceProxy interface) document_method("ping", """ ping(self, forward=True) -> bool Ping all devices in a group. Parameters : - forward : (bool) If fwd is set to true (the default), the request is also forwarded to subgroups. Otherwise, it is only applied to the local set of devices. Return : (bool) This method returns true if all devices in the group are alive, false otherwise. Throws : """) document_method("set_timeout_millis", """ set_timeout_millis(self, timeout_ms) -> bool Set client side timeout for all devices composing the group in milliseconds. Any method which takes longer than this time to execute will throw an exception. Parameters : - timeout_ms : (int) Return : None Throws : (errors are ignored) New in PyTango 7.0.0 """) document_method("command_inout_asynch", """ command_inout_asynch(self, cmd_name, forget=False, forward=True, reserved=-1 ) -> int command_inout_asynch(self, cmd_name, param, forget=False, forward=True, reserved=-1 ) -> int command_inout_asynch(self, cmd_name, param_list, forget=False, forward=True, reserved=-1 ) -> int Executes a Tango command on each device in the group asynchronously. The method sends the request to all devices and returns immediately. Pass the returned request id to Group.command_inout_reply() to obtain the results. Parameters : - cmd_name : (str) Command name - param : (any) parameter value - param_list : (tango.DeviceDataList) sequence of parameters. When given, it's length must match the group size. - forget : (bool) Fire and forget flag. If set to true, it means that no reply is expected (i.e. the caller does not care about it and will not even try to get it) - forward : (bool) If it is set to true (the default) request is forwarded to subgroups. Otherwise, it is only applied to the local set of devices. - reserved : (int) is reserved for internal purpose and should not be used. This parameter may disappear in a near future. Return : (int) request id. Pass the returned request id to Group.command_inout_reply() to obtain the results. Throws : """) document_method("command_inout_reply", """ command_inout_reply(self, req_id, timeout_ms=0) -> sequence Returns the results of an asynchronous command. Parameters : - req_id : (int) Is a request identifier previously returned by one of the command_inout_asynch methods - timeout_ms : (int) For each device in the hierarchy, if the command result is not yet available, command_inout_reply wait timeout_ms milliseconds before throwing an exception. This exception will be part of the global reply. If timeout_ms is set to 0, command_inout_reply waits "indefinitely". Return : (sequence) Throws : """) document_method("read_attribute_asynch", """ read_attribute_asynch(self, attr_name, forward=True, reserved=-1 ) -> int Reads an attribute on each device in the group asynchronously. The method sends the request to all devices and returns immediately. Parameters : - attr_name : (str) Name of the attribute to read. - forward : (bool) If it is set to true (the default) request is forwarded to subgroups. Otherwise, it is only applied to the local set of devices. - reserved : (int) is reserved for internal purpose and should not be used. This parameter may disappear in a near future. Return : (int) request id. Pass the returned request id to Group.read_attribute_reply() to obtain the results. Throws : """) document_method("read_attributes_asynch", """ read_attributes_asynch(self, attr_names, forward=True, reserved=-1 ) -> int Reads the attributes on each device in the group asynchronously. The method sends the request to all devices and returns immediately. Parameters : - attr_names : (sequence) Name of the attributes to read. - forward : (bool) If it is set to true (the default) request is forwarded to subgroups. Otherwise, it is only applied to the local set of devices. - reserved : (int) is reserved for internal purpose and should not be used. This parameter may disappear in a near future. Return : (int) request id. Pass the returned request id to Group.read_attributes_reply() to obtain the results. Throws : """) document_method("read_attribute_reply", """ read_attribute_reply(self, req_id, timeout_ms=0 ) -> sequence Returns the results of an asynchronous attribute reading. Parameters : - req_id : (int) a request identifier previously returned by read_attribute_asynch. - timeout_ms : (int) For each device in the hierarchy, if the attribute value is not yet available, read_attribute_reply wait timeout_ms milliseconds before throwing an exception. This exception will be part of the global reply. If timeout_ms is set to 0, read_attribute_reply waits "indefinitely". Return : (sequence) Throws : """) document_method("read_attributes_reply", """ read_attributes_reply(self, req_id, timeout_ms=0 ) -> sequence Returns the results of an asynchronous attribute reading. Parameters : - req_id : (int) a request identifier previously returned by read_attribute_asynch. - timeout_ms : (int) For each device in the hierarchy, if the attribute value is not yet available, read_attribute_reply ait timeout_ms milliseconds before throwing an exception. This exception will be part of the global reply. If timeout_ms is set to 0, read_attributes_reply waits "indefinitely". Return : (sequence) Throws : """) document_method("write_attribute_asynch", """ write_attribute_asynch(self, attr_name, value, forward=True, multi=False ) -> int Writes an attribute on each device in the group asynchronously. The method sends the request to all devices and returns immediately. Parameters : - attr_name : (str) Name of the attribute to write. - value : (any) Value to write. See DeviceProxy.write_attribute - forward : (bool) If it is set to true (the default) request is forwarded to subgroups. Otherwise, it is only applied to the local set of devices. - multi : (bool) If it is set to false (the default), the same value is applied to all devices in the group. Otherwise the value is interpreted as a sequence of values, and each value is applied to the corresponding device in the group. In this case len(value) must be equal to group.get_size()! Return : (int) request id. Pass the returned request id to Group.write_attribute_reply() to obtain the acknowledgements. Throws : """) document_method("write_attribute_reply", """ write_attribute_reply(self, req_id, timeout_ms=0 ) -> sequence Returns the acknowledgements of an asynchronous attribute writing. Parameters : - req_id : (int) a request identifier previously returned by write_attribute_asynch. - timeout_ms : (int) For each device in the hierarchy, if the acknowledgment is not yet available, write_attribute_reply wait timeout_ms milliseconds before throwing an exception. This exception will be part of the global reply. If timeout_ms is set to 0, write_attribute_reply waits "indefinitely". Return : (sequence) Throws : """) document_method("get_name", """ Get the name of the group. Eg: Group('name').get_name() == 'name' """) document_method("get_fully_qualified_name", """ Get the complete (dpt-separated) name of the group. This takes into consideration the name of the group and its parents. """) document_method("enable", "Enables a group or a device element in a group.") document_method("disable", "Disables a group or a device element in a group.") document_method("is_enabled", "Check if a group is enabled.\nNew in PyTango 7.0.0") document_method("name_equals", "New in PyTango 7.0.0") document_method("name_matches", "New in PyTango 7.0.0") document_method("get_size", """ get_size(self, forward=True) -> int Parameters : - forward : (bool) If it is set to true (the default), the request is forwarded to sub-groups. Return : (int) The number of the devices in the hierarchy Throws : """) def document_group_method(method_name, desc, append=True): return __document_method(Group, method_name, desc, append) document_group_method("add", _RealGroup._add.__doc__, False) document_group_method("add", """ add(self, subgroup, timeout_ms=-1) -> None Attaches a (sub)_RealGroup. To remove the subgroup use the remove() method. Parameters : - subgroup : (str) - timeout_ms : (int) If timeout_ms parameter is different from -1, the client side timeout associated to each device composing the _RealGroup added is set to timeout_ms milliseconds. If timeout_ms is -1, timeouts are not changed. Return : None Throws : TypeError, ArgumentError """) document_group_method("command_inout", """ command_inout(self, cmd_name, forward=True) -> sequence command_inout(self, cmd_name, param, forward=True) -> sequence command_inout(self, cmd_name, param_list, forward=True) -> sequence Just a shortcut to do: self.command_inout_reply(self.command_inout_asynch(...)) Parameters: - cmd_name : (str) Command name - param : (any) parameter value - param_list : (tango.DeviceDataList) sequence of parameters. When given, it's length must match the group size. - forward : (bool) If it is set to true (the default) request is forwarded to subgroups. Otherwise, it is only applied to the local set of devices. Return : (sequence) """) document_group_method("read_attribute", """ read_attribute(self, attr_name, forward=True) -> sequence Just a shortcut to do: self.read_attribute_reply(self.read_attribute_asynch(...)) """) document_group_method("read_attributes", """ read_attributes(self, attr_names, forward=True) -> sequence Just a shortcut to do: self.read_attributes_reply(self.read_attributes_asynch(...)) """) document_group_method("write_attribute", """ write_attribute(self, attr_name, value, forward=True, multi=False) -> sequence Just a shortcut to do: self.write_attribute_reply(self.write_attribute_asynch(...)) """) def group_init(doc=True): if doc: __doc_Group() __init_proxy_Group() pytango-9.2.2/tango/group_reply.py000066400000000000000000000064441316324373100172510ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ """ This is an internal PyTango module. """ __all__ = ["group_reply_init"] __docformat__ = "restructuredtext" from .utils import document_method as __document_method from ._tango import GroupReply, GroupCmdReply, GroupAttrReply, ExtractAs def __GroupCmdReply__get_data(self): return self.get_data_raw().extract() def __GroupAttrReply__get_data(self, extract_as=ExtractAs.Numpy): # GroupAttrReply.__get_data() extracts the data from the object, so # two successive calls to get_data() result in the second one returning # an empty value, which is an unexpected behaviour. # That's why we cache the result of the first call. try: data, orig_extract_as = self.__dataCache except AttributeError: data = self.__get_data(extract_as) self.__dataCache = data, extract_as return data if extract_as != orig_extract_as: raise Exception("Successive calls to get_data() must receive the same" " parameters as the first one.") return data def __init_GroupReply(): GroupCmdReply.get_data = __GroupCmdReply__get_data GroupAttrReply.get_data = __GroupAttrReply__get_data def __doc_GroupReply(): def document_method(method_name, desc, append=True): return __document_method(GroupReply, method_name, desc, append) GroupReply.__doc__ = """ This is the base class for the result of an operation on a PyTangoGroup, being it a write attribute, read attribute, or command inout operation. It has some trivial common operations: - has_failed(self) -> bool - group_element_enabled(self) ->bool - dev_name(self) -> str - obj_name(self) -> str - get_err_stack(self) -> DevErrorList """ __document_method(GroupCmdReply, "get_data", """ get_data(self) -> any Get the actual value stored in the GroupCmdRply, the command output value. It's the same as self.get_data_raw().extract() Parameters : None Return : (any) Whatever is stored there, or None. """) __document_method(GroupCmdReply, "get_data_raw", """ get_data_raw(self) -> any Get the DeviceData containing the output parameter of the command. Parameters : None Return : (DeviceData) Whatever is stored there, or None. """) __document_method(GroupAttrReply, "get_data", """ get_data(self, extract_as=ExtractAs.Numpy) -> DeviceAttribute Get the DeviceAttribute. Parameters : - extract_as : (ExtractAs) Return : (DeviceAttribute) Whatever is stored there, or None. """) def group_reply_init(doc=True): __init_GroupReply() if doc: __doc_GroupReply() pytango-9.2.2/tango/group_reply_list.py000066400000000000000000000061471316324373100203040ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ """ This is an internal PyTango module. """ __all__ = ["group_reply_list_init"] __docformat__ = "restructuredtext" from ._tango import GroupReplyList, GroupCmdReplyList, GroupAttrReplyList def __GroupReplyList__getitem(self, item): # Accessing an item in GroupReplyList and friends makes a C++ copy # of the item calling the copy constructor. But the copy constructor # of GroupReply is not fair: It extracts the data from the original # object!! So, we will only call the original getitem once, the # successive calls will just return the cached object. # GroupReplyList could be changed to use proxies to the internal # c++ object with the apropriate parameter to the # boost::vector_indexing_suite, but when the value is extracted # it is not anymore in any C++ object but in the python object, so # we must still keep it. # Slices support if isinstance(item, slice): return [self.__getitem__(x) for x in range(*item.indices(len(self)))] # We want to get the same cache value for x[-1] as for x[len(x)-1] if item < 0: item = item + len(self) # Try to get from cache try: return self.__listCache[item] except AttributeError: # The GroupReplyList object is created without the # cache attribute, so it is created here self.__listCache = dict() except KeyError: # The decision wheter we are out of bounds or it's just a cache # miss will be taken by original_getitem pass r = self.__GroupReplyList_original_getitem(item) self.__listCache[item] = r return r def __GroupReplyList__iter(self): # Same problem as getitem. In this case it is easier for me to just # reimplement __iter__ in terms of __getitem__ for x in range(len(self)): yield self[x] def __init_GroupReplyList(): GroupReplyList.__GroupReplyList_original_getitem = GroupReplyList.__getitem__ GroupReplyList.__getitem__ = __GroupReplyList__getitem GroupCmdReplyList.__GroupReplyList_original_getitem = GroupCmdReplyList.__getitem__ GroupCmdReplyList.__getitem__ = __GroupReplyList__getitem GroupAttrReplyList.__GroupReplyList_original_getitem = GroupAttrReplyList.__getitem__ GroupAttrReplyList.__getitem__ = __GroupReplyList__getitem GroupReplyList.__iter__ = __GroupReplyList__iter GroupCmdReplyList.__iter__ = __GroupReplyList__iter GroupAttrReplyList.__iter__ = __GroupReplyList__iter def __doc_GroupReplyList(): pass def group_reply_list_init(doc=True): __init_GroupReplyList() if doc: __doc_GroupReplyList() pytango-9.2.2/tango/log4tango.py000066400000000000000000000227151316324373100165770ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ """ This is an internal PyTango module. It provides tango log classes that can be used as decorators in any method of :class:`tango.DeviceImpl`. To access these members use directly :mod:`tango` module and NOT tango.log4tango. Example:: import tango class MyDev(tango.Device_4Impl): tango.InfoIt() def read_Current(self, attr): attr.set_value(self._current) """ __all__ = ["TangoStream", "LogIt", "DebugIt", "InfoIt", "WarnIt", "ErrorIt", "FatalIt"] __docformat__ = "restructuredtext" import functools class TangoStream: def __init__(self, fn): self._fn = fn self._accum = "" def write(self, s): self._accum += s # while there is no new line, just accumulate the buffer try: if s[-1] == '\n' or s.index('\n') >= 0: self.flush() except ValueError: pass def flush(self): b = self._accum if b is None or len(self._accum) == 0: return # take the '\n' because the log adds it if b[-1] == '\n': b = b[:-1] self._fn(b) self._accum = "" class LogIt(object): """A class designed to be a decorator of any method of a :class:`tango.DeviceImpl` subclass. The idea is to log the entrance and exit of any decorated method. Example:: class MyDevice(tango.Device_4Impl): @tango.LogIt() def read_Current(self, attr): attr.set_value(self._current, 1) All log messages generated by this class have DEBUG level. If you whish to have different log level messages, you should implement subclasses that log to those levels. See, for example, :class:`tango.InfoIt`. The constructor receives three optional arguments: * show_args - shows method arguments in log message (defaults to False) * show_kwargs - shows keyword method arguments in log message (defaults to False) * show_ret - shows return value in log message (defaults to False) """ def __init__(self, show_args=False, show_kwargs=False, show_ret=False): """Initializes de LogIt object. :param show_args: (bool) show arguments in log message (default is False) :param show_kwargs: (bool) show keyword arguments in log message (default is False) :param show_ret: (bool) show return in log message (default is False) """ self._show_args = show_args self._show_kwargs = show_kwargs self._show_ret = show_ret def __compact_elem(self, v, maxlen=25): v = repr(v) if len(v) > maxlen: v = v[:maxlen - 6] + " [...]" return v def __compact_elems(self, elems): return map(self.__compact_elem, elems) def __compact_elems_str(self, elems): return ", ".join(self.__compact_elems(elems)) def __compact_item(self, k, v, maxlen=None): if maxlen is None: return "%s=%s" % (k, self.__compact(v)) return "%s=%s" % (k, self.__compact(v, maxlen=maxlen)) def __compact_dict(self, d, maxlen=None): return (self.__compact_item(k, v) for k, v in d.items()) def __compact_dict_str(self, d, maxlen=None): return ", ".join(self.__compact_dict(d, maxlen=maxlen)) def is_enabled(self, obj): return obj.get_logger().is_debug_enabled() def get_log_func(self, obj): return obj.debug_stream def __call__(self, f): @functools.wraps(f) def log_stream(*args, **kwargs): dev = args[0] if not self.is_enabled(dev): return f(*args, **kwargs) log = self.get_log_func(dev) f_name = dev.__class__.__name__ + "." + f.__name__ sargs = "" if self._show_args: sargs = self.__compact_elems_str(args[1:]) if self._show_kwargs: sargs += self.__compact_dict_str(kwargs) log("-> {0}({1})".format(f_name, sargs)) with_exc = True try: ret = f(*args, **kwargs) with_exc = False return ret finally: if with_exc: log("<- {0}() raised exception!".format(f_name)) else: sret = "" if self._show_ret: sret = self.__compact_elem(ret) + " " log("{0}<- {1}()".format(sret, f_name)) log_stream._wrapped = f return log_stream class DebugIt(LogIt): """A class designed to be a decorator of any method of a :class:`tango.DeviceImpl` subclass. The idea is to log the entrance and exit of any decorated method as DEBUG level records. Example:: class MyDevice(tango.Device_4Impl): @tango.DebugIt() def read_Current(self, attr): attr.set_value(self._current, 1) All log messages generated by this class have DEBUG level. The constructor receives three optional arguments: * show_args - shows method arguments in log message (defaults to False) * show_kwargs - shows keyword method arguments in log message (defaults to False) * show_ret - shows return value in log message (defaults to False) """ def is_enabled(self, d): return d.get_logger().is_debug_enabled() def get_log_func(self, d): return d.debug_stream class InfoIt(LogIt): """A class designed to be a decorator of any method of a :class:`tango.DeviceImpl` subclass. The idea is to log the entrance and exit of any decorated method as INFO level records. Example:: class MyDevice(tango.Device_4Impl): @tango.InfoIt() def read_Current(self, attr): attr.set_value(self._current, 1) All log messages generated by this class have INFO level. The constructor receives three optional arguments: * show_args - shows method arguments in log message (defaults to False) * show_kwargs - shows keyword method arguments in log message (defaults to False) * show_ret - shows return value in log message (defaults to False) """ def is_enabled(self, d): return d.get_logger().is_info_enabled() def get_log_func(self, d): return d.info_stream class WarnIt(LogIt): """A class designed to be a decorator of any method of a :class:`tango.DeviceImpl` subclass. The idea is to log the entrance and exit of any decorated method as WARN level records. Example:: class MyDevice(tango.Device_4Impl): @tango.WarnIt() def read_Current(self, attr): attr.set_value(self._current, 1) All log messages generated by this class have WARN level. The constructor receives three optional arguments: * show_args - shows method arguments in log message (defaults to False) * show_kwargs - shows keyword method arguments in log message (defaults to False) * show_ret - shows return value in log message (defaults to False) """ def is_enabled(self, d): return d.get_logger().is_warn_enabled() def get_log_func(self, d): return d.warn_stream class ErrorIt(LogIt): """A class designed to be a decorator of any method of a :class:`tango.DeviceImpl` subclass. The idea is to log the entrance and exit of any decorated method as ERROR level records. Example:: class MyDevice(tango.Device_4Impl): @tango.ErrorIt() def read_Current(self, attr): attr.set_value(self._current, 1) All log messages generated by this class have ERROR level. The constructor receives three optional arguments: * show_args - shows method arguments in log message (defaults to False) * show_kwargs - shows keyword method arguments in log message (defaults to False) * show_ret - shows return value in log message (defaults to False) """ def is_enabled(self, d): return d.get_logger().is_error_enabled() def get_log_func(self, d): return d.error_stream class FatalIt(LogIt): """A class designed to be a decorator of any method of a :class:`tango.DeviceImpl` subclass. The idea is to log the entrance and exit of any decorated method as FATAL level records. Example:: class MyDevice(tango.Device_4Impl): @tango.FatalIt() def read_Current(self, attr): attr.set_value(self._current, 1) All log messages generated by this class have FATAL level. The constructor receives three optional arguments: * show_args - shows method arguments in log message (defaults to False) * show_kwargs - shows keyword method arguments in log message (defaults to False) * show_ret - shows return value in log message (defaults to False) """ def is_enabled(self, d): return d.get_logger().is_fatal_enabled() def get_log_func(self, d): return d.fatal_stream pytango-9.2.2/tango/pipe.py000066400000000000000000000122711316324373100156320ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ __all__ = ['PipeConfig'] from ._tango import Pipe, PipeWriteType, UserDefaultPipeProp, \ CmdArgType, DevState, DispLevel, constants from .utils import scalar_to_array_type, TO_TANGO_TYPE, \ is_non_str_seq, is_pure_str, is_integer, is_number from .utils import document_method as __document_method class PipeConfig(object): """ This class represents the python interface for the Tango IDL object PipeConfig.""" def __init__(self): self.name = '' self.description = '' self.label = '' self.level = DispLevel.OPERATOR self.writable = PipeWriteType.PIPE_READ self.extensions = [] def __get_pipe_type_simple(obj): if is_non_str_seq(obj): if len(obj) == 2 and \ is_pure_str(obj[0]) and \ (is_non_str_seq(obj[1]) or isinstance(obj[1], dict)): tg_type = CmdArgType.DevPipeBlob else: tg_type = __get_pipe_type(obj[0]) tg_type = scalar_to_array_type(tg_type) elif is_pure_str(obj): tg_type = CmdArgType.DevString elif isinstance(obj, DevState): tg_type = CmdArgType.DevState elif isinstance(obj, bool): tg_type = CmdArgType.DevBoolean elif is_integer(obj): tg_type = CmdArgType.DevLong64 elif is_number(obj): tg_type = CmdArgType.DevDouble else: raise ValueError('Cannot determine object tango type') return tg_type def __get_pipe_type_numpy_support(obj): try: ndim, dtype = obj.ndim, str(obj.dtype) except AttributeError: return __get_pipe_type_simple(obj) if ndim > 1: raise TypeError('cannot translate numpy array with {0} ' 'dimensions to tango type'.format(obj.ndim)) tg_type = TO_TANGO_TYPE[dtype] if ndim > 0: tg_type = scalar_to_array_type(dtype) return tg_type def __get_tango_type(dtype): if is_non_str_seq(dtype): tg_type = dtype[0] if is_non_str_seq(tg_type): raise TypeError("Pipe doesn't support 2D data") tg_type = TO_TANGO_TYPE[tg_type] tg_type = scalar_to_array_type(tg_type) else: tg_type = TO_TANGO_TYPE[dtype] return tg_type def __get_pipe_type(obj, dtype=None): if dtype is not None: return __get_tango_type(dtype) if constants.NUMPY_SUPPORT: return __get_pipe_type_numpy_support(obj) return __get_pipe_type_simple(obj) def __sanatize_pipe_element(elem): if isinstance(elem, dict): result = dict(elem) else: result = dict(name=elem[0], value=elem[1]) result['value'] = value = result.get('value', result.pop('blob', None)) result['dtype'] = dtype = __get_pipe_type(value, dtype=result.get('dtype')) if dtype == CmdArgType.DevPipeBlob: result['value'] = value[0], __sanatize_pipe_blob(value[1]) return result def __sanatize_pipe_blob(blob): if isinstance(blob, dict): return [__sanatize_pipe_element((k, v)) for k, v in blob.items()] else: return [__sanatize_pipe_element(elem) for elem in blob] def __Pipe__set_value(self, value): """ Set the pipe value. Check ref:`pipe data types `: for more information on which values are supported """ root_blob_name, blob = value self.set_root_blob_name(root_blob_name) value = __sanatize_pipe_blob(blob) self._set_value(value) def __Pipe__get_value(self): return (self.get_root_blob_name(), self._get_value()) def __init_Pipe(): Pipe.set_value = __Pipe__set_value def __doc_UserDefaultPipeProp(): def document_method(method_name, desc, append=True): return __document_method(UserDefaultPipeProp, method_name, desc, append) UserDefaultPipeProp.__doc__ = """ User class to set pipe default properties. This class is used to set pipe default properties. Three levels of pipe properties setting are implemented within Tango. The highest property setting level is the database. Then the user default (set using this UserDefaultPipeProp class) and finally a Tango library default value """ document_method("set_label", """ set_label(self, def_label) -> None Set default label property. Parameters : - def_label : (str) the user default label property Return : None """) document_method("set_description", """ set_description(self, def_description) -> None Set default description property. Parameters : - def_description : (str) the user default description property Return : None """) def pipe_init(doc=True): __init_Pipe() if doc: __doc_UserDefaultPipeProp() pytango-9.2.2/tango/pipe_data.py000066400000000000000000000164451316324373100166320ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ """ This is an internal PyTango module. """ from __future__ import with_statement from __future__ import print_function __all__ = ["PipeData"] __docformat__ = "restructuredtext" import inspect from ._tango import Except, DispLevel, Pipe, PipeWriteType, UserDefaultPipeProp from .utils import is_non_str_seq, is_pure_str class PipeData(object): """A helper class that contains the same information one of the items in DeviceClass.pipe_list but in object form""" def __init__(self, name, class_name, pipe_info=None): self.class_name = class_name self.pipe_name = name self.pipe_write = PipeWriteType.PIPE_READ self.display_level = DispLevel.OPERATOR if name is None: self.read_method_name = None self.write_method_name = None self.is_allowed_name = None else: self.read_method_name = "read_" + name self.write_method_name = "write_" + name self.is_allowed_name = "is_" + name + "_allowed" self.pipe_class = None self.pipe_args = [] self.pipe_prop = None if pipe_info is not None: self.from_pipe_info(pipe_info) @classmethod def from_dict(cls, pipe_dict): pipe_dict = dict(pipe_dict) name = pipe_dict.pop('name', None) class_name = pipe_dict.pop('class_name', None) self = cls(name, class_name) self.build_from_dict(pipe_dict) return self def build_from_dict(self, pipe_dict): self.display_level = pipe_dict.pop('display_level', DispLevel.OPERATOR) is_access_explicit = "access" in pipe_dict if is_access_explicit: self.pipe_write = pipe_dict.pop('access') else: # access is defined by which methods were defined r_explicit = "fread" in pipe_dict or "fget" in pipe_dict w_explicit = "fwrite" in pipe_dict or "fset" in pipe_dict if r_explicit and w_explicit: self.pipe_write = PipeWriteType.PIPE_READ_WRITE else: self.pipe_write = PipeWriteType.PIPE_READ fread = pipe_dict.pop('fget', pipe_dict.pop('fread', None)) if fread is not None: if is_pure_str(fread): self.read_method_name = fread elif inspect.isroutine(fread): self.read_method_name = fread.__name__ fwrite = pipe_dict.pop('fset', pipe_dict.pop('fwrite', None)) if fwrite is not None: if is_pure_str(fwrite): self.write_method_name = fwrite elif inspect.isroutine(fwrite): self.write_method_name = fwrite.__name__ fisallowed = pipe_dict.pop('fisallowed', None) if fisallowed is not None: if is_pure_str(fisallowed): self.is_allowed_name = fisallowed elif inspect.isroutine(fisallowed): self.is_allowed_name = fisallowed.__name__ self.pipe_class = pipe_dict.pop("klass", Pipe) self.pipe_args.extend((self.pipe_name, self.display_level, self.pipe_write)) if len(pipe_dict): self.pipe_prop = self.__create_user_default_pipe_prop(pipe_dict) return self def _set_name(self, name): old_name = self.pipe_name self.pipe_name = name self.pipe_args[0] = name if old_name is None: if self.read_method_name is None: self.read_method_name = "read_" + name if self.write_method_name is None: self.write_method_name = "write_" + name if self.is_allowed_name is None: self.is_allowed_name = "is_" + name + "_allowed" def __throw_exception(self, msg, meth="create_pipe()"): Except.throw_exception("PyDs_WrongPipeDefinition", msg, meth) def __create_user_default_pipe_prop(self, extra_info): """for internal usage only""" p = UserDefaultPipeProp() doc = extra_info.pop('doc', None) if doc is not None: extra_info['description'] = doc for k, v in extra_info.items(): k_lower = k.lower() method_name = "set_%s" % k_lower.replace(' ', '_') if hasattr(p, method_name): method = getattr(p, method_name) method(str(v)) else: msg = "Wrong definition of pipe. " \ "The object extra information '%s' " \ "is not recognized!" % (k,) Except.throw_exception("PyDs_WrongPipeDefinition", msg, "create_user_default_pipe_prop()") return p def from_pipe_info(self, pipe_info): name = self.class_name pipe_name = self.pipe_name throw_ex = self.__throw_exception # check for well defined pipe info # check parameter if not is_non_str_seq(pipe_info): throw_ex("Wrong data type for value for describing pipe %s in " "class %s\nMust be a sequence with 1 or 2 elements" % (pipe_name, name)) if len(pipe_info) < 1 or len(pipe_info) > 2: throw_ex("Wrong number of argument for describing pipe %s in " "class %s\nMust be a sequence with 1 or 2 elements" % (pipe_name, name)) extra_info = {} if len(pipe_info) == 2: # pipe_info[1] must be a dictionary # extra_info = pipe_info[1], with all the keys lowercase for k, v in pipe_info[1].items(): extra_info[k.lower()] = v pipe_info = pipe_info[0] # get write type try: self.pipe_write = PipeWriteType(pipe_info) except: throw_ex("Wrong data write type in pipe argument for " "pipe %s in class %s\nPipe write type must be a " "tango.PipeWriteType" % (pipe_name, name)) try: self.display_level = DispLevel(extra_info.get("display level", DispLevel.OPERATOR)) except: throw_ex("Wrong display level in pipe information for " "pipe %s in class %s\nPipe information for " "display level is not a tango.DispLevel" % (pipe_name, name)) self.pipe_class = extra_info.get("klass", Pipe) self.pipe_args.extend((self.pipe_name, self.display_level, self.pipe_write)) pipe_prop = None if extra_info: pipe_prop = self.__create_user_default_pipe_prop(extra_info) self.pipe_prop = pipe_prop def to_pipe(self): pipe = self.pipe_class(*self.pipe_args) if self.pipe_prop is not None: pipe.set_default_properties(self.pipe_prop) return pipe pytango-9.2.2/tango/pytango_init.py000066400000000000000000000075321316324373100174050ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ """ This is an internal PyTango module. """ __all__ = ['init'] __docformat__ = "restructuredtext" from .attribute_proxy import attribute_proxy_init from .base_types import base_types_init from .exception import exception_init from .callback import callback_init from .api_util import api_util_init from .encoded_attribute import encoded_attribute_init from .connection import connection_init from .db import db_init from .device_attribute import device_attribute_init from .device_class import device_class_init from .device_data import device_data_init from .device_proxy import device_proxy_init from .device_server import device_server_init from .group import group_init from .group_reply import group_reply_init from .group_reply_list import group_reply_list_init from .pytango_pprint import pytango_pprint_init from .pyutil import pyutil_init from .time_val import time_val_init from .auto_monitor import auto_monitor_init from .pipe import pipe_init from ._tango import constants from ._tango import _get_tango_lib_release __INITIALIZED = False __DOC = True def init_constants(): import sys import platform tg_ver = tuple(map(int, constants.TgLibVers.split("."))) tg_ver_str = "0x%02d%02d%02d00" % (tg_ver[0], tg_ver[1], tg_ver[2]) constants.TANGO_VERSION_HEX = int(tg_ver_str, 16) BOOST_VERSION = ".".join(map(str, (constants.BOOST_MAJOR_VERSION, constants.BOOST_MINOR_VERSION, constants.BOOST_PATCH_VERSION))) constants.BOOST_VERSION = BOOST_VERSION class Compile(object): PY_VERSION = constants.PY_VERSION TANGO_VERSION = constants.TANGO_VERSION BOOST_VERSION = constants.BOOST_VERSION NUMPY_VERSION = constants.NUMPY_VERSION # UNAME = tuple(map(str, json.loads(constants.UNAME))) tg_rt_ver_nb = _get_tango_lib_release() tg_rt_major_ver = tg_rt_ver_nb // 100 tg_rt_minor_ver = tg_rt_ver_nb // 10 % 10 tg_rt_patch_ver = tg_rt_ver_nb % 10 tg_rt_ver = ".".join(map(str, (tg_rt_major_ver, tg_rt_minor_ver, tg_rt_patch_ver))) class Runtime(object): PY_VERSION = ".".join(map(str, sys.version_info[:3])) TANGO_VERSION = tg_rt_ver BOOST_VERSION = '0.0.0' if constants.NUMPY_SUPPORT: import numpy NUMPY_VERSION = numpy.__version__ else: NUMPY_VERSION = None UNAME = platform.uname() constants.Compile = Compile constants.Runtime = Runtime def init(): global __INITIALIZED if __INITIALIZED: return global __DOC doc = __DOC init_constants() base_types_init(doc=doc) exception_init(doc=doc) callback_init(doc=doc) api_util_init(doc=doc) encoded_attribute_init(doc=doc) connection_init(doc=doc) db_init(doc=doc) device_attribute_init(doc=doc) device_class_init(doc=doc) device_data_init(doc=doc) device_proxy_init(doc=doc) device_server_init(doc=doc) group_init(doc=doc) group_reply_init(doc=doc) group_reply_list_init(doc=doc) pytango_pprint_init(doc=doc) pyutil_init(doc=doc) time_val_init(doc=doc) auto_monitor_init(doc=doc) pipe_init(doc=doc) # must come last: depends on device_proxy.init() attribute_proxy_init(doc=doc) __INITIALIZED = True pytango-9.2.2/tango/pytango_pprint.py000066400000000000000000000142121316324373100177470ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ """ This is an internal PyTango module. """ __all__ = ["pytango_pprint_init"] __docformat__ = "restructuredtext" from ._tango import (StdStringVector, StdLongVector, CommandInfoList, AttributeInfoList, AttributeInfoListEx, PipeInfoList, DeviceDataHistoryList, GroupReplyList, GroupAttrReplyList, GroupCmdReplyList, DbData, DbDevInfos, DbDevExportInfos, DbDevImportInfos, DbHistoryList, LockerInfo, DevCommandInfo, AttributeDimension, CommandInfo, PipeInfo, DeviceInfo, DeviceAttributeConfig, AttributeInfo, AttributeAlarmInfo, ChangeEventInfo, PeriodicEventInfo, ArchiveEventInfo, AttributeEventInfo, AttributeInfoEx, DeviceAttribute, DeviceAttributeHistory, DeviceData, DeviceDataHistory, DevicePipe, DbDatum, DbDevInfo, DbDevImportInfo, DbDevExportInfo, DbServerInfo, GroupReply, GroupAttrReply, GroupCmdReply, DevError, EventData, AttrConfEventData, DataReadyEventData, TimeVal, DevFailed, CmdArgType) from .device_server import AttributeAlarm, EventProperties from .device_server import ChangeEventProp, PeriodicEventProp, ArchiveEventProp from .device_server import AttributeConfig, AttributeConfig_2 from .device_server import AttributeConfig_3, AttributeConfig_5 import collections def __inc_param(obj, name): ret = not name.startswith('_') ret &= name not in ('except_flags',) ret &= not isinstance(getattr(obj, name), collections.Callable) return ret def __single_param(obj, param_name, f=repr, fmt='%s = %s'): param_value = getattr(obj, param_name) if param_name is 'data_type': param_value = CmdArgType.values.get(param_value, param_value) return fmt % (param_name, f(param_value)) def __struct_params_s(obj, separator=', ', f=repr, fmt='%s = %s'): """method wrapper for printing all elements of a struct""" s = separator.join([__single_param(obj, n, f, fmt) for n in dir(obj) if __inc_param(obj, n)]) return s def __struct_params_repr(obj): """method wrapper for representing all elements of a struct""" return __struct_params_s(obj) def __struct_params_str(obj, fmt, f=repr): """method wrapper for printing all elements of a struct.""" return __struct_params_s(obj, '\n', f=f, fmt=fmt) def __repr__Struct(self): """repr method for struct""" return '%s(%s)' % (self.__class__.__name__, __struct_params_repr(self)) def __str__Struct_Helper(self, f=repr): """str method for struct""" attrs = [n for n in dir(self) if __inc_param(self, n)] fmt = attrs and '%%%ds = %%s' % max(map(len, attrs)) or "%s = %s" return '%s[\n%s]\n' % (self.__class__.__name__, __struct_params_str(self, fmt, f)) def __str__Struct(self): return __str__Struct_Helper(self, f=repr) def __str__Struct_extra(self): return __str__Struct_Helper(self, f=str) def __registerSeqStr(): """helper function to make internal sequences printable""" _SeqStr = lambda self: (self and "[%s]" % (", ".join(map(repr, self)))) or "[]" _SeqRepr = lambda self: (self and "[%s]" % (", ".join(map(repr, self)))) or "[]" seqs = (StdStringVector, StdLongVector, CommandInfoList, AttributeInfoList, AttributeInfoListEx, PipeInfoList, DeviceDataHistoryList, GroupReplyList, GroupAttrReplyList, GroupCmdReplyList, DbData, DbDevInfos, DbDevExportInfos, DbDevImportInfos, DbHistoryList) for seq in seqs: seq.__str__ = _SeqStr seq.__repr__ = _SeqRepr def __str__DevFailed(self): if isinstance(self.args, collections.Sequence): return 'DevFailed[\n%s]' % '\n'.join(map(str, self.args)) return 'DevFailed[%s]' % (self.args) def __repr__DevFailed(self): return 'DevFailed(args = %s)' % repr(self.args) def __str__DevError(self): desc = self.desc.replace("\n", "\n ") s = """DevError[ desc = %s origin = %s reason = %s severity = %s]\n""" % (desc, self.origin, self.reason, self.severity) return s def __registerStructStr(): """helper method to register str and repr methods for structures""" structs = (LockerInfo, DevCommandInfo, AttributeDimension, CommandInfo, DeviceInfo, DeviceAttributeConfig, AttributeInfo, AttributeAlarmInfo, ChangeEventInfo, PeriodicEventInfo, ArchiveEventInfo, AttributeEventInfo, AttributeInfoEx, PipeInfo, DeviceAttribute, DeviceAttributeHistory, DeviceData, DeviceDataHistory, DevicePipe, DbDatum, DbDevInfo, DbDevImportInfo, DbDevExportInfo, DbServerInfo, GroupReply, GroupAttrReply, GroupCmdReply, DevError, EventData, AttrConfEventData, DataReadyEventData, AttributeConfig, AttributeConfig_2, AttributeConfig_3, AttributeConfig_5, ChangeEventProp, PeriodicEventProp, ArchiveEventProp, AttributeAlarm, EventProperties) for struct in structs: struct.__str__ = __str__Struct struct.__repr__ = __repr__Struct # special case for TimeVal: it already has a str representation itself TimeVal.__repr__ = __repr__Struct # special case for DevFailed: we want a better pretty print # also, because it is an Exception it has the message attribute which # generates a Deprecation warning in python 2.6 DevFailed.__str__ = __str__DevFailed DevFailed.__repr__ = __repr__DevFailed DevError.__str__ = __str__DevError def pytango_pprint_init(doc=True): __registerSeqStr() __registerStructStr() pytango-9.2.2/tango/pyutil.py000066400000000000000000000545701316324373100162330ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ """ This is an internal PyTango module. """ __all__ = ["Util", "pyutil_init"] __docformat__ = "restructuredtext" import os import copy from ._tango import Util, Except, DevFailed, DbDevInfo from .utils import document_method as __document_method # from utils import document_static_method as __document_static_method from .globals import class_list, cpp_class_list, get_constructed_classes import collections def __simplify_device_name(dev_name): if dev_name.startswith("tango://"): dev_name = dev_name[8:] if dev_name.count("/") > 2: dev_name = dev_name[dev_name.index("/") + 1:] return dev_name.lower() # # Methods on Util # def __Util__get_class_list(self): """ get_class_list(self) -> seq Returns a list of objects of inheriting from DeviceClass Parameters : None Return : (seq) a list of objects of inheriting from DeviceClass""" return get_constructed_classes() def __Util__create_device(self, klass_name, device_name, alias=None, cb=None): """ create_device(self, klass_name, device_name, alias=None, cb=None) -> None Creates a new device of the given class in the database, creates a new DeviceImpl for it and calls init_device (just like it is done for existing devices when the DS starts up) An optional parameter callback is called AFTER the device is registered in the database and BEFORE the init_device for the newly created device is called Throws tango.DevFailed: - the device name exists already or - the given class is not registered for this DS. - the cb is not a callable New in PyTango 7.1.2 Parameters : - klass_name : (str) the device class name - device_name : (str) the device name - alias : (str) optional alias. Default value is None meaning do not create device alias - cb : (callable) a callback that is called AFTER the device is registered in the database and BEFORE the init_device for the newly created device is called. Typically you may want to put device and/or attribute properties in the database here. The callback must receive a parameter: device name (str). Default value is None meaning no callback Return : None""" if cb is not None and not isinstance(cb, collections.Callable): Except.throw_exception("PyAPI_InvalidParameter", "The optional cb parameter must be a python callable", "Util.create_device") db = self.get_database() device_name = __simplify_device_name(device_name) device_exists = True try: db.import_device(device_name) except DevFailed as df: device_exists = not df[0].reason == "DB_DeviceNotDefined" # 1 - Make sure device name doesn't exist already in the database if device_exists: Except.throw_exception("PyAPI_DeviceAlreadyDefined", "The device %s is already defined in the database" % device_name, "Util.create_device") # 2 - Make sure the device class is known klass_list = self.get_class_list() klass = None for k in klass_list: name = k.get_name() if name == klass_name: klass = k break if klass is None: Except.throw_exception("PyAPI_UnknownDeviceClass", "The device class %s could not be found" % klass_name, "Util.create_device") # 3 - Create entry in the database (with alias if necessary) dev_info = DbDevInfo() dev_info.name = device_name dev_info._class = klass_name dev_info.server = self.get_ds_name() db.add_device(dev_info) if alias is not None: db.put_device_alias(device_name, alias) # from this point on, if anything wrong happens we need to clean the database try: # 4 - run the callback which tipically is used to initialize # device and/or attribute properties in the database if cb is not None: cb(device_name) # 5 - Initialize device object on this server k.device_factory([device_name]) except: try: if alias is not None: db.delete_device_alias(alias) except: pass db.delete_device(device_name) def __Util__delete_device(self, klass_name, device_name): """ delete_device(self, klass_name, device_name) -> None Deletes an existing device from the database and from this running server Throws tango.DevFailed: - the device name doesn't exist in the database - the device name doesn't exist in this DS. New in PyTango 7.1.2 Parameters : - klass_name : (str) the device class name - device_name : (str) the device name Return : None""" db = self.get_database() device_name = __simplify_device_name(device_name) device_exists = True try: db.import_device(device_name) except DevFailed as df: device_exists = not df[0].reason == "DB_DeviceNotDefined" # 1 - Make sure device name exists in the database if not device_exists: Except.throw_exception("PyAPI_DeviceNotDefined", "The device %s is not defined in the database" % device_name, "Util.delete_device") # 2 - Make sure device name is defined in this server class_device_name = "%s::%s" % (klass_name, device_name) ds = self.get_dserver_device() dev_names = ds.query_device() device_exists = False for dev_name in dev_names: p = dev_name.index("::") dev_name = dev_name[:p] + dev_name[p:].lower() if dev_name == class_device_name: device_exists = True break if not device_exists: Except.throw_exception("PyAPI_DeviceNotDefinedInServer", "The device %s is not defined in this server" % class_device_name, "Util.delete_device") db.delete_device(device_name) dimpl = self.get_device_by_name(device_name) dc = dimpl.get_device_class() dc.device_destroyer(device_name) def __Util__init__(self, args): args = copy.copy(args) args[0] = os.path.splitext(args[0])[0] Util.__init_orig__(self, args) def __Util__add_TgClass(self, klass_device_class, klass_device, device_class_name=None): """Register a new python tango class. Example:: util.add_TgClass(MotorClass, Motor) util.add_TgClass(MotorClass, Motor, 'Motor') # equivalent to previous line .. deprecated:: 7.1.2 Use :meth:`tango.Util.add_class` instead.""" if device_class_name is None: device_class_name = klass_device.__name__ class_list.append((klass_device_class, klass_device, device_class_name)) def __Util__add_Cpp_TgClass(self, device_class_name, tango_device_class_name): """Register a new C++ tango class. If there is a shared library file called MotorClass.so which contains a MotorClass class and a _create_MotorClass_class method. Example:: util.add_Cpp_TgClass('MotorClass', 'Motor') .. note:: the parameter 'device_class_name' must match the shared library name. .. deprecated:: 7.1.2 Use :meth:`tango.Util.add_class` instead.""" cpp_class_list.append((device_class_name, tango_device_class_name)) def __Util__add_class(self, *args, **kwargs): """ add_class(self, class, class, language="python") -> None Register a new tango class ('python' or 'c++'). If language is 'python' then args must be the same as :meth:`tango.Util.add_TgClass`. Otherwise, args should be the ones in :meth:`tango.Util.add_Cpp_TgClass`. Example:: util.add_class(MotorClass, Motor) util.add_class('CounterClass', 'Counter', language='c++') New in PyTango 7.1.2""" language = kwargs.get("language", "python") f = self.add_TgClass if language != "python": f = f = self.add_Cpp_TgClass return f(*args) def __init_Util(): Util.__init_orig__ = Util.__init__ Util.__init__ = __Util__init__ Util.add_TgClass = __Util__add_TgClass Util.add_Cpp_TgClass = __Util__add_Cpp_TgClass Util.add_class = __Util__add_class Util.get_class_list = __Util__get_class_list Util.create_device = __Util__create_device Util.delete_device = __Util__delete_device def __doc_Util(): Util.__doc__ = """\ This class is a used to store TANGO device server process data and to provide the user with a set of utilities method. This class is implemented using the singleton design pattern. Therefore a device server process can have only one instance of this class and its constructor is not public. Example:: util = tango.Util.instance() print(util.get_host_name()) """ def document_method(method_name, desc, append=True): return __document_method(Util, method_name, desc, append) # def document_static_method(method_name, desc, append=True): # return __document_static_method(_Util, method_name, desc, append) # document_static_method("instance", """ # instance(exit = True) -> Util # # Static method that gets the singleton object reference. # If the class has not been initialised with it's init method, # this method prints a message and aborts the device server process # # Parameters : # - exit : (bool) # # Return : (Util) the tango Util object # """ ) document_method("set_trace_level", """ set_trace_level(self, level) -> None Set the process trace level. Parameters : - level : (int) the new process level Return : None """) document_method("get_trace_level", """ get_trace_level(self) -> int Get the process trace level. Parameters : None Return : (int) the process trace level. """) document_method("get_ds_inst_name", """ get_ds_inst_name(self) -> str Get a COPY of the device server instance name. Parameters : None Return : (str) a COPY of the device server instance name. New in PyTango 3.0.4 """) document_method("get_ds_exec_name", """ get_ds_exec_name(self) -> str Get a COPY of the device server executable name. Parameters : None Return : (str) a COPY of the device server executable name. New in PyTango 3.0.4 """) document_method("get_ds_name", """ get_ds_name(self) -> str Get the device server name. The device server name is the / Parameters : None Return : (str) device server name New in PyTango 3.0.4 """) document_method("get_host_name", """ get_host_name(self) -> str Get the host name where the device server process is running. Parameters : None Return : (str) the host name where the device server process is running New in PyTango 3.0.4 """) document_method("get_pid_str", """ get_pid_str(self) -> str Get the device server process identifier as a string. Parameters : None Return : (str) the device server process identifier as a string New in PyTango 3.0.4 """) document_method("get_pid", """ get_pid(self) -> TangoSys_Pid Get the device server process identifier. Parameters : None Return : (int) the device server process identifier """) document_method("get_tango_lib_release", """ get_tango_lib_release(self) -> int Get the TANGO library version number. Parameters : None Return : (int) The Tango library release number coded in 3 digits (for instance 550,551,552,600,....) """) document_method("get_version_str", """ get_version_str(self) -> str Get the IDL TANGO version. Parameters : None Return : (str) the IDL TANGO version. New in PyTango 3.0.4 """) document_method("get_server_version", """ get_server_version(self) -> str Get the device server version. Parameters : None Return : (str) the device server version. """) document_method("set_server_version", """ set_server_version(self, vers) -> None Set the device server version. Parameters : - vers : (str) the device server version Return : None """) document_method("set_serial_model", """ set_serial_model(self, ser) -> None Set the serialization model. Parameters : - ser : (SerialModel) the new serialization model. The serialization model must be one of BY_DEVICE, BY_CLASS, BY_PROCESS or NO_SYNC Return : None """) document_method("get_serial_model", """ get_serial_model(self) ->SerialModel Get the serialization model. Parameters : None Return : (SerialModel) the serialization model """) document_method("connect_db", """ connect_db(self) -> None Connect the process to the TANGO database. If the connection to the database failed, a message is displayed on the screen and the process is aborted Parameters : None Return : None """) document_method("reset_filedatabase", """ reset_filedatabase(self) -> None Reread the file database. Parameters : None Return : None """) document_method("unregister_server", """ unregister_server(self) -> None Unregister a device server process from the TANGO database. Parameters : None Return : None """) document_method("get_dserver_device", """ get_dserver_device(self) -> DServer Get a reference to the dserver device attached to the device server process. Parameters : None Return : (DServer) the dserver device attached to the device server process New in PyTango 7.0.0 """) document_method("server_init", """ server_init(self, with_window = False) -> None Initialize all the device server pattern(s) embedded in a device server process. Parameters : - with_window : (bool) default value is False Return : None Throws : DevFailed If the device pattern initialistaion failed """) document_method("server_run", """ server_run(self) -> None Run the CORBA event loop. This method runs the CORBA event loop. For UNIX or Linux operating system, this method does not return. For Windows in a non-console mode, this method start a thread which enter the CORBA event loop. Parameters : None Return : None """) document_method("trigger_cmd_polling", """ trigger_cmd_polling(self, dev, name) -> None Trigger polling for polled command. This method send the order to the polling thread to poll one object registered with an update period defined as "externally triggerred" Parameters : - dev : (DeviceImpl) the TANGO device - name : (str) the command name which must be polled Return : None Throws : DevFailed If the call failed """) document_method("trigger_attr_polling", """ trigger_attr_polling(self, dev, name) -> None Trigger polling for polled attribute. This method send the order to the polling thread to poll one object registered with an update period defined as "externally triggerred" Parameters : - dev : (DeviceImpl) the TANGO device - name : (str) the attribute name which must be polled Return : None """) document_method("set_polling_threads_pool_size", """ set_polling_threads_pool_size(self, thread_nb) -> None Set the polling threads pool size. Parameters : - thread_nb : (int) the maximun number of threads in the polling threads pool Return : None New in PyTango 7.0.0 """) document_method("get_polling_threads_pool_size", """ get_polling_threads_pool_size(self) -> int Get the polling threads pool size. Parameters : None Return : (int) the maximun number of threads in the polling threads pool """) document_method("is_svr_starting", """ is_svr_starting(self) -> bool Check if the device server process is in its starting phase Parameters : None Return : (bool) True if the server is in its starting phase New in PyTango 8.0.0 """) document_method("is_svr_shutting_down", """ is_svr_shutting_down(self) -> bool Check if the device server process is in its shutting down sequence Parameters : None Return : (bool) True if the server is in its shutting down phase. New in PyTango 8.0.0 """) document_method("is_device_restarting", """ is_device_restarting(self, (str)dev_name) -> bool Check if the device is actually restarted by the device server process admin device with its DevRestart command Parameters : dev_name : (str) device name Return : (bool) True if the device is restarting. New in PyTango 8.0.0 """) document_method("get_sub_dev_diag", """ get_sub_dev_diag(self) -> SubDevDiag Get the internal sub device manager Parameters : None Return : (SubDevDiag) the sub device manager New in PyTango 7.0.0 """) document_method("reset_filedatabase", """ reset_filedatabase(self) -> None Reread the file database Parameters : None Return : None New in PyTango 7.0.0 """) document_method("get_database", """ get_database(self) -> Database Get a reference to the TANGO database object Parameters : None Return : (Database) the database New in PyTango 7.0.0 """) document_method("unregister_server", """ unregister_server(self) -> None Unregister a device server process from the TANGO database. If the database call fails, a message is displayed on the screen and the process is aborted Parameters : None Return : None New in PyTango 7.0.0 """) document_method("get_device_list_by_class", """ get_device_list_by_class(self, class_name) -> sequence Get the list of device references for a given TANGO class. Return the list of references for all devices served by one implementation of the TANGO device pattern implemented in the process. Parameters : - class_name : (str) The TANGO device class name Return : (sequence) The device reference list New in PyTango 7.0.0 """) document_method("get_device_by_name", """ get_device_by_name(self, dev_name) -> DeviceImpl Get a device reference from its name Parameters : - dev_name : (str) The TANGO device name Return : (DeviceImpl) The device reference New in PyTango 7.0.0 """) document_method("get_dserver_device", """ get_dserver_device(self) -> DServer Get a reference to the dserver device attached to the device server process Parameters : None Return : (DServer) A reference to the dserver device New in PyTango 7.0.0 """) document_method("get_device_list", """ get_device_list(self) -> sequence Get device list from name. It is possible to use a wild card ('*') in the name parameter (e.g. "*", "/tango/tangotest/n*", ...) Parameters : None Return : (sequence) the list of device objects New in PyTango 7.0.0 """) document_method("server_set_event_loop", """ server_set_event_loop(self, event_loop) -> None This method registers an event loop function in a Tango server. This function will be called by the process main thread in an infinite loop The process will not use the classical ORB blocking event loop. It is the user responsability to code this function in a way that it implements some kind of blocking in order not to load the computer CPU. The following piece of code is an example of how you can use this feature:: _LOOP_NB = 1 def looping(): global _LOOP_NB print "looping", _LOOP_NB time.sleep(0.1) _LOOP_NB += 1 return _LOOP_NB > 100 def main(): py = tango.Util(sys.argv) # ... U = tango.Util.instance() U.server_set_event_loop(looping) U.server_init() U.server_run() Parameters : None Return : None New in PyTango 8.1.0 """) # document_static_method("init_python", """ # init_python() -> None # # Static method # For internal usage. # # Parameters : None # Return : None # """ ) def pyutil_init(doc=True): __init_Util() if doc: __doc_Util() pytango-9.2.2/tango/release.py000066400000000000000000000053501316324373100163150ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ """ This is an internal PyTango module. """ __all__ = ["Release"] __docformat__ = "restructuredtext" class Release: """Summarize release information as class attributes. Release information: - name: (str) package name - version_info: (tuple) The five components of the version number: major, minor, micro, releaselevel, and serial. - version: (str) package version in format .. - release: (str) pre-release, post-release or development release; it is empty for final releases. - version_long: (str) package version in format .. - version_description: (str) short description for the current version - version_number: (int) *100 + *10 + - description : (str) package description - long_description: (str) longer package description - authors: (dict>) package authors - url: (str) package url - download_url: (str) package download url - platform: (seq) list of available platforms - keywords: (seq) list of keywords - license: (str) the license """ name = 'PyTango' version_info = (9, 2, 2) version = '.'.join(map(str, version_info[:3])) release = ''.join(map(str, version_info[3:])) separator = '.' if 'dev' in release or 'post' in release else '' version_long = version + separator + release version_description = 'This version implements the C++ Tango 9.2 API.' version_number = int(version.replace('.', '')) description = 'A python binding for the Tango control system' long_description = 'This module implements the Python Tango Device API ' \ 'mapping.' license = 'LGPL' authors = {'Coutinho': ('Tiago Coutinho', 'coutinho@esrf.fr')} author_lines = "\n".join(["%s <%s>" % x for x in authors.values()]) url = 'http://github.com/tango-cs/pytango' download_url = 'http://pypi.python.org/pypi/pytango' platform = ['Linux', 'Windows XP/Vista/7/8'] keywords = ['Tango', 'CORBA', 'binding'] pytango-9.2.2/tango/server.py000066400000000000000000001615241316324373100162110ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ """Server helper classes for writing Tango device servers.""" from __future__ import with_statement from __future__ import print_function from __future__ import absolute_import import sys import copy import inspect import logging import functools import traceback from ._tango import AttrDataFormat, AttrWriteType, CmdArgType, PipeWriteType from ._tango import DevFailed, GreenMode, SerialModel from .attr_data import AttrData from .pipe_data import PipeData from .device_class import DeviceClass from .device_server import LatestDeviceImpl from .utils import is_seq, is_non_str_seq from .utils import scalar_to_array_type, TO_TANGO_TYPE from .green import get_green_mode, get_executor from .pyutil import Util __all__ = ["DeviceMeta", "Device", "LatestDeviceImpl", "attribute", "command", "pipe", "device_property", "class_property", "run", "server_run", "Server"] API_VERSION = 2 # Worker access _WORKER = get_executor() def set_worker(worker): global _WORKER _WORKER = worker def get_worker(): return _WORKER # Helpers def _get_tango_type_format(dtype=None, dformat=None): if dformat is None: dformat = AttrDataFormat.SCALAR if is_non_str_seq(dtype): dtype = dtype[0] dformat = AttrDataFormat.SPECTRUM if is_non_str_seq(dtype): dtype = dtype[0] dformat = AttrDataFormat.IMAGE return TO_TANGO_TYPE[dtype], dformat def from_typeformat_to_type(dtype, dformat): if dformat == AttrDataFormat.SCALAR: return dtype elif dformat == AttrDataFormat.IMAGE: raise TypeError("Cannot translate IMAGE to tango type") return scalar_to_array_type(dtype) def set_complex_value(attr, value): is_tuple = isinstance(value, tuple) dtype, fmt = attr.get_data_type(), attr.get_data_format() if dtype == CmdArgType.DevEncoded: if is_tuple and len(value) == 4: attr.set_value_date_quality(*value) elif is_tuple and len(value) == 3 and is_non_str_seq(value[0]): attr.set_value_date_quality(value[0][0], value[0][1], *value[1:]) else: attr.set_value(*value) else: if is_tuple: if len(value) == 3: if fmt == AttrDataFormat.SCALAR: attr.set_value_date_quality(*value) elif fmt == AttrDataFormat.SPECTRUM: if is_seq(value[0]): attr.set_value_date_quality(*value) else: attr.set_value(value) else: if is_seq(value[0]) and is_seq(value[0][0]): attr.set_value_date_quality(*value) else: attr.set_value(value) else: attr.set_value(value) else: attr.set_value(value) def _get_wrapped_read_method(attribute, read_method): read_args = inspect.getargspec(read_method) nb_args = len(read_args.args) green_mode = attribute.read_green_mode if nb_args < 2: if green_mode == GreenMode.Synchronous: @functools.wraps(read_method) def read_attr(self, attr): ret = read_method(self) if not attr.get_value_flag() and ret is not None: set_complex_value(attr, ret) return ret else: @functools.wraps(read_method) def read_attr(self, attr): worker = get_worker() ret = worker.execute(read_method, self) if not attr.get_value_flag() and ret is not None: set_complex_value(attr, ret) return ret else: if green_mode == GreenMode.Synchronous: read_attr = read_method else: @functools.wraps(read_method) def read_attr(self, attr): return get_worker().execute(read_method, self, attr) return read_attr def __patch_read_method(tango_device_klass, attribute): """ Checks if method given by it's name for the given DeviceImpl class has the correct signature. If a read/write method doesn't have a parameter (the traditional Attribute), then the method is wrapped into another method which has correct parameter definition to make it work. :param tango_device_klass: a DeviceImpl class :type tango_device_klass: class :param attribute: the attribute data information :type attribute: AttrData """ read_method = getattr(attribute, "fget", None) if read_method: method_name = "__read_{0}__".format(attribute.attr_name) attribute.read_method_name = method_name else: method_name = attribute.read_method_name read_method = getattr(tango_device_klass, method_name) read_attr = _get_wrapped_read_method(attribute, read_method) method_name = "__read_{0}_wrapper__".format(attribute.attr_name) attribute.read_method_name = method_name setattr(tango_device_klass, method_name, read_attr) def _get_wrapped_write_method(attribute, write_method): green_mode = attribute.write_green_mode if green_mode == GreenMode.Synchronous: @functools.wraps(write_method) def write_attr(self, attr): value = attr.get_write_value() return write_method(self, value) else: @functools.wraps(write_method) def write_attr(self, attr): value = attr.get_write_value() return get_worker().execute(write_method, self, value) return write_attr def __patch_write_method(tango_device_klass, attribute): """ Checks if method given by it's name for the given DeviceImpl class has the correct signature. If a read/write method doesn't have a parameter (the traditional Attribute), then the method is wrapped into another method which has correct parameter definition to make it work. :param tango_device_klass: a DeviceImpl class :type tango_device_klass: class :param attribute: the attribute data information :type attribute: AttrData """ write_method = getattr(attribute, "fset", None) if write_method: method_name = "__write_{0}__".format(attribute.attr_name) attribute.write_method_name = method_name else: method_name = attribute.write_method_name write_method = getattr(tango_device_klass, method_name) write_attr = _get_wrapped_write_method(attribute, write_method) setattr(tango_device_klass, method_name, write_attr) def __patch_attr_methods(tango_device_klass, attribute): """ Checks if the read and write methods have the correct signature. If a read/write method doesn't have a parameter (the traditional Attribute), then the method is wrapped into another method to make this work. :param tango_device_klass: a DeviceImpl class :type tango_device_klass: class :param attribute: the attribute data information :type attribute: AttrData """ if attribute.attr_write in (AttrWriteType.READ, AttrWriteType.READ_WRITE): __patch_read_method(tango_device_klass, attribute) if attribute.attr_write in (AttrWriteType.WRITE, AttrWriteType.READ_WRITE): __patch_write_method(tango_device_klass, attribute) def _get_wrapped_pipe_read_method(pipe, read_method): read_args = inspect.getargspec(read_method) nb_args = len(read_args.args) green_mode = pipe.read_green_mode if nb_args < 2: if green_mode == GreenMode.Synchronous: @functools.wraps(read_method) def read_pipe(self, pipe): ret = read_method(self) if not pipe.get_value_flag() and ret is not None: pipe.set_value(pipe, ret) return ret else: @functools.wraps(read_method) def read_pipe(self, pipe): worker = get_worker() ret = worker.execute(read_method, self) if ret is not None: pipe.set_value(ret) return ret else: if green_mode == GreenMode.Synchronous: read_pipe = read_method else: @functools.wraps(read_method) def read_pipe(self, pipe): return get_worker().execute(read_method, self, pipe) return read_pipe def __patch_pipe_read_method(tango_device_klass, pipe): """ Checks if method given by it's name for the given DeviceImpl class has the correct signature. If a read/write method doesn't have a parameter (the traditional Pipe), then the method is wrapped into another method which has correct parameter definition to make it work. :param tango_device_klass: a DeviceImpl class :type tango_device_klass: class :param pipe: the pipe data information :type pipe: PipeData """ read_method = getattr(pipe, "fget", None) if read_method: method_name = "__read_{0}__".format(pipe.pipe_name) pipe.read_method_name = method_name else: method_name = pipe.read_method_name read_method = getattr(tango_device_klass, method_name) read_pipe = _get_wrapped_pipe_read_method(pipe, read_method) method_name = "__read_{0}_wrapper__".format(pipe.pipe_name) pipe.read_method_name = method_name setattr(tango_device_klass, method_name, read_pipe) def _get_wrapped_pipe_write_method(pipe, write_method): green_mode = pipe.write_green_mode if green_mode == GreenMode.Synchronous: @functools.wraps(write_method) def write_pipe(self, pipe): value = pipe.get_value() return write_method(self, value) else: @functools.wraps(write_method) def write_pipe(self, pipe): value = pipe.get_value() return get_worker().execute(write_method, self, value) return write_pipe def __patch_pipe_write_method(tango_device_klass, pipe): """ Checks if method given by it's name for the given DeviceImpl class has the correct signature. If a read/write method doesn't have a parameter (the traditional Pipe), then the method is wrapped into another method which has correct parameter definition to make it work. :param tango_device_klass: a DeviceImpl class :type tango_device_klass: class :param pipe: the pipe data information :type pipe: PipeData """ write_method = getattr(pipe, "fset", None) if write_method: method_name = "__write_{0}__".format(pipe.pipe_name) pipe.write_method_name = method_name else: method_name = pipe.write_method_name write_method = getattr(tango_device_klass, method_name) write_pipe = _get_wrapped_pipe_write_method(pipe, write_method) setattr(tango_device_klass, method_name, write_pipe) def __patch_pipe_methods(tango_device_klass, pipe): """ Checks if the read and write methods have the correct signature. If a read/write method doesn't have a parameter (the traditional Pipe), then the method is wrapped into another method to make this work. :param tango_device_klass: a DeviceImpl class :type tango_device_klass: class :param pipe: the pipe data information :type pipe: PipeData """ __patch_pipe_read_method(tango_device_klass, pipe) if pipe.pipe_write == PipeWriteType.PIPE_READ_WRITE: __patch_pipe_write_method(tango_device_klass, pipe) def __patch_standard_device_methods(klass): # TODO allow to force non green mode init_device_orig = klass.init_device @functools.wraps(init_device_orig) def init_device(self): return get_worker().execute(init_device_orig, self) setattr(klass, "init_device", init_device) delete_device_orig = klass.delete_device @functools.wraps(delete_device_orig) def delete_device(self): return get_worker().execute(delete_device_orig, self) setattr(klass, "delete_device", delete_device) dev_state_orig = klass.dev_state @functools.wraps(dev_state_orig) def dev_state(self): return get_worker().execute(dev_state_orig, self) setattr(klass, "dev_state", dev_state) dev_status_orig = klass.dev_status @functools.wraps(dev_status_orig) def dev_status(self): return get_worker().execute(dev_status_orig, self) setattr(klass, "dev_status", dev_status) read_attr_hardware_orig = klass.read_attr_hardware @functools.wraps(read_attr_hardware_orig) def read_attr_hardware(self, attr_list): return get_worker().execute(read_attr_hardware_orig, self, attr_list) setattr(klass, "read_attr_hardware", read_attr_hardware) always_executed_hook_orig = klass.always_executed_hook @functools.wraps(always_executed_hook_orig) def always_executed_hook(self): return get_worker().execute(always_executed_hook_orig, self) setattr(klass, "always_executed_hook", always_executed_hook) class _DeviceClass(DeviceClass): def __init__(self, name): DeviceClass.__init__(self, name) self.set_type(name) def dyn_attr(self, dev_list): """Invoked to create dynamic attributes for the given devices. Default implementation calls :meth:`TT.initialize_dynamic_attributes` for each device :param dev_list: list of devices :type dev_list: :class:`tango.DeviceImpl`""" for dev in dev_list: init_dyn_attrs = getattr(dev, "initialize_dynamic_attributes", None) if init_dyn_attrs and callable(init_dyn_attrs): try: init_dyn_attrs() except Exception: dev.warn_stream("Failed to initialize dynamic attributes") dev.debug_stream("Details: " + traceback.format_exc()) def __create_tango_deviceclass_klass(tango_device_klass, attrs=None): klass_name = tango_device_klass.__name__ if not issubclass(tango_device_klass, (BaseDevice)): msg = "{0} device must inherit from " \ "tango.server.Device".format(klass_name) raise Exception(msg) if attrs is None: attrs = tango_device_klass.__dict__ attr_list = {} pipe_list = {} class_property_list = {} device_property_list = {} cmd_list = {} for attr_name, attr_obj in attrs.items(): if isinstance(attr_obj, attribute): if attr_obj.attr_name is None: attr_obj._set_name(attr_name) else: attr_name = attr_obj.attr_name attr_list[attr_name] = attr_obj if not attr_obj.forward: __patch_attr_methods(tango_device_klass, attr_obj) elif isinstance(attr_obj, pipe): if attr_obj.pipe_name is None: attr_obj._set_name(attr_name) else: attr_name = attr_obj.pipe_name pipe_list[attr_name] = attr_obj __patch_pipe_methods(tango_device_klass, attr_obj) elif isinstance(attr_obj, device_property): attr_obj.name = attr_name # if you modify the attr_obj order then you should # take care of the code in get_device_properties() device_property_list[attr_name] = [attr_obj.dtype, attr_obj.doc, attr_obj.default_value, attr_obj.mandatory] elif isinstance(attr_obj, class_property): attr_obj.name = attr_name class_property_list[attr_name] = [attr_obj.dtype, attr_obj.doc, attr_obj.default_value] elif inspect.isroutine(attr_obj): if hasattr(attr_obj, "__tango_command__"): cmd_name, cmd_info = attr_obj.__tango_command__ cmd_list[cmd_name] = cmd_info __patch_standard_device_methods(tango_device_klass) devclass_name = klass_name + "Class" devclass_attrs = dict(class_property_list=class_property_list, device_property_list=device_property_list, cmd_list=cmd_list, attr_list=attr_list, pipe_list=pipe_list) return type(_DeviceClass)(devclass_name, (_DeviceClass,), devclass_attrs) def _init_tango_device_klass(tango_device_klass, attrs=None, tango_class_name=None): klass_name = tango_device_klass.__name__ tango_deviceclass_klass = __create_tango_deviceclass_klass( tango_device_klass, attrs=attrs) if tango_class_name is None: if hasattr(tango_device_klass, "TangoClassName"): tango_class_name = tango_device_klass.TangoClassName else: tango_class_name = klass_name tango_device_klass.TangoClassClass = tango_deviceclass_klass tango_device_klass.TangoClassName = tango_class_name tango_device_klass._api = API_VERSION return tango_device_klass def is_tango_object(arg): """Return tango data if the argument is a tango object, False otherwise. """ classes = attribute, device_property if isinstance(arg, classes): return arg try: return arg.__tango_command__ except AttributeError: return False def inheritance_patch(attrs): """Patch tango objects before they are processed by the metaclass.""" for key, obj in attrs.items(): if isinstance(obj, attribute): if getattr(obj, 'attr_write', None) == AttrWriteType.READ_WRITE: if not getattr(obj, 'fset', None): method_name = obj.write_method_name or "write_" + key obj.fset = attrs.get(method_name) class DeviceMeta(type(LatestDeviceImpl)): """ The :py:data:`metaclass` callable for :class:`Device`. This implementation of DeviceMeta makes device inheritance possible. """ def __new__(metacls, name, bases, attrs): # Attribute dictionary dct = {} # Filter object from bases bases = tuple(base for base in bases if base != object) # Set tango objects as attributes for base in reversed(bases): for key, value in base.__dict__.items(): if is_tango_object(value): dct[key] = value # Inheritance patch inheritance_patch(attrs) # Update attribute dictionary dct.update(attrs) # Create device class cls = type(LatestDeviceImpl).__new__(metacls, name, bases, dct) # Initialize device class _init_tango_device_klass(cls, dct) cls.TangoClassName = name # Return device class return cls class BaseDevice(LatestDeviceImpl): """ Base device class for the High level API. It should not be used directly, since this class is not an instance of MetaDevice. Use tango.server.Device instead. """ def __init__(self, cl, name): self._tango_properties = {} LatestDeviceImpl.__init__(self, cl, name) self.init_device() def init_device(self): """ Tango init_device method. Default implementation calls :meth:`get_device_properties`""" self.get_device_properties() def delete_device(self): pass delete_device.__doc__ = LatestDeviceImpl.delete_device.__doc__ def read_attr_hardware(self, attr_list): return LatestDeviceImpl.read_attr_hardware(self, attr_list) def dev_state(self): return LatestDeviceImpl.dev_state(self) def dev_status(self): return LatestDeviceImpl.dev_status(self) def get_device_properties(self, ds_class=None): if ds_class is None: try: # Call this method in a try/except in case this is called # during the DS shutdown sequence ds_class = self.get_device_class() except: return try: pu = self.prop_util = ds_class.prop_util self.device_property_list = copy.deepcopy( ds_class.device_property_list) class_prop = ds_class.class_property_list pu.get_device_properties( self, class_prop, self.device_property_list) for prop_name in class_prop: value = pu.get_property_values(prop_name, class_prop) self._tango_properties[prop_name] = value for prop_name in self.device_property_list: value = self.prop_util.get_property_values( prop_name, self.device_property_list) self._tango_properties[prop_name] = value properties = self.device_property_list[prop_name] mandatory = properties[3] if mandatory and value is None: msg = "Device property {0} is mandatory ".format(prop_name) raise Exception(msg) except DevFailed as df: print(80 * "-") print(df) raise df def always_executed_hook(self): """ Tango always_executed_hook. Default implementation does nothing """ pass def initialize_dynamic_attributes(self): """ Method executed at initializion phase to create dynamic attributes. Default implementation does nothing. Overwrite when necessary. """ pass @classmethod def run_server(cls, args=None, **kwargs): """Run the class as a device server. It is based on the tango.server.run method. The difference is that the device class and server name are automatically given. Args: args (iterable): args as given in the tango.server.run method without the server name. If None, the sys.argv list is used kwargs: the other keywords argument are as given in the tango.server.run method. """ if args is None: args = sys.argv[1:] args = [cls.__name__] + list(args) green_mode = getattr(cls, 'green_mode', None) kwargs.setdefault("green_mode", green_mode) return run((cls,), args, **kwargs) class attribute(AttrData): ''' Declares a new tango attribute in a :class:`Device`. To be used like the python native :obj:`property` function. For example, to declare a scalar, `tango.DevDouble`, read-only attribute called *voltage* in a *PowerSupply* :class:`Device` do:: class PowerSupply(Device): voltage = attribute() def read_voltage(self): return 999.999 The same can be achieved with:: class PowerSupply(Device): @attribute def voltage(self): return 999.999 It receives multiple keyword arguments. ===================== ================================ ======================================= ======================================================================================= parameter type default value description ===================== ================================ ======================================= ======================================================================================= name :obj:`str` class member name alternative attribute name dtype :obj:`object` :obj:`~tango.CmdArgType.DevDouble` data type (see :ref:`Data type equivalence `) dformat :obj:`~tango.AttrDataFormat` :obj:`~tango.AttrDataFormat.SCALAR` data format max_dim_x :obj:`int` 1 maximum size for x dimension (ignored for SCALAR format) max_dim_y :obj:`int` 0 maximum size for y dimension (ignored for SCALAR and SPECTRUM formats) display_level :obj:`~tango.DispLevel` :obj:`~tango.DisLevel.OPERATOR` display level polling_period :obj:`int` -1 polling period memorized :obj:`bool` False attribute should or not be memorized hw_memorized :obj:`bool` False write method should be called at startup when restoring memorize value (dangerous!) access :obj:`~tango.AttrWriteType` :obj:`~tango.AttrWriteType.READ` read only/ read write / write only access fget (or fread) :obj:`str` or :obj:`callable` 'read_' read method name or method object fset (or fwrite) :obj:`str` or :obj:`callable` 'write_' write method name or method object fisallowed :obj:`str` or :obj:`callable` 'is__allowed' is allowed method name or method object label :obj:`str` '' attribute label enum_labels sequence None the list of enumeration labels (enum data type) doc (or description) :obj:`str` '' attribute description unit :obj:`str` '' physical units the attribute value is in standard_unit :obj:`str` '' physical standard unit display_unit :obj:`str` '' physical display unit (hint for clients) format :obj:`str` '6.2f' attribute representation format min_value :obj:`str` None minimum allowed value max_value :obj:`str` None maximum allowed value min_alarm :obj:`str` None minimum value to trigger attribute alarm max_alarm :obj:`str` None maximum value to trigger attribute alarm min_warning :obj:`str` None minimum value to trigger attribute warning max_warning :obj:`str` None maximum value to trigger attribute warning delta_val :obj:`str` None delta_t :obj:`str` None abs_change :obj:`str` None minimum value change between events that causes event filter to send the event rel_change :obj:`str` None minimum relative change between events that causes event filter to send the event (%) period :obj:`str` None archive_abs_change :obj:`str` None archive_rel_change :obj:`str` None archive_period :obj:`str` None green_mode :obj:`~tango.GreenMode` None green mode for read and write. None means use server green mode. read_green_mode :obj:`~tango.GreenMode` None green mode for read. None means use server green mode. write_green_mode :obj:`~tango.GreenMode` None green mode for write. None means use server green mode. forwarded :obj:`bool` False the attribute should be forwarded if True ===================== ================================ ======================================= ======================================================================================= .. note:: avoid using *dformat* parameter. If you need a SPECTRUM attribute of say, boolean type, use instead ``dtype=(bool,)``. Example of a integer writable attribute with a customized label, unit and description:: class PowerSupply(Device): current = attribute(label="Current", unit="mA", dtype=int, access=AttrWriteType.READ_WRITE, doc="the power supply current") def init_device(self): Device.init_device(self) self._current = -1 def read_current(self): return self._current def write_current(self, current): self._current = current The same, but using attribute as a decorator:: class PowerSupply(Device): def init_device(self): Device.init_device(self) self._current = -1 @attribute(label="Current", unit="mA", dtype=int) def current(self): """the power supply current""" return 999.999 @current.write def current(self, current): self._current = current In this second format, defining the `write` implicitly sets the attribute access to READ_WRITE. .. versionadded:: 8.1.7 added green_mode, read_green_mode and write_green_mode options ''' def __init__(self, fget=None, **kwargs): self._kwargs = dict(kwargs) self.name = kwargs.pop("name", None) class_name = kwargs.pop("class_name", None) forward = kwargs.get("forwarded", False) if forward: expected = 2 if "label" in kwargs else 1 if len(kwargs) > expected: raise TypeError( "Forwarded attributes only support label argument") else: green_mode = kwargs.pop("green_mode", True) self.read_green_mode = kwargs.pop("read_green_mode", green_mode) self.write_green_mode = kwargs.pop("write_green_mode", green_mode) if fget: if inspect.isroutine(fget): self.fget = fget if 'doc' not in kwargs and 'description' not in kwargs: if fget.__doc__ is not None: kwargs['doc'] = fget.__doc__ kwargs['fget'] = fget super(attribute, self).__init__(self.name, class_name) self.__doc__ = kwargs.get('doc', kwargs.get('description', 'TANGO attribute')) if 'dtype' in kwargs: kwargs['dtype'], kwargs['dformat'] = \ _get_tango_type_format(kwargs['dtype'], kwargs.get('dformat')) self.build_from_dict(kwargs) def get_attribute(self, obj): return obj.get_device_attr().get_attr_by_name(self.attr_name) # -------------------- # descriptor interface # -------------------- def __get__(self, obj, objtype): if obj is None: return self return self.get_attribute(obj) def __set__(self, obj, value): attr = self.get_attribute(obj) set_complex_value(attr, value) def __delete__(self, obj): obj.remove_attribute(self.attr_name) def setter(self, fset): """ To be used as a decorator. Will define the decorated method as a write attribute method to be called when client writes the attribute """ self.fset = fset if self.attr_write == AttrWriteType.READ: if getattr(self, 'fget', None): self.attr_write = AttrWriteType.READ_WRITE else: self.attr_write = AttrWriteType.WRITE return self def write(self, fset): """ To be used as a decorator. Will define the decorated method as a write attribute method to be called when client writes the attribute """ return self.setter(fset) def __call__(self, fget): return type(self)(fget=fget, **self._kwargs) class pipe(PipeData): ''' Declares a new tango pipe in a :class:`Device`. To be used like the python native :obj:`property` function. Checkout the :ref:`pipe data types ` to see what you should return on a pipe read request and what to expect as argument on a pipe write request. For example, to declare a read-only pipe called *ROI* (for Region Of Interest), in a *Detector* :class:`Device` do:: class Detector(Device): ROI = pipe() def read_ROI(self): return ('ROI', ({'name': 'x', 'value': 0}, {'name': 'y', 'value': 10}, {'name': 'width', 'value': 100}, {'name': 'height', 'value': 200})) The same can be achieved with (also showing that a dict can be used to pass blob data):: class Detector(Device): @pipe def ROI(self): return 'ROI', dict(x=0, y=10, width=100, height=200) It receives multiple keyword arguments. ===================== ================================ ======================================= ======================================================================================= parameter type default value description ===================== ================================ ======================================= ======================================================================================= name :obj:`str` class member name alternative pipe name display_level :obj:`~tango.DispLevel` :obj:`~tango.DisLevel.OPERATOR` display level access :obj:`~tango.PipeWriteType` :obj:`~tango.PipeWriteType.READ` read only/ read write access fget (or fread) :obj:`str` or :obj:`callable` 'read_' read method name or method object fset (or fwrite) :obj:`str` or :obj:`callable` 'write_' write method name or method object fisallowed :obj:`str` or :obj:`callable` 'is__allowed' is allowed method name or method object label :obj:`str` '' pipe label doc (or description) :obj:`str` '' pipe description green_mode :obj:`~tango.GreenMode` None green mode for read and write. None means use server green mode. read_green_mode :obj:`~tango.GreenMode` None green mode for read. None means use server green mode. write_green_mode :obj:`~tango.GreenMode` None green mode for write. None means use server green mode. ===================== ================================ ======================================= ======================================================================================= The same example with a read-write ROI, a customized label and description:: class Detector(Device): ROI = pipe(label='Region Of Interest', doc='The active region of interest', access=PipeWriteType.PIPE_READ_WRITE) def init_device(self): Device.init_device(self) self.__roi = 'ROI', dict(x=0, y=10, width=100, height=200) def read_ROI(self): return self.__roi def write_ROI(self, roi): self.__roi = roi The same, but using pipe as a decorator:: class Detector(Device): def init_device(self): Device.init_device(self) self.__roi = 'ROI', dict(x=0, y=10, width=100, height=200) @pipe(label="Region Of Interest") def ROI(self): """The active region of interest""" return self.__roi @ROI.write def ROI(self, roi): self.__roi = roi In this second format, defining the `write` / `setter` implicitly sets the pipe access to READ_WRITE. .. versionadded:: 9.2.0 ''' def __init__(self, fget=None, **kwargs): self._kwargs = dict(kwargs) name = kwargs.pop("name", None) class_name = kwargs.pop("class_name", None) green_mode = kwargs.pop("green_mode", True) self.read_green_mode = kwargs.pop("read_green_mode", green_mode) self.write_green_mode = kwargs.pop("write_green_mode", green_mode) if fget: if inspect.isroutine(fget): self.fget = fget if 'doc' not in kwargs and 'description' not in kwargs: if fget.__doc__ is not None: kwargs['doc'] = fget.__doc__ kwargs['fget'] = fget super(pipe, self).__init__(name, class_name) self.__doc__ = kwargs.get('doc', kwargs.get('description', 'TANGO pipe')) self.build_from_dict(kwargs) def get_pipe(self, obj): dclass = obj.get_device_class() return dclass.get_pipe_by_name(self.pipe_name) # -------------------- # descriptor interface # -------------------- def __get__(self, obj, objtype): if obj is None: return self return self.get_attribute(obj) def __set__(self, obj, value): attr = self.get_attribute(obj) set_complex_value(attr, value) def setter(self, fset): """ To be used as a decorator. Will define the decorated method as a write pipe method to be called when client writes to the pipe """ self.fset = fset self.pipe_write = PipeWriteType.PIPE_READ_WRITE return self def write(self, fset): """ To be used as a decorator. Will define the decorated method as a write pipe method to be called when client writes to the pipe """ return self.setter(fset) def __call__(self, fget): return type(self)(fget=fget, **self._kwargs) def __build_command_doc(f, name, dtype_in, doc_in, dtype_out, doc_out): doc = "'{0}' TANGO command".format(name) if dtype_in is not None: arg_spec = inspect.getargspec(f) if len(arg_spec.args) > 1: # arg[0] should be self and arg[1] the command argument param_name = arg_spec.args[1] else: param_name = 'arg' dtype_in_str = str(dtype_in) if not isinstance(dtype_in, str): try: dtype_in_str = dtype_in.__name__ except: pass msg = doc_in or '(not documented)' doc += '\n\n:param {0}: {1}\n:type {0}: {2}'.format( param_name, msg, dtype_in_str) if dtype_out is not None: dtype_out_str = str(dtype_out) if not isinstance(dtype_out, str): try: dtype_out_str = dtype_out.__name__ except: pass msg = doc_out or '(not documented)' doc += '\n\n:return: {0}\n:rtype: {1}'.format(msg, dtype_out_str) return doc def command(f=None, dtype_in=None, dformat_in=None, doc_in="", dtype_out=None, dformat_out=None, doc_out="", display_level=None, polling_period=None, green_mode=None): """ Declares a new tango command in a :class:`Device`. To be used like a decorator in the methods you want to declare as tango commands. The following example declares commands: * `void TurnOn(void)` * `void Ramp(DevDouble current)` * `DevBool Pressurize(DevDouble pressure)` :: class PowerSupply(Device): @command def TurnOn(self): self.info_stream('Turning on the power supply') @command(dtype_in=float) def Ramp(self, current): self.info_stream('Ramping on %f...' % current) @command(dtype_in=float, doc_in='the pressure to be set', dtype_out=bool, doc_out='True if it worked, False otherwise') def Pressurize(self, pressure): self.info_stream('Pressurizing to %f...' % pressure) return True .. note:: avoid using *dformat* parameter. If you need a SPECTRUM attribute of say, boolean type, use instead ``dtype=(bool,)``. :param dtype_in: a :ref:`data type ` describing the type of parameter. Default is None meaning no parameter. :param dformat_in: parameter data format. Default is None. :type dformat_in: AttrDataFormat :param doc_in: parameter documentation :type doc_in: str :param dtype_out: a :ref:`data type ` describing the type of return value. Default is None meaning no return value. :param dformat_out: return value data format. Default is None. :type dformat_out: AttrDataFormat :param doc_out: return value documentation :type doc_out: str :param display_level: display level for the command (optional) :type display_level: DispLevel :param polling_period: polling period in milliseconds (optional) :type polling_period: int :param green_mode: set green mode on this specific command. Default value is None meaning use the server green mode. Set it to GreenMode.Synchronous to force a non green command in a green server. .. versionadded:: 8.1.7 added green_mode option .. versionadded:: 9.2.0 added display_level and polling_period optional argument """ if f is None: return functools.partial( command, dtype_in=dtype_in, dformat_in=dformat_in, doc_in=doc_in, dtype_out=dtype_out, dformat_out=dformat_out, doc_out=doc_out, display_level=display_level, polling_period=polling_period, green_mode=green_mode) name = f.__name__ dtype_format_in = _get_tango_type_format(dtype_in, dformat_in) dtype_format_out = _get_tango_type_format(dtype_out, dformat_out) din = [from_typeformat_to_type(*dtype_format_in), doc_in] dout = [from_typeformat_to_type(*dtype_format_out), doc_out] config_dict = {} if display_level is not None: config_dict['Display level'] = display_level if polling_period is not None: config_dict['Polling period'] = polling_period if green_mode == GreenMode.Synchronous: cmd = f else: @functools.wraps(f) def cmd(self, *args, **kwargs): return get_worker().execute(f, self, *args, **kwargs) cmd.__tango_command__ = name, [din, dout, config_dict] # try to create a minimalistic __doc__ if cmd.__doc__ is None: try: cmd.__doc__ = __build_command_doc( f, name, dtype_in, doc_in, dtype_out, doc_out) except Exception: cmd.__doc__ = "TANGO command" return cmd class _BaseProperty(object): def __init__(self, dtype, doc='', default_value=None, update_db=False): self.name = None dtype = from_typeformat_to_type(*_get_tango_type_format(dtype)) self.dtype = dtype self.doc = doc self.default_value = default_value self.update_db = update_db self.__doc__ = doc or 'TANGO property' def __get__(self, obj, objtype): if obj is None: return self return obj._tango_properties.get(self.name) def __set__(self, obj, value): obj._tango_properties[self.name] = value if self.update_db: import tango db = tango.Util.instance().get_database() db.put_device_property(obj.get_name(), {self.name: value}) def __delete__(self, obj): del obj._tango_properties[self.name] class device_property(_BaseProperty): """ Declares a new tango device property in a :class:`Device`. To be used like the python native :obj:`property` function. For example, to declare a scalar, `tango.DevString`, device property called *host* in a *PowerSupply* :class:`Device` do:: from tango.server import Device, DeviceMeta from tango.server import device_property class PowerSupply(Device): host = device_property(dtype=str) port = device_property(dtype=int, mandatory=True) :param dtype: Data type (see :ref:`pytango-data-types`) :param doc: property documentation (optional) :param mandatory (optional: default is False) :param default_value: default value for the property (optional) :param update_db: tells if set value should write the value to database. [default: False] :type update_db: bool .. versionadded:: 8.1.7 added update_db option """ def __init__(self, dtype, doc='', mandatory=False, default_value=None, update_db=False): super(device_property, self).__init__(dtype, doc, default_value, update_db) self.mandatory = mandatory if mandatory and default_value is not None: msg = "device_property arguments mandatory " \ "and default_value are incompatible" raise Exception(msg) class class_property(_BaseProperty): """ Declares a new tango class property in a :class:`Device`. To be used like the python native :obj:`property` function. For example, to declare a scalar, `tango.DevString`, class property called *port* in a *PowerSupply* :class:`Device` do:: from tango.server import Device, DeviceMeta from tango.server import class_property class PowerSupply(Device): port = class_property(dtype=int, default_value=9788) :param dtype: Data type (see :ref:`pytango-data-types`) :param doc: property documentation (optional) :param default_value: default value for the property (optional) :param update_db: tells if set value should write the value to database. [default: False] :type update_db: bool .. versionadded:: 8.1.7 added update_db option """ pass def __to_cb(post_init_callback): if post_init_callback is None: return lambda: None err_msg = "post_init_callback must be a callable or " \ "sequence " if callable(post_init_callback): f = post_init_callback elif is_non_str_seq(post_init_callback): length = len(post_init_callback) if length < 1 or length > 3: raise TypeError(err_msg) cb = post_init_callback[0] if not callable(cb): raise TypeError(err_msg) args, kwargs = [], {} if length > 1: args = post_init_callback[1] if length > 2: kwargs = post_init_callback[2] f = functools.partial(cb, *args, **kwargs) else: raise TypeError(err_msg) return f def _to_classes(classes): uclasses = [] if is_seq(classes): for klass_info in classes: if is_seq(klass_info): if len(klass_info) == 2: klass_klass, klass = klass_info klass_name = klass.__name__ else: klass_klass, klass, klass_name = klass_info else: if not hasattr(klass_info, '_api') or klass_info._api < 2: raise Exception( "When giving a single class, it must " "implement HLAPI (see tango.server)") klass_klass = klass_info.TangoClassClass klass_name = klass_info.TangoClassName klass = klass_info uclasses.append((klass_klass, klass, klass_name)) else: for klass_name, klass_info in classes.items(): if is_seq(klass_info): if len(klass_info) == 2: klass_klass, klass = klass_info else: klass_klass, klass, klass_name = klass_info else: if not hasattr(klass_info, '_api') or klass_info._api < 2: raise Exception( "When giving a single class, it must " "implement HLAPI (see tango.server)") klass_klass = klass_info.TangoClassClass klass_name = klass_info.TangoClassName klass = klass_info uclasses.append((klass_klass, klass, klass_name)) return uclasses def _add_classes(util, classes): for class_info in _to_classes(classes): util.add_class(*class_info) def __server_run(classes, args=None, msg_stream=sys.stdout, util=None, event_loop=None, post_init_callback=None, green_mode=None): if green_mode is None: green_mode = get_green_mode() write = msg_stream.write if msg_stream else lambda msg: None if args is None: args = sys.argv post_init_callback = __to_cb(post_init_callback) if util is None: util = Util(args) if green_mode in (GreenMode.Gevent, GreenMode.Asyncio): util.set_serial_model(SerialModel.NO_SYNC) worker = get_executor(green_mode) set_worker(worker) if event_loop is not None: event_loop = functools.partial(worker.execute, event_loop) util.server_set_event_loop(event_loop) log = logging.getLogger("tango") def tango_loop(): log.debug("server loop started") _add_classes(util, classes) util.server_init() worker.execute(post_init_callback) write("Ready to accept request\n") util.server_run() log.debug("server loop exit") worker.run(tango_loop, wait=True) return util def run(classes, args=None, msg_stream=sys.stdout, verbose=False, util=None, event_loop=None, post_init_callback=None, green_mode=None, raises=False): """ Provides a simple way to run a tango server. It handles exceptions by writting a message to the msg_stream. The `classes` parameter can be either a sequence of: * :class:`~tango.server.Device` or * a sequence of two elements :class:`~tango.DeviceClass`, :class:`~tango.DeviceImpl` or * a sequence of three elements :class:`~tango.DeviceClass`, :class:`~tango.DeviceImpl`, tango class name (str) or a dictionary where: * key is the tango class name * value is either: * a :class:`~tango.server.Device` class or * a sequence of two elements :class:`~tango.DeviceClass`, :class:`~tango.DeviceImpl` or * a sequence of three elements :class:`~tango.DeviceClass`, :class:`~tango.DeviceImpl`, tango class name (str) The optional `post_init_callback` can be a callable (without arguments) or a tuple where the first element is the callable, the second is a list of arguments (optional) and the third is a dictionary of keyword arguments (also optional). .. note:: the order of registration of tango classes defines the order tango uses to initialize the corresponding devices. if using a dictionary as argument for classes be aware that the order of registration becomes arbitrary. If you need a predefined order use a sequence or an OrderedDict. Example 1: registering and running a PowerSupply inheriting from :class:`~tango.server.Device`:: from tango.server import Device, DeviceMeta, run class PowerSupply(Device): pass run((PowerSupply,)) Example 2: registering and running a MyServer defined by tango classes `MyServerClass` and `MyServer`:: from tango import Device_4Impl, DeviceClass from tango.server import run class MyServer(Device_4Impl): pass class MyServerClass(DeviceClass): pass run({'MyServer': (MyServerClass, MyServer)}) Example 3: registering and running a MyServer defined by tango classes `MyServerClass` and `MyServer`:: from tango import Device_4Impl, DeviceClass from tango.server import Device, DeviceMeta, run class PowerSupply(Device): pass class MyServer(Device_4Impl): pass class MyServerClass(DeviceClass): pass run([PowerSupply, [MyServerClass, MyServer]]) # or: run({'MyServer': (MyServerClass, MyServer)}) :param classes: a sequence of :class:`~tango.server.Device` classes or a dictionary where keyword is the tango class name and value is a sequence of Tango Device Class python class, and Tango Device python class :type classes: sequence or dict :param args: list of command line arguments [default: None, meaning use sys.argv] :type args: list :param msg_stream: stream where to put messages [default: sys.stdout] :param util: PyTango Util object [default: None meaning create a Util instance] :type util: :class:`~tango.Util` :param event_loop: event_loop callable :type event_loop: callable :param post_init_callback: an optional callback that is executed between the calls Util.server_init and Util.server_run :type post_init_callback: callable or tuple (see description above) :param raises: Disable error handling and propagate exceptions from the server :type raises: bool :return: The Util singleton object :rtype: :class:`~tango.Util` .. versionadded:: 8.1.2 .. versionchanged:: 8.1.4 when classes argument is a sequence, the items can also be a sequence [, tango class name] .. versionchanged:: 9.2.2 `raises` argument has been added """ server_run = functools.partial( __server_run, classes, args=args, msg_stream=msg_stream, util=util, event_loop=event_loop, post_init_callback=post_init_callback, green_mode=green_mode) # Run the server without error handling if raises: return server_run() # Run the server with error handling write = msg_stream.write if msg_stream else lambda msg: None try: return server_run() except KeyboardInterrupt: write("Exiting: Keyboard interrupt\n") except DevFailed as df: write("Exiting: Server exited with tango.DevFailed:\n" + str(df) + "\n") if verbose: write(traceback.format_exc()) except Exception as e: write("Exiting: Server exited with unforseen exception:\n" + str(e) + "\n") if verbose: write(traceback.format_exc()) write("\nExited\n") def server_run(classes, args=None, msg_stream=sys.stdout, verbose=False, util=None, event_loop=None, post_init_callback=None, green_mode=None): """ Since PyTango 8.1.2 it is just an alias to :func:`~tango.server.run`. Use :func:`~tango.server.run` instead. .. versionadded:: 8.0.0 .. versionchanged:: 8.0.3 Added `util` keyword parameter. Returns util object .. versionchanged:: 8.1.1 Changed default msg_stream from *stderr* to *stdout* Added `event_loop` keyword parameter. Returns util object .. versionchanged:: 8.1.2 Added `post_init_callback` keyword parameter .. deprecated:: 8.1.2 Use :func:`~tango.server.run` instead. """ return run(classes, args=args, msg_stream=msg_stream, verbose=verbose, util=util, event_loop=event_loop, post_init_callback=post_init_callback, green_mode=green_mode) # Instanciate DeviceMeta using BaseDevice Device = DeviceMeta("Device", (BaseDevice,), {'__doc__': """\ Device class for the high-level API. All device specific classes should inherit from this class. """}) # Avoid circular imports from .tango_object import Server # noqa: E402 pytango-9.2.2/tango/tango_numpy.py000066400000000000000000000125651316324373100172430ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ """ This is an internal PyTango module. """ __all__ = ["NumpyType", "numpy_type", "numpy_spectrum", "numpy_image"] __docformat__ = "restructuredtext" from ._tango import Except, Attribute, AttributeInfo, constants from ._tango import CmdArgType as ArgType from .attribute_proxy import AttributeProxy import collections def _numpy_invalid(*args, **kwds): Except.throw_exception( "PyTango_InvalidConversion", "There's no registered conversor to numpy.", "NumpyType.tango_to_numpy" ) def _define_numpy(): if not constants.NUMPY_SUPPORT: return None, _numpy_invalid, _numpy_invalid, _numpy_invalid try: import numpy class NumpyType(object): DevShort = numpy.int16 DevLong = numpy.int32 DevDouble = numpy.float64 DevFloat = numpy.float32 DevBoolean = numpy.bool8 DevUShort = numpy.uint16 DevULong = numpy.uint32 DevUChar = numpy.ubyte DevLong64 = numpy.int64 DevULong64 = numpy.uint64 mapping = { ArgType.DevShort: DevShort, ArgType.DevLong: DevLong, ArgType.DevDouble: DevDouble, ArgType.DevFloat: DevFloat, ArgType.DevBoolean: DevBoolean, ArgType.DevUShort: DevUShort, ArgType.DevULong: DevULong, ArgType.DevUChar: DevUChar, ArgType.DevLong64: DevLong64, ArgType.DevULong: DevULong64, } @staticmethod def tango_to_numpy(param): if isinstance(param, ArgType): tg_type = param if isinstance(param, AttributeInfo): # or AttributeInfoEx tg_type = param.data_type elif isinstance(param, Attribute): tg_type = param.get_data_type() elif isinstance(param, AttributeProxy): tg_type = param.get_config().data_type else: tg_type = param try: return NumpyType.mapping[tg_type] except Exception: _numpy_invalid() @staticmethod def spectrum(tg_type, dim_x): """ numpy_spectrum(self, tg_type, dim_x, dim_y) -> numpy.array numpy_spectrum(self, tg_type, sequence) -> numpy.array Get a square numpy array to be used with tango. One version gets dim_x and creates an object with this size. The other version expects any sequence to convert. Parameters: - tg_type : (ArgType): The tango type. For convenience, it can also extract this information from an Attribute, AttributeInfo or AttributeProxy object. - dim_x : (int) - sequence: """ np_type = NumpyType.tango_to_numpy(tg_type) if isinstance(dim_x, collections.Sequence): return numpy.array(dim_x, dtype=np_type) else: return numpy.ndarray(shape=(dim_x,), dtype=np_type) @staticmethod def image(tg_type, dim_x, dim_y=None): """ numpy_image(self, tg_type, dim_x, dim_y) -> numpy.array numpy_image(self, tg_type, sequence) -> numpy.array Get a square numpy array to be used with tango. One version gets dim_x and dim_y and creates an object with this size. The other version expects a square sequence of sequences to convert. Parameters: - tg_type : (ArgType): The tango type. For convenience, it can also extract this information from an Attribute, AttributeInfo or AttributeProxy object. - dim_x : (int) - dim_y : (int) - sequence: """ np_type = NumpyType.tango_to_numpy(tg_type) if dim_y is None: return numpy.array(dim_x, dtype=np_type) else: return numpy.ndarray(shape=(dim_y, dim_x,), dtype=np_type) return ( NumpyType, NumpyType.spectrum, NumpyType.image, NumpyType.tango_to_numpy) except Exception: return None, _numpy_invalid, _numpy_invalid, _numpy_invalid NumpyType, numpy_spectrum, numpy_image, numpy_type = _define_numpy() pytango-9.2.2/tango/tango_object.py000066400000000000000000000464011316324373100173350ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ """Server for tango objects.""" import os import sys import types import logging import weakref import inspect import functools from .codec import loads, dumps from .attr_data import AttrData from .utils import TO_TANGO_TYPE from ._tango import AttrDataFormat, CmdArgType, GreenMode from ._tango import DbDevInfo, Database, DevState, constants from .server import Device, _to_classes, _add_classes from .server import get_worker, set_worker from .green import get_executor __all__ = ['Server'] _CLEAN_UP_TEMPLATE = """ import sys from tango import Database db = Database() server_instance = '{server_instance}' try: devices = db.get_device_class_list(server_instance)[::2] for device in devices: db.delete_device(device) try: db.delete_device_alias(db.get_alias(device)) except: pass except: print ('Failed to cleanup!') """ def __to_tango_type_fmt(value): dfmt = AttrDataFormat.SCALAR value_t = type(value) dtype = TO_TANGO_TYPE.get(value_t) max_dim_x, max_dim_y = 1, 0 if dtype is None: if constants.NUMPY_SUPPORT: import numpy else: numpy = None if numpy and isinstance(value, numpy.ndarray): dtype = TO_TANGO_TYPE.get(value.dtype.name) shape_l = len(value.shape) if shape_l == 1: dfmt = AttrDataFormat.SPECTRUM max_dim_x = max(2 ** 16, value.shape[0]) elif shape_l == 2: dfmt = AttrDataFormat.IMAGE max_dim_x = max(2 ** 16, value.shape[0]) max_dim_y = max(2 ** 16, value.shape[1]) else: dtype = CmdArgType.DevEncoded return dtype, dfmt, max_dim_x, max_dim_y def create_tango_class(server, obj, tango_class_name=None, member_filter=None): slog = server.server_instance.replace("/", ".") log = logging.getLogger("tango.Server." + slog) obj_klass = obj.__class__ obj_klass_name = obj_klass.__name__ if tango_class_name is None: tango_class_name = obj_klass_name class DeviceDispatcher(Device): TangoClassName = tango_class_name def __init__(self, tango_class_obj, name): tango_object = server.get_tango_object(name) self.__tango_object = weakref.ref(tango_object) Device.__init__(self, tango_class_obj, name) def init_device(self): Device.init_device(self) self.set_state(DevState.ON) @property def _tango_object(self): return self.__tango_object() @property def _object(self): return self._tango_object._object DeviceDispatcher.__name__ = tango_class_name DeviceDispatcher.TangoClassName = tango_class_name DeviceDispatcherClass = DeviceDispatcher.TangoClassClass for name in dir(obj): if name.startswith("_"): continue log.debug("inspecting %s.%s", obj_klass_name, name) try: member = getattr(obj, name) except: log.info( "failed to inspect member '%s.%s'", obj_klass_name, name) log.debug("Details:", exc_info=1) if inspect.isclass(member) or inspect.ismodule(member): continue if member_filter and not member_filter(obj, tango_class_name, name, member): log.debug("filtered out %s from %s", name, tango_class_name) continue if inspect.isroutine(member): # try to find out if there are any parameters in_type = CmdArgType.DevEncoded out_type = CmdArgType.DevEncoded try: arg_spec = inspect.getargspec(member) if not arg_spec.args: in_type = CmdArgType.DevVoid except TypeError: pass if in_type == CmdArgType.DevVoid: def _command(dev, func_name=None): obj = dev._object f = getattr(obj, func_name) result = server.worker.execute(f) return server.dumps(result) else: def _command(dev, param, func_name=None): obj = dev._object args, kwargs = loads(*param) f = getattr(obj, func_name) result = server.worker.execute(f, *args, **kwargs) return server.dumps(result) cmd = functools.partial(_command, func_name=name) cmd.__name__ = name doc = member.__doc__ if doc is None: doc = "" cmd.__doc__ = doc cmd = types.MethodType(cmd, None, DeviceDispatcher) setattr(DeviceDispatcher, name, cmd) DeviceDispatcherClass.cmd_list[name] = \ [[in_type, doc], [out_type, ""]] else: read_only = False if hasattr(obj_klass, name): kmember = getattr(obj_klass, name) if inspect.isdatadescriptor(kmember): if kmember.fset is None: read_only = True else: continue value = member dtype, fmt, x, y = __to_tango_type_fmt(value) if dtype is None or dtype == CmdArgType.DevEncoded: dtype = CmdArgType.DevEncoded fmt = AttrDataFormat.SCALAR def read(dev, attr): name = attr.get_name() value = server.worker.execute(getattr, dev._object, name) attr.set_value(*server.dumps(value)) def write(dev, attr): name = attr.get_name() value = attr.get_write_value() value = loads(*value) server.worker.execute(setattr, dev._object, name, value) else: def read(dev, attr): name = attr.get_name() value = server.worker.execute(getattr, dev._object, name) attr.set_value(value) def write(dev, attr): name = attr.get_name() value = attr.get_write_value() server.worker.execute(setattr, dev._object, name, value) read.__name__ = "_read_" + name setattr(DeviceDispatcher, read.__name__, read) pars = dict(name=name, dtype=dtype, dformat=fmt, max_dim_x=x, max_dim_y=y, fget=read) if not read_only: write.__name__ = "_write_" + name pars['fset'] = write setattr(DeviceDispatcher, write.__name__, write) attr_data = AttrData.from_dict(pars) DeviceDispatcherClass.attr_list[name] = attr_data return DeviceDispatcher class Server: """ Server helper """ Phase0, Phase1, Phase2 = range(3) PreInitPhase = Phase1 PostInitPhase = Phase2 class TangoObjectAdapter: def __init__(self, server, obj, full_name, alias=None, tango_class_name=None): self.__server = weakref.ref(server) self.full_name = full_name self.alias = alias self.class_name = obj.__class__.__name__ if tango_class_name is None: tango_class_name = self.class_name self.tango_class_name = tango_class_name self.__object = weakref.ref(obj, self.__onObjectDeleted) def __onObjectDeleted(self, object_weak): self.__object = None server = self._server server.log.info("object deleted %s(%s)", self.class_name, self.full_name) server.unregister_object(self.full_name) @property def _server(self): return self.__server() @property def _object(self): obj = self.__object if obj is None: return None return obj() def __init__(self, server_name, server_type=None, port=None, event_loop_callback=None, init_callbacks=None, auto_clean=False, green_mode=None, tango_classes=None, protocol="pickle"): if server_name is None: raise ValueError("Must give a valid server name") self.__server_name = server_name self.__server_type = server_type self.__port = port self.__event_loop_callback = event_loop_callback if init_callbacks is None: init_callbacks = {} self.__init_callbacks = init_callbacks self.__util = None self.__objects = {} self.__running = False self.__auto_clean = auto_clean self.__green_mode = green_mode self.__protocol = protocol self.__tango_classes = _to_classes(tango_classes or []) self.__tango_devices = [] if self.async_mode: self.__worker = get_executor(self.green_mode) else: self.__worker = get_worker() set_worker(self.__worker) self.log = logging.getLogger("tango.Server") self.__phase = Server.Phase0 def __build_args(self): args = [self.server_type, self.__server_name] if self.__port is not None: args.extend(["-ORBendPoint", "giop:tcp::{0}".format(self.__port)]) return args def __exec_cb(self, cb): if not cb: return self.worker.execute(cb) def __find_tango_class(self, key): pass def __prepare(self): """Update database with existing devices""" self.log.debug("prepare") if self.__phase > 0: raise RuntimeError("Internal error: Can only prepare in phase 0") server_instance = self.server_instance db = Database() # get list of server devices if server was already registered server_registered = server_instance in db.get_server_list() if server_registered: dserver_name = "dserver/{0}".format(server_instance) if db.import_device(dserver_name).exported: import tango dserver = tango.DeviceProxy(dserver_name) try: dserver.ping() raise Exception("Server already running") except: self.log.info("Last time server was not properly " "shutdown!") _, db_device_map = self.get_devices() else: db_device_map = {} db_devices_add = {} # all devices that are registered in database that are not registered # as tango objects or for which the tango class changed will be removed db_devices_remove = set(db_device_map) - set(self.__objects) for local_name, local_object in self.__objects.items(): local_class_name = local_object.tango_class_name db_class_name = db_device_map.get(local_name) if db_class_name: if local_class_name != db_class_name: db_devices_remove.add(local_name) db_devices_add[local_name] = local_object else: db_devices_add[local_name] = local_object for device in db_devices_remove: db.delete_device(device) try: db.delete_device_alias(db.get_alias(device)) except: pass # register devices in database # add DServer db_dev_info = DbDevInfo() db_dev_info.server = server_instance db_dev_info._class = "DServer" db_dev_info.name = "dserver/" + server_instance db_dev_infos = [db_dev_info] aliases = [] for obj_name, obj in db_devices_add.items(): db_dev_info = DbDevInfo() db_dev_info.server = server_instance db_dev_info._class = obj.tango_class_name db_dev_info.name = obj.full_name db_dev_infos.append(db_dev_info) if obj.alias: aliases.append((obj.full_name, obj.alias)) db.add_server(server_instance, db_dev_infos) # add aliases for alias_info in aliases: db.put_device_alias(*alias_info) def __clean_up_process(self): if not self.__auto_clean: return clean_up = _CLEAN_UP_TEMPLATE.format( server_instance=self.server_instance) import subprocess res = subprocess.call([sys.executable, "-c", clean_up]) if res: self.log.error("Failed to cleanup") def __initialize(self): self.log.debug("initialize") async_mode = self.async_mode event_loop = self.__event_loop_callback util = self.tango_util u_instance = util.instance() if async_mode: if event_loop: event_loop = functools.partial(self.worker.execute, event_loop) if event_loop: u_instance.server_set_event_loop(event_loop) _add_classes(util, self.__tango_classes) def __run(self, timeout=None): return self.worker.run(self.__tango_loop, wait=True, timeout=timeout) def __tango_loop(self): self.log.debug("server loop started") self.__running = True u_instance = self.tango_util.instance() u_instance.server_init() self._phase = Server.Phase2 self.log.info("Ready to accept request") u_instance.server_run() if self.__auto_clean: self.__clean_up_process() self.log.debug("server loop exit") @property def _phase(self): return self.__phase @_phase.setter def _phase(self, phase): self.__phase = phase cb = self.__init_callbacks.get(phase) self.__exec_cb(cb) @property def server_type(self): server_type = self.__server_type if server_type is None: server_file = os.path.basename(sys.argv[0]) server_type = os.path.splitext(server_file)[0] return server_type @property def server_instance(self): return "{0}/{1}".format(self.server_type, self.__server_name) @property def tango_util(self): if self.__util is None: import tango self.__util = tango.Util(self.__build_args()) self._phase = Server.Phase1 return self.__util @property def green_mode(self): gm = self.__green_mode if gm is None: from tango import get_green_mode gm = get_green_mode() return gm @green_mode.setter def green_mode(self, gm): if gm == self.__green_mode: return if self.__running: raise RuntimeError("Cannot change green mode while " "server is running") self.__green_mode = gm @property def async_mode(self): return self.green_mode in (GreenMode.Gevent, GreenMode.Asyncio) @property def worker(self): return self.__worker def dumps(self, obj): return dumps(self.__protocol, obj) def get_devices(self): """ Helper that retuns a dict of devices for this server. :return: Returns a tuple of two elements: - dict - dict :rtype: tuple """ if self.__util is None: import tango db = tango.Database() else: db = self.__util.get_database() server = self.server_instance dev_list = db.get_device_class_list(server) class_map, dev_map = {}, {} for class_name, dev_name in zip(dev_list[1::2], dev_list[::2]): dev_names = class_map.get(class_name) if dev_names is None: class_map[class_name] = dev_names = [] dev_name = dev_name.lower() dev_names.append(dev_name) dev_map[dev_name] = class_name return class_map, dev_map def get_tango_object(self, name): return self.__objects.get(name.lower()) def get_tango_class(self, tango_class_name): for klass in self.__tango_classes: if klass.TangoClassName == tango_class_name: return klass def register_tango_device(self, klass, name): if inspect.isclass(klass): if isinstance(klass, Device): # TODO raise NotImplementedError else: raise ValueError else: raise NotImplementedError def register_tango_class(self, klass): if self._phase > Server.Phase1: raise RuntimeError("Cannot add new class after phase 1 " "(i.e. after server_init)") self.__tango_classes.append(klass) def unregister_object(self, name): tango_object = self.__objects.pop(name.lower()) if self._phase > Server.Phase1: import tango util = tango.Util.instance() if not util.is_svr_shutting_down(): util.delete_device(tango_object.tango_class_name, name) def register_object(self, obj, name, tango_class_name=None, member_filter=None): """ :param member_filter: callable(obj, tango_class_name, member_name, member) -> bool """ slash_count = name.count("/") if slash_count == 0: alias = name full_name = "{0}/{1}".format(self.server_instance, name) elif slash_count == 2: alias = None full_name = name else: raise ValueError("Invalid name") class_name = tango_class_name or obj.__class__.__name__ tango_class = self.get_tango_class(class_name) if tango_class is None: tango_class = create_tango_class(self, obj, class_name, member_filter=member_filter) self.register_tango_class(tango_class) tango_object = self.TangoObjectAdapter(self, obj, full_name, alias, tango_class_name=class_name) self.__objects[full_name.lower()] = tango_object if self._phase > Server.Phase1: import tango util = tango.Util.instance() util.create_device(class_name, name) return tango_object def run(self, timeout=None): self.log.debug("run") async_mode = self.async_mode running = self.__running if not running: self.__prepare() self.__initialize() else: if not async_mode: raise RuntimeError("Server is already running") self.__run(timeout=timeout) pytango-9.2.2/tango/test_context.py000066400000000000000000000251331316324373100174210ustar00rootroot00000000000000"""Provide a context to run a device without a database.""" from __future__ import absolute_import # Imports import os import sys import six import time import struct import socket import tempfile import collections from functools import partial # Concurrency imports import threading import multiprocessing from six.moves import queue # CLI imports from ast import literal_eval from importlib import import_module from argparse import ArgumentParser # Local imports from .server import run from . import DeviceProxy, Database, Util __all__ = ["DeviceTestContext", "run_device_test_context"] # Helpers IOR = collections.namedtuple( 'IOR', 'first dtype_length dtype nb_profile tag ' 'length major minor wtf host_length host port body') def ascii_to_bytes(s): convert = lambda x: six.int2byte(int(x, 16)) return b''.join(convert(s[i:i + 2]) for i in range(0, len(s), 2)) def parse_ior(encoded_ior): assert encoded_ior[:4] == 'IOR:' ior = ascii_to_bytes(encoded_ior[4:]) dtype_length = struct.unpack_from('II', ior)[-1] form = 'II{:d}sIIIBBHI'.format(dtype_length) host_length = struct.unpack_from(form, ior)[-1] form = 'II{:d}sIIIBBHI{:d}sH0I'.format(dtype_length, host_length) values = struct.unpack_from(form, ior) values += (ior[struct.calcsize(form):],) strip = lambda x: x[:-1] if isinstance(x, bytes) else x return IOR(*map(strip, values)) def get_server_host_port(): util = Util.instance() ds = util.get_dserver_device() encoded_ior = util.get_dserver_ior(ds) ior = parse_ior(encoded_ior) return ior.host.decode(), ior.port def literal_dict(arg): return dict(literal_eval(arg)) def device(path): """Get the device class from a given module.""" module_name, device_name = path.rsplit(".", 1) module = import_module(module_name) return getattr(module, device_name) def get_hostname(): """Get the hostname corresponding to the primary, external IP. This is useful because an explicit hostname is required to get tango events to work properly. Note that localhost does not work either. """ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # Connecting to a UDP address doesn't send packets s.connect(('8.8.8.8', 0)) # Get ip address ip = s.getsockname()[0] # Return host name return socket.gethostbyaddr(ip)[0].split('.')[0] # Device test context class DeviceTestContext(object): """ Context to run a device without a database.""" nodb = "dbase=no" command = "{0} {1} -ORBendPoint giop:tcp:{2}:{3} -file={4}" thread_timeout = 3. process_timeout = 5. def __init__(self, device, device_cls=None, server_name=None, instance_name=None, device_name=None, properties=None, db=None, host=None, port=0, debug=3, process=False, daemon=False, timeout=None): """Inititalize the context to run a given device.""" # Argument tangoclass = device.__name__ if not server_name: server_name = tangoclass if not instance_name: instance_name = server_name.lower() if not device_name: device_name = 'test/nodb/' + server_name.lower() if db is None: _, db = tempfile.mkstemp() if host is None: host = get_hostname() if properties is None: properties = {} if timeout is None: timeout = self.process_timeout if process else self.thread_timeout # Patch bug #819 if process: os.environ['ORBscanGranularity'] = '0' # Attributes self.db = db self.host = host self.port = port self.timeout = timeout self.device_name = device_name self.server_name = "/".join(("dserver", server_name, instance_name)) self.device = self.server = None self.queue = multiprocessing.Queue() if process else queue.Queue() # File self.generate_db_file(server_name, instance_name, device_name, tangoclass, properties) # Command args string = self.command.format( server_name, instance_name, host, port, db) string += " -v{0}".format(debug) if debug else "" cmd_args = string.split() # Target and arguments if device_cls: class_dct = {tangoclass: (device_cls, device)} runserver = partial(run, class_dct, cmd_args) elif not hasattr(device, 'run_server'): runserver = partial(run, (device,), cmd_args) else: runserver = partial(device.run_server, cmd_args) # Thread cls = multiprocessing.Process if process else threading.Thread self.thread = cls(target=self.target, args=(runserver, process)) self.thread.daemon = daemon def target(self, runserver, process=False): try: runserver(post_init_callback=self.post_init, raises=True) except Exception: # Put exception in the queue etype, value, tb = sys.exc_info() if process: tb = None # Traceback objects can't be pickled self.queue.put((etype, value, tb)) finally: # Put something in the queue just in case exc = RuntimeError("The server failed to report anything") self.queue.put((None, exc, None)) # Make sure the process has enough time to send the items # because the it might segfault while cleaning up the # the tango resources if process: time.sleep(0.01) def post_init(self): try: host, port = get_server_host_port() self.queue.put((host, port)) except Exception as exc: self.queue.put((None, exc, None)) finally: # Put something in the queue just in case exc = RuntimeError( "The post_init routine failed to report anything") self.queue.put((None, exc, None)) def generate_db_file(self, server, instance, device, tangoclass=None, properties={}): """Generate a database file corresponding to the given arguments.""" if not tangoclass: tangoclass = server # Open the file with open(self.db, 'w') as f: f.write("/".join((server, instance, "DEVICE", tangoclass))) f.write(': "' + device + '"\n') # Create database db = Database(self.db) # Patch the property dict to avoid a PyTango bug patched = dict((key, value if value != '' else ' ') for key, value in properties.items()) # Write properties db.put_device_property(device, patched) return db def get_device_access(self): """Return the full device name.""" form = 'tango://{0}:{1}/{2}#{3}' return form.format(self.host, self.port, self.device_name, self.nodb) def get_server_access(self): """Return the full server name.""" form = 'tango://{0}:{1}/{2}#{3}' return form.format(self.host, self.port, self.server_name, self.nodb) def start(self): """Run the server.""" self.thread.start() self.connect() return self def connect(self): try: args = self.queue.get( timeout=self.timeout) except queue.Empty: if self.thread.is_alive(): raise RuntimeError( 'The server appears to be stuck at initialization. ' 'Check stdout/stderr for more information.') elif hasattr(self.thread, 'exitcode'): raise RuntimeError( 'The server process stopped with exitcode {}. ' 'Check stdout/stderr for more information.' ''.format(self.thread.exitcode)) else: raise RuntimeError( 'The server stopped without reporting. ' 'Check stdout/stderr for more information.') try: self.host, self.port = args except ValueError: six.reraise(*args) # Get server proxy self.server = DeviceProxy(self.get_server_access()) self.server.ping() # Get device proxy self.device = DeviceProxy(self.get_device_access()) self.device.ping() def stop(self): """Kill the server.""" try: if self.server: self.server.command_inout('Kill') self.join(self.timeout) finally: os.unlink(self.db) def join(self, timeout=None): self.thread.join(timeout) def __enter__(self): """Enter method for context support.""" if not self.thread.is_alive(): self.start() return self.device def __exit__(self, exc_type, exception, trace): """Exit method for context support.""" self.stop() # Command line interface def parse_command_line_args(args=None): """Parse arguments given in command line.""" desc = "Run a given device on a given port." parser = ArgumentParser(description=desc) # Add arguments msg = 'The device to run as a python path.' parser.add_argument('device', metavar='DEVICE', type=device, help=msg) msg = "The hostname to use." parser.add_argument('--host', metavar='HOST', type=str, help=msg, default=None) msg = "The port to use." parser.add_argument('--port', metavar='PORT', type=int, help=msg, default=8888) msg = "The debug level." parser.add_argument('--debug', metavar='DEBUG', type=int, help=msg, default=3) msg = "The properties to set as python dict." parser.add_argument('--prop', metavar='PROP', type=literal_dict, help=msg, default='{}') # Parse arguments namespace = parser.parse_args(args) return (namespace.device, namespace.host, namespace.port, namespace.prop, namespace.debug) def run_device_test_context(args=None): device, host, port, properties, debug = parse_command_line_args(args) context = DeviceTestContext( device, properties=properties, host=host, port=port, debug=debug) context.start() msg = '{0} started on port {1} with properties {2}' print(msg.format(device.__name__, context.port, properties)) print('Device access: {}'.format(context.get_device_access())) print('Server access: {}'.format(context.get_server_access())) context.join() print("Done") # Main execution if __name__ == "__main__": run_device_test_context() pytango-9.2.2/tango/test_utils.py000066400000000000000000000032351316324373100170740ustar00rootroot00000000000000"""Test utilities""" # Local imports from . import DevState, GreenMode from .server import Device from .test_context import DeviceTestContext # Conditional imports try: import pytest except ImportError: pytest = None try: import numpy.testing except ImportError: numpy = None __all__ = ['DeviceTestContext', 'SimpleDevice'] # Test devices class SimpleDevice(Device): def init_device(self): self.set_state(DevState.ON) # Helpers TYPED_VALUES = { int: (1, 2), float: (2.71, 3.14), str: ('hey hey', 'my my'), bool: (False, True), (int,): ([1, 2, 3], [9, 8, 7]), (float,): ([0.1, 0.2, 0.3], [0.9, 0.8, 0.7]), (str,): (['ab', 'cd', 'ef'], ['gh', 'ij', 'kl']), (bool,): ([False, False, True], [True, False, False])} def repr_type(x): if not isinstance(x, tuple): return x.__name__ return '({},)'.format(x[0].__name__) # Numpy helpers if numpy and pytest: def assert_close(a, b): try: assert a == pytest.approx(b) except ValueError: numpy.testing.assert_allclose(a, b) # Pytest fixtures if pytest: @pytest.fixture(params=DevState.values.values()) def state(request): return request.param @pytest.fixture( params=list(TYPED_VALUES.items()), ids=lambda x: repr_type(x[0])) def typed_values(request): return request.param @pytest.fixture(params=GreenMode.values.values()) def green_mode(request): return request.param @pytest.fixture(params=[ GreenMode.Synchronous, GreenMode.Asyncio, GreenMode.Gevent]) def server_green_mode(request): return request.param pytango-9.2.2/tango/time_val.py000066400000000000000000000121511316324373100164720ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ """ This is an internal PyTango module. """ __all__ = ["time_val_init"] __docformat__ = "restructuredtext" import time import datetime from ._tango import TimeVal import numbers def __TimeVal__init(self, a=None, b=None, c=None): TimeVal.__init_original(self) if a is None: return if isinstance(a, datetime.datetime): assert (b is None and c is None) a = time.mktime(a.timetuple()) + a.microsecond * 1E-6 elif isinstance(a, numbers.Number): if b is None: self.tv_sec = int(a) usec = (a - self.tv_sec) * 1E6 self.tv_usec = int(usec) self.tv_nsec = int((usec - self.tv_usec) * 1E3) else: self.tv_sec, self.tv_usec, self.tv_nsec = a, b, c def __TimeVal__totime(self): """ totime(self) -> float Returns a float representing this time value Parameters : None Return : a float representing the time value .. versionadded:: 7.1.0""" return self.tv_sec + 1E-6 * self.tv_usec + 1E-9 * self.tv_nsec def __TimeVal__todatetime(self): """ todatetime(self) -> datetime.datetime Returns a :class:`datetime.datetime` object representing the same time value Parameters : None Return : (datetime.datetime) the time value in datetime format .. versionadded:: 7.1.0""" return datetime.datetime.fromtimestamp(self.totime()) def __TimeVal__fromtimestamp(ts): """ fromtimestamp(ts) -> TimeVal A static method returning a :class:`tango.TimeVal` object representing the given timestamp Parameters : - ts : (float) a timestamp Return : (TimeVal) representing the given timestamp .. versionadded:: 7.1.0""" return TimeVal(ts) def __TimeVal__fromdatetime(dt): """ fromdatetime(dt) -> TimeVal A static method returning a :class:`tango.TimeVal` object representing the given :class:`datetime.datetime` Parameters : - dt : (datetime.datetime) a datetime object Return : (TimeVal) representing the given timestamp .. versionadded:: 7.1.0 .. versionadded:: 7.1.2 Documented """ return TimeVal(dt) def __TimeVal__now(): """ now() -> TimeVal A static method returning a :class:`tango.TimeVal` object representing the current time Parameters : None Return : (TimeVal) representing the current time .. versionadded:: 7.1.0 .. versionadded:: 7.1.2 Documented """ return TimeVal(time.time()) def __TimeVal__strftime(self, format): """ strftime(self, format) -> str Convert a time value to a string according to a format specification. Parameters : format : (str) See the python library reference manual for formatting codes Return : (str) a string representing the time according to a format specification. .. versionadded:: 7.1.0 .. versionadded:: 7.1.2 Documented """ return self.todatetime().strftime(format) def __TimeVal__isoformat(self, sep='T'): """ isoformat(self, sep='T') -> str Returns a string in ISO 8601 format, YYYY-MM-DDTHH:MM:SS[.mmmmmm][+HH:MM] Parameters : sep : (str) sep is used to separate the year from the time, and defaults to 'T' Return : (str) a string representing the time according to a format specification. .. versionadded:: 7.1.0 .. versionadded:: 7.1.2 Documented .. versionchanged:: 7.1.2 The `sep` parameter is not mandatory anymore and defaults to 'T' (same as :meth:`datetime.datetime.isoformat`) """ return self.todatetime().isoformat(sep) def __TimeVal__str__(self): """ __str__(self) -> str Returns a string representation of TimeVal Parameters : None Return : (str) a string representing the time (same as :class:`datetime.datetime`) .. versionadded:: 7.1.0 .. versionadded:: 7.1.2 Documented """ return str(self.todatetime()) def __init_TimeVal(): TimeVal.__init_original = TimeVal.__init__ TimeVal.__init__ = __TimeVal__init TimeVal.totime = __TimeVal__totime TimeVal.todatetime = __TimeVal__todatetime TimeVal.fromtimestamp = staticmethod(__TimeVal__fromtimestamp) TimeVal.fromdatetime = staticmethod(__TimeVal__fromdatetime) TimeVal.now = staticmethod(__TimeVal__now) TimeVal.strftime = __TimeVal__strftime TimeVal.isoformat = __TimeVal__isoformat TimeVal.__str__ = __TimeVal__str__ def time_val_init(doc=True): __init_TimeVal() pytango-9.2.2/tango/utils.py000066400000000000000000001436741316324373100160510ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ """ This is an internal PyTango module. """ from __future__ import with_statement from __future__ import print_function import os import sys import six import types import numbers import collections from ._tango import StdStringVector, StdDoubleVector, \ DbData, DbDevInfos, DbDevExportInfos, CmdArgType, AttrDataFormat, \ EventData, AttrConfEventData, DataReadyEventData, DevFailed, constants, \ DevState, CommunicationFailed, PipeEventData, DevIntrChangeEventData from . import _tango from .constants import AlrmValueNotSpec, StatusNotSet, TgLibVers from .release import Release __all__ = [ "requires_pytango", "requires_tango", "is_pure_str", "is_seq", "is_non_str_seq", "is_integer", "is_number", "is_scalar_type", "is_array_type", "is_numerical_type", "is_int_type", "is_float_type", "is_bool_type", "is_binary_type", "is_str_type", "obj_2_str", "seqStr_2_obj", "scalar_to_array_type", "document_method", "document_static_method", "document_enum", "CaselessList", "CaselessDict", "EventCallBack", "get_home", "from_version_str_to_hex_str", "from_version_str_to_int", "seq_2_StdStringVector", "StdStringVector_2_seq", "dir2", "TO_TANGO_TYPE"] __docformat__ = "restructuredtext" # Types _scalar_int_types = ( CmdArgType.DevShort, CmdArgType.DevUShort, CmdArgType.DevInt, CmdArgType.DevLong, CmdArgType.DevULong, CmdArgType.DevLong64, CmdArgType.DevULong64,) _scalar_float_types = ( CmdArgType.DevFloat, CmdArgType.DevDouble,) _scalar_numerical_types = ( _scalar_int_types + _scalar_float_types) _scalar_str_types = ( CmdArgType.DevString, CmdArgType.ConstDevString,) _scalar_bool_types = ( CmdArgType.DevBoolean,) _scalar_types = ( _scalar_numerical_types + _scalar_str_types + _scalar_bool_types + (CmdArgType.DevEncoded, CmdArgType.DevUChar,)) _array_int_types = ( CmdArgType.DevVarShortArray, CmdArgType.DevVarUShortArray, CmdArgType.DevVarLongArray, CmdArgType.DevVarULongArray, CmdArgType.DevVarLong64Array, CmdArgType.DevVarULong64Array) _array_float_types = ( CmdArgType.DevVarFloatArray, CmdArgType.DevVarDoubleArray) _array_numerical_types = ( _array_int_types + _array_float_types) _array_str_types = ( CmdArgType.DevVarStringArray,) _array_bool_types = ( CmdArgType.DevVarBooleanArray,) _array_types = ( _array_numerical_types + _array_bool_types + _array_str_types + (CmdArgType.DevVarCharArray, CmdArgType.DevVarDoubleStringArray, CmdArgType.DevVarLongStringArray,)) _binary_types = ( CmdArgType.DevEncoded, CmdArgType.DevVarCharArray,) def __build_to_tango_type(): ret = { int: CmdArgType.DevLong64, str: CmdArgType.DevString, bool: CmdArgType.DevBoolean, bytearray: CmdArgType.DevEncoded, float: CmdArgType.DevDouble, chr: CmdArgType.DevUChar, None: CmdArgType.DevVoid, 'int': CmdArgType.DevLong64, 'int16': CmdArgType.DevShort, 'int32': CmdArgType.DevLong, 'int64': CmdArgType.DevLong64, 'uint': CmdArgType.DevULong64, 'uint16': CmdArgType.DevUShort, 'uint32': CmdArgType.DevULong, 'uint64': CmdArgType.DevULong64, 'str': CmdArgType.DevString, 'string': CmdArgType.DevString, 'text': CmdArgType.DevString, 'bool': CmdArgType.DevBoolean, 'boolean': CmdArgType.DevBoolean, 'bytes': CmdArgType.DevEncoded, 'bytearray': CmdArgType.DevEncoded, 'float': CmdArgType.DevDouble, 'float32': CmdArgType.DevFloat, 'float64': CmdArgType.DevDouble, 'double': CmdArgType.DevDouble, 'byte': CmdArgType.DevUChar, 'chr': CmdArgType.DevUChar, 'char': CmdArgType.DevUChar, 'None': CmdArgType.DevVoid, 'state': CmdArgType.DevState, 'enum': CmdArgType.DevEnum, 'blob': CmdArgType.DevPipeBlob, } try: ret[long] = ret[int] except NameError: pass for key in dir(CmdArgType): if key.startswith("Dev"): value = getattr(CmdArgType, key) ret[key] = ret[value] = value if constants.NUMPY_SUPPORT: import numpy FROM_TANGO_TO_NUMPY_TYPE = { CmdArgType.DevBoolean: numpy.bool8, CmdArgType.DevUChar: numpy.ubyte, CmdArgType.DevShort: numpy.short, CmdArgType.DevUShort: numpy.ushort, CmdArgType.DevLong: numpy.int32, CmdArgType.DevULong: numpy.uint32, CmdArgType.DevLong64: numpy.int64, CmdArgType.DevULong64: numpy.uint64, CmdArgType.DevString: numpy.str, CmdArgType.DevDouble: numpy.float64, CmdArgType.DevFloat: numpy.float32, } for key, value in FROM_TANGO_TO_NUMPY_TYPE.items(): ret[value] = key return ret TO_TANGO_TYPE = __build_to_tango_type() _scalar_to_array_type = { CmdArgType.DevBoolean: CmdArgType.DevVarBooleanArray, CmdArgType.DevUChar: CmdArgType.DevVarCharArray, CmdArgType.DevShort: CmdArgType.DevVarShortArray, CmdArgType.DevUShort: CmdArgType.DevVarUShortArray, CmdArgType.DevInt: CmdArgType.DevVarLongArray, CmdArgType.DevLong: CmdArgType.DevVarLongArray, CmdArgType.DevULong: CmdArgType.DevVarULongArray, CmdArgType.DevLong64: CmdArgType.DevVarLong64Array, CmdArgType.DevULong64: CmdArgType.DevVarULong64Array, CmdArgType.DevFloat: CmdArgType.DevVarFloatArray, CmdArgType.DevDouble: CmdArgType.DevVarDoubleArray, CmdArgType.DevString: CmdArgType.DevVarStringArray, CmdArgType.ConstDevString: CmdArgType.DevVarStringArray, } # add derived scalar types to scalar to array map for k, v in TO_TANGO_TYPE.items(): if v in _scalar_to_array_type: _scalar_to_array_type[k] = _scalar_to_array_type[v] __NO_STR_VALUE = AlrmValueNotSpec, StatusNotSet __device_classes = None bool_ = lambda value_str: value_str.lower() == "true" def __import(name): __import__(name) return sys.modules[name] def __requires(package_name, min_version=None, conflicts=(), software_name="Software"): from distutils.version import LooseVersion package_name_l = package_name.lower() if package_name_l == 'pytango': curr_version = LooseVersion(Release.version) elif package_name_l == 'tango': curr_version = LooseVersion(TgLibVers) else: try: package = __import(package_name) curr_version = LooseVersion(package.__version__) except ImportError: msg = "Could not find package {0} required by {1}".format( package_name, software_name) raise Exception(msg) except: msg = "Error importing package {0} required by {1}".format( package_name, software_name) raise Exception(msg) if min_version is not None: min_version = LooseVersion(min_version) if min_version > curr_version: msg = "{0} requires {1} {2} but {3} installed".format( software_name, package_name, min_version, curr_version) raise Exception(msg) conflicts = map(LooseVersion, conflicts) if curr_version in conflicts: msg = "{0} cannot run with {1} {2}".format( software_name, package_name, curr_version) raise Exception(msg) return True def requires_pytango(min_version=None, conflicts=(), software_name="Software"): """ Determines if the required PyTango version for the running software is present. If not an exception is thrown. Example usage:: from tango import requires_pytango requires_pytango('7.1', conflicts=['8.1.1'], software='MyDS') :param min_version: minimum PyTango version [default: None, meaning no minimum required]. If a string is given, it must be in the valid version number format (see: :class:`~distutils.version.LooseVersion`) :type min_version: None, str, :class:`~distutils.version.LooseVersion` :param conflics: a sequence of PyTango versions which conflict with the software using it :type conflics: seq :param software_name: software name using tango. Used in the exception message :type software_name: str :raises Exception: if the required PyTango version is not met New in PyTango 8.1.4 """ return __requires("pytango", min_version=min_version, conflicts=conflicts, software_name=software_name) def requires_tango(min_version=None, conflicts=(), software_name="Software"): """ Determines if the required Tango version for the running software is present. If not an exception is thrown. Example usage:: from tango import requires_tango requires_tango('7.1', conflicts=['8.1.1'], software='MyDS') :param min_version: minimum Tango version [default: None, meaning no minimum required]. If a string is given, it must be in the valid version number format (see: :class:`~distutils.version.LooseVersion`) :type min_version: None, str, :class:`~distutils.version.LooseVersion` :param conflics: a sequence of Tango versions which conflict with the software using it :type conflics: seq :param software_name: software name using Tango. Used in the exception message :type software_name: str :raises Exception: if the required Tango version is not met New in PyTango 8.1.4 """ return __requires("Tango", min_version=min_version, conflicts=conflicts, software_name=software_name) def get_tango_device_classes(): global __device_classes if __device_classes is None: __device_classes = [_tango.DeviceImpl] i = 2 while True: dc = "Device_{0}Impl".format(i) try: __device_classes.append(getattr(_tango, dc)) i = i + 1 except AttributeError: break return __device_classes def get_latest_device_class(): return get_tango_device_classes()[-1] try: __str_klasses = basestring, except NameError: __str_klasses = str, __int_klasses = int, __number_klasses = numbers.Number, __seq_klasses = collections.Sequence, bytearray, StdStringVector __use_unicode = False try: unicode __use_unicode = True __str_klasses = tuple(list(__str_klasses) + [unicode]) except NameError: pass __use_long = False try: long __use_long = True __int_klasses = tuple(list(__int_klasses) + [long]) except NameError: pass if constants.NUMPY_SUPPORT: import numpy __int_klasses = tuple(list(__int_klasses) + [numpy.integer]) __number_klasses = tuple(list(__number_klasses) + [numpy.number]) __seq_klasses = tuple(list(__seq_klasses) + [numpy.ndarray]) __str_klasses = tuple(__str_klasses) __int_klasses = tuple(__int_klasses) __number_klasses = tuple(__number_klasses) __seq_klasses = tuple(__seq_klasses) def __get_tango_type(obj): if is_non_str_seq(obj): tg_type, tg_format = get_tango_type(obj[0]) tg_format = AttrDataFormat(int(tg_format) + 1) return tg_type, tg_format elif is_pure_str(obj): r = CmdArgType.DevString elif isinstance(obj, DevState): r = CmdArgType.DevState elif isinstance(obj, bool): r = CmdArgType.DevBoolean elif isinstance(obj, __int_klasses): r = CmdArgType.DevLong64 elif isinstance(obj, __number_klasses): r = CmdArgType.DevDouble return r, AttrDataFormat.SCALAR def __get_tango_type_numpy_support(obj): try: ndim, dtype = obj.ndim, str(obj.dtype) if ndim > 2: raise TypeError('cannot translate numpy array with {0} ' 'dimensions to tango type'.format(obj.ndim)) return TO_TANGO_TYPE[dtype], AttrDataFormat(ndim) except AttributeError: return __get_tango_type(obj) def get_tango_type(obj): if constants.NUMPY_SUPPORT: return __get_tango_type_numpy_support(obj) return __get_tango_type(obj) def is_pure_str(obj): """ Tells if the given object is a python string. In python 2.x this means any subclass of basestring. In python 3.x this means any subclass of str. :param obj: the object to be inspected :type obj: :py:obj:`object` :return: True is the given obj is a string or False otherwise :rtype: :py:obj:`bool` """ return isinstance(obj, __str_klasses) def is_seq(obj): """ Tells if the given object is a python sequence. It will return True for any collections.Sequence (list, tuple, str, bytes, unicode), bytearray and (if numpy is enabled) numpy.ndarray :param obj: the object to be inspected :type obj: :py:obj:`object` :return: True is the given obj is a sequence or False otherwise :rtype: :py:obj:`bool` """ return isinstance(obj, __seq_klasses) def is_non_str_seq(obj): """ Tells if the given object is a python sequence (excluding string sequences). It will return True for any collections.Sequence (list, tuple (and bytes in python3)), bytearray and (if numpy is enabled) numpy.ndarray :param obj: the object to be inspected :type obj: :py:obj:`object` :return: True is the given obj is a sequence or False otherwise :rtype: :py:obj:`bool` """ return is_seq(obj) and not is_pure_str(obj) def is_integer(obj): """ Tells if the given object is a python integer. It will return True for any int, long (in python 2) and (if numpy is enabled) numpy.integer :param obj: the object to be inspected :type obj: :py:obj:`object` :return: True is the given obj is a python integer or False otherwise :rtype: :py:obj:`bool` """ return isinstance(obj, __int_klasses) def is_number(obj): """ Tells if the given object is a python number. It will return True for any numbers.Number and (if numpy is enabled) numpy.number :param obj: the object to be inspected :type obj: :py:obj:`object` :return: True is the given obj is a python number or False otherwise :rtype: :py:obj:`bool` """ return isinstance(obj, __number_klasses) def is_scalar(tg_type): """Tells if the given tango type is a scalar :param tg_type: tango type :type tg_type: :class:`tango.CmdArgType` :return: True if the given tango type is a scalar or False otherwise :rtype: :py:obj:`bool` """ global _scalar_types return tg_type in _scalar_types is_scalar_type = is_scalar def is_array(tg_type): """Tells if the given tango type is an array type :param tg_type: tango type :type tg_type: :class:`tango.CmdArgType` :return: True if the given tango type is an array type or False otherwise :rtype: :py:obj:`bool` """ global _array_types return tg_type in _array_types is_array_type = is_array def is_numerical(tg_type, inc_array=False): """Tells if the given tango type is numerical :param tg_type: tango type :type tg_type: :class:`tango.CmdArgType` :param inc_array: (optional, default is False) determines if include array in the list of checked types :type inc_array: :py:obj:`bool` :return: True if the given tango type is a numerical or False otherwise :rtype: :py:obj:`bool` """ global _scalar_numerical_types, _array_numerical_types if tg_type in _scalar_numerical_types: return True if not inc_array: return False return tg_type in _array_numerical_types is_numerical_type = is_numerical def is_int(tg_type, inc_array=False): """Tells if the given tango type is integer :param tg_type: tango type :type tg_type: :class:`tango.CmdArgType` :param inc_array: (optional, default is False) determines if include array in the list of checked types :type inc_array: :py:obj:`bool` :return: True if the given tango type is integer or False otherwise :rtype: :py:obj:`bool` """ global _scalar_int_types, _array_int_types if tg_type in _scalar_int_types: return True if not inc_array: return False return tg_type in _array_int_types is_int_type = is_int def is_float(tg_type, inc_array=False): """Tells if the given tango type is float :param tg_type: tango type :type tg_type: :class:`tango.CmdArgType` :param inc_array: (optional, default is False) determines if include array in the list of checked types :type inc_array: :py:obj:`bool` :return: True if the given tango type is float or False otherwise :rtype: :py:obj:`bool` """ global _scalar_float_types, _array_float_types if tg_type in _scalar_float_types: return True if not inc_array: return False return tg_type in _array_float_types is_float_type = is_float def is_bool(tg_type, inc_array=False): """Tells if the given tango type is boolean :param tg_type: tango type :type tg_type: :class:`tango.CmdArgType` :param inc_array: (optional, default is False) determines if include array in the list of checked types :type inc_array: :py:obj:`bool` :return: True if the given tango type is boolean or False otherwise :rtype: :py:obj:`bool` """ global _scalar_bool_types, _array_bool_types if tg_type in _scalar_bool_types: return True if not inc_array: return False return tg_type in _array_bool_types is_bool_type = is_bool def is_str(tg_type, inc_array=False): """Tells if the given tango type is string :param tg_type: tango type :type tg_type: :class:`tango.CmdArgType` :param inc_array: (optional, default is False) determines if include array in the list of checked types :type inc_array: :py:obj:`bool` :return: True if the given tango type is string or False otherwise :rtype: :py:obj:`bool` """ global _scalar_str_types, _array_str_types if tg_type in _scalar_str_types: return True if not inc_array: return False return tg_type in _array_str_types is_str_type = is_str def is_binary(tg_type, inc_array=False): """Tells if the given tango type is binary :param tg_type: tango type :type tg_type: :class:`tango.CmdArgType` :param inc_array: (optional, default is False) determines if include array in the list of checked types :type inc_array: :py:obj:`bool` :return: True if the given tango type is binary or False otherwise :rtype: :py:obj:`bool` """ global _binary_types return tg_type in _binary_types is_binary_type = is_binary def seq_2_StdStringVector(seq, vec=None): """Converts a python sequence object to a :class:`tango.StdStringVector` :param seq: the sequence of strings :type seq: sequence<:py:obj:`str`> :param vec: (optional, default is None) an :class:`tango.StdStringVector` to be filled. If None is given, a new :class:`tango.StdStringVector` is created :return: a :class:`tango.StdStringVector` filled with the same contents as seq :rtype: :class:`tango.StdStringVector` """ if vec is None: if isinstance(seq, StdStringVector): return seq vec = StdStringVector() if not isinstance(vec, StdStringVector): raise TypeError('vec must be a tango.StdStringVector') for e in seq: vec.append(str(e)) return vec def StdStringVector_2_seq(vec, seq=None): """Converts a :class:`tango.StdStringVector` to a python sequence :param seq: the :class:`tango.StdStringVector` :type seq: :class:`tango.StdStringVector` :param vec: (optional, default is None) a python sequence to be filled. If None is given, a new list is created :return: a python sequence filled with the same contents as seq :rtype: sequence """ if seq is None: seq = [] if not isinstance(vec, StdStringVector): raise TypeError('vec must be a tango.StdStringVector') for e in vec: seq.append(str(e)) return seq def seq_2_StdDoubleVector(seq, vec=None): """Converts a python sequence object to a :class:`tango.StdDoubleVector` :param seq: the sequence of floats :type seq: sequence<:py:obj:`float`> :param vec: (optional, default is None) an :class:`tango.StdDoubleVector` to be filled. If None is given, a new :class:`tango.StdDoubleVector` is created :return: a :class:`tango.StdDoubleVector` filled with the same contents as seq :rtype: :class:`tango.StdDoubleVector` """ if vec is None: if isinstance(seq, StdDoubleVector): return seq vec = StdDoubleVector() if not isinstance(vec, StdDoubleVector): raise TypeError('vec must be a tango.StdDoubleVector') for e in seq: vec.append(str(e)) return vec def StdDoubleVector_2_seq(vec, seq=None): """Converts a :class:`tango.StdDoubleVector` to a python sequence :param seq: the :class:`tango.StdDoubleVector` :type seq: :class:`tango.StdDoubleVector` :param vec: (optional, default is None) a python sequence to be filled. If None is given, a new list is created :return: a python sequence filled with the same contents as seq :rtype: sequence """ if seq is None: seq = [] if not isinstance(vec, StdDoubleVector): raise TypeError('vec must be a tango.StdDoubleVector') for e in vec: seq.append(float(e)) return seq def seq_2_DbDevInfos(seq, vec=None): """Converts a python sequence object to a :class:`tango.DbDevInfos` :param seq: the sequence of DbDevInfo :type seq: sequence :param vec: (optional, default is None) an :class:`tango.DbDevInfos` to be filled. If None is given, a new :class:`tango.DbDevInfos` is created :return: a :class:`tango.DbDevInfos` filled with the same contents as seq :rtype: :class:`tango.DbDevInfos` """ if vec is None: if isinstance(seq, DbDevInfos): return seq vec = DbDevInfos() if not isinstance(vec, DbDevInfos): raise TypeError('vec must be a tango.DbDevInfos') for e in seq: vec.append(e) return vec def seq_2_DbDevExportInfos(seq, vec=None): """Converts a python sequence object to a :class:`tango.DbDevExportInfos` :param seq: the sequence of DbDevExportInfo :type seq: sequence :param vec: (optional, default is None) an :class:`tango.DbDevExportInfos` to be filled. If None is given, a new :class:`tango.DbDevExportInfos` is created :return: a :class:`tango.DbDevExportInfos` filled with the same contents as seq :rtype: :class:`tango.DbDevExportInfos` """ if vec is None: if isinstance(seq, DbDevExportInfos): return seq vec = DbDevExportInfos() if not isinstance(vec, DbDevExportInfos): raise TypeError('vec must be a tango.DbDevExportInfos') for e in seq: vec.append(e) return vec def seq_2_DbData(seq, vec=None): """Converts a python sequence object to a :class:`tango.DbData` :param seq: the sequence of DbDatum :type seq: sequence :param vec: (optional, default is None) an :class:`tango.DbData` to be filled. If None is given, a new :class:`tango.DbData` is created :return: a :class:`tango.DbData` filled with the same contents as seq :rtype: :class:`tango.DbData` """ if vec is None: if isinstance(seq, DbData): return seq vec = DbData() if not isinstance(vec, DbData): raise TypeError('vec must be a tango.DbData') for e in seq: vec.append(e) return vec def DbData_2_dict(db_data, d=None): if d is None: d = {} if not isinstance(db_data, DbData): raise TypeError( 'db_data must be a tango.DbData. A %s found instead' % type(db_data)) for db_datum in db_data: d[db_datum.name] = db_datum.value_string return d def seqStr_2_obj(seq, tg_type, tg_format=None): """Translates a sequence to a sequence of objects of give type and format :param seq: the sequence :type seq: sequence :param tg_type: tango type :type tg_type: :class:`tango.CmdArgType` :param tg_format: (optional, default is None, meaning SCALAR) tango format :type tg_format: :class:`tango.AttrDataFormat` :return: a new sequence """ if tg_format: return _seqStr_2_obj_from_type_format(seq, tg_type, tg_format) return _seqStr_2_obj_from_type(seq, tg_type) def _seqStr_2_obj_from_type(seq, tg_type): if is_pure_str(seq): seq = seq, # Scalar cases global _scalar_int_types if tg_type in _scalar_int_types: return int(seq[0]) global _scalar_float_types if tg_type in _scalar_float_types: return float(seq[0]) global _scalar_str_types if tg_type in _scalar_str_types: return seq[0] if tg_type == CmdArgType.DevBoolean: return seq[0].lower() == 'true' # sequence cases if tg_type in (CmdArgType.DevVarCharArray, CmdArgType.DevVarStringArray): return seq global _array_int_types if tg_type in _array_int_types: argout = [] for x in seq: argout.append(int(x)) return argout global _array_float_types if tg_type in _array_float_types: argout = [] for x in seq: argout.append(float(x)) return argout if tg_type == CmdArgType.DevVarBooleanArray: argout = [] for x in seq: argout.append(x.lower() == 'true') return argout return [] def _seqStr_2_obj_from_type_format(seq, tg_type, tg_format): if tg_format == AttrDataFormat.SCALAR: return _seqStr_2_obj_from_type(tg_type, seq) elif tg_format == AttrDataFormat.SPECTRUM: return _seqStr_2_obj_from_type(_scalar_to_array_type[tg_type], seq) elif tg_format == AttrDataFormat.IMAGE: if tg_type == CmdArgType.DevString: return seq global _scalar_int_types if tg_type in _scalar_int_types: argout = [] for x in seq: tmp = [] for y in x: tmp.append(int(y)) argout.append(tmp) return argout global _scalar_float_types if tg_type in _scalar_float_types: argout = [] for x in seq: tmp = [] for y in x: tmp.append(float(y)) argout.append(tmp) return argout # UNKNOWN_FORMAT return _seqStr_2_obj_from_type(tg_type, seq) def scalar_to_array_type(tg_type): """ Gives the array tango type corresponding to the given tango scalar type. Example: giving DevLong will return DevVarLongArray. :param tg_type: tango type :type tg_type: :class:`tango.CmdArgType` :return: the array tango type for the given scalar tango type :rtype: :class:`tango.CmdArgType` :raises ValueError: in case the given dtype is not a tango scalar type """ try: return _scalar_to_array_type[tg_type] except KeyError: raise ValueError("Invalid tango scalar type: {0}".format(tg_type)) def str_2_obj(obj_str, tg_type=None): """Converts a string into an object according to the given tango type :param obj_str: the string to be converted :type obj_str: :py:obj:`str` :param tg_type: tango type :type tg_type: :class:`tango.CmdArgType` :return: an object calculated from the given string :rtype: :py:obj:`object` """ if tg_type is None: return obj_str f = str if is_scalar_type(tg_type): if is_numerical_type(tg_type): if obj_str in __NO_STR_VALUE: return None if is_int_type(tg_type): f = int elif is_float_type(tg_type): f = float elif is_bool_type(tg_type): f = bool_ return f(obj_str) def obj_2_str(obj, tg_type=None): """Converts a python object into a string according to the given tango type :param obj: the object to be converted :type obj: :py:obj:`object` :param tg_type: tango type :type tg_type: :class:`tango.CmdArgType` :return: a string representation of the given object :rtype: :py:obj:`str` """ if tg_type is None: return obj if tg_type in _scalar_types: # scalar cases if is_pure_str(obj): return obj elif is_non_str_seq(obj): if not len(obj): return "" obj = obj[0] return str(obj) # sequence cases if obj is None: return '' return '\n'.join([str(i) for i in obj]) def __get_meth_func(klass, method_name): meth = getattr(klass, method_name) func = meth if hasattr(meth, '__func__'): func = meth.__func__ elif hasattr(meth, 'im_func'): func = meth.im_func return meth, func def copy_doc(klass, fnname): """Copies documentation string of a method from the super class into the rewritten method of the given class""" base_meth, base_func = __get_meth_func(klass.__base__, fnname) meth, func = __get_meth_func(klass, fnname) func.__doc__ = base_func.__doc__ def document_method(klass, method_name, d, add=True): meth, func = __get_meth_func(klass, method_name) if add: cpp_doc = meth.__doc__ if cpp_doc: func.__doc__ = "%s\n%s" % (d, cpp_doc) return func.__doc__ = d if func.__name__ != method_name: try: func.__name__ = method_name except AttributeError: pass def document_static_method(klass, method_name, d, add=True): meth, func = __get_meth_func(klass, method_name) if add: cpp_doc = meth.__doc__ if cpp_doc: meth.__doc__ = "%s\n%s" % (d, cpp_doc) return meth.__doc__ = d def document_enum(klass, enum_name, desc, append=True): # derived = type(base)('derived', (base,), {'__doc__': 'desc'}) # Get the original enum type base = getattr(klass, enum_name) # Prepare the new docstring if append and base.__doc__ is not None: desc = base.__doc__ + "\n" + desc # Create a new type, derived from the original. Only difference # is the docstring. derived = type(base)(enum_name, (base,), {'__doc__': desc}) # Replace the original enum type with the new one setattr(klass, enum_name, derived) class CaselessList(list): """A case insensitive lists that has some caseless methods. Only allows strings as list members. Most methods that would normally return a list, return a CaselessList. (Except list() and lowercopy()) Sequence Methods implemented are : __contains__, remove, count, index, append, extend, insert, __getitem__, __setitem__, __getslice__, __setslice__ __add__, __radd__, __iadd__, __mul__, __rmul__ Plus Extra methods: findentry, copy , lowercopy, list Inherited methods : __imul__, __len__, __iter__, pop, reverse, sort """ def __init__(self, inlist=[]): list.__init__(self) for entry in inlist: if not isinstance(entry, str): raise TypeError( 'Members of this object must be strings. ' 'You supplied \"%s\" which is \"%s\"' % (entry, type(entry))) self.append(entry) def findentry(self, item): """A caseless way of checking if an item is in the list or not. It returns None or the entry.""" if not isinstance(item, str): raise TypeError( 'Members of this object must be strings. ' 'You supplied \"%s\"' % type(item)) for entry in self: if item.lower() == entry.lower(): return entry return None def __contains__(self, item): """A caseless way of checking if a list has a member in it or not.""" for entry in self: if item.lower() == entry.lower(): return True return False def remove(self, item): """Remove the first occurence of an item, the caseless way.""" for entry in self: if item.lower() == entry.lower(): list.remove(self, entry) return raise ValueError(': list.remove(x): x not in list') def copy(self): """Return a CaselessList copy of self.""" return CaselessList(self) def list(self): """Return a normal list version of self.""" return list(self) def lowercopy(self): """Return a lowercase (list) copy of self.""" return [entry.lower() for entry in self] def append(self, item): """Adds an item to the list and checks it's a string.""" if not isinstance(item, str): raise TypeError( 'Members of this object must be strings. ' 'You supplied \"%s\"' % type(item)) list.append(self, item) def extend(self, item): """Extend the list with another list. Each member of the list must be a string.""" if not isinstance(item, list): raise TypeError( 'You can only extend lists with lists. ' 'You supplied \"%s\"' % type(item)) for entry in item: if not isinstance(entry, str): raise TypeError( 'Members of this object must be strings. ' 'You supplied \"%s\"' % type(entry)) list.append(self, entry) def count(self, item): """Counts references to 'item' in a caseless manner. If item is not a string it will always return 0.""" if not isinstance(item, str): return 0 count = 0 for entry in self: if item.lower() == entry.lower(): count += 1 return count def index(self, item, minindex=0, maxindex=None): """Provide an index of first occurence of item in the list. (or raise a ValueError if item not present) If item is not a string, will raise a TypeError. minindex and maxindex are also optional arguments s.index(x[, i[, j]]) return smallest k such that s[k] == x and i <= k < j """ if maxindex is None: maxindex = len(self) minindex = max(0, minindex) - 1 maxindex = min(len(self), maxindex) if not isinstance(item, str): raise TypeError( 'Members of this object must be strings. ' 'You supplied \"%s\"' % type(item)) index = minindex while index < maxindex: index += 1 if item.lower() == self[index].lower(): return index raise ValueError(': list.index(x): x not in list') def insert(self, i, x): """s.insert(i, x) same as s[i:i] = [x] Raises TypeError if x isn't a string.""" if not isinstance(x, str): raise TypeError( 'Members of this object must be strings. ' 'You supplied \"%s\"' % type(x)) list.insert(self, i, x) def __setitem__(self, index, value): """For setting values in the list. index must be an integer or (extended) slice object. (__setslice__ used for simple slices) If index is an integer then value must be a string. If index is a slice object then value must be a list of strings - with the same length as the slice object requires. """ if isinstance(index, int): if not isinstance(value, str): raise TypeError( 'Members of this object must be strings. ' 'You supplied \"%s\"' % type(value)) list.__setitem__(self, index, value) elif isinstance(index, slice): if not hasattr(value, '__len__'): raise TypeError( 'Value given to set slice is not a sequence object.') for entry in value: if not isinstance(entry, str): raise TypeError( 'Members of this object must be strings. ' 'You supplied \"%s\"' % type(entry)) list.__setitem__(self, index, value) else: raise TypeError('Indexes must be integers or slice objects.') def __setslice__(self, i, j, sequence): """Called to implement assignment to self[i:j].""" for entry in sequence: if not isinstance(entry, str): raise TypeError( 'Members of this object must be strings. ' 'You supplied \"%s\"' % type(entry)) list.__setslice__(self, i, j, sequence) def __getslice__(self, i, j): """Called to implement evaluation of self[i:j]. Although the manual says this method is deprecated - if I don't define it the list one is called. (Which returns a list - this returns a CaselessList)""" return CaselessList(list.__getslice__(self, i, j)) def __getitem__(self, index): """For fetching indexes. If a slice is fetched then the list returned is a CaselessList.""" if not isinstance(index, slice): return list.__getitem__(self, index) else: return CaselessList(list.__getitem__(self, index)) def __add__(self, item): """To add a list, and return a CaselessList. Every element of item must be a string.""" return CaselessList(list.__add__(self, item)) def __radd__(self, item): """To add a list, and return a CaselessList. Every element of item must be a string.""" return CaselessList(list.__add__(self, item)) def __iadd__(self, item): """To add a list in place.""" for entry in item: self.append(entry) def __mul__(self, item): """To multiply itself, and return a CaselessList. Every element of item must be a string.""" return CaselessList(list.__mul__(self, item)) def __rmul__(self, item): """To multiply itself, and return a CaselessList. Every element of item must be a string.""" return CaselessList(list.__rmul__(self, item)) class CaselessDict(dict): def __init__(self, other=None): if other: # Doesn't do keyword args if isinstance(other, dict): for k, v in other.items(): dict.__setitem__(self, k.lower(), v) else: for k, v in other: dict.__setitem__(self, k.lower(), v) def __getitem__(self, key): return dict.__getitem__(self, key.lower()) def __setitem__(self, key, value): dict.__setitem__(self, key.lower(), value) def __contains__(self, key): return dict.__contains__(self, key.lower()) def __delitem__(self, k): dict.__delitem__(self, k.lower()) def has_key(self, key): return key.lower() in self def get(self, key, def_val=None): return dict.get(self, key.lower(), def_val) def setdefault(self, key, def_val=None): return dict.setdefault(self, key.lower(), def_val) def update(self, other): for k, v in other.items(): dict.__setitem__(self, k.lower(), v) def fromkeys(self, iterable, value=None): d = CaselessDict() for k in iterable: dict.__setitem__(d, k.lower(), value) return d def pop(self, key, def_val=None): return dict.pop(self, key.lower(), def_val) def keys(self): return CaselessList(dict.keys(self)) __DEFAULT_FACT_IOR_FILE = "/tmp/rdifact.ior" __BASE_LINE = "notifd" __END_NOTIFD_LINE = "/DEVICE/notifd:" __NOTIFD_FACTORY_PREFIX = "notifd/factory/" def notifd2db(notifd_ior_file=__DEFAULT_FACT_IOR_FILE, files=None, host=None, out=sys.stdout): ior_string = "" with open(notifd_ior_file) as ior_file: ior_string = ior_file.read() if files is None: return _notifd2db_real_db(ior_string, host=host, out=out) else: return _notifd2db_file_db(ior_string, files, out=out) def _notifd2db_file_db(ior_string, files, out=sys.stdout): raise RuntimeError("Not implemented yet") print("going to export notification service event factory to " "device server property file(s) ...", file=out) for f in files: with open(f, "w"): pass return def _notifd2db_real_db(ior_string, host=None, out=sys.stdout): from . import Database print("going to export notification service event factory to " "Tango database ...", file=out) num_retries = 3 while num_retries > 0: try: db = Database() db.set_timeout_millis(10000) num_retries = 0 except DevFailed as df: num_retries -= 1 if num_retries == 0: print("Can't create Tango database object", file=out) print(str(df), file=out) return print("Can't create Tango database object, retrying....", file=out) if host is None: import socket host_name = socket.getfqdn() global __NOTIFD_FACTORY_PREFIX notifd_factory_name = __NOTIFD_FACTORY_PREFIX + host_name args = notifd_factory_name, ior_string, host_name, str(os.getpid()), "1" num_retries = 3 while num_retries > 0: try: db.command_inout("DbExportEvent", args) print("Successfully exported notification service event " "factory for host", host_name, "to Tango database !", file=out) break except CommunicationFailed as cf: if len(cf.errors) >= 2: if cf.errors[1].reason == "API_DeviceTimedOut": if num_retries > 0: num_retries -= 1 else: num_retries = 0 else: num_retries = 0 except Exception: num_retries = 0 if num_retries == 0: print("Failed to export notification service event factory " "to TANGO database", file=out) class EventCallback(object): """ Useful event callback for test purposes Usage:: >>> dev = tango.DeviceProxy(dev_name) >>> cb = tango.utils.EventCallback() >>> id = dev.subscribe_event("state", tango.EventType.CHANGE_EVENT, cb, []) 2011-04-06 15:33:18.910474 sys/tg_test/1 STATE CHANGE [ATTR_VALID] ON Allowed format keys are: - date (event timestamp) - reception_date (event reception timestamp) - type (event type) - dev_name (device name) - name (attribute name) - value (event value) New in PyTango 7.1.4 """ def __init__(self, format="{date} {dev_name} {name} {type} {value}", fd=sys.stdout, max_buf=100): self._msg = format self._fd = fd self._evts = [] self._max_buf = max_buf def get_events(self): """Returns the list of events received by this callback :return: the list of events received by this callback :rtype: sequence """ return self._evts def push_event(self, evt): """Internal usage only""" try: self._push_event(evt) except Exception as e: print("Unexpected error in callback for %s: %s" % (str(evt), str(e)), file=self._fd) def _push_event(self, evt): """Internal usage only""" self._append(evt) import datetime now = datetime.datetime.now() try: date = self._get_date(evt) except: date = now try: reception_date = evt.reception_date.todatetime() except: reception_date = now try: evt_type = evt.event.upper() except: evt_type = "" try: dev_name = evt.device.dev_name().upper() except: dev_name = "" try: attr_name = evt.attr_name.split("/")[-1].upper() except: attr_name = "" try: value = self._get_value(evt) except Exception as e: value = "Unexpected exception in getting event value: %s" % str(e) d = {"date": date, "reception_date": reception_date, "type": evt_type, "dev_name": dev_name, "name": attr_name, "value": value} print(self._msg.format(**d), file=self._fd) def _append(self, evt): """Internal usage only""" evts = self._evts if len(evts) == self._max_buf: evts.pop(0) evts.append(evt) def _get_date(self, evt): if isinstance(evt, EventData): return evt.attr_value.time.todatetime() elif isinstance(evt, PipeEventData): return evt.pipe_value.time.todatetime() else: return evt.get_date().todatetime() def _get_value(self, evt): """Internal usage only""" if evt.err: e = evt.errors[0] return "[%s] %s" % (e.reason, e.desc) if isinstance(evt, EventData): return "[%s] %s" % ( evt.attr_value.quality, str(evt.attr_value.value)) elif isinstance(evt, AttrConfEventData): cfg = evt.attr_conf return "label='%s'; unit='%s'" % (cfg.label, cfg.unit) elif isinstance(evt, DataReadyEventData): return "" elif isinstance(evt, PipeEventData): return evt.pipe_value elif isinstance(evt, DevIntrChangeEventData): print("utils::_get_value()") return def get_home(): """ Find user's home directory if possible. Otherwise raise error. :return: user's home directory :rtype: :py:obj:`str` New in PyTango 7.1.4 """ path = '' try: path = os.path.expanduser("~") except: pass if not os.path.isdir(path): for evar in ('HOME', 'USERPROFILE', 'TMP'): try: path = os.environ[evar] if os.path.isdir(path): break except: pass if path: return path else: raise RuntimeError('please define environment variable $HOME') def _get_env_var(env_var_name): """ Returns the value for the given environment name Search order: * a real environ var * HOME/.tangorc * /etc/tangorc :param env_var_name: the environment variable name :type env_var_name: str :return: the value for the given environment name :rtype: str New in PyTango 7.1.4 """ if env_var_name in os.environ: return os.environ[env_var_name] fname = os.path.join(get_home(), '.tangorc') if not os.path.exists(fname): if os.name == 'posix': fname = "/etc/tangorc" if not os.path.exists(fname): return None for line in open(fname): strippedline = line.split('#', 1)[0].strip() if not strippedline: # empty line continue tup = strippedline.split('=', 1) if len(tup) != 2: # illegal line! continue key, val = map(str.strip, tup) if key == env_var_name: return val def from_version_str_to_hex_str(version_str): v = map(int, version_str.split('.')) return "0x%02d%02d%02d00" % (v[0], v[1], v[2]) def from_version_str_to_int(version_str): return int(from_version_str_to_hex_str(version_str), 16) def info(): # Compile and Runtime are set by `tango.pytango_init.init` from .constants import Compile, Runtime msg = """\ PyTango {0.version} {0.version_info} PyTango compiled with: Python : {1.PY_VERSION} Numpy : {1.NUMPY_VERSION} Tango : {1.TANGO_VERSION} Boost : {1.BOOST_VERSION} PyTango runtime is: Python : {2.PY_VERSION} Numpy : {2.NUMPY_VERSION} Tango : {2.TANGO_VERSION} Boost : {2.BOOST_VERSION} PyTango running on: {2.UNAME} """ msg = msg.format(Release, Compile, Runtime) return msg def get_attrs(obj): """Helper for dir2 implementation.""" if not hasattr(obj, '__dict__'): return [] # slots only proxy_type = types.MappingProxyType if six.PY3 else types.DictProxyType if not isinstance(obj.__dict__, (dict, proxy_type)): print(type(obj.__dict__), obj) raise TypeError("%s.__dict__ is not a dictionary" % obj.__name__) return obj.__dict__.keys() def dir2(obj): """Default dir implementation. Inspired by gist: katyukha/dirmixin.py https://gist.github.com/katyukha/c6e5e2b829e247c9b009 """ attrs = set() if not hasattr(obj, '__bases__'): # obj is an instance if not hasattr(obj, '__class__'): # slots return sorted(get_attrs(obj)) klass = obj.__class__ attrs.update(get_attrs(klass)) else: # obj is a class klass = obj for cls in klass.__bases__: attrs.update(get_attrs(cls)) attrs.update(dir2(cls)) attrs.update(get_attrs(obj)) return list(attrs) pytango-9.2.2/tests/000077500000000000000000000000001316324373100143525ustar00rootroot00000000000000pytango-9.2.2/tests/test_client.py000066400000000000000000000206561316324373100172520ustar00rootroot00000000000000"""Client tests that run against the standard TangoTest device. Due to a TANGO 9 issue (#821), the device is run without any database. Note that this means that various features won't work: * No device configuration via properties. * No event generated by the server. * No memorized attributes. * No device attribute configuration via the database. So don't even try to test anything of the above as it will not work and is even likely to crash the device (!) """ from distutils.spawn import find_executable from subprocess import Popen import platform from time import sleep import psutil import pytest from functools import partial from tango import DeviceProxy, DevFailed, GreenMode from tango import DeviceInfo, AttributeInfo, AttributeInfoEx from tango.utils import is_str_type, is_int_type, is_float_type, is_bool_type from tango.gevent import DeviceProxy as gevent_DeviceProxy from tango.futures import DeviceProxy as futures_DeviceProxy from tango.asyncio import DeviceProxy as asyncio_DeviceProxy ATTRIBUTES = [ 'ampli', 'boolean_scalar', 'double_scalar', 'double_scalar_rww', 'double_scalar_w', 'float_scalar', 'long64_scalar', 'long_scalar', 'long_scalar_rww', 'long_scalar_w', 'no_value', 'short_scalar', 'short_scalar_ro', 'short_scalar_rww', 'short_scalar_w', 'string_scalar', 'throw_exception', 'uchar_scalar', 'ulong64_scalar', 'ushort_scalar', 'ulong_scalar', 'boolean_spectrum', 'boolean_spectrum_ro', 'double_spectrum', 'double_spectrum_ro', 'float_spectrum', 'float_spectrum_ro', 'long64_spectrum_ro', 'long_spectrum', 'long_spectrum_ro', 'short_spectrum', 'short_spectrum_ro', 'string_spectrum', 'string_spectrum_ro', 'uchar_spectrum', 'uchar_spectrum_ro', 'ulong64_spectrum_ro', 'ulong_spectrum_ro', 'ushort_spectrum', 'ushort_spectrum_ro', 'boolean_image', 'boolean_image_ro', 'double_image', 'double_image_ro', 'float_image', 'float_image_ro', 'long64_image_ro', 'long_image', 'long_image_ro', 'short_image', 'short_image_ro', 'string_image', 'string_image_ro', 'uchar_image', 'uchar_image_ro', 'ulong64_image_ro', 'ulong_image_ro', 'ushort_image', 'ushort_image_ro', 'wave', 'State', 'Status', ] device_proxy_map = { GreenMode.Synchronous: DeviceProxy, GreenMode.Futures: futures_DeviceProxy, GreenMode.Asyncio: partial(asyncio_DeviceProxy, wait=True), GreenMode.Gevent: gevent_DeviceProxy} # Helpers def get_ports(pid): p = psutil.Process(pid) conns = p.connections(kind="tcp") # Sorting by family in order to make any IPv6 address go first. # Otherwise there's a 50% chance that the proxy will just # hang (presumably because it's connecting on the wrong port) # This works on my machine, not sure if it's a general # solution though. conns = reversed(sorted(conns, key=lambda c: c.family)) return [c.laddr[1] for c in conns] def start_server(server, inst, device): exe = find_executable(server) cmd = ("{0} {1} -ORBendPoint giop:tcp::0 -nodb -dlist {2}" .format(exe, inst, device)) proc = Popen(cmd.split()) proc.poll() return proc def get_proxy(host, port, device, green_mode): access = "tango://{0}:{1}/{2}#dbase=no".format( host, port, device) return device_proxy_map[green_mode](access) def wait_for_proxy(host, proc, device, green_mode, retries=200, delay=0.01): for i in range(retries): ports = get_ports(proc.pid) if ports: try: proxy = get_proxy(host, ports[0], device, green_mode) proxy.ping() proxy.state() return proxy except DevFailed: pass sleep(delay) else: raise RuntimeError("TangoTest device did not start up!") # Fixtures @pytest.fixture(params=[GreenMode.Synchronous, GreenMode.Asyncio, GreenMode.Gevent], scope="module") def tango_test(request): green_mode = request.param server = "TangoTest" inst = "test" device = "sys/tg_test/17" host = platform.node() proc = start_server(server, inst, device) proxy = wait_for_proxy(host, proc, device, green_mode) yield proxy proc.terminate() # let's not wait for it to exit, that takes too long :) @pytest.fixture(params=ATTRIBUTES) def attribute(request): return request.param @pytest.fixture(params=[a for a in ATTRIBUTES if a not in ("no_value", "throw_exception")]) def readable_attribute(request): return request.param @pytest.fixture(params=[a for a in ATTRIBUTES if "scalar" in a and a.split("_")[-1] not in ("ro", "rww")]) def writable_scalar_attribute(request): return request.param # Tests def test_ping(tango_test): duration = tango_test.ping(wait=True) assert isinstance(duration, int) def test_info(tango_test): info = tango_test.info() assert isinstance(info, DeviceInfo) assert info.dev_class == "TangoTest" # ... def test_read_attribute(tango_test, readable_attribute): "Check that readable attributes can be read" # For read-only string spectrum and read-only string image types, # the following error is very likely to be raised: # -> MARSHAL CORBA system exception: MARSHAL_PassEndOfMessage # An explicit sleep fixes the problem but it's annoying to maintain if readable_attribute in ["string_image_ro", "string_spectrum_ro"]: pytest.xfail() tango_test.read_attribute(readable_attribute, wait=True) def test_write_scalar_attribute(tango_test, writable_scalar_attribute): "Check that writable scalar attributes can be written" attr_name = writable_scalar_attribute config = tango_test.get_attribute_config(writable_scalar_attribute) if is_bool_type(config.data_type): tango_test.write_attribute(attr_name, True, wait=True) elif is_int_type(config.data_type): tango_test.write_attribute(attr_name, 76, wait=True) elif is_float_type(config.data_type): tango_test.write_attribute(attr_name, -28.2, wait=True) elif is_str_type(config.data_type): tango_test.write_attribute(attr_name, "hello", wait=True) else: pytest.xfail("Not currently testing this type") def test_read_attribute_config(tango_test, attribute): tango_test.get_attribute_config(attribute) def test_attribute_list_query(tango_test): attrs = tango_test.attribute_list_query() assert all(isinstance(a, AttributeInfo) for a in attrs) assert set(a.name for a in attrs) == set(ATTRIBUTES) def test_attribute_list_query_ex(tango_test): attrs = tango_test.attribute_list_query_ex() assert all(isinstance(a, AttributeInfoEx) for a in attrs) assert set(a.name for a in attrs) == set(ATTRIBUTES) def test_device_proxy_dir_method(tango_test): lst = dir(tango_test) attrs = tango_test.get_attribute_list() cmds = tango_test.get_command_list() pipes = tango_test.get_pipe_list() methods = dir(type(tango_test)) internals = tango_test.__dict__.keys() # Check attributes assert set(attrs) < set(lst) assert set(map(str.lower, attrs)) < set(lst) # Check commands assert set(cmds) < set(lst) assert set(map(str.lower, cmds)) < set(lst) # Check pipes assert set(pipes) < set(lst) assert set(map(str.lower, pipes)) < set(lst) # Check internals assert set(methods) <= set(lst) # Check internals assert set(internals) <= set(lst) def test_device_polling_command(tango_test): dct = {"SwitchStates": 1000, "DevVoid": 10000, "DumpExecutionState": 5000} for command, period in dct.items(): tango_test.poll_command(command, period) ans = tango_test.polling_status() for info in ans: lines = info.split('\n') command = lines[0].split('= ')[1] period = int(lines[1].split('= ')[1]) assert dct[command] == period def test_device_polling_attribute(tango_test): dct = {"boolean_scalar": 1000, "double_scalar": 10000, "long_scalar": 5000} for attr, poll_period in dct.items(): tango_test.poll_attribute(attr, poll_period) ans = tango_test.polling_status() for x in ans: lines = x.split('\n') attr = lines[0].split('= ')[1] poll_period = int(lines[1].split('= ')[1]) assert dct[attr] == poll_period pytango-9.2.2/tests/test_event.py000066400000000000000000000133471316324373100171140ustar00rootroot00000000000000 # Imports import time import socket from functools import partial import pytest from six import StringIO from tango import EventType, GreenMode, DeviceProxy, AttrQuality from tango.server import Device from tango.server import command, attribute from tango.test_utils import DeviceTestContext from tango.utils import EventCallback from tango.gevent import DeviceProxy as gevent_DeviceProxy from tango.futures import DeviceProxy as futures_DeviceProxy from tango.asyncio import DeviceProxy as asyncio_DeviceProxy # Helpers device_proxy_map = { GreenMode.Synchronous: DeviceProxy, GreenMode.Futures: futures_DeviceProxy, GreenMode.Asyncio: partial(asyncio_DeviceProxy, wait=True), GreenMode.Gevent: gevent_DeviceProxy} def get_open_port(): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(("", 0)) s.listen(1) port = s.getsockname()[1] s.close() return port # Test device class EventDevice(Device): def init_device(self): self.set_change_event("attr", True, False) @attribute def attr(self): return 0. @command def send_event(self): self.push_change_event("attr", 1.) @command def send_event_with_timestamp(self): self.push_change_event("attr", 2., 3., AttrQuality.ATTR_WARNING) @command(dtype_in=str) def add_dyn_attr(self, name): attr = attribute( name=name, dtype='float', fget=self.read) self.add_attribute(attr) @command(dtype_in=str) def delete_dyn_attr(self, name): self._remove_attribute(name) def read(self, attr): attr.set_value(1.23) # Device fixture @pytest.fixture(params=[GreenMode.Synchronous, GreenMode.Futures, GreenMode.Asyncio, GreenMode.Gevent], scope="module") def event_device(request): green_mode = request.param # Hack: a port have to be specified explicitely for events to work port = get_open_port() context = DeviceTestContext(EventDevice, port=port, process=True) with context: yield device_proxy_map[green_mode](context.get_device_access()) # Tests def test_subscribe_change_event(event_device): results = [] def callback(evt): results.append(evt.attr_value.value) # Subscribe eid = event_device.subscribe_event( "attr", EventType.CHANGE_EVENT, callback, wait=True) assert eid == 1 # Trigger an event event_device.command_inout("send_event", wait=True) # Wait for tango event retries = 20 for _ in range(retries): event_device.read_attribute("state", wait=True) if len(results) > 1: break time.sleep(0.05) # Test the event values assert results == [0., 1.] # Unsubscribe event_device.unsubscribe_event(eid) def test_subscribe_interface_event(event_device): results = [] def callback(evt): results.append(evt) # Subscribe eid = event_device.subscribe_event( "attr", EventType.INTERFACE_CHANGE_EVENT, callback, wait=True) assert eid == 1 # Trigger an event event_device.command_inout("add_dyn_attr", 'bla', wait=True) event_device.read_attribute('bla', wait=True) == 1.23 # Wait for tango event retries = 30 for _ in range(retries): event_device.read_attribute("state", wait=True) if len(results) > 1: break time.sleep(0.05) event_device.command_inout("delete_dyn_attr", 'bla', wait=True) # Wait for tango event retries = 30 for _ in range(retries): event_device.read_attribute("state", wait=True) if len(results) > 2: break time.sleep(0.05) # Test the first event value assert set(cmd.cmd_name for cmd in results[0].cmd_list) == \ {'Init', 'State', 'Status', 'add_dyn_attr', 'delete_dyn_attr', 'send_event', 'send_event_with_timestamp'} assert set(att.name for att in results[0].att_list) == \ {'attr', 'State', 'Status'} # Test the second event value assert set(cmd.cmd_name for cmd in results[1].cmd_list) == \ {'Init', 'State', 'Status', 'add_dyn_attr', 'delete_dyn_attr', 'send_event', 'send_event_with_timestamp'} assert set(att.name for att in results[1].att_list) == \ {'attr', 'State', 'Status', 'bla'} # Test the third event value assert set(cmd.cmd_name for cmd in results[2].cmd_list) == \ {'Init', 'State', 'Status', 'add_dyn_attr', 'delete_dyn_attr', 'send_event', 'send_event_with_timestamp'} assert set(att.name for att in results[2].att_list) == \ {'attr', 'State', 'Status'} # Unsubscribe event_device.unsubscribe_event(eid) def test_push_event_with_timestamp(event_device): string = StringIO() ec = EventCallback(fd=string) # Subscribe eid = event_device.subscribe_event( "attr", EventType.CHANGE_EVENT, ec, wait=True) assert eid == 1 # Trigger an event event_device.command_inout("send_event_with_timestamp", wait=True) # Wait for tango event retries = 20 for _ in range(retries): event_device.read_attribute("state", wait=True) if len(ec.get_events()) > 1: break time.sleep(0.05) # Test the event values and timestamp results = [evt.attr_value.value for evt in ec.get_events()] assert results == [0., 2.] assert ec.get_events()[-1].attr_value.time.totime() == 3. # Check string line1 = "TEST/NODB/EVENTDEVICE ATTR#DBASE=NO CHANGE [ATTR_VALID] 0.0" line2 = "TEST/NODB/EVENTDEVICE ATTR#DBASE=NO CHANGE [ATTR_WARNING] 2.0" assert line1 in string.getvalue() assert line2 in string.getvalue() # Unsubscribe event_device.unsubscribe_event(eid) pytango-9.2.2/tests/test_server.py000066400000000000000000000205301316324373100172710ustar00rootroot00000000000000# -*- coding: utf-8 -*- import sys import textwrap import pytest from tango import DevState, AttrWriteType, GreenMode, DevFailed from tango.server import Device from tango.server import command, attribute, device_property from tango.test_utils import DeviceTestContext, assert_close # Asyncio imports try: import asyncio except ImportError: import trollius as asyncio # noqa: F401 # Pytest fixtures from tango.test_utils import state, typed_values, server_green_mode state, typed_values, server_green_mode # Constants PY3 = sys.version_info >= (3,) YIELD_FROM = "yield from" if PY3 else "yield asyncio.From" RETURN = "return" if PY3 else "raise asyncio.Return" # Test state/status def test_empty_device(server_green_mode): class TestDevice(Device): green_mode = server_green_mode with DeviceTestContext(TestDevice) as proxy: assert proxy.state() == DevState.UNKNOWN assert proxy.status() == 'The device is in UNKNOWN state.' def test_set_state(state, server_green_mode): status = 'The device is in {0!s} state.'.format(state) class TestDevice(Device): green_mode = server_green_mode def init_device(self): self.set_state(state) with DeviceTestContext(TestDevice) as proxy: assert proxy.state() == state assert proxy.status() == status def test_set_status(server_green_mode): status = '\n'.join(( "This is a multiline status", "with special characters such as", "Café à la crème")) class TestDevice(Device): green_mode = server_green_mode def init_device(self): self.set_state(DevState.ON) self.set_status(status) with DeviceTestContext(TestDevice) as proxy: assert proxy.state() == DevState.ON assert proxy.status() == status # Test commands def test_identity_command(typed_values, server_green_mode): dtype, values = typed_values if dtype == (bool,): pytest.xfail('Not supported for some reasons') class TestDevice(Device): green_mode = server_green_mode @command(dtype_in=dtype, dtype_out=dtype) def identity(self, arg): return arg with DeviceTestContext(TestDevice) as proxy: for value in values: assert_close(proxy.identity(value), value) def test_polled_command(server_green_mode): dct = {'Polling1': 100, 'Polling2': 100000, 'Polling3': 500} class TestDevice(Device): green_mode = server_green_mode @command(polling_period=dct["Polling1"]) def Polling1(self): pass @command(polling_period=dct["Polling2"]) def Polling2(self): pass @command(polling_period=dct["Polling3"]) def Polling3(self): pass with DeviceTestContext(TestDevice) as proxy: ans = proxy.polling_status() for info in ans: lines = info.split('\n') comm = lines[0].split('= ')[1] period = int(lines[1].split('= ')[1]) assert dct[comm] == period # Test attributes def test_read_write_attribute(typed_values, server_green_mode): dtype, values = typed_values class TestDevice(Device): green_mode = server_green_mode @attribute(dtype=dtype, max_dim_x=10, access=AttrWriteType.READ_WRITE) def attr(self): return self.attr_value @attr.write def attr(self, value): self.attr_value = value with DeviceTestContext(TestDevice) as proxy: for value in values: proxy.attr = value assert_close(proxy.attr, value) # Test properties def test_device_property_no_default(typed_values, server_green_mode): dtype, values = typed_values patched_dtype = dtype if dtype != (bool,) else (int,) default = values[0] value = values[1] class TestDevice(Device): green_mode = server_green_mode prop = device_property(dtype=dtype) @command(dtype_out=patched_dtype) def get_prop(self): return default if self.prop is None else self.prop with DeviceTestContext(TestDevice, process=True) as proxy: assert_close(proxy.get_prop(), default) with DeviceTestContext(TestDevice, properties={'prop': value}, process=True) as proxy: assert_close(proxy.get_prop(), value) def test_device_property_with_default_value(typed_values, server_green_mode): dtype, values = typed_values patched_dtype = dtype if dtype != (bool,) else (int,) default = values[0] value = values[1] class TestDevice(Device): green_mode = server_green_mode prop = device_property(dtype=dtype, default_value=default) @command(dtype_out=patched_dtype) def get_prop(self): print(self.prop) return self.prop with DeviceTestContext(TestDevice, process=True) as proxy: assert_close(proxy.get_prop(), default) with DeviceTestContext(TestDevice, properties={'prop': value}, process=True) as proxy: assert_close(proxy.get_prop(), value) # Test inheritance def test_inheritance(server_green_mode): class A(Device): green_mode = server_green_mode prop1 = device_property(dtype=str, default_value="hello1") prop2 = device_property(dtype=str, default_value="hello2") @command(dtype_out=str) def get_prop1(self): return self.prop1 @command(dtype_out=str) def get_prop2(self): return self.prop2 @attribute(access=AttrWriteType.READ_WRITE) def attr(self): return self.attr_value @attr.write def attr(self, value): self.attr_value = value def dev_status(self): return ")`'-.,_" class B(A): prop2 = device_property(dtype=str, default_value="goodbye2") @attribute def attr2(self): return 3.14 def dev_status(self): return 3 * A.dev_status(self) if server_green_mode == GreenMode.Asyncio: code = textwrap.dedent("""\ @asyncio.coroutine def dev_status(self): coro = super(type(self), self).dev_status() result = {YIELD_FROM}(coro) {RETURN}(3*result) """).format(**globals()) exec(code) with DeviceTestContext(B) as proxy: assert proxy.get_prop1() == "hello1" assert proxy.get_prop2() == "goodbye2" proxy.attr = 1.23 assert proxy.attr == 1.23 assert proxy.attr2 == 3.14 assert proxy.status() == ")`'-.,_)`'-.,_)`'-.,_" def test_polled_attribute(server_green_mode): dct = {'PolledAttribute1': 100, 'PolledAttribute2': 100000, 'PolledAttribute3': 500} class TestDevice(Device): green_mode = server_green_mode @attribute(polling_period=dct["PolledAttribute1"]) def PolledAttribute1(self): return 42.0 @attribute(polling_period=dct["PolledAttribute2"]) def PolledAttribute2(self): return 43.0 @attribute(polling_period=dct["PolledAttribute3"]) def PolledAttribute3(self): return 44.0 with DeviceTestContext(TestDevice) as proxy: ans = proxy.polling_status() for x in ans: lines = x.split('\n') attr = lines[0].split('= ')[1] poll_period = int(lines[1].split('= ')[1]) assert dct[attr] == poll_period def test_mandatory_device_property(typed_values, server_green_mode): dtype, values = typed_values patched_dtype = dtype if dtype != (bool,) else (int,) default, value = values[:2] class TestDevice(Device): green_mode = server_green_mode prop = device_property(dtype=dtype, mandatory=True) @command(dtype_out=patched_dtype) def get_prop(self): return self.prop with DeviceTestContext(TestDevice, properties={'prop': value}, process=True) as proxy: assert_close(proxy.get_prop(), value) with pytest.raises(DevFailed) as context: with DeviceTestContext(TestDevice, process=True) as proxy: pass assert 'Device property prop is mandatory' in str(context.value) pytango-9.2.2/win/000077500000000000000000000000001316324373100140055ustar00rootroot00000000000000pytango-9.2.2/win/PyTango_VS10/000077500000000000000000000000001316324373100161375ustar00rootroot00000000000000pytango-9.2.2/win/PyTango_VS10/PyTango.props000077500000000000000000000533141316324373100206160ustar00rootroot00000000000000 _tango 9 2 0 $(TangoMajorVer).$(TangoMinorVer).$(TangoPatchVer) 9 2 0 $(PyTangoMajorVer).$(PyTangoMinorVer).$(PyTangoPatchVer) 1 61 0 $(BoostMajorVer).$(BoostMinorVer).$(BoostPatchVer) C: $(RootDir)\wsp\tango $(RootDir)\wsp\boost-$(BoostVer) C:\Python 10 0 $(VCMajorVer).$(VCMinorVer) $(BoostBaseDir)\include\boost-$(BoostMajorVer)_$(BoostMinorVer) $(BoostBaseDir)\multi\release\msvc-$(VCVer)\$(Platform) $(BoostBaseDir)\multi\debug\msvc-$(VCVer)\$(Platform) $(TangoBaseDir)\$(Platform)\include\vc$(VCMajorVer) $(TangoBaseDir)\$(Platform)\lib $(TangoBaseDir)\$(Platform)\lib C:\Python\$(Platform) Lib\site-packages\numpy\core\include -Zm200 /MP8 /bigobj $(ProjectDir)..\.. $(Configuration)_$(Platform)_msvc$(VCVer) $(RootDir)\pytango\build_$(PyTangoVer)_tg$(TangoVer)_boost$(BoostVer) $(BaseBuildDir)\lib $(BaseBuildDir)\temp $(BaseBuildDir)\dist $(BaseBuildDir)\temp_dist $(BaseBuildLibDir)\$(RelPrefixDir) $(BaseDistDir)\$(RelPrefixDir) $(BaseTempDir)\$(RelPrefixDir) $(BaseBDistDir)\$(RelPrefixDir) $(BuildDir)\tango $(TempDir)\tango $(BaseDir)\ext $(BoostIncludeDir);$(TangoIncludeDir);$(BaseSrcDir);$(BaseSrcDir)\server $(PythonDir)\33 $(Python33Dir)\python $(Python33Dir)\include $(Python33Dir)\$(NumpyRelativeIncludeDir) $(Python33IncludeDir);$(Numpy33IncludeDir) $(Python33Dir)\libs $(PythonDir)\34 $(Python34Dir)\python $(Python34Dir)\include $(Python34Dir)\$(NumpyRelativeIncludeDir) $(Python34IncludeDir);$(Numpy34IncludeDir) $(Python34Dir)\libs WIN32 _WINSTATIC LOG4TANGO_HAS_DLL;TANGO_HAS_DLL $(BasePreFlags);$(PreTangoSharedFlags);BOOST_PYTHON_DYNAMIC_LIB $(BasePreFlags);$(PreTangoSharedFlags);BOOST_PYTHON_STATIC_LIB $(BasePreFlags);$(PreTangoStaticFlags);BOOST_PYTHON_STATIC_LIB precompiled_header.hpp $(TempDir)\$(PyTangoBaseFile)_Boost-$(BoostVer).pch $(TempDir)\$(PyTangoBaseFile)_Boost-$(BoostVer).pdb 4005;4996;4250 comctl32.lib;ws2_32.lib;mswsock.lib COS4.lib;omniORB4.lib;omniDynamic4.lib;omnithread.lib COS4_rt.lib;omniORB4_rt.lib;omniDynamic4_rt.lib;omnithread_rt.lib zmq.lib;log4tango.lib;tango.lib;$(BaseLibs) $(omniStaticLibs);$(BaseTangoLibs) $(omniSharedLibs);$(BaseTangoLibs) COS4d.lib;omniORB4d.lib;omniDynamic4d.lib;omnithreadd.lib COS4_rtd.lib;omniORB4_rtd.lib;omniDynamic4_rtd.lib;omnithread_rtd.lib zmqd.lib;log4tangod.lib;tangod.lib;$(BaseLibs) $(omniStaticLibs_Debug);$(BaseTangoLibs_Debug) $(omniSharedLibs_Debug);$(BaseTangoLibs_Debug) $(BasePyTangoStaticLibs);python33.lib $(BasePyTangoSharedLibs);python33.lib $(BasePyTangoStaticLibs);python34.lib $(BasePyTangoSharedLibs);python34.lib $(BoostLibDir)\shared\runtime_shared $(BoostLibDir)\static\runtime_shared $(BoostLibDir)\static\runtime_static $(BaseBoostSharedRtSharedLibDir)\33;$(Python33LibDir) $(BaseBoostStaticRtSharedLibDir)\33;$(Python33LibDir) $(BaseBoostStaticRtStaticLibDir)\33;$(Python33LibDir) $(BaseBoostSharedRtSharedLibDir)\34;$(Python34LibDir) $(BaseBoostStaticRtSharedLibDir)\34;$(Python34LibDir) $(BaseBoostStaticRtStaticLibDir)\34;$(Python34LibDir) $(Boost33SharedRtSharedLibDir);$(TangoLibDir)\vc$(VCMajorVer)_dll $(Boost33StaticRtSharedLibDir);$(TangoLibDir)\vc$(VCMajorVer)_dll $(Boost33StaticRtStaticLibDir);$(TangoLibDir)\vc$(VCMajorVer) $(Boost34SharedRtSharedLibDir);$(TangoLibDir)\vc$(VCMajorVer)_dll $(Boost34StaticRtSharedLibDir);$(TangoLibDir)\vc$(VCMajorVer)_dll $(Boost34StaticRtStaticLibDir);$(TangoLibDir)\vc$(VCMajorVer) $(BasePyTangoStaticLibs_Debug);python33.lib $(BasePyTangoSharedLibs_Debug);python33.lib $(BasePyTangoStaticLibs_Debug);python34.lib $(BasePyTangoSharedLibs_Debug);python34.lib $(BoostLibDir)\shared\runtime_shared $(BoostLibDir)\static\runtime_shared $(BoostLibDir)\static\runtime_static $(BaseBoostSharedRtSharedLibDir_Debug)\33;$(Python33LibDir) $(BaseBoostStaticRtSharedLibDir_Debug)\33;$(Python33LibDir) $(BaseBoostStaticRtStaticLibDir_Debug)\33;$(Python33LibDir) $(BaseBoostSharedRtSharedLibDir_Debug)\34;$(Python34LibDir) $(BaseBoostStaticRtSharedLibDir_Debug)\34;$(Python34LibDir) $(BaseBoostStaticRtStaticLibDir_Debug)\34;$(Python34LibDir) $(Boost33SharedRtSharedLibDir_Debug);$(TangoLibDir_Debug)\vc$(VCMajorVer)_dll $(Boost33StaticRtSharedLibDir_Debug);$(TangoLibDir_Debug)\vc$(VCMajorVer)_dll $(Boost33StaticRtStaticLibDir_Debug);$(TangoLibDir_Debug)\vc$(VCMajorVer) $(Boost34SharedRtSharedLibDir_Debug);$(TangoLibDir_Debug)\vc$(VCMajorVer)_dll $(Boost34StaticRtSharedLibDir_Debug);$(TangoLibDir_Debug)\vc$(VCMajorVer)_dll $(Boost34StaticRtStaticLibDir_Debug);$(TangoLibDir_Debug)\vc$(VCMajorVer) $(BaseDir)\winsetup.py $(BuildDir) $(DistDir) $(BDistDir) $(Configuration) $(Platform) <_ProjectFileVersion>10.0.30319.1 <_PropertySheetDisplayName>PyTango Project Properties $(PyTangoBuildDir)\ $(PyTangoTempDir)\ false $(PyTangoCompileOptions) %(AdditionalOptions) Full false false $(PreCompHeaderDst) $(PreCompHeaderSrc) $(DatabaseFile) Level3 ProgramDatabase MultiThreadedDLL Default $(BaseDisableWarningFlags);%(DisableSpecificWarnings) Prompt NotSet $(OutDir)$(PyTangoBaseFile).pyd true $(DatabaseFile) Windows true true false MachineX86 [PyTango]: Starting python $(WinSetupArgs) $(IntDir)BuildLog.html $(PyTangoBaseFile) $(TangoMajorVer) $(TangoMinorVer) $(TangoPatchVer) $(TangoVer) $(BoostMajorVer) $(BoostMinorVer) $(BoostPatchVer) $(BoostVer) $(TangoBaseDir) $(BoostBaseDir) $(PythonBaseDir) $(VCMajorVer) $(VCMinorVer) $(VCVer) $(BoostIncludeDir) $(BoostLibDir) $(TangoIncludeDir) $(TangoLibDir) $(PythonDir) $(NumpyRelativeIncludeDir) $(PyTangoCompileOptions) $(BaseDir) $(RelPrefixDir) $(BaseBuildDir) $(BaseBuildLibDir) $(BaseTempDir) $(BaseDistDir) $(BaseBDistDir) $(BuildDir) $(DistDir) $(TempDir) $(BDistDir) $(PyTangoBuildDir) $(PyTangoTempDir) $(BaseSrcDir) $(CommonIncludeDirs) $(Python33Dir) $(Py33) $(Python33IncludeDir) $(Numpy33IncludeDir) $(Python33IncludeDirs) $(Python33LibDir) $(Python34Dir) $(Py34) $(Python34IncludeDir) $(Numpy34IncludeDir) $(Python34IncludeDirs) $(Python34LibDir) $(BasePreFlags) $(PreTangoStaticFlags) $(PreTangoSharedFlags) $(PreBoostSharedTangoSharedFlags) $(PreBoostStaticTangoSharedFlags) $(PreBoostStaticTangoStaticFlags) $(PreCompHeaderSrc) $(PreCompHeaderDst) $(DatabaseFile) $(BaseDisableWarningFlags) $(omniStaticLibs) $(omniSharedLibs) $(BaseTangoLibs) $(BasePyTangoStaticLibs) $(BasePyTangoSharedLibs) $(PyTango33StaticLibs) $(PyTango33SharedLibs) $(PyTango34StaticLibs) $(PyTango34SharedLibs) $(BaseBoostSharedRtSharedLibDir) $(BaseBoostStaticRtSharedLibDir) $(BaseBoostStaticRtStaticLibDir) $(Boost33SharedRtSharedLibDir) $(Boost33StaticRtSharedLibDir) $(Boost33StaticRtStaticLibDir) $(Py33BoostSharedTangoSharedLibDir) $(Py33BoostStaticTangoSharedLibDir) $(Py33BoostStaticTangoStaticLibDir) $(Boost34SharedRtSharedLibDir) $(Boost34StaticRtSharedLibDir) $(Boost34StaticRtStaticLibDir) $(Py34BoostSharedTangoSharedLibDir) $(Py34BoostStaticTangoSharedLibDir) $(Py34BoostStaticTangoStaticLibDir) $(WinSetupArgs) pytango-9.2.2/win/PyTango_VS10/PyTango.sln000077500000000000000000000213541316324373100202460ustar00rootroot00000000000000 Microsoft Visual Studio Solution File, Format Version 11.00 # Visual Studio 2010 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PyTango", "PyTango.vcxproj", "{764A5ECE-11AE-4363-9A1C-573972A1BADD}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution py33_bopyshared_tangoshared_release|Win32 = py33_bopyshared_tangoshared_release|Win32 py33_bopyshared_tangoshared_release|x64 = py33_bopyshared_tangoshared_release|x64 py33_bopystatic_tangoshared_release|Win32 = py33_bopystatic_tangoshared_release|Win32 py33_bopystatic_tangoshared_release|x64 = py33_bopystatic_tangoshared_release|x64 py33_bopystatic_tangostatic_release|Win32 = py33_bopystatic_tangostatic_release|Win32 py33_bopystatic_tangostatic_release|x64 = py33_bopystatic_tangostatic_release|x64 py33_bopyshared_tangoshared_debug|Win32 = py33_bopyshared_tangoshared_debug|Win32 py33_bopyshared_tangoshared_debug|x64 = py33_bopyshared_tangoshared_debug|x64 py33_bopystatic_tangoshared_debug|Win32 = py33_bopystatic_tangoshared_debug|Win32 py33_bopystatic_tangoshared_debug|x64 = py33_bopystatic_tangoshared_debug|x64 py33_bopystatic_tangostatic_debug|Win32 = py33_bopystatic_tangostatic_debug|Win32 py33_bopystatic_tangostatic_debug|x64 = py33_bopystatic_tangostatic_debug|x64 py34_bopyshared_tangoshared_release|Win32 = py34_bopyshared_tangoshared_release|Win32 py34_bopyshared_tangoshared_release|x64 = py34_bopyshared_tangoshared_release|x64 py34_bopystatic_tangoshared_release|Win32 = py34_bopystatic_tangoshared_release|Win32 py34_bopystatic_tangoshared_release|x64 = py34_bopystatic_tangoshared_release|x64 py34_bopystatic_tangostatic_release|Win32 = py34_bopystatic_tangostatic_release|Win32 py34_bopystatic_tangostatic_release|x64 = py34_bopystatic_tangostatic_release|x64 py34_bopyshared_tangoshared_debug|Win32 = py34_bopyshared_tangoshared_debug|Win32 py34_bopyshared_tangoshared_debug|x64 = py34_bopyshared_tangoshared_debug|x64 py34_bopystatic_tangoshared_debug|Win32 = py34_bopystatic_tangoshared_debug|Win32 py34_bopystatic_tangoshared_debug|x64 = py34_bopystatic_tangoshared_debug|x64 py34_bopystatic_tangostatic_debug|Win32 = py34_bopystatic_tangostatic_debug|Win32 py34_bopystatic_tangostatic_debug|x64 = py34_bopystatic_tangostatic_debug|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py33_bopyshared_tangoshared_release|Win32.ActiveCfg = py33_bopyshared_tangoshared_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py33_bopyshared_tangoshared_release|Win32.Build.0 = py33_bopyshared_tangoshared_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py33_bopyshared_tangoshared_release|x64.ActiveCfg = py33_bopyshared_tangoshared_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py33_bopyshared_tangoshared_release|x64.Build.0 = py33_bopyshared_tangoshared_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py33_bopystatic_tangoshared_release|Win32.ActiveCfg = py33_bopystatic_tangoshared_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py33_bopystatic_tangoshared_release|Win32.Build.0 = py33_bopystatic_tangoshared_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py33_bopystatic_tangoshared_release|x64.ActiveCfg = py33_bopystatic_tangoshared_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py33_bopystatic_tangoshared_release|x64.Build.0 = py33_bopystatic_tangoshared_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py33_bopystatic_tangostatic_release|Win32.ActiveCfg = py33_bopystatic_tangostatic_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py33_bopystatic_tangostatic_release|Win32.Build.0 = py33_bopystatic_tangostatic_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py33_bopystatic_tangostatic_release|x64.ActiveCfg = py33_bopystatic_tangostatic_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py33_bopystatic_tangostatic_release|x64.Build.0 = py33_bopystatic_tangostatic_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py33_bopyshared_tangoshared_debug|Win32.ActiveCfg = py33_bopyshared_tangoshared_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py33_bopyshared_tangoshared_debug|Win32.Build.0 = py33_bopyshared_tangoshared_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py33_bopyshared_tangoshared_debug|x64.ActiveCfg = py33_bopyshared_tangoshared_debug|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py33_bopyshared_tangoshared_debug|x64.Build.0 = py33_bopyshared_tangoshared_debug|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py33_bopystatic_tangoshared_debug|Win32.ActiveCfg = py33_bopystatic_tangoshared_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py33_bopystatic_tangoshared_debug|Win32.Build.0 = py33_bopystatic_tangoshared_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py33_bopystatic_tangoshared_debug|x64.ActiveCfg = py33_bopystatic_tangoshared_debug|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py33_bopystatic_tangoshared_debug|x64.Build.0 = py33_bopystatic_tangoshared_debug|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py33_bopystatic_tangostatic_debug|Win32.ActiveCfg = py33_bopystatic_tangostatic_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py33_bopystatic_tangostatic_debug|Win32.Build.0 = py33_bopystatic_tangostatic_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py33_bopystatic_tangostatic_debug|x64.ActiveCfg = py33_bopystatic_tangostatic_debug|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py33_bopystatic_tangostatic_debug|x64.Build.0 = py33_bopystatic_tangostatic_debug|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py34_bopyshared_tangoshared_release|Win32.ActiveCfg = py34_bopyshared_tangoshared_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py34_bopyshared_tangoshared_release|Win32.Build.0 = py34_bopyshared_tangoshared_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py34_bopyshared_tangoshared_release|x64.ActiveCfg = py34_bopyshared_tangoshared_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py34_bopyshared_tangoshared_release|x64.Build.0 = py34_bopyshared_tangoshared_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py34_bopystatic_tangoshared_release|Win32.ActiveCfg = py34_bopystatic_tangoshared_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py34_bopystatic_tangoshared_release|Win32.Build.0 = py34_bopystatic_tangoshared_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py34_bopystatic_tangoshared_release|x64.ActiveCfg = py34_bopystatic_tangoshared_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py34_bopystatic_tangoshared_release|x64.Build.0 = py34_bopystatic_tangoshared_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py34_bopystatic_tangostatic_release|Win32.ActiveCfg = py34_bopystatic_tangostatic_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py34_bopystatic_tangostatic_release|Win32.Build.0 = py34_bopystatic_tangostatic_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py34_bopystatic_tangostatic_release|x64.ActiveCfg = py34_bopystatic_tangostatic_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py34_bopystatic_tangostatic_release|x64.Build.0 = py34_bopystatic_tangostatic_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py34_bopyshared_tangoshared_debug|Win32.ActiveCfg = py34_bopyshared_tangoshared_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py34_bopyshared_tangoshared_debug|Win32.Build.0 = py34_bopyshared_tangoshared_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py34_bopyshared_tangoshared_debug|x64.ActiveCfg = py34_bopyshared_tangoshared_debug|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py34_bopyshared_tangoshared_debug|x64.Build.0 = py34_bopyshared_tangoshared_debug|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py34_bopystatic_tangoshared_debug|Win32.ActiveCfg = py34_bopystatic_tangoshared_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py34_bopystatic_tangoshared_debug|Win32.Build.0 = py34_bopystatic_tangoshared_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py34_bopystatic_tangoshared_debug|x64.ActiveCfg = py34_bopystatic_tangoshared_debug|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py34_bopystatic_tangoshared_debug|x64.Build.0 = py34_bopystatic_tangoshared_debug|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py34_bopystatic_tangostatic_debug|Win32.ActiveCfg = py34_bopystatic_tangostatic_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py34_bopystatic_tangostatic_debug|Win32.Build.0 = py34_bopystatic_tangostatic_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py34_bopystatic_tangostatic_debug|x64.ActiveCfg = py34_bopystatic_tangostatic_debug|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py34_bopystatic_tangostatic_debug|x64.Build.0 = py34_bopystatic_tangostatic_debug|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal pytango-9.2.2/win/PyTango_VS10/PyTango.vcxproj000077500000000000000000001653531316324373100211550ustar00rootroot00000000000000 py33_bopyshared_tangoshared_release Win32 py33_bopyshared_tangoshared_release x64 py33_bopystatic_tangoshared_release Win32 py33_bopystatic_tangoshared_release x64 py33_bopystatic_tangostatic_release Win32 py33_bopystatic_tangostatic_release x64 py33_bopyshared_tangoshared_debug Win32 py33_bopyshared_tangoshared_debug x64 py33_bopystatic_tangoshared_debug Win32 py33_bopystatic_tangoshared_debug x64 py33_bopystatic_tangostatic_debug Win32 py33_bopystatic_tangostatic_debug x64 py34_bopyshared_tangoshared_release Win32 py34_bopyshared_tangoshared_release x64 py34_bopystatic_tangoshared_release Win32 py34_bopystatic_tangoshared_release x64 py34_bopystatic_tangostatic_release Win32 py34_bopystatic_tangostatic_release x64 py34_bopyshared_tangoshared_debug Win32 py34_bopyshared_tangoshared_debug x64 py34_bopystatic_tangoshared_debug Win32 py34_bopystatic_tangoshared_debug x64 py34_bopystatic_tangostatic_debug Win32 py34_bopystatic_tangostatic_debug x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD} PyTango_VS10 Win32Proj DynamicLibrary NotSet true Windows7.1SDK DynamicLibrary NotSet true Windows7.1SDK DynamicLibrary NotSet true Windows7.1SDK DynamicLibrary NotSet true Windows7.1SDK DynamicLibrary NotSet true Windows7.1SDK DynamicLibrary NotSet true Windows7.1SDK DynamicLibrary NotSet false Windows7.1SDK DynamicLibrary NotSet false Windows7.1SDK DynamicLibrary NotSet false Windows7.1SDK DynamicLibrary NotSet false Windows7.1SDK DynamicLibrary NotSet false Windows7.1SDK DynamicLibrary NotSet false Windows7.1SDK DynamicLibrary NotSet true Windows7.1SDK DynamicLibrary NotSet true Windows7.1SDK DynamicLibrary NotSet true Windows7.1SDK DynamicLibrary NotSet true Windows7.1SDK DynamicLibrary NotSet true Windows7.1SDK DynamicLibrary NotSet true Windows7.1SDK DynamicLibrary NotSet false Windows7.1SDK DynamicLibrary NotSet false Windows7.1SDK DynamicLibrary NotSet false Windows7.1SDK DynamicLibrary NotSet false Windows7.1SDK DynamicLibrary NotSet false Windows7.1SDK DynamicLibrary NotSet false Windows7.1SDK <_ProjectFileVersion>10.0.30319.1 $(PyTangoBaseFile) .pyd $(PyTangoBaseFile) .pyd $(PyTangoBaseFile) .pyd $(PyTangoBaseFile) .pyd $(PyTangoBaseFile) .pyd $(PyTangoBaseFile) .pyd $(PyTangoBaseFile) .pyd $(PyTangoBaseFile) .pyd $(PyTangoBaseFile) .pyd $(PyTangoBaseFile) .pyd $(PyTangoBaseFile) .pyd $(PyTangoBaseFile) .pyd $(PyTangoBaseFile) .pyd $(PyTangoBaseFile) .pyd $(PyTangoBaseFile) .pyd $(PyTangoBaseFile) .pyd $(PyTangoBaseFile) .pyd $(PyTangoBaseFile) .pyd $(PyTangoBaseFile) .pyd $(PyTangoBaseFile) .pyd $(PyTangoBaseFile) .pyd $(PyTangoBaseFile) .pyd $(PyTangoBaseFile) .pyd $(PyTangoBaseFile) .pyd true $(CommonIncludeDirs);$(Python33IncludeDirs);%(AdditionalIncludeDirectories) $(PreBoostSharedTangoSharedFlags);%(PreprocessorDefinitions);NDEBUG $(PyTango33SharedLibs);%(AdditionalDependencies) $(Py33BoostSharedTangoSharedLibDir);%(AdditionalLibraryDirectories) UseLinkTimeCodeGeneration $(Py33) $(WinSetupArgs) X64 true $(CommonIncludeDirs);$(Python33IncludeDirs);%(AdditionalIncludeDirectories) $(PreBoostSharedTangoSharedFlags);%(PreprocessorDefinitions);NDEBUG $(PyTango33SharedLibs);%(AdditionalDependencies) $(Py33BoostSharedTangoSharedLibDir);%(AdditionalLibraryDirectories) UseLinkTimeCodeGeneration MachineX64 $(Py33) $(WinSetupArgs) true $(CommonIncludeDirs);$(Python33IncludeDirs);%(AdditionalIncludeDirectories) $(PreBoostStaticTangoSharedFlags);%(PreprocessorDefinitions);NDEBUG $(PyTango33SharedLibs);%(AdditionalDependencies) $(Py33BoostStaticTangoSharedLibDir);%(AdditionalLibraryDirectories) UseLinkTimeCodeGeneration $(Py33) $(WinSetupArgs) X64 true $(CommonIncludeDirs);$(Python33IncludeDirs);%(AdditionalIncludeDirectories) $(PreBoostStaticTangoSharedFlags);%(PreprocessorDefinitions);NDEBUG $(PyTango33SharedLibs);%(AdditionalDependencies) $(Py33BoostStaticTangoSharedLibDir);%(AdditionalLibraryDirectories) UseLinkTimeCodeGeneration MachineX64 $(Py33) $(WinSetupArgs) true $(CommonIncludeDirs);$(Python33IncludeDirs);%(AdditionalIncludeDirectories) $(PreBoostStaticTangoStaticFlags);%(PreprocessorDefinitions);NDEBUG MultiThreaded CompileAsCpp $(PyTango33StaticLibs);%(AdditionalDependencies) $(Py33BoostStaticTangoStaticLibDir);%(AdditionalLibraryDirectories) UseLinkTimeCodeGeneration $(Py33) $(WinSetupArgs) X64 true $(CommonIncludeDirs);$(Python33IncludeDirs);%(AdditionalIncludeDirectories) $(PreBoostStaticTangoStaticFlags);%(PreprocessorDefinitions);NDEBUG MultiThreaded CompileAsCpp $(PyTango33StaticLibs);%(AdditionalDependencies) $(Py33BoostStaticTangoStaticLibDir);%(AdditionalLibraryDirectories) UseLinkTimeCodeGeneration MachineX64 $(Py33) $(WinSetupArgs) false $(CommonIncludeDirs);$(Python33IncludeDirs);%(AdditionalIncludeDirectories) $(PreBoostSharedTangoSharedFlags);%(PreprocessorDefinitions);_DEBUG $(PyTango33SharedLibs_Debug);%(AdditionalDependencies) $(Py33BoostSharedTangoSharedLibDir_Debug);%(AdditionalLibraryDirectories) UseLinkTimeCodeGeneration $(Py33) $(WinSetupArgs) X64 false $(CommonIncludeDirs);$(Python33IncludeDirs);%(AdditionalIncludeDirectories) $(PreBoostSharedTangoSharedFlags);%(PreprocessorDefinitions);_DEBUG $(PyTango33SharedLibs_Debug);%(AdditionalDependencies) $(Py33BoostSharedTangoSharedLibDir_Debug);%(AdditionalLibraryDirectories) UseLinkTimeCodeGeneration MachineX64 $(Py33) $(WinSetupArgs) false $(CommonIncludeDirs);$(Python33IncludeDirs);%(AdditionalIncludeDirectories) $(PreBoostStaticTangoSharedFlags);%(PreprocessorDefinitions);_DEBUG $(PyTango33SharedLibs_Debug);%(AdditionalDependencies) $(Py33BoostStaticTangoSharedLibDir_Debug);%(AdditionalLibraryDirectories) UseLinkTimeCodeGeneration $(Py33) $(WinSetupArgs) X64 false $(CommonIncludeDirs);$(Python33IncludeDirs);%(AdditionalIncludeDirectories) $(PreBoostStaticTangoSharedFlags);%(PreprocessorDefinitions);_DEBUG $(PyTango33SharedLibs_Debug);%(AdditionalDependencies) $(Py33BoostStaticTangoSharedLibDir_Debug);%(AdditionalLibraryDirectories) UseLinkTimeCodeGeneration MachineX64 $(Py33) $(WinSetupArgs) false $(CommonIncludeDirs);$(Python33IncludeDirs);%(AdditionalIncludeDirectories) $(PreBoostStaticTangoStaticFlags);%(PreprocessorDefinitions);_DEBUG MultiThreaded CompileAsCpp $(PyTango33StaticLibs_Debug);%(AdditionalDependencies) $(Py33BoostStaticTangoStaticLibDir_Debug);%(AdditionalLibraryDirectories) UseLinkTimeCodeGeneration $(Py33) $(WinSetupArgs) X64 false $(CommonIncludeDirs);$(Python33IncludeDirs);%(AdditionalIncludeDirectories) $(PreBoostStaticTangoStaticFlags);%(PreprocessorDefinitions);_DEBUG MultiThreaded CompileAsCpp $(PyTango33StaticLibs_Debug);%(AdditionalDependencies) $(Py33BoostStaticTangoStaticLibDir_Debug);%(AdditionalLibraryDirectories) UseLinkTimeCodeGeneration MachineX64 $(Py33) $(WinSetupArgs) true $(CommonIncludeDirs);$(Python34IncludeDirs);%(AdditionalIncludeDirectories) $(PreBoostSharedTangoSharedFlags);%(PreprocessorDefinitions);NDEBUG $(PyTango34SharedLibs);%(AdditionalDependencies) $(Py34BoostSharedTangoSharedLibDir);%(AdditionalLibraryDirectories) UseLinkTimeCodeGeneration $(Py34) $(WinSetupArgs) X64 true $(CommonIncludeDirs);$(Python34IncludeDirs);%(AdditionalIncludeDirectories) $(PreBoostSharedTangoSharedFlags);%(PreprocessorDefinitions);NDEBUG $(PyTango34SharedLibs);%(AdditionalDependencies) $(Py34BoostSharedTangoSharedLibDir);%(AdditionalLibraryDirectories) UseLinkTimeCodeGeneration MachineX64 $(Py34) $(WinSetupArgs) true $(CommonIncludeDirs);$(Python34IncludeDirs);%(AdditionalIncludeDirectories) $(PreBoostStaticTangoSharedFlags);%(PreprocessorDefinitions);NDEBUG $(PyTango34SharedLibs);%(AdditionalDependencies) $(Py34BoostStaticTangoSharedLibDir);%(AdditionalLibraryDirectories) UseLinkTimeCodeGeneration $(Py34) $(WinSetupArgs) X64 true $(CommonIncludeDirs);$(Python34IncludeDirs);%(AdditionalIncludeDirectories) $(PreBoostStaticTangoSharedFlags);%(PreprocessorDefinitions);NDEBUG $(PyTango34SharedLibs);%(AdditionalDependencies) $(Py34BoostStaticTangoSharedLibDir);%(AdditionalLibraryDirectories) UseLinkTimeCodeGeneration MachineX64 $(Py34) $(WinSetupArgs) true $(CommonIncludeDirs);$(Python34IncludeDirs);%(AdditionalIncludeDirectories) $(PreBoostStaticTangoStaticFlags);%(PreprocessorDefinitions);NDEBUG MultiThreaded CompileAsCpp $(PyTango34StaticLibs);%(AdditionalDependencies) $(Py34BoostStaticTangoStaticLibDir);%(AdditionalLibraryDirectories) UseLinkTimeCodeGeneration $(Py34) $(WinSetupArgs) X64 true $(CommonIncludeDirs);$(Python34IncludeDirs);%(AdditionalIncludeDirectories) $(PreBoostStaticTangoStaticFlags);%(PreprocessorDefinitions);NDEBUG MultiThreaded CompileAsCpp $(PyTango34StaticLibs);%(AdditionalDependencies) $(Py34BoostStaticTangoStaticLibDir);%(AdditionalLibraryDirectories) UseLinkTimeCodeGeneration MachineX64 $(Py34) $(WinSetupArgs) false $(CommonIncludeDirs);$(Python34IncludeDirs);%(AdditionalIncludeDirectories) $(PreBoostSharedTangoSharedFlags);%(PreprocessorDefinitions);_DEBUG $(PyTango34SharedLibs_Debug);%(AdditionalDependencies) $(Py34BoostSharedTangoSharedLibDir_Debug);%(AdditionalLibraryDirectories) UseLinkTimeCodeGeneration $(Py34) $(WinSetupArgs) X64 false $(CommonIncludeDirs);$(Python34IncludeDirs);%(AdditionalIncludeDirectories) $(PreBoostSharedTangoSharedFlags);%(PreprocessorDefinitions);_DEBUG $(PyTango34SharedLibs_Debug);%(AdditionalDependencies) $(Py34BoostSharedTangoSharedLibDir_Debug);%(AdditionalLibraryDirectories) UseLinkTimeCodeGeneration MachineX64 $(Py34) $(WinSetupArgs) false $(CommonIncludeDirs);$(Python34IncludeDirs);%(AdditionalIncludeDirectories) $(PreBoostStaticTangoSharedFlags);%(PreprocessorDefinitions);_DEBUG $(PyTango34SharedLibs_Debug);%(AdditionalDependencies) $(Py34BoostStaticTangoSharedLibDir_Debug);%(AdditionalLibraryDirectories) UseLinkTimeCodeGeneration $(Py34) $(WinSetupArgs) X64 false $(CommonIncludeDirs);$(Python34IncludeDirs);%(AdditionalIncludeDirectories) $(PreBoostStaticTangoSharedFlags);%(PreprocessorDefinitions);_DEBUG $(PyTango34SharedLibs_Debug);%(AdditionalDependencies) $(Py34BoostStaticTangoSharedLibDir_Debug);%(AdditionalLibraryDirectories) UseLinkTimeCodeGeneration MachineX64 $(Py34) $(WinSetupArgs) false $(CommonIncludeDirs);$(Python34IncludeDirs);%(AdditionalIncludeDirectories) $(PreBoostStaticTangoStaticFlags);%(PreprocessorDefinitions);_DEBUG MultiThreaded CompileAsCpp $(PyTango34StaticLibs_Debug);%(AdditionalDependencies) $(Py34BoostStaticTangoStaticLibDir_Debug);%(AdditionalLibraryDirectories) UseLinkTimeCodeGeneration $(Py34) $(WinSetupArgs) X64 false $(CommonIncludeDirs);$(Python34IncludeDirs);%(AdditionalIncludeDirectories) $(PreBoostStaticTangoStaticFlags);%(PreprocessorDefinitions);_DEBUG MultiThreaded CompileAsCpp $(PyTango34StaticLibs_Debug);%(AdditionalDependencies) $(Py34BoostStaticTangoStaticLibDir_Debug);%(AdditionalLibraryDirectories) UseLinkTimeCodeGeneration MachineX64 $(Py34) $(WinSetupArgs) pytango-9.2.2/win/PyTango_VS10/PyTango.vcxproj.filters000077500000000000000000000242531316324373100226150ustar00rootroot00000000000000 {93995380-89BD-4b04-88EB-625FBE52EBFB} h;hpp;hxx;hm;inl;inc;xsd {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files pytango-9.2.2/win/PyTango_VS14/000077500000000000000000000000001316324373100161435ustar00rootroot00000000000000pytango-9.2.2/win/PyTango_VS14/PyTango.props000066400000000000000000000424461316324373100206230ustar00rootroot00000000000000 _tango 9 2 0 $(TangoMajorVer).$(TangoMinorVer).$(TangoPatchVer) 9 2 0 $(PyTangoMajorVer).$(PyTangoMinorVer).$(PyTangoPatchVer) 1 61 0 $(BoostMajorVer).$(BoostMinorVer).$(BoostPatchVer) C: $(RootDir)\wsp\tango $(RootDir)\wsp\boost-$(BoostVer) C:\Python 14 0 $(VCMajorVer).$(VCMinorVer) $(BoostBaseDir)\include\boost-$(BoostMajorVer)_$(BoostMinorVer) $(BoostBaseDir)\multi\release\msvc-$(VCVer)\$(Platform) $(BoostBaseDir)\multi\debug\msvc-$(VCVer)\$(Platform) $(TangoBaseDir)\$(Platform)\include\vc$(VCMajorVer) $(TangoBaseDir)\$(Platform)\lib $(TangoBaseDir)\$(Platform)\lib C:\Python\$(Platform) Lib\site-packages\numpy\core\include -Zm200 /MP8 /bigobj $(ProjectDir)..\.. $(Configuration)_$(Platform)_msvc$(VCVer) $(RootDir)\pytango\build_$(PyTangoVer)_tg$(TangoVer)_boost$(BoostVer) $(BaseBuildDir)\lib $(BaseBuildDir)\temp $(BaseBuildDir)\dist $(BaseBuildDir)\temp_dist $(BaseBuildLibDir)\$(RelPrefixDir) $(BaseDistDir)\$(RelPrefixDir) $(BaseTempDir)\$(RelPrefixDir) $(BaseBDistDir)\$(RelPrefixDir) $(BuildDir)\tango $(TempDir)\tango $(BaseDir)\ext $(BoostIncludeDir);$(TangoIncludeDir);$(BaseSrcDir);$(BaseSrcDir)\server $(PythonDir)\35 $(Python35Dir)\python $(Python35Dir)\include $(Python35Dir)\$(NumpyRelativeIncludeDir) $(Python35IncludeDir);$(Numpy35IncludeDir) $(Python35Dir)\libs WIN32 _WINSTATIC LOG4TANGO_HAS_DLL;TANGO_HAS_DLL $(BasePreFlags);$(PreTangoSharedFlags);BOOST_PYTHON_DYNAMIC_LIB $(BasePreFlags);$(PreTangoSharedFlags);BOOST_PYTHON_STATIC_LIB $(BasePreFlags);$(PreTangoStaticFlags);BOOST_PYTHON_STATIC_LIB precompiled_header.hpp $(TempDir)\$(PyTangoBaseFile)_Boost-$(BoostVer).pch $(TempDir)\$(PyTangoBaseFile)_Boost-$(BoostVer).pdb 4005;4996;4250 comctl32.lib;ws2_32.lib;mswsock.lib COS4.lib;omniORB4.lib;omniDynamic4.lib;omnithread.lib COS4_rt.lib;omniORB4_rt.lib;omniDynamic4_rt.lib;omnithread_rt.lib zmq.lib;log4tango.lib;tango.lib;$(BaseLibs) $(omniStaticLibs);$(BaseTangoLibs) $(omniSharedLibs);$(BaseTangoLibs) COS4d.lib;omniORB4d.lib;omniDynamic4d.lib;omnithreadd.lib COS4_rtd.lib;omniORB4_rtd.lib;omniDynamic4_rtd.lib;omnithread_rtd.lib zmqd.lib;log4tangod.lib;tangod.lib;$(BaseLibs) $(omniStaticLibs_Debug);$(BaseTangoLibs_Debug) $(omniSharedLibs_Debug);$(BaseTangoLibs_Debug) $(BasePyTangoStaticLibs);python35.lib $(BasePyTangoSharedLibs);python35.lib $(BoostLibDir)\shared\runtime_shared $(BoostLibDir)\static\runtime_shared $(BoostLibDir)\static\runtime_static $(BaseBoostSharedRtSharedLibDir)\35;$(Python35LibDir) $(BaseBoostStaticRtSharedLibDir)\35;$(Python35LibDir) $(BaseBoostStaticRtStaticLibDir)\35;$(Python35LibDir) $(Boost35SharedRtSharedLibDir);$(TangoLibDir)\vc$(VCMajorVer)_dll $(Boost35StaticRtSharedLibDir);$(TangoLibDir)\vc$(VCMajorVer)_dll $(Boost35StaticRtStaticLibDir);$(TangoLibDir)\vc$(VCMajorVer) $(BasePyTangoStaticLibs_Debug);python35.lib $(BasePyTangoSharedLibs_Debug);python35.lib $(BoostLibDir)\shared\runtime_shared $(BoostLibDir)\static\runtime_shared $(BoostLibDir)\static\runtime_static $(BaseBoostSharedRtSharedLibDir_Debug)\35;$(Python35LibDir) $(BaseBoostStaticRtSharedLibDir_Debug)\35;$(Python35LibDir) $(BaseBoostStaticRtStaticLibDir_Debug)\35;$(Python35LibDir) $(Boost35SharedRtSharedLibDir_Debug);$(TangoLibDir_Debug)\vc$(VCMajorVer)_dll $(Boost35StaticRtSharedLibDir_Debug);$(TangoLibDir_Debug)\vc$(VCMajorVer)_dll $(Boost35StaticRtStaticLibDir_Debug);$(TangoLibDir_Debug)\vc$(VCMajorVer) $(BaseDir)\winsetup.py $(BuildDir) $(DistDir) $(BDistDir) $(Configuration) $(Platform) <_ProjectFileVersion>14.0.30319.1 <_PropertySheetDisplayName>PyTango Project Properties $(PyTangoBuildDir)\ $(PyTangoTempDir)\ false $(PyTangoCompileOptions) %(AdditionalOptions) Full false false $(PreCompHeaderDst) $(PreCompHeaderSrc) $(DatabaseFile) Level3 ProgramDatabase MultiThreadedDLL Default $(BaseDisableWarningFlags);%(DisableSpecificWarnings) Prompt NotSet $(OutDir)$(PyTangoBaseFile).pyd true $(DatabaseFile) Windows true true false MachineX86 [PyTango]: Starting python $(WinSetupArgs) $(IntDir)BuildLog.html $(PyTangoBaseFile) $(TangoMajorVer) $(TangoMinorVer) $(TangoPatchVer) $(TangoVer) $(BoostMajorVer) $(BoostMinorVer) $(BoostPatchVer) $(BoostVer) $(TangoBaseDir) $(BoostBaseDir) $(PythonBaseDir) $(VCMajorVer) $(VCMinorVer) $(VCVer) $(BoostIncludeDir) $(BoostLibDir) $(TangoIncludeDir) $(TangoLibDir) $(PythonDir) $(NumpyRelativeIncludeDir) $(PyTangoCompileOptions) $(BaseDir) $(RelPrefixDir) $(BaseBuildDir) $(BaseBuildLibDir) $(BaseTempDir) $(BaseDistDir) $(BaseBDistDir) $(BuildDir) $(DistDir) $(TempDir) $(BDistDir) $(PyTangoBuildDir) $(PyTangoTempDir) $(BaseSrcDir) $(CommonIncludeDirs) $(Python35Dir) $(Py35) $(Python35IncludeDir) $(Numpy35IncludeDir) $(Python35IncludeDirs) $(Python35LibDir) $(BasePreFlags) $(PreTangoStaticFlags) $(PreTangoSharedFlags) $(PreBoostSharedTangoSharedFlags) $(PreBoostStaticTangoSharedFlags) $(PreBoostStaticTangoStaticFlags) $(PreCompHeaderSrc) $(PreCompHeaderDst) $(DatabaseFile) $(BaseDisableWarningFlags) $(omniStaticLibs) $(omniSharedLibs) $(BaseTangoLibs) $(BasePyTangoStaticLibs) $(BasePyTangoSharedLibs) $(PyTango35StaticLibs) $(PyTango35SharedLibs) $(BaseBoostSharedRtSharedLibDir) $(BaseBoostStaticRtSharedLibDir) $(BaseBoostStaticRtStaticLibDir) $(Boost35SharedRtSharedLibDir) $(Boost35StaticRtSharedLibDir) $(Boost35StaticRtStaticLibDir) $(Py35BoostSharedTangoSharedLibDir) $(Py35BoostStaticTangoSharedLibDir) $(Py35BoostStaticTangoStaticLibDir) $(WinSetupArgs) pytango-9.2.2/win/PyTango_VS14/PyTango.sln000066400000000000000000000110731316324373100202440ustar00rootroot00000000000000 Microsoft Visual Studio Solution File, Format Version 14.00 # Visual Studio 2014 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PyTango", "PyTango.vcxproj", "{764A5ECE-11AE-4363-9A1C-573972A1BADD}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution py35_bopyshared_tangoshared_release|Win32 = py35_bopyshared_tangoshared_release|Win32 py35_bopyshared_tangoshared_release|x64 = py35_bopyshared_tangoshared_release|x64 py35_bopystatic_tangoshared_release|Win32 = py35_bopystatic_tangoshared_release|Win32 py35_bopystatic_tangoshared_release|x64 = py35_bopystatic_tangoshared_release|x64 py35_bopystatic_tangostatic_release|Win32 = py35_bopystatic_tangostatic_release|Win32 py35_bopystatic_tangostatic_release|x64 = py35_bopystatic_tangostatic_release|x64 py35_bopyshared_tangoshared_debug|Win32 = py35_bopyshared_tangoshared_debug|Win32 py35_bopyshared_tangoshared_debug|x64 = py35_bopyshared_tangoshared_debug|x64 py35_bopystatic_tangoshared_debug|Win32 = py35_bopystatic_tangoshared_debug|Win32 py35_bopystatic_tangoshared_debug|x64 = py35_bopystatic_tangoshared_debug|x64 py35_bopystatic_tangostatic_debug|Win32 = py35_bopystatic_tangostatic_debug|Win32 py35_bopystatic_tangostatic_debug|x64 = py35_bopystatic_tangostatic_debug|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py35_bopyshared_tangoshared_release|Win32.ActiveCfg = py35_bopyshared_tangoshared_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py35_bopyshared_tangoshared_release|Win32.Build.0 = py35_bopyshared_tangoshared_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py35_bopyshared_tangoshared_release|x64.ActiveCfg = py35_bopyshared_tangoshared_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py35_bopyshared_tangoshared_release|x64.Build.0 = py35_bopyshared_tangoshared_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py35_bopystatic_tangoshared_release|Win32.ActiveCfg = py35_bopystatic_tangoshared_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py35_bopystatic_tangoshared_release|Win32.Build.0 = py35_bopystatic_tangoshared_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py35_bopystatic_tangoshared_release|x64.ActiveCfg = py35_bopystatic_tangoshared_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py35_bopystatic_tangoshared_release|x64.Build.0 = py35_bopystatic_tangoshared_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py35_bopystatic_tangostatic_release|Win32.ActiveCfg = py35_bopystatic_tangostatic_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py35_bopystatic_tangostatic_release|Win32.Build.0 = py35_bopystatic_tangostatic_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py35_bopystatic_tangostatic_release|x64.ActiveCfg = py35_bopystatic_tangostatic_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py35_bopystatic_tangostatic_release|x64.Build.0 = py35_bopystatic_tangostatic_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py35_bopyshared_tangoshared_debug|Win32.ActiveCfg = py35_bopyshared_tangoshared_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py35_bopyshared_tangoshared_debug|Win32.Build.0 = py35_bopyshared_tangoshared_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py35_bopyshared_tangoshared_debug|x64.ActiveCfg = py35_bopyshared_tangoshared_debug|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py35_bopyshared_tangoshared_debug|x64.Build.0 = py35_bopyshared_tangoshared_debug|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py35_bopystatic_tangoshared_debug|Win32.ActiveCfg = py35_bopystatic_tangoshared_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py35_bopystatic_tangoshared_debug|Win32.Build.0 = py35_bopystatic_tangoshared_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py35_bopystatic_tangoshared_debug|x64.ActiveCfg = py35_bopystatic_tangoshared_debug|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py35_bopystatic_tangoshared_debug|x64.Build.0 = py35_bopystatic_tangoshared_debug|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py35_bopystatic_tangostatic_debug|Win32.ActiveCfg = py35_bopystatic_tangostatic_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py35_bopystatic_tangostatic_debug|Win32.Build.0 = py35_bopystatic_tangostatic_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py35_bopystatic_tangostatic_debug|x64.ActiveCfg = py35_bopystatic_tangostatic_debug|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py35_bopystatic_tangostatic_debug|x64.Build.0 = py35_bopystatic_tangostatic_debug|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal pytango-9.2.2/win/PyTango_VS14/PyTango.vcxproj000066400000000000000000000773021316324373100211520ustar00rootroot00000000000000 py35_bopyshared_tangoshared_release Win32 py35_bopyshared_tangoshared_release x64 py35_bopystatic_tangoshared_release Win32 py35_bopystatic_tangoshared_release x64 py35_bopystatic_tangostatic_release Win32 py35_bopystatic_tangostatic_release x64 py35_bopyshared_tangoshared_debug Win32 py35_bopyshared_tangoshared_debug x64 py35_bopystatic_tangoshared_debug Win32 py35_bopystatic_tangoshared_debug x64 py35_bopystatic_tangostatic_debug Win32 py35_bopystatic_tangostatic_debug x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD} PyTango_VS14 Win32Proj DynamicLibrary NotSet true v140 DynamicLibrary NotSet true v140 DynamicLibrary NotSet true v140 DynamicLibrary NotSet true v140 DynamicLibrary NotSet true v140 DynamicLibrary NotSet true v140 DynamicLibrary NotSet false v140 DynamicLibrary NotSet false v140 DynamicLibrary NotSet false v140 DynamicLibrary NotSet false v140 DynamicLibrary NotSet false v140 DynamicLibrary NotSet false v140 <_ProjectFileVersion>10.0.30319.1 $(PyTangoBaseFile) .pyd $(PyTangoBaseFile) .pyd $(PyTangoBaseFile) .pyd $(PyTangoBaseFile) .pyd $(PyTangoBaseFile) .pyd $(PyTangoBaseFile) .pyd $(PyTangoBaseFile) .pyd $(PyTangoBaseFile) .pyd $(PyTangoBaseFile) .pyd $(PyTangoBaseFile) .pyd $(PyTangoBaseFile) .pyd $(PyTangoBaseFile) .pyd true $(CommonIncludeDirs);$(Python35IncludeDirs);%(AdditionalIncludeDirectories) $(PreBoostSharedTangoSharedFlags);%(PreprocessorDefinitions);NDEBUG $(PyTango35SharedLibs);%(AdditionalDependencies) $(py35BoostSharedTangoSharedLibDir);%(AdditionalLibraryDirectories) UseLinkTimeCodeGeneration $(py35) $(WinSetupArgs) X64 true $(CommonIncludeDirs);$(Python35IncludeDirs);%(AdditionalIncludeDirectories) $(PreBoostSharedTangoSharedFlags);%(PreprocessorDefinitions);NDEBUG $(PyTango35SharedLibs);%(AdditionalDependencies) $(py35BoostSharedTangoSharedLibDir);%(AdditionalLibraryDirectories) UseLinkTimeCodeGeneration MachineX64 $(py35) $(WinSetupArgs) true $(CommonIncludeDirs);$(Python35IncludeDirs);%(AdditionalIncludeDirectories) $(PreBoostStaticTangoSharedFlags);%(PreprocessorDefinitions);NDEBUG $(PyTango35SharedLibs);%(AdditionalDependencies) $(py35BoostStaticTangoSharedLibDir);%(AdditionalLibraryDirectories) UseLinkTimeCodeGeneration $(py35) $(WinSetupArgs) X64 true $(CommonIncludeDirs);$(Python35IncludeDirs);%(AdditionalIncludeDirectories) $(PreBoostStaticTangoSharedFlags);%(PreprocessorDefinitions);NDEBUG $(PyTango35SharedLibs);%(AdditionalDependencies) $(py35BoostStaticTangoSharedLibDir);%(AdditionalLibraryDirectories) UseLinkTimeCodeGeneration MachineX64 $(py35) $(WinSetupArgs) true $(CommonIncludeDirs);$(Python35IncludeDirs);%(AdditionalIncludeDirectories) $(PreBoostStaticTangoStaticFlags);%(PreprocessorDefinitions);NDEBUG MultiThreaded CompileAsCpp $(PyTango35StaticLibs);%(AdditionalDependencies) $(py35BoostStaticTangoStaticLibDir);%(AdditionalLibraryDirectories) UseLinkTimeCodeGeneration $(py35) $(WinSetupArgs) X64 true $(CommonIncludeDirs);$(Python35IncludeDirs);%(AdditionalIncludeDirectories) $(PreBoostStaticTangoStaticFlags);%(PreprocessorDefinitions);NDEBUG MultiThreaded CompileAsCpp $(PyTango35StaticLibs);%(AdditionalDependencies) $(py35BoostStaticTangoStaticLibDir);%(AdditionalLibraryDirectories) UseLinkTimeCodeGeneration MachineX64 LinkVerbose $(py35) $(WinSetupArgs) false $(CommonIncludeDirs);$(Python35IncludeDirs);%(AdditionalIncludeDirectories) $(PreBoostSharedTangoSharedFlags);%(PreprocessorDefinitions);_DEBUG $(PyTango35SharedLibs_Debug);%(AdditionalDependencies) $(py35BoostSharedTangoSharedLibDir_Debug);%(AdditionalLibraryDirectories) UseLinkTimeCodeGeneration $(py35) $(WinSetupArgs) X64 false $(CommonIncludeDirs);$(Python35IncludeDirs);%(AdditionalIncludeDirectories) $(PreBoostSharedTangoSharedFlags);%(PreprocessorDefinitions);_DEBUG $(PyTango35SharedLibs_Debug);%(AdditionalDependencies) $(py35BoostSharedTangoSharedLibDir_Debug);%(AdditionalLibraryDirectories) UseLinkTimeCodeGeneration MachineX64 $(py35) $(WinSetupArgs) false $(CommonIncludeDirs);$(Python35IncludeDirs);%(AdditionalIncludeDirectories) $(PreBoostStaticTangoSharedFlags);%(PreprocessorDefinitions);_DEBUG $(PyTango35SharedLibs_Debug);%(AdditionalDependencies) $(py35BoostStaticTangoSharedLibDir_Debug);%(AdditionalLibraryDirectories) UseLinkTimeCodeGeneration $(py35) $(WinSetupArgs) X64 false $(CommonIncludeDirs);$(Python35IncludeDirs);%(AdditionalIncludeDirectories) $(PreBoostStaticTangoSharedFlags);%(PreprocessorDefinitions);_DEBUG $(PyTango35SharedLibs_Debug);%(AdditionalDependencies) $(py35BoostStaticTangoSharedLibDir_Debug);%(AdditionalLibraryDirectories) UseLinkTimeCodeGeneration MachineX64 $(py35) $(WinSetupArgs) false $(CommonIncludeDirs);$(Python35IncludeDirs);%(AdditionalIncludeDirectories) $(PreBoostStaticTangoStaticFlags);%(PreprocessorDefinitions);_DEBUG MultiThreaded CompileAsCpp $(PyTango35StaticLibs_Debug);%(AdditionalDependencies) $(py35BoostStaticTangoStaticLibDir_Debug);%(AdditionalLibraryDirectories) UseLinkTimeCodeGeneration $(py35) $(WinSetupArgs) X64 false $(CommonIncludeDirs);$(Python35IncludeDirs);%(AdditionalIncludeDirectories) $(PreBoostStaticTangoStaticFlags);%(PreprocessorDefinitions);_DEBUG MultiThreaded CompileAsCpp $(PyTango35StaticLibs_Debug);%(AdditionalDependencies) $(py35BoostStaticTangoStaticLibDir_Debug);%(AdditionalLibraryDirectories) UseLinkTimeCodeGeneration MachineX64 $(py35) $(WinSetupArgs) pytango-9.2.2/win/PyTango_VS14/PyTango.vcxproj.filters000066400000000000000000000236201316324373100226130ustar00rootroot00000000000000 {93995380-89BD-4b04-88EB-625FBE52EBFB} h;hpp;hxx;hm;inl;inc;xsd {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files pytango-9.2.2/win/PyTango_VS8/000077500000000000000000000000001316324373100160665ustar00rootroot00000000000000pytango-9.2.2/win/PyTango_VS8/PyTango.sln000066400000000000000000000140371316324373100201720ustar00rootroot00000000000000 Microsoft Visual Studio Solution File, Format Version 9.00 # Visual Studio 2005 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PyTango", "PyTango.vcproj", "{764A5ECE-11AE-4363-9A1C-573972A1BADD}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Test01", "..\Test01\Test01.vcproj", "{43864AA2-4DB3-4E0B-9EA5-85800E17AA59}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 Debug|x64 = Debug|x64 Release_Py26_boost141_static|Win32 = Release_Py26_boost141_static|Win32 Release_Py26_boost141_static|x64 = Release_Py26_boost141_static|x64 Release_Py26_boost141|Win32 = Release_Py26_boost141|Win32 Release_Py26_boost141|x64 = Release_Py26_boost141|x64 Release_Py27_boost1461|Win32 = Release_Py27_boost1461|Win32 Release_Py27_boost1461|x64 = Release_Py27_boost1461|x64 Release_Py27_boost147_static|Win32 = Release_Py27_boost147_static|Win32 Release_Py27_boost147_static|x64 = Release_Py27_boost147_static|x64 Release_Py27_boost147|Win32 = Release_Py27_boost147|Win32 Release_Py27_boost147|x64 = Release_Py27_boost147|x64 Release|Win32 = Release|Win32 Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {764A5ECE-11AE-4363-9A1C-573972A1BADD}.Debug|Win32.ActiveCfg = Release_Py27_boost147_static|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.Debug|x64.ActiveCfg = Release_Py27_boost147_static|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.Debug|x64.Build.0 = Release_Py27_boost147_static|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.Release_Py26_boost141_static|Win32.ActiveCfg = Release_Py27_boost147_static|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.Release_Py26_boost141_static|Win32.Build.0 = Release_Py27_boost147_static|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.Release_Py26_boost141_static|x64.ActiveCfg = Release_Py26_boost141_static|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.Release_Py26_boost141_static|x64.Build.0 = Release_Py26_boost141_static|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.Release_Py26_boost141|Win32.ActiveCfg = Release_Py26_boost141|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.Release_Py26_boost141|Win32.Build.0 = Release_Py26_boost141|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.Release_Py26_boost141|x64.ActiveCfg = Release_Py26_boost141|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.Release_Py26_boost141|x64.Build.0 = Release_Py26_boost141|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.Release_Py27_boost1461|Win32.ActiveCfg = Release_Py27_boost1461|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.Release_Py27_boost1461|Win32.Build.0 = Release_Py27_boost1461|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.Release_Py27_boost1461|x64.ActiveCfg = Release_Py27_boost1461|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.Release_Py27_boost1461|x64.Build.0 = Release_Py27_boost1461|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.Release_Py27_boost147_static|Win32.ActiveCfg = Release_Py27_boost147_static|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.Release_Py27_boost147_static|x64.ActiveCfg = Release_Py27_boost147_static|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.Release_Py27_boost147_static|x64.Build.0 = Release_Py27_boost147_static|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.Release_Py27_boost147|Win32.ActiveCfg = Release_Py27_boost147|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.Release_Py27_boost147|Win32.Build.0 = Release_Py27_boost147|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.Release_Py27_boost147|x64.ActiveCfg = Release_Py27_boost147|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.Release_Py27_boost147|x64.Build.0 = Release_Py27_boost147|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.Release|Win32.ActiveCfg = Release_Py27_boost147_static|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.Release|x64.ActiveCfg = Release_Py27_boost147_static|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.Release|x64.Build.0 = Release_Py27_boost147_static|x64 {43864AA2-4DB3-4E0B-9EA5-85800E17AA59}.Debug|Win32.ActiveCfg = Debug|Win32 {43864AA2-4DB3-4E0B-9EA5-85800E17AA59}.Debug|Win32.Build.0 = Debug|Win32 {43864AA2-4DB3-4E0B-9EA5-85800E17AA59}.Debug|x64.ActiveCfg = Debug|Win32 {43864AA2-4DB3-4E0B-9EA5-85800E17AA59}.Release_Py26_boost141_static|Win32.ActiveCfg = Release_Py26_boost141_static|Win32 {43864AA2-4DB3-4E0B-9EA5-85800E17AA59}.Release_Py26_boost141_static|Win32.Build.0 = Release_Py26_boost141_static|Win32 {43864AA2-4DB3-4E0B-9EA5-85800E17AA59}.Release_Py26_boost141_static|x64.ActiveCfg = Release_Py26_boost141_static|Win32 {43864AA2-4DB3-4E0B-9EA5-85800E17AA59}.Release_Py26_boost141|Win32.ActiveCfg = Release|Win32 {43864AA2-4DB3-4E0B-9EA5-85800E17AA59}.Release_Py26_boost141|Win32.Build.0 = Release|Win32 {43864AA2-4DB3-4E0B-9EA5-85800E17AA59}.Release_Py26_boost141|x64.ActiveCfg = Release|Win32 {43864AA2-4DB3-4E0B-9EA5-85800E17AA59}.Release_Py27_boost1461|Win32.ActiveCfg = Release|Win32 {43864AA2-4DB3-4E0B-9EA5-85800E17AA59}.Release_Py27_boost1461|Win32.Build.0 = Release|Win32 {43864AA2-4DB3-4E0B-9EA5-85800E17AA59}.Release_Py27_boost1461|x64.ActiveCfg = Release|Win32 {43864AA2-4DB3-4E0B-9EA5-85800E17AA59}.Release_Py27_boost147_static|Win32.ActiveCfg = Release|Win32 {43864AA2-4DB3-4E0B-9EA5-85800E17AA59}.Release_Py27_boost147_static|Win32.Build.0 = Release|Win32 {43864AA2-4DB3-4E0B-9EA5-85800E17AA59}.Release_Py27_boost147_static|x64.ActiveCfg = Release|Win32 {43864AA2-4DB3-4E0B-9EA5-85800E17AA59}.Release_Py27_boost147|Win32.ActiveCfg = Release_Py27_boost147|Win32 {43864AA2-4DB3-4E0B-9EA5-85800E17AA59}.Release_Py27_boost147|Win32.Build.0 = Release_Py27_boost147|Win32 {43864AA2-4DB3-4E0B-9EA5-85800E17AA59}.Release_Py27_boost147|x64.ActiveCfg = Release_Py27_boost147|Win32 {43864AA2-4DB3-4E0B-9EA5-85800E17AA59}.Release|Win32.ActiveCfg = Release|Win32 {43864AA2-4DB3-4E0B-9EA5-85800E17AA59}.Release|Win32.Build.0 = Release|Win32 {43864AA2-4DB3-4E0B-9EA5-85800E17AA59}.Release|x64.ActiveCfg = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal pytango-9.2.2/win/PyTango_VS8/PyTango.vcproj000066400000000000000000001023131316324373100206740ustar00rootroot00000000000000 pytango-9.2.2/win/PyTango_VS9/000077500000000000000000000000001316324373100160675ustar00rootroot00000000000000pytango-9.2.2/win/PyTango_VS9/PyTango.sln000066400000000000000000000417121316324373100201730ustar00rootroot00000000000000 Microsoft Visual Studio Solution File, Format Version 10.00 # Visual C++ Express 2008 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PyTango", "PyTango.vcproj", "{764A5ECE-11AE-4363-9A1C-573972A1BADD}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution py26_bopyshared_tangoshared_debug|Win32 = py26_bopyshared_tangoshared_debug|Win32 py26_bopyshared_tangoshared_debug|x64 = py26_bopyshared_tangoshared_debug|x64 py26_bopyshared_tangoshared_release|Win32 = py26_bopyshared_tangoshared_release|Win32 py26_bopyshared_tangoshared_release|x64 = py26_bopyshared_tangoshared_release|x64 py26_bopystatic_tangoshared_debug|Win32 = py26_bopystatic_tangoshared_debug|Win32 py26_bopystatic_tangoshared_debug|x64 = py26_bopystatic_tangoshared_debug|x64 py26_bopystatic_tangoshared_release|Win32 = py26_bopystatic_tangoshared_release|Win32 py26_bopystatic_tangoshared_release|x64 = py26_bopystatic_tangoshared_release|x64 py26_bopystatic_tangostatic_debug|Win32 = py26_bopystatic_tangostatic_debug|Win32 py26_bopystatic_tangostatic_debug|x64 = py26_bopystatic_tangostatic_debug|x64 py26_bopystatic_tangostatic_release|Win32 = py26_bopystatic_tangostatic_release|Win32 py26_bopystatic_tangostatic_release|x64 = py26_bopystatic_tangostatic_release|x64 py27_bopyshared_tangoshared_debug|Win32 = py27_bopyshared_tangoshared_debug|Win32 py27_bopyshared_tangoshared_debug|x64 = py27_bopyshared_tangoshared_debug|x64 py27_bopyshared_tangoshared_release|Win32 = py27_bopyshared_tangoshared_release|Win32 py27_bopyshared_tangoshared_release|x64 = py27_bopyshared_tangoshared_release|x64 py27_bopystatic_tangoshared_debug|Win32 = py27_bopystatic_tangoshared_debug|Win32 py27_bopystatic_tangoshared_debug|x64 = py27_bopystatic_tangoshared_debug|x64 py27_bopystatic_tangoshared_release|Win32 = py27_bopystatic_tangoshared_release|Win32 py27_bopystatic_tangoshared_release|x64 = py27_bopystatic_tangoshared_release|x64 py27_bopystatic_tangostatic_debug|Win32 = py27_bopystatic_tangostatic_debug|Win32 py27_bopystatic_tangostatic_debug|x64 = py27_bopystatic_tangostatic_debug|x64 py27_bopystatic_tangostatic_release|Win32 = py27_bopystatic_tangostatic_release|Win32 py27_bopystatic_tangostatic_release|x64 = py27_bopystatic_tangostatic_release|x64 py31_bopyshared_tangoshared_debug|Win32 = py31_bopyshared_tangoshared_debug|Win32 py31_bopyshared_tangoshared_debug|x64 = py31_bopyshared_tangoshared_debug|x64 py31_bopyshared_tangoshared_release|Win32 = py31_bopyshared_tangoshared_release|Win32 py31_bopyshared_tangoshared_release|x64 = py31_bopyshared_tangoshared_release|x64 py31_bopystatic_tangoshared_debug|Win32 = py31_bopystatic_tangoshared_debug|Win32 py31_bopystatic_tangoshared_debug|x64 = py31_bopystatic_tangoshared_debug|x64 py31_bopystatic_tangoshared_release|Win32 = py31_bopystatic_tangoshared_release|Win32 py31_bopystatic_tangoshared_release|x64 = py31_bopystatic_tangoshared_release|x64 py31_bopystatic_tangostatic_debug|Win32 = py31_bopystatic_tangostatic_debug|Win32 py31_bopystatic_tangostatic_debug|x64 = py31_bopystatic_tangostatic_debug|x64 py31_bopystatic_tangostatic_release|Win32 = py31_bopystatic_tangostatic_release|Win32 py31_bopystatic_tangostatic_release|x64 = py31_bopystatic_tangostatic_release|x64 py32_bopyshared_tangoshared_debug|Win32 = py32_bopyshared_tangoshared_debug|Win32 py32_bopyshared_tangoshared_debug|x64 = py32_bopyshared_tangoshared_debug|x64 py32_bopyshared_tangoshared_release|Win32 = py32_bopyshared_tangoshared_release|Win32 py32_bopyshared_tangoshared_release|x64 = py32_bopyshared_tangoshared_release|x64 py32_bopystatic_tangoshared_debug|Win32 = py32_bopystatic_tangoshared_debug|Win32 py32_bopystatic_tangoshared_debug|x64 = py32_bopystatic_tangoshared_debug|x64 py32_bopystatic_tangoshared_release|Win32 = py32_bopystatic_tangoshared_release|Win32 py32_bopystatic_tangoshared_release|x64 = py32_bopystatic_tangoshared_release|x64 py32_bopystatic_tangostatic_debug|Win32 = py32_bopystatic_tangostatic_debug|Win32 py32_bopystatic_tangostatic_debug|x64 = py32_bopystatic_tangostatic_debug|x64 py32_bopystatic_tangostatic_release|Win32 = py32_bopystatic_tangostatic_release|Win32 py32_bopystatic_tangostatic_release|x64 = py32_bopystatic_tangostatic_release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py26_bopyshared_tangoshared_debug|Win32.ActiveCfg = py26_bopyshared_tangoshared_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py26_bopyshared_tangoshared_debug|Win32.Build.0 = py26_bopyshared_tangoshared_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py26_bopyshared_tangoshared_debug|x64.ActiveCfg = py26_bopyshared_tangoshared_debug|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py26_bopyshared_tangoshared_debug|x64.Build.0 = py26_bopyshared_tangoshared_debug|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py26_bopyshared_tangoshared_release|Win32.ActiveCfg = py26_bopyshared_tangoshared_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py26_bopyshared_tangoshared_release|Win32.Build.0 = py26_bopyshared_tangoshared_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py26_bopyshared_tangoshared_release|x64.ActiveCfg = py26_bopyshared_tangoshared_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py26_bopyshared_tangoshared_release|x64.Build.0 = py26_bopyshared_tangoshared_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py26_bopystatic_tangoshared_debug|Win32.ActiveCfg = py26_bopystatic_tangoshared_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py26_bopystatic_tangoshared_debug|Win32.Build.0 = py26_bopystatic_tangoshared_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py26_bopystatic_tangoshared_debug|x64.ActiveCfg = py26_bopystatic_tangoshared_debug|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py26_bopystatic_tangoshared_debug|x64.Build.0 = py26_bopystatic_tangoshared_debug|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py26_bopystatic_tangoshared_release|Win32.ActiveCfg = py26_bopystatic_tangoshared_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py26_bopystatic_tangoshared_release|Win32.Build.0 = py26_bopystatic_tangoshared_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py26_bopystatic_tangoshared_release|x64.ActiveCfg = py26_bopystatic_tangoshared_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py26_bopystatic_tangoshared_release|x64.Build.0 = py26_bopystatic_tangoshared_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py26_bopystatic_tangostatic_debug|Win32.ActiveCfg = py26_bopystatic_tangostatic_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py26_bopystatic_tangostatic_debug|Win32.Build.0 = py26_bopystatic_tangostatic_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py26_bopystatic_tangostatic_debug|x64.ActiveCfg = py26_bopystatic_tangostatic_debug|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py26_bopystatic_tangostatic_debug|x64.Build.0 = py26_bopystatic_tangostatic_debug|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py26_bopystatic_tangostatic_release|Win32.ActiveCfg = py26_bopystatic_tangostatic_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py26_bopystatic_tangostatic_release|Win32.Build.0 = py26_bopystatic_tangostatic_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py26_bopystatic_tangostatic_release|x64.ActiveCfg = py26_bopystatic_tangostatic_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py26_bopystatic_tangostatic_release|x64.Build.0 = py26_bopystatic_tangostatic_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py27_bopyshared_tangoshared_debug|Win32.ActiveCfg = py27_bopyshared_tangoshared_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py27_bopyshared_tangoshared_debug|Win32.Build.0 = py27_bopyshared_tangoshared_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py27_bopyshared_tangoshared_debug|x64.ActiveCfg = py27_bopyshared_tangoshared_debug|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py27_bopyshared_tangoshared_debug|x64.Build.0 = py27_bopyshared_tangoshared_debug|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py27_bopyshared_tangoshared_release|Win32.ActiveCfg = py27_bopyshared_tangoshared_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py27_bopyshared_tangoshared_release|Win32.Build.0 = py27_bopyshared_tangoshared_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py27_bopyshared_tangoshared_release|x64.ActiveCfg = py27_bopyshared_tangoshared_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py27_bopyshared_tangoshared_release|x64.Build.0 = py27_bopyshared_tangoshared_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py27_bopystatic_tangoshared_debug|Win32.ActiveCfg = py27_bopystatic_tangoshared_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py27_bopystatic_tangoshared_debug|Win32.Build.0 = py27_bopystatic_tangoshared_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py27_bopystatic_tangoshared_debug|x64.ActiveCfg = py27_bopystatic_tangoshared_debug|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py27_bopystatic_tangoshared_debug|x64.Build.0 = py27_bopystatic_tangoshared_debug|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py27_bopystatic_tangoshared_release|Win32.ActiveCfg = py27_bopystatic_tangoshared_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py27_bopystatic_tangoshared_release|Win32.Build.0 = py27_bopystatic_tangoshared_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py27_bopystatic_tangoshared_release|x64.ActiveCfg = py27_bopystatic_tangoshared_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py27_bopystatic_tangoshared_release|x64.Build.0 = py27_bopystatic_tangoshared_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py27_bopystatic_tangostatic_debug|Win32.ActiveCfg = py27_bopystatic_tangostatic_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py27_bopystatic_tangostatic_debug|Win32.Build.0 = py27_bopystatic_tangostatic_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py27_bopystatic_tangostatic_debug|x64.ActiveCfg = py27_bopystatic_tangostatic_debug|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py27_bopystatic_tangostatic_debug|x64.Build.0 = py27_bopystatic_tangostatic_debug|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py27_bopystatic_tangostatic_release|Win32.ActiveCfg = py27_bopystatic_tangostatic_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py27_bopystatic_tangostatic_release|Win32.Build.0 = py27_bopystatic_tangostatic_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py27_bopystatic_tangostatic_release|x64.ActiveCfg = py27_bopystatic_tangostatic_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py27_bopystatic_tangostatic_release|x64.Build.0 = py27_bopystatic_tangostatic_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py31_bopyshared_tangoshared_debug|Win32.ActiveCfg = py31_bopyshared_tangoshared_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py31_bopyshared_tangoshared_debug|Win32.Build.0 = py31_bopyshared_tangoshared_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py31_bopyshared_tangoshared_debug|x64.ActiveCfg = py31_bopyshared_tangoshared_debug|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py31_bopyshared_tangoshared_debug|x64.Build.0 = py31_bopyshared_tangoshared_debug|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py31_bopyshared_tangoshared_release|Win32.ActiveCfg = py31_bopyshared_tangoshared_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py31_bopyshared_tangoshared_release|Win32.Build.0 = py31_bopyshared_tangoshared_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py31_bopyshared_tangoshared_release|x64.ActiveCfg = py31_bopyshared_tangoshared_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py31_bopyshared_tangoshared_release|x64.Build.0 = py31_bopyshared_tangoshared_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py31_bopystatic_tangoshared_debug|Win32.ActiveCfg = py31_bopystatic_tangoshared_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py31_bopystatic_tangoshared_debug|Win32.Build.0 = py31_bopystatic_tangoshared_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py31_bopystatic_tangoshared_debug|x64.ActiveCfg = py31_bopystatic_tangoshared_debug|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py31_bopystatic_tangoshared_debug|x64.Build.0 = py31_bopystatic_tangoshared_debug|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py31_bopystatic_tangoshared_release|Win32.ActiveCfg = py31_bopystatic_tangoshared_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py31_bopystatic_tangoshared_release|Win32.Build.0 = py31_bopystatic_tangoshared_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py31_bopystatic_tangoshared_release|x64.ActiveCfg = py31_bopystatic_tangoshared_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py31_bopystatic_tangoshared_release|x64.Build.0 = py31_bopystatic_tangoshared_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py31_bopystatic_tangostatic_debug|Win32.ActiveCfg = py31_bopystatic_tangostatic_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py31_bopystatic_tangostatic_debug|Win32.Build.0 = py31_bopystatic_tangostatic_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py31_bopystatic_tangostatic_debug|x64.ActiveCfg = py31_bopystatic_tangostatic_debug|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py31_bopystatic_tangostatic_debug|x64.Build.0 = py31_bopystatic_tangostatic_debug|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py31_bopystatic_tangostatic_release|Win32.ActiveCfg = py31_bopystatic_tangostatic_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py31_bopystatic_tangostatic_release|Win32.Build.0 = py31_bopystatic_tangostatic_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py31_bopystatic_tangostatic_release|x64.ActiveCfg = py31_bopystatic_tangostatic_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py31_bopystatic_tangostatic_release|x64.Build.0 = py31_bopystatic_tangostatic_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py32_bopyshared_tangoshared_debug|Win32.ActiveCfg = py32_bopyshared_tangoshared_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py32_bopyshared_tangoshared_debug|Win32.Build.0 = py32_bopyshared_tangoshared_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py32_bopyshared_tangoshared_debug|x64.ActiveCfg = py32_bopyshared_tangoshared_debug|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py32_bopyshared_tangoshared_debug|x64.Build.0 = py32_bopyshared_tangoshared_debug|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py32_bopyshared_tangoshared_release|Win32.ActiveCfg = py32_bopyshared_tangoshared_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py32_bopyshared_tangoshared_release|Win32.Build.0 = py32_bopyshared_tangoshared_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py32_bopyshared_tangoshared_release|x64.ActiveCfg = py32_bopyshared_tangoshared_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py32_bopyshared_tangoshared_release|x64.Build.0 = py32_bopyshared_tangoshared_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py32_bopystatic_tangoshared_debug|Win32.ActiveCfg = py32_bopystatic_tangoshared_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py32_bopystatic_tangoshared_debug|Win32.Build.0 = py32_bopystatic_tangoshared_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py32_bopystatic_tangoshared_debug|x64.ActiveCfg = py32_bopystatic_tangoshared_debug|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py32_bopystatic_tangoshared_debug|x64.Build.0 = py32_bopystatic_tangoshared_debug|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py32_bopystatic_tangoshared_release|Win32.ActiveCfg = py32_bopystatic_tangoshared_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py32_bopystatic_tangoshared_release|Win32.Build.0 = py32_bopystatic_tangoshared_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py32_bopystatic_tangoshared_release|x64.ActiveCfg = py32_bopystatic_tangoshared_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py32_bopystatic_tangoshared_release|x64.Build.0 = py32_bopystatic_tangoshared_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py32_bopystatic_tangostatic_debug|Win32.ActiveCfg = py32_bopystatic_tangostatic_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py32_bopystatic_tangostatic_debug|Win32.Build.0 = py32_bopystatic_tangostatic_debug|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py32_bopystatic_tangostatic_debug|x64.ActiveCfg = py32_bopystatic_tangostatic_debug|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py32_bopystatic_tangostatic_debug|x64.Build.0 = py32_bopystatic_tangostatic_debug|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py32_bopystatic_tangostatic_release|Win32.ActiveCfg = py32_bopystatic_tangostatic_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py32_bopystatic_tangostatic_release|Win32.Build.0 = py32_bopystatic_tangostatic_release|Win32 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py32_bopystatic_tangostatic_release|x64.ActiveCfg = py32_bopystatic_tangostatic_release|x64 {764A5ECE-11AE-4363-9A1C-573972A1BADD}.py32_bopystatic_tangostatic_release|x64.Build.0 = py32_bopystatic_tangostatic_release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal pytango-9.2.2/win/PyTango_VS9/PyTango.vcproj000066400000000000000000002357341316324373100207130ustar00rootroot00000000000000 pytango-9.2.2/win/PyTango_VS9/PyTango.vsprops000066400000000000000000000442511316324373100211140ustar00rootroot00000000000000 pytango-9.2.2/winsetup.py000066400000000000000000000056651316324373100154540ustar00rootroot00000000000000#!/usr/bin/env python # ------------------------------------------------------------------------------ # This file is part of PyTango (http://pytango.rtfd.io) # # Copyright 2006-2012 CELLS / ALBA Synchrotron, Bellaterra, Spain # Copyright 2013-2014 European Synchrotron Radiation Facility, Grenoble, France # # Distributed under the terms of the GNU Lesser General Public License, # either version 3 of the License, or (at your option) any later version. # See LICENSE.txt for more info. # ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ # WARNING: This script should only be executed as a Post-Build Event from inside # Microsoft Visual Studio and not from the command line # ------------------------------------------------------------------------------ from __future__ import print_function import sys import os import os.path as osp def main(): executable = sys.executable curr_dir = os.getcwd() winsetup_dir = osp.dirname(osp.abspath(__file__)) os.chdir(winsetup_dir) setup_name = "setup.py" bitmap = osp.join(winsetup_dir, 'doc', 'logo-medium.bmp') ver = ".".join(map(str, sys.version_info[:2])) if len(sys.argv) < 6: print("Need to supply build directory, distribution directory, temporary binary install directory, configuration name and platform name") return 1 build_dir, dist_dir, bdist_dir = map(osp.abspath, sys.argv[1:4]) config_name, plat_name = sys.argv[4:6] # temp_base_dir = osp.abspath(os.environ["TEMP"]) # temp_dir = osp.join(temp_base_dir, "PyTango", config_name) if plat_name == 'x64': plat_name = 'win-amd64' try: cmd_line = '%s %s ' % (executable, setup_name) cmd_line += 'build_py --force --no-compile ' \ '--build-lib=%s ' \ % (build_dir,) cmd_line += 'build_scripts --force ' cmd_line += 'install_lib --skip-build --no-compile ' \ '--build-dir=%s ' \ % (build_dir, ) cmd_line += 'bdist_msi --skip-build --target-version=%s ' \ '--bdist-dir=%s ' \ '--dist-dir=%s ' \ '--plat-name=%s ' % (ver, bdist_dir, dist_dir, plat_name) cmd_line += 'bdist_wininst --skip-build --target-version=%s ' \ '--bdist-dir=%s ' \ '--dist-dir=%s ' \ '--title="PyTango 8" ' \ '--bitmap="%s" ' \ '--plat-name=%s ' % (ver, bdist_dir, dist_dir, bitmap, plat_name) os.system(cmd_line) except: print("Failed:") import traceback traceback.print_exc() return 2 finally: os.chdir(curr_dir) return 0 if __name__ == "__main__": ret = main() sys.exit(ret)