├── README.md
├── cookiecutter.json
├── hooks
└── post_gen_project.py
└── {{cookiecutter.project_slug}}
├── .editorconfig
├── .github
└── ISSUE_TEMPLATE.md
├── .gitignore
├── .travis.yml
├── AUTHORS.rst
├── CONTRIBUTING.rst
├── HISTORY.rst
├── LICENSE
├── MANIFEST.in
├── Makefile
├── README.rst
├── demo
├── customTkinter_
│ ├── complex_example.py
│ └── sample.mp4
├── kivy_
│ ├── README.txt
│ ├── android.txt
│ ├── data
│ │ ├── background.png
│ │ ├── faust_github.jpg
│ │ ├── icons
│ │ │ ├── README
│ │ │ ├── bug.png
│ │ │ ├── chevron-left.png
│ │ │ └── chevron-right.png
│ │ └── screens
│ │ │ ├── accordions.kv
│ │ │ ├── bubbles.kv
│ │ │ ├── buttons.kv
│ │ │ ├── carousel.kv
│ │ │ ├── checkboxes.kv
│ │ │ ├── codeinput.kv
│ │ │ ├── dropdown.kv
│ │ │ ├── filechoosers.kv
│ │ │ ├── popups.kv
│ │ │ ├── progressbar.kv
│ │ │ ├── rstdocument.kv
│ │ │ ├── scatter.kv
│ │ │ ├── screenmanager.kv
│ │ │ ├── sliders.kv
│ │ │ ├── spinner.kv
│ │ │ ├── splitter.kv
│ │ │ ├── switches.kv
│ │ │ ├── tabbedpanel + layouts.kv
│ │ │ ├── textinputs.kv
│ │ │ └── togglebutton.kv
│ ├── main.py
│ ├── requirements.txt
│ ├── sample.gif
│ └── showcase.kv
├── pysimplegui_
│ ├── LICENSE
│ ├── README.md
│ ├── SimpleGUIBuilder.gif
│ ├── layout_parser.py
│ ├── requirements.txt
│ ├── simple_gui_builder.py
│ └── tree_node.py
├── tkinter_
│ ├── README.md
│ ├── icons
│ │ ├── align-center.png
│ │ ├── align-justify.png
│ │ ├── align-left.png
│ │ ├── align-right.png
│ │ ├── bold.png
│ │ ├── copy.png
│ │ ├── cut.png
│ │ ├── find.png
│ │ ├── font-color.png
│ │ ├── highlight.png
│ │ ├── italic.png
│ │ ├── new.png
│ │ ├── open.png
│ │ ├── paste.png
│ │ ├── redo.png
│ │ ├── save.png
│ │ ├── strike.png
│ │ ├── underline.png
│ │ └── undo.png
│ ├── main.py
│ ├── requirements.txt
│ └── text-editor.png
├── toga_
│ ├── sample.gif
│ └── togablogpost
│ │ ├── .gitignore
│ │ ├── CHANGELOG
│ │ ├── LICENSE
│ │ ├── README.rst
│ │ ├── pyproject.toml
│ │ ├── src
│ │ └── togablogpost
│ │ │ ├── __init__.py
│ │ │ ├── __main__.py
│ │ │ ├── app.py
│ │ │ ├── english_words.txt
│ │ │ ├── hangman_images
│ │ │ ├── death.PNG
│ │ │ ├── hangman-0.PNG
│ │ │ ├── hangman-1.PNG
│ │ │ ├── hangman-2.PNG
│ │ │ ├── hangman-3.PNG
│ │ │ ├── hangman-4.PNG
│ │ │ ├── hangman-5.PNG
│ │ │ ├── hangman-6.PNG
│ │ │ └── victory.PNG
│ │ │ └── resources
│ │ │ ├── __init__.py
│ │ │ ├── togablogpost.icns
│ │ │ ├── togablogpost.ico
│ │ │ └── togablogpost.png
│ │ └── tests
│ │ ├── __init__.py
│ │ ├── test_app.py
│ │ └── togablogpost.py
├── wxPython_
│ ├── README.md
│ ├── demo.gif
│ ├── main.py
│ └── requirements.txt
└── {{cookiecutter.gui_framework}}
│ ├── README.md
│ ├── generator.py
│ ├── main.py
│ ├── main_window.ui
│ ├── my_theme.xml
│ └── requirements.txt
├── docs
├── Makefile
├── authors.rst
├── conf.py
├── contributing.rst
├── history.rst
├── index.rst
├── installation.rst
├── make.bat
├── readme.rst
└── usage.rst
├── pyproject.toml
├── requirements_dev.txt
├── setup.cfg
├── setup.py
├── tests
└── __init__.py
├── tox.ini
└── {{cookiecutter.project_slug}}
├── __init__.py
└── {{cookiecutter.project_slug}}.py
/README.md:
--------------------------------------------------------------------------------
1 | Cookiecutter Python GUI Application
2 | ===================================
3 |
4 | Cookiecutter template for Python GUI Applications based on cookiecutter-pypackage: https://github.com/audreyfeldroy/cookiecutter-pypackage.
5 |
6 | This template is mostly intended for beginners in GUI development using Python, but it can be used by experienced developers also.
7 |
8 | Besides providing the template and the needed tools for GUI development, this repository also comes with demos for most used python GUI frameworks, in order to get you started, if you are a beginner.
9 |
10 | Features
11 | -----
12 | * Free software: BSD license
13 | * Dependency tracking using [poetry](https://python-poetry.org/ "poetry")
14 | * [Pytest](http://pytest.org/ "Pytest") runner: Supports `unittest`, `pytest`, `nose` style tests and more
15 | * [Travis-CI](http://travis-ci.org/"Travis-CI"): Ready for Travis Continuous integration testing
16 | * [Tox](http://testrun.org/tox/ "Tox") testing: Setup to easily test for python 2.6, 2.7, 3.3 and PyPy_
17 | * [Sphinx](http://sphinx-doc.org/ "Sphinx") docs: Documentation ready for generation with, for example, [ReadTheDocs](https://readthedocs.org/ "ReadTheDocs")
18 | * [Wheel](http://pythonwheels.com "Wheel") support: Use the newest python package distribution standard from the get go
19 |
20 |
21 | Supported GUI Frameworks
22 | -----
23 | * [PyQt5 and PyQt6](https://www.riverbankcomputing.com/static/Docs/PyQt6 "PyQt5 and PyQt6")
24 | * [PySide2 and PySide6](https://doc.qt.io/qtforpython "PySide2 and PySide6")
25 | * [tkinter](https://docs.python.org/3/library/tk.html "tkinter") and [CustomTkinter](https://github.com/TomSchimansky/CustomTkinter)
26 | * TODO: Add [DearPyGui](https://github.com/hoffstadt/DearPyGui)
27 | * [PySimpleGUI](https://www.pysimplegui.org/en/latest/ "PySimpleGUI")
28 | * [wxPython](https://www.wxpython.org/ "wxPython")
29 | * TODO: Add [DelphiFMX4Python](https://pypi.org/project/delphifmx/) and [DelphiVCL](https://pypi.org/project/delphivcl/)
30 | * [Toga](https://github.com/beeware/toga "Toga")
31 | * TODO: Add [PyForms](https://github.com/UmSenhorQualquer/pyforms-gui)
32 | * [Kivy](https://kivy.org/doc/stable "Kivy")
33 | * TODO: Add [libavg](https://www.libavg.de/site/projects/libavg/wiki/ReleaseInstall)
34 |
35 | Demos:
36 | -----
37 | * Qt family (PyQt5, PyQt6, PySide2, PySide6):
38 |
39 | 
40 |
41 |
42 | * Tkinter:
43 |
44 | 
45 |
46 | * CustomTkinter:
47 |
48 | https://github.com/MihailCosmin/cookiecutter-python-gui-application/assets/14019626/84625eb8-f643-47bc-b8e4-b7c6adc1fd06
49 |
50 | * PySimpleGUI:
51 |
52 | 
53 |
54 | * wxPython:
55 |
56 | 
57 |
58 |
59 | * [Toga](https://github.com/Depot-Analytics/toga-hangman):
60 |
61 | 
62 |
63 | * Kivy:
64 |
65 | 
66 |
67 |
68 | Frameworks Comparison
69 | -----
70 | TODO: Add comparison table, main features
71 |
72 | Usage
73 | -----
74 |
75 | Generate a Python GUI Application project:
76 |
77 | cookiecutter https://github.com/MihailCosmin/cookiecutter-python-gui-application.git
78 |
79 | Not Exactly What You Want?
80 | --------------------------
81 |
82 | Don't worry, you have options:
83 |
84 | Similar Cookiecutter Templates
85 | ------------------------------
86 |
87 | * [audreyr/cookiecutter-pypackage](https://github.com/audreyfeldroy/cookiecutter-pypackage "audreyr/cookiecutter-pypackage"): The original pypackage.
88 |
89 | Fork This
90 | ---------
91 |
92 | If you have differences in your preferred setup, I encourage you to fork this
93 | to create your own version. Once you have your fork working, add it to the
94 | Similar Cookiecutter Templates list with a brief explanation. It's up to you
95 | whether or not to rename your fork.
96 |
97 | Or Submit a Pull Request
98 | ------------------------
99 |
100 | I also accept pull requests on this, if they're small, atomic, and if they
101 | make my own packaging experience better.
102 |
--------------------------------------------------------------------------------
/cookiecutter.json:
--------------------------------------------------------------------------------
1 | {
2 | "full_name": "Cosmin Munteanu",
3 | "email": "cosmin@munteanu.com",
4 | "github_username": "MihailCosmin",
5 | "project_name": "Python GUI Application",
6 | "project_slug": "{{ cookiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}",
7 | "project_short_description": "Python template that contains all the things you need to create a Python GUI Application.",
8 | "pypi_username": "{{ cookiecutter.github_username }}",
9 | "version": "0.1.0",
10 | "gui_framework": ["PyQt5", "PyQt6", "PySide2", "PySide6", "Tkinter", "customTkinter", "PySimpleGUI", "wxPython", "Toga", "Kivy", "None - Will decide later"],
11 | "use_poetry": "y",
12 | "use_pytest": "y",
13 | "use_pypi_deployment_with_travis": "y",
14 | "add_pyup_badge": "y",
15 | "create_author_file": "y",
16 | "open_source_license": ["MIT license", "BSD license", "ISC license", "Apache Software License 2.0", "GNU General Public License v3", "Not open source"],
17 | "keep_demo": "y"
18 | }
19 |
--------------------------------------------------------------------------------
/hooks/post_gen_project.py:
--------------------------------------------------------------------------------
1 | # cat post_gen_project.py
2 | import os
3 | import shutil
4 |
5 | print(os.getcwd()) # prints /absolute/path/to/{{cookiecutter.project_slug}}
6 |
7 | def remove(filepath: str):
8 | """Remove a file or a directory.
9 |
10 | Args:
11 | filepath (str): Path to the file or directory to remove.
12 | """
13 | if os.path.isfile(filepath):
14 | os.remove(filepath)
15 | elif os.path.isdir(filepath):
16 | shutil.rmtree(filepath)
17 |
18 | use_poetry = '{{cookiecutter.use_poetry}}' == 'y'
19 | keep_demo = '{{cookiecutter.keep_demo}}' == 'y'
20 |
21 | if not use_poetry:
22 | # remove top-level file inside the generated folder
23 | remove('pyproject.toml')
24 |
25 | if not keep_demo:
26 | # remove top-level file inside the generated folder
27 | remove('demo')
28 | else:
29 | if '{{cookiecutter.gui_framework}}'.startswith('PySide') or '{{cookiecutter.gui_framework}}'.startswith('PyQt'):
30 | remove('demo/toga_')
31 | remove('demo/kivy_')
32 | remove('demo/tkinter_')
33 | remove('demo/wxPython_')
34 | remove('demo/pysimplegui_')
35 | remove('demo/customTkinter_')
36 |
37 | if '{{cookiecutter.gui_framework}}' == 'Tkinter':
38 | remove('demo/Tkinter')
39 | os.rename('demo/tkinter_', 'demo/tkinter')
40 | remove('demo/toga_')
41 | remove('demo/kivy_')
42 | remove('demo/wxPython_')
43 | remove('demo/pysimplegui_')
44 | remove('demo/customTkinter_')
45 |
46 | if '{{cookiecutter.gui_framework}}' == 'customTkinter':
47 | remove('demo/customTkinter')
48 | os.rename('demo/customTkinter_', 'demo/customTkinter')
49 | remove('demo/toga_')
50 | remove('demo/kivy_')
51 | remove('demo/tkinter_')
52 | remove('demo/wxPython_')
53 | remove('demo/pysimplegui_')
54 |
55 | if '{{cookiecutter.gui_framework}}' == 'Kivy':
56 | remove('demo/Kivy')
57 | os.rename('demo/kivy_', 'demo/kivy')
58 | remove('demo/toga_')
59 | remove('demo/tkinter_')
60 | remove('demo/wxPython_')
61 | remove('demo/pysimplegui_')
62 | remove('demo/customTkinter_')
63 |
64 | if '{{cookiecutter.gui_framework}}' == 'wxPython':
65 | remove('demo/wxPython')
66 | os.rename('demo/wxPython_', 'demo/wxPython')
67 | remove('demo/toga_')
68 | remove('demo/kivy_')
69 | remove('demo/tkinter_')
70 | remove('demo/pysimplegui_')
71 | remove('demo/customTkinter_')
72 |
73 | if '{{cookiecutter.gui_framework}}' == 'PySimpleGUI':
74 | remove('demo/PySimpleGUI')
75 | os.rename('demo/pysimplegui_', 'demo/PySimpleGUI')
76 | remove('demo/toga_')
77 | remove('demo/kivy_')
78 | remove('demo/tkinter_')
79 | remove('demo/wxPython_')
80 | remove('demo/customTkinter_')
81 |
82 | if '{{cookiecutter.gui_framework}}' == 'Toga':
83 | remove('demo/Toga')
84 | os.rename('demo/toga_', 'demo/Toga')
85 | remove('demo/kivy_')
86 | remove('demo/tkinter_')
87 | remove('demo/wxPython_')
88 | remove('demo/pysimplegui_')
89 | remove('demo/customTkinter_')
90 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | indent_style = space
7 | indent_size = 4
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 | charset = utf-8
11 | end_of_line = lf
12 |
13 | [*.bat]
14 | indent_style = tab
15 | end_of_line = crlf
16 |
17 | [LICENSE]
18 | insert_final_newline = false
19 |
20 | [Makefile]
21 | indent_style = tab
22 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | * {{ cookiecutter.project_name }} version:
2 | * Python version:
3 | * Operating System:
4 |
5 | ### Description
6 |
7 | Describe what you were trying to get done.
8 | Tell us what happened, what went wrong, and what you expected to happen.
9 |
10 | ### What I Did
11 |
12 | ```
13 | Paste the command(s) you ran and the output.
14 | If there was a crash, please include the traceback here.
15 | ```
16 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 |
28 | # PyInstaller
29 | # Usually these files are written by a python script from a template
30 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
31 | *.manifest
32 | *.spec
33 |
34 | # Installer logs
35 | pip-log.txt
36 | pip-delete-this-directory.txt
37 |
38 | # Unit test / coverage reports
39 | htmlcov/
40 | .tox/
41 | .coverage
42 | .coverage.*
43 | .cache
44 | nosetests.xml
45 | coverage.xml
46 | *.cover
47 | .hypothesis/
48 | .pytest_cache/
49 |
50 | # Translations
51 | *.mo
52 | *.pot
53 |
54 | # Django stuff:
55 | *.log
56 | local_settings.py
57 |
58 | # Flask stuff:
59 | instance/
60 | .webassets-cache
61 |
62 | # Scrapy stuff:
63 | .scrapy
64 |
65 | # Sphinx documentation
66 | docs/_build/
67 |
68 | # PyBuilder
69 | target/
70 |
71 | # Jupyter Notebook
72 | .ipynb_checkpoints
73 |
74 | # pyenv
75 | .python-version
76 |
77 | # celery beat schedule file
78 | celerybeat-schedule
79 |
80 | # SageMath parsed files
81 | *.sage.py
82 |
83 | # dotenv
84 | .env
85 |
86 | # virtualenv
87 | .venv
88 | venv/
89 | ENV/
90 |
91 | # Spyder project settings
92 | .spyderproject
93 | .spyproject
94 |
95 | # Rope project settings
96 | .ropeproject
97 |
98 | # mkdocs documentation
99 | /site
100 |
101 | # mypy
102 | .mypy_cache/
103 |
104 | # IDE settings
105 | .vscode/
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/.travis.yml:
--------------------------------------------------------------------------------
1 | # Config file for automatic testing at travis-ci.com
2 |
3 | language: python
4 | python:
5 | - 3.8
6 | - 3.7
7 | - 3.6
8 |
9 | # Command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors
10 | install: pip install -U tox-travis
11 |
12 | # Command to run tests, e.g. python setup.py test
13 | script: tox
14 |
15 | {% if cookiecutter.use_pypi_deployment_with_travis == 'y' -%}
16 | # Assuming you have installed the travis-ci CLI tool, after you
17 | # create the Github repo and add it to Travis, run the
18 | # following command to finish PyPI deployment setup:
19 | # $ travis encrypt --add deploy.password
20 | deploy:
21 | provider: pypi
22 | distributions: sdist bdist_wheel
23 | user: {{ cookiecutter.pypi_username }}
24 | password:
25 | secure: PLEASE_REPLACE_ME
26 | on:
27 | tags: true
28 | repo: {{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }}
29 | python: 3.8
30 | {%- endif %}
31 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/AUTHORS.rst:
--------------------------------------------------------------------------------
1 | =======
2 | Credits
3 | =======
4 |
5 | Development Lead
6 | ----------------
7 |
8 | * {{ cookiecutter.full_name }} <{{ cookiecutter.email }}>
9 |
10 | Contributors
11 | ------------
12 |
13 | None yet. Why not be the first?
14 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/CONTRIBUTING.rst:
--------------------------------------------------------------------------------
1 | .. highlight:: shell
2 |
3 | ============
4 | Contributing
5 | ============
6 |
7 | Contributions are welcome, and they are greatly appreciated! Every little bit
8 | helps, and credit will always be given.
9 |
10 | You can contribute in many ways:
11 |
12 | Types of Contributions
13 | ----------------------
14 |
15 | Report Bugs
16 | ~~~~~~~~~~~
17 |
18 | Report bugs at https://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }}/issues.
19 |
20 | If you are reporting a bug, please include:
21 |
22 | * Your operating system name and version.
23 | * Any details about your local setup that might be helpful in troubleshooting.
24 | * Detailed steps to reproduce the bug.
25 |
26 | Fix Bugs
27 | ~~~~~~~~
28 |
29 | Look through the GitHub issues for bugs. Anything tagged with "bug" and "help
30 | wanted" is open to whoever wants to implement it.
31 |
32 | Implement Features
33 | ~~~~~~~~~~~~~~~~~~
34 |
35 | Look through the GitHub issues for features. Anything tagged with "enhancement"
36 | and "help wanted" is open to whoever wants to implement it.
37 |
38 | Write Documentation
39 | ~~~~~~~~~~~~~~~~~~~
40 |
41 | {{ cookiecutter.project_name }} could always use more documentation, whether as part of the
42 | official {{ cookiecutter.project_name }} docs, in docstrings, or even on the web in blog posts,
43 | articles, and such.
44 |
45 | Submit Feedback
46 | ~~~~~~~~~~~~~~~
47 |
48 | The best way to send feedback is to file an issue at https://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }}/issues.
49 |
50 | If you are proposing a feature:
51 |
52 | * Explain in detail how it would work.
53 | * Keep the scope as narrow as possible, to make it easier to implement.
54 | * Remember that this is a volunteer-driven project, and that contributions
55 | are welcome :)
56 |
57 | Get Started!
58 | ------------
59 |
60 | Ready to contribute? Here's how to set up `{{ cookiecutter.project_slug }}` for local development.
61 |
62 | 1. Fork the `{{ cookiecutter.project_slug }}` repo on GitHub.
63 | 2. Clone your fork locally::
64 |
65 | $ git clone git@github.com:your_name_here/{{ cookiecutter.project_slug }}.git
66 |
67 | 3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development::
68 |
69 | $ mkvirtualenv {{ cookiecutter.project_slug }}
70 | $ cd {{ cookiecutter.project_slug }}/
71 | $ python setup.py develop
72 |
73 | 4. Create a branch for local development::
74 |
75 | $ git checkout -b name-of-your-bugfix-or-feature
76 |
77 | Now you can make your changes locally.
78 |
79 | 5. When you're done making changes, check that your changes pass flake8 and the
80 | tests, including testing other Python versions with tox::
81 |
82 | $ flake8 {{ cookiecutter.project_slug }} tests
83 | $ python setup.py test or pytest
84 | $ tox
85 |
86 | To get flake8 and tox, just pip install them into your virtualenv.
87 |
88 | 6. Commit your changes and push your branch to GitHub::
89 |
90 | $ git add .
91 | $ git commit -m "Your detailed description of your changes."
92 | $ git push origin name-of-your-bugfix-or-feature
93 |
94 | 7. Submit a pull request through the GitHub website.
95 |
96 | Pull Request Guidelines
97 | -----------------------
98 |
99 | Before you submit a pull request, check that it meets these guidelines:
100 |
101 | 1. The pull request should include tests.
102 | 2. If the pull request adds functionality, the docs should be updated. Put
103 | your new functionality into a function with a docstring, and add the
104 | feature to the list in README.rst.
105 | 3. The pull request should work for Python 3.5, 3.6, 3.7 and 3.8, and for PyPy. Check
106 | https://travis-ci.com/{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }}/pull_requests
107 | and make sure that the tests pass for all supported Python versions.
108 |
109 | Tips
110 | ----
111 |
112 | To run a subset of tests::
113 |
114 | {% if cookiecutter.use_pytest == 'y' -%}
115 | $ pytest tests.test_{{ cookiecutter.project_slug }}
116 | {% else %}
117 | $ python -m unittest tests.test_{{ cookiecutter.project_slug }}
118 | {%- endif %}
119 |
120 | Deploying
121 | ---------
122 |
123 | A reminder for the maintainers on how to deploy.
124 | Make sure all your changes are committed (including an entry in HISTORY.rst).
125 | Then run::
126 |
127 | $ bump2version patch # possible: major / minor / patch
128 | $ git push
129 | $ git push --tags
130 |
131 | Travis will then deploy to PyPI if tests pass.
132 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/HISTORY.rst:
--------------------------------------------------------------------------------
1 | =======
2 | History
3 | =======
4 |
5 | {{ cookiecutter.version }} ({% now 'local' %})
6 | ------------------
7 |
8 | * First release on PyPI.
9 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/LICENSE:
--------------------------------------------------------------------------------
1 | {% if cookiecutter.open_source_license == 'MIT license' -%}
2 | MIT License
3 |
4 | Copyright (c) {% now 'local', '%Y' %}, {{ cookiecutter.full_name }}
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | {% elif cookiecutter.open_source_license == 'BSD license' %}
24 |
25 | BSD License
26 |
27 | Copyright (c) {% now 'local', '%Y' %}, {{ cookiecutter.full_name }}
28 | All rights reserved.
29 |
30 | Redistribution and use in source and binary forms, with or without modification,
31 | are permitted provided that the following conditions are met:
32 |
33 | * Redistributions of source code must retain the above copyright notice, this
34 | list of conditions and the following disclaimer.
35 |
36 | * Redistributions in binary form must reproduce the above copyright notice, this
37 | list of conditions and the following disclaimer in the documentation and/or
38 | other materials provided with the distribution.
39 |
40 | * Neither the name of the copyright holder nor the names of its
41 | contributors may be used to endorse or promote products derived from this
42 | software without specific prior written permission.
43 |
44 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
45 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
46 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
47 | IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
48 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
49 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
50 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
51 | OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
52 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
53 | OF THE POSSIBILITY OF SUCH DAMAGE.
54 | {% elif cookiecutter.open_source_license == 'ISC license' -%}
55 | ISC License
56 |
57 | Copyright (c) {% now 'local', '%Y' %}, {{ cookiecutter.full_name }}
58 |
59 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
60 |
61 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
62 | {% elif cookiecutter.open_source_license == 'Apache Software License 2.0' -%}
63 | Apache Software License 2.0
64 |
65 | Copyright (c) {% now 'local', '%Y' %}, {{ cookiecutter.full_name }}
66 |
67 | Licensed under the Apache License, Version 2.0 (the "License");
68 | you may not use this file except in compliance with the License.
69 | You may obtain a copy of the License at
70 |
71 | http://www.apache.org/licenses/LICENSE-2.0
72 |
73 | Unless required by applicable law or agreed to in writing, software
74 | distributed under the License is distributed on an "AS IS" BASIS,
75 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
76 | See the License for the specific language governing permissions and
77 | limitations under the License.
78 | {% elif cookiecutter.open_source_license == 'GNU General Public License v3' -%}
79 | GNU GENERAL PUBLIC LICENSE
80 | Version 3, 29 June 2007
81 |
82 | {{ cookiecutter.project_short_description }}
83 | Copyright (C) {% now 'local', '%Y' %} {{ cookiecutter.full_name }}
84 |
85 | This program is free software: you can redistribute it and/or modify
86 | it under the terms of the GNU General Public License as published by
87 | the Free Software Foundation, either version 3 of the License, or
88 | (at your option) any later version.
89 |
90 | This program is distributed in the hope that it will be useful,
91 | but WITHOUT ANY WARRANTY; without even the implied warranty of
92 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
93 | GNU General Public License for more details.
94 |
95 | You should have received a copy of the GNU General Public License
96 | along with this program. If not, see .
97 |
98 | Also add information on how to contact you by electronic and paper mail.
99 |
100 | You should also get your employer (if you work as a programmer) or school,
101 | if any, to sign a "copyright disclaimer" for the program, if necessary.
102 | For more information on this, and how to apply and follow the GNU GPL, see
103 | .
104 |
105 | The GNU General Public License does not permit incorporating your program
106 | into proprietary programs. If your program is a subroutine library, you
107 | may consider it more useful to permit linking proprietary applications with
108 | the library. If this is what you want to do, use the GNU Lesser General
109 | Public License instead of this License. But first, please read
110 | .
111 | {% endif %}
112 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/MANIFEST.in:
--------------------------------------------------------------------------------
1 | {% if cookiecutter.create_author_file == 'y' -%}
2 | include AUTHORS.rst
3 | {% endif -%}
4 | include CONTRIBUTING.rst
5 | include HISTORY.rst
6 | include LICENSE
7 | include README.rst
8 |
9 | recursive-include tests *
10 | recursive-exclude * __pycache__
11 | recursive-exclude * *.py[co]
12 |
13 | recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif
14 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: clean clean-test clean-pyc clean-build docs help
2 | .DEFAULT_GOAL := help
3 |
4 | define BROWSER_PYSCRIPT
5 | import os, webbrowser, sys
6 |
7 | from urllib.request import pathname2url
8 |
9 | webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1])))
10 | endef
11 | export BROWSER_PYSCRIPT
12 |
13 | define PRINT_HELP_PYSCRIPT
14 | import re, sys
15 |
16 | for line in sys.stdin:
17 | match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line)
18 | if match:
19 | target, help = match.groups()
20 | print("%-20s %s" % (target, help))
21 | endef
22 | export PRINT_HELP_PYSCRIPT
23 |
24 | BROWSER := python -c "$$BROWSER_PYSCRIPT"
25 |
26 | help:
27 | @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST)
28 |
29 | clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts
30 |
31 | clean-build: ## remove build artifacts
32 | rm -fr build/
33 | rm -fr dist/
34 | rm -fr .eggs/
35 | find . -name '*.egg-info' -exec rm -fr {} +
36 | find . -name '*.egg' -exec rm -f {} +
37 |
38 | clean-pyc: ## remove Python file artifacts
39 | find . -name '*.pyc' -exec rm -f {} +
40 | find . -name '*.pyo' -exec rm -f {} +
41 | find . -name '*~' -exec rm -f {} +
42 | find . -name '__pycache__' -exec rm -fr {} +
43 |
44 | clean-test: ## remove test and coverage artifacts
45 | rm -fr .tox/
46 | rm -f .coverage
47 | rm -fr htmlcov/
48 | rm -fr .pytest_cache
49 |
50 | lint: ## check style with flake8
51 | flake8 {{ cookiecutter.project_slug }} tests
52 |
53 | test: ## run tests quickly with the default Python
54 | {%- if cookiecutter.use_pytest == 'y' %}
55 | pytest
56 | {%- else %}
57 | python setup.py test
58 | {%- endif %}
59 |
60 | test-all: ## run tests on every Python version with tox
61 | tox
62 |
63 | coverage: ## check code coverage quickly with the default Python
64 | {%- if cookiecutter.use_pytest == 'y' %}
65 | coverage run --source {{ cookiecutter.project_slug }} -m pytest
66 | {%- else %}
67 | coverage run --source {{ cookiecutter.project_slug }} setup.py test
68 | {%- endif %}
69 | coverage report -m
70 | coverage html
71 | $(BROWSER) htmlcov/index.html
72 |
73 | docs: ## generate Sphinx HTML documentation, including API docs
74 | rm -f docs/{{ cookiecutter.project_slug }}.rst
75 | rm -f docs/modules.rst
76 | sphinx-apidoc -o docs/ {{ cookiecutter.project_slug }}
77 | $(MAKE) -C docs clean
78 | $(MAKE) -C docs html
79 | $(BROWSER) docs/_build/html/index.html
80 |
81 | servedocs: docs ## compile the docs watching for changes
82 | watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D .
83 |
84 | release: dist ## package and upload a release
85 | twine upload dist/*
86 |
87 | dist: clean ## builds source and wheel package
88 | python setup.py sdist
89 | python setup.py bdist_wheel
90 | ls -l dist
91 |
92 | install: clean ## install the package to the active Python's site-packages
93 | python setup.py install
94 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/README.rst:
--------------------------------------------------------------------------------
1 | {% set is_open_source = cookiecutter.open_source_license != 'Not open source' -%}
2 | {% for _ in cookiecutter.project_name %}={% endfor %}
3 | {{ cookiecutter.project_name }}
4 | {% for _ in cookiecutter.project_name %}={% endfor %}
5 |
6 | {% if is_open_source %}
7 | .. image:: https://img.shields.io/pypi/v/{{ cookiecutter.project_slug }}.svg
8 | :target: https://pypi.python.org/pypi/{{ cookiecutter.project_slug }}
9 |
10 | .. image:: https://img.shields.io/travis/{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }}.svg
11 | :target: https://travis-ci.com/{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }}
12 |
13 | .. image:: https://readthedocs.org/projects/{{ cookiecutter.project_slug | replace("_", "-") }}/badge/?version=latest
14 | :target: https://{{ cookiecutter.project_slug | replace("_", "-") }}.readthedocs.io/en/latest/?version=latest
15 | :alt: Documentation Status
16 | {%- endif %}
17 |
18 | {% if cookiecutter.add_pyup_badge == 'y' %}
19 | .. image:: https://pyup.io/repos/github/{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }}/shield.svg
20 | :target: https://pyup.io/repos/github/{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }}/
21 | :alt: Updates
22 | {% endif %}
23 |
24 |
25 | {{ cookiecutter.project_short_description }}
26 |
27 | {% if is_open_source %}
28 | * Free software: {{ cookiecutter.open_source_license }}
29 | * Documentation: https://{{ cookiecutter.project_slug | replace("_", "-") }}.readthedocs.io.
30 | {% endif %}
31 |
32 | Features
33 | --------
34 |
35 | * TODO
36 |
37 | Credits
38 | -------
39 |
40 | This package was created with Cookiecutter_ and the `audreyr/cookiecutter-pypackage`_ project template.
41 |
42 | .. _Cookiecutter: https://github.com/audreyr/cookiecutter
43 | .. _`audreyr/cookiecutter-pypackage`: https://github.com/audreyr/cookiecutter-pypackage
44 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/customTkinter_/complex_example.py:
--------------------------------------------------------------------------------
1 | import tkinter
2 | import tkinter.messagebox
3 | import customtkinter
4 |
5 | customtkinter.set_appearance_mode("System") # Modes: "System" (standard), "Dark", "Light"
6 | customtkinter.set_default_color_theme("blue") # Themes: "blue" (standard), "green", "dark-blue"
7 |
8 |
9 | class App(customtkinter.CTk):
10 | def __init__(self):
11 | super().__init__()
12 |
13 | # configure window
14 | self.title("CustomTkinter complex_example.py")
15 | self.geometry(f"{1100}x{580}")
16 |
17 | # configure grid layout (4x4)
18 | self.grid_columnconfigure(1, weight=1)
19 | self.grid_columnconfigure((2, 3), weight=0)
20 | self.grid_rowconfigure((0, 1, 2), weight=1)
21 |
22 | # create sidebar frame with widgets
23 | self.sidebar_frame = customtkinter.CTkFrame(self, width=140, corner_radius=0)
24 | self.sidebar_frame.grid(row=0, column=0, rowspan=4, sticky="nsew")
25 | self.sidebar_frame.grid_rowconfigure(4, weight=1)
26 | self.logo_label = customtkinter.CTkLabel(self.sidebar_frame, text="CustomTkinter", font=customtkinter.CTkFont(size=20, weight="bold"))
27 | self.logo_label.grid(row=0, column=0, padx=20, pady=(20, 10))
28 | self.sidebar_button_1 = customtkinter.CTkButton(self.sidebar_frame, command=self.sidebar_button_event)
29 | self.sidebar_button_1.grid(row=1, column=0, padx=20, pady=10)
30 | self.sidebar_button_2 = customtkinter.CTkButton(self.sidebar_frame, command=self.sidebar_button_event)
31 | self.sidebar_button_2.grid(row=2, column=0, padx=20, pady=10)
32 | self.sidebar_button_3 = customtkinter.CTkButton(self.sidebar_frame, command=self.sidebar_button_event)
33 | self.sidebar_button_3.grid(row=3, column=0, padx=20, pady=10)
34 | self.appearance_mode_label = customtkinter.CTkLabel(self.sidebar_frame, text="Appearance Mode:", anchor="w")
35 | self.appearance_mode_label.grid(row=5, column=0, padx=20, pady=(10, 0))
36 | self.appearance_mode_optionemenu = customtkinter.CTkOptionMenu(self.sidebar_frame, values=["Light", "Dark", "System"],
37 | command=self.change_appearance_mode_event)
38 | self.appearance_mode_optionemenu.grid(row=6, column=0, padx=20, pady=(10, 10))
39 | self.scaling_label = customtkinter.CTkLabel(self.sidebar_frame, text="UI Scaling:", anchor="w")
40 | self.scaling_label.grid(row=7, column=0, padx=20, pady=(10, 0))
41 | self.scaling_optionemenu = customtkinter.CTkOptionMenu(self.sidebar_frame, values=["80%", "90%", "100%", "110%", "120%"],
42 | command=self.change_scaling_event)
43 | self.scaling_optionemenu.grid(row=8, column=0, padx=20, pady=(10, 20))
44 |
45 | # create main entry and button
46 | self.entry = customtkinter.CTkEntry(self, placeholder_text="CTkEntry")
47 | self.entry.grid(row=3, column=1, columnspan=2, padx=(20, 0), pady=(20, 20), sticky="nsew")
48 |
49 | self.main_button_1 = customtkinter.CTkButton(master=self, fg_color="transparent", border_width=2, text_color=("gray10", "#DCE4EE"))
50 | self.main_button_1.grid(row=3, column=3, padx=(20, 20), pady=(20, 20), sticky="nsew")
51 |
52 | # create textbox
53 | self.textbox = customtkinter.CTkTextbox(self, width=250)
54 | self.textbox.grid(row=0, column=1, padx=(20, 0), pady=(20, 0), sticky="nsew")
55 |
56 | # create tabview
57 | self.tabview = customtkinter.CTkTabview(self, width=250)
58 | self.tabview.grid(row=0, column=2, padx=(20, 0), pady=(20, 0), sticky="nsew")
59 | self.tabview.add("CTkTabview")
60 | self.tabview.add("Tab 2")
61 | self.tabview.add("Tab 3")
62 | self.tabview.tab("CTkTabview").grid_columnconfigure(0, weight=1) # configure grid of individual tabs
63 | self.tabview.tab("Tab 2").grid_columnconfigure(0, weight=1)
64 |
65 | self.optionmenu_1 = customtkinter.CTkOptionMenu(self.tabview.tab("CTkTabview"), dynamic_resizing=False,
66 | values=["Value 1", "Value 2", "Value Long Long Long"])
67 | self.optionmenu_1.grid(row=0, column=0, padx=20, pady=(20, 10))
68 | self.combobox_1 = customtkinter.CTkComboBox(self.tabview.tab("CTkTabview"),
69 | values=["Value 1", "Value 2", "Value Long....."])
70 | self.combobox_1.grid(row=1, column=0, padx=20, pady=(10, 10))
71 | self.string_input_button = customtkinter.CTkButton(self.tabview.tab("CTkTabview"), text="Open CTkInputDialog",
72 | command=self.open_input_dialog_event)
73 | self.string_input_button.grid(row=2, column=0, padx=20, pady=(10, 10))
74 | self.label_tab_2 = customtkinter.CTkLabel(self.tabview.tab("Tab 2"), text="CTkLabel on Tab 2")
75 | self.label_tab_2.grid(row=0, column=0, padx=20, pady=20)
76 |
77 | # create radiobutton frame
78 | self.radiobutton_frame = customtkinter.CTkFrame(self)
79 | self.radiobutton_frame.grid(row=0, column=3, padx=(20, 20), pady=(20, 0), sticky="nsew")
80 | self.radio_var = tkinter.IntVar(value=0)
81 | self.label_radio_group = customtkinter.CTkLabel(master=self.radiobutton_frame, text="CTkRadioButton Group:")
82 | self.label_radio_group.grid(row=0, column=2, columnspan=1, padx=10, pady=10, sticky="")
83 | self.radio_button_1 = customtkinter.CTkRadioButton(master=self.radiobutton_frame, variable=self.radio_var, value=0)
84 | self.radio_button_1.grid(row=1, column=2, pady=10, padx=20, sticky="n")
85 | self.radio_button_2 = customtkinter.CTkRadioButton(master=self.radiobutton_frame, variable=self.radio_var, value=1)
86 | self.radio_button_2.grid(row=2, column=2, pady=10, padx=20, sticky="n")
87 | self.radio_button_3 = customtkinter.CTkRadioButton(master=self.radiobutton_frame, variable=self.radio_var, value=2)
88 | self.radio_button_3.grid(row=3, column=2, pady=10, padx=20, sticky="n")
89 |
90 | # create slider and progressbar frame
91 | self.slider_progressbar_frame = customtkinter.CTkFrame(self, fg_color="transparent")
92 | self.slider_progressbar_frame.grid(row=1, column=1, padx=(20, 0), pady=(20, 0), sticky="nsew")
93 | self.slider_progressbar_frame.grid_columnconfigure(0, weight=1)
94 | self.slider_progressbar_frame.grid_rowconfigure(4, weight=1)
95 | self.seg_button_1 = customtkinter.CTkSegmentedButton(self.slider_progressbar_frame)
96 | self.seg_button_1.grid(row=0, column=0, padx=(20, 10), pady=(10, 10), sticky="ew")
97 | self.progressbar_1 = customtkinter.CTkProgressBar(self.slider_progressbar_frame)
98 | self.progressbar_1.grid(row=1, column=0, padx=(20, 10), pady=(10, 10), sticky="ew")
99 | self.progressbar_2 = customtkinter.CTkProgressBar(self.slider_progressbar_frame)
100 | self.progressbar_2.grid(row=2, column=0, padx=(20, 10), pady=(10, 10), sticky="ew")
101 | self.slider_1 = customtkinter.CTkSlider(self.slider_progressbar_frame, from_=0, to=1, number_of_steps=4)
102 | self.slider_1.grid(row=3, column=0, padx=(20, 10), pady=(10, 10), sticky="ew")
103 | self.slider_2 = customtkinter.CTkSlider(self.slider_progressbar_frame, orientation="vertical")
104 | self.slider_2.grid(row=0, column=1, rowspan=5, padx=(10, 10), pady=(10, 10), sticky="ns")
105 | self.progressbar_3 = customtkinter.CTkProgressBar(self.slider_progressbar_frame, orientation="vertical")
106 | self.progressbar_3.grid(row=0, column=2, rowspan=5, padx=(10, 20), pady=(10, 10), sticky="ns")
107 |
108 | # create scrollable frame
109 | self.scrollable_frame = customtkinter.CTkScrollableFrame(self, label_text="CTkScrollableFrame")
110 | self.scrollable_frame.grid(row=1, column=2, padx=(20, 0), pady=(20, 0), sticky="nsew")
111 | self.scrollable_frame.grid_columnconfigure(0, weight=1)
112 | self.scrollable_frame_switches = []
113 | for i in range(100):
114 | switch = customtkinter.CTkSwitch(master=self.scrollable_frame, text=f"CTkSwitch {i}")
115 | switch.grid(row=i, column=0, padx=10, pady=(0, 20))
116 | self.scrollable_frame_switches.append(switch)
117 |
118 | # create checkbox and switch frame
119 | self.checkbox_slider_frame = customtkinter.CTkFrame(self)
120 | self.checkbox_slider_frame.grid(row=1, column=3, padx=(20, 20), pady=(20, 0), sticky="nsew")
121 | self.checkbox_1 = customtkinter.CTkCheckBox(master=self.checkbox_slider_frame)
122 | self.checkbox_1.grid(row=1, column=0, pady=(20, 0), padx=20, sticky="n")
123 | self.checkbox_2 = customtkinter.CTkCheckBox(master=self.checkbox_slider_frame)
124 | self.checkbox_2.grid(row=2, column=0, pady=(20, 0), padx=20, sticky="n")
125 | self.checkbox_3 = customtkinter.CTkCheckBox(master=self.checkbox_slider_frame)
126 | self.checkbox_3.grid(row=3, column=0, pady=20, padx=20, sticky="n")
127 |
128 | # set default values
129 | self.sidebar_button_3.configure(state="disabled", text="Disabled CTkButton")
130 | self.checkbox_3.configure(state="disabled")
131 | self.checkbox_1.select()
132 | self.scrollable_frame_switches[0].select()
133 | self.scrollable_frame_switches[4].select()
134 | self.radio_button_3.configure(state="disabled")
135 | self.appearance_mode_optionemenu.set("Dark")
136 | self.scaling_optionemenu.set("100%")
137 | self.optionmenu_1.set("CTkOptionmenu")
138 | self.combobox_1.set("CTkComboBox")
139 | self.slider_1.configure(command=self.progressbar_2.set)
140 | self.slider_2.configure(command=self.progressbar_3.set)
141 | self.progressbar_1.configure(mode="indeterminnate")
142 | self.progressbar_1.start()
143 | self.textbox.insert("0.0", "CTkTextbox\n\n" + "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.\n\n" * 20)
144 | self.seg_button_1.configure(values=["CTkSegmentedButton", "Value 2", "Value 3"])
145 | self.seg_button_1.set("Value 2")
146 |
147 | def open_input_dialog_event(self):
148 | dialog = customtkinter.CTkInputDialog(text="Type in a number:", title="CTkInputDialog")
149 | print("CTkInputDialog:", dialog.get_input())
150 |
151 | def change_appearance_mode_event(self, new_appearance_mode: str):
152 | customtkinter.set_appearance_mode(new_appearance_mode)
153 |
154 | def change_scaling_event(self, new_scaling: str):
155 | new_scaling_float = int(new_scaling.replace("%", "")) / 100
156 | customtkinter.set_widget_scaling(new_scaling_float)
157 |
158 | def sidebar_button_event(self):
159 | print("sidebar_button click")
160 |
161 |
162 | if __name__ == "__main__":
163 | app = App()
164 | app.mainloop()
165 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/customTkinter_/sample.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/customTkinter_/sample.mp4
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/kivy_/README.txt:
--------------------------------------------------------------------------------
1 | How to Use:
2 | ===========
3 |
4 | * Create and activate a virtual environment:
5 |
6 | virtualenv venv
7 |
8 | venv\Scripts\Activate
9 |
10 | * Install dependencies:
11 |
12 | pip install -r requirements.txt
13 |
14 | * Run the GUI demo:
15 |
16 | python main.py
17 |
18 |
19 | Note:
20 | ----
21 | This Demo is taken from the official Kivy repository:
22 | https://github.com/kivy/kivy
23 |
24 |
25 | Sample:
26 | ----
27 | Kivy Showcase:
28 |
29 | 
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/kivy_/android.txt:
--------------------------------------------------------------------------------
1 | title=Showcase
2 | author=Kivy team
3 | orientation=landscape
4 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/kivy_/data/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/kivy_/data/background.png
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/kivy_/data/faust_github.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/kivy_/data/faust_github.jpg
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/kivy_/data/icons/README:
--------------------------------------------------------------------------------
1 | Icons adapted from the Open Iconic set of icons, which are licensed under MIT.
2 |
3 | https://useiconic.com/
4 | https://github.com/iconic/open-iconic
5 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/kivy_/data/icons/bug.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/kivy_/data/icons/bug.png
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/kivy_/data/icons/chevron-left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/kivy_/data/icons/chevron-left.png
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/kivy_/data/icons/chevron-right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/kivy_/data/icons/chevron-right.png
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/kivy_/data/screens/accordions.kv:
--------------------------------------------------------------------------------
1 | ShowcaseScreen:
2 | name: 'Accordions'
3 |
4 | fullscreen: True
5 |
6 | BoxLayout:
7 | size_hint_y: None
8 | height: '48dp'
9 |
10 | ToggleButton:
11 | id: tbh
12 | text: 'Horizontal'
13 | group: 'accordion'
14 | state: 'down'
15 |
16 | ToggleButton:
17 | text: 'Vertical'
18 | group: 'accordion'
19 |
20 | Accordion:
21 |
22 | orientation: 'horizontal' if tbh.state == 'down' else 'vertical'
23 |
24 | AccordionItem:
25 | title: 'Panel 1'
26 | Label:
27 | text: 'This is a label fit to the content view'
28 | text_size: self.width, None
29 |
30 | AccordionItem:
31 | title: 'Panel 2'
32 | Button:
33 | text: 'A button, what else?'
34 |
35 | AccordionItem:
36 | title: 'Panel 3'
37 | Label:
38 | text: 'This is a label fit to the content view'
39 | text_size: self.width, None
40 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/kivy_/data/screens/bubbles.kv:
--------------------------------------------------------------------------------
1 | ShowcaseScreen:
2 | name: 'Bubbles'
3 |
4 | Bubble:
5 | size_hint_y: None
6 | height: '48dp'
7 |
8 | BubbleButton:
9 | text: 'Cut'
10 | BubbleButton:
11 | text: 'Copy'
12 | BubbleButton:
13 | text: 'Paste'
14 |
15 | Widget:
16 | size_hint_y: None
17 | height: '48dp'
18 |
19 | BoxLayout:
20 | size_hint_y: None
21 | height: '48dp'
22 | Label:
23 | text: 'Hello'
24 |
25 | Bubble:
26 | arrow_pos: 'left_mid'
27 | Label:
28 | text: 'World'
29 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/kivy_/data/screens/buttons.kv:
--------------------------------------------------------------------------------
1 | ShowcaseScreen:
2 | name: 'Buttons'
3 |
4 | Button:
5 | size_hint_y: None
6 | height: '48dp'
7 | text: 'Button normal'
8 |
9 | Button:
10 | size_hint_y: None
11 | height: '48dp'
12 | text: 'Button down'
13 | state: 'down'
14 |
15 | Button:
16 | size_hint_y: None
17 | height: '48dp'
18 | text: 'Button disabled'
19 | disabled: True
20 |
21 | Button:
22 | size_hint_y: None
23 | height: '48dp'
24 | text: 'Button down disabled'
25 | state: 'down'
26 | disabled: True
27 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/kivy_/data/screens/carousel.kv:
--------------------------------------------------------------------------------
1 | :
2 | font_size: '48sp'
3 | color: (.6, .6, .6, 1)
4 | canvas.before:
5 | Color:
6 | rgb: (.9, .9, .9)
7 | Rectangle:
8 | pos: self.x + sp(2), self.y + sp(2)
9 | size: self.width - sp(4), self.height - sp(4)
10 |
11 | ShowcaseScreen:
12 | name: 'Carousel'
13 | fullscreen: True
14 |
15 | BoxLayout:
16 | size_hint_y: None
17 | height: '48dp'
18 |
19 | ToggleButton:
20 | text: 'Loop'
21 | id: btnloop
22 |
23 | Label:
24 | size_hint_x: None
25 | width: self.height
26 | text: '{}'.format(carousel.index)
27 |
28 | Button:
29 | size_hint_x: None
30 | width: self.height
31 | text: 'Prev'
32 | on_release: carousel.load_previous()
33 |
34 | Button:
35 | size_hint_x: None
36 | width: self.height
37 | text: 'Next'
38 | on_release: carousel.load_next()
39 |
40 | Carousel:
41 | id: carousel
42 | loop: btnloop.state == 'down'
43 |
44 | ColoredLabel:
45 | text: 'Slide 0'
46 |
47 | ColoredLabel:
48 | text: 'Slide 1'
49 |
50 | ColoredLabel:
51 | text: 'Slide 2'
52 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/kivy_/data/screens/checkboxes.kv:
--------------------------------------------------------------------------------
1 | ShowcaseScreen:
2 | name: 'CheckBoxes'
3 |
4 | GridLayout:
5 |
6 | cols: 3
7 | spacing: '8dp'
8 | size_hint: .5, None
9 | height: self.minimum_height
10 |
11 | Label:
12 | text: 'Checkbox'
13 |
14 | CheckBox:
15 | size_hint_y: None
16 | height: '48dp'
17 |
18 | CheckBox:
19 | size_hint_y: None
20 | height: '48dp'
21 |
22 | Label:
23 | text: 'CheckBox with group'
24 |
25 | CheckBox:
26 | size_hint_y: None
27 | height: '48dp'
28 | group: 'g2'
29 |
30 | CheckBox:
31 | size_hint_y: None
32 | height: '48dp'
33 | group: 'g2'
34 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/kivy_/data/screens/codeinput.kv:
--------------------------------------------------------------------------------
1 | ShowcaseScreen:
2 |
3 | fullscreen: True
4 | name: 'CodeInput'
5 |
6 | CodeInput:
7 | padding: '4dp'
8 | text: 'class Hello(object):\n\tpass\n\nprint("Hello world")'
9 | focus: True if root.parent else False
10 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/kivy_/data/screens/dropdown.kv:
--------------------------------------------------------------------------------
1 | ShowcaseScreen:
2 | fullscreen: True
3 | name: 'DropDown'
4 |
5 | # trick to not lost the Dropdown instance
6 | # Dropdown itself is not really made to be used in kv.
7 | __safe_id: [dropdown.__self__]
8 |
9 | Button:
10 | id: btn
11 | text: '-'
12 | on_release: dropdown.open(self)
13 | size_hint_y: None
14 | height: '48dp'
15 |
16 | Widget:
17 | on_parent: dropdown.dismiss()
18 |
19 | DropDown:
20 |
21 | id: dropdown
22 | on_select: btn.text = 'Selected value: {}'.format(args[1])
23 |
24 | Button:
25 | text: 'Value A'
26 | size_hint_y: None
27 | height: '48dp'
28 | on_release: dropdown.select('A')
29 |
30 | Button:
31 | text: 'Value B'
32 | size_hint_y: None
33 | height: '48dp'
34 | on_release: dropdown.select('B')
35 |
36 | Button:
37 | text: 'Value C'
38 | size_hint_y: None
39 | height: '48dp'
40 | on_release: dropdown.select('C')
41 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/kivy_/data/screens/filechoosers.kv:
--------------------------------------------------------------------------------
1 | ShowcaseScreen:
2 | name: 'FileChoosers'
3 | fullscreen: True
4 |
5 | BoxLayout:
6 | size_hint_y: None
7 | height: '48dp'
8 |
9 | ToggleButton:
10 | text: 'Icon'
11 | state: 'down'
12 | group: 'filechooser'
13 | on_release: filechooser.view_mode = 'icon'
14 |
15 | ToggleButton:
16 | text: 'List'
17 | group: 'filechooser'
18 | on_release: filechooser.view_mode = 'list'
19 |
20 | FileChooser:
21 | id: filechooser
22 |
23 | FileChooserIconLayout
24 | FileChooserListLayout
25 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/kivy_/data/screens/popups.kv:
--------------------------------------------------------------------------------
1 | ShowcaseScreen:
2 | popup: popup.__self__
3 | fullscreen: True
4 | name: 'Popups'
5 | BoxLayout:
6 | id: bl
7 | Popup:
8 | id: popup
9 | title: "Hello World"
10 | on_parent:
11 | if self.parent == bl: self.parent.remove_widget(self)
12 | Button:
13 | text: 'press to dismiss'
14 | on_release: popup.dismiss()
15 | Button:
16 | text: 'press to show Popup'
17 | on_release: root.popup.open()
18 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/kivy_/data/screens/progressbar.kv:
--------------------------------------------------------------------------------
1 | ShowcaseScreen:
2 | name: 'ProgressBar'
3 |
4 | Label:
5 | text: 'Progression: {}%'.format(int(pb.value))
6 | size_hint_y: None
7 | height: '48dp'
8 |
9 | ProgressBar:
10 | id: pb
11 | size_hint_x: .5
12 | size_hint_y: None
13 | height: '48dp'
14 | value: (app.time * 20) % 100.
15 |
16 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/kivy_/data/screens/rstdocument.kv:
--------------------------------------------------------------------------------
1 | ShowcaseScreen:
2 | name: 'RstDocument'
3 | fullscreen: True
4 | on_parent: if not args[1]: textinput.focus = False
5 |
6 | GridLayout:
7 | cols: 2 if root.width > root.height else 1
8 | spacing: '8dp'
9 |
10 | TextInput:
11 | id: textinput
12 | text:
13 | ('.. _top:\n'
14 | '\n'
15 | 'Hello world\n'
16 | '===========\n'
17 | '\n'
18 | 'This is an **emphased text**, *italic text*, ``interpreted text``.\n'
19 | 'And this is a reference to top_::\n'
20 | '\n'
21 | ' $ print("Hello world")\n')
22 |
23 | RstDocument:
24 | text: textinput.text
25 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/kivy_/data/screens/scatter.kv:
--------------------------------------------------------------------------------
1 | ShowcaseScreen:
2 | name: 'Scatter'
3 |
4 | Widget:
5 |
6 | Scatter:
7 | id: scatter
8 | size_hint: None, None
9 | size: image.size
10 |
11 | Image:
12 | id: image
13 | source: 'data/faust_github.jpg'
14 | size: self.texture_size
15 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/kivy_/data/screens/screenmanager.kv:
--------------------------------------------------------------------------------
1 | #:import Factory kivy.factory.Factory
2 |
3 | ShowcaseScreen:
4 | name: 'ScreenManager'
5 | fullscreen: True
6 |
7 | BoxLayout:
8 | size_hint_y: None
9 | height: '48dp'
10 |
11 | Spinner:
12 | text: 'Default transition'
13 | values: ('SlideTransition', 'SwapTransition', 'FadeTransition', 'WipeTransition')
14 | on_text: sm.transition = Factory.get(self.text)()
15 |
16 | ScreenManager:
17 | id: sm
18 |
19 | Screen:
20 | name: 'screen1'
21 | canvas.before:
22 | Color:
23 | rgb: .8, .2, .2
24 | Rectangle:
25 | size: self.size
26 |
27 | AnchorLayout:
28 | Button:
29 | size_hint: None, None
30 | size: '150dp', '48dp'
31 | text: 'Go to screen 2'
32 | on_release: sm.current = 'screen2'
33 |
34 | Screen:
35 | name: 'screen2'
36 | canvas.before:
37 | Color:
38 | rgb: .2, .8, .2
39 | Rectangle:
40 | size: self.size
41 | AnchorLayout:
42 | Button:
43 | size_hint: None, None
44 | size: '150dp', '48dp'
45 | text: 'Go to screen 1'
46 | on_release: sm.current = 'screen1'
47 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/kivy_/data/screens/sliders.kv:
--------------------------------------------------------------------------------
1 | ShowcaseScreen:
2 | name: 'Sliders'
3 |
4 | BoxLayout:
5 | size_hint_y: None
6 | height: '48dp'
7 |
8 | Label:
9 | text: 'Default'
10 |
11 | Slider:
12 | id: s1
13 |
14 | Label:
15 | text: '{}'.format(s1.value)
16 |
17 |
18 | BoxLayout:
19 | size_hint_y: None
20 | height: '48dp'
21 |
22 | Label:
23 | text: 'Stepped'
24 |
25 | Slider:
26 | id: s2
27 | step: 20
28 |
29 | Label:
30 | text: '{}'.format(s2.value)
31 |
32 | AnchorLayout:
33 | size_hint_y: None
34 | height: '100dp'
35 |
36 | GridLayout:
37 | cols: 2
38 | spacing: '8dp'
39 | size_hint_x: None
40 | width: self.minimum_width
41 |
42 | Slider:
43 | size_hint_x: None
44 | width: '48dp'
45 | orientation: 'vertical'
46 | value: s1.value
47 | on_value: s1.value = self.value
48 |
49 | Slider:
50 | size_hint_x: None
51 | width: '48dp'
52 | orientation: 'vertical'
53 | step: 20
54 | value: s2.value
55 | on_value: s2.value = self.value
56 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/kivy_/data/screens/spinner.kv:
--------------------------------------------------------------------------------
1 | ShowcaseScreen:
2 | name: 'Spinner'
3 | fullscreen: True
4 |
5 | Spinner:
6 | text: 'Home'
7 | values: ('Home', 'Work', 'Other', 'Not defined')
8 | size_hint_y: None
9 | height: '48dp'
10 |
11 | Widget
12 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/kivy_/data/screens/splitter.kv:
--------------------------------------------------------------------------------
1 | ShowcaseScreen:
2 | name: 'Splitter'
3 | fullscreen: True
4 |
5 | RelativeLayout:
6 | id: rl
7 |
8 | Splitter:
9 | sizable_from: 'right'
10 | min_size: 10
11 | max_size: rl.width
12 | Button:
13 | text: 'Panel'
14 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/kivy_/data/screens/switches.kv:
--------------------------------------------------------------------------------
1 | ShowcaseScreen:
2 | name: 'Switches'
3 |
4 | BoxLayout:
5 | size_hint_y: None
6 | height: '48dp'
7 |
8 | Label:
9 | text: 'Switch normal'
10 | Switch:
11 |
12 | BoxLayout:
13 | size_hint_y: None
14 | height: '48dp'
15 |
16 | Label:
17 | text: 'Switch active'
18 | Switch:
19 | active: True
20 |
21 | BoxLayout:
22 | size_hint_y: None
23 | height: '48dp'
24 |
25 | Label:
26 | text: 'Switch off & disabled'
27 | Switch:
28 | disabled: True
29 | active: False
30 |
31 | BoxLayout:
32 | size_hint_y: None
33 | height: '48dp'
34 |
35 | Label:
36 | text: 'Switch on & disabled'
37 | Switch:
38 | disabled: True
39 | active: True
40 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/kivy_/data/screens/tabbedpanel + layouts.kv:
--------------------------------------------------------------------------------
1 | #:import random random.random
2 |
3 | ShowcaseScreen:
4 | name: 'TabbedPanel + Layouts'
5 | fullscreen: True
6 | on_parent: if args[1] and tp.current_tab == tab_fl: app.showcase_floatlayout(fl)
7 |
8 | TabbedPanel:
9 | id: tp
10 | do_default_tab: False
11 |
12 | TabbedPanelItem:
13 | id: tab_fl
14 | text: 'FloatLayout'
15 | on_release: app.showcase_floatlayout(fl)
16 | FloatLayout:
17 | CFloatLayout:
18 | id: fl
19 | TabbedPanelItem:
20 | text: 'BoxLayout'
21 | on_release: app.showcase_boxlayout(box)
22 | FloatLayout
23 | CBoxLayout:
24 | id: box
25 | TabbedPanelItem:
26 | text: 'GridLayout'
27 | on_release: app.showcase_gridlayout(grid)
28 | FloatLayout
29 | CGridLayout:
30 | id: grid
31 | rows: 3
32 | TabbedPanelItem:
33 | on_release: app.showcase_stacklayout(stack)
34 | text: 'StackLayout'
35 | FloatLayout
36 | CStackLayout:
37 | id: stack
38 | TabbedPanelItem:
39 | text: 'AnchorLayout'
40 | on_release: app.showcase_anchorlayout(anchor)
41 | FloatLayout
42 | CAnchorLayout:
43 | id: anchor
44 | BoxLayout:
45 | orientation: 'vertical'
46 | size_hint: .4, .5
47 | Button
48 | Button
49 | text: 'anchor_x: {}'.format(anchor.anchor_x)
50 | Button
51 | text: 'anchor_y: {}'.format(anchor.anchor_y)
52 | Button
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | pos_hint: {'center_x': .5, 'center_y': .5}
63 | size_hint: .9, .9
64 | canvas.before:
65 | Color:
66 | rgba: .2, .3, .4, 1
67 | Rectangle:
68 | size: self.size
69 | pos: self.pos
70 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/kivy_/data/screens/textinputs.kv:
--------------------------------------------------------------------------------
1 | ShowcaseScreen:
2 | name: 'TextInputs'
3 | focused: ti_default
4 | on_parent:
5 | if not args[1] and self.focused: self.focused.focus = False
6 | if args[1]: ti_default.focus = True
7 |
8 | CTextInput
9 | size_hint_y: None
10 | height: '32dp'
11 | multiline: False
12 | text: 'Monoline textinput'
13 |
14 | CTextInput:
15 | id: ti_default
16 | size_hint_y: None
17 | height: '32dp'
18 | text: 'Focused textinput'
19 | focus: True
20 |
21 | CTextInput:
22 | size_hint_y: None
23 | height: '32dp'
24 | text: 'Password'
25 | password: True
26 |
27 | CTextInput:
28 | size_hint_y: None
29 | height: '32dp'
30 | text: 'Readonly textinput'
31 | readonly: True
32 |
33 | CTextInput:
34 | size_hint_y: None
35 | height: '48dp'
36 | text: 'Multiline textinput\nSecond line'
37 | multiline: True
38 |
39 | CTextInput:
40 | size_hint_y: None
41 | height: '32dp'
42 | disabled: True
43 | text: 'Disabled textinput'
44 |
45 |
46 | on_focus:
47 | screen = self.parent.parent.parent.parent
48 | if screen.parent: screen.focused = self
49 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/kivy_/data/screens/togglebutton.kv:
--------------------------------------------------------------------------------
1 | ShowcaseScreen:
2 | name: 'ToggleButton'
3 |
4 | GridLayout:
5 |
6 | cols: 3
7 | spacing: '8dp'
8 | size_hint_y: None
9 | height: self.minimum_height
10 |
11 | Label:
12 | text: 'Choice 1'
13 |
14 | ToggleButton:
15 | size_hint_y: None
16 | height: '48dp'
17 | text: 'A'
18 | group: 'g1'
19 |
20 | ToggleButton:
21 | size_hint_y: None
22 | height: '48dp'
23 | text: 'B'
24 | group: 'g1'
25 |
26 | Label:
27 | text: 'Choice 2'
28 |
29 | ToggleButton:
30 | size_hint_y: None
31 | height: '48dp'
32 | text: 'A'
33 | group: 'g2'
34 |
35 | ToggleButton:
36 | size_hint_y: None
37 | height: '48dp'
38 | text: 'B'
39 | group: 'g2'
40 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/kivy_/main.py:
--------------------------------------------------------------------------------
1 | '''
2 | Showcase of Kivy Features
3 | =========================
4 |
5 | This showcases many features of Kivy. You should see a
6 | menu bar across the top with a demonstration area below. The
7 | first demonstration is the accordion layout. You can see, but not
8 | edit, the kv language code for any screen by pressing the bug or
9 | 'show source' icon. Scroll through the demonstrations using the
10 | left and right icons in the top right or selecting from the menu
11 | bar.
12 |
13 | The file showcase.kv describes the main container, while each demonstration
14 | pane is described in a separate .kv file in the data/screens directory.
15 | The image data/background.png provides the gradient background while the
16 | icons in data/icon directory are used in the control bar. The file
17 | data/faust_github.jpg is used in the Scatter pane. The icons are
18 | from `http://www.gentleface.com/free_icon_set.html` and licensed as
19 | Creative Commons - Attribution and Non-commercial Use Only; they
20 | sell a commercial license.
21 |
22 | The file android.txt is used to package the application for use with the
23 | Kivy Launcher Android application. For Android devices, you can
24 | copy/paste this directory into /sdcard/kivy/showcase on your Android device.
25 |
26 | '''
27 |
28 | from time import time
29 | from kivy.app import App
30 | from os.path import dirname, join
31 | from kivy.lang import Builder
32 | from kivy.properties import NumericProperty, StringProperty, BooleanProperty,\
33 | ListProperty
34 | from kivy.clock import Clock
35 | from kivy.animation import Animation
36 | from kivy.uix.screenmanager import Screen
37 |
38 |
39 | class ShowcaseScreen(Screen):
40 | fullscreen = BooleanProperty(False)
41 |
42 | def add_widget(self, *args, **kwargs):
43 | if 'content' in self.ids:
44 | return self.ids.content.add_widget(*args, **kwargs)
45 | return super(ShowcaseScreen, self).add_widget(*args, **kwargs)
46 |
47 |
48 | class ShowcaseApp(App):
49 |
50 | index = NumericProperty(-1)
51 | current_title = StringProperty()
52 | time = NumericProperty(0)
53 | show_sourcecode = BooleanProperty(False)
54 | sourcecode = StringProperty()
55 | screen_names = ListProperty([])
56 | hierarchy = ListProperty([])
57 |
58 | def build(self):
59 | self.title = 'hello world'
60 | Clock.schedule_interval(self._update_clock, 1 / 60.)
61 | self.screens = {}
62 | self.available_screens = sorted([
63 | 'Buttons', 'ToggleButton', 'Sliders', 'ProgressBar', 'Switches',
64 | 'CheckBoxes', 'TextInputs', 'Accordions', 'FileChoosers',
65 | 'Carousel', 'Bubbles', 'CodeInput', 'DropDown', 'Spinner',
66 | 'Scatter', 'Splitter', 'TabbedPanel + Layouts', 'RstDocument',
67 | 'Popups', 'ScreenManager'])
68 | self.screen_names = self.available_screens
69 | curdir = dirname(__file__)
70 | self.available_screens = [join(curdir, 'data', 'screens',
71 | '{}.kv'.format(fn).lower()) for fn in self.available_screens]
72 | self.go_next_screen()
73 |
74 | def on_pause(self):
75 | return True
76 |
77 | def on_resume(self):
78 | pass
79 |
80 | def on_current_title(self, instance, value):
81 | self.root.ids.spnr.text = value
82 |
83 | def go_previous_screen(self):
84 | self.index = (self.index - 1) % len(self.available_screens)
85 | screen = self.load_screen(self.index)
86 | sm = self.root.ids.sm
87 | sm.switch_to(screen, direction='right')
88 | self.current_title = screen.name
89 | self.update_sourcecode()
90 |
91 | def go_next_screen(self):
92 | self.index = (self.index + 1) % len(self.available_screens)
93 | screen = self.load_screen(self.index)
94 | sm = self.root.ids.sm
95 | sm.switch_to(screen, direction='left')
96 | self.current_title = screen.name
97 | self.update_sourcecode()
98 |
99 | def go_screen(self, idx):
100 | self.index = idx
101 | self.root.ids.sm.switch_to(self.load_screen(idx), direction='left')
102 | self.update_sourcecode()
103 |
104 | def go_hierarchy_previous(self):
105 | ahr = self.hierarchy
106 | if len(ahr) == 1:
107 | return
108 | if ahr:
109 | ahr.pop()
110 | if ahr:
111 | idx = ahr.pop()
112 | self.go_screen(idx)
113 |
114 | def load_screen(self, index):
115 | if index in self.screens:
116 | return self.screens[index]
117 | screen = Builder.load_file(self.available_screens[index])
118 | self.screens[index] = screen
119 | return screen
120 |
121 | def read_sourcecode(self):
122 | fn = self.available_screens[self.index]
123 | with open(fn) as fd:
124 | return fd.read()
125 |
126 | def toggle_source_code(self):
127 | self.show_sourcecode = not self.show_sourcecode
128 | if self.show_sourcecode:
129 | height = self.root.height * .3
130 | else:
131 | height = 0
132 |
133 | Animation(height=height, d=.3, t='out_quart').start(
134 | self.root.ids.sv)
135 |
136 | self.update_sourcecode()
137 |
138 | def update_sourcecode(self):
139 | if not self.show_sourcecode:
140 | self.root.ids.sourcecode.focus = False
141 | return
142 | self.root.ids.sourcecode.text = self.read_sourcecode()
143 | self.root.ids.sv.scroll_y = 1
144 |
145 | def showcase_floatlayout(self, layout):
146 |
147 | def add_button(*t):
148 | if not layout.get_parent_window():
149 | return
150 | if len(layout.children) > 5:
151 | layout.clear_widgets()
152 | layout.add_widget(Builder.load_string('''
153 | #:import random random.random
154 | Button:
155 | size_hint: random(), random()
156 | pos_hint: {'x': random(), 'y': random()}
157 | text:
158 | 'size_hint x: {} y: {}\\n pos_hint x: {} y: {}'.format(\
159 | self.size_hint_x, self.size_hint_y, self.pos_hint['x'],\
160 | self.pos_hint['y'])
161 | '''))
162 | Clock.schedule_once(add_button, 1)
163 | Clock.schedule_once(add_button)
164 |
165 | def showcase_boxlayout(self, layout):
166 |
167 | def add_button(*t):
168 | if not layout.get_parent_window():
169 | return
170 | if len(layout.children) > 5:
171 | layout.orientation = 'vertical'\
172 | if layout.orientation == 'horizontal' else 'horizontal'
173 | layout.clear_widgets()
174 | layout.add_widget(Builder.load_string('''
175 | Button:
176 | text: self.parent.orientation if self.parent else ''
177 | '''))
178 | Clock.schedule_once(add_button, 1)
179 | Clock.schedule_once(add_button)
180 |
181 | def showcase_gridlayout(self, layout):
182 |
183 | def add_button(*t):
184 | if not layout.get_parent_window():
185 | return
186 | if len(layout.children) > 15:
187 | layout.rows = 3 if layout.rows is None else None
188 | layout.cols = None if layout.rows == 3 else 3
189 | layout.clear_widgets()
190 | layout.add_widget(Builder.load_string('''
191 | Button:
192 | text:
193 | 'rows: {}\\ncols: {}'.format(self.parent.rows, self.parent.cols)\
194 | if self.parent else ''
195 | '''))
196 | Clock.schedule_once(add_button, 1)
197 | Clock.schedule_once(add_button)
198 |
199 | def showcase_stacklayout(self, layout):
200 | orientations = ('lr-tb', 'tb-lr',
201 | 'rl-tb', 'tb-rl',
202 | 'lr-bt', 'bt-lr',
203 | 'rl-bt', 'bt-rl')
204 |
205 | def add_button(*t):
206 | if not layout.get_parent_window():
207 | return
208 | if len(layout.children) > 11:
209 | layout.clear_widgets()
210 | cur_orientation = orientations.index(layout.orientation)
211 | layout.orientation = orientations[cur_orientation - 1]
212 | layout.add_widget(Builder.load_string('''
213 | Button:
214 | text: self.parent.orientation if self.parent else ''
215 | size_hint: .2, .2
216 | '''))
217 | Clock.schedule_once(add_button, 1)
218 | Clock.schedule_once(add_button)
219 |
220 | def showcase_anchorlayout(self, layout):
221 |
222 | def change_anchor(self, *l):
223 | if not layout.get_parent_window():
224 | return
225 | anchor_x = ('left', 'center', 'right')
226 | anchor_y = ('top', 'center', 'bottom')
227 | if layout.anchor_x == 'left':
228 | layout.anchor_y = anchor_y[anchor_y.index(layout.anchor_y) - 1]
229 | layout.anchor_x = anchor_x[anchor_x.index(layout.anchor_x) - 1]
230 |
231 | Clock.schedule_once(change_anchor, 1)
232 | Clock.schedule_once(change_anchor, 1)
233 |
234 | def _update_clock(self, dt):
235 | self.time = time()
236 |
237 |
238 | if __name__ == '__main__':
239 | ShowcaseApp().run()
240 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/kivy_/requirements.txt:
--------------------------------------------------------------------------------
1 | kivy
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/kivy_/sample.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/kivy_/sample.gif
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/kivy_/showcase.kv:
--------------------------------------------------------------------------------
1 | #:kivy 1.8.0
2 | #:import KivyLexer kivy.extras.highlight.KivyLexer
3 | #:import Factory kivy.factory.Factory
4 |
5 |
6 | background_color: .4, .4, .4, 1
7 |
8 |
9 | canvas.before:
10 | Color:
11 | rgba: 0.128, 0.128, 0.128, 1
12 | Rectangle:
13 | size: self.size
14 | pos: self.pos
15 | border: 27, 20, 12, 12
16 | background_normal: 'atlas://data/images/defaulttheme/action_group'
17 | option_cls: Factory.ActionSpinnerOptions
18 |
19 | :
20 | on_size: self.width = '220dp'
21 |
22 | :
23 | ScrollView:
24 | do_scroll_x: False
25 | do_scroll_y: False if root.fullscreen else (content.height > root.height - dp(16))
26 | AnchorLayout:
27 | size_hint_y: None
28 | height: root.height if root.fullscreen else max(root.height, content.height)
29 | GridLayout:
30 | id: content
31 | cols: 1
32 | spacing: '8dp'
33 | padding: '8dp'
34 | size_hint: (1, 1) if root.fullscreen else (.8, None)
35 | height: self.height if root.fullscreen else self.minimum_height
36 |
37 |
38 | BoxLayout:
39 | orientation: 'vertical'
40 |
41 | canvas.before:
42 | Color:
43 | rgb: .6, .6, .6
44 | Rectangle:
45 | size: self.size
46 | source: 'data/background.png'
47 |
48 | ActionBar:
49 |
50 | ActionView:
51 | id: av
52 | ActionPrevious:
53 | with_previous: (False if sm.current_screen.name == 'button' else True) if sm.current_screen else False
54 | title: 'Showcase' + ('' if not app.current_title else ' - {}'.format(app.current_title))
55 | on_release: app.go_hierarchy_previous()
56 |
57 | ActionSpinner:
58 | id: spnr
59 | important: True
60 | text: 'Jump to Screen'
61 | values: app.screen_names
62 | on_text:
63 | if sm.current != args[1]:\
64 | idx = app.screen_names.index(args[1]);\
65 | app.go_screen(idx)
66 | ActionToggleButton:
67 | text: 'Toggle sourcecode'
68 | icon: 'data/icons/bug.png'
69 | on_release: app.toggle_source_code()
70 | ActionButton:
71 | text: 'Previous screen'
72 | icon: 'data/icons/chevron-left.png'
73 | on_release: app.go_previous_screen()
74 |
75 | ActionButton:
76 | text: 'Next screen'
77 | icon: 'data/icons/chevron-right.png'
78 | on_release: app.go_next_screen()
79 | important: True
80 |
81 | ScrollView:
82 | id: sv
83 | size_hint_y: None
84 | height: 0
85 |
86 | CodeInput:
87 | id: sourcecode
88 | lexer: KivyLexer()
89 | text: app.sourcecode
90 | readonly: True
91 | size_hint_y: None
92 | font_size: '12sp'
93 | height: self.minimum_height
94 |
95 | ScreenManager:
96 | id: sm
97 | on_current_screen:
98 | spnr.text = args[1].name
99 | idx = app.screen_names.index(args[1].name)
100 | if idx > -1: app.hierarchy.append(idx)
101 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/pysimplegui_/README.md:
--------------------------------------------------------------------------------
1 | How to Use:
2 | ===========
3 |
4 | * Create and activate a virtual environment:
5 |
6 | virtualenv venv
7 |
8 | venv\Scripts\Activate
9 |
10 | * Install dependencies:
11 |
12 | pip install -r requirements.txt
13 |
14 | * Run the GUI demo:
15 |
16 | python simple_gui_builder.py
17 |
18 |
19 | Note:
20 | ----
21 | This Demo is taken from this repository and updated for python 3:
22 | https://github.com/PriestTheBeast/SimpleGUIBuilder
23 |
24 |
25 | Sample:
26 | ----
27 | SimpleGUIBuilder:
28 |
29 | 
30 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/pysimplegui_/SimpleGUIBuilder.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/pysimplegui_/SimpleGUIBuilder.gif
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/pysimplegui_/layout_parser.py:
--------------------------------------------------------------------------------
1 | import inspect
2 | import sys
3 | import ast
4 |
5 | import PySimpleGUI as sg
6 |
7 | CONTAINER_ELEMENTS = (sg.Column, sg.Frame, sg.Tab)
8 | CONTAINER_LAYOUT_ARG = {sg.Column: 0, sg.Frame: 1, sg.Tab: 1}
9 |
10 | GUI_CLASSES = dict(inspect.getmembers(sys.modules["PySimpleGUI"], inspect.isclass))
11 |
12 |
13 | # This file is one I don't wish to revisit so soon, but it does all the hard work of
14 | # taking a layout in string format and interpret it, making possible creating a Tree structure from it
15 | # all I have to say is, god damn handling strings in parsing the string layout xD
16 |
17 | # Parses info into a list of something, start and end are the delimiters,
18 | # save_all I think makes it also save the beginning b4 the start delimiter
19 | # filter_beginning allows it to ignore some chars at the start of the info, like " " and ","
20 | def parse_with_delimiters(info, start, end, save_all=False, filter_beginning=None):
21 | rows = []
22 | row = []
23 | quote = None
24 | escaped = False
25 | deep_lvl = 0
26 | in_beginning = True
27 | for letter in info:
28 | if in_beginning:
29 | if filter_beginning is None or letter not in filter_beginning:
30 | in_beginning = False
31 | else:
32 | continue
33 |
34 | if escaped:
35 | escaped = False
36 | row.append(letter)
37 | continue
38 | if letter in ("'", '"'):
39 | if quote is None:
40 | quote = letter
41 | elif letter == quote:
42 | quote = None
43 | row.append(letter)
44 | continue
45 | if quote is not None:
46 | if letter == "\\":
47 | escaped = True
48 | row.append(letter)
49 | continue
50 |
51 | if letter == start:
52 | if save_all or deep_lvl > 0:
53 | row.append(letter)
54 | deep_lvl += 1
55 | elif letter == end:
56 | if deep_lvl == 1:
57 | if save_all:
58 | row.append(letter)
59 | rows.append("".join(row))
60 | row = []
61 | in_beginning = True
62 | else:
63 | row.append(letter)
64 | deep_lvl -= 1
65 | elif save_all or deep_lvl > 0:
66 | row.append(letter)
67 |
68 | return rows
69 |
70 |
71 | def parse_with_single_char(info, ch, filter_beginning=None):
72 | if not info:
73 | return []
74 | rows = []
75 | row = []
76 | quote = None
77 | escaped = False
78 | collection_start = {"(": ")", "{": "}", "[": "]"}
79 | collection_char = None
80 | deep_lvl = 0
81 | in_beginning = True
82 | for letter in info:
83 | if in_beginning:
84 | if filter_beginning is None or letter not in filter_beginning:
85 | in_beginning = False
86 | else:
87 | continue
88 |
89 | if escaped:
90 | escaped = False
91 | row.append(letter)
92 | continue
93 | if letter in ("'", '"'):
94 | if quote is None:
95 | quote = letter
96 | elif letter == quote:
97 | quote = None
98 | row.append(letter)
99 | continue
100 | if quote is not None:
101 | if letter == "\\":
102 | escaped = True
103 | row.append(letter)
104 | continue
105 |
106 | if collection_char is not None:
107 | if letter == collection_start[collection_char]:
108 | deep_lvl -= 1
109 | if deep_lvl == 0:
110 | collection_char = None
111 | elif letter == collection_char:
112 | deep_lvl += 1
113 | else:
114 | if letter in collection_start:
115 | collection_char = letter
116 | deep_lvl += 1
117 | elif letter == ch:
118 | rows.append("".join(row))
119 | row = []
120 | in_beginning = True
121 | continue
122 | row.append(letter)
123 |
124 | rows.append("".join(row))
125 | return rows
126 |
127 |
128 | def strip_except_inside_quote(info):
129 | new_info = ""
130 |
131 | quote = None
132 | escaped = False
133 | for letter in info:
134 | if escaped:
135 | escaped = False
136 | new_info += letter
137 | continue
138 | if letter in ("'", '"'):
139 | if quote is None:
140 | quote = letter
141 | elif letter == quote:
142 | quote = None
143 | new_info += letter
144 | continue
145 | if quote is not None:
146 | if letter == "\\":
147 | escaped = True
148 | new_info += letter
149 | continue
150 |
151 | if letter not in (" ", "\n", "\t"):
152 | new_info += letter
153 |
154 | return new_info
155 |
156 |
157 | def parse_string_layout(string_layout):
158 | # remove first "[ ]"
159 | rows_string = strip_except_inside_quote(string_layout)[1:-1]
160 |
161 | # separate into rows
162 | rows = parse_with_delimiters(rows_string, "[", "]")
163 |
164 | # separate each row into list of elements
165 | tree = []
166 | for row in rows:
167 | tree.append(parse_with_delimiters(row, "(", ")", save_all=True, filter_beginning=(" ", ",")))
168 |
169 | # for row in tree:
170 | # for element in row:
171 | # print(element, end=" - ")
172 | # print()
173 |
174 | # separate each element into the element name and list of evaluated args
175 | new_tree = []
176 | for row in tree:
177 | new_row = []
178 | new_tree.append(new_row)
179 | for element in row:
180 | element_name_ended = False
181 | element_name = ""
182 | args = ""
183 | for letter in element:
184 | if element_name_ended:
185 | args += letter
186 | elif letter == "(":
187 | element_name_ended = True
188 | args += letter
189 | else:
190 | element_name += letter
191 |
192 | args_list = parse_with_single_char(args[1:-1], ",")
193 | are_kargs = False
194 | args_values = []
195 | kargs_values = {}
196 | for i, arg in enumerate(args_list):
197 | container_with_args = (GUI_CLASSES[element_name[3:]] in CONTAINER_ELEMENTS
198 | and i == CONTAINER_LAYOUT_ARG[GUI_CLASSES[element_name[3:]]])
199 | if not are_kargs and "=" in arg and not container_with_args:
200 | eq_pos = arg.index("=")
201 | if ("'" not in arg or eq_pos < arg.index("'")) and ('"' not in arg or eq_pos < arg.index('"')):
202 | are_kargs = True
203 |
204 | if not are_kargs:
205 | if container_with_args:
206 | args_values.append(parse_string_layout(arg))
207 | else:
208 | try:
209 | args_values.append(ast.literal_eval(arg))
210 | except Exception as e:
211 | print(arg)
212 | raise Exception(str(e), arg) from e
213 | else:
214 | kargs_values[arg[:arg.index("=")]] = ast.literal_eval(arg[arg.index("=") + 1:])
215 |
216 | new_row.append((element_name[3:], args_values, kargs_values))
217 |
218 | # for row in new_tree:
219 | # for element in row:
220 | # print(element, end=" - ")
221 | # print()
222 |
223 | return new_tree
224 |
225 |
226 | # Test a very bad case scenario
227 | def main():
228 | test_layout = '[ \n \t ' \
229 | '[ sg.Text ( " This is a very \\" sure then \\" \' right \' ][ basic PySimpleGUI layout")],' \
230 | '[sg.Input()],' \
231 | '[ sg.Button( \' Button "right" \\\' maybe \\\' \'), sg.Button( "Exit", 4, 6, key= "-nope-", visible=True)]' \
232 | ']'
233 |
234 | print(parse_string_layout(test_layout))
235 |
236 |
237 | if __name__ == "__main__":
238 | main()
239 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/pysimplegui_/requirements.txt:
--------------------------------------------------------------------------------
1 | PySimpleGUI
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/pysimplegui_/simple_gui_builder.py:
--------------------------------------------------------------------------------
1 | import inspect
2 | import sys
3 | import time
4 |
5 | import PySimpleGUI as sg
6 | from tree_node import TreeNode
7 |
8 | GUI_CLASSES = dict(inspect.getmembers(sys.modules["PySimpleGUI"], inspect.isclass))
9 |
10 | ELEMB_KEY = "ELEMB-"
11 |
12 |
13 | # Function that returns the layout of the middle Elements Frame, where you choose the an element
14 | # It's lists made by hand, but any Element that is not in any list will be added at the last list (Others)
15 | def make_elements_frame():
16 | layout = []
17 |
18 | elements = set(GUI_CLASSES.values())
19 | elements_names = [e.__name__ for e in elements if
20 | issubclass(e, sg.Element) and e not in (sg.Element, sg.ErrorElement)]
21 |
22 | # Makes a button for each string in the given list of strings
23 | make_button_row = (lambda element_list: [sg.Button(button_text=e, key=ELEMB_KEY + e) for e in element_list])
24 |
25 | layout.append([sg.Button(button_text="Delete Element", button_color=("black", "darkred"), key="DeleteElement"),
26 | sg.Button('Move Element Up', key="MoveUp"), sg.Button('Move Element Down', key="MoveDown")])
27 |
28 | layout.append([sg.Text(text="Main Elements")])
29 | main_elements = ["Text", "InputText", "Output", "Button", "Combo", "Listbox", "Radio", "Checkbox"]
30 | layout.append(make_button_row(main_elements))
31 |
32 | layout.append([sg.Text(text="Containers")])
33 | container_elems = ["TabGroup", "Tab", "Frame", "Column"]
34 | layout.append(make_button_row(container_elems))
35 |
36 | layout.append([sg.Text(text="Separators")])
37 | design_elems = ["HorizontalSeparator", "VerticalSeparator"]
38 | layout.append(make_button_row(design_elems))
39 |
40 | layout.append([sg.Text(text="Menus")])
41 | organization_elems = ["Menu", "MenuButton", "OptionMenu", "ButtonMenu"]
42 | layout.append(make_button_row(organization_elems))
43 |
44 | layout.append([sg.Text(text="Structured Info")])
45 | structured_info_elems = ["Tree", "Table"]
46 | layout.append(make_button_row(structured_info_elems))
47 |
48 | layout.append([sg.Text(text="Images/Drawing")])
49 | drawing_elems = ["Image", "Graph", "Canvas"]
50 | layout.append(make_button_row(drawing_elems))
51 |
52 | layout.append([sg.Text(text="Others")])
53 | other_elements = ["Slider", "Spin", "Multiline", "ProgressBar", "StatusBar"]
54 | for e in elements_names:
55 | if e not in (main_elements + container_elems + design_elems + organization_elems +
56 | structured_info_elems + drawing_elems + other_elements + ["Pane"]):
57 | other_elements.append(e)
58 | # print(e)
59 | layout.append(make_button_row(other_elements))
60 |
61 | return layout
62 |
63 |
64 | # Creates the main window layout from scratch. Necessary to reset the window.
65 | def make_main_window(tree):
66 |
67 | # STEP 1 define the layout
68 | tree_elem = sg.Tree(tree.get_tree_data(), ["Visible", "Key"],
69 | key="-TREE-", enable_events=True, show_expanded=True, col0_width=30,
70 | auto_size_columns=False, col_widths=[5, 10])
71 |
72 | frame_elem = sg.Frame("Elements", make_elements_frame())
73 |
74 | frame_prop = sg.Frame("Properties", [])
75 |
76 | stuff_row = [tree_elem, frame_elem, frame_prop]
77 |
78 | layout = [
79 | [sg.Text('M.M. - Welcome to a layout builder for PySimpleGUI, made with PySimpleGUI!'),
80 | sg.Button("About"), sg.Button('Preview'), sg.Button('Import'), sg.Button('Export'),
81 | sg.Button("Save"), sg.Button("Load"), sg.Button("Setup"), sg.Button('Apply Properties'),
82 | sg.Text(size=(20, 1), auto_size_text=True, key="Status_text", text_color="Light Green")],
83 | stuff_row
84 | ]
85 |
86 | # STEP 2 - create the window
87 | # print(location, size)
88 | w = sg.Window('My new window', layout, finalize=True, resizable=True)
89 | w.maximize()
90 |
91 | # Resizes the Main 3 elements according to the window size
92 | size = tuple(w.Size)
93 | padded_size = (size[0], size[1] - 50)
94 | elem_w, elem_h = padded_size[0] // len(stuff_row), padded_size[1]
95 | for elem in stuff_row:
96 | elem.set_size((elem_w, elem_h))
97 |
98 | return w, tree_elem, frame_elem, frame_prop, (elem_w, elem_h)
99 |
100 |
101 | # Main Function :)
102 | def main():
103 | print("Hello World!")
104 | sg.theme('DarkAmber') # No gray windows please!
105 |
106 | # auto_load from file if it exists
107 | try:
108 | f = open("autosave.txt")
109 | template_string_layout = f.read()
110 | f.close()
111 | except:
112 | print("There is no autosave file. Giving template layout.")
113 | template_string_layout = "[" \
114 | "[sg.Text(text='This is a very basic PySimpleGUI layout')], " \
115 | "[sg.InputText()], " \
116 | "[sg.Button('Button', key='-ExampleKey-'), sg.Button(button_text='Exit')] " \
117 | "]"
118 |
119 | # Create tree structure from the string layout
120 | # This tree structure is different from the TreeElement,
121 | # the TreeElement shows a simplified visual representation of this Tree
122 | tree = TreeNode.parse_string_layout(template_string_layout)
123 |
124 | # Create window
125 | window, tree_element, frame_elements, frame_properties, elem_size = make_main_window(tree)
126 |
127 | # Initial variable setup
128 | current_tree_node, current_property = None, None
129 | property_count = 0
130 | win2 = None
131 | win2_active = False
132 | current_time = time.time()
133 |
134 | # The event loop
135 | while True:
136 | # Read the event that happened and the values dictionary
137 | event, values = window.read()
138 |
139 | # print(event, values)
140 | window["Status_text"].update(value="")
141 |
142 | # If user closed window with X or if user clicked "Exit" button then exit
143 | if event in (sg.WIN_CLOSED, 'Exit'):
144 | break
145 |
146 | # I don't remember why this is here
147 | if event is None:
148 | continue
149 |
150 | # If user clicked button on an element in the tree
151 | if event == "-TREE-":
152 | tree_node = values[event][0]
153 | if tree_node != current_tree_node:
154 | if current_tree_node is not None:
155 | current_property.update(visible=False)
156 |
157 | if property_count > 50:
158 | window.close()
159 | window, tree_element, frame_elements, frame_properties, elem_size = make_main_window(tree)
160 | property_count = 0
161 |
162 | current_tree_node = tree_node
163 | current_property = tree_node.get_properties_layout(elem_size, property_count + 1)
164 | window.extend_layout(frame_properties, [[sg.pin(current_property)]])
165 | property_count += 1
166 |
167 | # If user clicked button on an element in the tree
168 | if event == "Apply Properties":
169 | selected_tree_element = values["-TREE-"]
170 | if current_tree_node is None:
171 | sg.popup("No element selected")
172 | else:
173 | try:
174 | if selected_tree_element:
175 | current_tree_node = selected_tree_element[0]
176 | if current_tree_node.element in ("Root", "Row"):
177 | sg.popup("Root/Row have no properties to apply.")
178 | else:
179 | current_tree_node.apply_properties(values, property_count)
180 | # sg.popup_auto_close("Properties Applied", auto_close_duration=3)
181 | window["Status_text"].update(value='Properties Applied')
182 | tree_element.update(values=tree.get_tree_data())
183 | except Exception as e:
184 | sg.popup_error("Error: " + str(e) +
185 | "\nError applying properties. Prob tried to enter a bad value to a property.")
186 |
187 | # If user clicked on an element to add
188 | if event[:6] == ELEMB_KEY:
189 | element_name = event[6:]
190 | selected_tree_element = values["-TREE-"]
191 | if not selected_tree_element:
192 | sg.popup("No element selected")
193 | else:
194 | selected_tree_element[0].add_tree_node(TreeNode(GUI_CLASSES[element_name]))
195 | tree_element.update(values=tree.get_tree_data())
196 |
197 | # If user clicked on the DeleteElement button
198 | if event == "DeleteElement":
199 | selected_tree_element = values["-TREE-"]
200 | if not selected_tree_element:
201 | sg.popup("No element selected")
202 | else:
203 | response = sg.popup_ok_cancel("Confirm deletion?", selected_tree_element[0])
204 | if response == "OK":
205 | selected_tree_element[0].remove_tree_node()
206 | tree_element.update(values=tree.get_tree_data())
207 |
208 | # If user clicked on the MoveUp element button
209 | if event == "MoveUp":
210 | selected_tree_element = values["-TREE-"]
211 | if not selected_tree_element:
212 | sg.popup("No element selected")
213 | else:
214 | selected_tree_element[0].move_tree_node("back")
215 | tree_element.update(values=tree.get_tree_data())
216 |
217 | # If user clicked on the MoveDown element button
218 | if event == "MoveDown":
219 | selected_tree_element = values["-TREE-"]
220 | if not selected_tree_element:
221 | sg.popup("No element selected")
222 | else:
223 | selected_tree_element[0].move_tree_node("forward")
224 | tree_element.update(values=tree.get_tree_data())
225 |
226 | # If user clicked on the Import button
227 | # This allows the user to import a layout to SimpleGUIBuilder from a string layout
228 | if event == "Import":
229 | text = sg.popup_get_text("ATTENTION! Importing will replace current layout.\n"
230 | "Also, importing '[]' is a great way to clear if you want to.")
231 | if text == "":
232 | sg.popup_auto_close("Text is empty. Importing cancelled.", auto_close_duration=3)
233 | elif text is None:
234 | # sg.popup_auto_close("Import cancelled.", auto_close_duration=3)
235 | pass
236 | else:
237 | try:
238 | tree = TreeNode.parse_string_layout(text)
239 | tree_element.update(values=tree.get_tree_data())
240 | except Exception as e:
241 | sg.popup_error(("Error: " + str(e) if len(e.args) == 1
242 | else "Error: " + str(e.args[0]) + "\n, in this place: " + str(e.args[1])) +
243 | "\n Importing cancelled or Error in importing (mistake in the text given).")
244 |
245 | # If user clicked on the Export button
246 | # Shows the correspondent string layout for the GUI being built
247 | if event == "Export":
248 | sg.popup_get_text("Current layout:", default_text=tree.layout_to_string())
249 |
250 | # If user clicked on the Save button
251 | # Export button but string layout goes to file
252 | if event == "Save":
253 | try:
254 | file_path = sg.popup_get_file("Where do you want to save your file?", save_as=True)
255 | if file_path is not None:
256 | with open(file_path, "w") as f:
257 | f.write(tree.layout_to_string())
258 | except Exception as e:
259 | sg.popup_error("Error: " + str(e) +
260 | "\nError in writing to file.")
261 |
262 | # If user clicked on the Load button
263 | # Import button but string layout comes from file
264 | if event == "Load":
265 | try:
266 | file_path = sg.popup_get_file("ATTENTION! Loading will replace current layout.\n"
267 | "Where is the file you want to load?")
268 | if file_path is not None:
269 | with open(file_path, "r") as f:
270 | text = f.read()
271 | if text == "":
272 | sg.popup_auto_close("Text is empty. Loading cancelled.", auto_close_duration=3)
273 | elif text is None:
274 | # sg.popup_auto_close("Import cancelled.", auto_close_duration=3)
275 | pass
276 | else:
277 | try:
278 | tree = TreeNode.parse_string_layout(text)
279 | tree_element.update(values=tree.get_tree_data())
280 | except Exception as e:
281 | sg.popup_error(("Error: " + str(e) if len(e.args) == 1
282 | else "Error: " + str(e.args[0]) + "\n, in this place: " + str(e.args[1])) +
283 | "\n Loading cancelled or Error in Loading (mistake in the text given).")
284 | except Exception as e:
285 | sg.popup_error("Error: " + str(e) +
286 | "\nError in reading from file.")
287 |
288 | # If user clicked on the Setup button
289 | # This button will create a file from the template in here
290 | # https://pysimplegui.trinket.io/demo-programs#/demo-programs/the-basic-pysimplegui-program
291 | # Except with the layout switched to the exported string layout
292 | # Most of it is in "tree.write_to_file" function
293 | if event == "Setup":
294 | try:
295 | file_path = sg.popup_get_file("Where do you want to save your Setup file?", save_as=True)
296 | if file_path is not None:
297 | with open(file_path, "w") as f:
298 | tree.write_to_file(f)
299 | except Exception as e:
300 | sg.popup_error("Error: " + str(e) +
301 | "\nError in writing to file.")
302 |
303 | # If user clicked on the About button
304 | # This button will show info I want to show for people to see if anyone wants to see xD
305 | if event == "About":
306 | sg.popup("I don't really like frontend but I really like the idea of giving my backend/terminal programs "
307 | "something more pleasurable to interact with.\n\n"
308 | "That's when I came across PySimpleGUI, a simple solution to quickly give my programs an interactive front. "
309 | "But in checking out PySimpleGUI I found I wanted more and had an idea: \n"
310 | "It would be nice if PySimpleGUI and therefore GUI making/designing was in itself more interactive.\n\n"
311 | "And that's how SimpleGUIBuilder came to be:\n"
312 | "A GUI for creating/designing GUI's for PySimpleGUI, made with PySimpleGUI.\n"
313 | "I hope this will be useful to people :)\n\n"
314 | "Made by Miguel Martins.\n\n"
315 | "If you want to support me: https://www.buymeacoffee.com/MMartins\n\n"
316 | "Version - 1.0.2")
317 |
318 | # Handle window preview
319 | # Check if in the meantime if the window was closed
320 | # Have to see if I need timeout and win2_active variable with the window being modal
321 | if win2_active:
322 | ev, v = win2.read(timeout=100)
323 | if ev == sg.WIN_CLOSED:
324 | win2_active = False
325 | win2.close()
326 |
327 | if not win2_active and event == "Preview":
328 | try:
329 | win2 = sg.Window('Preview', tree.get_layout(), finalize=True, modal=True)
330 | win2_active = True
331 | except Exception as e:
332 | sg.popup_error("Error: " + str(e) +
333 | "\nError in preview (mistake in the layout). You can try finding it with export")
334 |
335 | # autosave
336 | if time.time() - current_time > 180:
337 | try:
338 | with open("autosave.txt", "w") as f:
339 | f.write(tree.layout_to_string())
340 | current_time = time.time()
341 | except Exception as e:
342 | sg.popup_error("Error: " + str(e) +
343 | "\nError in autosaving to file")
344 |
345 | window.close()
346 | # autosave
347 | try:
348 | with open("autosave.txt", "w") as f:
349 | f.write(tree.layout_to_string())
350 | except Exception as e:
351 | sg.popup_error("Error: " + str(e) +
352 | "\nError in autosaving to file")
353 |
354 |
355 | if __name__ == "__main__":
356 | try:
357 | main()
358 | except Exception as e:
359 | sg.popup_error("Error: " + str(e) +
360 | "\nGeneral error. This error exists so the program doesn't just crash." +
361 | "\nAn error happened that I didn't catch properly, so no good message to help in what's wrong" +
362 | " except the prob bluberish above :(" +
363 | "\nI hope no one sees this, but you know how it is, it will happen ¯\_(ツ)_/¯")
364 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/pysimplegui_/tree_node.py:
--------------------------------------------------------------------------------
1 | import inspect
2 | import sys
3 | import ast
4 |
5 | import PySimpleGUI as sg
6 | import layout_parser as ps
7 |
8 | CONTAINER_ELEMENTS = (sg.Column, sg.Frame, sg.Tab, sg.TabGroup)
9 | CONTAINER_LAYOUT_ARG = {sg.Column: 0, sg.Frame: 1, sg.Tab: 1, sg.TabGroup: 0}
10 |
11 | GUI_CLASSES = dict(inspect.getmembers(sys.modules["PySimpleGUI"], inspect.isclass))
12 |
13 |
14 | # This custom structure is core of the whole thing
15 | # It's my centralized way of storing a layout, built it, modify it, modify its initial parameters
16 | # String layouts are translated into the Tree structure and vice-versa through the layout_parser
17 | # which could also be called string_layout_interpreter? anyway
18 | class TreeNode:
19 |
20 | def __init__(self, element, parent=None):
21 | # element class
22 | self.element = element
23 | self.parent = parent
24 |
25 | self.container = []
26 | if element in CONTAINER_ELEMENTS:
27 | self.container.append(TreeNode("Root"))
28 |
29 | self.necessary_args_name = []
30 | self.necessary_args = {}
31 | self.optional_args = {}
32 | # this exists so it can compare and not print all optional args when creating the string layout
33 | # optional args in export
34 | self.default_optional_args = {}
35 | # this exists so the user can give first optional args without key
36 | # Example: Button("OMG") instead of Button(button_text="less omg")
37 | self.default_optional_args_name = []
38 | self.args_docs = {}
39 |
40 | if self.element not in ("Row", "Root"):
41 | self.set_properties()
42 |
43 | # If element is ("Row", "Root") give, else give the Class name
44 | def __str__(self):
45 | if isinstance(self.element, str):
46 | return self.element
47 |
48 | return self.element.__name__
49 |
50 | # If the element is a container Element, i.e. can contain other elements
51 | def is_container(self):
52 | return self.element in ("Row", "Root") + CONTAINER_ELEMENTS
53 |
54 | # Adds a child node to this node, creates Row element if necessary
55 | def add_tree_node(self, tree_node):
56 | # how does this work with container elements - it has logic outside it
57 | # and if someone wants to add something and self is not a container then nothing happens
58 | if self.is_container():
59 | if self.element == "Row" or tree_node.element == "Row":
60 | self.container.append(tree_node)
61 | tree_node.parent = self
62 | return self
63 |
64 | row = TreeNode("Row", parent=self).add_tree_node(tree_node)
65 | tree_node.parent = row
66 | self.container.append(row)
67 | else:
68 | sg.popup("Target element is not a Container/Root/Row")
69 |
70 | # Removes itself from parent node
71 | def remove_tree_node(self):
72 | if self.parent is None:
73 | sg.popup("Cannot remove Root element")
74 | else:
75 | self.parent.container.remove(self)
76 |
77 | # Moves tree node location in parent node
78 | def move_tree_node(self, direction):
79 | if self.parent is None:
80 | sg.popup("Cannot move Root element")
81 | else:
82 | container = self.parent.container
83 | position = container.index(self)
84 | if direction == "back":
85 | container[position], container[position - 1] = container[position - 1], container[position]
86 | else:
87 | next_pos = position + 1 if position + 1 != len(container) else 0
88 | # if next_pos == len(container):
89 | # next_pos = 0
90 | container[position], container[next_pos] = container[next_pos], container[position]
91 |
92 | # Returns the layout object of the Tree, which is given to the Preview window to render
93 | def get_layout(self):
94 | # if self.element in ("Root", "Row"):
95 | if self.container:
96 | if self.element in ("Root", "Row"):
97 | return [x.get_layout() for x in self.container]
98 |
99 | return self.element(*[self.necessary_args[x] if x != "layout" else self.container[0].get_layout()
100 | for x in self.necessary_args_name], **self.optional_args)
101 |
102 | # if element is empty root or row
103 | if self.element in ("Root", "Row"):
104 | if self.element == "Root":
105 | raise Exception("Root cannot be empty")
106 | return []
107 |
108 | return self.element(*[self.necessary_args[x] for x in self.necessary_args_name], **self.optional_args)
109 |
110 | # Returns the tree data representation of the Tree,
111 | # which is given to the TreeElement for visualization and element picking
112 | def get_tree_data(self, treedata=None, parent=""):
113 | if treedata is None:
114 | treedata = sg.TreeData()
115 |
116 | treedata.Insert(parent, self, str(self),
117 | [self.optional_args["visible"], self.optional_args["key"]]
118 | if self.element not in ("Row", "Root") else ["_", "_"])
119 |
120 | for x in self.container:
121 | x.get_tree_data(treedata, self)
122 |
123 | return treedata
124 |
125 | # Builds and returns a Tree structure from a previously parsed layout
126 | # It is separate from the parse_string_layout for recursion purposes
127 | @staticmethod
128 | def make_tree_from_parsed_layout(parsed_layout):
129 | tree = TreeNode("Root")
130 |
131 | for parsed_row in parsed_layout:
132 | row = TreeNode("Row")
133 | tree.add_tree_node(row)
134 | for parsed_elem in parsed_row:
135 | print(parsed_elem)
136 | elem = TreeNode(GUI_CLASSES[parsed_elem[0]])
137 | row.add_tree_node(elem)
138 | for i in range(len(elem.necessary_args_name)):
139 | elem.necessary_args[elem.necessary_args_name[i]] = parsed_elem[1][i]
140 | necessary_to_optional = parsed_elem[1][len(elem.necessary_args_name):]
141 | if necessary_to_optional:
142 | for i in range(len(necessary_to_optional)):
143 | elem.optional_args[elem.default_optional_args_name[i]] = necessary_to_optional[i]
144 | for key in parsed_elem[2]:
145 | elem.optional_args[key] = parsed_elem[2][key]
146 | if GUI_CLASSES[parsed_elem[0]] in CONTAINER_ELEMENTS:
147 | elem.container = [TreeNode.make_tree_from_parsed_layout(elem.necessary_args["layout"])]
148 |
149 | return tree
150 |
151 | # Builds and returns a Tree structure from a string layout
152 | # The magic parsing of the string layout has it's own file to be able to contain all the fun xD
153 | @staticmethod
154 | def parse_string_layout(string_layout):
155 | parsed_layout = ps.parse_string_layout(string_layout)
156 |
157 | tree = TreeNode.make_tree_from_parsed_layout(parsed_layout)
158 |
159 | return tree
160 |
161 | # Converts value to string, needs its own function because string needs quotations to "string"
162 | @staticmethod
163 | def convert_to_str(value):
164 | if isinstance(value, str):
165 | return '"{}"'.format(value)
166 | else:
167 | return str(value)
168 |
169 | # Builds and returns a string layout from a Tree structure
170 | def layout_to_string(self):
171 | string_layout = ""
172 |
173 | if self.element in ("Root", "Row"):
174 | string_layout = "[{}]".format(", ".join([x.layout_to_string() for x in self.container]))
175 | else:
176 | args = [TreeNode.convert_to_str(
177 | self.necessary_args[arg_name]) if self.element not in CONTAINER_ELEMENTS or arg_name != "layout"
178 | else self.container[0].layout_to_string()
179 | for arg_name in self.necessary_args_name]
180 |
181 | kargs = ["{0}={1}".format(arg_name, TreeNode.convert_to_str(arg_value))
182 | for arg_name, arg_value in self.optional_args.items()
183 | if self.optional_args[arg_name] != self.default_optional_args[arg_name]]
184 |
185 | string_layout = "sg.{elem_name}({args})".\
186 | format(elem_name=self.element.__name__, args=", ".join((args + kargs)))
187 |
188 | return string_layout
189 |
190 | # Gets and creates all the necessary data structures with the argument info of the element
191 | # Also does that to get property tooltips from the Element's docs
192 | def set_properties(self):
193 | args_c = self.element.__init__.__code__.co_argcount
194 | args = self.element.__init__.__code__.co_varnames
195 | defaults = self.element.__init__.__defaults__
196 |
197 | # parse argument tooltips from docs
198 | docs = self.element.__init__.__doc__
199 | current_text = ""
200 | inside_param = False
201 | have_name = False
202 | current_name = ""
203 | for char in docs:
204 | if not inside_param:
205 | if char not in [" ", "\n", "\t", "\r"]:
206 | current_text += char
207 | if current_text == ":param":
208 | inside_param = True
209 | current_text = ""
210 | if char == "\n":
211 | current_text = ""
212 | else:
213 | current_text += char
214 | if not have_name:
215 | if char == ":":
216 | current_name = current_text[1:-1]
217 | current_text = ""
218 | have_name = True
219 | else:
220 | if char == "\n":
221 | self.args_docs[current_name] = current_text[:-1]
222 | current_text = ""
223 | inside_param = False
224 | have_name = False
225 | current_name = ""
226 |
227 | # print(self.args_docs)
228 |
229 | real_args = args[1:args_c]
230 |
231 | if not defaults:
232 | necessary_args = real_args
233 | def_args = []
234 | else:
235 | necessary_args = real_args[:-len(defaults)]
236 | def_args = real_args[-len(defaults):]
237 |
238 | # print(real_args)
239 | # print(necessary_args)
240 | # print(def_args)
241 |
242 | for arg in necessary_args:
243 | self.necessary_args_name.append(arg)
244 | self.necessary_args[arg] = None
245 |
246 | for i, arg in enumerate(def_args):
247 | self.optional_args[arg] = defaults[i]
248 | self.default_optional_args_name.append(arg)
249 |
250 | # print(self.necessary_args)
251 | # print(self.optional_args)
252 | self.default_optional_args = {k: v for k, v in self.optional_args.items()}
253 |
254 | # Returns the layout that is shown on the right, with all the properties available for change for the element
255 | def get_properties_layout(self, elem_size, property_count):
256 | layout = []
257 |
258 | layout.append([sg.Text(text="Necessary Args")])
259 |
260 | for arg_name in self.necessary_args_name:
261 | if arg_name == "layout":
262 | continue
263 | value = str(self.necessary_args[arg_name] if not isinstance(self.necessary_args[arg_name], str)
264 | else '"' + self.necessary_args[arg_name] + '"')
265 | layout.append([sg.Text(text=arg_name,
266 | tooltip=(self.args_docs[arg_name] if arg_name in self.args_docs else None)),
267 | sg.Input(default_text=value, key=str(property_count) + "_" + arg_name)])
268 |
269 | layout.append([sg.Text(text="Optional Args")])
270 |
271 | for arg_name, arg_value in self.optional_args.items():
272 | value = str(arg_value if not isinstance(arg_value, str)
273 | else '"' + arg_value + '"')
274 | layout.append([sg.Text(text=arg_name,
275 | tooltip=(self.args_docs[arg_name] if arg_name in self.args_docs else None)),
276 | sg.Input(default_text=value, key=str(property_count) + "_" + arg_name)])
277 |
278 | return sg.Column(layout, scrollable=True, vertical_scroll_only=True, size=elem_size)
279 |
280 | # Applies the properties
281 | # Some day this might be automatic or provide a notice like "hey, you have unsaved properties, save?"
282 | # But today is not that day
283 | def apply_properties(self, values, property_count):
284 | for arg_name in self.necessary_args:
285 | key = str(property_count) + "_" + arg_name
286 | if key in values:
287 | self.necessary_args[arg_name] = ast.literal_eval(values[key])
288 |
289 | for arg_name in self.optional_args:
290 | key = str(property_count) + "_" + arg_name
291 | if key in values:
292 | self.optional_args[arg_name] = ast.literal_eval(values[key])
293 |
294 | # Receives a file and writes to it the template file with in layout in it
295 | # Dunno if this belongs here or in main, but it's here for now
296 | def write_to_file(self, file):
297 | file_text = '''import PySimpleGUI as sg
298 |
299 | # Template file taken from here https://pysimplegui.trinket.io/demo-programs#/demo-programs/the-basic-pysimplegui-program
300 | sg.theme('DarkAmber') # No gray windows please!
301 |
302 | # STEP 1 define the layout
303 | layout = ''' + self.layout_to_string() + '''
304 |
305 | #STEP 2 - create the window
306 | window = sg.Window('Template Window', layout, resizable=True)
307 | # If you don't want to start with the window maximized comment this bellow vvv
308 | #window.maximize()
309 |
310 | # STEP3 - the event loop
311 | while True:
312 | event, values = window.read() # Read the event that happened and the values dictionary
313 | print(event, values)
314 | if event == sg.WIN_CLOSED or event == 'Exit': # If user closed window with X or if user clicked "Exit" button then exit
315 | break
316 |
317 | window.close()'''
318 | file.write(file_text)
319 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/tkinter_/README.md:
--------------------------------------------------------------------------------
1 | How to Use:
2 | ===========
3 |
4 | * Create and activate a virtual environment:
5 |
6 | virtualenv venv
7 |
8 | venv\Scripts\Activate
9 |
10 | * Install dependencies:
11 |
12 | pip install -r requirements.txt
13 |
14 | * Run the GUI demo:
15 |
16 | python main.py
17 |
18 |
19 | Note:
20 | ----
21 | This Demo is taken from this repository and updated for python 3:
22 | https://github.com/amandeep511997/Text-Editor
23 |
24 |
25 | Sample:
26 | ----
27 | Text Editor:
28 |
29 | 
30 |
31 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/tkinter_/icons/align-center.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/tkinter_/icons/align-center.png
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/tkinter_/icons/align-justify.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/tkinter_/icons/align-justify.png
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/tkinter_/icons/align-left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/tkinter_/icons/align-left.png
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/tkinter_/icons/align-right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/tkinter_/icons/align-right.png
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/tkinter_/icons/bold.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/tkinter_/icons/bold.png
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/tkinter_/icons/copy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/tkinter_/icons/copy.png
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/tkinter_/icons/cut.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/tkinter_/icons/cut.png
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/tkinter_/icons/find.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/tkinter_/icons/find.png
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/tkinter_/icons/font-color.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/tkinter_/icons/font-color.png
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/tkinter_/icons/highlight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/tkinter_/icons/highlight.png
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/tkinter_/icons/italic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/tkinter_/icons/italic.png
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/tkinter_/icons/new.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/tkinter_/icons/new.png
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/tkinter_/icons/open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/tkinter_/icons/open.png
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/tkinter_/icons/paste.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/tkinter_/icons/paste.png
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/tkinter_/icons/redo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/tkinter_/icons/redo.png
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/tkinter_/icons/save.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/tkinter_/icons/save.png
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/tkinter_/icons/strike.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/tkinter_/icons/strike.png
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/tkinter_/icons/underline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/tkinter_/icons/underline.png
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/tkinter_/icons/undo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/tkinter_/icons/undo.png
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/tkinter_/requirements.txt:
--------------------------------------------------------------------------------
1 | pillow
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/tkinter_/text-editor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/tkinter_/text-editor.png
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/toga_/sample.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/toga_/sample.gif
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # OSX useful to ignore
7 | *.DS_Store
8 | .AppleDouble
9 | .LSOverride
10 |
11 | # Thumbnails
12 | ._*
13 |
14 | # Files that might appear in the root of a volume
15 | .DocumentRevisions-V100
16 | .fseventsd
17 | .Spotlight-V100
18 | .TemporaryItems
19 | .Trashes
20 | .VolumeIcon.icns
21 | .com.apple.timemachine.donotpresent
22 |
23 | # Directories potentially created on remote AFP share
24 | .AppleDB
25 | .AppleDesktop
26 | Network Trash Folder
27 | Temporary Items
28 | .apdisk
29 |
30 | # C extensions
31 | *.so
32 |
33 | # Distribution / packaging
34 | .Python
35 | env/
36 | build/
37 | develop-eggs/
38 | dist/
39 | downloads/
40 | eggs/
41 | .eggs/
42 | lib/
43 | lib64/
44 | parts/
45 | sdist/
46 | var/
47 | *.dist-info/
48 | *.egg-info/
49 | .installed.cfg
50 | *.egg
51 |
52 | # IntelliJ Idea family of suites
53 | .idea
54 | *.iml
55 | ## File-based project format:
56 | *.ipr
57 | *.iws
58 | ## mpeltonen/sbt-idea plugin
59 | .idea_modules/
60 |
61 | # Briefcase log files
62 | logs/
63 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/CHANGELOG:
--------------------------------------------------------------------------------
1 | # Toga Blog Post Release Notes
2 |
3 | ## 0.0.1 (18 Apr 2023)
4 |
5 | * Initial release
6 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Copyright (c) 2023, Alyssa Caples
3 | All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without modification,
6 | are permitted provided that the following conditions are met:
7 |
8 | * Redistributions of source code must retain the above copyright notice, this
9 | list of conditions and the following disclaimer.
10 |
11 | * Redistributions in binary form must reproduce the above copyright notice, this
12 | list of conditions and the following disclaimer in the documentation and/or
13 | other materials provided with the distribution.
14 |
15 | * Neither the name of the copyright holder nor the names of its
16 | contributors may be used to endorse or promote products derived from this
17 | software without specific prior written permission.
18 |
19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22 | IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
23 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
26 | OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
27 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
28 | OF THE POSSIBILITY OF SUCH DAMAGE.
29 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/README.rst:
--------------------------------------------------------------------------------
1 | Toga Blog Post
2 | ==============
3 |
4 | **This cross-platform app was generated by** `Briefcase`_ **- part of**
5 | `The BeeWare Project`_. **If you want to see more tools like Briefcase, please
6 | consider** `becoming a financial member of BeeWare`_.
7 |
8 | This will be the app for creating a blog post application
9 |
10 | .. _`Briefcase`: https://briefcase.readthedocs.io/
11 | .. _`The BeeWare Project`: https://beeware.org/
12 | .. _`becoming a financial member of BeeWare`: https://beeware.org/contributing/membership
13 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/pyproject.toml:
--------------------------------------------------------------------------------
1 | # This project was generated with Unknown using template: https://github.com/beeware/briefcase-template@v0.3.13
2 | [tool.briefcase]
3 | project_name = "Toga Blog Post"
4 | bundle = "com.example"
5 | version = "0.0.1"
6 | url = "https://example.com/togablogpost"
7 | license = "BSD license"
8 | author = "Alyssa Caples"
9 | author_email = "alyssa@depotanalytics.co"
10 |
11 | [tool.briefcase.app.togablogpost]
12 | formal_name = "Toga Blog Post"
13 | description = "This will be the app for creating a blog post application"
14 | long_description = """More details about the app should go here.
15 | """
16 | icon = "src/togablogpost/resources/togablogpost"
17 | sources = [
18 | "src/togablogpost",
19 | ]
20 | test_sources = [
21 | "tests",
22 | ]
23 |
24 | requires = [
25 | ]
26 | test_requires = [
27 | "pytest",
28 | ]
29 |
30 | [tool.briefcase.app.togablogpost.macOS]
31 | requires = [
32 | "toga-cocoa~=0.3.0",
33 | "std-nslog~=1.0.0"
34 | ]
35 |
36 | [tool.briefcase.app.togablogpost.linux]
37 | requires = [
38 | "toga-gtk~=0.3.0",
39 | ]
40 |
41 | [tool.briefcase.app.togablogpost.linux.system.debian]
42 | system_requires = [
43 | # Needed to compile pycairo wheel
44 | 'libcairo2-dev',
45 | # Needed to compile PyGObject wheel
46 | 'libgirepository1.0-dev',
47 | ]
48 |
49 | system_runtime_requires = [
50 | # Needed to provide GTK
51 | "libgtk-3-0",
52 | # Needed to provide GI bindings to GTK
53 | "libgirepository-1.0-1",
54 | "gir1.2-gtk-3.0",
55 | # Needed to provide WebKit2 at runtime
56 | # "libwebkit2gtk-4.0-37",
57 | # "gir1.2-webkit2-4.0",
58 | ]
59 |
60 | [tool.briefcase.app.togablogpost.linux.system.rhel]
61 | system_requires = [
62 | # Needed to compile pycairo wheel
63 | 'cairo-gobject-devel',
64 | # Needed to compile PyGObject wheel
65 | 'gobject-introspection-devel',
66 | ]
67 |
68 | system_runtime_requires = [
69 | # Needed to support Python bindings to GTK
70 | "gobject-introspection",
71 | # Needed to provide GTK
72 | "gtk3",
73 | # Needed to provide WebKit2 at runtime
74 | # "webkit2gtk3",
75 | ]
76 |
77 | [tool.briefcase.app.togablogpost.linux.system.arch]
78 | system_requires = [
79 | # Needed to compile pycairo wheel
80 | 'cairo',
81 | # Needed to compile PyGObject wheel
82 | 'gobject-introspection',
83 | ]
84 |
85 | system_runtime_requires = [
86 | # Needed to provide GTK
87 | "gtk",
88 | # Needed to provide WebKit2 at runtime
89 | # "webkit2gtk",
90 | ]
91 |
92 | [tool.briefcase.app.togablogpost.linux.appimage]
93 | system_requires = [
94 | "libcairo2-dev",
95 | "libgirepository1.0-dev",
96 | "libgtk-3-dev",
97 | "libpango1.0-dev",
98 | "librsvg2-dev",
99 | # Needed to support Webkit2
100 | # "gir1.2-webkit2-4.0",
101 | # "libwebkit2gtk-4.0-dev",
102 | ]
103 | linuxdeploy_plugins = [
104 | "DEPLOY_GTK_VERSION=3 gtk",
105 | ]
106 |
107 | [tool.briefcase.app.togablogpost.linux.flatpak]
108 | flatpak_runtime = "org.gnome.Platform"
109 | flatpak_runtime_version = "42"
110 | flatpak_sdk = "org.gnome.Sdk"
111 |
112 | [tool.briefcase.app.togablogpost.windows]
113 | requires = [
114 | "toga-winforms~=0.3.0",
115 | ]
116 |
117 | # Mobile deployments
118 | [tool.briefcase.app.togablogpost.iOS]
119 | requires = [
120 | "toga-iOS~=0.3.0",
121 | "std-nslog~=1.0.0"
122 | ]
123 |
124 | [tool.briefcase.app.togablogpost.android]
125 | requires = [
126 | "toga-android~=0.3.0"
127 | ]
128 |
129 | # Web deployments
130 | [tool.briefcase.app.togablogpost.web]
131 | requires = [
132 | "toga-web~=0.3.0",
133 | ]
134 | style_framework = "Bootstrap v4.6"
135 |
136 | # 2023-02-26: This is a workaround for briefcase#1089/pyscript#1204.
137 | extra_pyscript_toml_content = """
138 | [[runtimes]]
139 | src = "https://cdn.jsdelivr.net/pyodide/v0.22.1/full/pyodide.js"
140 | name = "Python runtime"
141 | lang = "python"
142 | """
143 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/__init__.py
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/__main__.py:
--------------------------------------------------------------------------------
1 | from togablogpost.app import main
2 |
3 | if __name__ == '__main__':
4 | main().main_loop()
5 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/app.py:
--------------------------------------------------------------------------------
1 | import toga
2 | from toga.style import Pack
3 | from toga.style.pack import COLUMN
4 | import random
5 | from pathlib import Path
6 |
7 | def read_in_words():
8 | file_path = Path(__file__).parent / "english_words.txt"
9 | lines = []
10 | with open(file_path, "r") as file:
11 | for line in file:
12 | lines.append(line.strip()) # add each line to the list, stripped of any whitespace
13 | return lines
14 |
15 | class Hangman(toga.App):
16 | valid_alphabet = set("abcdefghijklmnopqrstuvwxyz")
17 | all_words = read_in_words()
18 |
19 | #styles
20 | game_font = "Century Schoolbook"
21 | title_style = Pack(padding=(0, 5), font_family=game_font, font_size=20, text_align="center", font_weight="bold")
22 | button_style = Pack(padding=(0, 5), font_family=game_font, font_size=15, text_align="center")
23 | game_over_style = Pack(padding=(0, 5), font_family=game_font, font_size=50, text_align="center", font_weight="bold")
24 | secret_word_style = Pack(padding=(0, 5), font_family=game_font, font_size=35, text_align="center", font_weight="bold")
25 |
26 | def startup(self):
27 | self.secret_word = ""
28 |
29 | main_box = toga.Box(style=Pack(direction=COLUMN))
30 | main_box.add(toga.Label("Welcome to Hangman!", style=Hangman.title_style ))
31 | main_box.add(toga.Button("Play Hangman" , style=Hangman.button_style, on_press=self.start_new_game))
32 | main_box.add(toga.Button("Start Game with Own Word", style=Hangman.button_style, on_press=self.enter_own_word))
33 |
34 | self.main_window = toga.MainWindow(title="Hangman", position=(100, 100), size=(800, 600))
35 | self.main_window.content = main_box
36 | self.main_window.show()
37 |
38 | def enter_own_word(self, widget):
39 | box = toga.Box(style=Pack(direction=COLUMN))
40 | box.add(toga.Label("Enter your secret word:", style=Hangman.title_style ))
41 | box.add(toga.TextInput(placeholder="Enter Your Word Here", on_change=self.update_secret_word_handler)) #could add valitors here if needed
42 | box.add(toga.Button("Start Game", style=Hangman.button_style, on_press=self.start_new_game))
43 | self.main_window.content = box
44 | self.main_window.show()
45 |
46 | def start_new_game(self, widget):
47 |
48 | if widget.text == "Play Hangman" or widget.text == "Start a New Game":
49 | self.secret_word = self.get_word() # get a random word
50 | print(self.secret_word)
51 |
52 | #initialize variables used for each game
53 | self.letters = set(self.secret_word)
54 | self.used_letters = []
55 | self.lives = 7
56 | self.correct_letters = set()
57 | self.hangman_image_path = str(Path(__file__).parent / "hangman_images" )
58 | self.hangman_image = "/hangman-0" #set to beginning
59 | self.revealed_word = ["_"] * len(self.secret_word)
60 | self.game_state()
61 |
62 | def get_word(self):
63 | random_item = random.choice(Hangman.all_words)
64 | return random_item
65 |
66 | def game_state(self):
67 | box = toga.Box(style=Pack(direction=COLUMN))
68 |
69 | #heading
70 | box.add(toga.Label("Guess a Letter!", style=Hangman.title_style ))
71 | box.add(toga.Label("Word: " + " ".join(self.revealed_word), style=Hangman.secret_word_style))
72 | box.add(toga.TextInput(validators=[self.max_length, self.letters_only], on_change=self.process_letter))
73 |
74 | # used letters + lives
75 | box.add(toga.Label("Used Letters: " + " ".join(self.used_letters), style=Hangman.title_style))
76 | box.add(toga.Label("Lives: " + str(self.lives), style=Hangman.title_style))
77 |
78 | #buttons
79 | box.add(toga.Button("Start a New Game", style=Hangman.button_style, on_press=self.start_new_game))
80 | box.add(toga.Button("Play Hangman with Own Word", style=Hangman.button_style, on_press=self.enter_own_word))
81 |
82 | #image
83 | box.add(toga.ImageView(image=self.hangman_image_path + self.hangman_image + ".PNG"))
84 |
85 | self.main_window.content = box
86 |
87 | def kill_game(self, widget):
88 | self.end_game(victory=False)
89 |
90 | def process_letter(self, widget):
91 | self.used_letters.append(widget.value)
92 | if widget.value in self.letters: #if letter is in word, replace underscore with letter
93 | self.correct_letters = self.correct_letters.union(widget.value)
94 |
95 | for i in range(len(self.secret_word)):
96 | if self.secret_word[i] in self.correct_letters:
97 | self.revealed_word[i] = self.secret_word[i]
98 |
99 | else: #else, remove lives & change image
100 | self.lives = self.lives - 1
101 | number = int(self.hangman_image[-1]) + 1
102 | self.hangman_image = self.hangman_image[:-1] + str(number)
103 |
104 | if self.lives == 0: #check if game is over
105 | self.end_game(victory=False)
106 | elif "_" not in self.revealed_word:
107 | self.end_game(victory=True)
108 | else:
109 | self.game_state()
110 |
111 | def end_game(self, victory=False):
112 | box = toga.Box(style=Pack(direction=COLUMN))
113 |
114 | if victory:
115 | box.add(toga.Label("You won! :)", style=Hangman.game_over_style))
116 | img_path = self.hangman_image_path + "/victory.PNG"
117 | else:
118 | box.add(toga.Label("You lost! :C", style=Hangman.game_over_style))
119 | img_path = self.hangman_image_path + "/death.PNG"
120 |
121 | box.add(toga.Label("The word was: " + self.secret_word, style=Hangman.secret_word_style))
122 | box.add(toga.Button("Start a New Game", style=Hangman.button_style, on_press=self.start_new_game))
123 | box.add(toga.Button("Play Hangman with Own Word", style=Hangman.button_style, on_press=self.enter_own_word))
124 |
125 | box.add(toga.ImageView(image=img_path))
126 | self.main_window.content = box
127 | self.main_window.show()
128 |
129 |
130 |
131 | #save variable handler
132 | def update_secret_word_handler(self, widget):
133 | self.secret_word = widget.value
134 |
135 | # validators
136 | def letters_only(self, letter):
137 | lowercase = letter.lower()
138 | if lowercase in Hangman.valid_alphabet:
139 | return None
140 | else:
141 | return "Please enter a letter"
142 |
143 | def max_length(self, letter):
144 | if len(letter) > 1:
145 | return "Please enter only one letter"
146 | else:
147 | return None
148 |
149 | def main():
150 | return Hangman()
151 |
152 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/english_words.txt:
--------------------------------------------------------------------------------
1 | abruptly
2 | absurd
3 | abyss
4 | affix
5 | askew
6 | avenue
7 | awkward
8 | axiom
9 | azure
10 | bagpipes
11 | bandwagon
12 | banjo
13 | bayou
14 | beekeeper
15 | bikini
16 | blitz
17 | blizzard
18 | boggle
19 | bookworm
20 | boxcar
21 | boxful
22 | buckaroo
23 | buffalo
24 | buffoon
25 | buxom
26 | buzzard
27 | buzzing
28 | buzzwords
29 | caliph
30 | cobweb
31 | cockiness
32 | croquet
33 | crypt
34 | curacao
35 | cycle
36 | daiquiri
37 | dirndl
38 | disavow
39 | dizzying
40 | duplex
41 | dwarves
42 | embezzle
43 | equip
44 | espionage
45 | euouae
46 | exodus
47 | faking
48 | fishhook
49 | fixable
50 | fjord
51 | flapjack
52 | flopping
53 | fluffiness
54 | flyby
55 | foxglove
56 | frazzled
57 | frizzled
58 | fuchsia
59 | funny
60 | gabby
61 | galaxy
62 | galvanize
63 | gazebo
64 | giaour
65 | gizmo
66 | glowworm
67 | glyph
68 | gnarly
69 | gnostic
70 | gossip
71 | grogginess
72 | haiku
73 | haphazard
74 | hyphen
75 | iatrogenic
76 | icebox
77 | injury
78 | ivory
79 | ivy
80 | jackpot
81 | jaundice
82 | jawbreaker
83 | jaywalk
84 | jazziest
85 | jazzy
86 | jelly
87 | jigsaw
88 | jinx
89 | jiujitsu
90 | jockey
91 | jogging
92 | joking
93 | jovial
94 | joyful
95 | juicy
96 | jukebox
97 | jumbo
98 | kayak
99 | kazoo
100 | keyhole
101 | khaki
102 | kilobyte
103 | kiosk
104 | kitsch
105 | kiwifruit
106 | klutz
107 | knapsack
108 | larynx
109 | lengths
110 | lucky
111 | luxury
112 | lymph
113 | marquis
114 | matrix
115 | megahertz
116 | microwave
117 | mnemonic
118 | mystify
119 | naphtha
120 | nightclub
121 | nowadays
122 | numbskull
123 | nymph
124 | onyx
125 | ovary
126 | oxidize
127 | oxygen
128 | pajama
129 | peekaboo
130 | phlegm
131 | pixel
132 | pizazz
133 | pneumonia
134 | polka
135 | pshaw
136 | psyche
137 | puppy
138 | puzzling
139 | quartz
140 | queue
141 | quips
142 | quixotic
143 | quiz
144 | quizzes
145 | quorum
146 | razzmatazz
147 | rhubarb
148 | rhythm
149 | rickshaw
150 | schnapps
151 | scratch
152 | shiv
153 | snazzy
154 | sphinx
155 | spritz
156 | squawk
157 | staff
158 | strength
159 | strengths
160 | stretch
161 | stronghold
162 | stymied
163 | subway
164 | swivel
165 | syndrome
166 | thriftless
167 | thumbscrew
168 | topaz
169 | transcript
170 | transgress
171 | transplant
172 | triphthong
173 | twelfth
174 | twelfths
175 | unknown
176 | unworthy
177 | unzip
178 | uptown
179 | vaporize
180 | vixen
181 | vodka
182 | voodoo
183 | vortex
184 | walkway
185 | waltz
186 | wave
187 | wavy
188 | waxy
189 | wellspring
190 | wheezy
191 | whiskey
192 | whizzing
193 | whomever
194 | wimpy
195 | witchcraft
196 | wizard
197 | woozy
198 | wristwatch
199 | wyvern
200 | xylophone
201 | yachtsman
202 | yippee
203 | yoked
204 | youthful
205 | yummy
206 | zephyr
207 | zigzag
208 | zigzagging
209 | zilch
210 | zipper
211 | zodiac
212 | zombie
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/hangman_images/death.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/hangman_images/death.PNG
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/hangman_images/hangman-0.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/hangman_images/hangman-0.PNG
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/hangman_images/hangman-1.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/hangman_images/hangman-1.PNG
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/hangman_images/hangman-2.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/hangman_images/hangman-2.PNG
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/hangman_images/hangman-3.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/hangman_images/hangman-3.PNG
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/hangman_images/hangman-4.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/hangman_images/hangman-4.PNG
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/hangman_images/hangman-5.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/hangman_images/hangman-5.PNG
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/hangman_images/hangman-6.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/hangman_images/hangman-6.PNG
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/hangman_images/victory.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/hangman_images/victory.PNG
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/resources/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/resources/__init__.py
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/resources/togablogpost.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/resources/togablogpost.icns
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/resources/togablogpost.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/resources/togablogpost.ico
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/resources/togablogpost.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/src/togablogpost/resources/togablogpost.png
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/tests/__init__.py
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/tests/test_app.py:
--------------------------------------------------------------------------------
1 | def test_first():
2 | "An initial test for the app"
3 | assert 1 + 1 == 2
4 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/toga_/togablogpost/tests/togablogpost.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import tempfile
4 | from pathlib import Path
5 |
6 | import pytest
7 |
8 |
9 | def run_tests():
10 | project_path = Path(__file__).parent.parent
11 | os.chdir(project_path)
12 |
13 | # Determine any args to pass to pytest. If there aren't any,
14 | # default to running the whole test suite.
15 | args = sys.argv[1:]
16 | if len(args) == 0:
17 | args = ["tests"]
18 |
19 | returncode = pytest.main(
20 | [
21 | # Turn up verbosity
22 | "-vv",
23 | # Disable color
24 | "--color=no",
25 | # Overwrite the cache directory to somewhere writable
26 | "-o",
27 | f"cache_dir={tempfile.gettempdir()}/.pytest_cache",
28 | ] + args
29 | )
30 |
31 | print(f">>>>>>>>>> EXIT {returncode} <<<<<<<<<<")
32 |
33 |
34 | if __name__ == "__main__":
35 | run_tests()
36 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/wxPython_/README.md:
--------------------------------------------------------------------------------
1 | How to Use:
2 | ===========
3 |
4 | * Create and activate a virtual environment:
5 |
6 | virtualenv venv
7 |
8 | venv\Scripts\Activate
9 |
10 | * Install dependencies:
11 |
12 | pip install -r requirements.txt
13 |
14 | * Run the GUI demo:
15 |
16 | python main.py
17 |
18 |
19 | Note:
20 | ----
21 | This Demo is taken from this repository:
22 | https://github.com/Andereoo/WxPyBrowser
23 |
24 | Sample:
25 | ----
26 | Web Browser:
27 |
28 | 
29 |
30 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/wxPython_/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MihailCosmin/cookiecutter-python-gui-application/696a6d56c4223510c51716b0ad16f1133253a4bc/{{cookiecutter.project_slug}}/demo/wxPython_/demo.gif
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/wxPython_/main.py:
--------------------------------------------------------------------------------
1 | import wx
2 | import wx.aui
3 | import wx.lib.platebtn
4 | import wx.html2
5 | import wx.lib.agw.aui.tabart
6 |
7 |
8 | class HistoryPage(wx.Panel):
9 | def __init__(self, parent, history_var):
10 | wx.Panel.__init__(self, parent=parent)
11 |
12 | self.open = True
13 | self.parent = parent
14 | self.history_var = history_var
15 | self.frame = wx.GetTopLevelParent(self)
16 |
17 | pagesizer = wx.BoxSizer(wx.VERTICAL)
18 | self.listbox = listbox = wx.ListBox(self)
19 | top_bar_container = wx.BoxSizer(wx.HORIZONTAL)
20 | label = wx.StaticText(self, label='Double-click on an item to open it.', style=wx.ST_ELLIPSIZE_END)
21 | new = wx.Button(self, label='+', size=(30, 30), style=wx.BORDER_NONE)
22 | new_tip = wx.ToolTip('Open a new tab')
23 | new.SetToolTip(new_tip)
24 | top_bar_container.Add(label, 1, wx.LEFT | wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 5)
25 | top_bar_container.AddSpacer(30)
26 | top_bar_container.Add(new, 0, wx.BOTTOM | wx.RIGHT | wx.TOP, 5)
27 | pagesizer.Add(top_bar_container, proportion=False, flag=wx.EXPAND)
28 | pagesizer.Add(listbox, proportion=True, flag=wx.EXPAND)
29 | new.Bind(wx.EVT_BUTTON, self.tab_new)
30 | self.Bind(wx.EVT_LISTBOX_DCLICK, self.open_link)
31 |
32 | self.SetSizer(pagesizer)
33 |
34 | self.refresh()
35 |
36 | def refresh(self):
37 | if self.open:
38 | try:
39 | self.listbox.AppendItems(self.history_var[len(self.listbox.GetItems()):])
40 | wx.CallLater(3000, self.refresh)
41 | except:
42 | pass
43 |
44 | def open_link(self, event):
45 | page = WebPage(self.parent, self.history_var, url=event.GetString())
46 | self.parent.AddPage(page, caption="Loading")
47 |
48 | def tab_new(self, event):
49 | page = WebPage(self.parent, self.history_var)
50 | self.parent.AddPage(page, caption="Loading", select=True)
51 |
52 | def on_close(self):
53 | self.open = False
54 |
55 | def on_select(self):
56 | self.frame.SetTitle("WxPyBrowser History")
57 |
58 |
59 | class SourceCode(wx.Panel):
60 | def __init__(self, parent, windowname, windowurl, history_var, *args, **kwargs):
61 | wx.Panel.__init__(self, parent)
62 |
63 | self.name = windowname
64 | self.parent = parent
65 | self.history_var = history_var
66 | self.frame = wx.GetTopLevelParent(self)
67 |
68 | pagesizer = wx.BoxSizer(wx.VERTICAL)
69 | self.source = source = wx.TextCtrl(self, *args, **kwargs)
70 | top_bar_container = wx.BoxSizer(wx.HORIZONTAL)
71 | label = wx.StaticText(self, label=('Source: '+windowurl), style=wx.ST_ELLIPSIZE_END)
72 | new = wx.Button(self, label='+', size=(30, 30), style=wx.BORDER_NONE)
73 | new_tip = wx.ToolTip('Open a new tab')
74 | new.SetToolTip(new_tip)
75 | top_bar_container.Add(label, 1, wx.LEFT | wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 5)
76 | top_bar_container.AddSpacer(30)
77 | top_bar_container.Add(new, 0, wx.BOTTOM | wx.RIGHT | wx.TOP, 5)
78 | pagesizer.Add(top_bar_container, proportion=False, flag=wx.EXPAND)
79 | pagesizer.Add(source, proportion=True, flag=wx.EXPAND)
80 | new.Bind(wx.EVT_BUTTON, self.tab_new)
81 |
82 | self.SetSizer(pagesizer)
83 |
84 | def on_select(self):
85 | self.frame.SetTitle(self.name)
86 |
87 | def tab_new(self, event):
88 | page = WebPage(self.parent, self.history_var)
89 | self.parent.AddPage(page, caption="Loading", select=True)
90 |
91 | def on_close(self):
92 | pass
93 |
94 | class WebPage(wx.Panel):
95 | def __init__(self, parent, history_var, url="https://duckduckgo.com/"):
96 | wx.Panel.__init__(self, parent=parent)
97 |
98 | self.parent = parent
99 | self.visited = history_var
100 | self.remember_history = True
101 | self.frame = wx.GetTopLevelParent(self)
102 |
103 | self.pagesizer = pagesizer = wx.BoxSizer(wx.VERTICAL)
104 | self.top_bar_container = top_bar_container = wx.FlexGridSizer(1, 8, 4, 4)
105 | self.back = back = wx.Button(self, label='<', size=(30, 30))
106 | back_tip = wx.ToolTip('Go back one page')
107 | back.SetToolTip(back_tip)
108 | self.forward = forward = wx.Button(self, label='>', size=(30, 30))
109 | forward_tip = wx.ToolTip('Go forward one page')
110 | forward.SetToolTip(forward_tip)
111 | self.reload = reload = wx.Button(self, label='⟳', size=(30, 30))
112 | reload_tip = wx.ToolTip('Reload current page')
113 | reload.SetToolTip(reload_tip)
114 | self.url_field = url_field = wx.TextCtrl(self, style = wx.TE_PROCESS_ENTER)
115 | new = wx.Button(self, label='+', size=(30, 30))
116 | new_tip = wx.ToolTip('Open a new tab')
117 | new.SetToolTip(new_tip)
118 | menu = wx.lib.platebtn.PlateButton(self, label='☰', size=(30, 30))
119 | menu_tip = wx.ToolTip('Show menu')
120 | menu.SetToolTip(menu_tip)
121 | self.html_window = html_window = wx.html2.WebView.New(self)
122 | top_bar_container.AddGrowableCol(4)
123 | top_bar_container.Add(back, 0, wx.LEFT | wx.BOTTOM | wx.TOP, 5)
124 | top_bar_container.Add(forward, 0, wx.LEFT | wx.BOTTOM | wx.TOP, 5)
125 | top_bar_container.Add(reload, 0, wx.LEFT | wx.BOTTOM | wx.TOP, 5)
126 | top_bar_container.AddSpacer(30)
127 | top_bar_container.Add(url_field, 0, wx.EXPAND | wx.BOTTOM | wx.TOP, 5)
128 | top_bar_container.Add(menu, 0, wx.RIGHT | wx.BOTTOM | wx.TOP, 5)
129 | top_bar_container.AddSpacer(30)
130 | top_bar_container.Add(new, 0, wx.ALIGN_RIGHT | wx.BOTTOM | wx.RIGHT | wx.TOP, 5)
131 |
132 | self.find_container = find_container = wx.BoxSizer(wx.HORIZONTAL)
133 | self.find_field = find_field = wx.TextCtrl(self, style = wx.TE_PROCESS_ENTER)
134 | find_next = wx.Button(self, label='>', size=(30, 30))
135 | find_next_tip = wx.ToolTip('Find next occurance')
136 | find_next.SetToolTip(find_next_tip)
137 | self.entire_word = entire_word = wx.CheckBox(self, label='Entire word')
138 | self.match_case = match_case = wx.CheckBox(self, label='Match case')
139 | self.highlight_results = highlight_results = wx.CheckBox(self, label='Highlight results')
140 | find_results_label = wx.StaticText(self)
141 | find_close = wx.Button(self, label='×', size=(30, 30))
142 | find_close_tip = wx.ToolTip('Close find bar')
143 | find_close.SetToolTip(find_close_tip)
144 | find_container.Add(find_field, 0, wx.EXPAND | wx.LEFT | wx.TOP | wx.BOTTOM, 5)
145 | find_container.Add(find_next, 0, wx.LEFT | wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL, 5)
146 | find_container.Add(entire_word, 0, wx.LEFT | wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL, 5)
147 | find_container.Add(match_case, 0, wx.LEFT | wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL, 5)
148 | find_container.Add(highlight_results, 0, wx.LEFT | wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL, 5)
149 | find_container.AddSpacer(30)
150 | find_container.Add(find_results_label, 1, wx.LEFT | wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL, 5)
151 | find_container.Add(find_close, 0, wx.BOTTOM | wx.RIGHT | wx.TOP, 5)
152 |
153 | self.zoom_container = zoom_container = wx.BoxSizer(wx.HORIZONTAL)
154 | self.zoom_slider = zoom_slider = wx.Slider(self, value=3, minValue=1, maxValue=5)
155 | zoom_close = wx.Button(self, label='×', size=(30, 30))
156 | zoom_close_tip = wx.ToolTip('Close zoom bar')
157 | zoom_close.SetToolTip(zoom_close_tip)
158 | zoom_container.Add(zoom_slider, 1, wx.EXPAND | wx.LEFT | wx.TOP | wx.BOTTOM, 5)
159 | zoom_container.AddSpacer(30)
160 | zoom_container.Add(zoom_close, 0, wx.BOTTOM | wx.RIGHT | wx.TOP, 5)
161 |
162 | pagesizer.Add(top_bar_container, proportion=False, flag=wx.EXPAND)
163 | pagesizer.Add(html_window, proportion=True, flag=wx.EXPAND)
164 | pagesizer.Add(find_container, proportion=False, flag=wx.EXPAND)
165 | pagesizer.Add(zoom_container, proportion=False, flag=wx.EXPAND)
166 | pagesizer.Hide(find_container)
167 | pagesizer.Hide(zoom_container)
168 |
169 | find_field.Bind(wx.EVT_TEXT_ENTER, self.continue_find)
170 | find_next.Bind(wx.EVT_BUTTON, lambda event, goprev=True: self.continue_find(event, goprev))
171 | entire_word.Bind(wx.EVT_CHECKBOX, self.continue_find)
172 | match_case.Bind(wx.EVT_CHECKBOX, self.continue_find)
173 | highlight_results.Bind(wx.EVT_CHECKBOX, self.continue_find)
174 | find_field.Bind(wx.EVT_TEXT, self.continue_find)
175 | find_close.Bind(wx.EVT_BUTTON, self.close_find)
176 | zoom_close.Bind(wx.EVT_BUTTON, self.close_zoom)
177 | zoom_slider.Bind(wx.EVT_COMMAND_SCROLL, self.zoom_slider_change)
178 | back.Bind(wx.EVT_BUTTON, self.tab_back)
179 | forward.Bind(wx.EVT_BUTTON, self.tab_foward)
180 | reload.Bind(wx.EVT_BUTTON, self.tab_reload)
181 | new.Bind(wx.EVT_BUTTON, self.tab_new)
182 | url_field.Bind(wx.EVT_TEXT_ENTER, self.loadpage)
183 | url_field.Bind(wx.EVT_SET_FOCUS, self.click_on_url_field)
184 | html_window.Bind(wx.html2.EVT_WEBVIEW_LOADED, self.post_load_config)
185 | html_window.Bind(wx.html2.EVT_WEBVIEW_NEWWINDOW, self.open_in_new_tab)
186 | html_window.Bind(wx.html2.EVT_WEBVIEW_TITLE_CHANGED, self.change_title)
187 |
188 | self.load_url(url)
189 | url_field.SetValue(url)
190 |
191 | settings_menu = wx.Menu()
192 | page_menu = wx.Menu()
193 | menu_zoom = wx.MenuItem(settings_menu, 0, "Change zoom")
194 | self.menu_contextmenu = menu_contextmenu = wx.MenuItem(settings_menu, 2, "Enable context menu", "", wx.ITEM_CHECK)
195 | self.menu_historyenabled = menu_historyenabled = wx.MenuItem(settings_menu, 3, "Remember page history", "", wx.ITEM_CHECK)
196 | menu_source = wx.MenuItem(page_menu, 0, "Show source")
197 | menu_history = wx.MenuItem(page_menu, 1, "Show history")
198 | menu_print = wx.MenuItem(page_menu, 2, "Print this page")
199 | menu_find = wx.MenuItem(page_menu, 3, "Find in page")
200 | settings_menu.Append(menu_zoom)
201 | settings_menu.AppendSeparator()
202 | try:
203 | self.html_window.EnableAccessToDevTools()
204 | self.menu_devtools = menu_devtools = wx.MenuItem(settings_menu, 1, "Enable access to dev tools", "", wx.ITEM_CHECK)
205 | settings_menu.Append(menu_devtools)
206 | menu_devtools.Check()
207 | settings_menu.Bind(wx.EVT_MENU, self.enable_devtools, menu_devtools)
208 | except:
209 | pass
210 | settings_menu.Append(menu_contextmenu)
211 | settings_menu.Append(menu_historyenabled)
212 | page_menu.Append(menu_source)
213 | page_menu.Append(menu_history)
214 | page_menu.AppendSeparator()
215 | page_menu.Append(menu_print)
216 | page_menu.Append(menu_find)
217 | page_menu.AppendSeparator()
218 | page_menu.AppendSubMenu(settings_menu, 'Page Settings')
219 | html_window.EnableContextMenu()
220 | html_window.EnableHistory()
221 | menu_contextmenu.Check()
222 | menu_historyenabled.Check()
223 | settings_menu.Bind(wx.EVT_MENU, self.enable_contextmenu, menu_contextmenu)
224 | settings_menu.Bind(wx.EVT_MENU, self.enable_historyenabled, menu_historyenabled)
225 | settings_menu.Bind(wx.EVT_MENU, self.adjust_zoom, menu_zoom)
226 | page_menu.Bind(wx.EVT_MENU, self.show_source, menu_source)
227 | page_menu.Bind(wx.EVT_MENU, self.show_history, menu_history)
228 | page_menu.Bind(wx.EVT_MENU, self.print_page, menu_print)
229 | page_menu.Bind(wx.EVT_MENU, self.find_in_page, menu_find)
230 |
231 | menu.SetMenu(page_menu)
232 | self.SetSizer(pagesizer)
233 |
234 | def show_source(self, event):
235 | title = self.html_window.GetCurrentTitle()
236 | if title == "":
237 | title = ("WxPyBrowser - Source for "+self.html_window.GetCurrentURL())
238 | else:
239 | title = ("WxPyBrowser - Source for "+self.html_window.GetCurrentTitle())
240 | page = SourceCode(self.parent, windowname=title, windowurl=self.html_window.GetCurrentURL(), history_var=self.visited, style=wx.TE_MULTILINE | wx.TE_READONLY | wx.HSCROLL)
241 | page.source.SetValue(self.html_window.GetPageSource())
242 | self.parent.AddPage(page, caption=title, select=False)
243 |
244 | def show_history(self, event):
245 | history_tab = HistoryPage(self.parent, self.visited)
246 | self.parent.AddPage(history_tab, caption="WxPyBrowser History", select=False)
247 |
248 | def adjust_zoom(self, event):
249 | self.pagesizer.Hide(self.find_container)
250 | self.pagesizer.Show(self.zoom_container)
251 | self.pagesizer.Layout()
252 |
253 | def close_zoom(self, event):
254 | self.pagesizer.Hide(self.zoom_container)
255 | self.pagesizer.Layout()
256 |
257 | def zoom_slider_change(self, event):
258 | scalenum = event.GetInt()
259 | scale_val = None
260 | if scalenum == 1:
261 | scale_val = wx.html2.WEBVIEW_ZOOM_TINY
262 | elif scalenum == 2:
263 | scale_val = wx.html2.WEBVIEW_ZOOM_SMALL
264 | elif scalenum == 3:
265 | scale_val = wx.html2.WEBVIEW_ZOOM_MEDIUM
266 | elif scalenum == 4:
267 | scale_val = wx.html2.WEBVIEW_ZOOM_LARGE
268 | elif scalenum == 5:
269 | scale_val = wx.html2.WEBVIEW_ZOOM_LARGEST
270 |
271 | self.html_window.SetZoom(scale_val)
272 |
273 | def print_page(self, event):
274 | self.html_window.Print()
275 |
276 | def find_in_page(self, event):
277 | self.pagesizer.Hide(self.zoom_container)
278 | self.pagesizer.Show(self.find_container)
279 | self.pagesizer.Layout()
280 |
281 | def continue_find(self, event, gonext=False):
282 | match_case = self.match_case.IsChecked()
283 | entire_word = self.entire_word.IsChecked()
284 | highlight_results = self.highlight_results.IsChecked()
285 | if not gonext:
286 | self.html_window.Find("")
287 | if match_case and entire_word and highlight_results:
288 | self.html_window.Find(self.find_field.GetValue(), flags=wx.html2.WEBVIEW_FIND_HIGHLIGHT_RESULT | wx.html2.WEBVIEW_FIND_ENTIRE_WORD | wx.html2.WEBVIEW_FIND_MATCH_CASE)
289 | elif match_case and highlight_results:
290 | self.html_window.Find(self.find_field.GetValue(), flags=wx.html2.WEBVIEW_FIND_HIGHLIGHT_RESULT | wx.html2.WEBVIEW_FIND_MATCH_CASE)
291 | elif entire_word and highlight_results:
292 | self.html_window.Find(self.find_field.GetValue(), flags=wx.html2.WEBVIEW_FIND_HIGHLIGHT_RESULT | wx.html2.WEBVIEW_FIND_ENTIRE_WORD)
293 | elif entire_word and match_case:
294 | self.html_window.Find(self.find_field.GetValue(), flags=wx.html2.WEBVIEW_FIND_ENTIRE_WORD | wx.html2.WEBVIEW_FIND_MATCH_CASE)
295 | elif match_case:
296 | self.html_window.Find(self.find_field.GetValue(), flags=wx.html2.WEBVIEW_FIND_MATCH_CASE)
297 | elif entire_word:
298 | self.html_window.Find(self.find_field.GetValue(), flags=wx.html2.WEBVIEW_FIND_ENTIRE_WORD)
299 | elif highlight_results:
300 | self.html_window.Find(self.find_field.GetValue(), flags=wx.html2.WEBVIEW_FIND_HIGHLIGHT_RESULT)
301 | else:
302 | self.html_window.Find(self.find_field.GetValue())
303 |
304 | def close_find(self, event):
305 | self.pagesizer.Hide(self.find_container)
306 | self.pagesizer.Layout()
307 |
308 | def click_on_url_field(self, event):
309 | self.url_field.SelectAll()
310 |
311 | def enable_devtools(self, event):
312 | self.html_window.EnableAccessToDevTools(self.menu_devtools.IsChecked())
313 |
314 | def enable_contextmenu(self, event):
315 | self.html_window.EnableContextMenu(self.menu_contextmenu.IsChecked())
316 |
317 | def enable_historyenabled(self, event):
318 | history_enabled = self.menu_historyenabled.IsChecked()
319 | self.html_window.EnableHistory(history_enabled)
320 | self.remember_history = history_enabled
321 |
322 | if self.html_window.CanGoBack():
323 | self.back.Enable()
324 | else:
325 | self.back.Disable()
326 | if self.html_window.CanGoForward():
327 | self.forward.Enable()
328 | else:
329 | self.forward.Disable()
330 |
331 | def load_url(self, url=None):
332 | self.back.Disable()
333 | self.forward.Disable()
334 | self.reload.SetLabel("×")
335 | self.url_field.Disable()
336 |
337 | if url:
338 | self.html_window.LoadURL(url)
339 |
340 | def tab_back(self, event):
341 | self.load_url()
342 | self.html_window.GoBack()
343 |
344 | def tab_foward(self, event):
345 | self.load_url()
346 | self.html_window.GoForward()
347 |
348 | def tab_reload(self, event):
349 | if self.reload.GetLabel() == "×":
350 | self.html_window.Stop()
351 | self.post_load_config()
352 | else:
353 | self.load_url()
354 | self.html_window.Reload()
355 |
356 | def tab_new(self, event):
357 | page = WebPage(self.parent,self.visited)
358 | self.parent.AddPage(page, caption="Loading", select=True)
359 |
360 | def post_load_config(self, event=None):
361 | url = self.html_window.GetCurrentURL()
362 |
363 | self.url_field.SetValue(url)
364 | if self.parent.GetSelection() == self.parent.GetPageIndex(self):
365 | self.on_select()
366 |
367 | if self.html_window.CanGoBack():
368 | self.back.Enable()
369 | if self.html_window.CanGoForward():
370 | self.forward.Enable()
371 | self.reload.SetLabel("⟳")
372 | self.url_field.Enable()
373 | if self.remember_history:
374 | self.visited.append(url)
375 |
376 | def change_title(self, event):
377 | title = self.html_window.GetCurrentTitle()
378 | current_page_index = self.parent.GetPageIndex(self)
379 | self.parent.SetPageText(current_page_index, title)
380 |
381 | def on_select(self):
382 | title = self.html_window.GetCurrentTitle()
383 | divider = ""
384 | if title != "":
385 | divider = " - "
386 | self.frame.SetTitle("WxPyBrowser"+divider+title)
387 |
388 | def on_close(self):
389 | if self.html_window.IsBusy():
390 | self.html_window.Stop()
391 |
392 | def open_in_new_tab(self, event):
393 | page = WebPage(self.parent, self.visited, url=event.URL)
394 | self.parent.AddPage(page, caption="Loading")
395 |
396 | def loadpage(self, event):
397 | url = self.url_field.GetValue()
398 | if (not url.startswith("https://") and not url.startswith("http://")):
399 | url = "https://"+url
400 | self.load_url(url)
401 |
402 |
403 |
404 | class Browser(wx.Frame):
405 | def __init__(self, parent, title):
406 | wx.Frame.__init__(self, parent, title=title)
407 |
408 | self.SetMinSize((900, 500))
409 |
410 | self.history_closed = []
411 | self.load_notebook()
412 |
413 | def load_notebook(self):
414 | self.panel = panel = wx.Panel(self)
415 | box = wx.BoxSizer(wx.HORIZONTAL)
416 | self.notebook = notebook = wx.aui.AuiNotebook(panel, style=wx.aui.AUI_NB_DEFAULT_STYLE)
417 | box.Add(notebook, proportion=True, flag=wx.EXPAND)
418 | panel.SetSizer(box)
419 |
420 | notebook.AddPage(WebPage(self.notebook, self.history_closed), caption="Loading", select=True)
421 |
422 | notebook.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self.on_page_close)
423 | notebook.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, self.on_page_select)
424 |
425 |
426 | def on_page_close(self, event):
427 | event.Skip()
428 | try:
429 | page = self.notebook.GetCurrentPage()
430 | page.on_close()
431 | except:
432 | pass
433 | if self.notebook.GetPageCount() <= 1:
434 | self.Close()
435 |
436 |
437 | def on_page_select(self, event):
438 | self.notebook.GetCurrentPage().on_select()
439 |
440 |
441 | def main():
442 | app = wx.App()
443 | browser = Browser(None, title='WxPyBrowser')
444 | browser.Show()
445 | app.MainLoop()
446 |
447 |
448 | if __name__ == '__main__':
449 | main()
450 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/wxPython_/requirements.txt:
--------------------------------------------------------------------------------
1 | wxpython
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/{{cookiecutter.gui_framework}}/README.md:
--------------------------------------------------------------------------------
1 | How to Use:
2 | ===========
3 |
4 | * Create and activate a virtual environment:
5 | virtualenv venv
6 |
7 | venv\Scripts\Activate
8 |
9 | * Install dependencies:
10 | pip install -r requirements.txt
11 |
12 | * Run the GUI demo:
13 | python main.py --{%- if cookiecutter.gui_framework == 'PyQt5' %}pyqt5
14 | {% elif cookiecutter.gui_framework == 'PyQt6' %}pyqt6
15 | {% elif cookiecutter.gui_framework == 'PySide2' %}pyside2
16 | {% elif cookiecutter.gui_framework == 'PySide6' %}pyside6
17 | {%- endif %}
18 |
19 |
20 | Note:
21 | ----
22 | This Demo is taken from this repository:
23 | https://github.com/UN-GCPDS/qt-material
24 |
25 |
26 | Sample:
27 | ----
28 | Dark theme:
29 | 
30 | Light theme:
31 | 
32 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/{{cookiecutter.gui_framework}}/generator.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from qt_material import list_themes
4 |
5 | themes = list_themes()
6 | themes = [t.replace('.xml', '') for t in themes]
7 |
8 | for theme in themes:
9 | os.system(f'python main.py --pyside6 {theme}')
10 |
11 | os.chdir('screenshots')
12 |
13 | commands = (
14 | 'convert -delay 100 light_* light.gif',
15 | 'convert -delay 100 dark_* dark.gif',
16 | # 'rm ../../../docs/source/images/light.gif',
17 | # 'rm ../../../docs/source/images/dark.gif',
18 | # 'cp light.gif ../../../docs/source/images/light.gif',
19 | # 'cp dark.gif ../../../docs/source/images/dark.gif',
20 | # 'cp theme.png ../../../docs/source/images/theme.png',
21 | )
22 |
23 | for command in commands:
24 | os.system(command)
25 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/{{cookiecutter.gui_framework}}/main.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import logging
4 | from multiprocessing import freeze_support
5 | import psutil
6 | import signal
7 | import importlib.resources
8 |
9 | if '--pyside2' in sys.argv:
10 | from PySide2.QtWidgets import QApplication, QMainWindow, QFileDialog
11 | from PySide2.QtCore import QTimer, Qt, QCoreApplication
12 | from PySide2.QtGui import QIcon
13 | from PySide2.QtUiTools import QUiLoader
14 |
15 | elif '--pyside6' in sys.argv:
16 | from PySide6.QtWidgets import QApplication, QMainWindow, QFileDialog
17 | from PySide6.QtCore import QTimer, Qt, QCoreApplication
18 | from PySide6.QtGui import QIcon, QPixmap
19 | from PySide6.QtUiTools import QUiLoader
20 |
21 | elif '--pyqt5' in sys.argv:
22 | from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog
23 | from PyQt5.QtCore import QTimer, Qt, QCoreApplication
24 | from PyQt5 import uic, QtWebEngineWidgets
25 | from PyQt5.QtGui import QIcon
26 |
27 | elif '--pyqt6' in sys.argv:
28 | from PyQt6.QtWidgets import QApplication, QMainWindow, QFileDialog
29 | from PyQt6.QtCore import QTimer, Qt, QCoreApplication
30 | from PyQt6.QtGui import QIcon
31 | from PyQt6 import uic, QtWebEngineWidgets
32 |
33 |
34 | from qt_material import apply_stylesheet, QtStyleTools
35 |
36 | freeze_support()
37 | QCoreApplication.setAttribute(Qt.AA_ShareOpenGLContexts)
38 |
39 | app = QApplication([])
40 | app.processEvents()
41 | app.setQuitOnLastWindowClosed(False)
42 | app.lastWindowClosed.connect(lambda: app.quit())
43 |
44 | # Extra stylesheets
45 | extra = {
46 |
47 | # Button colors
48 | 'danger': '#dc3545',
49 | 'warning': '#ffc107',
50 | 'success': '#17a2b8',
51 |
52 | # Font
53 | 'font_family': 'Roboto',
54 | }
55 |
56 |
57 | ########################################################################
58 | class RuntimeStylesheets(QMainWindow, QtStyleTools):
59 | # ----------------------------------------------------------------------
60 | def __init__(self):
61 | """Constructor"""
62 | super().__init__()
63 |
64 | if '--pyside2' in sys.argv:
65 | self.main = QUiLoader().load('main_window.ui', self)
66 | self.main.setWindowTitle(f'{self.main.windowTitle()} - PySide2')
67 |
68 | elif'--pyside6' in sys.argv:
69 | self.main = QUiLoader().load('main_window.ui', self)
70 | self.main.setWindowTitle(f'{self.main.windowTitle()} - PySide6')
71 |
72 | elif '--pyqt5' in sys.argv:
73 | self.main = uic.loadUi('main_window.ui', self)
74 | self.main.setWindowTitle(f'{self.main.windowTitle()} - PyQt5')
75 |
76 | elif '--pyqt6' in sys.argv:
77 | self.main = uic.loadUi('main_window.ui', self)
78 | self.main.setWindowTitle(f'{self.main.windowTitle()} - PyQt6')
79 |
80 | else:
81 | logging.error(
82 | 'must include --pyside2, --pyside6 or --pyqt5 in args!')
83 | sys.exit()
84 | self.custom_styles()
85 |
86 | self.set_extra_colors(extra)
87 | self.add_menu_theme(self.main, self.main.menuStyles)
88 | self.show_dock_theme(self.main)
89 |
90 | logo = QIcon("qt_material:/logo/logo.svg")
91 | logo_frame = QIcon("qt_material:/logo/logo_frame.svg")
92 |
93 | self.main.setWindowIcon(logo)
94 | self.main.actionToolbar.setIcon(logo)
95 | [self.main.listWidget_2.item(i).setIcon(logo_frame)
96 | for i in range(self.main.listWidget_2.count())]
97 |
98 | # ----------------------------------------------------------------------
99 | def custom_styles(self):
100 | """"""
101 | for i in range(self.main.toolBar_vertical.layout().count()):
102 | tool_button = self.main.toolBar_vertical.layout().itemAt(i).widget()
103 | tool_button.setMaximumWidth(150)
104 | tool_button.setMinimumWidth(150)
105 |
106 |
107 | T0 = 1000
108 |
109 | if __name__ == "__main__":
110 |
111 | # ----------------------------------------------------------------------
112 | def take_screenshot():
113 | pixmap = frame.main.grab()
114 | pixmap.save(os.path.join('screenshots', f'{theme}.png'))
115 | print(f'Saving {theme}')
116 |
117 | try:
118 | theme = sys.argv[2]
119 | QTimer.singleShot(T0, take_screenshot)
120 | QTimer.singleShot(T0 * 2, app.closeAllWindows)
121 | except:
122 | theme = 'default'
123 |
124 | # Set theme on in itialization
125 | apply_stylesheet(app, theme + '.xml',
126 | invert_secondary=(
127 | 'light' in theme and 'dark' not in theme),
128 | extra=extra)
129 |
130 | frame = RuntimeStylesheets()
131 | frame.main.show()
132 |
133 | app.exec_()
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/{{cookiecutter.gui_framework}}/my_theme.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #1de9b6
4 | #6effe8
5 | #232629
6 | #4f5b62
7 | #31363b
8 | #000000
9 | #ffffff
10 |
11 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/demo/{{cookiecutter.gui_framework}}/requirements.txt:
--------------------------------------------------------------------------------
1 | {%- if cookiecutter.gui_framework == 'PyQt5' %}pyqt5
2 | psutil
3 | PyQtWebEngine
4 | qt_material
5 | {%- endif %}
6 | {%- if cookiecutter.gui_framework == 'PyQt6' %}pyqt6
7 | psutil
8 | PyQtWebEngine
9 | qt_material
10 | {%- endif %}
11 | {%- if cookiecutter.gui_framework == 'PySide2' %}PySide2
12 | psutil
13 | qt_material
14 | {%- endif %}
15 | {%- if cookiecutter.gui_framework == 'PySide6' %}PySide6
16 | psutil
17 | qt_material
18 | {%- endif %}
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = python -msphinx
7 | SPHINXPROJ = {{ cookiecutter.project_slug }}
8 | SOURCEDIR = .
9 | BUILDDIR = _build
10 |
11 | # Put it first so that "make" without argument is like "make help".
12 | help:
13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14 |
15 | .PHONY: help Makefile
16 |
17 | # Catch-all target: route all unknown targets to Sphinx using the new
18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19 | %: Makefile
20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
21 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/docs/authors.rst:
--------------------------------------------------------------------------------
1 | .. include:: ../AUTHORS.rst
2 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/docs/conf.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # {{ cookiecutter.project_slug }} documentation build configuration file, created by
4 | # sphinx-quickstart on Fri Jun 9 13:47:02 2017.
5 | #
6 | # This file is execfile()d with the current directory set to its
7 | # containing dir.
8 | #
9 | # Note that not all possible configuration values are present in this
10 | # autogenerated file.
11 | #
12 | # All configuration values have a default; values that are commented out
13 | # serve to show the default.
14 |
15 | # If extensions (or modules to document with autodoc) are in another
16 | # directory, add these directories to sys.path here. If the directory is
17 | # relative to the documentation root, use os.path.abspath to make it
18 | # absolute, like shown here.
19 | #
20 | import os
21 | import sys
22 | sys.path.insert(0, os.path.abspath('..'))
23 |
24 | import {{ cookiecutter.project_slug }}
25 |
26 | # -- General configuration ---------------------------------------------
27 |
28 | # If your documentation needs a minimal Sphinx version, state it here.
29 | #
30 | # needs_sphinx = '1.0'
31 |
32 | # Add any Sphinx extension module names here, as strings. They can be
33 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
34 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode']
35 |
36 | # Add any paths that contain templates here, relative to this directory.
37 | templates_path = ['_templates']
38 |
39 | # The suffix(es) of source filenames.
40 | # You can specify multiple suffix as a list of string:
41 | #
42 | # source_suffix = ['.rst', '.md']
43 | source_suffix = '.rst'
44 |
45 | # The master toctree document.
46 | master_doc = 'index'
47 |
48 | # General information about the project.
49 | project = '{{ cookiecutter.project_name }}'
50 | copyright = "{% now 'local', '%Y' %}, {{ cookiecutter.full_name }}"
51 | author = "{{ cookiecutter.full_name }}"
52 |
53 | # The version info for the project you're documenting, acts as replacement
54 | # for |version| and |release|, also used in various other places throughout
55 | # the built documents.
56 | #
57 | # The short X.Y version.
58 | version = {{ cookiecutter.project_slug }}.__version__
59 | # The full version, including alpha/beta/rc tags.
60 | release = {{ cookiecutter.project_slug }}.__version__
61 |
62 | # The language for content autogenerated by Sphinx. Refer to documentation
63 | # for a list of supported languages.
64 | #
65 | # This is also used if you do content translation via gettext catalogs.
66 | # Usually you set "language" from the command line for these cases.
67 | language = None
68 |
69 | # List of patterns, relative to source directory, that match files and
70 | # directories to ignore when looking for source files.
71 | # This patterns also effect to html_static_path and html_extra_path
72 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
73 |
74 | # The name of the Pygments (syntax highlighting) style to use.
75 | pygments_style = 'sphinx'
76 |
77 | # If true, `todo` and `todoList` produce output, else they produce nothing.
78 | todo_include_todos = False
79 |
80 |
81 | # -- Options for HTML output -------------------------------------------
82 |
83 | # The theme to use for HTML and HTML Help pages. See the documentation for
84 | # a list of builtin themes.
85 | #
86 | html_theme = 'alabaster'
87 |
88 | # Theme options are theme-specific and customize the look and feel of a
89 | # theme further. For a list of options available for each theme, see the
90 | # documentation.
91 | #
92 | # html_theme_options = {}
93 |
94 | # Add any paths that contain custom static files (such as style sheets) here,
95 | # relative to this directory. They are copied after the builtin static files,
96 | # so a file named "default.css" will overwrite the builtin "default.css".
97 | html_static_path = ['_static']
98 |
99 |
100 | # -- Options for HTMLHelp output ---------------------------------------
101 |
102 | # Output file base name for HTML help builder.
103 | htmlhelp_basename = '{{ cookiecutter.project_slug }}doc'
104 |
105 |
106 | # -- Options for LaTeX output ------------------------------------------
107 |
108 | latex_elements = {
109 | # The paper size ('letterpaper' or 'a4paper').
110 | #
111 | # 'papersize': 'letterpaper',
112 |
113 | # The font size ('10pt', '11pt' or '12pt').
114 | #
115 | # 'pointsize': '10pt',
116 |
117 | # Additional stuff for the LaTeX preamble.
118 | #
119 | # 'preamble': '',
120 |
121 | # Latex figure (float) alignment
122 | #
123 | # 'figure_align': 'htbp',
124 | }
125 |
126 | # Grouping the document tree into LaTeX files. List of tuples
127 | # (source start file, target name, title, author, documentclass
128 | # [howto, manual, or own class]).
129 | latex_documents = [
130 | (master_doc, '{{ cookiecutter.project_slug }}.tex',
131 | '{{ cookiecutter.project_name }} Documentation',
132 | '{{ cookiecutter.full_name }}', 'manual'),
133 | ]
134 |
135 |
136 | # -- Options for manual page output ------------------------------------
137 |
138 | # One entry per manual page. List of tuples
139 | # (source start file, name, description, authors, manual section).
140 | man_pages = [
141 | (master_doc, '{{ cookiecutter.project_slug }}',
142 | '{{ cookiecutter.project_name }} Documentation',
143 | [author], 1)
144 | ]
145 |
146 |
147 | # -- Options for Texinfo output ----------------------------------------
148 |
149 | # Grouping the document tree into Texinfo files. List of tuples
150 | # (source start file, target name, title, author,
151 | # dir menu entry, description, category)
152 | texinfo_documents = [
153 | (master_doc, '{{ cookiecutter.project_slug }}',
154 | '{{ cookiecutter.project_name }} Documentation',
155 | author,
156 | '{{ cookiecutter.project_slug }}',
157 | 'One line description of project.',
158 | 'Miscellaneous'),
159 | ]
160 |
161 |
162 |
163 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/docs/contributing.rst:
--------------------------------------------------------------------------------
1 | .. include:: ../CONTRIBUTING.rst
2 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/docs/history.rst:
--------------------------------------------------------------------------------
1 | .. include:: ../HISTORY.rst
2 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/docs/index.rst:
--------------------------------------------------------------------------------
1 | Welcome to {{ cookiecutter.project_name }}'s documentation!
2 | ======================================
3 |
4 | .. toctree::
5 | :maxdepth: 2
6 | :caption: Contents:
7 |
8 | readme
9 | installation
10 | usage
11 | modules
12 | contributing
13 | {% if cookiecutter.create_author_file == 'y' -%}authors
14 | {% endif -%}history
15 |
16 | Indices and tables
17 | ==================
18 | * :ref:`genindex`
19 | * :ref:`modindex`
20 | * :ref:`search`
21 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/docs/installation.rst:
--------------------------------------------------------------------------------
1 | .. highlight:: shell
2 |
3 | ============
4 | Installation
5 | ============
6 |
7 |
8 | Stable release
9 | --------------
10 |
11 | To install {{ cookiecutter.project_name }}, run this command in your terminal:
12 |
13 | .. code-block:: console
14 |
15 | $ pip install {{ cookiecutter.project_slug }}
16 |
17 | This is the preferred method to install {{ cookiecutter.project_name }}, as it will always install the most recent stable release.
18 |
19 | If you don't have `pip`_ installed, this `Python installation guide`_ can guide
20 | you through the process.
21 |
22 | .. _pip: https://pip.pypa.io
23 | .. _Python installation guide: http://docs.python-guide.org/en/latest/starting/installation/
24 |
25 |
26 | From sources
27 | ------------
28 |
29 | The sources for {{ cookiecutter.project_name }} can be downloaded from the `Github repo`_.
30 |
31 | You can either clone the public repository:
32 |
33 | .. code-block:: console
34 |
35 | $ git clone git://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }}
36 |
37 | Or download the `tarball`_:
38 |
39 | .. code-block:: console
40 |
41 | $ curl -OJL https://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }}/tarball/master
42 |
43 | Once you have a copy of the source, you can install it with:
44 |
45 | .. code-block:: console
46 |
47 | $ python setup.py install
48 |
49 |
50 | .. _Github repo: https://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }}
51 | .. _tarball: https://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }}/tarball/master
52 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | pushd %~dp0
4 |
5 | REM Command file for Sphinx documentation
6 |
7 | if "%SPHINXBUILD%" == "" (
8 | set SPHINXBUILD=python -msphinx
9 | )
10 | set SOURCEDIR=.
11 | set BUILDDIR=_build
12 | set SPHINXPROJ={{ cookiecutter.project_slug }}
13 |
14 | if "%1" == "" goto help
15 |
16 | %SPHINXBUILD% >NUL 2>NUL
17 | if errorlevel 9009 (
18 | echo.
19 | echo.The Sphinx module was not found. Make sure you have Sphinx installed,
20 | echo.then set the SPHINXBUILD environment variable to point to the full
21 | echo.path of the 'sphinx-build' executable. Alternatively you may add the
22 | echo.Sphinx directory to PATH.
23 | echo.
24 | echo.If you don't have Sphinx installed, grab it from
25 | echo.http://sphinx-doc.org/
26 | exit /b 1
27 | )
28 |
29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
30 | goto end
31 |
32 | :help
33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
34 |
35 | :end
36 | popd
37 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/docs/readme.rst:
--------------------------------------------------------------------------------
1 | .. include:: ../README.rst
2 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/docs/usage.rst:
--------------------------------------------------------------------------------
1 | =====
2 | Usage
3 | =====
4 |
5 | To use {{ cookiecutter.project_name }} in a project::
6 |
7 | import {{ cookiecutter.project_slug }}
8 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/pyproject.toml:
--------------------------------------------------------------------------------
1 | {%- set license_classifiers = {
2 | 'MIT': 'License :: OSI Approved :: MIT License',
3 | 'BSD-3-Clause': 'License :: OSI Approved :: BSD License',
4 | 'ISC': 'License :: OSI Approved :: ISC License (ISCL)',
5 | 'Apache-2.0': 'License :: OSI Approved :: Apache Software License',
6 | 'GPL-3.0-only': 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)'
7 | } -%}
8 | [tool]
9 | [tool.poetry]
10 | name = "{{ cookiecutter.project_slug }}"
11 | version = "{{ cookiecutter.version }}"
12 | homepage = "https://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }}"
13 | description = "Top-level package for {{ cookiecutter.project_name }}."
14 | authors = ["{{ cookiecutter.full_name.replace('\"', '\\\"') }} <{{ cookiecutter.email }}>"]
15 | readme = "README.rst"
16 | {%- if cookiecutter.open_source_license in license_classifiers %}
17 | license = "{{ cookiecutter.open_source_license }}"
18 | {%- endif %}
19 | classifiers=[
20 | 'Development Status :: 2 - Pre-Alpha',
21 | 'Environment :: Win32 (MS Windows)',
22 | 'Environment :: MacOS X',
23 | 'Environment :: X11 Applications',
24 | 'Environment :: Web Environment',
25 | 'Environment :: Other Environment',
26 | 'Intended Audience :: Developers',
27 | 'Intended Audience :: End Users/Desktop',
28 | {%- if cookiecutter.open_source_license in license_classifiers %}
29 | '{{ license_classifiers[cookiecutter.open_source_license] }}',
30 | {%- endif %}
31 | 'Natural Language :: English',
32 | 'Programming Language :: Python :: 3',
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 | 'Topic :: Software Development',
39 | 'Topic :: Communications :: Email',
40 | 'Topic :: Office/Business',
41 | ]
42 | packages = [
43 | { include = "{{ cookiecutter.project_slug }}" },
44 | { include = "tests", format = "sdist" },
45 | ]
46 |
47 | [tool.poetry.dependencies]
48 | python = "*"
49 |
50 | [tool.poetry.dev-dependencies]
51 | bumpversion = "*"
52 | coverage = "*"
53 | flake8 = "*"
54 | invoke = "*"
55 | isort = "*"
56 | pylint = "*"
57 | {% if cookiecutter.use_pytest == 'y' -%}
58 | pytest = "*"{% endif %}
59 | {%- if cookiecutter.gui_framework == 'PyQt5' %}
60 | PyQt5 = "*"
61 | {% elif cookiecutter.gui_framework == 'PyQt6' %}
62 | PyQt6 = "*"
63 | {% elif cookiecutter.gui_framework == 'PySide2' %}
64 | PySide2 = "*"
65 | {% elif cookiecutter.gui_framework == 'PySide6' %}
66 | PySide6 = "*"
67 | {% elif cookiecutter.gui_framework == 'Kivy' %}
68 | Kivy = "*"
69 | {% elif cookiecutter.gui_framework == 'wxPython' %}
70 | wxPython = "*"
71 | {% elif cookiecutter.gui_framework == 'Pyforms' %}
72 | Pyforms = "*"
73 | {% elif cookiecutter.gui_framework == 'PySimpleGUI' %}
74 | PySimpleGUI = "*"
75 | {% elif cookiecutter.gui_framework == 'PyGTK' %}
76 | PyGTK = "*"{%- endif %}sphinx = "*"
77 | tox = "*"
78 | yapf = "*"
79 |
80 | [tool.poetry.scripts]
81 |
82 | [build-system]
83 | requires = ["poetry>=0.12"]
84 | build-backend = "poetry.masonry.api"
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/requirements_dev.txt:
--------------------------------------------------------------------------------
1 | pip>=19.2.3
2 | bump2version>=0.5.11
3 | wheel>=0.33.6
4 | watchdog>=0.9.0
5 | flake8>=3.7.8
6 | tox>=3.14.0
7 | coverage>=4.5.4
8 | Sphinx>=1.8.5
9 | twine>=1.14.0{% if cookiecutter.gui_framework == 'PyQt5' -%}
10 | PyQt5>=5.15.4{% endif %}{% if cookiecutter.gui_framework == 'PyQt6' -%}
11 | PyQt6>=6.1.1{% endif %}{% if cookiecutter.gui_framework == 'PySide2' -%}
12 | PySide2>=5.15.2{% endif %}{% if cookiecutter.gui_framework == 'PySide6' -%}
13 | PySide6>=6.1.2{% endif %}{% if cookiecutter.gui_framework == 'customTkinter' -%}
14 | customTkinter>=5.1.3{% endif %}{% if cookiecutter.gui_framework == 'Kivy' -%}
15 | Kivy>=2.0.0{% endif %}{% if cookiecutter.gui_framework == 'Toga' -%}
16 | toga>=0.3.1
17 | briefcase>=0.3.14{% endif %}{% if cookiecutter.gui_framework == 'wxPython' -%}
18 | wxPython>=4.1.1{% endif %}{% if cookiecutter.gui_framework == 'Pyforms' -%}
19 | Pyforms>=4.0.3{% endif %}{% if cookiecutter.gui_framework == 'PySimpleGUI' -%}
20 | PySimpleGUI>=4.45.0{% endif %}{% if cookiecutter.gui_framework == 'PyGTK' -%}
21 | PyGTK>=2.24.2{% endif %}{% if cookiecutter.use_pytest == 'y' -%}
22 | pytest>=6.2.4{% endif %}
23 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/setup.cfg:
--------------------------------------------------------------------------------
1 | [bumpversion]
2 | current_version = {{ cookiecutter.version }}
3 | commit = True
4 | tag = True
5 |
6 | [bumpversion:file:setup.py]
7 | search = version='{current_version}'
8 | replace = version='{new_version}'
9 |
10 | [bumpversion:file:{{ cookiecutter.project_slug }}/__init__.py]
11 | search = __version__ = '{current_version}'
12 | replace = __version__ = '{new_version}'
13 |
14 | [bdist_wheel]
15 | universal = 1
16 |
17 | [flake8]
18 | exclude = docs
19 |
20 | {%- if cookiecutter.use_pytest == 'y' %}
21 | [tool:pytest]
22 | collect_ignore = ['setup.py']
23 | {%- endif %}
24 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | """The setup script."""
4 |
5 | from setuptools import setup, find_packages
6 |
7 | with open('README.rst') as readme_file:
8 | readme = readme_file.read()
9 |
10 | with open('HISTORY.rst') as history_file:
11 | history = history_file.read()
12 |
13 | requirements = [
14 | {%- if cookiecutter.gui_framework == 'PyQt5' %}'PyQt5>=5.15.4',
15 | {% elif cookiecutter.gui_framework == 'PyQt6' %}'PyQt6>=6.1.1',
16 | {% elif cookiecutter.gui_framework == 'PySide2' %}'PySide2>=5.15.2',
17 | {% elif cookiecutter.gui_framework == 'PySide6' %}'PySide6>=6.1.2',
18 | {% elif cookiecutter.gui_framework == 'Kivy' %}'Kivy>=2.0.0',
19 | {% elif cookiecutter.gui_framework == 'wxPython' %}'wxPython>=4.1.1',
20 | {% elif cookiecutter.gui_framework == 'Pyforms' %}'Pyforms>=4.0.3',
21 | {% elif cookiecutter.gui_framework == 'PySimpleGUI' %}'PySimpleGUI>=4.45.0',
22 | {% elif cookiecutter.gui_framework == 'PyGTK' %}'PyGTK>=2.24.2',
23 | {%- endif %}
24 | ]
25 |
26 | test_requirements = [{%- if cookiecutter.use_pytest == 'y' %}'pytest>=3',{%- endif %} ]
27 |
28 | {%- set license_classifiers = {
29 | 'MIT license': 'License :: OSI Approved :: MIT License',
30 | 'BSD license': 'License :: OSI Approved :: BSD License',
31 | 'ISC license': 'License :: OSI Approved :: ISC License (ISCL)',
32 | 'Apache Software License 2.0': 'License :: OSI Approved :: Apache Software License',
33 | 'GNU General Public License v3': 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)'
34 | } %}
35 |
36 | setup(
37 | author="{{ cookiecutter.full_name.replace('\"', '\\\"') }}",
38 | author_email='{{ cookiecutter.email }}',
39 | python_requires='>=3.6',
40 | classifiers=[
41 | 'Development Status :: 2 - Pre-Alpha',
42 | 'Environment :: Win32 (MS Windows)',
43 | 'Environment :: MacOS X',
44 | 'Environment :: X11 Applications',
45 | 'Environment :: Web Environment',
46 | 'Environment :: Other Environment',
47 | 'Intended Audience :: Developers',
48 | 'Intended Audience :: End Users/Desktop',
49 | {%- if cookiecutter.open_source_license in license_classifiers %}
50 | '{{ license_classifiers[cookiecutter.open_source_license] }}',
51 | {%- endif %}
52 | 'Natural Language :: English',
53 | 'Programming Language :: Python :: 3',
54 | 'Programming Language :: Python :: 3.6',
55 | 'Programming Language :: Python :: 3.7',
56 | 'Programming Language :: Python :: 3.8',
57 | 'Programming Language :: Python :: 3.9',
58 | 'Topic :: Software Development',
59 | 'Topic :: Communications :: Email',
60 | 'Topic :: Office/Business',
61 | ],
62 | description="{{ cookiecutter.project_short_description }}",
63 | install_requires=requirements,
64 | {%- if cookiecutter.open_source_license in license_classifiers %}
65 | license="{{ cookiecutter.open_source_license }}",
66 | {%- endif %}
67 | long_description=readme + '\n\n' + history,
68 | include_package_data=True,
69 | keywords='{{ cookiecutter.project_slug }}',
70 | name='{{ cookiecutter.project_slug }}',
71 | packages=find_packages(include=['{{ cookiecutter.project_slug }}', '{{ cookiecutter.project_slug }}.*']),
72 | test_suite='tests',
73 | tests_require=test_requirements,
74 | url='https://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }}',
75 | version='{{ cookiecutter.version }}',
76 | zip_safe=False,
77 | )
78 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/tests/__init__.py:
--------------------------------------------------------------------------------
1 | """Unit test package for {{ cookiecutter.project_slug }}."""
2 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | envlist = py36, py37, py38, flake8
3 |
4 | [travis]
5 | python =
6 | 3.8: py38
7 | 3.7: py37
8 | 3.6: py36
9 |
10 | [testenv:flake8]
11 | basepython = python
12 | deps = flake8
13 | commands = flake8 {{ cookiecutter.project_slug }} tests
14 |
15 | [testenv]
16 | setenv =
17 | PYTHONPATH = {toxinidir}
18 | {% if cookiecutter.use_pytest == 'y' -%}
19 | deps =
20 | -r{toxinidir}/requirements_dev.txt
21 | ; If you want to make tox run the tests with the same versions, create a
22 | ; requirements.txt with the pinned versions and uncomment the following line:
23 | ; -r{toxinidir}/requirements.txt
24 | commands =
25 | pip install -U pip
26 | pytest --basetemp={envtmpdir}
27 | {% else %}
28 | commands = python setup.py test
29 | {%- endif %}
30 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py:
--------------------------------------------------------------------------------
1 | """Top-level package for {{ cookiecutter.project_name }}."""
2 |
3 | __author__ = """{{ cookiecutter.full_name }}"""
4 | __email__ = '{{ cookiecutter.email }}'
5 | __version__ = '{{ cookiecutter.version }}'
6 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}.py:
--------------------------------------------------------------------------------
1 | """Main module."""
2 |
3 | {%- if cookiecutter.gui_framework == 'PyQt5' %}
4 |
5 | import sys
6 | from PyQt5.QtWidgets import QApplication, QWidget
7 |
8 |
9 | def main():
10 |
11 | app = QApplication(sys.argv)
12 |
13 | w = QWidget()
14 | w.setWindowTitle('{{ cookiecutter.project_name }}')
15 | w.show()
16 |
17 | sys.exit(app.exec_())
18 |
19 |
20 | if __name__ == '__main__':
21 | main()
22 |
23 |
24 | {%- endif %}
--------------------------------------------------------------------------------