From f138f60ac110c667ad6de10d102c2cfa799e1e00 Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Sat, 9 Jan 2021 11:24:04 +0100 Subject: [PATCH 01/52] Changing main file of the lib struct : creation of the TextBox class which allows to encapsulate all attributes and generalist methods. DialogBox now inherits this class. utils.py contains the non-library related functions used by the main files. Addition two new propertys to TextBox class : TextBox.position and TextBox.dimensions. Parameters title and title_colors_pair_nb are now no longer required to instanciation of TextBox and DialogBox. color_pair_nb parameter is now optionnal in DialogBox.char_by_char and DialogBox.word_by_word methods (set by default to 1). The default delay between each character is now 0.04s instead of 0.05s in DialogBox.char_by_char method. Change in the way the title of the TextBox works : the default title is an empty string and is only displayed in if it is non-empty. Added more accurate hinting typing in the signatures of the function. Addition of instructions to built documentation in README. Typo minors change in documentation. Start writing a tutorial. Moving the tests contained in core.py to /tests/test.py Start implementing the ChoiceBox class to provide selectable choices in dialog.py Start implementing panic feature in box.py --- CONTRIBUTING.md | 54 +++- README.md | 28 +- doc/examples/confrontation.py | 10 +- doc/examples/context.py | 7 +- doc/examples/monologue.py | 10 +- doc/examples/text_attributes.py | 2 - doc/generate-documentation.sh | 13 + doc/tutorial.md | 1 + tests/test.py | 62 ++++ visualdialog/__init__.py | 5 +- visualdialog/box.py | 214 ++++++++++++++ visualdialog/choices_box.py | 81 ++++++ visualdialog/core.py | 484 -------------------------------- visualdialog/dialog.py | 374 ++++++++++++++++++++++++ visualdialog/utils.py | 61 ++++ 15 files changed, 886 insertions(+), 520 deletions(-) create mode 100644 doc/generate-documentation.sh create mode 100644 doc/tutorial.md create mode 100644 tests/test.py create mode 100644 visualdialog/box.py create mode 100644 visualdialog/choices_box.py delete mode 100755 visualdialog/core.py create mode 100755 visualdialog/dialog.py create mode 100644 visualdialog/utils.py diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bf63df9..4aa510e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,15 +4,26 @@ ## Setup your development environment You need: + * `Python3.6`: you can install it by following [Python3's documentation](https://www.python.org/downloads/). -* `curses`: available in standard library of `Python` but it doesn't work out-of-the-box on Windows. See [this](https://www.devdungeon.com/content/curses-windows-python) explanations to install `curses` on Windows. +* `curses`: available in standard library of `Python` but it doesn't work out-of-the-box on `Windows`. See [this](https://www.devdungeon.com/content/curses-windows-python) explanations to install `curses` on Windows. ## Branches -* main : stable releases. +* `main` : stable releases. +* `dev` : beta releases. To fix a minor problem or add new features create a new branch in this form: `username-dev`. -Please test your code with `Python3.6` version before **pull request** to be sure not to break compatability. + +## Conventions + +When you make a pull request make sure to: + +* Test your code with `Python3.6` to be sure not to break compatability. +* Format your code according [PEP 8](https://www.python.org/dev/peps/pep-0008/). [PEP8 Check](https://github.com/quentinguidee/actions-pep8) will ensure that your code is correctly formatted . +* Document your code following [Numpy documentation conventions](https://numpydoc.readthedocs.io/en/latest/format.html#docstring-standard) if you add new functionality. + +If you add a feature that changes the API, notify it explicitly. ## Download @@ -21,11 +32,13 @@ Download projet: git clone https://github.com/Tim-ats-d/Visual-dialog ``` -Install Visual-dialog using `pip`: +Install Visual-dialog using `pip` (The lib is not yet available on **pypy**): + ```bash python3 -m pip install git+git://github.com/Tim-ats-d/Visual-dialog ``` or update lib to the latest version: + ```bash python3 -m pip install git+git://github.com/Tim-ats-d/Visual-dialog --upgrade ``` @@ -38,24 +51,39 @@ The following snippet describes Visual-dialog's repository structure. ```text . ├── .github/ -| Contains Github specific files such as actions definitions and issue templates. +│ Contains Github specific files such as actions definitions and issue templates. │ ├── doc/ -| Contains documentation. +│ Contains the files related to the documentation. │ │ │ ├── examples/ -│ │ Contains several examples of how to use Visual-dialog. +│ │ Contains several examples of use cases of Visual-dialog. +│ │ +│ ├── generate-documentation.sh +│ │ A script to generate documentation of public API from the source code. +│ │ Produces two files: visualdialog-documentation.txt and visualdialog.core.html +│ │ +│ └── tutorial.md +│ A tutorial to learn how to use Visual-dialog. +│ +├── tests/ +│ Contains tests for debugging libraries. │ │ -│ └── documentation.md -│ Documentation of public API (coming soon). +│ └── test.py │ ├── visualdialog/ -| Source for Visual-dialog's library. +│ Source for Visual-dialog's library. +│ │ +│ ├── __init__.py +│ │ +│ ├── box.py +│ │ Contains the parent class TextBox which serves as a basis for the implementation of the other classes. │ │ -| ├── __init__.py +│ ├── dialog.py +│ │ Contains the DialogBox class, which is the main class of the library. │ │ -| └── core.py -| Contains Visual-dialog's core functionnalities. +│ └── utils.py +│ Contains the classes and functions used but not related to the libriarie. │ ├── LICENSE │ diff --git a/README.md b/README.md index 31def27..0209b16 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,9 @@ Demo +This library is still under development. +The api can change. + ## Features 📃 Automatic text scrolling. @@ -25,27 +28,44 @@ ⚙️ Hackable and configurable . + ## Installation ### Using pip +Install Visual-dialog using `pip` (The lib is not yet available on **pypy**): + ```bash python3 -m pip install git+git://github.com/Tim-ats-d/Visual-dialog ``` -(The lib is not yet available on **pypy**). +or update lib to the latest version: + +```bash +python3 -m pip install git+git://github.com/Tim-ats-d/Visual-dialog --upgrade +``` ### Requirements * **Python 3.6** or more. +* **pydoc3** to generate the documentation of library. * [`curses`](https://docs.python.org/3/library/curses.html) module (available on **UNIX** system by default). * Knowledge of [`curses`](https://docs.python.org/3/library/curses.html) librairie. ## Quick-start +### Minimal code + +Coming soon. + Read these [examples](doc/examples/). + ## Documentation -Coming soon ! +Visualdialog's documentation is automatically generated from the source code. + +See [this script](doc/generate-documentation.sh) to build documentation of public API. + +If you have any problems with `pydoc` look at its [documentation](https://docs.python.org/3/library/pydoc.html). ## Contributing @@ -54,9 +74,11 @@ We would love for you to contribute to improve **Visual-dialog**. Take a look at our [Contributing guide](CONTRIBUTING.md) to get started. + ## License -Distributed under the **GPL-2.0 License** . See [LICENSE](LICENSE) for more information. +Distributed under the **GPL-2.0 License** . See [License](LICENSE) for more information. + ## Acknowledgements diff --git a/doc/examples/confrontation.py b/doc/examples/confrontation.py index 738a78b..c74d008 100644 --- a/doc/examples/confrontation.py +++ b/doc/examples/confrontation.py @@ -48,8 +48,7 @@ def main(stdscr): colors_pair_nb=0, delay=0.03) - phoenix_wright.getkey(stdscr) # Wait until a key contained in phoenix_wright.confirm_dialog_key is pressed. - stdscr.clear() # Clear the screen. + stdscr.clear() # Clear entierely current window object. phoenix_wright.char_by_char(stdscr, "You're lying April May !", @@ -58,7 +57,6 @@ def main(stdscr): delay=0.03, text_attributes=(curses.A_BOLD,)) - phoenix_wright.getkey(stdscr) # Wait until a key contained in phoenix_wright.confirm_dialog_key is pressed. stdscr.clear() april_may.char_by_char(stdscr, @@ -67,7 +65,6 @@ def main(stdscr): delay=0.02, text_attributes=(curses.A_ITALIC,)) - april_may.getkey(stdscr) # Wait until a key contained in april_may.confirm_dialog_key is pressed. stdscr.clear() miles_edgeworth.char_by_char(stdscr, @@ -78,7 +75,6 @@ def main(stdscr): text_attributes=(curses.A_BOLD,) ) - miles_edgeworth.getkey(stdscr) # Wait until a key contained in miles_edgeworth.confirm_dialog_key is pressed. stdscr.clear() miles_edgeworth.char_by_char(stdscr, @@ -86,8 +82,8 @@ def main(stdscr): colors_pair_nb=0, delay=0.03) - miles_edgeworth.getkey(stdscr) stdscr.clear() -curses.wrapper(main) # Execution of the function. +# Execution of main function. +curses.wrapper(main) diff --git a/doc/examples/context.py b/doc/examples/context.py index 9978699..1b680f1 100644 --- a/doc/examples/context.py +++ b/doc/examples/context.py @@ -45,9 +45,8 @@ def main(stdscr): 2, # Display of the reply variable colored with color pair 2. text_attributes=attributs) # Pass the attributes to the text. - # Wait until a key contained in textbox.confirm_dialog_key is pressed. - textbox.getkey(stdscr) - stdscr.clear() # Clear the screen. + stdscr.clear() # Clear entierely current window object. -curses.wrapper(main) # Execution of the function. +# Execution of main function. +curses.wrapper(main) diff --git a/doc/examples/monologue.py b/doc/examples/monologue.py index dc17460..5c3f101 100644 --- a/doc/examples/monologue.py +++ b/doc/examples/monologue.py @@ -25,8 +25,9 @@ def main(stdscr): textbox = DialogBox( 20, 15, # Position 20;15 in terminal. - 40, 6, # Length and width (in character). - title="Dogm", title_colors_pair_nb=3) # Title and color_pair used to colored title. + 40, 6, # Length and width of textbox (in character). + title="Tim-ats-d", title_colors_pair_nb=3) + # Title and color pair used to colored title. # Definition of accepted key codes to pass a dialog. # See documentation of the curses constants for more informations. @@ -39,9 +40,8 @@ def main(stdscr): 2, # Display of the reply variable colored with color pair 2. delay=0.04) # Set delay between each characters to 0.04 seconde. - textbox.getkey(stdscr) # Waiting for a key press. - stdscr.clear() # Clear the screen. + stdscr.clear() # Clear entierely current window object. -# Execution of the function. +# Execution of main function. curses.wrapper(main) diff --git a/doc/examples/text_attributes.py b/doc/examples/text_attributes.py index 9978699..cd96f8f 100644 --- a/doc/examples/text_attributes.py +++ b/doc/examples/text_attributes.py @@ -45,8 +45,6 @@ def main(stdscr): 2, # Display of the reply variable colored with color pair 2. text_attributes=attributs) # Pass the attributes to the text. - # Wait until a key contained in textbox.confirm_dialog_key is pressed. - textbox.getkey(stdscr) stdscr.clear() # Clear the screen. diff --git a/doc/generate-documentation.sh b/doc/generate-documentation.sh new file mode 100644 index 0000000..545c75e --- /dev/null +++ b/doc/generate-documentation.sh @@ -0,0 +1,13 @@ +#!/bin/sh +# Generates the Visual-dialog public API documentation from the source code. +# Writes documentation in plain text in visualdialog-documentation.txt and in +# HTML in visualdialog.core.html. + + +pydoc3 visualdialog.box >> visualdialog-doc.txt +pydoc3 visualdialog.__init__ >> visualdialog-doc.txt + +echo "wrote visualdialog-doc.txt" +pydoc3 -w visualdialog.core + +echo "done." diff --git a/doc/tutorial.md b/doc/tutorial.md new file mode 100644 index 0000000..8d1c8b6 --- /dev/null +++ b/doc/tutorial.md @@ -0,0 +1 @@ + diff --git a/tests/test.py b/tests/test.py new file mode 100644 index 0000000..e7a52ed --- /dev/null +++ b/tests/test.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# +# test.py +# +#  This file contains the tests used to debug the library. + +import curses + + +__import__("sys").path.append("../visualdialog") +from dialog import DialogBox + + +def main(stdscr): + text = ( + "Hello world, how are you today ? test", + "Press a key to skip this dialog. ", + "This is a basic example. See doc for more informations." + " If you have a problem don't hesitate to open an issue.", + ) + + curses.curs_set(0) + + curses.start_color() + curses.init_pair(1, curses.COLOR_RED, curses.COLOR_BLACK) + curses.init_pair(2, curses.COLOR_CYAN, curses.COLOR_BLACK) + curses.init_pair(3, curses.COLOR_GREEN, curses.COLOR_BLACK) + + textbox = DialogBox(20, 15, + 40, 6, + title="Tim-ats-d", + title_colors_pair_nb=3, + end_dialog_indicator="o") + + textbox.confirm_dialog_key = (32, 10) + # ~ textbox.panic_key = (32, ) + + special_words = { + "test": (curses.A_BOLD, curses.A_ITALIC), + "this": (curses.A_BLINK, curses.color_pair(1)) + } + + def func(text: str): + stdscr.addstr(0, 0, text) + + + for reply in text: + textbox.char_by_char(stdscr, + reply, + cargs=(reply, ), + callback=func, + text_attr=(curses.A_ITALIC, curses.A_BOLD), + words_attr=special_words) + # ~ textbox.word_by_word(stdscr, + # ~ reply, + # ~ 2, + # ~ words_attr=special_words) + + + +if __name__ == "__main__": + curses.wrapper(main) diff --git a/visualdialog/__init__.py b/visualdialog/__init__.py index 1ee755f..186e403 100644 --- a/visualdialog/__init__.py +++ b/visualdialog/__init__.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 """ -A librairie which provides class and functions to make easier dialog box in terminal. +A librairie which provides class to make easier dialog box in terminal. """ __version__ = 0.6 +__author__ = "Arnouts Timéo" -from .core import DialogBox +from dialog import DialogBox diff --git a/visualdialog/box.py b/visualdialog/box.py new file mode 100644 index 0000000..ebb744c --- /dev/null +++ b/visualdialog/box.py @@ -0,0 +1,214 @@ +#!/usr/bin/env python3 +# +# box.py +# +# 2020 Timéo Arnouts +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# + +import curses +import curses.textpad +from typing import NewType, Tuple, Union + +from utils import CursesTextAttributesConstants, TextAttributes + + +__all__ = ["TextBox"] + + +class ExitError(Exception): + + def __str__(self): + return "Exit text box" + + +class TextBox: + """This class provides attributs and methods to manage a text box. + + This class provides a general API for text boxes, it does not need to be + instantiated. + + Attributes + ---------- + pos_x : int + x position of the dialog box in the terminal. + pos_y : int + y position of the dialog box in the terminal + box_length : int + Length of the dialog box in the terminal. + box_width : int + Width of the dialog box in the terminal. + title : str, optional + String that will be displayed in the upper left corner of dialog box + if title is an empty string, the title will not be displayed (by + default an empty string). + title_colors_pair_nb : CursesTextAttributesConstants, optional + Number of the curses color pair that will be used to color the title + (by default 0. The number zero corresponding to the pair of white + color on black background initialized by `curses`). + title_text_attributes : list or tuple of CursesTextAttributesConstants, optional + Dialog box title text attributes (by default a tuple contains + curses.A_BOLD). + downtime_chars : list of str or tuple of str, optional + List of characters that will trigger a `downtime_chars_delay` time + second between the writing of each character (by + default (",", ".", ":", ";", "!", "?")). + `word_by_word` method was not effected by this parameter. + downtime_chars_delay : float, optional + Waiting time in seconds after writing a character contained in + `downtime_chars` (by default 0.6). + `word_by_word` method was not effected by this parameter. + + Class attributes + ---------------- + confirm_dialog_key : list or tuple + List of accepted key codes to skip dialog. `curses` constants are + supported. + + To see the list of key constants please refer to `curses` module + documentation + (https://docs.python.org/3/library/curses.html?#constants). + panic_key : list or tuple + + """ + confirm_dialog_key: Tuple = () + panic_key: Tuple = () + + # ~ slow_mode: bool = False + # ~ speed_key: List = () + + def __init__( + self, + pos_x: int, + pos_y: int, + box_length: int, + box_width: int, + title: str = "", + title_colors_pair_nb: CursesTextAttributesConstants = 0, + title_text_attributes: Tuple[CursesTextAttributesConstants] = ( + curses.A_BOLD, ), + downtime_chars: Tuple[str] = (",", ".", ":", ";", "!", "?"), + downtime_chars_delay: Union[int, float] = 0.6): + self.pos_x, self.pos_y = pos_x, pos_y + self.box_length, self.box_width = box_length, box_width + + # Compensation for the left border of the dialog box. + self.text_pos_x = pos_x + 2 + # Compensation for the upper border of the dialog box. + self.text_pos_y = pos_y + 3 + + self.nb_char_max_line = box_length - 4 + self.nb_lines_max = box_width - 2 + + self.title = title + if self.title: + self.title_colors = curses.color_pair(title_colors_pair_nb) + self.title_text_attributes = title_text_attributes + + self.end_dialog_indicator_pos_x = pos_x + box_length - 2 + self.end_dialog_indicator_pos_y = pos_y + box_width + 1 + + self.downtime_chars = downtime_chars + self.downtime_chars_delay = downtime_chars_delay + + @property + def position(self) -> Tuple[int, int]: + """Returns a tuple contains x;y position. + + Returns + ------- + tuple of two int + x;y position of DialogBox. + """ + return self.text_pos_x - 2, self.text_pos_y - 3 + + @property + def dimensions(self) -> Tuple[int, int]: + """Returns a tuple contains dimensions of dialog box. + + Returns + ------- + tuple of two int + Dialog box length and width. + """ + return self.box_length, self.box_width + + def framing_box(self, stdscr): + """Displays dialog box borders and his title. + If attribute self.title is empty doesn't display the title. + + Parameters + --------- + stdscr + `curses` window object on which the method will have effect. + + Returns + ------- + None. + """ + title_box_length = len(self.title) + 4 + title_box_width = 2 + + # Displays the title and the title box. + if self.title: + attr = (self.title_colors, *self.title_text_attributes) + + curses.textpad.rectangle(stdscr, self.pos_y, self.pos_x + 1, + self.pos_y + title_box_width, + self.pos_x + title_box_length) + + with TextAttributes(stdscr, *attr): + stdscr.addstr(self.pos_y + 1, + self.pos_x + 3, + self.title) + + # Displays the borders of the dialog box. + curses.textpad.rectangle(stdscr, self.pos_y + 2, self.pos_x, + self.pos_y + 2 + self.box_width, + self.pos_x + self.box_length) + + def getkey(self, stdscr): + """Blocks execution as long as a key contained in + `self.confirm_dialog_key` is not detected. + + Parameters + --------- + stdscr + `curses` window object on which the method will have effect. + + Returns + ------- + None. + + See also + -------- + - To see the list of key constants please refer to `curses` module + documentation + (https://docs.python.org/3/library/curses.html?#constants). + - Documentation of `window.getch` method from `curses` module + (https://docs.python.org/3/library/curses.html?#curses.window.getch). + """ + while 1: + key = stdscr.getch() + + if key in self.confirm_dialog_key: + break + elif key in self.panic_key: + raise + else: + ... diff --git a/visualdialog/choices_box.py b/visualdialog/choices_box.py new file mode 100644 index 0000000..0d8bfb6 --- /dev/null +++ b/visualdialog/choices_box.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +# +# choices_box.py +# +# 2020 Timéo Arnouts +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# + +import curses +from typing import Any, Dict, NewType, Tuple, Union + +import box +from utils import CursesTextAttributesConstants, TextAttributes, _make_chunk + + +class ChoiceBox(box.TextBox): + + def __init__( + self, + pos_x: int, + pos_y: int, + box_length: int, + box_width: int, + title: str = "", + title_colors_pair_nb: CursesTextAttributesConstants = 0, + title_text_attributes: Tuple[CursesTextAttributesConstants] = ( + curses.A_BOLD, ), + downtime_chars: Tuple[str] = (",", ".", ":", ";", "!", "?"), + downtime_chars_delay: Union[int, float] = 0.6): + super().__init__(pos_x, + pos_y, + box_length, + box_width, + title, + title_colors_pair_nb, + title_text_attributes, + downtime_chars, + downtime_chars_delay) + + def chain( + self, + stdscr, + *propositions: Dict[str, Any]) -> Any: + """""" + super().framing_box(stdscr) + + + for y, proposition in enumerate(propositions): + stdscr.addstr(self.pos_y + y*2, + self.pos_x, + proposition) + stdscr.refresh() + + +def main(stdscr): + choices_box = ChoiceBox(10, 10, 40, 4) + + choices_box.chain(stdscr, + "Quel âge as-tu ?" + "14", + "16", + "18") + stdscr.getch() + + +curses.wrapper(main) diff --git a/visualdialog/core.py b/visualdialog/core.py deleted file mode 100755 index b128c91..0000000 --- a/visualdialog/core.py +++ /dev/null @@ -1,484 +0,0 @@ -#!/usr/bin/env python3 -# -# visualdialog.py -# -# Copyright 2020 Timéo Arnouts -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, -# MA 02110-1301, USA. -# -# - -__all__ = ["DialogBox"] -__version__ = 0.6 -__author__ = "Arnouts Timéo" - -import curses -import curses.textpad -import random -import textwrap -import time - -from typing import Callable, Generator, Iterable, Tuple - - -def _make_chunk(iterable: Iterable, chunk_length: int) -> Generator: - """Returns a generator that contains the given iterator separated into - chunk_length bundles.""" - return (iterable[chunk:chunk + chunk_length] - for chunk in range(0, len(iterable), chunk_length)) - - -class TextAttributes: - """A context manager to manage curses text attributs. - - Attributes - ---------- - window - `curses` window object for which the attributes will be managed. - attributes - List of attributes to activate and deactivate. - """ - - def __init__(self, stdscr, *attributes): - self.window = stdscr - self.attributes = attributes - - def __enter__(self): - """Activates one by the attributes contained in self.attributes.""" - for attribute in self.attributes: - self.window.attron(attribute) - - def __exit__(self, type, value, traceback): - """Disable one by the attributes contained in self.attributes.""" - for attribute in self.attributes: - self.window.attroff(attribute) - - -class DialogBox: - """This class provides methods and attributs to manage a dialog text - box. - - Attributes - ---------- - pos_x : int - x position of the dialog box in the terminal. - pos_y : int - y position of the dialog box in the terminal - box_length : int, - Length of the dialog box in the terminal. - box_width : int, - Width of the dialog box in the terminal. - title : str, - String that will be displayed in the upper left corner of dialog box. - title_colors_pair_nb : int, - Number of the curses color pair that will be used to color the title. - title_text_attributes : list of str or tuple of str, optional - Dialog box title text attributes (by default (curses.A_BOLD, )). - downtime_chars : Tuple[str], optional - List of characters that will trigger a `downtime_chars_delay` time second between the - writing of each character (by default (",", ".", ":", ";", "!", "?")). - `word_by_word` method was not effected by this parameter. - downtime_chars_delay : float, optional - Waiting time in seconds after writing a character contained in `downtime_chars` (by - default 0.6). - `word_by_word` method was not effected by this parameter. - end_dialog_indicator : str - Character that will be displayed in the lower right corner the character once all the - characters have been completed (by default "►"). - String with a length of more than 1 character can lead to an overflow of the dialog - box frame. - - Class attributes - ---------------- - confirm_dialog_key : tuple - List of accepted key codes to skip dialog curses constants are supported. - - To see the list of key constants please refer to `curse` module documentation - (https://docs.python.org/3/library/curses.html?#constants). - """ - confirm_dialog_key: Tuple = () - - def __init__(self, - pos_x: int, - pos_y: int, - box_length: int, - box_width: int, - title: str, - title_colors_pair_nb: int, - title_text_attributes: Tuple = (curses.A_BOLD, ), - downtime_chars: Tuple[str] = (",", ".", ":", ";", "!", "?"), - downtime_chars_delay: float = 0.6, - end_dialog_indicator: str = "►"): - self.pos_x, self.pos_y = pos_x, pos_y - self.box_length, self.box_width = box_length, box_width - - self.text_pos_x = pos_x + 2 # Compensation for the left border of the dialog box. - self.text_pos_y = pos_y + 3 # Compensation for the upper border of the dialog box. - - self.nb_char_max_line = box_length - 7 - self.nb_lines_max = box_width - 2 - - self.title = title - self.title_colors = curses.color_pair(title_colors_pair_nb) - self.title_text_attributes = title_text_attributes - - self.end_dialog_indicator_pos_x = pos_x + box_length - 2 - self.end_dialog_indicator_pos_y = pos_y + box_width + 1 - - self.downtime_chars = downtime_chars - self.downtime_chars_delay = downtime_chars_delay - - self.end_dialog_indicator_char = end_dialog_indicator - - def __enter__(self): - """Returns self.""" - return self - - def __exit__(self, type, value, traceback): - """Returns None.""" - ... - - def framing_box(self, stdscr): - """Displays dialog box and his title. - - Displayed dialog box have for position self.pos_x;self.pos_y and for size - `self.box_length` × `self.box_width`. - - Parameters - --------- - stdscr - `curses` window object on which the method will have effect. - - Returns - ------- - None. - - Notes - ----- - Method flow: - - Display title frame box. - - Display title . - - Display text frame box. - """ - title_box_length = len(self.title) + 4 - title_box_width = 2 - - attr = (self.title_colors, *self.title_text_attributes) - - curses.textpad.rectangle(stdscr, self.pos_y, self.pos_x + 1, - self.pos_y + title_box_width, - self.pos_x + title_box_length) - - with TextAttributes(stdscr, *attr): - stdscr.addstr(self.pos_y + 1, self.pos_x + 3, self.title) - - curses.textpad.rectangle(stdscr, self.pos_y + 2, self.pos_x, - self.pos_y + 2 + self.box_width, - self.pos_x + self.box_length) - - def getkey(self, stdscr): - """Blocks execution as long as a key contained in `self.confirm_dialog_key` is - not detected. - - Parameters - --------- - stdscr - `curses` window object on which the method will have effect. - - Returns - ------- - None. - - See also - -------- - - To see the list of key constants please refer to `curse` module documentation - (https://docs.python.org/3/library/curses.html?#constants). - - Documentation of `window.getch` method from `curses` module - (https://docs.python.org/3/library/curses.html?#curses.window.getch). - """ - while 1: - if stdscr.getch() in self.confirm_dialog_key: - break - - def _display_end_dialog_indicator(self, - stdscr, - text_attributes: Tuple = (curses.A_BOLD, curses.A_BLINK)): - """Displays an end of dialog indicator in the lower right corner of textbox. - - Parameters - ---------- - stdscr - `curses` window object on which the method will have effect. - text_attributes : tuple of curses text attribute constants, optional - Text attributes of `end_dialog_indicator` method - (by default (curses.A_BOLD, curses.A_BLINK)). - - Returns - ------- - None. - - See also - -------- - """ - if self.end_dialog_indicator_char: - with TextAttributes(stdscr, *text_attributes): - stdscr.addch(self.end_dialog_indicator_pos_y, - self.end_dialog_indicator_pos_x, - self.end_dialog_indicator_char) - - def char_by_char(self, - stdscr, - text: str, - colors_pair_nb: int, - text_attributes: Tuple = (), - flash_screen: bool = False, - delay: float = .05, - random_delay: Tuple[int, int] = (0, 0), - callback: Callable = None, - cargs=()): - """Writes the given text character by character at position in the current dialog box. - - Parameters - ---------- - stdscr - `curses` window object on which the method will have effect. - text : str - Text that will be displayed character by character in the dialog box. This text can - be wrapped to fit the proportions of the dialog box. See Notes section for more - informations. - colors_pair_nb : int, - Number of the curses color pair that will be used to color the text. - text_attributes : tuple of curses text attribute constants, optional - Dialog box text attributes (by default an empty tuple). - flash_screen : bool, optional - Allows or not to flash screen with a short light effect done before writing the first - character via `flash` function from `curses` module (by default False). - delay : int or float, optional - Waiting time between the writing of each character of text in second (by default 0.05). - random_delay : list of two number or tuple of two number, optional - Waiting time between the writing of each character in seconds where time waited is a - random number generated in `random_delay` interval (by default (0, 0)). - callback : callable, optional - Callable called after writing a character and the delay time has elapsed - (by default None). - cargs : list or tuple, optional - All the arguments that will be passed to callback (by default an empty tuple). - - Returns - ------- - None. - - Notes - ----- - Method flow: - - Calling `framing_box` method. - - Flash screen depending `flash_screen` parameter. - - Cutting text into line via `wrap` function from `textwrap` module (to stay within the - dialog box frame). - - Writing paragraph by paragraph. - - Writing each line of the current paragraph, character by character. - - Calling `_display_end_dialog_indicator` method. - - Notes - ----- - If the volume of text displayed is too large to be contained in a dialog box, text - will be automatically cut into paragraphs. The screen will be completely cleaned when - writing each paragraph via `window.clear()` method of `curses` module. - - See Also - -------- - - Documentation of `wrap` function from `textwrap` module for more information - of the behavior of text wrap - (https://docs.python.org/fr/3.8/library/textwrap.html#textwrap.wrap). - - Documentation of `flash` function from `curses` module - (https://docs.python.org/3/library/curses.html?#curses.flash). - - Documentation of `window.clear()` method from `curses` module - (https://docs.python.org/3/library/curses.html?#curses.window.clear). - """ - self.framing_box(stdscr) - - if flash_screen: - curses.flash() - - wrapped_text = textwrap.wrap(text, self.nb_char_max_line - 1) - wrapped_text = _make_chunk(wrapped_text, self.nb_lines_max) - - for paragraph in wrapped_text: - stdscr.clear() - self.framing_box(stdscr) - for y, line in enumerate(paragraph): - for x, char in enumerate(line): - attr = (curses.color_pair(colors_pair_nb), - *text_attributes) - - with TextAttributes(stdscr, *attr): - stdscr.addstr(self.text_pos_y + y, self.text_pos_x + x, - char) - stdscr.refresh() - - if char in self.downtime_chars: - time.sleep(self.downtime_chars_delay + - random.uniform(*random_delay)) - else: - time.sleep(delay + random.uniform(*random_delay)) - - if callback: - callback(*cargs) - - self._display_end_dialog_indicator(stdscr) - - def word_by_word(self, - stdscr, - text: str, - colors_pair_nb: int, - cut_char: str = " ", - text_attributes: Tuple = (), - flash_screen: bool = False, - delay: float = .15, - random_delay: Tuple[int, int] = (0, 0), - callback: Callable = None, - cargs=()): - """Writes the given text word by word at position at position in the current dialog box. - - Parameters - ---------- - stdscr - `curses` window object on which the method will have effect. - text : str - Text that will be displayed word by word in the dialog box. This text can be wrapped - to fit the proportions of the dialog box. See Notes section for more informations. - colors_pair_nb : int - Number of the curses color pair that will be used to color the text. - cut_char : str, optional - The delimiter according which to split the text in word (by default a space). - flash_screen : bool, optional - Allows or not to flash screen with a short light effect done before writing the first - word via `flash` function from `curses` module (by default False). - delay : int or float, optional - Waiting time between the writing of each character of text in second (by default 0.15). - random_delay : list of two number or tuple of two number, optional - Waiting time between the writing of each character in seconds where time waited is a - random number generated in `random_delay` interval (by default (0, 0)). - callback : callable, optional - Callable called after writing a character and the `delay` time has elapsed - (by default None). - cargs : list or tuple, optional - All the arguments that will be passed to callback (by default an empty tuple). - - Returns - ------- - None. - - Notes - ----- - Method flow: - - Calling `framing_box` method. - - Flash screen depending `flash_screen` parameter. - - Cutting text into line via `textwrap.wrap` function from `textwrap` module (to - stay within the dialog box frame). - - Writing each line of the current paragraph, word by word. - - Calling `_display_end_dialog_indicator` method. - - Notes - ----- - If the volume of text displayed is too large to be contained in a dialog box, text - will be automatically cut into paragraphs. The screen will be completely cleaned when - writing each paragraph via `window.clear` method from `curses` module. - - See Also - -------- - - Documentation of `wrap` function from `textwrap` module for more information - on the behavior of text wrap - (https://docs.python.org/fr/3.8/library/textwrap.html#textwrap.wrap). - - Documentation of `flash` function from `curses` module - (https://docs.python.org/3/library/curses.html?#curses.flash). - - Documentation of `window.clear()` method from `curses` module - (https://docs.python.org/3/library/curses.html?#curses.window.clear). - """ - self.framing_box(stdscr) - - if flash_screen: - curses.flash() - - attr = (curses.color_pair(colors_pair_nb), - *text_attributes) - - wrapped_text = textwrap.wrap(text, self.nb_char_max_line - 1) - wrapped_text = _make_chunk(wrapped_text, self.nb_lines_max) - - for paragraph in wrapped_text: - stdscr.clear() - self.framing_box(stdscr) - for y, line in enumerate(paragraph): - offsetting_x = 0 - for word in line.split(cut_char): - attr = (curses.color_pair(colors_pair_nb), - *text_attributes) - - with TextAttributes(stdscr, *attr): - stdscr.addstr(self.text_pos_y + y, self.text_pos_x + offsetting_x, - word) - stdscr.refresh() - - offsetting_x += len(word) + 1 # Compensates for the space between words. - time.sleep(delay + random.uniform(*random_delay)) - - if callback: - callback(*cargs) - - self._display_end_dialog_indicator(stdscr) - - -def main(stdscr): - text = ("Hello world, how are you today ? ", - "Press a key to skip this dialog. " - "This is a basic example. See doc for more informations. " - "If you have a problem don't hesitate to open an issue.",) - - curses.curs_set(0) - - curses.start_color() - curses.init_pair(1, curses.COLOR_RED, curses.COLOR_BLACK) - curses.init_pair(2, curses.COLOR_CYAN, curses.COLOR_BLACK) - curses.init_pair(3, curses.COLOR_GREEN, curses.COLOR_BLACK) - - - textbox = DialogBox(20, 15, - 40, 6, - title="Tim-ats-d", - title_colors_pair_nb=3, - end_dialog_indicator="►") - - textbox.confirm_dialog_key = (10, 32) - - def func(reply: str): - stdscr.addstr(0, 0, reply) - - for reply in text: - textbox.char_by_char(stdscr, - reply, - 2, - cargs=(reply, ), - callback=func, - text_attributes=(curses.A_ITALIC, curses.A_BOLD)) - - textbox.getkey(stdscr) - stdscr.clear() - - -if __name__ == "__main__": - curses.wrapper(main) - diff --git a/visualdialog/dialog.py b/visualdialog/dialog.py new file mode 100755 index 0000000..38aea60 --- /dev/null +++ b/visualdialog/dialog.py @@ -0,0 +1,374 @@ +#!/usr/bin/env python3 +# +# dialog.py +# +# 2020 Timéo Arnouts +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# + +import curses +import random +import textwrap +import time +from typing import Callable, Dict, Generator, List, NewType, Tuple, Union + +import box +from utils import CursesTextAttributesConstants, TextAttributes, _make_chunk + + +__all__ = ["DialogBox"] + + +class DialogBox(box.TextBox): + """This class provides methods and attributs to manage a dialog box. + + This class inherits all the methods and arguments of TextBox. See TextBox + documentation for more informations. + + Attributes + ---------- + end_dialog_indicator : str, optional + Character that will be displayed in the lower right corner the + character once all the characters have been completed (by default "►"). + String with a length of more than one character can lead to an overflow + of the dialog box frame. + """ + + def __init__( + self, + pos_x: int, + pos_y: int, + box_length: int, + box_width: int, + title: str = "", + title_colors_pair_nb: CursesTextAttributesConstants = 0, + title_text_attributes: Tuple[CursesTextAttributesConstants] = ( + curses.A_BOLD, ), + downtime_chars: Tuple[str] = (",", ".", ":", ";", "!", "?"), + downtime_chars_delay: Union[int, float] = 0.6, + end_dialog_indicator: str = "►"): + super().__init__(pos_x, + pos_y, + box_length, + box_width, + title, + title_colors_pair_nb, + title_text_attributes, + downtime_chars, + downtime_chars_delay) + + self.end_dialog_indicator_char = end_dialog_indicator + + def __enter__(self): + """Returns self.""" + return self + + def __exit__(self, type, value, traceback): + """Returns None.""" + ... + + def _display_end_dialog_indicator( + self, + stdscr, + text_attributes: Tuple[CursesTextAttributesConstants] = ( + curses.A_BOLD, curses.A_BLINK)): + """Displays an end of dialog indicator in the lower right corner of + textbox. + + Parameters + ---------- + stdscr + `curses` window object on which the method will have effect. + text_attributes : tuple of curses text attribute constants, optional + Text attributes of `end_dialog_indicator` method (by default + (curses.A_BOLD, curses.A_BLINK)). + + Returns + ------- + None. + """ + if self.end_dialog_indicator_char: + with TextAttributes(stdscr, *text_attributes): + stdscr.addch(self.end_dialog_indicator_pos_y, + self.end_dialog_indicator_pos_x, + self.end_dialog_indicator_char) + + def char_by_char( + self, + stdscr, + text: str, + colors_pair_nb: int = 0, + text_attr: Tuple[CursesTextAttributesConstants] = (), + words_attr: Union[CursesTextAttributesConstants, + Dict[Tuple[str], + Tuple[CursesTextAttributesConstants]]] = {}, + flash_screen: bool = False, + delay: Union[int, float] = .04, + random_delay: Tuple[int, int] = (0, 0), + callback: Callable = None, + cargs=()): + """Writes the given text character by character in the + current dialog box. + + Parameters + ---------- + stdscr + `curses` window object on which the method will have effect. + text : str + Text that will be displayed character by character in the dialog + box. This text can be wrapped to fit the proportions of the dialog + box. + colors_pair_nb : int, optional + Number of the curses color pair that will be used to color the + text (by default 0. The number zero corresponding to the pair of + white color on black background initialized by `curses`). + text_attr : tuple or list of CursesTextAttributesConstants, optional + Dialog box text attributes (by default an empty tuple). + words_attr : + + flash_screen : bool, optional + Allows or not to flash screen with a short light effect done + before writing the first character via `flash` function from + `curses` module (by default False). + delay : int or float, optional + Waiting time between the writing of each character of text in + second (by default 0.04). + random_delay : list of two number or tuple of two number, optional + Waiting time between the writing of each character in seconds + where time waited is a random number generated in `random_delay` + interval (by default (0, 0)). + callback : callable, optional + Callable called after writing a character and the delay time has + elapsed (by default None). + cargs : list or tuple, optional + All the arguments that will be passed to callback + (by default an empty tuple). + + Returns + ------- + None. + + Notes + ----- + Method flow: + - Calling `framing_box` method. + - Flash screen depending `flash_screen` parameter. + - Cutting text into line via `wrap` function from `textwrap` + module (to stay within the dialog box frame). + - Writing paragraph by paragraph. + - Writing each line of the current paragraph, character by + character. + - Calling `_display_end_dialog_indicator` method. + - Waits until a key contained in the class attribute + `confirm_dialog_key` was pressed before writing the following + paragraph. + + Notes + ----- + If the volume of text displayed is too large to be contained in a + dialog box, text will be automatically cut into paragraphs. The + screen will be completely cleaned when writing each paragraph via + `window.clear()` method of `curses` module. + + See Also + -------- + - Documentation of `wrap` function from `textwrap` module for more + information of the behavior of text wrap + (https://docs.python.org/fr/3.8/library/textwrap.html#textwrap.wrap). + - Documentation of `flash` function from `curses` module + (https://docs.python.org/3/library/curses.html?#curses.flash). + - Documentation of `window.clear()` method from `curses` module + (https://docs.python.org/3/library/curses.html?#curses.window.clear). + """ + self.framing_box(stdscr) + + if flash_screen: + curses.flash() + + wrapped_text = textwrap.wrap(text, self.nb_char_max_line) + wrapped_text = _make_chunk(wrapped_text, self.nb_lines_max) + + for paragraph in wrapped_text: + stdscr.clear() + super().framing_box(stdscr) + + for y, line in enumerate(paragraph): + offsetting_x = 0 + for word in line.split(): + if word in words_attr.keys(): + attr = words_attr[word] + + # Test if only one argument is passed. + if isinstance(attr, int): + attr = (attr, ) + else: + attr = (curses.color_pair(colors_pair_nb), + *text_attr) + + with TextAttributes(stdscr, *attr): + for x, char in enumerate(word): + stdscr.addstr(self.text_pos_y + y, + self.text_pos_x + x + offsetting_x, + char) + stdscr.refresh() + + if char in self.downtime_chars: + time.sleep(self.downtime_chars_delay + + random.uniform(*random_delay)) + else: + time.sleep(delay + + random.uniform(*random_delay)) + + if callback: + callback(*cargs) + + # Waiting for space character. + time.sleep(delay) + + # Compensates for the space between words. + offsetting_x += len(word) + 1 + + self._display_end_dialog_indicator(stdscr) + super().getkey(stdscr) + + def word_by_word( + self, + stdscr, + text: str, + colors_pair_nb: int, + cut_char: str = " ", + text_attr: Tuple[CursesTextAttributesConstants] = (), + words_attr: Dict[str, Tuple[CursesTextAttributesConstants]] = {}, + flash_screen: bool = False, + delay: Union[int, float] = .15, + random_delay: Tuple[int, int] = (0, 0), + callback: Callable = None, + cargs=()): + """Writes the given text word by word at position in the current + dialog box. + + Parameters + ---------- + stdscr + `curses` window object on which the method will have effect. + text : str + Text that will be displayed word by word in the dialog box. This + text can be wrapped to fit the proportions of the dialog box. + See Notes section for more informations. + colors_pair_nb : int, optional + Number of the curses color pair that will be used to color the + text (by default 1). + text_attr : tuple or list of CursesTextAttributesConstants, optional + Dialog box text attributes (by default an empty tuple). + words_attr : + + cut_char : str, optional + The delimiter according which to split the text in word + (by default space character). + flash_screen : bool, optional + Allows or not to flash screen with a short light effect done + before writing the first word via `flash` function from `curses` + module (by default False). + delay : int or float, optional + Waiting time between the writing of each character of text in + second (by default 0.15). + random_delay : list of two number or tuple of two number, optional + Waiting time between the writing of each character in seconds + where time waited is a random number generated in `random_delay` + interval (by default (0, 0)). + callback : callable, optional + Callable called after writing a character and the `delay` time has + elapsed (by default None). + cargs : list or tuple, optional + All the arguments that will be passed to callback (by default + an empty tuple). + + Returns + ------- + None. + + Notes + ----- + Method flow: + - Calling `framing_box` method. + - Flash screen depending `flash_screen` parameter. + - Cutting text into line via `textwrap.wrap` function from + `textwrap` module (to stay within the dialog box frame). + - Writing each line of the current paragraph, word by word. + - Calling `_display_end_dialog_indicator` method. + - Waits until a key contained in the class attribute + `confirm_dialog_key` was pressed before writing the following + paragraph. + + Notes + ----- + If the volume of text displayed is too large to be contained in a + dialog box, text will be automatically cut into paragraphs.The + screen will be completely cleaned when writing each paragraph via + `window.clear` method from `curses` + module. + + See Also + -------- + - Documentation of `wrap` function from `textwrap` module for more + information on the behavior of text wrap + (https://docs.python.org/fr/3.8/library/textwrap.html#textwrap.wrap). + - Documentation of `flash` function from `curses` module + (https://docs.python.org/3/library/curses.html?#curses.flash). + - Documentation of `window.clear()` method from `curses` module + (https://docs.python.org/3/library/curses.html?#curses.window.clear). + """ + super().framing_box(stdscr) + + if flash_screen: + curses.flash() + + attr = (curses.color_pair(colors_pair_nb), + *text_attr) + + wrapped_text = textwrap.wrap(text, self.nb_char_max_line) + wrapped_text = _make_chunk(wrapped_text, self.nb_lines_max) + + for paragraph in wrapped_text: + stdscr.clear() + super().framing_box(stdscr) + for y, line in enumerate(paragraph): + offsetting_x = 0 + for word in line.split(cut_char): + if word in words_attr: + attr = words_attr[word] + else: + attr = (curses.color_pair(colors_pair_nb), + *text_attr) + + with TextAttributes(stdscr, *attr): + stdscr.addstr(self.text_pos_y + y, + self.text_pos_x + offsetting_x, + word) + stdscr.refresh() + + # Compensates for the space between words. + offsetting_x += len(word) + 1 + + time.sleep(delay + random.uniform(*random_delay)) + + if callback: + callback(*cargs) + + self._display_end_dialog_indicator(stdscr) + super().getkey(stdscr) diff --git a/visualdialog/utils.py b/visualdialog/utils.py new file mode 100644 index 0000000..c96a676 --- /dev/null +++ b/visualdialog/utils.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +# +# utils.py +# +# 2020 Timéo Arnouts +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# + +from typing import List, NewType, Tuple, Union + + +# curses text attribute constants are integers. +# See https://docs.python.org/3/library/curses.html?#constants +CursesTextAttributesConstants = NewType("CursesTextAttributesConstants", int) + + +def _make_chunk(iterable: Union[Tuple, List], chunk_length: int) -> Tuple: + """Returns a generator that contains the given iterator separated into + chunk_length bundles.""" + return (iterable[chunk:chunk + chunk_length] + for chunk in range(0, len(iterable), chunk_length)) + + +class TextAttributes: + """A context manager to manage curses text attributs. + + Attributes + ---------- + win + `curses` window object for which the attributes will be managed. + attributes : CursesTextAttributesConstants + List of attributes to activate and desactivate. + """ + def __init__(self, stdscr, *attributes: CursesTextAttributesConstants): + self.win = stdscr + self.attributes = attributes + + def __enter__(self): + """Activates one by one the attributes contained in self.attributes.""" + for attribute in self.attributes: + self.win.attron(attribute) + + def __exit__(self, type, value, traceback): + """Disable one by one the attributes contained in self.attributes.""" + for attribute in self.attributes: + self.win.attroff(attribute) From 6a6276f82d14cc47ae44078341bee05bb826299f Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Sat, 6 Mar 2021 16:50:50 +0100 Subject: [PATCH 02/52] Remove typage hinting in docsting of functions and class. Remove useless shebangs at top of file. Make cargs as default argument. --- doc/examples/confrontation.py | 3 +- doc/examples/context.py | 3 +- doc/examples/monologue.py | 3 +- doc/examples/text_attributes.py | 3 +- doc/generate-documentation.sh | 4 +- setup.py | 2 - tests/test.py | 2 - visualdialog/__init__.py | 2 - visualdialog/box.py | 108 ++++++++++---------- visualdialog/choices_box.py | 2 - visualdialog/dialog.py | 168 +++++++++++++++----------------- visualdialog/utils.py | 9 +- 12 files changed, 145 insertions(+), 164 deletions(-) mode change 100755 => 100644 visualdialog/dialog.py diff --git a/doc/examples/confrontation.py b/doc/examples/confrontation.py index c74d008..e4019da 100644 --- a/doc/examples/confrontation.py +++ b/doc/examples/confrontation.py @@ -1,5 +1,4 @@ -#!/usr/bin/env python3 -# confrontation_example.py +# confrontation.py import curses diff --git a/doc/examples/context.py b/doc/examples/context.py index 1b680f1..a6a1454 100644 --- a/doc/examples/context.py +++ b/doc/examples/context.py @@ -1,5 +1,4 @@ -#!/usr/bin/env python3 -# monologue.py +# context.py import curses diff --git a/doc/examples/monologue.py b/doc/examples/monologue.py index 5c3f101..019b94a 100644 --- a/doc/examples/monologue.py +++ b/doc/examples/monologue.py @@ -1,5 +1,4 @@ -#!/usr/bin/env python3 -# monologue.py +# monologue.py import curses diff --git a/doc/examples/text_attributes.py b/doc/examples/text_attributes.py index cd96f8f..06ad57d 100644 --- a/doc/examples/text_attributes.py +++ b/doc/examples/text_attributes.py @@ -1,5 +1,4 @@ -#!/usr/bin/env python3 -# monologue.py +# text_attributes.py import curses diff --git a/doc/generate-documentation.sh b/doc/generate-documentation.sh index 545c75e..71c01f5 100644 --- a/doc/generate-documentation.sh +++ b/doc/generate-documentation.sh @@ -5,9 +5,11 @@ pydoc3 visualdialog.box >> visualdialog-doc.txt -pydoc3 visualdialog.__init__ >> visualdialog-doc.txt +pydoc3 visualdialog.dialog >> visualdialog-doc.txt echo "wrote visualdialog-doc.txt" pydoc3 -w visualdialog.core +mv visualdialog.core.html visualdialog-doc.html +pydoc3 -w visualdialog.dialog >> visualdialog-doc.html echo "done." diff --git a/setup.py b/setup.py index 06e57a4..5dc6d84 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - from setuptools import setup, find_packages setup( diff --git a/tests/test.py b/tests/test.py index e7a52ed..d151f5d 100644 --- a/tests/test.py +++ b/tests/test.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# # test.py # #  This file contains the tests used to debug the library. diff --git a/visualdialog/__init__.py b/visualdialog/__init__.py index 186e403..e2a75c1 100644 --- a/visualdialog/__init__.py +++ b/visualdialog/__init__.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - """ A librairie which provides class to make easier dialog box in terminal. """ diff --git a/visualdialog/box.py b/visualdialog/box.py index ebb744c..8586c3c 100644 --- a/visualdialog/box.py +++ b/visualdialog/box.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# # box.py # # 2020 Timéo Arnouts @@ -21,20 +19,19 @@ # # +__all__ = ["TextBox"] + import curses import curses.textpad -from typing import NewType, Tuple, Union - -from utils import CursesTextAttributesConstants, TextAttributes - - -__all__ = ["TextBox"] +from typing import List, NewType, Tuple, Union +from utils import (CursesKeyConstants, CursesTextAttributesConstants, + TextAttributes) -class ExitError(Exception): +class PanicError(Exception): def __str__(self): - return "Exit text box" + return "text box was aborted" class TextBox: @@ -45,65 +42,64 @@ class TextBox: Attributes ---------- - pos_x : int + pos_x x position of the dialog box in the terminal. - pos_y : int + pos_y y position of the dialog box in the terminal - box_length : int + box_length Length of the dialog box in the terminal. - box_width : int + box_width Width of the dialog box in the terminal. - title : str, optional + title : optional String that will be displayed in the upper left corner of dialog box if title is an empty string, the title will not be displayed (by default an empty string). - title_colors_pair_nb : CursesTextAttributesConstants, optional + title_colors_pair_nb : optional Number of the curses color pair that will be used to color the title (by default 0. The number zero corresponding to the pair of white color on black background initialized by `curses`). - title_text_attributes : list or tuple of CursesTextAttributesConstants, optional + title_text_attributes : optional Dialog box title text attributes (by default a tuple contains curses.A_BOLD). - downtime_chars : list of str or tuple of str, optional + downtime_chars : optional List of characters that will trigger a `downtime_chars_delay` time second between the writing of each character (by default (",", ".", ":", ";", "!", "?")). `word_by_word` method was not effected by this parameter. - downtime_chars_delay : float, optional + downtime_chars_delay : optional Waiting time in seconds after writing a character contained in `downtime_chars` (by default 0.6). `word_by_word` method was not effected by this parameter. - - Class attributes - ---------------- - confirm_dialog_key : list or tuple + confirm_dialog_key List of accepted key codes to skip dialog. `curses` constants are supported. + panic_key + List of accepted key codes to raise PanicError. `curses` constants are + supported. - To see the list of key constants please refer to `curses` module - documentation - (https://docs.python.org/3/library/curses.html?#constants). - panic_key : list or tuple - + See also + -------- + To see the list of key constants please refer to `curses` module + documentation (https://docs.python.org/3/library/curses.html?#constants). """ - confirm_dialog_key: Tuple = () - panic_key: Tuple = () - - # ~ slow_mode: bool = False - # ~ speed_key: List = () + confirm_dialog_key: Union[Tuple[CursesKeyConstants], + List[CursesKeyConstants]] = () + panic_key: Union[Tuple[CursesKeyConstants], List[CursesKeyConstants]] = () def __init__( - self, - pos_x: int, - pos_y: int, - box_length: int, - box_width: int, - title: str = "", - title_colors_pair_nb: CursesTextAttributesConstants = 0, - title_text_attributes: Tuple[CursesTextAttributesConstants] = ( - curses.A_BOLD, ), - downtime_chars: Tuple[str] = (",", ".", ":", ";", "!", "?"), - downtime_chars_delay: Union[int, float] = 0.6): + self, + pos_x: int, + pos_y: int, + box_length: int, + box_width: int, + title: str = "", + title_colors_pair_nb: CursesTextAttributesConstants = 0, + title_text_attributes: Union[Tuple[CursesTextAttributesConstants], + List[CursesTextAttributesConstants]] = ( + curses.A_BOLD, ), + downtime_chars: Union[Tuple[str], + List[str]] = (",", ".", ":", ";", "!", "?"), + downtime_chars_delay: Union[int, float] = .6): self.pos_x, self.pos_y = pos_x, pos_y self.box_length, self.box_width = box_length, box_width @@ -132,8 +128,8 @@ def position(self) -> Tuple[int, int]: Returns ------- - tuple of two int - x;y position of DialogBox. + position + x;y position of TextBox. """ return self.text_pos_x - 2, self.text_pos_y - 3 @@ -143,8 +139,8 @@ def dimensions(self) -> Tuple[int, int]: Returns ------- - tuple of two int - Dialog box length and width. + dimension + TextBox length and width. """ return self.box_length, self.box_width @@ -153,7 +149,7 @@ def framing_box(self, stdscr): If attribute self.title is empty doesn't display the title. Parameters - --------- + ---------- stdscr `curses` window object on which the method will have effect. @@ -173,9 +169,7 @@ def framing_box(self, stdscr): self.pos_x + title_box_length) with TextAttributes(stdscr, *attr): - stdscr.addstr(self.pos_y + 1, - self.pos_x + 3, - self.title) + stdscr.addstr(self.pos_y + 1, self.pos_x + 3, self.title) # Displays the borders of the dialog box. curses.textpad.rectangle(stdscr, self.pos_y + 2, self.pos_x, @@ -187,7 +181,7 @@ def getkey(self, stdscr): `self.confirm_dialog_key` is not detected. Parameters - --------- + ---------- stdscr `curses` window object on which the method will have effect. @@ -195,6 +189,11 @@ def getkey(self, stdscr): ------- None. + Raises + ------ + PanicError + If a key contained in self.panic_key is pressed. + See also -------- - To see the list of key constants please refer to `curses` module @@ -209,6 +208,7 @@ def getkey(self, stdscr): if key in self.confirm_dialog_key: break elif key in self.panic_key: - raise + raise PanicError else: + # Ignore incorrect keys. ... diff --git a/visualdialog/choices_box.py b/visualdialog/choices_box.py index 0d8bfb6..e52cbf3 100644 --- a/visualdialog/choices_box.py +++ b/visualdialog/choices_box.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# # choices_box.py # # 2020 Timéo Arnouts diff --git a/visualdialog/dialog.py b/visualdialog/dialog.py old mode 100755 new mode 100644 index 38aea60..55a0b0d --- a/visualdialog/dialog.py +++ b/visualdialog/dialog.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# # dialog.py # # 2020 Timéo Arnouts @@ -21,6 +19,8 @@ # # +__all__ = ["DialogBox"] + import curses import random import textwrap @@ -31,9 +31,6 @@ from utils import CursesTextAttributesConstants, TextAttributes, _make_chunk -__all__ = ["DialogBox"] - - class DialogBox(box.TextBox): """This class provides methods and attributs to manage a dialog box. @@ -42,35 +39,28 @@ class DialogBox(box.TextBox): Attributes ---------- - end_dialog_indicator : str, optional + end_dialog_indicator : optional Character that will be displayed in the lower right corner the character once all the characters have been completed (by default "►"). String with a length of more than one character can lead to an overflow of the dialog box frame. """ - def __init__( - self, - pos_x: int, - pos_y: int, - box_length: int, - box_width: int, - title: str = "", - title_colors_pair_nb: CursesTextAttributesConstants = 0, - title_text_attributes: Tuple[CursesTextAttributesConstants] = ( - curses.A_BOLD, ), - downtime_chars: Tuple[str] = (",", ".", ":", ";", "!", "?"), - downtime_chars_delay: Union[int, float] = 0.6, - end_dialog_indicator: str = "►"): - super().__init__(pos_x, - pos_y, - box_length, - box_width, - title, - title_colors_pair_nb, - title_text_attributes, - downtime_chars, - downtime_chars_delay) + self, + pos_x: int, + pos_y: int, + box_length: int, + box_width: int, + title: str = "", + title_colors_pair_nb: CursesTextAttributesConstants = 0, + title_text_attributes: Tuple[CursesTextAttributesConstants] = ( + curses.A_BOLD, ), + downtime_chars: Tuple[str] = (",", ".", ":", ";", "!", "?"), + downtime_chars_delay: Union[int, float] = 0.6, + end_dialog_indicator: str = "►"): + super().__init__(pos_x, pos_y, box_length, box_width, title, + title_colors_pair_nb, title_text_attributes, + downtime_chars, downtime_chars_delay) self.end_dialog_indicator_char = end_dialog_indicator @@ -83,10 +73,10 @@ def __exit__(self, type, value, traceback): ... def _display_end_dialog_indicator( - self, - stdscr, - text_attributes: Tuple[CursesTextAttributesConstants] = ( - curses.A_BOLD, curses.A_BLINK)): + self, + stdscr, + text_attributes: Tuple[CursesTextAttributesConstants] = ( + curses.A_BOLD, curses.A_BLINK)): """Displays an end of dialog indicator in the lower right corner of textbox. @@ -109,19 +99,20 @@ def _display_end_dialog_indicator( self.end_dialog_indicator_char) def char_by_char( - self, - stdscr, - text: str, - colors_pair_nb: int = 0, - text_attr: Tuple[CursesTextAttributesConstants] = (), - words_attr: Union[CursesTextAttributesConstants, - Dict[Tuple[str], - Tuple[CursesTextAttributesConstants]]] = {}, - flash_screen: bool = False, - delay: Union[int, float] = .04, - random_delay: Tuple[int, int] = (0, 0), - callback: Callable = None, - cargs=()): + self, + stdscr, + text: str, + colors_pair_nb: int = 0, + text_attr: Union[Tuple[CursesTextAttributesConstants], + List[CursesTextAttributesConstants]] = (), + words_attr: Union[Dict[Tuple[str], CursesTextAttributesConstants], + Dict[Tuple[str], + Tuple[CursesTextAttributesConstants]]] = {}, + flash_screen: bool = False, + delay: Union[int, float] = .04, + random_delay: Tuple[int, int] = (0, 0), + callback: Callable = None, + cargs: Union[Tuple, List] = ()): """Writes the given text character by character in the current dialog box. @@ -129,35 +120,35 @@ def char_by_char( ---------- stdscr `curses` window object on which the method will have effect. - text : str + text Text that will be displayed character by character in the dialog box. This text can be wrapped to fit the proportions of the dialog box. - colors_pair_nb : int, optional + colors_pair_nb : optional Number of the curses color pair that will be used to color the text (by default 0. The number zero corresponding to the pair of white color on black background initialized by `curses`). - text_attr : tuple or list of CursesTextAttributesConstants, optional - Dialog box text attributes (by default an empty tuple). - words_attr : + text_attr : optional + Dialog box curses text attributes (by default an empty tuple). + words_attr : optional - flash_screen : bool, optional + flash_screen : optional Allows or not to flash screen with a short light effect done before writing the first character via `flash` function from `curses` module (by default False). - delay : int or float, optional + delay : optional Waiting time between the writing of each character of text in second (by default 0.04). - random_delay : list of two number or tuple of two number, optional + random_delay : optional Waiting time between the writing of each character in seconds where time waited is a random number generated in `random_delay` interval (by default (0, 0)). - callback : callable, optional + callback : optional Callable called after writing a character and the delay time has elapsed (by default None). - cargs : list or tuple, optional - All the arguments that will be passed to callback - (by default an empty tuple). + cargs : optional + All the arguments that will be passed to callback (by default an + empty tuple). Returns ------- @@ -217,8 +208,7 @@ def char_by_char( if isinstance(attr, int): attr = (attr, ) else: - attr = (curses.color_pair(colors_pair_nb), - *text_attr) + attr = (curses.color_pair(colors_pair_nb), *text_attr) with TextAttributes(stdscr, *attr): for x, char in enumerate(word): @@ -247,18 +237,21 @@ def char_by_char( super().getkey(stdscr) def word_by_word( - self, - stdscr, - text: str, - colors_pair_nb: int, - cut_char: str = " ", - text_attr: Tuple[CursesTextAttributesConstants] = (), - words_attr: Dict[str, Tuple[CursesTextAttributesConstants]] = {}, - flash_screen: bool = False, - delay: Union[int, float] = .15, - random_delay: Tuple[int, int] = (0, 0), - callback: Callable = None, - cargs=()): + self, + stdscr, + text: str, + colors_pair_nb: int, + cut_char: str = " ", + text_attr: Union[Tuple[CursesTextAttributesConstants], + List[CursesTextAttributesConstants]] = (), + words_attr: Union[Dict[Tuple[str], CursesTextAttributesConstants], + Dict[Tuple[str], + Tuple[CursesTextAttributesConstants]]] = {}, + flash_screen: bool = False, + delay: Union[int, float] = .15, + random_delay: Tuple[int, int] = (0, 0), + callback: Callable = None, + cargs: Union[Tuple, List] = ()): """Writes the given text word by word at position in the current dialog box. @@ -266,35 +259,35 @@ def word_by_word( ---------- stdscr `curses` window object on which the method will have effect. - text : str + text Text that will be displayed word by word in the dialog box. This text can be wrapped to fit the proportions of the dialog box. See Notes section for more informations. - colors_pair_nb : int, optional + colors_pair_nb : optional Number of the curses color pair that will be used to color the text (by default 1). - text_attr : tuple or list of CursesTextAttributesConstants, optional + text_attr : optional Dialog box text attributes (by default an empty tuple). - words_attr : + words_attr : optional - cut_char : str, optional - The delimiter according which to split the text in word - (by default space character). - flash_screen : bool, optional + cut_char : optional + The delimiter according which to split the text in word (by default + space character). + flash_screen : optional Allows or not to flash screen with a short light effect done before writing the first word via `flash` function from `curses` module (by default False). - delay : int or float, optional + delay : optional Waiting time between the writing of each character of text in second (by default 0.15). - random_delay : list of two number or tuple of two number, optional + random_delay : optional Waiting time between the writing of each character in seconds where time waited is a random number generated in `random_delay` interval (by default (0, 0)). - callback : callable, optional + callback : optional Callable called after writing a character and the `delay` time has elapsed (by default None). - cargs : list or tuple, optional + cargs : optional All the arguments that will be passed to callback (by default an empty tuple). @@ -338,8 +331,7 @@ def word_by_word( if flash_screen: curses.flash() - attr = (curses.color_pair(colors_pair_nb), - *text_attr) + attr = (curses.color_pair(colors_pair_nb), *text_attr) wrapped_text = textwrap.wrap(text, self.nb_char_max_line) wrapped_text = _make_chunk(wrapped_text, self.nb_lines_max) @@ -353,13 +345,11 @@ def word_by_word( if word in words_attr: attr = words_attr[word] else: - attr = (curses.color_pair(colors_pair_nb), - *text_attr) + attr = (curses.color_pair(colors_pair_nb), *text_attr) with TextAttributes(stdscr, *attr): stdscr.addstr(self.text_pos_y + y, - self.text_pos_x + offsetting_x, - word) + self.text_pos_x + offsetting_x, word) stdscr.refresh() # Compensates for the space between words. diff --git a/visualdialog/utils.py b/visualdialog/utils.py index c96a676..cb13043 100644 --- a/visualdialog/utils.py +++ b/visualdialog/utils.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# # utils.py # # 2020 Timéo Arnouts @@ -23,11 +21,14 @@ from typing import List, NewType, Tuple, Union - # curses text attribute constants are integers. # See https://docs.python.org/3/library/curses.html?#constants CursesTextAttributesConstants = NewType("CursesTextAttributesConstants", int) +# curses key constants are integers. +# See https://docs.python.org/3/library/curses.html?#constants +CursesKeyConstants = NewType("CursesKeyConstants", int) + def _make_chunk(iterable: Union[Tuple, List], chunk_length: int) -> Tuple: """Returns a generator that contains the given iterator separated into @@ -43,7 +44,7 @@ class TextAttributes: ---------- win `curses` window object for which the attributes will be managed. - attributes : CursesTextAttributesConstants + attributes List of attributes to activate and desactivate. """ def __init__(self, stdscr, *attributes: CursesTextAttributesConstants): From d8c58b972af602f3862ebf9cd38308c029da92f1 Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Sun, 7 Mar 2021 18:11:47 +0100 Subject: [PATCH 03/52] Rewrite of documentation in rst. Clarification in documentation. Add instruction to build doc in README. Change in README. Remove condition to call callback in DialogBox.word_by_word and char_by_char. Change parameter name of TextBox.box_width and TextBox.box_length to width and length. --- CONTRIBUTING.md | 29 +- Makefile | 19 + README.md | 33 +- doc/conf.py | 179 +++++++++ doc/generate-documentation.sh | 15 - doc/images/demo.gif | Bin 0 -> 137105 bytes doc/images/demo.png | Bin 0 -> 3779 bytes doc/index.rst | 56 +++ doc/installation.rst | 13 + doc/requirements.rst | 18 + doc/tutorial.md | 1 - doc/visualdialog.rst | 16 + {doc/examples => examples}/confrontation.py | 0 {doc/examples => examples}/context.py | 0 {doc/examples => examples}/monologue.py | 0 {doc/examples => examples}/text_attributes.py | 0 make.bat | 35 ++ visualdialog/box.py | 188 +++++----- visualdialog/dialog.py | 355 +++++++++--------- 19 files changed, 643 insertions(+), 314 deletions(-) create mode 100644 Makefile create mode 100644 doc/conf.py delete mode 100644 doc/generate-documentation.sh create mode 100644 doc/images/demo.gif create mode 100644 doc/images/demo.png create mode 100644 doc/index.rst create mode 100644 doc/installation.rst create mode 100644 doc/requirements.rst delete mode 100644 doc/tutorial.md create mode 100644 doc/visualdialog.rst rename {doc/examples => examples}/confrontation.py (100%) rename {doc/examples => examples}/context.py (100%) rename {doc/examples => examples}/monologue.py (100%) rename {doc/examples => examples}/text_attributes.py (100%) create mode 100755 make.bat diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4aa510e..c7c7979 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,7 +21,7 @@ When you make a pull request make sure to: * Test your code with `Python3.6` to be sure not to break compatability. * Format your code according [PEP 8](https://www.python.org/dev/peps/pep-0008/). [PEP8 Check](https://github.com/quentinguidee/actions-pep8) will ensure that your code is correctly formatted . -* Document your code following [Numpy documentation conventions](https://numpydoc.readthedocs.io/en/latest/format.html#docstring-standard) if you add new functionality. +* Document your code with [reStructuredText](https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html) if you add new functionality. If you add a feature that changes the API, notify it explicitly. @@ -56,15 +56,20 @@ The following snippet describes Visual-dialog's repository structure. ├── doc/ │ Contains the files related to the documentation. │ │ -│ ├── examples/ -│ │ Contains several examples of use cases of Visual-dialog. +│ ├── images/ +│ │ Contains images used in documentation. │ │ -│ ├── generate-documentation.sh -│ │ A script to generate documentation of public API from the source code. -│ │ Produces two files: visualdialog-documentation.txt and visualdialog.core.html +│ ├── conf.py +│ │ Sphinx's configuration file. │ │ -│ └── tutorial.md -│ A tutorial to learn how to use Visual-dialog. +│ ├── index.rst +│ │ Documentation home page. +│ │ +│ └── visualdialog.rst +│ Documentation of all the public classes and methods in Visual-dialog. +│ +├── examples/ +│ Contains several examples of use cases of Visual-dialog. │ ├── tests/ │ Contains tests for debugging libraries. @@ -90,11 +95,17 @@ The following snippet describes Visual-dialog's repository structure. ├── CONTRIBUTING.md │ This document. │ +├── make.bat +│ To generate documentation on Windows. +│ +├── Makefile +│ To generate documentation on GNU/Linux or MacOS. +│ ├── MANIFEST.in │ Contains list of non Python file. │ ├── README.md │ └── setup.py - Installation of the library. + Installation of the library. ``` diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..bdf21ac --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SOURCEDIR = doc +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/README.md b/README.md index 0209b16..aee6f77 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ This library is still under development. -The api can change. +API can change. ## Features @@ -35,45 +35,46 @@ The api can change. Install Visual-dialog using `pip` (The lib is not yet available on **pypy**): -```bash +```sh python3 -m pip install git+git://github.com/Tim-ats-d/Visual-dialog ``` or update lib to the latest version: -```bash +```sh python3 -m pip install git+git://github.com/Tim-ats-d/Visual-dialog --upgrade ``` ### Requirements * **Python 3.6** or more. -* **pydoc3** to generate the documentation of library. -* [`curses`](https://docs.python.org/3/library/curses.html) module (available on **UNIX** system by default). +* [**Sphinx**](https://www.sphinx-doc.org/en/master/usage/installation.html) to generate the documentation of library. +* [`curses`](https://docs.python.org/3/library/curses.html) Python module (available in standard library of Python on **UNIX**). * Knowledge of [`curses`](https://docs.python.org/3/library/curses.html) librairie. ## Quick-start -### Minimal code - Coming soon. -Read these [examples](doc/examples/). - +Read these [examples](examples/). ## Documentation -Visualdialog's documentation is automatically generated from the source code. - -See [this script](doc/generate-documentation.sh) to build documentation of public API. - -If you have any problems with `pydoc` look at its [documentation](https://docs.python.org/3/library/pydoc.html). +Visualdialog's documentation is automatically generated from the source code by **Sphinx**. +To build it on **GNU/Linux** or **MacOS**: +```sh +make html +``` +Or on **Windows** with **Git Bash**: +```sh +./make.bat html``` +Once generated, the result will be in the `build/html/` folder. ## Contributing We would love for you to contribute to improve **Visual-dialog**. -Take a look at our [Contributing guide](CONTRIBUTING.md) to get started. - +Take a look at our [contributing guide](CONTRIBUTING.md) to get started. +You can also help by reporting bugs. ## License diff --git a/doc/conf.py b/doc/conf.py new file mode 100644 index 0000000..f8549c2 --- /dev/null +++ b/doc/conf.py @@ -0,0 +1,179 @@ +# -*- coding: utf-8 -*- +# +# Configuration file for the Sphinx documentation builder. +# +# This file does only contain a selection of the most common options. For a +# full list see the documentation: +# http://www.sphinx-doc.org/en/master/config + +import os +import sys + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) +sys.path.insert(0, os.path.abspath('../visualdialog')) + +# -- Project information ----------------------------------------------------- + +project = 'Visual-dialog' +copyright = '2021, Arnouts Timéo' +author = 'Arnouts Timéo' + +# The short X.Y version +version = '' +# The full version, including alpha/beta/rc tags +release = '0.6' + + +# -- General configuration --------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', +] + +autodoc_member_order = 'bysource' + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The master toct ree document. +master_doc = 'index' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'friendly' + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'sphinx_rtd_theme' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# The default sidebars (for documents that don't match any pattern) are +# defined by theme itself. Builtin themes are using these templates by +# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', +# 'searchbox.html']``. +# +# html_sidebars = {} + + +# -- Options for HTMLHelp output --------------------------------------------- + +# Output file base name for HTML help builder. +htmlhelp_basename = 'Visual-dialogdoc' + + +# -- Options for LaTeX output ------------------------------------------------ + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'Visual-dialog.tex', 'Visual-dialog Documentation', + 'Arnouts Timéo', 'manual'), +] + + +# -- Options for manual page output ------------------------------------------ + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'visual-dialog', 'Visual-dialog Documentation', + [author], 1) +] + + +# -- Options for Texinfo output ---------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'Visual-dialog', 'Visual-dialog Documentation', + author, 'Visual-dialog', 'One line description of project.', + 'Miscellaneous'), +] + + +# -- Options for Epub output ------------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = project + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +# +# epub_identifier = '' + +# A unique identification for the text. +# +# epub_uid = '' + +# A list of files that should not be packed into the epub file. +epub_exclude_files = ['search.html'] diff --git a/doc/generate-documentation.sh b/doc/generate-documentation.sh deleted file mode 100644 index 71c01f5..0000000 --- a/doc/generate-documentation.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh -# Generates the Visual-dialog public API documentation from the source code. -# Writes documentation in plain text in visualdialog-documentation.txt and in -# HTML in visualdialog.core.html. - - -pydoc3 visualdialog.box >> visualdialog-doc.txt -pydoc3 visualdialog.dialog >> visualdialog-doc.txt - -echo "wrote visualdialog-doc.txt" -pydoc3 -w visualdialog.core -mv visualdialog.core.html visualdialog-doc.html -pydoc3 -w visualdialog.dialog >> visualdialog-doc.html - -echo "done." diff --git a/doc/images/demo.gif b/doc/images/demo.gif new file mode 100644 index 0000000000000000000000000000000000000000..479ed2f4c1be0bdf36da1724570242b2d517f1e6 GIT binary patch literal 137105 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 Zm*DC6&8xdOl?y7RF)@IGYw!K`@;`ImMHK)5 literal 0 HcmV?d00001 diff --git a/doc/index.rst b/doc/index.rst new file mode 100644 index 0000000..b62eea0 --- /dev/null +++ b/doc/index.rst @@ -0,0 +1,56 @@ +.. Visual-dialog documentation master file, created by + sphinx-quickstart on Sat Mar 6 17:30:24 2021. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to Visual-dialog's documentation +======================================== + +**Visual-dialog** is a **Python library** that allows you to make dialog box in a terminal easily. +**Visual-dialog** uses ``curses`` to display text in terminals. + +.. image:: ./images/demo.png + :align: center + +**Features:** + + - Automatic text scrolling. + - Text coloring and formatting. + - Hackable and configurable. + +.. IMPORTANT:: + I recommend that you have some knowledge of Python ``curses`` module in order to use the library to its full potential. + + Here several links to learn ``curses``: + + - https://docs.python.org/3/howto/curses.html#curses-howto + - https://docs.python.org/3/library/curses.html#module-curses + +Getting started +--------------- + +- **First steps:** +- **Examples:** Many examples are available in the `repository `_. + +Getting help +------------ + +- If you're looking for something specific, try the :ref:`index ` or :ref:`searching `. +- Report bugs in the `issue tracker `_. + +.. toctree:: + :maxdepth: 3 + :caption: Contents: + + ./requirements.rst + ./installation.rst + ./visualdialog + +Changelog +--------- + +The list of versions and their changelogs can be found on repository: + +- https://github.com/Tim-ats-d/Visual-dialog/releases/ + + diff --git a/doc/installation.rst b/doc/installation.rst new file mode 100644 index 0000000..2db73f2 --- /dev/null +++ b/doc/installation.rst @@ -0,0 +1,13 @@ +Installation +============ + +Using PIP +--------- + +Install **Visual-dialog** using ``pip`` (The lib is not yet available on **pypy**):: + + python3 -m pip install git+git://github.com/Tim-ats-d/Visual-dialog + +or update library to the latest version:: + + python3 -m pip install git+git://github.com/Tim-ats-d/Visual-dialog --upgrade diff --git a/doc/requirements.rst b/doc/requirements.rst new file mode 100644 index 0000000..fd41259 --- /dev/null +++ b/doc/requirements.rst @@ -0,0 +1,18 @@ +Requirements +============ + +Curses +------ + +**Visual-dialog** works with ``curses`` Python module. +It is available in the standard **Python** library on **UNIX** but it doesn't work out-of-the-box on **Windows**. + +See this explanations to install ``curses`` on **Windows** (untested). + +- https://www.devdungeon.com/content/curses-windows-python + +Other requirements +------------------ + +- **Python 3.6** or more. +- `Sphinx `_ to generate the documentation of library. diff --git a/doc/tutorial.md b/doc/tutorial.md deleted file mode 100644 index 8d1c8b6..0000000 --- a/doc/tutorial.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/doc/visualdialog.rst b/doc/visualdialog.rst new file mode 100644 index 0000000..6cb11ae --- /dev/null +++ b/doc/visualdialog.rst @@ -0,0 +1,16 @@ +API documentation +================= + +Text box +-------- + +.. IMPORTANT:: + **Visual-dialog** contains two **modules**: ``visualdialog.box`` and ``visualdialog.dialog``. + These two **modules** are both imported when you import ``visualdialog``. + + Two **classes** are defined in these modules but only ``DialogBox`` is destined to be instantiated. + +.. automodule:: box + :members: +.. automodule:: dialog + :members: diff --git a/doc/examples/confrontation.py b/examples/confrontation.py similarity index 100% rename from doc/examples/confrontation.py rename to examples/confrontation.py diff --git a/doc/examples/context.py b/examples/context.py similarity index 100% rename from doc/examples/context.py rename to examples/context.py diff --git a/doc/examples/monologue.py b/examples/monologue.py similarity index 100% rename from doc/examples/monologue.py rename to examples/monologue.py diff --git a/doc/examples/text_attributes.py b/examples/text_attributes.py similarity index 100% rename from doc/examples/text_attributes.py rename to examples/text_attributes.py diff --git a/make.bat b/make.bat new file mode 100755 index 0000000..788bb23 --- /dev/null +++ b/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=doc +set BUILDDIR=build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% + +:end +popd diff --git a/visualdialog/box.py b/visualdialog/box.py index 8586c3c..d825fbd 100644 --- a/visualdialog/box.py +++ b/visualdialog/box.py @@ -37,61 +37,71 @@ def __str__(self): class TextBox: """This class provides attributs and methods to manage a text box. - This class provides a general API for text boxes, it does not need to be - instantiated. - - Attributes - ---------- - pos_x - x position of the dialog box in the terminal. - pos_y - y position of the dialog box in the terminal - box_length - Length of the dialog box in the terminal. - box_width - Width of the dialog box in the terminal. - title : optional - String that will be displayed in the upper left corner of dialog box - if title is an empty string, the title will not be displayed (by - default an empty string). - title_colors_pair_nb : optional - Number of the curses color pair that will be used to color the title - (by default 0. The number zero corresponding to the pair of white - color on black background initialized by `curses`). - title_text_attributes : optional - Dialog box title text attributes (by default a tuple contains - curses.A_BOLD). - downtime_chars : optional - List of characters that will trigger a `downtime_chars_delay` time - second between the writing of each character (by - default (",", ".", ":", ";", "!", "?")). - `word_by_word` method was not effected by this parameter. - downtime_chars_delay : optional + .. NOTE:: + This class provides a general API for text boxes, it does not need + to be instantiated. + + :param pos_x: x position of the dialog box in ``curses`` window + object on which methods will have effects. + :type pos_x: int + + :param pos_y: y position of the dialog box in ``curses`` window + object on which methods will have effects. + :type pos_y: int + + :param length: Length of the dialog box in ``curses`` window object + on which methods will have effects. + :type length: int + + :param width: Width of the dialog box in ``curses`` window object on + which methods will have effects. + :type width: int + + :param width: Width of the dialog box in the terminal. + :type width: Optional[int] + + :param title: String that will be displayed in the upper left corner + of dialog box. + If title is an empty string, the title will not be displayed. + This defaults an empty string. + :type title: Optional[str] + + :param title_colors_pair_nb: + Number of the curses color pair that will be used to color the + title. Zero corresponding to the pair of white color on black + background initialized by ``curses``). + This defaults to ``0``. + :type title_colors_pair_nb: Optional[int] + + :param title_text_attributes: + Dialog box title text attributes. This defaults to a tuple + contains ``curses.A_BOLD``. + :type title_text_attrubutes: Optional[Union[Tuple[CursesTextAttributesConstants],List[CursesTextAttributesConstants]]] + + :param downtime_chars: + List of characters that will trigger a ``downtime_chars_delay`` + time second between the writing of each character. + This defaults to ``(",", ".", ":", ";", "!", "?")``. + :type downtime_chars: Optional[Union[Tuple[str],List[str]]] + + :param downtime_chars_delay: Waiting time in seconds after writing a character contained in - `downtime_chars` (by default 0.6). - `word_by_word` method was not effected by this parameter. - confirm_dialog_key - List of accepted key codes to skip dialog. `curses` constants are - supported. - panic_key - List of accepted key codes to raise PanicError. `curses` constants are - supported. - - See also - -------- - To see the list of key constants please refer to `curses` module - documentation (https://docs.python.org/3/library/curses.html?#constants). + ``downtime_chars``. + This defaults to ``0.6``. + :type downtime_chars_delay: Optional[Union[int,float]] """ + #: List of accepted key codes to skip dialog. ``curses`` constants are supported. This defaults to an empty tuple. confirm_dialog_key: Union[Tuple[CursesKeyConstants], - List[CursesKeyConstants]] = () + List[CursesKeyConstants]] = () + #: List of accepted key codes to raise PanicError. ``curses`` constants are supported. This defaults to an empty tuple. panic_key: Union[Tuple[CursesKeyConstants], List[CursesKeyConstants]] = () def __init__( self, pos_x: int, pos_y: int, - box_length: int, - box_width: int, + length: int, + width: int, title: str = "", title_colors_pair_nb: CursesTextAttributesConstants = 0, title_text_attributes: Union[Tuple[CursesTextAttributesConstants], @@ -101,106 +111,86 @@ def __init__( List[str]] = (",", ".", ":", ";", "!", "?"), downtime_chars_delay: Union[int, float] = .6): self.pos_x, self.pos_y = pos_x, pos_y - self.box_length, self.box_width = box_length, box_width + self.length, self.width = length, width # Compensation for the left border of the dialog box. self.text_pos_x = pos_x + 2 # Compensation for the upper border of the dialog box. self.text_pos_y = pos_y + 3 - self.nb_char_max_line = box_length - 4 - self.nb_lines_max = box_width - 2 + self.nb_char_max_line = length - 4 + self.nb_lines_max = width - 2 self.title = title if self.title: self.title_colors = curses.color_pair(title_colors_pair_nb) self.title_text_attributes = title_text_attributes - self.end_dialog_indicator_pos_x = pos_x + box_length - 2 - self.end_dialog_indicator_pos_y = pos_y + box_width + 1 + self.end_dialog_indicator_pos_x = pos_x + length - 2 + self.end_dialog_indicator_pos_y = pos_y + width + 1 self.downtime_chars = downtime_chars self.downtime_chars_delay = downtime_chars_delay @property def position(self) -> Tuple[int, int]: - """Returns a tuple contains x;y position. + """Returns a tuple contains x;y position of ``TextBox``. - Returns - ------- - position - x;y position of TextBox. + :returns: x;y position of ``TextBox``. + :rtype: Tuple[int, int] """ return self.text_pos_x - 2, self.text_pos_y - 3 @property def dimensions(self) -> Tuple[int, int]: - """Returns a tuple contains dimensions of dialog box. + """Returns a tuple contains dimensions of ``TextBox``. - Returns - ------- - dimension - TextBox length and width. + :returns: Length and width of ``TextBox``. + :rtype: Tuple[int, int] """ - return self.box_length, self.box_width + return self.length, self.width def framing_box(self, stdscr): """Displays dialog box borders and his title. - If attribute self.title is empty doesn't display the title. - Parameters - ---------- - stdscr - `curses` window object on which the method will have effect. + If attribute ``self.title`` is empty doesn't display the title. - Returns - ------- - None. + :param stdscr: ``curses`` window object on which the method will + have effect. """ - title_box_length = len(self.title) + 4 - title_box_width = 2 + title_length = len(self.title) + 4 + title_width = 2 # Displays the title and the title box. if self.title: attr = (self.title_colors, *self.title_text_attributes) curses.textpad.rectangle(stdscr, self.pos_y, self.pos_x + 1, - self.pos_y + title_box_width, - self.pos_x + title_box_length) + self.pos_y + title_width, + self.pos_x + title_length) with TextAttributes(stdscr, *attr): stdscr.addstr(self.pos_y + 1, self.pos_x + 3, self.title) # Displays the borders of the dialog box. curses.textpad.rectangle(stdscr, self.pos_y + 2, self.pos_x, - self.pos_y + 2 + self.box_width, - self.pos_x + self.box_length) + self.pos_y + 2 + self.width, + self.pos_x + self.length) def getkey(self, stdscr): """Blocks execution as long as a key contained in - `self.confirm_dialog_key` is not detected. - - Parameters - ---------- - stdscr - `curses` window object on which the method will have effect. - - Returns - ------- - None. - - Raises - ------ - PanicError - If a key contained in self.panic_key is pressed. - - See also - -------- - - To see the list of key constants please refer to `curses` module - documentation - (https://docs.python.org/3/library/curses.html?#constants). - - Documentation of `window.getch` method from `curses` module - (https://docs.python.org/3/library/curses.html?#curses.window.getch). + ``self.confirm_dialog_key`` is not detected. + + + :param stdscr: ``curses`` window object on which the method will + have effect. + :raises PanicError: If a key contained in ``self.panic_key`` is + pressed. + + .. NOTE:: + - To see the list of key constants please refer to + `this curses documentation `_. + - This method uses ``window.getch`` method from ``curses`` module. Please refer to `curses documentation `_ for more informations. """ while 1: key = stdscr.getch() diff --git a/visualdialog/dialog.py b/visualdialog/dialog.py index 55a0b0d..1bf0aa7 100644 --- a/visualdialog/dialog.py +++ b/visualdialog/dialog.py @@ -34,23 +34,28 @@ class DialogBox(box.TextBox): """This class provides methods and attributs to manage a dialog box. - This class inherits all the methods and arguments of TextBox. See TextBox - documentation for more informations. - - Attributes - ---------- - end_dialog_indicator : optional + :param end_dialog_indicator: Character that will be displayed in the lower right corner the - character once all the characters have been completed (by default "►"). - String with a length of more than one character can lead to an overflow - of the dialog box frame. + character once all the characters have been completed. + String with a length of more than one character can lead to an + overflow of the dialog box frame. + This defaults to ``"►"``. + :type end_dialog_indicator: str + + .. NOTE:: + This class inherits all the methods and arguments of ``TextBox``. + See ``TextBox`` documentation for more informations. + + .. WARNING:: + Parameters ``downtime_chars`` and ``downtime_chars_delay`` do + not affect ``word_by_word`` method. """ def __init__( self, pos_x: int, pos_y: int, - box_length: int, - box_width: int, + length: int, + width: int, title: str = "", title_colors_pair_nb: CursesTextAttributesConstants = 0, title_text_attributes: Tuple[CursesTextAttributesConstants] = ( @@ -58,18 +63,16 @@ def __init__( downtime_chars: Tuple[str] = (",", ".", ":", ";", "!", "?"), downtime_chars_delay: Union[int, float] = 0.6, end_dialog_indicator: str = "►"): - super().__init__(pos_x, pos_y, box_length, box_width, title, + super().__init__(pos_x, pos_y, length, width, title, title_colors_pair_nb, title_text_attributes, downtime_chars, downtime_chars_delay) self.end_dialog_indicator_char = end_dialog_indicator def __enter__(self): - """Returns self.""" return self def __exit__(self, type, value, traceback): - """Returns None.""" ... def _display_end_dialog_indicator( @@ -77,20 +80,16 @@ def _display_end_dialog_indicator( stdscr, text_attributes: Tuple[CursesTextAttributesConstants] = ( curses.A_BOLD, curses.A_BLINK)): - """Displays an end of dialog indicator in the lower right corner of - textbox. - - Parameters - ---------- - stdscr - `curses` window object on which the method will have effect. - text_attributes : tuple of curses text attribute constants, optional - Text attributes of `end_dialog_indicator` method (by default - (curses.A_BOLD, curses.A_BLINK)). - - Returns - ------- - None. + """Displays an end of dialog indicator in the lower right corner + of textbox. + + :param stdscr: ``curses`` window object on which the method + will have effect. + + :param text_attributes: Text attributes of + ``end_dialog_indicator`` method. This defaults to + ``(curses.A_BOLD, curses.A_BLINK)``. + :type text_attributes: Optional[Tuple[CursesTextAttributesConstants]] """ if self.end_dialog_indicator_char: with TextAttributes(stdscr, *text_attributes): @@ -110,81 +109,84 @@ def char_by_char( Tuple[CursesTextAttributesConstants]]] = {}, flash_screen: bool = False, delay: Union[int, float] = .04, - random_delay: Tuple[int, int] = (0, 0), - callback: Callable = None, + random_delay: Tuple[float, float] = (0, 0), + callback: Callable = lambda : None, cargs: Union[Tuple, List] = ()): - """Writes the given text character by character in the - current dialog box. - - Parameters - ---------- - stdscr - `curses` window object on which the method will have effect. - text - Text that will be displayed character by character in the dialog - box. This text can be wrapped to fit the proportions of the dialog - box. - colors_pair_nb : optional - Number of the curses color pair that will be used to color the - text (by default 0. The number zero corresponding to the pair of - white color on black background initialized by `curses`). - text_attr : optional - Dialog box curses text attributes (by default an empty tuple). - words_attr : optional - - flash_screen : optional - Allows or not to flash screen with a short light effect done - before writing the first character via `flash` function from - `curses` module (by default False). - delay : optional - Waiting time between the writing of each character of text in - second (by default 0.04). - random_delay : optional - Waiting time between the writing of each character in seconds - where time waited is a random number generated in `random_delay` - interval (by default (0, 0)). - callback : optional - Callable called after writing a character and the delay time has - elapsed (by default None). - cargs : optional - All the arguments that will be passed to callback (by default an - empty tuple). - - Returns - ------- - None. - - Notes - ----- - Method flow: - - Calling `framing_box` method. - - Flash screen depending `flash_screen` parameter. - - Cutting text into line via `wrap` function from `textwrap` - module (to stay within the dialog box frame). - - Writing paragraph by paragraph. - - Writing each line of the current paragraph, character by - character. - - Calling `_display_end_dialog_indicator` method. - - Waits until a key contained in the class attribute - `confirm_dialog_key` was pressed before writing the following - paragraph. - - Notes - ----- - If the volume of text displayed is too large to be contained in a - dialog box, text will be automatically cut into paragraphs. The - screen will be completely cleaned when writing each paragraph via - `window.clear()` method of `curses` module. - - See Also - -------- - - Documentation of `wrap` function from `textwrap` module for more - information of the behavior of text wrap - (https://docs.python.org/fr/3.8/library/textwrap.html#textwrap.wrap). - - Documentation of `flash` function from `curses` module - (https://docs.python.org/3/library/curses.html?#curses.flash). - - Documentation of `window.clear()` method from `curses` module - (https://docs.python.org/3/library/curses.html?#curses.window.clear). + """Writes the given text character by character in the current + dialog box. + + :param stdscr: ``curses`` window object on which the method will + have effect. + + :param text: Text that will be displayed character by character + in the dialog box. This text can be wrapped to fit the + proportions of the dialog box. + :type text: str + + :param colors_pair_nb: + Number of the curses color pair that will be used to color + the text. The number zero corresponding to the pair of + white color on black background initialized by ``curses``). + This defaults to ``0``. + :type colors_pair_nb: Optional[int] + + :param text_attr: Dialog box curses text attributes. This + defaults an empty tuple. + :type text_attr: Optional[Union[Tuple[CursesTextAttributesConstants],List[CursesTextAttributesConstants]]] + + :param words_attr: TODO + :type words_atttr: TODO + + :param flash_screen: Allows or not to flash screen with a short + light effect done before writing the first character by + ``flash`` function from ``curses`` module. This defaults to + ``False``. + :type flash_screen: Optional[bool] + + :param delay: Waiting time between the writing of each character + of text in second. This defaults to ``0.04``. + :type delay: Optional[Union[int, float]] + + :param random_delay: Waiting time between the writing of each + character in seconds where time waited is a random number + generated in ``random_delay`` interval. This defaults to + ``(0, 0)``. + :type random_delay: Optional[Tuple[float, flot],List[float, float]] + + :param callback: Callable called after writing a character and + the delay time has elapsed. This defaults to a lambda which + do nothing. + :type callback: Optional[Callable] + + :param cargs: All the arguments that will be passed to callback. + This defaults to an empty tuple. + :type cargs: Optional[Union[Tuple[Any],List[Any]]] + + .. NOTE:: + Method flow: + - Calling ``framing_box`` method. + - Flash screen depending ``flash_screen`` parameter. + - Cutting text into line to stay within the dialog box + frame. + - Writing paragraph by paragraph. + - Writing each line of the current paragraph, character + by character. + - Waits until a key contained in the class attribute + ``confirm_dialog_key`` was pressed before writing the + following paragraph. + - Complete cleaning ``stdscr``. + + .. WARNING:: + If the volume of text displayed is too large to be contained + in a dialog box, text will be automatically cut into + paragraphs using ``textwrap.wrap`` function. See + `textwrap module documentation `_. + for more information of the behavior of text wrap. + + .. WARNING:: + ``stdscr`` will be completely cleaned when writing each + paragraph by ``window.clear()`` method of ``curses`` + module. """ self.framing_box(stdscr) @@ -224,8 +226,7 @@ def char_by_char( time.sleep(delay + random.uniform(*random_delay)) - if callback: - callback(*cargs) + callback(*cargs) # Waiting for space character. time.sleep(delay) @@ -249,82 +250,89 @@ def word_by_word( Tuple[CursesTextAttributesConstants]]] = {}, flash_screen: bool = False, delay: Union[int, float] = .15, - random_delay: Tuple[int, int] = (0, 0), + random_delay: Tuple[float, float] = (0, 0), callback: Callable = None, cargs: Union[Tuple, List] = ()): """Writes the given text word by word at position in the current dialog box. - Parameters - ---------- - stdscr - `curses` window object on which the method will have effect. - text - Text that will be displayed word by word in the dialog box. This - text can be wrapped to fit the proportions of the dialog box. - See Notes section for more informations. - colors_pair_nb : optional - Number of the curses color pair that will be used to color the - text (by default 1). - text_attr : optional - Dialog box text attributes (by default an empty tuple). - words_attr : optional - - cut_char : optional - The delimiter according which to split the text in word (by default - space character). - flash_screen : optional - Allows or not to flash screen with a short light effect done - before writing the first word via `flash` function from `curses` - module (by default False). - delay : optional - Waiting time between the writing of each character of text in - second (by default 0.15). - random_delay : optional - Waiting time between the writing of each character in seconds - where time waited is a random number generated in `random_delay` - interval (by default (0, 0)). - callback : optional - Callable called after writing a character and the `delay` time has - elapsed (by default None). - cargs : optional - All the arguments that will be passed to callback (by default - an empty tuple). - - Returns - ------- - None. - - Notes - ----- + :param stdscr: ``curses`` window object on which the method will + have effect. + + :param text: Text that will be displayed word by word in the + dialog box. This text can be wrapped to fit the proportions + of the dialog box. + :type text: str + + :param colors_pair_nb: + Number of the curses color pair that will be used to color + the text. The number zero corresponding to the pair of + white color on black background initialized by ``curses``). + This defaults to ``0``. + :type colors_pair_nb: Optional[int] + + :param text_attr: Dialog box curses text attributes. This + defaults an empty tuple. + :type text_attr: Optional[Union[Tuple[CursesTextAttributesConstants],List[CursesTextAttributesConstants]]] + + :param words_attr: TODO + :type words_atttr: TODO + + :param cut_char: The delimiter according which to split the text + in word. This defaults to ``" "``. + :type cut_char: str + + :param flash_screen: Allows or not to flash screen with a short + light effect done before writing the first character by + ``flash`` function from ``curses`` module. This defaults to + ``False``. + :type flash_screen: Optional[bool] + + :param delay: Waiting time between the writing of each word of + ``text`` in second. This defaults to ``0.15``. + :type delay: Optional[Union[int, float]] + + :param random_delay: Waiting time between the writing of each + word in seconds where time waited is a random number + generated in ``random_delay`` interval. This defaults to + ``(0, 0)``. + :type random_delay: Optional[Tuple[float, float],List[float, float]] + + :param callback: Callable called after writing a word and the + delay time has elapsed. This defaults to a lambda which do + nothing. + :type callback: Optional[Callable] + + :param cargs: All the arguments that will be passed to callback. + This defaults to an empty tuple. + :type cargs: Optional[Union[Tuple[Any],List[Any]]] + + .. NOTE:: Method flow: - - Calling `framing_box` method. - - Flash screen depending `flash_screen` parameter. - - Cutting text into line via `textwrap.wrap` function from - `textwrap` module (to stay within the dialog box frame). - - Writing each line of the current paragraph, word by word. - - Calling `_display_end_dialog_indicator` method. + - Calling ``framing_box`` method. + - Flash screen depending ``flash_screen`` parameter. + - Cutting text into line to stay within the dialog box + frame. + - Writing paragraph by paragraph. + - Writing each line of the current paragraph, word by + word. + - Calling ``_display_end_dialog_indicator`` method. - Waits until a key contained in the class attribute - `confirm_dialog_key` was pressed before writing the following - paragraph. - - Notes - ----- - If the volume of text displayed is too large to be contained in a - dialog box, text will be automatically cut into paragraphs.The - screen will be completely cleaned when writing each paragraph via - `window.clear` method from `curses` + ``confirm_dialog_key`` was pressed before writing the + following paragraph. + - Complete cleaning ``stdscr``. + + .. WARNING:: + If the volume of text displayed is too large to be contained + in a dialog box, text will be automatically cut into + paragraphs using ``textwrap.wrap`` function. See + `textwrap module documentation `_ + for more information of the behavior of text wrap. + + .. WARNING:: + ``stdscr`` will be completely cleaned when writing each + paragraph by ``window.clear()`` method of ``curses`` module. - - See Also - -------- - - Documentation of `wrap` function from `textwrap` module for more - information on the behavior of text wrap - (https://docs.python.org/fr/3.8/library/textwrap.html#textwrap.wrap). - - Documentation of `flash` function from `curses` module - (https://docs.python.org/3/library/curses.html?#curses.flash). - - Documentation of `window.clear()` method from `curses` module - (https://docs.python.org/3/library/curses.html?#curses.window.clear). """ super().framing_box(stdscr) @@ -357,8 +365,7 @@ def word_by_word( time.sleep(delay + random.uniform(*random_delay)) - if callback: - callback(*cargs) + callback(*cargs) self._display_end_dialog_indicator(stdscr) super().getkey(stdscr) From 32541b11dcc4981ee078b6030d5f4fe1a962a506 Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Sun, 7 Mar 2021 18:33:06 +0100 Subject: [PATCH 04/52] Mardown correction in README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index aee6f77..80e0a95 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,8 @@ make html ``` Or on **Windows** with **Git Bash**: ```sh -./make.bat html``` +./make.bat html +``` Once generated, the result will be in the `build/html/` folder. From 0316f526f0ff4fef4a192a2ea1b124c9f90d9815 Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Sun, 7 Mar 2021 20:54:37 +0100 Subject: [PATCH 05/52] Add git clone in installation processus --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 80e0a95..2617c4e 100644 --- a/README.md +++ b/README.md @@ -61,10 +61,14 @@ Read these [examples](examples/). Visualdialog's documentation is automatically generated from the source code by **Sphinx**. To build it on **GNU/Linux** or **MacOS**: ```sh +git clone https://github.com/Tim-ats-d/Visual-dialog.git +cd Visual-dialog make html ``` Or on **Windows** with **Git Bash**: ```sh +git clone https://github.com/Tim-ats-d/Visual-dialog.git +cd Visual-dialog ./make.bat html ``` From 5324a631788632d2791202359805eafb010af6aa Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Mon, 8 Mar 2021 15:16:24 +0100 Subject: [PATCH 06/52] Add more accurate instructions in CONTRIBUTING.md --- CONTRIBUTING.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c7c7979..944043f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,6 +14,7 @@ You need: * `dev` : beta releases. To fix a minor problem or add new features create a new branch in this form: `username-dev`. +Please push on the `dev` branch, any pull request on the `main` branch will be refused. ## Conventions @@ -27,9 +28,9 @@ If you add a feature that changes the API, notify it explicitly. ## Download -Download projet: +Download the `dev` branch of the project: ```bash -git clone https://github.com/Tim-ats-d/Visual-dialog +git clone -b dev https://github.com/Tim-ats-d/Visual-dialog.git ``` Install Visual-dialog using `pip` (The lib is not yet available on **pypy**): From 4a6b1b195326ff045efb388f588280cf1f14293b Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Mon, 8 Mar 2021 15:45:08 +0100 Subject: [PATCH 07/52] Change Python version required --- CONTRIBUTING.md | 4 ++-- README.md | 2 +- doc/requirements.rst | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 944043f..21b9d99 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,7 +5,7 @@ You need: -* `Python3.6`: you can install it by following [Python3's documentation](https://www.python.org/downloads/). +* `Python3.7`: you can install it by following [Python3's documentation](https://www.python.org/downloads/). * `curses`: available in standard library of `Python` but it doesn't work out-of-the-box on `Windows`. See [this](https://www.devdungeon.com/content/curses-windows-python) explanations to install `curses` on Windows. ## Branches @@ -20,7 +20,7 @@ Please push on the `dev` branch, any pull request on the `main` branch will be r When you make a pull request make sure to: -* Test your code with `Python3.6` to be sure not to break compatability. +* Test your code with `Python3.7` to be sure not to break compatability. * Format your code according [PEP 8](https://www.python.org/dev/peps/pep-0008/). [PEP8 Check](https://github.com/quentinguidee/actions-pep8) will ensure that your code is correctly formatted . * Document your code with [reStructuredText](https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html) if you add new functionality. diff --git a/README.md b/README.md index 2617c4e..4abab45 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ python3 -m pip install git+git://github.com/Tim-ats-d/Visual-dialog --upgrade ``` ### Requirements -* **Python 3.6** or more. +* **Python 3.7** or more. * [**Sphinx**](https://www.sphinx-doc.org/en/master/usage/installation.html) to generate the documentation of library. * [`curses`](https://docs.python.org/3/library/curses.html) Python module (available in standard library of Python on **UNIX**). * Knowledge of [`curses`](https://docs.python.org/3/library/curses.html) librairie. diff --git a/doc/requirements.rst b/doc/requirements.rst index fd41259..e616416 100644 --- a/doc/requirements.rst +++ b/doc/requirements.rst @@ -14,5 +14,5 @@ See this explanations to install ``curses`` on **Windows** (untested). Other requirements ------------------ -- **Python 3.6** or more. +- **Python 3.7** or more. - `Sphinx `_ to generate the documentation of library. From c663e99acd6895839da6bed2600ca3999319023d Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Mon, 8 Mar 2021 19:25:19 +0100 Subject: [PATCH 08/52] Add better instructions in README, fix import in __init__.py --- CONTRIBUTING.md | 12 ++++++------ README.md | 25 ++++++++++++++----------- visualdialog/__init__.py | 2 +- visualdialog/box.py | 2 +- visualdialog/dialog.py | 6 +++--- 5 files changed, 25 insertions(+), 22 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 21b9d99..fc1ad61 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,8 +5,9 @@ You need: -* `Python3.7`: you can install it by following [Python3's documentation](https://www.python.org/downloads/). +* `Python 3.7`: you can install it by following [Python3's documentation](https://www.python.org/downloads/). * `curses`: available in standard library of `Python` but it doesn't work out-of-the-box on `Windows`. See [this](https://www.devdungeon.com/content/curses-windows-python) explanations to install `curses` on Windows. +* [`Sphinx`](https://www.sphinx-doc.org/en/master/usage/installation.html): to generate documentation. ## Branches @@ -29,18 +30,17 @@ If you add a feature that changes the API, notify it explicitly. ## Download Download the `dev` branch of the project: -```bash +```sh git clone -b dev https://github.com/Tim-ats-d/Visual-dialog.git ``` -Install Visual-dialog using `pip` (The lib is not yet available on **pypy**): - -```bash +Install Visual-dialog using `pip` (The lib is not yet available on **pypi**): +```sh python3 -m pip install git+git://github.com/Tim-ats-d/Visual-dialog ``` or update lib to the latest version: -```bash +```sh python3 -m pip install git+git://github.com/Tim-ats-d/Visual-dialog --upgrade ``` The list of versions and their changelogs can be found [here](https://github.com/Tim-ats-d/Visual-dialog/releases/). diff --git a/README.md b/README.md index 4abab45..0b6203c 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,3 @@ -

- Visual-dialog -

- Library to make easier dialog box in terminal. -

-
-
- Demo -
+

+ Library to make easier dialog box in terminal. +

+ Visual-dialog +

This library is still under development. API can change. @@ -28,12 +24,11 @@ API can change. ⚙️ Hackable and configurable . - ## Installation ### Using pip -Install Visual-dialog using `pip` (The lib is not yet available on **pypy**): +Install Visual-dialog using `pip` (The lib is not yet available on **pypi**): ```sh python3 -m pip install git+git://github.com/Tim-ats-d/Visual-dialog @@ -44,6 +39,14 @@ or update lib to the latest version: python3 -m pip install git+git://github.com/Tim-ats-d/Visual-dialog --upgrade ``` +### From source + +```sh +git clone https://github.com/Tim-ats-d/Visual-dialog.git +cd Visual-dialog +pip install . +``` + ### Requirements * **Python 3.7** or more. * [**Sphinx**](https://www.sphinx-doc.org/en/master/usage/installation.html) to generate the documentation of library. diff --git a/visualdialog/__init__.py b/visualdialog/__init__.py index e2a75c1..5d5b787 100644 --- a/visualdialog/__init__.py +++ b/visualdialog/__init__.py @@ -5,4 +5,4 @@ __version__ = 0.6 __author__ = "Arnouts Timéo" -from dialog import DialogBox +from .dialog import DialogBox diff --git a/visualdialog/box.py b/visualdialog/box.py index d825fbd..82c3514 100644 --- a/visualdialog/box.py +++ b/visualdialog/box.py @@ -25,7 +25,7 @@ import curses.textpad from typing import List, NewType, Tuple, Union -from utils import (CursesKeyConstants, CursesTextAttributesConstants, +from .utils import (CursesKeyConstants, CursesTextAttributesConstants, TextAttributes) diff --git a/visualdialog/dialog.py b/visualdialog/dialog.py index 1bf0aa7..faea7be 100644 --- a/visualdialog/dialog.py +++ b/visualdialog/dialog.py @@ -27,11 +27,11 @@ import time from typing import Callable, Dict, Generator, List, NewType, Tuple, Union -import box -from utils import CursesTextAttributesConstants, TextAttributes, _make_chunk +from .box import TextBox +from .utils import CursesTextAttributesConstants, TextAttributes, _make_chunk -class DialogBox(box.TextBox): +class DialogBox(TextBox): """This class provides methods and attributs to manage a dialog box. :param end_dialog_indicator: From f0111e84d19688c33f48ce133fdf48d9cb04831c Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Mon, 8 Mar 2021 20:30:53 +0100 Subject: [PATCH 09/52] Change documentation struct, minor corrections --- CONTRIBUTING.md | 43 ++++++++++++++---------------- README.md | 22 ++++++++++----- doc/Makefile | 19 +++++++++++++ doc/make.bat | 35 ++++++++++++++++++++++++ doc/{ => source}/conf.py | 2 +- doc/{ => source}/images/demo.gif | Bin doc/{ => source}/images/demo.png | Bin doc/{ => source}/index.rst | 0 doc/{ => source}/installation.rst | 2 +- doc/{ => source}/requirements.rst | 1 + doc/{ => source}/visualdialog.rst | 4 +-- 11 files changed, 94 insertions(+), 34 deletions(-) create mode 100644 doc/Makefile create mode 100644 doc/make.bat rename doc/{ => source}/conf.py (98%) rename doc/{ => source}/images/demo.gif (100%) rename doc/{ => source}/images/demo.png (100%) rename doc/{ => source}/index.rst (100%) rename doc/{ => source}/installation.rst (93%) rename doc/{ => source}/requirements.rst (84%) rename doc/{ => source}/visualdialog.rst (84%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fc1ad61..15b6f8b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,11 +3,7 @@ ## Setup your development environment -You need: - -* `Python 3.7`: you can install it by following [Python3's documentation](https://www.python.org/downloads/). -* `curses`: available in standard library of `Python` but it doesn't work out-of-the-box on `Windows`. See [this](https://www.devdungeon.com/content/curses-windows-python) explanations to install `curses` on Windows. -* [`Sphinx`](https://www.sphinx-doc.org/en/master/usage/installation.html): to generate documentation. +You need [all requirements](README#requirements). ## Branches @@ -57,25 +53,32 @@ The following snippet describes Visual-dialog's repository structure. ├── doc/ │ Contains the files related to the documentation. │ │ -│ ├── images/ -│ │ Contains images used in documentation. -│ │ -│ ├── conf.py -│ │ Sphinx's configuration file. +│ ├── source/ +│ │ Contains images used in documentation. +│ │ │ +│ │ ├── images/ +│ │ │ Contains images used in documentation. +│ │ │ +│ │ ├── conf.py +│ │ │ Sphinx's configuration file. +│ │ │ +│ │ ├── index.rst +│ │ │ Documentation home page. +│ │ │ +│ │ └── visualdialog.rst +│ │ Documentation of all the public classes and methods in Visual-dialog. │ │ -│ ├── index.rst -│ │ Documentation home page. +│ ├── make.bat +│ │ To generate documentation on Windows. │ │ -│ └── visualdialog.rst -│ Documentation of all the public classes and methods in Visual-dialog. +│ └── Makefile +│ To generate documentation on GNU/Linux or MacOS. │ ├── examples/ -│ Contains several examples of use cases of Visual-dialog. +│ Contains several examples of use cases of Visual-dialog. │ ├── tests/ │ Contains tests for debugging libraries. -│ │ -│ └── test.py │ ├── visualdialog/ │ Source for Visual-dialog's library. @@ -96,12 +99,6 @@ The following snippet describes Visual-dialog's repository structure. ├── CONTRIBUTING.md │ This document. │ -├── make.bat -│ To generate documentation on Windows. -│ -├── Makefile -│ To generate documentation on GNU/Linux or MacOS. -│ ├── MANIFEST.in │ Contains list of non Python file. │ diff --git a/README.md b/README.md index 0b6203c..ee91012 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@

FeaturesInstallation • + RequirementsDocumentationQuick startContributing • @@ -47,11 +48,18 @@ cd Visual-dialog pip install . ``` -### Requirements -* **Python 3.7** or more. +## Requirements + +#### Curses + +**Visual-dialog** works with `curses` Python module. It is available in the standard **Python** library on **UNIX** but it doesn’t work out-of-the-box on **Windows**. + +See [this explanations](https://www.devdungeon.com/content/curses-windows-python) to install `curses` on **Windows** (untested). + +#### Other requirements +* [**Python 3.7**](https://www.python.org/downloads/) or more. * [**Sphinx**](https://www.sphinx-doc.org/en/master/usage/installation.html) to generate the documentation of library. -* [`curses`](https://docs.python.org/3/library/curses.html) Python module (available in standard library of Python on **UNIX**). -* Knowledge of [`curses`](https://docs.python.org/3/library/curses.html) librairie. +* [**sphinx-rtd-theme**](https://pypi.org/project/sphinx-rtd-theme/) used as documentation theme. ## Quick-start @@ -65,17 +73,17 @@ Visualdialog's documentation is automatically generated from the source code by To build it on **GNU/Linux** or **MacOS**: ```sh git clone https://github.com/Tim-ats-d/Visual-dialog.git -cd Visual-dialog +cd Visual-dialog/doc make html ``` Or on **Windows** with **Git Bash**: ```sh git clone https://github.com/Tim-ats-d/Visual-dialog.git -cd Visual-dialog +cd Visual-dialog/doc ./make.bat html ``` -Once generated, the result will be in the `build/html/` folder. +Once generated, the result will be in the `doc/build/html/` folder. ## Contributing diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 0000000..ba501f6 --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,19 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/doc/make.bat b/doc/make.bat new file mode 100644 index 0000000..fa24171 --- /dev/null +++ b/doc/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% + +:end +popd diff --git a/doc/conf.py b/doc/source/conf.py similarity index 98% rename from doc/conf.py rename to doc/source/conf.py index f8549c2..b27f536 100644 --- a/doc/conf.py +++ b/doc/source/conf.py @@ -18,7 +18,7 @@ # import os # import sys # sys.path.insert(0, os.path.abspath('.')) -sys.path.insert(0, os.path.abspath('../visualdialog')) +sys.path.insert(0, os.path.abspath('../../')) # -- Project information ----------------------------------------------------- diff --git a/doc/images/demo.gif b/doc/source/images/demo.gif similarity index 100% rename from doc/images/demo.gif rename to doc/source/images/demo.gif diff --git a/doc/images/demo.png b/doc/source/images/demo.png similarity index 100% rename from doc/images/demo.png rename to doc/source/images/demo.png diff --git a/doc/index.rst b/doc/source/index.rst similarity index 100% rename from doc/index.rst rename to doc/source/index.rst diff --git a/doc/installation.rst b/doc/source/installation.rst similarity index 93% rename from doc/installation.rst rename to doc/source/installation.rst index 2db73f2..72ec1e5 100644 --- a/doc/installation.rst +++ b/doc/source/installation.rst @@ -4,7 +4,7 @@ Installation Using PIP --------- -Install **Visual-dialog** using ``pip`` (The lib is not yet available on **pypy**):: +Install **Visual-dialog** using ``pip`` (The lib is not yet available on **pypi**):: python3 -m pip install git+git://github.com/Tim-ats-d/Visual-dialog diff --git a/doc/requirements.rst b/doc/source/requirements.rst similarity index 84% rename from doc/requirements.rst rename to doc/source/requirements.rst index e616416..5ac463d 100644 --- a/doc/requirements.rst +++ b/doc/source/requirements.rst @@ -16,3 +16,4 @@ Other requirements - **Python 3.7** or more. - `Sphinx `_ to generate the documentation of library. +* `sphinx-rtd-theme `_ used as documentation theme. diff --git a/doc/visualdialog.rst b/doc/source/visualdialog.rst similarity index 84% rename from doc/visualdialog.rst rename to doc/source/visualdialog.rst index 6cb11ae..52eb277 100644 --- a/doc/visualdialog.rst +++ b/doc/source/visualdialog.rst @@ -10,7 +10,7 @@ Text box Two **classes** are defined in these modules but only ``DialogBox`` is destined to be instantiated. -.. automodule:: box +.. automodule:: visualdialog.box :members: -.. automodule:: dialog +.. automodule:: visualdialog.dialog :members: From 47b31da87dacf1e5db8e8975fd493ed520a4b7e2 Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Mon, 8 Mar 2021 20:34:13 +0100 Subject: [PATCH 10/52] Change title place in README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ee91012..e75c956 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,9 @@

- Library to make easier dialog box in terminal. -

Visual-dialog +

+ Library to make easier dialog box in terminal.

This library is still under development. From e67e32e6e54beed2eee4c7ee976c92cad9628194 Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Mon, 8 Mar 2021 23:18:12 +0100 Subject: [PATCH 11/52] Complete rewriting of examples, add possibility to pass one curses text attributes instead of a tuple contains once in some methods, correction of typing hinting. --- README.md | 13 +++---- doc/make.bat | 0 examples/confrontation.py | 70 +++++++++++++++++-------------------- examples/context.py | 49 +++++++------------------- examples/monologue.py | 24 ++++++------- examples/text_attributes.py | 57 ++++++++++++++---------------- setup.py | 1 + tests/test.py | 8 ++--- visualdialog/box.py | 26 ++++++++------ visualdialog/dialog.py | 47 ++++++++++++++++--------- 10 files changed, 143 insertions(+), 152 deletions(-) mode change 100644 => 100755 doc/make.bat diff --git a/README.md b/README.md index e75c956..de9ed52 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +

FeaturesInstallation • @@ -9,14 +10,14 @@

- Visual-dialog -

- Library to make easier dialog box in terminal. + Visual-dialog +
+ Library to make easier dialog box in terminal. +
+ This library is still under development. + API can change.

-This library is still under development. -API can change. - ## Features 📃 Automatic text scrolling. diff --git a/doc/make.bat b/doc/make.bat old mode 100644 new mode 100755 diff --git a/examples/confrontation.py b/examples/confrontation.py index e4019da..01b731b 100644 --- a/examples/confrontation.py +++ b/examples/confrontation.py @@ -1,4 +1,6 @@ # confrontation.py +# +#  A concrete example exploiting the possibilities of Visual-dialog. import curses @@ -15,74 +17,68 @@ def main(stdscr): curses.init_pair(2, curses.COLOR_MAGENTA, curses.COLOR_BLACK) curses.init_pair(3, curses.COLOR_RED, curses.COLOR_BLACK) - textbox_position = (10, 10) # Position 10;10 in terminal. - textbox_dimension = (40, 6) # Length and width (in character). + position = (1, 1) # Position 1;1 in stdscr. + width, length = 6, 35 # Width and length (in character). - phoenix_wright = DialogBox( - *textbox_position, - *textbox_dimension, - title="Phoenix", title_colors_pair_nb=1 # Title and color_pair used to colored title. - ) + max_y, max_x = stdscr.getmaxyx() - april_may = DialogBox( - *textbox_position, - *textbox_dimension, - title="April", title_colors_pair_nb=2 # Title and color_pair used to colored title. - ) + left_x = 2 # Left alignment. + right_x = max_x - length - 4 # Calculation of right alignment. + center_x = max_x // 2 - length // 2 # Calculation of center alignment. + bottom_y = max_y - width - 4 # Calculation of bottom alignment. - miles_edgeworth = DialogBox( - *textbox_position, - *textbox_dimension, - title="Edgeworth", title_colors_pair_nb=3 # Title and color_pair used to colored title. - ) + phoenix_wright = DialogBox(left_x, bottom_y, + length, width, + title="Phoenix", + title_colors_pair_nb=1) # Title and color_pair used to colored title. + + april_may = DialogBox(center_x, bottom_y, + length, width, + title="April", + title_colors_pair_nb=2) + + miles_edgeworth = DialogBox(right_x, bottom_y, + length, width, + title="Edgeworth", + title_colors_pair_nb=3) # Definition of accepted key codes to pass a dialog. # See documentation of curses constants for more informations. - phoenix_wright.confirm_dialog_key = (10, 32) # Key Enter and Space. - april_may.confirm_dialog_key = (10, 32) # Key Enter and Space. - miles_edgeworth.confirm_dialog_key = (10, 32) # Key Enter and Space. + # 10 and 32 correspond to curses constants of the enter and space keys. + phoenix_wright.confirm_dialog_key = (10, 32) + april_may.confirm_dialog_key = (10, 32) + miles_edgeworth.confirm_dialog_key = (10, 32) phoenix_wright.char_by_char(stdscr, "This testimony is a pure invention !", - colors_pair_nb=0, - delay=0.03) - - stdscr.clear() # Clear entierely current window object. + colors_pair_nb=0, # Color pair 0 is initialized by curses. It corresponds to white on black text. + delay=0.03) # Set delay between writting each characters to 0.03 seconde. phoenix_wright.char_by_char(stdscr, "You're lying April May !", colors_pair_nb=0, - flash_screen=True, + flash_screen=True, # A short luminous glow will be displayed before writing the text. delay=0.03, - text_attributes=(curses.A_BOLD,)) - - stdscr.clear() + text_attr=curses.A_BOLD) april_may.char_by_char(stdscr, "Arghh !", colors_pair_nb=0, delay=0.02, - text_attributes=(curses.A_ITALIC,)) - - stdscr.clear() + text_attr=curses.A_ITALIC) miles_edgeworth.char_by_char(stdscr, "OBJECTION !", colors_pair_nb=0, flash_screen=True, delay=0.03, - text_attributes=(curses.A_BOLD,) - ) - - stdscr.clear() + text_attr=curses.A_BOLD) miles_edgeworth.char_by_char(stdscr, "These accusations are irrelevant !", colors_pair_nb=0, delay=0.03) - stdscr.clear() - # Execution of main function. curses.wrapper(main) diff --git a/examples/context.py b/examples/context.py index a6a1454..2541c5a 100644 --- a/examples/context.py +++ b/examples/context.py @@ -1,4 +1,6 @@ # context.py +# +# An example of how to use a text box with a context manager. import curses @@ -9,42 +11,17 @@ def main(stdscr): # Makes the cursor invisible. curses.curs_set(0) - # Definition of several colors pairs. - curses.start_color() - curses.init_pair(1, curses.COLOR_GREEN, curses.COLOR_BLACK) - curses.init_pair(2, curses.COLOR_WHITE, curses.COLOR_BLACK) - - textbox = DialogBox( - 0, 0, # Position 0;0 in terminal. - 40, 6, # Length and width (in character). - title="Tim-ats-d", - title_colors_pair_nb=1, # Title and color_pair used to colored title. - title_text_attributes=(curses.A_UNDERLINE, )) # Attribute to underline the title. - # It is necessary to think of passing a tuple even if it contains only one element - # (see doc for more informations). - - # Definition of accepted key codes to pass a dialog. - # See documentation of the curses constants for more informations. - textbox.confirm_dialog_key = (10, 32) # Key Enter and Space. - - sentences = { - "An important text.": (curses.A_BOLD, ), - "An action performed by a character.": (curses.A_ITALIC, ), - "An underlined text.": (curses.A_UNDERLINE, ), - "A very important text.": (curses.A_BOLD, curses.A_ITALIC), - "Incomprehensible gibberish ": (curses.A_ALTCHARSET, ), - "The colors of the front and the background reversed.": - (curses.A_REVERSE, ), - } - - for text, attributs in sentences.items(): - textbox.char_by_char( - stdscr, - text, - 2, # Display of the reply variable colored with color pair 2. - text_attributes=attributs) # Pass the attributes to the text. - - stdscr.clear() # Clear entierely current window object. + replys = ( + "This text is displayed by an anonymous text box.", + "Its behavior is the same as that of a normal dialog box.", + "The advantage is that the syntax is lighter." + ) + + for reply in replys: + # The keyword "as" allows to capture the returned DialogBox object. + with DialogBox(1, 1, 30, 6) as db: + db.confirm_dialog_key = (10, 32) + db.char_by_char(stdscr, reply) # Execution of main function. diff --git a/examples/monologue.py b/examples/monologue.py index 019b94a..cd16c40 100644 --- a/examples/monologue.py +++ b/examples/monologue.py @@ -1,4 +1,6 @@ # monologue.py +# +#  A simple example of how to use Visual-dialog. import curses @@ -7,7 +9,7 @@ def main(stdscr): replys = ( - "Hello world, how are you today ?", + "Hello world", "Press a key to skip this dialog.", "That is a basic example.", "See doc for more informations." @@ -18,28 +20,26 @@ def main(stdscr): # Definition of several colors pairs. curses.start_color() - curses.init_pair(1, curses.COLOR_RED, curses.COLOR_BLACK) + curses.init_pair(1, curses.COLOR_GREEN, curses.COLOR_BLACK) curses.init_pair(2, curses.COLOR_CYAN, curses.COLOR_BLACK) - curses.init_pair(3, curses.COLOR_GREEN, curses.COLOR_BLACK) textbox = DialogBox( - 20, 15, # Position 20;15 in terminal. - 40, 6, # Length and width of textbox (in character). - title="Tim-ats-d", title_colors_pair_nb=3) - # Title and color pair used to colored title. + 1, 1, # Position 1;1 in stdscr. + 40, 6, # Length and width of textbox (in character). + title="Tim-ats-d", # Title of textbox. + title_colors_pair_nb=1) # Curses color_pair used to colored title. # Definition of accepted key codes to pass a dialog. # See documentation of the curses constants for more informations. - textbox.confirm_dialog_key = (10, 32) # Key Enter and Space. + # 10 and 32 correspond to curses constants of the enter and space keys. + textbox.confirm_dialog_key = (10, 32) # We iterate on each sentence contained in replys. for reply in replys: textbox.char_by_char(stdscr, reply, - 2, # Display of the reply variable colored with color pair 2. - delay=0.04) # Set delay between each characters to 0.04 seconde. - - stdscr.clear() # Clear entierely current window object. + 2, # Display text colored with color pair 2. + delay=0.04) # Set delay between writting each characters to 0.04 seconde. # Execution of main function. diff --git a/examples/text_attributes.py b/examples/text_attributes.py index 06ad57d..3415c43 100644 --- a/examples/text_attributes.py +++ b/examples/text_attributes.py @@ -1,4 +1,6 @@ # text_attributes.py +# +# An example showing the possibilities of text formatting. import curses @@ -11,40 +13,35 @@ def main(stdscr): # Definition of several colors pairs. curses.start_color() - curses.init_pair(1, curses.COLOR_GREEN, curses.COLOR_BLACK) - curses.init_pair(2, curses.COLOR_WHITE, curses.COLOR_BLACK) - - textbox = DialogBox( - 0, 0, # Position 0;0 in terminal. - 40, 6, # Length and width (in character). - title="Tim-ats-d", - title_colors_pair_nb=1, # Title and color_pair used to colored title. - title_text_attributes=(curses.A_UNDERLINE, )) # Attribute to underline the title. - # It is necessary to think of passing a tuple even if it contains only one element - # (see doc for more informations). - - # Definition of accepted key codes to pass a dialog. - # See documentation of the curses constants for more informations. - textbox.confirm_dialog_key = (10, 32) # Key Enter and Space. - + curses.init_pair(1, curses.COLOR_YELLOW, curses.COLOR_BLACK) + curses.init_pair(2, curses.COLOR_MAGENTA, curses.COLOR_BLACK) + + demo_textbox = DialogBox(1, 1, + 40, 6, + title="Demonstration", + title_colors_pair_nb=1, # Display title colored with color pair 1. + title_text_attr=curses.A_UNDERLINE) # curse text attributes that will be applied to the title. + demo_textbox.confirm_dialog_key = (10, 32) + + # A key/value dictionary containing the text and the attributes + # with which it will be displayed. + # You can pass one or more curses text attributes arguments as a tuple. sentences = { - "An important text.": (curses.A_BOLD, ), - "An action performed by a character.": (curses.A_ITALIC, ), - "An underlined text.": (curses.A_UNDERLINE, ), + "An important text.": curses.A_BOLD, + "An action performed by a character.": curses.A_ITALIC, + "An underlined text.": curses.A_UNDERLINE, "A very important text.": (curses.A_BOLD, curses.A_ITALIC), - "Incomprehensible gibberish ": (curses.A_ALTCHARSET, ), - "The colors of the front and the background reversed.": - (curses.A_REVERSE, ), + "Incomprehensible gibberish.": curses.A_ALTCHARSET, + "A blinking text.": curses.A_BLINK, + "The colors of the front and the background reversed.": curses.A_REVERSE, } for text, attributs in sentences.items(): - textbox.char_by_char( - stdscr, - text, - 2, # Display of the reply variable colored with color pair 2. - text_attributes=attributs) # Pass the attributes to the text. - - stdscr.clear() # Clear the screen. + demo_textbox.char_by_char(stdscr, + text, + 2, # Display text colored with color pair 2. + text_attr=attributs) # Pass the attributes to the text. -curses.wrapper(main) # Execution of the function. +# Execution of main function. +curses.wrapper(main) diff --git a/setup.py b/setup.py index 5dc6d84..7b36793 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,6 @@ from setuptools import setup, find_packages + setup( name="visualdialog", version=0.6, diff --git a/tests/test.py b/tests/test.py index d151f5d..03586cb 100644 --- a/tests/test.py +++ b/tests/test.py @@ -4,9 +4,7 @@ import curses - -__import__("sys").path.append("../visualdialog") -from dialog import DialogBox +from visualdialog import DialogBox def main(stdscr): @@ -55,6 +53,8 @@ def func(text: str): # ~ words_attr=special_words) - if __name__ == "__main__": curses.wrapper(main) + + + diff --git a/visualdialog/box.py b/visualdialog/box.py index 82c3514..240ff65 100644 --- a/visualdialog/box.py +++ b/visualdialog/box.py @@ -69,14 +69,13 @@ class TextBox: :param title_colors_pair_nb: Number of the curses color pair that will be used to color the title. Zero corresponding to the pair of white color on black - background initialized by ``curses``). - This defaults to ``0``. + background initialized by ``curses``). This defaults to ``0``. :type title_colors_pair_nb: Optional[int] - :param title_text_attributes: - Dialog box title text attributes. This defaults to a tuple - contains ``curses.A_BOLD``. - :type title_text_attrubutes: Optional[Union[Tuple[CursesTextAttributesConstants],List[CursesTextAttributesConstants]]] + :param title_text_attr: + Dialog box title text attributes. This defaults to + ``curses.A_BOLD``. + :type title_text_attr: Optional[Union[CursesTextAttributesConstants,Tuple[CursesTextAttributesConstants],List[CursesTextAttributesConstants]]] :param downtime_chars: List of characters that will trigger a ``downtime_chars_delay`` @@ -104,9 +103,9 @@ def __init__( width: int, title: str = "", title_colors_pair_nb: CursesTextAttributesConstants = 0, - title_text_attributes: Union[Tuple[CursesTextAttributesConstants], - List[CursesTextAttributesConstants]] = ( - curses.A_BOLD, ), + title_text_attr: Union[CursesTextAttributesConstants, + Tuple[CursesTextAttributesConstants], + List[CursesTextAttributesConstants]] = curses.A_BOLD, downtime_chars: Union[Tuple[str], List[str]] = (",", ".", ":", ";", "!", "?"), downtime_chars_delay: Union[int, float] = .6): @@ -124,7 +123,12 @@ def __init__( self.title = title if self.title: self.title_colors = curses.color_pair(title_colors_pair_nb) - self.title_text_attributes = title_text_attributes + + # Test if only one argument is passed instead of a tuple + if isinstance(title_text_attr, int): + self.title_text_attr = (title_text_attr, ) + else: + self.title_text_attr = title_text_attr self.end_dialog_indicator_pos_x = pos_x + length - 2 self.end_dialog_indicator_pos_y = pos_y + width + 1 @@ -163,7 +167,7 @@ def framing_box(self, stdscr): # Displays the title and the title box. if self.title: - attr = (self.title_colors, *self.title_text_attributes) + attr = (self.title_colors, *self.title_text_attr) curses.textpad.rectangle(stdscr, self.pos_y, self.pos_x + 1, self.pos_y + title_width, diff --git a/visualdialog/dialog.py b/visualdialog/dialog.py index faea7be..679476b 100644 --- a/visualdialog/dialog.py +++ b/visualdialog/dialog.py @@ -25,7 +25,8 @@ import random import textwrap import time -from typing import Callable, Dict, Generator, List, NewType, Tuple, Union +from typing import (Callable, Dict, Generator, List, NewType, Optional, + Tuple, Union) from .box import TextBox from .utils import CursesTextAttributesConstants, TextAttributes, _make_chunk @@ -58,13 +59,15 @@ def __init__( width: int, title: str = "", title_colors_pair_nb: CursesTextAttributesConstants = 0, - title_text_attributes: Tuple[CursesTextAttributesConstants] = ( - curses.A_BOLD, ), - downtime_chars: Tuple[str] = (",", ".", ":", ";", "!", "?"), - downtime_chars_delay: Union[int, float] = 0.6, + title_text_attr: Union[CursesTextAttributesConstants, + Tuple[CursesTextAttributesConstants], + List[CursesTextAttributesConstants]] = curses.A_BOLD, + downtime_chars: Union[Tuple[str], + List[str]] = (",", ".", ":", ";", "!", "?"), + downtime_chars_delay: Union[int, float] = .6, end_dialog_indicator: str = "►"): super().__init__(pos_x, pos_y, length, width, title, - title_colors_pair_nb, title_text_attributes, + title_colors_pair_nb, title_text_attr, downtime_chars, downtime_chars_delay) self.end_dialog_indicator_char = end_dialog_indicator @@ -78,7 +81,7 @@ def __exit__(self, type, value, traceback): def _display_end_dialog_indicator( self, stdscr, - text_attributes: Tuple[CursesTextAttributesConstants] = ( + text_attr: Optional[Union[Tuple[CursesTextAttributesConstants],List[CursesTextAttributesConstants]]] = ( curses.A_BOLD, curses.A_BLINK)): """Displays an end of dialog indicator in the lower right corner of textbox. @@ -86,13 +89,13 @@ def _display_end_dialog_indicator( :param stdscr: ``curses`` window object on which the method will have effect. - :param text_attributes: Text attributes of + :param text_attr: Text attributes of ``end_dialog_indicator`` method. This defaults to ``(curses.A_BOLD, curses.A_BLINK)``. - :type text_attributes: Optional[Tuple[CursesTextAttributesConstants]] + :type text_attr: Optional[Union[Tuple[CursesTextAttributesConstants],List[CursesTextAttributesConstants]]] """ if self.end_dialog_indicator_char: - with TextAttributes(stdscr, *text_attributes): + with TextAttributes(stdscr, *text_attr): stdscr.addch(self.end_dialog_indicator_pos_y, self.end_dialog_indicator_pos_x, self.end_dialog_indicator_char) @@ -102,7 +105,8 @@ def char_by_char( stdscr, text: str, colors_pair_nb: int = 0, - text_attr: Union[Tuple[CursesTextAttributesConstants], + text_attr: Union[CursesTextAttributesConstants, + Tuple[CursesTextAttributesConstants], List[CursesTextAttributesConstants]] = (), words_attr: Union[Dict[Tuple[str], CursesTextAttributesConstants], Dict[Tuple[str], @@ -132,7 +136,7 @@ def char_by_char( :param text_attr: Dialog box curses text attributes. This defaults an empty tuple. - :type text_attr: Optional[Union[Tuple[CursesTextAttributesConstants],List[CursesTextAttributesConstants]]] + :type text_attr: Optional[Union[CursesTextAttributesConstants,Tuple[CursesTextAttributesConstants],List[CursesTextAttributesConstants]]] :param words_attr: TODO :type words_atttr: TODO @@ -206,10 +210,13 @@ def char_by_char( if word in words_attr.keys(): attr = words_attr[word] - # Test if only one argument is passed. + # Test if only one argument is passed instead of a tuple. if isinstance(attr, int): attr = (attr, ) else: + if isinstance(text_attr, int): + text_attr = (text_attr, ) + attr = (curses.color_pair(colors_pair_nb), *text_attr) with TextAttributes(stdscr, *attr): @@ -243,7 +250,8 @@ def word_by_word( text: str, colors_pair_nb: int, cut_char: str = " ", - text_attr: Union[Tuple[CursesTextAttributesConstants], + text_attr: Union[CursesTextAttributesConstants, + Tuple[CursesTextAttributesConstants], List[CursesTextAttributesConstants]] = (), words_attr: Union[Dict[Tuple[str], CursesTextAttributesConstants], Dict[Tuple[str], @@ -273,7 +281,7 @@ def word_by_word( :param text_attr: Dialog box curses text attributes. This defaults an empty tuple. - :type text_attr: Optional[Union[Tuple[CursesTextAttributesConstants],List[CursesTextAttributesConstants]]] + :type text_attr: Optional[Union[CursesTextAttributesConstants,Tuple[CursesTextAttributesConstants],List[CursesTextAttributesConstants]]] :param words_attr: TODO :type words_atttr: TODO @@ -350,9 +358,16 @@ def word_by_word( for y, line in enumerate(paragraph): offsetting_x = 0 for word in line.split(cut_char): - if word in words_attr: + if word in words_attr.keys(): attr = words_attr[word] + + # Test if only one argument is passed instead of a tuple. + if isinstance(text_attr, int): + text_attr = (text_attr, ) else: + if isinstance(text_attr, int): + text_attr = (text_attr, ) + attr = (curses.color_pair(colors_pair_nb), *text_attr) with TextAttributes(stdscr, *attr): From d0ece054842a4cecd6bde95afc9c4eceaf8538c6 Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Mon, 8 Mar 2021 23:30:55 +0100 Subject: [PATCH 12/52] Fix title level in README.md, correction of dev release installation instructions, remove Makefile and make.bat. --- CONTRIBUTING.md | 14 +++----------- Makefile | 19 ------------------- README.md | 6 +++--- make.bat | 35 ----------------------------------- tests/test.py | 3 --- 5 files changed, 6 insertions(+), 71 deletions(-) delete mode 100644 Makefile delete mode 100755 make.bat diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 15b6f8b..a1aa582 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,19 +25,11 @@ If you add a feature that changes the API, notify it explicitly. ## Download -Download the `dev` branch of the project: +Download the `dev` branch of the project and install dev release: ```sh git clone -b dev https://github.com/Tim-ats-d/Visual-dialog.git -``` - -Install Visual-dialog using `pip` (The lib is not yet available on **pypi**): -```sh -python3 -m pip install git+git://github.com/Tim-ats-d/Visual-dialog -``` -or update lib to the latest version: - -```sh -python3 -m pip install git+git://github.com/Tim-ats-d/Visual-dialog --upgrade +cd Visual-dialog +pip install . ``` The list of versions and their changelogs can be found [here](https://github.com/Tim-ats-d/Visual-dialog/releases/). diff --git a/Makefile b/Makefile deleted file mode 100644 index bdf21ac..0000000 --- a/Makefile +++ /dev/null @@ -1,19 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -SOURCEDIR = doc -BUILDDIR = build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/README.md b/README.md index de9ed52..8d8e8eb 100644 --- a/README.md +++ b/README.md @@ -51,13 +51,13 @@ pip install . ## Requirements -#### Curses +### Curses **Visual-dialog** works with `curses` Python module. It is available in the standard **Python** library on **UNIX** but it doesn’t work out-of-the-box on **Windows**. See [this explanations](https://www.devdungeon.com/content/curses-windows-python) to install `curses` on **Windows** (untested). -#### Other requirements +### Other requirements * [**Python 3.7**](https://www.python.org/downloads/) or more. * [**Sphinx**](https://www.sphinx-doc.org/en/master/usage/installation.html) to generate the documentation of library. * [**sphinx-rtd-theme**](https://pypi.org/project/sphinx-rtd-theme/) used as documentation theme. @@ -95,7 +95,7 @@ You can also help by reporting bugs. ## License -Distributed under the **GPL-2.0 License** . See [License](LICENSE) for more information. +Distributed under the **GPL-2.0 License** . See [license](LICENSE) for more information. ## Acknowledgements diff --git a/make.bat b/make.bat deleted file mode 100755 index 788bb23..0000000 --- a/make.bat +++ /dev/null @@ -1,35 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=doc -set BUILDDIR=build - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% - -:end -popd diff --git a/tests/test.py b/tests/test.py index 03586cb..1f53470 100644 --- a/tests/test.py +++ b/tests/test.py @@ -55,6 +55,3 @@ def func(text: str): if __name__ == "__main__": curses.wrapper(main) - - - From ea6ab5fc482c880e8ae2f394a5d355b86b5eb05b Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Wed, 10 Mar 2021 22:39:39 +0100 Subject: [PATCH 13/52] Fix typo in README. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8d8e8eb..84e748f 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ 🔖 Text coloring and formatting. -⚙️ Hackable and configurable . +⚙️ Hackable and configurable. ## Installation From a0cdec912542a30d32565526e4e2b7cde601e39b Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Wed, 10 Mar 2021 23:44:46 +0100 Subject: [PATCH 14/52] Update of supported Python versions in setup.p.py, fixed a bug that displayed the textbox two characters below when the title was empty. --- setup.py | 8 -------- tests/test.py | 15 ++++++--------- visualdialog/box.py | 27 +++++++++++++++++---------- visualdialog/dialog.py | 7 +++++++ 4 files changed, 30 insertions(+), 27 deletions(-) diff --git a/setup.py b/setup.py index 7b36793..6af0460 100644 --- a/setup.py +++ b/setup.py @@ -17,14 +17,6 @@ "License :: OSI Approved :: GNU General Public License v2 (GPLv2)", "Natural Language :: English", "Natural Language :: French", "Operating System :: POSIX :: Linux", - "Programming Language :: Python :: 3.0", - "Programming Language :: Python :: 3.1", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.2", - "Programming Language :: Python :: 3.3", - "Programming Language :: Python :: 3.4", - "Programming Language :: Python :: 3.5", - "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", diff --git a/tests/test.py b/tests/test.py index 1f53470..52a969f 100644 --- a/tests/test.py +++ b/tests/test.py @@ -5,6 +5,7 @@ import curses from visualdialog import DialogBox +import visualdialog def main(stdscr): @@ -22,9 +23,9 @@ def main(stdscr): curses.init_pair(2, curses.COLOR_CYAN, curses.COLOR_BLACK) curses.init_pair(3, curses.COLOR_GREEN, curses.COLOR_BLACK) - textbox = DialogBox(20, 15, + textbox = DialogBox(0, 0, 40, 6, - title="Tim-ats-d", + # ~ title="Tim-ats-d", title_colors_pair_nb=3, end_dialog_indicator="o") @@ -37,19 +38,15 @@ def main(stdscr): } def func(text: str): - stdscr.addstr(0, 0, text) + stdscr.addstr(0, 0, str(visualdialog.__version__)) for reply in text: textbox.char_by_char(stdscr, reply, cargs=(reply, ), - callback=func, - text_attr=(curses.A_ITALIC, curses.A_BOLD), - words_attr=special_words) - # ~ textbox.word_by_word(stdscr, - # ~ reply, - # ~ 2, + callback=func) + # ~ text_attr=(curses.A_ITALIC, curses.A_BOLD), # ~ words_attr=special_words) diff --git a/visualdialog/box.py b/visualdialog/box.py index 240ff65..26361c7 100644 --- a/visualdialog/box.py +++ b/visualdialog/box.py @@ -93,7 +93,8 @@ class TextBox: confirm_dialog_key: Union[Tuple[CursesKeyConstants], List[CursesKeyConstants]] = () #: List of accepted key codes to raise PanicError. ``curses`` constants are supported. This defaults to an empty tuple. - panic_key: Union[Tuple[CursesKeyConstants], List[CursesKeyConstants]] = () + panic_key: Union[Tuple[CursesKeyConstants], + List[CursesKeyConstants]] = () def __init__( self, @@ -112,16 +113,18 @@ def __init__( self.pos_x, self.pos_y = pos_x, pos_y self.length, self.width = length, width + self.title_offsetting_y = 2 if title else 0 + # Compensation for the left border of the dialog box. self.text_pos_x = pos_x + 2 # Compensation for the upper border of the dialog box. - self.text_pos_y = pos_y + 3 + self.text_pos_y = pos_y + self.title_offsetting_y + 1 self.nb_char_max_line = length - 4 self.nb_lines_max = width - 2 self.title = title - if self.title: + if title: self.title_colors = curses.color_pair(title_colors_pair_nb) # Test if only one argument is passed instead of a tuple @@ -130,9 +133,6 @@ def __init__( else: self.title_text_attr = title_text_attr - self.end_dialog_indicator_pos_x = pos_x + length - 2 - self.end_dialog_indicator_pos_y = pos_y + width + 1 - self.downtime_chars = downtime_chars self.downtime_chars_delay = downtime_chars_delay @@ -169,16 +169,22 @@ def framing_box(self, stdscr): if self.title: attr = (self.title_colors, *self.title_text_attr) - curses.textpad.rectangle(stdscr, self.pos_y, self.pos_x + 1, + curses.textpad.rectangle(stdscr, + self.pos_y, + self.pos_x + 1, self.pos_y + title_width, self.pos_x + title_length) with TextAttributes(stdscr, *attr): - stdscr.addstr(self.pos_y + 1, self.pos_x + 3, self.title) + stdscr.addstr(self.pos_y + 1, + self.pos_x + 3, + self.title) # Displays the borders of the dialog box. - curses.textpad.rectangle(stdscr, self.pos_y + 2, self.pos_x, - self.pos_y + 2 + self.width, + curses.textpad.rectangle(stdscr, + self.pos_y + self.title_offsetting_y, + self.pos_x, + self.pos_y + self.title_offsetting_y + self.width, self.pos_x + self.length) def getkey(self, stdscr): @@ -196,6 +202,7 @@ def getkey(self, stdscr): `this curses documentation `_. - This method uses ``window.getch`` method from ``curses`` module. Please refer to `curses documentation `_ for more informations. """ + curses.raw() while 1: key = stdscr.getch() diff --git a/visualdialog/dialog.py b/visualdialog/dialog.py index 679476b..663ab26 100644 --- a/visualdialog/dialog.py +++ b/visualdialog/dialog.py @@ -72,6 +72,13 @@ def __init__( self.end_dialog_indicator_char = end_dialog_indicator + self.end_dialog_indicator_pos_x = pos_x + length - 2 + + if title: + self.end_dialog_indicator_pos_y = pos_y + width + 1 + else: + self.end_dialog_indicator_pos_y = pos_y + width - 1 + def __enter__(self): return self From 5c238615bd4643a1f7f34ca9d164592466548084 Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Wed, 10 Mar 2021 23:47:21 +0100 Subject: [PATCH 15/52] Remove activation of raw mode in TextBox.getkey. --- visualdialog/box.py | 1 - 1 file changed, 1 deletion(-) diff --git a/visualdialog/box.py b/visualdialog/box.py index 26361c7..6853d8f 100644 --- a/visualdialog/box.py +++ b/visualdialog/box.py @@ -202,7 +202,6 @@ def getkey(self, stdscr): `this curses documentation `_. - This method uses ``window.getch`` method from ``curses`` module. Please refer to `curses documentation `_ for more informations. """ - curses.raw() while 1: key = stdscr.getch() From 52dec7080eac2a0747cb082b9b1c9bc9ae779010 Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Thu, 11 Mar 2021 23:32:37 +0100 Subject: [PATCH 16/52] Improvement of examples, develop PanicError, change in README. --- README.md | 4 +--- examples/confrontation.py | 13 ++++++++----- examples/context.py | 7 ++++++- examples/monologue.py | 9 ++++++--- examples/text_attributes.py | 7 ++++++- tests/test.py | 5 ++--- visualdialog/box.py | 19 +++++++++++++++---- 7 files changed, 44 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 84e748f..d8b5d23 100644 --- a/README.md +++ b/README.md @@ -64,9 +64,7 @@ See [this explanations](https://www.devdungeon.com/content/curses-windows-python ## Quick-start -Coming soon. - -Read these [examples](examples/). +Various examples showing the capabilities of **Visual-dialog** can be found in [examples](examples/). ## Documentation diff --git a/examples/confrontation.py b/examples/confrontation.py index 01b731b..63331df 100644 --- a/examples/confrontation.py +++ b/examples/confrontation.py @@ -7,6 +7,11 @@ from visualdialog import DialogBox +# Definition of curses key constants. +# 10 and 32 correspond to enter and space keys. +ENTER_KEY = 10 +SPACE_KEY = 32 + def main(stdscr): # Makes the cursor invisible. curses.curs_set(0) @@ -43,11 +48,9 @@ def main(stdscr): title_colors_pair_nb=3) # Definition of accepted key codes to pass a dialog. - # See documentation of curses constants for more informations. - # 10 and 32 correspond to curses constants of the enter and space keys. - phoenix_wright.confirm_dialog_key = (10, 32) - april_may.confirm_dialog_key = (10, 32) - miles_edgeworth.confirm_dialog_key = (10, 32) + phoenix_wright.confirm_dialog_key = (ENTER_KEY, SPACE_KEY) + april_may.confirm_dialog_key = (ENTER_KEY, SPACE_KEY) + miles_edgeworth.confirm_dialog_key = (ENTER_KEY, SPACE_KEY) phoenix_wright.char_by_char(stdscr, "This testimony is a pure invention !", diff --git a/examples/context.py b/examples/context.py index 2541c5a..9796583 100644 --- a/examples/context.py +++ b/examples/context.py @@ -7,6 +7,11 @@ from visualdialog import DialogBox +# Definition of curses key constants. +# 10 and 32 correspond to enter and space keys. +ENTER_KEY = 10 +SPACE_KEY = 32 + def main(stdscr): # Makes the cursor invisible. curses.curs_set(0) @@ -20,7 +25,7 @@ def main(stdscr): for reply in replys: # The keyword "as" allows to capture the returned DialogBox object. with DialogBox(1, 1, 30, 6) as db: - db.confirm_dialog_key = (10, 32) + db.confirm_dialog_key = (ENTER_KEY, SPACE_KEY) db.char_by_char(stdscr, reply) diff --git a/examples/monologue.py b/examples/monologue.py index cd16c40..411d4c8 100644 --- a/examples/monologue.py +++ b/examples/monologue.py @@ -7,6 +7,11 @@ from visualdialog import DialogBox +# Definition of curses key constants. +# 10 and 32 correspond to enter and space keys. +ENTER_KEY = 10 +SPACE_KEY = 32 + def main(stdscr): replys = ( "Hello world", @@ -30,9 +35,7 @@ def main(stdscr): title_colors_pair_nb=1) # Curses color_pair used to colored title. # Definition of accepted key codes to pass a dialog. - # See documentation of the curses constants for more informations. - # 10 and 32 correspond to curses constants of the enter and space keys. - textbox.confirm_dialog_key = (10, 32) + textbox.confirm_dialog_key = (ENTER_KEY, SPACE_KEY) # We iterate on each sentence contained in replys. for reply in replys: diff --git a/examples/text_attributes.py b/examples/text_attributes.py index 3415c43..2bb9087 100644 --- a/examples/text_attributes.py +++ b/examples/text_attributes.py @@ -7,6 +7,11 @@ from visualdialog import DialogBox +# Definition of curses key constants. +# 10 and 32 correspond to enter and space keys. +ENTER_KEY = 10 +SPACE_KEY = 32 + def main(stdscr): # Makes the cursor invisible. curses.curs_set(0) @@ -21,7 +26,7 @@ def main(stdscr): title="Demonstration", title_colors_pair_nb=1, # Display title colored with color pair 1. title_text_attr=curses.A_UNDERLINE) # curse text attributes that will be applied to the title. - demo_textbox.confirm_dialog_key = (10, 32) + demo_textbox.confirm_dialog_key = (ENTER_KEY, SPACE_KEY) # A key/value dictionary containing the text and the attributes # with which it will be displayed. diff --git a/tests/test.py b/tests/test.py index 52a969f..bd52d20 100644 --- a/tests/test.py +++ b/tests/test.py @@ -29,8 +29,8 @@ def main(stdscr): title_colors_pair_nb=3, end_dialog_indicator="o") - textbox.confirm_dialog_key = (32, 10) - # ~ textbox.panic_key = (32, ) + textbox.confirm_dialog_key = (32, ) + textbox.panic_key = (10, ) special_words = { "test": (curses.A_BOLD, curses.A_ITALIC), @@ -40,7 +40,6 @@ def main(stdscr): def func(text: str): stdscr.addstr(0, 0, str(visualdialog.__version__)) - for reply in text: textbox.char_by_char(stdscr, reply, diff --git a/visualdialog/box.py b/visualdialog/box.py index 6853d8f..e56f45e 100644 --- a/visualdialog/box.py +++ b/visualdialog/box.py @@ -30,8 +30,12 @@ class PanicError(Exception): + + def __init__(self, key: int): + self.key = key + def __str__(self): - return "text box was aborted" + return f"text box was aborted by pressing the {self.key} key" class TextBox: @@ -199,8 +203,12 @@ def getkey(self, stdscr): .. NOTE:: - To see the list of key constants please refer to - `this curses documentation `_. - - This method uses ``window.getch`` method from ``curses`` module. Please refer to `curses documentation `_ for more informations. + `this curses documentation + `_. + - This method uses ``window.getch`` method from ``curses`` + module. Please refer to `curses documentation + `_ + for more informations. """ while 1: key = stdscr.getch() @@ -208,7 +216,10 @@ def getkey(self, stdscr): if key in self.confirm_dialog_key: break elif key in self.panic_key: - raise PanicError + raise PanicError(key) else: # Ignore incorrect keys. ... + + + From cd164d58b433d51a7d001ce6b3dd3675bc304ccf Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Thu, 11 Mar 2021 23:42:55 +0100 Subject: [PATCH 17/52] char_by_char and word_by_word methods of DialogBox now call their own method and not the behavior of the parent TextBox class, improve examples. --- examples/confrontation.py | 2 +- examples/context.py | 2 +- examples/monologue.py | 2 +- examples/text_attributes.py | 2 +- visualdialog/dialog.py | 10 +++++----- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/confrontation.py b/examples/confrontation.py index 63331df..1856895 100644 --- a/examples/confrontation.py +++ b/examples/confrontation.py @@ -14,7 +14,7 @@ def main(stdscr): # Makes the cursor invisible. - curses.curs_set(0) + curses.curs_set(False) # Definition of several colors pairs. curses.start_color() diff --git a/examples/context.py b/examples/context.py index 9796583..4ed28f3 100644 --- a/examples/context.py +++ b/examples/context.py @@ -14,7 +14,7 @@ def main(stdscr): # Makes the cursor invisible. - curses.curs_set(0) + curses.curs_set(False) replys = ( "This text is displayed by an anonymous text box.", diff --git a/examples/monologue.py b/examples/monologue.py index 411d4c8..d2f8422 100644 --- a/examples/monologue.py +++ b/examples/monologue.py @@ -21,7 +21,7 @@ def main(stdscr): ) # Makes the cursor invisible. - curses.curs_set(0) + curses.curs_set(False) # Definition of several colors pairs. curses.start_color() diff --git a/examples/text_attributes.py b/examples/text_attributes.py index 2bb9087..0431472 100644 --- a/examples/text_attributes.py +++ b/examples/text_attributes.py @@ -14,7 +14,7 @@ def main(stdscr): # Makes the cursor invisible. - curses.curs_set(0) + curses.curs_set(False) # Definition of several colors pairs. curses.start_color() diff --git a/visualdialog/dialog.py b/visualdialog/dialog.py index 663ab26..bd14063 100644 --- a/visualdialog/dialog.py +++ b/visualdialog/dialog.py @@ -209,7 +209,7 @@ def char_by_char( for paragraph in wrapped_text: stdscr.clear() - super().framing_box(stdscr) + self.framing_box(stdscr) for y, line in enumerate(paragraph): offsetting_x = 0 @@ -249,7 +249,7 @@ def char_by_char( offsetting_x += len(word) + 1 self._display_end_dialog_indicator(stdscr) - super().getkey(stdscr) + self.getkey(stdscr) def word_by_word( self, @@ -349,7 +349,7 @@ def word_by_word( paragraph by ``window.clear()`` method of ``curses`` module. """ - super().framing_box(stdscr) + self.framing_box(stdscr) if flash_screen: curses.flash() @@ -361,7 +361,7 @@ def word_by_word( for paragraph in wrapped_text: stdscr.clear() - super().framing_box(stdscr) + self.framing_box(stdscr) for y, line in enumerate(paragraph): offsetting_x = 0 for word in line.split(cut_char): @@ -390,4 +390,4 @@ def word_by_word( callback(*cargs) self._display_end_dialog_indicator(stdscr) - super().getkey(stdscr) + self.getkey(stdscr) From a81f2f9b2b09ed76490706cfe10ae365877b76ee Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Sat, 13 Mar 2021 21:31:30 +0100 Subject: [PATCH 18/52] Add utils.rst in doc, document PanicError, re-formatting documentation in rst in utils.py. --- doc/source/api.rst | 8 +++++++ doc/source/index.rst | 6 +++--- doc/source/installation.rst | 4 ++-- doc/source/requirements.rst | 2 +- doc/source/utils.rst | 20 ++++++++++++++++++ doc/source/visualdialog.rst | 11 ++++------ tests/test.py | 3 +++ visualdialog/__init__.py | 1 + visualdialog/box.py | 16 ++++++++------ visualdialog/dialog.py | 4 +++- visualdialog/utils.py | 42 +++++++++++++++++++++++-------------- 11 files changed, 81 insertions(+), 36 deletions(-) create mode 100644 doc/source/api.rst create mode 100644 doc/source/utils.rst diff --git a/doc/source/api.rst b/doc/source/api.rst new file mode 100644 index 0000000..403fabe --- /dev/null +++ b/doc/source/api.rst @@ -0,0 +1,8 @@ +API documentation +================= + +.. toctree:: + :maxdepth: 2 + + visualdialog.rst + utils.rst diff --git a/doc/source/index.rst b/doc/source/index.rst index b62eea0..437577d 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -42,9 +42,9 @@ Getting help :maxdepth: 3 :caption: Contents: - ./requirements.rst - ./installation.rst - ./visualdialog + requirements.rst + installation.rst + api.rst Changelog --------- diff --git a/doc/source/installation.rst b/doc/source/installation.rst index 72ec1e5..84818fb 100644 --- a/doc/source/installation.rst +++ b/doc/source/installation.rst @@ -6,8 +6,8 @@ Using PIP Install **Visual-dialog** using ``pip`` (The lib is not yet available on **pypi**):: - python3 -m pip install git+git://github.com/Tim-ats-d/Visual-dialog + python3 -m pip install git+git://github.com/Tim-ats-d/Visual-dialog or update library to the latest version:: - python3 -m pip install git+git://github.com/Tim-ats-d/Visual-dialog --upgrade + python3 -m pip install git+git://github.com/Tim-ats-d/Visual-dialog --upgrade diff --git a/doc/source/requirements.rst b/doc/source/requirements.rst index 5ac463d..ef84bc4 100644 --- a/doc/source/requirements.rst +++ b/doc/source/requirements.rst @@ -16,4 +16,4 @@ Other requirements - **Python 3.7** or more. - `Sphinx `_ to generate the documentation of library. -* `sphinx-rtd-theme `_ used as documentation theme. +- `sphinx-rtd-theme `_ used as documentation theme. diff --git a/doc/source/utils.rst b/doc/source/utils.rst new file mode 100644 index 0000000..b7698bc --- /dev/null +++ b/doc/source/utils.rst @@ -0,0 +1,20 @@ +Utils +===== + +.. NOTE:: + A sub-module of **Visual-dialog** (``visualdialog.utils``) contains + functions and classes used by the private API. The context manager + ``TextAttributes`` is used by the library to manage in a more + pythonic way the textual ``curses`` attributes curses but you can + also use it in your programs. + + ``visualdialog.utils`` is automatically imported when importing + visualdialog so you can use its module functions and classes just like this:: + + import visualdialog + + visualdialog.function(args) + + +.. autoclass:: visualdialog.utils.TextAttributes + :members: diff --git a/doc/source/visualdialog.rst b/doc/source/visualdialog.rst index 52eb277..4352c3c 100644 --- a/doc/source/visualdialog.rst +++ b/doc/source/visualdialog.rst @@ -1,14 +1,11 @@ -API documentation -================= - Text box --------- +======== .. IMPORTANT:: - **Visual-dialog** contains two **modules**: ``visualdialog.box`` and ``visualdialog.dialog``. - These two **modules** are both imported when you import ``visualdialog``. + **Visual-dialog** contains two **modules**: ``visualdialog.box`` and ``visualdialog.dialog``. + These two **modules** are both imported when you import ``visualdialog``. - Two **classes** are defined in these modules but only ``DialogBox`` is destined to be instantiated. + Two **classes** are defined in these modules but only ``DialogBox`` is destined to be instantiated. .. automodule:: visualdialog.box :members: diff --git a/tests/test.py b/tests/test.py index bd52d20..9fd5167 100644 --- a/tests/test.py +++ b/tests/test.py @@ -48,6 +48,9 @@ def func(text: str): # ~ text_attr=(curses.A_ITALIC, curses.A_BOLD), # ~ words_attr=special_words) + with visualdialog.TextAttributes(stdscr, curses.A_BOLD, curses.A_ITALIC): + ... + if __name__ == "__main__": curses.wrapper(main) diff --git a/visualdialog/__init__.py b/visualdialog/__init__.py index 5d5b787..2b528c0 100644 --- a/visualdialog/__init__.py +++ b/visualdialog/__init__.py @@ -6,3 +6,4 @@ __author__ = "Arnouts Timéo" from .dialog import DialogBox +from .utils import TextAttributes diff --git a/visualdialog/box.py b/visualdialog/box.py index e56f45e..637ed11 100644 --- a/visualdialog/box.py +++ b/visualdialog/box.py @@ -25,13 +25,20 @@ import curses.textpad from typing import List, NewType, Tuple, Union -from .utils import (CursesKeyConstants, CursesTextAttributesConstants, - TextAttributes) +from .utils import (CursesKeyConstants, + CursesTextAttributesConstants, + TextAttributes) class PanicError(Exception): + """Exception thrown when a key contained in ``TextBox.panic_key`` is + pressed. - def __init__(self, key: int): + :param key: Key pressed that caused the exception to be thrown. + :type key: CursesKeyConstants + """ + def __init__(self, + key: CursesKeyConstants): self.key = key def __str__(self): @@ -220,6 +227,3 @@ def getkey(self, stdscr): else: # Ignore incorrect keys. ... - - - diff --git a/visualdialog/dialog.py b/visualdialog/dialog.py index bd14063..ff0e576 100644 --- a/visualdialog/dialog.py +++ b/visualdialog/dialog.py @@ -29,7 +29,9 @@ Tuple, Union) from .box import TextBox -from .utils import CursesTextAttributesConstants, TextAttributes, _make_chunk +from .utils import (CursesTextAttributesConstants, + TextAttributes, + _make_chunk) class DialogBox(TextBox): diff --git a/visualdialog/utils.py b/visualdialog/utils.py index cb13043..aaa7238 100644 --- a/visualdialog/utils.py +++ b/visualdialog/utils.py @@ -21,6 +21,7 @@ from typing import List, NewType, Tuple, Union + # curses text attribute constants are integers. # See https://docs.python.org/3/library/curses.html?#constants CursesTextAttributesConstants = NewType("CursesTextAttributesConstants", int) @@ -29,10 +30,14 @@ # See https://docs.python.org/3/library/curses.html?#constants CursesKeyConstants = NewType("CursesKeyConstants", int) +def _make_chunk(iterable: Union[Tuple, List], + chunk_length: int) -> Tuple: + """Returns a tuple that contains the given iterator separated + into chunk_length bundles. -def _make_chunk(iterable: Union[Tuple, List], chunk_length: int) -> Tuple: - """Returns a generator that contains the given iterator separated into - chunk_length bundles.""" + :returns: Iterator separated into chunk_length bundles. + :rtype: Tuple + """ return (iterable[chunk:chunk + chunk_length] for chunk in range(0, len(iterable), chunk_length)) @@ -40,23 +45,28 @@ def _make_chunk(iterable: Union[Tuple, List], chunk_length: int) -> Tuple: class TextAttributes: """A context manager to manage curses text attributs. - Attributes - ---------- - win - `curses` window object for which the attributes will be managed. - attributes - List of attributes to activate and desactivate. + :param win: `curses` window object for which the attributes will be + managed. + + :param attributes: List of attributes to activate and desactivate. + :type attributes: Union[Tuple[CursesTextAttributesConstants],List[CursesTextAttributesConstants]] """ - def __init__(self, stdscr, *attributes: CursesTextAttributesConstants): + def __init__(self, + stdscr, + *attributes: CursesTextAttributesConstants): self.win = stdscr self.attributes = attributes def __enter__(self): - """Activates one by one the attributes contained in self.attributes.""" - for attribute in self.attributes: - self.win.attron(attribute) + """Activates one by one the attributes contained in + self.attributes. + """ + for attr in self.attributes: + self.win.attron(attr) def __exit__(self, type, value, traceback): - """Disable one by one the attributes contained in self.attributes.""" - for attribute in self.attributes: - self.win.attroff(attribute) + """Disable one by one the attributes contained in + self.attributes. + """ + for attr in self.attributes: + self.win.attroff(attr) From c39f80854645f43b36588ab17e7c149960cbfe87 Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Sat, 13 Mar 2021 22:28:34 +0100 Subject: [PATCH 19/52] Add frequently asked questions. --- doc/source/faq.rst | 17 +++++++++++++++++ doc/source/index.rst | 1 + 2 files changed, 18 insertions(+) create mode 100644 doc/source/faq.rst diff --git a/doc/source/faq.rst b/doc/source/faq.rst new file mode 100644 index 0000000..e79c4f2 --- /dev/null +++ b/doc/source/faq.rst @@ -0,0 +1,17 @@ +Frequently asked questions +========================== + +Why is DialogBox a context manager? +----------------------------------- + +You can use this behavior to avoid having to instantiate a new dialog box. + +See the `dedicated example `_. + +How can I continue to manage screen display while a DialogBox is writing text on the screen? +-------------------------------------------------------------------------------------------- + +``char_by_char`` and ``word_by_word`` methods of ``DialogBox`` class accept a callback in parameter. +You can also pass arguments to this callback via ``cargs`` parameter. + +The past callback is executed after downtime delay between character or word display. diff --git a/doc/source/index.rst b/doc/source/index.rst index 437577d..ef187e9 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -45,6 +45,7 @@ Getting help requirements.rst installation.rst api.rst + faq.rst Changelog --------- From 0782eaf28c6c40ca917c9b3d0dede8c3fdc35507 Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Sat, 13 Mar 2021 23:04:34 +0100 Subject: [PATCH 20/52] Optimization of text wrapping. --- doc/source/faq.rst | 1 + visualdialog/dialog.py | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/doc/source/faq.rst b/doc/source/faq.rst index e79c4f2..453132f 100644 --- a/doc/source/faq.rst +++ b/doc/source/faq.rst @@ -15,3 +15,4 @@ How can I continue to manage screen display while a DialogBox is writing text on You can also pass arguments to this callback via ``cargs`` parameter. The past callback is executed after downtime delay between character or word display. +You can use this behavior to perform multiple tasks while ``DialogBox`` scrolling. diff --git a/visualdialog/dialog.py b/visualdialog/dialog.py index ff0e576..ffc495d 100644 --- a/visualdialog/dialog.py +++ b/visualdialog/dialog.py @@ -81,6 +81,8 @@ def __init__( else: self.end_dialog_indicator_pos_y = pos_y + width - 1 + self.text_wrapper = textwrap.TextWrapper(width=self.nb_char_max_line) + def __enter__(self): return self @@ -206,7 +208,8 @@ def char_by_char( if flash_screen: curses.flash() - wrapped_text = textwrap.wrap(text, self.nb_char_max_line) + wrapped_text = self.text_wrapper.wrap(text, + self.nb_char_max_line) wrapped_text = _make_chunk(wrapped_text, self.nb_lines_max) for paragraph in wrapped_text: @@ -358,7 +361,8 @@ def word_by_word( attr = (curses.color_pair(colors_pair_nb), *text_attr) - wrapped_text = textwrap.wrap(text, self.nb_char_max_line) + wrapped_text = self.text_wrapper.wrap(text, + self.nb_char_max_line) wrapped_text = _make_chunk(wrapped_text, self.nb_lines_max) for paragraph in wrapped_text: From 47bf0fefbf683205f2475a79eabb5d3c1902b53b Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Sat, 13 Mar 2021 23:34:45 +0100 Subject: [PATCH 21/52] Add a specific README in examples. --- examples/README.md | 20 ++++++++++++++++++++ examples/context.py | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 examples/README.md diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..e4151bd --- /dev/null +++ b/examples/README.md @@ -0,0 +1,20 @@ +# Examples + +This directory contains several examples of use of **Visual-dialog**. +They are classified below by order of difficulty (approximately). + +## Monologue + +A complete concrete example of how to use **Visual-dialog**. + +## Text attributes + +An example showing the possibilities of text formatting in a text box. + +## Context + +An example of how to use a text box as a **context manager**. + +## Confrontation + +A concrete example exploiting the possibilities of library. diff --git a/examples/context.py b/examples/context.py index 4ed28f3..e79dc9e 100644 --- a/examples/context.py +++ b/examples/context.py @@ -1,6 +1,6 @@ # context.py # -# An example of how to use a text box with a context manager. +# An example of how to use a text box as a context manager. import curses From e5371495e6dc885e47754f6da7239673d9b573ba Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Sat, 13 Mar 2021 23:34:45 +0100 Subject: [PATCH 22/52] Add a specific README in examples. --- examples/README.md | 20 ++++++++++++++++++++ examples/context.py | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 examples/README.md diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..e4151bd --- /dev/null +++ b/examples/README.md @@ -0,0 +1,20 @@ +# Examples + +This directory contains several examples of use of **Visual-dialog**. +They are classified below by order of difficulty (approximately). + +## Monologue + +A complete concrete example of how to use **Visual-dialog**. + +## Text attributes + +An example showing the possibilities of text formatting in a text box. + +## Context + +An example of how to use a text box as a **context manager**. + +## Confrontation + +A concrete example exploiting the possibilities of library. diff --git a/examples/context.py b/examples/context.py index 4ed28f3..e79dc9e 100644 --- a/examples/context.py +++ b/examples/context.py @@ -1,6 +1,6 @@ # context.py # -# An example of how to use a text box with a context manager. +# An example of how to use a text box as a context manager. import curses From ac4c30f0eaa8ddcaee34c6f6b62a0d38308bfcd5 Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Sun, 14 Mar 2021 19:29:18 +0100 Subject: [PATCH 23/52] Added clarification in doc, improvement of examples. --- examples/README.md | 4 ++++ examples/confrontation.py | 36 +++++++++++++------------------ examples/monologue.py | 12 +++++------ examples/text_attributes.py | 5 ++--- examples/word.py | 40 ++++++++++++++++++++++++++++++++++ visualdialog/box.py | 8 +++---- visualdialog/dialog.py | 43 ++++++++++++++++++------------------- 7 files changed, 90 insertions(+), 58 deletions(-) create mode 100644 examples/word.py diff --git a/examples/README.md b/examples/README.md index e4151bd..f234006 100644 --- a/examples/README.md +++ b/examples/README.md @@ -7,6 +7,10 @@ They are classified below by order of difficulty (approximately). A complete concrete example of how to use **Visual-dialog**. +## Word + +An example of using the ``word_by_word`` method from text boxes. + ## Text attributes An example showing the possibilities of text formatting in a text box. diff --git a/examples/confrontation.py b/examples/confrontation.py index 1856895..50f442c 100644 --- a/examples/confrontation.py +++ b/examples/confrontation.py @@ -17,7 +17,6 @@ def main(stdscr): curses.curs_set(False) # Definition of several colors pairs. - curses.start_color() curses.init_pair(1, curses.COLOR_BLUE, curses.COLOR_BLACK) curses.init_pair(2, curses.COLOR_MAGENTA, curses.COLOR_BLACK) curses.init_pair(3, curses.COLOR_RED, curses.COLOR_BLACK) @@ -53,34 +52,29 @@ def main(stdscr): miles_edgeworth.confirm_dialog_key = (ENTER_KEY, SPACE_KEY) phoenix_wright.char_by_char(stdscr, - "This testimony is a pure invention !", - colors_pair_nb=0, # Color pair 0 is initialized by curses. It corresponds to white on black text. - delay=0.03) # Set delay between writting each characters to 0.03 seconde. + "This testimony is a pure invention !", + delay=0.03) # Set delay between writting each characters to 0.03 seconde. phoenix_wright.char_by_char(stdscr, - "You're lying April May !", - colors_pair_nb=0, - flash_screen=True, # A short luminous glow will be displayed before writing the text. - delay=0.03, - text_attr=curses.A_BOLD) + "You're lying April May !", + flash_screen=True, # A short luminous glow will be displayed before writing the text. + delay=0.03, + text_attr=curses.A_BOLD) april_may.char_by_char(stdscr, - "Arghh !", - colors_pair_nb=0, - delay=0.02, - text_attr=curses.A_ITALIC) + "Arghh !", + delay=0.02, + text_attr=curses.A_ITALIC) miles_edgeworth.char_by_char(stdscr, - "OBJECTION !", - colors_pair_nb=0, - flash_screen=True, - delay=0.03, - text_attr=curses.A_BOLD) + "OBJECTION !", + flash_screen=True, + delay=0.03, + text_attr=curses.A_BOLD) miles_edgeworth.char_by_char(stdscr, - "These accusations are irrelevant !", - colors_pair_nb=0, - delay=0.03) + "These accusations are irrelevant !", + delay=0.03) # Execution of main function. diff --git a/examples/monologue.py b/examples/monologue.py index d2f8422..44cceae 100644 --- a/examples/monologue.py +++ b/examples/monologue.py @@ -24,20 +24,18 @@ def main(stdscr): curses.curs_set(False) # Definition of several colors pairs. - curses.start_color() curses.init_pair(1, curses.COLOR_GREEN, curses.COLOR_BLACK) curses.init_pair(2, curses.COLOR_CYAN, curses.COLOR_BLACK) - textbox = DialogBox( - 1, 1, # Position 1;1 in stdscr. - 40, 6, # Length and width of textbox (in character). - title="Tim-ats-d", # Title of textbox. - title_colors_pair_nb=1) # Curses color_pair used to colored title. + textbox = DialogBox(1, 1, # Position 1;1 in stdscr. + 40, 6, # Length and width of textbox (in character). + title="Tim-ats-d", # Title of textbox. + title_colors_pair_nb=1) # Curses color_pair used to colored title. # Definition of accepted key codes to pass a dialog. textbox.confirm_dialog_key = (ENTER_KEY, SPACE_KEY) - # We iterate on each sentence contained in replys. + # Iterate on each sentence contained in replys. for reply in replys: textbox.char_by_char(stdscr, reply, diff --git a/examples/text_attributes.py b/examples/text_attributes.py index 0431472..8a48c74 100644 --- a/examples/text_attributes.py +++ b/examples/text_attributes.py @@ -17,13 +17,12 @@ def main(stdscr): curses.curs_set(False) # Definition of several colors pairs. - curses.start_color() - curses.init_pair(1, curses.COLOR_YELLOW, curses.COLOR_BLACK) + curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_YELLOW) curses.init_pair(2, curses.COLOR_MAGENTA, curses.COLOR_BLACK) demo_textbox = DialogBox(1, 1, 40, 6, - title="Demonstration", + title="Demo", title_colors_pair_nb=1, # Display title colored with color pair 1. title_text_attr=curses.A_UNDERLINE) # curse text attributes that will be applied to the title. demo_textbox.confirm_dialog_key = (ENTER_KEY, SPACE_KEY) diff --git a/examples/word.py b/examples/word.py new file mode 100644 index 0000000..85ca12a --- /dev/null +++ b/examples/word.py @@ -0,0 +1,40 @@ +# robot.py +# +#  A simple example of how to use Visual-dialog. + +import curses + +from visualdialog import DialogBox + + +# Definition of curses key constants. +# 10 and 32 correspond to enter and space keys. +ENTER_KEY = 10 +SPACE_KEY = 32 + +def main(stdscr): + instructions = ( + "Instead of the char_by_char method, word_by_word displays the " + "given text word by word.", + "It can be useful to make robots talk for example." + ) + + # Makes the cursor invisible. + curses.curs_set(False) + + textbox = DialogBox(1, 1, # Position 1;1 in stdscr. + 40, 6, # Length and width of textbox (in character). + title="Robot") # Title of textbox. + + # Definition of accepted key codes to pass a dialog. + textbox.confirm_dialog_key = (ENTER_KEY, SPACE_KEY) + + # Iterate on each sentence contained in replys. + for instruction in instructions: + textbox.word_by_word(stdscr, + instruction, + delay=0.2) # Set delay between writting each words to 0.1 seconde. + + +# Execution of main function. +curses.wrapper(main) diff --git a/visualdialog/box.py b/visualdialog/box.py index 637ed11..081ad8a 100644 --- a/visualdialog/box.py +++ b/visualdialog/box.py @@ -68,9 +68,6 @@ class TextBox: which methods will have effects. :type width: int - :param width: Width of the dialog box in the terminal. - :type width: Optional[int] - :param title: String that will be displayed in the upper left corner of dialog box. If title is an empty string, the title will not be displayed. @@ -84,8 +81,9 @@ class TextBox: :type title_colors_pair_nb: Optional[int] :param title_text_attr: - Dialog box title text attributes. This defaults to - ``curses.A_BOLD``. + Dialog box title text attributes. It should be a single curses + text attribute or a tuple of curses text attribute. This + defaults to ``curses.A_BOLD``. :type title_text_attr: Optional[Union[CursesTextAttributesConstants,Tuple[CursesTextAttributesConstants],List[CursesTextAttributesConstants]]] :param downtime_chars: diff --git a/visualdialog/dialog.py b/visualdialog/dialog.py index ffc495d..e8e7128 100644 --- a/visualdialog/dialog.py +++ b/visualdialog/dialog.py @@ -37,12 +37,11 @@ class DialogBox(TextBox): """This class provides methods and attributs to manage a dialog box. - :param end_dialog_indicator: - Character that will be displayed in the lower right corner the - character once all the characters have been completed. - String with a length of more than one character can lead to an - overflow of the dialog box frame. - This defaults to ``"►"``. + :param end_dialog_indicator: Character that will be displayed in the + lower right corner the character once all the characters have + been completed. String with a length of more than one character + can lead to an overflow of the dialog box frame. This defaults + to ``"►"``. :type end_dialog_indicator: str .. NOTE:: @@ -133,20 +132,21 @@ def char_by_char( :param stdscr: ``curses`` window object on which the method will have effect. - :param text: Text that will be displayed character by character + :param text: Text that will be displayed character by character in the dialog box. This text can be wrapped to fit the proportions of the dialog box. :type text: str - :param colors_pair_nb: - Number of the curses color pair that will be used to color - the text. The number zero corresponding to the pair of - white color on black background initialized by ``curses``). - This defaults to ``0``. + :param colors_pair_nb: Number of the curses color pair that + will be used to color the text. The number zero + corresponding to the pair of white color on black + background initialized by ``curses``). This defaults to + ``0``. :type colors_pair_nb: Optional[int] - :param text_attr: Dialog box curses text attributes. This - defaults an empty tuple. + :param text_attr: Dialog box curses text attributes. It should + be a single curses text attribute or a tuple of curses text + attribute. This defaults an empty tuple. :type text_attr: Optional[Union[CursesTextAttributesConstants,Tuple[CursesTextAttributesConstants],List[CursesTextAttributesConstants]]] :param words_attr: TODO @@ -208,8 +208,7 @@ def char_by_char( if flash_screen: curses.flash() - wrapped_text = self.text_wrapper.wrap(text, - self.nb_char_max_line) + wrapped_text = self.text_wrapper.wrap(text) wrapped_text = _make_chunk(wrapped_text, self.nb_lines_max) for paragraph in wrapped_text: @@ -260,7 +259,7 @@ def word_by_word( self, stdscr, text: str, - colors_pair_nb: int, + colors_pair_nb: int = 0, cut_char: str = " ", text_attr: Union[CursesTextAttributesConstants, Tuple[CursesTextAttributesConstants], @@ -271,7 +270,7 @@ def word_by_word( flash_screen: bool = False, delay: Union[int, float] = .15, random_delay: Tuple[float, float] = (0, 0), - callback: Callable = None, + callback: Callable = lambda : None, cargs: Union[Tuple, List] = ()): """Writes the given text word by word at position in the current dialog box. @@ -291,8 +290,9 @@ def word_by_word( This defaults to ``0``. :type colors_pair_nb: Optional[int] - :param text_attr: Dialog box curses text attributes. This - defaults an empty tuple. + :param text_attr: Dialog box curses text attributes. It should + be a single curses text attribute or a tuple of curses text + attribute. This defaults an empty tuple. :type text_attr: Optional[Union[CursesTextAttributesConstants,Tuple[CursesTextAttributesConstants],List[CursesTextAttributesConstants]]] :param words_attr: TODO @@ -361,8 +361,7 @@ def word_by_word( attr = (curses.color_pair(colors_pair_nb), *text_attr) - wrapped_text = self.text_wrapper.wrap(text, - self.nb_char_max_line) + wrapped_text = self.text_wrapper.wrap(text) wrapped_text = _make_chunk(wrapped_text, self.nb_lines_max) for paragraph in wrapped_text: From d031be330489141e70dd7d47fc35bb54bf6d44a8 Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Mon, 15 Mar 2021 16:46:57 +0100 Subject: [PATCH 24/52] Add more precise type hinting in TextBox. --- visualdialog/box.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/visualdialog/box.py b/visualdialog/box.py index 081ad8a..77f5924 100644 --- a/visualdialog/box.py +++ b/visualdialog/box.py @@ -23,7 +23,7 @@ import curses import curses.textpad -from typing import List, NewType, Tuple, Union +from typing import ClassVar, List, NewType, Tuple, Union from .utils import (CursesKeyConstants, CursesTextAttributesConstants, @@ -99,11 +99,11 @@ class TextBox: :type downtime_chars_delay: Optional[Union[int,float]] """ #: List of accepted key codes to skip dialog. ``curses`` constants are supported. This defaults to an empty tuple. - confirm_dialog_key: Union[Tuple[CursesKeyConstants], - List[CursesKeyConstants]] = () + confirm_dialog_key: ClassVar[Union[Tuple[CursesKeyConstants], + List[CursesKeyConstants]]] = () #: List of accepted key codes to raise PanicError. ``curses`` constants are supported. This defaults to an empty tuple. - panic_key: Union[Tuple[CursesKeyConstants], - List[CursesKeyConstants]] = () + panic_key: ClassVar[Union[Tuple[CursesKeyConstants], + List[CursesKeyConstants]]] = () def __init__( self, From 578674cab05d38f4e6353fcc02ae95fe0a1cd71c Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Tue, 16 Mar 2021 22:41:27 +0100 Subject: [PATCH 25/52] confirm_dialog_key and panic_key are now instance attributes of the TextBox class. --- visualdialog/box.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/visualdialog/box.py b/visualdialog/box.py index 77f5924..ff935af 100644 --- a/visualdialog/box.py +++ b/visualdialog/box.py @@ -23,7 +23,7 @@ import curses import curses.textpad -from typing import ClassVar, List, NewType, Tuple, Union +from typing import List, NewType, Tuple, Union from .utils import (CursesKeyConstants, CursesTextAttributesConstants, @@ -98,12 +98,6 @@ class TextBox: This defaults to ``0.6``. :type downtime_chars_delay: Optional[Union[int,float]] """ - #: List of accepted key codes to skip dialog. ``curses`` constants are supported. This defaults to an empty tuple. - confirm_dialog_key: ClassVar[Union[Tuple[CursesKeyConstants], - List[CursesKeyConstants]]] = () - #: List of accepted key codes to raise PanicError. ``curses`` constants are supported. This defaults to an empty tuple. - panic_key: ClassVar[Union[Tuple[CursesKeyConstants], - List[CursesKeyConstants]]] = () def __init__( self, @@ -145,6 +139,13 @@ def __init__( self.downtime_chars = downtime_chars self.downtime_chars_delay = downtime_chars_delay + #: List of accepted key codes to skip dialog. ``curses`` constants are supported. This defaults to an empty tuple. + self.confirm_dialog_key: Union[Tuple[CursesKeyConstants], + List[CursesKeyConstants]] = () + #: List of accepted key codes to raise PanicError. ``curses`` constants are supported. This defaults to an empty tuple. + self.panic_key: Union[Tuple[CursesKeyConstants], + List[CursesKeyConstants]] = () + @property def position(self) -> Tuple[int, int]: """Returns a tuple contains x;y position of ``TextBox``. From 30c20cc319e1026627fc6cb857b16d1c5ac0eb1f Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Wed, 17 Mar 2021 14:58:13 +0100 Subject: [PATCH 26/52] Rename choices_box.py to choices.py, remove useless imports. --- visualdialog/box.py | 4 ++-- visualdialog/{choices_box.py => choices.py} | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) rename visualdialog/{choices_box.py => choices.py} (91%) diff --git a/visualdialog/box.py b/visualdialog/box.py index ff935af..0182c3a 100644 --- a/visualdialog/box.py +++ b/visualdialog/box.py @@ -23,7 +23,7 @@ import curses import curses.textpad -from typing import List, NewType, Tuple, Union +from typing import List, Tuple, Union from .utils import (CursesKeyConstants, CursesTextAttributesConstants, @@ -118,7 +118,7 @@ def __init__( self.title_offsetting_y = 2 if title else 0 - # Compensation for the left border of the dialog box. + # Compensation for the left border of the dialog box. self.text_pos_x = pos_x + 2 # Compensation for the upper border of the dialog box. self.text_pos_y = pos_y + self.title_offsetting_y + 1 diff --git a/visualdialog/choices_box.py b/visualdialog/choices.py similarity index 91% rename from visualdialog/choices_box.py rename to visualdialog/choices.py index e52cbf3..0f75452 100644 --- a/visualdialog/choices_box.py +++ b/visualdialog/choices.py @@ -20,10 +20,10 @@ # import curses -from typing import Any, Dict, NewType, Tuple, Union +from typing import Any, Dict, Tuple, Union import box -from utils import CursesTextAttributesConstants, TextAttributes, _make_chunk +from .utils import CursesTextAttributesConstants, TextAttributes, _make_chunk class ChoiceBox(box.TextBox): @@ -57,11 +57,10 @@ def chain( """""" super().framing_box(stdscr) - for y, proposition in enumerate(propositions): stdscr.addstr(self.pos_y + y*2, - self.pos_x, - proposition) + self.pos_x, + proposition) stdscr.refresh() From f3731434f1407c583690a3cae3c1b4e10e3fe173 Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Wed, 17 Mar 2021 15:32:59 +0100 Subject: [PATCH 27/52] Fix type hinting in utils.py, continuation of deletion of useless imports, fix import in choices.py, pep8 fixes. --- visualdialog/choices.py | 6 +++--- visualdialog/dialog.py | 9 ++++----- visualdialog/utils.py | 10 +++++----- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/visualdialog/choices.py b/visualdialog/choices.py index 0f75452..d3f342f 100644 --- a/visualdialog/choices.py +++ b/visualdialog/choices.py @@ -1,4 +1,4 @@ -# choices_box.py +# choices.py # # 2020 Timéo Arnouts # @@ -22,11 +22,11 @@ import curses from typing import Any, Dict, Tuple, Union -import box +from .dialog import DialogBox from .utils import CursesTextAttributesConstants, TextAttributes, _make_chunk -class ChoiceBox(box.TextBox): +class ChoiceBox(DialogBox): def __init__( self, diff --git a/visualdialog/dialog.py b/visualdialog/dialog.py index e8e7128..527b0ec 100644 --- a/visualdialog/dialog.py +++ b/visualdialog/dialog.py @@ -25,8 +25,7 @@ import random import textwrap import time -from typing import (Callable, Dict, Generator, List, NewType, Optional, - Tuple, Union) +from typing import Callable, Dict, List, Optional, Tuple, Union from .box import TextBox from .utils import (CursesTextAttributesConstants, @@ -91,7 +90,7 @@ def __exit__(self, type, value, traceback): def _display_end_dialog_indicator( self, stdscr, - text_attr: Optional[Union[Tuple[CursesTextAttributesConstants],List[CursesTextAttributesConstants]]] = ( + text_attr: Optional[Union[Tuple[CursesTextAttributesConstants], List[CursesTextAttributesConstants]]] = ( curses.A_BOLD, curses.A_BLINK)): """Displays an end of dialog indicator in the lower right corner of textbox. @@ -124,7 +123,7 @@ def char_by_char( flash_screen: bool = False, delay: Union[int, float] = .04, random_delay: Tuple[float, float] = (0, 0), - callback: Callable = lambda : None, + callback: Callable = lambda: None, cargs: Union[Tuple, List] = ()): """Writes the given text character by character in the current dialog box. @@ -270,7 +269,7 @@ def word_by_word( flash_screen: bool = False, delay: Union[int, float] = .15, random_delay: Tuple[float, float] = (0, 0), - callback: Callable = lambda : None, + callback: Callable = lambda: None, cargs: Union[Tuple, List] = ()): """Writes the given text word by word at position in the current dialog box. diff --git a/visualdialog/utils.py b/visualdialog/utils.py index aaa7238..e507a03 100644 --- a/visualdialog/utils.py +++ b/visualdialog/utils.py @@ -19,24 +19,24 @@ # # -from typing import List, NewType, Tuple, Union +from typing import Generator, List, Tuple, Union # curses text attribute constants are integers. # See https://docs.python.org/3/library/curses.html?#constants -CursesTextAttributesConstants = NewType("CursesTextAttributesConstants", int) +CursesTextAttributesConstants = int # curses key constants are integers. # See https://docs.python.org/3/library/curses.html?#constants -CursesKeyConstants = NewType("CursesKeyConstants", int) +CursesKeyConstants = int def _make_chunk(iterable: Union[Tuple, List], - chunk_length: int) -> Tuple: + chunk_length: int) -> Generator: """Returns a tuple that contains the given iterator separated into chunk_length bundles. :returns: Iterator separated into chunk_length bundles. - :rtype: Tuple + :rtype: Generator """ return (iterable[chunk:chunk + chunk_length] for chunk in range(0, len(iterable), chunk_length)) From 1df58b9a6e3712879f690d46f3858319a5d37b4a Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Wed, 17 Mar 2021 15:48:58 +0100 Subject: [PATCH 28/52] Adding an example in quickstart section of readme. --- README.md | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d8b5d23..81b895e 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,30 @@ See [this explanations](https://www.devdungeon.com/content/curses-windows-python ## Quick-start -Various examples showing the capabilities of **Visual-dialog** can be found in [examples](examples/). +### A hello world with **Visual-dialog** + +```python3 +import curses + +from visualdialog import DialogBox + + +def main(stdscr): + curses.curs_set(False) + + textbox = DialogBox(0, 0, + 35, 6, + title="Demo") + textbox.char_by_char(stdscr, + "Hello world") + + +curses.wrapper(main) +``` + +### Examples + +Other various examples showing the capabilities of **Visual-dialog** can be found in [examples](examples/). ## Documentation From 38559b52000078b2eae4bf9ca8412c0ee484658b Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Wed, 17 Mar 2021 15:46:23 +0100 Subject: [PATCH 29/52] Improving examples --- examples/README.md | 2 +- examples/confrontation.py | 3 +-- examples/text_attributes.py | 4 ++-- examples/word.py | 4 ++-- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/examples/README.md b/examples/README.md index f234006..be7d164 100644 --- a/examples/README.md +++ b/examples/README.md @@ -9,7 +9,7 @@ A complete concrete example of how to use **Visual-dialog**. ## Word -An example of using the ``word_by_word`` method from text boxes. +An example of using ``word_by_word`` method from text boxes. ## Text attributes diff --git a/examples/confrontation.py b/examples/confrontation.py index 50f442c..8aa4f08 100644 --- a/examples/confrontation.py +++ b/examples/confrontation.py @@ -9,8 +9,7 @@ # Definition of curses key constants. # 10 and 32 correspond to enter and space keys. -ENTER_KEY = 10 -SPACE_KEY = 32 +ENTER_KEY, SPACE_KEY = 10, 32 def main(stdscr): # Makes the cursor invisible. diff --git a/examples/text_attributes.py b/examples/text_attributes.py index 8a48c74..2e25c83 100644 --- a/examples/text_attributes.py +++ b/examples/text_attributes.py @@ -40,11 +40,11 @@ def main(stdscr): "The colors of the front and the background reversed.": curses.A_REVERSE, } - for text, attributs in sentences.items(): + for text, attributes in sentences.items(): demo_textbox.char_by_char(stdscr, text, 2, # Display text colored with color pair 2. - text_attr=attributs) # Pass the attributes to the text. + text_attr=attributes) # Pass attributes to text. # Execution of main function. diff --git a/examples/word.py b/examples/word.py index 85ca12a..dd907c2 100644 --- a/examples/word.py +++ b/examples/word.py @@ -1,6 +1,6 @@ -# robot.py +# words.py # -#  A simple example of how to use Visual-dialog. +#  An example of using the word_by_word method from text boxes. import curses From d26b7694d81882f18f0c9277deeede7562811788 Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Wed, 17 Mar 2021 18:23:54 +0100 Subject: [PATCH 30/52] Fix typo. --- README.md | 4 ++-- visualdialog/choices.py | 4 +++- visualdialog/dialog.py | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 81b895e..46dfe16 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ See [this explanations](https://www.devdungeon.com/content/curses-windows-python ## Quick-start -### A hello world with **Visual-dialog** +### Hello world with **Visual-dialog** ```python3 import curses @@ -112,7 +112,7 @@ Once generated, the result will be in the `doc/build/html/` folder. We would love for you to contribute to improve **Visual-dialog**. Take a look at our [contributing guide](CONTRIBUTING.md) to get started. -You can also help by reporting bugs. +You can also help by reporting **bugs**. ## License diff --git a/visualdialog/choices.py b/visualdialog/choices.py index d3f342f..4681df0 100644 --- a/visualdialog/choices.py +++ b/visualdialog/choices.py @@ -23,7 +23,9 @@ from typing import Any, Dict, Tuple, Union from .dialog import DialogBox -from .utils import CursesTextAttributesConstants, TextAttributes, _make_chunk +from .utils import (CursesTextAttributesConstants, + TextAttributes, + _make_chunk) class ChoiceBox(DialogBox): diff --git a/visualdialog/dialog.py b/visualdialog/dialog.py index 527b0ec..bf6a0c2 100644 --- a/visualdialog/dialog.py +++ b/visualdialog/dialog.py @@ -51,6 +51,7 @@ class DialogBox(TextBox): Parameters ``downtime_chars`` and ``downtime_chars_delay`` do not affect ``word_by_word`` method. """ + def __init__( self, pos_x: int, From a19406a3113e9cdae8cae48a8beffbe045237866 Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Thu, 18 Mar 2021 21:50:01 +0100 Subject: [PATCH 31/52] Add link in example readme, improve confrontation.py. --- examples/README.md | 10 +++++----- examples/confrontation.py | 9 ++++----- examples/context.py | 1 + 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/README.md b/examples/README.md index be7d164..d3f3b1e 100644 --- a/examples/README.md +++ b/examples/README.md @@ -3,22 +3,22 @@ This directory contains several examples of use of **Visual-dialog**. They are classified below by order of difficulty (approximately). -## Monologue +## [Monologue](monologue.py) A complete concrete example of how to use **Visual-dialog**. -## Word +## [Word](word.py) An example of using ``word_by_word`` method from text boxes. -## Text attributes +## [Text attributes](text_attributes.py) An example showing the possibilities of text formatting in a text box. -## Context +## [Context](contex.py) An example of how to use a text box as a **context manager**. -## Confrontation +## [Confrontation](confrontation.py) A concrete example exploiting the possibilities of library. diff --git a/examples/confrontation.py b/examples/confrontation.py index 8aa4f08..62472b9 100644 --- a/examples/confrontation.py +++ b/examples/confrontation.py @@ -9,7 +9,7 @@ # Definition of curses key constants. # 10 and 32 correspond to enter and space keys. -ENTER_KEY, SPACE_KEY = 10, 32 +PASS_DIALOG_KEY = (10, 32) def main(stdscr): # Makes the cursor invisible. @@ -20,7 +20,6 @@ def main(stdscr): curses.init_pair(2, curses.COLOR_MAGENTA, curses.COLOR_BLACK) curses.init_pair(3, curses.COLOR_RED, curses.COLOR_BLACK) - position = (1, 1) # Position 1;1 in stdscr. width, length = 6, 35 # Width and length (in character). max_y, max_x = stdscr.getmaxyx() @@ -46,9 +45,9 @@ def main(stdscr): title_colors_pair_nb=3) # Definition of accepted key codes to pass a dialog. - phoenix_wright.confirm_dialog_key = (ENTER_KEY, SPACE_KEY) - april_may.confirm_dialog_key = (ENTER_KEY, SPACE_KEY) - miles_edgeworth.confirm_dialog_key = (ENTER_KEY, SPACE_KEY) + phoenix_wright.confirm_dialog_key = PASS_DIALOG_KEY + april_may.confirm_dialog_key = PASS_DIALOG_KEY + miles_edgeworth.confirm_dialog_key = PASS_DIALOG_KEY phoenix_wright.char_by_char(stdscr, "This testimony is a pure invention !", diff --git a/examples/context.py b/examples/context.py index e79dc9e..c4c14e1 100644 --- a/examples/context.py +++ b/examples/context.py @@ -12,6 +12,7 @@ ENTER_KEY = 10 SPACE_KEY = 32 + def main(stdscr): # Makes the cursor invisible. curses.curs_set(False) From 130179e26ab385cbdde6c2a07fcf8825798b3d47 Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Thu, 18 Mar 2021 23:33:52 +0100 Subject: [PATCH 32/52] Configuration change for doc in conf.py, fix typo. --- README.md | 5 +- doc/build/doctrees/api.doctree | Bin 0 -> 2594 bytes doc/build/doctrees/environment.pickle | Bin 0 -> 20691 bytes doc/build/doctrees/faq.doctree | Bin 0 -> 5952 bytes doc/build/doctrees/index.doctree | Bin 0 -> 12831 bytes doc/build/doctrees/installation.doctree | Bin 0 -> 3892 bytes doc/build/doctrees/requirements.doctree | Bin 0 -> 6863 bytes doc/build/doctrees/utils.doctree | Bin 0 -> 10383 bytes doc/build/doctrees/visualdialog.doctree | Bin 0 -> 113472 bytes doc/source/conf.py | 197 +++++------------- .../images/{demo.png => visual-dialog.png} | Bin doc/source/index.rst | 16 +- visualdialog/__init__.py | 4 +- .../__pycache__/__init__.cpython-37.pyc | Bin 0 -> 381 bytes visualdialog/__pycache__/box.cpython-37.pyc | Bin 0 -> 6486 bytes .../__pycache__/dialog.cpython-37.pyc | Bin 0 -> 12721 bytes visualdialog/__pycache__/utils.cpython-37.pyc | Bin 0 -> 2050 bytes 17 files changed, 70 insertions(+), 152 deletions(-) create mode 100644 doc/build/doctrees/api.doctree create mode 100644 doc/build/doctrees/environment.pickle create mode 100644 doc/build/doctrees/faq.doctree create mode 100644 doc/build/doctrees/index.doctree create mode 100644 doc/build/doctrees/installation.doctree create mode 100644 doc/build/doctrees/requirements.doctree create mode 100644 doc/build/doctrees/utils.doctree create mode 100644 doc/build/doctrees/visualdialog.doctree rename doc/source/images/{demo.png => visual-dialog.png} (100%) create mode 100644 visualdialog/__pycache__/__init__.cpython-37.pyc create mode 100644 visualdialog/__pycache__/box.cpython-37.pyc create mode 100644 visualdialog/__pycache__/dialog.cpython-37.pyc create mode 100644 visualdialog/__pycache__/utils.cpython-37.pyc diff --git a/README.md b/README.md index 46dfe16..70596bc 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ def main(stdscr): title="Demo") textbox.char_by_char(stdscr, "Hello world") - + curses.wrapper(main) ``` @@ -92,7 +92,7 @@ Other various examples showing the capabilities of **Visual-dialog** can be foun ## Documentation Visualdialog's documentation is automatically generated from the source code by **Sphinx**. -To build it on **GNU/Linux** or **MacOS**: +To build it in **HTML** on **GNU/Linux** or **MacOS**: ```sh git clone https://github.com/Tim-ats-d/Visual-dialog.git cd Visual-dialog/doc @@ -106,6 +106,7 @@ cd Visual-dialog/doc ``` Once generated, the result will be in the `doc/build/html/` folder. +You can also generate documentation in **Latex**, **Texinfo** or **man-pages**. ## Contributing diff --git a/doc/build/doctrees/api.doctree b/doc/build/doctrees/api.doctree new file mode 100644 index 0000000000000000000000000000000000000000..3b69fbb888e03e35da70a4f7545ccab143d823f5 GIT binary patch literal 2594 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 K1m!2V3;ZvxLBD(e literal 0 HcmV?d00001 diff --git a/doc/build/doctrees/installation.doctree b/doc/build/doctrees/installation.doctree new file mode 100644 index 0000000000000000000000000000000000000000..28bfa3994f926702613eec9f83f68f482de829f2 GIT binary patch literal 3892 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 literal 0 HcmV?d00001 diff --git a/doc/build/doctrees/utils.doctree b/doc/build/doctrees/utils.doctree new file mode 100644 index 0000000000000000000000000000000000000000..00ce8c2398d9df68a6f576da0c626049cd5ea0b0 GIT binary patch literal 10383 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

;r!Gx~*SrDR4mi`6>`i}lQW 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 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. # -# This file does only contain a selection of the most common options. For a -# full list see the documentation: -# http://www.sphinx-doc.org/en/master/config import os import sys +from datetime import datetime -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -# import os -# import sys -# sys.path.insert(0, os.path.abspath('.')) -sys.path.insert(0, os.path.abspath('../../')) - -# -- Project information ----------------------------------------------------- +from visualdialog import __version__ -project = 'Visual-dialog' -copyright = '2021, Arnouts Timéo' -author = 'Arnouts Timéo' -# The short X.Y version -version = '' -# The full version, including alpha/beta/rc tags -release = '0.6' +sys.path.insert(0, os.path.abspath("../../")) +project = "Visual-dialog" +copyright = f"2021-{datetime.now().year}, Timéo Arnouts" +author = "Timéo Arnouts" -# -- General configuration --------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -# -# needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. extensions = [ - 'sphinx.ext.autodoc', + "sphinx.ext.autodoc" ] -autodoc_member_order = 'bysource' +master_doc = "index" +source_suffix = ".rst" +autodoc_member_order = "bysource" -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -# source_suffix = ['.rst', '.md'] -source_suffix = '.rst' +version = str(__version__) +release = version -# The master toct ree document. -master_doc = 'index' +templates_path = ["_templates"] -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = None - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. exclude_patterns = [] -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'friendly' - - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = 'sphinx_rtd_theme' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# -# html_theme_options = {} +pygments_style = "friendly" -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# Custom sidebar templates, must be a dictionary that maps document names -# to template names. -# -# The default sidebars (for documents that don't match any pattern) are -# defined by theme itself. Builtin themes are using these templates by -# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', -# 'searchbox.html']``. -# -# html_sidebars = {} - - -# -- Options for HTMLHelp output --------------------------------------------- - -# Output file base name for HTML help builder. -htmlhelp_basename = 'Visual-dialogdoc' +html_title = "Visual-dialog Documentation" +html_theme = "sphinx_rtd_theme" +html_static_path = ["_static"] +html_show_sourcelink = True +html_theme_options = { + "display_version": True +} +html_logo = "images/visual-dialog.png" -# -- Options for LaTeX output ------------------------------------------------ +latex_logo = "images/visual-dialog.png" latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', + "pointsize": "12pt", + "fontpkg": r""" + \setmainfont{Open Sans} + \setsansfont{Bitter} + \setmonofont{Ubuntu Mono} + """ } -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'Visual-dialog.tex', 'Visual-dialog Documentation', - 'Arnouts Timéo', 'manual'), + (master_doc, "Visual-dialog.tex", "Visual-dialog Documentation", + "Arnouts Timéo", "manual"), ] - -# -- Options for manual page output ------------------------------------------ - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). man_pages = [ - (master_doc, 'visual-dialog', 'Visual-dialog Documentation', + (master_doc, "visual-dialog", "Visual-dialog Documentation", [author], 1) ] - -# -- Options for Texinfo output ---------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'Visual-dialog', 'Visual-dialog Documentation', - author, 'Visual-dialog', 'One line description of project.', - 'Miscellaneous'), + (master_doc, "Visual-dialog", "Visual-dialog Documentation", + author, "Visual-dialog", "One line description of project.", + "Miscellaneous"), ] - - -# -- Options for Epub output ------------------------------------------------- - -# Bibliographic Dublin Core info. -epub_title = project - -# The unique identifier of the text. This can be a ISBN number -# or the project homepage. -# -# epub_identifier = '' - -# A unique identification for the text. -# -# epub_uid = '' - -# A list of files that should not be packed into the epub file. -epub_exclude_files = ['search.html'] diff --git a/doc/source/images/demo.png b/doc/source/images/visual-dialog.png similarity index 100% rename from doc/source/images/demo.png rename to doc/source/images/visual-dialog.png diff --git a/doc/source/index.rst b/doc/source/index.rst index ef187e9..18911e1 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -9,7 +9,7 @@ Welcome to Visual-dialog's documentation **Visual-dialog** is a **Python library** that allows you to make dialog box in a terminal easily. **Visual-dialog** uses ``curses`` to display text in terminals. -.. image:: ./images/demo.png +.. image:: ./images/visual-dialog.png :align: center **Features:** @@ -39,12 +39,22 @@ Getting help - Report bugs in the `issue tracker `_. .. toctree:: - :maxdepth: 3 - :caption: Contents: + :hidden: + :caption: Introduction requirements.rst installation.rst + +.. toctree:: + :hidden: + :caption: API Reference + api.rst + +.. toctree:: + :hidden: + :caption: Meta + faq.rst Changelog diff --git a/visualdialog/__init__.py b/visualdialog/__init__.py index 2b528c0..3b4b4bf 100644 --- a/visualdialog/__init__.py +++ b/visualdialog/__init__.py @@ -1,9 +1,9 @@ """ -A librairie which provides class to make easier dialog box in terminal. +A library to make easier dialog box in terminal. """ __version__ = 0.6 -__author__ = "Arnouts Timéo" +__author__ = "Timéo Arnouts" from .dialog import DialogBox from .utils import TextAttributes diff --git a/visualdialog/__pycache__/__init__.cpython-37.pyc b/visualdialog/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aee828dcb7a8d5ae3070ca2f9359e7416d8fd40e GIT binary patch literal 381 zcmXw!y-ve05Xa**O-i7B2D8zDW?*1JK&vh+NQjCd%i-2mbE*?Z{wRev>TAGTWbMQ& zFyW-|m+tQWvvePx4Tovqh38MNpS~Vi_*dx7y$^HY6A$u$lbrA{4|$JAvR6iVL;~;o zvR}q|?0x(kjFXRtbV?;IHA9Vpz7@DCXsy*2xiEAknK9HVTC$Cxf*BN=a%57i=~C?| zR@4ezV#VYnU0-!N+_{lIVX7y662q&;wt(^mCmRsuWp`*<6^7 rRdx1)#xZ$$Y|geF{Q&@0XaOemdo#Ex6?anHwj)hDFbX3Qg=gd!#GY+8 literal 0 HcmV?d00001 diff --git a/visualdialog/__pycache__/box.cpython-37.pyc b/visualdialog/__pycache__/box.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9de17d94230f8f33a2dcdb1ab5ab58827c714048 GIT binary patch literal 6486 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 wS79vT%v51D>e`Cd%Obw!s4h{Sg6R?V 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 axN^8}0_g63pIV>Gi#PFe9n(e)feg zg0mowwulHt?Tm=psTVgy{TXRRe?dgj3d+WoR8n(W=rVYc8C!O3%ru zze!FhU9xNoLx;HK={hXaH8hZT^7i5Cuo%hJakjcC&Au(h zt1mJ$;rRoR@w^zU9=OqLFtJ%~+T-K5jqU(4cr?~)EeK)|S3pnhCnLTu6Sq^ln-?k1 z&02fms*wi32{l&(S^AJuz~i7eP>AOWz$KU{Z(IucDNGpDCyAD*UWR9igLy+0Jce%C zU?hpDATekm_GJLdq%&oPl`i9&sEg=~#C6etqiUA7p35V3gE6gUBEmW zam5ExGsu1Q%F>B867+&!`E1!j11OjgJIqvv?N*MvI1Ua%IiV=eXg2`0Th{TC{9lAYI&A%o9px3v-281o@JLOg*rW zGbdcoi4O;+Z^LIO2!WA#zpNQ6Osd_alW%35PNF2qRA!Up5k_?)@oV(t?o}4;x!09D zh29&ujDb1&cuMxjCfQkj4Y2m;FW!{?O5sjLKR_`AaNfz&_1R$>Rw&7O$L@iZxop8g1j0S>PdZX}6RP+^j!3qBR&V2Pfr zyu*0rmbya$U#yt_Mmd;K4z5xDZZk7jkpCybbw{}0Z-q0)sLwJy;Q2(B4Xb%7dpzCO zD**laK|to^5oX?{(2>M*csKKv*LEu^VTs}+nIvU1Nk)a3Lg1~Fi Date: Thu, 18 Mar 2021 23:35:56 +0100 Subject: [PATCH 33/52] Revert "Configuration change for doc in conf.py, fix typo." This reverts commit 130179e26ab385cbdde6c2a07fcf8825798b3d47. --- README.md | 5 +- doc/build/doctrees/api.doctree | Bin 2594 -> 0 bytes doc/build/doctrees/environment.pickle | Bin 20691 -> 0 bytes doc/build/doctrees/faq.doctree | Bin 5952 -> 0 bytes doc/build/doctrees/index.doctree | Bin 12831 -> 0 bytes doc/build/doctrees/installation.doctree | Bin 3892 -> 0 bytes doc/build/doctrees/requirements.doctree | Bin 6863 -> 0 bytes doc/build/doctrees/utils.doctree | Bin 10383 -> 0 bytes doc/build/doctrees/visualdialog.doctree | Bin 113472 -> 0 bytes doc/source/conf.py | 197 +++++++++++++----- .../images/{visual-dialog.png => demo.png} | Bin doc/source/index.rst | 16 +- visualdialog/__init__.py | 4 +- .../__pycache__/__init__.cpython-37.pyc | Bin 381 -> 0 bytes visualdialog/__pycache__/box.cpython-37.pyc | Bin 6486 -> 0 bytes .../__pycache__/dialog.cpython-37.pyc | Bin 12721 -> 0 bytes visualdialog/__pycache__/utils.cpython-37.pyc | Bin 2050 -> 0 bytes 17 files changed, 152 insertions(+), 70 deletions(-) delete mode 100644 doc/build/doctrees/api.doctree delete mode 100644 doc/build/doctrees/environment.pickle delete mode 100644 doc/build/doctrees/faq.doctree delete mode 100644 doc/build/doctrees/index.doctree delete mode 100644 doc/build/doctrees/installation.doctree delete mode 100644 doc/build/doctrees/requirements.doctree delete mode 100644 doc/build/doctrees/utils.doctree delete mode 100644 doc/build/doctrees/visualdialog.doctree rename doc/source/images/{visual-dialog.png => demo.png} (100%) delete mode 100644 visualdialog/__pycache__/__init__.cpython-37.pyc delete mode 100644 visualdialog/__pycache__/box.cpython-37.pyc delete mode 100644 visualdialog/__pycache__/dialog.cpython-37.pyc delete mode 100644 visualdialog/__pycache__/utils.cpython-37.pyc diff --git a/README.md b/README.md index 70596bc..46dfe16 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ def main(stdscr): title="Demo") textbox.char_by_char(stdscr, "Hello world") - + curses.wrapper(main) ``` @@ -92,7 +92,7 @@ Other various examples showing the capabilities of **Visual-dialog** can be foun ## Documentation Visualdialog's documentation is automatically generated from the source code by **Sphinx**. -To build it in **HTML** on **GNU/Linux** or **MacOS**: +To build it on **GNU/Linux** or **MacOS**: ```sh git clone https://github.com/Tim-ats-d/Visual-dialog.git cd Visual-dialog/doc @@ -106,7 +106,6 @@ cd Visual-dialog/doc ``` Once generated, the result will be in the `doc/build/html/` folder. -You can also generate documentation in **Latex**, **Texinfo** or **man-pages**. ## Contributing diff --git a/doc/build/doctrees/api.doctree b/doc/build/doctrees/api.doctree deleted file mode 100644 index 3b69fbb888e03e35da70a4f7545ccab143d823f5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2594 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 K1m!2V3;ZvxLBD(e diff --git a/doc/build/doctrees/installation.doctree b/doc/build/doctrees/installation.doctree deleted file mode 100644 index 28bfa3994f926702613eec9f83f68f482de829f2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3892 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 diff --git a/doc/build/doctrees/utils.doctree b/doc/build/doctrees/utils.doctree deleted file mode 100644 index 00ce8c2398d9df68a6f576da0c626049cd5ea0b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10383 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

;r!Gx~*SrDR4mi`6>`i}lQW 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 -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, -# MA 02110-1301, USA. +# Configuration file for the Sphinx documentation builder. # +# This file does only contain a selection of the most common options. For a +# full list see the documentation: +# http://www.sphinx-doc.org/en/master/config import os import sys -from datetime import datetime -from visualdialog import __version__ +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) +sys.path.insert(0, os.path.abspath('../../')) + +# -- Project information ----------------------------------------------------- +project = 'Visual-dialog' +copyright = '2021, Arnouts Timéo' +author = 'Arnouts Timéo' -sys.path.insert(0, os.path.abspath("../../")) +# The short X.Y version +version = '' +# The full version, including alpha/beta/rc tags +release = '0.6' -project = "Visual-dialog" -copyright = f"2021-{datetime.now().year}, Timéo Arnouts" -author = "Timéo Arnouts" +# -- General configuration --------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. extensions = [ - "sphinx.ext.autodoc" + 'sphinx.ext.autodoc', ] -master_doc = "index" -source_suffix = ".rst" -autodoc_member_order = "bysource" +autodoc_member_order = 'bysource' -version = str(__version__) -release = version +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' -templates_path = ["_templates"] +# The master toct ree document. +master_doc = 'index' +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. exclude_patterns = [] -pygments_style = "friendly" +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'friendly' -html_title = "Visual-dialog Documentation" -html_theme = "sphinx_rtd_theme" -html_static_path = ["_static"] -html_show_sourcelink = True -html_theme_options = { - "display_version": True -} -html_logo = "images/visual-dialog.png" +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'sphinx_rtd_theme' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] -latex_logo = "images/visual-dialog.png" +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# The default sidebars (for documents that don't match any pattern) are +# defined by theme itself. Builtin themes are using these templates by +# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', +# 'searchbox.html']``. +# +# html_sidebars = {} + + +# -- Options for HTMLHelp output --------------------------------------------- + +# Output file base name for HTML help builder. +htmlhelp_basename = 'Visual-dialogdoc' + + +# -- Options for LaTeX output ------------------------------------------------ latex_elements = { - "pointsize": "12pt", - "fontpkg": r""" - \setmainfont{Open Sans} - \setsansfont{Bitter} - \setmonofont{Ubuntu Mono} - """ + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', } +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, "Visual-dialog.tex", "Visual-dialog Documentation", - "Arnouts Timéo", "manual"), + (master_doc, 'Visual-dialog.tex', 'Visual-dialog Documentation', + 'Arnouts Timéo', 'manual'), ] + +# -- Options for manual page output ------------------------------------------ + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). man_pages = [ - (master_doc, "visual-dialog", "Visual-dialog Documentation", + (master_doc, 'visual-dialog', 'Visual-dialog Documentation', [author], 1) ] + +# -- Options for Texinfo output ---------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) texinfo_documents = [ - (master_doc, "Visual-dialog", "Visual-dialog Documentation", - author, "Visual-dialog", "One line description of project.", - "Miscellaneous"), + (master_doc, 'Visual-dialog', 'Visual-dialog Documentation', + author, 'Visual-dialog', 'One line description of project.', + 'Miscellaneous'), ] + + +# -- Options for Epub output ------------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = project + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +# +# epub_identifier = '' + +# A unique identification for the text. +# +# epub_uid = '' + +# A list of files that should not be packed into the epub file. +epub_exclude_files = ['search.html'] diff --git a/doc/source/images/visual-dialog.png b/doc/source/images/demo.png similarity index 100% rename from doc/source/images/visual-dialog.png rename to doc/source/images/demo.png diff --git a/doc/source/index.rst b/doc/source/index.rst index 18911e1..ef187e9 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -9,7 +9,7 @@ Welcome to Visual-dialog's documentation **Visual-dialog** is a **Python library** that allows you to make dialog box in a terminal easily. **Visual-dialog** uses ``curses`` to display text in terminals. -.. image:: ./images/visual-dialog.png +.. image:: ./images/demo.png :align: center **Features:** @@ -39,22 +39,12 @@ Getting help - Report bugs in the `issue tracker `_. .. toctree:: - :hidden: - :caption: Introduction + :maxdepth: 3 + :caption: Contents: requirements.rst installation.rst - -.. toctree:: - :hidden: - :caption: API Reference - api.rst - -.. toctree:: - :hidden: - :caption: Meta - faq.rst Changelog diff --git a/visualdialog/__init__.py b/visualdialog/__init__.py index 3b4b4bf..2b528c0 100644 --- a/visualdialog/__init__.py +++ b/visualdialog/__init__.py @@ -1,9 +1,9 @@ """ -A library to make easier dialog box in terminal. +A librairie which provides class to make easier dialog box in terminal. """ __version__ = 0.6 -__author__ = "Timéo Arnouts" +__author__ = "Arnouts Timéo" from .dialog import DialogBox from .utils import TextAttributes diff --git a/visualdialog/__pycache__/__init__.cpython-37.pyc b/visualdialog/__pycache__/__init__.cpython-37.pyc deleted file mode 100644 index aee828dcb7a8d5ae3070ca2f9359e7416d8fd40e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 381 zcmXw!y-ve05Xa**O-i7B2D8zDW?*1JK&vh+NQjCd%i-2mbE*?Z{wRev>TAGTWbMQ& zFyW-|m+tQWvvePx4Tovqh38MNpS~Vi_*dx7y$^HY6A$u$lbrA{4|$JAvR6iVL;~;o zvR}q|?0x(kjFXRtbV?;IHA9Vpz7@DCXsy*2xiEAknK9HVTC$Cxf*BN=a%57i=~C?| zR@4ezV#VYnU0-!N+_{lIVX7y662q&;wt(^mCmRsuWp`*<6^7 rRdx1)#xZ$$Y|geF{Q&@0XaOemdo#Ex6?anHwj)hDFbX3Qg=gd!#GY+8 diff --git a/visualdialog/__pycache__/box.cpython-37.pyc b/visualdialog/__pycache__/box.cpython-37.pyc deleted file mode 100644 index 9de17d94230f8f33a2dcdb1ab5ab58827c714048..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6486 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 wS79vT%v51D>e`Cd%Obw!s4h{Sg6R?V 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 axN^8}0_g63pIV>Gi#PFe9n(e)feg zg0mowwulHt?Tm=psTVgy{TXRRe?dgj3d+WoR8n(W=rVYc8C!O3%ru zze!FhU9xNoLx;HK={hXaH8hZT^7i5Cuo%hJakjcC&Au(h zt1mJ$;rRoR@w^zU9=OqLFtJ%~+T-K5jqU(4cr?~)EeK)|S3pnhCnLTu6Sq^ln-?k1 z&02fms*wi32{l&(S^AJuz~i7eP>AOWz$KU{Z(IucDNGpDCyAD*UWR9igLy+0Jce%C zU?hpDATekm_GJLdq%&oPl`i9&sEg=~#C6etqiUA7p35V3gE6gUBEmW zam5ExGsu1Q%F>B867+&!`E1!j11OjgJIqvv?N*MvI1Ua%IiV=eXg2`0Th{TC{9lAYI&A%o9px3v-281o@JLOg*rW zGbdcoi4O;+Z^LIO2!WA#zpNQ6Osd_alW%35PNF2qRA!Up5k_?)@oV(t?o}4;x!09D zh29&ujDb1&cuMxjCfQkj4Y2m;FW!{?O5sjLKR_`AaNfz&_1R$>Rw&7O$L@iZxop8g1j0S>PdZX}6RP+^j!3qBR&V2Pfr zyu*0rmbya$U#yt_Mmd;K4z5xDZZk7jkpCybbw{}0Z-q0)sLwJy;Q2(B4Xb%7dpzCO zD**laK|to^5oX?{(2>M*csKKv*LEu^VTs}+nIvU1Nk)a3Lg1~Fi Date: Thu, 18 Mar 2021 23:41:23 +0100 Subject: [PATCH 34/52] Configuration change for doc in conf.py, fix typo. --- README.md | 4 +- doc/source/conf.py | 196 ++++++++++----------------------------- visualdialog/__init__.py | 4 +- 3 files changed, 56 insertions(+), 148 deletions(-) diff --git a/README.md b/README.md index 46dfe16..601e5e9 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ def main(stdscr): title="Demo") textbox.char_by_char(stdscr, "Hello world") - + curses.wrapper(main) ``` @@ -107,6 +107,8 @@ cd Visual-dialog/doc Once generated, the result will be in the `doc/build/html/` folder. +You can also generate the documentation in **Latex**, **Texinfo** or **man-pages**. + ## Contributing We would love for you to contribute to improve **Visual-dialog**. diff --git a/doc/source/conf.py b/doc/source/conf.py index b27f536..9afc33c 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -1,179 +1,85 @@ -# -*- coding: utf-8 -*- # -# Configuration file for the Sphinx documentation builder. +# 2020 Timéo Arnouts +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. # -# This file does only contain a selection of the most common options. For a -# full list see the documentation: -# http://www.sphinx-doc.org/en/master/config import os import sys +from datetime import datetime -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -# import os -# import sys -# sys.path.insert(0, os.path.abspath('.')) -sys.path.insert(0, os.path.abspath('../../')) - -# -- Project information ----------------------------------------------------- +from visualdialog import __version__ -project = 'Visual-dialog' -copyright = '2021, Arnouts Timéo' -author = 'Arnouts Timéo' -# The short X.Y version -version = '' -# The full version, including alpha/beta/rc tags -release = '0.6' +sys.path.insert(0, os.path.abspath("../../")) +project = "Visual-dialog" +copyright = f"2021-{datetime.now().year}, Timéo Arnouts" +author = "Timéo Arnouts" -# -- General configuration --------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -# -# needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. extensions = [ - 'sphinx.ext.autodoc', + "sphinx.ext.autodoc" ] -autodoc_member_order = 'bysource' +master_doc = "index" +source_suffix = ".rst" +autodoc_member_order = "bysource" -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -# source_suffix = ['.rst', '.md'] -source_suffix = '.rst' +version = str(__version__) +release = version -# The master toct ree document. -master_doc = 'index' +templates_path = ["_templates"] -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = None - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. exclude_patterns = [] -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'friendly' - - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = 'sphinx_rtd_theme' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# -# html_theme_options = {} +pygments_style = "friendly" -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# Custom sidebar templates, must be a dictionary that maps document names -# to template names. -# -# The default sidebars (for documents that don't match any pattern) are -# defined by theme itself. Builtin themes are using these templates by -# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', -# 'searchbox.html']``. -# -# html_sidebars = {} - - -# -- Options for HTMLHelp output --------------------------------------------- - -# Output file base name for HTML help builder. -htmlhelp_basename = 'Visual-dialogdoc' +html_title = "Visual-dialog Documentation" +html_theme = "sphinx_rtd_theme" +html_static_path = ["_static"] +html_show_sourcelink = True +html_theme_options = { + "display_version": True +} +html_logo = "images/visual-dialog.png" -# -- Options for LaTeX output ------------------------------------------------ +latex_logo = "images/visual-dialog.png" latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', + "pointsize": "12pt", + "fontpkg": r""" + \setmainfont{Open Sans} + \setsansfont{Bitter} + \setmonofont{Ubuntu Mono} + """ } -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'Visual-dialog.tex', 'Visual-dialog Documentation', - 'Arnouts Timéo', 'manual'), + (master_doc, "Visual-dialog.tex", "Visual-dialog Documentation", + "Arnouts Timéo", "manual"), ] - -# -- Options for manual page output ------------------------------------------ - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). man_pages = [ - (master_doc, 'visual-dialog', 'Visual-dialog Documentation', + (master_doc, "visual-dialog", "Visual-dialog Documentation", [author], 1) ] - -# -- Options for Texinfo output ---------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'Visual-dialog', 'Visual-dialog Documentation', - author, 'Visual-dialog', 'One line description of project.', - 'Miscellaneous'), + (master_doc, "Visual-dialog", "Visual-dialog Documentation", + author, "Visual-dialog", "One line description of project.", + "Miscellaneous"), ] - - -# -- Options for Epub output ------------------------------------------------- - -# Bibliographic Dublin Core info. -epub_title = project - -# The unique identifier of the text. This can be a ISBN number -# or the project homepage. -# -# epub_identifier = '' - -# A unique identification for the text. -# -# epub_uid = '' - -# A list of files that should not be packed into the epub file. -epub_exclude_files = ['search.html'] diff --git a/visualdialog/__init__.py b/visualdialog/__init__.py index 2b528c0..3b4b4bf 100644 --- a/visualdialog/__init__.py +++ b/visualdialog/__init__.py @@ -1,9 +1,9 @@ """ -A librairie which provides class to make easier dialog box in terminal. +A library to make easier dialog box in terminal. """ __version__ = 0.6 -__author__ = "Arnouts Timéo" +__author__ = "Timéo Arnouts" from .dialog import DialogBox from .utils import TextAttributes From a1b434e02e53b70638690d6b2707567fce670822 Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Thu, 18 Mar 2021 23:43:24 +0100 Subject: [PATCH 35/52] Fix project description for textinfo documentation in conf.py. --- doc/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 9afc33c..7c09174 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -80,6 +80,6 @@ texinfo_documents = [ (master_doc, "Visual-dialog", "Visual-dialog Documentation", - author, "Visual-dialog", "One line description of project.", + author, "Visual-dialog", "A library to make easier dialog box in terminal.", "Miscellaneous"), ] From 023980506bc8b36661425a6a1516d3539680c334 Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Thu, 18 Mar 2021 23:45:39 +0100 Subject: [PATCH 36/52] Fix fonts for latex documentation in conf.py. --- doc/source/conf.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 7c09174..355db50 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -60,12 +60,7 @@ latex_logo = "images/visual-dialog.png" latex_elements = { - "pointsize": "12pt", - "fontpkg": r""" - \setmainfont{Open Sans} - \setsansfont{Bitter} - \setmonofont{Ubuntu Mono} - """ + "pointsize": "12pt" } latex_documents = [ From 487cb6bbab8b1debfaf1d8a619d83ca1001b2c64 Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Fri, 19 Mar 2021 14:48:09 +0100 Subject: [PATCH 37/52] Fix in setup.py. --- setup.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 6af0460..f19a113 100644 --- a/setup.py +++ b/setup.py @@ -1,18 +1,21 @@ from setuptools import setup, find_packages +from visualdialog import __version__ + setup( name="visualdialog", - version=0.6, + version=__version__, packages=find_packages(), - author="Arnouts Timéo", + author="Timéo Arnouts", author_email="tim.arnouts@protonmail.com", description="A library to make easier dialog box in terminal.", long_description=open("README.md").read(), include_package_data=True, url="https://github.com/Tim-ats-d/Visual-dialog", classifiers=[ - "Development Status :: 3 - Alpha", "Environment :: Console :: Curses", + "Development Status :: 3 - Alpha", + "Environment :: Console :: Curses", "Intended Audience :: Developers", "License :: OSI Approved :: GNU General Public License v2 (GPLv2)", "Natural Language :: English", "Natural Language :: French", @@ -23,4 +26,5 @@ "Programming Language :: Python :: Implementation", "Topic :: Games/Entertainment :: Role-Playing", "Topic :: Software Development :: Libraries :: Python Modules" - ]) + ], + keywords="curses, ncurses, ui, dialogbox") From e1589352ddf275525f7286b79df55fc7f29c5d01 Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Fri, 19 Mar 2021 15:26:59 +0100 Subject: [PATCH 38/52] Informations in setup.py are no longer harcoded. --- doc/source/conf.py | 6 +++--- setup.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 355db50..ed2f467 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -21,14 +21,14 @@ import sys from datetime import datetime -from visualdialog import __version__ +from visualdialog import __author__, __version__ sys.path.insert(0, os.path.abspath("../../")) project = "Visual-dialog" -copyright = f"2021-{datetime.now().year}, Timéo Arnouts" -author = "Timéo Arnouts" +copyright = f"2021-{datetime.now().year}, {__author__}" +author = __author__ extensions = [ "sphinx.ext.autodoc" diff --git a/setup.py b/setup.py index f19a113..f95b201 100644 --- a/setup.py +++ b/setup.py @@ -1,13 +1,13 @@ -from setuptools import setup, find_packages +from setuptools import find_packages, setup -from visualdialog import __version__ +from visualdialog import __author__, __version__ setup( name="visualdialog", version=__version__, packages=find_packages(), - author="Timéo Arnouts", + author=__author__, author_email="tim.arnouts@protonmail.com", description="A library to make easier dialog box in terminal.", long_description=open("README.md").read(), From 5cd33c2e5228dcfda11bba5b450c068029b7a56b Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Sun, 21 Mar 2021 20:50:04 +0100 Subject: [PATCH 39/52] DialogBox arguments are no longer hardcoded, fix in docs. --- CONTRIBUTING.md | 1 + doc/source/visualdialog.rst | 5 +++-- visualdialog/dialog.py | 35 +++++++++++------------------------ 3 files changed, 15 insertions(+), 26 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a1aa582..89b9309 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -36,6 +36,7 @@ The list of versions and their changelogs can be found [here](https://github.com ## Repository Structure The following snippet describes Visual-dialog's repository structure. +This tree does not contain a description of all the files in the repository, only the most relevant ones ```text . diff --git a/doc/source/visualdialog.rst b/doc/source/visualdialog.rst index 4352c3c..dda9e1d 100644 --- a/doc/source/visualdialog.rst +++ b/doc/source/visualdialog.rst @@ -7,7 +7,8 @@ Text box Two **classes** are defined in these modules but only ``DialogBox`` is destined to be instantiated. -.. automodule:: visualdialog.box +.. autoclass:: visualdialog.box.TextBox :members: -.. automodule:: visualdialog.dialog +.. autoclass:: visualdialog.dialog.DialogBox :members: + :show-inheritance: diff --git a/visualdialog/dialog.py b/visualdialog/dialog.py index bf6a0c2..c1f2253 100644 --- a/visualdialog/dialog.py +++ b/visualdialog/dialog.py @@ -44,7 +44,9 @@ class DialogBox(TextBox): :type end_dialog_indicator: str .. NOTE:: - This class inherits all the methods and arguments of ``TextBox``. + This class inherits all the methods and attributes of ``TextBox``. + You can pass all the arguments of the ``TextBox`` class to + ``DialogBox``. See ``TextBox`` documentation for more informations. .. WARNING:: @@ -52,33 +54,18 @@ class DialogBox(TextBox): not affect ``word_by_word`` method. """ - def __init__( - self, - pos_x: int, - pos_y: int, - length: int, - width: int, - title: str = "", - title_colors_pair_nb: CursesTextAttributesConstants = 0, - title_text_attr: Union[CursesTextAttributesConstants, - Tuple[CursesTextAttributesConstants], - List[CursesTextAttributesConstants]] = curses.A_BOLD, - downtime_chars: Union[Tuple[str], - List[str]] = (",", ".", ":", ";", "!", "?"), - downtime_chars_delay: Union[int, float] = .6, - end_dialog_indicator: str = "►"): - super().__init__(pos_x, pos_y, length, width, title, - title_colors_pair_nb, title_text_attr, - downtime_chars, downtime_chars_delay) + def __init__(self, + end_dialog_indicator: str = "►", + **kwargs): + super().__init__(**kwargs) self.end_dialog_indicator_char = end_dialog_indicator + self.end_dialog_indicator_pos_x = self.pos_x + self.length - 2 - self.end_dialog_indicator_pos_x = pos_x + length - 2 - - if title: - self.end_dialog_indicator_pos_y = pos_y + width + 1 + if self.title: + self.end_dialog_indicator_pos_y = self.pos_y + self.width + 1 else: - self.end_dialog_indicator_pos_y = pos_y + width - 1 + self.end_dialog_indicator_pos_y = self.pos_y + self.width - 1 self.text_wrapper = textwrap.TextWrapper(width=self.nb_char_max_line) From 88e38ab5a7df6ed817f73d82d5c15a325b864bf5 Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Sun, 21 Mar 2021 20:57:07 +0100 Subject: [PATCH 40/52] Start writing documentation for the words_attr attribute. --- visualdialog/dialog.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/visualdialog/dialog.py b/visualdialog/dialog.py index c1f2253..94ec94c 100644 --- a/visualdialog/dialog.py +++ b/visualdialog/dialog.py @@ -136,8 +136,8 @@ def char_by_char( attribute. This defaults an empty tuple. :type text_attr: Optional[Union[CursesTextAttributesConstants,Tuple[CursesTextAttributesConstants],List[CursesTextAttributesConstants]]] - :param words_attr: TODO - :type words_atttr: TODO + :param words_attr: This defaults to an empty dictionary. + :type words_atttr: Union[Dict[Tuple[str],CursesTextAttributesConstants],Dict[Tuple[str],Tuple[CursesTextAttributesConstants]]] :param flash_screen: Allows or not to flash screen with a short light effect done before writing the first character by @@ -282,8 +282,8 @@ def word_by_word( attribute. This defaults an empty tuple. :type text_attr: Optional[Union[CursesTextAttributesConstants,Tuple[CursesTextAttributesConstants],List[CursesTextAttributesConstants]]] - :param words_attr: TODO - :type words_atttr: TODO + :param words_attr: This defaults to an empty dictionary. + :type words_atttr: Union[Dict[Tuple[str],CursesTextAttributesConstants],Dict[Tuple[str],Tuple[CursesTextAttributesConstants]]] :param cut_char: The delimiter according which to split the text in word. This defaults to ``" "``. From 777e8722dce201330fa54014ab42f6ff3a9814b7 Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Mon, 22 Mar 2021 08:54:51 +0100 Subject: [PATCH 41/52] Add kwargs documentation in DialogBox, add doc/build directory in .gitignore. --- visualdialog/dialog.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/visualdialog/dialog.py b/visualdialog/dialog.py index 94ec94c..530012f 100644 --- a/visualdialog/dialog.py +++ b/visualdialog/dialog.py @@ -35,6 +35,9 @@ class DialogBox(TextBox): """This class provides methods and attributs to manage a dialog box. + + Keyword arguments correspond to the instance attributes of ``TextBox``, + documented below. :param end_dialog_indicator: Character that will be displayed in the lower right corner the character once all the characters have @@ -45,9 +48,6 @@ class DialogBox(TextBox): .. NOTE:: This class inherits all the methods and attributes of ``TextBox``. - You can pass all the arguments of the ``TextBox`` class to - ``DialogBox``. - See ``TextBox`` documentation for more informations. .. WARNING:: Parameters ``downtime_chars`` and ``downtime_chars_delay`` do From 3e6574c1e2a4641d4c1477b9858220855702d041 Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Mon, 22 Mar 2021 09:56:15 +0100 Subject: [PATCH 42/52] Many fix in doc, correction of warnings when documentation is generated. --- .../demo.png => _static/visual-dialog.png} | Bin doc/source/conf.py | 4 +-- doc/source/images/demo.gif | Bin 137105 -> 0 bytes doc/source/index.rst | 2 +- doc/source/visualdialog.rst | 18 ++++++++++--- visualdialog/box.py | 8 +++--- visualdialog/dialog.py | 24 +++++++++--------- visualdialog/utils.py | 2 +- 8 files changed, 35 insertions(+), 23 deletions(-) rename doc/source/{images/demo.png => _static/visual-dialog.png} (100%) delete mode 100644 doc/source/images/demo.gif diff --git a/doc/source/images/demo.png b/doc/source/_static/visual-dialog.png similarity index 100% rename from doc/source/images/demo.png rename to doc/source/_static/visual-dialog.png diff --git a/doc/source/conf.py b/doc/source/conf.py index ed2f467..ae0ab39 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -55,9 +55,9 @@ "display_version": True } -html_logo = "images/visual-dialog.png" +html_logo = "_static/visual-dialog.png" -latex_logo = "images/visual-dialog.png" +latex_logo = "_static/visual-dialog.png" latex_elements = { "pointsize": "12pt" diff --git a/doc/source/images/demo.gif b/doc/source/images/demo.gif deleted file mode 100644 index 479ed2f4c1be0bdf36da1724570242b2d517f1e6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 137105 zcmeFZXH=7IoBn&Jhcr?lK!DI&fY3uv=$%kRx-u3*qUmsfz*Ir+T2?1>sL*XYBiUHOE#y<-4k7EKL*8mWb z=G4*HosA@E1`l>L_2%Q1T;TkzcrCx#C* zBZumoFPn)OXz1;rfV(f#qkm1W`o?S_PJyh>{yL#d6aJ51Fi;=4r-`{JrEtYe?*7@NGj}WT3g0$T>bi9blml*$IWZsK2BbYe>ryZ`j5}k zy=%qtp0{rN{5t#K^p<iyiWrw?UllX&sJ|{T zd=&e-l=ZIlb(zKY+1KT4xW*fSoy4{`6;7(B-c-67e|Ym3*FocLm9O8nw+H>BPrW@9 zy!*r3!(n+E@2aB?ZF^S}d-Bw~BMFy3ysJ&Vqw&5j_0hKX^=a=;y>H0;{^9*m9$a&_ zF-Ib9wy8k1ZT47+@!afjfrI9U<|@Cq4=1Xl+diDE+dcQ8r7=%)uC@74+}x?wlWlWt z?U(1~POo*`(frun`6%w=nXY$jAJ5+SKKJpQ5Uw@f(I*iYx9>sE?i%{qxJd5@}v0AH&@=B z{(Nio`^V3>0R&@F2$f7&>_)1!FZN(e<`;YMj*O)~^5%r4erinn(jD5K`K1ARKI6-v z^5KLpchy_kzYH<1%zqhX4lurs7(Pz;I?8(A{&mda$NblOY=ri*$WAhGdE80u%<_FV zlTXVJxQ^Q2CVV$1etYO2bLQKl;60zdJr2v){yrIXIPv?F*p@TjpC(-S^!-`#fcB56 z)W?ZGo~OM(^TXps=8sQ5Uh)t+E7LiWNh>o2YG+qom6$B7ycRg>{CrcjIqB!y>X@@X z-_`9|`1!suU+33s^WmglA6i?^{+esQvheF;$AHf7`Oe2lzcWFgf1EGae>h+HU=(=c zC?cbPsHiAF#XS6Sj$9`ZPy~S@gUrm$k*bWUswzx4L*K(MJa3O_mnYy1fUFr3b?Vj$ zegKA4P*8v>18iS-YY(eyYH7kUV?kUy0~-gZ5`{n@=tdbeH8siEfVf<)`C*;J#6*v~ zapn!)XbhN9ETeZ*hn5Udi^!I@)>cqd^b&gD59}o?!Wrod)8+Ss zzy)A6GWSWg5$Kx%4o`(-Kvg~e4Wv6mn8fBmgq5+;=9FU}}o0}Wa+kjx^ z=5$0syNc=g+QV+xOy+12NSDNI+C?+5l6Fzt-m_0_XDWn7(($!m#@NF$BrGq5Yu`{{ z_$BHEO0$csxgR$}eS@qo+h|_0w0Ch(%4T4#lr_t|Jo2_WcxgxlCsQiX_WRNZSwuIH z2gH^_*M~dyav~=8x(|mLow7%hXc!E}O61ARRq%a~f+3=%Qgl%asnDstx`(W^_tBJe zsSfp#5VRQgAjctLo6qtl;toq~Jsm&|z+ zx|-QVQ}S^!^P>pFNZ>d)7lNaKW)Vj332bXv0C-1hb7H_hqitmv zbTdAevNjBLgZ-5eXK7+I_%h<=&ob9#??zJcXcGv`KKDwS&Y(A+4S zr`h5VFn&O0yS1k8zr6Wtz`EGq`to04c84=%6E)8p1z*+BUsgb)oBJQha;IY^9%0%=^n)Pp{YZYw5-=$3=g>+3>dZ$*t9t zcjp{{DFNKfxKp15$fde^Ynsd+bL}Z`fF=u%VC%dPjM)x13|?7*WHQ&Wv&Z?;q>yb8 zP+%|wB8HUcMN7*YDp_g2Y2P`vGX1)n{eCssMdLDFN#AXX{k2=+NnLQIu4vhZhu%X2 z*^WFIn+h0({nRfu9i=MePDaVR#-J*MtKteJ{*}F!Zc&g2Sd1|8o(YrY?!Bfhf*Lqj zHLUc)AI-j1?X@EdnUF+2`KqZe~NjdP6aEm*fHY zufx+4F^Oq+=4{@$>V*Y;;b}f&%|JBDOL&)`0_w-g)Q`U*{Dlo+y~+kP*Ftw$pE~E! z)q5@|Z-w1m9O0q#vRPOxPMr$R0>Fkl*eq>!_88$ob%kU9dh;ugl~gD9z@P6i(*~=U zOZ=U1p#A7QXbM=oIZOPAdKO8pyHIK3TsXL5%Nj7r2ie&1fgCtu^^o|(^EgHQ(+yE) z9R+gguPBV^X^YlzsPS?m1(>ukU=U#50!5sHj1xj)21QDE+p)2E4RpE|DuAz{Od?3e?cJGHtuIyw)KRnAjAzqQl0Hrp8h^~ z;KnP&@334;3$H@63GdQPA33((3Rs7*R}G)mR(c>J0pIwR4Q@%0^DJ{ZdsDk2JdxTj z4Kx<-<0{B&2w+y5!wvJBU(v2~31Q$Rq(;{?xQm^QXi;-N^2bXN=l~gCx;uK{%?zcq zF^hRe>Pl9fLA|y)Ox$}~EXLnPJfNTt9VCDkN?qEID&AuGj8IGf!lh0k?Q~mb%0uSC z;3cOlO`#(~mDwb=H*E`;=EY}EW#Pc|I|jZ0sO6_P&*5P|WPJuCkr^S=mX-s%wF9-o z?5}2(Z?D@mD=!>Ox=07YYM%{m^GQ9#+quZL8-5CcMpBg$kA*?$hZJ(h%z9;ZQgbbQ z#`B{s=xu)$yd|zR@)I_?i+-oI7hfDe*^g1m7+M;jmDynv(J=#93R>9rr%`FCk1qN| z*@)lrD{yY-%3b&GvszhtZ0W7r@xWr69V&>IAa|J_q~045uPLZK0=`g5L8$Ts{9!q^ zbge&Je2)FnwQL%>7nzTxdKGY6k4b9a9duJ$V3gob@)rf#UtgFAm~7jfKmGaIuadE; zBR#T^ahS#I4FBi-$FLuGHZgj<+=kT+Bm-LLST28#FWNO(iZK%F3Hf~4mtM3_X7A_+ ze=?hXAz<1DqZwb`S(7c%)tg&+a|L>0eH{sm;$4H#9~BC92O^cbQXqq+x#6aIWPC;&qU$&DQR?&@r;hv2@$Ed4=^L_Tn+7M;$Tq4K}N%=q2noRW!aSq&qNFz?yV z#2oyjQ)=Syvq(pT)P4K?@&Mm;VOowTUQ%F#>!jnTG&(7i-3#;87D%KvL1V*SNMpV{ zm7BMbm7jF<-Lz+V$Kl$kbC)RXv0{|4RhpRMjV-FZDv2Wh(42-!6C6t|<*QJI>I%{m%B;nj7R-4jy9jNCCWEy1C1c@G@wbYa;M^tg-bOYwVRaz$KDsev+RxTm#s^9_B?sHie1%R;A&*&|-MZ zGM(QnfU&tiEYo5Co&Cq(@6Qk40SK`KK>Q^gw~&o@55ryb!+o6=(-ioX^2FXkaFukx zO$5gPs75?^=N0@VI;utq^s*7I$wtI2Vja7-gU_dz(m`*zxK{=!cK~<-;La{$qy2FW zVW7T_Jp2yGv`GAIBfLQfyC=f!TiGXy&JJq{A$pZDQQAl7ox;S#s75oudLh_6BxGAB^C!U{>_`*T(# zB?hQn)y4bH7Z;Bg-|CVSwo-doC3|;hI&?4|-&cQ}Tawj;RuqAcje`{%(W2_or(LB} zlcg_KN~fu1uUKVo{L9{@mCaU{&2^Q{PnIqC6AWH~zOc%d{mZ|nm9JEn|LQ7Voh)Bl zDF>tkU{e7!Kmgw>K-LIQR|S|S0^CmlK0t79mZB|0xY~fJH5JlVD`-zDejXrnH>N} z0L0cDio1F!;mM(-pNEpA4yTwNP7OG`d+*`2n#1W=4`)6(y#ME6o>Vp8v^poCI`7os zCVj{XS0WPt8!VQc?beDkl~A0n7Ce!@aiKcOtEPN!&FPAot=%;ZKWok$tEt*nbG$~n z@oG(L&5@S#M_QiLoZTkfp&@b1w6-(gNYj%eU8d5Pq$I99sl9cowq2vP=c>-hZM8S{ z);+eVJAb#XyRuGrwN50aJ9et>PC)&mHB;cem_+xf`e#q-w=C5&&LdD<*d{!P(1<*T zYzSSdec@cML4czGvBF02D~)1#+}s5XiAB?fFL&|bVGU^_5R?NNY6MUs$R}rfcn2hJ z5$d@J)@5Q>pBxQaIvUDpkT?NgnV>W_zEudqh9NJ~gE1hOIT!Yki?yR;uk)HBo0>wp zo4`dVmPkz42wk)V1VYT~MHIXm&us*%rs4aNoAjSj42LMj*YM%I<63mMg&E!o)f~-i zMuK2a0PfUw!nKIvevRVUhW9Fh_ynE+?>Ol>k)j+k&|ndzmC5a;_{j?G(7goqS%#1NLn&v^&%qV*NJ_n=_98%G8DE*XMdi{f%dhGbAxL@K4b0?LKAnTd94*{^S zMciaE&iR{o)!YdqVao+gN=*@^HXWaBhJ46{)pX!$*kXl?U}PiiIvW@lp2-m)$ZXL5 zM)B7jAitaz^Tw8ur|sOO_RWMdaJ@6xi()H9m_a5?$OCGGxc&IEG6KXGC}0(cm7ybg z4z?$Cv@c$xe2Jqh2jU;NKs~uwPadEPV5W|Uz3;$9v7yM~hD}UdA<(uHaXRByM<8MC z{3gBgIL#A^wHU+UW8}GWHDTB&CQ`^d_p=ByCEW(@?!XOSh;TW*wC%J>(Mj_;G{8k) z5}vH-fYHs7(jB0x4$y}bKai^is?vuwl&@4b~^L2C&1x{{6k^yVYw)ULG zEBlK&fuhUNfn7((g>jRc|Ae(yyHc{uDcKo8xfxw~ zb0@2widFG|untfd^9CUqhe;12^G=TN0GSTZd)}3czsZ-hC|7=qcdfNuXV0BX4s828 z4gb|Bo+LmTbu>~)@Z~3Bo}!CFZaY@`rk&nRFRz=Dh^BrQd`1{DZ{v@K+mQia{T6Tg z@vt&H#C3sP1{d>a@%E$u1Dn6)4!X@%xV_E>aq+>pz_Wh^>Ak&%$Pj@t=vN1V@MLDA z>IPrcPIG#9oz9cNmCc1=bK|K2DXC+Y!n94GAZda@>=H9z^0`EHerx&LCIMqh@DdZrt^ox-0s1xA8`QbWq)ix`AUB=bGz= zwq_39`!Hm4V~F@{=)&rq&eaQ-Glx?#!;^Q19qWeq*N27k13d}X`+`TZGe;(BM}|rU zM`*XlR!2%{qXdi53C7)9!Ka_lhMv`RKDRjj=tj>ZZA`;rj2S%ku8v$bMH1W?GaDbX zSQ=X_A$_?)S`NNvyyKo(#l2r{q}4jo+Uh-hf{3Ljf?`SVeI#VPNawuBcwB@VCE`O! zM9XnauW`d2E@VPt$Ao6Zgo``Dt)A%dXCgC?mG^@J1k4_itJr`#W&-bXm|XX0EU;ryA2iy?$dArn{J30JWb*DVP* zZ$7xKP3TU1&?`&mmwhmRCEOjoKl}$jy6^tIwNLnQ?fVa&;~zdBe>{qRQab+ZCjPnQ z_{%f+nNOnE_4v2-qW7iv5ALFm`|zLs-20q}UpjN|YY6^Z$h{x#_@CH&zb)~9ZjP;M z<3UMdkTr3b+!&%^`uUCN=#WXZnQ4FQ%x||xW~NA_L0#k;7n z$I%O8u?_FCZod0E^)4xCGP&$t%ECKWtM^hN@7MlhzTejHK7C<4Q%AJl;~h_KR>^V} z5i+}F|7^k88J`!FOp&^|&xb?%KU6n-ICASl-HQ(mYabfr=8jp-HHXff+&|aaFxPf# zuKmT_*|oWjvvZ0f^}jwGrEo#Lf{)j3eZ2AF@KtNFgr`8)gP2OH*xZq1Lp zm>*l47s-9PZ}n*+^wXpLpN2z0Xgc_koP-o}V`BR8iq*oK(1myV7iJq4=58&_zgSpU zTUeC){Ke|?a_Hyp`#-NVeExOo^XiMwYipkY-OpD&z+!mZ)(Vs zFA=Sm$Xk{q4lGfRE=k{BqP<*_TVJBfe^IplqP!*fi|WfI(+8i*Z+&6B{Gzk|g(?44 z-}nV3fKl}T;I|nNXR01`R`%I2rTS2H5Zpl9u*VVD8HC%%QjOdTHgeWJrUJ_a01yG< z0l4%AlaJ&D<)$*D^+7?qC0RPC^i2JJb@MY@C3Fx_buHBZkoKNU^mGu#lq+uuVoh<( z%Cr;?8~{jQP)wNpR4_Qy3`CLwY1x7l=>R_we?-eE!Pl_GM7cB@?4$`U-luNDbR9^c z1#On{bq1_txpgU4=WRg*WvX*1DIbptQlzIsP(&2WG|cFkfr_`eSnN*7u4F7lL&}9t zR@Lx&ZzriN0}9;g+~X*b?uc)cG0u^fVsX$!N%GDz$?coWTh+}99fIx#gCHmXO0(ai zjM@Qq+{c46Wbo#)`sD%U!KN9yC&#BYfS7mucrzEL-kRc{a`3D$((SHo8ip7{YEXWYIcSuh1DbpS*61?>k1Ro02gW4K*V2sZL39#|6m4fOQf>H!Uql z=tirTK-mW2T=o4QWY9Zk-cwFgM*}&pZ2_0eNt)g;6cHa+h*mQLXfnD^Rzx!ZgjH54 zF2yLyiD?l~iZ~Qa;vZK{e-GUMr(Nip9+n9lS}9|53%=#Dl~Tp(HMVFd;+gBpVeb-; zV&05c?;LX$)Pe(c`WS(C2`deUZWPuk9>DuzI0@C+Eo|MXRE&wHsY2}bF|L6~YaO2= zuwJLnsQFiVT&oq|X7|O@5R$kgZUs}A`QWw&pIp2LU&o}}x6CX;1#7Q2{uEgrrjdKC zZ`i!)w;5R_L0}O32ZapxWvR+~`0OeJHT@84>-B@7{~$C>65@ zeVYgh{kTpxY8>MQwz(Gdt6YIn`sE&#k2i#$#ECz4vlZa9@X#9;kPqx&v(5!;va({a zggicK?$)}SD4w&>sU!dpu2J3Ww<}>gNBZ6Z-k41*Y&;l=_2SlHENuuw;3p|_>{Z^9 zxVgXh=7I)P4R#l2H;N2jGeV2!U^q!>$86Myk4zo8@eAR)WvX|cXs$A#{Rk!fW=T$y1l+#I*OzqDy_KMJ|P+=53qsZDrt zu9$b;dCEpk6oZcK{QT_Zy~k#v^Xs#>ywow<07Uc&fv4b%qFMDr=Gr)V&%=BBZ+z%y z?^eYtC%=({T&-ifIDoT}c(^((WSW*1_> zAHtBZFWGv89PzcI!~)8rt*}7glRQK)I5U-{_w|&(q(vPmLkD2C+~ZMjy;s&|@A2`O zO5)|o`34ap`1Vvj7WM)BpsTz8Kv*w52kB!MCsb3#MN#V0j>T7cVci)}LLAA-JQI(4 zyCg#0c-?E4P?2|_`8W-kVIytB6F8uh!Ai*wbfFQ>jt}!pv zbA#-uHa%*6U$ zzGxb?wlZKUJzfx6*GTslK()p=T)g`9(H|4;X!@*$` zigOm=yRb9e)(nCdlo3b&ncOGG2+s)~X*@2stCY1Mz}q=Ni5Lx`v2aHLtz8v02plfKB3^ymlAu z5-y^r=s@edycBaoY}*8_9uMhXS75UKdPTP?o8W8gc$udiGw_4TNd3)Oz!mzEtdm4pBg#P7eF+$JR4Eal zXyjK15jFWL`r*YsE4IWamXLUep0k71jZbHId1)RFSO~4HHwQM4~EGi>viBaR_`;#p_0$>e9|L2e@C+o?SbgAuVF#ejVoygC)^H3qWXe*nWX8O%cM;ALwa^2+g9 zmYg^co`>(7;Tv=c<-V^VI*$&`2;_Fb)k-g6UUIPckyv^# z3|$da=5RTr%zh3_ijvb}4=TLK7i7}1S}>~d5-1_mpj`nGsnQ8IOpZ9Gsf-pl*=wB5 z;wRfRAs;uUDbRF2-=qx_cwIZMtgS1+?kV(P9>reZMDKvQ%>VBHK4N2q6c#`x!^PBE zv!x}hAPD>+w~i=feQ zJGx+?YA#gMjW6Zmhz!48SL{5gBo?yPqbz-6SvY@;Ud(h-Nvc5Yn}4+cd@{43&g>y| zf=OK~`ATv;u>b0FOi6Vg=48e9Y`g?yc`z5{*CCE!yvFUO#y_8Vg*2lpqZU*OluMS% zWu^ecu@!_W?!qrf*M5SM+8`?K06=3LyZlwqBR7#;4Czut9c?tX1-RwvQDFo?U}I?~ zLd}+2>0E3boYz|8!!o}%V!i`B%k#Y4fOg<%wXm;6K_4ww2Q*!*?*DzxbN$z|^|h6I zx7U8$TVMOVvDiiBfmuALKM$V9Lss)pT|CSr54Xa@Q?rPyEV6%=L|PWLI!n4Mi#C}h zw~|Gt@)cQpWq-bE8ehGdui3?CO!9SB_)KcHJ}cYMKifDhn^m1{)|G8BnQgU_&8Ftq zuyXADa~#rgoT_tNx^mnmb39gZxYS&4R<5squ3uWNe|2tPS8niRZsx(Z4r3(8gs1k}PxR$-NY;i0s`>gvKHU4^Hz;NG)^b=0C`tfFTBqLXPwt<^q5D55OX6Y#4YFSk^XFvC+)2 zVJG0LX?xH}Qf{Nqk_P-!WpV-u*_o~{0|A{)8)lofn<43_0rqt@_6b50$;QWIAq<8x z%hBA+9!0{C%&1yhzyL!Ax;0GSd#B1J1L&?@K#YS#iU45SIb4rWJ77)a7lu5{))}*Q z5;{8UNcMZ-LGyJ*Dw7Q|`k&b{J6=`GvxVcH$x>=vs)pRieI9nNM$&`})U{{9ep3Q)NOu)S@8Yj5$Dm0~} z08f^d(vSt|5jPfhu!^SgyZw;cc2H#$G@Ets@hDDr5IWGt;*yPR7z?W_EgS6u&}eWp zni08k^YZ46mJk=84>NV6xJ7CeaiL0DM#1vLl2jNOMYXd8p=EGeV=*cKCpH$Bn&LR) zEoI0hI>?f?T3~nQav#QkwDk1~c$|y+5GR$uBPkL_DvVT$YFr4bhl$0kvba$dNW+cvFWK9CS3&eDvU3)ktR6EhF`cgJqDV8(viv21_w zz-S0j8Bl;Ia%1QEKWpRzgLp zG83bxggY#$8fZ_=*hP+ak=@6OI=$a>S0tc=HVN^NRV1l+`x51(L4ICJy8`7^;bOLI zz(9(kq-~a~3SlV8mz7Gl@lp1m9WAlc0hByIe6BlbUZv(|&6aEKY~G5`xW zlq1%t1l|{UG84H7?LYECOrJ}|fq?gU3#*a6+?`W*!!%Uwr{8B@rrH(Qq@?i(c$l*i zR~5DG>pi3OH#R(CA;P1)K7&1>0W%Qr|a5iqvkX-0^baC zY~W3_w2zC?z4YOw;QRDYT8{X+AN4*09hv7Y0(^^dm6J^r-8@t?6(bO#!S-5}&FKEP z#LOqiJ}@VleXYWje3y=Ic|Esh*oj7aV|(MfWs^v3B`?O_S;nFna|Bk?h^LePh;(<1 z5&L>r>$4HtYy=ijCF}%SnLR9rH)jsZX)+D(uoLaPhP|uE*@ITUI~Ckn{NHc$Y%-ZU zf6A1UkGravF7t|{4!*Z%b8gf72CYAr2&*7fbxpRk`jXIJ;kS@E-7;Ca{W-FZBN4wp zJ^mwCzSvAUDg)&68pqVMMYd&Tz@(`Fl80|9Qz-8g^(;&IuV*awYFW!@tp|6PD(~(% z)}vAoOzKzuD4trt>W|u)i~hWYnEQ(*BmZT@ z>I~)4MO!fjUThPY2R|r4V7+dcS0-2DCTZEVf&1d7Qtc8lMJn3-_y96i=o_Nx}m{oCIEQe(WnHRvMI; zF65bv007g8jh$i_t3s1tD%JK(47*_BPqvs_YXjS(+Cn43o&fLeb!kf$M5bn?V8d-W z$2ze5Mlj;2ldb6rFMk3=7n4rzHIESF9}s$>?g_I9`aHbVFFl4MwSWSMAEB#3Ah{W7isH}@t~g3c68Hy#0f|d%34jjl6)$4( z{$V%zF_@hQlww23eK+s=nMOo<*+q#-tRSSnY_aL?SnRoxp?FzaYY}#?{Rzx6-iO&!}xVG@@LfttOH#N5wvX3oh~7& ztEo`BDjR;A{#NZrV@WPe5#(hFNL~(n*w%PX68zzgt+xOls@_ZhD>!g)FoN2fX?Ygi ziJ_k{U^Uqbu5RR*Jk5exabIIAsjr;skKk2TvQe1nHCq#JW|jf*az}U;!|tg7m+jlD zq~LGIKzf%$Cn2OH0H&VI$uut83Pt-8SEP3k#BcyZ8iI!NKKjsh0)0xLIj zMqZ>o7;w9Tr?4EUn2&ebzJA`oePj=vj(3!nK41FXgNM9I)&$)Vlr`$)h)uD3G;b~D zI_KDyKR+`}4()*=ctSjN41%POc)CQRbsaDX?0ngtk=Js+R|eR)3xa&NH;OVp2c0}FR309AqiX*1F1PGLRg2qO&7hw{ z{$m#oT^V_+lkszSW3!}s!0nxW?az@|kBcvlJRW&xeEsKWa@obY_ipdap8Xt48@t%> zW8}Tn@1OU0xXwm|`>c)3FHwO<=P}9ASqJl9Uyr)RE}h*o`Z4tPug5~%<&J#! z`3RZclY<_YFB~47k2e4PL{xUUv&H>WT+r{QlVg`JUm5+Bl=1u7H10~*fcrv9?eD2s zk1N+6k1p)K{`>i2*_9jb-9M*4`~6~N?8>blqo4Qx{{0dV?-C+B7Wp!((@0O201WI!!VFMiH>}MyIFgCWJ$k5%TtaU^P9712rHh8Ek8S+ZEz5;SBhN z%X{x3shR`YO3>I;lPW7>c7{QEA<7bf5cj_)rBX3!T%VuhP22)TE z*xG4Dq`Q@e>saH^`{OLnxdIRr)@PT;gD^!acN|rjrb~yY(6kd$H1pHtN{T5S5->Cw zFt$*Q4Zu~2$$L2ZrWb>Kf#i+B{vxn+Ym{XQ0@Kk(;?c^% zsU#aSOhzOzBUYD}4+MDt(j*`t81UnujnzphJhj9~F+(argF=u|7n8vNnpl9VN0I~p zf)b7-%c^t|*ESVXF_Q2x18bS-Z9M>(8iAYx)y=sQKH?tFJYasYfRUp-%YntKV0Z<| z1_r9Sg-Gxcm<5?sT`R}rWZGVDLfTHntO9CeGA1nxC9iJkxfxAX0Q8(0MVWxVEp-nU zrh){lsQ(Cf{dYZoo8AARX7^uXWa<9>Hk~<7W+b9dYIrHeJ?(O;MfZSWa*9Siyduz@ zqO#Z;lg^6h{r?sta}3?#t%k&kL|O%The23{TmsxqXpw5ujOB-mw8i#yYow}@?RZXj z@|%47?h==J$u-TH>9*Xsk^jsX*$FJ+R3n&@&*c@}*nR1hFXmgD5J3=hf`HqR*jECi zBpZg67B>>{KNOPsEBoc-W$lM2%<24pO#kY^UAJV(8!1y2zlYyZ@Zd zVH>JJAr3!WASiWzMn|?>j*xLNs==ubqeg`FWZP=4EZ#MAWnRqD3+U5Jx$8LW2e-e( zP%1%=1y?%CAw-~@@sQW{VCU2LiD9SODTN+7oW*oo)7&OGy6c(SRYk6Bc#515 zE>ZJAhT->8do^~8a%@E15y20@48Jf0 zIao=?bRswY@$@zA5o*S?)e zEuW#pg*!D1l>tv7g_l^Sh_6W)0$vbA3>;Gwx5sLhW z!@=zi)fc*;JpsVn7#$q0p1$AfMfiqMalUV=R%^N8bC^jeNh>`-MrOnLSi36#o-P}L z?s$_E`6zxPfME5;QqkH?Oai;1vtF<5Q5$o)L6;3B_5HGiav=U69)N8FC=n4D6fP!p z!(bSu6##xVc5}8c8x!}d_N27%|1M4Y`+NSbz`s}EZ-?_g*5TCnBSP~uqGKQ>8n0l{ z7piTJB<#0i*Q$97#!<-^Ryg(dH*L(4(#}q@H-hqUG%#NxE_RO$4HXV8a_C|om(~RS zYl8A;&Zr@iKV7~TJ^bm)a5|vlt6Z`Xl#kv<=YSL&hiORlu73;4d)(!revTai>_|u` z2%)-sng|peWC}nMtG)bgBZbxbNbjx5A`l*t&p}Fw{}4iC|J#G|r~ea!@}IiipZ&dX zSCYd_SL09wR5W*leo{TUqz0(f0idUZ~yacGUnW$?^Am||M@YUue-ML z=J58lpR+CJ)_%=j`MmafaX@!{b@}o3^*<}`&#kYm{`kCcqc`uu{uj!{|1E_N{EK+O zjpXEYmHz!y|NZIj3jA+SfxpG;f2?@@N6O0_l>M)imxoy3-zYB{$AR>}DKGz@C@;SM z>Eg8&4*e%XV5?fkjkD%4zB%ZO)&BJVcR5 z%?&m32;6{*ZLHsc*c!5mGDz-@ef#zi3U$qHxo?DgfFND-kXQ=~OF+pS6cq>#0yaQm z01YtD*(`fXTh=Sp=7qPSuC|n!nk3tka8hgoFt))JGqSN$+XT|i*8>rBjGvg1^AA`= z`l0b)=UZE}+Rc@=FaRi8EG|iASGsx5VZ1aHrK}5LOA#fdH@3lGTCUm!QUI06%&~#G zXh8h9M&bJmf~A#9`Njbb8yqs<*D>lGruq4>tOUMSFY=c84drA^Sq|5rJ%QIl6XY?gFrta@;JU_dEEDgMR zJt0c6fDYWcf+JbL@jh(_3bz*L6U73f467Li>A6^Q3UfC_Bd?sKY$_gv1A7{nzJ1Ja4kc=Kk7Om#YhW|x@*=&(d=UAU{ z0Jd*Y$=0wId1`5C>9x|VGW=P+42d{5wNQ@TfaL}P4NXKV*HmaaNq{y7X}3B+x9oQx z*(#nM=5$0&w^@n2(?D!HX#-=Xxj9K(8NC5H^AqjXb`*2W+=teYg^*B~y_sk-86Uw` z_w}W;k*GPPUW6fX`Q(#G5qxW^+#>< z7lewg^A}I*X|-RRI2rvZrZ@U@V&|yp$HYt1p&QuB=ERQyS7r~LOp2MmZ2t6mUBA}$ z*yW@RqS(>5r#FaVyWf3wz$Pq?62wbv9>7t@4P9`xwDa9?tkVWjtQOnu1r|6Z7s`C^ z!uvq3c5?F2l*2g%hr$~%}dT%EO zsYeUX9@Na|mVzRbeb{t^qd`0GytPUlHd#sS=$mv+)icI~EOv-Xfe%84G#r5AAW7z1 zUb*;AMlWn_List7ufjF7>3JC7dEF@5MCQ#Em3_(}C*pl&g}uZG1_$#9sDg+YlNRwQ zL)w;Hclirb7q2)M+Y*MRt8G)4W)7)@inJMffNW|L6?8|kspCT4(*%f!4Srif+UX`< zoz*8fR2S+!$0x~+f9&v4HRmY5=9Oy=3+cm>T2XpDqO^Z^xB9o7WT=T_pT79ZjD+bi z)o&ut1Al4(teI`b?2WtY%a9}NSCag2HwbDAn{9oKa~mtr-2V~?1G3>2D>J%KHD)&G z*yC&Y+;i!bSLMDZg=eSGK*^=tJeff}5;t`W#F~X-RPjaJD5WTfvrzd6yDTGhMs6)0 zsff{O!exlgC}hM!2#vUM|;V9687Qk}>?N^n?zWN-v- zr$1P0Rfw6fY?cep?j|mDXZrxh2m#2*8Fs4_LHZ5qe60O+S{C^7rgl)`tPna4Dp#g` zD%fD;5(YsqyvTV@eoV1GlKLSO7(h{_s@ znpeW>O!p|ag9{4oFg!b_>IvJe zpfs~xD4F6a4J2?n1KxZhI#)x~k{-i6s$QY6dk0uO6@{wJbXfFSRW4#GEH`Dko3b^G zPuSS?N@!-w?wAHYGTu=nX$S_na~(;THc*6Slz3rBo+^70<~gNvXgVy!@Pw3koI~Fc znGrCmQv;8iRK$tG__}_2SF!|O$rrWOy7XmD>b(y&hB`Zmo+jUaia<7gKrj#T3; zLS~vyi)X=}WTxSggg#aEVK$nX3dPzf6$DS@njK_=KNK590HG4uLs7|DV$J-$3Cg_| zEa2Mq&K^XR*0ax=MTkrc7=ujXQwF&nu{gi@+|m+xbfy2n3#qk@JkC^{CjRahHe^O{ zN4iQ-s6Q2nv#B0nnF++(Og(Di)6{-3!hx6iGq<&XUQ}4|CAbp@{2)wDlEubLRep^L}gYr1$XBG7Ber&%~pDv%{H#$=IiqSXbH=a0reJ- zUFLL4jCPBgH5Doi+ekD_gVyfnm=yZ7Ke}{bxeo~DA?{>lLH+QN$PqlNBJ^>xZjxIG z;{Nl4(#nLHQW*pKw$O|f>fly?aOV4P@k}mXQh6}-Bm!Ts*XaxFqW|;FucsmS_AttO zVX!40llLwlRXnN-uA<(AQ8iQ~$FK$%9$&ThK> z%-BiDrtO^On5==jnS}Z3XM#{0_3uriwy1k=fyMzHRlG@c0EnE;L)`Sy79ctTvx1$d zr!e2K8cta%%F9oKD&2L7?vxp+PfXItb-)S@I z*MTr8IG^aa$9%9-0-P-wQS9u-rZo-rkX|KYPBr|Zeczq0^F^VUvN%oKf#gwTxaF#y zJ(Q6G$g=~dF@cM<3NrqVW1Q(iM)NerA?Y&W=%-y&`R@ucCI!j~3R#l8z)E+R`l?qZ;IPAEahF# zTgf$kfnH}Aj9&0Xh5Z>lex-W(#QLufvV8BIZ@lL9yZTw}+j9&~8_3LcyvFXk5gNhX zg>!v`mjO_31gJ%xd3-Yb6df%TLZ0LyZC?jn0K|VOL$kslyBu&wfXoYF{|9^T9n|FB z_WR!HJ-HzPLI@B@XrYDPA%W0a=p9271cXqeE1?Il1Z=>vgrXEtu>cTZ3SqsA&^4?LmLQYR9GH`* zP%$H@ybiFJE9n(U_85o2<^YMPd9R~S{aMtx#pV{rX50zMUgPGT*cS5h)JiIK^C(q0 z=2WfH+G5pu`jN)jj(-@%Dht@LpxM(}1m{g_O#$mq2JKI-VN&Kl8F2cajAA$|Cb{VU zW)xfAuAuJtkBnlSJGN)3-yCHbj{ILnG0iqrr#4Dln|gWMe_#~TdJBijw`LSTDBLwj zEa%^hV&yv>J9j#d?R0&-)BW2{uIB&JDCRY0*bt7U*s8b4fe;boA4ainyM>y2Ql0js z$L(2PzUMzOioM+f6(hVwyHmT-1_Jof8`^~z#EdX>U0lb}f{x~~j;&*~?f&X5{vBozm-_{cWA8Cpvqax(&p9C^r9!f(dD+jR6z(KfJc772UztZvTt=eC2!G&^I zD+2@P0s78J%X#Ikj$zIc!x(ro0Q0ljL6I2$I~O)8hk9^I{dlM-8S-}q`l7NSN)E!g z07{M=VxTZF9oh?Ox(ndg;z4sEPF93A662E<0C5N`1~F>_jPk~%8^PAm4yTRx2JnD8 zBWRQiIm+$xY3U2N(f4(1Tj&k7uyN2@gx)SAObQ^#7RcHNmyitXSA1nfJkS++(dvn2X-68 zz4&&1qsxKGBS(uB_!0%~p!h5S#LOwsW(?H*)^bpcl`5a=GQ{U{oZBM0Q3hmBLd&g@+3ha2~2ch+4~E#NU4c zutCfYF5#^hW}E~5A;5q#oQLp~Ag6e{0^%&a%$&v92#5>y7d@XM+eOE!S5TUAh%4@(foaj3;X!a~_NJRL9z5P;`+Czlmd5OdZd_(`k`y~m(iQCb2ps_ z&ZvoS5-xC#bM3@=;zr>d91mYlfFI@}ZZ85D@wHr+TcU)!4~I#GBe&SU-&(BdXM=UwM-~+GkR;6Rq}gIQaZ}gL=UMk- zYVKPexv#~B^=D&$6^$;4P=y@i6~)5wXRz0u*7Ga4xAGH_;6rH2tqu-cDm`m0Lp{6j zfOizEJc`ARAV4|sumW>`7GENUrLTb)kE4$i6_AT!?6&>5-So#hw;hr79)N9ptXRSC z7SE17J#r7d5UscH_t2R_`I*L+J1#=V4glWHDgAW;1_zu^{WdfXjNWS@{w_z}W-JzS zZ{{m*85dn~8k%g)1@FjEPF?cWMZ~!7i`8V@ac{w`jRIKWBGickRTsYa#GQI}@dZXHcHe%vSA5!dflVap+&8LmC#5-Fb zJYB-Zt4dG^?0p#RN&Yo&(?6u{=i$cEe7yh7Bx58OEX zejE@j46EE5cyxiWHSB1tb7ad;p@fj@gRW zkXgU}_IDQyWT1z{$dBV69ruZ>^U(7$lKppNi2~LvuzFqj20!;aOb)gGj+qc0cru8z z93oxfz~&XG2>F%D%0D7=pZ&;wrxWn*sRkyQgHdw^;{}N15j1tUph3eJ^p+|I>?GtT(QUsp!{I)7;Yj~=UE27lT-jClEAxp-}M6&V1; zi&jq(ewkTWHO`ma`@V2)@#Iz7Unk6;Jp1|3#h?GqtM88{9f}RzzkBYFh!21K_A~Xp zrJ6$b`^k>Ait)8Cx|*MK)&9Kn9e{8bxwQy_4!=Y2+_RCO?Oty@R1cJb=E+kX1u5OC z-nLudn&C@+?FPXI_?_V|1NN9F{dn5AO+Tnh*QsK-Gval~0k_R-a9U(maHm?++J*AS zx8a9__q#XHc4veQa&|^v>5BR_`dHFM_r8_;OHn5>ZXNLcA^PamksO=$O(nUc6zx=yUAYVSi0#ArqOC`FL-i1@4Jd?l3LtK=1@d?j^&bDE`n1uO3AKgQzkNf@5bviuw?f9<2 zAP2;zqak}&Y$GFX(V_;!Lo?1CiQEx?CMs^@57PrzQuo_E>`&Il#w1lowZwE+AIp5eX?vex;$*ur?BhH3c@Hr+96xjV!L;nk*@RzCH*8JFzoyn4e{jmc;n~)i-!Vy> zen#M)CLC=zmwfEe%j-#%U+vI8@BjSLK?tng#|Ue_8aNu&k&Zb$?^0jCoYb_T*6~HL z(b<;dR);Ua$yeB~MiaKKgE}qc_?8T&@9gy+GuR!Id^TfGa^qNRXH3u7t9AL&CYk$- z)?PXp9IW1Bm$j?Wuq&!J)YExG#9p1iwElkexnHKb4#!6f1Wr2V1e`M*%sH-&sAQeE z=5so??Y2*X;EY%5`Hk*BBTe$ge(!r?IQYk_O4$Xwljrm8zHiCNpZLR&-YX}4xBk$o zp6MER&Ait2qlr<|K;g|FrdIWCIe45bYH~-p`ORtRU;L~c`tYXc{{HownGe?)IhQnq z_y6tvggoi?RlU@Ap!DZZ_!{GR)493qYwLY~T6#fn{;`~J(9*c#ZF%IH^{eCOI#wpn zzCMuh2kNAI74n+KK-DKTzAo_RL!*(uzdrZ1uAG(k|D62o!yVn9{w{d8ZfZ?K{{At^ zZk5RxTSIop>&&G!kfKGH%6jf2hM?A{myu^y#@mwtI%+KmXIv$8uyJ-hvpxh9JZeKp z_H8hQ$Kx)3@}#NWxr=9>~K{-;NiNy*S(ER!|`b$S&=Ld7!OQ+Iw z{v7pn|1!6u8lKKTIQe<1-<3A;)AiNH{Cw=Kr4%7oZywzljFw|IPNf^#jroU^+}(K) zo?-0m6cE1W?yi1*20LmjAo}dx-N)-Q%rcw;;~#|YR^dz_zJQ%>)B^G)_+R2s1?QF6bouOCB3*F`Dco~!-)nxR z^Zl`qlC$^r|52am`qC-1;=#QGU#BwN{~Qb5^5x#a@9-=x!dXzOJ}*OgXL+iP3mWa` zyGadMd;{mOmgxB&jVoC`cH?2&OXhoZ5gYuxox|Jr%=a02ZwQDQ58rck{*YzEhM)}R zh^`0ohaIkL2-!3qap22*zXu{)(BK@|qkeyYNuZ-=*@t-J{D6@OYD?FU~2x?lGqIbs~v=jK=Xj*75cyR4=7(J*k2U-B+;uk6`}%R zA7Az{wXACbf&z?7ZJ=pHvO9zx7Yo?Y+S}XVk--i}11zGvoX&Aj zRe5BlJH$v+m0{*~Kfu5(8Qh8DRh1>pq_Uj&>RNDuCK*GcF!+&=ekCOpRx{_R=T{TG!&MzT z$eY|%!;K-X`sR)0a07z&_VsG{VOTuieo<90^Q!gSu8$&6HD&eQ9SUVLGr6 z-$E^*7{-Lxg;kqv#541Sfk6v<>9s8foE2hw5POizBK;EAv9cY&*v{ctVKV!R?~HB>Wyl{rB)1fY^@-^&!o z`M**=gwlK7=g=L6Zxn-Eu{!0{O;eC*aa6Uyx{iD1jg>dYZj~Hh)84}{Wy$xMfIu>l zbp|E(OP}noNMGGy(RITtk!nlXWy3q2dh;^40$zsUHsL9TUiVgEm^14VPN3F!LEp*4 zj!>sSP|F8(*XQv$&(C}V7@;lLg7>EV_@gQBzwSJSW&FLv>OuG?5^2*abjLa8rBr8} znM8V;JGjcs8-H#6afjm*EWFXICur`l=LA~u8}IPJyrbN&!=LU@6enL;yu2E>{TsI< zC=EY*ko5cu0Vs975Rb>t zQzFwPYM6F*$%#P5%~O{-?FH`eD|1(lV{iG3nk+L$Od5(F`oEh4E{OYZ#9oW$@J@HCA{%@R+LPZ}>;J9lpL| z@2PH&*kw|4_>%U3qRC&&Vea9hRQ*yj^!YC;Z8GSY{{Nlv&=*sX|J-<}yPXP0D$&G0 z*>eTU*ZwY>@?`u+Z(~nvzhF&H z+9Hg+-Po6aa1!&89{}a{ANkSoa>O#Dj@0+XV7(BD8XAGnV7lQ#b_reho`R>3Dz96u43IJhomA@PhgB50gz#(^7*M)68nguU9{Nfa<`ya;}00E zcJZp+u^LH^RCt;5#l{nw3zm%ZG9JhW{Q49mO=Vxi^=$Mef3Z}>w(HPsd$hs(6lvOG z-FZr!$Yiap?p7oGzJs#Q@Pe>8d?xksS;55c(G)a)8bH1jaGkb*Ikupus)PaHDU60N zB~I)4ojCcu8=a-KGz(3+>cP@`x;^q2-53kB=9<|X&>}aLVa`C?vfN0H`zAHuClI#E z1M5&`y><@wG}qG&S0!}1P%if+QwlH!+nU3YSfj5Roe=c?IKqpV^47*e6Aw=ELgW*h07ojgsLh&&~S#x!Vr1ScXOT;{xCE0 zq#^uA4(^Dw?9NMgihTunFSz>)$seeMTA^+zO6bp%{>)Tia=}CqxO+VsF9a_bf$OD( zDk)VkUJ@==HO`(iG@J756%sE89>st^spEc>18@$?T2@=cK&*49C8)$d`IEGC80<1s zS^f(CbOwp)0FTCibS~-I6%`U#4+|rF9Dy_%B@eFOa%xX~pdYx(#inziCmZleDJ7dx z5bFo_&YG{yGBELelJD|5*a;yCS%QXCqJ@I@kV|8yd;+J}H=jrhwQ4#3JnSgB-8>P;*9pY?<$jA&B8X>tzjy=Xg~2 z76o@b>Vy!h(o*4|!j+b0E*5?Vaoa@TP8rtY3Z#7zM|DuaIj%?5kgAP9x-cKrhOQUk z8f54bNEO9Z2|T5>+!0I$;2%VUogz&7SyD3y6^Y>dCGxTd%#dGmFTd771^;fD;$snjnT}H=s9ww6CNv<-k5r7{JA-m4Y_L;1Ov$N)PprgZnOn zS1gl?W_M|PfCMo>^F_QaL(JMhxL&G4=R!G|rGOI%7hr|g3Evg$CfO&K+< z_fOy_8T<$cvr_DGnk9Y7O7IcxCWNJb7sCEjkVX%se-{DvGSKUALV^gCmaF6m{arM= zkh~12Rp%aKm0D@%?*gG_3=tMaQv2Gem4Z}Lz-tw7au)d4GQ3lfhB%SkKho7PhWK3u z+%DT?oJj+O$mCh*Un3o`Fo+tX20OR!M@MwcnsLqM!F@k{+rOxJ;DytHrMLsH$`8Eh zJn(Mpz;BNay#IDUp?UD5)4|Vi2fvgbT<$!$GImfI@3i*qAV8Bron`QN8L~o#?vi20 zW%vadaYY8wx>cRKDe>Lv72VXXZrXS^eW6=trJF(P(Rc1)#rGIi^cZ*bu*ZAM7J4jJ zdN{ORYv*3u_+I;pUdOIp=kZ?Gg-c>$#bfEZBvp#V5Y(PCO9+B0j!%*|5TmM{mep49jQYR>#Xzca(9fZO3Y;~rhH9;bTNoG^8_{E8iK-O1 zD__roi?RolGF0`34Exw>w`}n}=4CPK8y1s;zhoT&y8%qVs7z&c7p~W^jDMv`P z)Q?(yq=14T7|)mJ~g{Y6R!Z2XF5H?Q%&5clvu&sA^;B2urEX- z#|IJO=Jnj&v2L$hLJBNB)zH+M*;cEWvB|5^B{FusYYyA;hCeNCX)oSXWgo|6}QeBQ#QiP*U_@_W1-rF5t>tiC)46(#ahLVKKCM&4lf+n$*MVxkXNKD0=$ko>%>-BRz%~>Fxn+` zhP7zTz+8>`GCjSY z7%hQJJ7f1ra|jv3OK}7eB1ncI8h#-Jw{WG5)@Q_o<{E&(V4&9KKx~@YPHm$GT-ncYA+1MD0BV!`GER@MAwrW-hH37qcgb))_ajIr+ zlb)@HPI)zHQ=wTiujj2@wbm2x$xbSEb>1 z@6*AMj%f(~(jMGJoZ7&1XZ5x6D+ctXc1e#S+R)INw|SM?LfbpMbjs&`-h7b4k9P>a zs;!11aHN{w`}8G0YH9~b2arx8!D*1h+BQX~^_;tCOg@)MmP@C$sednfBG8Dio$}`Q zul8E-bXS>vH?LlI+Np=uOA_3sG&4za@>_y?swp79Np+3bciW?9^_S4AL$N_Bb^;RW zJ@13+{IC3x)jIZLC%C95WEdKiK^uxTr z{u7#pmMcM)v7}wz!Wr(lSWAlDx)ewq)aHudx*Dw8QgG*|h`l`C=d9T+n^dXwf3F@* z;oa5Iw(FTCpOiw?e@wxs`|08CGtl}_2_mqn4lyv`gH;$&Jc9bDF3V^}_=|?>#pk*$ zTGPZu09A|5Wr!9pd(57LZ6&~glL{|_q@cmHFAdf9rk#61uhl|P)3_GHKsq0uP!>cR zAYV<mJlBBq#B?;bs61DxFOnBE{6foavr)i3{kZN zC8X0~?0uQr_i{h5s%@r?tv|Nzolk?KzSVJEQ>3;WIG`@z>aEO@Ld#xgk;K=ayP_oC zlZ#qA=4)6-A2wQx;ZltPAI~3`cLbr+iKZs{fkT|O#MR5%B*rxT0;jRswOh?VTCXx7 zg#wHo?GQhic>)9DKU{|e6d5i<1>P@yf3B%4BzP<=wnNkFsZNkeWTm@K!(k zmWdnwI&hWIU2t>y9L9Ey*$FML)os}T_x*f#k7Dg}5;B^h5-|UAyHOnPZ)AHDiuzGc z=S>>eq1f?W4X(05QU|>ugOqZ3C^OL{{Gm};i}mv!v*}5n-mkC&Q#?k#;)V`OF4)St z%*dmn^+CM`#|yRS-Hy7%sBa`bgF(!Yzx}o(Z1nQaCkH`leWePDb>54??~nK%=oi>H zxttnS)}Zx4Dy}+v8OkyCrF68lCeGiJ*iSTa?Wt|=cFUQY1eos@)?0VkFJaVTr}W`| z;>7wC?Kn{;;^TNsD8Cy!`i32^`y#X%eoSAb%+Gx~^;RQe5);Dxg-la{B9-~XHNTsz zx67$`0C8qc-qk!P42FwgxL#MaxwhT)gRxFQr!Y zllvptf%|@jv2i@{O8DeOpV$|lB~o~OnV50WO#&HGgzMAcFx@u3C%CGDrLeHx-TIyf zMA+z#U$niKCtNrRtna?LJx3K{rxxxD{Z%MkM=1u@YTs6?Y+*xE{T-B9B$`k9F6mlb z@uI&2HRwwSyOJ7dk`%5zlG10%@pecQ0(!5XYrPiyo~`?-zWQ_;GitrWG!e4R!V6aO zjWWIG;?~E@0p&N8*44HDA6r*PCpB&>8eLYOgF8m2j6y!NB&@!m-Wk2>aQnmd%GF;O zq_Jst-;j^(KddephmPHdx&3j^`PG-49b+?@A)mUQtiE!*Gj_Z5_NN1Xt-j`x#%G&C zKKE$-{l+hJ{O;b{pAWmR82{wZ?%$K-fHZyD0Tz_RG!Hfa3N|1D5&@QnVMR7X3uX|S z7T+a;aF~FfkEW$JGChG=Dgg^>0k}HS1miMk2XS?x2Js9wZc#I58iYFH;uHA=1u!}p z2(-d#GNBnch{_ZIj*_}I z4{*-S0ZhowhkTH{FsGm>Tw(~FWo%i$31H$Wu69B%27FA-*tnnrq5FDA*TCc*l!h5*`!BI3xF<|G6rLb{yyjR^0owzu_$P|e5vIz7e z=<}482K&T(XhJ4Qjjont2ae>%q8*M67Cc_aj!qr(+d|HO*QD=sO3Bc9CmrIaV-ege`nm}aa zZ{u!k2AH5{&IbOv6#B;XAdSfUp3sj|_emO7pb>5}aw# zQTq1Y09yxNlTJuU0+@!7@Gy>d0=J^dxO$sfTQiYvYF1kXOA6GBPlB>+Ad$fodrxQS z0RX21YUyEJB9t~@i_#`AJLX>~{a+EK2Vd?fP=U$URG5QeJGRM@_uiia?o;oAJ85U%kw$7 zHR7!}KhaixSk}mXH_*2y``zHc;h}d!!)M>UJF+%%mHq4R=?DM&PS+ztrXP%XbtIsy zQkxM+wL%!a?D|1nH|H(TqCG5_J$Bxz!TKJfX7tnpvP>*EPFfB}aV~yQch#0dBe)}z zC3Dg?Rf1FDoUQGWZ0c$N{NBVxqIRMsP|Q6ULewR(YPD$jPr1tWZ2Sq&r+!hV{DngP zHT7qX1-I^8EDrb6x3-*8$X9dcF!s+Dxz{|36{lLr7eyk1!+zJvJ2Tz9lL*x=P7i_U z&FO70OX9Muu~_~+sLg_3YVg>fQm}>U02EzbbGR{6G;=vo>y_ABD(R^sZd|?Fi1-5B zyKLocRQ%b>cs3$frz)1m(PoKzf{lr0c{@BX(tF-Z>-F`MsL;PfZRpk2k5eWQim+Dg zrs3z1F}=EQFUP5+3A|1JzBQ5xoKb+r>5K%*&Aa)WP-W)x`$$8Bw_&Q-q5Ch+3WcAec4vEV;8iui0m~hOGA6eo7Cp!*3u(XGfNXCB6U_!O~NE zuc%uV)!^(#XKOh@H&Hs_t%M*x+&Y#A-VH+{04`kr(KX!?Ii^6|#!yau+O|khK7Mt0 z4DLDlp$KKZ>xHywmp5!k!)GkklQAfmW-|4mVP#CT9>Z&mt=XC#*|UA?u6=)arfiL< zucdf6^0e3VP@!Tu;tRJ)YyI0s;V2J$le3h_hp0Xpw9-Bg6PuppZ1sxlfxDs&JPrHB zn=i=Fpri(>91U`jeWcn@(X1Doy8gGyrU*$hn$dmLZWj#YK6*vNr>$9g7ngp+RWfbc zUl*J1g`lmyfkGrA!0I!g_NLEE+ns@@Pd(rM^Y9IKx@v3nNS!upPKP%lmS(!bfwZh^ zgvoAv7X)QsB*f-jfRh*hCIshvZ_#in$u#y2lGtiXf9=nYd*@LM`T-@4R z=4myPvK_Z?5|{j_k@@JQlr)@53XpVi3q{1y7mM4hm#;8pc%-w*Le(A9heOPFl454U z!Dst;bmEG%AmmH3R6K?MjP>;!ojkEajlZTIxNx`Y%jtpOquDVx7aoj%IWw|t?PzXh z;FE=gFK0(@9nCAf`Q)dSFX!ZhW1^{{SYAG^L@coTfQ;^EDozKnmpsMvPA>UH4HD+^zLT)uUD z%h#JfudRH&1Q18WsGvot*0LNOGE%FdER?$}Pk`G;>WzY)V-uDqskcWO9cG>rE0-@b zh$kezK`&H)_WN|w^HX2=Dg!L9F`B$B)fiezIXV`<`p6(kqL$q*! zfjO}-Q$MYWSW*S7FEwxpMkd7=wrs>k*#kj_`pzC`2L=X*BcvyxdG5;UAx1?*$-mZ3 z_5Z*bH!FRQ6jN Mztn%=&e*P*R?@p%+G14WfsmuxMCwZARHM0S1!FPsOvyJT{RMLV;X@!=7e)xn`-O4&loHsPw9dL{Qa$^8%Pgrz34o}vu zZw0VSQc;#p>wYB}sb`myoNNgNBF)HZ|BVGmPNYF-5#Xh5xw8a8W8$eOoysU!ZVtrD z3_>ou z6mGJpsi0dsqy39DS~jWhjFs@NW9E5ws)WH}{z+vh;}BH!NPuttqy3{- z=x9?1v0yAV=`uzZHzuANO}}d9_gN9D`6oJck}k)l2UxwHdC_ zvjWbw<_5(Ma)ci%*mY%I?}mvy8VodED^?+Z1npTrHOutw8`{j7O6*Pkr7JVsZ@bdM zh^s8Pzvsm&1wO^DJ|ReE_>42aAvz0X!x5{PxfLfd7l`cE~V4#PVK0yzl@D}(z|hz14)jZ zP&*@*F;Za1_`7<@ghyAOsheo6TjiqpLb&=>Bj12MhRUqyT0CpcIrMfTQf*m0>%8Gc z^#7qKfQkQaM*$eA{j(rX;NP>2=$=4^aw-+7k{J*9`ci}?08tZWYG&va1xhv}*5@Mm zZn{1jA!N9bWQ%fLg!-od<{+mknWC+0?;E0A(&(G(plNY%4>cfTGbAQhS@?nx^#Lu2 zx|cmI#0$v|L{*0Cr*2lQ*lFtOqi(7NAXQyb(jmMAk1ZvHyv@4)k@TVrfJRhH&Q%ta z2zfaw(L8fOA(pR2+f%4(?XJe=!5qzr>2Uy-4W(dlk+v`_o^BtFO-lF5$uX^IBnO2< zViL(LbNeJAuC9{0u2d_+0>)O71s!H(M%h}AwW0t%biLdLgJJ7+@ca#$uJ3K>)>?P_iBHH-#GFRRay^RV9YJ6r2{5Y;0>? zSC1v5)D5(tIcc<*D7*>@u(N?ElSnhtl&MKre@{qwgsn%odTR?nVcW-58VCA-&fcJC zBh}P_kahfS*h^!p7#A236 z;<_&0!YLTg_Xje>_*iGqK@$k{g~bI?%^fMiGH!M0e|Jj$7kKqAp!pZj{4eWZs@Ntv zh9&1i1Ev%JpnAQ)2fD@V2%O={P53Q6*{pk(3HvtUt>*(zu`t{*JmJMprljz&JiIC& zTV8+i{n=gKKs_%PoS(3`i2rm$zURUu*Saz52}t+$183ycRoyUDW0m-iOaKtqnzE$Q zE7tkq)SDWV`4^3oxa%oU)ys*hXmUQM?J`__-RfiQ&9-_$gtX$3dh}`A=QpW#HpNX) zGlnM;GP@%7K5Jk33Pv58YDZ}3W|@g$xRK}lIh;zZEzUpjxz~J8dBKdX{Zji0uFiY` z+}aLYl+tfySp}`J=reQj8A0#GPPLpT9Py-%NcAdmlS_RqJ*n)4r_r5ZMgZE#l~J~? zwRp+v`I!qGDeCGj?zv~gO(G<<2oIT}R89-9mOtdYssnOg_cWgEn%BFDq{-fcbhO{E zP170t6u;lQYEpd2x$_DhDq6y=(~e7oeDM?s3%+=i2K6AU23;ps6EC9YTl`#BMR2uk zj*upcfyJ7?QBRDPybvu@i;bjGd5yY^1O2prheZ^+`RbPEn9Ov2Sn)^$K2`-|<$6;O zZ9HkVo`-SBk_^Qhfqd$TerxoZR&cKuZ0*Xz_aWZi$oN zTB7mu3}^89Wr#*WK`t0K#$WuZnz7EepuhBk4nRjnbXW(WY3=hR}WW!c0Nox2eI z8_!#h#9o1Q4P3Dw|FBK8m3hE|am{(*!}hY7UY)Pg*W6b=>=5Jo7%2Z~PufSRB&bhc zj0k0?Ptaah!sBAi@hUz*Fo3)ZI9ECz|K_QY@&#tdgiDcmbwL z<*7P6%!m-221I59`Y!mQCY_uxJejE~ETgAwhT`z*5hfN9nS^*QEg_Y}AYe-}&3s)+ z)%)?a)nv9ko=k8mRkp&!1IjXsGUPtN4MNe<+%18zO-%RJ0opL)##DHmk8@rgfY;Px z=`jm@k+}`Z1>YpWn_=V3$jb(7?dgSEbTjHPVRl$;8U#kSiHZUnTeV}uD21s;wQbZ4 z7hG62+S&)uwkB-bj8Dr3A`$_%Dh5X;B$lwGRd|+(8i$2RFH)9qsKI`4YgIG`2TKeD zOriLkM5q>-pv3~LY5I;lTz0mS+W1K+K%kwbnX`>V3M3?|qy%aN2yo^)>a}?gPaATC z3nXwIg$@$a5_AO#a6K}?-P}1k4xs5^w6Tz=AZp$wk|9MsO=(KtP<8c?7IwJgbd$zL z9TN*%FMq4%Ef5zQY;_gPJIJ^q6R`US$Uss;^ek)uhamgXR=`OY6CFfx)n-K{px0N> zB1{dPZ6HjRW>hfJ#FY?|MR4&`$;l^{wXr2FKz^>OyBkzfA8muDNbBs&+Yt$=K*2^d zMGNq9qflx1@*HTGi*I@s;9`noawukOT5K-BPXz*lv23WHSyd_xO{y6=tn6m$rdTB=6X+(U_07Dz4CBlSOhA%q^G@fg-Lx1E z5TErAO7e)HS5;vg)CmazDxqo0fp}VimuABzVx*gJN4jVfcqF~9T{FM{!E51{Ats9!lAFk+S7K@uPe!9;kc(boja)0DCFR_J=L^Xs}2cDlWYF`DK zC~c=X9CX~wDI)utz|)`JV8$Tfi4Fv+COR{n1Jk8~UTco`X692buaD02>6;8(zBg@ZcM!tUz6FY8Yg!7+1PE&VRHIPEB_RQ7C z4dgSF?m?@CCP(^IzS`$gKU1j__+Z8H>U_Ox2!oR~fk(2?Sd=knmRe z+WqAm1QjGbIJ&|htiH{!qecc9f~xKoWiXYsBg!Y1T#UvYvqUHlRXnEcNrq-z;B&7R z;Zr|~{z_!dG4@{$nI%#qCkUP+o^&b~sAb9Vg&bvIxuZGQ(M=d;PSBNCR(LuMnREJ? z8aV>lQ2&RP(_$U`!1G4D{4%arYg+TA*caj#$iT=hzxyo(4%@?2DUZGE_DXog?c^bx zevPI!!U_^H`wMhm&@)pd<(W#kTkOij*yz3WFhMH$>Il>Tm?OAVv|GI|i#tDwfNEXn zfo216okc_~QjvdiRA+w2vYm(kE# zIA0IjIbE+43BVbSQh2|J%kW`C*0Ce)(sTG$$AF2Q3+u~AgcEG%x8b{f1l7&#HU!6b zoh{CSNo=2O$cBGt$YDheoJ=wPw|vD(G&=B2UBtRiM`9=dGGiY*Cs(|q{Pcu+-2I{K zT&cul)ZV)Sw8q_*+mf#w*i$RJ@tSv138?Rj)Mkq4YhfAWa zU`Iv}D7f9oPRPWAa9+K(yiP|ThTGc$vC}1n5Da*g93p~JpgaWm&)lq&QVC=pa&*Xi zjgWKk!@}vSCAPzxOmp*1X%EIr&Rsy<%KcS{dxHFGV=wU3$_{(_OReD;jOxXD@h2}M9a4Mm!u0SidcKoWWh>Z2&AQBYA) z!HNPJ5CnU$B9;fRiv_IMVCOyO+ZpdJW1O?k*dN}1;T~(Od*1V!*Zkd?=~rJZLs>JQ zcimqZYNyQu1u_oOusx6@*M2tW7miVN?FV^DNNlmN??V_7-o{aWDpRPHfX^nE=O^xw z|Bso)ZTw%G#r=N`6UX8H(;f#Lq`Ztl3t!aIRRGb58JPuW0YDT7;l^-Tw@Q^4NG#3) z5qK0`71lLWcMG>GNC%?RL68R6P;HCzs4F)Bp)N=rC-s7j>M@B(Ute&kAI-xOUb6=uaT#J&Cg2|l;qZoK#RMHyrLX|C2!N|A zNK;h`MAa}Wbas?RP6^=5wOkscl)p;BN@Y3#jz*r5D}goZNK4`gzDwybS%9TB(K#IJ zp@CCFkgBC90}#n_AZjykM5M{eNUB5x`dV^UNtB))o<>tk4KUoi1)}1eMdfIeHZ{f@ zhzv)i<`EOPwq+%z5m6vPOU=QKY@q8Hw+b@EY3Jq?c zr)7kA4qgvsCawb9ElElR=Hd?NY7vq`RJFAM2W@(27^SdUy`~bUs)e=kP)a>K%mA#g zI4?)QR8w<9k(IO_u%qF;BY>sLOc#YKpB&V6%Ckx&BxN9sEL>_fQ!PDooWoVTb=}Hp z9g3>yDFNiHG(@PU&gPAV@dBg37$7K`8qdWCM^Z(VN=@&SUu_YVgyV+7#!O9DW8K5TKL1mf#ct2=e}WuG@df(EpO5 z{|_1ZJ|X;MOa8W?Q`a|Ap)&_@BI~@k?3xyvmjyK~ z*s1MBhse~@Bgsvj(!UIM)#6)(G?sa-&o)57=AXeAms+>+$`_mf00TL(t>67$E2OlDr z6@udwd#zt-sC_fY)zx-Tj<@4>eSMVSAj6rgS}6TIKkRp@&*wC8Hq0d%c%nP3*T$Sc z*bdiz9=MZmTIDuY(A0{#fwE&)SZ2=gNw$L8F3~;F4-+t*RC% z>4ZG)4KL!)$RLE^2}6rfzI4QTvk(+MW$q(!i2Yb|Jeb7)e$M$s zRfk>}&`&RCN@+*waQ`S0#&8_b5#{4acb9Gd?0~}8wa>X_wr5?bbU+~ZROi8M9qm7Lg zCjFCZeaY{OnlZaxfoq;5t5{Wvm04?zpzHyL%u%SLDID_8YurZVfsR}j+J`)dAhZ05 zksC_+@-4ViZ7UOvBVdoj2Gy835qjezY-bq;w>YW z{YH^0VVUCx)_cx#{g~&^kMNM6AWC-tEv`!(#hN4-WQgpIONr@LNw^kQf4emq;ip(3 zjPG;9(0qghSKL9lHYP$7JHtSlvc;OM$@w)?j%^XCwg4#A-tRawq72PC=8JzYcBT5_ z5Rxs~Zg{ZkM(H4*da4q%F%PN_73?FBxR3DhERqbmPqmh{hZBtK(G%gR(C9?W{~OHr z_bzE&N2*!KqigZsPd44`NOQUV=vLwPQ!>)AjKKdS61MICxJZ~2?f-HL{}Vit|9{|- zQ0tKZLX~;|XB~!0IxB)!Emy|b5$-(mHeYLYtg?}dVPIfYutJGSVh^(+4;GTMvSiaH zz{pd{L&6G5=()~Hw2!-oJD>v@XNi^MB(3Ih0B@;fW`~GjsrvB3uBF(v8EA#9R1+_^ z4)g`sTA-PcRe=?PfH$lB2h>ni4mef)Y!It*siMsm%h&5#EJ8#@C~-=Q-vla&N_Zvh zCMgZH3T76Nl&~Myf+Ehz6!jdQhb*7}o;%*Y<9;G#vUOY%Xg zzGY-4@6|Gb5e<_FdL3G?4n`m6#=~1XsMRh1;fW_;n#_$p!3UCTt&t2($#2C6d>#3fyN7Em=h_h<6Coc8$%7 zXj`we4^_3zs0j%%Hwpj;O}HWt<(I(Ru*UDFWpvKSgUB6l1TJJx2 zPCNtW+cv%rylf;onz?9S;W~o4Dl){IUC$x#(;!v6)e4cv884ctHj-c%`7uhXgG%yZ zAPK5~whpaMsHTV3Kf%!2kcT56!8%ohjg@-E9(H#WJpmarwHo3&;8h`_En8zvpm%B- zpzQ+%hNG5qbQ*GO+_?b>j&Obo7!}W0vH`M>b2`9MwYFeZRGMuR5~xHyUpvrDn;zz4 zS!topXQNU}?BfF+!kO=4VVook4GlFp?d4Iv$5mXzma_EXVtGS6okhlgmyt?gBWM5-mt=!> z4yJVnL2GYlgTQ4)K3za`p#;gSA#;*lzc{yTW2gIsjPlm9UuNz)%)OfqvP~H#3RA$lmPIw zTjl;V0akwbKgAds56NkM*sOx&@OkHm9fLURX_(3Z<5qcDKWuuG&0|C%riPwNi~9ZC z){Koh12NfmQLg{EmVON((Ya^j?jw13;`JO9RZEG~GUl0+<8S*>x8O<;=o4PNpWi*Oc8h!aZN z3ced|H!J0!ZkHt&LZY?cGO}x=M7m zH2Q0^3~0_WKV(Om(4l>bW`A!C;-gvL| zz2P0eh|iboXR&9HTYv_yQfg>Z!c)ETMAXz}y*)F~BHm;b_I)W5t?=>fWS1K%1M@%T z6XmK@1Mx;bAHCL2v1uFQVs5k#>cj||tR<^7n-fxv*#Ra}gNWO1NWv8WQf*>anQu3u zg*FX-m1%~(4^uU?6adyS-0mDmi7BDW)#T>4O4)g!vO^3IPNMiUdA1z5;(3})#IOg- z)Yl0wb0fl2+d=v+fxn><4~K{|0KplIMSz?g(n2YT&k&*$IoM$Aq}pOZnfI+7j^7OK zmr2{3P}R9O)0tBMf)2S(bG<6#PP8>VYUM<42n(*9QX4iJAx@gQTzGW4#pq3oR-rqP zI<~l7o9?f&CvNcMPaEpz!2!wuV~MtS>h!**L079rKWnzn#v3*BestVhXxX;2lEv~; zwbP97QZ|H0d!NnocpdKo7}BhXLeD7~yK#KxN{8Br8UJzbbCa8> z*)bi2Q&NTN97w&F(yj$WkZq&8bzU|#TQN8_&-EVQ-?E0G0D0_|U>Ey)OF9tv$B`fG z&mMfClWPLW%0I`%sHP*9WB0Ji>h?cCrw?&d7tbKwr&|9b8))i^pD>0P8?RdmzhJ3i z4V};bbb5Xs73Y_!T%um@IXX~o2Vk{nvPM#6;QZI%;^AYvT|3Q6DzC+4#SLC?UCly& zQW%D$Xt%lyzr_l*@d^nts=MU0h0`Y6}E#`mO^ejSa4SDz0Xi&-yAJLGtS zIDZXZDJ7UUMe-2HM-~~}g({~MsP-~d=b6X$=?=QuJh9fIxhEBWzTK=2>N?~dF_xfh z4B8${xU>4&Ypv!Vx2K=75x?3cr0C{zNjY++mH{eGDyLSPUPf3ftI~DVyq4xHKYiw5 zV1Z0{K5_S+q0XF~RbL(?OS$D0Bp6t{?tF()&Zz~V?t52{!3$QmR&%JGq-Z&Gf0L^O z#Tp&7GHktl`^s#gD3I%e4=-3V{atT<`S!Zhdrm50>evgC2@E1`f@axCw_|`+;l)$f znY2A%%b(naq9>4n>qq1Fd|)jwiR%=kttio5R^|{e24%{tyE0!(PJoBt4~fPQ8?81% zfByRZoG0}t?A9V5-8y&H`_@S6T0)yXJx4bHuw>@6&QKq$<_*Ja=z* zgtPC%n~QDdIxOtd-(QGku==1XvsrQOSdv~Y4{S((|GpEk%fPyzzw+j_36tRfXR+%Y z-P&E-BqtcvucRF9|0lH(T=w&y zV=CKMa<(te+rk-WKiLg$9Gcycv~PQ^ zdDBi#6JMpNGDxfXr&{f!rjf`U!lm1{xM?-KRcqY1^NG)n%*>rFnKs*&Hpw)1tu+T) z&o>ok?mDz{mvrf_j>EQler|7_qT0xiIYK0t0a9UXpFbSTz=%K}2}-PBLzyGNp0nvaX%xvn}`J z#L7+zMTi~Zx7>CkY=-wzW`I0?OF%#Bf)MMaM7BtY62T@L7U2vV*)g%VJPCP2j{OQy z-n^w+!m619=&lsEcS43NZ87ALhG483Uj5xwwQ_Tnb3vp$DS+hQ1LSyfIEFL{-H~J6 z7jyqb0Hs?(x`xE;2 zkVFG9PdEvyF=tjs7;Q&KZE7ZV0Z9Hl>*3Ci0g<~n>&x|6QFzIsp>*fEst2Cb?AnS z=*ByQ??=gm$aC_REU-P^pq(%YWzG8@GkrZC+hGu!qFqj{ZDKVOxBx09aJ{8!K4xvs#SPkg&pCO6wxO==%9jB zl_IYSp?*B|(_vz<09|JR-`3o1E$E8p99x=n46kxxxBM7#_Sm>$o)9?EiU;u-g5z%s z;py??Tlj~3Cm{~w#5(?w@9qaK%TC~nRFD9^9wy(EVs=V#=cI^2fHcgYRJf`2{W@xB zN##rsX{)+Mj&$AF1>Zgo88n}XJkBBjF4M%9bI+)^`*vb(=Hvrs^-}|d) zFvByr7AbmGPCUcDT-S+?_Jjxd$P1lBvrhC40C``59iAA}YeuWGPJD|mX<{Mb5=o!r z!~_O8#=S?);6S~gNBuD{@BZh~X+D~^3(XKf*JT8=8O+ir*dYbdE)Y+a1Ad7lb|;Ym zP*U+gjOXz78^oa*+@9^|F$H#MBIyjTqe&P^W(^)nBy=CCc>No5@j^oAsSz^;WVH7D z81G7aG1M=Yw8=R)W^lod9cBV(NLC%kzUH7qeh-io05YH#O+E!>%D~P1E9t%HETRhQ z8u6dvD`qn{HA4<*Gp?oE==^qA0}VD@a%PL_7}~pBNnn5C#JB^!8&H`S_+`hq_#cOx|O zFvLDof|6;&hkox;NtC0`*7SUS0hP&!RxGT6@bX!K5<7)$mSUPGunAJ-N-mZp$9-Ut z669z?ABrUt@=qr?vn3#4~Q96UpSwf}H$sf-de1KIq7 z-#jMZq~tZbQAN7|8wHTefZX7RYyT3X_(bi_;|W4+K$cznP0V^Ju|UyNC`5Gw1ix>{#M|8C-^~*W%rS;Fv5eC zv@IUy-~{+sc{jW`a#kDhQAYU%+`BzP{LFZ~ICmhXaX+~Gn7KBje`wU^&q+-S#Cj>_ zLKQMfibr3&?VdR5_2f8xcYo~+IrHzoZZp7AKKUa_nWiAQ2|!!yDh5%|?Y3{xOgUQ`XC&r7*!*>p?jWogg4kXXH4>v!+hur-`FIRmhDqz-<5*KLP!nAY2pz z=NQkaJgf)_%nDz*NpaJ>*Eu$$PPUCq}7;8W!|Dab+o@7iZd&H~0v z5Bkyp2g?ZSrN}7w`7{IUkOHG#{Y)>?rwQz67C&+v?an@YRjOS7+;fYCxj8}R@+gaKmYBS`J>n+ER0H36&odBiJf7CPk)S{M& zTe}dKS=iU`$z%a$97c2r_7Ic6TQZ`z5V97?YmPoNQDD|FNUMb8@y-W;0&-^{?1aE) zf#+A!$IqQ%cKQ5(zw@W=2jVqPV7eKf?yv~T_d#UX3)P68Ng3w69^o7h8ih%Zr7zBQ zVxejDu4j)1JP-*mDNT5nBtzD~B$n{bgKw9vGH`bf_CNA~L&+$Enc3Ipe6oRZ7DDxeZs@u3N)QeUU?BG$}(3;6=KRi=6(j-(}|Zmsa& z3&(5e{0AP11IgOOUl{N29B|{0$z9L&??2{Ymn`G{P!SRUjda;#!^vlXHm7Xl%$N*` z%1T~F(7@NmLyI+Lcir*n4>~f`Xry{8Ip)}SYv9@e4gm~Lj}@Jo?>sNWj++|ePSai( zGBE@PoE25Nq4MUbQ0vn=d5%~OJL@OvnKdz$jK|WyEhhaJ)RmLTkc%(oxq%AFG3r5h zBqZ>W%rQW)**LE2)FDpR4cVaN?91(4_ja8x9x=*(%oEvkyEZ$RY!8JJ$^Idx;GV2^ z_m8a@zSUYT+%(p=8Kb`_wOlKr*EjJ^fAn_i`mc^DNmu0c7EVP5C!`bNYBcJ-{(uV&Cr$hHJyhiUl* zqh>W>$&E79?u{|bel}UI9=|qE$_EUBeD%6xYZ&2fTW9-BpI*FPJtvJ~< zwLR3Bn-o6X)}OXD>}%4xt!ZMrL86XOCv$tgB6Db)?XMBzLuG@VD+%fab|Srbziyz~ z>PB}ULHFR*shvpYxdXSXcFDF`b8JG;sGHob1A*?{5W7n2pZ+d)le9@A9NU3YreLqj z5^07y-AC+L>N^E(nh!&> zP5CV4>z1xL&!!-GEqpS?GwyIj$ly++avh`WO9^XlrH2=d`z|cO;bV@-Cljoj$Sm`i492ta1*heJj{G^*;QQ;@}5+ zn~liYz&O4ELF>+_PpwBm&`=XHcxEL&v3Ky6{f%vol{DSd5!8+|+Y~RIX?bn+fC+5` zRPp<@I#rvS+F`x+8&MB+t-4RG-c`)aEq_v&x{lMy@u_^b{M+>Ib3Nbwtyqv1-wt`M zv8MQ5^)|KfMozQmjC2_3K3Te2As3=`{7%nL7#NudS~W1iJg)i+EQW)NUfQlmO_Uli zjYYmk_66zxnjkK6O;+ueN$lI%`gWm48c$g?jlvB^mX7?ui_Gy+@mEI?{|z^&EPg99 z&!!;E-0jtt2(HfPco5Z3Q4^MtLO3l)sC?N!#Bog~Byh`p z+n+;`PbPIuS)$M}W@BRNK;FGvg1cg0gLyWGQZPdR+L8!4ooq^!G}#udQZsv#3ny^> ztsZn$xr^CQ(CJ&pw@9x%q9Nl34Aq(ICQqdrMkr ziTp)vH|DtBmgK~j?i%K%^PM&h-Ikk%dl&zfo)0a$*|6eU&%X_Ak)m_ofFh}D)v*}$OdLD zW(?`qDhaJ^20%R9(W%g%Taxi+OYMs~L+0Ux3K(<0a;3EJ>g{DU$Tgo%;V0F*GCyBc7>?=CSz z@0J|^FQoO#VC@gVt8HOX#tRO4)tC=4v=Os08=%#&RP^!&ady2%XqM7TOcY$!Ik__M zlY+UVco>WfTg55br=>Fdm>j_9r^lbKSII5Ce%xwwEyF;nk&^p3s_?CmWd`_>VINB2 z%a_IRpiM5WAQ^e0743EcH!ud6++b@?vcysUT2RmN_GxU15E7mD#ajzFT9nW$bAtiZ z^XdYhc8|5?wpp2#t4BCR_i<%=8AmiVxUG(9s#|Y^9V%jP`U_$eHyVt2MR-2m|IKt4V6t?X03LcQrLVswf@SftbkR!@62Nj z?qz>u-Wk;t?KFaqNVg(t6;Lw$g-g0Agzp55HIAm}|Ncj^_}UY~I$A%GtbnxWirTaZ zgOk};k9o8aM9w#SD5G-_6gMosgyt82CC*8__Jz- zxa1BtTruM>n!Ua{e{C791+R|k?chd&rF$#z7;iT*bI%zF2MMnZnEq3)d~P%Tbc_4w8K zQ~AzCHCO?)#c8`W`6Sn!SB-qKU$t-qB-tm` zlDheuO>u7 ztQ2Zsma0ULLHe1Bp}IJ2+cHJr*~Q1S{+>-Vo=0uT^<)3?oLDv+mf>ki6A*f%E*b=> z4O)|P*(IISqYtaTqw*BRT5o){FIQfn-ID%1`zJ}+n01PZY!oM1-LbWaq#4dGZy1S@ zA>Z&Z^~|1WIi$-3a|D=oHIlV+1S4LB@r2}6YcByXGK?hIV9R~qpb%60!$!(rw%+9_ zgxT+NMoa&BoCj*~fItBf*<{%F9J!cHR^3@^S>D^za zOt{wPFYi|imDmG_T*02fYmQ<;Y4a#LNig4>GS)LC01?q{EVfufNCMi08%9d)JAKso zB6B&ySMvyC0>SL;Rp*LataheFSoKLRO4;}#>gzhW* z(Sb_zdCPFL47@lG3CFqaE-6O30;H84=5uZ--c@YP^v|o5IAW16xaNLLL~hfri~LLa zwHo>_Mm(?YDCw_Pla@Ql2SmKGf;iE7VRzI3T7^;3WL%MgMenx9ny{dz+N&B9;zX*O zN`{!De4}CyIyRvKDuWDN%fsoO@8o40;=~$s)ljx*seEA44r;5tnj)~FKLSLc;=;gN`xLz>;fj0yi2hV#}hBO*h$+&0$b z9h-?Tr|~pj@)o}_)?f(L-$Z;X3pZS=uw4c&o-^qS>5OCqeiiFCY`c34f7N~HuIK%` zUN1QhM-g7EMT`7Re-ihaW1~5J8t(r0zD3;jUw5F3;qCq|^Od)F^5xNN;za^t`Y35(;sMjxAXN0QcjM)dvjSU-t6;a#Gc3 zk=QolXvnuEn?`SieYltW&FV|^gK5S6G|7VuNwgKK%*pkJqio5_^`Mcw+?45-iKNk`RyxRUwK!WK{lo%qozB0d7jC2up>{E8a5_Xd(?QQzNGHK zrUOJPhGe!Fn>mMgIQ;_Pn>^HI>p8qP28S8yVWLQqp5M-evMw>@=R{|EQ6Hf*F0u zZ~VmteFRx4ZdnAx1>Fk5(gwye1Q@qUnPww8hI@U3_2U7jC-GhhEB)i&%g&_biY|rq zcONMANUmWTJz+jdSWcH5^y^&X_2f3{X>!<;8XTEUX^Ns-s_~NlP1r+beFAImWWpqE#)7uor8Rp5`r#43VI)| zD%Tl78oogV(rGD?@hBK9DL~`q?3~@NeJCmS z*3bUFT;b>SY`OI^v0S_~4oYf5tm0!REFHf%^cZ%?dk&&ADnekinK6_a$~!ycpPMm3 z{^=4>ATEJ1UM)cv+lU-FIzcCC4O?vMs#2yXTiR5vN1wzLoSPq-)VXzc;pKhc)y?Q? zzjd9SI4Q8dHW9nJJe?&nXKQwO z_dg3ALvD6c*<-Dq#S<1!qp#nablv{^^m8Q=C&}DE7|Dh#WMzNf1s%r@?e@havdW#~ zFcXnmXh>9MH_CAuKG!bNfvb(fgneH|NqNY;t`oFwiTe!T+l~o0cR2XGYog_ORKUwq z31h{5DS^$UIz04d;Sb83~5A=MgsoEphon{*7&`*gv{;JgU%}n>J_?(r!a`Yk@w7K9htvLSt z%H6bHQ6O6w$4xigDN1+;n*dGcYmdXf_q(=DOSMXhzqj158c zpn+XAU{1!{oe9W4(zN5I?~i@YiaL||F5}6)mv1rKAD`M#>M@2Xk!S4YZz+*Ue8ygi z*^0Aj637=LxGF{S{W|US_`q~Nk7q@U>{bO|;!DOMDGiKuovQE&M&UAEC2HJbqzx_Qgjrp^p!v;i`Je>zK zyqEJc5>F&O1C4xwjx!7jkXg9x8lVEEH+f}v{$N^VDXFS)OB>{zfxfuD;@i_ZJY!$` z{0h$z(ds7X3Ig}xNa}lCfGx_FByMsi5I}pk8uU>)QE`L}X`gg5(toBiN zXW}I8Y2WLIR z@SiXFQE)Clb#K1ku^&aR^P_L)bDsX#q)|Xz$X`U7D-E1`Xfx-f_CuUskhG$}%WJOU z&|GQ#oM+Bl^}{*Br2^00b9I=Xt{>;zZ_Y_wHty8h==OJRYx>X0u#I06e{SEg@p8$= z&$T~y4sYCl>}Nyo&z2t>8=r2hfB$o@S)qvZOJC>L{@7oIUWIyAKMz#=I+;_*c=Emd zT;crgU#E+H9ew>vdh^%Uj$g+$igtPD7d`#e6gM~yuRDeg z{kd`O)S1AC^QRvEx&3d_E$`SHn7?<CHel+5;@XfnqVqx{?^o6+}&VfciMt?XuaRLG%kbj16+Kb?1vY=799JiNtv)%TSTPiY-Ek~96?`bl@p-E03~&)PieE&TC$ zhEsuEw26sCIiC8J!oFk@?Z){;4cDw8bQ)KZ;Y z-G(*0!hVcZT3>U0&SCCSGpd_+JDHKP)b-um^9s)`FO8)B@yr)-ep=^K%ed7C47Wz0 zf9_8S4&G=|vbY>&vVECF`+?Y)Lbq4>{MADyJ2M}oAB;&p@-rLF6|(5$vSuU!#Pr)A?AUd_G#u(Te5zc7e>dp z!0iW~U3nY1XV(Rb)OHJr`O&WLW)>+&t`$!6kDGoY=|HYCmYw~0538;rts<+FS1o$1 zPTh3>ZFPDy*u(nF{rDp5vyZZRtj|3SXy%+y?|sO@(3*;DF1$U`W7G5D!f%`2*=ruQ zec#55Y%l(N*JFF>@2}ssa**K3n(v?60C3P#?guM@lx-n4gDN4Oc0+0j#dcRTSM}Nr z>lXd78&SH!_E$}si|wzOAMLfjZoN7CBT0+-gsy(cBG2KL+jOtPZLi;d926Xam*c3v zc8TMiAj>|-yI~%G9q&bicsbo)V)l3XMt4@9)5B#&f1SpXYP_5ut!yrF9?v}5=lnRQ z=dbgV-0NO0PuD*#ae20Jy3gg`lHY$_CQ1p5Tqh;kn_Q==EHAn~uk)C9ebE@Q$nE7n z37gzr?Ob)y?e*@WdAB$FY8JV_J;?ApT6kv zsqgo^$4o!L+jDkEd$Z@~tCp8Mzufd#Q1Sda8shEs?S8^$ukVjmtte*yC$s_(2P)Iw z_$Yw58v!VZSg;KLGHTlu`7(+mjN-0 zRVyT@jI~;O9Kr{J_(tJ`(1Zzn!fY3DzvS)29^vjAS>FDWaN07PJ=*;ru z$+cE%CG>_&kJCwT0K>?_(6hqdC2oz1tBq#!4&wm6>M}AZGXq)-tmqK}+9>CmDvXXb zTA_B{02B0m>X>hi#*Qc<^4iD`;O16XP=_*caqKc6cdWOQ|%_}mr zkEn;;4@|thMk}jaJ!VBM|Iej_Bov8e2E0Wp5yuvO_K`56W8SIP3_QZ&5F( zSGQruXTMORO&I_C9vVgYe%tii>snNUyi9jXKv)m6 zvWQrcM@#`!kCGx@Ecvh1)c+G#Q*p8Mh10$<9!k0Y#9dBIQnj$CYrO6a3ye(HV%S@% zffb6ce!I;#eh$|$?X601U+DOG-4=cLa|7gFijc6A_6!Ch_S|@$zIyo)mU3kqD;C>d zk(R#kZa1a|QWq4OclTeI*cXY|`VP%>n7_pQLZNQHaoOLtpeYF6vC#3E0yqXWMzT82 z9(hc^+&x?4y}8%{MqZqc3Nl&#eHX01zz>NuSZVS7=R&9uVg73{8j?Zs>>kjt(wNS;Lo}2Aj-v?V*#T6?+jcEcEyEeStuh#5r;uR%=O}h_w-|fxfbV8pw2zxm^3--Vv!Ndp!62V3 zb!}n?3o9&&r}XEmfPm}hJXFYB3R%^$5=DxXCo_^N7I;Irp05o=1~Cbm9QbN63WOXE z18wI5yW+UbFaD-%q=F1z>8#jQ-l>m}a!zkTQ;XxFUhQ!*C@Sl8SsQ@fg@9ar$diwTU)j zKCVD7Wwjx*nn6ze#{N>l)tVzU{u-tno5YJ(BnVC`hq@2Jy=NG^FoAYqaxPcHt(1j% zAyVU41j>6(s58u-vR&vB2hI7e>9rhDJKkAUz9EEhq8MxXAe!g_8RfpJ4Kt;D#w90C z>VIjfxKS;rj-pL&*LWl(Ba`c++osZn6_45dUA1{t25NV8uz0K4hUmBfDv`OuXvul0 zP19xlEfAXZN*YmT`aS~(2HMbt$j#ei(j)W%FgwX~kGfwBC5dBmR@k#*j!jM&8}NRi z;$14era!NcSYhr6>5iT1^Sm*(^gJAV1Spnl6Is`yuOB>!#;+dR%Z~X)$!E*+t6pIC zTiNx6{Yjw@iAIo5WFlAB2b5gF<*$|Qwd)mAr%_i^l7Fl_T{8QR!S<)~TiMPA?91pw z4SM|_*y|GDC-G}fpbp<%Wj>Jb_`>4${`jgaj12fLpQRKigm(RN!&+n12B$ZiX5y%b@XRQw_Qiz$?>E7!^AV++ z+dxe_yMct&{H?rue){CnA#c~&2K`&F)9XdpvIRS|M978@6yVG50$Z7XT6GqH%j^p? zlu&Uw-Ju^nze_6_`=|S`4SPe?j)g{?4$=5}5A(W7?eBOWLD$7IL5ZcS2fX}-<1h}8T36PiwTj4GJaXCzRF4e2Qv=hnSJQFmP#dFB`1tv zd<*}!%(UrGBXip?{WVPP5fwgMte(KQuQgJQ$-}bM{1TwI0|s}zq^Y!2(g*fg|N6Q{ z{#S53X~J^n;V>!9rRRxu0oOijg5WCS=$_(^3}9@e%n)Ne+L}t2TEXhFj--jNrrfUi zogenlE~VnsCZ`W;OjTQG4-yx~>^KkJ!=`*+9aJXhOZDd?=MgV3WEE~vw0pd8SA4mh`C&MoqbO(la5avINXvIiv4J=ZI z$*b5rv&Y~d>onVnEk(AMY8lRb!y=)sbZHsme84I82J6PmnPkRODI#NrVm6_&$OR9T z5-<@YS1ATxx)yDRMS{o%K@PTSRl^Kc>9k`HVjw$`r8JY=m43Vvp8P=>LX}`gNegTW zsx2RrgdY{=QD%t?6D9f)r3;F*SFhJ2OR(AlD`U zJ4<}_Fvc(f@6!%zvA{7j1<|$6P)WDu!wo+uxn{7g6k|A%x(caU{s7|tp!hJ5UwLKw zWAXXfH;gJ$}W%p(c zyUnBsP>IBWCPmc7bR-B-+jVbBaR(UK5YzP=)mL}@pkSGB7?5n`8D`^pv}YM*n>{ybasgtLsy8vey`bmXg^W7m8!xA9N!9&ETpTK?PI<+YShViW=0d*!BhsHq>B6ML5|r=RNOwXMShq%)8E< zHS4UMzq7KEC&_a?_jBLZ_2Iz2d4U`n9HoJUL6D{Zsb8G-`!`YmrzTdPJU+h>7L>nY zLr$LMp)~S@>>3WIW|dP1GbbeH3gqG|ew*3pyA^yztC9&q-4UV_?jd$C_CV(l@T@Wy zG+C73e-^A5|6O%2u$wqp41?@Z3IewC^5NuK`9-u6z3O)bBFO;!`W1bdMQ+UnCz54a zx7Oj}VYcErSUkGO4t$ynA{s%P4ScW$aG|01=z}g&aQJsnjtzM{xCOqrck$Jpc1+Bs zZ-9+8(u!6@Dy)|0gYlE(!*YOxcQ(CO5X6(Xi=SKXYY+agAzLI98uh`AhNaZ_!|x5i z@iGEz5#KV4o#pR@&E+UC)P_>XJMut(0b!-m8!Q&16FFHjg=i}utiKO7yc=xb?HQWy zT4(}$yTJnyAZ)1_+JMIBFL!SVAn%10H}o4>_EitMz`fd%~K!r<=!q! zU8N1e7khDwJuoK9o!*QZNs1OlyrlD}@vsdHtZf7Xv)77aXxRX7ij5f?2uqi1^1YVGjmA{n2+)6AM*;I2z zEq!=>S?_v{9*rv1)$OB(Rq_MkQIiX!0~#k(3#C63!iia|fvAPn6~B1VUj`FV*K^lvA_j6CL5)e z18$oHf1Q(wHU`ri$pZkaUD(TZxDr2ye&Y>>T#%i4P2LD1R@xeF!Fkw0#^i4Ikp3 zk%>>jHu6@*6K&n#faqNBV|W=CMiL^%zE2#;y^4YW2oZcZ9qq$Dq^X5#lVCfmrVfKQ zoFo{73s+5~mKyA~RW_ksx90qn*@EU;~Uz8D}oZ?64I!^n6RAHZ!1nVQZ zbCLlJaR^3S0tlN@rAA#Habu+0gK-l?N|AG z`h9Q&6~953H}|LF2Ud7 z!Yn0F`4xfGW`w$Ba8Y{zjBE|~DdS2X4&?q$HVm%(%+a5Xh`RnmFv2eug?1PIZ^ z!ZB(e&?9j%dbbVZ26X!IPX_Q>h)9>f?g-(e4G?)6mPoCUKD#kHJ41NBW(3|D{*a#zmM}(E;8{7C5wxxf z8J`Yr_XFwopF5-=$68Ymw8!gK3_P7CC5b;EaY!bg%dIG7HJbM4KP1*mJ9~YCn7mF1k zRq2FUG$1dPJTdl`>XXh`*-aDoph0t#iYoZX&PR&n5o|(r1#cXIH1`~n##Xp5*9@!advf;?excUl7iw@ae@dt&ADiWagg7~W- zp}TSX!)D}&KjFNXxPO+gSy$RBff?RHS^kMX4;Jm)0k?64gmd5&3HqxPJ5GB?a*+*Y zbt4Pk6_|n)0gNjEKR1$Ri4!Z%hz>e>0}U`$K$W?W34hl6**=g2`92tifQ zxupToGbSA>*GDoC!Y}X$GYr@p#U-YYls5$Al7Ju)k}35Y<)MxodjGS5lrDx3nW0b6 zp&hiB9ha7j+K$SSj|ZvrsI0_itIXQos0Ig8Aq)^da|;qLLO(W}d6d~Z!ox5CnC6`i zGlzaXKXi9i9e*2uJ`iCpNH7!C%R2pOzZZZ9U9K99?7EBq_7ot2toc6~MX*~f5FW^2 zmY~<07_->-6r%==pTpYtb+v(JnmhmUJ+NwZ{13V}1;6mAH(7V7L@{LEuX#=Cvu;rZBJbIhhy@hWRdE=nR zf$(?HgBedQWuz98`N^H!1*6~5kp4jKuN8z!q=YkC`XrVTwsEYLTm)WtogDcw>Dqpi zxGRstjkG-DKdxJ;+aL8g`F5xOAjv9gO*&J%6LsrCe>@kDPzx(lH zak~)Kk@4j^gOQ2V9@bH7yxD^;8xDlkk&=&e4o0V)9JgMZarVXF+D*S9Y+|w|bk4?X zzU5)FZp;1kXV-20v(zSb+sjU=@8QF^P27%eFV4p8T1D7$3Q@X4oMMuvZG5T1hN1X! z^)lOp3caqOgxzLWY!mm`%nv12yCCh7YP@uZlj;IJ?bh#xLH|*Y3;$P-3!1HfIf8;| zxF9p4AXYFKGg0TXopw$b5a{IclmTq^G|Josvor>_76C+kJ%10Ku02M6>rp{~eY&Zp znVqJfh~lKRUKGh1cc!MiRIEFx9RLM)!ctX z%U1e7NwzDERPl?_5>TaJZH*9sQD)+lULHE@7JNAkh_wX*wOFGr5MGu{r6q($Q&2@2 zmqmk3_0-5Tr3@;@L7r5rYb-Y4+|M^~WD&EI9R$0SHx-*sx;lrf2N+TIb#9bSnrnTk zsgDU@<_(LaI>gxNX75E{Fdnx<&cp)jNuNbZ@ zqv(~cy1T+GVuwq~A)N;M@C6oV3nL{m42zsxvofHvAd;!0m=-BTJt{`*LdDVbBXhKi zxY}p62$=%iSSEgdm26rzZH=DuWmApJ44uR<*C(Mk29sKnsI#F#^l}(Jb zi}pr~{N;Bwk&Nx}F{wTS0jl{St~+<(0=yK}D4N@MIrduGjs~Z`N>Sig%9Y^PG@1tO z&REmU!YOJfngv+%X%-cA&|QG6f4EtYEZ}e9)VVS0NrX`X2S-*nOf-@!DB5^A*HXF_ z8~TtF^Pmh%tTu`4;BTI>!6R`aemzuaQ?kYSUHFKtuJu8xhm5=~`@u|2Wpf=^^FcB+ zC!#DKik4Mq1i>9ys=Y+3^SV*}j{1S&Fy9Rj4l5`njAEx|ltI#{^)_e=_O7!cG1kg> zzyVJpcFQj9BdXBs%{s>3C|#(*X%&sc0E;BDp|b_q6drKkrD8aJo zdpe!@RVK`}Y6lc?s5HB>~U4BvyXPRsff%lUL>LPbFMztW-cuT60OdNHu%IWUoL7o-^~eC zhF$dDn7wdGSK9yYr3%>F30)1D*=NoABRuJYhYG{mwR%6cfM!uQPrTD`F`+c7Hr z^i7vk=aDgwlCLf6elytaD5jogEeBeS2_4unA3{3@81+_3DBac5me(1kzVB2msyS_A z3|Q6cl>XJcU)3wJ@I~f=`-SDhlMLu%Hv0*V@a${8_G;Y~B!~;JtL%FYqn-_-)*?$8f1ZMmbnUYTk50CVn3o`Mvx zw|E7TtqPX2#x#`{`7FP7nTE+RUHiofc2zs&2y5Ihda*Kcs$Kr<VeszKnjD5KE*&k*$yxAIuAsN=3K>%Q zJX`Cc5XN#;tciARqZaXS%DDSjwWbCfGq34CbAh-o3LT{GK1A5 zh`Bqa0A#~_zt&X4d1C_l$b`4BLV&A6AWbe4+t2h-y-?)549kLw5RHP^ktVzbTD`-sl!;C zWrhq6DqC)&y;?%Bv5VEXnNR8uhtRg>GC_ivr|O$*D|DkQAbYi+>G(b&qRWJWAe9b? zLwC1%rGZR-hxTFERWDa7QH}fZWeRexmQ(e;X3r13eYlY$%%V3n(do~DRbIIb@@chq zf)2tYK1nqoOVANr(xhU?bdC8z(zMmCT|L?z7#TlPl2l9^SwhEOx5VZ&cF6Pmy&+N? z`vAI5Gs%SSW@PbjH+zoe_HM;nm+l@Q6u83f>hmL%E%>`QH?_*eK8m`>1fO)qRbbs$ zjP^XeSyT|}HhG4XV;Q`AJsy6LnDWN^pabQpX%ZH%&)@o>NL=4h?{m@#?Yk*^y67mZ z&ck4nncfli`ZBvwQstwI9*Y(FRYk z`>$w=Z2OD6DT+0U>F#Z)wl|D`?VY-4cePdYn9KVf@{gw;EoK-GmXfe6(-=O08aDR4 zM@#IOWzwGcS0n77`jv*dshcs!{W1oUD!~Db?tQE^`Q4!5=Jgk~o8Eb6rhrbgvFn6x z(wVxg0zFyx-!{vk zb$9iqM}5+sMC@RB$p&LFYdc0x_P%q=w1YbBb2tX}^Nh~@s&=2MAHx_NwL88?_WhMd z`lN6)@v8}_*1JTT%H;PP7T(aLxI$2csuxQ?#x;ry52?<*W?vFAN#}6 zd<(0JFhf%5xDAK6%^+&89l>^BdiD=LXo7wzT2j#=cb#xP9{>(0lmN$#(Hfr>w-(dp z)aM_6cS)}ydVmEV^7|d!Vh?QoQ_1}fw&I;juMhZ+*nv$}m^5*YuR=z~%c_eV<~@oc zDn9R^JwEDvaCLEGQE0*+)T4J@gnh74+!(IQihuREQBB&yoC5YY5A+4F%t3#aGH_@| zAatcSp(JOg?(o?T#@*R_6eV|+)BwI*0Xsvx;1|xXVQ1+YA#X!c+)tzLRv{O9z86{* z&|d8WuZRk;b2vC3?AZ!DmgM)B?@BF9k|igRGzt`_f!BIq5Can`Bqeiq5n0LrSfsX8 z*sTEty^4mJ(zAQf$;G@?AUsu6G!$Q8p|3m^UToV~Y(G@&I8*GrRLr23xZ0MuN0vyf z4XnlzpP>@JnUa8|5;nCo*tRq@vNSxeG_tWYdZ;vJrZje`ltV2`uq{i9EZdM*mfTpD zI#iZEQ9O75a z@b@n9rKw)EwiWv#E9&zq8XGH`hbmfTDh@7H2&t9rwv`=`m0fw2M;j}T{kz#-)T&Y2 zs{c^7*HhcQ&m;HF=k5I$* z%^fywwL9_B^c1v=@^Lk%CxKEbsZEwgOtE zuM`z0Z)S?JQ&Z>1dN#(ZbGB&<^7Yo!05w^w#6aJoRzQYml#rm=sN|X-X4>E+8^!{( z^&xgF)yy1u4?jZn7RMVw_=xp5z8*1lr{|~-rF^elt0AyHR>#5LaEqL3U?wrSN+oY2 zo}gjkucKgrBc>$yDh0W4k zebHFBg0~Y%S<3ZIQ-OBJEgZXO1Pv^~erT1Dn8$D_vm$tK< zyg32OSfin#q0TJ=OiXNplEAHd49YB&S}M@m6!Ve<`(5RP_)H8A!;DTsXKhzbqbWz` z0k(v{i?#oX#ag#C6Xn-P=ce~Fod}yanIZ3{x5L3=9D=~>s>;659j^m_#}NSNal1M= zbh@yjMUv6Rc3S0dKoe;HqKpFX*b!G(1MX*tNA&VDX`&21r;IUWmHKBF)g%#gfxgJ9 z?LL@UKIx*5FRAicX8U0S_1frI; z&gI3-6|@zWIl8=gO3Qei_=bxLCFv&XQ%gb23FM2zpKdWwCZEOb3K^fLS$fAm-}bY4 z_xTRn%jC=5(1?sLzejF7{^efG)^}g-bIMJ=&LlNte0`971W@4eZ-wG`Sv*H ze#W;ac`uKDTlvHBM=;{g)C!Tn8Qy6bM^At;rMAyYH_{dg zu3UX4zpd!;%guLqcQJ?zz&;S3?7rk(Vr5me@oNHwIIQ@3=Ls4!-XS z%sfHG_dQ!C^)rutSD!^2%I5Ey+LUsKH=FCcbV#Ga2QLY zfoj%rmcf}v6;%1Bh&o{+?L}Tv9A%`2dEz;;xuXhgVGib(Jm1xZ$1NpUKx(TFYxJ|$ zC+cemzTdG}AqA0~nU$s=;3J(nbUWaY`Pt-Zc={r}qVER_;n@ zX0fGZVpaJppd+wl;Z8`(G9GrSgEbSL2Zg-C=TFspD3dZPWZx3w(U#nmZ9K+ZkpRno zu1G4>bsi7KH`o?x5m@{+sA~#9sJRxYG}&FzXWFE*h{h3Z@UXcyy*K~!VH%;E<>hm` zw)u01zJIq*#I@V?=RSAR_jLPh^tsdg@N<{#jqZT0*X|tr@%adYaEx8QEF#OL>8(O-^5-Z&P1_S)|!^1t+O2tASiCs`S`|G!unh<}V6gZ*`k z0z!<*wHP2E4j}7N3ZspCSvrC7fT@RBJ{3?#0!BJOfSuj}Cn?n)*bvKl;HegyuNLhD zSm6{jbqse^m>$u$Pl<(Pv2xN-2D_|p!)76L2sDO-(uCA^{Nuf&dUcG>=IBR`O+P1K-VHis<^XgV0 zz*BDf4#L{?y5a7~Eg5L(TeSwUJdB zpOTdX9#A4Ca}gUj@+w-kDG5MuERdOopit@C75_Tz{{8q*TY; zgRbP5s+9Sr`LM$IX81ax$i+VNF603IA&o5y)v*9oa`sY3+`Oq2*F#^gvTJ0merFuz z?J$1x>hMT6S(a4;R6Ag(tUd85G6bKJRdZc`n*j*OjB*=qXmoO>W#Z$RHL9D|0Ed(ho*9`Y(h3bOhrel2gs4NsSY$2sSnwsA`8+sZvN z?uI5&+`LKn=N^5=%a*J-5B36Ctz&=xw0iJB*wU4*esXY(0_WV(k!U2g3=hvT()WetLEc-&pGGs-?+S|WS5ZcPv0KAp}u2DL0 z?S)b;P{f$!S+~L9-(^cD{Ut@zDO!-WaD7u&|a)jnPy3ezF!+QVscdeoTQXq z)rQ~5x~%k2Qf52VM*Q!`s5}4T81=vX&bxz#@}6{6`Y7Q0b!aMFo%FQ-SR<(F(AM+5 z>J$C3wsERMx7w}DKmTK$5Z|fq<$W!<`QyH$ew~I9SFeSilVS*}I*m7aUypwHv3_W( z({$_A>#;vRHi+CZ^ z12~2O8DU6!nzmCEAy+_BH-&^H00sb9nhjGY0e)UUggMMNLoOu`Cr7Z1SPSH&0Bhu6 z950H3x>CbtTu82hrX!FLiwm?xCC6(@S)gjlI3fiI^G4C+bX-H>!RY{~ikFcG{C)q^ zcI>}j`0o|?kFCJp8vkEZ<70AP|C7egHzw6fH9onkd%ih+@b&xw?iGWVEjjm6Ubg1V zcfUMX@b&e}Lp-G6D`AOz>Z>-s?y*7gqlnbhNV(Dp>&HJTi z0x#3=&-X=Set*%p@x=G}maXr~Vkon{F(IY2*yy-pr{>R(C3DckdQsYG^ zv;Rlq^-eB-oU-|_{7LL(_UrTAh)ut~%xpaQ>+9pKAAWtCEjL?Pdfu>U<@>86Cs%$f zoc*x!Q!-(;y8QY6rqy3dFI^JHA&}w)D&e-^>uZ$HHEs-1Dp)g62gpiRkTznTiTC=`n%ei>-KbaU~y;& z4tuDj)=po}mmM(J+ivCN>||}EK?b(9cWhV}Tsm;pGd6Pn?!t@>(G-ddO-HN!V10l8 z$=t);{R92mbCOF3hW}&x+uzUrzitKomaM-e>t9LM|Ew%M{|9jU-z`heORgAwxbXR2 z+J}ov^F1FfEq{Ic;Wq$fED^yK(j_Bkyw zEB(_A+n2qcZaRMZUz4o)uWTkJ=GGV-B^Lwc`eC)FnIJI<7{PE3waNg@WQi-5WZZW7 zE82V?XYjG^caAF&Cy^A(kleX&j5?Tg8A1n4YiHRsjbgkjdX5RDo(2nv4t&}+*1o_( zS4xoM$Ynf&Cv+uf@ST5nbp~>_LoL&RBESj=a)+I|lUamhcZiE{`MdgDk+v<|kv~T^ zKQD&BX|qtwn#Ta_^K|*$F;E_?8Noc-8dpo2|A&%wa&=|-+lSRv051ekED=;m!bAHB zp$d~Cq`jmNtP;ZYSR>fzzsgb}(q{61tt`#LqW?kZ2GPPwcLS-ec{)AmlR*<$AYrdk{G}1e-Z8>4yq7CEu%aWQ%^UA7+)#gUVq0v zDaKyiXA=O!LeshA;#~@EbX6l~dh})mW}uX94^^}#X+R++J^?S?EVq_9x2=^D?hSGY zQaHO@xH%9JBZb`Kl;zDv4Ir{qXnY1qOApG{ggH1#Y4@Q$c@DAb>gwtok1#+jMKEIXI3+8ckZKxJCiL!jDk||TQ*6d^)PMI zpcC#g(d*P*VvVD1NqN!ILJ@0bNRU;AtP7L^@0*&MSVDb87iTpSb!|?u6lQPAk*EU6HTvN}DGQDh_=k{b}{>6}>#rD(~AsKR`mjp7Ows8sj+0R$5TJPrm)d5Wk$sZ>%&aNYrgG4V3^h^L#>Lvx{Krs^lvRU;w^ zrY;cka0ts%VK0bdab$K|Dz1fNwig(lan{>bxTcFP#n{J|=4rVFfq6-&&S{CZf&CKmPX>_ENWbc}pZX1+hWvZs^tOY2$ z8u5+I*Lo@XG09}|nz)@MePzgD=oe>O zvKuFjeRpHX1y}t^NDaG5@kIVVbi2pI(-r6ebFT8zy zDPZ!M7ELnwKa98c)0{*W&A8sN#U4(&u3fV5jAw#p!MUOXgx(-$3;44!Lr9B^EKIYJ z%+rx;I6W%M;0KM$KbYm14^p>xnY&sBJeA_@KR901euj7Vx5brAm(h=OEZo-qISZ*U zsc+Q2?(9<_^1vV`Ri?++B<7xC4xn#amAbZ1)2AocdQc_f6(uDrv{a1e1E=WXkgL`8n$>vVXU{vya<-qa)Y`uOTPkb znS~kmwQs^)bu}6_-#swwn&Av0iCsLtjWG}RZRUxQF#fXs$9ELBw00xS*-DuuS`wHg znz=bxi8v}QzUL5w_OLaoq;s(QCmElxH2m2xGCT5_|zo{_gHWjE@>bu7oBZBz&Y%k&8}V z9+ivdT~UY`K_qfAu!Nv?8BrmW%S+_%qZMg57V;ej3#maXLlb)P{a6ZGYXhD(2K6E1 zG=;)Lvy#WJskMkyG;=Fr3snBF1Y_&^ym2z8$^th4MRA@sO>6U!hUPrZ*i*HKDrn6o zkwp-y7T$fS)SM4NCsGrzgIpdBupKF@0A%KX31j&=xSqD840+Vm846(5ap&aDEscK{ z+aQqXLW-d=Q@@Xg&@OqJ1l>jrgy{3L=o}Rbh9~9eaA}+|a6s<(A08(grmv>&qZx&$ zS`ldea~iIgY~OB1xDS-eiI-Q;Q^a0F65RX_%e^&D4cZfrSI>jCMo}L{pGv+K*gJ}{ z9Xd=q&48+c5TU{Lkucxuqku48DTJ|dgaNN9)b*(!IGD(rspnSTYlo0BtJ2Jsi>A++ z&Yyt2e&zM;;r+9vZVH=w4)|1f7HMh@cFrcl9;)Cl=KDrfXAzrJ(<|?I>leyu&$==z zz}*oAP_T_No?aY;eW_YaGn<)Ec;j1QZ(LMZBrpS@*O;ayBchnuBI}gvYo%ff`w$6W zBK7XypZU{qH&5xV=EkR%sUYWm#2as2X=pzRRjJ#VM#%uR|nv zR{xcuYfYi`<6;>J!33zkf8nJ`D6LLXOdaaG7r}-q^)MdtLTGbO-kIXpssO}uZRfUA zM6$`^j9KYiLSiiSveNI|759C(;7dyIvFRsWCCuEXl#_W%5Wp3Z!>n|!YX{a!##onw zD!KBr1*}GdPC~rrag&pmJm-_;T>DDRUKL&Jmvq3~zn0Yg7{0~3GD@KqR3{H4zzPpb zxQf9sR_e%M=p21B!Q!+i)oy)B=jMc<-h>F-dWdN6@xDX{j$%nM{H)keiOYj)Ja!vt z?ebLw zP)<86`Dj~2pRZ^dzL4Q8uxp`FWmigeDa4~G>1*$s$k|RS3}6&lw({c0F-_zT?9t-E zgl7z>?y*=DTJyx(xSil@IKMq&b#qb<_9+#1*#rKWwG2E7<88GZo>C}I5P~k#xdWqZ^CdTNV z<4W6FouNkfKD5ie&IN%SSXAtC!VS|RnS#Djo*&pWHRWAa;<}uGOJ-kUhJ!qt(B@mt zeTY#WLJ`DAmcuhR&QCt!+GxqIOGwhhh&Uj*4aCOvAv}_7HnRzTx?#@=afR|5dUm?Z!7*J1x z>epEf(r{Owfev|CO|V1?t!K^@Bl?S_CUVU>i+UO^WT;eRO~&_?dT+)uCV`>nxi*tj z%L(f7du2Np%XX8?vnLgDj#8yYnEWimU7+#fkEs5M@}ewlB28(9$k5&7#V_pVA6P7@ z`nN4Gd^t-C;(yEnb9A?5wtU5qwzc@*!Q1}}3(Oz-KxL$S6Bj^9P@f~KzvfjhHCF!^ zs$QO{URkOJ6a`Q_0bGi=-zGpe39!Qg`~v~;yWlUpeUu!Ks#bSft$tJOKjZC9zt_?gSL!V6>a3#bthfDzw>L2Wc5xAPY3yv4`@*}GBE9!V5AQ!Zy+0vnKS!}X z`1L-YsQNCA`gmr2YLrqsMm|%DpC>oOnK$Gpnugfb$2B$NSJdx}YS`GcKSi3$IqnB-5#J3A@^P*-n6S&2N4vU*o;~QcN8w}=}?}-S%`e3&u zzz5n|*dp|T2tM8iD_xNQ7K~=M8nm*c<+hl#iHF9|HQyh`=`mXQ@r1W**e9;LbwAWx z18V?~)`+kRDPUI=>=6yNncGrp(6H|{Sf4`JPCp>cQS)NA49vD77NPTOShE1xO2;yQ z7Bc`y7vR4LkT*dX3uy7IL0fW>%XB|qBX z-s)Hf;8m7szo4FV zG)%O(9kpta>{BnizFlax9wM(G0@B3%iWXF)||bQ68z% zUgrHaNY|Sa(79$kF8l!d(vKJi>xd+0p{5W>)T5tZ3 z-U0ws%)^vKA1|vs`k<2hD4O(8NMwST8WE&Hj7bx$K>1>NqYv!u!)^=6$&U})0h~-{ z{;}iVp+=IEcIV}wJ#z2^;F*WGVqPC?q^DKf2OT+((}x;nx2#5k@{HaqmA!In@e0aq zMiO-J0i2X!PWmBDi~@4Nf#0v9oUZ{!B2FM{?LajHm3;tMj|0r;@QbwL zca_>a#0NrrQ8KszW)Jx296pnc(`AQM(_twhXyYvEus7h*hrvkDdcv~_=P?6cptpd* z?T^j|Zf_cK_%7f!}&14N|wOAP9duF$Cho=}3l-hJ)_N31#=%C%1{$b2| z-*Xr){AU!rx(@?+2=@7+3h7ApiU4(nHaPJ8T!G;HFoQ68eh6R>{b6)q$M%!ovA`|x zLVj z*S!FM8h}It0n|@;`NQl8UVXV@JcO(Tx{~@huinBGgL}+~fkm40XD2Lc74t zF%yr3gy~ttZ5o`!gFF==SwY{T}tgs+EQc<&YY9q$>s> zs5}yVzBk6V-HZXDq_$<#P;YqHOT4QXCh$rMrx#;0LqHoYDosGV%OGI;aFXR4bP%db zFB`1HL3SWFG7z~O(y_kgpQ(L!Qo(8wPP!Y0G7#BZ!Zr|^nnr$f4INmu60qcmH0B^8 zrhq>IRJJrM(2&&3Ks3za_#){SMcgWx{?iymXX7r>4rz*URgxR6;LRPgCcNi+CDrdd?KBRJ1(PXTa!vye0%@RfFt^aJ`zj2N1|v5x>t?GsBwVDE_$ zPw+Q$o}hGvyIv_jToB;NzmRu%0oNWPis@K23G%zpssl&Lq~qVvNY?&{2`+v=ea64- zx5$y-(o*|;-kya%?sF4BYegicu<4T+)yh72Y$g3>q|u1d@tblgX=@n7P!N7hfI9}l zOF4)l-U(RYEl+M|U~7yGh=Q~N+ZnK092ks>(nErPUyL|uSf zdf~)zHs(Bo*dTy+zZ+b`nQRsv_Rf_4Fk#;%=yoCICJh$Y8uyNkZ}2~hX*(lh(B8NEQmTw~+j zr4layuxUC}`{Ed?22DLd$QGkM&EiBH)a6NTI_oPi6=;znXnFr7`0Ww86NBXgX zuAIfwMYoK&@O!(lEFk^T`*SczGsbm2Rh*^g0E@beOQpcrQ=L_Amvn+9O~70_jM`Ta}#566!q z#s*I=mCiCR3}P9?)>)Kj17seAPt$MftRrjn5Y!mR77=EVN4UwwYlsljw<{>s;Y zy0`cg4)iXa1Jv;EBeX2QSkMRtTgaw zEem^N5Ml)6$WYrx6|%?o*sg%j??|4G+bEcWq0NJ9o*#CwE)0!=0119+Z(ps;qwT3Q zbpP%iEs!Zu6cm`pX(yb96`B$7Xl>VHqqhu(Tb*{TAXOi3u9k=DR{Rz7_U0xvI>!4~ zQJ|Ukeb1o&^6inQ*EB>oy>h(?D`I`BI`kU#thT9F-Y-iZseNL~EBz+rQ{9&K&G4zD zBpqHR{$lil#MJfT-9g8r7gERA_mAx_S%1{E@4Tg9Yv>{6mBC|B-UDuwR>>#V+CA1L zqwRbk=-ewn7HJl;9<{!p=_%tLT2MsJ@C@=&+_r#m-Ggatex+=CTQR=c!2KMvhPoV- zdQf}*!3#5*B#K#LO6~VjTcg=TYp_dd;%j5L{YOZ|`KDR;imT;I6@RN5uY;C1sbx)T z?uwXh$Mf0>)gnC?Cl0c@zQ00-aQM2_Ppf)X=AiitL0X3l^oK4VOguU*R8Z*c(}bA6 zXdW}_yScs0B>J=@x!r5|yr=|lF=}sK|EB+N^)}51Zr7?`N#6SHdd@yvtKjA{1|9z+ zIlW`!Y}Vm=j0K#ALx=H`_PRyEtY@THCVy$n+nRf8X4t-5kYs9Lp^zIo%-HL^eAFFk z<0#?D2K3D(p=??fO?tfiFq-!7<_C!9w>&8+#K&=2!4xfFUhzO&-4tZ6BfIb!afv60 z$75U>P?uj_k!#2vD@E{=n)xH^OTg!~1gf_@{B;vb%PL+IyE{l19<%2~pIXLU4T}cZ z0I}$Pb*P_Ss}FIisF!+T(N&47y0WOLl+QM2*30R?a#cHY5lm3nbWr%}mFYrFnXK7h z5L4jqdVN$Wvl63utsyL{t1j9QwntToQ#fwD{>RX^cI8QjQ*YZg`juWu zRzmLyy%Tzegc_uUDkvbmiAWJe5J*CRP=o*~U_nq+6!nh{!5Rb+5D^s>6%`bfrUIg( zV%WLgnLW>b_uOCh%--*QX5RZFACt+juIpUqar}B(q(fMnt0-}_7Fny6_szM4swp-z zBqW<)JA3(zICw(8$lm+qmRErN^6)qByY|bW?$F)lrxB9eg7qzur%GX1mf& zDxy;@YopLi(H$5S^9yb!b4T#Sxwj?0tA(gFd6YR(U#gv?<1XSe>dutjG;=*BT{3%A zr&e6WlXOfnNXQ%1S+3I!nDA}&~ya~)nO}bxzlsTz8A+DEmA?x zz@%-bO7)n1N+-M6^@rnbnxzY6wFd-Q;ph{&EQW-em(MYodpeytbiUN?zTSRzwO4)u zqPn9*FC_|IFu=i5>3y!u4}Dt6Y7*ONjMju1y#(Qq$sV8t;ltLC$OiP}r6RQg-(QbO zeq1LE3|UGlvafC{R_>Z{1vhcfFV9lRm>gHACF#D;K&su0UcchWEV->ocOs_E?*3wl zR-xZdLsXOIhbNXhkOc-V(hQ-i%1kue>ovt`b~Las$^>Z^lo2EC!i7xH`1UjcKnS3t zhGr#o+wMs0rZvLNv=KgIDYd1UM;{PYxN;IM%Kk51>9F7ElCny`EvFBYNrLM!)nNqB z#|+7#DoSe$hK!$x4Lvibj z3z^}fr4EO%u+1EYJ!ZwO4ZR(FIvKdL)qEOSq#7hn^`u&&o{C`urZv&&8w~dG?4*3U zPDOIy)%~(LRy`l1inMHL&^=6EkZdSY%^X09v{(>`>G%g5$&9Et@}&IB+|ANxgYx+4 z%-RgMi_A9%&L8s>AZ^Mq0{d&30fwK4$l(BT^D#6WO6s499iNW5Du z+W&CN1wP9R*dz^4w5P!R8lZ}`CY8~vEhgk31H3y4CU=-E+Z5j)hPE$?4v)H6$A(F) zWt1h~NTREwPlbmpzJf|b2fN0hoZLrIjNu+B31*Sy$GnS)&9TbkIYRqqdJ53R7VZ_n zmyl(7tB}S!#oHH)3{MJ*GlE>CTm*ddnG85-JO;5KeMKfcc!5f}{*)}oyF2}&S4nAU zB(S3&87k!ZkHzFA!`uo_n;>?IfL@h2cCZ?`hht8^3keKm$c$$2sQjO2u~9s;P|8$x zdkHbJyDbY61(84fUU`2`3G55K%5$TFxqbuuwi_!fl1Y)6hts$8N5SRa(joQf?$$Xk zo0Vl<%sx4uLSMduv}q(1$+NNJlX${Cc zq$3hq$r8uvF^%Ru5UMs5et(>!ycVUiv)OUE{S*Cp>>X{I@J>ugfR9Wjg$56LD62J& zhMc`nx$}l0n!&s-z4O%**P!FL2todV1rpb*CPj=BmxpB0Y<{(C?B*y(Dcxsj=Ia;qL<6rN0h8S_!eGo{P{M0*}* zt`3$k(`cnbhuL2@Z)PM5IPd`+c7Wq09tp*jnCn#QF5zE{0~8p@3gp)Ep9bAT<)2Ds*;ay6K#4pL75D;$8`|5G;Xg}V2y zJFAv!Hd-?J#G>$#c4H32mIc`9lt`^`V{!ljYrnJ{6s3=pjw^}j;u(e;=QKkS#v#TH zeUxVPM>#xs8Z=R-x1d4QCSBz${i^7t3bX?VX2SAVoRl_$ooqrfALY9bnJ0iq)hJri zBn{D!IOn~GyJ3$vxSC7?4NO7&4BV7o>S+$8zw1`(s8#0oc`m>SG|nv89pA>ioP#`j z36-~4;#a{ybU7GE$ z4s2MFj%V4}K*^pWjL@unfU8#n;q*1sGLa>CI4yui>L_WNKu9y8>;=X>5K^}~SXXvk zcCuKCJ+Ngrd*022nzBwgx86 z;v74ROvxF{AJfQ(`)bX5j0&tY)@;B3=D&H#Q{(HN+9%D!wsD;WiGmRyHsdQt|K5XWi&hvb0#Ig6b zhxxlJms9;dT?AHJPxx5-{j+Se$+p(syLLWk^tS;wakBf(nbC%v5?A!-(q&)h5!ja- zC(P54dI7ED)@{qKzN!_v3-*tOYeq3oAHKQh>vm@pp^Par_1lw&w3;u%2u{ofJ)AO^ znbLUNr8lA;{8%&ou~z&=xf)nJAd=E9ZhylqRTdnfo_PF*dcbg7yTs^A z;*xFp&VS5Z%-jF1+*E|}SS&SPL|7CD9a9H#X4v(QAA9(Wc}v^6ON)8?V1_iL2kzx( z7ktc)o7KisOA2m8KMngp54iF){AsXOeu*qi0qqccCFfE2xu;=QokP!eD19lmMDPX%gbClkkX5YVw{8{<$nA?r*Z0wGgme-^27Z1 zhBf9OK{m=K2X!K#;;=HZsn}c1mgCUX)2X*9d%Q?#>wX>iXQ%e?BHK!xn_sp+eRWg$ z$=L^E=OQl2kMFxq7aV^5g8v!6=6dniIUDaG?poYlI7Iu_iHV^T0k?~Tgc6voXT^m9Ey+S&eQ-f~@o7FM;<=l^>;5aj!0RW7 zvbR?zrZgjMK0i)-8jd)0$czc20Yw&cV4;R%(-Cp;<{7JTvgDL%wI?)Z2DUp1fprl~ zU!>Yu7hOE4{D{d@XR#w@)PlzmAZAN-7bLQ)Da34erh6;ryTnpQ(|2SVDTy)-EJbP@=H%-~!t) zdmQ1bQz9J%>8cx$A4fEom8Ju?s~Dwm={)5DH`6HSmIj{vw3mXgBz(HGV6k+ynYMXx zvS2`T7e((B$dxKgmMH7Qn9h_&Bo&|EKz_5oEQ40&v*(=^^F9Gt?8CcKFpezc+2D7+ zw`xL5>>QiUhuCwsXlGu>wB6U0+nlyo8r`M5dal$Q4dED=VA~*aQAlrL5YSu{H_FSk zF0olEzO7-)+#vMg^O!6XBEPyTN<7*tF%lw(%u>rH{)uVCdm15lvNV?2^kF3?deEbt+ZTa!Q{`c-aZ?a6wHE2+Ep@mrKsTv3il|zOf zFDi~f9-HFJb^uwE>}_drk&`94qdZK*Lmwvl@?@Px$xo~Spa1x3bS|llIsJc%> zAq2Fb5L#AqktDNC5^a&(R;;69Y#e?GIdHoEC9gQZRUrDBv2VJZ4K2D~l(4UaM$q1}sP}kwxLh*7Bc9NYtKyE}%iA zoZhbo&2o>3g(FZ~c9BdiN^d|d(D9a0NRog)<HYQ;ut}>hsWg z(w7O7oWUSzbE~1UC3Uos!)RI>yI=-L)EleOx2b#q$^=Cu61aqTlha)MdfRwtQX^4~o4uMrt zmCKi9v6Y*lgyzp{$mO)x+Zhq_iQq);;nFm_;e#BW&A#TKXABuQGwgW)0^oGeo~=U z``K}IiGtb@r=00vm>C(0yc(i+_(RFX&o8u$7h$UM-fT1YAoR*2Z70jaRPN|QoudXn zVOs{FXQE2{WU??tQ&QcqnGZw~4Us9AE&Uw#@><)sP|o&r<18;r%VtR8;y=kzsL4Cu zH_bz>=uaLa$}-Z~p1>P=;!Y3c6??-$^98aSA{z=b97i$If#REAgazVPr5p(%!n13> z|CPF2&hIQ{$rzx0#$Th~L18IlR)wA4}s3KoMk(7ti-za8$&ptj(iF z?%aOTOYE^@m8STY_SQaAWR_4^h}6VV>kjD|a}*ZM#mOOSmM;xq;p)ty=!S@F>)S0S z(j#fmMHHXHc>JBwD&Df;J?K{ znv*`emn!clXA_=M`9UAVM=6|U+x;BR*De+Y(&cleZPnrdr>p6 z^RsB~yZ(4bc;01@=4tTqa;VAWuH|gRQT3-E9m6=_7gCs)wC%V?`)q~uG5WA_Kpz?C zn)x{LTK%2zGikLd@cfVI!6vHykMcd1aStbM6w32My>~qU{ry%IKzC2DRxVEf}->g$618aZLWhni7ML9w~3I7ML3Ffi+Ov)YpXFkWbg1_mzcernZSuM zg4LOtX7Eg;^p%Em^%^&Y-EpO+YJ=f>y==>#nz=2R;rI83wzz9rJ9KN-N95}6uicXS zw6!5#W*n#PaQb+ag4VD}cw@$p&8Ld!$GfE)ccnERLc6Tm@C=t{ZFE|SO%qSGus$^E z9N%mEyRC)qTt0fNZd0!A$;M8d+1CAH-oCA+{F>4!L9VUdnbT*k=>?u}%++hZFl0M_ z{Jbqz|6KRnwfXu^%X0mH#Jqj#k@)Ba-HyJ0@%Ar%4JBQC@Gssz%HdGb<){DR?foLQ zCUw3pxBGJWt&D@*Y~RgnA?G2+mH-+Z#FCpX!s|dSi(vd%w}-9<{>Y$h{1%y_mN@9-P^z-M(fz z{1kKc^nH_8doEoyQ-38h;_ydF=6=kxHM2*0sJ|aZ-Sm~0A4O{2G#`_)di~*v|K|L~ zvG9<5i|3+*n;)JDBqy^7Z5Xz0(uf^F9l&JwD#F zn!NsF+w@zGqS5@Mn@oZA`_o#!)9?0r5$4`oxjWi?tPe?h|DiGO%gjf!og$mr;u6C9 znG@~4pJt6u-m+bY>HP9;{=%zU9~KOs71(|C_&DBdT3KC{C?!XlRiAieI;B0U>`cH~=i{iV*J+}MAoSNJ% zRd*e}^MneC8rP@(*bvuA$4i*yoR>58=e$p68y0|fiKf-%!5S25gAkq{cT6$#GEe4u zZ6C^TsUmhWL!B}TcL<^bduRGJM%&zVuidXm>wAl(0i4XsbmD_Hy8Hsu9n~RH+q*IB zc+tyMkMoF3!Rf9zy-lOs1alVv8r3@7HRIsqdoh2%0ZPlbd(d^~amA|3SdB~Sg8}ZJ z_ce^gnUPOdr-eextEXdcGCjS%1(Tr?!=t3WzZfA?v( zL`TU3!s+Mcr@I|ctx>Rq;aICHWS_a=fWsRCA6asK_!JjEIpPrhX~)athYzO$ZGTTB ze*7(eq!79z6Ix7Q;9hnWIcm3H9h6*-ydNz^8XbGksN?oC!C&jC*>RULP0tPIwOOyTzFhO9=?4X$-k2wcHW{9ZdZFvr^6|+(fQ6`SgW_C+ zZA#EO^@_e*#N5M!L;Ky=dXR)Jy^u?1o}SCR(jNI&FSJ+h*@fCGXA>OsH$6G??8?O} z=hE`^!{6&Y?|yvceDYnPDx6m3_k9v5I0r<*^v8S1ogF0(1~HQD<9AIvFI43l#Hs7Q z7!2sVc;Kc%f?4~E2brChn)WV04zf8nQ_4Z8PSKXY_LomDc3wH3ZAUcL3#Fuh2BVv2mVtN-;jM5>_7V2>VdCLN$2(|IPGoz^*$ow`e71XQw2xx_VVaG`@?$J5pU& zpO&LZdEnW1(wDmK8WosS#u~hrf6~=&dCR0K>+E~wZ(a8sMJ9qGgK4T{_kg>jX>Hxv zX-%{4LEi$?ea8(x=mvHVh1@bdaOvy^!>sQ6kw3Q3fqxTj1<;U-|D3_aLx@dC2xtaP z3XwUu-5_xrLZ1b-1|%BR>xeRIF<`Z1=(<@M zZOfG}Gaz#Fl=Q72R+^B2P`y;5jDmu=v;knJ(FS!aQfNq7h|9E>N>Ywvw+kdV(Ik5x zU}dEjoQmWDfTsuGs&2YxTiC5alp--4;wLtu&~zFz(cZUA3kZkszUF2AEIMo`O6t8+h4xp&nANG*()FAk{>#E6WTI-7| zEP##H26>9&h6ki35Hug*$O}SN$QpPuN%73hPj^AAm^K|Ys@!0D%0}Eyo|Ji*YFZg$ ztD>}~70y~+skD%61F&v;yWTU^x3r|}ZMEm*c?oR2@B3)`ZY267Xzp<~-K=cPKj_*L z8($@WNvaXOf}Oc)2z?bvMFU6(RVrr#L=H!fOY|vAvI)s{JETeocay*(tx7xy3A#fx&2Oo6qoFK%7YLa4>l_q#JiwfB4o>*q(buWaUsO~(3nfb)T=U} zV2^*NE!+Ym@5+&~rrKHR_%8$+S$pDC)WMC(wq2}-DD8?G4H+gCuffH8sUeBbrU6Ch7c>E%guyLpf{1 zAYq(jUB!RGt^cdStxxclAKTtn+5eb%Urk5qP77RRlBa8!TBoOLy)9>_>sao(ANB=> zB!Ac+njo%lw%hL?ViInNdT29f9UZ8thf%xhr0-30Zn&=;?Pg~`4NBU;UYvd`=QasV zOmk*(5HjodO>Oisoz_oY&TCYG_4cM6_i#&VjjzbC-OJVbA$W77jG6`oG=1z%t(3HP z{Y_Ss&dDs=AgbGR@Q}m082_}7o1jCSnK@>G>JDq_n6vu`l=*F6(5827?yb2vqutIf zRs+Wfm*M1Xao>mlYH+#wD)QIVBYe+|ML+b@)iCnOy$yK=X*Vg~{I1{G$@)y+I;1`- z)_#6hBT}ue%{x6BrzfH8`I0pfgLqfS8JM&)hj1runP__*y~7ue#kt~owLm?PDCtRB zb{d0!OWVHBv`AoY>pF#1(2!giAsJV;Tq49One;m)tsOt_vPlV2P8Yv%;7b;yv6!Z| zc-K2d$*nbQa_dsD9F@N->cgkhJmPws&Mj9WbVB-K7ly8c1rD2H!`KW{vMW_^8yd#B zE3(l-2l43V)+Bpfmk#a**rxLl2~ZFcEkG+=iWNtXG#mfEblQaSY4 zII`P`)K|!l&XG~H%GZAt;_kK|l%@7IbBORX=VFide@I7GVwaRmlKv14?xBl((f^?L zvCpiBUZ~Kh_0tF3?yQ*!rKIAjbLTqUE{>|fr}nndOFCx%v_K7GiWsG!RE5-$8Nr3S z>v%JdZ!n1d0vt8Vqib+zWtGix&TaIY_F96k96!r;#Vwb<>;zK;2Kg{*kfMmgD}+~5 zVke8P?OUw2VmqUHnN#$CEi5RzB992@XfCo!g52k|eUV4Vwl3L;p7ar=EArb8P>OsV zXnQ~t)uGWufdn!|wnK7YIR+>$)a>QMez|DzdhS#`&<`~-XX>^JZhFVX$o6(j(ubN& zpM)7m&QN->?zGE=-T+oT)cN|@PFM5>*D_cSg<&~E*e1?M4l^~(kR4F}7w?rHDWZuB z6cDq>DTFR<>Y!CtX3jf~xbGp5*s?^m0nYfM>Zm?i zPKIQFHHTqYI(H$XJ$JbPPPum|o}tIM#Q{dC&T>pVnT^7=4eu|ALmM7+!vszqi>;?I z4H21MMd@A?#Z}bsrbqZKhR#H&xTNw;$BmS=UKm;g2{e1;&+8#gABIBO=u&dfcYj@Q zHGL;jqfQIdM6p@xpbJz$;c_d#sebTAE4Pi7U6!7be%}n}m_~bgi(C{}luGFR(cm`r zz7x+czjqM=4;L_S(#b%bRJAsc7DcCQaDgelq;Sj0)5qj}&`xnQLG$0(*5g@R*_#3c z8QrR!G$R~Mooq|$fJit9?$KJMN&6EPR0lgw`);{Z$y|819dn^bh1UYBKvXICT01+D zzoT66-V`lniu0RBgj_-x?jl=I`uCLyuRb4242Of}Oxhg}l zD@o&~Jmt+Bov*CkBM>IJywypli^#*e{#aQd<(FzPo2i1W8Rm8=xlGj(H#sw0WKQ8Z}c7L{khXe4Qlf z`(x>J^+O$eQhz15$!hw(>Jox!oguJz{_+{Z#Q7`&x=AB@Wxt?~iU@5IkK?$DFr?E6A2q}fP+a<8xxW1RHae&}9Zfx$gOVQjo5qoXy?~y+ z>}YmgAS#f$+_ zwO&v}rP`FMEdcpmnBH6@$FOrzF#*Ht71W{YrO;*u{FY)LsYMI@%p_)&o37x4)9uk9 z$7C0oJKDdJ8R$SQCj?Xa-pkrRZ~=l8h0&-%LNeX?u)hEXLRQv8?HP+y0aMlWEt>S( zsnZCI$4^ic8g?+c1RUC!3N>%io*|6;T#}=&w9d?C-uVn#bijU`W{Q>&BIj@1JzUD0 zIqrh$yHB|dfs{27isWwmcU)ch4XVeC@i82OUQnXeIWa%%Hx?W!)Lz17Kqwhd4hvyO zCqTdF6Cm(YCqevT_PQW`9mvN(kPkIUJQ~=4NdgMNX!fAjC-ZuLg3F(_LZgWvHzu8N z1z(R!C~y+CAAnEnA|?IY1!b_)thTJvl7l;unxiQA671G<#@H#aN&teRBnkE|{3BEu zfPVi8obC|6Lu|3e}BB>heUrW&B zdA^cAYJC!kTuPxcGd-A~XA)-llh|i3NA=`#N=RUH2@FyOzkon>5U?Z&j8qI|6dxH- zE?Ol1`bo^LhaCJtoM1!iw2NRZ*inOI#%;fIFelrLgDZJVZ*e~kgkG8p5lUlX*At2SNjC6dfHngxTmG#;mw zz-r);R^ZeRo+Jl|+no+0?UQ^|jbfl@X~g`t#>O&V*b;(u6}Z|~L*xjMRP3%_#6poL zDRYrHriB)if>qt1ljo83=AwxfD1t<&v)bDCgZO@i6tOeAwgH3#hhMD{Uk`u>>EZ+^ zxbHGJ0TNXBU>FO0&_z1m4JITWCbgux3!wjW5+22+#_lF1B<@K}26dTysDI=i0fz(D z_gO}Ijw0^ske-EsSV1Ww1E*|Dvi^}$X90dsPnn&@{n2GrdhSM3_ZLdBVM!`|ug907fM# zfuuBvW3+;|fP?TF++3j)AZ_jFIz#q91A{;!CXvy?lcXLr>s|`{0{*Bl@bYpUtOj?@vQnx?qAI;P zBa15^z3r6{L$@u2G0Ir(r0i2U^BKF>1<*KK5G6Q<@K2r{J@sIhM6MM`P#{GB64)1r zU)_aJ27Ez*1n|KVj9 z(cwBQBRf0gQ7B4JozQ45sJA|I{MQ z0p~y2z^5&*9l;$HqDW`$jymncVjIBpP?A?Ym`yv1o2pux>4tJ*5f>m!y5K+aq|QZr zXOwt>z6x+lyVLG}QzVlo*HV_=wqFzq=FfwtM~__iLuA%t=h)lbh1;Kcfu}pJ!!KZ2 z_qvY@Gt9|ba^<=xoZJ7*k*}A7rR}#7{yd4A8^$e=xg2s1AVfw>{8c2CUI*7+ro4G{ zL|(TiYYMMU1n=i}XUzcFvV#DoSypN6TC0;UaN9Pv%gw&@jB$ za!;={o+P_T^56jU?V(*#SLx5a``5&bZ%bXto^W1w?sMi#F z4Cgv-`0tT#5E5skQ(X#^xNd@1{A4b*;tg|cgyze|!_go&^gNbwr6s1018b($|?;1lBC>JTRp1cCjDlx}p*&^irUzu1d|T)TGoI`RV6 zh?RIL9k`P^XqO?eRjd1i9cVGRHIs%N``DWB328A1c6LB=C_*f?2#$in8L@qXV5Sh$ zFT6mkDX8wqE)54iuMyu}?;pA$d8QtGG+by7sipt}x0Ar=k4+oXhe!;>j!mFBa2J)q z`F>XNlniclp%}{1{uPDC9(jO*3=n?#L3?shQ;q9NgU2TihR)w7GRkggi#VGqpz+T} zogj}u>N#Qy@?$C7U>HpE0`PWVLh9r7bfD>Kmjnadk_*;@T^sq`Mtdb`ufemM(~o+Q z<5K$xQ;BoWFXAtts1YE?7F4`3X5UhWdn8119@*)GXFz1d>f*E*L{^#dbJ=@o9 zNp?}su#bufq+@(9@xNh6lVR7cZizf_r%o7{bfXD%0W~K_xOHvxz+Oo<1zzr~!5;sf@EzziMQ zz2rzAZh-E2;8VjXUH|%ld~hB`(w^jM8i8r&@LUNJJN-QhQa0dlcO!j#U3d|B0mIX( zoc0paL+{bidp7(f%6@rA&-I~AgX8x9FUZ}Eg$8n)Aa-qLqVW4SY=+?sUn}re4!l=sU z$Bv)JB0oPX_&nb9`Q@$86K_7h5q+Li`QkMNbELrlRR8qmEA^|C8GuS(B=*rHbdD|uaWGiovT5Nr>!aUn zZ-29Y`^|Cv8(nq9*=faf%Zhv93iIg7hTALNZ&%2iufAU;S*qXd{oe)^eh)qRebepl z;cvf3u78hG{So8zBW}x&gu)+5M}MT;{*n6jN80+2bk(1kPCs{S`MI<3XU@@|xwn7r zdHb_q{U=*>mFu+1+p=0xxXM4eT7G-A^6hHX`l>+nSEVZW@5c0ixNb#=i2)SOfiNXJ zBGL)%D%m<{yx9e{yL8ux6?qN>P{IuDUMm%4$2{R}sgX9E3M{7gujlm6c zi&Hk5C->&%yB+7KPydA7L zyMU%aPM&!hYQ>eC&g^vIF#ua9ZDVsuPCCR{PHhXD z#E1u22AKUY=r%v*h!a+t7ChxeD{ci9$Obo+WFn12dcE|scAJG1;x{Q8pYm{>Vf}JibQ#PpHAyUYf zs#OkN36!*#^StIjjuOXLV`%PbjuAWHEI=+TS|vjuU0C9fof7jhPL35Xe}F*t(>K3q z1dp_kry41yYXD|ah{(;-YK}S^Vr5UNiOC}MeTo2H+2MeeQ&_z3P?%GViFz;~nVP=& z-d1@7S!olf@1(zFsI60)p>t77XmzG?;ZBKN|3Whz_g2FEwmH0TajuJU?GH3@^|kPb zQ7iK?+L@%WugLzKFFt>}aWp1+JWu12s^;D-)7o5|q=ZXw0>MXVA}<}}zv*qL zYatuSAxmz|(6MoJ?_*$hMOfZ;F+QUw5$vPsY3j2hPI^0u5Q`UElgN5WVtRo}MT`nX zo>q87GSR^_AqHo)f#7PWoE_l5;9|Kuhu|*npB!vdW(`kplH-vjiw+>^2}X|Qc7i7R zVIy2xIxK~U*Px3vsnEq_oL{8tWB?E>Z@Z6S+U_E0NHHoG81FN*?ubpENsRB=A?7vB zQv{lEffi}*@RB`HhCX?tvz(Eg{e70CETEl{3*8c9pSWB0pn*ol7QmPwUz;V9Kz8fZ z^?c=wrC=Q^to}2m|6etxf4|rI|H+u%yO9<`X+}#y>+hEx$NHSV5yVpq5|^r|+4}>W zL-R}2Q5%>vq*v1q>>I?EPf#CvUtV<9&WoJR)G2ozU+!l>y`w!ul8 zQ$%4cdO%d{T2>GJ16pI)L)Lm(!dbMR5LZIKo(ey8>Xt+^X!z-JMkyn92e#L#YM;qj zw|M+MoD`z${Q&*J+WTIo^pKQc%x$#P2BS=t)O{y25ZBw0_%7uT8vkqgp)Q_N!#V^; zbBgH+`%Jd!)r!mpoUYtiBB~=_uts6h@qZ+oU^i2YGE&*s1T9d=yL)<2#u{k%h^H>F zf!zf>8A}E{T}Dro`2)mJgUX1037w1e{JqAKuLXnP>ZOc^pUXGD?)^V4=bf6@)o0CAPLnNg1~-fZZxMrsM=8R6Yt`10 z=V}I>1TJrWr}6y$3}~)bg0)V_B_j~gnqvY=JSO&CbTV(fkCE5%gcu;=d(= z$jLCVv?z0R;I2{bQbiR0xZFKvzv;oH$|UdO3eN}nEw3%@O|Lwz^oi;5juz!IqT{AZ zevIZUJPOzA8ddH+#*8PfMAIdM9)H)u9tI)$r7-|>5EoXT3 zP+cCav6hG#_&Aq45W$ieKywJPha*ZI6+X}_bIy=Y&4D#E& z?5&~(Y~9`wph10-St34~ssyblPYR^$4a7*gJCC#t2-WV#VNG;vk;_p+%5w@|u5UM( z{$^O~X%yf5Yz#J#)~YOh`lYuTmYhjx1>i4QS^OnIKy$MbC75qZFeS#lgh$v4(Vx)) zm6fz!%}IN{k?4_MZJfCV>id8t?_yvnaEVf%3hE&P_LeBLN{4%fi|jxatkpdXM~5@HS~dU_(AZ*?>`4r$imd6+jj z1J2nOCEP;#lqrKA&Ik4Ov1=0{WUyS0u>7Qi^5KWT}M-*q1J84Ho5|d?|R%ZH19cT-^xK<2P35RF9`j5P!T< zzWBS=@tb^Uu=e$Vj!yMDSM)VBnO|`^^O}y&h8@z&BUL$Ejko8Bw!~$~AvM>ID-xb6 zopu6yR}S^Z-GM#2TufuoPd)B9bGP9;AD%`7g>iR~{VNtybM~p$>+B7CTxz3Ijf&gh zQ|%samWG=4O{&fh5EYyQ#(2{b>aWI~L(+fMgazeLCHu=&d@sJMTxn28S@MHXrZ`2z zJN83=r)$XV(^_VO7kpL7b;r-jgtLAPg^pZ2a5Y@R&#_P5FwCI-p7%%7L%)WTWQIhE zD5z)AM3-K-vZwLm1<5)rPddY4z;@zywL|2}8PLe;O-Ft9^ z>3)26kK_$4csJDKqtjvrXlDn%IDV5^qq|Mo`+X}!dg&rP2ut$U;9PI*-=NUcet;sW z(S-1Xo{xR7-$iGc_@{NS#jW;L1abQ5jUV0>ikZB1tv^Nw!4)rh1X<+x$Kc>ej8z%7 zMt$W6<^C7hGbut>k-Q@~!F5+9x{k+Gwfo$}z>iGfcm0kLyQP zPTKvQWmR7rJLUWR%;vvyp%1S;yE^jyeBR&rDB|_;d%iy|9{RhGe_s?>hHp+f* zhcAuGt0NT!KZV3wi=}>R?-bWp2mEd=*FRkQXt(}rwEEV{DZhN}gy!G2-HtypHj}$P z=dJ&lB;H=V=O!Ls6MtnIo z)S3+sWg~Og=ms{ni;W*=i-X&s9EWVpkqYI=2#?C zLriUTSgW&4T)LR?^m`d5?JF0;+ab2w#gdG6yRwaopUtp7t?+>O_$ZdwfmjW(EUH#( zEf&xPx9_^)!e~Si8QAc7E0G~fRrGLt8KtH zFHCm4TuPFdQPsVyq?)@;zKs7r)t>+7kN^F?f&W}H{<~f?{Ate!;U&6arR>cWhFB?U zJPKNqTEt4(yJtd%A-=E3L!JqTF^r;uis^;8LI3vQ;3rzX!@C7h?>V(k@*9f0CD!W41%G2L!-ypG-{f0 zZuQ7wHIcLzIJ?t&?VGcw656%bnW|*h1gKM~|L<4b(^J?PF$SrXT1b_DDP>Pp^He7rkK0lfUTM%T&<>WsG{?gIkgbi{-~8F{x*>B zV;Y(n&9I<<1G>W;IsXN^@8XF2a)cATW$de>42^~Vp_JX)1m#+laNWG5&RgHmGx%?! zl>L7~zWiS#QmOyDM4EG({cl%h|Hmv2u4D;pjzh|$j5a3&K2{JY7GY{ZF3tkvm2{KR zWa2yk2XRY3fY|B-WTylAibzhmRd$uS1p|Od0`dd}FNR)bk&*$;BzzCxpaGbBOFLTu zo&nf{`;|85(D((Qz99rBsp+=~P;(JaMbLNvu{{AVuLi>tr8CL^S5usoAH+Z%$l3#t z0MwQcfGlOPIRFUUAbaW)58n(pZUllufv|A9>}^spOx<8-ki&z~9A)@Bv6gCpt2>e^4cO}f%niid8Gu_b z-r85k)eEpBpv0TRcD4#mRse;HvvxE_PrrP`f>G$NYZ`{$)Xm|hIeJn4fOO&Ih0Oczbkkn7 zZ93kry?rt`6Y|#7=C6a!Jzj?QWOuJEZKSuGo8IzL-*$-Rfx7~x zVM&Oz*q(W**H^V!d~?Dd`gTkH(oMrhGV=L5Hu@b8`pNWs`{0NOuaur%rUhdzLg2j0 zo;ymrJE&~wxTHHpFVV_?5N#?1Y_9ug2VWgkzQ5OGpwUd?^G*E^%D;0Ss?eM<3L11i zfhLDzQ?X7lo+{98is0p$_D4l(5fPYRYEaO_^wV|!lw*8xV1 zaPsnWnem>ix;HnQOK){s2=*LNY=mkjIgFfsNd44H5l2YQJ@EO;m9SA=IkS<`<*A*G zN>`;Wf_AZnX+-1nJIW#m-=|rav!3Kkd;>rg?Ku!ps$3+-1a${mP|d}AtHMl7|G}vF z=etdeLr%;%U>z5;f_5DHR*CK*g>27#%4orc={UXgQdiOhd+>DyFAZ^E@uv;1m9S4M!txk5E{Hi|I?VyIA z>Ri#f!~Px6+`AT^VcI>IY`TJuEz$uu^l;0u)5oU%5BAz4#_PkR`FN6{xKp^xQ zdguwg8;W!=^bP?LK|w?B60jkn21G;=ji7*lN^hbDY*_zb@1mljB2J$5&a9bp*1OJ} z_rsYrv*!7huUWZQ?%%bqYwvx#!^`!T>GoFtde6)1?zAuSG)dA)y|h!_7j^FB`(4b1 zY)*1Z7`PK}?t5~4Imw~r5<_6O3bgaJ&?_8Yu8S((W1B)qQr->Lp1rvd`Y(evX}Ere_^rpuZwn1?FD6|zO=Vth@*mN0?P_jozy3r%$VkXbmGvHv`1;nhJyhms;d{-% zxJ&nLCG&-WT^nzrq^*6%HFC0*FLW6l>w9qXO?K~IY$tTyiZZ9OdeY+e)OjmU-zS@S zq)N^x{3-Y5lZEfS`vTH3x(2=Oh4!7e*u{YmEuWmqvglOWf5zq<`p#65RH21p8w@G0 z_-yv(-t^KD(8hd-3}Luc8Ucnkasb<0(-SY9)6&1?`NPir=ws@hp=s7czF)ZAd7ydv z+mpfITY*_uC^>ryBKkxAjBY;r{>zy7vbAcrh)N;~DMfFt-H8nSe){?2NF&eh3xs>U zqcZWD{FFJLt-Uh+m-E}#+qAFtKA8hubIxQzMU^as8gvV%P>FIN;yd`jXoB>+8gJCH zqV>(rU1nJuMN{E7DEPaljGVK!i|tm?mkPnM+(L&B6a*`ABn$0bsAHn5s}bBJ z!dLz=Gzsiu*%=zk&BcGf=ABvcHcYq2B)M+cQ4zYD?ff!4-Vg7)@SyZYj}9G-9tQz9 zH^mpRD|(iG2UTuMA2_KjHkXBF+&gn3yEt#=Vbs;Y0|jbd+AP+SIOv@hMF%p@?SB9M zeSO`%E49h>c_*Zwdc-|=w9(sl9Q=`MapG2xcHgCgPhX_h?R@-cM_>OjY(_`!cHQW+ zKhAJ;3=gdQK9YH@@5)vEx$g^M2S3^}wo80WkKMHS@$tLguilwk$(}keLO6+tlg7}-A{>-znH-{^Z@_4;!c(0S7?m`)s-CA`d#ss7Kh&`C8d@f>H31h%cjTraBL3l`PNW# z`I`1sVd|}2vAe7r|!V^#*N2S`3aXE zxu@l3zx(|8edH8JJ&3iVe{@sU-cHqeJv9g7d!P!!aa-`hC zWAo|qi-k&Sg;a8pDx*j(xJV1${+Ye|Bv(egR9zetNv^G{JAyLjWx6VHFJyqmwf)$75}MB z3827Y4wcYn65L|}hyp}Uo)(@%HgJF{nNhS10YwNVD-9LuqMnybc4q_9iU3PP++zlK zIN2vA!(*bL(llsE7Fb`;i0uXt5t6GjyWDC9}7Z{6Z0Qc`%xSlA`DaxFAR{Kmi4f4Uo>s zH{MwenD{^ybU;qluG}niQiAlhSg?_vikXc`a4OcvLxLTy!}77&xfNrkrM4*)gq8xd zRi)HT5IB@{-ZrcQl@OY)>R|$kNmEqUlw%lzR0(FK#elg9nyQ134hJamQW^>%hN@-I z7JNz=V9CVjDbhpzAvA08G6v=5tCX-6K&vVQ`C>Bia3&f6R26}fMw6tN$q7J!Gp#ra z@byEu+5=RKBFzNs86+MS0aXCZP{PEfzzvN6c~wapP3QDfxSAf#(p@Tbt4dJ`GRl__ z<;DsKQ!;a)WF_gC+XC)p(msjUATGp72bq^Er(`P?xm8kGLp?B@VqoK$oTXn~X;N8; zijF63az}-vqSBJYv$6{}NseNQFkt zUP95@(wKraA?3cZ`5h2gK2b_^rsoMMkU}e?+Lz| z`-PxcRc^<;aP0Wz6?d#=iEtR)PUln#3Hq6UhGsu!L>|Sjc_3rT^{ax$GovQuQ|Op_ z!nSdvBACUiGd)RQx6dqcnHIo=%1yV7Ipw8(ag^0!{4q`P{pWL`Y-wtm@4YQSwmC_j zrhZiz{^|&n1jEdI+t7%}UjF{D{xhMT;8{a;gy;oF!HBtudc=pjs0zu;xI@4U;Q2NZKSp_rBHMU4{LAJQ2883;W zVzb7kdt!Ra$x7M7Fu+!89xO=!{^9p3Q(MGp9`)Ez^e>GI<7&lopItHPHDIKg#n4AK z)Dgqsk)$Xi13*d->!||tLP>-9BKE#QNltF@gYT{fADER8 zh;I&JZCGH*#X_lEk%F_15GKLpqAU^PP;GLfp4)ghK9?!KAU+&u8!jLkv1Fp%9FL!0 zDgVE@h6w#!75qPU4KZc);Pal`x#LPdrl;(FeXiqS#Ng^a?I8b!U*XlJDmydnV)dn- zU(=?h=|1BT`lX?9u1&*YX2xg7m%Rea2`yLmS-++)`#QW%=mgEo1`d4LFRVGCm*9Ra z^ud<{{c|S_w$EIP{PpGFFs7Ye?mibS|Fv=2tKInE%v`+H*F&P3cGHvY*ONoPHZ9Jz zoA=FJPv7zN@Dk=EW6J$TR@2w!*Ip;B?#EZ6VZlIB9y!SB0*&lCO8TSj_rn{YT9z3?p7{ z>iz45!3T3ece;k(iDvszc$3hCTt%AX7jG z1lsHanEAt^gKRQ0%nFVG`bN^$CJ+)5aJB{DN+dD`6kGtv*n;J-fGHChzFEo7RlzSu zH7wPnxEQpp2p7Xa#ivNs#DkcDfQcDgQ;BZpqnYN5bEb$%gkBK~MWN6sSqPG%RpCxj zWHim#nxLd95fqI(Q00`CqTm-G<^`md0X}mlNRk4wG?lL2s$$N9sA_3(>rpY`nt?e+ zVSx}y5?UQER#zp|7}%6Fnw2(?8Vw}*VjP?#?4;5D-V#2>P+c@d%Rowvj25RlqcLO- z55TAa^74QO8*HpEVav2jNI=(=$XPg9?9QW?)I03V#Cz(Ke8j~=SU{C7zdiL|nBeb^ zAV^Sh3jhp7Wp@=eGKrX)i8LoD1qEByHUY7=XnQlH99mLZ!89z7=IjqPwZ{;p(2kaX zt1dYy7G{h=8arUMWzfd@K%#>}f-4~-Q9CLK(DPEX@IgmJpd11WigzM(7}(@3xazF{ zji#4hA>FV;%ie?~7Pu8vfVeEl=ydspRE3a0>+k?jTqqz(Wq1bz6dPhrHeg1k8o86z z&78SuFe?T^!S{tXfThKTQKOQJ9ne3MLSd z^B#2BaOMom#zy>nPBz`;$tl0?9`6t47r8j;QI=7ZXF5G7gM>Dbeo|6wD@zvk9F5e? zVFUAUz1M)ZY*F_7gz@_1=!c;e#p5H=TULk@xO69?r`%t2;e?F!doD{{Ir(gi=Abvn z-4epv=Z2OESn@;xsUjxI!Ra|m>8O+MHHD~kRgl&U1^6&?v@5^XWSDAvVFqvZbVil5 zkL{tIEoF>ChGM);6O`PxPH6_#j8xOoZP30D6jX8=8<^TXL*R{rT;wEDAK!&x zC~cMUmC%T6{w1i zo;jzv5Fr)cQHJdPT*|7c%g+?!k`tYvk_*6_mx`fN!-WdL4nV)>w0!L65|va3sOlnA zVqFAEsaWPA0_NaS3kV+(6=YVSLLFE_qhbjvy6j=YqoPV5H9&IXDz6ml^SPMNCe(ML zkAb~Zp<&a7NUy{)omg%KCh@w8(h}ILwF2wP2E)kwakaC#PBZH*pj0=SLgWhgrSo(A zZntNUNPdyj84xO6@Bd>EQTAVki1|G`R*zG$r`%jU78;sX+qAq-c?8Wa92i(VL9adK zmEdu!>A`9{QL`rvpxU+&LZk z?ercb;y*EZh}~fSk~V4pWIG6OO9V)&CjN1%Y*XzMo`5P7OaTKPUMk7?sFXaD>o!O; zRazYxFpz-6djU|QoFS98Ee-H7lu}g1@CqDfQawAfA*m^n#u^|6DGmO9jb0iiG7JPk z(L4ie^Me2dx^*;%#`Fbpa_Gf7025>XL)L&V1DWLySULh`ie67bP1sH#G#X_W4hf3} zG*yuDP+Znli=%oFk`yDYT5RY5q5`CwOaVJpSsfFN@N9TaoQ#(Ts-zSk0a6Te<-B_5 zH;!aCTet2|v1S4IdxD}7r!G~h&iBBhU_N>!of zm*O21F_KaSY)6BGwg%BwP^)0DU#Ln+jnvMqk#}N|Xb3hn7EnMF-PJHgc3?F#Qh5nj zT@r)>F?u)Lyuq9O01`(2C=ncaukoP!B z#X7hHrjq)hTY=<26bXeUQRtytCG!hO#aUU` z!=%m4arXg7PZ!gYB7~j+po#|X+|8(V5W@~Y(mZz}nJBb8J>kPm%A9`QtF-)}~lB&oy~SU{a84yE?agem>a z_9H;kPbZEm{YCId2O&}ZC-d@gYURFF>w6`Rz%nyMf?^r4a$#(vkdFRWc%K{`lXO6&=T)i>Hri zl4?b4IP|iLUKK>DZKRu!lUXkd;?8O2zU|!U3QGMv zK~6aHd6KgI{pTs_zb>%U4yJyY(K>nN%dB4C`!Cn%Q+i+LOz)+Bz0P=b=IafcAMd~3 zWJ>6-ik{qG_Eu0_@yHI#>6kzn4T)l^7d-p+SGmuuOO5Jo#~6EH1A_D)-;LO9Te7%V z|G{iAG31VpH?$U`A5vDg$>H_4q@I+njsW)e5& z{UbT{U3a0~2(=hPnqZHfl(}6Ytb94$NE@YvuE>q)Pc+hy#+Ti|5Qz_7N{v`>p$Y#v z{s?6L?uC^xtX%9OXNJmebC5WAa19tGl{;;6AuT_;^^qb4?XJS!a=KXwbAPC$u(FPr z$J;hGcSC3;s~j}YF4#M)t9_v!tT7tEg(`xjqnT7eFcSrT4VHEC$rmD7;KY#{Qlko( zq{XvtHNvV_L?et6CFF1LkY3YZ=y0Q}?v7#goua3NKmAi;$SEdMafKzzicV@P1lYo8 zeh-_8(BRl%3y7bd~sTS zsnE9u%qo#n;FluQAoxxm+%}O}oRMEBH!E;e1lWM29#c`8$uex9f^n~T{JcvOUhicl zjdZ@$j5U^GvX)RWk21kI!4ec|<{s;AKJndW_n|!clY85?hMBnEl|O_1hhuXyq|%d@xzIEuCU*248A}v^2{v+nQyR1 zZEw>(Wnv3G7oRz*g^ye}>t8AT>&DaJvFid-X-9JAN0c{OJZcd|_)lH{M82>P;S0@7^2C5usG8LM+TNMj_esNfpTv=+j0p@inXDZf}$Ut+H5 zDI9F?T99H4kEk1f(824=_&O8|RDrHGru@JUn=L}UGAqH^fQ;gbLPp=U#~*k67+k`h z%boIk@}=p=&};8=JMLY3@_pdP<(1lV`LF&fc}!l*sIdRJJf@liuV?ZP*CrV^&hObi z_e|;c+7uJl%`f+QuBz~J+RdlC;o#hJ4eOsX>^1lrD^~&nu&l|ZndXD~>du8|g=S?2&f zRrdO87wcd1{5==iG`-(=go*o_Z(L}%xcEJED(?b|12Eg&$1qAI2xYC%B(soH|!pKLws0}64gsHi{_NMbn+ z!%?=ev3083f}oiyoHTTJ!lt>VB4eRcJ$=IpMN*7wXlSUkom)#wi>$R4W6>rpEe%Bk zL*#YQk_wu|8P;j3?pL;0oiZ0wtD2pJg@c`BI}$`wmsW;tVzWWnnaWuicExmsuwb1i zLy#;99JvK%$_9D57*1MZCACb#<=h4OagXC6Rwk_c0=x6ko0pU2SRqgaOz@RLf|wZ5 zC`u*>5&|fjut+p{(m^UXF;(438ALZRIH7vz&>@{RE6d~n?I>sO*N&(xidh89?uffi zDczvBdeb|9zwu2Wrd5q_|-aS6vJS^RL@ZNf0h+mqczpBrQU| z2*Vt(wan6&Z#|CJHL)zKma{VryT;LBJLu%ggGh={`yiN|B-wAXiXj;s@1sbEX$F7* zsG_c}uJ6fhGV%pDOD5~KyRvq2<+-fQ^Em*`-rP4(oKNcL=xEYv03xAeE4RsH znKJTfHtn;*Z{=F`Skg;)&Q(;|{dhDdQp}LJAgKSUpMFTDqzwQvU`o4dP>RdI-ln>d zq1fQ`=*2uzPJw!Z0<4yVNl7Os;Q$2+n8HA-gFFKaS^0rr8k(9}1~yc{#Ix};{2Ze$V6gQz@ zq!>Zc37;0WjFl5;9?fp&TPLc?CdrSc&mW(vqdHbm+`HRm_GIX13YA zr~Ue2#@4rwW_nKEJjN_WDSBM!m_P2eS0`fj!l_#)*~dMOcw9Vvr!%-G`SI+<&U@!L zV^xZty=U(CB;Gn5ajm!OVITL|>=DntvyTUJzrKBZt?%4FS9uV$l2^ZQd8~q@8#&j1 z{@E0t=6TfXQum8%jV38i<}UTTx+!q1R`MRW@a9&Be`nHN`#$|MT#Q!s8S4N1YlV1l_2h4>qjaAYuo zeeF2kMdwZ1zQE)+CmQ2VziDsId3)*vn8O0MR!&Zq0UEbeL-jQf%;ZxW=Z-YJJ>7K$ zm6FnZT3){M>=Sw2(*s45on2Q>AJFZ(EOYI_`N6xV-**qIdcN;zH_|=RbGtqML&8{m zXWE798t>9BE=B5o>~BkZ7t;6oKzn+^`(En@gUu7V8Huau@t-aqd(nCS%6iVrPcA;( zqB=`F-`F1_t5qYZ-6bUnrnZ?St;+~f3UZ+d)uyjk7kBSX71~RMmSh7eHUIX$1-I>le zxGsP2w@80Y*z9ab=ck!thA}Nw%m=A0Zjw%z;#JS)=Hh9}uuWRS#%&>e8G#K>vDLrV zct4eY`zYCO{He3)*c!P;RBs0G$4y``Rc1@kgu&0fYua=K_?w%bCujPfLe~3}lVGWgBb7KJ_NgaV- zQ*RQURl+2n<$z_?qOcjFI!c?)(w~i2b;q2Yia>hf7{ao}u=^*K+nq;a&z9R-gbO4! zmjtvxVz;T_9B5a6nW`2C=_!gLA0|~uS&iU=h5`6Suh7|Q5>y-z?vUObhESj`m1gEg z;n)`@l*;Pi7Kx7dUZ7N>qHr+hHi7(@-~qs`GP@`C50Tl?X#kC}Cu1*`5oz!_Hpq9BM=6?ERB>c=aYvz?nAVdkPST|Qro*&z5;vXho< zR-s&gF+QddLi3x)*qly}-;sLcp4wCBZfF+2R1INn^;z5kREVHfJXez@qK8hjBDMxQ z8%_!*)B9UgWSM2M;%}bA8<9D+!GNJ_eJee(L0A?5Gh(lxiNY9iQHKi#wOl28;TZ~k zG2!L+a)R;cXCx)Y1jx?_j$a%*bgMYtw9DdpMF&QvNbR`|qNvb=#{{$1G^EBa0oDU^ zGR}M`-AYs)SyLpDPA#=x0WpDJV^OL6SB!=FJ%(*hVbQ{6s&Qd05G#NjLu~m8=Gs}9 zG9hji&ek|8mcWea)G7}41y$e$^$`3Sw^2^&^Rdp-$jHGyi{@g$=Cd9Ic=o*UR5U66tOhBkuI<1&+<2C^L)hHoaz}irG zOaLRZ>DyEuKd4YPi709g7D9bLKYZ%qKT$;$$FF77UayX{e;y5V3JP1LZFygvqd{(o zW$K9ltW))=)Um|wiKAz1OuX`_ZM<>$fj*QwW0Y94_I!F>8zkQtHE&Ahy#z8}n&MmK z`{?B;$8|5uo0p8Xl^v5}CqI_Dx}db=)Kwj|o}h?$Fv;-gQh3AzC2#u8uNQ$~4ct=A zfEcA#QIUjkL1>}PeZpC3CwWZ619i?A-iH1Di=V}Zq@ckkrwWG6Ma^NG^+0pfvYyC- zKkp`>Vbtwc(vMzmgt`D@3g$~tZKV$hO#8#4Pu6Xd`1`$S$Gr(fi(uW2kINu$2#_~x z&9i{cqx|?)QftHIQaZ~Lv6|!#H)lx=eQZ+fGHhMn0tn4~xuSSU!L|BO#vrve z3Uj<%uv8?+c}VahM?F_+G$s2j`@S-V4tN;OjewR`i}X|^{kdAFZ?PnlR;cfG<4o@v z-QX)Nsh|Z~#xE3f;2OVWf#zu*^uQc~{Juz{h?$1S=E2+Fav)Ni_F|&a8l31PNY&gI1v-WJVC$1vnof4SSRn-cP!Ih;$Yz*&A(x>q`HbIv z1zV_Y{#34;D|aq|Tz$~CbqP5vLQJv?7N1GH0?Q#GPJ>VyC&$K2=R zkbEK&tj0#1pO%*AfH1>AN_Rcwe!bGqda7K5s%3*(NP|XRgVv!2ol6aR_Ztj;HqhlZ z_8MF6H4WKop0}5AszEM9%K|Ws&4gh1;EMu~Q^-D-ynSwm_IX^|=XHOd&(D2qx&3~Y z`vXGu2j=Y$KD0md(*E%K`y+qu=g1w14%y%00PbSTSa851~PY~156U#;C( zYk6oN@=$r@p%cD`qKq1wE#;3QWdw3f%aV=9@(#7n=r_$YHe702S!g=&vq^Z$u=`Nc zg^uj7EH4I6FK-rK$KJZ#o63WECVB`7(<))zK7b3{Q8zL0Izu4fw zJcZ5k6Ne+04=WNO4nm-v106a+fO0|VOW-4XK#hf05kju9kxwc~x9DhV4pcfua)k*~h)5A&4{=+(t?(5(1S0@g;T+i$ zN;FzJ)%f7VmS4ENJ33|RJ8c2{xMg(eY3cO0gcSh4r3x+0!@joYltoF{5s(j7n&Y?~ z5F+G2vc%0(UGQaS{xJ5T9C9Y<2oZ$WV?&;V!Y2m0s(v*GvT{s*e0jT?PsO<3BI5vFHsw1C{eG%Fs zQEf2QBt6q4fKt(EOJ@|Sght$r7H=l(hyeChfa~E&_zhcfL-8&29+MT}iX+Zf{A+jO zT7*FTFp@ZeXIb}L);%qBbl^@je)+@%4;>(c8GH7a zSMQP~fISE(YXwxOK%!oBtY!+FOGg{fdkp9hP2L6Y2)LAvHtB?^@LN&x;2t_?#~_e9 zj2xDy(W<4@9}+YVcS{q(9`0!Q2uO?w;2Avh5D(JIjTpW!(I@~qgxEuLaq=N-j*k_m z9M2Q5(gLykP1Ic)xW9rxagW1B;PwLOJYjH10Jq~|4go|X5vqWX@39_QT7skiAgd`t z^v2+&B>{YX36l)qW`zUd6{Ly~*+?In7a$)}aaYiQ?h52E6Mo)$=#>zG9Jaj0!#)3v z@D{>VLJ7LOt78g-OG50d09%Kq)$f$v`}FmSphtmisBp=Y3Klcpx7DuoXe13Qv4m!M(F4bS(iHba2XW*q+xt zxJ-~P7n8#r`LhJv=b;N%+U{0&d-EXEbo}bDL;({yB*Y2dLiISX*Bq#e2=6hBJ|%v9 zzd;(s0j*1$eYohe7$EL6a-RV6N4HZ?T)ez=*>AiE(y9C&^hRo|)QIo+K+ z%@xcvWAFu{=~lj@g5FFNfQE+?rs$xd6?p6L3_^Ccd>H+S4L2Ve>No>W;r8^rJ%`Bz z1qz@BOJG(NYCG?m=$IpBcLV z{>HrVP>3Uio|EuG_!6*|>{)Oos9;!}GAi+ofBJ=J%3u!klYrULb+3t!sUsi`@s1-@ zdOvaIUb|k;V8RG=Jm&+XiGcYot-O&)`F@aNFG9(updwc>O6r zG-AhxY+pg0V_)>#5Y6C5B+z_p6Au^-fgl7()Dnc^vQQdRAy-pwgAag~ciGWQeAhdLd!EZ&W`UL1O7^8!mqYl!B$7emT z%G8BVx4YF9CHT}u3DzQh&T@*4snkau;@!yY1kN2J?nCmGhY18YjV^+N@FPQD1Q)(B zBm(O`AtWkd+s-_?G%KN)0xKY3^|`o*H3V7Cvl|1?eR$nc1>r+QBdZr7kMiAIqiJYNZM z%LRC}1ngTu*qnW2zj4A);oUW>xruP3>z;>okAO?R;bvVJy3SdV0pYd)n#n^i+1z#* z#{84|3SoWYz7Q*&X5_On6TP82h?BnTLr7G5m}2)Z9rvnQcn8V@_6av2v)qemAMlSv zkT&`o-(mC(zNjc4@lXN3(Cm~tjD0kW6d6GOu|uY;pL>Br^oa%t-U8r= z0HrsKtj)7>|ZH4``c{n`+x{%nLW$$dxTRQR8J@V-O5 z&M_$RiqV=AG6=5gAmh8TFNJChzq&(HvSPyvQRi&$Z=JJL|A^;YA$h{Pym~dn@EBpY z$Jpe{wxmk?-I0b*P4hm&bbWKyPo310JgWR!2f6z>{rtOUsv^ux1Fucwi$T%$6eeUw58O{Fd!x( zadT+KgjrC8(UHwTVcTKm!BOS4J@>+QtDA?!9gNE{jLCHF4M{qAqW6A6Ms06sTHgzq zyD3T6d&4rP)PF2w#9;fvx7~}ocXMl$cV9%_s}tKtwr{EJi!Au@;@b>&6H^2YL zx8L>qTl1EizdpTEzOnxG(U!l^{`SB3wRsRHPSFM`_#{L_R0s{_G|R6sQDBWCZW9M) z_S#Tl85f`cRXLX~E5w(HO6mrd4jbn>$jqvhriVtM;~M$0z1n3a%~9C<*^ZQ6c6rLv zDEy{1N5}o_3fBuHqOz2eS~|ND9UV>5U2}pt`&QxGqKnStPC(;{tgz=#fy=p6dfEBi zF{W&}>$&!(GH$ggHCq&rt1gVQoA&HAj#m7ZI6?$h@7cNjM3MN_l^MT@U#=3Xa&o)d zn3`Yx^%yk{<>&M>=P!nP+r)<1H?m&wS=$f4_tdlvv1Wg9-gi7dLfhU zR>rM>JH}n7+)`&xG(Bt%vv~H!o7?N%%vDLSyqR;Oug(8RRLn`QM?ZZoj4T8_XnQ69 z%)KG+B`M`M#ohkT8}pq?=wpz}s?3ya>=JX|78EhjasPKwiBmwU)A83w$UEkh;#a9M zFA{Ispa6c^YCKKG544cwbZ z&$&1l@iFJ#;6Jd8+zIQ*F9+@oy>b@k1`I}i@4a{VQ)x#2b%jEy#3j4YXh+?3U&O}u zdsqL$xICDRSnyvQmT=(~Ne^-07K_5hGLX{EKb?O#Y(wNtg7t&X;+u5 zhW$ga8W#6wHkD=VJ7E*26LfzzXfo@-#i2NZ?f0*3fp2Y`u!%Q5cz-UzWoy%&p?LGY z``0tdwl=@8NwB(i|Hk&otw+BNCD{GAe{(lHTL3HQ<^5+_LU4(H1@3`^L8zkuI5+^1 z($x;ztkLQY+H3-%NkG&+j7Hor+X7V*iWRo$LQHkck7r&;ebNy!8v)P0HQ(B zL70YudRB^UrLOcDNrQL`$&g?ynWXG)&gb)0c0+6%y-0PXjJ`1JW>?pWK<$1TuyZ8c z$p>(;*9c37SSp1-+2%Uos$)i$N=pP=*(k*;yU%6-=AHlq38NVzQ&ObWq^ut^q^k|J zimQzGTB0(+P!bB85CRR0A#Kl6PIE!IgFxDnPRC+Ao6Ru-vYODsu22o+qOMaC78sL) z$dFRnT`rvy=l$BrDmlZhhAEX;E~jOq%&;TYYIqM=Nfl>DUfxBp@U@vT3Y=i$q!hri zG+K>3-6K3BBg5x?I1uY1X$nzK-J_R?HQeTj)U}SBPB-&&l%XjRW1P(jD*!Z=+AHhc z8%78LVBtHJTzv`7QA{6IJr_$P8O$DZ!_?#bN|I5U2&_31lN4*v%7`q@CD*24-3o-wW?g5bQaw{_;bC=nfWnn1N2DnEEVQE?d}|`cLK&ctWw+;B6>i4w z-Rr}*HB5BT3eI+ObA#&ZQ`Pk)6wu(f7WjxY8t2vne4V*a$4h-^conJIn%S zaKm2N-BH8LK=B9zK!Nm3tkGWSRF`zxpjz17;>eeghQ&;5!G4X+&SGm1q*`Z0 z`n>g`og$kR?LbB*i|gP4d5}z94%k-4H!+Bjo8{DHWf2kqG7kmJObJoc$dfx{b(PXz zrvLlg^MCoGeNyY$f3j%*AJ%W?MOcI--ft#Etj81`H1_zqNq%3=w2HRK7+pOxC_8y7@h>!5$ne7;Fh>i>!-|D|N{&jET+Z`PUo zY@YOoT&|yC;gx~IhJV>HZ|ob?x)U8z8(D1AL&JVh)|PzYNv9sf?J9>B4wEhQH#Pe7 zIY_~L)k#Q_?)+iyhS>gf>GAc;-PUtfNse7O1AoPDL2sk_ws zZcl>MEJw75xfLNbq5F5`ew^lB4(~iOHJ`8Jb@|G+gbOz}n9m4Cgf^JFDj%@)x;?{e=1NxGj3>EnGVsug)nuuX zFRWeymx4#^6*1W;>s3j$(=1nZwTKuv=c})Nls&%5`dI+Qn^vn^uIT`7bYv^S)aOxG z!A3nZFTryDuA1bIPJTNWxr=Y%%3YQ)ur)RIVimTsM9K+Dt0S$q&qOI-wJQJaqheLaeGINjqRvI2M6vYQ}^VQlMNg8fqKt48L3jw_s1RnE>n$oKDHVWD!& za~tz@E{k*c;@{<#;dJ|wGbojJOHhQkt|W7g^}XtNn9 zJg_5~!EaMGAt-9jNv<-}GchfqFm_d2u8GaeL8RImC>jhr5&Y5}2(_gNN+)N9kORU0Q#H8G}^4YtuYgBfq zPTOvbz*%m^#GEz~S!a6@3FD}Q`^l1|(@hu`i+28933J%zP6!sLpgL__%vDC2v78m< znhkP@NyKqP)rz4Paq+NvC$&)3u7v-DDn|ylm+|DBz!BVKW~eN*u$4(w`|S#S`kD~5 z{;aSFfKZ%9*hCvfu{e831z=^N$!}xwAlGAHlYMxKONXNd*|$qZ=D=$ zXYiEA-R`DuXA-?TL+{Pr?H%~mRoMayzcvbmtV6h_Z%RqKBr67Pd^512yQ^)ez>jfS z;D7mJ9)9QVT|GLEn%R(za6YdaQW&$zuhI|kTn2n-*uI9! z*Y9VGAcrKH>&Drb?cJ3>IHo_ z)d>dwMf*4zoX2z)5=y`;PX})-vdE(qFeal|<{tzN28^Ir#o5?})1%w-h>@!U`#Kw? z-7qo@$kO-QskQV8r{Ti7vPEfHu9h3doGPh7a3cRPLnv+N5+0~Kkx};n5#$j7gvKsJC46k9EbM`7EUPrP?_kMu;1m&*LzO8 zcrz6tsg)&BrnMvqJp|S;Ybg3{4wVA?>S?QGNuZ@-XJ!>g?B#`_r8QCVkonOK`*bdx zY7Rx$_m8T_vdW^TTgZZ3C?-@?~Cjf&K#ndn=hBAIa} zz$)JiIYp>Q{K1?qqTWTE)vsur_aYQ~j}L&2wy_M19_@ZzO!XdeBW-;4(lseT{pGMu z`@ZEnBcMu(Qt%R7&FT2fH^D4*DNr#z+fmk- z4#g~lMMe`Gm4`&-cIiMB%BE$?{m*9?Y9S$d{kVLj%yed2TD?cWQ)N2ufO9t!1474Q z{!$B7=-tpVo8sHBk*rJ4^@-LM%W%!3ZfXYNW_QFdg)>?_?1t4wiAGrq+?)zQAC0Pw zCU9iVPmW@q|3((%Kb2cq8C6QB;&<6^hQzqBSlI;xw~`0ci4OP%qcyoGU=$~0G70o( zWYKFt`fEppNMZp9SwHS6aVpg@e;0unFp8EPaR3=`N|HMu&3`MLl(}rI=ilQ1ieF0M zMb$-{vXs(yeb#K`)Df6Pkf3AbDpDVwRj#sBR(%jQZi?udM}?SJH#Y`TrA$3&mnw}6 z5u&4^t)6ZDf2m~#8&QQP{z8@^a^hBX^eH`?4Q1w<>x+|}{Ak(OYq72fT`4M##s`qz z70#sKT{vuW>+Ij*BHCzSMcBTlQr+@yY6BY>yitXUbc|M+tlMxAboj`bcEgH!q91R! z7fpEJ`X}cmyVc!8&lJJ;A2>GhDt6E9c>PvD{F&qGU{1L19DHXp&^iHGOTgVIqR%)gqt*EPN~k(ulagpdRXAwZZU%poA` zgapE9P(UV;8Dugjvs4m@3^EjyqlmWR05TiYdK8BSMZ~Ga)~VFnQl%BORcuixTDa+1 z_pbYk>z;Mj>b$`UL`#$gceLfF}5?~SmQkV`OJ58`x=u=yOXVV$4ipd?{ zgPt}Z7)Uw0Kne_`BJ^>+5Ewg0ve#CNY2?HbGMH8I?Ot5C9O%`;ZNaS>Jud*c?bQw@|C&TOb$p$0(&|^_epf( z-a7It42Gkm4i{hH;H^Bt0Rh5Qw?hmOYy*MFB;wsUynd#dwpf2eiEx}FwhG|%PDIy2 zaefgBgDJ+{-Z{)Cr~Ck_1Pw=24d9{D-*h>}oe20LBF(u9LvDl@psSTdJFhf03lMhc zh!a}0m`~n$H-JLMmf7GX0{noMKuaQ6P7@x#CXEX)BLFT8!bi^G<67{~bvT-08PS=A zi3bhFm9UjF%|)e=G#m1c2QgoDfd}@Ivwx^u0dv;tVv)|}w1}7oEt^pCN>B`}sM6(0 z*%zb1UIqU6BB`eq+$;oXljtVd-`31EAk^t}HELXmb%8b^A>aW7OzZ_Nf*51f=A%jj z%{_E&0FXJ2&smSBa}2ib!`%hZT}iMh?e6TNZSdsvHseSyd4@>@K5Ky_fxz&jea6lP zcR8Sy0Hs|Zou37TbEH?(s1~qx1$0=M+_}LE{6rCBlY#wGSK}jW#j1;6sSG7|wj%{Z zD^7=z3VHr2m==Qw5umJe9W5ZqM@7W801Y~1BG4YTJcesHgLcruu-Xsk3cS^v;VS_5 zse&L(g3V8F3yZ2NQIusbk_<|~g9`9Gof5S`3jUQe=K)SI$gz_{=`8 zij3=uF7Jxz=!zTZN|@?OT1xv zM*7cB^wSV3f5Lj(3I6^QcIQqwe9btq>g@>u>!geKNw@fu9@|a|_n#D<>wPciOK&jHi$r8J zfIEVMlO18P@iwu^{KISEFp5r0|5Hy%I8x|c3 zlW#CePX&K02Ll|Ift?e$MAN-NF+d7I=ayb zDAijYsE(Bl9`RYs=m^&CJjV$>!p=^Tn}N#S!v2^AC-Y#X#TWl0Jo@tTC+`DzD-iI+JtK%N{A(5H!=6>oK_KwZyGkoQ^pe)q#;0Q)SO2 z(kpA|a%Vx!HWPby0K)4Ao=tLuWS_2O9fD>_E z+ap~<*VERQVfa3HmK9mH2IdoF7l=vHloDfeot1LQy*2t&2zYEHGT4^uv(9I?)aP{i zrtvMPz0}Z(jj<#C))fT?CC&O^Fd!yVp9f~-Xyq7h3as^~M*5Jv&Gi-$p?89pWkw#{)d*fo^-s?wvFWQfrm*Y0neDsm8XNI}39Qnc1TG$9 zrGI;n#F`z>CxfVTz`1aXQ9gmdXJMHLV^@(;QI0;hj9+RJ{Zxj{t{{s-iGfzGQ3@bf z6xj71MXkiq%T5q>@j4O1l(TL7Q zF%w{9SE~^HgpEl+Ps<%2=ko_*Y`+ZPCh)?}% zJo0B8kMw@OC!siYt#R5FZT;S~Jx^m@U2i?E-8U1lR>2|E>#S==9bRSNk>Y}u11_du zl{-Jr*=BQ7l3TdgI6+16g5PzXEy)3AK9eL_ORxBa(jsrkmCKiB{VJBLuluQJZcnAZ z`0RWrxXHg`=eG>Mb@_9~3g%K%z_WKpY{YYzqqCCzYv(Wd%`Hw|GU%h1Oh|KZ$Qw|V zuk<=Z(o`$(a(-Js)Dnuk4k3I_j@n#nzV9IRR(z4|-{sKO3&A5#;l&r{*5((!qPRJV zdmbnhc1Ysr_^N}6W5M3Ogs~ee`bXuPOQLz9U?<{l5tFVfKiaqQ;jfvk_BIT+Zs@Yl zz(+KM!yQ9$@v`Nx^s570@MCyb!40UkeQwQlc-H|ZNq&2yndet7cUJjN#I#L749tcE zyLByNSHkWs|DyTw>IF3^(0<*_4vAUheBP0jgq@#kwjj(u_lvLOA^pW9-q^_{98a@9 za~t~kvft>l7BV85fXp#A3oPv6us=vSZCi$aq6{3s%oE0s(KbvvRZfZ~i4Tium;EX= z6mc0CJVY=37d2wd%ns!3id?ywOjv-XP z8pIrvT3kx&RNI-{{Q2SwlOA_s$OT~or9)e1p8&%^Aj&i{9SGn{S%{~)#N|K}{|Sg{ zq=ewcJ$AE?thxbcoqy|ipZP#!9@D(9~fIKXzt?)O_UDm;H*)1BmFo~5Z3^3JZi5IM|v zhhrdnmqimU2BJ5mBTY^_N2zC$P48!+RtaOG)SwD25L_mwSYRnF9O`|c0W-yU?Nl$9 zZwp}s!Dqp^2O7r8Y%Mgu4ZV4w>hB1zbi~}(gX)=2IPPq@(LF)KBQwvl1v*61LDmMh zTubKL-H77&;YfAig88>URh!)G+{u}@#E5lp`UIqYbA71M?pMb+#5!cvoS2iums1QL zg;x#u<5v_v`K2|a3WSWav5I^b;A`wGW$Osz^oyE;+qX!Sp6Ang;O3`qrUxn4l@Byw z*dS-7t&tk(n?JE^Ds{m$Wqn~mT)gVC+!|iZQE|p-nCgae2%de}rSLuZ4lToexkoL| zMZTHCxKGN#FX09|J9S8%_-QZ;hUER0=o{10YPy+cY_bm#sydKbHxIKYF_Q}m9wt3r zDmxo$ShzM*T$?{J-3vuQo)OI!CB*RI&-Kdw5m+ZYoRk{VfY$5q!k$uEdX=d zc%}&TFoe6^T8P*DO#4ptJ^BRv=GHV!4x};tMvOLTe{4ooN(3F8ruB8z?Vs?~4$-=| z;_1;V$lLxFF3G?*HuE2NY=#rFCTomNS(rX;SCMbt--p0U*=T2tg^cBL${# z0@JgeN!(ZtgeG{N4+6ruyy8~sddMPK3S@2~nlV976WS3rQ;c-FDgd0N3|X*P)lN@G z!t^(*KMgU=%R_Ci5JEvfAQA9kI`juK!kMNY?Z+EiVUpz(4b3sF+*ZGhycWgO$WUA! z5F`b*q#N(H@$Zsy*W|H^vl-qVsDMo#-JS#+cNh*%>?ipQuOe4An)?M=r<&_??Ht0x zQ9N*UMw(v4kTTXgm*lvAxE+u=(i4<`tFdAJwumV)%E=C1Rs#2pvdLfL7-r?y>cqCS z1;PQ-+>G_7L`h%A(s;HQ6NFdO0h@Y0kOB~5UCiVOC=!nGAsfr_76g$k*Z9DiDRwIE z>bv1MDdbsDV&uxB^B9IHX)t3qzMBMCuOv}inGFT2bBghfBqE=MFUVdUzK&M{;5LXt zHm3$(^bI*^xhgh9Pg0_}IOzdRWxl>D6M>XHx4{Dgp)uc27rSW~Q5hkPZ?dthzIU?|_i1LsLH z2;QovHkus9dd9{||1H8oRYS?#h=EK23!=?-F{n(!xP{^pH&T+lP+_S?B{^>ESOAK` zZgL@RFQBG{f%!#vSukircQ5-uf8)Tg4QnL>^wN+J0Z`a>{7p)H>Q>WWo}STUVrm9h zcmm;}c9q)<1sr}@962`@RaI?L+eUS!m_!7cZ!M(g%?G}gCO+#hB5w>C1GIyb7+*kU zOt3aV+e%GrY&dCQ3^!|hS}v)m!Xzd~n7s+i%GE=w2^=iFx<&LrF4$TM7g&0Q$AUs9 zV2!Qe79~YlM#@U%E4*0paQP7lF)P7n(;EGF2oMVh4pgeT8SsYmkwb8XJFxNn_{P6v z+@Ds)^|o`p!1T2*$=0a!ExWiYZvIcov{{EI zvDjb1FHPuAU)hunG1A8%gm&&jVMEO^goIpr3NP!YNdvnc?|@p&NAy8)0V;;u6~Aef z$eg)MF@eZjw3i}c6=HPyS&reb|+nWEBJft*tI|`GXag+Ls(HI^SXmb7>i?nRu zFsDNr=l&s3qw~zwzAM#0e3RC_*-`%P$vQ~rezz1`>Pg@ZxlR&c=ny;C3157 z(PkRWVt14UQVfAM)Tq(FL%Xj2{J8)lc2-!yqjX4_3!(7lEe#vFMm8xuT__n~Ys3L3 z-XVe>nG}52>SDiIFbelD`SO#DCx0zE~k#ZN&y<37ZaCv^AU*9I>D3lbTVS!xZfqk-(nMv(pH3t5(i-m z`3%A`Ng-n~Qb>U}EltVx4xf4X(qsD5bejl}ncCB*; zjT1&GsDf(C?O-#i?)LVo-W?Jpr$rQ*ipDLX0h28srh^*x?lug_wE)AHj~Jph0Zt-a zbBYSm)V*so4L;UtAV31g&s=dts;@^@WmKFl{EV^nEIZ+6ikIVJpwIR{jMx0rp8&P^ zPq*C4rv8K{!S_d|UJsSt>Q7v_e*faq>*FfoKoUCS!6oJ!ZC%(vit&vHvU@nW^EdoMz8O1Ho8-liuXmHyECiAjLz;bC_mqjkfuAU3evqeH#dHRi9ldE zFE8wos9s)Fd+1(%3ZVbM=I1|&j#A^i`P*`v!TB#jJ%5`2QW5C*`cCBfyw`VQ-jk`D zb!_mzGIbXo7T(WW__xw0gA1kKR4n`>Q+L(kw8l7p@w+;kp~Xjyp8va Tuple[int, int]: """Returns a tuple contains x;y position of ``TextBox``. :returns: x;y position of ``TextBox``. - :rtype: Tuple[int, int] + :rtype: tuple[int, int] """ return self.text_pos_x - 2, self.text_pos_y - 3 @@ -160,7 +160,7 @@ def dimensions(self) -> Tuple[int, int]: """Returns a tuple contains dimensions of ``TextBox``. :returns: Length and width of ``TextBox``. - :rtype: Tuple[int, int] + :rtype: tuple[int, int] """ return self.length, self.width diff --git a/visualdialog/dialog.py b/visualdialog/dialog.py index 530012f..afafaf6 100644 --- a/visualdialog/dialog.py +++ b/visualdialog/dialog.py @@ -35,9 +35,6 @@ class DialogBox(TextBox): """This class provides methods and attributs to manage a dialog box. - - Keyword arguments correspond to the instance attributes of ``TextBox``, - documented below. :param end_dialog_indicator: Character that will be displayed in the lower right corner the character once all the characters have @@ -46,6 +43,9 @@ class DialogBox(TextBox): to ``"►"``. :type end_dialog_indicator: str + :key kwargs: Keyword arguments correspond to the instance attributes of + ``TextBox``. + .. NOTE:: This class inherits all the methods and attributes of ``TextBox``. @@ -89,7 +89,7 @@ def _display_end_dialog_indicator( :param text_attr: Text attributes of ``end_dialog_indicator`` method. This defaults to ``(curses.A_BOLD, curses.A_BLINK)``. - :type text_attr: Optional[Union[Tuple[CursesTextAttributesConstants],List[CursesTextAttributesConstants]]] + :type text_attr: Optional[Union[tuple[CursesTextAttributesConstants],list[CursesTextAttributesConstants]]] """ if self.end_dialog_indicator_char: with TextAttributes(stdscr, *text_attr): @@ -134,10 +134,10 @@ def char_by_char( :param text_attr: Dialog box curses text attributes. It should be a single curses text attribute or a tuple of curses text attribute. This defaults an empty tuple. - :type text_attr: Optional[Union[CursesTextAttributesConstants,Tuple[CursesTextAttributesConstants],List[CursesTextAttributesConstants]]] + :type text_attr: Optional[Union[CursesTextAttributesConstants,tuple[CursesTextAttributesConstants],list[CursesTextAttributesConstants]]] :param words_attr: This defaults to an empty dictionary. - :type words_atttr: Union[Dict[Tuple[str],CursesTextAttributesConstants],Dict[Tuple[str],Tuple[CursesTextAttributesConstants]]] + :type words_atttr: Union[Dict[tuple[str],CursesTextAttributesConstants],Dict[tuple[str],tuple[CursesTextAttributesConstants]]] :param flash_screen: Allows or not to flash screen with a short light effect done before writing the first character by @@ -153,7 +153,7 @@ def char_by_char( character in seconds where time waited is a random number generated in ``random_delay`` interval. This defaults to ``(0, 0)``. - :type random_delay: Optional[Tuple[float, flot],List[float, float]] + :type random_delay: Optional[tuple[float, flot],list[float, float]] :param callback: Callable called after writing a character and the delay time has elapsed. This defaults to a lambda which @@ -162,7 +162,7 @@ def char_by_char( :param cargs: All the arguments that will be passed to callback. This defaults to an empty tuple. - :type cargs: Optional[Union[Tuple[Any],List[Any]]] + :type cargs: Optional[Union[tuple[Any],list[Any]]] .. NOTE:: Method flow: @@ -280,10 +280,10 @@ def word_by_word( :param text_attr: Dialog box curses text attributes. It should be a single curses text attribute or a tuple of curses text attribute. This defaults an empty tuple. - :type text_attr: Optional[Union[CursesTextAttributesConstants,Tuple[CursesTextAttributesConstants],List[CursesTextAttributesConstants]]] + :type text_attr: Optional[Union[CursesTextAttributesConstants,tuple[CursesTextAttributesConstants],list[CursesTextAttributesConstants]]] :param words_attr: This defaults to an empty dictionary. - :type words_atttr: Union[Dict[Tuple[str],CursesTextAttributesConstants],Dict[Tuple[str],Tuple[CursesTextAttributesConstants]]] + :type words_atttr: Union[Dict[tuple[str],CursesTextAttributesConstants],Dict[tuple[str],tuple[CursesTextAttributesConstants]]] :param cut_char: The delimiter according which to split the text in word. This defaults to ``" "``. @@ -303,7 +303,7 @@ def word_by_word( word in seconds where time waited is a random number generated in ``random_delay`` interval. This defaults to ``(0, 0)``. - :type random_delay: Optional[Tuple[float, float],List[float, float]] + :type random_delay: Optional[tuple[float, float],list[float, float]] :param callback: Callable called after writing a word and the delay time has elapsed. This defaults to a lambda which do @@ -312,7 +312,7 @@ def word_by_word( :param cargs: All the arguments that will be passed to callback. This defaults to an empty tuple. - :type cargs: Optional[Union[Tuple[Any],List[Any]]] + :type cargs: Optional[Union[tuple[Any],list[Any]]] .. NOTE:: Method flow: diff --git a/visualdialog/utils.py b/visualdialog/utils.py index e507a03..f415241 100644 --- a/visualdialog/utils.py +++ b/visualdialog/utils.py @@ -49,7 +49,7 @@ class TextAttributes: managed. :param attributes: List of attributes to activate and desactivate. - :type attributes: Union[Tuple[CursesTextAttributesConstants],List[CursesTextAttributesConstants]] + :type attributes: Union[tuple[CursesTextAttributesConstants],list[CursesTextAttributesConstants]] """ def __init__(self, stdscr, From d1a73a200a259f61d84b1c2a3d6a09480ca28f38 Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Sat, 27 Mar 2021 11:29:05 +0100 Subject: [PATCH 43/52] Rewriting of type hintings. --- .gitignore | 3 ++ visualdialog/box.py | 41 +++++++++++----------- visualdialog/choices.py | 24 ++----------- visualdialog/dialog.py | 78 ++++++++++++++++++++--------------------- visualdialog/utils.py | 7 ++-- 5 files changed, 70 insertions(+), 83 deletions(-) diff --git a/.gitignore b/.gitignore index aafda3a..0a28388 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ .vscode .idea + +# Sphinx documentation +/doc/build/ \ No newline at end of file diff --git a/visualdialog/box.py b/visualdialog/box.py index 6ccdcf4..51df59b 100644 --- a/visualdialog/box.py +++ b/visualdialog/box.py @@ -25,7 +25,9 @@ import curses.textpad from typing import List, Tuple, Union -from .utils import (CursesKeyConstants, +from .utils import (CursesKeyConstant, + CursesKeyConstants, + CursesTextAttributesConstant, CursesTextAttributesConstants, TextAttributes) @@ -38,7 +40,7 @@ class PanicError(Exception): :type key: CursesKeyConstants """ def __init__(self, - key: CursesKeyConstants): + key: CursesKeyConstant): self.key = key def __str__(self): @@ -49,8 +51,8 @@ class TextBox: """This class provides attributs and methods to manage a text box. .. NOTE:: - This class provides a general API for text boxes, it does not need - to be instantiated. + This class provides a general API for text boxes, it does not + need to be instantiated. :param pos_x: x position of the dialog box in ``curses`` window object on which methods will have effects. @@ -72,31 +74,31 @@ class TextBox: of dialog box. If title is an empty string, the title will not be displayed. This defaults an empty string. - :type title: Optional[str] + :type title: str :param title_colors_pair_nb: Number of the curses color pair that will be used to color the title. Zero corresponding to the pair of white color on black background initialized by ``curses``). This defaults to ``0``. - :type title_colors_pair_nb: Optional[int] + :type title_colors_pair_nb: int :param title_text_attr: Dialog box title text attributes. It should be a single curses text attribute or a tuple of curses text attribute. This defaults to ``curses.A_BOLD``. - :type title_text_attr: Optional[Union[CursesTextAttributesConstants,tuple[CursesTextAttributesConstants],list[CursesTextAttributesConstants]]] + :type title_text_attr: Union[CursesTextAttributesConstant,CursesTextAttributesConstants] :param downtime_chars: List of characters that will trigger a ``downtime_chars_delay`` time second between the writing of each character. This defaults to ``(",", ".", ":", ";", "!", "?")``. - :type downtime_chars: Optional[Union[tuple[str],list[str]]] + :type downtime_chars: Union[tuple[str],list[str]] :param downtime_chars_delay: Waiting time in seconds after writing a character contained in ``downtime_chars``. This defaults to ``0.6``. - :type downtime_chars_delay: Optional[Union[int,float]] + :type downtime_chars_delay: Union[int,float] """ def __init__( @@ -106,10 +108,9 @@ def __init__( length: int, width: int, title: str = "", - title_colors_pair_nb: CursesTextAttributesConstants = 0, - title_text_attr: Union[CursesTextAttributesConstants, - Tuple[CursesTextAttributesConstants], - List[CursesTextAttributesConstants]] = curses.A_BOLD, + title_colors_pair_nb: int = 0, + title_text_attr: Union[CursesTextAttributesConstant, + CursesTextAttributesConstants] = curses.A_BOLD, downtime_chars: Union[Tuple[str], List[str]] = (",", ".", ":", ";", "!", "?"), downtime_chars_delay: Union[int, float] = .6): @@ -140,27 +141,25 @@ def __init__( self.downtime_chars_delay = downtime_chars_delay #: List of accepted key codes to skip dialog. ``curses`` constants are supported. This defaults to an empty tuple. - self.confirm_dialog_key: Union[Tuple[CursesKeyConstants], - List[CursesKeyConstants]] = () + self.confirm_dialog_key: List[CursesKeyConstants] = [] #: List of accepted key codes to raise PanicError. ``curses`` constants are supported. This defaults to an empty tuple. - self.panic_key: Union[Tuple[CursesKeyConstants], - List[CursesKeyConstants]] = () + self.panic_key: List[CursesKeyConstants] = [] @property - def position(self) -> Tuple[int, int]: + def position(self) -> Tuple[int]: """Returns a tuple contains x;y position of ``TextBox``. :returns: x;y position of ``TextBox``. - :rtype: tuple[int, int] + :rtype: tuple[int] """ return self.text_pos_x - 2, self.text_pos_y - 3 @property - def dimensions(self) -> Tuple[int, int]: + def dimensions(self) -> Tuple[int]: """Returns a tuple contains dimensions of ``TextBox``. :returns: Length and width of ``TextBox``. - :rtype: tuple[int, int] + :rtype: tuple[int] """ return self.length, self.width diff --git a/visualdialog/choices.py b/visualdialog/choices.py index 4681df0..cb000cc 100644 --- a/visualdialog/choices.py +++ b/visualdialog/choices.py @@ -30,27 +30,9 @@ class ChoiceBox(DialogBox): - def __init__( - self, - pos_x: int, - pos_y: int, - box_length: int, - box_width: int, - title: str = "", - title_colors_pair_nb: CursesTextAttributesConstants = 0, - title_text_attributes: Tuple[CursesTextAttributesConstants] = ( - curses.A_BOLD, ), - downtime_chars: Tuple[str] = (",", ".", ":", ";", "!", "?"), - downtime_chars_delay: Union[int, float] = 0.6): - super().__init__(pos_x, - pos_y, - box_length, - box_width, - title, - title_colors_pair_nb, - title_text_attributes, - downtime_chars, - downtime_chars_delay) + def __init__(self, + **kwargs): + super().__init__(**kwargs) def chain( self, diff --git a/visualdialog/dialog.py b/visualdialog/dialog.py index afafaf6..f04b897 100644 --- a/visualdialog/dialog.py +++ b/visualdialog/dialog.py @@ -28,7 +28,8 @@ from typing import Callable, Dict, List, Optional, Tuple, Union from .box import TextBox -from .utils import (CursesTextAttributesConstants, +from .utils import (CursesTextAttributesConstant, + CursesTextAttributesConstants, TextAttributes, _make_chunk) @@ -43,11 +44,12 @@ class DialogBox(TextBox): to ``"►"``. :type end_dialog_indicator: str - :key kwargs: Keyword arguments correspond to the instance attributes of - ``TextBox``. + :key kwargs: Keyword arguments correspond to the instance attributes + of ``TextBox``. .. NOTE:: - This class inherits all the methods and attributes of ``TextBox``. + This class inherits all the methods and attributes of + ``TextBox``. .. WARNING:: Parameters ``downtime_chars`` and ``downtime_chars_delay`` do @@ -78,8 +80,8 @@ def __exit__(self, type, value, traceback): def _display_end_dialog_indicator( self, stdscr, - text_attr: Optional[Union[Tuple[CursesTextAttributesConstants], List[CursesTextAttributesConstants]]] = ( - curses.A_BOLD, curses.A_BLINK)): + text_attr: CursesTextAttributesConstants = (curses.A_BOLD, + curses.A_BLINK)): """Displays an end of dialog indicator in the lower right corner of textbox. @@ -89,7 +91,7 @@ def _display_end_dialog_indicator( :param text_attr: Text attributes of ``end_dialog_indicator`` method. This defaults to ``(curses.A_BOLD, curses.A_BLINK)``. - :type text_attr: Optional[Union[tuple[CursesTextAttributesConstants],list[CursesTextAttributesConstants]]] + :type text_attr: CursesTextAttributesConstants """ if self.end_dialog_indicator_char: with TextAttributes(stdscr, *text_attr): @@ -102,15 +104,13 @@ def char_by_char( stdscr, text: str, colors_pair_nb: int = 0, - text_attr: Union[CursesTextAttributesConstants, - Tuple[CursesTextAttributesConstants], - List[CursesTextAttributesConstants]] = (), - words_attr: Union[Dict[Tuple[str], CursesTextAttributesConstants], - Dict[Tuple[str], - Tuple[CursesTextAttributesConstants]]] = {}, + text_attr: Union[CursesTextAttributesConstant, + CursesTextAttributesConstants] = (), + words_attr: Union[Dict[Tuple[str], CursesTextAttributesConstant], + Dict[Tuple[str], CursesTextAttributesConstants]] = {}, flash_screen: bool = False, delay: Union[int, float] = .04, - random_delay: Tuple[float, float] = (0, 0), + random_delay: Union[Tuple[int, float], List[int, float]] = (0, 0), callback: Callable = lambda: None, cargs: Union[Tuple, List] = ()): """Writes the given text character by character in the current @@ -129,40 +129,40 @@ def char_by_char( corresponding to the pair of white color on black background initialized by ``curses``). This defaults to ``0``. - :type colors_pair_nb: Optional[int] + :type colors_pair_nb: int :param text_attr: Dialog box curses text attributes. It should be a single curses text attribute or a tuple of curses text attribute. This defaults an empty tuple. - :type text_attr: Optional[Union[CursesTextAttributesConstants,tuple[CursesTextAttributesConstants],list[CursesTextAttributesConstants]]] + :type text_attr: Union[CursesTextAttributesConstant,CursesTextAttributesConstants] :param words_attr: This defaults to an empty dictionary. - :type words_atttr: Union[Dict[tuple[str],CursesTextAttributesConstants],Dict[tuple[str],tuple[CursesTextAttributesConstants]]] + :type words_atttr: Union[dict[Tuple[str], CursesTextAttributesConstant],dict[Tuple[str], CursesTextAttributesConstants]] :param flash_screen: Allows or not to flash screen with a short light effect done before writing the first character by ``flash`` function from ``curses`` module. This defaults to ``False``. - :type flash_screen: Optional[bool] + :type flash_screen: bool :param delay: Waiting time between the writing of each character of text in second. This defaults to ``0.04``. - :type delay: Optional[Union[int, float]] + :type delay: Union[int, float] :param random_delay: Waiting time between the writing of each character in seconds where time waited is a random number generated in ``random_delay`` interval. This defaults to ``(0, 0)``. - :type random_delay: Optional[tuple[float, flot],list[float, float]] + :type random_delay: Union[tuple[int, float],list[int, float]] :param callback: Callable called after writing a character and the delay time has elapsed. This defaults to a lambda which do nothing. - :type callback: Optional[Callable] + :type callback: Callable :param cargs: All the arguments that will be passed to callback. This defaults to an empty tuple. - :type cargs: Optional[Union[tuple[Any],list[Any]]] + :type cargs: Union[tuple,list] .. NOTE:: Method flow: @@ -182,7 +182,8 @@ def char_by_char( If the volume of text displayed is too large to be contained in a dialog box, text will be automatically cut into paragraphs using ``textwrap.wrap`` function. See - `textwrap module documentation `_. + `textwrap module documentation + `_. for more information of the behavior of text wrap. .. WARNING:: @@ -248,15 +249,13 @@ def word_by_word( text: str, colors_pair_nb: int = 0, cut_char: str = " ", - text_attr: Union[CursesTextAttributesConstants, - Tuple[CursesTextAttributesConstants], - List[CursesTextAttributesConstants]] = (), - words_attr: Union[Dict[Tuple[str], CursesTextAttributesConstants], - Dict[Tuple[str], - Tuple[CursesTextAttributesConstants]]] = {}, + text_attr: Union[CursesTextAttributesConstant, + CursesTextAttributesConstants] = (), + words_attr: Union[Dict[Tuple[str], CursesTextAttributesConstant], + Dict[Tuple[str], CursesTextAttributesConstants]] = {}, flash_screen: bool = False, delay: Union[int, float] = .15, - random_delay: Tuple[float, float] = (0, 0), + random_delay: Union[Tuple[int, float], List[int, float]] = (0, 0), callback: Callable = lambda: None, cargs: Union[Tuple, List] = ()): """Writes the given text word by word at position in the current @@ -275,15 +274,15 @@ def word_by_word( the text. The number zero corresponding to the pair of white color on black background initialized by ``curses``). This defaults to ``0``. - :type colors_pair_nb: Optional[int] + :type colors_pair_nb: int :param text_attr: Dialog box curses text attributes. It should be a single curses text attribute or a tuple of curses text attribute. This defaults an empty tuple. - :type text_attr: Optional[Union[CursesTextAttributesConstants,tuple[CursesTextAttributesConstants],list[CursesTextAttributesConstants]]] + :type text_attr: Union[CursesTextAttributesConstant,CursesTextAttributesConstants] :param words_attr: This defaults to an empty dictionary. - :type words_atttr: Union[Dict[tuple[str],CursesTextAttributesConstants],Dict[tuple[str],tuple[CursesTextAttributesConstants]]] + :type words_atttr: Union[dict[Tuple[str], CursesTextAttributesConstant],dict[Tuple[str], CursesTextAttributesConstants]] :param cut_char: The delimiter according which to split the text in word. This defaults to ``" "``. @@ -293,26 +292,26 @@ def word_by_word( light effect done before writing the first character by ``flash`` function from ``curses`` module. This defaults to ``False``. - :type flash_screen: Optional[bool] + :type flash_screen: bool :param delay: Waiting time between the writing of each word of ``text`` in second. This defaults to ``0.15``. - :type delay: Optional[Union[int, float]] + :type delay: Union[int, float] :param random_delay: Waiting time between the writing of each word in seconds where time waited is a random number generated in ``random_delay`` interval. This defaults to ``(0, 0)``. - :type random_delay: Optional[tuple[float, float],list[float, float]] + :type random_delay: Union[tuple[int, float],list[int, float]] :param callback: Callable called after writing a word and the delay time has elapsed. This defaults to a lambda which do nothing. - :type callback: Optional[Callable] + :type callback: Callable :param cargs: All the arguments that will be passed to callback. This defaults to an empty tuple. - :type cargs: Optional[Union[tuple[Any],list[Any]]] + :type cargs: Union[tuple,list] .. NOTE:: Method flow: @@ -333,7 +332,8 @@ def word_by_word( If the volume of text displayed is too large to be contained in a dialog box, text will be automatically cut into paragraphs using ``textwrap.wrap`` function. See - `textwrap module documentation `_ + `textwrap module documentation + `_ for more information of the behavior of text wrap. .. WARNING:: diff --git a/visualdialog/utils.py b/visualdialog/utils.py index f415241..5a3d07c 100644 --- a/visualdialog/utils.py +++ b/visualdialog/utils.py @@ -24,11 +24,14 @@ # curses text attribute constants are integers. # See https://docs.python.org/3/library/curses.html?#constants -CursesTextAttributesConstants = int +CursesTextAttributesConstant = int +CursesTextAttributesConstants = Union[Tuple[int], List[int]] # curses key constants are integers. # See https://docs.python.org/3/library/curses.html?#constants -CursesKeyConstants = int +CursesKeyConstant = int +CursesKeyConstants = Union[Tuple[int], List[int]] + def _make_chunk(iterable: Union[Tuple, List], chunk_length: int) -> Generator: From 47e6836b60264970787cdd6f885143799c8bb3fe Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Sat, 27 Mar 2021 11:42:19 +0100 Subject: [PATCH 44/52] Rename TextBox to BaseTextBox, add Numeric type hinting. --- doc/source/visualdialog.rst | 2 +- visualdialog/box.py | 9 +++++---- visualdialog/dialog.py | 23 ++++++++++++----------- visualdialog/utils.py | 4 +++- 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/doc/source/visualdialog.rst b/doc/source/visualdialog.rst index 32b1ede..a7fadae 100644 --- a/doc/source/visualdialog.rst +++ b/doc/source/visualdialog.rst @@ -10,7 +10,7 @@ Text boxes TextBox ------- -.. autoclass:: visualdialog.box.TextBox +.. autoclass:: visualdialog.box.BaseTextBox :members: :undoc-members: :private-members: diff --git a/visualdialog/box.py b/visualdialog/box.py index 51df59b..f7c0fa0 100644 --- a/visualdialog/box.py +++ b/visualdialog/box.py @@ -19,7 +19,7 @@ # # -__all__ = ["TextBox"] +__all__ = ["BaseTextBox"] import curses import curses.textpad @@ -29,6 +29,7 @@ CursesKeyConstants, CursesTextAttributesConstant, CursesTextAttributesConstants, + Numeric, TextAttributes) @@ -47,7 +48,7 @@ def __str__(self): return f"text box was aborted by pressing the {self.key} key" -class TextBox: +class BaseTextBox: """This class provides attributs and methods to manage a text box. .. NOTE:: @@ -98,7 +99,7 @@ class TextBox: Waiting time in seconds after writing a character contained in ``downtime_chars``. This defaults to ``0.6``. - :type downtime_chars_delay: Union[int,float] + :type downtime_chars_delay: Numeric """ def __init__( @@ -113,7 +114,7 @@ def __init__( CursesTextAttributesConstants] = curses.A_BOLD, downtime_chars: Union[Tuple[str], List[str]] = (",", ".", ":", ";", "!", "?"), - downtime_chars_delay: Union[int, float] = .6): + downtime_chars_delay: Numeric = .6): self.pos_x, self.pos_y = pos_x, pos_y self.length, self.width = length, width diff --git a/visualdialog/dialog.py b/visualdialog/dialog.py index f04b897..aaa422f 100644 --- a/visualdialog/dialog.py +++ b/visualdialog/dialog.py @@ -27,14 +27,15 @@ import time from typing import Callable, Dict, List, Optional, Tuple, Union -from .box import TextBox +from .box import BaseTextBox from .utils import (CursesTextAttributesConstant, CursesTextAttributesConstants, + Numeric, TextAttributes, _make_chunk) -class DialogBox(TextBox): +class DialogBox(BaseTextBox): """This class provides methods and attributs to manage a dialog box. :param end_dialog_indicator: Character that will be displayed in the @@ -49,7 +50,7 @@ class DialogBox(TextBox): .. NOTE:: This class inherits all the methods and attributes of - ``TextBox``. + ``BaseTextBox``. .. WARNING:: Parameters ``downtime_chars`` and ``downtime_chars_delay`` do @@ -109,8 +110,8 @@ def char_by_char( words_attr: Union[Dict[Tuple[str], CursesTextAttributesConstant], Dict[Tuple[str], CursesTextAttributesConstants]] = {}, flash_screen: bool = False, - delay: Union[int, float] = .04, - random_delay: Union[Tuple[int, float], List[int, float]] = (0, 0), + delay: Numeric = .04, + random_delay: Union[Tuple[Numeric], List[Numeric]] = (0, 0), callback: Callable = lambda: None, cargs: Union[Tuple, List] = ()): """Writes the given text character by character in the current @@ -147,13 +148,13 @@ def char_by_char( :param delay: Waiting time between the writing of each character of text in second. This defaults to ``0.04``. - :type delay: Union[int, float] + :type delay: Numeric :param random_delay: Waiting time between the writing of each character in seconds where time waited is a random number generated in ``random_delay`` interval. This defaults to ``(0, 0)``. - :type random_delay: Union[tuple[int, float],list[int, float]] + :type random_delay: Union[tuple[Numeric],list[Numeric]] :param callback: Callable called after writing a character and the delay time has elapsed. This defaults to a lambda which @@ -254,8 +255,8 @@ def word_by_word( words_attr: Union[Dict[Tuple[str], CursesTextAttributesConstant], Dict[Tuple[str], CursesTextAttributesConstants]] = {}, flash_screen: bool = False, - delay: Union[int, float] = .15, - random_delay: Union[Tuple[int, float], List[int, float]] = (0, 0), + delay: Numeric = .15, + random_delay: Union[Tuple[Numeric], List[Numeric]] = (0, 0), callback: Callable = lambda: None, cargs: Union[Tuple, List] = ()): """Writes the given text word by word at position in the current @@ -296,13 +297,13 @@ def word_by_word( :param delay: Waiting time between the writing of each word of ``text`` in second. This defaults to ``0.15``. - :type delay: Union[int, float] + :type delay: Numeric :param random_delay: Waiting time between the writing of each word in seconds where time waited is a random number generated in ``random_delay`` interval. This defaults to ``(0, 0)``. - :type random_delay: Union[tuple[int, float],list[int, float]] + :type random_delay: Union[Tuple[Numeric], List[Numeric]] :param callback: Callable called after writing a word and the delay time has elapsed. This defaults to a lambda which do diff --git a/visualdialog/utils.py b/visualdialog/utils.py index 5a3d07c..98b76d7 100644 --- a/visualdialog/utils.py +++ b/visualdialog/utils.py @@ -19,9 +19,11 @@ # # -from typing import Generator, List, Tuple, Union +from typing import Generator, List, Tuple, TypeVar, Union +Numeric = TypeVar("Numeric", int, float) + # curses text attribute constants are integers. # See https://docs.python.org/3/library/curses.html?#constants CursesTextAttributesConstant = int From 6c7504fadbc2130217555503269325ac1ee9ae2a Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Sat, 27 Mar 2021 21:44:38 +0100 Subject: [PATCH 45/52] Rename argument `stdscr` to `win`, add CursesWindow, fix bug occured during instantiation of DialogBox, improve documentation. --- doc/source/faq.rst | 6 ++ doc/source/utils.rst | 2 +- doc/source/visualdialog.rst | 4 -- visualdialog/box.py | 47 ++++++-------- visualdialog/dialog.py | 118 ++++++++++++++++++------------------ visualdialog/utils.py | 18 +++--- 6 files changed, 92 insertions(+), 103 deletions(-) diff --git a/doc/source/faq.rst b/doc/source/faq.rst index 453132f..27ba176 100644 --- a/doc/source/faq.rst +++ b/doc/source/faq.rst @@ -16,3 +16,9 @@ You can also pass arguments to this callback via ``cargs`` parameter. The past callback is executed after downtime delay between character or word display. You can use this behavior to perform multiple tasks while ``DialogBox`` scrolling. + +I am not satisfied with the behavior of DialogBox, how can I change it? +----------------------------------------------------------------------- + +You can create your own derived class by inheriting from ``BaseTextBox``. +Additionally, you can override the methods of ``DialogBox``. diff --git a/doc/source/utils.rst b/doc/source/utils.rst index b7698bc..2eacc2e 100644 --- a/doc/source/utils.rst +++ b/doc/source/utils.rst @@ -16,5 +16,5 @@ Utils visualdialog.function(args) -.. autoclass:: visualdialog.utils.TextAttributes +.. automodule:: visualdialog.utils :members: diff --git a/doc/source/visualdialog.rst b/doc/source/visualdialog.rst index a7fadae..f35da96 100644 --- a/doc/source/visualdialog.rst +++ b/doc/source/visualdialog.rst @@ -13,14 +13,10 @@ TextBox .. autoclass:: visualdialog.box.BaseTextBox :members: :undoc-members: - :private-members: DialogBox --------- .. autoclass:: visualdialog.dialog.DialogBox - :special-members: :undoc-members: :members: - :private-members: - diff --git a/visualdialog/box.py b/visualdialog/box.py index f7c0fa0..7ec6d81 100644 --- a/visualdialog/box.py +++ b/visualdialog/box.py @@ -29,6 +29,7 @@ CursesKeyConstants, CursesTextAttributesConstant, CursesTextAttributesConstants, + CursesWindow, Numeric, TextAttributes) @@ -38,7 +39,6 @@ class PanicError(Exception): pressed. :param key: Key pressed that caused the exception to be thrown. - :type key: CursesKeyConstants """ def __init__(self, key: CursesKeyConstant): @@ -52,54 +52,45 @@ class BaseTextBox: """This class provides attributs and methods to manage a text box. .. NOTE:: - This class provides a general API for text boxes, it does not - need to be instantiated. + This class provides a general API for text boxes, it is not + intended to be instantiated. :param pos_x: x position of the dialog box in ``curses`` window object on which methods will have effects. - :type pos_x: int :param pos_y: y position of the dialog box in ``curses`` window object on which methods will have effects. - :type pos_y: int :param length: Length of the dialog box in ``curses`` window object on which methods will have effects. - :type length: int :param width: Width of the dialog box in ``curses`` window object on which methods will have effects. - :type width: int :param title: String that will be displayed in the upper left corner of dialog box. If title is an empty string, the title will not be displayed. This defaults an empty string. - :type title: str :param title_colors_pair_nb: Number of the curses color pair that will be used to color the title. Zero corresponding to the pair of white color on black background initialized by ``curses``). This defaults to ``0``. - :type title_colors_pair_nb: int :param title_text_attr: Dialog box title text attributes. It should be a single curses text attribute or a tuple of curses text attribute. This defaults to ``curses.A_BOLD``. - :type title_text_attr: Union[CursesTextAttributesConstant,CursesTextAttributesConstants] :param downtime_chars: List of characters that will trigger a ``downtime_chars_delay`` time second between the writing of each character. This defaults to ``(",", ".", ":", ";", "!", "?")``. - :type downtime_chars: Union[tuple[str],list[str]] :param downtime_chars_delay: Waiting time in seconds after writing a character contained in ``downtime_chars``. This defaults to ``0.6``. - :type downtime_chars_delay: Numeric """ def __init__( @@ -111,9 +102,9 @@ def __init__( title: str = "", title_colors_pair_nb: int = 0, title_text_attr: Union[CursesTextAttributesConstant, - CursesTextAttributesConstants] = curses.A_BOLD, + CursesTextAttributesConstants] = curses.A_BOLD, downtime_chars: Union[Tuple[str], - List[str]] = (",", ".", ":", ";", "!", "?"), + List[str]] = (",", ".", ":", ";", "!", "?"), downtime_chars_delay: Numeric = .6): self.pos_x, self.pos_y = pos_x, pos_y self.length, self.width = length, width @@ -142,16 +133,15 @@ def __init__( self.downtime_chars_delay = downtime_chars_delay #: List of accepted key codes to skip dialog. ``curses`` constants are supported. This defaults to an empty tuple. - self.confirm_dialog_key: List[CursesKeyConstants] = [] + self.confirm_dialog_key: List[CursesKeyConstant] = [] #: List of accepted key codes to raise PanicError. ``curses`` constants are supported. This defaults to an empty tuple. - self.panic_key: List[CursesKeyConstants] = [] + self.panic_key: List[CursesKeyConstant] = [] @property def position(self) -> Tuple[int]: """Returns a tuple contains x;y position of ``TextBox``. :returns: x;y position of ``TextBox``. - :rtype: tuple[int] """ return self.text_pos_x - 2, self.text_pos_y - 3 @@ -160,16 +150,15 @@ def dimensions(self) -> Tuple[int]: """Returns a tuple contains dimensions of ``TextBox``. :returns: Length and width of ``TextBox``. - :rtype: tuple[int] """ return self.length, self.width - def framing_box(self, stdscr): + def framing_box(self, win: CursesWindow): """Displays dialog box borders and his title. If attribute ``self.title`` is empty doesn't display the title. - :param stdscr: ``curses`` window object on which the method will + :param win: ``curses`` window object on which the method will have effect. """ title_length = len(self.title) + 4 @@ -179,30 +168,30 @@ def framing_box(self, stdscr): if self.title: attr = (self.title_colors, *self.title_text_attr) - curses.textpad.rectangle(stdscr, + curses.textpad.rectangle(win, self.pos_y, self.pos_x + 1, self.pos_y + title_width, self.pos_x + title_length) - with TextAttributes(stdscr, *attr): - stdscr.addstr(self.pos_y + 1, - self.pos_x + 3, - self.title) + with TextAttributes(win, *attr): + win.addstr(self.pos_y + 1, + self.pos_x + 3, + self.title) # Displays the borders of the dialog box. - curses.textpad.rectangle(stdscr, + curses.textpad.rectangle(win, self.pos_y + self.title_offsetting_y, self.pos_x, self.pos_y + self.title_offsetting_y + self.width, self.pos_x + self.length) - def getkey(self, stdscr): + def getkey(self, win: CursesWindow): """Blocks execution as long as a key contained in ``self.confirm_dialog_key`` is not detected. - :param stdscr: ``curses`` window object on which the method will + :param win: ``curses`` window object on which the method will have effect. :raises PanicError: If a key contained in ``self.panic_key`` is pressed. @@ -217,7 +206,7 @@ def getkey(self, stdscr): for more informations. """ while 1: - key = stdscr.getch() + key = win.getch() if key in self.confirm_dialog_key: break diff --git a/visualdialog/dialog.py b/visualdialog/dialog.py index aaa422f..a80b373 100644 --- a/visualdialog/dialog.py +++ b/visualdialog/dialog.py @@ -30,6 +30,7 @@ from .box import BaseTextBox from .utils import (CursesTextAttributesConstant, CursesTextAttributesConstants, + CursesWindow, Numeric, TextAttributes, _make_chunk) @@ -43,24 +44,41 @@ class DialogBox(BaseTextBox): been completed. String with a length of more than one character can lead to an overflow of the dialog box frame. This defaults to ``"►"``. - :type end_dialog_indicator: str :key kwargs: Keyword arguments correspond to the instance attributes of ``TextBox``. .. NOTE:: - This class inherits all the methods and attributes of - ``BaseTextBox``. + This class inherits from ``BaseTextBox``. + + .. NOTE:: + This class is a context manager. .. WARNING:: Parameters ``downtime_chars`` and ``downtime_chars_delay`` do not affect ``word_by_word`` method. """ - def __init__(self, - end_dialog_indicator: str = "►", - **kwargs): - super().__init__(**kwargs) + def __init__( + self, + pos_x: int, + pos_y: int, + length: int, + width: int, + title: str = "", + title_colors_pair_nb: int = 0, + title_text_attr: Union[CursesTextAttributesConstant, + CursesTextAttributesConstants] = curses.A_BOLD, + downtime_chars: Union[Tuple[str], + List[str]] = (",", ".", ":", ";", "!", "?"), + downtime_chars_delay: Numeric = .6, + end_dialog_indicator: str = "►"): + BaseTextBox.__init__(self, + pos_x, pos_y, + length, width, + title, + title_colors_pair_nb, title_text_attr, + downtime_chars, downtime_chars_delay) self.end_dialog_indicator_char = end_dialog_indicator self.end_dialog_indicator_pos_x = self.pos_x + self.length - 2 @@ -80,29 +98,28 @@ def __exit__(self, type, value, traceback): def _display_end_dialog_indicator( self, - stdscr, + win: CursesWindow, text_attr: CursesTextAttributesConstants = (curses.A_BOLD, curses.A_BLINK)): """Displays an end of dialog indicator in the lower right corner of textbox. - :param stdscr: ``curses`` window object on which the method + :param win: ``curses`` window object on which the method will have effect. :param text_attr: Text attributes of ``end_dialog_indicator`` method. This defaults to ``(curses.A_BOLD, curses.A_BLINK)``. - :type text_attr: CursesTextAttributesConstants """ if self.end_dialog_indicator_char: - with TextAttributes(stdscr, *text_attr): - stdscr.addch(self.end_dialog_indicator_pos_y, - self.end_dialog_indicator_pos_x, - self.end_dialog_indicator_char) + with TextAttributes(win, *text_attr): + win.addch(self.end_dialog_indicator_pos_y, + self.end_dialog_indicator_pos_x, + self.end_dialog_indicator_char) def char_by_char( self, - stdscr, + win: CursesWindow, text: str, colors_pair_nb: int = 0, text_attr: Union[CursesTextAttributesConstant, @@ -117,53 +134,44 @@ def char_by_char( """Writes the given text character by character in the current dialog box. - :param stdscr: ``curses`` window object on which the method will + :param win: ``curses`` window object on which the method will have effect. :param text: Text that will be displayed character by character in the dialog box. This text can be wrapped to fit the proportions of the dialog box. - :type text: str :param colors_pair_nb: Number of the curses color pair that will be used to color the text. The number zero corresponding to the pair of white color on black background initialized by ``curses``). This defaults to ``0``. - :type colors_pair_nb: int :param text_attr: Dialog box curses text attributes. It should be a single curses text attribute or a tuple of curses text attribute. This defaults an empty tuple. - :type text_attr: Union[CursesTextAttributesConstant,CursesTextAttributesConstants] :param words_attr: This defaults to an empty dictionary. - :type words_atttr: Union[dict[Tuple[str], CursesTextAttributesConstant],dict[Tuple[str], CursesTextAttributesConstants]] :param flash_screen: Allows or not to flash screen with a short light effect done before writing the first character by ``flash`` function from ``curses`` module. This defaults to ``False``. - :type flash_screen: bool :param delay: Waiting time between the writing of each character of text in second. This defaults to ``0.04``. - :type delay: Numeric :param random_delay: Waiting time between the writing of each character in seconds where time waited is a random number generated in ``random_delay`` interval. This defaults to ``(0, 0)``. - :type random_delay: Union[tuple[Numeric],list[Numeric]] :param callback: Callable called after writing a character and the delay time has elapsed. This defaults to a lambda which do nothing. - :type callback: Callable :param cargs: All the arguments that will be passed to callback. This defaults to an empty tuple. - :type cargs: Union[tuple,list] .. NOTE:: Method flow: @@ -177,7 +185,7 @@ def char_by_char( - Waits until a key contained in the class attribute ``confirm_dialog_key`` was pressed before writing the following paragraph. - - Complete cleaning ``stdscr``. + - Complete cleaning ``win``. .. WARNING:: If the volume of text displayed is too large to be contained @@ -188,11 +196,11 @@ def char_by_char( for more information of the behavior of text wrap. .. WARNING:: - ``stdscr`` will be completely cleaned when writing each + ``win`` will be completely cleaned when writing each paragraph by ``window.clear()`` method of ``curses`` module. """ - self.framing_box(stdscr) + self.framing_box(win) if flash_screen: curses.flash() @@ -201,8 +209,8 @@ def char_by_char( wrapped_text = _make_chunk(wrapped_text, self.nb_lines_max) for paragraph in wrapped_text: - stdscr.clear() - self.framing_box(stdscr) + win.clear() + self.framing_box(win) for y, line in enumerate(paragraph): offsetting_x = 0 @@ -219,12 +227,12 @@ def char_by_char( attr = (curses.color_pair(colors_pair_nb), *text_attr) - with TextAttributes(stdscr, *attr): + with TextAttributes(win, *attr): for x, char in enumerate(word): - stdscr.addstr(self.text_pos_y + y, - self.text_pos_x + x + offsetting_x, - char) - stdscr.refresh() + win.addstr(self.text_pos_y + y, + self.text_pos_x + x + offsetting_x, + char) + win.refresh() if char in self.downtime_chars: time.sleep(self.downtime_chars_delay + @@ -241,12 +249,12 @@ def char_by_char( # Compensates for the space between words. offsetting_x += len(word) + 1 - self._display_end_dialog_indicator(stdscr) - self.getkey(stdscr) + self._display_end_dialog_indicator(win) + self.getkey(win) def word_by_word( self, - stdscr, + win: CursesWindow, text: str, colors_pair_nb: int = 0, cut_char: str = " ", @@ -262,57 +270,47 @@ def word_by_word( """Writes the given text word by word at position in the current dialog box. - :param stdscr: ``curses`` window object on which the method will + :param win: ``curses`` window object on which the method will have effect. :param text: Text that will be displayed word by word in the dialog box. This text can be wrapped to fit the proportions of the dialog box. - :type text: str :param colors_pair_nb: Number of the curses color pair that will be used to color the text. The number zero corresponding to the pair of white color on black background initialized by ``curses``). This defaults to ``0``. - :type colors_pair_nb: int :param text_attr: Dialog box curses text attributes. It should be a single curses text attribute or a tuple of curses text attribute. This defaults an empty tuple. - :type text_attr: Union[CursesTextAttributesConstant,CursesTextAttributesConstants] :param words_attr: This defaults to an empty dictionary. - :type words_atttr: Union[dict[Tuple[str], CursesTextAttributesConstant],dict[Tuple[str], CursesTextAttributesConstants]] :param cut_char: The delimiter according which to split the text in word. This defaults to ``" "``. - :type cut_char: str :param flash_screen: Allows or not to flash screen with a short light effect done before writing the first character by ``flash`` function from ``curses`` module. This defaults to ``False``. - :type flash_screen: bool :param delay: Waiting time between the writing of each word of ``text`` in second. This defaults to ``0.15``. - :type delay: Numeric :param random_delay: Waiting time between the writing of each word in seconds where time waited is a random number generated in ``random_delay`` interval. This defaults to ``(0, 0)``. - :type random_delay: Union[Tuple[Numeric], List[Numeric]] :param callback: Callable called after writing a word and the delay time has elapsed. This defaults to a lambda which do nothing. - :type callback: Callable :param cargs: All the arguments that will be passed to callback. This defaults to an empty tuple. - :type cargs: Union[tuple,list] .. NOTE:: Method flow: @@ -327,7 +325,7 @@ def word_by_word( - Waits until a key contained in the class attribute ``confirm_dialog_key`` was pressed before writing the following paragraph. - - Complete cleaning ``stdscr``. + - Complete cleaning ``win``. .. WARNING:: If the volume of text displayed is too large to be contained @@ -338,11 +336,11 @@ def word_by_word( for more information of the behavior of text wrap. .. WARNING:: - ``stdscr`` will be completely cleaned when writing each + ``win`` will be completely cleaned when writing each paragraph by ``window.clear()`` method of ``curses`` module. """ - self.framing_box(stdscr) + self.framing_box(win) if flash_screen: curses.flash() @@ -353,8 +351,8 @@ def word_by_word( wrapped_text = _make_chunk(wrapped_text, self.nb_lines_max) for paragraph in wrapped_text: - stdscr.clear() - self.framing_box(stdscr) + win.clear() + self.framing_box(win) for y, line in enumerate(paragraph): offsetting_x = 0 for word in line.split(cut_char): @@ -370,10 +368,10 @@ def word_by_word( attr = (curses.color_pair(colors_pair_nb), *text_attr) - with TextAttributes(stdscr, *attr): - stdscr.addstr(self.text_pos_y + y, - self.text_pos_x + offsetting_x, word) - stdscr.refresh() + with TextAttributes(win, *attr): + win.addstr(self.text_pos_y + y, + self.text_pos_x + offsetting_x, word) + win.refresh() # Compensates for the space between words. offsetting_x += len(word) + 1 @@ -382,5 +380,5 @@ def word_by_word( callback(*cargs) - self._display_end_dialog_indicator(stdscr) - self.getkey(stdscr) + self._display_end_dialog_indicator(win) + self.getkey(win) diff --git a/visualdialog/utils.py b/visualdialog/utils.py index 98b76d7..97cffa6 100644 --- a/visualdialog/utils.py +++ b/visualdialog/utils.py @@ -18,19 +18,21 @@ # MA 02110-1301, USA. # # - +import _curses from typing import Generator, List, Tuple, TypeVar, Union Numeric = TypeVar("Numeric", int, float) -# curses text attribute constants are integers. -# See https://docs.python.org/3/library/curses.html?#constants +CursesWindow = _curses.window + +#: curses text attribute constants are integers. +#: See https://docs.python.org/3/library/curses.html?#constants CursesTextAttributesConstant = int CursesTextAttributesConstants = Union[Tuple[int], List[int]] -# curses key constants are integers. -# See https://docs.python.org/3/library/curses.html?#constants +#: curses key constants are integers. +#: See https://docs.python.org/3/library/curses.html?#constants CursesKeyConstant = int CursesKeyConstants = Union[Tuple[int], List[int]] @@ -41,7 +43,6 @@ def _make_chunk(iterable: Union[Tuple, List], into chunk_length bundles. :returns: Iterator separated into chunk_length bundles. - :rtype: Generator """ return (iterable[chunk:chunk + chunk_length] for chunk in range(0, len(iterable), chunk_length)) @@ -54,12 +55,11 @@ class TextAttributes: managed. :param attributes: List of attributes to activate and desactivate. - :type attributes: Union[tuple[CursesTextAttributesConstants],list[CursesTextAttributesConstants]] """ def __init__(self, - stdscr, + win: CursesWindow, *attributes: CursesTextAttributesConstants): - self.win = stdscr + self.win = win self.attributes = attributes def __enter__(self): From 31f4a43e6a6fea045b8f45d617043a973bc6748d Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Sat, 27 Mar 2021 23:57:10 +0100 Subject: [PATCH 46/52] , change lib version number. --- README.md | 7 ++-- tests/test.py | 13 ++++---- visualdialog/__init__.py | 2 +- .../__pycache__/__init__.cpython-37.pyc | Bin 0 -> 358 bytes visualdialog/__pycache__/box.cpython-37.pyc | Bin 0 -> 6486 bytes .../__pycache__/dialog.cpython-37.pyc | Bin 0 -> 12833 bytes visualdialog/__pycache__/utils.cpython-37.pyc | Bin 0 -> 2050 bytes visualdialog/dialog.py | 31 +++++++++--------- 8 files changed, 27 insertions(+), 26 deletions(-) create mode 100644 visualdialog/__pycache__/__init__.cpython-37.pyc create mode 100644 visualdialog/__pycache__/box.cpython-37.pyc create mode 100644 visualdialog/__pycache__/dialog.cpython-37.pyc create mode 100644 visualdialog/__pycache__/utils.cpython-37.pyc diff --git a/README.md b/README.md index 601e5e9..bb5f1b0 100644 --- a/README.md +++ b/README.md @@ -72,11 +72,14 @@ import curses from visualdialog import DialogBox +x, y = (0, 0) +length, width = (35, 6) + def main(stdscr): curses.curs_set(False) - textbox = DialogBox(0, 0, - 35, 6, + textbox = DialogBox(x, y, + length, width, title="Demo") textbox.char_by_char(stdscr, "Hello world") diff --git a/tests/test.py b/tests/test.py index 9fd5167..c0cbbcd 100644 --- a/tests/test.py +++ b/tests/test.py @@ -18,16 +18,15 @@ def main(stdscr): curses.curs_set(0) - curses.start_color() curses.init_pair(1, curses.COLOR_RED, curses.COLOR_BLACK) curses.init_pair(2, curses.COLOR_CYAN, curses.COLOR_BLACK) curses.init_pair(3, curses.COLOR_GREEN, curses.COLOR_BLACK) textbox = DialogBox(0, 0, 40, 6, - # ~ title="Tim-ats-d", - title_colors_pair_nb=3, - end_dialog_indicator="o") + # title="Tim-ats-d", + # title_colors_pair_nb=3, + end_indicator="o") textbox.confirm_dialog_key = (32, ) textbox.panic_key = (10, ) @@ -44,9 +43,9 @@ def func(text: str): textbox.char_by_char(stdscr, reply, cargs=(reply, ), - callback=func) - # ~ text_attr=(curses.A_ITALIC, curses.A_BOLD), - # ~ words_attr=special_words) + callback=func, + text_attr=(curses.A_ITALIC, curses.A_BOLD), + words_attr=special_words) with visualdialog.TextAttributes(stdscr, curses.A_BOLD, curses.A_ITALIC): ... diff --git a/visualdialog/__init__.py b/visualdialog/__init__.py index 3b4b4bf..2a3130e 100644 --- a/visualdialog/__init__.py +++ b/visualdialog/__init__.py @@ -2,7 +2,7 @@ A library to make easier dialog box in terminal. """ -__version__ = 0.6 +__version__ = 0.7 __author__ = "Timéo Arnouts" from .dialog import DialogBox diff --git a/visualdialog/__pycache__/__init__.cpython-37.pyc b/visualdialog/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5a051bf414c286ea7724ceb6a1a7f8068d3ee5f9 GIT binary patch literal 358 zcmXw!&rZTX5XQTuv{;2_*rObzi5|Qd6~fsI#6vIR;%;EWZkOyI0=^OPF?<9Q5502o z6+AgD`Xw{-{U({2%%tDXBh=^D?ByNnv4MZZXdW@#B_rB#xOoMr&jGf=I%z6| z9DoJCG8UjrPv>p(Slta?DrYwsMw@-zyVHgTCGMM*u34DG2}$A$@(X8eWp@Ao literal 0 HcmV?d00001 diff --git a/visualdialog/__pycache__/box.cpython-37.pyc b/visualdialog/__pycache__/box.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6fbbe7b4fb0dd3db8bd84943273f884ea262240d GIT binary patch literal 6486 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 B9tr>e literal 0 HcmV?d00001 diff --git a/visualdialog/__pycache__/dialog.cpython-37.pyc b/visualdialog/__pycache__/dialog.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..35781c7adec769ca06443242c17ef6f212736129 GIT binary patch literal 12833 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 literal 0 HcmV?d00001 diff --git a/visualdialog/dialog.py b/visualdialog/dialog.py index a80b373..a134051 100644 --- a/visualdialog/dialog.py +++ b/visualdialog/dialog.py @@ -72,7 +72,7 @@ def __init__( downtime_chars: Union[Tuple[str], List[str]] = (",", ".", ":", ";", "!", "?"), downtime_chars_delay: Numeric = .6, - end_dialog_indicator: str = "►"): + end_indicator: str = "►"): BaseTextBox.__init__(self, pos_x, pos_y, length, width, @@ -80,13 +80,13 @@ def __init__( title_colors_pair_nb, title_text_attr, downtime_chars, downtime_chars_delay) - self.end_dialog_indicator_char = end_dialog_indicator - self.end_dialog_indicator_pos_x = self.pos_x + self.length - 2 + self.end_indicator_char = end_indicator + self.end_indicator_pos_x = self.pos_x + self.length - 2 if self.title: - self.end_dialog_indicator_pos_y = self.pos_y + self.width + 1 + self.end_indicator_pos_y = self.pos_y + self.width + 1 else: - self.end_dialog_indicator_pos_y = self.pos_y + self.width - 1 + self.end_indicator_pos_y = self.pos_y + self.width - 1 self.text_wrapper = textwrap.TextWrapper(width=self.nb_char_max_line) @@ -96,26 +96,26 @@ def __enter__(self): def __exit__(self, type, value, traceback): ... - def _display_end_dialog_indicator( + def _display_end_indicator( self, win: CursesWindow, text_attr: CursesTextAttributesConstants = (curses.A_BOLD, curses.A_BLINK)): - """Displays an end of dialog indicator in the lower right corner - of textbox. + """Displays an end indicator in the lower right corner of + textbox. :param win: ``curses`` window object on which the method will have effect. :param text_attr: Text attributes of - ``end_dialog_indicator`` method. This defaults to + ``end_indicator`` method. This defaults to ``(curses.A_BOLD, curses.A_BLINK)``. """ - if self.end_dialog_indicator_char: + if self.end_indicator_char: with TextAttributes(win, *text_attr): - win.addch(self.end_dialog_indicator_pos_y, - self.end_dialog_indicator_pos_x, - self.end_dialog_indicator_char) + win.addch(self.end_indicator_pos_y, + self.end_indicator_pos_x, + self.end_indicator_char) def char_by_char( self, @@ -249,7 +249,7 @@ def char_by_char( # Compensates for the space between words. offsetting_x += len(word) + 1 - self._display_end_dialog_indicator(win) + self._display_end_indicator(win) self.getkey(win) def word_by_word( @@ -321,7 +321,6 @@ def word_by_word( - Writing paragraph by paragraph. - Writing each line of the current paragraph, word by word. - - Calling ``_display_end_dialog_indicator`` method. - Waits until a key contained in the class attribute ``confirm_dialog_key`` was pressed before writing the following paragraph. @@ -380,5 +379,5 @@ def word_by_word( callback(*cargs) - self._display_end_dialog_indicator(win) + self._display_end_indicator(win) self.getkey(win) From 02439b5780fc9379e988b1762761544de376621c Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Sat, 27 Mar 2021 23:57:50 +0100 Subject: [PATCH 47/52] Revert ", change lib version number." This reverts commit 31f4a43e6a6fea045b8f45d617043a973bc6748d. --- README.md | 7 ++-- tests/test.py | 13 ++++---- visualdialog/__init__.py | 2 +- .../__pycache__/__init__.cpython-37.pyc | Bin 358 -> 0 bytes visualdialog/__pycache__/box.cpython-37.pyc | Bin 6486 -> 0 bytes .../__pycache__/dialog.cpython-37.pyc | Bin 12833 -> 0 bytes visualdialog/__pycache__/utils.cpython-37.pyc | Bin 2050 -> 0 bytes visualdialog/dialog.py | 31 +++++++++--------- 8 files changed, 26 insertions(+), 27 deletions(-) delete mode 100644 visualdialog/__pycache__/__init__.cpython-37.pyc delete mode 100644 visualdialog/__pycache__/box.cpython-37.pyc delete mode 100644 visualdialog/__pycache__/dialog.cpython-37.pyc delete mode 100644 visualdialog/__pycache__/utils.cpython-37.pyc diff --git a/README.md b/README.md index bb5f1b0..601e5e9 100644 --- a/README.md +++ b/README.md @@ -72,14 +72,11 @@ import curses from visualdialog import DialogBox -x, y = (0, 0) -length, width = (35, 6) - def main(stdscr): curses.curs_set(False) - textbox = DialogBox(x, y, - length, width, + textbox = DialogBox(0, 0, + 35, 6, title="Demo") textbox.char_by_char(stdscr, "Hello world") diff --git a/tests/test.py b/tests/test.py index c0cbbcd..9fd5167 100644 --- a/tests/test.py +++ b/tests/test.py @@ -18,15 +18,16 @@ def main(stdscr): curses.curs_set(0) + curses.start_color() curses.init_pair(1, curses.COLOR_RED, curses.COLOR_BLACK) curses.init_pair(2, curses.COLOR_CYAN, curses.COLOR_BLACK) curses.init_pair(3, curses.COLOR_GREEN, curses.COLOR_BLACK) textbox = DialogBox(0, 0, 40, 6, - # title="Tim-ats-d", - # title_colors_pair_nb=3, - end_indicator="o") + # ~ title="Tim-ats-d", + title_colors_pair_nb=3, + end_dialog_indicator="o") textbox.confirm_dialog_key = (32, ) textbox.panic_key = (10, ) @@ -43,9 +44,9 @@ def func(text: str): textbox.char_by_char(stdscr, reply, cargs=(reply, ), - callback=func, - text_attr=(curses.A_ITALIC, curses.A_BOLD), - words_attr=special_words) + callback=func) + # ~ text_attr=(curses.A_ITALIC, curses.A_BOLD), + # ~ words_attr=special_words) with visualdialog.TextAttributes(stdscr, curses.A_BOLD, curses.A_ITALIC): ... diff --git a/visualdialog/__init__.py b/visualdialog/__init__.py index 2a3130e..3b4b4bf 100644 --- a/visualdialog/__init__.py +++ b/visualdialog/__init__.py @@ -2,7 +2,7 @@ A library to make easier dialog box in terminal. """ -__version__ = 0.7 +__version__ = 0.6 __author__ = "Timéo Arnouts" from .dialog import DialogBox diff --git a/visualdialog/__pycache__/__init__.cpython-37.pyc b/visualdialog/__pycache__/__init__.cpython-37.pyc deleted file mode 100644 index 5a051bf414c286ea7724ceb6a1a7f8068d3ee5f9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 358 zcmXw!&rZTX5XQTuv{;2_*rObzi5|Qd6~fsI#6vIR;%;EWZkOyI0=^OPF?<9Q5502o z6+AgD`Xw{-{U({2%%tDXBh=^D?ByNnv4MZZXdW@#B_rB#xOoMr&jGf=I%z6| z9DoJCG8UjrPv>p(Slta?DrYwsMw@-zyVHgTCGMM*u34DG2}$A$@(X8eWp@Ao diff --git a/visualdialog/__pycache__/box.cpython-37.pyc b/visualdialog/__pycache__/box.cpython-37.pyc deleted file mode 100644 index 6fbbe7b4fb0dd3db8bd84943273f884ea262240d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6486 zcmcgwOOG4J5uOJ*9C9C8t%O(s;-s~dLN)JmJT^NxdaFh$FP6^LGlxV{DAz1x#ZxJj=3tAeAPW1K6Y)#KoSzu)352S zs;>IFs;;fAH7xx8b@0pX%a<+d3u;V%6;xiwlXX#WYv1lz+~!VfcWnK3^xKWyVWm?U zRy)<9*YRwNS9mq94gHQktas|eMyFw0Ke4#SYY!}56D#-K&MMkIx6rokJDoM|ylXY< zdF{41QrFU>N7&BVZMu2oW|XPCdV4&IMP7X`iPGeejWG;uULVU$WIq+h*V80ZVWP79 zNz+HGyrGng_QpzNv*Go{fgbHL{kf>Tjwky*3SogUtK)FHH8iJps(rVl}yBa-T1R5 z*Uj;FmKkTu%t92FZ>koe{b+FFw6VUt$oq42$mv+nA?9-8HkXnq|} z_7@cMn#&yy?LDZHIrgi$S`oG7_cUt3+z+FKr-$VR>E40pDF*O397MfA$?#zm$7~RO1QYM~ zF(aF|>0;L?Q4456-M|az`v034?7B=$ERuaSXtSI8&Hgj7Fe8qcbiR>TrduwPau{)< zk{tC=QR!Jwd=e9*u-#vFHQt zQwhy2>3}3>%4M{Il>RN~9j%Z11eU|ZaHNhI5DHsuYmGFcdP7q4`Aui5Xw%`MAC6;n zYMpr%EO>caUwmQw0e1RuK^gUgpqIv}%z{xE$spOAndt6#xCj0UtuV#`MlqSuTo61b z9CO>H#W%0h3_VjUrARN@L9r3u&4O*PYcG3YTAe$AW@7sU&lO;m(?xi<)9Hj^$n zms$eo4tq*S+%KiR!D9CJp_(C%cX5G$3z%rPTS|x4W|@GGN2j0;1>8Qe-9rf_f`voK zA_S(>_IjT$voozr&uwjQZL_Tw6>Tc6P;r@xt6R-QaW4|qUY53zCz+&?8r8m1HiosC zgx(Jg2bx3NUxrBlLLbs9`3q;9!L(+MZYiK;2~L?PFX=`7dZG2obVghIu8V9IL@qN6 ztJ?0zX{f*T<*uL`EzWms}y8n{?eQ^~rM^#^<=5?HV=I+R= z8oKkUwz~Y{Ss<35Grgs<=j)4f<`);+hVGsYw?G)ZT|C$EWPd~fC);r>ar>CU#GBOUJ@HX)OzJ4lbNY5umlw4A@9jP!scO+ma!Vgk zkfJ0cS9y36SKHFD<$3xszYt$CJ^{*7~}=4&S}uY&Z?O;dpk%UUhX1b+6*N zCmRHbh{rbv8>3%al8v^_ULTMZk`HKXkjCXRAoUSxdee~<;>jk}&knZy zsmuCX%rVhcZ6=)Q;DSg<9kmTnAVea;V{1{^C%^epQE*^(k|9{n2%f)A5QXzfk%$I{ zQ{NDy^!qwtKm?^_e2EAeGtxer6T;?HLuSKeyk5n;`cw>mLq%yJ+}AL151?_+C85X% zC>=8P1NV2%y^2C`__;^1{Q(UBz&9X*x(1U2f8so0J*?W6s!wcuRr$>M#QOD?4WRnG zLEjYfeGULZ+kWWU*83K({>*yE`h~T$`ihOs$6~W0YR-?P#1gP4NFAa$lVlH%>-TIP}Oxi9qgvyuE+-&P4Vo;ZcuMR}i7$+z-w3Jp1Z6Bu=&(1;5KY3oqG{Oo_P9Bmm! zNNloMim6RO-Dy^)N?u-En(~rT(!RxHBK4fkl2GsS>~;$0j`D_rk%LVDhg*@{Ig?vu zh!(<3Fe&=*{8YNI4P#xUQ#jhYM+&7k2D++{j$>)!s{^G*S$k&(lQLxgkk(0BsodXr zX(x{MWGIh!itu8fhVj*>N&;T%1~UUHXr?_2!$WMCK_JFLTl+%w1`tOv*q7;W&W@Kz zGKsntyS2z6EA0Ni%Z`>%Yo1InwnPwRrG}}bd=sieEyg16LyYe>D|uCOD1QLckuOp~ z-el7?j?0gzgHp81*6iUcoa)~(lu?4wy5QJ8T#^cmw&9-W{5UxwX4)=NbmwW6O)4%> zL0*R>M9MuA{mB=o;S%s5k7r4`iKO-j$;9)jcF1*>Gxw3&LoP!d%OBFDSE#r`#gC~t zEB#6NL0Uh%gu;Kh;ybqERvgE^x#ifG(7NRNmjRndsfKZkl&Z$c^+JCN$jm*sCqOVs z1R2tZ$h-=$h%=Lg)ud|_t(QrBEm57KJd3v6f7PhXYu_RQ$@TDnhQ58#xmeve_iq5l B9tr>e diff --git a/visualdialog/__pycache__/dialog.cpython-37.pyc b/visualdialog/__pycache__/dialog.cpython-37.pyc deleted file mode 100644 index 35781c7adec769ca06443242c17ef6f212736129..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12833 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 diff --git a/visualdialog/dialog.py b/visualdialog/dialog.py index a134051..a80b373 100644 --- a/visualdialog/dialog.py +++ b/visualdialog/dialog.py @@ -72,7 +72,7 @@ def __init__( downtime_chars: Union[Tuple[str], List[str]] = (",", ".", ":", ";", "!", "?"), downtime_chars_delay: Numeric = .6, - end_indicator: str = "►"): + end_dialog_indicator: str = "►"): BaseTextBox.__init__(self, pos_x, pos_y, length, width, @@ -80,13 +80,13 @@ def __init__( title_colors_pair_nb, title_text_attr, downtime_chars, downtime_chars_delay) - self.end_indicator_char = end_indicator - self.end_indicator_pos_x = self.pos_x + self.length - 2 + self.end_dialog_indicator_char = end_dialog_indicator + self.end_dialog_indicator_pos_x = self.pos_x + self.length - 2 if self.title: - self.end_indicator_pos_y = self.pos_y + self.width + 1 + self.end_dialog_indicator_pos_y = self.pos_y + self.width + 1 else: - self.end_indicator_pos_y = self.pos_y + self.width - 1 + self.end_dialog_indicator_pos_y = self.pos_y + self.width - 1 self.text_wrapper = textwrap.TextWrapper(width=self.nb_char_max_line) @@ -96,26 +96,26 @@ def __enter__(self): def __exit__(self, type, value, traceback): ... - def _display_end_indicator( + def _display_end_dialog_indicator( self, win: CursesWindow, text_attr: CursesTextAttributesConstants = (curses.A_BOLD, curses.A_BLINK)): - """Displays an end indicator in the lower right corner of - textbox. + """Displays an end of dialog indicator in the lower right corner + of textbox. :param win: ``curses`` window object on which the method will have effect. :param text_attr: Text attributes of - ``end_indicator`` method. This defaults to + ``end_dialog_indicator`` method. This defaults to ``(curses.A_BOLD, curses.A_BLINK)``. """ - if self.end_indicator_char: + if self.end_dialog_indicator_char: with TextAttributes(win, *text_attr): - win.addch(self.end_indicator_pos_y, - self.end_indicator_pos_x, - self.end_indicator_char) + win.addch(self.end_dialog_indicator_pos_y, + self.end_dialog_indicator_pos_x, + self.end_dialog_indicator_char) def char_by_char( self, @@ -249,7 +249,7 @@ def char_by_char( # Compensates for the space between words. offsetting_x += len(word) + 1 - self._display_end_indicator(win) + self._display_end_dialog_indicator(win) self.getkey(win) def word_by_word( @@ -321,6 +321,7 @@ def word_by_word( - Writing paragraph by paragraph. - Writing each line of the current paragraph, word by word. + - Calling ``_display_end_dialog_indicator`` method. - Waits until a key contained in the class attribute ``confirm_dialog_key`` was pressed before writing the following paragraph. @@ -379,5 +380,5 @@ def word_by_word( callback(*cargs) - self._display_end_indicator(win) + self._display_end_dialog_indicator(win) self.getkey(win) From 7fded8af729b64cfb9aeb5cad9120e35101d8374 Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Sun, 28 Mar 2021 00:00:28 +0100 Subject: [PATCH 48/52] Improve readme example, change DialogBox arg to , patch .gitignore, change lib version number. --- .gitignore | 133 ++++++++++++++++++++++++++++++++++++++- README.md | 7 ++- tests/test.py | 13 ++-- visualdialog/__init__.py | 2 +- visualdialog/dialog.py | 31 +++++---- 5 files changed, 157 insertions(+), 29 deletions(-) diff --git a/.gitignore b/.gitignore index 0a28388..e1e182a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,132 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + .vscode .idea - -# Sphinx documentation -/doc/build/ \ No newline at end of file diff --git a/README.md b/README.md index 601e5e9..bb5f1b0 100644 --- a/README.md +++ b/README.md @@ -72,11 +72,14 @@ import curses from visualdialog import DialogBox +x, y = (0, 0) +length, width = (35, 6) + def main(stdscr): curses.curs_set(False) - textbox = DialogBox(0, 0, - 35, 6, + textbox = DialogBox(x, y, + length, width, title="Demo") textbox.char_by_char(stdscr, "Hello world") diff --git a/tests/test.py b/tests/test.py index 9fd5167..c0cbbcd 100644 --- a/tests/test.py +++ b/tests/test.py @@ -18,16 +18,15 @@ def main(stdscr): curses.curs_set(0) - curses.start_color() curses.init_pair(1, curses.COLOR_RED, curses.COLOR_BLACK) curses.init_pair(2, curses.COLOR_CYAN, curses.COLOR_BLACK) curses.init_pair(3, curses.COLOR_GREEN, curses.COLOR_BLACK) textbox = DialogBox(0, 0, 40, 6, - # ~ title="Tim-ats-d", - title_colors_pair_nb=3, - end_dialog_indicator="o") + # title="Tim-ats-d", + # title_colors_pair_nb=3, + end_indicator="o") textbox.confirm_dialog_key = (32, ) textbox.panic_key = (10, ) @@ -44,9 +43,9 @@ def func(text: str): textbox.char_by_char(stdscr, reply, cargs=(reply, ), - callback=func) - # ~ text_attr=(curses.A_ITALIC, curses.A_BOLD), - # ~ words_attr=special_words) + callback=func, + text_attr=(curses.A_ITALIC, curses.A_BOLD), + words_attr=special_words) with visualdialog.TextAttributes(stdscr, curses.A_BOLD, curses.A_ITALIC): ... diff --git a/visualdialog/__init__.py b/visualdialog/__init__.py index 3b4b4bf..2a3130e 100644 --- a/visualdialog/__init__.py +++ b/visualdialog/__init__.py @@ -2,7 +2,7 @@ A library to make easier dialog box in terminal. """ -__version__ = 0.6 +__version__ = 0.7 __author__ = "Timéo Arnouts" from .dialog import DialogBox diff --git a/visualdialog/dialog.py b/visualdialog/dialog.py index a80b373..a134051 100644 --- a/visualdialog/dialog.py +++ b/visualdialog/dialog.py @@ -72,7 +72,7 @@ def __init__( downtime_chars: Union[Tuple[str], List[str]] = (",", ".", ":", ";", "!", "?"), downtime_chars_delay: Numeric = .6, - end_dialog_indicator: str = "►"): + end_indicator: str = "►"): BaseTextBox.__init__(self, pos_x, pos_y, length, width, @@ -80,13 +80,13 @@ def __init__( title_colors_pair_nb, title_text_attr, downtime_chars, downtime_chars_delay) - self.end_dialog_indicator_char = end_dialog_indicator - self.end_dialog_indicator_pos_x = self.pos_x + self.length - 2 + self.end_indicator_char = end_indicator + self.end_indicator_pos_x = self.pos_x + self.length - 2 if self.title: - self.end_dialog_indicator_pos_y = self.pos_y + self.width + 1 + self.end_indicator_pos_y = self.pos_y + self.width + 1 else: - self.end_dialog_indicator_pos_y = self.pos_y + self.width - 1 + self.end_indicator_pos_y = self.pos_y + self.width - 1 self.text_wrapper = textwrap.TextWrapper(width=self.nb_char_max_line) @@ -96,26 +96,26 @@ def __enter__(self): def __exit__(self, type, value, traceback): ... - def _display_end_dialog_indicator( + def _display_end_indicator( self, win: CursesWindow, text_attr: CursesTextAttributesConstants = (curses.A_BOLD, curses.A_BLINK)): - """Displays an end of dialog indicator in the lower right corner - of textbox. + """Displays an end indicator in the lower right corner of + textbox. :param win: ``curses`` window object on which the method will have effect. :param text_attr: Text attributes of - ``end_dialog_indicator`` method. This defaults to + ``end_indicator`` method. This defaults to ``(curses.A_BOLD, curses.A_BLINK)``. """ - if self.end_dialog_indicator_char: + if self.end_indicator_char: with TextAttributes(win, *text_attr): - win.addch(self.end_dialog_indicator_pos_y, - self.end_dialog_indicator_pos_x, - self.end_dialog_indicator_char) + win.addch(self.end_indicator_pos_y, + self.end_indicator_pos_x, + self.end_indicator_char) def char_by_char( self, @@ -249,7 +249,7 @@ def char_by_char( # Compensates for the space between words. offsetting_x += len(word) + 1 - self._display_end_dialog_indicator(win) + self._display_end_indicator(win) self.getkey(win) def word_by_word( @@ -321,7 +321,6 @@ def word_by_word( - Writing paragraph by paragraph. - Writing each line of the current paragraph, word by word. - - Calling ``_display_end_dialog_indicator`` method. - Waits until a key contained in the class attribute ``confirm_dialog_key`` was pressed before writing the following paragraph. @@ -380,5 +379,5 @@ def word_by_word( callback(*cargs) - self._display_end_dialog_indicator(win) + self._display_end_indicator(win) self.getkey(win) From e59410abee1657662007e060e7f3a84ad6e42639 Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Mon, 29 Mar 2021 07:51:28 +0200 Subject: [PATCH 49/52] Add doc for words_attr parameter. --- visualdialog/dialog.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/visualdialog/dialog.py b/visualdialog/dialog.py index a80b373..beba446 100644 --- a/visualdialog/dialog.py +++ b/visualdialog/dialog.py @@ -151,7 +151,10 @@ def char_by_char( be a single curses text attribute or a tuple of curses text attribute. This defaults an empty tuple. - :param words_attr: This defaults to an empty dictionary. + :param words_attr: Dictionary composed of string as a key and a + single curses text attribute or tuple as a value. Each key + is colored with its associated values This defaults to an + empty dictionary. :param flash_screen: Allows or not to flash screen with a short light effect done before writing the first character by @@ -287,7 +290,10 @@ def word_by_word( be a single curses text attribute or a tuple of curses text attribute. This defaults an empty tuple. - :param words_attr: This defaults to an empty dictionary. + :param words_attr: Dictionary composed of string as a key and a + single curses text attribute or tuple as a value. Each key + is colored with its associated values This defaults to an + empty dictionary. :param cut_char: The delimiter according which to split the text in word. This defaults to ``" "``. From d84703e44ef57740f93fd72601b575f2f8e1e55c Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Mon, 29 Mar 2021 17:08:31 +0200 Subject: [PATCH 50/52] Improve docstring of utils._make_chunk and utils.TextAttributes, fix type hinting. --- visualdialog/utils.py | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/visualdialog/utils.py b/visualdialog/utils.py index 97cffa6..2e6672c 100644 --- a/visualdialog/utils.py +++ b/visualdialog/utils.py @@ -18,8 +18,11 @@ # MA 02110-1301, USA. # # + +from contextlib import ContextDecorator import _curses -from typing import Generator, List, Tuple, TypeVar, Union +from typing import (Generator, Iterable, List, NoReturn, Tuple, TypeVar, + Union) Numeric = TypeVar("Numeric", int, float) @@ -39,39 +42,40 @@ def _make_chunk(iterable: Union[Tuple, List], chunk_length: int) -> Generator: - """Returns a tuple that contains the given iterator separated - into chunk_length bundles. + """Returns a tuple that contains given iterable separated into + ``chunk_length`` bundles. - :returns: Iterator separated into chunk_length bundles. + :returns: Generator separated into ``chunk_length`` bundles. """ return (iterable[chunk:chunk + chunk_length] - for chunk in range(0, len(iterable), chunk_length)) + for chunk in range(0, len(iterable), chunk_length)) -class TextAttributes: - """A context manager to manage curses text attributs. +class TextAttributes(ContextDecorator): + """A context manager to manage ``curses`` text attributes. - :param win: `curses` window object for which the attributes will be - managed. + :param win: ``curses`` window object for which the attributes will + be managed. - :param attributes: List of attributes to activate and desactivate. + :param attributes: Iterable of ``curses`` text attributes to activate + and desactivate. """ def __init__(self, win: CursesWindow, - *attributes: CursesTextAttributesConstants): + *attributes: Iterable[CursesTextAttributesConstant]): self.win = win self.attributes = attributes - def __enter__(self): - """Activates one by one the attributes contained in - self.attributes. + def __enter__(self) -> NoReturn: + """Activate one by one attributes contained in self.attributes + on ``self.win``. """ for attr in self.attributes: self.win.attron(attr) - def __exit__(self, type, value, traceback): - """Disable one by one the attributes contained in - self.attributes. + def __exit__(self, type, value, traceback) -> NoReturn: + """Disable one by one attributes contained in + ``self.attributes`` on ``self.win``. """ for attr in self.attributes: self.win.attroff(attr) From e074bc02ad7b24225c63311101272275cfbfebc0 Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Mon, 29 Mar 2021 17:27:34 +0200 Subject: [PATCH 51/52] Replace useless Numeric type hint by Number from numbers module. --- visualdialog/box.py | 4 ++-- visualdialog/dialog.py | 12 ++++++------ visualdialog/utils.py | 2 -- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/visualdialog/box.py b/visualdialog/box.py index 7ec6d81..b50626b 100644 --- a/visualdialog/box.py +++ b/visualdialog/box.py @@ -23,6 +23,7 @@ import curses import curses.textpad +from numbers import Number from typing import List, Tuple, Union from .utils import (CursesKeyConstant, @@ -30,7 +31,6 @@ CursesTextAttributesConstant, CursesTextAttributesConstants, CursesWindow, - Numeric, TextAttributes) @@ -105,7 +105,7 @@ def __init__( CursesTextAttributesConstants] = curses.A_BOLD, downtime_chars: Union[Tuple[str], List[str]] = (",", ".", ":", ";", "!", "?"), - downtime_chars_delay: Numeric = .6): + downtime_chars_delay: Number = .6): self.pos_x, self.pos_y = pos_x, pos_y self.length, self.width = length, width diff --git a/visualdialog/dialog.py b/visualdialog/dialog.py index 4b89418..8108e6b 100644 --- a/visualdialog/dialog.py +++ b/visualdialog/dialog.py @@ -22,6 +22,7 @@ __all__ = ["DialogBox"] import curses +from numbers import Number import random import textwrap import time @@ -31,7 +32,6 @@ from .utils import (CursesTextAttributesConstant, CursesTextAttributesConstants, CursesWindow, - Numeric, TextAttributes, _make_chunk) @@ -71,7 +71,7 @@ def __init__( CursesTextAttributesConstants] = curses.A_BOLD, downtime_chars: Union[Tuple[str], List[str]] = (",", ".", ":", ";", "!", "?"), - downtime_chars_delay: Numeric = .6, + downtime_chars_delay: Number = .6, end_indicator: str = "►"): BaseTextBox.__init__(self, pos_x, pos_y, @@ -127,8 +127,8 @@ def char_by_char( words_attr: Union[Dict[Tuple[str], CursesTextAttributesConstant], Dict[Tuple[str], CursesTextAttributesConstants]] = {}, flash_screen: bool = False, - delay: Numeric = .04, - random_delay: Union[Tuple[Numeric], List[Numeric]] = (0, 0), + delay: Number = .04, + random_delay: Union[Tuple[Number], List[Number]] = (0, 0), callback: Callable = lambda: None, cargs: Union[Tuple, List] = ()): """Writes the given text character by character in the current @@ -266,8 +266,8 @@ def word_by_word( words_attr: Union[Dict[Tuple[str], CursesTextAttributesConstant], Dict[Tuple[str], CursesTextAttributesConstants]] = {}, flash_screen: bool = False, - delay: Numeric = .15, - random_delay: Union[Tuple[Numeric], List[Numeric]] = (0, 0), + delay: Number = .15, + random_delay: Union[Tuple[Number], List[Number]] = (0, 0), callback: Callable = lambda: None, cargs: Union[Tuple, List] = ()): """Writes the given text word by word at position in the current diff --git a/visualdialog/utils.py b/visualdialog/utils.py index 2e6672c..9396e4c 100644 --- a/visualdialog/utils.py +++ b/visualdialog/utils.py @@ -25,8 +25,6 @@ Union) -Numeric = TypeVar("Numeric", int, float) - CursesWindow = _curses.window #: curses text attribute constants are integers. From b967aafc31a8e9779b5ab739c7b8e5dcd4d11d24 Mon Sep 17 00:00:00 2001 From: Tim-ats-d Date: Mon, 29 Mar 2021 20:24:06 +0200 Subject: [PATCH 52/52] Rename length variable to height. --- README.md | 6 ++++-- examples/confrontation.py | 12 ++++++------ visualdialog/box.py | 14 +++++++------- visualdialog/dialog.py | 6 +++--- 4 files changed, 20 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index bb5f1b0..bc3e7cc 100644 --- a/README.md +++ b/README.md @@ -73,13 +73,13 @@ from visualdialog import DialogBox x, y = (0, 0) -length, width = (35, 6) +height, width = (35, 6) def main(stdscr): curses.curs_set(False) textbox = DialogBox(x, y, - length, width, + height, width, title="Demo") textbox.char_by_char(stdscr, "Hello world") @@ -116,6 +116,8 @@ You can also generate the documentation in **Latex**, **Texinfo** or **man-pages We would love for you to contribute to improve **Visual-dialog**. +For major changes, please open an issue first to discuss what you would like to change. + Take a look at our [contributing guide](CONTRIBUTING.md) to get started. You can also help by reporting **bugs**. diff --git a/examples/confrontation.py b/examples/confrontation.py index 62472b9..1a05cfa 100644 --- a/examples/confrontation.py +++ b/examples/confrontation.py @@ -20,27 +20,27 @@ def main(stdscr): curses.init_pair(2, curses.COLOR_MAGENTA, curses.COLOR_BLACK) curses.init_pair(3, curses.COLOR_RED, curses.COLOR_BLACK) - width, length = 6, 35 # Width and length (in character). + width, height = 6, 35 # Width and height (in character). max_y, max_x = stdscr.getmaxyx() left_x = 2 # Left alignment. - right_x = max_x - length - 4 # Calculation of right alignment. - center_x = max_x // 2 - length // 2 # Calculation of center alignment. + right_x = max_x - height - 4 # Calculation of right alignment. + center_x = max_x // 2 - height // 2 # Calculation of center alignment. bottom_y = max_y - width - 4 # Calculation of bottom alignment. phoenix_wright = DialogBox(left_x, bottom_y, - length, width, + height, width, title="Phoenix", title_colors_pair_nb=1) # Title and color_pair used to colored title. april_may = DialogBox(center_x, bottom_y, - length, width, + height, width, title="April", title_colors_pair_nb=2) miles_edgeworth = DialogBox(right_x, bottom_y, - length, width, + height, width, title="Edgeworth", title_colors_pair_nb=3) diff --git a/visualdialog/box.py b/visualdialog/box.py index b50626b..4d5df8d 100644 --- a/visualdialog/box.py +++ b/visualdialog/box.py @@ -61,7 +61,7 @@ class BaseTextBox: :param pos_y: y position of the dialog box in ``curses`` window object on which methods will have effects. - :param length: Length of the dialog box in ``curses`` window object + :param height: Height of the dialog box in ``curses`` window object on which methods will have effects. :param width: Width of the dialog box in ``curses`` window object on @@ -97,7 +97,7 @@ def __init__( self, pos_x: int, pos_y: int, - length: int, + height: int, width: int, title: str = "", title_colors_pair_nb: int = 0, @@ -107,7 +107,7 @@ def __init__( List[str]] = (",", ".", ":", ";", "!", "?"), downtime_chars_delay: Number = .6): self.pos_x, self.pos_y = pos_x, pos_y - self.length, self.width = length, width + self.height, self.width = height, width self.title_offsetting_y = 2 if title else 0 @@ -116,7 +116,7 @@ def __init__( # Compensation for the upper border of the dialog box. self.text_pos_y = pos_y + self.title_offsetting_y + 1 - self.nb_char_max_line = length - 4 + self.nb_char_max_line = height - 4 self.nb_lines_max = width - 2 self.title = title @@ -149,9 +149,9 @@ def position(self) -> Tuple[int]: def dimensions(self) -> Tuple[int]: """Returns a tuple contains dimensions of ``TextBox``. - :returns: Length and width of ``TextBox``. + :returns: Height and width of ``TextBox``. """ - return self.length, self.width + return self.height, self.width def framing_box(self, win: CursesWindow): """Displays dialog box borders and his title. @@ -184,7 +184,7 @@ def framing_box(self, win: CursesWindow): self.pos_y + self.title_offsetting_y, self.pos_x, self.pos_y + self.title_offsetting_y + self.width, - self.pos_x + self.length) + self.pos_x + self.height) def getkey(self, win: CursesWindow): """Blocks execution as long as a key contained in diff --git a/visualdialog/dialog.py b/visualdialog/dialog.py index 8108e6b..211dada 100644 --- a/visualdialog/dialog.py +++ b/visualdialog/dialog.py @@ -63,7 +63,7 @@ def __init__( self, pos_x: int, pos_y: int, - length: int, + height: int, width: int, title: str = "", title_colors_pair_nb: int = 0, @@ -75,13 +75,13 @@ def __init__( end_indicator: str = "►"): BaseTextBox.__init__(self, pos_x, pos_y, - length, width, + height, width, title, title_colors_pair_nb, title_text_attr, downtime_chars, downtime_chars_delay) self.end_indicator_char = end_indicator - self.end_indicator_pos_x = self.pos_x + self.length - 2 + self.end_indicator_pos_x = self.pos_x + self.height - 2 if self.title: self.end_indicator_pos_y = self.pos_y + self.width + 1

FeaturesInstallation • @@ -13,9 +7,11 @@ License