././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1722935059.1807055 guidata-3.6.2/0000755000175100001770000000000014654363423012557 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/CHANGELOG.md0000644000175100001770000011122514654363416014374 0ustar00runnerdocker# Changelog # ## Version 3.6.2 ## In this release, test coverage is 74%. ЁЯЫая╕П Bug fixes: * Light/dark theme support: * Fix default color mode issues * Color theme test: allow to derive from, so that the test may be completed by other widgets ## Version 3.6.1 ## In this release, test coverage is 74%. ЁЯЫая╕П Bug fixes: * Light/dark theme support: * Auto light/dark theme: quering OS setting only once, or each time the `set_color_mode('auto')` function is called * Fix console widget color theme: existing text in console widget was not updated when changing color theme * Fixed issue with dark theme on Windows: the windows title bar background was not updated when the theme was changed from dark to light (the inverse was working) - this is now fixed in `guidata.qthelpers.win32_fix_title_bar_background` function * Added `guidata.qthelpers.set_color_mode` function to set the color mode ('dark', 'light' or 'auto' for system default) * Added `guidata.qthelpers.get_color_mode` function to get the current color mode ('dark', 'light' or 'auto' for system default) * Added `guidata.qthelpers.get_color_theme` function to get the current color theme ('dark' or 'light') * Added `guidata.qthelpers.get_background_color` function to get the current background `QColor` associated with the current color theme * Added `guidata.qthelpers.get_foreground_color` function to get the current foreground `QColor` associated with the current color theme * Added `guidata.qthelpers.is_dark_theme` function to check if the current theme is dark) * As a consequence, `guidata.qthelpers.is_dark_mode` and `guidata.qthelpers.set_dark_mode` functions are deprecated, respectively in favor of `guidata.qthelpers.is_dark_theme` and `guidata.qthelpers.set_color_mode` ## Version 3.6.0 ## In this release, test coverage is 74%. ЁЯТе New features: * Improved dark/light mode theme update: * The theme mode may be changed during the application lifetime * Added methods `update_color_mode` on `CodeEditor` and `ConsoleBaseWidget` widgets ## Version 3.5.3 ## In this release, test coverage is 74%. ЁЯЫая╕П Bug fixes: * Configuration initialization on Windows: * For various reasons, a `PermissionError` exception may be raised when trying to remove the configuration file on Windows, just after having created it for the first time. This is due to the fact that the file is still locked by the file system, even if the file has been closed. This is a known issue with Windows file system, and the solution is to wait a little bit before trying to remove the file. * To fix this issue, a new `try_remove_file` function has been added to the `userconfig` module, which tries multiple times to remove the file before raising an exception. * Moved back `conftest.py` to the `tests` folder (was in the root folder), so that `pytest` can be executed with proper configuration when running the test suite from the installed package ## Version 3.5.2 ## In this release, test coverage is 74%. ЁЯЫая╕П Bug fixes: * Add support for NumPy 2.0: * Use `numpy.asarray` instead of `numpy.array(..., copy=False)` * Remove deprecated `numpy.core.multiarray` module import ## Version 3.5.1 ## In this release, test coverage is 74%. ЁЯЫая╕П Bug fixes: * [PR #74](https://github.com/PlotPyStack/guidata/pull/74) - `configtools.font_is_installed`: fix PySide2 compat. issue (thanks to @xiaodaxia-2008) * Creating a dataset using the `create` class method: * Before, passing unknown keyword arguments failed silently (e.g. `MyParameters.create(unknown=42)`). * Now, an `AttributeError` exception is raised when passing unknown keyword arguments, as expected. * Processing Qt event loop in unattended mode before closing widgets and quitting the application, so that all pending events are processed before quitting: this includes for instance the drawing events of widgets, which may be necessary to avoid a crash when closing the application (e.g. if drawing the widget is required for some reason before closing it) or at least to ensure that test coverage includes all possible code paths. тД╣я╕П Other changes: * Preparing for NumPy V2 compatibility: this is a work in progress, as NumPy V2 is not yet released. In the meantime, requirements have been updated to exclude NumPy V2. * Internal package reorganization: moved icons to `guidata/data/icons` folder * The `delay` command line option for environment execution object `execenv` is now expressed in milliseconds (before it was in seconds), for practical reasons * Explicitely exclude NumPy V2 from the dependencies (not compatible yet) ## Version 3.5.0 ## In this release, test coverage is 74%. ЁЯТе New features: * New Sphinx autodoc extension: * Allows to document dataset classes and functions using Sphinx directives, thus generating a comprehensive documentation for datasets with labels, descriptions, default values, etc. * The extension is available in the `guidata.dataset.autodoc` module * Directives: * `autodataset`: document a dataset class * `autodataset_create`: document a dataset creation function * `datasetnote`: add a note explaining how to use a dataset * `BoolItem`/`TextItem`: add support for callbacks when the item value changes ЁЯЫая╕П Bug fixes: * Documentation generation: automatic requirement table generation feature was failing when using version conditions in the `pyproject.toml` file (e.g. `pyqt5 >= 5.15`). * [Issue #72](https://github.com/PlotPyStack/guidata/issues/72) - unit test leave files during the build usr/lib/python3/dist-packages/test.json * [Issue #73](https://github.com/PlotPyStack/guidata/issues/73) - `ChoiceItem` radio buttons are duplicated when using callbacks ## Version 3.4.1 ## In this release, test coverage is 76%. ЁЯЫая╕П Bug fixes: * [Issue #71](https://github.com/PlotPyStack/guidata/issues/71) - Random segmentation faults with applications embedding `CodeEditor` * [Issue #70](https://github.com/PlotPyStack/guidata/issues/70) - PermissionError: [Errno 13] Permission denied: '/usr/lib/python3/dist-packages/guidata/tests/data/genreqs/requirements.rst' ## Version 3.4.0 ## In this release, test coverage is 76%. ЁЯТе New features: * `dataset.io.h5fmt.HDF5Reader.read` method: added new `default` argument to set default value for missing data in the HDF5 file (backward compatible). The default value of `default` is `NoDefault` (a special value to indicate that no default value should be used, and that an exception should be raised if the data is missing). * `widgets.codeeditor.CodeEditor`: added new `inactivity_timeout` argument to set the time (in milliseconds) to wait after the user has stopped typing before emitting the `CodeEditor.SIG_EDIT_STOPPED` signal. * Added `execenv.accept_dialogs` attribute to control whether dialogs should be automatically accepted or not (default is `None`, meaning no automatic acceptance): this allows more coverage of the test suite. For now, this attribute has only been proven useful in `tests/dataset/test_all_features.py`. * Added unit tests for HDF5 and JSON serialization/deserialization: * Testing an arbitrary data model saved/loaded to/from HDF5 and JSON files, with various data sets and other data types. * Testing for backward compatibility with previous versions of the data model (e.g. new attributes, removed attributes, etc.) тЪая╕П API breaking changes: * `guidata.dataset.io` module is now deprecated and will be removed in a future release. Please use `guidata.io` instead. This change is backward compatible (the old module is still available and will be removed in a future release). The motivation for this change is to simplify the module structure and to help understand that the scope of the `io` module is not limited to `dataset.DataSet` objects, but may be used for any kind of data serialization/deserialization. ЁЯУЦ Documentation: * Added missing `DataSetEditDialog` and `DataSetEditLayout` classes * Added missing inheritance/member details on some classes * Reduced table of contents depth in left sidebar for better readability ## Version 3.3.0 ## In this release, test coverage is 72%. ЁЯТе New features: * Array editor now supports row/column insertion/deletion: * Added `variable_size` argument to `setup_and_check` method * The feature is disabled by default (backward compatible) * It supports standard arrays, masked arrays, record arrays and N-dimensional arrays * New dataset read-only mode: * Added `readonly` argument to `DataSet` constructor * This is useful to create a dataset that will be displayed in read-only mode (e.g. string editing widgets will be in read-only mode: text will be selectable but not editable) * The items remain modifiable programmatically (e.g. `dataset.item = 42`) * New dataset group edit mode: * Added `mode` argument to `DataSetGroup.edit` method, with the following options: * `mode='tabs'` (default): each dataset is displayed in a separate tab * `mode='table'`: all datasets are displayed in a single table * In the new table mode, the datasets are displayed in a single table with one row per dataset and one column per item * Clicking on a row will display the corresponding dataset in a modal dialog box ЁЯЫая╕П Bug fixes: * Qt console: * Fixed `RuntimeError: wrapped C/C++ object of type DockableConsole has been deleted` when closing the console widget (parent widget, e.g. a `QMainWindow`, was deleted) while an output stream is still writing to the console (e.g. a `logging` handler which will flush the output stream when closing the application) * This concerns all console-related widgets: `DockableConsole`, `Console`, `InternalShell`, `PythonShellWidget` and `ShellBaseWidget` * Code editor: fixed compatibility issue with PySide6 (`AttributeError: 'QFont' object has no attribute 'Bold'`) ## Version 3.2.2 ## ЁЯЫая╕П Bug fixes: * Fixed translation support (`gettext`): * Locale detection has been fixed in 3.1.1 (deprecation of `locale.getdefaultlocale`) * However, on frozen distributions on Windows (e.g. with `pyinstaller`), function `locale.getlocale` is returning `(None, None)` instead of proper locale infos * Added a workaround: on Windows, if locale can't be detected, we now use the Windows API to retrieve it (using the `GetUserDefaultLocaleName` function) * [Issue #68](https://github.com/PlotPyStack/guidata/issues/68) - Windows: gettext translation is not working on frozen applications * Embedded Qt console: * Fixed default encoding detection on frozen applications on Windows * [Issue #69](https://github.com/PlotPyStack/guidata/issues/69) - Windows/Qt console: output encoding is not detected on frozen applications ## Version 3.2.1 ## ЁЯЫая╕П Bug fixes: * Tests only: `qthelpers.close_widgets_and_quit` now ignores deleted widgets ЁЯТе Changes: * `dataset.ImageChoiceItem` and `dataset.ButtonItem`: added `size` argument to set the icon size * `dataset.io` reader and writer classes: removed deprecated `write_unicode` method ## Version 3.2.0 ## ЁЯЫая╕П Bug fixes: * [Issue #67](https://github.com/PlotPyStack/guidata/issues/67) - JSONReader/Deserializing object list: TypeError: 'NoneType' object is not subscriptable ЁЯТе Changes: * `qthelpers.qt_wait`: added `show_message` and `parent` arguments (backward compatible) * `qthelpers.qt_app_context`: removed `faulthandler` support (this need to be handled at the application level, see for example [DataLab's implementation](https://github.com/Codra-Ingenierie-Informatique/DataLab/blob/2a7e95477a8dfd827b037b39ef5e045309760dc8/cdlapp/utils/qthelpers.py#L87)) * Disabled command line argument parsing in `guidata.env` module: * The `guidata` library is parsing command line arguments for the purpose of creating the environment execution object named `execenv` (see `guidata.env` module). This object is used to determine the execution environment mainly for testing purposes: for example, to bypass the Qt event loop when running tests thanks to the `--unattended` command line option. * However this argument parsing is not always desirable, for example when using `guidata` as a dependency in another library or application. This is why the parsing mechanism is now disabled by default, and may be enabled by setting the environment variable `GUIDATA_PARSE_ARGS` to `1` (or any other non-empty value). As of today, it is still unclear if there will be a need to enable this mechanism in the future, so this is why the environment variable is used instead of a function argument. * Removed deprecated `guidata.disthelpers` module (we recommend using [PyInstaller](https://www.pyinstaller.org/) instead) ## Version 3.1.1 ## ЁЯЫая╕П Bug fixes: * 'Apply' button state is now correctly updated when modifying one of the following items: * `dataset.MultipleChoiceItem` * `dataset.dataitems.DictItem` * `dataset.dataitems.FloatArrayItem` * Fixed minor deprecation and other issues related to locale ЁЯТе Changes: * Removed `--unattended` command line option for `pytest`: * Before: `pytest --unattended guidata` (to run tests without Qt event loop) * Now: `pytest guidata` (there is no use case for running tests with Qt event loop, so the `--unattended` option was removed and the *unattended* mode is now the default) * Removed CHM documentation (obsolete format) ## Version 3.1.0 ## тЪа Exceptionally, this release contains the following API breaking changes: * Moved `utils.update_dataset` to `dataset.conv.update_dataset` * Moved `utils.restore_dataset` to `dataset.conv.restore_dataset` тЬФ API simplification (backward compatible): * Dataset items may now be imported from `guidata.dataset` instead of `guidata.dataset.dataitems` * Dataset types may now be imported from `guidata.dataset` instead of `guidata.dataset.datatypes` * Examples: * `from guidata.dataset.dataitems import FloatItem` becomes `from guidata.dataset import FloatItem` * `from guidata.dataset.datatypes import DataSet` becomes `from guidata.dataset import DataSet` * Or you may now write: ```python import guidata.dataset as gds class MyParameters(gds.DataSet): """My parameters""" freq = gds.FloatItem("Frequency", default=1.0, min=0.0, nonzero=True) amp = gds.FloatItem("Amplitude", default=1.0, min=0.0) ``` ЁЯТе New features: * New `dataset.create_dataset_from_dict`: create a dataset from a dictionary, using keys and values to create the dataset items * New `dataset.create_dataset_from_func`: create a dataset from a function signature, using type annotations and default values to create the dataset items * `dataset.dataitems.StringItem`: * Added argument `password` to hide text (useful for passwords) * Added argument `regexp` to validate text using a regular expression * `dataset.dataitems.FileSaveItem`, `dataset.dataitems.FileOpenItem`, `dataset.dataitems.FilesOpenItem` and `dataset.dataitems.DirectoryItem`: added argument `regexp` to validate file/dir name using a regular expression * `dataset.dataitems.DictItem`: added support for HDF5 and JSON serialization * `dataset.io.h5fmt` and `dataset.io.jsonfmt`: added support for lists and dictionnaries serialization тЩ╗ New PlotPyStack internal features: * `widgets.about`: handle about dialog box informations (Python, Qt, Qt bindings, ...) * Renamed development environment variable `GUIDATA_PYTHONEXE` to `PPSTACK_PYTHONEXE` ЁЯз╣ Bug fixes: * Fixed Qt6 compatibility issue with `QFontDatabase` ## Version 3.0.6 ## Bug fixes: * `widgets.console.interpreter`: replaced threading.Thread.isAlive (deprecated since Python 3.8) Other changes: * `DataSet.edit`, `DataSet.view` and `DataSetGroup.edit`: added missing arguments `size` and `wordwrap` * Documentation: added check-list before submitting a patch (see [`contribute.rst`](https://github.com/PlotPyStack/guidata/blob/master/doc/dev/contribute.rst) file) * Fixed some typing annotations and docstrings, as well as Pylint false positives * Removed unused functions from `guidata.utils.encoding` module: * `transcode` * `getfilesystemencoding` * Added missing docstrings and typing annotations in modules: * `guidata.dataset.qtitemwidgets` * `guidata.dataset.qtwidgets` * `guidata.utils.encoding` * `guidata.utils.misc` ## Version 3.0.5 ## Bug fixes: * [Issue #65](https://github.com/PlotPyStack/guidata/issues/65) - QVariant import erroneously used in typing annotations Other changes: * `tests.test_callbacks`: added an example of a callback function for dynamically changing the list of choices of a `ChoiceItem` object ## Version 3.0.4 ## Bug fixes: * [Issue #63](https://github.com/PlotPyStack/guidata/issues/63) - [3.0.2] there is no more guidata-test script * [Issue #62](https://github.com/PlotPyStack/guidata/issues/62) - [3.0.2] sphinx doc hang when building on the Debian infra Other changes: * [Issue #64](https://github.com/PlotPyStack/guidata/issues/64) - Add guidata-tests.desktop file to repository ## Version 3.0.3 ## Fixed project description: * This could be seen as a detail, but as this description text is used by PyPI, it is important to have a correct description. * Of course, nobody reads the description text, so it was not noticed since the first release of guidata v3.0. ## Version 3.0.2 ## Bug fixes: * [Pull Request #61](https://github.com/PlotPyStack/guidata/pull/61) - Make the build reproducible, by [@lamby](https://github.com/lamby) * [Issue #59](https://github.com/PlotPyStack/guidata/issues/59) - [3.0.1] the doc is missing * [Issue #60](https://github.com/PlotPyStack/guidata/issues/60) - [3.0.1] pyproject.toml/setuptools: automatic package discovery does not work on debian ## Version 3.0.1 ## API changes (fixes inconsistencies in API): * Moved `guidata.dataset.iniio.WriterMixin` to `guidata.dataset.io.WriterMixin` * Moved `guidata.dataset.iniio.BaseIOHandler` to `guidata.dataset.io.BaseIOHandler` * Moved `guidata.dataset.iniio` to `guidata.dataset.io.inifmt` and renamed: * `UserConfigIOHandler` to `INIHandler` * `UserConfigWriter` to `INIWriter` * `UserConfigReader` to `INIReader` * Moved `guidata.dataset.jsonio` to `guidata.dataset.io.jsonfmt` * Moved `guidata.dataset.hdf5io` to `guidata.dataset.io.h5fmt` Bug fixes: * [Issue #57](https://github.com/PlotPyStack/guidata/issues/57) - [Errno 2] No such file or directory: 'doc/dev/v2_to_v3.csv' * [Issue #58](https://github.com/PlotPyStack/guidata/issues/58) - Test suite: missing dependencies (pandas, Pillow) * Modules `guidata.dataset.datatypes` and `guidata.dataset.dataitems` should not critically depend on Qt (only modules specific to GUI should depend on Qt, such as `guidata.dataset.qtwidgets`). This was a regression introduced in version 3.0.0. A new unit test was added to prevent this kind of regression in the future. * Fixed documentation generation `.readthedocs.yaml` file (Qt 5.15 was not installed on ReadTheDocs servers, causing documentation build to fail) Other changes: * [Pull Request #55](https://github.com/PlotPyStack/guidata/pull/55) - DateItem and DateTimeItem: added 'format' parameter for formatting, by [@robochat](https://github.com/robochat) * Packaging: still using `setuptools`, switched from `setup.cfg` to `pyproject.toml` for configuration (see [PEP 517](https://www.python.org/dev/peps/pep-0517/)) ## Version 3.0.0 ## New major release: * New BSD 3-Clause License * Black code formatting on all Python files * New automated test suite: * Added module `guidata.env` to handle execution environment * Added support for an "unattended" execution mode (Qt loop is bypassed) * Added support for pytest fixtures * Added support for coverage testing: 70% coverage to date * Documentation was entirely rewritten using Sphinx * Reorganized modules: * Moved `guidata.hd5io` to `guidata.dataset.hdf5io` * Moved `guidata.jsonio` to `guidata.dataset.jsonio` * Renamed `guidata.userconfigio` to `guidata.dataset.iniio` * New package `guidata.utils` for utility functions: * Removed deprecated or unused functions in old `guidata.utils` module * Moved old `guidata.utils` module to `guidata.utils.misc`, except the functions `update_dataset` and `restore_dataset` which are still in `guidata.utils` (root module) * Moved `guidata.encoding` to `guidata.utils.encoding` * Moved `guidata.gettext_helpers` to `guidata.utils.gettext_helpers` * Splitted `guidata.qtwidgets` in two modules: * `guidata.widgets.dockable` for dockable widgets * `guidata.widgets.rotatedlabel` for rotated label * Other changes: * `guidata.guitest`: * Added support for subpackages * New comment directive (`# guitest: show`) to add test module to test suite or to show test module in test launcher (this replaces the old `SHOW = True` line) * `guidata.dataset.datatypes.DataSet`: new `create` class method for concise dataset creation, allowing to create a dataset with a single line of code by passing default item values as keyword arguments ## Older releases ## ### Version 2.3.1 ### Bug fixes: * Fixed critical compatibility issue with Python 3.11 (`codeset` argument was removed from `gettext.translation` function) * Fixed support for `DateTimeItem` and `DateItem` objects serializing (HDF5 and JSON) * Fixed JSONReader constructor documentation: more explicit docstring * Fixed test_dataframeeditor.py test script (issue with QApplication creation) ### Version 2.3.0 ### Changes: * Added JSON serialize/deserialize support for `DataSet` objects (from CodraFT project, ) * Array editor: switching to read-only mode when array is not writeable * Object editor (`oedit` function): cleaner implementation, handling widget parenting (code specifically related to Spyder internal shell was removed) Bug fixes: * Array editor: fixed error when NumPy array flag "writeable" is False, do not try to change flag value since it's a deprecated feature since NumPy v1.17 * Do not install Qt translator and set color mode (dark/light) on Qt application if it already has been initialized (QApplication instance is not None) ### Version 2.2.1 ### Bug fixes: * Collection editor: fixed "Save array" feature * Console widget context menu: added missing icons ### Version 2.2.0 ### Changes: * FloatArrayItem: added data type information on associated widget * guitest.TestModule.run: added timeout argument to wait for process termination Bug fixes: * FloatArrayItem: avoid RuntimeWarning when dealing with complex data * external/darkdetect: fixed compatibility issue with Windows Server 2008 R2 ### Version 2.1.1 ### Bug fixes: * win32_fix_title_bar_background: not working in 32bits ### Version 2.1.0 ### Changes: * Dark mode may be overriden by QT_COLOR_MODE environment variable ### Version 2.0.4 ### Bug fixes: * Fixed missing import for DictItem callback ### Version 2.0.3 ### Changes: * Code editor: added support for other languages than Python (C++, XML, ...) Bug fixes: * Fixed Qt5 translation standard support * Fixed code editor/console widgets dark mode default settings ### Version 2.0.2 ### Bug fixes: * Fixed PySide6 compatibility issues * Fixed remaining Python 3 compatibility issues ### Version 2.0.1 ### Bug fixes: * Fixed Python 3 compatibility issues ### Version 2.0.0 ### Changes: * Removed support for Python 2.7 and PyQt4 (guidata supports Python >=3.6 and PyQt5, PySide2, PyQt6, PySide6 through QtPy 2) * Added support for dark theme mode on Windows (including windows title bar background), MacOS and GNU/Linux. * Added embbeded Qt-based Python console widget * Dataset edit layout: now disabling/enabling "Apply" button depending on widget value changes * Code editor: widget minimum size area may now be set using rows and columns size * Test launcher: redesigned, added support for dark mode ### Version 1.8.0 ### Changes: * Added generic widgets: array, dictionary, text and code editors. * Removed `spyderlib`/`spyder` dependency. * Added setter method on DataItem object for "help" text (fixed part of the tooltip). ### Version 1.7.9 ### Changes: * Added PySide2 support: guidata is now compatible with Python 2.7, Python 3.4+, PyQt4, PyQt5 and PySide2! ### Version 1.7.8 ### Changes: * Added PyQt4/PyQt5/PySide automatic switch depending on installed libraries * Moved documentation to ### Version 1.7.7 ### Bug fixes: * Fixed Spyder v4.0 compatibility issues. ### Version 1.7.6 ### Bug fixes: * Fixed Spyder v3.0 compatibility issues. ### Version 1.7.5 ### Bug fixes: * `FilesOpenItem.check_value` : if value is None, return False (avoids "None Type object is not iterable" error) ### Version 1.7.4 ### Bug fixes: * Fixed compatibility issue with Python 3.5.1rc1 (Issue #32: RecursionError in `userconfig.UserConfig.get`) * `HDF5Reader.read_object_list`: fixed division by zero (when count was 1) * `hdf5io`: fixed Python3 compatibility issue with unicode_hdf type converter ### Version 1.7.3 ### Features: * Added CHM documentation to wheel package * hdf5io: added support for a progress bar callback in "read_object_list" (this allows implementing a progress dialog widget showing the progress when reading an object list in an HDF5 file) Bug fixes: * Python 3 compatibility: fixed `hdf5io.HDF5Writer.write_object_list` method * data items: * StringItem: when `notempty` parameter was set to True, item value was not checked at startup (expected orange background) * disthelpers: * Supporting recent versions of SciPy, h5py and IPython * Fixed compatibility issue (workaround) with IPython on Python 2.7 (that is the "collection.sys cx_Freeze error") ### Version 1.7.2 ### Bug fixes: * Fixed compatibility issues with old versions of Spyder () * disthelpers: * vs2008 option was ignored * added 'C:\Program Files (x86)' to bin includes (cx_Freeze) * Data items/callbacks: fixed callbacks for ChoiceItem (or derived items) which were triggered when other widgets were triggering their own callbacks... Other changes: * Added test for item callbacks * dataset.datatypes.FormatProp/new behavior: added `ignore_error` argument, default to True (ignores string formatting error: ValueError) * disthelpers: * Distribution.Setup: added `target_dir` option * Distribution.build: added `create_archive` option to create a ZIP archive after building the package * cx_Freeze: added support for multiple executables * added support for h5py 2.0 * added support for Maplotlib 1.1 * Allow DateTime edit widgets to popup calendar ### Version 1.4.0 ### Possible API compatibility issues: * disthelpers: removed functions remove_build_dist, add_module_data_files, add_text_data_file, get_default_excludes, get_default_includes, get_default_dll_excludes, create_vs2008_data_files (...) which were replaced by a class named Distribution, see the new disthelpers test for more details (tests/dishelpers.py) * reorganized utils and configtools modules Other changes: * disthelpers: replaced almost all functions by a class named Distribution, and added support for cx_Freeze (module remains compatible with py2exe), see the new disthelpers test for more details (tests/dishelpers.py) * reorganized utils and configtools modules ### Version 1.3.2 ### Since this version, `guidata` is compatible with PyQt4 API #1 *and* API #2. Please read carefully the coding guidelines which have been recently added to the documentation. Possible API compatibility issues: * Removed deprecated wrappers around QFileDialog's static methods (use the wrappers provided by `guidata.qt.compat` instead): * getExistingDirectory, getOpenFileName, getOpenFileNames, getSaveFileName Bug fixes: * qtwidgets.ShowFloatArrayWidget: fixed string float formatting issue (replaced %f by %g) * Fixed compatiblity issues with PyQt v4.4 (Contributor: Carlos Pascual) * Fixed missing 'child_title' attribute error with FileOpenItem, FilesOpenItem, FileSaveItem and DirectoryItem * (Fixes Issue 8) disthelpers.add_modules was failing when vs2008=False Other changes: * added *this* changelog * qtwidgets: removed ProgressPopUp dialog (it is now recommended to use QProgressDialog instead, which is pretty much identical) * Replaced QScintilla by spyderlib (as a dependency for array editor, code editor (test launcher) and dict editor) * qtwidgets.DockWidgetMixin: added method 'setup_dockwidget' to change dockwidget's features, location and allowed areas after class instantiation * guidata.utils.utf8_to_unicode: translated error message in english * Add support for 'int' in hdf5 save function * guidata.dataset/Numeric items (FloatItem, IntItem): added option 'unit' (automatically add suffix ' (unit)' to label in edit mode and suffix ' unit' to value in read-only mode) * Improved dataset __str__ method: code refactoring with read-only dataset widgets (DataItem: added methods 'format_string' and 'get_string_value', DataSet: added method 'to_string') * Added coding guidelines to the documentation * guidata.dataset.qtwidget: added specific widget (ShowBooleanWidget) for read-only display of bool items (text is striked out when value is False) * guidata.hdf5io.Dset: added missing keyword argument 'optional' (same effect as parent class Attr) * guidata.dataset.dataitems.IntItem objects: added support for sliders (fixes Issue 9) with option slider=True (see documentation) ### Version 1.3.1 ### Bug fixes: * setup.py: added svg icons to data files * gettext helpers were not working on Linux (Windows install pygettext was hardcoded) Other changes: * hdf5io: printing error messages in sys.stderr + added more infos when failing to load attribute ### Version 1.3.0 ### Bug fixes: * setup.py: added svg icons to data files * gettext helpers were not working on Linux (Windows install pygettext was hardcoded) * DataSet/bugfix: comment/title options now override the DataSet class __doc__ attribute * Added missing option 'basedir' for FilesOpenItem * DirectoryItem: fixed missing child_title attribute bug * For all DataSet GUI representation, the comment text is now word-wrapped * Bugfix: recent versions of PyQt don't like the QApplication reference to be stored in modules (why is that?) * Bugfix/tests: always keep a reference to the QApplication instance Other changes: * setup.py: added source archive download url * Tests: now creating real temporary files and cleaning up at exit * qtAllow a callback on LineEditWidget to notify about text changes (use set_prop("display", "callback", callback)) * qthelpers: provide wrapper for qt.getOpen/SaveFileName to work around win32 bug * qtwidgets: optionally hide apply button in DataSetEditGroupBox * added module guidata.qtwidgets (moved some generic widgets from guidata.qthelpers and from other external packages) * qthelpers: added helper 'create_groupbox' (QGroupBox object creation) * Array editor: updated code from Spyder's array editor (original code) * Added package guidata.editors: contains editor widgets derived from Spyder editor widgets (array editor, dictionary editor, text editor) * Array editor: added option to set row/col labels (resp. ylabels and xlabels) * ButtonItem: changed callback arguments to*instance* (DataSet object), *value* (item value), *parent* (button's parent widget) * editors.DictEditor.DictEditor: moved options from constructor to 'setup' method (like ArrayEditor's setup_and_check), added parent widget to constructor options * Added DictItem type: simple button to edit a dictionary * editors.DictEditor.DictEditor/bugfixes: added action "Insert" to context menu for an empty dictionary + fixed inline unicode editing (was showing the error message "Unable to assign data to item") * guidata.qtwidgets: added 'DockableWidgetMixin' to fabricate any dockable QWidget class * gettext helpers: added support for individual module translation (until now, only whole packages were supported) * DataSetShowGroupBox/DataSetEditGroupBox: **kwargs may now be passed to the DataSet constructor * disthelpers: added 'scipy.io' to supported modules (includes) * Added new "value_callback" display property: this function is called when QLineEdit text has changed (item value is passed) * Added option to pass a text formatting function in DataSetShowWidget ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/LICENSE0000644000175100001770000000277614654363416013602 0ustar00runnerdockerBSD 3-Clause License Copyright (c) 2023, CEA-Codra, Pierre Raybaut. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/MANIFEST.in0000644000175100001770000000011114654363416014310 0ustar00runnerdockergraft doc include *.desktop include CHANGELOG.md include requirements.txt././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1722935059.1807055 guidata-3.6.2/PKG-INFO0000644000175100001770000001501114654363423013652 0ustar00runnerdockerMetadata-Version: 2.1 Name: guidata Version: 3.6.2 Summary: Automatic GUI generation for easy dataset editing and display Author-email: Codra License: BSD 3-Clause License Copyright (c) 2023, CEA-Codra, Pierre Raybaut. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Project-URL: Homepage, https://github.com/PlotPyStack/guidata/ Project-URL: Documentation, https://guidata.readthedocs.io/en/latest/ Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Education Classifier: Intended Audience :: Science/Research Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: MacOS :: MacOS X Classifier: Operating System :: Microsoft :: Windows :: Windows 7 Classifier: Operating System :: Microsoft :: Windows :: Windows 8 Classifier: Operating System :: Microsoft :: Windows :: Windows 10 Classifier: Operating System :: Microsoft :: Windows :: Windows 11 Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Topic :: Scientific/Engineering Classifier: Topic :: Scientific/Engineering :: Human Machine Interfaces Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Software Development :: User Interfaces Classifier: Topic :: Software Development :: Widget Sets Classifier: Topic :: Utilities Requires-Python: <4,>=3.8 Description-Content-Type: text/markdown License-File: LICENSE Requires-Dist: h5py>=3.0 Requires-Dist: NumPy>=1.21 Requires-Dist: QtPy>=1.9 Requires-Dist: requests Requires-Dist: tomli Provides-Extra: dev Requires-Dist: ruff; extra == "dev" Requires-Dist: pylint; extra == "dev" Requires-Dist: Coverage; extra == "dev" Provides-Extra: doc Requires-Dist: PyQt5; extra == "doc" Requires-Dist: pillow; extra == "doc" Requires-Dist: pandas; extra == "doc" Requires-Dist: sphinx>6; extra == "doc" Requires-Dist: myst_parser; extra == "doc" Requires-Dist: sphinx-copybutton; extra == "doc" Requires-Dist: sphinx_qt_documentation; extra == "doc" Requires-Dist: python-docs-theme; extra == "doc" Provides-Extra: test Requires-Dist: pytest; extra == "test" Requires-Dist: pytest-xvfb; extra == "test" # guidata: Automatic GUI generation for easy dataset editing and display with Python [![pypi version](https://img.shields.io/pypi/v/guidata.svg)](https://pypi.org/project/guidata/) [![PyPI status](https://img.shields.io/pypi/status/guidata.svg)](https://github.com/PlotPyStack/guidata/) [![PyPI pyversions](https://img.shields.io/pypi/pyversions/guidata.svg)](https://pypi.python.org/pypi/guidata/) [![download count](https://img.shields.io/conda/dn/conda-forge/guidata.svg)](https://www.anaconda.com/download/) тД╣я╕П Created in 2009 by [Pierre Raybaut](https://github.com/PierreRaybaut) and maintained by the [PlotPyStack](https://github.com/PlotPyStack) organization. ## Overview The `guidata` package is a Python library generating Qt graphical user interfaces. It is part of the [PlotPyStack](https://github.com/PlotPyStack) project, aiming at providing a unified framework for creating scientific GUIs with Python and Qt. Simple example of `guidata` datasets embedded in an application window: ![Example](https://raw.githubusercontent.com/PlotPyStack/guidata/master/doc/images/screenshots/editgroupbox.png) See [documentation](https://guidata.readthedocs.io/en/latest/) for more details on the library and [changelog](https://github.com/PlotPyStack/guidata/blob/master/CHANGELOG.md) for recent history of changes. Copyrights and licensing: * Copyright ┬й 2023 [CEA](https://www.cea.fr), [Codra](https://codra.net/), [Pierre Raybaut](https://github.com/PierreRaybaut). * Licensed under the terms of the BSD 3-Clause (see [LICENSE](https://github.com/PlotPyStack/guidata/blob/master/LICENSE)). ## Features Based on the Qt library, `guidata` is a Python library generating graphical user interfaces for easy dataset editing and display. It also provides helpers and application development tools for Qt (PyQt5, PySide2, PyQt6, PySide6). Generate GUIs to edit and display all kind of objects regrouped in datasets: * Integers, floats, strings * Lists (single/multiple choices) * Dictionaries * `ndarrays` (NumPy's N-dimensional arrays) * Etc. Save and load datasets to/from HDF5, JSON or INI files. Application development tools: * Data model (internal data structure, serialization, etc.) * Configuration management * Internationalization (`gettext`) * Deployment tools * HDF5, JSON and INI I/O helpers * Qt helpers * Ready-to-use Qt widgets: Python console, source code editor, array editor, etc. ## Dependencies and installation See [Installation](https://guidata.readthedocs.io/en/latest/installation.html) section in the documentation for more details. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/README.md0000644000175100001770000000511314654363416014040 0ustar00runnerdocker# guidata: Automatic GUI generation for easy dataset editing and display with Python [![pypi version](https://img.shields.io/pypi/v/guidata.svg)](https://pypi.org/project/guidata/) [![PyPI status](https://img.shields.io/pypi/status/guidata.svg)](https://github.com/PlotPyStack/guidata/) [![PyPI pyversions](https://img.shields.io/pypi/pyversions/guidata.svg)](https://pypi.python.org/pypi/guidata/) [![download count](https://img.shields.io/conda/dn/conda-forge/guidata.svg)](https://www.anaconda.com/download/) тД╣я╕П Created in 2009 by [Pierre Raybaut](https://github.com/PierreRaybaut) and maintained by the [PlotPyStack](https://github.com/PlotPyStack) organization. ## Overview The `guidata` package is a Python library generating Qt graphical user interfaces. It is part of the [PlotPyStack](https://github.com/PlotPyStack) project, aiming at providing a unified framework for creating scientific GUIs with Python and Qt. Simple example of `guidata` datasets embedded in an application window: ![Example](https://raw.githubusercontent.com/PlotPyStack/guidata/master/doc/images/screenshots/editgroupbox.png) See [documentation](https://guidata.readthedocs.io/en/latest/) for more details on the library and [changelog](https://github.com/PlotPyStack/guidata/blob/master/CHANGELOG.md) for recent history of changes. Copyrights and licensing: * Copyright ┬й 2023 [CEA](https://www.cea.fr), [Codra](https://codra.net/), [Pierre Raybaut](https://github.com/PierreRaybaut). * Licensed under the terms of the BSD 3-Clause (see [LICENSE](https://github.com/PlotPyStack/guidata/blob/master/LICENSE)). ## Features Based on the Qt library, `guidata` is a Python library generating graphical user interfaces for easy dataset editing and display. It also provides helpers and application development tools for Qt (PyQt5, PySide2, PyQt6, PySide6). Generate GUIs to edit and display all kind of objects regrouped in datasets: * Integers, floats, strings * Lists (single/multiple choices) * Dictionaries * `ndarrays` (NumPy's N-dimensional arrays) * Etc. Save and load datasets to/from HDF5, JSON or INI files. Application development tools: * Data model (internal data structure, serialization, etc.) * Configuration management * Internationalization (`gettext`) * Deployment tools * HDF5, JSON and INI I/O helpers * Qt helpers * Ready-to-use Qt widgets: Python console, source code editor, array editor, etc. ## Dependencies and installation See [Installation](https://guidata.readthedocs.io/en/latest/installation.html) section in the documentation for more details. ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1722935059.140705 guidata-3.6.2/doc/0000755000175100001770000000000014654363423013324 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1722935059.140705 guidata-3.6.2/doc/_static/0000755000175100001770000000000014654363423014752 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/_static/favicon.ico0000644000175100001770000027613714654363416017115 0ustar00runnerdockerАА (f hО И Ў  С/~  иF00 и%╖V(А N N                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               ╝╝╝╗╗╗t╗╗╗│║║║╘╜╜╜2                                                                                                                                                                                                                                                                                                                                                                           ╣╣╣!╝╝╝г╗╗╗·╗╗╗ ╗╗╗ ╗╗╗ь───                                                                                                                                                                                                                                                                                                                                                                      ┐┐┐ ║║║Н╗╗╗∙╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ║║║Y                                                                                                                                                                                                                                                                                                                                                                     ║║║?╗╗╗┌╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╝╝╝Ф                                                                                                                                                                                                                                                                                                                                                                  ╢╢╢╗╗╗Т╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗└╠╠╠                                                                                                                                                                                                                                                                                                                                                               ╣╣╣!╗╗╗╩╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╝╝╝▀╢╢╢                                                                                                                                                                                                                                                                                                                                                               ╣╣╣M╗╗╗э╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗Є╝╝╝.                                                                                                                                                                                                                                                                                                                                                            ┐┐┐╗╗╗А╗╗╗¤╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗№╝╝╝L                                                                                                                                                                                                                                                                                                                                                            ╢╢╢╗╗╗├╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗i                                                                                                                                                                                                                                                                                                                                                            ╣╣╣,╗╗╗▐╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗Х                                                                                                                                                                                                                                                                                                                                                            ╗╗╗G╗╗╗Ё╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ║║║┼ккк                                                                                                                                                                                                                                                                                                                                                         ╝╝╝f╗╗╗√╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ц║║║                                                                                                                                                                                                                                                                                                                                                      ╠╠╠╗╗╗Щ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗∙╜╜╜6                                                                                                                                                                                                                                                                                                                                                      ┐┐┐ ╗╗╗╡╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ║║║w                                                                                                                                                                                                                                                                                                                                                      ╣╣╣╗╗╗╥╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ║║║┼ААА                                                                                                                                                                                                                                                                                                                                                   ╗╗╗1╗╗╗х╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗√╝╝╝.                                                                                                                                                                                                                                                                                                                                                   ╝╝╝E╗╗╗ё╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╝╝╝г                                                                                                                                                                                                                                                                                                                                                   ║║║h╗╗╗√╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╣╣╣7                                                                                                                                                                                                                                                                                                                                             ккк║║║Н╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗Їккк                                                                                                                                                                                                                                                                                                                                          ┐┐┐ ╝╝╝▓╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗·┐┐┐                                                                                                                                                                                                                                                                                                                                       ┐┐┐╗╗╗╧╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗Зккк                                                                                                                                                                                                                                                                                                                                 ║║║4╗╗╗х╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗╩╜╜╜2                                                                                                                                                                                                                                                                                                                           ║║║\╗╗╗ї╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗■╗╗╗╣║║║J╠╠╠                                                                                                                                                                                                                                                                                                            ╢╢╢║║║С╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╝╝╝ю╗╗╗в╣╣╣M╢╢╢                                                                                                                                                                                                                                                                                                ╜╜╜#╗╗╗═╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗я╝╝╝г╗╗╗R│││                                                                                                                                                                                                                                                                                     ╝╝╝T╗╗╗я╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗Ў╗╗╗░╝╝╝_╝╝╝                                                                                                                                                                                                                                                                     ╜╜╜╗╗╗░╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗√╝╝╝╜╝╝╝c╗╗╗                                                                                                                                                                                                                                                      ╗╗╗8╗╗╗в╗╗╗·╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗Ў║║║з╜╜╜IААА                                                                                                                                                                                                                                    ┐┐┐╗╗╗q║║║╘╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╝╝╝▀║║║w┐┐┐                                                                                                                                                                                                                        ╝╝╝&╗╗╗Щ╗╗╗ў╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ў╗╗╗в╜╜╜2                                                                                                                                                                                                            ╜╜╜2╗╗╗п╗╗╗¤╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗■╗╗╗│╜╜╜6                                                                                                                                                                                                ╛╛╛'╗╗╗в╗╗╗¤╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗─╣╣╣B                                                                                                                                                                                    ┐┐┐╗╗╗Ъ╗╗╗√╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗╣╣╣╣,                                                                                                                                                                        ААА╗╗╗^╗╗╗ъ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗√╗╗╗Щ╢╢╢                                                                                                                                                               ╗╗╗-╗╗╗╦╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╣┬─ ╢╟╦ │╠╥ ▓╤┘ п╪с м▄ч л▌ш крь йсэ зтя мзМ ░М_ ░Оc ░Пg ░Тl ▒Цt │ЬА ┤вМ ╡иЧ ╕пд ║╖┤ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗э║║║k┐┐┐                                                                                                                                                   ккк║║║w╗╗╗°╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ║╝╜ ╖╚╠ ┤╥┘ ░┌ф офё кэ№ ию  зю  жю  жю  ею  дю  гю  гю  вю  бю  змТ м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; к~I лИ\ пТp ▓ЮД ╢лЮ ║╣╖ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗╙╝╝╝A                                                                                                                                             ╝╝╝"║║║┼╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╜╛ ╕╩╬ ┤╓▐ ░уя нэ¤ мя  ля  кя  кя  йя  ию  зю  жю  жю  ею  дю  гю  гю  вю  бю  е╖е м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 еv< йГS нСo │вН ╣╡▒ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗■║║║Ш╢╢╢                                                                                                                                    ╝╝╝L╝╝╝ю╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗┐└ ╖╬╘ ┤▀ъ ▒ю¤ пя  оя  ня  ня  мя  ля  кя  кя  йя  ию  зю  жю  жю  ею  дю  гю  гю  вю  бю  д├╕ м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr8 иВS оЦy ╢лЯ ╗╗║ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗с╝╝╝E                                                                                                                           ккк╝╝╝Е╗╗╗■╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╜╛ ╣╧╒ ╡тэ ▓я■ ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  ля  кя  кя  йя  ию  зю  жю  жю  ею  дю  гю  гю  вю  бю  г╧╠ м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 гxD мТr ╢ог ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗■║║║Нккк                                                                                                                  ┐┐┐ ╗╗╗╡╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗┼╚ ╖┘т ┤э√ ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  ля  кя  кя  йя  ию  зю  жю  жю  ею  дю  гю  гю  вю  бю  в┌▐ м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 д|L нЧ| ╣╡▒ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗═╛╛╛'                                                                                                            ╕╕╕╗╗╗═╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ║╦╨ ╕фЁ ╢Ё  ╡Ё  ┤Ё  ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  ля  кя  кя  йя  ию  зю  жю  жю  ею  дю  гю  гю  вю  бю  ахЄ м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Яq9 кОl ╖░з ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗є╗╗╗S                                                                                                      ╣╣╣(╝╝╝▀╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╝╝ ║═╥ ╣чє ╕Ё  ╖Ё  ╢Ё  ╡Ё  ┤Ё  ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  ля  кя  кя  йя  ию  зю  жю  жю  ею  дю  гю  гю  вю  бю  аэ  м~D л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm4 жЕ] ╡ла ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗■╝╝╝ЕААА                                                                                             ╝╝╝*╗╗╗ш╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╠╤ ╗щї ╣Ё  ╕Ё  ╕Ё  ╖Ё  ╢Ё  ╡Ё  ┤Ё  ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  ля  кя  кя  йя  ию  зю  жю  жю  ею  дю  гю  гю  вю  бю  аэ  лКW л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl4 зИc ╖▒к ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ║║║╢───                                                                                        ╝╝╝*╗╗╗ш╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗┼╟ ╗фю ╗ё  ║ё  ╣Ё  ╕Ё  ╕Ё  ╖Ё  ╢Ё  ╡Ё  ┤Ё  ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  ля  кя  кя  йя  ию  зю  жю  жю  ею  дю  гю  гю  вю  бю  аэ  кХi л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ьn6 йПp ╣╢│ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗╬╕╕╕                                                                                 ╣╣╣(╗╗╗ш╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╝┐└ ╝┌т ╝Ё■ ╝ё  ╗ё  ║ё  ╣Ё  ╕Ё  ╕Ё  ╖Ё  ╢Ё  ╡Ё  ┤Ё  ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  ля  кя  кя  йя  ию  зю  жю  жю  ею  дю  гю  гю  вю  бю  аэ  иЯ| л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 ЮtA ▓еЦ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╝╝╝▀╣╣╣(                                                                           ╝╝╝╗╗╗▐╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╝╔═ ╛ь° ╜ё  ╝ё  ╗ё  ╗ё  ║ё  ╣Ё  ╕Ё  ╕Ё  ╖Ё  ╢Ё  ╡Ё  ┤Ё  ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  ля  кя  кя  йя  ию  зю  жю  жю  ею  дю  гю  гю  вю  бю  аэ  жлР л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 Ъk1 Щk2 зМk ║╕╡ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ы╕╕╕/                                                                     │││ ╗╗╗╩╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╜╜ ╛┘с ╛Ё■ ╛ё  ╜ё  ╝ё  ╗ё  ╗ё  ║ё  ╣Ё  ╕Ё  ╕Ё  ╖Ё  ╢Ё  ╡Ё  ┤Ё  ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  ля  кя  кя  йя  ию  зю  жю  жю  ею  дю  гю  гю  вю  бю  аэ  е╢в л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 Ъk1 Щj1 Щj1 ЭuC │╡▒ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ы╗╗╗-                                                               ААА╗╗╗░╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╝├┼ ┐шє ┐ё  ┐ё  ╛ё  ╜ё  ╝ё  ╗ё  ╗ё  ║ё  ╣Ё  ╕Ё  ╕Ё  ╖Ё  ╢Ё  ╡Ё  ┤Ё  ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  ля  кя  кя  йя  ию  зю  жю  жю  ею  дю  гю  гю  вю  бю  аэ  д┬╢ л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 Ъk1 Щj1 Щj1 Шi0 ХИb й╩╨ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗щ╛╛╛+                                                            ╝╝╝y╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╜╩═ └э· └ё  ┐ё  ┐ё  ╛ё  ╜ё  ╝ё  ╗ё  ╗ё  ║ё  ╣Ё  ╕Ё  ╕Ё  ╖Ё  ╢Ё  ╡Ё  ┤Ё  ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  ля  кя  кя  йя  ию  зю  жю  жю  ею  дю  гю  гю  вю  бю  аэ  в╠╚ л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 Ъk1 Щj1 Щj1 Шi0 ЦuC Ащ■ Я╤█ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ц╗╗╗                                                      ╗╗╗<╗╗╗№╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╛╨╒ ┬ё■ ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜ё  ╝ё  ╗ё  ╗ё  ║ё  ╣Ё  ╕Ё  ╕Ё  ╖Ё  ╢Ё  ╡Ё  ┤Ё  ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  ля  кя  кя  йя  ию  зю  жю  жю  ею  дю  гю  гю  вю  бю  аэ  в╪█ л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 Ъk1 Щj1 Щj1 Шi0 Шj1 Ац° щ  Ч╓у ║╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗╥╢╢╢                                                ╝╝╝╗╗╗щ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ┐╫▐ ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜ё  ╝ё  ╗ё  ╗ё  ║ё  ╣Ё  ╕Ё  ╕Ё  ╖Ё  ╢Ё  ╡Ё  ┤Ё  ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  ля  кя  кя  йя  ию  зю  жю  жю  ею  дю  гю  гю  вю  бю  аэ  афю л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 Ъk1 Щj1 Щj1 Шi0 Шi0 Г┌х щ  ~щ  Щ╒с ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗╣┐┐┐                                             ╜╜╜▒╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ └╫▐ ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜ё  ╝ё  ╗ё  ╗ё  ║ё  ╣Ё  ╕Ё  ╕Ё  ╖Ё  ╢Ё  ╡Ё  ┤Ё  ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  ля  кя  кя  йя  ию  зю  жю  жю  ею  дю  гю  гю  вю  бю  аэ  Яь¤ л~B лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 Ъk1 Щj1 Щj1 Шi0 Шi0 Д╙┌ щ  ~щ  }щ  Ы╙▐ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗П                                          ╗╗╗O╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ┐╒█ ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜ё  ╝ё  ╝ё  ╗ё  ║ё  ╣Ё  ╕Ё  ╕Ё  ╖Ё  ╢Ё  ╡Ё  ┤Ё  ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  ля  кя  кя  йя  ию  зю  жю  жю  ею  дю  гю  гю  вю  бю  аэ  Яэ  кИT лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 Ъk1 Щj1 Щj1 Шi0 Шi0 Ж╦═ щ  ~щ  }щ  |щ  Я╧┘ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗■╝╝╝L                                    │││ ╜╜╜ё╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ └╥╪ ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜ё  ╝ё  ╗ё  ╗ё  ║ё  ╣Ё  ╕Ё  ╕Ё  ╖Ё  ╢Ё  ╡Ё  ┤Ё  ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  ля  кя  кя  йя  ию  зю  жю  жю  ею  дю  гю  гю  вю  бю  аэ  Яэ  иТe лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 Ъk1 Щj1 Щj1 Шi0 Шi0 З╟╞ щ  ~щ  }щ  |щ  ~ш■ м┼╩ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ы╜╜╜                                 └└└е╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╛╩═ ┼ё■ ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜ё  ╝ё  ╗ё  ╗ё  ║ё  ╣Ё  ╕Ё  ╕Ё  ╖Ё  ╢Ё  ╡Ё  ┤Ё  ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  ля  кя  кя  йя  ию  зю  жю  жю  ею  дю  гю  гю  вю  бю  аэ  Яэ  иЭy лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 Ъk1 Щj1 Щj1 Шi0 Шi0 З─├ щ  ~щ  }щ  |щ  |щ  Вуў ╢╛└ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗╝                              ┼┼┼#╝╝╝■╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╝└┴ ┼э∙ ╞Є  ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜ё  ╝ё  ╗ё  ╗ё  ║ё  ╣Ё  ╕Ё  ╕Ё  ╖Ё  ╢Ё  ╡Ё  ┤Ё  ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  ля  кя  кя  йя  ию  зю  жю  жю  ею  дю  гю  гю  вю  бю  аэ  Яэ  звА лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 Ъk1 Щj1 Щj1 Шi0 Шi0 З├└ щ  ~щ  }щ  |щ  |щ  {щ  Н█ы ║╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╝╝╝[                           ───╥╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╝╝ ─хя ╞Є  ╞Є  ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜ё  ╝ё  ╗ё  ╗ё  ║ё  ╣Ё  ╕Ё  ╕Ё  ╖Ё  ╢Ё  ╡Ё  ┤Ё  ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  ля  кя  кя  йя  ию  зю  жю  жю  ею  дю  гю  гю  вю  бю  аэ  Яэ  звА лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 Ъk1 Щj1 Щj1 Шi0 Шi0 З├┴ щ  ~щ  }щ  |щ  |щ  {щ  zщ  б═╓ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ч╗╗╗                     ╦╦╦;╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ┴╓▌ ╟Є  ╞Є  ╞Є  ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜ё  ╝ё  ╗ё  ╗ё  ║ё  ╣Ё  ╕Ё  ╕Ё  ╖Ё  ╢Ё  ╡Ё  ┤Ё  ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  ля  кя  кя  йя  ию  зю  жю  жю  ею  дю  гю  гю  вю  бю  аэ  Яэ  звА лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 Ъk1 Щj1 Щj1 Шi0 Шi0 Ж╔╩ щ  ~щ  }щ  |щ  |щ  {щ  zщ  ц· ╖╛└ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╝╝╝Р                     ┼┼┼╓╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╜┬├ ╟Ё№ ╟Є  ╞Є  ╞Є  ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜ё  ╝ё  ╗ё  ╗ё  ║ё  ╣Ё  ╕Ё  ╕Ё  ╖Ё  ╢Ё  ╡Ё  ┤Ё  ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  ля  кя  кя  йя  ию  зю  жю  жю  ею  дю  гю  гю  вю  бю  аэ  Яэ  звА лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 Ъk1 Щj1 Щj1 Шi0 Шi0 Е╧╘ щ  ~щ  }щ  |щ  |щ  {щ  zщ  yщ  Ц╘р ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗·╜╜╜               ╤╤╤2╛╛╛ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ─▀ш ╚Є  ╟Є  ╞Є  ╞Є  ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜щЄ ╜╓╧ ╝╓╧ ╝╓╧ ║╓╧ ║╒╧ ╣╒╬ ╣╒╬ ╕╒╬ ╖╘╬ ╖╘╬ ╢╘╬ ╡╘╬ ┤╘╬ ┤╘╬ │╙╬ │╥╬ ▓╥═ ▒╥═ ▒╥═ п╥═ п╥═ о╥═ н╥═ н╤═ н╤═ л╤═ к╤═ й╤═ й╤╠ й╤╠ и╤╠ з╤╠ ж╨╠ ж╨╠ е╨╠ д╨╠ г╧╠ г╧╠ иЧn лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 Ъk1 Щj1 Щj1 Шi0 Шi0 Д╘▌ щ  ~щ  }щ  |щ  |щ  {щ  zщ  yщ  {ш¤ │└┬ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ║║║С               ╚╚╚╔╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ┐╚╦ ╔Є  ╚Є  ╟Є  ╞Є  ╞Є  ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜╒╬ ┐МK ╛ЛK ╛ЛJ ╜КJ ╜КJ ╝КI ╝ЙI ╗ЙI ╗ИH ║ИH ║ЗH ╣ЗG ╣ЖG ╕ЖF ╕ЕF ╖ЕF ╖ЕE ╢ДE ╢ДE ╡ГD ╡ГD ┤ВD ┤ВC │БC │БC ▓АB ▓АB ▒АB ▒A ░A п~@ п~@ о}@ о}? н|? н|? м{> м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 Ъk1 Щj1 Щj1 Шi0 Шi0 Г┌ч щ  ~щ  }щ  |щ  |щ  {щ  zщ  yщ  yщ  Ч╘р ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ї╢╢╢         ╓╓╓┴┴┴■╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╟цЁ ╔Є  ╚Є  ╟Є  ╞Є  ╞Є  ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜╒╬ ┐МK ╛ЛK ╛ЛJ ╜КJ ╜КJ ╝КI ╝ЙI ╗ЙI ╗ИH ║ИH ║ЗH ╣ЗG ╣ЖG ╕ЖF ╕ЕF ╖ЕF ╖ЕE ╢ДE ╢ДE ╡ГD ╡ГD ┤ВD ┤ВC │БC │БC ▓АB ▓АB ▒АB ▒A ░A п~@ п~@ о}@ о}? н|? н|? м{> м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 Ъk1 Щj1 Щj1 Шi0 Шi0 ВрЁ щ  ~щ  }щ  |щ  |щ  {щ  zщ  yщ  yщ  }ц· ╣╜╛ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╝╝╝Б         ╧╧╧Е╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╛╟╩ ╩Є  ╔Є  ╚Є  ╟Є  ╞Є  ╞Є  ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜╒╬ ┐МK ╛ЛK ╛ЛJ ╜КJ ╜КJ ╝КI ╝ЙI ╗ЙI ╗ИH ║ИH ║ЗH ╣ЗG ╣ЖG ╕ЖF ╕ЕF ╖ЕF ╖ЕE ╢ДE ╢ДE ╡ГD ╡ГD ┤ВD ┤ВC │БC │БC ▓АB ▓АB ▒АB ▒A ░A п~@ п~@ о}@ о}? н|? н|? м{> м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 Ъk1 Щj1 Щj1 Шi0 Шi0 Ач∙ щ  ~щ  }щ  |щ  |щ  {щ  zщ  yщ  yщ  xщ  в╠╘ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗▐         ╩╩╩с╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ┼▐ц ╩Є  ╔Є  ╚Є  ╟Є  ╞Є  ╞Є  ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜╒╬ ┐МK ╛ЛK ╛ЛJ ╜КJ ╜КJ ╝КI ╝ЙI ╗ЙI ╗ИH ║ИH ║ЗH ╣ЗG ╣ЖG ╕ЖF ╕ЕF ╖ЕF ╖ЕE ╢ДE ╢ДE ╡ГD ╡ГD ┤ВD ┤ВC │БC │БC ▓АB ▓АB ▒АB ▒A ░A п~@ п~@ о}@ о}? н|? н|? м{> м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 Ъk1 Щj1 Щj1 Шi0 Шk3 Аъ  щ  ~щ  }щ  |щ  |щ  {щ  zщ  yщ  yщ  xщ  Й█ь ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╣╣╣7   ╒╒╒$├├├ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╝╜╛ ╔ё№ ╩Є  ╔Є  ╚Є  ╟Є  ╞Є  ╞Є  ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜╒╬ ┐МK ╛ЛK ╛ЛJ ╜КJ ╜КJ ╝КI ╝ЙI ╗ЙI ╗ИH ║ИH ║ЗH ╣ЗG ╣ЖG ╕ЖF ╕ЕF ╖ЕF ╖ЕE ╢ДE ╢ДE ╡ГD ╡ГD ┤ВD ┤ВC │БC │БC ▓АB ▓АB ▒АB ▒A ░A п~@ п~@ о}@ о}? н|? н|? м{> м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 Ъk1 Щj1 Щj1 Шi0 Цs@ Аъ  щ  ~щ  }щ  |щ  |щ  {щ  zщ  yщ  yщ  xщ  xч■ ╢╛└ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗О   ╧╧╧j╜╜╜ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ └═╤ ╩є  ╩Є  ╔Є  ╚Є  ╟Є  ╞Є  ╞Є  ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜╒╬ ┐МK ╛ЛK ╛ЛJ ╜КJ ╜КJ ╝КI ╝ЙI ╗ЙI ╗ИH ║ИH ║ЗH ╣ЗG ╣ЖG ╕ЖF ╕ЕF ╖ЕF ╖ЕE ╢ДE ╢ДE ╡ГD ╡ГD ┤ВD ┤ВC │БC │БC ▓АB ▓АB ▒АB ▒A ░A п~@ п~@ о}@ о}? н|? н|? м{> м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 Ъk1 Щj1 Щj1 Шi0 ФU Аъ  щ  ~щ  }щ  |щ  |щ  {щ  zщ  yщ  yщ  xщ  wш  д╩╥ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗т   ═══╢╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ─▌ф ╩є  ╩Є  ╔Є  ╚Є  ╟Є  ╞Є  ╞Є  ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜╒╬ ┐МK ╛ЛK ╛ЛJ ╜КJ ╜КJ ╝КI ╝ЙI ╗ЙI ╗ИH ║ИH ║ЗH ╣ЗG ╣ЖG ╕ЖF ╕ЕF ╖ЕF ╖ЕE ╢ДE ╢ДE ╡ГD ╡ГD ┤ВD ┤ВC │БC │БC ▓АB ▓АB ▒АB ▒A ░A п~@ п~@ о}@ о}? н|? н|? м{> м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 Ъk1 Щj1 Щj1 Шi0 СНj Аъ  щ  ~щ  }щ  |щ  |щ  {щ  zщ  yщ  yщ  xщ  wш  Т╓ф ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╣╣╣╩╩╩ч╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╔ьЎ ╩є  ╩Є  ╔Є  ╚Є  ╟Є  ╞Є  ╞Є  ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜╒╬ ┐МK ╛ЛK ╛ЛJ ╜КJ ╜КJ ╝КI ╝ЙI ╗ЙI ╗ИH ║ИH ║ЗH ╣ЗG ╣ЖG ╕ЖF ╕ЕF ╖ЕF ╖ЕE ╢ДE ╢ДE ╡ГD ╡ГD ┤ВD ┤ВC │БC │БC ▓АB ▓АB ▒АB ▒A ░A п~@ п~@ о}@ о}? н|? н|? м{> м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 Ъk1 Щj1 Щj1 Шi0 ПЫА Аъ  щ  ~щ  }щ  |щ  |щ  {щ  zщ  yщ  yщ  xщ  wш  Г▀Є ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗R╞╞╞■╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╜┐└ ╦є  ╩є  ╩Є  ╔Є  ╚Є  ╟Є  ╞Є  ╞Є  ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜╒╬ ┐МK ╛ЛK ╛ЛJ ╜КJ ╜КJ ╝КI ╝ЙI ╗ЙI ╗ИH ║ИH ║ЗH ╣ЗG ╣ЖG ╕ЖF ╕ЕF ╖ЕF ╖ЕE ╢ДE ╢ДE ╡ГD ╡ГD ┤ВD ┤ВC │БC │БC ▓АB ▓АB ▒АB ▒A ░A п~@ п~@ о}@ о}? н|? н|? м{> м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 Ъk1 Щj1 Щj1 Шi0 МиХ Аъ  щ  ~щ  }щ  |щ  |щ  {щ  zщ  yщ  yщ  xщ  wш  xч■ ╕╝╜ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗З┴┴┴ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ └╔╠ ╦є  ╩є  ╩Є  ╔Є  ╚Є  ╟Є  ╞Є  ╞Є  ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜╒╬ ┐МK ╛ЛK ╛ЛJ ╜КJ ╜КJ ╝КI ╝ЙI ╗ЙI ╗ИH ║ИH ║ЗH ╣ЗG ╣ЖG ╕ЖF ╖С\ ┤кЗ ┤┤Ч │мК ╡Чf ╡ГD ╡ГD ┤ВD ┤ВC │БC │БC ▓АB ▓АB ▒АB ▒A ░A п~@ п~@ о}@ о}? н|? н|? м{> м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 Ъk1 Щj1 Щj1 Шi0 К╡й Аъ  щ  ~щ  }щ  |щ  |щ  {щ  zщ  yщ  yщ  xщ  wш  vш  п├╟ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗╣┐┐┐ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ┴╨╘ ╦є  ╩є  ╩Є  ╔Є  ╚Є  ╟Є  ╞Є  ╞Є  ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜╒╬ ┐МK ╛ЛK ╛ЛJ ╜КJ ╜КJ ╝КI ╝ЙI ╗ЙI ╗ИH ║ИH ║ЗH ╣ЗG ╕Щh ┤╙═ ▒Ё  ▒я  ░я  пя  оя  о▄▐ │вx ┤ВD ┤ВC │БC │БC ▓АB ▓АB ▒АB ▒A ░A п~@ п~@ о}@ о}? н|? н|? м{> м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 Ъk1 Щj1 Щj1 Шi0 И┬╛ Аъ  щ  ~щ  }щ  |щ  |щ  {щ  zщ  yщ  yщ  xщ  wш  vш  ж╔╨ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗╥╛╛╛ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ─╓▄ ╦є  ╩є  ╩Є  ╔Є  ╚Є  ╟Є  ╞Є  ╞Є  ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜╒╬ ┐МK ╛ЛK ╛ЛJ ╜КJ ╜КJ ╝КI ╝ЙI ╗ЙI ╗ИH ║ИH ║ЗH ╕йВ ┤э∙ ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня■ ░╖а ┤ВC │БC │БC ▓АB ▓АB ▒АB ▒A ░A п~@ п~@ о}@ о}? н|? н|? м{> м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 Ъk1 Щj1 Щj1 Шi0 Е╬╙ Аъ  щ  ~щ  }щ  |щ  |щ  {щ  zщ  yщ  yщ  xщ  wш  vш  Ю╬╪ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ч╝╝╝ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ─┘▀ ╦є  ╩є  ╩Є  ╔Є  ╚Є  ╟Є  ╞Є  ╞Є  ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜╒╬ ┐МK ╛ЛK ╛ЛJ ╜КJ ╜КJ ╝КI ╝ЙI ╗ЙI ╗ИH ║ИH ╣Эo ┤э· │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  ▒мЛ │БC │БC ▓АB ▓АB ▒АB ▒A ░A п~@ п~@ о}@ о}? н|? н|? м{> м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 Ъk1 Щj1 Щj1 Шi0 В▄щ Аъ  щ  ~щ  }щ  |щ  |щ  {щ  zщ  yщ  yщ  xщ  wш  vш  Щ╥▌ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗·╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ┼█т ╦є  ╩є  ╩Є  ╔Є  ╚Є  ╟Є  ╞Є  ╞Є  ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜╒╬ ┐МK ╛ЛK ╛ЛJ ╜КJ ╜КJ ╝КI ╝ЙI ╗ЙI ╗ИH ║ИI ╡┘╪ ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  лшє ▓ЙP │БC ▓АB ▓АB ▒АB ▒A ░A п~@ п~@ о}@ о}? н|? н|? м{> м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 Ъk1 Щj1 Щj1 Шj2 Аш№ Аъ  щ  ~щ  }щ  |щ  |щ  {щ  zщ  yщ  yщ  xщ  wш  vш  Ф╘с ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ─┘р ╦є  ╩є  ╩Є  ╔Є  ╚Є  ╟Є  ╞Є  ╞Є  ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╛╓╧ ┐МK ╛ЛK ╛ЛJ ╜КJ ╜КJ ╝КI ╝ЙI ╗ЙI ╗ИH ╣аr ┤Ё  ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  ля  ппУ │БC ▓АB ▓АB ▒АB ▒A ░A п~@ п~@ о}@ о}? н|? н|? м{> м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 Ъk1 Щj1 Щj1 ЦuC Аъ  Аъ  щ  ~щ  }щ  |щ  |щ  {щ  zщ  yщ  yщ  xщ  wш  vш  Х╘с ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ├╫▌ ╦є  ╩є  ╩Є  ╔Є  ╚Є  ╟Є  ╞Є  ╞Є  ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜┘╒ ┐МK ╛ЛK ╛ЛJ ╜КJ ╜КJ ╝КI ╝ЙI ╗ЙI ╗ИH ╖╝в ┤Ё  ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  ля  н═┼ │БC ▓АB ▓АB ▒АB ▒A ░A п~@ п~@ о}@ о}? н|? н|? м{> м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 Ъk1 Щj1 Щj1 Ф|O Аъ  Аъ  щ  ~щ  }щ  |щ  |щ  {щ  zщ  yщ  yщ  xщ  wш  vш  Ш╥▐ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ├╒┌ ╦є  ╩є  ╩Є  ╔Є  ╚Є  ╟Є  ╞Є  ╞Є  ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╛▐▄ ┐МK ╛ЛK ╛ЛJ ╜КJ ╜КJ ╝КI ╝ЙI ╗ЙI ╗ИH ╖╟╖ ┤Ё  ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  ля  м┌▄ │БC ▓АB ▓АB ▒АB ▒A ░A п~@ п~@ о}@ о}? н|? н|? м{> м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 Ъk1 Щj1 Щj1 УЖ^ Аъ  Аъ  щ  ~щ  }щ  |щ  |щ  {щ  zщ  yщ  yщ  xщ  wш  vш  Ъ╧┌ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗∙╝╝╝ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ┬╥╫ ╦є  ╩є  ╩Є  ╔Є  ╚Є  ╟Є  ╞Є  ╞Є  ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜су ┐МK ╛ЛK ╛ЛJ ╜КJ ╜КJ ╝КI ╝ЙI ╗ЙI ╗ИH ╖┼│ ┤Ё  ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  ля  м╫╫ │БC ▓АB ▓АB ▒АB ▒A ░A п~@ п~@ о}@ о}? н|? н|? м{> м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 Ъk1 Щj1 Щj1 РЩ~ Аъ  Аъ  щ  ~щ  }щ  |щ  |щ  {щ  zщ  yщ  yщ  xщ  wш  vш  Ю╬╪ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╝╝╝ю╜╜╜ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ └═╤ ╦є  ╩є  ╩Є  ╔Є  ╚Є  ╟Є  ╞Є  ╞Є  ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜шя ┐МK ╛ЛK ╛ЛJ ╜КJ ╜КJ ╝КI ╝ЙI ╗ЙI ╗ИH ╕╢Щ ┤Ё  ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  ля  н╚╝ │БC ▓АB ▓АB ▒АB ▒A ░A п~@ п~@ о}@ о}? н|? н|? м{> м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 Ъk1 Щj1 Щj1 Л▓е Аъ  Аъ  щ  ~щ  }щ  |щ  |щ  {щ  zщ  yщ  yщ  xщ  wш  vш  в╦╙ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ║║║у┐┐┐ў╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╛┼╟ ╦є  ╩є  ╩Є  ╔Є  ╚Є  ╟Є  ╞Є  ╞Є  ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜ё  ┐ТV ╛ЛK ╛ЛJ ╜КJ ╜КJ ╝КI ╝ЙI ╗ЙI ╗ИH ╣Шc ┤Ё■ ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  ля  ░жД │БC ▓АB ▓АB ▒АB ▒A ░A п~@ п~@ о}@ о}? н|? н|? м{> м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 Ъk1 Щj1 Щj1 Е╘▌ Аъ  Аъ  щ  ~щ  }щ  |щ  |щ  {щ  zщ  yщ  yщ  xщ  wш  vш  м┼╩ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗╩└└└▀╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╝╜╜ ╦Є■ ╩є  ╩Є  ╔Є  ╚Є  ╟Є  ╞Є  ╞Є  ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜ё  ╛вs ╛ЛK ╛ЛJ ╜КJ ╜КJ ╝КI ╝ЙI ╗ЙI ╗ИH ║ИH ╢╬├ ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  м▀ф ▓ГG │БC ▓АB ▓АB ▒АB ▒A ░A п~@ п~@ о}@ о}? н|? н|? м{> м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 Ъk1 Щj1 ХR Бъ  Аъ  Аъ  щ  ~щ  }щ  |щ  |щ  {щ  zщ  yщ  yщ  xщ  wш  vш  ╢╛└ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗и┴┴┴▓╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╔ыї ╩є  ╩Є  ╔Є  ╚Є  ╟Є  ╞Є  ╞Є  ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜ё  ╛│П ╛ЛK ╛ЛJ ╜КJ ╜КJ ╝КI ╝ЙI ╗ЙI ╗ИH ║ИH ╣С[ ╡ць │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мь· ▓Ъm │БC │БC ▓АB ▓АB ▒АB ▒A ░A п~@ п~@ о}@ о}? н|? н|? м{> м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 Ъk1 Щj1 Н░б Бъ  Аъ  Аъ  щ  ~щ  }щ  |щ  |щ  {щ  zщ  yщ  yщ  xщ  wш  {х√ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ║║║Ж╛╛╛q╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╞сщ ╩є  ╩Є  ╔Є  ╚Є  ╟Є  ╞Є  ╞Є  ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜ё  ╜╦╗ ╛ЛK ╛ЛJ ╜КJ ╜КJ ╝КI ╝ЙI ╗ЙI ╗ИH ║ИH ║ЗH ╣Щe ┤тч ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ощї ▒бz ┤ВC │БC │БC ▓АB ▓АB ▒АB ▒A ░A п~@ п~@ о}@ о}? н|? н|? м{> м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 Ъk1 Чt@ ГуЇ Бъ  Аъ  Аъ  щ  ~щ  }щ  |щ  |щ  {щ  zщ  yщ  yщ  xщ  wш  Ж▐Ё ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗e║║║;╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ┴╤╓ ╩є  ╩Є  ╔Є  ╚Є  ╟Є  ╞Є  ╞Є  ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜ё  ╜щЁ ╛НO ╛ЛJ ╜КJ ╜КJ ╝КI ╝ЙI ╗ЙI ╗ИH ║ИH ║ЗH ╣ЗG ╣ЛP ┤╝е ▒хы ▒я  ░я  пя  ошє ░─╢ ┤ПY ┤ВD ┤ВC │БC │БC ▓АB ▓АB ▒АB ▒A ░A п~@ п~@ о}@ о}? н|? н|? м{> м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 Ъk1 Н░б Въ  Бъ  Аъ  Аъ  щ  ~щ  }щ  |щ  |щ  {щ  zщ  yщ  yщ  xщ  wш  Ц╙▀ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╝╝╝A┐┐┐╗╗╗√╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╝┴┬ ╩є  ╩Є  ╔Є  ╚Є  ╟Є  ╞Є  ╞Є  ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜ё  ╝ё  ╜оЙ ╛ЛJ ╜КJ ╜КJ ╝КI ╝ЙI ╗ЙI ╗ИH ║ИH ║ЗH ╣ЗG ╣ЖG ╕ЖF ╕ЕG ╢Ф_ ╡Ьo ╡Хc ╢ЗI ╡ГD ╡ГD ┤ВD ┤ВC │БC │БC ▓АB ▓АB ▒АB ▒A ░A п~@ п~@ о}@ о}? н|? н|? м{> м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Ъk2 ЦВW Гш№ Въ  Бъ  Аъ  Аъ  щ  ~щ  }щ  |щ  |щ  {щ  zщ  yщ  yщ  xщ  wш  и╚╬ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗№╗╗╗   ╗╗╗╟╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╚ъЇ ╩Є  ╔Є  ╚Є  ╟Є  ╞Є  ╞Є  ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜ё  ╝ё  ╝рт ╛НM ╜КJ ╜КJ ╝КI ╝ЙI ╗ЙI ╗ИH ║ИH ║ЗH ╣ЗG ╣ЖG ╕ЖF ╕ЕF ╖ЕF ╖ЕE ╢ДE ╢ДE ╡ГD ╡ГD ┤ВD ┤ВC │БC │БC ▓АB ▓АB ▒АB ▒A ░A п~@ п~@ о}@ о}? н|? н|? м{> м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыk2 Щp; З╒▄ Гъ  Въ  Бъ  Аъ  Аъ  щ  ~щ  }щ  |щ  |щ  {щ  zщ  yщ  yщ  xщ  xч■ ╖╜╛ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╝╝╝╠      ║║║В╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ┬╫▌ ╩Є  ╔Є  ╚Є  ╟Є  ╞Є  ╞Є  ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜ё  ╝ё  ╗ё  ╝╣Ь ╜КJ ╜КJ ╝КI ╝ЙI ╗ЙI ╗ИH ║ИH ║ЗH ╣ЗG ╣ЖG ╕ЖF ╕ЕF ╖ЕF ╖ЕE ╢ДE ╢ДE ╡ГD ╡ГD ┤ВD ┤ВC │БC │БC ▓АB ▓АB ▒АB ▒A ░A п~@ п~@ о}@ о}? н|? н|? м{> м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыl2 Ыl4 М└╗ Гъ  Гъ  Въ  Бъ  Аъ  Аъ  щ  ~щ  }щ  |щ  |щ  {щ  zщ  yщ  yщ  xщ  К▄э ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╝╝╝Р      ╝╝╝D╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╜┴┬ ╔Ё¤ ╔Є  ╚Є  ╟Є  ╞Є  ╞Є  ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜ё  ╝ё  ╗ё  ╗ю√ ╜гu ╜КJ ╝КI ╝ЙI ╗ЙI ╗ИH ║ИH ║ЗH ╣ЗG ╣ЖG ╕ЖF ╕ЕF ╖ЕF ╖ЕE ╢ДE ╢ДE ╡ГD ╡ГD ┤ВD ┤ВC │БC │БC ▓АB ▓АB ▒АB ▒A ░A п~@ п~@ о}@ о}? н|? н|? м{> м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьl3 Ыm3 О╖л Дъ  Гъ  Гъ  Въ  Бъ  Аъ  Аъ  щ  ~щ  }щ  |щ  |щ  {щ  zщ  yщ  yщ  xщ  д╩╥ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗R      ╞╞╞ ╗╗╗Ё╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ─▐ц ╔Є  ╚Є  ╟Є  ╞Є  ╞Є  ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜ё  ╝ё  ╗ё  ╗ё  ║ыЇ ╝Яp ╝КI ╝ЙI ╗ЙI ╗ИH ║ИH ║ЗH ╣ЗG ╣ЖG ╕ЖF ╕ЕF ╖ЕF ╖ЕE ╢ДE ╢ДE ╡ГD ╡ГD ┤ВD ┤ВC │БC │БC ▓АB ▓АB ▒АB ▒A ░A п~@ п~@ о}@ о}? н|? н|? м{> м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 Ьm3 Ьn6 О╗│ Еъ  Дъ  Гъ  Гъ  Въ  Бъ  Аъ  Аъ  щ  ~щ  }щ  |щ  |щ  {щ  zщ  yщ  yщ  }ц· ╣╝╜ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗∙╕╕╕         ║║║Ш╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╛╞╔ ╔Є  ╚Є  ╟Є  ╞Є  ╞Є  ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜ё  ╝ё  ╗ё  ╗ё  ║ё  ╣ь° ║пЛ ╝ЙI ╗ЙI ╗ИH ║ИH ║ЗH ╣ЗG ╣ЖG ╕ЖF ╕ЕF ╖ЕF ╖ЕE ╢ДE ╢ДE ╡ГD ╡ГD ┤ВD ┤ВC │БC │БC ▓АB ▓АB ▒АB ▒A ░A п~@ п~@ о}@ о}? н|? н|? м{> м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эn4 Эm3 ЪxE Н╩╦ Жъ  Еъ  Дъ  Гъ  Гъ  Въ  Бъ  Аъ  Аъ  щ  ~щ  }щ  |щ  |щ  {щ  zщ  yщ  yщ  Х╒с ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╝╝╝о            ╗╗╗8╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ─▀ч ╚Є  ╟Є  ╞Є  ╞Є  ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜ё  ╝ё  ╗ё  ╗ё  ║ё  ╣Ё  ╕Ё  ║╨┼ ║Ф] ╗ИH ║ИH ║ЗH ╣ЗG ╣ЖG ╕ЖF ╕ЕF ╖ЕF ╖ЕE ╢ДE ╢ДE ╡ГD ╡ГD ┤ВD ┤ВC │БC │БC ▓АB ▓АB ▒АB ▒A ░A п~@ п~@ о}@ о}? н|? н|? м{> м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 Юn4 Эo5 ЦЪ| Й▐ь Зъ  Жъ  Еъ  Дъ  Гъ  Гъ  Въ  Бъ  Аъ  Аъ  щ  ~щ  }щ  |щ  |щ  {щ  zщ  yщ  |ш¤ │└┬ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ║║║Q               ╗╗╗╓╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╝└┴ ╟я√ ╟Є  ╞Є  ╞Є  ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜ё  ╝ё  ╗ё  ╗ё  ║ё  ╣Ё  ╕Ё  ╕Ё  ╖ь∙ ╕─▒ ║Х_ ║ЗH ╣ЗG ╣ЖG ╕ЖF ╕ЕF ╖ЕF ╖ЕE ╢ДE ╢ДE ╡ГD ╡ГD ┤ВD ┤ВC │БC │БC ▓АB ▓АB ▒АB ▒A ░A п~@ п~@ о}@ о}? н|? н|? м{> м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яp5 Яo5 Юo4 ЪДX О═╬ Иы  Зъ  Зъ  Жъ  Еъ  Дъ  Гъ  Гъ  Въ  Бъ  Аъ  Аъ  щ  ~щ  }щ  |щ  |щ  {щ  zщ  yщ  Ъ╥▌ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╝╝╝ю╢╢╢               ╗╗╗p╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ┴╙┘ ╟Є  ╞Є  ╞Є  ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜ё  ╝ё  ╗ё  ╗ё  ║ё  ╣Ё  ╕Ё  ╕Ё  ╖Ё  ╢Ё  ╡я■ ╢╙╠ ╖мЗ ╣МR ╕ЖF ╕ЕF ╖ЕF ╖ЕE ╢ДE ╢ДE ╡ГD ╡ГD ┤ВD ┤ВC │БC │БC ▓АB ▓АB ▒АB ▒A ░A п~@ п~@ о}@ о}? н|? н|? м{> м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бq6 аp6 аp5 Яq6 ЪПi С┬╗ Кщ№ Йы  Иы  Зъ  Зъ  Жъ  Еъ  Дъ  Гъ  Гъ  Въ  Бъ  Аъ  Аъ  щ  ~щ  }щ  |щ  |щ  {щ  zщ  Ах∙ ╕╜╛ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ║║║С                  ┐┐┐╗╗╗т╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╝╝ ─уь ╞Є  ╞Є  ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜ё  ╝ё  ╗ё  ╗ё  ║ё  ╣Ё  ╕Ё  ╕Ё  ╖Ё  ╢Ё  ╡Ё  ┤Ё  ┤Ё  │э√ ┤╙═ ┤╗в ╢вy ╢ЛP ╢ДE ╢ДE ╡ГD ╡ГD ┤ВD ┤ВC │БC │БC ▓АB ▓АB ▒АB ▒A ░A п~@ п~@ о}@ о}? н|? н|? м{> м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 гs8 гs8 вr7 вr7 бq7 бs8 ЭЗ\ ШйТ П╪р Лы  Кы  Кы  Йы  Иы  Зъ  Зъ  Жъ  Еъ  Дъ  Гъ  Гъ  Въ  Бъ  Аъ  Аъ  щ  ~щ  }щ  |щ  |щ  {щ  {щ  ж╔╨ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ў┐┐┐                     ╝╝╝f╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╝┐└ ─ъї ╞Є  ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜ё  ╝ё  ╗ё  ╗ё  ║ё  ╣Ё  ╕Ё  ╕Ё  ╖Ё  ╢Ё  ╡Ё  ┤Ё  ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░ю■ ░▌р ▒┼╢ ▓░Т ▓аu │ОY ┤ВC │БC │БC ▓АB ▓АB ▒АB ▒A ░A п~@ п~@ о}@ о}? н|? н|? м{> м{> л{> лz= кz= кy= йy< йx< иx; иw; зw; зv: жv: жu: еu9 еu9 дt9 дt8 вxA ЯЙ] ЭЪy ЩлЧ Ф╟─ Пфє Оы  Ны  Мы  Лы  Кы  Кы  Йы  Иы  Зъ  Зъ  Жъ  Еъ  Дъ  Гъ  Гъ  Въ  Бъ  Аъ  Аъ  щ  ~щ  }щ  |щ  |щ  {щ  Ч╘р ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗О                        ┐┐┐╗╗╗┌╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╜─╞ ╞Ё№ ┼Є  ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜ё  ╝ё  ╝ё  ╗ё  ║ё  ╣Ё  ╕Ё  ╕Ё  ╖Ё  ╢Ё  ╡Ё  ┤Ё  ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  лъЎ м┌▄ м╬╟ н┬│ н╖Я нкЙ обz оЩn нС` оЙS нБG о}? н|? н|? м{> м{> л{> лz= кz= кy= йy< и}E зДO жЙZ дРe гЧq бвГ ЯмЦ Ь╕й Ы├╝ Ш╧╨ Хры Ть  Сь  Сь  Ры  Пы  Оы  Оы  Ны  Мы  Лы  Кы  Кы  Йы  Иы  Зъ  Зъ  Жъ  Еъ  Дъ  Гъ  Гъ  Въ  Бъ  Аъ  Аъ  щ  ~щ  }щ  |щ  |щ  Йрё ║╝╜ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗Ї╝╝╝                           ╗╗╗O╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╛╚╦ ─я√ ─Є  ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜ё  ╝ё  ╗ё  ╗ё  ║ё  ╣Ё  ╕Ё  ╕Ё  ╖Ё  ╢Ё  ╡Ё  ┤Ё  ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  ля  кя  кя  йя  ию  зю  жю  жю  ею  дю  гю  гю  вю  бщЎ ахЄ ЯхЄ ЯхЄ ЮхЄ ЮхЄ Ьэ  Ьэ  Ыэ  Ъэ  Щэ  Шэ  Шь  Чь  Ць  Хь  Хь  Фь  Уь  Ть  Сь  Сь  Ры  Пы  Оы  Оы  Ны  Мы  Лы  Кы  Кы  Йы  Иы  Зъ  Зъ  Жъ  Еъ  Дъ  Гъ  Гъ  Въ  Бъ  Аъ  Аъ  щ  ~щ  }щ  |щ  ЗтЇ ╢┐┴ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗                                 ║║║Ь╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╜┼╚ ├э∙ ├Є  ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜ё  ╝ё  ╗ё  ╗ё  ║ё  ╣Ё  ╕Ё  ╕Ё  ╖Ё  ╢Ё  ╡Ё  ┤Ё  ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  ля  кя  кя  йя  ию  зю  жю  жю  ею  дю  гю  гю  вю  бю  аэ  Яэ  Яэ  Юэ  Ээ  Ьэ  Ьэ  Ыэ  Ъэ  Щэ  Шэ  Шь  Чь  Ць  Хь  Хь  Фь  Уь  Ть  Сь  Сь  Ры  Пы  Оы  Оы  Ны  Мы  Лы  Кы  Кы  Йы  Иы  Зъ  Зъ  Жъ  Еъ  Дъ  Гъ  Гъ  Въ  Бъ  Аъ  Аъ  щ  ~щ  }щ  Жтї ╡└┬ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗╬ккк                                 ┐┐┐ ╗╗╗┌╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╜─╞ ┬ыЎ ├Є  ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜ё  ╝ё  ╗ё  ╗ё  ║ё  ╣Ё  ╕Ё  ╕Ё  ╖Ё  ╢Ё  ╡Ё  ┤Ё  ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  ля  кя  кя  йя  ию  зю  жю  жю  ею  дю  гю  гю  вю  бю  аэ  Яэ  Яэ  Юэ  Ээ  Ьэ  Ьэ  Ыэ  Ъэ  Щэ  Шэ  Шь  Чь  Ць  Хь  Хь  Фь  Уь  Ть  Сь  Сь  Ры  Пы  Оы  Оы  Ны  Мы  Лы  Кы  Кы  Йы  Иы  Зъ  Зъ  Жъ  Еъ  Дъ  Гъ  Гъ  Въ  Бъ  Аъ  Аъ  щ  ~щ  ИрЄ ╡┐┴ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ў╛╛╛+                                       ╣╣╣3╗╗╗·╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╝╛┐ └▀ч ┬Є  ┴ё  └ё  ┐ё  ┐ё  ╛ё  ╜ё  ╝ё  ╗ё  ╗ё  ║ё  ╣Ё  ╕Ё  ╕Ё  ╖Ё  ╢Ё  ╡Ё  ┤Ё  ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  ля  кя  кя  йя  ию  зю  жю  жю  ею  дю  гю  гю  вю  бю  аэ  Яэ  Яэ  Юэ  Ээ  Ьэ  Ьэ  Ыэ  Ъэ  Щэ  Шэ  Шь  Чь  Ць  Хь  Хь  Фь  Уь  Ть  Сь  Сь  Ры  Пы  Оы  Оы  Ны  Мы  Лы  Кы  Кы  Йы  Иы  Зъ  Зъ  Жъ  Еъ  Дъ  Гъ  Гъ  Въ  Бъ  Аъ  Аъ  щ  Ф┘ч ╕╜╛ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╝╝╝r                                             ╗╗╗a╗╗╗■╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╛╨╒ ┴я№ └ё  ┐ё  ┐ё  ╛ё  ╜ё  ╝ё  ╗ё  ╗ё  ║ё  ╣Ё  ╕Ё  ╕Ё  ╖Ё  ╢Ё  ╡Ё  ┤Ё  ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  ля  кя  кя  йя  ию  зю  жю  жю  ею  дю  гю  гю  вю  бю  аэ  Яэ  Яэ  Юэ  Ээ  Ьэ  Ьэ  Ыэ  Ъэ  Щэ  Шэ  Шь  Чь  Ць  Хь  Хь  Фь  Уь  Ть  Сь  Сь  Ры  Пы  Оы  Оы  Ны  Мы  Лы  Кы  Кы  Йы  Иы  Зъ  Зъ  Жъ  Еъ  Дъ  Гъ  Гъ  Въ  Бъ  Аъ  Бщ¤ г╬╫ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗жААА                                                ║║║s╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╝┬─ ╛▀ш ┐ё  ┐ё  ╛ё  ╜ё  ╝ё  ╗ё  ╗ё  ║ё  ╣Ё  ╕Ё  ╕Ё  ╖Ё  ╢Ё  ╡Ё  ┤Ё  ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  ля  кя  кя  йя  ию  зю  жю  жю  ею  дю  гю  гю  вю  бю  аэ  Яэ  Яэ  Юэ  Ээ  Ьэ  Ьэ  Ыэ  Ъэ  Щэ  Шэ  Шь  Чь  Ць  Хь  Хь  Фь  Уь  Ть  Сь  Сь  Ры  Пы  Оы  Оы  Ны  Мы  Лы  Кы  Кы  Йы  Иы  Зъ  Зъ  Жъ  Еъ  Дъ  Гъ  Гъ  Въ  Бъ  Р▌ь ▓┬┼ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ║║║║ккк                                                      ║║║Ж╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╞╔ ╛фя ╛ё  ╜ё  ╝ё  ╗ё  ╗ё  ║ё  ╣Ё  ╕Ё  ╕Ё  ╖Ё  ╢Ё  ╡Ё  ┤Ё  ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  ля  кя  кя  йя  ию  зю  жю  жю  ею  дю  гю  гю  вю  бю  аэ  Яэ  Яэ  Юэ  Ээ  Ьэ  Ьэ  Ыэ  Ъэ  Щэ  Шэ  Шь  Чь  Ць  Хь  Хь  Фь  Уь  Ть  Сь  Сь  Ры  Пы  Оы  Оы  Ны  Мы  Лы  Кы  Кы  Йы  Иы  Зъ  Зъ  Жъ  Еъ  Дъ  Гъ  Гъ  Их° ж╠╘ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╝╝╝╠───                                                             ║║║Й╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╝═╤ ╝ую ╝ё  ╝ё  ╗ё  ║ё  ╣Ё  ╕Ё  ╕Ё  ╖Ё  ╢Ё  ╡Ё  ┤Ё  ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  ля  кя  кя  йя  ию  зю  жю  жю  ею  дю  гю  гю  вю  бю  аэ  Яэ  Яэ  Юэ  Ээ  Ьэ  Ьэ  Ыэ  Ъэ  Щэ  Шэ  Шь  Чь  Ць  Хь  Хь  Фь  Уь  Ть  Сь  Сь  Ры  Пы  Оы  Оы  Ны  Мы  Лы  Кы  Кы  Йы  Иы  Зъ  Зъ  Жъ  Еъ  Дъ  Луї д╬╓ ╣╝╜ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗═╣╣╣                                                                  ╗╗╗a╗╗╗∙╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗┼╚ ╗▄х ╗я¤ ║ё  ╣Ё  ╕Ё  ╕Ё  ╖Ё  ╢Ё  ╡Ё  ┤Ё  ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  ля  кя  кя  йя  ию  зю  жю  жю  ею  дю  гю  гю  вю  бю  аэ  Яэ  Яэ  Юэ  Ээ  Ьэ  Ьэ  Ыэ  Ъэ  Щэ  Шэ  Шь  Чь  Ць  Хь  Хь  Фь  Уь  Ть  Сь  Сь  Ры  Пы  Оы  Оы  Ны  Мы  Лы  Кы  Кы  Йы  Иы  Зъ  Зъ  Жъ  Т▀я й╔╨ ╗╝╝ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗░╣╣╣                                                                         ╜╜╜:╗╗╗ш╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗┐└ ╗╧╘ ╣ръ ╕ю¤ ╕Ё  ╖Ё  ╢Ё  ╡Ё  ┤Ё  ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  ля  кя  кя  йя  ию  зю  жю  жю  ею  дю  гю  гю  вю  бю  аэ  Яэ  Яэ  Юэ  Ээ  Ьэ  Ьэ  Ыэ  Ъэ  Щэ  Шэ  Шь  Чь  Ць  Хь  Хь  Фь  Уь  Ть  Сь  Сь  Ры  Пы  Оы  Оы  Ны  Мы  Лы  Кы  Кы  Йы  Иы  РтЇ б╙▌ │┬┼ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ║║║Сккк                                                                              ╣╣╣!╗╗╗├╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗┐└ ║╬╘ ╕▐ш ╖ы∙ ╡Ё  ┤Ё  ┤Ё  │Ё  ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  ля  кя  кя  йя  ию  зю  жю  жю  ею  дю  гю  гю  вю  бю  аэ  Яэ  Яэ  Юэ  Ээ  Ьэ  Ьэ  Ыэ  Ъэ  Щэ  Шэ  Шь  Чь  Ць  Хь  Хь  Фь  Уь  Ть  Сь  Сь  Ры  Пы  Оы  Оы  Ны  Мы  Лы  Мъ¤ Ц▐э г╥▄ ▓─╟ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ё╗╗╗a                                                                                       ААА║║║k╗╗╗Є╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ║─╞ ╕╨╓ ╢▄ц ┤щЎ ▓Ё  ▒Ё  ▒я  ░я  пя  оя  ня  ня  мя  ля  кя  кя  йя  ию  зю  жю  жю  ею  дю  гю  гю  вю  бю  аэ  Яэ  Яэ  Юэ  Ээ  Ьэ  Ьэ  Ыэ  Ъэ  Щэ  Шэ  Шь  Чь  Ць  Хь  Хь  Фь  Уь  Ть  Сь  Сь  Ры  Пы  Оы  Тч∙ Ъ▐э г╙▌ п╟╠ ╣╝╜ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗╕╗╗╗                                                                                                ╗╗╗╗╗╗╖╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ║╜╛ ╕╞╔ ╖╧╒ ╡╪с ▓сэ ░шЎ нэ¤ ня  мя  ля  кя  кя  йя  ию  зю  жю  жю  ею  дю  гю  гю  вю  бю  аэ  Яэ  Яэ  Юэ  Ээ  Ьэ  Ьэ  Ыэ  Ъэ  Щэ  Шэ  Шь  Чь  Ць  Хь  Хь  Фь  Уы■ Ццў Ыря а┌ц з╤┌ п╚╬ ╖└┬ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗Ё╝╝╝c                                                                                                            ╣╣╣M╝╝╝╠╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ║└┴ ╕╞╔ ╢╦╨ ┤╤╪ ▓╒▌ ▒╪с п┌х н▌щ лрэ куё ичЎ зчЎ жчЎ жщ° гъ· въ· бщ· ащ· ащ· бш° бцЎ ацЎ буЄ буё вся г▌ъ е█ц з╓с й╙▄ м╨╪ о╠╙ ▓╞╦ ╢┴├ ║╝╝ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ў╝╝╝Р┐┐┐                                                                                                                     ╝╝╝D╗╗╗╩╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗·╗╗╗Щ┐┐┐                                                                                                                                 ║║║N╗╗╗╣╗╗╗¤╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗э╝╝╝Р╗╗╗                                                                                                                                             ╜╜╜║║║w╗╗╗▄╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗¤╗╗╗─╝╝╝f│││                                                                                                                                                          ААА╣╣╣B╗╗╗Щ╗╗╗т╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗¤╗╗╗╩║║║~╣╣╣!                                                                                                                                                                              ╝╝╝"║║║o╗╗╗╗╗╗╗°╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗є╗╗╗│╝╝╝f║║║                                                                                                                                                                                                ┐┐┐ ╝╝╝D╝╝╝y╗╗╗н╗╗╗с╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗№║║║╘║║║а║║║k╜╜╜6┐┐┐                                                                                                                                                                                                                        ╝╝╝╗╗╗K╗╗╗{╝╝╝Я╝╝╝┴╗╗╗ф╗╗╗■╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗√╗╗╗▌║║║║║║║Ш╝╝╝r║║║?───                                                                                                                                                                                                                                                          ┐┐┐╗╗╗)╗╗╗K╗╗╗m╗╗╗А╗╗╗У╗╗╗ж╜╜╜└╜╜╜╫╜╜╜ъ╗╗╗∙╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗є╗╗╗▐║║║╔║║║╢╝╝╝г╝╝╝Р╝╝╝}╝╝╝g╝╝╝E╜╜╜#┐┐┐                                                                                                                                                                                                                                                                                                               ╢╢╢┤┤┤┤┤┤║║║╝╝╝"╝╝╝"╗╗╗-╣╣╣3╛╛╛+╝╝╝"╝╝╝"┐┐┐┤┤┤┤┤┤╠╠╠                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 ┴                             №              °              р              └              А             ■?             №             °              Ё              р              А                           ■             №             °             Ё             └             А                         ■            №            Ё           р           └                   №         р        А       ■       °       р      А           №     °     Ё    └    А       ■   №   °  Ё?  р  └  А  А   ■ ■ №°?°ЁЁрр└└ААААААА└└рррЁЁ°№№?■  А  └  р  Ё  °  №?  ■   А    р    °    №?     А      р      ■?       └        ■?         °            А                                                                                                       (  ЙЙ                        ┐┐┐╗╗╗O                                       ╝╝╝=╝╝╝╨║║║                                    ╗╗╗a╗╗╗∙╗╗╗^                                 ╗╗╗╗╗╗Ч╗╗╗ ╗╗╗ ╝╝╝ю╗╗╗П╜╜╜2                     ккк╜╜╜И╜╜╜Ў╗╝╝■╝─╞№║╩╬■╕┤й■╣║╖■╗╗╗■╛└└▀└└└E            ┬┬┬╛└└с╛┴┬■╣█у ▒ь√ кя  дю  йИU зv: дu; зНi ╖┤н■┼╟╟│ккк   ╠╠╠ ╛╛╛Ё┐╦╧ ╜я№ ╢Ё  ░я  кя  дю  иУh зv: гs7 Яo5 Ъl3 м┼╞ ─╚╚╤   ───▓└╞╚ ├ё■ ╜ъЄ ╖тч ▒сц лрц е▀ц иЦn зv: гs7 Яo5 Ъk2 Е╟╚ ╡╩╬ ╬╬╬Г╛┐┐·┼уь ├Є  ╛╗Я ╗ИH ╖ЕF │БC п}@ лz= зv: гs7 Яo5 Ъk2 Г╬╒ Л▌э ┬──я╗╗╗ ╚э∙ ├Є  ╛╗а ║Ц` ▒▄▐ ▒Ыp п}@ лz= зv: гs7 Яo5 Ъk2 Б┌ц ~х∙ ╝╜╜■╜╜╜■╟ъї ├Є  ╜┼▒ ╗Х_ ▒┘┘ ▒Ъn п}@ лz= зv: гs7 Яo5 ШyH ш■ Втї ╛┐┐·└└└┌┼╘┌ ├Є  ╜ыЇ ║е{ ╖ЕG │БC п}@ лz= зv: гs7 Э}K К╠╧ ~щ  е╫р ┬┬┬╔╛╛╛?╝╝╝№┬уь ╜ё  ╢Ё  ▒хя м╒╥ и╩┴ в├╕ Ь╩╟ Ф╒┘ Лщ¤ Еъ  РсЄ ╝╜╛∙╝╝╝=   ╛╛╛^╝╝╝·╛═╨ ╖ры ▒ы· кя  дю  Юэ  Чь  Ущ√ Ц▀ю н╨╫■╜╛╛ў╗╗╗a         ╕╕╕┐┴┴Ф╛┬├ю╝╛╛■║╜╛ ╣└┬ ╕└┬ ╣╛┐ ╗╜╜■╗┬├э┐┴┴Р╗╗╗                        ┤┤┤╞╞╞1╚╚╚.╕╕╕ккк                ?■?№Ё└АА└°(0 NN                                       ╝╝╝╝╝╝5                                                               ║║║U╗╗╗р╣╣╣,                                                         ккк╗╗╗З╗╗╗■║║║Q                                                         ┐┐┐ ╗╗╗│╗╗╗ ╗╗╗Э                                                         ┐┐┐╗╗╗╬╗╗╗ ╗╗╗ ╗╗╗м┐┐┐                                                ╠╠╠╝╝╝_╗╗╗ъ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗№╝╝╝╜╗╗╗a╗╗╗                                 ╠╠╠╗╗╗m╝╝╝ш╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ║║║∙╝╝╝Р┐┐┐                        ╝╝╝&╝╝╝╓╗╗╗ ╗╗╗ ║─╟ ╡╫▀ птю ищ∙ гэ■ кЖS л~F лЖW лЫ~ ││к ╗╗║ ╝╝╗∙┬┬┬p                  ╛╛╛;╜╜╜ў╗╗╗ ╝╔═ ╕цЄ │Ё  пя  ля  зю  гю  иТf йx< жv: гs8 бq6 бu@ нШ~ ╗╗╗ ├├├┼╞╞╞          ┬┬┬.╝╝╝√╝╜╜ ╛▌ц ╝ё  ╖Ё  │Ё  пя  ля  зю  гю  иЭy йx< жv: гs8 бq6 Юn4 Ыl2 ЭРo ╗┴├ ┼┼┼┌ккк      ┐┐┐ь╗╗╝ ┴уэ └ё  ╝ё  ╖Ё  │Ё  пя  ля  зю  гю  жйМ йx< жv: гs8 бq6 Юn4 Ыl2 ХyH ЖсЇ ╝┐└ ╞╞╞╣   ┼┼┼Й╗╗╗ ┬╪▐ ─Є  └ё  ╝█┘ ╕╒╬ ┤╘╬ ▒╥═ н╥═ й╤╠ ж╨╠ зЯ| йx< жv: гs8 бq6 Юn4 Ыl2 ЦvE }щ  Ц╫ф ╗╗╗ ───A┬┬┬ы╜╛╛ ╟Ё№ ─Є  └ё  ╛Яo ╝ЙI ╣ЗG ╢ДE │ВC ▒A о}? лz> йx< жv: гs8 бq6 Юn4 Ыl2 ФU }щ  {ш¤ ╗┐└ ╟╟╟╩╛╛╛■└╚╦ ╚Є  ─Є  └ё  ╛Яo ╝ЙI ╕У\ ┤йД ▓ЕI ▒A о}? лz> йx< жv: гs8 бq6 Юn4 Ыl2 УКd }щ  yщ  ░╚═ └└└Ї╗╗╗ ┬╧╙ ╚Є  ─Є  └ё  ╛бq ╝ЙI ┤сф пя  п┴п ▒A о}? лz> йx< жv: гs8 бq6 Юn4 Ыl2 СУt }щ  yщ  з╬╓ ╝╝╝■╝╝╝¤┬╩═ ╚Є  ─Є  └ё  ╛й ╝ЙI ┤▀с пя  п┐м ▒A о}? лz> йx< жv: гs8 бq6 Юn4 Ыl2 НйШ }щ  yщ  п╦╤ ╜╜╜ў╛╛╛щ╜┐└ ╚ё■ ─Є  └ё  ╜╔║ ╝ЙI ╕СX ┤д} │ЕG ▒A о}? лz> йx< жv: гs8 бq6 Юn4 ЩzI Гсё }щ  zщ  ║┴├ └└└╓╛╛╛О╗╗╗ ─▀ч ─Є  └ё  ╝Ё■ ╣╜ж ╕СX ╢ДE │ВC ▒A о}? лz> йx< жv: гs8 бq6 ЪМe Й┘у Въ  }щ  Н▌э ╗╗╗ ╗╗╗{┐┐┐╝╝╝Є╜╛┐ ├ыЎ └ё  ╝ё  ╖Ё  │Ё■ ░█▄ о╞╣ м╖а й▓Щ жлР впЧ Ю┤в Ш┬║ С█у Кы  Жъ  Въ  Вц· ╝├─■╝╝╝я╝╝╝   ╜╜╜I╝╝╝√╜╛╛ ╛▌ц ╝Ё■ ╖Ё  │Ё  пя  ля  зю  гю  Яэ  Ъэ  Ць  Ть  Оы  Кы  Жъ  П▀я ║├┼¤╗╗╗№╗╗╗R         ╝╝╝A╗╗╗щ╗╗╗ ╝├┼ ╣╒▄ ┤ую ░ъ∙ ля  зю  гю  Яэ  Ъэ  Ць  Фъ№ Чсё а╓с ▓╟╠ ╗╗╗ ╗╗╗ъ║║║N               ╞╞╞ ╝╝╝v╜╛╛р╗╗╗ ╗╗╗ ╗╗╗ ║╜╛ ╕┬─ ╕├┼ ╢├╞ ╖┬─ ╣┐└ ╗╗╗ ╗╗╗ ╗╗╗ ╝╝╝т╗╗╗╗╗╗                           ╜╜╜╜╜╜]─┼╟д└╚╩╫╜╚╩ч╜╔╦я╝╞╩я╝╚╦ч╜╔╦╘┴─╞б╜╜╜Y╣╣╣(                                                                                           ∙  ё  ├  З   №Ёр└АА└р№?   ЙPNG  IHDR\rиf/XIDATx┌эЭy|WХячVUпZ║eI▐╖─Iь8qВЭ&Ща╕[2ЪШ%ААф2dШ╟3 00!└L^ЄKИH К╒jG,vBЗ$ОЭ┼√╛╚┌Чnuw▌є■Рф┤d╔ЦT╒]╒ъ√¤|ьnUwЭ:U]ўWчnч3CбP'┬i Еs(P(К% Eг@б(bФ(EМЕвИQаP1JК"F АBQ─(P(К% Eг@б(btзPXзйййLJйЖ╥u]d2ЩРоы3ЧАФ2└;v?!DЎЎ~)ejє¤BИФiЪ@73gt]я╒4-╒▀▀▀_QQС\╗vm┬щkаШдf║Зцццj├0кдФ╒╠<@5U1s@@93ЗФQ@9Аз¤"ъ─Р@tш╨5Є7u╤ GдФmNЖq┤жжж╧i┐Л%y"П╧░ИЩXHDK,▐6@К/"p└!√ в8РH$Ўm╪░a└i'g2Jlдйй╔ыє∙Цe2Щ ЬCD+ЙшBf╛.yR├С┼f▐┴╠█ь╤u}╟╔У'_ihh0ЭЎп╨Q0 n╜їVqхХW.УR^"Д╕ФЩ/aцKИhБ╙╛)█Йш)хєDЇВоы╧╫╘╘t9эX!б`477/╘4э )хZ"║└┼POt╖▓ЧИЮgц-╠№дa[kjjТN;хVФМб▒▒QлииX═╠W2є[Иh-А∙N√еШ6)[Щ∙I"┌LD┐ПD"эN;хФИ╟учИИ2s@Е╙>)rЖ$в┐h╨вi┌ЯК9B(JxЄ╔'¤╜╜╜QM╙▐!е|Ык╗5 dцЗ ├xижжцР╙хУвАН7V!"DЇvяPъ┤O W▓@гФЄС║║║нN;Уkf┤l▐╝╣4СH\ рГ"4з}R/3є╧4M√∙║uыЎ;эL.Шq0▄Иw53└╡Pнї  внR╩{ ├°yMM═Iз¤▒э╝fК─b▒y>DDЯ░╨i3ЦA"zШИ╛╖n▌║-N;cХВАс9ы№3_ЛтJлp"┌ рО@ poбNИ*HhjjЄЖёaЯpЮ╙■(КЮDЇ=f■Q4эv┌ЩйPP╨┌┌ZТNзoЁПкыNсBzЩ∙n)х7╫п_╘ig&CA└pk■ч|C╙b 73@D?ЁяnuшjhllЇTTT▄─╠_╞╨t┘вD╫Ж╬049ЇкKhB┬╨M0ДР╨5@╫$Ih ¤о@г'═iC╨Ёя╬ Абks(A╚ я"%A22&aфv∙оdB╞∙'Р1╡мўщМЖTжh{_╗Ш∙Ы%%%╖╣╡Н└н@ёx№oЩ∙ыЦ:эLо╨5Ж▀╦Ёy$| П┴ЁцЁлД╫#ahtк░Ю·/ыuвўУ▄╞6┘╗-╦Wf m д3:RБTZ├`ZG2нc0еa0c`0нc0еЯЫ╞Af■╩Ц-[~z╦-╖HзЭ╔╞u░qу╞KД?"в╖8эЛx F╨╟°L╝■S ЖЮ║п3Й┬Uа0┘}ШБ┴┤ОD╩@b╨ГD╩└└аББд¤I/$| ╦'ЙшжH$Єв╙ОМрhjj*3 уVЯDvчi( HФе%~F└k┬╨╟▐ь ▄ А3mcТ)·Cb╨?шCo┬З■д.╣Е'K└ўдФ_нллыw┌W@,╗ЦИn0╧i_&PтЧХ2╩ГCЕ>р3Aзв╫3%╙╢=ОMS Ї&╝шM╨3рGw┐Г>Ш∙╞┌┌┌f'ЭpTЪЪЪ╩<╧ўЩ∙z'/┬┘─Х2┬еCЕ╛,(бЭКFзZФ╪)уэУ╩шшъ ал┐]¤Ї&`w╢-0▌жi┌ЭЪТьШ─b▒╖ !юaц%О8pИА╥cV9.5*a1┼ЫY А}>NQ╞n╦Ш}еhя)E{oТ)\╞KDЇA'┌Є.НННZ8■*АЖЛ&╤PQ╞и Хх&<╞╚'╙,pJьє╤вМ▌╓ЯЇбн╗'║Cш┬% ╤з#С╚ фєаyАx<>ЛЩа.Я'9BU!╞ЬYCЕ_S╜б╬Ё╣√|┤Y▓▀'=hы сxW]¤оHqg:Э╛╣╛╛~0╦Ы─b▒╦Иш╫чхАЭ0e└ЬYМъ В&d╓}б`┌v T▓m&R^эЬЕгХH8[MxТЩ▀[[[{$╫╩Л─уёў3є=kЮїzА∙U└№j└kгn %╓э╠╚■╝│пЗ┌лqв;ьTт"к╧u╗@╬аеех3╛Зъ√│╩ кБ╩^Q@ АА╔\╙┴┤З┌лq╕╜йL▐ЗзtI)▀YWWўЗ\ gpын╖К+о╕т;>У+ч'<)fW╧J┘Я(8mЫАIэ#Yрp{Ў╖═╦w/B└в╤шC╣0ЮN╦u3_ЧылУН└№*БEs й╡p'q*░ngЖ └╚{) G;л░я─|$Rз-╕Ь+L"·H$∙╣▌ЖmАс' =>ФзЛ"а:,░l!НSЁХ(░OF^Щ G:к▒√╪в|U L"║.Й▄gзQ[`╕Ё▀ р├∙╕"PX╢@ шж]рФX╖Sd0B:#░ў─BjЯ )s▐XШ"вўD"СGэ2hзPKK╦]>Ъыл Е°,╓P░\pХX╖Sд0Є╛?щ╟╦З╬EWrLRJyM]]▌&;М┘&---_pKо╧^`╔\ Kц И│Н╟Яp█Ш╧ХX╖Sф0Є■P√ь:║3з╒В.)х┌║║║Wм▓Eтё°GШ∙n %а╔eA┬╩st¤;~мSпJм█Qpъ¤`╩ГЧЬП╬■rфР▌║о┐┘ъЦ Л╜ХИ6╚YУ(░xоОsчkY}∙JжmgФM%v └╚5=prv]╬]"У?е╙щиХa├Ц<█┤i╙b"z9,№~/сЄХЦ-╘▓ц█+юgQх\vю6xНTоqеa▀▒b`┌░uыV├4═_ ЗKiWЖ4╝ё"╩ВоЩ4иPLЙЄ@▐╕ь/(єўцъЯМ┼b╫NwчiЧм╬╬╬яц2o▀╣ t\zБCWП}Eaу5R╕l┘ЛШ]▐Ц√BИЯl┌┤iё┤ЎЭ╬N---яpsnN╕hЩKч\Z@ЕbBЩ╕xё+XXy╪v█╠ЦR■╝▒▒q╩∙╫з,нннХ~ФЛЛdшД7\р┼ЬYEЫG^1гa\0o7Ц═┘У уWДBбONuз) @&У╣@╡▌▐{ ┬Ъ^Д╦T}_1│YR}ч╬┘g╗]"·╫йVжT┌b▒╪;╝▀n╟=aїr/JкЁ+КГе╒√▒╕ъА▌fKдФ =Х&=`x}╛W╠╡╙у╙ Ю√lЛp@2 дЗЧ■ъKMl1`0Їм╘ц║╞ЁгэФxyт╝ У: щ\ч┬0Щ}v║G:ц└NШ∙}╡╡╡НУ∙юд[┌╛DD╢~M.╜└ЗТАjщЯ =Iа╜_рxб7IшdЇ·ТД╛╘╨ч}ГД▐$╨Ч$$╥М┤IHдqZ!·s╠CАy╘э:Є∙╚6AМаgH В^Ж▀#4A/Ё0J}Aг╠'QY┬иHTЦJTЖ╓0TМ└X1 U$StЇ┘╫ЫND ▐╪╪°PCC├Y L*hnn^*Д╪Szл╬єб*м┴Q╡vYРС└╤nр`'сD/сd?╨╓Khы#Ьь#Ьь% Ъ├rм▀з^xМYsК╓`Ї5╢>zЗ ¤ $┬AЙъ2F╪/QU:Їoa╪─В░─ь▓ Н┘o╞FCпщМОзwнA"хЗН|&Н~яl_ЪФ─уё√Щ╣┴Nя.Xь┴┬9#╣╖ЛO·З ·╛Б¤ЭДг▌└╤n┬╛vB╩─щlФ╣┬Аё|╦╢н ╞ьRєBМ∙!єB&ЦVЪXZebnй9┴· Е-╨Ыт┘▌лaJ█BдЎ┴┴┴єо╣цЪ╬3}щмUАсl╛я╡╦+ШWеcс╧ШЛ2siы#ь\0o'vZnЧ╔Y^пў│■хL_:k╨╥╥Є[я┤╦лТА└х+¤╨DЎМ╛Щ0А}э└ З^ю╟╧ЯЄ#РX┐r}ё Ц╧M;э■┤X> 5tїЗР╩╪Тx┤ЬИ>р█}сМ@KK╦п╝╧оУ[▒╘З∙╒┘ЪS╕└▒┬Ц╜[Ўv$П Ы╘VEУ┐vУМЮ╬н6ё7oLрm'с╤ 'Ау▌╒╪╢%lтШоыK'Z|tBn∙▀ЫЄ∙З╦4мYШ┬ЕpЯдM`єБжЧ /ж,wrsg}{М[J&{э*JL╝яЄ╓$QъУгў;эz╕C╞╓▌o@g6ё╤h4zўxLX╕Йш&╪T°5AX▒┤ ╓lЧУ¤Д╗Ю╘p▌╜:╛╒всЕС┬пp=э}?~"Иw▌^БЯ?ъa)Ц/x D╢5p▐8╤уFMMM^├0и▓ушч.Ёbщ№С:MсDm¤@у_Y m:ў+╓`шO{п▌▄Є nОаЎ┬ф8╫├=╝zф|<╣6▒*Нn╗q▄'╝оыяГMЕ▀ы!,Ъы║ї╪╧H"M╕cЛ└╟~йуСЧ─йa│К┬чh╖Ж~аЯ╜п'z▌=,qI╒^▓чцcцПН╖}\B| 6▒lбпаZ¤Я┌/p╙пt№vЫ*°NУ╦j╓^єр}?уб┐╕╖jъ5RШ?╦ЮВЕjll<эI|Ъ┤╢╢.`ц+э8hiP├ЬYЕС╪#С╛°┌уm¤╓э)▄O_ТЁїGKЁХ▀Ц!СvчCjI╒>┌Ш9 Евc╖Я&щt·¤░йёo╔\и2yэ!|Ў╖~┐KMGv∙║s~╖═Л▌╞Ю6ў=м╝F │C╟m▒5▐И^1╬ЧlЩяяў TW╓ хШmG ЁБ¤ЭN{вK>{ZЎЭ╘Ё▒{┬x■А√D`QхA╗L];╢0JЪЫЫЧ╕╠О#-Щчu}яWО╖>.╨7шrGЛФ| *= ┬▀▀F№ew5ZЧ·{Qш▒├T(_Э╜aФ╤_├ЖыnшД╣Хю║Иcyн °╩я4╫╓¤∙НFHЩ└Ч(G╙6w5╬л8dЧй╖e 1V▐65[у3╖╩У╡nЯ√шN■╡YC╬╓kP╪БS╥lJр+Х!■r╬╓╗Щ2│╦█li 0к!ЁT1о╘╪qД╣Хюн√3▀yВ╨оZ·]ПУг-е╛№`╢юs╟╜мki╠*m╖├╘╩Н7Ю╩ьuJ╩╦╦пPb╒zYPCi╨╜,~╗НЁ▄!Ў+╬N*C°ьпB╪▀юО√yО=╜$Д8Ь"к▒├·l?¤╗Д_>чт║ЙbnРщ╛$сєўЧ╗внhVI√ШДм╙Cq╒йў#oИhнN║╣ыяg╧Ткў+ж╠о:╛■Hй╙n@╫╥(t[╢├╠oy/р╓[o.╖j╕4и┴яuчЎd?б∙UчU\1y▄4уЄёm>4╜ш|╧└мТУvШY┘┌┌Z g║ъклVШжYn╒je╚╜O жCн╗∙@Мєл BМP33Мг▌ДW█║Ь╛КщЁ▌Неx╦▓╩╬MЩU┌О▌╟ЧY5геRй╒■аАiЪo▓├╣pЩ√FQCЙ&7╛Т√з eЛ$╓Э'ёж%&J<└xS<%3v%№iПЖж)Х ¤ +┴W▀i= Я.е╛>hB┬Ф╓"m"║#рbлО A(/qGkщXЮ;DшJф╬■∙╒М▀Т┴┼є╞╬u?A└Eє═╦рлБ{Я╤ё╪vS═<,y▐Зў^6АЛц;УsРHв╠▀Г╬■РE;t!0▄ ДXe╒▒▓аMsч╙ьщ╣єыЭлL| =c  $ 7┐5Н zOсАУWH1YШБ ~┬ro╣%╩]6Ь/Ж@JyйUГбRwЖ  р┘ГЎ А.А╧н3ёўWЪЦч<мШ-ёГЖA,йpS│Чb"╢ьЄр┼C╬╡wХ∙н╧ Bм╤╘╘T└Є┬dn№sиkhy-╗╣щJ╡╪╫к8╗МёНwжPP"P▄∙√аc╟··,█`цp<Я-╝^п-KС╕Uv╢┘o│n╣─ЖХЎw)TХ0╛zM Ж;/е"Л'wyq┤█Щ*рI@╪0 └∙┬0М╒VнhВрў║│■┐ыд╜~═ 2>ёW╣k▒╗pО─{.═ф·▓(,"x╪бtbD~Пї░ЦЩ WY5Ї ╫f■┘m╦№Й╫╣юr oОЫ;>░&ГRЯк ╕ЭЗЯўC:Ї3}╢ &Щ/ИhЕU+~Я{c╓г=Ў ╙ЬR╢╡▐?%^р╜oP¤ВnчHЧЖWП:╙°э3ТЦm0єaЪжх─уn■Ы6БNG▌]qОДЦзSн9O @!Ё╘ngr╪!D┤@Y5фў╣S┌·lcИЎц%∙ЛўцЗ ├крvЮ┌уLц+Я╟║Ш+дФЦcЯ╟Э╨nу╙_╫А│є[ ╫,TQА█y~┐╟Сх╞╝·аfB╢Ф\ПсNш╖х Ў#oс  ╒└ ╫У2Б¤э∙o04[ц╡ЗДФ╓╡ ▌Э=╜ГЎаК`■ c╣ъ (Ў9░ЮАо┘╥UlC%┘0▄)v╬┤sвгCwoчК"Л╜m∙ бt═Ц╔H╓[яД ╫о¤Ч┤▒n╓iKЫ╦╘H83сL1Euц_14a¤╖,∙оO;з╪vЇч_фО┘8ЖAС;·Э)vмlKрVь╠╘7tф9Х°╬кPЇ%Э)B╪Р ╘кЧОdlnC{·@■Ф~╨Ю╦aЕ} 8Ф1Ш`¤ g=p▒╪9Ю┌Ч?xюА@2у▐kлxЭо~ЗкnИ▄Х╗5╖<{Рp░3?ЕЄ7/╕3┴КтtОў З&╣@ЬЪ хжю}&ўj ьБчЄX▌PX#Э!t:dLы├Р-{mЪEд■┤[`█С▄¤╪)УpчfўжWWМ╧1ТГдMы╟T└a_▀(r╓EўГ▀ы╪msEю9╓У_0еfы"!,оу]lЇ$ЗЦя┤╖а▐╖UG╙vUў/DОwч╖ РС╢DЙ╦йtёЙ└о6┬з╨liФ ▄╡E╟O╢и┬_иы╔пд╥╓АЩП┘"╔┴<н╣х2Оt>√н;┼┤█cv |с!┐┌к !s╝;┐┐_"х╖lГИъ╢@Jв╠О┼д7 |3жс┴оУЙ7,РУuвЧЁлчt№n╗@ж8їsFq,╧UАd┌8дkЪї╞ЛbН▓yэсKПш∙kЧШX╜А▒0,Qю Ї$Бу╜ДWN╢ь&╝v";j(╛*╘L#▀mvD╠|╪░s┌]Б╙Х 4э╨╨┤cшoNТ╚мК№ гнOГ)є79╬&xUш║ї║Ko┐Eq#х╨ъ┴∙b e}e"M╙^║о[Ющ▐;Р▒}▄╜BQh┤ўхG╥жОБA╦ЛТЁрррKВИ╢[нH Ї'Tа(n:·є3и7Q└rўє╛···р├░▐з╪гкК"'_@Oв╠3OА "[а╜KхпR7∙jш░.DЇжi■╤шшVэКтж=/@шNД-[1MsH.╣фТt]╖╝Дf:├шщWл┌*КЧО▐▄ @o"И┴┤хi└эuuu█Бб┘АRё{{кJ┼K{;·+э0╙Мс!*Ш∙ п╫·"З╟█U;Авx╔G#`{я,;╠№vфН╙4їx<ЦkЁ¤ =}к7@QЬtфVLйгл?d╒L*ЭN?>ЄЗАK/╜tЯa╧С >Пй(@QдЇ$rЫ╚хDwдї$ O╘╫╫ўМ№q╩Ът~Я╧Єш";ЩR╜Кв$ХбЬо|м{ОeDЇ╦ь┐O АiЪН>Яї╒(Si╞Й(КУ╛dnкйМБО╛ лf·5M{ {├)oWнZ╡╫ыї>cG5` Q╫хV( И▐ └ёо9`╢V6Йи▒жжж/{█(oЩ∙┐эш шщ3╤╒л┼GnЦ #юX`┘КФЄЮ▒█F @__▀}>Япo╥╧АК┼Hoаг/Д╛дхщ┐/╓╓╓■~ь╞Q░vэ┌Д╫ы╜█О4amЭitл.AEС╤ЧГХВЮ\d┘3 ╫x█┼8_№б▀я╖е╫A(КЛ▐Д╜РH∙p╥·рЯЖa▄7▐зеZ╡j╒л[╖nНїўў╫Y=jgO▌TФ;Уёv╒<╞й╒╦ЩМЧКЛ_х▒█▓▀╔▒▓■>ЛэQ¤в<Оў;з}W╛~мёП3Є2┴чc№╧ПГ▄йЦ%Я. Ы;└ЎЭXlGу▀э555у&■╖d·|╛Єx7^Y╣rх ~┐ i;Nвз▀─сj\Ав8HзэА=╟ЧZ^■ЛЩ 3НvOЇ∙Д╓Г┴рзь╚ EГйB╙rЕbъ╪ pм╦Є╚┐.]╫o?╙&А /╝Ё╧Б@рY;N&c2^;`9ўиBсzR6═Иэ╚2╦u VSS╙uж/Ь1╛╨u¤B[▌╟█3hыT∙3;"А╢ЮJЬь╡<яgggчmg√╥`═Ъ5╗}>_Л]g╟Ю$Sj!┼╠%mqшЛd п9▀▓╠№П gm┼?k CII╔uB[Jm:├╪▒GН P╠\Rk└▐уЛРHYЫХ╦╠▒┌┌┌З'є▌│ └кUлNЖё?v]аЎnОYя^T(▄И░P■{е╪▀╢╪к )х Ю┤┐У∙╥▒c╟n╓4н╫О ;д╨┘гЖ +f╙m2У,░¤р Hы _[┐~¤оI√;Щ/544ШЪж¤Г]Й╪╢+йVv!е╦Ц╨жь<║╠О ?█┬сЁwз▓├дG\u╒Uw !^│|ЕЖIе/юДTр*:▓9Г╨hъ└╔Ю Р ╘(@k╠-7aJБЧ.╟■╢∙vЩ╜▀0МП!G╜в╢ @MMM2ЭN_`╥єСзКФ└О╜l▀c┬T:`+m}*░┬К╣╧ь\Еу]Цє∙НЁ`8╛╬Ою╛Й░¤юЮxuц╩i8zRт╧/е╤;акv╤n╦▓░┼╔│XPv╛s·DЇЫp8№БйNяЭ*9С№h4·23┐@Ns $БgvdpЁ╕ мbJа;Gk█OЧBиР"м┐░7\1l╜ЫoДЗBб╨√s]°Б Dг╤V"·r<вSJр╒¤╧╛lb йвБщ╥ЩклuКД:n║jв╦├vК╒ущt:/Е╚б@$╣ПИn╔╟Йtї2Ю▐.q╕MЙ└tPс ╘X│иЯЛ,└╥Y╓2°fCDПд╙щw╒╫╫ч-uv╬c╛H$Єuf■I>N&c/яХxю LХv╒8)╩¤:оє╝M╝║}╫МЩвi┌╗єY°Б V╢ЫоооПЗ├a└Зєq╝Ор╔mМ┼sАsцB▌█gE :3D└ъЕ%x√┼│Ё╪{C╤╖в╤шЧр└╚╝НЖЖ│││єгDЇ│|Э3░я(уйЧАЎnыЎf:J&fn╣7]9я_Sewс7ЙшуСHфЛphЎs▐ЮН fGG╟ї~Ъ╧H2■Є*у╣W}Й|╣░PгO╟o╝ут |║fЮнu¤aдФ╫F"█g╙NЕ╝■ъND#tЇ~ xeРRщOгCEз╨╕тЬ2|бnо8╖b Й>'╔I╗'ЎLrbкmccгVQQq3_ч─IkX<X4ЗбkЬ|ёШ╫│m;├чlУЭьW╢╔╬(ЫCпўє^=.NmuW З│ўхlK<&Ахь▌F∙╧гw|¤╗cОї·ЯcяOє}y╞єmЇ&ЇНx├В в╦Cифмyl╜+Йь╔╒жВ# Й@8╛└ rъф }HV3t (fxя┴╫ВЩ+чP╗<Д┘еr╚¤R╩П╓╒╒їчЄ S┴19~< *3┼I' X4Ы▒аZ┬╨Бb╔@эm%п/ цр1Я:ЪM Иp╔|?▐║мs╩rZЁMf■?╡╡╡▀є#8О╙И┼b╫╤r·+Ь Mєк$╧СЁyF╢╬|шL▐їум1ь3\tA╕x^ы╬+CeI╬o╣f■Ы┌┌┌ц\h:╕Bаееe=АFеN√B╠Щ┼XX-Q;s`WЫЖюїg}43 ф╫Ёж%%╕|QAПmуЎ╧─6!─╡ы╓н█ЭПГM╫┤┤┤╝└г,пФheA╞┬┘│├f╓АвЩ%Oя╙ё∙│║╣fРАч╠Єaэт\8╟√Ї'фОd2∙Щ 6 фэИ╙└U---ЛШ∙wD┤╥i_▓1t╞╝JЙyХ┴Qл┐╛№ю%▀jЎf}T°Pю╙░zaл0+w-·у╤р╞h4·`>:]\'╨┌┌ZТ╔dюЁ~з}П▓рР╠о0│r└о▄√gю┌ь╔·и0└г мШэ┼ъ∙AЬ[х═√tb"·ГiЪ╫╒╒╒╠єбзя│`ДX,Ўi"·8▄88BХх&fWHTЖ2╨▓л$▀Л{ёЫ▓.q Аб╦л|╕xЮT√а G4eИшk▀Шю"ЭNсjАMЫ6╜EJy?█ТмхMХ!│+L╠*╧@#аPр╦√Ё╟]Ya▓╦ `,Яэ├Єj╬лЇ┬╨┼╕KёСuы╓mq╥ЙщтzАцццjM╙~╔╠ыЬЎe2ДKLTЕ3и,7сє0▄, K?v═jwЩє╦=8wЦ╦л╜XЄ╕![РЁ¤d2∙e╖7ЇЭЙВ`hф`(·:}yЮ├`Х╥АDEЩЙК2бТЇшк╞╛╧┐╝яоОўd]R@eP├╥ /╬Эх┴╣Х^°t∙╫yEqCб>ї│)!Л╜ХИю░╘i_жГ@Y`H ╩ГФЧd&? 9└ТQw[ R┘5╫< А.s╦ , X╥▒8ф▒}╬╜MШ╠№]├0nййй╔╔JX∙жр6o▐\ЪH$╛рFFю╚ !В>бх%&J}цpuюаgx√П╟d▓═бxtа*иa^ЩБye:цЧи*╤сL█▌ФxQqу║uыЮv┌;)Ha╙жMїR╩╗рвБCv ИQР( dPт7ЇЩ°L° ╗`oс·ЯF;`QМr┐@e@Ge@CeP├маЖ╩АЖRп+Яьgв└-║о▀Ю╦№№NQ╨П=ЎX╪ыї▐рoЭЎ%╫hВЇЫЁ{L°<&№^~5с╤% ¤lq·╢gўk°▄п╟$╗8ГhВ4АRП@╨#ЇJ╜AГPц√f5з║уьД№┬4═╧п_┐■и╙╬фКВАтё°╗Ш∙√9эЛSсФx CchCРДо1t-[ ]У╪qT├п╨ЁhC#h─Ёър34!ш_░╧ 3o'вOFг╤Vз}╔53Fр╤G x╜▐/╤чx,T╛ЗШп╝№N3г`ДН7.B▄FDїN√в(╥юNз╙_^┌оhШС0Bssє█5M╗НЩЧ8эЛ┬Х0¤ЪИ╛фц)╗╣dF 477ЕЯЁY╕ ╫А┬5К<нМдpOI)┐сЖT▄nвш`Дx<~>3 @ lnБbЄ╤№{$y▄i_▄H╤ └├=7╤НPUГЩxМЩ┐Y[[╗┘ig▄L╤ └нннsL╙╝ЩЩoPс┤?Кi1рWЪж}√ълп▐ю┤3ЕАА14775M√ 3└∙N√гШG▄)е№Q]]▌ зЭ)$ФL└Ё╩EИшfоЗj0t рGЭЭЭПZ*.╖а`l▄╕qот}DЇ19эOС╙р>M╙nSa╛uФL К┼bW╤ ▐ `╒аbR0А═DtПiЪў╣im╜BG └4imm Щж∙a)хчЙhБ╙■╠Pv╕ЧИюu╦j║3 %ijjЄЖq3АпZ4з║▄р▐h4║cr )ьE АMl┌┤i13 АЩ▀ю┤/H/3?FD┐╓u¤▒ЩТoпP`3---яЁCsЬЎ┼хtx└║оoTЕ▐ФфАцццj"║Gх#8НcDЇ(=╪▐▐ohhH9эP▒г wP,√} А╫▓╡┬e3?JDНЫ7o▐r╦-╖Hы&vб ╟l┌┤i╡ФЄ>ч9эKЮ╚╤Ш∙"zX╡▐╗%yаеееЬИюЭ┴ Д='вЗУ╔фяо╣цЪNзRL%∙Гb▒╪?╤703жbh(nгФЄ58з0QРgтё°f╛@╚i_ж╔S╠№?DЇЫh4┌э┤3 k(pАX,v=`Е╙╛LIDI)┐лц╫╧,Ф8Dkkk(ЭN▀OD╡N√r└/Ш∙╓┌┌┌ЭN;г░%╥┌┌кз╙щ█ЙшуN√2/╕1Н>х┤#К▄б└┤┤┤|└╨ЬЎeШяе╙щ/╓╫╫:эИ"╖(p ═══oB№@ЙУ~0є╓╓╓~╬щыб╚3б;jFPWWў3 3r╨НT&У╣╒щkб╚J\Dmmэє▐DD[rс╡···зпГ"(p╡╡╡GЙ─Ut┌┼╠G А ┘░a├└ц═ЫШ∙█y>Ї9НННniИTф╒шrтё° ╠№cF>ОGDWE"С?:}▐К№а"ЧЙD~BD╫╚╦░[f╛╬щsVфO<ё─J╙4░8╟З4Msщ·їыП:}╬К▄г"Асълп▐nЪц┌<ЇxЕ7;}╛К№а"Аг╡╡╡$У╔▄рЪж[╫їх555╟Ь>_EnQ@БQSS╙╫┘┘∙N"·aSЮ╔d╛уЇ╣*rПК ШX,Ўi"·OфH╚еФС║║║MNЯз"w((pтё°{Щ∙з№90 J0\╜vэ┌Д╙чй╚ к PрD"С_K)пРЛe▒Чўўўч{0Т"Пи`ЖП╟╧aц╟,╖┘4╤;"С╚гNЯг┬~Ф╠ тё°,f~└[m6}\J╣коо.QЖ┬AT`ЙD┌u]П°С═жg !hllЇ8}О {Q0├ийй╔Dг╤O0є?0m4}eEEЕjШaи*└ &П┐НЩяPnг┘в╤ш u·▄Ўа`Ж╙▄▄|СжiП0єЫL&Зg >уЇ╣)мгаhmmн4Mєf╛╩&УGL╙╝LM*|T@PSSs2ХJ╒°йM&чiЪ╓и 6╛=НкЩГМА"деех▌КВVm╤'"СИ▌▌ОК<баH┘┤i╙j╙4"вMeИшп#СH╠щsRL%ELkkke:Э■Е ыЎ╕2Нns·ЬSC51555'╗║║ъЙш[Zt║Ф╤├ёx|╢╙чдШ*Pb▒╪;Ишз░6hш┘d2∙╓ 6 8}>К╔б"а╢╢Ўa"z#3o╖`ц2┐▀r·|УC АтСHф╡@ ░╞ 0sC,Sы к аЧX,╓@DwMcw&в┐НD"ў9}К3г@1!Ы6mZlЪц/Иш-╙╪=!е№л║║:з:UL%К3▓uыVгггу_Йш1┼*#э╙4эЄЪЪЪУNЯЗb|Ф(&┼жMЫ▐"е╝ └К)ю·з╬╬╬HCCC╩щsPЬОjTLКuы╓m З├Ч0єLе0_З┐ы┤ КёQАb╩l▄╕ёM╙юp┘dўaц╫╓╓▐ы┤яК╤и@1e╓п_ BggчЫЙшуОLf"·AssєBз}WМFE K<·шг┐▀ if■╬>КЁёh4Zя┤╧К╫Qа░ЕсФф_Ёw8│мНFгO9эпbUP╪B$iПFг фў√╤'<Йq&1єїN√кx(rFKK╦"5▐LD Ш9р╤hЇ!з}S б@б(bT@б(bФ(EМЕвИQаP1JК"ц Ё"k╓┐Д.IENDоB`В( @                                                       ┐┐┐╕╕╕                                                                                       ╗╗╗a╗╗╗ф╝╝╝=                                                                                 ┐┐┐╗╗╗Ю╗╗╗ ╝╝╝j                                                                                 ┐┐┐╗╗╗╩╗╗╗ ║║║а                                                                                 ╗╗╗8╗╗╗щ╗╗╗ ╗╗╗▄┐┐┐                                                                               ╝╝╝T╗╗╗Ў╗╗╗ ╗╗╗ ╗╗╗О                                                                           ААА║║║~╗╗╗■╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗¤╗╗╗е╗╗╗G╠╠╠                                                               ╗╗╗@╗╗╗─╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗я╗╗╗б║║║?                                                   ║║║U╗╗╗╥╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗╓╗╗╗R                                       ╣╣╣!╗╗╗├╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ║┐┴ ╢╩╧ ▓╒▌ о┌ф к▌ш ░Чs ▒Фo ▒Ъ| │лЬ ╖╣╡ ╗╗╗ ╗╗╗ ╗╗╗ ╝╝╝╧╗╗╗8                              ╝╝╝P╗╗╗Ї╗╗╗ ╗╗╗ ╗╛┐ ╣╥╪ ┤фЁ пя  мя  ию  ею  вю  йСd кy< иw; жu: дs8 е{G мТs ╡▓л ╗╗╗ ╝╝╝■┐┐┐Р┐┐┐                     ╝╝╝g╝╝╝■╗╗╗ ╗╝╜ ╗╘┌ ╕ь· ╡Ё  ▓Ё  пя  мя  ию  ею  вю  иЭw кy< иw; жu: дs8 вr7 аp5 Юo5 жЕ] ╢пж ╗╗╗ └└└╨┐┐┐               ┬┬┬\╗╗╗ ╗╗╗ ╝├╞ ╜шЇ ╗ё  ╕Ё  ╡Ё  ▓Ё  пя  мя  ию  ею  вю  зиК кy< иw; жu: дs8 вr7 аp5 Юn4 Ьl2 Ъm6 к╣╡ ╗╗╗ ┴┴┴ф╢╢╢         ┴┴┴%╝╝╝№╗╗╗ ╛╔╠ ┴я№ ╛ё  ╗ё  ╕Ё  ╡Ё  ▓Ё  пя  мя  ию  ею  вю  ж│Э кy< иw; жu: дs8 вr7 аp5 Юn4 Ьl2 Щj1 МзУ б╤┌ ╗╗╗ ├├├╪ккк      ┴┴┴▐╗╗╗ ╛├┼ ─я√ ┴ё  ╛ё  ╗ё  ╕Ё  ╡Ё  ▓Ё  пя  мя  ию  ею  вю  е╜н кy< иw; жu: дs8 вr7 аp5 Юn4 Ьl2 Щj1 НвЛ }ш■ м╩╨ ╗╗╗ ╔╔╔Т   ╩╩╩[╗╗╗ ╗╝╝ ┼шє ─Є  ┴ё  ┐ю√ ╝╧├ ║╬┬ ╖═┬ ┤═┴ ▓╦┴ п╩└ л╩└ й╔└ ж╔┐ зиК кy< иw; жu: дs8 вr7 аp5 Юn4 Ьl2 Щj1 МзУ }щ  Бф° ║╜╜ ╝╝╝¤╣╣╣───╘╗╗╗ ┴═╤ ╚Є  ─Є  ┴ё  ╛ъє ╛ЛK ╝ЙI ║ЗH ╕ЖF ╢ДE ┤ВC ▓АB ░~A о}? м{> кy< иw; жu: дs8 вr7 аp5 Юn4 Ьl2 Щj1 Л░в }щ  zщ  д╠╘ ╗╗╗ ╟╟╟У┐┐┐√╗╗╗ ┼рш ╚Є  ─Є  ┴ё  ╛ъє ╛ЛK ╝ЙI ║ЗH ╕ЖF ╢ДE ┤ВC ▓АB ░~A о}? м{> кy< иw; жu: дs8 вr7 аp5 Юn4 Ьl2 Щj1 Й╗│ }щ  zщ  О┘ш ╗╗╗ ├├├с╜╜╜ ╗╗╗ ╚ъЇ ╚Є  ─Є  ┴ё  ╛ъє ╛ЛK ╝ЙI ║РX │╧─ ░╫╫ ▓Ьp ▓АB ░~A о}? м{> кy< иw; жu: дs8 вr7 аp5 Юn4 Ьl2 Щj1 З─┬ }щ  zщ  Грє ╗╗╗ ╛╛╛ў╗╗╗ ╗╗╗ ╔ьЎ ╚Є  ─Є  ┴ё  ╛эў ╛ЛK ╝ЙI ╖┴н ▓Ё  пя  н▐у ▓АB ░~A о}? м{> кy< иw; жu: дs8 вr7 аp5 Юn4 Ьl2 Щj1 Е╧╘ }щ  zщ  БтЎ ╗╗╗ ╝╝╝■╜╜╜■╗╗╗ ╟ця ╚Є  ─Є  ┴ё  ╛ё  ╛ПS ╝ЙI ╖┐к ▓Ё  пя  н▌▀ ▓АB ░~A о}? м{> кy< иw; жu: дs8 вr7 аp5 Юn4 Ьl2 Щl4 Бсё }щ  zщ  Ж▐Ё ╗╗╗ ╛╛╛Ї╛╛╛ё╗╗╗ ┼▐х ╚Є  ─Є  ┴ё  ╛ё  ╛бq ╝ЙI ║ОT ┤╔╝ ▒╥═ ▓Шi ▓АB ░~A о}? м{> кy< иw; жu: дs8 вr7 аp5 Юn4 Ьl2 СЦx Аъ  }щ  zщ  П╫ц ╗╗╗ ┐┐┐▄└└└н╗╗╗ └╚╩ ╚Є  ─Є  ┴ё  ╛ё  ╝╒╬ ╝ЛM ║ЗH ╕ЖF ╢ДE ┤ВC ▓АB ░~A о}? м{> кy< иw; жu: дs8 вr7 аp5 Юn4 Ш~P Ерю Аъ  }щ  zщ  м╦╥ ╗╗╗ ╗╗╗П╝╝╝=╗╗╗ ╗╝╝ ┼чё ─Є  ┴ё  ╛ё  ╗ё  ╣╪╙ ╣аt ╕ЖG ╢ДE ┤ВC ▓АB ░~A о}? м{> кy< иw; жu: дs8 вr7 аs; ЦЭА ИсЁ Гъ  Аъ  }щ  ВуЎ ╝╛╛ ╗╗╗ ╣╣╣B   ╗╗╗┬╗╗╗ ╛┴┬ ├я√ ┴ё  ╛ё  ╗ё  ╕Ё  ╡Ё  │хэ ▓╠├ п╝ж олН мв} лЫr йШo зШp дЫw адЖ Ы░Э Ц─╜ НфЇ Йы  Жъ  Гъ  Аъ  ~ш■ ╡╚╠ ╗╗╗ ╗╗╗├      ╝╝╝&╝╝╝Ў╗╗╗ ╜├─ └щї ╛ё  ╗ё  ╕Ё  ╡Ё  ▓Ё  пя  мя  ию  ею  вю  Яэ  Ьэ  Щэ  Ць  Уь  Ры  Мы  Йы  Жъ  Гъ  Вш№ ▓╩╧ ╗╗╗ ╗╗╗∙╕╕╕/         ╜╜╜6╗╗╗Ё╗╗╗ ╝╜╜ ╝╥╪ ╗ы° ╕Ё  ╡Ё  ▓Ё  пя  мя  ию  ею  вю  Яэ  Ьэ  Щэ  Ць  Уь  Ры  Мы  Йы  Жщ■ Х┌ш ╕┬─ ╗╗╗ ╗╗╗°║║║N               ╝╝╝.╗╗╗╦╗╗╗ ╗╗╗ ╗╜╜ ║╔═ ╕┘с ┤фЁ ░ъ∙ мя  ию  ею  вю  Яэ  Ьэ  Щэ  Ць  Фъ№ ШтЄ Э┌ч й═╘ ╖╛└ ╗╗╗ ╗╗╗ ╗╗╗╫╝╝╝=                        ║║║]╝╝╝╥╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ║╛┐ ╕┬─ ╖─╟ ╢╞╔ ╡╞╩ ╡─╚ ╢┬─ ╖┐┴ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗┘╗╗╗p╠╠╠                                 ╝╝╝"╜╜╜w┐┐┐┬╛┐└я╝╝╝■╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╝┐┐Ё╜╜╜╜╝╝╝Б║║║0                                                      ккк╣╣╣!┐┐┐4╔╔╔K╔╔╔d╞╞╞^───I╜╜╜2╝╝╝"┐┐┐                                                                                                                                    ╧   П  ■  №?  °?  Ё  └  А №°?Ёр└АААА└рЁ■ р     (0` $ЭЭ                                                                                                                                                                                                                                 ╗╗╗O╗╗╗┤╗╗╗1                                                                                                                                 ╝╝╝"║║║║╗╗╗ ╗╗╗и                                                                                                                                 ║║║N╝╝╝ю╗╗╗ ╗╗╗═│││                                                                                                                            ААА╝╝╝И╗╗╗■╗╗╗ ╗╗╗х║║║                                                                                                                           ╣╣╣ ╗╗╗п╗╗╗ ╗╗╗ ╗╗╗∙╗╗╗8                                                                                                                           ╕╕╕╝╝╝╠╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗|                                                                                                                           ╕╕╕/║║║у╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ьккк                                                                                                                        ║║║J╗╗╗Є╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╝╝╝╨ккк                                                                                                                     ╗╗╗p╗╗╗№╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗╓║║║Y│││                                                                                                          ╕╕╕╝╝╝к╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ї╗╗╗н║║║\┤┤┤                                                                                          ╣╣╣╗╗╗|╝╝╝ю╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗∙╝╝╝к║║║FААА                                                                           ╣╣╣(╗╗╗в╗╗╗°╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗▐║║║k┐┐┐                                                               ╝╝╝╗╗╗У╗╗╗·╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╝╝ ║║╕ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ф╝╝╝_ААА                                                      ╗╗╗K╗╗╗ъ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╝╝ ╣┼╚ ╡╨╓ ▒┌ф мфё ичЎ еы√ вю  кО^ м}C лБK мДT оОg ▒Ъ~ │лЭ ╣╣╖ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ║║║╔║║║0                                             ╠╠╠╗╗╗П╗╗╗■╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ║╟╩ ╢┘т ▒ъ∙ оя  мя  кя  ию  жю  дю  вю  йЩq лz= йy< иw; зv: еu9 дt8 гt8 зГW пЪБ ╖┤п ╗╗╗ ╗╗╗ ╗╗╗ ╝╝╝√╗╗╗xккк                                    ╞╞╞ ╝╝╝╖╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╝╝ ║╬╘ ╖шї ┤Ё  ▓Ё  ░я  оя  мя  кя  ию  жю  дю  вю  иеЕ лz= йy< иw; зv: еu9 дt8 гs7 бq6 аp6 Яo6 зЙc ╢од ╗╗╗ ╗╗╗ ╗╗╗ ╛╛╛┼┤┤┤                              ╢╢╢╜╜╜╚╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╝╠╨ ╗чє ╣Ё  ╢Ё  ┤Ё  ▓Ё  ░я  оя  мя  кя  ию  жю  дю  вю  з▒Ш лz= йy< иw; зv: еu9 дt8 гs7 бq6 аp6 Яo5 Эn4 Ьl4 еЖa ╖▓м ╗╗╗ ╗╗╗ ┐┐┐ц║║║%                        ААА╜╜╜╞╗╗╗ ╗╗╗ ╗╗╗ ╗┐└ ╛▀щ ╜ё  ╗ё  ╣Ё  ╢Ё  ┤Ё  ▓Ё  ░я  оя  мя  кя  ию  жю  дю  вю  е╝л лz= йy< иw; зv: еu9 дt8 гs7 бq6 аp6 Яo5 Эn4 Ьl3 Ъk2 Ыo: м▓к ╗╗╗ ╗╗╗ ╛╛╛Ї╛╛╛'                     └└└в╗╗╗ ╗╗╗ ╗╗╗ ╜┼╟ └ъЎ ┐ё  ╜ё  ╗ё  ╣Ё  ╢Ё  ┤Ё  ▓Ё  ░я  оя  мя  кя  ию  жю  дю  вю  д╞╛ лz= йy< иw; зv: еu9 дt8 гs7 бq6 аp6 Яo5 Эn4 Ьl3 Ъk2 Щj1 ТНk Э╥▌ ╗╗╗ ╗╗╗ ╛╛╛ё┐┐┐               ───_╗╗╗ ╗╗╗ ╗╗╗ ╜┼╚ ├я√ ┴ё  ┐ё  ╜ё  ╗ё  ╣Ё  ╢Ё  ┤Ё  ▓Ё  ░я  оя  мя  кя  ию  жю  дю  вю  г╤╤ лz= йy< иw; зv: еu9 дt8 гs7 бq6 аp6 Яo5 Эn4 Ьl3 Ъk2 Щj1 УВY ~щ  Ь╤▄ ╗╗╗ ╗╗╗ └└└ф┐┐┐         ┐┐┐┐┐┐Ў╗╗╗ ╗╗╗ ╝┴├ ─э∙ ├Є  ┴ё  ┐ё  ╜ё  ╗ё  ╣Ё  ╢Ё  ┤Ё  ▓Ё  ░я  оя  мя  кя  ию  жю  дю  вю  в▌у лz= йy< иw; зv: еu9 дt8 гs7 бq6 аp6 Яo5 Эn4 Ьl3 Ъk2 Щj1 Х~Q ~щ  }щ  ж╔╨ ╗╗╗ ╗╗╗ ┼┼┼Э         ───н╗╗╗ ╗╗╗ ╗╝╝ ┼чё ┼Є  ├Є  ┴ё  ┐ё  ╜ё  ╗ё  ╣Ё  ╢Ё  ┤Ё  ▓Ё  ░я  оя  мя  кя  ию  жю  дю  вю  в▀ш лz= йy< иw; зv: еu9 дt8 гs7 бq6 аp6 Яo5 Эn4 Ьl3 Ъk2 Щj1 Х}P ~щ  |щ  ц· ╕┐└ ╗╗╗ ╝╝╝■╜╜╜6   ╠╠╠╜╜╜¤╗╗╗ ╗╗╗ ┴╥╫ ╟Є  ┼Є  ├Є  ┴ё  ┐ё  ╛╒╠ ╜┬л ╗└к ╕┐к ╖┐й ╡┐й │╜й ▒╜и ░╝и п╗з н╗з л║ж й╣ж з╣е ж▒Щ лz= йy< иw; зv: еu9 дt8 гs7 бq6 аp6 Яo5 Эn4 Ьl3 Ъk2 Щj1 УВY ~щ  |щ  zщ  Ч╘р ╗╗╗ ╗╗╗ ┬┬┬╤   ╔╔╔Й╗╗╗ ╗╗╗ ╗╝╝ ╚ь° ╟Є  ┼Є  ├Є  ┴ё  ┐ё  ┐┤У ╛ЛJ ╝КI ╗ИH ║ЗG ╕ЖG ╖ЕF ╡ДE ┤ВD │БC ▒АB ░A п}@ н|? м{> лz= йy< иw; зv: еu9 дt8 гs7 бq6 аp6 Яo5 Эn4 Ьl3 Ъk2 Щj1 ТИb ~щ  |щ  zщ  }ц· ╣╝╜ ╗╗╗ ╗╗╗ ╗╗╗-───▐╗╗╗ ╗╗╗ └╠╨ ╔Є  ╟Є  ┼Є  ├Є  ┴ё  ┐ё  ┐┤У ╛ЛJ ╝КI ╗ИH ║ЗG ╕ЖG ╖ЕF ╡ДE ┤ВD │БC ▒АB ░A п}@ н|? м{> лz= йy< иw; зv: еu9 дt8 гs7 бq6 аp6 Яo5 Эn4 Ьl3 Ъk2 Щj1 СРo ~щ  |щ  zщ  xщ  ж╔╨ ╗╗╗ ╗╗╗ ┐┐┐М┴┴┴√╗╗╗ ╗╗╗ ─┌с ╔Є  ╟Є  ┼Є  ├Є  ┴ё  ┐ё  ┐┤У ╛ЛJ ╝КI ╗ИH ║ЗG ╕ЖG ╖ЕF ╡ДE ┤ВD │БC ▒АB ░A п}@ н|? м{> лz= йy< иw; зv: еu9 дt8 гs7 бq6 аp6 Яo5 Эn4 Ьl3 Ъk2 Щj1 ПЮД ~щ  |щ  zщ  xщ  Х╙р ╗╗╗ ╗╗╗ ┴┴┴╥╜╜╜ ╗╗╗ ╗╗╗ ╟хю ╔Є  ╟Є  ┼Є  ├Є  ┴ё  ┐ё  ┐┤У ╛ЛJ ╝КI ╗ИH ║ИI ╡╡Щ ▒╙╧ ░╟║ │С^ │БC ▒АB ░A п}@ н|? м{> лz= йy< иw; зv: еu9 дt8 гs7 бq6 аp6 Яo5 Эn4 Ьl3 Ъk2 Щj1 МйШ ~щ  |щ  zщ  xщ  К█ы ╗╗╗ ╗╗╗ ╜╜╜Ї╗╗╗ ╗╗╗ ╗╗╗ ╚щє ╔Є  ╟Є  ┼Є  ├Є  ┴ё  ┐ё  ┐┤У ╛ЛJ ╝КI ╗ИH ╖╛з ▓Ё  ░я  оя  нхэ ▓ИP ▒АB ░A п}@ н|? м{> лz= йy< иw; зv: еu9 дt8 гs7 бq6 аp6 Яo5 Эn4 Ьl3 Ъk2 Щj1 Й╖н ~щ  |щ  zщ  xщ  Г▀Є ╗╗╗ ╗╗╗ ╗╗╗■╗╗╗ ╗╗╗ ╗╗╗ ╚шЄ ╔Є  ╟Є  ┼Є  ├Є  ┴ё  ┐ё  ┐╢Ц ╛ЛJ ╝КI ╗ИH ┤хэ ▓Ё  ░я  оя  мя  ░зЕ ▒АB ░A п}@ н|? м{> лz= йy< иw; зv: еu9 дt8 гs7 бq6 аp6 Яo5 Эn4 Ьl3 Ъk2 Щj1 З─┬ ~щ  |щ  zщ  xщ  Г▀Є ╗╗╗ ╗╗╗ ╗╗╗■╝╝╝ ╗╗╗ ╗╗╗ ╟хю ╔Є  ╟Є  ┼Є  ├Є  ┴ё  ┐ё  ╛╗Я ╛ЛJ ╝КI ╗ИH ╡фъ ▓Ё  ░я  оя  мя  ░жВ ▒АB ░A п}@ н|? м{> лz= йy< иw; зv: еu9 дt8 гs7 бq6 аp6 Яo5 Эn4 Ьl3 Ъk2 Щj1 Д╒▀ ~щ  |щ  zщ  xщ  Ж▌ю ╗╗╗ ╗╗╗ ╝╝╝°╛╛╛ў╗╗╗ ╗╗╗ ┼▌ф ╔Є  ╟Є  ┼Є  ├Є  ┴ё  ┐ё  ╛╔╣ ╛ЛJ ╝КI ╗ИH ╖╕Э ▓Ё  ░я  оя  нрц │ЖL ▒АB ░A п}@ н|? м{> лz= йy< иw; зv: еu9 дt8 гs7 бq6 аp6 Яo5 Эn4 Ьl3 Ъk2 Ц~Q Аъ  ~щ  |щ  zщ  xщ  Р╫х ╗╗╗ ╗╗╗ ╝╝╝▌└└└┘╗╗╗ ╗╗╗ ┴╨╒ ╔Є  ╟Є  ┼Є  ├Є  ┴ё  ┐ё  ╜цы ╛МK ╝КI ╗ИH ║ЗG ╢нЙ ▓╩└ ▓┐л ┤МU │БC ▒АB ░A п}@ н|? м{> лz= йy< иw; зv: еu9 дt8 гs7 бq6 аp6 Яo5 Эn4 Ьl3 Ъk2 Л╝╡ Аъ  ~щ  |щ  zщ  xщ  Э╬╪ ╗╗╗ ╗╗╗ ╗╗╗│└└└Й╗╗╗ ╗╗╗ ╝┐└ ╔ё■ ╟Є  ┼Є  ├Є  ┴ё  ┐ё  ╜ё  ╜┤У ╝КI ╗ИH ║ЗG ╕ЖG ╖ЕF ╡ДE ┤ВD │БC ▒АB ░A п}@ н|? м{> лz= йy< иw; зv: еu9 дt8 гs7 бq6 аp6 Яo5 Эn4 Ьl3 УЦw Гщ■ Аъ  ~щ  |щ  zщ  xщ  │├╞ ╗╗╗ ╗╗╗ ╝╝╝y╣╣╣,╗╗╗ ╗╗╗ ╗╗╗ ┼▀ш ╟Є  ┼Є  ├Є  ┴ё  ┐ё  ╜ё  ╗ю· ╗мЖ ╗ИH ║ЗG ╕ЖG ╖ЕF ╡ДE ┤ВD │БC ▒АB ░A п}@ н|? м{> лz= йy< иw; зv: еu9 дt8 гs7 бq6 аp6 Яo5 Эn4 УЭВ Жч· Гъ  Аъ  ~щ  |щ  zщ  З▀Ё ╗╗╗ ╗╗╗ ╗╗╗ ╜╜╜2   ╝╝╝╨╗╗╗ ╗╗╗ ╜┴├ ╟Ё¤ ┼Є  ├Є  ┴ё  ┐ё  ╜ё  ╗ё  ╣Ё  ╖╤╔ ╕гx ╕ЗI ╖ЕF ╡ДE ┤ВD │БC ▒АB ░A п}@ н|? м{> лz= йy< иw; зv: еu9 дt8 гs7 бq6 аp6 ЬДY П┼┬ Зъ  Еъ  Гъ  Аъ  ~щ  |щ  zщ  н╚╬ ╗╗╗ ╗╗╗ ║║║╘      ║║║Q╗╗╗ ╗╗╗ ╗╗╗ └╨╘ ┼Є  ├Є  ┴ё  ┐ё  ╜ё  ╗ё  ╣Ё  ╢Ё  ┤Ё  │чя ▓╬┼ ▓╢Ь ▒еА ▓Цg ▒ЗN ░A п}@ н|? м{> лz= йy< иw; жx= дГP аСg ЭЯА Ш▒а С╤╘ Лы  Йы  Зъ  Еъ  Гъ  Аъ  ~щ  |щ  У╫х ╗╗╗ ╗╗╗ ╗╗╗ ║║║]         ╝╝╝╜╗╗╗ ╗╗╗ ╗╗╗ └╫▌ ├Є  ┴ё  ┐ё  ╜ё  ╗ё  ╣Ё  ╢Ё  ┤Ё  ▓Ё  ░я  оя  мя  кя  ию  жь№ ецЄ дсщ в▌х а▌х ЬуЁ ЪхЄ Чы■ Хь  Уь  Сь  Пы  Ны  Лы  Йы  Зъ  Еъ  Гъ  Аъ  ~щ  М▐ю ╗╜╛ ╗╗╗ ╗╗╗ ╗╗╗╦ккк         ┐┐┐╗╗╗щ╗╗╗ ╗╗╗ ╗╗╗ ╛╧╘ ┴я№ ┐ё  ╜ё  ╗ё  ╣Ё  ╢Ё  ┤Ё  ▓Ё  ░я  оя  мя  кя  ию  жю  дю  вю  аэ  Юэ  Ыэ  Щэ  Чь  Хь  Уь  Сь  Пы  Ны  Лы  Йы  Зъ  Еъ  Гъ  Аъ  Х╪ц ╗╜╛ ╗╗╗ ╗╗╗ ╗╗╗є╗╗╗)               ╜╜╜2╗╗╗Ё╗╗╗ ╗╗╗ ╗╗╗ ╝┴├ ╜▌ц ╜Ё■ ╗ё  ╣Ё  ╢Ё  ┤Ё  ▓Ё  ░я  оя  мя  кя  ию  жю  дю  вю  аэ  Юэ  Ыэ  Щэ  Чь  Хь  Уь  Сь  Пы  Ны  Лы  Йы  Зъ  Еъ  Луї й╩╨ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗∙║║║F                     ╣╣╣,╗╗╗▐╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗┬─ ╗╪▀ ╣щЎ ╢Ё  ┤Ё  ▓Ё  ░я  оя  мя  кя  ию  жю  дю  вю  аэ  Юэ  Ыэ  Щэ  Чь  Хь  Уь  Сь  Пы  Ны  Лы  Кщ¤ Ч▄ы й╩╤ ╗╝╝ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗э╗╗╗@                           ┐┐┐╗╗╗Э╗╗╗■╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ║─╟ ╣╥╪ ╢▄х ▓тю пъ° мя  кя  ию  жю  дю  вю  аэ  Юэ  Ыэ  Щэ  Чь  Хь  Фы¤ Шує Э█щ г╘▀ м╔╧ ╣╛┐ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗╣╜╜╜                                    ╣╣╣7║║║╢╗╗╗■╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ║╜╛ ╕─╟ ╕╞╔ ╡╚═ ╡╔╬ │╠╥ ▓╠╥ ▓╦╤ ▓╚═ ┤╞╩ ╡─╚ ╖┴├ ╗╝╝ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╝╝╝╨╗╗╗O                                                ╝╝╝&╗╗╗З╗╗╗█╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ъ╗╗╗Э╗╗╗<                                                               ╕╕╕$║║║d╗╗╗Э╛╛╛┘╝╝╝°╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╗ ╗╗╝√╗╗╗█║║║з╝╝╝r╣╣╣7ААА                                                                                    ╝╝╝╕╕╕/╜╜╜F├├├k┼┼┼Е╚╚╚М╟╟╟У╞╞╞Ш╞╞╞Ж┼┼┼╜╜╜d╜╜╜I╜╜╜6┐┐┐ААА                                                                                                                                                                                                                                                                                                                                                         у     З         №    °    Ё?    р?    └?    А   ■   °?  р  А   №°?Ёрр└ААААА└рЁ°■? А  Ё   А              ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1722935059.140705 guidata-3.6.2/doc/autodoc/0000755000175100001770000000000014654363423014762 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/autodoc/autodoc_example.py0000644000175100001770000000611714654363416020514 0ustar00runnerdocker# -*- coding: utf-8 -*- import atexit import datetime import shutil import tempfile import guidata.dataset as gds # Creating temporary files and registering cleanup functions TEMPDIR = tempfile.mkdtemp(prefix="test_") atexit.register(shutil.rmtree, TEMPDIR) FILE_ETA = tempfile.NamedTemporaryFile(suffix=".eta", dir=TEMPDIR) atexit.register(FILE_ETA.close) FILE_CSV = tempfile.NamedTemporaryFile(suffix=".csv", dir=TEMPDIR) atexit.register(FILE_CSV.close) class AutodocExampleParam1(gds.DataSet): """Example of a complete dataset with all possible items. Used as an autodoc example.""" dir = gds.DirectoryItem("Directory", TEMPDIR) a = gds.FloatItem("Parameter #1", default=2.3) b = gds.IntItem("Parameter #2", min=0, max=10, default=5) c = gds.StringItem("Parameter #3", default="default value") type = gds.ChoiceItem("Processing algorithm", ("type 1", "type 2", "type 3")) fname = gds.FileOpenItem("Open file", ("csv", "eta"), FILE_CSV.name) fnames = gds.FilesOpenItem("Open files", "csv", FILE_CSV.name) fname_s = gds.FileSaveItem("Save file", "eta", FILE_ETA.name) string = gds.StringItem("String") text = gds.TextItem("Text") float_slider = gds.FloatItem( "Float (with slider)", default=0.5, min=0, max=1, step=0.01, slider=True ) integer = gds.IntItem("Integer", default=5, min=3, max=16, slider=True).set_pos( col=1 ) dtime = gds.DateTimeItem("Date/time", default=datetime.datetime(2010, 10, 10)) date = gds.DateItem("Date", default=datetime.date(2010, 10, 10)).set_pos(col=1) bool1 = gds.BoolItem("Boolean option without label") bool2 = gds.BoolItem("Boolean option with label", "Label") _bg = gds.BeginGroup("A sub group") color = gds.ColorItem("Color", default="red") choice = gds.ChoiceItem( "Single choice 1", [ ("16", "first choice"), ("32", "second choice"), ("64", "third choice"), (128, "fourth choice"), ], ) mchoice2 = gds.ImageChoiceItem( "Single choice 2", [ ("rect", "first choice", "gif.png"), ("ell", "second choice", "txt.png"), ("qcq", "third choice", "file.png"), ], ) _eg = gds.EndGroup("A sub group") floatarray = gds.FloatArrayItem("Float array", format=" %.2e ").set_pos(col=1) mchoice3 = gds.MultipleChoiceItem( "MC type 1", [str(i) for i in range(12)] ).horizontal(4) mchoice1 = ( gds.MultipleChoiceItem( "MC type 2", ["first choice", "second choice", "third choice"] ) .vertical(1) .set_pos(col=1) ) dictionary = gds.DictItem( "Dictionary", help="This is a dictionary", ) def doc_test(self, a: int, b: float, c: str) -> str: """Test method for autodoc. Args: a: first parameter. b: second parameter. c: third parameter. Returns: Concatenation of c and (a + b). """ return c + str(a + b) class AutodocExampleParam2(AutodocExampleParam1): pass ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/autodoc/index.rst0000644000175100001770000000542214654363416016630 0ustar00runnerdocker:tocdepth: 3 Sphinx autodoc extension ======================== Extension --------- The :mod:`guidata` library provides a Sphinx extension to automatically document data set classes (:py:class:`guidata.dataset.datatypes.DataSet`). This extension is based on the Sphinx autodoc extension. Three directives are provided: * :code:`.. autodataset_create:: [module.dataset].create` to document the :code:`create()` classmethod of a :code:`DataSet` using its :code:`DataItem`. * :code:`.. datasetnote:: [module.dataset] [n]` to display a note on how to instanciate a dataset. Optional parameter :code:`n` gives the number of items to show. * :code:`.. autodataset:: [module.dataset]` used to document a dataset class. It is derived from the :code:`.. autoclass::` directive and therefore has the same options. By default, it will document a dataset without its constructor signature but will document its attributes and the :code:`create()` class method using the :code:`autodataset_create` directive. Several additional options are available to more finely tune the documentation (see examples below). Example dataset --------------- .. literalinclude:: autodoc_example.py Generated documentation ----------------------- Basic usage ~~~~~~~~~~~ In most cases, the :code:`.. autodataset::` directive should be sufficient to document a dataset. However, it might be useful to display examples on how to instanciate the given dataset. This can be done using the :code:`:shownote:` option (or the :code:`.. datasetnote::` directive). .. code-block:: rst .. autodataset:: autodoc_example.AutodocExampleParam1 .. autodataset:: autodoc_example.AutodocExampleParam1 :shownote: The second example line would result in the following documentation: .. autodataset:: autodoc_example.AutodocExampleParam1 :shownote: Advanced usage ~~~~~~~~~~~~~~ The :code:`.. autodataset::` directive behavior can be modified using all :code:`.. autoclass::` options, as well as the the following ones: * :code:`:showsig:` to show the constructor signature * :code:`:hideattr:` to hide the dataset attributes * :code:`:shownote: [n]` to add a note on how to instanciate the dataset with the first :code:`n` items. If :code:`n` is not provided, all items will be shown. * :code:`:hidecreate:` to hide the :code:`create()` method documentation which is shown by default. The following reST example shows how these options can be used. .. code-block:: rst .. autodataset:: autodoc_example.AutodocExampleParam2 :showsig: :hideattr: :hidecreate: :shownote: 5 :members: .. autodataset:: autodoc_example.AutodocExampleParam2 :showsig: :hideattr: :hidecreate: :shownote: 5 :members:././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/basic_example.py0000644000175100001770000000126114654363416016474 0ustar00runnerdocker# -*- coding: utf-8 -*- import guidata import guidata.dataset as gds # Note: the following line is not required if a QApplication has already been created _app = guidata.qapplication() class Processing(gds.DataSet): """Example""" a = gds.FloatItem("Parameter #1", default=2.3) b = gds.IntItem("Parameter #2", min=0, max=10, default=5) type = gds.ChoiceItem("Processing algorithm", ("type 1", "type 2", "type 3")) param = Processing() param.edit() print(param) # Showing param contents param.b = 4 # Modifying item value param.view() # Alternative way for creating a DataSet instance: param = Processing.create(a=7.323, b=4) print(param) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/changelog.rst0000644000175100001770000000007514654363416016011 0ustar00runnerdocker.. include:: ../CHANGELOG.md :parser: myst_parser.sphinx_ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/conf.py0000644000175100001770000000313414654363416014626 0ustar00runnerdocker# -*- coding: utf-8 -*- import os import sys sys.path.insert(0, os.path.abspath("..")) sys.path.insert(0, os.path.abspath("autodoc")) import guidata # noqa: E402 creator = "Pierre Raybaut" project = "guidata" copyright = "2009 CEA, " + creator version = ".".join(guidata.__version__.split(".")[:2]) release = guidata.__version__ extensions = [ "sphinx.ext.autodoc", "myst_parser", "sphinx.ext.intersphinx", "sphinx.ext.doctest", "sphinx_copybutton", "sphinx.ext.napoleon", "sphinx_qt_documentation", "guidata.dataset.autodoc", ] if "htmlhelp" in sys.argv: extensions += ["sphinx.ext.imgmath"] else: extensions += ["sphinx.ext.mathjax"] templates_path = ["_templates"] source_suffix = ".rst" master_doc = "index" exclude_trees = [] pygments_style = "sphinx" modindex_common_prefix = ["guidata."] autodoc_member_order = "bysource" intersphinx_mapping = { "python": ("https://docs.python.org/3", None), "numpy": ("https://numpy.org/doc/stable/", None), "h5py": ("https://docs.h5py.org/en/stable/", None), } # nitpicky = True # Uncomment to warn about all broken links if "htmlhelp" in sys.argv: html_theme = "classic" else: html_theme = "python_docs_theme" html_title = "%s %s Manual" % (project, version) html_short_title = "%s Manual" % project html_logo = "images/guidata-vertical.png" html_favicon = "_static/favicon.ico" html_static_path = ["_static"] html_use_modindex = True htmlhelp_basename = "guidata" latex_documents = [ ("index", "guidata.tex", "guidata Manual", creator, "manual"), ] ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1722935059.140705 guidata-3.6.2/doc/dev/0000755000175100001770000000000014654363423014102 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/dev/contribute.rst0000644000175100001770000000755314654363416017026 0ustar00runnerdockerHow to contribute ----------------- Coding guidelines ^^^^^^^^^^^^^^^^^ In general, we try to follow the standard Python coding guidelines, which cover all the important coding aspects (docstrings, comments, naming conventions, import statements, ...) as described here: * `Style Guide for Python Code `_ The easiest way to check that your code is following those guidelines is to run `pylint` (a note greater than 9/10 seems to be a reasonable goal). Moreover, the following guidelines should be followed: * Write docstrings for all classes, methods and functions. The docstrings should follow the `Google style `_. * Add typing annotations for all functions and methods. The annotations should use the future syntax (``from __future__ import annotations``) and the ``if TYPE_CHECKING`` pattern to avoid circular imports (see `PEP 484 `_). .. note:: To ensure that types are properly referenced by ``sphinx`` in the documentation, you may need to import the individual types for Qt (e.g. ``from qtpy.QtCore import QRectF``) instead of importing the whole module (e.g. ``from qtpy import QtCore as QC``): this is a limitation of ``sphinx-qt-documentation`` extension. * Try to keep the code as simple as possible. If you have to write a complex piece of code, try to split it into several functions or classes. * Add as many comments as possible. The code should be self-explanatory, but it is always useful to add some comments to explain the general idea of the code, or to explain some tricky parts. * Do not use ``from module import *`` statements, even in the ``__init__`` module of a package. * Avoid using mixins (multiple inheritance) when possible. It is often possible to use composition instead of inheritance. * Avoid using ``__getattr__`` and ``__setattr__`` methods. They are often used to implement lazy initialization, but this can be done in a more explicit way. Submitting patches ^^^^^^^^^^^^^^^^^^ Check-list ~~~~~~~~~~ Before submitting a patch, please check the following points: * The code follows the coding guidelines described above. * Build the documentation and check that it is correctly generated. *No warning should be displayed.* * Run pylint on the code and check that there is no error: .. code-block:: bash pylint --disable=fixme,C,R,W guidata * Run the tests and check that they all pass: .. code-block:: bash pytest Pull request ~~~~~~~~~~~~ If you want to contribute to the project, you can submit a patch. The recommended way to do this is to fork the project on GitHub, create a branch for your modifications and then send a pull request. The pull request will be reviewed and merged if it is accepted. Setting up development environment ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If you want to contribute to the project, you will probably want to set up a development environment. The easiest way to do this is to use `virtualenv `_ and `pip `_. Some parameters are required to be set before using the project from within Visual Studio Code (used in `launch.json` and `tasks.json`). These are: * ``PPSTACK_PYTHONEXE``: The path to the Python interpreter to use. This is used to launch the application from within Visual Studio Code. If not set, the default Python interpreter will be used. * `.env` file: This file is used to set environment variables for the application. It is used to set the ``PYTHONPATH`` environment variable to the root of the project. This is required to be able to import the project modules from within Visual Studio Code. To create this file, copy the ``.env.template`` file to ``.env`` (and eventually add your own paths). ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/dev/howto.rst0000644000175100001770000000154114654363416015777 0ustar00runnerdockerHow to build, test and deploy ----------------------------- Build instructions ^^^^^^^^^^^^^^^^^^ To build the package, you need to run the following command:: python -m build It should generate a source package (``.tar.gz`` file) and a Wheel package (``.whl`` file) in the `dist` directory. Running unittests ^^^^^^^^^^^^^^^^^ To run the unittests, you need: * Python * pytest * coverage (optional) Then run the following command:: pytest To run test with coverage support, use the following command:: pytest -v --cov --cov-report=html guidata Code formatting ^^^^^^^^^^^^^^^ The code is formatted with `ruff `_. If you are using `Visual Studio Code `_, the formatting is done automatically when you save a file, thanks to the project settings in the `.vscode` directory. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/dev/index.rst0000644000175100001770000000017514654363416015750 0ustar00runnerdockerDevelopment =========== .. toctree:: :maxdepth: 2 :caption: Contents: contribute howto v2_to_v3 platform ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/dev/platform.rst0000644000175100001770000000745514654363416016475 0ustar00runnerdockerReference test platforms ------------------------ About requirements ^^^^^^^^^^^^^^^^^^ The ``requirements.txt`` mentioned in the following sections is a text file which contains the list of all the Python packages required for building up the projet environment. It is used by the ``pip`` command to install all the dependencies. The ``requirements.txt`` file is generated automatically by the ``toml-to-requirements`` tool. It is based on the ``pyproject.toml`` file which is the reference file for the project dependencies. .. warning:: Please note that the generation is not systematic and the ``requirements.txt`` file may not be up-to-date. To update the ``requirements.txt`` file, you need to install the ``toml-to-requirements`` and execute the following command: .. code-block:: bash toml-to-req --toml-file .\pyproject.toml --include-optional Microsoft Windows 10 ^^^^^^^^^^^^^^^^^^^^ First, install the latest version of Python 3.10 from the WinPython project. .. note:: At the time of writing, the latest version is 3.10.11.1 which can be download from `here `_. Then install all the requirements using the following command from the WinPython command prompt: .. code-block:: bash pip install -r requirements.txt That's it, you can now run the tests using the following command: .. code-block:: bash pytest If you want to rely on Visual Studio Code for editing and take advantage of the project settings and tasks, you will need to set the following environment variable: .. code-block:: bash set PPSTACK_PYTHONEXE=C:\WPy64-31110\python-3.11.1.amd64\python.exe CentOS Stream 8.8 ^^^^^^^^^^^^^^^^^ .. note:: The following instructions have been tested on CentOS Stream which is the reference platform for the project. However, they should work on any other Linux distribution relying on the ``yum`` package manager. As for the other distributions, you may need to adapt the instructions to your specific environment (e.g. use ``apt-get`` instead of ``yum``). First, install the prerequisites: .. code-block:: bash sudo yum install groupinstall "Development Tools" -y sudo yum install openssl-devel.i686 libffi-devel.i686 bzip2-devel.i686 sqlite-devel -y Check that ``gcc`` is installed and available in the ``PATH`` environment variable: .. code-block:: bash gcc --version Install OpenSSL 1.1.1: .. code-block:: bash wget https://www.openssl.org/source/openssl-1.1.1v.tar.gz tar -xvf openssl-1.1.1v.tar.gz cd openssl-1.1.1v ./config --prefix=/usr --openssldir=/etc/ssl --libdir=lib no-shared zlib-dynamic make sudo make install openssl version which openssl cd .. Install Python 3.10.13 (the latest 3.10 version at the time of writing): .. code-block:: bash wget https://www.python.org/ftp/python/3.10.13/Python-3.10.13.tgz tar -xvf Python-3.10.13.tgz cd Python-3.10.13 ./configure --enable-optimizations --with-openssl=/usr --enable-loadable-sqlite-extensions sudo make altinstall cd .. Eventually add the ``/usr/local/bin`` directory to the ``PATH`` environment variable if Python has warned you about it: .. code-block:: bash sudo echo 'pathmunge /usr/local/bin' > /etc/profile.d/py310.sh chmod +x /etc/profile.d/py310.sh . /etc/profile # or logout and login again (reload the environment variables) echo $PATH # check that /usr/local/bin is in the PATH Create a virtual environment and install the requirements: .. code-block:: bash python3.10 -m venv guidata-venv source guidata-venv/bin/activate pip install --upgrade pip pip install -r requirements.txt That's it, you can now run the tests using the following command: .. code-block:: bash pytest././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/dev/v2_to_v3.csv0000644000175100001770000000432014654363416016261 0ustar00runnerdockerVersion 2,Version 3 ``.userconfigio.BaseIOHandler``,``.io.BaseIOHandler`` ``.userconfigio.WriterMixin``,``.io.WriterMixin`` ``.userconfigio.UserConfigIOHandler``,``.io.INIHandler`` ``.userconfigio.UserConfigWriter``,``.io.INIWriter`` ``.userconfigio.UserConfigReader``,``.io.INIReader`` ``.jsonio.JSONHandler``,``.io.JSONHandler`` ``.jsonio.JSONReader``,``.io.JSONReader`` ``.jsonio.JSONWriter``,``.io.JSONWriter`` ``.hdf5io.HDF5Handler``,``.io.HDF5Handler`` ``.hdf5io.HDF5Reader``,``.io.HDF5Reader`` ``.hdf5io.HDF5Writer``,``.io.HDF5Writer`` ``.gettext_helpers``,``.utils.gettext_helpers`` ``.disthelpers``,*removed* ``.encoding``,``.utils.encoding`` ``.encoding.transcode``,*removed* ``.encoding.getfilesystemencoding``,*removed* ``.qthelpers.text_to_qcolor``,*removed* ``.utils.update_dataset``,``.dataset.update_dataset`` ``.utils.restore_dataset``,``.dataset.restore_dataset`` ``.utils.to_string``,``.utils.misc.to_string.misc`` ``.utils.decode_fs_string``,``.utils.misc.decode_fs_string.misc`` ``.utils.assert_interfaces_valid``,``.utils.misc.assert_interfaces_valid.misc`` ``.utils.get_module_path``,``.utils.misc.get_module_path.misc`` ``.utils.is_program_installed``,``.utils.misc.is_program_installed.misc`` ``.utils.run_program``,``.utils.misc.run_program.misc`` ``.utils.run_shell_command``,``.utils.misc.run_shell_command.misc`` ``.utils.getcwd_or_home``,``.utils.misc.getcwd_or_home.misc`` ``.utils.remove_backslashes``,``.utils.misc.remove_backslashes.misc`` ``.qtwidgets.RotatedLabel``,``.widgets.rotatedlabel.RotatedLabel`` ``.qtwidgets.DockableWidgetMixin``,``.widgets.dockable.DockableWidgetMixin`` ``.qtwidgets.DockableWidget``,``.widgets.dockable.DockableWidget`` ``.utils.min_equals_max``,*removed* ``.utils.pairs``,*removed* ``.utils.add_extension``,*removed* ``.utils.bind``,*removed* ``.utils.trace``,*removed* ``.utils.utf8_to_unicode``,*removed* ``.utils.unicode_to_stdout``,*removed* ``.utils.localtime_to_isodate``,*removed* ``.utils.isodate_to_localtime``,*removed* ``.utils.FormatTime``,*removed* ``.utils.Timer``,*removed* ``.utils.tic``,*removed* ``.utils.toc``,*removed* ``.utils.is_module_available``,*removed* ``.utils.get_package_data``,*removed* ``.utils.get_subpackages``,*removed* ``.utils.cythonize_all``,*removed* ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/dev/v2_to_v3.rst0000644000175100001770000000220214654363416016273 0ustar00runnerdockerMigrating from version 2 to version 3 ===================================== Version 3 is a new major version of the library which brings many new features, fixes bugs and improves the API. However, it is not fully backward compatible with the previous version. The main changes are: * New automated test suite * New documentation * `guidata.guitest`: * Added support for subpackages * New comment directive (``# guitest: show``) to add test module to test suite or to show test module in test launcher (this replaces the old ``SHOW = True`` line) * `guidata.dataset.datatypes.DataSet`: new `create` class method for concise dataset creation This section describes the steps to migrate your code from :mod:`guidata` version 2 to version 3. The following table gives the equivalence between version 2 and version 3 imports. For most of them, the change in the module path is the only difference (only the import statement have to be updated in your client code). For others, the third column of this table gives more details about the changes that may be required in your code. .. csv-table:: Compatibility table :file: v2_to_v3.csv ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/examples.rst0000644000175100001770000000510414654363416015676 0ustar00runnerdocker.. _examples: Data set examples ================= Basic example ------------- Source code : .. literalinclude:: basic_example.py .. image:: images/basic_example.png Other examples -------------- A lot of examples are available in the :mod:`guidata` test module :: from guidata import tests tests.run() The two lines above execute the `guidata test launcher` : .. image:: images/screenshots/__init__.png All :mod:`guidata` items demo ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. literalinclude:: ../guidata/tests/dataset/test_all_items.py :start-after: guitest: .. image:: images/screenshots/all_items.png All (GUI-related) :mod:`guidata` features demo ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. literalinclude:: ../guidata/tests/dataset/test_all_features.py :start-after: guitest: .. image:: images/screenshots/all_features.png Embedding guidata objects in GUI layouts ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. literalinclude:: ../guidata/tests/dataset/test_editgroupbox.py :start-after: guitest: .. image:: images/screenshots/editgroupbox.png Data item groups and group selection ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. literalinclude:: ../guidata/tests/dataset/test_bool_selector.py :start-after: guitest: .. image:: images/screenshots/bool_selector.png Activable data sets ^^^^^^^^^^^^^^^^^^^ .. literalinclude:: ../guidata/tests/dataset/test_activable_dataset.py :start-after: guitest: .. image:: images/screenshots/activable_dataset.png Data set groups ^^^^^^^^^^^^^^^ .. literalinclude:: ../guidata/tests/dataset/test_datasetgroup.py :start-after: guitest: .. image:: images/screenshots/datasetgroup.png Utilities ^^^^^^^^^ Update/restore a dataset from/to a dictionary ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. literalinclude:: ../guidata/tests/unit/test_updaterestoredataset.py :start-after: guitest: Create a dataset class from a function signature ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. literalinclude:: ../guidata/tests/unit/test_dataset_from_func.py :start-after: guitest: Create a dataset class from a function dictionary ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. literalinclude:: ../guidata/tests/unit/test_dataset_from_dict.py :start-after: guitest: Data set HDF5 serialization/deserialization ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. literalinclude:: ../guidata/tests/dataset/test_loadsave_hdf5.py :start-after: guitest: Data set JSON serialization/deserialization ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. literalinclude:: ../guidata/tests/dataset/test_loadsave_json.py :start-after: guitest:././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1722935059.1447053 guidata-3.6.2/doc/images/0000755000175100001770000000000014654363423014571 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/images/basic_example.png0000644000175100001770000001121014654363416020070 0ustar00runnerdockerЙPNG  IHDRЫkНОsRGBо╬щgAMA▒П №a pHYs├├╟oиdIDATx^э▌[lWЮЁ╧.C`0!╦ea░┴vЪxмЭм═2╗6q"qy$цБZЦ├ГГЦhмь╩▒6СW╔К4;y FС, H86 ╢Вё,ёМX█Уa│M7╛ДIАЩl0сbўЮSuк╗N╗╦n╙▌q_╛_Tq╒щъr█╕╛:зк╗■Y├├├)F(,\╕P-Q&╗{ў.fиy""CБИ4 "╥0ИH├P "MLб0::Крс├З╞| └лЫDйnJЧ$GFFpу╞ |∙╫["╛┴у1`╓Sda гПg"+ xz▐<√7╦▒b┼J╠Э;W=УИRБ╝$u(▄╛}}}}XЁ4░l╤~8oD┬ИДQ 0В└╪ИЖ1у╣D╔dppлWп=█,c*//O┌┐S∙·zzz╘╥xЄ1╣NЯ+Ц╬┼▄┘╠Ъ│Чп╧B█я╛┼КUE╚═√ ~╒v┘╧оA`╞Ся·м╠y╖я,─Ъх1■";Л*їПbMUg╒C╙B╛Ю2їлEJ[Єor╧Ю=╕sчЪЫЫqс┬466кGУKww7■йlC─`РmЄ1╣NVC5/Ю_ъ ШЛ{НэХЪкe√╢mы·<БRm;Сцхт▐╨Ў(mИсCр▌w▀UK╔п╗╗;0k╬\cТєЙ є кЮВ╦х}К9Ё=F╓ш-М▄╗Ж#~° ўwbъ┬мя╝╕yгПюў├їЖ№x№]sЯ╬┴▓e╦╘V"▒ЯS8єФ┬f;l╔┌Ь▒┌┘s0О▄[Ё╛j2ЙэЬ8И|9╗y;─НЯm5Ц╘Є\ vlыц─┐юэBЯ╧x ─M<г ╒кз░х}tН[ЙRЭ<ПРь=Л}╚`J$BTб w ┘U├\°ЗbЎSўС╖ьKм]ykW▄└┌┐э╟┌х^,э├В97ё╫пf`Ў╝\м}■y╡Е8Р▌yЩFА°р)Uэ1ёу┌5;╬^ї╜╘─│аiзгг╣╣╣j)yЕЯC░% QЮhf╧ЮНїы╫cхПrpу╓,№цєё▀ЧЯAў╡ешёп@╫╒|t■ёy№∙N╓Цар╣╡FШLЭў;┐ь.╪╞Є╛>tХв@╬√[q▓╦h}]8┘к║ ╞vЎb{°■Ю КD_фM^ЯLkЄRдє%єфрtR1С┴u(H3f╠@^^╩6l└Т%KЁэH├ўА{gc╤ттЕо├O·ПX┤hСz╞dl]t1Х=Лгв;p┼sX 6у░ч ╢X'7Ж╒(РыющC╤ўJQ╘╖╟№Ю╒( в─╞чБxq┴╫:▒Iщвоо'NЬPK╔i▌║uу┴bГ\'Юb║█gЯ}f╝▒й░░0E▐╬,п>╝ЙB▀'8иN9QH╠╖c[╡jКЛЛ144d|JТИR_Lб ╔бВ Жo╛∙F╡Q*у▌ЬЙ(Иws&вq Dдa(СЖб@DЖi Dдa(С╞xЯВЪ'"bOБИt "╥0ИH├P " CБИ4 "╥0ИH├P " CБИ4 "╥Ёm╬if╤┐_WsDж; Цзцв├PH32╞■│X-QжЫё/╜SИH├P " CБ╔ryФy Dдa(Сf╩бPСЭНьрTНv╒>m·PQ╤А~╡л■Жj4╚Н╡Wгz▄╫Оъ8~пДєEЩU5[LeОеїeс▌╨zм░Э┘ж▐SX_П▐сa ╦щ░+ХvТIїуЬ╫ЕW╓И▌┐╪║I5 ¤ "wбQ-з_К╬╚щ Ккў b.Ь¤очГч╩ЫС╫гМ█Ёa╙V╕/y!▀.cю4Vв┬<┌кгxCuиWс╝Ю82WШэ╒э¤h╬Л╟%╣N°є─N▄░пЧ.╒аX┤W╚╞HыExс┌Н╟КQ╙hnkWc#vЙп╓ў_│┐ ├╜їXo.жЖ═╟pl│ЪG K╒l8m=·║КЁKїgмШBб┐с4оwA╛5┬╪iВ=ИB╘№Ч┌Ы─ы▌*█=Р^чї·рzO╢╣╤╕k`═┐#{"в█^ь┼aыy╜;╨╝O╢п┴■ў─Окz/m√п;м'╖п┐ОpЫ<т1ё¤▄зфєъсоя5╢сЙ┤r*Є╖т$~ЖнО;╗G╦фЁс#lC0#(уL=╘QYЙЛЫwа╖m┐╪5√zЧшdў∙═ЭQь░?╖яXОыэ0║э╚sЙЭ▄6/ыўгOt▄хС█x^▒ьШ=═DыЕ┐О─╙сТ w▌ ф?UЪ8Лк '┬╣РПГЯ╚с├v|ФU╞сCЛэЬВ=КЫ▒гW╡;u│г]/√ў5ж╚G№и╫│kп6Cож5┼fX5ю_╟ЯiLAЄ$тЫ(Ї}ВГQ 6c√▐.Ї∙╘"eЬ╪╬)╪YGwб \3.Щ│уE╗Ю▌Ъ|КК5╥╟ъ L╢ЮУMзрvЯ_{QяV┴ТъcуъCД@0┌лD\XЛU╢Ю┴Y|Ї~) ╘"eЬ°Д┬Ъ¤8\VьєFюD╗▐8Ыр╜К>yЇV╧═╢оz╚m║═!CECЮєzУ▒.7ЇЯГ╫їК┘▓1NР├єїзB'┬▀z]т┐ъВ╨х╞HЧ%єЯГmЭ-└Щh{ФО°)╔4╧OI╩РРЧ))uёSТD3Ж9b/!31ИH├s iЖ╖cгp╝┼Д├"╥0ИH├P " CБИ4 "╥ЁъCЪy√э╖╒Сщ╡╫^Ss╤a(д o╜їЦZвLў·ыпO98| " CБИ4 "╥0ИH├P "═ФC!#+D┘я@-&г╛DJл№─╗4Sж▐S░▀-9S*D]ўвP╓Г0~юS(м┘зК╤дВ╜8cT~Тя╜HУЛm°Р)в6ylEaЄрК·╛ЇDй'жP╚╚ Q¤ч╨М╨mъУ▀√╪вЖ╬fЙBж ъи,П─ЩW!JЖУИДў╘╧ЬЇ6уXpш0AБY"Ы╪╬)dTЕ(┘я└╒█Ж¤йСa&(0Kd█9╗tоeЇnR0╬Vб*Xк'YMЪвЯPHє Qfx]2{j╗)qY▓аW╢и╦С╒(:├j╥49~J2═ЁSТd╟OIQ╠ Dдa(СЖч╥ o╟Fсx;6"К ЗDдa(СЖб@DЖi Dдс╒З4єё╟л9"╙╦/┐мцв├PH32╓н[зЦ(╙uwwO98| " CБИ4 "╥0ИH├PШИ╝ ["ыZ$z√DO`╩б`пФФОв┬■┐я╫;x;w╟аZLм╘|o▀Лeъ=√▌ТУбB╘Ъ¤h│ю*ЭмRс5╞hЁ°Nфф╕╤дЦ)1._╛мц╞Ыш▒йИm°`UИRG?нУlЛ╪гР╖J╖┌U&m]л·Уl╢UУRс3о═:Єjп┴ЬЬjAF║Щk─╩UvN█╨┌m?{Ё╡И6╡▄/■╙*Z5¤Я\ч┬_│▒■$│в2ИуЗъ╨╙SЗН99╪y№"Оя╠AMЗzX┘svт°E┘ЫиG∙▒cЫSp┘╙Pm9r]Зn@nхi u╓вD-S№▌┐~°б8└┤йЦ┘&Уы─*жP░WИ╥+0╔в)5┴·Л╜ї}╪e■╡Л?ю]@░.гм└фP¤Iь√j ┼кf√йB3|╞╡╔яm ╛1i╒еBп┼й╓Дcх*Г╙6Ь~NA√}X┬*Zэ^ьЁЪЕ +fE+ХG─ОZRЛ╬б!Ьо▄А╩╗╤╘жЎ°О64э>А╩Хb╛╟ЛВ#Cы 5Кu▄5"2DhlЇсАlУSч6┤тЁ`║╠Я?╡╡╡hoo╫ВA╬╦6∙Ш\'VSuФУG-нBФ°CV`ТХЪl╦k^┘БїНнтИ)KоЕWМrиъ$k8эц╤wУGь\С┌МН(Ў╫`п.eow"П╬╓k░WоТЬ╢сЇs╩Еh╛зщ5KU╠КEyv7╡Й▌]fBvWФЫэ%█ЁRо9kм/.И 7┴mї6╩^Зj5·■ЕC╝AКэЬB╝╞╔ЎmУ▄┘7┴c╠oEл▒│╩ И╘6Йы▐╔ ╬╚@ШиrU4█Hхи╪▌Д╢О┤5эЖХ ОT/├ш)S╜╪M'{0─;д╪╬)8 л╘dSqoG·W░aЬЬк:Й╡┌╪╦ шE¤·>°;#┤▌0╓vfЬўh╞╣╔·▄Uоr┌Ж╙╧i.:ЛT3С┬ОюхU╡Ё║▌Ё╓VE▐┴х░BЎ6мЖлз╟Вч (YX┴я@Р rз╡Uj*ЦчМlцШ┌^┴й║]_╫ШфI9▒├бжX╡гy╟{╪┐1B█П╠яшLl_Ю#░к;╔сЙz$h╥╩UN█p·9'`пhеN4&Tn%ИЮБь┤╬ц╛Дm%%╪/ъdд1LpНз+С+"г╛SИjЧУ├%GуъГ1╝0╖:ЩIЙ"├ ▐Б e▐з$e1┘╓нб:СO"█HРи>%┘QГЬ_а╙╪ёyЕсp─Zж┤┴OI:h╖]Є╦Ц?Ю`gО╟6ТЕqВёА"у¤╥ яз@vь)Q╠╪SH3╝Еуэ╪И(&>СЖб@DЖi Dдa(СЖб@D^ТЬ&┘ qS═СУс_,WsСё=УЫъ{$Ж┬4a(L.ЪPр[║Э=╔[Ь%Ж┬4▒Bah <у+Еф4<4╛2bєдб└s Dдa(СЖб@DЖ╤t·^+xEЗбРa╠JNб{.я█■╟)o┘ЦЙўZT?wp╩└ЫM22И ДН-█l╖loДлnc(,2 ▄^╘vб>Гючnж╝iнэЦЎН0kddЖB╞ш└▒:аЎИ¤▐Мц▌Ъ╤r>╘CРБ░▒█:Oг2гnт8Иє-тў╙й╫╡(п7ЧїЦ*ЯзzW╟kB= ╜$_X╗\?╪ц\Вo║12┼р╝Ў*PЦ\Y█┴к ╤ВC┬рy┤Ї╕░┌сч6jeZ=ИFъмb=uЁUXэ╗╤ЇK9─ёЭFЧ#°Ь·Є╘)┴╟PаР`[н°Г╧╘zС%Xнf╟▒х▌MАw└№Х╘в╩ъZм.0 ь╩АБн]ТбЬ"%° Щ┬ш┤р|°▐nЇ ╘╬ {Хїш▄╓ВНЩvВ═щў#ЗT╓Q■ лkзH >ЖB╞(GU-┬z▓K['║/i5 r+Па╓ы2нй▀╧FYm;дгF-█Ж^Гч[dз╩Щм└Е░r{FшдF >ЖBСуbг`ua═Sэ8=юB.*O;\ЩHc╞яз╓ътЛ╔Н Ф╦╥{оPY╜C>╫$=ё√;вЧ█лщИ╛▀tувж ?хМИК~ КИтВб@DЖixNaЪЁ╬KУу9Е╪Ё╬K)Жб09▐г1v "К╧)СЖб@DЖi Dдa(СЖб@DЖi Dдa(СЖб@DЖi Dd№?╦W┐Ў&S.IENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/images/guidata-banner.png0000644000175100001770000004640314654363416020171 0ustar00runnerdockerЙPNG  IHDRЇЬЙY pHYsLL╡╦уtEXtSoftwarewww.inkscape.orgЫю< IDATxЬьЭy|Tхї ?ч╬ТХ@╢░ ╚"(jEm]к╤,H]║Z█·эп╡¤ЦюЛ╒к╔═У░~нЇ+mm▒Z[¤╢╢°╒o]JБДж*а╓ кИ ╚!!{23ў№■╕3╔Э;w&ў╬Т╔Є╝_п0╔sя}Ю3wЖ{ЮsЮєЬC╠МС╬3╧<Уэїz ЙиPQФ▒╞jЪVи(JАBЕ╠<ОИF╤"RШ┘ И╚лiZF░;3{В┐wСЯИ@;3√ЩY╨LD=└╠═:Щ╣@#АFEQЩ∙DшofnЇ√¤НЛ/ю░Ы"СH$Т!НЕ▐╨╨Р█╙╙3ЧИц╤щжШ№Щ ;ЭЄ┘ЕИЪь░ЯЩ?╛╛ODя455╜¤ї╫ўдWBЙD"СдЛaе╨╫п_ЯСССqжжiч╤90є\У╙-█рЁ¤ЫЩ╖╤v┐▀ ╞вEЛ▐0|>dЙD"СX2д·╞Нg╕\оЛЩ∙c.░АзЯ╦Fн^fцн.ЧkЫв(█КЛЛЫ╙-ФD"СHТ╦РRшїїї4M╗МИJ,pZ║eвьP╟╠u~┐├т┼Л[╥-РD"СHc░+t┌╝yє╠№If╛└Yщh╥`3¤╒хr=U\\|$▌I$Й─9ГRбo▄╕ёгDЇ%"·АIщЦgб╪FDЁчТТТ╞t $СH${ Е▐╨╨0┼яў└ШХny$шЁwf~░╣╣∙┘ып┐>РnБ$ЙDЭt+tкпп/Ёmf╛А;Э┬Hв▓└п}>▀ГЛ/>Юna$ЙDIZ║B╣швЛоVе└∙.А$^║№%T/Z┤hw║ЕСH$Iк╨▄Б@рkЦ2єЇ8┼╕ КBЖЖ█еБЩ╨@ hL╓ =|╤4нк╝╝№├t #СH$ТБSшTWWў+╠ИЭрRЩ^ Щ^FЖWCЖGГ█е┴этрПf°ax▄bЇхk1 Аарk╪q а4M █Pр|~В_s├ч'°№Ксo▌>7║z<шЎ╣ф~8аЛЩяєx<лф▐vЙD"I/)Wшuuu №└╟S:P?dx9Щr│╚╔╘Рсedz╚Їъ [╟дМ┴Е:╬╨jсчF(pCАMcz А у┬№w__║║]ш *°о:{Я в`Г▌ИАQ┘Єr╣Yr▓4фfjЁ╕═ 5▄jюU▐dRъ╜╩&еl╕╓x]ДеощBYN Lч╟Ъ─╝`MCGO┌:3╤┌z═D╖╧ї^%"z.|│╝╝№ЭT"СH$Rд╨7n▄°QEQ■`N╥;7б+pFAЮЖ╤╣ЄG-юРхkV╚╩U3Їf>?x^L%k5)░:▀tЮYС╓Ц:╧Пт╬'у5сcw√\hn╧┴╔╓Q8┘: Э=)WЁ]╠\╡uы╓ RUUы tЙD"С$Гд*ЇЖЖ╖▀япpАФ-°feу cG0:ЧаРЖи ╧╥nе\═wЁЫ&,\ч! =т\Ї╡ЗНe![шX╚г&лY^ ┘┬^CЧHпDGwЪZs╤╪ЪЗ-y)s╙3єz┐▀ ╣═M"СHЖд)ЇЖЖЖ▒~┐ OJУ╥бЙм `ь╞Д╞ШQf%i~╡бЇ┬╓╟)№\│╒┼·Нъ:Зй;ч│┴ШНШШ▐Л▒/╦IЛН√@╙╟OхсpS!N╢НЖж%7 ЯЩ╕\о╧^yхХ/'╡cЙD"СDРЕ╛yєц 5M{I.SъvУ╞EуАQYК8bm&+╒╨╓пы▄ЁjeбЗмg+i^{7╦f-╟╕6t╠╩ХnЧYя╫jЄaї>{╧╫┬▀GЁ_└Нc═∙8xr,Z:rСD║|╜┤┤ЇП╔ьT"╩!ЁCS╖ккЩiG2LH8Xm╙жMхDЇ8АQIРАnНOOШ2p╗BК/°Oп┬"Г▓ *6г"%│╒mTк╘wIШы<╪UD0\,k╫0<бoНЮ ЖY°Ж7&#№=▀Я2╕щM2Ш╧ыU▄╘з╘╔рХ╛z▄~L.<Ж╔Е╟╨▄> ЮШАcз вЛ`ЯLП╓╫╫O)))╣;с▐$ЙDbIB ╜ооюkDЇЫD√ QРLЫиаp4@angtї)┬0Еl╘hERф&+╡w ╖ьC▌Ж┬┤Ыa║6┬r╢Р█J^у√! En~пцў&oМЙ@я&╖╛A┘3cLN ╞ф┤а╜+√OсЁ╔qЙ&┼!f■i}}}AII╔Йt$СH$kтОИклл√АРe>:Ч░pЮВє╬paьї*_У" Y╧amaZ╩╘nP■a}їЪё}ЧЖ~ ЭG╘зп┘tФ▒Y┘ЫГт╪№ЛA^єX у{е╛г°╞ўe█ гWА(№╜Шо═╔ь─╝)яуТyпar┴╤╛['╠|√жMЫ~ЦX/ЙD"▒".ЕnPц ЕHge╬Щу┬є]╚eVtlRТ7и═╩>м▌тШY▒З╞Вй-ф.;?д-_Tw:·┌Н2E╝уdp2Дu`╕ЖMуpdwaБs&сМўнў}RЯТ╖xЮ╠Ы║╬┌О╤┘m▓█ЗИ~\___СP'ЙD"Й└▒u]WWw5А╡H@Щ3&╗0}вEщ╙F]=М╢FПЁ Vkяk╚вeУо2нqЗEГ█═ЦrД╦>╠?ОH-┘O╗y═=b¤▌▄ПU_╤О[Мm~Пя%Ие╒k └э ├▌Г,or2:z'3г▓█q■щ█qа▒╗ПЬЖА▀╬Dfоопп▀_RRЄ√╕:РH$IО·ц═Ы╧Ё╪cЮЫMШ?╙Н╝lъНS;tB├┴cZ;МgЪ╫луoIк╚Єvб( 8жН=Ч+RАйугpT3▐┌?нЭёE─3єAе■П$Л,СH$#█VvCC├XM╙■@▄{ЪжNpс┬3=╚╦╤Зmi╫Ёт█╝єБYЩK Э=Щ╪st*╢╛{>ОЭ*ьЭgegtрВ┘obJссx╗Ў0єc6l(JЦмЙD2Т▒е╨ЕК▀я└╘xq)└Y│<Ш;▌гЧe╞▒&╞л аглЯ .╔аа╟я╞[√╬└▐cS{]° i8cЄnЬ1y╖╛+┴9у▌nўгBИ┤UХСH$ТсВнщ%Ч\rАEё рї╬╧└─ВаЧЮБц6╞О=h2╙ўРc╧╤╙pЁd╨иоуO)<Д│O█L┴ы f╛2°¤ТH$IЇл╨ыыычиКзєм ┬є3t{08╦пoэЇ╓Ч =v>э▌┘}█эМ}ч╬x .%оYЪZ__ЯЄB>ЙD2ЬЙ╖n▌:╫Ш1c~GDN;╬╩ ,ЬЧЙ╠МЁ=█√√╤уУ╩|(гiД▌ЗзуЬщ;╨хф6c┴┤╖▒}▀Y`gE_2бoГ,Жuш┐dДQ]]} 3Ы'yTU}"-I$CАШ ╜аары╠|б╙N3╝ДЕє3Сщ5fL#hZОb^ЫУУГ┬┬Bx╜W╟[О▀яGSSNЭ:їЬнЕшш╔F╢╖г/√А▒y'1╩╗╪ёспбК┬▓╒gM▌Йw]_└Q■в[╫п_ т┼Л╗]"С QВ1▀15_акъ╔┤4D░┤╨▌nўс░j^ОВ╙зxM┼RА╛В&А╧▌┬Фkцс┤╖╖c√Ўэ╜╩:::Ё·ыпЗ╡еЪXЯЛ/рОТ{Юn╠*┌эt╚╔n╖√╦N/Т ;╪c№!"∙0!0s>АЩ╞п╫wЖ╥СBДщ┤m█╢,fvdЭ+ a■╠L▌]nоQn┤▐d№▓mvя▐Н@ 2р└чєa╧Ю=Ш7╧Y╨YJЙQ▓u╥ШC8trNuф9щёG~Ы ╤$CUU┐Шn$ТбFДЕ▐╓╓vАB'ЭЬVфAnО ╓╒╜ Mry▄>ЯНННQП=z┌`╔╩*■сЩ VА╣УwE┤╟ьТш╠║║║KУ&гD"СМ",t"2п[─$├KШ>)#°╝6Фe├┌*0 ╓∙■ГзЁ·О├8z╝ 'Ъ:pке ┘YМ╔╦┬╕┬l╠Я3g═╖+5ЩFП7╢гц┐ iyl┌ф╤╕їЫЧ╪ъз┐╕@ АЎЎЎ]KПJ┤"t@ячЯЧ╒Вqy'p╝eЬУЮ┐рЕДхУH$ТBШB▀╕qуКв\рдГЩS2рR`(╙i*m*йЦ"ы\╙■╣·ы[xootл6D■ш,\[:_·ЇМХЩTY{ъm№k√A╦cmэ=╢√ё∙bDщщ▒▀▀А`о┌╞мaцД=N·з╖m█ЎнЛ.║и3Y"J$╔p&╠TUх N.╬ЁК =a)@#Mrг;6╣ь?t ▀╕эпи■yГ-eMз:ёЗ }Я ╓_P ┬Юд╔rЁH+Ю№√╬дЇeg{┌@oaЛIDь^'Ю0*│ ∙9MNz╒╤╤W¤ЙD"ЙШ}╧ЯwrёЇIP\&eNЖ▀CДЎ''Q╜ї╬Q|у'┼╬ўО╟u}K[7*юй╟C~-aY4fм\єOtv∙юk╚╤ыЩ▒:`8N└╘┬ЭЎ■┘ДdУH$ТDпB▀╕qу ╢Cз] бhЬ╫ф^чpе▌{╠░╢Ю>8╨М[j6ае-▒э[╠└|~ъэД·╣я┴ё┌█q╫┌Д}╞╞Ї)·аg▄шуЁ║э/0єU▓┤кD"С╪гўaщr╣;╣pBбnW?oС┘_╞ч аЄЮ═hMPЩ∙┼├/┼mщ?°╪k°є╙ЙMЖK.ИXj!╞чu╥ы╪Л/╛°ь%УH$ТAпBgц2'НїЇ¤AVюtГщ╬╔[G╖№ц@├]┐|ЪГui@├к_<П▀■щ╒д╩2$▒№l Йzw;0╞П>ц░k*MP:ЙD"г▄ээйрї╞М2\J∙iХXж7(О╢╥█┌{Ё╚█cЮC\U<_№фLЯ2НMЭ°╟╢╜xш╧п┼М4oo#ъЮхЧ═ъWОГGZP╡║o┐у╚┌╛░сsfу■xВ∙єУ{ i╨ьЧW¤X▓┼ХH$ТсИїїї3╪▐S4.▀RМо╒р?l░╩м╓Rфo ╪╒яЎп~э"T■и│g┬уqaт°\|ёУ ЁЫ╗оCVfьЬтыЮ┘є╕ж1v╛№├'д2я┼р}1╬╠э iЭ╜,лО╦ўJ$╔H─ Ъж}╘IХ│В╤nЇжu5BўzoбJZ0▄╙Ы▐Нy|ЎМB|■┌│,П═ЬЦП?}NL∙█я├Ю¤MШ9-?т╪Kп└п■Ё2vэIо╗шcLhё}0ZшAЄsNвй=ЄGajCC├─ттт# ЛъРХ+W∙|╛П8z>щ╙МР @АVшy╟╖╤ы╠╝AUUgeц$ГТ┌┌┌╙Б└Ш` Аlгtш╨ря°АчTU=СБ-иннЭбi┌ц0є\╙бg═ ■МЖ■>:┤8р0╜рmп╫√тw▄сh┐щpДИижжfV ШKDsЩy6Ї{Y¤>цBп╥¤╗q ·sсэЁvffцЛ╖▌v[k*хtАв( Эьk╬╧єuuPБўZb@пеnX7э¤=┼~фXv╗6├uesc.╒_S2=ЎZ╠╡Є^▐╫л╨;╗№╪╝eЮX┐3юа╣Б╒¤ьлдj°№uЧ|~nр`)▌чєЭ `@║b▌╚╠_pF?зЗf%ч╕*°иGQGDўTVV■#╞8╣ЦЪ^QUїqзЄ╓╓╓╬ 7█Eyдвв"╢╗)▄}ў▌г:;;Ч█Ии╛▓▓rУ╙╛ДЁ)Sє}ккJDFуNpА/ШaуТЩМ╔╕XёА▀xLU╒Ц(╫е$w│b:}ТЩ/p)ЬЧ└>ш╦s╤▌▌B╝р ╖█¤?╦Ц-█пlлVн╫╙╙sЛ═╙/37ЇЇЇT!:т@Гкк╖{▓b]╦╠пкк║$М·═b┤T>b<┐││╙/Д°А╟=╧Яю╝є╬дoН )Ї╦н БXСЭй└ы ╣X5Г╗]3XцРсM'a ¤┼╫√▀├\rщ╠Ш╟'М╦┼y КЁ╩Ы╤Я █^;А ў╣s █ЎвЎ>ыTоТ ╞╧?в=Ї╗бPгsNБИ┴loЖGDє╘'E▐(╘╘╘|D╙┤JЯd╗ВYуЁ f■Дв└wTU▌eq▐L╖■■╟ =ЬnъЪж╜`└║▀я╧5╦┬╠]+t"·3Зїе(╩ЯдDб╫╓╓╬└НИRЕ╥&=юуcVVWWпbц_кк┌e:/йГ▐Д{\═╠╔мJц░└B┐▀_#Д°_EQj***▐r┌QOOO!L▀З|?Бk}╒пBпййYаi┌╧Ф&°,0уpАЛ|>▀]BИ?║\о╦Ч//YД╛╕│э^0*╟Мu3Xх╜ЙcBkшє╠╕дЮ└нyыЭ╪&▌д гР?:л▀~╬Ю{┬·╬юуЬMЮзMНЛNut═░┴°∙G;z%}∙EБЖLП∙∙У╣ЙИ !─!─o5M{║EШ╠ └%^B|П"╫┤ж%qIЬм[╖╬%Д╕#╝▌*Of▐ГBf╛└kBИsM╟Тк╨5M;└u╨pкP\пi┌kBИ{Vп^▌ wЇnФ!╡х─<╛▐BTн]╗╓╙я6p !ФK/╜╘vm╦╝\OЯU яx7°=nfцVшыцyD4@3B/"zа┤└----┼BИO╛╙#░·╜|ЗИ▐gцSD╘|.Мfц╤┴╫BчC┐Ч╤TїЁс├┼BИ╧%с╛Є╩+╧ъюю╢=╔╬░ИhmWъUцАAУ▌яёkє@@├Б├╤Цвt&М═╡╒╫Дq¤Я╖я@  =+╙Гo■╟∙╕■Ъ3бд(W¤Раw▓f·№Н)А╔╨Tъ┘Э8┘fsц)IФP]]¤ef~·(+■ID2є_cмГЖ!ДШFD_dц `▐ X рхЪЪЪk+**^0B]:ГГ┌┌┌┘Б@`=А╙гЬЄ¤КЩЯVU╒╓║нb"t+∙Ы╨ХвС f■}uuїШ╩╩╩5HнBgпxЮИЮw╗▌[я╝єN╟█rВ╦WCП)0┐@w┼?╖b┼КП/[╢╠║*U8]A╣ь0 @Сйm;АDЄk╟│\Ё2Їя├f▐вкjь`.DDUUU│\ры╨'Аf.Ё!─хN√7т╬╚╚╕╝╗█~╓╡мL%№!▐Ы╦Ы ╓Ъ) k(Є=N╜wЇD;№¤╕┴╟d█ъ╦╬yП─~~_r┴4▄║фLoo1м1~■0╜┐╜╓║■%╚Є8КmIкBB| └oa¤Н▄в(╩э[ЬЎлкъ~?B№,и╪+╛ЬUдiZCMM═UРz┌╛5@Wf▐$вWVV:О┘PUїА╡╓╓╘╘|\╙┤╗\l8ЕШ∙┐лллOBПЖN╠▄ р"z╩эv?УМАлх╦ЧяЁ ┐иоо^╠╠?ЗэodЖ▀я_┐zїъП▐|є═1')кк~▌bэ!D-А░Kп╫[╢tщ╥БИNnЁ┐DЇ┤╟у∙[вc▓╖ └╜D┤Zёf╛С╧А│№UqЕккqM\▄Ъж9JнЩХщ╢░╕НAo√╨{гсуQп3nK.deЇ▐▒(у}Ї#SЁї/ЬЗgL░5╓ИА═┐-q├╢56Xю╠╚Є:2Pьн╪@ёш]│2яБ■╣╖вв"бфр╞G╓мYєЧУ'O■└ЭBы ╣Ъж¤ I~аKь▒j╒кBшБQfe╬╓°Ieee┬╡Й+**Ю'вKЕ_bц√╨ў&f~└│ЙОaDU╒g<У╠>НTVVо_╜zuCKK╦¤╛b:╝аеех^▀N╒°IpYфС~OМГаr ▀╗я╛{cggч╣УуR*АКx·Wаяе│р2GпЗ┼CТМ└ЇЪ└╢╡О╬■ыГgxэ)tп╖ Шуxy╣°ь'цус{?НЯW-Ц╩▄ уТ>oу/╜█╓Ї▀=К┤pн_┐>Ъk▄6╡╡╡є№СБCзИш*UUяQU5i█Й╛ ¤яwлкZ ▌]iL°Яч█Й$ BD╘╙╙є"Г,5"·╢кк?TU5aeВЩ╣▓▓Є▄nў╣Ю3╩ЁЩdН3P▄|є═Экк~║╒nfIMM═┬i╚▄П■Y┤:\[[m)(& ┘╓Pnм-cЪWцж░=ъё╤┘╒┐BўzьwzlЬRш-ЬКg╛╖~є╠=}мн■G.ж] ╜^6ygЇяОKqж7E╔ID║╒лWgБu╨@iPk╧xвик·NNN╬╟<Ск1$¤SUUїVEи╛QYY∙ЫTН╗l┘▓ЛККJ<Ьк1Ши3╡)Ъж╒жCШбJ╨x╕ z╨нo иКзOzц+[╕\fк)@╬Фмп q[ч╨aг╬xЖ W:(DЁ╕cяL M ╞фe┌ЪМx┬&vжеШРen┤╪ЩсRЬ-QB ╜еехvgЪЪDЇyUU═ бТ╬н╖▐┌рX╧╚%)F1А░8t╖ккеz№%KЦ°ккк╛рюTПХj К╚╝l┤Ha^cЧ─ ш· "Г¤>┐rхJ╟ю`ЕЩmGv╣@ гя!z`Г▀┬H▄Bяшь▀ Цa├Х┬█П{▐╬Bb l"g▄╬h°╨MЯ┐S ▌уё╪Лz┤а╢╢vА█,нкмм\o┐NQU╒_TTЇUД╗_%├jЩж╢Чa ╝J%╠╠кк▐kЧїР"╕UmНйЩ№G─╥ик║·Rап╧ч╗┴i_ 3{эЮьv-█Pфr╚ХjTф (юhtuўп`э║▄эЬkg!1`ЩXЖвLюЇяКK▒ЧЩ░oО█B╦╨ФтuX[l)e╔Т%>шЦ║│ ЙЫ`┘ыL═=.ЧылёF'╚бG┘u~Е╚яё5щdиг(Кyr─q/шklсrв╒{-єhШ,є\ю.е фMЪf&╤_&╕▐ЙЛ─>aЯпaЫNвР╦▌ЩЕоiZ\·К+&C╧fцGizШЗ╢7IЕ>p▄a╤╢v∙Єх pI╨ы▓▐ЧО▒УIpЫцVSє╣BИ1щРg(SQQ▒└Sє%B{k╔A'U╓4═┼n╚№iЩЗ╓╬╖▄эlIыё┘>vў─>7;3)Y°FЯ┐╔Z7~┐ .x┐ц,6!Ш▄1~┐ Ы╨│zYпккt{ПВ╦-╫ЪЪ╗<П рJMЇМyч5Щ.w;ч┘ёHВDф0-╡ЇЮЗ░╧┐█o8ОIа╣╣┘▒E┼╠WY4пs┌ПdhLёj╬Ы¤aUU╒K/═Ё─эv[хЧ┘╖т└хr5Z4;║ЧК╦х▓])'а1|VzКЄ╗∙д8-Ї▄Ь■УД┘╡╨╗zиP╟n IDAT·_a╚╦M8)┘В╢mн7╖{шЗ┬╖8Ы:╗U^Їэ∙┌ЙиЭЩ█ЙиЙЩў║▌юўЦ-[v0╜^VмX19Ь═╠╨w╟h╨╝ЯMЎx<Юw╩3фff▐╛}{" нGе▒┘Звq^Дя9ЖuЬ╤pK└H ш╣У├╖└[Ёl¤оШ }╟оc°рА9~#ЬK.ШпИ#ФP╞@  №═▒!┼╬М╞VGK╧┼Yг▄jfv$О~$C"*▓fQЯ?QuuїеЪж▌╜lь╠hчЖюUш╒яўC╤`АmD┤ЕЩЯeq!"*aцы\ жв0~ў|>Д-▐░ └ ^жАN*nPхя^пўS==ЎКТoЄбhl╨ej vъk·ВцРР2АqЕ98sю╝¤NЇЙ╬жч▀╟Т"┤uїЯЯ~;цЩn\|■┤ДфqД Є▐/0#,8.b"G8┘{∙──3qJ6╬▄РУУ#╫OGV3√c.EX╖nЭkч╬Э_мккZжi┌ tХ р<ч1єw@ёАgЙшIf■g║ъ! k╫ої>|°&KЩyz]х╕0°єCB╝рiO╪зсЖ╧ddd╪vИЯhЎ├0ь36╓╜6g █┬ФШ░╫Ф─.╡█▐╤Г╩{6ге-| Ы╞М╟Юz ЫЮЛ╜Cпф╥Щ╚═╢]|NFФЫ╤Rg╞й╬Qшё█╛╟Ъ█э~:NБ╠│:■╔O~bo+Дd╚├╠│z":ХY!─╣;wю|└#Qц╤Шр╗╠\рИт!qWB╠╘╘╘\r°Ёс╫№·√N6│№zўГBИ√СащыА9sц|¤ї╫╖├fR}MckЇaЄпiЯ▒1}Ь┴b┬▌░q▓╕x6zь5klПz╬+o┬gЧ<ЖлКgу┤)c╨╘▄Йч_▐ЗўЎ╞ЖS┬НЯ>;1G"╤&iaЩ√хО6;КQx╛╕╕8^╖Фy fПL(2вИ╚┼╠╢╙\EДK▄Лўnаz,IЇ¤у▌╨-qt┼ьЕnMЮЖ╚аB3ЕnJLъ┴СтVf^ЙаОМB А╜╨яcЇ{чB▀=═ЗюЮя/ x"Аo%(zЯ░^пўў.Чы▄@└▐6▌ПЎш Э0-ш-T╕%QQп╫Ея|хBT▌√ПШч╡╡ўрёgн╥тFч║▓╣¤nНУX`╚■╛'=Ї┌╖№┬мрHsьаE#╠№pТЩF╛·Т ="Ф [Е.ДиPхЁN"·-=зi┌v;nr!Д`Тв(3Ш∙ f╛└еИмН0мB(UUUkШ∙;QNyНИbцчч╧Я┐├N^Лuы╓╣vэ┌5┼яў╧ вy┴{y·YЗПЗ^Еюёxїz╜ўtvv┌кЪ╤╓@c│Ег▌╦ aOў^╦╖;ё╬ВLЮ8 ▀√к9йШ─╜ОШ(о┴3sве=~█iu[=╧у HfVр2A ╚"By3││К@C!─░Vц;ЙшGкк╓9їN╫sЮЁ@pмID┤8VВ╪ьPфnV╩№7лкъ81QPщя ■4╕VмXqЪ▀я┐└ї>▌▓OИ▐c╬Ь9'ZZZ6tvv~┬ю┼√ўш =с▓╓├╓╨╤чvMВХ╦pЪNuт_█▀U1*7?╜│9rэЧ╦їух╦Ч'╝Яy$Їt|╙╘▄ъїzяHЗ<Йб╨╧=ў▄g╖n▌zаггcК▌N▐█▀Е▒crрї}%3CДъа'o ▌М╫ы┬т+fcё│сh8t┤╟О╖б╜╙З╝▄ фП╔┬╪№lфцдn╡ф╥Щ(╣4j"&G(J v\оA▀cСzА!б с¤г│ЭT╧}ммм,):нr№╬Ёr·4С▄НgБ╦хz╧b╟╬Ї4ИТИиМЩ'ЪЪ^╛|╣гr╪АИ>═╠цjQkЦ.]j5)╘XF(z╜▐Ы:;;7┘}P°№М]√║q╓,c╠ЙсIЯД=шvq╗LЫ4╙&%╫H23√/БkчЬ!lwCшў╛uї╞╓o▒╜м╤ъv╗У5+╢J№ЯКDiЕЩ#╓ЎИмД┐,3Q"J╦v┴ёу╟p°Ёa┬w7╠KЗ,йАЩнК<1рВ в▄╦'\Р$`i Ю■∙uЩЩЩОJViЇсHгU9WЇннKC┬гFНBмр─мм,de Т°ЮРўspЬ╞ ▐=d?pСЩEqqqRrE+КbХИрмdЇ=╚░╩╫ьи6m▓Ё√¤_╩t%s 1}`jЮ╡zїъAЄ'a╠√l╗<ЧAЖц{y└kщ$Qв·vБ└gЙ╚С ~go:╗MХ╢М┴Qi▒ЖКв`╩Фш+╙ж в|є╒╘·>фўOGG╖э└Є╖ юKЦXYYY;ШГ.NV Г+Еnлrb▓Q┼*▌j:У╣ШНOKK╦pI8aЮ)яUUUж6vH0aЖй∙ЭdфUOQ·eЧ]Ў^FFЖ#╖Г?└╪╛л ═X╜┐+1<Бr0Ьщ╙з#///в╜░░У'O09b}.r│[╘Con╧╟~√√╬ЩИ~░pс┬д╣go╜ї╓vпЪЪЛjkk█ы8°И(╚╠щZoК№┬'\К>ъ═ Dty:I&┴%Х▒жцщР% Г$└╟cЩtj╚▐╦Ш╤WDЇUEQ╠√yc╥╓бaчг╤╨W█cЛ{KKЛУaЖ=Квр╝є╬├iзЭЖььlффф`ц╠Щ8√ь│1РKд▒>Ч П╧0i нЫ|▐▐Шm╦∙ЫТТТ╪∙|у#вOM╙оK┴8щ─ъсУЦ╞ЪжYе▓L█├╤хrE(tf■t:dI&ў▐{o&"¤ЭщЬ8Щ2┘у▄n╖╒╠Р╜Ч1·%Ч\╥ъv╗┐ыTВгН~ь■0д╘йw =+#·■└Бшю╢йЦу┬хra╓мY╕швЛЁ▒П} 3f╠░Я,БЎю▌їxЦ╖╙░гA▀▌└╠x{ ь?т █Г^8&·p>Яп╝Є О?╗{р%йБЩ╤▄▄М╫^{ mm╤│пО═kВ9nўС╙╤╪j;є^З█э╛сЪkоI╔┌Яжi╧C╧Emфb!─░ О цЙ▐cj>'▓0Ч+╘4MK╞─Dx╠вэ█.E W1+в┴Фю2% ╜зз'щй{333н▓щ ┘{iыdЯ╧ўEQ▐Cь▓|╝╖▀╖Л0iЬ╛ РХA╚╦!┤┤[п╦vuuс═7▀╘sЩI▐░#Ї╙рq∙PР\╛ обh,┬╛уЎ ╤═W\qЕ│▓xPUUB№ └OLЗ~`Iк╞M╗╠5№=н╢╢ЎЇ4ьI■╕щя¤Г Pы╫╨єsнЙп !ДккёHвыfЗГT╞%╪_2Ix ЭИ|цgР╦хJzvо█o┐¤ФвсV∙`║Ч╔╡╨а╝╝№Cf╛▌й$╠Мяїср1o╞╕YSэ}╓~┐_■дщ╟NАтМ BQВ█ЙpмeЬг-j■TRR▓╓╔qЄ+DF╗uШ╟╜hn╫дBИ│Ш╖flHмPUuАїжц<wжA└╩Х+'╕:┴n>0¤=mх╩ХE ЎЩ,ТaНY{Z]&ГLЯu╧=ў8╦'Ю:Тgdы╓нkИhУSiBJ} С@Д№< nXТ|Є▓Z1╣ ФjЭp№T▐▐wЖэlp╠╝╒эv-ePUї15{Б└=1╛╡╡╡3Р─H`EQ"В┐|5Y¤█$┬уADйttМв(VйД ДHKв!Я╧w?"єЖ;╡╪_╖шўК╕ЕКУъъъ[ЩR7aЕ╬╠MmйК}0▀KoGG╟АoqB▄ЕHЭЬ| ╨▌Ч===72│UJ═~┘╡╧ЗяїБ5`▐ 7ЄrфжЇбHЖзgЯЎo(дя^8╤ZА╖Ў═Гf?в}/3║╕╕╪╤юЙDp╣\UИмП}Этл%C!─╠@ ╨А$*Ї╩╩╩Чь65ЯS]]ЭиhЛ+VLЁSs╖╟уЩ╦***■ р╧жц По]╗╓~y╔$P]]]└*╥▐╤ўБИ╢X4h└зт╟╠l51N╞Zў1s3ЯЫД~#░║Ч<+ДX└╩ юш^:2Х/^|ЬЩ┐АHж- рН]>h ЬwЖ уЄеRJфf╢у№╙▀DЖ╖ рP╙Dl `┤╚МЯ╤h$вл╩╦╦#■│жТх╦Ч┐`╡┼б5BИєJОЪЪЪЕ╨│y%5333¤╩в¤ЮБ╚Мцў√яCdv║╟Ч.]jUё.]▄ └\Бmсс├ЗяиT╣BИ%╠,вv9С#33єяИЬд~z >ЙИДw°YФSТёЭ{С╣EЛУ╨o.Чы)Л▒n NTSК┬-Д°-в/9║ЧО}▀ххх╧иrz]И╞S■╡г=>`┴,gLWРС║Ъ)Т$рvp·─¤╕`ЎЫ╚ЇшЖїЮ#зaчЗ│aС╛;ЭDЇщТТТ])4999╡■mj╬ЁlMM═Щй┐║║·FM╙ЮРТм@гFН·5"#·╧hii∙E*ЦтЯ25√Tзj╠xPUїЩГ#рыUUUwеЄаАаuЬкк*█V·m╖▌╓ рqS│ еяхо╗ю╩пккzСБжF.4бкъ цR▐чc5Т╩▓e╦╪hj╬Ё√¤лТ=ЦС`,┼_ПqZj:l┘▓e(╨▐╔°╫N?N╢&П#\╝└Е│L,r│c'аСд╖╦ПЬ╠NМ╦;ЙyS▐╟┼s_┼ЇёаРЖ╗ё╓╛╣╪s╘╤ф╡ЛИ>YRRЄ|кdюП[o╜╡]QФШ╖йМ╫4эЕъъъТTМ╗j╒кqBИ┐0єгHс■╓Ыo╛╣Сх4рkUUU?_╖n]╥│w╫OWX·oUU╙2qЛEeeх¤~oqш╢ккк╫мYУЇmQBИЙAXЙpe~╘|nAAБ│(E∙·к"Е╕ZСТА┐ЪЪЪK║╗╗_ЁICs'АSжSУх┌l╤VСд╛├ "л8ЛГ╓дS]]╜╚чє╜рJCs "ЯOОю%┼Ыru█╢mYэээЫ|,оВLп`Ў4.┼TG;в&збj[HfbCb┐╢ўЬ╨∙ЖkНm▒╞dу ╙╪0 ]╟¤Мc96ўх║g═Ф=tНa╠░ъucLє√3╦кЩ▐У╒╜ш{_=ЩxєГ3╨╓хH/u°Tiiщ'е !─чаяM6?<5 ЭУУSLЫлWп╬jmm¤3/Edц╢_°OД╝№NU╒ДЕ╕╔т╨&_SU5ст7w▌uW~wwўjDоЫ└█.PU5бЙъъъo3s╪2Вв(чUTTDГ9aїъ╒Y--- pБ┼с7|EU╒7╨]йDЇef■/D└= рч╨?У^rrrrЭ~ўД@ .Щ╣└2UUнr¤;тю╗я╒┘┘╣└wnv╤'Щ∙о1┤╖лкЪpБ !─yИL▀ "ZRYY∙@в¤[Мў$"╜M аr■№∙лВ9г·R┼W>┴;е(╩"M╙~ОpЭ║OU╒щv√П;▄№вЛ.ъt╗▌╫H(q─Бc^▐И▄Ы*ь┬╜ДCЖszПУсWУ╫Й ╟вMbМcЖЄУї▌Ў░╔Щ┌(zЩXc2є╪╞6╞~МчЩяAД<░ШьX ╥Чфз/ zЁЗ ╟├&z?ON└K╗╬qк╠{4M√№`QцакъуDЇ DZ6 АЫ███ў!n ■╟sМbвbyKK╦nf╛С╩№ЧUUU▀▒?)|╓U╖╩№[▒z┼Кз┼╙ў=ў▄У#Д°AwwўX+є#оMTЩзТа'гАUP┘┘^B№!▐e!Dот?╝├╠!RЩ =''ч/├ЇhooП╟7y мK▀ `л"юhэ╗ю║+┐║║║в││є▀G╕╛8рк╩╩╩M╠щ$Уbблк·Аз╠э╠№!Dm <*▀p╨╘Fjvю▄╣╣жжц#ёv,Д/ДX}Л▄W■╨▀р╩КККЧy/-_─mбЗиппЯ├╠[∙┼u0s2azбп╚ЫI Тёo+ы┌№ja5GX┌V╣┘·╡┤мMчP┤є═cD▒фгY┌!л▌j╝■о╡█╩║╖╕ол╟ГЭЮОУmО xu0єїeeeszс@ Д°АGD█o┌ря╨ |╝`ЧккiєДЩКвЬ╔╠Ч2є5.Gd▒@_W■Йкк?^╫ └=Т ш╡и■║╖┬}эоAQФч'LШЁZ░▄h┴JT3Йh!3║эЛpPQФ┼О╩.G#UzИ{ю╣'з╜╜¤qW┼8э%╧(КЄM╙vX%вY╗vнчЁс├g╤GШ∙jЯА╡Bc┐Ёу`ж7!▐AxBа╙TU▌яЇ╜╘╓╓╬ @Ї°МЧИшAf~^UUє║t/k╓м╔8yЄф<BП─┐╓Q╓/°b╚█#Д°1LrЩ▀ ■ў╬х╜b┼К╙№~ vVЗC╧3Qр╡h▐ИХ+W∙|╛╨=пккy╜╝ЧЪЪЪПhЪVЗш┘т№▐хr=+q╙ъ╒л│┌┌┌цkЪvtл rXoA√;А/cB╤ю╞%УUUm[JXб└╞Н?к(╩X▀tGМ╩&╠ЩфчЕмZг{Ш п▌хM1"╩∙Ц╫Ымфи.ў(nm+┼!пI&▓Р┐┐ЙЛё╛ж Ай?у√4╩:╟╘ўбУc▒ы╨t°ОЧ_O╕╢┤┤4"с╔`вжжцM╙░└ц%╟┤hВ>s}n{E▀Uхы╜Va*:╨лhV 2KZ4Zае8 ]!хBovBV рsё(гhдZбАBЁc5░g З~ЯЪб╟CфCЁўwПЎ╤7В╓мq№G▄hhZакъ█6┼г╢╢ЎЇ@ Ё$· .7B╖Bб┐ПQ╨Я█cаЧНї]n&в;Ш∙cy╤ъъъO3│9ж*?YЩ°ДхЮFь√▄ ¤=5AЯ░О■Ш=d▀PUї╖▒╞лййYаi┌УNяG┤cA ?s ·╜sb ┐;JD╖TVV■П▒▒║║·╠lL╕хSU╒v╪xR2╝ФЧЧ┐дi┌"DG8ж╡Гёъ;М╖v3║z╪а'═nk╫б╜чa╬М▐KcЬжш√Ъz▌╓V.nє l°%д@CП▐vуuЖcdjЛX╫6cT╚бkМKД0Ч;▐зС░kВу╨▐ХЕ╫ў╠┼╬OПGЩяaцЛ╗2АКККэ╬'в*шК║?╞Ш `!А3б[D▒АнTчХ∙@░d╔Якк╖)КR р5ЧфA/оrАyжвE╒ажиишТd*єБBUUMU╒{аЮQн6уа?фB┐G√Э$в█ЄЄЄцЩХyРWМ(К╖A┤|∙Єўsrr.░@м╡▐BшK W@╖┬Kб╟╠FЇяrАUNпмм№Н╣V83я╡╕&сHўккn ~П═юp#&A yЇяr\UГ^жє№Z├└xчBl ▌╦єбGвщ╓&YYY│═╩░╝ЧЮаз╠IKШ^^^■╥ц═Ып╘4mТР▄■шI╞ё&`┌D`╞$6═!иЁВ >LiвO]чamkсК,ь│3о{З l╕Ц чЕY╔╞їЇP?&ПГгбў%╩√3╩█;q0№╬Ж>═cЫМ¤▀CП╧Е╜G'с@у'еOН╝BD╫ФЦЦFDЁVВn:!Д°%tkЎ&Df╛r╩{╜▐{Ч.]zрf$─д ·r┼кби╚═ик║└в`NВ;\ {ЮЙhьpffцяn┐¤ЎиFОв(п Q%Ъ┌4PўГр╛цJшо▐Dv7ьЁА╫ы¤]мяqffц▐ооЁ░ Ч╦Х╘№█ВAr рЛ░^╓JAя┬╫ДkбG╓_Е─ р╓fff■>╓w┬хrэ ┬чcYYYY╨НГ~IК╦▌╚ц═Ы/╘4m#Тр~СсaLЯL(КХ;█ь:╛╞Z╧╢TьnpгB┤\Ы6╗└-ф▓rГGuЭ│)V└$Г╒RA┐СЇжыLkєM┴╟&b ёЙhё}gЩ∙▒юююпзкr┌@Ь ЧX =С┼Щ░u4є>Ї╡╝┐н╖ZУ6НУRЧ{Мqч#УC╓Д]%ЄЇ@▓┐xFU╒ЦЙШvДc\}{╓╨╜▒╨аяЩ~└╙UUU[8┘╓8BL"выЩ∙2Ча Йj·.ЕїЮ,я├Lp]¤{╨ ЯЮЕ■mА7ая*x(ЮIиbzЁ^~└┼ш?f╠`;А┐)КЄLEE┼┐ЬОIWш░a├Жs\.╫│Hr п8m"c╩xЖ╦┼И┬в┘E+tЧ╖a-┌мг)UєV3л@╡ИыB┐╝ ▒Вшz╫╛йЁ)┌X╤d╒ї∙<9√ПO@П?ю o└еееi╦НЮJВ┴-│4MЫаАИ▓а╗█В∙жўgff╛k╓mE║║I┼хrЭ╞╠╙4MЫ ЫИrШ┘=(ЁДв(5M{OU╒У)█`"╕ус,"╟╠гЙh43╖Q3я╦╔╔┘СМнОй&╕╬>║╡@╡h║zwц V?Ы│ЙиРЩ Йh3╖B╧}qРЩ?Ь?■√╔╪rВИиккj6tш^цв/╛b7АwТ▒e╨▒lйЪА544Lё√¤Г¤А#█x▄└╘ёжN╨рё└B▒ЫЬЭшяИWГХSQЪм^╦ывx┬Ц VsDаЪ╒╡&K▐2ш╧тХ5t·2░ °xj,p ─Н xIDAT@@K(▀H#}бддд.СNF"B┬Ч╝RU5V╞(ЙD"ЙI╩╩ЮА╛M щ{Я╪sH┴ █▌╪╣G┴й╢╨█)/CPШq 9·Зж/╣п═4╢╞n8'"╧Ё╖q▓Ш╞ж6юkcу╡ж╖0y(╝═pIфd4╡eу═}зcы┐╧─З╟╟%к╠_Ї√¤чKeюЬ`╓6s№╩Р^кРH$щ'iAqVФЦЦЮzї╒W?╤▄▄№+f╢╩fФ 8tB┴б r│У╞P46ПЦ╣с┬Р▓6ЕЕЫв╟┘╘ О SЄ╫╣9а.,░-фЪG╨U<▒юN}GЫР=╜уЗП▌╤э┼Сжi╩GGwRТцмr╗▌в┤┤4о"=#ЭЭ;wZ zЧнD"▄дTб└┬Е }╛QWW╖ └/С─э F┌: ╗>tcўA7╞ч0~МЕc4╕zwДЩ╓к├Фд╔Нсв╖r╫gш╧▐ю╕O▒3З+jШФ0╫╞Гmf╦Ю9╝▀░x-т┌┐ GЫFуHs>N╡'5}°З╠№х▓▓▓&│╙СЖ█э╬ў√├чBD$║D"IИФ+Їеее╒╒╒╜`·▀░7ЪitсHг .(└╕1~МА█жua║wШс 4b║╓wA╚R7║╨94Fш│b6lБ3КbсЩ│┌═kЎ┴╢╓ОLЬh╔EcK.Nud╟╗э,*DЇhWW╫о╛·ъждv<ё√¤╤╞╠,║D"IИSшPZZ·zCC├∙~┐ aДWьI  8╓ф┬▒&╚@a^cF∙1*+`H1kPФ╞}х@t7╕~░o░^е╠sсЗ╣ъГcQ╪╓К:ь╒<б║¤4╖eуDK[r╤уO┘GyH╙┤oХЧЧ?ЭкF S╠ D+iЖD"СЇ╦А*t(..nЁйMЫ6]п(╩oШ9оl>N╤4аёФ Нз\╝p)└ш\?╞ф0&╫П1╣>( LоvД+Ў+Э├нj#F╦║┐ЭVcФx @hэ╚DKGZ;│╨╥СБЎодWzМАИ╓∙¤■o-Z┤h─nYJD┤└╝╗ДИvзIЙD2LpЕвммl▌ц═Ы_Ё3_┘яI&а'[▄8┘тРE▓3╚═╥РЫхGnv9>deЖ╓и)<Р-Щ^НРAщGМвє√ ▌hяrг╜╦ЛОnOяk▓]ш▒`цDЇ╜ТТТЖt┴╠[95MЛZшA"СHьР▓}шNdилл√Ї\┴╤*_е Ч┬╚ЇРщex=dz5x▌·л╟нAб"╕\z▐╖ш▌Oю╫▄`f°│ВА°№Д┐=>║}К■ъW╨▌уFП?бmd╔аХЩEAA┴}┴`FIТ &е8=xИуккОOЧLЙdxР6 ▌ЧЦЦоihhx╥яў  └ч╙-РСАFAЛHq·рtвxФЩЧЦХХJ╖0├Щккк3о╠╜еD"С$─`Pшz╤▄PWWw?ЇКAgеYдm&вЯ\yхХv*rIчs╒зCЙD2╝HYж╕x)--m╚╧╧?ПИnБ^▀VТ▐`цлKJJJд2Вт■├▄╬╠RбK$ТД kшQihh╚ї∙|▀%веHbї╢С m╫4mEYY┘уИо'I ╒╒╒_dц?ЪЪ▀VU5щї$╔╚c╨╕▄н(..nЁ╙їы╫?фёxЦ°Ад╓┘AlPSRR▓!▌ВМD╓о]ыaц s;¤"ЄH$Тс╟аVш!/^|└П╫п_┐╩уё|└wМK│XCЕQMII╔?╥-╚HцЁс├K╠357ggg?Ъy$╔ЁcP╗▄г▒m█╢м╢╢╢п╤П╠N╖<ГРf■У╦х·Х\O?BИK╘#╝■9,UUїо4И$СHЖ!CRба║║║K|└НД√╪ШўЩ∙EQ~[RR" ╡╡╡sБ└s╠√╠w}d╔Т%r┐┐D"I C]бў╥╨╨0&|ЙЩ┐ рьt╦3Аtx └C[╢l┘дккЦnБ$:BИє№ С╦C~Wик*ўЯK$Тд1l║С···2f^рЄt╦Т"└ ╠№"ZWZZz*▌ fДЩ░ZU╒н0Юр{юСtЯИ╛SYYyкхРH$#Лaй╨Г╨жMЫn$вЯ!╥▌9a/3єУ╠№ЧЄЄЄ╜щhи░zїъмЦЦЦОрЯ/X]PPЁфў┐ ¤юdП%Д(ЗЮ╞°╝(з№LU╒[У=оD"С gЕxЎ┘gє333├╠╫з[Ц8Ё1s=й(╩SW^yе,▒&Ев└3DЇ3пWU╒|▄6BИЩоЁЯ╬МrQЕкк+y╕ зУH$ia╪+Їuuu └/фж[Ц~шPODOБ +//?ЦnБЖ:Q║С ░УИ▐bц╜N*К╥оiZ;tхEQЄdkЪ6ХИж2є╣╬0лZ▄дкъЙ┐ЙD"▒f─(t╪╕qуКв<Б╚¤└щц0Аз<ХУУ│∙вЛ.ъL╖@├  =U0А╟▌nў-╦Ц-√0 уK$Т─ИRш░e╦ЦQЭЭЭ°LЪE9р/╠№╟▓▓▓нРiXSСт?Ш∙╗>:CЎ°?EQюнииxi╞УH$ТСз╨ГP}}¤╠\ЛБ/Pє3 ╠уёГЩч@╧:8@>Їb@9╧юц╨ }M|А▌┴Я7╝РH`ЭD"С$╩ИVшPWWўf~КИждhИNfоЁx< --ЄбЗ┬ЭСС1к╗╗╗UUU∙∙I$ТA╦ИWш░i╙жIКв<┼╠ S╨¤зJKK ЪВ~%ЙD"щE*Ї ╧<єLvffц#Hn░▄ё╥╥╥сР╘F"СH$ГЬБ┤\s═5[╢l╣@2л_╔┘ТD"СHiб[PWWwА_#▓▄еcБ└ьEЛэN\*ЙD"СHв#-t JKKзi┌b═ЙЎе(╩gУ ТD"СH$1Сz 6m┌4ЫИЮЕ╛Э)^О║▌ющ┼┼┼]╔ТK"СH$3╥BПAYY┘{n╖√2 JаЫ ~┐ █╔ТI"СH$+дЕnГЖЖЖL┐▀ 0АтьвUQФy▓ZЪD"СHRЕ┤╨mP\\▄╒╘╘t#АЯ╟┘┼(M╙~ЩLЩ$ЙD"1"-tЗ╘╫╫/aц_p╟q∙Т╥╥╥Т-УD"СH$Rб╟A}}¤5╠№Gг^┌NDЦФФьLЕ\ЙD"╣HЧ{ФФФ<гi┌┼Ў:╝4ЗЩЯlhhУ ╣$ЙD2rС =N╩╦╦▀&в №╙сеs№~ Я╫н[чJЕ\ЙD"ЩHЕЮ%%%Н>Яo=ьЁ╥Є№№|С Щ$ЙD22СkшIb╙жM?$в{aТ─╛PZZ·ЧК%СH$ТВTшIд╛╛■S╠№А\ЫЧ┤1є╟╦╩╩▐Hе\ЙD"■HЕЮd6l╪pО╦хz└ ;ч3є-++;ФZ╔$ЙD2ЬСkшIf╤вE█}>▀╣■╧╬∙D4ЕИЮihh░k╒K$ЙDБ┤╨Sm┌┤щ6"Z {з┐555]w¤ї╫R-ШD"СHЖ╥BO\VVЎS"║└1чb╠Ш1wзZ(ЙD"С OдBO1%%%╫4mА┐їw.¤x╙жM 9bI$ЙdШ!]ю╒╒╒▌а@FМє║ЧЦЦ╛80bI$Йd8 ·│a├ЖY.Чыр ╖w╖*Ca╟Я╟bLя@░╪d,ZЇМ▐А╪╝/`ic ╗ гA0 ЦН═jPГa° ╒єДз╜╝o9r_bWkэ▄9wййау8╣╫,ВsЦeЮднд√З╪╕(КcЪж┐№ш°Clш Куx$i_U╒FRяMdя√■бцZАbа╖@Т$╙▓,wТ╓Т·/OП<╧'a▐кшzЛDQ4░╓оМ1KI3ICc╠┬є╝S╙▌эЎ┼\gх╫[╕IENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/images/guidata-vertical.png0000644000175100001770000001501514654363416020530 0ustar00runnerdockerЙPNG  IHDR}kZНг pHYsЧЧ│[e tEXtSoftwarewww.inkscape.orgЫю<ЪIDATxЬэЭyt╒Х╞пк╗╡┘▓-╦Цў▌`H┬b└asмFрД░0Щ $У Y $L c[еjМ'0╠$d&C&ёB фMМ'В└0█▓хMЦ╝╚-uw╒Э?кZъU▌Т[▌2╓wОT]U╖▐╗U_▌╖▄w▀+%" т─ВVhС Т~bРЇГдЯА$¤─ щ' I?с)┤╣BCCCеm█╙Dd0╔ыїN╞cБсJЙGS╩gЛэCDБ▓Qн╙▓д])╡╫╢эЭ╢mяZDдID╢F"Снєч╧?T╨Ы╦1╘ёшЬyю╣чfhЪvЮжig│Б╙Eдм│▄╝жФ·│m█╓4эеъъъ=¤Ш_┐т╕ ╜бб┴c█Ў%"Є╣ Ш^`ХlрUyF)їД▀ян└·Ї ЪЇїы╫O╖m√+└?т╙пЙ╚╧JKK= №єZЩLРд╗┼ўbе╘їЇб▌б°<6>пр╤m<║Нж@╙l4ew╔Y╢Bl[И╪:сИ"╤[l[їEї∙БИ№[MMM{_╚щ ┼Цe╒Й╚w╔Вl]ГЄ2Ыбе6e┼CJmК}|езwK╠Vbўг╟║ў;├!G;<щ(цp░И├┴R┬=Ы[┘e█ЎM555OїщAЇ3 щы╫п?╙╢э╟АУ{ТZ гG╪TФ█ФЧY(T ▒╤▀b╟ПТпbVАXю╣Дt░щz9▄у"┬СОb.co█pТщ╢эшш╕∙Є╦/?zм╧'ЧдпY│fБRъ┐А╥Tч=:Мн?J(-КТkГ%1ЖT─!▓ЛPИ│ЁЮ,_%[|╥╦│▀Ў▒│e$M√G╢╝)яMD^ёz╜W╬Э;wwnЮ╓▒грдп]╗Ўл└OphКГGWLзШXе╨╡2RЩdййО'чQЛя▓■D┘t∙Fхl,[╤╘2Ън╗'b┘)Л ўХRчФn^AI_╖n▌╡"Є┐@╥У=Bc╓TmЗ#┤:Cр<ь,╖тnn¤MМL╢щ╕╫╞е┐╒4бмиЭ╤├Z╨╡o7Mg бйnў╡ТТТЛBы╛`д╫╫╫╧╥4нСД"]╙'OЎP5RупяЖi=\°ъ'h╩цдq0~ф.╢юЮ└╓=SТdФR┐иоо■B■╡ЛGA|ялWпЎiЪЎ ДыЪтЇУ|Меєю╢╚qC8А-яьЪ╞бгCШV╡ЭУ╞╜Я$#"7м[╖ю┌иЗВX·┌╡k┐<зИRЬ~RХ├5,╦цПпЖ<√╪╟(+ыO/k▀a█6MMM4550v─nNЭЁ ╝╫<Хm√&%^▓=Я4■№╬|ыE▐\ъыы╦4M[Фx|┌x/Х├u└цH╨щМ3Ж╩╩╩╝щ╖c╟╢m█╞Д Ш2eJV╫╠Ь9УЭ;w║]║юn▄М1╨╓^С╪╡Ыфёx╛<ШK╜{Г╝я║о_МЙ=VVв1eЬпk▀▓║╧iZ╧*;┬┤ bgYb Ж┘╣√0;wж¤h(ю\8f╦Ц-tvv▓uыV:;│3FM╙║ЇМm╜+%Ь<юoIЄJйяРв╖Т/ф▌╥m█╛Qй°√=yJ1кП└╢Е √├[№·й╖╪╣█ї,)Ў2w╬n■┬lFНL_Шл6Ё№╦█°Чo\╚UЧ╬ъ: ▒m╟M+"┤╖╖STTФ╜bI╩K1z╪>Ў{bF}}¤9555/Cт}F^-}¤·їУХRч─RкQ1,ц▌╚d╦ю #л~║йЛpp,■Щ яqуO▓kOъ^Q¤є[║OЕ─Ў═1╡w]╜╗ё#ЪТNkЪV░]^Iт▒ёг}ё─їУ░~уVЮ┘Ё^╫■й3Gс┐`^пSмюkigх╝Шt▌Уї├\╒╨'╜√БhёU1дЯ'╛*СЛЄзL<ЄJ║оы РxмrД/юe▓Є_>∙F╫яs>1Ю №┴U,╜гЪE╖t?├Ч_kbы╢V▐▀vАяЮхЮ_└╢є╘Sщ║'?е)FФ╡&Ии3Я~·щФnч■F╛rз╟юФщФ╣( >Ў╖хнwўvэ_ю?╣ы∙·/Ш╞╨!▌їя_■Р^┘╞╛¤^j▄└РR_п┌}ЖD╜~╩u ├J█е<┼┼┼ ╔+щЦeU─юЧХh]╧&╓╖|╨чQЭ6й█▌йыУ' ПУ vД╗фOЭ9К Zu5zЖ▐@Nа]%ЦrЎKЛR┤MыeТС╖╓{ccу$Й{те%)'zh<55╟╟'О_:VО(щ·╜├ХЭ5╜Ты>√qj.ЪОоч╗`sяEД_2щJйёyV╚#щJйIЩG9ПхYu¤KB√╤p▄~С╧Уv┐¤hИ╣чMецвдl√▒┼╗{s=ТBм_Г9╙"oп╛ИМI<жk1E`╝t╩4::уЬ╧п╛╫╙╜ьИрєeхТ{─яю╛ж╒}XeМ┬шфУЇ$&%юGц╓╗7Бd╦КO2щ~░EE"║л(СоЫ┤ьФП║#_*┼"oд{<Ю$пH8"I▌ЫЮPRЭ [iўKKRG▓ф*╢И▓КУ─Dd^їrС7╥u]▀ныё╓╫╤щZfЬqз'┐jT|ixЁp╝б┤ъ▐;jhЯЇ╠ ║ 5╒хГш%╗sE$╜{░С7╥O;э┤╝^oЬ[кэp$ж╠▄БЮ╙%°░)╛я╗}ч┴о▀╙&зМ^╔bЭю╧C┴aЙRтСф∙ь├Ижiqovg╚цhgКе4Ш:qUХ▌ ▐u/nэ·¤╞▀Ў╨╥┌▌-: ьдqь№#ж╙╓ jЪЎ~uuuK╛UВ<П▓y╜▐▀▀П=╢koИ}two╥[╝RpE═,~·Шc ╧6╝GqСЗqUCY¤Ї[]r╙'WЁwз$uЄЗ(┘юнД┬^┌ОЦ╟Й╪╢¤Л_▄d┐PX╪▐ьVїY╘э>Я╬¤БOse═м╕~98■@р3Ь6л*ЧjўN╔eгhjЙw╝iЪЎЗK/╜tKaЇ*@EQQ╤WГ┴`cl╖¤ГЭМнBСП▌░QФХ·°■7/ф[ t.[><@GGШqc╩Щ4.й▒ФД┐1э╣─▐Eт~паЪZ& u╗З]+_╥ўДПyЧ:ым│^ї∙|п─│lxє¤а├w/╩х!е>>qъ╬;sbVДgBYY%%AEEEФЧЧg╕вtFК°`╧╘╕├Ъж=Rшй═ЕZ~ф*еTЬgеїР┼ЦN1_ьы&╛╜=У?ХRЬq╞LЭ:Х3╬8#c|^┴`╦ ь+Ў:qu"К7╖ЯJ╪ъ.Lu]▀зФ·nю5я 6┘с∙чЯ_ ЕЦ&Я9╔╦д1/■%D╚_йииш▓└Ббеее+ИrZ╒6жVmу▌ЭS┘╛Bми \ьў√У├zЄМBNkR Ы"С╚╣Й'fLЇPZмxcKЄ╚╘@FЙ/╚╣3_e█╛ l▌я'╨4э╢yєц¤░@к┼бРлKIggчз5MKrPl┘aЫ═i3tК шB╧JAe∙╬ЬЎяяЮЬD╕оы┬a╠Z}ю╣чN╫u}РT~ЧЧ)>>]G╙lОv─╠&U$L'ОЭНЪю/:359ЎzХ&I╝>~Ўлж,ЖЦ┤c ╝╣m-Зу=oJйЗллл┐BVa∙A┴IX│fMНRъ)└ЧxN╫`·┼─*║WЧHЪ_%╚mКХfzr╠їй,ИжЧf>z▄~╠<ў}Зёv╙TBС$ї╕qу╞яЖС<Ш^@ ╥╓о];x HXPV'MFЦ'Р▌╙ЬЄrт ПM'С№ШЧ I╢√\{G1[vO`▀┴°Б └nЎ√¤?═▌╩ щы╓нЫ-"╧#╙╔ S╞ZT│у-?9=YuO╓ЬaQГ├┴b╢э═Ю╢К$_ТRк═▓мkjjj╓ч№хКtА 6|╠▓м▀=╖Х уG┘МнМPф╡c,░нЇ/ARЭSмз▓~ЧьИе▒ў`9╗ZЖ╙╓Ю6╩йA╙┤/═Ы7п уф┘b└С░qу╞б┴`ЁA у~е`╪ЫQ├#М,П0д$BJkэi9Т4UA{ЗПЗK┘░М╓#e╪Т╢│╙!"Л_zщеUн■NЕIzk╓м╣A)їя@╓a0^П0|И┼Рч(-КPф│Ёy,тWХ▓▒┬ИXКpDq┤├C{ЗЧ#>╢▒▓Є╜7ш║■нO}ъSoe╨д╘╫╫O╘u¤n╣ЮcШ▐лФр╤ЭнBG4l9жё╫А;№~ oО%СB`└У┼Ъ5k╬VJ▌\X`UvЙ╚Jп╫√У╣sч$ЪїXq▄Р┼║uы№"ЄMр│дXХк▒Y)їаоыпdGq▄С┼·їы'█╢}ЁMzQчўGХR┐╢m√?/╣фТН¤ФG▐q▄ТE}}¤hM╙ъАЫ╚═Т└:е╘бPhїGmБ°РE}}¤gu] ╣ИЇ%Ў∙Ё{рёТТТg┬¤ЙП щ╧>√ьП╟єЁЙ,/yM)╡в┤┤Їwsц╠ оX▒bXGG╟чА┐ЖёJ╞лПS|д>▄s┘eЧ}шёx.Ю╬$лФ·Qkkыьъъъ_═Щ3'ЗGумS{u?лZP|дHШ;wюС╓╓╓лHXЬ0╢m?╡`┴л'ЩП*>rд,X░└Є√¤╖╖тМxе┬yTi@с╕&▌4═1жi·Ч-[ЦЄ├~┐ р3@R \)u╙┌╡kOыo"Оk╥ХR╒└╦▓╛СN╞яў?╠С─┼▄|└├ННН╟A@Vnq\У."═└Zе╘╗=╔∙¤■╫l█>GD[фg╖╢╢■и 4L ╙4п0Mє@ ╕&▀y├q■F├0╓Y+\zще═Ы6mЪ█▐▐■Ё╣ШS7п]╗Ў╡йФ·╨=цSJ=^UU5╣РКх }.▐Wо\94 VZyy∙О█n╗-ШC╜· ў▄s╧И╬╬╬▒║ояЯ1c╞EХХХ&Ё%авккъ?vэ┌ХU:лVн*ioogYVС╟у9ЙDЪє5уz ╟уФT ├шї║5╜r├ЪжY№3О╟ъ╘Д╙/(е■╡╢╢ЎIWЎg└УЖa№.V(|SD.╛hFVCФжi■ЁЖёэ╪у╦Ц-;┘▓меJйЯ╫╓╓■>Г▐w╫┐Jг l>|°ЖSN9е: ═ill╕╟0МЕЙщБoЛ╚╒8╦Э&Ж└^TJ=}1∙OюЙ94Ш╝ lOбЄ}Жa№ ║^╥я58▀мKЬ╨ ╘kЪЎг%KЦd512kKЯ┴ЙTmЦ*еtЩ |ND~kЪцпpF╝╛дФJZс^Df А/gЫ7╬ ЧqдЛHеЫ╓&ЬУ$ШжщF╣rП█ХRe"2╕╢ннmёжMЫ╓MЪ4щ2р┘tJИ╚=8ф>╝ 4JйС"r&pХИ|┌4═╟+**>╦-╖D┐P ЬУTt Хйо^qPJu uvvЮ |╧═я┐ХRК╚NЬкy pp╜m█7БE╡╡╡+╥щEVдБ+Ed5╨|╞0М?$╩м^╜zсц═ЫпVэЕеKЧ^ №Зи∙Жa$кФ║гоою └}█╖oOЪPЩАsыъъ▐H╡&АiЪх8╛√┐?pр└¤└╫ ├xУШ/AЫжy-░Z)їЭ┌┌┌_f╚єoПg╩вEЛ╥F╪Ъж9°?╣╟4═Жa<╓SВЙ1MsЬИ< ╥u}^*┬┴q}Жёи╟у∙i╛дШOмX▒bШm█  ДБЪTДИИЖёиоы│Iau▒0 упщw╧┬Йр} ╕╤4═У·~]i╢їD╕+│┼ыї╬ZБ╗L╙ь╤Ш│▒FиPJ▌╢xёт╖3 /Z┤hЁ╒,╥эWttt▄LTJ-6 упЩф/^№NUqL0 #,╟ х║ъX╙╦w▐yg3Ё3`Кжigї$█#щnqїyр]├0▓^ I╙┤В!<Ї╨C^р+└▐1c╞№8█ыt]?ШY*3╩╩╩6р4╧╠Ez┘B)╡└m_дE&K┐gn┘ ЇTм 4477ЯЕ╙╚Y¤╡п}-ЬI>╫╕¤Ў██Б62T╣Ж█└CDz╠7щч(е6хHп╝@)uо╗-д▐@ЄВ░¤Я'ЩЄ═╘zЯ "яф@б╝ADж╕█МmР▐┬4MПRъR╫╫0g╘е╘Ny╜ввbcLW-gpЭ2WgгБ ░S)╒мФzй╢╢Ў/uuuYеХЙЇшТMI рИ:Nrкw ╕°йИLB└Ь▐┴0шh7Msp KSuC)еL╙№ОИШ8б▐G▌|5`╕И ъъъЎ╬&═LдGGБОп┼_а└уёф╠тБ└∙%ЁОRъК#F╘╟Z┤iЪ>M╙NС╦Dd>9ъ╢╓╒╒¤PDnЮ╓4-`█vcм╗ў▐{я-kooЯ ╠╟ ╔ИLдG[│х8o╪ёВг"Rь<╓─Ц/_>JD▐ц╘╓╓&EтЖ^w VШжy╠∙║%╦н└/ъъъ╛Шк1э6А╫їЭ▒*╬╘Р█аiZ.?0╙ЧA ╒╦ыЪ,╦╩Й▐бPшs8UЖс:`Є∙:`y╜▐█s┘{ъСtе╘лnц╣ьoЁz╜╜∙h═░шuYb│╗=╗╫ЇД3╝^я ╜╝юXg▄Ь╝wчЭwю╔e╛=Т."/8╔eфh+АeYу▓6MS├щя╚6Я╧╖' 6W▒2Аp8▄█су#cD2╬Х.гo╒jП∙ЎH║a[АчБ╦Ц-[6│Щз┬╢mg5хX╙┤sqF╪^╧6ГЕ ю├ЩЁpоiЪЯ╠Ў:╦▓жж9╡@╫їмк х|6┌C·З¤┬┼ш Iэ▓ов4MЛyЎ°)шМ╛wе╘R@╖,ы!╙43╞tн\╣rиm█╦zYПST▀№└d№N╡m█▀╨4эwЩdPЗcэ?Y╣re╞YнжiОvwу"dгN╦▓о╠Ф╬ъ╒лї║║║GpMyЪж5╗?╙╜dQlкЦ.]:'S╛╦Ч/х0┴▒X:@mmэ:р▀Б╣└oЦ/_Ю╓┼gЪц▄`0°20;ЭМaА└мф▐▀nM╙ЖЙ╚╖▌sNїїIЬqїПз╤+dЪf°Ё'╙4яWJ╜-"Aе╘(А┌┌┌'Ч,Y╥hЪцbроP(ЇF x└mgRJU╣·_Пє▄+))╣) ж}┘в╚*╨┴0 █0Мпу86ум╢m√E`НИ▄П╙е╣ ╕(╒zп)╥╗Q)u-О-~o█Ў+"Є┤И▄ М╠ЄЄЄ .\Ш.╜#@гИдm▌ЖQлi┌8$}ID~ lС?╕z p}]]▌\7▐ьqЬд8╕%▐l`p ЁЁ║m█╧Л╚╧qъ▐пчЖ▒xHы6 у~р6ЬFъ"▓╓╒ы╖"rMМ▄▌JйлА}"r╖Ыюы"R/"лА¤Ъж]d╞ї▀√▐ўуD■$NьИCЯж*Ыж9Z╙┤У]л<дi┌╓┼Л┐s■<Ь·ш├0ю═"╜1ni1R)TJm╖m√н\║■ыщJйЙ└y╟0МP}+А┐SJНС}8C╧┘ET&`╒кU%GО9SD╞GEdЁfк{┐ыо╗&[Цu*PъNЇx│/~Г~ЩЯ.СzрF├0╬x┴ ЄК■Кcлt╖{{ФDA╨/д╟xЁvўG·Г86фЬt╫1q9╨dМMD■Сs╥ыъъо╞щr=юО< bА!зд╗!┐?:БЮ╝rГ( 2:gБ@╡И╠N╫-q▌│_VтЇ╫┐lFПs╞Q8dь▓ЩжyЁ+ЬёьFЬБП¤Jй#"2gn╪Е@╨мФ║9q.╫ 2Z·╪▒cЯhnn╛╟wЁў└ўe9Ду╜zxкммьWn$╟ 0·фЬQJй√ю╗п°xЩЮ<Иx|дVМDv(°╠╥AфГдЯА$¤─ щ' I?ё 2/ЩBС╞IENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/images/layout_example.png0000644000175100001770000003471714654363416020345 0ustar00runnerdockerЙPNG  IHDRх╓ё>sRGBо╬щgAMA▒П №a pHYs┬┬(JА9dIDATx^э▌ tWb/ЁkИa!h∙h╬ЮM"dЬи╝Ў<УъЇ┘зсРЛ╗╗Ш╓╝wh6($`╗█╜ЬemшR╬!╗┤й }╟Ж*ЦўяbЦ┼8ЦЦ░dЛ7UГ█MЙ▓▓ЕА&oВ!╬ K!№юЭ{чCЯЎ╚#i$¤ёqfю▄Й╤H■ы▐;3├├├└─&Й Jм▌щlПЙIrI║*ГЦ▐]Чб>^Е,` Г!p A┘╨ХWшсl▒d8аХ├▌ ╟¤ЯГ9▀║╥+└Йgg╬чй(▒W√ Б @?=y%╓█E\.╥╒;цqgm ┌мbМt╫t5їK:эС│вЄ√ Б :Є Л+Mь!M` ║┼ЧЛ;koїДBЮ·Нa█6Цж┘wwPн╔жУ┐W┘%╩т╕Z5)Щ+Лj<б°╣▓ц╔Ё'"ИM%n;y^│:[┴,ь6ы}╛·1■Щt▐┘▐.┐BтХP*е█uъZ╥IїUcnКФ┌лА¤Ж└Ба[▄x█бб!1o╬Ь9╥1╪J:mg▌Ц╒¤м∙ДЦ╘D6 │╖'W▒jъv7Hh\S2^▓1ъd_5ф═╤╔╥╩╓╡эRVP6╦Wdk6УNiYт"═%/в╘O<шv_CwЄc%нЮnчLЬ┤{U^Ў/ўтт x╔ LПЭ╫е%ьUг{uм]з]ЛелЯaС╝)║lМзgКW√▀ яВА +с=uЗ°┐,a▒Jj]щаЗЫ╡┴╒╝г7╓╓FhЙ╖ГХi╒╙║▌A_}}░;ь▌шcEЇ└U╛гЪЕmG╙2щ(╢.krx"gI5ЫЙE├─я╖╨ў4ч:K+ЁI║╚СЇ╕ЙЫнП{_%,╡┌ь─╙lС▐д>]║#▌ciд▌3УЄ}Ыю▒ъ}├├t/╥П ·г¤░уRь[яF^eA╡Г■╫о╙мХ▓>W*пЎ┐▐%$0VЄ{j╝¤A4оЁ=·єmЧPFї╜сю бqE %ь@Ч;n√╜ьНЧБ├+zxЩД7t╝╠ЫM▒Ф}\ ╙яФ╘X:■╟2ЎЇ;]~║c5tэ[EvkеT>пЎ?Ау╠+,о╨пBзЛы▓&т┘ўV#$Ф0xМV w╗E\aф╓╢Eй@!=FГ8╕if%mЬcЛD^R7Тr│╩УIXkw│nT·6гo╕pФдyм─Kб╤O щ╙@Ъж▀КкЁщt ╠dу▀u\╩·\╛ ╪ `HY_^б&СЫй·X┌:╝сfкEо╢╢mr∙iБєрeQУЦ╤└т'Ыx╟%н`чН4Ц╓И]$w╤lcaй]═тї╛~uуё├┤ъ}Эb+b#)7л<Щ▐eЙKн6в>fGЫ5╒c)лk╣░╪оФЯ%тм0¤?3Еёэ:UR}U∙╜ ╪ `HYIoЛn┼·иШJТ№B^)░°Ш+j╪ `И8Рh^yшбЗ─ @ю╜¤Ў█║Є ·Г└ьРW└ьРW└ьРW└ьРWL ╓юt╢╟─МiЬ▀╖j╒╛єb&[у▄И!Пе yаВnЛ┬ЕЩЩ3╙фЄ @╛╤рai&Э├B'щW`▒╢mV1`^я╝єОШ╥HY8~╚+∙╓█E╝¤╛z1Gъ}b║Wns .,╫h ╘Ў╡qЖ-Pл╘№r~▀кyТ%[O'Ф╠}6jБ(╤╠Ыч9Сb#cмЄЦTЙ:сQК╤╒лW_}ї╒@ ц%tЦ╥Eb^?фА|ы ┘m╔э$!OдБ╖╖╕№;hЁ║kY;ЫN╤Pёє9ы∙┤бшЖї└s_[H.ўэ}yш╪TJ4!НИщ9╡П ■хWш,}ъЇ└KдHr%t@IвМпX▌ш^nK▄]Ь\"fэB▀├'н5&t{Ў?А▒Т▀AYї-|└~iш2aйB┤Жl▀█╟чўЎї▒╓:ТjщС#R┘С▒0╛eрWa√RDЩ]¤р\щ!RPТБЫz\Ъоp╗лщ┐▓яЯгм +4PHЕC}п─u▀$oДжЫгQ нp };цРM√ ═/┐ў`\√ oй┌√ЄЫsW%5ЛиKе╒.┘~ш╥├|"оЭFi^IШО├УЖ┌░AiКФ╔кAM╡д╥FrT]*╤6╞┌┼▌╬[│╠=Ё╩└┬°Ц■h_)6╪ `И8Р╛ў╜яЙ)А|∙є? s1Х$∙д'пl▀~ЙO═eaГЕ Ц\▐T WТ├ЗЙш3в╥-ех ╝О ЄJ┌╝┴╖Р@╗>пА╝Rl░ ┴%p ╤╝ЄWїWb ў╛єЭяш╩+z·ГhцxNвЖХўфEыЮ╦ы(2/╙хбKsч╚9&ОнкЪ■О╝┼{kоДzRw█ИjГм(:╚вFuХ┌XТ╝2Л=яJSЩ╪∙╠sмЦ2┼х╚{вE+є╥>` J╤┬VIУWИm╣Р┬.║Є УТm∙у╡sHф(н─>т·vТ7BKФ$qч&╙еl6╛Е╒b w╦Н+Qv~o}С6И ┴+лєГdG╢ │Й╣v;╣4ge|P┌еi√Г(e%{Є0Шc=>д╤╜ЬЁA,·║}╥*БfфвЖ¤Ж(Б ¤AРgz√Гp=■3рc.╓юl%Б6лШ/да{f│Я╕:?ї╒ЛТ ╥╜┴╪g═╘Руzр╞xvИ√а$$ф╚│\О_У║- wPfF3Н│=&fас] х yе╚╨П,K3щ:Iў╕Л╡-`ОаЬт 7╬пtП+ёЇ┤Tp-=ваЁРWКLoёЎл}#ї>1▌+╖╣ИO>Цk4ъ73╡qЖ-PлMшЛ[╨=╙ba?═~Q┬зёBЎуЬ┬6ЫтTJ,3ЭэьЁS╓?юJmб║┴qплKЭS─<Аy8╝¤тKeTўi&╥{=ю ЪNO╦ rlФЇЭYБ─жБ╝Rd║Bv[r;I╚iР>ў:]■4x▌5╗╘╙я 7л_▐шGЦ┌8улз╒"Ы°LSWk╢Й%8Н} ║о ┌щe,^4W:╝Я╤┬с╬Ъ┌JЖїFА╬▓Я╧╝·мз╤Змўй%Д▄ЄnI╜┴qолP6╕!J╠,с;F╩┘%К─ ╡ /бє╬ЎvЎеD}█s╓╢¤8`oФ1Ї▐│aхr6ekX]{fч;ВI пGї1есЁnф_╨T│╧гX4,ЧXЧ59№rЯQМ╡╬ИЪнF№═№│о╞ E╬К·╗YлЖлAУ╪CUЖXОЪ┴Z8ЪYЕPd2+oЯ.╡Е╠Ё╨┼A7+qu^m│ж▐ 5ЮueХнЇA╫√}Й0ЕРзЖ┐їЪ-Т┐cд·╓ WwЁтС╛`░┬u┌=╗x■э┼И6█┬E}сA1P`╚+E╞ъъ═▓$Х7JЛЎ■убБг╞3Щк 'Rсm!ЮБ╛uiаб┴Н7ъШP┬[/∙;F╩oОжeR(g%№ Ж╥└B▀с(√lP╛╜Ф|ЪЩНЇ{UН┌╥t'╢·2VЫ=$╛k┼z╗BоёёE?фИ(g4╒&bAї-·;Н;Цм╢█Їw╚3U╗∙│RЛdRT║╠Ы╥ъЩмm I╣┴qо+8F::о│─"uФ&Vj║ЪxЁW_ПN╤Б3╡Ў*1P`╚+E╞┌`Н├№¤NEф,з▐'Wb-╩j╦Г╡нC]┘Tл1┘О╕╡╢]УФ░о╓╘┴╒_юзЙб▓Щїр░g√дz▀5╦┤ц┤.QoR{ыT╓╜ЪZ├k:зРTч║ъ?┴zг├Ks╧dO═4Q`Z╔▀1╥}ыРи%rЛ +a Я╕х+7ь9,Н▓Нv┐╓╖hбЎ┬рДы┼X \fкиa Г!ЇHNg{т5Yc ЪхъфЭ│ %ъм╘Э$нtЛs~. WwtР╓4WП╘о-?@═їтzZ*VьaОНюЦЖ▐╫╖-2z?ц└X╪ `И8Рp}[╚3\▀J Є ШЄ Ш╞пX t{5ь0D H▀√▐ў─@╛`╝m1)БП╣вЖ¤Ж└Б`мфw·Г└ьРW└ьРW└ьРW└ьРW└ьp~PБс┤В┬┬■Cш=Р.^╝(жК╙=ў▄#жr#∙Д╝R`z?ц└X╪ `╜═+ .3┼f``yr-∙Д╝R`z?ц└X╪ `╜R▒ч1╣Tцб0∙Д╝R`z?ц└X╪ `╜┌WЄЙюэв√█_М╧┘X╔я М╖│C^│C^│C^0нш╬║КК║ЭQ1 P╛РW╠*┌¤┘░Б╝╓]DБ%╓ю┤и▄AQ\Вnё╝й"{ъечШ▐╙ └XE╖ -█?SРw├╧}IL%╤{ Нє№аш╬║╡d цp╒6√рйglвФг╦к▄}|║╓7╕Ям]KV/r╗ўl86║{╣║TЮетЫр╙м~x┘│Gн─&╞cМєГh^i%Б6k┬tсМє\·dk<Ў╬a_╜4t╗ЙOLч╬J~!пШ▐П90V╤эЪWF╜И╚г ╧пЄЮWфФ1╪RСXzZ*V2Ф№Qх^$bЗ║ФхЦЁц╤M)ЄJ╒kл∙F5y%ш╢t7╚@"ЕВЯvx√;Hk+i▓{<~ ъRy6!·░·;ё√╒Jl"│ё¤э╫>X*∙}ц╚+╔я ЇШы Z▌@є─ЄХ·т╗Двgj}ЫЄЕRвYjkX]╗чpПTЬиV┌8E╖O╬ ╪хЄ╘Ё.Хц░wгЎ╧r╨-╡`0¤^/ y" tЮ■∙VЧЎ{├═щ║cBсъVg╕╙хO[I┐XoW╚nK█dтg^6РW╠И╞Х╛>wU╡bЙ,Гa╤dNo┐Їwy╕┐йл╡=&Ji&ИЖёЖRJ4Kн╦Ъ■ю╘╤M╦xкиopСpT▌·Д9кИй$&цхyаь╨?Аb ╠Л┼Х ╟FЕcё-,╔-.Z╢ЕЛ·▄;дFi++YSK_xРH%╥ДкчЁен┼XVЫ=9+f9¤)&─ЮjWo║ aцg^6РW╠Зuiz|╩Є▌╟ё╢Ч e╘нj∙юA▀Щ|свc╗Ч█3√EA┼┌ЁвZ^K4▐T░┴+Йгy ьЎk-ъ\2K Ю]R╙ыЬq5░ 9ю░iB┼6.╖Xа~гЧxj╘nЪа[╙ecъg^.РW kw:5 р▌YЗыv/█3зт2─Є▌гёЩВ╚M/d╤B[B}:+ЛQ┤J┴й▌╗E═Z▀ /ўH█ёR╟пР╬╕мї╛N╗XжМ]U╒√╪ш╛╨▐щл'╓╢Q`iН╪ХQ#i6>Q╓╢А№°L3СbЗ`ъg^&РWtрЯ6 ч╢m"Эф5ж╨$┬┐лъ6oщ1е<Ї┤ИЧЮ╡ОЭ7&Д■┘чГWШдУ`ъ}bIзЛ╪mVZY√Ч[]Wми|>QS3╞6┘╚Ї▄═¤╠╦Є Аьгж▀ыР?wЫ7╟}fхЙ№Uy╨W+E>╡m[№╫q(qJєJЦн#Йэ7yв^ПнЩ░vИтQ╝╧╝d пLАhVЙ╡╖zBRcпєрeuС°x╦K├ЛhVЙю\ыц'Х╘∙/йЛ°q\╫ Miд╚▓Н!бU#ПКўЩЧ фАЙcЭ╒╝╤%░f╢Tt╫D6ё╖ДS:sЛНлфН.з\sеТЮv╜0╔ръ╫╓"▒@Q┬їm Lяe1┴X┘ь~╡J■UIЩNШш нqГЄ╞y╬▒┼]▀6ЪtСu:Э0!]ЎTs■╗Ё·КК Ъ^DМO!оokRb [c^╜7∙Д╝R`z?ц└X┘ь qцеОб▓╔+JYЄК~╚+FуЦТфw·Г в╜.Ццz  _М╤\: аx!п┴┌╢╔хoV╟█к╫c`ЄyE█3Ы7ьYбО╖U/╞`─-'ЇШ▐fd0V╤э ╕■аl╤▄В■ ╜╨`Ї@iB^(;h\АвГ╝fWЬуWв=╛г>Y▌ш^^╠!╫█э ╞*║¤o┘■бШВ╝├°Cd7~EG^┘╗}╗|}o√кч╛Ц╦╖┌└С╜CuO╓Є Е&║:ЁJ▀╨Ь┌╟│Eх)╟,▒░шш¤Шca Г!ЇH╚+P╬r?▐vю├ыЯУм"ЗЎЎ╔wI1╪х>ЪЛЕ┼\ WвCД╠YhЫEf┘╥╠╨@ЇКXе(лё+ ░_вyE╩ЬФ_ш№▐╛╛#tЎ╚@ъеGОHeG─BV/▐ь┌'Я[ 0┐ё ХM^╣▄ўєЁ▄9│y╢-.s▀<%EПKo=@чYoQкеЧцмд│ЎЁб├ДO№."Ч╠дq1O: u╩Г6▓ФfXЙюмл█3E yаh)уW╘│~x`IW %▓Ш?м╘Uh╡ЇИтЬбYG╜u╖╘ t■Нгз╦>сЁ╚bЄ░┬╘·G╗ЧЛ╥Ь /:╞ы╪"ў┌Й$ЦЮЦD┌╘ая╠К─№AcСX:zМмр╔ич░(aklCQЩC^╨┴Yу Е<5l°м╡н├nVЖ╙╓√╪╕6═╞▒Ё№!╓╡│╕ё╣┴E╙тТ╟█╞[ъeуVден╒~/dXd1{XIImСS@r яEвХ$▒▌Д╬╫э▄╔4тг─Є▌r&к▓╫ЄЙьЇ▐│aе┤)[├ъ┌3ёёc0▄'ЦТх+7ЇЕ┘ДЄ╨tщвЕ┼|+~Ш8ф╥ЁY6╛Ц╬X█фYi^ЭХЧ┌м╥Zq╙FЩ┐юP╩Л┐Э╪╜ХиqЕZъХ║М╛ї-╡╛▓.Э` ))нh╖Щn√Pp}ю*)eHh╢шiйrє&╣▌"╣Доu╞╛ЯЧ▐Jb{цЫзО-rярu▄сХt>MЫM┤√5▓║┴Р╨`[╕И'Т8JДб┴HЩц▒ъЁ╩<┤$Б╣!пФqbРШГRФ╨8Sы█д┤[ь9▄У\┬╓QГХЁма4░м╪#вВ╝V*4╜╢z 39kуX╛{pїk"ИU╣╒╓лVЮ°р(r╚+%eйЧ7ХdD├ ═<° ·╞ъчщiй╪fЙKХ."(_╚+┼МїнИ■ЬhўklHr Ы╘╣┼ЕХ░ з┴Ъa +4sЁжщy █x№pZ┤╓═√Эв;[ф6ХЮ├{ТФ▄я░└Ї▐& МUt√ °ёуb Є╬$ў;мл█╣?!<░╞╖Ф96уу<J╘Yй;IZ╜зЕu▒В ╚√■¤dэZТ╕eэЦ$Є┌:hnМз<жЎyЖ7єiyб·Juu0Л№▀яyе└Ї~╠Б▒░ ┴z$▄ЯКю╧ 9 ▌╚&wГ╚5фХ"#▀QП╦эMЇ4Вю╠Й'8═bЩ╔ЄЎЬа| пЗ╖_║─З$W%Ую:▄ьsй─ж8Ы+Йуz Ёg^ё7OGc уW Lo╖╖╙┘▐Сpх1(jтN(┘u╓t5)1EL╩ВnKwГT╟cOU$╚+щ=Р╩*п$@^)Jz?ц█W┤IВ╟У■ъcЦЁF╣БЕ╡░tР╓ЇЙ$юQТ пф╙ў┐ }1yўэo[L%╤{ !п@9C^)Jz?цМ╔+┤Difс[@^СЩ№]@є N -ИчЯy└8Я╣,YmЎРgЧtJNм╖+фjиO.aУВZтhZ&еV┬■Я=ы▓!бо╩Щ╘█5ЩР[M╦К#м@▒@^)vї╛~o╕ЩЭ▄╠╞Я░с'╔%4MxjXБЕ╡к╨k█&╗(iН╪i┌╚ ╓юд I[H}о▓їFаsДДж╓XfxB─╒y5M+ @Ц╨T`zЫС┴XE╖ ╤T(ш0 ·Га4б}е└Ї~-c▌■G√Jба}К▌бCЗ─ФVнZ%жЇ├∙AEIя╟+Л¤╧N▒╥Ь ЮюЬк1(з\e<ў*YR^щiй╪ФтЖ╣I7┬ЕЙA^╔оЧ)┐╒╨╝Є╨CЙЩЙy√э╖СW╩О▐П90Ц▐¤╧┬Кz┴╜а█╥╬:▓p╚+тF йnЁO╙╩сХ,зho╫Б╝Т5║ы╨(ШOщv8Є LИ▐П90Ц▐¤я╢8л╡∙Дз vїхОт╢,┘hяx└Va∙Ж▀Й╔їТ7№*Н)╦zхZяПЫ║╛.]╦П╬╤Ъ;тHЦ╪╛Bє╚Z▓?1п(q%]╨ y%kт╧чс?єРS+а+п|q°╧FGG √П░▀tZ3A▌м°╥ї▀·.+╘(H^┴x[┬Єukv╡Ы╚Y: Ww╨Ё?<▄щЄ7│є╛нmiЮЦ╪еыс╨├юУ└∙ъ─·m^═3├├Б╢%mЫ\■nщФё`╖▀╡i"н6*█┬E}сA1ЗцТ╗┐№▀я╛wї╠{ xц╜d╣oХх╛&╦}_ тW╛■┼√■p╓W'▀■╡иZh╚+FPrL}ГЛДг1)ЯHW╕aM*┤ ╓█E╝╡яKF╫Х Л+q╫∙╚ ▐оBn│╢·Ы5нh~│r^пЁРWt░З║ziQ─вaGї1гё>ч"╡еЇ{3_СOГCуJtрLн╜J╠─С╥HЖ░r█4qy@ПН^тimЧK╨]у!ёDмm─╤┤ь╛д;XЧ5qЯДD╝GIR┐╤nnП╒УZtg]EKЫZ╛r├Ю├╥T┤√╡╛E 1xRгy$}XСf═ТXРWt░╢·Ы║°н ,6EМЙХяx└╩hQК;░б*№> Ф√+bhMЧЯ;y в▒╞С0F&-ЦOк▄}}юк СS╦w#+*и*ўвc89и┤╘▒Vп;=ъvFеI▓[+╡Ю├6y%НлMV╚m╙─фЭ╘Б┤├№\Й4fV[VяєЯ/└CНfM▀7┌Dб\S╠w%╡╪дe{ц¤мсX&aєJ6Y╛[│JJнoР┐╢Г╛3+д╚B_·┬Э╞r│╕╪й┤a┼<Н+Є АЙxbФ>Shd┘╢3╩[Jд▀;[фf)GHФжП╢P╥╥▌╣V4╬▒┼jey]uSlЩh╚a[HlHa9y╨W+ца╪╨@Т)мШ(▓ пLШUm,ЩаzЯж═`L члў╣├+yгZO ╗P oЖY¤┌ZIVРc╝И╓░=│▀W+5╒Ьzf░Еu▓r╓d├│Й║)█3ЫхёP=Зўl╪МЛ∙Ф·g+г╖G╪ o¤F╘.(фАRQы█─√ вg╚i─╘G3M┤√5"/M└N"Лl лky6Q6E╔╕Y\YЙ╞ТУоeЕМ~·╤/'▀1╡rшЙЪЕы█Ш▐╦bВ▒Кn  √▀SРw&╣╛m]▌NїВ┼Qщ·┼√╔Zх7_─╦╡ГZ2Фh╤щкЁцA√╢°╩╥%УПСЄuУ$o\╫╖═+Э╫╖Э~▒m╓WSЖZЄё╣7f▀W{∙¤╛ыў┼НP┬ї°╦С▐П90Ў?BяБdP^б)ВuёьоТтВ6п(Л╘p┴УИжD-P+Л▓M)┬MХЫд╕WХy┼<ЇцХПZg═kHVшяЛ▒7ю▒>B▀°╩╦b о╟cС╔J╢┘S6wP╦wKg lМ,░вФ░1*╥╕ZP╖│JйЬЎx╓QT╗║!E бY%▌yї`~4Юд +ь7-р┐MyаШиg▒П╩ №|цД│Ъ┘y;2^о)сЩДЯї╬кЛд% Ывzv╕I╩╕ў8i┬ШШXRЗйH■]x╚+0ЬTкд,Т:мHх|)лTp╚+0Жх╗╤vRвFG/Эы╜t>pщ\Ёу╪O?>ў╞┼¤∙┘G▒УЭ¤9M.ЇўэКйвrAщo╗w√ЎKb╥╛ъ╣пe9T,│╦}{_~У?╩▄З╫?Y;[ЪLэёНЁ╔ъFўЄ°╠%tрХ╛!>-ЦjЛифu Gя0=0Ў?BяБ4Сё╢E'▌ЁO╚]уm│cЎєГЎюэ[╔─└Сэ?ЯУ>ML└└С#фkRJ <|╠й}№1[TЮr╠ EФaЙdЦR╧AB■y╓cЪфbЮ─вўcМЕ¤Ж╨{ Х[^SР/╚+r^aaтW<ў╡┘ н!дoяaЄр▄7▀ │ШTKЗцТp°▒пZ?ччla·ЖЪ╦l[Єуiйq┼1K;═Чкq┼&j╞GУф5 Lя╟ √▀X╟ПS%ч╤GSйш=Р╩*пАI╨╝"жМPyЕu┌╝ў`\█0uC4б╠MО ъRi╡╦G╢║─ ЭH╫NУ6оМСWx , KхN$є┤н0z?ц└X╪ ╞вy┼и/pжB┐M"п%ў╫_╣Їц╦█5м╨T!Хl?&ЧЖ.╙Т╣╫)я┴Kмжл═Ю3WЩр5 бП▒2UО╦,╟КZ·Мї∙|,╕h╪Ц╗й╞j╢м7>(zЄ╩▄З╫?'Q┬ K.R╔·ЗУGцеЩLpt╠,╟c,ЧH╤Д╬╬YhЛы°Щ5Ых╡╦WоЁY0┐ЙЭ╧╠ЫIшЯ ╚{ЄйCЩЧж─Ъd╞+│l iтИ^!WвC<С\ Ё∙|BЪэa╜?╒┐яШ┼:ЙфE|2{Ц9Fп@7o▀S╩+│k `ош!:<47▒%є╥4дd#w;m▀╛╖Пu"%Ъхxм▒Ъ ї╜"u°T7&МЬНЎ╨фB)├n%м:г nс┼`F╫n][ў├o|єХ?єPЎp┐├╙;LМЕ¤o,C╞█╥░▓■Gk╖6nє тя=0┤пэ─В┬┴x[с~ЗPЇо▌daх/ rя[М|pўЧз<ї╥SbYv╬я[╡j▀y1┼ y cxфУw?{G╠H■є╓╡uЗ╛ё|╙wў╛=╛∙╦│ ■■▀>ївX╠╨Ї1oЄ@∙APБщmFca k№¤AЯ|~х█Б Y1:∙й?Xя░№7Zrэ╓o6№фЙям┌№П^9ї┘╧FЖn▀q|fўє=Sя╘▄╗Д╞ХgлIдъ┼CыцЛ▓1░U╚°лзД■ (et╜8╚╜s`,ьcН3п|r¤╩ ?▌║■лып▐║·7п ї╖ё,Ъ■;ыП╣Z█~v%x·╞[╫?№№ъПI╧єпOйЬ"╓СЁьёЇрТЧкN▓"т╦┴Гз┘╥5■ ▐√SЦ╨╝Є╚лЦ >}┴╗ФЯЁ╠'фХЙ└ї°єп▄п╟╣аўcМЕ¤oмqцХoxv╓я~a╓}_┤▀¤;7╔╚╬ є╥&}a¤cOЮ╣Ўoя▐·╖л_ ї >Ўн╫з▐СpWX╣йфЬЬ7h┴ТгН0ЖСССЫг$├н коW`╔√<єлРiiм▌┘J:mVЪ?,;к√┘Фа.Тз;HkBЙ2]┘╘_╜#C¤фэЫШ▐¤Щ╣>dv№°q1Ur╠s╜╕║║Э√╡@╤Эuk╔■¤d- ]хю и ╟Fw/╙2^▄]HЗы┼хХ╬ы┼¤¤┐Nz╞й╜ @вЭБЮ·п╖┼М ╫╖-Gz?ц2,eI├3Д8╝Ъ@б ╚+zў?@JzдцХ1│ЄJi╙ЩW:■х╢√лЩ╞а√^?▀·{Й]1╕╛-LDм╖+фт=>Tg|ЧPк."Х╒fyvI<╥VъщT(rЦH%╥Д*╪эw4-+Ж░P^l ї╣w─wd└╞█ОТ ?o FccW╝Y╨Р$Фz_з▌S#НХ╒6┬ї>6nЕ/┤w·ъЙ╡нCXZ#ve╝ ▀АеЩtG█ @╣Y╛{╨wfЕ█Т<тЦ ╞нrўI#`F╛@y┬∙AРw╓╢@\Жиў ╟g Z 7╜╗═ЪPЯ╬К┼4н─|>Q╙сэчEr╚┐─│Ч∙∙╠╩Y═ь YRзПva╥└(GЯ■∙MJ╥ ╨ вjб!пФЛ ;uY╥LX Ф╜t╫╖U~hQ╡╨РW╩Е╥╝ТeыHb√ ╜╣w▌▄їу_■CЁ╜C}gГ ·■йЁЗЇЗN╨YZH╤ вjбс№а╙{Z^#╜ЇюOьa╚В▐i"чЭtзл@Ош:?(;8Я╣щ¤Ш├kдЧ▐¤Й= Y╨{ Х[^SР/╚+`<╜sxНЇ╥╗?▒З! zд▓╩+`4пИ)# пФ╜sxНЇ╥╗?▒З! z$ф(g╕^Ф&ф0;ф0;ф0;ф0;ф0;ф0;ф0;ф0;фХ▓kw:█cb&7В╙,ЦЩ№╟eКX√Ї─е▒)N╣Д¤╕+еR(_╚+%┼i╤J╬FгYG<Ц%mцбсг╣Т8оўцuєЇ╕К┴i5Ю╔─um8a)+∙Ф ┤▄ K└╘РWJЛ├█?мЁ╒Л╥Ь9▒wЄ╟ъ┤{ZS&ЦXoeИ>пж+╣╜мщ!У╗z╒г.╪═┌N\ #D^9KИїF└GK╖кИ)(O╚+еNmС)ТKHdЧ(н$Йэ&RЯQ╗Ы╬╞╖t╘√фL┤а┌┴'ЇYPM3 ёKйЕ G┼1t╧┤XfxB#Э├W█м╝ ╩ЄJi yjx╠`h╢║k<╝ д▀nfa#╣Дооюр%Д╖ТX█lЮъ┤{vё:ЮHЭO╙fыэ"M╦Їз k█5▐уc▒╠dCї╛OЗЗ?эtU6зїeyе┤$Ї┼вaЗwг1м╦Ъ■ю`r [KD Vb2jK│ЯДгм═E^+ЪБ║Ъ:▓k╣▌`╣DК&tЎV╙▓█|╟`ФF(O°3IhXб∙ГЯ~╓№СQ╨m┘Q▌HЧVм╦FшB]Х12й╖k2O$№Ь g╗цЁ Nг╤И╕n╨э╨еЄ"╛ ▒█тB ФфХТf╡┘Cв?'╓█r5╘'Ч░IA-С[\X √м&SXaм7Э#$4╡Ж F!о╬Д┴(Хn~╥rs%;'HfЄ╠РNfцл|Ъ√С├`jЇ+┤Ш$dhhh╬Ь9b╞мш11U<>╒ьф╔√<єлРyй╙┘▐СXc ¤гO╣:∙шУДuVъNТV║YG+p╣H╕║гГ┤╢Т─-k╖$С╫6╜√3s}АФЇH/^\╕pбШ(3ў▄sПШI#∙Д╝Тy╦+РLя■─Ж,ш=РРWаЬeЧW╨fЗ╝fЗ■а|@Pщ▌Я╪├Р╜╥┼Л┼@Y┬°УB^) ╜√{▓А └X╔я г√Гb╣┐┘/ФЭy%╚n!#рщР:ЄJм▌ii&тv╝├├ЭD║Ф;@Ощ╚+╜]─█п^hT▄ЪЧеШTэ- хR?QК№МEG^щ ┘mЙ/Mu│_&хMА3▐р =уW╒ ─Ф,х═~й╘7╬pГ_А┤tф{илg■@▐щ╕■JМ▌▄╬.юЩ╟║|▄─╫╨- ┴еElid╙Ё╞иУ▌╧╢+u╣w├├їWpQ]ЇюOьa╚$╚Су╟ПЛйТєшгПКйTТ▀A·о'E∙~╝№v┐jС|│_ЮK╥Х│Т A^1°C╨╕Ч&У╪g═╘РуzрЖЇ@Хn╦4щ╨М├√Yаэ╢и#╩шБsm╪7"ж'@я■4~C└Б9Bє╩C=$fJ╚█o┐н7пш┐BИ╡-└╞╨r╝ЭE-Тцщ, ╦ЧоrI>'Л╦├йXЪ Єд}4MfZ┤AДгqdШ╣╧╝ЄL╘ 4ЬKЬ╛╝fчЁЎЛШHёDЩK┴n∙В<¤▐ЁО46ё╤╥Щ╨R2тm'╖mv·{rф, ╡7q *╖Тx@ЩB^)u▒д ф$ЧР╚.Q$nжаVс%t>ххs─Exи│Сз╗ПCl╩╓-дFУа{ж┼2├щ╛К9г▄╝}SL'фХ╥Є╘ЁШ┴╨lС|!ЬФЧ╞ WwЁтie∙Dэ═ы┤{vё:щ.Я├│MwC═9╩А╟╚29Ъ╘√>■┤╙U┘lЩО K╫n][ў├o|єХ?єEyе┤$Ї%_'їеqЪx\`%!╓3г6░4√I8╩┌\╥^>ЗgЫЖn╣mf╝В╙фA-#ЭbоjAї-·;┼ё 0Q4мм ╤┌я6■х┐№ЕuэOИ╥bS|╪╖яb√O╜X╨░R╙╒─ГO?∙:ї .Юt╞'6┼┘\╔&╫√Зпё kЯюlчфд▐о╔Їv█mi▓tэ& +╤°Ч{▀ъ°`фГ╗┐<хйЧЮ╦▓s~▀кU√╬ЛЩ№┴ў╫Тf╡┘Cв?'╓█r5╘'Ч░IA-С[\X √j▒v╖▄жьЎ'_ ШУ╬тщ$4╡&бЧG*▒╨чФўYз╙ 6═╞пWчз╣0 P:ЖG>yў│w─Мф?o][wш╧7}w р╦Б╤гсЫ┐<√яя эS/К┼ Mєц"шЕ╝R┌ъ}lФ ы╪aгV╪▀ ф°и╓кBKмmЫьвд5b╧╨╛b╡y╝L3щLw┬║t~РцЗ=жїF ╛p8pcI█Um ┬ └°}Є∙ХM╜юЭ?єЕЖ┴Kо▌·═·Я╕╛√?╢№уG√ еЄЯжW╬Шё/ўtn·╤М╗юцШєo%k╓РгoШ>░ш╗^NяeжЁщеwbCp AОМєzqЯ\┐Є┬O╖о ъ·л╖о■═ы¤эG<Лж ╬·cо╓╞╢Я] Ю╛ё╓ї?┐·c╥є№ыS*зИu$чўнzЦ╝°ЇрТЧкNZ7_Ъм&ЮfK╫°/xяOYB^<Ї╚лЦ >}┴╗ФЯЁ╠зЬ_/╠цЕnЫ5яюW┐й╝·╘>╣х'ё°?оy№ы▀▌°зsS#w▐╕ыjяш▒═= aЕ╖о4>2йs═iеЕхtдъ┼ М═AЧчD╩f■║з╫ Hs'╫<=■░ТфАтЎ╫П╜8pЄ?В┐ь¤щїc пrx├уO■щm╤I┐·┐╙/L╛}че_э┘Ї·╘;жК┌ W╤Ц┼R E Iф▄йJ8:+WЬмЩ%зРWКЮ ╧Ў▀ufю╗ГяЮо№┼ХiЯЫ:pщюo╤LrЁуЯ<}l╩фд░"┼Х╙з╖.ЩG╣╡Еe▄x`╔O\A^( ╛yро╛┘┐~oш╥╠По╬╛=Bў|px¤С;+ю5т░╕▓╞AЁk║Д8ЪC7>rпШcТJЦ╢lЙ╕\С--╣П+╚+ет@█┴[╟я╕t·╙╗>╣√╜Ч▐ ╤]Х$eXсЭAЪа┴╗Д> D┤╕╠Ыч"~>В6╣D1 С╞┼JwQn!пФ Sп\ь■ф?шuэлУGяе╔цп;>Цz/Zw/!Л╖Ьф-.№╘*бD╗тЙ▌[∙Ш▄C^()√Ю╪▀■ї=УF┘Е┬s*/' ╚+еf╥hV▀]RХh,ї*Н09З╝fЗ╝fЗыёШ▐╦x╙еb ╞-є■╘╡ R┬Б9r№°q1UrЇ^Пyе└Ё1WX╪ `H╞J~б?╠y╠y╠y╠y╠y╠y╠y╠.ёzqb Є(∙2Sb Є√ Б └X яй╕╝`Bшs#ф 1фHНA╛IENDоB`В././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1722935059.1447053 guidata-3.6.2/doc/images/screenshots/0000755000175100001770000000000014654363423017131 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/images/screenshots/__init__.png0000644000175100001770000024545314654363416021415 0ustar00runnerdockerЙPNG  IHDR▒Щnлм╢sRGBо╬щgAMA▒П №a pHYs├├╟oиd еIDATx^ь¤Р\╫}▀Й~GК■D╢d)"Mш╠╔╘n█┘ЭТ¤`╧Ф^ ▐╞к0Ї`гr7ЩM8ЭкLIP═У╓┌)AйIUzР°└З?ЇC▄ўБКА¤б╪wА╩Q∙ёE╚■▀¤■╗ёзw?М{▀¤О√Y|ф#▒%B!ДB!Э╙▓ИХsЁ═o~П№їўтЗХЯ└_№ш▌ЎИЖ╦f┬И+&ЬxhиВў╝ч]°└╗  чэГ8~№чpЁрAs6!ДB!ДРн iXk Ї█#Ьjў┐√w ?ўs?gSъQ∙є? є█f╖К╪ЦЮЙ-ХJ°ш_}?▐ ^ия╗ сЎ{pуерг?=ВЯ9Є▀тk7■8М╩╗юCф╪/р┴З &■ь═8№С М?■у?╢е╘УK Щ7nё∙▓=c@(╧#>Gs│sH∙ыGZ╡w╨┌E!ДBH гB/hыGT└> [Fм6вizL╧┘NZ▒o╜ї~·п:▐╫═{?└┐¤├ Д°╦°¤  Єk▀┬7 ├ЯрG?·1~№уЫsФ┐Їю ■ъ╦вФя┌Фz ю`м Й2%чєъT─ЮFЯй╚V+лh┘ь-PB!ДBї└╛╕·o╖YW└ъ1?/mз┤╕:qEDй│ўч▀}?∙я┼e╗ўЎўёыччЁсЯ·Iч`я~╫;°Л┐°С¤D!ДB!dп╤(d{)`ХЦDь√▐√>╝¤=╟╦·SўЄзocэ▀СЗЩ4?■№√┴√Чэз1с╣nxq 9'єёZ╚q*з▐╦QdСG:*i)=лёУ▒=▄zхGzчOё├╖7ЁоЦQ■П/╔Ц╟{■т╛єъ7ё¤?┐ГЄ╦┐ПW╩ ;~Ї╝ '╞З?№a[J Ф7Pp╜Т:▒F│╚K@t▒ьhЁвOaчДy ╡юX╙V№FжfD\╢IX╣Фн_╜п┤tЮ▀┼)ШGqуFOМ┘s═ч6┤+LЯ&1юК·╚fТyh╖n[ЫЫН!ДB!╗Ду ▀╚Ўс !ЎЖўВЦDмКu яG∙Хяс╜щ╧qф├пуg№~Ўгптg ┌№ьGn!·WЛ°р√╛Г ▓∙.╝ў?ГЯ}ф[B;$▒тЭ\ъЭ4Л'йзp╥╪╥4┬6ь{▄)wож█1TDП┬╢нДL╠ж7╥ъy}EУ1#ДB!Дь ЯБїЖўB╚╢╕░Ё▐ў╛'NЬ└Г=МW Ї=xс▀ $■ с■▌╞_┼7╦E■х■р |п╜┼╧ вG╓И╔╢И┼▓╛сиСйUФD╒МЛ▒9нЬ╙н;Я╞Ь┐х∙YП4КсШї^*╣k═=дAeФК╚╟Же$б╝Ме╝I▌Jлч╡Кэ╙kоиС<Ы╡Ю┘эj│a1!ДB!√┐EЬz)d[▒╩╗▐ї.9rё┐ї╖ЁS?їS°┴+╕√6ЁЎў▐Л▌ Q1ЇчёёП?К}шC6G╗$░P╩щиЪкЫ║Tлс╡CИжG0c▐gУ└x╥│ R╙s┌Aъ╢╧ЦjУШЁД╓F05S;6tM}П═(#1Н ╥Иj■╔"Fк╓Жv°Ю╫)NЯ\█═єло'zЫ┌▄l╠!ДB!mQ╜ЯЎ┘·СЯ ∙Я▀"`]\!лчl'Cwя▐н8p└~lП ЇЯ■JеЖЗЗёСП|─жю4м7Z─╠nДB!ДB┘┬╜{ў┌є─6Є╙?¤╙8~№8^yх╝є╬;6uoРЫK╫┬z !ДB!ДЇ]yb]~°├т√▀ >ю╗я>Ы2Иш{fгHWЯA╒┼КшЕ%ДB!ДР~A=▒█"b !ДB!ДР^╙u81!ДB!Д▓УP─B!ДB(b !ДB!Д ▒ДB!ДBКXB!ДB!E,!ДB!ДРББ"ЦB!ДB╚└`▐kў !ДB!ДР╛ЖЮXB!ДB!E,!ДB!ДРББ"ЦB!ДB╚└@K!ДB!d`аИ%ДB!Д20P─B!ДB(b !ДB!Д ▒ДB!ДBЖб╗wяVь~ /]╢{ДР╜╠3OЯ│{ДB!ДЇmЙX▐▄▓╖сuN!ДB·Э╢EьЯ¤┘Я┘BH┐pЇшQ▄╗w╧~ъКXB!Д╥яЁЩXBH pчN<ИГЮ-}╙█n┤оSЧp╟~lКўЬV╬'ДB!=З"Ц╥_Ь╕Аї╗wqW╖ї (ЮэСР=№4n▄xЗэ╟P┌=ЯB!ДЇКXBH в┬QЕьЬ╟Zї╥Ю┬%ы╜sщT═{[їЦ▐D║zосgOъе┤~NужыY5 жС>х9╫Иц;╕Ї╘yмнЭ╟qI;5;[яХ╡чЪ▓▄┤j∙▐r!ДB╚v┬gb ┘ ╩3▒п┐■║▌k╬o]┼йзАп╘yu╖жя"cNЇ№уч1|╒ж╒хg╓oрiнЇf╧W╡<ў╡з║┴EчSО шу╖жqўЩr}∙Z╬▄1м{┌╓~B!ДР¤╠<`ўВбИ%d0H"6Ё╦╔+mRU─о├ЬИ─5ЫъpN┤дК█│╕lЎо╖Ь╞4ў│К╪║s╡╛9SQ O┐є5]E┤┌╫Ш^W.!ДBё#Ї>╤├Й ┘д╒;9и▄)гxтОш╛ўyY│йh=ЙМ┘W1ы я%ДB!{К╪W^└ /╝b?▓ўpь` ┘ЫHkИюЇ╙8|8Вс╡є╕╪иPя\B┌<лbvNQ╞iЬAУs[сц2.Я8Г╙о√tэn█]CГwо?З╡scОШB!ДЇЬэ▒п,тS╟>Пь╟┴рПЁЫ┐■╒│y╗y/|■S°╘тvИ∙Ё∙cЯB│в^Y№О;цlЯяQПфьН┬u Дм]H╔YI├z▌чZEд┌╒К▌ЕУ╠"N"*q■╕M;Оч╬|O>Мз┐Rnz╒Ф▐oЭ·╕л|°iLЯ╗М│Т~ъ_┐aNm┤у°∙a\н>xK!ДBz═.zbEь|ъєM┼NoЁйясI№¤_¤~╙╫Р^╪┘'mўЁG▀┬╖nў╓аЗ'╖n▌┬o¤кM╪u·o╢п`═d2vп╧ЕмоF\.l[ri √6╤Є┬чmшиlЯ·№"\ФУ■ы°╛Е/Эr╧йЕА·х3╝ ї}кv№╪з<╟_Y─ч▌cПWX}°─зё╣П} ┐█аbCє∙╘gЁ▒3┤╠√хїЄm=V_]Щ▐И¤їvI∙rОhЛp√лЪ_П╦╝h╒ї╘ЯьЙ9╪#Е+Еlш1%ДB(║{┼О>x*З─Н▀┴ф├·ёS8їег°н[_D╜Oя,~■╛Д▀Єx√T¤&О╪╝═i╠зy~Я╗БЄK╬ъE|╪фwОс╖nрЛЯxX4▌чqъ7Пр╞яL╩юёА·T┤╒ЭятЧ/и╛ ;Х[Biь∙№)∙|Їs╕ёiнЁ|їл┐Л_∙┤МГ)?╝>Y┐yф~зzВ█╛[╥>¤(¤c>ЖНmнэж,3G╛Дгn9zЖ╘їыusAqыє?Ў╥ьВ=єКB!Д▓/┘╣Wь╝r▀·X┐do╚■е>ць:╝Є?П╧■л╕▌Nеo╛ЗqD*°╓Ч>#"эўё╩+ТтКБW■°U№Кг┌ЁЁ'~┐·н~┐╬ЕА9 K°jло▒└·ьь┐~yхўС√╓╟Ё╣OЛXТК~°°т]█)Я└п№*Ё5ыЮ~сw┐№ъп╘Дaр╪~ Й┌д@B·bуПBв╙ё█KsРB!ДJW"ЎХ? Pъ╣;їЫ╕Н_┴п№╩пр╚╤:iсO`>√ьцН┐П_9r┐√ЫзB2┐Ж_wC<Пiшf;|Я■▄╟квн5№ъ ││:э╧.°─з?ЗП}эw┼юрhX+a;░хш_wХ^эП▀▐ЪГДB!ДР0║▒╞ыхё4╜Є√9|╦┘u─┼╟°Їф'Ё їLm8GъЭR▀Bюл/рХW^q6I ╬'ч╝ач=,uЯNш2╖0N╛З :О╩?√▄oс╞-fk █▄ZЯЧЗ' >~їk┐┘фY╠&∙ыУs№ьмlK#Б¤b╝Э▀┬Ч>#b╩-oKЩэ╒g0х~ ┐╗°╗°┌╟>ЗO[ █┌╪*прЕ┼╧рK▀RядMVWцЖИcп--Н▀VЎ┬╘░уOщs╡цyZ}=СЖ0╖ё,1!ДB!√Ию┬ЙЮ─?∙ьB5Я┬WQ х4В_┬)уС· Ёў╒лўы8U]ч°в╛kE╙NЭТэ3FИч{_¤u=╫n_┌└п~юЯXС х▌°-═╔╣о'╠╗рОO}їhнИA5№Є╒dз╥К-їў╦├Ш№'┐Е╧ЙШ·u╖╬S_mбэ·Мжc√пMф╒Ч╘лш]рщa№Rтc°┌Ч╛&ЪюЧфУM [wёвS°═█ №╓Н·чSЭ№_│╢╢╥ЯьЕ9(в∙[▀║-"VїЄ╖p█┘С╤мх$ДB!Д8t╖░╙^─,t╦┬@;АЖ▒кЪlрcю"I√ЎK(║░╙··║¤╘>GО1 ra'B!Д▓┤sЯH█ їДm╦JLДь ▒ДB!dР┘╣╒Йў*░ДB!Д╥ЧP─B!ДB(b !┼П\┴ц█▀╟нo┐ЙW■╧X¤Пj6▌╫4=жчB!ДР╜ E,!d`°╙7┐Л┼▀ П8 ;▀─╣╦  ▌W_╞XЎ?ЪMў5MПщ9z.!ДB┘{P─BВ╒Ч ч~є|■w▀└╥лёЄП┬╜■╩Q│щ╛жщ1=G╧╒<ДB!doAKщk▐№єрЯ■ №▌п~ /¤(К╖я;В╩я∙╦Є Ўn`h╚┘t_╥ЇШЮгчj═лeB!ДР╜A█п╪9pрАM!ДЇ{ї;*B ┘я )╛{0╝ў'ljЛ№Ё╗°Й╗e№г_·0■ё ¤o┌─~х]°э'>АызА+┐ЎОMыРW▐З'╬╝H┐-e¤╪&:╝Є█?Й3№╩#▀╟sW~о╟▐_pМ!ДьG°КB╚Ю@├Б _/|█░я∙АMнчЯН¤Ф┘Ъ"y4пЦ▒Эб┼/|сОЧэ я▒)- ┬Є O№$╛ЁВ¤▄╚+я┴їЧ▀НЧп┐ п╪д^ЁЁ/■Щ╠ўё╔GlBл╝Ёз═ю&В√╖[54мэ~HЭOTы№IБ"Ц╥Чш┬L ╙ Ўяёц}╟мЖ ╖ЛцС╝ZЖЦ╡=Л=╜ \╢╗_┌╤Ieй╛<№\Yы╜Ў║=№>ёЙwр°▐█чСЇўРI┐ГG^~2g~▓e!╪Ўж╝_H┐/?Є╥Щя!¤Iрхrы▓┌ппПшrМ!ДР╜E,!дя╨Wфф╛їЯЁ}ўГб!─ h∙?Ы-)C╦╥2╗~¤ОёШЯ№дЖ№КаїК89ЎЕ'j▐╩'╛Ё>у94Ю█3яЗd├╫╙5╧в#5М╪M√`ЭwW├Jkчi┬√МgЄЙ▀v╛║лaM{тxбQP▐~╒У┘▓╙ы¤lтmН№Мм_√о<ў}<Вwу·█▄ЎА6╝Є.Ш▀ "яр╫D╨¤┌o№y╜└ў▒3м╛@№┌n√▌╟цЁ68уў|┴ЧЄ╛рМзё█2┐Ё█╢l═█кg?dМ!ДР╜E,!дя╕√▌ре█Ы°є┐Ї!ЫтO`8▒-K╦╘▓╗сХ?xПдwЁ╖у|╥#тМчЁ╠ЁїЧДGDр~ЄУ?┬╦_■g, =ы╜Ф│М'3г█ЁЛFС¤┐Ш╥╧[├G■E═Sл├й√G8¤Л╬sо m[ЦФНЧ▀ГЇч╤ьЄЄ╫▀ЕИ┌"х╛№ї[Вёz?┼∙ЬС65═ЎЁ;8нх▐v<Ю~╢╖=а  )щCИ▌Fи╜р¤sхogX}■┤]╜ф▒OlQ╤╣ЁuрУЩ?╟пЕ╡┴ ┬ЇИ┌є▄Ц▒╓ё, qн-_╧╚q╧ЕЗ@╖1FДB╚Е"Ц╥w№Ч╖■+■├■ Ttт■ц_{┐┘┬╨▓┤L-╗s▐Е?╕.вM╟лиRсq▌z8_xD█qsх7╛З▀P╧с·Яу7>!"G├CQЩ`<ЩЯp6WW=l?o 5B╬н├н√П {■═ВИкыО-x∙▌ёфИ╕▀P[╛д^S╘ЙзжШ6№щ/}┐ЎЙр7R"╩дО:o│/═m k{P>!}°Ь+╘╥/uАЭсї∙╓ЎOИHХ▒°zц¤x∙У╥з2о5В╞сG8ЄЛя в ■=¤╖#3_vdМ!ДР╜A╚_KB┘y▐■пярO■\v▐¤^'a;Р▓┤L-╗s▐Н█зк╧Гж▀Пп█¤?ЁИP╥&Я°█bпй├ c~ф┤d/|g─Ж2~МHд~т-╝Єn^█яFцМ UMлhФ67H╓C;╢xi!▀├*╘о▄├si╟+[єT╢hg[tPfзmяК^┤ЭB(b╦9фrю*-ДР~рёc▄√A┌╞п()K╦╘▓;╞їВ=wыы▓yЯ ¤ДЖЛ▐╠|╨<∙Е/шЎ╛ZШч├"rфЯпзuХ]ч╕є,у╗ЁВФл█m¤X╢Я]Qb╩Х:>зб─я e_ЩєК╦╡╗ъ┬}Чу!╢╝|¤¤R╧OZбS╦л!╠?г╞╝№~|╬╪ЄУ°m5╘╢AqBвu√■Ю╟ыX■c▒э╖?`^сгб═щ┐ўN╕->m╠з╧Мjёok▀╝ │zб╒Ый >╠N▀╛ мLлi #╓РaПШneВx9у╪ўЕ╧9╧ёF~ж√1"ДBЎ:н¤ХmХЄ<тC)фь╟┴`│гsfєvSF.G|~;─|йб8ЪUЮПchh╚┘R█▌у█┘BЪє┬┐Q1(т┬НM╡b╔y.Ї№╞s▀├'∙^~Y─ю╫Энц∙■^╞y╞UЯХ╒c╞{Ў╩{░РБTїь╛_Ў?АЕъ│╢"ОD╡╝млэК`йъ┤_√БyОЄы"В╥╫БФ +}72 ЮЬМя╞#П╝ГМ╪ц╒9Я°{▀╖╧a:чm┌рMЧнaE`]щМ}&є9ч╣╨p[Ъ╖=<▀{Р╤cжo~ДOf╛gЯC │╙зп (S╡■аa─┐f├КE л0jC+/uzфУ"Z╡.ї▓k∙ЮAъtМ!ДР╜╬╨▌╗w[Zкєте╦xцщs8pрАMiВК╪h3Х$lТ?"vт╫0╝╕А)╜ ь9■їхRCШ.a╡й!╜░│┌юP╞|<КЇ╚ * с#М╘54Лс╥кo█┤┐G▒uyёk├Nўїю▒╛╛nў┌ч╚╟Еф^ч╜в╒ЧX ╗Є╞ў╒ЧqяГb╫╗T ·є┐ ·OЫ o┐їЯ╠┐╛№°G8ЁЎm№пЯ~? _КРЮв┬°╠√БЇ█╕RїРB!√ЧVяХ▌¤∙6_░;;ДO}Йё$Єщol/ььУ╢;D0╡Z┘fQ╣╙┤aз√Ъt═ Є{Ё╫~Rv~ЇC'a;Р▓┤L-ЫB!Д .]Л╪Є| q"Gj┘&Z╘уцЖП╞Sєp=ЭЇQdСG:ъЮS CЎ╦g╚I}ё┌ёб╕чxy)ўX#F│Р Q*┘mq╩<УfОE╙└L╔ф-═щIGД╒g╝x3Idgы┼Hp> ·Вь ╖┼ ~╤3)bm$cы[┴К(Й1aї╣eо$mВKb\ЄxД}юЪ|Jb▄fЇ╖E╔г0<э/MаРО╢ ╚В·3┐6╖=д╛№fV%пLшё╒2▒<К%=р?╢сф▒T╟вщЧМVhДqdjFъY┬▓[yyK∙Z_яG■─√ЁшСC°╔┐x╙жtПЦеej┘Дь:·ю┘ї{ %&ДB:а;[*"Ы└Ш╜ГПМMИ\ЁP╬╧S*5Зb;Э╛∙вЦ Єrє?7╖,Bъt╒Cy#▓ЬДИК0п0├ЬЯ╞\и╪▓╓`g7°їЛ="╘жELIEСH "╓║к3БqЕYлbs╫D╕%╟kb;plcШиM LH_6Bв█ёkЧ░·b├2К═шflе_жОр5¤"в╓Tи}ЭG┌N╛ЄЄЄ▐╛▐З╝ы]CH|ьзё7~тmрЗ▀╡й] ehYZжЦM!ДBЧоDly#@ЩjHftEМc||├#uЄ╓Я└|Ў╣╟╥ ╞ЗЛ╕6E}╚kгnИз #mЗж3▒кhk ┐·┬ььАN√│ ╙─▓╫─ю keU╢МmEщu3~Э╨I}=[б╓╫e,/хk}╜П∙ЁЗ~ ╧ ╟Л╜u╦▓Хцk╨¤Є}│5EєH^-C╦╥2 !ДB╚`╙ХИ5ЮWПў╩xРЬ]Gр╞&0=Х@" ╬Сz╟ZKsr█^.;ЫдчУsrz^╤▒iLOh№h╞╔9К∙'ЦYA╔ДНъ╓╕Bю╓·╝Ш░╬ьlУg1Ыф мO╬ё││J░-НЎЛїъе'EL╣хm)│╜· ж▄,о═_C6Ц┴┤╒UнНнRFn~щЖ╨╪и║2 "╪╝╢┤4~Э╥ю°!∙├╞╓Жсцnр}G№С┐Ж с┼O▄Х^{ч{6╡Ю┤№Я═╓╔гy╡ -ЛB!Д >▌ЕGж░иПЎЩEsтШC-Ь╪B}V╥x╣&Бї4Н"Z]└&Б}ИQ╙в"вУF ч+anT╧╡[║Аdf╤ )OЯ]Тs]яZ▌b9═ылз>м╙┴/_P}Av*н╪ROp┐D0╡╕ВМИйQ╖╬шЬXстW_╬,╓д╢ыcЮ∙┤z╜Л#E06C6Э═:&Яljш╪║ )E1[Ь└Jй■Щ_'╓┌┌J╓ЖN╞/И░▒ду yўZ/1L _├д╓е╧у&WP[L┘y.;ЯN#ы ╤'└з∙№г_·p═#█*╓лy╡ B!Д▓7╪▐ў─ю╩э╝ыvЫ╤0]Ub ─2~я░▌'ьЙ~Q▒№■\їЄъ╗nЧ&:k╫^zOl3V_■№O █┐╟ ї▌т╧ ╥ЗPy╧_▐¤^∙│┐┼U~l^гглы"N· мЖ╙K!Д╥ ┤sЯ╪Э'v/Щ┬jizчмТX░aнї█╛░╩Юя Q╬ЫFЛ√}╝}P1z∙я_№Х√1ё╨]<ЄоWqрэ█└┘0ЫюkЪ╙sЇ\ XB!ДР╜El3╢e)aBZ#ЧК"::Л%}E╥jлпы┘Яш┬LУ┐Ї│╕ЁйЯ├хs  ■╫O?Вхф╧ЪMў5MПщ9\─ЙB!doBK╚ОР└В╧┬Q╬√lW▒║@█ ·КЬC|?О}ЇC°∙╚O!■│6ЫюkЪуkt!ДBЎ.▒ДB!ДBКXB!ДB!E,!ДB!ДРББ"ЦB!ДB╚└@K!ДB!d`аИ%ДB!Д20P─B!ДB(b !ДB!Д ▒ДB!ДBКXB!ДB!E,!ДB!ДРББ"╢ЬC.W╢!д╣ГЫщS8uщО¤╝╫╪╬Ў▌D·р)4+ъ╬еS8xЁа│еo┌TМ╓qчNЭJуц^Эв¤ЖЎўAщo√▒hq╛t─^ lБ╢╟╜}╓BЩ};?¤с▀З┴c{ElyёбrЎу`░Б┘╤╣│╣ЮЄ|CCC╬ЦкoI╨▒V)╧зd\Э2тє№d rщ╘Al∙█v3НГз.╔Яї▐R.оaэ╓m√iўp ╨╖wS$7╢"pВ▓ьD√?}wя▐┼╒s6бU╠ Щ╜╣╤н-▒▐Ўж╚╝к╒y щK7[Ьc╓╫-╖oamнИэ∙ ▒Km╪Ч V_╖ =╤Oэ█[zё▌║cПюhЯy─еЇЯ∙ю▌"Тх╝ъП'║я~w║[╕аю°яC╧шз╣█Яьв'6ЗT<ЕЭ╙D>їEж0У╠b╓╫Р^╪╣╜eFжVQйT░Т┤ ВО╡D.Еh║АСХJез"Ў@зьЇ╕▓}DЖБb╣■/╩ЭrОр░¤▄ущwq7s╥~▐-юр·sk8wю╓Ю╗▐ЮpБуO┐┤/Иs╕║╛Оїїл╕А╦8{╝ /C`█Ы!7/g/c°ъ║╣й║╗>m╙[дэ·╢БУ▒їЮ▐о a7┌░_Ш╛юЁ{вЯ┌╖у╢Їт╗uз╛пх{Ё°Y╧Lc]┐яоуъЩcЎX'С╤яълpB■╗ ▀гыыI@°=╚юЖч vgЗЁй/1ЮD>рНэЕЭ;▌Ў╔]╦╔,$"ИDd│щ]1 m'дС#╟N8┐>Ы_Б╖Ж╠▌L╫~∙=ХЎxg╒[ы■\ї░Э▓^]чфЇ%O║зр║2ы* ╬w╟ЫЮ╓¤╤u╙ё.╢5u╖╓╬a,3Жsk╧с║╫єЛ╣╟V█~╟■│"√╓p■╕{╝fЛo√LЯym╓Ў╓ьїэk╦нЛnЯ╫ўK 2о╒64√№Ёa∙▀I╣Б[╟ЕЧ1gOЁ│ен╢7iГ▐,: ╚M┘╙'▌O╛vЖ╒HH█¤·│.OО╒х *│├∙"'рФ'_+Q╞FЭ8ЮыVый╬7?;п[!д╧ЪвeЮ:х┤AЄ8∙k¤4'ъощы6╤е[B√║УЎY|п┐╦мыЧ║L■▀Г▌┤╧ў√3dN°Н▀╢┘╥8юt╥gmЧkЛnЮk╠┐╠;;ў;Чцp∙─|е·▌w'Я~'[∙бL┐лП╪¤#·╜mў╗└ol═ўK▌ў╨Э·и-┐╢Kz▌}Д∙np╩ Э/c┤ЯшZ─╓BMуH-█DK.e├X5 55_ 3r╥GСEщи{N- ┘/Я!'ї┼k╟ЗтЮухyд▄co_X}HL#╦тZГК ═чSЯ┴╟╬╨2ьЧЭ&аэ~v╢▌Дв╟kхH U╟Vў▌s╡_═╛╠7╖╙:З`rHй-M┬иMxv]e╠K∙FjУAхЎ-рД▄иЙЮ╜}k 'О9-OfЇ╫bчу38Пую_1їRнып┬E╠Х╟Ё9G>тЄ▓√'i ЧoI··:оj·∙Л╒?VnЩ═CЬ|Є╔╟з╬qf]є▐└3╟КrfbЄЁЙs░ж╖╞═e╣й8&YObь▄Ъi┐Г№╤~J■р_└║їVуn╦_U╟■л8з┐И{tл¤*ю█╛У"ФхOx╡Л┤nI│}√┌░Жт▒gЬуыgP<╝▒.7v╟╧╙ОўSЭЯчЯЄ╗18l╝Єn8ЭЯ-н╢}kNтЩ 'p∙м▄,╔MO}ш▓┐Эaї∙уФyy°к┐л├E╣qЄ▄0ЇзВч╘ы%и?;Э/Rц┘╦РЙmє╔ЎХзe4Zдz▌▐Б@б9ь ╝nГ┌┬┌0жoHхв3?И╘о%▀9╤xMу9╧5▌Щ-б}8'ВЁЫ/ЭўЩ█/э|╢╥╛ж╢}Ж|Ч√Н▀╢┘R7ю┴t╥g╞ЦАk╠╖╠╠O¤█ZЛr║Г;wЬm╖Ё█├OO╫ и{ч:Ю╙{═р:moў: Э/▌|ю!║▒"&╙LФ*иTV1Н%С'5 Ъо[ H#jяЇЭЇ$C╞ф╒m s╘?ЯгYHЕ&┤╒lЛS╓;(╟вi`жdЄЦfАЇд#6┬ъГФ05УDv╢^рчєп/╚╬p[№ёяЧэ╟в┌ dGнtEfP█¤эь╕эЙTJ╔U└ь╞8╡╬МШe~uшl┬╔cйш╘еuKбжэСй$єKXv[^╞R>ЙёVР <ЗЭxb▄Ф;рссa┘╜щ╣ю▄─еKiд╙q╦' ш╠iчWesє_ ╟Т?T╧H·с├8y·М|jЯ|·|тЙ38m═:▄м╠├O#s#╙V°ч═х╦8qFо ╣СPпtUДЫ?┌jЛ№!╡▐╩LF■рvїWUЕrэцPы╞╣1√G\ьы╥╧╒╞уМ4╛1 | w╩r[к7N╛├*в╜═~┤0юM ╚gцЗ▄ЬL╦M╧┘у·╦┐█╫]╪щЗ)╙3ЧЮ╤∙RDн╦╢╣?;Ю/G ╙kчЯ┬┼Л╫q[ДЯdеv▌╩їwFЎ╜┤╪Я[о█n╞┴№фГ▀Ь║жweNс3_zaз┴ч{0И [Z∙■Ъ~ЧwЄ]░ ╢┤П_ЯuvНїz~▐╣ЇО?n6ўлp╟ё[чG▌є├ю\я■нъъ:Єг├1┌Гt'bKEфc│к 26!B├C9g╝Yй╘КэDР·цЛbX*╚ЛаШЫ[Q"u║Кд╝!G┼ДУIМ╫ЛН0╠∙i╠╡к ы ░│:э╧pЕиyЦ6╣bч*╠#▒a}▌C;'╞FДЪg}D9ЎlbШШvъТЙНЙШИZShу╔<╥vвФЧЧРOО╖Ї#┘9f■0Чo╟╞О╖<_0 t|╖0Ж▒▒1о▌ь4ц9▌mч&TGоЭ?ы▄LЬ_SЕ·Лr7Ь|цNШ:Ь║╧╣n╪·║·CC Чq╓ ╧2б\~8Ю<уБяt▄[╔з7=│ъ*ЩєДр╡jgя╪▐■l√,▐·4╞О▌┬Є▄q)╖яач║НЬЦы╢|╖DD╫в:╡sЫ█0'┬пщ▌ЯA╘цK?┘┘▄ЦО┐?╗·░═╢tLg╫X/цз■Pк?>щ╫ЮёбQ ╬▒'dlkль║ю▀кЮ╨сўрд+[▐P(ЭEу╟ЁHЭ╝ї'0_SлъЫ┴°p╫fги┼═b╘Ж:aлэР└t&f╜{нтW_ШЭ╨iЎ Я╢яКЭ;8Bb:ГXЎЪФS╞ЄRI║aўk╧с╣т0"'#.╩╛╜6─OЬ┴3OЯ─╔УТPtВй║·┴╡C╠прЮ_║═/├╬nН;·Ь╬╓чz}▒с╝WMXУn╬sб╞Qj╝-k8 Ф№!╡!_fsrZ╓Ё▄E∙▀ЇШж\йуТЖ1_└3о│гх╛╛ГЫЧЮ┬∙jhЧГss$7^[╦x╩?'.\╡ЛИш╓░PС9 &.е╡L╟█╘Ъ-[█ШO╬╣щЙ!╛s[╘ЧK+v╢█╫жL╟Уач▀╝иєEц°Э┌╝?Ыdg╟єE■Х~╣Г#8r·8█╒К№╣Рytоrїюzх┬ w▀3Яu;Q╣pїBE■Vф/wх sсДM╖█╣л e╩∙ыЎє║цєФщ═g╖╓CЄ▌нмWыЗ╪]ш&Ъ╓'acHf&PH1cЮs╠!E╓<$CfeKгiфcФVэsЙ║ш╬и.єc╧)нb*Фя(цъЙ╠╙'k7}w.!}╩zi[Ї┌▌Lы∙gqk8▄їЁ╓ЗХ▐║юz╒є[+╘╔ыl[┬Ў╘cь┌в█йKнyМ}█а!ТQНRХЄ╜v╓┘ТЎ╘ечЭ:х╪"х9e╗бЦ6ь▓УP╞·┌Д█jLшоcГ┌э╓щ╫Я▌МQ╡N╔s▌&ZВ╞╓╖п├ш│:[╥║▀┌ь╧нЛ:l▐·B;ЪKAvъ╣m╧ы└9ш▀Яf╛╘ХЧ$oлС┌эўKw4а╣н┴}нї╖9?е<ў1Ш>Ї╘0■s7╪Цnц !d AK!-s╧\8Б╦gхцLnozя╧Їэ°y\╛jBЄоEЁД▀ДЭ╠▄┼▌╗Wq'pa]ўu╦HM.k(т▄РЇїл├╕|■b╡L'o│ЁQ▒хьeрМh╢п┤Єvз Шv<═ъh> Ф{s*В]*║|VolЭЄ╧]н┘щ┌rўю:╬р<О{яЇ╫Ж1}C┌╕М▌X╟ЕkиEос╣[c°Кц]┐а╢pSdg ▄╛щn▒с╩E`8тЇМ_v3F╛ИHxъ|QЖH╦║!╣ЯУRj°ПmH_√0?m9Vм│%i√▒g{╓╧аx■╕yї╬еа▒u╬m^ s╨п??=-ч?Зыю─║s╧нЭ├XK╬┼р~iюКА~ъмЇч█Ўл╞-╚В·ZY├e╜╞ЇШ\bю№t┼жvй ЖЮсъKЁ8═]?[║ЫГДР¤E,!Д┤БyюPn└жхFёьqїЖ╪[┼;e2"rЮ9iBЄN>sF>Q┐; с╬Ьvn9ж┌лОрШЬ╕&ВЁт┼ы╕-7ыbR8ж zУnы;9V2г3ПЯEё┬:ъ"EМиG5Э╛И[НQ╖'ОЙE~H√┤╧tўЁiЬqё\╡B┬ьЇсpd(Цq│,ў▀gd█ш`МDHпЭ8ЫMЪоєеEВ·┌Па∙┘Н-rж█vg№┤Ле╨░ы┴o.ОmЗєZ ЪГ╛¤ycвz╧_tоё;╫Я├┌╣1╧ПAїK√s╫╨┌Я"е╤З5 $#┬_Л ыkя1╧╪╓Й═sW┴-2№С╪.╞┴╟Цюц !d?BK!эв7`│ъJШk┴s▒╙╪ч╤╓з1vьЦчОг1№╒Я╦8k<2║im hисё9▄┬╞╞╞plx'n?;░╙ ╠Є- r·Шь\╟-╟№v╧╕г.рNшA_wlЛоg╗s№╞╢ЫyэCHЮ|цN\^Ц:юр·sk8╫Ъ╢)╡~щ`юЎ█?█= !{КXBiХ;wp╙C|G├R]G0 ╟ksG╧╗исб├h¤~~ ╧┘╝f│йЭ!∙┼╬;8В#зЯ┴3g╘╒╥ВW╪┤8qс*╓НWж┴3#7№╟╧укуBл>hn@OЬ┴3OЯ─╔УвЛN `km░э╓]уmкAG╘хSсэЧ0;¤╨|k╧с9╗ПчЮУ^┘Э12Ю&П╬x∙Ь▌@Zщk >k>?;╡е)є╥S8яЖ█╘gЁЩK┴c+mщd^┌Я╞ЛzsЧ:¤┴гY┐t2wЭ(ЕєOЙXt╟╡Е▒э[ц-Щw╘S=чХ┌Т╢═у╨╥╝г╧¤╢юL┘P─BH╦▄╢^╟Лr№м╛Цт+ЎЇ$2rC~оx╟П╟┘т0.м{ЯЫ ┬yFPЯK╙╝╟П?^ИЫfq╡CO[!а√╬B*╖qёмЦc╖єEЬ╗р┌Д╢с*ЖЯ;l┘╡[Ьчр╠│ЛЗЯ╞ єLгу}1╧ъ│Д&╧S└┤zпдМV▌┴ Ь9╢Мз4п>+xюj]Ш▓SЎeЬныЧ ;├X├ZQCKПрШюW├LГ·SщdМР>№К╤oZч)\Д7|╥▀ЦV·┌┐╧|цgа-aИx2∙Оcю╓еюЬи/`.Пmзє┌ЯЁ■<Мзз╧╔Ь╟eO╕k8A¤╥╔▄;╛rD,:ук█Eщ%ип;┼> /sя°ё9@ЕъЪ√╝p╨8Д]G>┤2е▒┼5яєЇДР¤╠╨▌╗w+v?РЛЧ.уЩз╧с└Б6eПP╬!WК"СИ╪B]\гSОqnу▌ы╝W╝■·ыxрБь'BЇЖw╟╓[yПьb{╕Z╖x╒~FW%>Оч╬му'ъ╬└9H╚╛дЭ√─эї─ЦчJ!g?ШЭ0Ыы)╧╟144фlй·ЦkН2rй8тєe√ЩB╚Ю├ O╜y i myг╜МЎ╟M#`╧у╛B█[8 !m░Лс─9дт)ьЬ6Єй/2ЕЩd│╛ЖЇ┬╬э-32╡КJеВХдMЁtмU6 yфЛ%√╔eз╟ПBHo╕Й┤zЎ9ї5.uKOяOnж╡?цЁЬЎ╟Н_хC:ДsР╥╗√Ll╛`wvЯ·уIф╙▐╪^╪╣╙mяШжV+и,$ьg╙BHrЩЦ^щAzЛОCmСб 6┼y √cgр$Д┤G╫"╢<ЯB▄Дл╞СZ╢ЙЦ\╩Ж▒╩O═├u┌9щг╚"Пt╘=зЖьЧ╧РУ·т╡уCq╧ёЄ$жСЙeqнA┼Жцєй╧рcghЩЎKил╧╙╕О√E·d(w·E╥Эsтu╟}╟╓ЧRZFu╓l5б╘ueФ1/хwUM!ДBщ ║▒"V&╙LФ*иTV1Н%С55 Ъо[ H#j╒ГУ╛В$b╚Ш╝║-└її∙х3Вe4 йеТ▌зр,╔$╟вi`жdЄЦfАЇд#`┬ъ3▐╞Щ$▓│їв)8Я}AvЖ█тП┐Ї╖╛╞Pф╬√E╚П`fUЄ╩D_-!╦├ЙT█0ЄX*ОcQэ(e┤B#М#S3R╧Ц▌╩╦╦X╩'1▐JgB!ДB·ТюDlйИ|lcViD╞&D╓x(чМЗ,ХЪC▒Э╚S▀|Q Ky)ss╦"tдNWхФ7PY5nWО$╞ыLц№4цZ╒ЕБї╪┘ ЭЎчN6▒aщЭft╙g1LL'┴├Ду%SaувЦ╙vP╦╦K╚'╟[·┴А╥ш";Ю╫ЇB!dЯ╥ХИ-o() Н╬вИqМППcxдN▐·Ш╧>ЯYЪ┴°p╫fги┼═b╘ E5соэР└t&ЖlcLq ~їЕ┘┘ЭЎчо╨╔8Їа╧Д─t▒ь5)зМхе<Тt├Т]хEX╟▄─┼│su╤2-qчNt▀Aъ"уР>■ю╩╛D_Kt i· ╨t╛ █╥ЖAЫg█yM√╖¤╬еS╬╗}u3я╠э{a▓t%bНч╒уe3Ю.g╫╕▒ LO%РHDБВsд■$Пе9Сх▓│IJp>9'зчEЫ╞ЇД╞╣░б#G1" ─2+(ЩЁV▌V1Uч═█ZЯ~ЪЭн╢╒╨$_`}rОЯЭUВmiдХ■Мк+│ ВнIЩA╟:з▌~ BЄЗїЩy╓╓є mkЗюЪРa`фин╨xfuїщeє1 7w▓#▄╣Ї╬ЧЫУ╛╛1Ф╕Sщm╛yэ╛╠;Чцp∙─Ь▐ж╒^╩┼5м▌║m?╣Ївэд9√╣п√йэГ2сv6┐ж╖Ч├O▀0ЛO]э▌ы╘█dз╟o?_╖д▀ш.Ь82ЕE}╤,ю╟jс─FъsЫ╞7 ╠иGl╤ъB; ,ш├ЦЪсЭ4b88_ sгzо▌╥$3ЛV IyеМ,╔╣о░nQЯцї╒S~ърЧ/и╛ ;ХVlй'╝?▌s▓mRf╨▒цфР▓э╥GUєiїМzxъд_Вы3бд┌▌}Ж╓K ├╫0йuщє╕╔╘SvЮw╬з╙╚zB▀ ┘ Їh¤B▒ ЕьZ╤юl#]Хy╧пс▄ЇvнXzO▀╕Л╗═^с╤Л╢Уцьч╛юз╢╩8┌pMяuvz№ЎєuK·КюDмр╛ЗT╜m SSXн.Ё#bF├C]O\BП╔■кg▒Ю─ВУVчн ╩'╟кч;╟╝*'т═+Ы╖.еi}ї╘┬O=°хєн/─NеYЩ2ь ?╧ц╟а~qi8зо╩&╟┬ъ3ч╓oл▐B█э9┐6О:OЬbGp6╓╫м╧Ь67{█╞j╢м6Ю`VЯVGЎШзп┘ъДмM фfзN┘Ё5▌N]BU ▐╣Д┤{мё╫ёаc>▄Lы∙gqk8▄цїЖ╡ХщcghЩAэs╣╣М╦'.ря¤iА-w.IЩж╝SH_╖Й╟gєЖЖ┘YWfZўэ1▒┐zо╢┼=зЦ▒╣ЭТ~╩▓ихT█~╙ gм╓Yoлrыв[п╘U▀xЯ~╤2╓ь2v{╞┴[Lhe▌x▌┴%й;,╩▓лyцG ¤ЩЎОбз╨а∙тkK╪╕kЪЫ╧фmэzиЫЯio▀v╤B╟A╕u╜y}НСр▀>сО┤╤╙gюё╢·╠k╚Ц9^ ┌"°^cAt╪/-gyьlл_█ч3? >mл╧Ч└kS░%и ╛▀╔JЗу@ЛоEьЮ#"Bй4-ТjPСWq╡нN8n';]▀Ог!╩9╠╟гH#Г┼=╙.▓kШЫв┌╒┌v┤IZm;┌Ё∙°∙5)ь2╬Ж▌эыM╫┘╦└Щuмп█э+о7RО?LпЫЁ╢їiр№Sю∙аc■8я┼╝Кs8Б ыю;3p┤cH}>vЖЦщ█>FsЧqт╠iOzА-rєЄ╘∙вйї▄└3xNn╣j8Ўl  ┤│▒╠c┼ZЩ'3╕╗~Ar1W├W╘Ю 2║╦:╢ОЭЧЗпЪ╢].╩═_ 7}Ж5 ∙╘Ж░·:#╚ЦА6}'w1?╔`A█МmYJШь6╣T╤╤Y,НdPjЇ╩╥ zуj■а6nM╥j█F├чї 'д░s╕·v╟ф╘5 /^╟э█"▄┐юw╩rГй7dN┬a╜asE╨▒N ,3└╬@Z╚wє"╬Л0Ъ~┌s ╚Ц█╖░цyvЎЁщ3r╦╒%-ЦyцЇIsєeЮЫ╙▒5v╩ ▀3Т. ;∙Мц+в▄╥8Ь└═з╗ЗOу╠ Т╒Фc5cфP╘B├ъУ∙k4шё│(^XGыСЧ~╢Ь─╪╣5Ь┐ш▄X▐╣.7▀ч╞║╗йэк╧ВЁФщ┐а▒ Ьє5╢М{╟╫Гpчжёzз╙qkK╘fmшК┌<;|фX█¤╥┐ЎЩ@┤}"`д}ЗЯD&#й╒~kК╬OўЗ∙*Y▒фЭЯ!}▌Ї в╙~ ┐@;Ё═╫с▀ХЮrн°╡aо[2°P─ТG├РWBзЬў┘оbuБЦЇВy№№0оо╖Є ╢}╬k}c╟nayю8ъ├╖.уl╒├лa^^ВОuК_Щav·ЮOo'жЯoєH~s"и AДїзБєEmi>╧В╟╜│ы┴М╙gt^╘?W▐╫~╫ШГУ ▓╡╒;w;°▐ ┐p;;ЩKЭ■]Q№╞!Иаk╙▀Ц└6tx▌Т╜┼╨▌╗w+v?РЛЧ.уЩз╧с└Б6Е╥OшВЭrф╚єп{ЭўК╫_<ЁА¤DHzs:Зcы}° ║Ж╣Эоv╜иЙ¤╘ЎVl╤UЙПу╣3ы╕╤wГE┘єЇ№;Щьэ▄'╥K!д9Йg·I└║бs7/!нсp▌.`┤'╨■╕iьy\└W(` !;┐Уў=▒ДB·ТЦWtэ97Сv├▌╬>Зв╛дїе}ў,7╙┌sxN√уFулС!дWЁ;Щ0ЬШР=├Й !ДB╚а┬pbB!ДB!{КXB!ДB!E,!ДB!ДРББ"ЦB!ДB╚└@K!ДB!d`аИ-чР╦ХэB!ДB!¤╠ЎК╪Є<тC)фь╟┴`│гs`sйб8ц╖QoЧчуr╢Tя{`зы#ДB!Дь=╢ў=▒*bгE╠TР░I■И(Л_├Ётж"6йз°╫ЧK av╕Д╒жЖЇ┬╬N╩T;Лс╥ъ╢ўЧ╢+и,ДП┌v░¤їэЇ\ъOх=▒ДB!Д4г╒ў─юоИэС(kN@}╣ЖFБХжvў┬╬N╩ь]э ╗гsй?K!ДBH╖tN\ЮO!nBDуH-█DЛК7|4ЮЪЗ ыдП"Л<╥QўЬZ▓_>Г╬x╝v|(ю9.":хЛзкб╖aї!1НL,Лk об∙|ъ3°╪Zf┼9▄&ПЇ╖╖┬ [ВОuJуJ`DиzъsчE╟cд?\╕чj[╠╛Їiш@и`Оc╛:я┼[и ЧЎЎГь═K▌МЬ&ДB!d√шN─К@ШL0QкаRY┼4ЦDJ╘H,h║n%L Ни╜Ыw╥WРD УW╖Ъ╘/ЯгYHЕ(Хь╢8╟∙&╟вi`жdЄЦfАЇд#(┬ъГФ05УDv╓+@┬Є∙╫dg╕-AфQЮvЄФ&PHGн@rl╔ОмШ║VF "ш\!tмS№█▄/Aх18)Bu$cы[┴К(╔┴V╞иi█ ╥З╔U└ь╞8╡╬ Рm№%г)y,Э6Б1┬ЫQрбЬ3лTj┼ВMk▀|Q Ky ss╦"LдN7|┤╝!rDГУIМ╫ К0╠∙i╠╡кюы ░│+bШиu6&дО┬ЖThl7ЭРz"HLы8аЗПuL╞!(ЯГ┌╩RQ$Т└┬В╒░>m▒эcr\■НLн╢т,уаeъо╡╞╨╞Уyдэ$*//!Яoё B!ДBH+t%b╦╩T├5г│(bуууйУ╖■цЛ`jU╜_3.т┌l╘ ╡GБ,FmИзb┌ Lgb-zт\№ъ │s√9жфzAп╞бЫёы╙─▓╫д/╩X^╩#I7,!ДB!█JW"╓x^=^6уyrvБЫ└ЇTЙD(8GъЭ`y,═╔э~╣ьlТЬO╬╔щyQD╟ж1=СФ4ыYЛ┼И№╦маdBJuk\шgk}^L8hv╢╔3ЬMЄ╓'ч°┘Y%╪Ц`╩╚═O"эЖк[аЦХЫ╙qБ╤╖A╟,QuлDx╡lЛЬ╙┼84н/(Яїvж'E(╗y╢╪щ7F┴m─h╘Йc╡pb#їyVуUЫf╘C5КhuсЫVD№hZT─Pt╥Иср|%╠Нъ╣vKР╠,ZБ$хщ│ТKrоы═л[dзy}ї╘ЗГ:°х к/╚Nе[Ъ!╢Щ╛Оb╢8БХТ√ьз┌ТA▓рФ7ZAжеcNЯg1┌▓-▌МГ_}A∙"ШZ\AFД▓УG╖9▒┬┼п?¤█naКжеGmЯ6[}I*(фє(╓*▓─01| Уjг>╟Ы\A- ┘y╛:ЯN#ы ╡'ДB!Дl█√КЭ╜@╣Э╫m3Jн &5╦°╜├╢ЗЇУ-}EоЕ╫∙Ф1Пbibg√j^▒sЁ╦▀▒{ДB!Д╘sў│▒{┴P─6г\Fmе"B╝Й╪2╩╣ЦgGСFеUw┼цЭaPDьЫЯ∙░¤╘9┤{ДB!д╕wяЮ▌ы ╜OlU─vN╝WбА%РKEЭ┼Т╛ hЗ,!ДB!√КXB┌"БЕ- Ж98ям]┼ъ,!ДB!╜В"ЦB!ДB╚└@K!ДB!d`аИ%ДB!Д20P─B!ДBщщt┌юm▒ДB·}UU9┘╩6ЕР╓)Ы╣У╦╔<▓)ДBvW└nзРеИ%Дьi╩є└╨Р▌R6СЇ)9дтQDgпс┌ЖMjь╕ r╬~ь?D`етИ╧oЗ╝╥wV╟╤миЄ|\ц√Р│е·╖7z╞Ж╠ЯYЩG>¤C!dчhо█%d)b !}╩▄№╩?┬щ}ТэЇWю╪ЇЎИLХ ░Т┤ ╗ЙYV\Ш-ОT┐▐eЛнй╕╪╖cиМ∙°(▓#+им.`a*▒лпкrЕ`{ВSEx*P8mЄ╚KЎSoИLн╩Ьп┤?ччз┤%╫rє├█▐Ф\╩Sз^нzр¤ъЛ 1%єgU█ЯGzrЮYB┘%╝В5У╔╪╜э▓▒ДР>х6V╬_NЬ└ Ы▓WИeV░▓ТA2ЦG6▌Aб╪Е|▐юэхe,хc╚L'l┬nR╞ЄR╔d∙ехЎDP╛`wЪ┴ФИл╩B?┤╤П$VJ%ФJ+╚ Л╤hЮэ└╢7CДшh#+%#║+еЫ▐"!ї%VР╠/aЩ*ЦBvW└zЕl╖P─B·ФУ°go╛Йы╫ 1ЖmJ rгЪК╫BЗу)УJN╬є═#wяqOЩC▓_=t,ДСг $SXXХЫl∙Ь╜ц╚Д\кцЛ{╜`bдыйЪ╧╣▐▓ЪЧ╘/ЯуML!eОз0/ф═Wчєx╡Ly╤4T┬fGэqн█cO│|avЦ5_▄ц3ЫG ХК╚╟&0╓─¤ZЮwыУ▓R║_╦Wwl┘&║4╓o╒+WB1Я─°┬°VTV`н╠x╩)╙ГQС}yдгюёЪЭucфuЪ>єЇГьефЬжckыЄRЬлїy╦^}уe╖хz╟╧%С %DpЧРЙe1kOЁ│ен╢7iГК{чЯD╜▐╟╬░·jD1╦cЛу█╬═~¤ёИBЎН┬u╗Д,E,!dO0?)ВkDфЗ▄░ъ&╗(m╜[▐Bb┴ 7╓mB>G=╧═жFх $╤-│┤X╜▌<╓)╤qї╨╩ЦiЫ╧b╘ ЕМNKZLvЄXЪ]┬H2ЙШьggЭу╛∙ Y`X╧╧в(╜в∙ z╘ёВхcId╘+,╟╙╓ыЭv╩╥п▒n3V`·ч ╢3З9═'9╘├щlу"3╩>^52Ущ&JгULд Kу1,╒ОY;хаМП▌зZг▄5dc├b[уIп*╦<╙РчМ-sEz┤hцYbAm╨%b╚{t[РЬуMB|"ФеэяN▌Т2n3║∙*ХТL╖┤╠OпЄТ▒ЮvОЧ&Ph╔л/¤M3ОўSЭЯ■!╖Х ╔ Ў│е╒╢omC╙2_ЇЗТ║m ■vЖ╒╫И3▀=╚дС∙;ьN>B!=┴O░nЗРеИ%Д >rП║$ъ%3-╖▌вRt[qЪhE▒H▐∙yе"^Л :jXЇX^югчцбкх║ г░блзкЧI╜I@╥U,╪└╡┘Y╠.Й!F К@╥d)<бjBЩYХ╢-`Uo▄W]QцУ╧├Ё╪╕Сп├╙·п┼И%Л ШRпЁМклЇ~_╜pЙ1╟ эxНЭ═╘Р/▄NADvA╠╟ЇB═ы▒∙╢╨рбНМMШ&ВО/ЬО╤дМ╤r[cФ╗&-Ь8*sгМитz╩k!╧╥)L√I█╪╥<єEЕ▓╟/u╦Ди ▓rNцзz╙ч╢╠O█ЙZу1!э▌"╪)o╚hйHvЄETD╖rhK∙╠є╗283#МF╒SыЎuvЖ╤(Щ┐]Н!ДР▌Д"V■╕ъR№ДР}И▄/EE╝╩ю°╕л%ў╫rГ-╟D╧]Ыu┬Ж]R╨▒0ЄщQМОжСAФ\)┴<"ЩK!*iСЩ##>ВоЭц3x┬1╒k)Я[[sиУ|"°д├J+LИ`IKDC@ы─╖ГпЗV:&J┼y■┤4#cT4л╒69m$╒С:F╤h╤t^f╦c█ Йщ bжзюъПjЭХ∙9.єs\цgMв√1r┤eЦ┼иОЭ┘ЬRЪSЖvqL]Ц╪bh%Я■°▒аb6#ж═z┬Ы[╡╙ o╡O!Д █+b═╩Жн▄$ЇШЭ0Ы['шU ┴пa╚∙╛╛бszQ&┘╗▄┴═Ы7e╗mD&n▌0Яя4[дXюQ╒ ХЮФY&єльnЎ░U╟в▄РWП╦╟▓╛╞EЄMOЙРP▌`5СЫп,ЧЕюG╟фН5\GW╨▒0Т+nфкXч█d#r│_}╕А 1╕м?╢┘у╞Лл ░х ─Д▒:─кс╜+иол9j╝╢┘╤8т)їв┘gЄ┘iо °цоe┘ж╗yъ ╬°s┌T_eeыx4√>мKУ>╠─▄:uл┘bиы3ў√о╤gє■▌q█Q ╖╚3╢ZЦ╡╫щя·2c2√mK~ў╕╬Гfу▐8F:Якe┘bёk╗_╛F;фx╥█?;]Ъ╓giЬы^ЪМ;!ДРюivя╫╬жўЙ═╥ЫmC·Є ФЛЧ.уЩз╧с└Б6е ъЙН1░└B ї╩═b╕╘°╦jпиO├ЭFБХжvў┬╬Эn╗6qгXi·jЗц╟ЎF╗ўыыыvп}О9b■uпє^qЁ╦▀┴ЫЯ∙░¤╘9┤{dпбл Oжuси J▐gi]┐п )c>ЕFБ╟Т+Xэы╫B╚▐в█{Q╜O╝√┘П╪Ф`║'z╜Б▀▓·a╦у√х3╚ Мя+LИРЫ▐╞r№ЙщZ╕ШЗ╨|>ї|ь -3И▓И@OЩ[√tkz╖°╜╛!░╛;ыQQы щ [_l╒y(ї┘M╕t]zs├╫*╥╧DжЪ,хЖЛъbX│цбQ XтГ}Zц,!Дь]║▒"т№_o ┌p█Чу┴т√╩9╓ёr№ЄGo&Y}]ЕKp> ·Вь ╖┼ W;(■}╓ ■пoЁп/╪╬:╠│g#p╓▄█0ЄX*Оc╤╪Щ1FкОНL═╘пfiVmЄ|!дП╤я┌и│╚╥ш ·▌BqB!Дьk║▒Бп7taИm]О?рХ ▌.╟o╬OcоUэX_чпvЁ%ь╒ЭЎu пoЁлпХWPИвUПJ\Dk,3m|7}&vN█Wu╪ЕWЦ╠@8яxL█A5Л╛╨ГC╚Абл╗?Ънbuб╒╖!Д▓WщJ─║лb6EЯ[┌Ўх°├^Щ╨═r№ЎеыН1┼Б°╒╫щл:д╙╛юєкВоъQ9кХ┤ь%1уКфїYэ╒e,/IНt├B!Д2╨t%bГ^o╨Ух°х }_Щ░ ╦ёЫЁ╙║w╘╣4╔XЯЬЎjЗ[╢ЁjЗV·║щл,A╟ъ_▀X_ш+(▄0jч╡г╓Kк╣;zЖ┴ЎеюO░ч╜АЎ╒│є╦(цc╨╫B!ДBЧю┬Й#SX╘G═Eq╠бNlб>+i╝ФУ└Мz─Fн.┤У└┬КM3╧;M1ЬпД9їт╣╧Gе HfнpФЄЇ∙╦%}Б╛їО╓-ъ╙╝╛zъ├O№Є╒dз╥К-НD0╡╕ВМ╗Q╖▄шЬ╘╘J_╗чИhlRЯ 1щ 3╢Q╠'░RrЮ▌ о╧▀╬zЇ<'Яє8mXЯ RH!ЯGqKa1L _├д┌г╧)'WP{dNъЩI"ЯN#ы }'ДB!Д &█√КЭ╜@[п ┌f4LW7j Ц)a╡▐еLкфZxmПоJ┼╥─▐юG╛bЗB!ДьїКЭ=Gd л%w▒б&▒`├Ты7 ╪N╤хЬє╬@d░╚~06Q^{Wцч1п█Хч▒╢iїпе0tUЯ█╬!uuй╫l║╥щ1B!Д El3║^JШЇ╣T╤╤Y,щ+9╝яЬ$Г┴жИ╪Ёё'з055Е'гoр┼▄ЪH[B!Д▓ЯбИ%N [Ёrp▐e╦Wr ,З"x№Й╟9d?║x}│El├╛OYtzМB!Д°AK67▀ОE·чЙ√Ж{hXдию7╝fк╙cДB!$КXBH │╣Ж▄Л└c'·╚з~▀VU/SПоbсAЫоtzМB!ДДBKщk6╦╧у╩│%▄?ЎN╪╨bB!Д▓бИ%ДЇ)eм]╣ВgЧБшXBь&67╣м!ДB╚~З"Ц╥ЯИ`-╜■║ь▄┬Л╦╧т┘gЭэ∙▓sШB!ДьO(b !¤╔бxb╩y╜Оw{ЬKMB!Дьk(b !ДB!Д ▒ДB!ДBКXB!ДB!E,!ДB!ДРББ"ЦB!ДB╚└@[╬!Чу;;!ДB!d╪^[ЮG|(ЕЬ¤8l`vtn└l▓п !█M∙х8Жо9█K№F ДB╚▐g=▒9дт)╠яШ╘з╛╚fТY╠·╥ ;√дэ=cзы#{УMФ╫ор╩№<ц═v╧пm┌c╗Н╠qW8z╢°╦;?щ#Пмвr╢ВХЗl!ДB╚gw├Йє╗│C°╘ЧO"ЯЁ╞Ў┬╬>i{╧╪щ·╚%КП?9Ейй)<∙╪¤╕їт·ч╖С2ПХP:]█Й╪cДB!дWt-b╦є)─ЗЖ04Gj┘&Zr)Mw╢xj╛zєщдП"Л<╥QўЬZhм_>CNъЛ╫О┼=╟╦єH╣╟<Ю└░·РШF&Ц┼╡Ъ╧з>ГПЭбe░·║о )▌╖╟доъ╣Zп{N-cє╛6a╒ё┌g-зjg)=VнS┌X7H~°ч+╧╟ы√AЎц┼окЭd9Д╚ЙИ╥¤Mln╛БУ╧цXЯp_яf/mъ5єxMцфU╧5ўТ╠)╫s√Тg.щy7тИ▀Рc7RHщ┐We╛е╡L┘Yцd╗^▀╖ф┌1e9х:хB!Д .▌ЙXУщ&JT*лШ╞ТHЧЙM╫нД д╡w·N· Тъ╔0yu[@┬ї╧gnфF│Р Q*┘mqкvуM3%У╖4д'Э─░· %L═$СЭїКУ░|■ї┘nЛ{бп█0\и╡!▒АJ)#╣ Ш▌╟вЦЫ▓цЧз╛ь╚К▒ceд ╣& В╔cйшФзхЛбї?6°╥<_dj╔№Ц▌2╩╦X╩'1:Ад36▒vECЙЯ┼7EтДQ┤¤├[eФэV#БЕ╟ф√фEа2w_╠"∙ШчЪ{Tц╘Y▌ь5ч}Ої▐fN╔їs?UBц@┼кшФ9yOцдц=-s▓ s2TРJ¤╫хZЦkUЄХЖe*{Е3!ДB╚╥ЭИ-СПM`╠║F"c"B<Фs╞ЫХJ═б╪Ntйo╛(ЖеВ╝К╣╣e4Rзнх С?*&ЬДHb╝^lДa╬Ocо5eR_АЭЭ▓·:м ЦЙ▒Д╦СйUTф╓▀╘'вxZ╥┼И─┤ц+`ге▒НaBєщnd 1-M ┐| М'єH█ЙR^^B>9^(d╗9ДOh8ёУ°8JxЎ╩Ъ╚┌~Aц┴ЛQDп;[╒єк<╕А╥И╞ыг(МФ░ЁаMWD╪кG5їТ\sН"Ї└░\y~╚Ь╢sЄ>ЩУ"pЧ╛2Ч▀▓╫ъГ╬EyPо╒{rн╥K!ДРж+[▐PK╓ЭEу╟ЁH3╣╥Д└|Lнкglу├E\ЫН:сзЎ(Р┼и  tBZ█!БщL╠z■Z┼п╛0;█g/Їu`И─t▒ь5щЛ2ЦЧЄH╥ ╗hhq╝^B╣oTl Щ╙ъQu╢:бъЗЖ _╖╫Ь╩с√Z╝V╗BоUВ{нj(╛█Х│rн▐з !ДBУю┬Й#SX╘╟═>q╠бjб>яeЗ╪Ф=Коv-bжХ┼Ц╢ я╒┼ФИeJXнwsю L{БХЮї╡ИЙбY Ч╜─a┤ТOW%Оbiв╞f}}▌ю╡╧С#G╠┐юu▐+~∙;xє3╢Я:чр┴Гvo┐ sЄк╠╔╙ЇвB!д?щЎ^Tяя~Ў#6%Шю<▒{С╚VK╙;/`]б╖Ю[█ЎФАu├ЛsєHiшя└-Кд╢чМАM#Г┼╜°у!ДB!} El3║^JШ4'ЗФЮ<║Д┬Hе║╪▐■'ЧR█g▒д╢п║п"ДB!Дь▒dI`бъa^┼ъBпEа╓╫n(▒тЯ╧yюN╪NИЩУ\РЙB!─@K!ДB!d`аИ%ДB!Д20P─B!dЗ(г№Zeє (B!д3(b !d[╤╫с─1▀W7щeф^К#n▐c█-■э+┐w▐Eл█K▐w=o#o═#~╒yя.┘^vd№^ЫC┤╕Шч╗√ёZ!Д2P─B·ЧЄЮ_+cSv7╫о`■╩Ъ┘▀╦╕Bв=┴)bрF*P l╝ХG■^╔~ъ СGVQ9[┴╩C6a╫ яЧэeзы█^z?~e╠│И=4╞Её!ДtE,!дo)Ч_─-QнЗьч~е№VЛтхнys^ю-?БZ╞Єлy$J" ъ▓|jГ{╗╙МжNUPyt░^i╡-ЎK╪щ··Фж╫─[╦X║Ч─╠#-H╪╨kЕB╚~Ж"Ц╥зlbє рБC¤*a╦╚╜ЬB№ъв/═Нc╠є Ьb1eC3уH╣^╒√╞0■0{=j╙s B╡Дв▄фП?:Оф╜%,╫ 6╘S=╡/═Ы╝╣ЧЇє(▓╚#}▌=^ ╖uО█<^яюkjЯ7,W╩ЧsRп┘O▐|╢./M█Ж&╢▄6<Цe█╧жоo█DЛЯЭmїKc√дoтЮ╛║с9ю╙Ж░·ёэЧ·1i3▀6шy7тNд<зl7lW╦Ф¤jЯ6╠Л ┬╞Oз [Чу═оЙ\1 МLгёзФ╬оB!√КXBHЯ""ЎїНЇЯИuB~'1{oЛз+иЬZ┼┬# OИdЕ╙&4│rzЕB╘ СП,`╒д╧`°▐,в^ё┌5d #*╖∙ухQмКД2ц_Бt_е╙%┘V0В"Jr<ёиФuvI─РQ[┤ь│ Uбрo"·аeС\╫кIъЦФёЭПn╛╩┘&РQтХc~э B─╙u1├%Уп4 дЫИу-ИpЪ,0a┌╢Кi,Iэ5№ьl╡_╢╢Oь|1+L√┘nП║яЕЎoCX}■їK П╔(╜и╘▒+∙X+mюН`цФ╪s?UBцАw>х▒дsWєЮ╬Е╔нВt ┴уgїu╣&0╙№ЪРqЬ}5ЖЙП6za;╝V!Дьk(b !¤╔ц&▐└¤шGGlфГИЙ(╚┐:Л╣o7єyn╓я├─╤ o{╬RПUqKпК; ч~╨Jд╫Їy┴гцxT╥│пYQb┬0E ЛШ║/"ЫИЫGE╠x╝\эгB╒:┤nїЬUEЧИ&ї╓е^ЪєИЧРЎ5ун МHvЄETD7zЫЫёV∙UП^фг╥ў╬оCаЭ°цЛbX┌Уa7W\6?D▄~ю┤ AДХ∙рJ#"пПв0R┬В¤С┴╘vєcИ2~├V`ЪёQ√эю╞/·а╩w╣&▐║&i[╦*{ ∙Зf0╡e╬vvнB┘▀P─B·Т═r пуЦчч1/█│/╛╝■"rk}░┤╙ГSX╒gL=" п╠ИЩ╜∙VoЪЖc^ПbШ▒л)#Lr╕ЎкКзQDхx┤ 7эп^У╘▐С╬ fъpъN>h%мЖг^ЯEу"Z╞1|_ЭllКi_(YМЪ░Q▌4Ї6ЬЄ█╧Шv`з!0Я}~X╞v№@╫К╬ъ n┐ сtPfзmя ;#"╢╒kк┌%╙g▐рцd3"Ь[!№Z!Д▓▀бИ%ДЇ%ЗN<БйййъЎфc<Ж─Й>r═▐Ч└╘г╬КоесqЫшEЯЫЭDZЯs╡7▀├├%Фф№╒Gзъ=й6ЬwE├'═ж!а6▄╫z╦╥/йP.гьnNNK^─Г╛│┘1L╣R╟╦╞Ь┴┤╡╤╟Ш~$Б─ГQNo ┌ЦЎ)ъE╞["О╜╢▄w#ЄOld┼┤▀iгИТo▓ё╝z<~╞гчь╢hч╓~ ╬'ч╝жчE¤ш4жJJZZKmhs┬╩▒-М`┼Д¤╓┬╢[гfXu╫x∙ыДшf№МШ=%╫├щр╒Q'°╞м╠пЪ7╜9m\+ДBЎ=▒хr╣╓■ьBИСELTo┤▌┼}вШ╜7!─>╟ивў┴Hэ9A[┬yхмгRЮю▒╝ВМИйQї╥Ъm╡ц8╧N╤`ОMZ╤Ч3 йчl╘xy╒Cц}о0В▒Зb╚ъ_{ydFфt┌ё0_Э5б[)╗║└СO√,N■м╡╒╡El╘gyн░1▐<яВI~▄7ЕENлyтШC-Ь8▄╬ц¤ЬпД╣▌>ЦнP@rd╤К╡░6°НCAezЮГХ~X5╧╟:^сЁ╢├─БkШ╘╝·ЬыC+ua╩█1~&ф▌x┤%╧╜<Т oП╒╙■╡B!Д ▌╜{╖bў╣xщ2Юy·8`SЪPЮGl?u╬┴Гэ!д=ЇЗ ∙>=ю▀4ь╣8М╥)?K!dп╨э╜и▐'▐¤ьGlJ0╗шЙХ?дёц├2▐&|ъЛLa&Щ┼мп!╜░sз█ю░Q╚#_м∙nv╟B!√Аз)` !Дl;╗NЬ/╪Э┬з╛─x∙ЇЬ╚9zaчN╖]n!жV+и,4ё7я╕-ДBЎ &Д▓¤t-b╦є)─ЗЖ04Gj┘&Zr)Mw╢xкЎьМУо+цСО║чx^рюУ╧РУ·т╡уCq╧ёЄ$жСЙeqнA┼Жцєй╧рcghЩA╘╫Є8x2Е┘RWfJўэ1i[ї\mз{N-cs;5╠Y╬л~╓rкуЧCJПUым╖U)╬╣їJ]їНўщ-sиfЧ▒╗Х╛Ў╖е<ў╪мФ1/uWы ДР╛&БЕ╘"ДB·ЩюDмИЗ╔tе *Х&/б_╨t▌ьЛ╪эЭ╛Уn_ oЄъV{~╙/ЯгYHЕ(Хь╢шy }4 ╠ФL▐╥ РЮt─FX}╞K9УDv╓+N┬Є∙╫dg╕-~╘╫т8мш"Ыmi,s╕P+3▒АJ)#╣ Ш▌╟в┌У▓цW╟╬мо`)э^)И@nQдK KEз<-_шдyЖзK(дгV8╒'7k╥шьи ggLТ+нЇ╡╥▄Ц╚╘ Т∙%,╗vХЧ▒ФOb╝╡B !ДB!]╥ЭИ-СПM`╠╞ E╞^B/┬A╜Yй╘КэDм·цЛbX*╚ЛаШЫ[╤"u║qJe√"ЎДУIМ╫ЛН0╠∙i╠╡ж╢Bъ ░│SВъ ЗNh▒╠Й1чЕ∙СйU'T┘╪)вxZ╥е╤Йi═W└FKу├Дц╙▌╚&b"$л(╟j╞╚1ааЕЖ╒'В█h╨ш( ЩЪES7╟╧Ц╞УyдэD)//!ЯЇо(K!ДBщ%]Й╪ЄFА2╒╨═и}√°8ЖGZФUБ∙ьsЭеМqm6┌Ъ┼и  t┬d█!БщL╠z[┼п╛0;;еy}Бу╨!╜(s╗9jEэУШ╬ Ц╜&уY╞ЄRI║a !ДB┘1║▒╞;чёvпФ│ыИа╪жзH$в@б┘Л╪єXЪ)P.;ЫдчУsrz^╤▒iLO╪Ч╨ы┴И}{f%л[у+Y╢╓ч┼ДКfg=!м.MЄ╓'ч°┘Y%╪Ц-╘4н▒╒ЦО╦4v:ЮJ-+7з∙F`Їж=VФB╦ъmЯm№Щ┴┌б╗&L╖ЩPХ2ч'СvCxГъSr)D╙#Xq▄▒[Я]5╧╙zЮ╙н`Лё╠ъК╓╦(цcЦiJ!ДB┘║ 'ОLa╤hї 6╝Д^б>╧j╝ЖУ└МzпFн.Ку<пMЛК╪ЛN┴ЬпД╣Q=╫nщТЩE+е╝╥ FЦф\╫[Y╖Oє·ъйuЁ╦T_РЭJ+╢4P_└8ШgFэ∙·Шn^ДЬю╫MЄ▒%аL│╕Q4-2╧.Uз ╒╬ Тз╝╤┬2%ў9Tыэв╤Y@┼}▐√╠s ├╫0йЎъє┐╔O°пнk(К┘тДИ╥ZЩ■їyЮГХЎмJ;│г ёТ■N"┬║ё═CБ╢8╧Pч╙id=!╫d√)??П∙y╧ve ЫЎ╪огя┐╝кєIц┘╒!д^│щJз╟!ДBH(Cwя▐н╪¤@.^║МgЮ>З╪Ф=Jyёh3--╢┤═h(╡*═bЩVы]╩;П▒ XщY┐и╪Ю┼pй╤{╛┤bЛоJ┼╥DМНе█L+юu▐+Ї%╓o~ц├ЎS8*bЧ1Жй╟ы√°р┴ГvoQ1·в\g╟qэк\ПU░Ё`Ч╟!ДBФnяEї>ёюg?bSВщ╬╗QП]izчмвл■VCЕk█оЙ$7╝87ПФЖ■r#A√#gl,ЎЙА▌╦їє}NL!ДBк0ЬШB░Йry╙уy=ДC╪]B!Д▓oбИ%ДЇ'Ыe|c∙YмЙРu?Ч^З9 !ДB╚■Д"Ц╥Я:Б─╪1╝!B╓╝^ч┘ю,Б▒ДB!√КXBH▀r(Є8ЮШЪ┬Ф┘Ю└уT░ДB!√КXB!ДB!E,!ДB!ДРББ"ЦB!ДB╚└@K!ДB!d`аИ%ДB!Д20P─B!ДB(b !ДB!Д ▒хr╣▓¤@щ;6╦X{■ о╠╧c■y^лДB!√Ээ▒хy─ЗR╚┘ПГ┴fGч╚ц2rй8тєн▐╠чРКгх╙█в][iУ═5\yvе7юGtьIрНэЕЭЧ┴╘jХЕД¤╝Ы╪▓╙cKЎЫeФ^ўc═ %Цэ╩єk"m !ДB╚~жk[ЮO!>4Дбб8R╦6╤ТKi║│┼SєpsN·(▓╚#u╧йЕ!√х3фд╛xэ°P▄s╝<ПФ{╠у лЙidbY\kP▒б∙|ъ3°╪Zfu¤R_Щ Ny╟в┴ Z\Ў9ц╫▒┐jЫ╢┼═k ї│%м}u∙ъ╞6д eїю·ф _Ь·цлї╒┌QЮПW╟╦бМy)▀m;┘aЭ└SSШzr ў▀zk-Н/!ДB┘лt'bEtЗpv !ДB╚■е;[*"Ы└Ш]k%26!╥┼C9g<]й╘КэDЧ·цЛbX*╚Л╪ШЫ[1#u║ы╝Ф7Dnй╨p"Йёz!Ж9?Н9пж "░╛;{ЖИ╞щД╘Aвqф╙ДдHt╕vм┼>ЫУrх▀╚╘jўa╠Бs┬з FDъ▒)s,I`aAД▒╒╒╕K┐h}║├DLDн╔Ш└x2П┤Э хх%фУу-¤╨@╢нўу╩e@l├Л∙\,!ДB╚■ж+[▐PжОЭEу╟ЁH╜мЄ%0Я}│4Гёс"о═FQКЫ┼и uBZ█!БщLмъil ┐·┬ьь'║щ│6щtNД▓¤mHLg╦^У1+cy)П$▌░╗@'╞ОсНхg═є░є╧Цp X'(b !ДBЎ5]ЙXуyїx╜М╟╩┘unl╙S $QарйwРх▒4'2б\v6I ╬'чфЇ╝(вc╙ШЮ╨X╓6Ї`ф(FфЯXf%ок█*жЭek}^Liv╢╔3ХMЄ╓'ч°┘Y%╪Цбе>ыФv╟6ы%MOжРs╦s√мХ6ШgfЫ╜f╚┌и╗╞█ М╡MЭ║jї2К∙Ж┼\▓єК<ю<k╢'Ё╕ZL!ДBЎ-▌ЕGж░иПЪ|тШC-╘B}ц╤x╟&БїlН"Z]0'Б}аR╙в"ЎвУF ч+anT╧╡[║Аdf╤ )OЯХ\Тs]п\▌т<═ылз>М╘┴/_P}Av*н╪╥H)[П>nЫOлw╖qБзvёoГY▄(ЪЩghк{v5╠Ццэ Я~D0╡╕В u√4:'╜мНГEN,фє╪·Vв&ЖпaRєшs╡╔╘веЭчдєщ4▓ЮРyB!ДB╚ю2tўю▌К▌фте╦xцщs8pрАM┘гФчП1╙тbK█КЖ█к*l Ц)au{▄гдК ёY ЧВ<╧e╠╟гXЪМ■___╖{эsф╚єп{ЭўКГ_■▐№╠Зэз╬9xЁа▌#ДB!¤@╖ўвzЯxў│▒)┴tчЙ▌ЛDж░ZЪ▐yлшК└╒Р╪┌F╗╙ФQ╬хМАM#ГEЎ?!ДB!}El3z┐Ф0щcrй(вг│X╥W·м║пp"ДB!ДЇ▒dЯТ└В╧"V╬╗lW▒║@K!ДBH┐AK!дПp┬∙s▓/°F!нP6▀'9є╓B╚^Б"ЦB║EюМтC║\XыФчЭь╡~ДРAА"Ц╥┐<Ё╞Ю|OЪэ1<  =vbЁЯT╬]У aвя%╓uфz▐"]u╜Rq╪цs▐∙Ї▌╦█К╠1dж╖{m°26 ╕ си¤ь%ЗЄяG?┐А_x0ВC▓uM╛`wЪ┴Ф▄HWj/доШпИ`чН№6╥щ° ╠8╠▌"▒░Вd~йЕwєB·КXBH_sш╨!│б\┬ыDС▌~└ЬZ╢Й.rГФК;╟Жф▀▌ _Sяж GК╓Здъ;▒%=5я№лч╘B¤єхRТMC%lv╘=gBЇJEфckж#_╦с·?pCГЭэ║'╗o▒ЗЖEКъ~├ЧS╨▒ ╡▐4HжСX├&3!dаИ%ДЇ-ъА5lЦQz¤D√┼ +7╜y╣GrЕ[dLЭ│лўuP96nUHD■╒f _У√3╣!Гs3(Ыюы╓ьUO█E$С@"1Жa√╣СXfл X╠h pяэ№ЄEдAЙ1'uфиЮуl▌6!rt─ю5┴бЯ█~o ╧ ╬2▐Ф √ufB°/5xG,сЎя,mмЫп╩h№Є8О╪╧ЖG╠│▓O№ўRюс ЮР}¤|z<▓;FyCF+)є╠щ∙Hb╝╜0╔hDю█K({╧/oКрyПMK ╩╡ЩО╩'IлЮу96ж╟,&▀QD╬иJDq4▀Pv╗T├╦зd╬E╠╝[РyjЪk┌юДЮы▒─ЇД|к═]─мРiЖ▀▒╨■Ї╘7жїm¤>At3FЭЎч}SX}ToИ`ъ╤U,-ЗwJ ┼╝■G╡J╚аCKщs·,ФX0ЮWы]U╩╦gС{#ї)╞2r╗d=н╜Ў╢·S╢яG\FQ?6Ь╧бўГ!∙"GM│гq─S)дdы·S=_"о╒т ^╦сЎK9#Z?Ї╪4>■ЛЯ═ГGq┐№sЇё°gзё mрп61фХ6_A√R¤▒7_йО┌6Т╟╥\хr┘┘lj8■∙в├╥ц┬5щ╧1;╥]Т цД]о:ємЕ0VГzъ▓5б9$╫╪ы"и╩╪,╦u'┬ъuI96wM>рБ╠ЮмLЩP╓й╩шj 6╣╚&ф"KOжjэоk╗│¤ХЫ[ТO#шJЧїgK4┐╜0AЇbМZэ│Nйz▄эgє№._┐C╚ AKщo╩kx▒ЯBЙ╣ЯZСЪО:Ю╓9IЄЖ├-ФD╚.Й°p╜▒qХЕ╗Аиы┘╤QМОжСU╜ЦO╦■(f├bNCє%0╜вOцС╧fСХн√7oHЩЩШc╜!╢I┬цk╫ЁН/ОтJ2j╢Х?┴гЯЭ27ыЪч>Я─ ╥9vхЛ╥щuDЁ 7 №ЮУ  V─м=в#т┐j▒ 7╡q╕ЎnjX╨Xёь(в╤иlУЎЗ]╚╞ё(щє╨yЩ8ї▐%┐|Сй#ЁGыОI}qIЄ╪▓Зтэ╛:$ВиЖ╢WЙ Qz G ╦x6·,Ц ЗЁXйХPq═7ЖCKТ╧ї╞Ю┼l╗?#ШZ\AF$З╙n▌цмз^█ЮA▓рЇ╫haЩR+╧╤MП_8¤<Aswз╟иKt*}ю93╜╒1ZW\▀э7BZgшю▌╗╗╚┼KЧё╠╙чpрАяТoДР]d}}▌ю╡╧С#Nж{ЭўКГ_■▐№╠Зэз╬9xЁа▌█'hИЭY кС┐. ¤С№╖ХXж┤}▐ЛV┘;╦є)Lж│╚╟2(╡√Jа╢╚с║s|▐чYX╙`ееEЦH(ь╧■`_ОCY─|щ╝|▀$W░:рп "дЯщЎ^Tяя~Ў#6%К╪r╣R ╗Ш!Г EьF├ёJ═\" ¤5∙п ╤Д│└╩N2 vn╛4П?№Wil Г'■Еы╒m@oЎgЗ{,дўь╧■Ау@щ!Г+b5L%Z─╠ ¤┬7p6╦Mbj│├Л;яe┘nq╛Ї1▒Д┤А╛^чЛ▄ ╦3x№'Ъ Xб<G┤8Г ╜6█√│?р8Bz╔NК╪]|&6ЗT▌╦п{НO}С)╠$│Шї5дvvWцF!П№╓7uяpB╚b^п│К┐ `Х╚╘*oЇ╖Ўg└q Дьvwaз╝▀╦э{ДO}Йё$j/∙oB/ьь╕╠жV+═ эtB!ДB╚╙╡И╒E8тfх║8R╦6╤ТK┘■t%╗TmХ?']▀┴ЦG:ъЮУкКH┐|ЖЬ╘пп[=░мKд╗щ5пdX}HL#█·jЗ╨|>ї|ь -3А║~ёT╓▒Э·lL<ю╪)щ╬9-оZTж[┐ЎБ┘Ч╣aН Ъ/ЭЦщO)mO╡╬Z┐iHU¤╩У║Ё├P eB!ДBvУюDмИО╔t%}П╫*жбялСXp▀ёU┬╥ИZЕрдп@_╨Р1yuл=щЧ╧И]∙rвДR╔nЛютrLЧNЯ)Щ╝е =щИФ░·Мws&Йьl╜`╬ч__РЭс╢°уЎЛофяеs;Е№fV%п ▄°jI─|+K╠;efGVL█VF "Юнh╓w╝Х2bI│уX╘:3@V!Ь/vХJKE'ПЦ!Еql^YС_к╜б╝Ме|Уw╟BP╩(Ыўчъ■╢▓╖(;яё╬┘wЇ▓OшN─ЦК╚╟&0f╫КМMИ╨ЁP╬/X*5Зb;Со╛∙в╨w╛чEИ╠═-ЛАТ:▌╡Нк/░v"·т|пH ├ЬЯ╞\+║H м/└╬Э&м_b├bmЫШ2E0O'д]$жu▄ ╪hшыЙ1ч╜v╒gpВцKЛу╖е╠PbШP;u╫╛Ш}╔Ъ└╕(w}╗R^AЭoщ╟B·Ъ▓╛▓╡шО▌AзЛ╫EУtN╬Й╢hRФЙ╢░C ▒╪F:┐эўVР╣П":{ ╫6lRl^НуЯП╔╟╛d╖P╞э╟┐6ч ┘┤^╤^Єz╪ vp╬o╚<ЯХ∙ює}H╚^д+[▐PжЭEу╟ЁHЭ╝ї'0Я}┤4Гёсв╣`ыCq│u┐дMxm;╪э╖ф▌sёл/╠╬ЭжЫ~┘>чЛagэLLg╦^Уq)cy)П$▌░}╟f∙y\ЩЯ╟╝┘ор∙╡║╫ЎУу▐°╢wжтбсёКЪ/N╖╜Ш║*[#WB1?╕▀C▓I[r-7?╝эMй>&б[й∙V=З╓╫%ЭО▀╬-JиПЗ╚▀РСTV░0e╚ьВCgWёW*¤х<^·╖═ Ъ▀■зQм№▐в┘ЮРн;:╜ОЪчы°z╪Їю:┌Йя:╜чLL╔<Ч√╬¤q▐mG╚ж+kВг!e═Чоэ4╧╙6√╥ЎЙюЪРa`─5╘xfueъeє1 ╖эТ&╜еМ╡х[╕ьILMMс╔▒√qы┼иcw √уN2Й№╥r{▀QБЛ╠,N╫7$▒bYAF\sЧhЕ╢╪Уш╤,FVЬG)Ї╟╧╢╪ё¤:┐А|█▌є]яD m/el■1p ├═■X╚▀Щ▀О~~┐Ё`ЗdыЪNпгЯ{АЮЇ┘╬╫%Їё░нQlДьE║ 'ОLaQ34Л ┼1ЗZxиД·<л∙eyШQп╫(в╒┼tX╨Я5-*b/:i.║р|%╠Нъ╣vKР╠,Zб#х╔ ╟╚ТЬы■в]╖pOє·ъй1uЁ╦T_РЭJ+╢4"7:╢}▄6ЯVяо╫C╥ЙЭЭвefР,8uНFР)9╧р╧M4-╥╤.2х [ Ш/AvЦщR╥▀;Ъ=╧├─Ё5Ljy·╠mr╡┐'ЄfFWжN#ы s&¤─▓9/B9tшР|║_■5ўuЛвеt┐&мLS/Я╗`Ъч║ зДв>'╛╨$┤┐мЮЛZЩю|aЛ╠∙-Ng"pъвUЬя;ўRп╦╫╕╪ЯPЬsS/fkнs~°▓х6є┬D"Є┐Д▄ДъZ╡╫░∙┘╥V█Ы┤A┐ПЬф╗╨ы9Ї▒3м╛^P╫Ж║╙ё╥╛ў╠C╧q┐|=kC├г+u╝Ц├їрЖ;█uOxЁц╒T5°Я Гy▄╢щ%l▐√PУВ_█└rўy╘~юВ░~щIЖ]Эрє=aЁлO┐ №Ь :ж°╢┴~Ўj·НСбУ~ hC=·8[+kЫ▓╕{ўnеХэЛ2ц▀=O)SЙ!YY▒wФХdEЖd╦╦Фь ;H?┘╥1+Х$bХ`УKХLl╨┌╒Ьo~єЫoН╫yп6|Ўх╩Ыo╛┘·v√+ ╦┐°+ у (█┐°_*x█I▀wШяе┌\.ebr=┌яйаcцpцwйфnц@8·╦╚в╗и$л_К╬5Гд3хIIйпZn°uзх╒_sОЭ╒:╠ўO│яaй[Євvb╡}╙^[Э║jч╗╕ї9∙JЮ╢:e╘╫▌╝МF[ФЁ╢7╦чМЩ┤#цэG%└NC+їm?~узm╥9▒R7kl═зlL╓їУKй▓Ў?аrq.Syу╒Rm│G▀XКU.&bХ╡5=Sy>Б╩є/┌Г╩ЛI9ЮмФэGГIУ2╢║|▐/ЭЇg█╫C╟}O8їUчK╥3_к╫╛Юг :хУГОЩ4┐64╘╖e~ъё▐\G[╟и├~ mГЛS■╓yA╚╬╨ь│╒═╜Ol╝wЇ█║є─юE"SX-MптОглЁVCik█jW▒┐╥O╢ЇД2t╒╩∙xid░╕g┌╡Ч╪─Zno▄ ╞╞╞0v X╬нIъ>$hQ┤└Ў:_d.w-Л╪─Q╣T╩ИJ!╒їкбЪSfa7їV.,,└о╔╓!ГjZ7╝ н.├DнёШРЎWЩkдЫЕm Яy^QgfдА╤иzf▄╛ю┬╬╟│╪_▌▄y"GїсФfDpшg─▓▀[┬є┐│М7%E├~ЭрО■Ё_j8Ё"ОУ╒Ў"s9иО╠зэу щ╝*╠оCьВи-┤ц╘]]hMC█\$░·▄{ н.$WЖYкAЪя└C+∙Ї╞tA┼м╛?╠╗6C,╠7pфЛ·Ф╔О№уU№├ь">■p▀°r╘Д Ы╞L8p j╧∙6ЮA║рмe▄■Г╝╧є░ЦЯ9jё ▓цпч1РV┐ l(b╔&БЯ┼бЬwыоbuБ╢o1╧└╛БM╫ї║YFщu╗┐╧Z-p┴4qаЛ╝Йї[d╬<Г╒░(ZюЪ▄&▒RН┬pЮ 5ОR√ккЇдИaw9w!╣*,\gZ╗6/u╟2Ш╢╢╡EХ2rєУH7╝яY╜╚(И8Ў┌╥╩Brц№цSZжуээ╔ВЕrО╛▀╤е\*┌=бея·║┘╪Ў%╠Ч ╘ '3x╦╦^╦сЎK9#Z?Ї╪4>■ЛЮ<К√хЯг┐╕И╟?;Н_xhx╡Й%пФ░∙Ъ┌ЧъП╜∙Jэк█>:э |_эЇ=╤стРБt▌ЖmЮГ~Ї║_кi√ЩР= E,!д?9tЙ╟юGщY}╜Оl╧Цp cOтёnntХаE╤L [dNh▓(┌Цp^DаЪN╕oSЛ║jпЖ╛║e╧y╝^ ЯEцta╟K╥|q║╞&b╚ж5Мyм·уR°"Бr╙g┌┼lq+vС9'┐о0ь╡El \Ё╬=щ┬ИФщ▄ ўj┴B╟Cю╪-`deЫ,l2╢Э6~~Дх iCG╪WцНкP░I┬цk╫ЁН/ОтJ2j╢Х?┴гЯЭ▓▐╙~сєI╝ё/ЭcW╛╕dRkDЁ Wь№=' 7■нИY{DОЁ_╡X(ыkЫ┌¤1┴п_║ы╧╬обэ6}Oh}═ЗьЬ┌рKpЯ╡▀Ўа1ъa┐шВQ║АefЧЙ#dЗ╥cэ~ /]╞3OЯ├Бl !дЯX__╖{эsфИ4ч^ч╜трЧ┐Г7?єa√йsФB╬~ 60;:╫ў6Ччуr╢TПмэ█ё╦!5╟|+┐5°┤б<ЯТtз т-DvЭCЗЁ└ы%Ф]╤║YFщu╗O!ДBЎ-╗шЙaO╡&L╢Я·"SШIf1ыkH/ьl┐Lє▓эJ+IЫ@Z'ЧB4]└╚J еR ЛSЫlp╖°ы<{Eч~<а┌ЦB!Дьcv7Ь8_░;;ДO}Йё$Єщol/ь▄щ╢яcr╫▓@r Й"┘l║!╣b~0█ъT¤1▓ыD┬<л[%▄KK!Д▓┐щZ─╓┬4уH-█DЛ╫╙O╒╝\N·(▓╚#u╧йЕА·х3фд╛xэxЭўм<ПФ{╠ущ лЙidbY\kP▒б∙|ъ3°╪Zfз╪╘ЯAуч╫чP-╜оL╔S▀ B5:и╧B(.{lїd lC▒с(╩х▓l6!mOєM┬УM╚wЭ7╖Мyikпв┐ўЫ╪Ф┴Y{■ Ю}ёu{Ь?4B!Дьs║▒"H&╙LФ╘У╡Кi,Й4лСX░оJ H#jяцЭЇ$C╞ф╒m s╘?ЯгYHЕ&,╘lЛоўLОE╙└L╔ф-═щIGPД╒)aj&Йьl╜└ ╬ч__РЭс╢tВcKvd┼╘╡2RБ\Т╛¤8~A}Pfb\┌ц∙A wM>%1n╘gaфQ W5▀╩▓оч<а о╨╓f ;ъgаЮ єщILNN"к?(┤NЬ╟RqЛ┌■RFa╩МL═ Щ_┬▓[HyKy╖эд╩╧?ЛgЧ┐Бв{r ПS┴B!Дь{║▒е"Є▒ М┘╦╚╪ДH3хЬёXеRs(╢=ыЫ/Кaй@Е╟▄▄▓+й╙╜й-oИ╚Q┴р$DTLyEц№4ц\ёF`}vЎcЛИтщД ЧMLы8░QS>¤8~!mЁг╞УвнКuBy╟С▐╒┼0a НDЗkv┤┴┌ц9тj╪Ё*ь#▒Ў°*VWїyуХ6╞_l╤╛╓▌╚&b"jM#┤эyдm!хe╘n█IGD╫Pт'Ё─у'`)&ДB!√ЬоDly#@ЩjXitEМc||├#5yH`╛жV╒√5Гёс"о═FQ║Ъ┼и ёtBv█!БщLм*╛Z├п╛0;wРА■ ┐а6ДМmb:ГXЎЪЬЫГгa╜2оЫ1┌JpВ(ЫW+╒Ї│#┌╗е╓Ў2ЦЧЄ m'ДB!ДtKW"╓x╜<Ю4уyrvqЫ└ЇTЙD(8GъЭny,═╔э╛y&╤┴∙ф#<вИОMczB]l╓у9К∙'ЦYA╔x▄/Ыыus╪ZЯЪЭmЄЬfУ|Бї╔9~vV ╢┼Пи*нВИд-╢8@M╦═щ8Мри╪╘ЯAу╘Ж╨▒5▐╔,о═_C6Ц┴┤луZгЎnCхe╠ОF1ч╛#╪Д■╞0,═йbЮ▀mЎj;v║kЄ#┌┘Кm√ь№2КНхB!ДB║ж╗pт╚їС@│@QsиЕrAи╧JП█$0гкQDл╧&░аqЮЪбЭ4B$8_ s"<ЬєeKР╠,Z$хХV0▓$ч║Ю╛║gЫ╫WO}8иГ_╛а·ВьTZ▒е9N d1║┼Ц Тз╝╤┬2%ч9█└■ ┐а6ДПmc1d╙Y╤║cN╪н!и╧:$░ h>ГВ┤╤╪]┬H▌ %╒чyх▀zbШ╛ЖIУ/mBХкWч∙ъ|:-╛цL!ДB┘Жю▐╜[▒√Б\╝t╧<}░){ФЄ<т╤"f║^lй4L╫мBTO,S┬j7ю╩A``┌ЮCjh├е ▓оJ┼╥─╬┌╛╛╛nў┌ч╚С#ц_ў:я┐№╝∙Щ█OЭsЁрA╗G!ДB·БnяEї>ёюg?bSВщ╬╗ЙLa╡4╜єVI,╪█·m╧ XeO┤]C░sF└жС┴т~7B!ДBvК╪fЇt)a▓W╔евИО╬bi$Г╥jэuDД╥╬a9┘║z╠ВB┘уP─╥ ,°,FU}e╧,щ Uo╢РШ>хwЮ▌╓н·▐ь@╧╪нХ┘ш╧■ФёПGЭ╜Жk6ЙB!MбИ%Дь*Ыхчqх╩<цччё|гА█\├єЎ╪№Хч▒╢i╙ўС)}Ч▒}чё╛C┼╜ЬfЛ#хо*▐!┴¤┘XЯn^!оb3╒Ї╟Ж╬╤чшGСYAeu SЎ=╘ДBi E,!d┘DY─@ЇуПсШMй▒Й╡▄Лx#·$жжжЁdЇ ╝Ш[УT▓ И!│RBй$[f┘╤9Пи▄ntuйg%#╡║ї6,ЇЧяЇ¤╘>╪W|eкя##ДBH▒ДР]фN<■8ND┘╧6╦(╜~ ?с;tту8Ўz х~Q▒ц=┬╓SWчЩs┬ВSє)─НOўkn╗\╩їю !Ю┌·Кйт\╩п╧Ип-шКр╓яo!s┘█╢Фю╫<Хu╟ЦmвЛ╘wэ╘н╙Wl┼ЖQ}sP█;щ%A─н *√╓-ъМ▌(▓·^nєZ/▌<^Z┐·┤пуqзэТюЬу /Сч+╣!ДРЦбИ%ДЇ'ЫЫx] -пс╩№мЩ■╫5╣бкяЮ)Щ░╘╥ РЮЇ ▓<▓┼q,ЦJX╔┘t═sш<;н[ ·╛х║g2є( O;╟K(дгd3╠:]\Д┌d║АЙТЮ┐КщсВXmi<Же┌1╡S_г5a=к║-╢·№╕ИFў╜╒K@f╤} ┤╙v ├╒ЄVF ".]Qtм3Ь▒[AR=┤жН║╣^┌Рq╚П`fUЄJЗМпЦРЙ╒▐A]▐╪f╧.!Д▓╟бИ%ДЇ7ъИ}@ =ДC·o?P▐@Aд╠x┬СRС─╕ИУ%,WЛЙDРЫРO╩9╠╧зРJ═б╕E╗─0с║у"cШРМЕН9j╦6╙р5Мx█t Q ╦З|zss╦",х╕=/Пh\&н╕7mўЇї┤╓WАщ▓аc╜ l╝▐у"GGь!ДBZБ"Ц╥ЯИh5Ъї╨ <ё─8qhЫп?а╔}Bг&ЬT7 1m +Н╬вИqМППcxдN▐6eфh+JпC[кyj[X8qР╫0╪г┴╘кИ╨╥ ╞ЗЛ╕6Х·:ЁМFuЯ╜╓НO╡Wt0.∙"мcЦB!!P─BvЧMз╒a▌╖E}р╛aЧ$▐\√nс■■▒СгP▀Y,│ВТ ├╒Ё┘╨H\y▒ LO%РHDБВl╗U6ЦСЫЯD:пЮ=Ы$D╒ХY╕Ж\╣М▓nЪ╪б-ЭЖяк╟├X^оЕ ╙6Хsjs╤▒iLOш╥└ ЮQєLiє╫ бd█Ь╙РхД"Ц▓ЛФё№│╧т┘gЧEа╖Цu?g_еs'Пс■╥│ц;╧Цю╟cO>▐'п╤lW0▓4КиыykaСв╚╘МИХ┤═3 ╠d╦J╒╝юВAQ╠'░╥░*оУ?ЛQ}64:i┼bg╢tLd ЛX;уШГ'd8шJШsЯk╒-]@2│X/ЄD8Є╡gEkxЮЙMC2b╤d╘╢gР,H█х╪haЩjЯsh▐Я·lлSO╛Zп╫c,хъ╗yt▄ъЄu3 LgbRд╘╙│Б#ДBЎCwя▐н╪¤@.^║МgЮ>З╪BH?▒╛╛nў┌ч╚С#ц_ў:я┐№╝∙Щ█OЭsЁрA╗Gv KVк СN╤ХЭ'╙YфcФV[]ЇКBщ║╜╒√─╗Я¤ИM ЖЮXB!эсЖ╤ццСЪ═╔q ╪m 2╡АU CжА%ДBбИ%Д╥6▄╓Д▄.б0ТAiБЦB!;El9ЗB"ДРI`┴│p╤ъ╜ЖДB┘Y╢W─ЦчяфХ ╗╩fGч·▐цЄ|▄Y(D7єВ─0уз6╞╣° !ДB!√Х]Ї─цР1ЄJ┬m─з╛╚fТY╠·╥ ;█/32╡j<║(ц╛жTD>_└Ж¤H!ДB┘_ьn8▒ИС┼з╛─x∙tА7╢vюt█ў ц▌Ц-╛{СB!Д▓чшZ─ъ+т&╠5О╘▓M┤фR6№U╢xкЎ╛<'}┘ъ;uлЕ▒·х3фд╛xэx▌{°╠ЛЄ▌ЇЪз3м>$жСЙm}╤|h>Я· >vЖЦ┘)╢їg╨°∙╡┴9TKп+S_╖Q╫ЮRrN5:и╧и з[[щkГo}jЧ┤┘█■ЦМqЄ═WєI√m>cc▌{!╦ШЧ║{¤MHяРy~Uц∙[ЎгЗЄ╦2╧п╩▄╫э%NnB!Дь<▌ЙXУщ&J╬╙XiV#▒р.■Q┬Д╛р▀▐═;щ+H"ЖМ╔л[эГ~∙╠Н╒hR!J%╗-║ЛК╚▒hШ)Щ╝е =щК░· %L═$СЭї Р░|■ї┘nK'8╢dGVL]+#╚5!щ█ЯБу╘╫e&╞еmЮr╫фSужБA}МNэЇЭЧ ;Г√╥┌lqЛz,dГ╝ёuф▒д∙╘ЮТdLOqЩЪA2┐Дe╖Aхe,х▌╢?6╦╧у╩Хy╠╧╧у∙Ж╔tl▀ёZJДг╬_Щ╫" Sп┘t√╣*,э╣7yDо┼│мЯЫ└ШU6С▒ Сf╩9у▒JецPl'z╓7_├RA^D├▄▄▓й╙UUх МXr"*ж╝В" s~sнйШР·ьь╞┼╙ й'В─┤ОCn█¤·3p№B┌р;F МЛ╩╠ZЫ╗цyЗd╖c╘Ф░9╨/▐cНs7Р&4ЯюF╞0Qkбm╧#m'Qyy y╛?3ДM}х(в ╟lJНаcдЩ╦ПХP:]█ё^░d[╣7 ПГ КЭДBHЫt%b╦ 5м4:Л"╞1>>ОсС%B`╛жV╒√5Гёс"о═FQ║Ъ┼и ёtBv█!БщLм*╛Z├п╛0;wРА■ ┐а6ДМmb:ГXЎЪ∙Х▀╤░^╫═5гП·Zи╡╜Мхе|C█╔Vс─уПуDфР¤ь%ш╪~&Ксv╫╦}D╝ЫITO[йЧSИню╫~5╩╜дi╬ikTD▒и╗н∙ykй╢▄Ц=ЦОЭєU;ы=╔╛vПвж╦5/√╒6Z╧b`╛q─╒N▒╤▒╫>э╙з<√8╚u{▄x4-~m▒3=▀-╙ФыiGзvVёЩKДBHЯ╙ХИ5Ю;П'═xЮЬ]G ┼&0=Х@" ╬С┌mЙТ╟╥Ь▄юЧ╦╬&)┴∙фЬЬЮEtl╙Xj=kСгСbЩФL╪йnН mн╧Л ═╬6yN│I╛└·ф?;л█тGT▌ОI[lq<АЪЦЫ╙q┴Q▒%и?Г╞/и бck╝УY\Ы┐Жl,ГiW╟╡4FэтogP┐┤ДyЮ╢с∙[Г;▌5!├└И[иm√ь№2К∙Же{┘юFьбaЩщ║▀фG┴╖ф:░[=ydяНcё┤Ж╘┘B-l>ёи\Гgu│П╘=уЪGс└┤s№Ї Еh вK─шї40\2∙J├@║Й8nО\Wjзй/#ЧёdUФ∙┌∙рВ9W#,f▀vЄЦ┤Нп9╟█wo3зVР╝МЯ*!s Пвй╧┐ NyЎqР╙n┘ЮGL№┌bз?RцЛYр!ПЧ¤Q╧у┘)Д═%B!д╧щ.Ь82ЕE}$╨,P╟Ь▄&╕Н ╘ЫуqЫf╘C5КhuсЫЇ}1ЪЭ4b*8_ sгzо▌╥$3ЛVIyеМ,╔╣озпnСЭцї╒SърЧ/и╛ ;ХVliО╙?YМn▒%Гd┴)o┤0ВL╔╣a ь╧Аё jC°╪F06C6Эн;fo╕Фа>ыФ░9╤╝_Zвд·\nlх▀zbШ╛ЖI╡_ЯёMо`бZиє|u>Э_ ╒&дkюЫ┬к02╟]┼┬Г6▌ ▀[/╩№┐юlїbSД╠p┬xgmЫ+g╝ЯйЧцмАє"є№гv▀7ЖЙr=╝r╡╛ex╨╔yp\DтЦ╖Ф▌ ╜оlШ╛йODэ╖m}Бv:L|╘╔kЮ┘}╘^РA∙X╫Hзmh1_S;}q<еyЇs┼eФдм╚}ЎP7}8Ч!ДР■з;+╘▄С?ДSЄЗ▒║HС єt╜m =&√л╡┼БРXp╥ъ▀·BьTЪХйa║о╕єlюъ╖ ¤S╡eкЦ╛║рi[├∙ у8~&╜Ц╖╓Жр2╖▄╒╞v√їYKm╠sо^ВьВ·Emпьi╗┼╢л&P=М╒╞o╡ё│┌╡▄Т╫ xBzЙ╫у&s╢Qв!о╫эc"АЖ[Ё╚Н|░ХЭ┼и·jBZ╗д; Эц3t┌Жmn╗|ГLЭТ1==ГёE\+Fе\oXЁv╫G!Д ]Л╪=ЗКЩ╥t╜Ш┘)╠;Paф▌╢┴╜HX█587П╘d║n1к■BCЫsШПGСFЛ√a▄╢Г═M¤ЯEўлВПСz<с─f│╔~Ф▀.&0¤HЙгТ┐с▒А*rэ╜<ЙЇ=ї·┘$!z@Dс[╫Рє╓wЯ}d@W╖В║rvSоў0Р<ЦК6L нe,▌sDsыv╓╙i╛╓┌`m▌╢╢√!e┐жхG¤ш4ж▓ПKи╖╡S; !ДР=El3ъЦс%╗O) ]Ba$ГRгЗ╜O╚ефFstK}lc Q╞є╧>ЛgЯ]╞-∙tkYўsX3Z5шйз>ЬX╖╔РЕШ"П╪╟Мo╬ Ўъ(в╒ЕГдL│(P│ў&░r║>R┴╔Я┼ийo╥Ж▒&░pz#ZОы!Ї.DH оaRєш│ЮнПrРЭ F▀Y+чц][=╧╝Ж╖╧П░6╚ё╟DLъё█n▐ныcg0%╠y╟╡P@rd╤ ╒NэlБЄ<т·>ь╓ОB┘qЖю▐╜[▒√Б\╝t╧<}p)CB·Сїїu╗╫>GО1 ║╫yп8°хяр═╧|╪~ъЬГ┌=▓7╚!uu├з╗ї\ТmAяV║~З9!ДР¤D╖ўвzЯxў│▒)┴╨K!ДРzb> _B!}E,!ДBкШ╫иНх#ДB·КXB!╗L ]/ВD╢ │║|╙e┘ !ДР■А"ЦB!ДB╚└@K!ДB!d`аИ%ДB!Д20P─BH▀SFnш ╓┌|oчц№╠═;[к^·Y~9Е╕}╖i<ф}╢[xk^ЄжP жUщЯЧтэЧ╒}▌OєM▐йj▐ўъ╛У╡хw╜6бi╢ 6lэМ_PЯm[6e;ч┘y╢О_oцR/пХЫГ= ^"d{бИ%Дь*ЫхчqхКИм∙y<_ў╖~х59&щzl■╩єX█┤ЗHKЪzSХ)М%m┬vёZJnїfGoЗРz═ж█╧╒ЫI│5▄I▐hбАС╟J(Э.aёСэYwун<ЄўJЎSoИ<▓К╩┘ V▓ ╗ОЇўНT╧─jS┌┐а>ыuю─Ь,va╛tAу°эц\ъФБЮГ╛▀єДЇ▒ДР]DДк╫ш╟├1ЫReSО╔▀■П?9Ейй)<}/ц╓$щ_XaSz,ГШ№Ч1BgARkф^╦═`с┴"ў╔f╙╗#ВйST▌З+ъ▐+╪ЭЭб7у╫ ЎёЬbЗчKчьЕёу$дЧP─BvСC8ё°у89d?{8┴уO<ў╨бCўпoюk√╞rWLxЁфц==Q^C.ощ▓┼s-Ж█хy╖╠y\q╦╠е044┤eЛ╧7┼Ё╗ыв┬╞}UОw┐ jслqд╛m-╣Ч4▌┘╢Дэ╜&∙n╘О▌ШЧV;╘х{йЦюR,кBПKЭнЖ╛5ПФ[_Г╟+и Эр╪?К,ЄH_╖u6x╜Л▀Ў╘щmCАЭ│ГeЪ╨Q 5сПN(иЎЗ;■■sB=J┌═√еnМ^╥¤VB+Э2ч}Bм}чЩёri║╘бє╘ьK╜╓█ШяF▄Щ╫╥'N x┬a}·,t╛°Н_ИЭA╫ШЭП_яЁ╜▐¤·%Да6°╧│р∙┘ё┤б╡яе&▀єДЇ▒ДРБ`sє рX?{~z═ы"р?О'*Sxrх6╥▀░7Л"Fг/3OЪ╨с'gА'[їX┐ОR1КДфЫ*=днN,аRйl┘Vзlя▀7М╪C├rkг√1УЖ{S7·к|xu╘╣бТз╨ЫB╣Ы,0qZl8╗Кi,╔mxН─гЪ▐,МPn_TпбЎj╢Gзкє╟═W9[┬╥И╓=GЧGс└┤s№Ї Еh бtR▀ї40\2∙J├╥Эо°iC'8Ўп йoSоn^п╖┤AjZU[A╢0W╗QЎ│3Арёs╩╠▐╖b·yх╛В%П@ъИ╩|л╚═uён26─ОС:гы?'Ф<▓ў╞▒иeОа╓/Нctа╨╞х▒дeъЬ╬Е╔ъ╝ЎЭg╩5&ч╞dФf▀vЄЦ╘Ю╫ЬуБєє▐fN╔╪▀╞OХР9РЧ>╨■c<_цDаЭТ/р егёы~╫{@┐Др█Ж╨yц3?н-MпЗ░1ЄkC╪ўR▀єДь$▒хr╣V╛Т!╗╞цrв╙;╤Єэ╤фD╟╖Їбш¤Є╔RVяЇQDЎX"Кг∙Т ╙G╩ЬО└фМDНЙи]nA■▐7ЕUs│┴╘глXx╨жPwSў╨К┘╫зй0Oн▄шцL`╠Ю∙шД▄м╡Ву=╚ЛаШ+.г$7╔u^с╖r╞{Цzi╬ /1L|╘╬╡√╞0!х▐щ╨╖6ф2ЙёЭ|С╟Ed,aY╦ю╕ ▌PkCDoF═ЮdgБуg╩q4Ь0!╞Йam_┴УО (3ЄA╣CЧу╣╖БЙЗd┐-╘эпп;═fG▌_║╚╢уЩх∙x-41╒NПчРКcK$cЭ╫G· │Ё╙│%▄?ЎN4Й:&╩ЦmHЁ№╨▓|ъТЦ├Й{O∙эNЯс│╧гЭЮ┴°Б"огиЖOj°▌їY1.7wуn┴╦рzЙВ╔b╘x(u╙╨MЗ╬█╨+Ъ█90XQ╛q8·╤aрэeя┼dЭ├Э╨У1ъ`Ю:═gшtl;╔pН╤Гё█nj╫√Ў^+╗є]0(▀KД┤╟.zbEШ─Sm У╬ёй/2ЕЩd│╛ЖЇ┬╬>i{(█kgdj╒Д$оlўJй>ьt}дC67ї▌w?Ф▒vх Ю]вc ░▐cдJфРёд>Р├Уl6№═uh°аа▐YщCу╣uyе╣▓є╣\F)/чХ╥┬┬ЙГxлМ▓ыЁюwИёx╝хo╖К+u┐&вИ~t╙щБу╣07o&0¤HЙгbзSт╓п║2r/O"}O=6IИР[Ё╖о!g┌зuўЕ·Фb#+(/еnОз▓є6┤BK┼ЬcЗkKvvМ)3П┤╡#W╘ЎНриз╠ж}fёяOЯ2їШЇчТ=п.╔╚╓╫╫.▌НС▌}kK"╬T╡>╧ъщ4_kc█d╛┤ФпТ▀ч д╦ёk{.╡L├ї▐q┐°╙ё<3╢_cM hCo┐Чщ=╗NЬ▀с_Б|ъKМ'СOxc{agЯ┤=ФЭ╢Уь3╩x■┘gём(╒[Єщ╓▓ючЬWщИ`-╜■║жтEУюlїпс!r+ВDi ЗЦЦёмыНН╫?{hъу8к▐┌иЇaTnВк}°в├%ф4П>WЫCвЎPeфР║.7┤/жхfHn╕^Ф¤nЯН╝o Лrц,F╟ф╞╦2їYГ>лЩ/и'╚]HеД9S┐▌ $GЭЫ╖GfР╘ч M▐I`8Г╪лгИVеq┐ЙbЎ▐VVXvЄg1j╩Ю┤7Б║2є F┤kSuСЫ└6X▐к-r╙:Rчc"┤╬:[В░│c┤╠ Тo9vМ╛5ВLK}цр▀ЯAe╩ {K├jг╓¤jИm╨ЬаХ1Є%ЖЙ╫0йїъєЗнШ╨╦аyЎotq#9╫\'ZзчЩ╫Ё∙щG╪╪·═ |f&;Го▒p:┐ЎчR~╫{X·╨ЖОчЩ їw3v╪v┐╛южэAsB1e7v╢рgЛй$╡мд█Zn]Е■у °╒чПS_ж┌▒╟Z╩─кcщ╨|юь▀№ц7;▐пє^m°ь╦Х7▀|│ыНlе╩ ■Uх ╫ ┘%^Хя╖е■РЭбхёР┐KЄўтЮ¤HH; ЄuoюK∙ЭEъivП┘ъц▐'6▐;·m▌yb╦єШL0QкаRi▓jфВжыfW╫│╧%:щvХ<УW╖┌/м~∙╠п[гYHЕ(Хь╢шоД'╟вi`жdЄЦfАЇдєkSX}╨ч:fТ╚╬╓ ┬Ь╧┐╛ ;├mёA├√Jvї╣Нq,jЭ {M√╞▒%лс"R╫╩Hщиу¤ш┤п;n{╚Ь┬╧З< ├╙╬ё╥ щ(Ь├Aу╨ rхЇ│Ў╗j~mМL═ Щ_┬▓[AyK∙$╞CРBBЁ[░Зь nъkєHхo°CусЫ iЧ╜4╧b№╬"╗Gw"╢TD>6Б1√ШTdм!бЬ├№| й╘КэDе·цЛbX*╚ЛаШЫ[┴&u║ПhХэ l '!ТпaШє╙Шєъж ы ░sШ│л╧щ3Я Є╒gl▒9-щRQbZ╟бАНV┌▐╔╡=lNhKL┌]-RhA╪э╕√"їiъойODн)4Бёdi;Q╩╦"╥У╝╤!ДtЗyЄ╛гЎGY▓єфЬPx▌^\Bс╛ JюJ┴Дl{gЮХ7ф;kД▀YdўшJ─Ъ ьЗоn╡лыНПcxдE)Ш/ВйUїМ═`|╕Иk│Q ╒нЖЬ┼hu%═vWСK`:│Ю═Vёл/╠╬>б╙124o{рЬв[FО║_Э  Ў■?╕С4╜є─╛▄]эП│UUf╒ю╬ДzB╥5▓*ЪЛ ┤Q▌ДbB┌ИЩ)╟ё╞7И Хk┘л█nn╜XazF>▐НfГЪ┌20┌Нpa╣y\jу0'ЩЎ.Бъ╢GЄД Toз├чГ╨.f*$yZпTЕою┘[▀z╨╧єцЫ@D&@Р AЄ√щ╬bц√т¤Щ/А№тy▐ў=═}?9ЩїR╒=╣ЯЎk-фhЖ%ЧТ2C @СЛc`{rdP -|уo-2 ЪюФ cСk─┤уl■шR╣ N%bНХ-dї2V) ╘3й,╓ d2╨Ўc d-╘╢tС╧?$$>Э╝жбпsрммc=kW┬╙╚Д]БнTЗk\RїР/ЦБOЗухЕ1ов╒═У╘GдЛ-O^U╧ёu9ж.╛uPєjlщ}Hвзё 'эыАУ╡=nL8jжnЛк╝^∙ЇиЕ l╗ї╘╕ ЗD│▒╠ъК╓√ш┤RXв/ !ДB!ч╞щ▄ЙlыtAG-`Г+ЫAиєНul ╪Pы╒2Ьt0_1ГJ]─ЭЖ9"ЎЬ5#|т╙╣╪Z╓╫┌г╪Fо┤mЛфч╓См╔kл\п,etyГ ║К·DеЛ+/оЮ╩$u─ьyъ╪╒ч┤┐цЛj]J╚╡¤№Ц█IФ▄Ё<█╤хM{Пb█3&№rлX>Q]l╗lv▓иў┌S╦иЄz╕кЧ[ш╚▀Atє·=мi~:ч6WG GuлXD5ф:M!ДBЩ= ║║У=Пхы▀x _№┬Ы╕yєж ╣вшТсNУ,╢t╓иKн.5DкфN╢?#9#╚/lb╔╢фЗёPN;иeчч▐╝ў▐{Ўьф╝·ълцoЁ>Я╖╛Ў}|°еO╪лщ╣uыЦ=#ДB!є└iЯEї9ёхЧ?iCт9Э%Ў*Т(ащоЯ┐АUtтЮKl аАЭ'№и.║▌╕є@оM▄їгяЬF~▀ШU╢нЛя маН╝M#G:Z═╕+ЗVэ╓∙·C жН┬╧O╖╣ b╙├║=k╢мЁ1ш┌╝ИE╣с╜├ЖzR╖Юu6 гaB ьJ╜{╓▌t(нwАFЧnр`┬>#ДB╚┼CKЩc║80о╞;°dю_S╦ЬИ╩╡bY╫ўXGxыкЄ║ВЎЖk▄|▌ а╕ц ╬@дЪїтк╦V8Ў┼йяYаЗЛмо~╩9о~~uфРB╔╘U╗H▐Ds■╗шzЎ░!JвR0n├Ег╟2 Юbgв∙п╦З@6d▌▌╛oЕ▒─9Oе╧Ы|KЯ=];Б8&ДB╚ЕBKЩcq С║?╞ыp▒│{MЕЖ█A+┤ЭУ┘П┘?=vИ╢╚╞╒МЩ╚м"gўjDкЩ┐Ъл[сrХў(Чє╚ч╖╨Щ╬°L9\▐┴ОуНЁ|X╧├A╣!в№;x1q=q[:щy▒Бяlyш║╥k╖Кф╗p2╛д]╠8╕█rНk;!ДBцКXB╚%@]Л▄y.BукXяpЬrлb╣чвлю╝аю╜╬&:X┼ъъ*ЦТ├;:Я1╕▀н╫єau5pG─+8лn'я°сcY─¤жфх╛gщ▄═Ф!ЧшCьn╞ √rE!ДР╦E,!фbщъ|W{оюд¤Л║ЮЛч╕Н┼kшQl,п╓║кx√!wт─]$хOкTЗ█s╙Н█у╪╟уTыЕ 2h√9Ъ"LЮ-tд@Oн╡Ы╟e▒│$в╖╜ЗЖзпС├Ж√┤P█┴ОЫr ▒юбМЗФГ╫хu ╡Ь╢Я√сц_Я┼%╢m╡дЖ]СхoC╧▒╕Є:^╧▐ї├┤2v╛эЭ╥ [╤\8zД√c·МB!єE,!фёЁdg;;√x&W╧Ўї╝│УОиН'╜эu╩╪qoу┴уЗ╫saзD█%╪┼Т╥╪B╚ЭXW╙vыH╓Цс╬ aJ6Р╙y░&═░QBк*yШ┤мЧRи8╬&Р═Й.Ь3ызпb┘╤╫мї╢йП·/k^╟тN╬bсu▄╒y░╞b*хo<└Эъ>vBЛ4∙п9─╛qEVёмб]|'фЮ╝Sьтn)cЕjwЛ5╔'░╞Ж}RЖ╖"ДB╚▄░ЁЄх╦#{╦╫┐ё╛°Е7qєцMBЩ'▐{я={vr^}їUє7xЯ╧К[_√>>№╥'ь╒Ї▄║u╦Ю2#╘¤yи SB!$Ц╙>Лъsт╦/╥Ж─CK,!Д2К╘{J!ДР∙Б"ЦB┬╠N▐╜╢√B!є E,!Д2Dв╨─QЕО─ДB╚ЮCl.oб┐╔├%┴l╒Р┐|їЮпЬЧ╢∙█zL~g╚╚╛>щ8#d2ж■ПjUДB!ч╦Е[b█-┤:о╜ h Я╬▀Я/Q└FоК═H1СюT╠"╧8.QyН<Ьb╔║ ╫u▒]Ш▀%PFП│8╬√>РK╟сЎ68▄KФB!ф\╣`Ы@бy4zёМV█Ю ТY═бUМ▒╞Fд;│╚3ОKR^cп ф6P╔$РH╚a├чПШq╟y▀ЗkJ╫{В▌▌2╩х2Ю ЛAяOm├З-lRяtgт¤6М═3Ж╛Ын┤{▀Z┌Юя?№О+/*Э!в ЖИ╢Яж}СшЄ╤:ЩsщГ ╙и√`╞N┌oГД√пЩ╠┬5q_gцй╡5o√t·~ё╟|yДЛ╡WN▐9+K┘╜~╣╢tсIз8п?└=╞єЮтЩи╓E{=Пd*uфZ5ь /B!Д2flЙmб┌Y┼╢ыв^к! jжrДг#╡b╪Л.ЕHбф·п9:каoCKа░СCusP─┼зqсБ ╫Д╗YN$n╣ d}ЧXslМUq|]"б╢VlKЦ··&╓QУЮш┤¤ш╚EE8V╔М+/*]\т┌>m√aиEв║lEЮЬЩ О▄Тф╪╞цб▄{-Sя╜∙╒┴пK5Y7uм'█"Cт░Х─FSъ#Э╡┌tQJ╡0╓x┬╛gFDоЙPMЦlЯ╒СDо┤a┌~ёiбжc^╙h├Лkж_ЕНAбуэг╓╩au▓Lп0Л╕ Ё!ю'F╔╘.║/А;Лє,aKУМUB!Дr&╠X─КX╧W╙╠JVо╬И╠кВ"╢zъg ▐бH* ╛мKШЇБа╨PС"6╢╢ЎE╠H№i¤b▌Zй,Vl>Йс╢{ cн╦ч╖╨9Й╟jd║Ш6─╢}:ДaоnE^├Sb│+rяхoo┐ESЧ╨ШX╫~iу0иKjIZrB╞їuFDj]Dьл+t"ГJEДъPNN YmЯЮ&VРqS3ЭЭ┴к║Э┌AыэЛ╪╬нN(Мп+"bЯ▀Б3Rр╬э▐@&ДB!│ф┬vЪО ╓K)k▌ЫФ*ЦнЛзя*`чK║X]ъШЕZNыRы╞(SuЫu6╤┴*VWW▒ФЬP┌╟ж╫Жи╢_~b√z╬╚мЧРкю╔}ё░_k!G3l<▌.^р6ц▐K!ДB╬Х╙Й╪─]$╤Bg▀ГзV┬═│ФG-╘╢фq▀╙╝Пoca▄3лЫ#цLОHgъ дJu╕╞j╢╩k╠6ЬХuмg╒╝▓ты2М▒ЖмЭ╞ъцЯ·в+Х┼z!ГL╞Св№Ш┴<ПЧЯN^╒Ж╪╢Ьм}Sc╟ЛZ#╡Ь╞ЦЎKwOa¤МыыXмХ┤╕&b?hў▒╢╟ЇЛЩ█;j╬оMгз╞┌ $ГЪ2uuэ}tZ),Э╪ь|╜шz.ЮуЎ╦■вO;OЯ╧Явq0K;їqх~Жю3!ДBЩ)з┤─ZЛhQ─У│ иxjЕчjF╤@▐Zu^eK╥ыyсЭ *ълZ]Ц|5я╡юпГюЩ>Qщ$\ч<╓$<░HЎ┘q▒╡мп╡G▒Н\i;$Є&й╦Й╢u:дY(Н-Ї]\Н°╓∙мжk└ЖZч$я^}FЧЯ.о qmWжh_ f#з(R╬.К40┤.%ф┌~Y╦э$JюдsM#Ищы°qЦ@a╗ОТИ¤ха▀Ь-щ╔А1¤"/l╖F═ГL!╗┤З5-Wч"чъш/Кь╧чnЛиЖ\ап=▌о■o╤s bё■# Е▐ё°┴р╬dю╧Щi╢ч▓oп !ДB╚LYx∙ЄхС=Пхы▀x _№┬Ы╕yєж Щ╝2╥N/║sЖи{пY┘hРT╔EsxrшedЮ┌wi·ZEє&Ц▄уєГ√шк─j┘│п√{я╜g╧N╬лп╛j■яєYqыk▀╟З_·Д╜R<<)яуЩ╜Є╣ГПaXлъ;;оГ╟Пюу▀╛u╦Ж^0║┬╡│МЎUy▀B!ДL╔iЯEї9ёхЧ?iCт╣▄"Vё╘L╔ЗG2─ЙXOЇОЛ¤═eQВ█ VП>;.зИЭО[.b¤#К- Хлгy╥=И !ДBоч)b/щ┬N!(`╔%аСwр,oвж[·╠@└Тє╞.жvtDK!Дr╬\~K╚▄РAe─VCК┐-QS,!ДB!зБ"ЦB!ДB╚еБ"ЦB!ДB╚еБ"ЦB!ДB╚еБ"ЦB╣╘шъч 4ф8┼6▀DzпQ╬#Я╖GЩ¤I ў!qцsX>ПmЩ-▒ДBfГюх╜РG├^Ю=║нUхйЯфб#ЯFz· цщГ┤gs{З6ИL═aнКv█^Ь╣7▀Фё∙▒╜<12>▀Хё∙>Й╟├╛>?╪╫∙аи╓Ж>tхsxS>ПOї╜D&Е"Цrбt╜'╪▌-г\.у╔╨З╛ў─я╗ш┌8B─b>╜АЕ9╥∙+°хщб,b╙┤яМ█x╪nб╒qэ╒iPAmы╫;┬^┼цY▀▌пx╒dG═ *ЕLїєЄX°жЦпЬ ╚`├эїBяH#?пУ╦╕~╟╓єщ╗ЁCsd√NOrC·▓2i╬Ц├Пe|~tуs┌.╝zя╦{э▌■╚їдэi{О╡{¤rm√Z─▌√╥╫╥цsє╫║пEH}=№9┘/ d Ўєa#i├l\єї\ ┼╡▓ФBf E,!фщ┬УOyчї╕gCОqoЕB┴?▌╟в &"d╓Кhg]│oнЫm_▒/OjК╪А+эє█╕Д╗6Ўt╪¤~╧dп▀ *о ╖^BJ■+╒х▄нHhИ╓ЩЩ°|╝}╘ZR╓·Iы/iH¤>+G2Йj█9╖З╫╔С√■оМыO╔╕■╝▄єO╔╕~ў║=╩°№ММ╧7╬/ъ╘M╟Юy╪Ч▒С]*!gCо&s╨╫╗╪√░ёYй╟Хєє0оХl┐п═чрЦ╗gAжRGоU├>UьLбИ%Д\ Л╕ Ё!ю'вещЭ┼Et╗]9l└\╨Ё▌X╦yдн╒-ьТ┌╚√a&<ziф√:9ў╙жС╖▀Ь▒щ╥iд╒┌Ъ╬[ллuW2B&З ╗Aqв░1┘Чgп.Г╟$о╡^п▌Rў╝Ю√┐tЛ█╖Б┌ц└bмGzВ┤╞К╥╛║═└*Ц(Р m╕▄┘ХоМЕzапЗ█ь╔¤ ╒sр^─Y╜ $Вg$G╬m¤▓ЦQE E'╚╖▀gСy╞▌w┼эаХ╩b%╘╟q░t╙ЮЖ╣!ї╙у╡ ъЯ┌?Ё3m╝+eXыD:№m,.їЦs▀В!¤m┼ol║wд j╔{G┌`,z╕"~,у·#╫п┘q¤ЪМыПd\K╤╛Щ1в╝Пe╝усЎ Э? їW╪┌ciш╧aKy\yq╓ы(╠╜╒ёY┼▓дs╘;бэ╪rEp╝QAсХq?═т>\│╛╛СAх3dn°/M№p°иГуЎJЎїYМы╠kЄ=b·┌Г√Qйф·рПОЖi√Z╥еZ8' E,!dоy■┤aK╪┘Щ7wтjЭUlлu╨-┼╡Ю╕╚T$╠X ]dQДи╘L┼╝6Е66¤┤Ъ┤║ч╟GжSZIl4ї╫]`╡щв|AкР╤°Ж╬?q`ТLЁхйu1e M+Ж#┴╡Vl#ыъыЫX_j√х+├qиїуT°/W!Сp╒bй╟vб'LгЁ█@jI вhIoог)uwыITЛ┴пщRЮS4о╢ZV=┘┘ОA_╫ПЩЧ╘▓н.║%[╧:ТРIso¤<▒aн▐z█ьFсЧ%ўN-ажoЇм┤cЄМ║яВщЫ(n,!ї)█o7R&h2oH▌>пЗГБ√▌+2^>k╟юdьыЕМ▌№°╚t╩G╥Ж╧H>Т6|F┌pS┌0ю!Їc;о?Рqнв╫ИхP║)█75Схй┼X╞╦ /j┘■м/╜Ў┘ёй¤ї lэС√■╢М╧2>%]¤ЖМ╧╖Cу╙Ўз■└0H\y~ЮX▓╓ы%KC┬c$┴╜╜)y┌2sф│└■А╦,ю√┌р¤@▐▀ЯZэ +Ўїў╡ФiД▒ГM∙╘┌▀Г3ъыЎс╪ZТS@KЩ[ (с╤гGЄwўЮ?┼┴▄|'дР]╖╓┴─ ▓".jБ∙╙k+m>┐ЕNД╬╚о°iЕf▀Э5.]мР4R┐k¤╪Д╠Ж!ы_b%kК5─┼I╡^-√[[√"%>Ї╠0=rlБ G<╠Щр╩CЦяjЫРВ2ыZЧ6╞>SЇ\tE`лe5СБ╬2Ц_Уgл╓ Ь╚мЮ▐el\Ю1ў=qwxNVИ4▀╨ ╘Т╓DхЁ▒OПўєX■Р№a{3>Ц1(a∙we ЎX╔■И╗п╔╪ \у╥▌3vуPKЙZBn YD╞╡ямЙ*╧XМe╝,IЬ▒n╦xyгoM3уєGь╜╒cs&|l╟чТЇед╦,┘ё╤ч=т╩3y╩Xz┼ЦўКМеС╓ышП7юJы╝ёu3Л√└╛Ц╫Ф▒&▀Zчьkє╥│ыki│║5▐┼jp▐ уYЇ59s(b !sК╬ЧэЖ,пЛX╝cOчuu6╤┴*VWW▒Фь}╜╟3m║@╕%фK╖┘D!св#"liЬjШ╥Э8╬·k╘ЗЭъn`uйcVp\°h4Fи╡:#\ъ.К*Ц{¤╡,Wg┴)ЄЬкoZ(>uр╝-G[╕■CЪ║▀╜m╟а<(.MjБШ6]┴Г▒>L~F╞ї ╫ЄР╗╘{И&гё]'}Ч╠ ╟Т▐┐зЄ╩я-лTё#╔хщwO2─l·┌,дїv ╔:юmр╡gЦу┌КэIЕёDшў0Р╝ы o2.ЧИї╘нРкД\)ц╗ъ╣╜шz°╬■D╚╫ює;XЬЫХЭZиm┘=тМї╬ ┬Є▌_│X/dР╔ИТl√╡у>╣жMч[Бл╪┤т╙+o╩|c┐;зt'6╓╒Ре╨█я╗ ╟┼iK<│ЮзХuмg╒Пw╚2jцЖЖц|*ЩuФд}╦щ2· ь├Ч╕+╜ bMяСчб▒еuЩа_мU╜╕&[╥iZsШ8═SnSй▐[dJ▌ж{]жп елщьy;fNТgj╡Х╗m╜╤O@ %╗а╔╤ч╒╩рf▄o╩|M╞р+2?ЮpьNЩ.Цrn╩╕╢Ь▐√v\╧█C╜йзМЧwe╝╓m=lt$7ь°ь╚xР╫7:v|Оk_\y&O╣╗ъBoюн╟ бW*pУ)IзюЪ╓э]╞G╧э╥ФсЯъ]╒Є.Д+▀╫ Ф▀I├y dlc¤Ж_▐Еp╞uяП&мчдЇ2+ўЁB┬╠Ў:;.n?╚р■▄И╪▓K{X╙1зssuиW░YXIчЪ▒╕lФРк.├Ўнr┌╝╢е_Є║└Oh╬k\║°╤Ь@a╗ДdM-Ы pjIФЖW┼=KHqvБв4╢аюbЦ╕8╕╪Zы╪г(_ёеэA▒&░▌Ю╧лэлгФмa┘фыў╧╓╪43и╕%ф┌╥ЗR▐r;▄/Н▐v8:M╖UЇ√╬ ▄░х╔#╚rPWg╦Z;5╧║Ї╡фi╙ўзТ<ї╡╦Ў■Ъ╢Ж?ў%нN└╒{jЄ\│b?.╧qd░^JIЦR╬d b1Л'щ4╓ГK2┐'їzG╞оnOё╢m██ЪєЧn·jйЯМыя╔╜С|Эя╔¤√ь ╞╡∙▐·Ad&h=эxQ╦╢9ВёЗ▄ў╧╩°№X·I╥,nЯМ%kuRWo]МF╧Гiв╦╙╥Ак}O 9&^9Ь}=Жp_ўыв╟Й?_т╨ЕїЩа4jб(rЦ,╝|∙Є╚Ю╟Єїo╝Е/~сM▄╝yЦKвy■╓ ║╟▌└2╥{X┌о√5ZWy▄\r#м╤щжч Є╘зГН▐Bq╠в qЬв+n}э√°ЁKЯ░W╙sы╓-{vQ╚√faKюД│лМyяїЙ>▀╚,╨Ё╓КU┤R%╕MЭ╟EжЧЎЩ$p`Ю~>Ььыs ю3вЧ╩╒╤<Ун█.з}╒ч─Ч_■д ЙчВ-▒Йш=·"Ў╙╦мц╨ън>9В│▐ЗOЩEЮq\ТЄ{U ╖БJFуЛ╞yк !WЮ└╢QF~S▀{б4╔╣У(T╠ъ╠G\ГМ[ эЄL▓э┌UйхШ`їl2'╛dj╪╫3&ю3┬j∙,╛оЎ╝9ЭИv]╨_Б┬оUЧя╣xъy▀°╡G▀╪¤Їь№ис9@Sя├зH╜Gэ]86╧тЎJh{h ┴qхEе3D┤┴╤Ў╙┤/A>Z's.}du╠╪Й┘qN1>Н;Hи?ПїўH№Су╜wиЩя▐6├╙ЕА8щ ЄТAe╥┼wо┌ЎрєвЙfЕ╓*2 l]E. ▐ЗєГ}Mо3▒"ОВ=·Ў <%f╜"╢zъg ▒√Ё═`я┬╪╜Е ЎРId║Ш6╠`_├@LЫ rї▐├ьЁ¤▒}0M]Bcbx▀╞)▄`&н╦h"╞зШ'ч7░OфXR{Зf░*ъ\WJU╠кнt┴$ДB!ф─\ЁЬ╪i▒+2Юh_Би}°м√ ў.М├╕tDб.нg╛Зф╕6D╡ЭЬ'Щu]mvOюЛЗ¤Z 9Ъa !ДB91з▒v╛╬╛.Є╤@Y°83Fьз┬╕gV7G╕НЮt>y═╕╜ ╟╘eШ╕╜'█ Єxyёщф5QmШh┬У╡ojьx9ё╛Н╙2э°М█'2└╠эх╢l√ROНEWзи╪ЎЎє▄GзХ┬W_ ДLМ|5tпt√∙B╚╡CЮ╠{`ш√Шr-9е%╓ZDЛ║╚╟&атйЮлE#fП>%▒Я^ШAўLЯиt╣▀╕╜ 'й╦1{%О▀ rtyёщт┌╫veКЎ┼`0К╪╙пK╘╛Н│`┌ёйл╧┘¤╩В>ээiСЛу√Y*гўїёчs╖КETCюцД\Y╠тjзєlЙ├|▐Яkc▀╫Ч∙╬L╦ч╨цЎm9=SМ╧│┌П№jН▌.╝№.v╦║┴ўМ9Фў└ж╝&^√ВrU╣р}bOЙ~M╝ ъгю╜f5бARеи=l/є╘╛K╙╫·у╠╕╜C¤}─j┘│п√e▌'╢ы=Aу;╧Ё№9poеАЗ├▌╥їppЁ╕╧Юу∙╜ф┐Oь|с╔{dm│КVK79 ¤pr┴Ь╔g┤╛пОOГ┐ uБ╖e я7~T4О┌?{T]rб}г╥Э 3у°~ъ┬Є┘°и~{▀\╚╜E#дr▌пg ╣ф6*пЭэg╬YрI╓:2v?ТЮь╒▀┘╛3тду╙|Ээ~ф╤cw╩▒$m·Н╡вямХ╩сз%¤O╬№╢wqР▐┴╙д|>W╬а░ ┌`·н═}| Щ7о╤>▒з$Q@╙]?лш*╝=ў▄■q%м2Oэ╗}э╗ЪЗQФ░}U╞╔йщ┬єчї╕gCш`wgюЛ█pVуё}Ў█q<ья┘НТ╚йлKоZ}]ОЩ┐ЗFюЯн▐$R~]W`Oбdъ4$А╬zЯo/Xhюд▀tТцБ╘я│r$Уи╢ф?░QsГМ]йSvщrМ▌s▀П№─c╔├яИ°√УмЛп╚wф▀╬╢ё[ke<╖▒│cўELЮЙАЭ░ ЩКюdp║E* !ЧЫ╦-b∙"!d▐iф8╦Ыищ╢=№х8Д<№<|И√ЙE{жЛГ╞Sр┴cZёЫюi├;ўО┤A J║Є╟6>╗o╚╪}e▄╪Нh▀─О╧ШёGф√VЄЛ▐л<Ъ╪▒ў╛їЎё╗н>mр╣S╪@RД▐√╢c$ц╟╫]ьJ╜╦щ·wa6OO┌[^ЁПAwb y]г▄└оЙ╫s&m╨]╞o├G╣║\~K╚▄РЙ▄;╘▀ И{jЮИоў∙▄╞Б<╔CР╗OD┌╬ нС{+С{:╟югУNЩbхXжїpРєБ}░Ч┌╥Цс╕Б=▓E°ы┤АС√YOВ]`mxС╡Hд╝Ш}йг·┌?∙■┘ёщ№║М▐л\И╣╖▒л▌▀XBъSvk▓оt/d▐Р║}^█Ўwmп╝"cт│v|■@╞з╝╞M╩°№└ЎMT:х#i├gд I>#m╕)m+b╟0e√"7>#юQ R═мЦъё¤╚c▀╖S;Цт▐╖n T_╙(уWе~┐cк╤┬ЯVш╡ёzswе│Ьцc╩3!nь╪=▓O╖'w5┤x▌Z╧┤├╕}й'╕Gg╞╕¤║cюmтоо/┴НЪoшj╤lЯ/·▒~=▐╧c∙{@ЄЗmЗ,mЧ░№╗╥ЎбЩ¤;>_Уё∙ЖЯqщn╬`|Оk▀IЙЯ1ў(й║т╚¤╚╧s,MВ▐mШ╝W■R  8=й█Ш╬ цм'░ШXФ╛vфъЬu!WКXB╚s[И}|Z\─тЙЮВ.uХ;є╜аg@ЬK_ qЦ┴Xлб СSь╔н V XlN├yў╡с√u╖:ГлгOD ┼з"·▀Цг-ЄьБыЛ@u¤}█╢¤i√д╬i╙═ёуSЩт═╙√╓Y┬_╓╫$ °ЫM№d┬┼╖R°KsёыWИ3iГЛN+┤Е!ф┌AK╣X║]¤▀вчЎBDыm<Гч┘kы^rьЎ1Цт▐╖Й№ХT▀╢oачхMyЗ%qgТqvЮЬEzЦs{M╣vP─B.Ovv░││/rx╢пч ▌Ъ└¤Х{x!a:╢╝утЎJўчF─О▐8nOчo┼ьг<~щ(t.Я║┘·Ц#▀э╢??ў╠Й┘;6.v?kЛИ├╤√/╟уў]╒юы▐|Ї╛╘╙ю╫▌ыыe{M{┬╓фиtZЧ╕¤║у░√]/K9У%И%ёЪm╗YаI┌╛$m Ю╘ыЯя╦°|█╢эmЙ═yНK7}╡д?╒R№╢╗╞r,cw╥╣┤├ 'Н#v|NwПт╟╥°╜╩GП]%j,┼С└OJ b═┴Wе>┐Ц─O╦Ш═╞N┌g▒штM■вN√rЯх│·╪O'eТ6╪∙╦е ┌ЭВ2\ю}b !=.ы>▒╙pё√─╩C╘╪=Бп ъ╕М╨^йdшк║k┼*Z)оp▐Гcяф\ъ>│{&╖АTоОцЩэ M9+╕O,!ДР∙%polФСW╔▄*E─МI*hк█%ь є▓р┘eт╥ЎЩЭWпо╟░Д\{(b !ДЬА└}YОх┌║ў1(╔`цг&яR╘ЯЎ!фк@K!'&╣'Ё╒G█,╞┬╜П╔┼1░=ЩЎ!фк@K!ДB!ф╥@K!ДB!ф╥@K!Д╠=║РV 9╬dwBNНg╞c├ь L!ч╦х▒Ю aI!ф`Ўд ягz╢x║яn░зghя═лG∙┤gs{З6ИЬЮ)╞зnuФ╢c.}К═Vп╠╪=Ф1╣)cє╠ЎЮ%ДР╔╕`ыбСOЯрЛрЫ╦[3{ Ъ#┐(O┌Ў8t╧╩УБ\Я@2╧t╜'╪▌-г\.уIx K╕Ж;^D╠gIш┴z!Э┐bУ·∙f█:Ї│╙,RstДz╬╛Ї╠P╤8кG╒%№┘Хю4ш▐Ш╦и&ы8jVP)d· i}Р╟┬7╡|)ўЫ ╚`├эїBяH# ■< ╗я╦╪ ъ∙ОЇ▌╟6JЙl▀╤╚├)╢См╗p]█зX┘m№╪Эr,╔є╞oдЁUЫ_ХЇ┐3izi█пjsдё╜В=╝/╧ гу╚dL6╡-╫╩ЄjB9.▄{╪nб╒qэU@─ЗwвАН\ЫСЯъS~ш╟2Л<}F╖=О│н╦ь ЩФоn9 чї╕gC╕є+Пу▒9рО№ўр>╫┬└s▒W6\┼`7█╛ТУ9+Ву4b"Zm{&ГКЦ_/!% ХLЭ*Г{фОLw ╝}╘ZR╓·IWФХ4д~ЯХ#ЩD╡э\╝цc╗▀У▒√Y╗ЯЧ▒√)╗я╬я╪mьщЮ╚иdH$ф░с3у─c╔├ямё'Y_С╧В┐-Я┐%Я╧ml$"|u╣К┐(у┘дл'ё'┼╡Р╬т╙Є∙bтJI┤Л[x▀╞d*uфZ5ь_╡B╚▄r┴"╓n\=j╣ўИя╠j-∙Н┤ЮїД2Л<у┌╟LъB╚E▒И√т~b╤^gqq╤*╓Ю▀qє╥sдс{?D╕6Є~Ш ╧З╩∙╛їN╬¤┤iО▒щ╥iд╙Ч╬#пяЛДлж)√DЭ╕ЫФ╧Й╞■<╓л╦р1ЙwH▀еRъЮ╫є╛5r n▀hЫM▌эС>Й`▒┬!8lh,ЄpюўХЦ5°`T_√с╦ивЕв╝&dm╒▓{юh]№╙▒щвъwo╖ГV*ЛХ╪;X║iO├▄Р·щёZїOэ°Щ6▐Х2мї3Н╞ЄйсRo9ў-дrн°НMўО┤сЙ{G┌а%▌АUu7dь~F╞ю  2ё├2v?5v#┌7▒у3f╝─∙╛Х№╥с{iюї°1;Цт▐╖▐>~╖Х├зэЇПЕ╟<╚Зk)U┼^/└gъ%т┴jlЮ1─}QF╡▌pК║tЎGўїt_╠ёщСyЎ╛`еn┌s.ї *╤╛xвыb\вЄP╫╗Е~yфЄТxИ╟Щ√"sХоh╪ч╕#вa.4мбЕZg█GG8rK@qнў>╚T|лш╤СЛ,КpВЩйШ╫ж╨╞цбЯVУVэZd:еХ─FSн└j╙Х╧┴╤М▐a╚нZGбu1e ═qNyяп█╚╦oыKmщ ╦pj¤8}/W!С}ЛъЎIЎШїрyЎ░!ёHyN╤╕сjYїd[>;√ЯЩQ}эЗK?л%╙Z╖ПОЖмн#ИOч╫о w7t╕Д>╖bюн╣ЯQ▄XBъSK"Ї╒:*║▓·БэЫиt╩G╥Ж╧H>Т6|F┌pS┌0N─с¤@┌·й╨╪Э▓}СМЯў(°╬╓сЛъ▓¤■ъ?√─╛oз v,┼╜oUМъke№к╘яwL51Г│Д┐╗F █sГЗ▀Ся╒п.8°╢┤Ё▀Л∙МhNЎю$ДР╙2cKl U}╚╙}P YPГ¤aW╓ё 6rиnКЯщ вм╞╫%В╪/╩ш╢Яо.-yьXGS█WOЖ·z╠├S,Qщ1y╞>дG╖o<гыТ(l ║0╫╗V╟▐$rPмбы┴}~╬|Шa-)d╫э№── ▓"╠╟a>{0Еыщ ▓ &V▓╥Ц╕8cйСw▓╝w╖╢Ўх¤/ёУ╜∙ ╒eОуkБzИ├;ФO!∙╝╘{$e╓╡.mЇЮ▒'╕GgЖйЛ~.∙ NdV?╖bюн▒мGqгАц·*▀Лo4Qy┼Ж|,В_ПўєX■Р№a█сK█%, о┤=Bhf─О╧╫d|╛a╟S\║ЫМ╧8>Ц▒+ўб┤╗у┌wRт╞g╠=°╬╬╒эўo=-wЮciЇFh├ф╜ЄЧ·o└h|Z┌Ў[О?яїWўБ┐(№е▐ Mр'ЫъNьт╙Є<єў╙╕(B╚МЩ▒И =@ <╠ЬєхR─╓д?v╞>@Ью┴j$▒rqЬж.Є@m L8K¤Є╞=<┼ёР>aЮ╟╥O┘╛СВ!ГU]P┬o┐Ж╓$V(rйш╬Х+ё╘┴┘DлX]]┼Rr▓w ╘щ,Юz985$ыбЗы8┤<ы┘>ОyЗ gМ╡к╤)юVЧ:fE╙I=[Ф\▌zМ╡ПуФ}=U,ў·Y╜jN└$юс╟Р╧┼з"·▀Цг-Я╪\_кыя█╢эпH█'╡pNЫnпVL┐0у▓{S┌■Ъ┤¤i√╟О╧)╙┼#cў╗Oeь>Р▒{C·ыу╙хGь°ЬЄ┼О%;;Rаз╓┌═у▀Ф#╟nПc)ю}ЫX┴_IUёm√z^▐<.F#▀cЮ7°Э|┐(яФze─╝W╔S·ьЯчйЇ~▄╢╫Д2cN'b'°АЮЮИЛq#нnО° >щД╝f▄Г╒Ш║   ╟┘╫eТ/цш/Q[ЦЮЖ╥з¤▓Яи}С_░uQМeVWн▐GзХ┬Т!фє▌Ц"*Э+eжВp9Rе#?j╕.йг▄pa╘eШp┐фJУ╢}┌║h║и╛"█О╩SюiP^*▄╫yЎ█mПБ√гyЖтF╢OыОг┴█:ж.КЇЛ╔єX█о▀¤юwз>Ж▀ч│:ЁхўП>№Ё├SПУ╫u░Е1я╜╨ч Щ nЁ∙wН?уО┴▒w┴╕Gв╤G'Bо%гЮ1'=Вч─сg╟иcA Са▒|¤oсЛ_x7oЮ╤:ўgБ■ ыt░1╔bKgН╬K1K"bЇЇє╡цВЄ ЫXr'tyЫ)У╘┼C9эаЦ╜*¤r▐{я={vr^}їUє7xЯ╧К[_√>>№╥'ь╒Ї▄║u╦Ю]єЇ■╕П w[Ы■ └'▐.МР╙в▀├ЫKpЫУ.H!dЦЬЎYTЯ_~∙У6$ЮK:'╓Т(ащ^Р√╩┤█SР3F]ФuNаГ"J╪f 2cмKнqлнбЭ,┴еА%АЩПЪ╝KK!╫Р╦-bХS/%L.3Н╝>Hoвж╥№5ЮЬT&]Шч╩бm~╕kвYс√О\ █SB╣V\~Kf─<=дG╫┼▀┐П╥ДB!Д\(b !ДB!Д\(b !ДР╣└Яу▀Рc╥]c9=Юs │!Д\(b !фЬЁtЯX│Яд3█╚їЬ1{uЖўJ=?оV6РO;p6ў░whГfАз{┴Ъ╜^хэ;ўL1╬╝r^╥°у#=ёf▓╟╣┤уьcщ│oN╪gЗ2ю6e№Эd▀]B╣@(b !J╫{В▌▌2╩х2Ю =<Щ8 ╫╕ryO║6цrbв9:B=gHх|H,дєcжg╙Я║}ТнCя Ыуыv2tЛ░eлвfХBж?╟ Г╝N-_╩сЩ └Ж$▌╗!Q·О╘ыcAт5щ│╧KЯ}╩Ь▒ї╝y8┼6ТuоыЮjх·ёуl╩1q▐}Y^ЩВМ╗ж╢▒ЕтZЩYB╚▄CK╣@║║х(Ь╫рЮ щус` nпv│K╕kc╧Ч *"z▄z )∙пdD╨╨^фн╢=9#╝}╘ZR╓·IV┌Х>{╟ЎЩИRжюз.к╧цП╞^╚mаТI СР├Ж╧М│DжRGоU├>U,!d╬бИ%Д\ Л╕ Ё!ю'эї0wфЁухъ╢№5ЧПз╓Ч╛╡.ЭY/╝r?ю$ЪиtН╝-'ПЖЬ√.ТiМїlье<&qнь╗bJ9y=W ╬И╕}аї ї╦BzлNc ┼VїJ▀Щ( ·├ч╝√SEПc╧A■i#пп[F-Ыo╪JW^:эўЛД√п ╣m║┤RYм─*-K7эйЄБЇ┘G╥goД·ь5щ│Ўтcй╦;R╬Д┌QщМO├е╜rЮ6ч╥g╟мЗCї<▒уl╩1с▀C ~╧ж├ў─▄│ёc7vLЇ╞┘рq№¤7|o╟Їu╠╜ї▐╜■Пlр1тюС─еZш╕ЎТBцКXB╚ЬТ└├╟п▀▒ю─Н.^№pЎХЙЁP^Sў╧ТqUt▌:Тш└5╧ж"nЭвq ╒╕z▓-╖!бIL║LGоZ█╪<\┼╢Z*K@uoLоЪ╬Z6├GsЬkе<╨п█╚║··&╓Ч┌ЄИnОCнзmXоB"m┐╚▒=~√+я░ дЦфёyє╙Я■Ц^uф╘Bk┌пG`еї╦├Жk┬▌ ║e╢Т╪hкХ Xm║(ЕДВi7ЦР·ФэЫ)дx?Р47c·ьmi√ i√gеэ7дэoO╪gQщ^С>√мэ│HЯйх7)}ЎБ═5вЮS3nЬEЇu Ruв║lЕc_Ь·ўPYсЬrОkьШў■Лъ│╪╛ЎяЦдэ╛$m╫О3╖kmщ│╧J9Яю3сўи}╪╣Д2ЧP─BцФ.√xq√VVV░r√Ў:Ї▄?Eай╒.СAе"о·lъ╩гзякqЩїмy√L8a║ьКoy3єЇ*'q?=CЦ┴─К╓┼'П╟Kr╤*оakk_─г─█╫M═eщOS^л╓|Ь╚м║eFКtyэ]Q(Q▄(а∙Ж■Р@сН&*п╪Ё8>╢m_Т╢▌Р╢/┘╢П│╞NШ.√#╢╧t~э╢╧жйgqу,жпСjцпцъV8ЖЎў(Чє╚ч╖╨╣hр ·ьX_Ы{$m┼╢¤i√G╥v╜GKЯ▌Ф>│╓°─ПД·L9ы{D!E,!d>ёЁЇ∙=╝■Ё╛!ЙЗ<└SМ/д╧─юМГ─YcнЖ·p▄╤рn`uйcV;\i4F─╡:╕№МU,ў·Y]LO└ █Я°aщ│ПоBЯН&~Ь)SЇ╡╛ЬMt░К╒╒U,%$▐┘3х√o<╥vу2мG┐э╞:j\tZ@Єnа· !d>бИ%Д\,▌о■o╤s{aц└╛ш╟u=╕╧э∙EУXA6елxК@є{G·мg)╡u╣a█▐С:~,mя╪╢єeчжЇ┘╟╥gппЩ4▌y;╬жьk▀m=ЛїBЩМ#c╞╧▒ ZшHБЮZk7П╦тСум╟И11н;цI3╘M▐.шео├╣G╞ЄXeяПB}6)=+╖╜&ДР9Е"ЦrБxx▓│ГЭЭ}<Улg√znW ^╝П╠Г█pwьЬ╪╖<╞├S< Э ╢ы(╔у▐▓#В═[╓*жл█ЦРk/ЫЁхvеб╒mЕ #Z№┤kЎA=:Э┘з╥)╩й ]╤|їX├ЦQ┤ч▌Я:▀RЄY╢ёж=ak▓фл╛кU?▀┴ЄъH╓$<░║M▓иХ!ГїRJ▓╘El╨X╘%T·ьЖЇ┘█RЦ▒╩IЯ}`√ь│╥ЎПе.oK█?Ц╢vи╧^│}&ё╬█╥#|в╙Щ¤e▀╢}вхЭtП┘сЕУ╞;╬жыkЬmЪ5`гДФ▐GУ╓▐Гв▐╙M@Аi ╬Щ=╬Фи11ё}нўH┌■=)+░╞╛c█~C·LоIє═с>Ы]мNчЧ╓═='ДРyfсх╦ЧGЎ<Цпу-|ё oтц═3Zvpф╢с:╚ЇЦм$Д╝ў▐{Ўьф╝·ълцoЁ>Я╖╛Ў}|°еO╪лщ╣uыЦ=#чВ║E.ї▐"Fdшj╝k┼*Zй▄ж╬]╝Bp ═1vЛлР╩╒╤Ь╒\{B╚Хч┤╧в·Ь°Є╦Я┤!ё\░%╓Уя╡Ї цЗbsykь№*╥╟XВ_к╧▄zs╥√G╣4nСН2ЄъZЩ[е°Ш1ЙBMu9╜j6 fq+rС╪╣ьъъLK╣$\╕;ёa╗Е╓▒ ╔╚П┌ў-Q└FоК═H╤СюTLЫч,ъrr╠КЫЄ┼dVj<1у█0·■B.7Є▐7nСr,╫╨╓нД°pKNБЩПЪ╝{5┼9!ДРsчВEм¤їo╘├QkЇ*{Щ╒Z┼klD║S1mЮ│и╦y█ЖШ√G╣─dP▒Л╨шВ9═╩╡ Тscж[BB╣vЬN─/╘аs^zЛ+4РЧ╕|9/пQwV=яЫЇВ ╔ї╗г·с║d╝]p├╝&┤иЖ]┼rx╟▒щ╠КШ6|╪║(їNq&▐o├╪<#ИM╫[r_о╡\s.}c3шЧ|xбК°■Мj├8в╩╫ЎБtЭ)DЎї,┌рчй√■∙yЎыc\йЄ╨y? ╜╛&ДB!Д\>flЙmб┌Y┼╢ыв^к! ъ└Жф!№Ё:rHбфЦАЁB 6rиn Ь°tъW6\юn┼╡ ╜─-WБм WъiОm▀ъ0╛.гЙMзKю╗%ЙicєP·Fыг}cUy╨/GG.▓║КтАтКъ╧ш6М#к╝qm╥wSЎ√║к╦ k=Уm┴aсЎm╨[[√"Ю$■Ь№ц▓+╥7Єw└═Jўз+чС╧oбs╠У7к?O╤Ж╪Єж└ЇuиЮыZ╧Ё>С3hГфТ╒<ї╘ю▀Y377Г╒\╦ьйШ}╣8 !ДB!ЧЪ _╪i:ь~n'┌╛Кх└M╒╕╔╪yЭюVЧ:╪█t$~╝╦ЁLPWcgмbuuK╔╛─Лg╩6L]▐,Ш═}╚мы>А{ТПЗ¤Z 9Ъa !ДB╣╘ЬN─&ю"Й:√║Ce▌Жс╠hб╢%╥#╪ц┴ЖW╤ъцИХsGд3їRе:\улG╙n└/пiшы8+ыX╫ ╬,ЗJ|]в9Y:│zc*ЛїBЩМ#╒h∙сц▀8$я1mp╘╠┘1к╦dхЭ░эvLиїS_▀╪к╔Uw╟ZU%яqў┴╠╡╡Y╛нгЮЧa]╙h,│║вї>:нЦ╕┐├▄╤їЮ`w╖Мr╣М'├ўVу$\у╩х]<9ш┌B!Дr]9е%╓ZDЛ║├&а┬г5<Пs фнUTзA╢$}xA═╖вУ-л╦■╬Z^cПAWQЯиtю╓СмIx`Нэ-°уbkY_kПb╣╥╢╕╩$u┼шtf▒!з(▓╦.Шъ+#╠u^кйу░бVDI?vБгqmЄоb9TЧёхE╡=ю■i_ЧРk√iЦ█IФ▄ёєИ'iГ╝Dtv ╟wЇI!╗┤З5нУ╬}╬╒╤_╙ЯC▌*Q┴╛2VLУєе OЖНє·▄│!=║╪▌Ж█+ПQ(Ёxх6^░R└├рyOP■╬"?║ПE╨┼┴ю║пЁя ╒[&ДB!Д╠з}╒ч─Ч_■д ЙчТ╬Й╡$ h║ыч/`]e╕чЪ▄?(`ч uQn[D █╝7ЧЛ┼E▄yю┬ ,п]юs{N!ДBо-Ч[─*У/aKоН╝gy╡d ns╥эz╚▄░xп▀Юю°sbw=р6юи╢%ДB!╫Ш╦/b╔5'ГJoСоA№╜lЫhV(`/+ЙЗП╠|X=2pёLd,E,!ДB╚їЖ"Цr▒t╗·┐E╧├+7╔╡чср╔.vЮ>╟╜ХЗ№AВB!фЪCK╣@<<┘┘┴╬╬>Ю╔╒│}=яп@ь=Сы¤я└ЕГХ╟бEЯ!ДB╚╡Е"ЦrБ$Ё╨║ ўПG╕o]Ж¤ыGя#A7bB!Д"P─B!ДB╣4P─B!ДB╣4P─B!ДB╣4P─B!ДB╣4P─B!ДB╣4P─B!ДB╣4P─B!ДB╣4P─B.Р.╝Г'╪-ЧQ╓cў ║6Jщр╔nD!ДB╣Ц\.ы5╨hxЎВrщщКИuБ╫P(Ё╪yБзНС╢&Нзxс<G!ДBо+,b=4Єiд╦У ╙Cl.oбaпо<^щЕ№їiяY└>╗\,&ЁЁ╤C$эхтmрy╫к]ює{x¤╛╣x u▄{ю┬гК%ДB╣╓\╕%Ў░▌Bлу┌лАЄщ<Оi█D╣*6#EoD║S1Лm├ЗнaRяtgт¤6М═3К ·е▄ыЧ┴Ў{с■┌╖БЦБ>╦∙MP╧и╢k╜В╫jШs)7H8Л>ЫA]f╤gQщ m7D╘sl┐\s║▐ью╕╕╜Є╓{╪ИVгYяу╤# яв√№ОB!ДРk╠М-▒-T;л╪v]╘K@5dA═TОpttДz╬X№Ё:rHбф·п9:каoлMа░СCusPX─зсшБ ╫Д╗@q-H/q╦U ы┬ХzЪc╗`▄╟╫eZZиi┐h~оtLq═;"А╓КmйКЦ╙─:jЄ╩>AЯ╣╚в╟*╝й█ЮйШЄShcє╨пПVзj~!ШEЯ∙yVУuУжЮlЛа│Bn┌║╠д╧в╙┼╡=оЮу╩╗╛x8╪▌┼╬>рмd|б° /&р▄yЖя╪%Й╗▀┴3▄жИ%ДB╣ц╠X─╩√zЙDЩХм\ЭЩUфZEl║b▐бHгV3╛╘HШЇ5ьф`I*╓!╣╡╡/вDтЗ╝╧Ю▓┌/zЪXA6%вV+уv╨Je▒b╦O ўЩ╫0▄|~ ЭI╜Rc█▐'+B_С(4}ўюYЇЩ╔34&╓╡}mЮж.│ш3%2]L█'ьkBл√\g╛>├╙¤ьь°╟╙gЛ╕ЯyА█юО┘bg╟╜НП┌ !ДB╚uх┬vЪО ╓K)kеЫФ*ЦўOу╓`чх║X]ъ`o╙╣07Oя0Feй╗н│ЙV▒║║КефАTCT█╟1O}6║.3щ│╪tу┌>m__S╘U╕рoп>JUт>▓сПЎ]Н !ДB╚╡хt"6qI┤╨┘ўрйхjє,┘[иm5$_═[Р(l W▌ЬзiС╬╘HХъpНзM╠Г▓╝жбпsрммc=л■═├╓┴°║cl┐╪№Ї╘█Gн$я&|+b╚rчэў]cНXKe▒^╚ УqдК~╠`]N┌Ўf╤g╢_К6╝▒еэKBЪOL]f╤gёщф5QmЯипGФgц╪ЦхZч╙ъ\jх№рrB!ДBИ╧)-▒╓"ZФzg╨·Vx■` фн╡Jз╢$╜Юў8╩авУeл╦Тпц╜6┬%3Г╒Ь/И·DеУp╖ОdM┬+Yo1[╦·Z{█╚Х╢C┬cТ║ 3о_R╚.эaMыбs(suШЪlыY│ЁO[ш╗╞╤оs3M¤╫АНRZз^;N▐vпЬ6х╖ф?Sц└}ЫEЯiЮ%ф┌~°r;ЙТы╧ Э║.3ш│°tqmПы3%в_D4╖ZуЪ▄n╡╨ёOD╦_B!ДB╚ /_╛<▓ч▒|¤oсЛ_x7o▐┤!sАWF┌щ`у"╚QЧSUрCдJ.ЪГж╖!T└ob╔Э└J╚ xя╜ўь┘╔yї╒W═▀р}>+n}э√°ЁKЯ░W╙sы╓-{F!ДBцБ╙>Лъsт╦/╥Ж─sIч─Z4▌їє░Кож█sэёЦB!ДB╚i╕▄"VЩ¤R┬ДB!ДBцД╦/b/T&YXЙB╚%EАkа!З?'▐3ч │(!ДBN E,!ДР3FD[>Zмя:╤@>э└┘▄├▐б RхzS┬trF!ДРйаИ%Д\ ]xO░[.гм╟юtmФ╨ї$n╫П{√р▀_ё▄?цyЛ"9∙K.d╞╖с░▌Bл3╧+l╧т>x(зЧQM╓q╘маR╚└w║I SРыцъ║к■Zx╒rB!ДЬКXB╚┼╤+:чї╟ zИ─в╜\╝ <яZ╗И√т~9 NЙДЕ tK▐Ўбп∙╛Х6ЭY┴tл,ЮGC╬╙Б╫К▀╪tщ4╥iЙKчС╫┐a7Qпl├№° ▄╧o╒`?фаl?:2▌╕zj╝йЛ ╫┤УY·"█зxjй<?о yЖ═Ьж б╢к%T^╙kCT█╟╫ЎiюГn▌╛ЧZя^ЮZgЙ+ў`иНnнT+▒k8XJщ~╨ЎТB!'Ж"Ц27t╗/А{"BэїYСй█`╣╚в'PN║UЦ[B mlоb[^#Чию∙ёСщФVM╡клMеЮ0буБ ╫дu7╨sїєУ4j5vГ╝Г}ог╙┼╫S╥щЮ╒YkЕ╓c╗0QF╖╧CyM▌bK6╧:Тш└Х╩─╖бЯз■И0@fU╥Ta╗Vк╜'W9мЪД1mП%оэ╙▐Зq┤Pы°ў@яЙd┌╝▐сфЦ▌рB!ДЬКXB╚|╨=@у)Ёр■)$ми,╧УгЬЗjЫф]ЫЧ╫0╓│|~ ЭЭС]ёч/& ═╛l\║╘{:Аw(RS┼Щ_vB┼█$югж;^O╡ьЙ┤1╡╡╡/BNтl│╟╒╛Ю[мB╡l'2иTDфMЪяH2Xa№@╨╪УФ[їЕу┤}╫ЎйєG ┘u;╫5▒ВlJDн═4q7i■B!d╢P─B.│А╙ОЛ█+Пp ▐├Г┤P\vр8rE╛╘]Нзюа╬&:X┼ъъ*ЦТвz&a┌tЖ*Цн╗йя╢:)╙д│єO▌ м.u╠ ╕Гn╗Ьк}╙СY/!U▌У║5рk╪░эsmЯЎ>ЬВVёЮ┬.:н╨,ДB91▒ДР ─├┴ю.vЎg%#╢Лn7╝<▒^█s ┼#ь┌ыЛутЩ╩b╜РA&уmQn■Нf┌tH▄Е┌уRе:\S┐>Г{C╖P█j°Vc=4hвtгРЇf Qя+ыX╧кo▐кfnшр╓.▒э│╞тЪ┬аОA={Мh├8L╛UьХўPMХ╨[ hm?╒}hб│пa Ф7ЗепMгз╞bд├.╙гшYИэ5!ДBN E,!фтQъ>.'╧Ёt;;■сoзусЙ╣▐ЧXyЕЙo l┴3 Й┬ЖHЖ"cН[6╘╕ ']╞╖╩i,8EС%vБЯ╨Ь╫╕tёВ-ГК╬!н╔k+р@Й╫ гЪЧZНЭ5ытЭ╬ЛйзZЎ╢ t`Е.mК5W5ърbBёэKа░]GIф╓rРп│▓0F╡б╤█ъH▌╣[E╡МЖ?J`%ЫB╡X¤╝т╗ф╞їYqmЯ■>мЧ┤О╢ и0nЕч зР]┌├Ъцзsnsu▀тo░iЧU№█а0║XЦж)н╦+BСк▒KIDAT !Д2- /_╛<▓ч▒|¤oсЛ_x7o▐┤!ДРyт╜ў▐│g'ч╒W_5Гў∙м╕ї╡яу├/}┬^M╧н[╖ь╣Єи█│*т!R%═ёж┌3FE·&Ц▄x+▒╬╔^б▐JХр6uб)х┤ГbKъ-в╖9я[B!Sp┌gQ}N|∙хO┌РxhЙ%Д2┐ш╩╠=wр■q■vrtЛзж╓╙Xт╧▌╒zS└B!зЖ"╓kа1╥яЛB!ДB╚╝q"╓C#Я▄~│╣№лi╬ДCl.o]P┘╫АСўvВ1A!sGХЙЬ"ДB╚,9Kьa╗ЕVxEСЩ╤@>ЭXБs,Й6rUlF&Ъ"╧▒╠"╧╦┼ш1┴~!ДB!Д─s"╓╬:пy@нЁО¤УСY═бUМ▒╞NСчXfСче!fL\ы~!ДB!ДМуT"╓l√а█ЧQ┬F╛┐ЭВЮЫm фv╒╒╙&.Н№╛ ┤ ─хї<фОjЎ;┤∙Жмv~Y║Щ╜▌В┬деkH>A:У6┤╒Bf%▌╗pH┼О═3в.ЖИЄ╞ц├@цCї╫╒;Г|┤\s.}зЩ╞┼Щh?┐с<═╜╪ОBW╫\шеЛk{▄╜(/Фhъ╛╙╛htХQ│╜║Ўы3╢эДB!ДР уl,▒nG╖╬C╟їа√ч┐g*■jМ║▀"H╓Кmd]Нob5С.Цс╕еv?NЕЗю▒╖сЪ|▌ а╕цЛ ┐м:rRСТIлG┼ю┼'щtЛЖм ╫╡╟v░jдТ@a#ЗъfX╕LРgD]т╩Л╧3Юа?ПО\du╟@UщъЭnIrlcєp█ZЯPUUgТО╬╙ь!┘к┘}│й░AL█уюнФ7<&&щыj▓n·▓ЮlЛ╨╡wL√тiб╓ё╙h╥#Оу█N!ДB╣HN%bwУ@√НC╤kY9Я╜нT+VE&V▓"B,qq▐бH~d"│:(6"q░$Щ┤DдlmэЛТ┤}ыcЄ*bkэг─╓eВЄж┴k╦a>┐ЕNД╫mv%c─rв╨<цо;2.2╧ Vs-mЗx√"Fsл╛иМk{▄¤ЫSЮИ█uй┐tdf]єlуpш╛╟╡}4)d5O=Mм ЫQk╙vr╞tс<┴n╣,уPО▌'8ш┌и╪8B!Дr]9Э%╓Y2х░▄]YТУ}tZ),9~tЮЪk#ИЛєйb┘║·юзУ`ч`║X]ъ`o╙ё▌OmмOые╘Д╝Аи║LR▐ Q╖YgмbuuK╔SK├▒yf╓KHUўд▐Ўk-фLСг█>■■]т█N╬МоUx¤qЕBПЭx┌8∙:&ОB!Д\[N'bwСl╒P│чи╒╨F╓Ы8cЭ YPНе╦?НН3х╔ЯTй╫╕Ыъ1╝▌A ╡-СЮч&L■6Ї▄Б│▓Оїм·▓╖ф7╥ъцр▄V├И'╛№Є'mHНt9F(zeдЄh╪╦єхЫ╦[TЎ0Л╛ЩчўЭР0▌4ЮюЗmнЛ╕ H▌Йуu╕╪┘ ╗B!ДРы╚╣Xb█-┤:о╜Ъ% ф╙yЬH7% ╪╚U▒ЩhК<╟2Л<чП╤ў¤z┤ЭЬМоў╗;.nп<┬hcл║;╕є▄ЕGK!Дrн9Ы@бyДгJ╞^╧ШV█ЮLNf5ЗV1╞;EЮcЩEЮsE╠}┐Єm'Уус`w;√А│Тлs_Gл╘очт9ncС┼ДB!╫ЪSЙXпЬ╞B^дЯq'MыZ#┐╨s!╒єЕ v+ї╩yIгqiфх6╠@\^╧CокRV>mє YЇ№▓ЦQE E╟╞З╙5$Я ЭI[юпrЪYG)U┼▐РКЫgD] хН═3OнШAщ╙|и 1uЙыы╪6LЩg╘}Я║?е/{п╒~5чRю╪NУ■Т╫Х{uэ╫╟М▌ЁР│▓Ф=>OrжИ`uЯ?ЧУgx║┐ГЭ xв7жысIo{Э2v▄█xЁ°!v"ДB╣цЬН%╓э)ауz8l╔╗■cfжrДгг#╘sц▓ПИХ╡bYWуЫXGMdНe8nй▌ПSQтБ ╫фыn┼5_И°e╒СУКФLZ=*ЁэАТn╣ ╔оkПэBшa8Б┬F╒═░иЩ ╧И║─ХЯg"▓╓D&K6╧:Тш@║\ЁыRM╓M\=┘Сh┼a\_ПkCT\lЮ╤ў}Т■┘ЖLGnIR╡▒y╕КmнO и ъ0Тj?Нц!Н0т8Q╪@оU├╛▀Xi╙>jнV╟▀rЦ,▐╟гВ┐ЕN°xиo]╣8■шaДл1!ДB╣NЬJ─&ю&БЎ!Зв╫▓r>)"z[й,VмКLмdEаXтт╝CС1*4№╚DfuPИDт`I2iЙА┘┌┌С$im■=L^ElMjЙЛн╦хЭ#▓D№нЛЦ╠Й *АЪпйЛ╞eL\f]√мНCн╦┤¤Чч┤─╡!DvEтхoв╨Ь╨E=ЕмцйзЙdS"jM#2X═╡P┤7▄█!Ю[Эр╟B!ДB╚Er:Kм│d─╦a╕╗▓$'√шИ╨Zr№ш(<5╫FчS┼▓u ї]S'┴╬╧t7░║╘┴▐жу╗ж┌XЯ ╓Kй н{QuЩд╝єсt¤9:n|ЮЧГ╠z йъЮ▄√╡r4├B!Д2ўЬN─&ю"┘кбf╧QлбН$м7q$╞r▓а+Шg╩У?йRоqE╒гЙ┬@y-╘╢DЦxЮШ0∙█╨s╬╩:╓│ъчz▄╩g\LлЫГєB #ЄМн╦$хНкg ╓КX\1диЛoU╘░╞ЦЎЩжю╧Ш╕╪<'"к?G╖a"╠|Z^Ў ╢,=5╓ь╛╗╗▀з║2їd?╛B!ДB.Ю3Ш█Bл╜дr KzЮ╥sеБ╝╡тщЇ╨VQнСvaЭD█:=╤,юУ╞╘u╘З *:┤╢ '░,╬#ё:│*ёО╘╚Y│B╦┼╓▓^█г╪Fо┤=$~ХASЯи<уъ2о╝и<уHа░]GI─ЁrРп│%%)ZЧrm?┐хv%╫╬5Э║?cтbєМ╣яЖи╢G╖┴,┬фut∙eОZ}I:в▌jс°NN)dЧЎ░жu╥9╛╣:·^╚■\шV▒Иj╚=ЪB!Д2┐,╝|∙Є╚Ю╟Єїo╝Е/~сM▄╝y╙ЖЬ║2э2PЯhёг3└+#эt░q^хЕ1mtшUR%═уКЫМEї&Ц▄ak}х┤ГZЎЄўё{я╜g╧N╬лп╛j■яєYqыk▀╟З_·Д╜ЪЮ[╖n┘3B!Д2ЬЎYTЯ_~∙У6$Ю│YЭ°м ▄Leф7E╘ЭчВ;ЙЪю·∙ XEWсэ╣юЎ ╪Yаулal%l│П !sН Щ╒Р├ўkё╠y├L]!ДBоs(buлы.╗\C[╖ФЩh┌3ф╘K УyзС╫ё╡ЙЪОпfx╗%r%Рз·ЇВ~ЪLОWЎ╙,╚СЦsвизBZ·у ЦеS/ЧБй}╠Фu∙╫c`К╚┘сЧ1j▐№e@╛╙ЄЩ╡╣З╜Cд╩їжД_┌vB!╙1З"6ГJ╧ ┘D│BБAжE╟╥hWb╧ZОпЛз яр v╦eФї╪}ВГоНR╝<9ЁфUЄ╩Г]Й?0чgОh4Э2Эм├lЙ╡]░сq╪┼─FM╤ЮЪYф╟╕Є╠bh-┤jЗ3╡Ў%V6PпЧРыOо┐Xцъ>ш ║GxG═ *╗eШ№Ы)╚uSўф╓E f#■ !ДРyd>▌Й !╫ГоИXНп?.аP(р▒єO}бъyOёL.эїмhь╔?9ШE┐╘c╥6t1▒│fy╞[ЮNп8rE<═°╟ЮDЩ╠ Цьх<07ўб╖Gx┤GRжRЯp╧tB!фj@K╣8x°ш!Vе..▐ЮwнИХ┐/А;ЛУIX/фЬ▀╖БЄpЯO√q Єў┤оЧН╝┐┌╡╩Оъ▓uГ ╗t6Є╞u╓wП═ФчiЬT┤ч>kўРЫg &н═/-х5┬iютkЎЙЦ╘м┘╧▀':Емнht]|Z╒6ТZ╔╖U]6uЙ'о?cРЫ╗\ХQРJI╗УhыyИиzО╗q}uoу┌0о<я░эЯL@{xєs)<)erlB!W КXB╚|╨=@у)Ёр╛}zяvё╖1С!V─лш┤▐ГbEБj╘к╩АUыНЩР┐9╤3к┴2_╪ъ╓┼в,╠╣улNи√ыКя№Ъ╝ло░■aТ5ЎDвдP┌оаР)а▓бЩ╖1а/D№┤еRKKлXп°щbє╦!Ў67▒YУL╡снО┐З┤йЛnПмє┐+иTЪ╥╛жuЫО//aоG╕°& ╨&╡j√╥╡Ўk*├Ц┬И║XRеmйЗ╘e╗dвП пa&щ╧°тO╙I{е╝эRoDXF╫s№}Иo▀и{╫Ж▒ўсn╥ЮMБ▄лJS╩ьWЮB╣P─B.Ьоў╗;.nп<┬}+Z╗ЮЛчxЖ}╗ш╙╬╙ч└єзh м№фуЕWlН`┘ZYї8╛єY╙B╤▒об╦ZZръйЛН╣pы%dУmЛ╦p┬.з╙╨╚├Y.К$J"Щ-x╞ ┼ТY5*√f╛жh╣ьКkуы╥C▌dэщxв·sJNR╧0▒щ╞▌█S┤aX(├EG:3yЧjХB╚їА"ЦrБx8╪▌┼╬>рмdD└v╤э·"uё■#│╪Sp<~p╕єЩ@хЖ0ЦWk]UD[їТ<╫л▄HХфQ▀ZZ'▒╢О%q╫ф[]N#Э╧#/ЗqC╬мкQ╫р╗ЁъQЗ┐.П╬O▌┬╓^Iэ╗ъЖмКQy╞╕Ы&│лXэЩM%OЭмiы╥*:f■жцЧ╧Ч√┬*▓╝`╥}йз╨>ЇпГ║Ш|[инй+q╢3cыbi╒╢дЬ┤r¤┤zгМ╤▒U─ЪйKfgЯ╪■М╞╖`ък╜i╙о╡b_2ПнgD┐─зЛ╣╖у┌w▀M┌*Ўт~щЁед\╧█аЗ]їx▄"ДB./_╛<Ъф°╗┐T2 !є╔w┐√▌йПсў∙м|∙¤г?№░№▐█GяёшЗО°_З^cП▀{√я¤т▀{√шўф|n)РзGG99O╔▀║Н;r%,╒ПЗЬKPПzN┬ф8)о$н"y·G.(P*#"ж. ┌/O┬├пI#u ╫$&╧Hъ¤▓R╣г║дў╧Г25>╒╦O╦ 9▓╝cїЇПT(a=g├*SЧбF▀З╔█wь▐ОiC▄}wK┌О¤еш¤Х╫ЗяMП║_зБ[D!Д╠ИQ╧ШУ┴sтЁ│c╘▒а ╚цX╛■Н╖Ё┼/╝ЙЫ7o┌B╚<ё▐{я┘│УєълпЪ┐┴√|V▄·┌ўёсЧ>aпжч╓н[ЎьЪ╨╚[╦х "\╨╝╬пQ┐xe╡&ытP%╕f╦#х┤50зru4uв3!ДrБЬЎYTЯ_~∙У6$КXBо▒W╧C├1+╥╔ sН5,√ЕBЩ(b !'Ж"ЦB!Д\ч)b╣░!ДB!ДРKEмз+prщFB!ДB╣ ЬГИї╨╚зСО[у▀+#}┌╜зцЫ╦[TЎьё╩i_B=ЄГнМЛЫ-МЙєfфЬ├zB!Дr═9Kьa╗Е╓йvжЯ▌зo№╛К$ ╪╚U▒ЩhК<╟2ЛA:У╢,ПRЦ╠:Jй*ЎЖЮD╞цQCDycєМ├S `РF·4jC\]ж%.╧И║Мk_▄ШИ.O╩╥▒с┤qў6ж qc0кЮSП ▌╧2xн╓╫ЬK╣▌°лL▐┴ьЦ╦(ы▒√]%xOlx/■@R╠ ╚=5FЕ╘Є╪peК╕╤:N/ЎдЬwd,Оz│a|yЗ╖╨·h╢ЮЙ╫Ъ8·№ъЯ▓╬▌З3╗ДB╚ys6ЦX╖дАОыс░ $я·Їe*Gг▌UхAн╪F╓╒°&╓QI`О[jўуЇЛ╙)о╔╫▌Кk╛`ё╦к#')Щ┤zTр█╨$ЭnИЯuс║Ў╪╓═т(lфP▌ ЙalЮuЙ+/>╧8<Ф╫D<%K6╧:Тш@║\ЁыRM╓M\=┘Без!о}╤u╫╛╚11╢ -T;л╪╓╕PэY╬г√:╢ qcPИкч$cbd2╣%I╒╞цб┤Cыгэ■хф║╤+Zцї╟ ╨q"уыБЬ╦їЎkg░сщGчь9[Ю|▐~F▐7oМ ╘╗r╠╒} ДB./з▒Й╗Iа}И╞бhИмЬOКИ▐V*Л√lЦX╔╩#Ы%.╬; Р├к▌┼>СYEоU├╛пмbp░$Щ┤Кk╪┌┌Б!iЗЯ M^ElMк)bы2Ay'┼█Gн%╢ы"╨$│D"ГJE─УцkъвqЧY╫>kуpl┐─╫╛╕║L╦╪6Дт┬c"опу┌7╬жe┬√Р]Сx∙kц$╧╜KїМYLрсгЗHXe║╕xx▐▒wEыJ╪▄Ш`ЗС1╣}ЎIтф╜tC{i╬х░Чъ║Ъ╟ZhЗ-lфСтL╝ cMу]╜╢ЮoёУ¤└хзїПЇ╗Г?ЁсуF┐.б°qх ф╢0ў,|Т┐╝&░Ї┼╓Eшt4╜╞зСЯ╘r╫Я1xяK_e¤С ┤D╒єD¤2▄╛И{kИh├╕Є·Ь╒╪%ДB╬Ч╙YbЭ%єр╪юо,╔╔>:"nЦ?: O═╡─┼∙T▒l\1ї╨/щI░s▌ м.u░╖щ°nЭ6╓'ГїRъДЦ▒и║LR▐e`Ъ╛>o╞їїш6Мgф"шv_ўB┬Mx■┤БFгБЭЭ9s'╛!ЯЯZТ╟y=· d┌╕HD╘╜]Ц\у"ы╩╟m▒'v$юйМьO∙Ц[s╝a=?▐Рў╞чнч└gї\ПIjЙ8ZТ6kqХ7D Эк╠ VE╪V?Ё┼[уnл}╤UC ┘▒cцЖ|6I_┤0fР─Ўg w╨║Щ┼КmkтGЖ<8bыCd║Ш{;mФє╗ДB╚ 8Г9▒-∙В╒_fх╦V╧SЎWZ¤Х╪Z└t╩bли▓СЬD█%╪Еq╥╪BшA .Ni*: ▓╢ '░оЕё╤xЭ─XХxGjфмYСтbkYпэQФп■╥ЎР°UфA*чЛР>Qy╞╒e\yQy╞! █uФф▒e9╚╫┘ТТнK ╣╢Я▀r;ЙТ;hm1]нТ#╩╫╛╕║(QэЛ┤a4q}╙Ж╪qWO%к}╤m0Ла9E}З°eЖWt:lг╒ъWшvлЕО"╜█я╤лN╫{В▌╖Wс~o╥kWД}╪╡xЛwьщ╡D▐гБKйq Рўг╬1¤ьVov░╫Сё:б╦p$ъ▐√Ў&:XС┤Кеs░║eЦJH}oOъ▌└▐ўА▄+Ў▌?E]Т?|ь├}Q¤НўГОi√,6▌╕{{Є6B!WБЕЧ/_┘єX╛■Н╖Ё┼/╝ЙЫ7╧y"Моъ║ ╘'Z№ш Ё╩H;lЬWyaL[П?ЖдJ.Ъ╟7!╝ў▐{Ўьф╝·ълцoЁ>Я╖╛Ў}|°еO╪+┼├┴юwЁЇ∙m╞CН0╫.Ь╟ПЁЩ√Ц ┬лИn█Єv ┘╧6QшY7╚ЛHi'ы╪~═ЯS▌╟Гзю│┴k h N╗НR/╜M√)I╠ї╧╡БоФь|/ ў3juєP~╟AёFюZ╢╜Fї7╓эПЦB/╧ёхщЬ══ЫЄ╣6░hХЯoчS9T┐╖d╦W-kKжняпЩПъ!╫Y?╜Дu5uЙы╧ЖюН╔█Цч─╓S▌/╙╫╤ўv\╞▀B!ф,9э│и>'╛№Є'mH<є)bўLw[Ы■jпч║ОФ▀_ИР╦┴е▒Vи>╖ЧўV "\} mc ЩН┐Г{2xx╖n]7+шbJ:ї#{}│dЕП/V·?еРKnгИ║8╨S]цG╤yТCyCЄ|'(K^  Л┌╙"ZAЩъ■·ю&КN°"hC┬qty├їЇI%√b╓ЕнБ░°║▄┼V(╧╘зJ╪X* ╣6е┌┘Яёu╘╝rIЭГ█┴Жi√Ш>╙ри~Щ░}~Щб{;о '╛яДB╚Ї|ЇQЁЕ4Ч\─╩ЧroЮR╣,╢+у,╣ю\NKьt\iK!Дr ╣ц"Ц2 з¤рP(b !ДB╚4ЬзИ=ГЕЭ!ДB!ДРєБ"ЦB!ДB╚еБ"ЦB!ДB╚еБ"ЦB!ДB╚еБ"ЦB9S▄╖см<╞у√╛$╕,Ъ└Q┼^Дi┘┐чEK╘ЇuemП╩╙█GнХBi=cОУй╘Сk╒░OK!ДL E,!фтXLрсгЗH,┌╦┼█└єо▒]4ЮуС╝ц╛╝h╤╛ювё╩└B^OА┤u╗ы∙ВДыv'┬лr^t·пi°╤Ж╬╛ЯзЖч'tEЎ╩yI│ i╥ТF╧є&╧F^├Це╝ЦФзчz°qуЁ╙·GZ*╥╫\ ф╒%╢Wж─ў╠ТqqcЁ╘┬Qжtx/n└ ъЧч╖Yуї▄П█Ў╪<Р^(7-H76O╖ГV*ЛХ╪▀Z,еZш╕Ў2аQ6mшХK!ДРH(b !sC╖√╕ЧЁ]0╗▄чwp=wу▌'!+э<аB$%┬S■кыpЄоЬйGG@=ч_с\Т4zоG╪nз6╛жД╣u╗EХTc1╢Vl#Yw%п&╓%З└╨ЫйIX]╩KIyzоGeа╝(№┤z╕╚вg@]╡Pымb[у▌Т(Є╡РМЛЛ┬CyM▌pKp]WО:Тш└5щDT:Eувлqїd[dXИ╖P╒Є4NКл╖L\|█¤<▒б}vwCлИц *rук╦:wU^╖\Eоюз╫ЯЮВ iuК#у'Х├Т№%ДBH<▒ДР∙а{cxv^╝ПGЕ ПWp√┘SМD│'бbUЇJу╚f¤░│"╗т MИШ}<k¤[╧╪┘Ч+┘╔╥НCЬZTє∙-tОi│▓ыv╛gb┘Ф╫Юl\\=7▄ЙД"$+"5яP║┌w╤╒╕╠║╢пН╛ ┼M┌vУgлAЯeV]|3¤э,г]rQЩDї Й╗I{6Й*═ ~Х!ДEм<ищ╢ДРЛгы=┴юОЛ█+Пp└e°╢ИУЮп1я°зОШЗроКN│Э.─Кvы▀─и н│ЙV▒║║КефЩ╚т9гКeу╕ЯнО1╨GуЪ▒Т╝K╡J!ДL╦9ИXOЮЗ╥ёєв<Э 4┘<н│чЫ╦╛√Щ пЬ╢~rp9vw▒│/║p%#╢Лn╫: Лh╜НgЁ<{m▌Лчb^мшПдСЪ=╫ХТ'╤%╡-i╜|,Ъ├ЖMГ▒╝ЖмИЮZf¤╙-)п!ey■aCг0┬Xн╗Е 2Qцm?╟~:ЫЯЮ+jXФ┼┼E`-╢┼5∙ъ╘3qI╔│hы▀╪к╔UЄ}=вэ&Oibй╫╕ы╤ь[AE─;┼$ъ╛9v─<╒И■TЛо╚с╜╕П┼Ю╪^Ш9║▄~ЗBЩДs▒─╩Pы╪*│а│їAЙ6rUlF&Ъ"╧▒╠"╧є#QhЪЗ╛с∙~ДЬмюєчrЄ Oўw░│у■N: ▄_╣Зn╢▀1ЦZ║7ш"╡j|]╥єФош~п r,W%╝шЯЗxк╘хЙs$БзZйV>├╢KI╘ьBCk{╛Х╕П?╟╒e)╦СcmlyЙ┬Ж╚м"єc╒░QBJ╙K#№д)dЧЎ░жё:╖4W╣▄╞┼EС@a╗ОТ╚╗eSG=╢мESъ/b2╫Ўы┐▄NвфN6п7║эЪg╔ЪДЫ6╩╤k[hмЇm╙╠П  ╚╫ЯмЧR■ыGї▒╬▒╒>)нп┐4╢▌▒р!ДBО▒ЁЄх╦#{╦╫┐ё╛°Е7qєцMrЖи%╓щ`c┬GвСДЕM,╣б_╘'A]чЦБ·╚Єз╠3ЦYфy■шJЭ╦иуh╥ cdж|Ї╤GЎlzВў∙м╕ї╡яу├/}┬^M╧н[╖ь9╞дЯзцsя╕mкфв√┴ў∙7uyЧ]-zнXE+UВ█,ИD╫¤c[╥^їM~fB╣ВЬЎYTЯ_~∙УЎ*ЮSYbН[й·Yw`▀ JЕMр:<░E├Р┘q`KИ}hИ╦ыyшWpуreє Y3╟n} Pщ╨Ў ¤_▌Е╠:Jйу.`cєМиЛ!в╝▒y╞q╞[Pт·%о}qLХ╬пчин9╠8 ╫K╬╩Т?=Щ 9ЩКuгQy╞╒e\yQy╞СШ┴qїЯз┐ G╒╓gТ~▒HеGo%╖5З?o╣U,вr7'ДР╦Е■x╪@C s╤3ч єГ"!ДB&a>╢╪Й├l╦╡¤═ Ё╬j╗Я)╕F[PGЇ╖эРоJьаЦ╜¤qr╕┼!єО|╬еuКE╣ь*╓ ║АИ╪ЄЎjUTu▌ГK╛ї!ДРы╦е┘bgfюйН2ЄЫ"ъ╬s]┼▒¤ypН╢а8╛х┬ь│И╢∙ДwЕш┬;xВ▌re=vЯраkг╝'~╪ЁёДЎк∙еяБуC[z═*(O░u╪й╤сtЕ∙:ОЪTМАU╚ф║й+∙ыФСIWs'ДBо/s(bх┴"po]обн[╩Ьў╛z╜%z╔<╨╚ыX╪DM╟Bo▀Er%шКИuБ╫P(Ё╪yБзНС╢Ц;░Є°1Ыую╚юs╠7)Фъv╦оR╒в3┐{:╖╞-$xЖЇVШП■>╦Tъ'X═ЭB╣╛╠бИ═а╥│B6╤мP┤\Ї╛Пvгєў╓хX╕Т,&ЁЁ╤C$эхтmрy╖/bЕ┼┼Esи┌}~╟щ╜Ўzб╬4╩х<╥╓╩i╓0Qykї╠г!ч~|┌ОЧN#ЭЦ░ty¤л∙МI╜<П^Щуpt09 │Ox█оj╫╚ЗЄ╩З,Оqm0╤1щт┌gС│iCVW??uыm┘їЇн╠Сn\=5▐╘┼Дk┌P]З╢ПНn{6j╜B!ДДЩOwbB╚╡д█}▄ёгЙЗxЬ╣_│к┼Ў9юИ8║Ц╓╨Bн│Кm¤Б╧╒╒я╓|qе╙фZўq▐<Їу5║╖]X+ЙНжZ°А╒жЛ╥$"iFS№дЇpСENа■╞┤!2Э┘>їъ)оIыnhЧ∙в╥╧O╥и╒╪ Є╓AИN_OIзkdнZПэ■oу╖ПыИ~B!ДМЖ"Ц2t╨xКwa5└║▄чwр\O3м%Еь║ЭGi╖▄к ∙ЭfW№°DбЙг`Fo┼°s─╡ыФєfu·▐VY^├XУє∙-t"4▌╚6─еЛjЯw(R3З╒М_v"│:ЩлюДщО╫Sни║ ╢╢ЎE─JЬm╢Т╕лЫПB!ф,аИ%Д\8]я vw\▄^yД√#tjўZ╗_=╖┘┴c2wтК┴╓]║mW▌ї╖╩╥<ЭMt░К╒╒U,%√Ыз┼2m:C╦╜·л√ЁдLУ.БBєGюVЧ:╪█╘mхB.╩Jлc╖EЛ┬E'╝?6!ДBFBK╣@<ьюbgpV2"`╗шv├3b║√┤P█▓{ЛЪEВf(vNхNv╤mКАї╙w┌T╓l+У╔8║Y┤n■Нf┌tH▄Е┌>Sе:▄^Жч▌█> V─╫аЙ╥НBWQ╫.'╧Ёt;;■1░ЛОwАз╫▐ХXI!╗┤З5╡ъЬ═\▌X8╜r┌\╖╘кЛЕчЛ╬Й┬Ж╚│"c▌\6JHUЧсд╦°VLт╥┼ ┘ *n╔Ъ╝6░кдСx]uJє2+тпYЧсшtё}эb+░@Vш╥vH№f░^JIqyИ╓=ОgчтЦ.hЛ7B!ф▒ЁЄх╦#{╦╫┐ё╛°Е7qєцMBЩ'N╗┴┤╝╧gЕnb¤сЧ>aпжч╓н[Ўь║алob╔Э─"HцЭ'╝VмвХ ╢ ╙¤c[@*WG3ШL!Д\2N√,к╧Й/┐№I{-▒ДB╚9б█5╒E╣╖ч╡ЭK+a░ДB╚dP─B!ДB╣4P─B╚▄УAeв┼Е!ДBо>▒ДB!ДB. ▒ДB!ДB. ▒^НС√B!чЙю5л▀Iv?`∙W╧ї;К▀RДBHЯs▒Є%ЬO#]О∙ Ў╩H/фq1╗bsyыВ╩>ч╪g║%D┌юЧ{чБС¤2┴$Д\b╬ы=о[е1к│Зl░пьT{їJ▐i╬цЎmРr(╫ЫQ.!Дr9Kьa╗ЕV╟╡W│DЄ'√вO░Слb32╤yОey╞qКЄy│i▓ю┬u]l_╥ХeFП┴є╛ф8]xO░[.гм╟юtmФ╨їBqх]< GТ9хb▐W¤OЪfЫЬz╬Ь▌+v╒dG═ *ЕLo√ЭLAоЫЪo ┼╡2-▓ДBИp"╓юБw^√▀╡┌Ўdr2л9┤К1╓╪)Є╦,ЄМc╩Є{U ╖БJ&БDB~╣ИГч}╚ ]▒в;^\@бP└cчЮ6D┌*ЎЯсЎ╩c?nх6Ю=m И\2зЬ√√ъТ┐╟╜}╘Z)Ф╓г┐'3Х:rнЎйb !ДР╙ЙXу>еnS╞Е╙wujф√.зz╕W ╗yї]T╙╚я█@╦@\^╧CюбRV>mє ¤┬юЧ╡М*Z(:6>Ьо!∙щL┌╨/┌ЩuФRUь й╪▒yF╘┼Q▐╪╚9E`├5∙║ш╣W∙e╒СУКФLZ=*Ё╫Цt╦UHж╞%╓█ЕРU1Б┬F╒═┴ЗХ▒yF╘%о╝°№Оi{<-╘:~═Ca·3Q╪┤░лLлc┼╒г█}▄ ,■"p┐|╟║7║x¤▒^w=~╞О╔╚ў\╤Яaqя╟qЯ7cї]2сч═4яё╤Я7¤╖°─┼yЗЄгў~d"│:б{Х■В-ПЄ╨╖╡╡/dТ╓ц▀├фU─╓$ZDЙн╦хЭФ╕~Q╝Ж▒╥фє[шЬ─{."▌└ГaоnшЪЮЫ]ёчnЩ∙`Б+_\]RK╥;#Иы╧╕╕q¤2 ж┐З╫▐ўчc?╠рЮтрbЙL╩╕╧░i>ЗcШЄ╗фtD┤aК╧░└√(Ю1}╓ъ ▐@юJЯLZ!Дr╡9ЭИM▄E▓UC═ЮгVCIМ√О5╓▓╨п╨╞ЄфЯ╞╞ЩЄфOкTЗk,ГгмГ-╘╢D xЮШ0▌{O╧8+ыX╧кyё╕e═╕uV7ч*Fф[ЧI╩U╧hт·┼╣TыЕ 2yъk√1Гy/o▓t'cъ<у·3&.v╝LD╘╜ї-г╓╪╥<╟Пыfю█иyЗ╢,=5.├бRc╤╤U▓╧ус}ЮЁp░╗ЛЭ}╤1+░]t╗╓╓jц└╛╨╡Я|║▄чЎ№Z3~N├╚ё*яЕи╧░й?Зc╨<з°.ЩМиў°ш6L■&Я х5З\ 5a╖ў╨Ш░<ГZfE╓╞■Ж┌│ц┌kB!ф:єЄх╦гIО┐√K%єwР·С<┌╔7єС+ ХR┴y(nшHХ№X╖Ф▓aйг\йtФBNR°─┼╣ТпЦф┘+╧R╧╔ыГ°╘С_▄p]4▀БT=ъ9Й╧їJєЩзYЧ ╩Л╩3Жш~ ╫CЄкkЬЬЗ√&к_╞д╒¤z╪c >&OйГЯ╖ЎП╓▌3╜фqў6&.╢_В╫ЗО` "яmй_^*╫ Пo╗┼╢y0Jы"∙Ч·хеЖ╙j h\╕▌'`°=;═╝╧guр╦я}°сЗ¤уў▐>·{┐°ЛG┐8t№├ ┌П ╜╖ a(■я¤├╖╧Д_?в╟OьШўЮS"╟k(Oє▐ Н╩й>Зу░хЩ|┐K╞~▐Дуь1┘{<к сpy¤└чт`yй\щи~мmCщ╟Цчу╖Sю╤▒№MлхНяHB!ф┬їьwТCЯGЕП:Ї∙B╦╫┐ё╛°Е7qєцMrNиk╫2P?╔т з┴+#эt░q^хЕ1m=юШ'.hЪ9╚егБ№┬&Ц▄aЛUх┤ГZv║√¤╤G┘│щ ▐ч│т╓╫╛П┐Ї {5=╖n▌▓g╫ЕI╞╧Ь├╧╖▒шJыk┼*Zй▄ж.дх&[╥O╣:ЪзЩL!Д╠Ш╙>Лъsт╦/╥^┼s6лЯ5БVгМ№ж<ЇЬч"7ЙЪю·┼,кглpЎ\═·Ёо::╓■├*J╪ц¤&W~╛Н%Qиай¤bм ёч&k?Q└B!=цP─ъV=Цkh'Kp╧√╦√╘K 29Н╝ОїM╘tмў^ !ДB!гШCЫAеў+}═ ъ╔U@╟їhWP;г│ы┼b╤Ю NцЩшёC!Дr▌ШOwbB╚Щ0,X)` !ДB╚eЗ"ЦР+N \)` !ДB╚UА"ЦР+JйT▓gГ6N╚№с/t╓Р├▀Ы╒3ч │o-!ДBE,!WЪa┴JK╬▌Цl!ПЖ╜ЇсЩO#]>йьl Яvрlюaя╨)Зr╜)с iЬ8KB!Д\9(b ╣т┬u>l▐┴ьЦ╦(ы▒√]еxб╕Є.Ю DТyц░▌Bлу┌лй∙!к{в.гЪмуиYAеРщm3У)╚uєї\ ┼╡2-▓ДB╚5З"ЦРk└▄Z`╗"bEч╝■╕АBбА╟╬ 2єaї╚└┼3С▒╫W─Ю7U,[╖`▀ ╪ч$╙╙ъ╚]О├Eз$яZM!ДРk╔┬╦Ч/Пьy,_ ╞[°т▐┤WДРл╚м▀ч╖╛Ў}|°еO╪+┼├┴юwЁЇ∙mnу$г~▌Ж1ew░1T!ДB.ЮП>·╚ЮMЗ>'╛№Є'эU<'▒ДРл╧╣КX]Бxч)Жз║b╒{R╞■│;╕s╧┴ыў√"ўzК╪M,ХТиui$ %B░)j╧СбYй▐s%ЮD─6D.ЗqА'eо-гdЭ*I>#8√eжР+e╤.┬╥вБ╒6 U ЙY]|j┘oГж/ЕДwO╗ТЧ ыбїqTX cB!Д╠s)b !ф┤▒SrmEь░еї в+пйPяЙf▌?╓Бjц@╕B!d■8O╦9▒ДBцЖDбВжо\lн╛тя9+a░ДBQ(b !ДB!Д\(b !dю╔аrtї]Й !ДB&Б"ЦB!ДB╚еБ"ЦB!ДB╚еБ"ЦB97╞Ё┌E╖√w╚╡╣Їр>┐З╫нUvё■ы╕ў▄Еw-Ul∙Е4╩х<╥ XР#]╢JзС7╫ y4ф▄ПO#▀░qщ4╥i KчС╫┐Ъ╧$"Iє2п╖G║▄Я│щХm^ЮяхзЦ─ЕuЧmбш╪xнЧM\ФИЄ┐L HчCu╝^I^√60 2O┐пєс┤tШ║ЇС▒╝.Ї█рv╨Je▒kbu░Фjбу┌KB!ДМД"v№лєпь!d<]ьЦEаэр;ЄЯщ╣w}cя╗х]uЁ\Гп)-╘:л╪>:┬С[Кk╛Ё╩T╠u ml·ё]▌│ъйХ─FS-|└j╙Ei"С$Bn╣ d]╕о=╢WЙsК└ЖЛ#-kCлтЛ╖LEъv$eImJоЮы1Б√nlв╦S№2їpСENаEдо█╥Нkb5щ┴?╧j▓n┌VO╢EtЗ┼v UэkН╙z╖BqгI6ни╞ЄЪ├кm╝w8╣u║}dB!ДРQP─Ю1 №_ s№▄ї√┐°Пl!$ЮE▄дю─Пё:\ьь╣ лж╜г▒ип-)d╫u! !▒ВмИ╤┌Р▀iv┼ПOЪ¤yЧй%8■┘ PЛаH9╩[[√"Ї$╧└ВшК╘Tqц$2лgъ{м у╩є╞BЭ╧oб╓ЙCЦ╧─JVz╨bЄЇ]{╥░╠║╞╡╤╫Об╕p║X2X═╡P▄Єхо╖/в9╖┌ЁЙ╗I{F!ДР╙B{ЖиА¤[┐▒Ж Їg~ г∙рч╛ё7l !d<ъZьрNр2,в╒h╓┼√xЇшю/v╤}~GГ╔╠Iа╨<┬С╗Б╒еЎ6 ║W▒l]x}ўсYQЮ║!;Ыш`лллXJЎхцI,ЯgEf╜ДTuO·╔├~нЕ\`Ж huow╤i╔╗┴/ДBEьё╧ Х/` ░ ╗w№╦pуG■■цп№M√Кyу╖ё ?·9№┌╪╦С№~√>З╧┼┐h╕,ї$ушz.Юу╢/Tpю<├w|╗lўр;x─]KZиm┘╜EНлъЙ37txЮмюeк√Ц:pV╓▒Ю═IШ╡T&юBmКйRоqуї▌uW"╢uї$=lшT─ФgДj*k╢к╔dйвя0lкй╘Р┼╓XF¤SЫзo5╒·5╢4.ЙI╗╙Q3u[─ъp√МЕ╝К═Є╛И╤Ц┬&p╡ Л№╝╝G╥│:█kB!ДМД"Ў╝№ЧтwЁ ░W}■┐ъB№ЫПё ╗ )╢▌ я¤ct■╒Г▀√o┐З э▀№e√*с~ Я√╤┼ПЎО╧сf.╝Dм~ю╞И╒h~ ┘wё]ўўэ╒№rЄzЮо_╚╤їЁдмєa¤c╟╜НП┌╣ЧЛ╕ЯyА█ю╬И╕ыH ┘е=мй5RчИцъ0▐╢║аР\╖TФщbJ╜ХД&─Uэ7╓╛ 9┤КETП-тФ┴z)%▌ТёkГ┬xvюoi}тzB!╫ю;!■ ║°_╛є?╟┬╤Я╞▀№╔┐Е√╖ШЁ■п ;ф■╤▀└ ъs°/ Ё┐@є W№╦ч ц[7▒ 7Ёч ьЯ7п3иИ¤┤Л┐єэ<~\п┐ВO┐∙ _∙Ўoтч~╠╝bи┼їWр+#*№║p▌█ЬЛ┘'v:о▀>▒"p6▒ф[▒s╞З } ┘;_┼ тс:ЄЯ╔бЇэ  ■я?°.■╒╤┐─▀~Т├▀∙w┐ИЇт7Ё■¤╖°╖■┼уO ╓M╘7Ю ╪0?ЎcЄ┐?Х╟W~т╗0Dcе ╣ў■Ў/рG?ўkЁ/Upй╒Ўм%w2 юo В╛ЎM№:╛Лп~:░■■Вф╓╟¤н╤y·i¤уШЫо╘эsЯы╟ўы9Ж╕tаЦ╤~▄ч~┴╞i?Ш0й╖ж7чRW█Ишz·}Ўk╜>ы╟O╥/ДР3FW ю╣ўПл#`=xНЖ/HQ┬vD╗Е Ъ┌v#`MИ? X√ВЦBЩК╪ °╧■лM,■ш №Ў?{ ▌¤3№═ ёАп№г ¤┐№Y№їч1■┼  ┐ чЯс╧■Л┐АЎЎС╪■▄¤9ЫzЁЄ┐┐]┴?■юO└1f┘q|┐ю~┐№эoунп┐■╒╩X╤їS┐ЇЗ°├?| ?ЛЯ└W╛нчz№~╩╞kЮ╧Р╟oJ°╖▀║7РзЯЎё╓╧┌А"▀№uрg╛НoK]╠ё╦?Зё╞╠╕tА_√yХў╛bу▐┬=╕°}╒Ь?їK°├oEZЁ ┐Є√╥~нл╢ ┐ж╤їT╛Лм}жэ╓D_¤yє#┴°~!ДРУ╤╚;pЦ7QKVB!фzS,э┘ Qс'Б"v■╫ ╙_╞с╖ _°? 7oу Є▀╫ёё╜Dюп °П■╞р¤й '■ш°З°╙ ц╧тO■П  Нї'°є&┬k°u╝∙щOу╙zии√╩/Oш╬*В+ S╞В√S?¤3ru№~цз¤┬ь╟Э є№q8Є┬яК мT~ ┐ √╞░<1щ■р╖МШ J^Dн▒R ~щЧDTх√3?-эЧ┐?Ўs┐Й?№еI$з┤O√LOьзё3?!вЎ╖┬╓ZB. TО-ЮDц ┐┌&Ъ XB!$`X░ЮЕАU(b'фн ┘6■B√/тw▌▀┼wш)║ ╓уў №!■ф╞ўёп%■~¤ПёП■NюO╟ XхgёЦ▒№й%╕ўу)└9т╟Ёs┐йЦ═┐Г╧8.▐∙ХOOшК;m:B!ДB╚e%оg%`К╪Ё■Ў?└_h▌╞ √¤ч°УЫ №│~Йє/╖·Ў■╓  vс╧┌WN┬O! ХЯ└п КЭ√∙c?О{°.▄▀RWу▀╞п¤╩пЫWЭ ▀┼?о№╢я┬мЗ ЭuГ╓<~?■╙yфFе°3▀ї7ЦШt╓J·╒ЯQ╘ё╘їTl╗ї╘X{З48╦~!ДB!ДФJ%{6(`├с╙B{B■┴~ ·[Є▌ПЁ>╝Бўх{°Н┐ё└с$╓ч╟~юярg┐√U№╝Yp╚К┌пклёп*Є$ю╙┴ FSєS°%Э0·ыo·.╠Я■yМўи╒EС№П╘у∙╗Rз■┬H┐П╩Ы╓ZПп>├╧NфЧю╟Ёs┐№╛"в╢чj¤щКдэ∙kЯ├П~·л"7э"L¤WOх'Ё3╬;°y}НфБЯ} }/фi·Е2{№Тr°;╤xц╝aЎн%ДB╚ebX░ЮЕАU╕┼╬Ф№▄ ~ ?t√ЗЁНзВ?uЇзm(ЩTрr ЭУ└-v╚╔qЩ_├ц╥Ўо0▄@>╜М*r╚eW▒^╚Ш-hх-ь╒ки╢R(]цmЖ!ДР+L▄;jЙ'`╣┼╬9Ёkc[lХЦrm9l╖╨ъ╕Ўъ┤ш~й"`Уu5+ил$Р)╚uєї\ ┼╡2-▓ДB╚%ум,░▒зрO▒√!╫╗┐щYэmъэгжЦ╓їш№2Х:rнЎйb !ДРk U╣в№~щщJLо ф╥(ЧєH/,`AОt┘*╣F▐\/,ф╤Рs?>Н|├╞е╙Hз%,ЭG^ j>уDа╔SЄ│Ч~∙ ~ЮzХўы0PПпl╦ё╦ в╜r ЪБ─зm4Я^z╖ГV*ЛХXWaKй╬╠°K!ДРK E,!Д\ ZиuV▒}tД#╖╫|БШйШы┌╪<Їу5║║gg+ЙНжZ0Б╒жЛ╥$"0│Кк▓@c╧╠S]╡FROTuяїп√И╪uК└Жkт▌ нцР√пИUйм╘┴├aH▐їUлз╥>╚СB!╫М/ьD╣║paзyE-бЫXъ-jдєG╘▓о┐иТZ7Э▓├ЛйEus nє.╢Ў░z┤ОCI╫┘8┬8/`╡Т.гn▄Е├чa4|s╔╓A1їш`уи Хбz╗~]ъ┘%жswKЫб║ЇъZ░saG1╘nB!Д╠ q ;M┬Iv:СИ%ДР╙аNДB!ДМВ"ЦB!ДB╚ХГsb !ДB!Д\(b !ДB!Д\(b !ДB!Д\(b !ДB!Д\(b !ДB!Д\(b !ДB!Д\(b !ДB!Д\(b !ДB!Д\(b !ДB!Д\А ?R\╡┐╕√#IENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/images/screenshots/activable_dataset.png0000644000175100001770000001110014654363416023271 0ustar00runnerdockerЙPNG  IHDR╩╡`╝sRGBо╬щgAMA▒П №a pHYs├├╟oиd╒IDATx^э▌}LФW╛Ё/Z╤╡v╒ты╡W^Ysoпx▌{б)E#-1Hb р■!aSиtеMLЬdJbВ╔&-ь■amSКi.╔╓МA╜Х(b+╗ё:▐оыВ8Sъ╢ъкыK-TчЮsц╠+ ЬБf╛єДч}╞a╛╧∙Э╟Щc╥рра D4&Ф╣sчъE" vч╬L╙єD4Е╚ГBdАA!2└аШTPЖЗЗq ■}z1lУ=■щТ┐зэ█╖уЎэ█hmm┼йSз╨╘╘д╖╞Цsч╬с?є_5,rЭ▄&ўЙ$га| ¤ўX╛x║Ъ ю╬П°т┐╞№╣?├й?їуу?■l╛┼#Qv=~,:-┌3╙]X№ЬC4[c]ХЄPowйЫr┌┐IпО[▒жW^yя╝єц═ЫЗТТ╝°тЛ1█вфццтOg╛OHф6╣O$▐їrЙ7╛{ю▐¤a╠y6YЇUТqчюC╝^ЦЛ%Лц╕7Щ>m?¤ЇH/╤TQ__П/┐№R&VЗ%Ъ!СМВ23y&ю>p╖ЛRfу█kwqЎ/▀ 3їy╡.Ф{Яб·Щ^2с@C~Є=Ч▄cUH╩ok=Л~еЪ╖N╙Wщc ╚ў+с ∙▐}╜чe_▀╢ ▀>IIUт╚Q°эУ▀pYпt∙\хco╞шBuЦg]ьСх╓[o╜ЕП?■X╡*▒╠?,╤ Й"яzНззз╟╒їEЗkЁ█╙о+W·]_9o║■╓{═5<<мж =W]ГГw\¤¤¤jyhh╚╒є╫ qЭ?{╘uїъU}Ц`G]Х▓йЄNХbНg╜Ьў№Н▄ЦчeЫЮ╟ч╒╗▄ЛХъ|yюНz┘ ▄~√┌ы]yчm^.V·╬чх>Wе~ВЎ·<ё8~╟xНq▐$J/╫{я╜зЧbЯшЛ╕f╠Ье&9 2#F-К┼bm╧LЇ8zNa└╤БG?╣0ы┘T,Y▓DЯe4■}Ф¤pwQ6a Q`│╕·тиgЭ&[uХЦWfт<v"S╬n*Бx3т╡W╒Т^╛И╦▐Ж├o▀╠Э°me║эjГПу▓8B_∙хуmэ@ЁNrЯ╝zь╥O0sчo┼у° ∙\cЫьЧ─zKтс_n∙Чa╤`∙ ЧM┌c╠ВcрТЯ╣ЗKn"√Ел╚^v┘ ╘Зье╜╚Z▄Нчf^┼Няж!yv▓Wн╥gИYц╚ьиP┘QЯз╫OКЧ/ъ┘*їcщ)Ь; QyоOFGG╥╥╥ЇRь юУ°Чa╤ЛagHNN╞║uыЁ┬?зт╩╡8¤Ч9°№┬є8wy1lОeшъ╔Dч_Wся╖│РЭУЛмХ┘*`с╡╝zУ╔f┼пo`яFW^▓ф╝у>эRk'а Я╤═Л:O%JВ3Р╣лE;░gм█SrЯоj№N?AG├_╦a№\¤яВЕЪ▓фmс╨╖ЇcCиО{4├bi┌┤iX▒bЄ_z Л-┬ПC. ▐Ешш'c~╩2ёD╫тЧ┐№╠Я?_1┐ЄFL∙ ╟Dg~3.╓я%╫&ьк┐И═Ю╬№ж]иG5▓ф╛█╗▒z┬Wщ<мю▐ю~╠мjм.яQ■┘ы!ЮЬў╣Нь|╦▒lvo▀О╫|еW╚ч║ %в╘Лх╬|mm-8аЧb╙┌╡kGД─├╣O$Mъл└_¤╡·╟╚ЬЬЬ)ЄQyе▐Г√ь╘]вёL·л└╦Ч/╟Ъ5k000а>=Lп&IЦY2,?№ЁГ^C8 ╤88 С!Е╚ГBdАA!2└а`PИ 0(D╘┐гшy" Б- СЕ╚ГBdАA!2└а`PИ 0(D" СЕ╚@▄|Дх─ЙzОh|6l╨sfт*(С╦ЙтУ№┐S┬ K/" СЕ╚ГBd rAщ█Звв}ш╙Л1╦┘И╥╥F─Ўxэk&ФЎjїЯbzзъv╜! O"XVджж·&kЗ▐Ж╦┘hEг<Щxn#ЯNмa=ЦНетя┼рG]╪Aщ█WДy█АГГГъgТ╙AA╪Q╔╪Бу╟w C/FЪ│▒йх@╙└АD\NM8.▐КaJл@KK"є_ы8q╥ЮЕїтd╟БвB╜ZP╧W<сf╜l─ym(┌p2БУrс┬=7╥X█┬vP>kъ╬╫cг^Ц6╓√Ц?лЎ╡4▐ЖF╢▐и┌кАе╒▐э·╕АcК░/мжG╝!█АЪ╬:°╜QXч[>iї╡4▐+╗l=╝-Р╒кАE\ё╜█їq╟Ф║[ЛQtи╟+@ms- ─╛х══(ўЬCHлh┴@g ■╟uю╨чt╩┐dqК,6┤%hRю▌╗ЗO>∙D\t┼Х'И\'╖╔}&+ьа┤Ю═Afиfрм╜пъЦц`9Ъ~'Г B░╞КЬГюїчы║▒-аTы├╛"╒Dy[и·НЄШ^ь╥╦Гч╖вїН0╩4yе╡YРк░╒┬^д[Ъж24 AAДааЦ&ў·╬Ъ^Ф╘F▓╠QMФ╖Ек+Ф╟╪ёж^ш,F██гЧAЕub╗xм2y╝DYMз>З▐!lюЛA▒hЮ Л╩`k;9ъу╞╗9sцажжэээaСєrЭ▄&ўЩмЁ√(ы,XбgGXWЗ▀xЪЦмУ?√шЎ[ЯёлнX╫фWкї}ЖV°'╔cDб┤═╙вИаЭ=█ЛпЇf#╣YH╫│#ф╓а╩єM╧r_┼Э¤шї[Я╢╛╣═~еЪ*s№ОУф1вXТ-ГjQD╨l6;·їц`bwd╔'╒oПkP╠╔▓oагэкЮO▒*уDRPfK▄Є+8,СЙvPr╬╢т│'qkKДы╝зEQS`╣7ж┤tXЮ╘GДл╙╙ви)░▄SЇMЕВ┌f╘И@Х7г╣\№┘Ы7&╦.Ыheчю█$n∙%∙З%╥!С┬╩oъы▌╧╨┌лЧddКpYё{╜CЯшфЬ-╒ўж╧°╢┬╖] :&|ЕикБxSъ~Ж╓a \а┬UЛ¤zїF,+Є╜щ╙╓Лы╖o╗tLHЕu"@M(+k?;QSж├5^▌▓П"╦.Ы╗МєTФuЙZ~yx┬щРHa%c╟qw?├█╤ЮЗmЁ{уП░їчы╨╜═╜яk╓√яЭБя√╢╦й║=Ё5Еy+YvОU?├SЙй~o№ Q'·╜ЄJпо■4╝С╙Pёоo╗ЬмБ╟и)╘нZ╧m.Q2┘│╓П╕Лжюzй╥═▌JМ┘╪МV&x∙х!щРH№Ї0%~zШ(J" С~ШR┬~Ш(ЪXz`PИ 0(D" СБ╕╣ы5oяU=G4╛┴▌KїЬЩ╕ Кл.[/ЕЦd╜vPXz`PИ 0(BRRТЮ#ГBdАA!2Еа┤г·iнъh@╛(зdI%з№З▐p U~ыУкОщїDcЛhP╘(Тє╢бI/?5Ўnм>ъВ╦%згX]╜*+╟▐їv╘_▄у^O4О░Г"Gt,Є █иFsЇНт(Ю<_ч╧ыi┌┤√7щyd!'O╧м╖г╗k5VfъEв1Д9: м┐VЎ╜╤КнчПcG┤О╟|К╫Ёк74ф╦╥ыJ\√с═ ╤┬/╜2vр}5\╤┤n}?╢C"√$Y"&v┬╫pdbчYzХрPR>K/22б>JFfОЮЛe▓у╛9Ў3╪9jy╡ %Х]ш╢ыEв1L (эиVcjDОїН0GЩB╘]пС!q4T∙╡ ╟pшГ<фdщEв1Lа3/S"╟▐И·Г9├лк╗^j@m+╓иїЖ'╠qфStЙ?╒Y╛[┴ЄqцJё№╜ы6GC╡6DБ°щaAG▐2ж─└OE Г"░5бё0(D°U`JH √U`вhbщEdАA!2└а`PИ 0(DтцоЧ├┴╧╦У╣╠╠Ё>фWA╔══╒KDб┘l6З■Kп!E╔O((ьг`PИ 0(D"С ╩н.|┤w/Ўъщго[z╤╘┴а▄─┬╥▌╪╜[NеXx·ШКaх░kбZСП▄БX╣[V║W)X░P╧┼Б░Г▓хїЧБ╙gp╖╨uил^ 5ЄRЇFП[╜[` ^O4EЕ_zефбфхh┘√!zVХМ ЙИ╨сELJЄD╗B&╘GI YWЙРь¤ FkeИж░ EДб( ю░л■ CBёiЭyЩТ-X)■l)]И╙╤С▌Т▄N╚[─°бHJ,№P$QЇ0(D°┼-J8  ┼-"S вhbЕ╚ГBdАA!2└а`PИ ЁЎ0%$АG4О╔E╥x°бHвшaPИ 0(D" КX┬ў5р╜ЗхДЙтCфВr∙ТqBП∙:^╛ё9GКд╕vP╠FК╝ЕЫ7bGcб8vP╞)Rм√HЦ^ЧР╜[О╘B┬/╜╞)2y┐ЦеW6.yZв80б>JшС"=V";чn2('&Ф╤GК╝╒u╪п╣МK▌ьгP№Ш@g~ЇС"SИоЛwФH╣ЗVе°┴ERbсЗ"ЙвЗA!2└/nQ┬сHСDЖ6(D╤─> СЕ╚ГBdАA!2└а`PИ ─╘эсy{пъ9 epўR=7║'Nш9 e├Ж z╬Г2┼Шeэ┌╡zЙВЭ;w.~В2░c╢·I>й√иЯ ╩фL4(ьг`PИ 0(DК=╬FФЦ6┬йcГ"8KСЪЪъЭJїп(°╓aU█нz9Qш┐╖wJ╕АAQ!)h+Fч└╘╘KmБ/,24х╜ищ@]б^Ч╘EдhЄ╛>h┬q$ZT<(╪_ ╘╝[Б4╜(D]g ╨v╥╫Т╚Р┤б╕│╛А'█─ы╙Y'^Я┬:ўr`K\ um╤нpг╒╫∙аX╜√ыїrя:}ОФ╪AqЎг7╖ыГ▀№iщ░╪ьшW mx;!C"8Oв═fAzИ┐wZEЛ╖Хh▓аv┐NДнЎ"╧·24 AЦпN4Цкж╔{L]бNБoz╬╤YМ╢╖cлoт┴>╩xl@qНx─ш/0ъr│РоgGЁo ╩ЫБ▐~ўkФ[Г*OФЮ5ТБ №╓KЄBЕfФ{╬QP Ыў[;(кхh├╔риЦF┐AdЛSQЗ╬т6$Z'6╘ы#y╦QOkPуD╕Dи|¤C9Цy▒"┴[ФBTЙюH`k!╦╤q)^я╫oСe╞╗ищ-┘╔Пk·ї)░t▐;мz┘пluК╬Мh|CK[Пb╘┬SЭ)*ИAыbT┬Ч^▓╬VнЕз∙w▀тA╦ИI*ZB▄ЛcъїйщїХGb*G ╙*Ёже╓√║╜m╖М╙вИ╫я▌ЇЦ√╬cэp▀8ё_Чc ~т┴EN№PddЁCСDQ─а`PИ ЁОS √(УW▀pд╨°Э∙╔ЫЄA!КUьг`PИ 0(D" СЕ╚ГBdАA!2└а`PИ 0(Dвq ╕йOўХnгIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/images/screenshots/all_features.png0000644000175100001770000010073414654363416022314 0ustar00runnerdockerЙPNG  IHDR╩╝ЎэЩЬsRGBо╬щgAMA▒П №a pHYs├├╟oиdБqIDATx^э¤ t╫aч∙ а'хЗ ┘Кх7% Ь╫уБxHмQА&с№ЧЁЇ■}2!╞ы%╧Дьx ┌И┤Н╧ТЖBь: NbРЇО %╗Ъ с5шs 2";rBNВРpмp:┬в┘VF▒Z▓н╚ТH№ыV▀ъоnT?╤ t7╛Ю"кn=╗║║ы╫╖oU╫┼b▒eHсх7╝с v└є╧?пыl?В2Аа  (╩@АUхW^yE?¤щOїтЛ/║¤╦╦▄i╡ба█├╜№Є╦zъйзЇь бО IWпI7▐░м:]╙+WпW]ЭЇ┌╫▄м█юнz√█▀бMЫ6┘9АъanЧwP~цЩgНFї·╫Jo╣эe▌·ЪЧЭР№▓Т_СЦ_╓Є╡ЧЭ░|M?}щz¤0Ў= ╙╫и╣∙▌zы[▀jЧTЗ╝ягьДi]╝xQ═Ыo╨эozГ^Z~Л■сзя╥╙╧o╤▀?▀ьt█ЇЇП ╣Ю∙iЛ~zэ=zуЫ▐в_╕ыG·█┐Н║єаЄ<∙фУ║ыо╗TWWчvў▌w_┼f7│}.\░C+ЩqfЪR╩+(/,,шэwl╥жЫЦuу═o╥Чo╘Ї_№Lo╫6▌╣хЯщПжв·█┤|▌нjl■E╜cє{ї╠soP├[ ┴}ВЭVЯ}R╝о}t╤О+С╙}╛хў9kА╟ф┤={Ўш╣чЮ╙ффд■ЇO Tуууvle9■╝■e√/ЖeSf╞ЩiJ)па№┬ /ш]w\яЎ ш∙Ящ╧ ъ{║э ╖шO ыwїх?·╢.<ё}]╜zM╫о]sз1n╕~Yw╝~Q╧?ЯэSIЫ" ╦юEА╦╦з┤-▄д║╛\q╓ьvх╠╘Лгj▀)ЭrЧэt !]╩║шl╦═sЭБV3/@∙№лїпЇ■├P}}╜vэ┌еў╛ў╜[г|ў▌wы┐Ю¤єa┘ ╔fЬЩжФЄ╝ы┼▓|у}?∙щ+z▌ko╥ыЭю∙┐д хгwы-o~]|dЪып{Eп╛z╒х▓C╟"j;~▓45┐ QЭk й╔кq┐Ўя░¤HЙDЇ╫¤╫n`оTщa╣Ь!┘╚+(▀|╙═·ёЛё┌т7┐щ5·■м┘я<н╞═ot╦2∙╔KпwBї-v(Н]┌▌v\'mR>▌чkЪс╓4Ы┌┘Э:оs 7yeA╙9vь╥▐saэ к╩5╡═▐ЇnУМрх╞e╖bnбF█╜▓:їЭ╬╢\А╩`Ъ[№╓o¤Ц╛№х/╗╡╩Х╠Ц╦ТН╝ВЄэ?ўs║tхe]}Эn╛╣N┐¤яI ╙п¤В▐√ wш╒W_╒└┐╣GЫn╛╤╜ЬvЫ`╘]╙Х▄ж═w6╪еn╟▒d│М╜╟jtqЗОЩ~п╔╞▒xё╩щ▄Rg┌э>╤dCл;й├ пM'┤█kЄqJ:8┌╕▄╕аu-├YщщЕ╖Э▓█▓мc;В╖аТШР№╗┐√╗ъээ╡%0Є ╩═══╬Ф7k■╩U╒╜ЄC╜№уK║юхE-■▌_8▌9▌°ъ╝~Ё╘╜ЇУ%-■эЯъ╩т7uї╒emzэf╜х-o▒K╔WЫB^{Й─┼xжV6ЛМ╙5j Y'а.Dtqз ╦ЛЧt╤лс5єь<оs╤Е°ф∙╩┤МжР┌Оя,¤EЙed┌%WzM▓╟▀▄┬▀ гЄ ╩& Ъ*эk┌д┼+/ъж~в-oyVя~╟Їю╖?еw┐mIя~ы╝ЪюИъї7 @ ¤G╫щж╫▄йw №╧█%фiqJ'╬m╙╓F╙я┐oAС╢°$+ф3]у~=тМ8ю╡щ╨▐фE~ж+кж7`╬z╬:¤ПhП╗╧hiк┴7┐∙M▌yчЭvиrе╖IЎ7├(GX╬єb>щжЫnRkkл▐ё╬═zъЗ7ъ[▀yЭ■ьЙ7ъ№е;taёэ:ў╖Нz№o~^ \У▐║[M[▀эЖ┼№Щц am;uLnlї_Мчh╙ ╙tзG}wЪX╘Ф3в═TU7n╒6П7Х(VОe4ю?л'Ш_╝D═2и|ц╢pЩoщ[2]╕W╓░Л┼Ц ї╩+п,?ё─╦333nч| YОFг╦Е-ы╘Є^s;НD╫╢Y░г\ ╦N╓МПk█╗╝╖-9■╘^[╛ўTЦщ╥ЦяNk-DЦ█╞е.7╒КqA╦8╡79м╜╬─e[.└z39е╖╖╫U&│Нч╧Я╖C+ЩqfЪR1╣6яЯ░Є╜я}╧¤1ТP(─OUаfф¤╓Щ╝ы]яRKKЛо\╣вW^y┼Ц╒oU5╩ЮЧ_~Y/╜ЇТn╜їV[TпU╫({╠Е~Дd╘ТТeа╓ФАe A@PФАe А√Г#╢АEН2Аа  (╩@В2Аа  (╩@В2Аа  (╩@А╥хе1uvОi╔e&м··z╖ ╧╪2?oеXW-┌√┼<╞·░ВПКUН█ \e/╠·╗╬G ╤О-╓М┬=Q ╧┼Л┼щ░┼хPк@╣┌хdЪ┐РхЦЄ▒д=зБV╥х╗■┤хwОхШ#█r[Ы╡┼ЎVмЇэпЖm+ФMРН═ л╡uXsж▀щж?·&;╢HKЛК*д╞;\N ЪЮ╨Zмк(ы╡}╛ч╙<┐╤Ю<├rN╬ЗаЦIu█A▒╪ДBєЧэ╕54:GIХй╞mоТ╢Q>иСLйMь╘╩╩DдЖ4лqїШiLM\╩╧▄pT=Й─т╪+ж35x│є2uЦKg&ъХж▄┘/k^i_Бg▄цфr▌┌s;yRГО&╖yzрrЖ╟l:т,╒(S│:нБЖЇ∙ъПЭP╒?╥Д]цD(■ШТ╥ЦajїЛ~°Ўc├└t|^w╣!чсШ║Є1O╫бо^√!(хy╠┤]yь│/;{фьЬ─·"Оc6кцгN╣┘'=¤Т╫o?dЯe юя|╢TГ╥e'ьє┌oiОЗK╖IЕ Iж35╟6|edцё-лa{╖Z╟зЬ╕Т&уt&ШE╡ш$Щ╦є!uэkV╘$e3}и╤Й1>Щ╢┘_ЮПlП│a@GMhлo╤dўQ'$╗sфц~eoЦпэИDЬGЦCйЮГt■╒Юq)║Кщ2L╫▒a▓k*>╬╪╒l╫╥M*э9J{▐RОЫ╓nm7√▌ь┐gdЯm;ГЎw6жж>f>(┘aP╤J┌Ї"Р&L═ЪФ▄.П└╖JNf╤№хMEЫ╡еa╗║гN`║<пPWkvжЫ╡╜╔Є8Лiе┌бИ╗Ь.M╣!═kBRДbЮ7x:√╧Д_у└vG>╙uDЬqъM╪╡?6КR-█ Jк╝A┘¤·~Hю7Ё∙JЫ╟4ЯШээZL▓Lgj52вhўv58 ╠р╘Ф╘Ь╧н:║╘;;й3БUжi╡╞┘чМ┬n+Б ЕЖ·S█зfлAuВg╪Э╪ц9 ╖╞k╔W╚U [╠s`╢╣eHбA█╢╓лeu╕√9▐ч_╨t&@√Ы\x<╫ve{\ц├П╥ц═ў╕)TQ√╧┤¤nН;mФиeоQО╖╤5wPp┐╢6▌КЛ╥╥е╬╙b┌щ▐/.╦tN╕1)н█ж638mNД╕ьЬхЪЎ╡-v{═WэvLКЖ ┌╢╖ю[БП╙ Ьї&%ЫHo╣╢f8e■А=b6zи┼./C│  2ь┼|+х∙8a╨\|ЯfD═sЎ6}fбф╕■∙P▓ж╪┐■3█ГзskЇ{ьrЭ╬╘:5<╦vх┌7╬▄ж░▐ЁLЦуaUЄ▄~9╖TГ║X,╢l√─№╩TЧb% ]ихoг\Еf|╖№КWТ6jФА╘(oKZв╣,@▐JФ═╒№9/╥█АL√f╙|#хЗ6╓Б╗¤ъ7(ЦыyZэ1Pы╟P%>>oЫja▀ч√jъ8│┐"Yь√Л┘^│2▐Н%ї;╦pБл√■╞▌dФG┴A9∙╞j║<яэ[ш╔i╡'│58zo■еy╙7w╟(`f░,ў■┼╙ЪЮш═~█╡╡VнбеZ╖╗Ъqмм░4╓пб╨DСЫ[@·ю{nю╚У╧/vбa`P╜j╒░]W╔н╥╝┐Хq√llЕ╫(√|aBъ╔ч$╨0ащi{O▐с¤\s5¤Dё╠╘╕TК{ kн▀CVgFGЖдсВ~B╘╟╜ЯyЄ╛чy¤▓d╤:ЙХ·╦x╗─О}N┐╖:Ф└ъЪ^╕?╬СZCЩ·5Ы¤:╠лU▒╟|wХX∙нсТ╞·═╧╟я╔ы╛▒Ъ∙╥ЧikNo╝ю4f\└№~)╦▓╡о)█Pлы═cГ√5Я3Mz│ o╪ы№ в#╔u·╦нЩpПЬыИ▀{7▒ Цщ▌Х├{lЙ}юМ ├└e-9єtj$ъ|╚Iл}╩Ў|еьПФ¤Цщk╬y1_зO┤╧╙ЯчGуўБ>Уї╪H█^│?ь╢&6╟v ЖNlП]^жr#h;эЄТ√"▀у╙╬ыХg║wЪ└ч#╜▄ x ╗НnЩ╗N▀>vkтq,7Ыв╢┼№х│╧¤█х}3bз ├фc╠┤]┴╥Цs6^tЬ.╫ю[ k!є·╙╓5Уч▒Тk√╪∙╘лq╨M[wрщ7?$b╞;▌D(G3▀Ў╕╦ы▒б1░<▀c#▀у3uy·;MрєСыq╗┐/3Б'фSюж_v>║Ш╫gЮ╧_Ъв╢%╙°М√Ы0ёэЧPcОУ╔J▐O@╧z┐╬g║w"шИШЯ╡Ю╒PЛйAvBqЦпеSNьNЧRйЬщ∙ЄяПl╧БЯпЖ╦▌╖fЪl√<]оc├Bw;тБ╖#Т·<мрпq3▀~8Пbё)з?и№ё"ПНL√&эqч-ш∙╚∙╕;╘╒ Йє╦є!uэkV╘$e│ ц╪╦ў∙KW╠╢dЯiЯю╗3N╦╢╧s╝╟x2-'╙qЦ╧k┴Ъ.╫6┘Ю : =╬єYw&юї ╬√В█2"╛╛╠Ьў╡┴Р&▌д╝дxN╬Їоc╣п_eКyl∙{┼0╧eжcК┤║6╩A!┘БH!5┘°╫щv╔ЗйEX3i╡чk!QгьuЙФ{Yє∙|ЧЯM!╧WЦч`эШ╢Оf▌]Ъr├Fо√)╒╛╔°|ф~▄NF╨№хMEЭc╘¤┘p'ф_ЮWи╦nIб█XЇ╢°Хl╗V)▀хоv¤y=▐u8╬═1╙:n┐Й╚┬∙РЪ< `╠]9С╬¤░P&╬ё>[Dеd│║6╩A|5G^нhQ╝Z╖"╙E▐╫z ╣═l▒TkС╢,w√Jpq[GW╝▒C▓й└Т╞┬л√z╤√0ы6-Y)▐о┘4п0M-╞є╗и2H>╧W╓ч Xb▀ц┌чЕ╘.9!%ь>╔&HШu[Cь[F╞╟р╡щ|зЎxхўxlф:>▌Є╘пйУ█e╛*h?o=AП;mVSоСE▌п┬M{MijJj6ЯьКx■\┼lKжч╚/├>Op┐т╧▓╜A█$╫r╥х╗▄ащ▓н+пў2Я|Юo╙╠└╓°@╩▒цF█ы,,є▒цgВяlптЯл▓═╙бо╨РZZЖТ┬\цI█/ю■J╝о▓м╟┘╔╢ЎО\├╖9К{└@щФ6(7 h╨╝Й║╡ їъЯW█cЦ╙ К▓slЛ█9┌_ж█╣б╨ ╔ё▀Чv▐tЭКi+╪b▀8Sцў┐ ;╙∙Ц╒b┌Цтчй▌█┼█╟{ЛЖ к6щ╨╛a│з╝пeЭ╟aЦщ╓Vyeёоsьqўфb╛ёэ╡П▌Э╫м█▌/╦r╫ яч+u┐╣]P0O<~█,╟▌╖YЎ╣ yz4~1_VN pЮd╗ ╢╣═╜^єЫ°ЄSГo{▄C┼√$░╝Аc#пу╙;&уeй√╓|╨║ЄkыL╧G╨уNЫ╒Э╞IG▐2═рx┤┘║<Я?┐b╖%h╝∙pТ╟>w;w╗тm{¤х▐Ex∙│FЦхд╦w╣з XЧyсЩщs+iЄy╛▌ й╜pЬzм╒П8Б╤Э╚╚pм■ч─н%ўj╖│╠уИW xб┌УiЮ╘¤Т·║╩╛Ю┬Шж │ 5ЦbYР─OXг6Щ'sб}PєааЄ╡d.аЪъ*Є■╖UиЎy 2w╞hЩ╠~e:╓▓='╣О╧аyЛ9жK∙:0╦i^y═ мRщЫ^╚╩|E▄ЫZмaриЖг=YЩпШcнТч dBrOT├G ╔JПe 5╩@В2Аа  (╩@В2аи█├=Ў╪c╢и|ў▀┐э╦_╤A∙Ю{ю▒C@х:■|QAЩж@jФ└ч╣чЮ╙~Ё]╗vM╦╦ё╖╟║ыo╥╒ku║fЖЭоNWuэ╒ЧЬ▐ф4o~є[Ї╬w╛Suuuю0аr[гLPЯh4к▄╩хя└7ы7~cПn╜їV[и4╜А╕zїкэ+╠Л/■┤шyХЙа >ж╔E1╝&А┌APА╚ФЯ№Т>Їб/щI;иeЁ╣сЖЇ┐ ┼;ї┘sow╗╧¤╒f}юBгSЎv╖;°Чo╫Ё\гєўЭЙ▓╧¤╒;u¤ї7┌%dС+0чи¤╙└ам╩рcЪ^\¤х┤|яMnўr█uz∙_\u╩nr╗Wщ&¤ь}WЭ┐7$╩^n╗┴╠_@6w~L_¤ъ╟tз,J)Ц╚ A|Кmk╝вm│йэ▌╝YЫMwяgu┴+K╘SC▐xз·цУ·╥'Эщ.|Vў:├·RЖzт─2жўпsєЗф.┬Э~HCЄн'╤_$ A|К ╩йє9!╪ ╟═уWtхК╙=■Рю╢cтLXэХ╝ёN7|▀Э·╪чЭщю~HП;├_¤Xо:уЇщ┐ымsA┐iЧwхёъыЯ┤б№┬╝Ъ>яФНTПЎ~RЄ· #═6 В2°Ш ╣■╧^U▌у/╗▌u▀zE7¤┼їN┘╦nw├Я┐мЫ ЄzчялЙ▓Ы╬9╙╫∙▐NЯ№оцЭ█wЯNўф7Їue_ │N=к^пF┘╘b_X╨w═╕╗?и_1╣√о&'X√·═8@FeЁ╣■·ыuїп╡№─з▌ю┌wшх┐┌чФ}┌э^¤Ўзї│┘}╬▀┴D┘╦▌А╜ю╖И│╡╦^-їХ+├*eАНЖа >┼▐GYZN■|їЭwй∙┬╫ї █ос╔o|=▐F┘sчпшГ·мО╡ЎjБєхMяо3├2E!(АOi~pф> П7ы│ў╞ЫA|rб9нЩC╝}ё|пm&с]Xwч╟ЇЫН7Я╚x1Я_╩Їwi°ё╘enц╓q░*u▒Xмря {ь1▌s╧=vj╟w╛є]xЄ╫ьP■■сo√╒╫7а█n╗═Ц*┼∙єчu ¤ў█б№Ф└чйзЮ╥╫╛vR/╛°вЦЧп╣5┼цB=╙Я№oba╞╜№Є╦n ы^wл>■ёПы╓[ouЗW╧▄>оWП┌бд╗ї╨у_U╬Ыb╩P&№■°╟?╓╒лWmI~╠EАп¤ыУэФг╪аLeЁ1A╫╘ Ы&ЕtfB2╘В2ашж@╡X│6╩@нгщАа  (╩@В2Аа рЎp5рсЗ╢}╚ф3Я∙Мэ╦A╣Ша<<x╨нA ЫZч░f╝e√7^я┬3ё%е╫T╫S]ДEНю ы▄╣░Ъъъ╘>║п%v·ы▄о]ж╚Эr┤▌Ц9Э[Л0/└VpPюъWП ▓ЙДk4hрш░Z[З5Лi·гoТЬ╨<▀ejЭ#NЬЎIФ;▌Dп╞GL ЮQ╕eHб [>ч,╦NОB4j #╡╡E┤░╝м│√╘╫╒ГN ▓щvы─';сyOxЫN┘ЄS█вZX1oг]&└╞TpPюИ╪0█5п∙M ╠>Nh▐ЧТР-∙Ццx ^ZT4╙Ї(▐т%]╘qэЇjОЫLН▒К╖jЫ[▐з╙╬d;О╙О°░КhzauD▄6╩╜уS╩Хєwy^│╢%fkИ▌e╖3бxЗО╣¤╗t╥ ╤ё└ АдВГrз┐┘н n╓;и┘yхqi▀J]ъЭЭ╘%ЧЖй56M═ё╣░F╥SЁти·▄6╚&0/(╥vQЧ╝&╔▐╝\┴A╣;┌У╝р╬▄уи╜ЫE├Аm√х╬G ╤Э6КLД4╘т-wИцb5ю╫Г{у═-┌GЫtl!вЛ;m╙ ╙Щ ўЬнpУ-k╥Й▌П╚mТЬ2/єАНн.Л-█■╩1V¤TЧbю5Р╦├?мссa;АtCCC·╠g>cЗЄS|хЫё▌2о╛GЪ $`ULPN▄M├э╥n)м▒К ╩@%й╠6╩(Иiг Аь mгLP╨Ї@PФАe A└]/j└я№╬я╪>dЄщO┌ЎхЗа\LP■▄ч>gЗРю╖√╖ ╩4╜ФАe A@P▐G╒▐>кE;XР╒╠ P┼ ╩KcъмпW╜э:╟ЦТхЭc▓Cй▓НC9н╛║:╒╣]Я3P╗ ╩сЦIu╧┼ЛЩnBб∙╦vL ЪЮPГD5Z╘h√Nщ╘▓ЦЧЭюФ┤УЪfP├КhzRc"ёv(щp■.iмH││Cj1╡╠║5╚caSы╓МWгl ╞╦у]x&╛дЇЪъzjаЛ▓8┌nk|M╫о╤DТНjд=и▄_K\з>SMlЪ[xeMaЭsз[PЇ▄^э┌сH;viя╣иS PЫ ╩]╜уъ1A6СpН Vkы░цb1MЇMТЪч╗Lнs─Й╙>ЙrзЫш╒°И ─3 ╖ )4a╦чЬe┘╔QШ╞¤gу5╛nня6ЕGlЙsz$^╛С┬{LmpZ-▒╙█сg'oє╩ЬЙ█тKp\╘еD└nRи═? P[ ╩f╗жт5┐)Б┘╟ ═√R▓х/▀╥─KЛКfЪЕё╫я<юd█Kёцm╗╒╒шNб╞о▌j3╡┴ЛS:бИ╜ZbcёТ.╢еХ╣vш╪┬nЭh▓╦оkR°▄6m╡╦и5E4╜░:"nх▐ё)eИ╩∙╗<пY█ЛU0!╣щДv/╒Ч@у~Э╡5╧ю▓█Bj▓гjM┴A╣╙_Гь╓7kЛ╘ь╝Є╕┤oеО.ї╬Nъ НТW╧Ws╝8u┬╢/NхЦя▌еН]┌н░╝╓о╞н┌vюДжlУКрe,jtOX┌▌%*Ф@н*8(wG{Т▄Щ;`╡w│h╨аm┐▄∙ш?║╙цпCСЙРЖZ╝хQ├\М╞¤zp[XM╢щ┼Юш╢dНЄ╣dy╙Й▌Z8f┌V4j #]▄щ5з0єэ╨1╙╢┘6▒HY╞щ>;]УNь^╨┘¤─dP╗ъb▒╪▓эп3a╒Ou)ц▐Q╣№╬я№О>ў╣╧┘!д√э▀■m}·╙Я╢C∙)╛НrЙ═°nW▀#MТ░О*&('южсvi╖Ф╓X┼eаТTfe─┤Q@vЕ╢Q&(hz (╩@В2Аа ро5рЁс├╢Щ8p└ЎхЗа\LP>tшР@║xараL╙ A@PФА╡ФЧ╞╘┘9ж%;флра▄Y_п·D╓М-jIс5╩н├ЪЛ┼3▌Д╘CН-j╨ъЪ^ttйwv^ЧЭ▐е▒N_Msз╞lzN)╖бzEЩ┐ЙДщO╘T/iм╙Ў╗хvo╝Эo,ЬVцM╫2дYS╢╤,Ок╜оNuжы;m -╞>lHл ╩Kc#om╓з┐a`:^╦ь╓4З4t─ЙнNhэ i┬ЦOДЬPT╓╨иР╕╧L*╘+M╣йў▓цeЦ?г░zCёyцЖгъ ╗H│CЪя2хuдMЫVk|к dQг{┬:gЗt|з┌Gэу 6к┬Г▓L[lНm╦d╖цж╘`╩¤5╣=уRtQK&k\=╢╢╖#тД┘а2ч_WoTЛKN4Юйk_│в&)/-*jTГ∙█:м}fEN(▀▐н╓ёйx ▓п▄Э▐?╝!-(ЪH|qчв ╢Пq~∙Н╒ъ┌(√Cr╦д║ч╥kr;qзэ╥ФвM8*У╢4KєЧg4m╓ЦЖэъО:A°Є╝B]:їбIб6█k╡ЕЪlу№Є6к╒╡QЎkэ╓v75╟ЫO╕mГЭv+Ыp<зс╓и(sM-▒FFэ▐юДяЩ┴й)й┘m╫aЪf ╔┤ц0▄хўv9KHуN7й3ncg▀vl(Н┌ HDЙ▄╖ўФ╬юo┤М+|╪иъb▒╪▓э╧Л╣xюиWУь3оЧiqa┤ЎЎJ╤fg║FйяС-VыЁЬж.+╝в╠,m╞)Qє▄┤тГa╒П4з6эH\Ь╫л ╙&┘Фї+u{╠|vCТ█▒r{k╔с├Зuш╨!;Аt<ЁА8`ЗЄSpPFх!(dWLP.]╙ аЖФАe mФkАiг Аь╕Ш(Ъ^╩@В2Аа  (╕ыE рЎp╣q{╕ ╚хCЗ┘!А┌є№є╧█╛тШ╝ThPжщАа  (╩@В2Аа\ЛG╒▐>кEяп-T╚┤Ю|з-f┘Ю|ч9▌з║║:╖{Kи└uф+}┼<Ю|e{<е^o!П╦7m▀i[$█2J╡¤х~>▓=Е*ў╢ц#█у)┼s █╛╥"(гКЭV▀╬ЛК,,ky!в-╖┘тТZЛux*u]╛iЧЧulЗ-^х▐Gе\■Z>ЯЩ╗ ХЇЬ@v^H.GX&(гz-^╥Em╙╓F;\k▒Oеоk-╖+ЧroK)Ч_ √н╪mидч▓H╟е╦хН┬¤╡]гц;Vєuл¤J╡о)мsё)Т№у╜y2I,wQгэ▐<ёояq;НЯ;r╠╕=з╒чn√qэ4хy\/╣;Є]╟2_;ўй╧ЦўЭNN к9╦:оBlWIЧяSю¤▓▐╟О3O^пБbФы9А2╦КKЦ ╩@╙■│:еЭj7 ╖j█╣Ъ▓▀С/NЭPJew|X#yЬ3SЦыX▌гЁ╢SпРOЯ▐/у╝lПQ╘:К┤ЦыЄ+t┐*█у*Dжэ*╒Є╙Хs┐мы▒SрkахzNа┌Ф7И╟Ni[╕╔96щ╪йm 7┼┐╞▌▌ЦЎ5m╝}т┼ЭЎk^╙e╣Ш'╣▄ дС░╣Пя┤єї)ш╝ЮЬ▐┐─╙Yц-l{М┬╫Q╝╡\Ч_б√╜P┴Пл┘ў┴ъЧм<√e╜ПЭ┬_┼(╫s╒м.Л-█~Tй├Зы╨бCvаЎ< №є╢п8&/8p└хЗe AяЄх╦Ew┼"(╩╡╠¤ен°┼?▄∙ а0хЪuZ}ц тў]-╒mн6 Вrнr═kЫ╢о·З6&ВrM:н>є3╖▐╧▐ЦсЮл╡Оа\Ут?P╨ц¤ьэ┘¤вbа0e A@PФАхZ╒╕_g╣Иаhe APpPопп_╤uО-┘▒XSgчШКШ(╗ВГr,SlnXнн├Ъ3¤N7=╨`╟втЭюSЭ∙╡>зы;m╦░BщЪ^ШтD-sзтХ╠3 √kЬ▌i╠╕%НїivvH-┼╓Hгз╒╖єв" ╦Z^^╓▒╢+Ф((;Б╕e^Г╢Ж96╫н╔~╙мвCС╣aiшИ3Е ╟УъЮЫ╓@CГО&kейС^#ЛЧtQ█┤Х[aфTЪа╝┤ии╞╒у╒(╖Ш┌тy]6уtt8ъМk╤dўQ'$╗sам5┌o^СlfqZ}MaЭ╙qэ4eэъ┴╙╪┘P┬ж╛6╦ё.в;кб1d√░6╡ l╝y┼Єй╜R[DГ;vш╪BDmN В)?{PWLcg@ЙВrCгB│C:2cЗS╠(▄#M─&ъ╖mЧ-п╓х▒8кv╙&∙С,?<Т╧4PЙjФуmСг=▐┼|Nч▐·═\╠gR▓й]vжЩiи%ьФ:4╪oо┴┼|х░и╤=am;uV√3зф<ж╪Шъb▒╪▓эGХ:|°░:dЗтG█╒}P╦■[[Ш┌у=╥#Ўзнзи@sss╢пp[╢lqє╥БlI~J╫Fф┤F┬чду;эЕz}NI║|ж╪╕╩5iЗОЩЛЇ▌1з─╤╕_gmmr╞iр"(╩@В2Аа╝Эю│Ё╒щ-бQ-┌тв∙Ц╟п√АZAP▐pNл╧№└╚┬▓Ц"┌rЫ-.Ъoy╦╦тNsаVФ7Ъ┼K║иm┌Zк)їЄ*A╣VЩ▒═!ъъ┌5ъ╢п8н╛ж░╬щ╕vЪЄП<оЧ▄Йnє 3▌вF█╜∙тЭ█Ь"Ях╡Ча@Е (╫$`гz╨╗GЄ┬nЭ╪cBь[Иин-вS■GўjУЩ▄ДрЭ╥йeєS╓Н┌╓╬wjпфL;╕#╧х%ю╤ P¤╩╡╚makyMgj}╧E╡`Gз8VУВ╙~p─ ╧y─ ┐Е,а ╠╧P█Ла\л╝Z▐DЧсЧўЬщNE.jg╩э*5║'мmзL │-╩wy5Ва\Л╖j█╣░FЄ╝U[╙■│:еЭjП7<╓тшЕ╖ЭJ▐┴в└х╘ВrMК╖╛╕╙6Х0]О эv;еmс&', 'НД╧I╟w┌y√t║ИхT╗║X,╢l√Qе>мCЗ┘!А┌є№є╧█╛тШ╝tр└;ФjФАe A@P@╒ З├╢/UжЄbФPХ╥Cq)C▓AP@╒Є┬qйC▓AP@╒ЙD"╢/5$√╦WЛа АкФКKТ В2кЦОKТ В2кZ9B▓APФАe APЛ┼Цm?к╘с├Зm29pрАэ╦A@╙ A@PФАe A@PTGP^SgчШЦь иbЬ╫Q%╩ФЧ4╓Yпz^lL&╫;Y е k╞О. !kи|AyщМ&╒ы№Ы╘Оf6ж╓a═┼bК%║И:ь(а╥Х-(/ЭЩФ║ўй+4л╔ Iyiм3∙ ╙|:L ФШ2<п#жЖ┌Э╛ScAЛL фъ[цX╪Ф┘O▒)╙∙╩В╓э■ +ЬXw╜┬л·( В╬╔n▒/#$╬√Kы╥ььРZЬЄ╬┤08П=Я'r@·pж∙╠║ЬsЄЬ?гpж№БЪUч|║[╢¤yyЎ┘gm_░█o┐▌∙▀\¤╥╤i \л~дYs╙jИOg╘Цy ┌OЦ3с░┤пY#юlvZ3Н>*ї╖Lк{╬YЮ3┬╨-У▌i╦4pП4S─,0e▐!Е╝Є┤щ▄e═*╢o1>}Оu;к°ь╔O─╣Ў х?чоЯЬЩрЕ ч\я[;╫лЙ9я|▀и#Aчф° ;╔Ь{з║тх▐∙9=Wдєц1чwpєЗ?дёп+S┐┼∙┐··Ъ)*(ч\I╩AlВщИЪ╜аЩмуцуЕ╬ЇГ▀V╙╦}!█U╚╝A╦J╝hs╠Яёё░vЄ:ЧY╬mH?чz▓Эг╜є╗█я ┘ж ЗЩ.╙2Наyrх#╙║▄s■Ф║Ьэqz4╒Х!\г*єЪ)K╙ ╙ь┬√Zд▐ ├A═/:q█*ui╩Э.∙uKQ.╧з}bU╔ оц█\█оynXнvTF┼╠cdЭпC]╜уЪЪЩ╤╘xп║╔NВЄТ╬L╬кw┬pжЫш╒ьфЩd√_├90├nCШч4▄╒тS╬рь╝.╗8УШ└m√¤▄Є▐оdm▓╤╤е▐┘<.lhT╚ ёGl*OYVы╓╠Ф╞[╗╡}┼╟XPРlчd▀╣v┼9┘w╛NСmЮl▓╠╫▒oX╤ЮEЗўецlе╩ю▌.Ж╡╧4ЕXч┼бб█p╛EУ▌G5pяАОG╒c╘ў╧ЗТЯъ5╘їёЎ╔+╛√pўD╚Yd|Ъz W()ЬщЬOЛ╤╗мбР&╠▓Є[╖█Ф*шыPа╠чф┴PЄ▄ЫrN6уz╟▌єu╩┼|┘ц╔&╫| █╒▌┌кnj╚6дЄ┤QоНюЛb╛Т╔╘ АuRmФkБ╔A7%@╒йШ6╩ыe╞╜╒ЛэL═/-ю└*╠LНлwРР╝Q╒nН25Мeа0╛F(В2Аа  (╩@В2Аа  (╩@~ЩРв■Ё3╢/╗╪Б;l╓┐╠жШуХа\х~°a█╘ж╧|ц3╢kЕа\╩иtП=ЎШэ+п√я┐▀ЎeGP▐АLP╢C@m"(пВru (г╥Ща|╧=ў╪бЄ8■|YГ2mФАe░цzO╪╛`╣╞W╗┐Q _Рfея■─n╧=ўЬв╤и.^╝и┐∙Ы┐q╗Л╤y}чт%}√oцїэ'■N¤─╙▄▄]╕ЁW:~╓э╛ў╜яiy╣аF%A╙Л*G╙ ╘2Ъ^мЪ^TЗjnzQH▀5f√j├╧о9Пi)▐¤ьj╝ьЖыдЮwI ~лt╦їё▓Z╘Ї┬Дф|рv(юьБ╘■ў▀╡C╥Я┐э]j¤▄├z╤∙0ё╩+╥5gЯ¤Я ча~у7Ўш╓[o╡S┼╤Ї╘/№Ъ┐Щ:o|-∙╓?H ·[╥▒ЕdH6^uВр~R┌ї╕4ї}[Xго^ї=p╦a┐k╢ц╕о╬¤уzё┼Я╬[nkФЧ╞╘┘9&чCT,iм│^їс;\аb╖-╫|Е.w&м··NНХg'PrЕрZ ╔▀√йЇЫчуM-╛ O╢0└ ЩЇ┐}G┌√ЧN|и╤ц╫╥Sq''{!┘№╜╬Iлы╤ь┬([╙Л╬·z═┌■╕^M╠5kд_::=а[Ъ78│╠╗4╓йЦ∙A┼"╢$ r2ї█Iєт═wTъЪ┐ШхЪ░<╥м╣<цImzqZ}u;u▄э▀лS╦╟┤├эOчЯ╬hSdсмЎ7┌AаBdkzё¤я╫x╡╦: ¤▀∙}ў¤ї╡п▌┘(▐Ў╢╖┘╛╠кнщЕinсЕ▀ ╟&ўпq°S/хоf_\Ф■п╦╥+yфC┐Эp°ё-╥ Тэ▄lЄB╧╕█√ццa¤ё_Шa╩,ищ┼w╛є¤┌п¤ЪК√є┴їKO'k ьmяR█я<мЧ_Ц█ЩКфьW_▀Аn╗э6;U\╣Ы^Ф/(wОХ&4z▓╬;гp¤ИЪчж5P╚В¤╦,v█r═W╘rMэxЛцc╩Х√¤A∙t_ЭNюZ╓1'/О╢л)·аЦ═└ &(Я╘оМAи ╣ВЄmыj╒[#╧┘╛ь~N=aб4Юs╬│╡Ф¤mТMX.]P6ч ┼гв╤кa7 °╦{5Л(╟щ4ГL╦╖Гy°╗з Q┐┐xЭ╬╜xЫй(═█?╗∙y}к∙U¤┬█▀dK╥∙▓ПК╠0FPNёЁ╓с9Mз=рЩp╜zв├9+ЇВВЄ√o Mп√╗┐K╘,▀x├ ·╦║╗Ь4№w╪и╗с9¤ў╛I╦╦ёiо┐■z┼bдП№уkФ╫пН▓ybъыUяvaчщЎК;mЩщ╝&Npь╥ььРZЬЄ╬Їv 3Soэ╓vєlЩхzM▄ux╦6с╙щOМO[цг шNu╞yЄ╜ї╡тH┘>УКы╡╙┤8ыp'p°╦╜╟fчs╫ыmkГ╢w╖*║ШЎ8│:нУ╟ўjЧM╛Н]╗╒vёТЬ▓м+п&╣<═)LО9A╩tё;юСSшЦ═ G╒Sl│L╫╩хвсMп╒'.NшуЯ  ╜х{kK3╗¤√Ч╒√╗╙o¤╫уj|уkliАеEERcБ█УУТ═юЛ?цаР<тьУV;\(Рy╙&▌√Ъ╫╕]█M79╔°┌┤щ▐Dwї┌█Їу ▓~ЄУ{▌ю∙ч╔╠_└+_P╢4*}a╪┘═ ;2p7 L█Г╤щ&B:b╩4ptXнн╬зЧА'mi1к╓юэёO5 Н ═╬ы▓)?3йPп4х.·▓ц╒м-ж╫Х╢╠П:Я╪ЬmЮяЄ╓▌лёС┤╢┼Nиэ %ЮЙP|=йR[l╬YGв|^Гv▐╪\╖&√эЄыM~тmh iv~х╥є╓╕U█╬E╡`W:оЭuuкs║ЎQт4кУйyг+}ЧпаyщV▀mжЦ╪ыЄ.╠Мж╞{╒eOи █╗╒]L=ЯЫs▓УMonEVyо·ё Ў9mzрР~∙п■Tс▀▐б] ╫зu╦OV~k│щ┼┤¤?NЯ№tЗюЫ¤╜~фўЇУ _ц╗ZЩм1лqїШМ╒{V?│хйХr╔ьХoEф╠TT├s┴5Ёf#═sЪ▐╫lK ╪╓8ПкЎ╝┌6Ч┴·4╜Ho╧k╩▌щ<1n┐п╓Y3Э7o@5┐yт·u4аg┬ЭZ▄7н╞#a╔y2GО4:Oъв:╜┐▐r№╦L_~р·╠ ╦|у√╟Ы╬{LAП═+ў?.WЦv█Бы_)┘Ї"╜9E╛═+╠tвН2*╖З[▄о:TS╙ г·╚╨▐Зv9▌ и=_°-¤│чдЯs╞╜┤э&¤ш├╫щ╓ж┐╫█n-0жЩцN╓╪g├Е[Ы>юd 3`2ЦWгl┌пиeў°ЪЪ89,┌c▓╬Т╬L╬jvи%>┐й°3╡╨ЭЕ7е1▀d ┘K/щё_t╗o¤У╣ ╚▀ыеЧOt╫]ў╜■їж╫╜юq╖{├■▄Щo}"ы·м╒m1$╖UЕ├4СШээКЗFпн▒├-Пў╞┘&щ▄f УgO╕9042ви█├┤ўХжждцd╗Лд ╦ фdaўb^8snНjёй°иў▒MъМ▌Ш─cH{╠╣╕═I78У┌╡ў╕NЮО-NЭ╨╣m[хV/Ок╜оOvФt║O}▐└тФNЬ█жн╘&к┌5╒о╣с╤▀Ї2╔фЗ№eX~/GOщ╓№╣╢╛э&mz -N╛E╫▐yГ▐цD░ЎЕ┐v╗w:¤╦o╜^?ъ~Нn°Х[┤ї7щН╧?бWц кО╦м╪КHS┘h▓╬RГжэ╝▐№Ў BЩ є~∙╖~Kў■п л█╜ УЯTыu?╘omК&║_╘╙┌╛¤ЧїБ▄ыv┐фьї╕E▄:╒(;A╙¤Ф d╙b┌¤Ъz}ч╙▌`(┘╢╣>Ф|"═╕▐x[Ьєutй╫NM(5щ┤█&n38mNЁ 2Н_╠ЧХYРўi╩√·N;.┴ylжmuK·cH}╠nч┐0E№У[и└·;ОЭТv╞█7Е╖щTр/M!]┤╙╒5Е╡эw┐мп¤▒╫м"╫pF3сфЕўKg49k.nыPЧsnП_ЯdК'3|C;г°5YjКЧoЗєЇ▄цZz▀чЇ╙╖╢щm╖▐а╗╢l╥╦ Я╫(╓yЛ6╜■:mzэuz■_m╥K▌п╒цнЫЇ╬·Ї╥╧╜WO╢■яю╝IлФ+╝"2╛яF╝ЭQфc╬&инё╡kй8╕9Є▓Ы[╓Z═№Дu╞v8╒╚┤*ъ>╩@mбНЄ·аНruаН▓├н)M^ у╡зuCpвння║вД°xп-п{▐uГ┌1-┐0?√┘╧ЇЇ╙Oы╒+йЯ╗Ї шцЯ~_╧¤╙U=єглюul?W╜nэїzх5wшЪ■┐к█▄жw╝├▄ "╣V0█╢вН▓эOlsЄ▒╗╖t│НП[{{еhsвН▒7.▐[ю-jЗ▄d╕Ю}╢(H╛ўQ■│#╟Ї╦7&╙ё╖^vЎ╩? ДК√г?╨▐╜¤╡sх╡┐ўЁPhв║├▓√b5WЬцw В2jAy}ФлC5хЄ▌G╣║╝Ё┬ z·{Wt╦w XoZ·кЇj№^╫▌p│~ЇошЕ- г▐·ОwщНo|г[^эЄ■┴С▀;о_║>∙FЎК┤№▐╘f х┐p1▀*┘Ў3╒^г▄Q1ўiаRХў>╩╒у╓[oUsш┤щ Н╛ў/╓Лoiu╗я■Лa]╫·q¤№╢ў╘LH╬─<╛їя ¤┐╙┐√w·─'·їЯ ЎпїЙ┐∙О■▌┼┐q ■Ч∙'▄dО р>цv7▌┤╔m▀╝╓jиFycвF╡МхїAНruижeмф5╟0▐■Ў╖ыцЫov√kIPН▓╣ я╟?■▒оЪ▀е.А ╔п¤ыW┤Sжщ▓2Aиeх╡GPоeT║аа\je░AХОа ╓AХ╬х╡@P)╩@aК9^kшо@щФАe A└┼|UюЁс├╢kс└Б╢╚пQ#Яў.ц S╠ёJPоrц$|ш╨!;ДrzрБ╩(пQ*▀ўВ2PШbОWЪ^╩@В2Аа ╪рAyIcЭїк╧╪с"╠ДU_▀й▒%; м╡е1uvО9Gs9ёYэЎЁЪTИЄeўДчЬd╜n5Б┤9N╪Kc¤ M(щ░%╣╪╨р_fGD▒ЙРЖ·╦TКqZ}эгZ┤Cюp]Эъ▄о╧╩$▀щ░^:¤п'╖ kUпкТ┐FК5г░╖∙Ъ*╡Ї╫h:^ЛP ╩ФЧ╞:U▀#M─bК┘nBSл;йЧ╘МО I├√ Kg4й^ч▀д╬°╧р√4м!йА╖8┌юЬXwъ╕6NўэФN-kyyY СЛ┌┘|┌═w:мг╓a═∙^S▒XDхЛ░E╝FКр╛W8oуv╪UAпйR zНжу╡ХбlA∙╠дsВЭK=ЙwDЬсЇ,o╪■ █Z▓Їao┌DMЪ¤j6e║xп╕^╥X РfgЗ╘тФuжП;3её╓nmop·▌eД6╡┼ю№╢ц8▒м╕%єа║ўй+4л╔Фд▄аэ▌нК.жнc4ю?лхЕИ┌ь░йЩ:y|пvэИ5vэV█┼K5Y∙NЗКЦЄI╓6╟├иWю5k(р5тЪQ8▒ √┌╚░╛B4 L+67мV;W9пйR[∙M╟k*E┘ВЄфlHНЙlЮЬЎ|ЧпЦ,e╪9I╖╠k╨лIЫы╓дў╒lb:зЫш╒°И)o╨└QчфkkржR7fi1к╓юэ╬T╓lT═Gэ№=¤Т╫я.╦Xr├╖У:║z5;y╞Ц╟54Ж4;┘U░╞н┌v.к;ШQ╛╙am┘PЫ ╜■pj^#C M─_ s├Qї╪Oznї^;жYГ[U[╚k─|xt┐"J,'╥Сy}Iйс:x╗ГU═kк▄x-└║)_х╓fm▒╜ysN╪)▀Є·ЗЧ╒╕z╝нsВЮЭЭЧ{їO╖е9нf*O^═ЩЩ▀▀яОt╕═.lyGЧzg╙Ъ_ШiгЛ)с(9jб╫▀Ї┬╝F|пЕЖэ▌j╖═Э№5┐=уRб╟к{№з╜>│н/бCСФэ ╪юLxM╓Y┘Вr(=HЦB╢РPfж┘Еўu╝=eZєЛ╦єЪ 5&kи+╒т%]l й╔fФяtи|&$╖8єцьыfE3Зr*╛F╣j^Sх╞k╓M┘ВЄ╛aiи%їd8╢├^M░├ а╢?лЖF'|qqПo]~ю╫║i═'23═.f╒ы√┌┘4╦Ё╧я~M▌\p·╪б]{Пыд╜hqъД╬m█кFw`TэЙ+ъ│LЗъРЎq_[╜]ёУ╛╢╞+^s∙╝F╢л;¤т║lыK(╛F╣r_Se└k*R┘В▓iщ╢YЇ╒"ї╚9Й6 шип╝>ФgэЦs┬ЭV┤'╣╝Ф█┤q╓5╪oо▒тBеацЩ}эЬ2мCЗ┘!Ф╙<а╪бНбdпСBнуkк╘xНвP∙╛╫T┬∙╕Ц2j_1╟k∙.цл G5эY¤/єїD5|┤·Oш@║Т╝F ┼k P!6tPv┐NЮОно╢╠№КXlZiw╓jD ^#Ет5и<(┴6tхZ`┌?bэl┤6╩X=^г(mФБ╥+цx%(PЕ╩@aК9^iz (╩@В2Аа роUО[O@эсЎp@щs╝ФлЬ ╩З▓CАjў└ФБ2(цxещАа  (╩@Аъ ╩Kcъь╙Т,╩jЦСm▐Щ░ъыы▌nы√V╣НXe ╩ЭNHьєE─|Bi)┬я║ЫQ╕'кс╣Шbs├║є ╢╕P5▒/кW∙jФ[{ЪьЧ?+oKЛК*д╞; АкT╞ж═┌7╥╨С;ьcjKm╙Д··░тS,iмH││CjIпНv═(ЬШз^с│ё╥3a_Щ╖кФхw·┬zЁ2\ns ;mж∙¤х-╬╢┌т$g∙n∙╕z╠4╜gї3;&°1ЫтN[f:o]╣Ў╩н╝mФ;"ЪPO2└║тa24S,╙▄pT=ю 8:м╓╓a═9х╙■*Y'8vЎHv╙E┌Эb'H╬w┘▓Й^НПШж f∙є┤╙┼ц║5┘o╩3,├0!╓5нБЖLєзn╖iV╤jgOъP─Ф█╟o╫═nyж╟ь<ъБщ─Ў─&╝┘Ў╓B┘/цы╪7ми`-╙4┴ А√:тГ █╗╒:>Хиa ┤tFУJ╬Ур[О╢4╟Гл█Ї┴╓шЪ╬╘Ё╬╬ыrжeШZ[7GЬШы╚8ъv$█cЎ╫4ўМK╤┼ф╛└║){PV├АОvOъ╚;╝lMlвж╓ ┴AЬi'|5╝оBц_ Т[&╒m.№3ы ме└z(Pv4 JC╢MoCгB│CЄЪ./ЭЩ╘loW2ИЪ┌[█Ы╨░]▌J╬УU┌Є▓,c╦└┤█D─m Ьq~S>й3╢║╫▌юxon┘sk╖╢█Ц+Ц┤/░&╓$(╗mw'zУ¤s├КЎ─Ы┤ Е4ё┌$ h░7▐ь!ї╢xЫ]o╙е\ИЧ"u∙nч▐f-√2:" ╡8ы▌Тa~єBjЙЧї╧З и¤═ЁШ═у ┼/╪[▒╠М√А┌╤{r└эАJTЛ┼Цm^Ю}ЎY▌~√эvыэЁс├:tшРT╗x@░CЩU┬∙╕█рЕфё]cю_а\К9^╫иFи.ePГfR~;!їw№№╙%ч sy▒╓j=(%В2иQ╜ЪH▄┴╩№VВ-ЎЩ 'c┴ ;Щ╩єbюjх^▀ФTЦїаьhг\хLe@mбНr)ШЪ┌)ue╜┼k┌4&рЎKGзu$░|@ юн]эЭ╝╘кс╣АюЯ▐-(b=f%U╠ёJPа U{P╬tзЛ╥fN{4nЗZЗч~щ6=L{├]Ъ╩зъЫ╖╟╝i7hрhЄ7рз?·ж°┤YЧУc▄Я╘Ь╫eз╫№№eиWЎч0/;o█■▀МXwр▓sэ╟ti╦]ё√qЩўqr?═ KCюsТ∙╣B*Ўы┴{aюm(∙{a0^s└╞QeM/ь╠╥ввN╣{Юp4lяVы°Фf2Х╟ЭwNg╝╞╒уЭlЬ7уY√жЪЄЙ┐g\К.╞▀╚|╦╙Цf╡┌▐Дlыlэ╓v/3vtй╫Y√bжw╟м╦╔▒ 2┐_Ўх∙Р║Ў5+j╬fЩ╣~3>h┘╣Ўc▒2юуф~r╫eЮУl╧R▒_7ч5╔{aОm(ї{a&╝цА г║Ъ^°j Кц╝&N6nQЗy╙kЩTўЬw╬■&YБЬўu═_Ю╤T┤Y[╢л;ъЬX.╧+╘х╜єп│bЎq╨seG┴b┐nL╝fTЎўB^s└ЖR]AyfJунц═╧Ь$Жф}█e╛bЫээRGжЄ°а=╣$╟зЁ╒╕є┼{s╦╡NП╗э╛ZХЇУ\╛╦╔└╘^hdD╤юэjp■Щ┴й)й9∙]cRоl╢mё═╗b?хZn√8▒оl╧R▒_7▐ 3*щ{a&╝цА г╩┌(Kю╦К8Ятг=ёЄЦбР&"цm4S╣'u╝█Щ ;4Jоз>T@-JЦun╗├мп7■UЬwIюm╧┴y36я╓▌Ў▌█ ОGЫУ'#Oр║╙e╪g▐гж╜аyЎэзЦ╔n═<Чnч]д│С∙П)sб√uур╜0?%}/ └k╪P°e╛r1_╧їKG╣░бё^╚{a╣Ё╦|@aК9^╦Ф ┼╔@нс╜Рў┬r!(Ейиа ╩За жШу╡║.ц╓A@PФАe A@Ppe(#єЮ *Яє,ўQ S╠ёJPА2т=Е╩ўШйДcЛу╒дШуХж@В2Аа  (╩@В2Аа  (╩@В2Аа цт┼Л:xЁа>ЄСПш°А√╫ Ыr└┌!(@Е╕zїкО9в/∙╦║ ■√їЁ·╓╖╛х■5├ж▄Мї╒WэАr"(@ЕИD"┌┤iУ╛°┼/║5╔o~єЫuуН7║═░)┐хЦ[4::jч╚b&м··z╖ ╧╪▓R[SgчШЦь ╘В2T╙мтщзЯ╓рраъъъ433г▌╗wы_■╦щ■5├ж№SЯ·Ф;]Ўf3 ўD5<SlnX╤С┬l9├/┴@Х!(@°┌╫╛ж°├n cП=жбб!---щХW^q ЪaЦ 3▌╔У'▌■@KЛК*д╞з┐a@╙╙2╜ыоТ╢Є@PА Ёэo[я{▀√▄■c╟О╣╙Щж╞=ў▄г'Юx┬э_iFсЦ!═j\=жщ┼√]ўЪZ\[Ы;6═1┬╬TжВ╖3╤<г▐нщ]╥X┐3яьРZЬ▓╬▒Ї║_g┘▐ЇN>/=у.╙Цy═<╠·╙╞╫ЧZгЬ╢м°∙цщ╘К╒└#(@xю╣чЇ╞7╛╤эъйз▄┐щ╝r$_xс╖еEцЖ╒┌:м╣XL▒ёv▌l╟╚ └є]NY,в'ФЎЕ4aжq║Й╨╝.лAGУєN°ы~Э▌┘#gwz╙E┌Эт─2ЭnвWуn3ПxX┘iчЖгъIi(░м3╧╝эplо[У¤4╙░╛╩Pn╗э6¤шG?r√▀∙╬w║╙yх&H▐zынnAЬ╝п├Ў74*ф╓:╟k{;"NxОП ╢tFУЄ═яё/sK│Z═_╙Ї├W▐░╜[нуSёZe#hYns[ n:S+>k┬;мВ2TАў╛ў╜N0Ьu√ўю▌ы■MўёП▄¤{с┬╜ч=яq√Л╫бИ[{█е)7Ь┌цы╔лOt9┬;ФA*└о]╗ЇХп|E╫о]╙п■ъпъsЯ√Ью║ы.▌p├ ю_3▄┘┘щО7╙ЩщWeiLa╖░ ╠snНj╤kчTУ█░]▌╥С|╥┤йнЮMN╗tfR│╜]╔╨┤м┤yаФаl█╢Mw▐yз> ∙╧kyy┘ ┼·Л┐° ўп6хf╝Щ╬L┐*N0╒PЛ╜pоEУ▌Gх6In╨`o╝ Dъ┼|ёЎ╦╤█4┬щ╝Л∙VК╖УЎжm1mб#■║сАe═д╬уv▄J└:лЛ┼b╦╢?/╧>√мn┐¤v;╚жРўLєЛ{_°┬tх╩ўpw▀}╖M3╙▄┬╘$o▐╝YЯ°─'▄Ъf╘ж|ПЩJ8У PMК9^ ╩PF┼╝gЪ1ўI6╖А3w╖0юЩ6╔ж╣┼кkТQёЄ=f*с|L&@5)цx%(@ёЮЙBх{╠T┬▒┼ёНjR╠ёJe A@PФА\╠Зкў╪cП┘╛Є║ ■√mР?▐3Qи|ПЩJ8╢8╛QMК9^ ╩UюЁс├╢п╢8p└ЎнdВЄ=ў▄cЗ╩у№∙єeЕўL*▀cжО-ОoTУbОWВrХ3A∙╨бCvи6=Ё└eT-▐3Qи|ПЩJ8╢8╛QMК9^iгМЪЇо═ЫW╒ФАeи0ц'м<иП|ф#·└>р■5├ж░v╩P!о^╜к#GОш╦_■▓█&■■рЇнo}╦¤kЖM╣ ълп┌9хT9AyiLЭЭcZ▓ГX#Лгjo╒в─▓┌╫▄║╜fЧ4╓Yп·ЁМ.P╛█=V}}з╞╓ЁF"m┌┤I_№т▌Ъф7┐∙═║ё╞▌┐f╪Ф▀r╦-╡sTа|ў/я∙к@yГ▓{вqNh^WьЙ @Ь ╛╫TзIq╒8J┤ЭKc¤ M(щ░%E╩╡=┼&Bъ_Ы}kЪU<¤Ї╙T]]Э-Me╩?їйO╣╙m╕fkыаlAyiмSї=╥D,жШэ&4%в2P╝p╦д║ч╝╫╘ДBєЧеЖMOи┴NS▒J▓Э3:2$ я[eH6Є┘ЮО}╓РОм┴╫╫╛Ў5}°├╢C┘ЩщNЮ█р╠╣UkўЎdнb┌{B├└tbЩn│ ╖*8Я╟Яy{CЪ55ўeЎ▄s╧щНo|г╩╬Д┴^x┴еqc P(ёm▐Dh^Ч3>╟ЩЮЛ\я┴NЧўёeT╞ёЕ*_х╓fm▒╜)ЦmЦў═i├Ўn╡О√Ъddя+GА╢Иw╪■┼K║иу┌щп9>╒В)ўOЗк╥▒Aаk*^sЦШ¤пС-═ё└ТЎЪZ┴МЧ ржsB╩ьм W>Kg4йАeф▒╛ХпёАeх│ A╥Ч┐V▒g\К.j)Я╟Яm{═у2╦▒Гхr█m╖щG?·С╩╬╖▐zлJ╙╨иР╗/у5▒ю7yЩЎoжч"╙■Ёя╟|П/гRП╚бlA94;й3|Ь__N ^ЁjФ▌╬╘4г&Ш ═bъїИL.╧g°*▄╟ &s^mЬ█н├╖6л▌Т¤m╕k6Л`Ў_и▒ьmc▀√▐ў:с.ч3х║pсВ▐єЮў╪бtК╕√пKSnp┤M*с9.зZ|╓E┘ВЄ╛aiи┼▀╓OЪ ;├ж╢c6yq╠╥ЩI═Ўv%▀╨rНG~╖j█╣░F▄╞╩>n∙ M┘Ў╩ЛS'hг\E:¤5╚n═[Жon№:║╘ЫэГk┌k.P├vuч{Q[╬╫x└▓Є┘З█ bЄLц┌]п¤▓├]пщqЧЭ|№ЙrOОэuЫ{4ч▄╦л╢k╫.}х+_╤╡k╫lI03▐Lgжф|`╗ tM`Ю╙pkTЛ╩░єx.r╛ч┌┐FЕ?Pи▓e╙V╨m[ц}цt=2ov╬Ыў▄░в=ё▓╙Ц.х6O╣╞#?;tl!вЛ;m╙ ╙╣ўKv╩OmS╕)^╢'║Н6╩Uд;┌Уx=╒Ы┌╙г∙▄└yMЩЎ║-▐|щ[е╛ц▄.q╤ФзAGSзё.╞Z)╫k8`Y3∙lГ#[шo╨`hH-v■■∙РнQN}№╔rO╢э]╥Щ╔YЕВ/╕(йm█╢щ╬;я╘ч? yўа ж▄М7╙Щщ9б╤y░v?╢h▓√и2э▀▄╧Eюўр\√╫иРу TЛ┼В▀С3xЎ┘gu√э╖█!м╖├Зы╨бCvи6=Ё└:pрАZщ▒╟sярўо═Ыm_q╛wхКэЛ;■╝√╦h5┴▄▀|кkїў!^'цО-єГk│¤f_Н4kn╖%+ф=╙№т▐╛Ё]qО?s ╕╗я╛█ }ж)БinajТ7;╟Ў'>ё ▌p├ v.╘Ъ|ПЩJ8У PMК9^╦w1АК1у╗Ч{є* ╔F├└Q ЫЪїЇ K═ДфЮиЖєк╡/ ~ўэ█з√o н√Ё╫¤╫ї■ў┐▀¤kЖM╣OHА╡AP6А─▌2jт"з L;Пг▄a▀╜`rZkХТ}L│К|P'NЬ╨ ё╗═p╞цА▓ (г&Щжлщhг\хLхНа╨6╩еVSmФ▒жx╧DбЄ=f*с╪т°F5)цx%(гъЩа╝╩(яЩ(T╛╟L%[▀и&┼пe(#▐3Qи|ПЩJ8╢8╛QMК9^iг  (╩@В2Аа  (@Е╣xёв<иП|ф#·└>р■5├ж░v╕=ФQ!яЩWп^╒я ■яы╩Х+·ЁЗ?мЦЦ▌v█mzю╣ч477зп|х+┌╝y│>ёЙOшЖn░sб╓ф{╠T┬∙╕Шm╪ЇЁ├╢/╗Ч>є█ФF1╟+5╩P!"СИ6m┌д/~ёЛnMЄЫ▀№f▌xуНю_3l╩o╣хНОО┌9Є░4ж╬╬1-┘┴Вх; j╫Иа └4лx·щз588и║║:[Ъ╩ФъSЯrз╦┌ #Я╨║▐┴Ц`╜aЩЪbў?╜√К█еЧХАа рk_√Ъ█▄"f║У'O┌б"5 hzz@ vp═нў·QГfопW}вы╘Xр'1 tag╚Уй╝Xk╡ФA*└╖┐¤m╜я}я│C┘▌s╧=zтЙ'ьP║%НїivvH-╬Й╕є╤tK╧Д╜s╜┬ц╠ь╒ш┌┐cюx{╥6e▐Й╝┼YЦ)[!5ФД╧╞KWм╟Ё/╧┐ОDНr┌▓╝эKФe <иv╜'l_jёz5Л)цv╙°$6юС3С;═▄pT=Ў@═TЮЧФу9о,ы┴Ъ#(@0ь╜ёНo┤C┘ЩЁ°┬ /╪бt 8:м╓╓a═9'тщП╛IrBє|W№─Ыш╒°HZУЗ─°И:Lhu┬q╚Ю╚cs╬▓ьdINяLЮьMiwК╫У║╝Х┴ `YfЮy ┌с╪\╖&√╙╢U/(Ч&,g3гйё^uu─З╢wл5║шз┴хОв>┤▒T$В2TswЛ¤шGv(;o╜їV;Ф'4я│'fmi^|¤уЧїY:гIL┤Ю┤х╣┴`|╩Й V╨▓╠<WПNLнЎь╝.█╤и у╗╞▄╬У>\Ьфq╙ЩOвmhT(ш╪JФЧшC[╬їаRФа╝ў╜яu┬`p#Зt.\╨{▐є;TгЬpmj─╜Zцxm7РMЗ"ЙуeBбб■╒7┘╔·б═╫d╚-П7wКЗЇ║Sа·Фаь┌╡╦╜OЄ╡k╫lI03▐Lgж╧к╪Z*╖ЖkRgl└X:3╣▓Нr├vukHGЄiZщ./9н╗╝▐оdш ZV┌<@с╢иyeЫбХ▄o<ЪЭй╙°╦3~hєs╙D╔7▌Ї└[▄)ЄY*A*└╢m█tчЭwъєЯ ╝ЦЧГ╩ФЫёf:3}F ьН╫Вyєх╧9∙OД4╘пыЯ┤QО╖ГОЎ╪┌4зє.ц[╔YЮ"╝i[ЖBЪИ°ыЖЦ5У:П█q+9ф2Ў]@zFУ│!5ЪЛ∙▄6╞▐▌%:╘х╝6ж№▄BН╬QШб╝шmо'>И ─/є@ЄЮ∙ълпъ _°BтЧ∙ю╛√n7$Ъ*╙▄В_ц█Є=f*с|\КmЁ.р[u√dИM;╪;a. uz▄r╙╬╪л 6M&z4юЎЫ╗dф(O[о[├Ь~kC3M┐t4е╝└їаьК9^ ╩PF┼╝gЪ1ўI6╖А3w╖0юЩ6╔ж╣E╓Ъd╘Д|ПЩJ8ЧbJФБК9^ ╩PF╝gвP∙3Хplq|гЪs╝╥F@PФАe A@PФАe A└/є@ЩўLаP№2Pz┼пeкA(L1╟+M/Аe A@PФАe ╖ЗА2Ъ∙У╟lР┐О_╜▀Ўe╞эсА┬s╝ФаМLPn¤┼{ьР█ь_Э'(eP╠ёJ╙ A@PФАe`гZSgчШЦь`┴V;╤Ц4╓Yп·ЁМ.БB╦LXїїЭ+Ў┴sHЫ7ov╗бo┌▓R{ЄK·╨З╛д'э аpeаЪШ@WяДD█uЪд╢nБ╡@%┌╬е▒~ Е&Лt╪Тu╨Ql"дб■b╧75╘;пЗ┐в+П?д∙ X@Ш-g°%X└ eаКД[&╒=S,f║ Ец/K ЪЮPГЭжbХd;gtdH▐╖О!┘╙▒O├╥СB+╢Я№оц╒м╗юt·я№Ш╛·╒П╔Їо╗J┌Цє─O╪╛Х▓Н░■╩@U й1С4;1╡к^Mн¤;N╓8'Z'д╒D╫з╫ьжМ╧╘д`Fa▀2┬gуеgrо/ь╠i╦ыM[ЦЩ ЯmШЩ╥xk╖╢'ЎAох°╫V╪4┘pз│═7╝y\є:b╦R╓┤┘f┘KCcH│жV?oўi°ёЗtў▌щё+Wtхў Еn╢cфрЕNзь╩░юsBщ'?█мq3Н╙Н7/ш╗║S√|r▐п~╠_ўыДшї╩Щ┴Э▐t├ ┬)N,╙щ╞?кG▌fё░▐lз}№бyїж4ФX╓}fЮ¤ж╛Є°їїO╥L#п{▌ыЇ╨Ciff&%,Ы~Sf╞ЩiT&В2PE:"ё└ыЪК╫pжц╓a%Z%liОтеEE¤хщ╠x┘n:'╬╬╬+%■-Э╤дЦС╟·╢wлu|*Y┤м|╢!]жхdZ╖Wm╢╙▀o╞╛Ъjw>│■\П┼╠]\шЛтр╛√l Эwй┘нuО╫Ў▐7ьДч°Ш`O~C_Чo~ПЩw5щnє╫4¤ЁХ▀∙+╘▌ПN╟kХНаe╣═El-╕щLн°▐СПЇ░LHкAиFцb▓╪Дz¤б-У╦єЪ╡╜9ap╬ЁDС?оЙJ╪ЖBЩ}j,C√Ё√4ь╓▐vj┌ з╢y─zЄj┴]ОЁО■░LHкAи"Э■d╖╢│Y[ь`F]ъЭЭ╘ЩL╒Ю Н ═ц╕(нa╗║є╜p-myKg&5██Х ╜A╦╩gnSЗ╔3ё▄<Ц│b▌yJ╠ЧcynSРцЬ╧@сЮ№ТЖ▄F└&0?оЗюЮ╫w╜vA5╣w■К>и╧ъX>i┌╘V_HN√ф7╛о эLЖ▐аeе═ГтxaЩР TВ2PE║г=Ўв2╙╩`¤U┬}ФБjRшkжра l4╜ФАe A@PФА▄UэЩgЮ▒}╚хО;ю░} eT5Ф╖n▌jЗР╔еKЧ╩ишаLM*A97В2Е[UPц─╗√emQгЬВ2Еуb╛ Є═з_лп=∙¤╔ў▀а┐|Ў ║°┬┤°b╝3¤┤°;%╩На\A~°╙ыЇ L·╫?/unС┌▀)▌¤╓xg·Н┤я@YФ+╚╒лWumY;у╪▐╗ ╦kАа\Arх№ръ;~┴ЭЦ░ P^х Т+(эxл■у |╖█╜√▌я╢sа╩╪xG╒^Wз║╛╙╢ юtЯS╓>кD]¤щ>╒Щщ╝.mzЧYЦЮ▓8н╛\█Jо╝AyiLЭїїк╧╪В╕Щ░S╓9ж%;ьи▐Lчui╙╗╠▓№єФ╒М┬k╢одW_}UWп)п╬Lи┌Ў╣╖╜╢ы[г╜▐╢W{/╘иЧpЭ└{ЁbЫ┌Гэк█)ЭZ^╓▓эNщдY╫┴щУfCьv,(т▀nP6хпQnэUotDЙ№угСhлZГЭкяС&b1┼l7б)'коw{Ь ╖├kщеЧ^╥╦WХWgж═иЪЎ∙хyЕ&╝эШPhи?╣▌e╥о▌╥Ййxт<=╓╢▌NБkQS'д╚┬1э░%╞Оcй├f║╤=aЭ;VS]Э┌G╙h{ЭТ╛ж&╕]гПЩZч>ї9у╝Zс─4^э╢█9╙р╟t,▒тE╧m╙╓F;╩f Ъ^4лл[Ъ<O?3GЖъv \K:3) ╧E╘aKМОHъ░ЩnмH││Cjqk╫Xg╜ТХа3 ╫wjьqSV╪ч╒P&жIй╣tж═╞жЫNД╩╡Ї╩+пш╒eх╒Щi3лв}▐Q$▒т-j^├▀┤ Am Пш┤[Ы╤`Ч▒8еyЕ╤Fэ$в╢╢ИЦЧuv ¤┌ р^?iSЁщУ:╛ўAэ▀ьЇЯ╗и╨#╢V°Ф3═╬>'F;A║)кmНїЄ┬nЭ╪УйЗ╩▌а}R╗Ц╙;(З5iг╝e`Pбб#Ъqk6З╡o╗▒tFУ│!56╪сМ4p╘ пн├ЪЛ┼4=pп{5>e┘╠Ф╞{5`ю5<UєQ[C9сL╙v"ЭъZц5hkOcs▌Ъь_√жkй*ў╣┘6uk{╬m+Х┌╡ў╕v6Е╡э┴¤Nьїi й╔Ўd╟.э=oвq·фqэ▌e#m█nuy+0╙шв.=v╔∙▀Y┐Wгьl╟╣sQ-╪╔R9б№м ╘╗t2S═3(й5║ШпC]╜уъiRhp└Й`>н═┌b{ ╥╤е▐ёxsБЩйqїv┘j╔V_╨2╙(к┼╟Э Эї{╡Ы╬v╠╬╬ы▓ЭмRФдНrB╡эsмЭШ|4m[╦l╟`DmNlїЄмлqл╢Э;!█*г@ёЁ}ЄЇiЭ<Ю╢▄ ╢6┌k╜Ь│╢╪, Ьв┴iФ╨e'?э3═zхe+WCгB│У▓- ВS33ЪO[n[3o k║Їжы╧ ╩╦N╬г╦ФлiЯЫf#jЮЫ╓└Zжdгq┐╬оз;ффgЕЫLєИд╙}й├ i╡└&|_▄╣S#Г┴б╫4╔05╠ўЫ@╓HрBУG√|5╚&А╖)TTu7(─Ъe5 hzEPъРУх4╘b╛кOЪ з'д╒HЪ эщQtx_p3═Lmч╜&щHрB+GikФ╒░╧▌v╠ыТ│h▄V СЛ╔fN╖S╗V_'h?hЪo8у█╜4█╪е▌mm┌Эhkс░№╣╦2w╙8kЪzь╨▒кУыH╣=Э╒╕╒Дvowfэчb>╩nэВrцт╣╣сhЄ+zзыQ╫╩цД╛A╙Ф└Я╕ЕX├vu╖╢к█▀и╒^|ц.╦▄┘a┌|Х▀б╚Ь x╔uд▄*═╟╜#Д█L ╛Ьа╗жХ╦╦/┐мWM╬г3╙лТЎ∙╥ЩI═: ЖZТ╙Х¤qж┘ кi╥╩MXN6Йp║фн'Rь8╓KпзGЦпM▓С╥─┬WГэ╓h{хN┤];О%╟;]Ж═%VЛ┼ЦmAЮyц▌q╟vhЭШ{П4k╬ fS;┘/їЖ╫┴jЎ╦g&Ц4°п ╣╩nф ¤╢юYЗGYa√▄ьян[╖┌б╩`~╕фф._а5╖А█#=В╫╚еKЧ╓ ї @Хйъаl~Dcк+Ц╝╜XХх╤?^╥П■щ:╜■57щ п╜Iп┐х&╜цц▄q/■ьU¤°Я^╓є?}Y?~ёe╜ёЦk┌ Б╡ФХ╢╧+1(W"В2Елюх ─~Y[х№Ф(▄║╖Q*APp╙ єU7PIhzСЫiz└FUlє├вВ2┴╒`5╫щ╨Ї@PФАe A@PФАe A@PФАe └·х┼Q╡╫╒йоя┤-И;▌чФ╡Пj╤;к3╙y]┌Ї.│, gЬW+ЬШ╞л▌v;g┌а╝уШО%V╝аш╣m┌┌hPSъb▒╪▓э╧╦3╧<гн[╖┌бь._╛l√ВmY■║┌ўHПЬ▌кС║У┌╡╥A3№И┤╟√█╒Г╦щ┴8А ║ю▓Ў╦═ож╣╞╔]Z6╔╓ы╝дЎж┌╜pV√═Dж▄нн▐еУf¤▐z╥ЧХ┬ хэM Я█ы╠Ч║]╣/Jc╦Ц-╢/╗KЧ.щО;ю░CЕ){P╬· |БtбпN;ПK{O-ыXУ-ўs``M│"▄ЪZфx°uztrЧo╣)╙TшOvы─пЖu╬-єм ┬йь╝^ш└Ъ╚Щ1}VФ+цо;#js┬щ.2m▄кmчN╚╢╩(╨э┌{\'OЯ╓╔уi╦ ╥╤ВпЇr╬Zl│№sК.╪A╘Ф╩╣=\у~Э]Nw╚╔╧ 7їе\╝w║/u8с\T■▄j┬ў┼Э;u12zOЯ╘ё╢▌ъ║▀Є░Fr\!╕8┌чk╗lxЫBMv5еrВrН√╧j!rQ;┌╒iзvн ╛N╨~pяqw║v/═6viw[Ыvw∙┌F╪ ■▄eЩЎ╔n3М:╢`Bur)╖з│╖Ъ╨юMу╬L│ А╡╛mФ╦═\мw0дпMr╓ЛЇP 6\хr8}Є╕Ў>H(@сj:(я8╢ь╗я▒├┤Гж6yища Ла  (╩@В2Аа  (╩@В2Аа  (ъb▒╪▓э╧╦3╧<гн[╖┌бь._╛м-[╢╪б╠ю╣ч█Ч┐єч╧█>╘ Оx8`p└├▒Аt∙fLу╥еK║уО;ьPa*&(r@:=к╟< 08рсX@║╡ ╩4╜╘XP>н╛║:╒╣]Я3ДZє╘SOi╟О:sцМ- ░8кЎ─qPзЎ╤E;┘щ>чxhG├u║ПўдЭ┌┼бА\j*(Яю█)ЭZ╓ЄЄ▓"╡│Пи\KLH▐╗wп√U╦√▀ ~[`!кmЎ8X^>еmс=╝np&$╘^╡┘al0&$╗зЗ°√┬┘¤Нv6ЦEНю9б▌ Ў№pjЫ┬{°ЁМьj((Я╓╔у{╡kG|и▒k╖┌.^тP#№!yddD╖▄rЛ`╟1│╟Б╘дщhC[m╫┴╨В╬Жl 6Ъ╙'/*▓pLЙ╖└h ёс9╒nх╞н┌v.к;Иъ`ЪTШж&{ ╔щзtB╗╒E╥ЖdBЄ=B тЖf*Q╬)▄ф}▌^'╛l▄и╡ ьГКz╟BУsvxd┐S d╞┼|и(жIЕ ─&ЫА╝кРl┌мєF╕Б-jъ─9Э 7┘УbXч╬Е╒D;х hoв┘┼ЄBDwr ╦FХlв╣аHЫєjД#┘╒nP^╝дЛm!5┘ATДM Ў┬ЄкBr▌AЕ╬К╩─Н╩╘┘pdR[[D g∙р┤б5viw█E]т╙╥╞│8кГ#t█рШў',_<╚5,╚кЖВЄэ┌{\'эЗ├┼й:╖m+'─*ф╦EЕdўкfB2#~n8ше!╙ы▄6mх╜aуqЫdЖХиDцX@jкFy╟▒S╥╬x█гжЁ6ЭJ^╤Е*cВёя¤▐я╣]a5╔ЎCТє╧▀&С█A╫Оc ┌}┬kВcюz└Е}╙sЫ▐╪s╟ЄPcM/ЬБў5ы2 F╒╕ lЄыv█q1ЬCgiv▒A∙ЫсЁM╙Жf▐чОфV1?a](~Ъ▓Ўp└├▒ГуОд[лЯ░оИа фkнВ2╖ЗФАe A@PФАe A@═йпп/ш/A╩jК ┐▒X╠э╧ў/A° kT Оx╓ъXЁЗjT▐рсX@║ ї╓цP╚]шЇиЁмцX №╓▐рсX@:~┬КPHHжН2 Ы ╩з╒╫>кE;ДН╞y■ыъTчu}зm9j╔SO=е;vш╠Щ3╢$.5№ж uэї╜1Pє\¤2~Лгэ╛c└v╝/lP■ўД>g╚оцВr№ qзО█al@зOJзЦ╡╝l║E.L Gи~&э▌╗╫¤┌э¤я┐-Н[~ў:ЗГw<Ь╒■F[ьаF╣║e;№ўЯ╡╧╝;╡Wj 5┘▒╪85┌╛3y~8%эдR 9╘\Pv▀"j│├╪Аv╙▒╢_ КЮ█жн╛pДъцG###║хЦ[ьШ╕B┬/5╩╒+╫qР╤ти▀л¤ЯШ░AШє┴^эЄ╬;viя╣иS dFe╘(Ss`╛Z;й]╦╟Ф╚═ицлtєХ║ DЮ|┬╤╩Ё{\;эWнэi_-Pг\∙К=29=Ц"Г╝'lXu)ё6╨дPЫXЙаМ╒и¤g═╫k╗t2н]*кГ∙*▌!ИL0╩7еЖ▀:Ц°╩¤Ф╢Еў╨F╣╩{в6yГs▐vыDУ╫F╣Ia╛qDe╘╕┌╡ўЬв|╖VuL2A╚ I∙Жг╠с╫╘┘^Лх╩WьqД┌dиq┐╬zЮM3═╢Рє╬dFPF═Yэє╒Ю╓╔уmт║ЭъфI∙ЖгФЁ{║OЙЫ,NщDZэ5╩╒бШу`jУСbQг{ЬN╗╗─Бlj.(╗w╜h ы▄╣░ЪъъТ'IlН[еpтл5sЕsъЭP]L ·╜▀√=╖╦'еД▀жР.ю┤╟Вє╛░эTj{ujФлGб╟A*КиMЖєс┘kvqbўВ╬rr@5Ф╙oФ╝√6М╟86░ФЁы Ъ5рXаFyгИ_│@(В №└ёА|T╠OXКЯжм=Ём╒▒`B5a╣rёЮ╟╥н╒OXWDPАR!№@э[ла╠┼|jК ╔^єЛ| Да ацx5╩∙■ A@PФАe A@PФАe e>а╘к■'мАї─OX%FPФАe A@PФАe A@PФАe @],[╢¤yyцЩglP∙ю╕у█WШВГ2░╨Ї@PФАe A@Pp{8@I╒╬я~√▒┼▌╫╓ A╣>|╪Ў├Бl*AХЖє$<ЕЮ?╩5└╝:t╚╡эБ (W8В2* чI┼Ь?hг  (╩@В2аГЄТ╞:ыUЮ▒├ZSgчШ│ФхЪп╨х╬ДU_▀й▒В7еPЁ]/:ыы5ыЎїj"QЗ█└├~щшЇАl╤ZXыT╦№аbСМ[ч▀╛L¤v╥╝xєХ·Гц/f╣&,П4k.╟<йWєЮV_▌N╖CRЫ" g╡┐╤U.█U╦▀ ■ўm╓Sш ╛▐Ўe¤їл╢┼z██▐f√╓GеgчI┼▄ївЁ█├х{░п╦ЛbFс·5╧MkаРХ·╖╡╪э╬5_Q╦5╡у-ЪМ)[ю_∙pR╗ЦПiЗ-jIоа|█э╖█!мЧ╖FЮ│}┘¤ |ЫэC1Ю{Ў┘u╩ХЭ Т8O┬X√█├Щ┐sLcсz╒;Я*Moёр╝■!═╬й┼)ы4э╠┤vЪФ&)хNч5OЪ>e}a'зЩЩ╥xk╖╢ЫWбЭ6╣,oz>Э■─°┤m}Ї▌й╬мxLйL═їКm6Vм╫N╙тм├Э└с/╧·╪┤╜╗U╤┼─╥иL)ч▒x╖оЩ(Б╒╖Qv№∙оШb1зЫш╒°И9и4ptXнн├Ъs╩з.+▄2пA3Нщц║5┘oжЫq╩ЗЪЁ╩Эy▄ЕЪЄащ¤ы[∙╧╥bTн▌█уЯVЪЭ╫eS~fRб^i╩}]╓╝Ъ╡┼Ї║╥╢їгo╩ЁШ|Ьg PHv√&Bёїд*═ckh iv~х╥│;оЭuuкs║Ў╤E[l жЦНn}╗|═KЧWС*(d╟y∙Y]╙ ╢qж▀_гъъ╒─\│FВц7э|єЩ>Нйхэ╫QчE;ю╘т╛i5 K√ЬyП4jz▀в:╜┐Щ╢5hЫR╓щ╝hы{4юoУх▀v ▀Їх·╪╫Я*є/ЩпЧ*D█+╘Рb╛:├┌тЧ∙6 9*¤|ХiЬщ_ГLр╟y╞┌7╜(Д¤$щ~t;'`^ЮO;Ё}Вж╖гЄ╡еYЪ┐<гйh│╢4lWwtJ3╬:C]Е.)]З"ю6ui╩¤и└п| ylfЕs╛ kRи═ЎP)╓!у<Йь╩Фm╙Зx3И!IOУ]ъЭЭ╘ЩФv ОL╙чр6SШ<Уh&╤░╜[Q╘mОa┌√JSSRs▓▌EТ╖н∙p>хЖ▌R&0╧i╕5к┼зтг▄╟Р|lж∙З√PрcsЫУnpз√╘w┌Ў/Nщ─╣m┌╩зd└z[уLРчIараь~]т░-╣jQ4╪;оЮz╙pЛ"s├КЎx ёЭ╬mая═ЙРЖZlYтлз8mО_ьчч▀V{1_VfAC-v█Z4┘}Tя┤уR[ |╚╢╡*ф▒-щ╠фмBН╘'7Еtqg╝▌U]SX█NqU/а<*:d┬y(╝Нr9Щ√Ouх╛ry▀G╣ueа╢╤F╣Є╤F%QВLрс< г▓█(g0у╗НL}П4▒╩D├└Q G{К e╛Ja▐ zв>Ъ√"jAй3░ZыФ;"еnЬ▀аБigY╒■тъИ8√г└NаКХ>л│юAиDХ╒FE1mпАНД6╩ХН6╩и4Ь'с)Ї№AP╨Ї@PФАe A@PФА▄Gмл·З`√РIь3o╡}┴{ь1█ЗLю┐ ~█Ч?В2XWх▄Є ╩ў▄sПB║єч╧Ф@їёВЄХБ╫╕С┤yьEў/AyuК ╩┤QФАe Aыя╔/щC·ТЮ┤ГХАа 6д'┐Ї!m▐╝9╤}шK6ве╢o╣уЗ╛iЗ7 √╕▌Ж█e░ЩР|я╫?и╟п\╤╖WєgяMЖeП ═╜єzшё+╛╧ЦmюЗИ^i<▒оh\╙┌hQЩа 6Шoъ╪gеЗ> 1▌iKд√4№°C╥╫┐СмI6!∙▐пыГПUKN╕<йo|▌┘?П;{%щ╛с°pjM№Зф~╢░╡Ё_J╓@'+а┐йб─Ї╢▄LЯ(│╦и@e░▒<∙]═▀¤A¤Jz°╜є.5_X╨w▌БпыУ2$;Ю№Ж╛~бYwex▄w~ьлЙZц+у═·ь1ЫИ/|V Э^∙Gїш4═WЮ╘Ч>фVM'ц╛╧ ╬ў.ш7╜e<■A}¤УХ╒6┘CPHwA·рCNм╨Wvw7щ.█╗В┐6╕ўQi■╗ё}tўCъєкаяj╥▌цп ▌ЄХцГКUп╖М{?л Й(ХЕа 6╖ц°ы·FzvkЪm@45╬╓у№║ю▌h▒e┌?Fв9КW№P<╩ ╒╔ЎсжKmцQ)╩`Г╣O})н╢╪4°мЇ┴_ё╡[6═ >пЗц{W^фW╙ь■╣w(хт╜o┘a_│Х'┐ёuS∙Ю┘Э┐въ│ЄZg╕▄ ЮVVб╩`├1эl▌┌bяы °-Ї╒ Тя╘╟╛ЪсО5╠▌?═'ЫG8]п:u▀Э╙o66▒▀>╣╨ЬгF┘┘ЯHє╜╔х }3~сд┐lsЕ▌?┘SЛ┼Цm?└Ъл°ю▀+пq "iє╪Лю▀╪g▐ъ■═ф▒╟╙=ў▄cЗРю№∙є║ ■√эP■иQФАe mФ└║Є┌(#3┌(пN▒mФ ╩`]Фs╦'(#;В2P"┤QФАe A@PФАe A@PФАe AXA· ╟┐%ьсшЫIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/images/screenshots/all_items.png0000644000175100001770000011406014654363416021614 0ustar00runnerdockerЙPNG  IHDRyЁы╢c▒sRGBо╬щgAMA▒П №a pHYs├├╟oиdЧ┼IDATx^э¤ x╫}╪√ аwы═░%█ТЛ2╩НИE]26+3CB∙ЗP:=IИGu Й╔╡Z▓┘K▌кzB╣bо╩═╔█(╨═¤+ ╤╟`єdDЦЗLКPhмаЫ@!╥o▓#┘Z╜Z"Eт╬Щ3╗│ЛЩ▌┘┼.░|?╘єzfц╠ьЩ▀Ь3│[УJецСbВ╝ў┐ ¤v╦▌kп╜&W╪~DA@фDA@фD╨ВВ╝Л/╩[o╜%o┐¤╢щЯЫу█XкAQ_бrс┬∙Ў╖┐-п╝№}╪¤X.]╣·к9йС╦rё╥ХRS#r├ї╫╩н║]>Ў▒ЯРыо╗╬. А┼в┐B%tРўГ№@Т╔д▄tГ╚m╕ 7_AxTАwQdюВ╠]╛а╜╦Є╓;W╩ўS╖╔ko]/ННw╦э╖▀nS└b¤=y*Ф╔╔Ii\uХ▄z╦√хЭ╣█фЯ▐║C╛√┌j∙╬kНкkТя╛ё╙ЄГ╖Ъхн╦?%╝х6∙gЯ°С№├?$═▓хvю▄9∙─'>!555ж╗ў▐{л6ю╨█ў▄s╧┘б∙Ї4=O9Е Єжззхc╣Nо╗fNо╛Ўy■ь╒2·7я╩╟юhТ;WR■tЇMй╜╡Nцо╕Yъ Е№─кO╔^}┐╘▌■Oц°;.▌Ўа╕▌║3vZЩяЎд▀н╓вB╟█╢mУW_}UЖЗЗхы_ ║ ┌й╒х╠Щ3Єп╓¤Мoаз╟щizЮr ф╜■·ыr╟Gо4¤?zэ]∙л┐¤Ц|р¤яУп їЛЄG·?х╣ч┐'Ч.]Ц╦Ч/Ыy┤лоЬУП▄4#п╜Ц/в^+¤╙sцЕН╣╣c╥oРЪюBбШ╫I┴xpцАм█,r╠дн║щШ╝Р7щ|щЖ\зпЕ, В№ы¤пх?■╟ (╡╡╡▓e╦∙╘з>U╡5yў▄sП№їй┐Ъш╣ЮЮжч)зРo╫╬йа═щ{єнЛrу ╫╚Mк{эНwф╫╕Gn√ЁН╬─W^qQ▐{яТ*dУЪюЧ╡ЗПЦз╞m:)з╫╞д┴J¤N┘╣╔ЎАHщяяЧ┐√╗┐3┴^╡╩ Ї*рiбВ╝kп╣V▐x█йе√Ё-╫╦ў╛ ЖМє╗R┐ъГf\Р7▀╣IДя│C!╘╖╦╓╡ЗхиНЄОw{ЪsM Яо█,Зх┤─▄q~є)Ы╢╚Ў╙q┘цWЕжk∙▄∙M3о║ОАiє╥0#х└:w\Нt╧Ч.(▌D√е/}I■шП■╚╘цU3oаW╔O ф▌·б╔ ч/╚ХW╓╚╡╫╓╚ Ў[?#┐№є L>ї╧>"я╜ўЮЇ■ъ╣ю┌л═Wжшa╙l[sY╬ ░IV▌YgS)▐жCЩж▄эЗў╩БЩMrHў╗═╝ЗЬк╣∙єЩ▒j▐i┘zд┴\fVE^ Gdл█L|LdяБ▀t~ыЇKCнЇ°~Й7│█2'З6∙o/(р¤■я ╛tuu┘1╨ByНННj╬keъ№%й╣°}╣Ё╞ r┼ЕЩ∙╟┐Q▌i╣·╜)yщ█╧╔;o╬╩╠?|]╬╧<+Ч▐ЫУыnX%╖▌vЫM%м╡s█X╙/Nш┌░<члЧЭзTp5▌/УЫmа7єВL║5kzЩ═Зхtr┌Щ=ма4b▓ЎЁцЄ┐@щчЁк╜╧хmвї6▌VBи O2║*ё▓\'3ч▀ЦkоzSV▀ЎК▄¤/╔▌√╢▄¤╤Y╣√Ў)i°HRn║Ў%y∙GW╚5╫▀)w фO┌BЪС#зЫфоz▌я}qbZ·╫:│╠f╛·ЭЄдЪp╪mЦэЩ2tWR ЫOj=зT У▓═фн│T▐│╧>+w▐yзк^╣╧рyЫn+шЕ|ёBфЪkоСЦЦ∙ЙПпТo j∙╦o▐(▀x■ГrцЕП╚s3У╙ P/' ■'х;п6╚▌▒{дсо╗MаЮnНK╙▒CbB.яЛ&°╙=>Вц;~└єFыММи kua¤]╥$ЗЭц╒RHг~ч)ЩVAхф ╘шPi·лSВ┐▓н:╜dQ╤@/ХJ═ыт┼Лs╧? №▄╪╪ШщT=ЧL&чКKы╪▄v¤┌n║[;╫?m'╙s*Nrжн▌>╖}mf·▒эv№Ўcyц╦I▀╠kMў╧нїЩЦЭn╢y╙№╥8╢=3,█╒8Єе F_c╗║║ьPu╥█xц╠;4ЯЮжч)Уї█╡╣╛їнoЩ/JО┼b№|@Х¤│fAю╕уinnЦєч╧╦┼ЛэX,╡╒ф╣.\╕ я╝єО▄|є═v Ц╩ВkЄ\·е <АъQЦ ╒Е  ВЄ"И  ВЄ"И  ВЄ"╚|▓э@DPУAyDРAyDРAyDРAyDРAyDРAyTЮ ov@┌┌d╓Цd,.╡╡╡жЛП┘q^ю:╩▒о(Z ∙вў▒6.~зG╒ZО█ ИДвГ<7єvmO¤╨N-╒Ш─;У╥7СТT*%¤нvt%Ф+Zh:A╦Уn9ў%чШ·┌╣┬о?'¤╢БKфK╖еQV█▐кХ╗¤╦aЫСStРзГ░╘DЯ┤┤Ї╔ДюW▌ш╖╪й%ЪЭСд─д╛╬WR]пМОЎ╩bмк$K╡}ЮуйПo▓3dаWР рЫЗе├Ёй╘Р─ж╬┌iEклWg╔2│╖ e}&яD▄з&(лзMцWтш !у2(Эz]Т╡LЮж.┐∙╝╡(f║╗№м ┤й~w║¤;PpЫUчнХ1TZ=jЫ╟╥мжЫЪймe▄¤T√цN╫╠eфдбkSK>><∙X╫;ъ,k╥Нй▌╤:№чkХЎ.└g╟аэ С7:/█:EeNz}¤нчГ6ЮФ╞Гj╝╬У╬╖▀▒∙зч╔?▀№│¤T^∙ВK╖║╤ МL3м╜└ыN╫╪┘└!Р^╞УV▌╞iQЧ┌Бєща")3ъ*|v*&э;%йг<=м^]В=В╢┘;>М|√Y╫+u└Q█,├UАgЦ(╠4єщ4Эй╓■~╡gФыфЄ╓duК$gЬ 'W└|н¤6jqжщрk!█5{BЖ%чх╖мєжеC6ъ|╫yтэ╫╙┤|╙єmз_~чгkHS:╚╖├TPYЫk}й бо╤0y╙ЕVH]oeъьШМ$eu▌FщHкЛ¤┘)Й╡ЗX│Ъo▄Ў%╧~╓╒ЧЄTVлЇЫt┌e─n│s J9&hR∙з7я3u╛5ЫJШ∙Z√╒┤!щJ_ЛnФd╣l'Х ЄLУ_BLл]X9╦ш&╫ёоЎ∙╒<єщZ┘┐_ТеN¤╙Г##"Нa^qlmЧоёa9с[UХ#][Чo?╟$nZЗ$Цш╔~+_═Х ЪтffьMH_ЛS;9Oб┌пRОБ▐ццД─v┘g╔▄┌-┼ф│╙ыЁо▀o>№yЫi▌р▒╨vх█/╕K╬▓a╧ЫbХФК▀ЎЫЪNЮ╔,О ╫ф9╧дщ75MSЧюц╜@Р+{Щf¤\ЪяwкфЩO]ШuД╤a#=8ШlL ∙йtїєd═v{uєЬЭТеоWv┘g═╠├ї╛√йВеZсщЪ7][#Ч╡╝OОшНN4█ЇЪz╜i╪/ц y T г_pц┘/НЎлlЇ:bЩi=S▒L Эw¤'6·╧gjR;m║к╙╡}uЁШg╗ хНZZ?ўц]6>Цч|XРР∙чUp√и╝ЪT*5g√сGIєH╗д╩0,О╩?У╖ Нy╛├йИ#└╦ 5yDM▐К1+│<└КQЮ O┐5XЁЕКH?╧зЫ|│╛x ШэшС¤2IеО╙B╧БиЯC╒╕ю6E!я├юCд╬3√ы,еЦ/:/▄╟R*°╓wЎ/сTрe$S╛ё╓:рзш /S(ш.фw╖[░.┤ ^ДВ▄-╕╩S`щ╖pЛ╚╧╝|╥2▀O7*гC]∙┐Ъd▒-╫ юr▌юхМseЮ┘БI─ЖJ|)LMТч{-їЫ a~ зu╜╗дKZд╧оль┐г╦╖ n?░Ь_Уч¤b╪!С╬0X▌¤ш~╣?с╡Ь~╢jldPд▀,╢Ц! 3&П'D·К·iє}ХЩя╡ їЛ-%kХ■╘AСЮ ~еPыщ╦¤юL lо5_Ь]3ф√#їю▌м¤; G▌╜rр]%аЧ╦M╙╓Xе 3ПЮц│╝WVZ╢╢+k╗|j╙▄eЇ>Шж5OnSм;ьv▐р7╣?│Nяxk,▐)*№RЬяVKoC@Ъю█┐ю╛еє\M c▀┤f╒2m▓?йЇЬ╗■|╟++?▓Є-иidJ╖?юЯ5П_ЮччзЬя∙;Сў▄╚┘^Эv[╙ЫуЫ┐qЙз╖╟ж4^є█NЫ^&/┬ЮЯvYw|╨ў-цЁ=╣у╜√л┘m4у╠:=yмЎ5╜>щцS╥╢(■╟(LЮ{╖╦нС╢є qfГ╢╦_N:зЬ▒~чЩo║6o╜ЯЕрїчмk,ф╣Rh>yмю╪d╨дхм█wЩ<ЇЧЗЛ∙Є°<ьvж╙═b╩1ч7бwM5gО}Yщ/╜oСдя7╞+╫ВВ╝┘Б¤ка╔■╤ йўP▐№uў╩¤Бўr■°┐*ї/9°■И╜╗]∙~▓jXzЇo▓ц╥ЕШЯохЬР╛м_2ыPх[Jtkй^OnЦ┤ЎЙЮ$ъ Cfy╡ ║Ui╢ЇM8√mЪZ╥го ·╖`ЭA¤ ·О^Еz▌г╜Єл>iншС╬┴ёЇ┼╞[╚пм№╨∙Ё#¤^ю¤лy╠б1єхy╬qzр╧:Uчwnи<щ╤_rмзлn(Vащ┘│=&╜NЁ°О{nД=?│╙ №9╕╛╟г╨~Ы_хp╞щЛuLЭ·чЪEНЩ¤∙ y№rФ┤-A╙є▄o╗tpj~&╞п║■u&uг`УРN└yю│4Я╧║Z├Ю+9ыqЮ╧╬$е%Фхм;ф╣f╪╔цaUFjХ0┐4У∙5аPБб>╞f╙Ь▀ln▌╤зюy Я{е╨?9>ХЫS└╩V|Рз #(╕Г,▓>фкАJ▀й·¤Ш╜*ЁК·Qў|?_WфП Ы&Ка▒╧∙┴{?у1[@;Е╜╦4Г*]ю╧Ащ@а▀У/▒·с|ю╧ВН╗┐zaГK╖kэ╫?u6.Йf]sз║ Ъ▀|Е╢Y╦w╝ы(Ў<│ю цy]U.Ш╓Tg}┴T╣╢+&├&╩Ы'╞ *u,є9ЁTш} sюХB╦аsXбЎLЮ_Ац╟ьЛх]зщ2Е^i?■_вЬZ╦┼Ро╔s╗tДvVж┬┤ хS╠ё╩s П~╢Gп╗]F╠Е▓╨EiСФ+oПGс¤V╫7Щ:;&#I¤╗└·зфTАzvJbэvKК▌╞Т╖е╚cT╢эZа░щ.t¤бЎw ╬s}╬┤ ┌рс╡▒]ЕoмsЩ@╖B╘∙>^┬ 5e {&╧ПчО▌нН*Й{╖gю№ВиuЫB■°NZf√╩Ё"Bk╗╙@Ъi^ЬХБ°┬Ъ$▄рu▄4G╧ч<╟зЫduєм║ы/ЄЬ┤0╟+я1ЁЧ╬█By^╠]╜║└╞═A╓A]УikцфнY│╞U╞Щ3gЄвBys}w█!░Tj ╕lГ<Ю╔иАд~Kj_¤Mй¤╤o╩√uў├▀ФЫ_∙ з{∙7фжъ5▌Н?шХы^■╗T∙фT└▄▄Ь▄№▒╧╦═?▒U▐ ┐д║_Ф┌П▀п║╒¤В|р╒}№чхГw▄'W^·о]к|Є bjjjlА%eИ╙ ╗м#>чпЮ╦∙kжл?eFРPN▄&└╗\ЙП /JfмУufь╨тZТu╧РuыH%╓║Фy╣Л▓▌Ю|_к|ZЇїVЁ\a:r р9уЇpyф╡╒╓JнзЛП┘ │╥╓6 │v░dхJ'JОwЫцЧt╫}▄N╚VWУЬNN;~е ^и▓╓╜хфН ■╠╚Бu* ТЗz=▐cй║А├Щ-ф▒+xФYf}6orЎMwХ╞цЯЕьkеє)L·zЮ чА┼зЯ╔ рй■ ─x%╘ф╡Ї╔D*%)▌MЇI▓╙zu╜2:┌+u╬\(]cQ│YфШ:··d╤▌19*╛q┴ж-▓¤p└┤J[╩uWВ▀■╠М╚┘о■ССЕ\Г╫Ў╦┤{<з√ers╚@/М┼>щї╒╦╬SЩ}Zы┘╟S;ыэ╠e┤Ь╬╖·Эrъ╘NХCyL'ещШ═┐╣c╥▀&─y@TЁ╘_¤-┐Е5╫ъ└Nz√d╓нБ│т║ж/.жвOПK╫■╡╔@║ЪnLтY╡В│2╨УРёёД4лс6=c╓▓ЮЇ▓╓бЦkє╘*Ъt╜ыYоfdфИH Ї!┘d╟hЫ┘asў┐╬s1╪$[╢Цг!о|&xtk▄ZЖм┌ЫонЕ8╨н╟uЫЛкя▓▐uЫe║е█S│у─J;╜oN-Q&Ну╥нзЭ╫¤I┘Я^O&/|╙▒ы││xЖU·█тr·t\╘№N-╩№╝Ь╤cы.┘╥tZО╕Q^╨╛Ж═ш@oп│]Б√Я│}■єi!╬╜mщeЭ|wш|u╟;█╝W╚s.kЭ▐уС╔гюуЩ┌└┬yш]п╧ёє[ЯЮ╙╗?Б╟>╪И9WЭ.│Н▐s8│щ∙■╨Щ╟I9;П╙ЯЯMЗфP·C▐ ▒╡ъ╧∙А¤╖ы4╦Щ¤tПб^╖ўxXr:v р╣╦kс╧ф╒╒Kl|J╬┌ACiSэ║╢п_Zu└╒<%╗╥╡2▄гЫcu`╓)2d╟ло┐╡NzЎIЛн-э=лЦMH╠╬3╤ЧФN7Т╦ZЗZnWЧ О╪ic#2╪╡KzЧ{╡во9:▌$wQ╥аоУ/ф┐Pщ ├╢xS║vЁXSRжї┼з!)█qs╙[х╚6{!Q└ф=^Ч╛╦:▓╓}zRbO┌┤ОmЧ├Ы.>▐┤╒`¤╬S╬2f╣&Йя╫K╒╦╬ЗUn$q№и▐■░ь\е·=ыQ▒Т─э6√зDе dжЎ╔нy╩╬K'р▐┌^/Ы╢lЧ╙GFЬ╝╤ВЎ5l╘▀%MзЭ| ▄ Ьэ╦╖∙╧}ЬущZгщ■I┘ьD*@0U╞щtu╨& Яs∙╬-ЫG&╢Й╕¤6ш═ЧЗЩїцц╧┤ ·|╧] cя+}о┌mIoгўVщ╣5Ъj]k╗k╜YZ╔╔c]уiзd15╞[е=ч№Nя┐ч|╤7Mj5╬GcZ▌Є─TИаZш╧о ц xsЧ/:╜Ч▐R +Я╩╝xбВ┤н╢vF<Г╥щ╓╞йаm\Е│'dX<є∙╤╦z╥к█╪!-Г#NmЮwZk╗t┘ic#Г╥╒Ю/сedmЮB[╫═Эяuй╛}лИ7ёг/rX6█S38єВLЪq╢╓@зэЕ─\и▄Z┐eЭ)┘ы^л.Rюvщж5Х║oрM[35v6uw╥є4╧?zX╢o▒ y╓г╫┐╓▌цаtКР╡?юЕWпKo╦iOУн▀╛ъZ╞аё∙Д▌ю<єх=ЇqЎф╣╔3Эпf rОЕb{ЄоO╦{n┘й.,ъ┬7э╝щщ╬я√U!╛╦┌iКYўXR]4▌Z/╒щ╟Р ╜]и╒яФЗЫ2╦mK6═пбZ╗╓<ЧцYOf ╥QуЯЇфeV·zЩэNЯўс{│?OюЧyMЩnУно- ┌╫0у═Е▌y■-я■{╖oд=>)╛╟┴╫┘╟╣A?гцм▄<Ыц=■▌╙ОЗЗs╬Eq!╧-?Ayhе╫Ыu№№╫tю√╥Ч¤q ▐l╫у╜б╔╔c▌Мlз8Їi╡yvЪ▐Ц┴ЧГў_яЛZ╪¤,ш┴├У1O аШРн@Аg■ъ╤ю▀2кIеReNr Н┼еvгL,єпrй¤▌Чdояn;T ▌ьsT╢ФгIмhz▌┐#▀X√y∙│0Б]1ЇўюН╔t╣╙═л@^ъ¤╛@ю6Н_4Л}T`}бЄp)╧ї*┤ 8ЗtPXю`екI№гд~√v;4▀3╧<#k╓м▒C┘nxйGnYїsк/OАз■~Ўд▄V╖▐№╜░ъ Vу▓Э9sF6l╪`З┬л╠ЛK─╝pСпщx┼╚<Ш╜°╘║? }yч;XFцЕ ╖Ys╤,e^.─boўRхS╣╫лГ╞LmZжє√·╚╧╣╒╩р9╙╒ЯЇ▀ЄЙVM^D,╝&Ф├BjЄо ^╖▀iк╟э75э╬ЎяЬи█I╣\sЭ╝╖ъ+fпRkЄЄкA╒a!A^╣фEИЄ@u ╚XatР╖Є`DъэZ8Є"И  ВЄ"И  ВЄ"И  В°Ю<Аёe╚(╩cП=f√└R█╜{╖эЫПЯ5CQtР╖o▀>;Ц╩Ю={ЦmР╟3y0Ю·░|¤╒;фo▀║C■ётЄэЪ;фхлЭNў ∙╦w╪9+Г а^yў∙╡OК№тOК┤нYўqС{nw:▌пэ√єЧЭЮ ╚иАKЧ.╔х9 ь┤C█яйXаGРPЕВ╝Ч^zI║?gцнDа╖|В╝▒╕╘╓╓Ъ.>жЖgднm@fЭйPfмУufь╨тZТu╧РuыH%╓║Фy╣Л▓▌Ю|_к|ZЇїVЁ\лДey■.│<┬(ф%6▌._∙7ўШюю╗я╢KХOIA▐ь@[:р╥]█@еCн1Йw&еo"%йЙ>Iю_a┴▌ёnййй╔t▌╟эДlїw5╔щф┤3рW`V░═ZўrФУ7■√3#╓й№_HъїxПеъg╢Р╟ор9PfЩї┘╝╔┘7▌U"╪Шw|▓пХ╬з0щыy╩Шge═Я,╟е█│ЭAeQ%яVы-░/:╚Moуn+░╘КЄtА╫<▄!)pЩnHbЙц╩z│3ТФШ╘╫й■║^э▌╗Ш┬j│╚▒╣9Щ│▌19кКZЫ╢╚Ў├╙*m)╫] ~√33"Gd╗·wDFr╡\█/╙юёЬюЧ╔═!╜0√8д╫W/;OeЎiнgOэм╖3Ч╤r:▀ъw╩йS;Uх1ЭФжc6 цОIS|Ы,(╬лT■?к #╗Э╙╥?╣wa█Y$рэUЯ└╡v8H¤╬SvЭю╪vї▒Л5╪й└тyя╜ўф╥e ╒щy╦нш яёДH▀AoР╒*¤}"├'d╓4б╞%▐Цйх3MлЪЮЦо¤k┌&╫Б╕╧№icoN╚╕ JзЮч│┐"ыsЫi¤╥ОД9"╥?}H6┘1┌жCv╪▄¤пє▓Ыd╦Ў├r4D╔Юuзы▐g╒&╪tm └}ў\╙m.╛╦z╫mЦщЦnO═ОoS(эЇ╛9╡DЩ4tmВЪv^ў'ez=Щ╝ЁM╟о╧╬тVщoЛ╦щ╙qiPє;╡(єєrFМн╗dK╙i9тFyA√6tа╜╜╬vю╬Ў∙╧зЕ8Ї╢еЧuЄ▌С]Kг╖7x=оРч\╓:╜╟#УG▌╟3╡БЕє╨╗^Яуч╖>=зwП}░sо:]f╜чpf╥є¤б3ПУrNMШ;~╙!9Ф■Р7HLG1чЎ▀о╙,gЎ╙=Жz▌nЮ№y°смtЛ╩ўмэЬЦфщ&╣+(z═╩ЫЬЇчЭwЩсyylщc╖76-зv┼ьgЬ;п ∙йиїэ=╝]Ў╜┘(p╬ы|.░╜@>я╝єО\╕$б:=o╣ф%[:dcn5Z]╜─╞зфмюOJуA[╦7╘%ГЭqжщ@mJv╣╡2▄c╡ёДL╡{цЯ╫ыС--}Nэср:╣╓NqфI{╣╙5G∙ Q ъъ0∙B■ Х.Ї╢┼Ы╥╡Г╟ЪТк╕VWCRvя~з╖╩СmЎBв.╔-z╝ .}Чudн√Їд─Ю┤iй█ш├Ы Eo┌j0ы№XУ─ўыеъeч├* 7Т8~ToXvоR¤ЮїиXIтvЫ¤╙ в╥2S√ф╓ю╬Ўх█┐№чА>╬ёtн╤t дlvо╝ъBoкМ╙щъЛyШ|,|╬х;╖lЩ№┘&тЎ█а7_f╓ЫЫ?╙■ыє=w¤П╜пЇ╣j╖%╜Н▐sXеч╓hкytнэоїfi%'ПuНзЭТ┼╘oХЎЬє;╜ ЮєE▀x4й╒8 tILЕИО└№щ·╨ВЄ▌┘Х-Ўs╚/╧╘Н═├▐єy▐тдР╟:Ё┌&O╬;>a╬╧у√уъNyЧ╧v·Ьє >чH╛э ╕xёв╝7'б:=o╣Х ┼ o╪┌.]к╪Щ9йЫ[mMЬюt═ЬкрmGлЩ[duг┤╪▐╨LSn@┌Q░6Sh╧гkБцNЙ╖▄лo▀*т @№шЛДЦ═Ўn╘╘ ╬╝ УfЬ╜ГUБ└i{!1*╖Dє[╓ЩТ╜ю╡ъ"хnЧn:Rй√╞▐┤5s╫l╖aєaQW)'=Oє╙ёгЗe√╗Рg=z¤k▌mJзY√у^xї║Ї╢ЬЎ4┘·элоe ЯO╪э╬3_▐s@gOЮЫ<╙∙jЎ/чXh!╢'я·┤╝чЦ═гЖШ B<¤zЪцЧЗvEБы Z_Юs7я╣Ъ╡Н∙╢yR·ЯЇ6╙ъЪпэтЮ║■t@м╬4w9▀¤╫╡tN>L'Ыd╦оШLъDяw╙]щїх=. ╚wХ▓ d╖╚╤а┌3W@Ющ%ўжэ°QХOюL╛єым╙*■kЁ╧╕4дk┘ьqЎ;?╒Ї└Z<┐s>р ▄^а╩ф┼╞ЗхDn5Щ┤Ze╡Їх╓─е╗~qc╗лd┌K╔▄▒∙№W}╗ E╘2yГЙMr╚▄йъBZРЎ.]░·N?}gь{Ч░мj▌yш[_ржэ·│j:▄цзуrT┌y/Фy╙)В╗?* uНЙ█▄US│Y]їё}Rd[Бє3╕/H└9R╘Ў╦юЩ╝}"ЙмцPчЩ9щ╪8 eИ▒╘5{ыusnBЯў╝]Шжт е╜ф6╔.▌┘Р}1:▐mЗїEx▐Эt╜▄╒д ▐ єDиш;}╡\╖YHh╙╥┐V▌еЛЮ?.y[55┐e╙ы░ы>g]║y├н0█ЬчтъйA0█ыЇЫTfLn▐,УЕ╢Щ▀mF J╟нARr╙ўNs╪¤Щvj╢{ЪutSRVУн╦▌W▌╘ц4^хДi>}x~═M▐э╦7_╨q╨╠НCц8зє╠>╟?яz\n>┘┴\9ы,Щў<2r╓ыцO╨·ЄЭ╗єО}щflУx╙1╧│k.¤мЭз┘╧╦|. xЮ¤7╡t{ў╩ф╓vХ ївПU7сY╒■∙S,╧zgt{╩}├╡6gЭaщmЮФг▌Gэ>Ф(▀∙йЄt^-Ю╖№ё;ч╧С2m/Vф═й .DWA^]яиLt K│█)╛╟┴\▄▓ПsГ~■╚Y╣yn╦{№╗з os╓|!╧-?Ayhе╫Ыu№№╫tю√╥Ч¤qj▐l╫у╜б╔╔c▌ьhз8╩i╡yvЪ▐Ц┴ЧГў_яЛZ╪¤,ш┴├У1OьЁ═Эn!ыu▓╨Оw&мu в╒╔├ЁBD∙>/╢65-Ю▀9\╛-x{▒"-uM^M*ХRёcЩш╖\{Dоап8йД╟{LЎэ█gЗJбЫ}B<]z▌┐#▀X√y∙│Ь ЄВщя ▄УщrзЫWБ╝╘5·╣ї▄m ┐h√и└·BхсRЮы Th ╩rХР?K~ю╒e╧Ю=▓{ўn;4▀3╧<#k╓м▒C┘{&% оузьP~┐7№╝ь▐PkЗ▓Э9sF6l╪`З┬+ ЛиЩ│ЯZўч┐/x\╠ n│цвY╩╝\И┼▐юе╩зrпWEЩЪЭLWрГк╡╟%jyФOб_╝Ёvz▐r+oM╩bс5yаRУў_╛ёy¤т5r╙ї╫╚√oP▀wН\эUf┌█я╛'o№°В╝Ў╓yуэ rє╒ф Я∙ИЩЦЛЪ<А*вГ╢З>ў∙їO▀ [ ∙╒▓й~N╓№вщt┐ззщyВ╝Еа&п щЪ<PJн╔+ЧRkЄЄJдГ╝┼@РГgЄ"И  ВЄ"И  ВЄ"И  ВЄ"Ия╔(_ЖМвЁ│fT~╓ eгГ╝}√Ў┘!░TЎь┘│lГ<Ю╔иАё╘Зхып▐!√╓ЄПяРo╫▄!/_эt║ ╧_╛├╬YyЁ╩╗╫╚п}RфRдm╡╚║ПЛ▄s╗╙щ~m▀Я┐ьЇTA@\║tI.╧I`з┌~O┼=В<А (ф╜Ї╥K╥}°93o%╜хфН┼е╢╢╓tё15<; mm2ыLНоЩ▓ожFjtў╦▌к┐[О█Ia═X'ы╠╪б∙├Лй*╢Eчщ║RЙ╡VS^/─ТьЗч╕TK>.∙vTЁ\нДj9n ▓╠Є▄lп║.№б╩ksЭиY'╦-╦гмPРЧ╪t╗|х▀▄c║╗я╛█.U>%y│mщАKwmХ╡╞$▐ЩФ╛ЙФд&·$╣w^k√eznNцЎ╞TLьh9▐m?╘╢ыЎ ъяjТ╙╔i;Ф3ьWаU░Р╦╗-╦QN^Е█┐9░NпЕф▒)╪=╟^uЗ?[╚cЫw?*x~╕В╫oє.g▀uWЙ`"o>h ╔ЛJчcШЇїпїф┴йЭїvц2Zъ|XИ·Эrъ╘NХcyL'ещШ═╧╣c╥▀╢░Zа┼╩пуGuсg╖[6У{╡ЎJx{╒'╕Pеч√Cg'хЬЪ/w№жCr(]И4HLG-чЄ├о╙,gЎ╙=╞z▌n∙їЁ├Yы)ъ╕dmў┤$O7╔]A╤lV^хд?я<═ ╧╦sK╦╜▒i9╡+f╟8у▄y¤╧gEнoясэЄ░я═HБ╧И╬ў└эuп иFя╝єО\╕$б:=o╣ф%[:dcn5Z]╜─╞зфмюOJуA[╦7╘%ГЭqжщ@mJv╣╡2▄cГоёДL╡{цЯ╫ыС--}Nэср:╣╓Nqдн┐ЮDLЖь°бШ▌╛(╨5A∙ 5 кЇЮ|!SЄф√RЕ╩╢xS║╢ЁXSRзк`iH╩├ю▌щЇV9▓═№кЁNn╤уU░щ╗м#я╢ЬЮФ╪У6mu█{x│╖PєЁоK f▌1kТ°~╜T╜ь|Xес^q╘▌ сэ╦╬Uк▀│I▄юГ:AT·OfjУ▄Ъд№yэш[█ыe╙Цэr·╚ИУwZ╨╛З═}G┌╔ч└№╚┘▐|√Ы?rщє"ЮоЪюЯФ═╬ХT]╕MХsz=·т&ЯЛ[┐Ця▄┤yhЄoЫИ█oГт|y╝╣∙9э┐~▀╧В ╣у+}о█mKo│ў3а╥sk8╒<║Цw╫z│┤Тs t иЭТ┼╘0oХЎЬ╧G:?<чЧ╛QiRлq>Z*╚Т╠c$бєлыC :.╬~щ шиl▒х@ ┐lq╥ ╚sxmУ'чп0чєё¤qug╛╦g;}># >чL╛эE╒║xёв╝7'б:=o╣Х ┼ o╪┌.]ъу?sR7╖┌Ъ8▌щЪ97(T┴█ОV3╖╚ъFi▒╜бЩж\Я┤uрi╞ы SmJ┐ #─√l^.]л3wJ╝хP}√VO@С;ь╦4Ц═Ўю╤╘╬╝ УfЬ╜├TЎ╙╢р7╖─ё[╓ЩТ[╓кЛМ╗▌║йGн═ў║ю]Чfюrэ6m>,ъктдчi.:~Ї░l▀bЄмGпн╗Aщ!я■╣R╜n╜mз=M╢~√оkГ╞чv?Є╠Чw?rщє┬sLLЮъ|7√Ыsм┤█W╘·╡╝чж═├ЖШ 2<¤zЪцЧ╟vEб╖#h¤y> бx╧їмm╚╫═У╥ д╖fG╫tmў╘ўзduf║╦∙цЗоХsЄe:┘$[v┼dRGz┐ЫюJппиу╢АувR╢Бэ9T{ц ╚C}гх▐?кЄ═Э╔w~}ГvZ┼ ЮуЧЖt-Ы=ю~ч│ЪXЛчў 8g╖PtРЦ╣эЮ:╨jiФ╒v╨Ч[Чю╩t∙ж▌*¤ж┐]FLш{С`юиЛ|Юл╛]ЕЮe▄с╝┴┬&9dю$u!к 0{н @}'Ю╛sї╜ЛXV+i[Є╨м╛@M█э╔кйpЫЛО╦QU╚ц╜╨хMзA√зЖu И█\US│YуЮ&█Е2БЦ ■├юGб∙╩}Ь\a╖/O> unйШэЁ]Ю╧BY═Шц╨жc┘7xЕщf┐╜Ы.╝ЬКw$9н>KУъ<╙∙0йy¤\ЯўГU╩q[¤?н╢╦c╙.щ╫√а■ЭЇФ╛<╡е║╙чн>▐OКl+p>╫т 8gК┌^TГeўL▐О>СД█╘j8╧╠I╟╞∙/CМН╚ао┘[пЫsЄx%в,╙TьУЎьА─═├y:╪ЫР╛Цд╠фз╦╓&┘еЫ▓/╟╗э░╛И╬╗│нЧ╗Ъ╝б╛0?`4БИ╛3WщtЫDtБг▀┌Rw╤вчПK▐VL═o┘Ї:╢хЬtщцўN▐ьSЮЛгчО▀l┐╙klRЩ5╣y│L▓f~╖┘#(╖FH╔M▀;═░╙NM└vO│Мn ╩j▓u╣√оЫ╬╝В╞лЬ1═е╧пЙ╔╗╜∙ц {Ь4sуС9/╥yj.Ё>чK▐ї║ВЄ╤ц╩┘ЖТy╧;г└v╕∙┤■|ЯЕyчNщflУx╙1╧│j.¤мЭў99є╣*рyЄ├╘╩э▌+У[█U.╘Л)ЄЛ?)╥╢Zd▌╟Eю╣▌щt┐╢я╧_vz*А а.]║$Чч$░╙m┐зbБA@ Є^zщ%щ>№ЬЩ╖Б^їycqйнн5]|L ╧H[█А╠:Sс1s`Эм;0cЗ╫Тм{цАм[w@*▒╓е╠╦ЕXФэЎф√Rх╙вп╖ВчZ%T▌∙╗╠Є(ЧBA^b╙эЄХsПщю╛√n╗T∙фщ ╦]║kиd╕5&ё╬дЇMд$5╤'╔¤+4╕;▐-555Що√╕ЭРн■о&9ЭЬv№ ╒ ┤Yы^ОrЄ╞fф└:Х  ╔C╜я▒T]└с╠Є╪<╩,│>Ы79√ж╗JєО╧BЎ╡╥∙&}=Oємм∙ухM'kЫ╫╔┬6∙╕tgmЯЎф╟┬╙/а\∙TЩвГ╝xє░tша+е╗!ЙMЭ╡S*`vFТУ·:╒_╫+гг╜в{W}G^│Yф╪▄Ь╠┘юШUEаПM[d√сАiХ╢Фыо┐¤ЩС#▓]¤;"# ╣мэЧiўxNў╦фцРБ^Л}╥ылЧЭз2√┤╓│Пзv╓█Щ╦h9Эoї;х╘йЭ*ЗЄШNJ╙1Ыs╟д)╛maAMеЄ'╜/*и▀vD╢N█m>╓$ёmеIжМSЕ▄a;Ь▒▌SюЭТJЬF@е╜ў▐{rщ▓ДъЇ╝хVBsн ║МVщяo5}│mщ┌╜┌┌61|ж╓/.║Х╒1&qw8лF╨╬ЯE═█ЬРqФN=╧gE╓ч6╙Lc╣ЫСС#"¤╙ЗdУгm:dЗ═Э┤ўwУl┘~XОЖ(┘ЭВ╒▐%ч╗3╖w╕║ї╕ns╤Ё]╓╗n│L╖║3w╙ b еЭ▐7зЦ(УЖ╛╦W╙╬ы■дьOп'У╛щ╪ї┘Y<├·ВЧ╙зу╥ацwjQцчхМ>[w╔Цж╙r─НЄВЎ5lшЛжЇЎ:█╕ 9█ч?Ят╨█Ц^╓╔wGvэЙ▐▐рї╕BЮsYыЇПLu╧╘╬Cяz}ОЯ▀·ЇЬ▐¤ <Ў┴F╠╣ъtЩmЇЮ├Щ}H╧ўЗ╬iiщУ =╧р:╣╓NqДIcЩ╙5GзЫфо"ю`╘╒aЄЕ%Т*ш╢┼Ы╥w╔╟ЪТ2н/> Iy╪╜sЮ▐*G▄;sU°%╖шё*╕Ї]╓С╡ю╙У{╥жul╗▐PЁz╙VГж└w╖A╫ь╫K╒╦╬ЗUn$q№и▐■░ь\е·=ыQ╫ЙtmВ:AT·OfjЯ▄Ъзь╝tюнэї▓i╦v9}d$sс ┌╫░yPЧ4ЭvЄ1p s╢/▀■х?ЇqОзkНж√'e│seWzSeЬNWaЄ▒Ё9Чя▄▓ydЄgЫИ█oГ▐|yШYon■L√п╧ў▄ї?Ў╛╥чк▌ЦЇ6z╧aХЮ[гйц╤╡╢╗╓ЫеХЬ< lLНёVi╧9┐╙√я9_ЇНGУZНє╤Ш6- ║W ╠ЯоХЦя*═l│▐╧З%┘`б╡┼Oъ╛ьєk▐>ъ ╦dAШЪ╣├▓┘ZnЁэ>Э╙╣В╬├∙ч@qхьт┼ЛЄ▐ЬДъЇ╝хVtР╫┌oГкЎз═ Ў╝╡jЭГ"╔p╡╢w╔аНЄ╞FТ╥╖гU═лЫam░и;]c7>%E5№Ц#Нх`mж╨ЮG╫хЦїэ[E╝И}С0иs┴45Г3/╚дзPнQЕцi{!1*╖Ц┴oYgJЎ║╫кЛФ╗]║щHеюx╙╓╝w┌ЫЛ║J9щyЪЯО=,█╖╪Е<ы╤ы_ыnsP:E╚┌ў┬л╫е╖х┤з╔╓o_u-c╨°|┬nwЮ∙ЄЮ·8{Є▄фЩ╬W│9╟B ▒=y╫зх=╖lщЪ o┐Юж∙хб]QрzГ╓Чч▄ ┼{оfmc@╛mЮФ~°╕Tvz╗╕зо?И╕Ут╗ ║Ц╬╔ЗщdУl┘УIхщ¤n║+╜╛╝╟e∙ю:▐эм╙╥┐Ў┤хЬ_YtMЩ ▓┬ф√&9фY▐цk┐є1шЬv3ЄЭЗ╣┬~Б*WBsн╒┌пВ╜!щС1рyЯ╒╙5pv6i▌!}I5П·7ТьРНnSп[CЧю·┼i°-B9╥иfцО╜╚ч┐ъ█U(вЦ╔L╕ш9j 2чвз/V·N6}ы[,лЕZw║`╒8ў9ЯмZ╖∙щ╕=\рBЩ7Э"╕√гЄ_╫Ш╕═9╬єCЮ&█Е2)╠З▌юBє-Ї8╕┬nП'ЯЕ:╖КФo╜╛ы╦sюЦХ╙№╫tм╪ч╚TАW│Wb╙ЕЧSёШ$з╒gaRЭ7:&UPгЯыє~0┬ЧRйscядXщZ=шMюХ'УъУ@УcБ╡l∙xЪп·╣sЦл№ФeўL^Ы╖ЩV╫ж╡4╩j▌▀Т рfO ╦╕╙л╘╔╞ОдМ─G$┘▒╤yqво^bу ╤-║%+GUoУь╥MР ┘гу▌vXFєЮйЧ╗ЪNKЄ┬№╤*·N_-╫m╥=}ою╥E╧ЧВн~╦ж╫a╫}╬║tєк[3`╢9╧┼╒SГ`╢╫щ56й╠Ш▄╝Y&√w═/Ш3 Ў-╬┤аt<╧■ЫZ║╜{erk╗╩Еz╤ГGПК─▓к¤ЄзXєЄ]╔═cў╤Т│%╗Ц;G├╬SrL6|ЎQ?╖ЧО▌┤їg╟я| :з═Аз╝)tЖ■|сЩ oNq!║кЄ:ТЭNєиi"ЦОГ╜RзЯ╜Л%д┘ОяЩКejЄФ║НТ┘╒ыVу9╧█%;m:║+·╗я╩СFї╙╧ЖШgLL═Г╙m[А╨╧МV■!¤╠Й√╠МъОlХiєа╒]:r┤уф╚╓'╒ЕE]°╘л~╙╙Э▀ўk▀eэ4┼м{,й L╖╓Ku║Uз╨█ЕZ¤Ny╕)│▄╢d╙№к╡k═sqiЮїdЎ/ 5■IO^fепЧ┘ю4хx/@fЮ▄/єЪ2uЦ╛Шщ┌▓а} 3▐\╪Эч▀Єю┐w√F┌єчУт{╠┼.√87шg╘ЬХЫчТ╝╟┐{║└ёЁp╬╣алg╚s╦OPZщїf┐ їЭ╗╟╛4╟e\ЕЗ7█їxohrЄX7┌)N qZmЮЭж╖eЁхр¤╫√вv? zЁЁd,;S|єGз[HБ|ЯwLM═Чн-╒╧░еЯ╒╦ьгk╙!▌№┌Р╬kєТГi:u╓iВ╗ЖXV>5Si~>В╬щ\y╬├"?_@XK]УWУJеT№ИjЄ╪cП╔╛}√ьP)t│╧Q┘RО&▒вщu О|cэчх╧ц]H┐Х╖7&╙хN7пyйk Їsы╣█4~╤,Ў9PБїЕ╩├е<╫и╨■Хх*!Єн╖╪╧аЮ шЩє ║Ахa╧Ю=▓{ўn;4▀3╧<#k╓м▒C┘{&% оузьP~┐7№╝ь▐PkЗ▓Э9sF6l╪`З┬+¤ЩЮРf]╗╖wпй╣Иы┌╛╕М╣5yЎп3▐щтcNJ╣5Д╡╘№Фмш п╜kP:uЦО╬┤:щ=╪'--}2СJ╔ш╖ИиАoк]╫ЎїлP╨#=^uC]2╕_scoNHl╚ОЯPi┘┘P╝вГ╝╓~И╡П85nY┴ЮЗ °vdEwЦw№ъF'ШЫЭСd╨№(Z ═╡Vk┐y&пkpD┬╝Ё╬N╔╕э└┬ф╡ykюL \гм╢Г2>%!^├Шп╡]║╞ЗхсФE╤A^G▓3єrД~╙Ўа}k╢оWv┘чї┌Ю·бЩ7╝VщКIв┘M7A═└╘дRй9█_=╞тR;╥.)єцю╩є╪cП╔╛}√ьXщЎь┘#╗wя╢CсФ■L^ЩНy╛Vе╢SdhЕxхP5A^·н]╙х|э КR5A╩з:Я╔[сЇ3y^┼>УGРA4╫DA@фDA@фDo╫V!╛Bфт+T"А▀о^╦·╖kP>yDРAyDРAyDРAyDРAyDРAyDРAyDРAyDРAyDРAyDРAyDРAyDРAyDРAyDРAyDРAyDРAyDРAyDРAyDРAyDРAyDРAyDРAyDРAyDРAyDРAyDРAyTtРW[[;пkШ╡SЛ0; mmR┬Т(аш /ХJIjвOZZ·dBўлn┤╖╬N@5(_sноЩK╫ю╡ЙS╣7&qoMЯЩGOЫХБЮДМП'д╣╘Ъ@*SРзВ╣ц)┘ekЎR2▄гЫb[евO$ё╕ЪCv├╥11*╜uu╥{0SHM @yХ'╚ЫЭСд Jз[У╫мkщжфмЮV╫+√ТjZ│ wTЮYT╛цZ╧3zN╫/нvR]}╠Ў`1Ф'╚ллЧ╪xB│├Y╞$▐)2ФТXв╟>лg╣╡}(л2╒ф9╧▐%;▌/Tg╛E┐xб#<]лзцКIв9о╞*u╜▓л╦iтх┼ АЄкIеRs╢Uт▒╟У}√Ў┘!░╥э┘│Gvя▐mЗ┬)▀3yиyDРAyDРAyDРAyDРAyDРAyDРAyDРAyDРAyDРAyDРAyDРAyDРAyDРAyDРAyDРAyDРAyDРAyDРAyDРAyDРAyDРAyDРAyDРAyDРAyDРAyDРA5йTj╬ЎгJ<Ў╪c╢└▒{ўn█A@╤\ AyDРAyDРA╝] амj√Бэ╦/╡√#╢(;*Б п ё=y╚Uьw#-% ъшгМBоrФQФхGРWЕt║o▀>;ДХn╧Ю=yи*ФQЁ*WE┘Q~<УAyDРAyTЮ ov@┌┌d╓Ц╫м ┤╒Jm|╠й╘m+┤\▒щО┼е╢╢M*УIYК~╗╢н╢V╞m┐гKЖ&eП╚┴╤^й│cC╙┴RЮeg┌дyjЧд·[эШ▐tВ·эмб╕╦щё[╛Фtuа╖┐Q& ,У¤ц┌qщо┘,ЗM v96wH6Щ■\▐∙┤╡╥?}Jv╓█A,[∙▐\√▐ў╛g√кGь ╜╥ЎхЧ№ХK╢пz|Їг╡}K#S╛кr5╒/Бе^йхZЩЧQe╧JSо2j9ЧЛе╪2к° пmа<П+я▓cп▌/Нг╥[L┬▐4K▌╢B╦ХФооХlЦй])╔│z ╨у▌5rt╦ЬRС▌╠Бu╥Р|XцЇ└<║а=*[Г@,WЕ ╨▄zлк╖ў┐j√Є{)■█W^}хХ%ЄBЧ+еЦke2?╚гьY╔╩UF-╫▓c▒ФRFХ Щ<]°и╗╤Z╙┼UШцОn│уtч6[каз'!уу iVу█r█2╟Fd░еC6ъRLзы6ПЪu╕iы└Iїззчд∙╘═\'тю║k┼пх7k√╝═░є╓kчiVы03(▐ёю╛┘х╠z▌mнУН-ТЬ╔┘╧@╟хшсэ▓┼ЦЬїэ[eэф 2у И▓м2─щЬ▓╦з▄Ї+Г╠м▐ёкє+╦ЄЦYЦ│тГ<[░д Нм┬`Lт*°Й е$ХJ╔D_R:mDU╫;j╞Щn(&Й╟ї°:щ=╪'--}2б╞ПцT╫═╬$ееcгsзZW/▒ё)9л╟ЯЦXЧ╚ИI·мLIгм╓╜FNЪ▄b╢yк▌]wЧ юўqЪ*▄z1▓█7s╓У-{▀RjщёS▓╦.ЫЪшРсЫ~z╜ЩfЧ║·ШМO═O=Ф·╗дщtRжэр|ЗesMН╘иn▌B┴ХB▀▌USЦ▀▓K┘U%▀▓+╖▄<PХз╠ З▓┴№>o~]X~╦оДо┼y╢`1ЕГщ<Е┴ьМ$╒ЇvD▌╞iQEКт╜sьI╬dZ╡J{WRt%╪┘йШ┤яhФдОЄЇ:cїN ─│M▓║╤t:АФAщ┤kk┐OЧ│oiz╝Y╓юЫос│┴h╓z]z¤Eя{Ыф╨▄Ь╠ЩюШ4┼╖ emЇщк√jы┬Є[vй╗кSиь╥В╩аrХYQЎ Ш▀ч,и ╦o┘Х╥kq╛BEx═├╥1a├ЇeqT'Sg╟d$┘(лы6JGRРgз$╓^tйФгU·M└┌.#жр+▓йBМББo.╡╜уЕВ╥ 3/╚ф┌Ш4╪┴` [k{DЯ_д╦;yЮb╩мвPЎ╒д╝AЮiRMИiЙUt│ъxW╗Sx╕╧╓)f╝╙ыpя"sШж═сщZ/]3(√ўK╥4съч█DFFD3m╡i·RAh▄<ФвГ╜ щkI╩╠╖ЭIif▀ЖхД▌ШЇ>фьs!ж ┌wГ¤lТ-█╦╤у╬╨╠╚9▌tЧШЧ╓f╚║Ъn▒УЇ╥э╠М╚С╙Mro╖╤хЦqAePk╗ty╩м┤"╦мВ({АкUцЪ<$MЇI▓╙ih╓╧╣щ╫HыzeW,є,_╧T,SУззu9Mє^╝╚-дTсд#л-ъ┴┴dc:xLєж∙ФєтE^:бD│╙tQ█,├еўуvZЪ┌7¤,asю>dя│щ▄ЗЫчЩХ├ул_П╖щ╨1С═╬│. ё&9ц√fн╥УI;_MC\ЪОёж░▄┤ЩжS]VhM╚*7WФA┘eVцe▒b╩м({АкUЇWи,╢╨▀У╖ФЇ=yXщЄ}=A5к}ь╢/┐╘юП╪>, ]■М┤Че,еМВW╣╩(╩ОЄ[ЬgЄаоўаЇ%;K ┼Лjб ╪╬дЇ\Ъя╡░ЄМy╛~е╢SЬЦ+F╒y*╠У▐╤╘Єп╔kэЧTк╚/uАhэп─╦ЦЛeфаXU L▐JдЯw╝x&╒Д2 ╣x&п:фD═╡DРAyDРAyDРA|ЕJт;иАе▒Ь╛Пp)QFKг╪2К п ёу▀└т+╫ПмпФQ@ё^{э5█W¤╣+╢Мв╣`№┼_№EI]йЄ╔Ж КъВ `С╠══╒-A└"ё ф>°┴·О╫▌BTgР7; mm2k▒3d▌║2cЛ╢╨х└eVи╦Ч/gu╖▄rЛп цN╙▌Bф╡╒╓JнзЛП┘ ∙┤═Ющ╪(u▐х№му╥ЭU░йсЪй1]╖╩63rDdk╗╘█сТЧ╫jz╛Yw└M! Є┼о╧4OШe╡Е.,Н┼(_+G}╛"_6 чне√╨З>d╟:Ї░w║юв°Ъ╝Ц>ЩHе$е╗Й>IvЖ,ИКpvJ╟xuv╚кыХ╤╤^╔╗в╠Xз Я═r╪k╟╗7ЛsNДщ■I┘▄Э]4M'u9шг Z^ї4┘∙ццОIS|Ыш▓4▀ЄемO Ъ'╠▓┌BЧG4МПП█╛∙ЄM[RЛP╛V┬J)ЫАrxя╜ў╥▌ў╛ў╜yЭw║юba═╡:Ё╥╤~ч.rvа═s┌&NЬ Ї$TбЪРf5╛MП╘wЮєцsН╔╚`LъыrЦ█╗╫╣[5wнqЙ╖╣w║j╛t┐M"o·╦W¤╬S27▌/kэ░*Цфшсэ▓eУ3T▀╛U╓N╛РugzЇpУ▄eoХ┤№жCr╚╬'╥ 1УH■хЛ_Я4OЮe═Э№:S░Ч┤<"чї╫_Чп|х+Є╒п~╒О╔╨уЇ4=OUлH∙Z+гl╩├╣! ▀-─┬Я╔ллЧ╪°ФЬ╒╜╜г╬июЖbТx\G]u╥{░OZьъhяYЙ7O╔оЇ▌jЗ ўxЪ╞Fd░л]ZsЧ{└i│6╞У╥xPпгK;{D▄~SОхO?╩ъяТж╙IЩ╢Гr№и▐╛E╥х_!aЧЯС#▓UьMxFюЄЕДЩ?hЮ░ыZшЄXЦn╛∙f∙Г?°╬ Їt┐ззщyк^╣╦╫еТ√yЛB┘Фшп ·пЛъв╝/^xя ;EТ3є Ч┘I╩аt║є5ы╗Pз╙╞Fел╜╒hщ╙Ъ╗║QnЮ~=н@·+╔ёгЗe╗{kZ хПKwГ*FЯ▄щyЦfЙ╒яФSsзdg╒lкAnа╖ь╝\e(_л┼К)Ы?√│?[T╖ Єtб╥╥(лu╘<,юд║╗┤│╠у}ю─t¤тДu│2УlС╞╒fаtБщG▄╠ 2╣6& ╬А╝0╣Vb╬@8Ч╫&яХ╪t@@Х╡|aцЪ'ь║║<Ц5oа╖,╝▓ЦпK(ыєС▓ (Сў═Ys3Ц╙yзыn!фщж╤Д─v┘"▄Z5E┐!Ыїh│{7iЪbZr═ЮUМе╙0К╜ ═Ч~фlТ-█╦Q√|░~█ьt╙]╬]lP│EЦ"Ц7╧╜хвyЦўf■аyЄmлўЩ╝ЦGд╣Б▐Єл┴+s∙║иЄ}^Чk┘ФЗўy╗■ЁЗvмC{зыn!КЄь╛N─╣_╒Эe┐╛MмыХ]▒╠┤ЮйXцNSOыrЪ┌VK┐}k,╣┌╫ ╙_Эт,Х╜▄S┘м50¤х╬╝╓Ч╙зу╥PS#·хпMЗОЙlv^√oИ7╔1√▓.д╠╫ Ш!╟BЦ7ЕЮ·op┐b└∙кВах╡b╓ч4OШe╡Е.Пш╤┴▌▓Ё*X╛V╥J)ЫАr╚ т^yх3^ ═Эж╗ЕиIеR KбМ╞т╡2╥n ╡ь▒╟У}√Ў┘б╥яоСг[цЩHе$е╗Й>IюПX Q╫+гг╜Rg+b1╓Цеъhо=;%у▒z'X╤5Gщ╛╕д+°В╞╗▓ж╖╔АНg┌ь8╧x[;5w╟╘$·н╙,Чx[ю▓│2╨УРёёД4лqm{ўf╫▓∙жS`ь<Щ4▄¤VыR█0ЦЮЮ│юз~hц:сЧvЮmI┘Aщ║ЩЪ├7ПН)y<ЭOЮёY█рЭФ╦╥y6p0·╬дЇэhU#╟$▐ЬР╪РS├7╤ЧФNЭНwщщS▓+]3╪!├=N╨R╫;ъМ╙▌PLП█х╘·з┌▌ё]28п&1╧:╟У╥x╨│lзЦъдў`Я┤╪╩╤nqц═ЫNБmилЧ╪°ФЬU╜│'Ж%╓%2b=л┬зFYн{ Яu√ж](sхд█ы_gЬ╟Щ|ЪшIШc|м@∙TIsн╜╨╧╬HRН7ёЮR╖▒CZGd,h╝3и" 5]е╙ U 3nГгмZг╬AСфМPx╥У╒Н╥b{╙Єн│еC6║ёNk╗tй╡╧E)y╙)░ ╥*э]N┌gзb╥╛гQТ:╩╙i║5ЯA№╥.ФПе ╠уL>ЩuщcТяXА▓йОцZOНU╔TЁТM╫/н:°hЦО 7Шьє дкЫК╧dъьШМ$eu▌FщHкаььФ─┌▌nЙХТ╟~╟╩NхQA▐╪И ╢ш F{ q[√tхxW╗┤НwmРШЩЮ┼SЫdЦsz +┤NЧ┘vO═^n░6Э║LЎяЧd╟FйS Їр╚ИHcжн6гPаЬo[<╦╬╦зBщЖ╚уЇ║Є+P6UЄLЮ╚РyK┤U·їЫ╢Э╬°цDLЖ·u4▐Х=▌t·┼Б║^┘╦мзg*VDM^Юu·n╗в╫╫х4E║/?▐ЎTPдгжEщ┴┴dc&иt∙о;W└╢иeъчєЇ■и.+Я╝щ·╜!С/П=∙╘<▄!>╟╥t▐Ч>@YЁЛ┼╥═У="∙ъP┼°э┌bфАeаш ╒п:^╝@YфDA@фDA@фDA@фDA@ёЛUh┼ tX0В╝*дГ╝3g╬╪б┬╓мYCР▓Tо╣V Р█А╠┌┴ВКЭЛГут/L╛ЪЗ╧аВКЄ╕╚,: инХZO│╙Ц3oа│A╧bпА2с┼Л(kщУЙTJR║ЫшУф■И(u╜2:┌+uv░"cT@YВ╝┘Б6OНQЫ д#Й)y╝═g|V-Уw~T╠┘)П╒;┴JV■╟%]┴4▐p▄|П┐нїИ╗уj¤╓iЦНK<}ю╕╦╬╩@OB╞╟╥м╞╡э▌Ы]╦цЫNБm░єd╥pў[нKm├Xzz╬║Я·бЩыDб¤│°М[YВ╝║▐Qз╢HwC1I*ф═╩@[н╘RЁ@yш`оVХлY]\╞ьфТ СU╣ oЎД KЧ·7,'(=ад╩MФ┘aGЧ M╕eg╜<юW╛9Е_Ж.╟F┌ЭёnYЧ[FчrЧ╤eе╖L5e╣╖М═с]WP┐EY фWlURРWp%YЕЖкЎKг$е9┴╓а.а▄А)╖░ёZ╣у=вQ╠▓~iе ╔╦юАх,T┘Va╖!╖№rх+я▄▓╥Ї{D▌ьлч JSє[жPЩкн╦ФЯ#╥о╢Gї╚H{@``ЮR╩иК4╫ъжZ╖·┐╓r~M╢н╥oЮ'iЧ3_жYб$gзrюnЖ ║tЛД}ОoвOZьд@е,гх]оU┌╗edlLF╗дЭии y│rbx\║Жь\wC]2>|"є╝Ыж В╕yC{╥╫ТФЩoл┴ё)9kfP│ш`╤Ў{Щё]эЩZ<н╡]║╞C╝фQW/1А>n#╩м┤Bм[╞Fd░еC6╬╗хА%Цп|єФ[є╩7O┘Ч%▀2∙фYоuGЯ$;;%┘╖#╗ PvхЄ╠[╡}▓├√щї └Ta$Йf√`n│ wФ▐ї╜r░/)ЭЎБ▌ЮйXц0]3Xы<П7пО_ЛC1Хд3Oн╖й ЛЪO▌Y&;mZЙШ щ┤ъ┬н█<ют╫мK.╕|█╦ФcYхЫЮ╓5h╩╛м/Є-УOбхъ6JGKЛtpз T\eЮ╔л>їЦD7==п 2Ц┼3yQа╦f┐ЧёфU5╧ф-Х1є ┐эtНOЇ@UФо]x└bИnMБЪ<╒l┼╫ф└AРAyDРAyDРAyDРAyDРA№мА%ё╠3╧╪╛╩┌░aГэ╦ПЯ5рЕ2К пJ<Ў╪c╢ИО▌╗w█╛∙t║f═;Tg╬Ь!╚PТ(ФQyUBy√Ўэ│C└Є╖g╧В╝"Q╛╒# e╧фиwмZ╡аn%ы:┌k√№Ъ╛▄}чmС°s"╜у"/╛iGeЎълпJ2ЩФ╔╔I∙√┐ {╙M&зфЫУ/╚ №√)∙Я╧ г№▌є K&&ЮУчЮ√[─НЫю[▀·Ц╠═UзVy░╠╣Ь■╘╣╙гц▌╦"ЗfD: Jф/ Iф№PфєзDЎ Г╚П/┘ЩА2yщеЧфч~ючdєц═╥▐▐n║ўэыЄє яЄ  ┐?Т_xjP>°їУjzЗ▄{я/╩g>єK▓n▌/╔Я¤┘Ч7▐x├ж▓xЄ`Щ▄2Р■╘╣╙гDu┐°Ч*╚ЫV┴Ю'а{O~rNd╦IССя┘С@\║4 ╬с▓:▀╝.█╗Ъє╟x√э╖|Чн4В<ИАB\Ф╝o╜%Є┼3Nєьў~lG·x∙]С ¤Ы"█ З╚,M╕(Г╦╣Эу╣Ю■{ЕК┤ЦвйVлЮ ov@┌┌d╓вH3d▌║2cDЫ╖щuўя]Чю┬ /g ╖mЪ=ї▓┬ЩЙ№O9╦ц5Ч┌┌Z╙▌ї/╣!ЬoХЭRsEН ьо╣FфълEо╝2кAЮчc║°ШЭ(UхЮ▒У╕╖╠оmУщx╟╟╒PйВ╥я3я√б┤\ўкd_V Л]¤ЪмU╦S█╓ЩФ╛ЙФд&·ф╬ў█╤┼Єл░Ё\ █|vx,ожQ╔▒,\u╒UЄН▀ }9∙■Яж;}рА№э╟[фў Ew║{n╒зх?№Зo╚я№╬I∙?■ПУЄ√┐ W*╨S╤▐иXР7;╨&╡Э"C)їБ▒▌РМ,аphХ}╞о╦SnПJoЭBLan╞MЇ%еsA7ьє╙/F▌-7╚oL╔╛№kr█╖■┴О vыў╬J╫я?(_·ы├R ┴ыэX│3ТФШ╘╣=щ╧s-═┘aрэWy╥bЗQ▌tsэgо╗N╓_╜щ╓ъъ║ЪЯРыо[Яю.]■и╝ё╞gф═7╫Ыю╡╫~F/щ$░╚*фЭuG╘/нvXkэ╖├·N'}'чsWш7▌▐ ш;Ю▌IV9█ьzа╗Fjj║х╕;оFыnЭpЫ╝утr┌О░▓<Ўя▐IwaЖЛ3&#Г]╥n є║Н╥ТЬ╔йurjш╥╡Tж /╛Ц.М7■ў $╫э┘'Я∙█пK№█$[■╦┐Чў╜∙кЭЪq▌█п╦╞?∙OЄ╨┐oХ{╟ Bn┌ Ях═ ╨gз╬oN╚╕ Jз╛юtЭТwэ°аыХй╚HПwўuVzT:у iVуu~МНш┌┴ьkбKз▒┐qBFw4┌1иv╛═о!ZbC=╦W ЄЖ╟ГюИTaа>L▒└╗┬<╙╒gк]Пў └D╞щ╕$╖╠йУщРlRa^wCRV'Ц>╣цж╖╩Сm·┘;=>.M╟▄ё¤▓╓.`e╔}цо╨p~6╨▒A╩[╜gЄZe╡э═вл─╒`Зўоp╨╙МЫo║g|днэЧ]Ыl ╠ 2)Зe│╖╞юtRжїxя|░`*XsЧ╘Р─=сkуъzхаВjЫe╕у`@А╖АЇнП╒^#╖╘}O^¤%С╖ЫпС\qЕ№є7_ЧЮ№П▓¤С-к√┘Ў}I>∙┌ПфCj┌;M╫╚П~щ ╣╣с;Є╤ЫЛ╝фх╗yk°:Eц╒n║<═╙*РLvъ┌└Y91<.уЙfgy]Лиk ┌вї7Qдп├▀xч9∙Ў█ж√╦ы╫╗┐#я╝s2▌]q┼Kr╙M▀Рoк.gыж ╬tW¤s╖_)?ъ╕^о·▄√фоЯ╕F>°┌єrq*]Э░0:└k╓╡ХЩр-╘о╘mФОЦд╠╠╓Iяи]╓]^╫■НFя╦кгц╩+пФ╧|щK▓■▀■[╙}ЎбЗдхКя╦ЧоKж╗!▀ХН?#?ўsыM╫┌·3&8\К┌╝Кy;t═}sЎ│scq5lк·bj╢Х┘├2▐╒Юi~-4}ей┐KЪN╟e┐y8╧├М?"#Ў∙╝ЩС#<УмP╣╧▄4O7'к┬╫>v╙*э]Г2т-Уcї2┐вN┐Эк_0╚SCчЫ╛щ╒UЫdЎ_■'yыЎ╡Є╤ЫпТOм╛N.№оЧT█√ф║ЫоРыn╕B^√╫╫╔;7╚к╗оУП╫^%я|шSrохw╠▓E╔w=jщРНv█═xз╫Сn╬vЄn┐Ы%ю3кЗ▀│uЧ/go■П▀═Щ@o▒U,╚╙╧+Шч▄ъl╒uК■p8╧n$;Эq═ЙШ ї{C╕B╙WЪMrh║_&7█цZ▌Щя├SуП5I╝┴╖-┘─3y└ Х√╠]бс@л╙eпnBМ 9╧?╖ЎЙ*╠єФ╔·┼ сщ∙UоЯQ╦╣╔7╥/╞m╖▌& ЯZ+o·!∙╬OяТ+j?&лo╣Z>№╔kхїн7╩k┐|г|ЁЮыдс╓лхъў▀&▀¤d\^_╖Gъ>ї3f┘т\ПъzeW╠y╣BПяЩКejЄЇ4╪щkЯ~ц░╡B:Ж▌fY]√ёg╩#о╘(ЦъЩ╝ЪT*U╘Ъ_yх╣ї╓[э╩х▒╟У}√Ў┘!`∙█│gПь▐╜█═ў╠3╧╚Ъ5kьРуОUлl_i╛u■╝эsЬ9sF6l╪`ЗЄлЖ▓-ь6x┐OНК7x╙╡uЕЖгтї╫_Чя~ы╝╝я┼?Ч[f┐*ЄЮєNьW]+?║учфї╒┐ ╖ ─Є┴~╨МКсWF}єЫ▀ФЯ ∙Я╖CО┐·╧Зхgо╠№d┘7.каюS┘Mя ї┐ЎHwwп|р░cХ.гЦцI@@╔*√=y╦╟═7▀,Н▒&╫¤Л_Хo¤л▀Х╖ok1▌ЛЯюУ+Z╛ ?┘ЇSx(+}>э▐╜K~ы╖~S~є7{х7~гG■ф■N~уя┐)┐9∙ўцяЭz^■ЇO{M`ў №?ЪюЪkо3╧є-6jЄк5yИjЄКG∙Z║w▀}W╛√▌яЪ■П}ьcrэ╡╬ЧаеЄ+гt│ыo╝!Ч.ejю┬╨▐M7▌4я╣"S_)"лdрFPмPк,єФщ╡╡mвЛї∙╝є┼╒Р+h|йk=└ЄU▒ /▐<,)Iеt7$▒й│"u╜2:┌+uvЮкUжэЬhSЕLз ┌свЬ{QждQ>qзъ┐єA∙ъW▌╗фкi[░"= №є╢o╛|╙P]2d╩t▌НJпO!9я5УЩgв/)Эq'╠ К╧НwE╓DLЫkcRЯ.Zе┐┐5єA╡тю▌Vнд?ЗzZ·.Lu╣╡`Y╙├▄Iк┤O9cO\Я╜ыs╖╙╠РУЦЮ!╘6шXqTR}╥bЗ├{VыХчф)щ╥═╡э█е]╫Ю┘Z┤'║ 7бц╥kўзЫtWЩ╢sЄ─Cj┘чХїj▄¤Oф╓╣й┤▌∙UЧ°gь╫LЪvЬ█4м╫ЧЮ╫Y_vM^NZ╬yЦ╣_цнXА7▀|S■°П X▌ДН┌1zЬЮжч┴RУС┴.iW┼╜V╖▒CZТ3к, пД,O│Х░`кXР╫▐5(Э·Ct5ЮРйvчn+5╘%Г√uPекцД─ь]╪№IOЯТ]zЪЩ▐!├=9Aаh╦▄╔щоЭb}єя·|╥j │ uпЇЭ|Dю╣ч9y■╝Ь ГO╦╡vКирm║MН;▀'ўкАъбGeP╧г║┴╞iyQюФ┐ЬYЎлzы▄TxЧи╠№║ы√┤ЭNSuГ╚Sжi╪ 4э╝'ЩТом}╥║W/3-_┤├чO▐'O?D╙.╩ч╞oФGyD╞╞╞▓=▌п╟щizЮХ(ўн┌╩╝ek╦u╒ЩGp йлЧ╪°ФЬ╡Гiщёe*O оXЩ*ф╡Ў█m√ИsЗЦь╡Ї╔{╖%лЭ`nvFТ▐ё╣ЇtO!Sл┤ё▄Ёь Я4Bм╧▄ї Оиb╟ЄK+╠6TТ ▐║я╡¤w~BMmЯS╦voЯ №Ь)■╬}MЮ╧Є.oЪЯhР{Ї_▌\ьччюУ{Юujє4┐┤L│н}╘ЭоН|NЮ@∙фzxО┴-╢/├o\X╬у&N9чtн╥яc·ЬDO╚Z╖<ЄЦз*╠ЯРf;▄6Ё}3А№*╪\k╡ЎЫб╦<9;%у╢7Р ╩&╥Ню·U╤│╚кaМ{е╧╘Ъ╡╔и мlУъRrk╙]Б└(Б7╨#└╦Ёu Ё4є╕Й-уFч=|╖Z├<Зbnд╒▄9╝у╦SOPй[u<єНЎ▐fцH │`кXР╫цн╣ √AkmЧоёa9twhк▐Єx╛h▒nгtHБy\9щ═ЮЦёоЎL└цЧVШmX,чЮРДyшM{'хС{жфE╖m╘пэ╬╧╔}Єи  ъZ┬ч2єЮ√┌╙Є▄mЩА═/нЬeАJr=╝E2ў<╦|BЖ╟эs╫цЩ:ў-╓Vєи╬И╖LН╒K]╨°Т╦╙"╫у +N┼В╝Оdg║к┐V┐i{0╠█къ╬m(&ЙfO╜ЭтP╙╒]▓╙N╫]юЛj-╜│чq_╝Ш/;╜цDLЖЇ "i>iНЕ┘ЗiюЁ43=ЮX2T╔гыэKыхщ√╛,ц╝;Ф/>р4Ыf┐xс<п7╒eЫSUч╛x1Яє\а;яz¤ь_Я╖N╬'нg│Ч1_╖В ╥┴▌JЁо√▌▀═ъ■ы?о2]ю°[▌Ш)ўT╣Єo┴hэё)S¤╟З/Os╖`eкIеRs╢?ФW^yEn╜їV;Taъ╬▒vд]R+рC·╠3╧╚Ъ5kьАr9sцМl╪░┴[╘▓-@)█6А{ч╖█ЎXОJ)к.╚Л╫Jg·ЛхЇw2-╒єnЛЛ иМиyVЖR╩З╩┐xQдЇ[╣ж[@╣U]РАЕлюgЄV▌\ а2hо░▄ХR>фАBРаЪХR>╨\ AyDРAyDРA╝][%° аr° ╦])хA^Хрg═А╩рg═DA)х═╡DР╖ ╝№Є╦ЄO ЇOщ┐AЭЮо;В╝eрЖnРЯ·йЯ2▌з>ї)∙щЯ■iinnЦ{ю╣╟tz°УЯ№д─b13/╧фUЙ|╧ф╜¤Ў█RWWg·kjjфК+о0uз═══╔еKЧLwю▄9╣■·ы═x╤&я║▀¤]█Ч▀;┐¤█╢└r─3yж9╖╗|∙r║_╙A▀ХъH^ёюєf`yy│╥╓6 │v░h ]╛ xГ<▌y=▌╜є¤гrхuw┌╣<ЫРUлVЩ.ёмWnчЮР√яB╬┘AХеkш╝▌/▀}▐t╣ум<Х Єt`U[+╡╢kP!╓r ╢╩▒Э~√┐щАюЄ{rёїх┬'ф▌№╡\x5)я╝|Rо╕ц╣Є┌Ыy№=+Йо)yффy9ЄЩ·JБX%7ВBаJНI▄SЖ╒╓╢Й1цЭ/оЖ\AуK╡XывгbA^╝yX:&RТJщnHbSgEъzet┤WЬз╦кX9╢єьФ─Ж<√Яш ( ╙БЫо╣{ые┐С7■ю1yєЕ?Фo=▒K■юб√фе┐╪#o═|E▐∙сфтЕwэ>╬╜(S╥(Я╨Х}w>(_¤ъГRа▐oqT╙╢`Yx■∙р╟ЄM[ ║ОЎ┌╛ь■╥u╔Р)├t7*╜>ЕтX╝S╘LfЮЙ╛дt╞Э0+h|(>7┌YqlоНI}║@hХ■■╓╠╫■И╗w_╡Т■\ц╘А╒ц╓иeMsgй╥>хМ=Qp}Ў.╨▌N3CNZzЖ0█╨┌/zЧле▒┼ЎЦш{╔crтя HZ'╟фO┐єВ\лУ■R╗\м█"=Ow╩п■ё╡2yr└Гє=+ЙїП╩sЄФtщц┌Ўэ╥оk╧l-┌ ▌ДЫPsщК╡√╙M║лL █9yт!╡ьsП╩z5ю■'rы▄T┌ю№кK№Н3Ўk&M;╬m╓ыK╧ым/╗&/'-gГ<╦▄/єVПх═7▀Ф?■у?V7aгvLЖззщyV"┐ао<Б^>c22╪%э╢мл█╪!-╔UvНWBХс╣JXА╩yэ]Г╥й?─AwUу Щjwю╛RC]2╕_U*аjNdj└&·$;6╥╙зdЧЮfжw╚pONиЖ┌2wv║ы_зFЗX▀№╗@Я┤Z├lCО┘2,▓▒─к┴╖▐|C▐·╬╫фю "┐y┼З$vщj╣0wYо^╡Y~№╜ ∙х[■D:n·orще1∙зя╫.хuпЇЭ|Dю╣ч9y■╝Ь ГO╦╡vКирm║MН;▀'ўкАъбGeP╧г║┴╞iyQюФ┐ЬYЎлzы▄TxЧи╠№║ы√┤ЭNSuГ╚Sжi╪ 4э╝'ЩТом}╥║W/3-_┤├чO▐'O?D╙юJvуН7╩#П<"cccYБЮю╫уЇ4=╧J4╕e└tо▄с╥╪r\uб9йлЧ╪°ФЬ╡Гiщё%ФЯ~ оАV▒ п╡▀~И█GЬ;╢▄`пеOv╕5]лЭ`nvFТ▐ё╣ЇtOбSл┤ё▄┤ и|╥▒>s88вК!╦/н0█РEj*─;Xzєяkп╜*5я~Gъo¤Є│7O╦Mя\Ря╝°в<ъi∙▐╖^ФЯ°щ_Х╪-?Р7▀╛,7▄x│]*$╝u▀k√я№Д4Ъ┌>зЦэ▐>°9S№Э√Ъ<-Юх]▐4?╤ ўш┐║╣╪3■╬╧▌'ў<5ъ╘цi~iЩ&f[√и;]∙Ь<▒ТхzxХ╥*¤n0╢└GN╥ЄЦЯкм╠ЯРf;▄6Ё}3АтT░╣╓jэ7DЧ7x rvJ╞mo ФMд ▌їлвhСЕ▐]hэЧ╞  gY┬·╚mУЧ▐°░№¤7пСs▀╜Q▐z3е╞^Цыj^ХV╡╩{o}O.▐v┐|Ї│ I>Ё┴[ЬЕJrпЇЩZ│65БХmR]Jnэc║+xbEЁzxЛ!ф#'ц╞╣Q═Э├;>░№ЇХ║╟3▀hяmfО┤0ыP╣ п═[sЎГ╫┌.]у├r"шn╤T┼'фё|╤b▌FщРє╕r╥Ы=1,у]эЩА═/н0█аЩчNрi·KПWЎ▀╩KЧR.╬]#?~√В\╕pI._╝и ▐_Тц _Р5?√амZ╦єvmчЮРДyшM{'хС{жфE╖m╘пэ╬╧╔}Єи  ъZ┬ч2єЮ√┌╙Є▄mЩА═/нЬe/7╨#└лР▒╕ч┘х2р4Ыf┐xс<п7╒eЫSUч╛x1Яє\а;яz¤ь_Я╖N╬'нg│Ч1_╖▄рU╚ъ╞L9з╩т╪РЛEk РИOъ?>\∙щз╕ї╨к√g═╘ЭdэH╗дV└З6▀╧Ъ╜■·ыr√э╖Ы~яOЪхvz┌╖┐¤m╣ю║ы╠╝в │f╣▄7j■╥АjЙЯ5є|═Iн~▒Х╗▓,Еjь\Ы`Y+╧[╡ва║kЄVР|5y?№сCo║6O╗хЦЕ╝|D╦Jл╔MСи╔├|:h╙╢Pзч#└A@╤\[%ts-А╩а╣└rWJ∙@Р AАjVJ∙@s-@фDA@фDA@фDA@ХЇ=yE╒Ё=yд╪2кш ╒ПцZА"╚И В<А"╚И В<А"╚И В<А"╚И В<А*Р7; m╡╡R│#cq5оm@fэ░!╡z>╖╦Щ▀╨iyЧйи1Й/┌║а╦н|u╖╫vmФ░└bкLM^KЧt%ўK·єм>ш√У-╥ТlУ┌NСбTJR╢Тf- │=jГэ0TнхT╛ЮЭТ╪Р╗CKЇd╢@┼Uи╣╢Q┌;DЖO8Яц▒╟ыP#МY91,╥7╤/нvМ╓┌Я=мчшI╚°xBЪ═рIhлХ╠ щШ─k█dрд╛НK\Msя╙єd▌Eкy Ч║▐QIMЇе Iи^╦и|mэЧ■ЇКWK#Е,░иj╘╓ЬэхХW^▒}■n}}H┌zDО╓╦у╡#╥>╤(√їЁAСўoєФьJх:>t!b╥ъХ:=мЫ F┌%еK ╖╟М┤5K╟─иЇъЩЇxs█.#z¤юzr╙╩0╜╨■ИО[o╜╒Ў-Н╚ЦпЪ╧<Фп@qК-гJ ЄЄо─єA>пХ╬AСобФЇп╢у▌┬(_aрЪW(ш╗Kз`Q=2╥юI7kЮ¤╥°▀;d°╘ЭкчъRЕS@сжРY╦╢E┘Є╒]╬ нФ2квo╫╢ю╨Mа]╥ю¤╘╫╒Kl|XlKCСZе╜kPF╞╞dd0']?-}2сy.%X└Є▓|╩W<`йTЎ+Tъzet▐┐UT┘$Йц╕·шgМ┼│З╙╞зфмэ╒t┴ЦььФd▀ elD[:dуz]╪%фq▀D`Щ[хло-$└ЦLeГ╝·EЗЙ╛дtж┌нХNiЯ_ииBlЧ║│╘єе_╜п█(--╥▒╤Sb╪ЗЗMZ·y╙┤╨*¤║└╩м#ы+<╠█h═ЩЗР¤╛mЦГj*_gO ╦╕·Чh╬╠╟╫иЛз№╧фUЪ~ЁwгL╕╧ИЁ,А2X╧фUх+P╡кюЩ╝JФо]8PnФп@┤,┐Ъ<иjЄT│QУА┬Є"И  ВЄ"И  ВЄ"И  ВЄ"И  ВЄ"И  В*Ў█╡6l░}с=є╠3╢х─▒и.Пъ─o╫иfеФ Є╬Ь9cЗ [│f ▓ сXTОGu"╚P═J)hоИаeфНI╝╢VjMWCиcqu<┌d╓c ╠H[·│╤& АG╒ycёNСбФдR)ЩшKJgЬ0oйщo┐tIЛ╞RШХБЮaщШp>йбШ$z║UфН╔╚`Ч┤╖:Cu;д%9├Еl ═┤╔■╞ ▌╤h╟а*мn$ш^AЇsЭ┼vVЮхїL^]╜─╞зфм─т╥^ПФ╤▐:;KзNzGw╔T│mоmЦОГ╜j,V ¤ЄN╪└╩─ЛiVN П╦xв┘ OH3╧х-Щ╠г ╥╫2.Й╟yФ%Л█ч:kеbO├шчG)+АE╖╝В╝┘I╢4╩j;И┼дkОьє_║ЫшУЦЦ>ЩеЎhIиЛц■dЯь0П2шcг╜ф~^╛@С╞$▐ЩФ>¤lз·L'ўИU2p#(╩в╩Г╝ViяФ{w9{bX╞cїАyt!!щ╩╗┘2<Уz>(Ж╛q{▐╘ї╩h╡▄┤U╙╢╦X╒╫ф╡ЎЙt:M ═ЙШ ї█╖0АнU·u═Л¤lШgЄ&·╒X м1Йы╟.dP▒ъ·ьп╚z]{fk╤Ї╫$┘пн╥╧у║M║╬W'щ╖╗э#j\█╝*dяW_╒J№Ф3ЎДI╙ОK▀аx┐ ╚~MVVM^NZ╬ёїA@╦а╣V]╠▄&┬▒к┴ЭЎ╥╙╟ ¤┘▐ЗAqЬє╪Е>З╫╔╡vКирmк▌Ц╣*аъ╤7╪Ў\КщЧ▀ъдў`f┘ьЧ▒T╪Ц∙ъ+▌їпSг╙iкnиKM╙░h╞┐&╦'нV╜╠Фь▓├йЙцыГ_ ЄЇ╧1ЕэPY~y╘бЄ№Є=иЭ ▐Ьч=¤hАйэsj┘Z√ ▄lыG─│╝╦Ыж√Х?ц9ы╠xє5YГ#NmЮцЧЦib╢╡П║3/Бён АЯК¤v-,'╦щ╖kїў▐є╒(·f┴ўўПu│gП╚A]+яЎщq╟┘┘║┘┤SЕW]2dk°╥╦┌9 ┐ё╣уВ╓е╟ыZ║ЙF┘я7] Z/q№v-а╝TP7╜щGgЇ╫ї$e╞mїлAл█(тy)(ЯЬИ╠╦u]эЩЪB┐┤r_:И LUт~?fm│ wtЮ мыХ]]N│iЎЛ╬єzщЧВTч╛x1_Ў Dє_оєIk,че#▌ёu+А/Ъk@YС═╡ЦНR╩(В<PЦ[РW,В<`y#╚А-з └╩SJ∙└3yDРAyDРA{ёВ╖┐к╟в║p<к/^иfеФ Є°зъ└▒и.ПъDРаЪХR>╨\ A╦(╚У8?]│─ЇФ{~Jи╢M▓~═Л╠{<тjАМeф═┤йЛXз ┌a,е.Jе$e║Qч7,▒feанS╘┴pО┼РH'7AПeф╒їОJjвOZь0А│25▐%эюo╣╖╢K╫°Ф АГgЄPдAщ┤MДm┤╒.▒д╠д┴jilёV:В<бU·╙M╡CKЇЁL▐ТQ╟bвCЖЫ▌gЄЪ%1УzЪ╧AJдkОl/ЦF]пМ║A╖~ЬбеQyo,.qў╬┘2L═QХШХБЮДH╟Fсp\╦"╚3o╫6'd|ЗК ▐кЄзёfднGфрho■╟ ┬╬D\)eT┼В<XNЄYеВ<ВBDT)e/^вC?VBАy░B9?i_жrO╫Д╣у▓~Я:√╖л═ pYє┌▀O╓уTZёЬy▌iю№·e:;:[╬zN9cOJ/g¤╬f┌f~╤FР+С vz▒ЇoQ┼Ї╧тйаиyJv┘q)¤Е█=:`╩∙нd╒ї╖ъyї[Ў╬ЁD_R:▌шkы▒/^╠Wh¤Е╖┘tщЧ4Ашс{Є@с{ЄT│R╩(В<PЦ[РW,В<`yлк ПBиzp,к ╟г:-з └╩SuA═ ╒БcQ]8╒Й @5+е|р┼ Ак■ /ы'hjеН▀аYZcqОE╒Уx╓Вj8¤Щ▒▀}Xqк?╚;;Х∙Ь╘Р─=№╓рR╤Ю∙Х чxМЎЄрK═∙э╤N┤├┌X<єSNєъ ░RTР╫┌/Щп>Z-Нє┐X Лdl$)}|;|5йы═∙ ╨1ьТv{Р╠O=%g°0XБЦ╫3yц╖;d#HK@ущ/2╒D╦А∙Ж~ЫVвeфНI╝YЕx{ЕoйtеЫjuэQ▓Уч╜иV╦$╚╙ТяЧ╞ЙQс1░*бR ╓%эА╒mvFТ-Н▓┌VОъЄ╠█╡xKпU┌╗e┐√╓Лn:ПI=╟д╩8╟i─V▒ЪmП╒S√ +P╒yц"е■yЯул;ЦFk Дt █4╫Mч╝Д▒ф╠█╡═ OH│}N▓╡H$ЇП╞вкъГ<єЎа√Шэ°ъОеR'╜гюqаf╡ф~>ЬxоU·╙у─`ет╖kWОEuсxT'~╓ @5+е|иXР╦ AАjVJ∙░М╛BaQУ╩rк╔г╔XyJ)гЄ@YnA▐Щ3gьPak╓м!╚Ц╣R╩(Ъk╬wТ╢ Ф■;╟aЧ_шzДFР+UШАkйГ2ВBаdyА`u╜2:║Д┐╛╘ыЦ1В<XСfeа'єk)mO¤╨М=╧№║Р■ХtMЪ¤;`ж╟┼№rЮgч5┐╝в╟═г{▄Уц)gь╝їh▐Ї╝ыH╫фхдхn_z\ЫЁГH@AмHu╥{░OZZ·d"ХТ╤nQ▀T╗¤╡Фб.▄Я╙LЪЮоIE\*░Л ┘∙'TZv╢ H╢uК╕єиоЭэ╗ЮьЇ&·Т╥ЩО■4Я┤Zї2S▓╦з&:d╕Зж]└EРpиАoЗ√;xлчm▐щ│3ТЇ√Щ=!├т3П▀zr╥л█╪!-Г#NmЮцЧЦ^Fе╙н╔3┐у<%gэd`елXРз_ё/╢Ce°хuбХуЧ▀Е:T`иk"▌┌=~п╚иhMЮ■з░*╦/╧Г:TЮ_╛u@EХZєUW/▒ёa9a█FgO ╧&пnгtHBў╢║1щeц5щu╡g6┐┤rЦРНцZXйъzeWЧ╙▄щ╛x^лЇ┼$╤ь4ХЎL┼|Ю╔sЮ√Kv║/Fd^╝ШOе7СЩ╖9Уб~oЭЬOZc┘╦ШОп[╥*ЎЛ║ЙйШZИрod╫oSu╩аТо!Ie}ЁQH╣О┼ь@Ы*xsю╒9E+▀g├е>#m3▓c▐╫LНЗ~ё@5Лц/^МНx▐жЪР╛ф~^С_"u╜гЮч^R2╘%╥╥╕┌N┼R╨Бwнў&╚ Пш╨Б[╪└╩T¤A^k┐d*К╬╩╘xLъйЦXz│▓░Kvїr0ЦТ ╝}╛║"h<вA╫╩█XyЦ╔3y·√СЇє#╥╬ЫSUaьёДH▀ОUjЩyu╥;кЫ█eДo4_z╘тPїЦ┘█╡н╥▐5.S|╙хТвАъWїA▐ь@▄Ss7&#Г-┬│■KИZ<ЦЕкЄъъ%¤=L·mAтЛет№а9╡x╒├╝Ek~╩╔∙Сyўз>Г╞VОъoоmэ╧·┌╛Тm)9╧FОeWН▄п╡q?Aу+GE┐ ╣X╝ц_Лъ┬ёиN╦щ╦Рм<еФ Є`9!╚P═J)Ц┘█╡Г  ВЄ"И  ВЄ"И  ВЄ"И  ВЄ"ид_╝А(кЖ_╝А ┼ЦQEyи~4╫DA@фDA@фDA@фDPI▀Уў╠3╧╪>T┌Ж l_x%yk╓м▒CиФ3g╬Фф╤\ A╘фи╕W_}U^zщ%╣|∙▓╠═9EN═Х╫╚е╦5rYлоF.╔хў▐Q╜Щy>№с█фу ╕╘╘╘ШaXЙJн╔#╚Pq╔dR~n├1;▐ю=╫╩п¤┌6╣∙цЫэXyhоP╡.]║d√КєЎ█oХ╝,мty*N7╙Ц┬m╢П @╒"╚А╥ёLАК√_ ы╔Щ'╙5zW_}Х║┼╝Z.]№▒╓яU\yї√╘Ё╗ц┼ э╩+пРЧ■ф[ЄЕ/|A>ЁБИЬ{Bю_ и7-/┌AqйлKd╨╓Ўйоя▐;х┴/?"ў╪Z└п■╥э╝*°Z и4┌yO>2%]ndж┬щ6Ы╞рЄФ$╒ш;№j:▌єГНЄшб▄∙√ф^Qы√вZf╘N{vTЮzрЛЄ UЗAАК╙_f|х7▐УЪУLw┼_^Фk■цJ5юВщо·л rэ ╕R¤}/=юЪ╙j■Ъ"Лиs_Узхщ╓╧щrюEЩRБЯ;яЭЯ╗Oюyj╘й═єМЧO4╚=╢7лцпы)СйЭAя№┌╜mЄАMы┘╤зфБ╢0хEРато╝ЄJ╣Їw╗dю∙o║╦▀▄-■vЗўяMў▐ №ўЄю°їwWz▄ЕчvЩр0omЮ ╘фv░вtА╖■i╣ядн╔;∙H&°Ыч^i{р)}ЎY}ъ!╞░ЄT\й▀У'2Чч'═lsы=/Q▄∙9╣O╖5KV│оbЪz3єЮ√┌╙Є▄m*<╦уЮ√фsvef~з╫╫╜▌·┼Р.Щzд;ЪP!y*оl_Жl_Юp^|°К4Ь┤_бТц<з▀║u_Рp▀╛¤тOIЧ╛ ┐¤╚ЩUЕ^}Ў ]=▀·Ge0;▒l:Н╞╠·Ъn╠SУзшАєЮ{ф>7*АE╞ўфи╕o~єЫЄ▄╣Я╖Cс¤╙?ЇHwwпє=y╦═│ YїХ9╔╫╡XаR┐'П @┼}√█▀Ц ■▀П╩█o┐-·╦Оu Э~йBўg■:═▓z┌Е L Н7▐l╛ ∙цЫo6├╦╔│ЙU2┌Ц[╙┼#╚P╡tрЎ╞o╚еKЧьШpЇ 7▌tSЮчЄ ·°┼ UKi║6N7╗╙щeЁа4yTrs-╟в=УАъFs-@фDA@фDA@фDA@ё=y,@эя╛d√$ї█╖█>№╚Ba|2ЛМ п░0A▐Ъ5kьrЭ9sЖ А┼цyч{п7С▒jрmєЧ oaJ Єx& ВЄ"И  ВЄъ▄r ¤O╚9;X ЄXЖ╬=q┐мZ╡*▌▌ Д /rГНgfzтY;╝R╪¤Nw+.ЄXvtА╖■щ√фф∙єr▐tГ╥°ш·Lач╥_╫Ф'O▄oк╙╦Ї▌лВ╛ї╙ЄE7НУў╔╙U╫│x.В<вц9С√QLХwOГ|┬Ў╬унЕыzJdъE'ПюyD║▌к┐O4╚=·п┼3^╙A╢<%]nыХч╥┴uu!╚`915vO╦╫rг7S├gГ]╙ў`ЯЬ╝яiY┐╥^8╩-▌Дэ╓┬=тs┼RaцyH▌e7 W В<ЦХ{е√╔йе╙MИПК▄ў9╧sz║iЄ╦Є╚T╫№2"═ц╧·D╓Л╧&ь░зй√▄╫Ю╓ХЮ┴ю№Ь▄'ПК█вkШ 2g\Х"╚`Щ╤╧ХЩZ:╖╔╨yХT╛:я╝;х┴п╝ya&Щ╩4йкоK┌ф▐;Ф/6>Ъ╬╖Зж ╘фй№√Є#2╒ХI'ёмєТЛw▄к*√~УЗ`<У╖0е>УGР└ф&╚C~y0x& ВЄ"И  ВЄ"И  ВЄ"И  ВЄ"И  ВЄ"И  ВЄ"Gф oА&p ╥┘эIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/images/screenshots/arrayeditor.png0000644000175100001770000005657214654363416022205 0ustar00runnerdockerЙPNG  IHDRZL<ёsRGBо╬щgAMA▒П №a pHYs├├╟oиd]IDATx^э▌xT╒Э?■w║█}v√|л}~√DфПaь8нНkж╩#PЧЪ6╗X0ш╓DєeЭ4-T╛ЫжелЕ.M\66▒л─а┘жвмХDIйMлR╗q*╠@∙ы<▀g)юvў┘~█№>ч▄3У;УЩ;ф&3Ё~ё▄gц▐s╧Э;ч▄sц3ч▄ 'NЬН9hЭ■∙fХИИИИ╞┬o~є|─<'"""в1╞@ЛИИИ╚% ┤ИИИИ\┬@ЛИИИ╚% ┤ИИИИ\Т╒пП;Жр°ЖЗн┐QЁGВ▀ бPы▓рў°├ √oy:▓╧┤i├ыїваа@пЭ+╘п│ ┤^{э5|Ў│Я5kЦ■UЗ└б¤f °∙ФЛqї▀?Д▀■°▌я AЁO Їъы┐КO|тf/"""вsC╓▐сў┐ ╜y6BRvzdK╪п■є? #e^"""вsAVБЦЪ2╠HтмXРе?"GОM!╤┘C▌Ф═Тk╘9╜∙цЫfm4Х6╓ч}┌7├'ЯH┴G tpї'|Їг└¤QБVoНUБD╨ЛЪВZ"&эмз▐п╝ўЪ^│уF9Ш╫К/чR9С╘g╝╙ТЛ▐xу \°\╩`KmSijЯ▒Ф╒=Zп╛·*■▀kп┼G╢>·╟М]3Б▀Пф+°уGЇ╪ЯKсZ√№СDZ ї_[░zїj№∙Я ╣▐ЦH}°/zЖ╤V[__╕AП▐сЭi■ёfЭя`╔№ ▒2P▄xЙ╟М┤рэоD╕?И╝(*""╩)ъK{ж`*Ы}&B,аz╡ ч╕тК+╥n Y▀гемы■ЇO1ўc╙KЙ╢*ШЖ?¤╙╣ёхўШВ?╝ ёsїЄЫ▀|Nх┤РV |^єЇU┘╨М┴5jDo№xВ иш╞VОj╤9FR*аКНl╣d┼dhеМH│RЭюэRгYэ@Э╖БTєXСl╙]Ў╢▐█4ШNP#6#╟=Ч*П▐*∙ий ╚ЎYK^OХ/ВЦ@т∙ш)P=¤y╝A4°ыPХr.╧:ЯС$√║y▐;RNъЬ╘hUь|SЦkВь▀╦xФ╤x░[nY╩щZYp╩╫6▄ГjФа9<М■QsdЄБэнГ┐╟╠їЖЫ1╕p$р(oЛ═╦1┌╫╚ЎЄДу П╠├┼Н╬c$8Їu╚Ў6X╣╫Gчє ╪PНЎ-#СBяЦvT7Ь■4\y[№uU╢s╩ЦЦkАu~=rN Puю╓·@▌:ь$Л┤мA{I%y▓/уQ~DDчв°Ч╤ хзм-U┴п№ўcчoлЧЯ¤╫╔╓C°я ▐_>ЄСг°°╟_┴ ·_;їr■∙?Ч|Y~┤╚^ J╪┤8/yВhи@(l╓c7╤ыQм,е═SВ╩EЎ░"i=U╛Є┼4l1AL/╢ 6ceь\cFфм╤ЭЇ$PьёгnЭє^гI`┘aВ"uNЎs╫ыГ╪k ИЇhЯЬП╖╬ПЮ╪¤Y┘╝e╝╦ПИшa}═╝╨╪▒O┌з▌РU$дnl┐ю+_┴▄п~U/╫╫╫уъП├W■4_о┬a▄t╙uX░`о^n╝ёs·v╠/└ш{ш╒ЕFsЙ┘юфtЄ(iєХceє ╓┤DЇш╨`хв╤гYЎы|jЙНЎ8(_Йц┴ЕЙSjc╩МЎН:Я,▐╦DФС Тя╔J╛gkмehе║╫ъH аR▀О5мГнSцЩ ?┌Я]Ть5эfД+┬@Й··╚Vtи'ЬN┼!ЯgQ%╨╜ы║С4вs║<vи)R√ИС╛√H▐Цд╤д▒Сё╜фE∙9Kwу╗Ы┴╓iZ┘8¤╤мr┤щ√▓╠╘Ы╖Хa3 гF~Pп┌^В▀6J▓╕:══ЁiєdрФOMg·█╤юo╗?├ ╟ьH.2ў3┼╩a PmR╞TжўТ/хGDDу╞║-$¤ТЛо╝Є╩QAVL,╪R√Мем■О╓+п╝Выо╗╬мY~╛бЯ√гС ^чХ▀I`їЩ{═Ъх┘gх├┤сi■ОV■S┐и█▓╪■7░Є╫D╝Ч│й№ИИИТe¤ЯJЗ├al┌╘Й №╧ Д·ГдjдJ▌шоЮПЙ>%эуr╣╢dY┐&╦kы}╣&╫╚5Щ&_HО┘╫oох;%¤)s-;чЛДli+юФc╝oRЬ╙┤h┐┤л;н}║·═╞<mХ·8O▐г,ыГ┘╖їРфы{╔Ф∙НТ╖╒Ф╣Hw╠P╨┌Ц╝t╜O─╖▀(╟▀gmW╥╜^жcТk╞1╨ъE═Bаgx├├=Ё╫U!]мх╬HJ[:ыp░8М▐ ├°чтA┤u╢рИIuT╘Мo5Жё╧ziЖ%и)+ЧД^<▌┌Ощ╡цШ╡~Їnо Ъ╥хЛЖёъn dЗSЯK┌╫╦Р6╞▄╤┌ПЦ╬F ўcx├ДЛ▀E]чHPфь44▒ЄU\ВЎ═ПHM╪6щ4╜╘▀ П▐╕[▀*4бZп'Са/╨┤▌G/Aem?┬eєLВ╘Є█Нh?sЫш~lСzМЯ╦)╜З\┴3╥>H√╪.╫dЗ\УнrMъ&╣& ^о╟╜4уR╣&kc╫дo┴жЕ╪y╘П╣╥N:ь╫ъщж)Б}Юzйbк┘ьx.yх}i▀УЎёпrm╜%╫╓>╣╢bAСГш{цЪ|+e╛▐╖┐ЧцZv╩╫Пuн╧└_k╬еvЦ┤╣oШа└)MH░h·[iW│д]¤л┤лАI╚7√д>юЧ·╪-яєдФOH╩╟09ш}√■╘eЮщШEбзq7┬zyHофk╨\vгФi+нПKЩЫ|╡> n╛'^цщ_Oд;&╣j№н▐-hп^ л╦+╟╩fа{kъ╦╘ХСФшV╝2TН┐.╡.┐)е (ъ╞ы▒!ГйЕLСГ▌UтъBУ ЧjьТЮRшУ└╟/√ъU-e╛┬r▄W▀Жл╠~S&∙БбP┬ЗZ·╫sNKоОhE╖г{h)JgъUOiкЗ~Вныc&╩KяF╣~╧√>Ў.J*ю5╫Хеф┬ЩИHI8╓LЧмE╨7╦м█YA*·╤_пЎЩ O╝LўcяQыШг╬CЫьoЭЛ╝ЗIЧH=Ж╢VєЛ┤ПЭ╥>╛d┌╟TiЛд}ь:ЕЎбuM╛#╫ф5║Lмр a┤╚ї~ЫOэгwзЫf╣ЇB/G#▓Ш 6й╧%╧D_СЎqЫ┤ПЛЇкзЇ╦╥>^╩▄> rMJ▀f▐│gТ\яCCцЪ|▀\╦ы╡О∙Ф╦d1чRX$╜▐,╠ОЧk║4+XD┼┐J╗jРvuСн]хЩшЛRwJ}X¤ЗзtХ╘╟│YЇW√LЩЩuЫ,Ощ-Ь%e&щГ╧bашєXд╩/║GЮ?ДХж/є°юCe╤.Дt>З╫3RУ\5nБVdя J|^│&╟l?Bй?Т\IЙЖаgЛB-°ъК╢шХ╝Чйб°┌Ё╧╦В░■Ы╥^▀=_ё"│.ScЁВЩ┬ы сп%АRIО∙9>(▀╨g╬w ╟ оОhE├R·"Ї(+nEЛоП7LgСЙFz 0А5╕&XЛ╪DUg▐&5ЭЧ┼У·оФ∙9k0╨52=Т┤JК@+Iф°╗RП В╛╝!эуї(э#(х·Мi│hrMZ#JьТkЄR╣&ї║▐J0ымщ?YВ]fФьt╙Мw6WamgкЪ$=6uшt.∙&:d┌╟SrM▐i┌╟█Y╢ПСу√фЪ,5╫ф{r-_&╫▓9I╠'AXуЧеЯ3╙ГЭChР~/cЪ/Уv╒aM╩ш╩bT.йрF=ЖZх╜▄hъ#▄8Т2┐F╩▄ К8╙╫ВЁ▓Zє~╢ю▐ЕТтЫмї┬9(▒d:`3╧Э^╧щШфкЬ╝▐╒С╜л`┐╨ЛЛ╙¤ ж─"~=*VВы№▒KSMG.─┴╔═°Vm╛5y▀░Mж╧gm┴?lF┬ЇЯS╛мО9F\┐GKЙ╫╟L°▓м=:Uпжы·╤АЯ└k ж╩Чиэ╧╔7шчфqУ|Cl─:▌Бea╥╜шW╙ННЫря7∙в√0ИKl▀▐╙И>К*й╟f█Фc^К╫З╙│мП°hУ Рц&_УУVвEMё5Ў`F║эїqiW/QSЖ¤hйяЧ╟,I╦x.∙&^ЯB√0вOЩk╥L╫E╩╡lЙJ#9ЯЩ·[ N■zj =Уўaa|Z╤)═ШT%эъ-iW (эъ{┘╖╟\пПвьъC╛P┬ч\цiО¤╙БФ-x*мEГTM]УuПU@╛з√q |j  пЧЎШфкЬ ┤\I╤╙zъ1ИРzqaяIG|qЪ 2Х#╔SuбuhS╙СKВ╕╩WОлЦtаux:й#Х╧╨7╥7uczн:│╤&]>┼)mм╕:в%фj╥Еwыа(XhНщ╬"k3Q^vЛ|╗ЛM9юGoh┐нУ?Хрэ,ЎЩQ+[╨№Й|ы▄ДЕfд╦╗∙ ∙╥╪И*█ °·&√жЯ└_л▐З┘Шoд}\кГ:А╣M┌╟Ai╙Oс¤N9UчG└gВЭQ┴█щдEЁz(b▌Jж>Ч<взр╘уэ╥>Чы╩Н╩╢}ш╘Ы^ТkRх5█_Тk∙╣Цн╤%яц╖хZ■Ю\╦I7╢'хCиujs╔э(ўф╦╠╖╨ 09еi│д]Щ┤╙ sЕE╥П╡R/I┘X#GЩъ#вжчЁ╕Ф╣y7яТ2┐_╩|_╓╟╘╟HЪт+_ЄТ╛?K-xэ&╕r|=ЫT╟$ўМ[аХТFОЇ/oLиOщH@дo\п╕F╩№!)єY┘3э▀> К_BK╫Н:ШкЦ■G═Й8╛^з ╟█°Нhy}(i▀b~╓Лuu╥аеоfwFR4■ж╔Z╢ПДе╩уФ6Ж\╤R╙╦Ър▀░FКv_Вц╞╡ц╛Cъap(щ╛нш╘Ш╤еQ∙ яFGэR J░е╙╒(SEЛ∙f.∙ЪЁ6-ХRЧТЧ}╝M▒ЯS╧├╩ф|╥qeЭКюУО7ф╔&╘щуYK╩?Сє<╕M┌╟ iъЮи*i╡╥>о6йЪФ╒╣nS▐╖%╫dл\УЙSuхЄб]НrНы?│ эcЖ|▒╕MЧыiжIЫ[ХЬV╦gд<Ч|sС┤ПпI√°K╣&/Чы|Ц\ч▒√в ▌>Тю█КФkЄmyЄМ\У)╫г╡d╝&ЭЄ▐ООКYшn2ўaйпКEЫъчЬ╥РvuЫ┤+ы=ш4 тЄs╘wЦ╘╟CR┼Є^╬У·ЁI}X┴MЬоПlю█К╔тШбЗQЧbКп╖лXВт╡ш╞ч╤╙x╥ФwЦ╥У▄Spт─Йaє|╠<№╚F ъ/├°сЗfНИИИЄ 2|ОZ¤╧Є┐P╚Я╓ ┼╚t│FmO┘AйП]fН&┌Ю▓kp KдЬqRўW5k4╤ЎФ¤ОБЦ╚╔йC"""в│-""""Ч0╨""""r -""""Ч0╨""""r -""""Ч0╨""""r -""""Ч0╨""""r -""""ЧМа╒[ГВВЇЪ╒qmA√·TоРe} zвf{╟B5hPyЇ@{_─дИР│п╟фщ▒╛А╖E?╫╥цЛ`╖ьЫ>═Ц/щ<╧%▀D7аf¤Яб`Е,ыWа%╦·@HЄї╜(%%е╒W*y7шч└>Ї╩z@O/е▓▀>Эвez╜шЛhщ2∙╗^4ЭП┘██nЦ°╣фЙш6)У*9wY╓?Ю}8х IZ▀пL¤мСЇmжLОKYоС▓4∙VмС¤Оы+эёС┤дcFB╢┤Д|тt╙rЭЇWлд┐Ъ"m}КЇПfY7;║LЮ╪"}╥~Х }G┬Ў╪╥eї╞iєЙ¤ТўVs.лBfг]┤ПvpлэxК╬?fлЄ║┐z\о∙Yr╔▓■я▓l+ф║■в\Г&▀К/╩5x└дйы·я0╟мI(WЗ|Т╟┌Ц┤tэtN3y╙Э ╣gнZ(╪TЫ-у+ВЮ╬:╝WFўЖa<\<ИНЭ╢а(н^<╫┌ОЛkM╛Z?╢oоК?╗▀о├v9╚Е╓ъщ$ТЄ╜g╦Tввq╪JлP╟\З▌js4М_╚УxZ┬y:ЯK~┘ЗЦ╬ХФў7╝с┐.~uЭ┘)╜oпD╗И╟м'к@CуY╟м╕эЫ┐oВ· п'AXаi1║П^К╩┌AД╦n2 J║cБ-:M/ї+╥ЬW.:.eЄдФ╔Z9ў)У├R&▒а╚Йs╛▐╖ЯtиЯле,;м|Sе,{м▓М╟╣юуi ╟№╓╡■■Zєz╡*▀г╓Э|Бд┤┴l╥r^ПJїоЇWGднў╦u█(¤@,Ё╔(╨гєще>ИЩf3КЪ▒й1М~╜4уJФайм▄$КФ∙"╪■6p╦Вf,╒ыIдп╗╡i!~r╘П[дoъПпПHuЙl╙яA·лM╥_e0цЦr═пСk■e╣ЦЎ╔ї╣Wо╧╟│h+╩B╣оўY∙*fыk╫ъ?`лФkхВЖ4ЯЙщЄЙвЇ4╛М░^д/Gs┘\ч4 н "э┴╝З┌┘╥VцI{╚oуhyь╞p█b│>╬в[10TНКRл√┐░┤eC▌x+лЛмDУп╨З┘ЁcrбZЛршQ`ЎЕ^ЭЦ ┬^щ╘nїЩ|╛Х()└!¤zЧQ;╞▒A╠оXЙb╡ZXОъ·6У&∙&∙БбфeМtчТgфpў╨▀абtЦ^їФ▐Пъб═╪Ъ▒>ЎaпF╔Ей>╞gб╝t╩uyHGrьФT|║█w|=+C┼ ·ы7 шЫO╝LОiиsЙDў╔b6фЛш█R&!e2IпzJoХ2y=s8ц;nъ╟JK4I╩r╛)╦уRЦЗе,ZeY°i┤╒▀i╥фШУж╩uakUи6f^пpк┤ВйШнЎН╞@╤Ч░╥g╥| QYFHЭЛSZоУ■ъ'╥_╒Щ■jжЇWKе┐┌Юх╣_)}╥■hD│┴ffбG/ь╞EХ(Л_ыщЄypўТ6▄эЫm╓эмАa<'¤╓▌╥▀═┤╧▐_═Ф■ъJщпf%дчЙhЯ\є-╫№ ╜ъС>бzи'Л■jЖ\є▒ы·А\є{хЪ┐█Ї3\ЄщoКЇZ"з|oс i r>Г=rЭ/─"[╣жLУzР lе╧╝▀▌╥▐╩ПЎРч╬Э{┤TргC-hX@ПжН>N$ЁСox┴L╫uЖP╤(БРN у╨P J№)>ЇUdфtаgЮkЇшiL/6г+LЗЪь╪ёA∙Ц╣╪╝Ю╙╣фЩш╗R·"┤Беh╤їёZН>В╨╨gQщ╖ж╤$h╥╙Г~мA:L`х°z:√м||_╥м)└@Ч}t-═1НБ═_FUчЧсm╩│йCИи╟╨6y▀kLЩdИ8ц;.їуХ·Ih)╟е,╒4▐*)╦ле,Sя9~Xо√+╠ЛaН╖╩uoж;╦7}∙RI*ш▓З:4╧Э╥rЭЇWoиGщпnХ■ъQ╙_Е3╒Нё╞ц*;лh▓M·┌╨┐╠6J╡{W/э)є9╤a .┴║°сн]▒|хXk·+=u(¤UЭЇWєtZЮQAКz╘S╤_4╫|╢A╩╣ц╒T▌ r═/Фk▐ t2KУ╧ўДЧ▌i┬╫╪║√-ФЧZыNi\Ч╪ГC<Ъчфкsяfx┘л/Е^LKїEb5х╕яMn╞ъ┌мЮ<ИcSy╤╜x/▌ИRa`гtX*(jРxщb∙v7-╛п ы╒Ї`X>║╗qЯ¤▐оШh 6lЦ╟Зт╬%_┼ы├ГФ_ьТE├─е╓hFJ│мW╙xГhР╓Ы°8╜▐дпа_M 5nБ┐%╓щ╬TI╠Є%j{·ы√фqЛ|╦╡ч╦ё2ЩФ]─д╩=&їcFЫRЪ$eйж╫JY╛.eЩbк2║ Ur▌7Ч}┌lPSХ А┴╔_BOэW╤3∙0╞ж чгA┌Y]УДдЭ∙сЕO╜╛SZ╛ИЧ▒▐,ыf▐5ї╫Пчъ√х▒KЗъЁИ╣&угM&@║┼Ў%╤)_FУVт95▌╪╪ГK·c∙╘h╫B╝+¤╒&щп6I╡ЇTж?sQ╝>жЯB[Щ!╫╝Ъ|Yо∙╣ц│ЭrLЯ/>тоГе╦хЛ═HЁЦ6нЁN╙м{┤мЎpy~╡ЗЛЧЇы{й╘▓BВйэ)Г2К╦*G┐Д╛щ╜й╫кs5╬%я^в'P╕B)┴Bkд*SгП nЦoЦ МЕfф╔╗∙5)єХи▓▀Їо═ByYЕ|Г3╙Г_яR,ЎЩСк┤A_╥1▒╜б}╢N3╦`1Wш)8ї8_╩дA╩─Н╩╪ё:фЛ ╛.їєSй+╕ёnK¤<)їУ|·$)╦лGЬД╛щ╜щu°k╒q═╞Pъ╘TхТ∙(ў}ZВ█╗╤М'уm∙Т∙0▓юэъРрн▌ш9ех4=═жГ:Ё╣[·л░ЇW▐Мч┴ОP─╠д╨ЎПЪ6╠._j~,0╖H$Д╥_5кщOщпцI5O·л&щп▓▐rЙ ╥Пw╩5 c╣6╩5кA╩ ╣ц&О*e%}╛HКi├ШTiхK~,m┴║ялCВ╖v∙T╠ЛЎРч╬б@kJК┌▒┘A╟·╓М|ЇпЙ7ЧыmGc█lSАЦОR·цЇ iКE═╢i└ИL╜шщ ш мм6ї4Я░┼╬EЎЧs╕п(йэ└нЛЪПrЗs╔;Ехи,·gм1Rдя!iЇI#U·WВе 7kzJ╒╚СE▓Цp┼gе╠╫НЪ╬Sма╠╙щїф├┴/┴█ Ъ43ХШкM8жь╖ж╒/°Щєхд┬╦дL~*ebAС╛чF"·╫ЕkoШu╚ч) l╘оЁJ¤|)хбФ┼^яWhС╫ё╩u_Y{7VJЁЙЪрLvЗ▒7~▌'O╩╛!╔▀%∙%░ло5╙КЪSZУ■ъщпЪM╡_·лMrХ&▄▀дХH╝╣\·ДцVп3ж╧0#WЙZКi├L∙ю█R╧c}╥l\"╡ Bк|:X─╛x╜й4є<▀Ц╩5 /r═[┐╥ЛЇmРwЭдш_%~╤ёцr№╪єE╪юэT╧S  pT>-ij0A║4yН╨Ni_ФЎЁЦ┤ЗяфG{╚sч╨╘б Ч5ут▌^kЇi╖╦УяoТ ■╜бд√╢фхК ?╠`еeкР█g╥ьюЄJ└┤FH%V7ПфСфv3 6ъ\в{%xRw┤cгt|ў5YK╗·xчТ{f!╕l№Є▐ї╚╘юK╤▄╕!▒╤K= es▀Ц}5fдkЇ1Э^я&мм¤ J╨дє6mЖ┐ЎЗ╓ИК╙1 Wа#9_Е╔Ч&IЩ|I╩dХЬХ╝╖йЄ▐Т]╔ўmeС/Хшпд,═}V╔∙в╟$xR╖╛ uнлрm▓¤Уў┬∙шиШКn3XаF╝*╓в═\ў╜]▓oыsш╞╒шiьИoWЬ╥rЫwKuЙЇWъ■жА\╖M╔ў7IЭ╝+}E┬}[╥G┤╘Vу]щ;Ї}Q╥G\R╤Б╗э╫дiJЬ6t╬╫ЛU╥ЪJ░lТ}RVАWО{Ує╔DЭOS·лЯи√╜т╟ cm^ЎW3фЪoРk■╣g╔╡;[о▌д E╖Хд√╢в;хЪП¤9Еф|Т╓tГ\чwIo/=~лzn~шШ╧=К║дi├╕4i╜]Єнд=,ФЎ░/П┌C~+8qт─░y>f~d#╕?h╓Тївж` ╖%^4╞С#G╨ ь│Fэ╙e{0g√t│FmO┘AйП]fН&┌Ю▓kp▐Ў9fН&┌I▌_}╘м╤D█SЎ;╠ЩУ?эу█╡р╛{ЧЫ╡▒3#ZхhKdЭM╬╜_НZDDDD.aаEDDDфZDDDD.aаEDDDфZDDDD.aаEDDDфZDDDD.ЩА┐ яL¤e°?№╨мQ╛т_Ж╧╤@k╩│l╓hвэ)√w|В ┼H╬8б ЛСЯЩ5Ъh{╩о╟ЎЯ▓}фК▓┐╪Г aХ3■DїW ┤8uHDDDфZDDDD.aаEDDDфZDDDD.aаEDDDфZDDDD.aаEDDDфZDDDD.aаEDDDфZDDDD.╫@л╖ж╓hЙШну(┌ОЪїУP░BЦї_CK╘lw┤╜}7#аЄшхf╘Їэ7iFЇe┤tЩ}║^6Н▒N ╔{ш{кЇ"r^ы█їє╝m┴╩ї╕pЕ,ыkЁhVї╝▄eЄ─Цї-RK6╤^<┌└-*нл╫┌кI╠[T║SЪ╪/щ╖Шє\╥ЫМ^юУ┤╪■зЁrЖ\g5ылх║Цe¤ж,█Дp╚ mB└д╒$Ф╫╥Ц$MхХOе}w$m┼wх: └ЦцФ/]Ъ╙╣фбZ╨╡╢+юСem т┼У╔` ╓лiУЇIwи╝▒>)&M┌{╥яшmz р[}#їЎsщнэfС>Ё=Уже{=ЗcТ{╞/╨К┤`ятa ле■║*Мoм╡-Э`░°5 o8Оpё╗иы╠6H╣ Н╟н|Ча}є?!~щJЁh║▌G/AeэkЧ▌`Д i╜o?АЎcА╟мчпэм├╗┼a█0МWЛё@gR└ф$╨гєще>ИЩf│ъ oiZИчО·qkmпЦХЫQ╘М'eЫ^ЪqJЁэXz┌┤╢┐-W└Вf,│Ў уЕ▌@]гuз№&▄╥&~$mbН\█э╥&KЫ░ВxgN∙>└V)п╩Еj╜n¤[д╝█╙╝▐Х#iSеЭm│┌ЩS>╟c:ЬK▐ЙаocО^╞Ж гё╩Al▐h'ЬнЧrЩ╝┬ф[с╟╤юкС Mеc!vЎгXЎi╝╤╓^Фыzt>╜м в╨lVэеЎ[▓┐^ЪQ$эе"Ц╫)-oE░I·лИЇW┐Т╢■╝┤ї╡╥╓ВЫtдO║C·дm╥'═Ч>щy{ЯФ6н[█сСm·їj¤xzsUbp'}аJ╙ЛЇБЫ═iПй╢'3Т|Lr┼°ZЮ Вёы╦ _Йy:^в/б{hJнПdOщWP=Ї╢f╝╚fв╝┤х║З┘ПЁ▒wQRё┐a╜+xC┼kшп В╛ЩЁ─{"w╥ЎJ.,2ыy,║╧ Uго╘ gЦ6`┘P7╢g┘шп║╨Л¤╤И,fГfoиу'їm╕█ч┴╠x┘YК ╒6y═┴n№виe╢Ї╘i▄╜Dk╢▐'Aa9╓╔ы▄`О1sТ a╚Z═}╤У6q╜┤Й ЇкзtС┤Й72╖ ╟| ╕dй\╖УtZВB?┌ъЧЪ╢$∙&MХЄ:К░^╗@┌┘ &эigЗеЭ═╖┌ЩS╛ ╟L{.∙цГн╪-эe┴<л╜╬k@@┌╦`жQнфzФ└з╠oЄ∙Wв╕h╟t>+xCeїл┌P*√ZUW4┘+▒lD┼│г^аЎ╖┌╦Р┤┐-пSZ^Т■jЫФ╡щп.Ц■ъ R■?╦╪_YЪъУЮР╛bйЇIЫk╒9MQТцї }° №ШaK МЇБяI°^RШЎШ╤~)╫┬r┘ж\ь[Й∙r- e|tж&шн0B~╠╢ъ{|Dўb@=Ж┌Xq3ZЇ4┬/╩ъ"УрGO9~kp+:L░foW╔х OrLХ> Б.3JцFЪ|ДЗ$н╥┐╔_╥шбC-╕eEПъ·└▐,¤/фЫ╪}ЭU╕╢IM┘ЩQ$╝ХрмЛOч▌╥e╥|mxuYlф+Вэ╗pUё"k▌)эь?>(▀2├66Щ█вGMЫxYо╡яЪ6▒/sЫ8▌|I"╟Ky}╞|iQ>Рvжж∙дЭ])э,їзєш|#Ь╥ЄЪ Ш╘у` ╓▀@Я\jк╜XУГ |(▓d:`│?/┴di/▒й┼їO&ОТ uWбscЪ╛!щ▒йCЧ╟F╖"|cEW.▓╓Э╥ЄЩ R╘гЇWwH╡╔ЇWГаХH╕┤.>ewЗЇIz$╠)Mорo46/Шi╛╬к█Ё9Эf∙еЇБ ╥▐,}`|ъ╨щШ*X│Зz_єЬ\5!БVo═B 6пЬШ╬P╡x5 TX_╓C3мWSЗпб╧┴Ы|_╘д Н■ Т▐°№¤`ЭnД╞XжEўaP┬И┘y▀k┘─ы├Л┘Y╓╟ K╘T]?~R▀/П=X6TЗ╓Д▓[ЙЯlР}{pI HZ|t╦d╖Ъo∙КSZVв-╕o3Fж"єI╝.8Е6!N7Я}UR^═e~│A╣@┌ЩЪ\#эь ig)ж1Sц3Ь╥╬*ЎTe}БY l_─ВыА═*PRБФhУQВ э1ь+Qпж┐╒Г╔п╘a╗тф√╟Ч╘Фa?ъWї╦c╥╬biёС/м█┌ЛSZ▐│їWEзr═KЯЇДЪтУ>╔#}╥╞д■jtЪЩZИ╚фf┤╓ЎаuЄ jmSХЯУ>ЁW╥>!}рпд№В╘M╞cQ╓к└L`RЧ╣Кb}╣fЬнZXу г?8╬Нпp╢ИEa5·ыЯG░╨ЄЭ╥E6хe╖в$a╩ё,ЎЩ▒ПQ┴█╪жEЯУяPЭXhF║╝Ы!_9@UЄ═∙∙@╛]]еГ:`║╗0М╜╥)g"#x9▒Fй┤ф═Пfh<]Ё╢?┼┤aМSZ:·f∙жn\Rл▐З┘Ш ╒Gоz╝A┌─╫еM| mbVц6q║∙ }Гz╙Ё╫к╝fcВ дЭ])э,q╙)_цcц952еГ:Ё)╜ Мc╥^ж4|_RБТuЯ╒2tг_┌╚дx>?.ЛB ┴[б┴Иmt+u`uШtJ╦;zъN=up│T·л!) ьВ?ц┘·д─-MZh╓кй╩%A|╬W.БUVa$√╣ЇБ▒аK╒MV╟Я[в3д cН\ O╦╛ЎщHr╟8Z*╚к:Ж╟?╚R oDeQ'╓Ша$╥ў}┤'Пщ_%▐ь°╦++╪1∙ g╔e┌Й-!є▒oж¤ЇО iЮ╥чх█╛Y│ЦpЕД*E▀Щ╩╠'ЕЛpkQ;Ъ═п^ЎўнСwэOь╕въWЙБ─_ЄE╖в╣╒ЛVщhbыj╩кП┘R3эx!UZЬ╙╘`Ъ┤Д{┴╘є╪8K/Хє╗╢╕╡╢╡ЕЎ┤Аvu┴-Cє▓я%~√Uўo╡> ▀КПгMўDB}╨4▌.ЧоСФ/·кZ;хCA╣ ╒╡ (yнПi7╥ь╘ЯwЁю╛с·ji\ю╪SЎя°─Ў9fmМй@к│Э║>кёэemЙ#Bъ■н╓:\R;Мu▒·jщ╛╓vы/Ф`YE╓ЩЫTGеI┤.ЎэNСЇ [ёэ╞гO)╙z▒r┼B эJм}а~сXg^k─▓дєK'╩Ў`╬ЎЯЩ╡1аоя╬Щ6q╜\█KУ┌─╦╥&~$mв}дM(iє вf┼├#эEЫЕц╞п#9V╙П╠u=вZ╗PЄ5┘Є┘П)пХ>ЯCЪ╧с\Ты■4э)╗█ъR√HжйНuш7эеbyncSўomи├ф├XbЫ=Uжбї∙░╜о n ┬g╦мAчЖvєО ┬:$п╒^FеUJЪ╣_S╩a├ *╛еF╪╠╢з4Х¤┼№ПЛ¤╒╖д┐z┌Ф *щпЦ&┤їл╛:xд¤├╓V╘ЯihР>I▀у%х°щУ╛a·$╟┤>I█lKУ~южЯХ╧)═vLїg!j√KЁЩ@%к╦В°▄╡Гt■DїWs╞й}МБo?╘В√ю]n╓╞╬╕ZСЦ╝uЙ▌aIєш)DW-:eоZt╩╞<╨в32оБeфjаEзМБЦe▄ж=┴~є7┤FЦ ЩB$"""'ЇчИИИИ╬~ ┤ИИИИ\┬@ЛИИИ╚% ┤ИИИИ\┬@ЛИИИ╚% ┤ИИИИ\┬@ЛИИИ╚% ┤ИИИИ\2~ OЦ╘_Ж Ё├═х+■e° ┤·ЮЭb╓hв]Y╢З∙_ZфМйR┐c}фМПJ}№БїС3>в ЛкW═M┤=e╫2╨Ь:$"""r -""""Ч0╨""""r -""""Ч0╨""""r -""""Ч0╨""""r -""""Ч0╨""""r -""""ЧМcаAKа╓R╙k6Пзh [_Ае+dY_Гвf{╟C5hRyЇ└c}+A╢[█ТЦ.їц"x╗╧Ц╧■zєR┐ЮcZТ·°╛╘╟ Є^nРЄ┘ЬM}H┘щ¤У]vF┤Ы╗╕/a{пK}шmj▒┐Ю╙1_O╙╝О^°~Ю╫GУ╘╟зф╜|J╩gSЦэCУ2▀$e╛Lх╡╫ЕТ&э=)█eцїЪBfcМ├╣ш|j╗^д=╪┌г╡-iI>Я|!eЁM)Я╝ЯФ┴зXOHЩIх╡┐ Р│пхщA╣v}ы[ЇsUv·uТsЭ┐"mGK-╔чТюШ:Я9╜d┐^ЦўЄДФOлФ╧aУъH╩юA)╖'Ї╥МKеьjce'Nў5-─╬г~╠Х:{"╛=М~йПеRv)_╧щШNiRёcJ}ыС·°7y/[е|╛+хєЮIu$e╛L╩№E)єЫд╠╖╞╦FдMЛрgrс▐┤а_0[F8ЭK/ЧЎшСcщ4iПOK{МbRWПH=m╒K3>#uїu√∙фНЮР2KДф}ЎJ<$e`0HЩ╔Vц╜╢ў ╩█uш>L7ы дь┌д▄zї╥Мb)╗√U^i;/K█╣GоєTчтxLiё|╥>║е}╝bRr▀q┤tva░°! ox\ътъ:cAКУL∙оBCууVZ┼4┤o~┴|╟ViХ ЦаZп█Hрh¤)№╡цШ╡╙0╕∙З#БСJo·>║ПNCеь.√┤I0_╤∙ЇR?╡-z[д^учТї√гS5ОБV9╩cэ▌3~єt▄D╖b╫P5ЧъK УJ0oи╗│КрKd1∙ }∙1╣Pпjzd╗дv#\TЙbХVXО╗ъ█pЩ┘o╥$y╟C!HЧ2Я╝╬eеAУ/Вc╟снXЙ╦╘кcZЮС·╪)ї▒╘╘╟TйПЫе>^╦Є╒T)7╡и▓{G╩ю│жLTЁЖК0Ц▓пЁй}Їю║>╛"█о6ыSM}╪╗╘╟┤дNєрjйыШЦ·╕Tъуj╡Ъoд>^Ф·и1їq▒╘╟д>~Ц▒>маHХyзФяR)єЛухцФц┴╥%j█l│nУё\F┌у┼╥?#эqЖно.ЦzRЛкл_J]]oK╦ж ю1e0]╩аR╩`gїб4UцOJЩ▀!e>▌V√П┼z═·hк▄жЫ▓█-e7WхХ╢єM9╓uц8╙M█▒_зczpЭ┤+_яI√(ЦЎqЭN╦╤╖╤=Їh(ЭдW=е╖аzш╪ЪйєMByщ|Фы29ОЁ▒C(йX ЯОVZpIВ╛ їZВшQ -┴JЯ9жo*Л"щcZБ*B╜╩? ЮxЭ[J.ЬДИV√╣~m▓┐u.r╠I╙д^П"lн╥ЪШ{┤z╖а╜z▒╣╕╞I4d]@б4нр=,;А#;. ША╠t]gЛ%АRI╛6№├▓а4%В▌╗р-^d╓?>(▀*gЩ/В╓л╫єbЛ·Fh:[ЛSZС·xG=J}▄'ї▒┘╘╟{ЩъC╩ю )╗йz%В╫дь.Х▓╙ы:x+┴LмЛOч▌╫ХzФь░й9╙)MУрNъуйПMR_╧у·°еzФ·X&ї▒╔╘╟╛LїбВ∙H]Я╬[&eо?ДЭ╥Ь8ЮK9M{╘╙В╥kд=~N%I]mХ║║X=Чz∙Щ╘╒gдомї<#e░[=J|I╩р SCY╓ЗW╩<6eў%)skЇ)М!I╗╔ЯтХ▓ыХ▓│Fе"╪)eW,eЧjФъаi;V└фpLM?=¤щ┼д}№}>╡▄и╟╨6V|-║b┴НГМ∙$0╥╙Гўc оBЗ ╚NFЙ=╚╙┴Ь¤╣G╛n╝Я t%ОL l■!к:oУ}ъ0Qф°!й╫тё¤\>GМkаi XўhmYМс╢ кN╜йG/жи╟М╘ФуB╝?╣+k{░rЄ ╓uОLNК}sРnЧt8╫дъpв-°┴f╪ж3хє`A╜Ъ c1║ё╒їЎ)Nз┤╛+ї12¤ч|Lч╫єаBъуeйПеRwH}d5¤Щлlї1+╦·╨д╠;е╠ M╩▄#e■╕╜╠Э╥Ьд<5J╢iППH{|D┌у╜╢)╬°ИЩ╘Х 8╥y┬VEзXOJЩЗд╠╜▒2ПюХ░╚ПЩ▒2J∙r*;i; G┌ОЮR╘ы╬╟Tэуi!iўH√(П▀┐ХGтu0 ╛SйГ┤∙&!XпжыB~oЪ└'Aс|4А:(й@Jb]┐Фн╧^юУа_M 6~■■.м3эм|Йzн┐C¤▀╔уWP=4Т▌Ж*й╫цф)GуhyВ¤╓=ZЛ╖H└@╦xNы)?їDc}?ЖqD:У)i;#┤Ы╘ФуТ .єХу▓%ЄБZЗнIъёA√Ї▀}#}S7.кUпi6┌д╦gёр▓▓JxSNq:ехйПKїcK}TH}╝'ї12╡Ф┘сДi╝?>єС"x;,їqЯ╘╟LйПКпХ·Шз4UWK}\*їСэЇgN╤Spъ1ИNйПеR√д>feU~▄`+є─═)- зsСЎ°]5н(эёs╥?'эёы╥УГ╖ўдоЄv┌PС2(╓ПA<)epЗФБ9*:Н·ИhеLvг5fдл|є0TЗ УtГ║▐O╩NO┌Ф╢є%i;^i;wШ┤lПй┌╟u╥>К│Ъ■╠jI?╬╫AJ░Ё8BCI┴M*YчЫДЄ▓лGкФ/QБТu?UЗhэШЖ┘ёcN├b3нШ╪Goш╕-Р,ъЁЫ~н:W│С╞╘─LЦ╖бзzбёЬ .\ДkК┌▒┼t╟√╓`ЗtHЎ{н╘╖╡╟╓Ни┤AНmУo{╗bC╢qйж zёВылн└5╡XT┴ёhrчу<▌иш@,∙< з┤Ь'ї1WъcУйП├R╧╦{╣(й>╛/eШ·╫И)жё gcжt?¤!S╬f╩ ▐z▒YОuЗ╘╟\йПJйП├)ъcЇ╘`МSЪEb╔я!_H}▄$ї╤fъу=йПзх╜╪я}Rї╤$eШЁkD)sПФ∙╦╢2Wг!:(rJ╙ы█T▒znЎs:Д тАн=╛Шв=цї┤бb╩рж JtK$МI}|Sъ#сАRц▐eо┤ще¤·жЇ╪╥[!с@Qs╥t^кi├^жС*Mъу√ъu╠ТЄ=ф ЦJ}xд>╘╜OЛф╜|]▐Л╛ў)Fъ!"eШx▀V9юФ2ПHЩъ{жд╠=RцKuGюФ╓Л&) EM %ИЮЦ}5┼~=шp.╥┐#эёEiПёcJ{lLjП▀Х║╩яiCюР2ЁJшС")Г√е n$Ч▓ K}$▐╖eХyX╩SхєI∙xе╠c#PI┘=Ф\v╥v^4mч!9n╣╘ЫZ╛Щ<¤ФL┌╟7╒9Ш%х{╚iУ\╢■▌ўыщ:яюihn4┴MМФ¤рPЄ}[∙в┐BН╣ПjЇ1%нщ~xЫ╛/%-е-Т╖iфЧЕ╜]▓▐· ╕пBOууhЛ_єЯ╞╩┌┐└аьпПлGз╛lНN╬GGrZЕIЛУаK╠?EЭ~-kїg%шМЬ8qb╪<3?▓▄4kFдoЭuУаиюFк█┤О9В╛gзШ5ЪhWЦэ┴сэs╠M┤йR┐c}фМПJ}№БїС3>"ї1g√лfН&┌Ю▓k1gN■┤Пo?╘В√ю]n╓╞╬°НhyВшWўgЩeвюЕ'"""/sП╤9АБСKh╣ДБСKh╣ДБСKh╣ДБСK╞я/├gI¤e°?№╨мQ╛т_Ж╧╤@ы$  ЮЬQP╢ Ч ┼H╬°sйП╖X9уr¤_╛░>r┼]y√_ЙЯuЎФ╜╟@KpъРИИИ╚% ┤ИИИИ\┬@ЛИИИ╚% ┤ИИИИ\┬@ЛИИИ╚% ┤ИИИИ\┬@ЛИИИ╚% ┤ИИИИ\┬@ЛИИИ╚% ┤ИИИИ\2!БVд%АВ@ "f}▄D[╨╕╛Я\!╦·<5█│э┼]▄оЄvїЪН"$╟ьы┼Ayz░/ ╟m╤╧х]тg▓оў╫K@Ў│╜у┤∙d=TУ:Яl╖╢%-Ўє╔'Rkе>ЄR▌┘╘ЗФБ▐?y╤e┴л}5╕'╢-╒1е╗еї>▒rs<жХ?ж╘╟Z[}М╩гЦ<оПJ}иkяv)╗▐,█╟1)Зoкhїb]▌Аy>Ю"xв│ст0~╜a/т┴╬Ср╞Сtx╖7-─ G¤XP╞ eх&°┘█uшЦOАщf=Q%юi╢^п┬Пю═ыЁ3УТ>_/kmЗW^GчлU∙кFВ┬вf┤5╩9ше┼(┴j█∙фП║е>Жд>·х}■HъгEъуРIu$eЁ=y ?╥K3>%eTe уХ▌@ХФy╩cJ=▐#ї╕CъqЮФяПьхЦЎШТGъгHЎ╫╟Ф·Т·ИpщЄх∙└Ф·8$їёФ╝╧їRvЭRv▒р&╜^ж^ъГf┐v┐ \│аєЇzЗ╫╦/╣vы0(ї1,я?,їQ'їС═dпФk╗Фл╟м'РЄ H∙tK∙TJ∙ДУ╦G╩\╜Ю^д╠ї1Tйc┐ьп╧EъxPъx$иDГ┤9Э&¤\╗Їs сmкcц╜}R?лд~■M▐╫oе~BR?┘0╜oпJ]?╤╢H ╒╨°█Ї╟ <л╙ЇR┐┬╘╧йЯ╟д~╠╣╘·д~юN №Rхгq3юБVдe ╨▄,Gу,║/ UуЮRыЫ^┌А╩бnь┤_М)Y*фГд╛ w°<Ш^hТ$э└Qа°BпY╖єр·╥ о╫√FpЁ╪ К+VтzЭцФOQеc╬│╨'┴Ф3тпй╢йsРЇ┴nь.к─\[Z▐Р·╪!їQeъcЪ╘╟_J} dм╦EЄ■зЩ2°7)ГUЕхX%utн)ПiУ№└Pяы5+░Sї°┘зRъqZR╣е7уhEZPj@█"│>Юд│Р/ zX]Mo<бз░/╙Ез┤x▒NOUий║█╗b#aaьУ┤■НFУ MOUzё∙╓ўащ4ЭєХгй▒x┴L vЖpOcЫа∙┌Ё┬▓аЛ`чю/J3ЪЦуTге>юС·ш6їa`RТ2°СФ┴4╜┴АФ┴зд мїDЗО╩7╣┼╕Vнш└оERП▒i└{дїhЧ╙1%p°Ф=╘╟1╧Oс\rЮ╘╟^ї(їёMйП^SЗ3v╠х°▓╣^їЯ\пЛхz¤МJ╥┴[ЙФ╟║°╘т7е╠нQ▓0K┌5i█░ws~╨YЕ·&╔kЫ:L╦ёїЄМ n╘г╘ЗЪоn1їa ЁL-МРФAeкrХЄщЦ4┐ФOlк/ хc1Р2пТ2ўJЩ╟з∙д Ф╪Г<}є\УNOqz▒F·╣Оx?gIy╠|зВї┌ e9╧╘ObpУZDъч│R?VPф$r\╕%!h╪|╖Фх▌RЦ╢)└┬Kд~lAЮ═s#e>7уhIcмъFх╩ ╩W▀К╘г│╘c╢&н╘S┐nьБ╖┐ПйЖ▌+▌ZтhS"юиWSЗa▄Гn,РNFhО∙╘┌BД'7гн╢mУQcЫтМПжIgз└ЇA^Ю░╒╟М,ы#>%eаВзy)?TZ░f3FOуI=■@ъ▒_ъ▒Hъё)▌A:│0Ик╨":8У╪н%ё║╦ъ\ЄЙн>жfUji!╔ї║Jо╫Ur╜оMЮrФ2 жФ∙SRц╙д╠Я7mчР┤kфk┤╧,QSЖ¤°f}┐<Ў`▐Р╔ЧНTпЧпlїс╦ж>д\е\gз)WM╩GMГK∙°е|╓ЩЄ)Ч2Ц2яЧ2Ц2пЦ2╫i╥д ╘й@Ig╥№╥|ё╫Ё (¤▄░Їs ╥╧ym┴T┌cЮ-тїу╔▓~┬R?>ч·QвP%¤Ws┘MfГ*K5ї╖C╩rЗ<>+e╣╩╘╧ S?╓=XV¤|6^?iє╤╕╖@+╥RЕю╩'ъsHO┴й╟ ЮТFGб5к4+╙п∙qГ6╖h╒╘┌Q#ЭП}Z░Y╛уHG▓:∙f`щИо/лD▒|+TSХО∙BыЁаЪт\─ї╛r\┐длa;}М|Э6T╘HС~ тRХRд>╥нгJ3UwH▌╝▐╘НвZu\│Qєу:[=ж ьRє┌%ЄAбВ3Y╘╔┐╩q.J~═4чТ7ЇФЯz ърж\ъCН8M═Ї~фzэTSОr╜~Fо╫╧╚ї║Lо╫Ср╞П+me ▐ОIyэХ6░Vо5ЄTo┌└t█ЙрЧбИ-X╦6шSR┐^▐QгH·1иГФа╘Зй nRЛH╣H╣.Ф2UAС╫ФkU╝OЄc▒н|FВ Ше╠Gzо─└о\┌@ь>лiэrЬ╤┴Вх╥╧НМ~93пйQ$¤╕B0┴Bkд*s¤<#їєШ╘Пy7┐&ї│JъgфцїИ║▒╜щ°k╒q═Fuг|hЯн,╗Є%*И▓ю┴ъ└3R?▒`╬9НПq ┤мръ╝((Р└[ЗББ:x╟єЧЗЕЛ░аи▌tфъЧ~kд╗HUКк_%НX8[║Зv╝,ЖfFТTА6╜┤_▀░[^иРжW╘lЫ"aW╓ы9ц╙ср╚Ъ~=є<.╧з йПyRж>I}М `д>╓J}д■5bкй║^t╦■╒ ╠лэ└э╥∙КЪzУz,Тz|┼VПj*1░K7¤'╟ Y┐V№+∙р·╦┌6k:2.╧з йПkд>╢Ш·8&ї▒Cъ#a─IъуЗR╛ ┐F╘┌ О┌о╫]▒ыU╩|ЪФ∙╢2WS{*x╗P┌@№FwY╓Ы6аябФ¤╢┤z%XЭ╧Z╖▀╖еЮП╘q║╫╦;RХRkL}Dд>F7R5RЎЫЮ=Rоё╧e Ыr╒╙yR>~9╩[∙ийD╚є5RцыRеi0IP┐VT┴[╡┤БTsVаg╬3у1єXa╣╘╧cR?VАщ√оФl╥HХ■Uт╝д· И╘о°м╘╧Zй5Х°"ZdпЇ_Х╡Пbeб║з╩`╤^)╦OIYОмw'v▓oHЄwI~ ▐кk7XїУ1НЗq ┤╩╤6, ?╢ДЫQR╥Мp x■┼Г;Ц5├╗█kН"эЎcuь▐зiсбф√╢╩qWm5┬╥aш{жЪ║сХё;2]иrA7к¤═ТЄїRСo░V°ёBУ╔л^п"М&ЯIWЇиW╛OzP)їQ$їбжф■J╩'(хУ└H= I}д╝oK╩аE╩ aк.║WВ'uчD;Zд╛■к╔Z╓ъ╤Хr▄.ї8$█╒ыЇИWGтИWкcКW╗ф8н*Ёи─ўЗ▒╩^JЪ|∙┼ГrйПiRzДIъcYь^лйЗCR╛ ўm╔їzП\п╗фz╒ўhI╣NУыї╦║М╩q│Ф∙!)єxЪФyьf▀┤╘1УєU─ЄїтЗRзїъ╫г▓╢CЎйoК¤╩ё4_/'yФ·ЁK}шС)йПfйПДрF▐єа╘Gц√Вb╩▒R╩gP╩G й)┐ФП5С2яHNУ2ПНиЇJЁJшЦ6╨#mа-╓дЯS#є:OЄyf8f~Ы%ї│VъчSЄ▐>&я█'я█71║~▓╣o╦ИЖ%zMЮ<Ж: О╝M╓Rcж;jяТ▓┤^п@НxU╟м╤D√sйП╖X9уrйП9мПЬ▒G╫╟┼fН&┌Ю▓ў0gN■┤Пo?╘В√ю]n╓╞╬°■ъ0╞D ╕ОfН┐Й ┤ИИИИ╬ ┤ИИИИ\┬@ЛИИИ╚% ┤ИИИИ\┬@ЛИИИ╚% ┤ИИИИ\┬@ЛИИИ╚% ┤ИИИИ\21▐Б·╦Ё~°бY#""в|┼┐ яbаu&'{■∙чЫgDDDDю;╗■ """вs-""""Ч0╨""""r -""""Ч0╨""""r -""""Ч0╨""""r -""""Ч0╨""""r -""""Ч0╨""""r -""""Ч0╨""""r -""""Ч0╨""""r -""""Ч0╨""""r -""""Ч0╨""""r -""""Ч0╨""""r -""""Ч0╨""""r -""""Ч0╨""""r -""""ЧЬ8qb╪<3?▓╤<#"""╩ў▌╗▄<;оZDDDD─йC""""╫0╨""""r -""""Ч0╨""""r -""""Ч0╨""""rЙk▐схЧ_6╧(Эn╕┴╙Ц╒ш3╔оRп}█c8`Vє▐и▓╝ ПЭ╬Ы;Л╩х¤ў▀Gyy9^|ёE│%Й (>ё |┬╢╘╜d╥ИИИ2╚Пн+▒єрAМ/в╘$╤)▓Чх╬Ыё№▄╙Zg▄ЕgЮ╣ 3╠j╛RAVuu5f═ЪЕып┐▐lMсъё╓Й8бЦ╖DhGrИИ(;∙=uШ0Bc3┌Є╪j│Mпп╞ъ█м¤Vў└cёч·(Тх6s ╡─Fyd┐·&╝∙fц╩Ў█Nkш'╟═ШЙ9цйЦPЮ#г] хгF▓т#Z#e[tЩж9N.▒Yы╓н├Я¤┘ЯЩФ Ўэ┴ы>Кфщ╨#єm#]єёH<·z uЎ░~│Yyйnd▀ф╤25gжфйS█ъфH╔√┘╢┘зюbы ∙н%>g?╬хлё║┘Ь(щ▄U▐TпoЧ*=сшЧЄ8CR*аК[з5]hWфБяї=╪зВм╦Я┼ч▀К`йFИl$ш°▒}┤GMAZ╧ИИш,Ц┐БЦiy│ zЦO╪ё<▐╝}■щ}а╟F╣Д>Ої╘2Бг1оR7┤oD|д*╣<у$]нo▓R╫N4^▒√YI╩Б╟ъ╤4g#М|║уL {░uFAЦЄ╥Vl╝zfйчW7йЫ╡─╨Л╧ZБS╤M°╬╚ые▄?╒╣;╜╛Т)ЭИшЧ╟#ZЄ┴┐│{Ц[STs╒}DёO√S ╟▀╬ЩNл╧╤Ri╖[╙`gх═ЁеZў╢щ╓╦S/ъЖw Ь╨4╫lЫЛчo^П╗жЩ№в╡5IX·╘rУоВ╢4╟19&К м6l╪аЧS▓╘┤_|К°ё╢{QTt/V·F╢▀│╟gF┤КpяDшЛfYь7├▀╪№c°V_.┴╓,4л√║.7√]ЮюцЇ╤м~щhОw∙j~▄,aМ╝■╘ЩyНС╫OGОc{╜╘√з8ўЧ╥╝~\жt"вs  Ю в■9  Ю3Уй U┌й№┼wU╓Ў_Ёи_#n]Д RИИ╬*╝GЛ╬j*x╩vo/┘■Г)cРEDt╓сИ╓сИ╓Щ╦ж ЙИИ&G┤ИИИИ\┬@ЛИИИ╚%оNТ│lж╔зЙИ(Ч╣hЭы8uHDDDфZDDDD.aаEDDDфZDDDD.aаEDDDфZDDDD.aаEDDDфZDDDD.aаEDDDфZDDDD.aаEDDDфZDDDD.aаEDDDфZDDDD.aаEDDDфZDDDD.)8qт─░yNDDФ│vя▐mЮ╤щ(..6╧R√─CG═3Jч─¤У═│ь1╨""в╝анП№уfНN┼З~╚@k 0╨""в│н╙w*Б╓┴{?жi─ЇG~лO'╨т=ZDDDD.aаEDDDфZDDDD.aаEDDфЦУ┐╞K/¤'═jFз║╛;Ёn╗э10лg#ZDDФT@ЄЇ╙x┌,/¤:CУ├╗тчпЧ]ЗM┬Yкo5жOЯ>▓мю3 g7ZDDФgc╫╢CШ6  °┬╘r-╬;∙бI╦'¤Ю~╕VЯ┐╡\ЛCЄ╬╬N╗ ╙Ч─A│l─6Ь б-""╩Cчс╝є╠SL┼5╫L╒╧t%z zаKП~э▓1и┼╓F╞╠■I╥єе_у╫╗b█ЯF|@╩~╠mяр7fєИУ8|╕t■5rц#ж^У╕Юxn#чЯp>▒i╞,▐╟─9А╧Н;Dй┘вФ>Ш╕озу#^луAШ╥b█c╙М √▐Ж╟rxюСБхЩйШv╤√xUI╙mч}Є╞°╤о=я№Z╥╧√$|▓ б╪ох╝╤49К; _l ∙╙ph╫ш√гRS∙═;89-╢¤"╝Ry╒1▀┴y╫Ъэє/┼∙╓▐#N╞б▀╪┼TП3 ╥УxU╜W иv╜s^|$ь┌єNт├,▀╟Д9░╧┐93gШїФ·░znцl┤F╗v6ю┴r5╡(U}╙Ь°H╪╞9aь╫√Жё╖f█┴Э7у∙·▄╜╧ЛБхЭй╫Шаb┌!k'p┘Gv^}_╓OъАcъ4 ДLдu°╨I\·╔й&═ljQгO┐QБKТ4╟─∙ЧBF√°yV@е╥ь█╙9 <8■щ╒дуЬ7u╬ KtvЮ>gkДKПВe√>&╥^╠4OS:░{оhDНтЪ1яf\ё╘6Ї═ШЙ9x ╦═ЧS√ъmfDK┤7▀TXnbаEDD∙kъ5p]ЛЛTв"√╜[Ў╤дйЯ─е'╒=PЗqшф4LНН&I03?6дЧ╙wщОy║T░Їu╛f¤ФL┼5·<зсРм╠Фbжў1СT░Їцє╪qZCNеxPП\═╟6XЩ)E ╩v╞F┤ЇТ4 ЩChQ~Q┴П}╩PП■ШвєGВиУЗ┘юП:SзЭ─б]Зpr┌TYSЫT└єb3Бiе=f IATъ¤зтУЧяl│▀7▐e[O:7}5▌)я}Ч╛K\єqщ∙j4+╦ў1aJQ╙4═╣яJщ[m[╫┴X┌╠Ж;Ю╟Ы╖╧GщБ╟░Z▀АеоЭh╝bЎ#q▀\╟ ыРИИЄ┬╚ ux┐~i▐ЙG0чу╥∙7тУ ▐ї4╘ьЮ▐z╤E▓ыy╕ц╞OZБХЭRў2┘F{Ї6█ ыjd(╢┐СЄШ╫%;╢о■lГ╔0ъl╘Mэ█F▐p╤╡°┬'OО'с▄.┬╡·╝c╫╙п┬ЬОЬю|▄и▐xЖўС  ╫б║й}n╙ЫfM▄╛kЎу╢z`¤3waЖ║┴]MZЙ╪иGй·░z·r<е╖йБмЭxцо·▐нС}ЕсR╟0лcэL■пCZDDФ°ЯJЯ>■з╥gЖ й4QbаEDDDфZDDDD.с=ZDDФxП╓щ;Х{┤(=▐ ODDg-h╤щcаuцhх▐гEDDDфZDDDD.aаEDDDфZDDDD.aаEDDDфZDDDD.aаEDDDфZDDDD.aаEDDDфZDDDD.aаEDDDфZDDDDо■2eбц}ф*IENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/images/screenshots/bool_selector.png0000644000175100001770000001737714654363416022513 0ustar00runnerdockerЙPNG  IHDR▄80°ЭбsRGBо╬щgAMA▒П №a pHYs├├╟oиdФIDATx^э▌l╫ЭЁ/∙$i )ж!Ч4А ░░Ew9├Y:SСRdG─\HFзJзмalщЪl+Qсk%)ХQС┌%W╔Р(ЙгУ"K!■,B ┼н|╞msh╔┌ЛГC█$╙Д$Е▀{o▐ь╬╬юь╬┌╗╧k√√Й&Ю?o■э╬o▐{│╗?fММММВИМP7g╬=ID┼rэ┌5▄ж╟Й╚СA 8"ГpD1рИ W└▌╝y╫п_╟зЯ~к╞GG∙ Q6y},pу╞ \║t W. Вь3|y ╕єОQ╠└-▄№Єv╠Ш▄sў,Ф═>°f╧Юн╫$"∙▒АяА√Ё├НFqя=└В√nрлw▀┴vC█M`ЇFo▌Aw ╫?┐М,└╡ыw#XКx@oБhzє¤9ЬJЬ;wБЕwаl▐|>║╣■0■tн╝├r№щу╞З╫лq¤╓?тkєрЫЛ Кєчгj]Ъ>.^╝И┼ЛЛ╓╬ 5мY│жdпy|g╧Ю╒Sщф2YжР|▄рр ╝6f╧┼Э│цсэ wт°o О^ОE Д_ s╦╩1z█WQ°<┤Ё|°╤Ф?Ёїd▀НU·═Q├к▌ИыEеы(╢╬XЕ▌Е8Pu■┌V РяўУO>ЙП>·]]]xє═7╤╤╤бЧЦЦ╛╛>№ыкoe :9O.Уe ╔W└¤эo├├ў▀о╞ zэя°ї ╛З√ц▄Е7є.^¤╒яpЎэ?уK╤Ь╝uKtъ┤;n┼¤ў╞E5Ъхю&/╢к}╪<8к╕╚с╚Є(їтй╔мХ╧т╠ш<[йз'╣o√█°╔O~В╣sчb├Ж xфСGJ╢Ж[▒b~sц╫iAgЫ\&╦Т╧зФг"Ам▒Oо▀─WюЩ)·r3qэу╧▒х╗+░ры_▒║▄~█M|ё┼Чz╩-О▌OЖ▒№Hъ┼╢nя^м╙у4╣E"№■ў┐WБWк▄AW╠`У|▄мЩ│ЁёзVэїїywу╧|М▐?№ Х ┐жцy∙фє{Ep▐ез\тЗ▒пз <г╦к ╢n]%ЪЪ[┼Фрj~nU3%w╧9н╟П&╫]Х▒¤&nл2l;eЯ·8▄<╦╕╖)Пхq╝ИДл─<╡╫▒ч:╟▌[╦2ЯGiР═╚я √xї╒WU-W╩ЬAW╠`У|\┘№∙╛Б█oЯБY│fр┐■є[°ў√&∙ц¤в√- ▒│g▌й>Р╙кi9уЖп.╟┬Eхz+╘QеGу╗e`╔ ╔8=8|M45eн'.╕*Y#ъцч`чwЦ═F\р╧пщї~2}╜г╗^~$╤┤▌лnrЯО&яр∙Ї=╩╚`л┬╛═ГОmо├^Qа ╡И╚Є╓NrЭг8Пш╜Я&ЇДwЙ5JУ ╢_№тЕBzI╛.ИТ│■3n~Ар╢q─▀∙нzpч1╝щ,> dёєob8~_~1К┘ў,─В ЇV2шIЎ╫*Я=#.$y1:╒bsГno╞pN,M╘ИвяєужD}u°─■┌│P[ЄZп*И┌Oн5╘>um$oПЛ║╔╜вW]Г 8Я╬Y╬sч▒M/\╖AФ<Зw№Ч┘o+їЪ═цlF:ЫЧ┼р+рфЕ$л╪[ШН°ЁзШy╟'иXpKzK╝Де 0Де─Puў╬zЧ zf▐╜KЧ-╙[╚аr ЦO╚╟└9=ъд^Мт5<й╬7┘Фk┬]Cй!нVТ№ФЩ^NЮ<ЙEЛщй╥хю│9ЫЧ┼:ЯMАЩ3gвжж}c!.}p'N с+xыэпбoр~ЬН?ИЮєХ8ї╦Ё╟Пк░4╕UKЦк ╫█:lУн╗*П~СЫ ╨q└.,·:╧┐h╫U╓:jВгDIзь;м#;G▀Q╓┤ГСZЬУw╜╧ЇfдГWЩ╩lо═▒о[╓sЇKўї╘n╜╞ЛO~Рє#б цїАдиA'к~qS╬╧═Ы7G▀~√э╤ююn5И╗┘h4╦╢D_D~31╘К╬Н^0*·:гЙIi02ZЫ(ыZц▄NSУc]k;MM╡ЙхMв:JУrMb--eЯbP+╗О-cIЦK╬╖gЛюЧ╧m9╧╤¤z╕зm╬∙^у┼'П_Ї▀ЇTiТ╟╪╫╫зз╥╔e▓Lб╚°WКЕў▐{O}( K°+\Є╬■<ВГSч│.ЪЬ╞ЭbссЗFuu5ЖЗЗ╒пИ(╗qЬt▀}ўйа√ь│╧Ї"Є┬м]DЖ0kСa 8"ГpD1рИ b└─А#2ИGdР·NПQС▒Ж#2ИGdО╚ СA 8"ГpD1рИ b└─А#2ИGdО╚ СA╙7р║├ъ▀0Ыю╓3(/|¤╞dМ7ДЎzёb╦▄1Lп╫╛au▐a1fjпWпC}√РЮCФj '/┤j┤Ўб╬їпд╪CдNЩж╩[Ол╫сxKЦвЛж╡╝nи}ф┐╪\╙╓Я9└Ж┌Q/я№їэR═л░я■ЎРи\MУю░╡\M┌█rr╗кd*{={H█╛=xмЯ╡\Жe nTп─ ╒|qЮоs╔y╬її╔є╦є╕ьє╡╖Щ╪Ч\ЮсuKь;ё■8Ўэо ╨ О2╪dэjпЧё¤)ВR}¤JI▐W▐▓M5ефEэ╖Iш╛╗╗я■J4Ю╣oрРq=[]─zУ√█`▌wф╓O3╔V.qь╗ь╛╦┌├¤_чЬCоуяV¤H┘дХ═[╤,╘¤&╗┘oB<кG╟╔ы▓'√pй╞╨дмCDў :eУeоъ?Й√<[%"T√┐г1Q^▐ ╒ ░ю╡-ёnб:▒-ч2╤'r▀8У═'1T╖вWЎ9d!Д*э╓РЎцg+'Чщ>Й<╒DSpЮ▒√?j~Єу%█9√хy\з╘╟2rЫбN┘w╘╟"ПQ]Ею_ZM╝f╤Ызп┐я╫ЩJЬРТY╓Dу 6в)в°5╤U25e╞А#2ИGdО╚ СA 8"ГpD1рИ b└─А#2ИGdP╤╛K╣vэZ=ц▀Й'ЇQщ╦w)Лp}}}z*╖Х+W2рhRсЧЧ╜╚▀╥e╩Qщ5ЯиHJ;рd@╪┐лK їр┐╒оЩ╛a°▄▀P{╪zП║├~m?╜ЇЦ~ ' AўСИжп<▓tQЙ┬▒X@хЪщ> 48~{╠╜У╡Iйя┤VRYk░яд╓ЫЪй6МсЕD┘ ╡dJmъ▒▄#m╞}жгХф╓╗\r╗сю!┤'╞х╓uўzт┬noNцmйЧ33Х╦pN)╟d╫<~ўчb¤╥┐нVЩ╞ОХЫ╞>&шЭ чL$√вєc ║╓ы бcЧ5_╜йvm(У┘i┴zгь▒цЛўн═╬fНhц╚ЬШЎz¤╤Х▓\slCэ│╤║А╜ўiгХм6ч▒йm╩─-z\ЭУ╫▒ХгeП╕xu рx╦яspGВмfЩ╝Iп╙Ф┘╥№ю/╜ЩQх┼qЗ:хzm╡їлm0├F╥фkR┌IД─№gь7▓"Р╝k:я╬НвёН[Э#е^∙cQуH┼Зб8ввбcg │2~9Ц█Ьi∙d:@▒V\n▄sЯОcФrЫ<ч╕\цў╪▓ХsЗMж*TыX7О║ИH┐√є VGаBМ\ИХЎЛE╢╔┘дЇ"/hХ]g>═w`{еPw╗фsЯ&О-яsиCDХk└a`║╔9Ц╫Bfg█иnэ░R$КJЗLБШ■╘dZЫZ'9j!п$▓)Ig%WRZ_ьD┤▀у>Ўй°-чФы╪ь┌g,ч na╒УБ╫П╢Qc├ч■▄ъ""0; uК┐b[!┤lOжШ|}8┘Й┐дЧ╣Х╖`[0Y▐+Йlu╫FЇз\йIi╒`ўЭЬ╟b'в═╢O'┐х╥d96╣═Р╒№лoпЁwN"HEuд╦Wгkу┤Ф√▌_Ж-█П%ЗО!x,-иz@3═ЇЄЫ&~╔&!╤ТC╔}╡+_ 8ЪLJ*рИж║▒─┬╘{hBT┬pD1рИ b└─А#2ИGdО╚ ~'─уq=Fdймм╘c▐°┴ў╔А[▒bЕЮвщюь┘│ ╕b▓ю┌╡kzMWsц╠)j└▒GdО╚ СA 8"ГКp/я▄ЙЭЙс Ї№┬└┴Ч{pUOyє[╬IмS┤ужщоx5▄№╒╪▓};╢╦a░?я ?│л=/Л@╪ПиЮЎт╖Ь█└┴¤└&ы╕╖м╛М¤rT8fЪФKЦ"x∙ о^э┴╦"Ёzъ┌CM╡РUг╕КЮ─╕^╫e^э╙╪╛e5цыi/~╦е└;╤ Ц.▒жцЦa╛ЬlУ═╒+╕l╫╠D`жWl▓/XР'КK░4┼;zCWcчpTPУ.р╘╙╟ЧNЛоЯ╒dїz╕т╖Ь█Т'ф#U▌>=ЫЮp╢Й╞З_^°хe▓ё╦╦DSО╚ 6)■ХЬ°╘"cКrc└ХШ▒─√pD1рИ b└─А#2ИGdЯR №XА▄°▒@ёГorт▀E╞//УН_^&ЪBpD1рИ b└T┤З&2єЄe=▒╔NЛW2гЄмЄJN$sЬ╚Ї zr■ъ-x║╓;3IъCЩy┘N Ыэ╕╜╩9ч ┴M╪╬4 У╞ф}h2СЩЧп^┴|Э=y╗╪∙№╙╨уsч■2/╦Д╡╔r)ч7ЁNr■Ў-X}∙-▀√жй╧LУ╥tцх%O Yй╠CЩя$Ч~3/_┼Х╦╔rЙєSу╬}╦rєQ╞┤_д ╕ ═╝|5Жє╨ fєХ5єЄe\I,РAэЬ╢oя`iAЫ╥4┘/рJ"є▓шO╜$┬mCб╤.┴[Цс№K╓∙э▄∙Nз╘dєP√┤╝й,┼;;_fУТ╠Їс&$є▓|xё╩╢<Н,╧K▓╦Цyy^-Ю╢╧O6q3ЦУЙeЭ5Mwf·p┼ц╬╝мж╟lY2/╗ўС ЪПN╦к▄╒ЮГОMЎ ┘ЗгдIp~2*л@ ЭN4∙vтeЯэ:▀ЩЧ&ЪУчЧ%?vнa╟~хУ╠q╘░4хЁ╦╦┐╝L6~yЩh a└─&е└аТАZd2рИЬpD%f,▒└>СA 8"ГpD1рИ b└─А#2ИGd╨Ш>З#"K╤?°&в▒cУТ╚ СA 8"ГpD1рИ b└─А#2ИGdО╚айpCэиЯ;sїP▀>дM╝йpbvО`dDЭ╢6Г1Gев8чкeц╓╖cH╬█├r^▌iхєdy9.┘╙ъoсz╗№\Д╒ .uDъЇ8*и╤гD%ар┐(+√┬sQ╦и _J3░g╨\▌кj+ ║S╩ ╡╫г:╢ #╧─нЄ╟[P.Лем▀ЕН¤╟╤"tЗaнA"╛▄ьuэm №╡╥─з╔s_ф╬А╔4▀9]├╢■v∙Y_ь.ьLУk9╤°ФFЪ<╤Зъ╒гЗ┴FейЁW╫АPoОхzPQ^Й`o+^╨¤░бc]ш 5X═├▐.и╣z╛O╤}5ёШ;аdН╚`гUДЗ&uИt╤ZнnИ~[цOФыoC┤╤*W▌Dзь╠Х╖`O[Н·┴Hs,И─sа╒z╛ъ┐%ЪЧIVАЎ&ў/~4@ев°й╬х├Н├ I>:wЯПhВХFNшVП■5╤xГНhК(~ G4EХL GDЩ1рИ b└─А#2ИGdО╚ СA 8"ГpD1рИ b└T┤яRо]╗VП∙wт─ =FT·╞Є]╩в\__ЯЮ╩mх╩Х 8ЪT°хe/Є╖t╬L`6п∙DE2бwщ╥%м[╖╟О╙s\T║╟oы╘P╧<У6╙7 Я√j[яQw8Щ╩╨ї^N╫_сOX└╔`kjjBEEVп^нчfP╙Ж~Х╘╒Шлд┤ сX,аr═tь▀3Aп2!ч ╢]╗vсо╗ю╥K|╥wZ+йм5╪wRЩ▀╥ЮЧZ╞ЁB"ЙlЖZ2хь▒▄#m╞}жгХф╓╗\r╗сю!┤'╞х╓uўzт┬nonEп╬євjМLх2ЗS╩1┘╡Ч▀¤╣X┐ЇпFkЗUж▒гCхжQч┴╜JQN6eУQШ-я`s&ТГ}QИ∙▒}╟ь бcЧ5┐╝х╕╛Л╩∙A┤┌i┴zгь▒цў╖н═╬жQ7┬2'ж╜^ Ftе,╫█P√l┤.`я}┌╟h%л═yljЫ2qЛWчфulхh┘╙Ж▌8▐r┴√\╟С лY&o╥ыte╢4┐√Kof╘EDyq▄!YУЙ9╘╓п╢СЦacш║Р!у┌4P╘АУMEX2└dаНйfs7)э$Bb■3ЎYHfЎr▐Э;Аh▄║ш)ї╩█ИG*> ┼Еu7Vы╔Lc╬х6gZ>ЩPмЧў▄зуе\╟&╧├9.Ч∙=╢lх▄╟aУй ╒:╓Нг."╥я■<И╒и#в ЙJ√┼rТ-┬m╧ЇLU╘АУ%╦║q5#¤Р┤JЗоГS▄e}╖\▄БЭ-Е║╙%Я√4qlyЯC"к\л╙M╬▒╝2;Ы╪FukЗХвP▄P:d ─D╗XbВ▐вўсЬAW╘`│9j!п$▓)Ig%WRZ_ьD┤▀у>Ўй°-чФы╪ь┌g,ч na╒УБ╫П╢Qc├ч■▄DM> Е:┼_▒нРZ╗=йjw&ш5Є╨D╪/∙K5фlт═Oщ├╔N|▓KШк╝█В╔Є^Idл╗6в?еcСЪФV v_╤╔y,v"┌l√tЄ[.MЦcУ█ Y═┐·Ў чр$ВTTG║|5║6ю┴рw╢l?Ц}┤Xр▒Ф&#ЇZ°M┐фЪЙh╔бф╛┌Х/M&%pDS▌Xb┴HОИ, 8"ГpD1рИ b└─А#2ИGd?З~Ў│Ящ1"╦°C=цН|ПС ╕Я■ЇзzКж╗¤шGE 86)Й b└─А#2ИGdО╚ав\2═Ъ╥╙│НЙ·Щ~>┐юF8╫пЮ╙┼╓30C [┼QспЖs&вщє╛Ё3╚#ЩиХo▒z┌пг[ОМbttГСsx|+CО ╟LУRжФУ╔gd %/СФTMчHВъФG2QХ2Я╠X╩Qx▒ ╓YSХ ЫQ{nqkТh▄М▄P√.t╘DИ╜оддYУаfa"Щhх,яЙbPOНWё╬Э%+SW╔Щ°╘9оzЩ▐╔DiЄ2╙З+hтГ╔Dу8WDХЮ$/3}╕BQO)3ЫЪ_И'бы░бщE╨╧IтЗўбg∙TZУDу6й.Яdвъ)е╩Лo5m3>Д╔`▌▐#└у╓╟Uсх8▓W?A!*■Z@рп╚Й┐ Ъ"pD1рИ bN`КrcКв├З&D%ОGdО╚ СA 8"Г°ФRр╟ф╞ПКИ▀е$'~ЧТhК`└─А#2ИGdО╚ав▄Дf^Ў[.#┐ЩЧ╜╩9чЛБЙd╔бx5▄Df^╬#C│Ы┐╠╦qь^Х,7*╙ам┌m%М=z 9tСs╧c73╔ТfжIi:єrЪS∙═╝<ИhO▓╓m@УЭ0v▌^$є╔r╦▒Дi┐H3pЪyy<Ъ│f^>ЗБD$V!XыЬЦ5аlR└Ж╤╜`▐/▓/рJ"єr▒24п├▐┴═╪Weў╒кNй╔*ёь┘д▄А3V▒II f·pТy╣Ъ│e^о|gT?M Г╘f,'╦Ў ╩ЬА43}╕BQO}d^Ў*ЧSЦ╠╦ё▌XХёйеh>>67иrё▌[5Ъь╓"╚\щдMкАєЫy9Я ═n╛3/▌ЪhNю█<И3╧ZэI╤эC8╤╘ФO2╧@/"тп$■ZАЬ°kв)ВGdО╚ ЎсжX 7жX *1|hBTтpD1рИ b└─А#2ИGdО╚а1}GDЦвЁMDc╟&%СA 8"ГpD1рИ b└─А#2ИGd╨Ф№nю╬ўїy┘■А╦ь─ЙzМ╝м]╗VП∙╟АЫж№▄╩Х+ї╣їїї1рlv└ ╖▄н■R╥┬ЎO╒_▄°М5р╪З#2ИGdО╚ M]_┴жMпрвЮ, ╕║°╩&,\╕01lzE┐╒ю7■dлZ▐zROO·╝├┤{p#Гэ╤Cыqjx├jш@р╣GУAgУ┴Кa╟йa┤н╤єжu3 Й╫g8Ощr ╕В8Й╜╧;~■щ9└┤Э┌z#Y│╔`{Ї╓Я┌ПзТзБЛxуРx}N╡ЙW%iMЫ5Э┌2╪uП╥нВWZУ5b▓B<Й╓Dy=_ЦO╠╙█(A ╕B╕°.b+╓у;ю Z┤Б│ГxWM┬жe░ ▀└б│,Ў8яEOэO╘z├<╖WG╓┘ч0Xo╧ .^ o┘,┐ИW6йк2▒N█АПт{Ў6Nн╟бФV▀═╞А3х,░~З╕ШJЇB(║UXмG╙8kз╨ы@ь]ы5Z▒[э*qqV╚┐2xсШ/╔^G╚▐╞г╧сlтFWZpЕаj▓Cx├Iкц╙ЪмЯj├йїЗЁшt{Xрї·HЙf╢];э░+_"8У¤g9д6_Kо ╓`лшое╓^▓Щ#:vы┐уш╫╔ц╙╧▒#JШ2ещ╫ч╤╓ФЗ$'[ї┤г9~QtЎDc└█вя`=ЮГ▌ъTT@╗цХ(\Б╚~Ик╜ьfНїH√╙:lЛЁ╘~П'ШSШz}v─Т═>1ДPП5ЛЮ┬ў╧%^╖ r╘pтї√∙─B╔э┤Ю┤P9ч-,▒╧▀l№ЄЄ4├//┐╝L4 0рИ b└─_|OSь├Нёэ└А╦Н9M╞ПGTт╪З#2ИGdО╚ СA 8"ГpD1рИ b└─А#2ИGdО╚ С1└ ╨╦┐╗∙Ю═nIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/images/screenshots/codeeditor.png0000644000175100001770000012523414654363416021771 0ustar00runnerdockerЙPNG  IHDRоШГ4`+sRGBо╬щgAMA▒П №a pHYs├├╟oиdк1IDATx^э¤[l$Yzч ■)╡.гю╩М"CRц*▓+╖╥-B┼q`RЛЩс^ААzPЕ;▒╪"EьzXw,к юITbA1>└|pW╟Л√Л@░XБa═`°BwЖjfgDgге┼Ї8Ш4Л┌╠ОшйюQ╨#"леUЧ.╛▀w╬1w3єЛЩ_x ~Щ'hfч~1│┐ЯsьЬЙWп^5!В В ч%\▀|єMs*В В чП╫п_уЧ╠▒ В В ЬkD╕ В В оВ В ┬Е@Дл В Вp!с*В В \FоўwЗ┐■ы┐╞▀№═▀иуfSV╓AANЖБЦ├·┼/~Бg╧Юс┼_¤л ?№├?┐ЄOЪШ└?тя■сЧ11№╙▀°5\ ═╖ё;┐s┐■ы┐n| В В ┬ЁЁrXСЕы№П ЗЗЗ°┬?▐·т/Ё╞o№ВDы/H┤■╨№Ъ ° п И┐■█_╞xї^ їoр╓н▀┼█o┐mBAAДсИ,\╔ ■т/■_∙? *~╤№з°√°ec├S╚и)M5U`bвЙ_∙Х_┬o№Єз°▀Ю^├я¤▐яу┌╡k╩╡ В ВрgВЗм#p▐жdr║ ═┐∙7°¤▀ }s┼k╟ё/■┼╪╥y█╢ё;┐¤ы°ї_%Q·kS°╖OЎЁ?уw■yя╛ў┴√g  ╫о═_z▒[А_·пЁ_╛Й/┐¤р╙O?5бЇ┬A19б2пL▓HWеКьD┼┴=Жр ╫)"y"qВ Вp╒aq╫╧ЬGX┤▐N~U ╘ |Нэ╪═8Й$\? №s№є▀╓╜мН╫ ■°▀сЛo■°ЯЎ ┐°╙ Ў Н┐°╖ ;■с■ °П и▄0 фЧЫ°э/8дО_Щ+]Pb╨┬сКзr╓БЭк▒?O─r╪kю!уУУ╩В В юi▌▀√єёъКV╢ы╒;,WhТ╒G щп  ьЯ■*╛@цї╧  ўo■>▐·нж-№Є/¤■■я ┴ЬqP\╩#^iвФ2ЧИ9я╣ В Вp. К╫УнL$с·k┐·k°∙▀ш▐╘▀Ъ· №я сч8°_ =b_ЪT╫zёЯ■Ў $r  s└┘┴V-Г╣~"Uї╚╢зd╜=▒╗dё╚\4°№e╤╡╖ЧЫЮс║╜м№7Н2j╚[ф╬MT╧┤j┘lR┼#В ВpЩЁК╫УнL$сz¤7GЯ¤┐№╦°╡_Ы└ є;_┼┐№┐■Ч°п■╦▀╞▀ ¤▀у ▒°/Ёы┐Ў+j∙+>WS&■Я╟ёеw┐lBщBbЦ9ьДЯе{d╒╗Аz┌Юў█нcЛДд █ma▐6■*└j╟Ш~/7¤┬uIбD2Hа└■Uwq┐┤25╘з╫╔оd╬AA8 ▄д0#\L" ╫[╖nС╦_├У╧■ўЁЛЯсЧ~с└∙ <$S├﹤№ь┘_рo ╙Oс|Є?с3ч─?№}┐■O┐Д╖▐z╦Д╥Е┌!ls╪БsД:╔├VПl,ЗХL ЗьБэ,╗XnЕ\Ф?╙╩Н3]жh▒Їr╙/▄~ЇKл"Б∙Y59VAДDu E0┬°ЁNЁN8 " Ww▄х√П°u8Я¤ ~їЯ№'╝ў╓ №юНЯсwч~ў ЇS№ю█O`¤Ў!╛Ёk?├_5~ ┐·ятw┐ЄBb│ШOФ▒▌uT2иxиoнK7В В B/ВsZ╜╙NB╝F№8 °╒_¤U╠╠╠р╞;_┬│ Ё+°Я ╫Ж х▀Nт▀¤6■┬∙╘>Йс ї┐}╧_Z°▌щ▀ЗuєwC║тc╚нdPNw╬]-Єyь&тЁ[║╛Z6╜ЪlW╦c═╪9┼╒ЎР╛ё╫9=└C/7¤┬эG┐┤ В В \Bz}ИuТт5▓pe~щЧ~ я╜ўТ_¤*~ы╖~  ∙M╝·9Ёє┐∙U|qъw(б я┐ ╛°┼/!дJf>и▓g│╠*┴ЧB╔kзцдЦшк▒лh╤╦vKШў щk╚[э0}╩Шщхж_╕^RШ╦x?╬ъЧVAAД■┤ЇHsс═Вв╒┼пьfЬD▐Є╡ ю▀¤;╡9┴ЇЇ┤lэ*В В ЬСw╬ъ┼? ч ┐ў{┐З╧>√ ўwgо В В ┬°й╟╒х┐°■Ўo o╝ёЖ╣"В В уcфW■pKDл В ВpТМE╕ В В ┬I#┬UAA╕ИpAA."\AAД БWAAсB ┬UAA╕ИpAA.jВk?°Щ9AAДє╟лП▐n ╫Ч▀{╦\>yо]╗fОAAД╦яz5 мUY╕╩TAAсB ┬UAA╕ИpAA.чO╕:@6 LLhSдє╦ДS╠"Щ,r6√раШЬ@Є▓e> NСъВъЮM▓г■гХ▀)S═ЪЇ▓╔вj.ЯT<Фn!┬Й╥■AД│ehс┌pv▒╣YD▒╚f╗ cгil╗╢┘НЁ╚Z@=╪╢6╣Ш▒╕$╪игV;д┐уц2И▌*╒Юъ┐BuoУ┘ыи с╦я╦'U╥щнd╠ЕУГє]лщ┐чН" j·MqFМг~├█▀Ё\сгВ cd8с┌8@uчS╓,▒8;Е'√Э┬Ї·m╠▓╜1w┬^UаLVJ@М▄▓╣lдr{h6KHЩs┴CuЫъ?CїЯв║Пuн єZ~*╜ц°$Iх@∙з┐ц\#┌Я В0~Є∙╝9 g8с:9ГЕ▄ю╠─099Й╔╪ n_О■^WF┘ЖsD $KЯ╢!AЬЇLpз$│╞▐рЭbРїЎ№Р V╫╚╜ Гь║∙яK5Ki╨CИ╔lХN╜╜'Udщ:]6╧┘╗;№╚ж╦p▓/№s▒НSL╢№')сU╧Пmg!_jyлэ╬╙╗╙╧4Jb;М ├▀yDyжJ╥ЎI*пе)*°v;З√Эг:╒ tg¤ГХЯ┐~вФO Ї3aЎ!8ь▀S~d╝э# ╩∙iЯW:сk¤┌7З║'Zў¤ F▀є■бмЎ╗ x·ЯыЄmЗ1Pяk▀√ЛёЦ┐vун~├щ╫■№ё╓╛╟Х>AД╦И+ZгК╫1═qmаёШ К╙√икщЫ╪▄= W╜qшю┌t╠чн╟z X'KИ▌ZжW╚MЕ^e¤"e°┼╬╜╡r╟ve╩PL/╞[tl╚?╜зZqЇЕ^Lщ2j=Д╕2╜ :ИTЙЗmT8├?─╜ф╝─rыf°▓В∙xiл-▐┤]Eх-QpЗ9mм{╞9√∙ПB5kй<иА9ЬB╝М№Z╡U~╒lZїVU8ОJЬ╩▀ъ( Є╓!VФ=>Uy78T▀Оз■ї╣╖n+?¤D)Я░ЇG╔_oЧ╪ ╝ЎoLiАn╙TЙ╩Е ИЛоaэ╗Lsы:Ь ╡п┤G╪Ў╜Bю┐XNЗй╦W│Yзы╤╗┐xШЭ╦п`╩Я█O║Х╛(ї█ЯЁЎ7J√=}В ЧУаXН$^y|ЇIєх╦Ч├Ы╟ўЫwя▐o>Ў\{·р~є■Г╟═╟П┘▄o▐╗{╖yў■ce─.ЁрgУ1 ║Ц ╖.|Юйt3Є█ЄO╫9<╢.$┤;'█┌E(0zm+ Ы┬е┼ї]б°сЙ?x▐╞ж°├"B├w▒ЫЇ┬#Sh&(ё~ы^~╝Їє▀П▐∙╤t┌WиB╨║аэ█iєзUЧ ╣Ъ._~¤╩'Z·{█:╥сву&╒╥$сcо З{п°таўZпЎ═ўЗў▐ Ж╙ў■1├·с╕╜ЎС л┐.эU╡_∙ўл▀■Д╖┐╤┌╖f°Ї В \Xoz═┐·W кe║Э kU■;zПkуЫ;Op¤ЎМoО▀ф╠5Х@╧╗ГФЮK╨╡╫Х{l°5╩╜D╡-='Х¤нё4"╩УЫГ░√╟Wпxu∙Ъ0МЙDX¤┼rX╔pПkEo√3╓Ъэh╞╤╛O2}В П^"5T╝║єZєU#Щ╟═√u▌<%7ў┌vўю╖эzЬЧчт╬╟є╧t│Єы┌g╝sэ╕л╤°щ50ФJжIZП┬A3AБw╠Wє┌ Ф}мз┴щ9p|ю7Юy}╛Ё+jЭo╬eN√IP▐**~>ў╬╗k┌П╣о Yъ"·7щLР}'6е╔?╨?_╧7╟░гыэ╕║╧ўSs Уf/ГЦ_Ч·azЦ╙/¤LШ╜╞WFФэк3¤Ь╞ю!tЗчУТ╖гЄO ╕╟╜┌╖ЪПJ╫Zў¤mХЭбeg▄║D╜ 82oAДЛМw╛ъ0╞Эу:┴ \√┴╧ЁЄ{o╤│Їt╕vэЪ9║иЁЄ7i╘ 6Ўф╦рsИ╘O^ Х╙ЬУ: cо┐j▄E┌AYSYсTx¤·╡9╓кп>z{\╦a]Z├{U│< Б∙YEчйЯЛ═I╓Я╗│YЗ╤*Вp╤с ┼% Ц┼&НнzХ▒n)МЖ╘╧┼цфыOпn4╞RA╕0╚TAAсDСйВ В ┬ХBДл В Вp!с*В В \╬ЯpuАlШШ╨жc-№ ОS╠"Щ █юУ7Ш@Є╥dЮЧ7Ъ└╨EёrFfл╧ЙЙ,ЕvqqКа·W═№Rн}П╩╕яП█ч╪9┐ў щ╘я╨ Це▓╙╧Кd╟√ум╙?T№'■№;ыЎ╥ёЯ╖√{T.Ы>ЬбЕл┌юu│ИbС═&v║эя┌Аs└ю6ХЫоNd-аl[Ы╦Ўe╕Н:j╡├╬MлFцмє ╞я.gT╔Ш оўZ═│╓9вHВЪ▐й#qrэ[8 [┐N╡HВ═_ЇЬ с}RЁndm√n╗ўEБКХзўG┼,w╓╣2┼Y?{╟▀╟ %z■ ┬8N╕6P▌9╞Ф5Л┼┼E,╬Nс╔■v}ўЬГ▌═ ьь│╗ў1╗Ш┬L╪~пЇЛИЧ _)ёЄ5┌\6R9▐п^╓П╡|С9╛╚дrP√J]╓·ЧЎ}╣о~лX[▌цНаdVNc═эsКXJ╫o┘╟Q╬[ГўРU╖щ¤СбўGкчrgg▌>ЗН ▓< a '\'g░Р[└ЭЩ&''1Ы┴эы└qг▌еъьюр na╓╕ЛС╗0Ь#·'tьрNВШ~М╥/q=u└ЭJР╠{ГwКA╓█sD@uН▄л0╚оЫ ╛pПyr{ кYяпурPDч╨╗w{║ў°┬▀1█8┼d╦Т╬√┴╗h; ∙Pг~╦Эч╫{? СшУ (ё+О┌├xIк ┐-ХYл╟E╟S▐T▒н08П╞V╙'|zqъ╝ё╨b█Э▀ _oЧ!ЫAТH┼G~┌╞чХN°Z┐Ў╔;_qЫn╡q·М╛g√зВж,щ░щ8x ЁЇ>╫ї╫cР▐╫╨Ў=b∙НrDк▀*?QHiS╜Дк (Ов┌їЗяm]zCЄчў▀?эГв╩N _S6зMХY{и╜¤j| ╡єр╜┐S(ээa/gejєЇЬo╦aп╣ЗТ╟╛@Ўїг@Ер╒щ¤1▌∙■ вд┐¤╝╥╜ЯQы?╩єп_№СЯЯ}C√ачsў·s╙hь╞pue╪°╘zщ┴ц║сч┐ты╕┐√╝▀вх╧[■┴√[╖п╢єч=я"Ї∙p┼pў}∙Єхцqє■▌╗═√ПГчOЫП▄o▐╗wпy ┴уцSу╛╢▌lV╠■ъ:цsя>▐|╬√н'╚Ю╖╥╖╔p▀Х╗x%уёkьZ{З{▄Є^ю К'╕Ч{╠^є╝╖8%д┬{Ъ╙y{/Єр^х┴s╞Ў°їь▒пЖпўmю╒╬v6eоаЎlўЖaол¤╤u╩[M? a°╙gЫ}х█щ3сЎМ▀°OPЬ╩A∙oЧП▐█ЭўЙяnoаx╗з;>е┼Mп&<|╫OВ┬(P#т<ЄЮЇ:Л ╞ {QmЬ┌в/t┬╫·╡On√▐{CЭSЫwщ█■ ╫Oп√Зэ[qsl┤UDtЩto▀гЦЯ┐¤ ~p~·╒п╢kЧЧqы ┐н░t>┘Я═х@∙uCиPс╖█?╗яМпw■"▐├┬щQiеtPQёк4z╦Р╦╢W¤ЪЇў╝┐╜Ш0┬Ёд═█╞Мнж_· O{╓]8v┤чoп√#▄?╤Q'.г╢П@¤ё╜┬с∙0iъ■ cЇ°╒¤┼ёq▄фо└чф_Gп├og╥┌JЬЙЯ▌У_]G^√Ё№E╣┐├╬{▀?┴Їu{■] XoОbXлк┐юЙ_Иh▀o▐╜{┐∙╪=·аyяю=#X4░x%!{ў■ceD╜д∙E4Ю3г^╝ф╓Е╧▌╩ї3╘v┌■щ:З╟╓╘&Х╗БДk╟г¤В╥Д5╠6ъб╕щ┬├w╤7еMЙOt<z∙ё╥╧"еп_№║<┌v╖]╥г╩)XАщp Жo№╗n#Е ├[З:╜юЛaT╗3m▒Э╕╫z╡╧`█Ж╙╖¤·▌? ╟э╡Жоэ{╘Є╦¤╤п~▌cЎлDўv╓ГV·t║t¤Q9P~ul▐╕4ъEч^╦_дЎ;n№Oж@Ж├хkнЇ╖щ^┐┴▓э^?Z\p;аЧ┐v╨Q¤╤i2a{MЧЄщЮ~Нl?└│▒Eп6щзw№№ў*ЧС█G░■Иоq {Е1j№aq╣Ў=юяО╕║╒E┐№u╞я╗┐;ь╗Я╖├─)}п╞╕┬uЇU╪▄yВы╖gsp^`ъ}Ю&0ГЩЩ;Hщ╣шЎ}V╠╠√│ tB?%l:VпхТ╢ў┐iИ╣)ёd!иыKЧE5 baiiЙL5cНQ¤ПЗ°═~│┤j╚[юP╦,7o°▒Ыqsф-№v)j_M▌╛иeч╓+╚` iKхuNu8y╝m?ц9д¤w╜NЬУ.┐шэ╗{¤j╩i╜х,7НLeМє Эг1<ЯF┐?zbMєcЧ╝З└Mj ї#Ty╪ЭK┐;6H √ЫJЬ╜╗`пLcЛ┌Бoд╒ЕЗe╙e╥╠╦С╦?жцНR╕Ї6'ПЇ■╨ё4l▄й╡Q■r2│r? яє╙╧xюп0ЖО_▌_ LwЫ#тaўw╫№Нх■┐Д6# W╡▓└╞>p{ _^]GДiнуБъ;(S.╒5ф╦q╨/.ьё\▒=0к S!·й_8оЁ┼уЭзц╗∙1№ШЮз╟■ш?тх<моo▐УГЇD 5▄х"┤ У,┐1╡яLE╖Л y.пОQX╙пМ╤ыgЇ√г'*}uloє=caЗ╪>м!жЖ$Ц╩aЕ╦x█_ je+╩(Ў╬@9V│iФ$~╦л}чGЮO╞█>к█e ╥╠>Гў╟@ёлЎ[├a╚Rч√■aHс┌└┴ю&6vО15╗И=cНmЛ╔мы/░_=Агор` 05Йa╡муie▐cЧ9╒йНУЭC╧C·╤О╠Ь▒Х╘▌(eм╥Л╓б╚л┼5lu∙╔╔bm┐кVGЁA╫┘оuoйss1|}з▒¤6ъ▄Eт╗QcрN╞┌╓ХБОЛНП╛■·eЫьЄсИI_┌д╧йюtI_Д°{уў$Qд t¤vў]╞╢╟НО╗иоW▒ж~V╧щ_╒бсє╣9Ї╖qк&я╦Б^ьфН╜╢ы_ЯBmK\┼■╓ш¤╬Ooў╒╓■╜qїКW╫Я ├Ш╚Рc.ЫоэЫй№F╛?╝iёзлйх╡|√лўСIЩ·сПJ(~h║~LэЕх/Є¤б?ЄьгЮ6u╥н╙▀╟uuь├─┘л~√CyЄ╘?чUщ╖■uПЪ┼+ Tlм╧rЁГДw╧Їл:!ё╖╛G┬Ж╩ziPaЄ№3ч╜╦/ъє│╦єoачg?┤┐*╒ЕjЯ+9■M▄F8·¤╒Ыaу7ўWЪ▐]▐№ўHG╟¤·|ёЖ╒-▄Р√█╨WЇ#ъєя*с╬Ё═Y 5·ул╗AsяAы,Ючz ^█ю▐¤■g1v`^ЯЛ║N┐Х╝Ж~}uР!┐о}╞;WПк?ЕsCйш ╤Td═▐1╟─k╧є─╘< >ў <Ыv°5G╞7чEMHg√х═ЭзуЯW╙t'╡Ыp°'ж!вУN■@а_·2*ЮО96=у╫с╢уъ6G╟ж(▄4jуOЫ╞чЖъ┴>е╗?OtWv.╜├яЬ#Їл├ў║с6╚}_и:╔_зQё╨?юqпЎ╔■╣MS┤_·ыOг╟╬╕uЙz pd▐0и"ц▒│|┤q╦qЇЄє╖┐┴юПиїыmojОZЗ╗P┌┤[▌о[q·цИz┌жJc ў}є╟D╣?:є Эn7┐ny╡├ЙV┐mў┴√█Ыw6┴№ў┐╦█~tЦ╣K┤Ї√┌зw╨В y■∙уfh_=¤╖щ■№cв=?╗HЯЪГlмг▐_aМ┐ж3 ╛Ўчs╦х╚n<щЇ▌■ў{┤№yы.ф■юб╝yё▀?DшєсbрЭп:Мqч╕NЁ?╫~Ё3╝№▐[T&з├╡k╫╠╤EЕ{6╥иЯ╤Р╓┘├=│╢ц╧K■u}а2╪ЬкЛDvВ■йЬ╓Ь╘SжЪ┼wQtРб*=╗57[ЬўЇ╣Ё№Pk є]▐┐╨Ьu∙_Ф·яЕФ▀А\їў√╔ё·їks4╫Hл╛·шmИpН и;kл╚╙}X╕l/И╚Иp=m.╡p%xмУюЛ╚Яч=} %°6╕bj@╬║№/D¤ўA╩/JЯNб╝▀OозКjюЗЪЙD+ыЇr╕▓НZДыis┘Ел0шх[╡Б╘╒}0 ┬╚√¤┤с*В В \╞%\G_╟UAANоВ В ┬Е@Дл В Вp!8┬╒▓I`bBЫЛ╖ГIЬb╔d╪т╓■ЄЭ" j╥tЧЪУk В ┬U`8с┌8@uчS╓,▒8;Е'√╪їiЦ[Шe;cfo╤еыb¤Ў|нЄЖv└J j}╖Л▓F▐ дr╝_√х[g1*╜Єг╩>НъNхаЎЕТUе╬Жл▐■Aо*OЯ>┌xN╕N╬`!╖А;31LNNb26Г█╫БуFа;ХэФi└y┬║Х▄лn8GЇOш╪┴Ьq╥3u└ЭJР╠{ГwКA╓█sEВX]#ў* ▓ыц┐/╒,еAg'│U:їЎ.Є:ва╦Жр9{wЗ┬{ З√┬▀1█8┼d╦Т╬√╔╗h;╜]-o╡▌yz?√∙з■в─Ъ 0К3┘╬o∙ЖA┼K~┌╞чХN°Z┐Ў┴ыиrЫj╡1·М╛g√гbш╫~y·ЯыЄkЗ1Xя+ЧO╗l╕Н╢1їEk╖▒рp╜Ceф)_JЬз·o°I ╦я;м¤Ўп щы{  В WЕ1═qmаёШ"СкИ▌┴ттЭvЪур о├ъ╙▌╩k╕├З6єyы╡DнУ%$o-╙+фжB/∙▓ юннР;╢+У Ыщeа@┬xЛОэ∙п{тш ╜X╙e╘25д╜2╜Ко;╪ї!U▓Х▀ 'аГ`°З╪2Л!╗─rыz8Э27п#m╡_■┌овЄЦ(ш0╪м{цYЇє?*QтяЯ 0Ч╥T┐єT┐:6Г,╞Я*QЭS█иP╜ў"м}Ф╔bn]ЗУб·I{Дm▀Ў╥~c9ж.?}╠fЭоGГзip∙t∙p&╦щОЎ_▐:─J╦>П5П}5kй6]а p┘тeф╔БЫ jЦ├╧ШЁуф▌ЄД▐~г╘яЇН~  В ЧГёW%Lo∙ЖЎ] ╦8║╗╡ч4юq▓,Ї▓ :цs╥V-Za╧9,12,щ╧l╙Л,CВАw╗`;Ю.Y▐╓v.7MёЫ·odк█·еMJЙЗ┤S$╘╫_╝╜]м│7Щщєс╗█уYШ]ЮGu╡T█╣!S9У├╬бў~■G%j№=Є? n°Г┬^·┼╓>╘ЎT√"│L┌КХклн┬┌_+╣=┌п7;|мМ9┼┘!бШ@БФnК<╞R9мS-o√Хkb~┘╪╧кЎUo5АкNeП╥ж╦6Wjв╔э╤k┐┬╗╔░ Т╔Я ?r√э_ =╙7Ц√OA╕ М.\╪▄yВы╖gz╝hЭ╨i▄у─є╣ЧЛз ╪t╠ч═Т╢ўтЇn╒╜nЇ~лыKЧE5МkaiiЙLЮї¤Мъ м!!╡^!щ▓E?jЇTД╬бюУ╟█Ўb^q;@√ы┌~╟B yл=o╣√zИ╗╩<ИsDщO`║ЧкTЎ'O╧Ї В Вa$скV╪╪n/baжWwj°4БСбў]▄^Jкk╚ЧуиРЪ▀██#├"nFїИеPв┤7)6¤┬ЙЧє░Вcс'LЭч`╘|lЧs╤■(╨/>.ЯЦЙкКIЕ╟I°║╜┐({AA8{Жо ьnbcчS│Лjx┤╤hР╤╢^┬ж DЕч╝║xП]цxшrХ4┘9дgx\f╬XОJjОД^л$ФК╝Z\ыШ├╟Ё╨й╢_хQd?tЭэZ┌@ЭЫуИсы.F╢▀FЭЗJ}B#ЖЫд.j[kT:.6>··gxоdЧo }є┐9яЪelS%¤;U╖ўZвga╚{m╟п╧б╢ечд▓┐5Ю╓Bm╬ХЖaэ╧Wпxu∙Щ0МЙD,ЗХ ў╕&QЇФ]TяЬ Х■4╒╜╫+c┐╩ї╤їj╓ф╧ф>J√5aЖ╫в▐В ┬хч╒лWM|ЇIєх╦ЧШ╟═√wя6я═╜═з]▄▌ь╜Ў▓┘ ╗╨l"A═╣Л║о'┤L┼╪y╔Р_╫>C~ZpWгёS 7:шWO*Щ&i= ═^H╨▀В╟╖╫╛Pа┤шcОЛcжў╛:ўЫL;╛Ё+tъ ▀n(s┌OВЄVQёє╣▀`S<ц║2dйCИш▀д3AЎЇ═Ябg№Єoи┤╥IЖ╩╣ЧоOщЗB┼I■:НКЯ■qП{╡Ў╧mк╒╞шo0э╜┌_╘Ў╦Сy├ат П╢┐ь╚┤ыFЧ_√Ь┌╒Уп¤vёя╖ў╓-╖б@╩·╢▀░·ПР╛░√OA8╫№┼_№┼╨╞╒к№wВ ╣ЎГЯсхў▐вw┬щpэ┌5stQсх{╥иlь]╢ДоЁrXиМsNк0z{\╦a]╠PзуTQ╠ЄPyє│Є╥ДSAю?AБс ^G╘Вe▒IcлG┼▐╗t█╤ ┬∙Dю?AA#SAAДEж В В W оВ В ┬Е@Дл В Вp!8┬╒▓I`bBЫkс Wз$й}H│╕Ъ8┼,╒ щoў+В ЬЖоj╗╫═"КE6Ы╪=lЫ╒8└n╦╛И═▌t┘XлГм╘уАmksq╛ц]з&Р<+еэTIЁ╖ўкOЄ.C╞JЦ>▐СИDБё?1С$╖┴0╬▐yйVы▓щ╫9аHВЪ4╒pЇл?Rыэzqэ╟-▐N╕¤Ж╡╧√в┌A,ИН:╒ с∙л Ся?A! ├ Wе╒ЭcLY│X\\─тьЮьo`╖їLnра║П'╕НYeф╒а╕ Rх ?БХ#┴╩FИ/Фж▓+аBj▀оx N, ЁТм╥/Жt╛ОxЕ№л0ш╫├ЦwC■єA*╡п╘х┌ J¤%Ppы╞о ^╬├"qt1╦_└ЮєW▀┬vuЪ╩эQ¤Ч╬Y¤П~  В n╦╫аy┌|pяnє▐Гзц╝sл╫╟ўя6я▐мО{╤sV║Р0█`Є.Пю╢ШЙМ▒7Ї┌rУўХT╫╚╜ ├l┴Ї▀Ч>[Z┌ VЩо nyЩёlХ К▄▌сR√ўo┌эZO(m╝ зwLп ╨ЇQ┴s▐┌[nvг╧ЦЯ╞┐NC█ЧSп а]8ФEЄ╫6>▀t┬╫·╒я([╢Ж╡┐n[║*{o¤й?]╛]ь)ВЎе░Є lщJЙoWH√Х░№q┌Cту√нЭ╢р}б¤{╙╬╞▐hэп/г▐В WАn[╣F5▐-_╟$\;Еъ╙ўЪwя▌o>x№╕∙°┴¤ц=>~кэ║a╙3╝┬Ає╣ў▒╬ч,>d╧[щУ>вЗ┐М6о_c╫z7y▄╥;D КЮ"╣+ц┼H┌ФЫ"cб╓~ё╨5КTяЯn▄░1╢- ,VХ];<п╜ў]к^╘_оЭ"ЖЁ╜LC╥з▄┼АХ7¤╩╜?╜nЮ,Ии┤Ыv№7зQ√П"Ф;!п║НP]·╝╥ _ыW┐▄vZэГМ:ў[Jn█▐Дч╧_ Ў╟Ўн╕9 6┌*Ф╨· осхлЕЕ┴uCn |N\ ¤█яhД╖╧v·2\]╤iкpX▌┌кЫf6╪Q╩gF╛ Aом7G1оp╧╟YОГ'╕х┌ЯМYtх Ў=┬╬■`j▒IcА?╕▒, ЭзУ¤еc>╖▓┌ЮiЕ=фRtNЖД═pтvЩ╬WАO1 ;zв╝нэ\nЪ0т7ї▀╚T╖Qж╪*ее#Fс/cЮ▐|mшхWci7l╠Ч─№2еПэRXж╖'PЎж0з╥ы¤Vu~цF╡ж╤Nb Ї9GuJ▄4┘Ї┬дgед╙Я*ЩЄmU╙e х1GХK═QЙ╒t¤8;╪к%PX╧ 9мS■╜■г└qЇNcx¤&иэиЎAf╣@(On ┬┌O;▌█_╦Юрce╠∙P°ъ╧`╖╖=]╦╙НBUqДЦпй┐╩ЮоrУ+5╤фЎмь∙Zx√+Б№еJ╢.s║∙y~u╢cВлNS╧·oеyKy║[9пn╞╘■bА√OAИ╬ш┬╡qА═Э'╕~{ж¤ цk√└√9фР╦-т}ьcг= ╓G╠╠[┤YL╨╙▐жc╒ЯU╥Ў^╝вД▐╜аw/╜ШТ^чЮ╕лмИ╪═╕9╥д┤r╒B╩хQt+ьC■  ХЦ~╝8GС╦╖Э╟╒OS╫ПвЖ╝╒■x┼bсu╩x█N╠+nh?]█▀I╨QT~$ъ▄mO╦Йl_ф}╩W╒_╙¤T i╙С┐ЙWЮ┐J╢GЭЄ:°ЗLUd-·ї[░╗╘╦)╖┐ю?A!:# W╡▓ ╘█ЛXШёtз6xсыБЭ─ффuр╕ieББбx№2Ё|R?j┐ИU/з╒CYwUuwkфP┤Ё▄┬Оч=п├П╖z!√вzЗt▄]!Х7z∙&HO4IШx╠Йй╛ю╘=▀ЪСЦks╞э'Z¤╡╦П{&;щS╛к■Lяў0X√$Ы+aЕЄX(┴КIЇЩ Ў║.Errэoф√OAИ╠Р┬╡БГ▌MlьcjvQ п6Hм╥ Rм╬"*▐U2ъ хОпВ∙%■─╕k№b>╛*xь∙C'eчA}Д├Ў╜>Ф2@ё╫AAqєW╙yыЩ>F°тцQЕс√р─ы╫┐кА·8┼уп{·_╡Уфуї1UгВа▄у^ї╦■З]U j√у╚╝aЁ╟_эR б_¤йv╤ху,_9ЗХoз}ЇЎыBЎt}Рzk╤7lHЫ/nп╫^7 ▌э¤∙Л╥■N(.бх+Вpyё~h5Мq?╬Ъро¤рgx∙╜╖шYz:\╗v═]T╕ч╙┬╓╝▌cX2ўфдБКw╬gwк┘ дQrs╨t]иXй№OpNъX8чї╟!X[Ш╖=> ДфOскЄ·їks4мU_}ЇЎ>╬╞/ЄpiщrЕхa╒U │є ╘ЄKj╚ЯЗмЕЛ─9п?ї┴╤(є6%В ┬hИp=8┼%XщUl╒уи ▌█гЙх╓їW┘[л╪Ў~-"\╬u¤Y╦иT║╧╗НКфOAЩ* В В Ь(2U@AA╕RИpAA."\AAД ┴∙оРM┌ ╝ыгpж8┼,Т╔"WуP8EР╒ ДиT│ннL'&▓ш╡┌U`╘Ў'В ЬoЖоj╗╫═"КE6Ы╪=lц┌p░█▓/bsў ╥vпY и╟█╓цт,З╚k8▒ы╬╕pк$°█{▒'yЧ/cе KЯC·'щ@zЧеA▒QGнv╪▐сh@╪_нж Ю7К$иI GД·й▓шЄФТw13╢}IХш^▒aw▀ Ў|╨7 z╟*╫N█є.T╞zЖog|  В СN╕6P▌9╞Ф5Л┼┼E,╬Nс╔■v[╧|н;x2u█╪▀9└F█Awк@Щ■мФ└╗╞*#DБ^║Ki*╗*J└xO,E~ ыЕ╙yЫ╙BЕ№SD}u╟#.вС╩эб┘, ╜дP*╡я╘╣▐'``┬ыЗ╖,MчыИЫЄ╖+Їыmы╚╪ЖгЫх№▐.╤┌g╞═╗]A╝ЮGziЁЮ╙Q█Я Вp╬q╖╤Єoщ:иy┌|pяnє▐Гз·№щГц╜╗ўЪЮ╢▌<}pп╡%l/zn├JfM▐┼╤▌V3С1ЎяvЫ▌╢|═Р{Ж┘┬3ш┐/╛-_+t┌▐R▓s╦Sуо╡хд┘JТ"nm∙ш┘Є╡s√╬ю╫zBiуmX╜;\z¤ЗжпЛ?┴н0;╖╞фЄhЗ1▌( фпm|■щДпїл┐Q╢t k_▌╢|UЎ▐0·R?╖-oyvт▌.╘┐хn Oп▓яу▀─п╙╪v╟э|,ДхЯ╙╠`Ы[oNЁ╜╚~ Ўg┬зЖ╙╛З██З▐ o∙╩цд╦Oс└zsуn∙:ж9о 4^SУУц|pз=^G┌Є╧хН╥■°■[Q∙ч▀<╓LсўпГb▓│╟°┤╩OAh3сJ*є n╡ЗЎI└NсЎл╗888└┴ю&шE▀ ■ ╟▓HhРШаg?╜ФЇ9╜ЫZ┤┬ЮrЇВПСaбqh┬▌&СС!┴С"wlGя Ф╖╡ЭЛ√М▀╘#S▌╓/¤RJ╔жЦ1я{?╥╡%Xai7]Жnє╦Ф>╢KaЩ▀ТФ@¤юKaNе╫}Vu~·)Щ0мi┤У-}.╒з6цB$tШn,├@▐√·л?╥F║■╔,│ж2tK4м}░EПЎ╒▓'°Xs>Ю·╤┬n║O▐M{X)щЎC*_з▀═]с■[∙г6ЪгBКецФsє?v|э╙┼mwЇCС·╩Д█ ╡0╗wФ╗"Л█[│╕уyI Кw╪║█╢ъ░\еw┘9$° y╥~у┴ИJЮУкЖ2л_ЖУ}В┬щ 7╤▄С╖ЮщcьfБ ░e╟a$┌_^√ЄяYБ╦╥MЧ╫п6╜Є╤ п @^:М ╛u▄л■FYU j√т╚╝aPЎ<хBh¤╪═ e╬-cх╞[■Фв╢ л oz╛Ьяч┐єл·■эO╫∙АЇ═X╕▐╢╔iпP;╨сh?┌┐v█╕∙ЖOсС шў/Cїу-[Ox╤╦Oсът]!`уо*0┴ \√┴╧ЁЄ{o╤3ўt╕vэЪ9║иp╧зЕнy{СvHрЮк4P ЯєV═N Н╩Р├РГжыЄ@┼Fх{ВsR╟┬пзИд╡Еy{o╚НAоn√A╕ъ╝~¤┌ k╒W╜=жUДqрTлъФt9Б┬Є░ъ+Ж┘∙j∙%5ф╠C┌┬yтВ╫O`Йк┴СЎ)В МЖ╫sАS\ВХ^┼V=О╩╨╜Y^У▓RИг╛╡Кmя╫0┬╣рB╫П╡МJe└yйд} В г SAAДEж В В W оВ В ┬Е@Дл В Вp!8┬╒▓I`bBЫA╫:NзШE2Yфjъ/{4─Bїчз╩ЯjЖВK5█┌ъtbb┤mАг╡ЯЛNяЎ╤є┘яA╬?C ╫ЖsА══"КEm6w°6╬2╗ki√Mь°m{С╡Аz░mm.╬rПg№░vк$°█{й'yЧ/cещХ╛┴о█иг╓o_¤уt╩ЧєUлщ┐чН" j╥ ├╤╖}ш▒\;m╧╗\ыTЙюEЎ╢ж¤8Фqo┌┌&9╞╢¤┌Wg∙h3╕ИяЭ n▀=ы_юA.C WПш╤e═bqqЛ│╖А';8Ё╜Ш,}┌Ж╝╤ПmLШйюTВd╓╪╝S ▓▐Ю)─ъ╣WaР]7 }йf) э▐ ▐с╩¤їп{Д,фщA_╦[╩Нr╫║╟&K╖z=(r╖╖F√ўў▐t╗╓Уъ┼Э@a=З R_ыЇЖпх╫Ф ЁЇ╣Ё>ЁY╡╗&БiSЬ_╫_╫t∙╩g╟\Ї╨з№4Э=B.С╥пzФ╪]; OбPЄ╚O█°╝╥ _ы╫~xч.nsн6HГ╤ўlЯФН~эЫз/Ё╣╬;М╚╜п!эг ┘▒},Е╥zЙ┌в.╡кы╚╘ ╖эА┐■эз}┤█Иw8┌MЧ√d░╠╣WTїn?n°эЎв{я&МЫшўЗЫ╫Ш╦LH√яЧ шёIф·┐└ў┐ WЖ1╠qm└9иb╖A:╓\jр╫I╘ъ)NtL╩Ў╕ШN╨Ж5н√Ы▀жc>o=Ц(▄u▓фБ╩н%`zЕ▄Tш!V╓/zЖЕў╓V╚█Хщ.╙╦P=6[tl╚▌G_шбЪ.гЦ╤==ыs█Xх╚ ║G(╪Dю▌AхнC╠QF╪mЖЯ6 МхV(oel{╥kswGf.R╧ЖsDI╠c╓]М╗QP╟e0,}╓4Y╘Пи,llч╦╚oS!╥п ╡Eкд¤T8Р№х│2}И-J~Ыа¤*ш╘ Й╬!╖■┬╦Ч№леyэ▀ШA╢MХи╠)█u/┬┌OЩ,ц╓u8╩┌#l√╢╧РЎ╦щ0u■ї1╥!Сk]pЧ,oпш|╝О┤х7¤█ПЖяПC╒k▐·╥┐¤Pь(U2$zЦTyЄЖyrkЫї■хxz0ic.)┬┌ №GП8┬ъ т▀ В \%Fо╬.╧_▌└О=Е█йHзjH╕╛PP▌┘╟Nїаз`e╕G╔в_ЎizЩ╙█Т^z·Ь▐}-Z╜є@О▐712№вwG╕╢щAШб>%C┘ёt╝Є╢╢sq_┬ёЫ·odк█ЇP═аB/:╒╙ТZ╞╝я∙═╜/Э╜A┴╟jb~YўxPЧ∙)L ╘╧ццTz▌7mUчgnх─Ъц╬kC ЇщЧ┴/лL~Йuэ╟ ┼Gа|R╣@∙t╪плЧP gЗ^tЭ=BэЄшЯ■ о¤а░Чо∙3Д╡╥║¤СYжw/e┌╘ox√l%╖G√ЎfЗПХ1чCсk.о(#б┴┐╠╙}╦├Ч9 ╡0╗╝m3ц╖┤╧оэ√$ш╥>┌┬,Нz╝{╧+ю·саШ╒├╣KKKdЄГ╡=Cp>х`Д╖ЯTЙG:ш:¤Вж│.Si╥s╔Ш╚esN ╓ Е╛ A╕JМaкщ╙╪ ▐ў╬a%с:Е▄ёJvЇ╦Шз 4^SУ}ь╨╨─/╧'▐$5|ч%5G┬KOиъю╓╚@(ШПи├П8╘kz_О╚O№&┐к▒L▀ЙУ@Б~▒┤ДЫAT[,Е╥8╥дpёrV{м°TиєmГЪпэr╞э3j√Ё │╜╥┬МчPЦуи░?кГ╜=■q┌Д╖Яj6НrВZzy╡5┼ш*Z Чс■с╩0дpmи!┼)SeЬ]P^═bЫ▐▌з Ё\░$▓┴╖оJГЄFЬ╛"Ц(№Da┘~ЯЇ┼n╥+мО├CuBю╠q№f[╝ўfф┌ЬЫcS>лж|к┼5∙Д┘╟rXсa\+й> q╙ц╧e Єuкжn<╫юЧ%Q oь╡Э?}>д ЇJ2k<эЕ┌д[■aэ╙WпxН■╨aЙHэ#*№ук│№ъФыwuVBн┬$М√оэgTв┤ЯjЦ╩ЬЗгў╠ФБ`П|°¤╦ykЕнМ╣╓╛узw■г─_ |dС░·┐ў┐ WИWп^5ё╤'═Ч/_`7я▀╗█╝{╫5ўЪў< ╕y┌|p ^W√^╪·ЩЭа┐ц▄E]з▀р^S1v^2ф╫╡╧РЯ▄d№╚MЖz┼╒УJжIяb ═D&Cq╤▀B└╖]Q╫┘Н2СvA╫щ∙┘фD╟ыо'!хыї╦qxCГТK■:НJ¤уўj?ьЯ█\л ╥▀`Є{╡╧иэЫ#єЖA┼=П}█З.┐ОЄюВпОZeLэзu=б┌╣nOnШaэ'┐nПўW╧ыL┐Ўг├o√3э=Шс┌Ч6Q█ L╧°]t8┴dGвo¤_Ї√_ДЛы═Q kU■;┴ \√┴╧ЁЄ{o╤│рt╕vэЪ9║иp╧зЕнy{С&╦qOIи4Cч,Є21iTЖ&4]gЕ.Пzс╝з3:TmT┐'8'u,\ФЎ!t┼)"ima▐▐r¤┌єR Чя■!Ь╫п_Ыгс`н·ъг╖╟3╟UG uє.j8syXї├ь<п╧╕Дl▒ R>7┤Жў8Ял(#Бyя·<┬)pО█ЗО·а*т╝їоЬa¤╦¤/┬Шсzрu%нЇ*╢ъqTЖюM╤ЁЪЗХBїнUlG]=■─qP\j▒>О| ├q>█З kХ╩0єТ█ЬM¤╦¤/┬°РйВ В ┬Й"SAAД+ЕWAAсB ┬UAA╕ИpAA.CЬ╒pP}┤П/Ї∙ї[│H▌Й╡╢te√ГG6Ю░▓╦СЭЛ|Ь%В Вpu8уП│%Ja═bqqЛ│╝┐ыZлл4рР¤ёФЕ[╫═%AAAБ!Еk w░0├фф$&c3░|u3lg▒)sIAAF` s\pк╪╟mРОAAДa$съьQ,n`╟Ю┬эT{~л В В МЫСДkьN╣\ЛяO┬▐╪└оь )В В ЬcШ*5╟ї}■>╦х*В В Ь C ╫йНFCgПЮ╫'=УМЭЛrgОAAaPЖ\╟╒┴юцЮШ5\I▓т╓эю╠╕┬ХьЛdo╬ZШї\eWAAДл├╕╓qzВQс*В Вpu8у AAсtс*В В \D╕ В В оВ В ┬Е@Дл В Вp!с*В В \D╕ В В Вб╫qm8и>┌╟ │ ┴ї[│H▌Й┴▌ВАw╙к>zbь¤╚:оВ В WЗ3^╟╒┴┴#░f▒╕╕И┼┘[└У8╞║AвvчS-√)<┘▀└оk/В В 2дpНс╬┬fbШЬЬ─dl╓uc┼L╬`!╖А;√█d▄hВ В В0cШу┌АsP┼>nГtjh╝жH─ В В ┬0М$\Э▌"К┼ ь╪S╕Эj╧oэ└qЁ╖ы)lAAб?# ╫╪Эr╣▀ЯД╜╤ckуЫ;Op¤Ў D╖ В В ├2ЖйPsX▀чя│┐rхХ67ЎБ█ЛX0+ В В ┬0 )\pHд6 mHа>z\o═amр`w;╟ШЪ]D*FWФ[m+В В Г2ф:оv7wЁ─мс\зU┘╔▐Ь╡╕~Л 3°▓му*В Вpe╫:оCo@0 ▓Б В ┬╒сМ7 AAД╙EДл В Вp!с*В В \D╕ В В оВ В ┬Е@Ды╨mГ1с|┴ы_-▐ЩYA╬5C╕VБЙЙ╢б╙ КГbr╓D┼ %к╚R┴Od/n╔G┼)fСд╝ZЦЕУ╚о ?Y ∙ёв█IЄI5kQЮйОУ┘ ╓6AДл─╨┬╡с`s│ИbQЫ═]▐Н▒┬ь"╪6ЩК9?o8E%vвНЫqs0NL№н4hс3*Р╬╒м╩├─DЎl~ЬP№V╛Мx┼F│┘DЙ┌] З─;Чй[╬дju┘ЎЩ▌п█игV;д┐зM1Ь*5)╧6 (#oЭQ∙ В BC WПш╒k═bqqЛ│╖А';8h╜├ь'#cО/.1#ЎР;б╠╘O_НЛъv┘Х▒}ъ╩ЙД▌*┼ЯйР` V┘-е)UTшФ])P╙XR"0ж~МtЦ╗Н├┐щ+Х█г·/ёo▒sH ╣╜ 2Ф╙╒WA╬Т!Еk w░0├фф$&c3░о+EШ}wкY╧ФА$▓tКm┐IЄ[ ·еє,]oЕO╞7f?"╒l╗╖nвcкАКOл║зФ▌{K╗Ї°Id2HФ╖;{╦В=┬жg╙ЮDЦ @енhzp;zlП<щ╦·╦╕_·Z=йTж║чп├ еZщV╩CЖ■╘П<БGёR>╛ЄчсЁ`qv░UKа░▄ERV╫Рg╗їRЇ *Ц╩a╜Р@-┐(k╒в7ьж-}фп .=ЪФ╟Vпyv╟\Їр│пкЁ№╜з╜єя╣^-╩ ь╝╒v╙їKaО*а√ ▌Nп┬ФAс|2Ж9о 8Uьу6Hзv!╠^CяeдI╕*zZ@!Р.з▐─rf:ЩyЄЫ&┴р}╜Ч╕и7l╝C┴aЎгbMУ caЩ0║P╦з▒E▓-├njy╙г╟Ш?O,N┘╛VNw Пщi─QЗWєEзЖrЭеV ∙н-s)0д]▐╥щ╙ @z╔╢!щ│ЦQ)P▐щ░╛║Дн╕ыf╡-рЭ#J9щ╓╣eL│▌╓N╗юC¤ЗЧП[■n┌єщ% П√РrGаГTсq┴╠c╓cSs>tY[Ьр·е┬╞vЮ┬▐жR3∙qIХljS6хГsД!5■Zжв▄мLТИ6VКа¤к║W┌░Шяьvїe,╖N■*риtГql╓{t√╖є─в║I укqAA8eFо╬.╧_▌└О=Е█й&═uЧ0{/▄█FяeфH,Є┤А\ hТщгu;`╠ь2 0Їpj┌AЯА├ьЗ!F*ХJXщ7╟5Q└·╣[/иЇ╖z╜To [пcП┬(йс\П}ЛЫ$,jv╢@feє,nц╫▒┬╔м`П╙W┌CE'@ █░ЇQaжfз╒a-Na░A;н╬╬Эqe │ЬИ┌v▄· єб|╕№Ччц07╖bDp [нЖ└ЪVu─hK░Xeq╠впC╟(1Т~]иnУш╠аB┐Ф╪M*╖мъбEЗ¤║б-▄▐т@Пp╣5▀Вуvc╢T╩Ш+AT~║╬├хй{(Э╘<AAa$с╗УC.Ч├т√У░76░╨aЎ-ш:ўNН╥СS4╙ ЦЦ┤ёuXЇоWbЖ{b┘]Т▄{Уf*─oj1╗IТ╟ГAФз╓0/ўо╜bЦ}хэ.C═'IфЇС╕t+9UЄ|х`╟t1nнe▒ж:|╗ ╦о■Cу╫├шV:Н4Ы|╣г} МЙ│ =║Жщ9hЙM$ж╗ ╒бЖ<п аЄOyхyCв{ШO3эВ ВН1LАЪ├·>╒c!╚0{Vk,╘Жэ)ф9дEPi{{┌░їAqФш:i╣╖HЬ╢│?K▄▐=3T▄2ы9-t=и▐▓·!HBЭ f>к+nH_WLП)ЛпZ╣МrMЛоює,╗┐S\е0╔║аW h┌║G█З г√4 ▌щщ&Ф╕s{TM№Gt-~УKф;╩■4Iа`єЗ3ф\[UнюAсR╕6╘эНFCgПЮ╫'▌╔aЎЭЁ!х┤■ Кїнk║┴#а]э═y╡H┌НХДGў8]№РюjfЕ┌╓▓┘м╟їLx╤\[Uzжжz[Ўa─fї╨1Р╡┤Ж╡56K╪щжыRs╚╘X№Щs╞Їр╓ЄK* I3AТ╙)~Ч·*∙O"iz43+FШЖд╧б№WwМФ>▄A╡ъ.%e0╜Ч·хбEЧнЗ┬═Зfб■#ЦO]∙-"л╗tU■[є\U5ф╫8╞\ж▄г╣─їхPu▒Фп!QX╓лиЄнуP%СW0╟^ё╟■╚┤Тд╬═▒ ┐МUj№ьжZ\3B▐f╦a%├=оIїa╗Q╞Xk╠ък╬█n:╤?JZ=█>x.mY▀ф`AA8E^╜z╒─GЯ4_╛|9Аy▄╝яnєю]╫▄k▐Ё4▓}/*R-$[\У( >7tlЫы╧ї ∙+$╠qE█g\?╞$<~Щ0√╛╪Е&щ&Є4ЙfБ │oV(~:w█q╬╪Ф╟Д╧жbRh┬Oш╚(я╞ аЫЫ дgбЭк√хp*T $%)%МIПkиp n▄-zзOЗх5n╛5┌▐═M/з/╠┐жO∙°ьt╜Э]fO9t`У╖\┘%мэ╩-sЭ.7ээ░х╫2n∙╖['6ч9Ш╢╢}бUOm╕╬В∙7V.Бt>Ею╢В В╨ ╓Ыг╓к№wВ ╣ЎГЯсхў▐вw┘щpэ┌5s$ч ▐┘j╔╠Б%]XYВ{-l═█╪;╙ФxYк4ъЕёеГЧ╫RёЙ ые[gXA╕Ъ╝~¤┌ k╒W╜=Ю9оВpYр╒ЎЪMЁЄQ╦гхz┼5хвXlЪ┼и┤Жўy┌╔*╩H`▐╗>╫ИX╦Ч╡RA╞ГC╧Ts(\xD╕Ю3ЬO▓H■Y1░x№EзКьП'Р}nNЕЮи·з▓▓Xчи╝N╕■Юg1AсkУUЫN ═чEd ╠ +Йт)┐мв▌┐КФ╞ф'gpЧЯH∙╚¤=╜ы в? ╧k√п>┤шЩJm■╧▓з■L╞╧╨┬╡с`s│ИbQЫ═] cзhЁюY s═┴.╣!'чzа│`рЗoї!7Ё└═7Т}Ї╗Н:jп╜Ы~ WpV╜М°m═o4Q║aоЯ g(жnФ`▌Ж};c. ▌gЄи┐Q╤с}}╣7М╒)хя|▀┐¤╩'М3lКУО мєзщ▌~╬G·┬8╗Ў▀┐|R4щЩjгА2Є║№8>╤ў┐0nЖоQ╙┤f▒╕╕И┼┘[└УxjксT▒єшbVЭїf┬ugT√~д╛▓G7XIo%*\!ш┴{X▐йР`╜zыR┼▐И╡╖╟Цч█ЇZ╩`хГФ/▓(ч·■=х#Їчв? ╧w·c╚¤QEoЯ▌ч└Y╛ Еh )\c╕│░АЕЩ&''1ЫБu▌XН└┘ЩєЛA╙}цг┌ўF¤Jг_l▌ЗKMПэ'·W╗I> ■в#7ЇЛ╙ #∙pР╫░ЁН}л╟8pоЖzУ╚к<$Q№D¤vдсчCж▀№Ъ╒CЫmwГф╤∙$┘√╧и|╜├EС┬яУ>вo°Q°|[п(Lўzф{уз▓Ў=x╔Ооўo╜╤i╖Р ╘ъЦЙГ┬>▄й■┌e3╬ЎЕЁЁЭЯ╫Б7з▄7"r■·б┌E╗О┘┤яJ]▀√Ча{д]7;цвЧQ╦╧ы?╪>4╜╩'М│n╤тя>┘∙▄ы▐╣ уf,э#,!ї▀п¤ Ю @¤П°№TёлDO9к0█SMFn >√к /j¤V)╠╜Cю^wы>╗ў┐0cШу┌АsP┼>nГtмбБ╞1pЭDэЕсНЫИЫ├оМjBъ║│QЙў■╡V~vИ5дZЮх▒╓zqЄГ8M┐# и┤ь╙╛kzЗНщiЕ7k╪┬2l╩Gэє#▀╦л■╫+ИЮЗ╒z°Дд Н╓╔OЎ├m+╫ЮнFЮп√╩║н`■Н:╥▐св╨Ё├╦╖o°Q°№РbПуfП_ї!╟Я1ё╟Qжt░~Зн?ЭЎ ╒=zую0▓Нїп°√@9№9nзсПз¤ї&,|■Ё┬i O┌t╠чn█ЛЪ┐▐P№к№чu№╞xзrЇ┐ще╗_Fэў╩ЫЗЇ#┼X)F/┐■эг∙Дq╓э#<■░ЁS(ё¤\_Rў│є╔Єф╓■@ HK√шX¤ўo?Q╥╫╖■G|~╞╛рy│╣╜ЙБч╒hэ?h┐КЇ3cеш_╛Г╓Яъ ╝Ы╬·¤/ o@АП>i╛|∙r`є°■▌ц▌╗dю▌o>x·╘cў╕yЯо▀▄¤№ъQif╢╨╠<3з}░Mle╚Зэ?qhЫs╗Yxр9]h&╢═┬k}╩иpЎ¤бЇ&$№ОЇ╬ЯeLЪ╡?╛отPа+L0|вхЗИФ■`┴4E┴n┌п┘t╞╫7№╚х█/№╝х╤Ag^+√Ё─L{░■в╨╧OH°#╖?Cп2 _S·:М/■a╩─E√хЎ\б·эЗNK ∙w∙їo╤╩'М│n}тП╛*У\~╖Ъ~∙ !,■░·ўаык█s ╝№{?Ш`u·щЙЫ~╩gцР З╦╫Z╧ў6]╙Ц ▒▄╘_G|┬i┴zs├ZХ О╘у╗УC.Ч├т√У░76┌_58╞uLr╧+╙ ▀ч[┬А─┐╨паЖ<5iЖIмz═\ПN ЁGgщoЗСB)ЄLЇЛ¤бNZz╕D&O▒uзw°¤╥=№б°№us╪ПЛP├╙;№ШЪW╫T╜№x│ЫО∙╝iz╘F'Ж▄▄c╡ЕЇ=9╚TМhМP~!эуф╦Gs^█ЗKКыЁ5]geАП╥вr╥∙ыC─ч3╘єєНiи~╘╧Б/▄TёUy┌╔7GЯЧЩёХя░SfДє├ж @═q}Я┐╧rЇг╝с╪xA эяl`Гэ╞╞Ю╨ї';UИv=!(|▌╝РNш┼4Nк╧╦БЗ╟ ж ∙Є╧тиPШ{┤GЖE╚аЇI▀8┬W/З:О║ ▌]ИaиУng▄╛▀а=╒-╟k╜А°3яTЧq0B■д}сслсt&`КQt╬░}Юt¤лЁы╪~╬Є╪┬4▒M?oЮжЇ_∙┌№ухTE╖0nЖо╝╘ХczS╔8╗xD╩╘Э╙:9│аzb█fдkqkv3ыkн1cцЦ╡М╣╠Шkн)уA√~╝С├╩;№Л4ЙтsO°╞z\ФЯWU╕╒OVA▓st║кУjSf:збщчssш;хП╙┐Н·Ыю9~╘Єэ~▐Ш┼№Ы╟a71д?((fQхxЯgu∙▌чЛ1жцл╒Юнщ8МЙ─X█_Щ^РБ°╟■∙#╙Ў╜~|/nc╫ї■╜1G?d╩X%б╦nкЯм∙ч°НЬ┐л╨>·─%|U&$~>╪CЕ▌vЇШЯ`■┬ъЯ1~z? √е/м■╜a├НNЭ№M┐┴щилc&=C╡ ▒▄QыпКm*Ы╙▌┬╕^╕>┌1╜йdvО1u{ W[ХЖR▐чEР╜╞¤АзКм╣ЦVC e5$┘╢З'╟WшсХў─1ш╟U╜╤7$ЮеU╕лШGЖДYy▀ eun■╙Я╟╒пчр╟-╜╥╧SXxr╛FЇrQ▄XFс■`КЗЩЦ░Н9╩=M·гД▀╖|C┬ПF ╣щМ∙шаєБлЖ9▌v▒_G&ю 8hдж=q░9┤/З▐Ме¤▌(QЙЎ}r╕╙КсЯ┐*╓Ўu█╫&П·;╧╟aўп╛╕n┘═ъы9z+Лгця*┤П~ёў_№УИпл)ь╢А╬єУ╦_X¤G{■ў╧я·∙∙╔╜мЇг║ЎZМe╤│┐F┬▓=э`╘Ў┤Яжё╞╩е¤Д╫Я√СW+С?╝╬#<╤ї┌~ЖЧ▀{╦\:yо]╗fОAЁ┬;╧,╒╦${Б╠э╙▐Д@см!!№у4ъ$╛ў╞$0yy-╡R┴Ы>(Э└g! п_ЗГ╡ълП▐╧WA╞Cь+%ь}ГчQV░,WAонс¤*Кy*Zє┐3╛^QkЪЧ╔jв∙G"Z/"\с{Гw72'В Ч^Х┼ЭР╞╓чqT┌О8yЮ^.D╕ В ВpFЁVмэ╒Ў■иДФИLб"\AAД БWAAсB ┬UAA╕ИpAA.Cпу┌pP}┤П/Ї∙ї[│H▌Й┴▌Ваq░ЙН}ci╕5Ы9Сu\AAоg╝ОлГГG6`═bqqЛ│╖А';8n3r¤6f┘▐нВ В В0 C ╫ю,,`a&Ж╔╔IL╞f`]7VФ╜1В В В0,cШу┌АsP┼>nГtмЯ√иnQ,nbsўА\ В В ┬pМ$\Э]е╪▒зp;╒Ю▀кЩ┬н█│x ¤Y╠╬NOЎ▒▒ЬK В В ╤I╕╞юфР╦х░°■$ьН xuщф╠▄ЩЙ!csй█╫БуЖЇ║ В В C1Жй$Rc3xЯ┐╧rМrm88╪=А#*UAAC ╫йНFCgПЮ╫▌░шOу╔>vМЫЁ╥XSУБщВ В ВН!╫qu░╗╣Г'нeZпу╓эю╠xdiГ▄T█nо▀║Н╘Э%\eWAAДл├╕╓qzВQс*В Вpu8у AAсtс*В В \D╕ В В оВ В ┬Е@Дл В Вp!сz uc▌є░1/п,Ь'кs(В gД╫У@▓hNБ^юYЄ;1бMёT_Ўе{╓Dr╚xGї?:N1Л$ЬeY╚V═┼1╨°q :=A&Й?юЯ╣╞├"|З▌jўзПоЗdЧJPхC є┤лзЪ╡иN&0С╠ЮY█AДбЕk├9└цf┼в6Ы╗:wxm└9╪%wЫфfW` Xz┐гl[Ы\╠XЬ7)юQшэ┐К, ╩Йqк╔ ╒,м|ёКНf│ЙR╩\У▀X╟B┘F·[└╤Яосй╣▐┴├,6┐Я╟ё╗х~б╝g,╬6ъи╒щoР▐bwдJMкФС╖▓╘AсЇR╕:8xDпNkЛЛЛXЬ╜<┘┴БяЭ╔╗km`g S╓√Ш]L┴╗▒╓еД▐цe·│Rb$X┘Ь.1#0ЎЖ╠г·^лTzЩ ╓УИ<Ж╔1╝ўNe Ї╧) _лрЫжФ√╔╞тЬР╩эQ¤Ф0FM?1фЎ*╚P+_ХnWAс R╕╞pga 3ЇbЯЬ─dl╓ucepvwЁ╖0Ы[└r#wчЭI3╝Я╠Ъk╝S║╣qОшЯщy}zкT│Ф.╫З·uoi▓XU╜r╩M`╕╣ЯmЧVвхt█▌8{_Эl╒(,ўТdЮ~Ь─П╘Ё╜1▀)·{∙Я√З°<4╫=ha:Зў╠╣Я*ЬЯ7┐z▓Ръ└-{2╔`┘Uї m╖c.╢ё╫П┐╟╙)&щЪЕ| ихнЦ;ял7■$▓^;зhтцzo╗ыHг"Е╣ ┼s╪┘члтрpN▓W^A╕╥МaО+Oиb╖A·╘р└y▄z0Sv║M%8?d╙Ї2ж2япLГDФ▒0ЁЬWn▓╖+t@'▐ў3╕т╛╩m:цsпl8iмщ 2Щ $Ь{Q╦з▒r╟njy,y─K? ┌.┴ЪЬДyB╣SfzМ▌>D q▄ь╤┘·Їc ХЯ─aйс{c>ЪEыч╨├,~Ф╔у°ї·k5¤0 lйFЯS_:эЯ<М╧┬┐@э╟жЎSP?┌эЗ_║LэпBэ╧жЎw╪╤■R%ЄGvХBg┼rыdW[% : 6ыЮnє*5Ё2╒╜О?О2 ▄V№▒╓╔Жj`ki╙+ьЖ╬╩л]ч│Z╙Q¤иK√╢0═эcЬэBA<М$\Э]Ю▀║Б{ ╖S▒╢Иh4pМы8~TеЧ█$,k ╟√;╪╪=M)7ЇWв╘ ёзr└╝WP▓YH╓╔Оьc)`Э╡╟╢▒.╥+Ы▐╒щ<ЭР╗4є╣╒гчЎ$ИхJ(ХJXщ7Ю(`}П▄Qт9{▐^│~■╡▌ ╔J"╛в▄)s┌є ╛ ╒╞Ї╛6ПH5°-▀№ёЫ╦я▐l╖Iц∙ьЯ&`▌дЫDпюеMГ;═П╛▀гGwX▄▐фї╡Я╡Кд2╦█F9V╖╡и,еи¤┼и¤-√█ЯВ№С]wI╚vоНе▄)cоp▀жт╔мФL№%Р.m╟O╨e═№2r╘╚cй9%d╗uм╞x"t╫y╢<Х`яЇ█Е ВpeI╕╞юфР╦х░°■$ьН °uщ L╜╧╙f03sй█╫Бу╞╣юu #O┌`┬L░Xдb$tЫM└&1╦К╨жc>oТ>W─oj1╗йEшт╜m|ЁnЭЧеDхП>оz┌ТН╞Oщ╧gkxажXи|ЪA·├└Р єC·AяЬ╖·┴▓ю┴¤cRs$╫>hїшц№┬w$j╘~▄б· j?Б.╒У─9B▌ЖouyзPъёБЬsDб%ж╧dJМ Вp╡├Tи9ояєўY╛Епу╝OkФВmщyжkЪ4G=;хc°ГўЁ▌JS ╠Ядё╪Э├·№И)ёе9─╛║В;х&╛{п╘1П╡ў№V╙Гk╬┌=║ц┬XHP√с▀~,Ь2зYВ ┬хCДл М^ ы√uL}mw> YE└qP╡I╛Э╚╬^В Вpyс*В В \╞%\╟▓Ц В В Ь4"\AAД БWA8З┤╫`╬aы█ В зБ╫K/'ХL█Л█wЕКb!¤ @┤№ўfT гвтч-a- ┘.{1 Kу╟I╡Uю┐N'ёр╟¤s╟л,<°╗╒юOЯ▐эємъзЪ╡иN&0С╠BЎWA8;Жо чЫЫEЛ┌lю:эЭИЬ▌╓u┐┘─Ap╗"AуTСеЧ╡╗Ч}▓╡+UпЧxўы6ъи╒█;,ЭчC ПЪ ▐■O!╒,м|ёКНfs╝k╪N~c eщoG║╓}W1цaЫ▀╧у°▌Кr┐P▐3чГ│кЯTЙ╖щ╡Q@y+Л1■жA`Hсърр╜:мY,..bqЎЁd╛w╞-╠▓Э1ь╫-─╬хЮпg ╜tЧ╥ЇJ,аb█░+╡╡ъТz Ын<Гпjz╦x ╥Lй▄╜`п▐:г.гц ь╩П┌└*я├Z!┴z╦m┼0y#Жў▐б╞╘ЗзNi°Z▀№0е▄O▐0чД│m▀1фЎ*z{▐3■Б&ВpUR╕╞pga 3ЇbЫЬ─dl╓ucхЕэФi└y┬║Х▄+┴Cu ∙ZЕїR▒bй╓ ╘ЄkБЮ▐╟>лЎ╗╫$рn╧;╣╜╡]zДкzZўцюШЛ|ЎUЮ┐ўк│G╪┼)&щЪEy БЭ╖┌nNёх▐? Фv║Ю-=yЇ7ўє*∙sv░┼m`╣Ч$sЁЇу$~дЖяН∙N▒=╩┴<ўё?xhо{╨┬tо╟& U8?n~ї$daяЎгiЯг╫П7■$╡ПяИfо=#iTд0ЧщЎCRA8 ╞0╟ХDщA√╕ ╥▒Ъ╪,.▐iяИф8xВы░д╗╡+╬QЭ4шж╛d~эЬ¤█Ox√╜~кYО?cтПSї{ц╟шЗ"∙╧P l-mczЕ▌╨Yy╡ы|╓Ў¤(В Ь6# WgЧчнn`╟Ю┬эФ┐7u╥sтшюVЩ&0╓4▄W┤▒Л╒L~ivЭb▒║JТъ╢~iЧx╦╬R╣e╠{▀ ЎыJ┤p{=┬хэ╓ЫЯ№╣1S=│6ц╩щ╨' Ж─№▓I м╩Э┤шчЯэ╬:─Чщ╛в?z_НГ╟?д_▀▓ё═oЁ┐╣№юM ╟є╪?M└║H9яжzi╙8в╙гяўш╤Ц░Ў╓>г╘O█< cеdт/Бtйз¤Rоcj#9zЦ┼RsJ╚vыXUўуЩ╠#AFо▒;9фr9,╛? {c╗]╗ Щ&0 JШzр%]├Їж═%$ж√ ╡ёRCЮ┐к6├░П╦^0ВєБ/я}hуГwы$╕,%*Їq╒#*m4~J>[├5Э└Bх╙ ╥Ж№ЯтШ~ьt╠[¤`Yўр■1й9Тk┤ztC╢└И3l?Б╤Й~┤█H е╚щТ╙╝ўAЧ1LАЪу·>Я╒mбCЩ&Кю┴┘┬Оз°╘╦╤эQ5╜пGt-~У_ЧЗ╪QЎзIЫ┐мЎШq~Ў~╒QuмзЖt'Ж?°p▀н4╡└№IП▌9м╧ПHР_ЪCьл+╕Snт╗ўJєX{╧o5=╕цм▌гk.МЕ3l?▒Ыt'Н[щщ╤ANЛ!ЕkC-Р▐h4┤qvёИ{U╜є 2M fX2┐TDХ╩╒й▒Фп!QX╓_OлoЗЗъ7уц╪√ЄddZ├Чъ▄лЁ╦X═VХЫjq═?З0╠>Ц├JЖ{╠Тъ├0vгМ▒╓Ш╒╢╓tМё├s╞М _╫№G!╘Ф№Н@Мз/P{'▐║<птщC2╧щЮ#╙┴НЫШв?╟Я_╝┴?lкx№q▒√ТWЯnыp(╝р4А╞3·1Їхi|╤ЬwRU╣ №QZX√ kМё3\¤шк╩лYse╒ю╠Ь+Ь╜a├ вз$▄п"AД╙х╒лWM|ЇIєх╦ЧШ╟═√ўю6я▐u═╜ц¤O╗╗#√√П¤╫Е.╪Хf&Б&UЙ2ЙLеi+▓lФ]вYаЛv!б▌ЁЙВ№~У!C%╙LШыv%уїO°ь *>Я=ебТ╤ё║ЖВё╚|y`t:Еа╟Q ╦┐╢oзWЧч@х╟ДцoDи8▄L┼ъё~ж╣ЩBєOZ&╤▄▌ ─№м╨▄¤v█═цZбщ<3v.^7▀N4Ў═uГ│F╫╫·╒M░!д¤ЇmЯуип]вЩё┤mў~jЫ@╕-▄√░Ч╜ В╨ ╓Ыг╓к№wВ ╣ЎГЯсхў▐вgЎщpэ┌5s$°сI [є6ЎNёЛ№N╕g-НzсФ╥Q═b┬┐МAЫ▀√пё{∙?р/═йЯ *чa═┌1жЯwЖZ╩Ч╒№f╥]c▌Да?UЁъQч╝lФ╡Еy{g┌<╧^ОKUq"Г┬zщ╩х_aT^┐~mОЖГ╡ълП▐Ж╫sпIiхI┤V07Ы┬ЙмE▀ BU6v╓VA┌ ЕS(эa▌ ЬА▐vнп┴╧Шqз▀qx╩eп╧O▐тїёє8·iщJч▄╪Jау|№X8eN│>A.#"\/-zО▀ъVё∙uФNE9ъЮ^ўCяD"ГХu^:HЯ Ч^ ы√uL}mw> YEА─y╒&∙& CAоВ В ┬Е`\┬u,╦a В В ┬I#┬UAA╕ИpAA."\/ Н'═~єI<°qпп╪5№∙ГяШ¤ш╔¤щ├Г ▒Р¤╪ш?/GХL{оp5РЄA╬'C ╫ЖsА══"КEm6w N<Нь·ь:vъ╞╟ф7╓╒■ЄщoG║╓}╫$цaЫ▀╧у°▌К▐П╛╝g,╞F╡┌a{Зжg-╢╧R>В ┬Y2дpupЁИ^]╓,▒8{ x▓ГГ╓;лБГъ>Юр6fХ¤m▓▀Gї@ды╔бўЧяЭ■╗▓ы¤ъ+°цЗй╪П■тУ╩эбy╫)НКФП ВpЦ )\c╕│░АЕ>УУШМ═└║nм 4^╖▐ЯA╠╪┐O┌ЎECДko<¤8Ййс{c╛SЇўR?ўё?xhо{╨┬tо╟"ЄU8?n~u┘саЪMbbbBЫd■╬╡*▓Ic7СD6╪єV═"i№&│;цвпv╙e╧■╛Д°ЙЯwFr¤NLd)┤6╝)──Д^ч╢Ц╖Zю№╜Л}Є╧;NЩыEзэ.ryсз▀°╙┘ЯK\>В ┬Хa s\pк╪╟mРО5─0s√:Ю<┌┼я╚t░ЛG╟╖p╗э@ЁЇc ХЯ─aйс{c>Ъm/ 0ЛeЄ8■C=─Я■Z G? [ЧНOБй/Yц||T│Ц┌Є▓P▒a█6 ё2Єk$Д[ЎiФy S▓│+qФI└┤uЙЄ\╦TФ▀ХщClЩ═4<╠╠■ ╞(з=■├є?Р*щ|U sеM,╖Nv░Uва├`│ю┘вo■c9мУ  j╪Z┌╞Ї ╗б│Єj@№ў Uj┼щ7Q{>/y∙В W▐А}╥|∙Єх└цё¤╗═╗w╔▄╗▀|ЁЇй▀■щГц¤{lwO╣╣w Aєй▒:q╓╨№УogЪ╬3█\ёb7╛НццЦkgkўksnxVhnж═Гgц▄e?╙№У╣ЪoЪ╟╞I*═ ╨╠вk╙iO║г╔ЫэЫУ&Р!W.vУDN3Q0∙▒ ═MўФ▒ Й╢ 0┬№З┼яA∙є╣uщэ'4  эжэ?мL╟ИФП Вp╞░▐┼░Vх┐#ї╕╞юфР╦х░°■$ьН ь║╜#НlnьяУ¤┬╣Y─√╪╟F╦Бф╜m|ЁnХМеж№шукз7╒FузЇч│5═ ¤aа┐э∙!ОяЬ╖·┴▓ю┴¤cТ╚рГVПn╚Я.╬ъH`║WGо▓ХЄЦ;Ф<╦▌62г·БЄ┐щЎBжPj6QК╥e:ЦйЧ╕|AД+├ж а5ЗїЙcДiгБ╕ЕXkдpУУ╫БуF`h[h├|╕ЗяVЪZ`■$Н╟ю╓чG$HЙ/═!Ў╒▄)7ё▌{еОyм╜ч╖ъ╖\Ск?╩р├м╪MТ├5v~JоQЎгТ@┴nВ~Ф╡═@кeT #0Ц№ўaфй╠%.Aс╩0дpm└!С┌ БкМ│ЛGOАыУFСb╜Е'x─Kd)78╪LСА╒./╧лx·Р╠s*/2▄╕Й)·s№Ё┼▄эY┼уПЛ▌Ч╝·t[ЗCс$4Ю╒Б/OуЛц╝Ю ┘х├*ТGsде╦i▓л:кю╡1╓о¤jU╛^═к∙░Щ9#МRs╚аМ╒lU∙л╫№s(c9мd╕G0Йв7|cJШ ░°узе═╒╣9V─pУ╘WmkMч╤MH■)%э░ВсF#FўTз1Цa\БЄAою╝▀№╘PєX╧_х∙н╩▄k▐╨cОлq#s\{s╝Яin·цЯ&Ъ╗н∙мЖgЕцю╖█n6╫ M'8Ч╒ыц█Йц┴╛╣nш:/╓З;╧░Ы╗Y╔Ё№F┤М>#∙M╕vЙf╞gGT2═Ды/SQs¤■;├l~cИ ╛ёы|{¤jШ╦i{єHЖ┬iчаw■ї╝PП┐оsDO)AсьЁ╬W╞╕s\'°Яk?°^~я-zgЬ╫о]3G┬щQ┼Гt╟▀▓ё═oDэк;'ЁOювы ГКм)*В чЮ╫п_Ыгс`н·ъг╖╟3╟U8▀ш-^╙8"бў■Eн╠Xцx В Вp╤сz┘Q[╝nс°▌*Эt]FЪу)В ┬е@ДыeчГ╛[┘├7?М╕ЇХ В ┬9EДл В Вp!с*В В \D╕ В00э5Z╧ ▓■л ┬U@Ды%бёуд┌*Ў_зУxЁу■op╜╩╗╒юЕєN┘Й d#ьяъ│H&Лэ═╞М Я╖М╡мHщ9-кYЛ╥4БЙd√gВ ЧЖбЕk├9└цf┼в6Ы╝KЦ▒S4ь·ьd╗╫dЄыX(█H 8·╙╡ю╗j1jХБ<О▀н(ў х=cq╥Ёо\HvSNQЙб╓▐√ю▐№'(└┴)&[{№√MЄ▄Й$u╘jЗэо╞ ╒ЛХ/#^▒╒Ц▒юО▒Нт&КtЯwШMЬR∙дJ╝НнН╩╚[ж В ЧО!ЕлГГGЇj┤f▒╕╕И┼┘[└У╧KКDы╞ЮL▌6Ў╖╔~$nЕУ"Ж╔1╝ўN ]сЯ■y°Z▀№0е▄O▐0gNЙDG╘DЬг║╣v>Их╓═║▒Ф╥B┼Ьп#w╬ЦфJхЎH└Э─·╢Ї├cХўiнР`їgz2Ч┬вMў╣=Л█T>╫ Ї\Pч)╠Ьj∙─Р█лшэkе█UсR2дpНс╬┬шн499Й╔╪ мы╞Кi4pМы╕=3╙▓O▌&╟ щuэЙГз'ё#5|o╠wК■ЄzютЁ╨\ўаЕщ\ПїZлp~▄№ъ▓╞й"ЫЇў<·ЗК┘╛mЧЇXъK ∙P╦[m7>q╟№<░╡S┼╬щг ) ▐ЁУ╚·№З╥╗ нПР~nяz▌X╦Ь[н╡dнcюБmЗ╙YцzТўь7-T│ЮЇЕ yW│ф╞╛:w¤║=╫-Ly▌tr┌╜┘U=@█UUx╛·qv░UKа░▄нэЁ=оM╫sЭiї└R>У║Gv3ыЖO╫ш╝┌JtЁЬi√є√ Т┬\Ж┌┘сЙЇ9 В g╠ц╕6ш╜T┼>nЯrя╩хтщ╟*?Й├R├ў╞|4█^{їa?╩фq№ЗzИ?¤╡О~╢Їro| L}╔Xу┬Aq)Н2цQёь\х+√$█┤}еФ╙-aи{,Г╜Х6╓▌Х7gY╣оbЛтЩЫ6 ╒,ЗЯ1с╟Q&ь╙Ц}-¤б─rXз№eP├╓╥6жW8 :+п╢─з╖╫v>^G:0Ь═s4yW█BEзн/#┐F?4М╜Я*╢╔mb║]╧йТЎWсBюAyы+н№хA┴H╪Rф╡МоЫХщUХЎ!х.ОЫ├▄у№├╒Ю┼M╝А╜dcjeЛ:+?К8ХаБГфОш3╦=╣Х█Ф■ЭА░mcMS╘ПzФЭ ВpСI╕:╗EЛ╪▒зp;kЛм╔IL╤Kj┐║ЛГГьnт@:@┬∙2ўUСQC°┌h<■!)Йo┘°ц7xИ▀\~ўf╗╠Щч;░ЪАu;а.HЇъ^Z▐Ў8·~П▌И°z╖7n=З█еH╚СА*o╗╩в{oeЗК▌D╝Vцg╔Х-╘2+%~ д =с╞рщз▄№2rt/─RsJ╚╢;■8N■kavy шiУ┐╩ЮЎKsCщc┐$Д ╫q┤▌]Ъ╞n╞╣╦їdц∙ В g╩H┬5v'З\.З┼ў'aolа=Е5Ж;Л│╕=u █&iгт╘ `К^:╞Ерч╜m|ЁnЭЩеDхП>оzDеН╞Oщ╧gkxажXи|ЪA·├└░эєC#▐9oїГe▌Г√╟дЎHа|╨ъ╤Н║Ы йuюQ▄B┌╥C¤C═$╥Є№U╖n╢x^└└дPj6▒Ь8ъС╠Е╙J?ouIъ╝ш^]E5 └┬╥╥Щ<┼цAх/OjW╩TяЦХVS.2Х┴ч▒╢╙vvL▐t[ ф&¤PИЬ ·!l╡з lфщy╥5G:1▌W─ В У1Lр▐Ф╝╧▀gyRЬМaц╬ю`f╥┴г'└-▌х$t%Ж?°p▀н4╡└№IП▌9м╧ПHР_ЪCьл+╕Snт╗ўJєX{╧o5=╕цм▌гk.DБ~|ФЎ°├Я&¤) ^╬├ЄНе'P░∙╦nПiП┼Пў─Ъ├б9╦ЇW╫Р/╟Qб0ў( {ъ"*▐▐┘юdиmp║Toєъ)п╕`MzЙOЫы╕m╙e╗-Sъ■<▒I┘╟oRлA.C ╫ЖZА╝╤hhуь*az}╥█╟vОЪ&P▄╪'╒:Л;Є&щ╬є*Ю>$єЬ╩ФL7nbК■|ёў#Uё°уbў%п>▌╓сPx┴iНguр╦╙°в9яДчz?|╥8╒ккsя┬єн9Ц▒V2▄cЩD▒к▌(гm 1и▄н5T]{OX¤╤▄ФW∙г&ЄW═к9ШЩ9┐░фб}│Z\Y√=¤¤`ўц╨w@]чЇmг╬Cё-бjЄЧж▓ў╞▀#Ь╘rЙЪwО*a№┤Вьу┐5нбМUЄF╡╕Жн`ЗsМзP∙"Н ?#╠бя╪¤7z╢й)--°ЗqЖ{\7qPe ╞k?ЭєAД╦├Ё┬ї╤666┤┘9╞╘эE,╠┤ЕkуаJv;░Пзp{ЦьD╡Ўдё|П╛ЯV╙6┘|┐Ойo┘°·╞ ЫпЧ ╕∙iZ█g╥Zаz√фz\AAaЬИpAA."\AAД БWAAсB ┬UAA╕Иp╜А8цяYqё;╬Yч2ИCi2ЗВ В Ь "\{PL╔в97YЄ;1бMqмb╟бtM└ЪHО9▄иЬ|№N1Л$ЬeY╚V═┼1╨°q :=A&Й?юЯ°╞├"|З▌jўL5kQЪ&0С╠ЮQ┘ В В0╝pm4р8 Ёп,hvЛEьz^ш ч╗ЫЫ(╥їв╫тТC·ї8`█┌ф╞╝щ┬M {ь8E%У-Eжъ)ў`═ YКcвЯнfaх╦ИWl4ЫMФRц·Ш№╞:╩6╥▀О■t O═їf▒∙¤<О▀н(ў х=u9UjRЪlPF▐╩RnAA8mЖо зКЭG╜)Й┌G6Оз,▄║n.]H═Фщ╧J р▌m╟┐├m╠и╜▒ тhЬd№$ЦWйЇ2м'С╣&o─Ё▐;¤Хў╙?з4|нВo~ШRю'o E ╣╜ 2T╦л╥э*В з╬Ё┬╡ёШЪ─д9ў3ЙЩЕ,▄ЩAl╩\:яРшLЪс¤d╓\ рЭ╨═НsD $KЯ¤╚&УЎD╦┤{@╣3▓}}вcи▐ЇfМ╫┐▀c7р8|Ф°У┼кюйe7Ю▐Zэ7нD=╩щv8▐48;╪к%PXю╒═ърщ╟I№H ▀єЭвщё7<ўё?xhо{╨┬tяЩs?U8?n~╡_Wo sаvhЫs/zХAAЪ!ЕkНcр·dw┘z╔жIМР сс¤ХiРИ2Ює╩┬л┬S*t@'>▌E*═Х26є╣O█їЕПE┬ОтLd2╚3│▌єhMыk ╞ЭXXоTP1ж`▄─[■Ч8№ДЯьk$ }┬Циmн!Ы═ТYы╚ °5╡|[╨с╙ ЦL°┌/┼═'АЫ┐╠┤Gт█Зи!OЦ}<¤╪Bх'qXj°▐ШПf█?ЬfёгL╟иЗ°╙_лсшЗaKх╨°Ф~o}iиЯ-мi╩I¤иK¤ZШц№yє%В ┬╪^╕к╫K"\IА*QjЖ°S9`▐+╨Hб░Р+мУ┘╟R└zБ№lы"I╥*щ<ЭР╗4є╣╒гч╢єs╦X.ХP"Уєt№┼r·┌J╫Сюе)Еl#╧╕`╖чИк▐L╛╢О=[ wSRГ╜Ж╡2╩em║5$~CвАї=rGЕг┤л _√]!YJ─WT8╩ :▀р╦▄ЧOF сkгqЁ°ЗTГ▀▓ё═oЁ┐╣№юM И└є╪?M└║ИЧDпюеMГ;═П╛▀гG╫уЙ╛╡├╓Х6<Х`oЁ| В ВЙсДkгБc\'Q@ЦОщC╖╫№х!Obt┬L░Xдb$tЫM└&1╦К═жc>oТОF ╦Х yн!Oк╫тсцd╒ш]╢■╚*M.S┴ЮW<й▐L╥ZФ=LoЖэ╜Ж,v∙г(їТW╕G%~Уд╗йEъyяC╝[G%c)Q∙гПлЮ╓fгёS·є┘ищ*ЯfР■00ф №Р┌m<0oХ°`Yўр■1╦∙ >hїшц№┬╫р╒й░жЗЫ"В ┬╨ %\ОНЇ▀■╬66╪ьр ]▓S┼┴%╓оды┤ XШЖKХ░╟в╤╢Q)Ё$╩2╥kГ╠Хфщy╘╪┴╧ёнi=LOВ╓жЁ[f=зЕцy@е▒ОгЮb=Ж?°p▀н4╡└№IП▌9м╧ПHР_ЪCьл+╕Snт╗ўJєX{╧o5=╕цм▌гk.░щgА+╥AA85ЖоУ3 ╚хr3Л[t¤╓ьf▄╖┐ъЙmлX╒3kО╧дє╕пm5 57╡Z ╠q%Е▓B╕╟╡HZR═_ecмGЗ?ЬJъ∙еkk╪▐кЫызКвЪ{Ъ┼к▓кakН╧ЛжWЦЧпт^╘2є7aWли▓qЫ╒S╩iXKkXг8╓╓Ц░╙9╓▌Э╨°г└є?щ╧нMъ░▓┘j╗ Uk╚wы╧лx·Р╠s 2▄╕ ■Ё°3рЛ7╕┤К╟╗/yїщ╢З┬ ╢╟╞3╩▄ЧзёEs▐Э*╢I &║╬cхzаz ╠Aa< 9╟5 ╗к'v;║+Vў╠Юуї\KцГ+ЮЫ║zиЕкЧT и░xMk7lъэKн╓Ъ_ZжуD╞█szД-wюй╘5u╛ецdЄP9w▓а,S╙imVw▄Єц╣Ч6 ·л,O Зm┘BX№Qа4мWЇЗanъ^▀d╧ЕN┬6P├Нч█xЇ¤┤Ъ&░╔ц√uL}╦╞╫?0шЧ╟╫╦▄№4нэ3i= Ї╣▓lё▐┐$7T╔*ЬоТ6ЖЧЯQцВєb}╕?2Xщ:ПХыбЖzЧ┘пВ В М╬─лWпЪ╫~Ё3╝№▐[ц╥╔sэ┌5s$~xчме╝■8,Sя&¤йтЙ¤cїБWз(х%╜x·0¤в@a╜tFыш В ┬┼фїы╫цh8Xл╛·шэУъqДсрxопmW░▄m4■╨[╝ЄК╝▀E┤2╓2╧nв╣'вUA╬ о┬╣$KЭ└╬c]P[╝nс°▌*Эt╣ЬZzAAшЙWсjєA ▀ньсЫv_·JAДєГWAAсB ┬UAA╕ИpAA."\ЕsЕєI╔?+Жм0ыа°gH~u┌╦N┘O X╖vxд|AДє╔Ё┬╡╤Ау╕╗a9╪-с▌_асьbs│И"]/7▒{ЩўВЯС4тг·pAё6▓=Й#фЖMЄбg╫кsДН:jпO  ╨Є9k1w╞ёЯt√AД -\N;Пz╝zиюc╩Ъ┼тт"gзЁd├'lЕ▐XoЄ▐и╜▄ЮD╧C▐ёйА╩╫m╪╖ └│4Ц╬aПZъ+{h~г─╗ЁЮ"з|╬уoЯВ ВНсЕkу05┘} б╔,фpg&Ж╔IrЫ┴эы└qCz]√У└Їц░+C┌?_C■uЕrH╜CьFыёjї5и]kЯg1ёу$КЯ+╫ чУ$]╦j{EgПd ╙Ыж├h╗k╣ │'T/Ьr├╞пБ╥и├`;цвЧ>щ #д|tYXфt═j╟U╪ЪЄ═ке%ш7┤|ЩjЯВ ВС!ЕkНcр:Й╥hР{еseе╠Ю╝qqs╪ХьЭЯ╫Б7ч1ы ▒/░ы:ОXм▐ШC5╢ДлГЭg5рЭ9╙є╔├╘Э=Т<ьлxГД▐╫+*МнЗ█ШЮf7tЎlUЛс0{"ї]г░+$;!╤╡_FэЭКr│Єц!╢|;╟Ед/Д░ЄЙ}eЭтнаЁ&Iп╕NЫїп ╢#ч╗Ё&ХЦa│0■№╚И╦■щП∙┘!ц╕Х <╓<∙пкхМ ?О2 рv∙ДХ/qВэSAв2╝pDИ:ЮрЦь<╘ЧJ▀╪CО─SМЗ╦ (qНjрНi┤%b sяРЁynz∙>▀Q┬%s├ ╪лє╬╔Ц{"цК╛wЦС╗┴n№b8╠Ю\РЫ║юЄ·|[ЛоR╩Mъ+╦Ш'╫"B·╞W>Ь67eЦJГ2цJ4т╕i╩ ■ЕА╧╨ЇGЛ?Aeл¤╧кЄй ▄эUнb√╒чt╔╪ЧPё╓wX∙*N╣} В BЖоНОqУ,`щ╕=а╦TА╞6wЮр·эyQЭ'>?$┘╪&u#<█&ЙC┐3■¤jo░|C█ij╚?pЗЪ'`╒╜╛█┤E Х&J╛0┬эЗ'Z·"(ЯУgЇЇwbЧ╧ПP7ЗВ ВpСJ╕6/ш┐¤Э ll░┘┴║■dз ятjeБН}рЎ"fdЪ└YбЖ╜_oaз╒╗iЖ╟=╜Аz║@█╧√u Йwf?4(|╜Й&Й═Ц∙рt?бъ╧ЁщЛT>'╬ Цп ╙ В ЧДбДыф╠r╣Ь╟╠т]┐5╗нO8╪▌─╞╬1жfС"фяЩN3,ЯXDїs╬є"Цъ$Nу╦hK#w║@█╧Ш Пl}#ЗХw╕G0ЙтsЄ╧a░1╓я▒KШ=a┬l-Ге╬═▒╒лл╩MїУ5 ╠╨ЇЕй|bJ─╓Юнi7╞МЕHщ%~S╖ЗYУ┐,╥ю╤╙╩(>Tb°у)ЮЩ▀' ўучУ%:чПH){ кaЎъу fZ СЧu:[ю(¤╖9miхfїї =e╤в_·┬ЙX>╙ь╞MЩC;║8!J·GЙ?їБ╟я~Щ╕эЩж^╛В ВpШxїъUє┌~ЖЧ▀{╦\:yо]╗fОс<┴_ў[╪z╟╞▐А+В В╨Ы╫пG╩cн·ъг╖Oк╟U."1╠╛├ы╖.!√I╒nSAA83D╕ В^3╡Пг■l█ ~\AA"\┴пcZ┬▐эб$╙Aс\!┬UAA╕ИpAA."\б О#є[A."Э╧oЗwЮ?C╬:■╦ЕWсBс│H&Лc[?5И bЦe!ы_lv$?Nт_з'╚$ёр╟¤S▀xX─Гя░[э■ЇqPLN Y<л'mя°O║■Е!иf1AўМ6┴5Ъ¤\╠·л"Kyчєр▓н~}╛D/ ^╧яj╓вk╘>УYЬ┼cэму┐l /\ ·╤0 8╪-▒ыйРЖsА══"КtЭ═&Y╩ц'KС4=3╬'=|шaх╛рТЇTё▌┐ЎET╗▄р6ъи╒█;lНz[∙2т═fе1э╕╩L~c eщoG║Жзцz│╪№~╟яVФ√ЕЄЮ▒Ш▐ї╓b√╝sВхУ*┴╢m╪ХМ╣╨Ы╜Е3g°·C√ьє№NХЪt═Feфн■?оЖ├┴ ТMт┐бw╫3С─╚╟╔╟╡Z╕6Hhь<ъ╒╚<вжk═bqqЛ│╖А';8Рw╩ЕJK╝sVїВ+х4ЦZ7w└▐о ^▀┬vЧз_*╖GА╞и) ФЖ╒2Рй╨я$VИaЄF я╜7ч▌y·чФЖпUЁ═S╩¤dkw+Б9╣·F!ЛQ Gъяrsvїх∙CnOя ╕:цpЯd-№д╟<╜┐╛]ЙгЮ╖Ё▀wи╙УЛ к1╝pm╝ж&1i╬¤─pga 3ЇтЭ$7▒X╫НХ╨ ╡a·▒ ·▒жЖшЗЫ:NfН╜┴╜┤sК·Z╛╘Є7Ю▐╫,Э{ЗN|чтWю)<╫█E╛їкkФ╢ ы9д°Ч╩a╜Ра┤оЩ_Ю6)эЙ∙YmKб┤╖ч√┼\е╕╜▒Э├Сz(йmпН ╫{gПпg[Ь╞х^П\O?NтGj°▐Шя¤г╧¤C№Ъы┤0Э├{ц▄O╬OАЫ_ц▒яP%█∙яТЄц?Iuи╜кb╙e│c.z )┐PB№З─▀п■Э"ч█2э▀j╣ы] Б№╙ дуNRЩ╡▌ Ц╟╩_ ╡╙5ХGJCQ╜╖у'┐tЮ-█aP°э╘√╓ ∙рх3h▐├Йr r■┌m 8▄▄/}a■¤х╤qZ■Жг!╙б}щ:2╫╣n╜БGjЯ}т'·ЖВЄлЖ u╣йvг╥─щ╤n·╫/╤ч■О╓> * v▐х·№vIa.CёО│╧┐КCzм╟WJ° ╜┐озJШз8ъ█е@ЬD№WП!ЕkНcр:Й╥ppк╪╟mРО║Aх▓NэШ┌╢ЦАщТrj▄t3╕ў.▌█Ї; иР;╢уў┘╦╤5║NZЙВ>fC:1тg╩[└К'■╡nўeЬг:лR╠zъ?vУ{ы8RсЫЫ9┐Dy <м йў─┌иp&; бKvj╕ТMБsТ└|+BЖъьёї=█эC╘╟═mЇщ╟*?Й├R├ў╞|4█■сЎ0ЛeЄ8■C=─Я■Z G? [JGуS·╜ў%╦ЬПЮCХж:)Pс2(─╦╚S╣еY═r■3& qФщ╤╬?╜Р╚s-SQ~Wжщ%`м╩п/a■├тя_ ▒▄:┘UL√╫a░Y╧╡+│o■щZ' j[K█╘■┘ ЭХW}э┐гХ┐fzЩ№%( Xж6╠;╕╡№3хнC╠╤Н╩y═╨═ЩОXсх3j¤Ж╙ ■╒p■VZёч=╧Чhщыэ?Q╩_ЕOeпчa╡Т╛эK╫С{>^G┌;Ьъ?╝|·ЖВ~Vж■╧╦■ї█ ■Оr 2нЎ▀н~CЮ▀^мiК(P╖#сспш}є[ЇXQ-т °7M/М/cП  2╝pUо¤Ел│╦є[7░cOсv*╓гwV`шЗЪf╚╤П╞Ц_ъЗ╡p╛╤ ыЇА wl╖╬ўю╢Єбh∙'°XsЕ╛ёH{╢тЯ╫ў▐ЁX╙tл╖сOС+з∙wТ║┴█Ъ{bcш)∙╚NWю`)Oб▓ЗЎ{┘№Ї°Ц╗■"ю├Чб┌░┬╫FурёI╡|╦╞7┐┴C№цЄ╗7¤m■∙ьЯ&`▌╘ Й^▌KЫщ╤ў{ЇшЎдКmК^хЩ*И╦!╟sкJ)╙М¤J╔ф┐д╦┌═u[Л*vOЎй▄▓к▀гЦ_Ш ░°:_▌ыЯэ\K╣S╞\ ═?AЧ5є╦║ SsJ(Dы▒№эЧn╝╟█7AiS■щ\ц╖<=в╒√щS>у║?·вуьy н№еf═є┼<"жпз H V■еUБ║№#дП.kz╢/Ч ZШ]Юзgг√г^╙╫дЄщ~(x╖NВ├RвЄGW=н▌FузЇч│5═ ¤a ╧щ╛Йw╬[¤`Yўр■1╜ щ AлG7э╟╜PъЇ*Ъюе Ф¤иМV~г√БЄ▀-$Nв~аw*хяT╛^░▒pЖїcш%5сщыяtFM╙╜}9(кi&ЦЦЦ╚ф)┤юtў╧ЇЛ?z°]q;шЩНЫЇиб╩гhЇB8┘ў3о·╒#А^╤="T>┐н■цЁлT0O? ~SYtgьё_AЖо ╟╞ ·og$H76vЁДо?┘йтаK╧q}Я┐╧ТЕ╠FВЇ ;П)ЛsО■Е╣ЕOїлЫ╖ы╨ "/Б;4ЫdЖ╦2ьЖШ4 *?■▓╙c╝кD=Ь√їB─Ёюс╗ХжШ?Iу▒;Зї∙ RтKsИ}uw╩M|ў^йckя∙нж╫Ь╡{t═Е0b7й$√Ї*√Q )┐PFї?c╔N幤=L·■'gX?С8_щлr║O|МР>■аGЕ№ьээСсiГ╥'■Q├Wэ╖ОэmnsжqИэ├e Iп╨чwЫ?изшжЄ∙M║  ║ пзRрЭ┬ ╩─╤э╙Ю▒╟J╕N╬,иЮ╘╢ЩщR▄Ъ]└Мz√ЄRYОщН%уьт)█hsbп&^M▀бяйЕ│Ру╫bU█+cм]М>Tя]7^xj_лщ╪\sё║ ·3мХ_т%о8ФАе<=° ╦╨П╓*КY▓гДs╗qЬвZї=╒uз=╝в╬═1┴s є<╡l)w╩;Ю#╢ТсЙ$ХЯ▒є┌31~"7▌&╞=птщC2╧йMУщр╞ML╤Яу╧А/▐р4Wё°уbў%п>▌╓сPx┴▀xНgЇb°Є4╛h╬;aq°░GбчЧ╙zКE+-g╞~Х?╩аы╒мЪПЩЩ3/6U?eмfл╩_╡╕▀╙(х╫П0 aё3╞Oп·чЫD╖ 5ЭGc4!∙зФ┤├ ЖыхД╩?"Ь╖в ╖К5юQ╦╠Q╚mxhШунW;юя╛х3j¤·(c█F s▐╗■·0жЇї/Я(шxл┘до┐ХЬбщуssш;аоs·╢Qч.╬vaїў╡|zЖН:=Юж-nGuuь├─┘╡~г▄▀}я▀Ї{~√╨╙v║Лn╧╟gС┬4▌▀u║┐?б4┐а√{ЛтИw╜┐√┼/Dц╒лWM|ЇIєх╦Ч#Ш╟═√wя6я?ЎЬ▀╗█╝K╫┤╣╫╝ рi╦╜р╟.╨Oa·Нь5Їы╕ГJ╞я&td╙╡Д╟ ╣зKrЫ0╫ЯыО├И╞╕u)Р'2vЕтЕнMВkеM┘%ZvЎФz.°ь╡╔Шtv╖O┌!pIT(╙^√ОЄг╓╫╜■Ъ═у¤Ls3ЕцЯ┤Lв╣╗хw╙|Vhю~╗эfsн╨tЮ;пЫo'Ъ√ц║┴Yгыk┴Dy╤∙L║╣щ╠Я? ▐ЄO43>;ВЄNя2эП жB>p∙ї%─▀°├ъ▀hcаp┌9шЭЫs;L6Бp[ЬP∙лv╟q┌t_щrQiвL╗╥ёf(▐VЙ@╜хW(┤▄∙╩╕o∙МZ┐_·╜akу┐█ёщrИ▐■"°яW>╦┐7Х╜/iК▐щ o_o╦/╖НКJЗF┤Ў┘п|·ЗО.7^7=m┐aїKЇ╜┐ =█gФЎAиzdwХcЁчгУ`<Г`7 g*у╗ф .Хё╫┘@И░°/?м7G1мU∙я sэ?├╦я╜Eu~:\╗v═ W ю▒▓░5oў╬?{xч■╕Л;шmОуXитA:НcїБ╫Dкf1┴]PdP9У5/▄Фц)╢GR┐ЧЫKT┐╜Ю▀╝\Ч╩b"Г┬zй¤со^т╦┌┬╝э∙░wLDК  Ё·їks4мU_}Ї6D╕ з п┘╟▀Oe +ШЫM!uNo`Ох!а╪)дП╖x}№├<О~ЪA║╥97Ў"╨}XП┐6З┬Ь▒p%д~/7Чн~Г╧яH╧s%р∙6┐X?═ў╔yFДлpA╤sЬV╖ъИ╧пгtUz║ЁRX▀пcъkє╕єa─UД+╞┘ WA╕ЇРxп┌$/╧ko╩%@Дл В Вp!ЧprAAA8]D╕ В В оВ В ┬Е@Дл В Вp!^╕6xwмЖ┘¤╟┴n▒И]яКaЎВ В В0C ╫ЖS┼╬г▐J4╠^AAaxс┌xLMЎ\w2╠^AAaHс┌@у╕>┘S╢Ж╪ В В ┬` /\UЗjс┌╫^AAc8с┌hр╫1╔ХОщГ9│AAДj╦╫╞┴&6Ў_Ш3/╫q{q1з┐¤}Y╢|AA╕*Мk╦╫бДk'╝▄╒0Ы├ЭШ╣ф├oэЪWAAДл┬╕Ды╨л В В ┬i2ж╫┴РWAAДлГЇ╕ В В W оВ В ┬Е@Дл В Вp!с*В В \D╕ В В оЧ╟№╜╩8ОФ┬щтPЩЫCAA8!D╕ЎаШТEs2ЇЄ╬Т▀Й mКз·2w(▌░&Тзя∙┴)fСдВ╖, ┘к╣8?Nт_з'╚$ёр╟¤ ╖ё░И▀a╖┌¤UаЪ╡и╠'0С╠^┘╢'В Ь<├ ╫FО╙@CЭЁ╬XEьz^X╝-lСоyН╫■▓Bяo╘уАmkУы║У╪╔qУтО*▓$°&║й=зи─рDЗ9MБ▄'}.╒,м|ёКНf│ЙR╩\У▀X╟B┘F·[└╤Яосй╣▐┴├,6┐Я╟ё╗х~б╝g,.:·GQ▓GЕзJM*sФС╖▓T[В В0~Жо зКЭG!кх·m╠..b╤Шю█┴^"шm]ж?+% Fyes║─МА╪;┴Ь╚ УёЪy▄4Vg лU*¤LЕыId>Ж╔1╝ўN _O Ь╥Ё╡ ╛∙aJ╣Я╝a,о1фЎ*╚╨]░*▌оВ ┬ 0╝pm╝ж&1i╬{19InМ9╫РшLЪс¤d╓\ рЭ╨═НsD $KЯА█ЫXT╜Zк7У"ЁЎZU│ц║▒+V¤┬└g▀╤к├OлЮЁЛ<лA█*┐i%║QN╖├ Їn&цЧQ*Х<&╓ИN1й▄{Эы0=yа:Y7n2I_╪cHЯ│ГнZЕх^▌мЮ~Ь─П╘Ё╜1▀)Ъ├s  ГЗц║-LчЁЮ9ўSЕєрцW╟╪╒█вO∙U│t═_ч║N╝m(╝№│┼v╧zТ┌вЬ╦B╛╘ЄV;Мот4Е╣ ╣;┤═╣O▀^sAAш├Р┬╡Б╞1p=LМ╛╪GuУз lbsў└/╬┘4╜lщЕ╦├√+╙ d, <чХЕSЕзTшАN|║Й▐сюл┌жc>яЎZяK9П-dРIР·нХСЎИ7k┌эх╘v∙ЇТOи╕Ўь╡╡|┌ДпN░d╨~м╣IЭ&L▐9oїГe▌Г√╟$ (▀┤ztsбSiвSгЎзЗш┘Xм!1?(█ cДуo AДё1ФpЭЬY@.ЧєШY▄вы╖f0├oo2Н'√╪980=▓8╪ПЎ1╫Щ@яoю+[═ТH$сS-ц╕╥xЕpПkСДБЪ┐╩╞XП Ю╫╔єGУKъCШ`пU¤pUJ\vmKЭєЬS5 ╘йвhцЮо*┴KrM╧C |├╒ЮHxюi╥Э╟Zїх╤?╟5~lк╙5ЯVi╧┤║[ c╟a[KkX[c│ДЭ╓Ь╠(ДдO┼QC▐ЭШщхyOТyюаAжГ71EО?╛xГK╝К╟╗/yїщ╢З┬ ■.k<г┬ Є4╛h╬;╤'їZRк'▒╡?ю1MкПЄxseМ╡FU▐╬b╗Ь└№мG6FЄЖр6Pu¤УщD╧oNtЭ╟╩smУ╚ЪAApqч ╕sSЗ3Эs\_>еkўю6яЄ▄V2ўю?h>5vчТКЮ╟╩}йЙМЪж╫l═q5Ё5oЯkp║е═s]<pP▄∙Ы3╖Х %в╝▌,d·:EРйўd╘ЬCwNhЗqч5чЗЎШ/j╙u7~6┴9зж╦╝Iu╜█№Fo┤╔T\╧#ж╧е5╖╓w╡y╝Яin·цЯ&Ъ╗н∙мЖgЕцю╖█n6╫ M'8Ч╒ыц█Йц┴╛╣nш:/╓З╬W0[╤░){┴Є3V.& хвшч?ШоєYГхO№.┤┐юї╧шxЕб @A╕└xчлc▄9о№╧╡№ /┐ў╜ЛNЗk╫оЩ#A├=qiФ3Х╛╜*Ё╬YKf0щк▒nB╨Я*д╙8■ЦНo~г╟ 9oф`ma▐>бuv╧^▓,═Ч╓KЧ.В ┬h╝~¤┌ k╒W╜=№:оВpЙхJ╪k6┴╦7-Я╥╫AzЛ╫4ОР┴√╜D+▓d╫E╞Zц∙╦M4ўD┤ В 'ЗWсRЛеNgч2╡┼ыО▀-`б╥∙AЧkХ╩▓g5Ж╦├йХ╖ ВpеСйВ В ┬Й"SAAД+ЕWAAсB ┬UAA╕Иp╬╬'Y$ м▓8╛ГтЯM ∙Й,dпй"√у dЯЫ╙ССЄA╬'├ ╫FО╙0╗9╪-▒█ёЮ#7╗╪▄▄D▒╕й╖Г║єyI#>к'0o#█У╕!12An╪$·w┼:/╪игЎ·Р■Ю2бхs╓bюМу?щЎ)В Z╕6Ь*vї{їРШ▌▄└╬■1жмў1╗Ш╥█┴ бXoЄ▐ж╜▄ЮD╧├4╩(аЄuЎэЁ,Неs╪гЦ·╩Ъ▀(ЭЄТQз|╬уoЯВ ┬U#Я╧Ы#?╜о╗ /\/АйIЇ╥в╬юЮрfs ╕3ClRTk8 L┐a╗2д¤є5ф_'P° З╘T7rXП'PлпAэь <ЛЙ'Q№\╣V8Я$щZV█+:{$[Ш▐4F█]╦MШ=бzсФ6▐x ФF√█1╜ЇI_!хг╦┬"7аkV;Ои┬╓ФoVхС╩р>ж▒w·#╟ євзlГ=Ъ▐Ё)-A┐бх╦ЬP√Aо$AС&ZЩ!ЕkНcрzO1ъ└y▄z0Sv3н@ш╩77З]┴▐∙yxs│╤√╗оуИ┼ъН9dP├aK╕:╪yV▐Щ3=ЯAДлЛ+VгИVfxск:\{╫F╟╕ОуGч}є█дndАg█$qч▀oбЎf╦7┤ЭжЖ№wиyV▌ы╗M[Ф▒Piвф #▄~xве/2БЄ9yFOЗ v∙№us(В чЕаH н╠P┬╡с╪$K_`glx>+ЁdзъY9АДнLk=иaя╫[╪iїnЪсqO/аЮ.P╞Ўs└~]CтЭ┘@ПXЕп7╤$▒┘2Ьз]ўЗO_дЄ9qN░|eШ^A8з╕b5КheЖоУ3 ╚хr3Л[t¤╓ь╘╩У1X╫I╪Vри┘ьў ШK8A╠░|■a╒╧8╧ЛXкУ8Н/г-Н▄щYl?K`■w<▓їНV▐с┴$К╧╔?З┴╞XєMз%·╝╟.aЎД │╡ Ц:7╟FTп>м*7╒O╓№s0C╙BдЄЙ)[{╢ж▌3"еФ°M▌fM■▓Hє╘w*HX∙ В ┬ U┤2C╬q c3й█$fў▒гzdўq|kЛwz e 'L %їqTщм¤<ЁNеуу=]аМrрC%Ж?ЮтyС∙}Є╧aРq?■q>Yвs■°З─С▓ўп fп>2aж╒yYз│хО╥Ы╙ЦVnV_╧С╨S-·е/ЬИх3═n▄┤С9┤гЛувдФ°Sx№ю╫СЙ█Юiсх+В чБЙWп^5п¤рgx∙╜╖╠еУч┌╡kцH╬№u┐Енwlь ╕bА В ╜y¤z┤б<╓кп>z√дz\с"├ь;╝~ы▓ЯTQэ6еAAД3CДл xр5S+ё8ъ╧V▒¤я╟5@AДq╨Ъ* В В чЮ*аДл9AAДs Ё оА∙Qъ+IENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/images/screenshots/collectioneditor.png0000644000175100001770000006236014654363416023212 0ustar00runnerdockerЙPNG  IHDRМП╓OБхsRGBо╬щgAMA▒П №a pHYs├├╟oиddЕIDATx^э▌L[Wв/·/g8чщ=uШ╤╜D/-п'┴.ДAХ╨DтГNN5ЮеMhN"хкUJTBМЇl'\ж╔▄(zi.JDД¤4PЕFнNдd·Г╧щЩy)цp_*юЛ<b;LE'ч╛Ю╥є▐╗sЩсн╡Ў2╪`ocГ ▀OgOЎ┌█{yп╜Н¤eн╜M╬У'OADDDDГ М?°┴tСИИИ╢Уя╛√N╧eПчЮ{N╧╤Fx·Ї)■B╧E┼└HDDDDжЙИИИ╚#Щb`$""""S ▌%¤_ ы┼я {№∙╧╞твё-<9▀√+№щ╧9°│,Л)┬Я■?1╗№Ш^°_`╡ZСУУг╩DDDФYxЧ4┼#яТN(0■є? 3■▌П AЧў ╖а╡╡ ?№сї"""╩$ МO┬_лєз?¤I╧н═┐■ыwIoKDDDDЩ!б└(ЗвУЪ&"""╩4ЄТ╣DжL#ўщып┐╓е╒ф║TяwZoz1М#h ;rкtЇ:!рBeN%┬┼Х╠6 ркDN╙И.╘▓JЦvuд)·qИK╟╠kєJ#MkmQ·╔мb6eв{ўюб╝Є'QCг\&╫╔╟дRB╫0ОППу╫ _-ї4■х_цКи∙Ч°╙ UYd|я/ GQ■oтрП∙▐ў■ ўн  ? ∙╧ёo э┐U╦"╔аs┼■18-бr-·ОcёrНzD|+ы╚@2─оО9▒╝Л║н▌Ёлх▓№),^F═Ъ█Ф ╟ С}└eИ╠=WDD█╨v╛ЖQv╥─ ЕЙ ОАTГУ▌└ї█[н7╬чщ┤\▄JgЧИИhs╚@(Гaиз1]a1$б└Шl▓^є╡ПЦ╫p╕вЯкL!{н┬ЗZe╒ЄЁu╙И\_Л>x╤b╦╘РяКm╘u°6zyшqош├└r°4┤|y(┘╪жййR,пDе╪ЧИQf9д>─м`ф┼░╕(Ўщш╘i\~MЧЕ└C*Кн║$├K%ЁN∙u)LD{ЪвыиП╤эY^'ў] ЛыЄъ╦b╘▒ъШ╔х+╧├╩sе*м┼иЁ=\qМИИИ(сб1ЭaQJk`L]о V\?ьWu╩щrM ./у8*╨э╦V cЛ cmA╔░╛┴▀ _mxgъА▒n°8╝-u0j.ыmd¤}ч"╢ё_╦╟0v·8·МdлМ|┌Зуз├ЗЭ%?ж╝%xiiбh╟╤ы8|2╤!ўp▓=b[┘V╡╧└╣ЁРзШ=F┤ўpU╖╖п6G!█bФЧ█зОU╟,╩y╣ИЦТaуqbZ:5ЦЧPтЭGЕИИ╢Ъя √ MФЭ М▓Чш{wРє█?кщ/■ў О┐·чяЙeTSюW─ 0ё=ёя┬╥▓┐ЄК╟ч$sOM┬:█ Б█╕ю=О╙k╣°-Ё>c,Е'Nўb╣уNЬPpл9 щ├├P.Z║EЎЬЕлXю-Ф█, ЯПрS_7тх└Аыи╜WУ╗ЖO╡Gўт╔}лэ[▌ i·╤▐л:╨кЎоhKи¤ёъИu╠┬╔Ю─╛┌(7╣XQ\c""╩j╧Ю=Khв╘ ЖЮNЗД▌ў╛ў=№щ┐Ь─т¤ аж? Яэ°у с╦■ГЪ& ■█vИO.-√у╫'UрР=L S┴0╝Gn╚с╪Z`XїО∙╤╜tбсJЄZCЯъ}У╫$·V\з╕┌.╢xсm1■ЄMО╡^o мХ.`┼Ї╩!ъe╟ї~щ)ъ═AЙ<&Юu╓!┬∙Ш╪ю*Ок╢.▌пьq%""вdм╝fqх5НйЦP`LЎ{БEBЦw папm\=kB"t=д Вр╣╛░╟X№SЁVC┼5`╒╥и,по_─┼Х╫). яQУC╖a!L▐р║KZї╚-ўV^lЙRЯnПщ1Hф1ёдв═тГ_$n_иKQЎ^ЖО-%%╓ .щ Нi М2Щ ·T_╦v╜[ Ьc├( ї╬-їZ╒р└q]╟r7Цf▄uэл╒їлыЄвД╤ХjNв-░╩mОNб$fг З╣K·╨Wr:╞0│*S╞╗уYЎ╚ ╡кm▓{3┌W╧эН]:▒┌l■ШxТйc┼y√NIkK╔Єе2МЧ╝з'ЦИИhу,}╓┼Ш2╤▐╜{WЕ┼РPhФПIеД╛Зёю▌╗°╟ RеKЙ{·Hк╙ 1╞ў0n ЄОъO─ ║Вт^ї=М█┐ЗСИ(ёoIS<Є{ М~┐Я|Є1■ї_ ЄЛ╣e╧б╝бE╬/ kдp╣юП№гЪ с  ■■я ^№√CU▐r ГЄыkмSзУ╝ЮpkР┴║k∙Rv""┌ МO┬БQЖ@∙р?¤щOzIbф═2▓юLэ╥MЮё5?-^∙U2ь1#"вь┼└Hё$ЙИИhkb`дx■╙АDDDD┤}10С)F""""2┼└HDDDDжЙИИИ╚#Щт╫ъQLK▀├и╦DDDDDлpHЪИИИИL10С)F""""2┼└HDDDDжЙИИИ╚#Щb`$""""S МDDDDdКБСИИИИL10С)F""""2┼└HDDDDжЙИИИ╚#Щb`$""""S МDDDDdКБСИИИИL10С)F""""2Хєф╔УE=Я2ю▐ПЇm$З¤=Ч:i М┐шpъЁ°ёcь▄╣SЧ╢Пg╧Юблл mmm°■ў┐пЧn█їЬoU<Яй├cID╤Д▐Ю>}кЧ$Gf░tFIзЙ ЛMMMъф╦└(╦DDDD┘ИБ1 Baё╚С#8~№8■■я ЮбСИИИ▓cКЕЗ┼╫_]-{щеЧЙИИ(k10жБьU Е┼РPh№Ч∙╜ДИИИ(;00жШ╝╣e▀╛}║IЖF9eF"""в,╨╥╥вч6у║Ма)зоА."WerЪFtЩ▓╤HУ8З9лз╩хMЫaд)ь|4ЙЯ>!рBe─╧ ╤цРял▐#фk6цч┴╩╧"sб░╕YбСБ1ЕFЪм╕~╪П┼╦5z eгЪ╦ЛX\Ф╙0Ог▌~г<ц┤шG╨ЖУ┴░VчEL■b<ФЯ├'╞╟└SCЫнц└qxз№║d∙┤╟ЁєА╓oeH▄М╨╚└Ш"W%j1╠PAФ■)x+Кa╒EЭ№жLb-FE▀зF╧╖2ВO√ОГyС╓+<vwwы╣НН Мйp╗ ╓ыЗс_┘│иЖ╦┬З╨М!ыИ 9dQщk(sЩЭ7=м4▓|о#ЖеV╜()5p▄█Вгл╞яЦЗїф/m╦C╓b Э0Ю┌Ц╫p╕вЯЖ^`#Явя°╚OЕИ╦\вQпЮ^QцkШДPX НЙБq▌╝"хўс°i'"√┼╝ї:ыс╠┼aрЬ pЮ>О╛еw∙Юm[╩,Ц8чM╝╬W╒Pй°An9к▀шг╜VJL ./·q°║U}hF√╠╡8╟Мум.%8Оaї ╧m ^;\▀Cуї>qЩK▀╣5^╖╚╫0нЙЫ╫н▌├▌Ё╒о╕x9Ё>$м·╖┬┌>у·┘S▓4l1ВO}▌8╔!Л╠gz▐─kркП'NўB]╩ы5@I┴}L|`КPюлНеС&y▒уe╒│├s@╔Є┌aр·m─}(]C▒t├V-·ЇвДё5╝э┼ ЗS┴ъ─╪pЙ°Б^9Tp|∙"}9йПЬьЎй▀оsЁ~MўRQfKЇ╝╔ =лD{ ╨║ИP~╡╗"в╟wЙ°`Ц╫GfЮ┌ rX╫q{ф╢° ├xM╛ID▄░хЗxщ&Бпa┌| МйRs~(jC╫#Z^BЙ°]2┌╨Бё[шE\╝Vя(Ф bЯ7/о▀╓ч9 >(╝·BwУ╫н╤И+м?А█╫╜иXъ╛ Б╤╣Ўa╩s@JK-╡-ЄM┬°е2№Ж-ї■ gV▓в╕BПLHЄ·G=╦╫0e ╞Т╫P Ч┤└кBc .лы┘Мkо╘CУ├Ц%}ш+9═п╔&1╧[JжОч╪┌ВТ╨pи┘kА╓жц%LЕЖфrмh)Y¤Нr(ZЬ╘ЖОu╝ЯCв4Р┐XVИ Ц~йм9ЙnИ╧∙┌;:ЕТи=М·:щZ¤¤TЎ)ЖЁ5LЩ!ч╔У'Лz>e▄╜сN]?~МЭ;wъIЄо╣O,о:█:╢ъ9_}▐F╨Фs┼■н¤]А№NK"К&Ї▐ЁЇщS╜$92Г9ьяшRъ░Зq3\8╟Ы]▓╧mS МJ щ@∙ б╗j) Ё╝╤Ў╞└╕бЇ╫ВЁOЩe│є&┐РчУИИ╢6F""""2┼└HDDDDжЙИИИ╚╘Ж}н╬О;tЙ╢Г@ └п┘B°U0й#Пхw▀ъСс╣чfPXX╚п╒!"""вь─└HDDDDжЙИИИ╚#Щb`$""""S МDDDDdКБСИИИИL10С)F""""2╡╔Б1АQ{к▄]▐ю*фццУ}T/%Cз ╢Юа.Qж█Ї╞З╛qМ?XK`Е╜╩Оh30jGU(ихV┴╛IA╘т╕ЛЕЕ 5ъ┴?5БЙЩG║ФЬ6'вe╠а╟ [^Є╘dГУAt├иco3О╜╙гRRц╝M8╢7{╒TЙє╫╢╓/╤DФ¤690Zр╕╗АЕ▐j]N╨╕O╧ДEW]?JЖжUXЫ*AkC╘`IЫй═Юy╠╗l║ЬаЙ)=╬ў┴▀Ь─№№<&ocауD╘`Iй─Э!а╛╜ z %k;·`q√qя▐"▌%╕╒u╫цЇj"в ░iБq╘ъ ╠]1$= ╗ъ ї.ў█╘буh- mo[ДФЛ╔вц,Ц=вTВЧМв9┘3YкOLUn,эQ@Ўh.пл▓/пЛhC╪Єxbn'Ў├X.┌┤╘[*┌┐EF╡=╬PO`▐К!iЬкw0╘[╕▄ShlsШ@Gih{з╪"дLLjо`wб(├j)нDЁw╣╨l│ъ2нOЕШМ7л№№b╝,▐╗^╠WE"вМ░iБ▒║w┴d╪v¤Ўc`zCЧА■╓. НmЖ╨(вреic√ЕЕ^¤У╒шЭ>╝п├X├ЬЪн3#a]?ph╙т∙╘4р╨o▌╕D@-╣д╫ Й╖ёшДj├┬┬4бE &╗Ш█Uўba·ТhЭя?эЧ=е▓¤ЯmН─hs═лЮ└ЫQ╗д&00SЗ&'q│шpлPhls " vN█╧╧╗`ЇO┌рЪl.ш ybэУбuD┘вя Ю>╘C╥gжЁюрe!ЙИ2┼ж_├ЭДm╒░X,ио=д· у3┬ЭOД╗бб! Х°P╫РHпЯ{─М╖6алkXЕAё┤Ж└0nМ╦}R,┤XD(э!ti¤(▄n;ьЎ.<И6JK█кэ кы!╫:dЯХD t╪PPP█лїк▀0╛ zN─Tq'n▐╝ЙЫ┼S8xвG,%╩&\;SЛАе[╝/ ├mё┴q╞ОHQ&╔╨└ШД╤.┤О7тTп╒╒╒ию└%┤в+nчЬ╛Оr·ЎяyА╧▐/2ЖДї┌ШфЁq╤√xА¤╪┐?ЎФ$kУ▐ОVє╕╤1╤АvW3l6lо╨Й╕yeяEt▌?Оw▀sвввя]EZЁ▒Wп'"╩Y╟qC$┴@ `LrС║f╤ЗЗб.E╒;ичMЙэGeXj█╨vHОСыz,╡8T>О╓ C╧еЯ/Ё╨ФBЫCДS┘хш3Ю,ЇЇТEv]·>[▐V,Kd;КfГ" ГAcТЛ╘5ЛSЁЗ║Гw08бч)¤╘╣╨єтМ╚єBIP╫,·ЁMиKqю6<ўї<QЖ╪д└(ol1о5ФЧО╖╩^╜Х7┐─RН^yсcККК─╘Аa╣Щ┼ББK%╕║жшJ.M#■hn]u▓=╡·╨xi5ьlБc`Ч─Ыy]h}QЧ w╟)4╩ыU;АSЧP.ў)ьЖу1¤z[c?═╢√G∙¤НEн"ыЫz╢╘w8╩[Мk И°╫Qкц√>F\Є┬╟БГ(--╙ ▄СЫ4уГ╬b ЖnЖ)Dqч$╓z6%CЬOu.ф IЄ╘чЕwи'!▀Й│m%Ё╘ыkып├╥ц╟{╝ИСИ2H╬У'Oї|╩╕{?┬/:Ь║<~№;vь╨еM ЗБe2]б\╩╗F2дУ=к;wю╘%╩vЄgШч35ф▒№ю╗B]""2<ў▄ ёЇщS╜$92Г9ьяшRъlЭk═╚╗П╒]╔С├"Q|█#0Q╥ЙИИИ╚#Щb`$""""S МDDDDdКБСИИИИL10С)F""""2╡aщх┘│g║DDDDD+eЄ_z┘Р6 4р╓┬? Ш:ЄX~зKDDЖЩЩч°зЙИИИ({10С)F""""2┼└HDDDDжЙИИИ╚#Щb`$""""S МDDDDdКБСИh╙═a─uMMzry╨k▓╟Vh┼▓)Б1роBnnо1┘Gї╥Х╡Wб╩═╖Ьн ╪cC^^Ю19=zщJAxЬ6╪zВ║L┤}<╝~ >Я.dйшmЁв)ч\I┐ХЛ ┌t Хо9]Яоc╚╔┘kLM^╜t-V╫Щ Ш+ї~е║ю╡IO√▓╫z_ГZрЪ8┐ч1вЛ█═жFЛу.0╘и─Ё╨7Оёk9├г░W┘┴МЩy Ъ=ШЯЯ╟═╜  ╘&fщR"0z▓МmnвeшЬ╘╜\є.м▒Т╓aвуNЬ8БR╪9$ЭEцрRРї8'~b╥;|*▌Зп°m╒г╣ш╖┴╫RЯ╨Ёz═eг4Ў╨єj╞6n╟╦ш╓╜иЛЛя-bє:ягojо·1▄НДz^Cн╢O·Fш8жb=Я8?GE°)iS├█~┐%ШЕ_╘Щ|√D(┤vЙ:▌к╬сТАVсБjэm7н│ц=qо█P!bю╣Зв^▒O~Yяз \sъ╡рЇШhгxшБ▒AtW▄╟Ф_нP╧З╙ГкН■╙@╦Q#H╟;.ъ5И╖1&╖яU7iГТG[8мъ╗ВУтє(Щ+f╖К ТО╞В=х└xk║║ЖпK┌√¤isэFaЩL▄ю;xЇ(р╕gF0╗П╝>UўЙ╕┘╜Ы%ЇРхт NЛAkXп^z╝М├пщPj∙ W╛Зыь╜K 6NVи!хЪ╫l"ь─╨О╗uP╣gBЯM1Ю/Ёо{х║#╞Ё╢еB]jЇ ')ЁНLa╧wR>_ЧN№┌█┐N├с╫─zёп║Ў4СaЄК]░ъ┘ъ∙▐─Бу╡dй┘'Bе╖zё.┐-╓]a╟┌д ■Yx+lxMwKв╟eЛ╩║└иоkЬ>Е¤{р│ўЛ8ь╝хщы'█QW8Гб еv╬AГa=КF░зlг? ■╨MЭТЧ╥▌лI[╙-╘к^\9╔!шЇ мL╛█▄жFЛь.Ї}Ж╤@9щх▒Й╟М╩╟Y`йmC█!9fэ[ё█╠8ntНї%T'mд▌2UL ┴го{ ▒И╟и`▓╗_u└Q/╟мзрП╪pГnП╛Ц.С:i▌Вwpс`)▄т▄Д╩Гe(▄m)Еsj4┼ чS█3╕-З┘,xiCFkф┌g╨тХ╜Dвhy%╕Пй█▓]^╕╬▌2Ц kё╦т#р7Q╟HLz╣с>о_Ї╦Wн█ ╔╢Oї┬▐G╦╤є╦m[o√Ї╛┤шmF.жр╝згN3ъ∙АКn7№к'7ZonъОЛъQ √e╩°YЙ╧°fГkв>ymд╝ЬW▌шЯyF█фЇ1╘e|J2э█°█p▄glSыЛr▐╫,vЭъ╗2н]bПЇ╣Oъ√2WТ╧чF╔uё|║Эс7I┼>.fLОЛхоvC┐vПсв°╝Y5$эgхa^я,№тД∙╝Є·Kq№}Є╩ьМ9OЮ}кЧ$Gf0З¤]JЭ,╗ЖСИh+Т=J╞>лщъГVмЫ╓c═uо│ ЩL▐ё╗4║я▓щrВ&жЇ╠JїhЯї═╧c▓│n/Ер# by]¤TЎЙL╝Г┴Й2t:VЬ╦t√ы┤╣nвabw▓юD┐МЧ}┐Бs°╞'ц+Їb"в ▒9Бq╘О▄к*TUхКэ░╦├ЖУGэ▓lLUv7"FU─╢j;╜>╖j┼·%+Жиn¤м& ц~╩ЎйхvМ╩¤ ╒-╟╣═╓й╒F}+ы ╕лV┤5╖hOh╗lрqъ▐!1EI{рT╜ГбдхЮBcЫГ└:JC█;НPиzЦЪaS▌ZA<ЪЩBYз*╛╪рЄ╕Ї:Q┤Л╨9Г╡ЇkRПf0QVПWї1^ТОcЯpЭ╗QX6Б5u`g ,%|3Є<%6у5мЕzхtмщЪИФaF╬уXхЄ·╜Х+╓/Y1D╕Жєбэ*╧G ]╧╣DЭк╛c8[/L@╠¤ їа╩бm╣┐б║eк┘:!VЭsоc+┌:Зkв=ьХ%JЯ═ыa/┴й╗ChЎ▀Э╞еЄq<╨oZ╒╜ XXР╙4бEKйHД└║~р╨4жзї4рo╡QМ~Ж~Фр%╡RlW╘ ЬЪVїNЯZt°A▓б╒'кФ╧wm╕▒K Й╣Я╒╜XШ╛Дr°Ё■├¤Р╧y ш Lм7[з6Н^з┼qJл^z├╞НёFьп╓х,`s╜C7ЇВШй├УУ╕┘ЙеЮBcЫЫh@:uя╥№╝╞FВХ!▓PПЪWжC╨?4╘E|╙·иcЪАt√xuN∙│оЛ√Xр9'^ї┼/ъ%ЖК╦ўpoQNГв╜]и╫C╫*╓▐b╨пзлGРп╫F∙ nЙw╩╒√б╪╬┌ЬTїЮ║ОъЁ%ВфЩЦl~∙|WЁ╢°)╝/Ч' ц~╓╝З{■6╝,▐q?|╕gхsv╖>ы═╓ ▒ъ╠w╛Л7╜|╡Ї~(В╢ўMьл╤e"J╣═ Мх{в=)0 ╖█╗╜ |zЩb┴ЮrС5[╨╒5МАx│░ммD, М║Q%Вe∙е6и<x(тЩ W╞Г-╒√Ч├Wр╞╦бV╫cй=$┬\Вbюч▓C╡╒кЭ╟],ЇFж╗иыb╓YН¤"]╖v20,Вmу~г}[В1┤YPP█лївФ(=─=?Йv в╘eш3╪ГX=tJывz∙тI╟▒▀кч╙║Kf9╪^[∙^\sЭ╟∙жП1ёЮРП]└¤Ц3°°тWШєЛ%+▀¤sШ╣Жc"X╛▄¤6╘Hwр╧d╕2Ю'┐f▀r°Є╧т~Е ?╤їф┐fa.A1ўsЩэ╡ hєЭWpяrф╕{╘u1ым└╛уў╤u╤Рs╖E░=╛╧hеEц]├(ЗlЛ▐╟ь╟■¤√▒з$<╛Yр╕╗АЕщS╪┐ч>{┐╚┌╒kEФDk]КъZ┼\#NЕRа╥П:=╘ЫЫ['JЖ└├яlёШюgТт╘Y▌v х¤ЯЙЎ0|C┤0Ы║╙NMЗЪ+о_ zЬ░Хв°ж1:i=LЖЪ╙qьу╫∙3@▒5 O╢хо,^┴СЁ╖-9dk¤│╪З}ЎaWIx|╦╟С▒{╕ч√КgёЫsї╞╨о^+в$║jыQ_█%ц▐─╗AЇzиwoОCФ sC]vkd║ЯIКSg┼╔6╝▄g\ў∙╒u╤┬МЛDщФqБQ╕ЄChsTгZЎ·МbуmLЎКIGKm┌5Кe>,┐╟ХуТZЦC▄" ъ▐8X^BЙ\{i╙j╕WNwс╒л┼░б^╒sg╠Ъ2▀╧ф─н╙RЛCв]я╗Зё`╝{┬?X╢╝ ║=Г╞дЧЖ ▐П*ЖС<ш▒┘Pzи┐∙╗Нэ(Еluh└ЖМ J├ду╪'Xg╨П)▒Wu[дєQ╕ ▐vVаBЎ·Мbу┌=┘{(&є_{o~S, рЫе7МЧ╤жЖЦ╤V!вюНГхE5▓ёr╖Гj╕WNFPU=КaC╜кч╬Ш5e╛Я╔Й[зх'░Йv}ш· │▐Ч▒╦кЧQZd\`T╫ъ╔kўTO`pJЎк╒бH▌Ё@ЧьA,╥SлНЧTЁЛdБc└╪╬╕░╜╙C(╣!ъ ї2Жn ▒8 К╓"╣╝ ]HlH┌l? Q▐аR${9╟Нz├юLQ7п─Xg▐vї8N5b╝╡¤a├ш┘A▐╪b▄┤rp@─┐ОR5Я╪ў1┌рТ>DiiйШN╜И┴х:хT:XМ╬I}}гГbfх6╞ф\n(y68:╦─iq┬~9Ў┴╪╓ЄЭЙ ╒)^еjм┌x lъZ=yэЮъ <ЬЦ╜j╘л>цЁ▒ьA┤ъй%А7╗╧FЎP*∙8r╒╪╬╕)дя∙▌░\їДzC7РXОрl7╨eХ╦Псcq$щ+4█╧ ,oP▒╩^╬√F╜K╫`КЁg▓╬╝эъ8r·M▄oщ┬н░at"JПЬ'OЮ,ъ∙Фqў~Д_t8u x№°1vь╪бKYD╦ЫlV(┐4Н╗лSъСwGс╞б═▄З°Бvю▄йKФэф╧pмєьqтD╟&╩:1щiFBГ┴'Є7#n`JЦ╝ёй"OЦ5▄Дgн_┘┤┴ф▒№оЁ;]вїСwG╫├sxWЬсCюD┘ч╣ЩчPXXИзOЯъ%╔С╠aGЧR'єоa╠$ЄОце!ьхisВЪОUa▒Ч0Р┴aС╢ЧВf<ЄюїD├bHY!vы┘ї ▌°4ЯёaСRE╟{UXьB╬2,еcЦ╡╦Ыy▐╟НТKШ╛улДИ▓Д·ZЬbы┌&СцmТ7є|OI╟b|ХеcЦ0╛Яё.юЎ2,RЎ+hЎм¤п¤i╞ў3^┴Х╦ ЛDЕБСИИИИL10С)F""""2┼└HDDDDжЙИИИ╚#Щ┌░┐ЇЄь┘3]""""вХ2∙/╜ЁORZЁOn-fР╓FK∙б@DnffЖРИИИИ▓#Щb`$""""S МDDDDdКБСИИИИL10С)F""""2┼└HDDDDжЙИИИ╚Tv╞АU╣vМъb,wrssН╔я╤Ф╤В=░х9с╤┼XВ=6фххУ3▐гЙ╢АА Х9M╤┼XоJфффSS╝Gз┬Ъr*с шb,r +┼■╟{▄& ╕Ъ─q6О_e▄F%╪ЎШiкМxЮїЯ┐╒uf┤М}]o?█вЗ╤т╕ЛЕЕ 5ъ┤х4{0??ПЫ zе\╨уД═fДrfЄьaqОaqq├╟їВLсЯВ╫ы├C]\╘D°Ly&iВ╡┼ЗТa?№~?о:-zE·<ЇyсЭЄыR"ч/~█W╓╣dьыz сР4%!И;C@}{'Ш╔)%j.Л№1д,ГЙЁЩj#ЯЎ╟OуrНЛШЇЄЇ▒└9╢И┼╦5║Ь ╙╢'Y'm{ЫGэ╚нкBUUо°╫╗№7╖ ю╨oD╖^fм_Z.▄vTйсх*╪ЗїBI OЗ╒бЮ├НД~┴4y>JПy6Ы╤#es┬йzжlш ъї┴╜╠X┐┤\Ў8aS├╦68яшЕТЮлC=GПИ2 0y>JDЪ].4█м║LIiBNe%*+s─┐MhТ Жc\zЩ▒>╝чhyx┤M╖їBI уЕ╒бЮ├Хр√aьч3g╗йЛbB√╢2b(qхЁнYЭ┘У┌.ХMF√FЪd╣}Ёв┼ZHSI╢эqDm{Ьsd┤├Ш>О╫Ў╪uCчMспзЁїb▀╘ыSoЫ╨kiл╝оi╔цї0ОЧр╘▌!4О√яNуR∙8и8 {Q+·KЖ0==НбZЛЇuК"╪5┤·phz w╤ЖЫпУё|85нЖнзOн MZЫЙb┤{nвaиєLв│l3Пф Ье(╛Й╔╔I▄,ЮBGй╛NQ╗SиЯЬ╟№╝ Bl╛N╞єб}R [O╢' ЪDщр-┴щ▒aў╞№шоЁ┬1ф╓ЇХ л!╨aё~╪b╒@|mёс░Qї╠Э─u╓╦x>ЬЎлс= iахh"╚&√йxс+>йъ\ЇЖп┼К╨efбб─┼E╤~cСf╢/╕ОК`T╥нЮ╧яF жр+k.Зък@╖:6r║М°¤i▒█ Z╡}в╨WkДОЁрb*v█═эX█k╝╢Ы╫щE▀╘\Хmя═l╣и╧Я8.▓сЗНax5]u&╓╗Ъїпk ╖yБ▒|OЇ\р!|(╟е╢j╒х_▌vHФ|x(╧lр╞╦бVohйХы╓I=_#ЎWХZкўЛ{├|%е^Y!vы┘A┐xл/Cз├ЖВВ╪їв$▐№eВ{4ГЙ▓z╝Z`<┤рU╣nЭ╘є5а╬fTZ`л!vwШi│T#j_нz■'k╘√a═╔├вд▀х5ЗёZш¤Ё5╣nЭ╘є╟Б¤~Xs@|╪_╟эxяЗf√йTрЁЄОт░╪Q▀Є╩ш╠Ў%p╫╜Є∙DpСC├Ц\╛,ВС~КдШ┤!"hVєЙЯ'╤ЎM╓ЎИ╫Т┼врm9КЛoЛА'ЪСшq╬Ў╫5E╚кkSMКбuб╗иsыDЙ2EP╝yд╟кan9%вьТ╛ў├>╘к49╔с═Ї(y)С╘▒1√▓╤k{ж╨╫<·Oу@ё>=gч"╝ў8╡▓¤u╜Хe^`┤╝ДМг╡kБ@г]r╪╣ЄчKї(Жї■Ж├Ждїv─╩@`юўW╟>╦Юrёл▌gї╩║U5j;а№╥жфP╖1▄э╚жЯчlW ~Г┼:▄ГAx▄r╪Y№fZа{├z Вw┬Ждїv3be0шA╧Е╒▒owa05ПиW╓нкQ█eЭ719/З║Нсюf▌ЛI R╟T╧Л#+П/еШzЄвхтИz╧╣(ЗчЇ√бьy ы% ▄║╙█MЙХБ└\чV▀JnИч ї2&z│ еИ о╔N4LDii)Nгs╥%Ц ═°аш(5nzq#|H┌Gg:J┼vА·`веa▀ёR╨▄О┘Ы(ъ--=бГз|╛Ы(╧ъeLЇf╥фuаЄШ╜│Ну╦ЫЗRнЧ¤▌8юлЕ╒jEнx?ьЎыk╥,N\эЖ╛╣б>tWГУ▌шkЄ╓sА№Рў╢└vёЬ┼y╟eпЛи╫j=к?ахє гф║x╛PoLB7Шьз"┬Б┌O+╬M╞p─║X╠Ў┼ч╒atЛрbь┐Ь.b∙Лb─╢r№╕╧╪ЯхЎЩЙ╫Жd┼j╗┘9AУn│╝|╨+#ч├oTЙ~■дXmП_gt~\мХїшй┼Зу▌W╫╢LОuF╜о)\╬У'Oї|╩╕{?┬/:Ь║<~№;vь╨%┌фov;wю╘%╩vЄgШч35ф▒,,,╘%Z"яА╡^╟a?{~h{ЪЩЩQя OЯ>╒KТ#3Ш├■О.еNV]├HDD[МТqбщhK─═DФYЙИhУ╚п;▒в╢Ў:|Є+r╞№║"┌p МDD┤IjpY▀А0vЩaС(У10С)F""""2┼└HDDDDжЙИИИ╚#Щb`$""""SЎЧ^Ю={жKDDDD┤R& е■i@J ■i└нЕ0u°зЙ(■i@""""╩j МDDDDdКБСИИИИL10С)F""""2┼└HDDDDжЙИИИ╚#Щb`$""""SGa╧нВ;аЛ▒▄ик▓c4▐уИ2M░╢<'<║HФz#h╩йД+#▐▄ЧА ХХM╔ЁўЇАл Х99╚Sefр(iк╠р¤[∙║╚п ]М%ркTчDMMёM╔╚▐╞└МП√ЁP╫GДT>уЖT┌$8mNЇu1a╔ng&uЪ┘шчK\╨уД═ЦЗ╝╝<8Щ~╫A*¤A6mЙ√╡ЁO┴ыM╒{║8ж"|ж№О4┴┌тC╔░~┐WЭ╜"є<ЇyсЭЄы╥╓gqОaqq├╟їJ╣ь М╒╜XX╕ Gк~^E°д 61еg╓(┘э╠дгN3¤| т╬P▀▐Й╜Д╓ч╕!б)У├HZ╘\°cHY│E°L╡СO√─Й:Н╦5X,b╥╦3П╬▒E,^о╤eвї█╝└p├^ХЛ▄\1Eщ▌{╨e7╓хV┴╢2ро╥╦Нu█Щ╒Р╜ИбэrQewCо╡╦r·1О╓в╨z;FНн(ЕВ=N╪ЄdПФ NзЬЦ Ў└й{лЄ┬z╘Бс▄╕█┼x>%мч╠X▀#тQЄ√"E┤¤О^иїУ═i<╫ЄЄ╪╧k╗НQАfЧ ═6л.╙·щЪЇR9$╫Tй{WЎЪЩнж.6щ╦J4Енi╥█╚Ю╠&Чz/TFBПo┬ИШ7Ж^┼╢ЙМЁ%╣/CЙ+ЗоM█.{C█-╖├h[-·рEЛ5┤>■Ржз FЮЗ╩JT╩}√aь╙Є▒Йy■ДИu 0. hZRП<)з f╟zy╪_ьуm╜P█TЖ╫бЮ#▓¤1e╩╣═bЫEx+jE╔жзз1Tтa-<дН├╖з XШ>_kьzе┼q╫X╛0ДFcСf╘ЙS╙j¤Ї)а╡┴ЕтХwГЕ%Ч╘єMOб+л{CuХу╥┤ЬЧS/к╒vФ2"аЭшШB¤ф<цч=pNЙтБ│┤hЯыц1┘tЬ0Р═% (Cз┌VN.╪М c2▀.Ўєйu vУУz·аY─гфўeU█1╓ЎP╜rЪD=:Pк╟wу=_мэ([─{ТЮЇї!omA_╔░ъuяХ-╓P°1['yс+>йЖщ¤Зсk▒.┐Ъ╦bЩ\╛ш╟a┤└║╝B<╢Ёс▄├╕*#Кш√4^▄J~_BCЙЛЛ├ИM4ъ─i┐Zя? ┤ ЕГ\GE(,щ╓=▓├т=} ~▒╥hЫмл▌■P;/#~_[ь6ДBXmЯ(Ї╒б#<╕дГ╖з╟D;╝└Б1?║+╝Н0╟<Bh]ЇбY/·ж─yХэУч╡хb╪9JГШm0y╜И`w┤┼З├ъ▄Нс$оЛ╜^/│╫%jscрбx;ннZ¤&]▌vHФ|x╕tЎ╩qиV ~mй┼бr└╖╝2:Ug#ЎW█Yкўгq№ЖхfБa▄Ч╧ч╨┐╜Wг╖WД┬╠O╪z═`вмп╩ф%╝Z/bРЇЛ╖·╘┘МХ╢:4L тN║║╠LЯo7 ┼ОMtЬА█}ПЙїzЯУf╓v)шAOПNз3k}Nv;╩H}╡VXн╞t4ФD╘√Ъ>'k╘{W═╔├*╠й╖C│uJ┐z} З+┬▐G#p╣Ъ╨╘tS1Fn┐&ъ к@ohs=√Лкє8╘шўЇЪ"x\╟m╒Ў█╕юХ╧ч╘ящ5╕|YД┬ї╝зЫ┤!"ДVє)>Пжв1√я8╤Е╡я5┘╛4Л╒│╫Л╝Ц╡т0Ц_.)╪O│╫%,kоa,y)СЯ╠~╘щ!gcШЩ2EP╝ Ш└A=╝j ├ж[мч+@│gєУэи+Ь┴╨ЕR▒>▒aчXL█юq"пЇfPЗ║║:GD╔╪Т▌О2╓ёa#Ф╚i,MID╜П╩a<ы9ёK╙8p┼%iП Q%ЎЮ▐ЗZ╒Ы''9╠LЩr■╥%Ё0]ўЁ╡┤^Ы-/бD^3╪5кЖ_F╗nИR V┐Иuю┤О╦ЮC╜(U'P~i╙jXYN·ж╒K)ЮпA~ O┤ai7Ї■м^Gыеz╒┬z Гw┬Жe м( Фu▐─дbХУ═={t{ НI/Н/╩vж╧'уСП█Н▌п:риЧ╖tL┴ёДk█│╢л0YVG│ 6█nёT╞Ъ╚:W?_B█йы4m╫gw6ўИzBыВшq┌рLц6l╡?z^<│▄7J1ї╛цE╦┼ї╛4rQ╧щўJ│u─:╫Q┤xeЛ■@о8МУ╬╘╘XЯ1р╖оў╝$ў┼Ф~OпшЖ_ще^=╒K)ЮяhFBя┘rRЖxq]я╧ъuQ$▄Ж═ХЦє╖╤LО╡ъQ ы¤ ▄Цы4╜▌ФXР╜мчV╟>k▒ь╛■t∙u!Ъ╜Ц(aЫ╘├XН▐щKhЇ╒бииu╛\Ъ┐n0tJ▐pCыbСuбфЖи3╘╦X║Ж╤╟└.┴З:ё|Є9ЛК║┬~└─╢CН@┐▒?EE ╞P6еNA3>шД╛y├7┬ЗempM▐DёрAФЖz¤ЇН&▒■жnb}iйШN$8\k;│ч{ўA∙X=uLббєГ░ЁЪ─╛Ш┤╜а╣ Є·C╡'АЎNФ╔║ЧЎ'·є┼▀Nx$sфf─┐KD╨ЬШШQCэ╞:5#"q°Г!пХ√cЇ╬иcv"#┐■'╗╒р▓┐╟}╡jи║V╝Wv√C╫уЩнУ─▓║щ├КsSЗ1м╫YЬзq\^ўжzZОз╗Q╤'ъиtсх (╓▒е▐6б╗]дфЎ┼Ьмs%╫EЭбЮбе,p^F╖xOп╒├°VыE,ЙМ╪VО╦vйuG~М╫Ж╠`v■DЇZ·К&y╜е╖┼кц3я+ЪLО╡┼Йл▌╨пЧJ\D°Рt NvWаO┤╦j=ч╪y зq|·Їы"t▐═^KФиЬ'OЮ,ъ∙Фqў~Д_t8u x№°1vь╪бK)pгкшMзЁлu(eфov;wю╘е(фРъAрf"7Н─въX=x]╓9 Odўd·e╥╛дБ№6=ЯФ0y, uЙЦ╚;`н╫q╪┐Еz~фЁ▒║Sfе]т┐Yё▀j▌■┤]ТS╢ьч733г▐Ю>}кЧ$Gf0З¤]JЭм╣ЖqЙ"ггn╪Z1^~б{c( и!L1yzр╝ ┬UC]ЄaQ▓╣Їprф┤)-УЎЕ(Ы╚!╔ЪО╢D▄ь░%ияЦ БЖOПЁ(ъЄЇ]┐j*[ЎУ6UЦF∙╒9Eил╗Я№КЬ╗Охя*г ┬╙┴ALwb╥╡о╕HDYO~▌Й╡╡╫┼{z7№cN╛зeи, М╒ш╒7│▄эeX╠.6╕Цz▐<Ё╕Мя6$вэмЧUП╒╞.3,e▓ьТ&"""в ┼└HDDDDжЙИИИ╚#Щb`$""""S МDDDDdj├■╥╦│g╧tЙИИИИV╩ф┐ЇТ╜Р2Z▄? HYЕ0uф▒№оЁ;]""2<7є 4 e/F""""2┼└HDDDDжЙИИИ╚#Щb`$""""S МDDDDdКБСИИИИL10╤:ЇxрSP/INP╒сёИ·Ї""╩ [000jпBХ;а╦┤їЙас┤┴╓├Ш▒ё║мбї ┘╞i├┌щс°├╪чHя┐┴╪ю№╥v+ПY╪Є█zб╗N!щs¤X+I╛>У>╓fmHr_6№g%KmR`Е╜о84Нщi= 8`№b)╓╡зж▒░░АщS@kГp╒╜XШ╛Дr°Ё■├¤Ры/¤ЯЙ▒║wAm3╘иКaLъ4▌JЭe8(ВЭoЮИtцБ│┤h7zeчу╥pд═Е∙╔NФa №u°@ояЖМ─hs═лmnоъ╩2йSўvв~УУz·аy9╝RBВ■)=Ч>S■эу+Ёvў╦╕U+>м─Rd╟Ш°а╢vзН▐╟┴╙@╫Q¤б${ф№mxY╝У}°p╬╩ї▌└нOНhTq∙Ю┌╞}\├Ш╘)╫╒▐Cоj║z$БЮ4г╬[%n╡Н╗$ ┬ZxH╗П@ё█║╒Ж@K¤╥Зu╛єК▒|╤Н7НEЪ┘~╬с┌Q K┌Ї~║┼{Ў,ц─▒3┌-ыzm~у▄[|Oхxb╖!(}в╨ч╨!Бы2Х√ЁLчGЮ/┤ЬY┌.tОю-Кw╚.╘7Е╟┌√╕%╖У√"╧k╦╟╞ёцLK6╒╢+x[╝Я▌WП7─о3┘s√XЫЮw╙╫з┘╣5c╓У:3ъg%{mR`┤`O90▐┌Аооa─ ╧JhБЗ"6b╡▒└R╜Ну70╝тєPm╡ u╟▌°├╧жuЪь еTA│GД┐I┤Oс`йОбn┬а_─┴╘┘М╚V`лЛ:Y к╤#йъЙ7№lZзь╜&:N└э╛ГGП─zж┼5+░ы9Z/Ъ─╬╗т╫aХ=8·C>ЁН°И{√jМПб№Ъ}x╙ы┴W+▐mп=ТкЮxCjжuцcЧ╪№╛5_№ s~▒$СўCUзh'┼~XЄQq╥ж>Ь┐Y┌╧Ч┼>ъПR╦O`╧x'Шэgр+x╝Є∙─┤Ц╜▓ЧE(\╧{╖I"┼q╖ЪO|°\┤]╓)gU█EА╝н█Ёк▐╟єMc╓g,Z╢/п╔}╤ф╡Ю6№D?w~°:)fЭ╔Ю[Уcў╝V╜>M_ГfL┌Р`ЭЫ■│ТАА"╔Nщ┤iБQ]g8} √ў<└gяA 3ы╡@?ъB├├╣uвФ ▒ъМ╖/ФR"Щ┘\28╩n┬ a├─8╬;(JйлN}═уd;ъ g╘]╣j╚[пе5ШШAzFНaf(╢nг$/?p/╦р╪Ї}╓{u О╨РЧjMЕXuъы╕№яb_ё,~sо^мя)L╦K╞З░╣t┤=C╚aRыЗШ┼>ь;░╗J"b_LsMBБiЭwnУ╠╣Н╫Жн∙│Т)6)0К$<*&╓,╡mh;$╟Р}P?ЦЧP"■)┐4ДщcИyaс.ыIюжuЪь еNP~╟▐rЧaЁ╤МЮ мР}UeЭ71)oИQУ═ы╔ жu╩я Ф▀ї╖╗_u└Q/╟│з░-F?SIЎ┌КопИ$╬╖°Я&ч╫xpЧzИuy+ ╠┴;▓▄█6чЯ╒sВхEё╬╝▄э╞аъ╒Т╙:o 1нssb_ц─Зa■koуэ├rРxuП╤*к╬√ш║ш┼Ьl╧E9Lj┴ЛлЎSмsЭAЧWЎ┌шE▒Шэзюйы:*>а┼є╔чTУ┌0ф>Qз╪╬с│а═~▌а▐╟Ьz|8eГ;b],f√ЩП#W▌hя▀О╨╛Z?╓ы$▒э░°я3Ўз▐z&Бс╬xmH╓╦░ gф■╦ысО╗ёЮ╦∙╬wёж╝╞P╡э p║ /╦¤НwуДх╬vC╧c°X№ФД·═ыLр▄·┼'вў>f┼┐╦╠Оuьcf■·4;╖Z╘}1kCь:7№gEў√▐Y5Lm┤Al!┬{ ▓ЕY'ч╔У'Лz>e▄╜сN]?~М;vшmЄZКЭ;wъe;∙3ы|{Ь8╤1АЙ▓NLz╝yH▐¤~╕9я ЕЄ{K╤1Ф5▄ДgЛ~MТ<Ц▀~зK┤$p ╟м╪№ыь╩(2М|И][кMФ.╧═<З┬┬B|¤ї╫z╔┌Y,Х┴ЎwЇТ╘┘дF"┌* Ъ]Ё╚! D├bHY!vыYГ╛╢T╘╡U├"E!З2Gос№╤оИЫ9И(│00╤ЖS_╔Sl][└д-H~mI=╡ф╫╢Мmэп%!╩f МD┤с·j$┌*ЁЮ╛Ср╩хнe√8M[#Щb`$""""S МDDDDdКБСИИИИL10С)F""""2╡aщх┘│g║DDDDD+eЄ_zсЯд┤рЯ▄Z╠■4 нН<Цy▀ёOQд∙ч°зЙИИИ(Л10С)F""""2┼└HDDDDжЙИИИ╚#Щb`$""""S МDDDDdКБСИИИИL10m*/┌ўC Ь.╞2w o;П/ЧЧьvf6▒N3kz╛0[▓эщ╪Oв°240О┬^eЗ;аЛ┤ xр┤9╤╘E╩xAП6[ЄЄЄрЇшЕФ>s│╕w?А5 И$╗ЭЩt╘iЖm╧М╢╙╢Ц╣=Му>=C█╞─ФЮб╠─Э!а╛╜ z еY┼{x|я єu9Q╔ng&uЪa█3гэ┤нm^`╡гк*╣╣zкrCv(О┌e╣¤GkQh╜гzЫе▓▄^═W┴оVR╞ ыСRУнG¤Ўыq╩ЄA `ебїNиN+▒═RYnпцmь╤┌thv╣╨l│ъ2еNфРуь╡c╪╣wпЮ╠Ж"У▌╬L·ы4Уьєmп╢зc?ЙV█д└8 {]?ph╙╙zp└"╓Tў.`aaН(╟еi9/з^T╦═к{▒0}Iмёс¤З√1 ╓Й"·?cb╠|8їУШЬ╘╙═"v6╫<цчoвeшЬФєrr┴&7│╣0?┘)╓LсВ┐ИuвИБ!&F┌в╝┐┴'т▌░@ў э:rПя▌УoЛвKv;3PзЩdЯo[╡=√I┼&F ЎФун шъF Ц╚┤╕ЗjлE-b;╟],Їк8Im7 ╦АЙОp╗яр╤#а@ж┼5и╒жfA│є.'Й╢О╣9╠zпс ╟-ьm{пш┼q%╗ЭЩlй3x<Йв┌┤└ш╕╗АЕщS╪┐ч>{┐hy╪Щ╢и4{ц1?┘О║┬ ](]v&┌ЎюуМгХО.▄├Ыh∙I]PJ▓█Щ╔Ц:╙Б╟У(ЦM МF┼$Вге╢ mЗ┼2F▄=О]г─уфдЧR╢ "шvcўл8ъхнSЁG▄┬7БA╖┴аxЬЬЇR╩Pъ<щyy~Ч ┤f/уьаF─┘ЧoснП╜zy<╔ng&[ъLOвX6-0v╒биHOн>4^АciX║╜C"DЎ╫щ╟4`X$╞А╗ ╣Eн"Jъbx╖Kyў┴RФЦъйc ЭаyiX┌╫M"ъ╟Ь└С?В=6фХvИ(йoИс▌.┬з:OЄf%y┌Мs╞пEZп|4Юm├▐[┤п)$╗ЭЩlй3x<ЙV╩yЄф╔вЮOwяG°EЗSЧА╟Пc╟О║D█Бь▐╣sз.Q╢У?├<Яй!Пe▐w▀щ╥╚/bоўрї┴5~UJ▓█ЩIGЭf╪Ў╠h;е╒№s╧б░░_¤╡^▓vЛEe0З¤╜$u6йЗСИИ27З/╜╫╨~ж ў^╢сзЙ~°'╗ЭЩt╘iЖm╧М╢ МDD╦ЛЎ·z╝хЁ`┌╥Ж▒+G░Kп1Чьvf╥Qз╢=3┌NdрР4еЗд╖IзN╥C╥D┤еqHЪИИИИ▓#Щb`$""""S МDDDDdКБСИИИИL10Сй √ZЭg╧ЮщнФ╔_л├яaд┤рў0n-№╞╘Q▀├Ш╟яa$вHєє№F""""╩b МDDDDdКБСИИИИL10С)F""""2┼└HDDDDжЙИИИ╚#Щb`$""""S╡Wб╩╨х ╕QХk╟и.╞pW!77╫ШьёMыД╟iГн'и╦)ьБ-╧ П.╞ь▒!//╧ШЬёMФ%fпсНЭчёе.н╒&╞Q╪ль0╦Г}уРЖ└Ш Лу.0╘им┐ ╬з═ │<шЯЪ└─╠#]┌x═╠╧╧уfГ^░J№6lAП6ЫоЩн╙gЎ╦єxуН╜╪╣s/┌│5ёЙ└┌о█░єНєшЯ╒╦у∙Rl╫яЕ|°l 1▒э55╠с╦~q\d}kм3ЎёФu[оsч1ё▄szmnу╕O╧DcБуюzлu9CЩ╢БVЩШ╥3╤а┘3ПyЧMЧ3Фi╢Л ю їэЭИЩн)цЁыQрїЦ6╝еЧdЯ9Ї;╗0¤· ?╛З▒╫8у ?s_Оvс?░KЧЧ╠╬aЇ аeь▐ыМwй╢Эl:Nша&сЙО)╘лчЄ└БAєBL╢3Сhцч'QПФrЬЦёх∙еЁ>╜▒Юс╨┘kpЮ рu╒[vvёЪ┐зWIп\0z╤?─ышBe╗W-_цt╪л^OjЪЭ5Ю[╕cшWю>№сщNД┤в╜o┬╤Х8 ╜Чё·Oг╞fГт√н}Сс.jЭёyч╬ztЛЯlЧ╣DФ╤7╜─P╛Q3tр!|"╛эп6╓ZкўЛPy├2∙`╝№jїЖЦ┌C"цif█нG`n╖v{p╘z}╩ ▒[╧F·1%т[Эн@ lu"TтОL~Пf0QVПWНU(x╡^─<═l╗їz╨╙уД╙щ╞ Gн)Qп╝з├[фЇ∙z┬К i{m°йK╗~j├^c╓0ыE y┤╖  ┤^жTа·н√8╙k╚┘_Ла╣2И%KюЛ▄Й]∙░FьМ░ыИъ1l w│▀`Z╝█─ |*gэzБн╬╕Є╤°╣д[D╚оМшm%┌▐▓/0ЪъGЭ6ЖЛ БЗё[ЇэТ&З╬Л▐╟ь╟■¤√▒зd)ЮR╩ ра6ЖЛ A╝─}╗д╔бє╥ ШAъъъPX╝OЙ6ЬъqЛEЎhV~?Ўб║zмE/ыЖWьm╪√╔oЁ%цЁы/юунъAlнvэ2┬кpЯ~E8гч0^╧Я л╕Е╖tПkхЩ√└╜.8├z^╒ ,Х}"ы╒ S"_▓WЭmcЫ╟qгkБ@└ШЇ╥дX^BЙ°з№╥ж╒P░Ью┬a╤=Кa╜ЖБс░!iУэB,{Dрє}Ж╤и√╣║ *аЦBЫгZ╝!ЛК|╞│нл}[╞▌ГAc╥KУR`E▒°змє&&╒P░ЬY┘sиюв>qзєо╞+=оcgE░▌█жЗЙ╜шПп| x¤У│░Л║ъ╕DйSIЁx╟,БNвmbc5zхEК¤u(**SГtг░ы▐╛║~╟ZЛ╘|№яcїMбфЖи/╘[XепE┤80p ·цФ*t!lH┌l;═т8ЕF┘ ▒ЯRЇ6Пo╒ї5з.б\>fE╜█П .yСт└AФЦЦКщДt8uo▀┴╟:J╒|№яcїM▐Dёаи/╘[h╙╫"4уГNшЫSlp#lH┌l;на╣ ▓2b?ешm0▀бы;┤wвL>fE╜[З╝T╢▀шЭ8hЛDnв╡Ёв╜▓ХХ▓АO▐ТєgтЕ╠о#pЭ╬T╩▐╣cшп█╨(░║NQ^╖иzю╬-▓G╤6№ЪП╞Ц7qяL> ╓^"ВШ║1▐>,ї╣┌PЇE╜╤S°Еg╟▐ЛцuM▀[q]гЩ┘oЁЕx=НО╗ Xшн╓х e┌JЭ4{ц1я▓щrЪMLщЩT┌ m т╬P▀▐Й8ЩтЩЭ├ш@╦╪=<~|cпp╞y FоШ├пGБ╫[┌ЁЦ*ЗС╜o▌B╤'Г╞vЯX0}цLX ▒-╫y╓ВO╬|Ьe=Еfm7;ffц╨яь┬Їы·Шн8╓▒╫ЩIGЭDЩgSуи]є╓буh-╥C╛a├л╞zcКТ╡#╖к UUb]Хv∙on╒r_└нЧы├7 ╕эиRuV┴>мЖШl╦Ъ┌`w#▒N╚Q╪e{ЦЎ5▓¤жm╪┬^▒ЛрЧ└PиъйЛEь▀╬╩с╟>TWяГ╡шe╜┬ЁК╜ {?∙ ╛─~¤┼}╝U p:╕О╜Лjы,F╗ыE└M═Э╫k>f▓WV¤{Я.╖ЩГ Ю┘fы╠дгNв ┤╔Бq7║FМI/MКх%ФИ╩/ aZ C╖ЛюН ы5 З чЪlb┘#Яя3МF▌╧╒mPсо№┌╒тНUTф3Ю-▒Ўщ·фl`7─ж%/Y╠█░EьЦ▌iSCЁГ╩I/OLЮЮшШh@Э{█{d╗▒√Uїr,x ку.$╪з-╓W┌L`╨э1Ўci_т╫Щщm0юlюх╨vAЇ8W\7Щ(UпЮЧ√║\а5ёв Нcи| x¤У│░ЛP1;vG│ШЧ╜rЖыd∙K▒}╗╪■МiЯ╝u([ї°E УСTПbX░4╢╙є2Lю╡┴▐XБWфuУ╙ўНхъ Е]?┴ы{oб╗ лбHюгШDp▄ї╙╖a¤M▒,А`ЬЁк─l{Ьc&й╗УПE▐Х╝┤П╞cg√?\┘fыB╥Q'QЦ╪─└XН^yСbККК─╘а├Р╝ё├шэлыёй╡H═╟ >FQ▀ЇJnИ·B╜ЕU·ZDЛЄr@usJ║>Ьk▓ЭfqЬBгьЕМ╪O)zМ╟╖ъ·─З№йK(ЧПYQotх8┤ч34╚mх╡ХНCP7КЫ╢ak(hnGp░┤ее'pGхЬ·Жy9▀DЗьe ┐q$tcG).╠╘уцdшZ╜GpФїшйc Эа9|HўСИ_X¤-76╕ф┼ЖїЎб}Й_g╞╖┴?ЕЙЙ5╝mlзfD ]UAвMк^ysН|у9Т╔Э█▐ь7°тЮ _╖pцнzTVУqcЕэкьA°Dн?│X╛lх╖>─т|2vBiqVl║┴DLХ_Xpv,zШМ░ы\gC├▄╟╨+ъ НакыхuЛк╬3@ЛьQtД uчг▒хM▄;╙ЕO┬Ж╡e╨ы kWхЩ▐:{6БAУ╢Ы3M)iЄXцх}зK[Н╝╤ж_╝>И╧╣╔ЖИЦ╠╧?З┬┬B<}·T/IО╠`√;║Ф:[ь╞,P▌лЗ╜#з╗┘Юу▒╣Ї▌║С├"╤V ЗЭх0q=╬а-▒;▓Й(л00╤║, Н╡aьє╘~ee╞МRН▐7▄e:у√прє ЛDй╓╥╥вч"┼ZЮ. МDDDDle8▄ш░(10e╕PH▄М░(10eиююn=├ЧoF"""в ╢2ntXФЙИИИ2\($nFXФЙИИИ▓└fЕEi├■╥╦│g╧tЙИИИИV╩ф┐Ї┬? Hi▒ъORVуЯLЭн¤зЙ(Y№╙АDDDDФ╒ЙИИИ╚T┌ЖдЙИИИhуe═5МDDDD┤upHЪИИИИL10С)F""""2┼└HDDDDжЙИИИ╚#Щb`$""""S МDDDDdКБСИИИИL10С)F""""2┼└HDDDDжЙИИИ╚#Щb`$""""S9OЮoК┼у┐ =Tд╡╣№D-▓зr┼б╟ ь|;▒··┴иuJOЗ∙B╦є17%╖Хu■yхz∙╧~ДП^6 |√4<ЁFY╧╧~4Пq┘V '~Ч╖╘3YЮ7Пg ╢c╙№■K }]И]н╦Q¤~■7gQ°С╤√°█33xGYЛ`╪z╢pйgЄгB?f╒c¤°_ї▓o~[Зб╓╠╜ТБСИИhУ<_ж├╤ ▀╜jбр▐╙6>'╩є*8= Вt:1■с█y№h╧єzЭЮrТ╜БOe[!FЭ°┴П лQ╛ЯgC╣.|y,?╚ГщWРпи'я∙ЁГ╣oёС2є╘>=ОкW2╤vlж[▒K╧Fї√Y╠№° ЪtЧу_┐RЗ ├пЁO╜ Е°╝г{Uпд|мZж{E╨№·k$3#╤f{╛L╟rф╦0%Г]°╡Нс╜{╧я┴Пцх5В└╖є/р∙PяЮe? ї╠й)╩░pм:У%C▀S╣┐║╝&╧гLэч °VD=TпЫIЖ╛пЗЁeR]АЛ дzЖ_йАиЗкE╕№mиЗQM+Ж╖3#╤fР!.|(Zї╞щ╗,З┴∙?|v¤`Юa▀N|Л∙Ю%╣H╖▀!4┬S╠:гXг?■yь∙Ё╗_Е_W №a"м╝b▀T=r]┤}B]а(Гу╧Ёг╚▐┼█▒i■MgА││|]вЇO?+лPyЧїВ▀9Дп ¤╧Ё╖┐┐ВЯл ep№-╬№x│И|lжу▀Т&""┌@╦Kz<┐┬яЦТ╪ЁгЯ┘░GД║?L№r╘X-═╧═CЩmПUoб╝╓/мўM- ╗1EЎ╘ЕпEн│ ∙.▓юPY~]О▐`╒>ДС7п№j╣@~9■n╧№r=√ЦПr╡▀└─/╟бwGью╧`У П╙ОL°[╥ЄцХ┐9√╡.  ■#|╙4Л7[БK╖ОспхН,rx┘XЙПTпс?сч/╛ГP╦d╟тoqы╪_лkЧ+╚GYЗ.ж┌z■Ц4#╤ZМ┤VЩ│┘z#ЗдЙИИИ╚#Щb`$""""S╝ЖСИИhё╞фнхFКН7╜e8)y Мы╟└HDDDD)╟kЙИИИ╚#Щb`$""""S МDDDDdКБСИИИИL10С)F""""2┼└HDDDDжЙИИИ╚#Щb`$""""S МDDDDd° їшG3єУ╪TIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/images/screenshots/console.png0000644000175100001770000003430714654363416021312 0ustar00runnerdockerЙPNG  IHDRБO2YсsRGBо╬щgAMA▒П №a pHYs├├╟oиd8\IDATx^э▌[пЇXЭ▀ё¤К╕БР№.fо Ф╚R.#rЕ4 С"в\XEiBдfР:ЩиЖЗ~║ЫC╙ЫcГxЪ╞єpшph·Q8Игg¤╫┴^^^Ў:Tyя]╗╛щ/║j┘╦╦о▌э╦оЄ╒╙O?=PEQEQЧU:рr.!р.!р.╨╤!Ёў┐ ¤Ёл_¤j°їпн ∙╧■│m└]Uў╗▀ ▀■Ў╖З╧]┐8< ▄│├│╧>;|ЄЕO Яzсу├├З╧╧йў>w¤i╡L?№Ў╖┐╡kр.) Бп┐■·Ё№є╧_№№є├уo}|xЄ├ =№цї~√·GЖ▀■фяЗ▀№шяЖ' ў├П┐ї┴сх/№ЯсЕЮ~№у█╡pWdЗ└_■ЄЧ├├ЗЗ}ч╙├ў╛¤хбэл╢╛въхб жzяЫ_╛їъ╒ ~a°■w╛4№Ї√ 8<ў▄C╜.└M°└>РUwНМщg?√Щ}╡$mзwv№┬╛0╝·╡З_<■╘Ё├╟▀>v°╞Ё_■■є├o~√Зс№╙ЁяЯ·╘Ё│Я┐1╝Ў┌kz∙?■ёП├╫╛ЄьЁ╜G 0|х+_╤ян{cx∙г▐є╤Ч╒;7щ╗├├|tx∙f7 vРФNжNEB▐▀}ЁГ╤ ╕╒V+;╩,рпzнCр+_m°╖я¤чсЮye8< нс┐}°K├_■ыП п ┐Яп╛·к^^Bрлп|b°═у┐>∙╔Ї{Qo╝<|T}┐k_ ї▐╦■ы▌╕/╬5КX╪█#КВ°ьЁ╗_Ш°╥ч┐>№═zn°зg┐9|ф╜в█ ═{■)ўг┐╤2╬╠╬рн p_Ьs~ш█+Кь°ЩOz°∙у╧ъ°эow°W ю┐°╒ъЎ╡°Ля№╟сs╫Я╒я-шY└З*Вm░3ЕЄaIMБ╤╖ЧОmS▄№ЄЄ╕Nкпqu╣─°└Э"чяФЬenУ {@С%▄╜Їт ├Ы?■ЇЁ°ёўЖя| ч├7^{}°├■аыХW2╝∙цУс{▀√Ю~-┐°ъ╫>>╝№Е├ЁУЯ№─ЎР░╡y ЯД│0─╣░f┌>р┐+a╨6∙ч)сYй╛b ,/zсА╦т&NRuЧфМчоН9tзBа№ЇK/╜4|■·ЕсЗ▀╜Vя%¤EСW┐ЎUЯRїIU╧лznxїХgЗ■╤Гсы_∙чс╤7╛a{И╨Alcж-╥■▌З.╚a═н╫єgХ▄╛ь║■Ў2PА╗ъ▄Cа АЄ┐■?ЯZvЄC╤╫╫╫├'_xn°╬лЯ~ │gЖсЧj▌7■ч0№т┐├╧ ыЁ╟Я■чсЗп¤ПсеЯ╛ї┌лЙ'И$ю м Б╓/T╚z∙в╕L└Эv╬!0·Ў ВE!P№щO╥O ∙ф /ш/||·SЗ/~ю┘сKЯ0|Ў3Є─РЗ├ч>w=╝ёЖЧ╞╢ш╦╕ATA╠|;X┬Щ╫6 hщ($Ъ┴▄╛╠r\р<Эk▄ {{┴тшш√_yE?6Nъ =z4╝∙цЫvЙ:РyЧ_¤√gm[б╧{mГе)oV/╖пp<│Д ю▓)l╫]#c┌ y╥v╩qWЗ@ч?°БЮфёpчуш(ф╥п▄+(▀└▌wТ(фK#OЮ<▒пpЧЭ,р|.!р.!р.!р.╨▀єЮўPEQEQR│А√ПpББИpББИpББИИ╒█ ╜i¤у├pЁыЙm(t[у?F╕я}х╛юЧ;C√Ї╒pu}░пq^r?┐~шихЮnЖю╞CИ█ЎT═г╥8wкё▀Ї▀√r▀█╟╢ p╤╥!ЁI74▐ Dкy╨╬дlЭ°n;Ў├сQ;█╟ц║SяцыwCыN┤ъ╪,╫U√8ЮИЫб- й■╙█╧aП├ГFў3 Oь Т№єу6h╧БЗkc,Dэ¤7рВPл■n√бЧ╥япl╫■═╧Гт╓°K▄┬▀╗█ч┼g╕d∙!ЁA;┤╫кlH╨'T╗H┌╓ЙяNК3v√╬Ї■╔?Ч╠┘АбОЛ9NaЫfbп №qf лэ9╘1░уТу уЇГО ?ЎєоБ[Ў■ЁBа}╟p╟.8f╤x*╖°ўNx▓CаBЇgDЦ│#єоiП╘xt'┼╬озjqRЎCКД) j╓o╓╫Oyт-]╠=V█Бb▒┐c▄ь_I╡з╕╧и}[3шw5·ЯЯ╠╕┘fe■70ЯIK }(·єw ч├TY@Ы M·ЇЎї╛Щm╚▀С√╠▄>nН▀э ╓▀Я╠└щ┐╡n+│╢Є╧Л¤│ыJ_^Ы┘╢М█mGїм?o╣╟ЗЁ▌Йej7'+wТы╔ вЫе▓│mRсЄ╥З╠6┌Ус╘Я;Блўв3uйї3й 8^Цc▀╬╖┬╞п\NlЗN¤яZ ┘╢╥ (╒╛┬ йц▓▓\о╢Ч~╡∙ч╣╒▀Г>■ъ│Цvцo@.5Ых№Х ўaf(]{Wd╓Пy▓Ї~!kk№╔┐?w|э°▌хЎ)║▒йud▀Вї√G▓╝Яўябy╧Нгр°Юь╕╕<ЮфэI╚╜^ДBсЭ`Вї├e╜Рbм,я^/Ц╧уf{ф~└║oОоД░ё─ыЎ╦═Ш▌Сш>/ї╣6*Hш╧VПs~№╟╧s$В╧KЩВ╒▄ь <╠,√Ш╪6YOшв}нЗ└Y╚Тc ╟OяуrЬёёo ¤Щ■╜у■}.■^ч¤НБOt"╒rуШ¤хsО╧т│\▓№шU°┼  ф╕yв▄:╔З'┴р$9Е╩0ь$╓╧╘█/?tv&kbsдBаЩi╝s3Бv|╙ё ╞g█═,▀Їж/-ПўiCаС├ьoPfj}l┘8цz ╘√Є┐▓ъЯ╗Y╚Ъф¤m╧_/B`╕|тя{ Б2K¤Hfj╒ ЙОoЎё▒Я%! ▓Cрf(O.kAdы$┐}M═Ф$╫/╢╛╛■╜╡╒Ё▒▓я╔ёOк·е┌W╪╧nBl╚p!fY. н ╚8jCр╚^▓╫█/·|ч√4гў_.C7ъ╚}}цЯє╟┐▓ Ўї"Жй┐o|ц▀/ у^╗/u|Б╧iBрxтТKК▒e▌IX╡╣%uТ2KmЯDgыъЩ(√╧у6RызихїЙ▀М╦▄ў┘{] ∙щ ЩE┤?╙вЎп╙п▌·йё[GЇ┐▌ЮbПЧ f|6ЇнЕ╔EР░ы█ЁдН▀■DfWГcл_√Ч▌▐ч3О/√єо H╘▀r╪Е,∙g╡м█ ф°├┐╖р╡ y:╚Iс°у▀Е Я&Ик?ЯВуCxN╜УУ=Y-▀АЬЭ─фї┌ITы╞╛╝╣■╡│qщ0ф1┬r'єEЫ▌^X│хП_ї/▀@ UўЯ│¤Д┼ё▌Xw-║╥!╘[{ Aa'kЄ■l╜─°в╝(┐Ч'e[№ё╦>Н╟юя'9~╗■°ў╛V[W╟╠Їб┬Ъ√▒╫╛∙ў╜╟ёe╖╧Д@А'│┘mI°n─4█6е─ й╣?Ц√NИгCа|бB.Ч╣Я╧рГ╗╚\2ў*6[}OЕ√╬│ГтшшnЦЧ{╣.щ─ p╬ОБ8?Д@А D╕@Д@А tV!░я┌бi° Ъ5y╟з║цjh:О"ЧьмBрбkЖлл╚Sn▌▌Vy╟зfмGюЫ╝(OЯa╔O──FЮd`Я╛qя:цЬB!dЗ@ ╤Yц╤W.ьЄu┘чп*8°П┬╥A▒.DZ╒╟Хл`жыаъХ┌о^ж║N^лэ╡n)F╒ы╢ыЖV▌G╬Цйe\ЫЇхЗЭ^э╗}┐ызх\ ╜Ю}sыNUШ╘>ШmШ~eзїэ°╟Зпeu█СЩ└Y ▌"╨∙√╨╚▒Щэ~z ╢╓wS^┤zу╧Ш5╧%u╪╫?эЮПлВ▀сZ╜╢ыHPьКЯ*в·яU?▒╦Э6vк▌ЕJ╞{уLh╥┴O-╙KРУ╫^К╥!╩╡ы■ц!╦н#зS ╟,#█╘н║▌l[Еc╜ Uz═╙xж}4√с╖oЕ└═у│ш▀B?─╣її~┤aЎ¤═¤█Z▄EE!p4>Ё^брRпE°╧V─╬╓>╖╒╠8C2y╧Д@ G▒╕Ь╣r¤,CХЕ│ФЎоs─х╥q№N╪W╕нхxЭэуумН╒9Щ∙t╫╔┘┐нї└]S6и├Я r7;hчЩ╬ FЎrп▀╛c┴╦8чи^ы┘;ї^г>'╒6═r:[√Ч│>╕kЄBр╞=Б}фЮ@╙ж┬┴Йю tО Б~АЩў│ Uэ╥A╠Z▄5Щ!PЭшУ▀█TР┴S|;╪▐З6▐єж_█╢╠(ў№╔}Гю■>?фm▀8н╙╩¤А+сf╓Зн<жo∙BИ┘╟ЁЮ╜i╝╙1Bа▌^Ї°д·w╟O┐┤эН q~ ╩ъ■eоюЦ╝xлlZФ ║9/╢*Х╘|;╪╠о╣6йp]KВт╪З* ]╢)Iэ├╓╖wgэ▐╖ЬML1ы?№Ў▒:nЎrо┘wє%ym·╖Vў/s}pзЬA<Ц IчH╠xгЧ^NДxМЧX▌OмpO╪!Ё╓Щ╦╪ю2+?╢ n┬Д@ДБИpББшвB`п╚╪¤~рєE ~вхЇЄО?╕ ╟'j╪╫q5!РрШ#я°АЫР│g▀Nў╪╕Eў_fьЖцщ+╡РT3┤ПжGв5Ў}UT█U0zр╓qA▒2$m>Ў,№└хяъч▐┌їг3QЙ╟╢∙ПО ╟o∙X9╗\ц·╗S√■°┤П╘1S√<О╤?x╜·ьї√╥╟┤▄|y~КО l√є╟Ўщ6щпsуРmsIАcхЕ@лWa░їГ▌╡;ўкMй▒н5№╫ъ╡]GВbў╪4х1!@?л╢W}┘@Uї°╞u├Ўoс|V/|вЗ▀З}_G╙З.█jlн┐╖ЁxШYK┘▀┘ыжSсTНё`┬Ц№d▄╥З╪N%╪~,Гїm═Щў╓.ыРи├▒ы{∙yNэ╦ёАrE!pЇDЭШuрSA ╕╘+AQ╛сlН v╓░yЖД :╠C╫|ж. 9сы╔rLIЎя╕А#!d>│╢╛Оok¤}щРхИЭ┘ПOd<·8═а БуB■1ЮЗ└-╤у?ы╦ШНw▒эЬc R╩fu°3Aю╞fo=к╫zЎN╜╫и¤Р└Ss╓▀Щ╖П·°▀╨/ў5;Аы╟T .п]оН 0Ф*▒8╡okР+/n▄╪Gю 4mъd}К{o;.┌е ВШ╡■▐жc"k6╬мЩ╔їcТЩNYv"НcfзvB зРUРI~;8lS'oЩ<·█┴&╚╠}eс={ж]B├t▀YXЇeXяЮ4¤┌╢е·w!N┐┤эН Mє$cВЛ╗o═Цm╚Z▀ДЫЁKзcВUлЎu°tЫ╝/ў√╣ё█6Ч╗'░╒эЎmO0╟╬Ф Бєаi┌т╟?8v·x∙ЯЯ█╢}йПS╨?(ЦoЫ юТeь█╗│ЎNЕ╒> еBЗ^YЩї~√XЕ{9╫|s╒| D^O┴D╤ЧD]▀кTгщ!s}FЧщЁ4l╕Ъ] йА6Оq>63{7╜?;n┌Є°╬/gYf?2 рЬ ╩╙ї╛}tШ}·F~ЦУ~╒·н}╥Gq╠]ЯрЬd&p╬,W1бз╫V3шlпOpN√с ╧ЪН>Я6Нp3 Bр6└фЮ╝ж║╩grэрЬ,:·K"EўNБ7уф!P╘Ж1B └═8AьЗ├┴~3╪√vЁЄ▐;╣'п┌╪=yv]тt?╢irь·pNх'bT°Т√uн5YNЗ├pж╬╝?нok1гwь·pNpnБИpББИpББИpБ╬+ЎЭўЫД═¤9┬3╓wэ╨4]тЗохG│c?╞}√Є╞юююё┐ 8Х3 БЎ╟в▌Aюс9р╨5*▄╢jO╖╘Ьoц─Щ7■█p╩¤┐╗╟▌mo Фю╙╛└э╩ БП█съA;tПЧ с=\л  л╢├√╞HЕ╢зЫб╣Vбm╤Vса╞p'╞M;╟r█N╣ чx№я╙чщ╦p:Щ!░Ъзп╘BR═╨>R┴╬65Ў}UT█╒м╕u╘┤uPм w/│L▒KН╜█xyXOuВР╦┼2c8Щ▐_<╓NЗKїЮщe▀█f╢-╧*╟)cORж}:)ЕпЕ┘з°х╘░sBЬЯ─═·r║6ь├╛пў═]JWe[НнїsШїгуO_╗ює▒Яе ьч═╧O┘■№MЯ█√┐х╢П┐9n│┐}UУа¤`Bёt|l┐╒√/ь:UуK ПпЇя▀╘ё?v√p╣КBршЙ·│|ъD\ъХаи▀Г0Dй А█Y├цQ■д╫f2№РрNсЙ├,т │бCЕ{C9йЩУЙ╝]╟ї╙O|,b9╟ьл▀ЧТь▀q'@ S6╓╓ёmнЯg{№k╟7№ь┬~ЯЯЎоУ│ +n¤°Ыu]╚YИlO┐┘ё9b GХуKI▀фё?r√p┴╩fu°S';лw33Б╞<8ДЦAaдOZ!"RТ'йplыcЭЗ+ч$иgg╘{Н:╬rB, !9ыч┘┐├Є°FОЗ┐╧й╧O█°№╡н¤O╕ ╟┐W√ч]юФKвc│=>оmм┘┴8b П_Jъ°&П┐r╠ЎрВхЕ└Н{√╚=БжM¤╟·&ю ╘╢B└▓э^Е└E╗Ї_B▓╓╧sLЇ╟6яgy╝юT╝су/3q▓пу■/fцbN╣ ЕуK)>╛█√R╝}╕`Щ!P¤З8∙эр░MЭ,dFЁD▀^frЙ╩Е∙g√╢GЗЖ═{х$cN.Є■·╢b╠╢хЖv╣\╢╝g╔┤╦I╔┤K└ ЛМkl│у7Юш▀Н_┐┤эН:Iч└┘1░e▓╓▀d√ЛО?y|эёС√∙Їz╙ёr6??╒Kъєл√Яt√╟┐?ШmЫ2!╟A║o fz m┘6зz O0╛mЙуЫl?v√p╣ЄBр░╠╠мСЬ]йЦmЫиЙ ж=°vi2дdP}╚ЙI·П~{╤oў╛kВМ9╔Щ▒∙хэ╟м Ё█Уj▄ЎrЭ┘7є%ym·╖Ї%3╫╖*9йЪЖ╝їW%╞Я<╛f}┘ю8> +║═ё╟> №Є>eu 3▄ъё7╟g\O╒Єrз Gc■║Юъ¤?┼°J■¤QЭ l.╘┘Д└єaNJ│У6Шу╡-╕P№√7Еx уеиГЭ5iўhaB╝x№√╖Вx4sЩs╝ея╗▓M╚@╝l№√╖ЕpББИpББшЖ@sг9?1░ОpБЄB`╓cуьг├IЧПРТ╗бy·J-$╒ эгщСSЗGН}_╒╒6E╞╕u\Pм fЫПНRRПЭ╥П╠JшВK╜uр{y╚╜Э5lД░CлB_лвЮ\юM╡!е3Б:№MЧod&ЁшиЧГхТ1q\▓╝╕qO`╣'╨├У▄xКшщ√n╝| pй2Cа Z╔oЗmЗ};╪▄є'_Й▀єЧjW┴я`┌LЩ╕ ЙUИ] ПўI^╝mь▄е▄┼L▀м=№Ў░ ЙюRЁ·х`&;f└¤w!p╞Е5fьjЭG/хЖоХ/x4├Me└┐¤EQ╘YФ8Гh╛ш1^╩m┌сpГУАЄ╓7▀|Ув(ъN!@йєШ ╝E.~∙їБв(ъф%┬@WSД@еБ Д@КвЎ,║Ъ"(EL R╡gЙ0╨╒!@)B`!Рви=KДБожБJБEэY" t5EPКШp█!ЁЩў╡├[▐╓ ╧D┌ъы0№╒╒╒ЁWО╡й·░<К╧}#╗ЮК-CQ╘IJДБожБJ6Cа KU!н▐¤╢лс-яы#mS=ї>∙]─S▒DTї╠u?<гўпb█╫▌ЁЦ1D╩ёi╘╢╢ўsYy╟g╡о╒>кї▌▐ЄОnxъ:▓▄j╣}К╩,:┐▐∙╬wF▀Ла!0a+>#эн T═ЁювА!uЫ!#u╒1Q┴O┬д ▓е√z╠ё1ыJ8J┬мДDc╦о!Р║Щaаs%╨Uм▌/B АRД└ДїhBВ╠p┼├В ?я0с╟╠Ж╡cP╘с╤╜яХ▀╟SяЁ█В ж├┘зх№m╝Em{> fCр√ж;Щ)[╠fnЖ└pжэ0╡╣О╧Ы1▌_╬ё╔┘┐э╖>■ЬэS╘йJДБN╩А9AРа!0a5zAGЗ?й2!nЪ {╖╝V╦ШdfзLxTящ┘*U▐·f7Л1т╞Щ-;є5ГэS╢г├и▀ЗY_Вщ4Sопj5┌э╣Щ╢Ы09О'ЧяmН╧╛Яq|тыO╟?~:1■мэS╘iJДБ. ~сыXФ"&мЖ@ HnfKЬe╚┌╛№╕6Г8/3+╡ b:ф╕╨ж╖█Ю /Лf╞7█v,ЁнЕ└H╚ЫсH{Ї╜╒ёЩ╢Їё┘^ЪЙ ┬`j№║Є>К:╢DшbБПр╘Б k!PBШ\B4!$}╤P╓q!╨hzяRлю█а╖╝нQeВ╨4ЮHH-Б╥gP┘!05>╗╠ъё╔YZЎ)∙Ж╡,ы·JН▀оGдnвDш╓┬!└)т!╨Диї▒ Lа┐ H¤~сMЦ▌Бzfq$U╠ж┌g√СЯ╘╞ё╔Z^╦Щ╙эх Б╘MХ]MФ"&DC`,╔{^И╥бC ╣ if е╝х▌2у}yж╞v√z╝'P┐Ом√v▓Я~╧╬Д╜M-3Ж>э6ьм┌"┤ъ~жх№ё╣¤sў<ъrы╣Рх·╫√ркф°LнЯМ¤{╖№$╠8╢Nяпш6╟я/│Ў∙P╘ЙJДБожБJb!py∙U╒bvi Wо│JrY_╩┤%ЧЧuЫ i■║жT ё╫╖!m1Щ┼╖нВш√╠Чф╡ zA *шм═К═ЎaЎ тх■Н!RЛщ¤хяж╞ч·Y;>Йїїzє▒щKўуЎMлуw╡║}К:]Й0╨╒!@)B`Bt&Рв(ъD%┬@WSД@еБ Д@КвЎ,║Ъ"(EL R╡gЙ/∙╦╒EPЛШ@д(j╧▒pЧ[Д@╡Б .■ш EQ'/ w╣EPЛШ@д(j╧▒pЧ[Д@╡Б Д@КвЎ, w╣EPЛШ@д(j╧▒pЧ[Д@╡Б Д@КвЎ, w╣EPЛШp█!Ё┼ў╖├[▀▐ /F┌(К: ▒pЧ[Д@╡Б ёx■┌{▄Ш╘[▀u╪%и}ш¤Єh│v°Pдm╗·сCяЄЛ╓ ¤▒>▓EQ╖Y"юrЛа!0a+Jиzёлк>╓oХ °■╗▓·с╜o7┴я╜vМ&ЩQди╗W"юrЛа!0a;║╫&t═CаZF▒iжpjSeГгk√╨╗цы╦ыi/Ь ┤█чїс<╒╖А_ї╫ёълn=Yfч|М■°╡н`▀╘√╙■п╝Vу√x{═l&E▌ ▒pЧ[Д@╡Б ∙3Б~ш▓3qoяЖщv║┬Рt%ЧРэ,Эa│а5═рнЕ@м"¤┐(ы$f¤^ФЁ'█T}ш┘┬ 8ъ:Ў/mСРЧx=оo╖%√k┌)Кr%bс.╖Бj╢BаЩ%УТЩ2яЮ@=╙6ЯЙ╙┴╠Е к№`ЫI4е╫[ Б╙Єєї├иГкон>╠kф№6еCст┬Ў°kт√AQФИЕ╗▄"иEL╪Ю Ь▐ЫЕЬёrkP'Б╙Ў7Bрl,~?╦}╦оу╖╒Д└Y▀Л}ж(JJ─┬]n╘"&фЖ└┘ь_d&pV;З└ш=БЛmFЎaгНЩ@К┌зD,▄х!@-B`┬vtЧYU╙_фШBО∙bЗ b▐2╙=z6$йP%яH~ 0M?▓ыМўъ╫єїWCа}=█╛┌╞4>y╧▀╫╧T9ўJ(Ь╞iЧ√ ї╕зх¤mPe■; w╣EPЛШ░╟╦╝║┬▀сSсhЎ;}~HRеВХ╗L+▀ьЭЗ╕X R.─ЩЎїh▀ ╢я~"╞╠╩┼·їKmCIйЁ█┴к№ё{▀6c▓у{┐╫ЗJ}КвtЙX╕╦-B АZД└Дx╪Vр▄ДБожБJЄg¤Pdg╩ЪNЕ+i7б( 1Wъ 7ЛжC╥,M3lk!PЯh ╬zt¤ы┘B}Y╫▀F╨┐ rv:@&f¤Ї:▓Oк=[Gr╟ёK[$ф%^╧╞'пЧ8 aал)B АRД└Днhf▒дd&╦╗'P╧Д═g╩tpr!EЗ?tеfь№eЕ┘■┤№┌·[!PHАТ ╟ЎпxcCащC╩╛бЕ}Ш╫ц° lшP8╛╢╟_√уЛ'р<ДБожБJ╢gэKeB╞╦бA╣NзэЧЖ@ї╛¤RG╙╚╜|2╛eЇўo5╬Ў╒gд╟оу╖╒Д└Y▀Лc ЬП0╨╒!@)B`Bn4┴╞й┼╠Zр╢Cрb√╥▀v4ЧomЁ╙ы√э38FгХ■╟7┬Ў°k┐Ш ─9 ]MФ"&lЗ@wTf╓ц3a:╘HPЄЦЩ"Л 1*╒╚√∙-└ ╘и█f├Н~m█╞э█Ч╡!P┐m╖▀╚Xuл2▀?ўS0сЎf√з·ШЎ_▐є√╨o╬ШPiўKП╟я▀м+бp:Сv╣▀P╓чЮ@Ь╣0╨╒!@)B`┬V4Ч@]Е┐У7Е'W│МвВП╗М*▀╝ЭЗ╡X R.dЩЎй┐┬(яПc3ў3ЪPч· ╢п├Ц^╤уўa╩¤DМЩХє╓ў┬ёDm├n╙М!╪А|╝o√уУqєэ`▄aал)B АRД└Дx<5jЦa q6Т·pOДБожБJv Б·2ж╣Фif╘В{ь░БИ√% t5EPКШ░Otў╘┘╦Э·▐8█Д Д@▄/aал)B АRД└Д▌f└ ]MФ"&ь- t5EPКШ@░╖0╨╒!@)B`!└▐┬@WSД@еБ Д@{ ]MФ"&ь- t5EPКШp+!╨>F═O┬ю╗0╨╒!@)B`B<Ъ▀йЫВ┌Хy░m=¤C╥ю┐Ў╜╔r√z │_Ы^{dА╗& t5EPКШ░хY┴.м╔snO╕VCатЮ6вГЮ аЄ┌6Д@р\ДБожБJ╢Cа} \jЩёй ═╨.┬Xк]┘ БZ<шїЭ<Ж╬ї=╒·°TЫ ° ╜]5жV┌Ъбы╠хщ┘2N& t5EPКШР?8ЎяAФ~ЬiЧ5╧Y█эZeФў│Д│ЩB│▐U╙┘эwz&s▄╛ БЭjs¤ы`йЦ╖рxaал)B АRД└Дншf╤╠LЮOа ЙЎевC▀Ф▓эVum╜Д╛yh╒!╧m▄ощC▐&√ ]MФ"&l╧┌ЧК╣kЫY!/╒юь%╝х╢OnTшjКа!0!7Ъ`хf╫╬o&pЖ▄и0╨╒!@)B`┬v┤ў┌╔╜sт╝└жC▌Iю Ьogд_o};8╪F░╛nУ шї=╢Б║Ъ"(EL╪ БуeT]Нk╡М Pc█b┌-╒nZ╣╠lЧk]Лm?2ызBв  `ЩiЬЦшч}лC(!╕Qaал)B АRД└ДxА╙ ]MФ"&ь- t5EPКШ@░╖0╨╒!@)B`!└▐┬@WSД@еБ Д@{ ]MФ"&ь- t5EPКШ@░╖0╨╒!@)B`!└▐┬@WSД@еБ Д@{ ]MФ"&ь- t5EPКШБ╦╟╢5│G▓х0Пd[<ъmФj?F╬cчЎ▄>_шjКа!0a+╩│В√^╒бЪEИJ╣═и╚╕√Г▌Ж ░Є┌6Д@ржДБожБJ╢Cа}╣La√№u▀5ЛY8&.╒nи>╒6M[3┤aЫz┐э:Nї║mД<zх█Ч■╟ЭUMнzOНйХ╢fш:y,`║Ъ"(EL╚Я Tag╠Hє╨╖|ЩЕЫ═─е┌%gйА╒┤├┴n_BV╕╜й▌Д┴e[ЫэKm▀мw╒tё■mьTЫы_K╡|╕%Д@╖ГШ░▌,ШЩЙєя LЕ@▒└ЬнЎe:N)L╖OыоЗ╜їml┤ї·№╨+oйРч╢пCа а╢yЫм ]MФ"&l╧┌ЧК╣Д*┴GьuKЗ└й}нпн1д╖?Е`[nГД@аHшjКа!0!7Ъ`фf╟vБС■n>╬ggБ@С0╨╒!@)B`┬v┤ў╩╔╜o·KєЩ@ e╥v╨│Дa┤┴═▌╖g╦╖╒>k[╣'p3ъ■╢╛Ь▒} В▐1[ Б@С0╨╒!@)B`┬VЬ] Х√U й 4~3╖ы╞o╥╬Ва a■7luh┤M┌f╗▀╢Єэр╒d╓os√*▄╢&▄║╖GКДБожБJт!N' t5EPКШ@░╖0╨╒!@)B`!└▐┬@WSД@еБ Д@{ ]MФ"&ь- t5EPКШ@░╖0╨╒!@)B`!└▐┬@WSД@еБ Д@{ ]MФ"&ь- t5EPКШP├'v╘ы╗vhx╥pпЕБожБJ╓Cа<xz4Ь<║M?ГW╖Э.Ъч╦#╪N)ч▒qСч ╪EшjКа!0a-Z№фy┴}пJЮ<╬╪Э.юF╞▄l╨SсU^█&Г▄Ф0╨╒!@)B`B4ЎЭЮ\y6v▌╨к еg┘┌ЁТоZ╞╢щ0Д-2]┼f{╡М╠┌eЪvШwсў/█П 6Їz=√8н;Ў1[nгИї>┘а▄uЄzm ┬@WSД@еБ ╤иC╬╓%Z%ШЇ,с24ъР7╢ЫР4╧H2S'ЧЬуЧГ▌Ldgg";yн:01═Д╗лж[▌╛▒6█'}│ДR╢5┘┐ БЭМ╦ЎпГ%ў6Qaал)B АRД└ДXLзpЖ-╙╛Е╦Фfgх┬╕\F╧TJ│п▌╧bЕ╡(6┌R¤П!┘Ї!oБuaал)B АRД└Дcfз╠к╚хфв a3╢wйvмE ╟Д└Н■ Б@С0╨╒!@)B`B4FB▄\".┌ C`d¤ЩTH7·'E┬@WSД@еБ ╤и╕{ЄZяЮ╣Г▄ЯgZ!╨о┐uOаы╙Е@¤┌╢)уЎ▌╖УW┌▌=Г║lЫж▀█·vp0F[╬f Д@аHшjКа!0a-J░УАц_] ЙШ╪мЪZF╜g╓ ┐l╓w¤N%┴╩ ╛мj▐ ▓}O╝ ┼мЯ Й■7АeжrZbгB P$ t5EPКШ░р4┬@WSД@еБ Д@{ ]MФ"&ь- t5EPКШ@░╖0╨╒!@)B`!└▐┬@WSД@еБ Д@{ ]MФ"&ь- t5EPКШ@░╖0╨╒!@)B`!└▐┬@WSД@еБ ╗Д@√Ш8S■S@\в0╨╒!@)B`┬zФ╟╞╡▐cує ^█ЪвЯ╡;>^э╘r {ФА█║Ъ"(ELX БЗ╓┐Ўа┬Ь t╙│Г3эS░AOЕSymЫ B pWДБожБJв!░яЇ `╗Щ▐CлBЦЫ%lcak3nмo╖/яw*ш╣хЪ┼АтAпя█я╝ц╦∙█·╓уVc▓A╕ы╠хэхЎф]MФ"&DC`╞ ЮЮ)l╘2уe▀Hh▄ш'╡╛╠Є╔%▀F-╙╔lд fє╝╖6█Щ%Ь═ЪїоЪ╬n?╜у╢ж■u░,Э аЕБожБJb!0xL@ЫOЮйPж└╒Ш│╛ Бc└[о│┼FЫЮiЬJ╜╧оєq▄жyЫ╘ ]MФ"&T═F.Е└мїcб/tLT█ ╦mМЬTшjКа!0!#!mюцf╫╟ О Бсеe!8й0╨╒!@)B`B4*:Фйа╘zў╘ф▐<┐=ыЮ@y▀м/хlп/╦║( l▀Ўщ■╢╛l├ЦуЎO▀oш┌mЫ7!8Х0╨╒!@)B`┬ZФ vP┴╟┐l: Й╥T@2m+▀Vн OоП6o}╕▄:║$Р∙L@Ь/Щїє╛YмKе9o+є▒йC(!8й0╨╒!@)B`┬zА╙]MФ"&ь- t5EPКШ@░╖0╨╒!@)B`!└▐┬@WSД@еБ Д@{ ]MФ"&ь- t5EPКШ@░╖0╨╒!@)B`!└▐┬@WSД@еБ Д@{ ]MФ"&ь- t5EPКШ@░╖0╨╒!@)B`!└▐┬@WSД@еБ ы!░m3\]]Щj┌быmУv┌╞╢]5C;kTmъ¤╢ыЖ╞о▀┤Эъ╤:┤zХ╛Уm╡jMўMшjКа!0a-Zю║C?Ї}?tЄ║=МAN╖л`xPm╜u*ЇН ╬Д└й▌Д┴░}zн·Ч@9╜р ]MФ"&─C`╥B╦v ╟7L{3NїЩР7╜Ця├Рр> ]MФ"&DCаeє╦╡3С╨ БS√2к╞╦┐·Rpу].pпДБожБJЎЬ ▄ Б▐2▓ю╝ └}║Ъ"(ELИЗ@ъф ЎЮ@S╢Q╤эЙ{╖Cаэгm╒▓│О╬^шjКа!0a-JpЫ};X╒b&O╛╠б█V╛ЬБjf}.ўZшjКа!0a=└iДБожБJБЎ║Ъ"(EL ╪[шjКа!0Б`oaал)B АRД└B А╜ЕБожБJБЎ║Ъ"(EL ╪[шjКа!0Б`oaал)B АRД└B А╜ЕБожБJБbхЙ&ЧаяfO~╣ЫЗ`¤єщ╗vhxт╠Э║Ъ"(EL╕╗!Ё&ГY═╢n;8Юb√цё~M{X<·nY▀╫C'П6l╒ЮФ║э╧я▓ДБожБJБвf[╖"N░}¤ьцЪu╙Ў8╓╖¤∙]Ц0╨╒!@)B`┬zьUFРY{й░iзKЕ}74ух├├x9QfФ&╙√╦6щbъ╗Q}╝s▒▀ц╫№Д╜▌Т @fd▌nО▀╓·Ъ>nє~ц╗░╛y╟'Mў╗ФZ№∙6Cыo[ЗKї^k√шфu╕~Bтє9ш╛]┼Вь·▀яйОЄЕБожБJ╓Bа9╔к╕J/rй░У╫ъ$юNУ╜Д9q╩╔UЦ▒'~s5│,0rЩё`B┼<Ш~еЯNЯм¤╣}_Я°▌еJU╢5п -fьz╘·╣пlь6л╞'╢╫ЯН▀╓$╡╢▀═эo1╦ыKй■lлР■╫?_√ўб├нkє╞7.kЎCОыjрМ╩ |╓.o ¤╩{╟?Ф ]MФ"&─Cа9 Oб#╞ЖДё─ьнгgТж└ tXthO╛Схе═ИЕь■WшР▓ e╦mUОo┤╢╛Y╫░Ем¤╦┘~▄┌L╪╝ Н╧wЎ╧Ж]юНё°Ъ1╩█E!0√єq√Ж└х°ЦъП╩ЕБожБJв!0zB'Z╜~0д╞Е╒ ╪^кkЪFХ┤Дмd  ╔Рqф°r╓ЧЩ6яr░\ЄЫ│Ўok√y╢ГY·єї█юT╠·√=■°!_шjКа!0с╪Щ└їР░q^Ьфеп╥Ш:╔oHЕМc╟Ч╡■Df хXО!ъЖBLuМ┤1И-aал)B АRД└Дx┤'uDZ{OХ)█иNаюЮ1╙n▀ЎДўdщ▓mуI^┐aя∙jdY▌:╥}╕√╬l9Ы¤'Щ▒╦d╜┼=g╟О/c¤■р▌ЛfCаHrЎoы°фXf╥W╞ч╗yOаjS¤TЕ└╘ч#ь■Оўъ╫╢Mq╟/■ўk{№Р/ tя|ч;яm╜/EPКШ░%╠╛]щЭД═ь╦Ї╛9сЗЦыO33*МmЄ═RsУ■|E_2Э╓ЧЩжщ4╜╒T▄%W жРqь°RыЫР3озjv9X╦╪┐═уУ╢╠Є>_█▓П^/GЗ@eєєY?S■8╫ ~GG?ф ЭД╜0Ё┼▐єЛа!0a=Ю1;3╡мXШ╣Зю·■_·чsБ┬@чЯ }сыXФ"&▄╦иLЧ¤▓Нроя ]N+ tR~ЁK@)B АRД└Д√▄aаsХеБJБЎ:┐rа!@)B`!└▐┬@WSД@еБ Д@{ ]MФ"&ь- t5EPКШ@░╖0╨╒!@)B`!└▐┬@WSД@еБ .RE▌х"(ELР ░REЭC@ B └"\ B └"\ B └"\ B └"\ B └"\ B └"\ B └"\ B └"\ B └ZД@Кв(Кв(ъ2j БEQEQ╘%╒╙├┐╔·H┐■їпuєж 4ИM┐ё[·╒п7щ╫╢lЦM·Х~=¤ofuvЫ{ю╣Wў▀┐6m┌фХ6 √[ЗyнЯ№ф'·¤▀ }WЄН№_эк■фК+Iў∙{U∙┐в∙щ?■C&ФIп╛·╝в╤6▌u╫]n+АН!я╟;№ъW┐rk│lРJчїlщЭW №╧ Фq_АН паeЗ drV*d┘??cОЬBыЗЭФ╧Rhь9]╕p┴Хne█V·╝Ч<~■Йl·╠&/\¤╓oI┐∙Ы╥o№╞BAы┤Z▄СZкП&]█J·°A▒ч]-яTУGUЭZаА╪{|ое¤Їз?UUїd [╢╬╢┘mVR^s┤FGG5¤УЯ╠Їl¤fQС▐█tЯЇл┘¤6¤· ёw═┼ї╖∙ У┤■ї_√Ї▄s╧щwўw╜║╣lаxI~0вCбTy╖^{r@7╗ъ╝-2Ы┐_6∙nWh▓Эўэ·~ыНэ╝X(Lх│═ZHк╤С┐SEEE╓║ХРў-░┬Я¤м∙Э▀ёЦЗm╖╒ж{Ї┘╧>2│№ъ╫Я╫/╓?¤╙#▐ЄЛ_№Б▌╙?@^ъ╘ї┴=№ZЯЙ+╧)иR=[AЕмФ╝ВV╞DЪGH═knW║╨=■ЁkъsIыtK┌╨_Лнt╜^zWЗяO╒e┌.{Мj╡┤TЫэ[╝PЧy╗гзэЁЭ▀fЫТGэ~~y╬Pд7╠Ч:О▄[d█&н╛·шeWi╣sHf~▀sПчЯЯoю{│╥├VР!╦Zz╨╩├r╗ ы║Rc╜zЄ╡ЧLриSЧ]╫├:ЄБйwCМ╖nчU.Ш▄D▐╒╪Г?4█w╔!ч■/I?┤mOъ╡▌ЫЇ5┘¤№Є╗З┐ы┬Т 6ў┐б╟э9ym╥K╖Lм╩╢Нн?мЄ┐■Зz├к∙2╜я╣√▌№рИ╞vз╧чЪ}ПА┬Х║G-┤рЎФW╨▓ЁЁ┐¤Ы▐∙ЧёЦ їп jj?╤┐¤█;3╦g>є┐ї╣╧ ы?¤зw╝х╬; ╬ьЧ╫счyX▐яVO╖╕0█ЫУC╓э\0▒A─,│S┐╓у{╥&:х┌ ЗЗфmY╫рЭЩ¤╝ЄШ.█`У╝l╓╥B▌ю╫Їю√°█еd█╞╓?|D╧╕s ·ж9n╝у=йЖ╘{ ╥7Я|W│/;я= Rъ╡╨ВХУ>\Ш>МД╝ТРЭ╪■є?╫#mm▐ЄЗ╤и*?єП·є╧╛?│l╫з·У? л╢Ўoйй∙/P,ъЗ#∙╢▐x╖\╪|`З┼vK▐╪:Є░┐╔-Є▌.Ыхю?уIw ╖dЬ╨Я╧6 (єчd═Я│╡╥Є ZЩцZ¤·╫sTцщX7╜░ХЯ╘PШ?Ьз▐╫╗?(пs╦ `v%Г|╖╦f╣√[бTо╫2 ж╔╢Ннў░╛;3эъе▄╜w)юxй∙l60╛ЇZZШ#█─ў ├╓ТГV>ю═JЯCe]Р6╝WўМОш░ю╖m_{_х3=Mujx╥эg']e▌.O╦▌▀у ╞д9)ў^▄╣═СmSяц┘║пщё,CЗє▐╖;▐Ш█oУ7 ╦ЕT6s/╦▓в/∙╦╖ДмФT╪▓█мд╝Юг5<<мp8ьJ╛┐√▐k·Г▀Ш¤zЭс 0┴ъ┐D\╔ўцЫЗЇ═o╛Рх9ZыW▐_*¤┴ш№П G №╧ ,√@R█Se'║█ї┘?¤Їj█■¤▀ ▌[┐ыо м┐°Л┐рKе└ЖУw╨▓с╔n╝╪/И╢Ушэ▒ ╡  (y-,N▐_┴А┼#hДаВ@@Z!hДая9Zn+И-АА┤B╨A -АА┤B╨A -АА┤▓шпрItwkKkфа[╦╧ТВ╓є╧r%щъ╒лz╜з╫ХP╥?м-√▀╟ч? yW┬Zує░/╛rtэВ7ў┬`°, 7Ў┬┬ч`9Ц┤ШгВ@@Z!hДаВ░AЭn┘дMЫn]кП&▌АхZзAы┤Z6╡Ш/Ц▌пZ▄g░╘u▌╘═Ыv╨УzXG>Ё╦#ЗBn └r╤гВ╓Аы9<┌Тq╕j╬РVKк_2ў>X Iн▐дЩKnЭ6╫╗·иiq╫ ЇQUg║■╔┘·MKъMАїiнn╨i7ИъгЧ]еs╦═├┐═ь?s┬т╜л├я7°CWOъ▌├▀Э╣9╧╥zэе┤б▄ь√`%ДtшЫOъ╡╛┘лz║я5=∙═Cж┼2╫ %щЗЎ·pD:№5ў┘Ш ╞юCП╗бGє▒щеЇ ╞`█ =Z┘n╨ЎqXх■ тЗzCпyїVжЫЗthd@х▐■ж}╖40Т║ aq╓Сgъ№╒║=й1]N▌ЫmАї┬эю┤╧├╩▒VЖ╜опї═№ў╤7vDйKю] ║Яў╨!}є╔wї■f=y┘|ц┐▒√▌_Jv┐жw╜└ ZYn╨ЎёЁьН$tшЫж═╔zєиSЧ ]╗M╨@Ч)aE┘^D{im╕╜∙БО<ьъ▒JъЇ╠С1пG*yЇ%Н=╛'╦_$Т║<цV=O║╧╠-]№Чs┤rтц▒ъ>x_я>№аю╖ы╔╖ї╞╗^-VQh╧у╥▀╒w▀Р▀У│▐╒o╗.Dя│yR Ў?Й╨*╫k @;h┘─╗Зї]7%┼■ ~fи*ы═├ ┌▒─▌L·]qu╧шИы~█Л°╡ўUNП╓ъ│├Вхпщ╡ЄojюУV∙√_є{x╜!ўTПnЭ║╝9[ў╗!_│╠ЩQ╫я╤▓├АOъ╡▌■═сkz|vш0у═уuн▐н▒#╧ШVИe7УсЧ└\█Ы#i7ёЇrHЗF\тHЧ║FRї╣Ў┴Єd╛ЦOz▌Us=°╠╚LяЬ^╬FR=┐vбў<ы4h┘G┌▀╢s▌аы║fn#З═m╗хцёz! ї@╟╨!s╙a2<╓ЫфQ╜4g<`йШг└ё_▓╔■╢mъ╖ ╦B╨рдЖm3 ╔2T KA╨A -ААl║~¤·M╖ЮЧDчq= ь!WТо^╜к╫{zч╘aэ╝°╩Q}г∙лоД╡vэ┌5ЕBLl*╔dR%%%оЛcя▒нСГоФЯ Z{vЕ] ╓╥█gЖ ZДаUXZЦc)AЛбCАА┤B╨A -АА┤B╨A -`U *RV"щК+ Щлии╚_"ГоPZы▄dWЭ6o▐|╦R╫5щ╢└э.╘:мщщiї7╗ @┴ hнs┼-пъ▄╣s:w2жэцЯ╪I│n╩п╢╗-@PZы^▒КЛэТ*║▓]?u=\QЭ5ыu▐zЭвgM█dЧ)╫iжу╦n[╫еЩ~0╙нs=du╤┘эРЧёОИю +Т>ОШL(v├Ас╚▄!╞\mАВX╨ЙUив┬_Ъb'4хъQ@v╞uэЬэщW╟З╗їъ╡k2Eї ╪дХ╦YE+█е╢s▐Ч&ЯkУ┌ЯJ aX└и╞╩┌╝с╛щЙFНEKхOнTд4кюЄ~MLLи┐|L╤╥Ий]и Pи Z╒эtсВ]·Tг5─F\ QC═NпЧл╕х┤о┼w·Х┘L~hв┘э▐щwУя▄нч√4D╥╩SХwЗ№╒╨n5VIcЧУRЄ▓╞L[╝нVбPH╡mНж4&█Ф│ P░В:Ь╤Й1┼b=║┬═`ъ╤>7▒~єц}жДх(└/└║L╨Ййвс√║в┌▒cЗю ms ╕moUЩ╬ы╥╨д&'╧кл#-Jym╥Ў╪IЭ╗v═>╝vэ┤Ш_┐XI &Ъm╓▐ZS =аrН*┌1иd╥┤uЇЪR╣╝ Цл═ Х┘о▒╖4h┌э6№¤╓^ Akъcєё█jt`╡кля5ўУЛ~╜ўo я╤Хэ&NЭW{хfmЎf┴зь╘╙▒эъiпTeeЗ╘p@:▀оJoЫЭКЯ;й▓╛}кLїjеOФ╟L`*╡У┌KoT Dзl╬Т∙wчD\═cї*--U¤X╣тy╡∙BнэjV╖ъM{iiУHZ░ц6]┐~¤ж[╧Kвє╕ЮЎР+IWп^╒ы=╜┌│+ьjм┼ЪZu╩╦W█╘Ци╤PkЗ.nkS▀▒¤┌тmГ ╝}fX▀h■к+aн┘▐>;з Е┴ЎЇХФФ╕,╬ЛпUkфа+х'а9Z╒j?ЦЪ L√лўыШ]'dА $╕╔ЁA -АА┤B╨A -АА┤╨Ус▒VьУс4¤ЛYду╔ЁЦj)OЖ_▒а┼═╜0╝ёц _┴S@° Ю┬┬WЁXО5 ZЇh╛ы░░┤ A └r╨wАаВ@@Z!hДаВ@@Z╩д&╧Ю╒Y│L║Ъ╒5щ╜Ў┘│ц<\ ыAk├8лh]е*;4Ёбл ┌dЧъъв2╣j╓Зцї;╠ylоSi+p╔DXEEE■t╡А╒P╨QмвI'ж\klR]u√╘SvR╫N╟o┘йтT╦┘и CЫ╡yєfE╧║╩9№^и∙Щhс¤М╔K:~\│╣оX;[╠ыЯ╛жУ╬л¤й.z╢j╓ЇЇ┤·Ы]`U╤г╡Lйя№v┼Ю▐щ*R&54 5┤┼t└╒▄┬ь█▒пCCsQ√Y;у║vэ┤ZRй.═╬°I8▀7я╕м/нН└Ў,moP═-БзX-ё╕Zvnuх|х▐o▓л╬ыщЄЧlCД┼*┘~^Ч6b╨J*v├yf GJ║&%│mсИйЖ┴И)З╢mж▐▀&ь┌═ёьz"вpъШ3;. █ыy╟,╥╠hг}¤вИй,FаAыJOLfiRМq─53∙с╕[[Д│Q?(U╢ы╝∙з╜╥NuyLм*n9э}ЩЄ╡k'sўxуn┤дХTвй^▌хqMLLШе_хW2ШJгжн▀kы/S┤4-▄МЦл}╕_═г╥▐с ┼лF5>МF╒;╛W╟жз5=ЧвMiб) ї╘>с /N┤█▌RбпVЭ¤═ъо╖a╬lW▀нц■NS XМГ╓E%я;а .шB_НТ КН╕&мктнenm╝a?Ц╬┼┤▌№;gГ╙5Э╬4И№%╘;Zеx[лBбРYLащ4&d█.kL╢н╓kлmk4е1]Nжк2┘═2лRг▌╧оЖvл╤Д░▐БТЦўz═┌ы╜╕┘нvп q╜Ъ┘н╢S^f+н╫X|BЭд,X┤Г╓6╒Д╖°л[┬к┘f■¤cz╡╓╠∙K6ё|RЧ╬Ke[ nkл[їn╕▒ии▐Ф+iUчhЕ╛рВV╫╬▌:а d·э└╔I√?╟о╧Пc[╒pа┴№{Юў[└фЗ7g╡{■№№ї╬ї6EЫ"L&ХL-^█*ЧiыЇъ;zMй\dя╞J3к^╗Я]їz═дЄ┤CeU╥╪[│пщU┌╫Укт¤Ъ░CО▐2м╓╘nГХF╦╒яwk═╬╫фmВ╓ФFN╝аОЛПiG╡л┬*█йзc█╒│o▐3нь│╡*+UY╣╧─0Щv╗■╘▄╔ы┼;╒o╤╬9OyьЧ╦д▌┐]К=m╬lг йїX┐тS}iйJ╜еcv^Ф 5═cї^}¤X╣т∙╬ЛкRc┘[j▓=Sv▐Us Ьб╛Pk╗ЪmяХўzMnx╨╛^┐╩{═ыеz╡┬й9Ziє▓BнЎцk1k╙їы╫o║ї╝$:Пы∙g╣ТtїъU╜▐╙л=╗┬о╞▓╧╤j╒)W┌ЎXЫЮ8░_╒thюэ3├·FєW]iо╔оиЮjя╤∙э1Э;▌2є,н└╪ЦVЎйс\ъЎy^Хj?/m?pRзуы?f┘ymv╛U░ьo╞T6С╓ЕМlo^IIЙ+└т╝°╩Q╡F║R~ъ╤кV╗ЭяЦcэДмBP▄╫i;┴=шР5ir┌ешSэє+QмЦ╙nR¤Yмъ-l■░т╛}}/[еЮ3 A +lзт╢╫ь┌iЭО▓VGн:╙'▒ A -АА┤B╨A -ААЇdxм√d°╟═яK[АНИ'├Xке<~┼В7ў┬Ё╞ЫГ·╦gЮv%м5√Х/┴Є┼WЁXО5 ZЇh█гE╨*н┬B╨░Ї]З hДаВ@@Z!hДаВ@@Z└║С╘`$мp"щ╩А╡F╨┌ G┤▓ў▀ ОЙх^╫╦cгчCАBA╨┌(F╟▄╩ тШX╞u йuxZ╙Э|я( В╓:7)RQQ╜║5кhй]╖KDГо]╔Д"aW?┐'e0вpк═kO╚6/xLф╢ФыjЎЩ)█¤╜ї░"юв√√·╦▄б├AEьvЙ┤}╥┌Ущї╗╬ч+)аа5вXEУb'bjкиPЕ╖>х┌Lk╠╓∙KSьД╥ZrюЗ┼лэЬ╓Їt┐ЪUе°Д]╖Kз№>s.НJэ^¤D╗mЄo·^[}╖╘8бЙ ╖kХ¤zф▄╟DnK╝о╡ЭЪЮИЫЦ1┼.я╒1√y┼ею╖№Xфя;н■fп8╧и║╟═>ц╡·э>╤?LЩР▌3зbўV[┘Ш┘░Ть╤║иSWvш;}}J┤Iз:zLМЄU╖_╨Е vщSН:╘K╡X┘ў├ K^6╖эfэн╡╖y)T╗W═г╜ЁТVHeUцmRG╟АТж.фoЖeY■um▄]ы│PыpЮ├Д&╕╡Щ}╠ ╒юn4%'9о╤кFэvпJoмИГ╓6╡и╓Ц-[Tо1е4S#:q"жXмGWfG1Ь√!▌кwCN■░UКЫя3╤о╜eуz+Vj┌VZ╛┬╣о╔╦╠▒Аан■нСШ*╛п+┌б;vш╛QjuМк╖cP╔d╥_lUшХЫ?кт¤ЪЁЖй№!дVпЗ├l3h╖ )┤╗MmНvLjLЧчу ╟─ ч║z=X3=Шц╠z:А╢ъAkъcє ъ█jt`╡кля5 я~╤пў■Н`╘к╙N▐щоWiiйYЪ▄═╒╘OЇл╝╫╘зz╡▄─l:ъэ╢nЙОй9~╠Е0+█1С█╥оk2VQi╘!7Q>5 ▐c'╝√ЯЯЭ■5╡╜dє'┼gj╒▒╕▄─√░:─╨!м┤UZ[Ў?б╟ь╝,o▓√ ╥m┌vкU MщУт▒тj;5|KпХ27v;ФХjЎ'f{7№Щээ2м╬┘4р╦vLф░┤ыъ═╟J▀o╬▄м∙╟ЇЧaя╕╢-¤єn5╟Ю¤┼Е┘уЪєx`Ь-XaЫо_┐~╙нч%╤y\╧?{╚ХдлWпъїЮ^э┘v5XKoЯ╓_>є┤+aн┘б?; ╜`еЖ&УъИE╒]▐┐оЯ├e?ПТТWА┼yёХгjНtе№м■-┬>▐├ a╓ўjм<о v +КаlXщCО├юL V A -АА┤B╨A -ААЁ└╥u╞>░ЇёGyР ,░TKy`щК-nюЕсН7їНцп║╓┌╡k╫ √╔Ё OЖ░k┤ш╤* ╢GЛаU8ZЕЕа`9° АB╨A -АА┤B╨A -АА┤А5ТLD.*RСY┬Йд)З╜uoЙ ║н╖3В╓0y6к║║═┌╝y│вg]erэ╖╘c┬Ми4:жЄ■ MLLшXkHб╓aMOOл┐┘m│b Gd▓`Х┤╓╜I H m1p5∙╔╡▀RПЙФ┴╖║ецvu╓ЪА2Лл╠шШ[м&В╓║WмЦx\-;╖║r╛rэ╖╘cbYТ E┬nhq^╒`─╒█a╚HBй&┐╛^▌U┤4╡MD L└ъ(hН(V╤д╪ЙШЪ**TснO╣6╙│u■╥;б┤ЦЬ√╖╗T кя6Еюz|┬y ы *RUwy┐7╘╪_>fВ╙l`кэЬЎЖзз'╘ииJ▌/┐╛_═кR|"╡Mзj╜V@╨ь╤║иSWvш;}}J┤Iз:zLМЄU╖_╨Е vщSН:╘K╡X┘ўnwй@ф═├jюw┴gXн Н&/k╠Же╢Zoи▒╢н╤Ф╞t9╨ТГJ$"КD:4╬(!МГ╓6╡и╓Ц-[Tо1е4S#:q"жXмGWn∙Ы|О¤▄j0вв╥Ш╞╡W{ўюUYyХkм╡╒Яг5SE├ўuE;┤c╟▌"Jnr╥■╧▒ы3▀dЧвuuъЪWЭs┐\mXЦPЩ Jcoi0ЩT╥.^х*╖єм:╜║┴О^S*╫!█┘5&U5кн╡V╡╡жbl╘;╬▄┐├Мк╫э;sL@рV=hM}l■/~[НьпVuї╜цnp╤пў■НХwV╤╩JUVюSП)їь│ыO═ Uf}№№y]ЪУХrэЧ╟1▒dб╓v5л[їее*-m╥АЧКj╒9WєX╜й+U¤X╣т■\+√иJ╜∙^MR{\U▌f╗pjR╝┘╫ОU┌║9╟m╒Г╓Ц¤Oш1;/╦Ыь■ВЇDЫ╢ЭjUCS·дxмЬЭК_╗жksЦ╙j)v═╓╬Э6їёЭоь╔╡_╟DN▐\н╬lS╥M0NM\OЫ┐jЭнюLЫ╫5o√┌V {█┤╬>6в╢╙пЫL@а6]┐~¤ж[╧Kвє╕ЮЎР+IWп^╒ы=╜┌│+ьj░Ц▐>3мo4╒Х░╓l╡У╫Qь░iIIЙ+└т╝°╩Q╡F║R~VО└A╨A -АА┤B╨A -ААЁ└╥u╞>░ЇёG│=q,░TKy`щК-nюЕсН7y2|с╔ЁЕЕ'├XО5 ZЇh╛Вз░┤ A └rЁ<ДаВ@@Z!hДаВ@@Z!hk"й┴HXсD╥ХыAkШ<U]▌fm▐╝Y╤│оkюЄ╪иF╟┤ GD6А█Ak▌Ы╘╨А╘╨╙WГBRыЁ┤ж;∙бгcnp; hн{┼jЙ╟╒▓sл+cн FКTTф/sЗ) +ТИ(ь╡█u┐▌▀з^▌U┤4╡─ь(dн┼*Ъ;SSEЕ*╝ї)╫fZc╢╬_Ъb'Ф╓Тs?`=иэЬ╓ЇЇ┤·Ы]┼гъ▀лcъПK▌╤/L∙√ЇлYUКO°√OOwjС¤aАU`П╓EЭ║▓C▀щыSвM:╒╤cbФп║¤В.\░KЯj╘бЖXк┼╩╛░■Щ ╒VлP(д┌▌Нж╕Э┤╢йэ@╡╢l┘въpН)еЩ╤Й1┼b=║r╦─▐√▄FVО╓HL ▀╫э╨О;t_И(╕╔I√?╟о╧p█Uo╟аТ╔д┐╕Z@aZїа5ї▒╣5lл╤Б¤╒ко╛WJ^Їы╜cхЭU┤▓RХХ√╘cJ=√ь·Sъ"kн;с▌Я╠^▀mbS┤╘[╧яyZ╡ъ┤╗║ыUZZjЦ& Р┤аанz╨┌▓  =fчeyУ▌_РЮh╙╢SнjhJЯПХ│Sёk╫tm╬rZ-┼ол╠Д%o"√▄e╕5ф┌ЖхнZбV ╧ЯЁ^█iъR√еm (HЫо_┐~╙нч%╤y\╧?{╚ХдлWпъїЮ^э┘v5XKoЯ╓7Ъ┐ъJXk6╪┌Йэ( v╕╡дд─Х`q^|хиZ#])?л?G `Г hДаВ@@Z!hДаВ@@x`щ:cX·°гsЮ% ,░TKy`щК-nюЕсН7y2|с╔ЁЕЕ'├XО5 ZЇh╛Вз░┤ A └rЁ<ДаВ@@Z!hДаВ@@Z┬д╬vEН║еымйI╔╒ЦГа╡A|╪╫гёqWШ'W ┼а"Ea%То╕-┤6Р▓╢╕тq│┤ьT▒лK╔╪6┘еh▌fm▐lЦ║и║ЄъъЄ{╚ъь>ЩЎ;Ы╓╢╣N╤∙Э<лоhЭ┐MЇмл4╬Ъsq╜mУ]uц╕]3=oУЎШю<╙wё╧┼+├ыy√хj╦x╠eJ&.КШ╪Фr╗(s▐с┴@╨BУъzк]у ч╝пС9╫0оЎзf├MVУУшУ┌╬]╗u?▄ъЎїиьд;ц╔2Н╖?5─l{х>їНЧй┴lsющЭо┴фмБvї\╥-╤Юч╨А╘╨╙W3W├ь╣─╩╘╙■=∙╣щм╛7я\zf╬eбc┬3:цV┘┤Р┘фР·╬P[ЛmК[┌tр|ЯЖJZ┼;?╫NЧИК╖ЦIч/╣аuIч╖╟Ї┤k,▐∙┤╢Я╫%nl░SьЬNЫ¤[╠6┼│]k·p\┌^rk╠▓╤л%n╖▀ъ╩щК╡│е┼Э╦дy∙qmП=н┘°╢▌,ю\КKLйL[╜bоc╔я▌К$" й╚[╧╓e4п',ЩP$lў1KZOS2VQd╨їд∙█FКЎ6X╠ы═▓√╒л[гКЦ║╫t╜t▐ыЕЪ=JR s^Ўf╬yцїRчсdyp; .hMН(╓TбК iКЭ╨Фk┬m└Ж"√ч┘.╒mоSЧ╫ Ф E∙Ы┤ щ└n?▄╪0У╓╝0Ч╛nг╬ўfЖєъвй┤I]2m 5ЩВ╓BLАєЖ+╒б╜ъВг╠┼╧╡InшЁйKj;gвk];гъ▀лcъПK▌╤/└▄bЁ-t╩їАў}╒&└ФF╒]▐п ╗_∙Ш @щ├УF╥|U╥x2й╦cR╣┐гСчыейэЬ╓Їt┐Ъ═уv▌.Эк5mб╓v5ПЎj ТТъm╓^█шUп}=╗╧Дy┴hУ T■{P√Дw╝ У╣гMщБ nOн)ЭxбUзBmъыы3KB!]╤?Р┤n?6Чx?┼*▒.╞dЧ╝Nк╘`qЛ┌Ш{iе RC2┴j╗цtTm}Zзп]╙╡s'U╓╙оя┘А7∙б╞gzЫлX-зэ╨с9ЩЯFU╬╠э▓=h√4^╙╔У'u▓l\√Є Ь /m╡ ЕBк▌▌h│╤\&(% ╫wл*▐цЕ%/k,}┐6╗▀Ш.ЫФzа\╗м┴╦RcгY┐┼п╖h╡┌█<кhЗ╫Т╜m▐ыЯззJНЎїьjh╖лLЁ▓й╠{6Р∙0T╗wn`А█T0AkjXC╖ЩЫъ~m┘▓┼,╒jooWї╫О┬ч е┘?[t·Їi╡√╜JGя2Ё&УWЎйьд▌╫U;уз╜9Qvy╒ЯЮ9кL╗g╞gГ▌фPЯ╬Ы-ў╣Юо╩Ўє╥∙v=Х6y}a┼┌∙t├lП┌┘яй▌Н╞[┤sчNs^п*&ь Ц 0їе*нПЪ╡f╡яNїJх*є┬╙хqщБ▌efe@угU*╦c╫екmЛлк√- *йБ^sж│▌Y шV╜RЇЗ&рЎ╟-dV\гЖэ=ъpaf▓лc^(2╝▀JмЫў█Иg╒eъ*ўI '_╒╙&аMNжo`╩g¤▀,┤Бщ└I7\W╝╒╜Gg▌╢n(╤╗тЦ┘pцM\ПЩ╢=6; hО?√є_oЦ╪▄{ЁВф╕>LmЪ>Мie9ж █И]жЬzяУ▐{╣х╖'│ =аr!b CТI &biCА I ╒M(^eBЙы5ЄПщў"y╟ьш5%wL█6┌л^╖Эz{5Цўы-dT╜ю5╜┼╒·=UЎ=f un╗ъ +║aLя=Шwя╫Д7iЧa╡о╚y└┌ &hm лf█Eu╝╙╚╘ФжRЛk╞эаX-п╞T╓Wщў"їХ)6У╔учч═█Ъ№╨ЫXz╘╛пRХХ■ТzD┬┘и)яыPЯtЄ▄5┼g╕SOЯ<аq│П7g╩ы {uNoXfgї^cЯyEєк▐k║▀Ь4mоьЦўP▄вWceъs├Ш▐ы┼╬╣є╔q╠╟u▐NюЯyя▐КЙlщ!ЧZuЎ7K╤zХЦЦк>*5ў√єЫЄRы1█kTя&ЩЫcN─╒<цО9VnY·1G5:VfЎ ЩШi╓ль·r╣ўa╬┴╛fiiS┌0Я9┐ЎfНFгъоj╘▄О╖*5Ц╜е&█keчd5ўл╙;Q√·U▐kОЧъ╒Ъ3йnOЫо_┐~╙нч%╤y\╧?{╚ХдлWпъїЮ^э┘v5╬╘ИN╝Ё}u\╝ш*SтB╗к] ┴x√╠░╛╤№UWJ▒┬+eG▄<█c:w║┼Ы~Х╗ ╦e{рь№зН╟■╢aйz'4<╙-eы╨ДўЙ╡ый▓=o%%%оЛєт+G╒9шJ∙ nшpK╡Ў;ж .╕ЕР╡vlя╘9Э;чЦW╙ГTо6`▒ьd¤A/dE╫1╞■lp╠╤┌(КэsйRЛлK╔╒,┬`─N╓Пй╖<оЙс╓вА█A └КёЯ▒5мс╬L!лVЭLp░┴┤B╨A -АА┤B╨HpOЖ╟Ъ░OЖ№╤┼}б ░СЁdxK╡Ф'├пX╨тц^▐xsP∙╠╙оД╡f┐Є┼~Ч" Г¤J$ВАеZ╙аEПVa░=Zн┬A╨*,-╦QX▀u░┴┤B╨A -АА┤B╨A -▄╓ТЙ░ККК№%2шj( :hMЭhREEЕ┐─F\m>Fлh╥Й)W╠╙╥_o9) +Сt┼u&╘:мщщiї7╗К█└ф┘иъъ6{OМПЮuХ)У]К║╢═uQuM║z+Wа ┤цК┤еi▒╔ф&р4┼p▓┘▓ Ш.\╕а─cо"`л¤z(DУР┌b:рjfMкыйvН7Ьє╛*ц\├╕┌Яъ2╡ ╡ U Ak╦■яиппO}Й6m3 ┤%╠║)g ╖┼2\\з]3kd0тЖ▌╠О$4su#о>вA│Ў╓├JН╬%iu╗n╢єЫlг"aw▄p$┐▐4яї╥Осї─═╝^╓є╠┼ЬG8╜7╧╛F8m▀еЬч▓л%W╦╬ноЬfrH}чине╪+╖┤щ└∙> ┘4Хл P░:▄в-[╠rп+▐ы╩оийК5╣Юо∙=T#15е┌╝Ў▓═#1[n╒)]TGCк=ж|ржNШcz█7)│ы∙эЧє<Н+=1wц╕iН■╣·KS╠? BU█9э ╜MOOиQQХжТMmзж'тк╥ШbЧўъШ┘╞╒¤Цi7е):ж╞ ╗▀░┌╩╞4ъяeШАTХ┌'╝уN┤K╤ж<ВQэ^5л[ЎЁЮ┴╖LйY{k¤b╓є\▓%ЮgР&/щ╝¤єlЧъ6╫й╦V<пK^╨╩╤(Xk0GkD▒ЖщЙ>oня йуЕT1mнзд┐╠[╛│▀ h╒э╠Ў =f{╚·ь║]┌UээЧГ K/t$═!эЎ╟tр╛дЙj∙Ё╧єT(сЭG"Ф4/=а]TЄ╛■yЇ╒(┘╤а╘┤+ \э╥зuиa╒цc-ArPЙDDСHЗ╞╟\▌<Н╗k2zєб:MЄIОk┤кQ╗mен▀▌hЩУ╝lвЩ H~c╚и╤^ ,Ь┤┤╖┘9cЁнnйyпйuЄ8╧EYЄyо█i╡▌■Yм√g║\mАВ│·Akъc%M\┌Qэўomй▐б╟.i╪KZўъ╛m&┬t╝аЮЮa¤├?ШЎхО6■├]▄Vг░;╬ЦpНЙjyЁ╬╙Д║╒^o\ї╗_R╧tOmS═ьAUcЪL5NНш─ЙШb▒])Дw6v(н4жqэ╒▐╜{UV>ЧrJ^^(щtл▐ є╒ЫR~j█ткъ~KГц?g╣Ш╡─є\╪╥╬30┼%^ЖRqЛNЯ>нЦтI]:┐]%6\хjм5ш╤▓Nй╒ н∙├Б)[┤ Шэ!zB;ю╗в ∙¤╙Юч0_Sп^╥ }┴пСШ*╛п+┌б;vш╛P^▒.x╔д∙_┌bлl`кjT[kнjm╧╬Ш?╕╨єz░╥zТ╜│CЗбTn■иКўk┬ъєЗ[]яWNб▌jмъ╓[Й╖╘]WЫ╦Y∙Ьgи╠Дп1╥╥▐Я.г7'Ъ┤=b▒┤(╡Ьє\о╔I√?╟о╗BqН╢ўи├¤:сdWЗzTжн^╨╩╤(Xл┤╢|┴Ж┌╓ЦPЯ7╝fЧcЄч╔Oij─,║WўЖш@Н¤ї╝Ї^$ывЖzF45e╢│Лл═╞ы┴Ъщ13п0nвб├юaПgчS]╤sцwЩ╫mJЭO┌1m░yб├п▀ЎШ┌╛╙ю^kюyn{мMO╪/4t▐▒5jэ╨┼ЩўШaЫ▐Г■!%ўщ√цXбDBў}┐UC5 3Б.▌█gЖїЧ╧<эJл─э╒K¤╙Э│sкр▒╜k6б0╪GcФФФ╕,╬ЛпUkфа+х'╪аUИь╨^лy∙Lд┐ нZ╨J ╧%╘Лк╗╝▀Я(П9ZЕЕа`9Ц┤╓hО╓*K ╤НЬPь√зд╟vм╦Р╡zьг▄0d}п╞╩уЪ dpЛ ┤FkhPГ]ZЗФ ╡йпЭШ╡<╡ъLЫ@>▄┘:3Я ╠┌AлZэiУюП╡зцIkc мВ@@Z!hДаРНў└╥u╬>░ЇёGyжР ,░Tk·dxnюЕсН7W +xРХ}2|(─S╞ Е¤<ZЦКпр┴┌|╫!▓"hВАхр+x A -АА┤B╨A -АА┤6Шd╥нd`Я1ФMrp0ч╛рVн $Щл┤c╨ХцМи┤i@│T2бж·╖Фф╣Ы i>к░┬ЙRп∙Ь├Ee∙ ,┴ънйjкИi─▒╒ХтmЩ╛*)йDм[═эн╩ФеzUo_▓T.ПНjtЬюE╕╨г╡a╘кszXн{еBjЮVgЦ$U█:м╬╠;b╒∙Я╒t╢ PPZ└`дHEE■О$ц&КД]{8вЇй9√1T╡y├sс┘ы<1╫=їЩ *b┌"ЙИ┘╞^w╗>{▌s~I│oъs┤эщЯsО╧8Щ■Zо2%╟~А№┤жN─╘TQбКК&┼Ж]%╓Dmч┤жзэ2бFEUI═┬17ч╥и╘>с╡O┤K╤ж┘tj┐■fWБU0кюё╜:61б■╕╘эШЩ3Х¤єH*╤TпюЄ╕&╠~¤*╫╕√х 3ю.яў┌·╦╟-uє░LРjКОйq┬wXmъ5пЮТ√gРЯ`В╓╘ ╜╨СTM▀]╕pL4дЛо k 9иD"вHдCуcо╬J^╓ШЪ╡╖╓ ╒юUєhп╕Ыоб*o](RэюFS╩Cr@╜гv┐Vo┐PиVЭЭЭЄ>Vя3N;fЫ=цШ.█╧89о╤кFэvг┬бЇ╫уgVD0Aыошт╢Е╖°┼-сmєW▒┌ь╨TiLу┌л╜{ўкм|■н╗[їn╕йии▐Ф░Q$/ззюL°┘Ах $hM}╠_{ Еw3нjT[kнjmя─Ш?8ф}BбTn■иКўk┬ZЇЗРШў яЪПj| йдэiМн@| эVc╒ивM &эq▌т╡∙пэ░╧AKj░├Цыє{=Xi╜T╔Б┤бC~6`E┤╝мЛCЮЄ╦S├ оХPk╗Ъэ╝,пWвIjПлк╗^е▐ьZu┌∙<╜жЬъ╣Ш31█пл7Y`4Zън3)~╣j╒пR╖╣Юее1й▒┘^▄┤ys┘ф·ў╣╧╣уЧ┐№%Ak┤ Ak∙Zц#h-▌bВ╓╟С▀ё■─м/t■ЛўчRВs┤BПVш╤Z╛ЕосRz╗ь5╟┌у│├R╤г╡tЇh-=Z╪Рl╦wAa╔Їe[рvF╨Цc▓S╗vuj╥ ┬r╧й▀╙J╕▌▐╫z¤6Ъу╫ W\╨b╖┐▌}Ї=Ў╪ЇС+оGk┤ь■┬ЇЕ9╦s·▒k╘z√po╣ЦПщKysыш║LMMйооN?·╤П\═<ЎFv╫]║+m9<ф┌░ж№ьRЖ╧∙№ю*ЇpсiЄGoJП■ЙК]є╪@Є╫н┐v╦╨°mM>}oц№╜х╜O]├:їуч╥юQfynUюЎknэ{┤*╛еw>■X╧,/ыП\)¤Z╛Sп■GЦZ┐°uЭ:їu}╤oWЎF¤фУOjы╓н·├?№CWЫAх╦·┘їы║nЧЯ╜мў┐K┬Z╦ў│Ыь▄е╗е▐╘чgЦ^╜нВОZ┼Э9╔+<}x╔ц,bVfЯъ╜3ЯшЮ]к?¤S╗TщО┐tm╖ЗуC·ыQй╩;й╥'цЭнO¤р1}саt<э~\gVзceНю╨сЬ\o╦Юsu^∙9=ўШ┐▌s?■H?ШYўОт╕3╟IїЄШэв▀╓Е ▀╓#ж■▒%u¤╕/▐з╖ъЩs=g{╗ц\█У5╙г5{-SЛwM│зРд▀и┐√▌яъ╖√╖]╦╠Ээ▄Г!я&ш▌─gzJvйs&} щpZ╩сWmy╜+n█∙╜e╢├їft╢uЗ¤@0g╗┤║Ї^ПTy╬■■2╙БУ~ЬЗЮ╙9W=╫╝s╖√fz¤tЩ┌чЬGЖ}Ц! ╧nR╢├чхЯQНл▒jО╕rоўх╬?єї=м├╗№¤Mкsf}v╗М╫?%╧cф№,=Cz√°Г iБуЩV¤╧zWъ╫╛▐ЬЯуїр▌qЗ[╒▌·╩WюЎ╓╝3╙K4$пг╦ы¤z/-─ШаЦ*╧щs█╧УїШCу/U ╫ЪщРJ?цЩЯыоz╓ }·ЙЇе]_1g>ыюп╠-╧=╖┘єЯs>йa╞<▐╟┌∙H█/}ыЭ╣)ЇЄ╝ОХ9ўЧ┘ОВ[юS~e┴▀ЛR╓>h╣░3s╜Л√c=ў╚╖Ur▄O╜я|ыТжТУ┘■Г]╢▐}@.щ■╕)▀пгRj¤┐√╞┐~j&=|╝D▀ю▓╟∙в╛ Ц*\╨йп▀ю¤7№°МNT▄п√№В╣Юш┐еоГээК·б*·эТЩ┐a/∙@W╝э-sНNео█~п╖мхП▓╟э▒┌ь░Т^▓7чФEЗмs╧щб╘Mкё}╜▄ъ▀╢Л#gfzIоў>ич▐]╥▄ф╝.ФЩ╢#╒▐ц■Н╠k:гH▒╣╔Щ░є`j╗Я╜мJ╖Щ}╜K{l╜ s╖√┘╦ялёЦ;ў<3√ЫеўаО{=p9^oFЖsпYшїs┤╧yЛ╖ь╧nЄGzєЬ ";|Цp]S╬╜пТ┐2√┘k█°gRj=╒╙Щё·╧│╨1Rrkшm?╕╟┐╢ /є╧jНОШЯS0W├|ЎЎж¤Щ¤┘┤\ю╓=[ж4jC┼╝с╢;╩jfzИ■┤ъ¤|▄┤▀Qж═ЎЯд65)gj╦=ц(╢gьЖLm┐ы}Є▐нєг2╙·┼╧uуЮT¤M╜oў╡╟№╣юиrї╗╛д;¤нg▌°TЯ№"=(f2ў8╗╛tCгЎ╜Ъ@ї▐╧яШщ л║уЖ~Щч√X3¤н·/Фш╛Ь╖┌,ў¤Мўй┬║-д0З?║вK▐Н▌▀фЛ ╡^'\cZ╜зв^ ╒~xў▌oВS┌║╫hдз▐Г'дKW Ў├X╢Ї╨·▀я╫;й!@{=uBSmцЗ∙┬є├ъїz┘z o╖№э┬▓╫яр%}+nОХэ8n╙╒fЗХьM┘▐ЬэMz╤!╦Ъ3t°и▐№3w│єzRь╕Ї~RУЎцоЧх▓╪,s├|шбKz&:&Уz▀ўЦэмЇ·y█ ╔гк<╛└╨W·■[K№@ХыїR2Э√BпЯл}б╫[└╩|v%┌ъVчX╩uMй|T▐hЭ╜╢щы^гС■╛╙ы╙-tМФ╟z√╕юqН /╙╧кн/Ошпl╚╝ы!╜∙ш_нгРх╗√+.T▄єЙ▀ЛУ \щ=;г&╚▀╕сО╗я1A╚%нO?╣б/Х▌э┌\`│Лэ}·Е .єd9жю№Тьa<Я╗├T╢-╜>Ы;яP╬U╠;╬w▀г;з>╤з&Э▌сЭ│▀├хїВх√>╓╥╠_№│╚v▀╧tЯ*░{╤B wшp%╪РЁH┐ъ▀Iе▐o═░ї(Zэ√Ь CЧ)╨Ъ^Ў╓wщМў;█UыєЗXKОЯ╥Lз_╞ум {3╢7х╘ {╤7ъ∙КCzЁ▄%}ho\┘АT╦╘CФ╞▄0{╙{Mьд┐Ж,Ц¤┘yЯ╒Ы·╤Ь.вїbR╔ў+UТ1E╬│└╧jqшA╖╢О▌¤╕к┤┼ЖИ╥чnеў&▌]ж/▌░sа>╒'7ю╤▌й▐$fvеzВ╝%├Ё]╢c.Х K┐░чы╩Лr╖╛тЭч=·─ VnHqбў▒ЦlX║╨п┐]R/GЦ√T▌ЛRШA╦√P╛-oФ╧°шo√ua ое]─T/Чс╟_їp^;б▌№┼6ї7А∙╫sЖ в╧y█Ў∙}лтТо|т7Y¤ кoЧ╫╦й Яэ8k(¤Ж╜мРe┘сЪT/Iкч└░┐¤хзт?╤гzN▐╚╠<[#g╘лFNL═╠'xaaЎx▐ы╠ Щ└ч╒ж╜~6єBG╞э3Э{о╫╖j_жх}v5j╡#c═Э5t╪Фє9я┼\▀╒цї>╬■№-(╙╧кgHЗ╜╤т^=°▄Янп∙Y6№дz╜?оЗш╬┘uу╙O╥цG▌б╗я╣бO▐√D7ю╣█ФlХ ├╣ey¤ ╡/▀r>;;7╔Ых▐П]e╒ч╜шы╗║lX╩√▒YVM╚Є~аэpvъчceqaM▌q╖ю╣1ъУyCeЯшЮпФЩlRжя°╣╬╕·ўn╕с<╟┐▌ШТ,KugЩ░▓ыK║1ъОcЧ∙╧░ZрШ╖2╟┤є╕╬ф▐▐╬√Єц]е^╫,г▓є╞RцЮ█;/╦N°7╠▄эsFЯ▄єХЩы▒р√Xcv╛┤OЪ╜TzJЦ√~ж√╘ є^Ф _┴√4k╛Вgy║Ж╢m1O ╖╫z═╛╞┼■6т█{t}ЕC╩эъ╢·ь╓└╨с╗ЇЎ√╦ о3° Юеу+xЦg9_┴C╨ Ak∙Є ZЛ╡Ъ7k{│┤єТ}╒╗─▀╬[П ¤│[[╢'ъmэсч%#В╓╥┤ЦЗаU`Z╦Ч╧5░▒┤ЦОа╡<|й4@вG+Їh-=ZцгGkщ╙гЕьVuш╣х┤РA @:┤░tнх[╡аАЕ1G -АА┤B╨A -АА┤B╨A -АА┤B╨ДЇ НЬ╞"wъНЩIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/images/screenshots/datasetgroup.png0000644000175100001770000010130314654363416022341 0ustar00runnerdockerЙPNG  IHDRтb$$мSsRGBо╬щgAMA▒П №a pHYs├├╟oиdВXIDATx^э¤ x#╫}чy бЫхл$▀┌Ч╚Т▄es░[ъХc"▒";ЦM╨┘б;6w┐√Ъ╜Ю aMЬ▒▌ё┤Ъє(┌эv;OП ╔╗ЩwЬц╬мgDg%fС|#нм┤"О▄-Kз ш╥vЗ▒╟в,KЦ%uу=ч╘)аАHвЁ√щзЪu=U(P?Ь*D666╩алL┐ь▓╦ь АЭЎфУO╩╢@─А─А─А─Аl)И? №єЄЇ╙O╦3╧т*мK>ЯЧkп║H^¤к╦ф┘ЄыфЯЯ~У№├У{хяЯ╝Vuq∙Зз▐.ыO_'OЯ ЧЄ╩W╜N■┼5?Фo╗`Ц└ює°уП╦5╫\#СH─t7▌tS╧fC╜}зOЯ╢CЫщizЮэ(ИЛEyуЮKх╥K╩rёЛ^%▀zфb╣чы?Х7╛).Wя¤Y╣уЮ╦хп~│Ф/xЕDп¤яхgоzЫм?qЩ╝∙ї lЮ+Т▓OК╙%$[▓У╨ўtЬЬЬФ'ЮxBю║ы.∙є? s9qтДЭ┌[NЭ:%яL№╝o╫уЇ4=╧v ─Їг╔ЫЎ\h·°фOх/┐ё╣т▓╦Я ╒cЄ╟w|SNых▄╣єr■№y3Пv╤Еe┘єЄТ<∙d│O=#Т)Ц═EЮ┼МHz2+╜У┼ї>tъёх╖√╖хЄ╦/Чёёqy██▐╓│5т╫_╜№╒ъ_n уn╫╙Ї<█)р]S╩*X;}?~·yy┘K/СЧлю╔зЮХєСыхuп}Щ3▒╬Е)sТУt╠Sюж∙┤·m*I6с╬умwO9Йl╓Sє╛∙ё╒о╙[Ю}МХЗс╢¤Ю╟_єx║H7G∙═▀№M∙у?■cS+▐╦╝a|'C╕(И┐·5пС╡│╧╔ЕFфE/К╚нЯ°y∙Я~∙_╚█■┼yсЕфЦ ў>╣ЇEЫ█ъa╙D%r^╬■╖╕\uїЫm)═Х▓GdndB╞в╬Ёшм╙dе\^Цй╣#Ю└ЩУ№Ё╝?+гjи┘|щ#"єz┌ЄФ╠%#2)z9g8Ч>ю]Zc 2aЫ╚иbфH6&│║<╖щ╠м^У▀|ю╩<█┤r\╥ёeg╒ЩEkшr╥_vж╧╦В №^▐╟W;o╣ШС|╥╛Ыё<~з▌O└х╢Чс┐√╗┐+░cа т╫^{нЪєErць9Й< OЄ▄Skr┴s%)¤▌╫UЧУЛ_8#▀√юiyЎ╟ПJщ█.gKў╦╣╩rщKпТ╫╜юu╢?╢╞9СX:.╦л╙bs╕╩л)[ мkж╜Fd┬MыZУ∙2є╢╝╤qк+╦Щс╝мщ`ZZS}╒эИ$ч$W(:єy5Э╧SvlXFцТНkаu9#9h║iТуЇZЮ▓╠:зd▄ є╤iЩЩ╩Й▀цmцy№m-░╜t╗Ё^п wyЫгxЫйьД@A\O]%^.Х╥┘gфТЛ~,{_ўy╦╧|O▐Є╞я╩[▐Ёи╝хїg$╢з /╤ўф√?╝@.y╔╒ЄЦ╖╛╒Ц╨HїbM╖Ж█╨═1Т"╦f|Q2#v|╜ає╡4e╦░▌цjl+└|*Їокiє2iЎЫo╙Фо+╔Z▐Ўt┘¤ў▀/W_}╡ъ]їm┬╜═Tv"М╝XSфТK.Сn╕A~ц╩лф╗ t▒|эсЧ╔_|ыХrjmПЬ.╜Qr▀О╩╔┐y빤1y╦ЁїzЛ в)$72,1▌_ZТЕЬ╗Y╨∙ЪЙI\ц<═L:ЯЭ^Хв·dР7╒ю║Ь\ZО█АnЪф8╜Ы┘u.║a^}Ё82ч╓Р╟dx─S╦╜▓XWNN▄+_═╛ё╘мtС╛maу[ZўЖFfюd─╡ .╕@Ўю▌+ЙЯ yyэk_+?}о,OЙ<ї╠%r┼л▐и6tЯ╝у?'W\qЕ]вCг%#iЙщ& УЙ7кщ:_Sг2k┌P╟Ь&'║3╒╪г2>eЫв╪a ∙ъTЪ╩8═mfжm3У UОm│очЩФЙ║ж)^╬:єv▐Иiгю~sХщЩj9СE]_я5"ёВS+1э╠=▀8t╤э╖▀.єєєvи7э█╖oSw╣a\╧│Э ─╜Яя|ч;ц╟~ЖЗЗ∙)√Nщц5▒В╠xЫцlЩ╛k╩.о╩ж╧]рЯ╕oфMozУ\w▌urЎьYy■∙чэX┤cхx║┌╝╗╞ЦВ╕жЫбш0■УЯ№─ОAs╡ўO╬M╒▐-╗┬ЦЪжh▀ЦЫжш AAAAAAБ╣П╕э╨%╘И! И! И! И! И! И! И! И! И! И!h√'ю╫╫╫m╢b╧Ю=╢╗QGA|hh╚бkkkqА]О ─╤>╕АAC5тэ╤╟√ .╓B@B@B░│A╝ФХD$"OЧZ▒╙Ъ╤╦%▓R▓Г ╒ХЯ╚╢X"`╣еlJLQ+)Яэ]СTРmЪ╪∙ёСМ╦e)ыоШС|2`oIт╪ВLm┘хeЙКv┌VФdй0,cQ╡ЖEСёQ;Z)e*Ё'e╬я╕G┐(7_~╣\ющ╥ў┌i═шхn■вLМ8Г▌q├1ypcC6tўр1)|8`oщ^I_wЧ|ЁA[Ў╞Wd°╠#v┌V<*vцZy▀Ы╒ЦD╞▐kG?rFЖ┐тY╫бП;a`Чъ~ёшР─s╤u╫&╪║╡х╦qI╫Й7*╙є*ь┌ЪЇ╒щhГ∙Fe|╩ ╩СЪ*v]S^Рw■тД,Lър┐╣▄zг│║№)ЩZ╓╦ed*S4e╕∙>to╛Eю╤a№╕S+¤шoо╘0_~∙═6╪>*_№°!yрБCrЭнyЎЯOЦи ╠ОўJ&cSsMэuуrы▌Ы╓є_'ЗN8є|°─ ∙░·k>8╝7#nё"{х┌l/└.ю┼Ъ▐ЪыфЬH~═╘Фo╥`>Ьu╪_tжщ@^ZУ╝╪Ао╗ШоwВjqОйЮbA<╒э╜у═Q~рМш║ы7▀rПнaV▌WЖх╨ящ─√f╣хП╔ ╢&¤Ю[▐▄`╛ў╩╪'(_^S┼оk╩╧╚Aw■?(w}\ ═х╓{oFЧ@шЪoїБс└▒M╒n=·grЧ|╨╘ЪьV▌т:(П KLЗkoяFM>В╠7:лж-╦╘▄вШ║qo╗t╙═J╦JэХФ ю▒ЇЬдcNрЯK┌p▀л╝5╫>!R(ЩЪЄM╠gВ│█cK╬4╚-IAl@╫▌u║▄ ■Aи┼х┌╜кчС3тйnў░Mb■Ёэvп.q▌l$-ёЩi1u═#цвHн┤┤ 9з╫сн┼ЎЫOtoHv╛i·Т╙z┼Oг┌q7╠O-л┐E╔L┘0▀3эR,Фo╕VЎъpэmу¤р1ёmэd╛ўf╘┤п╚БK*&+▐vщж╦H}еЎ&ўжMp┐ю╨ 9tЭ°O|╪Ж√ ┬/?.╫>xП°Tиь*;─э┼СжЩHфИ mЫыш┤╠─л╙& ёjM╖Юf█'Ц╞№чЛО╔D>i╦UЭо5Я╫Tfїб║6█Эц▐n╨[о╧┼ЪХ█дФЦд0<ц|XЁ0НЪж.╬Ўt┐▓\╫&ТсГ╢6∙ЖjєОG ь.y└щuxk▒¤ц╙▌Т▌АoЪ╛╙z┼Oг┌q7╠°К·√а;`├|M╗sB8А+▓▒▒Q╢¤Бмпп╦╨╨РB'╓╓╓d╧Ю=vи SЫ}╚░oРcЮ л/О╘-M┤)\+xП╥▌i7{PЮ╣╬g╛ў╔Я▌|Экю)╗~╜║Ж▄з▄Mэ─їэ %#Щ╜_ФЇЯ╜O2Ющ·В╤ык+3|╦ЁбП╣@√ аП─C8И├ ИАAю]SА]К ДА ДА6т!╨m─0:╜ЦН th+7саi В8В8В8В8В8В8В8В8В8В8ВюёRVЙмФь`GVRЙDLЧZ▒у╝▄ul╟║╤n╪/·1FRтwxЇм~▄f╨▒ тnXЎvЙ▀╖S;╡"йd^2┼▓Ф╦eЩ╡гw┬v╓нЦ╙h∙v╩▌╬╟RўЬ·~кt¤uх'▓-ЦhVю╚░─lo╧к▀■~╪f░-v4Иыа\.fdd$#E▌п║╒п▒S;TZУ╝─e(jЗwRtZVWзелъHX█чy>їєЫO у-йY▒Щ░▓╩хeЙКvZЫвCъ(щ3¤╕═аcб╡_J∙╘и╓╘Ж&dseиji╔╔Ь$ї<║&▒fЩ&_ы√═чнН4╙▌хKТMи~w║¤Ыm╣═кє╓nкмI╡═╣┤─╘tS├[│М√8╒csзkf=═gyЧ╗}ж7|ь╖Э=uЇЗ╞П°lGe∙═П┴>═√!kTf▌п<|╖л╔╛╤╦V>д8√╓]Яyм5хyПЗФдю|к№JАщЪ▀vЪх№ОЯ·эПU╖┘Ф` А╛NW┴г0nk=Чзd╬Д8▓ 2SйiЭРЕ╔·@й┬Х╖Ж}uHОл`_vЦ)fЄТм$"/'└oЪO╫@ц вы\KK ЯY4Ле uMns╡\S√ogпК╩Ї|uЫWзЛ зє╪$}\ХкЪо^Хщh¤Є>ї▀*┤Mжу▓l╦\О;Пйко ¤нD╟╧Б╧~МNп:╦Ъrуъсш║∙1°╧7*уSЎCV═є╪h╗ь╜/IQ;з▓╛┘╤╟ГЦ╦╦Ё╝пўIrR─э╖4ЪOo▓ |ўwРэГ*Ь оВ╟A╖mwl╪ пж╔Й a║╙5▀6▄5дЧёФЫРС╣EЗъ4ЬO┐╝мйдT,─e№р░фu╫є╟ЗTLЄh┤═▐ёA4{Ь╤iЩ╫б0УЕЙy┬═нЩ& ║LзfwtvV=▓╢ы9ичнN╬Йф╫Ь Zп┴|г│6мО/:╙t@▐╩vХЦdAъЮг║чнц╕ЩР1╜▀ї>ёЎыiZ│щ═╢╙o7г┐i(ыbv Ь╨Ъж°RaE╫ Ъ f║БrЛT&ТBqEє├ЛО╔D^▓bAту╓мц╦┘▐╢4yЬ╤бNZ П╩м)g\MtЫ╪tаУч└[╡ t╕Ў╢ёЎ¤Ж@ 2▀шмЪ╢,SХА▄¤cг#¤▓Э t╜─MєЖ┤Ш A╒-гЫЧфж╞7Я&єщ┌P9rDЄcU Їртв╚pР[WМО╦TnAЦ|л|ыTj╜Ы=╬IЩV╦OO╓╢nVмВm╩╠мyQ2#N- &нjС;yЇ6╟╥Я▒mЫ▌Zb┼ьgз╫с]┐▀|:а{Ыд╕┐╒v5{\·├Х╘-Ї╕iWG√Oё█~єНm─d=T#ю┤С╓wр0_ыыn╙EЗїjЧЙщv╥╛ў3l2Я O:N╪TичЄ├ХР╪Ь*W╖oО┘э╒MьФ╤iЩ▒mЯ═y╛ПS┌ИNс║╒-╫╓l╫,я│GЇFзc╢╝═Z╝e4╝Еd└ч@ЕM}qб3╧.┌█Hъu─л╙& ёjM╖w¤Kc■єЩo$Т╢\╒щZєyЁЫlWл}гЦ╓э░╜╦жVЪ[p y╡▄~0и"e█╚··║ йрЗ═Ї -ОKy[Bz▌┌┌Ъь┘│╟╡з╖┌Иўб╧-щЬ mB8Z#ИoQх.\ША6─сг$%Ъ+ьиюq}7ИЦaюB║}╣n▐RєC6!0█1)У·╘ЭzЮ╢z ·1╘ЛП╧▌жA╪ўA├@gЎW`;}╤√┬mВ╖Гwєй¤┼▌╕А┘╝┐q7"╜cGГxїН[wяm▌ю╔oл'╦.Ьl▌У╦ЎЬTЇ▌U┌╪ЯM∙Фeю▀╜*л╦S═o ╪m¤К·u╗√╟╩&еьдду╦^HоoQъ╣я┐╛гSР_▄э@tzFжdD2v]█■k│·¤m╖┌╡│5т▐7YI9╔Dзeu╒▐Уz@╕?ч▐O?a╛▓8'▓ў╓║m▀C╢fEОзE2m¤░З╣Яї╛ Б~╢cг2[ЮЩ▄┴█yОФL¤o @H║╫4┼№°Mm kэ╫РЎыB╖V╚■═zюJ▓∙[╒Тd'ї╧И;ўд6o▄z╣·2m═oхН▌╠гз∙,яUSЦн5о┘.ЯZiw¤╠╫аjЮ·f'ю░█y?афПT╫щoндТв"▓т▄{║▓ ╩tяът>╢╩>W╙ гoY%╡LBОф╒Зи║┌│f╧W═■и┘oН╛.╚q¤Uy¤<~√╝■y╢ўA_jzl╘mп▐v[+ЫуЫ┐)IU╢╟Ц╫h╝ц╖Э╢╝ъ╛z|┌e▌ёНюG_╟ў∙ия}╝Ъ▌F3╬м╙│П╒cн<ЯrЫщh[ ч(╚>ўnЧ√═ОЭ ?VcгэЄWW╬Igм▀qц[о▌╖▐╫Bуї╫нk%р▒╥j>√X}кЦ9oРо[╖я2Mш└єhM╪эмФ[?▄ИyЫЩ/╦L!V}ю╖Х■с╢╔√■ъtW╫Вx){DЭ Ж┼√ГХnMqхы╬·* uR*М╗╙зdюH¤╣єc-#╢ц}u║(йXAf▄2Л▓`╛Вt~hEн@ЭВЇ O═║*╙╤·х╜зu▓╥┐╣ьФU╠ф%щЮ*█╒ь.) 2Щtbn }в╤у+▀%SєлЎъT▌2Dпз~ЧМ╬.ЛЮ$ъ e│╝┌}ТSeОdК╬у6═J╥2й╬▄·о.╬а■еN]3ж"Ю^ўъ┤№kЯ▓b┘II╬х*Б└{"l°|╒ь╜▀№ЮГ:╣╝ лУн│o╒Scцi┤╧ыЮзпёмSu~╟Ж┌'У·ЗzЇt╒-╟[4│ёlП)/iCйя°а╟F╨у│╢<▀Я№ўс√|┤z▄ц╫?Эq:P┼╒░h6╜и>щ╫g└чпNG█╥hz├}ю╖]·Д∙9Zg╝ъfo4е-▀c*Ф╙р8 ЎZh4Я╧║FГ+uыpЬЧ╓Є2R ╬uыxмЎ├~lA╜G╡·v╧№вmїWЗЕw¤ЫM╙я═║т:#∙Mя∙█#:Ч\б~O@ўэlW' ц▄7o■j▐И╒IдRуг├i~нЎMWЭФ*▀ж∙:TЕZй▌UЭоYtkсг╙2пSг_Юмg╛Оно_ ■╚▄в:U(▐эj$╖'QчДь2M>Ф)ў'сuXЫїьЧ°PЛУ╒fюO─ч▄_╫┤▄═шм■┘√Ьдc║\Ею&_█╫╒╒TК7z╛╝√г┘sрхйб3√V╧╙lЯ╫kulш└i╢├ ╘г│╡╧├&▐C¤эНzkgU┐▀°√:<6эЫ║╟Ш▀є╤ЄqП╩°Фz ъЙ+т2~pXЄ:Йыm╨╟^╨чп^'█╥hzг}ю╗яЦT▄k╢╧[╝╟╕Х╙ш8 ЄZ╨№цk╡═Z│ч┴╗ОvПє ыn─\?в▐L╦g}Нйў╡Щ╕,Ш$^'З7z╫▒╠ы└SYг[Рcп·╣lt,@uпН╕_ў^╘N═L3▐uЪоzb╥╡ ]SW√▀ Хq╖лдшвВ┤uhжЭчл╔s╨=║нй^ў╕,Ъ0╙*8t╔vэЫЖ╧Gы╟н2ИК+▓ШW╟ио╣╠л┼В─╟эЦ┤╗НoKЫ╧╤╢m╫-wлыЇxC8╬ї132g┐IiB}ИК/,9<т3н+?ъЩ#;Dя╣*=`╗uпН╕O═Ч[л█╖╓─╘а4║╟¤┌sYтжЩЖн∙╒║╘Хe╢o.^wГTЫRФ$Ы┌┌╫пюМЬiz│Щ╙о\7?╤MQцВ]4ы'╚є╒Ї9ЁW┘╖нЎy;╡c*е╠УмГК■F└╓p{╩h°▄6╡W┘aЧ;■=mнОO3╛Ўk№ъvщж>╫/h~╧З▀уо[T╫р╦С#Т7Mt{YС┼EСa¤╔▒Гч╧шd[=G^ ЎyЕi╤d{¤╢╦Oлrъ-╫o╛fы Ї^цф∙╓═0t vjО5Hmп*мё▒цеГunJЬ╧m═ЦХёxZb▒tїCЮ╤`Щ║¤bЎWхu╒d=jTпuPZ +ж╣О9р \сёш┤╠ш7iSЛС╔B╝│┌*]╬Фєn"▌<Яt╩4Э Э*Д;┐?п▐╘╒ K╖╒М┘7цЪх╜oЄj>OY1▌s;~╛▐▄>╦i├э<ЎШд█кЎХГ╜з▄пн╒у╨eЪ┌6wЬ╙%▓ўЩУЧ■F|╩>v│м^╖┘/>eЩu°№|╒ю7╙∙ ╩у╖═Ц╠╛m▓╧╜╧У╜X│)8╘Уl╖┴6GzП█<╔)┐ц1x╢╟*ю78╛у█86Яю1щМл▌╖·█МС═_ы7z>№w▌вfХ╛▄2їр\~╪╞А╧ЯWз█т7]° ░╧Mg╢╦i[эя^d№Ш╒ЪФS/h╣ чєYЧ~сщ∙[+uВ<▀цBy7|╫kС#*РЪЩ┤╟Ъц}NL-┐[;▀d┼йxpC╗л╤2╡√еЎu╒|=э╤Mer┌О▓`k"e█╚··║ й7`Pш3}г┐цS~у╗I_ ╖8▐с¤Я√P/ьєдям+╠4?Оk═ЮУV╟з▀▓Э╙█∙:╨e▐|═thmmMЎь┘cЗ┌n╙Mщп╨зjлБ╢Eзч%УO6¤e═NО╡^^╞Чс╔╝dц сz5т@Зи· AAAAA┴О▀╛Ё╬;я┤}@я█┐┐эkm+╖/ьJ▀╖oЯz╫йSз║─iЪДАqш╨O{:к╞╜╤tG■·Нrь┴и·{ee▄g┐qе\xс┼╢Д&ZЄ Б▌;zA:дЫжЬ√ЕЛд|у%ж{nфyюч╬йqЧШюЕЯ┐D~·ОsъяEХq╧Н\дЧt hцъП╔Эw~Lо╢Г┘О2;Ж ъ┤нўж╢х║╢·клф*▌▌x╗Ьv╟Uj░яЧCюt╒║ q∙╥'╒|зoЧ╒Ё■/5ичоФс3┐wЭWэSДЩ Р┌яYOе▀)░}т╨бNГxэr*dлЁ}эЙ│rЎмъN▐&╫█)ЖИ╕╙Uwьжлхc_Pє]ЫЬT├w~мUЭw¤№Пйuх╫mygO~@ю■д ¤з╧Hь j▄ЙП╚Ч|R─э }Ъ╡└v#И@ЗЇЄ\°/Hффsж╗рk╧╦%_┐PН{╬t¤хsЄв┐╛P¤}б2юТЬЪ?тyы}№19гrъ&;\яёп╩▌╥dz'Ї:х╦r└н╫╡ЁзЛЄШЮv¤ф▌:╫_S┴▌╙пз╢A:tсЕ╩╣ЗJ∙[Я1▌∙ЗKЮ√╞oиqЯ1▌ ▀№М№ЇБ▀PV╞=w·а Ёб▀┬╨╓О╗╡ьg╧Уэ╠·А╓т╨бNя#.Rо■╝¤╒╫╚╡зяЦп┌vПїnзН╕ыъw╦фvЩїkгэ╓bх╬o╓┘аL@╫─аC█єГ>7╔▒╫╩э7:═D>Y╝╢оИ╙╛√╠█М─╜pЄъП╔п─i^╥ЁbMпЪ∙пСc'k╦╝К[@╫E666┌·~t}}]ЖЖЖьPkw▐yзь█╖╧└рx°сЗхЇу┐lЗВ√чo\Rй[фК+о░c╜т╘йS▓ ~;╘┌┌┌Ъь┘│╟╡З ·юw┐+·зЛЄ╠3╧H╣|▐╘tы 1uїп╙EO{ю╣чL ╦^Ў ∙╒_¤Uy┼+^aЖ╖N▀▐ЁА|┘U]/╖Э╝SZ▐TPAА>а├їSO=%ч╬Э│cВ╤y╛№х/п╢ЇМnq┌И@ЗtР╓╡┌║ЙI;Э^Ж И!шJ╙а_ Lq`P╤Fш3q q q q q ▄╛pЧ9|°░э@#GП╡}═qqжГx╨ `7j'/qqа╧─А─А─А─Аьl/e%ЙH─vЙlй:>С;Tл┘4`@ьhO┼dвXЦrYw╦/эФ&в╙▓║:-Q; вnЪЧбJвХ┘┘Qї╖$┘╔┤фriЙщZЄЩSЮMщZєФм╕5тЎп3▐щR+NIї5эj╨╨gv4ИПO═IRхJВ╓в2=ЯСССМ╦eY=Ё╩ у║╓|V┼uП╩x╒-O╔▄╕W$KK|┘О/к▓ьь@┐╪╤ >:k├Є°вSs]╚=T(?XУ└-я°╪░╕KkТo4?╨'v╕iК5:k┌ИO═-JГ(\▒ 9█ Їл т o ╕й╔ЦШФ\A\║╣┘ш╕LхdЙFсшc;─'Є╔ъХ·*єЎn(╤iЩ▒э╟'╛oц nTfЧуТО╣хжй!@▀ЙlllФm ыыы244dЗz└JJ"ЛуR6wdA+ЗЦгGП┌!╘k'/ннн╔Ю={ьP{║╙F|ЫнxniIК,┬╨g·2ИWю╞b║║[}а/Г8╨я·┐Н8┌в█<а╣n┤'И┌uk¤О ДА ДА ДА ДА╗жь2▄╛а5n_ИmзГx╨ `7j'/q√Bа╧─А─А─А─QлФХD"+%;╪Цн, ░╦ьl╫┴,СИэY╤Ъ6┬▄АXСTх╣Oй!xэhO┼dвXЦrYw╦/эФ&в╙▓║:-Q;И~TТl")▓lЯ√eС$оjьp╙Ф╕ Uїи╠╬Ок┐*дMж%ЧKKL╫Т╧╠ШЁl╩╓Ь║5тЎп3▐щRn╡j]M{ДР╫СR6Q▌ЗСД╕_XИфx┬o╝╖Ц█>▐ч"жЮW3_Q ╣)╫O╖6:.S╣В ╫Оёёй9IъАVI╨ZTжч322ТСb╣,л^#вBya\╫Ю╬к╕юQпkUзdюИ▄* к└wk[Лк,;;┌Э^uЎб┘┐qI╖╧S./├є╬x╡{%=йў{]-╖ъfGЫ=yYл°Ш ПxЗ░гA|t╓┤ёEз╞┤&Р{иP~░&Б[▐ё▒a'фХ╓$▀h~┤╟[ЫЭЬS┘y═∙fadB╞ь7╤▒ ╤╡┘е%YР║¤▐Ё╣Х┘тД,─l┘СШдs▐oG░├MSм╤Y╚Чejnqыэ ╢∙╢DЗpo■э■fA╖ї╖5чжьСaЙ┘I╪с ЮЁ╓АЫ┌SOы┤═░io╝ K4s╪:O═wii┴ўО?5.г╤1ЩР┤╕нWМшР─=╧Е╬521╞╕;─'ЄI█4Au║Ўu▐▐ %:-3╢¤xт─ў═╝┴Н╩мn╧ь6{и\ И╢шч ю\0лўуd!^н╖╥ъё▒Е )ЪЛlЭ╢¤∙д▌якKн╘>5eмдь|1YШ(╩ъ41└+▓▒▒Q╢¤Бмпп╦╨╨Ръ:Ё-ОK┘ДE┤r°Ёa9zЇи@╜vЄ╥┌┌Ъь┘│╟╡з;m─╖┘КчЦЖ}#B8·L_ё╩▌XLWw╦CаЇe·] ╖G[tЫ'4╫Н6тqаC╗юbMа▀─А─А─А─Аp╫Ф]ц╓[o╡}hф│Я¤мэkО█"0─ГX╗Q;yЙ█}Ж ДА ДА ДА ю*e%С╚J╔;iGГx"СHеK╔Кьv;[#>ТСb╣,e▌-Л$йqМю5MЧй\AКк╖ФMxj╩Т╡щ╝f╝ эЫ╞yЫРш■JM{I▓ █o╞█e▄щv╣lкnЬ;_,-9=шВоёRЎИ╠Н KLїGзWЭZrSSЧЇqЛU(ЮL╟e┘О_Ол╨ю7.:$q7╨/-H|Jd╤дъвDЧ┐")ку╦╬2┼L^Т)3ГH.-Еq=~VFыц+32т╠ь╕Э т*°╞lНslaBКл╙╒у╜5╤╔9С№ЪФt└Ц9I┌┌ъ╤YЦ¤╞йуSyY+йш]И╦°┴a╔ы$^ZУ||Hв·яHFОъй╨?6!#sЛN ╕g╝Щ▀; tQў┌И{CxlA&Кї5╤г2kцЧE╥u°Ў')Wd1?,▒шШLфU╨.$>Nк@ш^qпС 3й▄i^b┌fлАЮ2Н┼u°.Jf$/kў∙МSГ║Ц[ОС№─Ш ўQ╤ГЛЛ"├ж▌Лn║Т▌┌E3хOНлъШ∙d╔46ўl╨▌т╤iЩЙWЫмLтNН╕ ╞ТО9═U"1YШШЧщў°М╙^╧лRєДMєzp.?l├╜ э┼МфУN∙1▌╞|╓пж\═з█з╟ъ╢шВ╚╞╞F┘Ў▓╛╛.CC*∙в/▌zынЄ┘╧~╓а^;yimmMЎь┘cЗ┌N╙`Ч#И! И!аН°.г█<а╣n┤'ИтbMа╧─А─А─А─Аp╫Ф]цЁс├╢Н=z╘Ў5╟э ШтA,А▌иЭ╝─э А>CB@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B@B░гA<Йlъ┘ТЭ┌ЖRVЙмt░$╨Уv4ИЧ╦e)322ТСвюW▌ъt╘NvпpЪжшюJ-yBЬJЄIyk╠═┐{sА╔▐\P╟╗оF¤ч ■Ї5│╢╢&{Ўь▒Cm╥A╝Эю╠Щ3хаJеТэkвШ)ПМd╩E3░\ЮТСr╞Ё╨уE}`ШR}V═rК;ь7▐╗Ь╓╬▓~e]╛суа{ЭПwX╦m╪t╡Эcї░{~7¤:'╪╬ЭпQЩЪ▀2~ыи_╛╤║╠9▀┘Юх))O╒ЇЫv^3:√eц ]шMSt│ўkгИ·д;з> onЮ2*│жн╪╕,Ъ∙к_GuдXи√─ ·Тй╡╓▀F█vх┼Миа▄\'╦hMЧХёй9Y\YС┼╣)ўлAъД─K▓┤Рїй╤9аuз>FцЦкэп5uрзLC+╚ЛТ╔╦┌Y5Ш+H╤╠аf╤Б▐Ў{ЩёSуХж!╞ш╕Lх\Т╕·Рp▄ж■Ъ▓м[VendB╞Ъ~ZjvNЎЬk7ЭУ=чы═Цiж╔rг3ТO&%Я9XЫ;А┬ тцn)9ш=Z¤B▓zёI:f/МИ╔┬─╝L┐gZц3yI┌ && ёъз╥J {─i╛йaЧ Ї╦qUд3OdSЫ4ЧЪO}┌═'mYщ╕,ы▓в┴╓mЪ▓╡jЧh|NЮЙW╧╜5чd=mj╬Ьпk.╓l╢L3нЦЛО╔─╚ИLPЗАv№b═░/ ─чвКОшпмВ\@ї┬∙╕o2┴Vш<сw╙ ЇЭvО╫н\м┘ў┴К╣Сэt═їVC8╪╒VчdjЖОрvmЭї┤K▀о ИN╦*ЯВ╪Хt╢а^э╪╡AAAAAAAAAAdccгl√Y__Чбб!;╘\;┐╙ш?С├k╢п╣Є╤`ч ьМ^8У ╨O┌9^╫╓╓d╧Ю=vи=ё]фЁс├╢LGП╡}шВx Иг╫▌yчЭ╢ogэ▀┐▀Ў5G╟╢╙AЬаВA┼ёВx Иг╫щ ╛o▀>;┤3NЭ:╒sAЬ6т@таз}p■#╢╧_лщ¤ю;?>/ цkO╦GяZ∙╤y;vЁ=ё─R($Я╧╦▀№═▀Ш._8#ч╫фЫsF╛∙н┐УЗ╛ї╖ЄрГзхЇщo╚йSШю;▀∙ОФ╦m5° AЇ,7dы┐Н:w·аyЎ\Y▓∙gх¤+O╔W ёY]AТ ┐зф}Ё'Є╠ ¤4╖т{▀√Ю╝ ¤яЧd2)cccж╗ьл.┐№Я┐$ ъ №c∙W_>!п№єУj·хжЫ~E~с>$Й─Зф ■┐ TЮzъ)[Jo#ИАЮu╫фЧ+uюЇAr▀?╒dеd╖WI▓ЙИDR+v╕MЭn[лх┌-w%е╝Дdwf'╨ZЕьA сП?u^>vЄiЩ·ЛgфяЯn▄ e¤'чх╙_ Й№┐╛·┤ЯьП╨┘оєїй█П╩сn╫/P╔╢_Ъеh=q╫ФД┌s9█яШТхт░ЩTЯ·Vз%j╟жmУeK┘Д─ 3RЮ╡cЁЦ╙и▀╬И╗▄╝╚д▀ЄЭФл├°Сa)XжЎо+ТК$e╬Їл¤]Ю ╜сЭOСLqUж█zр└╬kv╫Фo√█╢█m°?_h√Ъ+№╧Гz┴[▀·V█╫X┐▌5E73q├їп o╒ШЄ√ >╥r╕Я¤┴▀■Tu╧╩єm6┐XЕ╧O№ЛKUў";╞З╬ Iчl╛чн∙╦BЫfЗ∙▌5хсЗЦ_■х_╢CО┐<8#? П┌!С┐x├Ыdфw>'╧='ж╙с33ЧTъ╣тК+ь\О^╝kJoёDv{Bйлщ▓:XСсv├д╖╠N╖н╒rХлkўcRШ)Kл╧▐а▓ТК╚т╕│Lє&z-╩x├аЇЖVA№КW┐┌a;╜>єДэkю{щ┌"╢╟?°┴└qo[o╞╖/И7кX Z1╒╩╓+о■Ў; ,Я╧?'_√╤╦uEo`╫┐ЇiЩ∙┘Лф_╛щ╡vL=OЎС3МцЧS<$SФ╒║мєF2ЯiYaш─ Ўo V^ЎwWй┐°вЛфп#╫и┤}Щ╓"=!▀ зWI╣ь╠sсЕ╩╞╞Єл┐·л}─√гН╕~т#ЙШ.е'wt┬О╙Э█DC╙╔┤фriЙйёЙ·v+Л272!c·h╨х║MA╠:▄▓u╕U¤ХщueЮ°╛ЩkI\ю·¤Z╣╘lЯ╖╔╔жї┌ybjf┼;▐}lv╣мYп╗нQЫС№Z▌уljEчжd▄╛╙D╟&d$┐V▌>Bт╓ДяLs┤╦жщB╣ьДфХTR╘H3оШ╔K▓╙fл╞цЄ█{э+ф7■ю.∙7┐;)п√NыoЄ^¤ПП╚ Т∙╫Єщ■г ╜·evмП╥Ъф%.CmnOK&Ды▌чХEЗ$Ю+HQП_ZР°Ф╚в)║(ЦШю5ъ╩<Ё│═Еqw▌S2wдоm╖ ═Ущxхр\О;ыйU√╪╩E╡О╩°В╠╪e╦┼ YШ┤хW╓[¤─КKо░╣Ї└<√┴▀Ь$эs│щГ ╨'t═!▌ЎwA∙-K╖їn7╨╡▄nd╕=A*жtm▓ч№g*╩vц·м■Ї┐∙╘┐ЧЯр~I▀:*у ▀╧╚Л╝∙[зKЯ∙С╝я┐|V>∙Щў╩/■ї╜r╔С╧╦~ы6;u3Э5rю╣№<)╧┌ё╡Х~эWtо,ц%SЇ AЧqd╕(лЗэШЎщ ┤IАп ╡-я╜▀4е╛=╡oBкzтM┐зYe=Я╗м╧╫ ·└ШФ∙J@_I%dэрк OЙиГх╚ё!u╨мI┬¤ыЦу-│╛|▀їщо■К╩є5Ч;Я√Ш№Ы;▐√╕ UNгvє╛ы▀м·╒╜▐6osУ·сFЇ|4ы║аY╙ь~Y│?ЇS╙mg┌И╗чeЗ╙Мвь|и╧│&{Ф5чДыsЇ+┐╜Ухw╬ЮХ╠ ╣рw w)o№HЮPйєь╦.У√~%-_▀ вцИ╚█■Є Тў ЯGхк' пТ фВЧ┐T╬OРWт▀╚Ыо╛┌)иЮ7'T·Зф╕SЭ▌╝Щкої^w╞╫фН┌╟лMy╩кdнАеaё┐ {;ф°▌ч▀!Ч^|Эyц╣фь#7╪!╟ /ЇOё■hЪт╟╝(dвX_Ы▄ЮШ·аV(кO─∙aЙE╟d"┐(+┼В─▌П╟ХYSг=.ЛuЯ4▒╡яц▒ЩоIHV█ЫЛ5=└Ы╥_YНx┐h$&├Э~┐@и▄є▓юЦ%ЮЮ ^лЭЦy¤Н|$& є *г╢P╛ї╞╦/С╦п·о№ЁW╩Є╠uЧ╚\  ▌П$Щ mЩ║m\u J& ў▀ФЯ}ЄЗЄ5э┘°%Є▀~%"/}єYy├+┌Мtц▄ЯСГ6\ШoцT╥:c╣5т║¤w├цлЮж8*ЗхУ:ыФdi!'╣t╠Y^W,ъZЇD╓.Ь^■/Ю}VN>єМщ╛ЎУЯи▒/╧>{▓╥]p┴ўфх/  y┘╦NЪю▓╦■R-╫?ё╢ў╖╘4ЫHЛiuвш&$╣йq'Ф║m╜3▐щu4hjaЪq,,U(}р╔С#Т7═Ut{kС┼EСa┐T┌┤∙Fuз╠+P┐0ЛТ╔╦┌YgRЕyl ▓d7жЄъs+ж╣Ня72*уSs╢О]пф═Л╧єбA}о4Ч+-╔Bn┌Ш╨U *ЦЪTLщ№\gWO?№зЄтя▄/Co╕D.}╫ЛхЗz▒Ь┐Є"yГКkЙтCж╗RїЧ_б№ЁГ/СЛ▐¤b·ЩKф эAy&┐lK┘вN+:ueж╬:еиLп┌e▌хmЛЕvщ /с7Sn№╘зLўоO~Rn╕рЯф7/-T║ ^■A▐ў╛_Рў┐ F╙╜ў╜?o╝^w?шГП *╚ЪOY╬'│Шnwн┐ўPЯNgт╒╢хУЕxї@╤╙T╨╘mб6╡iUЯ(=сWЗ^Э~'lв╫Гs∙сJ└пЁЦi/╓lJф~t?A_eзUи╟ж█╢╟ъCэc6Эўb╧╬'╧xЫщxtV╜`ыўйЯ╪pu;L{ЎV═W╪>n√o╖┘IлсЖ|+ЦЪTL╒XчЪ╬&5▌█Pqїtь_╔·╗│Є╙+▀%ox┼Er═▐Kх╣ с%▓qєЛх╥Ч_ Ч╛ЇyЄ/Хg?°R╣jшR╣ЄЄЛфЕ╫яУ■┼╧Ыe█RWщ╫~Eз│яО╕;c*ы№┌zЯ?_░¤ЫГЧMnщ=╤F╝█╢ГъG║эVGў ╟w8h#▐h#оШЪ▐ъїWn{f▓+mЭ=╫uU8╙▌╢╘ц╝k¤┌С√Х▀ЮЯ■ЇзЄ▌я~WЮ}dU^ё7_ТЛЯ·{yт'чd¤Зч╠uКп╣№ByїK/Фs/{╜<? я}Ч\yхХЄт┐╪)└П▐╢Mm─meЫлП▌▄r╨6■ЩЪ╔W┌x╗╙Ь6ЁbnбЬ64╕]гw}vФЯаў Л▀ЫХ_╕╕Ъ╛┐ЎЬ┌+o ╖v╚q╟╖╚╘╘╟i#▐лв╙єТ╔'; e═^a▐ ЄТЩo┬шю}─╜ў▀╤iYuЫKиоТ╜m╗¤╛їuжWцЭїЯпa∙эy╤Л^$╤hTо№╣q∙╤{│&l_ЎЄЧJь ЧШ&+пz┼Kфйk?,?№ем╝юЖЪ╩╤ж!\╙█цс·■╩6W╙шlїqм╬╬VчўLs.Dї6Cip#я·╢AфВ┌[~5▀z{·┼о тХз╙WIп0o▄┴08vЎ>т¤у▓╦.Ус ю_╩+ Z~ЁЮ▀Уч~f─t м·_№ ┐&ёЯ}╗╝ъUп▓sжW╛ЄХЄ[┐uP>ёЙ_У_√╡[ф▀■█П╦∙ЎCЄo цa∙╡№▀Ш┐ ї╠╖L °¤пЧ у °Шщ.╣фR╙╛╝ь╩ж)╗_▌cРq|ЗГж)¤б▀ЪжаЦ█\E╙═Ptн∙аёkЪвk╢Яzъ)9з╖╛ :Д┐№х/▀T[╬O▄#T:иГМ ▐}ё■@Gпє т█Н ║О О^GИ @!Иг╫щ ▐ q╨Uqа=▌ т╗Їо)@╕т@т@т@╕Xsс>т▌┼=н╤.^гшDРў.╓┌╙нЛ5 т╗┐<╪=ьktВуэ z╠─БЎt+И╙4AAAA<░ТdЙдVьpVRЙ$$[▓├@╖Х▓ТHd╒╤╝:xНlu{xM·XяqsBU'q╖█JрэDЛ@P╩NJ:╛,х┘Q;жJ╝eО╬Jy9.щ╔Э BЭXСT═уV├Хч!еЖ :┬Ти╤▒ ╔п∙╘─Э=нц5RнIu┬Я;▐mЎ╤╞k─Ё╓╥┌╫FГї╡cє1лї╬kj╗∙?^/^Л╨╧z"И/фт2T9БдAa▄S╦W3мB@м 3nM`qB▄по+єйnyJцОшёQЩЮW';[Г╕:]╗1е╡╝МLМй╣м\^ЖчэЄ╔I╖▀ФеХ╠ЗЛ ХJF╟з$╖░TsbМ┼%W(┌бТxо -╖4ш|ш.Ълб┌~їk$-qЯЪT■▄╫ОnЎaкЪ█yНшз╒ZZ▌═О6^_Umxў▀n}єЪ┌i╝апЇFёСaЙ┘▐└T и∙▄;\ZУ╝╠I╥=СлРsON▐∙b├MjЪЪpk■ЇЄ▐~3Q1═Rь°╤qЩ╩╒5O╤єRkЕЭfCs%T{Ыжш╫Ич╡`jRчls0o═urNд▌c╒ uп╧fылХ┘Ъэї┘юFxM·POёx}P▌═B╚╙═R▄пЁЭЎЭu═SК╔┼Зк5ь╜╩ДзТВ╬З▐зCxL}М,┌╫M╙f█нєё╛yMэ4^Л╨Wz"И╠Идc╡'█ХФЎ|═jоэo╩|=█┴┼[ ╛╥5_{╫5/iL7K╔╔Фчky▌l┼╗╝∙╕OХг2>5'Лv┐Щ¤эЖSKъ>GMцCи{НШчpj▄∙░ъiы╜щ5ф5УЙ·Л'Ынпвєё▐}Mэ^Л00z"Иы6йж═из,)ъ$ЭЦy╧°╔B<`эЬ:б3ТOV╦л╣На╡оuB╙ы┌t!Ъ_єТF№╛ЦпY▐ ъё╢┼o?sQЮi╢у╘▐ы&╗г│╦вvВ┘g▒t\Ц▄К.ш|шU╡пС╩sи_ёj█ЄЪ╫\р╫И╙Ю▄√·Kн4X_Ы№О┘^zMm7 ╟[Л╫"Їп╚╞╞F┘Ў▓╛╛.CCCvи╣GyDЎю▌kЗ·Ы>!╞ 3[┐G▓╛_·Сa)оNw╜╓ъЁс├rЇшQ;ДЭ┤ўї╢╜F┌тkj╗ёE╗В3╜p>дLА┴╫╬ё║╢╢&{Ўь▒CэщНЛ5√@tz^2∙ф╓Y3ЩЧ╠| а▐╢╝F┌┼k ╨╟тБEez╡╝╡┌>¤+АхUй╗є0 ╢с5╥.^SА>FB@ё]D╖%Dў╨╓эт5КN╨F╪~▌j#N`└─БЎt+И╙4AAAAwM┘E╕5 n_l┐n▌5Е ╛Лш ╬╜н`p}_'ИэщVзi В8В8В8В▌─KYI$▓R▓Г┘J═Ц]II$1▌ыЖ╖╕Нш=─*Д&▓Ю$ЇnG╕▌КдТy╔╦R.fdяvt╗b_ь.╜Q#>2%ёЕIёfё]б┤&yЙ╦P╘`╫шСж)├rp&.щу+v╪C╫Ў┌жСHJЬ9JТЭLK.ЧЦX}m║▒"й╩2IЭt╞.е<у▄U╒ФЯЁ|Ё/├0═Iь╝НЦўОПйm╡глT∙f№Ь$ї< уIy╓Nё╠zt┬О╙Э╗оV√╜иw┌ИП╬╩▓$л┘p┬j|╣,хrYКЩ╝$═ QЩЮ╧╚╚HFКj№ъ┤╖JY╙DR─.г╗┘╒hT уv▄ЄФ╠╤M9t∙Щ▒єХЛ▓0й╟7(C╙!┘LZХщhгхk╖[7;▒ЛWН╩мoC∙ОхR3╛╤cVПzz╡▓=хeўГK│}А^╒SkО╠H▐dK7▌Pєри3ЫРС╣┼J ▒п╥Т,HuЩ O9vВ▒ibkдuзkиs)6*C╫:Ыр=лb┤╥p∙┌эnK│╟ьн)O╬Йф╫к√ }езВ╕Dзe~bAО/┘сn░5╔ХЪf7d√Qє.{jиНvЦ▀ ┬c 2б/ь╘ыёнe@┐шн оDзgD╥╢MutHт╣┤╕M╟KK ТЫп]]√l{+вc2!╒eЪк+┐вI▒щU╙Д╞┤┼n╕╝┐ K╢║┌l╖╙█Z│╟<2!c╢х╔ж2¤ЎzV╧q╙vzyк┌_╠H>щ4╟Иеу▓<ы╢┘ШЦЩ)зYHэКNЫiw▌╒\hYг╢|╙Щ█6/ctvYтщШZoм┴Є·1─%s╞Mтm╘^7x╠·ё╞Э 27Х┘p_08>8 ╙Г"▓▒▒Q╢¤Бмпп╦╨╨РjюСGС╜{ў┌!ДэЁс├rЇшQ;шwA▀╫{с|╝█рЖЁ╗&┐l■;еЭуummMЎь┘cЗ┌╙Г5т└р#ИА]jERMЩZM╖MB╜┐ы╤x|з║╡ЇВ8╪uЬ╔K╩ЬЎ│Тк■жИўw=НD▀н.№я╚z╨h#╛Лш╢ДА┴Bё-╨бxRd~uZ6 ЮоН^Фё╩oЗ╕є╔q▀ёк sлa{ч7СLQ °Яи┌┤╬╓гЗ▒г║╒FЬ └Аыў ▐шN)[фM├m]@о П╦bРёН╩n─[о╟╞NъVзi шi~Б╗чюЬ╥ш╫╢═Dв╜ул╖#Nd ╤╠Б▌Й zЮ7xЗ┬u╨ЦШмЁОo°k█г2ыО╙┐МэЩou· fОК ы┴└ Иh║┘Hхю$г2>5'ЛЎ·HєЛ╓ё!Й6▀ш╫╢[js=╬ AЇж▀■эЪюо╟ЖLW?╛цо)Юf"~7$╤┐д-ї┐t▌p|г_ыnн╜ї`Рp▒&оo/╓ ▓; у@#▌║XУ └Аы█ Дд[AЬж)@zвF|▀╛}╢/╕SзN┘> ╝bзP#┤з[5т=─█9Щ┤;?Ї▐ ▒Sт@{║─iЪRO▀║(рU╬╗J7ЎKлu┤╗ <Ч┴░_сЗч┘_Р¤╥j^sм┴ т·Н╩╜]Рэ№nE╘w╝o└▌x3ю╞·║ё8└>▐нЇыЛў┬ныЎ·ьJГU#ю¤E+}/╧#Ў╞ЭЦ╒╒щЭ╜Щ7╓`gё^╕u╝шВ┴mЪR,TБкжЖ╚¤┼,е╤xW═ЇДdэЩ╠№@¤x[cТM╣у╘B∙н╙,ЫТTв~┘Тd'л?6РШЩйнбё-з┼6╪yкe╕П[нKm├Jez▌║O|▀╠╡фWvУmйЬ№Хыю╘:╛√╪(╚ё╩~ЄМп┘я№Ёb┐юB╝z╩ё░єT╦pў▐ }ЁЪ╨╠`q√Fi▐└Тy╔╘┐@╡"йXZт╦NэP1УЧдy╫l4▐езdжRл4! У╬Ыvtz╒з╗х╕д▌▀│Uы/М╗узdnS-TУuцЄ2<яY6йO QЩЮ╧╚Ин▌Z=Ёg▐жх┤╪є╝)к^¤s╣ё)▒?Я[TзЕaЙщ^├g▌╛e╖┌Пїъ╩ЭЎпoj╝Пл√йШIЫчдёsЕZь╫]Вў┬╓█░эяЕ■x═hfАЫж╪7░╥Ъф╒xsRвc22╖(+Н╞;ГъЭYMЧ9I║'3їfЯ│o┌55╔9С№ЪєFщ)Ob├2b{+ЪнsdB╞▄L::.SjэkН▐}ЫЦ╙bdT╞зЬ▓ЛЕ╕МЦ╝>√ш2▌Z│F№╩n╡;╒pWўУYЧ~NЪ=Wи┼~▌╘kТў┬█░▌яЕНЁЪ╨─р6Mё╘vtL╜┘VNfжЫХQ¤ж[РЙв{Т╦4юAъ╝!ЕтК,цЗ%УЙ╝:q w╧,!ыd√=Wv,Ўыю─{aC;■^╚k@ Г─WenD┐╣ъУPZ▄oїWР╣йqm4▐┤'пъЇЮЪ │Ь╙█Zлu║╠╢{jЕъOвA╦i@╫╛╚С#ТЯУи·зEЖл▀┼V╡:Б7█╧▓ЫЎSлrьу╩║Ъ=Wи┼~▌}x/lh[▀ с5аЙn#.▓lоxХ┘bFЄIg|,ЧхY¤6▌h╝лv║щЇЕ;╤iЩЙW╫3YИ╖Q ╘dЭ╛█ошїM9_U║ ╡▐Ў╘Ы╜>L╪│ГЬ╦WOv.▀u╫k░-j┘y▌^S?╒╒ь'o╣~W"5█╟Ю¤[ШРв╧si:ў"м▌╠{Lщ ╤╪п╗яЕ┴lы{б^sZрЧ5{Б■·rRdЮ[e╗яЕ╝ю~YhO╖~Y│gВx╗8∙4╝Є^╕Sт@{vU;З ┤з[A|p/╓zAAAAAAўАб▀3Бv9╧rqа=▌║П8AzяЩhW╨cжО-ОoЇУvОW~╨ш3q q q q q q q q q qшc=ЇР▄zынТL&хЭя|з∙лЗїx@o#И@:wюЬ№╬я№О№┴№Б╝ы]яТ'N╚╫╛Ў5єWыёz· /╝`ЧЇВ8Їбc╟О╔\ ЇG$я √х╡п}н\|ё┼цп╓у/╝ЁB∙▄ч>gЧhb%%СH─tй;n╗Х▓ТHdеdqш;?№░<Ў╪crЁрAЮя╜ў^ЩШШ0MSЇ_=м╟·╙Я6єщ∙[СT2/ЩbY╩┼МфП┤Цw2\▄ьqш3 Єб}╚Ї▀w▀}rш╨!yЇ╤Gх∙чЯ7ї░уЪЮяО;ю0¤╛JkТЧ╕ EUtZVWзEўЖоЧ╢vA·╠7╛ё y╟;▐a·ggg═▀z║iК╢o▀>∙ц7┐i·7[СT,-9ЩУдnЪr¤~╣^╫B█┌шlJ7WIй╣tuв╥|%bjкKТЭT╦ц╥Sу┘·║kU╢;┐ъR'Э▒KжL;╬mг╫WЩ╫Y_mНx]Y╬yЦI╚ж╒@ И@Я∙с(п|х+M w┐√]є╖Ю;■Є╦/ЧНН ╙┐┘и╠322ТСb╣,х;nФKэQ╗0о╞ХgeTЕ▐╔t\Цї<к[Од(QЩЮп.╗:эн╗V!=С╡АЩ_w│7к╤Х2U╖<%sжМєa nч-fЄТмiиюS╓и^ж 3v╕\ЬРЕIЪ▒ш?qш3:Ды0о]yхХцo=w╝с:М╖MьГг╢?:$qSkю╘VП╬кpюLёWZТё,яЄЦЦ¤W7НёМПОM╚╚▄вS+о∙ХeЪ╙╪Z|▌щZ¤Ь■p¤Е }F77yрБL ╘╘Ф∙[яWїW═▀╙зO╦█▀■v╙▀╣QЩ5╡╧у▓h┬пm>&╖┐╥╡°p=И }F▀хO■фOф№∙єЄK┐ЇKЄ┘╧~Vо╣ц╣швЛ╠_=|є═7Ыщz>= ЦФ▓Т2Н░u /Jf$/kn;┐ЪшшШLHZОIы║╢=WЭ╖┤┤ ╣йёjиЎ+лnшWqш3o{██dhhH╛ЁЕ/Ш┌`║┐ЄХп╚╫┐■uєWыёz║ЮO╧┐%*°J:f/ММ╔┬─╝Ш&с╤iЩЩrЪИ╘^мщ┤╧'m╙╒╣knц┤SwчНщ╢ш│▐║mЯ▓VjЧ1╖:╨З"e█╚··║ycтСGС╜{ў┌!@3э╝gъ_╠№№ч?/┼b╤▄вЁ·ыпп\ШйЫгшЪЁX,&Я·╘зLM9S╨cж╬╟dЇУvО╫╡╡5┘│gПj5т╨Зt╕■╠g>#Я°─'╠O█Їг5?mп ъa=^O'Д@я"И@╙═NtЫЁХХ∙л┐·+єWo╣9 `╟─А─А─А─Аp√Bь*w▐yзэ█Y√ўя╖}@p╝gв]AПЩ^8╢8╛╤O┌9^╖r√BВ°.r°Ёa█7╪О=j√6╙A\ <°N:uъAс=э z╠Ї┬▒┼ёН~╥╬ёJG :И7 йГа╒c$ИгЧёЮЙv=fzс╪т°F?iчxх}А-x╙UWmйшAA·╪C=$╖▐zл$УIyч;▀i■ъa=╨█т╨З╬Э;'┐є;┐#Ё яz╫╗ф─ЙЄ╡п}═№╒├z╝Ю■┬ /╪%╜ж?Гx)+ЙDVJv]┬~▀╜╢·▄ЗvьФ$ЫИH$╡bЗ█t╗WRЙ$$█┼xь╪1╣рВ фП■шПф¤я┐╝Ў╡пХЛ/╛╪№╒├z№Е^(Я√▄чь=(ш■х╜└АъЭ nNdъДщvЭЮ88txё╝ж:%ЎKа┘жэ,e'%_ЦЄьи╙бV█3:+хх╕д'╗│o~°ayь▒╟фр┴Гц╣їг╟·╙Я6єщ∙wВ;А>╤A╝ФMH$)▓\.K┘v╦▓(Dqаsй╪ВL▌╫╘▓─ EСш┤моNK╘╬╙│╢e;WфxZ$spЛ!\ ▓=г%#i9▐Е7оЕЕ∙╨З>dЗЪ╙є▌q╟vhЧшЧу└о╫A|iAЭ,Л│т=]О╬┌сЪZ╜╘цpю7▌╓ЖdS Ц┘э№ЎO═~Ї|┼юKK╬ОF?И╦P%ЙМ╩мо╢╧}йцp║╩ЧP5╟Въъk+5V$х)#u╥╗╘r}╡пaзш║▓Ї A╢aeQцF&dLя[ЮўШ7~ex╦vПyяЎ╘м█√■Х▒Й╔п∙юРmїНo|C▐ёОw╪бцЇ}є┐∙═o┌б═jЎГяcЇю▀V╧Eэє╫Є°j°Ю╥╟tAOёЕЬ70xй7PїF_vjїКЩ╝$+я╛ZУщ╣┤╞ї°┌Алf ш¤XР√mD╣8! ц+Ў┌¤[.fd─.О▐7>5'I4j^3Хc@u╦S2w$╚s▐шXё╥э▓═W\╬<кЫ╜QН░╛═пqЯ▓FГlГZr-/#c╒Z╤║ўДшЇjеL╙м─Tey№Н╖7:ЧЬ■цaЗ¤ЁЗ?ФW╛ЄХvи╣╦/┐\666ьPH'╙ё╩╖С╦ёВ>╟НЮЛ√ггуKыНу║б7┌ИП K╠Ў╓(нI~$#ю7╦╤▒ Щє4Yi6▌3>╝√GяG▒бMwъ─Ц╦йr▌■EЭ╡Ac|╤y^ы╣ў╣Н ;Би╒s▐шX▒УН╥Т,ИO╓╖∙5юSVРmЁS ╕╝╡в╔9С№ЪФВ<■f█лЧ.╟ю┬uBЗp╞}EЗ$nЎеSУl╛Йl┤=НЎЗw?=╛┤^=~`ЇDПчdЙъИpйУY╤н!2▀$ }!ayYж╝еСbбuєг^8V╢║ :Д{█╨o╫╖=z ┼Зv╝m▓nnЄ└╪бцNЯ>-o√█эP╜QЩ5√o\M0╡M;¤¤Аў;=в'В°┴МH:цmk)▓ТR├║╢&W╜°й┤┤ ╣йёъfлщжn?VШё╒If :╜ш o ╕й9lЁ═У╫ш╕L5√`▄шXёКО╔D╨Л[╛╞}╩ ▓ Кi&▓░╘╕v┌m?оTОэV╟|Лэ5═aЖ[юх-ЫШШР?∙У?Сєч╧█1■Їt=ЯЮ▀Ч·@Т2 дu /Jf$/k╥` x.6=їZэ_нGОшЖЮт║нжi█ч~Uи║дш7Sur(f$Яt╞┼t[╞Ъ█Р╡ЪО`jўгщ╠ЕNj╝n;s╞Mт█SkИоШ╚'л╧зо¤ЭrЙ┌ч▄|moз8+^QЩЮпЭ╟╜╪n│VпaЯ▓VВlГ╥ьCEtZfтiЙ┘хл╟vлc╛┘ЎЦdi!'q  ^╢╒█▐Ў6Т/|с жF╫Ппзы∙Ї№╛T(U╓ю╟Ш,L╠╦t┤╤■m¤\┤~n╡╡9~а "■ят мппЫ7Ў yф┘╗wпB╪>,GП╡CГй╒c╝є╬;═╫·^o║ъ*█╫ЩяЬ=k√зNЭТ¤√ў█б>зяя┐8╛ї√pЗD▀$VШщ╬Ўы}udXК[╕m^;яЩ·3? ∙╧K▒X4╖(╝■·ы+fъц(║&<Л╔з>ї)╣швЛьR4AПЩ^8У ╨O┌9^╫╓╓d╧Ю=vи=╜q▒&АЮ▒т╣EЬ╣┐ЯЖp-:=/¤═@¤Ек█MЗЁd^2Б╛u╪:\ц3ЯСO|тцзэ?·╤ПЪЯ╢╫ї░пз┬аw─╘и▄me .bЛ╩Їкz;¤a┬\╗*╙▌Jс║┘╔g?√YYYYС┐·л┐2їp├ц(АЮA╟озЫЦlешm─w▌~z7h╖Н°vи6тш*▐3╤оа╟L/[▀ш'эп[i#N╟овГx7─╤ ▐3╤оа╟L/[▀ш'эпq╝gв]AПЩ^8╢8╛╤O┌9^╣k ╨gт@т@т@т@т╨╟zш!╣ї╓[%ЩL╩;▀∙NєWыёА▐╞э аG┤єЮyю▄9∙ с?H▒XФ}шCr▌u╫╔W\!O<ёД<°рГЄ'Є'Л┼фSЯ·Ф\t╤Ev) Ъа╟L/ЬП;┌Ж▀■m█╙B╨∙АА┌9^╣}!ь2╟ОУ .╕@■шП■H▐ ■ў╦k_√Z╣°тЛ═_=м╟_xсЕЄ╣╧}╬.@)+ЙDVJv░mAЧ▀ъz`@─а╧<№Ё├Є╪cП╔┴Г%Й╪▒╡Ї°O·╙f>=CABq╪┴Щр╛{щЪnOў┴k╓LW?шWqш3 ж9Jz╛;ю╕├u(:-лл╙╡Г]Ў·1└V$╒ЇCЮЪо>╘ъ╢СHJ ╣НяT╖╓Г^CА>єНo|C▐ёОw╪бцЎэ█'▀№ц7эP╜Тd'╙Т╦е%жNЇЙ▀7cЧRюЙ?")}цwkдэ▀мЩnCБчЕШ*KП█─&TЩ'Э▒Ы╓гy╦єогЦъ╩r╖п2.!Yк╬╥ч?b√j√;U╩&╘ёТФ9;ьg%ХY.K╣\Цb&/I{а6H═ёь╪Сїа/─а╧№ЁЗ?ФW╛ЄХvи╣╦/┐\666ьP╜иL╧gdd$#Euв_=Ё╩ у╬Й┐╝<%sGъj +╙geTЗb╛у6(ФЛк,;[Х √ЙjШ╨▌ьНj┤яzj╦█<|╩╒╦d╞ЧЛ▓0Y╖═ш{~┴{лa<:╜┌рШuн╚т▄ФМП:C╤▒ ╔пЩу╘╝╥╤З┬╓ГБAА>гC╕уAшо├x`*Ф┤'~Й o)▐ще5╔{З¤ФЦdA|цё[O]y&x╠-к8b∙ХеЧС9I║сG╫╩ч R┤У1юЪ№▓щ\ї├]Т╕▀▒U┐M [оГД }F77yрБьPsзOЯЦ╖┐¤эvh@йЁоkЇ▌Zrз╢ш▓ж =Mк╠xз9ШNd ╤╠Б▌Й }fbb┬▄'№№∙єvМ?=]╧зчoк╙Z6SC╖ K╢╩п┤┤░╣НxtL&$-╟Г4m5хUч5хMНWC╡_Yu╦;╞|c3,1;Xс▀ЁCси╠║уts╧|л╙o0sTYA·╠█▐Ў6є├j_°┬╠Й▄Ппзы∙Ї№ Eзef╩й┼s/╓ NЕЛх╕дcN═▐d!ю╙▐╓iЗЮO┌┌@╒╣knж╩S!┼Э7ЦО╦Єм╖n█змХ┌eL╫Ї.@жН╖{wТQWпНEя├°Р: Мя°CaЫыq1 °eMшэ╝g╛Ё┬ Є∙╧╛Є╦Ъ╫_}х┬L▌Е_╓▄В3╜p>▐Оmp/╨▄ОЎс·о)▒tї;Ьйх▓╠╞T7э╝▌Ъl▌д─╜│╩Ф,╖пГ╝nzb╞+║ц╗■╓ЫzЮIС∙Ъёmо;оЭуu+┐мIА╤╔{цC=dю+оoQш^Шй█Дыц(Mk┬1В3╜p>▐Оm╪╬ 4╙╬ёJА└{&┌ЇШщЕcЛу¤дЭуu+AЬ6т@т@т@т@т@т@т@т@т@°eMш·=h┐м l┐n¤▓&AАG┌╙н N╙ q q q q q ▄╛z─Эw▐i√АрЎя▀o√уЎЕ@{║u√BВ8Ї─ўэ█gЗА╓NЭ:Ev@╖В8MSА─А─А─А─┤V╩J"СХТl█VЧяXI▓ЙИDR+vx┤√XVRЙ$$█щГ┐ Р\u╒Uж;t┐╖▌ Ть▀ %y▄║Г  *#*Д┌.бУ`hБ╕M█┤Эеьдду╦RЮ╡cB0:+хх╕д';y<ў╦бgф╢Уgхь╔█ф╠я╖Цw2\▄`[─БХК-╚D▒,х▓юЦ%^(КDзeuuZвvЮЮ╡-█╣"╟╙"ЩГ!Жp╫шA╔HZО╖[1 °crFоХkоV¤WLю╝єcв{C╫K█2`╛їнo┘╛═ЪM╨Ят└└К╦P%╔О╩моvkЪэ▀lкZc^i╜QWУйпЩоЩ▐и╔┼Кд"_6═`Ь╫┌yO▐vF╘4Tў)ы&╜LQ~▌Я=∙╣√У4c тe/{Щ▄v█mrя╜ў╓Дq▌п╟щizГГ  и╤Y'Ц╟Э┌·@>ТСJлН╪░╕KkТўОпззЛ °║S┴3Ч+HM╝,-╔В°Ф`}╤▒ Щ[м╓$√Хdъ5*з╤║▌Ъt╜Э▐~=Mє╘┤ЫхЇ·[=╜|~m╙ЖОиАЭ║╔Ў_}Н\kj═Э┌ъЫОйpюLёў°Wхnё,яЄЦyMLо╫u╙╧°л▀¤╣■╦ў8╡тЪ_Yж9Нн┼╫Эо╒?н? И·0NAt·b┴Є▓LyCa#┼ВфloC*lu└пt│т═╖]╤ █╨.╜oуC;╨> &9fjЯoЦ{L°╡═G┬ф╓тW║P├╞ с└`#И*сн7╡╡├│Г НО╦TnAЦU█FЗ$Юkq╤atL&В^ШXW^iiArSу╒PэWVРmPLSРЕ%з:@9Ы╓Pe╣хЩж2├-ЯБЎ=■%9daы@~Rn╗■М<ц╢ёлЙ╛·▌Є╣]fГдu]█~║:яу_╜[NфцjиЎ+лnt╞ уДp`░─Б5СO:M7LєНЩШrТQЩ╒╖┌ЛyЪ}╪)5╜ШС|╥N╫▌ж█ Fez╛vўb══j╦Леу▓\sлAЯ▓VВlГRєбвu9Ы╫▌Д ▄1[NlaBКf╣fхХdi!'ёъ╒│█G_╣¤F{aфНrў╛ жI°╒У_ И╙DдЎbMз¤°Щ╢щИъ▄Л57s┌й╗є▐и█вє╓m√Фuэ2жуVЗ╤Ь ╢╚╞╞F┘Ў▓╛╛.CCCvи╣GyDЎю▌kЗЇ¤c4Лус▐З{ J┘Д─ 3сo┐▐ПGЖе╪тЦМw▐yзь█╖╧нЭ:uJЎя▀oЗыЕє1Щ¤дЭуummMЎь┘cЗ┌CН8А+Ю[ FЇНF·4Дk╤щy╔шoъ/Tэ&┬Уy╔·F░Ы─╘и▄mе_.Вl**╙лъqД∙a┬\,╗*╙дp@В8┌И@П╨m─Бv╤F╪~▌j#N`└─БЎt+И╙4AAAAAA┴О▀GДпю#ЇУn▄G|GГ80╚°Aа╧─А─А─А─А─Аьшэ їэ\ ╖Дш?={q╜a╠╓╪O¤Й ▐чЇ~zпW┌Cяs[y╤}№▓п¤╡є┌Онф8.╓ьSў ├KхO┐L■Я╝L■·ЧI■GЧIщз╙¤wФ.│sа─√╘?=}Б|ЇgE~хн"7яI\)r¤ыЭNўk┐uG╔щ@╧!Иўйsч╬╔∙▓4ь┤┘йы у=К ▐зZёя}я{ТЪ;mц%МЇВxЯj─Н╛^~ __o║╖╝х-v)ЇК▐ тем$"ЙдVь╟JJНKdеRз╗ТТИЮ╧эъц7tY▐ev╘Кд║╢. К▐кЩТй№╔║йVъ#∙й &$ТY.Чеl╗eYTQ8f{╘═┘сnzсЕф▄y ╘щy}=·E╣∙Є╦хЄЇ╜vДу▐┤wєхQ;мF╚хz>╖лЫ▀╨eyЧ┘ юЎ┌юц/юш┌¤╢Пф^I╖┌╨zмi╩░МOИ,,9I|хxZтjДQТеСLqVFэmt╢vX╧ЧЭLK.ЧЦX$"Йь}ТMDдZq╛"йHB▓ўщZєФд╘4╖v╜2П[;o:5oГъюшЇкФЛЩ╩ЕnzЎ┘gх╣sи╙є6t├9P8.ХLл┬тё┬ rCeЁf╣№├"_┘╪Р █}EЦT▄ ┴#gd°+юv|EЖ}╝║▌;йЯЎ╤╜KzCьv<(╟╝█ zJ╧╡ПM╧H<}\VLmxFО┘ е%Y╚┼e(jЗК╩Ї╝ ╟#)Ц╦▓:¤ЩЮЩТ╣EЫ▓WenjFжпR¤╣╝ ╧█┌їe5O2еb║ ъ▒В╠╕╡ю┼ YШь╜ж'╧? ╝╝PЦ@ЭЮ╖▒keьГ"w¤ЩУ╓ю¤╜C2№A5┬xT■ь.Сcfф╜vМЎ▐Lэ░ЮяЛ?$2!cю Ї<ТЧ╡√╓╘ j¤nН╕┌О\о E;█ ┌{╦A>Ї{rпйщ=&┐ё>;с╤?У╗╣7╦-xLn╕сШ<╕▒!ў▄rг▄rЁАЬX▓ Є▐%9qра▄вяo■@Aо¤C[c√5╧З╙*Вкz▌9hkУ7№а▄їёM8Ї╢╔х}] Щ¤╡ПTш7A~I╞6ъ?А^╤ГA\eтГ║╣╟Ф╕y┘ИI<╖ ╢╒JЫЬp┐╕▓"Лsuх·▒╡щn;Їr╣╛∙K°╢еНx┼{eь└ ∙ЁuЗd°р-*2z▄pнь╡╜myяШ8с4╧╕wщД│qЁOx╓єHAJ'Kъ╡~╖╢Wm╟ЬСGьlЫщPкb°╓mыОъз}дB =:░П╔RгЪs║Ю тЭЦ╒MсwTT>ЧtL7йZI╒W╘╒bыpЯO&%Я9шкuУ]C■°╙r▄╖╨▐aВxYэ]ы оЄ▐oУTфs│аёци ?pЧ╪mrВы╥╜ў╩╥Й║r¤╪Ъbзm│ю╒фъ&╟х┌яС[║Ч┬Н■┘G.]■rжё'в▐ т шЛ#ЛЩ|╡┘ИъТ2╛9Xл ?гЫ╖ищ ўJ╦шШLММ╚Dе-Кb/ш4eщ╗▒мъж0г2[╘б╜║ОЪ█'zШ╗жШж+N9~wR▄)█[#о╝∙╣gS░{пиь)Зо╙M#кюM╫W╘╒╨ърZ°ЁЗеpь7№гnОбko╘aЎР№ЮoбжНt8!▄шГ}Їш╙ЮpЁoРk;ко;нwВ╕о7A╕N▌xsзo│СY F#г│╬Ї╒i╗ф╩qIЛзM╕V╙┼Sojф▌ёкє█.е~[l╩Оxю╣чф▓tz▐N╜∙Ц{ф┴cЕjУ╒}X╞6ЗFRъжjzх╢Вo~Я|ЁЖфГ▐Ж▄ЎbESЦ╛╙╚=║Щ╟{%єад╒u╘▄╨zЇ╧юТ╘┐C╫Uчы╩- [ше}ЇfuаVўПY8Ь-ае╚╞╞F┘Ў▓╛╛.CCCvи╣╡╡╡└єю4¤├@ЛуЮ░мoQ8)2▀ dwУ▐O{Ўь▒C┴№╗п<*хэvи╣у ╫7хs!Нщ{k┐V4AR╤5┌∙Cw8·°mw_яи┌Gэ╝╢АгУчълж)[бk╚kjм╒└ўЙ╫┐\фў■Їa∙у{┐-wцС{┐∙]Y-|╧t║_П╙╙Ї╖Щ╔eЧ]f■╢▓Хж)q@ёЖъnqЪж! И! И! И! И! И! И! И! И! И! И! И!шЯ ^╩J"СHj┼Оpмд╘╕DVJvXНРИЮ╧эъц7tY▐ev╘Кд║╢.ЇЛ■кЩТй№╔║йVъ#∙й &$ТY.Чеl╗eYTQ8f{╘═┘a└╒gMSЖe|Bda╔Iт+╟╙ЯP#МТ,-ИdК│2j╟hг│╡├z╛ьdZr╣┤─"IdяУl""╒КєIEТ╜O╫Ъз$еж╣╡ыХy▄┌y╙йyTwGзWе\╠T>(о╚╞╞F┘Ў▓╛╛.CCCvи╣╡╡╡└є>Є╚#╢╧▀▐Є▌ТШЩ_ТуСE/╦=:6!#sЛf=Ж_Yж9Нн┼╫ЭоA╩щУ%lС▀√]гўЬFяOН▐╙╝·т=└А#И75*│жжd\═╡¤к3LъDcjё+]ЛHГў╗A╧с=@Ит═Ф▓Т2 ї к(ЩС╝м╣▀Y·╒ЪD╟dB╥r+г:лЁ_╢єUYv╢*ЎIw╒═▐иF√оз╢╝b&/╔JB╫|╩╒╦d╞ЧЛ▓0I3А~A╙Ч хGmlxs░ЎN/нI▐;ьз┤$ т3П▀zъ╩ЛОM╚╚▄вS+о∙ХеЧС9I║5т║V>WРвЭ А▐Fяg*╝ы}╖Ц▄йн@?╪▌A╝╙фшР─s ▓d█БФЦ6╖ПО╔Ддх╕╖ЕI#ж╝ъ╝ж╝йёjиЎ+лnЇЧ▌─г╙23х4эp/╓ nTfЧуТО9═B& qЯ6тN;Ї|╥╜Ш▓z▒цfк╝bu▐X:.╦│▐║mЯ▓VjЧ1╖:ш}∙ўэ№bf╗є`pЇЄO▄ўeoA`w"И╨╬NВшх ╬]SА─А─А─А─А─А─А─А─АЇ╘/k█НЯ╕zX╖Г8MSА─А─А─А─А─А─А─А─А─А─А─А─АD666╩╢?Рїїu▓C═ннн┘>аўэ┘│╟№╜ь▓╦╠▀Vt▐uЧi╫Оё'Я|╥Ў¤гяГ80╚╢─i#ДА ДА ДА ДА ДА╗жB9ьў&╩G9яшM▄╛Б>|╪Ў╗├╤гGmzA╜Жє$\A╧qж▀`&╪-8▐{A╜Жў hэ▄Gш3q q q ╗<ИЧ$ЫИH$╡bЗ█T╩J"СUе┤й╒rэЦ╗ТТH$!┘╢7a┘╤╗ж$"╔Щ╛)Y.╧╩ищўбГчд╚№ъ┤Dэиn(e+╠Hy╢сЦ9╝█╫и▀╬И╗▄╝╚д▀ЄЭФл├°Сa)╢XжЎ*рIEТ2gЗDF$S\Хщn> └jv╒√╖┐¤m█З0  ч m_sЕ ∙ЬэCз▐·╓╖┌╛pЇz&pqЮД╓н╗жььэ Г╛ШBy╤щ╫nўх▌╓N╖╗╒rХлkўcRШ)K│╧Ы▀`e╝┘"╨╟Zё+^¤j;Д░╝>єДэkю{щ+l:ё─~zяэLP┼yZ╖ВxўЪжшV"+┘TD"ъS▒юЬ!*@Nж%ЧKKLНKшЎz^;OMУЛЪёкsЫo°═_│╛Фz)╒YYФ╣С ╙пr;o╡,w~nUez▌╢Ю°╛Щki╙cкеk▐7m│╢i╜vЮШZЗЩAёОo·╪в261"∙╡JщЇжЪєШ╙ЕЪ АРt╖Н╕za╞╦R.лnyJцОшMTжч322ТСв┐:]ФTм 3z▌'daR╧з>ХкА_v╟лeLбz╝▀№▐їm■$[Z╦╦╚─Шєi;:$ё\AКz№╥В─зD═л┤(ЦШю5ъ╢ї└k<&їтЯL╟e┘n▀r▄YOнэyl╤б╕ф ЫKonNТЎ ╦╝с╗Ио%д ╖ ╩oY║р]OъбL╨чIь ▌4еЭю╠Щ3ъ╪иШ)лSY╜Шj√╡F╙t┐Иn.ущж╩╦НЦ:Эbfд<ТйN]Ю)ы┴х)╗ьФК╬▐┐Н╢╒oЫьаc╣мr╜│MvLe╛·┐vr═Їv[гё╖▐zлэлз╖╙┘└аh|╝гW╚нgu▐sT¤∙к╤4▌ф<шЭ┐ ╬У╨┌9шlьЧЩГt╜y╫√IX=6█йOп┼B╡╣F=┐∙эдаb├"ЕтК,цЗ%УЙ№вмиu╞╟╖┌*lTf═6Н╦в∙D▌цWbэ<6╜ПтCN-█b2м▐╜ш)!dЬ'▒¤z'И█ж!N3С┤пOлгу2Х[Ре·oЕ═▀Вi╞▒░TiFЫ9rDЄж╣Кno-▓╕(2\mЧRхnkемд╠WY:Р%3ТЧ╡│╬д єкПM7П1o0m>6╙▄╞wГXIU█┤ЧЦd!Чб0оМ└л╦Ща!╬У╪a;─·вCїВИ╡кОN╦╠Ф╙+СН╔l1#∙д╙лzЖ ▓╦qI╟ь╕╩Нj╝я№-╘┐И╒ЛW8aо▐tчЄ├╬┼Ь^▐m╡k6е J╟ь╢┼dab^жп▓╙*j█d!n█║╡є╪J▓┤РУx;я▒сj┘жн▌v╒Pлз3A#Ь'▒├vЎЎЕ;I▀7{q╝ї=└Ы|ё~╨╤}─Б┴╞ё▐√"З╫l_sхг=p▐Aя┌ЖLрт}┌р▌╛pмxnsIК,oёЭЮЧL>┘∙/kЎ ¤Ф╠Kf>Ь{о╨m█Э А0ЇUЭ▌юЛ/в2╜к╩ъўяшм┌№╥`ў╪■Lt__q`PЇoqtD╖yv┌zЎ6┌Иг╫pЮДлm─ т@Зv═┼Ъ└а И! И! И! И! И! И! И! И! И! И!ИlllФm ыыы╢└Ю={l_{┌т╢Ож)@т@т@т@т@т@т@╕П8hЧю{╢Нl№╗╫█>ў▌wЯэC#яy╧{l_pq0╨тн т√Ўэ│Cиwъ╘)В8@=7ИЯ╜х%ц/ко·т3ц/A|k: т┤B@B@B@└р{№K▓ Чфq;╪ т;рё/эЧло║к╥э ТНАїБЁ■Cf·б√эЁnawе█u;А ░эt┐ёю╚╔│gхмщN╚╡╖▀X у.╩ЬС█NЮХc7┘q╗А∙Рr@фDe ЬХrПь╢(N╪Vў╦ьэ"╖}сcr╡#rУ;yЫ╚▌_н╓Ды~у▌ЄБУw╩╟к3юП╦WяV√чф1╡Wкn:ц ╫~У░_╠g√-┬ЧUk╨лшў╦б╩№v╝Ю┐2╬Ц╤Гт█щё╟ф╠їРw╫ЗылпСkOх13p╖|rWЖpхёп╩▌зпХk<юл?vgеЦ№ьЙkхЎYЫ╕O▀.┼Ы▌ёС/ ╛n▐є╕|i┐йZп,sь&╠o,╩п╗eЬ№А▄¤╔▐jю"Иt█iС▄жBfП─w}Lо▒╜Ыxk│|Yф╠c╬>║■6I╣Uш╫─фz¤WЗzёМ╫Ї!∙▓p╦╕ёv9]∙╘[т█╔╘|▀-_нO╪жж▄P]c■▒crЄw╦Н╗э"┼F√Gл4╫qk│osw╗Thп╢╧╫]m3Ш^A╪V7Iъ6йлэ╓═%n∙└╗=э╞u3М/╚mgl╛Иsа┘¤sубЪЛ3я?dЗ=═z ъ▌·╦Г╞о~╖|@n╖їКaВ~▌╕E╪f║Э│йэvЫG8╖С;75┐Z>vgГ;к 0│n;Sm>в║r│▄tї╟ф╫п╜╜▓▀>Y╝╢EН╕┌_╕M╬иЦsш~ч┬Xя╕лzь■со╚╞╞F┘Ў Ь╦?ў=єўь-/1Qu╒Я17■▌ы═▀Fю╗я>┘╖oЯB╜SзN╔{▐є;5т@т@т@h#Ъ█FН╤F|k:m#NН ▐ZР Оцт@ЯаН8В8В8В8В8В8В8В8В8В8В8В8╨u" !n╪░хibЛIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/images/screenshots/editgroupbox.png0000644000175100001770000006070614654363416022365 0ustar00runnerdockerЙPNG  IHDRЄЧ╤J┤8sRGBо╬щgAMA▒П №a pHYs├├╟oиda[IDATx^э▌|ї}ч ╖l сП&$MR@╚Vвє5=сS{rJ8╔▒E╦нh╧ЧВJВm%┼▐цWsшWО*.nУ5∙¤N╢9и ╘╫в╓r)▓kщЕ─jк╪jI\QYB@BЪ╚Б└╪·═wц;л┘╒о┤л▌Хvv_O2╤╠wfgw=│╗я∙юgf╦&&&& T▄ ■∙ч█IЕю─ЙZd╟ДA!В<By ДЄ@х$╚ONNъЭw▐╤╧~Ў3█ Яц|∙╔ЗЮ╓▐╟■Y?{эUЭ╗°¤╫ №Ї╔ ┤RgЬqЖ▐¤юw█ефZVЧЯ╝я╨а╛t├пкьф╧▄0 @wЯр№у█%ф╦ЬГ№ф[?╒╓?¤ -zчg*;uRяЬ:хЎ╞┐∙цЫv 3eeei Е╞<жгGП┌йщ╠╝\?ю9∙┐ ╙╧щы_▌жн┐∙ЯЭйIп╤ёСП|$уЪ∙ГЫЭ ▓∙аЭ╩╓Am.[нЭг╬шшNнЎ╟│Х╦uд└┐└2Yrжб9rD iїп$ єж═╠3╦фR┌5Є_√╞3║пў)M╛∙Ъ╜¤║Э№й;Г&O╗є-*╙▀э∙Cщхд╖OJяZпe yЎЪyРп╥JSїЗ╡н╥╢╧Щ дwкz$█uхj=aQj╧"╙k=[XOgЩЕрЎ8№M]q┼)█r!г∙? √!Э~ызNА├-з1Гт═?ц'~щг╥щWдУoKo╜&¤°б┤jцG{Цо▌оэ╫Jў╨ Аp2A▌v┐g>_!▐ЧvРуїЯ─|Mх√┤ўF╘√┐ ╚▌√З║∙wоТ&v9!▐ °o╜щ▄`▄эНЯ╣╝fTnОoмTeгЫфЭЯ-ї8hJZ╝Zи╒▒║ПЩц┘хb│F╡sїTmХ_═уЦЎ╪6п─╟▄nЭЎи_Сх┴╢└║▄RЫй█MU┘хvnО═K■╪qы╪ь▄╥{|▒х:ыX╜3Ўo2¤q║н╙■-╠м╤Элc╦f■яцШЎ╪0Ы`Ш╧gИ7╥ЄчЭq╩эН/sВ№═-Я╥EgvIп|╬>ы / ЮЇ╙▓A▐NЭбSзN╣A0е╤=мkхфx9I▐{XёЭЄNР╛Sz└╘CНDе╚їБP>╙╝dLH^оЗпЙ╒Wэ^ы═Y╗█п╣:аM{юt╓│V╗═╕ъq┌¤cЬ@╝<вХьэЬ√?╢.x└р<╢бї▐╝Ы╘┘С$Ыu8╧▐м▀]N║sз┤эЁнtЧwцпУ▐&┐╥e·у┤3В ╬¤эYWжыї@К√Oч▀-┘cЫё ┐So╢сФVР7anуп■;-9¤ЦЭzK╜ч<'м╦╠ё8}┌ыЕўC╝йС▀oыеЧ^╥9чЬу-УДWV╙hГjеLз|dG0Є:A·d+╖щ╢M¤qg8fЪЧД9hш▀д█ТАЫЮowG6╜Ёi=оcN╠_яч√dПm╗Ф░▐YЄШО'ц`wNи6=■ц╛╫эQ┐╗ч ┬ ╬ыЬ╟в╗ЭйАФП3Ёoс▐_Э√-Зk┌¤зёяЦЄ▒А0ё:gР;┴rЪ`ЩM>дфM╧·зы>мў№╛zя¤C█j7·┘ЯСЦ¤ЩЇ┴┐tВсг╥┐\я№тУzёМF╜Ё┬ ║°тЛ╜хж9иС~їGЦ█pZжх╬┤ЎьЇНъ°1;:═LєfaJHLfvwфEыl√╝╪dя╫╙z■ЄЄ8g·w╦р▒`ZM|b═|оефMн√Т%K▄WЯ}ЎY█j8a~тK╧оЧЮоw[>ьxуг¤и╬<єLw|ЪГ√╡з.кС`XtъэП%∙■й`mПzм|╞yIШ╥g▌╙JDFЖ╘_Wнхf▄]П█:│╩Zй└уtBЎЭ{f╣ Dv╙KVlIНйgYиMЯ╦уL*Н╖ФП ╔д:▒5Яa>эy╚▀ ■ўы▓╦.│-ЛеУ'╜RЪў¤▒[Nczю/╝ЁB]zщеnO╝  й▄┐Gu▒▓Я)пй╙ЮXBо╙╩бы╜{╖&=Xj2╙╝d*m¤∙╘7ю∙вk╖+кИЦЫ6s╠XOўZн▀d╦Kж╬d╡╓j╖[янз╠н'Яэ■yыpю Ўx╩6я╘╬╒ыt,║▌Щ╗V█г╟┤╬?┘5ху╠T: n╔[т┐@~─ЄGКбнZ╡jZИў∙a▐,УKi_G>╚ЇЮЧ¤мGzхaщНЧЬя№Г^°Ы·Q┘ўЗа>°┴┌%│qPйпk>╙<д╞┐@1╚ш:ЄAжч¤зУWщ╘√ LZqPя|°╜╕шУ·┴~аў╛ў╜v)∙2з я╫╠┐°тЛn═№ў┐ }╖Ч■~сR╫─╚Щ9Х╓X8s.н░░Є@фА"╚!DРBИ ДA!ў:Єv@H╨#ДA!В<By Д2:┘ї№є╧╖crэ─ЙvlvЇ╚!DРBИ ДA!В<B9Є╡╣мLeБaї╬QitзVЧнЦїЦё╟єe·у╪|╨╬Кx,qП1╫ВПg│3d'=ЄuКОLjr╥oлФ*╖щЁфaЩ╤∙ 8vп╡═й─=╞l<оj√Xl┌г;є{А@i═|X╗-v│╝║╬▓0OA~Жn╖дe╦Nў╖zчq█h°П╤№]з=ъWd╣│\ЄЪЬ╣9╕Y╦╛V╠яW(ByЄ6█░ ┴▄_D+xўўАv{в╡┌э<РM~i╬м59i2wVkфЁ6уРн╝╫╚╧ШГGПыX0pп█г■б;3[┴╟▒█ЙчsuQm╖Пйr█mN`Я'#Cъ_╣ВАЬ(А∙M:`C┐;фк╝╨м▌]╝╧ єnaГ|х н╘<^┼┼▄_D;l╣╧ш╬;УФ╓фЙ[Ы╧е'Р ▄#┐V╗GвRdyмж>w'Ч╞╫ъ╗╫│7ўw`УЎмє┌о╫╡)Jk╓j¤ж<Ьь фH┘───дЯ╒∙чЯo╟ф┌Й'ь╪ь аF@жЄ@фА"╚!Ф╤╔оЛС√А|9}·┤Ы]FA@aаЛбтЄcjhш╨ШЭМI╒ЮОlnЛ∙Сэ6JчЎьЕЛэxx-%+OA~L х*OчE|s█E>o▒╝YЪчQюьvhш░╧иXЮ_√┴tl ьу┐Sйт╡Р^ └МЄф╟й[-╬▌:Ф╔лпвU╜╜нк░Ул>Ej║╒48бЙ 3tйz°;┼Пэxx-╚п╝∙▒C▌R╙V5Vи;.╔;ojБЮЙH▀Ш:╢┤i`аM5жзт╬;╜#o╖#т,э3╖єж╟:b╖//oР▀╣! ыє-└┤v+оW$╔|#╕LНє╕b═╔ю3с▒;Нй[\╗▀│РЇёL_gxUл2vDVпh┤▐∙Ыц┐ЩэБщИ°эf_qW я6█6К[П▌ЯR▄>N&ў┴~Р█?~╣Д█¤Ў╟^ ё╦%▄Ю╫РХМоZ3>>n╟Т[╢lЩє ╬ пaЛ┤лWн╧DT╛гJГn/╗iп╤ЁЎ ╣яc>єтuwЦ М?у╝сЇ4┌e√ЬїЇ4j"юЖ┐}ыи▄^ч>Э7Lє&Q╙▌д┴]╥w}Х║з╝GНQчm╘╝OwEЖ9XhЦ║ь}&]╞|,йЦ1т█░╢█√юЛDдhгzR=ЮыЬэ▀>_╝m/Э¤а╧┘~═Э╬DKW№vK√▀мM╒■╢0эю~d╢c█(q=imcЎГ ╢ ,ўaJ√9Еў}╝f╜│P┌╧Й╫0Уdя7йЬa жm╓Х╗e5M┌e^qНji▐бCcнjХio╫.ўU:╗·╞5ўЇ9o їъыR√╓и7├╝иЭ7дXяAmХw4_█д5ЎU^▒жI╡m├zFU^├╪иЖ╘й╬rєnъkqц;╦z▐2╡)_╥√мЯ■FХl╣КJUлM═хц¤╨yУК:╧├Yn╟lП'A&5WfzгЬэё╘G'Ь7vg─╝yЫГ─1_╩э┘он■тЧWй╓№═xЩ┐Бї╠t{√A █?AЙmLс╡РА╫Р7Щдц╝┤╞Ф╒°_ГЩ7нNч%_^Уж·нjъQЯє_╧Р щюЫBа▐p░▌{SKЗєЖ4ш╓(·Г=ЄЯM║ўЩr╣zE▌√3=ц▀─~н9╫╟6їQч╣uйе╙l╦┘l╧а\нg&ьs├Ў/ээП)╝x-yРу ?жC▌jщ ╝╗Z4╨}╚9 _г&ч(№Юiя`О╙{ЮиBkЪЖ╘щ╤P╙ЪйгЄ@╧╗{╨рН╞q█[з▐LАspСЇ╛}ю2S'ч╞н{ж√ >Ўd╦9of╖о╧╝y к╜vHгЪхё$¤ўє+фt╕=;U║▄N╬·oЦ╩\╖Сoж█√╪▓╟Ў/ээП)╝x-yЦ█ яЦ╒╛╛3ъ╒т╛TиuW╗ЖЪ═╕7╕яoн┌▐╥йfg║aя╦▐m,S"3╘)m7ЕяnГ│l╡▀█_о-├╒S=╬ЫА▀ю╓╟╟}uщ╝a ╞▀ўЇKc:╦tUлн&a▌3▌gЁ▒ZУ|9ч═╨Yй╜▀u7эRk┼ П'╕╬░Ю╪c┌ЖЪзЮЫщХ┘eы╙∙7Ki█(NК█╟a?╚█┐┤╖?жЁZр╡фY╞'╗RЧV:RmoЎГ╥└Ў<╝╠ЧL▀WЄsyyEРBИ ДA!В<By ДЄ@фА"╚!DРBИ ДP┘───дЯ╒°°╕Ц-[fзPьRmoЎГ╥PJ█┐№ю┌1ФЪЙ[▐o╟Rу╜└|╔Ї}Е ПФ°Ё*mе┤¤MРЯl ░ЭBй(k√WВ<АВТщ√ е5к▒54th╠N@AАB╨Qyy∙╘щ│32@ЁJ AЄимм╠ОйНu4и╝YъЪШ╨Д║╘гМг|Eлz{[Ua'7В< jLЗ║еЎ┴иъmЛQЭЪ>ЩъйПu╘Ы▐ўX~─ ¤q=Є}К─ц██┼▌жA╢ы▐=РЁ█щ╤BГУ]С'xХ╢y▌■&\lСv-POтЬOv▌й╒╫K▐жJ█Ф╚Ї╚ONж¤6Лy4_'╗Ъхf▓ь']jи╓ЎЙ° я2пНЪ6UwM(jfЪЄЫUьн╘=^╛█nВx═ЁvMl╡пе5:╘PгсэЎv.ь{╘ш▀П ║█%m ▄_$""f{№╥Ую√E&Я▒yдФj{│ФЖl╖┐щ┘KT√√┐/=yб╪Гс╜ В№┌╣z╣"¤╢1аю╓[е╟/Є{0╝фCm>Г№М╦═┤ '╬ Жя─v╞л┤#┘|├]жMv╥╙т 4к╟9(шt╟УL╚Z╬▐/PZ /▄:▀┴v╒╓╢k╨╓№Ў▐v[╫яVj█сI7tOОDUW╒ИwЖ├w▐й├3Дu +Хкш╓б∙иg ╝╜┴ўzE▌qшMyН-╙PЁЄцПщtыo╟╘▒еMmкqВC├▐Чэ│М_лис]Pжч}їNН: э╝>в■■ИЦЧХiuчKvЗY╞i3=Ёeeл╡s╘╢│к╫╓vйн&>@ЫЧФБ┌  m║╟.0vи[-НS╜щk╘дй∙оД█─8п╣И√B3Б~Pэ╡CеHВ<АPб╓]S╜ї╜/┤э}К╕╡║╢╖p░I▌[ щ─╗Jm{`к╖■p╦E╢¤а6/╥m╢rфZ=|╜ ■@z*Z{5╪>дц╪Al╣Ъц╙8б{░]C═▐▓5m╒ъЪ*Жwxп1╛"}ё╖qs`э|ч(┬╢╒и╗iЧZ єk3 Є ╟╪иЖ╘9fL=я└░Ю▒│ ╓шq╙нє{фЧЫ^√!Н╪┘@:LШЯ*yq╠/'Ь6у▒хmm{╩∙Ўд╫Д╢ wY┐┤╞zIё@hфЦд5╝!ийўЖ▌ZkgРy '▒╖=U oбIьmп\бХ¤э8hзШy гвU█[╝2ЪйУ]S╘Ё┌╣бrЫn█фХ╤LЭь║V╗Gв:╢╬?┘╒▄Уc╚о#ПФRmoЎГ╥PJ█╬?Хо#_╕ ц:ЄК^╛▐/шСBИ yDo< _Є@Q#ПФRmoЎГ╥PJ█▀╘╚г4Q#`>фы¤В ПФRmoЎГ╥└Ў<┘╛x═╚╫√е5@фА"╚!DРBИУ]СRкэ═~P╪■А'█╫B║╦]}ї╒v,}П=ЎШ+ <З┬└s(<╣~┐ЁфСRкэ═~PJi√ЄЫ?Є+Э░Рэk!▌х╠c9rфИЭЪ▌кUл 2|ё╧бЁф·¤┬GРGJй╢7√Ai(еэo▐№═ЗJЛ &,╠&█╫B║╦╛м▒5lСvї╢к┬6═'╢C є╝]Кa;х·¤┬GН<By ДЄС▒ОХЧЧ█бAcvFи ыЮЖ░?З>Eb█б\С>█&жЬ╞5m░═б|б▌ЧR#╚PD*Z{511с ]╒j╗'Д r`HU╗╝ч0╪.╡mщP╕Є╫Ш:Ъе.╗Ь!ZogЕЖs тДўj 98в╓╬ єЖ╡▌nГЙ┴&uЗn_ЪA~6гНF=8j`╧▌п ю╫svШw┴╚цNih4|┴е╢IkьХkЪT;0мg╝╔p;дn╡kkш┬{└╪иЖjЛр9иS═■ы┴|л╢}iy Єў▌}╖юО Пш╕mЯ_╟ї╚>ЖWrx┼╢фЖ╗▐ЗЇКє▀└CQM;╛xe@Щw`сL}ХQи·M┐ф]┌Ї╕ЭХсЕ┬Д°Ъn5 Ж╣╚!ч`d╨яСwЗи┬|lТ(?=Є}\7▐rЛn1├i▀}¤z┘╬Ъ/╟┘'mЁ├НI√Щ▀(AэgЙDЇЩ┌ lKn╝ЄЄ╕Цни╘пМъ°°2][¤иЪ `┐╞m ░РzMАp▀@C°жy┼эzт∙чї╝:еBz(|ч;▀▒c╙═4пшz│╟uЗ│о9└}-НсzйXг&╡)МUM1Хкш╓!√uN(ў%ў9Д|;╠" е5+>мъЧ╞▌  r }БЮ·√╘я5ъ>'шў?2╒sЮz╣GЇ╚}^√#╟_Vl▄Щч╕■uиZ^сM]Xї]d├М№2Ўj┐№-пз;б▌яyўЗX|bINкu╧tЯг╜∙ПKу¤┌└>о■¤^∙Jнu"╫T╣╦Е'╔Й_q'!┘Ю{╙╓╨бОH┬▓ хкm<:вgЭ( ЖK╘ыЮ\mЧl╨¤╧9э_╝CGП▐б+/╣D■╩{╜mкW?vЫ╕▐~█╙o{єяO╢<╥Ў·ыпыk_√Ъz{{m╦╙fцЩeК^Eл╢W╖й╞╛v╢ WЗ│G▐ _■sищn╥`ш ╠+╘║л]C═Є>6'їКЪs,j┬╝/9╧a0~;Ф;Я/╘╚gрх■ohшвe║╨┐░ю│^/╜█SСЮ─Зэ▓]-ъ▄▒po╝╧▌ Uэ╜b╣.╙е║сжН┌█kSЎу╜┌╗ё&▌pй╙■х█uЕэ┼▀ўHNиiЁ{ЇЭ█|╒Їш;┴ ╩;T╒щ╡?q√░Z№─ЮtydтЬs╬╤э╖▀о╛╛╛╕0o╞MЫЩgЦ)ї╤й╫Yo4ъ№,╠+═Щs0╥k┐;Дэё√ЮG°Nvu╘GcП?Ф√ТQ,√S ∙ ЄN0┐╫Ўи▀√ЇGtуgы▄ яЎк█Ў╗ў 9╦┘p}╤╟╡┌ЎЮ╗R.ўU╣GN0ОЫyYqВ╢гъЧkхUл\а┌╡■╕у┬ ж╞н╪mЦW║+Ч{╜с├#ёI>╒║5z▄-Гq{╪Mo╝ √f·хщA╛rн ·ю┴Аs ░╠Мzk╜╗ W▓┐NдrOhыьёzхГ'X]^5 ╜@╢w▌ЇО_∙ш5zb▀ NМwШ▐∙╜╜nO·у╜{╡▒с*╙:Эъ7√│.[о+╠▀чЮ╒pа¤╥O\г+ь║Т.ПМ%Ж∙0ЖxєыФщЕ*┘cM5кdП5╒PиТ=╓TCбJЎXS е* 5Є┴я╙·╚Н╢¤╞П'рщ.ЧЙЧ╟їТ¤Vа┼zф¤!1Щ√х7^]НМ>(єEА{└╒tА▄ ╓╚√!▐uХ6юUяуПлwяFе╩ёX8┴0╢o~^>╙б╨${М│ Е&┘cЬm(4╔уlCбIЎgJQ■kфГ№^t╟╦├Oы%ot║tЧKiЕ>\=д╡9ю:f Є▒▐Їo∙5ъпhр`Єzu_b|b╜/╒║uБўИ╞√ Q─q╡d'~%ЬДЦ┌о┌|╗Ж[Z4|√f'╓╕uЇ3╕Ї2U╜C╗m5═s_TG76─п9сЗ∙0ЕxШЛ∙ Є╓щcMХ▄ь┐(yO{║╦═b┼з╠хr╝u▄√фE┌Ёй`эNХkc5ъЪ▐o╙ы=█┘▒╬mо35.├П╞ND5╜ы╙J]Rн█┤_Wзe╓гn╗7L╗dеY.rНкЬ%ыоЛxўщ╫█╟ю╠^╡╞Цч°ўEЗ=JГ{╜^s┬Ъ9Й5┘Й_ё'!╒┤Uл+ EдЧ~B╫\qЕо∙─T?╜.╜A7m▄лЦKжNvЭю*╡?al╔╬Uъl'╞чЛ ЁДx┼оlbbb╥О╧j||\╦Ц9!є╧=aV║&▓VNb╫╖.╝.чЧ╢LФj{│ФЖR┌■ц+┘┤k,o╙%_]>U7П╨:rфИо╛·j;ХZ╢п▐3фы¤b~Kk0wnп№ZЩ■wsтk╛C<Аф▄У\o"─A2pU√єв"PЄ@Q#ПФRmяR▐·"чл┘№d╫Й╝ ╕G╓ў5vЦjЦjаЎM Ў╛Хё`Ф╥Ў/╒╦ЦA╘╚Ш∙z┐ ╚#еT█;ы¤└№ iW]Н ╧√JO╢п^3Єї~Ai Єл/╗▄а;°?┴Я №ю'· =ЄH)╒ЎNw?ыhЁо >Н¤╚O_$"mн╥Ої╚√=уA▒^Є╛│U▐╝─k4№▐o┐7▄6╡эпй╖їt╥їйх MDOж╛п╦ч░╛нЛтzфЯёЧsж'Ьщ┘d╗¤БbСэk┴,щ╛_dЄKРGJ┘~xu44Ш┤о╓─┤nKkЪк█╘fhK╫ДМ═<єCB^лwWК╙зHy│№▄ъ▐юЄрmj╒>Шф>ч╩ъ A;оe╡┐м░з,r■-╬U█└)ч▒╜xl э╧╠p_qе5iоOБ╥ЪэЛUXw:▓▌■@▒р╡`╛d·╛Bi Єж{аZХйї@ЫЖ'фHjвлEЭ;LщМ╥Э@^эДs╙>╪>дц╕RЬ1'и6╦Iў▐эЬ!Zon3мэvzb░I▌[rWЖ╙╫уї╢╖4N┐cЗЦ╕mчк╝№№X╧№└ЁbўяX╟9^{╣ ╫nSЬ╛И╫▐╥хЕёЩю╦╚t}SЦhЛyl&╨зт@с#╚#jлt╣Эж╢][¤^ц╦лTk■ОНj(╨^▒жI╡Э=N╝╖╞й[Б█ц6ъT│_ГozцЖїМЭ=L№──Йй┴ ╦&t╫┤-О═sОUтШ∙nЩK╦iЭ╚Ъ╒·Lш7 └K╡еГЧ<┼В╥дФэ╫╔СЄU%+sI╝jН?╜K┌Т╪nz█mM}т|#q]9 ╨┴zt╖▄E~}№IuM╝;└ЁЧєng╩МRФ╝о&3╙}5Ўd╛╛╕л╓Ш;3о─rЬ╘▓▌■aЄе/}╔Об╘▄|є═v,╡Rz-XXЩ╛пфСR╢^iЯь уХ║з▄+Э1╜╩юэЗ╖kbыиЭ┐FЗj4╝▌╓╙╗╝Ъy 6╣ч╫Э█I+V╖ЮЄд╓┼╬у:█╓ЄЯRmэb ╕┴√ 'Q')НqC°Iч∙е║п%Щп╧я6р╦(ШП ч└c6┘n 01A■о╗ю▓S(╖▐z+A@A╔Ї}Е ПФrёсхЕ∙@╩lщ є─ яL╗╜Ё3Ьь7▀м.ёdWGm╗\є╙C_Jr▒¤├В _ЪЄ Mжя+yд─ЗWi+еэOР/MyЕ&╙ў╬|BИyдD/Ti+еэOП|iЪ╧∙╟{╠О(EW_}╡ЫYжЯ▒yдФЛ/ДW)mВ|iЪя ┐j╒*;аФ9r$oAЮ╥ycNvvпяoЗЖО\¤TЧ├Ь°▄`№+8Ю3╡yїNН┌йщЬ∙ee*sЗ═╬єЛ  /▄+u7i╨ ╒▌Й.U╖╒ф.╠W┤к7OW'▌╣┌ чы┤╟N'spє:щ└д&''5=жuЫЙЄ@бyї╒W544дc╟Ощ╗▀¤о;╓S╟ОыЯ╛;м·╬┐ъЯ┐є/<кгG┐н#G▄с{▀√Ю√┌ A@^▄╙&╡я эzE█еюCs{╨#К4Lї╓GьO°╞ўт7h*ўЫ▀ , 5┐~L[╠/·╢й╞ioшxB╬z¤їy╖ оgvХ█kr$к:;=▌Aэ▀│Iы╫zSХН╫к╬ й{я,Д■ЁЗ·ф╒┤о■я╘╪p╚╓]¤╖·╡·GЇщЖ┐╒з╫<кOн9иїНПлщЪ'┤с╫ЮtЗG∙╜Ў┌kv-@с"╚╚Лб┌&нIь.пиTї└░Ю1уCк┌e{ы╗Z╘┘q"╖щhя╡=°ж╜ZmўШV'м7x?№х╧Ло6+1*╘║л]╡цўЬЎ▐╓+╒║▌Y_ПMЄ}=ъl┘Ю╓п┘╬Yх нь╥ИЭPNЭ:e╟2є╞?ЭєmБ∙DР░0ВA┐╛Q-╥ищ57╜ї~╧{sзsD0к▒▒CъV╗╢ж√ы╜f}Э=юБA_OзZєЄ│┐ ▄щ╙зэXf(лAXффEї@╖%Ц│МНjи╢JЧ█╔i╛яД°Ъn5 ┌Юў┴v╒┌YЩйWcKзz··╘╙┘в╝ч°╤у:VWнхv@╕фyy▒╡]j█╝ТLЯ"5mR╙Ъщ'иЪЄ╙C !g<╨S?vи[fдbНЪ╘&╖╩&┐d╟кw└Ps│Ж┌╖:▒>Fwjuьъ4k╡~╙э╖ч╖ОЎ<м■Х+TщM(gЬqЖ■ш[╥¤pЗ?■Ў%·угХN█▄с╬№А┌+Э┐К╡¤ё╖?д┼ЛЧ╪58Ю╗_.╣DЧЖ╢╟є6▄пчьф4┴∙│- ╠A@^ШZў┴жnўTяUп╞╜╫/V╖'зz%4╬,sЪКVmпЮj▀2\m{ф╜:°бf]хКvgx╠эZ:╒ь┤╟оКc┬mнs▄Рyq╝{╒ЪхїўG┤╝мL╔.H│vўiЭw∙╔хСХ:░█Ю∙ а`Ш╥ЪS;CУWЮщo╫-╥█┐|╩i;╙▐∙Х3ї╓/Эr■Юk{╗ю sKo╛+n╫╧?пч═Ё─эn▒a■╥┤o▀ ║╘[jfЩ, дЙ  oтN\5'и╗╞э╔й▐╝hм╫╝>:╡|o4:uЙIs╣╔╪Є╬║~;■ЄУ■эb }ўиMIN╕MГ{╒ЪIя╥Тfp3zх6Ю▄нй╕╛V╗c╦█К╣Ц╚╠X[o╣ є_╜_╧┼ї▓?о╢╕^√чt я╨╤гwшJgz├Я№I|я|l┘6чЦ╢═Щ[pfЖЩ╡!╓v╔%t┐YI▄Єf╬¤m|[р>╗,КA@QrOr▌ЮЯы╠З╣∙Yowщeк::вgэдУкЭ▌"u┌^{gh┐ъR▌Ёх█uЕэ═▀ў╪eЭА}хк▓╦>q√░Z№ЇэД■С╗О╬Н┌kЬцKo╪[яєЭU║cwтЄэ║J╬¤▌ф▄ж╫╬{╝W{7▐д° аиф╠┐<■ШУ╧Ї╨╟}афШ╥╖┼▀xGeO╝эЛЮ<й3┐╡╪i{█╬°ц█:ы;▀Й╡Э┘я,_Цa╨о╦Цы ;╫Г▀▓W~╓ы┘.o\╒аНv]Пўю╒╞Жt┬М К╥т┼ЛuъЯ╖kЄ; ═N?u│▐■ЎVзэ┐╣├; Ї▀Ї╓└VчяЎX██G╖╗3Ў╩╗a|╣.│УyeB№ХПъЪ'lП№╖O№iоR├╞╜ъ}№qїю▌(r|ё+ЫШШH√{зёёq-[╢╠Nб╪е┌▐ьебФ╢ Ч╛Ї%;ЖRsє═7█▒╘rёZxь▒╟┤j╒*;Е∙Є▌я~W▀k┤Sщ■╝n╝q│▐єЮўxA·Л╥Чc'кЪ┌sпМж¤2▐'Її Wjф&SRу.фqC°Иn2е/▒ї\ж▌■эЭeM¤√Х#7щ∙═╧╞▀П┐№Че/┌▌х╜FO$┤╟╕ўyЗt√┌G]MA8rфИо╛·j;5│L?c ЄH)^/╢?р╔┼kБ ┐0Юzъ)}ю╫ьT·^·╫Vm┌┤e*╚;┴°иЭ']сdф}^эy,Ь;a:a╣Н6и?▐vЙL5╠7▌$ї_Рd┘НNж¤Д яL?k╫a\▒qг4╝|Z└Яbъї▌╘╟В<D.>╝^l└УЛ╫A~a╠5╚ °щ-┌╝╣╒ ЄaєxЫ.∙ъr=1-рcбф3╚S#К╥\а7╛┐]у├_p{┘M@щ_?ЯЁ╖56яЕзnpЗ3╧\ъ╓╫ЗС{ТыMД°RAП°ХaG}tj·Pdкз>╓Qoz▀c=°/Ї╟ї╚Ы▀BH╕]▄m╘С╧о{S╛r╔%║─ W▐бг╢9о¤Т ║▀╓╖пчЯwЖ'n╫▒Ў▌d┌▄ЎkЇш╜ZїKo╪ч╡Щб│Jwь6Й¤R▌░╧o█(]q╗6_Хj▌муЧ╜х▌Вйє∙}║сR█ф└9чЬг█o┐]}}}qa▐МЫ63╧,3ЄфSmХ.╖г╙╘╢kл▀5yХўcgcг ┤WмiRmgаgьР║╕ЭanгN5√=Є╬Б└└└░Ю▒│sъ╣g5ьЖn;э3э┌л┐w▄Їж╤│ю╝@/{╦^i°┘йУQ▌└=м█┐|Г.M╡n#▌uШщгwшJўав]╔Vd+1╠/DИ7ЄфQї@╖═╟кN°Ї{ф▌!╛Ьg^8!№ ┐╫▄Ь mBЎХПъЪ'l[аЧ▌Iр║ Лж~Ц^єL╫с<О╬█З╒т╓ъ∙ є т В<y┤╡]jл▒uюV_$~:NEе■█tП]`ьP╖ZзBy┼5ij╛+с6yuщeк:·и╛n╗├Я√·г^╗█~З▄КЧDW\гO╪Р[▐М▀ E▌Q╒йv┐█<╒║Нt╫a]v├>uкE(РG∙a~!B╝AР П*Z{╜:ў╪ЙихjV ШOSпш`╗ЖЪ╜ek┌к╒ .]б╓]Sє═щЛ┐Н;фэRХWй▌╘и_щХ╣|qд╩ЎО;эO▄ос[■bs=ўKo╨MUwшJ█6╡№у┌}З╟ў╢╪х█ЬЦы╬hSоjяT╒WцСW&└/DИ7╩&&&&э°м╞╟╟╡l┘2;ЕbЧj{│ФЖl╖?√ ]╢√r)эуП=ЎШVнZeзdъ╚С#║·ълэTjЩ╛пфС^е-█э╧~ВD& ╬Зt>,НlўхR┌╟ Є@vЄШw|xХ╢l╖?√ ═GLў├╥╚v_.е}Ь d'_AЮy ДшСGJЇBХ╢l╖6√I╦■Vuоя░S╙═6?,^xC·Узе╖NI7WKЧ-╠╣Rє&Yпюлп╛к■ЁЗ:}·┤&'╜Пг▓┼gъ╘щ2Э6╙╬PжS:¤╬Ы╬ш╘2я}я√Їб}Heeeю┤П∙№Шп▓(аШQZГy┼ЗWi╦v√╧u?1!=]a єoЭv√Ш7ШoЬ▒Hj■yщwWHяZь╡ЫdA~hhHЯ№ф'эФчЁ═╖jї ю╧╣╛∙s?п┌?╛[o8>'O╩ ¤╥Я■щv]w▌ї:я╝єьRВ<А0╦Ї}Е╥┼чцoк┴ЯFO■X·ї'е▌#S!▐x╟ з ч9i¤R╧┐┘╞pъTр┴2A=╚эЩw;▀▀xузIo еД ар╠╥├т┐ўSщж#RфиЇo?│НI╝ЇЦЇ▀ЯТ6¤г4Ў║m,bждfVNОўC╝∙╗╚∙фЄKlаФф,╕`9══▓46д3ўОJ═▀Ф┐d╥pфщ┐Ўn[jы▐╦Х╣с¤╠3е%Kд┼Л Є`ф,(?─gRЯЩ>E▓¤Е╦╛Hь╫2:2_╙╟▐ї▓jЧ╛к°x:╗ъ%'Tч▄6ХЖ┘Ю╫XG┬2╬┐Е лЯfИ╪▀є7╦┌чЄчъМ3╬╨7╛Є=ёзъ¤;wъ█к╒W■уц╪pЇТ_╓№┴7ЇGЇД■ч |B_∙╩7Э0я$z(qy *Я5яc N0mVзЭЮтЫеоЙ M8CokЕЭС╛К ▀н╧ы╥ч╛|Э▐ў╜зmkj╦■э╡|х¤▐?ьQхg█╓щz{[Х╤гщы1O─}Гj┌!7│?3мъX{Чк█╢xэє└Ф╓|lщR]yЎ┘юPg║▌╦>иеKпМ зN Ь^{эcz¤ї+▌с─Й_1╖ЇV%Мл╓НTЇ╤a;!U]╤┌J;QВ╕RCi╦v√g▓ЯфїrТж╖yЛ┤kZш5╜╙═jЇ┬╣Yож[MГ╜ fї╛HГF╖╞╖eъхэаЯ▄√А~кI¤█тE·╓п■ж¤╓-·┘9я▒KxЦ╛ё]∙7 по<Ё┐їБw▐╤9*╙y7^п w№б]"Ющm▀╡K┌т<┐жъ6╡┘#Ц'ФGы╟╘╤Pг╢пн╓Ю1ц∙ўиq"кz█т1╖s ┴▓z╬╔$╗j═w╛є]є¤я█)╧WN■ТЦ.й▒S╥oш∙gjэФч╘йVm┌┤EяyO№┐!Wн╔П/}щKv └\▌|є═v,╡L▀WЄфя╗√nMХВVk├-Я╥ ;5 ОыС√╞╡·│u║╨╢$ї╩Аz░_у╦ъt▌g*5·╨Гъ_ж║ы>г┌ ь2%Жп╥ЦэЎ╧d? ∙`э√Ч~ ═YзgХ2╚;▄Ё>мэ[5Ъ4╝za?╪гяЕd;СжЬаzbя_щ] ы>щ─kz╒ Ї╧Яs╛√їИ╛╡цwЬ%╩ЇЛ▀№k}Є╧я╥%'╞ЭўкE*;чl╜┘▓Qчm║^№∙ЯўVФ фk┌▄uўqЩovTi╨<▀д╧▌°-ъЪт3¤{e)YРъйзЇk/╝`з<щ∙w▐┘в═Ы[ Єє─∙╗ю║╦N╚╘н╖▐ЪЧ ЯЯ╥ЪЛ>оo╣E╖ШaГ┤я╛~еоЄ╠ЯЧ√я╙▌wя╙РЭЮ╔+г╟5ю№]╢вR8 Uо0 Иу:>·К;@кh╒оЎ!5Ч╫и╗iWКhxm┘╔`╗ЖЪ#N╝╧╠╩╧╘Е жW├ е5gъ=Лщ▀┐■m|рh╙эыЭс╙║■¤Ю>zт]ф╠{sхЩzх7щ╝х/шч╬Kуm║╢][¤D~yХт#oв ╡ЎЪч╙иЮЄЖДч└┼|+▒+ў!>sbы7▐|SO╝ёЖ;<∙3sIЯЇцЫO─ЖEЛ~иs╧¤Ж╬9ч w8 №o:╖г2Є N╕т├к~i▄ Є^░╛█ўй▀k╘}N╨я─┤=ву3.ўИ╣╧kф°╦ъПН;єУ╕░ю│║х╞Пы"; Й**лэX*╓ийvHг╓П┐=t@ч¤ЁЫZёsgjщ╟▀хДЇwщЇЗ╬╨╧9o┴лG■┘>фМO╛▒^i:[g|т]Zё┴3u┴ЙяшфpжЗ щкWc╦АЖЯ▒Ую╖;TХPZФoЛ/╓╟~яўtх є ╕├╟┐°E╒.·С~oщPl°П·Б╓м∙Ш>∙╔+▌б╛■W▄о\а╘х=╚┐▄  ]┤╠-kqГuмз■"=y╪&ЁЧЮ╘°ЗM╗WВУz╣Ч┤l╜iл╓╨╛¤Т?■НЕщёР{ж\╞╥Щ╬О^▌YSЭрi┬nзv°3╞й{аZХ▌W/Yл▒_·c¤Ї¤u·╣є╬╨eЧ/╒█Я:[ я╥╥siщ╗щ─п.╒ЫMя╓%+ЦъCхgш═Л~Q╧╒■С{█м ╦╧ъcС└sьSOgнк.wFM9═Дx#┘uфOЯОш╔/5?9э2ХPjЄфЭ`~пэQ┐ўщПшF┐>▌Їк█Ў╗ў 9╦y=їжgu░И>хrQХ{D░L╟═<E┴┐F╝_ ?█ЇL▄л╓╘┤i`аM5ххЄп╢шёъ▀╒eъ─ыэкV[═Ї▓Щ·шаЪ║k╝K3║'├&й+Я┼√▐ў>- ┼:╜ё╦_╘  a╗Х@Ч_╕Dя¤шY·╔╡чш─oЭг оXкх╦Цh╔∙я╙>╤OV▀кК_№ў╢sV╤кэ╬БH│╜дdEеЬчш_f╥ым│TQQбў]╤инn╫ПЧ Эw╬┘Z■sg║%7Юw╢^╛ь╙zбо]■ЗkTYYйеKg>Xq/?щДї╕╦P&L╫G╜╟ю^▒╞}О╙ = Э╠0Ч╦lцК∙иаd=я%ф═╖&┘■Ав3┐g ∙╜шОЧЗЯ\┘&A║╦х╥╡·╠5UNТя╫ГQs┼s∙╔╥╜b 0_Єy∙Bt▐yчйк·▀iщ№m}я?▌н7▐Wы╧■r╗╒~NY∙ ║рВ╥yу1╧їцЫ╖ыwў ·┬Zї∙╧o╤ y·Яї∙я>е/√о√ў/Ж┐г┐№╦V¤┼_l╤Г▐рgЮ╣╘нпГрПmyCц'LчO▀~0эа6;Wцлмl│3`бфчЄУўїk}Т╦=фnЩJувъjщеeZ┐^┌o╩▌╦╧║Ьщ╡O6ю▌$╞Ь4{яУБ an╕EЯZ╕ы`ЖЧ\+m┘nЎУ┘╜ї╓[·┴~рОрp{эЛY▓╦OЪЮї╫^{MзNЭ▓-щ1!■▄s╧Э╓[_ИЧЯt/Ъэх<єpYPSzVc~hа╢▌╗\йmO&x∙╔ГЫ╦┤¤дvпХFwо╓Єб█4i&дФп╦OЄГPH)█/Д[╢█Я¤ЙТ∙\ UР7с▄Ь├сNоыЯм▌ Є├кVз:▌╡j╖''╟╣╦╢╦ ■▐Ее°═#═Дй ozуўk¤фn╣╤}tзV_/=pxЫJ°ўБYЕы:Є└cO╢О/н1╫ьў~╚╦ЬУ0h~╧└=;U╗Y╧Ркv∙эR█п$&юs╥Ў=■Єmn4эЩЯаЭ╢╩Z┘?д; `~фє╞ЇШчs(Hжt┼┌ю`z╪G5°!пК5MкэьQ_кv3Q█д5╢█▄mў/-jz╒¤ГДцNih╘лy■PАвDР╠ SЄ2CIq╦p╠eQэA┬`√,┐ьЫcг╟uмоZ╦э$А∙EР`╛UTкzаM~М{- ЦF╒зjў&cт┌=ї▐oф╙Zн▀┤G√эеjF{V ╩╘╟ Д └╝лWt░]C═^ILM[╡║▄ √зjwjэk║Ы4h┌═П~UO╡oоN╗G~цLKmэю╥:яЄУ╦#+uА+╓ Жл╓ еlп╘Аp╦v√│Яа╨e╗/Ч╥>╝№$А╠q╒1y ДЄ@Q#ПФи -m┘nЎ║lўхR┌╟MН<АьфгFЮ ПФ°Ё*m┘nЎ║lўeЎq╣Цщ√ е5@фА"╚!DРBИ ДWнAJ\йб┤e╗¤┘OPш▓▌Ч╙╜¤Л/╛h╟J╙┼_l╟╠&╙╧NВ╗¤с▌╗зnW╒HТхє.оF^ъъmM8┘╡^╤оj'°┌ej┌RЇT;╦Щл▐4{╦╒┤Uл+ъ|·T┤jЧй╖ў▒e╕:о╓|ц√╬ПК╓^пЦ▌┐oghЦ ▀1┼ў╝С;y╠┐dg╒й┌5м{bg╒7─B┤╚┌m)N▄Y№ ╠g▄╔B3;┤─}├мm:щ╝╣Э╓ЪжS╬╘buте`at/1ш97=эaхД═▐t:~ъгS╦t╡Hц╩6ц╢Й4n}Sыr▀ m{o4:u╗╕Оз∙эt >&w0с;Q>oфi∙<╥wмSЭU?├┘ЎБ│ъЭ№н6{T▄ЫЯщнЁ┐Otюwъ,~(Nkw√╜ц9ь9O·Н@сшs;iь`zРУЕ▐"Tк╧│#╚#ТХ╓д:л~ж│э5{n╗_ыь┴oюФЖF╜Ъў└z┼г>ЪЁЩb█│Тмg╗└Фъє╞ьЄ'тk║╒4h▀╪╥,Х(eNЪ┤у│╫▓e╦ьК]кэЭю~`j╒wM;┌яS─¤^pBцЫASч^3╝]Q%o▀:кЖ-Кнg╞Ўю& юТ╢┌S2i-wЦs└░Tцd╫▐У:╘pо┌Nй}Ёu╡╬x├Ё╦v√чт¤вeл√╖s}З√╚еlўхto тЛ/j┼Кvк┤?~▄ОеытЛ/╢c│╦Ї│У ПФ▓¤ЁJфno║╓}ЛУ▌э╫Д╔┌у┌ж\╟о╙╘ ЪК╖╣еEк╥о4В╝·█ж╬∙o▒)Щ╦OЪ+╫X-]'f^╛Hd╗¤sё~AРG>e╗/з{√RЄ@й3│y,Иl?╝n┘n \ь'yфS╢√r║╖'╚е+▀AЮyEn╢ 2х^■!ВЧ,═┤А∙EРP┤L╒l?■╒ё╬═0'M/{Ъi;єН аh═■у_}ъщlQг=ч┴╜╝й{╙L█Шy╟╘╞√їёFтt▐TTк┌ ЭВаL█Шy'┘╔нЬЁ @<В<АВ юєт▌_о╥хv2&╙vцA@i1┐M╗┌L╜[:╒c╧W;╘нБъJUd▄└№у:ЄH)█k'#▄▓▌■s┘OЦ▐}╖Ы┘Ы╖▄b╟fЦЇ╟┐.7?26мэ■С9С▐№к░weЫ└Фe▄О░╔v_NўЎ\G(]№ L╢^╖l╖ \ЎУ\y`&┘ю╦щ▐Ю Ф.В<L╢^╖l╖?√ ]╢√r║╖'╚еЛ_v0 A!В<By ДЄ@фА"╚!─uфСR╢╫NF╕e╗¤┘OPш▓▌Ч╙╜=╫СЯ╗[o╜╒Оєчо╗ю▓c┘уб░`▓¤ЁB╕e╗¤┘OPш▓▌Ч╙╜=A~юLР╧eиfУы}ОД0 A!В<By ДЄФЪ╤ЭZ╜zзFэ$Аp"╚"л╦╩T7l╓A;o┴ШГ└cZ╜3З╡9нчЬj╣`╗3l^Ё5 ky┬д.кС╔IM╞Ж▌Zkg-ШС!н<р?ЮZ╣^╣╬Є7пУь}МDПi]╥ >кЭлзЦsК╓∙▀<▄?╒>9вш▒;s■Б∙FРа─їКzвS╡kH;V√элcбvtчj█h╖е8;7'о├Z╗[╗cG╦U]gGsцаЎя┘дїЎ>*пU▌▒уIJГF4╘?╡Ь╓о╫ж■!з╒МгYnеVT┌I дЄДID╦cA█╓╡yy$╓+>╒cЭк▌мчШкЁ█е╚ї^╧uх╢├╢╫┌мTdЗ┐|DCыM√,▀МЎшa]л╞|Жф╩ZщЇiОщx,сЫГКр┤щ▒7 f√╡╛╛╔▓DР LТХ╓М╫1з}{░╟z╧~L╒n&ъж┬╢█юу`■║=N.╢=▀БїдfЬ └6-LgўZэ╣V/ўrЦ+╫є^йmЗ═┐┘zэ| ДAxLИ7A|─$МDХ~ХМ9ЩЇNUП╓╢|зxўеZ╦эdЬ╩m:ьфШ╟Яt╣╡Z┐й_C╔╗ЇБ╨ ╚vnйID~╠h╧├ъ▀┤^kS╡{У1qэБЮz╖▌ЭЩ█ЛЯ╧oВўэ>ПХ+╝^ў╛У╘эЫ2Ъы#╥╡Нюrг;7zрM═}ЭкУ сСЧ ▀▌wыю╪ЁИО█Ўyїr▄у╕п e;cгНFc├┴_╣╜2ЁPЄх^╨CБvwH╡r╞ФФDulЭWR▓<▓R▄3;S╡;╡Ў╦╛V#ж╜rЫn[9╒~¤╨╩┤zф╜└▀пHмд%ўЧа\╗█\В&╔єHtp│} ╦їЁ╡#:lП,ЬcЪ└у3W╢ЩЗoА<+ЫШШШ┤у│╫▓e╦ьTjў▌╫пїЯн╙Еfт°#║√╦tг?=_Ь√}DЯ╥зЬо3бGю■ЖЦ▌°Y╒еz&Д?╪пёeu║ю3Х}шAїП/S▌uЯQэv├Д¤GЗекkY{БВ╦╔Y╟?^а╧м5я п╪yf╤И▄жРI╡╜╙▌n┘nЎ║lўхto тЛ/j┼ ў├║ї╓[u╫]w┘)  r╜╧?~\_|▒ЭЪ]жЯЭ∙/нYёaU┐4.╙■r }БЮ·√фvТЫЮs'°ў?2╒{Яz9'Ь▀ч╡?r№eї╟╞Э∙ЙV°!▐╕P╦.▓г)╝2z\Nц╓▓Х║└∙пrЕ∙G╫ё╤W▄∙╛╤'─;кЦЫd>╡▄╦f▒ jmИZж Г@ф=╚┐▄  ]фДYg№┬║╧ъЦ[nёЖ щ╔├6Б┐Їд╞?l┌?%У╜S/ўТЦн7m╒┌╖_Є╟┐╤я(дЇЄ░Ю╓GTХГп.╕╨;JЙ ╩ЁхWж шASVczулtM$бG╚Б№y'Ш▀k{╘я}·#Se5┴║ї}C╬r^O╜.·╕V┐uL╣Ь уNШ╛(8nцеt\П▄ы─°ї╣)э╣аvЭъLЦ~╘нБ╨╘╬$и\Q$╤5U├z╘YЖ2yфZ■kф}&ЬЫ@э╫йЫi╙й╛^┌o■├■l╦∙mЙуцЎq╥иН╖╠ м&Ф/л╗NЯй╜`┌t*жў¤╤с$╡ЇОt╫Qи▓н E╕e╗¤┘OPш▓▌Ч╙╜=5ЄsgъХБ∙ж∙∙ ЄБ└mър▌▐·dA~╢х╥ Єж=x@0ЫNvнM╚уN|нtГ√н│╦p▓+┬-█э╧~ВBЧэ╛Ьюэ Є@щ  ╔о╛ ыЇ▒ЛжJnЎП_Ф╝$&▌хfёЄЁ╙z╔∙я╔{mЙО3╠x Jsвъ5U╬┐`┐tы█MOV▀>кГ■е%!▐7▐ а╜Їd╕C< [^zфQ▓эЕB╕e╗¤┘OPш▓▌Ч╙╜==Є@щ*Юy9CРBИ ДA!NvEJ┘ЮрЕp╦v√│Яа╨e╗/з{{Nv▓╖o▀>;Ц_6l░c╣╩ы╚г8d√сЕp╦v√│Яа╨e╗/з{{В№▄ёГPеeжb2A~╒кUv*?О9BРGё╚Ў├ сЦэЎg?Aб╦v_NўЎ∙╣3A>Ч┐▓Й┬5█╢&╚'GРGJ┘~x!▄▓▌■ь'(t┘ю╦щ▐Ю ?w▒p╖ ┐┌еїЮUР╧─яjr╥Й│▐ ╝┐f:a▄№yзь¤zє╜рN' cРчdWДЦ ъч}р7u▐п╒∙№ g°uХhГ349├зїЮЯwЖ¤Ъ.°∙k┤°Ї┐┘[В<┬╦эp7 w┌ыv7▌о°Д┐ю|чO!╚ ┤╝lЮNИ?]l9Ю А3щ<Ня╡ЩщтAР DVЧХй,0l>hgМю╘ъ╒;5j'ч,WыЩ╤Am╬√}аTx'│жтЭё"╦ёyВ1u4Фл╝б├╠и.к'НШЁ29╒▒u6╠Wn╙с├█Tщ-U░Fwоv@╓iПЭrcЎ_t)▐AР╟┬;дn╡8 uыI╥g┬╗ єwю╘и▀Уn ю▄lzь7╦э░7m▒^№╒┌ы ?и═q╜√г┌y}D¤¤-wжWЫуnX_▄}8╖[°v└]oЁ~жTn;ьА╘┘i k&Язт¤┐┼Г П7vи[j┌к╞ъuзJЄcj(/W╣?Ш▐{╙ц№эИШ╢И·ж-h ЎЎ√╙ю▀И"ц█{ЫИ{С╩Z┘?д;щrВ°╨z╙k┐[kMи^>д█b╜°╫ъсыMYЛ ▀ыд╢▌vпн╘╢ЬРm{¤oqn╤J╗╠HЇШ╓∙i=ю>Ь█▌╢I{Ў█yўk╧ж█┤н╨┐@Q0√ж╪g ёУзOzгз~ъ№_q ╚#o╠П╠4x╞фц°5кol╤@ўб$х5}К╘┤й║kB╬0╪оZ;Gmn4эQ╒',7╪>дц┘Т∙└Ркv┘їv╡и│┘Ж+┘у.Ж@СsВ°Ў╡v|Ї╕ОiП╓∙╜ъN0я7┴┤G+░\2ц╢БuU6^л║=√╜^∙р}k╫kУЭwp mZ?╙КБЫн'▐Щ>ёгз┤°МеZ2■ 9╙┼Б П╝2┐NЦjp╣e5Mrr╝Ь$пЦБ$х5cгкm╫╓z;lOXоbMУj;{тВ∙4╡Ў╛ s ╥hр■У=ю░КМ╢л╡▄N&млw╙ЛЮkk╡~╙э?xP√ўl9є┼═ъ│Дx╙■ЎЫп깤5Z№vёЬfMР╟В2e5mкqK[Ъ╒й$х5╧ ;нАщL┘LD+oЫс$W╖Ї&в▒·uл▓Q╫*I╗сЧъ$▄v┤чaїoZЯЄ `эvsЄэ:Лn╧├БРВ╪gёцп9И]|╞Yю▀bAР╟2e5jёKfly╦┤ЄЪT=їЙ**Uэ▄c╗р▌ГДЦF╣Ї├z╞m╡эvKИw Ъf oС ╚cс╕e5 %3IC{╜в]╒jл▒'е╓┤ешбwЦl╫P│╖\M[╡║в╬╩+Z╡╦╘╦█Z╖ W╟╒╪{▀8C│╘╒█*r<АBv╪ )S%2ЗзN(ї/?Щь2Фж-x;~B╗Я╤╫юЎжЫХ╟-cKrТ▌ЗqpЗ"║V3фxчжЗcў╝O`╬▄`n■oЖя№uЛ¤-y,'`ўN ╬NЯшUkbЪоП╞ї┌л║R╔no┌№х▄`¤ц▐╪э{г╤й█╒╢k0╔ЄА╠╣'╣╬TцфБ╠gё▐|чOьoq ╚#·▄KLz╬MO;ааШЮ|z╪1яЬ ╥│ЗЇ╥s╜╬▀>¤xь ъ╟╧~]/ОЩсяїг▒'Їгg╛сфўIўящ▓еЎЖсW611СЎqЙ╣lW╜(й╢w║√√Ka╩v√▒¤Q,цы╡ЁтЛ/j┼Кv Щ╕ї╓[u╫]w┘)│┘╢ї╛}√┤j╒*;ХGО╤Ж ьTn?~\_|▒ЭЪ]жЯЭyдФэЗ√Ka╩v√▒¤Q,цы╡@РЯ;юP:ЄЩvфСR╢^ь/Е)█э╟ЎG▒Шп╫A╚Ю ЄєБ ПвСэЗ√Ka╩v√▒¤Q,цы╡@РJW╛Г<'╗!DРBИ Д5ЄH)█║PЎЧ┬ФэЎ╦чЎ_zў▌vlfo▐rЛцn╛^ ╘╚еЛy╙ф ╙╙~ы├╧╗Cb; ╚(f}ХЧЧ╗CC╟ШmL╘зИ]ж╝<тL∙2m`~Q#ПФ▓н e)L┘n┐∙┌■-√[эШзs}ЗKУ ё═R╫DTї╢)Щ╛H╣z'uыhP═ЁvM8Щ╢#|цы╡@Н<Р=~*9В6▌иЮМ┌¤iД╔|╜ЄsgВ№Lс┼c╢mMРOО ПФ▓¤Ёb)L┘n┐∙▄■┘∙╚T└6=ч5├┌>-pфK┘|╜Єs w√ лmAQZ чY∙БЙўъН╔еzўЩ╥╣╬`■.=├Ыўц;╥┐№X·фE▀єf╞ ╧╔оК_┼5╒i4╒∙о╛▒Q ╒Vщr;Уi;`▐М┐uжо√иЇыСЬ7ф╒Тоx┐7Шqуо┐{╔)2yEй▒еS;№+╒МRў@╡*M!╗щЭП]mж▐]о╟^zfьP╖к+UСq;`бЬ:uJз'Хr0vo║в(├ ыыёz╪[MП└╘r├╧8*▐Ro4▒зрФк°╡!sСNрК5Є└BhSM,h√┴║OСЪ╢Xп°`√РЪ▌ощTэf=Cк┌х╖Km[╝Ъ∙К╓^╖═║к╒vП┐|ЫЖM√,▀ШRУ╓фрW.п2┴]ъ┤Б▐74:їRьЛШ▓Ъs╒6pR]пO]6╥U╣B+√#┌1╜ц(:nРЯtВzA╚╡dе5юO▀╖kлM╪kЪT█┘г╛Tэfвv*l╗э├2▌q=°═Э&5{'┼╓УЪ9ppb№о╓Ь№zgEы▒ТSяЦ┘$иПЪ╥ЪъjYвfgJkdnнvПDul]аЦ>┼х%▌л╓╕е7^э¤f┬?BЖyаXЩя■кз=Hl╫,U2NИ/▀бк┴└П eэ┤Z{╜аюЕu╙vJMkЬwЧ~я}░╖Т^f╥┐№dЁ2Фf>оe╦Ц┘)╗T█;▌¤А¤е0e╗¤╪■(єїZxё┼╡b┼ ;ЕL▄zын║ыо╗ьК┘l█z▀╛}Z╡jХЭ╩П#GОh├Ж v*7О?оЛ/╛╪N═.╙╧NВ█|F┘───дЯ╒°°╕Ц-[fзК╦∙╬БC)911a╟RK╡╜╙▌Кy │l╖_&╖ ▌|╝^|ёE;а]|ё┼vlvщ╛п°ЄA~║l?╝Кy │l╖█еЖ╫А∙Тщ√J~оZ пЄ(ЎG├№bIcgйб№|Х7Ьx,K1mБбб├╛Ї№хГCdЙ7`y,╕й_ЄїЗB°ї▐>'|S▄Ч%c├z═R ╪Ц8-ohbтД3╝жЎZє√fgл├O· є╘y╢f╜;PЄЄЦй/еба╘╢k╨yLц╫|╜б~╜╖пG▓┐0<11иЎбS┴;йУКЪ0▐u╥N9єв~√iUVЫ┐Л5№Мєзт-ї╞ц∙Nй*щ/кL!╚гpЩrЩd╜Їй┌5м{№ЎЖXЁыh░mБv[К╙I\ЗU╒╘ХOЯ╤Ё@╡*+ьd6╞╬╥ОN3╓√"жмц\╡ ЬtО^Wk.ю 5В<▐@ЫjbA█╓}К╘┤й┌ЎК╗┐Kр╓Ыдj7ыR╒.┐]j█т╒╠Ы▀5Ёz╓ЭблZm■/О9ў;▄h┌S}0жў└аGН╣°Ц└╘├√е7╡'╡&╓ыгж┤цД║ZЦи╣№|Jk└мЄXx╔Jk╞F5ф┤o╡щ╣bMУj;{╘Чк▌L╘6┼┬▒█>0,S╜╫Г▀▄) Нz'в╓У\ЕZ{═уiTOаЗN·╬╘╧ЯTWя[╬┌з╗╝ъФ√whФЧ&Ш╫СGJ┘^;9▌хLЙ╦о▐╓°`k┬ўM╡ЫщЪamм╥ОL█k║╒4╪ыХл°ы▌%m .?Л╛H╣z'х6)Ш└▐╝─9HxSГ~XПыЙ┤;╞:╬╤╜б▐╓╙╬╘"u4ШЄйеы─ьў5G┘n┐toПЕї╪cП┘1дrї╒W█▒ЩёZf╞√═ь▓}┐IЕ ПФ▓¤ЁJw╣дA▐Ф╨Ф7╗'ЬЪ@kъ▄kЖ╖k"кфэ[GуВ Мэ▌MЬ%╚ПuDthM╘╓кЫ╟▓CU■┴@Rцк5g╦-p∙хБ фД·┐iZвO╖-╢ Ю|Жx#█эЧюэ▒░╠ыкUль9rД фя73╦┼√M*|ПUпш`╗ЖЪ╜ТШЪ╢ju╣щ6U╗#PkяЖu╙^╤кэ╒Sэ[ЖлeоЁ8ЫКJйн╞╗M╣{р0SИ7ьUkўбЩ+╙$Щ7╤√Цоl}}Z{>C<(Ї╚#еl{б╪_ S╢█ПэЇР═Мy wx┐Щ=ЄАВє╬щwьфя7╔ф{у╘║сп~[_xЁє╢ЄГўЫ╘ЄАМШ╒ ·z¤┴5 Cя∙└╗uC╟я╪9Р[╝▀╠М H█яxк ▌∙P╜я[╗Ї┬╔t▐╬╥ч╛·9╗D=w┐6l╕_╧┘IеЕўЫ┘фq&N╛кyэ;vj╩╧╠╫█√~[╖6¤Б╣W╜УПjшЭзЇ╠w┐пп|ю╦v)▀s║├%║Д `╝▀dЗ ИyїэW┤¤PD; >кБЙ░нцыэЯъ╞┐m╤№╓э·╦=аo/∙ж╬Yrо╬¤Ў┼ъ┌■╫:ў]ч┘%нч╛оG╡╤∙яQ}Э$ ▐o▓GР╕^}є▌╒{З~ У█╡╣aУвO№й■∙╡гzgЄд╛ЁwЫtS╙V¤э╦нзЧ|Wg┐uо¤|╕эя┤Ї╠еv SЮ√·г╥5Ы╒PuTП·ЯмюW╫mj3=gЧxC█у3┤╟x╜mSmПлэТ ║Я ┤x┐╔ В<└u╫_▌й .9OП┐▐лЯ.y]Я√╡╧ъЎ┐¤я║ю/7ъ║O ╢▐·жЮ]:м3▀zЧ^?4щ|и╘YK╬▓╖zNючъ'.╒U uЇ╤пO}▌}tX╦┐№╝Ю▐:7joKЫє19C╗ыR▌pУ╙╓k[я╒▐Н7щЖK╜Iс├√Mnфо?∙╠Чu№ЙяйяйC·┐o╨OЦLh╙uЯ╒чзUгЛ■U?8чy->}ж^·Ы╫up√▀iщ╙{╞\ю╫▄╫╚∙\Хє╔кНG_w_a█ 3O├zЎgTцkюгGя╨Хю╫╓-┌л└╫▌svХ6юUяуПлwяFСуБЁу¤&{y@ЬЗ╛ЁР▐╒б■эщq╜t■ПЇ·╣:}R┘єВЎ▀°И╬,;╙.ЩМ∙Ъ√и6v┌пнэW╫q_w√╠W╓жgьГv┌Чв¤к═╖k╕еE├╖ov>f▐o▓CРLєPы^Эzь ╜tЇД▐їъyz·л▀╫_ N╖ЦhжUЗ√5ўэ┌№фє┐ю6_]╟z╬ЬбEъ▄wГ▄o╕S╡]· ]s┼n-,Ат┴√═▄ХMLLL┌ёYНППk┘▓ev ┼.╒ЎNw?`)L┘n?╢k8<Ў╪cZ╡jХЭЪ╗■ьz-╣pЙ:>╜[Л&█╓92WЛ°вЇх─═TэЙo╙%_]о'f[. GО╤╒W_mзf╞kШя73╦┼√M*Ї╚R║ wp>Tўd бЪюIg7e б а0ё~У9В<`FЛ&sЇQqщ ┌Чмw+U{Вл┌ЯW;┼ё@Qу¤&3y ДЄ@q▓+R╩Ў/│ S║█/ЫэПЕeN>├╠8┘╚ ▐ofЧпУ] ЄHЙп╥╞Ў<╝╠ЧL▀W(нBИ ДA!В<By ДЄ@фА"╚!ФёBб┤д·Ф╢?рс╡`╛dЄГPyЕБ╥ ДЄ@фА"╚!DРBИ ДОЇ 1tJ9Ы#IENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/images/screenshots/importwizard.png0000644000175100001770000003600514654363416022400 0ustar00runnerdockerЙPNG  IHDRАсgoi@sRGBо╬щgAMA▒П №a pHYs├├╟oиd;ЪIDATx^э▌}lчЭ'ЁпЬфТv7╢c7u{Й/qJ╩йJ▄х┼h╓dБ6█─[I┘;╡Hy╕т╣▌┘т╨Кmвb▌шP^░N▓dяПмФmcсю╞╪h KDуs│*╞qb╟╣eЩXdЬdЭ┤ы°%К╙э╦:╢ю∙=є 5дHКФ(iдч√1╞ЬyцЕ3гgЮ▀єtюp╤Eщ∙тC·о╕т 3EDD~!Яj*№ЄЧ┐─Щ3g░zїj╝ё╗╫p°фЛ8╖ъ_ёыЛ▐╟хЧ\Ж-ч>ЗM6с№∙єX╡jNЭ:Е-[╢Ш╡ЙИ╚/цЇ1╨uы╓су 8╬о=Г╔╦▀┼█Ч┐Е#W└▒╒птЭsя`у╞Н╕·ълї+∙╫Ью\╕pяO╜ПП сU╕z═┐├*(мY│я Ў}=_j mmmzЬИhй╝ё╞╪┤iУ.Пd╕ї╓[199iц·Ль▀бCЗ╠╘L2п╒хъЬ└o╧¤>№1|ьЄПa¤ъu╕·Тkpс№╜Гr`Ў═ ▐W /S┼"mдКf║eф╝,─vЙь#а╖╖W▀╗|·щзё Ё╪╜{╖Щы//╛°"╢F>S5HЪ╠УeZiN`jj ┐∙═oЁбK>М_·X╔Х╕f*PЪ'ZйZoЮmауSушsЫИ|шsЯ√■т/■k╫оEOOn╕с▀╢n╛∙f<7■єA└-№eЮ,╙Jsn№■╘\╝ъ"м║╕ л╒┐u╕╥∙DР ю@Dф╔dGО╤Б└п*Г└B■вщ ╗АK╬^КХS КўqV яс╥K.╒ЯТб╣`jущVq·ътщeЙшq"ек·╠eзч)║k╞Iw╖уp╓Л╟▌mvaY$Вj|z!-Я╣^щ-d√zцt║w?ї`VЦэTжХяЗщєьs$5бЧ"в╓СnЯo√█xтЙ't+└╧╝A`! ╤P╕0u╧╛║{^|{е╒ы ┴eЧ]Мў/Ь┼щ9Й7╧╜Бc╜К38Й'¤o №№═╧ З▀|QЛ╞и┬xЦ╓├X C]mш┼░╙ЪP╙┘─.з░╘<╦Т@в╫╨кp &3н5/╫хэц╔"╫a╢95Ж┬H╘°`зЩяшьQя?b▐-3В\HП:)ОжБОаw·╞=█Мa╠lпs╨ьЗд T┘ПAtVьє0╥*0Q+Iс WїW╪╛}╗I!╤p рЯ▀9Аў/∙5^╗╕А_№╫8vЎ▐∙ї Ьzў$ЄЧцЁOпрчW■╧]╜OЮ№╬м:a╓nД*МЗ√а╗╘;{tсэ6ьz:ЗЙRъY6╨ЗЭ▒,Є5^ЬPK┼╨уЦч▐yЪgЫї;╬M@▐.3ТCtgT"АЪ.B╩ Z█╚─╗аJUиЩ╕iHk├╦│▓╧с$·═JБ╛ЭъИиХд▀▀я5Ч╖█╟█┤ л┌Vс┐№ЗпсSя└?Ор¤S┐Fюэ ЗW▀∙G╝~ъ5Ь№╒);■:ОO¤▐╗r'ПЯ╞╓s;n█╪н?║░КШ╚Щ╤V t#кjтг┼ FrQtw╩tЕтиJU╙╒╩U╪wa е╞ДtыH<╨-АТкADKу┘gЯ┼╡╫^kж№л▓╧┐ЄЮ@л5U: ч _▒q═╒°╟г/уЧ ЇK{ч5OMр╘щS8yЄ$▐9s┐zё▄x*Вoю│╓B╚Ц║d Еr╓╘·эй║╢█{#ЕЁ└РзE╨░║е╥▀;А\┤[M╔t#╗ЄR¤wZe2p* Ю7*фС w@wщ}╘й3╔>g╪eЎ╣Ш`QЛ╔╟?х#б~VыЖяBБж└e}▀№▄wpыїЯ╟Й╫~ЕтСrGs°х/Еу |┐╧Э├m Єз°юэ;░цC ∙+caДЄ╜NўКю?w╗]:1и√¤═Н╫акп<]2e:╤л~X$иB█экСщ▄PоjўПt¤ й]цFn[$Еbg?ТH (╙╜yДj╢╘>Ы{▓nпja░ Ии╡ю╗я> Ы)Т╟цT■.7┤·╤:M? Hwст °_/<Б№▀GёЫЛ~ПЛ?╓Ж╡ЁЗИт[ш¤Ї╙Е eЧ]Ж\.╖╧ТO╤ аг└╧р═U╙╧Тз{╩7ъ▐;∙■Ї┌/с┐▀┌Зk/▌ИwЎj|ї▀~]Wїрф█'Q,uс╧'Б∙╫╝~■╫┐ є╣т№Еєш ╟p∙eЧЫ9 Й-"в∙j·q╨DD┤2╠щq╨DD┤20YКАИ╚R DDЦb "▓СеИИ,е┐`╞ЙИ╚"lYКАИ╚R DDЦb "▓СеИИ,┼@Dd)""K1YКАИ╚R|─∙╦┐№K3FD┤8╛√▌яЪ▒╞0, ў▀┐Щ""ZXў▐{o╙А]@DDЦb "▓СеИИ,хпpь1l█ЎОЩ╔ТZщSИDR(Ъ╔ТZщDDF╙а╝ >Ж╟╢m├cНЦ╠ЛQР╦{м]Лm▐ЭZ╬Д9-Рж└ё4~тЦд╟~вж╛И?╣╬L╧ц║oрЩg╛БFЯ│OoG╟╙_o<0∙Yауу}ШI"вVi:№╔Бз▌Ё┌Q■Dш╟█Ж╡кцэ жU`j▐П%$-Б}ЮЪx╒х╡г°┴╢jщЖйс╫Ьпm╞╖·;░у√╠Ї┤┌√Щ@┬╝obЯ┤l▄qg╜╞▐╖ й┴╖┼С1УPcq3]LE╨╓╓fЖRR═75■T\╥╘rЮ@╒х╡▌7Ю┴ффд3<х)|ю└╤nIOтv'Eл╜|Ы ┌I?№ ░уы▐оЫ}H▄t¤юzЗU{дl╛╟эI<Е/Oр╞мя√╘vь■Є╫w|Чl┐ЙўндjЁ;cCq ├╠Жb=шTгБ╛qLMM9├XЙ]fбl∙I╘╦╣j/ЯC╟░У^HЙ^oЧС 8┴1 8╦╧·■▓lW/рОыїцЁ■D┤(Ъ┐ |{7╢яUEт1є█╤эЦъ▐ЄЧwлJi╤) ?¤ ╛х-∙]5ЧЯюR║N57>}Ё(Ьpг+к║юn|┘]яж8шЭ_сЎo=И╝.└=f{▀Ol╞з╜у2п╔ўн╘┘г C2#9$√M▒юнw ╣ з` 'с.RжцЄQtЫ>в@wсlgRн3БЖ╨хоL ыЭ_Нў¤ГЫ╤Y▀_ЦїО╦╝╣╝?-КцАк╟wo╧г╕я'x║г█й╒Kбz╙╙°тa╖Ж№аSp╓╥ьЄ^*аvkтz(oYФ╣ю°ы/>Н№─L/╓√VъьG27въ┬МфL)Еi0Нh┴н'з ┌jЪ]▐Kш╖оЗЄЦECЦ·¤Йихцд╨Б_▐БОRї_ё╘▄П¤фitFkk`yЭ╛▌q]wаJ╫~M╫}г╪бjьf║щ¤sx▀rtGsЙП эЮ╛бый╣G╙╚:г╡5░╝N7]LZабlno═╝4╗┐вХяOD-5за╗Б╘┐R∙пj┌¤;pУщ"∙·╤О·5ыz╦лВ╓M┐щщ/тp╥[╧╛IUk╧┘t┼╚0ы╟;╒:OmwFЫ▌╧Т╣╝o9щЪ╔ ;√L *ўB M╫Ho>T┐F]oyU└║щ┴tЕAo¤║Гк╢Юы2]02╠хcе═юoIЛ▐ЯИZОO] |(-&> ФЪ Gї╘╩K?жId kub░ь╞м;М├эе"вХНАИ╚R╝░@°УРD┤╪°УРDD╘vYКАИ╚R DDЦb "▓СеЪ■╨Й'╠ym╪░┴М-s эээfК───-; - └ tD+@ HX┐~╜ЩZ╣NЯ>═@┤ВЁ&0СеИИ,┼@DdйЕ ЄCт ¤c#Єє∙Й┴∙оOD┤L-| ЬD┴¤▒СBщ`╓■>°щ~°a<\Ўв`f═Лlw°NЫI"вF,nPа!3*Кй╚╠╓Б╖Fо[n└("й *н7Бм∙QЇИlам╒с╢8ЬЯ?╘єЕ^FцUYб]∙|їЮ{pП   °√V▄ы ╜╜ДХ 9$"jе┼ Щ Е;4УБ╛ёщЯ" !▒Kя$▓y]3.ОжК#║╘/ ПщuЇ '6нМё╛т┴>UўЇуЗгш65qщю╔:гtGББфв▌к╞АLОМх¤?╙J╡|щ>J@zТf╩ ▐МMН!Фш- 4Т70-4я=Аz¤Ў▐хЇ -%╕Я9}TэoGOл┌хjм╖ю#└й3▓ОкёпWA╢qц╓oоuRЙhе[▄{ЭГ(шnШRш├╬╨tўPo>TjHA.╤ jвГLй╓А,╩и└▓3цt√DRA▌╫Яы2]@2ш╩rXJi tbPю7╕ЯF*[n7Bw┘№╧UНJыU+р4Ою=К╙кЁ.+ ым╖>╕YЭ╙мYG╢= |Д¤?D╓т│АZасg╔ ╪╜└Х5 jщТ&]8fR╫ь▌∙zЮЇїЫVБw¤Ъы╔ у,>Є╒^№Сlд░g?2╜═ЁY@D+ @ ЁapD┤-n∙СеИИ,┼{-`╙/ВС ╠ї▐╤2&╨╣vYКАИ╚R DDЦb "▓СеИИ,┼@Dd)""K1YКАИ╚R DDЦb "▓СеИИ,┼@Dd)""K1YКАИ╚R DDЦb "▓Се┌&''з╠xCNЬ8БЎЎv3Eї9rщtZ┐╛√ю╗╕тК+p├ 7 Нъ╫хd%ЛXi╟CЎЪШШ└Ж ╠Ts└∙єчёЁ├гP(р╬;я─M7▌д )h>М={Ў  тю╗я╞┼_l╓ЄзХt,bеА╧ р╥K/┼=ў▄Г╢╢6У:mjj П<Є~ў╗▀aч╬Э&u┼"╜└Ёx&i1,╚▒4г┼╟╜ф╟C╘bє ╝╨b/┐№2^¤uЇўўыf▀╛}║[aы╓н·Uж%] YNЦo),#)═ф|UK5 v, ае╟3▀s▌т┐╤\,yИи N.║╢╢82&mA-ЁЕ'¤╩╥╡ Ўя▀П;vр╪▒c8wюЬ~Хi BЦ{Є╔'ї╕yПe6~?▒╥ОЗh╛Ц<МТЗУ(L в╙д-g/╝Ёn╣х=>88и_+=■°у·u╦Ц-xщеЧЇxUм▄L [JОШа)C)═КHїкe▓ UzD%V_оq▐cЩ═м╟в┬{╝┤/mИK┤ўЯ╖Pу╕╦Ч_╩уЩyоляЫs╠z╛╨╦╚╝*ы-u╔Й#q.дxF](еёЩєKщвь4ЕЙ^>ЕT▄Mлс╒*0╦╥gi=Ь9sы╓н╙у╟П╫пХ▄Ї╡k╫brrRП╧д U°Е╞жt┐ЇФJ3'╨7юд╔0BbЧm}├n0Э┬x_а╞rНє╦lъЛ№╗ўX╘0╪Y~|Еd]·ПYы╕%=ПЭf¤йBщ▐цZrн;Ю╩s]и▒oЭT√пN╝╔{iD уш ╠№[- ▌╚ц╨1м.в▒Ж║фюЯ0╗;_.4╜Мь╡ Y>Б|ПдK г■ЕW╡└TAд7ReЧУ>╩г`ЦпF )h─╞Нїk%7] )hк*N зЎ│┐Z│╚ь║ЖА▄DїВ░╤хjЁ╦lъ╦(╥и8ЦКу tGAж╓qK:Ж╨хП┤ ▓ї ХZv<Хъэ[а├Т█ВHGЗUсп╫ Є Аp▌rС;TAэ╫37MtЎ ж.╜Й¤5 ЩиUИVSн└ ┤#д/nзU╤9X┐лJ║<и╟c▒Ш~нt╫]wщ╫CЗс╞o╘у У} JM╥*O╦аLг╦╒с=Ц┘╠щXЪе■Ц╕KA║╔n├=Ю:√hЩ1"ё_X*5 L╒М╫tFtpЁЇUW!ЯЇС╧Т_╕p╖▌vxрl┌┤Iж\^ez█╢mz╛,'╦W%Б'Ы╞ий▓G╙╙}сЮ XЦ.╝╡тz╦5└{,ї╠~,▌И"Б▓(}|╙iz b=шмu▄╦╧E╦О╟Uкх╫█7╒:╒╜_c%z╦я[4┘В!j╡х2#ТBює5 grжZ^╡SЖ╕╛j%Р лGЭ~∙й|Oт╤G╒БD √зЮz ╨п2-щ2_Цл¤нSї~╥ tZ$╜∙РР}╪rюaФе Щs║""г▌╡ЧkPх▒T╙╪▒8]o╣.g_dИgЬ■q7-(▌lГЄлq▄ТюY^M~Ълu╟гx╧u*Xc▀Tс▀&е┐┤▄у2И▓їЫ9 в╓YЄ/ВIЧЛS3Нaм╨БўK?R#пzеU//╘:nS[╫р▌y&▌╗ Эю╚─█ =<сdбь>АЫ.┬╥}УыPы╢cЧ║ИMЄМuк∙рГЇЧЙ▄oЫ▐|є═еЫК╥╡ ╡╦хЄm╙Хt,bеС=▀оQа√Х√╝∙8б0R╨H┐▓t-╘н]·╨J:▒╥ОЗь┼@Dd){ш├8 "вЦрзАИИ,┼@Dd)""K1YКАИ╚R DDЦb "▓СеИИ,┼@Dd)""K1YКАИ╚R DDЦb "▓СеИИ,╡а? yя╜ўЪ1""j╞¤ў▀o╞ъєэoKhЇ ИИ╚╤L┘i╧oQ╦0YКАИ╚R DDЦb "▓╘Є ┼"СКf▓iє]ЯИhЕY┬РA▄S S┤╡╡ХёМЩлцПжБh7f║r¤i╒╙g__MЧ▐;ожuў+/еERЛZj┐╦{<Ю}Ў╚─Uz▌m╤J╢$└)T╗0dжEаoSSSеa,Д;Вf.P╚K∙э▀╒╓╡╥E¤їЛHE║А1є■c@Ч)kюЧ■z'}╝o:┤,┤z╟YТЩ>ЮйТ╣xcФ■И!lжЙ╚>KtбZH╓.|К) ┼░│Tиf02B╗Щм╡~ээ╬╢~∙l =Эf▓│▒l^еVЁьWf$Зdaю*Лi╓є':1X┌99╛щуЧ2╨Q└xЗУ@DVЄх=А╠оРьЯ.\Umv(╓3ў┬╢бїsШ(╒РГш{з╙√%%ЛDp║ЛеJЛH╦FЎo=SN░Т┬┐├Л┌b!"Є_ШQ√ЧЄ{▒Rї╝y│п▀Й┴BщRБD┬Sc╓fьWм╘¤#╡ё\╫Ї} аo\Ў▒#mдКEМж│╚&В╬q╚fЄ>СХ|f╘■U╤4С ├s;аI ош├╕з@З;T;`┌╠¤Єt#Zе┼рЭшЙeС/╕┴{ЬI╞√<7╟Й╚■ Uj (О"Н(╠¤█ц5╜~й^U╪{?14c┐д@┬А{WU▐г▓┼░╪╘>F╩>╜ў▄ЇХ.л∙Q"ZЙЦ$шO▒╕▌е■sSЁV╘▓g~|│╓·╒╙^┐ЇС╬ ╥╤ВзП╝·~uM╗])*─,т сZ╟яhЗчЕ|\i▐╕JDф√╟A╦╟Gzж<Яhi╬|╫'"Zl|┤&]ЮПg6m╛ын\>Э4_ЬЫ∙оOD┤r∙<╤BY>@>х2Я╧л╧w}"вf @эЗЩU{H┘Їзyк?┤MoпФ.Г|ё╔╠RЪ[▀П_ъrUg╡ПХQ╦7qЮЙ╚KЇ╟k<╠м╓C╩▄З╣eт╙m+$sш*+=▀╬Э* ╪c¤їk? ╬wfy╚Ы╫\╬3┘cI@нЗЩIБU¤!eю├▄╩?╒шО"ЬЫhааЮm¤чuЄVй∙єLD6ё═=)Фj>дм╓├▄эХ╘Cш2]#e╧чoh¤┘ч3Є╓и║чЩИмтУP !eН= N>ЄщvНМ!Фш-uН┤фap╛R∙Р7У<+> ОИж∙$╘{HYЭЗ╣'РлxhЫCjЁf┤╤їgyЬ?╣y3У│т├рИhЪo║Аj*{ШЫє╢s▀W>┘У ╡;ЕW&>¤Lя├┘]┐д╩├р|дцC▐*GD4Ы% ·╙)│<╠╠U∙0╖╬A∙ИО╙ULД0ц▐ v g╥e█б1є(Но_єap■╥╠C▐Ъ9╧Dd> ОИ╚g°08н№cЫ═Ыя·DD+Ч╧GD┤P№ШИИСеИИ,┼@Dd)""K1YКАИ╚R DDЦb "▓СеИИ,┼@Dd)""K1YКАИ╚R DDЦb "▓СеИИ,┼@Dd)""K1YКАИ╚R DDЦb "▓СеИИ,┼@Dd)""K1YКАИ╚R DDЦb "▓СеИИ,┼@Dd)""K1YКАИ╚R DDЦb "▓СеИИ,┼@Dd)""K1YКАИ╚R DDЦb "▓СеИИ,┼@Dd)""K1YКАИ╚R DDЦb "▓СеИИ,┼@Dd)""K1YКАИ╚R DDЦb "▓Се┌&''з╠xCNЬ8БЎЎv3U▀╜ў▐k╞ИИиў▀┐лobb6l0S═Y╨@DD k>А]@DDЦb "▓СеИИ,┼@Dd)""K1YКАИ╚R DDЦb "▓СеИИ,┼@Dd)""K1YКАИ╚R DDЦb "▓СеИИ,┼@Ddй╢╔╔╔)3▐Р'Nа╜╜▌L-_ў▐{п[\ў▀┐Ы┐ЙЙ l╪░┴L5ЗАИм%аХЕq#Z¤Ює ь""▓СеhЖ#GОр{▀√║║║░uыV¤*╙Тюw╦y▀¤Кчtхт=*9■<~°a ▄yчЭ╕щжЫp┼Wр▌w▀┼с├З▒g╧ГA▄}ў▌╕°тЛ═Z■░Ьў▌пl8з╝░▄SИDR(ЪIjЭ|лVн┬уП?О/|с °шG?КK.╣D┐╩┤д_t╤ExшбЗ╠-╨в┐чвю╗%yРчtхєG╚─╤╓╓6=─3fЖхёвx∙хЧё·ыпг┐┐_  кСЇ{ю╣G/'╦╧TD*т∙;zЖHjсОв5√оTЮoЩnЛ`z╫3И╖┼╒ 5x╫_цZх9▌╖oв╤июТWЩnшЬ╓│╠╧╤J░фаШКан ЫЪ┬Ф╞0R√"гСNзu3┐▓▄УO>iж╝ш7╟Bсpє7я ШeZп5√о┌╩цQ0У┼╤4ЄеД ф┬Ъ╔}яSga∙єЮ╙¤√ўc╟О8vь╬Э;з_eZВАи{N╔╫Ц<╚5Ц, в╙LЛ╬A3нk`n-▓вцUY{pзїkqSНgжkе║aaЦK┼▌эЪЇ :0Щ∙mю√ФэПйV╝Яw{e█([▐}чШк.з▐1╒Ы@6Ы@Pеыt┘√ЫєQe{╬r▐Ъыь^xс▄r╦-fк╛-[╢реЧ^2SНл~Ь"П]еє╫▄~Л╓э{'zb9LШў/фC╪╣3Д▄tэ.ЁгХy╚№-К│■эжПqF>╙█h"?i╥2йX╛╞√5╩{N╒їXНtЙYєC┘╛╕╫q┼9┌}Rз╬8зв┌▒шєTСяйiK╥┘┌лVЩTж&sjРЕd]е1Лl├j╜▒Ж║zw|└ф*╙х{ЬэЦе╗TцъMДJнТ▒Р╘ eЄ╪i╥ж Qд{▌эЩўs╖╫хd╚@▀╕Уж╙CHь2√_z'╨U_N╒жЗзk╤у}Е┌чгb{sqц╠м[╖╬L╒╖vэZLNNЪй╞╒>╙чO5Рp╧kГZ╣я┴Х'Gu Г и┌~gBщQ╜?ЩС!Д▄╠Z7U√█U╔;UєЩ╥T~Т ОnBЧц v╓╔л ЄЮ╙у╟Пы╫Jnz¤sZы:о8G█пмqNы]wє╧ў╢[·{╡Ъ╘║╣ЭD┐∙╦║г5╪5Ов[оSu5З╜уzжт┘nY║K║0Д.S│╨-┘Эfj"*Sg▌юў=Д*0bjI]iЇ╓\║ЖакТN╞ї╛┐и╡ЬW╜єQ╣=щКШG3╜.r▒╦E▀╣╪хвoZ═є1}■Їqy║a╤╩}Чў╫}>┼Qд╤оКйа ▓?* фbшq╧єly╚лV▐йЦ╧d∙fЄУ▐╧╩№T'п6╚{N7n▄и_+╣щu╧i3╫q╡sZў║л8nj┌ТАP6 ]сЄХN ъGFt╞3MLХс▄>mgиSєxS]м┴4в│мЇЙЫYeфвnd╣&═°ГЪй·:Дo╝╤L5hП│е√.Е▓PЕ°П<ЄHщЫЯ7▀|sщЯ4єежч╫o~.ч}/уг№┤b╬ie▀╩Хя#НЇ`кЕлj№ЇM`А∙ZA└%╧xС╧Б╦G√фbЧЛ^·xеЩ▀t═С-ч}╫|ШЯЦ¤9н├[╦╟lw╡ПcБ╦""?hua▄?Ае (- ╢И╚ZR_ ь""вyc5НАИ╚R DDЦb "▓СеИИ,┼@Dd)""K1YКАИ╚R DDЦb "▓СеИИ,┼╟AС╡ф╖НЧВ№╛rлЁўИИц@@+ уF┤·=ИИц└-М▀{я=У▓░╓мYул└{DDЦbаО9Вя}я{шъъ┬╓н[їлLK:-ЮwZlьвТєч╧усЗFбP└Эw▐ЙЫn║ W\q▐}ў]>|{ЎьA0─▌w▀НЛ/╛╪мEє┼є╛t╪Dd<°рГX╡j№q|с _└G?·Q\r╔%·Uж%¤вЛ.┬C=d╓аVX╘є^L!Iбh&╔nK"mmh+ дVb╬\▌╦/┐М╫_¤¤¤·o▒o▀>DгQ▌!п2-щў▄sП^NЦпJО╡ьoZ■w-ж"eє"╙3м,Ш*╧{5Т>ыyпЗЕ>╒░Ї-АpЕй)L╔PИ"М#cf╤тIз╙║√Aь▀┐;vь└▒c╟pю▄9¤*╙Д,ўфУOъёк╝S=Мг/р■┴t╘3o бDp:X╚{▐g3ыy'jТ┐║АэЩQнм6iГ╖6гч╗гИTdfЁ(лqЦнчn╫╘NїvуИG▄Ї6─═╞╩kн▐хSH┼%═y▀к╦╔~ї&Р═&Tщ║░+{ ЄуЄn╧YnqZE/╝Ёn╣х=>88и_+IWД╪▓e ^zщ%=▐╕ v%АфpT,0:1XHкRp╘┌┌йў╝╧f╓є^-_Uц┐▌'uъи╬g╬рцєЄїkчєыt?|шЗ╚Ю6╙ЁW╚М`(▄Ба3Бx0Б╨ШS[,$sшТЬ*A"ЫGA-QM#Ftо, w]Ce▐▐Dcж╞9Тїd╗yьtkб╥ъш5Б!ЫC╟░IЛaи╦╔ЁБ╛q'MзЗР╪e.uQх{$}Pe╡Ц аo8Й░йПўкЧи╪▐b:sц ╓н[з╟П?о_+╣щk╫о┼ффдп╩6nгoq╣p▌╙е┐├єў┤Сў╝╧ж■yпq╜Tц┐эWzЄЩфSХ╧$ ╫╗.Ц._╥┬Z·р-,:P75D]`$╤or\а;КЁ╨И╩жЭшЙх0бrf!BOrd∙P╗зvйш┼║L═еS╒l;e9Эf▐S]4Y╖ЄPЭ=Ий%х}╩jF]C@n┬╣0<√з╒Z╬лцq)Х█ Їa▄tЯ,4)Дд07n╘пХ▄t)Дд0к╔6║ С┴¤Ы╥ ▐є>Ы║ч╜^╛кф═g┴Дх╡юuQС/Wкїa№┘Я ┬ы═┤№sааj) ╓UЮE╛Р┴HN╒°▌ИцTF/фъй╠еЭ╘ЕPFt╞6M╪╩к^═цMUи╙И╠▓▓ЯfV)№Y╬зд{ср┴Гz<S═к*ю║ы.¤*c╗ё╞їx├tM?Н╤╩Ии оКЦЫE╝ч}6s:я═hц║а┴?]@R█Cй╢ю ╕╜-╥▌УНїш )╡ эV5╦drdши,ETб╫Щ HЖUНх█нIwGй┴5j▄╙2╨√сМ╬To97╕╒9оtЛbqю╚'}фєц.\└m╖▌ЖxЫ6m╥Я;ЧWЩ▐╢mЫЮ/╦╔Є═щDH╕▌ Ъ╙m¤w┤Уў╝╫3ыyЯ-_═V╣кX▀N°ёCс╟f╥■║╨9шЇ]ъB╧╣AШыrЪдAщ╦4┘YeV)]гж┤Х╔!╒и╓┐МD╨i╥╢СОг/P╛]=╕7З╒PъОъ╞дыBжЭбщЇ▐|иz═╛▐r2/ц4п#й`эуZB7▄pГ■В▀гП>кkR╪?ї╘S8pрА~ХiIЧ∙▓Ь,_Уў<ъ┴ brПдM{ц╔IЦ√"цW╢▐ ┐сhTЮўj;яїоO■37Бgкs]╪тЇ)ЬDо╖ш{о№&░Kj█╜└░┼¤╒|ЁyфС╥7Rх█КюНGщ~р7R╧√╥q┐Хл┐ <ёc<Їъї°є ╕pхЫ▀╛ ╠рb(Сg╧╚ч╙х#ЗюНGщ{ЦюЗ║5ЪЮў┼ч ?■!NEЎ&0СOФ╡СO┤║0nСHa╝ИИh▐ц№ї1P""Z4 DDЦb "▓СеИИ,┼@Dd)""K1YКАИ╚R DDЦb "▓СеИИ,┼@Dd)""K1YКАИ╚R DDЦb "▓СеИИ,┼@Dd)""K1YjeАb СH E3YR+}б,Ў√═┴2 Eд"mhЫO┴ъ-ШkН√╔r╪G"Z╢ЦO(О"НШ·Ч╞ш\K┴@╞╟√0У%╡╥¤d9ь#-+╦&G╙@┤=б,╥▐ 5у6╒2Р!Ш@╓$WM/╒вUkвWеeк∙СЭ;╦k┌юzmqd<ыетnzтzЖ╠КФ╥┌┌"H╒ N5╖G\Z7fЮ│э┌GwDd╜╢╔╔╔)3▐Р'Nа╜╜▌L═▀kп╜f╞к√─'>б■ЧюЯ^`x}Е8┌:P╨╡с тm]└╪;e1Uъ┼┌▒лZ·0╨лчлuK╦z╟╦╫УВ3Ш▀Йй■ DT ╣█╦xў┴C╥GzЬх▌mЫYjf┘╛Цo;НhAЫ,,█╨Л в│√(╟╠cзlO'╟БAg\╠v■ЙиїЬrн5&&&░a├3╒$ ═ GПЭjеb▒h╞ъ($з┬сфTAOМM┼ЮJ╩DY║тN7Т^m╝┌zИMНUKўо HuЖj█╒╢Qm█╡О╧ot;еi┘ЮьЫZ╞╠"ве╙PЩ╫)УлХ╒Н ╦в H║▄оР6U√BE7╨RСZ╣о╜OA¤а ]и`р3ЭФ}Ыъ┴И>ж╦ИИм╖ @гщ,bcжРХa,Жlz┼@;B┘щЫ┬:P╚HнЇJ┘< fT╙ы%░╦э;Чїb=еюТк┬QtЫ~ЮЪя#▌vfCЮm╬{UРКы HЖsШрM"R№ЇзТшўЦpЭ=Ищ^jc!$В╬ ╬▐|╚╘└kе{·░36Д.5?▓√дITыйZ|о╦Y/ШaLwи╫ █╣-УяSRg█е╓Нд{▀╜w╨Т}lЗ:╬╢█ВHGЗЭ{ Dd=_▄nх Сe╟{г╫$╤╩╒ъ2o>7БЧ╧ўИИиеЦ┐рEDKДАИ╚R DDЦb "▓СеИИ,┼@Dd)""K1YКАИ╚R|-╕{я╜╫М-о√я┐▀МСm№Ьч№Ї, Zpr1.va╝яI■сч<╟З┴╤Тc "▓СеИИ,╡╝А№ЪV$■─-QєЦOР┬▐¤▌\∙Н\¤CчU0(°\ё╥▀1ожЦБL╝ФяЇў┘^3╧7мШКCъoъ╖?уRX6 L#ZШ┬╘Ф cх fN■┬ЦпeтЄлў╬▀▒Р╠б╦чWa1q~и_ч;g├╚Є\TбИ╤|║UсРz:M▓┼ЦQPэеR╜ГГ=]KЛ ╡▀╘ЖtнHE∙╚t═mFYcjNй╕╠7╡╤▓ЦЖ'═нaщ∙n═╡ИФz╫Е╗^┘▓jЯX532КХ.╝@wс▄Дпkогi YT9nZчаЩоЩWжє]<#y─o`╛(█о╔?z=7пzЧWыў&Р═&Ti╥2f^Ь)г╧[Й!ч╩ж1║╪▌&*OKm^WxЇPеrSєт ц&~01ДDPЭУо!╒ЄRп \f,╦&D╝мКВ#╪7О1t╒■dР╚М`╚█"иFwI$░╦╝Хtўdc=·тУРЛvлFw292bj┼т·╜хт+ 6- ╦√]gТЦ╘Рщ>3чWZP╬д/ї'б НЄB4W╙uЄ╩╝Tl╖an+Бyq&╖╥SпъЬ─LА╡╜ GY6 ЪыrЪ─║YЬFt╕№У>ЭГc%ВИь>iRщr╫С{│~:H]4Е$rR;╨5Ж╞▄Lв.Ld╒~Ш"УCк5а'eB╜╖│Aдг├ьЎйA■Nиv~}*а*║▀═GjшВЇuЄ╩╝ФoWюН▄Z}╪iюСEF Рy▒ўc?┼Qф;дGbх> Tj ┤1╦я╡─╩ №BQ]lYК┐F N·FЧя╪╦╧y╬OўИИo╤Тc "▓СеИИ,┼@Dd)""K1Yj┘|`╦Ц-fмq/╛°в#"Є~╠гЩ╨LБ▐ьЄDDЛБ_#"в%gQ("ё№╚Жgи√KbDD+ФE А╛qєлЕ$┬Ю▀]чO&СЕ╪$ф╫├")дт╥"p~ ╡ШКxZ ц╖T╦ЦsўзК╦ЦЧЯЁ╙╦╞ў┤:J?kмЯ╒MЯ■╜┘█p=╦ЄўЕЙиu\┘Є=╥"Д№▓л№мn-╚0B┬¤Хю╥rТ├╨АS╪ў╩o┬Ъх╟Bц║│9t {ЦэТ┬>Гx0Б╨ШУоoV"C╒m╚▓yь4iSЕ(╥╜│№>,QГ\с$·╜┐щэнyw ╣ зрї.ь@X^эA~Ф█й═w:Aсиєгёв│1ф0▒9╧6▌QДЗFРй╢НвZVзЩ¤PБ#Ы5┴ЕИhЮкС┬?ШF┤р╓╝УNA_S'u-╜#║░Юю╓i\НmxюU8Г .DDє─PЛзЎ^M#ыМVзF\w╬K!^@2мj·o:│J2#Тm~^╒Ї│ ╕=Jz█▒U█п▓ Ф/KD╘J ╒·░3Ф@╨t╜ЇцCї[Бv t║i┌ВHGЗ╤wНJWЕ╖╗Н╢.`L H╜*рUЛ"╫хде▀P╒щлm#P╛м▄Ы├DDє─o/щFъЖuбODфр7БчH їF""кП? OD┤И╪ "в%╟@Dd)""K1YКАИ╚R DDЦb "▓СеИИ,хЛo┘─/▀^Є@DDs╟GAQ╙ИИ,┼@Dd)""K1YКАИ╚R DDЦb "▓СеИИ,┼@Dd)""K1YКАИ╚R DDЦb "▓СеИИ,┼@Dd)""K═щGсЙИ╚?ц·гЁM""Z╪DDd)""K1YКАИ╚R DDЦb "▓СецЇ=А╡¤╩МQ-У■q3╢╝╜Ї╥KfМj╣ё╞═Xu√ўя7cT╦ч? y3Vєсьf╦З╒0,РХ.┐№r3EХ▐ ¤Ж└Ц-[╠Uzё┼ ╠З╡5ТлЩWxє╓п4эЪ╟~г_ь└0 є7╫└{DDЦb "▓Сеь o№_·╥ПЁЖЩ$"▓Н└│;p═5╫L;Ю53h┘:√ Ўэ┘Г=есy╝mf═Лlw▀+8k&н'Хu═|щGЮъ╠|*8╢UОfф╙=x╛%╒ |▐°╤Чp═v`ўЫoтM3ь╞3`X╓| █ю╝w╩░xоўъOтЎ█?Й╒fТФЫ┐В═{┐o а&xєщ╢OсьsvА7Ё╙╜└ўЎ n5)т╓ЭiJ-Г/9▄╘P~┤c║┼0▌`x;J╦ЫtSC*█-╛ло╞╞ў╬т}SГхyOлам╢пHФ╨i▐V├█x^ж╜-А▓ї<█Є╧Ї┘WЎЩe╒╨К`фA─┐╣ў Vй6U═ ╬uRj5шed▐°╤wю├бCўс│▐∙6С ЖБ№ьy╠╔├╬P e╦Ы╝ьSK▐°)Ў┌МM╫Ъщ ╫~эяJнВ7w{2╕╩аЕmn·WЁ╖ CЪм*є~I7%Jыu!vl<О╖▄ ыэ╖p|у╒jyЧмў м▐ъм╖эSgё\╜jЫ║0Я ┼jl5я│u╡ Ff╓КpыГкх╝▌S╡Є нxЁg▀З║а╘Rшя┼?√;|э┌kё╡G┐ПЫo■>~жЦ ╗п╒╕0W║╒л▒Z*+їЄX)лaыF╫гF^v╓Ё▄╕9ИMftoэe√▀G_w oХAуnУaS7╦лx╥┼пу(■█▌m|Vj7╝nf╙S╔3ж6Ї╠[WcЫ█uгЪ▄ЯtKЄ│g╒r╧╣╡&u┴╜з/>i4и ╦DА╖▀:ЛOХVRd=╧vVл╞Ъуo95┤jфв╓яу╘то║EВ╧╩rk№√8к+CF╜№э╫ЁшўПкyЯ┼▐;UЕ┐^Г╝ъх1o╛|5╓╚kЭ╝ьGKо▌Д═ЗЎтз╒кфR°Vj&nэE╒L╠мжШ┌М█*xє═Єю&Z@▐╛╒z¤Ў▐хЇ` чл>ЙOЭХ юm╝uV╒■ч╒ёn╤█V╡2}Бz╗ЧV)╘я╪Л┴ЯЪiQ' _╗i│г2║р7н╒f╒╩╦>фГ└нPХ▄ў┘e7}Я▌aжo╛lj&o№t/9г╒]√╟╕ўбмTШК4Є▌▄■^йZпVнА│к¤Цjn_U@*╓;√Ў[x╧э"Є╘║t║y╧ыY █Ёй5ъ"_97Jо¤┌7╒еj·zв^■;tПщnl╛пт▓╒нd╙э╙б*,їЄX5uє▓ °в H·∙жЫвжЩкЖэ╪Ж[UmцЫЫЭЫQТЎЭ┬цYZN х╤э╙█┘ём╙╫щM╗ЖЯ ўU ЫO^87╬╘р╣A+═ю│╟БОOVV ╦╫{F·ўoQЧцъOтщл5█z■мiЮлЛ┐x╞╝╟3xыъ[0cУ+В╩є╗┐2=^5 ╦M`)¤е5 ╦oЮоД╔uўз█╚ЪЫ└Юо╩={ЄX╜эNHVкЩ╟jкЯЧ¤ЖГk1> ╬.|▄№ёapє╟З┴QSИИ,┼@Dd)■"╪с=;Ё└№ё└№═ї└сo█гС@їё7Бчo╤-╝@Dd)""K1YКАИ╚R DDЦb "▓СеИИ,┼@Dd)""K1YКАИ╚J└ │%°Ес╝IENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/index.rst0000644000175100001770000000232014654363416015164 0ustar00runnerdockerWelcome to :mod:`guidata`'s documentation! ========================================== .. image:: images/guidata-banner.png :align: center Based on the Qt library :mod:`guidata` is a Python library generating graphical user interfaces for easy dataset editing and display. It also provides :ref:`widgets` (Python console, code editor, array editor, etc. - see ), helpers and application development tools for Qt. .. figure:: images/layout_example.png :class: invert-in-dark-mode Simple example of layout generated by :mod:`guidata` (see :ref:`examples`). :mod:`guidata` is part of the `PlotPyStack`_ project, which aims at providing a full set of Python libraries for data plotting and data analysis. External resources: * Python Package Index: `PyPI`_ * Bug reports and feature requests: `GitHub`_ .. _PyPI: https://pypi.python.org/pypi/guidata .. _GitHub: https://github.com/PlotPyStack/guidata/ .. _PlotPyStack: https://github.com/PlotPyStack .. module:: guidata Table of contents ----------------- .. toctree:: :maxdepth: 2 overview installation examples widgets autodoc/index dev/index reference/index changelog * :ref:`genindex`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/installation.rst0000644000175100001770000000103414654363416016557 0ustar00runnerdockerInstallation ============ Dependencies ------------ .. include:: requirements.rst Installation using pip ---------------------- The easiest way to install guidata is using `pip `_:: pip install guidata Installation from source ------------------------ To install from source, clone the repository or download the source package from `PyPI `_. Then run the following command (using `build `_):: python -m build ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/overview.rst0000644000175100001770000000442014654363416015726 0ustar00runnerdockerOverview ======== When developping scientific software, from the simplest script to the most complex application, one systematically needs to manipulate data sets (e.g. parameters for a data processing feature). These data sets may consist of various data types: real numbers (e.g. physical quantities), integers (e.g. array indexes), strings (e.g. filenames), booleans (e.g. enable/disable an option), and so on. Most of the time, the programmer will need the following features: * allow the user to enter each parameter through a graphical user interface, using widgets which are adapted to data types (e.g. a single combo box or check boxes are suitable for presenting an option selection among multiple choices) * entered values have to be stored by the program with a convention which is again adapted to data types (e.g. when storing a combo box selection value, should we store the option string, the list index or an associated key?) * showing the stored values in a dialog box or within a graphical user interface layout, again with widgets adapted to data types * using the stored values easily (e.g. for data processing) by regrouping parameters in data structures * using those data structures to easily construct application data models (e.g. for storing application settings or data processing parameters) and to serialize and deserialize them (i.e. save and load them to/from HDF5, JSON or INI files) * update and restore a data set to/from a dictionary * generate a data set from a function signature (i.e. a function prototype) and use it to automatically generate a graphical user interface for calling the function This library aims to provide these features thanks to automatic graphical user interface generation for data set editing and display. Widgets inside GUIs are automatically generated depending on each data item type. The :mod:`guidata` library provides the following modules: * :py:mod:`guidata.dataset`: data set definition and manipulation * :py:mod:`guidata.widgets`: ready-to-use Qt widgets (console, code editor, array editor, etc.) * :py:mod:`guidata.qthelpers`: Qt helpers * :py:mod:`guidata.configtools`: library/application data management * :py:mod:`guidata.guitest`: automatic GUI-based test launcher * :py:mod:`guidata.utils`: utilities ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1722935059.1487052 guidata-3.6.2/doc/reference/0000755000175100001770000000000014654363423015262 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1722935059.1487052 guidata-3.6.2/doc/reference/dataset/0000755000175100001770000000000014654363423016707 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/reference/dataset/conv.rst0000644000175100001770000000006314654363416020407 0ustar00runnerdocker:tocdepth: 3 .. automodule:: guidata.dataset.conv ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/reference/dataset/dataitems.rst0000644000175100001770000000007014654363416021413 0ustar00runnerdocker:tocdepth: 3 .. automodule:: guidata.dataset.dataitems ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/reference/dataset/datatypes.rst0000644000175100001770000000007014654363416021436 0ustar00runnerdocker:tocdepth: 3 .. automodule:: guidata.dataset.datatypes ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/reference/dataset/index.rst0000644000175100001770000000021614654363416020551 0ustar00runnerdockerData set features ================= .. toctree:: :maxdepth: 2 :caption: Contents: datatypes dataitems conv io qtwidgets././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/reference/dataset/io.rst0000644000175100001770000000005014654363416020045 0ustar00runnerdocker:tocdepth: 3 .. automodule:: guidata.io././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/reference/dataset/qtwidgets.rst0000644000175100001770000000007014654363416021453 0ustar00runnerdocker:tocdepth: 3 .. automodule:: guidata.dataset.qtwidgets ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/reference/guitest.rst0000644000175100001770000000005614654363416017503 0ustar00runnerdocker:tocdepth: 3 .. automodule:: guidata.guitest ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/reference/index.rst0000644000175100001770000000021014654363416017116 0ustar00runnerdockerReference --------- .. toctree:: :maxdepth: 2 :caption: Contents: dataset/index utils widgets userconfig guitest ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/reference/userconfig.rst0000644000175100001770000000006014654363416020156 0ustar00runnerdocker:tocdepth: 3 .. automodule:: guidata.userconfig././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/reference/utils.rst0000644000175100001770000000021114654363416017150 0ustar00runnerdocker:tocdepth: 3 Utilities ========= .. automodule:: guidata.utils .. automodule:: guidata.utils.misc .. automodule:: guidata.configtools././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/reference/widgets.rst0000644000175100001770000000020014654363416017454 0ustar00runnerdocker:tocdepth: 3 Widgets and Qt helpers ====================== .. automodule:: guidata.qthelpers .. automodule:: guidata.widgets ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/requirements.rst0000644000175100001770000000436114654363416016607 0ustar00runnerdockerThe :mod:`guidata` package requires the following Python modules: .. list-table:: :header-rows: 1 :align: left * - Name - Version - Summary * - Python - >=3.8, <4 - Python programming language * - h5py - >=3.0 - Read and write HDF5 files from Python * - NumPy - >=1.21 - Fundamental package for array computing in Python * - QtPy - >=1.9 - Provides an abstraction layer on top of the various Qt bindings (PyQt5/6 and PySide2/6). * - requests - - Python HTTP for Humans. * - tomli - - A lil' TOML parser * - PyQt5 - >=5.11 - Python bindings for the Qt cross platform application toolkit Optional modules for development: .. list-table:: :header-rows: 1 :align: left * - Name - Version - Summary * - ruff - - An extremely fast Python linter and code formatter, written in Rust. * - pylint - - python code static checker * - Coverage - - Code coverage measurement for Python Optional modules for building the documentation: .. list-table:: :header-rows: 1 :align: left * - Name - Version - Summary * - PyQt5 - - Python bindings for the Qt cross platform application toolkit * - pillow - - Python Imaging Library (Fork) * - pandas - - Powerful data structures for data analysis, time series, and statistics * - sphinx - >6 - Python documentation generator * - myst_parser - - An extended [CommonMark](https://spec.commonmark.org/) compliant parser, * - sphinx-copybutton - - Add a copy button to each of your code cells. * - sphinx_qt_documentation - - Plugin for proper resolve intersphinx references for Qt elements * - python-docs-theme - - The Sphinx theme for the CPython docs and related projects Optional modules for running test suite: .. list-table:: :header-rows: 1 :align: left * - Name - Version - Summary * - pytest - - pytest: simple powerful testing with Python * - pytest-xvfb - - A pytest plugin to run Xvfb (or Xephyr/Xvnc) for tests.././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/update_requirements.py0000644000175100001770000000127714654363416017774 0ustar00runnerdocker# -*- coding: utf-8 -*- """Update requirements.rst file from pyproject.toml or setup.cfg file Warning: this has to be done manually at release time. It is not done automatically by the sphinx 'conf.py' file because it requires an internet connection to fetch the dependencies metadata - this is not always possible (e.g., when building the documentation on a machine without internet connection like the Debian package management infrastructure). """ import guidata from guidata.utils.genreqs import gen_module_req_rst # noqa: E402 if __name__ == "__main__": print("Updating requirements.rst file...", end=" ") gen_module_req_rst(guidata, ["Python>=3.8", "PyQt5>=5.11"]) print("done.") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/doc/widgets.rst0000644000175100001770000000314414654363416015530 0ustar00runnerdocker.. _widgets: Ready-to-use widgets ==================== As a complement to the :py:mod:`guidata.dataset` module which focus on automatic GUI generation for data sets editing and display, the :py:mod:`guidata.widgets` module provides a set of ready-to-use widgets for building interactive GUIs. .. note:: Most of the widgets originally come from the `Spyder`_ project (Copyright ┬й Spyder Project Contributors, MIT-licensed). They were adapted to be used outside of the Spyder IDE and to be compatible with guidata internals. .. _Spyder: https://github.com/spyder-ide/spyder Python console -------------- .. literalinclude:: ../guidata/tests/widgets/test_console.py :start-after: guitest: .. image:: images/screenshots/console.png Code editor ----------- .. literalinclude:: ../guidata/tests/widgets/test_codeeditor.py :start-after: guitest: .. image:: images/screenshots/codeeditor.png Array editor ------------ .. literalinclude:: ../guidata/tests/widgets/test_arrayeditor.py :start-after: guitest: .. image:: images/screenshots/arrayeditor.png Collection editor ----------------- .. literalinclude:: ../guidata/tests/widgets/test_collectionseditor.py :start-after: guitest: .. image:: images/screenshots/collectioneditor.png Dataframe editor ---------------- .. literalinclude:: ../guidata/tests/widgets/test_dataframeeditor.py :start-after: guitest: .. image:: images/screenshots/dataframeeditor.png Import wizard ------------- .. literalinclude:: ../guidata/tests/widgets/test_importwizard.py :start-after: guitest: .. image:: images/screenshots/importwizard.png ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1722935059.1487052 guidata-3.6.2/guidata/0000755000175100001770000000000014654363423014175 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/__init__.py0000644000175100001770000000307614654363416016316 0ustar00runnerdocker# -*- coding: utf-8 -*- """ guidata ======= Based on the Qt library :mod:`guidata` is a Python library generating graphical user interfaces for easy dataset editing and display. It also provides helpers and application development tools for Qt. """ __version__ = "3.6.2" # Dear (Debian, RPM, ...) package makers, please feel free to customize the # following path to module's data (images) and translations: DATAPATH = LOCALEPATH = "" import guidata.config # noqa: E402, F401 def qapplication(): """ Return QApplication instance Creates it if it doesn't already exist """ from qtpy.QtWidgets import QApplication app = QApplication.instance() if not app: app = QApplication([]) install_translator(app) from guidata import qthelpers qthelpers.set_color_mode() return app QT_TRANSLATOR = None def install_translator(qapp): """Install Qt translator to the QApplication instance""" global QT_TRANSLATOR if QT_TRANSLATOR is None: from qtpy.QtCore import QLibraryInfo, QLocale, QTranslator locale = QLocale.system().name() # Qt-specific translator qt_translator = QTranslator() paths = QLibraryInfo.location(QLibraryInfo.TranslationsPath) for prefix in ("qt", "qtbase"): if qt_translator.load(prefix + "_" + locale, paths): QT_TRANSLATOR = qt_translator # Keep reference alive break if QT_TRANSLATOR is not None: qapp.installTranslator(QT_TRANSLATOR) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/config.py0000644000175100001770000002604414654363416016024 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """ Handle *guidata* module configuration (options, images and icons) """ import os.path as osp from guidata.configtools import add_image_module_path, get_translation from guidata.userconfig import UserConfig APP_NAME = "guidata" APP_PATH = osp.dirname(__file__) add_image_module_path("guidata", "data/icons") _ = get_translation("guidata") def gen_mono_font_settings(size, other_settings=None): """Generate mono font settings""" settings = dict({} if other_settings is None else other_settings) settings.update( { "font/family/nt": ["Cascadia Code", "Consolas", "Courier New"], "font/family/posix": "Bitstream Vera Sans Mono", "font/family/mac": "Monaco", "font/size": size, } ) return settings def get_old_log_fname(fname): """Return old log fname from current log fname""" return osp.splitext(fname)[0] + ".1.log" DEFAULTS = { "faulthandler": {"enabled": False, "log_path": f".{APP_NAME}_faulthandler.log"}, "arrayeditor": gen_mono_font_settings(9), "dicteditor": gen_mono_font_settings(9), "texteditor": gen_mono_font_settings(9), "codeeditor": gen_mono_font_settings(10), "console": gen_mono_font_settings( 9, { "cursor/width": 2, "codecompletion/size": (300, 180), "codecompletion/case_sensitive": True, "external_editor/path": "SciTE", "external_editor/gotoline": "-goto:", }, ), "color_schemes": { "names": [ "emacs", "idle", "monokai", "pydev", "scintilla", "spyder", "spyder/dark", "zenburn", "solarized/light", "solarized/dark", ], "default/light": "spyder", "default/dark": "spyder/dark", # ---- Emacs ---- "emacs/name": "Emacs", # Name Color Bold Italic "emacs/background": "#000000", "emacs/currentline": "#2b2b43", "emacs/currentcell": "#1c1c2d", "emacs/occurrence": "#abab67", "emacs/ctrlclick": "#0000ff", "emacs/sideareas": "#555555", "emacs/matched_p": "#009800", "emacs/unmatched_p": "#c80000", "emacs/normal": ("#ffffff", False, False), "emacs/keyword": ("#3c51e8", False, False), "emacs/builtin": ("#900090", False, False), "emacs/definition": ("#ff8040", True, False), "emacs/comment": ("#005100", False, False), "emacs/string": ("#00aa00", False, True), "emacs/number": ("#800000", False, False), "emacs/instance": ("#ffffff", False, True), # ---- IDLE ---- "idle/name": "IDLE", # Name Color Bold Italic "idle/background": "#ffffff", "idle/currentline": "#f2e6f3", "idle/currentcell": "#feefff", "idle/occurrence": "#e8f2fe", "idle/ctrlclick": "#0000ff", "idle/sideareas": "#efefef", "idle/matched_p": "#99ff99", "idle/unmatched_p": "#ff9999", "idle/normal": ("#000000", False, False), "idle/keyword": ("#ff7700", True, False), "idle/builtin": ("#900090", False, False), "idle/definition": ("#0000ff", False, False), "idle/comment": ("#dd0000", False, True), "idle/string": ("#00aa00", False, False), "idle/number": ("#924900", False, False), "idle/instance": ("#777777", True, True), # ---- Monokai ---- "monokai/name": "Monokai", # Name Color Bold Italic "monokai/background": "#1f1f1f", "monokai/currentline": "#484848", "monokai/currentcell": "#3d3d3d", "monokai/occurrence": "#666666", "monokai/ctrlclick": "#0000ff", "monokai/sideareas": "#2a2b24", "monokai/matched_p": "#688060", "monokai/unmatched_p": "#bd6e76", "monokai/normal": ("#ddddda", False, False), "monokai/keyword": ("#f92672", False, False), "monokai/builtin": ("#ae81ff", False, False), "monokai/definition": ("#a6e22e", False, False), "monokai/comment": ("#75715e", False, True), "monokai/string": ("#e6db74", False, False), "monokai/number": ("#ae81ff", False, False), "monokai/instance": ("#ddddda", False, True), # ---- Pydev ---- "pydev/name": "Pydev", # Name Color Bold Italic "pydev/background": "#ffffff", "pydev/currentline": "#e8f2fe", "pydev/currentcell": "#eff8fe", "pydev/occurrence": "#ffff99", "pydev/ctrlclick": "#0000ff", "pydev/sideareas": "#efefef", "pydev/matched_p": "#99ff99", "pydev/unmatched_p": "#ff99992", "pydev/normal": ("#000000", False, False), "pydev/keyword": ("#0000ff", False, False), "pydev/builtin": ("#900090", False, False), "pydev/definition": ("#000000", True, False), "pydev/comment": ("#c0c0c0", False, False), "pydev/string": ("#00aa00", False, True), "pydev/number": ("#800000", False, False), "pydev/instance": ("#000000", False, True), # ---- Scintilla ---- "scintilla/name": "Scintilla", # Name Color Bold Italic "scintilla/background": "#ffffff", "scintilla/currentline": "#e1f0d1", "scintilla/currentcell": "#edfcdc", "scintilla/occurrence": "#ffff99", "scintilla/ctrlclick": "#0000ff", "scintilla/sideareas": "#efefef", "scintilla/matched_p": "#99ff99", "scintilla/unmatched_p": "#ff9999", "scintilla/normal": ("#000000", False, False), "scintilla/keyword": ("#00007f", True, False), "scintilla/builtin": ("#000000", False, False), "scintilla/definition": ("#007f7f", True, False), "scintilla/comment": ("#007f00", False, False), "scintilla/string": ("#7f007f", False, False), "scintilla/number": ("#007f7f", False, False), "scintilla/instance": ("#000000", False, True), # ---- Spyder ---- "spyder/name": "Spyder", # Name Color Bold Italic "spyder/background": "#ffffff", "spyder/currentline": "#f7ecf8", "spyder/currentcell": "#fdfdde", "spyder/occurrence": "#ffff99", "spyder/ctrlclick": "#0000ff", "spyder/sideareas": "#efefef", "spyder/matched_p": "#99ff99", "spyder/unmatched_p": "#ff9999", "spyder/normal": ("#000000", False, False), "spyder/keyword": ("#0000ff", False, False), "spyder/builtin": ("#900090", False, False), "spyder/definition": ("#000000", True, False), "spyder/comment": ("#adadad", False, True), "spyder/string": ("#00aa00", False, False), "spyder/number": ("#800000", False, False), "spyder/instance": ("#924900", False, True), # ---- Spyder/Dark ---- "spyder/dark/name": "Spyder Dark", # Name Color Bold Italic "spyder/dark/background": "#1f1f1f", "spyder/dark/currentline": "#2b2b43", "spyder/dark/currentcell": "#31314e", "spyder/dark/occurrence": "#abab67", "spyder/dark/ctrlclick": "#0000ff", "spyder/dark/sideareas": "#282828", "spyder/dark/matched_p": "#009800", "spyder/dark/unmatched_p": "#c80000", "spyder/dark/normal": ("#ffffff", False, False), "spyder/dark/keyword": ("#558eff", False, False), "spyder/dark/builtin": ("#aa00aa", False, False), "spyder/dark/definition": ("#ffffff", True, False), "spyder/dark/comment": ("#7f7f7f", False, False), "spyder/dark/string": ("#11a642", False, True), "spyder/dark/number": ("#c80000", False, False), "spyder/dark/instance": ("#be5f00", False, True), # ---- Zenburn ---- "zenburn/name": "Zenburn", # Name Color Bold Italic "zenburn/background": "#1f1f1f", "zenburn/currentline": "#333333", "zenburn/currentcell": "#2c2c2c", "zenburn/occurrence": "#7a738f", "zenburn/ctrlclick": "#0000ff", "zenburn/sideareas": "#3f3f3f", "zenburn/matched_p": "#688060", "zenburn/unmatched_p": "#bd6e76", "zenburn/normal": ("#dcdccc", False, False), "zenburn/keyword": ("#dfaf8f", True, False), "zenburn/builtin": ("#efef8f", False, False), "zenburn/definition": ("#efef8f", False, False), "zenburn/comment": ("#7f9f7f", False, True), "zenburn/string": ("#cc9393", False, False), "zenburn/number": ("#8cd0d3", False, False), "zenburn/instance": ("#dcdccc", False, True), # ---- Solarized Light ---- "solarized/light/name": "Solarized Light", # Name Color Bold Italic "solarized/light/background": "#fdf6e3", "solarized/light/currentline": "#f5efdB", "solarized/light/currentcell": "#eee8d5", "solarized/light/occurrence": "#839496", "solarized/light/ctrlclick": "#d33682", "solarized/light/sideareas": "#eee8d5", "solarized/light/matched_p": "#586e75", "solarized/light/unmatched_p": "#dc322f", "solarized/light/normal": ("#657b83", False, False), "solarized/light/keyword": ("#859900", False, False), "solarized/light/builtin": ("#6c71c4", False, False), "solarized/light/definition": ("#268bd2", True, False), "solarized/light/comment": ("#93a1a1", False, True), "solarized/light/string": ("#2aa198", False, False), "solarized/light/number": ("#cb4b16", False, False), "solarized/light/instance": ("#b58900", False, True), # ---- Solarized Dark ---- "solarized/dark/name": "Solarized Dark", # Name Color Bold Italic "solarized/dark/background": "#1f1f1f", "solarized/dark/currentline": "#083f4d", "solarized/dark/currentcell": "#073642", "solarized/dark/occurrence": "#657b83", "solarized/dark/ctrlclick": "#d33682", "solarized/dark/sideareas": "#073642", "solarized/dark/matched_p": "#93a1a1", "solarized/dark/unmatched_p": "#dc322f", "solarized/dark/normal": ("#839496", False, False), "solarized/dark/keyword": ("#859900", False, False), "solarized/dark/builtin": ("#6c71c4", False, False), "solarized/dark/definition": ("#268bd2", True, False), "solarized/dark/comment": ("#586e75", False, True), "solarized/dark/string": ("#2aa198", False, False), "solarized/dark/number": ("#cb4b16", False, False), "solarized/dark/instance": ("#b58900", False, True), }, } CONF = UserConfig(DEFAULTS) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/configtools.py0000644000175100001770000003301514654363416017101 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """ Configuration related functions ------------------------------- Access configured options ^^^^^^^^^^^^^^^^^^^^^^^^^ .. autofunction:: get_icon .. autofunction:: get_image_file_path .. autofunction:: get_image_label .. autofunction:: get_image_layout .. autofunction:: get_family .. autofunction:: get_font .. autofunction:: get_pen .. autofunction:: get_brush Add image paths ^^^^^^^^^^^^^^^ .. autofunction:: add_image_path .. autofunction:: add_image_module_path """ from __future__ import annotations import gettext import os import os.path as osp import sys from collections.abc import Callable from typing import TYPE_CHECKING from guidata.utils.misc import decode_fs_string, get_module_path, get_system_lang if TYPE_CHECKING: from qtpy import QtCore as QC from qtpy import QtGui as QG from qtpy import QtWidgets as QW from guidata.userconfig import UserConfig IMG_PATH = [] def get_module_data_path(modname: str, relpath: str | None = None) -> str: """Return module *modname* data path Handles py2exe/cx_Freeze distributions Args: modname (str): module name relpath (str): relative path to module data directory Returns: str: module data path """ datapath = getattr(sys.modules[modname], "DATAPATH", "") if not datapath: datapath = get_module_path(modname) parentdir = osp.normpath(osp.join(datapath, osp.pardir)) if osp.isfile(parentdir): # Parent directory is not a directory but the 'library.zip' file: # this is either a py2exe or a cx_Freeze distribution datapath = osp.abspath(osp.join(osp.join(parentdir, osp.pardir), modname)) if relpath is not None: datapath = osp.abspath(osp.join(datapath, relpath)) return datapath def get_translation(modname: str, dirname: str | None = None) -> Callable[[str], str]: """Return translation callback for module *modname* Args: modname (str): module name dirname (str): module directory Returns: Callable[[str], str]: translation callback """ if dirname is None: dirname = modname # fixup environment var LANG in case it's unknown if "LANG" not in os.environ: lang = get_system_lang() if lang is not None: os.environ["LANG"] = lang try: modlocpath = get_module_locale_path(dirname) _trans = gettext.translation(modname, modlocpath) lgettext = _trans.gettext def translate_gettext(x): y = lgettext(x) if isinstance(y, str): return y else: return str(y, "utf-8") return translate_gettext except IOError as _e: # print "Not using translations (%s)" % _e def translate_dumb(x): if not isinstance(x, str): return str(x, "utf-8") return x return translate_dumb def get_module_locale_path(modname: str) -> str: """Return module *modname* gettext translation path Args: modname (str): module name Returns: str: module gettext translation path """ localepath = getattr(sys.modules[modname], "LOCALEPATH", "") if not localepath: localepath = get_module_data_path(modname, relpath="locale") return localepath def add_image_path(path: str, subfolders: bool = True) -> None: """Append image path (opt. with its subfolders) to global list IMG_PATH Args: path (str): image path subfolders (bool): include subfolders """ if not isinstance(path, str): path = decode_fs_string(path) global IMG_PATH IMG_PATH.append(path) if subfolders: for fileobj in os.listdir(path): pth = osp.join(path, fileobj) if osp.isdir(pth): IMG_PATH.append(pth) def add_image_module_path(modname: str, relpath: str, subfolders: bool = True) -> None: """ Appends image data path relative to a module name. Used to add module local data that resides in a module directory but will be shipped under sys.prefix / share/ ... modname must be the name of an already imported module as found in sys.modules Args: modname (str): module name relpath (str): relative path to module data directory subfolders (bool): include subfolders """ add_image_path(get_module_data_path(modname, relpath=relpath), subfolders) def get_image_file_path(name: str, default: str = "not_found.png") -> str: """ Return the absolute path to image with specified name name, default: filenames with extensions Args: name (str): name of the image default (str): default image name. Defaults to "not_found.png". Raises: RuntimeError: if image file not found Returns: str: absolute path to image """ for pth in IMG_PATH: full_path = osp.join(pth, name) if osp.isfile(full_path): return osp.abspath(full_path) if default is not None: try: return get_image_file_path(default, None) except RuntimeError: raise RuntimeError("Image file %r not found" % name) else: raise RuntimeError() ICON_CACHE = {} def get_icon(name: str, default: str = "not_found.png") -> QG.QIcon: """ Construct a QIcon from the file with specified name name, default: filenames with extensions Args: name (str): name of the icon default (str): default icon name. Defaults to "not_found.png". Returns: QG.QIcon: icon """ try: return ICON_CACHE[name] except KeyError: # Importing Qt here because this module should be independent from it from qtpy import QtGui as QG # pylint: disable=import-outside-toplevel icon = QG.QIcon(get_image_file_path(name, default)) ICON_CACHE[name] = icon return icon def get_image_label(name, default="not_found.png") -> QW.QLabel: """ Construct a QLabel from the file with specified name name, default: filenames with extensions Args: name (str): name of the icon default (str): default icon name. Defaults to "not_found.png". Returns: QW.QLabel: label """ # Importing Qt here because this module should be independent from it from qtpy import QtGui as QG # pylint: disable=import-outside-toplevel from qtpy import QtWidgets as QW # pylint: disable=import-outside-toplevel label = QW.QLabel() pixmap = QG.QPixmap(get_image_file_path(name, default)) label.setPixmap(pixmap) return label def get_image_layout( imagename: str, text: str = "", tooltip: str = "", alignment: QC.Qt.Alignment = None ) -> tuple[QW.QHBoxLayout, QW.QLabel]: """ Construct a QHBoxLayout including image from the file with specified name, left-aligned text [with specified tooltip] Args: imagename (str): name of the icon text (str): text to display. Defaults to "". tooltip (str): tooltip to display. Defaults to "". alignment (QC.Qt.Alignment): alignment of the text. Defaults to None. Returns: tuple[QW.QHBoxLayout, QW.QLabel]: layout, label """ # Importing Qt here because this module should be independent from it from qtpy import QtCore as QC # pylint: disable=import-outside-toplevel from qtpy import QtWidgets as QW # pylint: disable=import-outside-toplevel if alignment is None: alignment = QC.Qt.AlignLeft layout = QW.QHBoxLayout() if alignment in (QC.Qt.AlignCenter, QC.Qt.AlignRight): layout.addStretch() layout.addWidget(get_image_label(imagename)) label = QW.QLabel(text) label.setToolTip(tooltip) layout.addWidget(label) if alignment in (QC.Qt.AlignCenter, QC.Qt.AlignLeft): layout.addStretch() return (layout, label) def font_is_installed(font: str) -> list[str]: """Check if font is installed Args: font (str): font name Returns: list[str]: list of installed fonts """ # Importing Qt here because this module should be independent from it from qtpy import QT5 from qtpy import QtGui as QG # pylint: disable=import-outside-toplevel if QT5: fontfamilies = QG.QFontDatabase().families() else: # Qt6 fontfamilies = QG.QFontDatabase.families() return [fam for fam in fontfamilies if str(fam) == font] MONOSPACE = [ "Cascadia Code PL", "Cascadia Mono PL", "Cascadia Code", "Cascadia Mono", "Consolas", "Courier New", "Bitstream Vera Sans Mono", "Andale Mono", "Liberation Mono", "Monaco", "Courier", "monospace", "Fixed", "Terminal", ] def get_family(families: str | list[str]) -> str: """Return the first installed font family in family list Args: families (str|list[str]): font family or list of font families Returns: str: first installed font family """ if not isinstance(families, list): families = [families] for family in families: if font_is_installed(family): return family else: print("Warning: None of the following fonts is installed: %r" % families) return "" def get_font(conf: UserConfig, section: str, option: str = "") -> QG.QFont: """ Construct a QFont from the specified configuration file entry conf: UserConfig instance section [, option]: configuration entry Args: conf (UserConfig): UserConfig instance section (str): configuration entry option (str): configuration entry. Defaults to "". Returns: QG.QFont: font """ # Importing Qt here because this module should be independent from it from qtpy import QtGui as QG # pylint: disable=import-outside-toplevel if not option: option = "font" if "font" not in option: option += "/font" font = QG.QFont() if conf.has_option(section, option + "/family/nt"): families = conf.get(section, option + "/family/" + os.name) elif conf.has_option(section, option + "/family"): families = conf.get(section, option + "/family") else: families = None if families is not None: if not isinstance(families, list): families = [families] family = None for family in families: if font_is_installed(family): break font.setFamily(family) if conf.has_option(section, option + "/size"): font.setPointSize(conf.get(section, option + "/size")) if conf.get(section, option + "/bold", False): font.setWeight(QG.QFont.Bold) else: font.setWeight(QG.QFont.Normal) return font def get_pen( conf: UserConfig, section: str, option: str = "", color: str = "black", width: int = 1, style: str = "SolidLine", ) -> QG.QPen: """ Construct a QPen from the specified configuration file entry conf: UserConfig instance section [, option]: configuration entry [color]: default color [width]: default width [style]: default style Args: conf (UserConfig): UserConfig instance section (str): configuration entry option (str): configuration entry. Defaults to "". color (str): default color. Defaults to "black". width (int): default width. Defaults to 1. style (str): default style. Defaults to "SolidLine". Returns: QG.QPen: pen """ # Importing Qt here because this module should be independent from it from qtpy import QtCore as QC # pylint: disable=import-outside-toplevel from qtpy import QtGui as QG # pylint: disable=import-outside-toplevel if "pen" not in option: option += "/pen" color = conf.get(section, option + "/color", color) color = QG.QColor(color) width = conf.get(section, option + "/width", width) style_name = conf.get(section, option + "/style", style) style = getattr(QC.Qt, style_name) return QG.QPen(color, width, style) def get_brush( conf: UserConfig, section: str, option: str = "", color: str = "black", alpha: float = 1.0, ) -> QG.QBrush: """ Construct a QBrush from the specified configuration file entry conf: UserConfig instance section [, option]: configuration entry [color]: default color [alpha]: default alpha-channel Args: conf (UserConfig): UserConfig instance section (str): configuration entry option (str): configuration entry. Defaults to "". color (str): default color. Defaults to "black". alpha (float): default alpha-channel. Defaults to 1.0. Returns: QG.QBrush: brush """ # Importing Qt here because this module should be independent from it from qtpy import QtGui as QG # pylint: disable=import-outside-toplevel if "brush" not in option: option += "/brush" color = conf.get(section, option + "/color", color) color = QG.QColor(color) alpha = conf.get(section, option + "/alphaF", alpha) color.setAlphaF(alpha) return QG.QBrush(color) return QG.QBrush(color) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1722935059.1367052 guidata-3.6.2/guidata/data/0000755000175100001770000000000014654363423015106 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1722935059.1567054 guidata-3.6.2/guidata/data/icons/0000755000175100001770000000000014654363423016221 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/apply.png0000644000175100001770000000142114654363416020054 0ustar00runnerdockerЙPNG  IHDR(-SgAMA▒П №a cHRMz&АД·Ашu0ъ`:ШpЬ║Q<5PLTE   У Эv] ЫxMЦwEед ЯwS HУ:П4DТ? Ян JFЕ IПD5П/л"╡ЕLHRf┤](Г#%} WfO0б$ bЩO0▒$l +╜T@┐6rI┌:XW╦N"t rЎd _ #АЬ Рg°Y╜╫║]ЮV╣╫╢ZЯRAЩ:QУI╖▄┤Z▓QЮХ ╖у│`─U%╖ г║▌╡о┌йктеj╨`7╔(/░"y╤oг▀ЭЯ▀ШахЩt█iI╪9=┬1_╫SСфИЦхОеъЯ|чqYчJL╓?^щOДяxДЄygїXVчJo■`x ieЁXБюw   ╫С;tRNS ═╖ ╨┤ ╤▓5 ╤▌э∙A ╤█ ьЎ<╤▄д■Їт▌ ╝∙▌ ╗∙▌╜°▌ ╗∙▄╛═ #жїlbKGDИHtIME▀МUR■ТIDAT╙c`└╤°L╠,(|V6kv$>знЭ╜Р┼═тєЄ9:9╗Ё 00 Л00ИК╣║╣{ИK00HJyzI╦╚╩y√°·╔+Х**∙*лЗДккБ S╫ ПИМКО╤╘ВпнЯРил╖P▀ )9┼╨╔ ╞&йжf(О4╖░DєЖnеd ЪX%tEXtdate:create2015-08-31T15:00:15+02:00q╩H%tEXtdate:modify2015-08-31T15:00:15+02:00ErЇIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/arredit.png0000644000175100001770000000214314654363416020363 0ustar00runnerdockerЙPNG  IHDR DдК╞gAMAп╚7Кщ cHRMz&АД·Ашu0ъ`:ШpЬ║Q<nPLTEХХХМММЙЙЙИИИЗЗЗЕЕЕДДДГГГЖЖЖКМОДКФАИФЗУ~ЖТ{ГП|ДРzГП{ГНАВДzzyyywxxuwwtvvsuurttq}}|МММssrЗЗЗookЖЖЖmmiДДДllhГГГkkgВВВjjfБББiieiifmmkxxxooliighhehhfhhdggcdd`eeaYYTXXSVVQXXT\\[   ОУЭ YYЛРЦ№№ єє ёё юю ьь шш чч ЗРЬДМХЎЎ ЄЄ ээ ъъ ыы ЖОЬГЛФїї щщ ЖНЫЇЇ яя цц ГКФЛУаЗПШ¤¤ √√ °° ЙЙЙГГГВВВНФЯККЙЖЖГЖЖВЗЗВИИГППЛ ЦЦЦЦ mm ЗЗЗ┴┴ 4eOKtRNSыЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇыїїkїїЛ%їїНїїО&їїїїїїїїХ(ПхяяяяяяяяяяяячК$iИ}j¤bKGDKi ЕPtIME▀МUR■wIDAT8╦Е╙WS┬Pр`я╜aLB╗`/ИНDJшб иИК▌я═їnвч!УЗo╬d6╗Ei■Е╥╥┌╓▐╤┘╒▌╙█╫?084<2:6>19еЭЮ╤╬╬щ╨s$╛K U  є▐0O#└ИИqёD(ЩКм╤Д╛└ ├eГ!!, & е▒╚ ВBв╬]зЧЦXiт"рЕU ╓а─e┼dКOоc░╤╝┴╓╪PPс╕Ы█╗B0`╟Ай▐╫PШъ├уS¤╣^yХ^61╨┐С°▐I№$ЖЯQ1,fi мЄ @е╚ааА J╜┴╓╪PP╨0и┌зъадНВ* Йak[lР6 @ ╗Г%mА 2(щw+А|г@╛QJфеhРoА ╗╕Ў╘ю▐■┴сСу╪)>дч╔щ┘∙Е╦Н.ЛЎx]╞жqy=тщщh╖I%nZ<▐╬ Єs/їЯj}Э%tEXtdate:create2015-08-31T15:00:15+02:00q╩H%tEXtdate:modify2015-08-31T15:00:15+02:00ErЇtEXtSoftwarePaint.NET v3.30@ДGIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/busy.png0000644000175100001770000000132114654363416017710 0ustar00runnerdockerЙPNG  IHDR(-SgAMA▒О|√QУ cHRMЗМ¤RБ@}yщЛ<х╠s<ЕwєPLTEыШqуg7┬X7p╦╡╠─о╟┼н╞╢Ы╕║б╛╦┤╠жМмзНоКoФпЦ╡┌╚┘Ш}бСyЬ╔▒╔ОvЫлС▒кЖО╗oR█╩┌аh[▓cffffё╫╛╓Р_╛Y/м9б*ЧxЮ;/Ы6-Ш2,Ф,)Р((Й&*█└╔ф╠╠х╬╬х══╬╡┼├з┐ч╙╙Єхх·ўў·ЎЎ╤╖╞Єффу╘▀╛з┴■№ЇЁтс■■■ є█ ├sэлkяуь■■· ы╔ ╞v ╝h∙ьсї╘╢є╜Еє╗Ає║~╦ЧБщ└ШчкS╙u.╢@!ЩБ#t   ЪЄ║■tRNSА@▀пp пАя0Я@0я╧╧А@яЯpАА`√pBbKGDPуnL╝ pHYs  н#╜utIME▀МUR■yIDAT╙c` 0╩╩╔╔+(*)├ШШUT╒╘54YрJX╡┤uttї╪рь·ЖF╞HжpЪШsq#Ы╦cf╬Л╠чу╖░D▓▓╢▒FЁEььЭDс|1qgW7w ШАдзЧЬ╖ПпЯ?L@JdИЄ(Л Цq$J╧%tEXtdate:create2015-08-31T15:00:15+02:00q╩H%tEXtdate:modify2015-08-31T15:00:15+02:00ErЇtEXtSoftwarePaint.NET v3.36йчт%IENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/cell_edit.png0000644000175100001770000000126114654363416020655 0ustar00runnerdockerЙPNG  IHDR(-SgAMA▒О|√QУ cHRMz%АГ∙ Ащu0ъ`:ШoТ_┼FэPLTE   ББББББfff   ███▄▄▌▐▐▐ррртттхффчччщщщьььюююёёёЇєє▄▄▄АААтстфффцчцшщшыыыююэЁЁёЄєЄїїї▌▌▌уууцццшшщэээЁяяЄЄЄЇїїўўЎ▀▀▀ххцшшшъъъэьэЁяЁЇЇЇўЎЎ∙°∙рссяюяёЄёЎЎЎ°°°√··ььыєєє°ў°···№√√ьыьЁЁЁ°ўў∙∙·√№√¤¤№щшшюэюєЄєЇїЇўўў√√√¤¤¤■■■ъыъяЁяЎўЎ∙∙∙·√·№¤¤¤■■д+f8tRNSЩЄЄ4hїъbKGDИH pHYs  d_СtIME▀МUR■ЬIDAT╙Е╧YБ@ЖсЩ▓E 3" IЦ";Y┘w  ч╕·О:ы9╝П▐б╪0 #─%вё!$SщМР═ЙТмф Д╒RYлTk аNhCojF╦lw║ДЎ,╗?0Эс`LиeKУй3Ы/B┼ем╕l╡▐иДne╧e;╨эшЭ╬ z╗Xуi▓╫√є¤Е├0 ╟E,$KdqW└%tEXtdate:create2015-08-31T15:00:15+02:00q╩H%tEXtdate:modify2015-08-31T15:00:15+02:00ErЇIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/copy.png0000644000175100001770000000147614654363416017713 0ustar00runnerdockerЙPNG  IHDR DдК╞gAMA▒П №a cHRMz&АД·Ашu0ъ`:ШpЬ║Q<╔PLTE   ░▒║▓│║╖╖┐▌▄фжз░   ююЄ■■■√·№°ў∙єєїЁЁЇ∙∙√ўЎ°Єёї▐▌х№√¤ўў∙цхыїїўьыёсрц╫╫▀хфъ▄█у┘┘сЬЬа╟╞╬шчэррц╖╖╖ъъъъъюфущтсч┤┤╢утфюэёщщэчцьыыя╡╡╡фуххфцЇЇЎццш··№▓▓▓│││ссу░░▓▄▄▐ннп▌▌▀▐▐р▓▒│пп▒┴└┬уухшчщттф▀▀сфучяяё╟`┤╪tRNSу▌▀3ЦMўтbKGDИH pHYs  ЪЬtIME▀МUR■KIDAT8╦Е╙┘rВ0Аam+зILВа╢UT\@h╡л▌╖ўи&MD╧%|sТЯНгуX═Є(p╛A└дMs╨ ╘юv$шЮB═Ф9Ш∙gP ф.Г═┤кБ╟Ж┬Л^п╕]^їоб?░Т └D░h╪u║0[╔ЎXьOж├)╠цV▓╪#Ь&щ┬ў' мф`М9EaЦжСA~c%oПрФ1ФДq┼Р▀Z╔Р`┘Q"Л╙ЄХХмПhБZБфКf+Умn@Лф+Ум┴ЭK)╘-╨ц█d °d▄╧M▓Є¤Ў Mr░┼ї<БзgУ▄┌}(∙- rСАїЛIV╩│~5╔ 4[f·г╤█√╟зI▐kупяЯ▀?У\╔`?∙ФТ+6ь'Ч┴Ar╪╔┼ ∙ ║╫/┤ ыV%tEXtdate:create2015-08-31T15:00:15+02:00q╩H%tEXtdate:modify2015-08-31T15:00:15+02:00ErЇIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/delete.png0000644000175100001770000000132214654363416020171 0ustar00runnerdockerЙPNG  IHDR(-SgAMAп╚7Кщ cHRMz&АД·Ашu0ъ`:ШpЬ║Q<╔PLTEу7шJцS!▓%╩╘,┐r ЁwLїХs┌S+о ╩█5эT6┘5оЇРs╓J&дЄ]?ыB(н їВd╓D&▌:Ї_@эF,│ GЇnOЇ_AЄK0V╓C%ЇmO╗"▄:Ї^@ё;"├ Нё ╚Щю╞У╙+шо╛╞p■╝ж■fH■жМ■Пs■vX■U8■6■■   M ┬б9tRNS'kd*▓∙РЙ∙╖.ЇРє·■жЯ■Ўв+■■■0ж■╢Я■■о■РЇРk·{+─E ,ЖbKGDB╫=ЇtIME▀МUR■ЭIDAT╙НП╟В@DWErP]JA╤5  O)#▄щ╦T┐йzUНPП Ж▌\f4f '/ $J▓ва╕лжМ4%жАц╡Ы=Ы;dсzЙа█ўхК`w╜i╡╞vў ╪ў║ОШ¤Б<Г0ъ║(9п8I█ ╧П¤0~gyр(7■(╔>eрдШН?═╦s АU-ЁU}щ3ї гb9╩aт%tEXtdate:create2015-08-31T15:00:15+02:00q╩H%tEXtdate:modify2015-08-31T15:00:15+02:00ErЇtEXtSoftwarePaint.NET v3.36йчт%IENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/dictedit.png0000644000175100001770000000161714654363416020527 0ustar00runnerdockerЙPNG  IHDR DдК╞gAMAп╚7Кщ cHRMz&АД·Ашu0ъ`:ШpЬ║Q<PLTEХХХМММЙЙЙИИИЗЗЗЕЕЕДДДГГГЖЖЖКМОДКФАИФЗУ~ЖТ{ГП|ДРzГП{ГНАВДzzyyywxxuwwtvvsuurttq}}|МММssrЗЗЗookЖЖЖmmiДДДllhГГГkkgВВВjjfБББiieiifmmkxxxooliighhehhfhhdggcdd`eeaYYTXXSVVQXXT\\[ ·mЙЙЙГГГВВВНФЯККЙЖЖГЖЖВЗЗВИИГППЛ ОЗЗЗТ    %vм╞KtRNSыЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇыїїkїїЛ%їїНїїО&їїїїїїїїХ(ПхяяяяяяяяяяяячК$iИ}j¤bKGDYЪ▓ЇtIME▀МUR■IDAT8╦Е╙kO┬0рN╝А╚E@█и└P@T. 0&L■ /▒н Й ЗЭM?├ўc!▄03gХ8zqїЕ1-┼8Я│r╚ш%Й]BpЗNhЯОah╧ ╜┌AU$╢c[╝рўJЩ6╝яЦHЙ=╨А╔RвFрАO▓ЭmЖЖ&┼┴ИikНпзЖFg═Ч╣IО╪D├ЦхЙ╢1╜U#+qПЫ╪ьВ╣<'цб(|╔:qnНщРz}╜gтlUЗ щ▀╪Апщx№свэоs.╗LЯ Щ°& ╪гч╓XМия=Г┴з:¤]└ZЭ{tТ╦Ю▐R_▒+Qпст╧s№ЬXjнlє▀Щ УE-БдТуH_mjw_mjwёnгЁ-ўV░╤ч9ЦE,Я╗╕╓╨oб%╒╙ЦbхчIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/edit.png0000644000175100001770000000164114654363416017660 0ustar00runnerdockerЙPNG  IHDR(-SgAMA▒О|√QУ cHRMz%АГ∙ Ащu0ъ`:ШoТ_┼F\PLTE   ┤┤┤ЯЯЯмммпппооомммлллииийййЬЬЬШШШ───ааа┴┴┴УУУ╛╛╛ССС╝╝╝╗╗╗╖╖╖ХХХ▓▓▓ЧЧЧлллмммШШШ│╡╡ssrГДВ╖╖╖ФФФQPQ░п░@>?~|}eeeннмШШШ FFF╖╖╕ЩЩЩЦЦЦФФФСССНННОООЛЛЛИИИЪЪЪеее▓▓▓   №№№¤¤¤ййй···√√√■■■ллл°°°кккЎЎЎїїїўўўщщщ▄▄▄░░░ЇЇЇ∙∙∙ццц┴┴┴КЙЙюээ╡╡╡ыыы╫╫╪гге╕╕╕╢╢╢ъщщ▐▐▀ЬЬЯШШЩ┐┐┐яяярррЫЫЮЛЛЛєєє│▒▒▀▐▀ббдЕЕЕэээ╨╨╨▒▓▓ЙИЙъъъёёё░░│КККшшшЁЁЁюююЄЄЄ┬┬─ЛММ┤┤┤жжжззздддбббЫЫЫб▓ЖД5tRNS▄хфффффхь╝╒╙╙╙╙╙;╔╙ нoєє■▄[ж<@CCCCCCCCF8ЫуcbKGDИH pHYs  ЪЬtIME▀МUR■ьIDAT╙-═gW┬P риU▄T▄{В╕╥p ╖Чкн ў╛╕E┼U╟ ?╟┤Шoy╬Ы7PQYeFuM$R[W▀Н+°?&RCsЕEDH- нYТЦeВ╘╞╨nУТ"Гa(АОмЩshuMpKx╥╣n╣▐╞ц╓vpeИэxn~wo▀\┌┼╨} ПОOДФ^zN╧╬/.uN9)╬╨[╕║╛╣U┌Uк▄╤ww ЁиЛЪ3!Ї<=┐Ф^K┼7э" ┬╨Ё√G▐сM╩рKFF╟╞?}▀ ▓╙Ўў╧яLNM╧╠╬%╔∙Tjaqi∙`4╟6Уъ╡%tEXtdate:create2015-08-31T15:00:15+02:00q╩H%tEXtdate:modify2015-08-31T15:00:15+02:00ErЇIENDоB`В././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1722935059.1567054 guidata-3.6.2/guidata/data/icons/editors/0000755000175100001770000000000014654363423017672 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/editors/copywop.png0000644000175100001770000000161714654363416022107 0ustar00runnerdockerЙPNG  IHDRє asRGBо╬щgAMA▒П №a pHYs  ЪЬ$IDAT8O}ТMhAЖ▀▌dЫ4НmЪ╞┤6▒IлнT(DAР·К╤ЛвЧВzP*їд<шE/Врй■╘Rш┼K=xЇ D╤U(UcjТ■$▌f█lТMЪv7╔f│ыьVеИ°┬0╗3▀<є╬ў}■╥Б¤юГ]ўx╢║t]пk║оA╙4sЯв(cP╣\nmbbтС╣°[###╠▌╗ў·KеТJ WУУУl(j▓Бр└└Й╙┴``З╫█┌хў√wн( кUuГ╝I║8ЭтHWВ┴ eї∙|√FGЯ<▄╪╘13є├Ь7oz!2OQ║f■╥4 UUA; єёx\4▐°·╦2╕╝Є асC\─lZ2┐Щ╟oщB|E2]k$а].WЪ8Г°№]Т▌Зя\С╘ЁВ]Br═ql ZЫ╧Ф╕ucтБPИC:∙. Ж-ТЭщцрawх╬ T/]E┘╙К╦$є┐Uлi╕p╝╧,▒┼j5╦h%│щ`╫оЮ№╩2Яв╔-╙'╧б▄юG├Ш├f│б▐nЗ├QП-[Ьp╗Ы╤ьjBSгл.SжГ╬╬о╟q?hлu┐У▒ь'bЩ&▌f!7╫╠ф╩ТмФХ▓D▄.ЁйЫ╧ч ┘l╢┘МППk├├├ їu <▐Vф лB4K%ТЛB<K╪э╢╣l&ЫМFгIQ╠-T*│o ¤iЎююю╜э█┌П ЩLмгc;Ч/rЫ╕─qе_! ЁСЙ╖<щ╝ТВIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/editors/edit.png0000644000175100001770000000164114654363416021331 0ustar00runnerdockerЙPNG  IHDR(-SgAMA▒О|√QУ cHRMz%АГ∙ Ащu0ъ`:ШoТ_┼F\PLTE   ┤┤┤ЯЯЯмммпппооомммлллииийййЬЬЬШШШ───ааа┴┴┴УУУ╛╛╛ССС╝╝╝╗╗╗╖╖╖ХХХ▓▓▓ЧЧЧлллмммШШШ│╡╡ssrГДВ╖╖╖ФФФQPQ░п░@>?~|}eeeннмШШШ FFF╖╖╕ЩЩЩЦЦЦФФФСССНННОООЛЛЛИИИЪЪЪеее▓▓▓   №№№¤¤¤ййй···√√√■■■ллл°°°кккЎЎЎїїїўўўщщщ▄▄▄░░░ЇЇЇ∙∙∙ццц┴┴┴КЙЙюээ╡╡╡ыыы╫╫╪гге╕╕╕╢╢╢ъщщ▐▐▀ЬЬЯШШЩ┐┐┐яяярррЫЫЮЛЛЛєєє│▒▒▀▐▀ббдЕЕЕэээ╨╨╨▒▓▓ЙИЙъъъёёё░░│КККшшшЁЁЁюююЄЄЄ┬┬─ЛММ┤┤┤жжжззздддбббЫЫЫб▓ЖД5tRNS▄хфффффхь╝╒╙╙╙╙╙;╔╙ нoєє■▄[ж<@CCCCCCCCF8ЫуcbKGDИH pHYs  ЪЬtIME▀МUR■ьIDAT╙-═gW┬P риU▄T▄{В╕╥p ╖Чкн ў╛╕E┼U╟ ?╟┤Шoy╬Ы7PQYeFuM$R[W▀Н+°?&RCsЕEDH- нYТЦeВ╘╞╨nУТ"Гa(АОмЩshuMpKx╥╣n╣▐╞ц╓vpeИэxn~wo▀\┌┼╨} ПОOДФ^zN╧╬/.uN9)╬╨[╕║╛╣U┌Uк▄╤ww ЁиЛЪ3!Ї<=┐Ф^K┼7э" ┬╨Ё√G▐сM╩рKFF╟╞?}▀ ▓╙Ўў╧яLNM╧╠╬%╔∙Tjaqi∙`4╟6Уъ╡%tEXtdate:create2015-08-31T15:00:15+02:00q╩H%tEXtdate:modify2015-08-31T15:00:15+02:00ErЇIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/editors/edit_add.png0000644000175100001770000000175114654363416022143 0ustar00runnerdockerЙPNG  IHDR(-SgAMA▒О|√QУ cHRMz%АГ∙ Ащu0ъ`:ШoТ_┼F┼PLTE;r>:q=?yBFГGGВJ@yC8m;7n:4f83i75j84f76h:;q>;l?:e>5f93f7-Y1b╡_QеP3b86k95j94j87m;:q=9z:hц^Y┤R@yA1{1C~C0v1@В@7v8д <┐1>y?7h:6h:8m<>lA;g?>jA:f>:f?;q>9x:2А25Д4:{;MРMe╡akоhRНR_╢\ЎrЕ▄|eжd^║]y№mp▐g\д[d╝`Ж xkш_XзUА taщR9Х5E╛=[╛Vm╗ii┤f`ЎNN╓?SЧP[аW_в\eеbаф=¤'b Rp `V∙D=╫/;▒.G░▓7*Э*╟(їA╡:*Ю)┴"№@║73Й3з+║<Ц8   Ю л?tRNS#╒╙╙╒,,***##",***L¤¤L***,"╓√√╓╘╘╘╘╓√√╓",***L¤¤L**,##***,,#╒╙╙╒h 7xbKGDЦСi+9 pHYs  ЪЬtIME▀МUR■┴IDAT╙c`F&fVF`│wptbGрpvquуDpўЁЇтBрЎЎёїуБ░y∙°ЕД¤DD┼─%$едГВCBe┬┬e#"гвЭффcbутУТSR╙╥32│▓srєЄ ЛКKJ╦╩ХФ+*лкkTjыTыЫЪ╒╘54╡┤ut[ZїЇ Нд V╖╡wtЪ ╣├┤л╗з╫ Yап┬Ds$ЛIУзL╡D░▓╢▒╡│3Н,н┐%tEXtdate:create2015-08-31T15:00:15+02:00q╩H%tEXtdate:modify2015-08-31T15:00:15+02:00ErЇIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/editors/editclear.png0000644000175100001770000000336114654363416022341 0ustar00runnerdockerЙPNG  IHDR szzЇsRGBо╬щgAMA▒П №a pHYs├├╟oиdЖIDATXG═WklU■ffgg_эvw██]ZК┤RЛE4Е(E5°К╞АПиёQГFгQи№0DЯи(/Н`ё┤╥BлР"╨"ПbKK█эnwgfgwvf╟{╫Yl╦+╓─/∙rvg╧=ў╗чЬ{я,ГQX~╩8Юл╫2·С╟╫в═|№ЯaДА╫я@]▒╖|Spъyg)IEь>Ёsыё╕|═ ЯB5]&мi│░qЎeБЄigGDХХЗ▄╒u ~Ых єч #░╝Pд┴rVшi.xЫ╙}ўs╧НЇЫHМм&─&Ыл╪pЙ╗PbА╘╗Уз╠ШT|╘zгщ2с!@тїg┤5wy+чfЫCK╞Pфdэ<╟>єз╟─Г3m▀╡AЭ?C┌m№uП╟СVЖажТ╨Y╗PМ√u;zM╫ ├)╡]║ MбоО5▒$ЯJ├YВ╙aїXмЦЧщя;>╢м╢/╧:OЄ6W°Ш■шСГ√i╝) Pф8╠м╓uEMe╙н╖ЁолM╫Н╝^h$хOзо н;╘л1<p╘╕Є∙ЕL╒ВBDгк╤╙ёe╖*ї▀Y═су╞iЫщ)аy>Pя$ЯUЯmО┤q▐н ╠Ве5рЩ├А▐IТ╚MUНGўЖcсЎзч▌Жw■=>М┘ДЧ╣H9:IВх9>цЎї73ч6╕аiiHН▄2║МД╘бpК├юм║lё№╬Вў7`ЫтoС╖rИПь5ЁXхRv√-+пЗ╦╡║┌ЮKCМДI■"╞АЕlU%╓л3Qи╣ы╤Ц╒╠√Жq·ьцpZoУлё5`Е┐╩ИjZЪ'QуH%N└beбH"Р!:X╣H┼БЭ$рa{∙м╟n▌╣V╪╝ы-Ёfи1qZ'┴})EБТЙкЖ├╔A/HИiR6чT─N┤бm╓╩┘O^Я┐╣їC°╠(y1.ЪЖ}I%a( й╖#"$ ЁЦ╕╤▀╙ З█ YTС╔ДрЇ╘"rм к╘╚U╘-Э-Wя∙q%цЪбN┴╕0:ЪУ╥`<н╙╦Йtг!6ЖЕ7`Р>Nл"∙\F╬o║■К╩цa░{дЁf&P╜$P6c╔Цk╕5▀пDСЄ$╞%а]E╗<╘MЦюГ$╩╨tЕ!█OUр+ЫВЁqrc9IV\Hзв╚h▌(й╝ ╤▀E4Ї ¤еюiЧ╝║╪╩ё╤жwQ`Ж═тя╨&r▌w тСЮЦЯDVhЕвР^░╪"▀╔K╦дЩъ? OpфAщdtн■s.%В4Ї∙╜┐оь епЪw╚¤·╫╝c ░║щK╢╠мW─D╩ъ(Гau ╥ГЪА╙$W╢Щt/К╬оЕ╔ %¤B╩RяфkНx┤лoуЦ]ў▐■4┌═xФ6B&▀ADWm'д╘:и▌╢}7^.-ЄW\ьЦ┬Зa╥t%$▌`I6g╤у{PT:ЛdЕ'╧ЛбИ╣гu▌юGЦ)о :√6E╧║hj)Н╤шCлIa8╡ мNЧmхх╡ярTеЕ%uЗ║╚VФ ╕к╚ЎL!A33┐№.■∙цю╝Дв┘ ╞(ТF╛╙*╖rjGPр`▀·^┴╩ъ·{'З┌▀Aq░├GЪн ┴єяA"╒Пэ_л┤юХ╛|ёMlИ╦Р╔╕$бb┌S8╓]@╙4?))*GGG677   ∙∙∙ЇЇїєєї■■■╒╒╓ЎЎЎўў∙ЇЇЎ¤¤¤ЎЎў╧╧╧їїїїїўЄЄЇ■■ ўў°╬╬╧єєЇЁЁё¤¤■єєє╠╠═ЇЇЇёёЄююя··№ЁёЄ╔╔╠єЇЇюяЁыьэў°∙∙··Ёяё╔╔╔ьэющъыїЎўЁЁЄ╚╚╚шщъєЇЎ├┼╞ЄЄєхцч┬┬├ЄЄЄфцчтуфтухстуэяЁЇїЎ╗║╗ьььъыьшшщыыьяЁЁ■  Ў∙·╟╟╟╡╡╢╕╕╕║║╗╖╖╕ммн█▌▐юGPtRNSЕХТТТТУЖ:№╔ pУ╡▀■├U├3э╨├ ║├Ю├|├V∙├3э├т├ ╟╔Я∙ю═ЦЖ"~щ╬╕ШuV72tN1 ▌ЭdhbKGDИH pHYs  ЪЬtIME▀МUR■хIDAT╙c`F&fV66vN.ЯЫЗ7 °°ЕДEDГ@ 8@LЬA" $4,< $едe"гвcbу└ ^LЦAN> !1&) RФФR╙╥32┴ KEСБA5 ;'7/ ╘АъЕE┼й%е`Pж╨╘*пH═пГ*m]╜фъЪ╩Z0╚╓7``04кйлohllljln16a`05kmkяш,э*щ╬э1╖``░┤ъэыЯ0q╥dk[;{а▀ж89╗╕║╣{x2АБЧ╖ПпЯ?d HЛэИ^L%tEXtdate:create2015-08-31T15:00:15+02:00q╩H%tEXtdate:modify2015-08-31T15:00:15+02:00ErЇIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/editors/editcut.png0000644000175100001770000000147014654363416022045 0ustar00runnerdockerЙPNG  IHDRє asRGBо╬щgAMA▒П №a pHYs  ЪЬ═IDAT8OНSKLQ╜єя┤е╒ф╗!bA╦бЪТHHг ВЪcQ$╕rн+VD&ь$F╘Э&▌тЗиФ4╡╨Ъ╨`a┌бЯ∙v╞;г▓СDNЄ2я╜yўЬ3ч▌!ръыыG***╘D"ёД$Iиоо╙4эw4EA▒Xгl═0`Ё╝▓╣РбPО╡┤@е┐▓?Й<Їx<=▓,█Е√ ▌]▌▌Р·СЪкккв╞╞╞ Ї╕u]Х,ЭьRДg п╞╟╟/?ЭЩy~qx╕озчЇEQАw╣└┴є└╨ЇЯу Ввi┌ЗGЗЖЖB┴╓╓ж╣∙y╡│3\ўyqQ&Hb╬╔;  Шає░1 ╓зR▐зT*╡Ь═f╧cв▄Щ╛>╬яє═єзт▒°╗г══╔фz$Y┌%@`FЦ,╠└bМн%Єщ#mэ▐6┐П6w─Aб\nZ√?YТ$┌0L*▀Eх)Кв.йк·ЦjllВ млТsв°~uuurЄ▐¤"гH]WпН║╙;ЕЎ/╦K]^пw╪ч;╔х─├В░=┴;°,╟Э│ ,X$Т$╟q░ЮH╠пm &ў╜ЩЭН~]Y┼В|ч╔ >ЧXО р∙4╤██kРHР┘┌ в╤(p,ы╞э8ЎЗ┘G C,К;а*┌6QЗоk11Я┐╜ыCБRйЫЫЫ Кв╒mкaЪ$╦╨g1└ К"╟E¤ЖъпeYЩ╞,^*к*SБ@└V▌Bulc╚d2Ў5щх2hЪFсб┴R▒°┐)+╩GU╙Т X ╬jjjа╢╢╓v▒▒,│сr╣оS$y╫1╠кХeXpp{№X0В│иz ЧiQ╦Б╗╘э▓b┌тЖG▓Я╕Ъ╢\NзM▓o ┴q$╕В╙╩▀;~ww9!Р─┴}IENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/editors/editdelete.png0000644000175100001770000000265514654363416022522 0ustar00runnerdockerЙPNG  IHDR(-SgAMA▒О|√QУ cHRMz%АГ∙ Ащu0ъ`:ШoТ_┼F┘PLTEЕОЯЬЫЫv]ЧЛrГ\Н╞К[30h=q^mtKFDGH-12$нз┼╙73╤;8╬52й Рж╪:6╪=:гжыZSэ^YгРуE>s N<y хIByЛ╕л!F2█пTИ═║@ь ^ПИ /╛▀K SiЖjЗ╣= Up{iaИJbanz ta$_g KZй╦wK_Н%MuЭvv mFбзд к ░г!2е ж,8hp"#(DddF)%Ўz№ЮЪ ┬╝ ├┐ ║╢№НЖ ЯЩ да∙КД▀nh╓lfщБz¤ЩФ бЫ {v·kd┼84╟>;¤kf woЄMHцHC▐:4ёF>ъE@ЇMF╠&"Ё$ш"┐80а5.╦╧╝л╫ ╒╖ Ь╛ЬЛ╟╚│ ~кК`м╗╖d ЛдwЧм ╢s ЧЩ н╕зи╪ │╕┘д╝ ╒Ї ╥ ╠╞┘яЎ · ¤2( E< JB >5  ў├щ72∙NG∙PHы:3╞   ┌Ї,╔ХtRNS^Ф╒▀╠s N╞╥[e°¤|CЇ┐ЦХ╩√X ╜Єdв╙A■·gгTm┼ еШ╚РЦЭ гЭС╕СШгЬЬ╢f╫йЬ ╚О9¤·Щs√S▓м}╬7я▄н╡╘ўQSЄ∙l>┤├RC|бгГO╩z:bKGDЄ█╢Оx pHYs  ЪЬtIME▀МUR■IDAT╙я■()*+,-./01ХЦЧШЩЪ23 45ЫЬЭЮЯабв67  89где:;<=жзи>? @AйкBCD EлмноFGHIпJKLM░▒▓│┤╡NO╢╖PQRS╕╣║TU╗╝VW╜╛XYZ┐└┴[\┬├]^─┼_`a╞╟╚bcd╔╩ef╦╠gh═╬╧ijkl╨╤mno├╥╙╘pqrst╒╓uvw╫╪┘┌xyz█▄▌{|}~▐▀рАБВстуГДЕЖфхцчшщъыЗИ !"ЙКьэюяЁёЛМ# !$%НОПРСТУФ&'╧Ўsc▓O!x%tEXtdate:create2015-08-31T15:00:15+02:00q╩H%tEXtdate:modify2015-08-31T15:00:15+02:00ErЇIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/editors/editpaste.png0000644000175100001770000000241714654363416022370 0ustar00runnerdockerЙPNG  IHDR(-SgAMA▒О|√QУ cHRMz%АГ∙ Ащu0ъ`:ШoТ_┼FPLTEtLТk<Рc-К\!ЕX#ВW)Д\ СlЧnjU   ЩolVЪp` A>8s[?=89:=ПhЛeМfНgКbКmGагекклмнпjf\wPЬuHХl;УtI╞╞╞╦╔╟Тg2З]+Й]/Д\З`0ьяёўў°Ж\Е^-yQГ[*^G,26?889:::ВZ(щщцхххёёЄЪЪЫАY'кккАX&ййкW%иий~V$ззз~U"ееж}T!ггд}T!ввгvLаааxP|S |R |R uI|lPййкSNBz|Бz{{АББaaa··№   ┘░М░ЭЙЁёЁыьЁЇї°ыыыЦ{a▒V╪оЖлЧБ╓╦└╥┼║╙┼╣╥┼╣╪╦┐╒╚╜Шy]│|L╤йГ╜Тeп}LпzGУjCЙd@Зb;Зa=РkHФkC═г{┬ХgвР}{ysФВr√ўєїЁъьшчьъшыщш═ЭtШКvx╢тDЯ▐]Коццщ╜╒ъЫ╛▀·№ №¤ ■  ╚ЩpеБ_8О┴Q╖ў@вцOК└[крYС╗ №∙їЇЇЇЇЇєЄЇ┴Фl╗ЖRж{TEЙп;▒ЎU╡Ёr║ьaР╢■·°єЄєЄЄє╜Пg│БQ╡}I`{Иh└є├ё~┴Ё\К▓¤∙їёЁЁЁЁЁ║Лa▓zFБl[#Г╜ М╦!И╦~├jНп∙їєьью№№■╡Ж\йvEдm:Эb-Нs`утч▀▄▐▄▐сыыьъъы··√┤ДZжtCиsBбf.ТxZ¤■ єёяяюэчшщшшщ°°∙жvPЫh=Щe<РX,МsVёЇ°стфтухффхєєЇ¤  єЇїBpйStRNS?шьЁПNЦМл■√НТХ=╥■■С┌М╫оR\`╫∙ў¤х╫ы╫ъ╫ъ╫ъ╫ъ╫ъ█ъ╥ъQЫЩШУ▄ыУ╫╤╒╞XHЧ^bKGD h╨ЇV pHYs  ЪЬtIME▀МUR■IDAT╙c``dТРФТЦafaecчрфbРХУWP QRVQUcчцсbP ╫ИИ╘МКО╤тхучb╨ОНЛOHLJNIM╙тb╨M╧╚╠╩╬╔═╦/╨╙70фb0*,*.)-+пим2615уb0пойнлohl ini ▒рb░lkяшьъющэыЯ01─КЛ┴z╥ф)SзMЯ1s╓ь9sBl╕lч╬Ы┐`св┼KЦ.[╛<─ОЛ┴~┼╩Uл╫м]╖~├╞Н79p18n▐▓u█Ў;wэ▐│g╧^'.ч}√ыPDЁ▐╧■P2 └┼╧#м╢s25'Ё3 33└б╓Ч╞gs\Mс├&\Й ВЮРp▓─рСаФB)ЕўнuЗЭйA!М└╓^-╡¤·v^Ё;ЯYзqN╔Цщ╤=фAЖ╡Цc2J┼Tш*W ВР,═fї┤╓ T jпm╪л┴ >|╦уSщИ┬Мs!ї-*╢JпMi┌&б╡ДQ╦╘и'юД3еfA╠А ┤В@P╧┼Иё╪▌ы^▀Vx╧sgR╘ф'Й@+%ъОйшJ)В╨b┤&.╟╘илJЗ№| C·Ф╬─Бf)░и┬╦W ТUЛШY'нС (Gb[жФ╤▒вjкЇ│ЖкЇ╬╓╤l╤M╫├╠HЯj┐oHщ╗мВ&lчГ ╨ ?uhAё═P╥DiC╔─Д&─Л },еЯ~╩T-рОhD' }фз╟╨(ж█(▌ЭЭ0╫X│.(k-Б1(н░Xzщc зQжМ ЛВШ`з║cЪбБ╕╙∙▄'хJ+Ф2hн1JФиP% Z╘y5g6s╠y9╠╝FФ П├г╨╙╩╥~▒tPаg╔GX@D ╒хq╩с─с|ANJFBЛyGчЬ─Ьa)ж{ ьBбЩ╢КЛШTrЬ╦╚]ЖxOBУ ъМ1JBЛrй▄┴єЛPfЪ╠╡SnйOp╙O2/ьsМ-_┌├ё┤О╦ Z.EBк2╞├1F╞fУ Ъьp<ў╥ШqъТ.─ЛР┘НЯ■р·mO┐╕ )║╤жНPiHєД.йЄ╚ ┐gWs{Ъ/cuАКu"A*BQ.paFб ░В╢А{КЧИ"C)о`JЦ0 ░&Шю бЁе┴МY~╣Пбб·¤у╟o3VГRШъkў│1╘ыїУE}u#;~с╩ю╙л┐Iт[$оE╙O╤Ф&ЩOH|B" IСr$╞Й╟В╧=.їdIJЪд┤Ъ >,H3╛ы1N/пхрф■Л├<Шm8█цъ6'EADsrЄцЮ=7╜0Ё4чпxЗ╟AсР▄УЦ$phлQV НвEРх╕╪d╞ШYR j(ёЗ√чЖл6єСю l~b7Кvq ВmC√└0┌а┤a╙ЩЯфUЎЄ╠■'X];У├ггикPдY%kГ0 МвсДбе( ┤Y╥АоNY┬У▀█┼╓ыют▐k╛├╬Э;█ЗjЦ▓нRj)Мnўьц╦n┼∙Ь]√Юв;юуН╞ж[╚ТМ╝ФрVh║)Є8 ╦ М╤(г05M °j■r n6o╕Г╗?vя "ЬwU╢J+─═хъ╖^~Е╦┘╡яi*кЛ ╛1АэSdqJ%дa xт"╞Ъд ак8ыш┘╝Їл=|ю├[°ц5▀>%Zc5╬╗fшЛы╛КBєзWvRMл╝6p▌гqqО/Го@OБSБ│'╬eяc°┬·[╕чъoЭТЖlY─ ▐╔в DД█╫mеVъцб~AmвПс┴Qт%`j ╕К╬Є¤╝ёьaю№°=lYw√В[╒bC┐Ё├W0БAл╬№╠┐Р|Ў7p¤%Яб∙JV╞╜$З└7щa8г┘╧фє [п╛Л═W▄╓Aхз>МB╨VсЄ╬ъXь,°№е7с|┴├√5qXbpшДp▐9лi╝▐тKW}Е-Ч▀▐б33Я┤0`Н╦T╟Я╣Jе╥!8s╖╗у╩пQОJl√єП9/юGi8VЯdы╞opчGя^   Ж Нт╣яях¤╖╝ц`╪╛}√у"вц▀pjЬ╞╗╗.Z5╕фпkз▓ .ш▐░п ╪├>°рмє╣n_╧ДAЕk5y├sёЦw2ЎцF-чH[Еs9Iлп=BЪyL/ХаєQ~g#╨V#┌ГRи╘╠О─C╘╒wLДxхA┴ У,%Зqx?%tEXtdate:create2015-08-31T15:00:15+02:00q╩H%tEXtdate:modify2015-08-31T15:00:15+02:00ErЇIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/editors/filesave.png0000644000175100001770000000217014654363416022200 0ustar00runnerdockerЙPNG  IHDRрw=°gAMA▒П №a cHRMz&АД·Ашu0ъ`:ШpЬ║Q<bKGD   а╜зУПIDATH╟ЭФ╦Л\E╞зк{nO╥cт8!N MpI▄H\H DA№+▄╗tх▐Е;7".─ЕВшB№▓Илd└ИKF#·>ъ<\▄█ЭюЙ.┌т╓н√иs╛·ъ|_╔┘│╧o═ъ┘O╟О?uю╣g╧ФлWп┤D░VK)sя■;?| уA╬щ╗_n▌°@NЬ╪▌F°∙шЇЙ│O;╩═Ы7╓╦╝╘.]zЩЭ▌ч8▓╣╔7_.# gФ2/]zЕ_ўnбnJмIб┌°RьИp"Д■ЖКa╢6@╩#0S▄l !▄Э┌Nq+k╫ $CfЖнDЇ?(j╕∙┌ $ы└р@DO-┌о`┌н╧Y0P╒eА@┬1эQKQ▄m}ЩкАк.rїDТЮA@WS]2╕fK "UВо(■?$х╛ЦкЗ╢h╚4_ї▐Э█Ь? ┬ТЦc~н&\╜▒╖w╗▀bUF·ШК@Т░oЯП?·ВцC ╕ЇBмF?w O╢wN ЫКzйnя``хрgхчъсТРрbХфЦТЦСХCR!,паид╠1CE╔ а-j╠*м(юP'фRT ▀2вЦоЮcжz%tEXtdate:create2015-08-31T15:00:15+02:00q╩H%tEXtdate:modify2015-08-31T15:00:15+02:00ErЇIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/editors/plot.png0000644000175100001770000000100314654363416021352 0ustar00runnerdockerЙPNG  IHDR(-SgAMA▒О|√QУ cHRMz%АГ∙ Ащu0ъ`:ШoТ_┼FZPLTE   LDeLDe√√¤її°··№┐┐┐ъъЄььЇiм╪SЯ╤r╝╣▌ююї>Х╠№№¤А╕▐ёёЎ>Ф╠АА┐єє°*Й╚+К╟°°√■■ ┬   А├ x┐В╔■dtRNS┴х°ў4&bKGDИH pHYs  н#╜utIME▀МUR■ВIDAT╙]Пэ┬0E╗о░*Иэм_я Ъ&jvюMN°)mЩЄ?SJy.е 8%ЫА┼┴╚┼┼\w√Zй:т rГ┼E;йЮХ╘`uqщ╞К▄{$дл 2RИaмh┴c╕╕┼w╢rёh╬│5╢КНА^ь~ъ'ш+╢▀╛^И ЮSLСм%tEXtdate:create2015-08-31T15:00:15+02:00q╩H%tEXtdate:modify2015-08-31T15:00:15+02:00ErЇIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/editors/rename.png0000644000175100001770000000126114654363416021651 0ustar00runnerdockerЙPNG  IHDR(-SgAMA▒О|√QУ cHRMz%АГ∙ Ащu0ъ`:ШoТ_┼FэPLTE   ББББББfff   ███▄▄▌▐▐▐ррртттхффчччщщщьььюююёёёЇєє▄▄▄АААтстфффцчцшщшыыыююэЁЁёЄєЄїїї▌▌▌уууцццшшщэээЁяяЄЄЄЇїїўўЎ▀▀▀ххцшшшъъъэьэЁяЁЇЇЇўЎЎ∙°∙рссяюяёЄёЎЎЎ°°°√··ььыєєє°ў°···№√√ьыьЁЁЁ°ўў∙∙·√№√¤¤№щшшюэюєЄєЇїЇўўў√√√¤¤¤■■■ъыъяЁяЎўЎ∙∙∙·√·№¤¤¤■■д+f8tRNSЩЄЄ4hїъbKGDИH pHYs  d_СtIME▀МUR■ЬIDAT╙Е╧YБ@ЖсЩ▓E 3" IЦ";Y┘w  ч╕·О:ы9╝П▐б╪0 #─%вё!$SщМР═ЙТмф Д╒RYлTk аNhCojF╦lw║ДЎ,╗?0Эс`LиeKУй3Ы/B┼ем╕l╡▐иДne╧e;╨эшЭ╬ z╗Xуi▓╫√є¤Е├0 ╟E,$KdqW└%tEXtdate:create2015-08-31T15:00:15+02:00q╩H%tEXtdate:modify2015-08-31T15:00:15+02:00ErЇIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/editors/selectall.png0000644000175100001770000000224014654363416022350 0ustar00runnerdockerЙPNG  IHDR szzЇsRGBо╬щgAMA▒П №a pHYs├├╟oиd5IDATXG╡Ч[oE╟╧▐╫О▌╞M-НBJУ╪╣|>HДрйн@т╤JyО╘V|$─йBтБВРй _еояё%▒ў┬93│ЮЭї.дA>╥╧sц▓{■╗ ┘╡нБИ0 ╡╟_╛ !╕bh9б┴∙г/~╓4-ф]OЮ╛ў┴З¤xг┤жЛбеDз╙Ю=√с¤З~в■╝XиA~u╡д╗о╦└ql╒аZвм░╠@{ч`ПКXк*▓8аFкАz{┐╓╬■ГgRМиEкА■╪Зч╒Щ╖╬is*MhЎ/пt(к╒j&├сИС6╟й1j╡Ъ8[╠ВДИ+яБRй√√√В=╪█Л╪Е▌▌ИQXn─dд 0 Ь¤LlS╫╥ч8f│4\з├═ВХ;∙Wр∙Бfd2їBМ╬сЇe[╨Бн╪яЎЗp1C┴║7ЛЦ╝ вFW╢`4в= ╜Ц╘б^пCл╒ЦEc$\Y└ъъu┼ыЭЭИ T*X__╟Ujq& б UАehpўэ╖Rx6Р╫ рв┐.·LРямП{Гх╕╚ўЄy╕S▓╘H0єШАДМ ?╟░э Жp┌ъв▀=F│Н9ГЄ 'SX+ШИGW$%dZpttДT╔g\ї\·1ЩL╘в1Т1 :~№ЇЁу╧>¤№╗Ьы┬oН38э╤█KL2/Eкф╪Лuт∙;E╓Є1~ хЖa└7▀~¤╔г/╛з∙Е;@ЛЁ╔Й=╫Ї<У╖█╚ч}█╠■83NzSх Й──C Tzш│|о╗╨d>sп%=x)K9IgнЬ fq=m7*s▒(-й %*s!0p╗48┤,1!/o9|ГB]сtsтsrГAZ:}KЖB]ИbqТbqСC^ИLЖ мг ╩┴ ╩┬ пж _P КГ │п ┌╪ ┌╫ │░ РИ fX▐0"ЄIA ib ЫЦ Їє ЄЄ ЩФ ngїNF▀/"╬ ┬▀;3■ми гЯ цх чч ╣╢№ЮЪ▌2)┴╠ ║┤чеб   °УО ▐▄ ▀▐·╕╡сРМ┤║╝й╜70чЯЫ╘ ї╞─ї├┴╙"щнк¤■■╗2,к╕ б Щ╙up╦@9├ ═/'═.'└ ╔?9═rnХдВМ╙xt╧A:╚╠$╦#╞═@9╤vrИ{вд╚B<ъвЮ╨╒&╘&╬шбЮ├>7ХЕ┬ьиеє╢│яrlюrlЄ╡▓щдб╜░ ╤╦"┌2)ўжв №№ўда╘,$─╟с.!у91№_W жв ╥╨ зв№^W▐4,╫% H: Аx ╕╡ ут хф ╗╖№C5 │н у▐ хс ╡пt┐f=tRNS'aВВb'#еїїж#;фх]{=[zPjЕ ?RbvКЩй6Wv1QqЙЧи[k.9BU[c}Рг6Tt-LkyНбV`jGWjh~ФYwФPoМg}УAUj%"! .'.)EVtРLjИ.K *GfF]vH_x,Ii=]}NgБOhВ;Z{4UuNgАMe-KjОж╣E`{WpКSkДF`{XrОЦкdБЬWxХTtСTlЕlЕЬcВЭE`|dАЫSrРQpНZqКJdАaЫCdГUoЙeАЩZyЧYxФ\tНUoЙJiЗUuТYsМnКгgЕЯYtОXwФруч▀тц╞╒с═█ц╫ты╒съ╬▄ц╥▀ъ║╚╒е┐╘Жб╕Ск┐л└╨к┐╨Тл┐Лд║и┬╓а▓┴tОвwЧ▓cВЭhЗвwФнbyР`wПiИвdДЮwХ░qЛа_|Ц[zЧHhЖKjЙcЪa~ЩJjЗXwХXvУNnЛB`?_?_~CbА8Wv;Zy7VuSqН[xФ>]}=[z=\|=\{=]{\zФEeДGfЕLlЛb~ЩgГЭMmЛIhЕFeД^{Ч[xУMlЛSrРYxХnЛеmИвZyЦRrП\yХTnЗRrСWwФaАЫxХнyФмcГЮWvФSsТUoЙb}ЦqРк}Щ▒АЪ▒nОиb|ЦtКЯmДЫ   вЛп]РtRNS&+)пNF▒52╫ЄVOё▀;L▀°hbЎцU╤∙nhў▐3╞╒D╗╦,ж·№╖!█ш0─├&╨╙,>╫їё▄B┌ўbOюшK╪єbNъч\-═шR?▐▌?Р@ABCСDE FGТУФHIJKХЦЧLMNШЩЪЫЬOPQRЭЮЯабSTUвгдежзижйклмVWXYноп░▒▓░│┤╡Z[ \]^╢╖╕╣║░_`a  bc▓╗╝╝╜╛de!"#$%fg┐└┴└┬├─┼hi&'(jk┼╞╟╚╔╩╦╠═╬lm)no╧╨╤╥╙pq╘╒╓╨╫rst╪┘┌█▄uvwx▌▐▀рсyz{туф|}*~хцчАБ+ВГшДЕ,-ЖЗщИЙ.+/КЛМ01-20НОП3┬ЕqmиA╠╡%tEXtdate:create2015-08-31T15:00:15+02:00q╩H%tEXtdate:modify2015-08-31T15:00:15+02:00ErЇIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/fileimport.png0000644000175100001770000000302414654363416021102 0ustar00runnerdockerЙPNG  IHDRрw=°gAMA▒П №a cHRMz&АД·Ашu0ъ`:ШpЬ║Q<bKGD   а╜зУ+IDATH╟}ЦmИ\W╟╧╣яwf▓3│┘$m╥$5+Aм╓╘╛д6╥Z aй╢VbAEЁУ┌ BаЛXбPъз╢Ї5·бФ╥RKmНie)*║Ы╘diЦMww2█╣3sч╬Э{╬ё├ьnf7╕Ь√  єЎ?┘єъW3+ЖBь= y ГЎщAVMmтўЁ6#З╞zzёo╣_ЎPОАlМ!╗_┌╢fу╫їgэи┐dЪdnJ/яБмk┘ф┼Д UjЫjыёo А╗ї│╒Хx┴ ШЬЬd0 "ЬX·# Ю√йэтЙ∙Д<╩Ёїv╓ўrўю╞Г╓н5ar№°qВ▄Zu cА┬b╠0xA)└З∙Hд┼Е■,оы0k■K╫mг▓/╥╗pяMўbнE)ЕR ╧єЖ└▒╖rm╙7 ┌▌3л)jн.х ╞гmШ╨@-╗А┐┼#╓%цЫєЧk=АД.╕пU▀рAs┘bчFЪl-}?cУкbНё ▒─ДQ@GфEСawG┴Т'Лс>ЫБ╫Ў╛ЕЙЛ5рY?гтVиGu*AЩаь3NН1зО]W╥ї$CтHР °o~zj Б1Ж^Ц║!eМ╚ПёbПЭьa┬l└3╢О@Б─▄╪Бqо╚аЭд(GzОч▒ЫыйН╢╓оЮ_+pW ]Б°JВ| л░о▒°xT╟#─▓xеыТ1g-Б╡i┴ИFг)а)╨hD╔j)W╓їцЪ┬Ba▒┌Вf╡о+З+iХМ╓ф&зу╢i░@Й ╒RuuЬW╛5}╨р┌╘BYз3]dцт ╙gжЇ╘ku╞KeC3["#еэ╡9з>dW╛УЪ_#щ$4M ]А@a ╬t■r┘Ў┘Н╬їЦўяЩ╫mЯFr ыА╢9Э^Ч▀>№:g√pбwуNyяRйЦ┘╧═Ь~ ,╟ ЄВ╪cрPСE7Еt║╦G'Ы╥РЭЗ'(Ъ;я┐ї╒Л│═я<|Ё)mЇXЯNФ╨ш/Т9╩┤Яє{Кr9вTл░Um%&,9\\~ ╘Ы|)╛Еc/┐"Nр ╫Ю└j╦R?a▀нЯ∙M;щ}ябЫ~Hт6╔*=·qЖ╕а<╨ЮцМЩ& #т8╞ў}ФгР╚зЮ■;_.d▓6╔с╟х╔PОгЁП╙=√¤█п;└▒йgh7:,╬/╨h.0Ч╠r▒=╟|kОд╒&i%┤ЦZ4/5i$ ·▌ЬSO╜╧mё=<ўГчЦGvyКd┘s'&╖?└О;8ЎЎ ь█u#ню∙XП"╩1о!▒Я╨╧CzYBwъЬ96├╫&ё╦o ъ M╕╦ВCФаЬсИ¤ьыПb┤хх??O╜║Щд┘$пжt├.╓7─¤*6;у,№о╔7о╣Пгў=╢бЪ▌QG╘хЫёСп■ЕЁ№█╧╨∙╕CзТу╓└ГfйK╡ТЭ(x`╧aО:║ё]┤Ю`┼╥4-╥4хG√Lб╡zс─╙*Ы╦╤-0иLTfк▄┐я!є╚┴ЯШ4M╫ИmwCВУ'OоюяўncёsЛ╝qцu│ A▀ец╓╣c√]|%<ажжж ╟d╫w╖ ]╔PЄБePЇ▒╣еЕ┼Ї,[o/╜a█з~▐╬■yqц╤№=~бJАтГ8`ЭсZЙKфч pоТ╦ЭQC |№Nч▒ь└tVhkєwyВ╥ьj╧Х√ТYЖ8фg%tEXtdate:create2015-08-31T15:00:15+02:00q╩H%tEXtdate:modify2015-08-31T15:00:15+02:00ErЇIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/filenew.png0000644000175100001770000000071714654363416020367 0ustar00runnerdockerЙPNG  IHDRJ~їsgAMA▒П №a cHRMz&АД·Ашu0ъ`:ШpЬ║Q<bKGD ЗП╠┐tIME▀МUR■╫IDAT8╦ХФ┴┬0Ж┐в&╛Ь>мя>ФxhэАv╞uIG:>`а┼╪╖О_уЎy-А / P┌а(╩┬┬Щ{╠Iюp@0сФKX║s▌%ДЪ`═┴┌┴░цъO─HВ1░╒╖&╖!ГЎxka╕ иЯ, №м╫№╡j^E╗а┼щSэ `]е╥х╒а╨`@▓╘yk Е╡э 1■кХmY}▀*АП╦╨* `7#Р╨┌єF└є¤tу│ОQ?;&рСfc╛╩▐[Cv·єsT)l╬l.%tEXtdate:create2015-08-31T15:00:15+02:00q╩H%tEXtdate:modify2015-08-31T15:00:15+02:00ErЇIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/fileopen.png0000644000175100001770000000300314654363416020526 0ustar00runnerdockerЙPNG  IHDR╫й═╩gAMA▒П №a cHRMz&АД·Ашu0ъ`:ШpЬ║Q<тPLTE   ├├├╚╚╚╔╔╔╩╩╩╫╓╓╪╪┘╤╪▐м╢╝├├├╦╦╦╙╙╙╒╒╒╫╫╫ррръъъёёЄўўў¤√·╣╔╤XXXэээ╖╟╨NNN┤┼╨KKKп├╨JJJл┬╨HHHз╛╨FFFд╜╬DDDб║═CCCШ╡╦BBBИм╚???е┼<<<|г├wwwzб┴dbb°°°xЮ╛(%%(%%WTTuЫ╝(%%(%%(%%(%%!WUUrШ║(%%(%%(%%!VTTqЦ╕(%%(%%(%%!USSdН│(%%!USSе╖╞Ъ┐т]Ш╨IН═/}╟(y╞(%%!WUU╓╓╓кййmgd:XsБ▒▀p─p┼ v╩(%%%"":77онняЁЁ┘┘┘╒╒╒╠╠╠╕╕╕ЎЎЎ├┬┬;Uyотcл%6I(%%%""ussїїїЁЁЁ >Y_е&,5)+1(')°∙√юї√шЇ№эў¤■■■   ¤■■ьЇ√хЄ√ыў■я· ъЎ√√√√хюЎ▐Ё√щ° щў цє√∙∙∙¤¤¤∙ўї╙▀ъчї■уї фї хї рё√°°°їЇЇяэь╜╟╤фє■▐Є ▀Є ▄ю·ёёёує■╪я ┌Ё ┘я■╓ы·ўўў╜╟╨сЄ¤╙э■╒ю■╙ы√╤щ°ЎЎЎ╝╟╨▐Ё№╨ъ№═цўЁЁЁюьы╗╞╧█ю№╦ч√╔фў╚уЎ╔уЎїїї№№№┘ь·─сў┬▀Ї┼рЇ╜▄єяяяэыъ║╞╧╒щ°╜█Є╛█Є┬▌Єм╥Ёєєє╕─═╨фї╖╓Ё║╪Ё╢╓яШ╞ъюююъшц┤┐╟╦тЇ▓╙ю╡╘юЮ╚ъТ┴ш···щщщсср▐рсж╜╨─▄ёЬ┼шП╛хС┐хуууцурл╗╟п╨ьМ╗уО╗уН╗тЄЄЄхххфтрж╕╞к╠щЛ╕сК╕ре╖╞и╩шЗ╡▐З┤▌И╡▌фффус▀д╢┼и╔чЙ┤▄Ао┌Ёёёэээюьщ╪╪╪$╔╣┬utRNS5[З┤r0@RuЯ╞█шЎЪ0шЫSЫRЫRЫRЫRЫRЫRЫRЫRЫQЫ[Ы9¤ЫOЫ\Ы&)$gЫ 73qШ@B{¤ю╨╛И.NД▀Ц^А▒Нq+d└№¤ш░n5a╗p @+ dl С °ebKGDИHtIME▀МUR■ЭIDAT(╧c`@МL╠,мlьN.n^>~Aб╥▓Є a╕╕ИheT╫╘╓╒7И┴%─лк└RM═-ннmp ╔ЎкJ шшьъющэыЧВKHOKLЬ4y╩╘i╙ж╦└%d!3&MЮ9kЎЬ╣rp ∙yPЙ∙ .Z╝D.б╕┤▓г$▒l∙К+W*┴%ФAлVпY╗n¤ЖН*p ╒MЫ![╢n█╛C .бФВЭ╗vя┘╗o  ╕Дц┴О╞FР─б├GО;оЧ╨Ю╖╣NЬ║Бэ0uўЁЬR╛j╒з╧_╝╝}|¤№┴╬БAUV┴╫рР╨░ЁИ╚(иcгcbутУТSR╥╥32с▐╦╩╬╔═╦З░ Л░Вт %№╞╫CНО%tEXtdate:create2015-08-31T15:00:15+02:00q╩H%tEXtdate:modify2015-08-31T15:00:15+02:00ErЇIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/filesave.png0000644000175100001770000000217014654363416020527 0ustar00runnerdockerЙPNG  IHDRрw=°gAMA▒П №a cHRMz&АД·Ашu0ъ`:ШpЬ║Q<bKGD   а╜зУПIDATH╟ЭФ╦Л\E╞зк{nO╥cт8!N MpI▄H\H DA№+▄╗tх▐Е;7".─ЕВшB№▓Илd└ИKF#·>ъ<\▄█ЭюЙ.┌т╓н√иs╛·ъ|_╔┘│╧o═ъ┘O╟О?uю╣g╧ФлWп┤D░VK)sя■;?| уA╬щ╗_n▌°@NЬ╪▌F°∙шЇЙ│O;╩═Ы7╓╦╝╘.]zЩЭ▌ч8▓╣╔7_.# gФ2/]zЕ_ўnбnJмIб┌°RьИp"Д■ЖКa╢6@╩#0S▄l !▄Э┌Nq+k╫ $CfЖнDЇ?(j╕∙┌ $ы└р@DO-┌о`┌н╧Y0P╒eА@┬1эQKQ▄m}ЩкАк.rїDТЮA@WS]2╕fK "UВо(■?$х╛ЦкЗ╢h╚4_ї▐Э█Ь? ┬ТЦc~н&\╜▒╖w╗▀bUF·ШК@Т░oЯП?·ВцC ╕ЇBмF?w O╢wN ЫКzйnя кюЬ¤╠мl▀▐]┼ЁCKeє╬нm-МП tkЩ╫м}√·шш┌┴жrЩ▄╩K╤H╬:Ўь{Д?╬L"D ш-R(ЄhTвъ?hD╒pу╞ЯаКВК▄2Аu9 DС5T!╞ИIИтoY5TdА*J╢БВBФ°┐ИiЪR.Ч1.4l╨цC$а(IъСР■'Г чз9]√Е╓╢vю?АbVД░ (hDВр} И№k╘ыuN|¤П>╝Ч╓═Ыщ╜{A$╙! a-Гии5Е╘$ДЫ2┴│07Coo/з&&YX╝╬╜╢Р$pаE kEU%Ф╘тMВў\YЬ├h╩┘││L╓j╝pЁe╝ЩUм╦┤ aCЙTQ╒╒[Я∙¤7 ┼∙b▒М1\╗▓Т0?┐└№тЖЯyЮ-э╒╒sg╬№ЦХ8BV;╣ннZ┼ШvЇь┌╣╕0═╢m╠КГ) Їїї184─╠∙ МНН1??GбX\SВьь╒л╫иv▄A>_╪шв╠к╒О█i$k-"Сr╣D▀■З8└╣щiОП}╔▄№5r∙V╥аYяиТsО#[к]"~ @УIАb▒АsёK ЇяaЁ╤~жзз∙т°qfg/R(@гр H\о С4Mp╣<╬┘ Жfг)ho▌╩K/фё'Ж Йы╫п1z°=;wРж)╞d%t╬qЄфI>·°┬r╚BаX*mpQTВ╧ФJEЎўэс╔Сзй╒NqЇГ9wnЖчЮaK[+Н╞Є*А╡Цоююж^g]╓h>l`аКHаT,1pр^эUN :┼Й▀03sСbйHМ┬ЄЄ2iЪо~gМб▒╘ xOЁ!╞╕^ГБЭ╦10╪╧шш╘NЭт╟┐зT*cМEB└{OЪzЦЧЧWus╬Сжi╓¤!г`н┼╗~TT*72<─[oО291┴╤c╟шььдеея}ЦsЎ▄∙ /]B6A╬D╔╛ш▓bКМLaб┤,(╪нЇDDРLР╙224╫╥Ф0║тш(ahлrї┌uyyNДsIU├ъ·ОЪ·::цvцRкЖ╩╩8$u,д─ -╘еt,Н┤М хнP(*(**(╚╔нNYmr╚╚ 8|}а*|eа*ЇЇ╪ЙW└╔┼═├ `Ъ°°∙╣И╩∙╛ИзBы╚ %tEXtdate:create2015-08-31T15:00:15+02:00q╩H%tEXtdate:modify2015-08-31T15:00:15+02:00ErЇIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/filetypes/gif.png0000644000175100001770000000275014654363416021506 0ustar00runnerdockerЙPNG  IHDR szzЇgAMA▒О|√QУ cHRMz%АГ∙ Ащu0ъ`:ШoТ_┼FbKGD   а╜зУ pHYs  ЪЬъIDATX├┼Ч]hU╟w>vf│Ы▌═Wє╤╪жН mkСJ+*┌j}HДJйQQPФ·&тЫP╤ё┴┐Ё╔К К6BJб╨/RкжMmHl║IЪ6Ыd7ЫЭ▌ЭЩ{}()Ы═юf╒R/,wч╠сЬ ¤ЯsцЬ+ФR№Я╦(ЇїїM!PJ!еD╙4КБ? !VBмРўЎЎон@┴Ю={┌Ц@,(▄K╔Ц@■дФH)QJбi¤¤¤У┼■┤b┴Э╔ еи╝]лФmгФт ет╝$+ ╤╥^Тт№)╟lI Х ┴Ф√_╕/╤4э&а№$Ж7№╧CP╩I%@┼zBР№┘я┴h[Ц╨╙╡Ъ├J 2cВy║╒T6■Х@д╞╛Fh`╖ьпШ+мFy5Ldч┘УXн/мZUF╣KF │\║Лxй?ё╙уаP∙4┬sA╫V▌nE6У┐■ Z┤гf]╔Cн а°Дnц:▐Ї▐№0JyhЦНЯBООу╬NСЩЫ&╕uv╫&jBS=@>g/лМк(VN^9Ж?}шN>╦┬╘ё_Oр$│шAA[{Ки=Л╨▓,▄a╬╟▄№9─·Чйoj¤w №~№ 6З9Ъ^фдpIxiЎ ЫЭvлc=нzpй]┼h║Ыl(┬СSуr )iЄ`Ї"[у`ю:Д╨Мъ┐Д з├║╚LNёQ|Д┴─8°y█6GяyФН║Aа)Bин╧s▌│─п ёхIН │!д?Ж;°|ў з~■ШЮ8X=n>╧т╒Є═╙uy7▄╚╖IЗ┤)шМEhё<2╛ПfXdriьН╚;IъОЁ▐Н╬╓`Ъ i7bxIj▌╙МОьо└ш╚%ъ RIЙЫsщ ╓sи╗ хчHх╥°JC╙ ▄\гхqjZb╝  ╛3ГЩўxeз└╢ ц'0Ё▒ УЙ─4`U`61M╠Ia═k=lsФЕДO*█Дu╕∙<╥Kqc·:nzЪ·ЩOIMН╧┤╤▒¤%._╗L█№OXM'Уq ╖╫С\pклВpmєcТ mУ─╘E▓Й$С<ф№Nо-lBH▌╘Щ╕Ъ└∙ ╡бБЦж┴5kшъZ╧|4KrHG](╬\ єLo7g╬ЮнОБM]▌ЇЭИ╤~mЦ▒щ ║┬U6Ом╙A34rОGccШPдЖАe22щР╬^ftфMЖЗT:Y╧┬ v╥єт[─ъък╧├4i▀▓ЧKГяSы&уxШБ@С[╠бiВhCДАC│ы╪¤▄;DгQдФф▓9дТDj#kj╨u¤╗UЧсЎПpxlюНohК╔KЕчБЧє╤ 2й,о)И╒h47╖▄r╫.sVn└БUЪ╤▐пbo~ЫєёЫ├ЕоJБУ╬сII╓Х─№a~;sмbу*╟@IЕF~ьIЮ~¤3╞┤з87b╬СxЮ┬ZC,╦ `М\8]ЎФЕНнк*+еиллg єoр{пqixИ┐FЖШO─qfgP╥╟7░лg▀▓юYЙ┘ККТ┬vlШ&ўn╛Я√╢l[ж[ят;EеQ ╢ $хЎ╒юwЇbRu▄╔U2~СRКrЩ[xвтўеЮ raт ╛Ю  ШnфЬ&╖%tEXtdate:create2015-08-31T15:00:15+02:00q╩H%tEXtdate:modify2015-08-31T15:00:15+02:00ErЇIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/filetypes/html.png0000644000175100001770000000347014654363416021705 0ustar00runnerdockerЙPNG  IHDR szzЇgAMA▒О|√QУ cHRMz%АГ∙ Ащu0ъ`:ШoТ_┼FbKGD   а╜зУ pHYs  ЪЬ:IDATX├┼Ч[h\╟╟3ч▓чь┘╒ъ▓л╦Jrd;V┘nМнBRRJ!$(Oе)ЕBB▐·▄З╥ў╥╥З>4Б╥BЯ[hбФуД@ыz еI\;Сэ─rм╦Z╖]I{;╖ЩщГ,g#пй┤d`9Ь┘┘є¤ц }чЫ  c Яч░ўN\╝xqIБ1н5RJЎВю╜B<Їp!─Cє│││г!ШЭЭ- пvи╡~{∙Єхх╧T└C╟ьк░╗Г╬k╖╣▌▀v~┤╓Яш6d7■_г█│эn wЙ╗хywоSб▌kз"╒╧Б{ея ЄY·~)R╩OA `7Ё▐v-пЗl6Sд%щ╧;Ф .Ц─й┴`░─И╫╩БЭБў*╞Ъ╫~{З┐-DФ·J}>hCнa┼ OyЎ\ЦDЙ&╥a ~╞><└~ \·╦*?║Xс╔│#|√┘cМ}▓▐╬┌f█ЁсrЭЯ┐▒╚л°ШЯ}gЪ├H M5НPэ[ ]╘╣╨├/▀\с┼╫nqЎTСo<3╬╠dЦFШ▓╒Tl5aв)|^xrФЮ\└╘╦oё╬|ЛМ-▒,┘╡!╨й└√K▀¤щOLХьє╣▒Xgq%╕Ui3┐▓╕▓Xm│╒J)│<¤е2п╝·╡ж┬Тт@А_├э╢те@╕YЗzKscй╔№j─╔rАcK╓╢cЪнФЬoг°ЛяkКЕ╒&?№}Е╝X╞Ё_6в▀¤cУы╖ыd}ЧэPS┘МX▄И╣│╓!x∙й,_}╝,┴╩V╠▄RУ[+-▐╜]ч╧W╫∙╔пц╕q/╞Ц[QБ85╝■пMР`█ТїzJ+ -zЬ╬aTЁ╫╣mе0╩0^Є∙┼ыs{i к!Wо╫yм\<№[а T)Л╡\╔╠T?╙╟zъ═░VO╕z╖┴═{m▐╗У#c ▒Fxl4╟;n╥И╟ю╓Ъ№¤ц6п|е Ё)h╟Ж0LIН)xю№ ▀√·═Д╣JЛмч`9sХ,Лo~╣╠єYкF($Ууzє.и6 Бц)h╟ c A6C▒рёзў╓yы╞6╡V╩@▐╟ўl4;y={<╧K3┐y╫у╩╡ЧНнР╗Х&╪ПОхIRu4в─`[В╙=8О═█sUVB/Гх┌фГ }=z{5╚`)Ou;&5nVP r╞'Q╙└ъ│1╣~O24P.e╤вDc╠■N{▀├(+5y▀ж╤6╕о`t(╟p Ф╟▒С6аS▓фш╧уZ╫СdIП/╤ZRа4З°─КБOУ═Ж1C╞8ОМ╨HЩь8`OА░0ўK╟s╛cРB┼╨Иў╖d]Ве4}8╨R¤TкXo╣cuСR"XЦ─╡%RА%A ╨R`8E┴▐ ╝tД\║tщ н╡шt╟√еjяў▌ю;МюCИ╧√я∙аD!┐ўxG%tEXtdate:create2015-08-31T15:00:15+02:00q╩H%tEXtdate:modify2015-08-31T15:00:15+02:00ErЇIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/filetypes/jpg.png0000644000175100001770000000275014654363416021521 0ustar00runnerdockerЙPNG  IHDR szzЇgAMA▒О|√QУ cHRMz%АГ∙ Ащu0ъ`:ШoТ_┼FbKGD   а╜зУ pHYs  ЪЬъIDATX├┼Ч]hU╟w>vf│Ы▌═Wє╤╪жН mkСJ+*┌j}HДJйQQPФ·&тЫP╤ё┴┐Ё╔К К6BJб╨/RкжMmHl║IЪ6Ыd7ЫЭ▌ЭЩ{}()Ы═юf╒R/,wч╠сЬ ¤ЯsцЬ+ФR№Я╦(ЇїїM!PJ!еD╙4КБ? !VBмРўЎЎон@┴Ю={┌Ц@,(▄K╔Ц@■дФH)QJбi¤¤¤У┼■┤b┴Э╔ еи╝]лФmгФт ет╝$+ ╤╥^Тт№)╟lI Х ┴Ф√_╕/╤4э&а№$Ж7№╧CP╩I%@┼zBР№┘я┴h[Ц╨╙╡Ъ├J 2cВy║╒T6■Х@д╞╛Fh`╖ьпШ+мFy5Ldч┘УXн/мZUF╣KF │\║Лxй?ё╙уаP∙4┬sA╫V▌nE6У┐■ Z┤гf]╔Cн а°Дnц:▐Ї▐№0JyhЦНЯBООу╬NСЩЫ&╕uv╫&jBS=@>g/лМк(VN^9Ж?}шN>╦┬╘ё_Oр$│шAA[{Ки=Л╨▓,▄a╬╟▄№9─·Чйoj¤w №~№ 6З9Ъ^фдpIxiЎ ЫЭvлc=нzpй]┼h║Ыl(┬СSуr )iЄ`Ї"[у`ю:Д╨Мъ┐Д з├║╚LNёQ|Д┴─8°y█6GяyФН║Aа)Bин╧s▌│─п ёхIН │!д?Ж;°|ў з~■ШЮ8X=n>╧т╒Є═╙uy7▄╚╖IЗ┤)шМEhё<2╛ПfXdriьН╚;IъОЁ▐Н╬╓`Ъ i7bxIj▌╙МОьо└ш╚%ъ RIЙЫsщ ╓sи╗ хчHх╥°JC╙ ▄\гхqjZb╝  ╛3ГЩўxeз└╢ ц'0Ё▒ УЙ─4`U`61M╠Ia═k=lsФЕДO*█Дu╕∙<╥Kqc·:nzЪ·ЩOIMН╧┤╤▒¤%._╗L█№OXM'Уq ╖╫С\pклВpmєcТ mУ─╘E▓Й$С<ф№Nо-lBH▌╘Щ╕Ъ└∙ ╡бБЦж┴5kшъZ╧|4KrHG](╬\ єLo7g╬ЮнОБM]▌ЇЭИ╤~mЦ▒щ ║┬U6Ом╙A34rОGccШPдЖАe22щР╬^ftфMЖЗT:Y╧┬ v╥єт[─ъък╧├4i▀▓ЧKГяSы&уxШБ@С[╠бiВhCДАC│ы╪¤▄;DгQдФф▓9дТDj#kj╨u¤╗UЧсЎПpxlюНohК╔KЕчБЧє╤ 2й,о)И╒h47╖▄r╫.sVn└БUЪ╤▐пbo~ЫєёЫ├ЕоJБУ╬сII╓Х─№a~;sмbу*╟@IЕF~ьIЮ~¤3╞┤з87b╬СxЮ┬ZC,╦ `М\8]ЎФЕНнк*+еиллg єoр{пqixИ┐FЖШO─qfgP╥╟7░лg▀▓юYЙ┘ККТ┬vlШ&ўn╛Я√╢l[ж[ят;EеQ ╢ $хЎ╒юwЇbRu▄╔U2~СRКrЩ[xвтўеЮ raт ╛Ю  ШnфЬ&╖%tEXtdate:create2015-08-31T15:00:15+02:00q╩H%tEXtdate:modify2015-08-31T15:00:15+02:00ErЇIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/filetypes/pdf.png0000644000175100001770000000300714654363416021506 0ustar00runnerdockerЙPNG  IHDR szzЇgAMA▒О|√QУ cHRMz%АГ∙ Ащu0ъ`:ШoТ_┼FbKGD   а╜зУ pHYs  ЪЬ IDATX├┼Ч═kW╞я{fо%Z┌и╤╞╞AРB*]ЩV╓veRZ-ж▌╡╕)n╠?╨КBAD н EA╘WZЪ6))ЕBE┴L┴v╒▒FRУ▄Щєvqfц▐kJ╛║Ё└с╠╜g8╧є>ч¤13ЮхИЄЗ│_]·sz ╘БD┘ъ▓U├D@$мH╒)VY═*┐═Зu╧Gэkч$А┴ОнэMоГFU3#УСjREаКДO=▐¤Ч┐ЪM}ж·╫(LЧзЙTP За┴(В"а*D▀ро JЄё'°╒НШY1╙╘c>( sШX╗mЖ∙lЪaф+┼сd╧юЧЯH▐┘НМ¤Н:AЭр"EЭ┼КЛХидИ[АJ  C РЁlАО?В╔'╨╝ЫЬ@DPS╝ўиixW@ 'aRG┬┴`ИИeюmш═ыHд°5Ни иHABDцTаЖЯЩe``NI, ╜░o/<ПLOб┐ еН@ЬUв&╖DИЮS\┤dЦB╠БE@$s(54v0=Й√ю"ЄrcПAг░║XИb┴E╠_Б\ц"╔фАbХйЖ:└┤мCо_Гц5A═=▐2┬┬|╞ ┬╜[╢S!QЕД╡mЖ╡M╚ТШ<UG╔|╟S>j╡S ╤p╖hF┬YР|т128╬╤ЕРи╚д!ХIъ0 qюU щы├Z[С▐▐Ё)А 2 sk╒QО24ўюСЬ;ЗЮ=ЛНП/ЪDA ┐Rx,{╢p=█╝▀╥Вok├uu╒/┌|j°4▄Nвp╛ ▄Т╜xСд│Аф╨!ф╬ф╘йEС(╥ДH░еиYn╠┬┴qw7V_П▀╕1ГRЙrOёО°╤Q|WW┼ я@@йїЄH└ъB*6 Щ1ъюж|фHaбИр) wt 7oТю▀уу╚╚ыо\iвгг╛─ь■м Д╩Fс|И─\O╛╡Хd█╢4├├ш╡kшэ█╚╚~╙&dxШи╜├vюд╟/яя"ЄV5ЙZ▓lв!k├ЄКР$─зO3}рzї*ю╞ ШЬД╒лI╖oЗЦl∙rдTB<└їЎвwя┬─└П└╧└@0УТхэк(ХBъ%GПтЫЫ▒·zHS╩|uuбтUз]3hh ┘╖g╞¤У'yх╠ЩЧА]└-рCDnavn&нд[qВyП>zДNLїї1qсм\Y ╡ёЮ кк`╬1╢~¤f]┘ лАЭ└─,W|АЄq ўдmmФdjя^l┼КJ┬и-ч =│┐y(┬гТ лIL■├╥╧>%▌░Би┐е╝gOaЭy├{ПO=iТ╓мбЎ┼; A"┴м╪и\AVыг{C,;~Шщ▌╗┬▌?╬уKЧHУ┤ъ +Zq uK$л%R╠(Оц╠╡aиPўїаКojbYg'у'NР4м┬Т4Ї№╛ТэrЁ№AE3_T3├'є$Р7!So╛M№╟ еc╟ №0Уп╛ЖMХ `м╩ё2╦-я═║PU%ЄiЪ╬У`xЮl┘┬ф╓╫┴kЪ2ЗУ Й№:<Q!Х4°┴|БеuuШ&ИжШўШk╜ўEПRm¤╙CDВU╛р╙yP? \Юї;n1CY:ы╛<ы╧є5╚жr▒·G%tEXtdate:create2015-08-31T15:00:15+02:00q╩H%tEXtdate:modify2015-08-31T15:00:15+02:00ErЇIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/filetypes/png.png0000644000175100001770000000275014654363416021525 0ustar00runnerdockerЙPNG  IHDR szzЇgAMA▒О|√QУ cHRMz%АГ∙ Ащu0ъ`:ШoТ_┼FbKGD   а╜зУ pHYs  ЪЬъIDATX├┼Ч]hU╟w>vf│Ы▌═Wє╤╪жН mkСJ+*┌j}HДJйQQPФ·&тЫP╤ё┴┐Ё╔К К6BJб╨/RкжMmHl║IЪ6Ыd7ЫЭ▌ЭЩ{}()Ы═юf╒R/,wч╠сЬ ¤ЯsцЬ+ФR№Я╦(ЇїїM!PJ!еD╙4КБ? !VBмРўЎЎон@┴Ю={┌Ц@,(▄K╔Ц@■дФH)QJбi¤¤¤У┼■┤b┴Э╔ еи╝]лФmгФт ет╝$+ ╤╥^Тт№)╟lI Х ┴Ф√_╕/╤4э&а№$Ж7№╧CP╩I%@┼zBР№┘я┴h[Ц╨╙╡Ъ├J 2cВy║╒T6■Х@д╞╛Fh`╖ьпШ+мFy5Ldч┘УXн/мZUF╣KF │\║Лxй?ё╙уаP∙4┬sA╫V▌nE6У┐■ Z┤гf]╔Cн а°Дnц:▐Ї▐№0JyhЦНЯBООу╬NСЩЫ&╕uv╫&jBS=@>g/лМк(VN^9Ж?}шN>╦┬╘ё_Oр$│шAA[{Ки=Л╨▓,▄a╬╟▄№9─·Чйoj¤w №~№ 6З9Ъ^фдpIxiЎ ЫЭvлc=нzpй]┼h║Ыl(┬СSуr )iЄ`Ї"[у`ю:Д╨Мъ┐Д з├║╚LNёQ|Д┴─8°y█6GяyФН║Aа)Bин╧s▌│─п ёхIН │!д?Ж;°|ў з~■ШЮ8X=n>╧т╒Є═╙uy7▄╚╖IЗ┤)шМEhё<2╛ПfXdriьН╚;IъОЁ▐Н╬╓`Ъ i7bxIj▌╙МОьо└ш╚%ъ RIЙЫsщ ╓sи╗ хчHх╥°JC╙ ▄\гхqjZb╝  ╛3ГЩўxeз└╢ ц'0Ё▒ УЙ─4`U`61M╠Ia═k=lsФЕДO*█Дu╕∙<╥Kqc·:nzЪ·ЩOIMН╧┤╤▒¤%._╗L█№OXM'Уq ╖╫С\pклВpmєcТ mУ─╘E▓Й$С<ф№Nо-lBH▌╘Щ╕Ъ└∙ ╡бБЦж┴5kшъZ╧|4KrHG](╬\ єLo7g╬ЮнОБM]▌ЇЭИ╤~mЦ▒щ ║┬U6Ом╙A34rОGccШPдЖАe22щР╬^ftфMЖЗT:Y╧┬ v╥єт[─ъък╧├4i▀▓ЧKГяSы&уxШБ@С[╠бiВhCДАC│ы╪¤▄;DгQдФф▓9дТDj#kj╨u¤╗UЧсЎПpxlюНohК╔KЕчБЧє╤ 2й,о)И╒h47╖▄r╫.sVn└БUЪ╤▐пbo~ЫєёЫ├ЕоJБУ╬сII╓Х─№a~;sмbу*╟@IЕF~ьIЮ~¤3╞┤з87b╬СxЮ┬ZC,╦ `М\8]ЎФЕНнк*+еиллg єoр{пqixИ┐FЖШO─qfgP╥╟7░лg▀▓юYЙ┘ККТ┬vlШ&ўn╛Я√╢l[ж[ят;EеQ ╢ $хЎ╒юwЇbRu▄╔U2~СRКrЩ[xвтўеЮ raт ╛Ю  ШnфЬ&╖%tEXtdate:create2015-08-31T15:00:15+02:00q╩H%tEXtdate:modify2015-08-31T15:00:15+02:00ErЇIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/filetypes/pps.png0000644000175100001770000000263714654363416021547 0ustar00runnerdockerЙPNG  IHDR DдК╞gAMA▒О|√QУ cHRMz%АГ∙ Ащu0ъ`:ШoТ_┼F[PLTE   ▓▓▓│││░░░▒▒▒┤┤┤╡╡╡╢╢╢╖╖╖╕╕╕лллйййиииккклллммм∙∙∙···√√√№№№¤¤¤■■■   ўўўЎЎЎ°°°╨╨╨ййй║║║┤┤┤їїїеее  √  ■пом 9'1E".H(G!A!?E-J#2L :■  ▓░пЇЇЇмли М+t0c░`С▀З╛Єд╤∙Ж╢юC{═EХ "а╢╡▒лйзWа Ф▄ кйзєў·ї∙ ў√ √  ╜╜╜єєєйжвGХЎv┴№  ў ∙ч  °  ЎкккзегkмЇЧ█■QFToN`~ZeИcsТeyТyОгbtИKYn#;g┤│▒ЄЄЄедвЭ╤ї┬Ї√#о>v─Е▒с╒ьїў°■√√ ў№ л┤╠:J`>и▒░оёёёдввk╢ъЛс∙v∙m▒√`з√cг№gй∙aаїЕ│ў^Ч▀pкЄHКЄ▒пнеед╗╗╗▐▐▐GХ°Б▒√lдёeЯэ]ЪыQТфlйя[ХъЫ╘■bз■пниЁЁЁЯЯЯююяЎўўbгЁЦ┐ыМ╛ьФ╜єХ└ыО╣хл┼ыxпфо█■u┤√омйяяяЗИИКЛМПООo░█У└┌Й╕╓Й╣╓К╡┘Ж╡┘Й╕╙а╬Ё╟я№Я┌√млй╢░м~┬ыЧ╓№а╪ўг▌√лф№нц√д▐°Э╪·Ю╒ЎМ╬їлййююю╥╥╥╓╓╓┘┘┘ссс▀▀▀╤╤╤═══ннк╣╣╗ўЎЎ ■ √∙√¤№¤эээРРРРСРРССТТУТТТПРПьььыыыЙ)╠tRNSххххххххх╕╣╣╣╣╣b;МцbKGDИH pHYs  ЪЬtIME▀МUR■IDAT8╦Н╙хw╙PЁе█Ъuv#}"Гl├mwVl├К;┴ з╕▄▌ lhбщ■,n┌├r ЬCЯ/ўффwЮў╜9'99┘Дєq╣ЧGУKуЮyЬПж°И@A%YБа,ЙDQUp >НhД0"HЕжх{@бПмвшБ|ESи2Д,{└пjz ▌▌Ё{ к╥LЪ╣{╝╫`(е(√¤║%й)LГ▀PЫгE╦Vн█┤m╫╛C╟КNщ=2:гK╫n▌{Їь╒╗O_╜_zП ╨Ъ4x╚PTд c ╟ИСци╤хeХUUХ.єЛqуMLШ8iЄФй╙ж╧ШЙYь-°Hx6ц╠51o■ВЕЛ/Y║l9Vd+▓лVЫX│v▌· 7m▐▓█dЩ╓vTя0▒sW═ю={ўэ?pЗ2@╘:М#GM;~тфй╙g╬Ю;П ТT╨ ьи~ё╥eWо^╗~уцн█wютЮ$ё,░эhЇ><|Ї°╔╙g╧_╝─+QdБэPaв·їЫ┌║║╖╡я▐уГ 0#█бт#>╤/Л┼Ё∙ JБipЬ8·W7ЙDт█ў?uBШЖ╕wЕ╡,+ЗBЖб`@@HFIHFH@?AКЛН╜┐┴}~ууфччч╦╩╦╔╔╔╩╔╔╦╦╦╠╠═уффКККПППАЕЕЕxxxkkkzzzwwwyyyabaВВВАААХХХюююхххццц▌▌▌ООО╧╧╧───╨╨╨ЗЗЗэээ┌┌┌███шшшссс╥╥╥ыыыььь╫╫╫╟╚╟▒▒▒┤┤┤╡╡╡╢╢╢rЬkПtRNSххххххххх╕╣╣╣╣╣b;МцbKGDИH pHYs  ЪЬtIME▀МUR■IDAT8╦c` 021232▓3АhF& А+`ЧР@(`$дАIR@R@IЕ(PЕд$+BБ8Р+ %-##+Є@BBмтТтТТ bКbPа$& 4 бАMBR\\RYQEE ╘╘БюcCШ !.!.о!ж Z┌@wИЙ▒#L╨║EБ(╚"6$:( DQшъшъш +√YБЮ╛БбСФ▒ЙйЩЩЩ╣ЩЕеХ╡Н-Тv;)I{G'gGGW7wOO/Io>╛~■■БA┴!!бaaсСQ╤H╛`ПЙНєЛПOHLJNIIMK╧╚╠М╬╩FVРУЫЧ_PPXT\RZZV^QYUU]c!Вма╢о╛бб▒▒йй╣╣е╡нн╜╜г│ YA76┬pQ=╜}¤¤&┬└дIУзLfGRр6m·М3gA┴╠┘│ч╠Э'Дм`■ВЕЛ/Y ╦ЦпX╕rХ ТлWпY╗n¤ЫН``│iєЪ-л┼┘СlYe╡vы60╪║▌JGB\@╔Д-л╖l┘▒S╪ Д╜wщHp")╪▓{Лў18╪╛GGBRaчю-╗wя▌╖ 8xЁр■ ║: ╢lY╜:к╗;''&╞╬NU╟о▌╗0TИЛ│пАУЛЫЗ└4/ЁёєsХє}А╔▒▌а]%tEXtdate:create2015-08-31T15:00:15+02:00q╩H%tEXtdate:modify2015-08-31T15:00:15+02:00ErЇIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/filetypes/tar.png0000644000175100001770000000305114654363416021522 0ustar00runnerdockerЙPNG  IHDR szzЇgAMA▒О|√QУ cHRMz%АГ∙ Ащu0ъ`:ШoТ_┼FbKGD   а╜зУ pHYs  ЪЬ+IDATX├┼Ч[П┼╟з║{.;;{ёТ╡Э8ё p.FО+Hб╞ЖЭGъ╧)tЫfя49▓Ўэ╒Я▒╕■sZ╜5L╓F5тэ)ФШ И╣Т▀GБшЎHхЧ°Г/ИЎ1ъ*\e┘Vcд█Zбl,┬┐X╗╟G╖ ╩╡ЧЦ╕ё╓яX┐°LЦУ|CЇ%;█ %──ъRЫФжrзц■ФP╞╔╔!╤]НШМ,oёЄ/^хзW_fН╕цяЪс└B█╨8ек ЭтМ╗ %TQ ▀НBК─шWНqШШbМ─ШBgгг╣їЙ╧К╟ Мўг╚бЪ╥▒#Zе]3║Ад╤·!F┼З─b7Яy╜Ый@╗╙┴d ЧэHОжWc 1%RМCа╤ў▌юp╦О}`.DД┐▀∙ьЭФТLЮ зЪJgигїЙєх ∙╛пч zCjUт╚Yw%tEXtdate:create2015-08-31T15:00:15+02:00q╩H%tEXtdate:modify2015-08-31T15:00:15+02:00ErЇIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/filetypes/tgz.png0000644000175100001770000000305114654363416021540 0ustar00runnerdockerЙPNG  IHDR szzЇgAMA▒О|√QУ cHRMz%АГ∙ Ащu0ъ`:ШoТ_┼FbKGD   а╜зУ pHYs  ЪЬ+IDATX├┼Ч[П┼╟з║{.;;{ёТ╡Э8ё p.FО+Hб╞ЖЭGъ╧)tЫfя49▓Ўэ╒Я▒╕■sZ╜5L╓F5тэ)ФШ И╣Т▀GБшЎHхЧ°Г/ИЎ1ъ*\e┘Vcд█Zбl,┬┐X╗╟G╖ ╩╡ЧЦ╕ё╓яX┐°LЦУ|CЇ%;█ %──ъRЫФжrзц■ФP╞╔╔!╤]НШМ,oёЄ/^хзW_fН╕цяЪс└B█╨8ек ЭтМ╗ %TQ ▀НBК─шWНqШШbМ─ШBgгг╣їЙ╧К╟ Мўг╚бЪ╥▒#Zе]3║Ад╤·!F┼З─b7Яy╜Ый@╗╙┴d ЧэHОжWc 1%RМCа╤ў▌юp╦О}`.DД┐▀∙ьЭФТLЮ зЪJgигїЙєх ∙╛пч zCjUт╚Yw%tEXtdate:create2015-08-31T15:00:15+02:00q╩H%tEXtdate:modify2015-08-31T15:00:15+02:00ErЇIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/filetypes/tif.png0000644000175100001770000000275014654363416021523 0ustar00runnerdockerЙPNG  IHDR szzЇgAMA▒О|√QУ cHRMz%АГ∙ Ащu0ъ`:ШoТ_┼FbKGD   а╜зУ pHYs  ЪЬъIDATX├┼Ч]hU╟w>vf│Ы▌═Wє╤╪жН mkСJ+*┌j}HДJйQQPФ·&тЫP╤ё┴┐Ё╔К К6BJб╨/RкжMmHl║IЪ6Ыd7ЫЭ▌ЭЩ{}()Ы═юf╒R/,wч╠сЬ ¤ЯsцЬ+ФR№Я╦(ЇїїM!PJ!еD╙4КБ? !VBмРўЎЎон@┴Ю={┌Ц@,(▄K╔Ц@■дФH)QJбi¤¤¤У┼■┤b┴Э╔ еи╝]лФmгФт ет╝$+ ╤╥^Тт№)╟lI Х ┴Ф√_╕/╤4э&а№$Ж7№╧CP╩I%@┼zBР№┘я┴h[Ц╨╙╡Ъ├J 2cВy║╒T6■Х@д╞╛Fh`╖ьпШ+мFy5Ldч┘УXн/мZUF╣KF │\║Лxй?ё╙уаP∙4┬sA╫V▌nE6У┐■ Z┤гf]╔Cн а°Дnц:▐Ї▐№0JyhЦНЯBООу╬NСЩЫ&╕uv╫&jBS=@>g/лМк(VN^9Ж?}шN>╦┬╘ё_Oр$│шAA[{Ки=Л╨▓,▄a╬╟▄№9─·Чйoj¤w №~№ 6З9Ъ^фдpIxiЎ ЫЭvлc=нzpй]┼h║Ыl(┬СSуr )iЄ`Ї"[у`ю:Д╨Мъ┐Д з├║╚LNёQ|Д┴─8°y█6GяyФН║Aа)Bин╧s▌│─п ёхIН │!д?Ж;°|ў з~■ШЮ8X=n>╧т╒Є═╙uy7▄╚╖IЗ┤)шМEhё<2╛ПfXdriьН╚;IъОЁ▐Н╬╓`Ъ i7bxIj▌╙МОьо└ш╚%ъ RIЙЫsщ ╓sи╗ хчHх╥°JC╙ ▄\гхqjZb╝  ╛3ГЩўxeз└╢ ц'0Ё▒ УЙ─4`U`61M╠Ia═k=lsФЕДO*█Дu╕∙<╥Kqc·:nzЪ·ЩOIMН╧┤╤▒¤%._╗L█№OXM'Уq ╖╫С\pклВpmєcТ mУ─╘E▓Й$С<ф№Nо-lBH▌╘Щ╕Ъ└∙ ╡бБЦж┴5kшъZ╧|4KrHG](╬\ єLo7g╬ЮнОБM]▌ЇЭИ╤~mЦ▒щ ║┬U6Ом╙A34rОGccШPдЖАe22щР╬^ftфMЖЗT:Y╧┬ v╥єт[─ъък╧├4i▀▓ЧKГяSы&уxШБ@С[╠бiВhCДАC│ы╪¤▄;DгQдФф▓9дТDj#kj╨u¤╗UЧсЎПpxlюНohК╔KЕчБЧє╤ 2й,о)И╒h47╖▄r╫.sVn└БUЪ╤▐пbo~ЫєёЫ├ЕоJБУ╬сII╓Х─№a~;sмbу*╟@IЕF~ьIЮ~¤3╞┤з87b╬СxЮ┬ZC,╦ `М\8]ЎФЕНнк*+еиллg єoр{пqixИ┐FЖШO─qfgP╥╟7░лg▀▓юYЙ┘ККТ┬vlШ&ўn╛Я√╢l[ж[ят;EеQ ╢ $хЎ╒юwЇbRu▄╔U2~СRКrЩ[xвтўеЮ raт ╛Ю  ШnфЬ&╖%tEXtdate:create2015-08-31T15:00:15+02:00q╩H%tEXtdate:modify2015-08-31T15:00:15+02:00ErЇIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/filetypes/txt.png0000644000175100001770000000341114654363416021553 0ustar00runnerdockerЙPNG  IHDR szzЇgAMA▒О|√QУ cHRMz%АГ∙ Ащu0ъ`:ШoТ_┼FbKGD   а╜зУ pHYs  ЪЬ IDATX├┼Ч█oG┼5}┐LO╧═ЎШё╪c BМвШ,┘Eyў═ -ФЧК▓╩З╝ь ┬p<А▒g`|Щ╛NwхaЁмёp┤JJ*╡къыъ╙чЬп.BJ╔oY╘г7n▄x*Д@JIЮч О=┌BЫ\qмeeх▄[!XYYЩ№■eЮчH)╣}√Ў│гcЕг┐╢$'2Р$ 2P╪эvY]]х▄фщ bп╧G ▓║·╦╦_ТeC╣·¤>╢mEЪжСч9╢mПц~'А├,f├╨t╝вПчЙ ╦вф9t:/┘xЎМ▒jЕЗўя3ё"/67)∙Ю?kє╟ы╫╧╞АФ)%iЪвi┌ы╚є )2dYA%G`XE╦┴ї\\┐Дч╪ьy╢mb║ю[e=└юю._є жбpщ├KььэтЎ\vГq?е▀яє▓╙awЯW╜M,╙`{sЫFcТ^╖ЗйYЇz/П═}Мю├їц═ЫOГ РQ╔0 G5К"EСМуX&I"У$СiЪ╩4Mх`0РYЦ╔,╦dЮч2╧s)еФyЮ╦,╦d2yы╓нзG┐w*A░Ўh ╜0Фа╤<╟■Hн^'I┬(`nfО№ыЯ\╕8╧Л╬ХjХ'ПЮ0√┴,[Э-ке2O█,,\>цйwЪp0░╙{ЕV0Ёы5╢╗/╨ Га░┐0=▒┘}╞╣▌i╢_t14ЭчЫЫМПНєк█#ф─at6ФBбАa;Ш┌╨║бр;L╦оpбP)Пб█ ┼вНб╗╘<╙0plЛєsч▒ У(К╬@JЙeY|ryё у|Ў√еQ√р∙Зk╫B0Vл#Да19ЖВЙFcєЛ▓а▀я│zяЖк0?7╟ъўhМM&!┴▐Ч?Yфю╖чу+W╪╪Xglв┴Г▀ч╥┬Ю╖7Ычщє6╫Ц╛8;B|╫A╫M ╙жRкрW=мH╟2t|╧├/√╕~СЄЮПя√°х"^╣F╨ПЁК{;▐ИБў^ E┴-∙ип╨╡nйДэXhкКкъPTкU4Mр╣жк3^нak*~йДaёл)o+з2а( cї:К2PPuZSM4MC:├V нV C1й╘╟QUХЙцЪn╝жбй╡│gБФТ °■√╗─0фг┼ |√╖╗LLOУЕ!╜Ч=Цо-qчп_є∙╒OY▄f▓╒dї╗я╕Єщ6п3╤hЄ°зG№yy∙l`ШЖЮу!Мс:аi6еJХКя3╨tКЦG╒/aк6║й!Дд┘ЪfbвБжи8оЛк¤o╟/▐Жэв;┌М4Mзь║шЪЙi R$B p<Ыr╜B╣^`ж5@╤uB0>>N#f▀[В<╧ЙвР$NH├Ш¤0жяж)A╪gРAD─щА╬ЎеbС╟э'╠6g╪▄┌ж\ўy╡╒aj·wgЧ ╦2v║{(ц0$╬S║▌m█ cvwwHвД▐╦Эн[·]╫GЖ┴╒/оbЪ&еbM╙и·UL╙д\.гк*╡ZэЧ-Dq│Ўp UЖЬkN▓╢╢F╡^cч─ё>нЩY<°││ч┘┌┌жZн╥^_g║╒в│╙г\*▒▌┘f~n■l■0N#RЖ B╨ПCJaJ<И ГANфINGHЩ╤2E!Жж╥я'Ю1OpА2╧s╥t∙Ёе(МИгШ0╟aЗ(NH· )2╦И┬И(┘' i─ЇЇ││ч ├ЁTЯЭ╩Аm█,--╜ь╦ыzc;B░№Чe┬0dёуqД┤Z-Д4зЪ!FЧТSН~╥╟╫г>№<ижiЮs4ЎЭ~эrвю▄╣єUЮчтЁ═ш4йООЯ╘>t2:FБ°нпч?[ш{Пъ╗╥%tEXtdate:create2015-08-31T15:00:15+02:00q╩H%tEXtdate:modify2015-08-31T15:00:15+02:00ErЇIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/filetypes/xls.png0000644000175100001770000000232514654363416021545 0ustar00runnerdockerЙPNG  IHDR DдК╞gAMA▒О|√QУ cHRMz%АГ∙ Ащu0ъ`:ШoТ_┼FЄPLTE   ▓▓▓│││░░░▒▒▒┤┤┤╡╡╡╢╢╢╖╖╖╕╕╕лллйййиииккклллммм∙∙∙···√√√№№№¤¤¤■■■   ўўўЎЎЎ°°°┬┬┬▐▐▐╔╔╔╟╟╟└└└╠╠╠╖╖╖┴┴┴єєєЇЇЇїїї╜ппгГГ╬ЛЛ╫ЬЬд||й##╔::╫QQ╒ee┼\[╡░кЄЄЄйmm╙ ╥╬''╥@@┌U\гuHc║7Б╛]╚с║ёёё└ ╫╘╫##┘;;▌LTИЯA`╟3[╗']╝*pЮXу▄▌▄с$&╚G@e╢7N╢(L▒"HпX┐*ЦбСЁЁЁ┼ёыщ╓).ЭC*E▒0=й2б'Ы.ж\zSяяянш °╓П╩v1[г0)П УНРTqS╣╟╙Щь{ щ2┌╔2РЭ"+y v yHaG╣с┌ўА  █ ╚+ ╛3Їп&╠Р`habmbюююxNNєєM ╝ ▒  ╡0 ▓; г1ЎК%иW < tQQЪ+╟jцЙ)ЄЫAЄЬL▄А6жI]4%пппэээууу╬╬╬wufO?nRб╞ЖЭGъ╧)tЫfя49▓Ўэ╒Я▒╕■sZ╜5L╓F5тэ)ФШ И╣Т▀GБшЎHхЧ°Г/ИЎ1ъ*\e┘Vcд█Zбl,┬┐X╗╟G╖ ╩╡ЧЦ╕ё╓яX┐°LЦУ|CЇ%;█ %──ъRЫФжrзц■ФP╞╔╔!╤]НШМ,oёЄ/^хзW_fН╕цяЪс└B█╨8ек ЭтМ╗ %TQ ▀НBК─шWНqШШbМ─ШBgгг╣їЙ╧К╟ Мўг╚бЪ╥▒#Zе]3║Ад╤·!F┼З─b7Яy╜Ый@╗╙┴d ЧэHОжWc 1%RМCа╤ў▌юp╦О}`.DД┐▀∙ьЭФТLЮ зЪJgигїЙєх ∙╛пч zCjUт╚Yw%tEXtdate:create2015-08-31T15:00:15+02:00q╩H%tEXtdate:modify2015-08-31T15:00:15+02:00ErЇIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/guidata-banner.svg0000644000175100001770000004037614654363416021637 0ustar00runnerdocker image/svg+xml guidata 01 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/guidata-vertical.svg0000644000175100001770000004034214654363416022174 0ustar00runnerdocker image/svg+xml guidata 01 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/guidata.svg0000644000175100001770000003713014654363416020366 0ustar00runnerdocker image/svg+xml 01 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/hist.png0000644000175100001770000000021414654363416017675 0ustar00runnerdockerЙPNG  IHDRэ▌тRPLTE jФ  ▄&     9V<№tRNS@ц╪f%IDATxЬc```dА, %\ cA%LЖ VЖ *#Ф!'└D б Щ▌SIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/max.png0000644000175100001770000000054414654363416017521 0ustar00runnerdockerЙPNG  IHDRbЭЄgAMA▒П №a cHRMz&АД·Ашu0ъ`:ШpЬ║Q< PLTE PаY   ╢┤╩ЖtRNSАЫ+NbKGD LЄtIME▀МUR■#IDAT╫c`А╞╨P┴└╘└%╕ н ╚b╪:цc╡║iЙ%tEXtdate:create2015-08-31T15:00:15+02:00q╩H%tEXtdate:modify2015-08-31T15:00:15+02:00ErЇtEXtSoftwarePaint.NET v3.30@ДGIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/min.png0000644000175100001770000000053714654363416017521 0ustar00runnerdockerЙPNG  IHDRbЭЄgAMA▒П №a cHRMz&АД·Ашu0ъ`:ШpЬ║Q< PLTE PаY   ╢┤╩ЖtRNSАЫ+NbKGD LЄtIME▀МUR■IDAT╫c`@ж|Д╓ ┴╡YМ14╘B└ ·c9 ╘з%tEXtdate:create2015-08-31T15:00:15+02:00q╩H%tEXtdate:modify2015-08-31T15:00:15+02:00ErЇtEXtSoftwarePaint.NET v3.30@ДGIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/none.png0000644000175100001770000000051314654363416017667 0ustar00runnerdockerЙPNG  IHDR7И┬╠gAMA▒П №a cHRMz&АД·Ашu0ъ`:ШpЬ║Q<tRNSvУ═8bKGD▌Кд pHYs├├ ╒;ttIME▀МUR■ IDAT╫c` 0╟кЕО%tEXtdate:create2015-08-31T15:00:15+02:00q╩H%tEXtdate:modify2015-08-31T15:00:15+02:00ErЇtEXtSoftwarePaint.NET v3.30@ДGIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/not_found.png0000644000175100001770000000102314654363416020720 0ustar00runnerdockerЙPNG  IHDR(-SgAMA▒П №a cHRMz&АД·Ашu0ъ`:ШpЬ║Q<QPLTE■j■i _■j■i■i■i o■i g■j■j j■j¤g■i¤i i j¤i■i¤i■i■i j   ╨юAtRNSH╖пh╫╧я ▀з0╟╤╝рЁ└╪ёхЯЗznд┐bKGDugф2 pHYs──[СЭ tIME▀МUR■aIDAT╙}ОIА [ED▄╖∙ GИ╞╪╖кЇбPB╔л:▓`Н╞<жemсLч ╞¤qЮ▐$ьM?$|ЩСyJ43/u┬В╫э╒c?{Вб¤┐2яQ8▓#N[╕О,:P%tEXtdate:create2015-08-31T15:00:15+02:00q╩H%tEXtdate:modify2015-08-31T15:00:15+02:00ErЇtEXtSoftwarePaint.NET v3.30@ДGIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/python.png0000644000175100001770000000156714654363416020263 0ustar00runnerdockerЙPNG  IHDR(-SgAMA▒П №a cHRMz&АД·Ашu0ъ`:ШpЬ║Q<nPLTE      ■■■···┐┐┐   ыыы   √√√···   №№№¤¤¤ттт▀▀▀ЁЁЁ№№№тттZZZ¤¤¤рррЁЁЁ[[[   г└╪I}и@pЧMsУи╖─@yж╙▀шAkОDfВIf?rЫAlРCgЕEc}Ec|▒╛╔в┬▄?|н>tа@nФBiЙEd~Ge} ьa ц\ ямGВ▓=uв@oЦBjЛDeАFd}ЙЬм ¤э ч[ ▀O ┘P=vд?qЩBkНtОгъюЁ эЕ рR ╪G ╧ ╠@е║╠FjИEd} ы` уV █K ╥@ ╦9 фЭ ў╛ эa чZ ▐O ╓D эe ш\ рQ Ў╒ ╨? ї│ у] ┘H ╙I цаDмаA'tRNS╕ўї╣%╢─ ї°!)╕*Ў∙╣╧#┬°═P; (╕#├P┬┼╟■bKGD -▐tIME▀МUR■╙IDAT╙c`F&ufV8`╙╨╘╥╓aчА pъъщrq├╘НМML═╘y`·Б└▄╠L]ЭЩhЫЕеХ╡НнЩЩЭ║╜Г#╨N'gW733wO/oа9ъ╛~■Б@MЮA┴!бъ| №aсСQ╤11▒qё Й< ВI╔)ъ@~jZzFfЦР0ГИиШ║zLL6╚*.q II)nїШЬ▄╝|uiY9ИS° ЛКKфс.U(-+пиRД ()ЛїлH"╝+й╩═н╤ЫЭ%╘∙то%tEXtdate:create2015-08-31T15:00:15+02:00q╩H%tEXtdate:modify2015-08-31T15:00:15+02:00ErЇIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/quickview.png0000644000175100001770000000157014654363416020743 0ustar00runnerdockerЙPNG  IHDR(-SgAMA▒О|√QУ cHRMz%АГ∙ Ащu0ъ`:ШoТ_┼FYPLTE   ffffffffffffffffffffffffffffffUUUUUUffffffUUUQQQMMMUUUUUUPPPUUUfffUUUJJJIIIzzzPPPUUUiiiNNNХХХ├├├pppяяяююю╞╞╞fffAAA555111;;;KKK&,3^yЦз╟чs╠]Э9RMMMА▒т║┘√   YЬ▀BЭ°$КЁ1t║FFFыыыЪ├ЁфЁ¤┐╦╪SoЛEб¤2Оэ╬█ш║║║°°°Ц├яOд¤Xk???333c░¤6Тё─▐ўтттэээ▌ъў;ФЄdС╛FOX_п■7ТЄщяїююю▄▄▄CCCьььЖ║ЁYз√iзфm╡■?Ю■bзяёёёццц╝╝╝===───Ц─ЇEЩЄ8УЄiмЄыё°щщщууу▌▌▌kkkўўўїїїъъъммм┌╖Їж&tRNSo/_я┐OЯ?╧?/▀▀_O┐пя╧╧пoЯяЯ?oo└┴╛еbKGDИH pHYs  ЪЬtIME▀МUR■╒IDAT╙c````dbffa@V65 `gДё9Аffv56ЯУН ,╟╔╠╞ рR▄<<▄@гx┴"| №ъЪZВB Ьь`гЕ╡utїЇ 5DD9┼8─%ММML═╠-,5$E9ЩxммmlэьЭЬ╡%EY8x\\▌▄=<╜╝}|¤А"| вR■БA^┴!бaсС╥@CEеввcbутгТТSd@Ў╚жжеgdfЕgчфц╔Б'.пР_^Qд╚ ўбТ▓К*╖(T)Ы Пlў%tEXtdate:create2015-08-31T15:00:15+02:00q╩H%tEXtdate:modify2015-08-31T15:00:15+02:00ErЇIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/save_all.png0000644000175100001770000000307714654363416020526 0ustar00runnerdockerЙPNG  IHDRрw=°gAMA▒П №a cHRMz&АД·Ашu0ъ`:ШpЬ║Q<bKGD   а╜зУVIDATH╟ЭФ╦П\G╞u╒╧ЩщxьЩЙ▒ c,ВLВРHШV┼%СМ $мX▒0╔ +6(A▒@"F,°0!q╚&рмI4'v^ШqOO?юэ{лъTЛю▒Г"9И#]╒U╜╛п╬∙╬з}ф╢╙{ў°спЬ8╞7N▐╦╒л F╝чfС$ ╗vїxyуUNўaV╫Vxыїю╧│№Mk,▌▐я[эРї√¤Ї╓}Зh╢ЪмннЄ┐╞▐╜{┘▐в╡цй▀ЭЛ;є╟┐·Mж╙╔ЬLкмўВў│u+Б╩8*+│╤8ж╡¤пп╢@U╒И8Dаxт╔з1╞р} ЛБhМ┘┘Д╡В╓ЭБЛ0xя!▐`оФ"█╡╚ц╓АKп╜╞╡~`░ёП ■№№ ╘UНJ"IТРб@DpsАFSєт▀_цЧ┐·5GП~Ъ╧}Ў.╩в"C)E┬- ]~pц4╣╓┤tЎ°Х+W6Г┴в╡╢╤jыхЖ╓p╦Є╩wО▄q4■шьу╤╟CМё▒ЯоG ~я╠у Я ┬ёx╧ЙS1o,|"#D╬ ╞Cк@чЪ=╖dй╫╗ЮЦЭ╛#Q(ЙJPjЎ0Ш║F┌m┤╬л,"яV $╩в─√ЭbZь╗▐ *IXш4йjЛsВИ#MSЯ#▐ Y▐`й1;╨]\$QрeжД┌ Nn▐J┌░╬В├{O ТEеh╡Z██[ёаfLпkИ1BМа┼др╥еWё"Д9СМq╬""ИwE (┌ ]╬?qОє┐=GUU8чHєЬ"│┌ФхФI1в┘╚!╤xёL&%г╤Иёx┬h\0Щ{Р╡ЖH$ДАO0О╪▄▄dyy7╜▐╥ ^cj&УТкЪBиЩN ък┬CUUX[#aц?╬ZTТаTBLP┘О╘╘\╒J)r▌ нюnЬВu╘ХбШЦxa:нШ%UUcdZ╒cС┌P╫5"bDМI3АVл┼ю={nX│нЯr╓R3│√уЕg9tЁЬ|рЮ┘с \╕pA)E╖█с╩х7АКё░Ъ┘vцЫ└╥╥╥\╗╫уН█>z°оc╟П- [╥[ьuЎммuўэSы╝Bl╞sцюЮ$Й>|╪▀~√/e]┘ ╩ўЮ°S╨n╖▀х-╡1╧ї√}}Ї╟;Sm`XШ+N╧<`АШC└мппє√&Е* KS*%tEXtdate:create2015-08-31T15:00:15+02:00q╩H%tEXtdate:modify2015-08-31T15:00:15+02:00ErЇIENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/selection.png0000644000175100001770000000135014654363416020715 0ustar00runnerdockerЙPNG  IHDR(-SgAMA▒П №a cHRMz&АД·Ашu0ъ`:ШpЬ║Q< PLTE/└┴╞ЬЭе╞╟╦СТЫ╞╟╦НПЧ╞╟╦ФЦЮ╞╟╦ЮЯз╛┐─egsY[hTVcLN\CETПРЩgitz|ЖKM[░▒╖02BsuА╞╟╦МОЦlnyCET-╗╝┴╞╟╦Z\i24E╩╦╧┤╡║СУЫceq─┼╩ФЦЮ╧╧╙ююЁЫЭд■■■ыьэЩЪв   ёёЄТФЬЁЁёМНЦ√√√°°°ъъыЇЇЇЁЁЁ▀▀рПРШ╬╬╥ьььччч╤╤╙ОПЧ╠╠╨ыыь▄▄▐ъъъ|}З╔╔═щщъY[g┐└├ГДН┐┐─МОЦРСЩ╨╨╤abnxzД╝╝┐РСШдек──╞fhsqs~ў╢╓#$tRNSб/э;▄F╫Dр5ї0y■ЄЄrЄц╔ўJбёYk╔3QH"╢╔Ф╔Иу╛abKGD.T╙ЗtIME▀МUR■ТIDAT╙c````Tab@Мк╠,иjъмl(ЪZь(┌:║Ь\╚zz·▄<(┌ЖF▄\H┌╞&жFЬ3МM╠╠-,┘бv1ZБ°╓6╢v╝P{G'g>~AиАЛлЭЫ╗З┬O/aoQ1ША╕пДдФЯА4L@FVОA^!0H┼ЗJ┴!╩ Вуhг├(%tEXtdate:create2015-08-31T15:00:15+02:00q╩H%tEXtdate:modify2015-08-31T15:00:15+02:00ErЇtEXtSoftwarePaint.NET v3.36йчт%IENDоB`В././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/data/icons/settings.png0000644000175100001770000000461514654363416020577 0ustar00runnerdockerЙPNG  IHDR(-S╬iCCPiccHЙхЦ{LSW╟ўЎ TZин ┴╩"+мгhW!Д╟РБ(рHg_BЕ┬═mAРMeAT|└@6ФаиdsAp╚p╛ШIbЖ члёЕ(:H@┤;╖]фуТ¤э799Яє╗ч№╬=ч▐я=А▌к"И, ┘&2!2LЬТ║V╠&░A╬рм╥Й╨°°xл&гъ+╛TоцШх║[э7Ц{Ми╖?>хл}√8л╕$Ъ#д█8Аb╡Н?гxгЙ0!NгXУбB91▒ДLJG\Зx&▌╩°~К╒VжUQЬзIGcщ{K│╡·l─g╤╡9ZЭQ└ъGёr Aв>l*┐п┴РГЄ│w"ЎжЎ┬vЫ9%Кг(┐█ll*?еЯ╠╞%а╢аC8{╛└║?╪'?╫╦№н!М▀БцЁ┤X╞jь*^.│Xж∙╦╠C╨ьо╔%є■▌г|Th`"X баД"8├Л├к░\ОWупhmФN2шМГ╠(ц+╓ЎNО╬.┌>╚a7|N▓cпБ ╪9EpKX4oй╦ФkЧ█юё щЭЮЕ^a▐ОЛя|x╬ўЁGХЧ╔JЧT╚(:═┴┬Р5a Я╛И\5;┤╩Рф░ж-UЯцеUuhўдgmИ3╚ Oг ПХ?S8╛∙n╤╒ТЮ╥ЎЄ#╗Ўя)л,м&jїuыыї фбт#▀7w¤8╤╨V▄nю\}ц·Е▄nсеє}AГ▄?GпП ▌╜ ╨~,Ї┘о╔з/2-Ц7╓юо Е`Аoа ж░е╪&ь"юВЫЁ!ZнГоаЯc$2Ю0лY╦┘╗ЫScG┌';DrCчD8&ЁЇ№ з.Б¤\╡Ё╥╝PЧWец~BLxyК╝░E╙> °╔дJ ╩А╦rбB╪LI kНpЛмМЮ╙╕ъf╥офИ╡М┤╛/╛Su▒~Щ<├1l№5я`┴╓/S╖HК&KNЦ╩чя№mw╞>VUcMHэН║═їЮ/7Ц6EЫ Я:┴9╣ф╘Ц╬╤│[║╝╗oЎ6¤^zе°j¤5єн─;·╟¤3їтН╡у└A>|ф┘░~БЗШK├`ПЁ`╝I╦дЩщZ·8гМщ├bэe's╝эvcЎ╖F╕гО└sу'85 \ц6ЙV║░▐ыЯ▀ш^(ОєЁёtЄbys|Dй_вtл[└╕\ж(<╠E╧¤pDj╤sПО╜┤*)ё▐ЪпS?°╝_Yм ╥╠м?пп╚Rц╚Hощ┴╞ЮMG┐┌▒5╗8vЫ╧v·Оk-{╦к╓╒(╛╒M╫Ы■8╘dи∙ёqзЦи╢кЎ╔NЄ,чBkўЖ^▀>╦АyЁпсG7ц╘{зG#Ю>ЫшЩю╡Xl^╡9─ЎMбt┐hЦЯg╝fT█№LЙ╞h,XmXqа6└+└y9Ю Рдь╢░СЕА=фг7*Ёw═UяЪУ▐5ў╪╬4лx·Х*Н8\ХеWУ*Уюї1╠=мh@ си╬Bm5РИLаэ ¤2щЄн^╧! H}zЖIК■tтЁСk╥СqT╢╞O"ЎЧJeT?█╣KЙ╔и[K╤щg╩7Є■nw█Pcn╬PLTEЬЬЬЪЪЬ│┤╣┤╡╣ЩЩЩ╖╣└╣╗└ШШЪ║╜╞ЩЩЫХХХджмезмИИП░▓╖ддз╝╛╞ееиКККагнББВЗГДЙААВUUUСТЧ~~Г||АРСЦБЙВВЖ   ААГhhlаабhhi```БК^^^jlxdfnegoUUUaciabi000\]j]_mEEEacsEEE\^k555 NPYQR\╗╛╟╘┘ш╒╪т┌▐ю┘▌ы╤╙▄╙╪ч┴├═╝┴╙ШЭмШЬлЧЬлЪЯп┬╟┘┴╞╪╛├╓бж╕┐┼█┴╟▌╜├┘Эв┤┬╞╪н░┬ей┐╡║╥еи╣ей║╡║╤гз╜о▓─мн│╣╜╥вз┐н▓╠м▓╠бз┐╣╜╙лм│КНЫвз┬вз┴жл┼ЛНЫПУлгз┴ад└лм╣лн║ае└ди┬РФнФШ▒ФШ╡лп┼ЩЭ╣мп╞м░╞ФЩ╢УЧ▒uxМПУ▒п▒╟░│╚о▒╟ПУ░twЛx}ЬuyХ}Вг|БгtxФy}Ьbdu   ў■┤╨PtRNS┌х хтO°= ┐╕х╝х│уAдиEхаЭ┌хдЬNгVтх╗║╣╕ ццa№_х *WodшяcnE"  '+-,%Kў7bKGD │k=АtIME▀МUR■фIDAT╙c`└ЩШYШa|V6v╬.v6VЯЫБ'0(8$4М╚>~БЁИ╚ишШXA!~>аА░H\|BbRRrJ\кИ0HЙhZzFжШxVvNоР+)ХЧ_P(═└ ST\R* С++пиФW`Pмм(пТiQкойнSVоohlRёU╒Ъ[Z█┌██::╗╘TБъЪ▌=╜}}¤&jiиГФh3шLЪ)" to the regex # and handling the cases in the replace_html_tags function or by adding a new dictionary # entry for specific tags if needed. This could be useful for tags like "
" or # "
" or "". The current does not handle tag attributes. # The regex pattern can only match specified tag but could be made generic using the # following pattern: "<(.+?)>(.*?)" and then using rhe dict.get() method in # replace_html_tags function with an empty pattern. _tags = "|".join(REPLACABLE_HTML_TAGS.keys()) HTML_TAG_PATTERN = re.compile(f"<({_tags})>(.*?)") def replace_html_tags(match: re.Match): """Replace HTML tags with reST directives. Args: match: Match object. Returns: New string with reST directives. """ tag = match.group(1) value = match.group(2) new_string = REPLACABLE_HTML_TAGS[tag].format(value) return new_string def datasetnote_option(arg: str) -> tuple[bool, int | None]: """Handles the datasetnote option for the datasetnote directive. Args: arg: Argument to parse (set after the directive). Returns: Returns True to signal the option exists and the number of example note_lines to display if set else None. """ if arg is None: return True, None try: return True, int(arg) except ValueError: return True, None def document_choice_item(item: gds.ChoiceItem) -> str: """Additional documentation for ChoiceItem containing the available choices. Args: item: ChoiceItem to document. Returns: Additional choice documentation. """ available_choices = item.get_prop("data", "choices") str_choices = ", ".join(object_description(key) for key, *_ in available_choices) doc = _("Single choice from: %s.") % str_choices return doc def document_multiple_choice_item(item: gds.MultipleChoiceItem) -> str: """Additional documentation for MultipleChoiceItem containing the available choices. Args: item: ChoiceItem to document. Returns: Additional choice documentation. """ available_choices = item.get_prop("data", "choices") str_choices = ", ".join(object_description(key) for key, *_ in available_choices) doc = _("Multiple choice from: %s.") % str_choices return doc def get_auto_help(item: gds.DataItem, dataset: gds.DataSet) -> str: """Get the auto-generated help for a DataItem. Args: item: DataItem to get the help from. Returns: Auto-generated help for the DataItem. """ auto_help = item.get_auto_help(dataset).rstrip(" .") if not auto_help or auto_help in IGNORED_AUTO_HELP: return "" return capitalize_sentences(auto_help) + "\\." def get_choice_help(item: gds.DataItem) -> str: """Get the choice help for a DataItem if it is a ChoiceItem or MultipleChoiceItem. Args: item: DataItem to get the choice help from. Returns: Choice help for the DataItem. If the DataItem is not a ChoiceItem or MultipleChoiceItem, an empty string is returned. """ choice_help = "" if isinstance(item, gds.MultipleChoiceItem): choice_help = document_multiple_choice_item(item) elif isinstance(item, gds.ChoiceItem): choice_help = document_choice_item(item) return choice_help def escape_docline(line: str) -> str: """Escape a line of documentation. Args: line: Line of documentation. Returns: Escaped line of documentation. """ return line.replace("*", "\\*").replace("\n", " ") def is_label_redundant(label: str, item_name: str) -> bool: """Check if the label is redundant with the item name. Args: label: Label to check. item_name: Item name to check against. Returns: True if the label is redundant with the item name, False otherwise. """ item_name = item_name.lower() return not any(word.strip() not in item_name for word in label.lower().split()) def capitalize_sentences(text: str) -> str: """Capitalize each sentence in a text. Args: text: Text to capitalize. Returns: Capitalized text. """ sentences = re.split("(?<=[.!?]) +", text) capitalized_sentences = [sentence.capitalize() for sentence in sentences] return " ".join(capitalized_sentences) class ItemDoc: """Wrapper class around a DataItem used to document it.""" def __init__(self, dataset: gds.DataSet, item: gds.DataItem) -> None: self.item = item self.item_type = stringify_annotation(type(item)) type_ = item.type if not type_: type_ = Any if isinstance(item, gds.ChoiceItem): types = set(type(key) for key, *_ in item.get_prop("data", "choices")) if types: if len(types) == 1: type_ = types.pop() else: type_ = f"Union[{', '.join(t.__name__ for t in types)}]" else: type_ = Any self.type_ = stringify_annotation(type_) label = item.get_prop("display", "label") if is_label_redundant(label, item.get_name()): label = "" if len(label) > 0 and not label.endswith("."): label += "\\." label = re.sub(HTML_TAG_PATTERN, replace_html_tags, label) self.label = label help_ = item._help or "" help_ = capitalize_sentences(help_) if len(help_) > 0 and not help_.endswith("."): help_ += "\\." auto_help = get_auto_help(item, dataset) if auto_help: help_ += " " + auto_help choice_help = get_choice_help(item) if choice_help: help_ += " " + choice_help self.help_ = help_ self.name = item.get_name() self.default = object_description(item.get_default()) def to_function_parameter(self) -> str: """Convert the item to a parameter docstring (e.g. used for Dataset.create()). Returns: Formated docstring of the item. """ return escape_docline( f"\t{self.name} ({self.type_}): {self.label} " f"{self.help_} " + _("Default: %s.") % self.default ) def to_attribute(self) -> str: """Convert the item to an attribute used in the DataSet docstring. Returns: Formated docstring of the item. """ return escape_docline( f"\t{self.name} ({self.item_type}): {self.label} {self.help_} " + _("Default: %s.") % self.default ) class CreateMethodDocumenter(MethodDocumenter): """Custom MethodDocumented specific to DataSet.create() method.""" objtype = "dataset_create" directivetype = MethodDocumenter.objtype priority = 10 + MethodDocumenter.priority option_spec = dict(MethodDocumenter.option_spec) parent: type[gds.DataSet] @classmethod def can_document_member(cls, member, membername, isattr, parent): """Override the parent method to only document the DataSet.create() method.""" is_create_method = ( membername == "create" and isinstance(member, classmethod) and issubclass(member.__class__, gds.DataSet) ) return is_create_method def format_signature(self, **kwargs: Any) -> str: """Override the parent method to dynamically generate a signature for the parent DataSet.create() method depending on the DataItem of the DatSet.""" instance = self.parent() params = [ Parameter( item.get_name(), Parameter.POSITIONAL_OR_KEYWORD, annotation=ItemDoc(instance, item).type_, ) for item in instance.get_items() ] sig = Signature(parameters=params, return_annotation=self.parent) return stringify_signature(sig, **kwargs) def get_doc(self) -> list[list[str]]: """Override the parent method to dynamically generate a docstring for the create method depending on the DataItem of the DatSet. Returns: list of docstring note_lines. """ self.object.__annotations__["return"] = self.parent docstring_lines = [ _( "Returns a new instance of :py:class:`%s` with the fields set to " "the given values." ) % self.parent.__name__, "", "Args:", ] dataset = self.parent() for item in dataset.get_items(): docstring_lines.append(ItemDoc(dataset, item).to_function_parameter()) docstring_lines.extend( ( "", "Returns:", _("\tNew instance of :py:class:`%s`.") % self.parent.__name__, ) ) docstring = prepare_docstring( "\n".join(docstring_lines), tabsize=self.directive.state.document.settings.tab_width, ) # return [[html.unescape(s) for s in docstring]] return [docstring] class DataSetDocumenter(ClassDocumenter): """ Specialized Documenter subclass for DataSet classes. """ objtype = "dataset" directivetype = ClassDocumenter.objtype priority = 10 + ClassDocumenter.priority option_spec = dict(ClassDocumenter.option_spec) option_spec.update( { "hideattr": bool_option, "hidecreate": bool_option, "showsig": bool_option, "shownote": datasetnote_option, } ) object: Type[gds.DataSet] @classmethod def can_document_member(cls, member, membername, isattr, parent) -> bool: """Override the parent method to only document DataSet classes.""" try: return issubclass(member, gds.DataSet) except TypeError: return False def format_signature(self, **kwargs) -> str: """Override the parent method to dynamically generate a signature for the DataSet class depending on the DataItem of the DatSet and if the 'showsig' option is set. Returns: Formated signature of the DataSet class. """ if self.options.get("showsig", False): return super().format_signature(**kwargs) return "" def get_doc(self) -> list[list[str]]: """Override the parent method to dynamically generate a docstring for the DataSet class depending on the DataItem of the DatSet. By default the dataset attributes are documented but can be hidden using the 'hideattr' option. Returns: Docstring note_lines. """ first_line = getdoc( self.object, self.get_attr, self.config.autodoc_inherit_docstrings, self.object, ) docstring_lines = [ first_line or "", ] if not self.options.get("hideattr", False): docstring_lines.extend(("", "Attributes:")) dataset = self.object() for item in dataset.get_items(): docstring_lines.append(ItemDoc(dataset, item).to_attribute()) return [ prepare_docstring( "\n".join(docstring_lines), tabsize=self.directive.state.document.settings.tab_width, ) ] def add_content(self, more_content: Any | None) -> None: """Override the parent method to hide the create method documentation if the 'hidecreate' option is used. Also add a datasetnote directive if the 'shownote' option is used. Args: more_content: Additional content to show/hide. """ source = self.get_sourcename() hide_create: bool = self.options.get("hidecreate", False) create_method_overwritten = "create" in self.object.__dict__ if hide_create or self.options.inherited_members and not hide_create: if self.options.exclude_members is None: self.options["exclude-members"] = set(("create",)) else: self.options["exclude-members"].add("create") super().add_content(more_content=more_content) if not hide_create and not create_method_overwritten: fullname = self.fullname + ".create" method_documenter = CreateMethodDocumenter( self.directive, fullname, indent=self.content_indent ) method_documenter.generate(more_content=more_content) show_note, example_lines = self.options.get("shownote", (False, None)) if show_note: self.add_line( ".. datasetnote:: " f"{self.object.__module__ + '.' + self.object.__qualname__} " f"{example_lines or ''}", source, ) class DatasetNoteDirective(SphinxDirective): """Custom directive to add a note about how to instanciate and modify a DataSet class.""" required_arguments = 1 # the class name is a required argument optional_arguments = 1 # the number of example note_lines to display is optional final_argument_whitespace = True has_content = True def __init__( self, name: str, arguments: list[str], options: dict[str, Any], content: StringList, lineno: int, content_offset: int, block_text: str, state: RSTState, state_machine: RSTStateMachine, ) -> None: super().__init__( name, arguments, options, content, lineno, content_offset, block_text, state, state_machine, ) self.current_line_offset = self.content_offset def add_lines(self, stringlist: StringList, *lines: str) -> None: source = self.get_source_info()[0] new_offset = self.current_line_offset + 1 i = new_offset for i, line in enumerate(lines, start=new_offset): stringlist.append(line, source=source, offset=i) new_offset = i self.current_line_offset += new_offset def add_code_lines(self, stringlist: StringList, *lines: str) -> None: source = self.get_source_info()[0] new_offset = self.current_line_offset + 1 tab = " " * self.state.document.settings.tab_width stringlist.append("", source=source, offset=new_offset) stringlist.append( ".. code-block:: python", source=source, offset=new_offset + 2 ) stringlist.append("", source=source, offset=new_offset + 3) new_offset += 4 i = new_offset for i, line in enumerate(lines, start=new_offset): stringlist.append(tab + line, source=source, offset=i) new_offset = i + 1 stringlist.append("", source=source, offset=new_offset) self.current_line_offset += new_offset def run(self): """Run the directive. Returns: list of returned nodes. """ class_name = self.arguments[0] example_lines: int | None if len(self.arguments) > self.required_arguments: example_lines = int(self.arguments[self.required_arguments]) else: example_lines = None cls: Type[gds.DataSet] try: # Try to import the class module_name, class_name = class_name.rsplit(".", 1) module = __import__(module_name, fromlist=[class_name]) cls = getattr(module, class_name) except Exception as e: logging.error(f"Failed to import class {class_name}: {e}") instance_str = _("Failed to import class %s") % class_name note_node = nodes.error(instance_str) return [note_node] # Create an instance of the class and get its string representation instance = cls() instance_str = str(instance) items = instance.get_items() formated_args = ", ".join( f"{item.get_name()}={object_description(item.get_value(instance))}" for item in instance.get_items() ) node = nodes.note() # Create a new ViewList instance and add your rst text to it source, lineno = self.get_source_info() self.current_line_offset = self.content_offset note_lines = StringList() self.add_lines( note_lines, _( "To instanciate a new :py:class:`%s` dataset, you can use the " "classmethod :py:meth:`%s.create()` like this:" ) % (cls.__name__, cls.__name__), "", ) self.add_code_lines( note_lines, f"{cls.__name__}.create({formated_args})", ) self.add_lines( note_lines, _( "You can also first instanciate a default :py:class:`%s` " "and then set the fields like this:" ) % cls.__name__, ) example_lines = min(len(items), example_lines) if example_lines else len(items) code_lines = [ f"param = {cls.__name__}()", *( f"param.{items[i].get_name()} = " f"{object_description(items[i].get_value(instance))}" for i in range(example_lines) ), ] if len(items) > example_lines: code_lines.append("...") self.add_code_lines(note_lines, *code_lines) nested_parse_with_titles(self.state, note_lines, node) return [node] def setup(app: Sphinx) -> None: """Setup extension""" app.setup_extension("sphinx.ext.autodoc") app.add_autodocumenter(CreateMethodDocumenter) app.add_autodocumenter(DataSetDocumenter) app.add_directive("datasetnote", DatasetNoteDirective) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/dataset/autodoc_method.py0000644000175100001770000000475414654363416021206 0ustar00runnerdockerfrom inspect import Parameter, Signature from typing import Any from sphinx.application import Sphinx from sphinx.ext.autodoc import MethodDocumenter, stringify_signature from sphinx.util.docstrings import prepare_docstring import guidata.dataset as gds class CreateMethodDocumenter(MethodDocumenter): objtype = "_create_method" directivetype = MethodDocumenter.objtype priority = 10 + MethodDocumenter.priority option_spec = dict(MethodDocumenter.option_spec) parent: type[gds.DataSet] @classmethod def can_document_member(cls, member, membername, isattr, parent): try: return issubclass(parent, gds.DataSet) and membername == "create" except TypeError: return False def format_signature(self, **kwargs: Any) -> str: instance = self.parent() params = [ Parameter( item.get_name(), Parameter.POSITIONAL_OR_KEYWORD, annotation=item.type, default=item._default, ) for item in instance.get_items() ] sig = Signature(parameters=params, return_annotation=self.parent) return stringify_signature(sig, **kwargs) def get_doc(self): self.object.__annotations__["return"] = self.parent docstring_lines = [ f"Returns a new instance of {self.parent.__name__} with the fields set to " "the given values.", "", "Args:", ] instance = self.parent() for item in instance.get_items(): type_ = item.type if not type_: type_ = Any label = item.get_prop("display", "label") if len(label) > 0 and not label.endswith("."): label += "." help_ = item.get_help(instance) if len(help_) > 0 and not help_.endswith("."): help_ += "." docstring_lines.append( f"\t{item.get_name()} ({type_.__name__}): {label} {help_} " f"Default: {item._default}" ) docstring_lines.extend( ("Returns:", "", f"\tNew instance of {self.parent.__name__}") ) return [ prepare_docstring( "\n".join(docstring_lines), tabsize=self.directive.state.document.settings.tab_width, ) ] def setup(app: Sphinx): app.setup_extension("sphinx.ext.autodoc") app.add_autodocumenter(CreateMethodDocumenter) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/dataset/conv.py0000644000175100001770000001516114654363416017147 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """ DataSet class conversion/creation functions =========================================== Update and restore datasets --------------------------- .. autofunction:: guidata.dataset.update_dataset .. autofunction:: guidata.dataset.restore_dataset Create dataset classes ---------------------- .. autofunction:: guidata.dataset.create_dataset_from_func .. autofunction:: guidata.dataset.create_dataset_from_dict """ from __future__ import annotations import inspect from typing import TYPE_CHECKING, Any import guidata.dataset.dataitems as gdi import guidata.dataset.datatypes as gdt if TYPE_CHECKING: import guidata.dataset.datatypes as gdt # ============================================================================== # Updating, restoring datasets # ============================================================================== def update_dataset( dest: gdt.DataSet, source: Any | dict[str, Any], visible_only: bool = False ) -> None: """Update `dest` dataset items from `source` dataset. Args: dest (DataSet): The destination dataset object to update. source (Union[Any, Dict[str, Any]]): The source object or dictionary containing matching attribute names. visible_only (bool): If True, update only visible items. Defaults to False. For each DataSet item, the function will try to get the attribute of the same name from the source. If the attribute exists in the source object or the key exists in the dictionary, it will be set as the corresponding attribute in the destination dataset. Returns: None """ for item in dest._items: key = item._name if hasattr(source, key): try: hide = item.get_prop_value("display", source, "hide", False) except AttributeError: # FIXME: Remove this try...except hide = False if visible_only and hide: continue setattr(dest, key, getattr(source, key)) elif isinstance(source, dict) and key in source: setattr(dest, key, source[key]) def restore_dataset(source: gdt.DataSet, dest: Any | dict[str, Any]) -> None: """Restore `dest` dataset items from `source` dataset. Args: source (DataSet): The source dataset object to restore from. dest (Union[Any, Dict[str, Any]]): The destination object or dictionary. This function is almost the same as `update_dataset` but requires the source to be a DataSet instead of the destination. Symmetrically from `update_dataset`, `dest` may also be a dictionary. Returns: None """ for item in source._items: key = item._name value = getattr(source, key) if hasattr(dest, key): try: setattr(dest, key, value) except AttributeError: # This attribute is a property, skipping this iteration continue elif isinstance(dest, dict): dest[key] = value # ============================================================================== # Generating a dataset class from a function signature # ============================================================================== def get_arg_info(func) -> dict[str, tuple[Any, Any]]: """Returns a dictionary where keys are the function argument names and values are tuples containing (default argument value, argument data type). Note: If the argument has no default value, it will be set to None. If the argument has no data type annotation, it will be set to None. Args: func: The function to get argument info from. Returns: The argument info dictionary. """ signature = inspect.signature(func) arg_info = {} for name, param in signature.parameters.items(): default_value = param.default if param.default != param.empty else None data_type = param.annotation if param.annotation != param.empty else None arg_info[name] = (default_value, data_type) return arg_info def __get_dataitem_from_type(data_type: Any) -> gdi.DataItem: """Returns a DataItem instance from a data type. Args: data_type: The data type to get the DataItem from. Returns: The DataItem. """ if not isinstance(data_type, str): # In case we are not using "from __future__ import annotations" data_type = data_type.__name__ data_type = data_type.split("[")[0].split(".")[-1] typemap = { "int": gdi.IntItem, "float": gdi.FloatItem, "bool": gdi.BoolItem, "str": gdi.StringItem, "dict": gdi.DictItem, "ndarray": gdi.FloatArrayItem, } ditem_klass = typemap.get(data_type) if ditem_klass is None: raise ValueError(f"Unsupported data type: {data_type}") return ditem_klass def create_dataset_from_func(func) -> gdt.DataSet: """Creates a DataSet class from a function signature. Args: func: The function to create the DataSet from. Returns: The DataSet class. Note: Supported data types are: int, float, bool, str, dict, np.ndarray. """ klassname = "".join([s.capitalize() for s in func.__name__.split("_")]) + "DataSet" arg_info = get_arg_info(func) klassattrs = {} for name, (default_value, data_type) in arg_info.items(): if data_type is None: raise ValueError(f"Argument '{name}' has no data type annotation.") ditem = __get_dataitem_from_type(data_type) klassattrs[name] = ditem(name, default=default_value) return type(klassname, (gdt.DataSet,), klassattrs) # ============================================================================== # Generating a dataset class from a dictionary # ============================================================================== def create_dataset_from_dict( dictionary: dict[str, Any], klassname: str | None = None ) -> gdt.DataSet: """Creates a DataSet class from a dictionary. Args: dictionary: The dictionary to create the DataSet class from. klassname: The name of the DataSet class. If None, the name is 'DictDataSet'. Returns: The DataSet class. Note: Supported data types are: int, float, bool, str, dict, np.ndarray. """ klassname = "DictDataSet" if klassname is None else klassname klassattrs = {} for name, value in dictionary.items(): ditem = __get_dataitem_from_type(type(value)) klassattrs[name] = ditem(name, default=value) return type(klassname, (gdt.DataSet,), klassattrs) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/dataset/dataitems.py0000644000175100001770000010372714654363416020163 0ustar00runnerdocker# # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """ Data items ---------- Base class ^^^^^^^^^^ .. autoclass:: guidata.dataset.DataItem Numeric items ^^^^^^^^^^^^^ .. autoclass:: guidata.dataset.FloatItem :members: .. autoclass:: guidata.dataset.IntItem :members: .. autoclass:: guidata.dataset.FloatArrayItem :members: Text items ^^^^^^^^^^ .. autoclass:: guidata.dataset.StringItem :members: .. autoclass:: guidata.dataset.TextItem :members: Date and time items ^^^^^^^^^^^^^^^^^^^ .. autoclass:: guidata.dataset.DateItem :members: .. autoclass:: guidata.dataset.DateTimeItem :members: Color items ^^^^^^^^^^^ .. autoclass:: guidata.dataset.ColorItem :members: File items ^^^^^^^^^^ .. autoclass:: guidata.dataset.FileSaveItem :members: .. autoclass:: guidata.dataset.FileOpenItem :members: .. autoclass:: guidata.dataset.FilesOpenItem :members: .. autoclass:: guidata.dataset.DirectoryItem :members: Choice items ^^^^^^^^^^^^ .. autoclass:: guidata.dataset.BoolItem :members: .. autoclass:: guidata.dataset.ChoiceItem :members: .. autoclass:: guidata.dataset.MultipleChoiceItem :members: .. autoclass:: guidata.dataset.ImageChoiceItem :members: Other items ^^^^^^^^^^^ .. autoclass:: guidata.dataset.ButtonItem :members: .. autoclass:: guidata.dataset.DictItem :members: .. autoclass:: guidata.dataset.FontFamilyItem :members: """ from __future__ import annotations import datetime import os import re from collections.abc import Callable from typing import TYPE_CHECKING, Any, Generic, Iterable, TypeVar import numpy as np from guidata.config import _ from guidata.dataset.datatypes import DataItem, DataSet, ItemProperty if TYPE_CHECKING: from numpy.typing import NDArray from guidata.io import ( HDF5Reader, HDF5Writer, INIReader, INIWriter, JSONReader, JSONWriter, ) _T = TypeVar("_T") class NumericTypeItem(DataItem): """Numeric data item Args: label: item name default: default value (optional) min: minimum value (optional) max: maximum value (optional) nonzero: if True, zero is not a valid value (optional) unit: physical unit (optional) even: if True, even values are valid, if False, odd values are valid if None (default), ignored (optional) slider: if True, shows a slider widget right after the line edit widget (default is False) help: text shown in tooltip (optional) check: if False, value is not checked (optional, default=True) """ type: type[int | float] def __init__( self, label: str, default: float | int | None = None, min: float | int | None = None, max: float | int | None = None, nonzero: bool | None = None, unit: str = "", help: str = "", check: bool = True, ) -> None: super().__init__(label, default=default, help=help, check=check) self.set_prop("data", min=min, max=max, nonzero=nonzero, check_value=check) self.set_prop("display", unit=unit) def get_auto_help(self, instance: DataSet) -> str: """Override DataItem method""" auto_help = {int: _("integer"), float: _("float")}[self.type] _min = self.get_prop_value("data", instance, "min") _max = self.get_prop_value("data", instance, "max") nonzero = self.get_prop_value("data", instance, "nonzero") unit = self.get_prop_value("display", instance, "unit") if _min is not None and _max is not None: auto_help += _(" between ") + str(_min) + _(" and ") + str(_max) elif _min is not None: auto_help += _(" higher than ") + str(_min) elif _max is not None: auto_help += _(" lower than ") + str(_max) if nonzero: auto_help += ", " + _("non zero") if unit: auto_help += ", %s %s" % (_("unit:"), unit) return auto_help def format_string( self, instance: DataSet, value: float | int, fmt: str, func: Callable ) -> str: """Override DataItem method""" text = fmt % (func(value),) # We add directly the unit to 'text' (instead of adding it # to 'fmt') to avoid string formatting error if '%' is in unit unit = self.get_prop_value("display", instance, "unit", "") if unit: text += " " + unit return text def check_value(self, value: float | int) -> bool: """Override DataItem method""" if not self.get_prop("data", "check_value", True): return True if not isinstance(value, self.type): return False if self.get_prop("data", "nonzero") and value == 0: return False _min = self.get_prop("data", "min") if _min is not None and value < _min: return False _max = self.get_prop("data", "max") if _max is not None and value > _max: return False return True def from_string(self, value: str) -> Any | None: """Override DataItem method""" # String may contains numerical operands: if re.match(r"^([\d\(\)\+/\-\*.]|e)+$", value): # pylint: disable=eval-used # pylint: disable=broad-except try: # pylint: disable=not-callable return self.type(eval(value)) except: # noqa pass return None class FloatItem(NumericTypeItem): """Construct a float data item Args: label: item name default: default value (optional) min: minimum value (optional) max: maximum value (optional) nonzero: if True, zero is not a valid value (optional) unit: physical unit (optional) even: if True, even values are valid, if False, odd values are valid if None (default), ignored (optional) slider: if True, shows a slider widget right after the line edit widget (default is False) help: text shown in tooltip (optional) check: if False, value is not checked (optional, default=True) """ type = float def __init__( self, label: str, default: float | None = None, min: float | None = None, max: float | None = None, nonzero: bool | None = None, unit: str = "", step: float = 0.1, slider: bool = False, help: str = "", check: bool = True, ) -> None: super().__init__( label, default=default, min=min, max=max, nonzero=nonzero, unit=unit, help=help, check=check, ) self.set_prop("display", slider=slider) self.set_prop("data", step=step) def get_value_from_reader( self, reader: HDF5Reader | JSONReader | INIReader ) -> float: """Reads value from the reader object, inside the try...except statement defined in the base item `deserialize` method""" return reader.read_float() class IntItem(NumericTypeItem): """Construct an integer data item Args: label: item name default: default value (optional) min: minimum value (optional) max: maximum value (optional) nonzero: if True, zero is not a valid value (optional) unit: physical unit (optional) even: if True, even values are valid, if False, odd values are valid if None (default), ignored (optional) slider: if True, shows a slider widget right after the line edit widget (default is False) help: text shown in tooltip (optional) check: if False, value is not checked (optional, default=True) """ type = int def __init__( self, label: str, default: int | None = None, min: int | None = None, max: int | None = None, nonzero: bool | None = None, unit: str = "", even: bool | None = None, slider: bool = False, help: str = "", check: bool = True, ) -> None: super().__init__( label, default=default, min=min, max=max, nonzero=nonzero, unit=unit, help=help, check=check, ) self.set_prop("data", even=even) self.set_prop("display", slider=slider) def get_auto_help(self, instance: DataSet) -> str: """Override DataItem method""" auto_help = super().get_auto_help(instance) even = self.get_prop_value("data", instance, "even") if even is not None: if even: auto_help += ", " + _("even") else: auto_help += ", " + _("odd") return auto_help def check_value(self, value: Any) -> bool: """Override DataItem method""" if not self.get_prop("data", "check_value", True): return True valid = super().check_value(value) if not valid: return False even = self.get_prop("data", "even") if even is not None: is_even = value // 2 == value / 2.0 if (even and not is_even) or (not even and is_even): return False return True def get_value_from_reader(self, reader: HDF5Reader | JSONReader | INIReader) -> Any: """Reads value from the reader object, inside the try...except statement defined in the base item `deserialize` method""" return reader.read_int() class StringItem(DataItem): """Construct a string data item Args: label: item name default: default value (optional) notempty: if True, empty string is not a valid value (optional) wordwrap: toggle word wrapping (optional) password: if True, text is hidden (optional) regexp: regular expression for checking value (optional) help: text shown in tooltip (optional) check: if False, value is not checked (ineffective for strings) """ type: Any = str def __init__( self, label: str, default: str | None = None, notempty: bool | None = None, wordwrap: bool = False, password: bool = False, regexp: str | None = None, help: str = "", check: bool = True, ) -> None: super().__init__(label, default=default, help=help, check=check) self.set_prop("data", notempty=notempty, regexp=regexp) self.set_prop("display", wordwrap=wordwrap, password=password) def get_auto_help(self, instance: DataSet) -> str: """Override DataItem method""" auto_help = _("string") notempty = self.get_prop_value("data", instance, "notempty") if notempty: auto_help += ", " + _("not empty") regexp = self.get_prop_value("data", instance, "regexp") if regexp: auto_help += ", " + _("regexp:") + " " + regexp return auto_help def check_value(self, value: Any) -> bool: """Override DataItem method""" notempty = self.get_prop("data", "notempty") if notempty and not value: return False regexp = self.get_prop("data", "regexp") if regexp is not None: return bool(re.match(regexp, "" if value is None else value)) return True def from_string(self, value: str) -> str: """Override DataItem method""" return value def get_string_value(self, instance: DataSet) -> str: """Override DataItem method""" strval = super().get_string_value(instance) if self.get_prop("display", "password"): return "*" * len(strval) return strval def get_value_from_reader(self, reader: HDF5Reader | JSONReader | INIReader) -> Any: """Reads value from the reader object, inside the try...except statement defined in the base item `deserialize` method""" return reader.read_unicode() class TextItem(StringItem): """Construct a text data item (multiline string) Args: label: item name default: default value (optional) notempty: if True, empty string is not a valid value (optional) wordwrap: toggle word wrapping (optional) help: text shown in tooltip (optional) """ def __init__( self, label: str, default: str | None = None, notempty: bool | None = None, wordwrap: bool = True, help: str = "", ) -> None: super().__init__( label, default=default, notempty=notempty, wordwrap=wordwrap, help=help, ) class BoolItem(DataItem): """Construct a boolean data item Args: text: form's field name (optional) label: item name default: default value (optional) help: text shown in tooltip (optional) check: if False, value is not checked (optional, default=True) """ type = bool def __init__( self, text: str = "", label: str = "", default: bool | None = None, help: str = "", check: bool = True, ) -> None: super().__init__(label, default=default, help=help, check=check) self.set_prop("display", text=text) def get_value_from_reader( self, reader: HDF5Reader | JSONReader | INIReader ) -> bool: """Reads value from the reader object, inside the try...except statement defined in the base item `deserialize` method""" return reader.read_bool() class DateItem(DataItem): """DataSet data item Args: label: item label default: default value (optional) format: date format (as in :py:func:`datetime.date.strftime`) help: text displayed on data item's tooltip check: check value (default: True) """ type = datetime.date def __init__( self, label: str, default: datetime.date | None = None, format: str | None = None, help: str | None = "", check: bool | None = True, ) -> None: super().__init__(label, default=default, help=help, check=check) self.set_prop("display", format=format) class DateTimeItem(DateItem): """DataSet data item Args: label: item label default: default value (optional) format: date format (as in :py:func:`datetime.date.strftime`) help: text displayed on data item's tooltip check: check value (default: True) """ type = datetime.datetime def __init__( self, label: str, default: datetime.datetime | None = None, format: str | None = None, help: str | None = "", check: bool | None = True, ) -> None: super().__init__(label, default, format, help, check) class ColorItem(StringItem): """Construct a color data item Args: label: item name default: default value (optional). Color can be specified as a string (e.g. "#FF0000" or "red") or as a Qt name (e.g. "red") help: text shown in tooltip (optional) check: if False, value is not checked (optional, default=True) """ def check_value(self, value: str) -> bool: """Override DataItem method""" if not self.get_prop("data", "check_value", True): return True if not isinstance(value, self.type): return False from qtpy import QtGui as QG return QG.QColor(value).isValid() def get_value_from_reader(self, reader: HDF5Reader | JSONReader | INIReader) -> str: """Reads value from the reader object, inside the try...except statement defined in the base item `deserialize` method""" # Using read_str converts `numpy.string_` to `str` -- otherwise, # when passing the string to a QColor Qt object, any numpy.string_ will # be interpreted as no color (black) return reader.read_str() class FileSaveItem(StringItem): """Construct a path data item for a file to be saved Args: label: item name formats: wildcard filter default: default value (optional) basedir: default base directory (optional) regexp: regular expression for checking value (optional) help: text shown in tooltip (optional) check: if False, value is not checked (optional, default=True) """ def __init__( self, label: str, formats: tuple[str, ...] | str = "*", default: list[str] | str | None = None, basedir: str | None = None, all_files_first: bool = False, regexp: str | None = None, help: str = "", check: bool = True, ) -> None: default = os.path.join(*default) if isinstance(default, list) else default super().__init__(label, default=default, regexp=regexp, help=help, check=check) if isinstance(formats, str): formats = [formats] # type:ignore self.set_prop("data", formats=formats) self.set_prop("data", basedir=basedir) self.set_prop("data", all_files_first=all_files_first) self.set_prop("display", func=os.path.basename) def get_auto_help(self, instance: DataSet) -> str: """Override DataItem method""" formats = self.get_prop("data", "formats") return ( _("all file types") if formats == ["*"] else _("supported file types:") + " *.%s" % ", *.".join(formats) ) def check_value(self, value: str) -> bool: """Override DataItem method""" if not self.get_prop("data", "check_value", True): return True if not isinstance(value, self.type): return False return len(value) > 0 def from_string(self, value) -> str: """Override DataItem method""" return self.add_extension(value) def add_extension(self, value) -> str: """Add extension to filename `value`: possible value for data item""" value = str(value) formats = self.get_prop("data", "formats") if ( len(formats) == 1 and formats[0] != "*" and not value.endswith("." + formats[0]) and len(value) > 0 ): return value + "." + formats[0] return value class FileOpenItem(FileSaveItem): """Construct a path data item for a file to be opened Args: label: item name formats: wildcard filter default: default value (optional) basedir: default base directory (optional) help: text shown in tooltip (optional) check: if False, value is not checked (optional, default=True) """ def check_value(self, value: str) -> bool: """Override DataItem method""" if not self.get_prop("data", "check_value", True): return True if not isinstance(value, self.type): return False return os.path.exists(value) and os.path.isfile(value) class FilesOpenItem(FileSaveItem): """Construct a path data item for multiple files to be opened. Args: label: item name formats: wildcard filter default: default value (optional) basedir: default base directory (optional) regexp: regular expression for checking value (optional) help: text shown in tooltip (optional) check: if False, value is not checked (optional, default=True) """ type = list def __init__( self, label: str, formats: str = "*", default: list[str] | str | None = None, basedir: str | None = None, all_files_first: bool = False, regexp: str | None = None, help: str = "", check: bool = True, ) -> None: if isinstance(default, str): default = [default] super().__init__( label, formats=formats, default=default, basedir=basedir, all_files_first=all_files_first, regexp=regexp, help=help, check=check, ) self.set_prop("display", func=self.paths_basename) @staticmethod def paths_basename(paths: str | list[str]): """Return the basename of a path or a list of paths""" return ( [os.path.basename(p) for p in paths] if isinstance(paths, list) else os.path.basename(paths) ) def check_value(self, value: str) -> bool: """Override DataItem method""" if not self.get_prop("data", "check_value", True): return True if value is None: return False allexist = True for path in value: allexist = allexist and os.path.exists(path) and os.path.isfile(path) return allexist def from_string(self, value: Any) -> list[str]: # type:ignore """Override DataItem method""" value = eval(value) if value.endswith("']") or value.endswith('"]') else [value] return [self.add_extension(path) for path in value] def serialize( self, instance: DataSet, writer: HDF5Writer | JSONWriter | INIWriter, ) -> None: """Serialize this item""" value = self.get_value(instance) writer.write_sequence([fname.encode("utf-8") for fname in value]) def get_value_from_reader( self, reader: HDF5Reader | JSONReader | INIReader ) -> list[str]: """Reads value from the reader object, inside the try...except statement defined in the base item `deserialize` method""" return [fname for fname in reader.read_sequence()] class DirectoryItem(StringItem): """Construct a path data item for a directory. Args: label: item name default: default value (optional) help: text shown in tooltip (optional) check: if False, value is not checked (optional, default=True) """ def check_value(self, value: str) -> bool: """Override DataItem method""" if not self.get_prop("data", "check_value", True): return True if not isinstance(value, self.type): return False return os.path.exists(value) and os.path.isdir(value) class FirstChoice: """ Special object that means the default value of a ChoiceItem is the first item. """ pass class ChoiceItem(DataItem, Generic[_T]): """Construct a data item for a list of choices. Args: label: item name choices: string list or (key, label) list function of two arguments (item, value) returning a list of tuples (key, label, image) where image is an icon path, a QIcon instance or a function of one argument (key) returning a QIcon instance default: default value (optional) help: text shown in tooltip (optional) check: if False, value is not checked (optional, default=True) radio: if True, shows radio buttons instead of a combo box (default is False) size: size (optional) of the combo box or button widget (for radio buttons) """ type = Any def __init__( self, label: str, choices: Iterable[_T] | Callable[[Any], Iterable[_T]], default: tuple[()] | type[FirstChoice] | int | _T | None = FirstChoice, help: str = "", check: bool = True, radio: bool = False, size: tuple[int, int] | None = None, ) -> None: _choices_data: Any if isinstance(choices, Callable): _choices_data = ItemProperty(choices) else: _choices_data = [] for idx, choice in enumerate(choices): _choices_data.append(self._normalize_choice(idx, choice)) if default is FirstChoice and not isinstance(choices, Callable): default = _choices_data[0][0] elif default is FirstChoice: default = None super().__init__(label, default=default, help=help, check=check) self.set_prop("data", choices=_choices_data) self.set_prop("display", radio=radio) self.set_prop("display", size=size) def _normalize_choice( self, idx: int, choice_tuple: tuple[Any, ...] ) -> tuple[int, str, None] | tuple[str, str, None]: if isinstance(choice_tuple, tuple): key, value = choice_tuple else: key = idx value = choice_tuple return (key, value, None) def get_string_value(self, instance: DataSet) -> str: """Override DataItem method""" value = self.get_value(instance) choices = self.get_prop_value("data", instance, "choices") # print "ShowChoiceWidget:", choices, value for choice in choices: if choice[0] == value: return str(choice[1]) return DataItem.get_string_value(self, instance) class MultipleChoiceItem(ChoiceItem): """Construct a data item for a list of choices -- multiple choices can be selected Args: label: item name choices: string list or (key, label) list default: default value (optional) help: text shown in tooltip (optional) check: if False, value is not checked (optional, default=True) """ def __init__( self, label: str, choices: list[str], default: tuple[()] = (), help: str = "", check: bool = True, ) -> None: super().__init__(label, choices, default, help, check=check) self.set_prop("display", shape=(1, -1)) def horizontal(self, row_nb: int = 1) -> MultipleChoiceItem: """ Method to arange choice list horizontally on `n` rows Example: nb = MultipleChoiceItem("Number", ['1', '2', '3'] ).horizontal(2) """ self.set_prop("display", shape=(row_nb, -1)) return self def vertical(self, col_nb: int = 1) -> MultipleChoiceItem: """ Method to arange choice list vertically on `n` columns Example: nb = MultipleChoiceItem("Number", ['1', '2', '3'] ).vertical(2) """ self.set_prop("display", shape=(-1, col_nb)) return self def serialize( self, instance: DataSet, writer: HDF5Writer | JSONWriter | INIWriter, ) -> None: """Serialize this item""" value = self.get_value(instance) seq = [] _choices = self.get_prop_value("data", instance, "choices") for key, _label, _img in _choices: seq.append(key in value) writer.write_sequence(seq) def deserialize( self, instance: DataSet, reader: HDF5Reader | JSONReader | INIReader, ) -> None: """Deserialize this item""" try: flags = reader.read_sequence() except KeyError: self.set_default(instance) else: # We could have trouble with objects providing their own choice # function which depend on not yet deserialized values _choices = self.get_prop_value("data", instance, "choices") value = [] for idx, flag in enumerate(flags): if flag: value.append(_choices[idx][0]) self.__set__(instance, value) class ImageChoiceItem(ChoiceItem): """Construct a data item for a list of choices with images Args: label: item name choices: (label, image) list, or (key, label, image) list, or function of two arguments (item, value) returning a list of tuples (key, label, image) where image is an icon path, a QIcon instance or a function of one argument (key) returning a QIcon instance default: default value (optional) help: text shown in tooltip (optional) check: if False, value is not checked (optional, default=True) """ def _normalize_choice( self, idx: int, choice_tuple: tuple[Any, ...] ) -> tuple[Any, Any, Any]: assert isinstance(choice_tuple, tuple) if len(choice_tuple) == 3: key, value, img = choice_tuple else: key = idx value, img = choice_tuple return (key, value, img) class FloatArrayItem(DataItem): """Construct a float array data item Args: label: item name default: default value (optional) help: text shown in tooltip (optional) format: formatting string (example: '%.3f') (optional) transpose: transpose matrix (display only) large: view all float of the array minmax: "all" (default), "columns", "rows" check: if False, value is not checked (optional, default=True) variable_size: if True, allows to add/remove row/columns on all axis """ type = np.ndarray def __init__( self, label: str, default: NDArray | None = None, help: str = "", format: str = "%.3f", transpose: bool = False, minmax: str = "all", check: bool = True, variable_size=False, ) -> None: super().__init__(label, default=default, help=help, check=check) self.set_prop("display", format=format, transpose=transpose, minmax=minmax) self.set_prop("edit", variable_size=variable_size) def format_string( self, instance: DataSet, value: Any, fmt: str, func: Callable ) -> str: """Override DataItem method""" larg = self.get_prop_value("display", instance, "large", False) fmt = self.get_prop_value("display", instance, "format", "%s") unit = self.get_prop_value("display", instance, "unit", "") v = func(value) if larg: text = "= [" for flt in v[:-1]: text += fmt % flt + "; " text += fmt % v[-1] + "]" else: text = "~= " + fmt % v.mean() text += " [" + fmt % v.min() text += " .. " + fmt % v.max() text += "]" text += " %s" % unit return str(text) def serialize( self, instance: DataSet, writer: HDF5Writer | JSONWriter | INIWriter, ) -> None: """Serialize this item""" value = self.get_value(instance) writer.write_array(value) def get_value_from_reader(self, reader: HDF5Reader | JSONReader | INIReader) -> Any: """Reads value from the reader object, inside the try...except statement defined in the base item `deserialize` method""" return reader.read_array() class DictItem(DataItem): """Construct a data item representing a dictionary Args: label: item name default: default value (optional) help: text shown in tooltip (optional) check: if False, value is not checked (optional, default=True) """ type: type[dict[str, Any]] = dict # pylint: disable=redefined-builtin,abstract-method def __init__(self, label, default: dict | None = None, help="", check=True): super().__init__(label, default=default, help=help, check=check) self.set_prop("display", callback=self.__dictedit) self.set_prop("display", icon="dictedit.png") @staticmethod # pylint: disable=unused-argument def __dictedit(instance: DataSet, item: DataItem, value: dict, parent): """Open a dictionary editor""" # pylint: disable=import-outside-toplevel from guidata.qthelpers import exec_dialog from guidata.widgets.collectionseditor import CollectionsEditor editor = CollectionsEditor(parent) value_was_none = value is None if value_was_none: value = {} editor.setup(value, readonly=instance.is_readonly()) if exec_dialog(editor): return editor.get_value() if value_was_none: return None return value def serialize(self, instance, writer): """Serialize this item""" value = self.get_value(instance) writer.write_dict(value) def get_value_from_reader(self, reader): """Reads value from the reader object, inside the try...except statement defined in the base item `deserialize` method""" return reader.read_dict() class ButtonItem(DataItem): """Construct a simple button that calls a method when hit Args: label: item name callback: function with four parameters (dataset, item, value, parent) where dataset (DataSet) is an instance of the parent dataset, item (DataItem) is an instance of ButtonItem (i.e. self), value (unspecified) is the value of ButtonItem (default ButtonItem value or last value returned by the callback) and parent (QObject) is button's parent widget icon: icon show on the button (optional) (str: icon filename as in guidata/guiqwt image search paths) default: default value passed to the callback (optional) help: text shown in button's tooltip (optional) check: if False, value is not checked (optional, default=True) size: size (optional) of the button widget The value of this item is unspecified but is passed to the callback along with the whole dataset. The value is assigned the callback`s return value. """ def __init__( self, label: str, callback: Callable, icon: str | None = None, default: Any | None = None, help: str = "", check: bool = True, size: tuple[int, int] | None = None, ) -> None: super().__init__(label, default=default, help=help, check=check) self.set_prop("display", callback=callback) self.set_prop("display", icon=icon) self.set_prop("display", size=size) def serialize( self, instance: DataSet, writer: HDF5Writer | JSONWriter | INIWriter, ) -> Any: pass def deserialize( self, instance: DataSet, reader: HDF5Reader | JSONReader | INIReader, ) -> Any: pass class FontFamilyItem(StringItem): """Construct a font family name item Args: label: item name default: default value (optional) help: text shown in tooltip (optional) check: if False, value is not checked (optional, default=True) """ pass ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/dataset/datatypes.py0000644000175100001770000013643214654363416020205 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """ Data sets --------- Defining data sets ^^^^^^^^^^^^^^^^^^ .. autoclass:: guidata.dataset.DataSet :members: .. autoclass:: guidata.dataset.DataSetGroup :members: .. autoclass:: guidata.dataset.ActivableDataSet :members: Grouping items ^^^^^^^^^^^^^^ .. autoclass:: guidata.dataset.BeginGroup :members: .. autoclass:: guidata.dataset.EndGroup :members: .. autoclass:: guidata.dataset.BeginTabGroup :members: .. autoclass:: guidata.dataset.EndTabGroup :members: Handling item properties ^^^^^^^^^^^^^^^^^^^^^^^^ .. autoclass:: guidata.dataset.ItemProperty .. autoclass:: guidata.dataset.FormatProp :members: .. autoclass:: guidata.dataset.GetAttrProp :members: .. autoclass:: guidata.dataset.ValueProp :members: .. autoclass:: guidata.dataset.NotProp :members: .. autoclass:: guidata.dataset.FuncProp :members: """ # pylint: disable-msg=W0622 # pylint: disable-msg=W0212 from __future__ import annotations import re import sys from abc import ABC, abstractmethod from collections.abc import Callable from copy import deepcopy from typing import TYPE_CHECKING, Any, TypeVar from guidata.io import INIReader, INIWriter from guidata.userconfig import UserConfig DEBUG_DESERIALIZE = False if TYPE_CHECKING: from qtpy.QtCore import QSize from qtpy.QtWidgets import QDialog, QWidget from guidata.dataset.qtwidgets import DataSetEditDialog from guidata.io import HDF5Reader, HDF5Writer, JSONReader, JSONWriter class NoDefault: pass class ItemProperty: """Base class for item properties Args: callable (Callable): callable to use to evaluate the value of the property """ def __init__(self, callable: Callable) -> None: self.callable = callable def __call__(self, instance: DataSet, item: Any, value: Any) -> Any: """Evaluate the value of the property given, the instance, the item and the value maintained in the instance by the item""" return self.callable(instance, item, value) def set(self, instance: DataSet, item: Any, value: Any) -> Any: """Sets the value of the property given an instance, item and value Depending on implementation the value will be stored either on the instance, item or self Args: instance (DataSet): instance of the DataSet item (Any): item to set the value of value (Any): value to set """ raise NotImplementedError FMT_GROUPS = re.compile(r"(? None: """fmt is a format string it can contain a single anonymous substition or several named substitions. """ self.fmt = fmt self.ignore_error = ignore_error self.attrs = FMT_GROUPS.findall(fmt) def __call__(self, instance: DataSet, item: DataItem, value: Any) -> Any: if not self.attrs: return self.fmt.format(value) dic = {} for attr in self.attrs: dic[attr] = getattr(instance, attr) try: return self.fmt % dic except TypeError: if not self.ignore_error: print(f"Wrong Format for {item._name} : {self.fmt!r} % {dic!r}") raise class GetAttrProp(ItemProperty): """A property that matches the value of an instance's attribute Args: attr (str): attribute to match """ def __init__(self, attr: str) -> None: self.attr = attr def __call__(self, instance: DataSet, item: DataItem, value: Any) -> Any: val = getattr(instance, self.attr) return val def set(self, instance: DataSet, item: DataItem, value: Any) -> None: setattr(instance, self.attr, value) class ValueProp(ItemProperty): """A property that retrieves a value stored elsewhere Args: value (Any): value to store """ def __init__(self, value: Any) -> None: self.value = value def __call__(self, instance: DataSet, item: DataItem, value: Any) -> Any: return self.value def set(self, instance: DataSet, item: DataItem, value: Any) -> None: """Sets the value of the property given an instance, item and value Args: instance (DataSet): instance of the DataSet item (Any): item to set the value of value (Any): value to set """ self.value = value class NotProp(ItemProperty): """Not property Args: prop (ItemProperty): property to negate """ def __init__(self, prop: ItemProperty): self.property = prop def __call__(self, instance: DataSet, item: DataItem, value: Any) -> Any: return not self.property(instance, item, value) def set(self, instance: DataSet, item: DataItem, value: Any) -> None: """Sets the value of the property given an instance, item and value Args: instance (DataSet): instance of the DataSet item (Any): item to set the value of value (Any): value to set """ self.property.set(instance, item, not value) class FuncProp(ItemProperty): """An 'operator property' Args: prop (ItemProperty): property to apply function to func (function): function to apply invfunc (function): inverse function (default: func) """ def __init__( self, prop: ItemProperty, func: Callable, invfunc: Callable | None = None, ) -> None: self.property = prop self.function = func if invfunc is None: invfunc = func self.inverse_function = invfunc def __call__(self, instance: DataSet, item: DataItem, value: Any) -> Any: return self.function(self.property(instance, item, value)) def set(self, instance: DataSet, item: DataItem, value: Any) -> None: """Sets the value of the property given an instance, item and value Args: instance (DataSet): instance of the DataSet item (Any): item to set the value of value (Any): value to set """ self.property.set(instance, item, self.inverse_function(value)) class DataItem(ABC): """DataSet data item Args: label (str): item label default (Any): default value help (str): text displayed on data item's tooltip check (bool): check value (default: True) """ type = type count = 0 def __init__( self, label: str, default: Any | None = None, help: str | None = "", check: bool | None = True, ) -> None: self._order = DataItem.count DataItem.count += 1 self._name: str | None = None self._default = default self._help = help self._props: dict[ Any, Any ] = {} # a dict realm->dict containing realm-specific properties self.set_prop("display", col=0, colspan=None, row=None, label=label) self.set_prop("data", check_value=check) def get_prop(self, realm: str, name: str, default: Any = NoDefault) -> Any: """Get one property of this item Args: realm (str): realm name name (str): property name default (Any): default value (default: NoDefault) Returns: Any: property value """ prop = self._props.get(realm) if not prop: prop = {} if default is NoDefault: return prop[name] return prop.get(name, default) def get_prop_value( self, realm: str, instance: DataSet, name: str, default: Any = NoDefault ) -> Any: """Get one property of this item Args: realm (str): realm name instance (DataSet): instance of the DataSet name (str): property name default (Any): default value (default: NoDefault) Returns: Any: property value """ value = self.get_prop(realm, name, default) if isinstance(value, ItemProperty): return value(instance, self, self.get_value(instance)) else: return value def set_prop(self, realm: str, **kwargs) -> DataItem: """Set one or several properties using the syntax:: set_prop(name1=value1, ..., nameX=valueX) It returns self so that we can assign to the result like this:: item = Item().set_prop(x=y) Args: realm (str): realm name kwargs: properties to set Returns: DataItem: self """ # noqa prop = self._props.setdefault(realm, {}) prop.update(kwargs) return self def set_pos( self, col: int = 0, colspan: int | None = None, row: int | None = None ) -> DataItem: """Set data item's position on a GUI layout Args: col (int): column number (default: 0) colspan (int): number of columns (default: None) row (int): row number (default: None) """ self.set_prop("display", col=col, colspan=colspan, row=row) return self def __str__(self) -> str: return f"{self._name} : {self.__class__.__name__}" def get_help(self, instance: DataSet) -> str: """Return data item's tooltip Args: instance (DataSet): instance of the DataSet Returns: str: tooltip """ auto_help = self.get_auto_help(instance) help = self._help or "" if auto_help: help = help + "\n(" + auto_help + ")" if help else auto_help.capitalize() return help def get_auto_help(self, instance: DataSet) -> str: """Return the automatically generated part of data item's tooltip Args: instance (DataSet): instance of the DataSet Returns: str: automatically generated part of tooltip """ return "" def format_string(self, instance: Any, value: Any, fmt: str, func: Callable) -> str: """Apply format to string representation of the item's value Args: instance (Any): instance of the DataSet value (Any): item's value fmt (str): format string func (Callable): function to apply to the value before formatting Returns: str: formatted string """ return fmt % (func(value),) def get_string_value(self, instance: DataSet) -> str: """Return a formatted unicode representation of the item's value obeying 'display' or 'repr' properties Args: instance (DataSet): instance of the DataSet Returns: str: formatted string """ value = self.get_value(instance) repval = self.get_prop_value("display", instance, "repr", None) if repval is not None: return repval else: fmt = self.get_prop_value("display", instance, "format", "%s") fmt = "%s" if fmt is None else fmt func = self.get_prop_value("display", instance, "func", lambda x: x) if ( isinstance(fmt, Callable) # type:ignore and value is not None ): return fmt(func(value)) if value is not None: text = self.format_string(instance, value, fmt, func) else: text = "" return text def get_name(self) -> str: """Return data item's name Returns: str: name """ return self._name or "" def set_name(self, new_name: str) -> None: """Set data item's name Args: new_name (str): new name """ self._name = new_name def set_help(self, new_help: str) -> None: """Set data item's help text Args: new_help (str): new help text """ self._help = new_help def set_from_string(self, instance: DataSet, string_value: str) -> None: """Set data item's value from specified string Args: instance (DataSet): instance of the DataSet string_value (str): string value """ value = self.from_string(string_value) self.__set__(instance, value) def get_default(self) -> Any: """Return data item's default value Returns: Any: default value """ return self._default def set_default(self, instance: DataSet) -> None: """Set data item's value to default Args: instance (DataSet): instance of the DataSet """ self.__set__(instance, self._default) def accept(self, visitor: object) -> None: """This is the visitor pattern's accept function. It calls the corresponding visit_MYCLASS method of the visitor object. Python's allow a generic base class implementation of this method so there's no need to write an accept function for each derived class unless you need to override the default behavior Args: visitor (object) """ funcname = "visit_" + self.__class__.__name__ func = getattr(visitor, funcname) func(self) def __set__(self, instance: Any, value: Any): setattr(instance, "_%s" % (self._name), value) def __get__(self, instance: Any, klass: type) -> Any | None: if instance is not None: return getattr(instance, "_%s" % (self._name), self._default) else: return self def get_value(self, instance: Any) -> Any: """Return data item's value Args: instance (Any): instance of the DataSet Returns: Any: data item's value """ return self.__get__(instance, instance.__class__) def check_item(self, instance: Any) -> Any: """Check data item's current value (calling method check_value) Args: instance (Any): instance of the DataSet Returns: Any: data item's value """ value = getattr(instance, "_%s" % (self._name)) return self.check_value(value) def check_value(self, value: Any) -> bool: """Check if `value` is valid for this data item Args: value (Any): value to check Returns: bool: value """ raise NotImplementedError() def from_string(self, string_value: str) -> Any: """Transform string into valid data item's value Args: string_value (str): string value Returns: Any: data item's value """ raise NotImplementedError() def bind(self, instance: DataSet) -> DataItemVariable: """Return a DataItemVariable instance bound to the data item Args: instance (DataSet): instance of the DataSet Returns: DataItemVariable: DataItemVariable instance """ return DataItemVariable(self, instance) def serialize( self, instance: DataSet, writer: HDF5Writer | JSONWriter | INIWriter, ) -> None: """Serialize this item using the writer object This is a default implementation that should work for everything but new datatypes Args: instance (DataSet): instance of the DataSet writer (HDF5Writer | JSONWriter | INIWriter): writer object """ value = self.get_value(instance) writer.write(value) def get_value_from_reader(self, reader: HDF5Reader | JSONReader | INIReader) -> Any: """Reads value from the reader object, inside the try...except statement defined in the base item `deserialize` method This method is reimplemented in some child classes Args: reader (HDF5Reader | JSONReader | INIReader): reader object """ return reader.read_any() def deserialize( self, instance: Any, reader: HDF5Reader | JSONReader | INIReader, ) -> None: """Deserialize this item using the reader object Default base implementation supposes the reader can detect expected datatype from the stream Args: instance (Any): instance of the DataSet reader (HDF5Reader | JSONReader | INIReader): reader object """ try: value = self.get_value_from_reader(reader) except KeyError: self.set_default(instance) return except RuntimeError as e: if DEBUG_DESERIALIZE: import traceback print("DEBUG_DESERIALIZE enabled in datatypes.py", file=sys.stderr) traceback.print_stack() print(e, file=sys.stderr) self.set_default(instance) return self.__set__(instance, value) class Obj: """An object that helps build default instances for ObjectItems""" def __init__(self, **kwargs) -> None: self.__dict__.update(kwargs) class ObjectItem(DataItem): """Simple helper class implementing default for composite objects""" klass: type | None = None def set_default(self, instance: DataSet) -> None: """Make a copy of the default value Args: instance (DataSet): instance of the DataSet """ # Avoid circular import # pylint: disable=import-outside-toplevel from guidata.dataset.conv import update_dataset if self.klass is not None: value = self.klass() # pylint: disable=not-callable if self._default is not None: update_dataset(value, self._default) self.__set__(instance, value) def deserialize( self, instance: DataSet, reader: HDF5Reader | JSONReader | INIReader, ) -> None: """Deserialize this item using the reader object We build a new default object and deserialize it Args: instance (DataSet): instance of the DataSet reader (HDF5Reader | JSONReader | INIReader): reader object """ if self.klass is not None: value = self.klass() # pylint: disable=not-callable value.deserialize(reader) self.__set__(instance, value) class DataItemProxy: """ Proxy for DataItem objects This class is needed to construct GroupItem class (see module guidata.qtwidgets) Args: item (DataItem): data item to proxy """ def __init__(self, item: DataItem): self.item = item def __str__(self): return self.item._name + "_proxy: " + self.__class__.__name__ def get_help(self, instance: DataSet) -> str: """DataItem method proxy Args: instance (DataSet): instance of the DataSet Returns: str: help string """ return self.item.get_help(instance) def get_auto_help(self, instance: DataSet) -> str: """DataItem method proxy Args: instance (DataSet): instance of the DataSet Returns: str: help string """ return self.item.get_auto_help(instance) def get_string_value(self, instance: DataSet) -> str: """DataItem method proxy Args: instance (DataSet): instance of the DataSet Returns: str: string value """ return self.item.get_string_value(instance) def set_from_string(self, instance: DataSet, string_value: str) -> None: """DataItem method proxy Args: instance (DataSet): instance of the DataSet string_value (str): string value """ self.item.set_from_string(instance, string_value) def set_default(self, instance: DataSet) -> None: """DataItem method proxy Args: instance (DataSet): instance of the DataSet """ self.item.set_default(instance) def __set__(self, instance: Any, value: Any): pass def accept(self, visitor: object) -> None: """DataItem method proxy Args: visitor (object): visitor object """ self.item.accept(visitor) def get_value(self, instance: DataItem) -> Any: """DataItem method proxy Args: instance (DataItem): instance of the DataItem Returns: Any: value """ return self.item.get_value(instance) def check_item(self, instance: DataItem) -> Any: """DataItem method proxy Args: instance (DataItem): instance of the DataItem Returns: Any: value """ return self.item.check_item(instance) def check_value(self, value: Any) -> Any: """DataItem method proxy Args: value (Any): value Returns: Any: value """ return self.item.check_value(value) def from_string(self, string_value: str) -> Any: """DataItem method proxy Args: string_value (str): string value Returns: Any: value """ return self.item.from_string(string_value) def get_prop(self, realm: str, name: str, default=NoDefault) -> Any: """DataItem method proxy Args: realm (str): realm name (str): name default (Any): default value Returns: Any: value """ return self.item.get_prop(realm, name, default) def get_prop_value( self, realm, instance: DataSet, name: str, default: Any = NoDefault ) -> Any: """DataItem method proxy Args: realm (str): realm instance (DataSet): instance of the DataSet name (str): name default (Any): default value Returns: Any: value """ return self.item.get_prop_value(realm, instance, name, default) def set_prop(self, realm: str, **kwargs) -> DataItem: """DataItem method proxy Args: realm (str): realm kwargs: keyword arguments Returns: DataItem: data item """ # noqa return self.item.set_prop(realm, **kwargs) def bind(self, instance: DataSet) -> DataItemVariable: """DataItem method proxy Args: instance (DataSet): instance of the DataSet Returns: DataItemVariable: data item variable """ return DataItemVariable(self, instance) class DataItemVariable: """An instance of a DataItemVariable represent a binding between an item and a dataset. could be called a bound property. since DataItem instances are class attributes they need to have a DataSet instance to store their value. This class binds the two together. Args: item (DataItem): data item instance (DataSet): instance of the DataSet """ def __init__( self, item: DataItem, instance: DataSet, ): self.item = item self.instance = instance def get_prop_value(self, realm: str, name: str, default: object = NoDefault) -> Any: """DataItem method proxy Args: realm (str): realm name (str): name default (object): default value Returns: Any: value """ return self.item.get_prop_value(realm, self.instance, name, default) def get_prop(self, realm: str, name: str, default: type | None = NoDefault) -> Any: """DataItem method proxy Args: realm (str): realm name (str): name default (type | None): default value Returns: Any: value """ return self.item.get_prop(realm, name, default) def get_help(self) -> str: """Re-implement DataItem method Returns: str: help string """ return self.item.get_help(self.instance) def get_auto_help(self) -> str: """Re-implement DataItem method Returns: str: help string """ return self.item.get_auto_help(self.instance) def get_string_value(self) -> str: """Return a unicode representation of the item's value obeying 'display' or 'repr' properties Returns: str: string value """ return self.item.get_string_value(self.instance) def set_default(self) -> None: """Re-implement DataItem method""" return self.item.set_default(self.instance) def get(self) -> Any: """Re-implement DataItem method Returns: Any: value """ return self.item.get_value(self.instance) def set(self, value: Any) -> None: """Re-implement DataItem method Args: value (Any): value """ return self.item.__set__(self.instance, value) def set_from_string(self, string_value) -> None: """Re-implement DataItem method Args: string_value (str): string value """ return self.item.set_from_string(self.instance, string_value) def check_item(self) -> Any: """Re-implement DataItem method Returns: Any: value """ return self.item.check_item(self.instance) def check_value(self, value) -> Any: """Re-implement DataItem method Args: value (Any): value Returns: Any: value """ return self.item.check_value(value) def from_string(self, string_value: str) -> Any: """Re-implement DataItem method Args: string_value (str): string value Returns: Any: value """ return self.item.from_string(string_value) def label(self) -> str: """Re-implement DataItem method Returns: str: label """ return self.item.get_prop("display", "label") class DataSetMeta(type): """ DataSet metaclass Create class attribute `_items`: list of the DataSet class attributes, created in the same order as these attributes were written """ def __new__(cls: type, name: str, bases: Any, dct: dict[str, Any]) -> type: items = {} for base in bases: if getattr(base, "__metaclass__", None) is DataSetMeta: for item in base._items: items[item._name] = item for attrname, value in list(dct.items()): if isinstance(value, DataItem): value.set_name(attrname) if attrname in items: value._order = items[attrname]._order items[attrname] = value items_list = list(items.values()) items_list.sort(key=lambda x: x._order) dct["_items"] = items_list return type.__new__(cls, name, bases, dct) Meta_Py3Compat = DataSetMeta("Meta_Py3Compat", (object,), {}) AnyDataSet = TypeVar("AnyDataSet", bound="DataSet") class DataSet(metaclass=DataSetMeta): """Construct a DataSet object is a set of DataItem objects Args: title (str): title comment (str): comment. Text shown on the top of the first data item icon (str): icon filename as in image search paths """ _items: list[DataItem] = [] __metaclass__ = DataSetMeta # keep it even with Python 3 (see DataSetMeta) def __init__( self, title: str | None = None, comment: str | None = None, icon: str = "", readonly: bool = False, ): self.__comment = comment self.__icon = icon comp_title, comp_comment = self._compute_title_and_comment() if title: self.__title = title else: self.__title = comp_title if comment is None: self.__comment = comp_comment self.__changed = False self.__readonly: bool = readonly self.set_defaults() def get_items(self, copy=False) -> list[DataItem]: """Returns all the DataItem objects from the DataSet instance. Ignore private items that have a name starting with an underscore (e.g. '_private_item = ...') Args: copy: If True, deepcopy the DataItem list, else return the original. Defaults to False. Returns: _description_ """ result_items = self._items if not copy else deepcopy(self._items) return list(filter(lambda s: not s.get_name().startswith("_"), result_items)) @classmethod def create(cls: type[AnyDataSet], **kwargs) -> AnyDataSet: """Create a new instance of the DataSet class Args: kwargs: keyword arguments to set the DataItem values Returns: DataSet instance """ # noqa instance = cls() for name in kwargs: for item in instance._items: if item._name == name: setattr(instance, name, kwargs[name]) break else: raise AttributeError(f"Unknown attribute {name}") return instance def _get_translation(self): """We try to find the translation function (_) from the module this class was created in This function is unused but could be useful to translate strings that cannot be translated at the time they are created. """ module = sys.modules[self.__class__.__module__] if hasattr(module, "_"): return module._ else: return lambda x: x def _compute_title_and_comment(self) -> tuple[str, str | None]: """ Private method to compute title and comment of the data set """ comp_title = self.__class__.__name__ comp_comment = None if self.__doc__: doc_lines = self.__doc__.splitlines() # Remove empty lines at the begining of comment while doc_lines and not doc_lines[0].strip(): del doc_lines[0] if doc_lines: comp_title = doc_lines.pop(0).strip() if doc_lines: comp_comment = "\n".join([x.strip() for x in doc_lines]) return comp_title, comp_comment def get_title(self) -> str: """Return data set title Returns: str: title """ return self.__title def get_comment(self) -> str | None: """Return data set comment Returns: str | None: comment """ return self.__comment def get_icon(self) -> str | None: """Return data set icon Returns: str | None: icon """ return self.__icon def set_defaults(self) -> None: """Set default values""" for item in self._items: item.set_default(self) def __str__(self): return self.to_string(debug=False) def check(self) -> list[str]: """Check the dataset item values Returns: list[str]: list of errors """ errors = [] for item in self._items: if not item.check_item(self) and item._name is not None: errors.append(item._name) return errors def text_edit(self) -> None: """Edit data set with text input only""" from guidata.dataset import textedit self.accept(textedit.TextEditVisitor(self)) def edit( self, parent: QWidget | None = None, apply: Callable | None = None, wordwrap: bool = True, size: QSize | tuple[int, int] | None = None, ) -> DataSetEditDialog: """Open a dialog box to edit data set Args: parent: parent widget (default is None, meaning no parent) apply: apply callback (default is None) wordwrap: if True, comment text is wordwrapped size: dialog size (QSize object or integer tuple (width, height)) """ # Importing those modules here avoids Qt dependency when # guidata is used without Qt # pylint: disable=import-outside-toplevel from guidata.dataset.qtwidgets import DataSetEditDialog from guidata.qthelpers import exec_dialog dlg = DataSetEditDialog( self, icon=self.__icon, parent=parent, apply=apply, wordwrap=wordwrap, size=size, ) return exec_dialog(dlg) def view( self, parent: QWidget | None = None, wordwrap: bool = True, size: QSize | tuple[int, int] | None = None, ) -> None: """Open a dialog box to view data set Args: parent: parent widget (default is None, meaning no parent) wordwrap: if True, comment text is wordwrapped size: dialog size (QSize object or integer tuple (width, height)) """ # Importing those modules here avoids Qt dependency when # guidata is used without Qt # pylint: disable=import-outside-toplevel from guidata.dataset.qtwidgets import DataSetShowDialog from guidata.qthelpers import exec_dialog dial = DataSetShowDialog( self, icon=self.__icon, parent=parent, wordwrap=wordwrap, size=size ) return exec_dialog(dial) def is_readonly(self) -> bool: return bool(self.__readonly) def set_readonly(self, readonly: bool = True): self.__readonly = readonly def to_string( self, debug: bool | None = False, indent: str | None = None, align: bool | None = False, show_hidden: bool | None = True, ) -> str: """Return readable string representation of the data set If debug is True, add more details on data items Args: debug (bool): if True, add more details on data items indent (str): indentation string (default is None, meaning no indentation) align (bool): if True, align data items (default is False) show_hidden (bool): if True, show hidden data items (default is True) Returns: str: string representation of the data set """ if indent is None: indent = "\n " txt = "%s:" % (self.__title) def _get_label(item): if debug: return item._name else: return item.get_prop_value("display", self, "label") length = 0 if align: for item in self._items: item_length = len(_get_label(item)) if item_length > length: length = item_length for item in self._items: try: hide = item.get_prop_value("display", self, "hide") if not show_hidden and hide is True: continue except KeyError: pass if isinstance(item, ObjectItem): composite_dataset = item.get_value(self) txt += indent + composite_dataset.to_string( debug=debug, indent=indent + " " ) continue elif isinstance(item, BeginGroup): txt += "%s%s:" % (indent, item._name) indent += " " continue elif isinstance(item, EndGroup): indent = indent[:-2] continue value = getattr(self, "_%s" % (item._name)) value_str = "-" if value is None else item.get_string_value(self) if debug: label = item._name else: label = item.get_prop_value("display", self, "label") if length and label is not None: label = label.ljust(length) txt += f"{indent}{label}: {value_str}" if debug: txt += " (" + item.__class__.__name__ + ")" return txt def accept(self, vis: object) -> None: """Helper function that passes the visitor to the accept methods of all the items in this dataset Args: vis (object): visitor object """ for item in self._items: item.accept(vis) def serialize(self, writer: HDF5Writer | JSONWriter | INIWriter) -> None: """Serialize the dataset Args: writer (HDF5Writer | JSONWriter | INIWriter): writer object """ for item in self._items: with writer.group(item._name): item.serialize(self, writer) def deserialize(self, reader: HDF5Reader | JSONReader | INIReader) -> None: """Deserialize the dataset Args: reader (HDF5Reader | JSONReader | INIReader): reader object """ for item in self._items: with reader.group(item._name): try: item.deserialize(self, reader) except RuntimeError as error: if DEBUG_DESERIALIZE: import traceback print( "DEBUG_DESERIALIZE enabled in datatypes.py", file=sys.stderr ) traceback.print_stack() print(error, file=sys.stderr) item.set_default(self) def read_config(self, conf: UserConfig, section: str, option: str) -> None: """Read configuration from a UserConfig instance Args: conf (UserConfig): UserConfig instance section (str): section name option (str): option name """ reader = INIReader(conf, section, option) self.deserialize(reader) def write_config(self, conf: UserConfig, section: str, option: str) -> None: """Write configuration to a UserConfig instance Args: conf (UserConfig): UserConfig instance section (str): section name option (str): option name """ writer = INIWriter(conf, section, option) self.serialize(writer) @classmethod def set_global_prop(klass, realm: str, **kwargs) -> None: """Set global properties for all data items in the dataset Args: realm (str): realm name kwargs (dict): properties to set """ # noqa for item in klass._items: item.set_prop(realm, **kwargs) class ActivableDataSet(DataSet): """An ActivableDataSet instance must have an "enable" class attribute which will set the active state of the dataset instance (see example in: tests/activable_dataset.py) Args: title (str): dataset title (optional) comment (str): dataset comment (optional) icon (str): dataset icon. Default is "" (no icon) """ _activable = True # default *instance* attribute value _active = True _activable_prop = GetAttrProp("_activable") _active_prop = GetAttrProp("_active") @property @abstractmethod def enable(self) -> DataItem: ... def __init__( self, title: str | None = None, comment: str | None = None, icon: str = "", ): DataSet.__init__(self, title, comment, icon) @classmethod def active_setup(cls) -> None: """ This class method must be called after the child class definition in order to setup the dataset active state """ cls.set_global_prop("display", active=cls._active_prop) cls.enable.set_prop( # type:ignore "display", active=True, hide=cls._activable_prop, store=cls._active_prop ) def set_activable(self, activable: bool): self._activable = not activable self._active = self.enable class DataSetGroup: """Construct a DataSetGroup object, used to group several datasets together Args: datasets (list[DataSet]): list of datasets title (str): group title (optional) icon (str): group icon. Default is "" (no icon) This class tries to mimics the DataSet interface. The GUI should represent it as a notebook with one page for each contained dataset. """ ALLOWED_MODES = ("tabs", "table", None) def __init__( self, datasets: list[DataSet], title: str | None = None, icon: str = "", ) -> None: self.__icon = icon self.datasets = datasets if title: self.__title = title else: self.__title = self.__class__.__name__ def __str__(self) -> str: return "\n".join([dataset.__str__() for dataset in self.datasets]) def get_title(self) -> str: """Return data set group title Returns: str: data set group title """ return self.__title def get_comment(self) -> None: """Return data set group comment --> not implemented (will return None) Returns: None: data set group comment """ return None def get_icon(self) -> str | None: """Return data set icon Returns: str | None: data set icon """ return self.__icon def check(self) -> list[list[str]]: """Check data set group items Returns: list[list[str]]: list of errors """ return [dataset.check() for dataset in self.datasets] def text_edit(self) -> None: """Edit data set with text input only""" raise NotImplementedError() def edit( self, parent: QWidget | None = None, apply: Callable | None = None, wordwrap: bool = True, size: QSize | tuple[int, int] | None = None, mode: str | None = None, ) -> int: """Open a dialog box to edit data set Args: parent: parent widget. Defaults to None. apply: apply callback. Defaults to None. wordwrap: if True, comment text is wordwrapped size: dialog size (default: None) mode: (str): dialog window style to use. Allowed values are "tabs", "table" and None. Use "tabs" to navigate between datasets with tabs. Use "table" to create a table with one dataset by row (allows dataset editing by double clicking on a row). Defaults to None. Returns: int: dialog box return code """ # Importing those modules here avoids Qt dependency when # guidata is used without Qt # pylint: disable=import-outside-toplevel assert mode in self.ALLOWED_MODES from guidata.dataset.qtwidgets import ( DataSetGroupEditDialog, DataSetGroupTableEditDialog, ) from guidata.qthelpers import exec_dialog dial: QDialog if mode in ("tabs", None): dial = DataSetGroupEditDialog( instance=self, # type: ignore icon=self.__icon, parent=parent, apply=apply, wordwrap=wordwrap, size=size, ) return exec_dialog(dial) else: dial = DataSetGroupTableEditDialog( instance=self, icon=self.__icon, parent=parent, apply=apply, wordwrap=wordwrap, size=size, ) return exec_dialog(dial) def accept(self, vis: object) -> None: """Helper function that passes the visitor to the accept methods of all the items in this dataset Args: vis (object): visitor """ for dataset in self.datasets: dataset.accept(vis) def is_readonly(self) -> bool: """Return True if all datasets in the DataSetGroup are in readonly mode. Returns: True if all datasets are in readonly, else False """ return all((ds.is_readonly() for ds in self.datasets)) def set_readonly(self, readonly=True): """Set all datasets of the dataset group to readonly mode Args: readonly: Readonly flag. Defaults to True. """ _ = [d.set_readonly(readonly) for d in self.datasets] class GroupItem(DataItemProxy): """GroupItem proxy Args: item (DataItem): data item """ def __init__(self, item: DataItem) -> None: DataItemProxy.__init__(self, item) self.group: list[Any] = [] class BeginGroup(DataItem): """Data item which does not represent anything but a begin flag to define a data set group Args: label (str): group label """ def __init__(self, label: str) -> None: super().__init__(label) def serialize(self, instance, writer) -> None: pass def deserialize(self, instance, reader) -> None: pass def get_group(self) -> "GroupItem": return GroupItem(self) class EndGroup(DataItem): """Data item which does not represent anything but an end flag to define a data set group Args: label (str): group label """ def __init__(self, label: str) -> None: super().__init__(label) def serialize(self, instance, writer) -> None: pass def deserialize(self, instance, reader) -> None: pass class TabGroupItem(GroupItem): pass class BeginTabGroup(BeginGroup): """Data item which does not represent anything but a begin flag to define a data set tab group Args: label (str): group label """ def get_group(self) -> "TabGroupItem": return TabGroupItem(self) class EndTabGroup(EndGroup): """Data item which does not represent anything but an end flag to define a data set tab group Args: label (str): group label """ pass ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/dataset/io.py0000644000175100001770000000103714654363416016606 0ustar00runnerdockerimport warnings # Compatibility imports with guidata <= 3.3 from guidata.io.base import BaseIOHandler, GroupContext, WriterMixin # noqa from guidata.io.h5fmt import HDF5Handler, HDF5Reader, HDF5Writer # noqa from guidata.io.inifmt import INIHandler, INIReader, INIWriter # noqa from guidata.io.jsonfmt import JSONHandler, JSONReader, JSONWriter # noqa warnings.warn( "guidata.dataset.io module is deprecated and will be removed in a future release. " "Please use guidata.io instead.", DeprecationWarning, stacklevel=2, ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/dataset/note_directive.py0000644000175100001770000000667714654363416021221 0ustar00runnerdocker# # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """Sphinx directive to display a note about how to instanciate a dataset class""" from __future__ import annotations from typing import TYPE_CHECKING, Type from docutils import nodes from sphinx.util import logging from sphinx.util.docutils import SphinxDirective import guidata.dataset as gds from guidata import __version__ if TYPE_CHECKING: import sphinx.application logger = logging.getLogger(__name__) class DatasetNoteDirective(SphinxDirective): """Directive to display a note about how to instanciate a dataset class""" required_arguments = 1 # the class name is a required argument optional_arguments = 1 # the number of example lines to display is optional final_argument_whitespace = True has_content = True def run(self): class_name = self.arguments[0] example_lines: int | None if len(self.arguments) > self.required_arguments: example_lines = int(self.arguments[self.required_arguments]) else: example_lines = None cls: Type[gds.DataSet] try: # Try to import the class module_name, class_name = class_name.rsplit(".", 1) module = __import__(module_name, fromlist=[class_name]) cls = getattr(module, class_name) except Exception as e: logger.warning(f"Failed to import class {class_name}: {e}") instance_str = f"Failed to import class {class_name}" note_node = nodes.error(instance_str) return [note_node] # Create an instance of the class and get its string representation instance = cls() instance_str = str(instance) items = instance.get_items() formated_args = ", ".join( f"{item.get_name()}={item._default}" for item in instance.get_items() ) note_node = nodes.note() paragraph1 = nodes.paragraph() paragraph1 += nodes.Text("To instanciate a new ") paragraph1 += nodes.literal(text=f"{cls.__name__}") paragraph1 += nodes.Text(" , you can use the create() classmethod like this:") paragraph1 += nodes.literal_block( text=f"{cls.__name__}.create({formated_args})", language="python" ) note_node += paragraph1 paragraph2 = nodes.paragraph() paragraph2 += nodes.Text("You can also first instanciate a default ") paragraph2 += nodes.literal(text=f" {cls.__name__}") paragraph2 += nodes.Text(" and then set the fields like this:") example_lines = min(len(items), example_lines) if example_lines else len(items) code_lines = [ f"dataset = {cls.__name__}()", *( f"dataset.{items[i].get_name()} = {repr(items[i]._default)}" for i in range(example_lines) ), ] if len(items) > example_lines: code_lines.append("...") paragraph2 += nodes.literal_block( text="\n".join(code_lines), language="python", ) note_node += paragraph2 # Create a note node return [note_node] def setup(app: sphinx.application.Sphinx) -> dict[str, object]: """Initialize the Sphinx extension""" app.add_directive("datasetnote", DatasetNoteDirective) return { "version": __version__, "parallel_read_safe": True, "parallel_write_safe": True, } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/dataset/qtitemwidgets.py0000644000175100001770000014021414654363416021072 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """ dataset.qtitemwidgets ===================== Widget factories used to edit data items (factory registration is done in guidata.dataset.qtwidgets) (data item types are defined in guidata.dataset.datatypes) There is one widget type for each data item type. Example: ChoiceWidget <--> ChoiceItem, ImageChoiceItem """ from __future__ import annotations import datetime import os import os.path as osp import sys from abc import abstractmethod from collections.abc import Callable from typing import TYPE_CHECKING, Any, Protocol import numpy as np from qtpy.compat import getexistingdirectory from qtpy.QtCore import QSize, Qt from qtpy.QtGui import QColor, QIcon, QPixmap from qtpy.QtWidgets import ( QAbstractButton, QCheckBox, QColorDialog, QComboBox, QDateEdit, QDateTimeEdit, QFrame, QGridLayout, QGroupBox, QHBoxLayout, QLabel, QLineEdit, QPushButton, QRadioButton, QSlider, QTabWidget, QTextEdit, QVBoxLayout, QWidget, ) from guidata.config import _ from guidata.configtools import get_icon, get_image_file_path, get_image_layout from guidata.dataset.conv import restore_dataset, update_dataset from guidata.dataset.datatypes import DataItemVariable from guidata.qthelpers import get_std_icon from guidata.utils.misc import convert_date_format from guidata.widgets.arrayeditor import ArrayEditor # ========================== IMPORTANT ================================= # # In this module, `item` is an instance of DataItemVariable (not DataItem) # (see guidata.datatypes for details) # # ========================== IMPORTANT ================================= # XXX: consider providing an interface here... if TYPE_CHECKING: from guidata.dataset.qtwidgets import DataSetEditLayout class AbstractDataSetWidget: """Base class for 'widgets' handled by `DataSetEditLayout` and it's derived classes. This is a generic representation of an input (or display) widget that has a label and one or more entry field. `DataSetEditLayout` uses a registry of *Item* to *Widget* mapping in order to automatically create a GUI for a `DataSet` structure Args: item: instance of `DataItemVariable` (not `DataItem`) parent_layout: parent `DataSetEditLayout` instance """ READ_ONLY = False def __init__( self, item: DataItemVariable, parent_layout: DataSetEditLayout ) -> None: # Derived constructors should create the necessary widgets. # The base class keeps a reference to item and parent self.item = item self.parent_layout = parent_layout self.group: QWidget | None = None # Layout/Widget grouping items self.label: QLabel | None = None self.build_mode = False def place_label(self, layout: QGridLayout, row: int, column: int) -> None: """Place item label on layout at specified position (row, column) Args: layout: parent layout row: row index column: column index """ label_text = self.item.get_prop_value("display", "label") unit = self.item.get_prop_value("display", "unit", "") if unit and not self.READ_ONLY: label_text += " (%s)" % unit self.label = QLabel(label_text) self.label.setToolTip(self.item.get_help()) layout.addWidget(self.label, row, column) def place_on_grid( self, layout: QGridLayout, row: int, label_column: int, widget_column: int, row_span: int = 1, column_span: int = 1, ) -> None: """Place widget on layout at specified position Args: layout: parent layout row: row index label_column: column index for label widget_column: column index for widget row_span: number of rows to span column_span: number of columns to span """ self.place_label(layout, row, label_column) layout.addWidget(self.group, row, widget_column, row_span, column_span) def is_active(self) -> bool: """Is associated item active? Returns: True if associated item is active """ return self.item.get_prop_value("display", "active", True) def is_readonly(self) -> bool: """Is the parent dataset in readonly mode Returns: True if associated dataset is readonly """ return self.item.instance.is_readonly() def check(self) -> bool: """Item validator Returns: True if item value is valid """ return True def set(self) -> None: """Update data item value from widget contents""" # XXX: consider using item.set instead of item.set_from_string... self.item.set_from_string(self.value()) def get(self) -> None: """Update widget contents from data item value""" pass def value(self) -> Any: """Returns the widget's current value Returns: Widget value """ return None def set_state(self) -> None: """Update the visual status of the widget and enables/disables the widget if necessary""" active = self.is_active() if self.group: self.group.setEnabled(active) if self.label: self.label.setEnabled(active) def notify_value_change(self) -> None: """Notify parent layout that widget value has changed""" if not self.build_mode: self.parent_layout.widget_value_changed() class GroupWidget(AbstractDataSetWidget): """GroupItem widget Args: item: instance of `DataItemVariable` (not `DataItem`) parent_layout: parent `DataSetEditLayout` instance """ def __init__( self, item: DataItemVariable, parent_layout: DataSetEditLayout ) -> None: super().__init__(item, parent_layout) embedded = item.get_prop_value("display", "embedded", False) if not embedded: self.group = QGroupBox(item.get_prop_value("display", "label")) else: self.group = QFrame() self.layout = QGridLayout() EditLayoutClass = parent_layout.__class__ self.edit = EditLayoutClass( self.group, item.instance, self.layout, item.item.group, change_callback=self.notify_value_change, ) self.group.setLayout(self.layout) def get(self) -> None: """Update widget contents from data item value""" self.edit.update_widgets() def set(self) -> None: """Update data item value from widget contents""" self.edit.accept_changes() def check(self) -> bool: """Item validator Returns: True if item value is valid """ return self.edit.check_all_values() def place_on_grid( self, layout: QGridLayout, row: int, label_column: int, widget_column: int, row_span: int = 1, column_span: int = 1, ) -> None: """Place widget on layout at specified position Args: layout: parent layout row: row index label_column: column index for label widget_column: column index for widget row_span: number of rows to span column_span: number of columns to span """ layout.addWidget(self.group, row, label_column, row_span, column_span + 1) def set_state(self) -> None: """Update the visual status of the widget""" super().set_state() self.edit.refresh_widgets() class TabGroupWidget(AbstractDataSetWidget): """TabGroupItem widget Args: item: instance of `DataItemVariable` (not `DataItem`) parent_layout: parent `DataSetEditLayout` instance """ def __init__( self, item_var: DataItemVariable, parent_layout: DataSetEditLayout ) -> None: super().__init__(item_var, parent_layout) self.tabs = QTabWidget() items = item_var.item.group self.widgets = [] for item in items: if item.get_prop_value("display", parent_layout.instance, "hide", False): continue item.set_prop("display", embedded=True) widget = parent_layout.build_widget(item) frame = QFrame() label = widget.item.get_prop_value("display", "label") icon = widget.item.get_prop_value("display", "icon", None) if icon is not None: self.tabs.addTab(frame, get_icon(icon), label) else: self.tabs.addTab(frame, label) layout = QGridLayout() layout.setAlignment(Qt.AlignTop) # type:ignore frame.setLayout(layout) widget.place_on_grid(layout, 0, 0, 1) try: widget.get() except Exception: print("Error building item :", item.item._name) raise self.widgets.append(widget) def get(self) -> None: """Update widget contents from data item value""" for widget in self.widgets: widget.get() def set(self) -> None: """Update data item value from widget contents""" for widget in self.widgets: widget.set() def check(self) -> bool: """Item validator Returns: True if item value is valid """ return True def place_on_grid( self, layout: QGridLayout, row: int, label_column: int, widget_column: int, row_span: int = 1, column_span: int = 1, ) -> None: """Place widget on layout at specified position Args: layout: parent layout row: row index label_column: column index for label widget_column: column index for widget row_span: number of rows to span column_span: number of columns to span """ layout.addWidget(self.tabs, row, label_column, row_span, column_span + 1) def set_state(self) -> None: """Update the visual status of the widget and all the contained item widgets""" super().set_state() for widget in self.widgets: widget.set_state() def _display_callback(widget: AbstractDataSetWidget, value): """Handling of display callback""" cb = widget.item.get_prop_value("display", "callback", None) if cb is not None: if widget.build_mode: widget.set() else: widget.parent_layout.update_dataitems() cb(widget.item.instance, widget.item.item, value) widget.parent_layout.update_widgets(except_this_one=widget) class LineEditWidget(AbstractDataSetWidget): """ QLineEdit-based widget """ def __init__( self, item: "DataItemVariable", parent_layout: "DataSetEditLayout" ) -> None: super().__init__(item, parent_layout) self.edit = self.group = QLineEdit() self.edit.setToolTip(item.get_help()) password = self.item.get_prop_value("display", "password", False) if password: self.edit.setEchoMode(QLineEdit.Password) self.edit.textChanged.connect(self.line_edit_changed) # type:ignore def get(self) -> None: """Update widget contents from data item value""" value = self.item.get() old_value = str(self.value()) if value is not None: if isinstance(value, QColor): # if item is a ColorItem object value = value.name() value = str(value) if value != old_value: self.edit.setText(value) else: self.line_edit_changed(value) def line_edit_changed(self, qvalue: str | None) -> None: """QLineEdit validator""" value = self.item.from_string(str(qvalue)) if qvalue is not None else None if not self.item.check_value(value): self.edit.setStyleSheet("background-color:rgb(255, 175, 90);") else: self.edit.setStyleSheet("") _display_callback(self, value) self.update(value) self.notify_value_change() def update(self, value: Any) -> None: cb = self.item.get_prop_value("display", "value_callback", None) if cb is not None: cb(value) def value(self) -> str: """Returns the widget's current value Returns: Widget value """ return str(self.edit.text()) def check(self) -> bool: """Item validator Returns: True if item value is valid """ value = self.item.from_string(str(self.edit.text())) return self.item.check_value(value) def set_state(self): """Update the visual status of the widget and modify the widget to readonly if necessary""" super().set_state() self.edit.setReadOnly(self.is_readonly()) class TextEditWidget(AbstractDataSetWidget): """ QTextEdit-based widget """ def __init__( self, item: "DataItemVariable", parent_layout: "DataSetEditLayout" ) -> None: super().__init__(item, parent_layout) self.edit = self.group = QTextEdit() self.edit.setToolTip(item.get_help()) self.edit.textChanged.connect(self.text_changed) # type:ignore def __get_text(self) -> str: """Get QTextEdit text, replacing UTF-8 EOL chars by os.linesep""" return str(self.edit.toPlainText()).replace("\u2029", os.linesep) def get(self) -> None: """Update widget contents from data item value""" value = self.item.get() if value is not None: self.edit.setPlainText(value) self.text_changed() def text_changed(self) -> None: """QLineEdit validator""" value = self.item.from_string(self.__get_text()) if not self.item.check_value(value): self.edit.setStyleSheet("background-color:rgb(255, 175, 90);") else: self.edit.setStyleSheet("") self.update(value) _display_callback(self, value) self.notify_value_change() def update(self, value: Any) -> Any: pass def value(self) -> str: """Returns the widget's current value Returns: Widget value """ return self.edit.toPlainText() def check(self) -> bool: """Item validator Returns: True if item value is valid """ value = self.item.from_string(self.__get_text()) return self.item.check_value(value) def set_state(self): """Update the visual status of the widget and modify the widget to readonly if necessary""" super().set_state() self.edit.setReadOnly(self.is_readonly()) class CheckBoxWidget(AbstractDataSetWidget): """ BoolItem widget """ def __init__( self, item: "DataItemVariable", parent_layout: "DataSetEditLayout" ) -> None: super().__init__(item, parent_layout) self.checkbox = QCheckBox(self.item.get_prop_value("display", "text")) self.checkbox.setToolTip(item.get_help()) self.group = self.checkbox self.store = self.item.get_prop("display", "store", None) self.checkbox.stateChanged.connect(self.state_changed) # type:ignore def get(self) -> None: """Update widget contents from data item value""" value = self.item.get() if value is not None: self.checkbox.setChecked(value) def set(self) -> None: """Update data item value from widget contents""" self.item.set(self.value()) def value(self) -> bool: """Returns the widget's current value Returns: Widget value """ return self.checkbox.isChecked() def place_on_grid( self, layout: "QGridLayout", row: int, label_column: int, widget_column: int, row_span: int = 1, column_span: int = 1, ) -> None: """Place widget on layout at specified position Args: layout: parent layout row: row index label_column: column index for label widget_column: column index for widget row_span: number of rows to span column_span: number of columns to span """ if not self.item.get_prop_value("display", "label"): widget_column = label_column column_span += 1 else: self.place_label(layout, row, label_column) layout.addWidget(self.group, row, widget_column, row_span, column_span) def state_changed(self, state: bool) -> None: _display_callback(self, state) self.notify_value_change() if self.store: self.do_store(state) def do_store(self, state: bool) -> None: self.store.set(self.item.instance, self.item.item, state) self.parent_layout.refresh_widgets() def set_state(self): """Update the visual status of the widget and enables/disables it if necessary""" super().set_state() self.checkbox.setEnabled(not self.is_readonly()) class DateWidget(AbstractDataSetWidget): """ DateItem widget """ def __init__( self, item: "DataItemVariable", parent_layout: "DataSetEditLayout" ) -> None: super().__init__(item, parent_layout) self.dateedit = self.group = QDateEdit() self.dateedit.setToolTip(item.get_help()) self.dateedit.dateTimeChanged.connect(self.date_changed) fmt = self.item.get_prop("display", "format", None) if fmt: qt_fmt = convert_date_format(fmt) self.dateedit.setDisplayFormat(qt_fmt) def date_changed(self, value): """Date changed""" _display_callback(self, value) self.notify_value_change() def get(self) -> None: """Update widget contents from data item value""" value = self.item.get() if value: if not isinstance(value, datetime.date): value = datetime.date.fromordinal(value) self.dateedit.setDate(value) def set(self) -> None: """Update data item value from widget contents""" self.item.set(self.value()) def value(self) -> datetime: # type:ignore """Returns the widget's current value Returns: Widget value """ try: return self.dateedit.date().toPyDate() except AttributeError: return self.dateedit.dateTime().toPython().date() # type:ignore # PySide def set_state(self): """Update the visual status of the widget and modify the widget to readonly if necessary""" super().set_state() self.dateedit.setReadOnly(self.is_readonly()) class DateTimeWidget(AbstractDataSetWidget): """ DateTimeItem widget """ def __init__( self, item: "DataItemVariable", parent_layout: "DataSetEditLayout" ) -> None: super().__init__(item, parent_layout) self.dateedit = self.group = QDateTimeEdit() self.dateedit.setCalendarPopup(True) self.dateedit.setToolTip(item.get_help()) self.dateedit.dateTimeChanged.connect( # type:ignore lambda value: self.notify_value_change() ) fmt = self.item.get_prop("display", "format", None) if fmt: qt_fmt = convert_date_format(fmt) self.dateedit.setDisplayFormat(qt_fmt) def date_changed(self, value): """Date changed""" _display_callback(self, value) self.notify_value_change() def get(self) -> None: """Update widget contents from data item value""" value = self.item.get() if value: if not isinstance(value, datetime.datetime): value = datetime.datetime.fromtimestamp(value) self.dateedit.setDateTime(value) def set(self) -> None: """Update data item value from widget contents""" self.item.set(self.value()) def value(self) -> datetime: # type:ignore """Returns the widget's current value Returns: Widget value """ try: return self.dateedit.dateTime().toPyDateTime() except AttributeError: return self.dateedit.dateTime().toPython() # type:ignore # PySide def set_state(self): """Update the visual status of the widget and modify the widget to readonly if necessary""" super().set_state() self.dateedit.setReadOnly(self.is_readonly()) class GroupLayout(QHBoxLayout): def __init__(self) -> None: QHBoxLayout.__init__(self) self.widgets: list[QWidget] = [] def addWidget(self, widget: QWidget) -> None: # type:ignore QHBoxLayout.addWidget(self, widget) self.widgets.append(widget) def setEnabled(self, state: bool) -> None: for widget in self.widgets: widget.setEnabled(state) class HasGroupProtocol(Protocol): @property def group(self): pass def place_label(self, layout: QGridLayout, row: int, column: int) -> None: """Place item label on layout at specified position (row, column) Args: layout: parent layout row: row index column: column index """ pass class HLayoutMixin: def __init__( self: "HasGroupProtocol", item: "DataItemVariable", parent_layout: "DataSetEditLayout", ) -> None: super().__init__(item, parent_layout) # type:ignore old_group = self.group self.group = GroupLayout() self.group.addWidget(old_group) def place_on_grid( self: "HasGroupProtocol", layout: "QGridLayout", row: int, label_column: int, widget_column: int, row_span: int = 1, column_span: int = 1, ): """Place widget on layout at specified position Args: layout: parent layout row: row index label_column: column index for label widget_column: column index for widget row_span: number of rows to span column_span: number of columns to span """ self.place_label(layout, row, label_column) layout.addLayout(self.group, row, widget_column, row_span, column_span) class ColorWidget(HLayoutMixin, LineEditWidget): """ ColorItem widget """ def __init__( self, item: "DataItemVariable", parent_layout: "DataSetEditLayout" ) -> None: super().__init__(item, parent_layout) self.button = QPushButton("") self.button.setMaximumWidth(32) self.__signal_connected: bool = False self.__handle_button_connection() self.group.addWidget(self.button) def update(self, value: str) -> None: """Reimplement LineEditWidget method""" LineEditWidget.update(self, value) color = QColor("" if value is None else value) if color.isValid(): bitmap = QPixmap(16, 16) bitmap.fill(color) icon = QIcon(bitmap) else: icon = get_icon("not_found.png") self.button.setIcon(icon) def select_color(self) -> None: """Open a color selection dialog box""" color = QColor(self.edit.text()) if not color.isValid(): color = Qt.gray # type:ignore color = QColorDialog.getColor(color, self.parent_layout.parent) if color.isValid(): value = color.name() self.edit.setText(value) self.update(value) self.notify_value_change() def __handle_button_connection(self): """Connects the button for the color selection function if parent dataset is not in readonly mode and signal is not already connected but disconnects it if the dataset is readonly. """ if not self.__signal_connected and not self.is_readonly(): self.__signal_connected = True self.button.clicked.connect(self.select_color) elif self.__signal_connected and self.is_readonly(): self.button.clicked.disconnect() self.__signal_connected = False def set_state(self): """Update the visual status of the widget and disconnects/reconnects the button action if necessary""" super().set_state() self.__handle_button_connection() class SliderWidget(HLayoutMixin, LineEditWidget): """ IntItem with Slider """ DATA_TYPE: type = int def __init__( self, item: DataItemVariable, parent_layout: "DataSetEditLayout" ) -> None: super().__init__(item, parent_layout) self.slider = self.vmin = self.vmax = None if item.get_prop_value("display", "slider"): self.vmin = item.get_prop_value("data", "min") self.vmax = item.get_prop_value("data", "max") assert ( self.vmin is not None and self.vmax is not None ), "SliderWidget requires that item min/max have been defined" self.slider = QSlider() self.slider.setOrientation(Qt.Horizontal) # type:ignore self.setup_slider(item) self.slider.valueChanged.connect(self.value_changed) # type:ignore self.group.addWidget(self.slider) def value_to_slider(self, value): return value def slider_to_value(self, value): return value def setup_slider(self, item): self.slider.setRange(self.vmin, self.vmax) def update(self, value): """Reimplement LineEditWidget method""" LineEditWidget.update(self, value) if self.slider is not None and isinstance(value, self.DATA_TYPE): self.slider.blockSignals(True) self.slider.setValue(self.value_to_slider(value)) self.slider.blockSignals(False) def value_changed(self, ivalue): """Update the lineedit""" value = str(self.slider_to_value(ivalue)) self.edit.setText(value) self.update(value) def set_state(self): """Update the visual status of the widget and enables/disables it if necessary""" super().set_state() if self.slider is not None: self.slider.setEnabled(not self.is_readonly()) class FloatSliderWidget(SliderWidget): """ FloatItem with Slider """ DATA_TYPE: type = float def value_to_slider(self, value): return int((value - self.vmin) * 100 / (self.vmax - self.vmin)) def slider_to_value(self, value): return value * (self.vmax - self.vmin) / 100 + self.vmin def setup_slider(self, item): self.slider.setRange(0, 100) def _get_child_title_func(ancestor): previous_ancestor = None while True: try: if previous_ancestor is ancestor: break return ancestor.child_title except AttributeError: previous_ancestor = ancestor ancestor = ancestor.parent() return lambda item: "" class FileWidget(HLayoutMixin, LineEditWidget): """ File path item widget """ def __init__( self, item: "DataItemVariable", parent_layout: "DataSetEditLayout", filedialog: Callable, ) -> None: super().__init__(item, parent_layout) self.filedialog = filedialog self.button = QPushButton() fmt = item.get_prop_value("data", "formats") self.button.setIcon(get_icon("%s.png" % fmt[0].lower(), default="file.png")) self.button.clicked.connect(self.select_file) # type:ignore self.group.addWidget(self.button) self.basedir = item.get_prop_value("data", "basedir") self.all_files_first = item.get_prop_value("data", "all_files_first") def select_file(self) -> None: """Open a file selection dialog box""" fname = self.item.from_string(str(self.edit.text())) if isinstance(fname, list): fname = osp.dirname(fname[0]) parent = self.parent_layout.parent _temp = sys.stdout sys.stdout = None # type:ignore if len(fname) == 0: fname = self.basedir _formats = self.item.get_prop_value("data", "formats") formats = [str(format).lower() for format in _formats] filter_lines = [ (_("%s files") + " (*.%s)") % (format.upper(), format) for format in formats ] all_filter = _("All supported files") + " (*.%s)" % " *.".join(formats) if len(formats) > 1: if self.all_files_first: filter_lines.insert(0, all_filter) else: filter_lines.append(all_filter) if fname is None: fname = "" child_title = _get_child_title_func(parent) fname, _filter = self.filedialog( parent, child_title(self.item), fname, "\n".join(filter_lines) ) sys.stdout = _temp if fname: if isinstance(fname, list): fname = str(fname) self.edit.setText(fname) def set_state(self): """Update the visual status of the widget and disbales/enables it if necessary""" super().set_state() self.button.setEnabled(not self.is_readonly() and self.is_active()) class DirectoryWidget(HLayoutMixin, LineEditWidget): """ Directory path item widget """ def __init__( self, item: "DataItemVariable", parent_layout: "DataSetEditLayout" ) -> None: super().__init__(item, parent_layout) self.button = QPushButton() self.button.setIcon(get_std_icon("DirOpenIcon")) self.button.clicked.connect(self.select_directory) # type:ignore self.group.addWidget(self.button) def select_directory(self) -> None: """Open a directory selection dialog box""" value = self.item.from_string(str(self.edit.text())) parent = self.parent_layout.parent child_title = _get_child_title_func(parent) dname = getexistingdirectory(parent, child_title(self.item), value) if dname: self.edit.setText(dname) def set_state(self): """Update the visual status of the widget and disbales/enables it if necessary""" super().set_state() self.button.setEnabled(not self.is_readonly() and self.is_active()) class ChoiceWidget(AbstractDataSetWidget): """ Choice item widget """ def __init__( self, item: "DataItemVariable", parent_layout: "DataSetEditLayout" ) -> None: super().__init__(item, parent_layout) self._first_call = True self.is_radio = item.get_prop_value("display", "radio") self.image_size = item.get_prop_value("display", "size", None) self.store = self.item.get_prop("display", "store", None) if self.is_radio: self.group = QGroupBox() self.group.setToolTip(item.get_help()) self.vbox = QVBoxLayout() self.group.setLayout(self.vbox) self._buttons: list[QAbstractButton] = [] else: self.combobox = self.group = QComboBox() if self.image_size is not None: width, height = self.image_size self.combobox.setIconSize(QSize(width, height)) self.combobox.setToolTip(item.get_help()) self.combobox.currentIndexChanged.connect(self.index_changed) # type:ignore def index_changed(self, index: int) -> None: """Update the data item value when the index of the combobox changes Args: index: index of the combobox, unused (but required by the signal) """ if self.store: self.store.set(self.item.instance, self.item.item, self.value()) self.parent_layout.refresh_widgets() _display_callback(self, self.value()) self.notify_value_change() def initialize_widget(self) -> None: """Widget initialization depending on the type of the widget (combobox or radiobuttons) """ if self.is_radio: for button in self._buttons: button.hide() button.toggled.disconnect(self.index_changed) # type:ignore self.vbox.removeWidget(button) button.deleteLater() self._buttons = [] else: self.combobox.blockSignals(True) while self.combobox.count(): self.combobox.removeItem(0) _choices = self.item.get_prop_value("data", "choices") for key, lbl, img in _choices: if self.is_radio: button = QRadioButton(lbl, self.group) if self.image_size is not None: width, height = self.image_size button.setIconSize(QSize(width, height)) if img: if isinstance(img, str): if not osp.isfile(img): img = get_image_file_path(img) img = QIcon(img) elif isinstance(img, Callable): # type:ignore img = img(key) if self.is_radio: button.setIcon(img) else: self.combobox.addItem(img, lbl) elif not self.is_radio: self.combobox.addItem(lbl) if self.is_radio: self._buttons.append(button) self.vbox.addWidget(button) button.toggled.connect(self.index_changed) # type:ignore if not self.is_radio: self.combobox.blockSignals(False) def set_widget_value(self, idx: int) -> None: """Set the value of the widget to the given index depending on the type of the widget (combobox or radiobuttons) Args: idx: index to set """ if self.is_radio: for button in self._buttons: button.blockSignals(True) self._buttons[idx].setChecked(True) for button in self._buttons: button.blockSignals(False) else: self.combobox.blockSignals(True) self.combobox.setCurrentIndex(idx) self.combobox.blockSignals(False) def get_widget_value(self) -> int | None: """Returns the index of the widget depending on the type of the widget (combobox or radiobuttons). Returns: current index """ if self.is_radio: for index, widget in enumerate(self._buttons): if widget.isChecked(): return index return None return self.combobox.currentIndex() def get(self) -> None: """Update widget contents from data item value""" self.initialize_widget() value = self.item.get() if value is not None: idx = 0 _choices = self.item.get_prop_value("data", "choices") for key, _val, _img in _choices: if key == value: break idx += 1 self.set_widget_value(idx) if self._first_call: self.index_changed(idx) self._first_call = False def set(self) -> None: """Update data item value from widget contents""" try: value = self.value() except IndexError: return self.item.set(value) def value(self) -> Any: """Returns the widget's current value Returns: Widget value """ index = self.get_widget_value() choices = self.item.get_prop_value("data", "choices") return choices[index][0] def set_state(self): """Update the visual status of the widget and disables/enables it if necessary""" super().set_state() enabled = not self.is_readonly() if self.is_radio: self.vbox.setEnabled(enabled) else: self.combobox.setEnabled(enabled) class MultipleChoiceWidget(AbstractDataSetWidget): """ Multiple choice item widget """ def __init__( self, item: "DataItemVariable", parent_layout: "DataSetEditLayout" ) -> None: super().__init__(item, parent_layout) self.groupbox = self.group = QGroupBox(item.get_prop_value("display", "label")) layout = QGridLayout() self.boxes = [] nx, ny = item.get_prop_value("display", "shape") cx, cy = 0, 0 _choices = item.get_prop_value("data", "choices") for _k, choice, _img in _choices: checkbox = QCheckBox(choice) checkbox.stateChanged.connect(lambda: self.notify_value_change()) layout.addWidget(checkbox, cx, cy) if nx < 0: cy += 1 if cy >= ny: cy = 0 cx += 1 else: cx += 1 if cx >= nx: cx = 0 cy += 1 self.boxes.append(checkbox) self.groupbox.setLayout(layout) def get(self) -> None: """Update widget contents from data item value""" value = self.item.get() _choices = self.item.get_prop_value("data", "choices") for (i, _choice, _img), checkbox in zip(_choices, self.boxes): if value is not None and i in value: checkbox.setChecked(True) def set(self) -> None: """Update data item value from widget contents""" _choices = self.item.get_prop_value("data", "choices") choices = [_choices[i][0] for i in self.value()] self.item.set(choices) def value(self) -> list[int]: """Returns the widget's current value Returns: Widget value """ return [i for i, w in enumerate(self.boxes) if w.isChecked()] def place_on_grid( self, layout: "QGridLayout", row: int, label_column: int, widget_column: int, row_span: int = 1, column_span: int = 1, ) -> None: """Place widget on layout at specified position Args: layout: parent layout row: row index label_column: column index for label widget_column: column index for widget row_span: number of rows to span column_span: number of columns to span """ layout.addWidget(self.group, row, label_column, row_span, column_span + 1) def set_state(self): """Update the visual status of the widget and disables/enables it if necessary""" super().set_state() self.groupbox.setEnabled(not self.is_readonly()) class FloatArrayWidget(AbstractDataSetWidget): """ FloatArrayItem widget """ def __init__( self, item: "DataItemVariable", parent_layout: "DataSetEditLayout" ) -> None: super().__init__(item, parent_layout) _label = item.get_prop_value("display", "label") self.groupbox = self.group = QGroupBox(_label) self.layout = QGridLayout() self.layout.setAlignment(Qt.AlignLeft) # type:ignore self.groupbox.setLayout(self.layout) self.first_line, self.dim_label = get_image_layout( "shape.png", _("Number of rows x Number of columns") ) edit_button = QPushButton(get_icon("arredit.png"), "") edit_button.setToolTip(_("Edit array contents")) edit_button.setMaximumWidth(32) self.first_line.addWidget(edit_button) self.layout.addLayout(self.first_line, 0, 0) self.min_line, self.min_label = get_image_layout( "min.png", _("Smallest element in array") ) self.layout.addLayout(self.min_line, 1, 0) self.max_line, self.max_label = get_image_layout( "max.png", _("Largest element in array") ) self.layout.addLayout(self.max_line, 2, 0) edit_button.clicked.connect(self.edit_array) # type:ignore self.arr = np.array([]) # le tableau si il a ├йt├й modifi├й self.instance = None self.dtype_line, self.dtype_label = get_image_layout("dtype.png", "") self.first_line.insertSpacing(2, 5) self.first_line.insertLayout(3, self.dtype_line) def edit_array(self) -> None: """Open an array editor dialog""" parent = self.parent_layout.parent label = self.item.get_prop_value("display", "label") variable_size = self.item.get_prop_value("edit", "variable_size", default=False) editor = ArrayEditor(parent) if ( editor.setup_and_check( self.arr, title=label, readonly=self.is_readonly(), variable_size=variable_size, ) and editor.exec() ): self.update(self.arr) self.notify_value_change() def get(self) -> None: """Update widget contents from data item value""" self.arr = np.asarray(self.item.get()) if self.item.get_prop_value("display", "transpose"): self.arr = self.arr.T self.update(self.arr) def update(self, arr: np.ndarray) -> None: shape = arr.shape if len(shape) == 1: shape = (1,) + shape dim = " x ".join([str(d) for d in shape]) self.dim_label.setText(dim) format = self.item.get_prop_value("display", "format") minmax = self.item.get_prop_value("display", "minmax") real_arr = np.real(arr) try: if minmax == "all": mint = format % real_arr.min() maxt = format % real_arr.max() elif minmax == "columns": mint = ", ".join( [format % real_arr[r, :].min() for r in range(arr.shape[0])] ) maxt = ", ".join( [format % real_arr[r, :].max() for r in range(arr.shape[0])] ) else: mint = ", ".join( [format % real_arr[:, r].min() for r in range(arr.shape[1])] ) maxt = ", ".join( [format % real_arr[:, r].max() for r in range(arr.shape[1])] ) except (TypeError, IndexError): mint, maxt = "-", "-" self.min_label.setText(mint) self.max_label.setText(maxt) typestr = str(arr.dtype) self.dtype_label.setText("-" if typestr == "object" else typestr) def set(self) -> None: """Update data item value from widget contents""" if self.item.get_prop_value("display", "transpose"): value = self.value().T else: value = self.value() self.item.set(value) def value(self) -> np.ndarray: """Returns the widget's current value Returns: Widget value """ return self.arr def place_on_grid( self, layout: "QGridLayout", row: int, label_column: int, widget_column: int, row_span: int = 1, column_span: int = 1, ) -> None: """Place widget on layout at specified position Args: layout: parent layout row: row index label_column: column index for label widget_column: column index for widget row_span: number of rows to span column_span: number of columns to span """ layout.addWidget(self.group, row, label_column, row_span, column_span + 1) class ButtonWidget(AbstractDataSetWidget): """ BoolItem widget """ def __init__( self, item: "DataItemVariable", parent_layout: "DataSetEditLayout" ) -> None: super().__init__(item, parent_layout) _label = self.item.get_prop_value("display", "label") self.button = self.group = QPushButton(_label) self.button.setToolTip(item.get_help()) image_size = item.get_prop_value("display", "size", None) if image_size is not None: width, height = image_size self.button.setIconSize(QSize(width, height)) _icon = self.item.get_prop_value("display", "icon") if _icon is not None: if isinstance(_icon, str): _icon = get_icon(_icon) self.button.setIcon(_icon) self.button.clicked.connect(self.clicked) # type:ignore self.cb_value = None def get(self) -> None: """Update widget contents from data item value""" self.cb_value = self.item.get() def set(self) -> None: """Update data item value from widget contents""" self.item.set(self.value()) def value(self) -> Any | None: """Returns the widget's current value Returns: Widget value """ return self.cb_value def place_on_grid( self, layout: "QGridLayout", row: int, label_column: int, widget_column: int, row_span: int = 1, column_span: int = 1, ): """Place widget on layout at specified position Args: layout: parent layout row: row index label_column: column index for label widget_column: column index for widget row_span: number of rows to span column_span: number of columns to span """ layout.addWidget(self.group, row, label_column, row_span, column_span + 1) def clicked(self, *args) -> None: """Execute callback function when button is clicked and updates the items and widget. """ self.parent_layout.update_dataitems() callback = self.item.get_prop_value("display", "callback") self.cb_value = callback( self.item.instance, self.item.item, self.cb_value, self.button.parent() ) self.set() self.parent_layout.update_widgets() self.notify_value_change() class DataSetWidget(AbstractDataSetWidget): """ DataSet widget """ @property @abstractmethod def klass(self) -> type: """Return the class of the dataset Returns: class of the dataset """ pass def __init__( self, item: "DataItemVariable", parent_layout: "DataSetEditLayout" ) -> None: super().__init__(item, parent_layout) self.dataset = self.klass() # Cr├йation du layout contenant les champs d'├йdition du signal embedded = item.get_prop_value("display", "embedded", False) if not embedded: self.group = QGroupBox(item.get_prop_value("display", "label")) else: self.group = QFrame() self.layout = QGridLayout() self.group.setLayout(self.layout) EditLayoutClass = parent_layout.__class__ self.edit = EditLayoutClass( self.parent_layout.parent, self.dataset, self.layout ) def get(self) -> None: """Update widget contents from data item value""" self.get_dataset() for widget in self.edit.widgets: widget.get() def set(self) -> None: """Update data item value from widget contents""" for widget in self.edit.widgets: widget.set() self.set_dataset() def get_dataset(self) -> None: """update's internal parameter representation from the item's stored value default behavior uses update_dataset and assumes internal dataset class is the same as item's value class""" item = self.item.get() update_dataset(self.dataset, item) def set_dataset(self) -> None: """update the item's value from the internal data representation default behavior uses restore_dataset and assumes internal dataset class is the same as item's value class""" item = self.item.get() restore_dataset(self.dataset, item) def place_on_grid( self, layout: "QGridLayout", row: int, label_column: int, widget_column: int, row_span: int = 1, column_span: int = 1, ) -> None: """Place widget on layout at specified position Args: layout: parent layout row: row index label_column: column index for label widget_column: column index for widget row_span: number of rows to span column_span: number of columns to span """ layout.addWidget(self.group, row, label_column, row_span, column_span + 1) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/dataset/qtwidgets.py0000644000175100001770000010571214654363416020217 0ustar00runnerdocker# # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """ Qt widgets for data sets ------------------------ This module provides a set of widgets to edit and show data sets, using ready-to-use dialog boxes, layouts and group boxes. Dialog boxes ^^^^^^^^^^^^ .. autoclass:: DataSetEditDialog :show-inheritance: :members: .. autoclass:: DataSetShowDialog :show-inheritance: :members: .. autoclass:: DataSetGroupEditDialog :show-inheritance: :members: Layouts ^^^^^^^ .. autoclass:: DataSetEditLayout :show-inheritance: :members: .. autoclass:: DataSetShowLayout :show-inheritance: :members: Group boxes ^^^^^^^^^^^ .. autoclass:: DataSetShowGroupBox :show-inheritance: :members: .. autoclass:: DataSetEditGroupBox :show-inheritance: :members: """ from __future__ import annotations from typing import TYPE_CHECKING, Any, Generic from qtpy.compat import getopenfilename, getopenfilenames, getsavefilename from qtpy.QtCore import ( QAbstractTableModel, QModelIndex, QObject, QRect, QSize, Qt, Signal, ) from qtpy.QtGui import QBrush, QColor, QCursor, QIcon, QPainter, QPicture from qtpy.QtWidgets import ( QAbstractButton, QApplication, QDialog, QDialogButtonBox, QGridLayout, QGroupBox, QLabel, QMessageBox, QPushButton, QSpacerItem, QTableView, QTabWidget, QVBoxLayout, QWidget, ) from guidata.config import CONF, _ from guidata.configtools import get_font, get_icon from guidata.dataset.datatypes import ( AnyDataSet, BeginGroup, DataItem, DataItemVariable, DataSet, DataSetGroup, EndGroup, GroupItem, TabGroupItem, ) from guidata.qthelpers import win32_fix_title_bar_background if TYPE_CHECKING: from typing import Callable class DataSetEditDialog(QDialog): """Dialog box for DataSet editing Args: instance: DataSet instance to edit icon: icon name (default: "guidata.svg") parent: parent widget apply: function called when Apply button is clicked wordwrap: if True, comment text is wordwrapped size: dialog size (default: None) """ def __init__( self, instance: DataSet | DataSetGroup, icon: str | QIcon = "", parent: QWidget | None = None, apply: Callable | None = None, wordwrap: bool = True, size: QSize | tuple[int, int] | None = None, ) -> None: super().__init__(parent) win32_fix_title_bar_background(self) self.wordwrap = wordwrap self.apply_func = apply self._layout = QVBoxLayout() if instance.get_comment(): label = QLabel(instance.get_comment()) label.setWordWrap(wordwrap) self._layout.addWidget(label) self.instance = instance self.edit_layout: list[DataSetEditLayout] = [] self.setup_instance(instance) if apply is not None: apply_button = QDialogButtonBox.Apply else: apply_button = QDialogButtonBox.NoButton if not instance.is_readonly(): bbox = QDialogButtonBox( QDialogButtonBox.Ok | QDialogButtonBox.Cancel | apply_button ) self.bbox = bbox bbox.accepted.connect(self.accept) bbox.rejected.connect(self.reject) bbox.clicked.connect(self.button_clicked) self._layout.addWidget(bbox) self.setLayout(self._layout) if parent is None: if not isinstance(icon, QIcon): icon = get_icon(icon, default="guidata.svg") self.setWindowIcon(icon) self.setModal(True) self.setWindowTitle(instance.get_title()) if size is not None: if isinstance(size, QSize): self.resize(size) else: self.resize(*size) def button_clicked(self, button: QAbstractButton) -> None: """Handle button click Args: button: button that was clicked """ role = self.bbox.buttonRole(button) if ( role == QDialogButtonBox.ApplyRole # type:ignore and self.apply_func is not None ) and self.check(): for edl in self.edit_layout: edl.accept_changes() self.apply_func(self.instance) def setup_instance(self, instance: Any) -> None: """Construct main layout Args: instance: DataSet instance to edit """ grid = QGridLayout() grid.setAlignment(Qt.AlignTop) # type:ignore self._layout.addLayout(grid) self.edit_layout.append(self.layout_factory(instance, grid)) def layout_factory(self, instance: DataSet, grid: QGridLayout) -> DataSetEditLayout: """A factory method that produces instances of DataSetEditLayout or derived classes (see DataSetShowDialog) Args: instance: DataSet instance to edit grid: grid layout Returns: DataSetEditLayout instance """ return DataSetEditLayout(self, instance, grid) def child_title(self, item: DataItemVariable) -> str: """Return data item title combined with QApplication title Args: item: data item Returns: title """ app_name = QApplication.applicationName() if not app_name: app_name = self.instance.get_title() return f"{app_name} - {item.label()}" def check(self) -> bool: """Check input of all widgets Returns: True if all widgets are valid """ is_ok = True for edl in self.edit_layout: if not edl.check_all_values(): is_ok = False if not is_ok: QMessageBox.warning( self, self.instance.get_title(), _("Some required entries are incorrect") + "\n" + _("Please check highlighted fields."), ) return False return True def accept(self) -> None: """Validate inputs""" if self.check(): for edl in self.edit_layout: edl.accept_changes() QDialog.accept(self) class DataSetGroupEditDialog(DataSetEditDialog): """Tabbed dialog box for DataSet editing Args: instance: DataSetGroup instance to edit icon: icon name (default: "guidata.svg") parent: parent widget apply: function called when Apply button is clicked wordwrap: if True, comment text is wordwrapped size: dialog size (default: None) """ def setup_instance(self, instance: DataSetGroup) -> None: """Construct main layout Args: instance: DataSetGroup instance to edit """ assert isinstance(instance, DataSetGroup) tabs = QTabWidget() # tabs.setUsesScrollButtons(False) self._layout.addWidget(tabs) for dataset in instance.datasets: layout = QVBoxLayout() layout.setAlignment(Qt.AlignmentFlag.AlignTop) if dataset.get_comment(): label = QLabel(dataset.get_comment()) label.setWordWrap(self.wordwrap) layout.addWidget(label) grid = QGridLayout() self.edit_layout.append(self.layout_factory(dataset, grid)) layout.addLayout(grid) page = QWidget() page.setLayout(layout) if dataset.get_icon(): tabs.addTab(page, get_icon(dataset.get_icon()), dataset.get_title()) else: tabs.addTab(page, dataset.get_title()) class DataSetEditLayout(Generic[AnyDataSet]): """Layout in which data item widgets are placed Args: parent: parent widget instance: DataSet instance to edit layout: grid layout items: list of data items first_line: first line of grid layout change_callback: function called when any widget's value has changed """ _widget_factory: dict[Any, Any] = {} @classmethod def register(cls: type, item_type: type, factory: Any) -> None: """Register a factory for a new item_type Args: item_type: item type factory: factory function """ cls._widget_factory[item_type] = factory def __init__( self, parent: QWidget | None, instance: AnyDataSet, layout: QGridLayout, items: list[DataItem] | None = None, first_line: int = 0, change_callback: Callable | None = None, ) -> None: self.parent = parent self.instance = instance self.layout = layout self.first_line = first_line self.change_callback = change_callback self.widgets: list[AbstractDataSetWidget] = [] # self.linenos = {} # prochaine ligne ├а remplir par colonne self.items_pos: dict[DataItem, list[int]] = {} if not items: items = self.instance._items items = self.transform_items(items) # type:ignore self.setup_layout(items) def transform_items(self, items: list[DataItem]) -> list[DataItem]: """Handle group of items: transform items into a GroupItem instance if they are located between BeginGroup and EndGroup Args: items: list of data items Returns: list of data items """ item_lists: Any = [[]] for item in items: if isinstance(item, BeginGroup): group_item = item.get_group() item_lists[-1].append(group_item) item_lists.append(group_item.group) elif isinstance(item, EndGroup): item_lists.pop() else: item_lists[-1].append(item) assert len(item_lists) == 1 return item_lists[-1] def check_all_values(self) -> bool: """Check input of all widgets Returns: True if all widgets are valid """ for widget in self.widgets: if widget.is_active() and not widget.check(): return False return True def accept_changes(self) -> None: """Accept changes made to widget inputs""" self.update_dataitems() def setup_layout(self, items: list[DataItem]) -> None: """Place items on layout Args: items: list of data items """ def last_col(col, span): """Return last column (which depends on column span)""" if not span: return col return col + span - 1 colmax = max( last_col( item.get_prop("display", "col"), item.get_prop("display", "colspan") ) for item in items ) # Check if specified rows are consistent sorted_items: list[DataItem | None] = [None] * len(items) rows = [] other_items = [] for item in items: row = item.get_prop("display", "row") if row is not None: if row in rows: raise ValueError( f"Duplicate row index ({row}) for item {item.get_name()}" ) if row < 0 or row >= len(items): raise ValueError( f"Out of range row index ({row}) for item {item.get_name()}" ) rows.append(row) sorted_items[row] = item else: other_items.append(item) for idx, item in enumerate(sorted_items[:]): # type:ignore if item is None: sorted_items[idx] = other_items.pop(0) self.items_pos = {} line = self.first_line - 1 last_item = [-1, 0, colmax] for item in sorted_items: # type:ignore col = item.get_prop("display", "col") colspan = item.get_prop("display", "colspan") if colspan is None: colspan = colmax - col + 1 if col <= last_item[1]: # on passe ├а la ligne si la colonne de debut de cet item # est avant la colonne de debut de l'item pr├йc├йdent line += 1 else: last_item[2] = col - last_item[1] last_item = [line, col, colspan] self.items_pos[item] = last_item for item in items: hide = item.get_prop_value("display", self.instance, "hide", False) if hide: continue widget = self.build_widget(item) self.add_row(widget) self.refresh_widgets() def build_widget(self, item: DataItem) -> DataSetShowWidget: """Build widget for item Args: item: data item Returns: widget """ factory = self._widget_factory[type(item)] widget = factory(item.bind(self.instance), self) self.widgets.append(widget) return widget def add_row(self, widget: DataSetShowWidget) -> None: """Add widget to row Args: widget: widget to add """ item = widget.item line, col, span = self.items_pos[item.item] if col > 0: self.layout.addItem(QSpacerItem(20, 1), line, col * 3 - 1) widget.place_on_grid(self.layout, line, col * 3, col * 3 + 1, 1, 3 * span - 2) try: widget.get() except Exception: print("Error building item :", item.item.get_name()) raise def refresh_widgets(self) -> None: """Refresh the status of all widgets""" for widget in self.widgets: widget.set_state() def update_dataitems(self) -> None: """Refresh the content of all data items""" for widget in self.widgets: if widget.is_active(): widget.set() def update_widgets( self, except_this_one: QWidget | AbstractDataSetWidget | None = None ) -> None: """Refresh the content of all widgets Args: except_this_one: widget to skip """ for widget in self.widgets: if widget is not except_this_one: widget.get() def widget_value_changed(self) -> None: """Method called when any widget's value has changed""" if self.change_callback is not None: self.change_callback() from guidata.dataset.dataitems import ( # noqa: E402 BoolItem, ButtonItem, ChoiceItem, ColorItem, DateItem, DateTimeItem, DictItem, DirectoryItem, FileOpenItem, FileSaveItem, FilesOpenItem, FloatArrayItem, FloatItem, ImageChoiceItem, IntItem, MultipleChoiceItem, StringItem, TextItem, ) # Enregistrement des correspondances avec les widgets from guidata.dataset.qtitemwidgets import ( # noqa: E402 AbstractDataSetWidget, ButtonWidget, CheckBoxWidget, ChoiceWidget, ColorWidget, DateTimeWidget, DateWidget, DirectoryWidget, FileWidget, FloatArrayWidget, FloatSliderWidget, GroupWidget, LineEditWidget, MultipleChoiceWidget, SliderWidget, TabGroupWidget, TextEditWidget, ) DataSetEditLayout.register(GroupItem, GroupWidget) DataSetEditLayout.register(TabGroupItem, TabGroupWidget) DataSetEditLayout.register(FloatItem, LineEditWidget) DataSetEditLayout.register(StringItem, LineEditWidget) DataSetEditLayout.register(TextItem, TextEditWidget) DataSetEditLayout.register(IntItem, SliderWidget) DataSetEditLayout.register(FloatItem, FloatSliderWidget) DataSetEditLayout.register(BoolItem, CheckBoxWidget) DataSetEditLayout.register(DateItem, DateWidget) DataSetEditLayout.register(DateTimeItem, DateTimeWidget) DataSetEditLayout.register(ColorItem, ColorWidget) DataSetEditLayout.register( FileOpenItem, lambda item, parent: FileWidget(item, parent, getopenfilename) ) DataSetEditLayout.register( FilesOpenItem, lambda item, parent: FileWidget(item, parent, getopenfilenames) ) DataSetEditLayout.register( FileSaveItem, lambda item, parent: FileWidget(item, parent, getsavefilename) ) DataSetEditLayout.register(DirectoryItem, DirectoryWidget) DataSetEditLayout.register(ChoiceItem, ChoiceWidget) DataSetEditLayout.register(ImageChoiceItem, ChoiceWidget) DataSetEditLayout.register(MultipleChoiceItem, MultipleChoiceWidget) DataSetEditLayout.register(FloatArrayItem, FloatArrayWidget) DataSetEditLayout.register(ButtonItem, ButtonWidget) DataSetEditLayout.register(DictItem, ButtonWidget) LABEL_CSS = """ QLabel { font-weight: bold; color: blue } QLabel:disabled { font-weight: bold; color: grey } """ class DataSetShowWidget(AbstractDataSetWidget): """Read-only base widget Args: item: data item variable (``DataItemVariable``) parent_layout: parent layout (``DataSetEditLayout``) """ READ_ONLY = True def __init__( self, item: DataItemVariable, parent_layout: DataSetEditLayout ) -> None: AbstractDataSetWidget.__init__(self, item, parent_layout) self.group = QLabel() wordwrap = item.get_prop_value("display", "wordwrap", False) self.group.setWordWrap(wordwrap) self.group.setToolTip(item.get_help()) self.group.setStyleSheet(LABEL_CSS) self.group.setTextInteractionFlags(Qt.TextSelectableByMouse) # type:ignore def get(self) -> None: """Update widget contents from data item value""" self.set_state() text = self.item.get_string_value() self.group.setText(text) def set(self) -> None: """Update data item value from widget contents""" # Do nothing: read-only widget pass class ShowColorWidget(DataSetShowWidget): """Read-only color item widget Args: item: data item variable (``DataItemVariable``) parent_layout: parent layout (``DataSetEditLayout``) """ def __init__( self, item: DataItemVariable, parent_layout: DataSetEditLayout ) -> None: DataSetShowWidget.__init__(self, item, parent_layout) self.picture: QPicture | None = None def get(self) -> None: """Update widget contents from data item value""" value = self.item.get() if value is not None: color = QColor(value) self.picture = QPicture() painter = QPainter() painter.begin(self.picture) painter.fillRect(QRect(0, 0, 60, 20), QBrush(color)) painter.end() self.group.setPicture(self.picture) class ShowBooleanWidget(DataSetShowWidget): """Read-only bool item widget Args: item: data item variable (``DataItemVariable``) parent_layout: parent layout (``DataSetEditLayout``) """ def place_on_grid( self, layout: QGridLayout, row: int, label_column: int, widget_column, row_span: int = 1, column_span: int = 1, ): """Place widget on layout at specified position Args: layout: parent layout row: row index label_column: column index for label widget_column: column index for widget row_span: number of rows to span column_span: number of columns to span """ if not self.item.get_prop_value("display", "label"): widget_column = label_column column_span += 1 else: self.place_label(layout, row, label_column) layout.addWidget(self.group, row, widget_column, row_span, column_span) def get(self) -> None: """Update widget contents from data item value""" DataSetShowWidget.get(self) text = self.item.get_prop_value("display", "text") self.group.setText(text) font = self.group.font() value = self.item.get() state = bool(value) font.setStrikeOut(not state) self.group.setFont(font) self.group.setEnabled(state) class DataSetShowLayout(DataSetEditLayout): """Read-only layout Args: parent: parent widget instance: DataSet instance to edit layout: grid layout items: list of data items first_line: first line of grid layout change_callback: function called when any widget's value has changed """ _widget_factory = {} class DataSetShowDialog(DataSetEditDialog): """Read-only dialog box Args: instance: DataSet instance to edit icon: icon name (default: "guidata.svg") parent: parent widget apply: function called when Apply button is clicked wordwrap: if True, comment text is wordwrapped size: dialog size (default: None) """ def layout_factory(self, instance: DataSet, grid: QGridLayout) -> DataSetShowLayout: """A factory method that produces instances of DataSetEditLayout or derived classes (see DataSetShowDialog) Args: instance: DataSet instance to edit grid: grid layout Returns: DataSetEditLayout instance """ return DataSetShowLayout(self, instance, grid) DataSetShowLayout.register(GroupItem, GroupWidget) DataSetShowLayout.register(TabGroupItem, TabGroupWidget) DataSetShowLayout.register(FloatItem, DataSetShowWidget) DataSetShowLayout.register(StringItem, DataSetShowWidget) DataSetShowLayout.register(TextItem, DataSetShowWidget) DataSetShowLayout.register(IntItem, DataSetShowWidget) DataSetShowLayout.register(BoolItem, ShowBooleanWidget) DataSetShowLayout.register(DateItem, DataSetShowWidget) DataSetShowLayout.register(DateTimeItem, DataSetShowWidget) DataSetShowLayout.register(ColorItem, ShowColorWidget) DataSetShowLayout.register(FileOpenItem, DataSetShowWidget) DataSetShowLayout.register(FilesOpenItem, DataSetShowWidget) DataSetShowLayout.register(FileSaveItem, DataSetShowWidget) DataSetShowLayout.register(DirectoryItem, DataSetShowWidget) DataSetShowLayout.register(ChoiceItem, DataSetShowWidget) DataSetShowLayout.register(ImageChoiceItem, DataSetShowWidget) DataSetShowLayout.register(MultipleChoiceItem, DataSetShowWidget) DataSetShowLayout.register(FloatArrayItem, DataSetShowWidget) DataSetShowLayout.register(DictItem, DataSetShowWidget) class DataSetShowGroupBox(Generic[AnyDataSet], QGroupBox): """Group box widget showing a read-only DataSet Args: label: group box label (string) klass: guidata.DataSet class wordwrap: if True, comment text is wordwrapped kwargs: keyword arguments passed to DataSet constructor """ def __init__( self, label: QLabel | str, klass: type[AnyDataSet], wordwrap: bool = False, **kwargs, ) -> None: QGroupBox.__init__(self, label) self.apply_button: QPushButton | None = None self.klass = klass self.dataset: AnyDataSet = klass(**kwargs) self._layout = QVBoxLayout() if self.dataset.get_comment(): label = QLabel(self.dataset.get_comment()) label.setWordWrap(wordwrap) self._layout.addWidget(label) self.grid_layout = QGridLayout() self._layout.addLayout(self.grid_layout) self.setLayout(self._layout) self.edit = self.get_edit_layout() def get_edit_layout(self) -> DataSetEditLayout[AnyDataSet]: """Return edit layout Returns: edit layout """ return DataSetShowLayout(self, self.dataset, self.grid_layout) def get(self) -> None: """Update group box contents from data item values""" for widget in self.edit.widgets: widget.build_mode = True widget.get() widget.set_state() widget.build_mode = False if self.apply_button is not None: self.apply_button.setVisible(not self.dataset.is_readonly()) class DataSetEditGroupBox(DataSetShowGroupBox[AnyDataSet]): """Group box widget including a DataSet Args: label: group box label (string) klass: guidata.DataSet class button_text: text of apply button (default: "Apply") button_icon: icon of apply button (default: "apply.png") show_button: if True, show apply button (default: True) wordwrap: if True, comment text is wordwrapped kwargs: keyword arguments passed to DataSet constructor When the "Apply" button is clicked, the :py:attr:`SIG_APPLY_BUTTON_CLICKED` signal is emitted. """ #: Signal emitted when Apply button is clicked SIG_APPLY_BUTTON_CLICKED = Signal() def __init__( self, label: QLabel | str, klass: type[AnyDataSet], button_text: str | None = None, button_icon: QIcon | str | None = None, show_button: bool = True, wordwrap: bool = False, **kwargs, ): DataSetShowGroupBox.__init__(self, label, klass, wordwrap=wordwrap, **kwargs) if show_button: if button_text is None: button_text = _("Apply") if button_icon is None: button_icon = get_icon("apply.png") elif isinstance(button_icon, str): button_icon = get_icon(button_icon) self.apply_button = applyb = QPushButton(button_icon, button_text, self) applyb.clicked.connect(self.set) # type:ignore layout = self.edit.layout layout.addWidget( applyb, layout.rowCount(), 0, 1, -1, Qt.AlignRight, # type:ignore ) def get_edit_layout(self) -> DataSetEditLayout[AnyDataSet]: """Return edit layout Returns: edit layout """ return DataSetEditLayout( self, self.dataset, self.grid_layout, change_callback=self.change_callback ) def change_callback(self) -> None: """Method called when any widget's value has changed""" self.set_apply_button_state(True) def set(self, check: bool = True) -> None: """Update data item values from layout contents Args: check: if True, check input of all widgets """ for widget in self.edit.widgets: if widget.is_active() and (not check or widget.check()): widget.set() self.SIG_APPLY_BUTTON_CLICKED.emit() self.set_apply_button_state(False) def set_apply_button_state(self, state: bool) -> None: """Set apply button enable/disable state Args: state: if True, enable apply button """ if self.apply_button is not None: self.apply_button.setEnabled(state) def child_title(self, item: DataItemVariable) -> str: """Return data item title combined with QApplication title Args: item: data item Returns: title """ app_name = QApplication.applicationName() if not app_name: app_name = str(self.title()) return f"{app_name} - {item.label()}" class DataSetTableModel(QAbstractTableModel, Generic[AnyDataSet]): """DataSet Table Model. Args: datasets: list of DataSet object. The Datasets must all contain identical \ DataItem(s) (content can vary) so they can be decomposed into table \ columns. parent: Parent. Defaults to None. """ def __init__( self, datasets: list[AnyDataSet], parent: QObject | None = None ) -> None: super().__init__(parent) self.datasets = datasets ref_col_names = self.datasets[0].get_items(copy=False) self._col_names = tuple(item.get_name() for item in ref_col_names) self._col_count = len(self._col_names) self.validate_datasets() self._row_names = tuple(dataset.get_title() for dataset in datasets) self._row_count = len(self._row_names) self.item_pointers = [dataset.get_items() for dataset in datasets] def validate_datasets(self): """Checks that all datasets present in the list of datasets are of the same type. Raises: ValueError: signals that the datasets are not of the same type. """ reference_instance = type(self.datasets[0]) for dataset in self.datasets[1:]: if not isinstance(dataset, reference_instance): raise ValueError( "All datasets must be of the same type. " f"Expected {reference_instance}, got {type(dataset)}" ) def rowCount(self, _parent: QModelIndex | None = None) -> int: """Number of rows Args: parent: Parent QModelIndex (not used). Defaults to None. Returns: the number of rows in the table """ return self._row_count def columnCount(self, _parent: QModelIndex | None = None) -> int: """Number of columns Args: parent: Parent QModelIndex (not used). Defaults to None. Returns: the number of columns in the table """ return self._col_count def headerData( self, section: int, orientation: Qt.Orientation, role: Qt.ItemDataRole = Qt.ItemDataRole.DisplayRole, ) -> Any: """Returns the data for the given role and section in the header with the specified orientation. Args: section: section from which to retrieve the data orientation: orientation from which to retrieve the data (row or columns) role: Flag used to chose the return value. Defaults to Qt.ItemDataRole.DisplayRole. Returns: _description_ """ if ( orientation == Qt.Orientation.Horizontal and role == Qt.ItemDataRole.DisplayRole ): return self._col_names[section] if ( orientation == Qt.Orientation.Vertical and role == Qt.ItemDataRole.DisplayRole ): return self._row_names[section] return super().headerData(section, orientation, role) def data(self, index: QModelIndex, role=Qt.ItemDataRole.DisplayRole) -> Any: """Returns the table data stored under the given role for the item referred to by the index. Args: index: index of the item to retrieve (e.g. row and column) role: Flag that determines the type of data requested. Defaults to Qt.ItemDataRole.DisplayRole. Returns: the data stored under the given role for the item referred to by the index. """ if role == Qt.ItemDataRole.DisplayRole: item = self.item_pointers[index.row()][index.column()] return item.get_string_value(self.datasets[index.row()]) if role == Qt.ItemDataRole.TextAlignmentRole: return int(Qt.AlignCenter | Qt.AlignVCenter) # type: ignore if role == Qt.ItemDataRole.FontRole: return get_font(CONF, "arrayeditor", "font") return None class DatasetTableView(QTableView): """Array view class""" def __init__(self, model: DataSetTableModel, parent: QWidget | None = None) -> None: QTableView.__init__(self, parent) self.setModel(model) total_width = 0 self.shape = (model.rowCount(), model.columnCount()) for k in range(self.shape[1]): total_width += self.columnWidth(k) if viewport := self.viewport(): viewport.resize(min(total_width, 1024), self.height()) self.doubleClicked.connect(self.open_dataset_dialog) self.setSelectionMode(self.SelectionMode.SingleSelection) self.setSelectionBehavior(self.SelectionBehavior.SelectRows) def resize_to_contents(self): """Resize cells to contents""" QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor)) self.resizeColumnsToContents() QApplication.restoreOverrideCursor() def open_dataset_dialog(self, index: QModelIndex) -> None: """Opens a new dialog box to edit the dataset Args: index: index of the dataset to edit """ if isinstance((model := self.model()), DataSetTableModel): model.datasets[index.row()].edit(self) class DataSetGroupTableEditDialog(QDialog): """DataSetGroup Table Edit Dialog use to edit DataSet in a DataSetGroup object using a table where each row represents a dataset""" def __init__( self, instance: DataSetGroup, icon: str | QIcon = "", parent: QWidget | None = None, apply: Callable | None = None, wordwrap: bool = True, size: QSize | tuple[int, int] | None = None, ): super().__init__(parent) win32_fix_title_bar_background(self) self.wordwrap = wordwrap self.apply_func = apply self._layout = QVBoxLayout() if instance.get_comment(): label = QLabel(instance.get_comment()) label.setWordWrap(wordwrap) self._layout.addWidget(label) self.instance = instance self.setup_instance(instance) self.setLayout(self._layout) if parent is None: if not isinstance(icon, QIcon): icon = get_icon(icon, default="guidata.svg") self.setWindowIcon(icon) # type:ignore self.setModal(True) self.setWindowTitle(instance.get_title()) if size is not None: if isinstance(size, QSize): self.resize(size) else: self.resize(*size) def setup_instance(self, instance: DataSetGroup) -> None: """ Setup DataSetGroupTableEditDialog: return False if data is not supported, True otherwise. Constructs main layout Args: instance: DataSet instance to edit """ grid = QGridLayout() grid.setAlignment(Qt.AlignTop) # type:ignore self._layout.addLayout(grid) table_model = DataSetTableModel(instance.datasets, parent=self) self._layout.addWidget(DatasetTableView(table_model, parent=self)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/dataset/textedit.py0000644000175100001770000000146414654363416020035 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """ Text visitor for DataItem objects (for test purpose only) """ def prompt(item): """Get item value""" return input(item.get_prop("display", "label") + " ? ") class TextEditVisitor: """Text visitor""" def __init__(self, instance): self.instance = instance def visit_generic(self, item): """Generic visitor""" while True: value = prompt(item) item.set_from_string(self.instance, value) if item.check_item(self.instance): break print("Incorrect value!") visit_FloatItem = visit_generic visit_IntItem = visit_generic visit_StringItem = visit_generic ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/env.py0000644000175100001770000002211414654363416015341 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """ Execution environmnent utilities """ from __future__ import annotations import argparse import enum import os import pprint import sys from contextlib import contextmanager from typing import Any, Generator DEBUG = os.environ.get("DEBUG", "").lower() in ("1", "true") PARSE = os.environ.get("GUIDATA_PARSE_ARGS", "").lower() in ("1", "true") class VerbosityLevels(enum.Enum): """Print verbosity levels (for testing purpose)""" QUIET = "quiet" NORMAL = "normal" DEBUG = "debug" class ExecEnv: """Object representing execution environment""" UNATTENDED_ARG = "unattended" ACCEPT_DIALOGS_ARG = "accept_dialogs" VERBOSE_ARG = "verbose" SCREENSHOT_ARG = "screenshot" DELAY_ARG = "delay" UNATTENDED_ENV = "GUIDATA_UNATTENDED" ACCEPT_DIALOGS_ENV = "GUIDATA_ACCEPT_DIALOGS" VERBOSE_ENV = "GUIDATA_VERBOSE" SCREENSHOT_ENV = "GUIDATA_SCREENSHOT" DELAY_ENV = "GUIDATA_DELAY" def __init__(self): if PARSE: self.parse_args() if self.unattended: # Do not execute this code in production # Check that calling `to_dict` do not raise any exception self.to_dict() def iterate_over_attrs_envvars(self) -> Generator[tuple[str, str], None, None]: """Iterate over CDL environment variables Yields: A tuple (attribute name, environment variable name) """ for name in dir(self): if name.endswith("_ENV"): envvar: str = getattr(self, name) attrname = "_".join(name.split("_")[:-1]).lower() yield attrname, envvar def to_dict(self): """Return a dictionary representation of the object""" # The list of properties match the list of environment variable attribute names, # modulo the "_ENV" suffix: props = [attrname for attrname, _envvar in self.iterate_over_attrs_envvars()] # Check that all properties are defined in the class and that they are # really properties: for prop in props: assert hasattr( self, prop ), f"Property {prop} is not defined in class {self.__class__.__name__}" assert isinstance( getattr(self.__class__, prop), property ), f"Attribute {prop} is not a property in class {self.__class__.__name__}" # Return a dictionary with the properties as keys and their values as values: return {p: getattr(self, p) for p in props} def __str__(self): """Return a string representation of the object""" return pprint.pformat(self.to_dict()) @staticmethod def __get_mode(env): """Get mode value""" env_val = os.environ.get(env) if env_val is None: return False return env_val.lower() in ("1", "true", "yes", "on", "enable", "enabled") @staticmethod def __set_mode(env, value): """Set mode value""" if env in os.environ: os.environ.pop(env) if value: os.environ[env] = "1" @property def unattended(self): """Get unattended value""" return self.__get_mode(self.UNATTENDED_ENV) @unattended.setter def unattended(self, value): """Set unattended value""" self.__set_mode(self.UNATTENDED_ENV, value) @property def accept_dialogs(self): """Whether to accept dialogs in unattended mode""" return self.__get_mode(self.ACCEPT_DIALOGS_ENV) @accept_dialogs.setter def accept_dialogs(self, value): """Set whether to accept dialogs in unattended mode""" self.__set_mode(self.ACCEPT_DIALOGS_ENV, value) @property def screenshot(self): """Get screenshot value""" return self.__get_mode(self.SCREENSHOT_ENV) @screenshot.setter def screenshot(self, value): """Set screenshot value""" self.__set_mode(self.SCREENSHOT_ENV, value) @property def verbose(self): """Get verbosity level""" env_val = os.environ.get(self.VERBOSE_ENV) if env_val in (None, ""): return VerbosityLevels.NORMAL.value return env_val.lower() @verbose.setter def verbose(self, value): """Set verbosity level""" os.environ[self.VERBOSE_ENV] = value @property def delay(self): """Delay (ms) before quitting application in unattended mode""" try: return int(os.environ.get(self.DELAY_ENV)) except (TypeError, ValueError): return 0 @delay.setter def delay(self, value: int): """Set delay (ms) before quitting application in unattended mode""" os.environ[self.DELAY_ENV] = str(value) def parse_args(self): """Parse command line arguments""" parser = argparse.ArgumentParser(description="Run test") parser.add_argument( "--" + self.UNATTENDED_ARG, action="store_true", help="non-interactive mode", default=None, ) parser.add_argument( "--" + self.ACCEPT_DIALOGS_ARG, action="store_true", help="accept dialogs in unattended mode", default=None, ) parser.add_argument( "--" + self.SCREENSHOT_ARG, action="store_true", help="automatic screenshots", default=None, ) parser.add_argument( "--" + self.DELAY_ARG, type=int, default=0, help="delay (ms) before quitting application in unattended mode", ) parser.add_argument( "--" + self.VERBOSE_ARG, choices=[lvl.value for lvl in VerbosityLevels], required=False, default=VerbosityLevels.NORMAL.value, help="verbosity level: for debugging/testing purpose", ) args, _unknown = parser.parse_known_args() self.set_env_from_args(args) def set_env_from_args(self, args): """Set appropriate environment variables""" for argname in ( self.UNATTENDED_ARG, self.ACCEPT_DIALOGS_ARG, self.SCREENSHOT_ARG, self.VERBOSE_ARG, self.DELAY_ARG, ): argvalue = getattr(args, argname) if argvalue is not None: setattr(self, argname, argvalue) def log(self, source: Any, *objects: Any) -> None: """Log text on screen Args: source: object from which the log is issued *objects: objects to log """ if DEBUG or self.verbose == VerbosityLevels.DEBUG.value: print(str(source) + ":", *objects) def print(self, *objects, sep=" ", end="\n", file=sys.stdout, flush=False): """Print in file, depending on verbosity level""" # print(f"unattended={self.unattended} ; verbose={self.verbose} ; ") # print(f"screenshot={self.screenshot}; delay={self.delay}") if self.verbose != VerbosityLevels.QUIET.value or DEBUG: print(*objects, sep=sep, end=end, file=file, flush=flush) def pprint( self, obj, stream=None, indent=1, width=80, depth=None, compact=False, sort_dicts=True, ): """Pretty-print in stream, depending on verbosity level""" if self.verbose != VerbosityLevels.QUIET.value or DEBUG: pprint.pprint( obj, stream=stream, indent=indent, width=width, depth=depth, compact=compact, sort_dicts=sort_dicts, ) @contextmanager def context( self, unattended=None, accept_dialogs=None, screenshot=None, delay=None, verbose=None, ) -> Generator[None, None, None]: """Return a context manager that sets some execenv properties at enter, and restores them at exit. This is useful to run some code in a controlled environment, for example to accept dialogs in unattended mode, and restore the previous value at exit. Args: unattended: whether to run in unattended mode accept_dialogs: whether to accept dialogs in unattended mode screenshot: whether to take screenshots delay: delay (ms) before quitting application in unattended mode verbose: verbosity level .. note:: If a passed value is None, the corresponding property is not changed. """ old_values = self.to_dict() new_values = { "unattended": unattended, "accept_dialogs": accept_dialogs, "screenshot": screenshot, "delay": delay, "verbose": verbose, } for key, value in new_values.items(): if value is not None: setattr(self, key, value) try: yield finally: for key, value in old_values.items(): setattr(self, key, value) execenv = ExecEnv() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1722935059.1647055 guidata-3.6.2/guidata/external/0000755000175100001770000000000014654363423016017 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/external/__init__.py0000644000175100001770000000000214654363416020122 0ustar00runnerdocker# ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1722935059.1647055 guidata-3.6.2/guidata/external/darkdetect/0000755000175100001770000000000014654363423020131 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/external/darkdetect/__init__.py0000644000175100001770000000234314654363416022246 0ustar00runnerdocker# ----------------------------------------------------------------------------- # Copyright (C) 2019 Alberto Sottile # # Distributed under the terms of the 3-clause BSD License. # ----------------------------------------------------------------------------- __version__ = "0.5.2" # Patched (see pull request below) import sys import platform if sys.platform == "darwin": from distutils.version import LooseVersion as V if V(platform.mac_ver()[0]) < V("10.14"): from ._dummy import * else: from ._mac_detect import * del V # Patch: https://github.com/albertosottile/darkdetect/pull/21 elif ( sys.platform == "win32" and platform.release().isdigit() and int(platform.release()) >= 10 ): # Checks if running Windows 10 version 10.0.14393 (Anniversary Update) OR HIGHER. The getwindowsversion method returns a tuple. # The third item is the build number that we can use to check if the user has a new enough version of Windows. winver = int(platform.version().split(".")[2]) if winver >= 14393: from ._windows_detect import * else: from ._dummy import * elif sys.platform == "linux": from ._linux_detect import * else: from ._dummy import * del sys, platform ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/external/darkdetect/_dummy.py0000644000175100001770000000055314654363416022002 0ustar00runnerdocker#----------------------------------------------------------------------------- # Copyright (C) 2019 Alberto Sottile # # Distributed under the terms of the 3-clause BSD License. #----------------------------------------------------------------------------- def theme(): return None def isDark(): return None def isLight(): return None ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/external/darkdetect/_linux_detect.py0000644000175100001770000000153414654363416023336 0ustar00runnerdocker#----------------------------------------------------------------------------- # Copyright (C) 2019 Alberto Sottile, Eric Larson # # Distributed under the terms of the 3-clause BSD License. #----------------------------------------------------------------------------- import subprocess def theme(): # Here we just triage to GTK settings for now try: out = subprocess.run( ['gsettings', 'get', 'org.gnome.desktop.interface', 'gtk-theme'], capture_output=True) stdout = out.stdout.decode() except Exception: return 'Light' # we have a string, now remove start and end quote theme = stdout.lower().strip()[1:-1] if theme.endswith('-dark'): return 'Dark' else: return 'Light' def isDark(): return theme() == 'Dark' def isLight(): return theme() == 'Light' ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/external/darkdetect/_mac_detect.py0000644000175100001770000000413614654363416022740 0ustar00runnerdocker#----------------------------------------------------------------------------- # Copyright (C) 2019 Alberto Sottile # # Distributed under the terms of the 3-clause BSD License. #----------------------------------------------------------------------------- import ctypes import ctypes.util try: # macOS Big Sur+ use "a built-in dynamic linker cache of all system-provided libraries" appkit = ctypes.cdll.LoadLibrary('AppKit.framework/AppKit') objc = ctypes.cdll.LoadLibrary('libobjc.dylib') except OSError: # revert to full path for older OS versions and hardened programs appkit = ctypes.cdll.LoadLibrary(ctypes.util.find_library('AppKit')) objc = ctypes.cdll.LoadLibrary(ctypes.util.find_library('objc')) void_p = ctypes.c_void_p ull = ctypes.c_uint64 objc.objc_getClass.restype = void_p objc.sel_registerName.restype = void_p # See https://docs.python.org/3/library/ctypes.html#function-prototypes for arguments description MSGPROTOTYPE = ctypes.CFUNCTYPE(void_p, void_p, void_p, void_p) msg = MSGPROTOTYPE(('objc_msgSend', objc), ((1 ,'', None), (1, '', None), (1, '', None))) def _utf8(s): if not isinstance(s, bytes): s = s.encode('utf8') return s def n(name): return objc.sel_registerName(_utf8(name)) def C(classname): return objc.objc_getClass(_utf8(classname)) def theme(): NSAutoreleasePool = objc.objc_getClass('NSAutoreleasePool') pool = msg(NSAutoreleasePool, n('alloc')) pool = msg(pool, n('init')) NSUserDefaults = C('NSUserDefaults') stdUserDef = msg(NSUserDefaults, n('standardUserDefaults')) NSString = C('NSString') key = msg(NSString, n("stringWithUTF8String:"), _utf8('AppleInterfaceStyle')) appearanceNS = msg(stdUserDef, n('stringForKey:'), void_p(key)) appearanceC = msg(appearanceNS, n('UTF8String')) if appearanceC is not None: out = ctypes.string_at(appearanceC) else: out = None msg(pool, n('release')) if out is not None: return out.decode('utf-8') else: return 'Light' def isDark(): return theme() == 'Dark' def isLight(): return theme() == 'Light' ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/external/darkdetect/_windows_detect.py0000644000175100001770000000232214654363416023665 0ustar00runnerdockerfrom winreg import HKEY_CURRENT_USER as hkey, QueryValueEx as getSubkeyValue, OpenKey as getKey def theme(): """ Uses the Windows Registry to detect if the user is using Dark Mode """ # Registry will return 0 if Windows is in Dark Mode and 1 if Windows is in Light Mode. This dictionary converts that output into the text that the program is expecting. valueMeaning = {0: "Dark", 1: "Light"} # In HKEY_CURRENT_USER, get the Personalisation Key. try: key = getKey(hkey, "Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize") # In the Personalisation Key, get the AppsUseLightTheme subkey. This returns a tuple. # The first item in the tuple is the result we want (0 or 1 indicating Dark Mode or Light Mode); the other value is the type of subkey e.g. DWORD, QWORD, String, etc. subkey = getSubkeyValue(key, "AppsUseLightTheme")[0] except FileNotFoundError: # some headless Windows instances (e.g. GitHub Actions or Docker images) do not have this key return None return valueMeaning[subkey] def isDark(): if theme() is not None: return theme() == 'Dark' def isLight(): if theme() is not None: return theme() == 'Light'././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/guitest.py0000644000175100001770000002621214654363416016240 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """ GUI-based test launcher ----------------------- Overview ^^^^^^^^ This module provides a GUI-based test launcher for any Python package. Usage example:: import your_package from guidata.guitest import run_testlauncher run_testlauncher(your_package) Reference/API ^^^^^^^^^^^^^ .. autofunction:: run_testlauncher .. autoclass:: TestModule .. autofunction:: get_tests """ from __future__ import annotations import os import os.path as osp import re import subprocess import sys import traceback from types import ModuleType from qtpy import QtCore as QC from qtpy import QtGui as QG from qtpy import QtWidgets as QW from guidata.config import _ from guidata.configtools import MONOSPACE, get_family, get_icon from guidata.qthelpers import get_std_icon, win32_fix_title_bar_background from guidata.widgets.codeeditor import CodeEditor def get_test_package(package): """Return test package for package Args: package (module): package to test Returns: str: test package """ test_package_name = "%s.tests" % package.__name__ _temp = __import__(test_package_name) return sys.modules[test_package_name] def get_tests(package, category: str) -> list[TestModule]: """Retrieve test scripts from test package Args: package (module): package to test category (str): test category (values: "all", "visible", "batch") Returns: list[TestModule]: list of test modules """ assert category in ("all", "visible", "batch") tests = [] test_package = get_test_package(package) if test_package.__file__ is None: print("Returning empty list") return tests test_path = osp.dirname(osp.realpath(test_package.__file__)) # Iterate over test scripts recursively within test package: for root, _dirs, files in os.walk(test_path): for fname in files: path = osp.join(root, fname) if ( fname.endswith((".py", ".pyw")) and not fname.startswith("_") and fname != "conftest.py" ): test = TestModule(test_package, path) if ( category == "all" or (category == "visible" and test.is_visible()) or (category == "batch" and not test.is_skipped()) ): tests.append(test) return tests class TestModule: """Object representing a test module (Python script) Args: test_package (module): test package path (str): test module path """ def __init__(self, test_package, path: str) -> None: self.path = path test_package_path = osp.dirname(osp.realpath(test_package.__file__)) self.name = osp.relpath(self.path, test_package_path) module_name, _ext = osp.splitext(osp.basename(path)) subpkgname = test_package.__name__ if len(self.name.split(os.sep)) > 1: subpkgname += "." + ".".join(self.name.split(os.sep)[:-1]) try: self.error_msg = "" _temp = __import__(subpkgname, fromlist=[module_name]) self.module = getattr(_temp, module_name) except ImportError: self.error_msg = traceback.format_exc() self.module = None self.__is_visible = False self.__is_skipped = False self.__contents = "" self.__read_contents() def __read_contents(self) -> str: """Read test module contents""" with open(self.path, "r", encoding="utf-8") as fdesc: lines = fdesc.readlines() startline = 0 for lineno, line in enumerate(lines): if re.match(r"^#[ ]*guitest[ ]*:", line.strip()): if "show" in line: self.__is_visible = True startline = lineno + 1 if "skip" in line: self.__is_skipped = True self.__contents = "".join(lines[startline:]).strip() def is_visible(self) -> bool: """Returns True if test module is visible""" return self.__is_visible def is_skipped(self) -> bool: """Returns True if test module is skipped""" return self.__is_skipped def get_contents(self) -> str: """Returns test module contents""" return self.__contents def is_valid(self) -> bool: """Returns True if test module is valid and can be executed""" return self.module is not None def get_description(self) -> str: """Returns test module description""" if self.is_valid(): doc = self.module.__doc__ if doc is None or not doc.strip(): return _("No description available") lines = doc.strip().splitlines() fmt = "%s" lines[0] = fmt % lines[0] return "
".join(lines) return self.error_msg def run(self, args: str = "", timeout: int = None) -> None: """Run test script Args: args (str): arguments to pass to the script timeout (int): timeout in seconds """ # Keep the same sys.path environment in child process: # (useful when the program is executed from Spyder, for example) os.environ["PYTHONPATH"] = os.pathsep.join(sys.path) command = [sys.executable, '"' + self.path + '"'] if args: command.append(args) proc = subprocess.Popen(" ".join(command), shell=True) if timeout is not None: proc.wait(timeout) class TestPropertiesWidget(QW.QWidget): """Test module properties panel Args: parent (QWidget): parent widget """ def __init__(self, parent: QW.QWidget = None) -> None: super().__init__(parent) self.lbl_icon = QW.QLabel() self.lbl_icon.setFixedWidth(32) self.desc_label = QW.QLabel() self.desc_label.setTextInteractionFlags(QC.Qt.TextSelectableByMouse) self.desc_label.setWordWrap(True) group_desc = QW.QGroupBox(_("Description"), self) layout = QW.QHBoxLayout() for label in (self.lbl_icon, self.desc_label): label.setAlignment(QC.Qt.AlignTop) layout.addWidget(label) group_desc.setLayout(layout) font = QG.QFont(get_family(MONOSPACE), 9, QG.QFont.Normal) self.editor = CodeEditor( self, columns=85, rows=30, language="python", font=font ) self.editor.setFont(font) self.editor.setReadOnly(True) self.desc_label.setFont(font) vlayout = QW.QVBoxLayout() vlayout.addWidget(group_desc) vlayout.addWidget(self.editor) self.setLayout(vlayout) def set_item(self, test: TestModule) -> None: """Set current item Args: test (TestModule): test module """ self.desc_label.setText(test.get_description()) self.editor.setPlainText(test.get_contents()) txt = "Information" if test.is_valid() else "Critical" self.lbl_icon.setPixmap(get_std_icon("MessageBox" + txt).pixmap(24, 24)) class TestMainView(QW.QSplitter): """Test launcher main view Args: package (module): test package parent (QWidget): parent widget """ def __init__(self, package, parent=None): super().__init__(parent) self.tests = get_tests(package, category="visible") listgroup = QW.QFrame() self.addWidget(listgroup) self.props = TestPropertiesWidget(self) font = self.props.editor.font() self.addWidget(self.props) vlayout = QW.QVBoxLayout() self.run_button = self.create_run_button(font) self.listw = self.create_test_listwidget(font) vlayout.addWidget(self.listw) vlayout.addWidget(self.run_button) listgroup.setLayout(vlayout) self.setStretchFactor(1, 1) enabled = len(self.tests) > 0 self.run_button.setEnabled(enabled) if enabled: self.props.set_item(self.tests[0]) def create_test_listwidget(self, font: QG.QFont) -> QW.QListWidget: """Create and setup test list widget Args: font (QFont): font to use Returns: QListWidget: test list widget """ listw = QW.QListWidget(self) listw.addItems([test.name for test in self.tests]) for index in range(listw.count()): item = listw.item(index) item.setSizeHint(QC.QSize(1, 25)) if not self.tests[index].is_valid(): item.setForeground(QG.QColor("#FF3333")) listw.setFont(font) listw.currentRowChanged.connect(self.current_row_changed) listw.itemActivated.connect(self.run_current_script) listw.setCurrentRow(0) return listw def create_run_button(self, font: QG.QFont) -> QW.QPushButton: """Create and setup run button Args: font (QFont): font to use Returns: QPushButton: run button """ btn = QW.QPushButton(get_icon("apply.png"), _("Run this script"), self) btn.setFont(font) btn.clicked.connect(self.run_current_script) return btn def current_row_changed(self, row: int) -> None: """Current list widget row has changed Args: row (int): row index """ current_test = self.tests[row] self.props.set_item(current_test) self.run_button.setEnabled(current_test.is_valid()) def run_current_script(self) -> None: """Run current script""" self.tests[self.listw.currentRow()].run() class TestLauncherWindow(QW.QMainWindow): """Test launcher main window Args: package (module): test package parent (QWidget): parent widget """ def __init__(self, package, parent: QW.QWidget = None) -> None: super().__init__(parent) win32_fix_title_bar_background(self) self.setWindowTitle(_("Tests - %s module") % package.__name__) self.setWindowIcon(get_icon("%s.svg" % package.__name__, "guidata.svg")) self.mainview = TestMainView(package, self) self.setCentralWidget(self.mainview) QW.QShortcut(QG.QKeySequence("Escape"), self, self.close) def show(self): """Show window""" super().show() if not self.mainview.tests: msg = _("No test found in this package.") QW.QMessageBox.critical(self, _("Error"), msg) def run_testlauncher(package: ModuleType) -> None: """Run test launcher Args: package (module): test package """ from guidata import qapplication # pylint: disable=import-outside-toplevel app = qapplication() win = TestLauncherWindow(package) win.show() app.exec_() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1722935059.1647055 guidata-3.6.2/guidata/io/0000755000175100001770000000000014654363423014604 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/io/__init__.py0000644000175100001770000000317514654363416016725 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """ Data serialization and deserialization -------------------------------------- The ``guidata.io`` package provides the core features for data (:py:class:`guidata.dataset.DataSet` or other objects) serialization and deserialization. Base classes for I/O handlers ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Base classes for writing custom readers and writers: * :py:class:`BaseIOHandler` * :py:class:`WriterMixin` .. autoclass:: GroupContext .. autoclass:: BaseIOHandler :members: .. autoclass:: WriterMixin :members: Configuration files (.ini) ^^^^^^^^^^^^^^^^^^^^^^^^^^ Reader and writer for the serialization of data sets into .ini files: * :py:class:`INIReader` * :py:class:`INIWriter` .. autoclass:: INIReader :members: .. autoclass:: INIWriter :members: JSON files (.json) ^^^^^^^^^^^^^^^^^^ Reader and writer for the serialization of data sets into .json files: * :py:class:`JSONReader` * :py:class:`JSONWriter` .. autoclass:: JSONReader :members: .. autoclass:: JSONWriter :members: HDF5 files (.h5) ^^^^^^^^^^^^^^^^ Reader and writer for the serialization of data sets into .h5 files: * :py:class:`HDF5Reader` * :py:class:`HDF5Writer` .. autoclass:: HDF5Reader :members: .. autoclass:: HDF5Writer :members: """ # pylint: disable=unused-import from .base import BaseIOHandler, GroupContext, WriterMixin # noqa from .h5fmt import HDF5Handler, HDF5Reader, HDF5Writer # noqa from .inifmt import INIHandler, INIReader, INIWriter # noqa from .jsonfmt import JSONHandler, JSONReader, JSONWriter # noqa ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/io/base.py0000644000175100001770000001313714654363416016077 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """ Base classes for I/O handlers """ from __future__ import annotations import datetime from collections.abc import Callable from typing import Any import numpy as np class GroupContext: """ Group context manager object. This class provides a context manager for managing a group within a handler. Args: handler (BaseIOHandler): The handler object. It should be an instance of a subclass of BaseIOHandler. group_name (str): The group name. Represents the name of the group in the context. """ def __init__(self, handler: BaseIOHandler, group_name: str) -> None: """ Initialization method for GroupContext. """ self.handler = handler self.group_name = group_name def __enter__(self) -> GroupContext: """ Enter the context. This method is called when entering the 'with' statement. It begins a new group on the handler. Returns: self (GroupContext): An instance of the GroupContext class. """ self.handler.begin(self.group_name) return self def __exit__( self, exc_type: type | None, exc_value: Exception | None, traceback: Any | None ) -> bool: """ Exit the context. This method is called when exiting the 'with' statement. It ends the group on the handler if no exception occurred within the context. Args: exc_type (Optional[type]): The type of exception that occurred, if any. exc_value (Optional[Exception]): The instance of Exception that occurred, if any. traceback (Optional[Any]): A traceback object encapsulating the call stack at the point where the exception originally occurred, if any. Returns: False (bool): A boolean False value indicating that the exception was not handled here. """ if exc_type is None: self.handler.end(self.group_name) return False class BaseIOHandler: """ Base I/O Handler with group context manager. This class serves as the base class for I/O handlers. It provides methods for managing sections of a file, referred to as "groups", as well as context management for these groups. """ def __init__(self) -> None: """ Initialization method for BaseIOHandler. This method initializes the option list, which will be used to manage the current section or "group". """ self.option = [] def group(self, group_name: str) -> GroupContext: """ Enter a group and return a context manager to be used with the `with` statement. Args: group_name (str): The name of the group to enter. Returns: GroupContext: A context manager for the group. """ return GroupContext(self, group_name) def begin(self, section: str) -> None: """ Begin a new section. This method is called when a new section is started. It adds the section to the list of options, which effectively makes it the current section. Args: section (str): The name of the section to begin. """ self.option.append(section) def end(self, section: str) -> None: """ End the current section. This method is called when a section is ended. It removes the section from the list of options, asserting it's the expected one, and moves to the previous section if any. Args: section (str): The name of the section to end. """ sect = self.option.pop(-1) assert ( sect == section ), "Ending section does not match the current section: %s != %s" % ( sect, section, ) class WriterMixin: """ Mixin class providing the write() method. This mixin class is intended to be used with classes that need to write different types of values. """ def write(self, val: Any, group_name: str | None = None) -> None: """ Write a value depending on its type, optionally within a named group. Args: val (Any): The value to be written. group_name (Optional[str]): The name of the group. If provided, the group context will be used for writing the value. """ if group_name: self.begin(group_name) if isinstance(val, bool): self.write_bool(val) elif isinstance(val, int): self.write_int(val) elif isinstance(val, float): self.write_float(val) elif isinstance(val, str): self.write_any(val) elif isinstance(val, np.ndarray): self.write_array(val) elif np.isscalar(val): self.write_any(val) elif val is None: self.write_none() elif isinstance(val, (list, tuple)): self.write_sequence(val) elif isinstance(val, datetime.datetime): self.write_float(val.timestamp()) elif isinstance(val, datetime.date): self.write_int(val.toordinal()) elif hasattr(val, "serialize") and isinstance(val.serialize, Callable): # The object has a DataSet-like `serialize` method val.serialize(self) else: raise NotImplementedError( "cannot serialize %r of type %r" % (val, type(val)) ) if group_name: self.end(group_name) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/io/h5fmt.py0000644000175100001770000006470014654363416016212 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """ HDF5 files (.h5) """ from __future__ import annotations import datetime import sys from collections.abc import Callable, Sequence from typing import Any from uuid import uuid1 import h5py import numpy as np from guidata.io.base import BaseIOHandler, WriterMixin class TypeConverter: """Handles conversion between types for HDF5 serialization. Args: to_type: The target type for the HDF5 representation. from_type: The original type from the HDF5 representation. Defaults to `to_type` if not specified. .. note:: Instances of this class are used to ensure data consistency when serializing and deserializing data to and from HDF5 format. """ def __init__( self, to_type: Callable[[Any], Any], from_type: Callable[[Any], Any] | None = None, ) -> None: self._to_type = to_type self._from_type = to_type if from_type is None else from_type def to_hdf(self, value: Any) -> Any: """Converts the value to the target type for HDF5 serialization. Args: value: The value to be converted. Returns: The converted value in the target type. Raises: Exception: If the conversion to the target type fails. """ try: return self._to_type(value) except Exception: print("ERR", repr(value), file=sys.stderr) raise def from_hdf(self, value: Any) -> Any: """Converts the value from the HDF5 representation to target type. Args: value: The HDF5 value to be converted. Returns: The converted value in the original type. """ return self._from_type(value) class Attr: """Helper class representing class attribute for HDF5 serialization. Args: hdf_name: Name of the attribute in the HDF5 file. struct_name: Name of the attribute in the object. Defaults to `hdf_name` if not specified. type: Attribute type. If None, type is guessed. optional: If True, attribute absence will not raise error. .. note:: This class manages serialization and deserialization of the object's attributes to and from HDF5 format. """ def __init__( self, hdf_name: str, struct_name: str | None = None, type: TypeConverter | None = None, optional: bool = False, ) -> None: self.hdf_name = hdf_name self.struct_name = hdf_name if struct_name is None else struct_name self.type = type self.optional = optional def get_value(self, struct: Any) -> Any: """Get the value of the attribute from the object. Args: struct: The object to extract the attribute from. Returns: The value of the attribute. """ if self.optional: return getattr(struct, self.struct_name, None) return getattr(struct, self.struct_name) def set_value(self, struct: Any, value: Any) -> None: """Set the value of the attribute in the object. Args: struct: The object to set the attribute value in. value: The value to set. """ setattr(struct, self.struct_name, value) def save(self, group: h5py.Group, struct: Any) -> None: """Save the attribute to an HDF5 group. Args: group: The HDF5 group to save the attribute to. struct: The object to save the attribute from. Raises: Exception: If an error occurs while saving the attribute. """ value = self.get_value(struct) if self.optional and value is None: if self.hdf_name in group.attrs: del group.attrs[self.hdf_name] return if self.type is not None: value = self.type.to_hdf(value) try: group.attrs[self.hdf_name] = value except Exception: # pylint: disable=broad-except print("ERROR saving:", repr(value), "into", self.hdf_name, file=sys.stderr) raise def load(self, group: h5py.Group, struct: Any) -> None: """Load the attribute from an HDF5 group into an object. Args: group: The HDF5 group to load the attribute from. struct: The object to load the attribute into. Raises: KeyError: If the attribute is not found in the HDF5 group. """ if self.optional and self.hdf_name not in group.attrs: self.set_value(struct, None) return try: value = group.attrs[self.hdf_name] except KeyError as err: raise KeyError(f"Unable to locate attribute {self.hdf_name}") from err if self.type is not None: value = self.type.from_hdf(value) self.set_value(struct, value) def createdset(group: h5py.Group, name: str, value: np.ndarray | list) -> None: """ Creates a dataset in the provided HDF5 group. Args: group: The group in the HDF5 file to add the dataset to. name: The name of the dataset. value: The data to be stored in the dataset. Returns: None """ group.create_dataset(name, compression=None, data=value) class Dset(Attr): """ Class for generic load/save for an hdf5 dataset. Handles the conversion of the scalar value, if any. Args: hdf_name: The name of the HDF5 attribute. struct_name: The name of the structure. Defaults to None. type: The expected data type of the attribute. Defaults to None. scalar: Function to convert the scalar value, if any. Defaults to None. optional: Whether the attribute is optional. Defaults to False. """ def __init__( self, hdf_name: str, struct_name: str | None = None, type: type | None = None, scalar: Callable | None = None, optional: bool = False, ) -> None: super().__init__(hdf_name, struct_name, type, optional) self.scalar = scalar def save(self, group: h5py.Group, struct: Any) -> None: """ Save the attribute to the given HDF5 group. Args: group: The group in the HDF5 file to save the attribute to. struct: The structure containing the attribute. """ value = self.get_value(struct) if isinstance(value, float): value = np.float64(value) elif isinstance(value, int): value = np.int32(value) if value is None or value.size == 0: value = np.array([0.0]) if value.shape == (): value = value.reshape((1,)) group.require_dataset( self.hdf_name, shape=value.shape, dtype=value.dtype, data=value, compression="gzip", compression_opts=1, ) def load(self, group: h5py.Group, struct: Any) -> None: """ Load the attribute from the given HDF5 group. Args: group: The group in the HDF5 file to load the attribute from. struct: The structure to load the attribute into. Raises: KeyError: If the attribute cannot be found in the HDF5 group. """ if self.optional: if self.hdf_name not in group: self.set_value(struct, None) return try: value = group[self.hdf_name][...] except KeyError as err: raise KeyError("Unable to locate dataset {}".format(self.hdf_name)) from err if self.scalar is not None: value = self.scalar(value) self.set_value(struct, value) class Dlist(Dset): """ Class for handling lists in HDF5 datasets. Inherits from the Dset class. Overrides the get_value and set_value methods from the Dset class to handle lists specifically. Args: hdf_name: The name of the HDF5 attribute. struct_name: The name of the structure. Defaults to None. type: The expected data type of the attribute. Defaults to None. scalar: Function to convert the scalar value, if any. Defaults to None. optional: Whether the attribute is optional. Defaults to False. """ def get_value(self, struct: Any) -> np.ndarray: """ Returns the value of the attribute in the given structure as a numpy array. Args: struct: The structure containing the attribute. Returns: The value of the attribute in the given structure as a numpy array. """ return np.array(getattr(struct, self.struct_name)) def set_value(self, struct: Any, value: np.ndarray) -> None: """ Sets the value of the attribute in the given structure to a list containing the values of the given numpy array. Args: struct: The structure in which to set the attribute. value: A numpy array containing the values to set the attribute to. """ setattr(struct, self.struct_name, list(value)) # ============================================================================== # Base HDF5 Store object: do not break API compatibility here as this class is # used in various critical projects for saving/loading application data # ============================================================================== class H5Store: """ Class for managing HDF5 files. Args: filename: The name of the HDF5 file. """ def __init__(self, filename: str) -> None: self.filename = filename self.h5 = None def open(self, mode: str = "a") -> h5py._hl.files.File: """ Opens an HDF5 file in the given mode. Args: mode: The mode in which to open the file. Defaults to "a". Returns: The opened HDF5 file. Raises: Exception: If there is an error while trying to open the file. """ if self.h5: return self.h5 try: self.h5 = h5py.File(self.filename, mode=mode) except Exception: print( "Error trying to load:", self.filename, "in mode:", mode, file=sys.stderr, ) raise return self.h5 def close(self) -> None: """ Closes the HDF5 file if it is open. """ if self.h5: self.h5.close() self.h5 = None def __enter__(self) -> "H5Store": """ Support for 'with' statement. Returns: The instance of the class itself. """ return self def __exit__(self, *args) -> None: """ Support for 'with' statement. Closes the HDF5 file on exiting the 'with' block. """ self.close() def generic_save(self, parent: Any, source: Any, structure: list[Attr]) -> None: """ Saves the data from source into the file using 'structure' as a descriptor. Args: parent: The parent HDF5 group. source: The source of the data to save. structure: A list of attribute descriptors (Attr, Dset, Dlist, etc.) that describes the conversion of data and the names of the attributes in the source and in the file. """ for instr in structure: instr.save(parent, source) def generic_load(self, parent: Any, dest: Any, structure: list[Attr]) -> None: """ Loads the data from the file into 'dest' using 'structure' as a descriptor. Args: parent: The parent HDF5 group. dest: The destination to load the data into. structure: A list of attribute descriptors (Attr, Dset, Dlist, etc.) that describes the conversion of data and the names of the attributes in the file and in the destination. Raises: Exception: If there is an error while trying to load an item. """ for instr in structure: try: instr.load(parent, dest) except Exception as err: print("Error loading HDF5 item:", instr.hdf_name, file=sys.stderr) raise err # ============================================================================== # HDF5 reader/writer: do not break API compatibility here as this class is # used in various critical projects for saving/loading application data and # in guiqwt for saving/loading plot items. # ============================================================================== class HDF5Handler(H5Store, BaseIOHandler): """ Base HDF5 I/O Handler object. Inherits from H5Store and BaseIOHandler. Args: filename: The name of the HDF5 file. """ def __init__(self, filename: str) -> None: super().__init__(filename) self.option = [] def get_parent_group(self) -> h5py._hl.group.Group: """ Returns the parent group in the HDF5 file based on the current option. Returns: The parent group in the HDF5 file. """ parent = self.h5 for option in self.option[:-1]: parent = parent.require_group(option) return parent SEQUENCE_NAME = "__seq" DICT_NAME = "__dict" class HDF5Writer(HDF5Handler, WriterMixin): """ Writer for HDF5 files. Inherits from HDF5Handler and WriterMixin. Args: filename: The name of the HDF5 file. """ def __init__(self, filename: str) -> None: super().__init__(filename) self.open("w") def write(self, val: Any, group_name: str | None = None) -> None: """ Write a value depending on its type, optionally within a named group. Args: val: The value to be written. group_name: The name of the group. If provided, the group context will be used for writing the value. """ if group_name: self.begin(group_name) if val is None: self.write_none() elif isinstance(val, (list, tuple)): self.write_sequence(val) elif isinstance(val, dict): self.write_dict(val) elif isinstance(val, datetime.datetime): self.write_float(val.timestamp()) elif isinstance(val, datetime.date): self.write_int(val.toordinal()) elif isinstance(val, np.ndarray): self.write_array(val) elif hasattr(val, "serialize") and isinstance(val.serialize, Callable): # The object has a DataSet-like `serialize` method val.serialize(self) else: group = self.get_parent_group() try: group.attrs[self.option[-1]] = val except TypeError: raise NotImplementedError( "cannot serialize %r of type %r" % (val, type(val)) ) if group_name: self.end(group_name) def write_any(self, val: Any) -> None: """ Write the value to the HDF5 file as an attribute. Args: val: The value to write. """ group = self.get_parent_group() group.attrs[self.option[-1]] = val write_str = write_list = write_int = write_float = write_any def write_bool(self, val: bool) -> None: """ Write the boolean value to the HDF5 file as an attribute. Args: val: The boolean value to write. """ self.write_int(int(val)) def write_array(self, val: np.ndarray) -> None: """ Write the numpy array value to the HDF5 file. Args: val: The numpy array value to write. """ group = self.get_parent_group() group[self.option[-1]] = val def write_none(self) -> None: """ Write a None value to the HDF5 file as an attribute. """ group = self.get_parent_group() group.attrs[self.option[-1]] = "" def write_sequence(self, val: list | tuple) -> None: """ Write the list or tuple value to the HDF5 file as an attribute. Args: The value to write. """ # Check if all elements are of the same type, raise an error if not for index, obj in enumerate(val): if val is None: raise ValueError("cannot serialize None value in sequence") with self.group(f"{SEQUENCE_NAME}{index}"): self.write(obj) self.write(len(val), SEQUENCE_NAME) def write_dict(self, val: dict[str, Any]) -> None: """Write dictionary to h5 file Args: val: dictionary to write """ # Check if keys are all strings, raise an error if not if not all(isinstance(key, str) for key in val.keys()): raise ValueError("cannot serialize dict with non-string keys") for key, value in val.items(): with self.group(key): if value is None: raise ValueError("cannot serialize None value in dict") self.write(value) self.write(len(val), DICT_NAME) def write_object_list(self, seq: Sequence[Any] | None, group_name: str) -> None: """ Write an object sequence to the HDF5 file in a group. Objects must implement the DataSet-like `serialize` method. Args: seq: The object sequence to write. Defaults to None. group_name: The name of the group in which to write the objects. """ with self.group(group_name): if seq is None: self.write_none() else: ids = [] for obj in seq: guid = bytes(str(uuid1()), "utf-8") ids.append(guid) with self.group(guid): if obj is None: self.write_none() else: obj.serialize(self) with self.group("IDs"): self.write_list(ids) class NoDefault: """Class to represent the absence of a default value.""" pass class HDF5Reader(HDF5Handler): """ Reader for HDF5 files. Inherits from HDF5Handler. Args: filename: The name of the HDF5 file. """ def __init__(self, filename: str): super().__init__(filename) self.open("r") def read( self, group_name: str | None = None, func: Callable[[], Any] | None = None, instance: Any | None = None, default: Any | NoDefault = NoDefault, ) -> Any: """ Read a value from the current group or specified group_name. Args: group_name: The name of the group to read from. Defaults to None. func: The function to use for reading the value. Defaults to None. instance: An object that implements the DataSet-like `deserialize` method. Defaults to None. default: The default value to return if the value is not found. Defaults to `NoDefault` (no default value: raises an exception if the value is not found). Returns: The read value. """ if group_name: self.begin(group_name) try: if instance is None: if func is None: func = self.read_any val = func() else: group = self.get_parent_group() if group_name in group.attrs: # This is an attribute (not a group), meaning that # the object was None when deserializing it val = None else: instance.deserialize(self) val = instance except Exception: # pylint:disable=broad-except if default is NoDefault: raise val = default if group_name: self.end(group_name) return val def read_any(self) -> str | bytes: """ Read a value from the current group as a generic type. Returns: The read value. """ group = self.get_parent_group() try: value = group.attrs[self.option[-1]] except KeyError: if self.read(SEQUENCE_NAME, func=self.read_int, default=None) is None: # No sequence found, this means that the data we are trying to read # is not here (e.g. compatibility issue), so we raise an error raise value = self.read_sequence() if isinstance(value, bytes): return value.decode("utf-8") else: return value def read_bool(self) -> bool | None: """ Read a boolean value from the current group. Returns: The read boolean value, or None if the value is not found. """ val = self.read_any() if val != "": return bool(val) def read_int(self) -> int | None: """ Read an integer value from the current group. Returns: The read integer value, or None if the value is not found. """ val = self.read_any() if val != "": return int(val) def read_float(self) -> float | None: """ Read a float value from the current group. Returns: The read float value, or None if the value is not found. """ val = self.read_any() if val != "": return float(val) read_unicode = read_str = read_any def read_array(self) -> np.ndarray: """ Read a numpy array from the current group. Returns: The read numpy array. """ group = self.get_parent_group() return group[self.option[-1]][...] def read_sequence(self) -> list[Any]: """ Read a sequence from the current group. Returns: The read sequence. """ length = self.read(SEQUENCE_NAME, func=self.read_int) if length is None: return [] seq = [] for index in range(length): name = f"{SEQUENCE_NAME}{index}" with self.group(name): dspath = "/".join(self.option) errormsg = f"cannot deserialize sequence at '{dspath}' (name '{name}')" try: group = self.get_parent_group() if name in group.attrs: obj = self.read_any() else: try: obj = self.read_array() except TypeError: obj_group = group[name] if DICT_NAME in obj_group.attrs: obj = self.read_dict() elif SEQUENCE_NAME in obj_group.attrs: obj = self.read_sequence() else: dspath = "/".join(self.option) raise ValueError(errormsg) except ValueError as err: raise ValueError(errormsg) from err seq.append(obj) return seq def read_dict(self) -> dict[str, Any]: """Read dictionary from h5 file Returns: Dictionary read from h5 file """ group = self.get_parent_group() dict_group = group[self.option[-1]] dict_val = {} for key, value in dict_group.attrs.items(): if key == DICT_NAME: continue dict_val[key] = value for key in dict_group: with self.group(key): try: dict_val[key] = self.read_array() except TypeError: key_group = dict_group[self.option[-1]] if DICT_NAME in key_group.attrs: dict_val[key] = self.read_dict() elif SEQUENCE_NAME in key_group.attrs: dict_val[key] = self.read_sequence() else: dspath = "/".join(self.option) raise ValueError( f"cannot deserialize dict at '{dspath}' (key '{key}'))" ) return dict_val def read_list(self) -> list[Any]: """ Read a list from the current group. Returns: The read list. """ group = self.get_parent_group() return list(group.attrs[self.option[-1]]) def read_object_list( self, group_name: str, klass: type[Any], progress_callback: Callable[[int], bool] | None = None, ) -> list[Any]: """Read an object sequence from a group. Objects must implement the DataSet-like `deserialize` method. `klass` is the object class which constructor requires no argument. Args: group_name: The name of the group to read the object sequence from. klass: The object class which constructor requires no argument. progress_callback: A function to call with an integer argument (progress: 0 --> 100). The function returns the `cancel` state (True: progress dialog has been canceled, False otherwise). """ with self.group(group_name): try: ids = self.read("IDs", func=self.read_list) except ValueError: # None was saved instead of list of objects self.end("IDs") return seq = [] count = len(ids) for idx, name in enumerate(ids): if progress_callback is not None: if progress_callback(int(100 * float(idx) / count)): break with self.group(name): try: group = self.get_parent_group() if name in group.attrs: # This is an attribute (not a group), meaning that # the object was None when deserializing it obj = None else: obj = klass() obj.deserialize(self) except ValueError: break seq.append(obj) return seq read_none = read_any read_none = read_any ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/io/inifmt.py0000644000175100001770000001042414654363416016447 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """ Configuration files (.ini) """ from __future__ import annotations from typing import Any from guidata.io.base import BaseIOHandler, GroupContext, WriterMixin class INIHandler(BaseIOHandler): """ User configuration IO handler. This class extends the BaseIOHandler to provide methods for handling configuration in a user-specific context. It overrides some of the methods in the base class to tailor them for a user-specific configuration. Args: conf (Any): The configuration object. section (str): The section of the configuration. option (str): The option within the section of the configuration. """ def __init__(self, conf: Any, section: str, option: str) -> None: """ Initialize the INIHandler with the given configuration, section, and option. """ super().__init__() self.conf = conf self.section = section self.option = [option] def begin(self, section: str) -> None: """ Begin a new section. This overrides the `begin` method of the base class. It appends a new section to the current list of options. Args: section (str): The name of the section to begin. """ self.option.append(section) def end(self, section: str) -> None: """ End the current section. This overrides the `end` method of the base class. It pops the last section from the list of options and ensures it matches the expected section. Args: section (str): The name of the section to end. """ sect = self.option.pop(-1) assert sect == section, ( "Ending section does not match the current section: %s != %s" % ( sect, section, ) ) def group(self, option: str) -> GroupContext: """ Enter a group. This returns a context manager, to be used with the `with` statement. Args: option (str): The name of the group to enter. Returns: GroupContext: A context manager for the group. """ return GroupContext(self, option) class INIWriter(INIHandler, WriterMixin): """ User configuration writer. This class extends INIHandler and WriterMixin to provide methods for writing different types of values into the user configuration. """ def write_any(self, val: Any) -> None: """ Write any value into the configuration. This method is used to write a value of any type into the configuration. It creates an option path by joining all the current options and writes the value into this path. Args: val (Any): The value to be written. """ option = "/".join(self.option) self.conf.set(self.section, option, val) # Make write_bool, write_int, write_float, write_array, write_sequence, # alias to write_any write_bool = write_int = write_str = write_float = write_array = write_sequence = ( write_dict ) = write_any def write_none(self) -> None: """ Write a None value into the configuration. This method writes a None value into the configuration. """ self.write_any(None) class INIReader(INIHandler): """ User configuration reader. This class extends the INIHandler to provide methods for reading different types of values from the user configuration. """ def read_any(self) -> Any: """ Read any value from the configuration. This method reads a value from the configuration located by an option path, formed by joining all the current options. Returns: Any: The value read from the configuration. """ option = "/".join(self.option) val = self.conf.get(self.section, option) return val # Make read_bool, read_int, read_float, read_array, read_sequence, read_none # and read_str alias to read_any read_bool = read_int = read_float = read_array = read_sequence = read_dict = ( read_none ) = read_str = read_unicode = read_any ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/io/jsonfmt.py0000644000175100001770000002353614654363416016651 0ustar00runnerdocker# -*- coding: utf-8 -*- # # This file is part of CodraFT Project # https://codra-ingenierie-informatique.github.io/CodraFT/ # # Licensed under the terms of the BSD 3-Clause or the CeCILL-B License # (see codraft/LICENSE for details) """ JSON files (.json) """ # pylint: disable=invalid-name # Allows short reference names like x, y, ... from __future__ import annotations import json import os from collections.abc import Callable, Sequence from typing import Any from uuid import uuid1 import numpy as np from guidata.io.base import BaseIOHandler, WriterMixin class CustomJSONEncoder(json.JSONEncoder): """Custom JSON Encoder""" def default(self, o: Any) -> Any: """Override JSONEncoder method""" if isinstance(o, np.ndarray): olist = o.tolist() if o.dtype in (np.complex64, np.complex128): olist = o.real.tolist() + o.imag.tolist() return ["array", olist, str(o.dtype)] if isinstance(o, np.generic): if isinstance(o, np.integer): return int(o) try: return float(o) except ValueError: return str(o) if isinstance(o, bytes): return o.decode() return json.JSONEncoder.default(self, o) class CustomJSONDecoder(json.JSONDecoder): """Custom JSON Decoder""" def __init__(self, *args, **kwargs) -> None: json.JSONDecoder.__init__(self, object_hook=self.object_hook, *args, **kwargs) def __iterate_dict(self, obj: Any) -> Any: """Iterate dictionaries""" if isinstance(obj, list) and len(obj) == 3: family, data, dtypestr = obj try: dtype = np.dtype(dtypestr) if family == "array": if dtype in (np.complex64, np.complex128): return np.asarray( data[: len(data) // 2], dtype ) + 1j * np.asarray(data[len(data) // 2 :], dtype) return np.asarray(data, dtype) except (TypeError, ValueError): pass elif isinstance(obj, dict): for key, value in list(obj.items()): obj[key] = self.__iterate_dict(value) return obj def object_hook(self, obj: dict) -> dict: # pylint: disable=E0202 """Object hook""" for key, value in list(obj.items()): obj[key] = self.__iterate_dict(value) return obj class JSONHandler(BaseIOHandler): """Class handling JSON r/w Args: filename: JSON filename (if None, use `jsontext` attribute) """ def __init__(self, filename: str | None = None) -> None: super().__init__() self.jsondata = {} self.jsontext: str | None = None self.filename = filename def get_parent_group(self) -> dict: """Get parent group""" parent = self.jsondata for option in self.option[:-1]: parent = parent.setdefault(option, {}) return parent def set_json_dict(self, jsondata: dict) -> None: """Set JSON data dictionary Args: jsondata: JSON data dictionary """ self.jsondata = jsondata def get_json_dict(self) -> dict: """Return JSON data dictionary""" return self.jsondata def get_json(self, indent: int | None = None) -> str | None: """Get JSON string Args: indent: Indentation level Returns: JSON string """ if self.jsondata is not None: return json.dumps(self.jsondata, indent=indent, cls=CustomJSONEncoder) return None def load(self) -> None: """Load JSON file""" if self.filename is not None: with open(self.filename, mode="rb") as fdesc: self.jsontext = fdesc.read().decode() self.jsondata = json.loads(self.jsontext, cls=CustomJSONDecoder) def save(self, path: str | None = None) -> None: """Save JSON file Args: path: Path to save the JSON file (if None, implies current directory) """ if self.filename is not None: filepath = self.filename if path: filepath = os.path.join(path, filepath) with open(filepath, mode="wb") as fdesc: fdesc.write(self.get_json(indent=4).encode()) def close(self) -> None: """Expected close method: do nothing for JSON I/O handler classes""" class JSONWriter(JSONHandler, WriterMixin): """Class handling JSON serialization""" def write_any(self, val) -> None: """Write any value type""" group = self.get_parent_group() group[self.option[-1]] = val def write_none(self) -> None: """Write None""" self.write_any(None) write_sequence = write_dict = write_str = write_bool = write_int = write_float = ( write_array ) = write_any def write_object_list(self, seq: Sequence[Any] | None, group_name: str) -> None: """ Write an object sequence to the HDF5 file in a group. Objects must implement the DataSet-like `serialize` method. Args: seq: The object sequence to write. Defaults to None. group_name: The name of the group in which to write the objects. """ with self.group(group_name): if seq is None: self.write_none() else: ids = [] for obj in seq: guid = str(uuid1()) ids.append(guid) with self.group(guid): if obj is None: self.write_none() else: obj.serialize(self) self.write(ids, "IDs") class NoDefault: """Class to represent the absence of a default value.""" pass class JSONReader(JSONHandler): """Class handling JSON deserialization Args: fname_or_jsontext: JSON filename or JSON text """ def __init__(self, fname_or_jsontext: str) -> None: """JSONReader constructor""" JSONHandler.__init__(self, fname_or_jsontext) if fname_or_jsontext is not None and not os.path.isfile(fname_or_jsontext): self.filename = None self.jsontext = fname_or_jsontext self.load() def read( self, group_name: str | None = None, func: Callable[[], Any] | None = None, instance: Any | None = None, default: Any | NoDefault = NoDefault, ) -> Any: """ Read a value from the current group or specified group_name. Args: group_name: The name of the group to read from. Defaults to None. func: The function to use for reading the value. Defaults to None. instance: An object that implements the DataSet-like `deserialize` method. Defaults to None. default: The default value to return if the value is not found. Defaults to `NoDefault` (no default value: raises an exception if the value is not found). Returns: The read value. """ if group_name: self.begin(group_name) try: if instance is None: if func is None: func = self.read_any val = func() else: group = self.get_parent_group() if group_name not in group: # This is an attribute (not a group), meaning that # the object was None when deserializing it val = None else: instance.deserialize(self) val = instance except Exception: # pylint:disable=broad-except if default is NoDefault: raise val = default if group_name: self.end(group_name) return val def read_any(self) -> Any: """Read any value type""" group = self.get_parent_group() return group[self.option[-1]] def read_object_list( self, group_name: str, klass: type[Any], progress_callback: Callable[[int], bool] | None = None, ) -> list[Any]: """Read an object sequence from a group. Objects must implement the DataSet-like `deserialize` method. `klass` is the object class which constructor requires no argument. Args: group_name: The name of the group to read the object sequence from. klass: The object class which constructor requires no argument. progress_callback: A function to call with an integer argument (progress: 0 --> 100). The function returns the `cancel` state (True: progress dialog has been canceled, False otherwise). """ with self.group(group_name): try: ids = self.read("IDs", func=self.read_sequence) except ValueError: # None was saved instead of list of objects self.end("IDs") return None seq = [] count = len(ids) for idx, name in enumerate(ids): if progress_callback is not None: if progress_callback(int(100 * float(idx) / count)): break with self.group(name): group = self.get_parent_group() if group[name] is None: # The object was None when deserializing it obj = None else: obj = klass() obj.deserialize(self) seq.append(obj) return seq read_unicode = read_sequence = read_dict = read_float = read_int = read_str = ( read_bool ) = read_array = read_any ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1722935059.1367052 guidata-3.6.2/guidata/locale/0000755000175100001770000000000014654363423015434 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1722935059.1367052 guidata-3.6.2/guidata/locale/fr/0000755000175100001770000000000014654363423016043 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1722935059.1647055 guidata-3.6.2/guidata/locale/fr/LC_MESSAGES/0000755000175100001770000000000014654363423017630 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/locale/fr/LC_MESSAGES/guidata.mo0000644000175100001770000003353714654363416021620 0ustar00runnerdocker▐Х┌ь╝ ╜ ▐ ф ю №   ( 2<AE7Зc┐7#8[.Ф├╔╥х∙   JEa4з ▄=ц$* ;GP W b$nУжм╗═рЇ ¤ %: N \jn s } КЦЭ░ └ ╠)╫( *8 =GKP dn~ДХпm└.5N!gЙО Ц а н ╖─ ┌Mш6<CJ [i z ИТWг&√*"1MГИСк░!╡╫ ▄!ш $)<AZ y&Ж н ╣╞╪"ч  Bat z2Ы╬╙█ф Ї &7 JX_fQ~╨▀я   +E6|Н бм─ ╪ уя ЗЯ#╣▌хЎ· =:V.С└ ╚╙j▄GN U_eij╘"э& -; JX `Zk3╞· "'-5: CMQ WaipЖМС ЧIе%я !!*!:!(J!s!Г! Т!IЮ!Iш!x2"Jл"DЎ">;#z# Г#П# и# ╔# ╙#▌#dЄ#TW$Fм$є$^№$[%`%p% w%Б%Й%Ь%1п%с%√%&&.&H& a&o&~&Э&д&╜&╤&ф&ї&№& '' !' /'!9'[' {' З'8Ф'4═' (( (#('(0(O(X(l(s("Ж(й(Ж╝(C)J)g),Д)▒)╢) ╛) ╩)╓)ы)!√)*Z5*Р*Ч*Я*и*┐*╘*ь* + +D +e+!Е+0з+╪+▌+ ф+я+ ,,,6,=,-P,~,Ю,в,╝,─,#т, -@- U-c-r-Е-%Ц-╝- ┼-^╤-0.L. S.5t.к.▒. ╣.┼. ╪. ц.Ё. °.//9/Q/Z/b/bВ/х/№/0*0@0T0j0\Б0▐0ў01&1!=1_1p1Б1Я1ж1Э║1X2;s2 п2╣2╬2╥2ф2TЎ2RK33Ю3 ╥3 р3 ю3К№3 З4 У4 а4л4▒4d╢4"50>5o5v5М5Х5е5╖5 ╞5╘5h█5<D6Б6Ц6Ю6г6 м6╖6╝6┼6╠6╥6┌6у6ъ6 Ё6 ■6 7#787@7F7O7 New instance of :py:class:`%s`. and between higher than lower than %s are currently not supported%s arrays%s editor%s filesUnable to assign data to item.

Error message:
%sUnable to plot data.

Error message:
%sUnable to proceed to next step

Please check your entries.

Error message:
%sUnable to save array

Error message:
%sUnable to show image.

Error message:
%sWarning: changes are applied separatelyAboutAbout...Additional optionsAll supported filesApplyArgumentsArray editorArray editor does not support array with x/y labels in variable size mode.Array editor was initialized in both readonly and variable size mode.Arrays with more than 3 dimensions are not supportedAttributeAutomatic GUI generation for easy dataset editing and displayAxis:Background colorBackground:Builtin:CancelClear lineClear shellClear shell contents ('cls' command)Clipboard contentsCloseColumn min/maxColumn separator:Column(s) deletionColumn(s) insertionComment:Comments:Conflicing edition flagsCopyCopy without promptsCreated by %s in %dCurrent cell:Current line:CutDataDataFrameDefault: %s.Definition:DeleteDelete from columnDelete from rowDescriptionDictionaryDo you want to remove all selected items?Do you want to remove the selected item?DocumentationDoneDuplicateEOLEditEdit array contentsEdit itemEmpty clipboardErrorExternal editor:Failed to import class %sFloat formattingFor performance reasons, changes applied to masked array won't be reflected in array's data (and vice-versa).FormatFormat (%s) is incorrectFormat ({}) is incorrectFormat ({}) should start with '%'HelpHelp...HistogramHistory logsImport asImport errorImport from clipboardImport wizardIn order to use commands like "input" run console with the multithread optionIndexIndex:InsertInsert at columnInsert at rowInsert column(s)Insert row(s)Instance:Internal editor:It is not possible to display this value because an error ocurred while trying to do itIt was not possible to copy this arrayIt was not possible to copy this dataframeIt was not possible to copy values for this arrayKeyKey:Keyword:Largest element in arrayLink:ListMaintained by the %s organizationMaskMasked dataMore details about %s on %s or %sMultiple choice from: %s.NameNew variable name:NextNo description availableNo test found in this package.Normal text:Nothing to be imported from clipboard.NumPy arrayNumPy arraysNumber of columnsNumber of rowsNumber of rows x Number of columnsNumber:Occurrence:Opening this variable can be slow Do you want to continue anyway?Overflow error: %sPastePlease check highlighted fields.Please install PlotPy or matplotlib.PlotPreviewPreviousProject websitePython help:Raw textRecord array fields:RemoveRemove column(s)Remove references:Remove row(s)RenameResizeResize rows to contentsReturns a new instance of :py:class:`%s` with the fields set to the given values.Row separator:Row(s) deletionRow(s) insertionRun script:Run this scriptSave and CloseSave arraySave current history log (i.e. all inputs and outputs) in a text fileSave history logSave history log...Select AllShell special commands:Show arrays min/maxShow imageSide areas:Single choice from: %s.SizeSkip rows:Slice %s is not valid. Expected an Iterable of slices and int like (slice(None), slice(None), n1, n2, ..., nX) with maximum two slices.Smallest element in arraySome required entries are incorrectString:System commands:TabTests - %s moduleText editorThe 'xlabels' argument length do no match array column numberThe 'ylabels' argument length do no match array row numberThe array editor will remain in readonly mode.To boolTo complexTo floatTo instanciate a new :py:class:`%s` dataset, you can use the classmethod :py:meth:`%s.create()` like this:To intTo strTransposeTupleTypeUnable to retrieve the value of this variable from the console.

The error mesage was:
%sUnsupported array formatUnsupported type %s, defaults to: ValueValue error: %sValue:Variable NameVariable name:Waiting: %s sWarningWhitespaceYou can also first instanciate a default :py:class:`%s` and then set the fields like this:You will not be able to add or remove rows/columns.all file typesarraycodedataelementsevenfloatintegerlistnon zeronot emptyoddotherread onlyregexp:stringsupported file types:tabletextunit:variable_nameProject-Id-Version: PACKAGE VERSION POT-Creation-Date: 2024-04-04 16:33+0200 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language-Team: LANGUAGE Language: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: utf-8 Generated-By: pygettext.py 1.5 Nouvelle instance de :py:class:`%s`. et compris entre sup├йrieur ├а inf├йrieur ├а Attention: %s ne sont pas pris en chargeles tableaux %s├Йditeur de %sFichiers %sImpossible d'acc├йder aux donn├йes

Message d'erreur :
%sImpossible d'afficher les donn├йes

Message d'erreur :
%sImpossible de passer ├а l'├йtape suivante

Merci de v├йrifier votre saisie.

Message d'erreur :
%sImpossible d'enregistrer le tableau

Message d'erreur :
%sImpossible d'afficher l'image

Message d'erreur :
%sAttention: les changements sont appliqu├йs s├йpar├йmentA proposA propos...Options suppl├йmentairesTous les fichiers pris en chargeAppliquerArguments├Йditeur de tableauxL'editeur de tableau ne prend pas en charge les tableaux avec des labels x/y enmode taille variable.L'├йditeur de tableaux a ├йt├й initialis├й en mode lecture seule et taille variable.Les tableaux ayant plus de trois dimensions ne sont pas pris en chargeAttributG├йn├йration automatique d'interfaces graphiques pour ├йditer et afficher des jeux de donn├йesAxe:Couleur de fondFond :Builtin :AnnulerSupprimer la ligneEffacer la consoleEffacer le contenu de la console (commande 'cls')Contenu du presse-papiersFermerMin/max de colonneS├йparateur de colonne :Suppression de colonne(s)Insertion de colonnes(s)Commentaire :Commentaires :Modes d'├йditions conflictuelsCopierCopier sans les ent├кtesCr├й├й par %s en %dCellule courante :Ligne courante :CouperDonn├йesDataFramePar d├йfaut : %s.D├йfinition :SupprimerSupprimer ├а partir de la colonneSupprimer ├а partir de la ligneDescriptionDictionnaireSouhaitez-vous supprimer les ├йl├йments s├йlectionn├йs ?Souhaitez-vous supprimer l'├йl├йment s├йlectionn├й ?DocumentationTerminerDupliquerEOLModifierModifier le contenu du tableauModifierPresse-papiers videErreur├Йditeur externe :Impossible d'importer la classe %sFormat de flottantPour des raisons de performance, les changements appliqu├йs au masque ne sont pas refl├йt├йs dans le tableau associ├й (et vice-versa).FormatLe format (%s) est incorrectLe format ({}) est incorrectLe format ({}) ne doit pas commencer par '%'AideAide...HistogrammeHistoriquesImporter en tant queErreur d'importImporter depuis le presse-papiersAssistant d'importationPour utiliser des commandes du type "input" dans la console, utiliser l'option multithreadIndiceIndice:Ins├йrerIns├йrer ├а la colonneIns├йrer ├а la ligneInsertion de colonne(s)Insertion de ligne(s)Instance :├Йditeur interne :Impossible d'afficher cette valeur en raison d'une erreur inattendueImpossible de copier ce tableauImpossible de copier ce DataFrameImpossible de copier les valeurs pour ce tableauCl├йCl├й :Mot-cl├й :Valeur maximale du tableauLien :ListeMaintenu par l'organisation %sMasqueDonn├йes masqu├йesPlus de d├йtails ├а propos de %s sur %s ou %sS├йlection multiple parmi : %s.NomNouveau nom de variable :SuivantAucune description disponibleAucun test trouv├й dans ce package.Text normal :Aucune donn├йe ne peut ├кtre import├йe depuis le presse-papiers.Tableau NumPyTableaux NumPyNombre de colonnesNombre de lignesNombre de lignes x Nombre de colonnesNombre :Occurence :Editer cette variable sera vraisemblablement tr├иs long. Souhaitez-vous n├йanmoins continuer ?Erreur de d├йpassement : %sCollerVeuillez v├йrifier votre saisie.Merci d'installer PlotPy ou matplotlib.TracerAper├зuPr├йc├йdentSite web du projetAide Python :Text brutChamps:SupprimerSuppression de colonne(s)Supprimer les r├йf├йrences :Suppression de ligne(s)RenommerAjusterAjuster les colonnes au contenuRenvoie une nouvelle instance de :py:class:`%s` avec les champs initialis├йs aux valeurs donn├йes.S├йparateur de ligne :Suppression de ligne(s)Insertion de ligne(s)Ex├йcuter le script :Ex├йcuter ce scriptEnregistrer et FermerEnregistrer le tableauEnregistrer l'historique actuel (c.-├а-d. toutes les entr├йes-sorties) dans un fichier texteEnregistrer l'historiqueEnregistrer l'historique...S├йlectionner toutCommandes sp├йciales :Afficher les min/max des tableauxAfficher l'imageZone lat├йrale :S├йlection unique parmi : %s.TailleSauter des lignes :Slice %s incorrecte. Un it├йrable de slices et d'entiers au format (slice(None), slice(None), n1, n2, ..., nX) avec un maximum de deux slices ├йtait attendu.Valeur minimale du tableauLes champs surlign├йs n'ont pas ├йt├й remplis correctement.Cha├оne :Commandes syst├иme :TabTests - Module %s├Йditeur de texteLa taille de l'argument 'xlabels' ne correspond pas au nombre de colonnes du tableauLa taille de l'argument 'ylabels' ne correspond pas au nombre de lignes du tableauL'├йditeur de tableaux reste en mode lecture seule.Vers bool├йenVers complexeVers flottantPour instancier un nouveau jeu de donn├йes :py:class:`%s`, vous pouvez utiliser la m├йthode de classe :py:class:`%s.create()` comme ceci :Vers entierVers cha├оneTransposerTupleTypeImpossible de r├йcup├йrer la valeur de cette variable.

Le message d'erreur est :
%sType de tableau non pris en chargeType %s non pris en charge, valeur par d├йfaut: ValeurErreur de valeur : %sValeur :Nom de variableNom de variable :Attente : %s sAvertissementEspaceVous pouvez ├йgalement instancier un :py:class:`%s` par d├йfaut puis initialiser les champs comme ceci :Vous ne pourrez pas ins├йrer ou supprimer de lignes/colonnestout type de fichiertableaucodedonn├йes├йl├йmentspairflottantentierlistenon nulnon videimpairautrelecture seuleexpr. r├йg. :cha├оnetypes de fichiers pris en charge : tableautexteunit├й :nom_de_variable././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/qthelpers.py0000644000175100001770000006222014654363416016562 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """ Qt helpers ---------- Actions ^^^^^^^ .. autofunction:: create_action .. autofunction:: add_actions .. autofunction:: add_separator .. autofunction:: keybinding Simple widgets ^^^^^^^^^^^^^^ .. autofunction:: create_toolbutton .. autofunction:: create_groupbox Icons ^^^^^ .. autofunction:: get_std_icon .. autofunction:: show_std_icons Application ^^^^^^^^^^^ .. autofunction:: qt_app_context .. autofunction:: exec_dialog Other ^^^^^ .. autofunction:: grab_save_window .. autofunction:: click_on_widget .. autofunction:: block_signals .. autofunction:: qt_wait .. autofunction:: save_restore_stds """ from __future__ import annotations import os import os.path as osp import sys import time import warnings from contextlib import contextmanager from datetime import datetime from typing import TYPE_CHECKING, Generator, Iterable, Literal from qtpy import QtCore as QC from qtpy import QtGui as QG from qtpy import QtWidgets as QW import guidata from guidata.config import _ from guidata.configtools import get_icon, get_module_data_path from guidata.env import execenv from guidata.external import darkdetect if TYPE_CHECKING: from collections.abc import Callable ENV_COLOR_MODE = "QT_COLOR_MODE" COLOR_MODES = LIGHT, DARK, AUTO = "light", "dark", "auto" CURRENT_THEME = None def set_dark_mode(state: bool) -> None: """Set dark mode for Qt application (deprecated, use `set_color_mode` instead) Args: state: True to enable dark mode """ mode = DARK if state else LIGHT warnings.warn( f"`set_dark_mode` is deprecated and will be removed in a future version. " f"Use `set_color_mode('{mode}')` instead.", DeprecationWarning, ) set_color_mode(mode) def is_dark_mode() -> bool: """Return True if current color mode is dark mode (deprecated, use `is_dark_theme` instead) Returns: True if dark mode is enabled """ warnings.warn( "`is_dark_mode` is deprecated and will be removed in a future version. " "Use `is_dark_theme` instead.", DeprecationWarning, ) return is_dark_theme() def get_color_theme() -> Literal["light", "dark"]: """Get color theme. Color theme value is updated only once per session because the query to the system may be expensive depending on the platform. It is updated when the color mode is set to 'auto' using `set_color_mode('auto')`. Returns: Color theme ('light' or 'dark') """ global CURRENT_THEME mode = get_color_mode() if mode == AUTO: theme = DARK if darkdetect.isDark() else LIGHT else: theme = mode CURRENT_THEME = theme return theme def is_dark_theme() -> bool: """Return True if current color mode is dark mode. Color theme value is updated only once per session because the query to the system may be expensive depending on the platform. It is updated when the color mode is set to 'auto' using `set_color_mode('auto')`. Returns: True if dark mode is enabled """ return get_color_theme() == DARK def get_color_mode() -> Literal["light", "dark", "auto"]: """Get color mode Returns: Color mode ('light', 'dark' or 'auto') """ mode = os.environ.get(ENV_COLOR_MODE, AUTO).lower() if mode not in COLOR_MODES: # Just show a warning, the mode will be set to 'auto': warnings.warn( f"Invalid color mode: {mode} (expected {COLOR_MODES}). " "Using 'auto' instead.", UserWarning, ) mode = AUTO return mode DEFAULT_STYLES = None def get_background_color() -> QG.QColor: """Get current theme background color""" return QW.QApplication.instance().palette().color(QG.QPalette.Base) def get_foreground_color() -> QG.QColor: """Get current theme foreground color""" return QW.QApplication.instance().palette().color(QG.QPalette.Text) def set_color_mode(mode: Literal["light", "dark", "auto"] | None = None): """Set color mode. Args: mode: Color mode ('light', 'dark' or 'auto'). If 'auto', the system color mode is used. If None, the `QT_COLOR_MODE` environment variable is used. """ global DEFAULT_STYLES, CURRENT_THEME CURRENT_THEME = None if mode is not None: assert ( mode in COLOR_MODES ), f"Invalid color mode: {mode} (expected {COLOR_MODES})" os.environ[ENV_COLOR_MODE] = mode app = QW.QApplication.instance() if DEFAULT_STYLES is None: # Store default palette to be able to restore it: DEFAULT_STYLES = app.style().objectName(), app.palette(), app.styleSheet() if is_dark_theme(): app.setStyle(QW.QStyleFactory.create("Fusion")) dark_palette = QG.QPalette() dark_color = QG.QColor(50, 50, 50) disabled_color = QG.QColor(127, 127, 127) dpsc = dark_palette.setColor dpsc(QG.QPalette.Window, dark_color) dpsc(QG.QPalette.WindowText, QC.Qt.white) dpsc(QG.QPalette.Base, QG.QColor(31, 31, 31)) dpsc(QG.QPalette.AlternateBase, dark_color) dpsc(QG.QPalette.ToolTipBase, QC.Qt.white) dpsc(QG.QPalette.ToolTipText, QC.Qt.white) dpsc(QG.QPalette.Text, QC.Qt.white) dpsc(QG.QPalette.Disabled, QG.QPalette.Text, disabled_color) dpsc(QG.QPalette.Button, dark_color) dpsc(QG.QPalette.ButtonText, QC.Qt.white) dpsc(QG.QPalette.Disabled, QG.QPalette.ButtonText, disabled_color) dpsc(QG.QPalette.BrightText, QC.Qt.red) dpsc(QG.QPalette.Link, QG.QColor(42, 130, 218)) dpsc(QG.QPalette.Highlight, QG.QColor(42, 130, 218)) dpsc(QG.QPalette.HighlightedText, QC.Qt.black) dpsc(QG.QPalette.Disabled, QG.QPalette.HighlightedText, disabled_color) app.setPalette(dark_palette) app.setStyleSheet( "QToolTip { " "color: white; background-color: #2a82da; border: 1px solid white;" " }" ) elif DEFAULT_STYLES is not None: style, palette, stylesheet = DEFAULT_STYLES app.setStyle(QW.QStyleFactory.create(style)) app.setPalette(palette) app.setStyleSheet(stylesheet) # Iterate over all top-level widgets: for widget in QW.QApplication.instance().topLevelWidgets(): win32_fix_title_bar_background(widget) def win32_fix_title_bar_background(widget: QW.QWidget) -> None: """Fix window title bar background for Windows 10+ dark theme Args: widget (QW.QWidget): Widget to fix """ if os.name != "nt" or sys.maxsize == 2**31 - 1: return import ctypes from ctypes import wintypes class ACCENTPOLICY(ctypes.Structure): _fields_ = [ ("AccentState", ctypes.c_uint), ("AccentFlags", ctypes.c_uint), ("GradientColor", ctypes.c_uint), ("AnimationId", ctypes.c_uint), ] class WINDOWCOMPOSITIONATTRIBDATA(ctypes.Structure): _fields_ = [ ("Attribute", ctypes.c_int), ("Data", ctypes.POINTER(ctypes.c_int)), ("SizeOfData", ctypes.c_size_t), ] accent = ACCENTPOLICY() data = WINDOWCOMPOSITIONATTRIBDATA() if is_dark_theme(): accent.AccentState = 3 # ACCENT_ENABLE_ACRYLICBLURBEHIND data.Attribute = 26 # WCA_USEDARKMODECOLORS else: accent.AccentState = 0 # ACCENT_DISABLED data.Attribute = 19 # WCA_ACCENT_POLICY data.SizeOfData = ctypes.sizeof(accent) data.Data = ctypes.cast(ctypes.pointer(accent), ctypes.POINTER(ctypes.c_int)) set_win_cpa = ctypes.windll.user32.SetWindowCompositionAttribute set_win_cpa.argtypes = (wintypes.HWND, WINDOWCOMPOSITIONATTRIBDATA) set_win_cpa.restype = ctypes.c_int set_win_cpa(int(widget.winId()), data) if not is_dark_theme(): # Setting dark mode attribute to False (0) to ensure the default light mode attribute_value = ctypes.c_int(0) hwnd = wintypes.HWND(int(widget.winId())) ctypes.windll.dwmapi.DwmSetWindowAttribute( hwnd, 20, # DWMWA_USE_IMMERSIVE_DARK_MODE, # Dark mode attribute ctypes.byref(attribute_value), ctypes.sizeof(attribute_value), ) def create_action( parent: QW.QWidget | None, title: str, triggered: Callable | None = None, toggled: Callable | None = None, shortcut: QG.QKeySequence | None = None, icon: QG.QIcon | None = None, tip: str | None = None, checkable: bool | None = None, context: QC.Qt.ShortcutContext = QC.Qt.WindowShortcut, enabled: bool | None = None, ) -> QW.QAction: """Create a new QAction Args: parent (QWidget or None): Parent widget title (str): Action title triggered (Callable or None): Triggered callback toggled (Callable or None): Toggled callback shortcut (QKeySequence or None): Shortcut icon (QIcon or None): Icon tip (str or None): Tooltip checkable (bool or None): Checkable context (Qt.ShortcutContext): Shortcut context enabled (bool or None): Enabled Returns: QAction: New action """ if isinstance(title, bytes): title = str(title, "utf8") action = QW.QAction(title, parent) if triggered: if checkable: action.triggered.connect(triggered) else: action.triggered.connect(lambda checked=False: triggered()) if checkable is not None: # Action may be checkable even if the toggled signal is not connected action.setCheckable(checkable) if toggled: action.toggled.connect(toggled) action.setCheckable(True) if icon is not None: assert isinstance(icon, QG.QIcon) action.setIcon(icon) if shortcut is not None: action.setShortcut(shortcut) if tip is not None: action.setToolTip(tip) action.setStatusTip(tip) if enabled is not None: action.setEnabled(enabled) action.setShortcutContext(context) return action def create_toolbutton( parent: QW.QWidget, icon: QG.QIcon | str | None = None, text: str | None = None, triggered: Callable | None = None, tip: str | None = None, toggled: Callable | None = None, shortcut: QG.QKeySequence | None = None, autoraise: bool = True, enabled: bool | None = None, ) -> QW.QToolButton: """Create a QToolButton Args: parent (QWidget): Parent widget icon (QIcon or str or None): Icon text (str or None): Text triggered (Callable or None): Triggered callback tip (str or None): Tooltip toggled (Callable or None): Toggled callback shortcut (QKeySequence or None): Shortcut autoraise (bool): Auto raise enabled (bool or None): Enabled Returns: QToolButton: New toolbutton """ if autoraise: button = QW.QToolButton(parent) else: button = QW.QPushButton(parent) if text is not None: button.setText(text) if icon is not None: if isinstance(icon, str): icon = get_icon(icon) button.setIcon(icon) if text is not None or tip is not None: button.setToolTip(text if tip is None else tip) if autoraise: button.setToolButtonStyle(QC.Qt.ToolButtonTextBesideIcon) button.setAutoRaise(True) if triggered is not None: button.clicked.connect(lambda checked=False: triggered()) if toggled is not None: button.toggled.connect(toggled) button.setCheckable(True) if shortcut is not None: button.setShortcut(shortcut) if enabled is not None: button.setEnabled(enabled) return button def create_groupbox( parent: QW.QWidget, title: str | None = None, toggled: Callable | None = None, checked: bool | None = None, flat: bool = False, layout: QW.QLayout | None = None, ) -> QW.QGroupBox: """Create a QGroupBox Args: parent (QWidget): Parent widget title (str or None): Title toggled (Callable or None): Toggled callback checked (bool or None): Checked flat (bool): Flat layout (QLayout or None): Layout Returns: QGroupBox: New groupbox """ if title is None: group = QW.QGroupBox(parent) else: group = QW.QGroupBox(title, parent) group.setFlat(flat) if toggled is not None: group.setCheckable(True) if checked is not None: group.setChecked(checked) group.toggled.connect(toggled) if layout is not None: group.setLayout(layout) return group def keybinding(attr: str) -> str: """Return keybinding Args: attr (str): Attribute name Returns: str: Keybinding """ ks = getattr(QG.QKeySequence, attr) return QG.QKeySequence.keyBindings(ks)[0].toString() def add_separator(target: QW.QMenu | QW.QToolBar) -> None: """Add separator to target only if last action is not a separator Args: target (QMenu or QToolBar): Target menu or toolbar """ target_actions = list(target.actions()) if target_actions: if not target_actions[-1].isSeparator(): target.addSeparator() def add_actions( target: QW.QMenu | QW.QToolBar, actions: Iterable[QW.QAction | QW.QMenu | QW.QToolButton | QW.QPushButton | None], ) -> None: """ Add actions (list of QAction instances) to target (menu, toolbar) Args: target (QMenu or QToolBar): Target menu or toolbar actions (list): List of actions (QAction, QMenu, QToolButton, QPushButton, None) """ for action in actions: if isinstance(action, QW.QAction): target.addAction(action) elif isinstance(action, QW.QMenu): target.addMenu(action) elif isinstance(action, QW.QToolButton) or isinstance(action, QW.QPushButton): target.addWidget(action) elif action is None: add_separator(target) def _process_mime_path(path: str, extlist: tuple[str, ...] | None = None) -> str | None: """Process path from MIME data Args: path (str): Path extlist (tuple or None): Extension list Returns: str or None: Processed path """ if path.startswith(r"file://"): if os.name == "nt": # On Windows platforms, a local path reads: file:///c:/... # and a UNC based path reads like: file://server/share if path.startswith(r"file:///"): # this is a local path path = path[8:] else: # this is a unc path path = path[5:] else: path = path[7:] path = path.replace("%5C", os.sep) # Transforming backslashes if osp.exists(path): if extlist is None or osp.splitext(path)[1] in extlist: return path def mimedata2url( source: QC.QMimeData, extlist: tuple[str, ...] | None = None ) -> list[str]: """ Extract url list from MIME data extlist: for example ('.py', '.pyw') Args: source (QMimeData): Source extlist (tuple or None): Extension list Returns: list: List of paths """ pathlist = [] if source.hasUrls(): for url in source.urls(): path = _process_mime_path(str(url.toString()), extlist) if path is not None: pathlist.append(path) elif source.hasText(): for rawpath in str(source.text()).splitlines(): path = _process_mime_path(rawpath, extlist) if path is not None: pathlist.append(path) if pathlist: return pathlist def get_std_icon(name: str, size: int | None = None) -> QG.QIcon: """ Get standard platform icon Call 'show_std_icons()' for details Args: name (str): Icon name size (int or None): Size Returns: QIcon: Icon """ if not name.startswith("SP_"): name = "SP_" + name icon = QW.QWidget().style().standardIcon(getattr(QW.QStyle, name)) if size is None: return icon else: return QG.QIcon(icon.pixmap(size, size)) class ShowStdIcons(QW.QWidget): """ Dialog showing standard icons Args: parent (QWidget): Parent widget """ def __init__(self, parent) -> None: QW.QWidget.__init__(self, parent) layout = QW.QHBoxLayout() row_nb = 14 cindex = 0 col_layout = QW.QVBoxLayout() for child in dir(QW.QStyle): if child.startswith("SP_"): if cindex == 0: col_layout = QW.QVBoxLayout() icon_layout = QW.QHBoxLayout() icon = get_std_icon(child) label = QW.QLabel() label.setPixmap(icon.pixmap(32, 32)) icon_layout.addWidget(label) icon_layout.addWidget(QW.QLineEdit(child.replace("SP_", ""))) col_layout.addLayout(icon_layout) cindex = (cindex + 1) % row_nb if cindex == 0: layout.addLayout(col_layout) self.setLayout(layout) self.setWindowTitle("Standard Platform Icons") self.setWindowIcon(get_std_icon("TitleBarMenuButton")) def show_std_icons() -> None: """Show all standard Icons""" app = QW.QApplication(sys.argv) dialog = ShowStdIcons(None) dialog.show() sys.exit(app.exec()) def close_widgets_and_quit(screenshot: bool = False) -> None: """Close Qt top level widgets and quit Qt event loop Args: screenshot (bool): If True, save a screenshot of each widget """ for widget in QW.QApplication.instance().topLevelWidgets(): try: wname = widget.objectName() except RuntimeError: # Widget has been deleted continue if screenshot and wname and widget.isVisible(): # pragma: no cover grab_save_window(widget, wname.lower()) assert widget.close() QW.QApplication.instance().quit() def close_dialog_and_quit(widget, screenshot: bool = False) -> None: """Close QDialog and quit Qt event loop Args: widget (QDialog): Dialog to close """ try: # Workaround for pytest wname = widget.objectName() if screenshot and wname and widget.isVisible(): # pragma: no cover grab_save_window(widget, wname.lower()) else: QW.QApplication.processEvents() if execenv.accept_dialogs: widget.accept() else: widget.done(QW.QDialog.Accepted) except Exception: # pylint: disable=broad-except pass QAPP_INSTANCE = None @contextmanager def qt_app_context(exec_loop: bool = False) -> Generator[QW.QApplication, None, None]: """Context manager handling Qt application creation and persistance Args: exec_loop (bool): If True, execute Qt event loop .. note:: This context manager was strongly inspired by the one in the `DataLab `_ project which is more advanced and complete than this one (it handles faulthandler and traceback log files, which need to be implemented at application level, that is why they were not included here). """ global QAPP_INSTANCE # pylint: disable=global-statement if QAPP_INSTANCE is None: QAPP_INSTANCE = guidata.qapplication() exception_occured = False try: yield QAPP_INSTANCE except Exception: # pylint: disable=broad-except exception_occured = True finally: if execenv.unattended: # pragma: no cover if execenv.delay > 0: mode = "Screenshot" if execenv.screenshot else "Unattended" message = f"{mode} mode (delay: {execenv.delay}ms)" msec = execenv.delay - 200 for widget in QW.QApplication.instance().topLevelWidgets(): if isinstance(widget, QW.QMainWindow): widget.statusBar().showMessage(message, msec) QC.QTimer.singleShot( execenv.delay, lambda: close_widgets_and_quit(screenshot=execenv.screenshot), ) if exec_loop and not exception_occured: QAPP_INSTANCE.exec() if exception_occured: raise # pylint: disable=misplaced-bare-raise def exec_dialog(dlg: QW.QDialog) -> int: """Run QDialog Qt execution loop without blocking, depending on environment test mode Args: dlg (QDialog): Dialog to execute Returns: int: Dialog exit code """ if execenv.unattended: QC.QTimer.singleShot( execenv.delay, lambda: close_dialog_and_quit(dlg, screenshot=execenv.screenshot), ) delete_later = not dlg.testAttribute(QC.Qt.WA_DeleteOnClose) result = dlg.exec() if delete_later: dlg.deleteLater() return result def grab_save_window(widget: QW.QWidget, name: str) -> None: # pragma: no cover """Grab window screenshot and save it Args: widget (QWidget): Widget to grab name (str): Widget name """ widget.activateWindow() widget.raise_() QW.QApplication.processEvents() pixmap = widget.grab() suffix = "" if not name[-1].isdigit() and not name.startswith(("s_", "i_")): suffix = "_" + datetime.now().strftime("%Y-%m-%d-%H%M%S") pixmap.save( osp.join( get_module_data_path("guidata"), os.pardir, "doc", "images", "shots", f"{name}{suffix}.png", ) ) def click_on_widget(widget: QW.QWidget) -> None: """Click on widget and eventually save a screenshot Args: widget (QWidget): Widget to click on """ wname = widget.objectName() if wname and widget.isVisible(): # pragma: no cover grab_save_window(widget, wname.lower()) widget.clicked() @contextmanager def block_signals(widget: QW.QWidget, enable: bool) -> Generator[None, None, None]: """Eventually block/unblock widget Qt signals before/after doing some things (enable: True if feature is enabled) Args: widget (QWidget): Widget to block/unblock enable (bool): True to block signals """ if enable: widget.blockSignals(True) try: yield finally: if enable: widget.blockSignals(False) class TopMessageBox(QW.QWidget): """Widget containing a message box, shown on top of all windows""" def __init__(self, parent: QW.QWidget | None = None) -> None: super().__init__(parent) self.__label = QW.QLabel() font = self.__label.font() font.setPointSize(20) self.__label.setFont(font) self.__label.setAlignment(QC.Qt.AlignCenter) layout = QW.QVBoxLayout() layout.addWidget(self.__label) self.setLayout(layout) self.setWindowFlags(QC.Qt.WindowStaysOnTopHint | QC.Qt.SplashScreen) def set_text(self, text: str) -> None: """Set message box text""" self.__label.setText(text) def qt_wait( timeout: float, except_unattended: bool = False, show_message: bool = False, parent: QW.QWidget | None = None, ) -> None: # pragma: no cover """Freeze GUI during timeout (seconds) while processing Qt events. Args: timeout: timeout in seconds except_unattended: if True, do not wait if unattended mode is enabled show_message: if True, show a message box with a timeout parent: parent widget of the message box """ if except_unattended and execenv.unattended: return start = time.time() msgbox = None if show_message: # Show a message box with a timeout msgbox = TopMessageBox(parent) msgbox.show() while time.time() <= start + timeout: time.sleep(0.01) if msgbox is not None: msgbox.set_text(_("Waiting: %s s") % int(timeout - (time.time() - start))) QW.QApplication.processEvents() if msgbox is not None: msgbox.close() msgbox.deleteLater() @contextmanager def save_restore_stds() -> Generator[None, None, None]: """Save/restore standard I/O before/after doing some things (e.g. calling Qt open/save dialogs)""" saved_in, saved_out, saved_err = sys.stdin, sys.stdout, sys.stderr sys.stdout = None try: yield finally: sys.stdin, sys.stdout, sys.stderr = saved_in, saved_out, saved_err if __name__ == "__main__": show_std_icons() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1722935059.1647055 guidata-3.6.2/guidata/tests/0000755000175100001770000000000014654363423015337 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/__init__.py0000644000175100001770000000121514654363416017451 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """ guidata test package ==================== """ import os.path as osp import guidata from guidata.configtools import get_module_data_path TESTDATAPATH = get_module_data_path("guidata", osp.join("tests", "data")) def get_path(filename: str) -> str: """Return absolute path of test file""" return osp.join(TESTDATAPATH, filename) def run(): """Run guidata test launcher""" from guidata.guitest import run_testlauncher run_testlauncher(guidata) if __name__ == "__main__": run() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/conftest.py0000644000175100001770000000110614654363416017536 0ustar00runnerdocker# content of conftest.py import qtpy import guidata from guidata.env import execenv # Turn on unattended mode for executing tests without user interaction execenv.unattended = True execenv.verbose = "quiet" def pytest_report_header(config): """Add additional information to the pytest report header.""" qtbindings_version = qtpy.PYSIDE_VERSION if qtbindings_version is None: qtbindings_version = qtpy.PYQT_VERSION return [ f"guidata {guidata.__version__}, " f"{qtpy.API_NAME} {qtbindings_version} [Qt version: {qtpy.QT_VERSION}]", ] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1722935059.1367052 guidata-3.6.2/guidata/tests/data/0000755000175100001770000000000014654363423016250 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1722935059.1647055 guidata-3.6.2/guidata/tests/data/genreqs/0000755000175100001770000000000014654363423017714 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/data/genreqs/pyproject.toml0000644000175100001770000000346514654363416022642 0ustar00runnerdocker# guidata setup configuration file # Important note: # Requirements (see [options]) are parsed by utils\genreqs.py to generate documentation [build-system] requires = ["setuptools"] build-backend = "setuptools.build_meta" [project] name = "guidata" authors = [{ name = "Codra", email = "p.raybaut@codra.fr" }] description = "Signal and image processing software" readme = "README.md" license = { file = "LICENSE" } classifiers = [ "Topic :: Scientific/Engineering", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Utilities", "Topic :: Scientific/Engineering", "Topic :: Scientific/Engineering :: Human Machine Interfaces", "Topic :: Software Development :: User Interfaces", "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", "Operating System :: OS Independent", "Operating System :: POSIX", "Operating System :: Unix", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", ] requires-python = ">=3.8, <4" dependencies = ["h5py>=3.0", "NumPy>=1.21", "QtPy>=1.9"] dynamic = ["version"] [project.urls] Homepage = "https://github.com/PlotPyStack/guidata/" Documentation = "https://guidata.readthedocs.io/en/latest/" [project.scripts] [project.optional-dependencies] dev = ["ruff", "pylint", "Coverage"] doc = [ "PyQt5", "pillow", "pandas", "sphinx", "sphinx-copybutton", "sphinx_qt_documentation", "python-docs-theme", ] test = ["pytest", "pytest-cov", "pytest-qt", "pytest-xvfb"] [tool.setuptools] include-package-data = false [tool.setuptools.package-data] "*" = ["*.png", "*.svg", "*.mo", "*.cfg", "*.toml", "*.rst"] [tool.setuptools.dynamic] version = { attr = "guidata.__version__" } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/data/genreqs/setup.cfg0000644000175100001770000000257514654363416021550 0ustar00runnerdocker [metadata] name = guidata version = attr: guidata.__version__ author = Codra author_email = p.raybaut@codra.fr url = https://github.com/PlotPyStack/guidata/ description = Signal and image processing software long_description = file: README.md long_description_content_type = text/markdown license = BSD 3-Clause License classifiers = Topic :: Scientific/Engineering Topic :: Software Development :: Libraries :: Python Modules Topic :: Utilities Topic :: Scientific/Engineering Topic :: Scientific/Engineering :: Human Machine Interfaces Topic :: Software Development :: User Interfaces Operating System :: MacOS Operating System :: Microsoft :: Windows Operating System :: OS Independent Operating System :: POSIX Operating System :: Unix Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 [options] python_requires = >=3.8, <4 install_requires = h5py>=3.0 NumPy>=1.21 QtPy>=1.9 packages = find_namespace: include_package_data = True [options.packages.find] include = guidata* [options.extras_require] dev = ruff pylint Coverage doc = PyQt5 pillow pandas sphinx sphinx-copybutton sphinx_qt_documentation python-docs-theme test = pytest pytest-cov pytest-qt pytest-xvfb ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1722935059.1687055 guidata-3.6.2/guidata/tests/dataset/0000755000175100001770000000000014654363423016764 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/dataset/__init__.py0000644000175100001770000000000014654363416021065 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/dataset/test_activable_dataset.py0000644000175100001770000000323414654363416024040 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """ ActivableDataSet example Warning: ActivableDataSet objects were made to be integrated inside GUI layouts. So this example with dialog boxes may be confusing. --> see tests/editgroupbox.py to understand the activable dataset usage """ # When editing, all items are shown. # When showing dataset in read-only mode (e.g. inside another layout), all items # are shown except the enable item. import guidata.dataset as gds from guidata.env import execenv from guidata.qthelpers import qt_app_context # guitest: show class Parameters(gds.ActivableDataSet): """ Example Activable dataset example """ def __init__(self, title=None, comment=None, icon=""): gds.ActivableDataSet.__init__(self, title, comment, icon) enable = gds.BoolItem( "Enable parameter set", help="If disabled, the following parameters will be ignored", default=False, ) param0 = gds.ChoiceItem("Param 0", ["choice #1", "choice #2", "choice #3"]) param1 = gds.FloatItem("Param 1", default=0, min=0) param2 = gds.FloatItem("Param 2", default=0.93) color = gds.ColorItem("Color", default="red") Parameters.active_setup() def test_activable_dataset(): """Test activable dataset""" with qt_app_context(): prm = Parameters() prm.set_activable(True) prm.edit() prm.set_activable(False) prm.edit() prm.set_readonly() prm.edit() prm.set_readonly(False) prm.edit() execenv.print("OK") if __name__ == "__main__": test_activable_dataset() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/dataset/test_activable_items.py0000644000175100001770000000205214654363416023531 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """ Example with activable items: items which active state is changed depending on another item's value. """ # guitest: show from guidata.dataset import ChoiceItem, DataSet, FloatItem, FuncProp, GetAttrProp from guidata.env import execenv from guidata.qthelpers import qt_app_context choices = (("A", "Choice #1: A"), ("B", "Choice #2: B"), ("C", "Choice #3: C")) class Parameters(DataSet): """Example dataset""" _prop = GetAttrProp("choice") choice = ChoiceItem("Choice", choices).set_prop("display", store=_prop) x1 = FloatItem("x1") x2 = FloatItem("x2").set_prop("display", active=FuncProp(_prop, lambda x: x == "B")) x3 = FloatItem("x3").set_prop("display", active=FuncProp(_prop, lambda x: x == "C")) def test_activable_items(): """Test activable items""" with qt_app_context(): test = Parameters() test.edit() execenv.print("OK") if __name__ == "__main__": test_activable_items() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/dataset/test_all_features.py0000644000175100001770000001157714654363416023060 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """ All guidata item/group features demo """ # guitest: show import atexit import shutil import tempfile import numpy as np import guidata.dataset as gds from guidata.dataset.qtitemwidgets import DataSetWidget from guidata.dataset.qtwidgets import DataSetEditLayout, DataSetShowLayout from guidata.env import execenv from guidata.qthelpers import qt_app_context # Creating temporary files and registering cleanup functions TEMPDIR = tempfile.mkdtemp(prefix="test_") atexit.register(shutil.rmtree, TEMPDIR) FILE_ETA = tempfile.NamedTemporaryFile(suffix=".eta", dir=TEMPDIR) atexit.register(FILE_ETA.close) FILE_CSV = tempfile.NamedTemporaryFile(suffix=".csv", dir=TEMPDIR) atexit.register(FILE_CSV.close) class SubDataSet(gds.DataSet): dir = gds.DirectoryItem("Directory", TEMPDIR) fname = gds.FileOpenItem("Single file (open)", ("csv", "eta"), FILE_CSV.name) fnames = gds.FilesOpenItem("Multiple files", "csv", FILE_CSV.name) fname_s = gds.FileSaveItem("Single file (save)", "eta", FILE_ETA.name) class SubDataSetWidget(DataSetWidget): klass = SubDataSet class SubDataSetItem(gds.ObjectItem): klass = SubDataSet DataSetEditLayout.register(SubDataSetItem, SubDataSetWidget) DataSetShowLayout.register(SubDataSetItem, SubDataSetWidget) class Parameters(gds.DataSet): """ DataSet test The following text is the DataSet 'comment':
Plain text or rich text2 are both supported, as well as special characters (╬▒, ╬▓, ╬│, ╬┤, ...) """ dict_ = gds.DictItem( "dict_item", { "strings_col": ["a", "b", "c"], "_col": [1, 2.0, 3], "float_col": 1.0, }, ) string = gds.StringItem("String") string_regexp = gds.StringItem("String", regexp=r"^[a-z]+[0-9]$", default="abcd9") password = gds.StringItem("Password", password=True) text = gds.TextItem("Text") _bg = gds.BeginGroup("A sub group") float_slider = gds.FloatItem( "Float (with slider)", default=0.5, min=0, max=1, step=0.01, slider=True ) fl1 = gds.FloatItem( "Current", default=10.0, min=1, max=30, unit="mA", help="Threshold current" ) fl2 = gds.FloatItem("Float (col=1)", default=1.0, min=1, max=1).set_pos(col=1) fl3 = gds.FloatItem("Not checked float").set_prop("data", check_value=False) bool1 = gds.BoolItem("Boolean option without label") bool2 = gds.BoolItem("Boolean option with label", "Label").set_pos(col=1, colspan=2) color = gds.ColorItem("Color", default="red") choice1 = gds.ChoiceItem( "Single choice (radio)", [(16, "first choice"), (32, "second choice"), (64, "third choice")], radio=True, ).set_pos(col=1, colspan=2) choice2 = gds.ChoiceItem( "Single choice (combo)", [(16, "first choice"), (32, "second choice"), (64, "third choice")], ).set_pos(col=1, colspan=2) _eg = gds.EndGroup("A sub group") floatarray = gds.FloatArrayItem( "Float array", default=np.ones((50, 5), float), format=" %.2e " ).set_pos(col=1) g0 = gds.BeginTabGroup("group") mchoice1 = gds.MultipleChoiceItem( "MC type 1", ["first choice", "second choice", "third choice"] ).vertical(2) mchoice2 = ( gds.ImageChoiceItem( "MC type 2", [ ("rect", "first choice", "gif.png"), ("ell", "second choice", "txt.png"), ("qcq", "third choice", "html.png"), ], ) .set_pos(col=1) .set_prop("display", icon="file.png") .set_prop("display", size=(32, 32)) ) mchoice3 = gds.MultipleChoiceItem( "MC type 3", [str(i) for i in range(10)] ).horizontal(2) eg0 = gds.EndTabGroup("group") integer_slider = gds.IntItem( "Integer (with slider)", default=5, min=-50, max=50, slider=True ) integer = gds.IntItem("Integer", default=5, min=3, max=6).set_pos(col=1) def test_all_features(): """Test all guidata item/group features""" with execenv.context(accept_dialogs=True): with qt_app_context(): prm1 = Parameters() prm1.floatarray[:, 0] = np.linspace(-5, 5, 50) execenv.print(prm1) if prm1.edit(): prm1.edit() execenv.print(prm1) prm1.view() prm2 = Parameters.create(integer=10101010, string="Using `create`") assert prm2.integer == 10101010 print(prm2) try: # Try to set an unknown attribute using the `create` method: Parameters.create(unknown_attribute=42) except AttributeError: pass else: raise AssertionError("AttributeError not raised") execenv.print("OK") if __name__ == "__main__": test_all_features() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/dataset/test_all_items.py0000644000175100001770000000726414654363416022361 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """ All guidata DataItem objects demo A DataSet object is a set of parameters of various types (integer, float, boolean, string, etc.) which may be edited in a dialog box thanks to the 'edit' method. Parameters are defined by assigning DataItem objects to a DataSet class definition: each parameter type has its own DataItem class (IntItem for integers, FloatItem for floats, StringItem for strings, etc.) """ # guitest: show import atexit import datetime import shutil import tempfile import numpy as np import guidata.dataset as gds from guidata.env import execenv from guidata.qthelpers import qt_app_context # Creating temporary files and registering cleanup functions TEMPDIR = tempfile.mkdtemp(prefix="test_") atexit.register(shutil.rmtree, TEMPDIR) FILE_ETA = tempfile.NamedTemporaryFile(suffix=".eta", dir=TEMPDIR) atexit.register(FILE_ETA.close) FILE_CSV = tempfile.NamedTemporaryFile(suffix=".csv", dir=TEMPDIR) atexit.register(FILE_CSV.close) class Parameters(gds.DataSet): """ DataSet test The following text is the DataSet 'comment':
Plain text or rich text2 are both supported, as well as special characters (╬▒, ╬▓, ╬│, ╬┤, ...) """ dir = gds.DirectoryItem("Directory", TEMPDIR) fname = gds.FileOpenItem("Open file", ("csv", "eta"), FILE_CSV.name) fnames = gds.FilesOpenItem("Open files", "csv", FILE_CSV.name) fname_s = gds.FileSaveItem("Save file", "eta", FILE_ETA.name) string = gds.StringItem("String") text = gds.TextItem("Text") float_slider = gds.FloatItem( "Float (with slider)", default=0.5, min=0, max=1, step=0.01, slider=True ) integer = gds.IntItem("Integer", default=5, min=3, max=16, slider=True).set_pos( col=1 ) dtime = gds.DateTimeItem("Date/time", default=datetime.datetime(2010, 10, 10)) date = gds.DateItem("Date", default=datetime.date(2010, 10, 10)).set_pos(col=1) bool1 = gds.BoolItem("Boolean option without label") bool2 = gds.BoolItem("Boolean option with label", "Label") _bg = gds.BeginGroup("A sub group") color = gds.ColorItem("Color", default="red") choice = gds.ChoiceItem( "Single choice 1", [("16", "first choice"), ("32", "second choice"), ("64", "third choice")], ) mchoice2 = gds.ImageChoiceItem( "Single choice 2", [ ("rect", "first choice", "gif.png"), ("ell", "second choice", "txt.png"), ("qcq", "third choice", "file.png"), ], ) _eg = gds.EndGroup("A sub group") floatarray = gds.FloatArrayItem( "Float array", default=np.ones((50, 5), float), format=" %.2e " ).set_pos(col=1) mchoice3 = gds.MultipleChoiceItem( "MC type 1", [str(i) for i in range(12)] ).horizontal(4) mchoice1 = ( gds.MultipleChoiceItem( "MC type 2", ["first choice", "second choice", "third choice"] ) .vertical(1) .set_pos(col=1) ) dictionary = gds.DictItem( "Dictionary", default={ "lkl": 2, "tototo": 3, "zzzz": "lklk", "bool": True, "float": 1.234, "list": [1, 2.5, 3, "str", False, 5, {"lkl": 2, "l": [1, 2, 3]}], }, help="This is a dictionary", ) def test_all_items(): """Test all DataItem objects""" with qt_app_context(): prm = Parameters() prm.floatarray[:, 0] = np.linspace(-5, 5, 50) execenv.print(prm) if prm.edit(): execenv.print(prm) prm.view() execenv.print("OK") if __name__ == "__main__": test_all_items() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/dataset/test_all_items_readonly.py0000644000175100001770000000202114654363416024240 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """ Same as test_all_items.py but with readonly=True to check that the readonly mode works on all DataItem types. """ # guitest: show import numpy as np from guidata.env import execenv from guidata.qthelpers import qt_app_context from guidata.tests.dataset.test_all_items import Parameters # check if array variable_size is ignored thanks to readonly Parameters.floatarray.set_prop("edit", variable_size=True) def test_all_features(): """Test all guidata item/group features""" with qt_app_context(): prm1 = Parameters(readonly=True) prm1.floatarray[:, 0] = np.linspace(-5, 5, 50) execenv.print(prm1) if prm1.edit(): prm1.edit() execenv.print(prm1) prm1.view() prm2 = Parameters.create(integer=10101010, string="Using create class method") print(prm2) execenv.print("OK") if __name__ == "__main__": test_all_features() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/dataset/test_bool_selector.py0000644000175100001770000000334414654363416023236 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """ DataItem groups and group selection DataSet items may be included in groups (these items are then shown in group box widgets in the editing dialog box) and item groups may be enabled/disabled using one group parameter (a boolean item). """ # guitest: show import guidata.dataset as gds from guidata.env import execenv from guidata.qthelpers import qt_app_context prop1 = gds.ValueProp(False) prop2 = gds.ValueProp(False) class Parameters(gds.DataSet): """ Group selection test Group selection example: """ g1 = gds.BeginGroup("group 1") enable1 = gds.BoolItem( "Enable parameter set #1", help="If disabled, the following parameters will be ignored", default=False, ).set_prop("display", store=prop1) prm11 = gds.FloatItem("Prm 1.1", default=0, min=0).set_prop("display", active=prop1) prm12 = gds.FloatItem("Prm 1.2", default=0.93).set_prop("display", active=prop1) _g1 = gds.EndGroup("group 1") g2 = gds.BeginGroup("group 2") enable2 = gds.BoolItem( "Enable parameter set #2", help="If disabled, the following parameters will be ignored", default=True, ).set_prop("display", store=prop2) prm21 = gds.FloatItem("Prm 2.1", default=0, min=0).set_prop("display", active=prop2) prm22 = gds.FloatItem("Prm 2.2", default=0.93).set_prop("display", active=prop2) _g2 = gds.EndGroup("group 2") def test_bool_selector(): """Test bool selector""" with qt_app_context(): prm = Parameters() prm.edit() execenv.print(prm) execenv.print("OK") if __name__ == "__main__": test_bool_selector() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/dataset/test_callbacks.py0000644000175100001770000000521714654363416022323 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """ Demonstrates how items may trigger callbacks when activated, or how the list of choices may be dynamically changed. """ # guitest: show import guidata.dataset as gds from guidata.env import execenv from guidata.qthelpers import qt_app_context class Parameters(gds.DataSet): """Example dataset""" def cb_example(self, item, value): execenv.print("\nitem: ", item, "\nvalue:", value) if self.results is None: self.results = "" self.results += str(value) + "\n" execenv.print("results:", self.results) def update_x1plusx2(self, item, value): execenv.print("\nitem: ", item, "\nvalue:", value) if self.x1 is not None and self.x2 is not None: self.x1plusx2 = self.x1 + self.x2 else: self.x1plusx2 = None string = gds.StringItem("String", default="foobar").set_prop( "display", callback=cb_example ) x1 = gds.FloatItem("x1").set_prop("display", callback=update_x1plusx2) x2 = gds.FloatItem("x2").set_prop("display", callback=update_x1plusx2) x1plusx2 = gds.FloatItem("x1+x2").set_prop("display", active=False) boolean = gds.BoolItem("Boolean", default=True).set_prop( "display", callback=cb_example ) color = gds.ColorItem("Color", default="red").set_prop( "display", callback=cb_example ) def choices_callback(self, item, value): """Choices callback: this demonstrates how to dynamically change the list of choices... even if it is not very useful in this case Note that `None` is systematically added as the third element of the returned tuples: that is to ensure the compatibility between `ChoiceItem` and `ImageChoiceItem` (see `guidata.dataset.dataitems`) """ execenv.print(f"[choices_callback]: item={item}, value={value}") return [ (16, "first choice", None), (32, "second choice", None), (64, "third choice", None), ] choice = ( gds.ChoiceItem( "Single choice", choices_callback, default=64, ) .set_pos(col=1, colspan=2) .set_prop("display", callback=cb_example) ) results = gds.TextItem("Results").set_prop("display", callback=cb_example) def test_callbacks(): """Test callbacks""" with qt_app_context(): prm = Parameters() execenv.print(prm) if prm.edit(): execenv.print(prm) prm.view() execenv.print("OK") if __name__ == "__main__": test_callbacks() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/dataset/test_datasetgroup.py0000644000175100001770000000226414654363416023105 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """ DataSetGroup demo DataSet objects may be grouped into DataSetGroup, allowing them to be edited in a single dialog box (with one tab per DataSet object). This code tests both the normal dataset group mode and the table mode (with one tab per DataSet object). """ # guitest: show from guidata.dataset import DataSetGroup from guidata.env import execenv from guidata.qthelpers import qt_app_context from guidata.tests.dataset.test_all_features import Parameters def test_dataset_group(): """Test DataSetGroup""" with qt_app_context(): e1 = Parameters("DataSet #1") e2 = Parameters("DataSet #2") g = DataSetGroup([e1, e2], title="Parameters group") g.edit() execenv.print(e1) g.edit() execenv.print("OK") g = DataSetGroup([e1, e2], title="Parameters group in table mode") g.edit(mode="table") execenv.print(e1) g.edit() execenv.print("OK") g.edit() execenv.print(e1) g.edit() execenv.print("OK") if __name__ == "__main__": test_dataset_group() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/dataset/test_editgroupbox.py0000644000175100001770000001502314654363416023113 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """ DataSetEditGroupBox and DataSetShowGroupBox demo These group box widgets are intended to be integrated in a GUI application layout, showing read-only parameter sets or allowing to edit parameter values. """ # guitest: show import numpy as np from qtpy.QtWidgets import QMainWindow, QSplitter import guidata.dataset as gds from guidata.configtools import get_icon from guidata.dataset.qtwidgets import DataSetEditGroupBox, DataSetShowGroupBox from guidata.env import execenv from guidata.qthelpers import ( add_actions, create_action, get_std_icon, qt_app_context, win32_fix_title_bar_background, ) from guidata.tests.dataset.test_activable_dataset import Parameters from guidata.widgets import about class AnotherDataSet(gds.DataSet): """ Example 2 Simple dataset example """ param0 = gds.ChoiceItem("Choice", ["deazdazk", "aeazee", "87575757"]) param1 = gds.FloatItem("Foobar 1", default=0, min=0) a_group = gds.BeginGroup("A group") param2 = gds.FloatItem("Foobar 2", default=0.93) param3 = gds.FloatItem("Foobar 3", default=123) _a_group = gds.EndGroup("A group") class ExampleMultiGroupDataSet(gds.DataSet): """Example DS with multiple groups""" choices = gds.MultipleChoiceItem("Choices", ["a", "b", "c", "d", "e"]) dictionary = gds.DictItem("Dictionary", {"a": 1, "b": 2, "c": 3}) param0 = gds.ChoiceItem("Choice", ["deazdazk", "aeazee", "87575757"]) param1 = gds.FloatItem("Foobar 1", default=0, min=0) t_group = gds.BeginTabGroup("T group") a_group = gds.BeginGroup("A group") param2 = gds.FloatItem("Foobar 2", default=0.93) dir1 = gds.DirectoryItem("Directory 1") file1 = gds.FileOpenItem("File 1") _a_group = gds.EndGroup("A group") b_group = gds.BeginGroup("B group") param3 = gds.FloatItem("Foobar 3", default=123) param4 = gds.BoolItem("Boolean") _b_group = gds.EndGroup("B group") c_group = gds.BeginGroup("C group") param5 = gds.FloatItem("Foobar 4", default=250) param6 = gds.DateItem("Date").set_prop("display", format="dd.MM.yyyy") param7 = gds.ColorItem("Color") _c_group = gds.EndGroup("C group") _t_group = gds.EndTabGroup("T group") class OtherDataSet(gds.DataSet): """Another example dataset""" title = gds.StringItem("Title", default="Title") icon = gds.ChoiceItem( "Icon", ( ("python.png", "Python"), ("guidata.svg", "guidata"), ("settings.png", "Settings"), ), ) opacity = gds.FloatItem("Opacity", default=1.0, min=0.1, max=1) transform = gds.FloatArrayItem("Transform", default=np.array([1, 2, 3, 4, 5, 6])) class MainWindow(QMainWindow): """Main window""" def __init__(self): QMainWindow.__init__(self) win32_fix_title_bar_background(self) self.setWindowIcon(get_icon("python.png")) self.setWindowTitle("Application example") # Instantiate dataset-related widgets: self.groupbox1 = DataSetShowGroupBox( "Activable dataset", Parameters, comment="" ) self.groupbox2 = DataSetShowGroupBox( "Standard dataset", AnotherDataSet, comment="" ) self.groupbox3 = DataSetEditGroupBox( "Standard dataset", OtherDataSet, comment="" ) self.groupbox4 = DataSetEditGroupBox( "Standard dataset", ExampleMultiGroupDataSet, comment="" ) self.groupbox3.SIG_APPLY_BUTTON_CLICKED.connect(self.update_window) self.update_groupboxes() splitter = QSplitter(self) splitter.addWidget(self.groupbox1) splitter.addWidget(self.groupbox2) splitter.addWidget(self.groupbox3) splitter.addWidget(self.groupbox4) self.setCentralWidget(splitter) self.setContentsMargins(10, 5, 10, 5) # File menu file_menu = self.menuBar().addMenu("File") quit_action = create_action( self, "Quit", shortcut="Ctrl+Q", icon=get_std_icon("DialogCloseButton"), tip="Quit application", triggered=self.close, ) add_actions(file_menu, (quit_action,)) # Edit menu edit_menu = self.menuBar().addMenu("Edit") editparam1_action = create_action( self, "Edit dataset 1", triggered=self.edit_dataset1 ) editparam2_action = create_action( self, "Edit dataset 2", triggered=self.edit_dataset2 ) editparam4_action = create_action( self, "Edit dataset 4", triggered=self.edit_dataset4 ) ro_param4_action = create_action( self, "Dataset 4: read-only", toggled=self.ro_dataset4 ) add_actions( edit_menu, (editparam1_action, editparam2_action, editparam4_action, ro_param4_action), ) # ? menu help_menu = self.menuBar().addMenu("?") about_action = create_action( self, "About guidata", icon=get_std_icon("MessageBoxInformation"), triggered=about.show_about_dialog, ) add_actions(help_menu, (about_action,)) def update_window(self): """Update window""" dataset = self.groupbox3.dataset self.setWindowTitle(dataset.title) self.setWindowIcon(get_icon(dataset.icon)) self.setWindowOpacity(dataset.opacity) def update_groupboxes(self): """Update groupboxes""" self.groupbox1.dataset.set_activable(False) # This is an activable dataset self.groupbox1.get() self.groupbox2.get() self.groupbox4.get() def ro_dataset4(self, readonly: bool): self.groupbox4.dataset.set_readonly(readonly) self.groupbox4.get() def edit_dataset1(self): """Edit dataset 1""" self.groupbox1.dataset.set_activable(True) # This is an activable dataset if self.groupbox1.dataset.edit(self): self.update_groupboxes() def edit_dataset2(self): """Edit dataset 2""" if self.groupbox2.dataset.edit(self): self.update_groupboxes() def edit_dataset4(self): """Edit dataset 4""" if self.groupbox4.dataset.edit(self): self.update_groupboxes() def test_editgroupbox(): """Test editgroupbox""" with qt_app_context(exec_loop=True): window = MainWindow() window.show() execenv.print("OK") if __name__ == "__main__": test_editgroupbox() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/dataset/test_inheritance.py0000644000175100001770000000257614654363416022702 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """ DataSet objects inheritance test From time to time, it may be useful to derive a DataSet from another. The main application is to extend a parameter set with additionnal parameters. """ # guitest: show import guidata.dataset as gds from guidata.env import execenv from guidata.qthelpers import qt_app_context class OriginalDataset(gds.DataSet): """Original dataset This is the original dataset""" bool = gds.BoolItem("Boolean") string = gds.StringItem("String") text = gds.TextItem("Text") float = gds.FloatItem("Float", default=0.5, min=0, max=1, step=0.01, slider=True) class DerivedDataset(OriginalDataset): """Derived dataset This is the derived dataset""" bool = gds.BoolItem("Boolean (modified in derived dataset)") a = gds.FloatItem("Level 1 (added in derived dataset)", default=0) b = gds.FloatItem("Level 2 (added in derived dataset)", default=0) c = gds.FloatItem("Level 3 (added in derived dataset)", default=0) def test_inheritance(): """Test DataSet inheritance""" with qt_app_context(): e = OriginalDataset() e.edit() execenv.print(e) e = DerivedDataset() e.edit() execenv.print(e) execenv.print("OK") if __name__ == "__main__": test_inheritance() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/dataset/test_item_order.py0000644000175100001770000000230214654363416022525 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """ DataSet item order test From time to time, it may be useful to change the item order, for example when deriving a dataset from another. """ # guitest: show import guidata.dataset as gds from guidata.env import execenv from guidata.qthelpers import qt_app_context class OriginalDataset(gds.DataSet): """Original dataset This is the original dataset""" param1 = gds.BoolItem("P1 | Boolean") param2 = gds.StringItem("P2 | String") param3 = gds.TextItem("P3 | Text") param4 = gds.FloatItem("P4 | Float", default=0) class DerivedDataset(OriginalDataset): """Derived dataset This is the derived dataset, with modified item order""" param5 = gds.IntItem("P5 | Int", default=0).set_pos(row=2) param6 = gds.DateItem("P6 | Date", default=0).set_pos(row=4) def test_item_order(): """Test DataSet item order""" with qt_app_context(): e = OriginalDataset() e.edit() execenv.print(e) e = DerivedDataset() e.edit() execenv.print(e) execenv.print("OK") if __name__ == "__main__": test_item_order() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/dataset/test_loadsave_hdf5.py0000644000175100001770000000211214654363416023077 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """ HDF5 I/O demo DataSet objects may be saved in HDF5 files, a universal hierarchical dataset file type. This script shows how to save in and then reload data from a HDF5 file. """ # guitest: show import os from guidata.env import execenv from guidata.io import HDF5Reader, HDF5Writer from guidata.qthelpers import qt_app_context from guidata.tests.dataset.test_all_items import Parameters def test_loadsave_hdf5(): """Test HDF5 I/O""" fname = "test.h5" with qt_app_context(): if os.path.exists(fname): os.unlink(fname) e = Parameters() if execenv.unattended or e.edit(): writer = HDF5Writer(fname) e.serialize(writer) writer.close() e = Parameters() reader = HDF5Reader(fname) e.deserialize(reader) reader.close() e.edit() os.unlink(fname) execenv.print("OK") if __name__ == "__main__": test_loadsave_hdf5() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/dataset/test_loadsave_json.py0000644000175100001770000000200414654363416023222 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """ JSON I/O demo DataSet objects may be saved in JSON files. This script shows how to save in and then reload data from a JSON file. """ # guitest: show import os from guidata.env import execenv from guidata.io import JSONReader, JSONWriter from guidata.qthelpers import qt_app_context from guidata.tests.dataset.test_all_items import Parameters def test_loadsave_json(): """Test JSON I/O""" fname = "test.json" with qt_app_context(): if os.path.exists(fname): os.unlink(fname) e = Parameters() if execenv.unattended or e.edit(): writer = JSONWriter(fname) e.serialize(writer) writer.save() e = Parameters() reader = JSONReader(fname) e.deserialize(reader) e.edit() os.unlink(fname) execenv.print("OK") if __name__ == "__main__": test_loadsave_json() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/dataset/test_rotatedlabel.py0000644000175100001770000000243414654363416023044 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """ RotatedLabel test RotatedLabel is derived from QLabel: it provides rotated text display. """ # guitest: show from qtpy.QtCore import Qt from qtpy.QtWidgets import QFrame, QGridLayout from guidata.env import execenv from guidata.qthelpers import qt_app_context, win32_fix_title_bar_background from guidata.widgets.rotatedlabel import RotatedLabel class Frame(QFrame): """Test frame""" def __init__(self, parent=None): QFrame.__init__(self, parent) win32_fix_title_bar_background(self) layout = QGridLayout() self.setLayout(layout) angle = 0 for row in range(7): for column in range(7): layout.addWidget( RotatedLabel( "Label %03d┬░" % angle, angle=angle, color=Qt.blue, bold=True ), row, column, Qt.AlignCenter, ) angle += 10 def test_rotatedlabel(): """Test RotatedLabel""" with qt_app_context(exec_loop=True): frame = Frame() frame.show() execenv.print("OK") if __name__ == "__main__": test_rotatedlabel() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1722935059.1727057 guidata-3.6.2/guidata/tests/unit/0000755000175100001770000000000014654363423016316 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/unit/__init__.py0000644000175100001770000000000014654363416020417 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/unit/test_config.py0000644000175100001770000000200414654363416021172 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """Config test""" import pytest from guidata.config import UserConfig from guidata.tests.dataset.test_all_features import Parameters @pytest.fixture() def config(): """Create a config object""" CONF = UserConfig({}) eta = Parameters() eta.write_config(CONF, "TestParameters", "") yield CONF def test_load(config): """Test load config""" eta = Parameters() eta.read_config(config, "TestParameters", "") def test_default(config): """Test default config""" eta = Parameters() eta.write_config(config, "etagere2", "") eta = Parameters() eta.read_config(config, "etagere2", "") def test_restore(config): """Test restore config""" eta = Parameters() eta.fl2 = 2 eta.integer = 6 eta.write_config(config, "etagere3", "") eta = Parameters() eta.read_config(config, "etagere3", "") assert eta.fl2 == 2.0 assert eta.integer == 6 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/unit/test_data.py0000644000175100001770000000243514654363416020646 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """Unit tests""" import unittest import guidata.dataset as gds from guidata.dataset.conv import update_dataset from guidata.env import execenv class Parameters(gds.DataSet): """Example dataset""" float1 = gds.FloatItem("float #1", min=1, max=250, help="height in cm") float2 = gds.FloatItem("float #2", min=1, max=250, help="width in cm") number = gds.IntItem("number", min=3, max=20) class TestCheck(unittest.TestCase): def test_range(self): """Test range checking of FloatItem""" e = Parameters() e.float1 = 150.0 e.float2 = 400.0 e.number = 4 errors = e.check() self.assertEqual(errors, ["float2"]) def test_typechecking(self): """Test type checking of FloatItem""" e = Parameters() e.float1 = 150 e.float2 = 400 e.number = 4.0 errors = e.check() self.assertEqual(errors, ["float1", "float2", "number"]) def test_update(self): e1 = Parameters() e2 = Parameters() e1.float1 = 23 update_dataset(e2, e1) self.assertEqual(e2.float1, 23) if __name__ == "__main__": unittest.main() execenv.print("OK") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/unit/test_dataset_from_dict.py0000644000175100001770000000175714654363416023416 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """ Generate a dataset class from a dictionary """ # guitest: show import numpy as np from guidata.dataset import create_dataset_from_dict from guidata.env import execenv TEST_DICT1 = { "a": 1, "b": 2.0, "c": "test", "d": {"x": 1, "y": 3}, "data": np.array([1, 2, 3]), } TEST_DICT2 = { "a": 1, "unsupported": [2.0, 3.0], } def test_dataset_from_dict(): """Test generate dataset class from a dictionary""" for dictionary in (TEST_DICT1,): execenv.print(dictionary) dataset = create_dataset_from_dict(dictionary) execenv.print(dataset) execenv.print(dataset.create()) execenv.print("") try: create_dataset_from_dict(TEST_DICT2) assert False, "Should have raised a ValueError" except ValueError: # This is expected pass if __name__ == "__main__": test_dataset_from_dict() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/unit/test_dataset_from_func.py0000644000175100001770000000256114654363416023420 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """ Generate a dataset class from a function signature This function is used to generate a dataset from a function signature which has type annotations. See the example below. """ # guitest: show from __future__ import annotations import numpy as np from guidata.dataset import create_dataset_from_func from guidata.env import execenv def func_ok( a: int, b: float, c: str = "test", d: dict[str, int] = {"x": 1, "y": 3} ) -> None: """A function with type annotations""" pass def func_no_type(a, b, c="test"): """A function without type annotations""" pass def func_no_default(a: int, b: float, c: str, data: np.ndarray) -> None: """A function without default values""" pass def test_dataset_from_func(): """Test generate dataset class from function""" for func in (func_ok, func_no_default): execenv.print(func.__name__) dataset = create_dataset_from_func(func) execenv.print(dataset) execenv.print(dataset.create(a=1, b=2.0)) execenv.print("") func = func_no_type try: create_dataset_from_func(func) assert False, "Should have raised a ValueError" except ValueError: # This is expected pass if __name__ == "__main__": test_dataset_from_func() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/unit/test_genreqs.py0000644000175100001770000000156614654363416021405 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """Generate install requirements RST table.""" import pytest from guidata.tests import get_path from guidata.utils import genreqs GR_PATH = get_path("genreqs") def test_compare_cfg_toml(): """Compare requirements generated from setup.cfg and pyproject.toml.""" req_toml = genreqs.extract_requirements_from_toml(GR_PATH) req_cfg = genreqs.extract_requirements_from_cfg(GR_PATH) assert req_toml == req_cfg @pytest.mark.skip(reason="This test should be run manually (development only)") def test_generate_requirement_tables(): """Test generate_requirement_tables.""" genreqs.gen_path_req_rst(GR_PATH, "guidata", ["Python>=3.8", "PyQt>=5.11"], GR_PATH) if __name__ == "__main__": test_compare_cfg_toml() test_generate_requirement_tables() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/unit/test_h5fmt.py0000644000175100001770000001430414654363416020756 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """ Test HDF5 I/O ------------- Testing various use cases of HDF5 I/O: * Serialize and deserialize a data model, handling versioning and compatibility breaks. """ from __future__ import annotations import atexit import os import os.path as osp import guidata.dataset as gds from guidata.env import execenv from guidata.io import HDF5Reader, HDF5Writer # The following class represents a data model that we want to serialize and deserialize. # This is the first version of the data model. class MyFirstDataSetV10(gds.DataSet): """First data set version 1.0""" alpha = gds.FloatItem("Alpha", default=0.0) number = gds.IntItem("Number", default=0) text = gds.StringItem("Text", default="") class MySecondDataSetV10(gds.DataSet): """Second data set version 1.0""" length = gds.FloatItem("Length", default=0.0) duration = gds.IntItem("Duration", default=0) class MyDataObjectV10: """Data object version 1.0""" def __init__(self, title: str = "") -> None: self.title = title self.metadata = {"author": "John Doe", "age": 24, "skills": ["Python", "C++"]} def __str__(self) -> str: """Return the string representation of the object""" return f"{self.__class__.__name__}({self.title})" def serialize(self, writer: HDF5Writer) -> None: """Serialize the data model to an HDF5 file""" writer.write(self.title, "title") with writer.group("metadata"): writer.write_dict(self.metadata) def deserialize(self, reader: HDF5Reader) -> None: """Deserialize the data model from an HDF5 file""" self.title = reader.read("title") with reader.group("metadata"): self.metadata = reader.read_dict() class MyDataModelV10: """Data model version 1.0""" VERSION = "1.0" MYDATAOBJCLASS = MyDataObjectV10 MYDATASETCLASS1 = MyFirstDataSetV10 MYDATASETCLASS2 = MySecondDataSetV10 def __init__(self) -> None: self.obj1 = MyDataObjectV10("first_obj_title") self.obj2 = MyDataObjectV10("second_obj_title") self.obj3 = MyDataObjectV10("third_obj_title") self.param1 = MyFirstDataSetV10() self.param2 = MySecondDataSetV10() def __str__(self) -> str: """Return the string representation of the object""" text = f"{self.__class__.__name__}:" text += f"\n {self.obj1}" text += f"\n {self.obj2}" text += f"\n {self.obj3}" text += f"\n {self.param1}" text += f"\n {self.param2}" return text def save(self, filename: str) -> None: """Save the data model from an HDF5 file""" objs = [self.obj1, self.obj2] writer = HDF5Writer(filename) writer.write(self.VERSION, "created_version") writer.write_object_list(objs, "ObjList") writer.write(self.obj3, "IndividualObj") writer.write(self.param1, "Param1") writer.write(self.param2, "Param2") writer.close() def load(self, filename: str) -> None: """Load the data model to an HDF5 file""" reader = HDF5Reader(filename) created_version = reader.read("created_version") self.obj1, self.obj2 = reader.read_object_list("ObjList", self.MYDATAOBJCLASS) self.obj3 = reader.read("IndividualObj", self.MYDATAOBJCLASS) self.param1 = reader.read("Param1", self.MYDATASETCLASS1) self.param2 = reader.read("Param2", self.MYDATASETCLASS2) execenv.print("Created version:", created_version) execenv.print("Current version:", self.VERSION) execenv.print("Model data:", self) reader.close() # The following class represents a new version of the data model: let's assume that # it replaces the previous version and we want to be able to deserialize the old # version as well as the new version. class MyFirstDataSetV11(MyFirstDataSetV10): """First data set version 1.1""" # Adding a new item beta = gds.FloatItem("Beta", default=0.0) class MySecondDataSetV11(gds.DataSet): """Second data set version 1.1""" # Redefining the data set with new items (replacing the previous version) width = gds.FloatItem("Width", default=10.0) height = gds.FloatItem("Height", default=20.0) class MyDataObjectV11(MyDataObjectV10): """Data object version 1.1""" def __init__(self, title: str = "", subtitle: str = "") -> None: super().__init__(title) self.subtitle = subtitle # New attribute def __str__(self) -> str: """Return the string representation of the object""" return f"{self.__class__.__name__}({self.title}, {self.subtitle})" def serialize(self, writer: HDF5Writer): """Serialize the data model to an HDF5 file""" super().serialize(writer) writer.write(self.subtitle, "subtitle") def deserialize(self, reader: HDF5Reader): """Deserialize the data model from an HDF5 file""" super().deserialize(reader) # Handling compatibility with the previous version is done by providing a # default value for the new attribute: self.subtitle = reader.read("subtitle", default="") class MyDataModelV11(MyDataModelV10): """Data model version 1.1""" VERSION = "1.1" MYDATAOBJCLASS = MyDataObjectV11 MYDATASETCLASS1 = MyFirstDataSetV11 MYDATASETCLASS2 = MySecondDataSetV11 def __init__(self) -> None: self.obj1 = MyDataObjectV11("first_obj_title") self.obj2 = MyDataObjectV11("second_obj_title") self.obj3 = MyDataObjectV11("third_obj_title") self.param1 = MyFirstDataSetV11() self.param2 = MySecondDataSetV11() def test_hdf5_datamodel_compatiblity(): """Test HDF5 I/O with data model compatibility""" path = osp.abspath("test.h5") atexit.register(os.unlink, path) # Serialize the first version of the data model model_v10 = MyDataModelV10() model_v10.save(path) # Deserialize the first version of the data model model_v10.load(path) # Deserialize using the new version of the data model model_v11 = MyDataModelV11() model_v11.load(path) if __name__ == "__main__": test_hdf5_datamodel_compatiblity() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/unit/test_jsonfmt.py0000644000175100001770000001036014654363416021411 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """ Test JSON I/O ------------- Testing various use cases of JSON I/O: * Serialize and deserialize a data model, handling versioning and compatibility breaks. """ from __future__ import annotations import atexit import os import os.path as osp from guidata.env import execenv from guidata.io import JSONReader, JSONWriter # The following class represents a data model that we want to serialize and deserialize. # This is the first version of the data model. class MyDataObjectV10: """Data object version 1.0""" def __init__(self, title: str = "") -> None: self.title = title def __str__(self) -> str: """Return the string representation of the object""" return f"{self.__class__.__name__}({self.title})" def serialize(self, writer: JSONWriter) -> None: """Serialize the data model to an JSON file""" writer.write(self.title, "title") def deserialize(self, reader: JSONReader) -> None: """Deserialize the data model from an JSON file""" self.title = reader.read("title") class MyDataModelV10: """Data model version 1.0""" VERSION = "1.0" MYDATAOBJCLASS = MyDataObjectV10 def __init__(self) -> None: self.obj1 = MyDataObjectV10("first_obj_title") self.obj2 = MyDataObjectV10("second_obj_title") def __str__(self) -> str: """Return the string representation of the object""" return f"{self.__class__.__name__}({self.obj1}, {self.obj2})" def save(self, filename: str) -> None: """Save the data model from an JSON file""" objs = [self.obj1, self.obj2] writer = JSONWriter(filename) writer.write(self.VERSION, "created_version") writer.write_object_list(objs, "ObjList") writer.save() def load(self, filename: str) -> None: """Load the data model to an JSON file""" reader = JSONReader(filename) created_version = reader.read("created_version") self.obj1, self.obj2 = reader.read_object_list("ObjList", self.MYDATAOBJCLASS) execenv.print("Created version:", created_version) execenv.print("Current version:", self.VERSION) execenv.print("Model data:", self) reader.close() # The following class represents a new version of the data model: let's assume that # it replaces the previous version and we want to be able to deserialize the old # version as well as the new version. class MyDataObjectV11(MyDataObjectV10): """Data object version 1.1""" def __init__(self, title: str = "", subtitle: str = "") -> None: super().__init__(title) self.subtitle = subtitle # New attribute def __str__(self) -> str: """Return the string representation of the object""" return f"{self.__class__.__name__}({self.title}, {self.subtitle})" def serialize(self, writer: JSONWriter): """Serialize the data model to an JSON file""" super().serialize(writer) writer.write(self.subtitle, "subtitle") def deserialize(self, reader: JSONReader): """Deserialize the data model from an JSON file""" super().deserialize(reader) # Handling compatibility with the previous version is done by providing a # default value for the new attribute: self.subtitle = reader.read("subtitle", default="") class MyDataModelV11(MyDataModelV10): """Data model version 1.1""" VERSION = "1.1" MYDATAOBJCLASS = MyDataObjectV11 def __init__(self) -> None: self.obj1 = MyDataObjectV11("first_obj_title") self.obj2 = MyDataObjectV11("second_obj_title") def test_json_datamodel_compatiblity(): """Test JSON I/O with data model compatibility""" path = osp.abspath("test.json") atexit.register(os.unlink, path) # atexit.register(lambda: os.unlink(path)) # Serialize the first version of the data model model_v10 = MyDataModelV10() model_v10.save(path) # Deserialize the first version of the data model model_v10.load(path) # Deserialize using the new version of the data model model_v11 = MyDataModelV11() model_v11.load(path) if __name__ == "__main__": test_json_datamodel_compatiblity() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/unit/test_no_qt.py0000644000175100001770000000131514654363416021051 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """ Test if some guidata features work without Qt (and they should!) """ import os def test_imports_without_qt(): """Test if some guidata features work without Qt""" os.environ["QT_API"] = "invalid_value" # Invalid Qt API try: # pylint: disable=unused-import # pylint: disable=import-outside-toplevel import guidata.dataset.dataitems # noqa: F401 import guidata.dataset.datatypes # noqa: F401 except ValueError as exc: raise AssertionError("guidata imports failed without Qt") from exc if __name__ == "__main__": test_imports_without_qt() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/unit/test_text.py0000644000175100001770000000133514654363416020717 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """Test in text mode""" import pytest import guidata.dataset as gds from guidata.env import execenv class Parameters(gds.DataSet): """Example dataset""" height = gds.FloatItem("Height", min=1, max=250, help="height in cm") width = gds.FloatItem("Width", min=1, max=250, help="width in cm") number = gds.IntItem("Number", min=3, max=20) @pytest.mark.skip(reason="interactive text mode: not suitable for automated testing") def test_text(): """Test text mode""" prm = Parameters() prm.text_edit() execenv.print(prm) execenv.print("OK") if __name__ == "__main__": test_text() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/unit/test_translations.py0000644000175100001770000000072514654363416022456 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """Little translation test""" # guitest: show from guidata.config import _ from guidata.env import execenv translations = (_("Some required entries are incorrect"),) def test_translations(): """Test translations""" for text in translations: execenv.print(text) execenv.print("OK") if __name__ == "__main__": test_translations() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/unit/test_updaterestoredataset.py0000644000175100001770000000374014654363416024171 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """ Update/Restore dataset from/to another dataset or dictionary """ # guitest: show import numpy as np from guidata.dataset.conv import restore_dataset, update_dataset from guidata.tests.dataset.test_all_items import Parameters def test_update_restore_dataset(): """Test update/restore dataset from/to another dataset or dictionary""" dataset = Parameters() dsdict = { "integer": 1, "float_slider": 1.0, "bool1": True, "string": "test", "floatarray": np.array([1, 2, 3]), "dictionary": {"a": 1, "b": 2}, } # Update dataset from dictionary update_dataset(dataset, dsdict) # Check dataset values assert dataset.integer == dsdict["integer"] assert dataset.float_slider == dsdict["float_slider"] assert dataset.bool1 == dsdict["bool1"] assert dataset.string == dsdict["string"] assert np.all(dataset.floatarray == dsdict["floatarray"]) assert dataset.dictionary == dsdict["dictionary"] # Update dataset from another dataset dataset2 = Parameters() update_dataset(dataset2, dataset) # Check dataset values assert dataset2.integer == dataset.integer assert dataset2.float_slider == dataset.float_slider assert dataset2.bool1 == dataset.bool1 assert dataset2.string == dataset.string assert np.all(dataset2.floatarray == dataset.floatarray) assert dataset2.dictionary == dataset.dictionary # Restore dataset from dictionary restore_dataset(dataset, dsdict) # Check dataset values assert dataset.integer == dsdict["integer"] assert dataset.float_slider == dsdict["float_slider"] assert dataset.bool1 == dsdict["bool1"] assert dataset.string == dsdict["string"] assert np.all(dataset.floatarray == dsdict["floatarray"]) assert dataset.dictionary == dsdict["dictionary"] if __name__ == "__main__": test_update_restore_dataset() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/unit/test_userconfig_app.py0000644000175100001770000000124514654363416022737 0ustar00runnerdocker# -*- coding: utf-8 -*- """ userconfig Application settings example This should create a file named ".app.ini" in your HOME directory containing: [main] version = 1.0.0 [a] b/f = 1.0 """ import guidata.dataset as gds from guidata import userconfig from guidata.env import execenv class DS(gds.DataSet): """Example dataset""" f = gds.FloatItem("F", 1.0) def test_userconfig_app(): """Test userconfig""" ds = DS("") uc = userconfig.UserConfig({}) uc.set_application("app", "1.0.0") ds.write_config(uc, "a", "b") print("Settings saved in: ", uc.filename()) execenv.print("OK") if __name__ == "__main__": test_userconfig_app() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1722935059.1727057 guidata-3.6.2/guidata/tests/widgets/0000755000175100001770000000000014654363423017005 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/widgets/__init__.py0000644000175100001770000000000014654363416021106 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/widgets/test_arrayeditor.py0000644000175100001770000000567514654363416022762 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Copyright ┬й Spyder Project Contributors # Licensed under the terms of the MIT License """ Tests for arrayeditor.py """ # guitest: show import numpy as np from guidata.env import execenv from guidata.qthelpers import exec_dialog, qt_app_context from guidata.widgets.arrayeditor import ArrayEditor def launch_arrayeditor(data, title="", xlabels=None, ylabels=None, variable_size=False): """Helper routine to launch an arrayeditor and return its result""" dlg = ArrayEditor() dlg.setup_and_check( data, title, xlabels=xlabels, ylabels=ylabels, variable_size=variable_size ) exec_dialog(dlg) return dlg.get_value() def test_arrayeditor(): """Test array editor for all supported data types""" with qt_app_context(): # Variable size version for title, data in ( ("string array", np.array(["kjrekrjkejr"])), ( "masked array", np.ma.array([[1, 0], [1, 0]], mask=[[True, False], [False, False]]), ), ("int array", np.array([1, 2, 3], dtype="int8")), ): launch_arrayeditor(data, "[Variable size] " + title, variable_size=True) for title, data in ( ("string array", np.array(["kjrekrjkejr"])), ("unicode array", np.array(["├▒├▒├▒├й├б├н├│"])), ( "masked array", np.ma.array([[1, 0], [1, 0]], mask=[[True, False], [False, False]]), ), ( "record array", np.zeros( (2, 2), { "names": ("red", "green", "blue"), "formats": (np.float32, np.float32, np.float32), }, ), ), ( "record array with titles", np.array( [(0, 0.0), (0, 0.0), (0, 0.0)], dtype=[(("title 1", "x"), "|i1"), (("title 2", "y"), ">f4")], ), ), ("bool array", np.array([True, False, True])), ("int array", np.array([1, 2, 3], dtype="int8")), ("float16 array", np.zeros((5, 5), dtype=np.float16)), ): launch_arrayeditor(data, title) for title, data, xlabels, ylabels in ( ("float array", np.random.rand(5, 5), ["a", "b", "c", "d", "e"], None), ( "complex array", np.round(np.random.rand(5, 5) * 10) + np.round(np.random.rand(5, 5) * 10) * 1j, np.linspace(-12, 12, 5), np.linspace(-12, 12, 5), ), ): launch_arrayeditor(data, title, xlabels, ylabels) arr = np.zeros((3, 3, 4)) arr[0, 0, 0] = 1 arr[0, 0, 1] = 2 arr[0, 0, 2] = 3 launch_arrayeditor(arr, "3D array") execenv.print("OK") if __name__ == "__main__": test_arrayeditor() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/widgets/test_arrayeditor_unit.py0000644000175100001770000001536314654363416024014 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Copyright ┬й Spyder Project Contributors # Licensed under the terms of the MIT License """ Unit tests for arrayeditor.py and its rows/columns insertion/deletion features """ # guitest: show from __future__ import annotations from typing import Any import numpy as np from guidata.env import execenv from guidata.qthelpers import qt_app_context from guidata.widgets.arrayeditor import ArrayEditor DEFAULT_ROW_VALUE = 1 DEFAULT_COL_VALUE = 2 DEFAULT_MASK_VALUE = True DEFAULT_INS_DEL_COUNT = 3 DEFAULT_INSERTION_INDEX = 1 def _create_3d_array() -> np.ndarray: """Creates a 3D numpy array with a single element in the first slice Returns: A 3D numpy array with a single element in the first slice """ arr_3d = np.zeros((3, 3, 4)) arr_3d[0, 0, 0] = 1 arr_3d[0, 0, 1] = 2 arr_3d[0, 0, 2] = 3 return arr_3d REQUIRED_3D_SLICE = [slice(None), slice(None), 0] BASIC_ARRAYS = ( ("string array", np.array(["kjrekrjkejr"])), ("unicode array", np.array(["├▒├▒├▒├й├б├н├│"])), ( "masked array", np.ma.array([[1, 0], [1, 0]], mask=[[True, False], [False, False]]), ), ( "record array", np.zeros( (2, 2), { "names": ("red", "green", "blue"), "formats": (np.float32, np.float32, np.float32), }, ), ), ( "record array with titles", np.array( [(0, 0.0), (0, 0.0), (0, 0.0)], dtype=[(("title 1", "x"), "|i1"), (("title 2", "y"), ">f4")], ), ), ("bool array", np.array([True, False, True])), ("int array", np.array([1, 2, 3], dtype="int8")), ("float16 array", np.zeros((5, 5), dtype=np.float16)), ("3D array", _create_3d_array()), ) LABELED_ARRAYS = ( ("float array", np.random.rand(5, 5), ["a", "b", "c", "d", "e"], None), ( "complex array", np.round(np.random.rand(5, 5) * 10) + np.round(np.random.rand(5, 5) * 10) * 1j, np.linspace(-12, 12, 5), np.linspace(-12, 12, 5), ), ) def insert_rows_and_cols( arr: np.ndarray | np.ma.MaskedArray, default_row_value: Any = DEFAULT_ROW_VALUE, default_col_value: Any = DEFAULT_COL_VALUE, index=DEFAULT_INSERTION_INDEX, insert_size=DEFAULT_INS_DEL_COUNT, default_mask_value=DEFAULT_MASK_VALUE, ) -> np.ndarray | np.ma.MaskedArray: """Inserts new rows and columns into a numpy array and returns the result. Args: arr: numpy array to be edited. default_row_value: Default value to insert. Defaults to DEFAULT_ROW_VALUE. default_col_value: Default value to insert. Defaults to DEFAULT_COL_VALUE. index: index at which to insert. Defaults to DEFAULT_INSERTION_INDEX. insert_size: number of rows/cols to insert. Defaults to DEFAULT_INS_DEL_COUNT. default_mask_value: Default mask value in case the input array is a MaskedArray. Defaults to DEFAULT_MASK_VALUE. Returns: A numpy array with the new rows and columns inserted. """ if arr.ndim == 1: arr.shape = (arr.size, 1) (default_np_row_value,) = np.array([default_row_value], dtype=arr.dtype) arr_2 = np.insert(arr, (index,) * insert_size, default_np_row_value, 0) (default_np_col_value,) = np.array([default_col_value], dtype=arr.dtype) arr_3 = np.insert(arr_2, (index,) * insert_size, default_np_col_value, 1) if isinstance(arr, np.ma.MaskedArray): mask_2 = np.insert(arr.mask, (index,) * insert_size, default_mask_value, 0) mask_3 = np.insert(mask_2, (index,) * insert_size, default_mask_value, 1) arr_3.mask = mask_3 return arr_3 MODIFIED_BASIC_ARRAYS = tuple( (name, insert_rows_and_cols(arr, 1, 2)) for name, arr in BASIC_ARRAYS ) MODIFIED_LABELED_ARRAYS = tuple( (name, insert_rows_and_cols(arr, 1, 2), labelx, labely) for (name, arr, labelx, labely) in LABELED_ARRAYS ) def launch_arrayeditor_insert( data, title="", xlabels=None, ylabels=None ) -> ArrayEditor: """Helper routine to launch an arrayeditor and return its result Args: data: numpy array to be edited. title: title of the arrayeditor. Defaults to "". xlabels: xlabels to use in the ArrayEditor. Defaults to None. ylabels: ylabels to use in the ArrayEditor. Defaults to None. Returns: An ArrayEditor instance with the given data and labels. """ dlg = ArrayEditor() dlg.setup_and_check( data, title, xlabels=xlabels, ylabels=ylabels, variable_size=True ) if data.ndim == 3: dlg.arraywidget.view.model().set_slice(REQUIRED_3D_SLICE) dlg.arraywidget.view.model().insert_row( DEFAULT_INSERTION_INDEX, DEFAULT_INS_DEL_COUNT, DEFAULT_ROW_VALUE ) dlg.arraywidget.view.model().insert_column( DEFAULT_INSERTION_INDEX, DEFAULT_INS_DEL_COUNT, DEFAULT_COL_VALUE ) return dlg def launch_arrayeditor_insert_delete( data: np.ndarray | np.ma.MaskedArray, title="", xlabels=None, ylabels=None, ) -> ArrayEditor: """Creates a new arrayeditor with given data, adds new rows and columns, and then deletes them before opening a new dialog box with the result. Args: data: numpy array to be edited. title: title of the arrayeditor. Defaults to "". xlabels: xlabels to use in the ArrayEditor. Defaults to None. ylabels: ylabels to use in the ArrayEditor. Defaults to None. Returns: An ArrayEditor instance with the given data and labels. """ dlg = launch_arrayeditor_insert(data, title, xlabels, ylabels) dlg.arraywidget.view.model().remove_row( DEFAULT_INSERTION_INDEX, DEFAULT_INS_DEL_COUNT ) dlg.arraywidget.view.model().remove_column( DEFAULT_INSERTION_INDEX, DEFAULT_INS_DEL_COUNT ) return dlg def test_arrayeditor() -> None: """Test array editor for all supported data types""" with qt_app_context(): for (title, data), (_, awaited_result) in zip( BASIC_ARRAYS, MODIFIED_BASIC_ARRAYS ): new_arr_1 = launch_arrayeditor_insert(data, title).get_value() assert (new_arr_1 == awaited_result).all() new_arr_2 = launch_arrayeditor_insert_delete(data, title).get_value() assert (new_arr_2 == data).all() # # TODO: This section can be uncommented when the support for label insertion # # alongside new row/values works # for (title, data, xlabels, ylabels), (*_, awaited_result) in zip( # LABELED_ARRAYS, MODIFIED_LABELED_ARRAYS # ): # new_arr = launch_arrayeditor_insert(data, title, xlabels, ylabels) # # assert (new_arr == awaited_result).all() execenv.print("OK") if __name__ == "__main__": test_arrayeditor() # pprint(MODIFIED_BASIC_ARRAYS) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/widgets/test_codeeditor.py0000644000175100001770000000137314654363416022545 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Copyright ┬й Spyder Project Contributors # Licensed under the terms of the MIT License """ Tests for codeeditor.py """ # guitest: show from guidata.configtools import get_icon from guidata.env import execenv from guidata.qthelpers import qt_app_context from guidata.widgets import codeeditor def test_codeeditor(): """Test Code editor.""" with qt_app_context(exec_loop=True): widget = codeeditor.CodeEditor(language="python") widget.set_text_from_file(codeeditor.__file__) widget.resize(800, 600) widget.setWindowTitle("Code editor") widget.setWindowIcon(get_icon("guidata.svg")) widget.show() execenv.print("OK") if __name__ == "__main__": test_codeeditor() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/widgets/test_collectionseditor.py0000644000175100001770000000732314654363416024152 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Copyright ┬й Spyder Project Contributors # Licensed under the terms of the MIT License """ Tests for collectionseditor.py """ # guitest: show import datetime import numpy as np from guidata.env import execenv from guidata.qthelpers import qt_app_context from guidata.widgets.collectionseditor import CollectionsEditor try: from PIL import Image as PILImage except ImportError: # PIL is not installed PILImage = None def get_test_data(): """Create test data.""" testdict = {"d": 1, "a": np.random.rand(10, 10), "b": [1, 2]} testdate = datetime.date(1945, 5, 8) test_timedelta = datetime.timedelta(days=-1, minutes=42, seconds=13) try: import pandas as pd except (ModuleNotFoundError, ImportError): test_timestamp = None test_pd_td = None test_dtindex = None test_series = None test_df = None else: test_timestamp = pd.Timestamp("1945-05-08T23:01:00.12345") test_pd_td = pd.Timedelta(days=2193, hours=12) test_dtindex = pd.date_range(start="1939-09-01T", end="1939-10-06", freq="12h") test_series = pd.Series({"series_name": [0, 1, 2, 3, 4, 5]}) test_df = pd.DataFrame( { "string_col": ["a", "b", "c", "d"], "int_col": [0, 1, 2, 3], "float_col": [1.1, 2.2, 3.3, 4.4], "bool_col": [True, False, False, True], } ) class Foobar: """ """ def __init__(self): self.text = "toto" self.testdict = testdict self.testdate = testdate foobar = Foobar() test_data = { "object": foobar, "module": np, "str": "kjkj kj k j j kj k jkj", "unicode": "├й├╣", "list": [1, 3, [sorted, 5, 6], "kjkj", None], "tuple": ([1, testdate, testdict, test_timedelta], "kjkj", None), "dict": testdict, "float": 1.2233, "int": 223, "bool": True, "array": np.random.rand(10, 10).astype(np.int64), "masked_array": np.ma.array( [[1, 0], [1, 0]], mask=[[True, False], [False, False]] ), "1D-array": np.linspace(-10, 10).astype(np.float16), "3D-array": np.random.randint(2, size=(5, 5, 5)).astype(np.bool_), "empty_array": np.array([]), "date": testdate, "datetime": datetime.datetime(1945, 5, 8, 23, 1, 0, int(1.5e5)), "timedelta": test_timedelta, "complex": 2 + 1j, "complex64": np.complex64(2 + 1j), "complex128": np.complex128(9j), "int8_scalar": np.int8(8), "int16_scalar": np.int16(16), "int32_scalar": np.int32(32), "int64_scalar": np.int64(64), "float16_scalar": np.float16(16), "float32_scalar": np.float32(32), "float64_scalar": np.float64(64), "bool_scalar": bool, "bool__scalar": np.bool_(8), "timestamp": test_timestamp, "timedelta_pd": test_pd_td, "datetimeindex": test_dtindex, "series": test_series, "ddataframe": test_df, "None": None, "unsupported1": np.arccos, # Test for Issue #3518 "big_struct_array": np.zeros( 1000, dtype=[("ID", "f8"), ("param1", "f8", 5000)] ), } if PILImage is not None: image = PILImage.fromarray(np.random.randint(256, size=(100, 100)), mode="P") test_data["image"] = image return test_data def test_collectionseditor(): """Test Collections editor.""" with qt_app_context(exec_loop=True): dialog = CollectionsEditor() dialog.setup(get_test_data()) dialog.show() execenv.print("OK") if __name__ == "__main__": test_collectionseditor() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/widgets/test_console.py0000644000175100001770000000130214654363416022056 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Copyright ┬й Spyder Project Contributors # Licensed under the terms of the MIT License """ Tests for codeeditor.py """ # guitest: show from guidata.configtools import get_icon from guidata.env import execenv from guidata.qthelpers import qt_app_context from guidata.widgets.console import Console def test_console(): """Test Console widget.""" with qt_app_context(exec_loop=True): widget = Console(debug=False, multithreaded=True) widget.resize(800, 600) widget.setWindowTitle("Console") widget.setWindowIcon(get_icon("guidata.svg")) widget.show() execenv.print("OK") if __name__ == "__main__": test_console() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/widgets/test_dataframeeditor.py0000644000175100001770000000375114654363416023561 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Copyright ┬й Spyder Project Contributors # Licensed under the terms of the MIT License """ Tests for dataframeeditor.py """ # guitest: show import numpy as np import pytest try: import pandas as pd import pandas.testing as pdt from guidata.widgets.dataframeeditor import DataFrameEditor except ImportError: # pandas is not installed pd = pdt = DataFrameEditor = None from guidata.env import execenv from guidata.qthelpers import exec_dialog, qt_app_context @pytest.mark.skipif(pd is None, reason="pandas is not installed") def test_dataframeeditor(): """DataFrame editor test""" def test_edit(data, title="", parent=None): """Test subroutine""" dlg = DataFrameEditor(parent=parent) if dlg.setup_and_check(data, title=title): exec_dialog(dlg) return dlg.get_value() else: import sys sys.exit(1) with qt_app_context(): df1 = pd.DataFrame( [ [True, "bool"], [1 + 1j, "complex"], ["test", "string"], [1.11, "float"], [1, "int"], [np.random.rand(3, 3), "Unkown type"], ["Large value", 100], ["├б├й├н", "unicode"], ], index=["a", "b", np.nan, np.nan, np.nan, "c", "Test global max", "d"], columns=[np.nan, "Type"], ) out = test_edit(df1) pdt.assert_frame_equal(df1, out) result = pd.Series([True, "bool"], index=[np.nan, "Type"], name="a") out = test_edit(df1.iloc[0]) pdt.assert_series_equal(result, out) df1 = pd.DataFrame(np.random.rand(100100, 10)) out = test_edit(df1) pdt.assert_frame_equal(out, df1) series = pd.Series(np.arange(10), name=0) out = test_edit(series) pdt.assert_series_equal(series, out) execenv.print("OK") if __name__ == "__main__": test_dataframeeditor() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/widgets/test_importwizard.py0000644000175100001770000000134714654363416023160 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Copyright ┬й Spyder Project Contributors # Licensed under the terms of the MIT License """ Tests for importwizard.py """ # guitest: show import pytest from guidata.env import execenv from guidata.qthelpers import exec_dialog, qt_app_context from guidata.widgets.importwizard import ImportWizard @pytest.fixture() def text(): """Return text to test""" return "17/11/1976\t1.34\n14/05/09\t3.14" def test_importwizard(text): """Test""" with qt_app_context(): dialog = ImportWizard(None, text) if exec_dialog(dialog): execenv.print(dialog.get_data()) execenv.print("OK") if __name__ == "__main__": test_importwizard("17/11/1976\t1.34\n14/05/09\t3.14") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/widgets/test_objecteditor.py0000644000175100001770000000275614654363416023107 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Copyright ┬й Spyder Project Contributors # Licensed under the terms of the MIT License """ Tests for objecteditor.py """ # guitest: show import datetime import numpy as np try: from PIL import Image except ImportError: # PIL is not installed Image = None from guidata.env import execenv from guidata.qthelpers import qt_app_context from guidata.widgets.objecteditor import oedit def test_objecteditor(): """Run object editor test""" with qt_app_context(): example = { "str": "kjkj kj k j j kj k jkj", "list": [1, 3, 4, "kjkj", None], "dict": {"d": 1, "a": np.random.rand(10, 10), "b": [1, 2]}, "float": 1.2233, "array": np.random.rand(10, 10), "date": datetime.date(1945, 5, 8), "datetime": datetime.datetime(1945, 5, 8), } if Image is not None: data = np.random.randint(255, size=(100, 100)).astype("uint8") image = Image.fromarray(data) example["image"] = image image = oedit(image) class Foobar: """ """ def __init__(self): self.text = "toto" foobar = Foobar() execenv.print(oedit(foobar)) execenv.print(oedit(example)) execenv.print(oedit(np.random.rand(10, 10))) execenv.print(oedit(oedit.__doc__)) execenv.print(example) execenv.print("OK") if __name__ == "__main__": test_objecteditor() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/tests/widgets/test_theme.py0000644000175100001770000001034714654363416021527 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """ Test dark/light theme switching """ from __future__ import annotations import os import sys from typing import Literal import pytest from qtpy import QtCore as QC from qtpy import QtWidgets as QW from guidata import qthelpers as qth from guidata.widgets.codeeditor import CodeEditor from guidata.widgets.console import Console class BaseTestWidget(QW.QWidget): """Base class for testing dark/light theme switching""" SIZE = (1200, 600) def __init__(self, default: Literal["light", "dark", "auto"] = qth.AUTO) -> None: super(BaseTestWidget, self).__init__() self.resize(*self.SIZE) self.default_theme = default self.combo: QW.QComboBox | None = None self.setWindowTitle(self.__doc__) self.grid_layout = QW.QGridLayout() self.setLayout(self.grid_layout) self.setup_widgets() if default != qth.AUTO: self.change_color_mode(default) def setup_widgets(self): """Setup widgets""" label = QW.QLabel("Select color mode:") self.combo = QW.QComboBox() self.combo.addItems(qth.COLOR_MODES) self.combo.setCurrentText(self.default_theme) self.combo.currentTextChanged.connect(self.change_color_mode) self.combo.setSizePolicy(QW.QSizePolicy.Expanding, QW.QSizePolicy.Minimum) self.combo.setToolTip( "Select color mode:" "
  • auto: follow system settings
  • " "
  • light: use light theme
  • " "
  • dark: use dark theme
" ) hlayout = QW.QHBoxLayout() hlayout.addWidget(label) hlayout.addWidget(self.combo) self.grid_layout.addLayout(hlayout, 0, 0, 1, -1) def change_color_mode(self, mode: str) -> None: """Change color mode""" qth.set_color_mode(mode) def closeEvent(self, event): """Close event""" self.console.close() event.accept() class TestWidget(BaseTestWidget): """Testing color mode switching for guidata's widgets: code editor and console""" def __init__(self, default: Literal["light", "dark", "auto"] = qth.AUTO) -> None: self.editor: CodeEditor | None = None self.console: Console | None = None super().__init__(default) qth.win32_fix_title_bar_background(self) def setup_widgets(self): """Setup widgets""" super().setup_widgets() self.editor = CodeEditor(self) self.console = Console(self, debug=False) for widget in (self.editor, self.console): widget.setSizePolicy(QW.QSizePolicy.Expanding, QW.QSizePolicy.Expanding) self.editor.setPlainText("Change theme using the combo box above" + os.linesep) self.add_info_to_codeeditor() self.console.execute_command("print('Console output')") self.grid_layout.addWidget(self.editor, 1, 0) self.grid_layout.addWidget(self.console, 1, 1) def add_info_to_codeeditor(self): """Add current color mode and theme to the code editor, with a prefix with date and time""" self.editor.setPlainText( os.linesep.join( [ self.editor.toPlainText(), "", f"{QC.QDateTime.currentDateTime().toString()}:", f" Current color mode: {qth.get_color_mode()}", f" Current theme: {qth.get_color_theme()}", ] ) ) def change_color_mode(self, mode: str) -> None: """Change color mode""" super().change_color_mode(mode) for widget in (self.editor, self.console): widget.update_color_mode() self.add_info_to_codeeditor() @pytest.mark.skipif(reason="Not suitable for automated testing") def test_dark_light_themes( default: Literal["light", "dark", "auto"] | None = None, ) -> None: """Test dark/light theme switching""" with qth.qt_app_context(exec_loop=True): widget = TestWidget(default=qth.AUTO if default is None else default) widget.show() if __name__ == "__main__": test_dark_light_themes(None if len(sys.argv) < 2 else sys.argv[1]) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/userconfig.py0000644000175100001770000003716314654363416016727 0ustar00runnerdocker#!/usr/bin/env python # -*- coding: utf-8 -*- # userconfig License Agreement (MIT License) # ------------------------------------------ # # Copyright ┬й 2009-2012 Pierre Raybaut # # 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. """ User configuration management ----------------------------- The ``guidata.userconfig`` module provides user configuration file (.ini file) management features based on ``ConfigParser`` (standard Python library). It is the exact copy of the open-source package `userconfig` (MIT license). This module provides the following functions and classes: * :py:func:`get_home_dir`: return user home directory * :py:func:`get_config_basedir`: return user configuration base directory * :py:class:`UserConfig`: user configuration file management class .. autofunction:: get_home_dir .. autofunction:: get_config_basedir .. autoclass:: UserConfig :members: """ import configparser as cp import os import os.path as osp import re import sys import time from typing import Any def try_remove_file(path: str, wait_time: float = 0.05, retries: int = 20) -> None: """Try to remove a file, waiting if necessary. Args: path: The path of the file to remove. wait_time: The time in seconds to wait between checks. retries: The number of times to retry before giving up. Raises: IOError: If the file cannot be removed. """ if os.path.isfile(path): attempt = 0 while attempt < retries: try: os.remove(path) return except IOError: time.sleep(wait_time) attempt += 1 raise IOError(f"Unable to remove file {path} due to file lock") def get_home_dir() -> str: """Return user home directory""" try: # expanduser() returns a raw byte string which needs to be # decoded with the codec that the OS is using to represent # file paths. path = os.fsdecode(osp.expanduser("~")) except Exception: path = "" if osp.isdir(path): return path else: # Get home from alternative locations for env_var in ("HOME", "USERPROFILE", "TMP"): # os.environ.get() returns a raw byte string which needs to be # decoded with the codec that the OS is using to represent # environment variables. path = os.fsdecode(os.environ.get(env_var, "")) if osp.isdir(path): return path else: path = "" if not path: raise RuntimeError( "Please set the environment variable HOME to " "your user/home directory path." ) def get_config_basedir() -> str: """Return user configuration base directory.""" if sys.platform.startswith("linux"): # Follow the XDG standard to save settings home = os.environ.get("XDG_CONFIG_HOME", "") if not home: home = osp.join(get_home_dir(), ".config") if not osp.isdir(home): os.makedirs(home) return home return get_home_dir() class NoDefault: """ Custom object used to explicitly mark that no default value were provided. """ pass class UserConfig(cp.ConfigParser): """ UserConfig class, based on ConfigParser name: name of the config options: dictionary containing options *or* list of tuples (section_name, options) Note that "get" and "set" arguments number and type differ from the overriden methods """ default_section_name = "main" def __init__(self, defaults): cp.ConfigParser.__init__(self) self.name = "none" self.raw = 0 # 0 = substitutions are enabled / 1 = raw config parser assert isinstance(defaults, dict) for _key, val in list(defaults.items()): assert isinstance(val, dict) if self.default_section_name not in defaults: defaults[self.default_section_name] = {} self.defaults = defaults self.reset_to_defaults(save=False) self.check_default_values() def update_defaults(self, defaults): """Update the default configuration :param defaults: dict section -> dict {option: default value} """ for key, sectdict in list(defaults.items()): if key not in self.defaults: self.defaults[key] = sectdict else: self.defaults[key].update(sectdict) self.reset_to_defaults(save=False) def save(self): """Save the configuration.""" # In any case, the resulting config is saved in config file: self.__save() def set_application(self, name, version, load=True, raw_mode=False): """ Set the application name and version :param name: name of the application :param version: current version in format "X.Y.Z" :param load: If True, load the configuration from dict :param raw_mode: If True, enable raw mode of ConfigParser """ self.name = name self.raw = 1 if raw_mode else 0 if (version is not None) and (re.match(r"^(\d+).(\d+).(\d+)", version) is None): raise RuntimeError( f"Version number {version!r} is incorrect - must be in X.Y.Z format" ) if load: # If config file already exists, it overrides Default options: self.__load() if version != self.get_version(version): # Version has changed -> overwriting .ini file self.reset_to_defaults(save=False) self.__remove_deprecated_options() # Set new version number self.set_version(version, save=False) if self.defaults is None: # If no defaults are defined, set .ini file settings as default self.set_as_defaults() def check_default_values(self): """Check the static options for forbidden data types""" errors = [] def _check(key, value): if value is None: return if isinstance(value, dict): for k, v in list(value.items()): _check(key + "{}", k) _check(key + "/" + k, v) elif isinstance(value, (list, tuple)): for v in value: _check(key + "[]", v) else: if not isinstance(value, (bool, int, float, str)): errors.append(f"Invalid value for {key}: {value}") for name, section in list(self.defaults.items()): assert isinstance(name, str) for key, value in list(section.items()): _check(key, value) if errors: for err in errors: print(err) raise ValueError("Invalid default values") def get_version(self, version="0.0.0"): """Return configuration (not application!) version""" return self.get(self.default_section_name, "version", version) def set_version(self, version="0.0.0", save=True): """Set configuration (not application!) version""" self.set(self.default_section_name, "version", version, save=save) def __load(self): """ Load config from the associated .ini file """ try: self.read(self.filename(), encoding="utf-8") except cp.MissingSectionHeaderError: print("Warning: File contains no section headers.") def __remove_deprecated_options(self): """ Remove options which are present in the .ini file but not in defaults """ for section in self.sections(): for option, _ in self.items(section, raw=self.raw): if self.get_default(section, option) is NoDefault: self.remove_option(section, option) if len(self.items(section, raw=self.raw)) == 0: self.remove_section(section) def __save(self): """ Save config into the associated .ini file """ fname = self.filename() if osp.isfile(fname): try_remove_file(fname, wait_time=0.05, retries=20) os.makedirs(osp.dirname(fname), mode=0o700, exist_ok=True) with open(fname, "w", encoding="utf-8") as configfile: self.write(configfile) def get_path(self, basename: str) -> str: """Return filename path inside configuration directory""" config_dir = osp.join(get_config_basedir(), f".{self.name}") if not osp.isdir(config_dir): os.makedirs(config_dir) return osp.join(config_dir, basename) def filename(self) -> str: """Return configuration file name""" return self.get_path(f"{self.name}.ini") def cleanup(self): """ Remove .ini file associated to config """ os.remove(self.filename()) def set_as_defaults(self): """ Set defaults from the current config """ self.defaults = {} for section in self.sections(): secdict = {} for option, value in self.items(section, raw=self.raw): secdict[option] = value self.defaults[section] = secdict def reset_to_defaults(self, save=True, verbose=False): """ Reset config to Default values """ for section, options in list(self.defaults.items()): for option in options: value = options[option] self.__set(section, option, value, verbose) if save: self.__save() def __check_section_option(self, section, option): """ Private method to check section and option types """ if section is None: section = self.default_section_name elif not isinstance(section, str): raise RuntimeError("Argument 'section' must be a string") if not isinstance(option, str): raise RuntimeError("Argument 'option' must be a string") return section def get_default(self, section, option): """ Get Default value for a given (section, option) Useful for type checking in 'get' method """ section = self.__check_section_option(section, option) options = self.defaults.get(section, {}) return options.get(option, NoDefault) def get(self, section, option, default: Any = NoDefault, raw=None, **kwargs): """ Get an option section=None: attribute a default section name default: default value (if not specified, an exception will be raised if option doesn't exist) """ if raw is None: raw = self.raw section = self.__check_section_option(section, option) if not self.has_section(section): if default is NoDefault: raise RuntimeError(f"Unknown section {section!r}") else: self.add_section(section) if not self.has_option(section, option): if default is NoDefault: raise RuntimeError(f"Unknown option {section!r}/{option!r}") else: self.set(section, option, default) return default value = cp.ConfigParser.get(self, section, option, raw=raw) default_value = self.get_default(section, option) if isinstance(default_value, bool): value = eval(value) elif isinstance(default_value, float): value = float(value) elif isinstance(default_value, int): value = int(value) elif isinstance(default_value, str): pass else: try: # lists, tuples, ... value = eval(value) except Exception: pass return value def get_section(self, section): """Returns configuration values of the given section. The returned dict includes unset default values. :param section: section name :return: dict option name -> value """ sect = self.defaults.get(section, {}).copy() for opt in self.options(section): sect[opt] = self.get(section, opt) return sect def __set(self, section, option, value, verbose): """ Private set method """ if not self.has_section(section): self.add_section(section) if not isinstance(value, str): value = repr(value) if verbose: print("{}[ {} ] = {}".format(section, option, value)) cp.ConfigParser.set(self, section, option, value) def set_default(self, section, option, default_value): """ Set Default value for a given (section, option) -> called when a new (section, option) is set and no default exists """ section = self.__check_section_option(section, option) options = self.defaults.setdefault(section, {}) options[option] = default_value def set(self, section, option, value, verbose=False, save=True): """ Set an option section=None: attribute a default section name """ section = self.__check_section_option(section, option) default_value = self.get_default(section, option) if default_value is NoDefault: default_value = value self.set_default(section, option, default_value) if isinstance(default_value, bool): value = bool(value) elif isinstance(default_value, float): value = float(value) elif isinstance(default_value, int): value = int(value) elif not isinstance(default_value, str): value = repr(value) self.__set(section, option, value, verbose) if save: self.__save() def remove_section(self, section): """Remove the given section and save the configuration.""" cp.ConfigParser.remove_section(self, section) self.__save() def remove_option(self, section, option): """ Remove the given option from the given section and save the configuration. """ cp.ConfigParser.remove_option(self, section, option) self.__save() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1722935059.1727057 guidata-3.6.2/guidata/utils/0000755000175100001770000000000014654363423015335 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/utils/__init__.py0000644000175100001770000000003014654363416017441 0ustar00runnerdocker# -*- coding: utf-8 -*- ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/utils/encoding.py0000644000175100001770000001234514654363416017504 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Copyright ┬й Spyder Project Contributors # Licensed under the terms of the MIT License # (see spyder/__init__.py for details) """ Text encoding utilities, text file I/O Functions 'get_coding', 'decode', 'encode' come from Eric4 source code (Utilities/__init___.py) Copyright ┬й 2003-2009 Detlev Offenbach """ from __future__ import annotations import os import re from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF32 # ------------------------------------------------------------------------------ # Functions for encoding and decoding *text data* itself, usually originating # from or destined for the *contents* of a file. # ------------------------------------------------------------------------------ # Codecs for working with files and text. CODING_RE = re.compile(r"coding[:=]\s*([-\w_.]+)") CODECS = [ "utf-8", "iso8859-1", "iso8859-15", "ascii", "koi8-r", "cp1251", "koi8-u", "iso8859-2", "iso8859-3", "iso8859-4", "iso8859-5", "iso8859-6", "iso8859-7", "iso8859-8", "iso8859-9", "iso8859-10", "iso8859-13", "iso8859-14", "latin-1", "utf-16", ] def get_coding(text: str) -> str | None: """ Function to get the coding of a text. @param text text to inspect (string) @return coding string """ for line in text.splitlines()[:2]: try: result = CODING_RE.search(str(line)) except UnicodeDecodeError: # This could fail because str assume the text # is utf8-like and we don't know the encoding to give # it to str pass else: if result: codec = result.group(1) # sometimes we find a false encoding that can # result in errors if codec in CODECS: return codec def decode(text: bytes) -> tuple[str, str] | tuple[str, str] | tuple[str, str]: """ Function to decode a text. @param text text to decode (bytes) @return decoded text and encoding """ try: if text.startswith(BOM_UTF8): # UTF-8 with BOM return str(text[len(BOM_UTF8) :], "utf-8"), "utf-8-bom" elif text.startswith(BOM_UTF16): # UTF-16 with BOM return str(text[len(BOM_UTF16) :], "utf-16"), "utf-16" elif text.startswith(BOM_UTF32): # UTF-32 with BOM return str(text[len(BOM_UTF32) :], "utf-32"), "utf-32" coding = get_coding(text) if coding: return str(text, coding), coding except (UnicodeError, LookupError): pass # Assume UTF-8 try: return str(text, "utf-8"), "utf-8-guessed" except (UnicodeError, LookupError): pass # Assume Latin-1 (behaviour before 3.7.1) return str(text, "latin-1"), "latin-1-guessed" def encode(text: str, orig_coding: str) -> tuple[bytes, str] | tuple[bytes, str]: """ Function to encode a text. @param text text to encode (string) @param orig_coding type of the original coding (string) @return encoded text and encoding """ if orig_coding == "utf-8-bom": return BOM_UTF8 + text.encode("utf-8"), "utf-8-bom" # Try saving with original encoding if orig_coding: try: return text.encode(orig_coding), orig_coding except (UnicodeError, LookupError): pass # Try declared coding spec coding = get_coding(text) if coding: try: return text.encode(coding), coding except (UnicodeError, LookupError): raise RuntimeError("Incorrect encoding (%s)" % coding) if ( orig_coding and orig_coding.endswith("-default") or orig_coding.endswith("-guessed") ): coding = orig_coding.replace("-default", "") coding = orig_coding.replace("-guessed", "") try: return text.encode(coding), coding except (UnicodeError, LookupError): pass # Try saving as ASCII try: return text.encode("ascii"), "ascii" except UnicodeError: pass # Save as UTF-8 without BOM return text.encode("utf-8"), "utf-8" def write(text: str, filename: str, encoding: str = "utf-8", mode: str = "wb") -> str: """ Write 'text' to file ('filename') assuming 'encoding' Return (eventually new) encoding """ text, encoding = encode(text, encoding) with open(filename, mode) as textfile: textfile.write(text) return encoding def writelines( lines: list[str], filename: str, encoding: str = "utf-8", mode: str = "wb" ) -> str: """ Write 'lines' to file ('filename') assuming 'encoding' Return (eventually new) encoding """ return write(os.linesep.join(lines), filename, encoding, mode) def read(filename: str, encoding: str = "utf-8") -> tuple[str, str]: """ Read text from file ('filename') Return text and encoding """ text, encoding = decode(open(filename, "rb").read()) return text, encoding def readlines(filename: str, encoding: str = "utf-8") -> tuple[list[str], str]: """ Read lines from file ('filename') Return lines and encoding """ text, encoding = read(filename, encoding) return text.split(os.linesep), encoding ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/utils/genreqs.py0000644000175100001770000001455714654363416017371 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Copyright (c) 2023, Codra, Pierre Raybaut. # Licensed under the terms of the BSD 3-Clause """ genreqs ======= Module for generating requirements tables in documentation. This module is derived from the `genreqs.py` module of the `DataLab` project. """ from __future__ import annotations import configparser as cp import os import os.path as osp import re from types import ModuleType import requests import tomli def extract_requirements_from_toml(path: str) -> dict[str, list[str]] | None: """Extract requirements from pyproject.toml file. Args: path (str): Path to folder containing pyproject.toml file Returns: dict[str, list[str]]: Dictionary of requirements (main project and optional dependencies) or None, if pyproject.toml file does not exist """ filepath = osp.join(path, "pyproject.toml") if not osp.isfile(filepath): return None with open(filepath, "rb") as f: data = tomli.load(f) requirements = {} # Get main project dependencies prj = data["project"] requirements["main"] = ["Python" + prj["requires-python"]] + prj["dependencies"] # Get optional dependencies optional_deps = prj.get("optional-dependencies", {}) requirements.update(optional_deps) return requirements def get_value_from_cfg(config: cp.ConfigParser, section: str, option: str) -> str: """Get value from setup.cfg's ConfigParser object. Args: config (cp.ConfigParser): ConfigParser object section (str): Section name option (str): Option name Returns: str: Value """ return config[section][option].strip().splitlines(False) def extract_requirements_from_cfg(path: str) -> dict[str, list[str]] | None: """Extract requirements from setup.cfg file. Args: filepath (str): Path to folder containing setup.cfg file Returns: dict[str, list[str]]: Dictionary of requirements (main project and optional dependencies) or None, if setup.cfg file does not exist """ filepath = osp.join(path, "setup.cfg") if not osp.isfile(filepath): return None config = cp.ConfigParser() config.read(filepath) requirements = { "main": ["Python" + get_value_from_cfg(config, "options", "python_requires")[0]] + get_value_from_cfg(config, "options", "install_requires"), } for option in config.options("options.extras_require"): requirements[option] = get_value_from_cfg( config, "options.extras_require", option ) return requirements def get_package_summary_from_pypi(package: str) -> str: """Get package summary from PyPI. Args: package (str): Package name Returns: str: Package summary or empty string if package not found on PyPI """ if package == "Python": return "Python programming language" try: response = requests.get(f"https://pypi.org/pypi/{package}/json") except requests.exceptions.ConnectionError: return "" if response.status_code != 200: return "" return response.json()["info"]["summary"] def reqlist_to_table(reqs: list[str]) -> str: """Convert requirements list to RST table. Args: reqs (list[str]): Requirements list Returns: str: RST table """ requirements = [ ".. list-table::", " :header-rows: 1", " :align: left", "", " * - Name", " - Version", " - Summary", ] modlist = [] for req in reqs: try: mod = re.split(" ?(>=|<=|=|<|>)", req)[0] ver = req[len(mod) :] except ValueError: mod, ver = req, "" if mod.lower() in modlist: continue modlist.append(mod.lower()) requirements.append(" * - " + mod) requirements.append(" - " + ver) summary = get_package_summary_from_pypi(mod) requirements.append(" - " + summary) return "\n".join(requirements) def gen_path_req_rst( path: str, modname: str, additional_reqs: list[str], destpath: str | None = None ) -> None: """Generate install 'requirements.rst' reStructuredText text. This reStructuredText text is written in a file which is by default located in the `doc` folder of the module. Args: path (str): Path to folder containing pyproject.toml or setup.cfg file modname (str): Module name additional_reqs (list[str]): Additional requirements destpath (str): Destination path for requirements.rst file (optional). """ requirements = extract_requirements_from_toml(path) if requirements is None: requirements = extract_requirements_from_cfg(path) if requirements is None: raise RuntimeError( "Could not find pyproject.toml or setup.cfg file in %s" % path ) requirements = extract_requirements_from_toml(path) if requirements is None: requirements = extract_requirements_from_cfg(path) if requirements is None: raise RuntimeError( "Could not find pyproject.toml or setup.cfg file in %s" % path ) text = f"""The :mod:`{modname}` package requires the following Python modules: {reqlist_to_table(requirements["main"]+additional_reqs)}""" for category, title in ( ("dev", "development"), ("doc", "building the documentation"), ("test", "running test suite"), ): if category in requirements: text += f""" Optional modules for {title}: {reqlist_to_table(requirements[category])}""" if destpath is None: destpath = osp.join(path, "doc") with open(osp.join(destpath, "requirements.rst"), "w") as fdesc: fdesc.write(text) def gen_module_req_rst( module: ModuleType, additional_reqs: list[str], destpath: str | None = None ) -> None: """Generate install 'requirements.rst' reStructuredText text. This reStructuredText text is written in a file which is by default located in the `doc` folder of the module. Args: module (ModuleType): Module to generate requirements for additional_reqs (list[str]): Additional requirements destpath (str): Destination path for requirements.rst file (optional). """ path = osp.abspath(osp.join(osp.dirname(module.__file__), os.pardir)) gen_path_req_rst(path, module.__name__, additional_reqs, destpath) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/utils/gettext_helpers.py0000644000175100001770000000747514654363416021134 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) import os import os.path as osp import subprocess import sys if os.name == "nt": # Find pygettext.py source on a windows install pygettext = ["python", osp.join(sys.prefix, "Tools", "i18n", "pygettext.py")] msgfmt = ["python", osp.join(sys.prefix, "Tools", "i18n", "msgfmt.py")] else: pygettext = ["pygettext"] msgfmt = ["msgfmt"] def get_files(modname): if not osp.isdir(modname): return [modname] files = [] for dirname, _dirnames, filenames in os.walk(modname): files += [ osp.join(dirname, f) for f in filenames if f.endswith(".py") or f.endswith(".pyw") ] for dirname, _dirnames, filenames in os.walk("tests"): files += [ osp.join(dirname, f) for f in filenames if f.endswith(".py") or f.endswith(".pyw") ] return files def get_lang(modname): localedir = osp.join(modname, "locale") for _dirname, dirnames, _filenames in os.walk(localedir): break # we just want the list of first level directories return dirnames def do_rescan(modname): files = get_files(modname) dirname = modname do_rescan_files(files, modname, dirname) def do_rescan_files(files, modname, dirname): localedir = osp.join(dirname, "locale") potfile = modname + ".pot" subprocess.call( pygettext + [ ##"-D", # Extract docstrings "-o", potfile, # Nom du fichier pot "-p", localedir, # dest ] + files ) for lang in get_lang(dirname): pofilepath = osp.join(localedir, lang, "LC_MESSAGES", modname + ".po") potfilepath = osp.join(localedir, potfile) print("Updating...", pofilepath) if not osp.exists(osp.join(localedir, lang, "LC_MESSAGES")): os.mkdir(osp.join(localedir, lang, "LC_MESSAGES")) if not osp.exists(pofilepath): outf = open(pofilepath, "w") outf.write("# -*- coding: utf-8 -*-\n") data = open(potfilepath).read() data = data.replace("charset=CHARSET", "charset=utf-8") data = data.replace( "Content-Transfer-Encoding: ENCODING", "Content-Transfer-Encoding: utf-8", ) outf.write(data) else: print("merge...") subprocess.call(["msgmerge", "-o", pofilepath, pofilepath, potfilepath]) def do_compile(modname, dirname=None): if dirname is None: dirname = modname localedir = osp.join(dirname, "locale") for lang in get_lang(dirname): pofilepath = osp.join(localedir, lang, "LC_MESSAGES", modname + ".po") subprocess.call(msgfmt + [pofilepath]) def main(modname): if len(sys.argv) < 2: cmd = "help" else: cmd = sys.argv[1] # lang = get_lang( modname ) if cmd == "help": print("Available commands:") print(" help : this message") print(" help_gettext : pygettext --help") print(" help_msgfmt : msgfmt --help") print(" scan : rescan .py files and updates existing .po files") print(" compile : recompile .po files") print() print("Pour fonctionner ce programme doit ├кtre lanc├й depuis") print("la racine du module") print("Traductions disponibles:") for i in get_lang(modname): print(i) elif cmd == "help_gettext": subprocess.call(pygettext + ["--help"]) elif cmd == "help_msgfmt": subprocess.call(msgfmt + ["--help"]) elif cmd == "scan": print("Updating pot files") do_rescan(modname) elif cmd == "compile": print("Builtin .mo files") do_compile(modname) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/utils/misc.py0000644000175100001770000002565714654363416016663 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) # pylint: disable=C0103 """ Miscellaneous utility functions ------------------------------- Running programs ^^^^^^^^^^^^^^^^ .. autofunction:: run_program .. autofunction:: is_program_installed .. autofunction:: run_shell_command Strings ^^^^^^^ .. autofunction:: to_string .. autofunction:: decode_fs_string """ from __future__ import annotations import collections.abc import ctypes import locale import os import os.path as osp import subprocess import sys from typing import Any, Type from guidata.userconfig import get_home_dir # MARK: Strings, Locale ---------------------------------------------------------------- def to_string(obj: Any) -> str: """Convert to string, trying utf-8 then latin-1 codec Args: obj (Any): Object to convert to string Returns: str: String representation of object """ if isinstance(obj, bytes): try: return obj.decode() except UnicodeDecodeError: return obj.decode("latin-1") try: return str(obj) except UnicodeDecodeError: return str(obj, encoding="latin-1") def decode_fs_string(string: str) -> str: """Convert string from file system charset to unicode Args: string (str): String to convert Returns: str: Converted string """ charset = sys.getfilesystemencoding() if charset is None: charset = locale.getpreferredencoding() return string.decode(charset) def get_system_lang() -> str | None: """ Retrieves the system language name. This function uses `locale.getlocale()` to obtain the locale name based on the current user's settings. If that fails on Windows (e.g. for frozen applications), it uses the Win32 API function `GetUserDefaultLocaleName` to obtain the locale name. Returns: The locale name in a format like 'en_US', or None if the function fails to retrieve the locale name. """ lang = locale.getlocale()[0] if lang is None and os.name == "nt": kernel32 = ctypes.WinDLL("kernel32", use_last_error=True) GetUserDefaultLocaleName = kernel32.GetUserDefaultLocaleName GetUserDefaultLocaleName.argtypes = [ctypes.c_wchar_p, ctypes.c_int] GetUserDefaultLocaleName.restype = ctypes.c_int locale_name = ctypes.create_unicode_buffer(85) if GetUserDefaultLocaleName(locale_name, 85): lang = locale_name.value.replace("-", "_") return lang # MARK: Interface checking ------------------------------------------------------------- def assert_interface_supported(klass: Type, iface: Type) -> None: """Makes sure a class supports an interface Args: klass (Type): The class. iface (Type): The interface. Raises: AssertionError: If the class does not support the interface. """ for name, func in list(iface.__dict__.items()): if name == "__inherits__": continue if isinstance(func, collections.abc.Callable): assert hasattr(klass, name), "Attribute %s missing from %r" % (name, klass) imp_func = getattr(klass, name) imp_code = imp_func.__code__ code = func.__code__ imp_nargs = imp_code.co_argcount nargs = code.co_argcount if imp_code.co_varnames[:imp_nargs] != code.co_varnames[:nargs]: assert False, "Arguments of %s.%s differ from interface: " "%r!=%r" % ( klass.__name__, imp_func.__name__, imp_code.co_varnames[:imp_nargs], code.co_varnames[:nargs], ) else: pass # should check class attributes for consistency def assert_interfaces_valid(klass: Type) -> None: """Makes sure a class supports the interfaces it declares Args: klass (Type): The class. Raises: AssertionError: If the class does not support the interfaces it declares. """ assert hasattr(klass, "__implements__"), "Class doesn't implements anything" for iface in klass.__implements__: assert_interface_supported(klass, iface) if hasattr(iface, "__inherits__"): base = iface.__inherits__() assert issubclass(klass, base), "%s should be a subclass of %s" % ( klass, base, ) # MARK: Module, scripts, programs ------------------------------------------------------ def get_module_path(modname: str) -> str: """Return module *modname* base path. Args: modname (str): The module name. Returns: str: The module base path. """ module = sys.modules.get(modname, __import__(modname)) return osp.abspath(osp.dirname(module.__file__)) def is_program_installed(basename: str) -> str | None: """Return program absolute path if installed in PATH, otherwise None. Args: basename (str): The program base name. Returns: str | None: The program absolute path if installed in PATH, otherwise None. """ for path in os.environ["PATH"].split(os.pathsep): abspath = osp.join(path, basename) if osp.isfile(abspath): return abspath def run_program( name, args: str = "", cwd: str = None, shell: bool = True, wait: bool = False ) -> None: """Run program in a separate process. Args: name (str): The program name. args (str): The program arguments. Defaults to ""."" cwd (str): The current working directory. Defaults to None. shell (bool): If True, run program in a shell. Defaults to True. wait (bool): If True, wait for program to finish. Defaults to False. Raises: RuntimeError: If program is not installed. """ path = is_program_installed(name) if not path: raise RuntimeError("Program %s was not found" % name) command = [path] if args: command.append(args) if wait: subprocess.call(" ".join(command), cwd=cwd, shell=shell) else: subprocess.Popen(" ".join(command), cwd=cwd, shell=shell) class ProgramError(Exception): """Exception raised when a shell command failed to execute.""" pass def alter_subprocess_kwargs_by_platform(**kwargs): """ Given a dict, populate kwargs to create a generally useful default setup for running subprocess processes on different platforms. For example, `close_fds` is set on posix and creation of a new console window is disabled on Windows. This function will alter the given kwargs and return the modified dict. """ kwargs.setdefault("close_fds", os.name == "posix") if os.name == "nt": CONSOLE_CREATION_FLAGS = 0 # Default value # See: https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863%28v=vs.85%29.aspx CREATE_NO_WINDOW = 0x08000000 # We "or" them together CONSOLE_CREATION_FLAGS |= CREATE_NO_WINDOW kwargs.setdefault("creationflags", CONSOLE_CREATION_FLAGS) return kwargs def run_shell_command(cmdstr, **subprocess_kwargs): """ Execute the given shell command. Note that args and kwargs will be passed to the subprocess call. If 'shell' is given in subprocess_kwargs it must be True, otherwise ProgramError will be raised. If 'executable' is not given in subprocess_kwargs, it will be set to the value of the SHELL environment variable. Note that stdin, stdout and stderr will be set by default to PIPE unless specified in subprocess_kwargs. :str cmdstr: The string run as a shell command. :subprocess_kwargs: These will be passed to subprocess.Popen. """ if "shell" in subprocess_kwargs and not subprocess_kwargs["shell"]: raise ProgramError( 'The "shell" kwarg may be omitted, but if ' "provided it must be True." ) else: subprocess_kwargs["shell"] = True if "executable" not in subprocess_kwargs: subprocess_kwargs["executable"] = os.getenv("SHELL") for stream in ["stdin", "stdout", "stderr"]: subprocess_kwargs.setdefault(stream, subprocess.PIPE) subprocess_kwargs = alter_subprocess_kwargs_by_platform(**subprocess_kwargs) return subprocess.Popen(cmdstr, **subprocess_kwargs) # MARK: Path utils --------------------------------------------------------------------- def getcwd_or_home(): """Safe version of getcwd that will fallback to home user dir. This will catch the error raised when the current working directory was removed for an external program. """ try: return os.getcwd() except OSError: print( "WARNING: Current working directory was deleted, " "falling back to home directory" ) return get_home_dir() def remove_backslashes(path): """Remove backslashes in *path* For Windows platforms only. Returns the path unchanged on other platforms. This is especially useful when formatting path strings on Windows platforms for which folder paths may contain backslashes and provoke unicode decoding errors in Python 3 (or in Python 2 when future 'unicode_literals' symbol has been imported).""" if os.name == "nt": # Removing trailing single backslash if path.endswith("\\") and not path.endswith("\\\\"): path = path[:-1] # Replacing backslashes by slashes path = path.replace("\\", "/") path = path.replace("/'", "\\'") return path # MARK: Date utils --------------------------------------------------------------------- def convert_date_format(format_string: str) -> str: """ Converts a date format string in Python strftime format to QDateTime style format. Args: format_string: The date format string in Python strftime format. Returns: The converted date format string in QDateTime style. Examples: >>> format_string = '%d.%m.%Y' >>> qt_format = convert_date_format(format_string) >>> print(qt_format) dd.MM.yyyy """ format_mapping = { "%d": "dd", "%-d": "d", "%dd": "dd", "%-dd": "d", "%ddd": "ddd", "%dddd": "dddd", "%b": "MMM", "%B": "MMMM", "%m": "MM", "%-m": "M", "%mm": "MM", "%-mm": "M", "%y": "yy", "%Y": "yyyy", "%I": "h", "%H": "HH", "%-H": "H", "%M": "mm", "%-M": "m", "%S": "ss", "%-S": "s", "%z": "z", "%Z": "zzz", } qt_format = "" i = 0 while i < len(format_string): if format_string[i : i + 2] in format_mapping: qt_format += format_mapping[format_string[i : i + 2]] i += 2 elif format_string[i] in format_mapping: qt_format += format_mapping[format_string[i]] i += 1 else: qt_format += format_string[i] i += 1 return qt_format ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1722935059.1767056 guidata-3.6.2/guidata/widgets/0000755000175100001770000000000014654363423015643 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/widgets/__init__.py0000644000175100001770000000127114654363416017757 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """ Ready-to-use Qt widgets ----------------------- Data editors ^^^^^^^^^^^^ .. autoclass:: guidata.widgets.arrayeditor.ArrayEditor .. autoclass:: guidata.widgets.collectionseditor.CollectionsEditor .. autoclass:: guidata.widgets.dataframeeditor.DataFrameEditor .. autoclass:: guidata.widgets.texteditor.TextEditor .. autofunction:: guidata.widgets.objecteditor.oedit Console and code editor ^^^^^^^^^^^^^^^^^^^^^^^ .. autoclass:: guidata.widgets.console.Console .. autoclass:: guidata.widgets.console.DockableConsole .. autoclass:: guidata.widgets.codeeditor.CodeEditor """ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/widgets/about.py0000644000175100001770000001117114654363416017332 0ustar00runnerdocker# -*- coding: utf-8 -*- """ about ===== """ from __future__ import annotations import platform import sys import qtpy from qtpy.QtCore import Qt from qtpy.QtWidgets import QMainWindow, QMessageBox import guidata from guidata.config import _ from guidata.configtools import get_icon def get_python_libs_infos(addinfos: str = "") -> str: """Get Python and libraries information Args: addinfos: additional information to be displayed Returns: str: Python and libraries information """ python_version = "{} {}".format( platform.python_version(), "64 bits" if sys.maxsize > 2**32 else "32 bits" ) if qtpy.PYQT_VERSION is None: qtb_version = qtpy.PYSIDE_VERSION qtb_name = "PySide" else: qtb_version = qtpy.PYQT_VERSION qtb_name = "PyQt" if addinfos: addinfos = ", " + addinfos return ( f"Python {python_version}, " f"Qt {qtpy.QT_VERSION}, {qtb_name} {qtb_version}" f"{addinfos} on {platform.system()}" ) def get_general_infos(addinfos: str = "") -> str: """Get general information (copyright, Qt versions, etc.) Args: addinfos: additional information to be displayed Returns: str: Qt information """ return "Copyright ┬й 2023 CEA-Codra\n\n" + get_python_libs_infos(addinfos=addinfos) class AboutInfo: """Object to generate information about the package Args: name: package name version: package version description: package description author: package author year: package year organization: package organization project_url: package project url doc_url: package documentation url """ def __init__( self, name: str, version: str, description: str, author: str, year: int, organization: str, project_url: str = "", doc_url: str = "", ) -> None: self.name = name self.version = version self.description = description self.author = author self.year = year self.organization = organization if not project_url: project_url = f"https://github.com/PlotPyStack/{name}" self.project_url = project_url if not doc_url: doc_url = f"https://{name}.readthedocs.io" self.doc_url = doc_url def __str__(self) -> str: return self.about() def about( self, html: bool = True, copyright_only: bool = False, addinfos: str = "" ) -> str: """Return text about this package Args: html: return html text. Defaults to True. copyright_only: if True, return only copyright addinfos: additional information to be displayed Returns: Text about this package """ auth, year, org = self.author, self.year, self.organization if html: author = f"
{auth}" organization = f"{org}" shdesc = f"{self.name} {self.version}\n{self.description}" if html: shdesc += "\n\n" pname = _("Project website") dname = _("Documentation") plink = f"{pname}" dlink = f"{dname}" shdesc += _("More details about %s on %s or %s") % (self.name, plink, dlink) shdesc += "\n\n" + _("Created by %s in %d") % (author, year) + "\n" shdesc += _("Maintained by the %s organization") % organization desc = get_general_infos(addinfos) if not copyright_only: desc = f"{shdesc}{desc}" if html: desc = desc.replace("\n", "
") return desc def about(html: bool = True, copyright_only: bool = False) -> str: """Return text about this package Args: html: return html text. Defaults to True. copyright_only: if True, return only copyright Returns: Text about this package """ info = AboutInfo( "guidata", guidata.__version__, _("Automatic GUI generation for easy dataset editing and display"), "Pierre Raybaut", 2009, "PlotPyStack", ) return info.about(html=html, copyright_only=copyright_only) def show_about_dialog() -> None: """Show ``guidata`` about dialog""" win = QMainWindow(None) win.setAttribute(Qt.WA_DeleteOnClose) win.hide() win.setWindowIcon(get_icon("guidata.svg")) QMessageBox.about(win, _("About") + " guidata", about(html=True)) win.close() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1722935059.1767056 guidata-3.6.2/guidata/widgets/arrayeditor/0000755000175100001770000000000014654363423020170 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/widgets/arrayeditor/__init__.py0000644000175100001770000000107314654363416022304 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) # # The array editor subpackage was derived from Spyder's arrayeditor.py module # which is licensed under the terms of the MIT License (see spyder/__init__.py # for details), copyright ┬й Spyder Project Contributors """guidata.widgets.arrayeditor =========================== This package provides a NumPy Array Editor Dialog based on Qt. .. autoclass:: ArrayEditor :show-inheritance: :members: """ from .arrayeditor import ArrayEditor # noqa: F401 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/widgets/arrayeditor/arrayeditor.py0000644000175100001770000004117014654363416023074 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) # # The array editor subpackage was derived from Spyder's arrayeditor.py module # which is licensed under the terms of the MIT License (see spyder/__init__.py # for details), copyright ┬й Spyder Project Contributors # pylint: disable=C0103 # pylint: disable=R0903 # pylint: disable=R0911 # pylint: disable=R0201 """ Module that provides array editor dialog boxes to edit various types of NumPy arrays """ from __future__ import annotations from typing import Generic import numpy as np from qtpy.QtCore import QModelIndex, Qt, Slot from qtpy.QtWidgets import ( QComboBox, QDialog, QGridLayout, QHBoxLayout, QLabel, QMessageBox, QPushButton, QSpinBox, QStackedWidget, QWidget, ) from guidata.config import _ from guidata.configtools import get_icon from guidata.qthelpers import win32_fix_title_bar_background from guidata.widgets.arrayeditor import utils from guidata.widgets.arrayeditor.arrayhandler import ( AnySupportedArray, BaseArrayHandler, MaskedArrayHandler, RecordArrayHandler, ) from guidata.widgets.arrayeditor.editorwidget import ( BaseArrayEditorWidget, DataArrayEditorWidget, MaskArrayEditorWidget, MaskedArrayEditorWidget, RecordArrayEditorWidget, ) class ArrayEditor(QDialog, Generic[AnySupportedArray]): """Array Editor Dialog Args: parent: Parent widget (default: None) """ __slots__ = ( "data", "is_record_array", "is_masked_array", "arraywidget", "arraywidgets", "stack", "layout", "btn_save_and_close", "btn_close", "dim_indexes", "last_dim", ) _data: BaseArrayHandler | MaskedArrayHandler | RecordArrayHandler arraywidget: ( BaseArrayEditorWidget | MaskArrayEditorWidget | DataArrayEditorWidget | RecordArrayEditorWidget ) layout: QGridLayout def __init__(self, parent: QWidget = None) -> None: QDialog.__init__(self, parent) win32_fix_title_bar_background(self) # Destroying the C++ object right after closing the dialog box, # otherwise it may be garbage-collected in another QThread # (e.g. the editor's analysis thread in Spyder), thus leading to # a segmentation fault on UNIX or an application crash on Windows self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose) self.is_record_array = False self.is_masked_array = False self.arraywidgets: list[BaseArrayEditorWidget] = [] self.btn_save_and_close = None self.btn_close = None # Values for 3d array editor self.dim_indexes = [{}, {}, {}] self.last_dim = 0 # Adjust this for changing the startup dimension self.stack: QStackedWidget | None = None def setup_and_check( self, data: AnySupportedArray, title="", readonly=False, xlabels=None, ylabels=None, variable_size=False, ) -> bool: """ Setup the array editor dialog box and check if the array is supported. Args: data: Array to edit title: Dialog box title readonly: Flag indicating if the array is read only xlabels: List of x labels ylabels: List of y labels variable_size: Flag indicating if the array is variable size Returns: True if the array is supported, False otherwise """ readonly = readonly or not data.flags.writeable self._variable_size = ( variable_size and not readonly and xlabels is None and ylabels is None ) if readonly and variable_size: QMessageBox.warning( self, _("Conflicing edition flags"), _( "Array editor was initialized in both readonly and variable " "size mode." ) + "\n" + _("The array editor will remain in readonly mode."), ) if variable_size and (xlabels is not None or ylabels is not None): QMessageBox.warning( self, _("Unsupported array format"), _( "Array editor does not support array with x/y labels in " "variable size mode." ) + "\n" + _("You will not be able to add or remove rows/columns."), ) self.is_record_array = data.dtype.names is not None self.is_masked_array = isinstance(data, np.ma.MaskedArray) if self.is_masked_array: self._data = MaskedArrayHandler(data, self._variable_size) # type: ignore elif self.is_record_array: self._data = RecordArrayHandler(data, self._variable_size) else: self._data = BaseArrayHandler(data, self._variable_size) if data.ndim > 3: self.error(_("Arrays with more than 3 dimensions are not supported")) return False if xlabels is not None and len(xlabels) != self._data.shape[1]: self.error( _("The 'xlabels' argument length do no match array column number") ) return False if ylabels is not None and len(ylabels) != self._data.shape[0]: self.error(_("The 'ylabels' argument length do no match array row number")) return False if not self.is_record_array: dtn = data.dtype.name if ( dtn not in utils.SUPPORTED_FORMATS and not dtn.startswith("str") and not dtn.startswith("unicode") ): arr_ = _("%s arrays") % data.dtype.name self.error(_("%s are currently not supported") % arr_) return False self.layout = QGridLayout() self.setLayout(self.layout) self.setWindowIcon(get_icon("arredit.png")) title = str(title) + " - " + _("NumPy array") if title else _("Array editor") if readonly: title += " (" + _("read only") + ")" self.setWindowTitle(title) self.resize(600, 500) # Stack widget self.stack = QStackedWidget(self) if self.is_record_array: for name in data.dtype.names: w = RecordArrayEditorWidget( self, self._data, # type: ignore name, readonly, xlabels, ylabels, variable_size, ) self.arraywidgets.append(w) self.stack.addWidget(w) elif self.is_masked_array: w1 = MaskedArrayEditorWidget( self, self._data, readonly, xlabels, ylabels, variable_size ) self.arraywidgets.append(w1) self.stack.addWidget(w1) w2 = DataArrayEditorWidget( self, self._data, # type: ignore readonly, xlabels, ylabels, variable_size, ) self.arraywidgets.append(w2) self.stack.addWidget(w2) w3 = MaskArrayEditorWidget( self, self._data, readonly, xlabels, ylabels, variable_size, ) self.arraywidgets.append(w3) self.stack.addWidget(w3) elif data.ndim == 3: pass else: w = BaseArrayEditorWidget( self, self._data, readonly, xlabels, ylabels, variable_size ) self.stack.addWidget(w) self.arraywidget = self.stack.currentWidget() if self.arraywidget: self.arraywidget.model.dataChanged.connect(self.save_and_close_enable) for wdg in self.arraywidgets: wdg.model.SIZE_CHANGED.connect(self.update_all_tables_on_size_change) self.stack.currentChanged.connect(self.current_widget_changed) self.layout.addWidget(self.stack, 1, 0) # Buttons configuration btn_layout = QHBoxLayout() if self.is_record_array or self.is_masked_array or data.ndim == 3: if self.is_record_array: btn_layout.addWidget(QLabel(_("Record array fields:"))) names = [] for name in data.dtype.names: field = data.dtype.fields[name] text = name if len(field) >= 3: title = field[2] if not isinstance(title, str): title = repr(title) text += " - " + title names.append(text) else: names = [_("Masked data"), _("Data"), _("Mask")] if data.ndim == 3: # QSpinBox self.index_spin = QSpinBox(self, keyboardTracking=False) self.index_spin.valueChanged.connect(self.change_active_widget) # QComboBox names = [str(i) for i in range(3)] ra_combo = QComboBox(self) ra_combo.addItems(names) ra_combo.currentIndexChanged.connect(self.current_dim_changed) # Adding the widgets to layout label = QLabel(_("Axis:")) btn_layout.addWidget(label) btn_layout.addWidget(ra_combo) self.shape_label = QLabel() btn_layout.addWidget(self.shape_label) label = QLabel(_("Index:")) btn_layout.addWidget(label) btn_layout.addWidget(self.index_spin) self.slicing_label = QLabel() btn_layout.addWidget(self.slicing_label) # set the widget to display when launched self.current_dim_changed(self.last_dim) else: ra_combo = QComboBox(self) ra_combo.currentIndexChanged.connect(self.stack.setCurrentIndex) ra_combo.addItems(names) btn_layout.addWidget(ra_combo) if self.is_masked_array: label = QLabel(_("Warning: changes are applied separately")) label.setToolTip( _( "For performance reasons, changes applied " "to masked array won't be reflected in " "array's data (and vice-versa)." ) ) btn_layout.addWidget(label) btn_layout.addStretch() if not readonly: self.btn_save_and_close = QPushButton(_("Save and Close")) self.btn_save_and_close.setDisabled(True) self.btn_save_and_close.clicked.connect(self.accept) btn_layout.addWidget(self.btn_save_and_close) self.btn_close = QPushButton(_("Close")) self.btn_close.setAutoDefault(True) self.btn_close.setDefault(True) self.btn_close.clicked.connect(self.reject) btn_layout.addWidget(self.btn_close) self.layout.addLayout(btn_layout, 2, 0) self.setMinimumSize(400, 300) # Make the dialog act as a window self.setWindowFlags(Qt.WindowType.Window) return True @Slot(bool, bool) def update_all_tables_on_size_change(self, rows: bool, cols: bool) -> None: """Updates all array editor widgets when rows and/or columns count changes. Args: rows: Flag to indicate the number of rows changed cols: Flag to indicate the number of columns changed """ for wdg in self.arraywidgets: wdg.model.fetch(rows, cols) wdg.model.set_hue_values() if self._data.ndim == 3: self.current_dim_changed(self.last_dim) @Slot(QModelIndex, QModelIndex) def save_and_close_enable( self, _left_top: QModelIndex, _bottom_right: QModelIndex ) -> None: """Handle the data change event to enable the save and close button.""" if self.btn_save_and_close: self.btn_save_and_close.setEnabled(True) self.btn_save_and_close.setAutoDefault(True) self.btn_save_and_close.setDefault(True) def current_widget_changed(self, index: int) -> None: """Handle the current widget change event to connect the dataChanged signal Args: index: Index of the current widget """ if self.stack is not None: self.arraywidget = self.stack.widget(index) self.arraywidget.model.dataChanged.connect(self.save_and_close_enable) def change_active_widget(self, index: int) -> None: """This is implemented for handling negative values in index for 3d arrays, to give the same behavior as slicing Args: index: Index of the current widget """ string_index = [":"] * 3 string_index[self.last_dim] = "%i" self.slicing_label.setText( (r"Slicing: [" + ", ".join(string_index) + "]") % index ) data_index = self._data.shape[self.last_dim] + index if index < 0 else index slice_index = [slice(None)] * 3 slice_index[self.last_dim] = data_index stack_index = self.dim_indexes[self.last_dim].get(data_index) if stack_index is None and self.stack is not None: stack_index = self.stack.count() try: w = BaseArrayEditorWidget( self, self._data, variable_size=self._variable_size, current_slice=slice_index, ) self.stack.addWidget(w) except IndexError: # Handle arrays of size 0 in one axis w = BaseArrayEditorWidget( self, self._data, variable_size=self._variable_size, current_slice=slice_index, ) self.stack.addWidget(w) self.arraywidgets.append( w ) # required to fetch the new columns/rows if added/deleted w.model.SIZE_CHANGED.connect(self.update_all_tables_on_size_change) self.dim_indexes[self.last_dim][data_index] = stack_index self.stack.update() self.stack.setCurrentIndex(stack_index) def current_dim_changed(self, index: int) -> None: """This change the active axis the array editor is plotting over in 3D Args: index: Index of the current widget """ self.last_dim = index string_size = ["%i"] * 3 string_size[index] = "%i" self.shape_label.setText( ("Shape: (" + ", ".join(string_size) + ") ") % self._data.shape ) if self.index_spin.value() != 0: self.index_spin.setValue(0) else: # this is done since if the value is currently 0 it does not emit # currentIndexChanged(int) self.change_active_widget(0) self.index_spin.setRange(-self._data.shape[index], self._data.shape[index] - 1) @Slot() def accept(self) -> None: """Reimplement Qt method""" self._data.apply_changes() QDialog.accept(self) def get_value(self) -> AnySupportedArray: """Return modified array -- the returned array is a copy if variable size is True and readonly is False Returns: Modified array """ # It is import to avoid accessing Qt C++ object as it has probably # already been destroyed, due to the Qt.WA_DeleteOnClose attribute self._data.reset_shape_if_changed() return self._data.get_array() def error(self, message: str) -> None: """An error occured, closing the dialog box Args: message: Error message """ QMessageBox.critical(self, _("Array editor"), message) self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose) self.reject() @Slot() def reject(self) -> None: """Reimplement Qt method""" self._data.clear_changes() QDialog.reject(self) class BaseArrayEditor(ArrayEditor[np.ndarray]): """Optional wrapper class to get type inferance for normal numpy arrays""" class RecordArrayEditor(ArrayEditor[np.ndarray]): """Optional wrapper class to get type inferance for record numpy arrays""" class MaskedArrayEditor(ArrayEditor[np.ma.MaskedArray]): """Optional wrapper class to get type inferance for masked numpy arrays""" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/widgets/arrayeditor/arrayhandler.py0000644000175100001770000004066614654363416023234 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) # # The array editor subpackage was derived from Spyder's arrayeditor.py module # which is licensed under the terms of the MIT License (see spyder/__init__.py # for details), copyright ┬й Spyder Project Contributors # pylint: disable=C0103 # pylint: disable=R0903 # pylint: disable=R0911 # pylint: disable=R0201 """ Provide classes to wrap Numpy arrays and handle changes made to them. Array handlers acts as a pointer to the original array and allows to share the same array in multiple models/widgets and views. They handle data access and changes. """ from __future__ import annotations import copy from typing import Any, Generic, TypeVar, Union, cast import numpy as np # TODO: (When Python 3.8-3.9 support is dropped) Use `np.ndarray | np.ma.MaskedArray` # instead of `Union[np.ndarray, np.ma.MaskedArray]` AnySupportedArray = TypeVar( "AnySupportedArray", bound=Union[np.ndarray, np.ma.MaskedArray] ) AnyArrayHandler = TypeVar("AnyArrayHandler", bound="BaseArrayHandler") class BaseArrayHandler(Generic[AnySupportedArray]): """Wrapper class around a Numpy nparray that is used a pointer to share the same array in multiple models/widgets and views. It handles data access and changes. Args: array: Numpy array to wrap variable_size: Flag to indicate if the size of the array can be modified (i.e. that data can be inserted on any axis). This flag changes the underlying data management strategy. If True, the original array will be copied and changes will be done inplace in the copy, else changes are stored in a dictionnary and applied inplace when the appropriate method is called. Defaults to False. """ __slots__ = ( "_variable_size", "_backup_array", "_array", "_dtype", "current_changes", "_og_shape", ) def __init__( self, array: AnySupportedArray, variable_size: bool = False, ) -> None: self._variable_size = variable_size self._og_shape = None self._array = array self._backup_array = array self._init_arrays(array) self._dtype = array.dtype self.current_changes: dict[tuple[str | int, ...] | str, bool] = {} def _init_arrays(self, array: np.ndarray | np.ma.MaskedArray) -> None: """Small method to handle variable initializations dependent on the array. Args: array: Numpy array to use """ if self._variable_size: self._backup_array = array if array.ndim == 1: self._array = array.reshape(-1, 1) else: self._array = copy.deepcopy(array) else: if array.ndim == 1: self._og_shape = array.shape array.shape = (array.size, 1) self._array = array @property def variable_size(self) -> bool: """Returns the variable_size flag. If True, the array is resizable and changes Returns Variable size flag """ return self._variable_size @property def ndim(self) -> int: """Numpy ndim property. Returns Number of dimensions of the array """ return self._array.ndim @property def flags(self) -> np.flagsobj: """Numpy flags property. Returns Flags of the array""" return self._array.flags @property def shape(self) -> tuple[int, ...]: """Numpy shape property. Returns Shape of the array """ return self._array.shape @shape.setter def shape(self, value: tuple[int, ...]) -> None: """Numpy shape property setter. Sets the shape of the array. Args: value: new shape of the array """ self._array.shape = value @property def dtype(self) -> Any: """Numpy dtype property. Returns dtype of the array """ return self._dtype @property def data(self) -> memoryview: """Used to get the underlying data of an array. Useful for Numpy's structured arrays. Returns A memoryview to the underlying data (= a np.ndarray) """ return self._array.data def insert_on_axis( self, index: int, axis: int, insert_number: int = 1, default: Any = 0 ) -> None: """Insert new row(s) of default values on an axis. Makes a copy of the original array. Args: index: Index from which to start the insertion. axis: axis on which to make the insertion. insert_number: Number of rows to insert at once. Defaults to 1. default: default value to insert. Defaults to the "zero value" of a type (e.g. "" for str or False for Booleans). Defaults to 0. """ indexes = (index,) * insert_number self._array = np.insert(self._array, indexes, default, axis=axis) def delete_on_axis(self, index: int, axis: int, remove_number: int = 1): """Delete row(s) on an axis. Makes a copy of the original array. Args: index: index from which to start the deletion. axis: axis on which to make the deletion. remove_number: Number of rows to delete at once. Defaults to 1. """ indexes = range(index, index + remove_number) self._array = np.delete(self._array, indexes, axis=axis) def get_array(self) -> AnySupportedArray: """Returns the current wrapped array. If variable_size is False, the returned array does not contain the current modifications as they are saved separately. Returns Numpy array contained in the handler instance """ return cast(AnySupportedArray, self._array) def new_row(self, index: int, insert_number: int = 1, default: Any = 0): """Insert new row(s) on axis 0. Do not for 3D+ arrays. Prefer the method 'insert_on_axis' instead to be sure to insert on the right axis from the Model. Args: index: Index from which to start the insertion. insert_number: Number of rows to insert at once. Defaults to 1. default: default value to insert. Defaults to the "zero value" of a type (e.g. "" for str or False for Booleans). Defaults to 0. """ self.insert_on_axis(index, 0, insert_number, default) def new_col(self, index: int, insert_number: int = 1, default: Any = 0): """Insert new column(s) on axis 1. Do not for 3D+ arrays. Prefer the method 'insert_on_axis' instead to be sure to insert on the right axis from the Model. Args: index: Index from which to start the insertion. insert_number: Number of columns to insert at once. Defaults to 1. default: default value to insert. Defaults to the "zero value" of a type (e.g. "" for str or False for Booleans). Defaults to 0. """ self.insert_on_axis(index, 1, insert_number, default) def __setitem__(self, key: tuple[int, ...] | str, item: Any): """Data setter that allows to use this class like the original array. The data storage strategy changes depending on the variable_size flag. Args: key: key to set (=array index) item: value to set. Must be of the same type as the array data. """ if self._variable_size: self._array[key] = item return self.current_changes[key] = item def __getitem__(self, key: tuple[int, ...] | str) -> Any: """Data getter that allows to use this class like the original array. The data retrieval strategy changes depending on the variable_size flag. Args: key: key to get (=array index) Returns: The requested value from the array """ if not self._variable_size: return self.current_changes.get(key, self._array[key]) return self._array[key] def apply_changes(self) -> None: """Apply changes. Only useful if flag viariable_size is False as it will write the values stored in a dictionary in the original array. This operation is non-reversible as it writes inplace. """ if not self._variable_size: for coor, value in self.current_changes.items(): self._array[coor] = value self.current_changes.clear() else: self._backup_array = copy.deepcopy(self._array) def clear_changes(self) -> None: """Deletes all the changes made until that point. If the variable_size flag is True, then restores a copy of the origninal array (makes new copy needed). Else, clears the dictionnary where changes are temporarily stored. """ if not self._variable_size: self.current_changes.clear() else: self._init_arrays(self._backup_array) def reset_shape_if_changed(self) -> None: """When a numpy array is 1D, the handler changes the shape to add a second dimension of size 1 to act like a normal 2d array in the BaseArrayModel. In some instances, the shape must be reset (i.e. when getting the array when the ArrayEditor is closed). When the shape is reset, the array editor may not work properly as the awaited 2nd dimension is removed. The method _init_arrays(array) or clear_changes() must be called to avoid errors possible IndexError. """ if self._og_shape is not None: self._array.shape = self._og_shape class MaskedArrayHandler(BaseArrayHandler[np.ma.MaskedArray]): """Same as the class BaseArrayHandler but with additionnal functionnalities to handled a Numpy MaskedArray. Args: array: Numpy MaskedArray to wrap variable_size: array resizability flag, refer to BaseArrayHandler for more information. Defaults to False. """ __slots__ = ("current_mask_changes",) _array: np.ma.MaskedArray _backup_array: np.ma.MaskedArray def __init__( self, array: np.ma.MaskedArray, variable_size: bool = False, ) -> None: super().__init__(array, variable_size) self.current_mask_changes: dict[tuple[int, ...], Any] = {} @property def mask(self) -> np.ndarray: """Numpy mask property. Returns: The mask of the array """ return self._array.mask def insert_on_axis( self, index: int, axis: int, insert_number: int = 1, default: Any = 0, default_mask: bool = False, ) -> None: """Refer to BaseArrayHandler.insert_on_axis for the full details. The only difference is that with this method is that it also inserts default values for the mask. Args: index: index from which to start the insertion. axis: axis on which to make the insertion. insert_number: Number of rows to insert at once. Defaults to 1. default: default value to insert. Defaults to the "zero value" of a type (e.g. "" for str or False for Booleans). Defaults to 0. default_mask: default mask value to insert. Defaults to False. """ indexes = (index,) * insert_number new_array: np.ma.MaskedArray = np.insert( self._array, indexes, default, axis=axis ) # The check is performed at init and array type cannot change new_mask = self._array.mask new_mask = np.insert(new_mask, indexes, default_mask, axis=axis) new_array.mask = new_mask self._array = new_array def delete_on_axis(self, index: int, axis: int, remove_number: int = 1) -> None: """Refer to BaseArrayHandler.delete_on_axis for the full details. The only difference is that with this method is that it also keep the previous value of the mask. Args: index: index from which to start the deletion. axis: axis on which to make the deletion. remove_number: number of rows to delete. Defaults to 1. """ # indexes = (index,) * remove_number indexes = range(index, min(index + remove_number, self._array.shape[axis])) new_array: np.ma.MaskedArray = np.delete(self._array, indexes, axis=axis) # The check is performed at init and array type cannot change new_mask = self._array.mask new_mask = np.delete(new_mask, indexes, axis=axis) new_array.mask = new_mask self._array = new_array def set_mask_value(self, key: tuple[int, ...], value: bool) -> None: """Setter for the mask values. Identical to BaseArrayHandler.__setitem__ but for the mask. Args: key: key to set (=mask index). item: value to set. """ if not self._variable_size: self.current_mask_changes[key] = value else: self._array.mask[key] = value def get_mask_value(self, key: tuple[int, ...]) -> bool: """Getter for the mask values. Identical to BaseArrayHandler.__getitem__ but for the mask. Args: key: key to get (=mask index). Returns: The requested value from the mask """ if not self._variable_size: return self.current_mask_changes.get(key, self._array.mask[key]) return self._array.mask[key] def get_data_value(self, key: tuple[int, ...]) -> Any: """Setter for the data values (unmasked). Identical to BaseArrayHandler.__setitem__ but for the unmasked array data. Args: key: key to set (=mask index). Must be the same type as the array. item: value to set. Returns: The requested value from the array """ if not self._variable_size: return self.current_changes.get(key, self._array.data[key]) return self._array.data[key] def set_data_value(self, key: tuple[int, ...], value: bool) -> None: """Getter for the data values (unmasked). Identical to BaseArrayHandler.__getitem__ but for the unmasked array data. Args: key: key to get (=array index). """ if not self._variable_size: self.current_changes[key] = value else: self._array.data[key] = value def apply_changes(self) -> None: """Same as BaseArrayHandler.apply_changes but also applies changes to the mask.""" super().apply_changes() for coor, value in self.current_mask_changes.items(): self._array.mask[coor] = value self.current_mask_changes.clear() def clear_changes(self) -> None: """Same as BaseArrayHandler.clear_changes but also clears the changes made to the mask.""" super().clear_changes() if not self._variable_size: self.current_mask_changes.clear() class RecordArrayHandler(BaseArrayHandler[np.ndarray]): """Same as the class BaseArrayHandler but with additionnal functionnalities to handled Numpy's structured arrays. Args: array: Numpy ndarray variable_size: array resizability flag, refer to BaseArrayHandler for more information. Defaults to False. """ def __init__( self, array: np.recarray, variable_size: bool = False, ) -> None: super().__init__(array, variable_size) def get_record_value(self, name: str, key: tuple[str | int, ...]) -> Any: """Getter for the Numpy's structured array. Identical to BaseArrayHandler.__getitem__ but for the named values. Args: name: type name to get. key: key to get. Returns: The requested value from the array """ if not self._variable_size: return self.current_changes.get((name, *key), self._array[name][key]) return self._array[name][key] def set_record_value( self, name: str, key: tuple[str | int, ...], value: Any ) -> None: """Setter for the Numpy's structured array. Identical to BaseArrayHandler.__setitem__ but for the named values. Args: name: type name to get. key: key to set (i.e. array index). """ if not self._variable_size: self.current_changes[(name, *key)] = value else: self._array[name][key] = value ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/widgets/arrayeditor/datamodel.py0000644000175100001770000010267514654363416022511 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) # # The array editor subpackage was derived from Spyder's arrayeditor.py module # which is licensed under the terms of the MIT License (see spyder/__init__.py # for details), copyright ┬й Spyder Project Contributors # pylint: disable=C0103 # pylint: disable=R0903 # pylint: disable=R0911 # pylint: disable=R0201 """ Data models for the array editor widget. """ from __future__ import annotations from abc import abstractmethod from collections.abc import Callable from functools import reduce from typing import Any, Generic, Sequence, TypeVar import numpy as np from qtpy.QtCore import QAbstractTableModel, QModelIndex, Qt, Signal from qtpy.QtGui import QColor from qtpy.QtWidgets import QMessageBox from guidata.config import CONF, _ from guidata.configtools import get_font from guidata.dataset.dataitems import BoolItem, FloatItem, IntItem, StringItem from guidata.dataset.datatypes import DataItem, DataSet from guidata.widgets.arrayeditor import utils from guidata.widgets.arrayeditor.arrayhandler import ( AnyArrayHandler, AnySupportedArray, MaskedArrayHandler, RecordArrayHandler, ) ArrayModelType = TypeVar("ArrayModelType", bound="BaseArrayModel") def handle_size_change(rows=False, cols=False) -> Callable: """Wrapper to signal when the table changed dimenstions, i.e. when a row or column is inserted. This decorator emits the BaseArrayModel.SIZE_CHANGED signal and fetch/update the model. Args: rows: If rows are inserter. Defaults to False. cols: If columns are inserter. Defaults to False. Returns: The wrapped method """ def inner_handle_size_change( model_method, ): def wrapped_method(self: "BaseArrayModel", *args, **kwargs): model_method(self, *args, **kwargs) self.fetch(rows, cols) self.set_hue_values() self.SIZE_CHANGED.emit(rows, cols) qidx = QModelIndex() self.dataChanged.emit(qidx, qidx) return wrapped_method return inner_handle_size_change class BaseArrayModel(QAbstractTableModel, Generic[AnyArrayHandler, AnySupportedArray]): """Array Editor Table Model that implements all the core functionnalities Args: array_handler: instance of BaseArrayHandler or child classes. format: format of the displayed values. Defaults to "%.6g". xlabels: labels for the columns (header). Defaults to None. ylabels: labels for the rows (header). Defaults to None. readonly: Flag to set the data in readonly mode. Defaults to False. parent: parent QObject. Defaults to None. current_slice: slice of the same dimension as the Numpy ndarray that will return a 2d array when applied to it. Defaults to None. """ ROWS_TO_LOAD = 500 COLS_TO_LOAD = 40 SIZE_CHANGED = Signal(bool, bool) # first bool is for rows, second is for columns class InsertionDataSet(DataSet): """Abstract class to create a dataset to insert new rows/columns in a table.""" index_field: IntItem insert_number: IntItem default_value: DataItem new_label: DataItem | None @abstractmethod def get_values_to_insert(self) -> tuple[Any, ...]: """Getter for the field values to insert in the table. The values are returned as a tuple in the same order as the fields are declared in the dataset. Raises: NotImplementedError: must be implemented by every subclass of InsertionDataSet Returns: Tuple of values to insert """ raise NotImplementedError class DeletionDataSet(DataSet): """Abstract class to create a dataset to delete row/columns from a table.""" index_field: IntItem remove_number: IntItem def __init__( self, array_handler: AnyArrayHandler, format="%.6g", xlabels=None, ylabels=None, readonly=False, parent=None, current_slice: Sequence[slice | int] | None = None, ): QAbstractTableModel.__init__(self) self.dialog = parent self.xlabels = xlabels self.ylabels = ylabels self.readonly = readonly self.test_array = np.array([0], dtype=array_handler.dtype) if array_handler.dtype in (np.complex64, np.complex128): self.color_func = np.abs else: self.color_func = np.real self._array_handler = array_handler # Backgroundcolor settings new_slice = self.default_slice() if current_slice is None else current_slice self.set_slice(new_slice) self.huerange = [0.66, 0.99] # Hue self.sat = 0.7 # Saturation self.val = 1.0 # Value self.alp = 0.6 # Alpha-channel self.bgcolor_enabled = False self._format = format self.rows_loaded = 0 self.cols_loaded = 0 self.set_hue_values() self.set_row_col_counts() @property def shape(self) -> tuple[int, ...]: """Property to simplify access to the array shape Returns The shape of the array """ return self._array_handler.shape @property def ndim(self) -> int: """Property to simplify access to the array dimension Returns The number of dimensions of the array """ return self._array_handler.ndim def get_insertion_dataset(self, index: int, axis: int) -> type[InsertionDataSet]: """Wrapper around the abstract class InsertionDataSet Args: index: default insertion index used in the dataset axis: axis on which to perform the insertion (row/column) Returns: new InsertionDataSet child class """ dtype = self._array_handler.dtype max_index = self._array_handler.shape[ self.correct_ndim_axis_for_current_slice(axis) ] ptype = type(np.zeros(1, dtype=dtype)[0].item()) index_label = ( _("Insert at row") if axis == 0 else _("Insert at column") if axis == 1 else "" ) number_label = ( _("Number of rows") if axis == 0 else _("Number of columns") if axis == 1 else "" ) value_label = _("Value") class NewInsertionDataSet(self.InsertionDataSet): """InsertionDataSet child class""" index_field = IntItem( label=index_label, default=index, min=-1, max=max_index, ) insert_number = IntItem( label=number_label, default=1, min=1, ) if ptype is int: default_value = IntItem(label=value_label, default=0) elif ptype is float: default_value = FloatItem(label=value_label, default=0.0) elif ptype is bool: default_value = BoolItem(label=value_label, default=False) elif ptype is str: default_value = StringItem(label=value_label, default="") else: default_value = IntItem( label=_("Unsupported type %s, defaults to: ") % ptype.__name__, default=0, ) default_value.set_prop("display", active=False, valid=False) new_label = None def get_values_to_insert(self) -> tuple[Any, ...]: return (self.default_value,) return NewInsertionDataSet def get_deletion_dataset(self, index: int, axis: int) -> type[DeletionDataSet]: """Wrapper around the abstract class DeletionDataSet Args: index: default deletion index used in the dataset axis: axis on which to perform the deletion (row/column) Returns: new DeletionDataSet child class """ max_index = self._array_handler.shape[ self.correct_ndim_axis_for_current_slice(axis) ] index_label = ( _("Delete from row") if axis == 0 else _("Delete from column") if axis == 1 else "" ) number_label = ( _("Number of rows") if axis == 0 else _("Number of columns") if axis == 1 else "" ) class NewDeletionDataSet(self.DeletionDataSet): """InsertionDataSet child class""" index_field = IntItem( label=index_label, default=index, min=-1, max=max_index, ) remove_number = IntItem( number_label, default=1, min=1, ) return NewDeletionDataSet def get_format(self) -> str: """Return current format Returns: Current format """ # Avoid accessing the private attribute _format from outside return self._format def set_format(self, format: str) -> None: """Change display format Args: format: new format """ self._format = format self.reset() def columnCount(self, qindex=QModelIndex()) -> int: """Array column number""" if self.total_cols <= self.cols_loaded: return self.total_cols return self.cols_loaded def rowCount(self, qindex=QModelIndex()) -> int: """Array row number""" if self.total_rows <= self.rows_loaded: return self.total_rows return self.rows_loaded def can_fetch_more(self, rows=False, columns=False) -> bool: """ Is there more data than the current slice can show? Useful when variable_size is True and columns/rows are added Args: rows: Should check the rows. Defaults to False. columns: Should check the columns. Defaults to False. Returns True if new data can be fetched """ return ( rows and self.total_rows > self.rows_loaded or columns and self.total_cols > self.cols_loaded ) def can_fetch_less(self, rows=False, columns=False) -> bool: """ Is there less data than the current slice can show? Useful when variable_size is True and columns/rows are deleted Args: rows: Should check the rows. Defaults to False. columns: Should check the columns. Defaults to False. Returns: True if less data should be fetched """ return ( rows and self.total_rows < self.rows_loaded or columns and self.total_cols < self.cols_loaded ) def fetch(self, rows=False, columns=False) -> None: """ Fetch more data if necessary. Args: rows: Should fetch rows. Defaults to False. columns: Should fetch columns. Defaults to False. """ if self.can_fetch_more(rows=rows): reminder = self.total_rows - self.rows_loaded items_to_fetch = min(reminder, self.ROWS_TO_LOAD) self.beginInsertRows( QModelIndex(), self.rows_loaded, self.rows_loaded + items_to_fetch - 1 ) self.rows_loaded += items_to_fetch self.endInsertRows() elif self._array_handler.variable_size and self.can_fetch_less(rows=rows): reminder = self.rows_loaded - self.total_rows items_to_remove = min(reminder, self.ROWS_TO_LOAD) self.beginRemoveRows( QModelIndex(), self.rows_loaded - items_to_remove, self.rows_loaded - 1, ) self.rows_loaded -= items_to_remove self.endRemoveRows() if self.can_fetch_more(columns=columns): reminder = self.total_cols - self.cols_loaded items_to_fetch = min(reminder, self.COLS_TO_LOAD) self.beginInsertColumns( QModelIndex(), self.cols_loaded, self.cols_loaded + items_to_fetch - 1 ) self.cols_loaded += items_to_fetch self.endInsertColumns() elif self._array_handler.variable_size and self.can_fetch_less(columns=columns): reminder = self.cols_loaded - self.total_cols items_to_remove = min(reminder, self.ROWS_TO_LOAD) self.beginRemoveColumns( QModelIndex(), self.cols_loaded - items_to_remove, self.cols_loaded - 1, ) self.cols_loaded -= items_to_remove self.endRemoveColumns() def bgcolor(self, state: int) -> None: """Toggle backgroundcolor Args: state: 0 or 2, 0 means disabled, 2 means enabled """ self.bgcolor_enabled = state > 0 self.reset() def get_value(self, index: tuple[int, int]) -> Any: """Use a 2d index to access data in the array handler. The index is converted into the n-dim of the array (i.e. 2d -> 3d if the array is 3-dimensional). The value returned include the changes contrary to get_array() method that may not return new changes. Args: index: 2d index tuple Returns: The value requested in the ArrayHandler """ return self._array_handler[self._compute_ndim_index(index)] def set_value(self, index: tuple[int, int], value: Any) -> None: """Same as get_value() but to set a value in the array handler. The input 2d index tuple is converted to n-dim and used to set value. Use this instead of get_array()[index] because this methods handles the changes storage. Args: index: 2d index tuple value: value to set """ self._array_handler[self._compute_ndim_index(index)] = value def _compute_ndim_index(self, index: tuple[int, int]) -> tuple[int, ...]: """Transfers a 2d index tuple into n-dim using the current_sice given to the array model. Args: index: 2d index tuple Returns: New index tuple in n-dim """ ( self._nd_index_template[self._row_axis_nd], self._nd_index_template[self._col_axis_nd], ) = index return tuple(self._nd_index_template) # type: ignore def correct_ndim_axis_for_current_slice(self, d2_axis: int) -> int: """Transfer a aixs 0/1 (row/col) from the table view to the real array axis depending on the real array dimensions and current slice selection in the interface. Args: d2_axis: 2d axis (0 or 1) Returns: The corresponding axis in n-dim (1 -> 2 if current slice is [0, :, :]) """ axis_offset = reduce( lambda x, y: x + 1 if isinstance(y, int) else x, self._current_slice[: d2_axis + 1], 0, ) return d2_axis + axis_offset def data(self, index: QModelIndex, role=Qt.ItemDataRole.DisplayRole) -> Any: """Cell content""" if not index.isValid(): return None value = self.get_value((index.row(), index.column())) if isinstance(value, bytes): try: value = str(value, "utf8") except BaseException as e: print(e) if role == Qt.ItemDataRole.DisplayRole: if value is np.ma.masked: return "" try: return self._format % value except TypeError: self.readonly = True return repr(value) elif role == Qt.ItemDataRole.TextAlignmentRole: return int(Qt.AlignmentFlag.AlignCenter | Qt.AlignmentFlag.AlignVCenter) elif ( role == Qt.ItemDataRole.BackgroundColorRole and self.bgcolor_enabled and value is not np.ma.masked ): try: hue = self.hue0 + self.dhue * ( float(self.vmax) - self.color_func(value) ) / (float(self.vmax) - self.vmin) hue = float(np.abs(hue)) color = QColor.fromHsvF(hue, self.sat, self.val, self.alp) return color except TypeError: return None elif role == Qt.ItemDataRole.FontRole: return get_font(CONF, "arrayeditor", "font") return None def setData(self, index, value, role=Qt.ItemDataRole.EditRole) -> bool: """Cell content change""" if not index.isValid() or self.readonly: return False i = index.row() j = index.column() dtype = self.get_array().dtype.name if dtype == "bool": try: val = bool(float(value)) except ValueError: val = value.lower() == "true" elif dtype.startswith("string") or dtype.startswith("bytes"): val = bytes(value, "utf8") elif dtype.startswith("unicode") or dtype.startswith("str"): val = str(value) else: if value.lower().startswith("e") or value.lower().endswith("e"): return False try: val = complex(value) if not val.imag: val = val.real except ValueError as e: QMessageBox.critical( self.dialog, "Error", _("Value error: %s") % str(e) ) return False try: self.test_array[0] = val # will raise an Exception eventually except OverflowError as e: print("OverflowError: " + str(e)) # spyder: test-skip QMessageBox.critical(self.dialog, "Error", _("Overflow error: %s") % str(e)) return False self.set_value((i, j), val) self.dataChanged.emit(index, index) if isinstance(val, (int, float, complex)): if val > self.vmax: self.vmax = val if val < self.vmin: self.vmin = val return True def default_slice(self) -> tuple[slice | int, ...]: """Computes a default n-dim slice to use to get a 2d array. Returns A tuple containing 0s and 2 slices of the form: (0, ..., 0, slice(None), slice(None)) """ default_slice = tuple( map( lambda ndim: slice(None) if ndim < 2 else 0, range(self._array_handler.ndim), ) ) return default_slice def set_slice(self, new_slice: Sequence[int | slice] | None) -> None: """Use this method to change the current slice handled by the model Args: new_slice: new_slice to set """ if new_slice is None: new_slice = self.default_slice() is_slice_valid = reduce( lambda x, s: x + 1 if isinstance(s, slice) else x, new_slice, 0 ) == min(2, self._array_handler.ndim) if len(new_slice) == self.get_array().ndim and is_slice_valid: self._current_slice = new_slice self._row_axis_nd = self.correct_ndim_axis_for_current_slice(0) self._col_axis_nd = self.correct_ndim_axis_for_current_slice(1) self._nd_index_template = [ None if isinstance(s, slice) else s for s in new_slice ] else: err_msg = _( "Slice %s is not valid. Expected an Iterable of " "slices and int like (slice(None), slice(None), n1, n2, ..., nX) " "with maximum two slices." ) % str(new_slice) print(err_msg) QMessageBox.critical(self.dialog, "Error", err_msg) def flags(self, index: QModelIndex) -> Qt.ItemFlags: """Set editable flag""" if not index.isValid(): return Qt.ItemFlag.ItemIsEnabled return Qt.ItemFlags( QAbstractTableModel.flags(self, index) | Qt.ItemFlag.ItemIsEditable ) def headerData(self, section, orientation, role=Qt.ItemDataRole.DisplayRole) -> Any: """Set header data""" if role != Qt.ItemDataRole.DisplayRole: return None labels = ( self.xlabels if orientation == Qt.Orientation.Horizontal else self.ylabels ) if labels is None: return int(section) return labels[section] @property def total_rows(self) -> int: """Total number of rows in the array""" return self._array_handler.shape[self._row_axis_nd] @property def total_cols(self) -> int: """Total number of columns in the array""" try: return self._array_handler.shape[self._col_axis_nd] except IndexError: return 1 def set_row_col_counts(self) -> None: """Set rows and columns instance variables""" size = self.total_rows * self.total_cols if size > utils.LARGE_SIZE: self.rows_loaded = self.ROWS_TO_LOAD self.cols_loaded = self.COLS_TO_LOAD else: if self.total_rows > utils.LARGE_NROWS: self.rows_loaded = self.ROWS_TO_LOAD else: self.rows_loaded = self.total_rows if self.total_cols > utils.LARGE_COLS: self.cols_loaded = self.COLS_TO_LOAD else: self.cols_loaded = self.total_cols def set_hue_values(self) -> None: """Set hue values depending on array values (min/max)""" try: self.vmin = np.nanmin(self.color_func(self.get_array())) self.vmax = np.nanmax(self.color_func(self.get_array())) if self.vmax == self.vmin: self.vmin -= 1 self.hue0 = self.huerange[0] self.dhue = self.huerange[1] - self.huerange[0] self.bgcolor_enabled = True except (TypeError, ValueError): self.vmin = None self.vmax = None self.hue0 = None self.dhue = None self.bgcolor_enabled = False @handle_size_change(rows=True) def insert_row( self, index: int, insert_number: int = 1, default_value: Any = 0 ) -> None: """Insert new rows with a default value. Args: index: row index at which start the insertion insert_number: number of rows to insert. Defaults to 1. default_value: default value to insert. Defaults to 0. """ self._array_handler.insert_on_axis( index, self._row_axis_nd, insert_number, default_value ) @handle_size_change(rows=True) def remove_row(self, index: int, remove_number: int = 1) -> None: """Removes rows from the array. Args: index: row index at which to start the deletion remove_number: number of rows to delete. Defaults to 1. """ self._array_handler.delete_on_axis(index, self._row_axis_nd, remove_number) @handle_size_change(cols=True) def insert_column( self, index: int, insert_number: int = 1, default_value: Any = 0 ) -> None: """Insert new columns with a default value. Args: index: column index at which start the insertion insert_number: number of columns to insert. Defaults to 1. default_value: default value to insert. Defaults to 0. """ self._array_handler.insert_on_axis( index, self._col_axis_nd, insert_number, default_value ) @handle_size_change(cols=True) def remove_column(self, index: int, remove_number: int = 1) -> None: """Removes columns from the array. Args: index: column index at which to start the deletion remove_number: number of columns to delete. Defaults to 1. """ self._array_handler.delete_on_axis(index, self._col_axis_nd, remove_number) def reset(self) -> None: """Reset the model.""" self.beginResetModel() self.endResetModel() def get_array(self) -> AnySupportedArray: """Return the array. Returns Array stored in the array handler. Does not include data changes if not in variable_size mode. """ return self._array_handler.get_array() def apply_changes(self) -> None: """Validates and apply changes in the array. Irreversible.""" self._array_handler.apply_changes() def clear_changes(self) -> None: """Cancel all changes in the array. Irreversible.""" self._array_handler.clear_changes() class MaskedArrayModel(BaseArrayModel[MaskedArrayHandler, np.ma.MaskedArray]): """Wrapper around a MaskedArrayHandler. More specifically, this model handles the masked data. Check BaseArrayModel for more info on core functionnalities. Args: array_handler: instance of MaskedArrayHandler. format: format of the displayed values. Defaults to "%.6g". xlabels: labels for the columns (header). Defaults to None. ylabels: labels for the rows (header). Defaults to None. readonly: Flag to set the data in readonly mode. Defaults to False. parent: parent QObject. Defaults to None. current_slice: slice of the same dimension as the Numpy ndarray that will return a 2d array when applied to it. Defaults to None. """ def __init__( self, array_handler: MaskedArrayHandler, format="%.6g", xlabels=None, ylabels=None, readonly=False, parent=None, current_slice: Sequence[slice | int] | None = None, ) -> None: super().__init__( array_handler, format, xlabels, ylabels, readonly, parent, current_slice ) def get_insertion_dataset( self, index: int, axis: int ) -> type[BaseArrayModel.InsertionDataSet]: """See BaseArrayModel.get_insertion_dataset(). This method modifies the DataSet to include a boolean field to set a default value to the mask. """ class NewInsertionDataSet(super().get_insertion_dataset(index, axis)): """InsertionDataSet child class to handle new mask value on insertion""" mask_value = BoolItem(label="Mask value", default=False) def get_values_to_insert(self) -> tuple[Any, ...]: """See BaseArrayModel.InsertionDataSet.get_values_to_insert()""" return (self.default_value, self.mask_value) return NewInsertionDataSet @handle_size_change(rows=True) def insert_row( self, index: int, insert_number: int, default_value: Any, default_mask_value: bool = True, ) -> None: """Same as BaseArrayModel.insert_row() but adds the capacity to add a row Args: index: index at which to insert the row insert_number: number of rows to insert default_value: default value to insert default_mask_value: default mask value to insert. Defaults to True. """ self._array_handler.insert_on_axis( index, self._row_axis_nd, insert_number, default_value, default_mask_value ) # calls MaskedArrayHandler.insert_on_axis which has a default_mask argument @handle_size_change(cols=True) def insert_column( self, index: int, insert_number: int, default_value: Any, default_mask_value: bool = False, ) -> None: """Same as BaseArrayModel.insert_column() but adds the capacity to add a column Args: index: index at which to insert the column insert_number: number of columns to insert default_value: default value to insert default_mask_value: default mask value to insert. Defaults to False. """ self._array_handler.insert_on_axis( index, self._col_axis_nd, insert_number, default_value, default_mask_value ) # calls MaskedArrayHandler.insert_on_axis which has a default_mask argument class MaskArrayModel(MaskedArrayModel): """Wrapper around a MaskedArrayHandler. More specifically, this model handles the mask data. Check BaseArrayModel and MaskedArrayHandler for more core functionnalites. Args: array_handler: instance of MaskedArrayHander. format: format of the displayed values. Defaults to "%.6g". xlabels: labels for the columns (header). Defaults to None. ylabels: labels for the rows (header). Defaults to None. readonly: Flag to set the data in readonly mode. Defaults to False. parent: parent QObject. Defaults to None. current_slice: slice of the same dimension as the Numpy ndarray that will return a 2d array when applied to it. Defaults to None. """ def get_array(self) -> np.ndarray: """Returns the array mask (override of the BaseArrayModel.get_array() method) Returns: Boolean mask """ return self._array_handler.mask def get_value(self, index: tuple[int, ...]) -> bool: """Get a mask value (include the changes made). Like get_array(), this is a method override. Args: index: index from which to get the value Returns: Mask boolean """ return self._array_handler.get_mask_value(index) def set_value(self, index: tuple[int, ...], value: bool) -> None: """Set mask value (override of the BaseArrayModel.set_value() method) Args: index: index at which to set the value value: mask boolean """ self._array_handler.set_mask_value(index, value) class DataArrayModel(MaskedArrayModel): """Wrapper around a MaskedArrayHandler. More specifically, this model handles the raw unmaksed, data. Check BaseArrayModel and MaskedArrayHandler for more core functionnalites. Args: array_handler: instance of MaskedArrayHander. format: format of the displayed values. Defaults to "%.6g". xlabels: labels for the columns (header). Defaults to None. ylabels: labels for the rows (header). Defaults to None. readonly: Flag to set the data in readonly mode. Defaults to False. parent: parent QObject. Defaults to None. current_slice: slice of the same dimension as the Numpy ndarray that will return a 2d array when applied to it. Defaults to None. """ def get_array(self) -> memoryview: """Returns a memoryview that correspond to the raw (unmasked) data in the MaskedArray. Th ememoryview can be used like a standard numpy array. Returns: Data memoryview """ return self._array_handler.data def get_value(self, index: tuple[int, ...]) -> Any: """Get a value from underlying masked array data. Args: index: index to get a value from Returns: Data value at index """ return self._array_handler.get_data_value(index) def set_value(self, index: tuple[int, ...], value: Any) -> None: """Set a value in the underlying masked array data. Args: index: index at which to set the value value: value to set """ self._array_handler.set_data_value(index, value) class RecordArrayModel(BaseArrayModel[RecordArrayHandler, AnySupportedArray]): """Array Editor Table Model made for record arrays (= Numpy's structured arrays). Args: array_handler: instance of BaseArrayHandler or child classes. dtype_name: name of the type to handle in the model format: format of the displayed values. Defaults to "%.6g". xlabels: labels for the columns (header). Defaults to None. ylabels: labels for the rows (header). Defaults to None. readonly: Flag to set the data in readonly mode. Defaults to False. parent: parent QObject. Defaults to None. current_slice: slice of the same dimension as the Numpy ndarray that will return a 2d array when applied to it. Defaults to None. """ __slots__ = ("_dtype_name",) def __init__( self, array_handler: RecordArrayHandler, dtype_name: str, format="%.6g", xlabels=None, ylabels=None, readonly=False, parent=None, current_slice: Sequence[slice | int] | None = None, ) -> None: self._dtype_name = dtype_name super().__init__( array_handler, format, xlabels, ylabels, readonly, parent, current_slice ) def get_array(self) -> np.ndarray: """Returns the array (override of the BaseArrayModel.get_array() method) Returns: Array stored in the array handler. Does not include data changes if not in variable_size mode. """ return self._array_handler.get_array()[self._dtype_name] def get_value(self, index: tuple[int, ...]) -> Any: """Get a value from underlying masked array data. Args: index: index to get a value from Returns: Data value at index """ return self._array_handler.get_record_value(self._dtype_name, index) def set_value(self, index: tuple[int, ...], value: Any) -> None: """Set a value in the underlying masked array data. Args: index: index at which to set the value value: value to set """ self._array_handler.set_record_value(self._dtype_name, index, value) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/widgets/arrayeditor/editorwidget.py0000644000175100001770000010255714654363416023250 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) # # The array editor subpackage was derived from Spyder's arrayeditor.py module # which is licensed under the terms of the MIT License (see spyder/__init__.py # for details), copyright ┬й Spyder Project Contributors # pylint: disable=C0103 # pylint: disable=R0903 # pylint: disable=R0911 # pylint: disable=R0201 """Array editor widget""" from __future__ import annotations import io from typing import Any, Generic, Sequence, cast import numpy as np from qtpy.QtCore import QItemSelection, QItemSelectionRange, QLocale, QPoint, Qt, Slot from qtpy.QtGui import QCursor, QDoubleValidator, QKeySequence from qtpy.QtWidgets import ( QAbstractItemDelegate, QApplication, QCheckBox, QHBoxLayout, QInputDialog, QItemDelegate, QLineEdit, QMenu, QMessageBox, QPushButton, QShortcut, QTableView, QVBoxLayout, QWidget, ) from guidata.config import CONF, _ from guidata.configtools import get_font, get_icon from guidata.qthelpers import add_actions, create_action, keybinding from guidata.widgets import about from guidata.widgets.arrayeditor import utils from guidata.widgets.arrayeditor.arrayhandler import ( AnySupportedArray, BaseArrayHandler, MaskedArrayHandler, RecordArrayHandler, ) from guidata.widgets.arrayeditor.datamodel import ( ArrayModelType, BaseArrayModel, DataArrayModel, MaskArrayModel, MaskedArrayModel, RecordArrayModel, ) class ArrayDelegate(QItemDelegate): """Array Editor Item Delegate Args: dtype: Numpy's dtype of the array to edit parent: parent QObject """ def __init__(self, dtype: np.dtype, parent=None) -> None: QItemDelegate.__init__(self, parent) self.dtype = dtype def createEditor(self, parent, option, index) -> QLineEdit | None: """Create editor widget""" model: BaseArrayModel = index.model() # type: ignore value = model.get_value((index.row(), index.column())) if model.get_array().dtype.name == "bool": value = not value model.setData(index, value) return None if value is not np.ma.masked: editor = QLineEdit(parent) editor.setFont(get_font(CONF, "arrayeditor", "font")) editor.setAlignment(Qt.AlignmentFlag.AlignCenter) if utils.is_number(self.dtype): validator = QDoubleValidator(editor) validator.setLocale(QLocale("C")) editor.setValidator(validator) editor.returnPressed.connect(self.commitAndCloseEditor) return editor return None def commitAndCloseEditor(self) -> None: """Commit and close editor""" editor = self.sender() # Avoid a segfault with PyQt5. Variable value won't be changed # but at least Spyder won't crash. It seems generated by a bug in sip. try: self.commitData.emit(editor) except AttributeError as e: print(e) pass self.closeEditor.emit(editor, QAbstractItemDelegate.EndEditHint.NoHint) def setEditorData(self, editor, index) -> None: """Set editor widget's data""" if (model := index.model()) is not None and editor is not None: text = model.data(index, Qt.ItemDataRole.DisplayRole) editor.setText(text) class DefaultValueDelegate(QItemDelegate): """Array Editor Item Delegate Args: dtype: Numpy's dtype of the array to edit parent: parent QObject """ def __init__(self, dtype: np.dtype, parent=None) -> None: QItemDelegate.__init__(self, parent) self.dtype = dtype (self.default_value,) = np.zeros(1, dtype=dtype) def createEditor(self, parent, option, index) -> QLineEdit: """Create editor widget""" editor = QLineEdit(parent) editor.setFont(get_font(CONF, "arrayeditor", "font")) editor.setAlignment(Qt.AlignmentFlag.AlignCenter) if utils.is_number(self.dtype): validator = QDoubleValidator(editor) validator.setLocale(QLocale("C")) editor.setValidator(validator) editor.returnPressed.connect(self.commitAndCloseEditor) return editor def commitAndCloseEditor(self): """Commit and close editor""" editor = self.sender() # Avoid a segfault with PyQt5. Variable value won't be changed # but at least Spyder won't crash. It seems generated by a bug in sip. try: self.commitData.emit(editor) except AttributeError as e: print(e) pass self.closeEditor.emit(editor, QAbstractItemDelegate.EndEditHint.NoHint) def setEditorData(self, editor, index): """Set editor widget's data""" if (model := index.model()) is not None and editor is not None: text = model.data(index, Qt.ItemDataRole.DisplayRole) editor.setText(text) # TODO: Implement "Paste" (from clipboard) feature class ArrayView(QTableView, Generic[ArrayModelType]): """Array view class Args: parent: parent QObject model: BaseArrayModel to use dtype: Numpy's dtype of the array to edit shape: Numpy's shape of the array to edit variable_size: Flag to indicate if the array dimensions can be modified. If a BaseArrayHandler is given as input, the handler should also be in readonly mode. Defaults to False. """ def __init__( self, parent: QWidget, model: ArrayModelType, dtype, shape, variable_size=False ) -> None: QTableView.__init__(self, parent) self._variable_size = variable_size self.setModel(model) self.setItemDelegate(ArrayDelegate(dtype, self)) total_width = 0 for k in range(self.model().shape[1]): total_width += self.columnWidth(k) self.viewport().resize(min(total_width, 1024), self.height()) QShortcut(QKeySequence(QKeySequence.Copy), self, self.copy) self.horizontalScrollBar().valueChanged.connect( lambda val: self.load_more_data(val, columns=True) ) self.verticalScrollBar().valueChanged.connect( lambda val: self.load_more_data(val, rows=True) ) if self._variable_size: self._current_row_index = None self.vheader_menu = self.setup_header_menu(0) vheader = self.verticalHeader() vheader.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) vheader.customContextMenuRequested.connect(self.verticalHeaderContextMenu) self._current_col_index = None self.hheader_menu = self.setup_header_menu(1) hheader = self.horizontalHeader() hheader.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) hheader.customContextMenuRequested.connect(self.horizontalHeaderContextMenu) self.cell_menu = self.setup_cell_menu() self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self.customContextMenuRequested.connect(self.cellContextMenu) def model(self) -> ArrayModelType: """Returns the current BaseArrayModel or raises an TypeError Raises: ValueError: if the model is not a BaseArrayModel Returns: Current array model """ assert isinstance(model := super().model(), BaseArrayModel) return cast(ArrayModelType, model) def load_more_data(self, value: int, rows=False, columns=False) -> None: """Load more data if needed Args: value: scrollbar value rows: Flag to indicate if rows should be loaded columns: Flag to indicate if columns should be loaded """ old_selection = self.selectionModel().selection() old_rows_loaded = old_cols_loaded = None if rows and value == self.verticalScrollBar().maximum(): old_rows_loaded = self.model().rows_loaded self.model().fetch(rows=rows) if columns and value == self.horizontalScrollBar().maximum(): old_cols_loaded = self.model().cols_loaded self.model().fetch(columns=columns) if old_rows_loaded is not None or old_cols_loaded is not None: # if we've changed anything, update selection new_selection = QItemSelection() for part in old_selection: top = part.top() bottom = part.bottom() if ( old_rows_loaded is not None and top == 0 and bottom == (old_rows_loaded - 1) ): # complete column selected (so expand it to match updated range) bottom = self.model().rows_loaded - 1 left = part.left() right = part.right() if ( old_cols_loaded is not None and left == 0 and right == (old_cols_loaded - 1) ): # compete row selected (so expand it to match updated range) right = self.model().cols_loaded - 1 top_left = self.model().index(top, left) bottom_right = self.model().index(bottom, right) part = QItemSelectionRange(top_left, bottom_right) new_selection.append(part) self.selectionModel().select( new_selection, self.selectionModel().ClearAndSelect ) def insert_row(self) -> None: """Insert row(s) in the array.""" if (i := self._current_row_index) is not None: ( i, insert_number, default_values, _new_label, valid, ) = self.ask_default_inserted_value(i, 0) if valid: self.model().insert_row(i, insert_number, *default_values) self._current_row_index = None def remove_row(self) -> None: """Remove row(s) in the array""" if (i := self._current_row_index) is not None: i, remove_number, valid = self.ask_rows_cols_to_remove(i, 0) if valid: self.model().remove_row(i, remove_number) self._current_row_index = None def insert_col(self) -> None: """Insert column(s) in the array""" if (j := self._current_col_index) is not None: ( j, insert_number, default_value, _new_label, valid, ) = self.ask_default_inserted_value(j, 1) if valid: self.model().insert_column(j, insert_number, *default_value) self._current_col_index = None def remove_col(self) -> None: """Remove column(s) in the array""" if (j := self._current_col_index) is not None: j, remove_number, valid = self.ask_rows_cols_to_remove(j, 1) if valid: self.model().remove_column(j, remove_number) self._current_col_index = None def ask_default_inserted_value( self, index: int, axis: int ) -> tuple[int, int, tuple[Any, ...], Any | None, bool]: """Create and open a new dialog with a form to input insertion parameters Args: index: default insertion index (choice made when clicking) axis: insertion axis (row (0) or column (1)) Returns: User inputs """ InsertionDataSet = self.model().get_insertion_dataset(index, axis) title = ( _("Row(s) insertion") if axis == 0 else _("Column(s) insertion") if axis == 1 else "" ) insertion_dataset = InsertionDataSet( title=title, icon="insert.png", ) is_ok = insertion_dataset.edit() index_: int = insertion_dataset.index_field max_index = ( self.model() .get_array() .shape[self.model().correct_ndim_axis_for_current_slice(axis)] ) index_ = max_index if index_ == -1 else index_ return ( index_, insertion_dataset.insert_number, insertion_dataset.get_values_to_insert(), insertion_dataset.new_label, is_ok, ) # type: ignore def ask_rows_cols_to_remove(self, index: int, axis: int) -> tuple[int, int, bool]: """Create and open a new dialog with a form to input deletion parameters Args: index: default deletion index (choice made when clicking) axis: deletion axis (row (0) or column (1)) Returns: User inputs """ DeletionDataSet = self.model().get_deletion_dataset(index, axis) title = ( _("Row(s) deletion") if axis == 0 else _("Column(s) deletion") if axis == 1 else "" ) deletion_dataset = DeletionDataSet( title=title, icon="delete.png", ) is_ok = deletion_dataset.edit() index_ = deletion_dataset.index_field max_index = ( self.model() .get_array() .shape[self.model().correct_ndim_axis_for_current_slice(axis)] ) number_to_del = min(deletion_dataset.remove_number, max_index - index_) return index_, number_to_del, is_ok # type: ignore def resize_to_contents(self) -> None: """Resize cells to contents""" QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor)) self.resizeColumnsToContents() self.model().fetch(columns=True) self.resizeColumnsToContents() QApplication.restoreOverrideCursor() def setup_cell_menu(self) -> QMenu: """Setup context menu Returns: New QMenu object """ self.copy_action = create_action( self, _("Copy"), shortcut=keybinding("Copy"), icon=get_icon("editcopy.png"), triggered=self.copy, context=Qt.ShortcutContext.WidgetShortcut, ) about_action = create_action( self, _("About..."), icon=get_icon("guidata.svg"), triggered=about.show_about_dialog, ) if self._variable_size: insert_row_action = create_action( self, title=_("Insert row(s)"), icon=get_icon("insert.png"), triggered=self.insert_row, ) insert_col_action = create_action( self, title=_("Insert column(s)"), icon=get_icon("insert.png"), triggered=self.insert_col, ) remove_row_action = create_action( self, title=_("Remove row(s)"), icon=get_icon("delete.png"), triggered=self.remove_row, ) remove_col_action = create_action( self, title=_("Remove column(s)"), icon=get_icon("delete.png"), triggered=self.remove_col, ) actions = ( self.copy_action, None, insert_row_action, insert_col_action, None, remove_row_action, remove_col_action, None, about_action, ) else: actions = ( self.copy_action, None, about_action, ) menu = QMenu(self) add_actions(menu, actions) return menu def setup_header_menu(self, axis: int) -> QMenu: """Creates and return a contextual menu for a header depending on input axis. Args: axis: 0 for rows (vertical header), 1 for columns (horizontal header) Returns: New QMenu object for the given header """ action_args = { 0: ( (_("Insert row(s)"), self.insert_row), (_("Remove row(s)"), self.remove_row), ), 1: ( (_("Insert column(s)"), self.insert_col), (_("Remove column(s)"), self.remove_col), ), }[axis] insert_action = create_action( self, title=action_args[0][0], icon=get_icon("insert.png"), triggered=action_args[0][1], ) remove_action = create_action( self, title=action_args[1][0], icon=get_icon("delete.png"), triggered=action_args[1][1], ) actions = ( insert_action, None, remove_action, ) menu = QMenu(self) add_actions(menu, actions) return menu def verticalHeaderContextMenu(self, pos: QPoint) -> None: """Reimplement Qt method""" vheader = self.verticalHeader() self._current_row_index = vheader.logicalIndexAt(pos) self.vheader_menu.popup(vheader.mapToGlobal(pos)) def horizontalHeaderContextMenu(self, pos: QPoint) -> None: """Reimplement Qt method""" hheader = self.horizontalHeader() self._current_col_index = hheader.logicalIndexAt(pos) self.hheader_menu.popup(hheader.mapToGlobal(pos)) def cellContextMenu(self, pos: QPoint) -> None: """Reimplement Qt method""" try: selected_index = self.selectedIndexes()[0] self._current_row_index, self._current_col_index = ( selected_index.row(), selected_index.column(), ) except IndexError: # click outside of cells self._current_row_index, self._current_col_index = ( self.model().total_rows, self.model().total_cols, ) # we get the index of the last array element to insert after the last row/column self.cell_menu.popup(self.viewport().mapToGlobal(pos)) def keyPressEvent(self, event) -> None: """Reimplement Qt method""" if event == QKeySequence.Copy: self.copy() else: QTableView.keyPressEvent(self, event) def _sel_to_text(self, cell_range: list[QItemSelectionRange]) -> str | None: """Copy an array portion to a unicode string Args: cell_range: list of QItemSelectionRange objects Returns: String representation of the selected array portion, or None if the selection is empty """ if not cell_range: return None model = self.model() row_min, row_max, col_min, col_max = utils.get_idx_rect(cell_range) if col_min == 0 and col_max == (model.cols_loaded - 1): # we've selected a whole column. It isn't possible to # select only the first part of a column without loading more, # so we can treat it as intentional and copy the whole thing col_max = model.total_cols - 1 if row_min == 0 and row_max == (model.rows_loaded - 1): row_max = model.total_rows - 1 model.apply_changes() _data = model.get_array() # TODO check if this should apply changes or not output = io.BytesIO() try: np.savetxt( output, _data[row_min : row_max + 1, col_min : col_max + 1], delimiter="\t", fmt=model.get_format(), ) except BaseException: QMessageBox.warning( self, _("Warning"), _("It was not possible to copy values for this array"), ) return None contents = output.getvalue().decode("utf-8") output.close() return contents @Slot() def copy(self) -> None: """Copy text to clipboard""" cliptxt = self._sel_to_text(self.selectedIndexes()) clipboard = QApplication.clipboard() clipboard.setText(cliptxt) class BaseArrayEditorWidget(QWidget): """Base ArrayEditdorWidget class. Used to wrap handle n-dimensional normal Numpy's ndarray. Args: parent: parent QObject data: Numpy's ndarray or BaseArrayHandler to use. readonly: Flag for readonly mode. Defaults to False. xlabels: labels for the columns (header). Defaults to None. ylabels: labels for the rows (header). Defaults to None. variable_size: Flag to indicate if the array dimensions can be modified. If a BaseArrayHandler is given as input, the handler should also be in readonly mode Defaults to False. current_slice: slice of the same dimension as the Numpy ndarray that will. Defaults to None """ def __init__( self, parent, data: np.ndarray | BaseArrayHandler, readonly=False, xlabels=None, ylabels=None, variable_size=False, current_slice: Sequence[slice | int] | None = None, ) -> None: QWidget.__init__(self, parent) self._variable_size = variable_size and not readonly self._data: BaseArrayHandler | MaskedArrayHandler self._init_handler(data) self._init_model(xlabels, ylabels, readonly, current_slice=current_slice) format = utils.SUPPORTED_FORMATS.get(self.model.get_array().dtype.name, "%s") self.model.set_format(format) self.view = ArrayView( self, self.model, self.model.get_array().dtype, self._data.shape, self._variable_size, ) btn_layout = QHBoxLayout() btn_layout.setAlignment(Qt.AlignmentFlag.AlignLeft) btn = QPushButton(_("Format")) # disable format button for int type btn.setEnabled(utils.is_float(self._data.dtype)) btn_layout.addWidget(btn) btn.clicked.connect(self.change_format) btn = QPushButton(_("Resize")) btn_layout.addWidget(btn) btn.clicked.connect(self.view.resize_to_contents) bgcolor = QCheckBox(_("Background color")) bgcolor.setChecked(self.model.bgcolor_enabled) bgcolor.setEnabled(self.model.bgcolor_enabled) bgcolor.stateChanged.connect(self.model.bgcolor) btn_layout.addWidget(bgcolor) layout = QVBoxLayout() layout.addWidget(self.view) layout.addLayout(btn_layout) self.setLayout(layout) def _init_handler( self, data: AnySupportedArray | BaseArrayHandler[AnySupportedArray] ) -> None: """Initializes and set the instance handler to use Args: data: Numpy's ndarray or BaseArrayHandler to use. """ if isinstance(data, np.ndarray): self._data = BaseArrayHandler[AnySupportedArray](data, self._variable_size) elif isinstance(data, BaseArrayHandler): self._data = data else: raise TypeError( "Given data must be of type np.ndarray or BaseArrayHandler, " f"not {type(data)}" ) def _init_model( self, xlabels: Sequence[str] | None, ylabels: Sequence[str] | None, readonly: bool, current_slice: Sequence[slice | int] | None = None, ) -> None: """Initializes and set the instance model to use Args: xlabels: labels for the columns (header). Defaults to None. ylabels: labels for the rows (header). Defaults to None. readonly: Flag for readonly mode. Defaults to False. current_slice: slice of the same dimension as the Numpy ndarray that will. """ self.model = BaseArrayModel( self._data, xlabels=xlabels, ylabels=ylabels, readonly=readonly, parent=self, current_slice=current_slice, ) def accept_changes(self) -> None: """Accept changes""" self.model.apply_changes() def reject_changes(self) -> None: """Reject changes""" self.model.clear_changes() def change_format(self) -> None: """Change display format""" format, valid = QInputDialog.getText( self, _("Format"), _("Float formatting"), QLineEdit.Normal, self.model.get_format(), ) if valid: format = str(format) try: format % 1.1 except BaseException: QMessageBox.critical( self, _("Error"), _("Format (%s) is incorrect") % format ) return self.model.set_format(format) class MaskedArrayEditorWidget(BaseArrayEditorWidget): """Same as BaseArrayWidgetEditorWidget but specifically handles MaskedArrayHandler and MaskedArrayModel. Specifically the masked data. Args: parent: parent QObject data: Numpy's ndarray or BaseArrayHandler to use. readonly: Flag for readonly mode. Defaults to False. xlabels: labels for the columns (header). Defaults to None. ylabels: labels for the rows (header). Defaults to None. variable_size: Flag to indicate if the array dimensions can be modified. If a BaseArrayHandler is given as input, the handler should also be in readonly mode Defaults to False. current_slice: slice of the same dimension as the Numpy ndarray that will. Defaults to None """ # _data: MaskedArrayHandler def _init_handler(self, data: np.ma.MaskedArray | MaskedArrayHandler) -> None: """Initializes and set the instance handler to use Args: data: Numpy's MaskedArray or MaskedArrayHandler to use. """ if isinstance(data, np.ma.MaskedArray): self._data = MaskedArrayHandler(data, self._variable_size) elif isinstance(data, MaskedArrayHandler): self._data = data else: raise TypeError( "Given data must be of type np.ma.MaskedArray or " "MaskedArrayHandler, not {type(data)}" ) def _init_model( self, xlabels: Sequence[str] | None, ylabels: Sequence[str] | None, readonly: bool, current_slice: Sequence[slice | int] | None = None, ) -> None: """Initializes and set the instance model to use Args: xlabels: labels for the columns (header). Defaults to None. ylabels: labels for the rows (header). Defaults to None. readonly: Flag for readonly mode. Defaults to False. current_slice: slice of the same dimension as the Numpy ndarray that will. """ assert isinstance(self._data, MaskedArrayHandler) self.model = MaskedArrayModel( self._data, xlabels=xlabels, ylabels=ylabels, readonly=readonly, parent=self, current_slice=current_slice, ) class MaskArrayEditorWidget(MaskedArrayEditorWidget): """Same as BaseArrayWidgetEditorWidget but specifically handles MaskedArrayHandler and MaskArrayModel. Specifically the boolean mask. Args: parent: parent QObject data: Numpy's ndarray or BaseArrayHandler to use. readonly: Flag for readonly mode. Defaults to False. xlabels: labels for the columns (header). Defaults to None. ylabels: labels for the rows (header). Defaults to None. variable_size: Flag to indicate if the array dimensions can be modified. If a BaseArrayHandler is given as input, the handler should also be in readonly mode Defaults to False. current_slice: slice of the same dimension as the Numpy ndarray that will. Defaults to None """ # _data: MaskedArrayHandler def _init_model( self, xlabels: Sequence[str] | None, ylabels: Sequence[str] | None, readonly: bool, current_slice: Sequence[slice | int] | None = None, ) -> None: """Initializes and set the instance model to use Args: xlabels: labels for the columns (header). Defaults to None. ylabels: labels for the rows (header). Defaults to None. readonly: Flag for readonly mode. Defaults to False. current_slice: slice of the same dimension as the Numpy ndarray that will. """ assert isinstance(self._data, MaskedArrayHandler) self.model = MaskArrayModel( self._data, xlabels=xlabels, ylabels=ylabels, readonly=readonly, parent=self, current_slice=current_slice, ) class DataArrayEditorWidget(MaskedArrayEditorWidget): """Same as BaseArrayWidgetEditorWidget but specifically handles MaskedArrayHandler and DataArrayModel. Specifically the raw unmasked data. Args: parent: parent QObject data: Numpy's ndarray or BaseArrayHandler to use. readonly: Flag for readonly mode. Defaults to False. xlabels: labels for the columns (header). Defaults to None. ylabels: labels for the rows (header). Defaults to None. variable_size: Flag to indicate if the array dimensions can be modified. If a BaseArrayHandler is given as input, the handler should also be in readonly mode Defaults to False. current_slice: slice of the same dimension as the Numpy ndarray that will. Defaults to None """ # _data: MaskedArrayHandler def __init__( self, parent: QWidget, data: np.ma.MaskedArray | MaskedArrayHandler, readonly=False, xlabels=None, ylabels=None, variable_size=False, current_slice: Sequence[slice | int] | None = None, ) -> None: super().__init__( parent, data, readonly, xlabels, ylabels, variable_size, current_slice ) def _init_model( self, xlabels: Sequence[str] | None, ylabels: Sequence[str] | None, readonly: bool, current_slice: Sequence[slice | int] | None = None, ) -> None: """Initializes and set the instance model to use Args: xlabels: labels for the columns (header). Defaults to None. ylabels: labels for the rows (header). Defaults to None. readonly: Flag for readonly mode. Defaults to False. current_slice: slice of the same dimension as the Numpy ndarray that will. """ assert isinstance(self._data, MaskedArrayHandler) self.model = DataArrayModel( self._data, xlabels=xlabels, ylabels=ylabels, readonly=readonly, parent=self, current_slice=current_slice, ) class RecordArrayEditorWidget(BaseArrayEditorWidget): """Same as BaseArrayWidgetEditorWidget but specifically handles RecordArrayHandler and RecordArrayModel which are made to wrap Numpy's structured arrays. Args: parent: parent QObject data: Numpy's ndarray or BaseArrayHandler to use. readonly: Flag for readonly mode. Defaults to False. xlabels: labels for the columns (header). Defaults to None. ylabels: labels for the rows (header). Defaults to None. variable_size: Flag to indicate if the array dimensions can be modified. If a BaseArrayHandler is given as input, the handler should also be in readonly mode Defaults to False. current_slice: slice of the same dimension as the Numpy ndarray that will. Defaults to None """ def __init__( self, parent, data: RecordArrayHandler, dtype_name: str, readonly=False, xlabels=None, ylabels=None, variable_size=False, current_slice: Sequence[slice | int] | None = None, ) -> None: self._dtype_name = dtype_name super().__init__( parent, data, readonly, xlabels, ylabels, variable_size, current_slice ) def _init_handler(self, data: np.ndarray | RecordArrayHandler) -> None: """Initializes and set the instance handler to use Args: data: Numpy's ndarray or BaseArrayHandler to use. """ if isinstance(data, np.ma.MaskedArray): self._data = RecordArrayHandler(data, self._variable_size) elif isinstance(data, RecordArrayHandler): self._data = data else: raise TypeError( f"Given data must be of type np.ndarray or RecordArrayHandler, not {type(data)}" ) def _init_model( self, xlabels: Sequence[str] | None, ylabels: Sequence[str] | None, readonly: bool, current_slice: Sequence[slice | int] | None = None, ) -> None: """Initializes and set the instance model to use Args: xlabels: labels for the columns (header). Defaults to None. ylabels: labels for the rows (header). Defaults to None. readonly: Flag for readonly mode. Defaults to False. current_slice: slice of the same dimension as the Numpy ndarray that will. """ assert isinstance(self._data, RecordArrayHandler) self.model = RecordArrayModel( self._data, self._dtype_name, xlabels=xlabels, ylabels=ylabels, readonly=readonly, parent=self, current_slice=current_slice, ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/widgets/arrayeditor/utils.py0000644000175100001770000000500114654363416021700 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) # # The array editor subpackage was derived from Spyder's arrayeditor.py module # which is licensed under the terms of the MIT License (see spyder/__init__.py # for details), copyright ┬й Spyder Project Contributors # Note: string and unicode data types will be formatted with '%s' (see below) """Basic utilitarian functions and variables for the various array editor classes""" import numpy as np SUPPORTED_FORMATS = { "single": "%.6g", "double": "%.6g", "float_": "%.6g", "longfloat": "%.6g", "float16": "%.6g", "float32": "%.6g", "float64": "%.6g", "float96": "%.6g", "float128": "%.6g", "csingle": "%r", "complex_": "%r", "clongfloat": "%r", "complex64": "%r", "complex128": "%r", "complex192": "%r", "complex256": "%r", "byte": "%d", "bytes8": "%s", "short": "%d", "intc": "%d", "int_": "%d", "longlong": "%d", "intp": "%d", "int8": "%d", "int16": "%d", "int32": "%d", "int64": "%d", "ubyte": "%d", "ushort": "%d", "uintc": "%d", "uint": "%d", "ulonglong": "%d", "uintp": "%d", "uint8": "%d", "uint16": "%d", "uint32": "%d", "uint64": "%d", "bool_": "%r", "bool8": "%r", "bool": "%r", } LARGE_SIZE = 5e5 LARGE_NROWS = 1e5 LARGE_COLS = 60 # ============================================================================== # Utility functions # ============================================================================== def is_float(dtype: np.dtype) -> bool: """Return True if datatype dtype is a float kind Args: dtype: numpy datatype Returns: True if dtype is a float kind """ return ("float" in dtype.name) or dtype.name in ["single", "double"] def is_number(dtype: np.dtype) -> bool: """Return True is datatype dtype is a number kind Args: dtype: numpy datatype Returns: True if dtype is a number kind """ return ( is_float(dtype) or ("int" in dtype.name) or ("long" in dtype.name) or ("short" in dtype.name) ) def get_idx_rect(index_list: list) -> tuple: """Extract the boundaries from a list of indexes Args: index_list: list of indexes Returns: tuple: (min_row, max_row, min_col, max_col) """ rows, cols = list(zip(*[(i.row(), i.column()) for i in index_list])) return (min(rows), max(rows), min(cols), max(cols)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/widgets/codeeditor.py0000644000175100001770000004040414654363416020342 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Copyright ┬й Spyder Project Contributors # Licensed under the terms of the MIT License # (see spyder/__init__.py for details) """ guidata.widgets.codeeditor ========================== This package provides an Editor widget based on QtGui.QPlainTextEdit. .. autoclass:: CodeEditor :show-inheritance: :members: """ # %% This line is for cell execution testing # pylint: disable=C0103 # pylint: disable=R0903 # pylint: disable=R0911 # pylint: disable=R0201 from __future__ import annotations from qtpy.QtCore import QRect, QSize, Qt, QTimer, Signal from qtpy.QtGui import QColor, QFont, QPainter from qtpy.QtWidgets import QPlainTextEdit, QWidget import guidata.widgets.syntaxhighlighters as sh from guidata.config import CONF, _ from guidata.configtools import get_font, get_icon from guidata.qthelpers import ( add_actions, create_action, is_dark_theme, win32_fix_title_bar_background, ) from guidata.utils import encoding from guidata.widgets import about class LineNumberArea(QWidget): """Line number area (on the left side of the text editor widget) Args: editor: CodeEditor widget """ def __init__(self, editor: CodeEditor) -> None: QWidget.__init__(self, editor) self.code_editor = editor self.setMouseTracking(True) def sizeHint(self): """Override Qt method""" return QSize(self.code_editor.compute_linenumberarea_width(), 0) def paintEvent(self, event): """Override Qt method""" self.code_editor.linenumberarea_paint_event(event) def mouseMoveEvent(self, event): """Override Qt method""" self.code_editor.linenumberarea_mousemove_event(event) def mouseDoubleClickEvent(self, event): """Override Qt method""" self.code_editor.linenumberarea_mousedoubleclick_event(event) def mousePressEvent(self, event): """Override Qt method""" self.code_editor.linenumberarea_mousepress_event(event) def mouseReleaseEvent(self, event): """Override Qt method""" self.code_editor.linenumberarea_mouserelease_event(event) def wheelEvent(self, event): """Override Qt method""" self.code_editor.wheelEvent(event) class CodeEditor(QPlainTextEdit): """Code editor widget Args: parent: Parent widget language: Language used for syntax highlighting font: Font used for the text columns: Number of columns rows: Number of rows inactivity_timeout: after this delay of inactivity (in milliseconds), the :py:attr:`CodeEditor.SIG_EDIT_STOPPED` signal is emitted """ # To have these attrs when early viewportEvent's are triggered linenumberarea = None #: Signal emitted when text changes and the user stops typing for some time SIG_EDIT_STOPPED = Signal() LANGUAGES = { "python": sh.PythonSH, "cython": sh.CythonSH, "fortran77": sh.Fortran77SH, "fortran": sh.FortranSH, "idl": sh.IdlSH, "diff": sh.DiffSH, "gettext": sh.GetTextSH, "nsis": sh.NsisSH, "html": sh.HtmlSH, "yaml": sh.YamlSH, "cpp": sh.CppSH, "opencL": sh.OpenCLSH, "enaml": sh.EnamlSH, # Every other language None: sh.TextSH, } def __init__( self, parent: QWidget = None, language: str | None = None, font: QFont | None = None, columns: int | None = None, rows: int | None = None, inactivity_timeout: int = 1000, ) -> None: QPlainTextEdit.__init__(self, parent) self.visible_blocks = [] self.highlighter = None self.normal_color = None self.sideareas_color = None self.linenumbers_color = None # Line number area management self.linenumbers_margin = True self.linenumberarea_enabled = None self.linenumberarea_pressed = None self.linenumberarea_released = None self.timer = QTimer() self.timer.setSingleShot(True) self.timer.setInterval(inactivity_timeout) # When the editor is destroyed, the timer is destroyed as well, so we do # not need to disconnect the timer from the SIG_EDIT_STOPPED signal. # But... we connect the timer to the SIG_EDIT_STOPPED signal directly: # we do not connect it to the `emit` method for two reasons. # # 1. The documented way to connect a signal to another signal is the # following: `signal1.connect(signal2)`. # # 2. When the editor is destroyed, if the timer is connected to the `emit` # method, the timer will try to call the `emit` method which is still bound # to the destroyed editor. This will cause a crash, eventually (there is a # time window between the destruction of the editor and the destruction of # the timer, so the crash is not guaranteed to happen, but it is possible). # On the other hand, if the timer is connected to the SIG_EDIT_STOPPED # signal, when the timeout is reached, the connection will be handled by # Qt and the `emit` method will not be called, so there will be no crash. self.timer.timeout.connect(self.SIG_EDIT_STOPPED) self.textChanged.connect(self.text_has_changed) self.setFocusPolicy(Qt.StrongFocus) self.setup(language=language, font=font, columns=columns, rows=rows) self.update_color_mode() def update_color_mode(self) -> None: """Update color mode according to the current theme""" win32_fix_title_bar_background(self) suffix = "dark" if is_dark_theme() else "light" color_scheme = CONF.get("color_schemes", "default/" + suffix) self.highlighter.set_color_scheme(color_scheme) self.highlighter.rehighlight() self.linenumbers_color = QColor( Qt.lightGray if is_dark_theme() else Qt.darkGray ) self.normal_color = self.highlighter.get_foreground_color() self.sideareas_color = self.highlighter.get_sideareas_color() self.linenumberarea.update() def text_has_changed(self) -> None: """Text has changed: restart the timer to emit SIG_EDIT_STOPPED after a delay""" if self.timer.isActive(): self.timer.stop() self.timer.start() def contextMenuEvent(self, event): """Override Qt method""" menu = self.createStandardContextMenu() about_action = create_action( self, _("About..."), icon=get_icon("guidata.svg"), triggered=about.show_about_dialog, ) add_actions(menu, (None, about_action)) menu.exec(event.globalPos()) def setup(self, language=None, font=None, columns=None, rows=None): """Setup widget""" if font is None: font = get_font(CONF, "codeeditor") self.setFont(font) self.setup_linenumberarea() if language is not None: language = language.lower() highlighter_class = self.LANGUAGES.get(language) if highlighter_class is None: raise ValueError("Unsupported language '%s'" % language) self.highlighter = highlighter_class(self.document(), self.font()) self.highlighter.rehighlight() self.normal_color = self.highlighter.get_foreground_color() self.sideareas_color = self.highlighter.get_sideareas_color() if columns is not None: self.set_minimum_width(columns) if rows is not None: self.set_minimum_height(rows) def set_minimum_width(self, columns): """Set widget minimum width to show the specified number of columns""" width = self.fontMetrics().width("9" * (columns + 8)) self.setMinimumWidth(width) def set_minimum_height(self, rows): """Set widget minimum height to show the specified number of rows""" height = self.fontMetrics().height() * (rows + 1) self.setMinimumHeight(height) def setup_linenumberarea(self): """Setup widget""" self.linenumberarea = LineNumberArea(self) self.blockCountChanged.connect(self.update_linenumberarea_width) self.updateRequest.connect(self.update_linenumberarea) self.linenumberarea_pressed = -1 self.linenumberarea_released = -1 self.set_linenumberarea_enabled(True) self.update_linenumberarea_width() def set_text_from_file(self, filename): """Set the text of the editor from file *fname*""" text, _enc = encoding.read(filename) self.setPlainText(text) # -----linenumberarea def set_linenumberarea_enabled(self, state): """ :param state: """ self.linenumberarea_enabled = state self.linenumberarea.setVisible(state) self.update_linenumberarea_width() def get_linenumberarea_width(self): """Return current line number area width""" return self.linenumberarea.contentsRect().width() def compute_linenumberarea_width(self): """Compute and return line number area width""" if not self.linenumberarea_enabled: return 0 digits = 1 maxb = max(1, self.blockCount()) while maxb >= 10: maxb /= 10 digits += 1 if self.linenumbers_margin: linenumbers_margin = 3 + self.fontMetrics().width("9" * digits) else: linenumbers_margin = 0 return linenumbers_margin def update_linenumberarea_width(self, new_block_count=None): """ Update line number area width. new_block_count is needed to handle blockCountChanged(int) signal """ self.setViewportMargins(self.compute_linenumberarea_width(), 0, 0, 0) def update_linenumberarea(self, qrect, dy): """Update line number area""" if dy: self.linenumberarea.scroll(0, dy) else: self.linenumberarea.update( 0, qrect.y(), self.linenumberarea.width(), qrect.height() ) if qrect.contains(self.viewport().rect()): self.update_linenumberarea_width() def linenumberarea_paint_event(self, event): """Painting line number area""" painter = QPainter(self.linenumberarea) painter.fillRect(event.rect(), self.sideareas_color) # This is needed to make that the font size of line numbers # be the same as the text one when zooming # See Issues 2296 and 4811 font = self.font() font_height = self.fontMetrics().height() active_block = self.textCursor().block() active_line_number = active_block.blockNumber() + 1 for top, line_number, block in self.visible_blocks: if self.linenumbers_margin: if line_number == active_line_number: font.setWeight(QFont.Bold) painter.setFont(font) painter.setPen(self.normal_color) else: font.setWeight(QFont.Normal) painter.setFont(font) painter.setPen(self.linenumbers_color) painter.drawText( 0, top, self.linenumberarea.width(), font_height, Qt.AlignRight | Qt.AlignBottom, str(line_number), ) def __get_linenumber_from_mouse_event(self, event): """Return line number from mouse event""" block = self.firstVisibleBlock() line_number = block.blockNumber() top = self.blockBoundingGeometry(block).translated(self.contentOffset()).top() bottom = top + self.blockBoundingRect(block).height() while block.isValid() and top < event.pos().y(): block = block.next() top = bottom bottom = top + self.blockBoundingRect(block).height() line_number += 1 return line_number def linenumberarea_mousemove_event(self, event): """Handling line number area mouse move event""" line_number = self.__get_linenumber_from_mouse_event(event) block = self.document().findBlockByNumber(line_number - 1) data = block.userData() # this disables pyflakes messages if there is an active drag/selection # operation check = self.linenumberarea_released == -1 if data and data.code_analysis and check: self.__show_code_analysis_results(line_number, data.code_analysis) if event.buttons() == Qt.LeftButton: self.linenumberarea_released = line_number self.linenumberarea_select_lines( self.linenumberarea_pressed, self.linenumberarea_released ) def linenumberarea_mousedoubleclick_event(self, event): """Handling line number area mouse double-click event""" line_number = self.__get_linenumber_from_mouse_event(event) shift = event.modifiers() & Qt.ShiftModifier def linenumberarea_mousepress_event(self, event): """Handling line number area mouse double press event""" line_number = self.__get_linenumber_from_mouse_event(event) self.linenumberarea_pressed = line_number self.linenumberarea_released = line_number self.linenumberarea_select_lines( self.linenumberarea_pressed, self.linenumberarea_released ) def linenumberarea_mouserelease_event(self, event): """Handling line number area mouse release event""" self.linenumberarea_released = -1 self.linenumberarea_pressed = -1 def linenumberarea_select_lines(self, linenumber_pressed, linenumber_released): """Select line(s) after a mouse press/mouse press drag event""" find_block_by_line_number = self.document().findBlockByLineNumber move_n_blocks = linenumber_released - linenumber_pressed start_line = linenumber_pressed start_block = find_block_by_line_number(start_line - 1) cursor = self.textCursor() cursor.setPosition(start_block.position()) # Select/drag downwards if move_n_blocks > 0: for n in range(abs(move_n_blocks) + 1): cursor.movePosition(cursor.NextBlock, cursor.KeepAnchor) # Select/drag upwards or select single line else: cursor.movePosition(cursor.NextBlock) for n in range(abs(move_n_blocks) + 1): cursor.movePosition(cursor.PreviousBlock, cursor.KeepAnchor) # Account for last line case if linenumber_released == self.blockCount(): cursor.movePosition(cursor.EndOfBlock, cursor.KeepAnchor) else: cursor.movePosition(cursor.StartOfBlock, cursor.KeepAnchor) self.setTextCursor(cursor) def resizeEvent(self, event): """Reimplemented Qt method to handle line number area resizing""" QPlainTextEdit.resizeEvent(self, event) cr = self.contentsRect() self.linenumberarea.setGeometry( QRect(cr.left(), cr.top(), self.compute_linenumberarea_width(), cr.height()) ) def paintEvent(self, event): """Overrides paint event to update the list of visible blocks""" self.update_visible_blocks(event) QPlainTextEdit.paintEvent(self, event) def update_visible_blocks(self, event): """Update the list of visible blocks/lines position""" self.visible_blocks[:] = [] block = self.firstVisibleBlock() blockNumber = block.blockNumber() top = int( self.blockBoundingGeometry(block).translated(self.contentOffset()).top() ) bottom = top + int(self.blockBoundingRect(block).height()) ebottom_bottom = self.height() while block.isValid(): visible = bottom <= ebottom_bottom if not visible: break if block.isVisible(): self.visible_blocks.append((top, blockNumber + 1, block)) block = block.next() top = bottom bottom = top + int(self.blockBoundingRect(block).height()) blockNumber = block.blockNumber() if __name__ == "__main__": from guidata.qthelpers import qt_app_context with qt_app_context(exec_loop=True): widget = CodeEditor(columns=80, rows=40) widget.set_text_from_file(__file__) widget.show() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/widgets/collectionseditor.py0000644000175100001770000015076014654363416021755 0ustar00runnerdocker# -*- coding: utf-8 -*- # ----------------------------------------------------------------------------- # Copyright ┬й Spyder Project Contributors # # Licensed under the terms of the MIT License # (see spyder/__init__.py for details) # ---------------------------------------------------------------------------- """ guidata.widgets.collectionseditor ================================= This package provides a Collections (i.e. dictionary, list and tuple) editor widget and dialog. .. autoclass:: CollectionsEditor :show-inheritance: :members: """ # TODO: Multiple selection: open as many editors (array/dict/...) as necessary, # at the same time # pylint: disable=C0103 # pylint: disable=R0903 # pylint: disable=R0911 # pylint: disable=R0201 import datetime import io import re import sys import warnings try: from PIL import Image as PILImage except ImportError: PILImage = None from qtpy.compat import getsavefilename from qtpy.QtCore import QAbstractTableModel, QDateTime, QModelIndex, Qt, Signal, Slot from qtpy.QtGui import QColor, QKeySequence from qtpy.QtWidgets import ( QAbstractItemDelegate, QApplication, QDateEdit, QDateTimeEdit, QDialog, QHBoxLayout, QInputDialog, QItemDelegate, QLineEdit, QMenu, QMessageBox, QPushButton, QTableView, QVBoxLayout, QWidget, ) from guidata.config import CONF, _ from guidata.configtools import get_font, get_icon from guidata.qthelpers import ( add_actions, create_action, mimedata2url, win32_fix_title_bar_background, ) from guidata.utils.misc import getcwd_or_home from guidata.widgets.importwizard import ImportWizard from guidata.widgets.nsview import ( DataFrame, DatetimeIndex, FakeObject, Image, MaskedArray, Series, array, display_to_value, get_color_name, get_human_readable_type, get_object_attrs, get_size, get_type_string, is_editable_type, is_known_type, ndarray, np_savetxt, sort_against, try_to_eval, unsorted_unique, value_to_display, ) from guidata.widgets.texteditor import TextEditor if ndarray is not FakeObject: from guidata.widgets.arrayeditor import ArrayEditor if DataFrame is not FakeObject: from guidata.widgets.dataframeeditor import DataFrameEditor LARGE_NROWS = 100 ROWS_TO_LOAD = 50 def fix_reference_name(name, blacklist=None): """Return a syntax-valid Python reference name from an arbitrary name""" name = "".join(re.split(r"[^0-9a-zA-Z_]", name)) while name and not re.match(r"([a-zA-Z]+[0-9a-zA-Z_]*)$", name): if not re.match(r"[a-zA-Z]", name[0]): name = name[1:] continue name = str(name) if not name: name = "data" if blacklist is not None and name in blacklist: def get_new_name(index): """Generate new name""" return name + ("%03d" % index) index = 0 while get_new_name(index) in blacklist: index += 1 name = get_new_name(index) return name class ProxyObject: """Dictionary proxy to an unknown object.""" def __init__(self, obj): """Constructor.""" self.__obj__ = obj def __len__(self): """Get len according to detected attributes.""" return len(get_object_attrs(self.__obj__)) def __getitem__(self, key): """Get the attribute corresponding to the given key.""" # Catch NotImplementedError to fix #6284 in pandas MultiIndex # due to NA checking not being supported on a multiindex. # Catch AttributeError to fix #5642 in certain special classes like xml # when this method is called on certain attributes. # Catch TypeError to prevent fatal Python crash to desktop after # modifying certain pandas objects. Fix issue #6727 . # Catch ValueError to allow viewing and editing of pandas offsets. # Fix issue #6728 . try: attribute_toreturn = getattr(self.__obj__, key) except (NotImplementedError, AttributeError, TypeError, ValueError): attribute_toreturn = None return attribute_toreturn def __setitem__(self, key, value): """Set attribute corresponding to key with value.""" # Catch AttributeError to gracefully handle inability to set an # attribute due to it not being writeable or set-table. # Fix issue #6728 . Also, catch NotImplementedError for safety. try: setattr(self.__obj__, key, value) except (TypeError, AttributeError, NotImplementedError): pass except Exception as e: if "cannot set values for" not in str(e): raise class ReadOnlyCollectionsModel(QAbstractTableModel): """CollectionsEditor Read-Only Table Model""" sig_setting_data = Signal() def __init__( self, parent, data, title="", names=False, minmax=False, dataframe_format=None ): QAbstractTableModel.__init__(self, parent) if data is None: data = {} self.names = names self.minmax = minmax self.dataframe_format = dataframe_format self.header0 = None self._data = None self.total_rows = None self.showndata = None self.keys = None self.title = str(title) # in case title is not a string if self.title: self.title = self.title + " - " self.sizes = [] self.types = [] self.set_data(data) def get_data(self): """Return model data""" return self._data def set_data(self, data, coll_filter=None): """Set model data""" self._data = data data_type = get_type_string(data) if coll_filter is not None and isinstance(data, (tuple, list, dict)): data = coll_filter(data) self.showndata = data self.header0 = _("Index") if self.names: self.header0 = _("Name") if isinstance(data, tuple): self.keys = list(range(len(data))) self.title += _("Tuple") elif isinstance(data, list): self.keys = list(range(len(data))) self.title += _("List") elif isinstance(data, dict): self.keys = list(data.keys()) self.title += _("Dictionary") if not self.names: self.header0 = _("Key") else: self.keys = get_object_attrs(data) self._data = data = self.showndata = ProxyObject(data) if not self.names: self.header0 = _("Attribute") if not isinstance(self._data, ProxyObject): self.title += " (" + str(len(self.keys)) + " " + _("elements") + ")" else: self.title += data_type self.total_rows = len(self.keys) if self.total_rows > LARGE_NROWS: self.rows_loaded = ROWS_TO_LOAD else: self.rows_loaded = self.total_rows self.sig_setting_data.emit() self.set_size_and_type() self.reset() def set_size_and_type(self, start=None, stop=None): """ :param start: :param stop: """ data = self._data if start is None and stop is None: start = 0 stop = self.rows_loaded fetch_more = False else: fetch_more = True # Ignore pandas warnings that certain attributes are deprecated # and will be removed, since they will only be accessed if they exist. with warnings.catch_warnings(): warnings.filterwarnings( "ignore", message=( r"^\w+\.\w+ is deprecated and " "will be removed in a future version" ), ) sizes = [get_size(data[self.keys[index]]) for index in range(start, stop)] types = [ get_human_readable_type(data[self.keys[index]]) for index in range(start, stop) ] if fetch_more: self.sizes = self.sizes + sizes self.types = self.types + types else: self.sizes = sizes self.types = types def sort(self, column, order=Qt.AscendingOrder): """Overriding sort method""" reverse = order == Qt.DescendingOrder if column == 0: self.sizes = sort_against(self.sizes, self.keys, reverse) self.types = sort_against(self.types, self.keys, reverse) try: self.keys.sort(reverse=reverse) except Exception: # pylint: disable=broad-except pass elif column == 1: self.keys[: self.rows_loaded] = sort_against(self.keys, self.types, reverse) self.sizes = sort_against(self.sizes, self.types, reverse) try: self.types.sort(reverse=reverse) except Exception: # pylint: disable=broad-except pass elif column == 2: self.keys[: self.rows_loaded] = sort_against(self.keys, self.sizes, reverse) self.types = sort_against(self.types, self.sizes, reverse) try: self.sizes.sort(reverse=reverse) except Exception: # pylint: disable=broad-except pass elif column == 3: values = [self._data[key] for key in self.keys] self.keys = sort_against(self.keys, values, reverse) self.sizes = sort_against(self.sizes, values, reverse) self.types = sort_against(self.types, values, reverse) self.beginResetModel() self.endResetModel() def columnCount(self, qindex=QModelIndex()): """Array column number""" return 4 def rowCount(self, index=QModelIndex()): """Array row number""" if self.total_rows <= self.rows_loaded: return self.total_rows else: return self.rows_loaded def canFetchMore(self, index=QModelIndex()): """ :param index: :return: """ if self.total_rows > self.rows_loaded: return True else: return False def fetchMore(self, index=QModelIndex()): """ :param index: """ reminder = self.total_rows - self.rows_loaded items_to_fetch = min(reminder, ROWS_TO_LOAD) self.set_size_and_type(self.rows_loaded, self.rows_loaded + items_to_fetch) self.beginInsertRows( QModelIndex(), self.rows_loaded, self.rows_loaded + items_to_fetch - 1 ) self.rows_loaded += items_to_fetch self.endInsertRows() def get_index_from_key(self, key): """ :param key: :return: """ try: return self.createIndex(self.keys.index(key), 0) except (RuntimeError, ValueError): return QModelIndex() def get_key(self, index): """Return current key""" return self.keys[index.row()] def get_value(self, index): """Return current value""" if index.column() == 0: return self.keys[index.row()] elif index.column() == 1: return self.types[index.row()] elif index.column() == 2: return self.sizes[index.row()] else: return self._data[self.keys[index.row()]] def get_bgcolor(self, index): """Background color depending on value""" if index.column() == 0: color = QColor(Qt.lightGray) color.setAlphaF(0.05) elif index.column() < 3: color = QColor(Qt.lightGray) color.setAlphaF(0.2) else: color = QColor(Qt.lightGray) color.setAlphaF(0.3) return color def data(self, index, role=Qt.DisplayRole): """Cell content""" if not index.isValid(): return None value = self.get_value(index) if index.column() == 3: display = value_to_display(value, minmax=self.minmax) else: display = str(value) if role == Qt.DisplayRole: return display elif role == Qt.EditRole: return value_to_display(value) elif role == Qt.TextAlignmentRole: if index.column() == 3: if len(display.splitlines()) < 3: return int(Qt.AlignLeft | Qt.AlignVCenter) else: return int(Qt.AlignLeft | Qt.AlignTop) else: return int(Qt.AlignLeft | Qt.AlignVCenter) elif role == Qt.BackgroundColorRole: return self.get_bgcolor(index) elif role == Qt.FontRole: return get_font(CONF, "dicteditor", "font") return None def headerData(self, section, orientation, role=Qt.DisplayRole): """Overriding method headerData""" if role != Qt.DisplayRole: return None i_column = int(section) if orientation == Qt.Horizontal: headers = (self.header0, _("Type"), _("Size"), _("Value")) return headers[i_column] else: return None def flags(self, index): """Overriding method flags""" # This method was implemented in CollectionsModel only, but to enable # tuple exploration (even without editing), this method was moved here if not index.isValid(): return Qt.ItemIsEnabled return Qt.ItemFlags(QAbstractTableModel.flags(self, index) | Qt.ItemIsEditable) def reset(self): """ """ self.beginResetModel() self.endResetModel() class CollectionsModel(ReadOnlyCollectionsModel): """Collections Table Model""" def set_value(self, index, value): """Set value""" self._data[self.keys[index.row()]] = value self.showndata[self.keys[index.row()]] = value self.sizes[index.row()] = get_size(value) self.types[index.row()] = get_human_readable_type(value) self.sig_setting_data.emit() def get_bgcolor(self, index): """Background color depending on value""" value = self.get_value(index) if index.column() < 3: color = ReadOnlyCollectionsModel.get_bgcolor(self, index) else: color_name = get_color_name(value) color = QColor(color_name) color.setAlphaF(0.2) return color def setData(self, index, value, role=Qt.EditRole): """Cell content change""" if not index.isValid(): return False if index.column() < 3: return False value = display_to_value(value, self.get_value(index), ignore_errors=True) self.set_value(index, value) self.dataChanged.emit(index, index) return True class CollectionsDelegate(QItemDelegate): """CollectionsEditor Item Delegate""" def __init__(self, parent=None): QItemDelegate.__init__(self, parent) self._editors = {} # keep references on opened editors def get_value(self, index): """ :param index: :return: """ if index.isValid(): return index.model().get_value(index) def set_value(self, index, value): """ :param index: :param value: """ if index.isValid(): index.model().set_value(index, value) def show_warning(self, index): """ Decide if showing a warning when the user is trying to view a big variable associated to a Tablemodel index This avoids getting the variables' value to know its size and type, using instead those already computed by the TableModel. The problem is when a variable is too big, it can take a lot of time just to get its value """ try: val_size = index.model().sizes[index.row()] val_type = index.model().types[index.row()] except Exception: # pylint: disable=broad-except return False if val_type in ["list", "tuple", "dict"] and int(val_size) > 1e5: return True else: return False def createEditor(self, parent, option, index): """Overriding method createEditor""" if index.column() < 3: return None if self.show_warning(index): answer = QMessageBox.warning( self.parent(), _("Warning"), _( "Opening this variable can be slow\n\n" "Do you want to continue anyway?" ), QMessageBox.Yes | QMessageBox.No, ) if answer == QMessageBox.No: return None try: value = self.get_value(index) if value is None: return None except Exception as msg: QMessageBox.critical( self.parent(), _("Error"), _( "Unable to retrieve the value of " "this variable from the console.

" "The error mesage was:
" "%s" ) % str(msg), ) return key = index.model().get_key(index) readonly = ( isinstance(value, tuple) or self.parent().readonly or not is_known_type(value) ) # CollectionsEditor for a list, tuple, dict, etc. if isinstance(value, (list, tuple, dict)): editor = CollectionsEditor(parent=parent) editor.setup(value, key, icon=self.parent().windowIcon(), readonly=readonly) self.create_dialog( editor, dict(model=index.model(), editor=editor, key=key, readonly=readonly), ) return None # ArrayEditor for a Numpy array elif isinstance(value, (ndarray, MaskedArray)) and ndarray is not FakeObject: editor = ArrayEditor(parent=parent) if not editor.setup_and_check(value, title=key, readonly=readonly): return self.create_dialog( editor, dict(model=index.model(), editor=editor, key=key, readonly=readonly), ) return None # ArrayEditor for an images elif ( isinstance(value, Image) and ndarray is not FakeObject and Image is not FakeObject ): arr = array(value) editor = ArrayEditor(parent=parent) if not editor.setup_and_check(arr, title=key, readonly=readonly): return def conv_func(arr): """Conversion function""" return PILImage.fromarray(arr, mode=value.mode) self.create_dialog( editor, dict( model=index.model(), editor=editor, key=key, readonly=readonly, conv=conv_func, ), ) return None # DataFrameEditor for a pandas dataframe, series or index elif ( isinstance(value, (DataFrame, DatetimeIndex, Series)) and DataFrame is not FakeObject ): editor = DataFrameEditor(parent=parent) if not editor.setup_and_check(value, title=key): return editor.dataModel.set_format(index.model().dataframe_format) editor.sig_option_changed.connect(self.change_option) self.create_dialog( editor, dict(model=index.model(), editor=editor, key=key, readonly=readonly), ) return None # QDateEdit and QDateTimeEdit for a dates or datetime respectively elif isinstance(value, datetime.date): if readonly: return None else: if isinstance(value, datetime.datetime): editor = QDateTimeEdit(value, parent=parent) else: editor = QDateEdit(value, parent=parent) editor.setCalendarPopup(True) editor.setFont(get_font(CONF, "dicteditor", "font")) return editor # TextEditor for a long string elif isinstance(value, str) and len(value) > 40: te = TextEditor(None, parent=parent) if te.setup_and_check(value): editor = TextEditor(value, key, readonly=readonly, parent=parent) self.create_dialog( editor, dict( model=index.model(), editor=editor, key=key, readonly=readonly ), ) return None # QLineEdit for an individual value (int, float, short string, etc) elif is_editable_type(value): if readonly: return None else: editor = QLineEdit(parent=parent) editor.setFont(get_font(CONF, "dicteditor", "font")) editor.setAlignment(Qt.AlignLeft) # This is making Spyder crash because the QLineEdit that it's # been modified is removed and a new one is created after # evaluation. So the object on which this method is trying to # act doesn't exist anymore. # editor.returnPressed.connect(self.commitAndCloseEditor) return editor # CollectionsEditor for an arbitrary Python object else: editor = CollectionsEditor(parent=parent) editor.setup(value, key, icon=self.parent().windowIcon(), readonly=readonly) self.create_dialog( editor, dict(model=index.model(), editor=editor, key=key, readonly=readonly), ) return None def create_dialog(self, editor, data): """ :param editor: :param data: """ self._editors[id(editor)] = data editor.accepted.connect(lambda eid=id(editor): self.editor_accepted(eid)) editor.rejected.connect(lambda eid=id(editor): self.editor_rejected(eid)) editor.show() @Slot(str, object) def change_option(self, option_name, new_value): """ Change configuration option. This function is called when a `sig_option_changed` signal is received. At the moment, this signal can only come from a DataFrameEditor. """ if option_name == "dataframe_format": self.parent().set_dataframe_format(new_value) def editor_accepted(self, editor_id): """ :param editor_id: """ data = self._editors[editor_id] if not data["readonly"]: index = data["model"].get_index_from_key(data["key"]) value = data["editor"].get_value() conv_func = data.get("conv", lambda v: v) self.set_value(index, conv_func(value)) self._editors.pop(editor_id) editor = self.sender() editor.deleteLater() def editor_rejected(self, editor_id): """ :param editor_id: """ self._editors.pop(editor_id) editor = self.sender() editor.deleteLater() def commitAndCloseEditor(self): """Overriding method commitAndCloseEditor""" editor = self.sender() # Avoid a segfault with PyQt5. Variable value won't be changed # but at least Spyder won't crash. It seems generated by a bug in sip. try: self.commitData.emit(editor) except AttributeError: pass self.closeEditor.emit(editor, QAbstractItemDelegate.NoHint) def setEditorData(self, editor, index): """ Overriding method setEditorData Model --> Editor """ value = self.get_value(index) if isinstance(editor, QLineEdit): if isinstance(value, bytes): try: value = str(value, "utf8") except Exception: # pylint: disable=broad-except pass if not isinstance(value, str): value = repr(value) editor.setText(value) elif isinstance(editor, QDateEdit): editor.setDate(value) elif isinstance(editor, QDateTimeEdit): editor.setDateTime(QDateTime(value.date(), value.time())) def setModelData(self, editor, model, index): """ Overriding method setModelData Editor --> Model """ if not hasattr(model, "set_value"): # Read-only mode return if isinstance(editor, QLineEdit): value = editor.text() try: value = display_to_value( value, self.get_value(index), ignore_errors=False ) except Exception as msg: raise QMessageBox.critical( editor, _("Edit item"), _( "Unable to assign data to item." "

Error message:
%s" ) % str(msg), ) return elif isinstance(editor, QDateEdit): qdate = editor.date() value = datetime.date(qdate.year(), qdate.month(), qdate.day()) elif isinstance(editor, QDateTimeEdit): qdatetime = editor.dateTime() qdate = qdatetime.date() qtime = qdatetime.time() value = datetime.datetime( qdate.year(), qdate.month(), qdate.day(), qtime.hour(), qtime.minute(), qtime.second(), ) else: # Should not happen... raise RuntimeError("Unsupported editor widget") self.set_value(index, value) class BaseTableView(QTableView): """Base collection editor table view""" sig_option_changed = Signal(str, object) sig_files_dropped = Signal(list) redirect_stdio = Signal(bool) def __init__(self, parent): QTableView.__init__(self, parent) self.array_filename = None self.menu = None self.empty_ws_menu = None self.paste_action = None self.copy_action = None self.edit_action = None self.plot_action = None self.hist_action = None self.imshow_action = None self.save_array_action = None self.insert_action = None self.remove_action = None self.minmax_action = None self.rename_action = None self.duplicate_action = None self.delegate = None self.setAcceptDrops(True) def setup_table(self): """Setup table""" self.horizontalHeader().setStretchLastSection(True) self.adjust_columns() # Sorting columns self.setSortingEnabled(True) self.sortByColumn(0, Qt.AscendingOrder) def setup_menu(self, minmax): """Setup context menu""" if self.minmax_action is not None: self.minmax_action.setChecked(minmax) return resize_action = create_action( self, _("Resize rows to contents"), triggered=self.resizeRowsToContents ) self.paste_action = create_action( self, _("Paste"), icon=get_icon("editpaste.png"), triggered=self.paste ) self.copy_action = create_action( self, _("Copy"), icon=get_icon("editcopy.png"), triggered=self.copy ) self.edit_action = create_action( self, _("Edit"), icon=get_icon("edit.png"), triggered=self.edit_item ) self.plot_action = create_action( self, _("Plot"), icon=get_icon("plot.png"), triggered=lambda: self.plot_item("plot"), ) self.plot_action.setVisible(False) self.hist_action = create_action( self, _("Histogram"), icon=get_icon("hist.png"), triggered=lambda: self.plot_item("hist"), ) self.hist_action.setVisible(False) self.imshow_action = create_action( self, _("Show image"), icon=get_icon("imshow.png"), triggered=self.imshow_item, ) self.imshow_action.setVisible(False) self.save_array_action = create_action( self, _("Save array"), icon=get_icon("filesave.png"), triggered=self.save_array, ) self.save_array_action.setVisible(False) self.insert_action = create_action( self, _("Insert"), icon=get_icon("insert.png"), triggered=self.insert_item ) self.remove_action = create_action( self, _("Remove"), icon=get_icon("editdelete.png"), triggered=self.remove_item, ) self.minmax_action = create_action( self, _("Show arrays min/max"), toggled=self.toggle_minmax ) self.minmax_action.setChecked(minmax) self.toggle_minmax(minmax) self.rename_action = create_action( self, _("Rename"), icon=get_icon("rename.png"), triggered=self.rename_item ) self.duplicate_action = create_action( self, _("Duplicate"), icon=get_icon("edit_add.png"), triggered=self.duplicate_item, ) menu = QMenu(self) menu_actions = [ self.edit_action, self.plot_action, self.hist_action, self.imshow_action, self.save_array_action, self.insert_action, self.remove_action, self.copy_action, self.paste_action, None, self.rename_action, self.duplicate_action, None, resize_action, ] if ndarray is not FakeObject: menu_actions.append(self.minmax_action) add_actions(menu, menu_actions) self.empty_ws_menu = QMenu(self) add_actions( self.empty_ws_menu, [self.insert_action, self.paste_action, None, resize_action], ) return menu # ------ Remote/local API --------------------------------------------------- def remove_values(self, keys): """Remove values from data""" raise NotImplementedError def copy_value(self, orig_key, new_key): """Copy value""" raise NotImplementedError def new_value(self, key, value): """Create new value in data""" raise NotImplementedError def is_list(self, key): """Return True if variable is a list or a tuple""" raise NotImplementedError def get_len(self, key): """Return sequence length""" raise NotImplementedError def is_array(self, key): """Return True if variable is a numpy array""" raise NotImplementedError def is_image(self, key): """Return True if variable is a PIL.Image image""" raise NotImplementedError def is_dict(self, key): """Return True if variable is a dictionary""" raise NotImplementedError def get_array_shape(self, key): """Return array's shape""" raise NotImplementedError def get_array_ndim(self, key): """Return array's ndim""" raise NotImplementedError def plot(self, key, funcname): """Plot item""" raise NotImplementedError def imshow(self, key): """Show item's image""" raise NotImplementedError def show_image(self, key): """Show image (item is a PIL image)""" raise NotImplementedError # --------------------------------------------------------------------------- def refresh_menu(self): """Refresh context menu""" index = self.currentIndex() condition = index.isValid() self.edit_action.setEnabled(condition) self.remove_action.setEnabled(condition) self.refresh_plot_entries(index) def refresh_plot_entries(self, index): """ :param index: """ if index.isValid(): key = self.model.get_key(index) is_list = self.is_list(key) is_array = self.is_array(key) and self.get_len(key) != 0 condition_plot = is_array and len(self.get_array_shape(key)) <= 2 condition_hist = is_array and self.get_array_ndim(key) == 1 condition_imshow = condition_plot and self.get_array_ndim(key) == 2 condition_imshow = condition_imshow or self.is_image(key) else: is_array = ( condition_plot ) = condition_imshow = is_list = condition_hist = False self.plot_action.setVisible(condition_plot or is_list) self.hist_action.setVisible(condition_hist or is_list) self.imshow_action.setVisible(condition_imshow) self.save_array_action.setVisible(is_array) def adjust_columns(self): """Resize two first columns to contents""" for col in range(3): self.resizeColumnToContents(col) def set_data(self, data): """Set table data""" if data is not None: self.model.set_data(data, self.dictfilter) self.sortByColumn(0, Qt.AscendingOrder) def mousePressEvent(self, event): """Reimplement Qt method""" if event.button() != Qt.LeftButton: QTableView.mousePressEvent(self, event) return index_clicked = self.indexAt(event.pos()) if index_clicked.isValid(): if ( index_clicked == self.currentIndex() and index_clicked in self.selectedIndexes() ): self.clearSelection() else: QTableView.mousePressEvent(self, event) else: self.clearSelection() event.accept() def mouseDoubleClickEvent(self, event): """Reimplement Qt method""" index_clicked = self.indexAt(event.pos()) if index_clicked.isValid(): row = index_clicked.row() # TODO: Remove hard coded "Value" column number (3 here) index_clicked = index_clicked.child(row, 3) self.edit(index_clicked) else: event.accept() def keyPressEvent(self, event): """Reimplement Qt methods""" if event.key() == Qt.Key_Delete: self.remove_item() elif event.key() == Qt.Key_F2: self.rename_item() elif event == QKeySequence.Copy: self.copy() elif event == QKeySequence.Paste: self.paste() else: QTableView.keyPressEvent(self, event) def contextMenuEvent(self, event): """Reimplement Qt method""" if self.model.showndata: self.refresh_menu() self.menu.popup(event.globalPos()) event.accept() else: self.empty_ws_menu.popup(event.globalPos()) event.accept() def dragEnterEvent(self, event): """Allow user to drag files""" if mimedata2url(event.mimeData()): event.accept() else: event.ignore() def dragMoveEvent(self, event): """Allow user to move files""" if mimedata2url(event.mimeData()): event.setDropAction(Qt.CopyAction) event.accept() else: event.ignore() def dropEvent(self, event): """Allow user to drop supported files""" urls = mimedata2url(event.mimeData()) if urls: event.setDropAction(Qt.CopyAction) event.accept() self.sig_files_dropped.emit(urls) else: event.ignore() @Slot(bool) def toggle_minmax(self, state): """Toggle min/max display for numpy arrays""" self.sig_option_changed.emit("minmax", state) self.model.minmax = state @Slot(str) def set_dataframe_format(self, new_format): """ Set format to use in DataframeEditor. Args: new_format (string): e.g. "%.3f" """ self.sig_option_changed.emit("dataframe_format", new_format) self.model.dataframe_format = new_format @Slot() def edit_item(self): """Edit item""" index = self.currentIndex() if not index.isValid(): return # TODO: Remove hard coded "Value" column number (3 here) self.edit(index.child(index.row(), 3)) @Slot() def remove_item(self): """Remove item""" indexes = self.selectedIndexes() if not indexes: return for index in indexes: if not index.isValid(): return one = _("Do you want to remove the selected item?") more = _("Do you want to remove all selected items?") answer = QMessageBox.question( self, _("Remove"), one if len(indexes) == 1 else more, QMessageBox.Yes | QMessageBox.No, ) if answer == QMessageBox.Yes: idx_rows = unsorted_unique([idx.row() for idx in indexes]) keys = [self.model.keys[idx_row] for idx_row in idx_rows] self.remove_values(keys) def copy_item(self, erase_original=False): """Copy item""" indexes = self.selectedIndexes() if not indexes: return idx_rows = unsorted_unique([idx.row() for idx in indexes]) if len(idx_rows) > 1 or not indexes[0].isValid(): return orig_key = self.model.keys[idx_rows[0]] if erase_original: title = _("Rename") field_text = _("New variable name:") else: title = _("Duplicate") field_text = _("Variable name:") data = self.model.get_data() if isinstance(data, (list, set)): new_key, valid = len(data), True else: new_key, valid = QInputDialog.getText( self, title, field_text, QLineEdit.Normal, orig_key ) if valid and str(new_key): new_key = try_to_eval(str(new_key)) if new_key == orig_key: return self.copy_value(orig_key, new_key) if erase_original: self.remove_values([orig_key]) @Slot() def duplicate_item(self): """Duplicate item""" self.copy_item() @Slot() def rename_item(self): """Rename item""" self.copy_item(True) @Slot() def insert_item(self): """Insert item""" index = self.currentIndex() if not index.isValid(): row = self.model.rowCount() else: row = index.row() data = self.model.get_data() if isinstance(data, list): key = row data.insert(row, "") elif isinstance(data, dict): key, valid = QInputDialog.getText( self, _("Insert"), _("Key:"), QLineEdit.Normal ) if valid and str(key): key = try_to_eval(str(key)) else: return else: return value, valid = QInputDialog.getText( self, _("Insert"), _("Value:"), QLineEdit.Normal ) if valid and str(value): self.new_value(key, try_to_eval(str(value))) def __prepare_plot(self): try: import plotpy.pyplot # analysis:ignore return True except Exception: # pylint: disable=broad-except try: if "matplotlib" not in sys.modules: import matplotlib matplotlib.use("Qt5Agg") return True except Exception: # pylint: disable=broad-except QMessageBox.warning( self, _("Import error"), _("Please install PlotPy" " or matplotlib."), ) def plot_item(self, funcname): """Plot item""" index = self.currentIndex() if self.__prepare_plot(): key = self.model.get_key(index) try: self.plot(key, funcname) except (ValueError, TypeError) as error: QMessageBox.critical( self, _("Plot"), _("Unable to plot data." "

Error message:
%s") % str(error), ) @Slot() def imshow_item(self): """Imshow item""" index = self.currentIndex() if self.__prepare_plot(): key = self.model.get_key(index) try: if self.is_image(key): self.show_image(key) else: self.imshow(key) except (ValueError, TypeError) as error: QMessageBox.critical( self, _("Plot"), _("Unable to show image." "

Error message:
%s") % str(error), ) @Slot() def save_array(self): """Save array""" title = _("Save array") if self.array_filename is None: self.array_filename = getcwd_or_home() self.redirect_stdio.emit(False) filename, _selfilter = getsavefilename( self, title, self.array_filename, _("NumPy arrays") + " (*.npy)" ) self.redirect_stdio.emit(True) if filename: self.array_filename = filename data = self.delegate.get_value(self.currentIndex()) try: import numpy as np np.save(self.array_filename, data) except Exception as error: QMessageBox.critical( self, title, _("Unable to save array" "

Error message:
%s") % str(error), ) @Slot() def copy(self): """Copy text to clipboard""" clipboard = QApplication.clipboard() clipl = [] for idx in self.selectedIndexes(): if not idx.isValid(): continue obj = self.delegate.get_value(idx) # Check if we are trying to copy a numpy array, and if so make sure # to copy the whole thing in a tab separated format if isinstance(obj, (ndarray, MaskedArray)) and ndarray is not FakeObject: output = io.BytesIO() try: np_savetxt(output, obj, delimiter="\t") except Exception: # pylint: disable=broad-except QMessageBox.warning( self, _("Warning"), _("It was not possible to copy " "this array"), ) return obj = output.getvalue().decode("utf-8") output.close() elif isinstance(obj, (DataFrame, Series)) and DataFrame is not FakeObject: output = io.StringIO() try: obj.to_csv(output, sep="\t", index=True, header=True) except Exception: # pylint: disable=broad-except QMessageBox.warning( self, _("Warning"), _("It was not possible to copy " "this dataframe"), ) return obj = output.getvalue() output.close() elif isinstance(obj, bytes): obj = str(obj, "utf8") else: obj = str(obj) clipl.append(obj) clipboard.setText("\n".join(clipl)) def import_from_string(self, text, title=None): """Import data from string""" data = self.model.get_data() # Check if data is a dict if not hasattr(data, "keys"): return editor = ImportWizard( self, text, title=title, contents_title=_("Clipboard contents"), varname=fix_reference_name("data", blacklist=list(data.keys())), ) if editor.exec(): var_name, clip_data = editor.get_data() self.new_value(var_name, clip_data) @Slot() def paste(self): """Import text/data/code from clipboard""" clipboard = QApplication.clipboard() cliptext = "" if clipboard.mimeData().hasText(): cliptext = str(clipboard.text()) if cliptext.strip(): self.import_from_string(cliptext, title=_("Import from clipboard")) else: QMessageBox.warning( self, _("Empty clipboard"), _("Nothing to be imported from clipboard.") ) class CollectionsEditorTableView(BaseTableView): """CollectionsEditor table view""" def __init__( self, parent, data, readonly=False, title="", names=False, minmax=False ): BaseTableView.__init__(self, parent) self.dictfilter = None self.readonly = readonly or isinstance(data, tuple) CollectionsModelClass = ( ReadOnlyCollectionsModel if self.readonly else CollectionsModel ) self.model = CollectionsModelClass( self, data, title, names=names, minmax=minmax ) self.setModel(self.model) self.delegate = CollectionsDelegate(self) self.setItemDelegate(self.delegate) self.setup_table() self.menu = self.setup_menu(minmax) # ------ Remote/local API --------------------------------------------------- def remove_values(self, keys): """Remove values from data""" data = self.model.get_data() for key in sorted(keys, reverse=True): data.pop(key) self.set_data(data) def copy_value(self, orig_key, new_key): """Copy value""" data = self.model.get_data() if isinstance(data, list): data.append(data[orig_key]) if isinstance(data, set): data.add(data[orig_key]) else: data[new_key] = data[orig_key] self.set_data(data) def new_value(self, key, value): """Create new value in data""" data = self.model.get_data() data[key] = value self.set_data(data) def is_list(self, key): """Return True if variable is a list or a tuple""" data = self.model.get_data() return isinstance(data[key], (tuple, list)) def get_len(self, key): """Return sequence length""" data = self.model.get_data() return len(data[key]) def is_array(self, key): """Return True if variable is a numpy array""" data = self.model.get_data() return isinstance(data[key], (ndarray, MaskedArray)) def is_image(self, key): """Return True if variable is a PIL.Image image""" data = self.model.get_data() return isinstance(data[key], Image) def is_dict(self, key): """Return True if variable is a dictionary""" data = self.model.get_data() return isinstance(data[key], dict) def get_array_shape(self, key): """Return array's shape""" data = self.model.get_data() return data[key].shape def get_array_ndim(self, key): """Return array's ndim""" data = self.model.get_data() return data[key].ndim def plot(self, key, funcname): """Plot item""" data = self.model.get_data() import plotpy.pyplot as plt plt.figure() getattr(plt, funcname)(data[key]) plt.show() def imshow(self, key): """Show item's image""" data = self.model.get_data() import plotpy.pyplot as plt plt.figure() plt.imshow(data[key]) plt.show() def show_image(self, key): """Show image (item is a PIL image)""" data = self.model.get_data() data[key].show() # --------------------------------------------------------------------------- def refresh_menu(self): """Refresh context menu""" data = self.model.get_data() index = self.currentIndex() condition = ( (not isinstance(data, tuple)) and index.isValid() and not self.readonly ) self.edit_action.setEnabled(condition) self.remove_action.setEnabled(condition) self.insert_action.setEnabled(not self.readonly) self.duplicate_action.setEnabled(condition) condition_rename = not isinstance(data, (tuple, list, set)) self.rename_action.setEnabled(condition_rename) self.refresh_plot_entries(index) def set_filter(self, dictfilter=None): """Set table dict filter""" self.dictfilter = dictfilter class CollectionsEditorWidget(QWidget): """Dictionary Editor Widget""" def __init__(self, parent, data, readonly=False, title=""): QWidget.__init__(self, parent) self.editor = CollectionsEditorTableView(self, data, readonly, title) layout = QVBoxLayout() layout.addWidget(self.editor) self.setLayout(layout) def set_data(self, data): """Set DictEditor data""" self.editor.set_data(data) def get_title(self): """Get model title""" return self.editor.model.title class CollectionsEditor(QDialog): """Collections Editor Dialog""" def __init__(self, parent=None): QDialog.__init__(self, parent) win32_fix_title_bar_background(self) # Destroying the C++ object right after closing the dialog box, # otherwise it may be garbage-collected in another QThread # (e.g. the editor's analysis thread in Spyder), thus leading to # a segmentation fault on UNIX or an application crash on Windows self.setAttribute(Qt.WA_DeleteOnClose) self.data_copy = None self.widget = None self.btn_save_and_close = None self.btn_close = None def setup(self, data, title="", readonly=False, width=650, icon=None, parent=None): """Setup editor.""" if isinstance(data, dict): # dictionary self.data_copy = data.copy() datalen = len(data) elif isinstance(data, (tuple, list)): # list, tuple self.data_copy = data[:] datalen = len(data) else: # unknown object import copy try: self.data_copy = copy.deepcopy(data) except NotImplementedError: self.data_copy = copy.copy(data) except (TypeError, AttributeError): readonly = True self.data_copy = data datalen = len(get_object_attrs(data)) # If the copy has a different type, then do not allow editing, because # this would change the type after saving; cf. issue #6936 if type(self.data_copy) != type(data): readonly = True self.widget = CollectionsEditorWidget( self, self.data_copy, title=title, readonly=readonly ) self.widget.editor.model.sig_setting_data.connect(self.save_and_close_enable) layout = QVBoxLayout() layout.addWidget(self.widget) self.setLayout(layout) # Buttons configuration btn_layout = QHBoxLayout() btn_layout.addStretch() if not readonly: self.btn_save_and_close = QPushButton(_("Save and Close")) self.btn_save_and_close.setDisabled(True) self.btn_save_and_close.clicked.connect(self.accept) btn_layout.addWidget(self.btn_save_and_close) self.btn_close = QPushButton(_("Close")) self.btn_close.setAutoDefault(True) self.btn_close.setDefault(True) self.btn_close.clicked.connect(self.reject) btn_layout.addWidget(self.btn_close) layout.addLayout(btn_layout) constant = 121 row_height = 30 error_margin = 10 height = constant + row_height * min([10, datalen]) + error_margin self.resize(width, height) self.setWindowTitle(self.widget.get_title()) if icon is None: self.setWindowIcon(get_icon("dictedit.png")) # Make the dialog act as a window self.setWindowFlags(Qt.Window) @Slot() def save_and_close_enable(self): """Handle the data change event to enable the save and close button.""" if self.btn_save_and_close: self.btn_save_and_close.setEnabled(True) self.btn_save_and_close.setAutoDefault(True) self.btn_save_and_close.setDefault(True) def get_value(self): """Return modified copy of dictionary or list""" # It is import to avoid accessing Qt C++ object as it has probably # already been destroyed, due to the Qt.WA_DeleteOnClose attribute return self.data_copy ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1722935059.1807055 guidata-3.6.2/guidata/widgets/console/0000755000175100001770000000000014654363423017305 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/widgets/console/__init__.py0000644000175100001770000000622214654363416021422 0ustar00runnerdocker# -*- coding: utf-8 -*- """ guidata.widgets.console ======================= This package provides a Python console widget. .. autoclass:: Console :show-inheritance: :members: .. autoclass:: DockableConsole :show-inheritance: :members: """ from qtpy.QtCore import Qt from guidata.config import CONF from guidata.configtools import get_font from guidata.qthelpers import win32_fix_title_bar_background from guidata.widgets.console.internalshell import InternalShell from guidata.widgets.dockable import DockableWidgetMixin class Console(InternalShell): """ Python console that run an interactive shell linked to the running process. :param parent: parent Qt widget :param namespace: available python namespace when the console start :type namespace: dict :param message: banner displayed before the first prompt :param commands: commands run when the interpreter starts :param type commands: list of string :param multithreaded: multithreaded support """ def __init__( self, parent=None, namespace=None, message=None, commands=None, multithreaded=True, debug=False, ): InternalShell.__init__( self, parent=parent, namespace=namespace, message=message, commands=commands or [], multithreaded=multithreaded, debug=debug, ) win32_fix_title_bar_background(self) self.setup() def setup(self): """Setup the calltip widget and show the console once all internal handler are ready.""" font = get_font(CONF, "console") font.setPointSize(10) self.set_font(font) self.set_codecompletion_auto(True) self.set_calltips(True) self.setup_completion(size=(300, 180), font=font) try: self.exception_occurred.connect(self.show_console) except AttributeError: pass def closeEvent(self, event): """Reimplement Qt base method""" InternalShell.closeEvent(self, event) self.exit_interpreter() event.accept() class DockableConsole(Console, DockableWidgetMixin): """ Dockable Python console that run an interactive shell linked to the running process. :param parent: parent Qt widget :param namespace: available python namespace when the console start :type namespace: dict :param message: banner displayed before the first prompt :param commands: commands run when the interpreter starts :param type commands: list of string """ LOCATION = Qt.BottomDockWidgetArea def __init__( self, parent, namespace, message, commands=None, multithreaded=True, debug=False ): DockableWidgetMixin.__init__(self) Console.__init__( self, parent=parent, namespace=namespace, message=message, commands=commands or [], multithreaded=multithreaded, debug=debug, ) def show_console(self): """Show the console widget.""" self.dockwidget.raise_() self.dockwidget.show() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/widgets/console/base.py0000644000175100001770000016075114654363416020605 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Copyright ┬й Spyder Project Contributors # Licensed under the terms of the MIT License # (see spyder/__init__.py for details) """QPlainTextEdit base class""" # pylint: disable=C0103 # pylint: disable=R0903 # pylint: disable=R0911 # pylint: disable=R0201 import os import re import sys from collections import OrderedDict from qtpy.QtCore import QEvent, QEventLoop, QPoint, Qt, Signal, Slot from qtpy.QtGui import ( QClipboard, QColor, QMouseEvent, QPalette, QTextCharFormat, QTextCursor, QTextFormat, QTextOption, ) from qtpy.QtWidgets import ( QAbstractItemView, QApplication, QListWidget, QListWidgetItem, QMainWindow, QPlainTextEdit, QTextEdit, QToolTip, ) from guidata import qthelpers as qth from guidata.config import CONF from guidata.configtools import get_font, get_icon from guidata.widgets.console.calltip import CallTipWidget from guidata.widgets.console.mixins import BaseEditMixin from guidata.widgets.console.terminal import ANSIEscapeCodeHandler def insert_text_to(cursor, text, fmt): """Helper to print text, taking into account backspaces""" while True: index = text.find(chr(8)) # backspace if index == -1: break cursor.insertText(text[:index], fmt) if cursor.positionInBlock() > 0: cursor.deletePreviousChar() text = text[index + 1 :] cursor.insertText(text, fmt) class CompletionWidget(QListWidget): """Completion list widget""" sig_show_completions = Signal(object) def __init__(self, parent, ancestor): QListWidget.__init__(self, ancestor) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setWindowFlags(Qt.SubWindow | Qt.FramelessWindowHint) self.textedit = parent self.completion_list = None self.case_sensitive = False self.enter_select = None self.hide() self.itemActivated.connect(self.item_selected) def setup_appearance(self, size, font): """ :param size: :param font: """ self.resize(*size) self.setFont(font) def show_list(self, completion_list, automatic=True): """ :param completion_list: :param automatic: :return: """ types = [c[1] for c in completion_list] completion_list = [c[0] for c in completion_list] if len(completion_list) == 1 and not automatic: self.textedit.insert_completion(completion_list[0]) return self.completion_list = completion_list self.clear() icons_map = { "instance": "attribute", "statement": "attribute", "method": "method", "function": "function", "class": "class", "module": "module", } self.type_list = types if any(types): for c, t in zip(completion_list, types): icon = icons_map.get(t, "no_match") self.addItem(QListWidgetItem(get_icon(icon + ".png"), c)) else: self.addItems(completion_list) self.setCurrentRow(0) QApplication.processEvents(QEventLoop.ExcludeUserInputEvents) self.show() self.setFocus() self.raise_() # Retrieving current screen height srect = self.textedit.screen().availableGeometry() screen_right = srect.right() screen_bottom = srect.bottom() point = self.textedit.cursorRect().bottomRight() point.setX(point.x() + self.textedit.get_linenumberarea_width()) point = self.textedit.mapToGlobal(point) # Computing completion widget and its parent right positions comp_right = point.x() + self.width() ancestor = self.parent() if ancestor is None: anc_right = screen_right else: anc_right = min([ancestor.x() + ancestor.width(), screen_right]) # Moving completion widget to the left # if there is not enough space to the right if comp_right > anc_right: point.setX(point.x() - self.width()) # Computing completion widget and its parent bottom positions comp_bottom = point.y() + self.height() ancestor = self.parent() if ancestor is None: anc_bottom = screen_bottom else: anc_bottom = min([ancestor.y() + ancestor.height(), screen_bottom]) # Moving completion widget above if there is not enough space below x_position = point.x() if comp_bottom > anc_bottom: point = self.textedit.cursorRect().topRight() point = self.textedit.mapToGlobal(point) point.setX(x_position) point.setY(point.y() - self.height()) if ancestor is not None: # Useful only if we set parent to 'ancestor' in __init__ point = ancestor.mapFromGlobal(point) self.move(point) if str(self.textedit.completion_text): # When initialized, if completion text is not empty, we need # to update the displayed list: self.update_current() # signal used for testing self.sig_show_completions.emit(completion_list) def hide(self): """ """ QListWidget.hide(self) self.textedit.setFocus() def keyPressEvent(self, event): """ :param event: """ text, key = event.text(), event.key() alt = event.modifiers() & Qt.AltModifier shift = event.modifiers() & Qt.ShiftModifier ctrl = event.modifiers() & Qt.ControlModifier modifier = shift or ctrl or alt if ( key in (Qt.Key_Return, Qt.Key_Enter) and self.enter_select ) or key == Qt.Key_Tab: self.item_selected() elif key in ( Qt.Key_Return, Qt.Key_Enter, Qt.Key_Left, Qt.Key_Right, ) or text in (".", ":"): self.hide() self.textedit.keyPressEvent(event) elif ( key in ( Qt.Key_Up, Qt.Key_Down, Qt.Key_PageUp, Qt.Key_PageDown, Qt.Key_Home, Qt.Key_End, Qt.Key_CapsLock, ) and not modifier ): QListWidget.keyPressEvent(self, event) elif len(text) or key == Qt.Key_Backspace: self.textedit.keyPressEvent(event) self.update_current() elif modifier: self.textedit.keyPressEvent(event) else: self.hide() QListWidget.keyPressEvent(self, event) def update_current(self): """ """ completion_text = str(self.textedit.completion_text) if completion_text: for row, completion in enumerate(self.completion_list): if not self.case_sensitive: print(completion_text) # spyder: test-skip completion = completion.lower() completion_text = completion_text.lower() if completion.startswith(completion_text): self.setCurrentRow(row) self.scrollTo(self.currentIndex(), QAbstractItemView.PositionAtTop) break else: self.hide() else: self.hide() def focusOutEvent(self, event): """ :param event: """ event.ignore() # Don't hide it on Mac when main window loses focus because # keyboard input is lost # Fixes Issue 1318 if sys.platform == "darwin": if event.reason() != Qt.ActiveWindowFocusReason: self.hide() else: self.hide() def item_selected(self, item=None): """ :param item: """ if item is None: item = self.currentItem() self.textedit.insert_completion(str(item.text())) self.hide() class TextEditBaseWidget(QPlainTextEdit, BaseEditMixin): """Text edit base widget""" BRACE_MATCHING_SCOPE = ("sof", "eof") cell_separators = None focus_in = Signal() zoom_in = Signal() zoom_out = Signal() zoom_reset = Signal() focus_changed = Signal() sig_eol_chars_changed = Signal(str) def __init__(self, parent=None): QPlainTextEdit.__init__(self, parent) BaseEditMixin.__init__(self) self.setAttribute(Qt.WA_DeleteOnClose) self.extra_selections_dict = OrderedDict() self.textChanged.connect(self.changed) self.cursorPositionChanged.connect(self.cursor_position_changed) self.indent_chars = " " * 4 self.tab_stop_width_spaces = 4 # Code completion / calltips if parent is not None: mainwin = parent while not isinstance(mainwin, QMainWindow): mainwin = mainwin.parent() if mainwin is None: break if mainwin is not None: parent = mainwin self.completion_widget = CompletionWidget(self, parent) self.codecompletion_auto = False self.codecompletion_case = True self.codecompletion_enter = False self.completion_text = "" self.setup_completion() self.calltip_widget = CallTipWidget(self, hide_timer_on=False) self.calltips = True self.calltip_position = None self.has_cell_separators = False self.highlight_current_cell_enabled = False # The color values may be overridden by the syntax highlighter # Highlight current line color self.currentline_color = QColor(Qt.red).lighter(190) self.currentcell_color = QColor(Qt.red).lighter(194) # Brace matching self.bracepos = None self.matched_p_color = QColor(Qt.green) self.unmatched_p_color = QColor(Qt.red) self.last_cursor_cell = None def setup_completion(self, size=None, font=None): """ :param size: :param font: """ size = size or CONF.get("console", "codecompletion/size") font = font or get_font(CONF, "texteditor", "font") self.completion_widget.setup_appearance(size, font) def set_indent_chars(self, indent_chars): """ :param indent_chars: """ self.indent_chars = indent_chars def set_tab_stop_width_spaces(self, tab_stop_width_spaces): """ :param tab_stop_width_spaces: """ self.tab_stop_width_spaces = tab_stop_width_spaces self.update_tab_stop_width_spaces() def update_tab_stop_width_spaces(self): """ """ self.setTabStopWidth(self.fontMetrics().width(" " * self.tab_stop_width_spaces)) def set_palette(self, background, foreground): """ Set text editor palette colors: background color and caret (text cursor) color """ palette = QPalette() # palette.setColor(QPalette.Base, background) palette.setColor(QPalette.Text, foreground) palette.setColor(QPalette.Background, background) self.setPalette(palette) # Set the right background color when changing color schemes # or creating new Editor windows. This seems to be a Qt bug. # Fixes Issue 2028 if sys.platform == "darwin": if self.objectName(): style = "QPlainTextEdit#%s {background: %s; color: %s;}" % ( self.objectName(), background.name(), foreground.name(), ) self.setStyleSheet(style) # ------Extra selections def extra_selection_length(self, key): """ :param key: :return: """ selection = self.get_extra_selections(key) if selection: cursor = self.extra_selections_dict[key][0].cursor selection_length = cursor.selectionEnd() - cursor.selectionStart() return selection_length else: return 0 def get_extra_selections(self, key): """ :param key: :return: """ return self.extra_selections_dict.get(key, []) def set_extra_selections(self, key, extra_selections): """ :param key: :param extra_selections: """ self.extra_selections_dict[key] = extra_selections self.extra_selections_dict = OrderedDict( sorted( self.extra_selections_dict.items(), key=lambda s: self.extra_selection_length(s[0]), reverse=True, ) ) def update_extra_selections(self): """ """ extra_selections = [] # Python 3 compatibility (weird): current line has to be # highlighted first if "current_cell" in self.extra_selections_dict: extra_selections.extend(self.extra_selections_dict["current_cell"]) if "current_line" in self.extra_selections_dict: extra_selections.extend(self.extra_selections_dict["current_line"]) for key, extra in list(self.extra_selections_dict.items()): if not (key == "current_line" or key == "current_cell"): extra_selections.extend(extra) self.setExtraSelections(extra_selections) def clear_extra_selections(self, key): """ :param key: """ self.extra_selections_dict[key] = [] self.update_extra_selections() def changed(self): """Emit changed signal""" self.modificationChanged.emit(self.document().isModified()) # ------Highlight current line def highlight_current_line(self): """Highlight current line""" selection = QTextEdit.ExtraSelection() selection.format.setProperty(QTextFormat.FullWidthSelection, True) selection.format.setBackground(self.currentline_color) selection.cursor = self.textCursor() selection.cursor.clearSelection() self.set_extra_selections("current_line", [selection]) self.update_extra_selections() def unhighlight_current_line(self): """Unhighlight current line""" self.clear_extra_selections("current_line") # ------Highlight current cell def highlight_current_cell(self): """Highlight current cell""" if self.cell_separators is None or not self.highlight_current_cell_enabled: return selection = QTextEdit.ExtraSelection() selection.format.setProperty(QTextFormat.FullWidthSelection, True) selection.format.setBackground(self.currentcell_color) ( selection.cursor, whole_file_selected, whole_screen_selected, ) = self.select_current_cell_in_visible_portion() if whole_file_selected: self.clear_extra_selections("current_cell") elif whole_screen_selected: if self.has_cell_separators: self.set_extra_selections("current_cell", [selection]) self.update_extra_selections() else: self.clear_extra_selections("current_cell") else: self.set_extra_selections("current_cell", [selection]) self.update_extra_selections() def unhighlight_current_cell(self): """Unhighlight current cell""" self.clear_extra_selections("current_cell") # ------Brace matching def find_brace_match(self, position, brace, forward): """ :param position: :param brace: :param forward: :return: """ start_pos, end_pos = self.BRACE_MATCHING_SCOPE if forward: bracemap = {"(": ")", "[": "]", "{": "}"} text = self.get_text(position, end_pos) i_start_open = 1 i_start_close = 1 else: bracemap = {")": "(", "]": "[", "}": "{"} text = self.get_text(start_pos, position) i_start_open = len(text) - 1 i_start_close = len(text) - 1 while True: if forward: i_close = text.find(bracemap[brace], i_start_close) else: i_close = text.rfind(bracemap[brace], 0, i_start_close + 1) if i_close > -1: if forward: i_start_close = i_close + 1 i_open = text.find(brace, i_start_open, i_close) else: i_start_close = i_close - 1 i_open = text.rfind(brace, i_close, i_start_open + 1) if i_open > -1: if forward: i_start_open = i_open + 1 else: i_start_open = i_open - 1 else: # found matching brace if forward: return position + i_close else: return position - (len(text) - i_close) else: # no matching brace return def __highlight(self, positions, color=None, cancel=False): if cancel: self.clear_extra_selections("brace_matching") return extra_selections = [] for position in positions: if position > self.get_position("eof"): return selection = QTextEdit.ExtraSelection() selection.format.setBackground(color) selection.cursor = self.textCursor() selection.cursor.clearSelection() selection.cursor.setPosition(position) selection.cursor.movePosition( QTextCursor.NextCharacter, QTextCursor.KeepAnchor ) extra_selections.append(selection) self.set_extra_selections("brace_matching", extra_selections) self.update_extra_selections() def cursor_position_changed(self): """Brace matching""" if self.bracepos is not None: self.__highlight(self.bracepos, cancel=True) self.bracepos = None cursor = self.textCursor() if cursor.position() == 0: return cursor.movePosition(QTextCursor.PreviousCharacter, QTextCursor.KeepAnchor) text = str(cursor.selectedText()) pos1 = cursor.position() if text in (")", "]", "}"): pos2 = self.find_brace_match(pos1, text, forward=False) elif text in ("(", "[", "{"): pos2 = self.find_brace_match(pos1, text, forward=True) else: return if pos2 is not None: self.bracepos = (pos1, pos2) self.__highlight(self.bracepos, color=self.matched_p_color) else: self.bracepos = (pos1,) self.__highlight(self.bracepos, color=self.unmatched_p_color) # -----Widget setup and options def set_codecompletion_auto(self, state): """Set code completion state""" self.codecompletion_auto = state def set_codecompletion_case(self, state): """Case sensitive completion""" self.codecompletion_case = state self.completion_widget.case_sensitive = state def set_codecompletion_enter(self, state): """Enable Enter key to select completion""" self.codecompletion_enter = state self.completion_widget.enter_select = state def set_calltips(self, state): """Set calltips state""" self.calltips = state def set_wrap_mode(self, mode=None): """ Set wrap mode Valid *mode* values: None, 'word', 'character' """ if mode == "word": wrap_mode = QTextOption.WrapAtWordBoundaryOrAnywhere elif mode == "character": wrap_mode = QTextOption.WrapAnywhere else: wrap_mode = QTextOption.NoWrap self.setWordWrapMode(wrap_mode) # ------Reimplementing Qt methods @Slot() def copy(self): """ Reimplement Qt method Copy text to clipboard with correct EOL chars """ if self.get_selected_text(): QApplication.clipboard().setText(self.get_selected_text()) def toPlainText(self): """ Reimplement Qt method Fix PyQt4 bug on Windows and Python 3 """ # Fix what appears to be a PyQt4 bug when getting file # contents under Windows and PY3. This bug leads to # corruptions when saving files with certain combinations # of unicode chars on them (like the one attached on # Issue 1546) if os.name == "nt": text = self.get_text("sof", "eof") return ( text.replace("\u2028", "\n") .replace("\u2029", "\n") .replace("\u0085", "\n") ) else: return super().toPlainText() def keyPressEvent(self, event): """ :param event: """ _text, key = event.text(), event.key() ctrl = event.modifiers() & Qt.ControlModifier meta = event.modifiers() & Qt.MetaModifier # Use our own copy method for {Ctrl,Cmd}+C to avoid Qt # copying text in HTML (See Issue 2285) if (ctrl or meta) and key == Qt.Key_C: self.copy() else: super().keyPressEvent(event) # ------Text: get, set, ... def get_selection_as_executable_code(self): """Return selected text as a processed text, to be executable in a Python/IPython interpreter""" ls = self.get_line_separator() _indent = lambda line: len(line) - len(line.lstrip()) line_from, line_to = self.get_selection_bounds() text = self.get_selected_text() if not text: return lines = text.split(ls) if len(lines) > 1: # Multiline selection -> eventually fixing indentation original_indent = _indent(self.get_text_line(line_from)) text = (" " * (original_indent - _indent(lines[0]))) + text # If there is a common indent to all lines, find it. # Moving from bottom line to top line ensures that blank # lines inherit the indent of the line *below* it, # which is the desired behavior. min_indent = 999 current_indent = 0 lines = text.split(ls) for i in range(len(lines) - 1, -1, -1): line = lines[i] if line.strip(): current_indent = _indent(line) min_indent = min(current_indent, min_indent) else: lines[i] = " " * current_indent if min_indent: lines = [line[min_indent:] for line in lines] # Remove any leading whitespace or comment lines # since they confuse the reserved word detector that follows below while lines: first_line = lines[0].lstrip() if first_line == "" or first_line[0] == "#": lines.pop(0) else: break # Add an EOL character after indentation blocks that start with some # Python reserved words, so that it gets evaluated automatically # by the console varname = re.compile(r"[a-zA-Z0-9_]*") # Matches valid variable names. maybe = False nextexcept = () for n, line in enumerate(lines): if not _indent(line): word = varname.match(line).group() if maybe and word not in nextexcept: lines[n - 1] += ls maybe = False if word: if word in ("def", "for", "while", "with", "class"): maybe = True nextexcept = () elif word == "if": maybe = True nextexcept = ("elif", "else") elif word == "try": maybe = True nextexcept = ("except", "finally") if maybe: if lines[-1].strip() == "": lines[-1] += ls else: lines.append(ls) return ls.join(lines) def __exec_cell(self): init_cursor = QTextCursor(self.textCursor()) start_pos, end_pos = self.__save_selection() cursor, whole_file_selected = self.select_current_cell() if not whole_file_selected: self.setTextCursor(cursor) text = self.get_selection_as_executable_code() self.last_cursor_cell = init_cursor self.__restore_selection(start_pos, end_pos) if text is not None: text = text.rstrip() return text def get_cell_as_executable_code(self): """Return cell contents as executable code""" return self.__exec_cell() def get_last_cell_as_executable_code(self): """ :return: """ text = None if self.last_cursor_cell: self.setTextCursor(self.last_cursor_cell) self.highlight_current_cell() text = self.__exec_cell() return text def is_cell_separator(self, cursor=None, block=None): """Return True if cursor (or text block) is on a block separator""" assert cursor is not None or block is not None if cursor is not None: cursor0 = QTextCursor(cursor) cursor0.select(QTextCursor.BlockUnderCursor) text = str(cursor0.selectedText()) else: text = str(block.text()) if self.cell_separators is None: return False else: return text.lstrip().startswith(self.cell_separators) def select_current_cell(self): """Select cell under cursor cell = group of lines separated by CELL_SEPARATORS returns the textCursor and a boolean indicating if the entire file is selected""" cursor = self.textCursor() cursor.movePosition(QTextCursor.StartOfBlock) cur_pos = prev_pos = cursor.position() # Moving to the next line that is not a separator, if we are # exactly at one of them while self.is_cell_separator(cursor): cursor.movePosition(QTextCursor.NextBlock) prev_pos = cur_pos cur_pos = cursor.position() if cur_pos == prev_pos: return cursor, False prev_pos = cur_pos # If not, move backwards to find the previous separator while not self.is_cell_separator(cursor): cursor.movePosition(QTextCursor.PreviousBlock) prev_pos = cur_pos cur_pos = cursor.position() if cur_pos == prev_pos: if self.is_cell_separator(cursor): return cursor, False else: break cursor.setPosition(prev_pos) cell_at_file_start = cursor.atStart() # Once we find it (or reach the beginning of the file) # move to the next separator (or the end of the file) # so we can grab the cell contents while not self.is_cell_separator(cursor): cursor.movePosition(QTextCursor.NextBlock, QTextCursor.KeepAnchor) cur_pos = cursor.position() if cur_pos == prev_pos: cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor) break prev_pos = cur_pos cell_at_file_end = cursor.atEnd() return cursor, cell_at_file_start and cell_at_file_end def select_current_cell_in_visible_portion(self): """Select cell under cursor in the visible portion of the file cell = group of lines separated by CELL_SEPARATORS Returns: - the textCursor - a boolean indicating if the entire file is selected - a boolean indicating if the entire visible portion of the file is selected""" cursor = self.textCursor() cursor.movePosition(QTextCursor.StartOfBlock) cur_pos = prev_pos = cursor.position() beg_pos = self.cursorForPosition(QPoint(0, 0)).position() bottom_right = QPoint(self.viewport().width() - 1, self.viewport().height() - 1) end_pos = self.cursorForPosition(bottom_right).position() # Moving to the next line that is not a separator, if we are # exactly at one of them while self.is_cell_separator(cursor): cursor.movePosition(QTextCursor.NextBlock) prev_pos = cur_pos cur_pos = cursor.position() if cur_pos == prev_pos: return cursor, False, False prev_pos = cur_pos # If not, move backwards to find the previous separator while not self.is_cell_separator(cursor) and cursor.position() >= beg_pos: cursor.movePosition(QTextCursor.PreviousBlock) prev_pos = cur_pos cur_pos = cursor.position() if cur_pos == prev_pos: if self.is_cell_separator(cursor): return cursor, False, False else: break cell_at_screen_start = cursor.position() <= beg_pos cursor.setPosition(prev_pos) cell_at_file_start = cursor.atStart() # Selecting cell header if not cell_at_file_start: cursor.movePosition(QTextCursor.PreviousBlock) cursor.movePosition(QTextCursor.NextBlock, QTextCursor.KeepAnchor) # Once we find it (or reach the beginning of the file) # move to the next separator (or the end of the file) # so we can grab the cell contents while not self.is_cell_separator(cursor) and cursor.position() <= end_pos: cursor.movePosition(QTextCursor.NextBlock, QTextCursor.KeepAnchor) cur_pos = cursor.position() if cur_pos == prev_pos: cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor) break prev_pos = cur_pos cell_at_file_end = cursor.atEnd() cell_at_screen_end = cursor.position() >= end_pos return ( cursor, cell_at_file_start and cell_at_file_end, cell_at_screen_start and cell_at_screen_end, ) def go_to_next_cell(self): """Go to the next cell of lines""" cursor = self.textCursor() cursor.movePosition(QTextCursor.NextBlock) cur_pos = prev_pos = cursor.position() while not self.is_cell_separator(cursor): # Moving to the next code cell cursor.movePosition(QTextCursor.NextBlock) prev_pos = cur_pos cur_pos = cursor.position() if cur_pos == prev_pos: return self.setTextCursor(cursor) def go_to_previous_cell(self): """Go to the previous cell of lines""" cursor = self.textCursor() cur_pos = prev_pos = cursor.position() if self.is_cell_separator(cursor): # Move to the previous cell cursor.movePosition(QTextCursor.PreviousBlock) cur_pos = prev_pos = cursor.position() while not self.is_cell_separator(cursor): # Move to the previous cell or the beginning of the current cell cursor.movePosition(QTextCursor.PreviousBlock) prev_pos = cur_pos cur_pos = cursor.position() if cur_pos == prev_pos: return self.setTextCursor(cursor) def get_line_count(self): """Return document total line number""" return self.blockCount() def __save_selection(self): """Save current cursor selection and return position bounds""" cursor = self.textCursor() return cursor.selectionStart(), cursor.selectionEnd() def __restore_selection(self, start_pos, end_pos): """Restore cursor selection from position bounds""" cursor = self.textCursor() cursor.setPosition(start_pos) cursor.setPosition(end_pos, QTextCursor.KeepAnchor) self.setTextCursor(cursor) def __duplicate_line_or_selection(self, after_current_line=True): """Duplicate current line or selected text""" cursor = self.textCursor() cursor.beginEditBlock() start_pos, end_pos = self.__save_selection() if str(cursor.selectedText()): cursor.setPosition(end_pos) # Check if end_pos is at the start of a block: if so, starting # changes from the previous block cursor.movePosition(QTextCursor.StartOfBlock, QTextCursor.KeepAnchor) if not str(cursor.selectedText()): cursor.movePosition(QTextCursor.PreviousBlock) end_pos = cursor.position() cursor.setPosition(start_pos) cursor.movePosition(QTextCursor.StartOfBlock) while cursor.position() <= end_pos: cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor) if cursor.atEnd(): cursor_temp = QTextCursor(cursor) cursor_temp.clearSelection() cursor_temp.insertText(self.get_line_separator()) break cursor.movePosition(QTextCursor.NextBlock, QTextCursor.KeepAnchor) text = cursor.selectedText() cursor.clearSelection() if not after_current_line: # Moving cursor before current line/selected text cursor.setPosition(start_pos) cursor.movePosition(QTextCursor.StartOfBlock) start_pos += len(text) end_pos += len(text) cursor.insertText(text) cursor.endEditBlock() self.setTextCursor(cursor) self.__restore_selection(start_pos, end_pos) def duplicate_line(self): """ Duplicate current line or selected text Paste the duplicated text *after* the current line/selected text """ self.__duplicate_line_or_selection(after_current_line=True) def copy_line(self): """ Copy current line or selected text Paste the duplicated text *before* the current line/selected text """ self.__duplicate_line_or_selection(after_current_line=False) def __move_line_or_selection(self, after_current_line=True): """Move current line or selected text""" cursor = self.textCursor() cursor.beginEditBlock() start_pos, end_pos = self.__save_selection() last_line = False # ------ Select text # Get selection start location cursor.setPosition(start_pos) cursor.movePosition(QTextCursor.StartOfBlock) start_pos = cursor.position() # Get selection end location cursor.setPosition(end_pos) if not cursor.atBlockStart() or end_pos == start_pos: cursor.movePosition(QTextCursor.EndOfBlock) cursor.movePosition(QTextCursor.NextBlock) end_pos = cursor.position() # Check if selection ends on the last line of the document if cursor.atEnd(): if not cursor.atBlockStart() or end_pos == start_pos: last_line = True # ------ Stop if at document boundary cursor.setPosition(start_pos) if cursor.atStart() and not after_current_line: # Stop if selection is already at top of the file while moving up cursor.endEditBlock() self.setTextCursor(cursor) self.__restore_selection(start_pos, end_pos) return cursor.setPosition(end_pos, QTextCursor.KeepAnchor) if last_line and after_current_line: # Stop if selection is already at end of the file while moving down cursor.endEditBlock() self.setTextCursor(cursor) self.__restore_selection(start_pos, end_pos) return # ------ Move text sel_text = str(cursor.selectedText()) cursor.removeSelectedText() if after_current_line: # Shift selection down text = str(cursor.block().text()) sel_text = os.linesep + sel_text[0:-1] # Move linesep at the start cursor.movePosition(QTextCursor.EndOfBlock) start_pos += len(text) + 1 end_pos += len(text) if not cursor.atEnd(): end_pos += 1 else: # Shift selection up if last_line: # Remove the last linesep and add it to the selected text cursor.deletePreviousChar() sel_text = sel_text + os.linesep cursor.movePosition(QTextCursor.StartOfBlock) end_pos += 1 else: cursor.movePosition(QTextCursor.PreviousBlock) text = str(cursor.block().text()) start_pos -= len(text) + 1 end_pos -= len(text) + 1 cursor.insertText(sel_text) cursor.endEditBlock() self.setTextCursor(cursor) self.__restore_selection(start_pos, end_pos) def move_line_up(self): """Move up current line or selected text""" self.__move_line_or_selection(after_current_line=False) def move_line_down(self): """Move down current line or selected text""" self.__move_line_or_selection(after_current_line=True) def go_to_new_line(self): """Go to the end of the current line and create a new line""" self.stdkey_end(False, False) self.insert_text(self.get_line_separator()) def extend_selection_to_complete_lines(self): """Extend current selection to complete lines""" cursor = self.textCursor() start_pos, end_pos = cursor.selectionStart(), cursor.selectionEnd() cursor.setPosition(start_pos) cursor.setPosition(end_pos, QTextCursor.KeepAnchor) if cursor.atBlockStart(): cursor.movePosition(QTextCursor.PreviousBlock, QTextCursor.KeepAnchor) cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor) self.setTextCursor(cursor) def delete_line(self): """Delete current line""" cursor = self.textCursor() if self.has_selected_text(): self.extend_selection_to_complete_lines() start_pos, end_pos = cursor.selectionStart(), cursor.selectionEnd() cursor.setPosition(start_pos) else: start_pos = end_pos = cursor.position() cursor.beginEditBlock() cursor.setPosition(start_pos) cursor.movePosition(QTextCursor.StartOfBlock) while cursor.position() <= end_pos: cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor) if cursor.atEnd(): break cursor.movePosition(QTextCursor.NextBlock, QTextCursor.KeepAnchor) cursor.removeSelectedText() cursor.endEditBlock() self.ensureCursorVisible() def set_selection(self, start, end): """ :param start: :param end: """ cursor = self.textCursor() cursor.setPosition(start) cursor.setPosition(end, QTextCursor.KeepAnchor) self.setTextCursor(cursor) def truncate_selection(self, position_from): """Unselect read-only parts in shell, like prompt""" position_from = self.get_position(position_from) cursor = self.textCursor() start, end = cursor.selectionStart(), cursor.selectionEnd() if start < end: start = max([position_from, start]) else: end = max([position_from, end]) self.set_selection(start, end) def restrict_cursor_position(self, position_from, position_to): """In shell, avoid editing text except between prompt and EOF""" position_from = self.get_position(position_from) position_to = self.get_position(position_to) cursor = self.textCursor() cursor_position = cursor.position() if cursor_position < position_from or cursor_position > position_to: self.set_cursor_position(position_to) # ------Code completion / Calltips def hide_tooltip_if_necessary(self, key): """Hide calltip when necessary""" try: calltip_char = self.get_character(self.calltip_position) before = self.is_cursor_before(self.calltip_position, char_offset=1) other = key in (Qt.Key_ParenRight, Qt.Key_Period, Qt.Key_Tab) if calltip_char not in ("?", "(") or before or other: QToolTip.hideText() except (IndexError, TypeError): QToolTip.hideText() def show_completion_widget(self, textlist, automatic=True): """Show completion widget""" self.completion_widget.show_list(textlist, automatic=automatic) def hide_completion_widget(self): """Hide completion widget""" self.completion_widget.hide() def show_completion_list(self, completions, completion_text="", automatic=True): """Display the possible completions""" if not completions: return if not isinstance(completions[0], tuple): completions = [(c, "") for c in completions] if len(completions) == 1 and completions[0][0] == completion_text: return self.completion_text = completion_text # Sorting completion list (entries starting with underscore are # put at the end of the list): underscore = set( [(comp, t) for (comp, t) in completions if comp.startswith("_")] ) completions = sorted(set(completions) - underscore, key=lambda x: x[0].lower()) completions += sorted(underscore, key=lambda x: x[0].lower()) self.show_completion_widget(completions, automatic=automatic) def select_completion_list(self): """Completion list is active, Enter was just pressed""" self.completion_widget.item_selected() def insert_completion(self, text): """ :param text: """ if text: cursor = self.textCursor() cursor.movePosition( QTextCursor.PreviousCharacter, QTextCursor.KeepAnchor, len(self.completion_text), ) cursor.removeSelectedText() self.insert_text(text) def is_completion_widget_visible(self): """Return True is completion list widget is visible""" return self.completion_widget.isVisible() # ------Standard keys def stdkey_clear(self): """ """ if not self.has_selected_text(): self.moveCursor(QTextCursor.NextCharacter, QTextCursor.KeepAnchor) self.remove_selected_text() def stdkey_backspace(self): """ """ if not self.has_selected_text(): self.moveCursor(QTextCursor.PreviousCharacter, QTextCursor.KeepAnchor) self.remove_selected_text() def __get_move_mode(self, shift): return QTextCursor.KeepAnchor if shift else QTextCursor.MoveAnchor def stdkey_up(self, shift): """ :param shift: """ self.moveCursor(QTextCursor.Up, self.__get_move_mode(shift)) def stdkey_down(self, shift): """ :param shift: """ self.moveCursor(QTextCursor.Down, self.__get_move_mode(shift)) def stdkey_tab(self): """ """ self.insert_text(self.indent_chars) def stdkey_home(self, shift, ctrl, prompt_pos=None): """Smart HOME feature: cursor is first moved at indentation position, then at the start of the line""" move_mode = self.__get_move_mode(shift) if ctrl: self.moveCursor(QTextCursor.Start, move_mode) else: cursor = self.textCursor() if prompt_pos is None: start_position = self.get_position("sol") else: start_position = self.get_position(prompt_pos) text = self.get_text(start_position, "eol") indent_pos = start_position + len(text) - len(text.lstrip()) if cursor.position() != indent_pos: cursor.setPosition(indent_pos, move_mode) else: cursor.setPosition(start_position, move_mode) self.setTextCursor(cursor) def stdkey_end(self, shift, ctrl): """ :param shift: :param ctrl: """ move_mode = self.__get_move_mode(shift) if ctrl: self.moveCursor(QTextCursor.End, move_mode) else: self.moveCursor(QTextCursor.EndOfBlock, move_mode) def stdkey_pageup(self): """ """ pass def stdkey_pagedown(self): """ """ pass def stdkey_escape(self): """ """ pass # ----Qt Events def mousePressEvent(self, event): """Reimplement Qt method""" if sys.platform.startswith("linux") and event.button() == Qt.MidButton: self.calltip_widget.hide() self.setFocus() event = QMouseEvent( QEvent.MouseButtonPress, event.pos(), Qt.LeftButton, Qt.LeftButton, Qt.NoModifier, ) QPlainTextEdit.mousePressEvent(self, event) QPlainTextEdit.mouseReleaseEvent(self, event) # Send selection text to clipboard to be able to use # the paste method and avoid the strange Issue 1445 # NOTE: This issue seems a focusing problem but it # seems really hard to track mode_clip = QClipboard.Clipboard mode_sel = QClipboard.Selection text_clip = QApplication.clipboard().text(mode=mode_clip) text_sel = QApplication.clipboard().text(mode=mode_sel) QApplication.clipboard().setText(text_sel, mode=mode_clip) self.paste() QApplication.clipboard().setText(text_clip, mode=mode_clip) else: self.calltip_widget.hide() QPlainTextEdit.mousePressEvent(self, event) def focusInEvent(self, event): """Reimplemented to handle focus""" self.focus_changed.emit() self.focus_in.emit() self.highlight_current_cell() QPlainTextEdit.focusInEvent(self, event) def focusOutEvent(self, event): """Reimplemented to handle focus""" self.focus_changed.emit() QPlainTextEdit.focusOutEvent(self, event) def wheelEvent(self, event): """Reimplemented to emit zoom in/out signals when Ctrl is pressed""" # This feature is disabled on MacOS, see Issue 1510 if sys.platform != "darwin": if event.modifiers() & Qt.ControlModifier: if hasattr(event, "angleDelta"): if event.angleDelta().y() < 0: self.zoom_out.emit() elif event.angleDelta().y() > 0: self.zoom_in.emit() elif hasattr(event, "delta"): if event.delta() < 0: self.zoom_out.emit() elif event.delta() > 0: self.zoom_in.emit() return QPlainTextEdit.wheelEvent(self, event) self.highlight_current_cell() class QtANSIEscapeCodeHandler(ANSIEscapeCodeHandler): """ """ def __init__(self): ANSIEscapeCodeHandler.__init__(self) self.base_format = None self.current_format = None def set_light_background(self, state): """ :param state: """ if state: self.default_foreground_color = 30 self.default_background_color = 47 else: self.default_foreground_color = 37 self.default_background_color = 40 def set_base_format(self, base_format): """ :param base_format: """ self.base_format = base_format def get_format(self): """ :return: """ return self.current_format def set_style(self): """ Set font style with the following attributes: 'foreground_color', 'background_color', 'italic', 'bold' and 'underline' """ if self.current_format is None: assert self.base_format is not None self.current_format = QTextCharFormat(self.base_format) # Foreground color if self.foreground_color is None: qcolor = self.base_format.foreground() else: cstr = self.ANSI_COLORS[self.foreground_color - 30][self.intensity] qcolor = QColor(cstr) self.current_format.setForeground(qcolor) # Background color if self.background_color is None: qcolor = self.base_format.background() else: cstr = self.ANSI_COLORS[self.background_color - 40][self.intensity] qcolor = QColor(cstr) self.current_format.setBackground(qcolor) font = self.current_format.font() # Italic if self.italic is None: italic = self.base_format.fontItalic() else: italic = self.italic font.setItalic(italic) # Bold if self.bold is None: bold = self.base_format.font().bold() else: bold = self.bold font.setBold(bold) # Underline if self.underline is None: underline = self.base_format.font().underline() else: underline = self.underline font.setUnderline(underline) self.current_format.setFont(font) def inverse_color(color): """Inverse color""" inv_color = QColor() inv_color.setHsv(color.hue(), color.saturation(), 255 - color.value()) return inv_color class ConsoleFontStyle(object): """Console font style management""" def __init__(self, foregroundcolor, backgroundcolor, bold, italic, underline): self.foregroundcolor = foregroundcolor self.backgroundcolor = backgroundcolor self.bold = bold self.italic = italic self.underline = underline self.format = None def apply_style(self, font, is_default): """Apply style to font Args: font: QFont is_default: Default is standard text (not error, link, etc.) """ self.format = QTextCharFormat() self.format.setFont(font) fg_color = QColor(self.foregroundcolor) if is_default: self.format.setForeground( inverse_color(fg_color) if qth.is_dark_theme() else fg_color ) else: self.format.setForeground(fg_color) self.format.setBackground(qth.get_background_color()) font = self.format.font() font.setBold(self.bold) font.setItalic(self.italic) font.setUnderline(self.underline) self.format.setFont(font) class ConsoleBaseWidget(TextEditBaseWidget): """Console base widget""" BRACE_MATCHING_SCOPE = ("sol", "eol") COLOR_PATTERN = re.compile(r"\x01?\x1b\[(.*?)m\x02?") exception_occurred = Signal(str, bool) userListActivated = Signal(int, str) completion_widget_activated = Signal(str) def __init__(self, parent=None): TextEditBaseWidget.__init__(self, parent) self.setMaximumBlockCount(300) # ANSI escape code handler self.ansi_handler = QtANSIEscapeCodeHandler() # Disable undo/redo (nonsense for a console widget...): self.setUndoRedoEnabled(False) self.userListActivated.connect( lambda user_id, text: self.completion_widget_activated.emit(text) ) self.default_style = ConsoleFontStyle( foregroundcolor=0x000000, backgroundcolor=0xFFFFFF, bold=False, italic=False, underline=False, ) self.error_style = ConsoleFontStyle( foregroundcolor=0xFF0000, backgroundcolor=0xFFFFFF, bold=False, italic=False, underline=False, ) self.traceback_link_style = ConsoleFontStyle( foregroundcolor=0x0000FF, backgroundcolor=0xFFFFFF, bold=True, italic=False, underline=True, ) self.prompt_style = ConsoleFontStyle( foregroundcolor=0x00AA00, backgroundcolor=0xFFFFFF, bold=True, italic=False, underline=False, ) self.font_styles = ( self.default_style, self.error_style, self.traceback_link_style, self.prompt_style, ) self.update_color_mode() self.setMouseTracking(True) def update_color_mode(self): """Update color mode according to the current theme""" self.set_light_background(not qth.is_dark_theme()) def set_light_background(self, state): """Set light background state Args: state: bool """ default_fg_color = QColor(self.default_style.foregroundcolor) if not state: default_fg_color = inverse_color(default_fg_color) bg_color = qth.get_background_color() cursor = self.textCursor() cursor.movePosition(QTextCursor.Start) while not cursor.atEnd(): cursor.setPosition(cursor.block().position()) cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor) charformat = cursor.charFormat() if charformat.background() == bg_color: break charformat.setBackground(bg_color) col = charformat.foreground().color() if col.red() == col.green() and col.green() == col.blue(): # Default style charformat.setForeground(default_fg_color) cursor.setCharFormat(charformat) cursor.movePosition(QTextCursor.NextBlock) self.set_palette(background=bg_color, foreground=qth.get_foreground_color()) self.ansi_handler.set_light_background(state) self.set_pythonshell_font() # ------Python shell def insert_text(self, text): """Reimplement TextEditBaseWidget method""" # Eventually this maybe should wrap to insert_text_to if # backspace-handling is required self.textCursor().insertText(text, self.default_style.format) def paste(self): """Reimplement Qt method""" if self.has_selected_text(): self.remove_selected_text() self.insert_text(QApplication.clipboard().text()) def append_text_to_shell(self, text, error, prompt): """ Append text to Python shell In a way, this method overrides the method 'insert_text' when text is inserted at the end of the text widget for a Python shell Handles error messages and show blue underlined links Handles ANSI color sequences Handles ANSI FF sequence """ cursor = self.textCursor() cursor.movePosition(QTextCursor.End) if "\r" in text: # replace \r\n with \n text = text.replace("\r\n", "\n") text = text.replace("\r", "\n") while True: index = text.find(chr(12)) if index == -1: break text = text[index + 1 :] self.clear() if error: is_traceback = False for text in text.splitlines(True): if text.startswith(" File") and not text.startswith(' File "<'): is_traceback = True # Show error links in blue underlined text cursor.insertText(" ", self.default_style.format) cursor.insertText(text[2:], self.traceback_link_style.format) else: # Show error/warning messages in red cursor.insertText(text, self.error_style.format) self.exception_occurred.emit(text, is_traceback) elif prompt: # Show prompt in green insert_text_to(cursor, text, self.prompt_style.format) else: # Show other outputs in black last_end = 0 for match in self.COLOR_PATTERN.finditer(text): insert_text_to( cursor, text[last_end : match.start()], self.default_style.format ) last_end = match.end() try: for code in [int(_c) for _c in match.group(1).split(";")]: self.ansi_handler.set_code(code) except ValueError: pass self.default_style.format = self.ansi_handler.get_format() insert_text_to(cursor, text[last_end:], self.default_style.format) self.set_cursor_position("eof") self.setCurrentCharFormat(self.default_style.format) def set_pythonshell_font(self, font=None): """Python Shell only""" if font is None: if self.default_style.format is None: font = self.font() else: font = self.default_style.format.font() for style in self.font_styles: style.apply_style(font=font, is_default=style is self.default_style) self.ansi_handler.set_base_format(self.default_style.format) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/widgets/console/calltip.py0000644000175100001770000002762014654363416021320 0ustar00runnerdocker# -*- coding: utf-8 -*- # ----------------------------------------------------------------------------- # Copyright (c) 2010 IPython Development Team # Copyright (c) 2013- Spyder Project Contributors # # Distributed under the terms of the Modified BSD License # (BSD 3-clause; see NOTICE.txt in the Spyder root directory for details). # ----------------------------------------------------------------------------- """ Calltip widget used only to show signatures. Adapted from IPython/frontend/qt/console/call_tip_widget.py of the `IPython Project `_. Now located at qtconsole/call_tip_widget.py as part of the `Jupyter QtConsole Project `_. """ from unicodedata import category from qtpy.QtCore import QBasicTimer, QCoreApplication, QEvent, Qt from qtpy.QtGui import QCursor, QPalette from qtpy.QtWidgets import ( QFrame, QLabel, QPlainTextEdit, QStyle, QStyleOptionFrame, QStylePainter, QTextEdit, QToolTip, ) class CallTipWidget(QLabel): """Shows call tips by parsing the current text of Q[Plain]TextEdit.""" # -------------------------------------------------------------------------- # 'QObject' interface # -------------------------------------------------------------------------- def __init__(self, text_edit, hide_timer_on=False): """Create a call tip manager that is attached to the specified Qt text edit widget. """ assert isinstance(text_edit, (QTextEdit, QPlainTextEdit)) super().__init__(None, Qt.ToolTip) self.app = QCoreApplication.instance() self.hide_timer_on = hide_timer_on self.tip = None self._hide_timer = QBasicTimer() self._text_edit = text_edit self.setFont(text_edit.document().defaultFont()) self.setForegroundRole(QPalette.ToolTipText) self.setBackgroundRole(QPalette.ToolTipBase) self.setPalette(QToolTip.palette()) self.setAlignment(Qt.AlignLeft) self.setIndent(1) self.setFrameStyle(QFrame.NoFrame) self.setMargin( 1 + self.style().pixelMetric(QStyle.PM_ToolTipLabelFrameWidth, None, self) ) def eventFilter(self, obj, event): """Reimplemented to hide on certain key presses and on text edit focus changes. """ if obj == self._text_edit: etype = event.type() if etype == QEvent.KeyPress: key = event.key() cursor = self._text_edit.textCursor() prev_char = self._text_edit.get_character(cursor.position(), offset=-1) if key in (Qt.Key_Enter, Qt.Key_Return, Qt.Key_Down, Qt.Key_Up): self.hide() elif key == Qt.Key_Escape: self.hide() return True elif prev_char == ")": self.hide() elif etype == QEvent.FocusOut: self.hide() elif etype == QEvent.Enter: if ( self._hide_timer.isActive() and self.app.topLevelAt(QCursor.pos()) == self ): self._hide_timer.stop() elif etype == QEvent.Leave: self._leave_event_hide() return super().eventFilter(obj, event) def timerEvent(self, event): """Reimplemented to hide the widget when the hide timer fires.""" if event.timerId() == self._hide_timer.timerId(): self._hide_timer.stop() self.hide() # -------------------------------------------------------------------------- # 'QWidget' interface # -------------------------------------------------------------------------- def enterEvent(self, event): """Reimplemented to cancel the hide timer.""" super().enterEvent(event) if self._hide_timer.isActive() and self.app.topLevelAt(QCursor.pos()) == self: self._hide_timer.stop() def hideEvent(self, event): """Reimplemented to disconnect signal handlers and event filter.""" super().hideEvent(event) self._text_edit.cursorPositionChanged.disconnect(self._cursor_position_changed) self._text_edit.removeEventFilter(self) def leaveEvent(self, event): """Reimplemented to start the hide timer.""" super().leaveEvent(event) self._leave_event_hide() def mousePressEvent(self, event): """ :param event: """ super().mousePressEvent(event) self.hide() def paintEvent(self, event): """Reimplemented to paint the background panel.""" painter = QStylePainter(self) option = QStyleOptionFrame() option.initFrom(self) painter.drawPrimitive(QStyle.PE_PanelTipLabel, option) painter.end() super().paintEvent(event) def setFont(self, font): """Reimplemented to allow use of this method as a slot.""" super().setFont(font) def showEvent(self, event): """Reimplemented to connect signal handlers and event filter.""" super().showEvent(event) self._text_edit.cursorPositionChanged.connect(self._cursor_position_changed) self._text_edit.installEventFilter(self) def focusOutEvent(self, event): """Reimplemented to hide it when focus goes out of the main window. """ self.hide() # -------------------------------------------------------------------------- # 'CallTipWidget' interface # -------------------------------------------------------------------------- def show_tip(self, point, tip, wrapped_tiplines): """Attempts to show the specified tip at the current cursor location.""" # Don't attempt to show it if it's already visible and the text # to be displayed is the same as the one displayed before. if self.isVisible(): if self.tip == tip: return True else: self.hide() # Attempt to find the cursor position at which to show the call tip. text_edit = self._text_edit cursor = text_edit.textCursor() search_pos = cursor.position() - 1 self._start_position, _ = self._find_parenthesis(search_pos, forward=False) if self._start_position == -1: return False if self.hide_timer_on: self._hide_timer.stop() # Logic to decide how much time to show the calltip depending # on the amount of text present if len(wrapped_tiplines) == 1: args = wrapped_tiplines[0].split("(")[1] nargs = len(args.split(",")) if nargs == 1: hide_time = 1400 elif nargs == 2: hide_time = 1600 else: hide_time = 1800 elif len(wrapped_tiplines) == 2: args1 = wrapped_tiplines[1].strip() nargs1 = len(args1.split(",")) if nargs1 == 1: hide_time = 2500 else: hide_time = 2800 else: hide_time = 3500 self._hide_timer.start(hide_time, self) # Set the text and resize the widget accordingly. self.tip = tip self.setText(tip) self.resize(self.sizeHint()) # Locate and show the widget. Place the tip below the current line # unless it would be off the screen. In that case, decide the best # location based trying to minimize the area that goes off-screen. padding = 3 # Distance in pixels between cursor bounds and tip box. cursor_rect = text_edit.cursorRect(cursor) screen_rect = text_edit.screen().geometry() point.setY(point.y() + padding) tip_height = self.size().height() tip_width = self.size().width() vertical = "bottom" horizontal = "Right" if point.y() + tip_height > screen_rect.height() + screen_rect.y(): point_ = text_edit.mapToGlobal(cursor_rect.topRight()) # If tip is still off screen, check if point is in top or bottom # half of screen. if point_.y() - tip_height < padding: # If point is in upper half of screen, show tip below it. # otherwise above it. if 2 * point.y() < screen_rect.height(): vertical = "bottom" else: vertical = "top" else: vertical = "top" if point.x() + tip_width > screen_rect.width() + screen_rect.x(): point_ = text_edit.mapToGlobal(cursor_rect.topRight()) # If tip is still off-screen, check if point is in the right or # left half of the screen. if point_.x() - tip_width < padding: if 2 * point.x() < screen_rect.width(): horizontal = "Right" else: horizontal = "Left" else: horizontal = "Left" pos = getattr(cursor_rect, "%s%s" % (vertical, horizontal)) adjusted_point = text_edit.mapToGlobal(pos()) if vertical == "top": point.setY(adjusted_point.y() - tip_height - padding) if horizontal == "Left": point.setX(adjusted_point.x() - tip_width - padding) self.move(point) self.show() return True # -------------------------------------------------------------------------- # Protected interface # -------------------------------------------------------------------------- def _find_parenthesis(self, position, forward=True): """If 'forward' is True (resp. False), proceed forwards (resp. backwards) through the line that contains 'position' until an unmatched closing (resp. opening) parenthesis is found. Returns a tuple containing the position of this parenthesis (or -1 if it is not found) and the number commas (at depth 0) found along the way. """ commas = depth = 0 document = self._text_edit.document() char = str(document.characterAt(position)) # Search until a match is found or a non-printable character is # encountered. while category(char) != "Cc" and position > 0: if char == "," and depth == 0: commas += 1 elif char == ")": if forward and depth == 0: break depth += 1 elif char == "(": if not forward and depth == 0: break depth -= 1 position += 1 if forward else -1 char = str(document.characterAt(position)) else: position = -1 return position, commas def _leave_event_hide(self): """Hides the tooltip after some time has passed (assuming the cursor is not over the tooltip). """ if ( self.hide_timer_on and not self._hide_timer.isActive() and # If Enter events always came after Leave events, we wouldn't need # this check. But on Mac OS, it sometimes happens the other way # around when the tooltip is created. self.app.topLevelAt(QCursor.pos()) != self ): self._hide_timer.start(800, self) # ------ Signal handlers ---------------------------------------------------- def _cursor_position_changed(self): """Updates the tip based on user cursor movement.""" cursor = self._text_edit.textCursor() position = cursor.position() document = self._text_edit.document() char = str(document.characterAt(position - 1)) if position <= self._start_position: self.hide() elif char == ")": pos, _ = self._find_parenthesis(position - 1, forward=False) if pos == -1: self.hide() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/widgets/console/dochelpers.py0000644000175100001770000002601314654363416022013 0ustar00runnerdocker# -*- coding: utf-8 -*- # ----------------------------------------------------------------------------- # Copyright (c) 2009- Spyder Kernels Contributors # # Licensed under the terms of the MIT License # (see spyder_kernels/__init__.py for details) # ----------------------------------------------------------------------------- """Utilities and wrappers around inspect module""" import builtins import inspect import re SYMBOLS = r"[^\'\"a-zA-Z0-9_.]" def getobj(txt, last=False): """Return the last valid object name in string""" txt_end = "" for startchar, endchar in ["[]", "()"]: if txt.endswith(endchar): pos = txt.rfind(startchar) if pos: txt_end = txt[pos:] txt = txt[:pos] tokens = re.split(SYMBOLS, txt) token = None try: while token is None or re.match(SYMBOLS, token): token = tokens.pop() if token.endswith("."): token = token[:-1] if token.startswith("."): # Invalid object name return None if last: # XXX: remove this statement as well as the "last" argument token += txt[txt.rfind(token) + len(token)] token += txt_end if token: return token except IndexError: return None def getobjdir(obj): """ For standard objects, will simply return dir(obj) In special cases (e.g. WrapITK package), will return only string elements of result returned by dir(obj) """ return [item for item in dir(obj) if isinstance(item, str)] def getdoc(obj): """ Return text documentation from an object. This comes in a form of dictionary with four keys: name: The name of the inspected object argspec: It's argspec note: A phrase describing the type of object (function or method) we are inspecting, and the module it belongs to. docstring: It's docstring """ docstring = inspect.getdoc(obj) or inspect.getcomments(obj) or "" # Most of the time doc will only contain ascii characters, but there are # some docstrings that contain non-ascii characters. Not all source files # declare their encoding in the first line, so querying for that might not # yield anything, either. So assume the most commonly used # multi-byte file encoding (which also covers ascii). try: docstring = str(docstring) except: # pylint: disable=bare-except pass # Doc dict keys doc = {"name": "", "argspec": "", "note": "", "docstring": docstring} if callable(obj): try: name = obj.__name__ except AttributeError: doc["docstring"] = docstring return doc if inspect.ismethod(obj): imclass = obj.__self__.__class__ if obj.__self__ is not None: doc["note"] = "Method of %s instance" % obj.__self__.__class__.__name__ else: doc["note"] = "Unbound %s method" % imclass.__name__ obj = obj.__func__ elif hasattr(obj, "__module__"): doc["note"] = "Function of %s module" % obj.__module__ else: doc["note"] = "Function" doc["name"] = obj.__name__ if inspect.isfunction(obj): sig = inspect.signature(obj) doc["argspec"] = str(sig) if name == "": doc["name"] = name + " lambda " doc["argspec"] = doc["argspec"][1:-1] # remove parentheses else: argspec = getargspecfromtext(doc["docstring"]) if argspec: doc["argspec"] = argspec # Many scipy and numpy docstrings begin with a function # signature on the first line. This ends up begin redundant # when we are using title and argspec to create the # rich text "Definition:" field. We'll carefully remove this # redundancy but only under a strict set of conditions: # Remove the starting charaters of the 'doc' portion *iff* # the non-whitespace characters on the first line # match *exactly* the combined function title # and argspec we determined above. signature = doc["name"] + doc["argspec"] docstring_blocks = doc["docstring"].split("\n\n") first_block = docstring_blocks[0].strip() if first_block == signature: doc["docstring"] = ( doc["docstring"].replace(signature, "", 1).lstrip() ) else: doc["argspec"] = "(...)" # Remove self from argspec argspec = doc["argspec"] doc["argspec"] = argspec.replace("(self)", "()").replace("(self, ", "(") return doc def getsource(obj): """Wrapper around inspect.getsource""" try: try: src = str(inspect.getsource(obj)) except TypeError: if hasattr(obj, "__class__"): src = str(inspect.getsource(obj.__class__)) else: # Bindings like VTK or ITK require this case src = getdoc(obj) return src except (TypeError, IOError): return def getsignaturefromtext(text, objname): """Get object signatures from text (object documentation) Return a list containing a single string in most cases Example of multiple signatures: PyQt5 objects""" if isinstance(text, dict): text = text.get("docstring", "") # Regexps oneline_re = objname + r'\([^\)].+?(?<=[\w\]\}\'"])\)(?!,)' multiline_re = objname + r'\([^\)]+(?<=[\w\]\}\'"])\)(?!,)' multiline_end_parenleft_re = r'(%s\([^\)]+(\),\n.+)+(?<=[\w\]\}\'"])\))' # Grabbing signatures if not text: text = "" sigs_1 = re.findall(oneline_re + "|" + multiline_re, text) sigs_2 = [g[0] for g in re.findall(multiline_end_parenleft_re % objname, text)] all_sigs = sigs_1 + sigs_2 # The most relevant signature is usually the first one. There could be # others in doctests but those are not so important if all_sigs: return all_sigs[0] else: return "" # Fix for Issue 1953 # TODO: Add more signatures and remove this hack in 2.4 getsignaturesfromtext = getsignaturefromtext def getargspecfromtext(text): """ Try to get the formatted argspec of a callable from the first block of its docstring This will return something like '(foo, bar, k=1)' """ blocks = text.split("\n\n") first_block = blocks[0].strip() return getsignaturefromtext(first_block, "") def getargsfromtext(text, objname): """Get arguments from text (object documentation)""" signature = getsignaturefromtext(text, objname) if signature: argtxt = signature[signature.find("(") + 1 : -1] return argtxt.split(",") def getargsfromdoc(obj): """Get arguments from object doc""" if obj.__doc__ is not None: return getargsfromtext(obj.__doc__, obj.__name__) def getargs(obj): """Get the names and default values of a function's arguments""" if inspect.isfunction(obj) or inspect.isbuiltin(obj): func_obj = obj elif inspect.ismethod(obj): func_obj = obj.__func__ elif inspect.isclass(obj) and hasattr(obj, "__init__"): func_obj = getattr(obj, "__init__") else: return [] if not hasattr(func_obj, "func_code"): # Builtin: try to extract info from doc args = getargsfromdoc(func_obj) if args is not None: return args else: # Example: PyQt5 return getargsfromdoc(obj) args, _, _ = inspect.getargs(func_obj.func_code) if not args: return getargsfromdoc(obj) # Supporting tuple arguments in def statement: for i_arg, arg in enumerate(args): if isinstance(arg, list): args[i_arg] = "(%s)" % ", ".join(arg) defaults = func_obj.__defaults__ if defaults is not None: for index, default in enumerate(defaults): args[index + len(args) - len(defaults)] += "=" + repr(default) if inspect.isclass(obj) or inspect.ismethod(obj): if len(args) == 1: return None if "self" in args: args.remove("self") return args def getargtxt(obj, one_arg_per_line=True): """ Get the names and default values of a function's arguments Return list with separators (', ') formatted for calltips """ args = getargs(obj) if args: sep = ", " textlist = None for i_arg, arg in enumerate(args): if textlist is None: textlist = [""] textlist[-1] += arg if i_arg < len(args) - 1: textlist[-1] += sep if len(textlist[-1]) >= 32 or one_arg_per_line: textlist.append("") if inspect.isclass(obj) or inspect.ismethod(obj): if len(textlist) == 1: return None if "self" + sep in textlist: textlist.remove("self" + sep) return textlist def isdefined(obj, force_import=False, namespace=None): """Return True if object is defined in namespace If namespace is None --> namespace = locals()""" if namespace is None: namespace = locals() attr_list = obj.split(".") base = attr_list.pop(0) if len(base) == 0: return False if base not in builtins.__dict__ and base not in namespace: if force_import: try: module = __import__(base, globals(), namespace) if base not in globals(): globals()[base] = module namespace[base] = module except Exception: return False else: return False for attr in attr_list: try: attr_not_found = not hasattr(eval(base, namespace), attr) except (SyntaxError, AttributeError): return False if attr_not_found: if force_import: try: __import__(base + "." + attr, globals(), namespace) except (ImportError, SyntaxError): return False else: return False base += "." + attr return True if __name__ == "__main__": class Test(object): def method(self, x, y=2): """ :param x: :param y: """ pass print(getargtxt(Test.__init__)) # spyder: test-skip print(getargtxt(Test.method)) # spyder: test-skip print(isdefined("numpy.take", force_import=True)) # spyder: test-skip print(isdefined("__import__")) # spyder: test-skip print(isdefined(".keys", force_import=True)) # spyder: test-skip print(getobj("globals")) # spyder: test-skip print(getobj("globals().keys")) # spyder: test-skip print(getobj("+scipy.signal.")) # spyder: test-skip print(getobj("4.")) # spyder: test-skip print(getdoc(sorted)) # spyder: test-skip print(getargtxt(sorted)) # spyder: test-skip ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/widgets/console/internalshell.py0000644000175100001770000003553514654363416022540 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Copyright ┬й Spyder Project Contributors # Licensed under the terms of the MIT License # (see spyder/__init__.py for details) """Internal shell widget : PythonShellWidget + Interpreter""" # pylint: disable=C0103 # pylint: disable=R0903 # pylint: disable=R0911 # pylint: disable=R0201 # FIXME: Internal shell MT: for i in range(100000): print i -> bug import builtins import os import platform import sys import threading from time import time from qtpy.QtCore import QEventLoop, QObject, Signal, Slot from qtpy.QtWidgets import QMessageBox from guidata.config import CONF, _ from guidata.configtools import get_icon from guidata.qthelpers import add_actions, create_action, get_std_icon, is_dark_theme from guidata.utils.misc import getcwd_or_home, run_program from guidata.widgets import about from guidata.widgets.console.dochelpers import getargtxt, getdoc, getobjdir, getsource from guidata.widgets.console.interpreter import Interpreter from guidata.widgets.console.shell import PythonShellWidget def create_banner(message): """Create internal shell banner""" system = platform.system() if message is None: bitness = 64 if sys.maxsize > 2**32 else 32 return "Python %s %dbits [%s]" % (platform.python_version(), bitness, system) else: return message class SysOutput(QObject): """Handle standard I/O queue""" data_avail = Signal() def __init__(self): QObject.__init__(self) self.queue = [] self.lock = threading.Lock() def write(self, val): """ :param val: """ self.lock.acquire() self.queue.append(val) self.lock.release() self.data_avail.emit() def empty_queue(self): """ :return: """ self.lock.acquire() s = "".join(self.queue) self.queue = [] self.lock.release() return s # We need to add this method to fix Issue 1789 def flush(self): """ """ pass # This is needed to fix Issue 2984 @property def closed(self): """ :return: """ return False class WidgetProxyData(object): pass class WidgetProxy(QObject): """Handle Shell widget refresh signal""" sig_new_prompt = Signal(str) sig_set_readonly = Signal(bool) sig_edit = Signal(str, bool) sig_wait_input = Signal(str) def __init__(self, input_condition): QObject.__init__(self) self.input_data = None self.input_condition = input_condition def new_prompt(self, prompt): """ :param prompt: """ self.sig_new_prompt.emit(prompt) def set_readonly(self, state): """ :param state: """ self.sig_set_readonly.emit(state) def edit(self, filename, external_editor=False): """ :param filename: :param external_editor: """ self.sig_edit.emit(filename, external_editor) def data_available(self): """Return True if input data is available""" return self.input_data is not WidgetProxyData def wait_input(self, prompt=""): """ :param prompt: """ self.input_data = WidgetProxyData self.sig_wait_input.emit(prompt) def end_input(self, cmd): """ :param cmd: """ self.input_condition.acquire() self.input_data = cmd self.input_condition.notify() self.input_condition.release() class InternalShell(PythonShellWidget): """Shell base widget: link between PythonShellWidget and Interpreter""" status = Signal(str) refresh = Signal() go_to_error = Signal(str) focus_changed = Signal() def __init__( self, parent=None, namespace=None, commands=[], message=None, max_line_count=300, font=None, exitfunc=None, profile=False, multithreaded=True, light_background=not is_dark_theme(), debug=False, ): PythonShellWidget.__init__(self, parent, profile) self.debug = debug self.set_light_background(light_background) self.multithreaded = multithreaded self.setMaximumBlockCount(max_line_count) if font is not None: self.set_font(font) # Allow raw_input support: self.input_loop = None self.input_mode = False # KeyboardInterrupt support self.interrupted = False # used only for not-multithreaded mode self.sig_keyboard_interrupt.connect(self.keyboard_interrupt) # Code completion / calltips getcfg = lambda option: CONF.get("console", option) case_sensitive = getcfg("codecompletion/case_sensitive") self.set_codecompletion_case(case_sensitive) # keyboard events management self.eventqueue = [] # Init interpreter self.exitfunc = exitfunc self.commands = commands self.message = message self.interpreter = None self.start_interpreter(namespace) # Clear status bar self.status.emit("") # ------ Interpreter def start_interpreter(self, namespace): """Start Python interpreter""" self.clear() if self.interpreter is not None: self.interpreter.closing() self.interpreter = Interpreter( namespace, self.exitfunc, SysOutput, WidgetProxy, self.debug ) self.interpreter.stdout_write.data_avail.connect(self.stdout_avail) self.interpreter.stderr_write.data_avail.connect(self.stderr_avail) self.interpreter.widget_proxy.sig_set_readonly.connect(self.setReadOnly) self.interpreter.widget_proxy.sig_new_prompt.connect(self.new_prompt) self.interpreter.widget_proxy.sig_edit.connect(self.edit_script) self.interpreter.widget_proxy.sig_wait_input.connect(self.wait_input) if self.multithreaded: self.interpreter.start() # Interpreter banner banner = create_banner(self.message) self.write(banner, prompt=True) # Initial commands for cmd in self.commands: self.run_command(cmd, history=False, new_prompt=False) # First prompt self.new_prompt(self.interpreter.p1) self.refresh.emit() return self.interpreter def exit_interpreter(self): """Exit interpreter""" self.interpreter.exit_flag = True if self.multithreaded: self.interpreter.stdin_write.write(b"\n") self.interpreter.restore_stds() def edit_script(self, filename, external_editor): """ :param filename: :param external_editor: """ filename = str(filename) if external_editor: self.external_editor(filename) else: self.parent().edit_script(filename) def stdout_avail(self): """Data is available in stdout, let's empty the queue and write it!""" data = self.interpreter.stdout_write.empty_queue() if data: self.write(data) def stderr_avail(self): """Data is available in stderr, let's empty the queue and write it!""" data = self.interpreter.stderr_write.empty_queue() if data: self.write(data, error=True) self.flush(error=True) # ------Raw input support def wait_input(self, prompt=""): """Wait for input (raw_input support)""" self.new_prompt(prompt) self.setFocus() self.input_mode = True self.input_loop = QEventLoop() self.input_loop.exec() self.input_loop = None def end_input(self, cmd): """End of wait_input mode""" self.input_mode = False self.input_loop.exit() self.interpreter.widget_proxy.end_input(cmd) # ----- Menus, actions, ... def setup_context_menu(self): """Reimplement PythonShellWidget method""" PythonShellWidget.setup_context_menu(self) help_action = create_action( self, _("Help..."), icon=get_std_icon("DialogHelpButton"), triggered=self.help, ) about_action = create_action( self, _("About..."), icon=get_icon("guidata.svg"), triggered=about.show_about_dialog, ) add_actions(self.menu, (None, help_action, about_action)) @Slot() def help(self): """Help on Spyder console""" QMessageBox.information( self, _("Help"), """%s

%s
edit foobar.py

%s
xedit foobar.py

%s
run foobar.py

%s
clear x, y

%s
!ls

%s
object? """ % ( _("Shell special commands:"), _("Internal editor:"), _("External editor:"), _("Run script:"), _("Remove references:"), _("System commands:"), _("Python help:"), ), ) def external_editor(self, filename, goto=-1): """Edit in an external editor Recommended: SciTE (e.g. to go to line where an error did occur)""" editor_path = CONF.get("console", "external_editor/path") goto_option = CONF.get("console", "external_editor/gotoline") try: args = [filename] if goto > 0 and goto_option: args.append("%s%d".format(goto_option, goto)) run_program(editor_path, args) except OSError: self.write_error("External editor was not found:" " %s\n" % editor_path) # ------ I/O def flush(self, error=False, prompt=False): """Reimplement ShellBaseWidget method""" PythonShellWidget.flush(self, error=error, prompt=prompt) if self.interrupted: self.interrupted = False raise KeyboardInterrupt # ------ Clear terminal def clear_terminal(self): """Reimplement ShellBaseWidget method""" self.clear() self.new_prompt( self.interpreter.p2 if self.interpreter.more else self.interpreter.p1 ) # ------ Keyboard events def on_enter(self, command): """on_enter""" if self.profile: # Simple profiling test t0 = time() for _ in range(10): self.execute_command(command) self.insert_text("\n<╬Фt>=%dms\n" % (1e2 * (time() - t0))) self.new_prompt(self.interpreter.p1) else: self.execute_command(command) self.__flush_eventqueue() def keyPressEvent(self, event): """ Reimplement Qt Method Enhanced keypress event handler """ if self.preprocess_keyevent(event): # Event was accepted in self.preprocess_keyevent return self.postprocess_keyevent(event) def __flush_eventqueue(self): """Flush keyboard event queue""" while self.eventqueue: past_event = self.eventqueue.pop(0) self.postprocess_keyevent(past_event) # ------ Command execution def keyboard_interrupt(self): """Simulate keyboard interrupt""" if self.multithreaded: self.interpreter.raise_keyboard_interrupt() else: if self.interpreter.more: self.write_error("\nKeyboardInterrupt\n") self.interpreter.more = False self.new_prompt(self.interpreter.p1) self.interpreter.resetbuffer() else: self.interrupted = True def execute_lines(self, lines): """ Execute a set of lines as multiple command lines: multiple lines of text to be executed as single commands """ for line in lines.splitlines(): stripped_line = line.strip() if stripped_line.startswith("#"): continue self.write(line + os.linesep, flush=True) self.execute_command(line + "\n") self.flush() def execute_command(self, cmd): """ Execute a command :param cmd: one-line command only, with ``'\n'`` at the end """ if self.input_mode: self.end_input(cmd) return if cmd.endswith("\n"): cmd = cmd[:-1] # cls command if cmd == "cls": self.clear_terminal() return self.run_command(cmd) def run_command(self, cmd, history=True, new_prompt=True): """Run command in interpreter""" if not cmd: cmd = "" else: if history: self.add_to_history(cmd) if not self.multithreaded: if "input" not in cmd: self.interpreter.stdin_write.write(bytes(cmd + "\n", "utf-8")) self.interpreter.run_line() self.refresh.emit() else: self.write( _( 'In order to use commands like "input" run console with the multithread option' ), error=True, ) else: self.interpreter.stdin_write.write(bytes(cmd + "\n", "utf-8")) # ------ Code completion / Calltips def _eval(self, text): """Is text a valid object?""" return self.interpreter.eval(text) def get_dir(self, objtxt): """Return dir(object)""" obj, valid = self._eval(objtxt) if valid: return getobjdir(obj) def get_globals_keys(self): """Return shell globals() keys""" return list(self.interpreter.namespace.keys()) def get_cdlistdir(self): """Return shell current directory list dir""" return os.listdir(getcwd_or_home()) def iscallable(self, objtxt): """Is object callable?""" obj, valid = self._eval(objtxt) if valid: return callable(obj) def get_arglist(self, objtxt): """Get func/method argument list""" obj, valid = self._eval(objtxt) if valid: return getargtxt(obj) def get__doc__(self, objtxt): """Get object __doc__""" obj, valid = self._eval(objtxt) if valid: return obj.__doc__ def get_doc(self, objtxt): """Get object documentation dictionary""" obj, valid = self._eval(objtxt) if valid: return getdoc(obj) def get_source(self, objtxt): """Get object source""" obj, valid = self._eval(objtxt) if valid: return getsource(obj) def is_defined(self, objtxt, force_import=False): """Return True if object is defined""" return self.interpreter.is_defined(objtxt, force_import) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/widgets/console/interpreter.py0000644000175100001770000002771214654363416022235 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Copyright ┬й Spyder Project Contributors # Licensed under the terms of the MIT License # (see spyder/__init__.py for details) """Shell Interpreter""" import atexit import ctypes import os import os.path as osp import pydoc import re import sys import threading from code import InteractiveConsole from guidata.utils.misc import getcwd_or_home, remove_backslashes, run_shell_command from guidata.widgets.console.dochelpers import isdefined sys.path.insert(0, "") def guess_filename(filename): """Guess filename""" if osp.isfile(filename): return filename if not filename.endswith(".py"): filename += ".py" for path in [getcwd_or_home()] + sys.path: fname = osp.join(path, filename) if osp.isfile(fname): return fname elif osp.isfile(fname + ".py"): return fname + ".py" elif osp.isfile(fname + ".pyw"): return fname + ".pyw" return filename class Interpreter(InteractiveConsole, threading.Thread): """Interpreter, executed in a separate thread""" p1 = ">>> " p2 = "... " def __init__( self, namespace=None, exitfunc=None, Output=None, WidgetProxy=None, debug=False ): """ namespace: locals send to InteractiveConsole object commands: list of commands executed at startup """ InteractiveConsole.__init__(self, namespace) threading.Thread.__init__(self) self._id = None self.exit_flag = False self.debug = debug # Execution Status self.more = False if exitfunc is not None: atexit.register(exitfunc) self.namespace = self.locals self.namespace["__name__"] = "__main__" self.namespace["execfile"] = self.execfile self.namespace["runfile"] = self.runfile self.namespace["raw_input"] = self.raw_input_replacement self.namespace["help"] = self.help_replacement # Capture all interactive input/output self.initial_stdout = sys.stdout self.initial_stderr = sys.stderr self.initial_stdin = sys.stdin # Create communication pipes pr, pw = os.pipe() self.stdin_read = os.fdopen(pr, "r") self.stdin_write = os.fdopen(pw, "wb", 0) self.stdout_write = Output() self.stderr_write = Output() self.input_condition = threading.Condition() self.widget_proxy = WidgetProxy(self.input_condition) self.redirect_stds() # ------ Standard input/output def redirect_stds(self): """Redirects stds""" if not self.debug: sys.stdout = self.stdout_write sys.stderr = self.stderr_write sys.stdin = self.stdin_read def restore_stds(self): """Restore stds""" if not self.debug: sys.stdout = self.initial_stdout sys.stderr = self.initial_stderr sys.stdin = self.initial_stdin def raw_input_replacement(self, prompt=""): """For raw_input builtin function emulation""" self.widget_proxy.wait_input(prompt) self.input_condition.acquire() while not self.widget_proxy.data_available(): self.input_condition.wait() inp = self.widget_proxy.input_data self.input_condition.release() return inp def help_replacement(self, text=None, interactive=False): """For help builtin function emulation""" if text is not None and not interactive: return pydoc.help(text) elif text is None: pyver = "%d.%d" % (sys.version_info[0], sys.version_info[1]) self.write( """ Welcome to Python %s! This is the online help utility. If this is your first time using Python, you should definitely check out the tutorial on the Internet at https://www.python.org/about/gettingstarted/ Enter the name of any module, keyword, or topic to get help on writing Python programs and using Python modules. To quit this help utility and return to the interpreter, just type "quit". To get a list of available modules, keywords, or topics, type "modules", "keywords", or "topics". Each module also comes with a one-line summary of what it does; to list the modules whose summaries contain a given word such as "spam", type "modules spam". """ % pyver ) else: text = text.strip() try: eval("pydoc.help(%s)" % text) except (NameError, SyntaxError): print( "no Python documentation found for '%r'" % text ) # spyder: test-skip self.write(os.linesep) self.widget_proxy.new_prompt("help> ") inp = self.raw_input_replacement() if inp.strip(): self.help_replacement(inp, interactive=True) else: self.write( """ You are now leaving help and returning to the Python interpreter. If you want to ask for help on a particular object directly from the interpreter, you can type "help(object)". Executing "help('string')" has the same effect as typing a particular string at the help> prompt. """ ) def run_command(self, cmd, new_prompt=True): """Run command in interpreter""" if cmd == "exit()": self.exit_flag = True self.write("\n") return # -- Special commands type I # (transformed into commands executed in the interpreter) # ? command special_pattern = r"^%s (?:r\')?(?:u\')?\"?\'?([a-zA-Z0-9_\.]+)" run_match = re.match(special_pattern % "run", cmd) help_match = re.match(r"^([a-zA-Z0-9_\.]+)\?$", cmd) cd_match = re.match(r"^\!cd \"?\'?([a-zA-Z0-9_ \.]+)", cmd) if help_match: cmd = "help(%s)" % help_match.group(1) # run command elif run_match: filename = guess_filename(run_match.groups()[0]) cmd = "runfile('%s', args=None)" % remove_backslashes(filename) # !cd system command elif cd_match: cmd = 'import os; os.chdir(r"%s")' % cd_match.groups()[0].strip() # -- End of Special commands type I # -- Special commands type II # (don't need code execution in interpreter) xedit_match = re.match(special_pattern % "xedit", cmd) edit_match = re.match(special_pattern % "edit", cmd) clear_match = re.match(r"^clear ([a-zA-Z0-9_, ]+)", cmd) # (external) edit command if xedit_match: filename = guess_filename(xedit_match.groups()[0]) self.widget_proxy.edit(filename, external_editor=True) # local edit command elif edit_match: filename = guess_filename(edit_match.groups()[0]) if osp.isfile(filename): self.widget_proxy.edit(filename) else: self.stderr_write.write("No such file or directory: %s\n" % filename) # remove reference (equivalent to MATLAB's clear command) elif clear_match: varnames = clear_match.groups()[0].replace(" ", "").split(",") for varname in varnames: try: self.namespace.pop(varname) except KeyError: pass # Execute command elif cmd.startswith("!"): # System ! command pipe = run_shell_command(cmd[1:]) out = pipe.stdout.read() err = pipe.stderr.read() try: out = out.decode("cp437") except UnicodeDecodeError: out = out.decode(erros="ignore") try: err = err.decode("cp437") except UnicodeDecodeError: err = err.decode(erros="ignore") if err: self.stderr_write.write(err) if out: self.stdout_write.write(out) self.stdout_write.write("\n") self.more = False # -- End of Special commands type II else: # Command executed in the interpreter # self.widget_proxy.set_readonly(True) self.more = self.push(cmd) # self.widget_proxy.set_readonly(False) if new_prompt: self.widget_proxy.new_prompt(self.p2 if self.more else self.p1) if not self.more: self.resetbuffer() def run(self): """Wait for input and run it""" while not self.exit_flag: self.run_line() def run_line(self): """ :return: """ line = self.stdin_read.readline() if self.exit_flag: return # Remove last character which is always '\n': self.run_command(line[:-1]) def get_thread_id(self): """Return thread id""" if self._id is None: for thread_id, obj in list(threading._active.items()): if obj is self: self._id = thread_id return self._id def raise_keyboard_interrupt(self): """ :return: """ if self.is_alive(): ctypes.pythonapi.PyThreadState_SetAsyncExc( self.get_thread_id(), ctypes.py_object(KeyboardInterrupt) ) return True else: return False def closing(self): """Actions to be done before restarting this interpreter""" pass def execfile(self, filename): """Exec filename""" source = open(filename, "r").read() try: try: name = filename.encode("ascii") except UnicodeEncodeError: name = "" code = compile(source, name, "exec") except (OverflowError, SyntaxError): InteractiveConsole.showsyntaxerror(self, filename) else: self.runcode(code) def runfile(self, filename, args=None): """ Run filename args: command line arguments (string) """ if args is not None and not isinstance(args, str): raise TypeError("expected a character buffer object") self.namespace["__file__"] = filename sys.argv = [filename] if args is not None: for arg in args.split(): sys.argv.append(arg) self.execfile(filename) sys.argv = [""] self.namespace.pop("__file__") def eval(self, text): """ Evaluate text and return (obj, valid) where *obj* is the object represented by *text* and *valid* is True if object evaluation did not raise any exception """ assert isinstance(text, str) try: return eval(text, self.locals), True except: return None, False def is_defined(self, objtxt, force_import=False): """Return True if object is defined""" return isdefined(objtxt, force_import=force_import, namespace=self.locals) # =========================================================================== # InteractiveConsole API # =========================================================================== def push(self, line): """ Push a line of source text to the interpreter The line should not have a trailing newline; it may have internal newlines. The line is appended to a buffer and the interpreterтАЩs runsource() method is called with the concatenated contents of the buffer as source. If this indicates that the command was executed or invalid, the buffer is reset; otherwise, the command is incomplete, and the buffer is left as it was after the line was appended. The return value is True if more input is required, False if the line was dealt with in some way (this is the same as runsource()). """ return InteractiveConsole.push(self, "#coding=utf-8\n" + line) def resetbuffer(self): """Remove any unhandled source text from the input buffer""" InteractiveConsole.resetbuffer(self) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/widgets/console/mixins.py0000644000175100001770000007265114654363416021203 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Copyright ┬й Spyder Project Contributors # Licensed under the terms of the MIT License # (see spyder/__init__.py for details) """Mix-in classes These classes were created to be able to provide Spyder's regular text and console widget features to an independant widget based on QTextEdit for the IPython console plugin. """ import os import os.path as osp import re import textwrap from xml.sax.saxutils import escape from qtpy.QtCore import QPoint, QRegularExpression, Qt from qtpy.QtGui import QCursor, QTextCursor, QTextDocument from qtpy.QtWidgets import QApplication, QToolTip from guidata.config import _ from guidata.utils import encoding from guidata.widgets.console.dochelpers import ( getargspecfromtext, getobj, getsignaturefromtext, ) # Order is important: EOL_CHARS = (("\r\n", "nt"), ("\n", "posix"), ("\r", "mac")) def get_eol_chars(text): """Get text EOL characters""" for eol_chars, _os_name in EOL_CHARS: if text.find(eol_chars) > -1: return eol_chars def get_error_match(text): """Return error match""" import re return re.match(r' File "(.*)", line (\d*)', text) class BaseEditMixin(object): """ """ def __init__(self): self.eol_chars = None self.calltip_size = 600 # ------Line number area def get_linenumberarea_width(self): """Return line number area width""" # Implemented in CodeEditor, but needed for calltip/completion widgets return 0 # ------Calltips def _format_signature(self, text): formatted_lines = [] name = text.split("(")[0] rows = textwrap.wrap(text, width=50, subsequent_indent=" " * (len(name) + 1)) for r in rows: r = escape(r) # Escape most common html chars r = r.replace(" ", " ") for char in ["=", ",", "(", ")", "*", "**"]: r = r.replace( char, "" + char + "", ) formatted_lines.append(r) signature = "
".join(formatted_lines) return signature, rows def show_calltip( self, title, text, signature=False, color="#2D62FF", at_line=None, at_position=None, ): """Show calltip""" if text is None or len(text) == 0: return # Saving cursor position: if at_position is None: at_position = self.get_position("cursor") self.calltip_position = at_position # Preparing text: if signature: text, wrapped_textlines = self._format_signature(text) else: if isinstance(text, list): text = "\n ".join(text) text = text.replace("\n", "
") if len(text) > self.calltip_size: text = text[: self.calltip_size] + " ..." # Formatting text font = self.font() size = font.pointSize() family = font.family() format1 = "

" % ( family, size, color, ) format2 = "
" % ( family, size - 1 if size > 9 else size, ) tiptext = ( format1 + ("%s
" % title) + "
" + format2 + text + "
" ) # Showing tooltip at cursor position: cx, cy = self.get_coordinates("cursor") if at_line is not None: cx = 5 cursor = QTextCursor(self.document().findBlockByNumber(at_line - 1)) cy = self.cursorRect(cursor).top() point = self.mapToGlobal(QPoint(cx, cy)) point.setX(point.x() + self.get_linenumberarea_width()) point.setY(point.y() + font.pointSize() + 5) if signature: self.calltip_widget.show_tip(point, tiptext, wrapped_textlines) else: QToolTip.showText(point, tiptext) # ------EOL characters def set_eol_chars(self, text): """Set widget end-of-line (EOL) characters from text (analyzes text)""" eol_chars = get_eol_chars(text) is_document_modified = eol_chars is not None and self.eol_chars is not None self.eol_chars = eol_chars if is_document_modified: self.document().setModified(True) if self.sig_eol_chars_changed is not None: self.sig_eol_chars_changed.emit(eol_chars) def get_line_separator(self): """Return line separator based on current EOL mode""" if self.eol_chars is not None: return self.eol_chars else: return os.linesep def get_text_with_eol(self): """Same as 'toPlainText', replace '\n' by correct end-of-line characters""" utext = str(self.toPlainText()) lines = utext.splitlines() linesep = self.get_line_separator() txt = linesep.join(lines) if utext.endswith("\n"): txt += linesep return txt # ------Positions, coordinates (cursor, EOF, ...) def get_position(self, subject): """Get offset in character for the given subject from the start of text edit area""" cursor = self.textCursor() if subject == "cursor": pass elif subject == "sol": cursor.movePosition(QTextCursor.StartOfBlock) elif subject == "eol": cursor.movePosition(QTextCursor.EndOfBlock) elif subject == "eof": cursor.movePosition(QTextCursor.End) elif subject == "sof": cursor.movePosition(QTextCursor.Start) else: # Assuming that input argument was already a position return subject return cursor.position() def get_coordinates(self, position): """ :param position: :return: """ position = self.get_position(position) cursor = self.textCursor() cursor.setPosition(position) point = self.cursorRect(cursor).center() return point.x(), point.y() def get_cursor_line_column(self): """Return cursor (line, column) numbers""" cursor = self.textCursor() return cursor.blockNumber(), cursor.columnNumber() def get_cursor_line_number(self): """Return cursor line number""" return self.textCursor().blockNumber() + 1 def set_cursor_position(self, position): """Set cursor position""" position = self.get_position(position) cursor = self.textCursor() cursor.setPosition(position) self.setTextCursor(cursor) self.ensureCursorVisible() def move_cursor(self, chars=0): """Move cursor to left or right (unit: characters)""" direction = QTextCursor.Right if chars > 0 else QTextCursor.Left for _i in range(abs(chars)): self.moveCursor(direction, QTextCursor.MoveAnchor) def is_cursor_on_first_line(self): """Return True if cursor is on the first line""" cursor = self.textCursor() cursor.movePosition(QTextCursor.StartOfBlock) return cursor.atStart() def is_cursor_on_last_line(self): """Return True if cursor is on the last line""" cursor = self.textCursor() cursor.movePosition(QTextCursor.EndOfBlock) return cursor.atEnd() def is_cursor_at_end(self): """Return True if cursor is at the end of the text""" return self.textCursor().atEnd() def is_cursor_before(self, position, char_offset=0): """Return True if cursor is before *position*""" position = self.get_position(position) + char_offset cursor = self.textCursor() cursor.movePosition(QTextCursor.End) if position < cursor.position(): cursor.setPosition(position) return self.textCursor() < cursor def __move_cursor_anchor(self, what, direction, move_mode): assert what in ("character", "word", "line") if what == "character": if direction == "left": self.moveCursor(QTextCursor.PreviousCharacter, move_mode) elif direction == "right": self.moveCursor(QTextCursor.NextCharacter, move_mode) elif what == "word": if direction == "left": self.moveCursor(QTextCursor.PreviousWord, move_mode) elif direction == "right": self.moveCursor(QTextCursor.NextWord, move_mode) elif what == "line": if direction == "down": self.moveCursor(QTextCursor.NextBlock, move_mode) elif direction == "up": self.moveCursor(QTextCursor.PreviousBlock, move_mode) def move_cursor_to_next(self, what="word", direction="left"): """ Move cursor to next *what* ('word' or 'character') toward *direction* ('left' or 'right') """ self.__move_cursor_anchor(what, direction, QTextCursor.MoveAnchor) # ------Selection def clear_selection(self): """Clear current selection""" cursor = self.textCursor() cursor.clearSelection() self.setTextCursor(cursor) def extend_selection_to_next(self, what="word", direction="left"): """ Extend selection to next *what* ('word' or 'character') toward *direction* ('left' or 'right') """ self.__move_cursor_anchor(what, direction, QTextCursor.KeepAnchor) # ------Text: get, set, ... def __select_text(self, position_from, position_to): position_from = self.get_position(position_from) position_to = self.get_position(position_to) cursor = self.textCursor() cursor.setPosition(position_from) cursor.setPosition(position_to, QTextCursor.KeepAnchor) return cursor def get_text_line(self, line_nb): """Return text line at line number *line_nb*""" # Taking into account the case when a file ends in an empty line, # since splitlines doesn't return that line as the last element # TODO: Make this function more efficient try: return str(self.toPlainText()).splitlines()[line_nb] except IndexError: return self.get_line_separator() def get_text(self, position_from, position_to): """ Return text between *position_from* and *position_to* Positions may be positions or 'sol', 'eol', 'sof', 'eof' or 'cursor' """ cursor = self.__select_text(position_from, position_to) text = str(cursor.selectedText()) all_text = position_from == "sof" and position_to == "eof" if text and not all_text: while text.endswith("\n"): text = text[:-1] while text.endswith("\u2029"): text = text[:-1] return text def get_character(self, position, offset=0): """Return character at *position* with the given offset.""" position = self.get_position(position) + offset cursor = self.textCursor() cursor.movePosition(QTextCursor.End) if position < cursor.position(): cursor.setPosition(position) cursor.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor) return str(cursor.selectedText()) else: return "" def insert_text(self, text): """Insert text at cursor position""" if not self.isReadOnly(): self.textCursor().insertText(text) def replace_text(self, position_from, position_to, text): """ :param position_from: :param position_to: :param text: """ cursor = self.__select_text(position_from, position_to) cursor.removeSelectedText() cursor.insertText(text) def remove_text(self, position_from, position_to): """ :param position_from: :param position_to: """ cursor = self.__select_text(position_from, position_to) cursor.removeSelectedText() def get_current_word(self): """Return current word, i.e. word at cursor position""" cursor = self.textCursor() if cursor.hasSelection(): # Removes the selection and moves the cursor to the left side # of the selection: this is required to be able to properly # select the whole word under cursor (otherwise, the same word is # not selected when the cursor is at the right side of it): cursor.setPosition(min([cursor.selectionStart(), cursor.selectionEnd()])) else: # Checks if the first character to the right is a white space # and if not, moves the cursor one word to the left (otherwise, # if the character to the left do not match the "word regexp" # (see below), the word to the left of the cursor won't be # selected), but only if the first character to the left is not a # white space too. def is_space(move): """ :param move: :return: """ curs = self.textCursor() curs.movePosition(move, QTextCursor.KeepAnchor) return not str(curs.selectedText()).strip() if is_space(QTextCursor.NextCharacter): if is_space(QTextCursor.PreviousCharacter): return cursor.movePosition(QTextCursor.WordLeft) cursor.select(QTextCursor.WordUnderCursor) text = str(cursor.selectedText()) # find a valid python variable name match = re.findall(r"([^\d\W]\w*)", text, re.UNICODE) if match: return match[0] def get_current_line(self): """Return current line's text""" cursor = self.textCursor() cursor.select(QTextCursor.BlockUnderCursor) return str(cursor.selectedText()) def get_current_line_to_cursor(self): """Return text from prompt to cursor""" return self.get_text(self.current_prompt_pos, "cursor") def get_line_number_at(self, coordinates): """Return line number at *coordinates* (QPoint)""" cursor = self.cursorForPosition(coordinates) return cursor.blockNumber() - 1 def get_line_at(self, coordinates): """Return line at *coordinates* (QPoint)""" cursor = self.cursorForPosition(coordinates) cursor.select(QTextCursor.BlockUnderCursor) return str(cursor.selectedText()).replace("\u2029", "") def get_word_at(self, coordinates): """Return word at *coordinates* (QPoint)""" cursor = self.cursorForPosition(coordinates) cursor.select(QTextCursor.WordUnderCursor) return str(cursor.selectedText()) def get_block_indentation(self, block_nb): """Return line indentation (character number)""" text = str(self.document().findBlockByNumber(block_nb).text()) text = text.replace("\t", " " * self.tab_stop_width_spaces) return len(text) - len(text.lstrip()) def get_selection_bounds(self): """Return selection bounds (block numbers)""" cursor = self.textCursor() start, end = cursor.selectionStart(), cursor.selectionEnd() block_start = self.document().findBlock(start) block_end = self.document().findBlock(end) return sorted([block_start.blockNumber(), block_end.blockNumber()]) # ------Text selection def has_selected_text(self): """Returns True if some text is selected""" return bool(str(self.textCursor().selectedText())) def get_selected_text(self): """ Return text selected by current text cursor, converted in unicode Replace the unicode line separator character ``\u2029`` by the line separator characters returned by :py:meth:`get_line_separator` """ return str(self.textCursor().selectedText()).replace( "\u2029", self.get_line_separator() ) def remove_selected_text(self): """Delete selected text""" self.textCursor().removeSelectedText() def replace(self, text, pattern=None): """Replace selected text by *text* If *pattern* is not None, replacing selected text using regular expression text substitution""" cursor = self.textCursor() cursor.beginEditBlock() if pattern is not None: seltxt = str(cursor.selectedText()) cursor.removeSelectedText() if pattern is not None: text = re.sub(str(pattern), str(text), str(seltxt)) cursor.insertText(text) cursor.endEditBlock() # ------Find/replace def find_multiline_pattern(self, regexp, cursor, findflag): """Reimplement QTextDocument's find method Add support for *multiline* regular expressions""" pattern = str(regexp.pattern()) text = str(self.toPlainText()) try: regobj = re.compile(pattern) except re.error: return if findflag & QTextDocument.FindBackward: # Find backward offset = min([cursor.selectionEnd(), cursor.selectionStart()]) text = text[:offset] matches = [_m for _m in regobj.finditer(text, 0, offset)] if matches: match = matches[-1] else: return else: # Find forward offset = max([cursor.selectionEnd(), cursor.selectionStart()]) match = regobj.search(text, offset) if match: pos1, pos2 = match.span() fcursor = self.textCursor() fcursor.setPosition(pos1) fcursor.setPosition(pos2, QTextCursor.KeepAnchor) return fcursor def find_text( self, text, changed=True, forward=True, case=False, words=False, regexp=False ): """Find text""" cursor = self.textCursor() findflag = QTextDocument.FindFlag() if not forward: findflag = findflag | QTextDocument.FindBackward if case: findflag = findflag | QTextDocument.FindCaseSensitively moves = [QTextCursor.NoMove] if forward: moves += [QTextCursor.NextWord, QTextCursor.Start] if changed: if str(cursor.selectedText()): new_position = min([cursor.selectionStart(), cursor.selectionEnd()]) cursor.setPosition(new_position) else: cursor.movePosition(QTextCursor.PreviousWord) else: moves += [QTextCursor.End] if regexp: text = str(text) else: text = re.escape(str(text)) pattern = QRegularExpression("\\b{}\\b".format(text) if words else text) if case: pattern.setPatternOptions(QRegularExpression.CaseInsensitiveOption) for move in moves: cursor.movePosition(move) if regexp and "\\n" in text: # Multiline regular expression found_cursor = self.find_multiline_pattern(pattern, cursor, findflag) else: # Single line find: using the QTextDocument's find function, # probably much more efficient than ours found_cursor = self.document().find(pattern, cursor, findflag) if found_cursor is not None and not found_cursor.isNull(): self.setTextCursor(found_cursor) return True return False def is_editor(self): """Needs to be overloaded in the codeeditor where it will be True""" return False def get_number_matches(self, pattern, source_text="", case=False, regexp=False): """Get the number of matches for the searched text.""" pattern = str(pattern) if not pattern: return 0 if not regexp: pattern = re.escape(pattern) if not source_text: source_text = str(self.toPlainText()) try: if case: regobj = re.compile(pattern) else: regobj = re.compile(pattern, re.IGNORECASE) except re.error: return None number_matches = 0 for match in regobj.finditer(source_text): number_matches += 1 return number_matches def get_match_number(self, pattern, case=False, regexp=False): """Get number of the match for the searched text.""" position = self.textCursor().position() source_text = self.get_text(position_from="sof", position_to=position) match_number = self.get_number_matches( pattern, source_text=source_text, case=case, regexp=regexp ) return match_number class TracebackLinksMixin(object): """ """ QT_CLASS = None go_to_error = None def __init__(self): self.__cursor_changed = False self.setMouseTracking(True) # ------Mouse events def mouseReleaseEvent(self, event): """Go to error""" self.QT_CLASS.mouseReleaseEvent(self, event) text = self.get_line_at(event.pos()) if get_error_match(text) and not self.has_selected_text(): if self.go_to_error is not None: self.go_to_error.emit(text) def mouseMoveEvent(self, event): """Show Pointing Hand Cursor on error messages""" text = self.get_line_at(event.pos()) if get_error_match(text): if not self.__cursor_changed: QApplication.setOverrideCursor(QCursor(Qt.PointingHandCursor)) self.__cursor_changed = True event.accept() return if self.__cursor_changed: QApplication.restoreOverrideCursor() self.__cursor_changed = False self.QT_CLASS.mouseMoveEvent(self, event) def leaveEvent(self, event): """If cursor has not been restored yet, do it now""" if self.__cursor_changed: QApplication.restoreOverrideCursor() self.__cursor_changed = False self.QT_CLASS.leaveEvent(self, event) class GetHelpMixin(object): """ """ def __init__(self): self.help = None self.help_enabled = False def set_help(self, help_plugin): """Set Help DockWidget reference""" self.help = help_plugin def set_help_enabled(self, state): """ :param state: """ self.help_enabled = state def inspect_current_object(self): """ """ text = "" text1 = self.get_text("sol", "cursor") tl1 = re.findall(r"([a-zA-Z_]+[0-9a-zA-Z_\.]*)", text1) if tl1 and text1.endswith(tl1[-1]): text += tl1[-1] text2 = self.get_text("cursor", "eol") tl2 = re.findall(r"([0-9a-zA-Z_\.]+[0-9a-zA-Z_\.]*)", text2) if tl2 and text2.startswith(tl2[0]): text += tl2[0] if text: self.show_object_info(text, force=True) def show_object_info(self, text, call=False, force=False): """Show signature calltip and/or docstring in the Help plugin""" text = str(text) # Show docstring help_enabled = self.help_enabled or force if force and self.help is not None: self.help.dockwidget.setVisible(True) self.help.dockwidget.raise_() if ( help_enabled and (self.help is not None) and (self.help.dockwidget.isVisible()) ): # Help widget exists and is visible if hasattr(self, "get_doc"): self.help.set_shell(self) else: self.help.set_shell(self.parent()) self.help.set_object_text(text, ignore_unknown=False) self.setFocus() # if help was not at top level, raising it to # top will automatically give it focus because of # the visibility_changed signal, so we must give # focus back to shell # Show calltip if call and self.calltips: # Display argument list if this is a function call iscallable = self.iscallable(text) if iscallable is not None: if iscallable: arglist = self.get_arglist(text) name = text.split(".")[-1] argspec = signature = "" is_signature = True if isinstance(arglist, bool): arglist = [] if arglist: argspec = "(" + "".join(arglist) + ")" else: doc = self.get__doc__(text) if doc is not None: # This covers cases like np.abs, whose docstring is # the same as np.absolute and because of that a # proper signature can't be obtained correctly argspec = getargspecfromtext(doc) if not argspec: signature = getsignaturefromtext(doc, name) if not signature: signature = doc is_signature = False if argspec or signature: if argspec: tiptext = name + argspec else: tiptext = signature self.show_calltip( _("Arguments"), tiptext, signature=is_signature, color="#2D62FF", ) def get_last_obj(self, last=False): """ Return the last valid object on the current line """ return getobj(self.get_current_line_to_cursor(), last=last) class SaveHistoryMixin(object): """ """ INITHISTORY = None SEPARATOR = None HISTORY_FILENAMES = [] append_to_history = None def __init__(self, history_filename=""): self.history_filename = history_filename self.create_history_filename() def create_history_filename(self): """Create history_filename with INITHISTORY if it doesn't exist.""" if self.history_filename and not osp.isfile(self.history_filename): try: encoding.writelines(self.INITHISTORY, self.history_filename) except EnvironmentError: pass def add_to_history(self, command): """Add command to history""" command = str(command) if command in ["", "\n"] or command.startswith("Traceback"): return if command.endswith("\n"): command = command[:-1] self.histidx = None if len(self.history) > 0 and self.history[-1] == command: return self.history.append(command) text = os.linesep + command # When the first entry will be written in history file, # the separator will be append first: if self.history_filename not in self.HISTORY_FILENAMES: self.HISTORY_FILENAMES.append(self.history_filename) text = self.SEPARATOR + text # Needed to prevent errors when writing history to disk # See issue 6431 try: encoding.write(text, self.history_filename, mode="ab") except EnvironmentError: pass if self.append_to_history is not None: self.append_to_history.emit(self.history_filename, text) class BrowseHistoryMixin(object): """ """ def __init__(self): self.history = [] self.histidx = None self.hist_wholeline = False def clear_line(self): """Clear current line (without clearing console prompt)""" self.remove_text(self.current_prompt_pos, "eof") def browse_history(self, backward): """Browse history""" if self.is_cursor_before("eol") and self.hist_wholeline: self.hist_wholeline = False tocursor = self.get_current_line_to_cursor() text, self.histidx = self.find_in_history(tocursor, self.histidx, backward) if text is not None: if self.hist_wholeline: self.clear_line() self.insert_text(text) else: cursor_position = self.get_position("cursor") # Removing text from cursor to the end of the line self.remove_text("cursor", "eol") # Inserting history text self.insert_text(text) self.set_cursor_position(cursor_position) def find_in_history(self, tocursor, start_idx, backward): """Find text 'tocursor' in history, from index 'start_idx'""" if start_idx is None: start_idx = len(self.history) # Finding text in history step = -1 if backward else 1 idx = start_idx if len(tocursor) == 0 or self.hist_wholeline: idx += step if idx >= len(self.history) or len(self.history) == 0: return "", len(self.history) elif idx < 0: idx = 0 self.hist_wholeline = True return self.history[idx], idx else: for index in range(len(self.history)): idx = (start_idx + step * (index + 1)) % len(self.history) entry = self.history[idx] if entry.startswith(tocursor): return entry[len(tocursor) :], idx else: return None, start_idx def reset_search_pos(self): """Reset the position from which to search the history""" self.histidx = None ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/widgets/console/shell.py0000644000175100001770000010005614654363416020772 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Copyright ┬й Spyder Project Contributors # Licensed under the terms of the MIT License # (see spyder/__init__.py for details) """Shell widgets: base, python and terminal""" # pylint: disable=C0103 # pylint: disable=R0903 # pylint: disable=R0911 # pylint: disable=R0201 import builtins import keyword import locale import os import os.path as osp import re import sys import time from qtpy.compat import getsavefilename from qtpy.QtCore import Property, QCoreApplication, Qt, QTimer, Signal, Slot from qtpy.QtGui import QKeyEvent, QTextCharFormat, QTextCursor from qtpy.QtWidgets import QApplication, QMenu, QToolTip from guidata.config import CONF, _ from guidata.configtools import get_icon from guidata.qthelpers import add_actions, create_action, keybinding from guidata.utils import encoding from guidata.widgets.console.base import ConsoleBaseWidget from guidata.widgets.console.mixins import ( BrowseHistoryMixin, GetHelpMixin, SaveHistoryMixin, TracebackLinksMixin, ) DEBUG = False STDERR = sys.stderr def tuple2keyevent(past_event): """Convert tuple into a QKeyEvent instance""" return QKeyEvent(*past_event) def restore_keyevent(event): """ :param event: :return: """ if isinstance(event, tuple): _, key, modifiers, text, _, _ = event event = tuple2keyevent(event) else: text = event.text() modifiers = event.modifiers() key = event.key() ctrl = modifiers & Qt.ControlModifier shift = modifiers & Qt.ShiftModifier return event, text, key, ctrl, shift class ShellBaseWidget(ConsoleBaseWidget, SaveHistoryMixin, BrowseHistoryMixin): """ Shell base widget """ redirect_stdio = Signal(bool) sig_keyboard_interrupt = Signal() execute = Signal(str) append_to_history = Signal(str, str) def __init__(self, parent, profile=False, initial_message=None, read_only=False): """ parent : specifies the parent widget """ ConsoleBaseWidget.__init__(self, parent) SaveHistoryMixin.__init__(self) BrowseHistoryMixin.__init__(self) # Read-only console is not the more used case, but it may be useful in # some cases (e.g. when using the console as a log window) self.setReadOnly(read_only) self.historylog_filename = "history.log" # Prompt position: tuple (line, index) self.current_prompt_pos = None self.new_input_line = True # Context menu self.menu = None self.setup_context_menu() # Simple profiling test self.profile = profile # Buffer to increase performance of write/flush operations self.__buffer = [] if initial_message: self.__buffer.append(initial_message) self.__timestamp = 0.0 self.__flushtimer = QTimer(self) self.__flushtimer.setSingleShot(True) self.__flushtimer.timeout.connect(self.flush) # Give focus to widget self.setFocus() # Cursor width self.setCursorWidth(CONF.get("console", "cursor/width")) def toggle_wrap_mode(self, enable): """Enable/disable wrap mode""" self.set_wrap_mode("character" if enable else None) def set_font(self, font): """Set shell styles font""" self.setFont(font) self.set_pythonshell_font(font) cursor = self.textCursor() cursor.select(QTextCursor.Document) charformat = QTextCharFormat() charformat.setFontFamilies([font.family()]) charformat.setFontPointSize(font.pointSize()) cursor.mergeCharFormat(charformat) # ------ Context menu def setup_context_menu(self): """Setup shell context menu""" self.menu = QMenu(self) self.cut_action = create_action( self, _("Cut"), shortcut=keybinding("Cut"), icon=get_icon("editcut.png"), triggered=self.cut, ) self.copy_action = create_action( self, _("Copy"), shortcut=keybinding("Copy"), icon=get_icon("editcopy.png"), triggered=self.copy, ) paste_action = create_action( self, _("Paste"), shortcut=keybinding("Paste"), icon=get_icon("editpaste.png"), triggered=self.paste, ) save_action = create_action( self, _("Save history log..."), icon=get_icon("filesave.png"), tip=_( "Save current history log (i.e. all " "inputs and outputs) in a text file" ), triggered=self.save_historylog, ) self.delete_action = create_action( self, _("Delete"), shortcut=keybinding("Delete"), icon=get_icon("editdelete.png"), triggered=self.delete, ) selectall_action = create_action( self, _("Select All"), shortcut=keybinding("SelectAll"), icon=get_icon("selectall.png"), triggered=self.selectAll, ) add_actions( self.menu, ( self.cut_action, self.copy_action, paste_action, self.delete_action, None, selectall_action, None, save_action, ), ) def contextMenuEvent(self, event): """Reimplement Qt method""" state = self.has_selected_text() self.copy_action.setEnabled(state) self.cut_action.setEnabled(state) self.delete_action.setEnabled(state) self.menu.popup(event.globalPos()) event.accept() # ------ Input buffer def get_current_line_from_cursor(self): """ :return: """ return self.get_text("cursor", "eof") def _select_input(self): """Select current line (without selecting console prompt)""" line, index = self.get_position("eof") if self.current_prompt_pos is None: pline, pindex = line, index else: pline, pindex = self.current_prompt_pos self.setSelection(pline, pindex, line, index) @Slot() def clear_terminal(self): """ Clear terminal window Child classes reimplement this method to write prompt """ self.clear() # The buffer being edited def _set_input_buffer(self, text): """Set input buffer""" if self.current_prompt_pos is not None: self.replace_text(self.current_prompt_pos, "eol", text) else: self.insert(text) self.set_cursor_position("eof") def _get_input_buffer(self): """Return input buffer""" input_buffer = "" if self.current_prompt_pos is not None: input_buffer = self.get_text(self.current_prompt_pos, "eol") input_buffer = input_buffer.replace(os.linesep, "\n") return input_buffer input_buffer = Property("QString", _get_input_buffer, _set_input_buffer) # ------ Prompt def new_prompt(self, prompt): """ Print a new prompt and save its (line, index) position """ if self.get_cursor_line_column()[1] != 0: self.write("\n") self.write(prompt, prompt=True) # now we update our cursor giving end of prompt self.current_prompt_pos = self.get_position("cursor") self.ensureCursorVisible() self.new_input_line = False def check_selection(self): """ Check if selected text is r/w, otherwise remove read-only parts of selection """ if self.current_prompt_pos is None: self.set_cursor_position("eof") else: self.truncate_selection(self.current_prompt_pos) # ------ Copy / Keyboard interrupt @Slot() def copy(self): """Copy text to clipboard... or keyboard interrupt""" if self.has_selected_text(): ConsoleBaseWidget.copy(self) elif not sys.platform == "darwin": self.interrupt() def interrupt(self): """Keyboard interrupt""" self.sig_keyboard_interrupt.emit() @Slot() def cut(self): """Cut text""" self.check_selection() if self.has_selected_text(): ConsoleBaseWidget.cut(self) @Slot() def delete(self): """Remove selected text""" self.check_selection() if self.has_selected_text(): ConsoleBaseWidget.remove_selected_text(self) @Slot() def save_historylog(self): """Save current history log (all text in console)""" title = _("Save history log") self.redirect_stdio.emit(False) filename, _selfilter = getsavefilename( self, title, self.historylog_filename, "%s (*.log)" % _("History logs") ) self.redirect_stdio.emit(True) if filename: filename = osp.normpath(filename) try: encoding.write(str(self.get_text_with_eol()), filename) self.historylog_filename = filename except EnvironmentError: pass # ------ Basic keypress event handler def on_enter(self, command): """on_enter""" self.execute_command(command) def execute_command(self, command): """ :param command: """ self.execute.emit(command) self.add_to_history(command) self.new_input_line = True def on_new_line(self): """On new input line""" self.set_cursor_position("eof") self.current_prompt_pos = self.get_position("cursor") self.new_input_line = False @Slot() def paste(self): """Reimplemented slot to handle multiline paste action""" if self.new_input_line: self.on_new_line() ConsoleBaseWidget.paste(self) def keyPressEvent(self, event): """ Reimplement Qt Method Basic keypress event handler (reimplemented in InternalShell to add more sophisticated features) """ if self.preprocess_keyevent(event): # Event was accepted in self.preprocess_keyevent return self.postprocess_keyevent(event) def preprocess_keyevent(self, event): """Pre-process keypress event: return True if event is accepted, false otherwise""" # Copy must be done first to be able to copy read-only text parts # (otherwise, right below, we would remove selection # if not on current line) ctrl = event.modifiers() & Qt.ControlModifier meta = event.modifiers() & Qt.MetaModifier # meta=ctrl in OSX if event.key() == Qt.Key_C and ( (Qt.MetaModifier | Qt.ControlModifier) & event.modifiers() ): if meta and sys.platform == "darwin": self.interrupt() elif ctrl: self.copy() event.accept() return True if self.new_input_line and ( len(event.text()) or event.key() in (Qt.Key_Up, Qt.Key_Down, Qt.Key_Left, Qt.Key_Right) ): self.on_new_line() return False def postprocess_keyevent(self, event): """Post-process keypress event: in InternalShell, this is method is called when shell is ready""" event, text, key, ctrl, shift = restore_keyevent(event) # Is cursor on the last line? and after prompt? if len(text): # XXX: Shouldn't it be: `if len(unicode(text).strip(os.linesep))` ? if self.has_selected_text(): self.check_selection() self.restrict_cursor_position(self.current_prompt_pos, "eof") cursor_position = self.get_position("cursor") if key in (Qt.Key_Return, Qt.Key_Enter): if self.is_cursor_on_last_line(): self._key_enter() # add and run selection else: self.insert_text(self.get_selected_text(), at_end=True) elif key == Qt.Key_Insert and not shift and not ctrl: self.setOverwriteMode(not self.overwriteMode()) elif key == Qt.Key_Delete: if self.has_selected_text(): self.check_selection() self.remove_selected_text() elif self.is_cursor_on_last_line(): self.stdkey_clear() elif key == Qt.Key_Backspace: self._key_backspace(cursor_position) elif key == Qt.Key_Tab: self._key_tab() elif key == Qt.Key_Space and ctrl: self._key_ctrl_space() elif key == Qt.Key_Left: if self.current_prompt_pos == cursor_position: # Avoid moving cursor on prompt return method = ( self.extend_selection_to_next if shift else self.move_cursor_to_next ) method("word" if ctrl else "character", direction="left") elif key == Qt.Key_Right: if self.is_cursor_at_end(): return method = ( self.extend_selection_to_next if shift else self.move_cursor_to_next ) method("word" if ctrl else "character", direction="right") elif (key == Qt.Key_Home) or ((key == Qt.Key_Up) and ctrl): self._key_home(shift, ctrl) elif (key == Qt.Key_End) or ((key == Qt.Key_Down) and ctrl): self._key_end(shift, ctrl) elif key == Qt.Key_Up: if not self.is_cursor_on_last_line(): self.set_cursor_position("eof") y_cursor = self.get_coordinates(cursor_position)[1] y_prompt = self.get_coordinates(self.current_prompt_pos)[1] if y_cursor > y_prompt: self.stdkey_up(shift) else: self.browse_history(backward=True) elif key == Qt.Key_Down: if not self.is_cursor_on_last_line(): self.set_cursor_position("eof") y_cursor = self.get_coordinates(cursor_position)[1] y_end = self.get_coordinates("eol")[1] if y_cursor < y_end: self.stdkey_down(shift) else: self.browse_history(backward=False) elif key in (Qt.Key_PageUp, Qt.Key_PageDown): # XXX: Find a way to do this programmatically instead of calling # widget keyhandler (this won't work if the *event* is coming from # the event queue - i.e. if the busy buffer is ever implemented) ConsoleBaseWidget.keyPressEvent(self, event) elif key == Qt.Key_Escape and shift: self.clear_line() elif key == Qt.Key_Escape: self._key_escape() elif key == Qt.Key_L and ctrl: self.clear_terminal() elif key == Qt.Key_V and ctrl: self.paste() elif key == Qt.Key_X and ctrl: self.cut() elif key == Qt.Key_Z and ctrl: self.undo() elif key == Qt.Key_Y and ctrl: self.redo() elif key == Qt.Key_A and ctrl: self.selectAll() elif key == Qt.Key_Question and not self.has_selected_text(): self._key_question(text) elif key == Qt.Key_ParenLeft and not self.has_selected_text(): self._key_parenleft(text) elif key == Qt.Key_Period and not self.has_selected_text(): self._key_period(text) elif len(text) and not self.isReadOnly(): self.hist_wholeline = False self.insert_text(text) self._key_other(text) else: # Let the parent widget handle the key press event ConsoleBaseWidget.keyPressEvent(self, event) # ------ Key handlers def _key_enter(self): command = self.input_buffer self.insert_text("\n", at_end=True) self.on_enter(command) self.flush() def _key_other(self, text): raise NotImplementedError def _key_backspace(self, cursor_position): raise NotImplementedError def _key_tab(self): raise NotImplementedError def _key_ctrl_space(self): raise NotImplementedError def _key_home(self, shift, ctrl): if self.is_cursor_on_last_line(): self.stdkey_home(shift, ctrl, self.current_prompt_pos) def _key_end(self, shift, ctrl): if self.is_cursor_on_last_line(): self.stdkey_end(shift, ctrl) def _key_pageup(self): raise NotImplementedError def _key_pagedown(self): raise NotImplementedError def _key_escape(self): raise NotImplementedError def _key_question(self, text): raise NotImplementedError def _key_parenleft(self, text): raise NotImplementedError def _key_period(self, text): raise NotImplementedError # ------ History Management def load_history(self): """Load history from a .py file in user home directory""" if osp.isfile(self.history_filename): rawhistory, _ = encoding.readlines(self.history_filename) rawhistory = [line.replace("\n", "") for line in rawhistory] if rawhistory[1] != self.INITHISTORY[1]: rawhistory[1] = self.INITHISTORY[1] else: rawhistory = self.INITHISTORY history = [line for line in rawhistory if line and not line.startswith("#")] # Truncating history to X entries: while len(history) >= CONF.get("historylog", "max_entries"): del history[0] while rawhistory[0].startswith("#"): del rawhistory[0] del rawhistory[0] # Saving truncated history: try: encoding.writelines(rawhistory, self.history_filename) except EnvironmentError: pass return history # ------ Simulation standards input/output def write_error(self, text): """Simulate stderr""" self.flush() self.write(text, flush=True, error=True) if DEBUG: STDERR.write(text) def write(self, text, flush=False, error=False, prompt=False): """Simulate stdout and stderr""" if prompt: self.flush() if not isinstance(text, str): # This test is useful to discriminate QStrings from decoded str text = str(text) self.__buffer.append(text) ts = time.time() if flush or prompt: self.flush(error=error, prompt=prompt) elif ts - self.__timestamp > 0.05: self.flush(error=error) self.__timestamp = ts # Timer to flush strings cached by last write() operation in series self.__flushtimer.start(50) def flush(self, error=False, prompt=False): """Flush buffer, write text to console""" # Fix for Spyder Issue #2452 try: text = "".join(self.__buffer) except TypeError: text = b"".join(self.__buffer) try: text = text.decode(locale.getpreferredencoding()) except UnicodeDecodeError: pass self.__buffer = [] try: self.insert_text(text, at_end=True, error=error, prompt=prompt) QCoreApplication.processEvents() self.repaint() # Clear input buffer: self.new_input_line = True except RuntimeError: # RuntimeError: wrapped C/C++ object of type QTextCursor has been deleted # # (This happens when the shell is closed while a thread is writing. # For example, when closing host application with an active logging # connected to the shell) pass # ------ Text Insertion def insert_text(self, text, at_end=False, error=False, prompt=False): """ Insert text at the current cursor position or at the end of the command line """ if at_end: # Insert text at the end of the command line self.append_text_to_shell(text, error, prompt) else: # Insert text at current cursor position ConsoleBaseWidget.insert_text(self, text) # ------ Re-implemented Qt Methods def focusNextPrevChild(self, next): """ Reimplemented to stop Tab moving to the next window """ if next: return False return ConsoleBaseWidget.focusNextPrevChild(self, next) # ------ Drag and drop def dragEnterEvent(self, event): """Drag and Drop - Enter event""" event.setAccepted(event.mimeData().hasFormat("text/plain")) def dragMoveEvent(self, event): """Drag and Drop - Move event""" if event.mimeData().hasFormat("text/plain"): event.setDropAction(Qt.MoveAction) event.accept() else: event.ignore() def dropEvent(self, event): """Drag and Drop - Drop event""" if event.mimeData().hasFormat("text/plain"): text = str(event.mimeData().text()) if self.new_input_line: self.on_new_line() self.insert_text(text, at_end=True) self.setFocus() event.setDropAction(Qt.MoveAction) event.accept() else: event.ignore() def drop_pathlist(self, pathlist): """Drop path list""" raise NotImplementedError # Example how to debug complex interclass call chains: # # from spyder.utils.debug import log_methods_calls # log_methods_calls('log.log', ShellBaseWidget) class PythonShellWidget(TracebackLinksMixin, ShellBaseWidget, GetHelpMixin): """Python shell widget""" QT_CLASS = ShellBaseWidget INITHISTORY = [ "# -*- coding: utf-8 -*-", "# *** Spyder Python Console History Log ***", ] SEPARATOR = "%s##---(%s)---" % (os.linesep * 2, time.ctime()) go_to_error = Signal(str) def __init__(self, parent, profile=False, initial_message=None, read_only=False): ShellBaseWidget.__init__(self, parent, profile, initial_message, read_only) TracebackLinksMixin.__init__(self) GetHelpMixin.__init__(self) # ------ Context menu def setup_context_menu(self): """Reimplements ShellBaseWidget method""" ShellBaseWidget.setup_context_menu(self) self.copy_without_prompts_action = create_action( self, _("Copy without prompts"), icon=get_icon("copywop.png"), triggered=self.copy_without_prompts, ) actions = [self.copy_without_prompts_action] if not self.isReadOnly(): clear_line_action = create_action( self, _("Clear line"), icon=get_icon("editdelete.png"), tip=_("Clear line"), triggered=self.clear_line, ) clear_action = create_action( self, _("Clear shell"), icon=get_icon("editclear.png"), tip=_("Clear shell contents " "('cls' command)"), triggered=self.clear_terminal, ) actions += [clear_line_action, clear_action] add_actions(self.menu, actions) def contextMenuEvent(self, event): """Reimplements ShellBaseWidget method""" state = self.has_selected_text() self.copy_without_prompts_action.setEnabled(state) ShellBaseWidget.contextMenuEvent(self, event) @Slot() def copy_without_prompts(self): """Copy text to clipboard without prompts""" text = self.get_selected_text() lines = text.split(os.linesep) for index, line in enumerate(lines): if line.startswith(">>> ") or line.startswith("... "): lines[index] = line[4:] text = os.linesep.join(lines) QApplication.clipboard().setText(text) # ------ Key handlers def postprocess_keyevent(self, event): """Process keypress event""" ShellBaseWidget.postprocess_keyevent(self, event) if QToolTip.isVisible(): _event, _text, key, _ctrl, _shift = restore_keyevent(event) self.hide_tooltip_if_necessary(key) def _key_other(self, text): """1 character key""" if self.is_completion_widget_visible(): self.completion_text += text def _key_backspace(self, cursor_position): """Action for Backspace key""" if self.has_selected_text(): self.check_selection() self.remove_selected_text() elif self.current_prompt_pos == cursor_position: # Avoid deleting prompt return elif self.is_cursor_on_last_line(): self.stdkey_backspace() if self.is_completion_widget_visible(): # Removing only last character because if there was a selection # the completion widget would have been canceled self.completion_text = self.completion_text[:-1] def _key_tab(self): """Action for TAB key""" if self.is_cursor_on_last_line(): empty_line = not self.get_current_line_to_cursor().strip() if empty_line: self.stdkey_tab() else: self.show_code_completion(automatic=False) def _key_ctrl_space(self): """Action for Ctrl+Space""" if not self.is_completion_widget_visible(): self.show_code_completion(automatic=False) def _key_pageup(self): """Action for PageUp key""" pass def _key_pagedown(self): """Action for PageDown key""" pass def _key_escape(self): """Action for ESCAPE key""" if self.is_completion_widget_visible(): self.hide_completion_widget() def _key_question(self, text): """Action for '?'""" if self.get_current_line_to_cursor(): last_obj = self.get_last_obj() if last_obj and not last_obj.isdigit(): self.show_object_info(last_obj) self.insert_text(text) # In case calltip and completion are shown at the same time: if self.is_completion_widget_visible(): self.completion_text += "?" def _key_parenleft(self, text): """Action for '('""" self.hide_completion_widget() if self.get_current_line_to_cursor(): last_obj = self.get_last_obj() if last_obj and not last_obj.isdigit(): self.insert_text(text) self.show_object_info(last_obj, call=True) return self.insert_text(text) def _key_period(self, text): """Action for '.'""" self.insert_text(text) if self.codecompletion_auto: # Enable auto-completion only if last token isn't a float last_obj = self.get_last_obj() if last_obj and not last_obj.isdigit(): self.show_code_completion(automatic=True) # ------ Paste def paste(self): """Reimplemented slot to handle multiline paste action""" text = str(QApplication.clipboard().text()) if len(text.splitlines()) > 1: # Multiline paste if self.new_input_line: self.on_new_line() self.remove_selected_text() # Remove selection, eventually end = self.get_current_line_from_cursor() lines = self.get_current_line_to_cursor() + text + end self.clear_line() self.execute_lines(lines) self.move_cursor(-len(end)) else: # Standard paste ShellBaseWidget.paste(self) # ------ Code Completion / Calltips # Methods implemented in child class: # (e.g. InternalShell) def get_dir(self, objtxt): """Return dir(object)""" raise NotImplementedError def get_module_completion(self, objtxt): """Return module completion list associated to object name""" pass def get_globals_keys(self): """Return shell globals() keys""" raise NotImplementedError def get_cdlistdir(self): """Return shell current directory list dir""" raise NotImplementedError def iscallable(self, objtxt): """Is object callable?""" raise NotImplementedError def get_arglist(self, objtxt): """Get func/method argument list""" raise NotImplementedError def get__doc__(self, objtxt): """Get object __doc__""" raise NotImplementedError def get_doc(self, objtxt): """Get object documentation dictionary""" raise NotImplementedError def get_source(self, objtxt): """Get object source""" raise NotImplementedError def is_defined(self, objtxt, force_import=False): """Return True if object is defined""" raise NotImplementedError def show_code_completion(self, automatic): """Display a completion list based on the current line""" # Note: unicode conversion is needed only for ExternalShellBase text = str(self.get_current_line_to_cursor()) last_obj = self.get_last_obj() if not text: return if text.startswith("import "): # pylint: disable=assignment-from-no-return obj_list = self.get_module_completion(text) words = text.split(" ") if "," in words[-1]: words = words[-1].split(",") self.show_completion_list( obj_list, completion_text=words[-1], automatic=automatic ) return elif text.startswith("from "): # pylint: disable=assignment-from-no-return obj_list = self.get_module_completion(text) if obj_list is None: return words = text.split(" ") if "(" in words[-1]: words = words[:-2] + words[-1].split("(") if "," in words[-1]: words = words[:-2] + words[-1].split(",") self.show_completion_list( obj_list, completion_text=words[-1], automatic=automatic ) return obj_dir = self.get_dir(last_obj) if last_obj and obj_dir and text.endswith("."): self.show_completion_list(obj_dir, automatic=automatic) return # Builtins and globals if ( not text.endswith(".") and last_obj and re.match(r"[a-zA-Z_0-9]*$", last_obj) ): b_k_g = dir(builtins) + self.get_globals_keys() + keyword.kwlist for objname in b_k_g: if objname.startswith(last_obj) and objname != last_obj: self.show_completion_list( b_k_g, completion_text=last_obj, automatic=automatic ) return else: return # Looking for an incomplete completion if last_obj is None: last_obj = text dot_pos = last_obj.rfind(".") if dot_pos != -1: if dot_pos == len(last_obj) - 1: completion_text = "" else: completion_text = last_obj[dot_pos + 1 :] last_obj = last_obj[:dot_pos] completions = self.get_dir(last_obj) if completions is not None: self.show_completion_list( completions, completion_text=completion_text, automatic=automatic ) return # Looking for ' or ": filename completion q_pos = max([text.rfind("'"), text.rfind('"')]) if q_pos != -1: completions = self.get_cdlistdir() if completions: self.show_completion_list( completions, completion_text=text[q_pos + 1 :], automatic=automatic ) return # ------ Drag'n Drop def drop_pathlist(self, pathlist): """Drop path list""" if pathlist: files = ["r'%s'" % path for path in pathlist] if len(files) == 1: text = files[0] else: text = "[" + ", ".join(files) + "]" if self.new_input_line: self.on_new_line() self.insert_text(text) self.setFocus() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/widgets/console/terminal.py0000644000175100001770000000760614654363416021505 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Copyright ┬й Spyder Project Contributors # Licensed under the terms of the MIT License # (see spyder/__init__.py for details) """Terminal emulation tools""" import os class ANSIEscapeCodeHandler(object): """ANSI Escape sequences handler""" if os.name == "nt": # Windows terminal colors: ANSI_COLORS = ( # Normal, Bright/Light ("#000000", "#808080"), # 0: black ("#800000", "#ff0000"), # 1: red ("#008000", "#00ff00"), # 2: green ("#808000", "#ffff00"), # 3: yellow ("#000080", "#0000ff"), # 4: blue ("#800080", "#ff00ff"), # 5: magenta ("#008080", "#00ffff"), # 6: cyan ("#c0c0c0", "#ffffff"), # 7: white ) elif os.name == "mac": # Terminal.app colors: ANSI_COLORS = ( # Normal, Bright/Light ("#000000", "#818383"), # 0: black ("#C23621", "#FC391F"), # 1: red ("#25BC24", "#25BC24"), # 2: green ("#ADAD27", "#EAEC23"), # 3: yellow ("#492EE1", "#5833FF"), # 4: blue ("#D338D3", "#F935F8"), # 5: magenta ("#33BBC8", "#14F0F0"), # 6: cyan ("#CBCCCD", "#E9EBEB"), # 7: white ) else: # xterm colors: ANSI_COLORS = ( # Normal, Bright/Light ("#000000", "#7F7F7F"), # 0: black ("#CD0000", "#ff0000"), # 1: red ("#00CD00", "#00ff00"), # 2: green ("#CDCD00", "#ffff00"), # 3: yellow ("#0000EE", "#5C5CFF"), # 4: blue ("#CD00CD", "#ff00ff"), # 5: magenta ("#00CDCD", "#00ffff"), # 6: cyan ("#E5E5E5", "#ffffff"), # 7: white ) def __init__(self): self.intensity = 0 self.italic = None self.bold = None self.underline = None self.foreground_color = None self.background_color = None self.default_foreground_color = 30 self.default_background_color = 47 def set_code(self, code): """ :param code: """ assert isinstance(code, int) if code == 0: # Reset all settings self.reset() elif code == 1: # Text color intensity self.intensity = 1 # The following line is commented because most terminals won't # change the font weight, against ANSI standard recommendation: # self.bold = True elif code == 3: # Italic on self.italic = True elif code == 4: # Underline simple self.underline = True elif code == 22: # Normal text color intensity self.intensity = 0 self.bold = False elif code == 23: # No italic self.italic = False elif code == 24: # No underline self.underline = False elif code >= 30 and code <= 37: # Text color self.foreground_color = code elif code == 39: # Default text color self.foreground_color = self.default_foreground_color elif code >= 40 and code <= 47: # Background color self.background_color = code elif code == 49: # Default background color self.background_color = self.default_background_color self.set_style() def set_style(self): """ Set font style with the following attributes: 'foreground_color', 'background_color', 'italic', 'bold' and 'underline' """ raise NotImplementedError def reset(self): """ """ self.current_format = None self.intensity = 0 self.italic = False self.bold = False self.underline = False self.foreground_color = None self.background_color = None ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/widgets/dataframeeditor.py0000644000175100001770000007570014654363416021363 0ustar00runnerdocker# -*- coding: utf-8 -*- # ----------------------------------------------------------------------------- # Copyright (c) 2011-2012 Lambda Foundry, Inc. and PyData Development Team # Copyright (c) 2013 Jev Kuznetsov and contributors # Copyright (c) 2014- Spyder Project Contributors # # Distributed under the terms of the New BSD License # (BSD 3-clause; see NOTICE.txt in the Spyder root directory for details). # ----------------------------------------------------------------------------- """ guidata.widgets.dataframeeditor =============================== This package provides a DataFrameModel based on the class ArrayModel from array editor and the class DataFrameModel from the pandas project. Present in pandas.sandbox.qtpandas in v0.13.1. .. autoclass:: DataFrameEditor :show-inheritance: :members: Originally based on pandas/sandbox/qtpandas.py of the `pandas project `_. The current version is qtpandas/models/DataFrameModel.py of the `QtPandas project `_. """ import io import numpy as np from pandas import DataFrame, DatetimeIndex, Series from qtpy.QtCore import QAbstractTableModel, QModelIndex, Qt, Signal, Slot from qtpy.QtGui import QColor, QCursor, QKeySequence from qtpy.QtWidgets import ( QAbstractItemView, QApplication, QCheckBox, QDialog, QGridLayout, QHBoxLayout, QHeaderView, QInputDialog, QLineEdit, QMenu, QMessageBox, QPushButton, QShortcut, QTableView, ) from guidata.config import CONF, _ from guidata.configtools import get_font, get_icon from guidata.qthelpers import ( add_actions, create_action, keybinding, win32_fix_title_bar_background, ) from guidata.widgets.arrayeditor.utils import get_idx_rect try: from pandas._libs.tslib import OutOfBoundsDatetime except ImportError: # For pandas version < 0.20 from pandas.tslib import OutOfBoundsDatetime # Supported Numbers and complex numbers REAL_NUMBER_TYPES = (float, int, np.int64, np.int32) COMPLEX_NUMBER_TYPES = (complex, np.complex64, np.complex128) # Used to convert bool intrance to false since bool('False') will return True _bool_false = ["false", "f", "0", "0.", "0.0", " "] # Default format for data frames with floats DEFAULT_FORMAT = "%.6g" # Limit at which dataframe is considered so large that it is loaded on demand LARGE_SIZE = 5e5 LARGE_NROWS = 1e5 LARGE_COLS = 60 # Background colours BACKGROUND_NUMBER_MINHUE = 0.66 # hue for largest number BACKGROUND_NUMBER_HUERANGE = 0.33 # (hue for smallest) minus (hue for largest) BACKGROUND_NUMBER_SATURATION = 0.7 BACKGROUND_NUMBER_VALUE = 1.0 BACKGROUND_NUMBER_ALPHA = 0.6 BACKGROUND_NONNUMBER_COLOR = Qt.lightGray BACKGROUND_INDEX_ALPHA = 0.8 BACKGROUND_STRING_ALPHA = 0.05 BACKGROUND_MISC_ALPHA = 0.3 def bool_false_check(value): """ Used to convert bool intrance to false since any string in bool('') will return True """ if value.lower() in _bool_false: value = "" return value def global_max(col_vals, index): """Returns the global maximum and minimum""" col_vals_without_None = [x for x in col_vals if x is not None] max_col, min_col = zip(*col_vals_without_None) return max(max_col), min(min_col) class DataFrameModel(QAbstractTableModel): """DataFrame Table Model""" ROWS_TO_LOAD = 500 COLS_TO_LOAD = 40 def __init__(self, dataFrame, format=DEFAULT_FORMAT, parent=None): QAbstractTableModel.__init__(self) self.dialog = parent self.df = dataFrame self.df_index = dataFrame.index.tolist() self.df_header = dataFrame.columns.tolist() self._format = format self.complex_intran = None self.display_error_idxs = [] self.total_rows = self.df.shape[0] self.total_cols = self.df.shape[1] size = self.total_rows * self.total_cols self.max_min_col = None if size < LARGE_SIZE: self.max_min_col_update() self.colum_avg_enabled = True self.bgcolor_enabled = True self.colum_avg(1) else: self.colum_avg_enabled = False self.bgcolor_enabled = False self.colum_avg(0) # Use paging when the total size, number of rows or number of # columns is too large if size > LARGE_SIZE: self.rows_loaded = self.ROWS_TO_LOAD self.cols_loaded = self.COLS_TO_LOAD else: if self.total_rows > LARGE_NROWS: self.rows_loaded = self.ROWS_TO_LOAD else: self.rows_loaded = self.total_rows if self.total_cols > LARGE_COLS: self.cols_loaded = self.COLS_TO_LOAD else: self.cols_loaded = self.total_cols def max_min_col_update(self): """ Determines the maximum and minimum number in each column. The result is a list whose k-th entry is [vmax, vmin], where vmax and vmin denote the maximum and minimum of the k-th column (ignoring NaN). This list is stored in self.max_min_col. If the k-th column has a non-numerical dtype, then the k-th entry is set to None. If the dtype is complex, then compute the maximum and minimum of the absolute values. If vmax equals vmin, then vmin is decreased by one. """ if self.df.shape[0] == 0: # If no rows to compute max/min then return return self.max_min_col = [] for dummy, col in self.df.items(): if col.dtype in REAL_NUMBER_TYPES + COMPLEX_NUMBER_TYPES: if col.dtype in REAL_NUMBER_TYPES: vmax = col.max(skipna=True) vmin = col.min(skipna=True) else: vmax = col.abs().max(skipna=True) vmin = col.abs().min(skipna=True) if vmax != vmin: max_min = [vmax, vmin] else: max_min = [vmax, vmin - 1] else: max_min = None self.max_min_col.append(max_min) def get_format(self): """Return current format""" # Avoid accessing the private attribute _format from outside return self._format def set_format(self, format): """Change display format""" self._format = format self.reset() def bgcolor(self, state): """Toggle backgroundcolor""" self.bgcolor_enabled = state > 0 self.reset() def colum_avg(self, state): """Toggle backgroundcolor""" self.colum_avg_enabled = state > 0 if self.colum_avg_enabled: self.return_max = lambda col_vals, index: col_vals[index] else: self.return_max = global_max self.reset() def headerData(self, section, orientation, role=Qt.DisplayRole): """Set header data""" if role != Qt.DisplayRole: return None if orientation == Qt.Horizontal: if section == 0: return "Index" elif type(self.df_header[section - 1]) in (bytes, str): # Don't perform any conversion on strings because it # leads to differences between the data present in # the dataframe and what is shown by Spyder return self.df_header[section - 1] else: return str(self.df_header[section - 1]) else: return None def get_bgcolor(self, index): """Background color depending on value""" column = index.column() if column == 0: color = QColor(BACKGROUND_NONNUMBER_COLOR) color.setAlphaF(BACKGROUND_INDEX_ALPHA) return color if not self.bgcolor_enabled: return value = self.get_value(index.row(), column - 1) if self.max_min_col[column - 1] is None: color = QColor(BACKGROUND_NONNUMBER_COLOR) if isinstance(value, str): color.setAlphaF(BACKGROUND_STRING_ALPHA) else: color.setAlphaF(BACKGROUND_MISC_ALPHA) else: if isinstance(value, COMPLEX_NUMBER_TYPES): color_func = abs else: color_func = float vmax, vmin = self.return_max(self.max_min_col, column - 1) hue = BACKGROUND_NUMBER_MINHUE + BACKGROUND_NUMBER_HUERANGE * ( vmax - color_func(value) ) / (vmax - vmin) hue = float(abs(hue)) if hue > 1: hue = 1 color = QColor.fromHsvF( hue, BACKGROUND_NUMBER_SATURATION, BACKGROUND_NUMBER_VALUE, BACKGROUND_NUMBER_ALPHA, ) return color def get_value(self, row, column): """Returns the value of the DataFrame""" # To increase the performance iat is used but that requires error # handling, so fallback uses iloc try: value = self.df.iat[row, column] except OutOfBoundsDatetime: value = self.df.iloc[:, column].astype(str).iat[row] except: value = self.df.iloc[row, column] return value def update_df_index(self): """ "Update the DataFrame index""" self.df_index = self.df.index.tolist() def data(self, index, role=Qt.DisplayRole): """Cell content""" if not index.isValid(): return None if role == Qt.DisplayRole or role == Qt.EditRole: column = index.column() row = index.row() if column == 0: df_idx = self.df_index[row] if type(df_idx) in (bytes, str): # Don't perform any conversion on strings # because it leads to differences between # the data present in the dataframe and # what is shown by Spyder return df_idx else: return str(df_idx) else: value = self.get_value(row, column - 1) if isinstance(value, float): try: return self._format % value except (ValueError, TypeError): # may happen if format = '%d' and value = NaN; # see issue 4139 return DEFAULT_FORMAT % value elif type(value) in (bytes, str): # Don't perform any conversion on strings # because it leads to differences between # the data present in the dataframe and # what is shown by Spyder return value else: try: return str(value) except Exception: self.display_error_idxs.append(index) return "Display Error!" elif role == Qt.BackgroundColorRole: return self.get_bgcolor(index) elif role == Qt.FontRole: return get_font(CONF, "arrayeditor", "font") elif role == Qt.ToolTipRole: if index in self.display_error_idxs: return _( "It is not possible to display this value because\n" "an error ocurred while trying to do it" ) return None def sort(self, column, order=Qt.AscendingOrder): """Overriding sort method""" if self.complex_intran is not None: if self.complex_intran.any(axis=0).iloc[column - 1]: QMessageBox.critical( self.dialog, "Error", "TypeError error: no ordering " "relation is defined for complex numbers", ) return False try: ascending = order == Qt.AscendingOrder if column > 0: try: self.df.sort_values( by=self.df.columns[column - 1], ascending=ascending, inplace=True, kind="mergesort", ) except AttributeError: # for pandas version < 0.17 self.df.sort( columns=self.df.columns[column - 1], ascending=ascending, inplace=True, kind="mergesort", ) except ValueError as e: # Not possible to sort on duplicate columns #5225 QMessageBox.critical( self.dialog, "Error", "ValueError: %s" % str(e) ) except SystemError as e: # Not possible to sort on category dtypes #5361 QMessageBox.critical( self.dialog, "Error", "SystemError: %s" % str(e) ) self.update_df_index() else: self.df.sort_index(inplace=True, ascending=ascending) self.update_df_index() except TypeError as e: QMessageBox.critical(self.dialog, "Error", "TypeError error: %s" % str(e)) return False self.reset() return True def flags(self, index): """Set flags""" if index.column() == 0: return Qt.ItemIsEnabled | Qt.ItemIsSelectable return Qt.ItemFlags(QAbstractTableModel.flags(self, index) | Qt.ItemIsEditable) def setData(self, index, value, role=Qt.EditRole, change_type=None): """Cell content change""" column = index.column() row = index.row() if index in self.display_error_idxs: return False if change_type is not None: try: val = self.data(index, role=Qt.DisplayRole) if change_type is bool: val = bool_false_check(val) self.df.iloc[row, column - 1] = change_type(val) except ValueError: self.df.iloc[row, column - 1] = change_type("0") else: val = value current_value = self.get_value(row, column - 1) if isinstance(current_value, (bool, np.bool_)): val = bool_false_check(val) supported_types = (bool, np.bool_) + REAL_NUMBER_TYPES if isinstance(current_value, supported_types) or isinstance( current_value, str ): try: self.df.iloc[row, column - 1] = current_value.__class__(val) except (ValueError, OverflowError) as e: QMessageBox.critical( self.dialog, "Error", str(type(e).__name__) + ": " + str(e) ) return False else: QMessageBox.critical( self.dialog, "Error", "Editing dtype {0!s} not yet supported.".format( type(current_value).__name__ ), ) return False self.max_min_col_update() self.dataChanged.emit(index, index) return True def get_data(self): """Return data""" return self.df def rowCount(self, index=QModelIndex()): """DataFrame row number""" if self.total_rows <= self.rows_loaded: return self.total_rows else: return self.rows_loaded def can_fetch_more(self, rows=False, columns=False): """ :param rows: :param columns: :return: """ if rows: if self.total_rows > self.rows_loaded: return True else: return False if columns: if self.total_cols > self.cols_loaded: return True else: return False def fetch_more(self, rows=False, columns=False): """ :param rows: :param columns: """ if self.can_fetch_more(rows=rows): reminder = self.total_rows - self.rows_loaded items_to_fetch = min(reminder, self.ROWS_TO_LOAD) self.beginInsertRows( QModelIndex(), self.rows_loaded, self.rows_loaded + items_to_fetch - 1 ) self.rows_loaded += items_to_fetch self.endInsertRows() if self.can_fetch_more(columns=columns): reminder = self.total_cols - self.cols_loaded items_to_fetch = min(reminder, self.COLS_TO_LOAD) self.beginInsertColumns( QModelIndex(), self.cols_loaded, self.cols_loaded + items_to_fetch - 1 ) self.cols_loaded += items_to_fetch self.endInsertColumns() def columnCount(self, index=QModelIndex()): """DataFrame column number""" # This is done to implement series if len(self.df.shape) == 1: return 2 elif self.total_cols <= self.cols_loaded: return self.total_cols + 1 else: return self.cols_loaded + 1 def reset(self): """ """ self.beginResetModel() self.endResetModel() class FrozenTableView(QTableView): """This class implements a table with its first column frozen For more information please see: https://doc.qt.io/qt-5/qtwidgets-itemviews-frozencolumn-example.html""" def __init__(self, parent): """Constructor.""" QTableView.__init__(self, parent) self.parent = parent self.setModel(parent.model()) self.setFocusPolicy(Qt.NoFocus) self.verticalHeader().hide() try: self.horizontalHeader().setSectionResizeMode(QHeaderView.Fixed) except: # support for qtpy<1.2.0 self.horizontalHeader().setResizeMode(QHeaderView.Fixed) parent.viewport().stackUnder(self) self.setSelectionModel(parent.selectionModel()) for col in range(1, parent.model().columnCount()): self.setColumnHidden(col, True) self.setColumnWidth(0, parent.columnWidth(0)) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.show() self.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel) def update_geometry(self): """Update the frozen column size when an update occurs in its parent table""" self.setGeometry( self.parent.verticalHeader().width() + self.parent.frameWidth(), self.parent.frameWidth(), self.parent.columnWidth(0), self.parent.viewport().height() + self.parent.horizontalHeader().height(), ) class DataFrameView(QTableView): """Data Frame view class""" def __init__(self, parent, model): QTableView.__init__(self, parent) self.setModel(model) self.frozen_table_view = FrozenTableView(self) self.frozen_table_view.update_geometry() self.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel) self.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel) self.horizontalHeader().sectionResized.connect(self.update_section_width) self.verticalHeader().sectionResized.connect(self.update_section_height) self.frozen_table_view.verticalScrollBar().valueChanged.connect( self.verticalScrollBar().setValue ) self.sort_old = [None] self.header_class = self.horizontalHeader() self.header_class.sectionClicked.connect(self.sortByColumn) self.menu = self.setup_menu() QShortcut(QKeySequence(QKeySequence.Copy), self, self.copy) self.horizontalScrollBar().valueChanged.connect( lambda val: self.load_more_data(val, columns=True) ) self.verticalScrollBar().valueChanged.connect( lambda val: self.load_more_data(val, rows=True) ) self.verticalScrollBar().valueChanged.connect( self.frozen_table_view.verticalScrollBar().setValue ) def update_section_width(self, logical_index, old_size, new_size): """Update the horizontal width of the frozen column when a change takes place in the first column of the table""" if logical_index == 0: self.frozen_table_view.setColumnWidth(0, new_size) self.frozen_table_view.update_geometry() def update_section_height(self, logical_index, old_size, new_size): """Update the vertical width of the frozen column when a change takes place on any of the rows""" self.frozen_table_view.setRowHeight(logical_index, new_size) def resizeEvent(self, event): """Update the frozen column dimensions. Updates takes place when the enclosing window of this table reports a dimension change """ QTableView.resizeEvent(self, event) self.frozen_table_view.update_geometry() def moveCursor(self, cursor_action, modifiers): """Update the table position. Updates the position along with the frozen column when the cursor (selector) changes its position """ current = QTableView.moveCursor(self, cursor_action, modifiers) col_width = self.frozen_table_view.columnWidth( 0 ) + self.frozen_table_view.columnWidth(1) topleft_x = self.visualRect(current).topLeft().x() overflow = self.MoveLeft and current.column() > 1 overflow = overflow and topleft_x < col_width if cursor_action == overflow: new_value = self.horizontalScrollBar().value() + topleft_x - col_width self.horizontalScrollBar().setValue(new_value) return current def scrollTo(self, index, hint): """Scroll the table. It is necessary to ensure that the item at index is visible. The view will try to position the item according to the given hint. This method does not takes effect only if the frozen column is scrolled. """ if index.column() > 1: QTableView.scrollTo(self, index, hint) def load_more_data(self, value, rows=False, columns=False): """ :param value: :param rows: :param columns: """ if rows and value == self.verticalScrollBar().maximum(): self.model().fetch_more(rows=rows) if columns and value == self.horizontalScrollBar().maximum(): self.model().fetch_more(columns=columns) def sortByColumn(self, index): """Implement a Column sort""" if self.sort_old == [None]: self.header_class.setSortIndicatorShown(True) sort_order = self.header_class.sortIndicatorOrder() if not self.model().sort(index, sort_order): if len(self.sort_old) != 2: self.header_class.setSortIndicatorShown(False) else: self.header_class.setSortIndicator(self.sort_old[0], self.sort_old[1]) return self.sort_old = [index, self.header_class.sortIndicatorOrder()] def contextMenuEvent(self, event): """Reimplement Qt method""" self.menu.popup(event.globalPos()) event.accept() def setup_menu(self): """Setup context menu""" copy_action = create_action( self, _("Copy"), shortcut=keybinding("Copy"), icon=get_icon("editcopy.png"), triggered=self.copy, context=Qt.WidgetShortcut, ) functions = ( (_("To bool"), bool), (_("To complex"), complex), (_("To int"), int), (_("To float"), float), (_("To str"), str), ) types_in_menu = [copy_action] for name, func in functions: # QAction.triggered works differently for PySide and PyQt slot = lambda _checked, func=func: self.change_type(func) types_in_menu += [ create_action(self, name, triggered=slot, context=Qt.WidgetShortcut) ] menu = QMenu(self) add_actions(menu, types_in_menu) return menu def change_type(self, func): """A function that changes types of cells""" model = self.model() index_list = self.selectedIndexes() [model.setData(i, "", change_type=func) for i in index_list] @Slot() def copy(self): """Copy text to clipboard""" if not self.selectedIndexes(): return (row_min, row_max, col_min, col_max) = get_idx_rect(self.selectedIndexes()) index = header = False if col_min == 0: col_min = 1 index = True df = self.model().df if col_max == 0: # To copy indices contents = "\n".join( map(str, df.index.tolist()[slice(row_min, row_max + 1)]) ) else: # To copy DataFrame if (col_min == 0 or col_min == 1) and (df.shape[1] == col_max): header = True obj = df.iloc[slice(row_min, row_max + 1), slice(col_min - 1, col_max)] output = io.StringIO() obj.to_csv(output, sep="\t", index=index, header=header) contents = output.getvalue() output.close() clipboard = QApplication.clipboard() clipboard.setText(contents) class DataFrameEditor(QDialog): """ Dialog for displaying and editing DataFrame and related objects. Signals ------- sig_option_changed(str, object): Raised if an option is changed. Arguments are name of option and its new value. """ sig_option_changed = Signal(str, object) def __init__(self, parent=None): QDialog.__init__(self, parent) win32_fix_title_bar_background(self) # Destroying the C++ object right after closing the dialog box, # otherwise it may be garbage-collected in another QThread # (e.g. the editor's analysis thread in Spyder), thus leading to # a segmentation fault on UNIX or an application crash on Windows self.setAttribute(Qt.WA_DeleteOnClose) self.is_series = False self.layout = None def setup_and_check(self, data, title=""): """ Setup DataFrameEditor: return False if data is not supported, True otherwise. Supported types for data are DataFrame, Series and DatetimeIndex. """ self.layout = QGridLayout() self.setLayout(self.layout) self.setWindowIcon(get_icon("arredit.png")) if title: title = str(title) + " - %s" % data.__class__.__name__ else: title = _("%s editor") % data.__class__.__name__ if isinstance(data, Series): self.is_series = True data = data.to_frame() elif isinstance(data, DatetimeIndex): data = DataFrame(data) self.setWindowTitle(title) self.resize(600, 500) self.dataModel = DataFrameModel(data, parent=self) self.dataModel.dataChanged.connect(self.save_and_close_enable) self.dataTable = DataFrameView(self, self.dataModel) self.layout.addWidget(self.dataTable) self.setLayout(self.layout) self.setMinimumSize(400, 300) # Make the dialog act as a window self.setWindowFlags(Qt.Window) btn_layout = QHBoxLayout() btn = QPushButton(_("Format")) # disable format button for int type btn_layout.addWidget(btn) btn.clicked.connect(self.change_format) btn = QPushButton(_("Resize")) btn_layout.addWidget(btn) btn.clicked.connect(self.resize_to_contents) bgcolor = QCheckBox(_("Background color")) bgcolor.setChecked(self.dataModel.bgcolor_enabled) bgcolor.setEnabled(self.dataModel.bgcolor_enabled) bgcolor.stateChanged.connect(self.change_bgcolor_enable) btn_layout.addWidget(bgcolor) self.bgcolor_global = QCheckBox(_("Column min/max")) self.bgcolor_global.setChecked(self.dataModel.colum_avg_enabled) self.bgcolor_global.setEnabled( not self.is_series and self.dataModel.bgcolor_enabled ) self.bgcolor_global.stateChanged.connect(self.dataModel.colum_avg) btn_layout.addWidget(self.bgcolor_global) btn_layout.addStretch() self.btn_save_and_close = QPushButton(_("Save and Close")) self.btn_save_and_close.setDisabled(True) self.btn_save_and_close.clicked.connect(self.accept) btn_layout.addWidget(self.btn_save_and_close) self.btn_close = QPushButton(_("Close")) self.btn_close.setAutoDefault(True) self.btn_close.setDefault(True) self.btn_close.clicked.connect(self.reject) btn_layout.addWidget(self.btn_close) self.layout.addLayout(btn_layout, 2, 0) return True @Slot(QModelIndex, QModelIndex) def save_and_close_enable(self, top_left, bottom_right): """Handle the data change event to enable the save and close button.""" self.btn_save_and_close.setEnabled(True) self.btn_save_and_close.setAutoDefault(True) self.btn_save_and_close.setDefault(True) def change_bgcolor_enable(self, state): """ This is implementet so column min/max is only active when bgcolor is """ self.dataModel.bgcolor(state) self.bgcolor_global.setEnabled(not self.is_series and state > 0) def change_format(self): """ Ask user for display format for floats and use it. This function also checks whether the format is valid and emits `sig_option_changed`. """ format, valid = QInputDialog.getText( self, _("Format"), _("Float formatting"), QLineEdit.Normal, self.dataModel.get_format(), ) if valid: format = str(format) try: format % 1.1 except: msg = _("Format ({}) is incorrect").format(format) QMessageBox.critical(self, _("Error"), msg) return if not format.startswith("%"): msg = _("Format ({}) should start with '%'").format(format) QMessageBox.critical(self, _("Error"), msg) return self.dataModel.set_format(format) self.sig_option_changed.emit("dataframe_format", format) def get_value(self): """Return modified Dataframe -- this is *not* a copy""" # It is import to avoid accessing Qt C++ object as it has probably # already been destroyed, due to the Qt.WA_DeleteOnClose attribute df = self.dataModel.get_data() if self.is_series: return df.iloc[:, 0] else: return df def resize_to_contents(self): """ """ QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) self.dataTable.resizeColumnsToContents() self.dataModel.fetch_more(columns=True) self.dataTable.resizeColumnsToContents() QApplication.restoreOverrideCursor() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/widgets/dockable.py0000644000175100001770000000672314654363416017773 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """ dockable -------- The `dockable` module provides a mixin class for widgets that can be docked into a QMainWindow. """ from __future__ import annotations from qtpy.QtCore import Qt from qtpy.QtWidgets import QDockWidget, QWidget class DockableWidgetMixin: """Mixin class for widgets that can be docked into a QMainWindow""" ALLOWED_AREAS = Qt.AllDockWidgetAreas LOCATION = Qt.TopDockWidgetArea FEATURES = ( QDockWidget.DockWidgetClosable | QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetMovable ) def __init__(self): self._isvisible = False self.dockwidget: QDockWidget | None = None self._allowed_areas = self.ALLOWED_AREAS self._location = self.LOCATION self._features = self.FEATURES @property def parent_widget(self) -> QWidget | None: """Return associated QWidget parent""" return self.parent() def setup_dockwidget( self, location: Qt.DockWidgetArea | None = None, features: QDockWidget.DockWidgetFeatures | None = None, allowed_areas: Qt.DockWidgetAreas | None = None, ) -> None: """Setup dockwidget parameters Args: location (Qt.DockWidgetArea): Dockwidget location features (QDockWidget.DockWidgetFeatures): Dockwidget features allowed_areas (Qt.DockWidgetAreas): Dockwidget allowed areas """ assert ( self.dockwidget is None ), "Dockwidget must be setup before calling 'create_dockwidget'" if location is not None: self._location = location if features is not None: self._features = features if allowed_areas is not None: self._allowed_areas = allowed_areas def get_focus_widget(self) -> QWidget | None: """Return widget to focus when dockwidget is visible""" return None def create_dockwidget(self, title: str) -> tuple[QDockWidget, Qt.DockWidgetArea]: """Add to parent QMainWindow as a dock widget Args: title (str): Dockwidget title Returns: tuple[QDockWidget, Qt.DockWidgetArea]: Dockwidget and location """ dock = QDockWidget(title, self.parent_widget) dock.setObjectName(self.__class__.__name__ + "_dw") dock.setAllowedAreas(self._allowed_areas) dock.setFeatures(self._features) dock.setWidget(self) dock.visibilityChanged.connect(self.visibility_changed) self.dockwidget = dock return (dock, self._location) def is_visible(self) -> bool: """Return dockwidget visibility state""" return self._isvisible def visibility_changed(self, enable: bool) -> None: """DockWidget visibility has changed Args: enable (bool): Dockwidget visibility state """ if enable: self.dockwidget.raise_() widget = self.get_focus_widget() # pylint: disable=assignment-from-none if widget is not None: widget.setFocus() self._isvisible = enable and self.dockwidget.isVisible() class DockableWidget(QWidget, DockableWidgetMixin): """Dockable widget Args: parent (QWidget): Parent widget """ def __init__(self, parent: QWidget): QWidget.__init__(self, parent) DockableWidgetMixin.__init__(self) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/widgets/importwizard.py0000644000175100001770000005554014654363416020763 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Copyright ┬й Spyder Project Contributors # Licensed under the terms of the MIT License # (see spyder/__init__.py for details) """ Text data Importing Wizard based on Qt """ # ----date and datetime objects support import datetime import io from functools import partial as ft_partial from itertools import zip_longest from numpy import nan from qtpy.QtCore import QAbstractTableModel, QModelIndex, Qt, Signal, Slot from qtpy.QtGui import QColor, QIntValidator from qtpy.QtWidgets import ( QCheckBox, QDialog, QFrame, QGridLayout, QGroupBox, QHBoxLayout, QLabel, QLineEdit, QMenu, QMessageBox, QPushButton, QRadioButton, QSizePolicy, QSpacerItem, QTableView, QTabWidget, QTextEdit, QVBoxLayout, QWidget, ) from guidata.config import _ from guidata.configtools import get_icon from guidata.qthelpers import add_actions, create_action, win32_fix_title_bar_background try: import pandas as pd except: pd = None def try_to_parse(value): """ :param value: :return: """ _types = ("int", "float") for _t in _types: try: _val = eval("%s('%s')" % (_t, value)) return _val except (ValueError, SyntaxError): pass return value def try_to_eval(value): """ :param value: :return: """ try: return eval(value) except (NameError, SyntaxError, ImportError): return value # ----Numpy arrays support class FakeObject: """Fake class used in replacement of missing modules""" pass try: from numpy import array, ndarray except: class ndarray(FakeObject): # analysis:ignore """Fake ndarray""" pass try: from dateutil.parser import parse as dateparse except: def dateparse(datestr, dayfirst=True): # analysis:ignore """Just for 'day/month/year' strings""" _a, _b, _c = list(map(int, datestr.split("/"))) if dayfirst: return datetime.datetime(_c, _b, _a) return datetime.datetime(_c, _a, _b) def datestr_to_datetime(value, dayfirst=True): """ :param value: :param dayfirst: :return: """ return dateparse(value, dayfirst=dayfirst) # ----Background colors for supported types COLORS = { bool: Qt.magenta, (float, int): Qt.blue, list: Qt.yellow, dict: Qt.cyan, tuple: Qt.lightGray, (str,): Qt.darkRed, ndarray: Qt.green, datetime.date: Qt.darkYellow, } def get_color(value, alpha): """Return color depending on value type""" color = QColor() for typ in COLORS: if isinstance(value, typ): color = QColor(COLORS[typ]) color.setAlphaF(alpha) return color class ContentsWidget(QWidget): """Import wizard contents widget""" asDataChanged = Signal(bool) def __init__(self, parent, text): QWidget.__init__(self, parent) self.text_editor = QTextEdit(self) self.text_editor.setText(text) self.text_editor.setReadOnly(True) # Type frame type_layout = QHBoxLayout() type_label = QLabel(_("Import as")) type_layout.addWidget(type_label) data_btn = QRadioButton(_("data")) data_btn.setChecked(True) self._as_data = True type_layout.addWidget(data_btn) code_btn = QRadioButton(_("code")) self._as_code = False type_layout.addWidget(code_btn) txt_btn = QRadioButton(_("text")) type_layout.addWidget(txt_btn) h_spacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) type_layout.addItem(h_spacer) type_frame = QFrame() type_frame.setLayout(type_layout) # Opts frame grid_layout = QGridLayout() grid_layout.setSpacing(0) col_label = QLabel(_("Column separator:")) grid_layout.addWidget(col_label, 0, 0) col_w = QWidget() col_btn_layout = QHBoxLayout() self.tab_btn = QRadioButton(_("Tab")) self.tab_btn.setChecked(False) col_btn_layout.addWidget(self.tab_btn) self.ws_btn = QRadioButton(_("Whitespace")) self.ws_btn.setChecked(False) col_btn_layout.addWidget(self.ws_btn) other_btn_col = QRadioButton(_("other")) other_btn_col.setChecked(True) col_btn_layout.addWidget(other_btn_col) col_w.setLayout(col_btn_layout) grid_layout.addWidget(col_w, 0, 1) self.line_edt = QLineEdit(",") self.line_edt.setMaximumWidth(30) self.line_edt.setEnabled(True) other_btn_col.toggled.connect(self.line_edt.setEnabled) grid_layout.addWidget(self.line_edt, 0, 2) row_label = QLabel(_("Row separator:")) grid_layout.addWidget(row_label, 1, 0) row_w = QWidget() row_btn_layout = QHBoxLayout() self.eol_btn = QRadioButton(_("EOL")) self.eol_btn.setChecked(True) row_btn_layout.addWidget(self.eol_btn) other_btn_row = QRadioButton(_("other")) row_btn_layout.addWidget(other_btn_row) row_w.setLayout(row_btn_layout) grid_layout.addWidget(row_w, 1, 1) self.line_edt_row = QLineEdit(";") self.line_edt_row.setMaximumWidth(30) self.line_edt_row.setEnabled(False) other_btn_row.toggled.connect(self.line_edt_row.setEnabled) grid_layout.addWidget(self.line_edt_row, 1, 2) grid_layout.setRowMinimumHeight(2, 15) other_group = QGroupBox(_("Additional options")) other_layout = QGridLayout() other_group.setLayout(other_layout) skiprows_label = QLabel(_("Skip rows:")) other_layout.addWidget(skiprows_label, 0, 0) self.skiprows_edt = QLineEdit("0") self.skiprows_edt.setMaximumWidth(30) intvalid = QIntValidator(0, len(str(text).splitlines()), self.skiprows_edt) self.skiprows_edt.setValidator(intvalid) other_layout.addWidget(self.skiprows_edt, 0, 1) other_layout.setColumnMinimumWidth(2, 5) comments_label = QLabel(_("Comments:")) other_layout.addWidget(comments_label, 0, 3) self.comments_edt = QLineEdit("#") self.comments_edt.setMaximumWidth(30) other_layout.addWidget(self.comments_edt, 0, 4) self.trnsp_box = QCheckBox(_("Transpose")) # self.trnsp_box.setEnabled(False) other_layout.addWidget(self.trnsp_box, 1, 0, 2, 0) grid_layout.addWidget(other_group, 3, 0, 2, 0) opts_frame = QFrame() opts_frame.setLayout(grid_layout) data_btn.toggled.connect(opts_frame.setEnabled) data_btn.toggled.connect(self.set_as_data) code_btn.toggled.connect(self.set_as_code) # self.connect(txt_btn, SIGNAL("toggled(bool)"), # self, SLOT("is_text(bool)")) # Final layout layout = QVBoxLayout() layout.addWidget(type_frame) layout.addWidget(self.text_editor) layout.addWidget(opts_frame) self.setLayout(layout) def get_as_data(self): """Return if data type conversion""" return self._as_data def get_as_code(self): """Return if code type conversion""" return self._as_code def get_as_num(self): """Return if numeric type conversion""" return self._as_num def get_col_sep(self): """Return the column separator""" if self.tab_btn.isChecked(): return "\t" elif self.ws_btn.isChecked(): return None return str(self.line_edt.text()) def get_row_sep(self): """Return the row separator""" if self.eol_btn.isChecked(): return "\n" return str(self.line_edt_row.text()) def get_skiprows(self): """Return number of lines to be skipped""" return int(str(self.skiprows_edt.text())) def get_comments(self): """Return comment string""" return str(self.comments_edt.text()) @Slot(bool) def set_as_data(self, as_data): """Set if data type conversion""" self._as_data = as_data self.asDataChanged.emit(as_data) @Slot(bool) def set_as_code(self, as_code): """Set if code type conversion""" self._as_code = as_code class PreviewTableModel(QAbstractTableModel): """Import wizard preview table model""" def __init__(self, data=[], parent=None): QAbstractTableModel.__init__(self, parent) self._data = data def rowCount(self, parent=QModelIndex()): """Return row count""" return len(self._data) def columnCount(self, parent=QModelIndex()): """Return column count""" return len(self._data[0]) def _display_data(self, index): """Return a data element""" return self._data[index.row()][index.column()] def data(self, index, role=Qt.DisplayRole): """Return a model data element""" if not index.isValid(): return None if role == Qt.DisplayRole: return self._display_data(index) elif role == Qt.BackgroundColorRole: return get_color(self._data[index.row()][index.column()], 0.2) elif role == Qt.TextAlignmentRole: return int(Qt.AlignRight | Qt.AlignVCenter) return None def setData(self, index, value, role=Qt.EditRole): """Set model data""" return False def get_data(self): """Return a copy of model data""" return self._data[:][:] def parse_data_type(self, index, **kwargs): """Parse a type to an other type""" if not index.isValid(): return False try: if kwargs["atype"] == "date": self._data[index.row()][index.column()] = datestr_to_datetime( self._data[index.row()][index.column()], kwargs["dayfirst"] ).date() elif kwargs["atype"] == "perc": _tmp = self._data[index.row()][index.column()].replace("%", "") self._data[index.row()][index.column()] = eval(_tmp) / 100.0 elif kwargs["atype"] == "account": _tmp = self._data[index.row()][index.column()].replace(",", "") self._data[index.row()][index.column()] = eval(_tmp) elif kwargs["atype"] == "unicode": self._data[index.row()][index.column()] = str( self._data[index.row()][index.column()] ) elif kwargs["atype"] == "int": self._data[index.row()][index.column()] = int( self._data[index.row()][index.column()] ) elif kwargs["atype"] == "float": self._data[index.row()][index.column()] = float( self._data[index.row()][index.column()] ) self.dataChanged.emit(index, index) except Exception as instance: print(instance) # spyder: test-skip def reset(self): """ """ self.beginResetModel() self.endResetModel() class PreviewTable(QTableView): """Import wizard preview widget""" def __init__(self, parent): QTableView.__init__(self, parent) self._model = None # Setting up actions self.date_dayfirst_action = create_action( self, "dayfirst", triggered=ft_partial(self.parse_to_type, atype="date", dayfirst=True), ) self.date_monthfirst_action = create_action( self, "monthfirst", triggered=ft_partial(self.parse_to_type, atype="date", dayfirst=False), ) self.perc_action = create_action( self, "perc", triggered=ft_partial(self.parse_to_type, atype="perc") ) self.acc_action = create_action( self, "account", triggered=ft_partial(self.parse_to_type, atype="account") ) self.str_action = create_action( self, "unicode", triggered=ft_partial(self.parse_to_type, atype="unicode") ) self.int_action = create_action( self, "int", triggered=ft_partial(self.parse_to_type, atype="int") ) self.float_action = create_action( self, "float", triggered=ft_partial(self.parse_to_type, atype="float") ) # Setting up menus self.date_menu = QMenu() self.date_menu.setTitle("Date") add_actions( self.date_menu, (self.date_dayfirst_action, self.date_monthfirst_action) ) self.parse_menu = QMenu(self) self.parse_menu.addMenu(self.date_menu) add_actions(self.parse_menu, (self.perc_action, self.acc_action)) self.parse_menu.setTitle("String to") self.opt_menu = QMenu(self) self.opt_menu.addMenu(self.parse_menu) add_actions( self.opt_menu, (self.str_action, self.int_action, self.float_action) ) def _shape_text( self, text, colsep="\t", rowsep="\n", transpose=False, skiprows=0, comments="#", ): """Decode the shape of the given text""" assert colsep != rowsep out = [] text_rows = text.split(rowsep)[skiprows:] for row in text_rows: stripped = str(row).strip() if len(stripped) == 0 or stripped.startswith(comments): continue line = str(row).split(colsep) line = [try_to_parse(str(x)) for x in line] out.append(line) # Replace missing elements with np.nan's or None's out = list(zip_longest(*out, fillvalue=nan)) # Tranpose the last result to get the expected one out = [[r[col] for r in out] for col in range(len(out[0]))] if transpose: return [[r[col] for r in out] for col in range(len(out[0]))] return out def get_data(self): """Return model data""" if self._model is None: return None return self._model.get_data() def process_data( self, text, colsep="\t", rowsep="\n", transpose=False, skiprows=0, comments="#", ): """Put data into table model""" data = self._shape_text(text, colsep, rowsep, transpose, skiprows, comments) self._model = PreviewTableModel(data) self.setModel(self._model) @Slot() def parse_to_type(self, **kwargs): """Parse to a given type""" indexes = self.selectedIndexes() if not indexes: return for index in indexes: self.model().parse_data_type(index, **kwargs) def contextMenuEvent(self, event): """Reimplement Qt method""" self.opt_menu.popup(event.globalPos()) event.accept() class PreviewWidget(QWidget): """Import wizard preview widget""" def __init__(self, parent): QWidget.__init__(self, parent) vert_layout = QVBoxLayout() # Type frame type_layout = QHBoxLayout() type_label = QLabel(_("Import as")) type_layout.addWidget(type_label) self.array_btn = array_btn = QRadioButton(_("array")) array_btn.setEnabled(ndarray is not FakeObject) array_btn.setChecked(ndarray is not FakeObject) type_layout.addWidget(array_btn) list_btn = QRadioButton(_("list")) list_btn.setChecked(not array_btn.isChecked()) type_layout.addWidget(list_btn) if pd: self.df_btn = df_btn = QRadioButton(_("DataFrame")) df_btn.setChecked(False) type_layout.addWidget(df_btn) h_spacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) type_layout.addItem(h_spacer) type_frame = QFrame() type_frame.setLayout(type_layout) self._table_view = PreviewTable(self) vert_layout.addWidget(type_frame) vert_layout.addWidget(self._table_view) self.setLayout(vert_layout) def open_data( self, text, colsep="\t", rowsep="\n", transpose=False, skiprows=0, comments="#", ): """Open clipboard text as table""" if pd: self.pd_text = text self.pd_info = dict( sep=colsep, lineterminator=rowsep, skiprows=skiprows, comment=comments ) if colsep is None: self.pd_info = dict( lineterminator=rowsep, skiprows=skiprows, comment=comments, delim_whitespace=True, ) self._table_view.process_data( text, colsep, rowsep, transpose, skiprows, comments ) def get_data(self): """Return table data""" return self._table_view.get_data() class ImportWizard(QDialog): """Text data import wizard""" def __init__( self, parent, text, title=None, icon=None, contents_title=None, varname=None ): QDialog.__init__(self, parent) win32_fix_title_bar_background(self) # Destroying the C++ object right after closing the dialog box, # otherwise it may be garbage-collected in another QThread # (e.g. the editor's analysis thread in Spyder), thus leading to # a segmentation fault on UNIX or an application crash on Windows self.setAttribute(Qt.WA_DeleteOnClose) if title is None: title = _("Import wizard") self.setWindowTitle(title) if icon is None: self.setWindowIcon(get_icon("fileimport.png")) if contents_title is None: contents_title = _("Raw text") if varname is None: varname = _("variable_name") self.var_name, self.clip_data = None, None # Setting GUI self.tab_widget = QTabWidget(self) self.text_widget = ContentsWidget(self, text) self.table_widget = PreviewWidget(self) self.tab_widget.addTab(self.text_widget, _("text")) self.tab_widget.setTabText(0, contents_title) self.tab_widget.addTab(self.table_widget, _("table")) self.tab_widget.setTabText(1, _("Preview")) self.tab_widget.setTabEnabled(1, False) name_layout = QHBoxLayout() name_label = QLabel(_("Variable Name")) name_layout.addWidget(name_label) self.name_edt = QLineEdit() self.name_edt.setText(varname) name_layout.addWidget(self.name_edt) btns_layout = QHBoxLayout() cancel_btn = QPushButton(_("Cancel")) btns_layout.addWidget(cancel_btn) cancel_btn.clicked.connect(self.reject) h_spacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) btns_layout.addItem(h_spacer) self.back_btn = QPushButton(_("Previous")) self.back_btn.setEnabled(False) btns_layout.addWidget(self.back_btn) self.back_btn.clicked.connect(ft_partial(self._set_step, step=-1)) self.fwd_btn = QPushButton(_("Next")) if not text: self.fwd_btn.setEnabled(False) btns_layout.addWidget(self.fwd_btn) self.fwd_btn.clicked.connect(ft_partial(self._set_step, step=1)) self.done_btn = QPushButton(_("Done")) self.done_btn.setEnabled(False) btns_layout.addWidget(self.done_btn) self.done_btn.clicked.connect(self.process) self.text_widget.asDataChanged.connect(self.fwd_btn.setEnabled) self.text_widget.asDataChanged.connect(self.done_btn.setDisabled) layout = QVBoxLayout() layout.addLayout(name_layout) layout.addWidget(self.tab_widget) layout.addLayout(btns_layout) self.setLayout(layout) def _focus_tab(self, tab_idx): """Change tab focus""" for i in range(self.tab_widget.count()): self.tab_widget.setTabEnabled(i, False) self.tab_widget.setTabEnabled(tab_idx, True) self.tab_widget.setCurrentIndex(tab_idx) def _set_step(self, step): """Proceed to a given step""" new_tab = self.tab_widget.currentIndex() + step assert new_tab < self.tab_widget.count() and new_tab >= 0 if new_tab == self.tab_widget.count() - 1: try: self.table_widget.open_data( self._get_plain_text(), self.text_widget.get_col_sep(), self.text_widget.get_row_sep(), self.text_widget.trnsp_box.isChecked(), self.text_widget.get_skiprows(), self.text_widget.get_comments(), ) self.done_btn.setEnabled(True) self.done_btn.setDefault(True) self.fwd_btn.setEnabled(False) self.back_btn.setEnabled(True) except (SyntaxError, AssertionError) as error: QMessageBox.critical( self, _("Import wizard"), _( "Unable to proceed to next step" "

Please check your entries." "

Error message:
%s" ) % str(error), ) return elif new_tab == 0: self.done_btn.setEnabled(False) self.fwd_btn.setEnabled(True) self.back_btn.setEnabled(False) self._focus_tab(new_tab) def get_data(self): """Return processed data""" # It is import to avoid accessing Qt C++ object as it has probably # already been destroyed, due to the Qt.WA_DeleteOnClose attribute return self.var_name, self.clip_data def _simplify_shape(self, alist, rec=0): """Reduce the alist dimension if needed""" if rec != 0: if len(alist) == 1: return alist[-1] return alist if len(alist) == 1: return self._simplify_shape(alist[-1], 1) return [self._simplify_shape(al, 1) for al in alist] def _get_table_data(self): """Return clipboard processed as data""" data = self._simplify_shape(self.table_widget.get_data()) if self.table_widget.array_btn.isChecked(): return array(data) elif pd and self.table_widget.df_btn.isChecked(): info = self.table_widget.pd_info buf = io.StringIO(self.table_widget.pd_text) return pd.read_csv(buf, **info) return data def _get_plain_text(self): """Return clipboard as text""" return self.text_widget.text_editor.toPlainText() @Slot() def process(self): """Process the data from clipboard""" var_name = self.name_edt.text() try: self.var_name = str(var_name) except UnicodeEncodeError: self.var_name = str(var_name) if self.text_widget.get_as_data(): self.clip_data = self._get_table_data() elif self.text_widget.get_as_code(): self.clip_data = try_to_eval(str(self._get_plain_text())) else: self.clip_data = str(self._get_plain_text()) self.accept() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/widgets/nsview.py0000644000175100001770000005424614654363416017545 0ustar00runnerdocker# -*- coding: utf-8 -*- # ----------------------------------------------------------------------------- # Copyright (c) 2009- Spyder Kernels Contributors # # Licensed under the terms of the MIT License # (see spyder_kernels/__init__.py for details) # ----------------------------------------------------------------------------- """ Utilities """ # ============================================================================== # Date and datetime objects support # ============================================================================== import datetime import re from itertools import islice NUMERIC_TYPES = (int, float, complex) # ============================================================================== # FakeObject # ============================================================================== class FakeObject(object): """Fake class used in replacement of missing modules""" pass # ============================================================================== # Numpy arrays and numeric types support # ============================================================================== try: from numpy import ( array, bool_, complex64, complex128, float16, float32, float64, get_printoptions, int8, int16, int32, int64, matrix, ndarray, recarray, ) from numpy import savetxt as np_savetxt from numpy import set_printoptions, uint8, uint16, uint32, uint64 from numpy.ma import MaskedArray except: ndarray = ( array ) = ( matrix ) = ( recarray ) = ( MaskedArray ) = ( np_savetxt ) = ( int64 ) = ( int32 ) = ( int16 ) = ( int8 ) = ( uint64 ) = ( uint32 ) = ( uint16 ) = ( uint8 ) = float64 = float32 = float16 = complex64 = complex128 = bool_ = FakeObject def get_numpy_dtype(obj): """Return NumPy data type associated to obj Return None if NumPy is not available or if obj is not a NumPy array or scalar""" if ndarray is not FakeObject: # NumPy is available import numpy as np if isinstance(obj, np.generic) or isinstance(obj, np.ndarray): # Numpy scalars all inherit from np.generic. # Numpy arrays all inherit from np.ndarray. # If we check that we are certain we have one of these # types then we are less likely to generate an exception below. try: return obj.dtype.type except (AttributeError, RuntimeError): # AttributeError: some NumPy objects have no dtype attribute # RuntimeError: happens with NetCDF objects (Issue 998) return # ============================================================================== # Pandas support # ============================================================================== try: from pandas import DataFrame, DatetimeIndex, Series except: DataFrame = DatetimeIndex = Series = FakeObject # ============================================================================== # PIL Images support # ============================================================================== try: import PIL.Image Image = PIL.Image.Image except: Image = FakeObject # analysis:ignore # ============================================================================== # BeautifulSoup support (see Issue 2448) # ============================================================================== try: import bs4 NavigableString = bs4.element.NavigableString except: NavigableString = FakeObject # analysis:ignore # ============================================================================== # Misc. # ============================================================================== def address(obj): """Return object address as a string: ''""" return "<%s @ %s>" % ( obj.__class__.__name__, hex(id(obj)).upper().replace("X", "x"), ) def try_to_eval(value): """Try to eval value""" try: return eval(value) except (NameError, SyntaxError, ImportError): return value def get_size(item): """Return size of an item of arbitrary type""" if isinstance(item, (list, tuple, dict)): return len(item) elif isinstance(item, (ndarray, MaskedArray)): return item.shape elif isinstance(item, Image): return item.size if isinstance(item, (DataFrame, DatetimeIndex, Series)): return item.shape else: return 1 def get_object_attrs(obj): """ Get the attributes of an object using dir. This filters protected attributes """ attrs = [k for k in dir(obj) if not k.startswith("__")] if not attrs: attrs = dir(obj) return attrs try: from dateutil.parser import parse as dateparse except: def dateparse(datestr): # analysis:ignore """Just for 'year, month, day' strings""" return datetime.datetime(*list(map(int, datestr.split(",")))) def datestr_to_datetime(value): """ :param value: :return: """ rp = value.rfind("(") + 1 v = dateparse(value[rp:-1]) print(value, "-->", v) # spyder: test-skip return v def str_to_timedelta(value): """Convert a string to a datetime.timedelta value. The following strings are accepted: - 'datetime.timedelta(1, 5, 12345)' - 'timedelta(1, 5, 12345)' - '(1, 5, 12345)' - '1, 5, 12345' - '1' if there are less then three parameters, the missing parameters are assumed to be 0. Variations in the spacing of the parameters are allowed. Raises: ValueError for strings not matching the above criterion. """ m = re.match(r"^(?:(?:datetime\.)?timedelta)?" r"\(?" r"([^)]*)" r"\)?$", value) if not m: raise ValueError("Invalid string for datetime.timedelta") args = [int(a.strip()) for a in m.group(1).split(",")] return datetime.timedelta(*args) # ============================================================================== # Background colors for supported types # ============================================================================== ARRAY_COLOR = "#00ff00" SCALAR_COLOR = "#0000ff" COLORS = { bool: "#ff00ff", NUMERIC_TYPES: SCALAR_COLOR, list: "#ffff00", dict: "#00ffff", tuple: "#c0c0c0", (str,): "#800000", (ndarray, MaskedArray, matrix, DataFrame, Series, DatetimeIndex): ARRAY_COLOR, Image: "#008000", datetime.date: "#808000", datetime.timedelta: "#808000", } CUSTOM_TYPE_COLOR = "#7755aa" UNSUPPORTED_COLOR = "#ffffff" def get_color_name(value): """Return color name depending on value type""" if not is_known_type(value): return CUSTOM_TYPE_COLOR for typ, name in list(COLORS.items()): if isinstance(value, typ): return name else: np_dtype = get_numpy_dtype(value) if np_dtype is None or not hasattr(value, "size"): return UNSUPPORTED_COLOR elif value.size == 1: return SCALAR_COLOR else: return ARRAY_COLOR def is_editable_type(value): """Return True if data type is editable with a standard GUI-based editor, like CollectionsEditor, ArrayEditor, QDateEdit or a simple QLineEdit""" return get_color_name(value) not in (UNSUPPORTED_COLOR, CUSTOM_TYPE_COLOR) # ============================================================================== # Sorting # ============================================================================== def sort_against(list1, list2, reverse=False): """ Arrange items of list1 in the same order as sorted(list2). In other words, apply to list1 the permutation which takes list2 to sorted(list2, reverse). """ try: return [ item for _, item in sorted( zip(list2, list1), key=lambda x: x[0], reverse=reverse ) ] except: return list1 def unsorted_unique(lista): """Removes duplicates from lista neglecting its initial ordering""" return list(set(lista)) # ============================================================================== # Display <--> Value # ============================================================================== def default_display(value, with_module=True): """Default display for unknown objects.""" object_type = type(value) try: name = object_type.__name__ module = object_type.__module__ if with_module: return name + " object of " + module + " module" else: return name except: type_str = str(object_type) return type_str[1:-1] def collections_display(value, level): """Display for collections (i.e. list, tuple and dict).""" is_dict = isinstance(value, dict) # Get elements if is_dict: elements = value.items() else: elements = value # Truncate values truncate = False if level == 1 and len(value) > 10: elements = islice(elements, 10) if is_dict else value[:10] truncate = True elif level == 2 and len(value) > 5: elements = islice(elements, 5) if is_dict else value[:5] truncate = True # Get display of each element if level <= 2: if is_dict: displays = [ value_to_display(k, level=level) + ":" + value_to_display(v, level=level) for (k, v) in list(elements) ] else: displays = [value_to_display(e, level=level) for e in elements] if truncate: displays.append("...") display = ", ".join(displays) else: display = "..." # Return display if is_dict: display = "{" + display + "}" elif isinstance(value, list): display = "[" + display + "]" else: display = "(" + display + ")" return display def value_to_display(value, minmax=False, level=0): """Convert value for display purpose""" # To save current Numpy threshold np_threshold = FakeObject try: numeric_numpy_types = ( int64, int32, int16, int8, uint64, uint32, uint16, uint8, float64, float32, float16, complex128, complex64, bool_, ) if ndarray is not FakeObject: # Save threshold np_threshold = get_printoptions().get("threshold") # Set max number of elements to show for Numpy arrays # in our display set_printoptions(threshold=10) if isinstance(value, recarray): if level == 0: fields = value.names display = "Field names: " + ", ".join(fields) else: display = "Recarray" elif isinstance(value, MaskedArray): display = "Masked array" elif isinstance(value, ndarray): if level == 0: if minmax: try: display = "Min: %r\nMax: %r" % (value.min(), value.max()) except (TypeError, ValueError): if value.dtype.type in numeric_numpy_types: display = str(value) else: display = default_display(value) elif value.dtype.type in numeric_numpy_types: display = str(value) else: display = default_display(value) else: display = "Numpy array" elif any([type(value) == t for t in [list, tuple, dict]]): display = collections_display(value, level + 1) elif isinstance(value, Image): if level == 0: display = "%s Mode: %s" % (address(value), value.mode) else: display = "Image" elif isinstance(value, DataFrame): if level == 0: cols = value.columns cols = [str(c) for c in cols] display = "Column names: " + ", ".join(list(cols)) else: display = "Dataframe" elif isinstance(value, NavigableString): # Fixes Issue 2448 display = str(value) if level > 0: display = "'" + display + "'" elif isinstance(value, DatetimeIndex): if level == 0: try: display = value._summary() except AttributeError: display = value.summary() else: display = "DatetimeIndex" elif isinstance(value, bytes): # We don't apply this to classes that extend string types # See issue 5636 if type(value) is bytes: try: display = str(value, "utf8") if level > 0: display = "'" + display + "'" except: display = value if level > 0: display = b"'" + display + b"'" else: display = default_display(value) elif isinstance(value, str): # We don't apply this to classes that extend string types # See issue 5636 if type(value) is str: display = value if level > 0: display = "'" + display + "'" else: display = default_display(value) elif isinstance(value, datetime.date) or isinstance(value, datetime.timedelta): display = str(value) elif ( isinstance(value, NUMERIC_TYPES) or isinstance(value, bool) or isinstance(value, numeric_numpy_types) ): display = repr(value) else: if level == 0: display = default_display(value) else: display = default_display(value, with_module=False) except: display = default_display(value) # Truncate display at 70 chars to avoid freezing Spyder # because of large displays if len(display) > 70: if isinstance(display, bytes): ellipses = b" ..." else: ellipses = " ..." display = display[:70].rstrip() + ellipses # Restore Numpy threshold if np_threshold is not FakeObject: set_printoptions(threshold=np_threshold) return display def display_to_value(value, default_value, ignore_errors=True): """Convert back to value""" try: np_dtype = get_numpy_dtype(default_value) if isinstance(default_value, bool): # We must test for boolean before NumPy data types # because `bool` class derives from `int` class try: value = bool(float(value)) except ValueError: value = value.lower() == "true" elif np_dtype is not None: if "complex" in str(type(default_value)): value = np_dtype(complex(value)) else: value = np_dtype(value) elif isinstance(default_value, bytes): value = bytes(value, "utf8") elif isinstance(default_value, str): value = str(value) elif isinstance(default_value, complex): value = complex(value) elif isinstance(default_value, float): value = float(value) elif isinstance(default_value, int): try: value = int(value) except ValueError: value = float(value) elif isinstance(default_value, datetime.datetime): value = datestr_to_datetime(value) elif isinstance(default_value, datetime.date): value = datestr_to_datetime(value).date() elif isinstance(default_value, datetime.timedelta): value = str_to_timedelta(value) elif ignore_errors: value = try_to_eval(value) else: value = eval(value) except (ValueError, SyntaxError): if ignore_errors: value = try_to_eval(value) else: return default_value return value # ============================================================================= # Types # ============================================================================= def get_type_string(item): """Return type string of an object.""" if isinstance(item, DataFrame): return "DataFrame" if isinstance(item, DatetimeIndex): return "DatetimeIndex" if isinstance(item, Series): return "Series" found = re.findall(r"<(?:type|class) '(\S*)'>", str(type(item))) if found: return found[0] def is_known_type(item): """Return True if object has a known type""" # Unfortunately, the masked array case is specific return isinstance(item, MaskedArray) or get_type_string(item) is not None def get_human_readable_type(item): """Return human-readable type string of an item""" if isinstance(item, (ndarray, MaskedArray)): return item.dtype.name elif isinstance(item, Image): return "Image" else: text = get_type_string(item) if text is None: return "unknown" else: return text[text.find(".") + 1 :] # ============================================================================== # Globals filter: filter namespace dictionaries (to be edited in # CollectionsEditor) # ============================================================================== def is_supported(value, check_all=False, filters=None, iterate=False): """Return True if the value is supported, False otherwise""" assert filters is not None if value is None: return True if not is_editable_type(value): return False elif not isinstance(value, filters): return False elif iterate: if isinstance(value, (list, tuple, set)): valid_count = 0 for val in value: if is_supported(val, filters=filters, iterate=check_all): valid_count += 1 if not check_all: break return valid_count > 0 elif isinstance(value, dict): for key, val in list(value.items()): if not is_supported( key, filters=filters, iterate=check_all ) or not is_supported(val, filters=filters, iterate=check_all): return False if not check_all: break return True def globalsfilter( input_dict, check_all=False, filters=None, exclude_private=None, exclude_capitalized=None, exclude_uppercase=None, exclude_unsupported=None, excluded_names=None, ): """Keep only objects that can be pickled""" output_dict = {} for key, value in list(input_dict.items()): excluded = ( (exclude_private and key.startswith("_")) or (exclude_capitalized and key[0].isupper()) or ( exclude_uppercase and key.isupper() and len(key) > 1 and not key[1:].isdigit() ) or (key in excluded_names) or ( exclude_unsupported and not is_supported(value, check_all=check_all, filters=filters) ) ) if not excluded: output_dict[key] = value return output_dict # ============================================================================== # Create view to be displayed by NamespaceBrowser # ============================================================================== REMOTE_SETTINGS = ( "check_all", "exclude_private", "exclude_uppercase", "exclude_capitalized", "exclude_unsupported", "excluded_names", "minmax", ) def get_supported_types(): """ Return a dictionary containing types lists supported by the namespace browser. """ from datetime import date, timedelta editable_types = [int, float, complex, list, dict, tuple, date, timedelta, str] try: from numpy import generic, matrix, ndarray editable_types += [ndarray, matrix, generic] except: pass try: from pandas import DataFrame, DatetimeIndex, Series editable_types += [DataFrame, Series, DatetimeIndex] except: pass picklable_types = editable_types[:] if Image is not FakeObject: editable_types.append(Image) return dict(picklable=picklable_types, editable=editable_types) def get_remote_data(data, settings, mode, more_excluded_names=None): """ Return globals according to filter described in *settings*: * data: data to be filtered (dictionary) * settings: variable explorer settings (dictionary) * mode (string): 'editable' or 'picklable' * more_excluded_names: additional excluded names (list) """ supported_types = get_supported_types() assert mode in list(supported_types.keys()) excluded_names = settings["excluded_names"] if more_excluded_names is not None: excluded_names += more_excluded_names return globalsfilter( data, check_all=settings["check_all"], filters=tuple(supported_types[mode]), exclude_private=settings["exclude_private"], exclude_uppercase=settings["exclude_uppercase"], exclude_capitalized=settings["exclude_capitalized"], exclude_unsupported=settings["exclude_unsupported"], excluded_names=excluded_names, ) def make_remote_view(data, settings, more_excluded_names=None): """ Make a remote view of dictionary *data* -> globals explorer """ data = get_remote_data( data, settings, mode="editable", more_excluded_names=more_excluded_names ) remote = {} for key, value in list(data.items()): view = value_to_display(value, minmax=settings["minmax"]) remote[key] = { "type": get_human_readable_type(value), "size": get_size(value), "color": get_color_name(value), "view": view, } return remote ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/widgets/objecteditor.py0000644000175100001770000000646714654363416020711 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Copyright ┬й Spyder Project Contributors # Licensed under the terms of the MIT License # (see spyder/__init__.py for details) """ guidata.widgets.objecteditor ============================ This package provides a generic object editor widget. .. autofunction:: oedit """ from __future__ import annotations from typing import TYPE_CHECKING import numpy as np try: from PIL import Image as PILImage except ImportError: PILImage = None from guidata.qthelpers import exec_dialog from guidata.widgets.arrayeditor import ArrayEditor from guidata.widgets.collectionseditor import CollectionsEditor from guidata.widgets.nsview import DataFrame, FakeObject, Series, is_known_type from guidata.widgets.texteditor import TextEditor try: from guidata.widgets.dataframeeditor import DataFrameEditor except ImportError: DataFrameEditor = FakeObject() if TYPE_CHECKING: from qtpy import QtWidgets as QW def create_dialog(obj, title, parent=None): """Creates the editor dialog and returns a tuple (dialog, func) where func is the function to be called with the dialog instance as argument, after quitting the dialog box The role of this intermediate function is to allow easy monkey-patching. (uschmitt suggested this indirection here so that he can monkey patch oedit to show eMZed related data) """ def conv_func(data): """Conversion function""" return data readonly = not is_known_type(obj) if isinstance(obj, np.ndarray): dialog = ArrayEditor(parent) if not dialog.setup_and_check(obj, title=title, readonly=readonly): return elif PILImage is not None and isinstance(obj, PILImage.Image): dialog = ArrayEditor(parent) data = np.array(obj) if not dialog.setup_and_check(data, title=title, readonly=readonly): return def conv_func(data): # pylint: disable=function-redefined """Conversion function""" return PILImage.fromarray(data, mode=obj.mode) elif isinstance(obj, (DataFrame, Series)) and DataFrame is not FakeObject: dialog = DataFrameEditor(parent) if not dialog.setup_and_check(obj): return elif isinstance(obj, str): dialog = TextEditor(obj, title=title, readonly=readonly, parent=parent) else: dialog = CollectionsEditor(parent) dialog.setup(obj, title=title, readonly=readonly) def end_func(dialog): """ :param dialog: :return: """ return conv_func(dialog.get_value()) return dialog, end_func def oedit( obj: dict | list | tuple | str | np.ndarray, title: str = None, parent: QW.QWidget = None, ) -> dict | list | tuple | str | np.ndarray: """Edit the object 'obj' in a GUI-based editor and return the edited copy (if Cancel is pressed, return None) Args: obj (dict | list | tuple | str | np.ndarray): object to edit title (str): dialog title parent (QW.QWidget): parent widget Returns: dict | list | tuple | str | np.ndarray: edited object """ title = "" if title is None else title result = create_dialog(obj, title, parent) if result is None: return dialog, end_func = result if exec_dialog(dialog): return end_func(dialog) return None ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/widgets/rotatedlabel.py0000644000175100001770000000377614654363416020676 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Licensed under the terms of the BSD 3-Clause # (see guidata/LICENSE for details) """ rotatedlabel ------------ The ``guidata.widgets.rotatedlabel`` module provides a widget for displaying rotated text. """ from math import cos, pi, sin from qtpy.QtCore import QSize, Qt from qtpy.QtGui import QPainter, QPen from qtpy.QtWidgets import QLabel from guidata.configtools import get_family class RotatedLabel(QLabel): """ Rotated QLabel (rich text is not supported) Arguments: * parent: parent widget * angle=270 (int): rotation angle in degrees * family (string): font family * bold (bool): font weight * italic (bool): font italic style * color (QColor): font color """ def __init__( self, text, parent=None, angle=270, family=None, bold=False, italic=False, color=None, ): QLabel.__init__(self, text, parent) font = self.font() if family is not None: font.setFamily(get_family(family)) font.setBold(bold) font.setItalic(italic) self.setFont(font) self.color = color self.angle = angle self.setAlignment(Qt.AlignCenter) def paintEvent(self, evt): painter = QPainter(self) if self.color is not None: painter.setPen(QPen(self.color)) painter.rotate(self.angle) transform = painter.transform().inverted()[0] rct = transform.mapRect(self.rect()) painter.drawText(rct, self.alignment(), self.text()) def sizeHint(self): hint = QLabel.sizeHint(self) width, height = hint.width(), hint.height() angle = self.angle * pi / 180 rotated_width = int(abs(width * cos(angle)) + abs(height * sin(angle))) rotated_height = int(abs(width * sin(angle)) + abs(height * cos(angle))) return QSize(rotated_width, rotated_height) def minimumSizeHint(self): return self.sizeHint() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/widgets/syntaxhighlighters.py0000644000175100001770000017012414654363416022154 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Copyright ┬й Spyder Project Contributors # Licensed under the terms of the MIT License # (see spyder/__init__.py for details) """ Editor widget syntax highlighters based on QtGui.QSyntaxHighlighter (Python syntax highlighting rules are inspired from idlelib) """ import builtins import keyword import re from qtpy.QtCore import Qt from qtpy.QtGui import ( QColor, QCursor, QFont, QSyntaxHighlighter, QTextCharFormat, QTextOption, ) from qtpy.QtWidgets import ( QApplication, ) from guidata.config import CONF, _ from guidata.qthelpers import is_dark_theme # ============================================================================= # Constants # ============================================================================= COLOR_SCHEME_KEYS = { "background": _("Background:"), "currentline": _("Current line:"), "currentcell": _("Current cell:"), "occurrence": _("Occurrence:"), "ctrlclick": _("Link:"), "sideareas": _("Side areas:"), "matched_p": _("Matched
parens:"), "unmatched_p": _("Unmatched
parens:"), "normal": _("Normal text:"), "keyword": _("Keyword:"), "builtin": _("Builtin:"), "definition": _("Definition:"), "comment": _("Comment:"), "string": _("String:"), "number": _("Number:"), "instance": _("Instance:"), } COLOR_SCHEME_NAMES = CONF.get("color_schemes", "names") # Mapping for file extensions that use Pygments highlighting but should use # different lexers than Pygments' autodetection suggests. Keys are file # extensions or tuples of extensions, values are Pygments lexer names. # ============================================================================== # Auxiliary functions # ============================================================================== def get_color_scheme(name): """Get a color scheme from config using its name""" name = name.lower() scheme = {} for key in COLOR_SCHEME_KEYS: try: scheme[key] = CONF.get("color_schemes", name + "/" + key) except: scheme[key] = CONF.get("color_schemes", "spyder/" + key) return scheme # ============================================================================== # Syntax highlighting color schemes # ============================================================================== class BaseSH(QSyntaxHighlighter): """Base Syntax Highlighter Class""" # Syntax highlighting rules: PROG = None BLANKPROG = re.compile(r"\s+") # Syntax highlighting states (from one text block to another): NORMAL = 0 # Syntax highlighting parameters. BLANK_ALPHA_FACTOR = 0.31 def __init__(self, parent, font=None, color_scheme=None): QSyntaxHighlighter.__init__(self, parent) self.font = font if color_scheme is None: suffix = "dark" if is_dark_theme() else "light" color_scheme = CONF.get("color_schemes", "default/" + suffix) if isinstance(color_scheme, str): self.color_scheme = get_color_scheme(color_scheme) else: self.color_scheme = color_scheme self.background_color = None self.currentline_color = None self.currentcell_color = None self.occurrence_color = None self.ctrlclick_color = None self.sideareas_color = None self.matched_p_color = None self.unmatched_p_color = None self.formats = None self.setup_formats(font) self.cell_separators = None def get_background_color(self): """ :return: """ return QColor(self.background_color) def get_foreground_color(self): """Return foreground ('normal' text) color""" return self.formats["normal"].foreground().color() def get_currentline_color(self): """ :return: """ return QColor(self.currentline_color) def get_currentcell_color(self): """ :return: """ return QColor(self.currentcell_color) def get_occurrence_color(self): """ :return: """ return QColor(self.occurrence_color) def get_ctrlclick_color(self): """ :return: """ return QColor(self.ctrlclick_color) def get_sideareas_color(self): """ :return: """ return QColor(self.sideareas_color) def get_matched_p_color(self): """ :return: """ return QColor(self.matched_p_color) def get_unmatched_p_color(self): """ :return: """ return QColor(self.unmatched_p_color) def get_comment_color(self): """Return color for the comments""" return self.formats["comment"].foreground().color() def get_color_name(self, fmt): """Return color name assigned to a given format""" return self.formats[fmt].foreground().color().name() def setup_formats(self, font=None): """ :param font: """ base_format = QTextCharFormat() if font is not None: self.font = font if self.font is not None: base_format.setFont(self.font) self.formats = {} colors = self.color_scheme.copy() self.background_color = colors.pop("background") self.currentline_color = colors.pop("currentline") self.currentcell_color = colors.pop("currentcell") self.occurrence_color = colors.pop("occurrence") self.ctrlclick_color = colors.pop("ctrlclick") self.sideareas_color = colors.pop("sideareas") self.matched_p_color = colors.pop("matched_p") self.unmatched_p_color = colors.pop("unmatched_p") for name, (color, bold, italic) in list(colors.items()): format = QTextCharFormat(base_format) format.setForeground(QColor(color)) format.setBackground(QColor(self.background_color)) if bold: format.setFontWeight(QFont.Bold) format.setFontItalic(italic) self.formats[name] = format def set_color_scheme(self, color_scheme): """ :param color_scheme: """ if isinstance(color_scheme, str): self.color_scheme = get_color_scheme(color_scheme) else: self.color_scheme = color_scheme self.setup_formats() self.rehighlight() def highlightBlock(self, text): """ :param text: """ raise NotImplementedError def highlight_spaces(self, text, offset=0): """ Make blank space less apparent by setting the foreground alpha. This only has an effect when 'Show blank space' is turned on. Derived classes could call this function at the end of highlightBlock(). """ flags_text = self.document().defaultTextOption().flags() show_blanks = flags_text & QTextOption.ShowTabsAndSpaces if show_blanks: format_leading = self.formats.get("leading", None) format_trailing = self.formats.get("trailing", None) match = self.BLANKPROG.search(text, offset) while match: start, end = match.span() start = max([0, start + offset]) end = max([0, end + offset]) # Format trailing spaces at the end of the line. if end == len(text) and format_trailing is not None: self.setFormat(start, end, format_trailing) # Format leading spaces, e.g. indentation. if start == 0 and format_leading is not None: self.setFormat(start, end, format_leading) format = self.format(start) color_foreground = format.foreground().color() alpha_new = self.BLANK_ALPHA_FACTOR * color_foreground.alphaF() color_foreground.setAlphaF(alpha_new) self.setFormat(start, end - start, color_foreground) match = self.BLANKPROG.search(text, match.end()) def rehighlight(self): """ """ QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) QSyntaxHighlighter.rehighlight(self) QApplication.restoreOverrideCursor() class TextSH(BaseSH): """Simple Text Syntax Highlighter Class (do nothing)""" def highlightBlock(self, text): pass class GenericSH(BaseSH): """Generic Syntax Highlighter""" # Syntax highlighting rules: PROG = None # to be redefined in child classes def highlightBlock(self, text): text = str(text) self.setFormat(0, len(text), self.formats["normal"]) match = self.PROG.search(text) index = 0 while match: for key, value in match.groupdict().items(): if value: start, end = match.span(key) index += end - start self.setFormat(start, end - start, self.formats[key]) match = self.PROG.search(text, match.end()) # ============================================================================== # Python syntax highlighter # ============================================================================== def any(name, alternates): "Return a named group pattern matching list of alternates." return "(?P<%s>" % name + "|".join(alternates) + ")" def make_python_patterns(additional_keywords=[], additional_builtins=[]): "Strongly inspired from idlelib.ColorDelegator.make_pat" kwlist = keyword.kwlist + additional_keywords builtinlist = [ str(name) for name in dir(builtins) if not name.startswith("_") ] + additional_builtins repeated = set(kwlist) & set(builtinlist) for repeated_element in repeated: kwlist.remove(repeated_element) kw = r"\b" + any("keyword", kwlist) + r"\b" builtin = r"([^.'\"\\#]\b|^)" + any("builtin", builtinlist) + r"\b" comment = any("comment", [r"#[^\n]*"]) instance = any( "instance", [ r"\bself\b", r"\bcls\b", (r"^\s*@([a-zA-Z_][a-zA-Z0-9_]*)" r"(\.[a-zA-Z_][a-zA-Z0-9_]*)*"), ], ) number_regex = [ r"\b[+-]?[0-9]+[lLjJ]?\b", r"\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b", r"\b[+-]?0[oO][0-7]+[lL]?\b", r"\b[+-]?0[bB][01]+[lL]?\b", r"\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?[jJ]?\b", ] # Based on # https://github.com/python/cpython/blob/ # 81950495ba2c36056e0ce48fd37d514816c26747/Lib/tokenize.py#L117 # In order: Hexnumber, Binnumber, Octnumber, Decnumber, # Pointfloat + Exponent, Expfloat, Imagnumber number_regex = [ r"\b[+-]?0[xX](?:_?[0-9A-Fa-f])+[lL]?\b", r"\b[+-]?0[bB](?:_?[01])+[lL]?\b", r"\b[+-]?0[oO](?:_?[0-7])+[lL]?\b", r"\b[+-]?(?:0(?:_?0)*|[1-9](?:_?[0-9])*)[lL]?\b", r"\b((\.[0-9](?:_?[0-9])*')|\.[0-9](?:_?[0-9])*)" "([eE][+-]?[0-9](?:_?[0-9])*)?[jJ]?\b", r"\b[0-9](?:_?[0-9])*([eE][+-]?[0-9](?:_?[0-9])*)?[jJ]?\b", r"\b[0-9](?:_?[0-9])*[jJ]\b", ] number = any("number", number_regex) sqstring = r"(\b[rRuU])?'[^'\\\n]*(\\.[^'\\\n]*)*'?" dqstring = r'(\b[rRuU])?"[^"\\\n]*(\\.[^"\\\n]*)*"?' uf_sqstring = r"(\b[rRuU])?'[^'\\\n]*(\\.[^'\\\n]*)*(\\)$(?!')$" uf_dqstring = r'(\b[rRuU])?"[^"\\\n]*(\\.[^"\\\n]*)*(\\)$(?!")$' sq3string = r"(\b[rRuU])?'''[^'\\]*((\\.|'(?!''))[^'\\]*)*(''')?" dq3string = r'(\b[rRuU])?"""[^"\\]*((\\.|"(?!""))[^"\\]*)*(""")?' uf_sq3string = r"(\b[rRuU])?'''[^'\\]*((\\.|'(?!''))[^'\\]*)*(\\)?(?!''')$" uf_dq3string = r'(\b[rRuU])?"""[^"\\]*((\\.|"(?!""))[^"\\]*)*(\\)?(?!""")$' string = any("string", [sq3string, dq3string, sqstring, dqstring]) ufstring1 = any("uf_sqstring", [uf_sqstring]) ufstring2 = any("uf_dqstring", [uf_dqstring]) ufstring3 = any("uf_sq3string", [uf_sq3string]) ufstring4 = any("uf_dq3string", [uf_dq3string]) return "|".join( [ instance, kw, builtin, comment, ufstring1, ufstring2, ufstring3, ufstring4, string, number, any("SYNC", [r"\n"]), ] ) class PythonSH(BaseSH): """Python Syntax Highlighter""" # Syntax highlighting rules: add_kw = ["async", "await"] PROG = re.compile(make_python_patterns(additional_keywords=add_kw), re.S) IDPROG = re.compile(r"\s+(\w+)", re.S) ASPROG = re.compile(r".*?\b(as)\b") # Syntax highlighting states (from one text block to another): ( NORMAL, INSIDE_SQ3STRING, INSIDE_DQ3STRING, INSIDE_SQSTRING, INSIDE_DQSTRING, ) = list(range(5)) # Comments suitable for Outline Explorer OECOMMENT = re.compile(r"^(# ?--[-]+|##[#]+ )[ -]*[^- ]+") def __init__(self, parent, font=None, color_scheme=None): BaseSH.__init__(self, parent, font, color_scheme) self.import_statements = {} self.found_cell_separators = False def highlightBlock(self, text): """ :param text: """ text = str(text) prev_state = self.previousBlockState() if prev_state == self.INSIDE_DQ3STRING: offset = -4 text = r'""" ' + text elif prev_state == self.INSIDE_SQ3STRING: offset = -4 text = r"''' " + text elif prev_state == self.INSIDE_DQSTRING: offset = -2 text = r'" ' + text elif prev_state == self.INSIDE_SQSTRING: offset = -2 text = r"' " + text else: offset = 0 prev_state = self.NORMAL import_stmt = None self.setFormat(0, len(text), self.formats["normal"]) state = self.NORMAL match = self.PROG.search(text) while match: for key, value in list(match.groupdict().items()): if value: start, end = match.span(key) start = max([0, start + offset]) end = max([0, end + offset]) if key == "uf_sq3string": self.setFormat(start, end - start, self.formats["string"]) state = self.INSIDE_SQ3STRING elif key == "uf_dq3string": self.setFormat(start, end - start, self.formats["string"]) state = self.INSIDE_DQ3STRING elif key == "uf_sqstring": self.setFormat(start, end - start, self.formats["string"]) state = self.INSIDE_SQSTRING elif key == "uf_dqstring": self.setFormat(start, end - start, self.formats["string"]) state = self.INSIDE_DQSTRING else: self.setFormat(start, end - start, self.formats[key]) if key == "keyword": if value in ("def", "class"): match1 = self.IDPROG.match(text, end) if match1: start1, end1 = match1.span(1) self.setFormat( start1, end1 - start1, self.formats["definition"], ) elif value == "import": import_stmt = text.strip() # color all the "as" words on same line, except # if in a comment; cheap approximation to the # truth if "#" in text: endpos = text.index("#") else: endpos = len(text) while True: match1 = self.ASPROG.match(text, end, endpos) if not match1: break start, end = match1.span(1) self.setFormat( start, end - start, self.formats["keyword"] ) match = self.PROG.search(text, match.end()) self.setCurrentBlockState(state) # Use normal format for indentation and trailing spaces. self.formats["leading"] = self.formats["normal"] self.formats["trailing"] = self.formats["normal"] self.highlight_spaces(text, offset) if import_stmt is not None: block_nb = self.currentBlock().blockNumber() self.import_statements[block_nb] = import_stmt def get_import_statements(self): """ :return: """ return list(self.import_statements.values()) def rehighlight(self): """ """ self.import_statements = {} self.found_cell_separators = False BaseSH.rehighlight(self) # ============================================================================== # Cython syntax highlighter # ============================================================================== C_TYPES = "bool char double enum float int long mutable short signed struct unsigned void NULL" class CythonSH(PythonSH): """Cython Syntax Highlighter""" ADDITIONAL_KEYWORDS = [ "cdef", "ctypedef", "cpdef", "inline", "cimport", "extern", "include", "begin", "end", "by", "gil", "nogil", "const", "public", "readonly", "fused", "static", "api", "DEF", "IF", "ELIF", "ELSE", ] ADDITIONAL_BUILTINS = C_TYPES.split() + [ "array", "bint", "Py_ssize_t", "intern", "reload", "sizeof", "NULL", ] PROG = re.compile( make_python_patterns(ADDITIONAL_KEYWORDS, ADDITIONAL_BUILTINS), re.S ) IDPROG = re.compile(r"\s+([\w\.]+)", re.S) # ============================================================================== # Enaml syntax highlighter # ============================================================================== class EnamlSH(PythonSH): """Enaml Syntax Highlighter""" ADDITIONAL_KEYWORDS = [ "enamldef", "template", "attr", "event", "const", "alias", "func", ] ADDITIONAL_BUILTINS = [] PROG = re.compile( make_python_patterns(ADDITIONAL_KEYWORDS, ADDITIONAL_BUILTINS), re.S ) IDPROG = re.compile(r"\s+([\w\.]+)", re.S) # ============================================================================== # C/C++ syntax highlighter # ============================================================================== C_KEYWORDS1 = "and and_eq bitand bitor break case catch const const_cast continue default delete do dynamic_cast else explicit export extern for friend goto if inline namespace new not not_eq operator or or_eq private protected public register reinterpret_cast return sizeof static static_cast switch template throw try typedef typeid typename union using virtual while xor xor_eq" C_KEYWORDS2 = "a addindex addtogroup anchor arg attention author b brief bug c class code date def defgroup deprecated dontinclude e em endcode endhtmlonly ifdef endif endlatexonly endlink endverbatim enum example exception f$ file fn hideinitializer htmlinclude htmlonly if image include ingroup internal invariant interface latexonly li line link mainpage name namespace nosubgrouping note overload p page par param post pre ref relates remarks return retval sa section see showinitializer since skip skipline subsection test throw todo typedef union until var verbatim verbinclude version warning weakgroup" C_KEYWORDS3 = "asm auto class compl false true volatile wchar_t" def make_generic_c_patterns( keywords, builtins, instance=None, define=None, comment=None ): "Strongly inspired from idlelib.ColorDelegator.make_pat" kw = r"\b" + any("keyword", keywords.split()) + r"\b" builtin = r"\b" + any("builtin", builtins.split() + C_TYPES.split()) + r"\b" if comment is None: comment = any("comment", [r"//[^\n]*", r"\/\*(.*?)\*\/"]) comment_start = any("comment_start", [r"\/\*"]) comment_end = any("comment_end", [r"\*\/"]) if instance is None: instance = any("instance", [r"\bthis\b"]) number = any( "number", [ r"\b[+-]?[0-9]+[lL]?\b", r"\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b", r"\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b", ], ) sqstring = r"(\b[rRuU])?'[^'\\\n]*(\\.[^'\\\n]*)*'?" dqstring = r'(\b[rRuU])?"[^"\\\n]*(\\.[^"\\\n]*)*"?' string = any("string", [sqstring, dqstring]) if define is None: define = any("define", [r"#[^\n]*"]) return "|".join( [ instance, kw, comment, string, number, comment_start, comment_end, builtin, define, any("SYNC", [r"\n"]), ] ) def make_cpp_patterns(): return make_generic_c_patterns(C_KEYWORDS1 + " " + C_KEYWORDS2, C_KEYWORDS3) class CppSH(BaseSH): """C/C++ Syntax Highlighter""" # Syntax highlighting rules: PROG = re.compile(make_cpp_patterns(), re.S) # Syntax highlighting states (from one text block to another): NORMAL = 0 INSIDE_COMMENT = 1 def __init__(self, parent, font=None, color_scheme=None): BaseSH.__init__(self, parent, font, color_scheme) def highlightBlock(self, text): """Implement highlight specific for C/C++.""" text = str(text) inside_comment = self.previousBlockState() == self.INSIDE_COMMENT self.setFormat( 0, len(text), self.formats["comment" if inside_comment else "normal"], ) match = self.PROG.search(text) index = 0 while match: for key, value in list(match.groupdict().items()): if value: start, end = match.span(key) index += end - start if key == "comment_start": inside_comment = True self.setFormat( start, len(text) - start, self.formats["comment"] ) elif key == "comment_end": inside_comment = False self.setFormat(start, end - start, self.formats["comment"]) elif inside_comment: self.setFormat(start, end - start, self.formats["comment"]) elif key == "define": self.setFormat(start, end - start, self.formats["number"]) else: self.setFormat(start, end - start, self.formats[key]) match = self.PROG.search(text, match.end()) last_state = self.INSIDE_COMMENT if inside_comment else self.NORMAL self.setCurrentBlockState(last_state) def make_opencl_patterns(): # Keywords: kwstr1 = "cl_char cl_uchar cl_short cl_ushort cl_int cl_uint cl_long cl_ulong cl_half cl_float cl_double cl_platform_id cl_device_id cl_context cl_command_queue cl_mem cl_program cl_kernel cl_event cl_sampler cl_bool cl_bitfield cl_device_type cl_platform_info cl_device_info cl_device_address_info cl_device_fp_config cl_device_mem_cache_type cl_device_local_mem_type cl_device_exec_capabilities cl_command_queue_properties cl_context_properties cl_context_info cl_command_queue_info cl_channel_order cl_channel_type cl_mem_flags cl_mem_object_type cl_mem_info cl_image_info cl_addressing_mode cl_filter_mode cl_sampler_info cl_map_flags cl_program_info cl_program_build_info cl_build_status cl_kernel_info cl_kernel_work_group_info cl_event_info cl_command_type cl_profiling_info cl_image_format" # Constants: kwstr2 = "CL_FALSE, CL_TRUE, CL_PLATFORM_PROFILE, CL_PLATFORM_VERSION, CL_PLATFORM_NAME, CL_PLATFORM_VENDOR, CL_PLATFORM_EXTENSIONS, CL_DEVICE_TYPE_DEFAULT , CL_DEVICE_TYPE_CPU, CL_DEVICE_TYPE_GPU, CL_DEVICE_TYPE_ACCELERATOR, CL_DEVICE_TYPE_ALL, CL_DEVICE_TYPE, CL_DEVICE_VENDOR_ID, CL_DEVICE_MAX_COMPUTE_UNITS, CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS, CL_DEVICE_MAX_WORK_GROUP_SIZE, CL_DEVICE_MAX_WORK_ITEM_SIZES, CL_DEVICE_PREFERRED_VECTOR_WIDTH_CHAR, CL_DEVICE_PREFERRED_VECTOR_WIDTH_SHORT, CL_DEVICE_PREFERRED_VECTOR_WIDTH_INT, CL_DEVICE_PREFERRED_VECTOR_WIDTH_LONG, CL_DEVICE_PREFERRED_VECTOR_WIDTH_FLOAT, CL_DEVICE_PREFERRED_VECTOR_WIDTH_DOUBLE, CL_DEVICE_MAX_CLOCK_FREQUENCY, CL_DEVICE_ADDRESS_BITS, CL_DEVICE_MAX_READ_IMAGE_ARGS, CL_DEVICE_MAX_WRITE_IMAGE_ARGS, CL_DEVICE_MAX_MEM_ALLOC_SIZE, CL_DEVICE_IMAGE2D_MAX_WIDTH, CL_DEVICE_IMAGE2D_MAX_HEIGHT, CL_DEVICE_IMAGE3D_MAX_WIDTH, CL_DEVICE_IMAGE3D_MAX_HEIGHT, CL_DEVICE_IMAGE3D_MAX_DEPTH, CL_DEVICE_IMAGE_SUPPORT, CL_DEVICE_MAX_PARAMETER_SIZE, CL_DEVICE_MAX_SAMPLERS, CL_DEVICE_MEM_BASE_ADDR_ALIGN, CL_DEVICE_MIN_DATA_TYPE_ALIGN_SIZE, CL_DEVICE_SINGLE_FP_CONFIG, CL_DEVICE_GLOBAL_MEM_CACHE_TYPE, CL_DEVICE_GLOBAL_MEM_CACHELINE_SIZE, CL_DEVICE_GLOBAL_MEM_CACHE_SIZE, CL_DEVICE_GLOBAL_MEM_SIZE, CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE, CL_DEVICE_MAX_CONSTANT_ARGS, CL_DEVICE_LOCAL_MEM_TYPE, CL_DEVICE_LOCAL_MEM_SIZE, CL_DEVICE_ERROR_CORRECTION_SUPPORT, CL_DEVICE_PROFILING_TIMER_RESOLUTION, CL_DEVICE_ENDIAN_LITTLE, CL_DEVICE_AVAILABLE, CL_DEVICE_COMPILER_AVAILABLE, CL_DEVICE_EXECUTION_CAPABILITIES, CL_DEVICE_QUEUE_PROPERTIES, CL_DEVICE_NAME, CL_DEVICE_VENDOR, CL_DRIVER_VERSION, CL_DEVICE_PROFILE, CL_DEVICE_VERSION, CL_DEVICE_EXTENSIONS, CL_DEVICE_PLATFORM, CL_FP_DENORM, CL_FP_INF_NAN, CL_FP_ROUND_TO_NEAREST, CL_FP_ROUND_TO_ZERO, CL_FP_ROUND_TO_INF, CL_FP_FMA, CL_NONE, CL_READ_ONLY_CACHE, CL_READ_WRITE_CACHE, CL_LOCAL, CL_GLOBAL, CL_EXEC_KERNEL, CL_EXEC_NATIVE_KERNEL, CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE, CL_QUEUE_PROFILING_ENABLE, CL_CONTEXT_REFERENCE_COUNT, CL_CONTEXT_DEVICES, CL_CONTEXT_PROPERTIES, CL_CONTEXT_PLATFORM, CL_QUEUE_CONTEXT, CL_QUEUE_DEVICE, CL_QUEUE_REFERENCE_COUNT, CL_QUEUE_PROPERTIES, CL_MEM_READ_WRITE, CL_MEM_WRITE_ONLY, CL_MEM_READ_ONLY, CL_MEM_USE_HOST_PTR, CL_MEM_ALLOC_HOST_PTR, CL_MEM_COPY_HOST_PTR, CL_R, CL_A, CL_RG, CL_RA, CL_RGB, CL_RGBA, CL_BGRA, CL_ARGB, CL_INTENSITY, CL_LUMINANCE, CL_SNORM_INT8, CL_SNORM_INT16, CL_UNORM_INT8, CL_UNORM_INT16, CL_UNORM_SHORT_565, CL_UNORM_SHORT_555, CL_UNORM_INT_101010, CL_SIGNED_INT8, CL_SIGNED_INT16, CL_SIGNED_INT32, CL_UNSIGNED_INT8, CL_UNSIGNED_INT16, CL_UNSIGNED_INT32, CL_HALF_FLOAT, CL_FLOAT, CL_MEM_OBJECT_BUFFER, CL_MEM_OBJECT_IMAGE2D, CL_MEM_OBJECT_IMAGE3D, CL_MEM_TYPE, CL_MEM_FLAGS, CL_MEM_SIZECL_MEM_HOST_PTR, CL_MEM_HOST_PTR, CL_MEM_MAP_COUNT, CL_MEM_REFERENCE_COUNT, CL_MEM_CONTEXT, CL_IMAGE_FORMAT, CL_IMAGE_ELEMENT_SIZE, CL_IMAGE_ROW_PITCH, CL_IMAGE_SLICE_PITCH, CL_IMAGE_WIDTH, CL_IMAGE_HEIGHT, CL_IMAGE_DEPTH, CL_ADDRESS_NONE, CL_ADDRESS_CLAMP_TO_EDGE, CL_ADDRESS_CLAMP, CL_ADDRESS_REPEAT, CL_FILTER_NEAREST, CL_FILTER_LINEAR, CL_SAMPLER_REFERENCE_COUNT, CL_SAMPLER_CONTEXT, CL_SAMPLER_NORMALIZED_COORDS, CL_SAMPLER_ADDRESSING_MODE, CL_SAMPLER_FILTER_MODE, CL_MAP_READ, CL_MAP_WRITE, CL_PROGRAM_REFERENCE_COUNT, CL_PROGRAM_CONTEXT, CL_PROGRAM_NUM_DEVICES, CL_PROGRAM_DEVICES, CL_PROGRAM_SOURCE, CL_PROGRAM_BINARY_SIZES, CL_PROGRAM_BINARIES, CL_PROGRAM_BUILD_STATUS, CL_PROGRAM_BUILD_OPTIONS, CL_PROGRAM_BUILD_LOG, CL_BUILD_SUCCESS, CL_BUILD_NONE, CL_BUILD_ERROR, CL_BUILD_IN_PROGRESS, CL_KERNEL_FUNCTION_NAME, CL_KERNEL_NUM_ARGS, CL_KERNEL_REFERENCE_COUNT, CL_KERNEL_CONTEXT, CL_KERNEL_PROGRAM, CL_KERNEL_WORK_GROUP_SIZE, CL_KERNEL_COMPILE_WORK_GROUP_SIZE, CL_KERNEL_LOCAL_MEM_SIZE, CL_EVENT_COMMAND_QUEUE, CL_EVENT_COMMAND_TYPE, CL_EVENT_REFERENCE_COUNT, CL_EVENT_COMMAND_EXECUTION_STATUS, CL_COMMAND_NDRANGE_KERNEL, CL_COMMAND_TASK, CL_COMMAND_NATIVE_KERNEL, CL_COMMAND_READ_BUFFER, CL_COMMAND_WRITE_BUFFER, CL_COMMAND_COPY_BUFFER, CL_COMMAND_READ_IMAGE, CL_COMMAND_WRITE_IMAGE, CL_COMMAND_COPY_IMAGE, CL_COMMAND_COPY_IMAGE_TO_BUFFER, CL_COMMAND_COPY_BUFFER_TO_IMAGE, CL_COMMAND_MAP_BUFFER, CL_COMMAND_MAP_IMAGE, CL_COMMAND_UNMAP_MEM_OBJECT, CL_COMMAND_MARKER, CL_COMMAND_ACQUIRE_GL_OBJECTS, CL_COMMAND_RELEASE_GL_OBJECTS, command execution status, CL_COMPLETE, CL_RUNNING, CL_SUBMITTED, CL_QUEUED, CL_PROFILING_COMMAND_QUEUED, CL_PROFILING_COMMAND_SUBMIT, CL_PROFILING_COMMAND_START, CL_PROFILING_COMMAND_END, CL_CHAR_BIT, CL_SCHAR_MAX, CL_SCHAR_MIN, CL_CHAR_MAX, CL_CHAR_MIN, CL_UCHAR_MAX, CL_SHRT_MAX, CL_SHRT_MIN, CL_USHRT_MAX, CL_INT_MAX, CL_INT_MIN, CL_UINT_MAX, CL_LONG_MAX, CL_LONG_MIN, CL_ULONG_MAX, CL_FLT_DIG, CL_FLT_MANT_DIG, CL_FLT_MAX_10_EXP, CL_FLT_MAX_EXP, CL_FLT_MIN_10_EXP, CL_FLT_MIN_EXP, CL_FLT_RADIX, CL_FLT_MAX, CL_FLT_MIN, CL_FLT_EPSILON, CL_DBL_DIG, CL_DBL_MANT_DIG, CL_DBL_MAX_10_EXP, CL_DBL_MAX_EXP, CL_DBL_MIN_10_EXP, CL_DBL_MIN_EXP, CL_DBL_RADIX, CL_DBL_MAX, CL_DBL_MIN, CL_DBL_EPSILON, CL_SUCCESS, CL_DEVICE_NOT_FOUND, CL_DEVICE_NOT_AVAILABLE, CL_COMPILER_NOT_AVAILABLE, CL_MEM_OBJECT_ALLOCATION_FAILURE, CL_OUT_OF_RESOURCES, CL_OUT_OF_HOST_MEMORY, CL_PROFILING_INFO_NOT_AVAILABLE, CL_MEM_COPY_OVERLAP, CL_IMAGE_FORMAT_MISMATCH, CL_IMAGE_FORMAT_NOT_SUPPORTED, CL_BUILD_PROGRAM_FAILURE, CL_MAP_FAILURE, CL_INVALID_VALUE, CL_INVALID_DEVICE_TYPE, CL_INVALID_PLATFORM, CL_INVALID_DEVICE, CL_INVALID_CONTEXT, CL_INVALID_QUEUE_PROPERTIES, CL_INVALID_COMMAND_QUEUE, CL_INVALID_HOST_PTR, CL_INVALID_MEM_OBJECT, CL_INVALID_IMAGE_FORMAT_DESCRIPTOR, CL_INVALID_IMAGE_SIZE, CL_INVALID_SAMPLER, CL_INVALID_BINARY, CL_INVALID_BUILD_OPTIONS, CL_INVALID_PROGRAM, CL_INVALID_PROGRAM_EXECUTABLE, CL_INVALID_KERNEL_NAME, CL_INVALID_KERNEL_DEFINITION, CL_INVALID_KERNEL, CL_INVALID_ARG_INDEX, CL_INVALID_ARG_VALUE, CL_INVALID_ARG_SIZE, CL_INVALID_KERNEL_ARGS, CL_INVALID_WORK_DIMENSION, CL_INVALID_WORK_GROUP_SIZE, CL_INVALID_WORK_ITEM_SIZE, CL_INVALID_GLOBAL_OFFSET, CL_INVALID_EVENT_WAIT_LIST, CL_INVALID_EVENT, CL_INVALID_OPERATION, CL_INVALID_GL_OBJECT, CL_INVALID_BUFFER_SIZE, CL_INVALID_MIP_LEVEL, CL_INVALID_GLOBAL_WORK_SIZE" # Functions: builtins = "clGetPlatformIDs, clGetPlatformInfo, clGetDeviceIDs, clGetDeviceInfo, clCreateContext, clCreateContextFromType, clReleaseContext, clGetContextInfo, clCreateCommandQueue, clRetainCommandQueue, clReleaseCommandQueue, clGetCommandQueueInfo, clSetCommandQueueProperty, clCreateBuffer, clCreateImage2D, clCreateImage3D, clRetainMemObject, clReleaseMemObject, clGetSupportedImageFormats, clGetMemObjectInfo, clGetImageInfo, clCreateSampler, clRetainSampler, clReleaseSampler, clGetSamplerInfo, clCreateProgramWithSource, clCreateProgramWithBinary, clRetainProgram, clReleaseProgram, clBuildProgram, clUnloadCompiler, clGetProgramInfo, clGetProgramBuildInfo, clCreateKernel, clCreateKernelsInProgram, clRetainKernel, clReleaseKernel, clSetKernelArg, clGetKernelInfo, clGetKernelWorkGroupInfo, clWaitForEvents, clGetEventInfo, clRetainEvent, clReleaseEvent, clGetEventProfilingInfo, clFlush, clFinish, clEnqueueReadBuffer, clEnqueueWriteBuffer, clEnqueueCopyBuffer, clEnqueueReadImage, clEnqueueWriteImage, clEnqueueCopyImage, clEnqueueCopyImageToBuffer, clEnqueueCopyBufferToImage, clEnqueueMapBuffer, clEnqueueMapImage, clEnqueueUnmapMemObject, clEnqueueNDRangeKernel, clEnqueueTask, clEnqueueNativeKernel, clEnqueueMarker, clEnqueueWaitForEvents, clEnqueueBarrier" # Qualifiers: qualifiers = "__global __local __constant __private __kernel" keyword_list = C_KEYWORDS1 + " " + C_KEYWORDS2 + " " + kwstr1 + " " + kwstr2 builtin_list = C_KEYWORDS3 + " " + builtins + " " + qualifiers return make_generic_c_patterns(keyword_list, builtin_list) class OpenCLSH(CppSH): """OpenCL Syntax Highlighter""" PROG = re.compile(make_opencl_patterns(), re.S) # ============================================================================== # Fortran Syntax Highlighter # ============================================================================== def make_fortran_patterns(): "Strongly inspired from idlelib.ColorDelegator.make_pat" kwstr = "access action advance allocatable allocate apostrophe assign assignment associate asynchronous backspace bind blank blockdata call case character class close common complex contains continue cycle data deallocate decimal delim default dimension direct do dowhile double doubleprecision else elseif elsewhere encoding end endassociate endblockdata enddo endfile endforall endfunction endif endinterface endmodule endprogram endselect endsubroutine endtype endwhere entry eor equivalence err errmsg exist exit external file flush fmt forall form format formatted function go goto id if implicit in include inout integer inquire intent interface intrinsic iomsg iolength iostat kind len logical module name named namelist nextrec nml none nullify number only open opened operator optional out pad parameter pass pause pending pointer pos position precision print private program protected public quote read readwrite real rec recl recursive result return rewind save select selectcase selecttype sequential sign size stat status stop stream subroutine target then to type unformatted unit use value volatile wait where while write" bistr1 = "abs achar acos acosd adjustl adjustr aimag aimax0 aimin0 aint ajmax0 ajmin0 akmax0 akmin0 all allocated alog alog10 amax0 amax1 amin0 amin1 amod anint any asin asind associated atan atan2 atan2d atand bitest bitl bitlr bitrl bjtest bit_size bktest break btest cabs ccos cdabs cdcos cdexp cdlog cdsin cdsqrt ceiling cexp char clog cmplx conjg cos cosd cosh count cpu_time cshift csin csqrt dabs dacos dacosd dasin dasind datan datan2 datan2d datand date date_and_time dble dcmplx dconjg dcos dcosd dcosh dcotan ddim dexp dfloat dflotk dfloti dflotj digits dim dimag dint dlog dlog10 dmax1 dmin1 dmod dnint dot_product dprod dreal dsign dsin dsind dsinh dsqrt dtan dtand dtanh eoshift epsilon errsns exp exponent float floati floatj floatk floor fraction free huge iabs iachar iand ibclr ibits ibset ichar idate idim idint idnint ieor ifix iiabs iiand iibclr iibits iibset iidim iidint iidnnt iieor iifix iint iior iiqint iiqnnt iishft iishftc iisign ilen imax0 imax1 imin0 imin1 imod index inint inot int int1 int2 int4 int8 iqint iqnint ior ishft ishftc isign isnan izext jiand jibclr jibits jibset jidim jidint jidnnt jieor jifix jint jior jiqint jiqnnt jishft jishftc jisign jmax0 jmax1 jmin0 jmin1 jmod jnint jnot jzext kiabs kiand kibclr kibits kibset kidim kidint kidnnt kieor kifix kind kint kior kishft kishftc kisign kmax0 kmax1 kmin0 kmin1 kmod knint knot kzext lbound leadz len len_trim lenlge lge lgt lle llt log log10 logical lshift malloc matmul max max0 max1 maxexponent maxloc maxval merge min min0 min1 minexponent minloc minval mod modulo mvbits nearest nint not nworkers number_of_processors pack popcnt poppar precision present product radix random random_number random_seed range real repeat reshape rrspacing rshift scale scan secnds selected_int_kind selected_real_kind set_exponent shape sign sin sind sinh size sizeof sngl snglq spacing spread sqrt sum system_clock tan tand tanh tiny transfer transpose trim ubound unpack verify" bistr2 = "cdabs cdcos cdexp cdlog cdsin cdsqrt cotan cotand dcmplx dconjg dcotan dcotand decode dimag dll_export dll_import doublecomplex dreal dvchk encode find flen flush getarg getcharqq getcl getdat getenv gettim hfix ibchng identifier imag int1 int2 int4 intc intrup invalop iostat_msg isha ishc ishl jfix lacfar locking locnear map nargs nbreak ndperr ndpexc offset ovefl peekcharqq precfill prompt qabs qacos qacosd qasin qasind qatan qatand qatan2 qcmplx qconjg qcos qcosd qcosh qdim qexp qext qextd qfloat qimag qlog qlog10 qmax1 qmin1 qmod qreal qsign qsin qsind qsinh qsqrt qtan qtand qtanh ran rand randu rewrite segment setdat settim system timer undfl unlock union val virtual volatile zabs zcos zexp zlog zsin zsqrt" kw = r"\b" + any("keyword", kwstr.split()) + r"\b" builtin = r"\b" + any("builtin", bistr1.split() + bistr2.split()) + r"\b" comment = any("comment", [r"\![^\n]*"]) number = any( "number", [ r"\b[+-]?[0-9]+[lL]?\b", r"\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b", r"\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b", ], ) sqstring = r"(\b[rRuU])?'[^'\\\n]*(\\.[^'\\\n]*)*'?" dqstring = r'(\b[rRuU])?"[^"\\\n]*(\\.[^"\\\n]*)*"?' string = any("string", [sqstring, dqstring]) return "|".join([kw, comment, string, number, builtin, any("SYNC", [r"\n"])]) class FortranSH(BaseSH): """Fortran Syntax Highlighter""" # Syntax highlighting rules: PROG = re.compile(make_fortran_patterns(), re.S | re.I) IDPROG = re.compile(r"\s+(\w+)", re.S) # Syntax highlighting states (from one text block to another): NORMAL = 0 def __init__(self, parent, font=None, color_scheme=None): BaseSH.__init__(self, parent, font, color_scheme) def highlightBlock(self, text): """Implement highlight specific for Fortran.""" text = str(text) self.setFormat(0, len(text), self.formats["normal"]) match = self.PROG.search(text) index = 0 while match: for key, value in list(match.groupdict().items()): if value: start, end = match.span(key) index += end - start self.setFormat(start, end - start, self.formats[key]) if value.lower() in ("subroutine", "module", "function"): match1 = self.IDPROG.match(text, end) if match1: start1, end1 = match1.span(1) self.setFormat( start1, end1 - start1, self.formats["definition"] ) match = self.PROG.search(text, match.end()) class Fortran77SH(FortranSH): """Fortran 77 Syntax Highlighter""" def highlightBlock(self, text): """Implement highlight specific for Fortran77.""" text = str(text) if text.startswith(("c", "C")): self.setFormat(0, len(text), self.formats["comment"]) else: FortranSH.highlightBlock(self, text) self.setFormat(0, 5, self.formats["comment"]) self.setFormat(73, max([73, len(text)]), self.formats["comment"]) # ============================================================================== # IDL highlighter # # Contribution from Stuart Mumford (Littlemumford) - 2012-02-02 # See spyder-ide/spyder#850. # ============================================================================== def make_idl_patterns(): """Strongly inspired by idlelib.ColorDelegator.make_pat.""" kwstr = "begin of pro function endfor endif endwhile endrep endcase endswitch end if then else for do while repeat until break case switch common continue exit return goto help message print read retall stop" bistr1 = "a_correlate abs acos adapt_hist_equal alog alog10 amoeba arg_present arra_equal array_indices ascii_template asin assoc atan beseli beselj besel k besely beta bilinear bin_date binary_template dinfgen dinomial blk_con broyden bytarr byte bytscl c_correlate call_external call_function ceil chebyshev check_math chisqr_cvf chisqr_pdf choldc cholsol cindgen clust_wts cluster color_quan colormap_applicable comfit complex complexarr complexround compute_mesh_normals cond congrid conj convert_coord convol coord2to3 correlate cos cosh cramer create_struct crossp crvlength ct_luminance cti_test curvefit cv_coord cvttobm cw_animate cw_arcball cw_bgroup cw_clr_index cw_colorsel cw_defroi cw_field cw_filesel cw_form cw_fslider cw_light_editor cw_orient cw_palette_editor cw_pdmenu cw_rgbslider cw_tmpl cw_zoom dblarr dcindgen dcomplexarr defroi deriv derivsig determ diag_matrix dialog_message dialog_pickfile pialog_printersetup dialog_printjob dialog_read_image dialog_write_image digital_filter dilate dindgen dist double eigenql eigenvec elmhes eof erode erf erfc erfcx execute exp expand_path expint extrac extract_slice f_cvf f_pdf factorial fft file_basename file_dirname file_expand_path file_info file_same file_search file_test file_which filepath findfile findgen finite fix float floor fltarr format_axis_values fstat fulstr fv_test fx_root fz_roots gamma gauss_cvf gauss_pdf gauss2dfit gaussfit gaussint get_drive_list get_kbrd get_screen_size getenv grid_tps grid3 griddata gs_iter hanning hdf_browser hdf_read hilbert hist_2d hist_equal histogram hough hqr ibeta identity idl_validname idlitsys_createtool igamma imaginary indgen int_2d int_3d int_tabulated intarr interpol interpolate invert ioctl ishft julday keword_set krig2d kurtosis kw_test l64indgen label_date label_region ladfit laguerre la_cholmprove la_cholsol la_Determ la_eigenproblem la_eigenql la_eigenvec la_elmhes la_gm_linear_model la_hqr la_invert la_least_square_equality la_least_squares la_linear_equation la_lumprove la_lusol la_trimprove la_trisol leefit legendre linbcg lindgen linfit ll_arc_distance lmfit lmgr lngamma lnp_test locale_get logical_and logical_or logical_true lon64arr lonarr long long64 lsode lu_complex lumprove lusol m_correlate machar make_array map_2points map_image map_patch map_proj_forward map_proj_init map_proj_inverse matrix_multiply matrix_power max md_test mean meanabsdev median memory mesh_clip mesh_decimate mesh_issolid mesh_merge mesh_numtriangles mesh_smooth mesh_surfacearea mesh_validate mesh_volume min min_curve_surf moment morph_close morph_distance morph_gradient morph_histormiss morph_open morph_thin morph_tophat mpeg_open msg_cat_open n_elements n_params n_tags newton norm obj_class obj_isa obj_new obj_valid objarr p_correlate path_sep pcomp pnt_line polar_surface poly poly_2d poly_area poly_fit polyfillv ployshade primes product profile profiles project_vol ptr_new ptr_valid ptrarr qgrid3 qromb qromo qsimp query_bmp query_dicom query_image query_jpeg query_mrsid query_pict query_png query_ppm query_srf query_tiff query_wav r_correlate r_test radon randomn randomu ranks read_ascii read_binary read_bmp read_dicom read_image read_mrsid read_png read_spr read_sylk read_tiff read_wav read_xwd real_part rebin recall_commands recon3 reform region_grow regress replicate reverse rk4 roberts rot rotate round routine_info rs_test s_test savgol search2d search3d sfit shift shmdebug shmvar simplex sin sindgen sinh size skewness smooth sobel sort sph_scat spher_harm spl_init spl_interp spline spline_p sprsab sprsax sprsin sprstp sqrt standardize stddev strarr strcmp strcompress stregex string strjoin strlen strlowcase strmatch strmessage strmid strpos strsplit strtrim strupcase svdfit svsol swap_endian systime t_cvf t_pdf tag_names tan tanh temporary tetra_clip tetra_surface tetra_volume thin timegen tm_test total trace transpose tri_surf trigrid trisol ts_coef ts_diff ts_fcast ts_smooth tvrd uindgen unit uintarr ul64indgen ulindgen ulon64arr ulonarr ulong ulong64 uniq value_locate variance vert_t3d voigt voxel_proj warp_tri watershed where widget_actevix widget_base widget_button widget_combobox widget_draw widget_droplist widget_event widget_info widget_label widget_list widget_propertsheet widget_slider widget_tab widget_table widget_text widget_tree write_sylk wtn xfont xregistered xsq_test" bistr2 = "annotate arrow axis bar_plot blas_axpy box_cursor breakpoint byteorder caldata calendar call_method call_procedure catch cd cir_3pnt close color_convert compile_opt constrained_min contour copy_lun cpu create_view cursor cw_animate_getp cw_animate_load cw_animate_run cw_light_editor_get cw_light_editor_set cw_palette_editor_get cw_palette_editor_set define_key define_msgblk define_msgblk_from_file defsysv delvar device dfpmin dissolve dlm_load doc_librar draw_roi efont empty enable_sysrtn erase errplot expand file_chmod file_copy file_delete file_lines file_link file_mkdir file_move file_readlink flick flow3 flush forward_function free_lun funct gamma_ct get_lun grid_input h_eq_ct h_eq_int heap_free heap_gc hls hsv icontour iimage image_cont image_statistics internal_volume iplot isocontour isosurface isurface itcurrent itdelete itgetcurrent itregister itreset ivolume journal la_choldc la_ludc la_svd la_tridc la_triql la_trired linkimage loadct ludc make_dll map_continents map_grid map_proj_info map_set mesh_obj mk_html_help modifyct mpeg_close mpeg_put mpeg_save msg_cat_close msg_cat_compile multi obj_destroy on_error on_ioerror online_help openr openw openu oplot oploterr particle_trace path_cache plot plot_3dbox plot_field ploterr plots point_lun polar_contour polyfill polywarp popd powell printf printd ps_show_fonts psafm pseudo ptr_free pushd qhull rdpix readf read_interfile read_jpeg read_pict read_ppm read_srf read_wave read_x11_bitmap reads readu reduce_colors register_cursor replicate_inplace resolve_all resolve_routine restore save scale3 scale3d set_plot set_shading setenv setup_keys shade_surf shade_surf_irr shade_volume shmmap show3 showfont skip_lun slicer3 slide_image socket spawn sph_4pnt streamline stretch strput struct_assign struct_hide surface surfr svdc swap_enian_inplace t3d tek_color threed time_test2 triangulate triql trired truncate_lun tv tvcrs tvlct tvscl usersym vector_field vel velovect voronoi wait wdelete wf_draw widget_control widget_displaycontextmenu window write_bmp write_image write_jpeg write_nrif write_pict write_png write_ppm write_spr write_srf write_tiff write_wav write_wave writeu wset wshow xbm_edit xdisplayfile xdxf xinteranimate xloadct xmanager xmng_tmpl xmtool xobjview xobjview_rotate xobjview_write_image xpalette xpcolo xplot3d xroi xsurface xvaredit xvolume xyouts zoom zoom_24" kw = r"\b" + any("keyword", kwstr.split()) + r"\b" builtin = r"\b" + any("builtin", bistr1.split() + bistr2.split()) + r"\b" comment = any("comment", [r"\;[^\n]*"]) number = any( "number", [ r"\b[+-]?[0-9]+[lL]?\b", r"\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b", r"\b\.[0-9]d0|\.d0+[lL]?\b", r"\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b", ], ) sqstring = r"(\b[rRuU])?'[^'\\\n]*(\\.[^'\\\n]*)*'?" dqstring = r'(\b[rRuU])?"[^"\\\n]*(\\.[^"\\\n]*)*"?' string = any("string", [sqstring, dqstring]) return "|".join([kw, comment, string, number, builtin, any("SYNC", [r"\n"])]) class IdlSH(GenericSH): """IDL Syntax Highlighter""" PROG = re.compile(make_idl_patterns(), re.S | re.I) # ============================================================================== # Diff/Patch highlighter # ============================================================================== class DiffSH(BaseSH): """Simple Diff/Patch Syntax Highlighter Class""" def highlightBlock(self, text): """Implement highlight specific Diff/Patch files.""" text = str(text) if text.startswith("+++"): self.setFormat(0, len(text), self.formats["keyword"]) elif text.startswith("---"): self.setFormat(0, len(text), self.formats["keyword"]) elif text.startswith("+"): self.setFormat(0, len(text), self.formats["string"]) elif text.startswith("-"): self.setFormat(0, len(text), self.formats["number"]) elif text.startswith("@"): self.setFormat(0, len(text), self.formats["builtin"]) # ============================================================================== # NSIS highlighter # ============================================================================== def make_nsis_patterns(): "Strongly inspired from idlelib.ColorDelegator.make_pat" kwstr1 = "Abort AddBrandingImage AddSize AllowRootDirInstall AllowSkipFiles AutoCloseWindow BGFont BGGradient BrandingText BringToFront Call CallInstDLL Caption ClearErrors CompletedText ComponentText CopyFiles CRCCheck CreateDirectory CreateFont CreateShortCut Delete DeleteINISec DeleteINIStr DeleteRegKey DeleteRegValue DetailPrint DetailsButtonText DirText DirVar DirVerify EnableWindow EnumRegKey EnumRegValue Exec ExecShell ExecWait Exch ExpandEnvStrings File FileBufSize FileClose FileErrorText FileOpen FileRead FileReadByte FileSeek FileWrite FileWriteByte FindClose FindFirst FindNext FindWindow FlushINI Function FunctionEnd GetCurInstType GetCurrentAddress GetDlgItem GetDLLVersion GetDLLVersionLocal GetErrorLevel GetFileTime GetFileTimeLocal GetFullPathName GetFunctionAddress GetInstDirError GetLabelAddress GetTempFileName Goto HideWindow ChangeUI CheckBitmap Icon IfAbort IfErrors IfFileExists IfRebootFlag IfSilent InitPluginsDir InstallButtonText InstallColors InstallDir InstallDirRegKey InstProgressFlags InstType InstTypeGetText InstTypeSetText IntCmp IntCmpU IntFmt IntOp IsWindow LangString LicenseBkColor LicenseData LicenseForceSelection LicenseLangString LicenseText LoadLanguageFile LogSet LogText MessageBox MiscButtonText Name OutFile Page PageCallbacks PageEx PageExEnd Pop Push Quit ReadEnvStr ReadINIStr ReadRegDWORD ReadRegStr Reboot RegDLL Rename ReserveFile Return RMDir SearchPath Section SectionEnd SectionGetFlags SectionGetInstTypes SectionGetSize SectionGetText SectionIn SectionSetFlags SectionSetInstTypes SectionSetSize SectionSetText SendMessage SetAutoClose SetBrandingImage SetCompress SetCompressor SetCompressorDictSize SetCtlColors SetCurInstType SetDatablockOptimize SetDateSave SetDetailsPrint SetDetailsView SetErrorLevel SetErrors SetFileAttributes SetFont SetOutPath SetOverwrite SetPluginUnload SetRebootFlag SetShellVarContext SetSilent ShowInstDetails ShowUninstDetails ShowWindow SilentInstall SilentUnInstall Sleep SpaceTexts StrCmp StrCpy StrLen SubCaption SubSection SubSectionEnd UninstallButtonText UninstallCaption UninstallIcon UninstallSubCaption UninstallText UninstPage UnRegDLL Var VIAddVersionKey VIProductVersion WindowIcon WriteINIStr WriteRegBin WriteRegDWORD WriteRegExpandStr WriteRegStr WriteUninstaller XPStyle" kwstr2 = "all alwaysoff ARCHIVE auto both bzip2 components current custom details directory false FILE_ATTRIBUTE_ARCHIVE FILE_ATTRIBUTE_HIDDEN FILE_ATTRIBUTE_NORMAL FILE_ATTRIBUTE_OFFLINE FILE_ATTRIBUTE_READONLY FILE_ATTRIBUTE_SYSTEM FILE_ATTRIBUTE_TEMPORARY force grey HIDDEN hide IDABORT IDCANCEL IDIGNORE IDNO IDOK IDRETRY IDYES ifdiff ifnewer instfiles instfiles lastused leave left level license listonly lzma manual MB_ABORTRETRYIGNORE MB_DEFBUTTON1 MB_DEFBUTTON2 MB_DEFBUTTON3 MB_DEFBUTTON4 MB_ICONEXCLAMATION MB_ICONINFORMATION MB_ICONQUESTION MB_ICONSTOP MB_OK MB_OKCANCEL MB_RETRYCANCEL MB_RIGHT MB_SETFOREGROUND MB_TOPMOST MB_YESNO MB_YESNOCANCEL nevershow none NORMAL off OFFLINE on READONLY right RO show silent silentlog SYSTEM TEMPORARY text textonly true try uninstConfirm windows zlib" kwstr3 = "MUI_ABORTWARNING MUI_ABORTWARNING_CANCEL_DEFAULT MUI_ABORTWARNING_TEXT MUI_BGCOLOR MUI_COMPONENTSPAGE_CHECKBITMAP MUI_COMPONENTSPAGE_NODESC MUI_COMPONENTSPAGE_SMALLDESC MUI_COMPONENTSPAGE_TEXT_COMPLIST MUI_COMPONENTSPAGE_TEXT_DESCRIPTION_INFO MUI_COMPONENTSPAGE_TEXT_DESCRIPTION_TITLE MUI_COMPONENTSPAGE_TEXT_INSTTYPE MUI_COMPONENTSPAGE_TEXT_TOP MUI_CUSTOMFUNCTION_ABORT MUI_CUSTOMFUNCTION_GUIINIT MUI_CUSTOMFUNCTION_UNABORT MUI_CUSTOMFUNCTION_UNGUIINIT MUI_DESCRIPTION_TEXT MUI_DIRECTORYPAGE_BGCOLOR MUI_DIRECTORYPAGE_TEXT_DESTINATION MUI_DIRECTORYPAGE_TEXT_TOP MUI_DIRECTORYPAGE_VARIABLE MUI_DIRECTORYPAGE_VERIFYONLEAVE MUI_FINISHPAGE_BUTTON MUI_FINISHPAGE_CANCEL_ENABLED MUI_FINISHPAGE_LINK MUI_FINISHPAGE_LINK_COLOR MUI_FINISHPAGE_LINK_LOCATION MUI_FINISHPAGE_NOAUTOCLOSE MUI_FINISHPAGE_NOREBOOTSUPPORT MUI_FINISHPAGE_REBOOTLATER_DEFAULT MUI_FINISHPAGE_RUN MUI_FINISHPAGE_RUN_FUNCTION MUI_FINISHPAGE_RUN_NOTCHECKED MUI_FINISHPAGE_RUN_PARAMETERS MUI_FINISHPAGE_RUN_TEXT MUI_FINISHPAGE_SHOWREADME MUI_FINISHPAGE_SHOWREADME_FUNCTION MUI_FINISHPAGE_SHOWREADME_NOTCHECKED MUI_FINISHPAGE_SHOWREADME_TEXT MUI_FINISHPAGE_TEXT MUI_FINISHPAGE_TEXT_LARGE MUI_FINISHPAGE_TEXT_REBOOT MUI_FINISHPAGE_TEXT_REBOOTLATER MUI_FINISHPAGE_TEXT_REBOOTNOW MUI_FINISHPAGE_TITLE MUI_FINISHPAGE_TITLE_3LINES MUI_FUNCTION_DESCRIPTION_BEGIN MUI_FUNCTION_DESCRIPTION_END MUI_HEADER_TEXT MUI_HEADER_TRANSPARENT_TEXT MUI_HEADERIMAGE MUI_HEADERIMAGE_BITMAP MUI_HEADERIMAGE_BITMAP_NOSTRETCH MUI_HEADERIMAGE_BITMAP_RTL MUI_HEADERIMAGE_BITMAP_RTL_NOSTRETCH MUI_HEADERIMAGE_RIGHT MUI_HEADERIMAGE_UNBITMAP MUI_HEADERIMAGE_UNBITMAP_NOSTRETCH MUI_HEADERIMAGE_UNBITMAP_RTL MUI_HEADERIMAGE_UNBITMAP_RTL_NOSTRETCH MUI_HWND MUI_ICON MUI_INSTALLCOLORS MUI_INSTALLOPTIONS_DISPLAY MUI_INSTALLOPTIONS_DISPLAY_RETURN MUI_INSTALLOPTIONS_EXTRACT MUI_INSTALLOPTIONS_EXTRACT_AS MUI_INSTALLOPTIONS_INITDIALOG MUI_INSTALLOPTIONS_READ MUI_INSTALLOPTIONS_SHOW MUI_INSTALLOPTIONS_SHOW_RETURN MUI_INSTALLOPTIONS_WRITE MUI_INSTFILESPAGE_ABORTHEADER_SUBTEXT MUI_INSTFILESPAGE_ABORTHEADER_TEXT MUI_INSTFILESPAGE_COLORS MUI_INSTFILESPAGE_FINISHHEADER_SUBTEXT MUI_INSTFILESPAGE_FINISHHEADER_TEXT MUI_INSTFILESPAGE_PROGRESSBAR MUI_LANGDLL_ALLLANGUAGES MUI_LANGDLL_ALWAYSSHOW MUI_LANGDLL_DISPLAY MUI_LANGDLL_INFO MUI_LANGDLL_REGISTRY_KEY MUI_LANGDLL_REGISTRY_ROOT MUI_LANGDLL_REGISTRY_VALUENAME MUI_LANGDLL_WINDOWTITLE MUI_LANGUAGE MUI_LICENSEPAGE_BGCOLOR MUI_LICENSEPAGE_BUTTON MUI_LICENSEPAGE_CHECKBOX MUI_LICENSEPAGE_CHECKBOX_TEXT MUI_LICENSEPAGE_RADIOBUTTONS MUI_LICENSEPAGE_RADIOBUTTONS_TEXT_ACCEPT MUI_LICENSEPAGE_RADIOBUTTONS_TEXT_DECLINE MUI_LICENSEPAGE_TEXT_BOTTOM MUI_LICENSEPAGE_TEXT_TOP MUI_PAGE_COMPONENTS MUI_PAGE_CUSTOMFUNCTION_LEAVE MUI_PAGE_CUSTOMFUNCTION_PRE MUI_PAGE_CUSTOMFUNCTION_SHOW MUI_PAGE_DIRECTORY MUI_PAGE_FINISH MUI_PAGE_HEADER_SUBTEXT MUI_PAGE_HEADER_TEXT MUI_PAGE_INSTFILES MUI_PAGE_LICENSE MUI_PAGE_STARTMENU MUI_PAGE_WELCOME MUI_RESERVEFILE_INSTALLOPTIONS MUI_RESERVEFILE_LANGDLL MUI_SPECIALINI MUI_STARTMENU_GETFOLDER MUI_STARTMENU_WRITE_BEGIN MUI_STARTMENU_WRITE_END MUI_STARTMENUPAGE_BGCOLOR MUI_STARTMENUPAGE_DEFAULTFOLDER MUI_STARTMENUPAGE_NODISABLE MUI_STARTMENUPAGE_REGISTRY_KEY MUI_STARTMENUPAGE_REGISTRY_ROOT MUI_STARTMENUPAGE_REGISTRY_VALUENAME MUI_STARTMENUPAGE_TEXT_CHECKBOX MUI_STARTMENUPAGE_TEXT_TOP MUI_UI MUI_UI_COMPONENTSPAGE_NODESC MUI_UI_COMPONENTSPAGE_SMALLDESC MUI_UI_HEADERIMAGE MUI_UI_HEADERIMAGE_RIGHT MUI_UNABORTWARNING MUI_UNABORTWARNING_CANCEL_DEFAULT MUI_UNABORTWARNING_TEXT MUI_UNCONFIRMPAGE_TEXT_LOCATION MUI_UNCONFIRMPAGE_TEXT_TOP MUI_UNFINISHPAGE_NOAUTOCLOSE MUI_UNFUNCTION_DESCRIPTION_BEGIN MUI_UNFUNCTION_DESCRIPTION_END MUI_UNGETLANGUAGE MUI_UNICON MUI_UNPAGE_COMPONENTS MUI_UNPAGE_CONFIRM MUI_UNPAGE_DIRECTORY MUI_UNPAGE_FINISH MUI_UNPAGE_INSTFILES MUI_UNPAGE_LICENSE MUI_UNPAGE_WELCOME MUI_UNWELCOMEFINISHPAGE_BITMAP MUI_UNWELCOMEFINISHPAGE_BITMAP_NOSTRETCH MUI_UNWELCOMEFINISHPAGE_INI MUI_WELCOMEFINISHPAGE_BITMAP MUI_WELCOMEFINISHPAGE_BITMAP_NOSTRETCH MUI_WELCOMEFINISHPAGE_CUSTOMFUNCTION_INIT MUI_WELCOMEFINISHPAGE_INI MUI_WELCOMEPAGE_TEXT MUI_WELCOMEPAGE_TITLE MUI_WELCOMEPAGE_TITLE_3LINES" bistr = "addincludedir addplugindir AndIf cd define echo else endif error execute If ifdef ifmacrodef ifmacrondef ifndef include insertmacro macro macroend onGUIEnd onGUIInit onInit onInstFailed onInstSuccess onMouseOverSection onRebootFailed onSelChange onUserAbort onVerifyInstDir OrIf packhdr system undef verbose warning" instance = any("instance", [r"\$\{.*?\}", r"\$[A-Za-z0-9\_]*"]) define = any("define", [r"\![^\n]*"]) comment = any("comment", [r"\;[^\n]*", r"\#[^\n]*", r"\/\*(.*?)\*\/"]) return make_generic_c_patterns( kwstr1 + " " + kwstr2 + " " + kwstr3, bistr, instance=instance, define=define, comment=comment, ) class NsisSH(CppSH): """NSIS Syntax Highlighter""" # Syntax highlighting rules: PROG = re.compile(make_nsis_patterns(), re.S) # ============================================================================== # gettext highlighter # ============================================================================== def make_gettext_patterns(): "Strongly inspired from idlelib.ColorDelegator.make_pat" kwstr = "msgid msgstr" kw = r"\b" + any("keyword", kwstr.split()) + r"\b" fuzzy = any("builtin", [r"#,[^\n]*"]) links = any("normal", [r"#:[^\n]*"]) comment = any("comment", [r"#[^\n]*"]) number = any( "number", [ r"\b[+-]?[0-9]+[lL]?\b", r"\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b", r"\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b", ], ) sqstring = r"(\b[rRuU])?'[^'\\\n]*(\\.[^'\\\n]*)*'?" dqstring = r'(\b[rRuU])?"[^"\\\n]*(\\.[^"\\\n]*)*"?' string = any("string", [sqstring, dqstring]) return "|".join([kw, string, number, fuzzy, links, comment, any("SYNC", [r"\n"])]) class GetTextSH(GenericSH): """gettext Syntax Highlighter""" # Syntax highlighting rules: PROG = re.compile(make_gettext_patterns(), re.S) # ============================================================================== # yaml highlighter # ============================================================================== def make_yaml_patterns(): "Strongly inspired from sublime highlighter" kw = any("keyword", [r":|>|-|\||\[|\]|[A-Za-z][\w\s\-\_ ]+(?=:)"]) links = any("normal", [r"#:[^\n]*"]) comment = any("comment", [r"#[^\n]*"]) number = any( "number", [ r"\b[+-]?[0-9]+[lL]?\b", r"\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b", r"\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b", ], ) sqstring = r"(\b[rRuU])?'[^'\\\n]*(\\.[^'\\\n]*)*'?" dqstring = r'(\b[rRuU])?"[^"\\\n]*(\\.[^"\\\n]*)*"?' string = any("string", [sqstring, dqstring]) return "|".join([kw, string, number, links, comment, any("SYNC", [r"\n"])]) class YamlSH(GenericSH): """yaml Syntax Highlighter""" # Syntax highlighting rules: PROG = re.compile(make_yaml_patterns(), re.S) # ============================================================================== # HTML highlighter # ============================================================================== class BaseWebSH(BaseSH): """Base class for CSS and HTML syntax highlighters""" NORMAL = 0 COMMENT = 1 def __init__(self, parent, font=None, color_scheme=None): BaseSH.__init__(self, parent, font, color_scheme) def highlightBlock(self, text): """Implement highlight specific for CSS and HTML.""" text = str(text) previous_state = self.previousBlockState() if previous_state == self.COMMENT: self.setFormat(0, len(text), self.formats["comment"]) else: previous_state = self.NORMAL self.setFormat(0, len(text), self.formats["normal"]) self.setCurrentBlockState(previous_state) match = self.PROG.search(text) match_count = 0 n_characters = len(text) # There should never be more matches than characters in the text. while match and match_count < n_characters: match_dict = match.groupdict() for key, value in list(match_dict.items()): if value: start, end = match.span(key) if previous_state == self.COMMENT: if key == "multiline_comment_end": self.setCurrentBlockState(self.NORMAL) self.setFormat(end, len(text), self.formats["normal"]) else: self.setCurrentBlockState(self.COMMENT) self.setFormat(0, len(text), self.formats["comment"]) else: if key == "multiline_comment_start": self.setCurrentBlockState(self.COMMENT) self.setFormat(start, len(text), self.formats["comment"]) else: self.setCurrentBlockState(self.NORMAL) try: self.setFormat(start, end - start, self.formats[key]) except KeyError: # Happens with unmatched end-of-comment. # See spyder-ide/spyder#1462. pass match = self.PROG.search(text, match.end()) match_count += 1 def make_html_patterns(): """Strongly inspired from idlelib.ColorDelegator.make_pat""" tags = any("builtin", [r"<", r"[\?/]?>", r"(?<=<).*?(?=[ >])"]) keywords = any("keyword", [r" [\w:-]*?(?==)"]) string = any("string", [r'".*?"']) comment = any("comment", [r""]) multiline_comment_start = any("multiline_comment_start", [r""]) return "|".join( [ comment, multiline_comment_start, multiline_comment_end, tags, keywords, string, ] ) class HtmlSH(BaseWebSH): """HTML Syntax Highlighter""" PROG = re.compile(make_html_patterns(), re.S) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata/widgets/texteditor.py0000644000175100001770000000746514654363416020426 0ustar00runnerdocker# -*- coding: utf-8 -*- # # Copyright ┬й Spyder Project Contributors # Licensed under the terms of the MIT License # (see spyder/__init__.py for details) """ guidata.widgets.texteditor ========================== This package provides a text editor widget based on QtGui.QPlainTextEdit. .. autoclass:: TextEditor :show-inheritance: :members: """ from qtpy.QtCore import Qt, Slot from qtpy.QtWidgets import QDialog, QHBoxLayout, QPushButton, QTextEdit, QVBoxLayout from guidata.config import CONF, _ from guidata.configtools import get_font, get_icon from guidata.qthelpers import win32_fix_title_bar_background class TextEditor(QDialog): """Array Editor Dialog""" def __init__( self, text, title="", font=None, parent=None, readonly=False, size=(400, 300) ): QDialog.__init__(self, parent) win32_fix_title_bar_background(self) # Destroying the C++ object right after closing the dialog box, # otherwise it may be garbage-collected in another QThread # (e.g. the editor's analysis thread in Spyder), thus leading to # a segmentation fault on UNIX or an application crash on Windows self.setAttribute(Qt.WA_DeleteOnClose) self.text = None self.btn_save_and_close = None # Display text as unicode if it comes as bytes, so users see # its right representation if isinstance(text, bytes): self.is_binary = True text = str(text, "utf8") else: self.is_binary = False self.layout = QVBoxLayout() self.setLayout(self.layout) # Text edit self.edit = QTextEdit(parent) self.edit.setReadOnly(readonly) self.edit.textChanged.connect(self.text_changed) self.edit.setPlainText(text) if font is None: font = get_font(CONF, "texteditor") self.edit.setFont(font) self.layout.addWidget(self.edit) # Buttons configuration btn_layout = QHBoxLayout() btn_layout.addStretch() if not readonly: self.btn_save_and_close = QPushButton(_("Save and Close")) self.btn_save_and_close.setDisabled(True) self.btn_save_and_close.clicked.connect(self.accept) btn_layout.addWidget(self.btn_save_and_close) self.btn_close = QPushButton(_("Close")) self.btn_close.setAutoDefault(True) self.btn_close.setDefault(True) self.btn_close.clicked.connect(self.reject) btn_layout.addWidget(self.btn_close) self.layout.addLayout(btn_layout) # Make the dialog act as a window self.setWindowFlags(Qt.Window) self.setWindowIcon(get_icon("edit.png")) self.setWindowTitle( _("Text editor") + "%s" % (" - " + str(title) if str(title) else "") ) self.resize(size[0], size[1]) @Slot() def text_changed(self): """Text has changed""" # Save text as bytes, if it was initially bytes if self.is_binary: self.text = bytes(self.edit.toPlainText(), "utf8") else: self.text = str(self.edit.toPlainText()) if self.btn_save_and_close: self.btn_save_and_close.setEnabled(True) self.btn_save_and_close.setAutoDefault(True) self.btn_save_and_close.setDefault(True) def get_value(self): """Return modified text""" # It is import to avoid accessing Qt C++ object as it has probably # already been destroyed, due to the Qt.WA_DeleteOnClose attribute return self.text def setup_and_check(self, value): """Verify if TextEditor is able to display strings passed to it.""" if isinstance(value, str): return True try: str(value, "utf8") return True except: return False ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/guidata-tests.desktop0000644000175100001770000000043014654363416016727 0ustar00runnerdocker[Desktop Entry] Version=1.0 Type=Application Name=guidata-tests GenericName=guidata Test launcher Comment=The guidata Python library provides GUIs for easy dataset editing and display TryExec=guidata-tests Exec=guidata-tests Icon=guidata.svg Categories=Education;Science;Physics; ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1722935059.1807055 guidata-3.6.2/guidata.egg-info/0000755000175100001770000000000014654363423015667 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935059.0 guidata-3.6.2/guidata.egg-info/PKG-INFO0000644000175100001770000001501114654363423016762 0ustar00runnerdockerMetadata-Version: 2.1 Name: guidata Version: 3.6.2 Summary: Automatic GUI generation for easy dataset editing and display Author-email: Codra License: BSD 3-Clause License Copyright (c) 2023, CEA-Codra, Pierre Raybaut. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Project-URL: Homepage, https://github.com/PlotPyStack/guidata/ Project-URL: Documentation, https://guidata.readthedocs.io/en/latest/ Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Education Classifier: Intended Audience :: Science/Research Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: MacOS :: MacOS X Classifier: Operating System :: Microsoft :: Windows :: Windows 7 Classifier: Operating System :: Microsoft :: Windows :: Windows 8 Classifier: Operating System :: Microsoft :: Windows :: Windows 10 Classifier: Operating System :: Microsoft :: Windows :: Windows 11 Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Topic :: Scientific/Engineering Classifier: Topic :: Scientific/Engineering :: Human Machine Interfaces Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Software Development :: User Interfaces Classifier: Topic :: Software Development :: Widget Sets Classifier: Topic :: Utilities Requires-Python: <4,>=3.8 Description-Content-Type: text/markdown License-File: LICENSE Requires-Dist: h5py>=3.0 Requires-Dist: NumPy>=1.21 Requires-Dist: QtPy>=1.9 Requires-Dist: requests Requires-Dist: tomli Provides-Extra: dev Requires-Dist: ruff; extra == "dev" Requires-Dist: pylint; extra == "dev" Requires-Dist: Coverage; extra == "dev" Provides-Extra: doc Requires-Dist: PyQt5; extra == "doc" Requires-Dist: pillow; extra == "doc" Requires-Dist: pandas; extra == "doc" Requires-Dist: sphinx>6; extra == "doc" Requires-Dist: myst_parser; extra == "doc" Requires-Dist: sphinx-copybutton; extra == "doc" Requires-Dist: sphinx_qt_documentation; extra == "doc" Requires-Dist: python-docs-theme; extra == "doc" Provides-Extra: test Requires-Dist: pytest; extra == "test" Requires-Dist: pytest-xvfb; extra == "test" # guidata: Automatic GUI generation for easy dataset editing and display with Python [![pypi version](https://img.shields.io/pypi/v/guidata.svg)](https://pypi.org/project/guidata/) [![PyPI status](https://img.shields.io/pypi/status/guidata.svg)](https://github.com/PlotPyStack/guidata/) [![PyPI pyversions](https://img.shields.io/pypi/pyversions/guidata.svg)](https://pypi.python.org/pypi/guidata/) [![download count](https://img.shields.io/conda/dn/conda-forge/guidata.svg)](https://www.anaconda.com/download/) тД╣я╕П Created in 2009 by [Pierre Raybaut](https://github.com/PierreRaybaut) and maintained by the [PlotPyStack](https://github.com/PlotPyStack) organization. ## Overview The `guidata` package is a Python library generating Qt graphical user interfaces. It is part of the [PlotPyStack](https://github.com/PlotPyStack) project, aiming at providing a unified framework for creating scientific GUIs with Python and Qt. Simple example of `guidata` datasets embedded in an application window: ![Example](https://raw.githubusercontent.com/PlotPyStack/guidata/master/doc/images/screenshots/editgroupbox.png) See [documentation](https://guidata.readthedocs.io/en/latest/) for more details on the library and [changelog](https://github.com/PlotPyStack/guidata/blob/master/CHANGELOG.md) for recent history of changes. Copyrights and licensing: * Copyright ┬й 2023 [CEA](https://www.cea.fr), [Codra](https://codra.net/), [Pierre Raybaut](https://github.com/PierreRaybaut). * Licensed under the terms of the BSD 3-Clause (see [LICENSE](https://github.com/PlotPyStack/guidata/blob/master/LICENSE)). ## Features Based on the Qt library, `guidata` is a Python library generating graphical user interfaces for easy dataset editing and display. It also provides helpers and application development tools for Qt (PyQt5, PySide2, PyQt6, PySide6). Generate GUIs to edit and display all kind of objects regrouped in datasets: * Integers, floats, strings * Lists (single/multiple choices) * Dictionaries * `ndarrays` (NumPy's N-dimensional arrays) * Etc. Save and load datasets to/from HDF5, JSON or INI files. Application development tools: * Data model (internal data structure, serialization, etc.) * Configuration management * Internationalization (`gettext`) * Deployment tools * HDF5, JSON and INI I/O helpers * Qt helpers * Ready-to-use Qt widgets: Python console, source code editor, array editor, etc. ## Dependencies and installation See [Installation](https://guidata.readthedocs.io/en/latest/installation.html) section in the documentation for more details. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935059.0 guidata-3.6.2/guidata.egg-info/SOURCES.txt0000644000175100001770000001641314654363423017560 0ustar00runnerdockerCHANGELOG.md LICENSE MANIFEST.in README.md guidata-tests.desktop pyproject.toml requirements.txt doc/basic_example.py doc/changelog.rst doc/conf.py doc/examples.rst doc/index.rst doc/installation.rst doc/overview.rst doc/requirements.rst doc/update_requirements.py doc/widgets.rst doc/_static/favicon.ico doc/autodoc/autodoc_example.py doc/autodoc/index.rst doc/dev/contribute.rst doc/dev/howto.rst doc/dev/index.rst doc/dev/platform.rst doc/dev/v2_to_v3.csv doc/dev/v2_to_v3.rst doc/images/basic_example.png doc/images/guidata-banner.png doc/images/guidata-vertical.png doc/images/layout_example.png doc/images/screenshots/__init__.png doc/images/screenshots/activable_dataset.png doc/images/screenshots/all_features.png doc/images/screenshots/all_items.png doc/images/screenshots/arrayeditor.png doc/images/screenshots/bool_selector.png doc/images/screenshots/codeeditor.png doc/images/screenshots/collectioneditor.png doc/images/screenshots/console.png doc/images/screenshots/dataframeeditor.png doc/images/screenshots/datasetgroup.png doc/images/screenshots/editgroupbox.png doc/images/screenshots/importwizard.png doc/reference/guitest.rst doc/reference/index.rst doc/reference/userconfig.rst doc/reference/utils.rst doc/reference/widgets.rst doc/reference/dataset/conv.rst doc/reference/dataset/dataitems.rst doc/reference/dataset/datatypes.rst doc/reference/dataset/index.rst doc/reference/dataset/io.rst doc/reference/dataset/qtwidgets.rst guidata/__init__.py guidata/config.py guidata/configtools.py guidata/env.py guidata/guitest.py guidata/qthelpers.py guidata/userconfig.py guidata.egg-info/PKG-INFO guidata.egg-info/SOURCES.txt guidata.egg-info/dependency_links.txt guidata.egg-info/entry_points.txt guidata.egg-info/requires.txt guidata.egg-info/top_level.txt guidata/data/icons/apply.png guidata/data/icons/arredit.png guidata/data/icons/busy.png guidata/data/icons/cell_edit.png guidata/data/icons/copy.png guidata/data/icons/delete.png guidata/data/icons/dictedit.png guidata/data/icons/dtype.png guidata/data/icons/edit.png guidata/data/icons/exit.png guidata/data/icons/expander_down.png guidata/data/icons/expander_right.png guidata/data/icons/file.png guidata/data/icons/fileclose.png guidata/data/icons/fileimport.png guidata/data/icons/filenew.png guidata/data/icons/fileopen.png guidata/data/icons/filesave.png guidata/data/icons/filesaveas.png guidata/data/icons/guidata-banner.svg guidata/data/icons/guidata-vertical.svg guidata/data/icons/guidata.svg guidata/data/icons/hist.png guidata/data/icons/max.png guidata/data/icons/min.png guidata/data/icons/none.png guidata/data/icons/not_found.png guidata/data/icons/python.png guidata/data/icons/quickview.png guidata/data/icons/save_all.png guidata/data/icons/selection.png guidata/data/icons/settings.png guidata/data/icons/shape.png guidata/data/icons/xmax.png guidata/data/icons/xmin.png guidata/data/icons/editors/copywop.png guidata/data/icons/editors/edit.png guidata/data/icons/editors/edit_add.png guidata/data/icons/editors/editclear.png guidata/data/icons/editors/editcopy.png guidata/data/icons/editors/editcut.png guidata/data/icons/editors/editdelete.png guidata/data/icons/editors/editpaste.png guidata/data/icons/editors/fileimport.png guidata/data/icons/editors/filesave.png guidata/data/icons/editors/imshow.png guidata/data/icons/editors/insert.png guidata/data/icons/editors/plot.png guidata/data/icons/editors/rename.png guidata/data/icons/editors/selectall.png guidata/data/icons/filetypes/doc.png guidata/data/icons/filetypes/gif.png guidata/data/icons/filetypes/html.png guidata/data/icons/filetypes/jpg.png guidata/data/icons/filetypes/pdf.png guidata/data/icons/filetypes/png.png guidata/data/icons/filetypes/pps.png guidata/data/icons/filetypes/ps.png guidata/data/icons/filetypes/tar.png guidata/data/icons/filetypes/tgz.png guidata/data/icons/filetypes/tif.png guidata/data/icons/filetypes/txt.png guidata/data/icons/filetypes/xls.png guidata/data/icons/filetypes/zip.png guidata/dataset/__init__.py guidata/dataset/autodoc.py guidata/dataset/autodoc_method.py guidata/dataset/conv.py guidata/dataset/dataitems.py guidata/dataset/datatypes.py guidata/dataset/io.py guidata/dataset/note_directive.py guidata/dataset/qtitemwidgets.py guidata/dataset/qtwidgets.py guidata/dataset/textedit.py guidata/external/__init__.py guidata/external/darkdetect/__init__.py guidata/external/darkdetect/_dummy.py guidata/external/darkdetect/_linux_detect.py guidata/external/darkdetect/_mac_detect.py guidata/external/darkdetect/_windows_detect.py guidata/io/__init__.py guidata/io/base.py guidata/io/h5fmt.py guidata/io/inifmt.py guidata/io/jsonfmt.py guidata/locale/fr/LC_MESSAGES/guidata.mo guidata/tests/__init__.py guidata/tests/conftest.py guidata/tests/data/genreqs/pyproject.toml guidata/tests/data/genreqs/setup.cfg guidata/tests/dataset/__init__.py guidata/tests/dataset/test_activable_dataset.py guidata/tests/dataset/test_activable_items.py guidata/tests/dataset/test_all_features.py guidata/tests/dataset/test_all_items.py guidata/tests/dataset/test_all_items_readonly.py guidata/tests/dataset/test_bool_selector.py guidata/tests/dataset/test_callbacks.py guidata/tests/dataset/test_datasetgroup.py guidata/tests/dataset/test_editgroupbox.py guidata/tests/dataset/test_inheritance.py guidata/tests/dataset/test_item_order.py guidata/tests/dataset/test_loadsave_hdf5.py guidata/tests/dataset/test_loadsave_json.py guidata/tests/dataset/test_rotatedlabel.py guidata/tests/unit/__init__.py guidata/tests/unit/test_config.py guidata/tests/unit/test_data.py guidata/tests/unit/test_dataset_from_dict.py guidata/tests/unit/test_dataset_from_func.py guidata/tests/unit/test_genreqs.py guidata/tests/unit/test_h5fmt.py guidata/tests/unit/test_jsonfmt.py guidata/tests/unit/test_no_qt.py guidata/tests/unit/test_text.py guidata/tests/unit/test_translations.py guidata/tests/unit/test_updaterestoredataset.py guidata/tests/unit/test_userconfig_app.py guidata/tests/widgets/__init__.py guidata/tests/widgets/test_arrayeditor.py guidata/tests/widgets/test_arrayeditor_unit.py guidata/tests/widgets/test_codeeditor.py guidata/tests/widgets/test_collectionseditor.py guidata/tests/widgets/test_console.py guidata/tests/widgets/test_dataframeeditor.py guidata/tests/widgets/test_importwizard.py guidata/tests/widgets/test_objecteditor.py guidata/tests/widgets/test_theme.py guidata/utils/__init__.py guidata/utils/encoding.py guidata/utils/genreqs.py guidata/utils/gettext_helpers.py guidata/utils/misc.py guidata/widgets/__init__.py guidata/widgets/about.py guidata/widgets/codeeditor.py guidata/widgets/collectionseditor.py guidata/widgets/dataframeeditor.py guidata/widgets/dockable.py guidata/widgets/importwizard.py guidata/widgets/nsview.py guidata/widgets/objecteditor.py guidata/widgets/rotatedlabel.py guidata/widgets/syntaxhighlighters.py guidata/widgets/texteditor.py guidata/widgets/arrayeditor/__init__.py guidata/widgets/arrayeditor/arrayeditor.py guidata/widgets/arrayeditor/arrayhandler.py guidata/widgets/arrayeditor/datamodel.py guidata/widgets/arrayeditor/editorwidget.py guidata/widgets/arrayeditor/utils.py guidata/widgets/console/__init__.py guidata/widgets/console/base.py guidata/widgets/console/calltip.py guidata/widgets/console/dochelpers.py guidata/widgets/console/internalshell.py guidata/widgets/console/interpreter.py guidata/widgets/console/mixins.py guidata/widgets/console/shell.py guidata/widgets/console/terminal.py././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935059.0 guidata-3.6.2/guidata.egg-info/dependency_links.txt0000644000175100001770000000000114654363423021735 0ustar00runnerdocker ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935059.0 guidata-3.6.2/guidata.egg-info/entry_points.txt0000644000175100001770000000006014654363423021161 0ustar00runnerdocker[gui_scripts] guidata-tests = guidata.tests:run ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935059.0 guidata-3.6.2/guidata.egg-info/requires.txt0000644000175100001770000000032214654363423020264 0ustar00runnerdockerh5py>=3.0 NumPy>=1.21 QtPy>=1.9 requests tomli [dev] ruff pylint Coverage [doc] PyQt5 pillow pandas sphinx>6 myst_parser sphinx-copybutton sphinx_qt_documentation python-docs-theme [test] pytest pytest-xvfb ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935059.0 guidata-3.6.2/guidata.egg-info/top_level.txt0000644000175100001770000000001014654363423020410 0ustar00runnerdockerguidata ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/pyproject.toml0000644000175100001770000000640314654363416015500 0ustar00runnerdocker# guidata setup configuration file # Important note: # Requirements are parsed by utils\genreqs.py to generate documentation [build-system] requires = ["setuptools"] build-backend = "setuptools.build_meta" [project] name = "guidata" authors = [{ name = "Codra", email = "p.raybaut@codra.fr" }] description = "Automatic GUI generation for easy dataset editing and display" readme = "README.md" license = { file = "LICENSE" } classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Intended Audience :: Education", "Intended Audience :: Science/Research", "License :: OSI Approved :: BSD License", "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows :: Windows 7", "Operating System :: Microsoft :: Windows :: Windows 8", "Operating System :: Microsoft :: Windows :: Windows 10", "Operating System :: Microsoft :: Windows :: Windows 11", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Topic :: Scientific/Engineering", "Topic :: Scientific/Engineering :: Human Machine Interfaces", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Software Development :: User Interfaces", "Topic :: Software Development :: Widget Sets", "Topic :: Utilities", ] requires-python = ">=3.8, <4" dependencies = ["h5py>=3.0", "NumPy>=1.21", "QtPy>=1.9", "requests", "tomli"] dynamic = ["version"] [project.urls] Homepage = "https://github.com/PlotPyStack/guidata/" Documentation = "https://guidata.readthedocs.io/en/latest/" [project.gui-scripts] guidata-tests = "guidata.tests:run" [project.optional-dependencies] dev = ["ruff", "pylint", "Coverage"] doc = [ "PyQt5", "pillow", "pandas", "sphinx>6", "myst_parser", "sphinx-copybutton", "sphinx_qt_documentation", "python-docs-theme", ] test = ["pytest", "pytest-xvfb"] [tool.setuptools.packages.find] include = ["guidata*"] [tool.setuptools.package-data] "*" = ["*.png", "*.svg", "*.mo", "*.cfg", "*.toml"] [tool.setuptools.dynamic] version = { attr = "guidata.__version__" } [tool.pytest.ini_options] addopts = "guidata" [tool.ruff] exclude = [".git", ".vscode", "build", "dist", "guidata/external"] line-length = 88 # Same as Black. indent-width = 4 # Same as Black. target-version = "py38" # Assume Python 3.8 [tool.ruff.lint] # all rules can be found here: https://beta.ruff.rs/docs/rules/ select = ["E", "F", "W", "I", "NPY201"] ignore = [ "E203", # space before : (needed for how black formats slicing) ] [tool.ruff.format] quote-style = "double" # Like Black, use double quotes for strings. indent-style = "space" # Like Black, indent with spaces, rather than tabs. skip-magic-trailing-comma = false # Like Black, respect magic trailing commas. line-ending = "auto" # Like Black, automatically detect the appropriate line ending. [tool.ruff.lint.per-file-ignores] "doc/*" = ["E402"] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1722935054.0 guidata-3.6.2/requirements.txt0000644000175100001770000000027114654363416016045 0ustar00runnerdockerCoverage NumPy>=1.21 PyQt5 QtPy>=1.9 h5py>=3.0 pandas pillow pylint ruff pytest pytest-xvfb python-docs-theme requests sphinx myst_parser sphinx-copybutton sphinx_qt_documentation tomli././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1722935059.1807055 guidata-3.6.2/setup.cfg0000644000175100001770000000004614654363423014400 0ustar00runnerdocker[egg_info] tag_build = tag_date = 0