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.
Files changed (23) hide show
  1. {tkinterweb-4.17.5/tkinterweb.egg-info → tkinterweb-4.18.0}/PKG-INFO +1 -1
  2. {tkinterweb-4.17.5 → tkinterweb-4.18.0}/setup.py +5 -5
  3. {tkinterweb-4.17.5 → tkinterweb-4.18.0}/tkinterweb/bindings.py +40 -22
  4. {tkinterweb-4.17.5 → tkinterweb-4.18.0}/tkinterweb/dom.py +0 -5
  5. {tkinterweb-4.17.5 → tkinterweb-4.18.0}/tkinterweb/extensions.py +11 -10
  6. {tkinterweb-4.17.5 → tkinterweb-4.18.0}/tkinterweb/handlers.py +87 -7
  7. {tkinterweb-4.17.5 → tkinterweb-4.18.0}/tkinterweb/htmlwidgets.py +4 -3
  8. {tkinterweb-4.17.5 → tkinterweb-4.18.0}/tkinterweb/utilities.py +50 -41
  9. {tkinterweb-4.17.5 → tkinterweb-4.18.0/tkinterweb.egg-info}/PKG-INFO +1 -1
  10. {tkinterweb-4.17.5 → tkinterweb-4.18.0}/tkinterweb.egg-info/requires.txt +4 -4
  11. {tkinterweb-4.17.5 → tkinterweb-4.18.0}/LICENSE.md +0 -0
  12. {tkinterweb-4.17.5 → tkinterweb-4.18.0}/MANIFEST.in +0 -0
  13. {tkinterweb-4.17.5 → tkinterweb-4.18.0}/README.md +0 -0
  14. {tkinterweb-4.17.5 → tkinterweb-4.18.0}/setup.cfg +0 -0
  15. {tkinterweb-4.17.5 → tkinterweb-4.18.0}/tkinterweb/__init__.py +0 -0
  16. {tkinterweb-4.17.5 → tkinterweb-4.18.0}/tkinterweb/imageutils.py +0 -0
  17. {tkinterweb-4.17.5 → tkinterweb-4.18.0}/tkinterweb/js.py +0 -0
  18. {tkinterweb-4.17.5 → tkinterweb-4.18.0}/tkinterweb/resources/combobox-2.3.tm +0 -0
  19. {tkinterweb-4.17.5 → tkinterweb-4.18.0}/tkinterweb/resources/pkgIndex.tcl +0 -0
  20. {tkinterweb-4.17.5 → tkinterweb-4.18.0}/tkinterweb/subwidgets.py +0 -0
  21. {tkinterweb-4.17.5 → tkinterweb-4.18.0}/tkinterweb.egg-info/SOURCES.txt +0 -0
  22. {tkinterweb-4.17.5 → tkinterweb-4.18.0}/tkinterweb.egg-info/dependency_links.txt +0 -0
  23. {tkinterweb-4.17.5 → tkinterweb-4.18.0}/tkinterweb.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tkinterweb
3
- Version: 4.17.5
3
+ Version: 4.18.0
4
4
  Summary: HTML/CSS viewer, editor, and app builder for Tkinter
5
5
  Home-page: https://github.com/Andereoo/TkinterWeb
6
6
  License: MIT
@@ -6,7 +6,7 @@ README = (HERE / "README.md").read_text()
6
6
 
7
7
  setup(
8
8
  name="tkinterweb",
9
- version="4.17.5",
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.2.0"],
30
+ "html": ["tkinterweb-tkhtml-extras>=1.3.0"],
31
31
  "images": ["pillow"],
32
- "svg": ["tkinterweb-tkhtml-extras>=1.2.0", "pillow", "cairosvg"],
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.2.0", "pillow"],
37
- "full": ["tkinterweb-tkhtml-extras>=1.2.0", "pillow", "cairosvg", "pythonmonkey", "brotli"],
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.downloads_have_occured = False
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
- # We assume that if no downloads have been made by now the document has finished loading, so we send the done loading signal
613
- if not self.downloads_have_occured:
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
- thread.start()
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 runs in a thread
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, True)
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 runs in a thread
756
+ # NOTE: this may run in a thread
749
757
 
750
758
  self.active_threads.remove(thread)
751
- if len(self.active_threads) == 0:
752
- self.post_to_queue(self._handle_load_finish, thread.is_subthread)
753
- else:
754
- self.post_to_queue(lambda: self._handle_load_finish(False), thread.is_subthread)
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
- # We assume that if no downloads have been made by now the document has finished loading, so we send the done loading signal
837
- if not self.downloads_have_occured:
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.append(url)
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
- text = self.html.get_node_text(self.selection_start_node)
151
- self.selection_start_offset = 0
152
- self.selection_end_node = self.selection_start_node
153
- self.selection_end_offset = len(text)
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
- text = self.html.get_node_text(self.selection_start_node)
190
- self.selection_start_offset = len(text)
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
- text = self.html.get_node_text(self.selection_end_node)
194
- self.selection_start_offset = 0
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 hyperlinks, body/html elements, and title/meta/base elements."
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 runs in a thread
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 runs in a thread
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 runs in a thread
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 runs in a thread
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 runs in a thread
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 runs in a thread
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.17.5"
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
- /* Formatting for <mark> */
227
- MARK {
228
- background: yellow;
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{{background-color:{bg};color:{fg};cursor:gobbler;width:100%;height:100%;margin:0}}</style><title>about:blank</title></head><body></body></html>{i1}{i2}",
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
- .header{{{{display:block;text-decoration:underline;margin-top:25px}}}}\
464
- .closeheader{{{{display:block;text-decoration:underline}}}}\
465
- .section{{{{margin-left:10px;border-left:2px solid lightgrey;padding-left:10px}}}}\
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
- <code class='header'>Debugging information</code><code class='section'>\
474
- <code class='closeheader'>Versioning</code>\
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
- <code class='header'>Resource loading</code>\
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
- <code class='header'>System specs</code>\
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
- <iframe src=\"about:tkinterweb\" height=\"200\" width=\"300\" title=\"Iframe Example\"></iframe> \
487
- <code class='header'>Preferences</code><code class='section'>\
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
- <code class='header'>HTTP settings</code>\
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
- <code class='header'>Threading settings</code>\
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
- <code class='header'>Image settings</code>\
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
- <code class='header'>Flags</code>\
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
- <code class='header'>Colours</code>\
519
- <span style='background-color:{find_match_highlight_color}'>&nbsp;</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}'>&nbsp;</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}'>&nbsp;</span><code class='colourbox'>Found text colour: {find_match_text_color}</code><code></code>\
521
530
  <span style='background-color:{find_current_highlight_color}'>&nbsp;</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}'>&nbsp;</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}'>&nbsp;</span><code class='colourbox'>Selected text highlight colour: {selected_text_highlight_color}</code><code></code>\
524
533
  <span style='background-color:{selected_text_color}'>&nbsp;</span><code class='colourbox'>Selected text colour: {selected_text_color}</code><code></code>\
525
534
  <span style='background-color:{bg}'>&nbsp;</span><code class='colourbox'>About page background colour: {bg}</code><code></code>\
526
- <span style='background-color:{fg}'>&nbsp;</span><code class='colourbox'>About page foreground colour: {fg}</code><code></code>\
527
- <code class='header'>Dark mode settings</code>\
535
+ <span style='background-color:{fg}'>&nbsp;</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
- <code class='header'>Site memory</code>\
536
- <code>Visited hyperlinks: {visited_links}</code>\
537
- </body></html>{i1}{i2}",
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{{background-color:{bg};color:{fg};width:100%;height:100%;margin:0}}</style></head>\
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>",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tkinterweb
3
- Version: 4.17.5
3
+ Version: 4.18.0
4
4
  Summary: HTML/CSS viewer, editor, and app builder for Tkinter
5
5
  Home-page: https://github.com/Andereoo/TkinterWeb
6
6
  License: MIT
@@ -5,10 +5,10 @@ brotli
5
5
  cairosvg
6
6
  pillow
7
7
  pythonmonkey
8
- tkinterweb-tkhtml-extras>=1.2.0
8
+ tkinterweb-tkhtml-extras>=1.3.0
9
9
 
10
10
  [html]
11
- tkinterweb-tkhtml-extras>=1.2.0
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.2.0
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.2.0
29
+ tkinterweb-tkhtml-extras>=1.3.0
File without changes
File without changes
File without changes
File without changes