Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
1cfa421
Will now "load" mailto links if request_func is specified
Zamy846692 Feb 8, 2026
486c314
Rename parsed URI bindings
Zamy846692 Feb 9, 2026
48f1dfb
"details" and "progress" are now loaded for experimental
Zamy846692 Feb 10, 2026
d3c0fce
HtmlLabel ttk style options are now dynamic converted to CSS rules. R…
Zamy846692 Feb 12, 2026
f41e054
HtmlLabel class's ttk style can now be changed after initialization a…
Zamy846692 Feb 13, 2026
b143d1b
Moved ttk_style_css to be a method of HtmlLabel
Zamy846692 Feb 13, 2026
6d58519
Revert to using the default stylesheet when adding ttk styles to CSS
Zamy846692 Mar 5, 2026
f09b6d0
Fix undefined window variable
Zamy846692 Mar 16, 2026
20eebf9
Simplified indexing syntax-like get/set
Zamy846692 Mar 16, 2026
cf77225
<html> and <body> are added automatically by TkHTML; so removed them …
Zamy846692 Mar 29, 2026
0d65e72
Overhauled HtmlFrame print_page
Zamy846692 May 18, 2026
2b2309c
Fixed errors in Parsed URI bindings
Zamy846692 May 19, 2026
768508b
Fixed error with the HtmlLabel class where the ttk style could not be…
Zamy846692 May 30, 2026
20b26ce
Improved HtmlLabel style loading
Zamy846692 May 31, 2026
084c57d
Corrected multiple errors and made UI improvements in TkHtmlParsedURI…
Zamy846692 May 31, 2026
69f1f0a
Add safety for TkHtmlParsedURI destruction
Zamy846692 May 31, 2026
54922fd
splitfrag now returns a DefragResult object
Zamy846692 May 31, 2026
58e5a3b
Add method 'webpage' to HtmlParse class, for synchronously loading HT…
Zamy846692 Jun 9, 2026
05b3d03
Add widget modding features from HtmlFrame.load_html to .webpage method
Zamy846692 Jun 9, 2026
2b4832a
Added explanation for the existence of handlers.py
Zamy846692 Jul 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions examples/TkinterWebBrowser.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,8 +267,7 @@ def __init__(self, master, *args, **kwargs):

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

self.sidebar.load_html(f"""<html>
<body>
self.sidebar.load_html(f"""
<style>
body p, span {{ margin-top: 5px; margin-bottom: 5px; cursor: default; }}
object {{ width: 100%; cursor: pointer; }}
Expand Down Expand Up @@ -331,8 +330,7 @@ def __init__(self, master, *args, **kwargs):
<object allowscrolling data={html_button}></object>
<object allowscrolling data={about_button}></object>
</div>
</body>
</html>""")
""")

frame.config = self.html_config

Expand Down Expand Up @@ -885,4 +883,4 @@ def on_tab_change(self, event):
self.destroy()

if __name__ == "__main__":
Browser()
Browser()
46 changes: 22 additions & 24 deletions tkinterweb/bindings.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from re import IGNORECASE, split, sub

from urllib.parse import urljoin
from urllib.parse import urljoin, DefragResult

from queue import Queue, Empty

Expand Down Expand Up @@ -297,7 +297,7 @@ def _setup_handlers(self):
self.register_lazy_handler("node", "base", "node_manager")
self.register_lazy_handler("attribute", "a", "node_manager")

if not self.using_tkhtml30:
if not self.using_tkhtml30 or self.experimental:
#self.register_lazy_handler("node", "details", "node_manager")
self.register_lazy_handler("node", "progress", "node_manager")
self.register_lazy_handler("attribute", "progress", "node_manager")
Expand Down Expand Up @@ -676,7 +676,6 @@ def _parse(self, html):
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):
Expand Down Expand Up @@ -1750,8 +1749,7 @@ def escape_uri(self, uri, query=False):
"""Returns the decoded data.

New in version 4.19."""
a = "-query" if query else ""
return self.tk.call("::tkhtml::escape_uri", a, uri)
return self.tk.call("::tkhtml::escape_uri", "-query" if query else "", uri)

class TkHtmlParsedURI:
"""Bindings for the Tkhtml URI parsing system.
Expand All @@ -1765,38 +1763,34 @@ def __init__(self, uri, html):
self.parsed = self.uri(uri)

def __repr__(self):
return f"{self._html._w}::{self.__class__.__name__.lower()}"
return f"{self._html._w}::{self.__class__.__name__.lower()}{self.parsed}"

def __str__(self):
return self.get(self.parsed)

def __del__(self):
self.destroy(self.parsed)
return self.get

def uri(self, uri):
"Returns name of parsed uri to be used in methods below."
return self._html.tk.call("::tkhtml::uri", uri)

def tkhtml_uri_decode(self, uri, base64=False):
def decode(self, base64=False):
"This command is designed to help scripts process data: URIs. It is completely separate from the html widget"
return self._html.tkhtml_uri_decode(uri, base64)
return self._html.decode_uri(self.get, base64)

def tkhtml_uri_encode(self, uri):
def encode(self):
"Encodes the uri."
return self._html.tkhtml_uri_encode(uri)
return self._html.encode_uri(self.get)

def tkhtml_uri_escape(self, uri, query=False):
def escape(self, query=False):
"Returns the decoded data."
return self._html.tkhtml_uri_escape(uri, query)
return self._html.escape_uri(self.get, query)

def uri_resolve(self, uri):
def resolve(self, uri):
"Resolve a uri."
return self._html.tk.call(self.parsed, "resolve", uri)

@property
def load(self, uri):
"Load a uri."
return self._html.tk.call(self.parsed, "load", uri)
"Load a URI, which overrides the previous URI string"
self._html.tk.call(self.parsed, "load", uri)

@property
def get(self):
Expand Down Expand Up @@ -1835,9 +1829,13 @@ def fragment(self):

@property
def splitfrag(self):
"Return namedtuple with uri and fragment"
return self.defrag, self.fragment
"Return DefragResult/namedtuple with uri and fragment"
return DefragResult(self.defrag, self.fragment)

def destroy(self):
"Destroy this uri."
self._html.tk.call(self.parsed, "destroy")
"Destroy this uri and free Tcl resources."
if self.parsed:
self._html.tk.call(self.parsed, "destroy")
self.parsed = None

__del__ = destroy
6 changes: 3 additions & 3 deletions tkinterweb/dom.py
Original file line number Diff line number Diff line change
Expand Up @@ -411,9 +411,9 @@ def textContent(self, contents): # Ditto
self.html.widget_manager.set_node_widget(node, None)

self.html.safe_tk_eval("""
set html %s
set node %s
set textnode %s

if {$textnode eq ""} {error "$node is empty"}
if {[$node tag] eq "html"} {error "textContent cannot be set on <$tag> elements"}
$node remove [$node children]
Expand All @@ -422,8 +422,8 @@ def textContent(self, contents): # Ditto
}
$node insert $textnode

if {[winfo ismapped $html]} {update} ; # This must be done to see changes on-screen
""" % (self.html, extract_nested(self.node), self.document.createTextNode(contents).node)
if {[winfo ismapped .]} {update} ; # This must be done to see changes on-screen
""" % (extract_nested(self.node), self.document.createTextNode(contents).node)
)
else:
self.html.set_node_text(self.node, contents)
Expand Down
34 changes: 23 additions & 11 deletions tkinterweb/handlers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
"""
Node handlers and associated extensions to Tkhtml3

This was created because TkinterWeb class was getting enormous and difficult to
maintain and debug. It was also full of old workarounds and meaningless code.
Generally, the Python philosophy is to have a self-explanatory, modular
codebase rather than one massive object that does a million things. It's been
on my bucket list for years to make the widget more modular, partly for this
reason. So, I split it into separate classes that each have a specific
function. This also simplified all the flags for extra features a lot: each
feature (i.e. images, CSS, etc.) is tied to a subclass that can be enabled or
disabled, saving me from having if/else statements everywhere checking if
actions are enabled. This also gave me the chance to add lazy loading into the
mix. Since less code has to be evaluated at startup, apps tend to start a bit
quicker (it's barely noticeable, but hey, I'll take it). Splitting off the node
handlers removes roughly 1000 lines from an object that as it is, is still
roughly at 1700 lines (which I personally think is still too much). Breaking
things into chunks just makes things simpler and easier to maintain. The
overhead in Python of creating another class is negligible, but the scalability
and ease of maintenance is much, much higher. That's my philosophy anyway!

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

Expand Down Expand Up @@ -159,7 +177,7 @@ def _update_details(self, node, display):

def _set_open(self, node, display):
self.html.set_node_attribute(node, "open", "" if display else "false")
if self.html.using_tkhtml30:
if self.html.using_tkhtml30 and not self.html.experimental:
# In Tkhtml 3.1+ we add an attribute handler, which does this for us
self._update_details(node, display)

Expand Down Expand Up @@ -204,7 +222,7 @@ def _handle_summary_click(self, node):

open = not self._is_open(details)
self._set_open(details, open)
if open and self.html.using_tkhtml30:
if open and self.html.using_tkhtml30 or not self.html.experimental:
self._close_other_details(details)

def _on_progress(self, node):
Expand Down Expand Up @@ -297,20 +315,14 @@ def _handle_form_submission(self, node, event=None):
continue
elif nodetype == "file":
for value in nodevalue:
data.append(
(nodeattrname, value),
)
data.append((nodeattrname, value),)
else:
data.append(
(nodeattrname, nodevalue),
)
data.append((nodeattrname, nodevalue),)
if not event:
nodeattrname = self.html.get_node_attribute(node, "name")
nodevalue = self.html.get_node_attribute(node, "value")
if nodeattrname and nodevalue:
data.append(
(nodeattrname, nodevalue),
)
data.append((nodeattrname, nodevalue),)

data = urlencode(data)

Expand Down
Loading