├── .gitignore ├── AUTHORS.txt ├── CHANGES.txt ├── LICENSE.txt ├── MANIFEST.in ├── README.md ├── code_of_conduct.md ├── docs ├── Makefile ├── alert_example.png ├── basics.rst ├── conf.py ├── confirm_example.png ├── index.rst ├── make.bat ├── native.rst ├── password_example.png ├── prompt_example.png ├── quickstart.rst ├── win32native_alert_example.png └── win32native_confirm_example.png ├── pyproject.toml ├── setup.py ├── src └── pymsgbox │ ├── __init__.py │ └── _native_win.py ├── tests └── test_pymsgbox.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | venv/ 2 | .tox/ 3 | 4 | *.pyc 5 | __pycache__/ 6 | 7 | instance/ 8 | 9 | .pytest_cache/ 10 | .coverage 11 | htmlcov/ 12 | 13 | dist/ 14 | build/ 15 | *.egg-info/ -------------------------------------------------------------------------------- /AUTHORS.txt: -------------------------------------------------------------------------------- 1 | Here is an inevitably incomplete list of MUCH-APPRECIATED CONTRIBUTORS -- 2 | people who have submitted patches, reported bugs, added translations, helped 3 | answer newbie questions, and generally made PyMsgBox that much better: 4 | 5 | Al Sweigart https://github.com/asweigart 6 | Jose Dzireh Chong https://github.com/josedzirehchong 7 | Joseph Ernest https://github.com/josephernest 8 | -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | v1.0.8, 2020/05/11 -- Text is automatically converted to string with str(). 2 | v1.0.7, 2019/06/18 -- Changes weren't recorded. See git repo. 3 | v1.0.6, 2017/03/18 -- Lack of tkinter will not cause pymsgbox to fail to be installed. 4 | v1.0.5, 2017/03/18 -- Added timeouts and localization. 5 | v1.0.4, 2017/02/26 -- Enabled the dialog windows' close buttons (returns None). Fixed issue #1. 6 | v1.0.3, 2014/09/07 -- Escape key press simulates clicking on Cancel. 7 | v1.0.2, 2014/09/03 -- Added alert() and confirm() functions for native Windows. 8 | v1.0.1, 2014/09/03 -- Default values are pre-highlighted. Added unit tests. Changed 'defaultValue' keyword argument to 'default' 9 | v1.0.0, 2014/09/02 -- Initial release. 10 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Al Sweigart 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the {organization} nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.md 2 | recursive-include docs *.bat 3 | recursive-include docs *.py 4 | recursive-include docs *.rst 5 | recursive-include docs Makefile 6 | recursive-include pymsgbox *.py 7 | recursive-include tests *.py -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PyMsgBox 2 | ======== 3 | 4 | A simple, cross-platform, pure Python module for JavaScript-like message boxes. 5 | 6 | To import, run: 7 | 8 | >>> from pymsgbox import *` 9 | 10 | There are four functions in PyMsgBox, which follow JavaScript's message box naming conventions: 11 | 12 | >>> alert(text='', title='', button='OK')` 13 | 14 | Displays a simple message box with text and a single OK button. Returns the text of the button clicked on. 15 | 16 | >>> confirm(text='', title='', buttons=['OK', 'Cancel'])` 17 | 18 | Displays a message box with OK and Cancel buttons. Number and text of buttons can be customized. Returns the text of the button clicked on. 19 | 20 | >>> prompt(text='', title='' , defaultValue='')` 21 | 22 | Displays a message box with text input, and OK & Cancel buttons. Returns the text entered, or None if Cancel was clicked. 23 | 24 | >>> password(text='', title='', defaultValue='', mask='*')` 25 | 26 | Displays a message box with text input, and OK & Cancel buttons. Typed characters appear as *. Returns the text entered, or None if Cancel was clicked. 27 | 28 | On Linux Python 2, you need to first install Tkinter by running: sudo apt-get install python-tk 29 | 30 | Modified BSD License 31 | 32 | Derived from Stephen Raymond Ferg's EasyGui http://easygui.sourceforge.net/ 33 | 34 | Support 35 | ------- 36 | 37 | If you find this project helpful and would like to support its development, [consider donating to its creator on Patreon](https://www.patreon.com/AlSweigart). 38 | -------------------------------------------------------------------------------- /code_of_conduct.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team. 59 | 60 | All complaints will be reviewed and investigated and will result in a response that 61 | is deemed necessary and appropriate to the circumstances. The project team is 62 | obligated to maintain confidentiality with regard to the reporter of an incident. 63 | Further details of specific enforcement policies may be posted separately. 64 | 65 | Project maintainers who do not follow or enforce the Code of Conduct in good 66 | faith may face temporary or permanent repercussions as determined by other 67 | members of the project's leadership. 68 | 69 | ## Attribution 70 | 71 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 72 | available at [http://contributor-covenant.org/version/1/4][version] 73 | 74 | [homepage]: http://contributor-covenant.org 75 | [version]: http://contributor-covenant.org/version/1/4/ -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PyMsgBox.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PyMsgBox.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/PyMsgBox" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PyMsgBox" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /docs/alert_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/PyMsgBox/944b7cdc67058d005ce5fd011c66f2d87d25aba0/docs/alert_example.png -------------------------------------------------------------------------------- /docs/basics.rst: -------------------------------------------------------------------------------- 1 | .. default-role:: code 2 | 3 | =============== 4 | PyMsgBox Basics 5 | =============== 6 | 7 | 8 | Installation 9 | ============ 10 | 11 | PyMsgBox can be installed from PyPI with pip: 12 | 13 | `pip install PyMsgBox` 14 | 15 | OS X and Linux may require sudo to install the module: 16 | 17 | `sudo pip install PyMsgBox` 18 | 19 | PyMsgBox uses Python's built-in TKinter module for its GUI. It is cross-platform and runs on Python 2 and Python 3. 20 | 21 | PyMsgBox's code is derived from Stephen Raymond Ferg's EasyGui. http://easygui.sourceforge.net/ 22 | 23 | Usage 24 | ===== 25 | 26 | 27 | There are four functions in PyMsgBox, which follow JavaScript's message box naming conventions: 28 | 29 | - `alert(text='', title='', button='OK')` 30 | 31 | Displays a simple message box with text and a single OK button. Returns the text of the button clicked on. 32 | 33 | >>> pymsgbox.alert('This is an alert.', 'Alert!') 34 | 'OK' 35 | 36 | .. image:: alert_example.png 37 | 38 | - `confirm(text='', title='', buttons=['OK', 'Cancel'])` 39 | 40 | Displays a message box with OK and Cancel buttons. Number and text of buttons can be customized. Returns the text of the button clicked on. 41 | 42 | >>> pymsgbox.confirm('Nuke the site from orbit?', 'Confirm nuke', ["Yes, I'm sure.", 'Cancel']) 43 | "Yes, I'm sure." 44 | 45 | .. image:: confirm_example.png 46 | 47 | - `prompt(text='', title='', defaultValue='')` 48 | 49 | Displays a message box with text input, and OK & Cancel buttons. Returns the text entered, or None if Cancel was clicked. 50 | 51 | >>> pymsgbox.prompt('What does the fox say?', default='This reference dates this example.') 52 | 'This reference dates this example.' 53 | 54 | .. image:: prompt_example.png 55 | 56 | - `password(text='', title='', defaultValue='', mask='*')` 57 | 58 | Displays a message box with text input, and OK & Cancel buttons. Typed characters appear as \*. Returns the text entered, or None if Cancel was clicked. 59 | 60 | >>> pymsgbox.password('Enter your password.') 61 | '12345' 62 | 63 | .. image:: password_example.png 64 | 65 | Timeout 66 | ======= 67 | 68 | All four functions have a `timeout` parameter which takes a number of milliseconds. At the end of the timeout, the message box will close and have a return value of `'Timeout'`. 69 | 70 | >>> pymsgbox.confirm('Nuke the site from orbit?', 'Confirm nuke', ["Yes, I'm sure.", 'Cancel'], timeout=2000) # closes after 2000 milliseconds (2 seconds) 71 | "Timeout" 72 | 73 | Localization 74 | ============ 75 | 76 | You can change the default `'OK'`, `'Cancel`', and `'Timeout'` strings by changing `pymsgbox.OK_TEXT`, `pymsgbox.CANCEL_TEXT`, and `pymsgbox.TIMEOUT_TEXT` variables respectively. -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # PyMsgBox documentation build configuration file, created by 5 | # sphinx-quickstart on Wed Aug 20 21:08:23 2014. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | import sys 17 | import os 18 | 19 | # If extensions (or modules to document with autodoc) are in another directory, 20 | # add these directories to sys.path here. If the directory is relative to the 21 | # documentation root, use os.path.abspath to make it absolute, like shown here. 22 | #sys.path.insert(0, os.path.abspath('.')) 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | #needs_sphinx = '1.0' 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = [] 33 | 34 | # Add any paths that contain templates here, relative to this directory. 35 | templates_path = ['_templates'] 36 | 37 | # The suffix of source filenames. 38 | source_suffix = '.rst' 39 | 40 | # The encoding of source files. 41 | #source_encoding = 'utf-8-sig' 42 | 43 | # The master toctree document. 44 | master_doc = 'index' 45 | 46 | # General information about the project. 47 | project = 'PyMsgBox' 48 | copyright = '2014, Al Sweigart' 49 | 50 | # The version info for the project you're documenting, acts as replacement for 51 | # |version| and |release|, also used in various other places throughout the 52 | # built documents. 53 | # 54 | # The short X.Y version. 55 | version = '0.9.0' 56 | # The full version, including alpha/beta/rc tags. 57 | release = '0.9.0' 58 | 59 | # The language for content autogenerated by Sphinx. Refer to documentation 60 | # for a list of supported languages. 61 | #language = None 62 | 63 | # There are two options for replacing |today|: either, you set today to some 64 | # non-false value, then it is used: 65 | #today = '' 66 | # Else, today_fmt is used as the format for a strftime call. 67 | #today_fmt = '%B %d, %Y' 68 | 69 | # List of patterns, relative to source directory, that match files and 70 | # directories to ignore when looking for source files. 71 | exclude_patterns = ['_build'] 72 | 73 | # The reST default role (used for this markup: `text`) to use for all 74 | # documents. 75 | #default_role = None 76 | 77 | # If true, '()' will be appended to :func: etc. cross-reference text. 78 | #add_function_parentheses = True 79 | 80 | # If true, the current module name will be prepended to all description 81 | # unit titles (such as .. function::). 82 | #add_module_names = True 83 | 84 | # If true, sectionauthor and moduleauthor directives will be shown in the 85 | # output. They are ignored by default. 86 | #show_authors = False 87 | 88 | # The name of the Pygments (syntax highlighting) style to use. 89 | pygments_style = 'sphinx' 90 | 91 | # A list of ignored prefixes for module index sorting. 92 | #modindex_common_prefix = [] 93 | 94 | # If true, keep warnings as "system message" paragraphs in the built documents. 95 | #keep_warnings = False 96 | 97 | 98 | # -- Options for HTML output ---------------------------------------------- 99 | 100 | # The theme to use for HTML and HTML Help pages. See the documentation for 101 | # a list of builtin themes. 102 | html_theme = 'default' 103 | 104 | # Theme options are theme-specific and customize the look and feel of a theme 105 | # further. For a list of options available for each theme, see the 106 | # documentation. 107 | #html_theme_options = {} 108 | 109 | # Add any paths that contain custom themes here, relative to this directory. 110 | #html_theme_path = [] 111 | 112 | # The name for this set of Sphinx documents. If None, it defaults to 113 | # " v documentation". 114 | #html_title = None 115 | 116 | # A shorter title for the navigation bar. Default is the same as html_title. 117 | #html_short_title = None 118 | 119 | # The name of an image file (relative to this directory) to place at the top 120 | # of the sidebar. 121 | #html_logo = None 122 | 123 | # The name of an image file (within the static path) to use as favicon of the 124 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 125 | # pixels large. 126 | #html_favicon = None 127 | 128 | # Add any paths that contain custom static files (such as style sheets) here, 129 | # relative to this directory. They are copied after the builtin static files, 130 | # so a file named "default.css" will overwrite the builtin "default.css". 131 | html_static_path = ['_static'] 132 | 133 | # Add any extra paths that contain custom files (such as robots.txt or 134 | # .htaccess) here, relative to this directory. These files are copied 135 | # directly to the root of the documentation. 136 | #html_extra_path = [] 137 | 138 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 139 | # using the given strftime format. 140 | #html_last_updated_fmt = '%b %d, %Y' 141 | 142 | # If true, SmartyPants will be used to convert quotes and dashes to 143 | # typographically correct entities. 144 | #html_use_smartypants = True 145 | 146 | # Custom sidebar templates, maps document names to template names. 147 | #html_sidebars = {} 148 | 149 | # Additional templates that should be rendered to pages, maps page names to 150 | # template names. 151 | #html_additional_pages = {} 152 | 153 | # If false, no module index is generated. 154 | #html_domain_indices = True 155 | 156 | # If false, no index is generated. 157 | #html_use_index = True 158 | 159 | # If true, the index is split into individual pages for each letter. 160 | #html_split_index = False 161 | 162 | # If true, links to the reST sources are added to the pages. 163 | #html_show_sourcelink = True 164 | 165 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 166 | #html_show_sphinx = True 167 | 168 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 169 | #html_show_copyright = True 170 | 171 | # If true, an OpenSearch description file will be output, and all pages will 172 | # contain a tag referring to it. The value of this option must be the 173 | # base URL from which the finished HTML is served. 174 | #html_use_opensearch = '' 175 | 176 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 177 | #html_file_suffix = None 178 | 179 | # Output file base name for HTML help builder. 180 | htmlhelp_basename = 'PyMsgBoxdoc' 181 | 182 | 183 | # -- Options for LaTeX output --------------------------------------------- 184 | 185 | latex_elements = { 186 | # The paper size ('letterpaper' or 'a4paper'). 187 | #'papersize': 'letterpaper', 188 | 189 | # The font size ('10pt', '11pt' or '12pt'). 190 | #'pointsize': '10pt', 191 | 192 | # Additional stuff for the LaTeX preamble. 193 | #'preamble': '', 194 | } 195 | 196 | # Grouping the document tree into LaTeX files. List of tuples 197 | # (source start file, target name, title, 198 | # author, documentclass [howto, manual, or own class]). 199 | latex_documents = [ 200 | ('index', 'PyMsgBox.tex', 'PyMsgBox Documentation', 201 | 'Al Sweigart', 'manual'), 202 | ] 203 | 204 | # The name of an image file (relative to this directory) to place at the top of 205 | # the title page. 206 | #latex_logo = None 207 | 208 | # For "manual" documents, if this is true, then toplevel headings are parts, 209 | # not chapters. 210 | #latex_use_parts = False 211 | 212 | # If true, show page references after internal links. 213 | #latex_show_pagerefs = False 214 | 215 | # If true, show URL addresses after external links. 216 | #latex_show_urls = False 217 | 218 | # Documents to append as an appendix to all manuals. 219 | #latex_appendices = [] 220 | 221 | # If false, no module index is generated. 222 | #latex_domain_indices = True 223 | 224 | 225 | # -- Options for manual page output --------------------------------------- 226 | 227 | # One entry per manual page. List of tuples 228 | # (source start file, name, description, authors, manual section). 229 | man_pages = [ 230 | ('index', 'pymsgbox', 'PyMsgBox Documentation', 231 | ['Al Sweigart'], 1) 232 | ] 233 | 234 | # If true, show URL addresses after external links. 235 | #man_show_urls = False 236 | 237 | 238 | # -- Options for Texinfo output ------------------------------------------- 239 | 240 | # Grouping the document tree into Texinfo files. List of tuples 241 | # (source start file, target name, title, author, 242 | # dir menu entry, description, category) 243 | texinfo_documents = [ 244 | ('index', 'PyMsgBox', 'PyMsgBox Documentation', 245 | 'Al Sweigart', 'PyMsgBox', 'One line description of project.', 246 | 'Miscellaneous'), 247 | ] 248 | 249 | # Documents to append as an appendix to all manuals. 250 | #texinfo_appendices = [] 251 | 252 | # If false, no module index is generated. 253 | #texinfo_domain_indices = True 254 | 255 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 256 | #texinfo_show_urls = 'footnote' 257 | 258 | # If true, do not generate a @detailmenu in the "Top" node's menu. 259 | #texinfo_no_detailmenu = False 260 | -------------------------------------------------------------------------------- /docs/confirm_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/PyMsgBox/944b7cdc67058d005ce5fd011c66f2d87d25aba0/docs/confirm_example.png -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. PyMsgBox documentation master file, created by 2 | sphinx-quickstart on Wed Aug 20 21:08:23 2014. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to PyMsgBox's documentation! 7 | ==================================== 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | quickstart.rst 15 | basics.rst 16 | native.rst 17 | 18 | 19 | Indices and tables 20 | ================== 21 | 22 | * :ref:`genindex` 23 | * :ref:`modindex` 24 | * :ref:`search` 25 | 26 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | goto end 41 | ) 42 | 43 | if "%1" == "clean" ( 44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 45 | del /q /s %BUILDDIR%\* 46 | goto end 47 | ) 48 | 49 | 50 | %SPHINXBUILD% 2> nul 51 | if errorlevel 9009 ( 52 | echo. 53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 54 | echo.installed, then set the SPHINXBUILD environment variable to point 55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 56 | echo.may add the Sphinx directory to PATH. 57 | echo. 58 | echo.If you don't have Sphinx installed, grab it from 59 | echo.http://sphinx-doc.org/ 60 | exit /b 1 61 | ) 62 | 63 | if "%1" == "html" ( 64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 68 | goto end 69 | ) 70 | 71 | if "%1" == "dirhtml" ( 72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 76 | goto end 77 | ) 78 | 79 | if "%1" == "singlehtml" ( 80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 84 | goto end 85 | ) 86 | 87 | if "%1" == "pickle" ( 88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can process the pickle files. 92 | goto end 93 | ) 94 | 95 | if "%1" == "json" ( 96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 97 | if errorlevel 1 exit /b 1 98 | echo. 99 | echo.Build finished; now you can process the JSON files. 100 | goto end 101 | ) 102 | 103 | if "%1" == "htmlhelp" ( 104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 105 | if errorlevel 1 exit /b 1 106 | echo. 107 | echo.Build finished; now you can run HTML Help Workshop with the ^ 108 | .hhp project file in %BUILDDIR%/htmlhelp. 109 | goto end 110 | ) 111 | 112 | if "%1" == "qthelp" ( 113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 117 | .qhcp project file in %BUILDDIR%/qthelp, like this: 118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\PyMsgBox.qhcp 119 | echo.To view the help file: 120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\PyMsgBox.ghc 121 | goto end 122 | ) 123 | 124 | if "%1" == "devhelp" ( 125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished. 129 | goto end 130 | ) 131 | 132 | if "%1" == "epub" ( 133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 137 | goto end 138 | ) 139 | 140 | if "%1" == "latex" ( 141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 145 | goto end 146 | ) 147 | 148 | if "%1" == "latexpdf" ( 149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 150 | cd %BUILDDIR%/latex 151 | make all-pdf 152 | cd %BUILDDIR%/.. 153 | echo. 154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 155 | goto end 156 | ) 157 | 158 | if "%1" == "latexpdfja" ( 159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 160 | cd %BUILDDIR%/latex 161 | make all-pdf-ja 162 | cd %BUILDDIR%/.. 163 | echo. 164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 165 | goto end 166 | ) 167 | 168 | if "%1" == "text" ( 169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 170 | if errorlevel 1 exit /b 1 171 | echo. 172 | echo.Build finished. The text files are in %BUILDDIR%/text. 173 | goto end 174 | ) 175 | 176 | if "%1" == "man" ( 177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 178 | if errorlevel 1 exit /b 1 179 | echo. 180 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 181 | goto end 182 | ) 183 | 184 | if "%1" == "texinfo" ( 185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 186 | if errorlevel 1 exit /b 1 187 | echo. 188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 189 | goto end 190 | ) 191 | 192 | if "%1" == "gettext" ( 193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 194 | if errorlevel 1 exit /b 1 195 | echo. 196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 197 | goto end 198 | ) 199 | 200 | if "%1" == "changes" ( 201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 202 | if errorlevel 1 exit /b 1 203 | echo. 204 | echo.The overview file is in %BUILDDIR%/changes. 205 | goto end 206 | ) 207 | 208 | if "%1" == "linkcheck" ( 209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 210 | if errorlevel 1 exit /b 1 211 | echo. 212 | echo.Link check complete; look for any errors in the above output ^ 213 | or in %BUILDDIR%/linkcheck/output.txt. 214 | goto end 215 | ) 216 | 217 | if "%1" == "doctest" ( 218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 219 | if errorlevel 1 exit /b 1 220 | echo. 221 | echo.Testing of doctests in the sources finished, look at the ^ 222 | results in %BUILDDIR%/doctest/output.txt. 223 | goto end 224 | ) 225 | 226 | if "%1" == "xml" ( 227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 228 | if errorlevel 1 exit /b 1 229 | echo. 230 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 231 | goto end 232 | ) 233 | 234 | if "%1" == "pseudoxml" ( 235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 236 | if errorlevel 1 exit /b 1 237 | echo. 238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 239 | goto end 240 | ) 241 | 242 | :end 243 | -------------------------------------------------------------------------------- /docs/native.rst: -------------------------------------------------------------------------------- 1 | .. default-role:: code 2 | ==================== 3 | Native Message Boxes 4 | ==================== 5 | 6 | In addition to displaying message boxes using Python's built-in TkInter GUI toolkit, PyMsgBox also has limited support for displaying message boxes by calling the operating system's native functions. These exist in the `native` module. To use them, call: 7 | 8 | >>> import pymsgbox 9 | >>> pymsgbox.native.alert('This is an alert.', 'The title.') 10 | 11 | Or, to avoid changing your function calls to use pymsgbox.native, run: 12 | 13 | >>> import pymsgbox.native as pymsgbox 14 | 15 | Support 16 | ======= 17 | 18 | These are the platforms and functions that have native message box support. Functions that do are not supported will default back to the normal TkInter-based message box. 19 | 20 | Support as of v1.0.1: 21 | 22 | +-------------+---------+---------+---------+--------------+ 23 | | | Windows | OS X | Linux | JVM / Jython | 24 | +-------------+---------+---------+---------+--------------+ 25 | | `alert()` | Yes | No | No | No | 26 | +-------------+---------+---------+---------+--------------+ 27 | | `confirm()` | Yes | No | No | No | 28 | +-------------+---------+---------+---------+--------------+ 29 | | `prompt()` | No | No | No | No | 30 | +-------------+---------+---------+---------+--------------+ 31 | |`password()` | No | No | No | No | 32 | +-------------+---------+---------+---------+--------------+ 33 | 34 | Special Notes 35 | ============= 36 | 37 | Windows - alert() 38 | ----------------- 39 | 40 | The message box will only show a button with text "OK", no matter what is passed for the `button` argument. The `button` argument will be the text that is returned by the function, just like the TkInter `alert()`. 41 | 42 | >>> pymsgbox.native.alert('This is an alert.', 'Alert!') 43 | 'OK' 44 | 45 | .. image:: win32native_alert_example.png 46 | 47 | Windows - confirm() 48 | ------------------- 49 | 50 | There will only be buttons "OK" and "Cancel", no matter what is passed for the `buttons` argument. If "OK" is clicked, the first item in the `buttons` list is returned (by default, this is `'OK'`). If "Cancel" is clicked, the second item in the `buttons` list is returned (by default, this is `'Cancel'`), just like the TkInter `confirm()`. 51 | 52 | >>> pymsgbox.native.confirm('Nuke the site from orbit?', 'Confirm nuke', ["Yes, I'm sure.", 'Cancel']) 53 | "Yes, I'm sure." 54 | 55 | .. image:: win32native_confirm_example.png 56 | -------------------------------------------------------------------------------- /docs/password_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/PyMsgBox/944b7cdc67058d005ce5fd011c66f2d87d25aba0/docs/password_example.png -------------------------------------------------------------------------------- /docs/prompt_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/PyMsgBox/944b7cdc67058d005ce5fd011c66f2d87d25aba0/docs/prompt_example.png -------------------------------------------------------------------------------- /docs/quickstart.rst: -------------------------------------------------------------------------------- 1 | 2 | .. default-role:: code 3 | 4 | ========== 5 | Quickstart 6 | ========== 7 | 8 | On Linux Python 2, you need to first install Tkinter by running: `sudo apt-get install python-tk` 9 | 10 | All of the arguments to PyMsgBox functions are optional. 11 | 12 | >>> import pymsgbox 13 | >>> pymsgbox.alert('This is an alert.') 14 | >>> pymsgbox.confirm('Click OK to continue, click Cancel to cancel.') 15 | >>> pymsgbox.prompt('Enter your name.') 16 | >>> pymsgbox.password('Enter your password. (It will appear hidden in this text field.)') 17 | 18 | Here are the default arguments for each function. 19 | 20 | >>> pymsgbox.alert(text='', title='', button='OK') 21 | >>> pymsgbox.confirm(text='', title='', buttons=['OK', 'Cancel']) 22 | >>> pymsgbox.prompt(text='', title='' , defaultValue='') 23 | >>> pymsgbox.password(text='', title='', defaultValue='', mask='*') 24 | 25 | To use native operating system message boxes, run the following: 26 | 27 | >>> import pymsgbox.native as pymsgbox 28 | 29 | (Only a few of the message boxes have native support. Unsupported message boxes will default to the normal message boxes.) -------------------------------------------------------------------------------- /docs/win32native_alert_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/PyMsgBox/944b7cdc67058d005ce5fd011c66f2d87d25aba0/docs/win32native_alert_example.png -------------------------------------------------------------------------------- /docs/win32native_confirm_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/PyMsgBox/944b7cdc67058d005ce5fd011c66f2d87d25aba0/docs/win32native_confirm_example.png -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/PyMsgBox/944b7cdc67058d005ce5fd011c66f2d87d25aba0/pyproject.toml -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import re 2 | from setuptools import setup, find_packages 3 | 4 | # Load version from module (without loading the whole module) 5 | with open('src/pymsgbox/__init__.py', 'r') as fo: 6 | version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', 7 | fo.read(), re.MULTILINE).group(1) 8 | 9 | # Read in the README.md for the long description. 10 | with open('README.md') as fo: 11 | long_description = fo.read() 12 | 13 | setup( 14 | name='PyMsgBox', 15 | version=version, 16 | url='https://github.com/asweigart/pymsgbox', 17 | author='Al Sweigart', 18 | author_email='al@inventwithpython.com', 19 | description=('''A simple, cross-platform, pure Python module for JavaScript-like message boxes.'''), 20 | long_description=long_description, 21 | long_description_content_type="text/markdown", 22 | license='GPLv3+', 23 | packages=find_packages(where='src'), 24 | package_dir={'': 'src'}, 25 | test_suite='tests', 26 | install_requires=[], 27 | keywords='', 28 | classifiers=[ 29 | 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)', 30 | 'Programming Language :: Python', 31 | 'Programming Language :: Python :: 3', 32 | 'Programming Language :: Python :: 3.4', 33 | 'Programming Language :: Python :: 3.5', 34 | 'Programming Language :: Python :: 3.6', 35 | 'Programming Language :: Python :: 3.7', 36 | 'Programming Language :: Python :: 3.8', 37 | 'Programming Language :: Python :: 3.9', 38 | ], 39 | ) 40 | -------------------------------------------------------------------------------- /src/pymsgbox/__init__.py: -------------------------------------------------------------------------------- 1 | # PyMsgBox - A simple, cross-platform, pure Python module for JavaScript-like message boxes. 2 | # By Al Sweigart al@inventwithpython.com 3 | 4 | __version__ = "1.0.9" 5 | 6 | # Modified BSD License 7 | # Derived from Stephen Raymond Ferg's EasyGui http://easygui.sourceforge.net/ 8 | 9 | """ 10 | The four functions in PyMsgBox: 11 | 12 | - alert(text='', title='', button='OK') 13 | 14 | Displays a simple message box with text and a single OK button. Returns the text of the button clicked on. 15 | 16 | - confirm(text='', title='', buttons=['OK', 'Cancel']) 17 | 18 | Displays a message box with OK and Cancel buttons. Number and text of buttons can be customized. Returns the text of the button clicked on. 19 | 20 | - prompt(text='', title='' , default='') 21 | 22 | Displays a message box with text input, and OK & Cancel buttons. Returns the text entered, or None if Cancel was clicked. 23 | 24 | - password(text='', title='', default='', mask='*') 25 | 26 | Displays a message box with text input, and OK & Cancel buttons. Typed characters appear as *. Returns the text entered, or None if Cancel was clicked. 27 | """ 28 | 29 | """ 30 | TODO Roadmap: 31 | - Be able to specify a custom icon in the message box. 32 | - Be able to place the message box at an arbitrary position (including on multi screen layouts) 33 | - Add mouse clicks to unit testing. 34 | - progress() function to display a progress bar 35 | - Maybe other types of dialog: open, save, file/folder picker, etc. 36 | """ 37 | 38 | import sys 39 | 40 | RUNNING_PYTHON_2 = sys.version_info[0] == 2 41 | 42 | # Because PyAutoGUI requires PyMsgBox but might be installed on systems 43 | # without tkinter, we don't want a lack of tkinter to cause installation 44 | # to fail. So exceptions won't be raised until the PyMsgBox functions 45 | # are actually called. 46 | TKINTER_IMPORT_SUCCEEDED = True 47 | 48 | try: 49 | if RUNNING_PYTHON_2: 50 | import Tkinter as tk 51 | else: 52 | import tkinter as tk 53 | 54 | rootWindowPosition = "+300+200" 55 | 56 | if tk.TkVersion < 8.0: 57 | raise RuntimeError( 58 | "You are running Tk version: " 59 | + str(tk.TkVersion) 60 | + "You must be using Tk version 8.0 or greater to use PyMsgBox." 61 | ) 62 | 63 | except ImportError: 64 | TKINTER_IMPORT_SUCCEEDED = False 65 | 66 | 67 | PROPORTIONAL_FONT_FAMILY = ("MS", "Sans", "Serif") 68 | MONOSPACE_FONT_FAMILY = "Courier" 69 | 70 | PROPORTIONAL_FONT_SIZE = 10 71 | MONOSPACE_FONT_SIZE = ( 72 | 9 73 | ) # a little smaller, because it it more legible at a smaller size 74 | TEXT_ENTRY_FONT_SIZE = 12 # a little larger makes it easier to see 75 | 76 | 77 | STANDARD_SELECTION_EVENTS = ["Return", "Button-1", "space"] 78 | 79 | # constants for strings: (TODO: for internationalization, change these) 80 | OK_TEXT = "OK" 81 | CANCEL_TEXT = "Cancel" 82 | YES_TEXT = "Yes" 83 | NO_TEXT = "No" 84 | RETRY_TEXT = "Retry" 85 | ABORT_TEXT = "Abort" 86 | IGNORE_TEXT = "Ignore" 87 | TRY_AGAIN_TEXT = "Try Again" 88 | CONTINUE_TEXT = "Continue" 89 | 90 | TIMEOUT_RETURN_VALUE = "Timeout" 91 | 92 | # Initialize some global variables that will be reset later 93 | __choiceboxMultipleSelect = None 94 | __widgetTexts = None 95 | __replyButtonText = None 96 | __choiceboxResults = None 97 | __firstWidget = None 98 | __enterboxText = None 99 | __enterboxDefaultText = "" 100 | __multenterboxText = "" 101 | choiceboxChoices = None 102 | choiceboxWidget = None 103 | entryWidget = None 104 | boxRoot = None 105 | buttonsFrame = None 106 | 107 | 108 | def _alertTkinter(text="", title="", button=OK_TEXT, root=None, timeout=None): 109 | """Displays a simple message box with text and a single OK button. Returns the text of the button clicked on.""" 110 | assert TKINTER_IMPORT_SUCCEEDED, "Tkinter is required for pymsgbox" 111 | text = str(text) 112 | retVal = _buttonbox( 113 | msg=text, title=title, choices=[str(button)], root=root, timeout=timeout 114 | ) 115 | if retVal is None: 116 | return button 117 | else: 118 | return retVal 119 | 120 | 121 | alert = _alertTkinter 122 | 123 | 124 | def _confirmTkinter( 125 | text="", title="", buttons=(OK_TEXT, CANCEL_TEXT), root=None, timeout=None 126 | ): 127 | """Displays a message box with OK and Cancel buttons. Number and text of buttons can be customized. Returns the text of the button clicked on.""" 128 | assert TKINTER_IMPORT_SUCCEEDED, "Tkinter is required for pymsgbox" 129 | text = str(text) 130 | return _buttonbox( 131 | msg=text, 132 | title=title, 133 | choices=[str(b) for b in buttons], 134 | root=root, 135 | timeout=timeout, 136 | ) 137 | 138 | 139 | confirm = _confirmTkinter 140 | 141 | 142 | def _promptTkinter(text="", title="", default="", root=None, timeout=None): 143 | """Displays a message box with text input, and OK & Cancel buttons. Returns the text entered, or None if Cancel was clicked.""" 144 | assert TKINTER_IMPORT_SUCCEEDED, "Tkinter is required for pymsgbox" 145 | text = str(text) 146 | return __fillablebox( 147 | text, title, default=default, mask=None, root=root, timeout=timeout 148 | ) 149 | 150 | 151 | prompt = _promptTkinter 152 | 153 | 154 | def _passwordTkinter(text="", title="", default="", mask="*", root=None, timeout=None): 155 | """Displays a message box with text input, and OK & Cancel buttons. Typed characters appear as *. Returns the text entered, or None if Cancel was clicked.""" 156 | assert TKINTER_IMPORT_SUCCEEDED, "Tkinter is required for pymsgbox" 157 | text = str(text) 158 | return __fillablebox(text, title, default, mask=mask, root=root, timeout=timeout) 159 | 160 | 161 | password = _passwordTkinter 162 | 163 | 164 | # Load the native versions of the alert/confirm/prompt/password functions, if available: 165 | if sys.platform == "win32": 166 | from . import _native_win 167 | 168 | NO_ICON = 0 169 | STOP = 0x10 170 | QUESTION = 0x20 171 | WARNING = 0x30 172 | INFO = 0x40 173 | alert = _native_win.alert 174 | confirm = _native_win.confirm 175 | 176 | 177 | def timeoutBoxRoot(): 178 | global boxRoot, __replyButtonText, __enterboxText 179 | boxRoot.destroy() 180 | __replyButtonText = TIMEOUT_RETURN_VALUE 181 | __enterboxText = TIMEOUT_RETURN_VALUE 182 | 183 | 184 | def _buttonbox(msg, title, choices, root=None, timeout=None): 185 | """ 186 | Display a msg, a title, and a set of buttons. 187 | The buttons are defined by the members of the choices list. 188 | Return the text of the button that the user selected. 189 | 190 | @arg msg: the msg to be displayed. 191 | @arg title: the window title 192 | @arg choices: a list or tuple of the choices to be displayed 193 | """ 194 | global boxRoot, __replyButtonText, __widgetTexts, buttonsFrame 195 | 196 | # Initialize __replyButtonText to the first choice. 197 | # This is what will be used if the window is closed by the close button. 198 | __replyButtonText = choices[0] 199 | 200 | if root: 201 | root.withdraw() 202 | boxRoot = tk.Toplevel(master=root) 203 | boxRoot.withdraw() 204 | else: 205 | boxRoot = tk.Tk() 206 | boxRoot.withdraw() 207 | 208 | boxRoot.title(title) 209 | boxRoot.iconname("Dialog") 210 | boxRoot.geometry(rootWindowPosition) 211 | boxRoot.minsize(400, 100) 212 | 213 | # ------------- define the messageFrame --------------------------------- 214 | messageFrame = tk.Frame(master=boxRoot) 215 | messageFrame.pack(side=tk.TOP, fill=tk.BOTH) 216 | 217 | # ------------- define the buttonsFrame --------------------------------- 218 | buttonsFrame = tk.Frame(master=boxRoot) 219 | buttonsFrame.pack(side=tk.TOP, fill=tk.BOTH) 220 | 221 | # -------------------- place the widgets in the frames ----------------------- 222 | messageWidget = tk.Message(messageFrame, text=msg, width=400) 223 | messageWidget.configure(font=(PROPORTIONAL_FONT_FAMILY, PROPORTIONAL_FONT_SIZE)) 224 | messageWidget.pack(side=tk.TOP, expand=tk.YES, fill=tk.X, padx="3m", pady="3m") 225 | 226 | __put_buttons_in_buttonframe(choices) 227 | 228 | # -------------- the action begins ----------- 229 | # put the focus on the first button 230 | __firstWidget.focus_force() 231 | 232 | boxRoot.deiconify() 233 | if timeout is not None: 234 | boxRoot.after(timeout, timeoutBoxRoot) 235 | boxRoot.mainloop() 236 | try: 237 | boxRoot.destroy() 238 | except tk.TclError: 239 | if __replyButtonText != TIMEOUT_RETURN_VALUE: 240 | __replyButtonText = None 241 | 242 | if root: 243 | root.deiconify() 244 | return __replyButtonText 245 | 246 | 247 | def __put_buttons_in_buttonframe(choices): 248 | """Put the buttons in the buttons frame""" 249 | global __widgetTexts, __firstWidget, buttonsFrame 250 | 251 | __firstWidget = None 252 | __widgetTexts = {} 253 | 254 | i = 0 255 | 256 | for buttonText in choices: 257 | tempButton = tk.Button(buttonsFrame, takefocus=1, text=buttonText) 258 | _bindArrows(tempButton) 259 | tempButton.pack( 260 | expand=tk.YES, side=tk.LEFT, padx="1m", pady="1m", ipadx="2m", ipady="1m" 261 | ) 262 | 263 | # remember the text associated with this widget 264 | __widgetTexts[tempButton] = buttonText 265 | 266 | # remember the first widget, so we can put the focus there 267 | if i == 0: 268 | __firstWidget = tempButton 269 | i = 1 270 | 271 | # for the commandButton, bind activation events to the activation event handler 272 | commandButton = tempButton 273 | handler = __buttonEvent 274 | for selectionEvent in STANDARD_SELECTION_EVENTS: 275 | commandButton.bind("<%s>" % selectionEvent, handler) 276 | 277 | if CANCEL_TEXT in choices: 278 | commandButton.bind("", __cancelButtonEvent) 279 | 280 | 281 | def _bindArrows(widget, skipArrowKeys=False): 282 | widget.bind("", _tabRight) 283 | widget.bind("", _tabLeft) 284 | 285 | if not skipArrowKeys: 286 | widget.bind("", _tabRight) 287 | widget.bind("", _tabLeft) 288 | 289 | 290 | def _tabRight(event): 291 | boxRoot.event_generate("") 292 | 293 | 294 | def _tabLeft(event): 295 | boxRoot.event_generate("") 296 | 297 | 298 | def __buttonEvent(event): 299 | """ 300 | Handle an event that is generated by a person clicking a button. 301 | """ 302 | global boxRoot, __widgetTexts, __replyButtonText 303 | __replyButtonText = __widgetTexts[event.widget] 304 | boxRoot.quit() # quit the main loop 305 | 306 | 307 | def __cancelButtonEvent(event): 308 | """Handle pressing Esc by clicking the Cancel button.""" 309 | global boxRoot, __widgetTexts, __replyButtonText 310 | __replyButtonText = CANCEL_TEXT 311 | boxRoot.quit() 312 | 313 | 314 | def __fillablebox(msg, title="", default="", mask=None, root=None, timeout=None): 315 | """ 316 | Show a box in which a user can enter some text. 317 | You may optionally specify some default text, which will appear in the 318 | enterbox when it is displayed. 319 | Returns the text that the user entered, or None if he cancels the operation. 320 | """ 321 | 322 | global boxRoot, __enterboxText, __enterboxDefaultText 323 | global cancelButton, entryWidget, okButton 324 | 325 | if title == None: 326 | title == "" 327 | if default == None: 328 | default = "" 329 | __enterboxDefaultText = default 330 | __enterboxText = __enterboxDefaultText 331 | 332 | if root: 333 | root.withdraw() 334 | boxRoot = tk.Toplevel(master=root) 335 | boxRoot.withdraw() 336 | else: 337 | boxRoot = tk.Tk() 338 | boxRoot.withdraw() 339 | 340 | boxRoot.title(title) 341 | boxRoot.iconname("Dialog") 342 | boxRoot.geometry(rootWindowPosition) 343 | boxRoot.bind("", __enterboxCancel) 344 | 345 | # ------------- define the messageFrame --------------------------------- 346 | messageFrame = tk.Frame(master=boxRoot) 347 | messageFrame.pack(side=tk.TOP, fill=tk.BOTH) 348 | 349 | # ------------- define the buttonsFrame --------------------------------- 350 | buttonsFrame = tk.Frame(master=boxRoot) 351 | buttonsFrame.pack(side=tk.TOP, fill=tk.BOTH) 352 | 353 | # ------------- define the entryFrame --------------------------------- 354 | entryFrame = tk.Frame(master=boxRoot) 355 | entryFrame.pack(side=tk.TOP, fill=tk.BOTH) 356 | 357 | # ------------- define the buttonsFrame --------------------------------- 358 | buttonsFrame = tk.Frame(master=boxRoot) 359 | buttonsFrame.pack(side=tk.TOP, fill=tk.BOTH) 360 | 361 | # -------------------- the msg widget ---------------------------- 362 | messageWidget = tk.Message(messageFrame, width="4.5i", text=msg) 363 | messageWidget.configure(font=(PROPORTIONAL_FONT_FAMILY, PROPORTIONAL_FONT_SIZE)) 364 | messageWidget.pack(side=tk.RIGHT, expand=1, fill=tk.BOTH, padx="3m", pady="3m") 365 | 366 | # --------- entryWidget ---------------------------------------------- 367 | entryWidget = tk.Entry(entryFrame, width=40) 368 | _bindArrows(entryWidget, skipArrowKeys=True) 369 | entryWidget.configure(font=(PROPORTIONAL_FONT_FAMILY, TEXT_ENTRY_FONT_SIZE)) 370 | if mask: 371 | entryWidget.configure(show=mask) 372 | entryWidget.pack(side=tk.LEFT, padx="3m") 373 | entryWidget.bind("", __enterboxGetText) 374 | entryWidget.bind("", __enterboxCancel) 375 | 376 | # put text into the entryWidget and have it pre-highlighted 377 | if __enterboxDefaultText != "": 378 | entryWidget.insert(0, __enterboxDefaultText) 379 | entryWidget.select_range(0, tk.END) 380 | 381 | # ------------------ ok button ------------------------------- 382 | okButton = tk.Button(buttonsFrame, takefocus=1, text=OK_TEXT) 383 | _bindArrows(okButton) 384 | okButton.pack(expand=1, side=tk.LEFT, padx="3m", pady="3m", ipadx="2m", ipady="1m") 385 | 386 | # for the commandButton, bind activation events to the activation event handler 387 | commandButton = okButton 388 | handler = __enterboxGetText 389 | for selectionEvent in STANDARD_SELECTION_EVENTS: 390 | commandButton.bind("<%s>" % selectionEvent, handler) 391 | 392 | # ------------------ cancel button ------------------------------- 393 | cancelButton = tk.Button(buttonsFrame, takefocus=1, text=CANCEL_TEXT) 394 | _bindArrows(cancelButton) 395 | cancelButton.pack( 396 | expand=1, side=tk.RIGHT, padx="3m", pady="3m", ipadx="2m", ipady="1m" 397 | ) 398 | 399 | # for the commandButton, bind activation events to the activation event handler 400 | commandButton = cancelButton 401 | handler = __enterboxCancel 402 | for selectionEvent in STANDARD_SELECTION_EVENTS: 403 | commandButton.bind("<%s>" % selectionEvent, handler) 404 | 405 | # ------------------- time for action! ----------------- 406 | entryWidget.focus_force() # put the focus on the entryWidget 407 | boxRoot.deiconify() 408 | if timeout is not None: 409 | boxRoot.after(timeout, timeoutBoxRoot) 410 | boxRoot.mainloop() # run it! 411 | 412 | # -------- after the run has completed ---------------------------------- 413 | if root: 414 | root.deiconify() 415 | try: 416 | boxRoot.destroy() # button_click didn't destroy boxRoot, so we do it now 417 | except tk.TclError: 418 | if __enterboxText != TIMEOUT_RETURN_VALUE: 419 | return None 420 | 421 | return __enterboxText 422 | 423 | 424 | def __enterboxGetText(event): 425 | global __enterboxText 426 | 427 | __enterboxText = entryWidget.get() 428 | boxRoot.quit() 429 | 430 | 431 | def __enterboxRestore(event): 432 | global entryWidget 433 | 434 | entryWidget.delete(0, len(entryWidget.get())) 435 | entryWidget.insert(0, __enterboxDefaultText) 436 | 437 | 438 | def __enterboxCancel(event): 439 | global __enterboxText 440 | 441 | __enterboxText = None 442 | boxRoot.quit() 443 | -------------------------------------------------------------------------------- /src/pymsgbox/_native_win.py: -------------------------------------------------------------------------------- 1 | # This module contains all the Windows-specific code to create native 2 | # message boxes using the winapi. 3 | 4 | # If you'd like to learn more about calling the winapi functions from 5 | # Python, you can check out my other module "nicewin" to see nicely-documented 6 | # examples. It is at https://github.com/asweigart/nicewin 7 | 8 | # The documentation for the MessageBox winapi is at: 9 | # https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-messagebox 10 | 11 | import sys, ctypes 12 | import pymsgbox 13 | 14 | MB_OK = 0x0 15 | MB_OKCANCEL = 0x1 16 | MB_ABORTRETRYIGNORE = 0x2 17 | MB_YESNOCANCEL = 0x3 18 | MB_YESNO = 0x4 19 | MB_RETRYCANCEL = 0x5 20 | MB_CANCELTRYCONTINUE = 0x6 21 | 22 | NO_ICON = 0 23 | STOP = MB_ICONHAND = MB_ICONSTOP = MB_ICONERRPR = 0x10 24 | QUESTION = MB_ICONQUESTION = 0x20 25 | WARNING = MB_ICONEXCLAIMATION = 0x30 26 | INFO = MB_ICONASTERISK = MB_ICONINFOMRAITON = 0x40 27 | 28 | MB_DEFAULTBUTTON1 = 0x0 29 | MB_DEFAULTBUTTON2 = 0x100 30 | MB_DEFAULTBUTTON3 = 0x200 31 | MB_DEFAULTBUTTON4 = 0x300 32 | 33 | MB_SETFOREGROUND = 0x10000 34 | MB_TOPMOST = 0x40000 35 | 36 | IDABORT = 0x3 37 | IDCANCEL = 0x2 38 | IDCONTINUE = 0x11 39 | IDIGNORE = 0x5 40 | IDNO = 0x7 41 | IDOK = 0x1 42 | IDRETRY = 0x4 43 | IDTRYAGAIN = 0x10 44 | IDYES = 0x6 45 | 46 | runningOnPython2 = sys.version_info[0] == 2 47 | if runningOnPython2: 48 | messageBoxFunc = ctypes.windll.user32.MessageBoxA 49 | else: # Python 3 functions. 50 | messageBoxFunc = ctypes.windll.user32.MessageBoxW 51 | 52 | 53 | def alert( 54 | text="", 55 | title="", 56 | button=pymsgbox.OK_TEXT, 57 | root=None, 58 | timeout=None, 59 | icon=NO_ICON, 60 | _tkinter=False, 61 | ): 62 | """Displays a simple message box with text and a single OK button. Returns the text of the button clicked on.""" 63 | text = str(text) 64 | if (_tkinter) or (timeout is not None) or (button != pymsgbox.OK_TEXT): 65 | # Timeouts are not supported by Windows message boxes. 66 | # Call the original tkinter alert function, not this native one: 67 | return pymsgbox._alertTkinter(text, title, button, root, timeout) 68 | 69 | messageBoxFunc(0, text, title, MB_OK | MB_SETFOREGROUND | MB_TOPMOST | icon) 70 | return button 71 | 72 | 73 | def confirm( 74 | text="", 75 | title="", 76 | buttons=(pymsgbox.OK_TEXT, pymsgbox.CANCEL_TEXT), 77 | root=None, 78 | timeout=None, 79 | icon=QUESTION, 80 | _tkinter=False, 81 | ): 82 | """Displays a message box with OK and Cancel buttons. Number and text of buttons can be customized. Returns the text of the button clicked on.""" 83 | text = str(text) 84 | buttonFlag = None 85 | if len(buttons) == 1: 86 | if buttons[0] == pymsgbox.OK_TEXT: 87 | buttonFlag = MB_OK 88 | elif len(buttons) == 2: 89 | if buttons[0] == pymsgbox.OK_TEXT and buttons[1] == pymsgbox.CANCEL_TEXT: 90 | buttonFlag = MB_OKCANCEL 91 | elif buttons[0] == pymsgbox.YES_TEXT and buttons[1] == pymsgbox.NO_TEXT: 92 | buttonFlag = MB_YESNO 93 | elif buttons[0] == pymsgbox.RETRY_TEXT and buttons[1] == pymsgbox.CANCEL_TEXT: 94 | buttonFlag = MB_RETRYCANCEL 95 | elif len(buttons) == 3: 96 | if ( 97 | buttons[0] == pymsgbox.ABORT_TEXT 98 | and buttons[1] == pymsgbox.RETRY_TEXT 99 | and buttons[2] == pymsgbox.IGNORE_TEXT 100 | ): 101 | buttonFlag = MB_ABORTRETRYIGNORE 102 | elif ( 103 | buttons[0] == pymsgbox.CANCEL_TEXT 104 | and buttons[1] == pymsgbox.TRY_AGAIN_TEXT 105 | and buttons[2] == pymsgbox.CONTINUE_TEXT 106 | ): 107 | buttonFlag = MB_CANCELTRYCONTINUE 108 | elif ( 109 | buttons[0] == pymsgbox.YES_TEXT 110 | and buttons[1] == pymsgbox.NO_TEXT 111 | and buttons[2] == pymsgbox.CANCEL_TEXT 112 | ): 113 | buttonFlag = MB_YESNOCANCEL 114 | 115 | if (_tkinter) or (timeout is not None) or (buttonFlag is None): 116 | # Call the original tkinter confirm() function, not this native one: 117 | return pymsgbox._confirmTkinter(text, title, buttons, root, timeout) 118 | 119 | retVal = messageBoxFunc( 120 | 0, text, title, buttonFlag | MB_SETFOREGROUND | MB_TOPMOST | icon 121 | ) 122 | if retVal == IDOK or len(buttons) == 1: 123 | return pymsgbox.OK_TEXT 124 | elif retVal == IDCANCEL: 125 | return pymsgbox.CANCEL_TEXT 126 | elif retVal == IDYES: 127 | return pymsgbox.YES_TEXT 128 | elif retVal == IDNO: 129 | return pymsgbox.NO_TEXT 130 | elif retVal == IDTRYAGAIN: 131 | return pymsgbox.TRY_TEXT 132 | elif retVal == IDRETRY: 133 | return pymsgbox.RETRY_TEXT 134 | elif retVal == IDIGNORE: 135 | return pymsgbox.IGNORE_TEXT 136 | elif retVal == IDCONTINUE: 137 | return pymsgbox.CONTINUE_TEXT 138 | elif retVal == IDABORT: 139 | return pymsgbox.ABORT_TEXT 140 | else: 141 | assert False, "Unexpected return value from MessageBox: %s" % (retVal) 142 | 143 | 144 | ''' 145 | def prompt(text='', title='' , default=''): 146 | """Displays a message box with text input, and OK & Cancel buttons. Returns the text entered, or None if Cancel was clicked.""" 147 | pass 148 | 149 | def password(text='', title='', default='', mask='*'): 150 | """Displays a message box with text input, and OK & Cancel buttons. Typed characters appear as *. Returns the text entered, or None if Cancel was clicked.""" 151 | pass 152 | 153 | ''' 154 | -------------------------------------------------------------------------------- /tests/test_pymsgbox.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import sys 3 | import os 4 | import time 5 | import threading 6 | import inspect 7 | 8 | import pymsgbox 9 | 10 | # Note: Yes, PyAutoGUI does have PyMsgBox itself as a dependency, but we won't be using that part of PyAutoGUI for this testing. 11 | import pyautogui # PyAutoGUI simulates key presses on the message boxes. 12 | pyautogui.PAUSE = 0.1 13 | 14 | 15 | GUI_WAIT = 0.4 # if tests start failing, maybe try bumping this up a bit (though that'll slow the tests down) 16 | 17 | 18 | """ 19 | NOTE: You will often see this code in this test: 20 | 21 | print('Line', inspect.currentframe().f_lineno); 22 | 23 | This is because due to the GUI nature of these tests, if something messes up 24 | and PyAutoGUI is unable to click on the message box, this program will get 25 | held up. By printing out the line number, you will at least be able to see 26 | which line displayed the message box that is held up. 27 | 28 | This is a bit unorthodox, and I'm welcome to other suggestions about how to 29 | deal with this possible scenario. 30 | """ 31 | 32 | class KeyPresses(threading.Thread): 33 | def __init__(self, keyPresses): 34 | super(KeyPresses, self).__init__() 35 | self.keyPresses = keyPresses 36 | 37 | def run(self): 38 | time.sleep(GUI_WAIT) 39 | pyautogui.typewrite(self.keyPresses, interval=0.05) 40 | 41 | 42 | class AlertTests(unittest.TestCase): 43 | def test_alert(self): 44 | for func in (pymsgbox._alertTkinter, pymsgbox.alert): 45 | if func is pymsgbox._alertTkinter: 46 | print('Testing tkinter alert()') 47 | elif func is pymsgbox.alert: 48 | print('Testing native alert()') 49 | # no text 50 | t = KeyPresses(['enter']) 51 | t.start() 52 | print('Line', inspect.currentframe().f_lineno) 53 | self.assertEqual(func(), 'OK') 54 | 55 | # text 56 | t = KeyPresses(['enter']) 57 | t.start() 58 | print('Line', inspect.currentframe().f_lineno) 59 | self.assertEqual(func('Hello'), 'OK') 60 | 61 | # text and title 62 | t = KeyPresses(['enter']) 63 | t.start() 64 | print('Line', inspect.currentframe().f_lineno) 65 | self.assertEqual(func('Hello', 'Title'), 'OK') 66 | 67 | # text, title, and custom button 68 | t = KeyPresses(['enter']) 69 | t.start() 70 | print('Line', inspect.currentframe().f_lineno) 71 | self.assertEqual(func('Hello', 'Title', 'Button'), 'Button') 72 | 73 | # using keyword arguments 74 | t = KeyPresses(['enter']) 75 | t.start() 76 | print('Line', inspect.currentframe().f_lineno) 77 | self.assertEqual(func(text='Hello', title='Title', button='Button'), 'Button') 78 | 79 | 80 | class ConfirmTests(unittest.TestCase): 81 | def test_confirm(self): 82 | for func in (pymsgbox._confirmTkinter, pymsgbox.confirm): 83 | if func is pymsgbox._confirmTkinter: 84 | print('Testing tkinter confirm()') 85 | elif func is pymsgbox.confirm: 86 | print('Testing native confirm()') 87 | 88 | # press enter on OK 89 | t = KeyPresses(['enter']) 90 | t.start() 91 | print('Line', inspect.currentframe().f_lineno) 92 | self.assertEqual(func(), 'OK') 93 | 94 | # press right, enter on Cancel 95 | t = KeyPresses(['right', 'enter']) 96 | t.start() 97 | print('Line', inspect.currentframe().f_lineno) 98 | self.assertEqual(func(), 'Cancel') 99 | 100 | # press right, left, right, enter on Cancel 101 | t = KeyPresses(['right', 'left', 'right', 'enter']) 102 | t.start() 103 | print('Line', inspect.currentframe().f_lineno) 104 | self.assertEqual(func(), 'Cancel') 105 | 106 | # press tab, enter on Cancel 107 | t = KeyPresses(['tab', 'enter']) 108 | t.start() 109 | print('Line', inspect.currentframe().f_lineno) 110 | self.assertEqual(func(), 'Cancel') 111 | 112 | # press tab, tab, enter on OK 113 | t = KeyPresses(['tab', 'tab', 'enter']) 114 | t.start() 115 | print('Line', inspect.currentframe().f_lineno) 116 | self.assertEqual(func(), 'OK') 117 | 118 | # with text 119 | t = KeyPresses(['enter']) 120 | t.start() 121 | print('Line', inspect.currentframe().f_lineno) 122 | self.assertEqual(func('Hello'), 'OK') 123 | 124 | # with text, title 125 | t = KeyPresses(['enter']) 126 | t.start() 127 | print('Line', inspect.currentframe().f_lineno) 128 | self.assertEqual(func('Hello', 'Title'), 'OK') 129 | 130 | # with text, title, and one custom button 131 | t = KeyPresses(['enter']) 132 | t.start() 133 | print('Line', inspect.currentframe().f_lineno) 134 | self.assertEqual(func('Hello', 'Title', ['A']), 'A') 135 | 136 | # with text, title, and one custom blank button 137 | t = KeyPresses(['enter']) 138 | t.start() 139 | print('Line', inspect.currentframe().f_lineno) 140 | self.assertEqual(func('Hello', 'Title', ['']), '') 141 | 142 | # with text, title, and two custom buttons 143 | t = KeyPresses(['enter']) 144 | t.start() 145 | print('Line', inspect.currentframe().f_lineno) 146 | self.assertEqual(func('Hello', 'Title', ['A', 'B']), 'A') 147 | 148 | t = KeyPresses(['right', 'enter']) 149 | t.start() 150 | print('Line', inspect.currentframe().f_lineno) 151 | self.assertEqual(func('Hello', 'Title', ['A', 'B']), 'B') 152 | 153 | t = KeyPresses(['right', 'left', 'enter']) 154 | t.start() 155 | print('Line', inspect.currentframe().f_lineno) 156 | self.assertEqual(func('Hello', 'Title', ['A', 'B']), 'A') 157 | 158 | t = KeyPresses(['tab', 'enter']) 159 | t.start() 160 | print('Line', inspect.currentframe().f_lineno) 161 | self.assertEqual(func('Hello', 'Title', ['A', 'B']), 'B') 162 | 163 | t = KeyPresses(['tab', 'tab', 'enter']) 164 | t.start() 165 | print('Line', inspect.currentframe().f_lineno) 166 | self.assertEqual(func('Hello', 'Title', ['A', 'B']), 'A') 167 | 168 | # with text, title, and three custom buttons 169 | t = KeyPresses(['tab', 'tab', 'enter']) 170 | t.start() 171 | print('Line', inspect.currentframe().f_lineno) 172 | self.assertEqual(func('Hello', 'Title', ['A', 'B', 'C']), 'C') 173 | 174 | # with text, title, and four custom buttons 175 | t = KeyPresses(['tab', 'tab', 'tab', 'enter']) 176 | t.start() 177 | print('Line', inspect.currentframe().f_lineno) 178 | self.assertEqual(func('Hello', 'Title', ['A', 'B', 'C', 'D']), 'D') 179 | 180 | # with text, title, and five custom buttons 181 | t = KeyPresses(['tab', 'tab', 'tab', 'tab', 'enter']) 182 | t.start() 183 | print('Line', inspect.currentframe().f_lineno) 184 | self.assertEqual(func('Hello', 'Title', ['A', 'B', 'C', 'D', 'E']), 'E') 185 | 186 | # with text, title, and three custom buttons specified with keyword arguments 187 | t = KeyPresses(['tab', 'tab', 'enter']) 188 | t.start() 189 | print('Line', inspect.currentframe().f_lineno) 190 | self.assertEqual(func(text='Hello', title='Title', buttons=['A', 'B', 'C']), 'C') 191 | 192 | # test that pressing Esc is the same as clicking Cancel (but only when there is a cancel button) 193 | t = KeyPresses(['escape']) 194 | t.start() 195 | print('Line', inspect.currentframe().f_lineno) 196 | self.assertEqual(func(text='Escape button press test'), 'Cancel') 197 | 198 | # Make sure that Esc keypress does nothing if there is no Cancel button. 199 | t = KeyPresses(['escape', 'enter']) 200 | t.start() 201 | print('Line', inspect.currentframe().f_lineno) 202 | self.assertEqual(func(text='Escape button press test', buttons=['OK', 'Not OK']), 'OK') 203 | 204 | class PromptPasswordTests(unittest.TestCase): 205 | def test_prompt(self): 206 | self._prompt_and_password_tests(pymsgbox._promptTkinter, 'prompt()') 207 | 208 | def test_password(self): 209 | # NOTE: Currently there is no way to test the appearance of the * or custom mask characters. 210 | self._prompt_and_password_tests(pymsgbox._passwordTkinter, 'password()') 211 | 212 | def _prompt_and_password_tests(self, msgBoxFunc, msgBoxFuncName): 213 | # entering nothing 214 | t = KeyPresses(['enter']) 215 | t.start() 216 | print('Line', inspect.currentframe().f_lineno) 217 | self.assertEqual((msgBoxFuncName, msgBoxFunc()), (msgBoxFuncName, '')) 218 | 219 | # entering text 220 | t = KeyPresses(['a', 'b', 'c', 'enter']) 221 | t.start() 222 | print('Line', inspect.currentframe().f_lineno) 223 | self.assertEqual((msgBoxFuncName, msgBoxFunc()), (msgBoxFuncName, 'abc')) 224 | 225 | # entering text, tabbing to the Ok key 226 | t = KeyPresses(['a', 'b', 'c', 'tab', 'enter']) 227 | t.start() 228 | print('Line', inspect.currentframe().f_lineno) 229 | self.assertEqual((msgBoxFuncName, msgBoxFunc()), (msgBoxFuncName, 'abc')) 230 | 231 | # entering text but hitting cancel 232 | t = KeyPresses(['a', 'b', 'c', 'tab', 'tab', 'enter']) 233 | t.start() 234 | print('Line', inspect.currentframe().f_lineno) 235 | self.assertEqual((msgBoxFuncName, msgBoxFunc()), (msgBoxFuncName, None)) 236 | 237 | # with text 238 | t = KeyPresses(['a', 'b', 'c', 'enter']) 239 | t.start() 240 | print('Line', inspect.currentframe().f_lineno) 241 | self.assertEqual((msgBoxFuncName, msgBoxFunc('Hello')), (msgBoxFuncName, 'abc')) 242 | 243 | # with text and title 244 | t = KeyPresses(['a', 'b', 'c', 'enter']) 245 | t.start() 246 | print('Line', inspect.currentframe().f_lineno) 247 | self.assertEqual((msgBoxFuncName, msgBoxFunc('Hello', 'Title')), (msgBoxFuncName, 'abc')) 248 | 249 | # with text, title and default value 250 | t = KeyPresses(['enter']) 251 | t.start() 252 | print('Line', inspect.currentframe().f_lineno) 253 | self.assertEqual((msgBoxFuncName, msgBoxFunc('Hello', 'Title', 'default')), (msgBoxFuncName, 'default')) 254 | 255 | # with text, title and default value specified by keyword arguments 256 | t = KeyPresses(['enter']) 257 | t.start() 258 | print('Line', inspect.currentframe().f_lineno) 259 | self.assertEqual((msgBoxFuncName, msgBoxFunc(text='Hello', title='Title', default='default')), (msgBoxFuncName, 'default')) 260 | 261 | class TimeoutTests(unittest.TestCase): 262 | def test_timeout(self): 263 | # Note: If these test's fail, the unit tests will hang. 264 | self.assertEqual(pymsgbox._alertTkinter('timeout test', timeout=300), pymsgbox.TIMEOUT_RETURN_VALUE) 265 | self.assertEqual(pymsgbox._confirmTkinter('timeout test', timeout=300), pymsgbox.TIMEOUT_RETURN_VALUE) 266 | self.assertEqual(pymsgbox.prompt('timeout test', timeout=300), pymsgbox.TIMEOUT_RETURN_VALUE) 267 | self.assertEqual(pymsgbox.password('timeout test', timeout=300), pymsgbox.TIMEOUT_RETURN_VALUE) 268 | 269 | 270 | """" 271 | # NOTE: This is weird. This test fails (the additional typed in text gets added 272 | # to the end of the default string, instead of replacing it), but when I run 273 | # this same code using PyAutoGUI from the interactive shell (on Win7 Py3.3) it 274 | # works. It also works when I type it in myself. 275 | # Commenting this out for now. 276 | 277 | class DefaultValueOverwriteTests(unittest.TestCase): 278 | def test_prompt(self): 279 | self._prompt_and_password_tests(pymsgbox.prompt, 'prompt()') 280 | 281 | def test_password(self): 282 | # NOTE: Currently there is no way to test the appearance of the * or custom mask characters. 283 | self._prompt_and_password_tests(pymsgbox.password, 'password()') 284 | 285 | def _prompt_and_password_tests(self, msgBoxFunc, msgBoxFuncName): 286 | # with text, title and default value that is typed over 287 | t = KeyPresses(['a', 'b', 'c', 'enter']) 288 | t.start() 289 | print('Line', inspect.currentframe().f_lineno); self.assertEqual((msgBoxFuncName, msgBoxFunc('Hello', 'Title', 'default')), (msgBoxFuncName, 'abc')) 290 | """ 291 | 292 | 293 | class WindowsNativeAlertTests(unittest.TestCase): 294 | def test_alert(self): 295 | if sys.platform != 'win32': 296 | return 297 | 298 | # TODO - We need some way of determining if the tkinter or native message box appeared. 299 | 300 | # test passing True for _tkinter 301 | t = KeyPresses(['enter']) 302 | t.start() 303 | print('Line', inspect.currentframe().f_lineno) 304 | self.assertEqual(pymsgbox.alert(_tkinter=True), pymsgbox.OK_TEXT) 305 | 306 | # test passing timeout 307 | t = KeyPresses(['enter']) 308 | t.start() 309 | print('Line', inspect.currentframe().f_lineno) 310 | self.assertEqual(pymsgbox.alert(timeout=300), pymsgbox.OK_TEXT) 311 | 312 | # test non-ok button to check that it falls back to tkinter 313 | t = KeyPresses(['enter']) 314 | t.start() 315 | print('Line', inspect.currentframe().f_lineno) 316 | self.assertEqual(pymsgbox.alert(button='Test'), 'Test') 317 | 318 | 319 | class WindowsNativeConfirmTests(unittest.TestCase): 320 | def test_confirm(self): 321 | if sys.platform != 'win32': 322 | return 323 | 324 | # TODO - We need some way of determining if the tkinter or native message box appeared. 325 | 326 | # press enter on OK 327 | #t = KeyPresses(['enter']) 328 | #t.start() 329 | #print('Line', inspect.currentframe().f_lineno) 330 | #self.assertEqual(pymsgbox.confirm(), pymsgbox.OK_TEXT) 331 | 332 | 333 | if __name__ == '__main__': 334 | unittest.main() 335 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # tox (https://tox.readthedocs.io/) is a tool for running tests 2 | # in multiple virtualenvs. This configuration file will run the 3 | # test suite on all supported python versions. To use it, "pip install tox" 4 | # and then run "tox" from this directory. 5 | 6 | [tox] 7 | envlist = py34, py35, py36, py37 8 | 9 | [testenv] 10 | deps = 11 | pytest 12 | commands = 13 | pytest 14 | --------------------------------------------------------------------------------