diff --git a/.gitignore b/.gitignore
index aafda3a..e1e182a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +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
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index bf63df9..89b9309 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -3,59 +3,89 @@
## 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.
+You need [all requirements](README#requirements).
## 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.
+Please push on the `dev` branch, any pull request on the `main` branch will be refused.
-## Download
+## Conventions
-Download projet:
-```bash
-git clone https://github.com/Tim-ats-d/Visual-dialog
-```
+When you make a pull request make sure to:
-Install Visual-dialog using `pip`:
-```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
+* 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.
+
+If you add a feature that changes the API, notify it explicitly.
+
+## Download
+
+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
+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/).
## 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
.
├── .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.
+│ │
+│ ├── 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.
│ │
-│ ├── examples/
-│ │ Contains several examples of how to use Visual-dialog.
+│ ├── make.bat
+│ │ To generate documentation on Windows.
│ │
-│ └── documentation.md
-│ Documentation of public API (coming soon).
+│ └── Makefile
+│ To generate documentation on GNU/Linux or MacOS.
+│
+├── examples/
+│ Contains several examples of use cases of Visual-dialog.
+│
+├── tests/
+│ Contains tests for debugging libraries.
│
├── 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
│
@@ -68,5 +98,5 @@ The following snippet describes Visual-dialog's repository structure.
├── README.md
│
└── setup.py
- Installation of the library.
+ Installation of the library.
```
diff --git a/README.md b/README.md
index 31def27..bc3e7cc 100644
--- a/README.md
+++ b/README.md
@@ -1,21 +1,22 @@
-
-
-
- Library to make easier dialog box in terminal.
-
Features •
Installation •
+ Requirements •
Documentation •
Quick start •
Contributing •
License
-
-

-
+
+
+
+ Library to make easier dialog box in terminal.
+
+ This library is still under development.
+ API can change.
+
## Features
@@ -23,40 +24,107 @@
🔖 Text coloring and formatting.
-⚙️ Hackable and configurable .
+⚙️ Hackable and configurable.
## Installation
### Using pip
-```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
```
-(The lib is not yet available on **pypy**).
+or update lib to the latest version:
+
+```sh
+python3 -m pip install git+git://github.com/Tim-ats-d/Visual-dialog --upgrade
+```
-### Requirements
-* **Python 3.6** or more.
-* [`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.
+### From source
+
+```sh
+git clone https://github.com/Tim-ats-d/Visual-dialog.git
+cd Visual-dialog
+pip install .
+```
+
+## 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.
+* [**sphinx-rtd-theme**](https://pypi.org/project/sphinx-rtd-theme/) used as documentation theme.
## Quick-start
-Read these [examples](doc/examples/).
+### Hello world with **Visual-dialog**
+
+```python3
+import curses
+
+from visualdialog import DialogBox
+
+
+x, y = (0, 0)
+height, width = (35, 6)
+
+def main(stdscr):
+ curses.curs_set(False)
+
+ textbox = DialogBox(x, y,
+ height, width,
+ 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
-Coming soon !
+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/doc
+make html
+```
+Or on **Windows** with **Git Bash**:
+```sh
+git clone https://github.com/Tim-ats-d/Visual-dialog.git
+cd Visual-dialog/doc
+./make.bat html
+```
+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**.
-Take a look at our [Contributing guide](CONTRIBUTING.md) to get started.
+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**.
## 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/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/examples/confrontation.py b/doc/examples/confrontation.py
deleted file mode 100644
index 738a78b..0000000
--- a/doc/examples/confrontation.py
+++ /dev/null
@@ -1,93 +0,0 @@
-#!/usr/bin/env python3
-# confrontation_example.py
-
-import curses
-
-from visualdialog import DialogBox
-
-
-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_BLUE, curses.COLOR_BLACK)
- 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).
-
- phoenix_wright = DialogBox(
- *textbox_position,
- *textbox_dimension,
- title="Phoenix", title_colors_pair_nb=1 # Title and color_pair used to colored title.
- )
-
- april_may = DialogBox(
- *textbox_position,
- *textbox_dimension,
- title="April", title_colors_pair_nb=2 # Title and color_pair used to colored title.
- )
-
- miles_edgeworth = DialogBox(
- *textbox_position,
- *textbox_dimension,
- title="Edgeworth", 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 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.
-
- phoenix_wright.char_by_char(stdscr,
- "This testimony is a pure invention !",
- 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.
-
- phoenix_wright.char_by_char(stdscr,
- "You're lying April May !",
- colors_pair_nb=0,
- flash_screen=True,
- 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,
- "Arghh !",
- colors_pair_nb=0,
- 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,
- "OBJECTION !",
- colors_pair_nb=0,
- flash_screen=True,
- delay=0.03,
- 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,
- "These accusations are irrelevant !",
- colors_pair_nb=0,
- delay=0.03)
-
- miles_edgeworth.getkey(stdscr)
- stdscr.clear()
-
-
-curses.wrapper(main) # Execution of the function.
diff --git a/doc/examples/context.py b/doc/examples/context.py
deleted file mode 100644
index 9978699..0000000
--- a/doc/examples/context.py
+++ /dev/null
@@ -1,53 +0,0 @@
-#!/usr/bin/env python3
-# monologue.py
-
-import curses
-
-from visualdialog import DialogBox
-
-
-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.
-
- # Wait until a key contained in textbox.confirm_dialog_key is pressed.
- textbox.getkey(stdscr)
- stdscr.clear() # Clear the screen.
-
-
-curses.wrapper(main) # Execution of the function.
diff --git a/doc/examples/monologue.py b/doc/examples/monologue.py
deleted file mode 100644
index dc17460..0000000
--- a/doc/examples/monologue.py
+++ /dev/null
@@ -1,47 +0,0 @@
-#!/usr/bin/env python3
-# monologue.py
-
-import curses
-
-from visualdialog import DialogBox
-
-
-def main(stdscr):
- replys = (
- "Hello world, how are you today ?",
- "Press a key to skip this dialog.",
- "That is a basic example.",
- "See doc for more informations."
- )
-
- # Makes the cursor invisible.
- curses.curs_set(0)
-
- # Definition of several colors pairs.
- 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, # 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.
-
- # 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.
-
- # 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.
-
- textbox.getkey(stdscr) # Waiting for a key press.
- stdscr.clear() # Clear the screen.
-
-
-# Execution of the function.
-curses.wrapper(main)
diff --git a/doc/examples/text_attributes.py b/doc/examples/text_attributes.py
deleted file mode 100644
index 9978699..0000000
--- a/doc/examples/text_attributes.py
+++ /dev/null
@@ -1,53 +0,0 @@
-#!/usr/bin/env python3
-# monologue.py
-
-import curses
-
-from visualdialog import DialogBox
-
-
-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.
-
- # Wait until a key contained in textbox.confirm_dialog_key is pressed.
- textbox.getkey(stdscr)
- stdscr.clear() # Clear the screen.
-
-
-curses.wrapper(main) # Execution of the function.
diff --git a/doc/make.bat b/doc/make.bat
new file mode 100755
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/source/_static/visual-dialog.png b/doc/source/_static/visual-dialog.png
new file mode 100644
index 0000000..6b13647
Binary files /dev/null and b/doc/source/_static/visual-dialog.png differ
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/conf.py b/doc/source/conf.py
new file mode 100644
index 0000000..ae0ab39
--- /dev/null
+++ b/doc/source/conf.py
@@ -0,0 +1,80 @@
+#
+# 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 os
+import sys
+from datetime import datetime
+
+from visualdialog import __author__, __version__
+
+
+sys.path.insert(0, os.path.abspath("../../"))
+
+project = "Visual-dialog"
+copyright = f"2021-{datetime.now().year}, {__author__}"
+author = __author__
+
+extensions = [
+ "sphinx.ext.autodoc"
+]
+
+master_doc = "index"
+source_suffix = ".rst"
+autodoc_member_order = "bysource"
+
+version = str(__version__)
+release = version
+
+templates_path = ["_templates"]
+
+exclude_patterns = []
+
+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 = "_static/visual-dialog.png"
+
+latex_logo = "_static/visual-dialog.png"
+
+latex_elements = {
+ "pointsize": "12pt"
+}
+
+latex_documents = [
+ (master_doc, "Visual-dialog.tex", "Visual-dialog Documentation",
+ "Arnouts Timéo", "manual"),
+]
+
+man_pages = [
+ (master_doc, "visual-dialog", "Visual-dialog Documentation",
+ [author], 1)
+]
+
+texinfo_documents = [
+ (master_doc, "Visual-dialog", "Visual-dialog Documentation",
+ author, "Visual-dialog", "A library to make easier dialog box in terminal.",
+ "Miscellaneous"),
+]
diff --git a/doc/source/faq.rst b/doc/source/faq.rst
new file mode 100644
index 0000000..27ba176
--- /dev/null
+++ b/doc/source/faq.rst
@@ -0,0 +1,24 @@
+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.
+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/index.rst b/doc/source/index.rst
new file mode 100644
index 0000000..ab430fd
--- /dev/null
+++ b/doc/source/index.rst
@@ -0,0 +1,57 @@
+.. 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:: _static/visual-dialog.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
+ api.rst
+ faq.rst
+
+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/source/installation.rst b/doc/source/installation.rst
new file mode 100644
index 0000000..84818fb
--- /dev/null
+++ b/doc/source/installation.rst
@@ -0,0 +1,13 @@
+Installation
+============
+
+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
+
+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/source/requirements.rst b/doc/source/requirements.rst
new file mode 100644
index 0000000..ef84bc4
--- /dev/null
+++ b/doc/source/requirements.rst
@@ -0,0 +1,19 @@
+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.7** or more.
+- `Sphinx `_ to generate the documentation of library.
+- `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..2eacc2e
--- /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)
+
+
+.. automodule:: visualdialog.utils
+ :members:
diff --git a/doc/source/visualdialog.rst b/doc/source/visualdialog.rst
new file mode 100644
index 0000000..f35da96
--- /dev/null
+++ b/doc/source/visualdialog.rst
@@ -0,0 +1,22 @@
+Text boxes
+==========
+
+.. 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.
+
+TextBox
+-------
+
+.. autoclass:: visualdialog.box.BaseTextBox
+ :members:
+ :undoc-members:
+
+DialogBox
+---------
+
+.. autoclass:: visualdialog.dialog.DialogBox
+ :undoc-members:
+ :members:
diff --git a/examples/README.md b/examples/README.md
new file mode 100644
index 0000000..d3f3b1e
--- /dev/null
+++ b/examples/README.md
@@ -0,0 +1,24 @@
+# Examples
+
+This directory contains several examples of use of **Visual-dialog**.
+They are classified below by order of difficulty (approximately).
+
+## [Monologue](monologue.py)
+
+A complete concrete example of how to use **Visual-dialog**.
+
+## [Word](word.py)
+
+An example of using ``word_by_word`` method from text boxes.
+
+## [Text attributes](text_attributes.py)
+
+An example showing the possibilities of text formatting in a text box.
+
+## [Context](contex.py)
+
+An example of how to use a text box as a **context manager**.
+
+## [Confrontation](confrontation.py)
+
+A concrete example exploiting the possibilities of library.
diff --git a/examples/confrontation.py b/examples/confrontation.py
new file mode 100644
index 0000000..1a05cfa
--- /dev/null
+++ b/examples/confrontation.py
@@ -0,0 +1,79 @@
+# confrontation.py
+#
+# A concrete example exploiting the possibilities of Visual-dialog.
+
+import curses
+
+from visualdialog import DialogBox
+
+
+# Definition of curses key constants.
+# 10 and 32 correspond to enter and space keys.
+PASS_DIALOG_KEY = (10, 32)
+
+def main(stdscr):
+ # Makes the cursor invisible.
+ curses.curs_set(False)
+
+ # Definition of several colors pairs.
+ 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)
+
+ width, height = 6, 35 # Width and height (in character).
+
+ max_y, max_x = stdscr.getmaxyx()
+
+ left_x = 2 # Left 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,
+ height, width,
+ title="Phoenix",
+ title_colors_pair_nb=1) # Title and color_pair used to colored title.
+
+ april_may = DialogBox(center_x, bottom_y,
+ height, width,
+ title="April",
+ title_colors_pair_nb=2)
+
+ miles_edgeworth = DialogBox(right_x, bottom_y,
+ height, width,
+ title="Edgeworth",
+ title_colors_pair_nb=3)
+
+ # Definition of accepted key codes to pass a dialog.
+ 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 !",
+ delay=0.03) # Set delay between writting each characters to 0.03 seconde.
+
+ phoenix_wright.char_by_char(stdscr,
+ "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 !",
+ delay=0.02,
+ text_attr=curses.A_ITALIC)
+
+ miles_edgeworth.char_by_char(stdscr,
+ "OBJECTION !",
+ flash_screen=True,
+ delay=0.03,
+ text_attr=curses.A_BOLD)
+
+ miles_edgeworth.char_by_char(stdscr,
+ "These accusations are irrelevant !",
+ delay=0.03)
+
+
+# Execution of main function.
+curses.wrapper(main)
diff --git a/examples/context.py b/examples/context.py
new file mode 100644
index 0000000..c4c14e1
--- /dev/null
+++ b/examples/context.py
@@ -0,0 +1,34 @@
+# context.py
+#
+# An example of how to use a text box as a context manager.
+
+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):
+ # Makes the cursor invisible.
+ curses.curs_set(False)
+
+ 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 = (ENTER_KEY, SPACE_KEY)
+ db.char_by_char(stdscr, reply)
+
+
+# Execution of main function.
+curses.wrapper(main)
diff --git a/examples/monologue.py b/examples/monologue.py
new file mode 100644
index 0000000..44cceae
--- /dev/null
+++ b/examples/monologue.py
@@ -0,0 +1,47 @@
+# monologue.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):
+ replys = (
+ "Hello world",
+ "Press a key to skip this dialog.",
+ "That is a basic example.",
+ "See doc for more informations."
+ )
+
+ # Makes the cursor invisible.
+ curses.curs_set(False)
+
+ # Definition of several colors pairs.
+ 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.
+
+ # 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 reply in replys:
+ textbox.char_by_char(stdscr,
+ reply,
+ 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.
+curses.wrapper(main)
diff --git a/examples/text_attributes.py b/examples/text_attributes.py
new file mode 100644
index 0000000..2e25c83
--- /dev/null
+++ b/examples/text_attributes.py
@@ -0,0 +1,51 @@
+# text_attributes.py
+#
+# An example showing the possibilities of text formatting.
+
+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):
+ # Makes the cursor invisible.
+ curses.curs_set(False)
+
+ # Definition of several colors pairs.
+ 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="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)
+
+ # 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,
+ "A very important text.": (curses.A_BOLD, curses.A_ITALIC),
+ "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, attributes in sentences.items():
+ demo_textbox.char_by_char(stdscr,
+ text,
+ 2, # Display text colored with color pair 2.
+ text_attr=attributes) # Pass attributes to text.
+
+
+# Execution of main function.
+curses.wrapper(main)
diff --git a/examples/word.py b/examples/word.py
new file mode 100644
index 0000000..dd907c2
--- /dev/null
+++ b/examples/word.py
@@ -0,0 +1,40 @@
+# words.py
+#
+# An example of using the word_by_word method from text boxes.
+
+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/setup.py b/setup.py
index 06e57a4..f95b201 100644
--- a/setup.py
+++ b/setup.py
@@ -1,35 +1,30 @@
-#!/usr/bin/env python3
+from setuptools import find_packages, setup
+
+from visualdialog import __author__, __version__
-from setuptools import setup, find_packages
setup(
name="visualdialog",
- version=0.6,
+ version=__version__,
packages=find_packages(),
- author="Arnouts Timéo",
+ author=__author__,
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",
"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",
"Programming Language :: Python :: Implementation",
"Topic :: Games/Entertainment :: Role-Playing",
"Topic :: Software Development :: Libraries :: Python Modules"
- ])
+ ],
+ keywords="curses, ncurses, ui, dialogbox")
diff --git a/tests/test.py b/tests/test.py
new file mode 100644
index 0000000..c0cbbcd
--- /dev/null
+++ b/tests/test.py
@@ -0,0 +1,55 @@
+# test.py
+#
+# This file contains the tests used to debug the library.
+
+import curses
+
+from visualdialog import DialogBox
+import visualdialog
+
+
+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.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")
+
+ textbox.confirm_dialog_key = (32, )
+ textbox.panic_key = (10, )
+
+ 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, 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)
+
+ 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 1ee755f..2a3130e 100644
--- a/visualdialog/__init__.py
+++ b/visualdialog/__init__.py
@@ -1,9 +1,9 @@
-#!/usr/bin/env python3
-
"""
-A librairie which provides class and functions to make easier dialog box in terminal.
+A library to make easier dialog box in terminal.
"""
-__version__ = 0.6
+__version__ = 0.7
+__author__ = "Timéo Arnouts"
-from .core import DialogBox
+from .dialog import DialogBox
+from .utils import TextAttributes
diff --git a/visualdialog/box.py b/visualdialog/box.py
new file mode 100644
index 0000000..4d5df8d
--- /dev/null
+++ b/visualdialog/box.py
@@ -0,0 +1,217 @@
+# 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.
+#
+#
+
+__all__ = ["BaseTextBox"]
+
+import curses
+import curses.textpad
+from numbers import Number
+from typing import List, Tuple, Union
+
+from .utils import (CursesKeyConstant,
+ CursesKeyConstants,
+ CursesTextAttributesConstant,
+ CursesTextAttributesConstants,
+ CursesWindow,
+ TextAttributes)
+
+
+class PanicError(Exception):
+ """Exception thrown when a key contained in ``TextBox.panic_key`` is
+ pressed.
+
+ :param key: Key pressed that caused the exception to be thrown.
+ """
+ def __init__(self,
+ key: CursesKeyConstant):
+ self.key = key
+
+ def __str__(self):
+ return f"text box was aborted by pressing the {self.key} key"
+
+
+class BaseTextBox:
+ """This class provides attributs and methods to manage a text box.
+
+ .. NOTE::
+ 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.
+
+ :param pos_y: y position of the dialog box in ``curses`` window
+ object on which methods will have effects.
+
+ :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
+ which methods will have effects.
+
+ :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.
+
+ :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``.
+
+ :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``.
+
+ :param downtime_chars:
+ List of characters that will trigger a ``downtime_chars_delay``
+ time second between the writing of each character.
+ This defaults to ``(",", ".", ":", ";", "!", "?")``.
+
+ :param downtime_chars_delay:
+ Waiting time in seconds after writing a character contained in
+ ``downtime_chars``.
+ This defaults to ``0.6``.
+ """
+
+ def __init__(
+ self,
+ pos_x: int,
+ pos_y: int,
+ height: 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: Number = .6):
+ self.pos_x, self.pos_y = pos_x, pos_y
+ self.height, self.width = height, 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 + self.title_offsetting_y + 1
+
+ self.nb_char_max_line = height - 4
+ self.nb_lines_max = width - 2
+
+ self.title = title
+ if title:
+ self.title_colors = curses.color_pair(title_colors_pair_nb)
+
+ # 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.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: List[CursesKeyConstant] = []
+ #: List of accepted key codes to raise PanicError. ``curses`` constants are supported. This defaults to an empty tuple.
+ 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``.
+ """
+ return self.text_pos_x - 2, self.text_pos_y - 3
+
+ @property
+ def dimensions(self) -> Tuple[int]:
+ """Returns a tuple contains dimensions of ``TextBox``.
+
+ :returns: Height and width of ``TextBox``.
+ """
+ return self.height, self.width
+
+ 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 win: ``curses`` window object on which the method will
+ have effect.
+ """
+ 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_attr)
+
+ curses.textpad.rectangle(win,
+ self.pos_y,
+ self.pos_x + 1,
+ self.pos_y + title_width,
+ self.pos_x + title_length)
+
+ 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(win,
+ self.pos_y + self.title_offsetting_y,
+ self.pos_x,
+ self.pos_y + self.title_offsetting_y + self.width,
+ self.pos_x + self.height)
+
+ def getkey(self, win: CursesWindow):
+ """Blocks execution as long as a key contained in
+ ``self.confirm_dialog_key`` is not detected.
+
+
+ :param win: ``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 = win.getch()
+
+ if key in self.confirm_dialog_key:
+ break
+ elif key in self.panic_key:
+ raise PanicError(key)
+ else:
+ # Ignore incorrect keys.
+ ...
diff --git a/visualdialog/choices.py b/visualdialog/choices.py
new file mode 100644
index 0000000..cb000cc
--- /dev/null
+++ b/visualdialog/choices.py
@@ -0,0 +1,62 @@
+# choices.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, Tuple, Union
+
+from .dialog import DialogBox
+from .utils import (CursesTextAttributesConstants,
+ TextAttributes,
+ _make_chunk)
+
+
+class ChoiceBox(DialogBox):
+
+ def __init__(self,
+ **kwargs):
+ super().__init__(**kwargs)
+
+ 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 100644
index 0000000..211dada
--- /dev/null
+++ b/visualdialog/dialog.py
@@ -0,0 +1,389 @@
+# 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.
+#
+#
+
+__all__ = ["DialogBox"]
+
+import curses
+from numbers import Number
+import random
+import textwrap
+import time
+from typing import Callable, Dict, List, Optional, Tuple, Union
+
+from .box import BaseTextBox
+from .utils import (CursesTextAttributesConstant,
+ CursesTextAttributesConstants,
+ CursesWindow,
+ TextAttributes,
+ _make_chunk)
+
+
+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
+ 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 ``"►"``.
+
+ :key kwargs: Keyword arguments correspond to the instance attributes
+ of ``TextBox``.
+
+ .. NOTE::
+ 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,
+ pos_x: int,
+ pos_y: int,
+ height: 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: Number = .6,
+ end_indicator: str = "►"):
+ BaseTextBox.__init__(self,
+ pos_x, pos_y,
+ 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.height - 2
+
+ if self.title:
+ self.end_indicator_pos_y = self.pos_y + self.width + 1
+ else:
+ self.end_indicator_pos_y = self.pos_y + self.width - 1
+
+ self.text_wrapper = textwrap.TextWrapper(width=self.nb_char_max_line)
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, type, value, traceback):
+ ...
+
+ def _display_end_indicator(
+ self,
+ win: CursesWindow,
+ text_attr: CursesTextAttributesConstants = (curses.A_BOLD,
+ curses.A_BLINK)):
+ """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_indicator`` method. This defaults to
+ ``(curses.A_BOLD, curses.A_BLINK)``.
+ """
+ if self.end_indicator_char:
+ with TextAttributes(win, *text_attr):
+ win.addch(self.end_indicator_pos_y,
+ self.end_indicator_pos_x,
+ self.end_indicator_char)
+
+ def char_by_char(
+ self,
+ win: CursesWindow,
+ text: str,
+ colors_pair_nb: int = 0,
+ text_attr: Union[CursesTextAttributesConstant,
+ CursesTextAttributesConstants] = (),
+ words_attr: Union[Dict[Tuple[str], CursesTextAttributesConstant],
+ Dict[Tuple[str], CursesTextAttributesConstants]] = {},
+ flash_screen: bool = False,
+ 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
+ dialog box.
+
+ :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.
+
+ :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 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.
+
+ :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
+ ``flash`` function from ``curses`` module. This defaults to
+ ``False``.
+
+ :param delay: Waiting time between the writing of each character
+ of text in second. This defaults to ``0.04``.
+
+ :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)``.
+
+ :param callback: Callable called after writing a character and
+ the delay time has elapsed. This defaults to a lambda which
+ do nothing.
+
+ :param cargs: All the arguments that will be passed to callback.
+ This defaults to an empty tuple.
+
+ .. 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 ``win``.
+
+ .. 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::
+ ``win`` will be completely cleaned when writing each
+ paragraph by ``window.clear()`` method of ``curses``
+ module.
+ """
+ self.framing_box(win)
+
+ if flash_screen:
+ curses.flash()
+
+ wrapped_text = self.text_wrapper.wrap(text)
+ wrapped_text = _make_chunk(wrapped_text, self.nb_lines_max)
+
+ for paragraph in wrapped_text:
+ win.clear()
+ self.framing_box(win)
+
+ 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 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(win, *attr):
+ for x, char in enumerate(word):
+ 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 +
+ random.uniform(*random_delay))
+ else:
+ time.sleep(delay +
+ random.uniform(*random_delay))
+
+ callback(*cargs)
+
+ # Waiting for space character.
+ time.sleep(delay)
+
+ # Compensates for the space between words.
+ offsetting_x += len(word) + 1
+
+ self._display_end_indicator(win)
+ self.getkey(win)
+
+ def word_by_word(
+ self,
+ win: CursesWindow,
+ text: str,
+ colors_pair_nb: int = 0,
+ cut_char: str = " ",
+ text_attr: Union[CursesTextAttributesConstant,
+ CursesTextAttributesConstants] = (),
+ words_attr: Union[Dict[Tuple[str], CursesTextAttributesConstant],
+ Dict[Tuple[str], CursesTextAttributesConstants]] = {},
+ flash_screen: bool = False,
+ 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
+ dialog box.
+
+ :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.
+
+ :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 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.
+
+ :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 ``" "``.
+
+ :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``.
+
+ :param delay: Waiting time between the writing of each word of
+ ``text`` in second. This defaults to ``0.15``.
+
+ :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)``.
+
+ :param callback: Callable called after writing a word and the
+ delay time has elapsed. This defaults to a lambda which do
+ nothing.
+
+ :param cargs: All the arguments that will be passed to callback.
+ This defaults to an empty tuple.
+
+ .. 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, word by
+ word.
+ - Waits until a key contained in the class attribute
+ ``confirm_dialog_key`` was pressed before writing the
+ following paragraph.
+ - Complete cleaning ``win``.
+
+ .. 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::
+ ``win`` will be completely cleaned when writing each
+ paragraph by ``window.clear()`` method of ``curses``
+ module.
+ """
+ self.framing_box(win)
+
+ if flash_screen:
+ curses.flash()
+
+ attr = (curses.color_pair(colors_pair_nb), *text_attr)
+
+ wrapped_text = self.text_wrapper.wrap(text)
+ wrapped_text = _make_chunk(wrapped_text, self.nb_lines_max)
+
+ for paragraph in wrapped_text:
+ win.clear()
+ self.framing_box(win)
+ for y, line in enumerate(paragraph):
+ offsetting_x = 0
+ for word in line.split(cut_char):
+ 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(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
+
+ time.sleep(delay + random.uniform(*random_delay))
+
+ callback(*cargs)
+
+ self._display_end_indicator(win)
+ self.getkey(win)
diff --git a/visualdialog/utils.py b/visualdialog/utils.py
new file mode 100644
index 0000000..9396e4c
--- /dev/null
+++ b/visualdialog/utils.py
@@ -0,0 +1,79 @@
+# 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 contextlib import ContextDecorator
+import _curses
+from typing import (Generator, Iterable, List, NoReturn, Tuple, TypeVar,
+ Union)
+
+
+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
+CursesKeyConstant = int
+CursesKeyConstants = Union[Tuple[int], List[int]]
+
+
+def _make_chunk(iterable: Union[Tuple, List],
+ chunk_length: int) -> Generator:
+ """Returns a tuple that contains given iterable 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))
+
+
+class TextAttributes(ContextDecorator):
+ """A context manager to manage ``curses`` text attributes.
+
+ :param win: ``curses`` window object for which the attributes will
+ be managed.
+
+ :param attributes: Iterable of ``curses`` text attributes to activate
+ and desactivate.
+ """
+ def __init__(self,
+ win: CursesWindow,
+ *attributes: Iterable[CursesTextAttributesConstant]):
+ self.win = win
+ self.attributes = 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) -> NoReturn:
+ """Disable one by one attributes contained in
+ ``self.attributes`` on ``self.win``.
+ """
+ for attr in self.attributes:
+ self.win.attroff(attr)