pdflinkcheck 1.1.73__py3-none-any.whl → 1.1.94__py3-none-any.whl

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 (177) hide show
  1. pdflinkcheck/__init__.py +2 -5
  2. pdflinkcheck/analyze_pymupdf.py +12 -6
  3. pdflinkcheck/analyze_pypdf.py +25 -7
  4. pdflinkcheck/analyze_pypdf_v2.py +5 -6
  5. pdflinkcheck/cli.py +82 -91
  6. pdflinkcheck/data/I Have Questions.md +51 -0
  7. pdflinkcheck/data/LICENSE +17 -654
  8. pdflinkcheck/data/README.md +49 -49
  9. pdflinkcheck/data/icons/BoxArt-1080x1080.png +0 -0
  10. pdflinkcheck/data/icons/Logo-150x150.png +0 -0
  11. pdflinkcheck/data/icons/Logo-300x300.png +0 -0
  12. pdflinkcheck/data/icons/Logo-71x71.png +0 -0
  13. pdflinkcheck/data/icons/PosterArt-720x1080.png +0 -0
  14. pdflinkcheck/data/icons/SmallLogo-44x44.png +0 -0
  15. pdflinkcheck/data/icons/SplashScreen-620x300.png +0 -0
  16. pdflinkcheck/data/icons/StoreLogo-50x50.png +0 -0
  17. pdflinkcheck/data/icons/WideLogo-310x150.png +0 -0
  18. pdflinkcheck/data/icons/red_pdf_512px.ico +0 -0
  19. pdflinkcheck/data/pyproject.toml +20 -23
  20. pdflinkcheck/data/themes/forest/forest-dark/border-accent-hover.png +0 -0
  21. pdflinkcheck/data/themes/forest/forest-dark/border-accent.png +0 -0
  22. pdflinkcheck/data/themes/forest/forest-dark/border-basic.png +0 -0
  23. pdflinkcheck/data/themes/forest/forest-dark/border-hover.png +0 -0
  24. pdflinkcheck/data/themes/forest/forest-dark/border-invalid.png +0 -0
  25. pdflinkcheck/data/themes/forest/forest-dark/card.png +0 -0
  26. pdflinkcheck/data/themes/forest/forest-dark/check-accent.png +0 -0
  27. pdflinkcheck/data/themes/forest/forest-dark/check-basic.png +0 -0
  28. pdflinkcheck/data/themes/forest/forest-dark/check-hover.png +0 -0
  29. pdflinkcheck/data/themes/forest/forest-dark/check-tri-accent.png +0 -0
  30. pdflinkcheck/data/themes/forest/forest-dark/check-tri-basic.png +0 -0
  31. pdflinkcheck/data/themes/forest/forest-dark/check-tri-hover.png +0 -0
  32. pdflinkcheck/data/themes/forest/forest-dark/check-unsel-accent.png +0 -0
  33. pdflinkcheck/data/themes/forest/forest-dark/check-unsel-basic.png +0 -0
  34. pdflinkcheck/data/themes/forest/forest-dark/check-unsel-hover.png +0 -0
  35. pdflinkcheck/data/themes/forest/forest-dark/check-unsel-pressed.png +0 -0
  36. pdflinkcheck/data/themes/forest/forest-dark/combo-button-basic.png +0 -0
  37. pdflinkcheck/data/themes/forest/forest-dark/combo-button-focus.png +0 -0
  38. pdflinkcheck/data/themes/forest/forest-dark/combo-button-hover.png +0 -0
  39. pdflinkcheck/data/themes/forest/forest-dark/down.png +0 -0
  40. pdflinkcheck/data/themes/forest/forest-dark/empty.png +0 -0
  41. pdflinkcheck/data/themes/forest/forest-dark/hor-accent.png +0 -0
  42. pdflinkcheck/data/themes/forest/forest-dark/hor-basic.png +0 -0
  43. pdflinkcheck/data/themes/forest/forest-dark/hor-hover.png +0 -0
  44. pdflinkcheck/data/themes/forest/forest-dark/notebook.png +0 -0
  45. pdflinkcheck/data/themes/forest/forest-dark/off-accent.png +0 -0
  46. pdflinkcheck/data/themes/forest/forest-dark/off-basic.png +0 -0
  47. pdflinkcheck/data/themes/forest/forest-dark/off-hover.png +0 -0
  48. pdflinkcheck/data/themes/forest/forest-dark/on-accent.png +0 -0
  49. pdflinkcheck/data/themes/forest/forest-dark/on-basic.png +0 -0
  50. pdflinkcheck/data/themes/forest/forest-dark/on-hover.png +0 -0
  51. pdflinkcheck/data/themes/forest/forest-dark/radio-accent.png +0 -0
  52. pdflinkcheck/data/themes/forest/forest-dark/radio-basic.png +0 -0
  53. pdflinkcheck/data/themes/forest/forest-dark/radio-hover.png +0 -0
  54. pdflinkcheck/data/themes/forest/forest-dark/radio-tri-accent.png +0 -0
  55. pdflinkcheck/data/themes/forest/forest-dark/radio-tri-basic.png +0 -0
  56. pdflinkcheck/data/themes/forest/forest-dark/radio-tri-hover.png +0 -0
  57. pdflinkcheck/data/themes/forest/forest-dark/radio-unsel-accent.png +0 -0
  58. pdflinkcheck/data/themes/forest/forest-dark/radio-unsel-basic.png +0 -0
  59. pdflinkcheck/data/themes/forest/forest-dark/radio-unsel-hover.png +0 -0
  60. pdflinkcheck/data/themes/forest/forest-dark/radio-unsel-pressed.png +0 -0
  61. pdflinkcheck/data/themes/forest/forest-dark/rect-accent-hover.png +0 -0
  62. pdflinkcheck/data/themes/forest/forest-dark/rect-accent.png +0 -0
  63. pdflinkcheck/data/themes/forest/forest-dark/rect-basic.png +0 -0
  64. pdflinkcheck/data/themes/forest/forest-dark/rect-hover.png +0 -0
  65. pdflinkcheck/data/themes/forest/forest-dark/right.png +0 -0
  66. pdflinkcheck/data/themes/forest/forest-dark/scale-hor.png +0 -0
  67. pdflinkcheck/data/themes/forest/forest-dark/scale-vert.png +0 -0
  68. pdflinkcheck/data/themes/forest/forest-dark/separator.png +0 -0
  69. pdflinkcheck/data/themes/forest/forest-dark/sizegrip.png +0 -0
  70. pdflinkcheck/data/themes/forest/forest-dark/spin-button-down-basic.png +0 -0
  71. pdflinkcheck/data/themes/forest/forest-dark/spin-button-down-focus.png +0 -0
  72. pdflinkcheck/data/themes/forest/forest-dark/spin-button-up.png +0 -0
  73. pdflinkcheck/data/themes/forest/forest-dark/tab-accent.png +0 -0
  74. pdflinkcheck/data/themes/forest/forest-dark/tab-basic.png +0 -0
  75. pdflinkcheck/data/themes/forest/forest-dark/tab-hover.png +0 -0
  76. pdflinkcheck/data/themes/forest/forest-dark/thumb-hor-accent.png +0 -0
  77. pdflinkcheck/data/themes/forest/forest-dark/thumb-hor-basic.png +0 -0
  78. pdflinkcheck/data/themes/forest/forest-dark/thumb-hor-hover.png +0 -0
  79. pdflinkcheck/data/themes/forest/forest-dark/thumb-vert-accent.png +0 -0
  80. pdflinkcheck/data/themes/forest/forest-dark/thumb-vert-basic.png +0 -0
  81. pdflinkcheck/data/themes/forest/forest-dark/thumb-vert-hover.png +0 -0
  82. pdflinkcheck/data/themes/forest/forest-dark/tree-basic.png +0 -0
  83. pdflinkcheck/data/themes/forest/forest-dark/tree-pressed.png +0 -0
  84. pdflinkcheck/data/themes/forest/forest-dark/up.png +0 -0
  85. pdflinkcheck/data/themes/forest/forest-dark/vert-accent.png +0 -0
  86. pdflinkcheck/data/themes/forest/forest-dark/vert-basic.png +0 -0
  87. pdflinkcheck/data/themes/forest/forest-dark/vert-hover.png +0 -0
  88. pdflinkcheck/data/themes/forest/forest-dark.tcl +536 -0
  89. pdflinkcheck/data/themes/forest/forest-light/border-accent-hover.png +0 -0
  90. pdflinkcheck/data/themes/forest/forest-light/border-accent.png +0 -0
  91. pdflinkcheck/data/themes/forest/forest-light/border-basic.png +0 -0
  92. pdflinkcheck/data/themes/forest/forest-light/border-hover.png +0 -0
  93. pdflinkcheck/data/themes/forest/forest-light/border-invalid.png +0 -0
  94. pdflinkcheck/data/themes/forest/forest-light/card.png +0 -0
  95. pdflinkcheck/data/themes/forest/forest-light/check-accent.png +0 -0
  96. pdflinkcheck/data/themes/forest/forest-light/check-basic.png +0 -0
  97. pdflinkcheck/data/themes/forest/forest-light/check-hover.png +0 -0
  98. pdflinkcheck/data/themes/forest/forest-light/check-tri-accent.png +0 -0
  99. pdflinkcheck/data/themes/forest/forest-light/check-tri-basic.png +0 -0
  100. pdflinkcheck/data/themes/forest/forest-light/check-tri-hover.png +0 -0
  101. pdflinkcheck/data/themes/forest/forest-light/check-unsel-accent.png +0 -0
  102. pdflinkcheck/data/themes/forest/forest-light/check-unsel-basic.png +0 -0
  103. pdflinkcheck/data/themes/forest/forest-light/check-unsel-hover.png +0 -0
  104. pdflinkcheck/data/themes/forest/forest-light/check-unsel-pressed.png +0 -0
  105. pdflinkcheck/data/themes/forest/forest-light/combo-button-basic.png +0 -0
  106. pdflinkcheck/data/themes/forest/forest-light/combo-button-focus.png +0 -0
  107. pdflinkcheck/data/themes/forest/forest-light/combo-button-hover.png +0 -0
  108. pdflinkcheck/data/themes/forest/forest-light/down-focus.png +0 -0
  109. pdflinkcheck/data/themes/forest/forest-light/down.png +0 -0
  110. pdflinkcheck/data/themes/forest/forest-light/empty.png +0 -0
  111. pdflinkcheck/data/themes/forest/forest-light/hor-accent.png +0 -0
  112. pdflinkcheck/data/themes/forest/forest-light/hor-basic.png +0 -0
  113. pdflinkcheck/data/themes/forest/forest-light/hor-hover.png +0 -0
  114. pdflinkcheck/data/themes/forest/forest-light/notebook.png +0 -0
  115. pdflinkcheck/data/themes/forest/forest-light/off-accent.png +0 -0
  116. pdflinkcheck/data/themes/forest/forest-light/off-basic.png +0 -0
  117. pdflinkcheck/data/themes/forest/forest-light/off-hover.png +0 -0
  118. pdflinkcheck/data/themes/forest/forest-light/on-accent.png +0 -0
  119. pdflinkcheck/data/themes/forest/forest-light/on-basic.png +0 -0
  120. pdflinkcheck/data/themes/forest/forest-light/on-hover.png +0 -0
  121. pdflinkcheck/data/themes/forest/forest-light/radio-accent.png +0 -0
  122. pdflinkcheck/data/themes/forest/forest-light/radio-basic.png +0 -0
  123. pdflinkcheck/data/themes/forest/forest-light/radio-hover.png +0 -0
  124. pdflinkcheck/data/themes/forest/forest-light/radio-tri-accent.png +0 -0
  125. pdflinkcheck/data/themes/forest/forest-light/radio-tri-basic.png +0 -0
  126. pdflinkcheck/data/themes/forest/forest-light/radio-tri-hover.png +0 -0
  127. pdflinkcheck/data/themes/forest/forest-light/radio-unsel-accent.png +0 -0
  128. pdflinkcheck/data/themes/forest/forest-light/radio-unsel-basic.png +0 -0
  129. pdflinkcheck/data/themes/forest/forest-light/radio-unsel-hover.png +0 -0
  130. pdflinkcheck/data/themes/forest/forest-light/radio-unsel-pressed.png +0 -0
  131. pdflinkcheck/data/themes/forest/forest-light/rect-accent-hover.png +0 -0
  132. pdflinkcheck/data/themes/forest/forest-light/rect-accent.png +0 -0
  133. pdflinkcheck/data/themes/forest/forest-light/rect-basic.png +0 -0
  134. pdflinkcheck/data/themes/forest/forest-light/rect-hover.png +0 -0
  135. pdflinkcheck/data/themes/forest/forest-light/right-focus.png +0 -0
  136. pdflinkcheck/data/themes/forest/forest-light/right.png +0 -0
  137. pdflinkcheck/data/themes/forest/forest-light/scale-hor.png +0 -0
  138. pdflinkcheck/data/themes/forest/forest-light/scale-vert.png +0 -0
  139. pdflinkcheck/data/themes/forest/forest-light/separator.png +0 -0
  140. pdflinkcheck/data/themes/forest/forest-light/sizegrip.png +0 -0
  141. pdflinkcheck/data/themes/forest/forest-light/spin-button-down-basic.png +0 -0
  142. pdflinkcheck/data/themes/forest/forest-light/spin-button-down-focus.png +0 -0
  143. pdflinkcheck/data/themes/forest/forest-light/spin-button-up.png +0 -0
  144. pdflinkcheck/data/themes/forest/forest-light/tab-accent.png +0 -0
  145. pdflinkcheck/data/themes/forest/forest-light/tab-basic.png +0 -0
  146. pdflinkcheck/data/themes/forest/forest-light/tab-hover.png +0 -0
  147. pdflinkcheck/data/themes/forest/forest-light/thumb-hor-accent.png +0 -0
  148. pdflinkcheck/data/themes/forest/forest-light/thumb-hor-basic.png +0 -0
  149. pdflinkcheck/data/themes/forest/forest-light/thumb-hor-hover.png +0 -0
  150. pdflinkcheck/data/themes/forest/forest-light/thumb-vert-accent.png +0 -0
  151. pdflinkcheck/data/themes/forest/forest-light/thumb-vert-basic.png +0 -0
  152. pdflinkcheck/data/themes/forest/forest-light/thumb-vert-hover.png +0 -0
  153. pdflinkcheck/data/themes/forest/forest-light/tree-basic.png +0 -0
  154. pdflinkcheck/data/themes/forest/forest-light/tree-pressed.png +0 -0
  155. pdflinkcheck/data/themes/forest/forest-light/up.png +0 -0
  156. pdflinkcheck/data/themes/forest/forest-light/vert-accent.png +0 -0
  157. pdflinkcheck/data/themes/forest/forest-light/vert-basic.png +0 -0
  158. pdflinkcheck/data/themes/forest/forest-light/vert-hover.png +0 -0
  159. pdflinkcheck/data/themes/forest/forest-light.tcl +544 -0
  160. pdflinkcheck/datacopy.py +2 -0
  161. pdflinkcheck/dev.py +10 -23
  162. pdflinkcheck/environment.py +64 -0
  163. pdflinkcheck/gui.py +229 -103
  164. pdflinkcheck/io.py +4 -18
  165. pdflinkcheck/report.py +148 -78
  166. pdflinkcheck/stdlib_server.py +14 -6
  167. pdflinkcheck/update_msix_version.py +47 -0
  168. pdflinkcheck/validate.py +50 -73
  169. pdflinkcheck/version_info.py +5 -2
  170. {pdflinkcheck-1.1.73.dist-info → pdflinkcheck-1.1.94.dist-info}/METADATA +54 -52
  171. pdflinkcheck-1.1.94.dist-info/RECORD +176 -0
  172. pdflinkcheck-1.1.94.dist-info/licenses/LICENSE +24 -0
  173. pdflinkcheck-1.1.94.dist-info/licenses/LICENSE-MIT +9 -0
  174. pdflinkcheck-1.1.73.dist-info/RECORD +0 -21
  175. {pdflinkcheck-1.1.73.dist-info → pdflinkcheck-1.1.94.dist-info}/WHEEL +0 -0
  176. {pdflinkcheck-1.1.73.dist-info → pdflinkcheck-1.1.94.dist-info}/entry_points.txt +0 -0
  177. /pdflinkcheck-1.1.73.dist-info/licenses/LICENSE → /pdflinkcheck-1.1.94.dist-info/licenses/LICENSE-AGPL3 +0 -0
pdflinkcheck/gui.py CHANGED
@@ -1,3 +1,5 @@
1
+ #!/usr/bin/env python3
2
+ # SPDX-License-Identifier: MIT
1
3
  # src/pdflinkcheck/gui.py
2
4
  import tkinter as tk
3
5
  from tkinter import filedialog, ttk, messagebox # Added messagebox
@@ -7,20 +9,15 @@ from typing import Optional # Added Optional
7
9
  import unicodedata
8
10
  from importlib.resources import files
9
11
  import pyhabitat
10
- """
11
- try:
12
- import sv_ttk
13
- # Apply Sun Valley Tk theme
14
- sv_ttk.set_theme("light")
15
- except Exception:
16
- # Theme not available in bundle — use default
17
- pass
18
- """
12
+ import ctypes
13
+
14
+
15
+
19
16
  # Import the core analysis function
20
- from pdflinkcheck.report import run_report
21
- from pdflinkcheck.validate import run_validation
17
+ from pdflinkcheck.report import run_report_and_call_exports
22
18
  from pdflinkcheck.version_info import get_version_from_pyproject
23
19
  from pdflinkcheck.io import get_first_pdf_in_cwd, get_friendly_path, PDFLINKCHECK_HOME
20
+ from pdflinkcheck.environment import pymupdf_is_available, clear_all_caches, is_in_git_repo
24
21
 
25
22
  class RedirectText:
26
23
  """A class to redirect sys.stdout messages to a Tkinter Text widget."""
@@ -37,30 +34,79 @@ class RedirectText:
37
34
  """Required for file-like objects, but does nothing here."""
38
35
  pass
39
36
 
37
+
40
38
  class PDFLinkCheckerApp(tk.Tk):
39
+
40
+ def _initialize_forest_theme(self):
41
+ from importlib.resources import files
42
+ # Path to pdflinkcheck/data/themes/forest/
43
+ theme_dir = files("pdflinkcheck.data.themes.forest")
44
+ # Load the theme files
45
+ self.tk.call("source", str(theme_dir / f"forest-light.tcl"))
46
+ self.tk.call("source", str(theme_dir / f"forest-dark.tcl"))
47
+
48
+
49
+
50
+ def _toggle_theme(self):
51
+ # You could instead assign the dark to light of a single theme here
52
+ """
53
+ Calls light/dark toggle for the forest theme with self._toggle_theme_just_forest()
54
+ """
55
+ return self._toggle_theme_forest()
56
+
57
+ def _toggle_theme_forest(self):
58
+ if ttk.Style().theme_use() == "forest-light":
59
+ ttk.Style().theme_use("forest-dark")
60
+ elif ttk.Style().theme_use() == "forest-dark":
61
+ ttk.Style().theme_use("forest-light")
62
+
63
+ def _set_icon(self):
64
+ from importlib.resources import files
65
+ # Path to pdflinkcheck/data/icons/
66
+ icon_dir = files("pdflinkcheck.data.icons")
67
+ # Convert to a real filesystem path
68
+ icon_path = icon_dir.joinpath("red_pdf_128px.ico")
69
+ icon_path = icon_dir.joinpath("red_pdf_512px.ico")
70
+
71
+ self.iconbitmap(str(icon_path))
72
+
41
73
  def __init__(self):
42
74
  super().__init__()
43
- self.title(f"PDF Link Check v{get_version_from_pyproject()}")
44
- self.geometry("800x600")
75
+
76
+
77
+ self._initialize_forest_theme() # load but do not set internally
45
78
 
46
- # Style for the application
47
- style = ttk.Style(self)
48
- style.theme_use('clam')
79
+ ttk.Style().theme_use("forest-dark") # but if you use _toggle_theme_just_forest(), then you had better do this
80
+
81
+ if is_in_git_repo() and not pyhabitat.as_pyinstaller() and not pyhabitat.is_pyz():
82
+ # Checking for PYZ is overkill, because a PYZ s not expected to carry a .git directory, which is a check that is already completed.
83
+ title_suffix = ""# " [Development]"
84
+ else:
85
+ title_suffix = ""
86
+
87
+ self.title(f"PDF Link Check v{get_version_from_pyproject()}{title_suffix}")
88
+ self.geometry("800x600")
89
+
90
+ self._set_icon()
91
+
49
92
 
50
93
  # --- 1. Initialize Variables ---
51
94
  self.pdf_path = tk.StringVar(value="")
52
95
  self.pdf_library_var = tk.StringVar(value="PyMuPDF")
53
- #self.pdf_library_var.set("PyMuPDF")
54
96
  self.max_links_var = tk.StringVar(value="50")
55
97
  self.show_all_links_var = tk.BooleanVar(value=True)
56
98
  self.do_export_report_json_var = tk.BooleanVar(value=True)
57
- self.do_export_report_txt_var = tk.BooleanVar(value=False)
99
+ self.do_export_report_txt_var = tk.BooleanVar(value=True)
58
100
  self.current_report_text = None
59
101
  self.current_report_data = None
60
102
 
61
103
  self.supported_export_formats = ["JSON", "MD", "TXT"]
62
104
  self.supported_export_formats = ["JSON"]
63
-
105
+
106
+ if not pymupdf_is_available():
107
+ print(f"pymupdf_is_available: {pymupdf_is_available()}")
108
+ self.pdf_library_var.set("pypdf")
109
+
64
110
 
65
111
  # --- 2. Create Widgets ---
66
112
  self._create_widgets()
@@ -69,7 +115,23 @@ class PDFLinkCheckerApp(tk.Tk):
69
115
  self._toggle_max_links_entry()
70
116
  self._toggle_json_export()
71
117
  self._toggle_txt_export()
72
-
118
+
119
+ # --- Menubar with dropdown ---
120
+ menubar = tk.Menu(self)
121
+ self.config(menu=menubar)
122
+
123
+ # Tools menu
124
+ tools_menu = tk.Menu(menubar, tearoff=0)
125
+ menubar.add_cascade(label="Tools", menu=tools_menu)
126
+
127
+ tools_menu.add_command(label="Toggle Theme", command=self._toggle_theme)
128
+ tools_menu.add_command(label="Clear Cache", command=self._clear_all_caches)
129
+
130
+ # Add existing License/Readme to tools menu
131
+ tools_menu.add_separator()
132
+ tools_menu.add_command(label="License", command=self._show_license)
133
+ tools_menu.add_command(label="Readme", command=self._show_readme)
134
+ tools_menu.add_command(label="I Have Questions", command=self._show_i_have_questions)
73
135
  # In class PDFLinkCheckerApp:
74
136
 
75
137
  def _copy_pdf_path(self):
@@ -98,22 +160,43 @@ class PDFLinkCheckerApp(tk.Tk):
98
160
  """Scrolls the output text widget to the bottom."""
99
161
  self.output_text.see(tk.END) # tk.END is the index for the position just after the last character
100
162
 
163
+ def _clear_all_caches(self):
164
+ """Clear caches and show confirmation."""
165
+ clear_all_caches()
166
+ messagebox.showinfo("Caches Cleared", f"All caches have been cleared.\nPyMuPDF available: {pymupdf_is_available()}")
167
+
101
168
  def _show_license(self):
102
169
  """
103
170
  Reads the embedded LICENSE file (AGPLv3) and displays its content in a new modal window.
104
171
  """
105
172
  try:
106
- # CORRECT WAY: Use the Traversable object's read_text() method.
173
+ # Use the Traversable object's read_text() method.
107
174
  # This handles files located inside zip archives (.pyz, pipx venvs) correctly.
108
175
  license_path_traversable = files("pdflinkcheck.data") / "LICENSE"
109
176
  license_content = license_path_traversable.read_text(encoding="utf-8")
110
177
 
111
178
  except FileNotFoundError:
112
- messagebox.showerror(
113
- "License Error",
114
- "LICENSE file not found within the installation package (pdflinkcheck.data/LICENSE). Check build process."
115
- )
179
+ if is_in_git_repo():
180
+ messagebox.showinfo(
181
+ "Local Development Mode",
182
+ "Embedded data files not found – copying from root..."
183
+ )
184
+ try:
185
+ from pdflinkcheck.datacopy import ensure_package_license, ensure_data_files_for_build
186
+ #ensure_package_license()
187
+ ensure_data_files_for_build()
188
+ # Retry display
189
+ self._show_license()
190
+ return
191
+ except Exception as e:
192
+ messagebox.showerror("Copy Failed", f"Could not copy file: {e}")
193
+ else:
194
+ messagebox.showerror(
195
+ "Packaging Error",
196
+ "Embedded file not found. This indicates a problem with the package build/installation."
197
+ )
116
198
  return
199
+
117
200
  except Exception as e:
118
201
  messagebox.showerror("Read Error", f"Failed to read embedded LICENSE file: {e}")
119
202
  return
@@ -146,17 +229,31 @@ class PDFLinkCheckerApp(tk.Tk):
146
229
  Reads the embedded README.md file and displays its content in a new modal window.
147
230
  """
148
231
  try:
149
- # CORRECT WAY: Use the Traversable object's read_text() method.
232
+ # Use the Traversable object's read_text() method.
150
233
  # This handles files located inside zip archives (.pyz, pipx venvs) correctly.
151
234
  readme_path_traversable = files("pdflinkcheck.data") / "README.md"
152
235
  readme_content = readme_path_traversable.read_text(encoding="utf-8")
153
236
  readme_content = sanitize_glyphs_for_tkinter(readme_content)
154
-
155
237
  except FileNotFoundError:
156
- messagebox.showerror(
157
- "Readme Error",
158
- "README.md file not found within the installation package (pdflinkcheck.data/README.md). Check build process."
159
- )
238
+ if is_in_git_repo():
239
+ messagebox.showinfo(
240
+ "Local Development Mode",
241
+ "Embedded data files not found – copying from root..."
242
+ )
243
+ try:
244
+ from pdflinkcheck.datacopy import ensure_package_readme, ensure_data_files_for_build
245
+ #ensure_package_readme()
246
+ ensure_data_files_for_build()
247
+ # Retry display
248
+ self._show_readme()
249
+ return
250
+ except Exception as e:
251
+ messagebox.showerror("Copy Failed", f"Could not copy file: {e}")
252
+ else:
253
+ messagebox.showerror(
254
+ "Packaging Error",
255
+ "Embedded file not found. This indicates a problem with the package build/installation."
256
+ )
160
257
  return
161
258
  except Exception as e:
162
259
  messagebox.showerror("Read Error", f"Failed to read embedded README.md file: {e}")
@@ -185,6 +282,43 @@ class PDFLinkCheckerApp(tk.Tk):
185
282
  readme_window.grab_set()
186
283
  self.wait_window(readme_window)
187
284
 
285
+ def _show_i_have_questions(self):
286
+ """
287
+ Reads the embedded I Have Questions.md file and displays its content in a new modal window.
288
+ """
289
+ try:
290
+ # Use the Traversable object's read_text() method.
291
+ # This handles files located inside zip archives (.pyz, pipx venvs) correctly.
292
+ i_have_questions_path_traversable = files("pdflinkcheck.data") / "I Have Questions.md"
293
+ i_have_questions_content = i_have_questions_path_traversable.read_text(encoding="utf-8")
294
+ i_have_questions_content = sanitize_glyphs_for_tkinter(i_have_questions_content)
295
+ except FileNotFoundError:
296
+ messagebox.showerror("Read Error", f"Failed to read embedded 'I Have Questions.md' file.")
297
+ return
298
+
299
+ # --- Display in a New Toplevel Window ---
300
+ i_have_questions_window = tk.Toplevel(self)
301
+ i_have_questions_window.title("I Have Questions.md")
302
+ i_have_questions_window.geometry("600x400")
303
+
304
+ # Text widget for content
305
+ text_widget = tk.Text(i_have_questions_window, wrap=tk.WORD, font=('Monospace', 10), padx=10, pady=10)
306
+ text_widget.insert(tk.END, i_have_questions_content)
307
+ text_widget.config(state=tk.DISABLED)
308
+
309
+ # Scrollbar
310
+ scrollbar = ttk.Scrollbar(i_have_questions_window, command=text_widget.yview)
311
+ text_widget['yscrollcommand'] = scrollbar.set
312
+
313
+ # Layout
314
+ scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
315
+ text_widget.pack(fill='both', expand=True)
316
+
317
+ # Make the window modal (optional, but good practice for notices)
318
+ i_have_questions_window.transient(self)
319
+ i_have_questions_window.grab_set()
320
+ self.wait_window(i_have_questions_window)
321
+
188
322
  def _create_widgets(self):
189
323
  # --- Control Frame (Top) ---
190
324
  control_frame = ttk.Frame(self, padding="10")
@@ -228,7 +362,7 @@ class PDFLinkCheckerApp(tk.Tk):
228
362
  ).pack(side='left', padx=5, pady=1)
229
363
 
230
364
  ttk.Label(report_brevity_frame, text="Max Links to Display:").pack(side='left', padx=5, pady=1)
231
- self.max_links_entry = ttk.Entry(report_brevity_frame, textvariable=self.max_links_var, width=4)
365
+ self.max_links_entry = ttk.Entry(report_brevity_frame, textvariable=self.max_links_var, width=7)
232
366
  self.max_links_entry.pack(side='left', padx=5, pady=5)
233
367
 
234
368
  # --- PDF Library Selection ---
@@ -274,11 +408,7 @@ class PDFLinkCheckerApp(tk.Tk):
274
408
  run_analysis_btn = ttk.Button(control_frame, text="▶ Run Analysis", command=self._run_report_gui, style='Accent.TButton')
275
409
  run_analysis_btn.grid(row=3, column=0, columnspan=2, pady=10, sticky='ew', padx=(0, 5))
276
410
 
277
- run_validation_btn = ttk.Button(control_frame, text="▶ Run Validation", command=self._run_validation_gui, style='Accent.TButton')
278
- run_validation_btn.grid(row=4, column=0, columnspan=2, pady=10, sticky='ew', padx=(0, 5))
279
- # Ensure the run button frame expands to fill its column
280
- #run_analysis_btn.grid_columnconfigure(0, weight=1)
281
-
411
+ """
282
412
  # 2. Create a Frame to hold the two file link buttons (This frame goes into column 2)
283
413
  info_btn_frame = ttk.Frame(control_frame)
284
414
  info_btn_frame.grid(row=3, column=2, columnspan=1, pady=10, sticky='ew', padx=(5, 0))
@@ -286,13 +416,14 @@ class PDFLinkCheckerApp(tk.Tk):
286
416
  info_btn_frame.grid_columnconfigure(0, weight=1)
287
417
  info_btn_frame.grid_columnconfigure(1, weight=1)
288
418
 
289
- # 3. Place License and Readme buttons inside the new frame
290
- license_btn = ttk.Button(info_btn_frame, text="License", command=self._show_license)
419
+ # 3. Placeholder buttons inside the info button frame
420
+ info_1_btn = ttk.Button(info_btn_frame, text="Empty1", command=self._do_stuff_1)
291
421
  # Use PACK or a 2-column GRID inside the info_btn_frame. GRID is cleaner here.
292
- license_btn.grid(row=0, column=0, sticky='ew', padx=(0, 2)) # Left side of the frame
422
+ info_1_btn.grid(row=0, column=0, sticky='ew', padx=(0, 2)) # Left side of the frame
293
423
 
294
- readme_btn = ttk.Button(info_btn_frame, text="Readme", command=self._show_readme)
295
- readme_btn.grid(row=0, column=1, sticky='ew', padx=(2, 0)) # Right side of the frame
424
+ info_2_btn = ttk.Button(info_btn_frame, text="Empty2", command=self._do_stuff_2)
425
+ info_2_btn.grid(row=0, column=1, sticky='ew', padx=(2, 0)) # Right side of the frame
426
+ """
296
427
 
297
428
  # Force the columns to distribute space evenly
298
429
  control_frame.grid_columnconfigure(0, weight=2)
@@ -342,7 +473,7 @@ class PDFLinkCheckerApp(tk.Tk):
342
473
  if self.pdf_path.get():
343
474
  initialdir = str(Path(self.pdf_path.get()).parent)
344
475
  else:
345
- initialdir = str(Path.cwd())
476
+ initialdir = str(Path.home())
346
477
 
347
478
  file_path = filedialog.askopenfilename(
348
479
  initialdir=initialdir,
@@ -370,6 +501,15 @@ class PDFLinkCheckerApp(tk.Tk):
370
501
  if self.do_export_report_txt_var.get():
371
502
  pass # placeholder # no side effects
372
503
 
504
+
505
+
506
+ def _toggle_pdf_library(self):
507
+ selected_lib = self.pdf_library_var.get().lower()
508
+ try:
509
+ self.pdf_library_var.set("pypdf" if selected_lib == "pymupdf" else "pymupdf")
510
+ except Exception:
511
+ pass
512
+
373
513
  def _assess_pdf_path_str(self):
374
514
  pdf_path_str = self.pdf_path.get().strip()
375
515
  if not pdf_path_str:
@@ -399,7 +539,7 @@ class PDFLinkCheckerApp(tk.Tk):
399
539
  else:
400
540
  try:
401
541
  max_links_to_pass = int(self.max_links_var.get())
402
- if max_links_to_pass < 1:
542
+ if max_links_to_pass < 0:
403
543
  self._display_error("Error: Max Links must be a positive number (or use 'Show All').")
404
544
  return
405
545
  except ValueError:
@@ -427,7 +567,7 @@ class PDFLinkCheckerApp(tk.Tk):
427
567
  try:
428
568
  # 3. Call the core logic function
429
569
  #self.output_text.insert(tk.END, "--- Starting Analysis ---\n")
430
- report_results = run_report(
570
+ report_results = run_report_and_call_exports(
431
571
  pdf_path=pdf_path_str,
432
572
  max_links=max_links_to_pass,
433
573
  export_format=export_format,
@@ -440,67 +580,44 @@ class PDFLinkCheckerApp(tk.Tk):
440
580
 
441
581
  except Exception as e:
442
582
  # Inform the user in the GUI with a clean message
443
- self._display_error(f"An unexpected error occurred during analysis: {e}")
444
-
445
- finally:
446
- # 4. Restore standard output and disable editing
447
- sys.stdout = original_stdout
448
- self.output_text.config(state=tk.DISABLED)
449
-
450
- def _run_validation_gui(self):
451
-
452
- pdf_path_str = self._assess_pdf_path_str()
453
- if not pdf_path_str:
454
- return
455
-
456
- pdf_library = self._discern_pdf_library()
457
-
458
- # 1. Clear previous output and enable editing
459
- self.output_text.config(state=tk.NORMAL)
460
- self.output_text.delete('1.0', tk.END)
461
-
462
- # 2. Redirect standard output to the Text widget
463
- original_stdout = sys.stdout
464
- sys.stdout = RedirectText(self.output_text)
465
-
466
- if not self.current_report_data:
467
- self._run_report_gui()
468
- report_results = self.current_report_data
469
-
470
- try:
471
- # 3. Call the core logic function
472
- #self.output_text.insert(tk.END, "--- Starting Analysis ---\n")
473
- validation_results = run_validation(
474
- report_results=report_results,
475
- pdf_path=pdf_path_str,
476
- pdf_library=pdf_library,
477
- export_json=True,
478
- print_bool=True
583
+ messagebox.showinfo( # Changed from showwarning less alarming
584
+ "PyMuPDF Not Available",
585
+ "PyMuPDF is not installed or not working.\n"
586
+ "Switched to pypdf engine automatically.\n\n"
587
+ "To use the faster PyMuPDF engine:\n"
588
+ "1. Install it: pip install pymupdf\n"
589
+ "2. Go to Tools → Clear Cache\n"
590
+ "3. Run analysis again"
479
591
  )
480
- self.current_report_text = report_results.get("text", "")
481
- self.current_report_data = report_results.get("data", {})
482
-
483
- #self.output_text.insert(tk.END, "\n--- Analysis Complete ---\n")
484
-
485
- except Exception as e:
486
- # Inform the user in the GUI with a clean message
487
- self._display_error(f"An unexpected error occurred during analysis: {e}")
488
-
592
+ self._toggle_pdf_library()
593
+
594
+
489
595
  finally:
490
596
  # 4. Restore standard output and disable editing
491
597
  sys.stdout = original_stdout
492
598
  self.output_text.config(state=tk.DISABLED)
493
-
494
-
599
+ # -- This call to _toggle_pdf_library() fails currently --
600
+
495
601
  def _discern_pdf_library(self):
496
602
  selected_lib = self.pdf_library_var.get().lower()
497
603
 
498
604
  if selected_lib == "pymupdf":
499
- print("Using high-speed PyMuPDF engine.")
605
+ self._display_msg("Using high-speed PyMuPDF engine.")
500
606
  elif selected_lib == "pypdf":
501
- print("Using pure-python pypdf engine.")
607
+ self._display_msg("Using pure-python pypdf engine.")
502
608
  return selected_lib
503
609
 
610
+ def _display_msg(self, message):
611
+ # Ensure output is in normal state to write
612
+ original_state = self.output_text.cget('state')
613
+ if original_state == tk.DISABLED:
614
+ self.output_text.config(state=tk.NORMAL)
615
+
616
+ #self.output_text.delete('1.0', tk.END)
617
+ self.output_text.insert(tk.END, f"{message}\n", 'msg')
618
+ self.output_text.tag_config('msg')#, foreground='red')
619
+ self.output_text.see(tk.END)
620
+
504
621
  def _display_error(self, message):
505
622
  # Ensure output is in normal state to write
506
623
  original_state = self.output_text.cget('state')
@@ -536,15 +653,7 @@ class PDFLinkCheckerApp(tk.Tk):
536
653
 
537
654
  except Exception as e:
538
655
  messagebox.showerror("View Error", f"Could not launch editor: {e}")
539
-
540
- """
541
- def toggle_theme():
542
- try:
543
- current = sv_ttk.get_theme()
544
- sv_ttk.set_theme("dark" if current == "light" else "light")
545
- except Exception:
546
- pass
547
- """
656
+
548
657
  def sanitize_glyphs_for_tkinter(text: str) -> str:
549
658
  """
550
659
  Converts complex Unicode characters (like emojis and symbols)
@@ -574,13 +683,30 @@ def auto_close_window(root, delay_ms:int = 0):
574
683
  return
575
684
 
576
685
 
686
+
577
687
  def start_gui(time_auto_close:int=0):
578
688
  """
579
689
  Entry point function to launch the application.
580
690
  """
581
691
  print("pdflinkcheck: start_gui ...")
692
+
582
693
  tk_app = PDFLinkCheckerApp()
583
694
 
695
+ # Bring window to front
696
+ tk_app.lift()
697
+ #tk_app.attributes('-topmost', True)
698
+ #tk_app.after(100, lambda: tk_app.attributes('-topmost', False))
699
+ tk_app.wm_attributes("-topmost", True)
700
+ tk_app.after(200, lambda: tk_app.wm_attributes("-topmost", False))
701
+ tk_app.deiconify()
702
+ tk_app.focus_force()
703
+
704
+ # Win32 nudge (optional but helpful)
705
+ if pyhabitat.on_windows():
706
+ hwnd = tk_app.winfo_id()
707
+ ctypes.windll.user32.SetForegroundWindow(hwnd)
708
+
709
+ # Ths is called in the CLI by the --auto-close flag value, for CI scripted testing purposes (like in .github/workflows/build.yml)
584
710
  auto_close_window(tk_app, time_auto_close)
585
711
 
586
712
  tk_app.mainloop()
pdflinkcheck/io.py CHANGED
@@ -1,3 +1,5 @@
1
+ #!/usr/bin/env python3
2
+ # SPDX-License-Identifier: MIT
1
3
  # src/pdflinkcheck/io.py
2
4
  import logging
3
5
  import json
@@ -112,6 +114,7 @@ def export_report_json(
112
114
  pdf_library: str
113
115
  ) -> Path:
114
116
  """Exports structured dictionary results to a .json file."""
117
+
115
118
  base_name = Path(pdf_filename).stem
116
119
  output_path = PDFLINKCHECK_HOME / f"{base_name}_{pdf_library}_report.json"
117
120
 
@@ -130,6 +133,7 @@ def export_report_txt(
130
133
  pdf_library: str
131
134
  ) -> Path:
132
135
  """Exports the formatted string buffer to a .txt file."""
136
+ #pdf_filename = implement_non_redundant_naming(pdf_filename)
133
137
  base_name = Path(pdf_filename).stem
134
138
  output_path = PDFLINKCHECK_HOME / f"{base_name}_{pdf_library}_report.txt"
135
139
 
@@ -141,24 +145,6 @@ def export_report_txt(
141
145
  error_logger.error(f"TXT export failed: {e}", exc_info=True)
142
146
  raise RuntimeError(f"TXT export failed: {e}")
143
147
 
144
- def export_validation_json(
145
- report_data: Dict[str, Any],
146
- pdf_filename: str,
147
- pdf_library: str
148
- ) -> Path:
149
- """Exports structured dictionary validation results to a .json file."""
150
- base_name = Path(pdf_filename).stem
151
- output_path = PDFLINKCHECK_HOME / f"{base_name}_{pdf_library}_validation.json"
152
-
153
- try:
154
- with open(output_path, 'w', encoding='utf-8') as f:
155
- json.dump(report_data, f, indent=4)
156
- print(f"\nJSON validation exported: {get_friendly_path(output_path)}")
157
- return output_path
158
- except Exception as e:
159
- error_logger.error(f"JSON validation export failed: {e}", exc_info=True)
160
- raise RuntimeError(f"JSON validation export failed: {e}")
161
-
162
148
  # --- helpers ---
163
149
  def get_friendly_path(full_path: str) -> str:
164
150
  p = Path(full_path).resolve()