tkinterweb 4.17.5__tar.gz → 4.18.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tkinterweb-4.17.5/tkinterweb.egg-info → tkinterweb-4.18.0}/PKG-INFO +1 -1
- {tkinterweb-4.17.5 → tkinterweb-4.18.0}/setup.py +5 -5
- {tkinterweb-4.17.5 → tkinterweb-4.18.0}/tkinterweb/bindings.py +40 -22
- {tkinterweb-4.17.5 → tkinterweb-4.18.0}/tkinterweb/dom.py +0 -5
- {tkinterweb-4.17.5 → tkinterweb-4.18.0}/tkinterweb/extensions.py +11 -10
- {tkinterweb-4.17.5 → tkinterweb-4.18.0}/tkinterweb/handlers.py +87 -7
- {tkinterweb-4.17.5 → tkinterweb-4.18.0}/tkinterweb/htmlwidgets.py +4 -3
- {tkinterweb-4.17.5 → tkinterweb-4.18.0}/tkinterweb/utilities.py +50 -41
- {tkinterweb-4.17.5 → tkinterweb-4.18.0/tkinterweb.egg-info}/PKG-INFO +1 -1
- {tkinterweb-4.17.5 → tkinterweb-4.18.0}/tkinterweb.egg-info/requires.txt +4 -4
- {tkinterweb-4.17.5 → tkinterweb-4.18.0}/LICENSE.md +0 -0
- {tkinterweb-4.17.5 → tkinterweb-4.18.0}/MANIFEST.in +0 -0
- {tkinterweb-4.17.5 → tkinterweb-4.18.0}/README.md +0 -0
- {tkinterweb-4.17.5 → tkinterweb-4.18.0}/setup.cfg +0 -0
- {tkinterweb-4.17.5 → tkinterweb-4.18.0}/tkinterweb/__init__.py +0 -0
- {tkinterweb-4.17.5 → tkinterweb-4.18.0}/tkinterweb/imageutils.py +0 -0
- {tkinterweb-4.17.5 → tkinterweb-4.18.0}/tkinterweb/js.py +0 -0
- {tkinterweb-4.17.5 → tkinterweb-4.18.0}/tkinterweb/resources/combobox-2.3.tm +0 -0
- {tkinterweb-4.17.5 → tkinterweb-4.18.0}/tkinterweb/resources/pkgIndex.tcl +0 -0
- {tkinterweb-4.17.5 → tkinterweb-4.18.0}/tkinterweb/subwidgets.py +0 -0
- {tkinterweb-4.17.5 → tkinterweb-4.18.0}/tkinterweb.egg-info/SOURCES.txt +0 -0
- {tkinterweb-4.17.5 → tkinterweb-4.18.0}/tkinterweb.egg-info/dependency_links.txt +0 -0
- {tkinterweb-4.17.5 → tkinterweb-4.18.0}/tkinterweb.egg-info/top_level.txt +0 -0
|
@@ -6,7 +6,7 @@ README = (HERE / "README.md").read_text()
|
|
|
6
6
|
|
|
7
7
|
setup(
|
|
8
8
|
name="tkinterweb",
|
|
9
|
-
version="4.
|
|
9
|
+
version="4.18.0",
|
|
10
10
|
python_requires=">=3.2",
|
|
11
11
|
description="HTML/CSS viewer, editor, and app builder for Tkinter",
|
|
12
12
|
long_description=README,
|
|
@@ -27,13 +27,13 @@ setup(
|
|
|
27
27
|
include_package_data=True,
|
|
28
28
|
install_requires=["tkinterweb-tkhtml>=2.1.0"],
|
|
29
29
|
extras_require = {
|
|
30
|
-
"html": ["tkinterweb-tkhtml-extras>=1.
|
|
30
|
+
"html": ["tkinterweb-tkhtml-extras>=1.3.0"],
|
|
31
31
|
"images": ["pillow"],
|
|
32
|
-
"svg": ["tkinterweb-tkhtml-extras>=1.
|
|
32
|
+
"svg": ["tkinterweb-tkhtml-extras>=1.3.0", "pillow", "cairosvg"],
|
|
33
33
|
"javascript": ["pythonmonkey"],
|
|
34
34
|
"requests": ["brotli"],
|
|
35
35
|
|
|
36
|
-
"recommended": ["tkinterweb-tkhtml-extras>=1.
|
|
37
|
-
"full": ["tkinterweb-tkhtml-extras>=1.
|
|
36
|
+
"recommended": ["tkinterweb-tkhtml-extras>=1.3.0", "pillow"],
|
|
37
|
+
"full": ["tkinterweb-tkhtml-extras>=1.3.0", "pillow", "cairosvg", "pythonmonkey", "brotli"],
|
|
38
38
|
},
|
|
39
39
|
)
|
|
@@ -201,7 +201,7 @@ If you benefited from using this package, please consider supporting its develop
|
|
|
201
201
|
|
|
202
202
|
self.fragment = ""
|
|
203
203
|
self.active_threads = []
|
|
204
|
-
self.
|
|
204
|
+
self.pending_threads = []
|
|
205
205
|
self.current_active_node = None
|
|
206
206
|
self.clicked_node = None
|
|
207
207
|
self.current_hovered_node = None
|
|
@@ -276,6 +276,10 @@ If you benefited from using this package, please consider supporting its develop
|
|
|
276
276
|
self.register_lazy_handler("node", "a", "node_manager")
|
|
277
277
|
self.register_lazy_handler("node", "base", "node_manager")
|
|
278
278
|
self.register_lazy_handler("attribute", "a", "node_manager")
|
|
279
|
+
|
|
280
|
+
if not self.using_tkhtml30:
|
|
281
|
+
#self.register_lazy_handler("node", "details", "node_manager")
|
|
282
|
+
self.register_lazy_handler("attribute", "details", "node_manager")
|
|
279
283
|
|
|
280
284
|
self.register_lazy_handler("node", "form", "form_manager")
|
|
281
285
|
self.register_lazy_handler("node", "table", "form_manager")
|
|
@@ -587,7 +591,6 @@ It is likely that not all dependencies are installed. Make sure Cairo is install
|
|
|
587
591
|
"Parse HTML code. Call :meth:`TkinterWeb.reset` before calling this method for the first time."
|
|
588
592
|
# NOTE: when thread_safe=True, this method is thread-safe
|
|
589
593
|
|
|
590
|
-
self.downloads_have_occured = False
|
|
591
594
|
html = self._crash_prevention(html)
|
|
592
595
|
html = self._dark_mode(html)
|
|
593
596
|
|
|
@@ -605,12 +608,14 @@ It is likely that not all dependencies are installed. Make sure Cairo is install
|
|
|
605
608
|
def _parse(self, html):
|
|
606
609
|
"Parse HTML code."
|
|
607
610
|
# NOTE: this must run in the main thread
|
|
608
|
-
|
|
611
|
+
self.parsing = True
|
|
609
612
|
self.tk.call(self._w, "parse", html)
|
|
613
|
+
self.parsing = False
|
|
614
|
+
|
|
610
615
|
self.post_event(utilities.DOM_CONTENT_LOADED_EVENT)
|
|
611
616
|
|
|
612
|
-
#
|
|
613
|
-
if not self.
|
|
617
|
+
# If any threads are active, they'll send the done loading signal when they finish
|
|
618
|
+
if not self.active_threads:
|
|
614
619
|
self._handle_load_finish()
|
|
615
620
|
else:
|
|
616
621
|
# Scroll to the fragment if given but do not issue a done loading event
|
|
@@ -619,6 +624,9 @@ It is likely that not all dependencies are installed. Make sure Cairo is install
|
|
|
619
624
|
self.script_manager._submit_deferred_scripts()
|
|
620
625
|
self.event_manager.send_onload()
|
|
621
626
|
|
|
627
|
+
#if self.using_tkhtml30: # Handle unsupported tags
|
|
628
|
+
self.node_manager._handle_load_finish()
|
|
629
|
+
|
|
622
630
|
def _handle_load_finish(self, post_event=True):
|
|
623
631
|
if self.fragment:
|
|
624
632
|
try:
|
|
@@ -707,6 +715,7 @@ It is likely that not all dependencies are installed. Make sure Cairo is install
|
|
|
707
715
|
"Stop loading resources."
|
|
708
716
|
for thread in self.active_threads:
|
|
709
717
|
thread.stop()
|
|
718
|
+
self.pending_threads.clear()
|
|
710
719
|
|
|
711
720
|
def resolve_url(self, url, base=None):
|
|
712
721
|
"Generate a full url from the specified url."
|
|
@@ -725,33 +734,38 @@ It is likely that not all dependencies are installed. Make sure Cairo is install
|
|
|
725
734
|
return utilities.cache_download(url, *args, insecure=self.insecure_https, cafile=self.ssl_cafile, headers=tuple(self.headers.items()), timeout=self.request_timeout)
|
|
726
735
|
|
|
727
736
|
def _thread_check(self, callback, url, *args, **kwargs):
|
|
728
|
-
if not self.downloads_have_occured:
|
|
729
|
-
self.downloads_have_occured = True
|
|
730
|
-
|
|
731
737
|
if not self.threading_enabled or url.startswith("file://"):
|
|
732
738
|
callback(url, *args, **kwargs)
|
|
733
|
-
elif len(self.active_threads) >= self.maximum_thread_count:
|
|
734
|
-
self.after(500, lambda callback=callback, url=url, args=args: self._thread_check(callback, url, *args, **kwargs))
|
|
735
739
|
else:
|
|
736
740
|
thread = utilities.StoppableThread(target=callback, args=(url, *args,), kwargs=kwargs)
|
|
737
|
-
|
|
741
|
+
|
|
742
|
+
if len(self.active_threads) >= 100:
|
|
743
|
+
self.pending_threads.append(thread)
|
|
744
|
+
else:
|
|
745
|
+
thread.start()
|
|
738
746
|
|
|
739
747
|
def _begin_download(self):
|
|
740
|
-
# NOTE: this
|
|
748
|
+
# NOTE: this may run in a thread
|
|
741
749
|
|
|
742
750
|
thread = utilities.get_current_thread()
|
|
743
751
|
self.active_threads.append(thread)
|
|
744
|
-
self.post_event(utilities.DOWNLOADING_RESOURCE_EVENT,
|
|
752
|
+
self.post_event(utilities.DOWNLOADING_RESOURCE_EVENT, thread.is_subthread)
|
|
745
753
|
return thread
|
|
746
754
|
|
|
747
755
|
def _finish_download(self, thread):
|
|
748
|
-
# NOTE: this
|
|
756
|
+
# NOTE: this may run in a thread
|
|
749
757
|
|
|
750
758
|
self.active_threads.remove(thread)
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
759
|
+
|
|
760
|
+
if thread.isrunning():
|
|
761
|
+
if thread.is_subthread and self.pending_threads:
|
|
762
|
+
self.pending_threads.pop(0).start()
|
|
763
|
+
|
|
764
|
+
elif not self.parsing:
|
|
765
|
+
if len(self.active_threads) == 0:
|
|
766
|
+
self.post_to_queue(self._handle_load_finish, thread.is_subthread)
|
|
767
|
+
else:
|
|
768
|
+
self.post_to_queue(lambda: self._handle_load_finish(False), thread.is_subthread)
|
|
755
769
|
|
|
756
770
|
def _finish_resource_load(self, message, url, resource, success):
|
|
757
771
|
# NOTE: this must run in the main thread
|
|
@@ -829,12 +843,11 @@ It is likely that not all dependencies are installed. Make sure Cairo is install
|
|
|
829
843
|
A document fragment isn't part of the active document but is comprised of nodes like the active document.
|
|
830
844
|
Changes made to the fragment don't affect the document.
|
|
831
845
|
Returns a root node."""
|
|
832
|
-
self.downloads_have_occured = False
|
|
833
846
|
html = self._crash_prevention(html)
|
|
834
847
|
html = self._dark_mode(html)
|
|
835
848
|
fragment = self.tk.call(self._w, "fragment", html)
|
|
836
|
-
#
|
|
837
|
-
if not self.
|
|
849
|
+
# If any threads are active, they'll send the done loading signal when they finish
|
|
850
|
+
if not self.active_threads:
|
|
838
851
|
self.post_event(utilities.DONE_LOADING_EVENT)
|
|
839
852
|
self.script_manager._submit_deferred_scripts()
|
|
840
853
|
return fragment
|
|
@@ -1549,7 +1562,8 @@ It is likely that not all dependencies are installed. Make sure Cairo is install
|
|
|
1549
1562
|
href = self.get_node_attribute(node_handle, "href")
|
|
1550
1563
|
url = self.resolve_url(href)
|
|
1551
1564
|
self.post_message(f"A link to '{utilities.shorten(url)}' was clicked")
|
|
1552
|
-
self.visited_links
|
|
1565
|
+
if url not in self.visited_links:
|
|
1566
|
+
self.visited_links.append(url)
|
|
1553
1567
|
self.on_link_click(url)
|
|
1554
1568
|
|
|
1555
1569
|
def _on_click_release(self, event):
|
|
@@ -1602,6 +1616,10 @@ It is likely that not all dependencies are installed. Make sure Cairo is install
|
|
|
1602
1616
|
if node_type == "submit":
|
|
1603
1617
|
self.form_manager._handle_form_submission(node)
|
|
1604
1618
|
break
|
|
1619
|
+
elif node_tag == "summary":
|
|
1620
|
+
self.node_manager._handle_summary_click(node)
|
|
1621
|
+
break
|
|
1622
|
+
|
|
1605
1623
|
except tk.TclError:
|
|
1606
1624
|
pass
|
|
1607
1625
|
|
|
@@ -52,11 +52,6 @@ def camel_case_to_property(string):
|
|
|
52
52
|
new_string += i
|
|
53
53
|
return new_string
|
|
54
54
|
|
|
55
|
-
# This also works:
|
|
56
|
-
# from re import finditer
|
|
57
|
-
# matches = finditer('.+?(?:(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|$)', string)
|
|
58
|
-
# return "-".join([m.group(0).lower() for m in matches])
|
|
59
|
-
|
|
60
55
|
|
|
61
56
|
class HTMLDocument:
|
|
62
57
|
"""Access this class via the :attr:`~tkinterweb.HtmlFrame.document` property of the :class:`~tkinterweb.HtmlFrame` and :class:`~tkinterweb.HtmlLabel` widgets.
|
|
@@ -147,10 +147,11 @@ class SelectionManager(utilities.BaseManager):
|
|
|
147
147
|
return
|
|
148
148
|
|
|
149
149
|
if self.selection_type == 1:
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
self.
|
|
153
|
-
self.
|
|
150
|
+
# Tkhtml seems to wrap the output of text("text") with \n, so this works
|
|
151
|
+
text = self.html.text("text")
|
|
152
|
+
index = self.html.text("offset", self.selection_start_node, self.selection_start_offset)
|
|
153
|
+
self.selection_start_node, self.selection_start_offset = self.html.text("index", text[:index].rfind("\n") + 1)
|
|
154
|
+
self.selection_end_node, self.selection_end_offset = self.html.text("index", text.find("\n", index))
|
|
154
155
|
self.update_selection()
|
|
155
156
|
self.selection_type = 2
|
|
156
157
|
|
|
@@ -183,16 +184,16 @@ class SelectionManager(utilities.BaseManager):
|
|
|
183
184
|
self.selection_end_offset = end_offset2
|
|
184
185
|
|
|
185
186
|
elif self.selection_type == 2:
|
|
187
|
+
text = self.html.text("text")
|
|
186
188
|
start_index = self.html.text("offset", self.selection_start_node, self.selection_start_offset)
|
|
187
189
|
end_index = self.html.text("offset", self.selection_end_node, self.selection_end_offset)
|
|
190
|
+
|
|
188
191
|
if start_index > end_index:
|
|
189
|
-
|
|
190
|
-
self.
|
|
191
|
-
self.selection_end_offset = 0
|
|
192
|
+
self.selection_start_node, self.selection_start_offset = self.html.text("index", text.find("\n", start_index))
|
|
193
|
+
self.selection_end_node, self.selection_end_offset = self.html.text("index", text[:end_index].rfind("\n") + 1)
|
|
192
194
|
else:
|
|
193
|
-
|
|
194
|
-
self.
|
|
195
|
-
self.selection_end_offset = len(text)
|
|
195
|
+
self.selection_start_node, self.selection_start_offset = self.html.text("index", text[:start_index].rfind("\n") + 1)
|
|
196
|
+
self.selection_end_node, self.selection_end_offset = self.html.text("index", text.find("\n", end_index))
|
|
196
197
|
|
|
197
198
|
self.update_selection()
|
|
198
199
|
|
|
@@ -8,15 +8,20 @@ import tkinter as tk
|
|
|
8
8
|
|
|
9
9
|
from urllib.parse import urlencode, urlparse
|
|
10
10
|
|
|
11
|
-
from . import subwidgets, utilities, imageutils
|
|
11
|
+
from . import subwidgets, utilities, imageutils, dom
|
|
12
12
|
|
|
13
13
|
class NodeManager(utilities.BaseManager):
|
|
14
|
-
"Handle
|
|
14
|
+
"Handle body, html, title, meta, base, details, and hyperlink elements."
|
|
15
15
|
def __init__(self, html):
|
|
16
16
|
super().__init__(html)
|
|
17
17
|
|
|
18
|
+
self._node_texts = {}
|
|
19
|
+
|
|
18
20
|
def __repr__(self):
|
|
19
21
|
return f"{self.html._w}::{self.__class__.__name__.lower()}"
|
|
22
|
+
|
|
23
|
+
def reset(self):
|
|
24
|
+
self._node_texts.clear()
|
|
20
25
|
|
|
21
26
|
# --- Handle title, base, and meta elements -------------------------------
|
|
22
27
|
|
|
@@ -110,6 +115,81 @@ class NodeManager(utilities.BaseManager):
|
|
|
110
115
|
self.html.motion_frame_bg = background
|
|
111
116
|
self.html.motion_frame.config(bg=background)
|
|
112
117
|
|
|
118
|
+
|
|
119
|
+
# --- Handle <details> elements -------------------------------------------
|
|
120
|
+
|
|
121
|
+
# Technically <details> elements should be visible whenever the open attribute is present
|
|
122
|
+
# But Tkhtml can't remove attributes (or at lease I can't figure out how to do it)
|
|
123
|
+
# So for now we hide the content if open="false"
|
|
124
|
+
# I could cut out most of this code if we could remove attributes though
|
|
125
|
+
|
|
126
|
+
def _is_open(self, node):
|
|
127
|
+
return self.html.get_node_attribute(node, "open", "false") != "false"
|
|
128
|
+
|
|
129
|
+
def _update_details(self, node, display):
|
|
130
|
+
for child in self.html.get_node_children(node):
|
|
131
|
+
if self.html.get_node_tag(child) == "summary":
|
|
132
|
+
continue
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
self.html.override_node_properties(child, "display", "" if display else "none")
|
|
136
|
+
except tk.TclError:
|
|
137
|
+
# We need a better solution here
|
|
138
|
+
if display and child in self._node_texts:
|
|
139
|
+
self.html.set_node_text(child, self._node_texts[child])
|
|
140
|
+
else:
|
|
141
|
+
self._node_texts[child] = self.html.get_node_text(child)
|
|
142
|
+
self.html.set_node_text(child, "")
|
|
143
|
+
|
|
144
|
+
def _set_open(self, node, display):
|
|
145
|
+
self.html.set_node_attribute(node, "open", "" if display else "false")
|
|
146
|
+
if self.html.using_tkhtml30:
|
|
147
|
+
# In Tkhtml 3.1+ we add an attribute handler, which does this for us
|
|
148
|
+
self._update_details(node, display)
|
|
149
|
+
|
|
150
|
+
def _close_other_details(self, node):
|
|
151
|
+
node = dom.extract_nested(node)
|
|
152
|
+
name = self.html.get_node_attribute(node, "name")
|
|
153
|
+
if not name:
|
|
154
|
+
return
|
|
155
|
+
|
|
156
|
+
for details in self.html.search(f"DETAILS[name={name}]"):
|
|
157
|
+
if dom.extract_nested(details) != node:
|
|
158
|
+
self._set_open(details, False)
|
|
159
|
+
|
|
160
|
+
def _on_details(self, node):
|
|
161
|
+
"Handle <details> elements."
|
|
162
|
+
if self._is_open(node):
|
|
163
|
+
self._close_other_details(node)
|
|
164
|
+
else:
|
|
165
|
+
self._update_details(node, False)
|
|
166
|
+
|
|
167
|
+
def _on_details_value_change(self, node, attribute, value):
|
|
168
|
+
if attribute != "open":
|
|
169
|
+
return
|
|
170
|
+
|
|
171
|
+
open = value != "false"
|
|
172
|
+
self._update_details(node, open)
|
|
173
|
+
if open:
|
|
174
|
+
self._close_other_details(node)
|
|
175
|
+
|
|
176
|
+
def _handle_load_finish(self):
|
|
177
|
+
"Collapse <details> elements. Only needed for Tkhtml 3.0, which doesn't support HTML5 elements."
|
|
178
|
+
"It turns out if groups are used this preserves the first box while handling _on_details preserves the last."
|
|
179
|
+
"I think there's value in preserving behaviour between Tkhtml versions, so for now I guess we'll keep this."
|
|
180
|
+
for details in self.html.search("DETAILS"):
|
|
181
|
+
self._on_details(details)
|
|
182
|
+
|
|
183
|
+
def _handle_summary_click(self, node):
|
|
184
|
+
"Handle clicks on <summary> elements"
|
|
185
|
+
details = self.html.get_node_parent(node)
|
|
186
|
+
if self.html.get_node_tag(details).lower() != "details":
|
|
187
|
+
return
|
|
188
|
+
|
|
189
|
+
open = not self._is_open(details)
|
|
190
|
+
self._set_open(details, open)
|
|
191
|
+
if open and self.html.using_tkhtml30:
|
|
192
|
+
self._close_other_details(details)
|
|
113
193
|
|
|
114
194
|
class FormManager(utilities.BaseManager):
|
|
115
195
|
"Handle forms and form elements."
|
|
@@ -479,7 +559,7 @@ class ScriptManager(utilities.BaseManager):
|
|
|
479
559
|
|
|
480
560
|
def fetch_scripts(self, attributes, url=None, data=None):
|
|
481
561
|
"Fetch and run scripts"
|
|
482
|
-
# NOTE: this
|
|
562
|
+
# NOTE: this may run in a thread
|
|
483
563
|
|
|
484
564
|
thread = self.html._begin_download()
|
|
485
565
|
|
|
@@ -563,7 +643,7 @@ class StyleManager(utilities.BaseManager):
|
|
|
563
643
|
|
|
564
644
|
def fetch_styles(self, url=None, node=None):
|
|
565
645
|
"Fetch stylesheets and parse the CSS code they contain"
|
|
566
|
-
# NOTE: this
|
|
646
|
+
# NOTE: this may run in a thread
|
|
567
647
|
|
|
568
648
|
thread = self.html._begin_download()
|
|
569
649
|
if url and thread.isrunning():
|
|
@@ -692,7 +772,7 @@ class ImageManager(utilities.BaseManager):
|
|
|
692
772
|
|
|
693
773
|
def fetch_images(self, url, name):
|
|
694
774
|
"Fetch images and display them in the document."
|
|
695
|
-
# NOTE: this
|
|
775
|
+
# NOTE: this may run in a thread
|
|
696
776
|
|
|
697
777
|
thread = self.html._begin_download()
|
|
698
778
|
if thread.isrunning():
|
|
@@ -717,7 +797,7 @@ class ImageManager(utilities.BaseManager):
|
|
|
717
797
|
|
|
718
798
|
def check_images(self, data, name, url, filetype, thread_safe):
|
|
719
799
|
"Invert images if needed and convert SVG images to PNGs."
|
|
720
|
-
# NOTE: this
|
|
800
|
+
# NOTE: this may run in a thread
|
|
721
801
|
|
|
722
802
|
data_is_image = False
|
|
723
803
|
if "svg" in filetype:
|
|
@@ -899,7 +979,7 @@ class ObjectManager(utilities.BaseManager):
|
|
|
899
979
|
self.html.widget_manager.map_node(node, True)
|
|
900
980
|
|
|
901
981
|
def fetch_objects(self, url, node):
|
|
902
|
-
# NOTE: this
|
|
982
|
+
# NOTE: this may run in a thread
|
|
903
983
|
|
|
904
984
|
thread = self.html._begin_download()
|
|
905
985
|
|
|
@@ -397,6 +397,8 @@ class HtmlFrame(Frame):
|
|
|
397
397
|
:type base_url: str, optional
|
|
398
398
|
:param fragment: The url fragment to scroll to after the document loads.
|
|
399
399
|
:type fragment: str, optional"""
|
|
400
|
+
if self._thread_in_progress:
|
|
401
|
+
self._thread_in_progress.stop()
|
|
400
402
|
self._html.reset(_thread_safe)
|
|
401
403
|
|
|
402
404
|
if fragment: fragment = "".join(char for char in fragment if char.isalnum() or char in ("-", "_", ".")).replace(".", r"\.")
|
|
@@ -593,9 +595,8 @@ class HtmlFrame(Frame):
|
|
|
593
595
|
"""Stop loading this page and abandon all pending requests."""
|
|
594
596
|
if self._thread_in_progress:
|
|
595
597
|
self._thread_in_progress.stop()
|
|
596
|
-
self._html.stop()
|
|
597
|
-
if self._thread_in_progress:
|
|
598
598
|
self._current_url = self._previous_url
|
|
599
|
+
self._html.stop()
|
|
599
600
|
self._html.post_event(utilities.URL_CHANGED_EVENT)
|
|
600
601
|
self._html.post_event(utilities.DONE_LOADING_EVENT)
|
|
601
602
|
|
|
@@ -1190,7 +1191,7 @@ class HtmlFrame(Frame):
|
|
|
1190
1191
|
|
|
1191
1192
|
def _continue_loading(self, url, data="", method="GET", decode=None, force=False, thread_safe=False):
|
|
1192
1193
|
"Finish loading urls and handle URI fragments."
|
|
1193
|
-
# NOTE: this
|
|
1194
|
+
# NOTE: this may run in a thread
|
|
1194
1195
|
|
|
1195
1196
|
code = 404
|
|
1196
1197
|
self._current_url = url
|
|
@@ -31,7 +31,7 @@ __title__ = "TkinterWeb"
|
|
|
31
31
|
__author__ = "Andrew Clarke"
|
|
32
32
|
__copyright__ = "(c) 2021-2025 Andrew Clarke"
|
|
33
33
|
__license__ = "MIT"
|
|
34
|
-
__version__ = "4.
|
|
34
|
+
__version__ = "4.18.0"
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
ROOT_DIR = os.path.join(os.path.abspath(os.path.dirname(__file__)), "resources")
|
|
@@ -91,7 +91,7 @@ DEFAULT_STYLE = r"""
|
|
|
91
91
|
/* This is a modified version of the stylesheet that comes bundled with Tkhtml. */
|
|
92
92
|
/* Display types for non-table items. */
|
|
93
93
|
ADDRESS, BLOCKQUOTE, BODY, DD, DIV, DL, DT, FIELDSET,
|
|
94
|
-
FRAME, H1, H2, H3, H4, H5, H6, NOFRAMES,
|
|
94
|
+
FRAME, H1, H2, H3, H4, H5, H6, NOFRAMES, DETAILS, SUMMARY,
|
|
95
95
|
OL, P, UL, APPLET, CENTER, DIR, HR, MENU, PRE, FORM
|
|
96
96
|
{ display: block }
|
|
97
97
|
HEAD, SCRIPT, TITLE { display: none }
|
|
@@ -188,6 +188,7 @@ TD, TH { display: table-cell }
|
|
|
188
188
|
CAPTION { display: table-caption }
|
|
189
189
|
TH { font-weight: bolder; text-align: center }
|
|
190
190
|
CAPTION { text-align: center }
|
|
191
|
+
/* General formatting */
|
|
191
192
|
H1 { font-size: 2em; margin: .67em 0 }
|
|
192
193
|
H2 { font-size: 1.5em; margin: .83em 0 }
|
|
193
194
|
H3 { font-size: 1.17em; margin: 1em 0 }
|
|
@@ -223,9 +224,17 @@ PRE, PLAINTEXT, XMP {
|
|
|
223
224
|
margin: 1em 0;
|
|
224
225
|
font-family: monospace;
|
|
225
226
|
}
|
|
226
|
-
/*
|
|
227
|
-
MARK {
|
|
228
|
-
|
|
227
|
+
/* Rules for recently-added elements*/
|
|
228
|
+
MARK { background: yellow }
|
|
229
|
+
Q:before { content: "“" }
|
|
230
|
+
Q:after { content: "”" }
|
|
231
|
+
DETAILS[open] SUMMARY:before {
|
|
232
|
+
content: "▾";
|
|
233
|
+
margin-right: 5px;
|
|
234
|
+
}
|
|
235
|
+
DETAILS SUMMARY:before, DETAILS[open="false"] SUMMARY:before {
|
|
236
|
+
content: "▸";
|
|
237
|
+
margin-right: 5px;
|
|
229
238
|
}
|
|
230
239
|
/* Display properties for hyperlinks */
|
|
231
240
|
:link { color: darkblue; text-decoration: underline ; cursor: pointer }
|
|
@@ -455,14 +464,15 @@ class BuiltinPageGenerator():
|
|
|
455
464
|
def __init__(self):
|
|
456
465
|
self._html = None
|
|
457
466
|
self._pages = {
|
|
458
|
-
"about:blank": "<html><head><style>html,body{{
|
|
467
|
+
"about:blank": "<html><head><style>html,body{{width:100%;height:100%;margin:0}}</style><title>about:blank</title></head><body></body></html>{bg}{fg}{i1}{i2}",
|
|
459
468
|
"about:tkinterweb": "<html tkinterweb-overflow-x=auto><head>\
|
|
460
469
|
<style>html,body{{{{background-color:{bg};color:{fg}}}}}\
|
|
461
470
|
code{{{{display:block}}}}\
|
|
462
|
-
span{{{{margin-right:10px;border:1px solid black;width:20px;padding-left:45px}}}}\
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
.
|
|
471
|
+
span{{{{cursor:gobbler;margin-right:10px;border:1px solid black;width:20px;padding-left:45px}}}}\
|
|
472
|
+
summary{{{{font-weight:bold;cursor:pointer;font-family:monospace;display:inline}}}}\
|
|
473
|
+
details[open]{{{{margin-bottom:25px;}}}}\
|
|
474
|
+
details[open='false'],details[open].bottom{{{{margin-bottom:0px;}}}}\
|
|
475
|
+
.section{{{{margin-left:10px;border-left:1px solid;padding-left:10px}}}}\
|
|
466
476
|
.indented{{{{margin-left:20px}}}}\
|
|
467
477
|
.colourbox{{{{margin-right:75px;display:inline}}}}</style>\
|
|
468
478
|
<title>about:tkinterweb</title></head><body>\
|
|
@@ -470,40 +480,39 @@ class BuiltinPageGenerator():
|
|
|
470
480
|
<code>✉ <a href=https://github.com/Andereoo/TkinterWeb>github.com/Andereoo/TkinterWeb</a></code>\
|
|
471
481
|
<code>✨ <a href=https://tkinterweb.readthedocs.io>tkinterweb.readthedocs.io</a></code>\
|
|
472
482
|
<code>☕ <a href=https://buymeacoffee.com/andereoo>buymeacoffee.com/andereoo</a></code>\
|
|
473
|
-
<
|
|
474
|
-
<
|
|
483
|
+
<details open style='margin-top:25px'><summary>Debugging information</summary><code class='section'>\
|
|
484
|
+
<details open><summary>Versioning</summary>\
|
|
475
485
|
<code>Version: {__version__}</code><code>Tkhtml version: {tkhtml_version}</code><code>TkinterWeb-Tkhtml version: {tkw_tkhtml_version}</code>\
|
|
476
|
-
<code>Python version: {python_version}</code><code>Tcl version: {tcl_version}</code><code>Tk version: {tk_version}</code>\
|
|
477
|
-
<
|
|
486
|
+
<code>Python version: {python_version}</code><code>Tcl version: {tcl_version}</code><code>Tk version: {tk_version}</code></details>\
|
|
487
|
+
<details open><summary>Resource loading</summary>\
|
|
478
488
|
<code>Use prebuilt Tkhtml: {use_prebuilt_tkhtml}</code>\
|
|
479
489
|
<code>Available Tkhtml binaries: {tkhtml_binaries}</code>\
|
|
480
490
|
<code>Resource directories: {root}{tkhtml_root}{tkhtml_extras_root}</code><code>Working directory: {working_dir}</code>\
|
|
481
491
|
<code>Tcl paths: {tcl_path}</code>\
|
|
482
|
-
<code>System dependency paths: {path}</code>\
|
|
483
|
-
<
|
|
492
|
+
<code>System dependency paths: {path}</code></details>\
|
|
493
|
+
<details open class='bottom'><summary>System specs</summary>\
|
|
484
494
|
<code>Platform: {platform}</code><code>Machine: {machine}</code><code>Processor: {processor}</code>\
|
|
485
|
-
</code>\
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
<code class='closeheader'>Renderer settings</code>\
|
|
495
|
+
</code></details>\
|
|
496
|
+
<details open><summary>Preferences</summary><code class='section'>\
|
|
497
|
+
<details open><summary>Renderer settings</summary>\
|
|
489
498
|
<code>Parse mode: {parse_mode}</code>\
|
|
490
499
|
<code>Rendering engine mode: {rendering_mode}</code>\
|
|
491
500
|
<code>Zoom: {zoom}</code>\
|
|
492
|
-
<code>Font scale: {font_scale}</code>\
|
|
493
|
-
<
|
|
501
|
+
<code>Font scale: {font_scale}</code></details>\
|
|
502
|
+
<details open><summary>HTTP settings</summary>\
|
|
494
503
|
<code>Headers: {headers}</code>\
|
|
495
504
|
<code>Insecure HTTPS: {insecure_https}</code>\
|
|
496
505
|
<code>CA file path: {ssl_cafile}</code>\
|
|
497
|
-
<code>Request timeout: {request_timeout}s</code>\
|
|
498
|
-
<
|
|
506
|
+
<code>Request timeout: {request_timeout}s</code></details>\
|
|
507
|
+
<details open><summary>Threading settings</summary>\
|
|
499
508
|
<code>Threading enabled: {threading_enabled}</code>\
|
|
500
509
|
<code>Tcl allows threading: {allow_threading}</code>\
|
|
501
|
-
<code>Maximum thread count: {maximum_thread_count}</code>\
|
|
502
|
-
<
|
|
510
|
+
<code>Maximum thread count: {maximum_thread_count}</code></details>\
|
|
511
|
+
<details open><summary>Image settings</summary>\
|
|
503
512
|
<code>Images enabled: {images_enabled}</code>\
|
|
504
513
|
<code>Ignore invalid images: {ignore_invalid_images}</code>\
|
|
505
|
-
<code>Image alt text: {image_alternate_text_enabled}</code>\
|
|
506
|
-
<
|
|
514
|
+
<code>Image alt text: {image_alternate_text_enabled}</code></details>\
|
|
515
|
+
<details open><summary>Flags</summary>\
|
|
507
516
|
<code>Experimental mode: {experimental}</code>\
|
|
508
517
|
<code>Caret browsing mode: {caret_mode}</code>\
|
|
509
518
|
<code>Stylesheets enabled: {stylesheets_enabled}</code>\
|
|
@@ -514,37 +523,37 @@ class BuiltinPageGenerator():
|
|
|
514
523
|
<code>Crash prevention enabled: {crash_prevention_enabled}</code>\
|
|
515
524
|
<code>Debug messages enabled: {messages_enabled}</code>\
|
|
516
525
|
<code>Events enabled: {events_enabled}</code>\
|
|
517
|
-
<code>Selection enabled: {selection_enabled}</code>\
|
|
518
|
-
<
|
|
519
|
-
<span style='background-color:{find_match_highlight_color}'> </span><code class='colourbox'>Found text highlight colour: {find_match_highlight_color}</code><code></code>\
|
|
526
|
+
<code>Selection enabled: {selection_enabled}</code></details>\
|
|
527
|
+
<details open><summary>Colours</summary>\
|
|
528
|
+
<div><span style='background-color:{find_match_highlight_color}'> </span><code class='colourbox'>Found text highlight colour: {find_match_highlight_color}</code><code></code>\
|
|
520
529
|
<span style='background-color:{find_match_text_color}'> </span><code class='colourbox'>Found text colour: {find_match_text_color}</code><code></code>\
|
|
521
530
|
<span style='background-color:{find_current_highlight_color}'> </span><code class='colourbox'>Current found match highlight colour: {find_current_highlight_color}</code><code></code>\
|
|
522
531
|
<span style='background-color:{find_current_text_color}'> </span><code class='colourbox'>Current found match text colour: {find_current_text_color}</code><code></code>\
|
|
523
532
|
<span style='background-color:{selected_text_highlight_color}'> </span><code class='colourbox'>Selected text highlight colour: {selected_text_highlight_color}</code><code></code>\
|
|
524
533
|
<span style='background-color:{selected_text_color}'> </span><code class='colourbox'>Selected text colour: {selected_text_color}</code><code></code>\
|
|
525
534
|
<span style='background-color:{bg}'> </span><code class='colourbox'>About page background colour: {bg}</code><code></code>\
|
|
526
|
-
<span style='background-color:{fg}'> </span><code class='colourbox'>About page foreground colour: {fg}</code><code></code>\
|
|
527
|
-
<
|
|
535
|
+
<span style='background-color:{fg}'> </span><code class='colourbox'>About page foreground colour: {fg}</code><code></code></div></details>\
|
|
536
|
+
<details open class='bottom'><summary>Dark mode settings</summary>\
|
|
528
537
|
<code>Dark mode: {dark_theme_enabled}</code>\
|
|
529
538
|
<code>Image inversion: {image_inversion_enabled}</code>\
|
|
530
539
|
<code>Dark theme general regexes: {general_dark_theme_regexes}</code>\
|
|
531
540
|
<code>Dark theme inline regexes: {inline_dark_theme_regexes}</code>\
|
|
532
541
|
<code>Dark theme style regex: {style_dark_theme_regex}</code>\
|
|
533
|
-
<code>Colour threshold: {dark_theme_limit}</code>\
|
|
534
|
-
</code>\
|
|
535
|
-
<
|
|
536
|
-
|
|
537
|
-
|
|
542
|
+
<code>Colour threshold: {dark_theme_limit}</code></details>\
|
|
543
|
+
</code></details>\
|
|
544
|
+
<details open class='bottom'><summary>Site memory</summary>\
|
|
545
|
+
<code class='section'><code>Visited hyperlinks: {visited_links}</code></code></details>\
|
|
546
|
+
</body></html>{i1}{i2}",
|
|
538
547
|
"about:error": "<html><head><style>html,body,table,tr,td{{background-color:{bg};color:{fg};width:100%;height:100%;margin:0}}</style><title>Error {i1}</title></head>\
|
|
539
548
|
<body><table><tr><td tkinterweb-full-page style='text-align:center;vertical-align:middle'>\
|
|
540
549
|
<h2 style='margin:0;padding:0;font-weight:normal'>Oops</h2>\
|
|
541
550
|
<h3 style='margin-top:10px;margin-bottom:25px;font-weight:normal'>The page you've requested could not be found :(</h3>\
|
|
542
551
|
<object handleremoval allowscrolling style='cursor:pointer' data='{i2}'></object>\
|
|
543
552
|
</td></tr></table></body></html>",
|
|
544
|
-
"about:loading": "<html><head><style>html,body,table,tr,td{{
|
|
553
|
+
"about:loading": "<html><head><style>html,body,table,tr,td{{width:100%;height:100%;margin:0}}</style></head>\
|
|
545
554
|
<body><table><tr><td tkinterweb-full-page style='text-align:center;vertical-align:middle'>\
|
|
546
555
|
<p>Loading...</p>\
|
|
547
|
-
</td></tr></table></body></html>{i1}{i2}",
|
|
556
|
+
</td></tr></table></body></html>{bg}{fg}{i1}{i2}",
|
|
548
557
|
"about:image": "<html><head><style>html,body,table,tr {{background-color:{bg};color:{fg};width:100%;height:100%;margin:0}}</style></head><body>\
|
|
549
558
|
<table><tr><td tkinterweb-full-page style='text-align:center;vertical-align:middle;padding:4px 4px 0px 4px'><img style='max-width:100%;max-height:100%' src='{i1}'><h3 style='margin:0;padding:0;font-weight:normal'></td></tr></table></body></html>{i2}",
|
|
550
559
|
"about:view-source": "<html tkinterweb-overflow-x=auto><head><style>\
|
|
@@ -566,7 +575,7 @@ class BuiltinPageGenerator():
|
|
|
566
575
|
general_dark_theme_regexes=("".join(f"<code class='indented'>{i.replace('{', '{{').replace('}', '}}')}</code>" for i in self._html.general_dark_theme_regexes)),
|
|
567
576
|
inline_dark_theme_regexes=("".join(f"<br><code class='indented'>{i.replace('{', '{{').replace('}', '}}')}</code>" for i in self._html.inline_dark_theme_regexes)),
|
|
568
577
|
style_dark_theme_regex=f"<code class='indented'>{self._html.style_dark_theme_regex.replace('{', '{{').replace('}', '}}')}</code>",
|
|
569
|
-
visited_links=(("".join(f"<code class='indented'>{i}</code>" for i in self._html.visited_links)) if self._html.visited_links else None),
|
|
578
|
+
visited_links=(("".join(f"<code class='indented'><a href='{i}'>{i}</a></code>" for i in self._html.visited_links)) if self._html.visited_links else None),
|
|
570
579
|
tkhtml_binaries=("".join(f"<code class='indented'>{os.path.join(i, e)}</code>" for i, e in tkinterweb_tkhtml.TKHTML_BINARIES)),
|
|
571
580
|
root=f"<code class='indented'>{ROOT_DIR}</code>",
|
|
572
581
|
tkhtml_root=f"<code class='indented'>{tkinterweb_tkhtml.TKHTML_ROOT_DIR}</code>",
|
|
@@ -5,10 +5,10 @@ brotli
|
|
|
5
5
|
cairosvg
|
|
6
6
|
pillow
|
|
7
7
|
pythonmonkey
|
|
8
|
-
tkinterweb-tkhtml-extras>=1.
|
|
8
|
+
tkinterweb-tkhtml-extras>=1.3.0
|
|
9
9
|
|
|
10
10
|
[html]
|
|
11
|
-
tkinterweb-tkhtml-extras>=1.
|
|
11
|
+
tkinterweb-tkhtml-extras>=1.3.0
|
|
12
12
|
|
|
13
13
|
[images]
|
|
14
14
|
pillow
|
|
@@ -18,7 +18,7 @@ pythonmonkey
|
|
|
18
18
|
|
|
19
19
|
[recommended]
|
|
20
20
|
pillow
|
|
21
|
-
tkinterweb-tkhtml-extras>=1.
|
|
21
|
+
tkinterweb-tkhtml-extras>=1.3.0
|
|
22
22
|
|
|
23
23
|
[requests]
|
|
24
24
|
brotli
|
|
@@ -26,4 +26,4 @@ brotli
|
|
|
26
26
|
[svg]
|
|
27
27
|
cairosvg
|
|
28
28
|
pillow
|
|
29
|
-
tkinterweb-tkhtml-extras>=1.
|
|
29
|
+
tkinterweb-tkhtml-extras>=1.3.0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|