├── tkinterweb.egg-info ├── top_level.txt ├── requires.txt └── SOURCES.txt ├── MANIFEST.in ├── requirements.txt ├── .gitignore ├── .github └── FUNDING.yml ├── images ├── tkinterweb-demo.png ├── tkinterweb-tkhtml.png └── tkinterweb-tkinter.png ├── docs ├── source │ ├── _static │ │ ├── logo.png │ │ ├── banner.png │ │ ├── banner.png~ │ │ ├── logo_full.png │ │ └── custom.css │ ├── api │ │ ├── tkinterweb.rst │ │ ├── notebook.rst │ │ ├── extensions.rst │ │ ├── htmldocument.rst │ │ ├── tkinterweb_api.rst │ │ └── htmlframe.rst │ ├── api.rst │ ├── conf.py │ ├── dom.rst │ ├── index.rst │ ├── compatibility.rst │ ├── geometry.rst │ ├── faq.rst │ ├── javascript.rst │ ├── caret.rst │ ├── usage.rst │ └── upgrading.rst ├── requirements.txt ├── Makefile └── make.bat ├── tkinterweb ├── resources │ ├── pkgIndex.tcl │ └── opensans.ttf ├── __init__.py ├── imageutils.py ├── subwidgets.py └── handlers.py ├── .readthedocs.yaml ├── LICENSE.md ├── setup.py ├── tools └── preparewheels.py ├── README.md └── examples └── TkinterWebBrowser.py /tkinterweb.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | tkinterweb 2 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include tkinterweb/resources * 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | tkinterweb-tkhtml >= 2.0.0 2 | Pillow >= 10.0.0 -------------------------------------------------------------------------------- /tkinterweb.egg-info/requires.txt: -------------------------------------------------------------------------------- 1 | pillow 2 | tkinterweb-tkhtml -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | tkinterweb/__pycache__/ 3 | tkinterweb/resources/ 4 | tkinterweb.egg-info 5 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | buy_me_a_coffee: andereoo 4 | -------------------------------------------------------------------------------- /images/tkinterweb-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Andereoo/TkinterWeb/HEAD/images/tkinterweb-demo.png -------------------------------------------------------------------------------- /docs/source/_static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Andereoo/TkinterWeb/HEAD/docs/source/_static/logo.png -------------------------------------------------------------------------------- /images/tkinterweb-tkhtml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Andereoo/TkinterWeb/HEAD/images/tkinterweb-tkhtml.png -------------------------------------------------------------------------------- /images/tkinterweb-tkinter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Andereoo/TkinterWeb/HEAD/images/tkinterweb-tkinter.png -------------------------------------------------------------------------------- /docs/source/_static/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Andereoo/TkinterWeb/HEAD/docs/source/_static/banner.png -------------------------------------------------------------------------------- /docs/source/_static/banner.png~: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Andereoo/TkinterWeb/HEAD/docs/source/_static/banner.png~ -------------------------------------------------------------------------------- /tkinterweb/resources/pkgIndex.tcl: -------------------------------------------------------------------------------- 1 | package ifneeded combobox 2.3 [list source [file join $dir combobox-2.3.tm]] 2 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx==7.1.2 2 | sphinx-rtd-theme==1.3.0rc1 3 | tkinterweb-tkhtml >= 2.0.0 4 | Pillow >= 10.0.0 5 | -------------------------------------------------------------------------------- /docs/source/_static/logo_full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Andereoo/TkinterWeb/HEAD/docs/source/_static/logo_full.png -------------------------------------------------------------------------------- /tkinterweb/resources/opensans.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Andereoo/TkinterWeb/HEAD/tkinterweb/resources/opensans.ttf -------------------------------------------------------------------------------- /docs/source/api/tkinterweb.rst: -------------------------------------------------------------------------------- 1 | Internals Documentation 2 | ======================= 3 | 4 | .. toctree :: 5 | :maxdepth: 2 6 | 7 | tkinterweb_api 8 | extensions -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | 3 | build: 4 | os: "ubuntu-22.04" 5 | tools: 6 | python: "3.10" 7 | 8 | python: 9 | install: 10 | - requirements: docs/requirements.txt 11 | 12 | sphinx: 13 | configuration: docs/source/conf.py -------------------------------------------------------------------------------- /docs/source/api.rst: -------------------------------------------------------------------------------- 1 | API Reference 2 | ============== 3 | 4 | .. note:: 5 | The API changed significantly in version 4. See :doc:`the changelog ` for details. 6 | 7 | .. toctree:: 8 | :maxdepth: 2 9 | :caption: Available Classes 10 | 11 | api/htmlframe 12 | api/htmldocument 13 | api/notebook 14 | 15 | ------------------- 16 | 17 | .. toctree:: 18 | :maxdepth: 3 19 | 20 | api/tkinterweb -------------------------------------------------------------------------------- /tkinterweb.egg-info/SOURCES.txt: -------------------------------------------------------------------------------- 1 | LICENSE.md 2 | MANIFEST.in 3 | README.md 4 | setup.py 5 | tkinterweb/__init__.py 6 | tkinterweb/bindings.py 7 | tkinterweb/dom.py 8 | tkinterweb/htmlwidgets.py 9 | tkinterweb/imageutils.py 10 | tkinterweb/subwidgets.py 11 | tkinterweb/utilities.py 12 | tkinterweb.egg-info/PKG-INFO 13 | tkinterweb.egg-info/SOURCES.txt 14 | tkinterweb.egg-info/dependency_links.txt 15 | tkinterweb.egg-info/requires.txt 16 | tkinterweb.egg-info/top_level.txt 17 | tkinterweb/resources/combobox-2.3.tm 18 | tkinterweb/resources/opensans.ttf 19 | tkinterweb/resources/pkgIndex.tcl -------------------------------------------------------------------------------- /docs/source/api/notebook.rst: -------------------------------------------------------------------------------- 1 | Notebook Documentation 2 | ======================= 3 | 4 | The TkinterWeb :class:`~tkinterweb.Notebook` widget should be used in place of :py:class:`ttk.Notebook`, which is incompatable with Tkhtml on 64-bit Windows and crashes when selecting tabs. See https://docs.python.org/3/library/tkinter.ttk.html#notebook for the full API. 5 | 6 | .. autoclass:: tkinterweb.Notebook 7 | :members: 8 | 9 | This widget also emits the following Tkinter virtual events that can be bound to: 10 | 11 | * ``<>``: Generated whenever the selected tab changes. 12 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 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 | -------------------------------------------------------------------------------- /docs/source/api/extensions.rst: -------------------------------------------------------------------------------- 1 | TkinterWeb Extensions 2 | ===================== 3 | 4 | The following objects are extensions to the :class:`~tkinterweb.TkinterWeb` widget and are largely internal. You will likely never need to access them, but they are described here just in case. 5 | 6 | The methods described in this page may change at any time without warning. If you are relying on anything here, please let me know so I know to keep compatibility. 7 | 8 | .. autoclass:: tkinterweb.extensions.SelectionManager 9 | :members: 10 | 11 | .. autoclass:: tkinterweb.extensions.CaretManager 12 | :members: 13 | 14 | .. autoclass:: tkinterweb.extensions.EventManager 15 | :members: 16 | 17 | .. autoclass:: tkinterweb.extensions.WidgetManager 18 | :members: 19 | 20 | .. autoclass:: tkinterweb.extensions.SearchManager 21 | :members: -------------------------------------------------------------------------------- /docs/source/api/htmldocument.rst: -------------------------------------------------------------------------------- 1 | Document Object Model Documentation 2 | =================================== 3 | 4 | .. note:: 5 | The API changed significantly in version 4. See :doc:`the changelog <../upgrading>` for details. 6 | 7 | The methods described in this page make it easy to modify the appearance and content of a loaded document and manage interaction with the document. For the most part, this page mirrors the core JavaScript DOM API. 8 | 9 | .. autoclass:: tkinterweb.dom.HTMLDocument 10 | :members: 11 | 12 | .. autoclass:: tkinterweb.dom.HTMLElement 13 | :members: 14 | 15 | .. autoclass:: tkinterweb.dom.HTMLCollection 16 | :members: 17 | 18 | .. autoclass:: tkinterweb.dom.CSSStyleDeclaration 19 | :members: 20 | 21 | .. autoclass:: tkinterweb.dom.DOMRect 22 | :members: 23 | 24 | Special thanks to `Zamy846692 `_ for the help making this happen! 25 | -------------------------------------------------------------------------------- /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=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/source/api/tkinterweb_api.rst: -------------------------------------------------------------------------------- 1 | Bindings Documentation 2 | ====================== 3 | 4 | .. note:: 5 | This API has changed significantly recently. See :doc:`the changelog <../upgrading>` for details. 6 | 7 | 8 | The following objects offer the core bindings to the Tkhtml3 HTML widget and are largely internal. You will likely never need to access them, but they are described here just in case. 9 | 10 | .. autoclass:: tkinterweb.TkinterWeb 11 | :members: 12 | 13 | 14 | The :class:`~tkinterweb.TkinterWeb` class and its extensions also include many variables that can be used to change the widget's behaviour or to get the its state (eg. access embedded input widgets or the nodes under the mouse). If this widget is being used through the :class:`~tkinterweb.HtmlFrame` or :class:`~tkinterweb.HtmlLabel` widget, many of these variables are exposed as properties or as configuration options, but many more are unique and could be useful in certain cases. 15 | 16 | Please see the source code for more details. 17 | 18 | 19 | .. autoclass:: tkinterweb.TkHtmlParsedURI 20 | :members: -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-2025 Andrew Clarke 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | 3 | # -- Project information 4 | 5 | import os 6 | import sys 7 | 8 | sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname((os.path.realpath(__file__)))))) 9 | 10 | from tkinterweb import __title__, __copyright__, __author__, __version__ 11 | 12 | project = __title__ 13 | copyright = __copyright__ 14 | author = __author__ 15 | 16 | release = ".".join(__version__.split(".")[:2]) 17 | version = __version__ 18 | 19 | # -- General configuration 20 | 21 | extensions = [ 22 | 'sphinx.ext.duration', 23 | 'sphinx.ext.doctest', 24 | 'sphinx.ext.autodoc', 25 | 'sphinx.ext.autosummary', 26 | 'sphinx.ext.intersphinx', 27 | ] 28 | 29 | intersphinx_mapping = { 30 | 'python': ('https://docs.python.org/3/', None), 31 | 'sphinx': ('https://www.sphinx-doc.org/en/master/', None), 32 | } 33 | 34 | html_logo = "_static/logo.png" 35 | html_theme_options = {"logo_only": True} 36 | templates_path = ['_templates'] 37 | 38 | html_static_path = ['_static'] 39 | html_css_files = ['custom.css'] 40 | 41 | # -- Options for HTML output 42 | 43 | autodoc_member_order = 'bysource' 44 | 45 | html_theme = 'sphinx_rtd_theme' # May switch to agogo or alabaster or python_docs_theme 46 | 47 | # -- Options for EPUB output 48 | epub_show_urls = 'footnote' 49 | 50 | def skip_member(app, what, name, obj, skip, options): 51 | if name == "destroy" and what == "class": 52 | return True 53 | return skip 54 | 55 | def setup(app): 56 | app.connect("autodoc-skip-member", skip_member) -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | from setuptools import setup, find_namespace_packages 3 | 4 | HERE = pathlib.Path(__file__).parent 5 | README = (HERE / "README.md").read_text() 6 | 7 | setup( 8 | name="tkinterweb", 9 | version="4.11.0", 10 | python_requires=">=3.2", 11 | description="HTML/CSS viewer, editor, and app builder for Tkinter", 12 | long_description=README, 13 | long_description_content_type="text/markdown", 14 | url="https://github.com/Andereoo/TkinterWeb", 15 | license="MIT", 16 | classifiers=[ 17 | "Intended Audience :: Developers", 18 | "License :: Freeware", 19 | "License :: OSI Approved :: MIT License", 20 | "Natural Language :: English", 21 | "Programming Language :: Python", 22 | "Programming Language :: Python :: 3", 23 | "Topic :: Software Development", 24 | ], 25 | keywords="tkinter, Tkinter, tkhtml, Tkhtml, Tk, HTML, CSS, webbrowser", 26 | packages=find_namespace_packages(include=["tkinterweb", "tkinterweb.*"]), 27 | include_package_data=True, 28 | install_requires=["tkinterweb-tkhtml>=2.1.0"], 29 | extras_require = { 30 | "html": ["tkinterweb-tkhtml-extras"], 31 | "images": ["pillow"], 32 | "svg": ["tkinterweb-tkhtml-extras", "pillow", "cairosvg"], 33 | "javascript": ["pythonmonkey"], 34 | "requests": ["brotli"], 35 | 36 | "recommended": ["tkinterweb-tkhtml-extras", "pillow"], 37 | "full": ["tkinterweb-tkhtml-extras", "pillow", "cairosvg", "pythonmonkey", "brotli"], 38 | }, 39 | ) 40 | -------------------------------------------------------------------------------- /docs/source/dom.rst: -------------------------------------------------------------------------------- 1 | Manipulating the Page 2 | ===================== 3 | 4 | .. note:: 5 | The API changed significantly in version 4. See :doc:`the changelog ` for details. 6 | 7 | Overview 8 | -------- 9 | 10 | **TkinterWeb provides a handful of functions that allow for manipulation of the webpage. They are fashioned after common JavaScript functions.** 11 | 12 | 13 | How-to 14 | -------- 15 | 16 | To manipulate the Document Object Model, use the :attr:`~tkinterweb.HtmlFrame.document` property of your :class:`~tkinterweb.HtmlFrame` or :class:`~tkinterweb.HtmlLabel` widget. For example, to create a heading with blue text inside of an element with the id "container", one can use the following: 17 | 18 | .. code-block:: python 19 | 20 | yourhtmlframe = tkinterweb.HtmlFrame(root) 21 | yourhtmlframe.load_html("

Test

") 22 | container = yourhtmlframe.document.getElementById("container") 23 | new_header = yourhtmlframe.document.createElement("h1") 24 | new_header.textContent = "Hello, world!" 25 | new_header.style.color = "blue" 26 | container.appendChild(new_header) 27 | 28 | 29 | .. _binding-to-an-element: 30 | 31 | Binding to an element 32 | --------------------- 33 | 34 | To manage bindings on HTML elements, simply use :meth:`~tkinterweb.dom.HTMLElement.bind` and :meth:`~tkinterweb.dom.HTMLElement.unbind` (new in version 4.9): 35 | 36 | .. code-block:: python 37 | 38 | container = yourhtmlframe.document.getElementById("container") 39 | 40 | def callback(event): 41 | print("Woah this is cool!") 42 | 43 | container.bind("", callback) 44 | 45 | ------------------- 46 | 47 | See the :doc:`api/htmldocument` for a complete list of supported commands. 48 | 49 | See :doc:`javascript` for information on manipulating the DOM through JavaScript. 50 | 51 | Please report bugs or request new features on the `issues page `_. -------------------------------------------------------------------------------- /docs/source/api/htmlframe.rst: -------------------------------------------------------------------------------- 1 | HTML Widgets Documentation 2 | ========================== 3 | 4 | .. note:: 5 | The API changed significantly in version 4. See :doc:`the changelog <../upgrading>` for details. 6 | 7 | The :class:`~tkinterweb.HtmlFrame` widget is a Tkinter frame that provides additional functionality to the :class:`~tkinterweb.TkinterWeb` widget by adding automatic scrollbars, error handling, and many convenience methods into one embeddable and easy to use widget. 8 | 9 | The :class:`~tkinterweb.HtmlFrame` widget is also capable managing other Tkinter widgets, making it easy to combine Tkinter widgets and HTML elements. 10 | 11 | 12 | .. autoclass:: tkinterweb.HtmlFrame 13 | :members: 14 | 15 | This widget also emits the following Tkinter virtual events that can be bound to: 16 | 17 | * ``<>``/:py:attr:`utilities.DOWNLOADING_RESOURCE_EVENT`: Generated whenever a new resource is being downloaded. 18 | * ``<>``/:py:attr:`utilities.DONE_LOADING_EVENT`: Generated whenever all outstanding resources have been downloaded. This is generally a good indicator as to when the website is done loading, but may be generated multiple times while loading a page. 19 | * ``<>``/:py:attr:`utilities.DOM_CONTENT_LOADED_EVENT`: Generated once the page content has loaded. The page may not be done loading, but at this point it is possible to interact with the DOM. 20 | * ``<>``/:py:attr:`utilities.URL_CHANGED_EVENT`: Generated whenever the url the widget is navigating to changes. Use :attr:`.HtmlFrame.current_url` to get the url. 21 | * ``<>``/:py:attr:`utilities.ICON_CHANGED_EVENT`: Generated whenever the icon of a webpage changes. Use :attr:`.HtmlFrame.icon` to get the icon. 22 | * ``<>``/:py:attr:`utilities.TITLE_CHANGED_EVENT`: Generated whenever the title of a website or file has changed. Use :attr:`.HtmlFrame.title` to get the title. 23 | * ``<>```/:py:attr:`utilities.FIELD_CHANGED_EVENT`: Generated whenever the content of any ```` element changes. 24 | 25 | .. autoclass:: tkinterweb.HtmlLabel 26 | :members: 27 | 28 | .. autoclass:: tkinterweb.HtmlText 29 | :members: 30 | 31 | .. autoclass:: tkinterweb.HtmlParse 32 | :members: -------------------------------------------------------------------------------- /tools/preparewheels.py: -------------------------------------------------------------------------------- 1 | """ 2 | Wheel and sdist generator for TkinterWeb 3 | 4 | Copyright (c) 2025 Andrew Clarke 5 | """ 6 | 7 | import os, shutil, sys 8 | import subprocess 9 | 10 | if os.name == "nt": 11 | PYTHON_CMD = "python" 12 | else: 13 | PYTHON_CMD = "python3" 14 | 15 | ROOT_PATH = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) 16 | DIST_ROOT_PATH = os.path.join(ROOT_PATH, "dist") 17 | BUILD_ROOT_PATH = os.path.join(ROOT_PATH, "build") 18 | EGG_ROOT_PATH = os.path.join(ROOT_PATH, "tkinterweb.egg-info") 19 | TKINTERWEB_ROOT_PATH = os.path.join(ROOT_PATH, "tkinterweb") 20 | 21 | existing_folders = [] 22 | if os.path.exists(DIST_ROOT_PATH): 23 | existing_folders.append(DIST_ROOT_PATH) 24 | if os.path.exists(BUILD_ROOT_PATH): 25 | existing_folders.append(BUILD_ROOT_PATH) 26 | if os.path.exists(EGG_ROOT_PATH): 27 | existing_folders.append(EGG_ROOT_PATH) 28 | 29 | if existing_folders: 30 | should_continue = input("The following directories already exist:\n {} \nRemove and continue (Y/N)? ".format("\n ".join(existing_folders))) 31 | if should_continue.upper() == "Y": 32 | print() 33 | for path in existing_folders: 34 | print(f"Removing {path}") 35 | shutil.rmtree(path) 36 | else: 37 | print("Cancelling") 38 | #exit() 39 | 40 | print() 41 | 42 | def run_shell(*args, cwd=ROOT_PATH, is_wheel=False): 43 | p = subprocess.Popen(args, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 44 | out, err = p.communicate() 45 | out = out.decode('utf-8') 46 | if is_wheel and "Successful" in out: 47 | print("success!\n") 48 | else: 49 | print(f"\n{out}") 50 | if err: 51 | err = err.decode('utf-8').replace("\n\n", "\n") # For some reason some errors have tons of blank space 52 | print(f"Error: {err}", file=sys.stderr) 53 | 54 | print(f"Creating wheel and sdist for {TKINTERWEB_ROOT_PATH}...", end="") 55 | run_shell(PYTHON_CMD, "-m", "build", "--no-isolation", is_wheel=True) 56 | 57 | # Upload to pip 58 | should_continue = input("Upload to pip (Y/N)?") 59 | if should_continue.upper() == "Y": 60 | run_shell("twine", "upload", "dist/*", "-u", "__token__") 61 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to TkinterWeb! 2 | ====================== 3 | 4 | .. image:: https://static.pepy.tech/badge/tkinterweb/month 5 | :target: https://pepy.tech/project/tkinterweb 6 | :alt: PyPi Downloads 7 | 8 | .. image:: https://img.shields.io/pypi/l/tkinterweb 9 | :target: https://pypi.org/project/tkinterweb/ 10 | :alt: MIT Licence 11 | 12 | .. image:: https://img.shields.io/pypi/pyversions/tkinterweb 13 | :target: https://pypi.org/project/tkinterweb/ 14 | :alt: Python 3 15 | 16 | .. image:: https://img.shields.io/badge/%F0%9F%87%A8%F0%9F%87%A6%20made%20in%20Canada-grey 17 | :target: https://pypi.org/project/tkinterweb/ 18 | :alt: Made in Canada 19 | 20 | 21 | **TkinterWeb** is a Python library that offers fast and lightweight web browser, rich text, and app design widgets for Tkinter. 22 | 23 | It provides bindings and extensions for the Tkhtml3 widget from `http://tkhtml.tcl.tk `_, which enables displaying HTML and CSS code in Tkinter applications. 24 | 25 | Top features 26 | ------------ 27 | 28 | * Displaying websites, feeds, help files, and other styled HTML 29 | * Displaying images, including SVG images 30 | * Creating a rich text or HTML editor 31 | * Designing apps using HTML templates 32 | * Creating prettier apps, with rounded buttons and more! 33 | 34 | Check out the :doc:`usage` section to learn how to get started and discover tips and tricks, :doc:`faq` for frequently asked questions, and the :doc:`api` to explore all of the widgets and functions at your disposal! 35 | 36 | 37 | Love this project? 38 | ------------------ 39 | 40 | You can help this project by submitting a `bug report `_ to report bugs or suggest new features, or by submitting a `pull request `_ to offer fixes. Your help makes TkinterWeb become more stable and full-featured! 41 | 42 | Or, if you’d like to support ongoing development and maintenance, please consider supporting this project by `buying me a coffee `_ — any amount is hugely appreciated! 43 | 44 | .. raw:: html 45 | 46 | Buy Me A Coffee 47 | 48 | Pages 49 | ----- 50 | 51 | .. toctree:: 52 | :maxdepth: 1 53 | 54 | usage 55 | geometry 56 | dom 57 | caret 58 | javascript 59 | faq 60 | compatibility 61 | upgrading 62 | api 63 | -------------------------------------------------------------------------------- /docs/source/compatibility.rst: -------------------------------------------------------------------------------- 1 | System and Webpage Compatibility 2 | ================================ 3 | 4 | System compatibility 5 | -------------------- 6 | 7 | **TkinterWeb supports all platforms but only ships with precompiled Tkhtml binaries for the most common platforms:** 8 | 9 | * x86_64 Windows, Linux, and macOS 10 | * i686 Windows and Linux 11 | * ARM64 Macos and Linux 12 | * ARMv71 Linux 13 | 14 | If your system is unsupported, compile and install Tkhtml by visiting https://github.com/Andereoo/TkinterWeb-Tkhtml/tree/3.1-TclTk9 and running ``python compile.py --install``. 15 | 16 | Alternatively, you can install Tkhtml system-wide (i.e. through your system package manager) and then add the parameter :attr:`use_prebuilt_tkhtml=False` when creating your :class:`~tkinterweb.HtmlFrame` or :class:`~tkinterweb.HtmlLabel` widget to use the system's Tkhtml. Keep in mind that some features will no longer work. 17 | 18 | If you are encountering issues, feel free to submit a bug report or feature request. 19 | 20 | The experimental Tkhtml version is not provided as a pre-built binary but can be compiled from the source code at https://github.com/Andereoo/TkinterWeb-Tkhtml/tree/experimental. This version has better cross-platform compatibility, is printable, and introduces support for some new CSS3 properties! 21 | 22 | Webpage compatibility 23 | --------------------- 24 | 25 | **HTML/CSS:** 26 | 27 | * TkinterWeb supports HTML 4.01 and CSS 2.1. A full list of supported CSS declarations can be found at `http://tkhtml.tcl.tk/support.html `_. ``overflow-x`` is also supported on ``body`` and ``html`` elements. 28 | * Most CSS pseudo-elements, such as ``:hover`` and ``:active`` are also supported. 29 | * On 64-bit Windows and Linux, if the TkinterWeb-Tkhtml-Extras package is installed, ``border-radius`` and more cursor options are also supported. 30 | 31 | **JavaScript:** 32 | 33 | * JavaScript partly supported at the moment. See :doc:`javascript` for more information. 34 | 35 | * To use JavaScript, :py:mod:`PythonMonkey` must be installed. 36 | 37 | * It is also possible for the user to connect their own JavaScript interpreter or manipulate the document through Python. See :doc:`javascript` and :doc:`dom` for more information. 38 | 39 | **Images:** 40 | 41 | * TkinterWeb supports nearly 50 different image types through :py:mod:`PIL`. 42 | 43 | * In order to load Scalable Vector Graphic images, :py:mod:`CairoSVG`, both :py:mod:`PyCairo` and :py:mod:`PyGObject`, or both :py:mod:`PyCairo` and :py:mod:`Rsvg` must also be installed. 44 | 45 | ------------------- 46 | 47 | Please report bugs or request new features on the `issues page `_. -------------------------------------------------------------------------------- /docs/source/geometry.rst: -------------------------------------------------------------------------------- 1 | Embedding Widgets 2 | ================= 3 | 4 | .. note:: 5 | The API changed significantly in version 4. See :doc:`the changelog ` for details. 6 | 7 | Overview 8 | -------- 9 | 10 | By default, Tkinter provides three geometry managers: pack, place, and grid. While these geometry managers are very powerful, achieving certain layouts, especially with scrolling, can be very difficult. 11 | 12 | **TkinterWeb provides a system for attaching Tkinter widgets onto the window, and handles layouts, images, selection, scrolling, and much more for you.** 13 | 14 | How-to 15 | ------ 16 | 17 | To place a Tkinter widget inside an HTML document, add the ``data=[yourwidget]`` attribute to an ```` element. For example, to add a button under some italic text, one could do: 18 | 19 | .. code-block:: python 20 | 21 | yourframe = tkinterweb.HtmlFrame(root) 22 | yourbutton = tkinter.Button(yourframe, text="Hello, world!") 23 | source_html = f"This is some text
" 24 | yourframe.load_html(source_html) # or use add_html to add onto the existing document 25 | 26 | **Ensure your HtmlFrame widget was created before the widget you are embedding, or else the widget might not be visible.** 27 | 28 | .. tip:: 29 | 30 | Add the ``allowstyling`` attribute to automatically change the widget's background color, text color, and font to match the containing HTML element. Use ``allowstyling="deep"`` to also style subwidgets (new in version 4.9). 31 | 32 | Add the ``handledelete`` attribute to automatically call :meth:`~tkinter.Widget.destroy` on the widget when it is removed from the page (i.e. if another webpage is loaded). 33 | 34 | .. note:: 35 | 36 | By default, scrolling over an embedded widget will scroll the page if the widget or subwidgets do not handle scrolling themselves (new in version 4.9). You can override this behaviour by adding the ``allowscrolling`` or ``allowscrolling=false`` attribute. 37 | 38 | Widget position and sizing can be modified using CSS styling on the widget's associated ```` element. 39 | 40 | See :doc:`dom` (new in version 3.25) for more details. 41 | 42 | To get the element containing your widget, either use :meth:`.HtmlFrame.widget_to_element`. 43 | 44 | Widget handling 45 | --------------- 46 | 47 | You can also set, remove, or change the widget in any element later (new in version 4.2): 48 | 49 | .. code-block:: python 50 | 51 | yourbutton = tkinter.Button(yourframe, text="Hello, world!") 52 | ... 53 | yourelement = yourframe.document.getElementById("#container") # get the element to fill 54 | yourelement.widget = yourbutton # set the element's widget 55 | 56 | The widget can be removed from the element via ``yourelement.widget = None``. 57 | 58 | ------------------- 59 | 60 | Please report bugs or request new features on the `issues page `_. -------------------------------------------------------------------------------- /docs/source/faq.rst: -------------------------------------------------------------------------------- 1 | Frequently Asked Questions 2 | ========================== 3 | 4 | How do I load websites or files? 5 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 6 | 7 | * Use the :meth:`~tkinterweb.HtmlFrame.load_website` or :meth:`~tkinterweb.HtmlFrame.load_file` commands. Alternatively, use the :meth:`~tkinterweb.HtmlFrame.load_url` command to load any generic url, but keep in mind that the url must be properly formatted, because the url scheme will not be automatically applied. As always, check out the :doc:`api/htmlframe` for more information. 8 | 9 | How do I manage clicks and use custom bindings? 10 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 11 | 12 | * The :attr:`on_link_click` configuration option can be used to assign a custom function to link clicks. Likewise :attr:`on_form_submit` can be used to handle form submissions. See the :doc:`api/htmlframe` for more information. 13 | * Like any other Tkinter widget, mouse and keyboard events can be bound to the :class:`~tkinterweb.HtmlFrame` widget and associated HTML elements. See the :doc:`usage` page for more information. 14 | 15 | TkinterWeb is crashing 16 | ~~~~~~~~~~~~~~~~~~~~~~ 17 | 18 | * That is defenitely not normal. Make sure your are using the most up-to-date TkinterWeb version and have crash protection enabled. 19 | * If you are using a :py:class:`ttk.Notebook` in your app, see the question below. 20 | * If all else fails, `file a bug report `_. Post your operating system, Python version, and TkinterWeb version, as well as any error codes or instructions for reproducing the crash. 21 | 22 | My app crashes when I open a tab with an HtmlFrame in it 23 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 24 | 25 | * Tkhtml (the underlying HTML engine) and the :py:class:`ttk.Notebook` widget aren't compatable on 64-bit Windows. 26 | * This is a known issue. Fixing this is beyond the scope of this project, but working around it is easy. 27 | * Instead of using :py:class:`ttk.Notebook`, use :class:`tkinterweb.Notebook`. This is a wrapper around ttk.Notebook that is designed to be a drop-in replacement for the :py:class:`ttk.Notebook` widget. It should look and behave exactly like a :py:class:`ttk.Notebook` widget, but without the crashes. See `bug #19 `_ for more information. 28 | * Please note that after adding a widget to the Notebook (eg. ``mynotebook.add(mywidget)``) there is no need to call :py:func:`~tkinterweb.Widget.pack` or :py:func:`~tkinterweb.Widget.grid` the widget. This may raise errors. TkinterWeb's Notebook widget handles all this on its own. 29 | 30 | I get a ModuleNotFoundError after compiling my code 31 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 32 | 33 | * When compiling your code, you might get an error popup saying ``ModuleNotFoundError: The files required to run TkinterWeb could not be found`` 34 | * Your app might also fail quietly if TkinterWeb's dependencies are not installed 35 | * This occurs when your Python script bundler isn't finding all the files nessessary for running TkinterWeb. You need to force it to get all of TkinterWeb's files and dependencies. 36 | * On PyInstaller: make sure you are using the latest version of PyInstaller by running ``pip install --upgrade pyinstaller pyinstaller-hooks-contrib``. Otherwise, you can also add the flags ``--collect-all tkinterweb --collect-all tkinterweb_tkhtml --collect-all tkinterweb_tkhtml_extras`` when bundling your app. 37 | * On py2app / py2exe: Add ``'packages': ['tkinterweb', 'tkinterweb_tkhtml', 'tkinterweb_tkhtml_extras']`` to the ``OPTIONS`` variable in your setup file. 38 | 39 | ------------------- 40 | 41 | Please report bugs or request new features on the `issues page `_. 42 | -------------------------------------------------------------------------------- /docs/source/javascript.rst: -------------------------------------------------------------------------------- 1 | Using JavaScript 2 | ================ 3 | 4 | .. note:: 5 | JavaScript support is new in version 4.1. Make sure you are using the latest version of TkinterWeb. 6 | 7 | Overview 8 | -------- 9 | 10 | **JavaScript is fully supported through Mozilla's SpiderMonkey engine, but not all DOM commands are supported.** See the :doc:`api/htmldocument` for an exhaustive list of supported DOM commands. 11 | 12 | Setup 13 | ------ 14 | 15 | To enable JavaScript support in TkinterWeb, first install PythonMonkey using pip: 16 | 17 | .. code-block:: console 18 | 19 | $ pip install pythonmonkey 20 | 21 | Or when installing TkinterWeb, use: 22 | 23 | .. code-block:: console 24 | 25 | $ pip install tkinterweb[javascript] 26 | 27 | Then add ``yourhtmlframe.configure(caret_browsing_enabled=True)`` to your script or add the parameter ``javascript_enabled=True`` when creating your :class:`~tkinterweb.HtmlFrame` or :class:`~tkinterweb.HtmlLabel` widget. 28 | 29 | **Only enable JavaScript on documents with code you know and trust.** 30 | 31 | How-to 32 | ------ 33 | 34 | To change the color and text of a ``

`` element when clicked, you could use the following: 35 | 36 | .. code-block:: python 37 | 38 | yourhtmlframe = tkinterweb.HtmlFrame(root, javascript_enabled=True) 39 | yourhtmlframe.load_html(""" 40 | 46 |

Hello, world!

47 | """) 48 | 49 | Add the ``defer`` attribute to the relevant ``

Hello, world!

") 65 | 66 | Using your own interpreter 67 | -------------------------- 68 | 69 | Alternatively, you can register your own callback for ``

Test

") 78 | 79 | 80 | You can also use the :attr:`on_element_script` parameter to handle event scripts (i.e. handle an element's ``onclick`` attribute). The element's corresponding Tkhtml node, relevant event, and code to execute will be passed as parameters. 81 | 82 | If needed you can always then create an :class:`~tkinterweb.dom.HTMLElement` instance from a Tkhtml node: 83 | 84 | .. code-block:: python 85 | 86 | from tkinterweb.dom import HTMLElement 87 | ... 88 | yourhtmlelement = HTMLElement(yourhtmlframe.document, yourtkhtmlnode) 89 | 90 | ------------------- 91 | 92 | It is also possible to interact with the document through Python instead. See :doc:`dom`. 93 | 94 | Please report bugs or request new features on the `issues page `_. -------------------------------------------------------------------------------- /docs/source/_static/custom.css: -------------------------------------------------------------------------------- 1 | /* This file is used to make tweak the documentation colours and make it CSS2 compatible.*/ 2 | 3 | .wy-nav-side { 4 | overflow: hidden !important; 5 | } 6 | 7 | .wy-side-nav-search > div.version { 8 | color: #cacaca !important; 9 | } 10 | 11 | .wy-side-nav-search, .wy-nav-top, .note .admonition-title, .wy-menu-vertical a:active { 12 | background-color: rgb(77, 122, 77) !important; 13 | } 14 | 15 | .wy-nav-content a { 16 | color: rgb(77, 122, 77); 17 | } 18 | 19 | .wy-nav-content a:hover { 20 | color: rgb(96, 156, 96); 21 | } 22 | 23 | code.literal, 24 | span.literal { 25 | color: rgb(94, 94, 94) !important; 26 | } 27 | 28 | .rst-content code.xref, 29 | .rst-content tt.xref, 30 | a .rst-content code, 31 | a .rst-content tt { 32 | color: initial !important 33 | } 34 | 35 | .note { 36 | background-color: rgb(230, 238, 230) !important; 37 | } 38 | 39 | #rtd-search-form input { 40 | width: 80% !important; 41 | color: black !important; 42 | margin-left: auto; 43 | margin-right: auto; 44 | } 45 | 46 | .wy-breadcrumbs .icon-home:before { 47 | content: "Home" !important; 48 | font-family: Lato, proxima-nova, Helvetica Neue, Arial, sans-serif !important; 49 | } 50 | 51 | .wy-breadcrumbs .wy-breadcrumbs-aside { 52 | display: none; 53 | } 54 | 55 | .wy-side-nav-search { 56 | padding-left: 0 !important; 57 | padding-right: 0 !important; 58 | } 59 | 60 | .wy-breadcrumbs .breadcrumb-item { 61 | display: inline !important; 62 | } 63 | 64 | .highlight-python, 65 | .highlight-console, 66 | .highlight-default { 67 | border: 1px solid #e1e4e5 !important; 68 | margin: 1px 0 24px !important; 69 | background: #f8f8f8 !important; 70 | width: 100% !important; 71 | } 72 | 73 | .highlight pre { 74 | white-space: pre !important; 75 | margin: 0 !important; 76 | padding: 12px !important; 77 | display: block !important; 78 | } 79 | 80 | .highlight { 81 | border: none !important; 82 | margin: 0 !important; 83 | } 84 | 85 | .sig-object { 86 | display: table !important; 87 | margin: 6px 0 !important; 88 | margin-top: 6px !important; 89 | font-size: 90% !important; 90 | line-height: normal !important; 91 | background: rgb(230, 238, 230) !important; 92 | color: rgb(77, 122, 77) !important; 93 | border-top: 3px solid rgb(101, 145, 101) !important; 94 | padding: 6px !important; 95 | position: relative !important; 96 | } 97 | 98 | .sig-object .property, 99 | .sig-object .sig-param, 100 | .sig-object .sig-paren { 101 | font-size: 90% !important; 102 | line-height: normal !important; 103 | color: rgb(77, 122, 77) !important; 104 | } 105 | 106 | .sig-object .sig-name, 107 | .sig-object .sig-prename { 108 | font-family: SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, Courier, monospace !important; 109 | color: #000 !important; 110 | } 111 | 112 | dd .sig-object { 113 | margin-bottom: 6px !important; 114 | border: none !important; 115 | border-left-width: medium !important; 116 | border-left-style: none !important; 117 | border-left-color: currentcolor !important; 118 | border-left: 3px solid #ccc !important; 119 | background: #f0f0f0 !important; 120 | color: #555 !important; 121 | } 122 | 123 | dd .sig-name { 124 | font-family: SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, Courier, monospace !important; 125 | color: #000 !important; 126 | } 127 | 128 | dd .property, 129 | dd .sig-param, 130 | dd .sig-paren { 131 | color: inherit !important 132 | } 133 | 134 | .rst-footer-buttons { 135 | display: table !important; 136 | width: 100% !important; 137 | } 138 | 139 | .rst-footer-buttons .float-left { 140 | display: table-cell !important; 141 | } 142 | 143 | .rst-footer-buttons .float-right { 144 | display: table-cell !important; 145 | } 146 | 147 | .fa-arrow-circle-right:before { 148 | content: " →" !important; 149 | } 150 | 151 | .fa-arrow-circle-left:before { 152 | content: "← " !important; 153 | } 154 | 155 | .btn { 156 | border-color: #e1e4e5 !important; 157 | padding: 6px 12px !important; 158 | border-bottom: 2px solid #ccc !important; 159 | box-shadow: none !important; 160 | transition: none !important; 161 | } 162 | 163 | .btn:active { 164 | border-top: 2px solid #ccc !important; 165 | padding: 5px 12px 7px !important; 166 | border-bottom-width: 1px !important; 167 | } 168 | 169 | .wy-side-nav-search > a { 170 | padding: 0 !important; 171 | margin: 0 !important; 172 | } 173 | 174 | .wy-side-nav-search > a:hover { 175 | background: transparent !important; 176 | } 177 | 178 | .wy-side-nav-search > a img.logo { 179 | max-width: 250px !important; 180 | margin: 0 !important; 181 | } -------------------------------------------------------------------------------- /tkinterweb/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | TkinterWeb v4 3 | This is a wrapper for the Tkhtml3 widget from http://tkhtml.tcl.tk/tkhtml.html, 4 | which displays styled HTML documents in Tkinter. 5 | 6 | Copyright (c) 2021-2025 Andrew Clarke 7 | """ 8 | 9 | 10 | try: 11 | from .htmlwidgets import HtmlFrame, HtmlLabel, HtmlText, HtmlParse 12 | from .subwidgets import Notebook 13 | from .bindings import TkHtmlParsedURI, TkinterWeb 14 | from .utilities import __title__, __author__, __copyright__, __license__, __version__ 15 | except (ImportError, ModuleNotFoundError) as error: 16 | import sys 17 | import tkinter as tk 18 | from tkinter import messagebox 19 | # Give useful troubleshooting information as a popup, as most bundled applications don't have a visible console 20 | # Also print the message in case something is also wrong with the Tkinter installation 21 | error_message = f"Error: {error} \n\n\ 22 | This may occur when bundling TkinterWeb into an app without forcing the application maker to include all nessessary files or when some of TkinterWeb's dependencies are not installed or bundled.\n\n\ 23 | See https://tkinterweb.readthedocs.io/en/latest/faq.html for more information." 24 | sys.stdout.write(error_message) 25 | root = tk.Tk() 26 | root.withdraw() 27 | # For older versions of pyinstaller, windowed app may crash without any message of any kind 28 | message = messagebox.showerror("Fatal Error Encountered", error_message) 29 | sys.exit() 30 | 31 | 32 | __all__ = ['Demo', 'HtmlFrame', 'HtmlLabel', 'HtmlText', 'HtmlParse', 'Notebook', 'TkHtmlParsedURI', 'TkinterWeb'] 33 | 34 | 35 | class Demo(): 36 | "A simple example of TkinterWeb in action displaying the Tkinter Wiki." 37 | 38 | def __init__(self): 39 | import tkinter as tk 40 | 41 | self.root = root = tk.Tk() 42 | self.frame = frame = HtmlFrame(root, on_navigate_fail=self.on_error, on_link_click=self.navigate, selected_text_highlight_color="#e6eee6") 43 | self.button = tk.Button(root, cursor="hand2") 44 | 45 | frame.load_url("https://tkinterweb.readthedocs.io/en/latest/") 46 | frame.bind("<>", lambda event: self.root.title(frame.title)) 47 | frame.bind("<>", self.done_loading) 48 | frame.pack(expand=True, fill="both") 49 | 50 | root.mainloop() 51 | 52 | def HTML_to_text(self, text, start, end): 53 | "Make HTML code bwtween two strings display as plain text" 54 | import re 55 | pattern = re.compile(re.escape(start) + r'(.*?)' + re.escape(end), re.DOTALL) 56 | def replacer(match): 57 | inner = match.group(1) 58 | escaped = inner.replace("<", "<").replace(">", ">").replace(">", ">", 1) 59 | return start + escaped + end 60 | return pattern.sub(replacer, text) 61 | 62 | def navigate(self, url): 63 | "Only display files from the docs page or from tkhtml.tcl.tk" 64 | from urllib.parse import urlparse 65 | if urlparse(self.frame.current_url).netloc == urlparse(url).netloc or "tkhtml.tcl.tk" in url: 66 | self.frame.load_url(url) 67 | else: 68 | import webbrowser 69 | webbrowser.open(url) 70 | 71 | def done_loading(self, event): 72 | "Remove the search bar and display code blocks in iframes to allow horizontal scrolling when the page loads" 73 | from tkinter import TclError 74 | try: 75 | self.frame.document.querySelector("div[role=\"search\"]").remove() 76 | head = self.frame.document.getElementsByTagName("head")[0].innerHTML 77 | for code_block in self.frame.document.getElementsByClassName("highlight"): 78 | iframe = HtmlFrame(self.frame, messages_enabled=False, horizontal_scrollbar="auto", shrink=True, overflow_scroll_frame=self.frame.html) 79 | text = self.HTML_to_text(code_block.innerHTML, "") 80 | iframe.load_html(f"{head}
{text}
", base_url=self.frame.base_url) 81 | code_block.widget = iframe 82 | except TclError: 83 | pass 84 | 85 | def on_error(self, url, error, code): 86 | "Show an error page if the page fails to load" 87 | self.button.configure(text="Try Again", command=lambda url=self.frame.current_url: self.frame.load_url(url)) 88 | html = f"""TkinterWeb Demo - Error {code} 89 |
90 |

Error {code}

An internet connection is required to display the TkinterWeb demo :(

91 |
""" 92 | self.frame.load_html(html) -------------------------------------------------------------------------------- /docs/source/caret.rst: -------------------------------------------------------------------------------- 1 | Making Documents Editable 2 | ========================= 3 | 4 | .. note:: 5 | Caret browsing support is new in version 4.8. Make sure you are using the latest version of TkinterWeb. 6 | 7 | Overview 8 | -------- 9 | 10 | **Caret browsing can be used to turn TkinterWeb into a rich text or HTML editor.** 11 | 12 | As the specific behaviour of rich text or HTML editors is highly variable, TkinterWeb is not intended to be a full-featured HTML editor out of the box, but it provides a useful API for developers to easily create their own what-you-see-is-what-you-get editor. 13 | 14 | This feature is new. Please reach out to report a bug, suggest an improvement, or seek support. 15 | 16 | Setup 17 | ------ 18 | 19 | To enable caret browsing mode in TkinterWeb, add ``yourhtmlframe.configure(caret_browsing_enabled=True)`` to your script or add the parameter ``caret_browsing_enabled=True`` when creating your :class:`~tkinterweb.HtmlFrame` or :class:`~tkinterweb.HtmlLabel` widget. 20 | 21 | .. tip :: 22 | Since version 4.11, you can instead use the new :class:`~tkinterweb.HtmlText` widget! 23 | 24 | When caret browsing mode is enabled, a caret will appear once the user clicks on text in the document. Unless the default keybindings are overridden, this caret can be moved by clicking elsewhere or by using the arrow keys. 25 | 26 | How-to 27 | ------ 28 | 29 | Use :meth:`.HtmlFrame.get_caret_position` to get the caret's position. The element returned will always be a text node. You can use the methods outlined in the `HTMLElement documentation `_ to get its parent or siblings if needed. 30 | 31 | Use :meth:`.HtmlFrame.shift_caret_left` or :meth:`.HtmlFrame.shift_caret_right` to shift the caret left or right. 32 | 33 | The following is a simple example showing how to handle keypresses to insert letters and numbers: 34 | 35 | .. code-block:: python 36 | 37 | def on_keypress(event): 38 | # Get the caret's position 39 | caret_position = yourhtmlframe.get_caret_position() 40 | if caret_position and event.char: 41 | element, text, index = caret_position 42 | 43 | # Add the key's character to the element's text 44 | newtext = text[:index] + event.char + text[index:] 45 | 46 | # Set the element's text 47 | element.textContent = newtext 48 | 49 | # Shift the caret right 50 | yourhtmlframe.shift_caret_right() 51 | 52 | yourhtmlframe.bind("", on_keypress) 53 | 54 | .. note:: 55 | Most HTML elements collapse spaces. To insert a space into the document's text, it is usually best to use a non-breaking space (``"\xa0"`` or ``" "``). 56 | 57 | Use :meth:`.HtmlFrame.set_caret_position` to set the caret's position if you know the element and index you want to place the caret at. 58 | 59 | Some extra logic will be needed to handle other types of keypresses. 60 | 61 | .. tip:: 62 | When handling backspaces at the start of a node or deletions at the end of a node, it is sometimes useful to find the previous or following text nodes, respectively. 63 | 64 | You can get the preceeding or following text nodes by using :meth:`.HtmlFrame.shift_caret_left` or :meth:`.HtmlFrame.shift_caret_right` followed by :meth:`.HtmlFrame.get_caret_position`. 65 | 66 | Managing selected text 67 | ---------------------- 68 | 69 | Use :meth:`.HtmlFrame.get_selection_position` to get the position of any selected text and :meth:`.HtmlFrame.clear_selection` to clear the selection. 70 | 71 | You will need to set the caret's position after modifying the document. 72 | 73 | .. tip:: 74 | :meth:`.HtmlFrame.set_caret_position` will raise an error if the element provided has been removed or is empty. 75 | 76 | If you need to remove or empty the elements returned by :meth:`.HtmlFrame.get_selection_position`, consider also getting the selection's position relative to the page text content using :meth:`.HtmlFrame.get_selection_page_position` to avoid losing track of the selection's position. 77 | 78 | You can then use :meth:`.HtmlFrame.set_caret_page_position` to set the caret relative to the page text content. 79 | 80 | 81 | The following code can be used as a starting point on handling backspaces when text is selected: 82 | 83 | .. code-block:: python 84 | 85 | def on_backspace(event): 86 | # Get the selection's position and deselect all selected text 87 | selection = yourhtmlframe.get_selection_position() 88 | 89 | if selection: 90 | start, end, middle = selection 91 | start_element, start_element_text, start_element_index = start 92 | end_element, end_element_text, end_element_index = end 93 | 94 | # Get the position of the selection relative to the page text and deselect all selected text 95 | start_pos, end_pos = yourhtmlframe.get_selection_page_position() 96 | yourhtmlframe.clear_selection() 97 | 98 | if start_element == end_element: 99 | # If the selected text is only in one element, cut out the selection from the element's text 100 | start_element.textContent = start_element_text[:start_element_index] + start_element_text[end_element_index:] 101 | 102 | else: 103 | # Otherwise, remove the selected part of the starting element's text (the part after 'start_element_index') 104 | start_element.textContent = start_element_text[:start_element_index] 105 | 106 | # Remove the selected part of the end element's text (the part before 'end_element_index') 107 | end_element.textContent = end_element_text[end_element_index:] 108 | 109 | # Remove each element that is fully selected, and its parent if it is now empty 110 | for element in middle: 111 | parent = element.parentElement 112 | element.remove() 113 | if len(parent.children) == 0: 114 | parent.remove() 115 | 116 | # Set the caret's position 117 | yourhtmlframe.set_caret_page_position(start_pos) 118 | 119 | yourhtmlframe.bind("", on_backspace) 120 | 121 | You can use :meth:`.HtmlFrame.set_selection_position` to set the selection if needed. 122 | 123 | ------------------- 124 | 125 | See the `HtmlFrame documentation `_ for a complete list of supported methods. 126 | 127 | Please report bugs or request new features on the `issues page `_. -------------------------------------------------------------------------------- /tkinterweb/imageutils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Generate Tk images and alt text 3 | 4 | Copyright (c) 2021-2025 Andrew Clarke 5 | """ 6 | 7 | from tkinter import PhotoImage as TkPhotoImage 8 | 9 | # Some folks only use TkinterWeb as a fancy label widget and don't need to load images 10 | # Or, if they do load images, they don't need support for images not supported by Tk 11 | # We only import PIL if/when needed 12 | # On my machine this reduces initial load time by up to a third 13 | # The same applies to alt-text and image inversion imports 14 | 15 | # For the same reasons as above, we only attempt to load Cairo when needed 16 | # Additionally, CairoSVG will only detect TkinterWeb-Tkhtml's Cairo binary after Tkhtml is loaded 17 | rsvg_type = None 18 | 19 | 20 | def load_cairo(): 21 | global rsvg_type 22 | if rsvg_type == None: 23 | try: 24 | import cairo 25 | globals()['cairo'] = cairo 26 | import rsvg 27 | globals()['rsvg'] = rsvg 28 | rsvg_type = 1 29 | except ImportError: 30 | try: 31 | import cairosvg as cairo 32 | globals()['cairo'] = cairo 33 | rsvg_type = 2 34 | except (ImportError, FileNotFoundError, OSError,): 35 | import gi 36 | gi.require_version('Rsvg', '2.0') 37 | from gi.repository import Rsvg as rsvg 38 | globals()['rsvg'] = rsvg 39 | # Don't import PyGobject's Cairo if PyCairo has already been imported 40 | if not cairo: 41 | gi.require_version('cairo', '1.0') 42 | from gi.repository import cairo 43 | globals()['cairo'] = cairo 44 | rsvg_type = 3 45 | 46 | 47 | def photoimage_del(image): 48 | "Monkey-patch to quiet Photoimage error messages. I think it's a PIL bug." 49 | try: 50 | name = image.__photo.name 51 | image.__photo.name = None 52 | image.__photo.tk.call("image", "delete", name) 53 | except AttributeError: 54 | pass 55 | 56 | 57 | def text_to_image(name, alt, nodebox, font_type, font_size, threshold): 58 | from PIL import Image 59 | from PIL.ImageTk import PhotoImage 60 | from PIL import ImageFont, ImageDraw 61 | 62 | if PhotoImage.__del__ is not photoimage_del: 63 | PhotoImage.__del__ = photoimage_del 64 | 65 | font = ImageFont.truetype(font_type, font_size) 66 | if len(nodebox) == 4: 67 | width = nodebox[2]-nodebox[0] 68 | height = nodebox[3]-nodebox[1] 69 | if (width < threshold) or (height < threshold): 70 | try: 71 | width, height = font.getsize(alt) 72 | except AttributeError: 73 | left, top, right, bottom = font.getbbox(alt) 74 | width = right - left 75 | height = bottom 76 | else: 77 | try: 78 | width, height = font.getsize(alt) 79 | except AttributeError: 80 | left, top, right, bottom = font.getbbox(alt) 81 | width = right - left 82 | height = bottom 83 | 84 | image = Image.new('RGBA', (width, height)) 85 | draw = ImageDraw.Draw(image) 86 | draw.text((0,0), alt, fill=(0, 0, 0), font=font) 87 | image = PhotoImage(image, name=name) 88 | return image 89 | 90 | def invert_image(image, limit): 91 | from PIL import Image, ImageOps 92 | from collections import Counter 93 | from io import BytesIO 94 | 95 | def is_mostly_one_color(image, tolerance=30): 96 | pixels = list(image.resize((100, 100), Image.Resampling.NEAREST).getdata()) 97 | counter = Counter(pixels) 98 | dominant_color = max(counter, key=counter.get) 99 | def is_similar(c1, c2): 100 | return sum(abs(a - b) for a, b in zip(c1, c2)) < tolerance * 3 101 | similar_count = sum(1 for p in pixels if is_similar(p, dominant_color)) 102 | ratio = similar_count / len(pixels) 103 | return ratio, dominant_color 104 | image = Image.open(BytesIO(image)) 105 | 106 | if image.mode not in {'RGBA', 'LA', 'P'} or "transparent" in image.info or "transparency" in image.info: 107 | image = image.convert("RGBA") 108 | white_bg = Image.new("RGB", image.size, (255, 255, 255)) 109 | white_bg.paste(image, mask=image.split()[3]) 110 | ratio, dominant_color = is_mostly_one_color(white_bg) 111 | if ratio >= 0.5 and sum(dominant_color) > limit: 112 | r, g, b, a = image.split() 113 | rgb_image = Image.merge('RGB', (r, g, b)) 114 | ratio, dominant_color = is_mostly_one_color(rgb_image) 115 | inverted_rgb = ImageOps.invert(rgb_image) 116 | r2, g2, b2 = inverted_rgb.split() 117 | image = Image.merge('RGBA', (r2, g2, b2, a)) 118 | return image 119 | else: 120 | image = image.convert("RGB") 121 | ratio, dominant_color = is_mostly_one_color(image) 122 | if ratio >= 0.5 and sum(dominant_color) > limit: 123 | image = ImageOps.invert(image) 124 | return image 125 | 126 | 127 | def svg_to_png(data): 128 | load_cairo() 129 | if rsvg_type == 1 or rsvg_type == 3: 130 | from io import BytesIO 131 | if rsvg_type == 1: 132 | svg = rsvg.Handle(data=data) 133 | img = cairo.ImageSurface( 134 | cairo.FORMAT_ARGB32, svg.props.width, svg.props.height) 135 | else: 136 | handle = rsvg.Handle() 137 | svg = handle.new_from_data(data.encode("utf-8")) 138 | dim = svg.get_dimensions() 139 | img = cairo.ImageSurface( 140 | cairo.FORMAT_ARGB32, dim.width, dim.height) 141 | ctx = cairo.Context(img) 142 | svg.render_cairo(ctx) 143 | png_io = BytesIO() 144 | img.write_to_png(png_io) 145 | svg.close() 146 | return png_io.getvalue() 147 | else: 148 | return cairo.svg2png(bytestring=data) 149 | 150 | 151 | def data_to_image(data, name, imagetype, data_is_image): 152 | if data_is_image: 153 | from PIL import Image 154 | from PIL.ImageTk import PhotoImage 155 | 156 | if PhotoImage.__del__ is not photoimage_del: 157 | PhotoImage.__del__ = photoimage_del 158 | 159 | return PhotoImage(image=data, name=name) 160 | elif imagetype in ("image/png", "image/gif", "image/ppm", "image/pgm",): 161 | # tkinter.PhotoImage has less overhead, so use it when possible 162 | return TkPhotoImage(data=data, name=name) 163 | else: 164 | from PIL import Image 165 | from PIL.ImageTk import PhotoImage 166 | 167 | if PhotoImage.__del__ is not photoimage_del: 168 | PhotoImage.__del__ = photoimage_del 169 | 170 | return PhotoImage(data=data, name=name) 171 | 172 | 173 | def blank_image(name): 174 | return TkPhotoImage(name=name) 175 | 176 | 177 | def create_RGB_image(data, w, h): 178 | from PIL import Image 179 | 180 | image = Image.new("RGB", (w, h)) 181 | for y, row in enumerate(data): 182 | for x, hexc in enumerate(row.split()): 183 | rgb = tuple(int(hexc[1:][i:i+2], 16) for i in (0, 2, 4)) 184 | image.putpixel((x, y), rgb) 185 | return image 186 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![PyPi Downloads](https://static.pepy.tech/badge/tkinterweb/month) 2 | ![MIT Licence](https://img.shields.io/pypi/l/tkinterweb) 3 | ![Python 3](https://img.shields.io/pypi/pyversions/tkinterweb) 4 | ![Made in Canada](https://img.shields.io/badge/%F0%9F%87%A8%F0%9F%87%A6%20made%20in%20Canada-grey) 5 | 6 |
Buy Me A Coffee
7 | 8 |

9 | 10 | **

Fast and lightweight web browser, rich text, and app design widgets for Tkinter.

** 11 | 12 | ## Overview 13 | **TkinterWeb offers bindings and extensions to a modified version of the Tkhtml3 widget from [http://tkhtml.tcl.tk](https://web.archive.org/web/20250219233338/http://tkhtml.tcl.tk/), which enables enables the display of HTML and CSS code in Tkinter applications.** 14 | 15 | Some of TkinterWeb's uses include: 16 | * Displaying websites, feeds, help files, and other styled HTML 17 | * Displaying images, including SVG images 18 | * Creating a rich text or HTML editor 19 | * Designing apps using HTML templates 20 | * Creating prettier apps, with rounded buttons and more! 21 | 22 | All [major operating systems](https://tkinterweb.readthedocs.io/en/latest/compatibility.html#a-note-on-tkhtml-binaries) running Python 3.2+ are supported. 23 | 24 | ## Usage 25 | 26 | **TkinterWeb provides:** 27 | * A [frame](https://tkinterweb.readthedocs.io/en/latest/api/htmlframe.html) widget to display and edit websites, help files, RSS feeds, and any other styled HTML in Tkinter. 28 | * A [label](https://tkinterweb.readthedocs.io/en/latest/api/htmlframe.html#tkinterweb.HtmlLabel) widget that can display styled HTML. 29 | * A [geometry manager](https://tkinterweb.readthedocs.io/en/latest/geometry.html) to display Tkinter widgets *and HTML elements* together in a Tkinter application. 30 | 31 | **TkinterWeb can be used in any Tkinter application to display and edit websites, help pages, documentation, and much more! Here is an example:** 32 | ``` 33 | import tkinter as tk 34 | from tkinterweb import HtmlFrame # import the HtmlFrame widget 35 | 36 | root = tk.Tk() # create the Tkinter window 37 | frame = HtmlFrame(root) # create the HtmlFrame widget 38 | frame.load_website("https://tkinterweb.readthedocs.io/en/latest/index.html") # load a website 39 | frame.pack(fill="both", expand=True) # attach the HtmlFrame widget to the window 40 | root.mainloop() 41 | ``` 42 | ![TkinterWeb](/images/tkinterweb-demo.png) 43 | 44 | See [Getting Started](https://tkinterweb.readthedocs.io/en/latest/usage.html) for more tips and tricks. 45 | 46 | ## Installation 47 | To install TkinterWeb, simply type `pip install tkinterweb[recommended]` in the command prompt or terminal. That's it! 48 | 49 | Or, you can also choose from the following extras: `pip install tkinterweb[html,images,svg,javascript,requests]`. You can also use `pip install tkinterweb[full]` to install all optional dependencies or ``pip install tkinterweb`` to install the bare minimum. 50 | 51 | ## Dependencies 52 | **In order to load webpages and show images, TkinterWeb requires the following packages:** 53 | * Tkinter 54 | * TkinterWeb-Tkhtml 55 | 56 | I also **strongly** recommended installing the following: 57 | * TkinterWeb-Tkhtml-Extras 58 | * PIL 59 | * PIL.ImageTk 60 | 61 | You can also choose from the following list for extra functionality: 62 | * Brotli (for faster page loads on some sites) 63 | * PythonMonkey (for basic JavaScript support) 64 | * CairoSVG or PyGObject (for SVG support) 65 | 66 | Pip will automatically install dependencies when installing TkinterWeb. PIL.ImageTk should be automatically installed with PIL but might need to installed separately on some systems. 67 | 68 | ## API Documentation 69 | 70 | > [!WARNING] 71 | > The API changed significantly in version 4.0.0. See [the changelog](https://tkinterweb.readthedocs.io/en/latest/upgrading.html) for details. 72 | 73 | **Documentation and additional information on built-in classes can be found in the corresponding API reference pages:** 74 | * [`tkinterweb.Demo`](https://tkinterweb.readthedocs.io/en/latest/usage.html#installation) 75 | * [`tkinterweb.HtmlFrame`](https://tkinterweb.readthedocs.io/en/latest/api/htmlframe.html) 76 | * [`tkinterweb.HtmlLabel`](https://tkinterweb.readthedocs.io/en/latest/api/htmlframe.html#tkinterweb.HtmlLabel) 77 | * [`tkinterweb.HtmlParse`](https://tkinterweb.readthedocs.io/en/latest/api/htmlframe.html#tkinterweb.HtmlParse) 78 | * [`tkinterweb.TkinterWeb`](https://tkinterweb.readthedocs.io/en/latest/api/tkinterweb.html) 79 | * [`tkinterweb.Notebook`](https://tkinterweb.readthedocs.io/en/latest/api/notebook.html) (a Tkhtml-compatible drop-in replacement for `ttk.Notebook`) 80 | 81 | ## FAQs 82 | See [Frequently Asked Questions](https://tkinterweb.readthedocs.io/en/latest/faq.html). 83 | 84 | ## Webpage Compatability 85 | **HTML/CSS:** 86 | * TkinterWeb supports HTML 4.01 and CSS 2.1. A full list of supported CSS declarations can be found at [http://tkhtml.tcl.tk/support.html](https://web.archive.org/web/20250325123206/http://tkhtml.tcl.tk/support.html). 87 | * Most CSS pseudo-elements, such as `:hover` and `:active` are also supported. 88 | * On 64-bit Windows and Linux, if the TkinterWeb-Tkhtml-Extras package is installed, ``border-radius`` and more cursor options are also supported. 89 | 90 | **JavaScript:** 91 | * Javascript only partly supported at the moment. 92 | * To use JavaScript, PythonMonkey must be installed. 93 | * It is also possible for the user to connect their own JavaScript interpreter or manipulate the document through Python. 94 | * See [Using JavaScript](https://tkinterweb.readthedocs.io/en/latest/javascript.html) for more information and [DOM Manipulation with TkinterWeb](https://tkinterweb.readthedocs.io/en/latest/dom.html) for information on manipulating the document through Python. 95 | 96 | **Images:** 97 | * TkinterWeb supports nearly 50 different image types through PIL. 98 | * In order to load Scalable Vector Graphic images, CairoSVG, both PyCairo and PyGObject, or both PyCairo and Rsvg must also be installed. 99 | 100 | ## Contributing 101 | **The best ways to contribute to this project are by submitting a [bug report](https://github.com/Andereoo/TkinterWeb/issues/new) to report bugs or suggest new features, or by submitting a [pull request](https://github.com/Andereoo/TkinterWeb/pulls) to offer fixes. Your help makes TkinterWeb become more stable and full-featured!** 102 | 103 | ☕ If you’d like to support ongoing development and maintenance, please consider supporting this project by [buying me a coffee](https://buymeacoffee.com/andereoo) — any amount is hugely appreciated! 104 | 105 | Please check the [FAQs](https://tkinterweb.readthedocs.io/en/latest/faq.html) and [closed bugs](https://github.com/Andereoo/TkinterWeb/issues?q=is%3Aissue) before submitting a bug report to see if your question as already been answered. 106 | 107 | ## Credits 108 | **TkinterWeb is powered by the [Tkhtml project](https://web.archive.org/web/20250219233338/http://tkhtml.tcl.tk/).** 109 | 110 | Special thanks to [Christopher Chavez](https://github.com/chrstphrchvz), [Zamy846692](https://github.com/Zamy846692), [Jośe Fernando Moyano](https://github.com/jofemodo), [Bumshakalaka](https://github.com/Bumshakalaka), [Trov5](https://github.com/TRVRStash), [Mark Mayo](https://github.com/marksmayo), [Jaedson Silva](https://github.com/jaedsonpys), [Nick Moore](https://github.com/nickzoic), [Leonardo Saurwein](https://github.com/Sau1707), and [Hbregalad](https://github.com/hbregalad) for their code suggestions and pull requests. 111 | 112 | Special thanks to [Christopher Chavez](https://github.com/chrstphrchvz), Jan Nijtmans, and everyone else in the tcl-core mailing list for the help making border rounding work on Windows and MacOSX, and to [Zamy846692](https://github.com/Zamy846692) for spearheading experimental Tkhtml development. 113 | 114 | Thanks to the [TkinterHtml package](https://bitbucket.org/aivarannamaa/tkinterhtml) for providing the bindings on which this project is based, the [BRL-CAD project](https://github.com/BRL-CAD/brlcad) for providing modifications for Tkhtml on 64-bit Windows, and [Google Fonts](https://github.com/google/fonts) for providing the font used for generating alternative text when images fail to load. 115 | 116 | A huge thanks to everyone else who supported this project by reporting bugs and providing suggestions! 117 | -------------------------------------------------------------------------------- /docs/source/usage.rst: -------------------------------------------------------------------------------- 1 | Getting Started 2 | =============== 3 | 4 | .. note:: 5 | The API changed significantly in version 4. See :doc:`the changelog ` for details. 6 | 7 | Installation 8 | ------------ 9 | 10 | To use TkinterWeb, first install it using pip: 11 | 12 | .. code-block:: console 13 | 14 | $ pip install tkinterweb[recommended] 15 | 16 | .. tip:: 17 | 18 | You can also choose from the following extras: 19 | 20 | .. code-block:: console 21 | 22 | $ pip install tkinterweb[html,images,svg,javascript,requests] 23 | 24 | Run ``pip install tkinterweb[full]`` to install all optional dependencies or ``pip install tkinterweb`` to install the bare minimum. 25 | 26 | Run the TkinterWeb demo to see if it worked! 27 | 28 | >>> from tkinterweb import Demo 29 | >>> Demo() 30 | 31 | .. image:: ../../images/tkinterweb-demo.png 32 | 33 | TkinterWeb requires :py:mod:`Tkinter`, :py:mod:`TkinterWeb-Tkhtml`, :py:mod:`PIL`, and :py:class:`PIL.ImageTk`. All dependencies should be installed when installing TkinterWeb, but on some systems :py:class:`PIL.ImageTk` may need to be installed seperately in order to load most image types. 34 | 35 | Getting started 36 | ---------------- 37 | 38 | TkinterWeb is very easy to use! Here is an example: 39 | 40 | .. code-block:: python 41 | 42 | import tkinter as tk 43 | from tkinterweb import HtmlFrame # import the HtmlFrame widget 44 | 45 | root = tk.Tk() # create the Tkinter window 46 | 47 | yourhtmlframe = HtmlFrame(root) # create the HtmlFrame widget 48 | yourhtmlframe.load_html("

Hello, World!

") # load some HTML code 49 | yourhtmlframe.pack(fill="both", expand=True) # attach the HtmlFrame widget to the window 50 | 51 | root.mainloop() 52 | 53 | .. tip:: 54 | To load a website, call ``yourhtmlframe.load_website("www.yourwebsite.com")``. 55 | 56 | To load a file, call ``yourhtmlframe.load_file("/path/to/your/file.html")``. 57 | 58 | To load any generic url, call ``yourhtmlframe.load_url(yourwebsiteorfile)``. Keep in mind that the url must be properly formatted and include the url scheme. 59 | 60 | The :class:`~tkinterweb.HtmlFrame` widget behaves like any other Tkinter widget and supports bindings. It also supports link clicks, form submittions, website title changes, and much, much more! Refer below for more tips and tricks! 61 | 62 | Tips and tricks 63 | --------------- 64 | 65 | Creating bindings 66 | ~~~~~~~~~~~~~~~~~ 67 | 68 | Like any other Tkinter widget, mouse and keyboard events can be bound to the :class:`~tkinterweb.HtmlFrame` widget. 69 | 70 | The following is an example of the usage of bingings to show a menu: 71 | 72 | .. code-block:: python 73 | 74 | def on_right_click(event): 75 | # Get the element under the mouse and its url 76 | element = yourhtmlframe.get_currently_hovered_element() 77 | url = element.getAttribute("href") 78 | 79 | if url: 80 | # Resolve the url to ensure it is a full url 81 | url = yourhtmlframe.resolve_url(url) 82 | 83 | # Create the menu and add a button with the url 84 | menu = tk.Menu(root, tearoff=0) 85 | menu.add_command(label="Open %s" % url, 86 | command=lambda url=url: yourhtmlframe.load_url(url)) 87 | 88 | # Show the menu 89 | menu.tk_popup(event.x_root, event.y_root, 0) 90 | 91 | yourhtmlframe.bind("", on_right_click) 92 | 93 | This will make a popup open when the user right-clicks on a link. Clicking the link shown in the popup would load the website. 94 | 95 | Note that some keypress events are automatically bound to the widget. If you notice a feature unintentionally stops working after adding a binding, consider using ``bind(event, callback, add="+")`` to add your binding instead of replacing the default one. 96 | 97 | .. tip:: 98 | Since version 4.10, you can also bind to a specific HTML element! See :ref:`binding-to-an-element` for more details. 99 | 100 | Changing the title 101 | ~~~~~~~~~~~~~~~~~~ 102 | 103 | To change the title of the window every time the title of a website changes, use the following: 104 | 105 | .. code-block:: python 106 | 107 | def change_title(event): 108 | root.title(yourhtmlframe.title) # change the title 109 | 110 | yourhtmlframe.bind("<>", change_title) 111 | 112 | Similarily, the ``<>`` event fires when the website's icon changes. 113 | 114 | Handling url changes 115 | ~~~~~~~~~~~~~~~~~~~~ 116 | 117 | Normally, a website's url may change when it is loaded. For example, "https://github.com" will redirect to "https://www.github.com". This can be handled with a binding to ``<>``: 118 | 119 | .. code-block:: python 120 | 121 | def url_changed(event): 122 | updated_url = yourhtmlframe.current_url 123 | ### Do stuff, such as change the content of an address bar 124 | 125 | yourhtmlframe.bind("<>", url_changed) 126 | 127 | This is highly recomended if your app includes an address bar. This event will fire on page redirects and url changes when a page stops loading. 128 | 129 | 130 | Searching the page 131 | ~~~~~~~~~~~~~~~~~~ 132 | 133 | Use :meth:`~tkinterweb.HtmlFrame.find_text` to search the page for specific text. To search the document for the word 'python', for example, the following can be used: 134 | 135 | .. code-block:: python 136 | 137 | number_of_matches = yourhtmlframe.find_text("python") 138 | 139 | Or, to select the second match found: 140 | 141 | .. code-block:: python 142 | 143 | number_of_matches = yourhtmlframe.find_text("python", 2) 144 | 145 | Refer to the API reference for more information. 146 | 147 | .. tip:: 148 | 149 | Check out `bug 18 `_ or the `sample web browser `_ for a sample find bar! 150 | 151 | Done loading? 152 | ~~~~~~~~~~~~~ 153 | 154 | The ``<>`` event fires when the document is done loading. 155 | 156 | When binding to ``<>`` to, for example, change a 'stop' button to a 'refresh' button, it is generally a good idea to bind to ``<>`` to do the opposite. Otherwise, the document may show that is is done loading while it is still loading. 157 | 158 | Stop loading 159 | ~~~~~~~~~~~~ 160 | 161 | The method :meth:`~tkinterweb.HtmlFrame.stop` can be used to stop loading a webpage. If :meth:`~tkinterweb.HtmlFrame.load_url`, :meth:`~tkinterweb.HtmlFrame.load_website`, or :meth:`~tkinterweb.HtmlFrame.load_file` was used to load the document, passing ``yourhtmlframe.current_url`` with ``force=True`` will force a page refresh. 162 | 163 | Handling link clicks 164 | ~~~~~~~~~~~~~~~~~~~~ 165 | 166 | Link clicks can also be easily handled. By default, when a link is clicked, it will be automatically loaded. 167 | To, for example, run some code before loading the new website, use the following: 168 | 169 | .. code-block:: python 170 | 171 | yourhtmlframe = HtmlFrame(master, on_link_click=load_new_page) 172 | 173 | def load_new_page(url): 174 | ### Do stuff 175 | yourhtmlframe.load_url(url) # load the new website 176 | 177 | Similarily, :attr:`on_form_submit` can be used to override the default form submission handlers. 178 | 179 | Zooming 180 | ~~~~~~~ 181 | 182 | Setting the zoom of the :class:`~tkinterweb.HtmlFrame` widget is very easy. This can be used to improve accessibility in your application. To set the zoom to 2x magnification the following can be used: 183 | 184 | .. code-block:: python 185 | 186 | yourhtmlframe = HtmlFrame(master, zoom=2) 187 | ### Or yourhtmlframe.configure(zoom=2) 188 | ### Or yourhtmlframe["zoom"] = 2 189 | 190 | To scale only the text, use ``fontscale=2`` instead. 191 | 192 | Embedding a widget 193 | ~~~~~~~~~~~~~~~~~~ 194 | 195 | There are many ways to embed widgets in an :class:`~tkinterweb.HtmlFrame` widget. One way is to use ```` elements: 196 | 197 | .. code-block:: python 198 | 199 | yourcanvas = tkinter.Canvas(yourhtmlframe) 200 | yourhtmlframe.load_html(f"

This is a canvas!

") 201 | 202 | Refer to :doc:`geometry` for more information. 203 | 204 | Manipulating the DOM 205 | ~~~~~~~~~~~~~~~~~~~~ 206 | 207 | Refer to :doc:`dom` (new in version 3.25). 208 | 209 | Using JavaScript 210 | ~~~~~~~~~~~~~~~~ 211 | 212 | Refer to :doc:`javascript` (new in version 4.1). 213 | 214 | Making the page editable 215 | ~~~~~~~~~~~~~~~~~~~~~~~~ 216 | 217 | Refer to :doc:`caret` (new in version 4.8). 218 | 219 | Using dark mode 220 | ~~~~~~~~~~~~~~~ 221 | 222 | You can set ``dark_theme_enabled=True`` when creating your :class:`~tkinterweb.HtmlFrame` or calling :meth:`~tkinterweb.HtmlFrame.configure` to turn on dark mode and automatically modify page colours. 223 | 224 | If you set ``image_inversion_enabled=True``, an algorithm will attempt to detect and invert images with a predominantly light-coloured background. This helps make light-coloured images or pictures with a white background darker. 225 | 226 | Refresh the page for these features to take full effect. This features may cause hangs or crashes on more complex websites. 227 | 228 | ------------------- 229 | 230 | See the :doc:`api/htmlframe` for a complete list of available commands. 231 | -------------------------------------------------------------------------------- /docs/source/upgrading.rst: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | **The API changed significantly in version 4.0.0** 5 | 6 | Key changes 7 | ----------- 8 | 9 | * Faster load speed 10 | * A more intuitive API 11 | * Support for experimental Tkhtml features, such as page printing 12 | * Widget behaviour and API is now more closely aligned with standard Tkinter widgets 13 | * Many DOM improvements. The DOM API now more closely mirrors its JavaScript counterpart. 14 | * Dozens of new configuration options, including access to more settings and the ability to link a JavaScript interpreter 15 | 16 | * Basic JavaScript support (new in version 4.1) 17 | * Improved embedded widget handling (new in version 4.2) 18 | * SVG support on Windows and ``border-radius`` support on Windows and Linux (new in version 4.4) 19 | * Support for Tcl 9 (new in version 4.5) 20 | * Caret browsing functionality (new in version 4.8) 21 | * Improved thread safety (new in version 4.9) 22 | * Ability to bind to HTML elements (new in version 4.10) 23 | 24 | Removed 25 | ------- 26 | 27 | Version 4.0: 28 | 29 | * ``HtmlFrame.get_zoom()`` - use ``HtmlFrame.cget("zoom")`` 30 | * ``HtmlFrame.set_zoom()`` - use ``HtmlFrame.configure(zoom=)`` 31 | * ``HtmlFrame.get_fontscale()`` - use ``HtmlFrame.cget("fontscale")`` 32 | * ``HtmlFrame.set_fontscale()`` - use ``HtmlFrame.configure(fontscale=)`` 33 | * ``HtmlFrame.get_parsemode()`` - use ``HtmlFrame.cget("parsemode")`` 34 | * ``HtmlFrame.set_parsemode()`` - use ``HtmlFrame.configure(parsemode=)`` 35 | * ``HtmlFrame.set_message_func()`` - use ``HtmlFrame.configure(message_func=)`` 36 | * ``HtmlFrame.set_broken_webpage_message()`` - use ``HtmlFrame.configure(on_navigate_fail=)``. Note that :attr:`on_navigate_fail` requires a function instead. 37 | * ``HtmlFrame.set_maximum_thread_count()`` - use ``HtmlFrame.configure(threading_enabled=)`` 38 | * ``HtmlFrame.set_recursive_hover_depth()`` - use ``HtmlFrame.html.recursive_hover_depth=`` 39 | * ``HtmlFrame.add_visited_links()`` - use ``HtmlFrame.configure(visited_links=)`` 40 | * ``HtmlFrame.clear_visited_links()`` - use ``HtmlFrame.configure(visited_links=)`` 41 | * ``HtmlFrame.enable_stylesheets()`` - use ``HtmlFrame.configure(stylesheets_enabled=)`` 42 | * ``HtmlFrame.enable_images()`` - use ``HtmlFrame.configure(images_enabled=)`` 43 | * ``HtmlFrame.enable_forms()`` - use ``HtmlFrame.configure(forms_enabled=)`` 44 | * ``HtmlFrame.enable_objects()`` - use ``HtmlFrame.configure(objects_enabled=)`` 45 | * ``HtmlFrame.enable_caches()`` - use ``HtmlFrame.configure(caches_enabled=)`` 46 | * ``HtmlFrame.enable_dark_theme()`` - use ``HtmlFrame.configure(dark_theme_enabled=, image_inversion_enabled=)`` 47 | * ``HtmlFrame.on_image_setup()`` - use ``HtmlFrame.configure(on_resource_setup=)`` 48 | * ``HtmlFrame.on_downloading_resource()`` - bind to ``<>`` 49 | * ``HtmlFrame.on_done_loading()`` - bind to ``<>`` 50 | * ``HtmlFrame.on_url_change()`` - bind to ``<>`` and use :attr:`.HtmlFrame.current_url` 51 | * ``HtmlFrame.on_icon_change()`` - bind to ``<>`` and use :attr:`.HtmlFrame.title` 52 | * ``HtmlFrame.on_title_change()`` - bind to ``<>`` and use :attr:`.HtmlFrame.title` 53 | * ``HtmlFrame.on_form_submit()`` - use ``HtmlFrame.configure(on_form_submit=)`` 54 | * ``HtmlFrame.on_link_click()`` - use ``HtmlFrame.configure(on_link_click=)`` 55 | * ``HtmlFrame.yview_toelement()`` - use :meth:`.HTMLElement.scrollIntoView` 56 | * ``HtmlFrame.get_currently_hovered_node_text()`` - :meth:`.HtmlFrame.get_currently_hovered_element` 57 | * ``HtmlFrame.get_currently_hovered_node_tag()`` - :meth:`.HtmlFrame.get_currently_hovered_element` 58 | * ``HtmlFrame.get_currently_hovered_node_attribute()`` - :meth:`.HtmlFrame.get_currently_hovered_element` 59 | * ``HtmlFrame.get_current_link()`` - use :meth:`.HtmlFrame.get_currently_hovered_element` 60 | 61 | * The ``widgetid`` attribute no longer embeds widgets. Use ```` or :attr:`.HTMLElement.widget` instead. This improves load speeds and allows for widget style handling. 62 | 63 | Version 4.2: 64 | 65 | * ``TkinterWeb.replace_widget()`` 66 | * ``TkinterWeb.replace_element()`` 67 | * ``TkinterWeb.remove_widget()`` 68 | 69 | Version 4.8 70 | 71 | * ``HtmlFrame.replace_widget()`` (deprecated in version 4.0) - use :meth:`.HtmlFrame.widget_to_element` and :attr:`.HTMLElement.widget` 72 | * ``HtmlFrame.replace_element()`` (deprecated in version 4.0) - use :attr:`.HTMLElement.widget` 73 | * ``HtmlFrame.remove_widget()`` (deprecated in version 4.0) - use :meth:`.HTMLElement.remove` 74 | 75 | Deprecated 76 | ---------- 77 | 78 | Version 4.11: 79 | 80 | * ``TkinterWeb.update_tags()`` - use :meth:`.SelectionManager.update_tags` 81 | * ``TkinterWeb.select_all()`` - use :meth:`.SelectionManager.select_all` 82 | * ``TkinterWeb.clear_selection()`` - use :meth:`.SelectionManager.clear_selection` 83 | * ``TkinterWeb.update_selection()`` - use :meth:`.SelectionManager.update_selection` 84 | * ``TkinterWeb.get_selection()`` - use :meth:`.SelectionManager.get_selection` 85 | * ``TkinterWeb.copy_selection()`` - use :meth:`.SelectionManager.copy_selection` 86 | * ``TkinterWeb.allocate_image_name()`` - use :meth:`.ImageManager.allocate_image_name` 87 | * ``TkinterWeb.handle_node_replacement()`` - use :meth:`.WidgetManager.handle_node_replacement` 88 | * ``TkinterWeb.map_node()`` - use :meth:`.WidgetManager.map_node` 89 | * ``TkinterWeb.find_text()`` - use :meth:`.SearchManager.find_text` 90 | * ``TkinterWeb.send_onload()`` - use :meth:`.EventManager.send_onload` 91 | 92 | Renamed 93 | ------- 94 | 95 | Version 4.0: 96 | 97 | * ``HtmlFrame.get_currently_selected_text()`` -> :meth:`.HtmlFrame.get_selection` 98 | 99 | * ``TkwDocumentObjectModel`` -> :class:`.HTMLDocument` 100 | * ``HtmlElement`` -> :class:`.HTMLElement` 101 | 102 | * ``HtmlElement.style()`` -> :attr:`.HTMLElement.style` 103 | * ``HtmlElement.innerHTML()`` -> :attr:`.HTMLElement.innerHTML` 104 | * ``HtmlElement.textContent()`` -> :attr:`.HTMLElement.textContent` 105 | * ``HtmlElement.attributes()`` -> :attr:`.HTMLElement.attributes` 106 | * ``HtmlElement.tagName()`` -> :attr:`.HTMLElement.tagName` 107 | * ``HtmlElement.parentElement()`` -> :attr:`.HTMLElement.parentElement` 108 | * ``HtmlElement.children()`` -> :attr:`.HTMLElement.children` 109 | 110 | * The ``scroll-x`` attribute was changed to the ``tkinterweb-scroll-x`` attribute. Like the ``overflow`` CSS property, valid options are now "auto", "visible", "clip", "scroll", and "hidden". 111 | 112 | Added 113 | ----- 114 | 115 | Version 4.0: 116 | 117 | * :meth:`.HtmlFrame.clear_selection` 118 | * :meth:`.HtmlFrame.get_currently_hovered_element` 119 | * :meth:`.HtmlFrame.save_page` 120 | * :meth:`.HtmlFrame.snapshot_page` 121 | * :meth:`.HtmlFrame.show_error_page` 122 | * :meth:`.HtmlFrame.print_page` 123 | * :meth:`.HtmlFrame.screenshot_page` 124 | 125 | * :attr:`.HtmlFrame.base_url` 126 | * :attr:`.HtmlFrame.icon` 127 | * :attr:`.HtmlFrame.title` 128 | 129 | * :meth:`.HTMLElement.getElementById` 130 | * :meth:`.HTMLElement.getElementsByClassName` 131 | * :meth:`.HTMLElement.getElementsByName` 132 | * :meth:`.HTMLElement.getElementsByTagName` 133 | * :meth:`.HTMLElement.querySelector` 134 | * :meth:`.HTMLElement.querySelectorAll` 135 | * :meth:`.HTMLElement.scrollIntoView` 136 | 137 | * :class:`.CSSStyleDeclaration` 138 | * :attr:`.CSSStyleDeclaration.*` (any camel-case CSS property) 139 | * :attr:`.CSSStyleDeclaration.cssText` 140 | * :attr:`.CSSStyleDeclaration.length` 141 | * :attr:`.CSSStyleDeclaration.cssProperties` 142 | * :attr:`.CSSStyleDeclaration.cssInlineProperties` 143 | 144 | * :meth:`.TkinterWeb.enable_imagecache` 145 | * :meth:`.TkinterWeb.destroy_node` 146 | * :meth:`.TkinterWeb.get_node_properties` 147 | * :meth:`.TkinterWeb.override_node_properties` 148 | * :meth:`.TkinterWeb.update_tags` 149 | 150 | * ``utilities.DOWNLOADING_RESOURCE_EVENT`` (equivalent to ``<>``) 151 | * ``utilities.DONE_LOADING_EVENT`` (equivalent to ``<>``) 152 | * ``utilities.URL_CHANGED_EVENT`` (equivalent to ``<>``) 153 | * ``utilities.ICON_CHANGED_EVENT`` (equivalent to ``<>``) 154 | * ``utilities.TITLE_CHANGED_EVENT`` (equivalent to ``<>``) 155 | 156 | * Many new configuration options were added. See the :doc:`api/htmlframe` for a complete list. 157 | 158 | * The ``tkinterweb-full-page`` attribute can now be added to elements to make them the same height as the viewport. This can be used for vertical alignment of page content. See the TkinterWeb Demo class in `__init__.py `_ for example usage. 159 | 160 | Version 4.1: 161 | 162 | * :meth:`.HtmlFrame.register_JS_object`` 163 | 164 | * :attr:`.HTMLElement.widget` (updated again in version 4.2) 165 | * :attr:`.HTMLElement.value` 166 | * :attr:`.HTMLElement.checked` 167 | * :attr:`.HTMLElement.onchange` 168 | * :attr:`.HTMLElement.onload` 169 | * :attr:`.HTMLElement.onclick` 170 | * :attr:`.HTMLElement.oncontextmenu` 171 | * :attr:`.HTMLElement.ondblclick` 172 | * :attr:`.HTMLElement.onmousedown` 173 | * :attr:`.HTMLElement.onmouseenter` 174 | * :attr:`.HTMLElement.onmouseleave` 175 | * :attr:`.HTMLElement.onmousemove` 176 | * :attr:`.HTMLElement.onmouseout` 177 | * :attr:`.HTMLElement.onmouseover` 178 | * :attr:`.HTMLElement.onmouseup` 179 | 180 | * :attr:`.CSSStyleDeclaration.setProperty` 181 | * :attr:`.CSSStyleDeclaration.getPropertyValue` 182 | * :attr:`.CSSStyleDeclaration.removeProperty` 183 | 184 | * :meth:`.TkinterWeb.send_onload` 185 | 186 | * Added support for many JavaScript events. 187 | 188 | * The new configuration option ``on_element_script`` can be used to add a callback to run when a JavaScript event attribute on an element is encountered. 189 | * The new configuration option ``javascript_enabled`` can be used to enable JavaScript support. 190 | 191 | Version 4.2: 192 | 193 | * :meth:`.HtmlFrame.widget_to_element` 194 | 195 | * :meth:`.TkinterWeb.replace_node_contents` 196 | * :meth:`.TkinterWeb.map_node` 197 | * :meth:`.TkinterWeb.replace_node_with_widget` 198 | * :meth:`.TkinterWeb.get_node_stacking` 199 | 200 | Version 4.4: 201 | 202 | * :class:`.HtmlParse` 203 | * :class:`.TkHtmlParsedURI` 204 | * :class:`.HTMLCollection` 205 | 206 | * :meth:`.HtmlFrame.insert_html` 207 | 208 | * :attr:`.HTMLElement.id` 209 | * :attr:`.HTMLElement.className` 210 | 211 | * :meth:`.TkinterWeb.override_node_CSS` 212 | * :meth:`.TkinterWeb.write` 213 | * :meth:`.TkinterWeb.get_child_text` 214 | * :meth:`.TkinterWeb.safe_tk_eval` 215 | * :meth:`.TkinterWeb.serialize_node` 216 | * :meth:`.TkinterWeb.serialize_node_style` 217 | 218 | * Added support for the HTML number input. 219 | 220 | * The new configuration option ``tkhtml_version`` can be used to choose a specific Tkhtml version to load. 221 | 222 | Version 4.5: 223 | 224 | * The new configuration option ``ssl_cafile`` can be used to provide a path to a CA Certificate file. See `bug #28 `_. 225 | 226 | Version 4.6: 227 | 228 | * The new configuration option ``request_timeout`` can be used to specify the number of seconds to wait before a request times out. 229 | 230 | Version 4.7: 231 | 232 | * The new ``<>`` event will be generated once the page DOM content has loaded. The page may not be done loading, but at this point it is possible to interact with the DOM. 233 | 234 | Version 4.8: 235 | 236 | * :meth:`.HtmlFrame.get_page_text` 237 | * :meth:`.HtmlFrame.get_caret_position` 238 | * :meth:`.HtmlFrame.get_caret_page_position` 239 | * :meth:`.HtmlFrame.set_caret_position` 240 | * :meth:`.HtmlFrame.set_caret_page_position` 241 | * :meth:`.HtmlFrame.shift_caret_left` 242 | * :meth:`.HtmlFrame.shift_caret_right` 243 | * :meth:`.HtmlFrame.get_selection_position` 244 | * :meth:`.HtmlFrame.get_selection_page_position` 245 | * :meth:`.HtmlFrame.set_selection_position` 246 | * :meth:`.HtmlFrame.set_selection_page_position` 247 | 248 | * :attr:`.HTMLElement.previousSibling` 249 | * :attr:`.HTMLElement.nextSibling` 250 | 251 | * :attr:`.TkinterWeb.caret_manager` 252 | 253 | * :meth:`.TkinterWeb.update_selection` 254 | * :meth:`.TkinterWeb.tkhtml_offset_to_text_index` 255 | 256 | * :class:`.CaretManager` 257 | 258 | * The new configuration option ``caret_browsing_enabled`` can be used to enable or disable caret browsing mode. 259 | 260 | Version 4.9: 261 | 262 | * :meth:`.TkinterWeb.post_to_queue` 263 | * :meth:`.TkinterWeb.allocate_image_name` 264 | * :meth:`.TkinterWeb.check_images` 265 | 266 | Version 4.10: 267 | 268 | * :meth:`.HTMLElement.bind` 269 | * :meth:`.HTMLElement.unbind` 270 | 271 | * :attr:`.TkinterWeb.event_manager` 272 | 273 | * :class:`.EventManager` 274 | 275 | * You can now set ``allowstyling="deep"`` on elements with embedded widgets to also style their subwidgets. 276 | 277 | Version 4.11: 278 | 279 | * :meth:`.HtmlFrame.unbind` 280 | 281 | * :class:`.HtmlText` 282 | 283 | * :attr:`.TkinterWeb.selection_manager` 284 | * :attr:`.TkinterWeb.widget_manager` 285 | * :attr:`.TkinterWeb.search_manager` 286 | * :attr:`.TkinterWeb.script_manager` 287 | * :attr:`.TkinterWeb.style_manager` 288 | * :attr:`.TkinterWeb.image_manager` 289 | * :attr:`.TkinterWeb.object_manager` 290 | * :attr:`.TkinterWeb.form_manager` 291 | * :attr:`.TkinterWeb.node_manager` 292 | 293 | * :class:`.SelectionManager` 294 | * :class:`.WidgetManager` 295 | 296 | Changed/Fixed 297 | ------------- 298 | 299 | Version 4.0: 300 | 301 | * :meth:`.HtmlFrame.configure`, :meth:`.HtmlFrame.config`, :meth:`.HtmlFrame.cget`, and :meth:`.HtmlFrame.__init__` now support more configuration options. 302 | * :meth:`.HtmlFrame.load_website`, :meth:`.HtmlFrame.load_file`, and :meth:`.HtmlFrame.load_url` no longer accept the ``insecure`` parameter. use ``HTMLElement.configure(insecure=)``. 303 | 304 | * Enabling/disabling caches now enables/disables the Tkhtml image cache. 305 | * Threading now cannot be enabled if the Tcl/Tk build does not support it. 306 | 307 | * :meth:`.HTMLElement.remove` now raises a :py:class:`tkinter.TclError` when invoked on ```` or ```` elements, which previously caused segmentation faults. 308 | * :attr:`.HTMLElement.innerHTML` and :attr:`.HTMLElement.textContent` now raise a :py:class:`tkinter.TclError` when invoked on ```` elements, which previously caused segmentation faults. 309 | 310 | * Shorthand CSS properties can now be set and returned after the document is loaded. 311 | 312 | * The ability to style color selector inputs was improved. 313 | * The ability to access form elements has improved. 314 | * Text elements now emit the ``<>`` event *after* the content updates. 315 | * The TkinterWeb demo and some of the built-in pages have been updated. Many internal methods and variables have been renamed, removed, or modified. 316 | 317 | Version 4.1: 318 | 319 | * :meth:`.HtmlFrame.screenshot_page` is now partially supported on Windows and now accepts the additional parameter ``show``. 320 | * The default selection and find text colors are less abrupt. 321 | 322 | Version 4.2: 323 | 324 | * Widgets embedded in the document can now be removed without removing the containing element. 325 | 326 | Version 4.3: 327 | 328 | * Prebuilt Tkhtml binaries have been split off into a new package, `TkinterWeb-Tkhtml `_. This has been done to work towards `bug #52 `_ and reduce the download size of the TkinterWeb package when updating. 329 | 330 | Version 4.4: 331 | 332 | * :meth:`.HtmlFrame.add_html` is now accepts the additional parameter ``return_element``. 333 | * It is now only possible to enable experimental mode if an experimental Tkhtml release is detected. 334 | * Some experimental HTML features were enabled in Windows and Linux. ``border-radius`` is now supported! 335 | 336 | Version 4.5: 337 | 338 | * Periods are now supported in url fragments. See `bug #143 `_ . 339 | * Tkhtml file loading was updated in version 4.5. Some error messages have also been updated. Please submit a bug report if you notice any issues. 340 | 341 | Version 4.6: 342 | 343 | * Url fragments are now tracked as the document loads. This ensures that the fragment is still visible even after loading CSS files or images that change the layout of the document. 344 | * ``gzip`` and ``deflate`` content encodings are now supported. Brotli compression is also supported if the :py:mod:`brotli` module is installed. This increases page load speeds and decreases bandwidth usage in some websites. 345 | * Pressing Ctrl-A in an HTML number input, text input, or textarea will cause the widget's text to be selected. Pasting will now overwrite any selected text. 346 | * Loading local files with a query string in the url will no longer raise an error. 347 | * Fixed :meth:`.HTMLDocument.querySelector`. 348 | 349 | Version 4.7: 350 | 351 | * Fixed flickering when moving the mouse over scrollbars in ``