Full Code of Andereoo/TkinterWeb for AI

main 4a317a4c5d88 cached
43 files
535.1 KB
124.8k tokens
750 symbols
1 requests
Download .txt
Showing preview only (555K chars total). Download the full file or copy to clipboard to get everything.
Repository: Andereoo/TkinterWeb
Branch: main
Commit: 4a317a4c5d88
Files: 43
Total size: 535.1 KB

Directory structure:
gitextract_67ltj4rn/

├── .github/
│   └── FUNDING.yml
├── .gitignore
├── .readthedocs.yaml
├── LICENSE.md
├── MANIFEST.in
├── README.md
├── docs/
│   ├── Makefile
│   ├── make.bat
│   ├── requirements.txt
│   └── source/
│       ├── _static/
│       │   ├── banner.png~
│       │   └── custom.css
│       ├── api/
│       │   ├── extensions.rst
│       │   ├── htmldocument.rst
│       │   ├── htmlframe.rst
│       │   ├── jsengine.rst
│       │   ├── notebook.rst
│       │   ├── tkinterweb.rst
│       │   └── tkinterweb_api.rst
│       ├── api.rst
│       ├── caret.rst
│       ├── compatibility.rst
│       ├── conf.py
│       ├── dom.rst
│       ├── faq.rst
│       ├── geometry.rst
│       ├── index.rst
│       ├── javascript.rst
│       ├── shrink.rst
│       ├── upgrading.rst
│       └── usage.rst
├── examples/
│   └── TkinterWebBrowser.py
├── setup.py
├── tkinterweb/
│   ├── __init__.py
│   ├── bindings.py
│   ├── dom.py
│   ├── extensions.py
│   ├── handlers.py
│   ├── htmlwidgets.py
│   ├── imageutils.py
│   ├── js.py
│   ├── subwidgets.py
│   └── utilities.py
└── tools/
    └── preparewheels.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms

buy_me_a_coffee: andereoo


================================================
FILE: .gitignore
================================================
build/
tkinterweb/__pycache__/
tkinterweb/resources/
tkinterweb.egg-info


================================================
FILE: .readthedocs.yaml
================================================
version: "2"

build:
  os: "ubuntu-22.04"
  tools:
    python: "3.10"

python:
  install:
    - requirements: docs/requirements.txt

sphinx:
  configuration: docs/source/conf.py

================================================
FILE: LICENSE.md
================================================
MIT License

Copyright (c) 2021-2025 Andrew Clarke

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

================================================
FILE: MANIFEST.in
================================================
recursive-include tkinterweb/resources *


================================================
FILE: README.md
================================================
![PyPi Downloads](https://static.pepy.tech/badge/tkinterweb/month)
![MIT Licence](https://img.shields.io/pypi/l/tkinterweb) 
![Python 3](https://img.shields.io/pypi/pyversions/tkinterweb)
![Made in Canada](https://img.shields.io/badge/%F0%9F%87%A8%F0%9F%87%A6%20made%20in%20Canada-grey)

<div style="margin-bottom: 0px"><a href="https://www.buymeacoffee.com/andereoo" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-violet.png" alt="Buy Me A Coffee" style="height: 36px; width: 146px;" ></a></div>

<p align="center"><img src="./docs/source/_static/banner.png" style="width: 425px"/></p>

**<p align="center">Fast and lightweight web browser, rich text, and app design widgets for Tkinter.</p>**

## Overview
**TkinterWeb adds HTML and CSS rendering capabilities to Tkinter widgets.**

Common use cases include displaying help files, documentation, and other HTML content, rendering images (including SVG), building rich-text editors, designing apps with HTML templates, and creating more modern-looking interfaces, with advanced styling and even round buttons!

All [major operating systems](https://tkinterweb.readthedocs.io/en/latest/compatibility.html#a-note-on-tkhtml-binaries) running Python 3.2+ are supported. 

## Usage

**TkinterWeb provides:**
* A [frame widget](https://tkinterweb.readthedocs.io/en/latest/api/htmlframe.html) to display and edit websites, help files, RSS feeds, and any other styled HTML in Tkinter.
* A [label widget](https://tkinterweb.readthedocs.io/en/latest/api/htmlframe.html#tkinterweb.HtmlLabel) that can display styled HTML.
* A [text widget](https://tkinterweb.readthedocs.io/en/latest/api/htmlframe.html#tkinterweb.HtmlText) that allows the user to edit styled HTML.

**TkinterWeb can be used in any Tkinter application to display and edit websites, help pages, documentation, and much more! Here is an example:**
```
import tkinter as tk
from tkinterweb import HtmlFrame # import the HtmlFrame widget

root = tk.Tk() # create the Tkinter window
frame = HtmlFrame(root, messages_enabled=True) # create the HtmlFrame widget
frame.load_website("https://tkinterweb.readthedocs.io/en/latest/index.html") # load a website
frame.pack(fill="both", expand=True) # attach the HtmlFrame widget to the window
root.mainloop()
```
![TkinterWeb](/images/tkinterweb-demo.png)

See [Getting Started](https://tkinterweb.readthedocs.io/en/latest/usage.html) for more tips and tricks.

## Installation
To install TkinterWeb, simply type `pip install tkinterweb[recommended]` in the command prompt or terminal. That's it!

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.

## Dependencies

**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/). Tkinter and the [TkinterWeb-Tkhtml](https://pypi.org/project/tkinterweb-tkhtml/) package are required.**

I also **strongly** recommended installing the following:
* [TkinterWeb-Tkhtml-Extras](https://pypi.org/project/tkinterweb-tkhtml-extras/) (for better HTML/CSS support and bug fixes)
* [PIL](https://pillow.readthedocs.io/) (for better image support)
* [PIL.ImageTk](https://pillow.readthedocs.io/en/stable/reference/ImageTk.html)

You can also choose from the following list for extra functionality:
* [Brotli](https://github.com/google/brotli) (for faster page loads on some sites)
* [PythonMonkey](http://pythonmonkey.io/) (for basic JavaScript support)
* [CairoSVG](https://cairosvg.org/) or [PyGObject](https://pygobject.gnome.org/) (for SVG support)

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.

## API Documentation

> [!WARNING]
> The API changed significantly in version 4.0.0. See [the changelog](https://tkinterweb.readthedocs.io/en/latest/upgrading.html) for details.

**Documentation and additional information on built-in classes can be found in the corresponding API reference pages:**
* [`tkinterweb.Demo`](https://tkinterweb.readthedocs.io/en/latest/usage.html#installation)
* [`tkinterweb.HtmlFrame`](https://tkinterweb.readthedocs.io/en/latest/api/htmlframe.html)
* [`tkinterweb.HtmlLabel`](https://tkinterweb.readthedocs.io/en/latest/api/htmlframe.html#tkinterweb.HtmlLabel)
* [`tkinterweb.HtmlText`](https://tkinterweb.readthedocs.io/en/latest/api/htmlframe.html#tkinterweb.HtmlText)
* [`tkinterweb.HtmlParse`](https://tkinterweb.readthedocs.io/en/latest/api/htmlframe.html#tkinterweb.HtmlParse)
* [`tkinterweb.TkinterWeb`](https://tkinterweb.readthedocs.io/en/latest/api/tkinterweb.html)
* [`tkinterweb.Notebook`](https://tkinterweb.readthedocs.io/en/latest/api/notebook.html) (a Tkhtml-compatible drop-in replacement for `ttk.Notebook`)

## FAQs
See [Frequently Asked Questions](https://tkinterweb.readthedocs.io/en/latest/faq.html).

## Webpage Compatability
**HTML & CSS:**
* 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).
* Most CSS pseudo-elements, such as `:hover` and `:active` are also supported. 
* On 64-bit Windows and Linux, if the [TkinterWeb-Tkhtml-Extras](https://pypi.org/project/tkinterweb-tkhtml-extras/) is installed, HTML5 tags and some extra CSS properties (including `border-radius` and `overflow-x`) and cursors are also supported. To use these features on all other platforms, you will simply need to compile Tkhtml yourself. Visit and clone https://github.com/Andereoo/TkinterWeb-Tkhtml. Then run `python compile.py --install`.

**JavaScript:**
* Javascript only partly supported at the moment.
   * To use JavaScript, PythonMonkey must be installed.
* It is also possible for the user to connect their own JavaScript interpreter or manipulate the document through Python.
* 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.

**Images:**
* TkinterWeb supports nearly 50 different image types through PIL.
* In order to load Scalable Vector Graphic images, CairoSVG, both PyCairo and PyGObject, or both PyCairo and Rsvg must also be installed. 

## Support & Donations
**☕ 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!**

This project is released under the [MIT License](./LICENSE.md) and is free to use, including for commercial purposes.

If you use this project in a commercial product or derive financial benefit from it, please kindly consider supporting its development with a donation. This helps cover maintenance time and ongoing improvements, which in turn will improve your own software!

## Contributing
**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!**

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.

## Credits
**TkinterWeb is powered by the [Tkhtml project](https://web.archive.org/web/20250219233338/http://tkhtml.tcl.tk/).**

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.

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.

Thanks to the [TkinterHtml package](https://bitbucket.org/aivarannamaa/tkinterhtml) for providing the bindings on which this project is based and the [BRL-CAD project](https://github.com/BRL-CAD/brlcad) for providing modifications for Tkhtml on 64-bit Windows.

A huge thanks to everyone else who supported this project by reporting bugs and providing suggestions!


================================================
FILE: docs/Makefile
================================================
# Minimal makefile for Sphinx documentation
#

# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS    ?=
SPHINXBUILD   ?= sphinx-build
SOURCEDIR     = source
BUILDDIR      = build

# Put it first so that "make" without argument is like "make help".
help:
	@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

.PHONY: help Makefile

# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)


================================================
FILE: docs/make.bat
================================================
@ECHO OFF

pushd %~dp0

REM Command file for Sphinx documentation

if "%SPHINXBUILD%" == "" (
	set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=source
set BUILDDIR=build

if "%1" == "" goto help

%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
	echo.
	echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
	echo.installed, then set the SPHINXBUILD environment variable to point
	echo.to the full path of the 'sphinx-build' executable. Alternatively you
	echo.may add the Sphinx directory to PATH.
	echo.
	echo.If you don't have Sphinx installed, grab it from
	echo.http://sphinx-doc.org/
	exit /b 1
)

%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end

:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%

:end
popd


================================================
FILE: docs/requirements.txt
================================================
sphinx==7.1.2
sphinx-rtd-theme==1.3.0rc1
sphinx_design==0.6.1
tkinterweb-tkhtml >= 2.0.0
Pillow >= 10.0.0


================================================
FILE: docs/source/_static/custom.css
================================================
/* This file is used to make tweak the documentation and make it CSS2 compatible.*/

.wy-nav-side {
  overflow: hidden !important;
}

.wy-side-nav-search > div.version {
  color: #cacaca !important;
}

.wy-side-nav-search, .wy-nav-top, .note .admonition-title, .wy-menu-vertical a:active {
  background-color: rgb(77, 122, 77) !important;
}

.wy-nav-content a {
  color: rgb(77, 122, 77);
}

.wy-nav-content a:hover {
  color: rgb(96, 156, 96);
}

code.literal,
span.literal {
  color: rgb(94, 94, 94) !important;
}

.rst-content code.xref,
.rst-content tt.xref,
a .rst-content code,
a .rst-content tt {
  color: initial !important
}

.note {
  background-color: rgb(230, 238, 230) !important;
}

#rtd-search-form input {
  width: 80% !important;
  color: black !important;
  margin-left: auto;
  margin-right: auto;
}

.wy-breadcrumbs .icon-home:before {
  content: "Home" !important;
  font-family: Lato, proxima-nova, Helvetica Neue, Arial, sans-serif !important;
}

.wy-breadcrumbs .wy-breadcrumbs-aside {
  display: none;
}

.wy-side-nav-search {
  padding-left: 0 !important;
  padding-right: 0 !important;
}

.wy-breadcrumbs .breadcrumb-item {
  display: inline !important;
}

.highlight-python,
.highlight-console,
.highlight-default {
  border: 1px solid #e1e4e5 !important;
  margin: 1px 0 24px !important;
  background: #f8f8f8 !important;
  width: 100% !important;
}

.highlight pre {
  white-space: pre !important;
  margin: 0 !important;
  padding: 12px !important;
  display: block !important;
}

.highlight {
  border: none !important;
  margin: 0 !important;
}

.sig-object {
  display: table !important;
  margin: 6px 0 !important;
  margin-top: 6px !important;
  font-size: 90% !important;
  line-height: normal !important;
  background: rgb(230, 238, 230) !important;
  color: rgb(77, 122, 77) !important;
  border-top: 3px solid rgb(101, 145, 101) !important;
  padding: 6px !important;
  position: relative !important;
}

.sig-object .property,
.sig-object .sig-param,
.sig-object .sig-paren {
  font-size: 90% !important;
  line-height: normal !important;
  color: rgb(77, 122, 77) !important;
}

.sig-object .sig-name,
.sig-object .sig-prename {
  font-family: SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, Courier, monospace !important;
  color: #000 !important;
}

dd .sig-object {
  margin-bottom: 6px !important;
  border: none !important;
  border-left-width: medium !important;
  border-left-style: none !important;
  border-left-color: currentcolor !important;
  border-left: 3px solid #ccc !important;
  background: #f0f0f0 !important;
  color: #555 !important;
}

dd .sig-name {
  font-family: SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, Courier, monospace !important;
  color: #000 !important;
}

dd .property,
dd .sig-param,
dd .sig-paren {
  color: inherit !important
}

.rst-footer-buttons {
  display: table !important;
  width: 100% !important;
}

.rst-footer-buttons .float-left {
  display: table-cell !important;
}

.rst-footer-buttons .float-right {
  display: table-cell !important;
}

.fa-arrow-circle-right:before {
  content: " →" !important;
}

.fa-arrow-circle-left:before {
  content: "← " !important;
}

.btn {
  border-color: #e1e4e5 !important;
  padding: 6px 12px !important;
  border-bottom: 2px solid #ccc !important;
  box-shadow: none !important;
  transition: none !important;
}

.btn:active {
  border-top: 2px solid #ccc !important;
  padding: 5px 12px 7px !important;
  border-bottom-width: 1px !important;
}

.wy-side-nav-search > a {
  padding: 0 !important;
  margin: 0 !important;
}

.wy-side-nav-search > a:hover {
  background: transparent !important;
}

.wy-side-nav-search > a img.logo {
  max-width: 250px !important;
  margin: 0 !important;
}

.sd-card-header {
  display: block;
}

details.sd-dropdown .sd-summary-content p {
  cursor: text;
}

details.sd-dropdown summary.sd-summary-title{
  padding: .5em .6em .5em 1em !important;
}

.sd-mb-3, .sd-my-3 {
  margin-bottom: 16px !important;
}

.sd-card-header, .sd-card {
  border-color: #dcdcdc;
  border-radius: 4px;
}

.sd-card-body {
  padding: 0px 17px !important;
}

.rst-content .section ul, .rst-content .toctree-wrapper ul, .rst-content section ul, .wy-plain-list-disc, article ul {
  margin: 12px 0px !important;
}

.sd-card-body > p {
  margin: 12px 0px !important;
}

================================================
FILE: docs/source/api/extensions.rst
================================================
TkinterWeb Extensions
=====================

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.

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.

.. autoclass:: tkinterweb.extensions.SelectionManager
   :members:

.. autoclass:: tkinterweb.extensions.CaretManager
   :members:

.. autoclass:: tkinterweb.extensions.EventManager
   :members:

.. autoclass:: tkinterweb.extensions.WidgetManager
   :members:

.. autoclass:: tkinterweb.extensions.SearchManager
   :members:

================================================
FILE: docs/source/api/htmldocument.rst
================================================
Document Object Model Documentation
===================================

.. note::
    The API changed significantly in version 4. See :doc:`the changelog <../upgrading>` for details.

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.

.. autoclass:: tkinterweb.dom.HTMLDocument
   :members:

.. autoclass:: tkinterweb.dom.HTMLElement
   :members:

The following JavaScript event properties are also supported: ``onchange``, ``onclick``, ``oncontextmenu``, ``ondblclick``, ``onload``, ``onmousedown``, ``onmouseenter``, ``onmouseleave``, ``onmousemove``, ``onmouseout``, ``onmouseover``, and ``onmouseup``.

.. autoclass:: tkinterweb.dom.HTMLCollection
   :members:

.. autoclass:: tkinterweb.dom.CSSStyleDeclaration
   :members:

.. autoclass:: tkinterweb.dom.DOMRect
   :members:

Special thanks to `Zamy846692 <https://github.com/Zamy846692>`_ for the help making this happen!


================================================
FILE: docs/source/api/htmlframe.rst
================================================
HTML Widgets Documentation
==========================

.. note::
    The API changed significantly in version 4. See :doc:`the changelog <../upgrading>` for details.

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.

The :class:`~tkinterweb.HtmlFrame` widget is also capable managing other Tkinter widgets, making it easy to combine Tkinter widgets and HTML elements.


.. autoclass:: tkinterweb.HtmlFrame
   :members:

This widget also emits the following Tkinter virtual events that can be bound to:

* ``<<DownloadingResource>>``/:py:attr:`utilities.DOWNLOADING_RESOURCE_EVENT`: Generated whenever a new resource is being downloaded.
* ``<<DoneLoading>>``/: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.
* ``<<DOMContentLoaded>>``/: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.
* ``<<UrlChanged>>``/:py:attr:`utilities.URL_CHANGED_EVENT`: Generated whenever the widget's url changes or redirects. Use :attr:`.HtmlFrame.current_url` to get the url.
* ``<<IconChanged>>``/:py:attr:`utilities.ICON_CHANGED_EVENT`: Generated whenever the icon of a webpage changes. Use :attr:`.HtmlFrame.icon` to get the icon.
* ``<<TitleChanged>>``/: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.
* ``<<Modified>>```/:py:attr:`utilities.FIELD_CHANGED_EVENT`: Generated whenever the content of an interactive element changes.

.. autoclass:: tkinterweb.HtmlLabel
   :members:

.. autoclass:: tkinterweb.HtmlText
   :members:

.. autoclass:: tkinterweb.HtmlParse
   :members:

================================================
FILE: docs/source/api/jsengine.rst
================================================
JavaScript Engine Documentation
===============================


The methods described in this page make it easy to interact with the JavaScript Engine.

.. autoclass:: tkinterweb.js.JSEngine
   :members:


================================================
FILE: docs/source/api/notebook.rst
================================================
Notebook Documentation
=======================

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.

.. autoclass:: tkinterweb.Notebook
   :members:

This widget also emits the following Tkinter virtual events that can be bound to:

* ``<<NotebookTabChanged>>``: Generated whenever the selected tab changes.


================================================
FILE: docs/source/api/tkinterweb.rst
================================================
Internals Documentation
=======================

.. toctree ::
   :maxdepth: 2

   tkinterweb_api
   extensions

================================================
FILE: docs/source/api/tkinterweb_api.rst
================================================
Bindings Documentation
======================

.. note::
   This API has changed significantly recently. See :doc:`the changelog <../upgrading>` for details.


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. 

Refer to the `Tkhtml Documentation <https://web.archive.org/web/20250219233338/http://tkhtml.tcl.tk/>`_ for more details on some of the commands.

.. autoclass:: tkinterweb.TkinterWeb
   :members:

.. autoclass:: tkinterweb.TkHtmlParsedURI
   :members:

================================================
FILE: docs/source/api.rst
================================================
API Reference
==============

.. note::
    The API changed significantly in version 4. See :doc:`the changelog <upgrading>` for details.

.. toctree::
   :maxdepth: 2
   :caption: Available Classes

   api/htmlframe
   api/htmldocument
   api/jsengine
   api/notebook

-------------------

.. toctree::
   :maxdepth: 3

   api/tkinterweb

================================================
FILE: docs/source/caret.rst
================================================
Making Documents Editable
=========================

.. note::
    Caret browsing support is new in version 4.8. The :class:`~tkinterweb.HtmlText` widget was made editable in version 4.15. Make sure you are using the latest version of TkinterWeb.

Overview
--------

**TkinterWeb can be used to create a rich text or HTML editor.**

The :class:`~tkinterweb.HtmlText` widget provides a simple HTML editor that can be extended to adapt to the needs of your application.

TkinterWeb also provides a useful API for developers to create their own HTML-based what-you-see-is-what-you-get editor.

These features are new. Please reach out to report a bug, suggest an improvement, or seek support.

Setup
------

To enable caret browsing mode, 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.

When enabled, a caret will appear once the user clicks on text in the document. Use the methods described below to handle keypresses, or instead use the :class:`~tkinterweb.HtmlText` widget which handles most cases on its own.

How-to
------

Simply create your :class:`~tkinterweb.HtmlText` and start editing!

.. code-block:: python

    from tkinterweb import HtmlText
    yourhtmlframe = HtmlText(root, messages_enabled=True)

You can also load html, files, and websites. For instance, to create an editable page with a heading, an orange block, and a list, you could use the following:

.. code-block:: python

    yourhtmlframe = HtmlText(root, messages_enabled=True)
    yourhtmlframe.load_html("""<h2>Hello, world!</h2>
    <div style='background: orange; border-radius: 10px; padding: 10px'>
        <div>Tkinter is so cool.</div>
    </div>
    <ul>
        <li>TkinterWeb is also cool</li>
        <li>Python is also cool</li>
    </ul>""")

.. image:: ./_static/text_widget.png

It's that easy! 

You can insert and edit hyperlinks, images, and much more. Click on a hyperlink while pressing the Ctrl key to navigate to it. Like the :class:`~tkinterweb.HtmlFrame` widget, the :class:`~tkinterweb.HtmlText` widget is also scrollable out of the box!

You can also use :meth:`.HtmlText.insert` and :meth:`.HtmlText.delete` to easily modify the document.

Customization
-------------

Everything described below applies to all HTML widgets with caret browsing enabled.

Use :meth:`.HtmlFrame.get_caret_position` to get the caret's position. The element returned will always be a text node.

.. tip::
     You can use the methods outlined in the `HTMLElement documentation <api/htmldocument.html#tkinterweb.dom.HTMLElement>`_ to get the element's parent if needed. From here you can insert new elements, change the text and much more!

Use :meth:`.HtmlFrame.shift_caret_left` or :meth:`.HtmlFrame.shift_caret_right` to shift the caret left or right.

The following is a simple example showing how to handle keypresses to insert letters and numbers:

.. code-block:: python
    
    def on_keypress(event):
        # Get the caret's position
        caret_position = yourhtmlframe.get_caret_position()
        if caret_position and event.char:
            element, text, index = caret_position
            
            # Add the key's character to the element's text
            newtext = text[:index] + event.char + text[index:]

            # Set the element's text
            element.textContent = newtext

            # Shift the caret right
            yourhtmlframe.shift_caret_right()

    yourhtmlframe.bind("<Key>", on_keypress)

This works on all HTML widgets.

.. warning::
    If using the :class:`~tkinterweb.HtmlText` widget, binding to ``<Key>`` will remove all default key bindings. Either bind to individual keys as needed or use ``yourhtmlframe.bind("<Key>", on_keypress, add="+")``, but keep in mind then both bindings will fire.

.. note::
    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 ``"&nbsp;"``).

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.

Some extra logic will be needed to handle other types of keypresses. See the :class:`~tkinterweb.HtmlText` source code for inspiration.

.. tip::
    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. 
    
    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`.

Use :meth:`.HtmlFrame.get_selection_position` to get the position of any selected text and :meth:`.HtmlFrame.clear_selection` to clear the selection. 

You may need to set the caret's position after modifying the document.

.. tip::
    :meth:`.HtmlFrame.set_caret_position` will raise an error if the element provided has been removed or is empty. 
    
    If you need to remove or empty the elements returned by :meth:`.HtmlFrame.get_selection_position` or :meth:`.HtmlFrame.get_caret_position`, you can also get the selection or caret's position relative to the page text content using :meth:`HtmlFrame.get_selection_position(return_elements=False) <.HtmlFrame.get_selection_position>` and :meth:`HtmlFrame.get_caret_position(return_element=False) <.HtmlFrame.get_caret_position>`, respectively.

    You can then set the selection or caret's position as usual, providing only indexes (i.e. ``yourhtmlframe.set_selection_position(start_index=5, end_index=10)``.


The following code can be used as a starting point on handling backspaces when text is selected:

.. code-block:: python

    def on_backspace(event):
        # Get the selection's position and deselect all selected text
        selection = yourhtmlframe.get_selection_position()

        if selection:
            start, end, middle = selection
            start_element, start_element_text, start_element_index = start
            end_element, end_element_text, end_element_index = end

            # Deselect all selected text
            d.clear_selection()

            # Cut out the selection
            start_element.textContent = start_element_text[:start_element_index] + start_element_text[end_element_index:]

            if start_element != end_element:
                # Delete the end element
                end_element.remove()

                # Remove each element that is fully selected, and its parent if it is now empty
                for element in middle:
                    parent = element.parentElement
                    element.remove()
                    if len(parent.children) == 0:
                        parent.remove()

            # Set the caret's position
            yourhtmlframe.set_caret_position(start_element, start_element_index)

    yourhtmlframe.bind("<BackSpace>", on_backspace)

You can use :meth:`.HtmlFrame.set_selection_position` to set the selection if needed.

-------------------

See the `HtmlFrame documentation <api/htmlframe.html#tkinterweb.HtmlFrame.get_caret_position>`_ for a complete list of supported methods.

Please report bugs or request new features on the `issues page <https://github.com/Andereoo/TkinterWeb/issues>`_.

================================================
FILE: docs/source/compatibility.rst
================================================
System and Webpage Compatibility
================================

System compatibility
--------------------

**TkinterWeb supports all platforms but only ships with precompiled Tkhtml binaries for the most common platforms:**

* x86_64 Windows, Linux, and macOS
* i686 Windows and Linux
* ARM64 Macos and Linux
* ARMv71 Linux

If your system is unsupported, compile and install Tkhtml by visiting and cloning https://github.com/Andereoo/TkinterWeb-Tkhtml. Then run ``python compile.py --install``.

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`, :class:`~tkinterweb.HtmlLabel`, or :class:`~tkinterweb.HtmlText` widget to use the system's Tkhtml. Keep in mind that some features will no longer work.

If you are encountering issues, feel free to submit a bug report or feature request.

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!

Webpage compatibility
---------------------

**HTML & CSS:**

* 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>`_.
* Most CSS pseudo-elements, such as ``:hover`` and ``:active`` are also supported.
* On 64-bit Windows and Linux, if the `TkinterWeb-Tkhtml-Extras <https://pypi.org/project/tkinterweb-tkhtml-extras/>`_ package is installed, HTML5 tags and some extra CSS properties (including ``border-radius`` and ``overflow-x``) and cursors are also supported. To use these features on all other platforms, you will simply need to compile Tkhtml yourself. Visit and clone https://github.com/Andereoo/TkinterWeb-Tkhtml. Then run ``python compile.py --install``.

**JavaScript:**

* JavaScript partly supported at the moment. See :doc:`javascript` for more information.

  * To use JavaScript, `PythonMonkey <http://pythonmonkey.io/>`_  must be installed.

* 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.

**Images:**

* TkinterWeb supports nearly 50 different image types through `PIL <https://pillow.readthedocs.io/>`_.

* In order to load Scalable Vector Graphic images, `CairoSVG <https://cairosvg.org/>`_, `PyGObject <https://pygobject.gnome.org/>`_, or both :py:mod:`PyCairo` and :py:mod:`Rsvg` must also be installed. 

-------------------

Please report bugs or request new features on the `issues page <https://github.com/Andereoo/TkinterWeb/issues>`_.

================================================
FILE: docs/source/conf.py
================================================
# Configuration file for the Sphinx documentation builder.

# -- Project information

import os
import sys

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname((os.path.realpath(__file__))))))

from tkinterweb import __title__, __copyright__, __author__, __version__

project = __title__
copyright = __copyright__
author = __author__

release = ".".join(__version__.split(".")[:2])
version = __version__

# -- General configuration

extensions = [
    'sphinx.ext.duration',
    'sphinx.ext.doctest',
    'sphinx.ext.autodoc',
    'sphinx.ext.autosummary',
    'sphinx.ext.intersphinx',
    "sphinx_design",
]

intersphinx_mapping = {
    'python': ('https://docs.python.org/3/', None),
    'sphinx': ('https://www.sphinx-doc.org/en/master/', None),
}

html_logo = "_static/logo.png"
html_theme_options = {"logo_only": True}
templates_path = ['_templates']

html_static_path = ['_static']
html_css_files = ['custom.css']

# -- Options for HTML output

autodoc_member_order = 'bysource'

html_theme = 'sphinx_rtd_theme' # May switch to agogo or alabaster or python_docs_theme

# -- Options for EPUB output
epub_show_urls = 'footnote'

def skip_member(app, what, name, obj, skip, options):
    if name == "destroy" and what == "class":
        return True
    return skip

def setup(app):
    app.connect("autodoc-skip-member", skip_member)

================================================
FILE: docs/source/dom.rst
================================================
Manipulating the Page
=====================

.. note::
    The API changed significantly in version 4. See :doc:`the changelog <upgrading>` for details.

Overview
--------

**TkinterWeb provides a handful of functions that allow for manipulation of the webpage. They are fashioned after common JavaScript functions.**


How-to
--------

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:

.. code-block:: python

    yourhtmlframe = tkinterweb.HtmlFrame(root, messages_enabled=True)
    yourhtmlframe.load_html("<div id='container'><p>Test</p></div>")
    container = yourhtmlframe.document.getElementById("container")
    new_header = yourhtmlframe.document.createElement("h1")
    new_header.textContent = "Hello, world!"
    new_header.style.color = "blue"
    container.appendChild(new_header)


.. _binding-to-an-element:

Binding to an element
---------------------

To manage bindings on HTML elements, simply use :meth:`~tkinterweb.dom.HTMLElement.bind` and :meth:`~tkinterweb.dom.HTMLElement.unbind` (new in version 4.9):

.. code-block:: python

    container = yourhtmlframe.document.getElementById("container")

    def callback(event):
        print("Woah this is cool!")

    container.bind("<Button-3>", callback)

-------------------

See the :doc:`api/htmldocument` for a complete list of supported commands.

See :doc:`javascript` for information on manipulating the DOM through JavaScript.

Please report bugs or request new features on the `issues page <https://github.com/Andereoo/TkinterWeb/issues>`_.

================================================
FILE: docs/source/faq.rst
================================================
Frequently Asked Questions
==========================

How do I load websites or files?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

* 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.

How do I manage clicks and use custom bindings?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

* 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.
* 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.
 
TkinterWeb is crashing
~~~~~~~~~~~~~~~~~~~~~~

* That is defenitely not normal. Make sure your are using the most up-to-date TkinterWeb version and have crash protection enabled.
* If you are using a :py:class:`ttk.Notebook` in your app, see the question below.
* If all else fails, `file a bug report <https://github.com/Andereoo/TkinterWeb/issues/new>`_. Post your operating system, Python version, and TkinterWeb version, as well as any error codes or instructions for reproducing the crash.

I'm having issues when using shrink or the HtmlLabel widget
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

* See :doc:`shrink` for more information.

My app crashes when I open a tab with an HtmlFrame in it
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

* Tkhtml (the underlying HTML engine) and the :py:class:`ttk.Notebook` widget aren't compatable on 64-bit Windows.
* This is a known issue. Fixing this is beyond the scope of this project, but working around it is easy.
* 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 <https://github.com/Andereoo/TkinterWeb/issues/19>`_ for more information.
* 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.

I get a ModuleNotFoundError after compiling my code
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

* When compiling your code, you might get an error popup saying ``ModuleNotFoundError: The files required to run TkinterWeb could not be found``
* Your app might also fail quietly if TkinterWeb's dependencies are not installed
* 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.
* 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.
* On py2app / py2exe: Add ``'packages': ['tkinterweb', 'tkinterweb_tkhtml', 'tkinterweb_tkhtml_extras']`` to the ``OPTIONS`` variable in your setup file.

-------------------

Please report bugs or request new features on the `issues page <https://github.com/Andereoo/TkinterWeb/issues>`_.


================================================
FILE: docs/source/geometry.rst
================================================
Embedding Widgets
=================

.. note::
    The API changed significantly in version 4. See :doc:`the changelog <upgrading>` for details.

Overview
--------

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.

**TkinterWeb provides a system for attaching Tkinter widgets onto the window, and handles layouts, images, selection, scrolling, and much more for you.**

How-to
------

To place a Tkinter widget inside an HTML document, add the ``data=[yourwidget]`` attribute to an ``<object>`` element. For example, to add a button under some italic text, one could do:

.. code-block:: python

    yourframe = tkinterweb.HtmlFrame(root, messages_enabled=True)
    yourbutton = tkinter.Button(yourframe, text="Hello, world!")
    source_html = f"<i>This is some text</i><br><object data={yourbutton}></object>"
    yourframe.load_html(source_html) # or use add_html to add onto the existing document

**Ensure your HtmlFrame widget was created before the widget you are embedding, or else the widget might not be visible.**

.. tip::

    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).

    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).

.. note::
    
    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. 

Widget position and sizing can be modified using CSS styling on the widget's associated ``<object>`` element.

See :doc:`dom` (new in version 3.25) for more details.

To get the element containing your widget, either use :meth:`.HtmlFrame.widget_to_element`.

Widget handling
---------------

You can also set, remove, or change the widget in any element later (new in version 4.2):

.. code-block:: python

    yourbutton = tkinter.Button(yourframe, text="Hello, world!")
    ...
    yourelement = yourframe.document.getElementById("#container") # get the element to fill
    yourelement.widget = yourbutton # set the element's widget

The widget can be removed from the element via ``yourelement.widget = None``.

-------------------

Please report bugs or request new features on the `issues page <https://github.com/Andereoo/TkinterWeb/issues>`_.

================================================
FILE: docs/source/index.rst
================================================
Welcome to TkinterWeb!
======================

.. image:: https://static.pepy.tech/badge/tkinterweb/month
    :target: https://pepy.tech/project/tkinterweb
    :alt: PyPi Downloads

.. image:: https://img.shields.io/pypi/l/tkinterweb
    :target: https://pypi.org/project/tkinterweb/
    :alt: MIT Licence

.. image:: https://img.shields.io/pypi/pyversions/tkinterweb
    :target: https://pypi.org/project/tkinterweb/
    :alt: Python 3

.. image:: https://img.shields.io/badge/%F0%9F%87%A8%F0%9F%87%A6%20made%20in%20Canada-grey
    :target: https://pypi.org/project/tkinterweb/
    :alt: Made in Canada


**TkinterWeb** is a Python library that adds HTML and CSS rendering capabilities to Tkinter widgets.

Common use cases include displaying help files, documentation, and other HTML content, rendering images (including SVG), building rich-text editors, designing apps with HTML templates, and creating more modern-looking interfaces, with advanced styling and even round buttons!

Getting started
---------------

TkinterWeb provides a `frame widget <./api/htmlframe.html>`_, a `label widget <./api/htmlframe.html#tkinterweb.HtmlLabel>`_, and a `text widget <./api/htmlframe.html#tkinterweb.HtmlText>`_. 

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!

Love this project?
------------------

You can help this project 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!

☕ Or, 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!

This project is released under the `MIT License <https://github.com/Andereoo/TkinterWeb/blob/main/LICENSE.md>`_ and is free to use, including for commercial purposes.

If you use this project in a commercial product or derive financial benefit from it, please kindly consider supporting its development with a donation. This helps cover maintenance time and ongoing improvements, which in turn will improve your own software!

.. raw:: html
   
   <a href="https://www.buymeacoffee.com/andereoo" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-violet.png" alt="Buy Me A Coffee" style="height: 35px !important;width: 140px !important;" ></a>

Pages
-----

.. toctree::
   :maxdepth: 1

   usage
   geometry
   dom
   caret
   shrink
   javascript
   faq
   compatibility
   upgrading
   api


================================================
FILE: docs/source/javascript.rst
================================================
Using JavaScript
================

.. note::
    JavaScript support is new in version 4.1. Embedded Python support is new in version 4.19. Make sure you are using the latest version of TkinterWeb.

Overview
--------

**Scripting support makes it easy to embed JavaScript or Python code in your document.**

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.

Setup
------

To enable JavaScript support in TkinterWeb, first install `PythonMonkey <http://pythonmonkey.io/>`_ using pip:

.. code-block:: console

   $ pip install pythonmonkey

Skip this step if you are embedding Python code in your document.

Or when installing TkinterWeb, use:

.. code-block:: console

   $ pip install tkinterweb[javascript]

Then add ``yourhtmlframe.configure(javascript_enabled=True)`` to your script or add the parameter ``javascript_enabled=True`` when creating your :class:`~tkinterweb.HtmlFrame`, :class:`~tkinterweb.HtmlLabel`, or :class:`~tkinterweb.HtmlText` widget.

.. note::
    If using Windows, ensure you are using an up-to-date Python version. In some Python versions prior to version 3.13, Python will crash when loading PythonMonkey.

**Only enable JavaScript in documents with code you know and trust.**

How-to
------

To change the color and text of a ``<p>`` element when clicked, you could use the following:

.. code-block:: python
    
    yourhtmlframe = tkinterweb.HtmlFrame(root, messages_enabled=True, javascript_enabled=True)
    yourhtmlframe.load_html("""
        <script>
        function changeColor(element) {
            element.style.color = "blue"
            element.textContent = "I've been clicked!"
        }
        </script>
        <div id='container'><p onclick="changeColor(this)">Hello, world!</p></div>
        """)

Add the ``defer`` attribute to the relevant ``<script>`` element if you want it to run after the page loads. Otherwise, the script will be executed as soon as it is encountered in the document.

The following JavaScript event attributes are supported: ``onchange``, ``onload``, ``onclick``, ``oncontextmenu``, ``ondblclick``, ``onmousedown``, ``onmouseenter``, ``onmouseleave``, ``onmousemove``, ``onmouseout``, ``onmouseover``, and ``onmouseup``.

.. tip::

    As of version 4.25, it is highly recommended to enable debug messages (i.e. via ``HtmlFrame(root, messages_enabled=True, ...)``) when testing new scripts. Otherwise, if a script fails it will fail silently.

Embedding Python in your document
---------------------------------

To run embedded scripts as Python code instead of JavaScript, simply use the parameters ``javascript_enabled=True`` and ``javascript_backend="python"`` when creating your HTML widget. Ensure you are running code you trust.

Like normal JavaScript code, by default scripts can access the ``document`` property and inline event callbacks can also access the ``this`` property. You will need to register new objects if you want the document's scripts to be able to access other functions, classes or variables.

That's it!

Registering new objects
-----------------------

To register new objects, use :meth:`.JSEngine.register`. This gives the document's scripts access to Python objects. This, for instance, can be used to implement a ``window`` API or to add a callback for the JavaScript ``alert()`` function:

.. code-block:: python

    yourhtmlframe = tkinterweb.HtmlFrame(root, messages_enabled=True, javascript_enabled=True)
    def open_alert_window(text):
        ## Do stuff
    yourhtmlframe.javascript.register("alert", open_alert_window)
    yourhtmlframe.load_html("<script>alert('Hello, world!')</script><p>Hello, world!</p>")

Using your own interpreter
--------------------------

Alternatively, you can register your own callback for ``<script>`` elements using the :attr:`on_script` parameter:

.. code-block:: python

    yourhtmlframe = tkinterweb.HtmlFrame(root, messages_enabled=True)
    def handle_scripts(attributes, tagcontents):
        ## Do stuff
    yourhtmlframe.configure(javascript_enabled=True, on_script=handle_scripts)
    yourhtmlframe.load_html("<div id='container'><script>// Do stuff</script><p>Test</p></div>")


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.

If needed you can always then create an :class:`~tkinterweb.dom.HTMLElement` instance from a Tkhtml node:

.. code-block:: python
    
    from tkinterweb.dom import HTMLElement
    ...
    yourhtmlelement = HTMLElement(yourhtmlframe.document, yourtkhtmlnode)

-------------------

It is also possible to interact with the document through Python instead. See :doc:`dom`.

Please report bugs or request new features on the `issues page <https://github.com/Andereoo/TkinterWeb/issues>`_.

================================================
FILE: docs/source/shrink.rst
================================================
Creating a Label Widget
=======================

.. note::
    This API changed in version 4.17. See :doc:`the changelog <upgrading>` for details.

Overview
--------

**Shrink makes HTML widgets behave like label widgets, automatically resizing to match their content.**

How-to
------

Use the :class:`.HtmlLabel` widget or add the parameter ``shrink=True`` to the :class:`.HtmlFrame` widget.

Your widget will now shrink to match its content!

.. tip::
    Use the :class:`.HtmlLabel` widget if you want an HTML widget that looks and behaves like a ttk Label. Use the :class:`.HtmlFrame` widget if you want full control.

Tips and tricks
---------------

Word wrapping
~~~~~~~~~~~~~

By default, word wrapping is disabled when shrink is enabled. This forces text to keep inline, which is generally expected from label-like widgets, and prevents a number of bugs that cause the widget to shake or wrap when it shouldn't.

.. note::

    Full shrink word wrapping support is currently only rolled out to 64-bit Windows and Linux users. Ensure you installed TkinterWeb via ``pip install tkinterweb[recommended]`` or ``pip install tkinterweb[full]`` to prevent bugs when using shrink.

    If you are encountering issues on an unsupported platform, either submit a feature request or compile and install Tkhtml 3.1 by visiting and cloning https://github.com/Andereoo/TkinterWeb-Tkhtml. Then run ``python compile.py --install``.

If you need word wrapping, you can re-enable it by using ``HtmlFrame(..., textwrap=True)``. 

It is a known issue that when word wrapping is enabled and the widget is shrunk it will often not re-expand. You will need to signal to the geometry manager that the widget should expand, or use the experimental ``HtmlFrame.unshrink = True`` (not recommended).

Height and width
~~~~~~~~~~~~~~~~

Since the purpose of shrink is to automatically resize the widget according to its content, height and width have little effect.

If you need to set the height and width, simply disable shrink.

Scrollbars
~~~~~~~~~~

Scrollbars are disabled by default when shrink is enabled. Use ``HtmlFrame(..., vertical_scrollbar="auto", horizontal_scrollbar="auto")`` to enable them.

-------------------

Please report bugs or request new features on the `issues page <https://github.com/Andereoo/TkinterWeb/issues>`_.

================================================
FILE: docs/source/upgrading.rst
================================================
Changelog
=========

**The API changed significantly in version 4.**

.. dropdown:: Key Changes
    :open:
    :color: primary

    * Faster load speed
    * A more intuitive API
    * Support for experimental Tkhtml features, such as page printing
    * Widget behaviour and API is now more closely aligned with standard Tkinter widgets
    * Many DOM improvements. The DOM API now more closely mirrors its JavaScript counterpart.
    * Dozens of new configuration options, including access to more settings and the ability to link a JavaScript interpreter

    * Added basic JavaScript support (new in version 4.1)
    * Improved embedded widget handling (new in version 4.2)
    * Cross-platform SVG and ``border-radius`` support (new in version 4.4)
    * Support for Tcl 9 (new in version 4.5)
    * Caret browsing functionality (new in version 4.8)
    * Improved thread safety (new in version 4.9)
    * Ability to bind to HTML elements (new in version 4.10)
    * Added an HTML-based text widget (new in version 4.15)

.. dropdown:: Removed

    Version 4.0:

    * ``HtmlFrame.get_zoom()`` - use ``HtmlFrame.cget("zoom")``
    * ``HtmlFrame.set_zoom()`` - use ``HtmlFrame.configure(zoom=)``
    * ``HtmlFrame.get_fontscale()`` - use ``HtmlFrame.cget("fontscale")``
    * ``HtmlFrame.set_fontscale()`` - use ``HtmlFrame.configure(fontscale=)``
    * ``HtmlFrame.get_parsemode()`` - use ``HtmlFrame.cget("parsemode")``
    * ``HtmlFrame.set_parsemode()`` - use ``HtmlFrame.configure(parsemode=)``
    * ``HtmlFrame.set_message_func()`` - use ``HtmlFrame.configure(message_func=)``
    * ``HtmlFrame.set_broken_webpage_message()`` - use ``HtmlFrame.configure(on_navigate_fail=)``. Note that :attr:`on_navigate_fail` requires a function instead.
    * ``HtmlFrame.set_maximum_thread_count()`` - use ``HtmlFrame.configure(threading_enabled=)``
    * ``HtmlFrame.set_recursive_hover_depth()`` - use ``HtmlFrame.html.recursive_hover_depth=``
    * ``HtmlFrame.add_visited_links()`` - use ``HtmlFrame.configure(visited_links=)``
    * ``HtmlFrame.clear_visited_links()`` - use ``HtmlFrame.configure(visited_links=)``
    * ``HtmlFrame.enable_stylesheets()`` - use ``HtmlFrame.configure(stylesheets_enabled=)``
    * ``HtmlFrame.enable_images()`` - use ``HtmlFrame.configure(images_enabled=)``
    * ``HtmlFrame.enable_forms()`` - use ``HtmlFrame.configure(forms_enabled=)``
    * ``HtmlFrame.enable_objects()`` - use ``HtmlFrame.configure(objects_enabled=)``
    * ``HtmlFrame.enable_caches()`` - use ``HtmlFrame.configure(caches_enabled=)``
    * ``HtmlFrame.enable_dark_theme()`` - use ``HtmlFrame.configure(dark_theme_enabled=, image_inversion_enabled=)``
    * ``HtmlFrame.on_image_setup()`` - use ``HtmlFrame.configure(on_resource_setup=)``
    * ``HtmlFrame.on_downloading_resource()`` - bind to  ``<<DownloadingResource>>``/:py:attr:`utilities.DOWNLOADING_RESOURCE_EVENT`
    * ``HtmlFrame.on_done_loading()`` - bind to ``<<DoneLoading>>``/:py:attr:`utilities.DONE_LOADING_EVENT`
    * ``HtmlFrame.on_url_change()`` - bind to ``<<UrlChanged>>``/:py:attr:`utilities.URL_CHANGED_EVENT and use :attr:`.HtmlFrame.current_url`
    * ``HtmlFrame.on_icon_change()`` - bind to ``<<IconChanged>>``/:py:attr:`utilities.ICON_CHANGED_EVENT` and use :attr:`.HtmlFrame.title`
    * ``HtmlFrame.on_title_change()`` - bind to ``<<TitleChanged>>``/:py:attr:`utilities.TITLE_CHANGED_EVENT` and use :attr:`.HtmlFrame.title`
    * ``HtmlFrame.on_form_submit()`` - use ``HtmlFrame.configure(on_form_submit=)``
    * ``HtmlFrame.on_link_click()`` - use ``HtmlFrame.configure(on_link_click=)``
    * ``HtmlFrame.yview_toelement()`` - use :meth:`.HTMLElement.scrollIntoView`
    * ``HtmlFrame.get_currently_hovered_node_text()`` - :meth:`.HtmlFrame.get_currently_hovered_element`
    * ``HtmlFrame.get_currently_hovered_node_tag()`` - :meth:`.HtmlFrame.get_currently_hovered_element`
    * ``HtmlFrame.get_currently_hovered_node_attribute()`` - :meth:`.HtmlFrame.get_currently_hovered_element`
    * ``HtmlFrame.get_current_link()`` - use :meth:`.HtmlFrame.get_currently_hovered_element`

    * The ``widgetid`` attribute no longer embeds widgets. Use ``<object data=name_of_your_widget></object>`` or :attr:`.HTMLElement.widget` instead. This improves load speeds and allows for widget style handling.

    Version 4.2:

    * ``TkinterWeb.replace_widget()``
    * ``TkinterWeb.replace_element()``
    * ``TkinterWeb.remove_widget()``

    Version 4.8

    * ``HtmlFrame.replace_widget()`` (deprecated in version 4.0) - use :meth:`.HtmlFrame.widget_to_element` and :attr:`.HTMLElement.widget`
    * ``HtmlFrame.replace_element()`` (deprecated in version 4.0) - use :attr:`.HTMLElement.widget`
    * ``HtmlFrame.remove_widget()`` (deprecated in version 4.0) - use :meth:`.HTMLElement.remove`

    Version 4.14:

    * The ``style`` configuration option no longer sets the CSS style of :class:`.HtmlLabel` widgets. See `bug #145 <https://github.com/Andereoo/TkinterWeb/issues/145>`_.

.. dropdown:: Deprecated

    Version 4.11:

    * ``TkinterWeb.update_tags()`` - use :meth:`.SelectionManager.update_tags`
    * ``TkinterWeb.select_all()`` - use :meth:`.SelectionManager.select_all`
    * ``TkinterWeb.clear_selection()`` - use :meth:`.SelectionManager.clear_selection`
    * ``TkinterWeb.update_selection()`` - use :meth:`.SelectionManager.update_selection`
    * ``TkinterWeb.get_selection()`` - use :meth:`.SelectionManager.get_selection`
    * ``TkinterWeb.copy_selection()`` - use :meth:`.SelectionManager.copy_selection`
    * ``TkinterWeb.allocate_image_name()`` - use :meth:`.ImageManager.allocate_image_name`
    * ``TkinterWeb.handle_node_replacement()`` - use :meth:`.WidgetManager.handle_node_replacement`
    * ``TkinterWeb.map_node()`` - use :meth:`.WidgetManager.map_node`
    * ``TkinterWeb.find_text()`` - use :meth:`.SearchManager.find_text`
    * ``TkinterWeb.send_onload()`` - use :meth:`.EventManager.send_onload`

    Version 4.12:

    * ``Htmlframe.register_JS_object()`` - use :meth:`.JSEngine.register`

    Version 4.14:

    * The configuration option ``default_style`` - use ``tkinterweb.utilities.DEFAULT_STYLE`` or the ``defaultstyle`` configuration option.
    * The configuration option ``dark_style`` - use ``tkinterweb.utilities.DARK_STYLE`` or the ``defaultstyle`` configuration option.
    * The configuration option ``about_page_background`` - use ``ttk.Style().configure("TFrame", background=)``.
    * The configuration option ``about_page_foreground`` - use ``ttk.Style().configure("TFrame", foreground=)``.

    Version 4.16:

    * :meth:`.HtmlFrame.get_caret_page_position` - use  :meth:`HtmlFrame.get_caret_position(return_element=False) <.HtmlFrame.get_caret_position>`
    * :meth:`.HtmlFrame.set_caret_page_position` - use :meth:`HtmlFrame.set_caret_position(index=) <.HtmlFrame.set_caret_position>`
    * :meth:`.HtmlFrame.get_selection_page_position` - use :meth:`HtmlFrame.get_selection_position(return_elements=False) <.HtmlFrame.get_selection_position>`
    * :meth:`.HtmlFrame.set_selection_page_position` - use :meth:`HtmlFrame.set_selection_position(start_index=, end_index=) <.HtmlFrame.set_selection_position>`

    Version 4.22:

    * :meth:`.HtmlFrame.insert_html` - use :meth:`.HtmlFrame.add_html`


.. dropdown:: Renamed

    Version 4.0:

    * ``HtmlFrame.get_currently_selected_text()`` -> :meth:`.HtmlFrame.get_selection`

    * ``TkwDocumentObjectModel`` -> :class:`.HTMLDocument`
    * ``HtmlElement`` -> :class:`.HTMLElement`

    * ``HtmlElement.style()`` -> :attr:`.HTMLElement.style`
    * ``HtmlElement.innerHTML()`` -> :attr:`.HTMLElement.innerHTML`
    * ``HtmlElement.textContent()`` -> :attr:`.HTMLElement.textContent`
    * ``HtmlElement.attributes()`` -> :attr:`.HTMLElement.attributes`
    * ``HtmlElement.tagName()`` -> :attr:`.HTMLElement.tagName`
    * ``HtmlElement.parentElement()`` -> :attr:`.HTMLElement.parentElement`
    * ``HtmlElement.children()`` -> :attr:`.HTMLElement.children`

    * 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".

.. dropdown:: Added

    Version 4.0:

    * :meth:`.HtmlFrame.clear_selection`
    * :meth:`.HtmlFrame.get_currently_hovered_element`
    * :meth:`.HtmlFrame.save_page`
    * :meth:`.HtmlFrame.snapshot_page`
    * :meth:`.HtmlFrame.show_error_page`
    * :meth:`.HtmlFrame.print_page`
    * :meth:`.HtmlFrame.screenshot_page`

    * :attr:`.HtmlFrame.base_url`
    * :attr:`.HtmlFrame.icon`
    * :attr:`.HtmlFrame.title`

    * :meth:`.HTMLElement.getElementById`
    * :meth:`.HTMLElement.getElementsByClassName`
    * :meth:`.HTMLElement.getElementsByName`
    * :meth:`.HTMLElement.getElementsByTagName`
    * :meth:`.HTMLElement.querySelector`
    * :meth:`.HTMLElement.querySelectorAll`
    * :meth:`.HTMLElement.scrollIntoView`

    * :class:`.CSSStyleDeclaration`
    * :attr:`.CSSStyleDeclaration.*` (any camel-case CSS property)
    * :attr:`.CSSStyleDeclaration.cssText`
    * :attr:`.CSSStyleDeclaration.length`
    * :attr:`.CSSStyleDeclaration.cssProperties`
    * :attr:`.CSSStyleDeclaration.cssInlineProperties`

    * :meth:`.TkinterWeb.enable_imagecache`
    * :meth:`.TkinterWeb.destroy_node`
    * :meth:`.TkinterWeb.get_node_properties`
    * :meth:`.TkinterWeb.override_node_properties`
    * :meth:`.TkinterWeb.update_tags`

    * ``utilities.DOWNLOADING_RESOURCE_EVENT`` (equivalent to ``<<DownloadingResource>>``)
    * ``utilities.DONE_LOADING_EVENT`` (equivalent to ``<<DoneLoading>>``)
    * ``utilities.URL_CHANGED_EVENT`` (equivalent to ``<<UrlChanged>>``)
    * ``utilities.ICON_CHANGED_EVENT`` (equivalent to ``<<IconChanged>>``)
    * ``utilities.TITLE_CHANGED_EVENT`` (equivalent to ``<<TitleChanged>>``)

    * Many new configuration options were added. See the :doc:`api/htmlframe` for a complete list.

    * The ``tkinterweb-full-page`` attribute can now be added to elements to make them the same height as the viewport. Use this to align content vertically. This has no effect when shrink is enabled.

    Version 4.1:

    * :meth:`.HtmlFrame.register_JS_object``

    * :attr:`.HTMLElement.widget` (updated again in version 4.2)
    * :attr:`.HTMLElement.value`
    * :attr:`.HTMLElement.checked`
    * :attr:`.HTMLElement.onchange`
    * :attr:`.HTMLElement.onload`
    * :attr:`.HTMLElement.onclick`
    * :attr:`.HTMLElement.oncontextmenu`
    * :attr:`.HTMLElement.ondblclick`
    * :attr:`.HTMLElement.onmousedown`
    * :attr:`.HTMLElement.onmouseenter`
    * :attr:`.HTMLElement.onmouseleave`
    * :attr:`.HTMLElement.onmousemove`
    * :attr:`.HTMLElement.onmouseout`
    * :attr:`.HTMLElement.onmouseover`
    * :attr:`.HTMLElement.onmouseup`

    * :attr:`.CSSStyleDeclaration.setProperty`
    * :attr:`.CSSStyleDeclaration.getPropertyValue`
    * :attr:`.CSSStyleDeclaration.removeProperty`

    * :meth:`.TkinterWeb.send_onload`

    * Added support for many JavaScript events.

    * 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.
    * The new configuration option ``javascript_enabled`` can be used to enable JavaScript support.

    Version 4.2:

    * :meth:`.HtmlFrame.widget_to_element`

    * :meth:`.TkinterWeb.replace_node_contents`
    * :meth:`.TkinterWeb.map_node`
    * :meth:`.TkinterWeb.replace_node_with_widget`
    * :meth:`.TkinterWeb.get_node_stacking`

    Version 4.4:

    * :class:`.HtmlParse`
    * :class:`.TkHtmlParsedURI`
    * :class:`.HTMLCollection`

    * :meth:`.HtmlFrame.insert_html`

    * :attr:`.HTMLElement.id`
    * :attr:`.HTMLElement.className`

    * :meth:`.TkinterWeb.override_node_CSS`
    * :meth:`.TkinterWeb.write`
    * :meth:`.TkinterWeb.get_child_text`
    * :meth:`.TkinterWeb.safe_tk_eval`
    * :meth:`.TkinterWeb.serialize_node`
    * :meth:`.TkinterWeb.serialize_node_style`

    * Added support for the HTML number input.

    * The new configuration option ``tkhtml_version`` can be used to choose a specific Tkhtml version to load.

    Version 4.5:

    * The new configuration option ``ssl_cafile`` can be used to provide a path to a CA Certificate file. See `bug #28 <https://github.com/Andereoo/TkinterWeb/issues/28>`_.

    Version 4.6:

    * The new configuration option ``request_timeout`` can be used to specify the number of seconds to wait before a request times out.

    Version 4.7:

    * The new ``<<DOMContentLoaded>>`` 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.

    Version 4.8:

    * :meth:`.HtmlFrame.get_page_text`
    * :meth:`.HtmlFrame.get_caret_position`
    * :meth:`.HtmlFrame.get_caret_page_position` (deprecated in version 4.16)
    * :meth:`.HtmlFrame.set_caret_position`
    * :meth:`.HtmlFrame.set_caret_page_position` (deprecated in version 4.16)
    * :meth:`.HtmlFrame.shift_caret_left`
    * :meth:`.HtmlFrame.shift_caret_right`
    * :meth:`.HtmlFrame.get_selection_position`
    * :meth:`.HtmlFrame.get_selection_page_position` (deprecated in version 4.16)
    * :meth:`.HtmlFrame.set_selection_position`
    * :meth:`.HtmlFrame.set_selection_page_position` (deprecated in version 4.16)

    * :attr:`.HTMLElement.previousSibling`
    * :attr:`.HTMLElement.nextSibling`

    * :attr:`.TkinterWeb.caret_manager`

    * :meth:`.TkinterWeb.update_selection`
    * :meth:`.TkinterWeb.tkhtml_offset_to_text_index`

    * :class:`.CaretManager`

    * The new configuration option ``caret_browsing_enabled`` can be used to enable or disable caret browsing mode.

    Version 4.9:

    * :meth:`.TkinterWeb.post_to_queue`
    * :meth:`.TkinterWeb.allocate_image_name`
    * :meth:`.TkinterWeb.check_images`

    Version 4.10:

    * :meth:`.HTMLElement.bind`
    * :meth:`.HTMLElement.unbind`

    * :attr:`.TkinterWeb.event_manager`

    * :class:`.EventManager`

    * You can now set ``allowstyling="deep"`` on elements with embedded widgets to also style their subwidgets.

    Version 4.11:

    * :meth:`.HtmlFrame.unbind`

    * :class:`.HtmlText`

    * :attr:`.TkinterWeb.selection_manager`
    * :attr:`.TkinterWeb.widget_manager`
    * :attr:`.TkinterWeb.search_manager`
    * :attr:`.TkinterWeb.script_manager`
    * :attr:`.TkinterWeb.style_manager`
    * :attr:`.TkinterWeb.image_manager`
    * :attr:`.TkinterWeb.object_manager`
    * :attr:`.TkinterWeb.form_manager`
    * :attr:`.TkinterWeb.node_manager`

    * :class:`.SelectionManager`
    * :class:`.WidgetManager`

    Version 4.12:

    * :class:`.JSEngine`

    Version 4.13:

    * :meth:`.TkinterWeb.get_node_replacement`

    Version 4.14:

    * :attr:`.HTMLElement.innerText`

    * The new configuration option ``request_func`` can be used to set a custom script to use to download resources.
    * The new configuration option ``defaultstyle`` can be used to set the default stylesheet to use when parsing HTML.

    Version 4.15:

    * :meth:`.HtmlFrame.add_css` now accepts the additional parameter ``priority``. 
    * :meth:`.CaretManager.shift_left`, :meth:`.CaretManager.shift_right`, :meth:`.CaretManager.shift_up`, :meth:`.CaretManager.shift_down`, and :meth:`.CaretManager.shift_update` now accept the additional parameter ``update``.

    * The :class:`.HtmlText` widget now supports the ``background``, ``foreground``, ``bg``, and ``fg`` keywords.

    Version 4.16:

    * :meth:`.HtmlFrame.get_caret_position` now accepts the additional parameter ``return_element``. 
    * :meth:`.HtmlFrame.get_selection_position` now accepts the additional parameter ``return_elements``. 

    * :attr:`.HtmlText.insert`
    * :attr:`.HtmlText.delete`

    * The :class:`.HtmlText` widget now supports the ``state`` keyword.

    * Added introductory support for :class:`.HtmlLabel` and :class:`.HtmlFrame(shrink=True)` widget resizing. This feature is experimental and may change at any time. Set ``HtmlFrame.unshrink = True`` to enable it and let me know how it works!

    Version 4.17:

    * The new configuration option ``textwrap`` can be used to enable or disable text wrapping. In general, text wrapping should be disabled when shrink is enabled, and should be enabled when shrink is disabled. This is the default behaviour. This is only partially supported in Tkhtml 3.0; make sure you have the `TkinterWeb-Tkhtml-Extras <https://pypi.org/project/tkinterweb-tkhtml-extras/>`_ package installed and up-to-date.

    Version 4.18:

    * Added basic support for most HTML5 elements in the corresponding `TkinterWeb-Tkhtml-Extras <https://pypi.org/project/tkinterweb-tkhtml-extras/>`_ release (version 1.3.0)
    * Added support for the HTML ``<details>``, ``<summary>``, and ``<q>`` tags. 

    Version 4.19:

    * :meth:`.HtmlFrame.generate_style_report`
    * :attr:`.TkinterWeb.tkhtml_default_style`
    * :attr:`.TkinterWeb.images`
    * :attr:`.TkinterWeb.style_report`
    * :meth:`.TkinterWeb.decode_uri`
    * :meth:`.TkinterWeb.encode_uri`
    * :meth:`.TkinterWeb.escape_uri`

    * Added support for the ``media`` attribute of ``<link>`` elements. Ensure our experimental Tkhtml release is installed.
    * Added support for the HTML ``<progress>`` tag. Ensure `TkinterWeb-Tkhtml-Extras <https://pypi.org/project/tkinterweb-tkhtml-extras/>`_ is installed.
    * The new configuration option ``javascript_backend`` can be used to evaluate ``<script>`` elements and JavaScript events as Python code.

    Version 4.20:

    * :meth:`.HTMLDocument.write`
    * :meth:`.HTMLElement.removeAttribute`

    Version 4.21:

    * :meth:`.HtmlFrame.reload`

    Version 4.22:
    
    * :meth:`.HtmlFrame.add_html` now accepts the new parameter ``index``.

    Version 4.23:

    * :meth:`.HtmlFrame.snapshot_page` now accepts the new parameter ``include_head``.

    Version 4.24:
    
    * :meth:`.HtmlFrame.load_form_data` now accepts the new parameter ``force``.

.. dropdown:: Changed/Fixed

    Version 4.0:

    * :meth:`.HtmlFrame.configure`, :meth:`.HtmlFrame.config`, :meth:`.HtmlFrame.cget`, and :meth:`.HtmlFrame.__init__` now support more configuration options.
    * :meth:`.HtmlFrame.load_website`, :meth:`.HtmlFrame.load_file`, and :meth:`.HtmlFrame.load_url` no longer accept the ``insecure`` parameter. use ``HTMLElement.configure(insecure=)``.

    * Enabling/disabling caches now enables/disables the Tkhtml image cache.
    * Threading now cannot be enabled if the Tcl/Tk build does not support it.

    * :meth:`.HTMLElement.remove` now raises a :py:class:`tkinter.TclError` when invoked on ``<html>`` or ``<body>`` elements, which previously caused segmentation faults.
    * :attr:`.HTMLElement.innerHTML` and :attr:`.HTMLElement.textContent` now raise a :py:class:`tkinter.TclError` when invoked on ``<html>`` elements, which previously caused segmentation faults.

    * Shorthand CSS properties can now be set and returned after the document is loaded.
    
    * The ability to style color selector inputs was improved.
    * The ability to access form elements has improved.
    * Text elements now emit the ``<<Modified>>`` event *after* the content updates.
    * The TkinterWeb demo and some of the built-in pages have been updated. Many internal methods and variables have been renamed, removed, or modified.

    Version 4.1:

    * :meth:`.HtmlFrame.screenshot_page` is now partially supported on Windows and now accepts the additional parameter ``show``. 
    * The default selection and find text colors are less abrupt.

    Version 4.2:

    * Widgets embedded in the document can now be removed without removing the containing element. 

    Version 4.3:

    * Prebuilt Tkhtml binaries have been split off into a new package, `TkinterWeb-Tkhtml <https://pypi.org/project/tkinterweb-tkhtml/>`_. This has been done to work towards `bug #52 <https://github.com/Andereoo/TkinterWeb/issues/52>`_ and reduce the download size of the TkinterWeb package when updating.

    Version 4.4:

    * :meth:`.HtmlFrame.add_html` is now accepts the additional parameter ``return_element``. 
    * It is now only possible to enable experimental mode if an experimental Tkhtml release is detected.
    * Some experimental HTML features were enabled in Windows and Linux. ``border-radius`` is now supported!

    Version 4.5:

    * Periods are now supported in url fragments. See `bug #143 <https://github.com/Andereoo/TkinterWeb/issues/143>`_ .
    * 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.

    Version 4.6:

    * 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.
    * ``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.
    * 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.
    * Loading local files with a query string in the url will no longer raise an error.
    * Fixed :meth:`.HTMLDocument.querySelector`.

    Version 4.7:

    * Fixed flickering when moving the mouse over scrollbars in ``<iframe>`` elements.
    * ``bind()`` calls to the :meth:`.HtmlFrame.bind` respect requests to bind ``<Enter>`` and ``<Leave>``. All other events are still bound to the associated :class:`~tkinterweb.TkinterWeb` instance. Keep in mind that overriding the default bindings to ``<Enter>`` and ``<Leave>`` may cause unwanted side effects. 

    Version 4.8:

    * All HTML widgets now bind to ``<Up>``, ``<Down>``, ``<Left>``, ``<Right>``, ``<Prior>``, ``<Next>``, ``<Home>``, and ``<End>`` by default.
    * Fixed :meth:`.HTMLElement.parentElement`.

    Version 4.9:

    * TkinterWeb is now thread-safe when loading resources. All callbacks now will run on the main thread.
    * Fixed loading of data urls.
    * Local files will now load regardless of the number of slashes before the path.
    * Fixed some dark mode and image inversion bugs.

    Version 4.10:

    * Binding button presses and motion events to the widget no longer removes internal bindings.
    * Setting ``html.maximum_thread_count = 0`` no longer disables threading. Use ``html.threading_enabled = False``.
    * :py:mod:`PIL` is now an optional dependency. I also recommend installing the new `TkinterWeb-Tkhtml-Extras <https://pypi.org/project/tkinterweb-tkhtml-extras/>`_ package.
    * The :attr:`.HTMLElement.widget` property now returns a Tk widget when used on ``<input>``, ``<textarea>``, ``<select>``, ``<iframe>``, and some ``<object>`` elements.
    * Fixed scrollbar flashes when the widget opens.
    * DOM objects now provide more useful information when printed.
    * By default, scrolling on embedded widgets now scrolls the page if the embedded widget or subwidgets do not bind to the mousewheel.
    * If dark theme is enabled, HTML code passed to the configuration option ``dark_style`` will now be automatically appended onto the code set by ``default_style``.
    * Plain text is no longer rendered as a blank page.
    * The event queue now only runs when threading is enabled.
    * Modifying the selection when selection is disabled now raises an error.
    * Modifying the caret position when caret browsing is disabled now raises an error.
    * Local file loading now happens on the main thread.
    * Fixed a fatal scrollbar error when loading TkinterWeb on Tk 8.5 on MacOS.
    * Fixed a fatal binding error when loading TkinterWeb on MacOS.
    * Many internal changes were made in this release. If you notice any bugs, please report them.

    Version 4.11:

    * Fixed some minor bugs.
    * JavaScript events no longer fire when events are disabled.
    * The :class:`.TkinterWeb` widget was restructured in this release. If you notice any bugs, please report them.

    Version 4.12:

    * Fixed more minor bugs.
    * Side-scrolling is now supported.

    Version 4.13:

    * Fixed more minor bugs, including a segfault when inserting a widget into the page's root element.
    * ``grid_propagate(0)`` and ``pack_propagate(0)`` no longer have any effect on the widget. Requested width and height will now always be respected.

    Version 4.14:

    * Fixed more minor bugs.
    * The :class:`.HtmlLabel` widget now automatically matches the ttk style.
    * Alternate text for broken images is now displayed natively through Tkhtml.

    Version 4.15:

    * Fixed more minor bugs.
    * Improved some error messages.
    * Improved code autocompletion.
    * All HTML widgets now bind to ``<Ctrl-A>`` by default.
    * Equality checking between :class:`.HTMLElement` objects is now fully supported.
    * The :class:`.HtmlText` widget is now editable out-of-the-box!
    * The :class:`.HtmlLabel` widget now uses the ``TLabel`` style by default instead of ``TFrame``.

    Version 4.16:

    * Fixed more minor bugs.
    * :meth:`.HtmlFrame.set_caret_position` now sets the caret relative to the document text when no element is provided. 
    * :meth:`.HtmlFrame.set_selection_position` now sets the selection relative to the document text when no elements are provided. 
    * A ``NotImplementedError`` will now raise when changing some settings via ``HtmlFrame.configure()``. This occurs on settings that have no effect after the widget loads and on the shrink value, which has been causing segfaults when changed after the widget loads. If you absolutely need to change the shrink value on the fly use ``HtmlFrame.html.configure()``
    * Text wrapping has been disabled by default in the :class:`.HtmlLabel` and :class:`.HtmlFrame(shrink=True)` widgets. 
    
    Version 4.17:
    
    * Fixed some :class:`.HtmlFrame` shrink regressions. See `bug #147 <https://github.com/Andereoo/TkinterWeb/issues/147>`_.
    * Fixed some image loading and ``<iframe>`` scrolling regressions.
    * Fixed a bug where stopping a page load prevented the page from loading again when caches were enabled.
    * ``<style>`` tags and local files are now always evaluated in the main thread.
    * The configuration options ``horizontal_scrollbar`` and ``vertical_scrollbar`` now accept another option, ``"dynamic"``. This behaves like ``"auto"``, with the difference that scrollbars are always hidden in :class:`.HtmlLabel` and :class:`.HtmlFrame(shrink=True)` widgets. This is the new default for vertical scrollbars.

    Version 4.18:

    * Triple-clicking on text now highlights all text in the line, even if multiple inline elements are present.
    * Fixed some bugs that arose when stopping a page load and introduced some minor optimizations.

    Version 4.19:
    
    * Fixed some bugs.
    * Callbacks now accept ``None`` as a valid value.
    * Permitted values for configuration option are now more tightly restricted.
    * :attr:`.CSSStyleDeclaration.cssText` can now be set.

    Version 4.20:

    * Fixed some regressions and bugs.
    * The :class:`.HtmlText` widget now emits the ``<<Modified>>`` event when the user types in it.

    Version 4.21:

    * Fixed a bug where ``<style>`` and ``<script>`` elements were left out of the output from :meth:`.HtmlFrame.save_page`. The output of this method is now the document's original HTML and is unaffected by JavaScript or DOM changes.
    * Fixed a bug where adding :class:`.HtmlLabel` widgets causes the app to open in the wrong part of the screen.
    * :attr:`.HtmlFrame.current_url` no longer returns the working directory when loading plain HTML code.

    Version 4.23:

    * Whitespace is now automatically stripped from the page's title.
    * Improved :meth:`.HtmlFrame.snapshot_page` output formatting and accuracy.
    * In an effort to reduce the widget's memory footprint, all HTML widgets no longer remember the original HTML code they are displaying:

        * The output of :meth:`.HtmlFrame.save_page` is now the document's original HTML only when the page has been cached. This is the case when caching is enabled and a url is loaded. Otherwise, :meth:`.HtmlFrame.snapshot_page` is used, with the contents of the ``<head>`` tag included if the widget is still loading.
        * :meth:`.HtmlFrame.reload` now only reloads pages loaded from a url.
        
        The original intent of both both methods is to be used when a url is loaded, and in an ideal world caching should always be enabled.

    * :meth:`.HtmlFrame.reload` and :meth:`.HtmlFrame.save_page` now fully support pages generated through form submissions.
    * The ``<<UrlChanged>>``/:py:attr:`utilities.URL_CHANGED_EVENT` event now also fires when a url is navigated to.
    * The page cache backend was overhauled in this release. Please file a bug report if you notice any issues. 
    * Loading cached stylesheets, scripts, and images no longer spawns new threads. This fixes some bugs when loading cached documents and improves load times on some pages.
    * Disabling the cache now also clears it.

    Version 4.24:

    * Stopped windows from teleporting across the galaxy or disappearing altogether when changing :attr:`.HTMLElement.innerHTML` and :attr:`.HTMLElement.textContent` before the app opens.
    * Fixed a minor threading bug in :meth:`.HtmlFrame.load_form_data`.
    * Fixed `bug #150 <https://github.com/Andereoo/TkinterWeb/issues/150>`_ .

    Version 4.25:

    * In an effort to closer match stock Tk widgets, debug message behaviour has been adjusted:
       
        * Debug messages are now disabled by default. Set the configuration option ``messages_enabled=True`` to enable them.
        * In an effort to keep backwards compatibility, if the configuration option ``message_func`` is set, the value of ``messages_enabled`` is ignored.

    * Auto-scrolling behaviour when searching the page for text been improved.
    * Fixed a regression impacting :attr:`.HTMLElement.textContent`.

-------------------

Please report bugs or request new features on the `issues page <https://github.com/Andereoo/TkinterWeb/issues>`_.


================================================
FILE: docs/source/usage.rst
================================================
Getting Started
===============

.. note::
    The API changed significantly in version 4. See :doc:`the changelog <upgrading>` for details.

Installation
------------

To use TkinterWeb, first install it using pip:

.. code-block:: console

   $ pip install tkinterweb[recommended]

.. tip::
    
    You can also choose from the following extras:

    .. code-block:: console

        $ pip install tkinterweb[html,images,svg,javascript,requests]
    
    Run ``pip install tkinterweb[full]`` to install all optional dependencies or ``pip install tkinterweb`` to install the bare minimum.

Run the TkinterWeb demo to see if it worked!

>>> from tkinterweb import Demo
>>> Demo()

.. image:: ../../images/tkinterweb-demo.png

TkinterWeb requires Tkinter, `TkinterWeb-Tkhtml <https://pypi.org/project/tkinterweb-tkhtml/>`_, `PIL <https://pillow.readthedocs.io/>`_, and `PIL.ImageTk <https://pillow.readthedocs.io/en/stable/reference/ImageTk.html>`_. All dependencies should be installed when installing TkinterWeb the recomended way, but on some systems `PIL.ImageTk <https://pillow.readthedocs.io/en/stable/reference/ImageTk.html>`_ may need to be installed seperately in order to load most image types.

Getting started
----------------

TkinterWeb is very easy to use! Here is an example:

.. code-block:: python

    import tkinter as tk
    from tkinterweb import HtmlFrame # import the HtmlFrame widget
    
    root = tk.Tk() # create the Tkinter window
    
    yourhtmlframe = HtmlFrame(root, messages_enabled=True) # create the HtmlFrame widget
    yourhtmlframe.load_html("<h1>Hello, World!</h1>") # load some HTML code
    yourhtmlframe.pack(fill="both", expand=True) # attach the HtmlFrame widget to the window
    
    root.mainloop()

You can also use :meth:`~tkinterweb.HtmlFrame.load_website`, :meth:`~tkinterweb.HtmlFrame.load_file`, or :meth:`~tkinterweb.HtmlFrame.load_url` to load webpages.

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! See below for more tips and tricks.

.. tip::
    Use the :class:`~tkinterweb.HtmlLabel` widget for an HTML-based label widget and the :class:`~tkinterweb.HtmlText` widget for an HTML-based text widget.
    
Tips and tricks
---------------

Creating bindings
~~~~~~~~~~~~~~~~~

Like any other Tkinter widget, mouse and keyboard events can be bound to the :class:`~tkinterweb.HtmlFrame` widget.

The following is an example of the usage of bingings to show a menu:

.. code-block:: python

    def on_right_click(event):
        # Get the element under the mouse and its url
        element = yourhtmlframe.get_currently_hovered_element()
        url = element.getAttribute("href")

        if url:
            # Resolve the url to ensure it is a full url
            url = yourhtmlframe.resolve_url(url)

            # Create the menu and add a button with the url
            menu = tk.Menu(root, tearoff=0)
            menu.add_command(label="Open %s" % url, 
                command=lambda url=url: yourhtmlframe.load_url(url))

            # Show the menu
            menu.tk_popup(event.x_root, event.y_root, 0)

    yourhtmlframe.bind("<Button-3>", on_right_click)

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.

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.

.. tip::
    Since version 4.10, you can also bind to a specific HTML element! See :ref:`binding-to-an-element` for more details.

Changing the title
~~~~~~~~~~~~~~~~~~

To change the title of the window every time the title of a website changes, use the following:

.. code-block:: python

    def change_title(event):
        root.title(yourhtmlframe.title) # change the title
        
    yourhtmlframe.bind("<<TitleChanged>>", change_title)

Similarily, the ``<<IconChanged>>`` event fires when the website's icon changes.

Handling url changes
~~~~~~~~~~~~~~~~~~~~

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 ``<<UrlChanged>>``:

.. code-block:: python

    def url_changed(event):
        updated_url = yourhtmlframe.current_url
        ### Do stuff, such as change the content of an address bar
        
    yourhtmlframe.bind("<<UrlChanged>>", url_changed)

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.


Searching the page
~~~~~~~~~~~~~~~~~~

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:

.. code-block:: python

    number_of_matches = yourhtmlframe.find_text("python")

Or, to select the second match found:

.. code-block:: python

    number_of_matches = yourhtmlframe.find_text("python", 2)

Refer to the API reference for more information.

.. tip::
    
    Check out `bug 18 <https://github.com/Andereoo/TkinterWeb/issues/18#issuecomment-881649007>`_ or the `sample web browser <https://github.com/Andereoo/TkinterWeb/blob/main/examples/TkinterWebBrowser.py>`_ for a sample find bar!

Done loading?
~~~~~~~~~~~~~

Website loading is performed asynchronously. When loading a website, you can bind to the ``<<DoneLoading>>`` event, which fires when the document has finished loading.

If you bind to ``<<DoneLoading>>`` to update GUI state (for example, switching a 'Stop' button to 'Refresh'), it is generally recommended to also bind to the ``<<DownloadingResource>>`` event to handle the opposite case. Without this, the document may report that it has finished loading while additional resources (such as images, scripts, or stylesheets) are still being downloaded.

When loading raw HTML or local files, the page loads synchronously and can be manipulated immediately.

Stop loading
~~~~~~~~~~~~

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.

Handling link clicks
~~~~~~~~~~~~~~~~~~~~

Link clicks can also be easily handled. By default, when a link is clicked, it will be automatically loaded.
To, for example, run some code before loading the new website, use the following: 

.. code-block:: python

    yourhtmlframe = HtmlFrame(master, on_link_click=load_new_page)
    
    def load_new_page(url):
        ### Do stuff
        yourhtmlframe.load_url(url) # load the new website    

Similarily, :attr:`on_form_submit` can be used to override the default form submission handlers.

Zooming
~~~~~~~

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: 

.. code-block:: python

    yourhtmlframe = HtmlFrame(master, zoom=2)
    ### Or yourhtmlframe.configure(zoom=2)
    ### Or yourhtmlframe["zoom"] = 2

To scale only the text, use ``fontscale=2`` instead.

Embedding a widget
~~~~~~~~~~~~~~~~~~

There are many ways to embed widgets in an :class:`~tkinterweb.HtmlFrame` widget. One way is to use ``<object>`` elements:

.. code-block:: python

    yourcanvas = tkinter.Canvas(yourhtmlframe)
    yourhtmlframe.load_html(f"<p>This is a canvas!</p><object data="{yourcanvas}"></object>")

Refer to :doc:`geometry` for more information.

Manipulating the DOM
~~~~~~~~~~~~~~~~~~~~

Refer to :doc:`dom` (new in version 3.25).

Using JavaScript
~~~~~~~~~~~~~~~~

Refer to :doc:`javascript` (new in version 4.1).

Making the page editable
~~~~~~~~~~~~~~~~~~~~~~~~

Refer to :doc:`caret` (new in version 4.8).

Shrinking a widget to match its contents
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Refer to :doc:`shrink`.

Using dark mode
~~~~~~~~~~~~~~~

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.

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.

Refresh the page for these features to take full effect. This features may cause hangs or crashes on more complex websites.

-------------------

See the :doc:`api/htmlframe` for a complete list of available commands.


================================================
FILE: examples/TkinterWebBrowser.py
================================================
"""
A proof-of-concept web browser using TkinterWeb

Note that TkinterWeb is not necessarily intended to be a full-blown modern web browser
These already exist and are generally resource-hungry and not highly integratable with Tkinter
Being based on Tkhtml, TkinterWeb is intended to be fast, lightweight, and highly integrated with Tkinter while providing far more control over layouts and styling than is feasible than Tkinter
TkinterWeb displays older or simpler websites well but may be found lacking on more modern websites

This code was created for testing TkinterWeb and is a bit of a mess, but nonetheless is a great example of some of the things that can be done with the software, including:
 - loading pages
 - searching pages
 - embedding Tkinter widgets
 - managing input elements
 - embedding Python code
 - manipulating the DOM
 - making round buttons
 - and more
 
Copyright (c) 2026 Andrew Clarke
"""

import os
import sys

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.realpath(__file__))))


import tkinter as tk
from tkinter import filedialog
from tkinter import ttk

from tkinterweb import HtmlFrame, Notebook, __version__
from tkinterweb.utilities import BUILTIN_PAGES, DONE_LOADING_EVENT, URL_CHANGED_EVENT, TITLE_CHANGED_EVENT, DOWNLOADING_RESOURCE_EVENT, DOM_CONTENT_LOADED_EVENT
from tkinterweb.subwidgets import ScrolledTextBox, FormEntry

import os


if os.name == "nt":
	from ctypes import windll
	windll.shcore.SetProcessDpiAwareness(1)

version = []
for letter in __version__.split("."):
    version.append(int(letter))
if tuple(version) < (4, 19, 0):
    raise RuntimeError("This demo needs TkinterWeb version 4.19.0 or higher.")

if len(sys.argv) > 1:
    NEW_TAB = sys.argv[1]
else:
    NEW_TAB = "about:tkinterweb"


def check_url(entry):
    url = entry.get()
    if not any((url.startswith("file:"), url.startswith("http:"), url.startswith("about:"), url.startswith("view-source:"), url.startswith("https:"), url.startswith("data:"))):
        if os.path.exists(url):
            url = f"file://{url}"
        else:
            url = "http://{}".format(url)
    return url


class HTMLPlayground(ttk.PanedWindow):
    def __init__(self, master):
        ttk.PanedWindow.__init__(self, master, orient=tk.HORIZONTAL)

        self.master = master

        text_frame = ttk.Frame(self)
        html_frame = ttk.Frame(self)
        text_frame._scroll = lambda *a: None
        text_frame._xscroll = lambda *a: None
        text_frame._scroll_x11 = lambda *a: None
        text_frame._xscroll_x11 = lambda *a: None
        self.textarea = textarea = ScrolledTextBox(text_frame, content="Type HTML code here", padx=8, pady=8, wrap=tk.NONE)
        self.iframe = iframe = HtmlFrame(html_frame,
            messages_enabled=False,
            message_func = master.html.message_func,
            images_enabled = master.html.images_enabled,
            forms_enabled = master.html.forms_enabled,
            objects_enabled = master.html.objects_enabled,
            ignore_invalid_images = master.html.ignore_invalid_images,
            crash_prevention_enabled = master.html.crash_prevention_enabled,
            dark_theme_enabled = master.html.dark_theme_enabled,
            image_inversion_enabled = master.html.image_inversion_enabled,
            caches_enabled = master.html.caches_enabled,
            threading_enabled = master.html.threading_enabled,
            image_alternate_text_enabled = master.html.image_alternate_text_enabled,
            selection_enabled = master.html.selection_enabled,
            find_match_highlight_color = master.html.find_match_highlight_color,
            find_match_text_color = master.html.find_match_text_color,
            find_current_highlight_color = master.html.find_current_highlight_color,
            find_current_text_color = master.html.find_current_text_color,
            selected_text_highlight_color = master.html.selected_text_highlight_color,
            selected_text_color = master.html.selected_text_color,
            caret_browsing_enabled = master.html.caret_browsing_enabled)
        iframe.html.text_mode = False
        iframe.load_html("HTML output shows here")
        iframe.config(messages_enabled=True)

        self.urlbar = urlbar = FormEntry(text_frame, placeholder="https://")
        urlbar.bind("<Return>", self._load_url)
        go_button = ttk.Button(text_frame, text="Go", cursor="hand2", command=self._load_url)

        self.base_title = "HTML Playground"
        master.add_html(f"<title>{self.base_title}</title>")

        # Make a round button
        # Quite unnecessary, but very fun
        s = ttk.Style()
        run_button = HtmlFrame(textarea.tbox, messages_enabled=False, shrink=True, javascript_enabled=True, javascript_backend="python", selection_enabled=False)
        run_button.load_html(f"""<body>
                                <style>
                                    body{{margin:0;background-color:white}}
                                    button{{color:{s.lookup("TButton", "foreground")};background-color:{s.lookup("TButton", "background")};border-radius:5px;padding:6px 11px;border-width:0}}
                                    button:hover{{background-color:{s.lookup("TButton", "background", state=("active",))}}}
                                    button:active{{background-color:{s.lookup("TButton", "background")}}}
                                </style>
                                <button onclick='update_iframe()'>&gt;</button>
                                </body>""")
        run_button.javascript.register("update_iframe", self._update_iframe)
        run_button.place(relx=1.0, rely=1.0, anchor="se")

        self.iframe.bind(DOM_CONTENT_LOADED_EVENT, self._on_page_loaded)
        self.iframe.bind(TITLE_CHANGED_EVENT, self._on_title_change)

        text_frame.grid_rowconfigure(1, weight=1)
        text_frame.grid_columnconfigure(0, weight=1)
        urlbar.grid(row=0, column=0, sticky="nsew", padx=(5,0))
        go_button.grid(row=0, column=1, padx=(5,0))
        textarea.grid(row=1, column=0, columnspan=2, sticky="nsew", pady=(5,0), padx=(5,0))
        iframe.pack(expand=True, fill="both", padx=(0,5))

        self.add(text_frame)
        self.add(html_frame)

    def _load_url(self, event=None):
        url = check_url(self.urlbar)
        self.iframe.load_url(url)

    def _on_title_change(self, event, addendum=None):
        if addendum is None:
            addendum = f" - {self.iframe.title}"
        self.master.add_html(f"<title>{self.base_title}{addendum}</title>")

    def _on_page_loaded(self, event):
        html = self.iframe.save_page()
        self.textarea.delete("0.0", "end")
        self.textarea.insert("1.0", html)

    def _update_iframe(self, event=None):
        self.iframe.unbind("<<DOMContentLoaded>>")
        self.master.add_html(f"<title>{self.base_title}</title>")
        self.iframe.load_html(self.textarea.get(), self.iframe.base_url)
        self.iframe.bind("<<DOMContentLoaded>>", self._on_page_loaded)


HTML_TEST_PAGE = """
<head>
    <style>
        html, body {{margin:0;overflow: hidden}}
        object {{width:100%}}
    </style>
</head>
<body><object tkinterweb-full-page data={}></object></body>
</html>"""


class Page(ttk.Frame):
    def __init__(self, master, *args, **kwargs):
        ttk.Frame.__init__(self, master, *args, **kwargs)

        self.master = master
        self.back_history = []
        self.forward_history = []

        self.style = ttk.Style(self)
        self.style.theme_use("default")

        topbar = ttk.Frame(self)
        self.bottombar = bottombar = ttk.Frame(self)
        self.findbar = findbar = ttk.Frame(self)

        self.html_playground = None
        
        self.linklabel = linklabel = ttk.Label(bottombar, text="Welcome to TkinterWeb!", cursor="hand2")

        self.backbutton = backbutton = ttk.Button(topbar, text="Back", command=self.back, state="disabled")
        self.forwardbutton = forwardbutton = ttk.Button(topbar, text="Forward", command=self.forward, state="disabled")
        self.reloadbutton = reloadbutton = ttk.Button(topbar, text="Reload", command=self.reload, cursor="hand2")
        self.urlbar = urlbar = ttk.Entry(topbar, width=100)
        newbutton = ttk.Button(topbar, text="New tab", command=self.open_new_tab, cursor="hand2")
        closebutton = ttk.Button(topbar, text="Close", command=self.close_current_tab, cursor="hand2")
        self.findbutton = findbutton = ttk.Button(topbar, text="Find",  command=self.open_findbar, cursor="hand2")
        self.settingsbutton = settingsbutton = ttk.Button(topbar, text="Settings", command=self.open_sidebar, cursor="hand2")

        self.message_box = tk.Text(self, height=8)

        self.find_select_num = 1
        self.find_match_num = 0
        
        self.findbox_var = findbox_var = tk.StringVar()
        self.find_box = find_box = ttk.Entry(findbar, textvariable=findbox_var)
        self.find_previous = find_previous = ttk.Button(findbar, text="Prevous", command=self.previous_and_find, state="disabled")
        self.find_next = find_next = ttk.Button(findbar, text="Next", command=self.next_and_find, state="disabled")
        self.ignore_case_var = ignore_case_var = tk.IntVar(value=1)
        ignore_case = ttk.Checkbutton(findbar, text="Ignore Cases", variable=ignore_case_var, command=self.search_in_page, cursor="hand2")
        self.highlight_all_var = highlight_all_var = tk.IntVar(value=1)
        highlight_all = ttk.Checkbutton(findbar, text="Highlight All", variable=highlight_all_var, command=lambda change=False: self.search_in_page(change=change), cursor="hand2")
        self.find_bar_caption = find_bar_caption = ttk.Label(findbar, text="")
        find_close = ttk.Button(findbar, text="Close", command=self.open_findbar, cursor="hand2")

        self.frame = frame = HtmlFrame(self, message_func=self.add_message, on_link_click=self.link_click, on_form_submit=self.form_submit)
        
        self.sidebar = sidebar = HtmlFrame(frame, width=250, fontscale=0.8, selection_enabled=False, javascript_enabled=True, javascript_backend="python")

        self.images_var = images_var = tk.IntVar(value=self.frame["images_enabled"])
        images_enabled = ttk.Checkbutton(sidebar, text="Enable images", variable=images_var, command=self.toggle_images)
        self.styles_var = styles_var = tk.IntVar(value=self.frame["stylesheets_enabled"])
        styles_enabled = ttk.Checkbutton(sidebar, text="Enable stylesheets", variable=styles_var, command=self.toggle_styles)
        self.forms_var = forms_var = tk.IntVar(value=self.frame["forms_enabled"])
        forms_enabled = ttk.Checkbutton(sidebar, text="Enable forms", variable=forms_var, command=self.toggle_forms)
        self.objects_var = objects_var = tk.IntVar(value=self.frame["objects_enabled"])
        objects_enabled = ttk.Checkbutton(sidebar, text="Enable objects", variable=objects_var, command=self.toggle_objects)
        self.caches_var = caches_var = tk.IntVar(value=self.frame["caches_enabled"])
        caches_enabled = ttk.Checkbutton(sidebar, text="Enable caches", variable=caches_var, command=self.toggle_caches)
        self.crashes_var = crashes_var = tk.IntVar(value=self.frame["crash_prevention_enabled"])
        emojis_enabled = ttk.Checkbutton(sidebar, text="Enable crash prevention", variable=crashes_var, command=self.toggle_emojis)
        self.threads_var = threads_var = tk.IntVar(value=self.frame["threading_enabled"])
        threads_enabled = ttk.Checkbutton(sidebar, text="Enable threading", variable=threads_var, command=self.toggle_threads)
        self.invert_page_var = invert_page_var = tk.IntVar(value=self.frame["dark_theme_enabled"])
        self.js_var = js_var = tk.IntVar(value=self.frame["javascript_enabled"])
        js_enabled = ttk.Checkbutton(sidebar, text="Enable javascript", variable=js_var, command=self.toggle_js)
        invert_page_enabled = ttk.Checkbutton(sidebar, text="Dark theme", variable=invert_page_var, command=self.toggle_theme)
        self.invert_images_var = invert_images_var = tk.IntVar(value=self.frame["image_inversion_enabled"])
        invert_images_enabled = ttk.Checkbutton(sidebar, text="Image inverter", variable=invert_images_var, command=self.toggle_inverter)
        self.selection_var = selection_var = tk.IntVar(value=self.frame["selection_enabled"])
        selection_enabled = ttk.Checkbutton(sidebar, text="Text selection enabled", variable=selection_var, command=self.toggle_selection)
        self.caret_browsing_var = caret_browsing_var = tk.IntVar(value=self.frame["caret_browsing_enabled"])
        caret_browsing_enabled = ttk.Checkbutton(sidebar, text="Caret browsing enabled", variable=caret_browsing_var, command=self.toggle_caret_browsing)
        
        self.view_source_button = view_source_button = ttk.Button(sidebar, text="View page source", command=self.view_source)
        about_button = ttk.Button(sidebar, text="About TkinterWeb", command=lambda: self.open_new_tab("about:tkinterweb"))
        html_button = ttk.Button(sidebar, text="HTML playground", command=lambda: self.open_new_tab("about:html"))
        style_button = ttk.Button(sidebar, text="Get style report", command=self.style_report)
        
        frame.bind(TITLE_CHANGED_EVENT, self.change_title)
        frame.bind(URL_CHANGED_EVENT, self.url_change)
        frame.bind(DONE_LOADING_EVENT, self.done_loading)
        frame.bind(DOWNLOADING_RESOURCE_EVENT, self.on_downloading)

        for i in {"<Up>", "<Down>", "<Left>", "<Right>", "<Prior>", "<Next>", "<Home>", "<End>"}:
            frame.unbind(i)

        linklabel.bind("<Button-1>", self.hide_messsage_box)
        urlbar.bind("<Return>", self.load_site)
        #frame.bind("<Motion>", self.on_motion)
        frame.bind("<Leave>", lambda event: linklabel.config(text="Done"))

        self.columnconfigure(0, weight=1)
        self.rowconfigure(1, weight=1)
        topbar.grid(column=0, row=0, sticky="ew")
        frame.grid(column=0, row=1, sticky="nsew")
        bottombar.grid(column=0, row=4, sticky="ew")

        self.sidebar.javascript.register("frame", frame)

        self.sidebar.load_html(f"""<html>
  <body>
    <style>
      body p, span {{ margin-top: 5px; margin-bottom: 5px; cursor: default; }}
      object {{ width: 100%; cursor: pointer; }}
      input[type="color"] {{ height: 15px; width: 30px; border: 1px solid black; padding: 0; margin: 5px; background-color: transparent; }}
      label {{ margin-left: 5px; }}
    </style>

    <object allowscrolling data={images_enabled}></object><br>
    <object allowscrolling data={styles_enabled}></object><br>
    <object allowscrolling data={forms_enabled}></object><br>
    <object allowscrolling data={objects_enabled}></object><br>
    <object allowscrolling data={caches_enabled}></object><br>
    <object allowscrolling data={emojis_enabled}></object><br>
    <object allowscrolling data={threads_enabled}></object><br>
    <object allowscrolling data={js_enabled}></object><hr>
    <object allowscrolling data={selection_enabled}></object>
    <object allowscrolling data={caret_browsing_enabled}></object><hr>
    
    <object allowscrolling data={invert_page_enabled}></object><br>
    <object allowscrolling data={invert_images_enabled}></object><hr>

    <div>
      <p style="float:left">Zoom:</p>
      <span style="float:right" id="zoom">{frame['zoom']}</span>
      <input onchange="document.getElementById('zoom').textContent = this.value; frame.config(zoom=this.value)" style="width: 100%" type="range" min="0.1" max="10" step="0.1" value="{self.frame['zoom']}">
    </div>
    
    <div>
      <p style="float:left">Font scale:</p>
      <span style="float:right" id="fontscale">{frame['fontscale']}</span>
      <input onchange="document.getElementById('fontscale').textContent = this.value; frame.config(fontscale=this.value)" style="width: 100%" type="range" min="0.1" max="10" step="0.1" value="{self.frame['fontscale']}">
    </div>
    
    <hr style="margin-bottom:10px;margin-top:10px">
    
    <p>User agent:</p>
    <input onchange="frame['headers']['User-Agent'] = this.value" style="padding: 5px 0px 3px 0px; width: 100%; color:black" type="text" value="{frame['headers']['User-Agent']}">
    <hr style="margin-bottom:10px;margin-top:0">
    
    <p>Parse mode:</p>
    <select onchange="frame.config(parsemode=this.value)" style="padding: 3px 0px 1px 0px; width:100%; color:black">
      <option value="xml">xml</option>
      <option value="xhtml">xhtml</option>
      <option value="html">html</option>
    </select>
    <hr style="margin-bottom:10px;margin-top:0">

    <input type="color" onchange="frame.config(find_match_highlight_color=this.value)" value="{frame['find_match_highlight_color']}">
    <input type="color" onchange="frame.config(find_match_text_color=this.value)" value="{frame['find_match_text_color']}"><label>Find matches</label><br>
    <input type="color" onchange="frame.config(find_current_highlight_color=this.value)" value="{frame['find_current_highlight_color']}">
    <input type="color" onchange="frame.config(find_current_text_color=this.value)" value="{frame['find_current_text_color']}"><label>Current match</label><br>
    <input type="color" onchange="frame.config(selected_text_highlight_color=this.value)" value="{frame['selected_text_highlight_color']}">
    <input type="color" onchange="frame.config(selected_text_color=this.value)" value="{frame['selected_text_color']}"><label>Selected text</label><br>

    <hr>
    
    <div style="margin-top: 20px">
      <object allowscrolling data={view_source_button}></object>
      <object allowscrolling data={style_button}></object>
      <object allowscrolling data={html_button}></object>
      <object allowscrolling data={about_button}></object>
    </div>
  </body>
</html>""")
        
        frame.config = self.html_config

        linklabel.pack(expand=True, fill="both")
        topbar.columnconfigure(4, weight=1)
        backbutton.grid(row=0, column=1, pady=5, padx=5)
        forwardbutton.grid(row=0, column=2, pady=5)
        reloadbutton.grid(row=0, column=3, pady=5, padx=5)
        urlbar.grid(row=0, column=4, pady=5, padx=20, sticky="NS")
        newbutton.grid(row=0, column=5, pady=5, padx=(5,0))
        closebutton.grid(row=0, column=6, pady=5, padx=5)
        findbutton.grid(row=0, column=7, pady=5)
        settingsbutton.grid(row=0, column=8, pady=5, padx=5)

        findbar.columnconfigure(6, weight=1)
        find_box.grid(row=0, column=0, padx=5)
        find_previous.grid(row=0, column=1)
        find_next.grid(row=0, column=2)
        ttk.Separator(findbar, orient="vertical").grid(row=0, column=3, sticky="ns", pady=4, padx=8)
        ignore_case.grid(row=0, column=4, sticky="ns")
        highlight_all.grid(row=0, column=5, sticky="ns", padx=5)
        find_bar_caption.grid(row=0, column=7)
        find_close.grid(row=0, column=8, sticky="ns", padx=5)
        ttk.Separator(findbar).grid(row=1, column=0, columnspan=9, sticky="ew", pady=4, padx=8)

        findbox_var.trace("w", self.search_in_page)

        frame.bind("<Button-3>", self.on_right_click)
        for widget in [urlbar, find_box]:
            widget.bind("<Control-a>", lambda e: self.after(50, self.select_all_in_entry, e.widget))

        for child in findbar.winfo_children():
            child.bind("<Escape>", lambda x: self.open_findbar())
        for child in sidebar.winfo_children():
            child.bind("<Escape>", lambda x: self.close_sidebar())
        settingsbutton.bind("<Escape>", lambda x: self.close_sidebar())

        self.toggle_theme(False)

    def html_config(self, **kwargs):
        self.frame.configure(**kwargs)
        if self.html_playground is not None: 
            self.html_playground.iframe.configure(**kwargs)

    def style_report(self):
        if self.frame.base_url == "about:html":
            self.html_playground.iframe.generate_style_report().grab_set()
        else:
            self.frame.generate_style_report().grab_set()

    def apply_dark_theme(self):
        self.style.configure(".", background="#2b2b2b", foreground="#FFFFFF")
        self.style.configure("TButton",
            background="#444444",
            foreground="#FFFFFF")
        self.style.map("TButton",
            background=[("active", "#666666"),
                ("!active", "#444444")])
        self.style.configure("TLabel",
            background="#2b2b2b",
            foreground="#FFFFFF")
        self.style.configure("TEntry",
            fieldbackground="#555555",
            foreground="#FFFFFF")    
        self.style.map('TScale',
          background=[('active', '#2b2b2b'),
                      ('!active', '#2b2b2b')])  
        self.style.configure("TFrame",
            background="#2b2b2b")
        self.style.configure("TScrollbar",
            background="#444444",
            troughcolor="#2b2b2b",
            arrowcolor="#FFFFFF")
        self.style.map("TScrollbar",
            background=[("active", "#666666"),
                ("!active", "#444444")])
        self.style.configure("TCheckbutton",
            foreground="#FFFFFF")
        self.style.map("TCheckbutton",
            background=[("active", "#666666"),
                ("!active", "#2b2b2b")])
        self.style.configure("TNotebook",
            background="#2b2b2b")
        self.style.configure("TNotebook.Tab",
            background="#444444",
            foreground="#FFFFFF")
        self.style.map("TNotebook.Tab", 
            background=[("selected", "#444444"), 
                ("!selected", "#2b2b2b")],
            foreground=[("selected", "#FFFFFF"),
                ("!selected", "#FFFFFF")])
        self.sidebar.document.body.style.backgroundColor = "#2b2b2b"
        self.sidebar.document.body.style.color = "#FFFFFF"
        
    def apply_light_theme(self):
        self.style.configure(".", background="#F0F0F0", foreground="#000000",)
        self.style.configure("TButton",
            background="#DDDDDD",
            foreground="#000000",
            font=("Arial", 10),
            relief="flat")
        self.style.map("TButton",
            background=[("active", "#CCCCCC"),
                ("!active", "#DDDDDD")])
        self.style.configure("TLabel",
            background="#F0F0F0",
            foreground="#000000",
            font=("Arial", 10))
        self.style.configure("TEntry",
            fieldbackground="#FFFFFF",
            foreground="#000000",
            insertcolor="black",
            borderwidth=0,
            font=("Arial", 10))
        self.style.configure("TScale",
            troughcolor="white",)
        self.style.map('TScale',
          background=[('active', '#F0F0F0'),
                      ('!active', '#F0F0F0')])
        self.style.configure("TFrame",
            background="#F0F0F0",)
        self.style.configure("TScrollbar",
            background="#DDDDDD",
            troughcolor="#F0F0F0",
            arrowcolor="#000000")
        self.style.map("TScrollbar",
            background=[("active", "#CCCCCC"),
                ("!active", "#DDDDDD")])
        self.style.configure("TCheckbutton",
            background="#F0F0F0",
            foreground="#000000",
            font=("Arial", 10))
        self.style.map("TCheckbutton",
            background=[("active", "#DDDDDD"),
                ("!active", "#F0F0F0")])
        self.style.configure("TNotebook",
            background="#F0F0F0",
            relief="flat",
            borderwidth=0,
            tabmargins=(5, 5, 5, 0),
            padding=0)
        self.style.configure("TNotebook.Tab",
            background="#DDDDDD",
            foreground="#000000",
            padding=(10, 5),
            relief="flat",
            borderwidth=0,
            font=("Arial", 10))
        self.style.map("TNotebook.Tab", 
            background=[("selected", "#DDDDDD"),
                ("!selected", "#F0F0F0")],
            foreground=[("selected", "#000000"),
                ("!selected", "#000000")])
        # this only works on the non-experimental version of tkhtml
        self.sidebar.document.body.style.backgroundColor = "#F0F0F0"
        self.sidebar.document.body.style.color = "#000000"

    def select_all_in_entry(self, widget):
        widget.select_range(0, 'end')
        widget.icursor('end')
    
    def select_all_in_text(self, widget):
        widget.tag_add(tk.SEL, "1.0", tk.END)
        widget.mark_set(tk.INSERT, "1.0")
        widget.see(tk.INSERT)

    def on_right_click(self, event):
        url = self.frame.get_currently_hovered_element().getAttribute("href")
        if url:
            url = self.frame.resolve_url(url)
        selection = self.frame.get_selection()
        menu = tk.Menu(self, tearoff=0)
        if len(self.back_history) > 1:
            menu.add_command(label="Back", accelerator="Alt-Back", command=self.back)
        if len(self.forward_history) == 1: 
            menu.add_command(label="Forward", accelerator="Alt-Forward", command=self.forward)
        menu.add_command(label="Reload", accelerator="Ctrl-R", command=self.reload)
        menu.add_separator()
        if url:
            menu.add_command(label="Open link", command=lambda url=url: self.link_click(url))
            menu.add_command(label="Open link in new tab", command=lambda url=url: self.open_new_tab(url))
            menu.add_separator()
        menu.add_command(label="Select all", accelerator="Ctrl-A", command=self.frame.select_all)
        if selection:
            menu.add_command(label="Copy", accelerator="Ctrl-C", command=self.frame.html.copy_selection)
        menu.add_separator()
        if self.frame["experimental"] or not os.name == "nt":
            menu.add_command(label="Take screenshot", command=self.screenshot)
        else:
            menu.add_command(label="Take screenshot", state="disabled", command=self.screenshot)
        menu.add_command(label="Snapshot page", command=self.snapshot)
        if self.frame["experimental"]:
            menu.add_command(label="Print page", accelerator="Ctrl-P", command=self.print)
        else:
            menu.add_command(label="Print page", accelerator="Ctrl-P", state="disabled", command=self.print)
        menu.add_command(label="Save page", accelerator="Ctrl-S", command=self.save)
        menu.add_separator()
        menu.add_command(label="Find in page", accelerator="Ctrl-F", command=lambda: self.open_findbar(True))
        if str(self.view_source_button.cget("state")) == "normal":
            menu.add_command(label="View page source", accelerator="Ctrl-U", command=self.view_source)
        menu.tk_popup(event.x_root, event.y_root, 0)

    def screenshot(self):
        file_path = filedialog.asksaveasfilename(
            filetypes=[("JPG files", "*.jpg"), ("PNG files", "*.png"), ("All files", "*.*")],
            title="Save Screenshot As"
        )
        if file_path:
            self.frame.screenshot_page(file_path)

    def snapshot(self):
        file_path = filedialog.asksaveasfilename(
            filetypes=[("HTML files", "*.html"), ("XHTML files", "*.xhtml"), ("XML files", "*.xml"), ("All files", "*.*")],
            title="Snapshot Page As"
        )
        if file_path:
            self.frame.snapshot_page(file_path)

    def print(self):
        if self.frame["experimental"]:
            file_path = filedialog.asksaveasfilename(
                filetypes=[("Postscript files", "*.ps"), ("All files", "*.*")],
                title="Print Page As"
            )
            if file_path:
                self.frame.print_page()

    def save(self):
        file_path = filedialog.asksaveasfilename(
            filetypes=[("HTML files", "*.html"), ("XHTML files", "*.xhtml"), ("XML files", "*.xml"), ("All files", "*.*")],
            title="Save Page As"
        )
        if file_path:
            self.frame.save_page(file_path)

    def urlbar_focus(self):
        self.urlbar.focus()
        self.urlbar.select_range(0, 'end')
        self.urlbar.icursor('end')

    def previous_and_find(self):
        self.find_select_num -= 1
        if self.find_select_num == 0:
            self.find_select_num = self.find_match_num
        self.search_in_page(change=False)

    def next_and_find(self):
        self.find_select_num += 1
        if self.find_select_num == self.find_match_num+1:
            self.find_select_num = 1
        self.search_in_page(change=False)

    def search_in_page(self, x=None, y=None, change=True):
        if self.frame.base_url == "about:html":
            frame = self.html_playground.iframe
        else:
            frame = self.frame
        if change:
            self.find_select_num = 1
        self.find_match_num = frame.find_text(self.findbox_var.get(), self.find_select_num, self.ignore_case_var.get(), self.highlight_all_var.get())
        if self.find_match_num > 0:
            self.find_bar_caption.configure(text="Selected {} of {} matches.".format(self.find_select_num, self.find_match_num))
        else:
            self.find_bar_caption.configure(text="No matches")

        if self.find_match_num > 1:
            self.find_previous.config(state="normal", cursor="hand2")
            self.find_next.config(state="normal", cursor="hand2")
        else:
            self.find_previous.config(state="disabled", cursor="arrow")
            self.find_next.config(state="disabled", cursor="arrow")

    def hide_messsage_box(self, event):
        if self.message_box.winfo_ismapped():
            self.message_box.grid_forget()
        else:
            self.message_box.grid(column=0, row=5, sticky="ew", padx=4, pady=(0, 4,))

    def add_message(self, message):
        self.message_box.insert("end", message+"\n\n")
        self.message_box.yview("end")
        if f"Error loading {self.urlbar.get()}" in message:
            self.handle_view_source_button("about:error")
        self.linklabel.config(text=self.cut_text(message, 80))

    def toggle_images(self):
        self.frame.config(images_enabled= self.images_var.get())
        self.reload()

    def toggle_styles(self):
        self.frame.config(stylesheets_enabled = self.styles_var.get())
        self.reload()

    def toggle_forms(self):
        self.frame.config(forms_enabled = self.forms_var.get())
        self.reload()

    def toggle_js(self):
        try:
            val = self.js_var.get()
            self.frame.config(javascript_enabled = val)
            self.reload()
        except ModuleNotFoundError:
            self.js_var.set(0)
            tk.messagebox.showerror("Error", "PythonMonkey must be installed to enable JavaScript.")

    def toggle_objects(self):
        self.frame.config(objects_enabled = self.objects_var.get())
        self.reload()

    def toggle_caches(self):
        self.frame.config(caches_enabled = self.caches_var.get())
        self.reload()

    def toggle_emojis(self):
        self.frame.config(crash_prevention_enabled = self.crashes_var.get())
        self.reload()

    def toggle_threads(self):
        self.frame.config(threading_enabled = self.threads_var.get())
        self.reload()

    def toggle_theme(self, update_page=True):
        value = self.invert_page_var.get()
        if value:
            self.apply_dark_theme()
        else:
            self.apply_light_theme()

        self.frame.config(dark_theme_enabled = value)
        if update_page:
            self.reload()

    def toggle_inverter(self):
        self.frame.config(image_inversion_enabled = self.invert_images_var.get())
        self.reload()

    def toggle_selection(self):
        self.frame.config(selection_enabled = self.selection_var.get())

    def toggle_caret_browsing(self):
        self.frame.config(caret_browsing_enabled = self.caret_browsing_var.get())

    def open_sidebar(self, keep_open=False):
        if self.sidebar.winfo_ismapped() and not keep_open:
            self.close_sidebar()
        else:
            self.sidebar.grid(row=0, column=2, sticky="nsew")
            self.sidebar.update()
            self.settingsbutton.state(['pressed'])
            
    def close_sidebar(self):
        if self.sidebar.winfo_ismapped():
            self.sidebar.grid_forget()
            self.settingsbutton.state(['!pressed'])

    def open_findbar(self, keep_open=False):
        if self.findbar.winfo_ismapped() and not keep_open:
            self.findbar.grid_forget()
            self.frame.find_text("")
            self.findbutton.state(['!pressed'])
        else:
            self.findbar.grid(column=0, row=2, sticky="ew", pady=(4,0,))
            self.find_box.focus()
            self.findbutton.state(['pressed'])

    def on_motion(self, event):
        text = self.frame.get_currently_hovered_node_text()
        link = self.frame.get_currently_hovered_node_attribute("href")
        if link:
            self.linklabel.config(text=self.cut_text("Hyper-link: "+link, 80))
        elif text:
            self.linklabel.config(text=self.cut_text("Text: "+text, 80))
        else:
            elm = self.frame.get_currently_hovered_node_tag()
            self.linklabel.config(text=self.cut_text("Element: "+elm, 80))

    def back(self):
        if len(self.back_history) == 1:
            return
        self.forwardbutton.config(state="normal", cursor="hand2")
        self.forward_history.append(self.back_history[-1])
        url = self.back_history[-2]
        self.back_history = self.back_history[:-1]
        self.load_url(url)
        if len(self.back_history) <= 1:
            self.backbutton.config(state="disabled", cursor="arrow")

    def on_downloading(self, event):
        self.reloadbutton.config(text="Stop", command=self.frame.stop)

    def load_url(self, url, decode=None, force=False):
        if url == "about:html":
            if self.html_playground is None: self.html_playground = HTMLPlayground(self.frame)
            self.frame.load_html(HTML_TEST_PAGE.format(self.html_playground), url)
        else:
            self.frame.load_url(url, decode, force)

    def forward(self):
        if len(self.forward_history) == 0:
            return
        url = self.forward_history[-1]
        self.forward_history = self.forward_history[:-1]
        if self.forward_history == []:
            self.forwardbutton.config(state="disabled", cursor="arrow")
        self.backbutton.config(state="normal", cursor="hand2")
        self.back_history.append(url)
        self.load_url(url)

    def cut_text(self, text, limit):
        if (len(text) > limit):
            text = text[:limit] + "..."
        return text

    def focus_on_url(self):
        self.urlbar.focus()
        self.urlbar.select_range(0, 'end')

    def open_new_tab(self, url=NEW_TAB):
        page = Page(self.master)
        self.master.add(page, text='')
        self.master.select(page)
        page.invert_page_var.set(self.invert_page_var.get())
        page.toggle_theme(False)
        page.link_click(url, history=False)

    def close_current_tab(self):
        self.master.forget(self)

    def done_loading(self, event):
        self.linklabel.config(text="Done")
        self.reloadbutton.config(text="Reload", command=self.reload)

        self.search_in_page()

    def handle_view_source_button(self, url):
        if url in BUILTIN_PAGES or url.startswith("view-source:") or url == "about:html":
            self.view_source_button.config(state="disabled", cursor="arrow")
        else:
            self.view_source_button.config(state="normal", cursor="hand2")

    def url_change(self, url=None):
        if not isinstance(url, str): url = self.frame.current_url;
        self.master.tab(self, text=self.cut_text(url, 40))
        self.urlbar.delete(0, "end")
        self.urlbar.insert(0, url)
        self.handle_view_source_button(url)

    def addtohist(self, url):
        self.back_history.append(url)
        self.forward_history = []
        self.forwardbutton.config(state="disabled", cursor="arrow")
        self.backbutton.config(state="normal", cursor="hand2")

    def form_submit(self, url, data, method):
        if method == "GET":
            self.addtohist(url+data)
        else:
            self.addtohist(url)
        self.frame.load_form_data(url, data, method)

    def load_site(self, event):
        url = check_url(self.urlbar)
        self.addtohist(url)
        self.load_url(url, force=True)
        self.handle_view_source_button(url)

    def link_click(self, url, history=True):
        self.addtohist(url)
        self.master.tab(self, text=self.cut_text(url, 40))
        if not history:
            self.backbutton.config(state="disabled", cursor="arrow")
        self.urlbar.delete(0, "end")
        self.urlbar.insert(0, url)
        self.load_url(url)
        self.handle_view_source_button(url)

    def reload(self):
        if self.frame.base_url == "about:html": 
            self.html_playground.iframe.reload()
        else:
            self.frame.reload()

    def change_title(self, event):
        self.master.tab(self, text=self.cut_text(self.frame.title, 40))  
    
    def select_all(self):
        if self.focus_get() not in (self.urlbar, self.find_box):
            if self.frame.base_url == "about:html":
                self.html_playground.iframe.select_all()
            else:
                self.frame.select_all()

    def view_source(self):
        if str(self.view_source_button.cget("state")) == "normal":
            self.open_new_tab("view-source:"+self.urlbar.get())
        
class Browser(tk.Tk):
    "TkinterWeb Browser"

    def __init__(self):

        tk.Tk.__init__(self)
        self.title("TkinterWebBrowser")
        self.minsize(800, 500)
        self.main_frame = main_frame = tk.Frame(self, highlightthickness=0, bd=0)

        self.frame = frame = Notebook(main_frame)
        frame.enable_traversal()
        frame.bind("<<NotebookTabChanged>>", self.on_tab_change)

        page = Page(frame)
 
        self.bind_all("<Up>", lambda e: frame.select().frame.html._on_up(e))
        self.bind_all("<Down>", lambda e: frame.select().frame.html._on_down(e))
        self.bind_all("<Left>", lambda e: frame.select().frame.html._on_left(e))
        self.bind_all("<Right>", lambda e: frame.select().frame.html._on_right(e))
        self.bind_all("<Prior>", lambda e: frame.select().frame.html._on_prior(e))
        self.bind_all("<Next>", lambda e: frame.select().frame.html._on_next(e))
        self.bind_all("<Home>", lambda e: frame.select().frame.html._on_home(e))
        self.bind_all("<End>", lambda e: frame.select().frame.html._on_end(e))
        self.bind_all("<Control-w>", lambda e: frame.select().close_current_tab())
        self.bind_all("<Control-t>", lambda e: frame.select().open_new_tab())
        self.bind_all("<Control-f>", lambda e: frame.select().open_findbar(True))
        self.bind_all("<Control-b>",  lambda e: frame.select().open_sidebar(True))
        self.bind_all("<Control-l>", lambda e: frame.select().urlbar_focus())
        self.bind_all("<Control-i>", lambda e: frame.select().hide_messsage_box(e))
        self.bind_all("<Control-r>", lambda e: frame.select().reloadbutton.invoke())
        self.bind_all("<Control-n>", lambda e: Browser())
        self.bind_all("<Control-q>", lambda e: self.destroy())
        self.bind_all("<Control-a>", lambda e: frame.select().select_all())
        self.bind_all("<Control-u>", lambda e: frame.select().view_source())
        self.bind_all("<Control-p>", lambda e: frame.select().print())
        self.bind_all("<Control-s>", lambda e: frame.select().save())
        self.bind_all("<Alt-Left>", lambda e: frame.select().back())
        self.bind_all("<Alt-Right>", lambda e: frame.select().forward())

        frame.pack(expand=True, fill="both")
        main_frame.pack(expand=True, fill="both")
        frame.add(page, text='')

        page.link_click(NEW_TAB, history=False)

        self.mainloop()
    
    def on_tab_change(self, event):
        if self.frame.pages:
            self.frame.select().toggle_theme(False)
        else:
            self.destroy()

if __name__ == "__main__":   
    Browser()

================================================
FILE: setup.py
================================================
import pathlib
from setuptools import setup, find_namespace_packages

HERE = pathlib.Path(__file__).parent
README = (HERE / "README.md").read_text()

setup(
    name="tkinterweb",
    version="4.25.2",
    python_requires=">=3.2",
    description="HTML/CSS widgets for Tkinter",
    long_description=README,
    long_description_content_type="text/markdown",
    url="https://github.com/Andereoo/TkinterWeb",
    license="MIT",
    classifiers=[
        "Intended Audience :: Developers",
        "License :: Freeware",
        "License :: OSI Approved :: MIT License",
        "Natural Language :: English",
        "Programming Language :: Python",
        "Programming Language :: Python :: 3",
        "Topic :: Software Development",
      ],
    keywords="tkinter, Tkinter, tkhtml, Tkhtml, Tk, HTML, CSS, webbrowser",
    packages=find_namespace_packages(include=["tkinterweb", "tkinterweb.*"]),
    include_package_data=True,
    install_requires=["tkinterweb-tkhtml>=2.1.1"],
    extras_require = {
          "html": ["tkinterweb-tkhtml-extras>=1.3.1"],
          "images": ["pillow"],
          "svg": ["tkinterweb-tkhtml-extras>=1.3.1", "pillow", "cairosvg"],
          "javascript": ["pythonmonkey"],
          "requests": ["brotli"],

          "recommended": ["tkinterweb-tkhtml-extras>=1.3.1", "pillow"],
          "full": ["tkinterweb-tkhtml-extras>=1.3.1", "pillow", "cairosvg", "pythonmonkey", "brotli"],
    },
)


================================================
FILE: tkinterweb/__init__.py
================================================
"""
TkinterWeb v4
This is a wrapper for the Tkhtml3 widget from http://tkhtml.tcl.tk/tkhtml.html, 
which displays styled HTML documents in Tkinter.

Copyright (c) 2021-2025 Andrew Clarke
"""


try:
    from .htmlwidgets import HtmlFrame, HtmlLabel, HtmlText, HtmlParse
    from .subwidgets import Notebook
    from .bindings import TkHtmlParsedURI, TkinterWeb
    from .utilities import __title__, __author__, __copyright__, __license__, __version__
except (ImportError, ModuleNotFoundError) as error:
    import sys
    import tkinter as tk
    from tkinter import messagebox
    # Give useful troubleshooting information as a popup, as most bundled applications don't have a visible console
    # Also print the message in case something is also wrong with the Tkinter installation
    error_message = f"Error: {error} \n\n\
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\
See https://tkinterweb.readthedocs.io/en/latest/faq.html for more information."
    sys.stdout.write(error_message)
    root = tk.Tk()
    root.withdraw()
    # For older versions of pyinstaller, windowed app may crash without any message of any kind
    message = messagebox.showerror("Fatal Error Encountered", error_message)
    sys.exit()


__all__ = ['Demo', 'HtmlFrame', 'HtmlLabel', 'HtmlText', 'HtmlParse', 'Notebook', 'TkHtmlParsedURI', 'TkinterWeb']


class Demo():
    "A simple example of TkinterWeb in action displaying the Tkinter Wiki."

    def __init__(self):
        import tkinter as tk

        self.root = root = tk.Tk()
        self.frame = frame = HtmlFrame(root, messages_enabled=True, on_navigate_fail=self.on_error, on_link_click=self.navigate, selected_text_highlight_color="#e6eee6")
        self.button = tk.Button(root, cursor="hand2")
        
        frame.load_url("https://tkinterweb.readthedocs.io/en/latest/")
        frame.bind("<<TitleChanged>>", lambda event: self.root.title(frame.title))
        frame.bind("<<DOMContentLoaded>>", self.done_loading)
        frame.pack(expand=True, fill="both")

        root.mainloop()

    def HTML_to_text(self, text, start, end):
        "Make HTML code bwtween two strings display as plain text"
        import re
        pattern = re.compile(re.escape(start) + r'(.*?)' + re.escape(end), re.DOTALL)
        def replacer(match):
            inner = match.group(1)
            escaped = inner.replace("<", "&lt;").replace(">", "&gt;").replace("&gt;", ">", 1)
            return start + escaped + end
        return pattern.sub(replacer, text)
    
    def navigate(self, url):
        "Only display files from the docs page or from tkhtml.tcl.tk"
        from urllib.parse import urlparse
        if urlparse(self.frame.current_url).netloc == urlparse(url).netloc or "tkhtml.tcl.tk" in url:
            self.frame.load_url(url)
        else:
            import webbrowser
            webbrowser.open(url)

    def done_loading(self, event):
        "Display code blocks in iframes to allow horizontal scrolling when the page loads"
        from tkinter import TclError
        try:
            #self.frame.document.querySelector("div[role=\"search\"]").remove()
            head = self.frame.document.getElementsByTagName("head")[0].innerHTML
            for code_block in self.frame.document.getElementsByClassName("highlight"):
                iframe = HtmlFrame(self.frame, messages_enabled=False, horizontal_scrollbar="auto", shrink=True, overflow_scroll_frame=self.frame.html)
                text = self.HTML_to_text(code_block.innerHTML, "<span", "</span>")
                iframe.load_html(f"{head}<div class='highlight'>{text}</div>", base_url=self.frame.base_url)
                code_block.widget = iframe
        except TclError:
            pass

    def on_error(self, url, error, code):
        "Show an error page if the page fails to load"
        self.button.configure(text="Try Again", command=lambda url=self.frame.current_url: self.frame.load_url(url))
        html = f"""<html><head><title>TkinterWeb Demo - Error {code}</title><style>td {{text-align:center;vertical-align:middle}} h3{{margin:0 0 10px 0;padding:0;font-weight:normal}} html,body,table,tr{{background-color:{self.frame["about_page_background"]};color:{self.frame["about_page_foreground"]};width:100%;height:100%;margin:0}}</style></head>
        <body><table><tr><td tkinterweb-full-page>
        <h3>Error {code}</h3><h3>An internet connection is required to display the TkinterWeb demo :(</h3><object id="button" data={self.button}></object>
        </td></tr></table></body></html>"""
        self.frame.load_html(html)

================================================
FILE: tkinterweb/bindings.py
================================================
"""
The core Python bindings to Tkhtml3

Copyright (c) 2021-2026 Andrew Clarke
"""

from re import IGNORECASE, split, sub

from urllib.parse import urljoin

from queue import Queue, Empty

import tkinter as tk
from . import extensions, utilities, handlers

import tkinterweb_tkhtml


class TkinterWeb(tk.Widget):
    """This object provides the low-level widget that bridges the gap between the underlying Tkhtml3 widget and Tkinter. 

    **Do not use this widget on its own unless absolutely nessessary.** Instead use the :class:`~tkinterweb.HtmlFrame` widget.

    This widget can be accessed through the :attr:`~tkinterweb.HtmlFrame.html` property of the :class:`~tkinterweb.HtmlFrame` and :class:`~tkinterweb.HtmlLabel` widgets to access underlying settings and commands that are not a part of the :class:`~tkinterweb.HtmlFrame` API.
    
    This widget stores many useful instance variables and configuration flags. Some are exposed through the main API, others are not. Please see the source code for more details."""

    def __init__(self, master, tkinterweb_options=None, **kwargs):
        self.master = master
        tkinterweb_options = tkinterweb_options.copy()

        # Setup most variables
        self._setup_status_variables()

        # Setup the settings variables
        _delayed_options = {"dark_theme_enabled", "caches_enabled", "threading_enabled"}
        tkinterweb_options = self._setup_settings(tkinterweb_options, _delayed_options)

        # Load Tkhtml3
        self._load_tkhtml()

        # Register image loading infrastructure
        if "imagecmd" not in kwargs:
            kwargs["imagecmd"] = master.register(self._on_image_cmd)

        # Get Tkhtml folder and register crash handling
        # Not supported by standard Tkhtml releases
        if "drawcleanupcrashcmd" not in kwargs and self.use_prebuilt_tkhtml:
            kwargs["drawcleanupcrashcmd"] = master.register(self._on_draw_cleanup_crash_cmd)

        # Log everything
        # if "logcmd" not in kwargs:
        #    kwargs["logcmd"] = tkhtml_notifier

        shrink = bool(kwargs.get("shrink"))
        textwrap = kwargs.get("textwrap")

        # Set the textwrap value if needed
        if self.using_tkhtml30:
            if self.default_style:
                # For Tkhtml 3.0, we do our best by applying CSS to block word wrapping
                if textwrap == "auto" and shrink:
                    self.default_style += utilities.TEXTWRAP_STYLE
                elif not textwrap:
                    self.default_style += utilities.TEXTWRAP_STYLE
            # Version 3.0 doesn't support textwrap
            kwargs.pop("textwrap", None)
        elif textwrap == "auto":
            kwargs["textwrap"] = not(shrink)

        # Set the default style if needed
        if not kwargs.get("defaultstyle", "") and self.default_style:
            kwargs["defaultstyle"] = self.default_style

        # Unset width and height if null
        if kwargs.get("width") == 0: 
            del kwargs["width"]
        if kwargs.get("height") == 0: 
            del kwargs["height"]

        # Provide OS information for troubleshooting
        self.post_message(f"Starting TkinterWeb for {utilities.PLATFORM.processor} {utilities.PLATFORM.system} with Python {'.'.join(utilities.PYTHON_VERSION)}")

        # Check tkinterweb_tkhtml_extras
        if not self.using_tkhtml30 and tkinterweb_tkhtml.TKHTML_EXTRAS_VERSION is not None:
            version = []
            for letter in tkinterweb_tkhtml.TKHTML_EXTRAS_VERSION.split("."): version.append(int(letter))
            if tuple(version) < (1, 3, 0):
                raise RuntimeError(
                    f"tkinterweb-tkhtml-extras >= 1.3.0 is required but version {tkinterweb_tkhtml.TKHTML_EXTRAS_VERSION} is installed. " \
                    "Upgrade with pip install --upgrade tkinterweb[recommended]."
                )

        # Initialize the Tkhtml3 widget
        tk.Widget.__init__(self, master, "html", kwargs)

        # Setup threading settings
        try:
            self.allow_threading = bool(self.tk.call("set", "tcl_platform(threaded)"))
        except tk.TclError:
            self.allow_threading = True

        # Set remaining settings
        for key in _delayed_options:
            setattr(self, key, tkinterweb_options[key])

        # Create a tiny, blank frame for cursor updating
        self.motion_frame_bg = "white"
        self.motion_frame = tk.Frame(self, bg=self.motion_frame_bg, width=1, height=1)
        self.motion_frame.place(x=0, y=0)


        # Setup bindings        
        self._setup_bindings()
        self._setup_handlers()
        
        self.post_message(f"""Welcome to TkinterWeb!
                                
The API changed in version 4. See https://tkinterweb.readthedocs.io/ for details.

Debugging messages are enabled. Use the parameter `messages_enabled = False` when calling HtmlFrame() or HtmlLabel() to disable these messages.
                                
Load about:tkinterweb for debugging information.
                                
If you benefited from using this package, please consider supporting its development by donating at https://buymeacoffee.com/andereoo - any amount helps!""")
        
        # Check tkinterweb_tkhtml_extras
        if not tkinterweb_tkhtml.TKHTML_EXTRAS_ROOT_DIR:
            self.post_message("The tkinterweb-tkhtml-extras package is either not installed or does not support your system. Some functionality may be missing.")

    # --- Widget setup --------------------------------------------------------

    def _setup_settings(self, options, delayed_options):
        """Widget settings. 
        Some settings have extra logic that needs to run when changing them, so they're defined elsewhere as properties.
        They are set when needed. If the settings are set through the options attribute, they will be added here."""
        settings = {
            "messages_enabled": True,
            "stylesheets_enabled": True,
            "events_enabled": True,
            "images_enabled": True,
            "forms_enabled": True,
            "objects_enabled": True,
            "ignore_invalid_images": True,
            "image_alternate_text_enabled": True,
            "overflow_scroll_frame": None,
            "default_style": "",
            "dark_style": "",
            "text_mode": False,

            "use_prebuilt_tkhtml": True,
            "tkhtml_version": "",
            "experimental": False,

            "find_match_highlight_color": "#ef0fff",
            "find_match_text_color": "#fff",
            "find_current_highlight_color": "#38d878",
            "find_current_text_color": "#fff",
            "selected_text_highlight_color": "#3584e4",
            "selected_text_color": "#fff",
            "visited_links": [],

            "maximum_thread_count": 20,

            "queue": None,
            "queue_delay": 50,
            "queue_after": None,

            "embed_obj": None,
            "manage_vsb_func": None,
            "manage_hsb_func": None,
            "on_link_click": None,
            "on_form_submit": None,
            "message_func": None,
            "on_script": None,
            "on_element_script": None,
            "on_resource_setup": None,

            "request_func": None,
            "insecure_https": False,
            "ssl_cafile": None,
            "request_timeout": 15,
            "headers": {},
            
            "dark_theme_limit": 280,
            "style_dark_theme_regex": r"([^:;\s{]+)\s?:\s?([^;{!]+)(?=!|;|})",
            "general_dark_theme_regexes": [
                r'(<[^>]+bgcolor=")([^"]*)',
                r'(<[^>]+text=")([^"]*)',
                r'(<[^>]+link=")([^"]*)'
            ],
            "inline_dark_theme_regexes": [
                r'(<[^>]+style=")([^"]*)',
                r'([a-zA-Z-]+:)([^;]*)'
            ],

            "node_tag": f"tkinterweb.{id(self)}.nodes",
            "tkinterweb_tag": f"tkinterweb.{id(self)}.tkinterweb",
            "scrollable_node_tag": f"tkinterweb.{id(self)}.scrollablenodes",
        }
        settings.update(options)
        for key, value in settings.items():
            if key not in delayed_options:
                setattr(self, key, value)

        return settings

    def _setup_status_variables(self):
        "Widget status variables."
        self.base_url = ""
        self.title = ""
        self.icon = ""

        self.fragment = ""
        self.parsing = False
        self.active_threads = []
        self.pending_threads = []
        self.current_active_node = None
        self.clicked_node = None
        self.current_hovered_node = None
        self.hovered_nodes = []

        self._style_count = 0
        self._current_cursor = ""

        # This set is used when resetting the widget and contains a reference to all loaded managers
        # Managers automatically add themselves to this set as they are created
        self._managers = set()

    def _setup_bindings(self):
        "Widget bindtags and bindings."
        self._add_bindtags(self, False, True)

        self.bind_class(self.node_tag, "<Motion>", self._on_mouse_motion, True)
        self.bind_class(self.node_tag, "<FocusIn>", self._on_focusout, True)

        self.bind_class(self.tkinterweb_tag, "<<Copy>>", self._copy_selection, True)
        self.bind_class(self.tkinterweb_tag, "<Control-a>", self._select_all, True)
        self.bind_class(self.tkinterweb_tag, "<B1-Motion>", self._extend_selection, True)
        self.bind_class(self.tkinterweb_tag, "<Button-1>", self._on_click, True)
        self.bind_class(self.tkinterweb_tag, "<Button-2>", self._on_middle_click, True)
        self.bind_class(self.tkinterweb_tag, "<Button-3>", self._on_right_click, True)
        self.bind_class(self.tkinterweb_tag, "<Double-Button-1>", self._on_double_click, True)
        self.bind_class(self.tkinterweb_tag, "<ButtonRelease-1>", self._on_click_release, True)
        self.bind_class(self.tkinterweb_tag, "<Destroy>", self._on_destroy)

        for i in {"<Left>", "Control-Left>", "Control-Shift-Left>", "<KP_Left>", "<Control-KP_Left>", "<Control-Shift-KP_Left>", 
                "<Right>", "Control-Right>", "Control-Shift-Right>", "<KP_Right>", "<Control-KP_Right>", "<Control-Shift-KP_Right>",
                "<Up>", "Control-Up>", "Control-Shift-Up>", "<KP_Up>", "<Control-KP_Up>", "<Control-Shift-KP_Up>", 
                "<Down>", "Control-Down>", "Control-Shift-Down>", "<KP_Down>", "<Control-KP_Down>", "<Control-Shift-KP_Down>",
                "<Prior>", "<KP_Prior>", "<Next>", "<KP_Next>", "<Home>", "<KP_Home>", "<End>", "<KP_End>", "<FocusOut>", "<FocusIn>"}:
            method = "_on_" + i.strip("<>").split("-")[-1].split("_")[-1].lower()
            # We use bind and not bind_class here because users may want to override these bindings
            try:
                self.bind(i, getattr(self, method))
            except tk.TclError:
                # KP_ bindings don't work on MacOS
                pass

        # Fix for Bug #151
        # Externally map .!tkinterweb.document to .!tkinterweb on Windows
        if utilities.PLATFORM.system == "Windows":
            widget = tk.Widget(self, None)
            widget._w = self._w # or is f"{self}.document" better?
            self.children["document"] = widget

    def _add_bindtags(self, widgetid, allowscrolling=True, master=False):
        "Add bindtags to allow scrolling and on_embedded_mouse function calls."
        if allowscrolling:
            tags = (
                self.node_tag,
                self.scrollable_node_tag,
            )
        else:
            tags = (self.node_tag,)

        if master:
            tags = (self.node_tag, self.tkinterweb_tag)

        widgetid.bindtags(widgetid.bindtags() + tags)

    def _on_destroy(self, event):
        self._end_queue()
        self.stop()

    def _setup_handlers(self):
        "Setup node handlers"
        # Node handlers don't work on body and html elements. 
        # Body and html elements also cannot be removed without causing a segfault in vanilla Tkhtml. 
        # Weird.
        self.register_lazy_handler("parse", "body", "node_manager")
        self.register_lazy_handler("parse", "html", "node_manager")

        self.register_lazy_handler("node", "meta", "node_manager")
        self.register_lazy_handler("node", "title", "node_manager")
        self.register_lazy_handler("node", "a", "node_manager")
        self.register_lazy_handler("node", "base", "node_manager")
        self.register_lazy_handler("attribute", "a", "node_manager")

        if not self.using_tkhtml30:
            #self.register_lazy_handler("node", "details", "node_manager")
            self.register_lazy_handler("node", "progress", "node_manager")
            self.register_lazy_handler("attribute", "progress", "node_manager")
            self.register_lazy_handler("attribute", "details", "node_manager")
        
        self.register_lazy_handler("node", "form", "form_manager")
        self.register_lazy_handler("node", "table", "form_manager")
        self.register_lazy_handler("node", "select", "form_manager")
        self.register_lazy_handler("attribute", "select", "form_manager")
        self.register_lazy_handler("node", "textarea", "form_manager")
        self.register_lazy_handler("node", "input", "form_manager")
        self.register_lazy_handler("attribute", "input", "form_manager")

        self.register_lazy_handler("script", "script", "script_manager")

        self.register_lazy_handler("script", "style", "style_manager")
        self.register_lazy_handler("node", "link", "style_manager")

        self.register_lazy_handler("node", "img", "image_manager")
        self.register_lazy_handler("attribute", "img", "image_manager")

        self.register_lazy_handler("node", "iframe", "object_manager")
        self.register_lazy_handler("attribute", "iframe", "object_manager")
        self.register_lazy_handler("node", "object", "object_manager")
        self.register_lazy_handler("attribute", "object", "object_manager")

    def _load_tkhtml(self):
        "Load Tkhtml"
        if self.tkhtml_version == "auto":
            self.tkhtml_version = None

        try:
            loaded_version = tkinterweb_tkhtml.get_loaded_tkhtml_version(self.master)
            self.post_message(f"Using Tkhtml {loaded_version} because it is already loaded")
        except tk.TclError:
            if self.use_prebuilt_tkhtml:
                try:
                    file, loaded_version, self.experimental = tkinterweb_tkhtml.get_tkhtml_file(self.tkhtml_version, experimental=self.experimental)
                    tkinterweb_tkhtml.load_tkhtml_file(self.master, file)
                    self.post_message(f"Tkhtml {loaded_version} successfully loaded from {tkinterweb_tkhtml.TKHTML_ROOT_DIR}")
                except tk.TclError as error: # If something goes wrong, try again with version 3.0 in case it is a Cairo issue
                    self.post_message(f"WARNING: An error occured while loading Tkhtml {loaded_version}: {error}\n\n\
It is likely that not all dependencies are installed. Make sure Cairo is installed on your system. Some features may be missing.")
                    file, loaded_version, self.experimental = tkinterweb_tkhtml.get_tkhtml_file(index=0, experimental=self.experimental)
                    try:
                        tkinterweb_tkhtml.load_tkhtml_file(self.master, file)
                        self.post_message(f"Tkhtml {loaded_version} successfully loaded from {tkinterweb_tkhtml.TKHTML_ROOT_DIR}")
                    except tk.TclError as error: # If it still won't load it never will. It is most likely that the system is not supported. The user needs to compile and install Tkhtml.
                        raise tk.TclError(f"{error} It is likely that your system is not supported out of the box. {tkinterweb_tkhtml.HELP_MESSAGE}") from error
            else:
                tkinterweb_tkhtml.load_tkhtml(self.master)
                loaded_version = tkinterweb_tkhtml.get_loaded_tkhtml_version(self.master)
                self.post_message(f"Tkhtml {loaded_version} successfully loaded")

        self.tkhtml_version = float(loaded_version)
        self.using_tkhtml30 = float(loaded_version) == 3

    # --- Extensions ----------------------------------------------------------

    # The following 'managers' each offer extra functionality. 
    # The ones in handlers.py are primarily node handlers.
    # These objects are created when needed, if enabled
    # Most can be disabled, except search_manager and widget_manager, which run at the user's request or via other managers that can be disabled
    # Any calls to a disabled manager will be ignored and return 'None'
    
    @utilities.lazy_manager("selection_enabled")
    def selection_manager(self):
        """The widget's selection manager.
        
        :rtype: :class:`~tkinterweb.extensions.SelectionManager`
        
        New in version 4.11."""
        return extensions.SelectionManager(self)
        
    @utilities.lazy_manager("caret_browsing_enabled")
    def caret_manager(self):
        """The widget's caret manager.
        
        :rtype: :class:`~tkinterweb.extensions.CaretManager`
        
        New in version 4.8."""
        return extensions.CaretManager(self)
    
    @utilities.lazy_manager("events_enabled")
    def event_manager(self):
        """The widget's event manager.
        
        :rtype: :class:`~tkinterweb.extensions.EventManager`
        
        New in version 4.10."""
        return extensions.EventManager(self)

    @utilities.lazy_manager(None)
    def widget_manager(self):
        """The widget's widget manager.
        
        :rtype: :class:`~tkinterweb.extensions.WidgetManager`
        
        New in version 4.11."""
        return extensions.WidgetManager(self)
    
    @utilities.lazy_manager(None)
    def search_manager(self):
        """The widget's document search manager.
        
        :rtype: :class:`~tkinterweb.extensions.SearchManager`
        
        New in version 4.11."""
        return extensions.SearchManager(self)

    @utilities.lazy_manager("javascript_enabled")
    def script_manager(self):
        """The widget's script manager.
        
        :rtype: :class:`~tkinterweb.handlers.ScriptManager`
        
        New in version 4.11."""
        return handlers.ScriptManager(self)

    @utilities.lazy_manager("stylesheets_enabled")
    def style_manager(self):
        """The widget's style manager.
        
        :rtype: :class:`~tkinterweb.handlers.StyleManager`
        
        New in version 4.11."""
        return handlers.StyleManager(self)

    @utilities.lazy_manager("images_enabled")
    def image_manager(self):
        """The widget's image manager.
        
        :rtype: :class:`~tkinterweb.handlers.ImageManager`
        
        New in version 4.11."""
        return handlers.ImageManager(self)

    @utilities.lazy_manager("objects_enabled")
    def object_manager(self):
        """The widget's object manager.
        
        :rtype: :class:`~tkinterweb.handlers.ObjectManager`
        
        New in version 4.11."""
        return handlers.ObjectManager(self)

    @utilities.lazy_manager("forms_enabled")
    def form_manager(self):
        """The widget's form manager.
        
        :rtype: :class:`~tkinterweb.handlers.FormManager`
        
        New in version 4.11."""
        return handlers.FormManager(self)

    @utilities.lazy_manager(None)
    def node_manager(self):
        """The widget's node handler manager.
        
        :rtype: :class:`~tkinterweb.extensions.NodeManager`
        
        New in version 4.11."""
        return handlers.NodeManager(self)

    # --- Properties ----------------------------------------------------------

    @utilities.special_setting(True)
    def caches_enabled(self, prev_enabled, enabled):
        "Disable the Tkhtml image cache when disabling caches."
        if prev_enabled != enabled: 
            self.imagecache = enabled
            if not enabled: utilities.lru_cache.clear()

    @property
    def imagecache(self):
        return bool(self.tk.call(self._w, "cget", "-imagecache"))

    @imagecache.setter
    def imagecache(self, toggle):
        self.tk.call(self._w, "configure", "-imagecache", toggle)

    @utilities.special_setting(False)
    def javascript_enabled(self, prev_enabled, enabled):
        "Warn the user when enabling JavaScript."
        if prev_enabled != enabled:
            if enabled:
                self.post_message("WARNING: JavaScript support is enabled. This feature is a work in progress. Only enable JavaScript support on documents you know and trust.")

    @utilities.special_setting(True)
    def crash_prevention_enabled(self, prev_enabled, enabled):
        "Warn the user when disabling crash prevention."
        if prev_enabled != enabled:
            if not enabled:
                self.post_message("WARNING: crash prevention is disabled. You may encounter segmentation faults on some pages.")
    
    @utilities.special_setting(False)
    def dark_theme_enabled(self, prev_enabled, enabled):
        "Warn the user when enabling dark mode."
        if prev_enabled != enabled:
            if enabled:
                self.post_message("WARNING: dark theme is enabled. This feature may cause hangs or crashes on some pages.")
            if enabled and self.dark_style:
                self.config(defaultstyle=self.default_style + self.dark_style)
            elif self.default_style:
                self.config(defaultstyle=self.default_style)

    @utilities.special_setting(False)
    def image_inversion_enabled(self, prev_enabled, enabled):
        "Warn the user when enabling image inversion."
        if prev_enabled != enabled:
            prev_enabled = enabled
            if enabled:
                self.post_message("WARNING: image inversion is enabled. This feature may cause hangs or crashes on some pages.")

    @utilities.special_setting(True)
    def threading_enabled(self, prev_enabled, enabled):
        "Warn the user when disabling threading and ensure that threading is disabled if Tcl/Tk is not built with thread support."
        if self.allow_threading:
            if enabled:
                # Initialize the queue
                # The queue evaluates Tcl/Tk commands running in a thread
                # The queue will start or stop when self.maximum_thread_count is set
                if not self.queue:
                    self.queue = Queue()
                self._check_queue()
            else:
                self.post_message("WARNING: threading is disabled. Your app may hang while loading webpages.")
                self._end_queue()
        else:
            self._threading_enabled = False
            self.post_message("WARNING: threading is disabled because your Tcl/Tk library does not support threading. Your app may hang while loading webpages.")
            self._end_queue()

    @utilities.special_setting(False)
    def caret_browsing_enabled(self, prev_enabled, enabled):
        "Enable or disable caret browsing."
        if getattr(self, "_caret_manager", False) and not enabled:
            self._caret_manager.reset()

    @utilities.special_setting(True)
    def selection_enabled(self, prev_enabled, enabled):
        "Enable or disable text selection."
        if getattr(self, "_selection_manager", False) and not enabled:
            self._selection_manager.clear_selection()

    @property # will rename to default_style once I finally remove the default_style setting
    def tkhtml_default_style(self):
        """Return the current document's default stylesheet. Use for debugging.
        
        New in version 4.19."""
        return self.tk.call("::tkhtml::htmlstyle")

    @property
    def images(self):
        """Return a dictionary containing the document's images. Use for debugging.
        
        New in version 4.19."""
        NAMES = ("name", "pixmap", "w", "h", "alpha", "ref",)
        return {i[0]:dict(zip(NAMES, i[1:])) for i in self.tk.call(self._w, "_images")}

    @property
    def style_report(self):
        """Return the document's style report. Use for debugging.
        
        New in version 4.19."""
        return self.tk.call(self._w, "_stylereport")

    # --- Queuing, messaging, and events --------------------------------------

    def _check_queue(self):
        try:
            while True:
                msg = self.queue.get_nowait()
                msg()
        except Empty:
            pass
        self.queue_after = self.after(self.queue_delay, self._check_queue)

    def _end_queue(self):
        if self.queue_after:
            self.after_cancel(self.queue_after)
            self.queue_after = None
        self.queue = None

    def post_to_queue(self, callback, thread_safe=True):
        """Use this method to send a callback to TkinterWeb's thread-safety queue. The callback will be evaluated on the main thread.
        Use this when running Tkinter commands from within a thread. 
        If the queue is not running (i.e. threading is disabled), the callback will be evaluated immediately.
        
        New in version 4.9."""
        if thread_safe and self.queue:
            self.queue.put(callback)
        else:
            callback()

    def post_event(self, event, thread_safe=False):
        "Generate a virtual event."
        # NOTE: when thread_safe=True, this method is thread-safe
        # Would you believe that?
        if not self.events_enabled:
            return

        if thread_safe and self.queue:
            self.post_to_queue(lambda event=event: self._post_event(event))
        else:
            self._post_event(event)

    def _post_event(self, event):
        "Generate a virtual event."
        try:
            self.event_generate(event)
        except tk.TclError:
            # The widget doesn't exist anymore
            pass

    def post_message(self, message, thread_safe=False):
        "Post a message."
        # NOTE: when thread_safe=True, this method is thread-safe
        # Amazing stuff, eh?
        if self.message_func is None and not self.messages_enabled:
            return
        
        if thread_safe and self.queue:
            self.post_to_queue(lambda message=message: self._post_message(message))
        else:
            self._post_message(message)

    def _post_message(self, message):
        "Post a message."
        if self.overflow_scroll_frame:
            message = "[EMBEDDED DOCUMENT] " + message
        if self.message_func is not None:
            self.message_func(message)
        elif self.messages_enabled:
            utilities.notifier(message)

    # --- HTML/CSS parsing ----------------------------------------------------

    def parse(self, html, thread_safe=False):
        "Parse HTML code. Call :meth:`TkinterWeb.reset` before calling this method for the first time."
        # NOTE: when thread_safe=True, this method is thread-safe

        html = self._crash_prevention(html)
        html = self._dark_mode(html)

        # By default Tkhtml won't display plain text
        if "<" not in html and ">" not in html:
            html = f"<html><body><div>{html}</div></body></html>"
        elif "<html>" not in html and "</html>" not in html:
            # Otherwise, document.write can be buggy
            html = f"<html>{html}</html>"

        # Send the HTML code to the queue if needed
        # Otherwise, evaluate directly so that the document can be manipulated as soon as parse() returns
        if thread_safe:
            self.post_to_queue(lambda html=html: self._parse(html))
        else:
            self._parse(html)
    
    def _parse(self, html):
        "Parse HTML code."
        # NOTE: this must run in the main thread
        self.parsing = True
        self.tk.call(self._w, "parse", html)
        self.parsing = False

        self.post_event(utilities.DOM_CONTENT_LOADED_EVENT)

        # If any threads are active, they'll send the done loading signal when they finish
        if not self.active_threads:
            self._handle_load_finish()
        else:
            # Scroll to the fragment if given but do not issue a done loading event
            self._handle_load_finish(False)

        self.script_manager._submit_deferred_scripts()
        self.event_manager.send_onload()

        #if self.using_tkhtml30: # Handle unsupported tags
        self.node_manager._handle_load_finish()

    def _handle_load_finish(self, post_event=True):
        if self.fragment:
            try:
                if isinstance(self.fragment, tuple):
                    self.yview(self.fragment)
                else:
                    node = self.search(f"[id='{self.fragment}']")
                    if not node: 
                        node = self.search(f"[name={self.fragment}]")
                    if node:
                        self.fragment = node
                        self.yview(node)
            except tk.TclError:
                pass
        
        if post_event:
            self.post_event(utilities.DONE_LOADING_EVENT)

    def parse_css(self, sheetid=None, data="", url=None, fallback_priority="author"):
        "Parse CSS code."
        if not url: url = self.base_url
        data = self._crash_prevention(data)
        data = self._css_dark_mode(data)
        
        try:
            importcmd = self.register(
                lambda new_url, media=None, parent_url=url: 
                    self.style_manager._on_atimport(parent_url, new_url, media)
            )
            urlcmd = self.register(
                lambda new_url, url=url: self.resolve_url(
                    new_url, url
                )
            )
            if not sheetid:
                self._style_count += 1
                sheetid = f"{fallback_priority}{self._style_count:04d}"
                
            self.tk.call(
                self._w, "style",
                "-id", sheetid,
                "-importcmd", importcmd,
                "-urlcmd", urlcmd, data
            )
        except tk.TclError:
            # The widget doesn't exist anymore
            pass

    def reset(self, thread_safe=False):
        "Reset the widget."
        # NOTE: when thread_safe=True, this method is thread-safe. Imagine that!

        self.stop()

        self.title = ""
        self.icon = ""
        self.fragment = ""

        if thread_safe:
            self.post_to_queue(self._reset)
        else:
            self._reset()

    def _reset(self):
        # NOTE: this must run in the main thread
        
        # Reset the scrollbars to the default setting
        if self.manage_vsb_func is not None:
            self.manage_vsb_func()
        if self.manage_hsb_func is not None:
            self.manage_hsb_func()

        # Note to self: these need to be here
        # Or very strange errors will magically appear,
        # Usually when switching between pages quickly
        self.hovered_nodes.clear()
        self.current_hovered_node = None

        self._set_cursor("default")
        self.tk.call(self._w, "reset")

        for manager in self._managers:
            manager.reset()

    def stop(self):
        "Stop loading resources."
        for thread in self.active_threads:
            thread.stop()
        self.pending_threads.clear()
    
    def resolve_url(self, url, base=None):
        "Generate a full url from the specified url."
        if not base: base = self.base_url
        return urljoin(base, url)
    
    # --- Resource loading ----------------------------------------------------

    def download_url(self, url, *args):
        if self.request_func:
            return self.request_func(url, *args)
        
        if url.startswith("file://") or (not self.caches_enabled):
            return utilities.download(url, *args, insecure=self.insecure_https, cafile=self.ssl_cafile, headers=tuple(self.headers.items()), timeout=self.request_timeout)
        else:
            return utilities.cache_download(url, *args, insecure=self.insecure_https, cafile=self.ssl_cafile, headers=tuple(self.headers.items()), timeout=self.request_timeout)
    
    def _check_url_cache_state(self, url, *args):
        return utilities.check_download(url, *args, insecure=self.insecure_https, cafile=self.ssl_cafile, headers=tuple(self.headers.items()), timeout=self.request_timeout)
    
    def _thread_check(self, callback, url, *args, **kwargs):
        if not self.threading_enabled or url.startswith("file://") or self._check_url_cache_state(url):
            callback(url, *args, **kwargs)
        else:
            thread = utilities.StoppableThread(target=callback, args=(url, *args,), kwargs=kwargs)

            if len(self.active_threads) >= 100:
                self.pending_threads.append(thread)
            else:
                thread.start()

    def _begin_download(self):
        # NOTE: this may run in a thread

        thread = utilities.get_current_thread()
        self.active_threads.append(thread)
        self.post_event(utilities.DOWNLOADING_RESOURCE_EVENT, thread.is_subthread)
        return thread

    def _finish_download(self, thread):
        # NOTE: this may run in a thread

        self.active_threads.remove(thread)

        if thread.isrunning():
            if thread.is_subthread and self.pending_threads:
                self.pending_threads.pop(0).start()

            elif not self.parsing:
                if len(self.active_threads) == 0:
                    self.post_to_queue(self._handle_load_finish, thread.is_subthread)
                else:
                    self.post_to_queue(lambda: self._handle_load_finish(False), thread.is_subthread)

    def _finish_resource_load(self, message, url, resource, success):
        # NOTE: this must run in the main thread

        self.post_message(message)

        if self.on_resource_setup is not None:
            self.on_resource_setup(url, resource, success)

    # --- Bindings ------------------------------------------------------------

    def node(self, *args):
        "Retrieve one or more document node handles from the current document."
        nodes = self.tk.call(self._w, "node", *args)
        if nodes:
            return nodes
        else:
            return None, None

    def text(self, subcommand, *args):
        "Interact with the text of the HTML document. Valid subcommands are bbox, index, offset, and text."
        return self.tk.call(self._w, "text", subcommand, *args)

    def tag(self, subcommand, tag_name, *args):
        "Highlight regions of text displayed by the widget. Valid subcommands are add, remove, configure, and delete."
        return self.tk.call(self._w, "tag", subcommand, tag_name, *args)

    def search(self, selector, *a, cnf={}, **kw):
        """Search the document for the specified CSS selector; return a Tkhtml node if found."""
        return self.tk.call((self._w, "search", selector)+utilities.TclOpt(a)+self._options(cnf, kw))

    def xview(self, *args, auto_scroll=False):
        "Control horizontal scrolling."
        #if args:
        #    return self.tk.call(self._w, "xview", *args)
        #coords = map(float, self.tk.call(self._w, "xview").split()) #raises an error
        #return tuple(coords)
        xview = self.tk.call(self._w, "xview", *args)
        if args:
            self.caret_manager.update(auto_scroll=auto_scroll, xview=xview)
        return xview

    def xview_scroll(self, number, what, auto_scroll=False):
        """Shifts the view in the window left or right, according to number and what.
        "number" is an integer, and "what" is either "units" or "pages"."""
        return self.xview("scroll", number, what, auto_scroll=auto_scroll)

    def xview_moveto(self, number, auto_scroll=False):
        "Shifts the view horizontally to the specified position"
        return self.xview("moveto", number, auto_scroll=auto_scroll)

    def yview(self, *args, auto_scroll=False):
        """Control vertical scrolling."""
        yview = self.tk.call(self._w, "yview", *args)
        if args:
            self.caret_manager.update(auto_scroll=auto_scroll, yview=yview)
        return yview

    def yview_scroll(self, number, what, auto_scroll=False):
        """Shifts the view in the window left or right, according to number and what.
        "number" is an integer, and "what" is either "units" or "pages"."""
        return self.yview("scroll", number, what, auto_scroll=auto_scroll)

    def yview_moveto(self, number, auto_scroll=False):
        "Moves the view vertically to the specified position."
        return self.yview("moveto", number, auto_scroll=auto_scroll)

    def bbox(self, node=None):
        "Get the bounding box of the viewport or a specified node."
        return self.tk.call(self._w, "bbox", node)
    
    def parse_fragment_simple(self, html):
        return self.tk.call(self._w, "fragment", html)

    def parse_fragment(self, html):
        """Parse a document fragment.
        A document fragment isn't part of the active document but is comprised of nodes like the active document.
        Changes made to the fragment don't affect the document.
        Returns a root node."""
        html = self._crash_prevention(html)
        html = self._dark_mode(html)
        fragment = self.tk.call(self._w, "fragment", html)
        # If any threads are active, they'll send the done loading signal when they finish
        if not self.active_threads:
            self.post_event(utilities.DONE_LOADING_EVENT)
        self.script_manager._submit_deferred_scripts()
        return fragment

    def get_node_text(self, node_handle, *args):
        "Get the text content of the given node."
        return self.tk.call(node_handle, "text", *utilities.TclOpt(args))

    def set_node_text(self, node_handle, new):
        "Set the text content of the given node."
        self.tk.call(node_handle, "text", "set", new)
        self.relayout() # needed for pathName text text to return the updated string

    def relayout(self):
        self.tk.call(self._w, "_relayout")

    def get_child_text(self, node):
        """Get text of node and all its descendants recursively.
        
        New in version 4.4."""
        text = self.get_node_text(node, "-pre")
        for child in self.get_node_children(node):
            text += self.get_child_text(child)
        return text
    
    def get_node_tag(self, node_handle):
        "Get the HTML tag of the given node."
        return self.tk.call(node_handle, "tag")

    def get_node_parent(self, node_handle):
        "Get the parent of the given node."
        return self.tk.call(node_handle, "parent")

    def get_node_children(self, node_handle):
        "Get the children of the given node."
        return self.tk.call(node_handle, "children")

    def get_node_attribute(self, node_handle, attribute, default="", value=None):
        "Get the specified attribute of the given node."
        if value:  # Backwards compatability
            return self.tk.call(node_handle, "attribute", attribute, value)
        else:
            return self.tk.call(node_handle, "attribute", "-default", default, attribute)

    def set_node_attribute(self, node_handle, attribute, value):
        "Set the specified attribute of the given node."
        return self.tk.call(node_handle, "attribute", attribute, value)

    def get_node_attributes(self, node_handle):
        "Get the attributes of the given node."
        attr = self.tk.call(node_handle, "attribute")
        return dict(zip(attr[0::2], attr[1::2]))

    def get_node_property(self, node_handle, node_property, *args):
        "Get the specified CSS property of the given node."
        return self.tk.call(node_handle, "property", *utilities.TclOpt(args), node_property)

    def get_node_properties(self, node_handle, *args):
        "Get the CSS properties of the given node."
        prop = self.tk.call(node_handle, "property", *utilities.TclOpt(args))
        return dict(zip(prop[0::2], prop[1::2]))

    def set_node_property(self, node_handle, node_property, new_value, *args):
        "Set the specified CSS property of the given node."
        current = self.get_node_properties(node_handle, "-inline")
        current[node_property] = new_value
        style = " ".join(f"{p}: {v};" for p, v in current.items())
        self.set_node_attribute(node_handle, "style", style)

    def override_node_properties(self, node_handle, *props):
        "Get/set the CSS property override list."
        if props: return self.tk.call(node_handle, "override", " ".join(props))
        return self.tk.call(node_handle, "override")

    def insert_node(self, node_handle, child_nodes):
        "Experimental, insert the specified nodes into the parent node."
        return self.tk.call(node_handle, "insert", child_nodes)

    def insert_node_before(self, node_handle, child_nodes, before):
        "Experimental, place the specified nodes is before another node."
        return self.tk.call(node_handle, "insert", "-before", before, child_nodes)
    
    def replace_node_contents(self, node_handle, contents, *args, check=True):
        """Fill a node with either a Tk widget or with Tkhtml nodes.
        
        New in version 4.2."""
        if check and (node_handle != contents) and not self.get_node_parent(node_handle):
            raise RuntimeError(f"root elements cannot be replaced")

        if not contents:
            # Calling replace with empty text causes Tkhtml to segfault
            contents = self.tk.call(self._w, "fragment", " ")
        return self.tk.call(node_handle, "replace", contents, *args)
    
    def get_node_replacement(self, node_handle):
        """Return the Tk widget contained by the given node.
        
        New in version 4.13."""
        return self.tk.call(node_handle, "replace")

    def delete_node(self, node_handle):
        "Delete the given node."
        if self.experimental:
            node_parent = self.get_node_parent(node_handle)
            if node_parent:
                self.tk.call(node_parent, "remove", node_handle)
            else:
                raise RuntimeError(f"root elements cannot be removed")
        else:
            node_parent = self.get_node_parent(node_handle)
            node_tag = self.get_node_tag(node_handle)
            # Removing the body element causes a segfault
            if node_parent:
                if node_tag != "body":
                    self.tk.call(node_parent, "remove", node_handle)
                else:
                    raise RuntimeError(f"{node_tag} elements cannot be removed")
            elif node_tag:
                raise RuntimeError(f"{node_tag} elements cannot be removed")
            else:
                raise RuntimeError(f"element is invalid or has already been removed")

    def destroy_node(self, node_handle):
        "Destroy a node. May cause crashes so avoid it whenever possible."
        self.tk.call(node_handle, "destroy")

    def set_node_flags(self, node, name):
        "Set dynamic flags on the given node."
        self.tk.call(node, "dynamic", "set", name)

    def remove_node_flags(self, node, name):
        "remove dynamic flags on the given node."
        self.tk.call(node, "dynamic", "clear", name)

    def get_node_tkhtml(self, node_handle):
        "Get the path name of the node's corresponding Tkhtml instance."
        return self.tk.call(node_handle, "html")

    def get_node_stacking(self, node_handle):
        """Return the node-handle that forms the stacking context this node is located in.
        Return "" for the root-element or any element that is part of an orphan subtree.
        
        New in version 4.2."""
        return self.tk.call(node_handle, "stacking")

    def get_current_hovered_node(self, event):
        "Get the current node."
        if self.widget_manager.hovered_embedded_node:
            return self.widget_manager.hovered_embedded_node

        return self.tk.eval(
            f"""set node [lindex [lindex [{self} node {event.x} {event.y}] end] end]"""
        )

    def get_current_hovered_node_parent(self, node):
        "Get the parent of the node returned by :meth:`TkinterWeb.get_current_hovered_node`."
        return self.tk.eval(f"""set node [lindex [lindex [{node} parent] end] end]""")

    def register_handler(self, handler_type, node_tag, callback):
        "Register a node handler."
        self.tk.call(self._w, "handler", handler_type, node_tag, self.register(callback))

    def _lazy_handler(self, manager, method):
        def callback(*args, **kwargs):
            manager_obj = getattr(self, manager)
            return getattr(manager_obj, method)(*args, **kwargs)
        return callback
    
    def register_lazy_handler(self, handler_type, node_tag, manager_name):
        "Register a node handler to run lazily in the given manager."
        if handler_type == "attribute":
            callback_name = f"_on_{node_tag}_value_change"
        else:
            callback_name = f"_on_{node_tag}"

        self.tk.call(self._w, "handler", handler_type, node_tag, 
                     self.register(
                         self._lazy_handler(manager_name, callback_name)
                         )
                     )
        
    def image(self, full=False):
        """Return the name of a new Tk image containing the rendered document.
        The returned image should be deleted when the script has finished with it.
        Note that this command is mainly intended for automated testing.
        Be wary of running this command on large documents.
        Does not work on Windows unless experimental Tkhtml is used."""
        full = "-full" if full else ""
        name = self.tk.call(self._w, "image", full)
        data = self.tk.call(name, "data")
        self.tk.call("image", "delete", name)
        return data

    def postscript(self, cnf={}, **kwargs):
        """Print the contents of the canvas to a postscript file.
        Valid options: colormap, colormode, file, fontmap, height, 
        pageanchor, pageheight, pagesize, pagewidth, pagex, pagey, 
        nobg, noimages, rotate, width, x, and y.
        Does not work unless experimental Tkhtml is used."""
        return self.tk.call((self._w, "postscript")+self._options(cnf, kwargs))

    def preload_image(self, url):
        """Preload an image for use later. 
        Only useful if caches are enabled and reset() is not called after preloading."""
        return self.tk.call(self._w, "preload", url)
    
    def get_computed_styles(self):
        "Get a tuple containing the computed CSS rules for each CSS selector."
        return self.tk.call(self._w, "_styleconfig")

    def override_node_CSS(self, node, *props):
        """Overrides the node's properties; if it is a text node, it overrides the parent's properties.
        
        New in version 4.4."""
        if not self.get_node_tag(node): node = self.get_node_parent(node)
        return self.override_node_properties(node, *props)

    def write(self, *arg, cnf={}, **kw):
        """Write directly to an open HTML document stream, may be used when parsing.
        
        New in version 4.4."""
        return self.tk.call(self._w, "write", *arg+self._options(cnf, kw))
 
    # --- Cmds & crash prevention ---------------------------------------------

    def _on_image_cmd(self, url):
        "Handle images."
        return self.image_manager._on_image_cmd(url)
       
    def _on_draw_cleanup_crash_cmd(self):
        if self.crash_prevention_enabled:
            self.post_message("WARNING: HtmlDrawCleanup has encountered a critical error. This is being ignored because crash prevention is enabled.")
        else:
            self.post_message("WARNING: HtmlDrawCleanup has encountered a critical error.")
            self.destroy()

    def _crash_prevention(self, data):
        if self.crash_prevention_enabled:
            ### TODO: enable emojis & noto colo emoji font in Tcl/Tk 9

            # From Bug #11
            data = "".join(c for c in data if c <= "\uFFFF")

            # I moved these workarounds to Tkhtml in version 3.1
            if self.using_tkhtml30:
                data = sub(
                    "font-family:[^;']*(;)?",
                    self._remove_noto_emoji,
                    data, flags=IGNORECASE,
                )
                data = sub(
                    r"rgb\([^0-9](.*?)\)", 
                    "inherit", 
                    data, flags=IGNORECASE)

                # From Bug #150
                # Not really crash prevention
                data = sub(
                    r'style=(["\'])\s+', 
                    r'style=\1', 
                    data, flags=IGNORECASE)

        return data

    def _remove_noto_emoji(self, match):
        "Remove noto color emoji font, which causes Tkinter to crash."
        match = match.group().lower()
        match = match.replace("noto color emoji", "arial")
        return match

    # --- Dark mode -----------------------------------------------------------

    def _generate_altered_colour(self, match, matchtype=1):
        "Invert document colours. Highly experimental."
        colors = match.group(2).replace("\n", "")
        colors = split(r"\s(?![^()]*\))", colors)
        changed = False

        for count, color in enumerate(colors):
            try:
                if color.startswith("#"):
                    color = color.lstrip("#")
                    lv = len(color)
                    if lv == 3:
                        color = color + color
                        lv = len(color)
                    colors[count] = utilities.invert_color(
                        list(
                            int(color[i : i + lv // 3], 16)
                            for i in range(0, lv, lv // 3)
                        ),
                        match.group(1), self.dark_theme_limit
                    )
                    changed = True
                elif color.startswith("rgb(") or color.startswith("rgba("):
                    colors_list = (list(
                            map(
                                int,
                                color.lstrip("rgba(")
                                .lstrip("rgb(")
                                .rstrip(")")
                                .strip(" ")
                                .split(","),
                            )
                        ),)
                    if len(colors_list) == 3:
                        colors[count] = utilities.invert_color(
                            colors_list,
                            match.group(1), self.dark_theme_limit
                        )
                        changed = True
                else:
                    try:
                        color = list(self.winfo_rgb(color))
                        colors[count] = utilities.invert_color(color, match.group(1), self.dark_theme_limit)
                        changed = True
                    except tk.TclError:
                        pass
            except ValueError as error:
                pass

        if changed:
            if matchtype:
                return match.group(1) + " ".join(colors)
            else:
                return match.group(1) + ": " + " ".join(colors)
        else:
            return match.group()
            
    def _dark_mode(self, html):
        if self.dark_theme_enabled:
            html = sub(self.inline_dark_theme_regexes[0], lambda match: match.group(1) + sub(self.inline_dark_theme_regexes[1], self._generate_altered_colour, match.group(2)), html)
            for regex in self.general_dark_theme_regexes:
                html = sub(regex, self._generate_altered_colour, html, flags=IGNORECASE)
        return html
    
    def _css_dark_mode(self, data):
        if self.dark_theme_enabled:
            return sub(self.style_dark_theme_regex, lambda match, matchtype=0: self._generate_altered_colour(match, matchtype), data)
        return data
    
    # --- Miscellaneous -------------------------------------------------------

    def safe_tk_eval(self, expr):
        """Always evaluate the given expression on the main thread.

        Since version 4.9 all callbacks are evaluated on the main thread. Except for niche cases this command should not need to be used.

        This command may be removed at any time.
        
        New in version 4.4."""
        return utilities.safe_tk_eval(self, expr)

    def serialize_node(self, ib=3):
        """Pretty-print a node's contents. Similar to innerHTML, but formatted.

        New in version 4.4."""
        return utilities.safe_tk_eval(self, r"""
            proc indent {d} {return [string repeat { } $d]}
            proc prettify {node} {
                set depth [expr {([info level] - 1) * %d}]
                set tag [$node tag]
                if {$tag eq ""} {
                if {[string trim [$node text]] eq ""} return
                set z [string map {< &lt; > &gt;} [$node text -pre]]
                if {[[$node parent] tag] ne "pre"} {
                        return [indent $depth][regsub -all {\s+} $z " "]\n
                } else {
                        return [indent $depth]$z\n
                }
                }
                set ret [indent $depth]<$tag
                foreach {zKey zVal} [$node attribute] {
                    append ret " $zKey=\"[string map [list \x22 \x5C\x22] $zVal]\""
                }
                append ret >\n
                set void {area base br col embed hr img input keygen link meta param source track wbr}
                if {[lsearch -exact $void $tag] != -1} {
                    return $ret
                }
                foreach child [$node children] {
                append ret [prettify $child]
                }
                return $ret[indent $depth]</$tag>\n
            }
                prettify [%s node] """ % (ib, self)
        )

    def serialize_node_style(self, ib=3, return_as_dict=False):
        """Pretty-print a node's style.

        New in version 4.4."""
        style = {
            i[0]: dict(j.split(":", 1) for j in i[1].split("; ") if j.strip())
            for i in self.get_computed_styles()
            if "agent" != i[2]
        }

        if return_as_dict:
            return style
        else:
            text = ""
            for i in style:
                text += i + " {\n"
                for j in style[i]:
                    text += " "*ib + style[i][j] + ";\n"
                text += "}\n"
            return text
        
    def tkhtml_offset_to_text_index(self, node, offset, invert=False):
        """Translate a Tkhtml node offset to a node text index or back.

        New in version 4.8."""

        text = self.get_node_text(node, "-pre")

        ws = len(text) - len(text.lstrip())

        if invert:
            #index = self.text("offset", node, 0) + offset
            #node, offset = self.text("index", index)
            return text, max(offset - ws, 0)
        else:
            try:
                offset = self.text("offset", node, offset + ws) - self.text("offset", node, 0)
            except TypeError:
                pass
            return text, offset

    def _set_cursor(self, cursor):
        "Set the document cursor."
        if self._current_cursor != cursor:
            cursor = utilities.CURSOR_MAP[cursor]
            try:
                self.master.config(cursor=cursor, _override=True)
            except tk.TclError:
                self.master.config(cursor=cursor)
            self._current_cursor = cursor
            # I've noticed that the cursor won't always update when the binding is tied to a different widget than the one we are changing the cursor of
            # However, the html widget doesn't support the cursor property so there's not much we can do about this
            # update_idletasks() or update() have no effect, but updating the color or text of another widget does
            # print() also works. Don't ask me why.
            # Therefore we update the background color of a tiny frame that is barely visible whenever we need to change the cursor
            # I might as well match the background color of the page but it doesn't really matter
            # It's weird but hey, it works
            self.motion_frame.config(bg=self.motion_frame_bg)

    # --- Widget-user interaction ---------------------------------------------

    def _finish_scrolling(self, event, widget, x11, vsb):
        if not widget: widget = event.widget

        # If the user scrolls on the page while its resources are loading, stop scrolling to the fragment
        if isinstance(widget.fragment, tuple):
            widget.fragment = None

        if x11:
            scroll_up = (event.num == 4)
        else:
            scroll_up = event.delta > 0

        if vsb:
            yview = widget.yview()
            at_edge = yview[0] == 0 if scroll_up else yview[1] == 1
            vsb_locked = widget.manage_vsb_func is not None and widget.manage_vsb_func(check=True) == 0

            for node_handle in widget.hovered_nodes:
                if x11: stype = "onscrollup" if scroll_up else "onscrolldown"
                else: stype = "onscroll"
                widget.event_manager.post_element_event(node_handle, stype, event)

        else:
            xview = widget.xview()
            at_edge = xview[0] == 0 if scroll_up else xview[1] == 1
            vsb_locked = widget.manage_hsb_func is not None and widget.manage_hsb_func(check=True) == 0

        if widget.overflow_scroll_frame and (at_edge or vsb_locked):
            if vsb:
                widget.overflow_scroll_frame._finish_scrolling(event, wid
Download .txt
gitextract_67ltj4rn/

├── .github/
│   └── FUNDING.yml
├── .gitignore
├── .readthedocs.yaml
├── LICENSE.md
├── MANIFEST.in
├── README.md
├── docs/
│   ├── Makefile
│   ├── make.bat
│   ├── requirements.txt
│   └── source/
│       ├── _static/
│       │   ├── banner.png~
│       │   └── custom.css
│       ├── api/
│       │   ├── extensions.rst
│       │   ├── htmldocument.rst
│       │   ├── htmlframe.rst
│       │   ├── jsengine.rst
│       │   ├── notebook.rst
│       │   ├── tkinterweb.rst
│       │   └── tkinterweb_api.rst
│       ├── api.rst
│       ├── caret.rst
│       ├── compatibility.rst
│       ├── conf.py
│       ├── dom.rst
│       ├── faq.rst
│       ├── geometry.rst
│       ├── index.rst
│       ├── javascript.rst
│       ├── shrink.rst
│       ├── upgrading.rst
│       └── usage.rst
├── examples/
│   └── TkinterWebBrowser.py
├── setup.py
├── tkinterweb/
│   ├── __init__.py
│   ├── bindings.py
│   ├── dom.py
│   ├── extensions.py
│   ├── handlers.py
│   ├── htmlwidgets.py
│   ├── imageutils.py
│   ├── js.py
│   ├── subwidgets.py
│   └── utilities.py
└── tools/
    └── preparewheels.py
Download .txt
SYMBOL INDEX (750 symbols across 13 files)

FILE: docs/source/conf.py
  function skip_member (line 51) | def skip_member(app, what, name, obj, skip, options):
  function setup (line 56) | def setup(app):

FILE: examples/TkinterWebBrowser.py
  function check_url (line 55) | def check_url(entry):
  class HTMLPlayground (line 65) | class HTMLPlayground(ttk.PanedWindow):
    method __init__ (line 66) | def __init__(self, master):
    method _load_url (line 139) | def _load_url(self, event=None):
    method _on_title_change (line 143) | def _on_title_change(self, event, addendum=None):
    method _on_page_loaded (line 148) | def _on_page_loaded(self, event):
    method _update_iframe (line 153) | def _update_iframe(self, event=None):
  class Page (line 171) | class Page(ttk.Frame):
    method __init__ (line 172) | def __init__(self, master, *args, **kwargs):
    method html_config (line 375) | def html_config(self, **kwargs):
    method style_report (line 380) | def style_report(self):
    method apply_dark_theme (line 386) | def apply_dark_theme(self):
    method apply_light_theme (line 430) | def apply_light_theme(self):
    method select_all_in_entry (line 493) | def select_all_in_entry(self, widget):
    method select_all_in_text (line 497) | def select_all_in_text(self, widget):
    method on_right_click (line 502) | def on_right_click(self, event):
    method screenshot (line 538) | def screenshot(self):
    method snapshot (line 546) | def snapshot(self):
    method print (line 554) | def print(self):
    method save (line 563) | def save(self):
    method urlbar_focus (line 571) | def urlbar_focus(self):
    method previous_and_find (line 576) | def previous_and_find(self):
    method next_and_find (line 582) | def next_and_find(self):
    method search_in_page (line 588) | def search_in_page(self, x=None, y=None, change=True):
    method hide_messsage_box (line 608) | def hide_messsage_box(self, event):
    method add_message (line 614) | def add_message(self, message):
    method toggle_images (line 621) | def toggle_images(self):
    method toggle_styles (line 625) | def toggle_styles(self):
    method toggle_forms (line 629) | def toggle_forms(self):
    method toggle_js (line 633) | def toggle_js(self):
    method toggle_objects (line 642) | def toggle_objects(self):
    method toggle_caches (line 646) | def toggle_caches(self):
    method toggle_emojis (line 650) | def toggle_emojis(self):
    method toggle_threads (line 654) | def toggle_threads(self):
    method toggle_theme (line 658) | def toggle_theme(self, update_page=True):
    method toggle_inverter (line 669) | def toggle_inverter(self):
    method toggle_selection (line 673) | def toggle_selection(self):
    method toggle_caret_browsing (line 676) | def toggle_caret_browsing(self):
    method open_sidebar (line 679) | def open_sidebar(self, keep_open=False):
    method close_sidebar (line 687) | def close_sidebar(self):
    method open_findbar (line 692) | def open_findbar(self, keep_open=False):
    method on_motion (line 702) | def on_motion(self, event):
    method back (line 713) | def back(self):
    method on_downloading (line 724) | def on_downloading(self, event):
    method load_url (line 727) | def load_url(self, url, decode=None, force=False):
    method forward (line 734) | def forward(self):
    method cut_text (line 745) | def cut_text(self, text, limit):
    method focus_on_url (line 750) | def focus_on_url(self):
    method open_new_tab (line 754) | def open_new_tab(self, url=NEW_TAB):
    method close_current_tab (line 762) | def close_current_tab(self):
    method done_loading (line 765) | def done_loading(self, event):
    method handle_view_source_button (line 771) | def handle_view_source_button(self, url):
    method url_change (line 777) | def url_change(self, url=None):
    method addtohist (line 784) | def addtohist(self, url):
    method form_submit (line 790) | def form_submit(self, url, data, method):
    method load_site (line 797) | def load_site(self, event):
    method link_click (line 803) | def link_click(self, url, history=True):
    method reload (line 813) | def reload(self):
    method change_title (line 819) | def change_title(self, event):
    method select_all (line 822) | def select_all(self):
    method view_source (line 829) | def view_source(self):
  class Browser (line 833) | class Browser(tk.Tk):
    method __init__ (line 836) | def __init__(self):
    method on_tab_change (line 881) | def on_tab_change(self, event):

FILE: tkinterweb/__init__.py
  class Demo (line 35) | class Demo():
    method __init__ (line 38) | def __init__(self):
    method HTML_to_text (line 52) | def HTML_to_text(self, text, start, end):
    method navigate (line 62) | def navigate(self, url):
    method done_loading (line 71) | def done_loading(self, event):
    method on_error (line 85) | def on_error(self, url, error, code):

FILE: tkinterweb/bindings.py
  class TkinterWeb (line 19) | class TkinterWeb(tk.Widget):
    method __init__ (line 28) | def __init__(self, master, tkinterweb_options=None, **kwargs):
    method _setup_settings (line 133) | def _setup_settings(self, options, delayed_options):
    method _setup_status_variables (line 208) | def _setup_status_variables(self):
    method _setup_bindings (line 230) | def _setup_bindings(self):
    method _add_bindtags (line 267) | def _add_bindtags(self, widgetid, allowscrolling=True, master=False):
    method _on_destroy (line 282) | def _on_destroy(self, event):
    method _setup_handlers (line 286) | def _setup_handlers(self):
    method _load_tkhtml (line 327) | def _load_tkhtml(self):
    method selection_manager (line 367) | def selection_manager(self):
    method caret_manager (line 376) | def caret_manager(self):
    method event_manager (line 385) | def event_manager(self):
    method widget_manager (line 394) | def widget_manager(self):
    method search_manager (line 403) | def search_manager(self):
    method script_manager (line 412) | def script_manager(self):
    method style_manager (line 421) | def style_manager(self):
    method image_manager (line 430) | def image_manager(self):
    method object_manager (line 439) | def object_manager(self):
    method form_manager (line 448) | def form_manager(self):
    method node_manager (line 457) | def node_manager(self):
    method caches_enabled (line 468) | def caches_enabled(self, prev_enabled, enabled):
    method imagecache (line 475) | def imagecache(self):
    method imagecache (line 479) | def imagecache(self, toggle):
    method javascript_enabled (line 483) | def javascript_enabled(self, prev_enabled, enabled):
    method crash_prevention_enabled (line 490) | def crash_prevention_enabled(self, prev_enabled, enabled):
    method dark_theme_enabled (line 497) | def dark_theme_enabled(self, prev_enabled, enabled):
    method image_inversion_enabled (line 508) | def image_inversion_enabled(self, prev_enabled, enabled):
    method threading_enabled (line 516) | def threading_enabled(self, prev_enabled, enabled):
    method caret_browsing_enabled (line 535) | def caret_browsing_enabled(self, prev_enabled, enabled):
    method selection_enabled (line 541) | def selection_enabled(self, prev_enabled, enabled):
    method tkhtml_default_style (line 547) | def tkhtml_default_style(self):
    method images (line 554) | def images(self):
    method style_report (line 562) | def style_report(self):
    method _check_queue (line 570) | def _check_queue(self):
    method _end_queue (line 579) | def _end_queue(self):
    method post_to_queue (line 585) | def post_to_queue(self, callback, thread_safe=True):
    method post_event (line 596) | def post_event(self, event, thread_safe=False):
    method _post_event (line 608) | def _post_event(self, event):
    method post_message (line 616) | def post_message(self, message, thread_safe=False):
    method _post_message (line 628) | def _post_message(self, message):
    method parse (line 639) | def parse(self, html, thread_safe=False):
    method _parse (line 660) | def _parse(self, html):
    method _handle_load_finish (line 682) | def _handle_load_finish(self, post_event=True):
    method parse_css (line 700) | def parse_css(self, sheetid=None, data="", url=None, fallback_priority...
    method reset (line 730) | def reset(self, thread_safe=False):
    method _reset (line 745) | def _reset(self):
    method stop (line 766) | def stop(self):
    method resolve_url (line 772) | def resolve_url(self, url, base=None):
    method download_url (line 779) | def download_url(self, url, *args):
    method _check_url_cache_state (line 788) | def _check_url_cache_state(self, url, *args):
    method _thread_check (line 791) | def _thread_check(self, callback, url, *args, **kwargs):
    method _begin_download (line 802) | def _begin_download(self):
    method _finish_download (line 810) | def _finish_download(self, thread):
    method _finish_resource_load (line 825) | def _finish_resource_load(self, message, url, resource, success):
    method node (line 835) | def node(self, *args):
    method text (line 843) | def text(self, subcommand, *args):
    method tag (line 847) | def tag(self, subcommand, tag_name, *args):
    method search (line 851) | def search(self, selector, *a, cnf={}, **kw):
    method xview (line 855) | def xview(self, *args, auto_scroll=False):
    method xview_scroll (line 866) | def xview_scroll(self, number, what, auto_scroll=False):
    method xview_moveto (line 871) | def xview_moveto(self, number, auto_scroll=False):
    method yview (line 875) | def yview(self, *args, auto_scroll=False):
    method yview_scroll (line 882) | def yview_scroll(self, number, what, auto_scroll=False):
    method yview_moveto (line 887) | def yview_moveto(self, number, auto_scroll=False):
    method bbox (line 891) | def bbox(self, node=None):
    method parse_fragment_simple (line 895) | def parse_fragment_simple(self, html):
    method parse_fragment (line 898) | def parse_fragment(self, html):
    method get_node_text (line 912) | def get_node_text(self, node_handle, *args):
    method set_node_text (line 916) | def set_node_text(self, node_handle, new):
    method relayout (line 921) | def relayout(self):
    method get_child_text (line 924) | def get_child_text(self, node):
    method get_node_tag (line 933) | def get_node_tag(self, node_handle):
    method get_node_parent (line 937) | def get_node_parent(self, node_handle):
    method get_node_children (line 941) | def get_node_children(self, node_handle):
    method get_node_attribute (line 945) | def get_node_attribute(self, node_handle, attribute, default="", value...
    method set_node_attribute (line 952) | def set_node_attribute(self, node_handle, attribute, value):
    method get_node_attributes (line 956) | def get_node_attributes(self, node_handle):
    method get_node_property (line 961) | def get_node_property(self, node_handle, node_property, *args):
    method get_node_properties (line 965) | def get_node_properties(self, node_handle, *args):
    method set_node_property (line 970) | def set_node_property(self, node_handle, node_property, new_value, *ar...
    method override_node_properties (line 977) | def override_node_properties(self, node_handle, *props):
    method insert_node (line 982) | def insert_node(self, node_handle, child_nodes):
    method insert_node_before (line 986) | def insert_node_before(self, node_handle, child_nodes, before):
    method replace_node_contents (line 990) | def replace_node_contents(self, node_handle, contents, *args, check=Tr...
    method get_node_replacement (line 1002) | def get_node_replacement(self, node_handle):
    method delete_node (line 1008) | def delete_node(self, node_handle):
    method destroy_node (line 1030) | def destroy_node(self, node_handle):
    method set_node_flags (line 1034) | def set_node_flags(self, node, name):
    method remove_node_flags (line 1038) | def remove_node_flags(self, node, name):
    method get_node_tkhtml (line 1042) | def get_node_tkhtml(self, node_handle):
    method get_node_stacking (line 1046) | def get_node_stacking(self, node_handle):
    method get_current_hovered_node (line 1053) | def get_current_hovered_node(self, event):
    method get_current_hovered_node_parent (line 1062) | def get_current_hovered_node_parent(self, node):
    method register_handler (line 1066) | def register_handler(self, handler_type, node_tag, callback):
    method _lazy_handler (line 1070) | def _lazy_handler(self, manager, method):
    method register_lazy_handler (line 1076) | def register_lazy_handler(self, handler_type, node_tag, manager_name):
    method image (line 1089) | def image(self, full=False):
    method postscript (line 1101) | def postscript(self, cnf={}, **kwargs):
    method preload_image (line 1109) | def preload_image(self, url):
    method get_computed_styles (line 1114) | def get_computed_styles(self):
    method override_node_CSS (line 1118) | def override_node_CSS(self, node, *props):
    method write (line 1125) | def write(self, *arg, cnf={}, **kw):
    method _on_image_cmd (line 1133) | def _on_image_cmd(self, url):
    method _on_draw_cleanup_crash_cmd (line 1137) | def _on_draw_cleanup_crash_cmd(self):
    method _crash_prevention (line 1144) | def _crash_prevention(self, data):
    method _remove_noto_emoji (line 1172) | def _remove_noto_emoji(self, match):
    method _generate_altered_colour (line 1180) | def _generate_altered_colour(self, match, matchtype=1):
    method _dark_mode (line 1237) | def _dark_mode(self, html):
    method _css_dark_mode (line 1244) | def _css_dark_mode(self, data):
    method safe_tk_eval (line 1251) | def safe_tk_eval(self, expr):
    method serialize_node (line 1261) | def serialize_node(self, ib=3):
    method serialize_node_style (line 1296) | def serialize_node_style(self, ib=3, return_as_dict=False):
    method tkhtml_offset_to_text_index (line 1317) | def tkhtml_offset_to_text_index(self, node, offset, invert=False):
    method _set_cursor (line 1337) | def _set_cursor(self, cursor):
    method _finish_scrolling (line 1357) | def _finish_scrolling(self, event, widget, x11, vsb):
    method _scroll_x11 (line 1402) | def _scroll_x11(self, event, widget=None): self._finish_scrolling(even...
    method _xscroll_x11 (line 1403) | def _xscroll_x11(self, event, widget=None): self._finish_scrolling(eve...
    method _scroll (line 1404) | def _scroll(self, event): self._finish_scrolling(event, self, False, T...
    method _xscroll (line 1405) | def _xscroll(self, event): self._finish_scrolling(event, self, False, ...
    method _on_right_click (line 1407) | def _on_right_click(self, event):
    method _on_middle_click (line 1412) | def _on_middle_click(self, event):
    method _on_focusout (line 1416) | def _on_focusout(self, event):
    method _on_focusin (line 1425) | def _on_focusin(self, event):
    method _on_up (line 1428) | def _on_up(self, event):
    method _on_down (line 1434) | def _on_down(self, event):
    method _on_left (line 1440) | def _on_left(self, event):
    method _on_right (line 1443) | def _on_right(self, event):
    method _on_prior (line 1446) | def _on_prior(self, event): self.yview_scroll(-1, "pages")
    method _on_next (line 1448) | def _on_next(self, event): self.yview_scroll(1, "pages")
    method _on_home (line 1450) | def _on_home(self, event): self.yview_moveto(0)
    method _on_end (line 1452) | def _on_end(self, event): self.yview_moveto(1)
    method _on_click (line 1454) | def _on_click(self, event, redirected=False):
    method _on_leave (line 1484) | def _on_leave(self, event=None):
    method _handle_recursive_hovering (line 1498) | def _handle_recursive_hovering(self, event, node_handle, prev_hovered_...
    method _on_mouse_motion (line 1515) | def _on_mouse_motion(self, event):
    method _on_click_release (line 1582) | def _on_click_release(self, event):
    method _on_double_click (line 1640) | def _on_double_click(self, event):
    method _extend_selection (line 1652) | def _extend_selection(self, event):
    method _copy_selection (line 1678) | def _copy_selection(self, event):
    method _select_all (line 1681) | def _select_all(self, event):
    method update_tags (line 1686) | def update_tags(self):
    method select_all (line 1690) | def select_all(self):
    method clear_selection (line 1694) | def clear_selection(self):
    method update_selection (line 1698) | def update_selection(self):
    method get_selection (line 1702) | def get_selection(self):
    method copy_selection (line 1706) | def copy_selection(self, event=None):
    method allocate_image_name (line 1710) | def allocate_image_name(self):
    method handle_node_replacement (line 1714) | def handle_node_replacement(self, node, widgetid, deletecmd, stylecmd=...
    method map_node (line 1718) | def map_node(self, node, force=False):
    method replace_node_with_widget (line 1722) | def replace_node_with_widget(self, node, widgetid):
    method find_text (line 1726) | def find_text(self, searchtext, select, ignore_case, highlight_all):
    method send_onload (line 1730) | def send_onload(self, root=None, children=None):
    method decode_uri (line 1736) | def decode_uri(self, uri, base64=False):
    method encode_uri (line 1743) | def encode_uri(self, uri):
    method escape_uri (line 1749) | def escape_uri(self, uri, query=False):
  class TkHtmlParsedURI (line 1756) | class TkHtmlParsedURI:
    method __init__ (line 1763) | def __init__(self, uri, html):
    method __repr__ (line 1767) | def __repr__(self):
    method __str__ (line 1770) | def __str__(self):
    method __del__ (line 1773) | def __del__(self):
    method uri (line 1776) | def uri(self, uri):
    method tkhtml_uri_decode (line 1780) | def tkhtml_uri_decode(self, uri, base64=False):
    method tkhtml_uri_encode (line 1784) | def tkhtml_uri_encode(self, uri):
    method tkhtml_uri_escape (line 1788) | def tkhtml_uri_escape(self, uri, query=False):
    method uri_resolve (line 1792) | def uri_resolve(self, uri):
    method load (line 1797) | def load(self, uri):
    method get (line 1802) | def get(self):
    method defrag (line 1807) | def defrag(self):
    method scheme (line 1812) | def scheme(self):
    method authority (line 1817) | def authority(self):
    method path (line 1822) | def path(self):
    method query (line 1827) | def query(self):
    method fragment (line 1832) | def fragment(self):
    method splitfrag (line 1837) | def splitfrag(self):
    method destroy (line 1841) | def destroy(self):

FILE: tkinterweb/dom.py
  function escape_Tcl (line 29) | def escape_Tcl(string):
  function extract_nested (line 40) | def extract_nested(nested):
  function camel_case_to_property (line 46) | def camel_case_to_property(string):
  function DOM_element_events (line 56) | def DOM_element_events(cls):  # class
  class HTMLDocument (line 75) | class HTMLDocument:
    method __init__ (line 79) | def __init__(self, html):
    method __repr__ (line 84) | def __repr__(self):
    method body (line 88) | def body(self):  # Taken from hv3_dom_html.tcl line 161
    method documentElement (line 97) | def documentElement(self):
    method write (line 105) | def write(self, *text):
    method createElement (line 118) | def createElement(self, tagname):  # Taken from hv3_dom_core.tcl line 214
    method createTextNode (line 132) | def createTextNode(self, text):
    method getElementById (line 156) | def getElementById(self, query, _root=None):
    method getElementsByClassName (line 165) | def getElementsByClassName(self, query, _root=None):
    method getElementsByName (line 174) | def getElementsByName(self, query, _root=None):
    method getElementsByTagName (line 183) | def getElementsByTagName(self, query, _root=None):
    method querySelector (line 192) | def querySelector(self, query, _root=None):
    method querySelectorAll (line 201) | def querySelectorAll(self, query, _root=None):
    method _node_to_html (line 210) | def _node_to_html(self, node, deep=True):  # From hv3_dom_core.tcl lin...
  class HTMLElement (line 246) | class HTMLElement:
    method __init__ (line 258) | def __init__(self, document_manager, node):
    method __repr__ (line 274) | def __repr__(self):
    method __eq__ (line 277) | def __eq__(self, other):
    method __hash__ (line 282) | def __hash__(self):
    method style (line 300) | def style(self):
    method outerHTML (line 309) | def outerHTML(self):
    method innerHTML (line 313) | def innerHTML(self):  # Taken from hv3_dom2.tcl line 61
    method innerHTML (line 331) | def innerHTML(self, contents):  # Taken from hv3_dom2.tcl line 88
    method innerText (line 364) | def innerText(self):  # Original for this project
    method innerText (line 384) | def innerText(self, contents):  # Ditto
    method textContent (line 388) | def textContent(self):  # Original for this project
    method textContent (line 406) | def textContent(self, contents):  # Ditto
    method id (line 432) | def id(self):
    method id (line 441) | def id(self, new):
    method className (line 445) | def className(self):
    method className (line 454) | def className(self, new):
    method attributes (line 458) | def attributes(self):
    method tagName (line 468) | def tagName(self):
    method parentElement (line 475) | def parentElement(self):
    method children (line 483) | def children(self):
    method previousSibling (line 491) | def previousSibling(self):
    method nextSibling (line 500) | def nextSibling(self):
    method widget (line 509) | def widget(self): # Not a real JS property, but still useful
    method widget (line 526) | def widget(self, widget): # Not a real JS property, but still useful
    method value (line 540) | def value(self):
    method value (line 555) | def value(self, value):
    method checked (line 564) | def checked(self):
    method checked (line 578) | def checked(self, value):
    method getAttribute (line 582) | def getAttribute(self, attribute):
    method setAttribute (line 593) | def setAttribute(self, attribute, value):
    method removeAttribute (line 605) | def removeAttribute(self, attribute):
    method remove (line 614) | def remove(self):
    method appendChild (line 624) | def appendChild(self, children):
    method insertBefore (line 631) | def insertBefore(self, children, before):
    method getElementById (line 641) | def getElementById(self, query):
    method getElementsByClassName (line 650) | def getElementsByClassName(self, query):
    method getElementsByName (line 659) | def getElementsByName(self, query):
    method getElementsByTagName (line 668) | def getElementsByTagName(self, query):
    method querySelector (line 677) | def querySelector(self, query):
    method querySelectorAll (line 686) | def querySelectorAll(self, query):
    method scrollIntoView (line 695) | def scrollIntoView(self):
    method getBoundingClientRect (line 699) | def getBoundingClientRect(self):
    method _insert_children (line 705) | def _insert_children(self, children, before=None):
    method _find_siblings (line 719) | def _find_siblings(self, reverse=False):
    method bind (line 733) | def bind(self, sequence, func, add=None):
    method unbind (line 766) | def unbind(self, sequence, funcid=None):
  class HTMLCollection (line 778) | class HTMLCollection:
    method __init__ (line 791) | def __init__(self, document_manager, search_string, root=None):
    method __repr__ (line 797) | def __repr__(self):
    method __iter__ (line 802) | def __iter__(self):
    method __getitem__ (line 806) | def __getitem__(self, index):
    method __len__ (line 809) | def __len__(self):
    method length (line 813) | def length(self):
    method item (line 817) | def item(self, index):
    method namedItem (line 829) | def namedItem(self, key):
  class DOMRect (line 840) | class DOMRect:
    method __init__ (line 851) | def __init__(self, element_manager):
    method __repr__ (line 860) | def __repr__(self):
  class CSSStyleDeclaration (line 864) | class CSSStyleDeclaration:
    method __init__ (line 871) | def __init__(self, element_manager):
    method __repr__ (line 875) | def __repr__(self):
    method __getitem__ (line 878) | def __getitem__(self, property):
    method __setitem__ (line 913) | def __setitem__(self, property, value):
    method __delitem__ (line 916) | def __delitem__(self, property):
    method __setattr__ (line 945) | def __setattr__(self, property, value):
    method __getattr__ (line 951) | def __getattr__(self, property):
    method cssText (line 955) | def cssText(self):
    method cssText (line 964) | def cssText(self, contents):
    method length (line 968) | def length(self):
    method cssProperties (line 975) | def cssProperties(self): # Not a JS function, but still useful
    method cssInlineProperties (line 982) | def cssInlineProperties(self):
    method cssInlineStyles (line 989) | def cssInlineStyles(self):
    method getPropertyPriority (line 996) | def getPropertyPriority(self, property):
    method getPropertyValue (line 1010) | def getPropertyValue(self, property):
    method removeProperty (line 1022) | def removeProperty(self, property):
    method setProperty (line 1033) | def setProperty(self, property, value):

FILE: tkinterweb/extensions.py
  class BlinkyFrame (line 12) | class BlinkyFrame(Frame):
    method __init__ (line 14) | def __init__(self, master, *args, blink_delays=[600, 300], **kwargs):
    method place (line 23) | def place(self, x, y, *args, _internal=False, **kwargs):
    method place_forget (line 35) | def place_forget(self, _internal=False):
    method _blink (line 43) | def _blink(self):
  class SelectionManager (line 56) | class SelectionManager(utilities.BaseManager):
    method __init__ (line 73) | def __init__(self, html):
    method __repr__ (line 82) | def __repr__(self):
    method begin_selection (line 85) | def begin_selection(self, node, offset):
    method reset (line 90) | def reset(self):
    method reset_selection_type (line 94) | def reset_selection_type(self):
    method clear_selection (line 98) | def clear_selection(self):
    method update_tags (line 104) | def update_tags(self):
    method select_all (line 110) | def select_all(self):
    method _word_in_node (line 124) | def _word_in_node(self, node, offset):
    method double_click_selection (line 144) | def double_click_selection(self):
    method extend_selection (line 170) | def extend_selection(self, node, offset):
    method update_selection (line 200) | def update_selection(self):
    method get_selection (line 220) | def get_selection(self):
    method copy_selection (line 276) | def copy_selection(self):
  class CaretManager (line 284) | class CaretManager(utilities.BaseManager):
    method __init__ (line 305) | def __init__(self, html):
    method __repr__ (line 321) | def __repr__(self):
    method set (line 324) | def set(self, node, offset, recalculate=False):
    method is_placed (line 350) | def is_placed(self):
    method register_nodes_from_index (line 354) | def register_nodes_from_index(self, event, index, update_caret_start=F...
    method shift_up (line 376) | def shift_up(self, event=None, update=True):
    method shift_down (line 414) | def shift_down(self, event=None, update=True):
    method shift_left (line 451) | def shift_left(self, event=None, update_caret_start=True, update=True):
    method shift_right (line 486) | def shift_right(self, event=None, update_caret_start=True, update=True):
    method update (line 523) | def update(self, event=None, auto_scroll=True, fallback=None, update=T...
    method hide (line 578) | def hide(self):
    method reset (line 583) | def reset(self):
    method _scroll_if_needed (line 590) | def _scroll_if_needed(self, node_start, node_end, viewport_start, view...
  class EventManager (line 623) | class EventManager(utilities.BaseManager):
    method __init__ (line 639) | def __init__(self, html):
    method __repr__ (line 645) | def __repr__(self):
    method reset (line 650) | def reset(self):
    method post_event (line 660) | def post_event(self, node, JS_event_name, event=None, Tk_event_name=No...
    method create_modified_event (line 682) | def create_modified_event(self, node, event, Tk_event_name=None):
    method create_new_event (line 701) | def create_new_event(self, node, Tk_event_name):
    method _on_demand_binding_callback (line 712) | def _on_demand_binding_callback(self, event, name):
    method _check_binding_name (line 731) | def _check_binding_name(self, event):
    method bind (line 741) | def bind(self, node, event, callback, add=None):
    method unbind (line 757) | def unbind(self, node, event, funcid=None):
    method post_element_event (line 780) | def post_element_event(self, node_handle, attribute, event=None, event...
    method send_onload (line 806) | def send_onload(self, root=None, children=None):
  class WidgetManager (line 824) | class WidgetManager(utilities.BaseManager):
    method __init__ (line 837) | def __init__(self, html):
    method __repr__ (line 844) | def __repr__(self):
    method reset (line 847) | def reset(self):
    method get_node_widget (line 850) | def get_node_widget(self, node):
    method handle_node_replacement (line 859) | def handle_node_replacement(self, node, widgetid, deletecmd, stylecmd=...
    method _handle_node_removal (line 903) | def _handle_node_removal(self, widgetid):
    method _handle_node_style (line 906) | def _handle_node_style(self, node, widgetid, widgettype="button"):
    method map_node (line 955) | def map_node(self, node, force=False):
    method set_node_widget (line 960) | def set_node_widget(self, node, widgetid=None):
    method _on_embedded_mouse_enter (line 1036) | def _on_embedded_mouse_enter(self, event, node_handle):
    method _on_embedded_mouse_leave (line 1040) | def _on_embedded_mouse_leave(self, event, node_handle):
  class SearchManager (line 1047) | class SearchManager(utilities.BaseManager):
    method __init__ (line 1058) | def __init__(self, html):
    method __repr__ (line 1061) | def __repr__(self):
    method clear_tags (line 1064) | def clear_tags(self):
    method update_tags (line 1070) | def update_tags(self, selected, matches):
    method find_text (line 1123) | def find_text(self, searchtext, select, ignore_case, highlight_all, te...

FILE: tkinterweb/handlers.py
  class NodeManager (line 13) | class NodeManager(utilities.BaseManager):
    method __init__ (line 15) | def __init__(self, html):
    method __repr__ (line 20) | def __repr__(self):
    method reset (line 23) | def reset(self):
    method _on_title (line 28) | def _on_title(self, node):
    method _on_base (line 35) | def _on_base(self, node):
    method _on_meta (line 41) | def _on_meta(self, node):
    method _on_a (line 56) | def _on_a(self, node):
    method _on_a_value_change (line 67) | def _on_a_value_change(self, node, attribute, value):
    method _handle_link_click (line 75) | def _handle_link_click(self, node_handle):
    method _on_body (line 88) | def _on_body(self, node, index):
    method _on_html (line 95) | def _on_html(self, node, index):
    method _handle_overflow_property (line 98) | def _handle_overflow_property(self, overflow, overflow_function):
    method _set_overflow (line 109) | def _set_overflow(self, node):
    method _is_open (line 142) | def _is_open(self, node):
    method _update_details (line 145) | def _update_details(self, node, display):
    method _set_open (line 160) | def _set_open(self, node, display):
    method _close_other_details (line 166) | def _close_other_details(self, node):
    method _on_details (line 176) | def _on_details(self, node):
    method _on_details_value_change (line 183) | def _on_details_value_change(self, node, attribute, value):
    method _handle_load_finish (line 192) | def _handle_load_finish(self):
    method _handle_summary_click (line 199) | def _handle_summary_click(self, node):
    method _on_progress (line 210) | def _on_progress(self, node):
    method _on_progress_value_change (line 215) | def _on_progress_value_change(self, node, attribute, value):
  class FormManager (line 224) | class FormManager(utilities.BaseManager):
    method __init__ (line 226) | def __init__(self, html):
    method __repr__ (line 236) | def __repr__(self):
    method reset (line 239) | def reset(self):
    method _handle_form_reset (line 246) | def _handle_form_reset(self, node):
    method _handle_form_submission (line 272) | def _handle_form_submission(self, node, event=None):
    method _on_form (line 334) | def _on_form(self, node):
    method _on_table (line 347) | def _on_table(self, node):
    method _on_select (line 374) | def _on_select(self, node):
    method _on_select_value_change (line 411) | def _on_select_value_change(self, node, attribute, value):
    method _on_textarea (line 416) | def _on_textarea(self, node):
    method _on_input (line 438) | def _on_input(self, node):
    method _on_input_value_change (line 547) | def _on_input_value_change(self, node, attribute, value):
    method _on_input_change (line 568) | def _on_input_change(self, node, widgetid):
  class ScriptManager (line 573) | class ScriptManager(utilities.BaseManager):
    method __init__ (line 575) | def __init__(self, html):
    method __repr__ (line 579) | def __repr__(self):
    method _on_script (line 582) | def _on_script(self, attributes, tag_contents):
    method fetch_scripts (line 599) | def fetch_scripts(self, url=None, attributes="", data=None):
    method _submit_deferred_scripts (line 625) | def _submit_deferred_scripts(self):
  class StyleManager (line 632) | class StyleManager(utilities.BaseManager):
    method __init__ (line 634) | def __init__(self, html):
    method __repr__ (line 637) | def __repr__(self):
    method _on_style (line 640) | def _on_style(self, attributes, tag_contents):
    method _on_link (line 646) | def _on_link(self, node):
    method _on_atimport (line 667) | def _on_atimport(self, parent_url, new_url, media=None):
    method _fix_css_urls (line 677) | def _fix_css_urls(self, match, url):
    method fetch_styles (line 685) | def fetch_styles(self, url=None, node=None, media=None):
    method _finish_fetching_styles (line 705) | def _finish_fetching_styles(self, node=None, url=None, data=None):
  class ImageManager (line 720) | class ImageManager(utilities.BaseManager):
    method __init__ (line 722) | def __init__(self, html):
    method __repr__ (line 731) | def __repr__(self):
    method reset (line 734) | def reset(self):
    method _on_img (line 738) | def _on_img(self, node):
    method _on_img_value_change (line 743) | def _on_img_value_change(self, node, attribute, value):
    method load_alt_text (line 767) | def load_alt_text(self, url, name):
    method _on_image_cmd (line 789) | def _on_image_cmd(self, url):
    method fetch_images (line 818) | def fetch_images(self, url, name):
    method check_images (line 843) | def check_images(self, data, name, url, filetype, thread_safe):
    method finish_fetching_images (line 864) | def finish_fetching_images(self, data, name, url, filetype, data_is_im...
    method _on_image_error (line 886) | def _on_image_error(self, url, name, error):
    method _on_image_delete (line 893) | def _on_image_delete(self, name):
    method _finish_image_delete (line 897) | def _finish_image_delete(self, name):
    method allocate_image_name (line 902) | def allocate_image_name(self):
  class ObjectManager (line 908) | class ObjectManager(utilities.BaseManager):
    method __init__ (line 910) | def __init__(self, html):
    method __repr__ (line 915) | def __repr__(self):
    method reset (line 918) | def reset(self):
    method _on_iframe (line 923) | def _on_iframe(self, node):
    method _on_iframe_value_change (line 940) | def _on_iframe_value_change(self, node, attribute, value):
    method _create_iframe (line 952) | def _create_iframe(self, node, url, html=None, vertical_scrollbar="aut...
    method _on_object (line 999) | def _on_object(self, node, data=None):
    method _on_object_value_change (line 1020) | def _on_object_value_change(self, node, attribute, value):
    method fetch_objects (line 1029) | def fetch_objects(self, url, node):
    method _finish_fetching_image_objects (line 1053) | def _finish_fetching_image_objects(self, node, data, name, url, filety...
    method _finish_fetching_HTML_objects (line 1060) | def _finish_fetching_HTML_objects(self, node, data, url, filetype):

FILE: tkinterweb/htmlwidgets.py
  class HtmlFrame (line 17) | class HtmlFrame(Frame):
    method __init__ (line 147) | def __init__(self, master, *,
    method title (line 373) | def title(self):
    method icon (line 380) | def icon(self):
    method current_url (line 387) | def current_url(self):
    method base_url (line 394) | def base_url(self):
    method document (line 401) | def document(self):
    method javascript (line 412) | def javascript(self):
    method html (line 423) | def html(self):
    method grid_propagate (line 429) | def grid_propagate(self, *args, **kwargs):
    method pack_propagate (line 434) | def pack_propagate(self, *args, **kwargs):
    method load_html (line 439) | def load_html(self, html_source, base_url=None, fragment=None):
    method _load_html (line 459) | def _load_html(self, html_source, base_url=None, fragment=None, _threa...
    method _finish_loading_html (line 475) | def _finish_loading_html(self):
    method load_file (line 481) | def load_file(self, file_url, decode=None, force=False):
    method load_website (line 497) | def load_website(self, website_url, decode=None, force=False):
    method load_url (line 511) | def load_url(self, url, decode=None, force=False):
    method load_form_data (line 551) | def load_form_data(self, url, data, method="GET", decode=None, force=F...
    method reload (line 575) | def reload(self):
    method add_html (line 588) | def add_html(self, html_source, return_element=False, index=-1):
    method insert_html (line 620) | def insert_html(self, html_source, index=0, return_element=False):
    method add_css (line 624) | def add_css(self, css_source, priority="author"):
    method import_css (line 637) | def import_css(self, url):
    method stop (line 646) | def stop(self):
    method find_text (line 654) | def find_text(self, text, select=1, ignore_case=True, highlight_all=Tr...
    method widget_to_element (line 676) | def widget_to_element(self, widget):
    method screenshot_page (line 691) | def screenshot_page(self, filename=None, full=False, show=False):
    method print_page (line 737) | def print_page(self, filename=None, cnf={}, **kwargs):
    method save_page (line 775) | def save_page(self, filename=None):
    method snapshot_page (line 813) | def snapshot_page(self, filename=None, allow_agent=False, include_head...
    method get_page_text (line 856) | def get_page_text(self):
    method show_error_page (line 865) | def show_error_page(self, url, error, code):
    method resolve_url (line 880) | def resolve_url(self, url):
    method yview (line 889) | def yview(self, *args):
    method yview_moveto (line 895) | def yview_moveto(self, number):
    method yview_scroll (line 902) | def yview_scroll(self, number, what):
    method get_currently_hovered_element (line 911) | def get_currently_hovered_element(self, ignore_text_nodes=True):
    method get_caret_position (line 927) | def get_caret_position(self, return_element=True):
    method get_caret_page_position (line 955) | def get_caret_page_position(self):
    method set_caret_position (line 961) | def set_caret_position(self, element=None, index=0):
    method set_caret_page_position (line 990) | def set_caret_page_position(self, index):
    method shift_caret_left (line 994) | def shift_caret_left(self):
    method shift_caret_right (line 1006) | def shift_caret_right(self):
    method get_selection_position (line 1018) | def get_selection_position(self, return_elements=True):
    method get_selection_page_position (line 1098) | def get_selection_page_position(self):
    method set_selection_position (line 1104) | def set_selection_position(self, start_element=None, start_index=0, en...
    method set_selection_page_position (line 1146) | def set_selection_page_position(self, start_index, end_index):
    method get_selection (line 1150) | def get_selection(self):
    method clear_selection (line 1157) | def clear_selection(self):
    method select_all (line 1161) | def select_all(self):
    method _handle_html_resize (line 1170) | def _handle_html_resize(self, event=None, force=False):
    method _handle_frame_resize (line 1190) | def _handle_frame_resize(self, event):
    method _adjust_allow (line 1212) | def _adjust_allow(self, allow):
    method _manage_vsb (line 1223) | def _manage_vsb(self, allow=None, check=False):
    method _manage_hsb (line 1233) | def _manage_hsb(self, allow=None, check=False):
    method _get_about_page (line 1243) | def _get_about_page(self, url, i1="", i2=""):
    method _update_current_url (line 1256) | def _update_current_url(self, url, thread_safe=True):
    method _continue_loading (line 1262) | def _continue_loading(self, url, data="", method="GET", decode=None, f...
    method _finish_loading_image (line 1349) | def _finish_loading_image(self, data, name, url, filetype, data_is_ima...
    method _finish_loading_nothing (line 1357) | def _finish_loading_nothing(self):
    method _finish_loading_error (line 1367) | def _finish_loading_error(self, url, error, code):
    method _finish_css (line 1380) | def _finish_css(self):
    method register_JS_object (line 1391) | def register_JS_object(self, name, obj):
    method _on_script (line 1395) | def _on_script(self, attributes, tag_contents):
    method _on_element_script (line 1398) | def _on_element_script(self, node_handle, attribute, attr_contents):
    method generate_style_report (line 1401) | def generate_style_report(self, return_report=False):
    method _check_options (line 1425) | def _check_options(self, options, init_args, kwargs, set_attr=False):
    method _check_value (line 1437) | def _check_value(self, key, settings, value):
    method _check_changeability (line 1472) | def _check_changeability(self, key, settings):
    method configure (line 1477) | def configure(self, **kwargs):
    method config (line 1510) | def config(self, _override=False, **kwargs):
    method cget (line 1519) | def cget(self, key):
    method bind (line 1531) | def bind(self, sequence, *args, **kwargs):
    method unbind (line 1538) | def unbind(self, sequence, *args, **kwargs):
    method __getitem__ (line 1545) | def __getitem__(self, key):
    method __setitem__ (line 1548) | def __setitem__(self, key, value):
  class HtmlLabel (line 1552) | class HtmlLabel(HtmlFrame):
    method __init__ (line 1565) | def __init__(self, master, *, text="", **kwargs):
    method load_html (line 1586) | def load_html(self, *args, _relayout=True, **kwargs):
    method configure (line 1608) | def configure(self, **kwargs):
    method cget (line 1618) | def cget(self, key):
    method config (line 1626) | def config(self, **kwargs):
  class HtmlText (line 1630) | class HtmlText(HtmlFrame):
    method __init__ (line 1661) | def __init__(self, master, *, background="#ffffff", foreground="#00000...
    method load_html (line 1692) | def load_html(self, *args, **kwargs):
    method _duplicate (line 1703) | def _duplicate(self, element):
    method _delete (line 1728) | def _delete(self, element):
    method _check_text (line 1734) | def _check_text(self, text):
    method _strip_text (line 1739) | def _strip_text(self, text):
    method _next_in_document (line 1744) | def _next_in_document(self, node):
    method _descendants (line 1758) | def _descendants(self, node):
    method _remove_between (line 1769) | def _remove_between(self, element1, element2):
    method delete (line 1783) | def delete(self, start_index, end_index=None, start_element=None, end_...
    method insert (line 1869) | def insert(self, index, text_or_element, element=None):
    method _on_key_selection (line 1930) | def _on_key_selection(self, event, selection):
    method _on_key (line 1984) | def _on_key(self, event):
    method configure (line 2078) | def configure(self, **kwargs):
    method cget (line 2126) | def cget(self, key):
    method config (line 2153) | def config(self, **kwargs):
  class HtmlParse (line 2158) | class HtmlParse(HtmlFrame):
    method __init__ (line 2165) | def __init__(self, **kwargs): #markup=""
    method __str__ (line 2185) | def __str__(self):
    method destroy (line 2188) | def destroy(self):

FILE: tkinterweb/imageutils.py
  function load_cairo (line 20) | def load_cairo():
  function photoimage_del (line 47) | def photoimage_del(image):
  function invert_image (line 57) | def invert_image(image, limit):
  function svg_to_png (line 94) | def svg_to_png(data):
  function data_to_image (line 118) | def data_to_image(data, name, imagetype, data_is_image):
  function blank_image (line 140) | def blank_image(name):
  function create_RGB_image (line 144) | def create_RGB_image(data, w, h):

FILE: tkinterweb/js.py
  class JSEngine (line 15) | class JSEngine:
    method __init__ (line 25) | def __init__(self, html, document, backend):
    method __repr__ (line 35) | def __repr__(self):
    method register (line 38) | def register(self, name, obj):
    method eval (line 59) | def eval(self, expr, _this=None):
    method _initialize_javascript (line 93) | def _initialize_javascript(self):
    method _initialize_exec_context (line 102) | def _initialize_exec_context(self):
    method _on_script (line 110) | def _on_script(self, attributes, tag_contents):
    method _on_element_script (line 121) | def _on_element_script(self, node_handle, attribute, attr_contents):

FILE: tkinterweb/subwidgets.py
  function load_combobox (line 21) | def load_combobox(master, force=False):
  class Combobox (line 29) | class Combobox(tk.Widget):
    method __init__ (line 32) | def __init__(self, master):
    method insert (line 53) | def insert(self, data, values, selected):
    method set (line 62) | def set(self, value):
    method reset (line 66) | def reset(self):
    method get (line 69) | def get(self):
  class AutoScrollbar (line 74) | class AutoScrollbar(ttk.Scrollbar):
    method __init__ (line 76) | def __init__(self, *args, **kwargs):
    method set (line 81) | def set(self, low, high):
    method set_type (line 97) | def set_type(self, scroll, low, high):
    method pack (line 102) | def pack(self, **kwargs):
    method place (line 105) | def place(self, **kwargs):
  class ScrolledTextBox (line 109) | class ScrolledTextBox(tk.Frame):
    method __init__ (line 112) | def __init__(self, parent, content="", onchangecommand=None, **kwargs):
    method _on_paste (line 150) | def _on_paste(self, event):
    method select_all (line 158) | def select_all(self, event):
    method scroll (line 164) | def scroll(self, event):
    method scroll_x11 (line 171) | def scroll_x11(self, event):
    method configure (line 178) | def configure(self, *args, **kwargs):
    method insert (line 181) | def insert(self, *args, **kwargs):
    method get (line 184) | def get(self, *args, **kwargs):
    method delete (line 189) | def delete(self, *args, **kwargs):
    method set (line 192) | def set(self, value):
  class FormEntry (line 198) | class FormEntry(tk.Entry):
    method __init__ (line 199) | def __init__(self, parent, value="", placeholder="", entry_type="", on...
    method placeholder (line 223) | def placeholder(self):
    method placeholder (line 227) | def placeholder(self, value):
    method _halfway (line 233) | def _halfway(self, bg=None):
    method _on_key_press (line 241) | def _on_key_press(self, event):
    method _on_click (line 255) | def _on_click(self, event):
    method _on_motion (line 261) | def _on_motion(self, event):
    method _on_paste (line 265) | def _on_paste(self, event):
    method _select_all (line 269) | def _select_all(self, event):
    method set (line 277) | def set(self, value):
    method _show_placeholder (line 287) | def _show_placeholder(self):
    method _hide_placeholder (line 293) | def _hide_placeholder(self):
    method get (line 298) | def get(self):
    method configure (line 304) | def configure(self, **kwargs):
    method config (line 326) | def config(self, **kwargs):
  class FormCheckbox (line 329) | class FormCheckbox(ttk.Checkbutton):
    method __init__ (line 330) | def __init__(self, parent, value=0, onchangecommand=None, **kwargs):
  class FormRadioButton (line 345) | class FormRadioButton(ttk.Checkbutton):
    method __init__ (line 346) | def __init__(self, parent, token, value=0, checked=False, variable=Non...
    method set (line 367) | def set(self, value):
    method get (line 370) | def get(self):
  class FormRange (line 373) | class FormRange(ttk.Scale):
    method __init__ (line 374) | def __init__(self, parent, value=50, from_=0, to=100, step=1, onchange...
    method _update_value (line 388) | def _update_value(self, *args):
    method _check_value (line 393) | def _check_value(self, value, default):
    method configure (line 399) | def configure(self, **kwargs):
  class Tooltip (line 428) | class Tooltip:
    method __init__ (line 429) | def __init__(self, widget, text=""):
    method show (line 436) | def show(self, text=None):
    method hide (line 461) | def hide(self, event=None):
  class FormNumber (line 472) | class FormNumber(tk.Spinbox):
    method __init__ (line 473) | def __init__(self, parent, value=0, from_=0, to=100, step=1, onchangec...
    method _on_paste (line 490) | def _on_paste(self, event):
    method _select_all (line 498) | def _select_all(self, event):
    method _update_value (line 503) | def _update_value(self, *args):
    method check (line 506) | def check(self):
    method _check_value (line 540) | def _check_value(self, value, default):
    method _decimalize_value (line 546) | def _decimalize_value(self, value, default):
    method configure (line 552) | def configure(self, **kwargs):
    method set (line 564) | def set(self, value):
    method get (line 567) | def get(self):
  class FileSelector (line 573) | class FileSelector(tk.Frame):
    method __init__ (line 576) | def __init__(self, parent, accept, multiple, onchangecommand=None, **k...
    method generate_filetypes (line 592) | def generate_filetypes(self, accept):
    method select_file (line 632) | def select_file(self):
    method set (line 659) | def set(self, value):
    method get (line 664) | def get(self):
    method configure (line 667) | def configure(self, *args, **kwargs):
  class ColourSelector (line 677) | class ColourSelector(tk.Frame):
    method __init__ (line 680) | def __init__(self, parent, colour="#000000", onchangecommand=None, **k...
    method select_colour (line 692) | def select_colour(self):
    method set (line 697) | def set(self, colour):
    method get (line 703) | def get(self):
  class Notebook (line 707) | class Notebook(ttk.Frame):
    method __init__ (line 710) | def __init__(self, master, takefocus=True, **kwargs):
    method on_tab_change (line 727) | def on_tab_change(self, event):
    method add (line 739) | def add(self, child, **kwargs):
    method insert (line 747) | def insert(self, where, child, **kwargs):
    method enable_traversal (line 755) | def enable_traversal(self):
    method select (line 759) | def select(self, tabId=None):
    method transcribe (line 768) | def transcribe(self, item, reverse=False):
    method tab (line 771) | def tab(self, tabId, option=None, **kwargs):
    method forget (line 777) | def forget(self, tabId):
    method index (line 787) | def index(self, child):
    method tabs (line 794) | def tabs(self):

FILE: tkinterweb/utilities.py
  class BuiltinPageGenerator (line 464) | class BuiltinPageGenerator():
    method __init__ (line 467) | def __init__(self):
    method __getitem__ (line 570) | def __getitem__(self, key):
    method __len__ (line 614) | def __len__(self):
    method __iter__ (line 617) | def __iter__(self):
    method keys (line 620) | def keys(self):
    method items (line 623) | def items(self):
    method values (line 626) | def values(self):
  class unset (line 693) | class unset:
    method __repr__ (line 695) | def __repr__(self):
  class StoppableThread (line 700) | class StoppableThread(threading.Thread):
    method __init__ (line 703) | def __init__(self, *args, **kwargs):
    method stop (line 710) | def stop(self):
    method isrunning (line 713) | def isrunning(self):
  class PlaceholderThread (line 717) | class PlaceholderThread:
    method __init__ (line 721) | def __init__(self, *args, **kwargs):
    method stop (line 724) | def stop(self):
    method isrunning (line 727) | def isrunning(self):
  class Empty (line 730) | class Empty:
    method __init__ (line 732) | def __init__(self, *args, **kwargs):
    method __call__ (line 734) | def __call__(self, *args, **kwargs):
    method __getattr__ (line 736) | def __getattr__(self, name):
    method __getitem__ (line 738) | def __getitem__(self, key):
    method __bool__ (line 740) | def __bool__(self):
    method __repr__ (line 742) | def __repr__(self):
  class PlaceholderClass (line 747) | class PlaceholderClass:
    method __getattr__ (line 748) | def __getattr__(self, name):
    method __getitem__ (line 751) | def __getitem__(self, key):
  class BaseManager (line 754) | class BaseManager:
    method __init__ (line 755) | def __init__(self, html):
    method reset (line 759) | def reset(self):
  function lazy_manager (line 764) | def lazy_manager(setting):
  function special_setting (line 785) | def special_setting(default=None):
  function download (line 803) | def download(url, data="", method="GET", decode=None, insecure=False, ca...
  class LRUCache (line 857) | class LRUCache:
    method __init__ (line 863) | def __init__(self):
    method check (line 868) | def check(self, url, *args):
    method fetch (line 878) | def fetch(self, url, *args):
    method clear (line 899) | def clear(self):
  function cache_download (line 905) | def cache_download(url, data="", method="GET", decode=None, insecure=Fal...
  function check_download (line 908) | def check_download(url, data="", method="GET", decode=None, insecure=Fal...
  function shorten (line 912) | def shorten(string):
  function get_current_thread (line 919) | def get_current_thread():
  function rgb_to_hex (line 928) | def rgb_to_hex(red, green, blue, *args):
  function invert_color (line 932) | def invert_color(rgb, match, limit):
  function notifier (line 945) | def notifier(text):
  function tkhtml_notifier (line 954) | def tkhtml_notifier(name, text, *args):
  function deprecate (line 963) | def deprecate(name, manager, new_name=None, stacklevel=3):
  function deprecate_param (line 968) | def deprecate_param(name, new_name, stacklevel=3):
  function warn (line 972) | def warn(message, stacklevel=3):
  function TclOpt (line 977) | def TclOpt(options):
  function safe_tk_eval (line 982) | def safe_tk_eval(html, expr):

FILE: tools/preparewheels.py
  function run_shell (line 42) | def run_shell(*args, cwd=ROOT_PATH, is_wheel=False):
Condensed preview — 43 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (575K chars).
[
  {
    "path": ".github/FUNDING.yml",
    "chars": 73,
    "preview": "# These are supported funding model platforms\n\nbuy_me_a_coffee: andereoo\n"
  },
  {
    "path": ".gitignore",
    "chars": 73,
    "preview": "build/\ntkinterweb/__pycache__/\ntkinterweb/resources/\ntkinterweb.egg-info\n"
  },
  {
    "path": ".readthedocs.yaml",
    "chars": 177,
    "preview": "version: \"2\"\n\nbuild:\n  os: \"ubuntu-22.04\"\n  tools:\n    python: \"3.10\"\n\npython:\n  install:\n    - requirements: docs/requi"
  },
  {
    "path": "LICENSE.md",
    "chars": 1074,
    "preview": "MIT License\n\nCopyright (c) 2021-2025 Andrew Clarke\n\nPermission is hereby granted, free of charge, to any person obtainin"
  },
  {
    "path": "MANIFEST.in",
    "chars": 42,
    "preview": "recursive-include tkinterweb/resources *\r\n"
  },
  {
    "path": "README.md",
    "chars": 9163,
    "preview": "![PyPi Downloads](https://static.pepy.tech/badge/tkinterweb/month)\n![MIT Licence](https://img.shields.io/pypi/l/tkinterw"
  },
  {
    "path": "docs/Makefile",
    "chars": 638,
    "preview": "# Minimal makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line, and also\n# from the "
  },
  {
    "path": "docs/make.bat",
    "chars": 799,
    "preview": "@ECHO OFF\r\n\r\npushd %~dp0\r\n\r\nREM Command file for Sphinx documentation\r\n\r\nif \"%SPHINXBUILD%\" == \"\" (\r\n\tset SPHINXBUILD=sp"
  },
  {
    "path": "docs/requirements.txt",
    "chars": 106,
    "preview": "sphinx==7.1.2\nsphinx-rtd-theme==1.3.0rc1\nsphinx_design==0.6.1\ntkinterweb-tkhtml >= 2.0.0\nPillow >= 10.0.0\n"
  },
  {
    "path": "docs/source/_static/custom.css",
    "chars": 4353,
    "preview": "/* This file is used to make tweak the documentation and make it CSS2 compatible.*/\n\n.wy-nav-side {\n  overflow: hidden !"
  },
  {
    "path": "docs/source/api/extensions.rst",
    "chars": 729,
    "preview": "TkinterWeb Extensions\n=====================\n\nThe following objects are extensions to the :class:`~tkinterweb.TkinterWeb`"
  },
  {
    "path": "docs/source/api/htmldocument.rst",
    "chars": 1044,
    "preview": "Document Object Model Documentation\n===================================\n\n.. note::\n    The API changed significantly in "
  },
  {
    "path": "docs/source/api/htmlframe.rst",
    "chars": 2108,
    "preview": "HTML Widgets Documentation\n==========================\n\n.. note::\n    The API changed significantly in version 4. See :do"
  },
  {
    "path": "docs/source/api/jsengine.rst",
    "chars": 206,
    "preview": "JavaScript Engine Documentation\n===============================\n\n\nThe methods described in this page make it easy to int"
  },
  {
    "path": "docs/source/api/notebook.rst",
    "chars": 528,
    "preview": "Notebook Documentation\n=======================\n\nThe TkinterWeb :class:`~tkinterweb.Notebook` widget should be used in pl"
  },
  {
    "path": "docs/source/api/tkinterweb.rst",
    "chars": 111,
    "preview": "Internals Documentation\n=======================\n\n.. toctree ::\n   :maxdepth: 2\n\n   tkinterweb_api\n   extensions"
  },
  {
    "path": "docs/source/api/tkinterweb_api.rst",
    "chars": 598,
    "preview": "Bindings Documentation\n======================\n\n.. note::\n   This API has changed significantly recently. See :doc:`the c"
  },
  {
    "path": "docs/source/api.rst",
    "chars": 338,
    "preview": "API Reference\n==============\n\n.. note::\n    The API changed significantly in version 4. See :doc:`the changelog <upgradi"
  },
  {
    "path": "docs/source/caret.rst",
    "chars": 7400,
    "preview": "Making Documents Editable\n=========================\n\n.. note::\n    Caret browsing support is new in version 4.8. The :cl"
  },
  {
    "path": "docs/source/compatibility.rst",
    "chars": 2905,
    "preview": "System and Webpage Compatibility\n================================\n\nSystem compatibility\n--------------------\n\n**TkinterW"
  },
  {
    "path": "docs/source/conf.py",
    "chars": 1353,
    "preview": "# Configuration file for the Sphinx documentation builder.\n\n# -- Project information\n\nimport os\nimport sys\n\nsys.path.ins"
  },
  {
    "path": "docs/source/dom.rst",
    "chars": 1771,
    "preview": "Manipulating the Page\n=====================\n\n.. note::\n    The API changed significantly in version 4. See :doc:`the cha"
  },
  {
    "path": "docs/source/faq.rst",
    "chars": 3816,
    "preview": "Frequently Asked Questions\n==========================\n\nHow do I load websites or files?\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
  },
  {
    "path": "docs/source/geometry.rst",
    "chars": 2724,
    "preview": "Embedding Widgets\n=================\n\n.. note::\n    The API changed significantly in version 4. See :doc:`the changelog <"
  },
  {
    "path": "docs/source/index.rst",
    "chars": 2800,
    "preview": "Welcome to TkinterWeb!\n======================\n\n.. image:: https://static.pepy.tech/badge/tkinterweb/month\n    :target: h"
  },
  {
    "path": "docs/source/javascript.rst",
    "chars": 5011,
    "preview": "Using JavaScript\n================\n\n.. note::\n    JavaScript support is new in version 4.1. Embedded Python support is ne"
  },
  {
    "path": "docs/source/shrink.rst",
    "chars": 2325,
    "preview": "Creating a Label Widget\n=======================\n\n.. note::\n    This API changed in version 4.17. See :doc:`the changelog"
  },
  {
    "path": "docs/source/upgrading.rst",
    "chars": 30270,
    "preview": "Changelog\n=========\n\n**The API changed significantly in version 4.**\n\n.. dropdown:: Key Changes\n    :open:\n    :color: p"
  },
  {
    "path": "docs/source/usage.rst",
    "chars": 8971,
    "preview": "Getting Started\n===============\n\n.. note::\n    The API changed significantly in version 4. See :doc:`the changelog <upgr"
  },
  {
    "path": "examples/TkinterWebBrowser.py",
    "chars": 40154,
    "preview": "\"\"\"\nA proof-of-concept web browser using TkinterWeb\n\nNote that TkinterWeb is not necessarily intended to be a full-blown"
  },
  {
    "path": "setup.py",
    "chars": 1431,
    "preview": "import pathlib\nfrom setuptools import setup, find_namespace_packages\n\nHERE = pathlib.Path(__file__).parent\nREADME = (HER"
  },
  {
    "path": "tkinterweb/__init__.py",
    "chars": 4713,
    "preview": "\"\"\"\nTkinterWeb v4\nThis is a wrapper for the Tkhtml3 widget from http://tkhtml.tcl.tk/tkhtml.html, \nwhich displays styled"
  },
  {
    "path": "tkinterweb/bindings.py",
    "chars": 75519,
    "preview": "\"\"\"\nThe core Python bindings to Tkhtml3\n\nCopyright (c) 2021-2026 Andrew Clarke\n\"\"\"\n\nfrom re import IGNORECASE, split, su"
  },
  {
    "path": "tkinterweb/dom.py",
    "chars": 40658,
    "preview": "\"\"\"\nA thin wrapper on top of bindings.py that offers some JavaScript-like functions \nand converts Tkhtml nodes into Pyth"
  },
  {
    "path": "tkinterweb/extensions.py",
    "chars": 48388,
    "preview": "\"\"\"\nExtensions to Tkhtml3\n\nCopyright (c) 2021-2025 Andrew Clarke\n\"\"\"\n\nfrom re import IGNORECASE, MULTILINE, finditer\n\nfr"
  },
  {
    "path": "tkinterweb/handlers.py",
    "chars": 48351,
    "preview": "\"\"\"\nNode handlers and associated extensions to Tkhtml3\n\nCopyright (c) 2021-2025 Andrew Clarke\n\"\"\"\n\nimport tkinter as tk\n"
  },
  {
    "path": "tkinterweb/htmlwidgets.py",
    "chars": 112666,
    "preview": "\"\"\"\r\nWidgets that expand on the functionality of the basic bindings\r\nby adding scrolling, file loading, and many other c"
  },
  {
    "path": "tkinterweb/imageutils.py",
    "chars": 5368,
    "preview": "\"\"\"\nGenerate Tk images and alt text\n\nCopyright (c) 2021-2025 Andrew Clarke\n\"\"\"\n\nfrom tkinter import PhotoImage as TkPhot"
  },
  {
    "path": "tkinterweb/js.py",
    "chars": 5571,
    "preview": "\"\"\"\nA simple JavaScript-Tkhtml bridge.\nCopyright (c) 2021-2025 Andrew Clarke\n\"\"\"\n\nfrom . import dom, utilities\n\nfrom tex"
  },
  {
    "path": "tkinterweb/subwidgets.py",
    "chars": 28229,
    "preview": "\"\"\"\r\nVarious constants and utilities used by TkinterWeb\r\n\r\nCopyright (c) 2021-2026 Andrew Clarke\r\n\r\nSome of the CSS code"
  },
  {
    "path": "tkinterweb/utilities.py",
    "chars": 43372,
    "preview": "\"\"\"\r\nVarious constants and utilities used by TkinterWeb\r\n\r\nCopyright (c) 2021-2026 Andrew Clarke\r\n\r\nSome of the CSS code"
  },
  {
    "path": "tools/preparewheels.py",
    "chars": 2013,
    "preview": "\"\"\"\r\nWheel and sdist generator for TkinterWeb\r\n\r\nCopyright (c) 2025 Andrew Clarke\r\n\"\"\"\r\n\r\nimport os, shutil, sys\r\nimport"
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the Andereoo/TkinterWeb GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 43 files (535.1 KB), approximately 124.8k tokens, and a symbol index with 750 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!