pomera-ai-commander 0.1.0 → 1.2.1

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 (191) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +105 -680
  3. package/bin/pomera-ai-commander.js +62 -62
  4. package/core/__init__.py +65 -65
  5. package/core/app_context.py +482 -482
  6. package/core/async_text_processor.py +421 -421
  7. package/core/backup_manager.py +655 -655
  8. package/core/backup_recovery_manager.py +1033 -1033
  9. package/core/content_hash_cache.py +508 -508
  10. package/core/context_menu.py +313 -313
  11. package/core/data_validator.py +1066 -1066
  12. package/core/database_connection_manager.py +744 -744
  13. package/core/database_curl_settings_manager.py +608 -608
  14. package/core/database_promera_ai_settings_manager.py +446 -446
  15. package/core/database_schema.py +411 -411
  16. package/core/database_schema_manager.py +395 -395
  17. package/core/database_settings_manager.py +1507 -1507
  18. package/core/database_settings_manager_interface.py +456 -456
  19. package/core/dialog_manager.py +734 -734
  20. package/core/efficient_line_numbers.py +510 -510
  21. package/core/error_handler.py +746 -746
  22. package/core/error_service.py +431 -431
  23. package/core/event_consolidator.py +511 -511
  24. package/core/mcp/__init__.py +43 -43
  25. package/core/mcp/protocol.py +288 -288
  26. package/core/mcp/schema.py +251 -251
  27. package/core/mcp/server_stdio.py +299 -299
  28. package/core/mcp/tool_registry.py +2372 -2345
  29. package/core/memory_efficient_text_widget.py +711 -711
  30. package/core/migration_manager.py +914 -914
  31. package/core/migration_test_suite.py +1085 -1085
  32. package/core/migration_validator.py +1143 -1143
  33. package/core/optimized_find_replace.py +714 -714
  34. package/core/optimized_pattern_engine.py +424 -424
  35. package/core/optimized_search_highlighter.py +552 -552
  36. package/core/performance_monitor.py +674 -674
  37. package/core/persistence_manager.py +712 -712
  38. package/core/progressive_stats_calculator.py +632 -632
  39. package/core/regex_pattern_cache.py +529 -529
  40. package/core/regex_pattern_library.py +350 -350
  41. package/core/search_operation_manager.py +434 -434
  42. package/core/settings_defaults_registry.py +1087 -1087
  43. package/core/settings_integrity_validator.py +1111 -1111
  44. package/core/settings_serializer.py +557 -557
  45. package/core/settings_validator.py +1823 -1823
  46. package/core/smart_stats_calculator.py +709 -709
  47. package/core/statistics_update_manager.py +619 -619
  48. package/core/stats_config_manager.py +858 -858
  49. package/core/streaming_text_handler.py +723 -723
  50. package/core/task_scheduler.py +596 -596
  51. package/core/update_pattern_library.py +168 -168
  52. package/core/visibility_monitor.py +596 -596
  53. package/core/widget_cache.py +498 -498
  54. package/mcp.json +51 -61
  55. package/package.json +61 -57
  56. package/pomera.py +7482 -7482
  57. package/pomera_mcp_server.py +183 -144
  58. package/requirements.txt +32 -0
  59. package/tools/__init__.py +4 -4
  60. package/tools/ai_tools.py +2891 -2891
  61. package/tools/ascii_art_generator.py +352 -352
  62. package/tools/base64_tools.py +183 -183
  63. package/tools/base_tool.py +511 -511
  64. package/tools/case_tool.py +308 -308
  65. package/tools/column_tools.py +395 -395
  66. package/tools/cron_tool.py +884 -884
  67. package/tools/curl_history.py +600 -600
  68. package/tools/curl_processor.py +1207 -1207
  69. package/tools/curl_settings.py +502 -502
  70. package/tools/curl_tool.py +5467 -5467
  71. package/tools/diff_viewer.py +1071 -1071
  72. package/tools/email_extraction_tool.py +248 -248
  73. package/tools/email_header_analyzer.py +425 -425
  74. package/tools/extraction_tools.py +250 -250
  75. package/tools/find_replace.py +1750 -1750
  76. package/tools/folder_file_reporter.py +1463 -1463
  77. package/tools/folder_file_reporter_adapter.py +480 -480
  78. package/tools/generator_tools.py +1216 -1216
  79. package/tools/hash_generator.py +255 -255
  80. package/tools/html_tool.py +656 -656
  81. package/tools/jsonxml_tool.py +729 -729
  82. package/tools/line_tools.py +419 -419
  83. package/tools/markdown_tools.py +561 -561
  84. package/tools/mcp_widget.py +1417 -1417
  85. package/tools/notes_widget.py +973 -973
  86. package/tools/number_base_converter.py +372 -372
  87. package/tools/regex_extractor.py +571 -571
  88. package/tools/slug_generator.py +310 -310
  89. package/tools/sorter_tools.py +458 -458
  90. package/tools/string_escape_tool.py +392 -392
  91. package/tools/text_statistics_tool.py +365 -365
  92. package/tools/text_wrapper.py +430 -430
  93. package/tools/timestamp_converter.py +421 -421
  94. package/tools/tool_loader.py +710 -710
  95. package/tools/translator_tools.py +522 -522
  96. package/tools/url_link_extractor.py +261 -261
  97. package/tools/url_parser.py +204 -204
  98. package/tools/whitespace_tools.py +355 -355
  99. package/tools/word_frequency_counter.py +146 -146
  100. package/core/__pycache__/__init__.cpython-313.pyc +0 -0
  101. package/core/__pycache__/app_context.cpython-313.pyc +0 -0
  102. package/core/__pycache__/async_text_processor.cpython-313.pyc +0 -0
  103. package/core/__pycache__/backup_manager.cpython-313.pyc +0 -0
  104. package/core/__pycache__/backup_recovery_manager.cpython-313.pyc +0 -0
  105. package/core/__pycache__/content_hash_cache.cpython-313.pyc +0 -0
  106. package/core/__pycache__/context_menu.cpython-313.pyc +0 -0
  107. package/core/__pycache__/data_validator.cpython-313.pyc +0 -0
  108. package/core/__pycache__/database_connection_manager.cpython-313.pyc +0 -0
  109. package/core/__pycache__/database_curl_settings_manager.cpython-313.pyc +0 -0
  110. package/core/__pycache__/database_promera_ai_settings_manager.cpython-313.pyc +0 -0
  111. package/core/__pycache__/database_schema.cpython-313.pyc +0 -0
  112. package/core/__pycache__/database_schema_manager.cpython-313.pyc +0 -0
  113. package/core/__pycache__/database_settings_manager.cpython-313.pyc +0 -0
  114. package/core/__pycache__/database_settings_manager_interface.cpython-313.pyc +0 -0
  115. package/core/__pycache__/dialog_manager.cpython-313.pyc +0 -0
  116. package/core/__pycache__/efficient_line_numbers.cpython-313.pyc +0 -0
  117. package/core/__pycache__/error_handler.cpython-313.pyc +0 -0
  118. package/core/__pycache__/error_service.cpython-313.pyc +0 -0
  119. package/core/__pycache__/event_consolidator.cpython-313.pyc +0 -0
  120. package/core/__pycache__/memory_efficient_text_widget.cpython-313.pyc +0 -0
  121. package/core/__pycache__/migration_manager.cpython-313.pyc +0 -0
  122. package/core/__pycache__/migration_test_suite.cpython-313.pyc +0 -0
  123. package/core/__pycache__/migration_validator.cpython-313.pyc +0 -0
  124. package/core/__pycache__/optimized_find_replace.cpython-313.pyc +0 -0
  125. package/core/__pycache__/optimized_pattern_engine.cpython-313.pyc +0 -0
  126. package/core/__pycache__/optimized_search_highlighter.cpython-313.pyc +0 -0
  127. package/core/__pycache__/performance_monitor.cpython-313.pyc +0 -0
  128. package/core/__pycache__/persistence_manager.cpython-313.pyc +0 -0
  129. package/core/__pycache__/progressive_stats_calculator.cpython-313.pyc +0 -0
  130. package/core/__pycache__/regex_pattern_cache.cpython-313.pyc +0 -0
  131. package/core/__pycache__/regex_pattern_library.cpython-313.pyc +0 -0
  132. package/core/__pycache__/search_operation_manager.cpython-313.pyc +0 -0
  133. package/core/__pycache__/settings_defaults_registry.cpython-313.pyc +0 -0
  134. package/core/__pycache__/settings_integrity_validator.cpython-313.pyc +0 -0
  135. package/core/__pycache__/settings_serializer.cpython-313.pyc +0 -0
  136. package/core/__pycache__/settings_validator.cpython-313.pyc +0 -0
  137. package/core/__pycache__/smart_stats_calculator.cpython-313.pyc +0 -0
  138. package/core/__pycache__/statistics_update_manager.cpython-313.pyc +0 -0
  139. package/core/__pycache__/stats_config_manager.cpython-313.pyc +0 -0
  140. package/core/__pycache__/streaming_text_handler.cpython-313.pyc +0 -0
  141. package/core/__pycache__/task_scheduler.cpython-313.pyc +0 -0
  142. package/core/__pycache__/visibility_monitor.cpython-313.pyc +0 -0
  143. package/core/__pycache__/widget_cache.cpython-313.pyc +0 -0
  144. package/core/mcp/__pycache__/__init__.cpython-313.pyc +0 -0
  145. package/core/mcp/__pycache__/protocol.cpython-313.pyc +0 -0
  146. package/core/mcp/__pycache__/schema.cpython-313.pyc +0 -0
  147. package/core/mcp/__pycache__/server_stdio.cpython-313.pyc +0 -0
  148. package/core/mcp/__pycache__/tool_registry.cpython-313.pyc +0 -0
  149. package/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  150. package/tools/__pycache__/ai_tools.cpython-313.pyc +0 -0
  151. package/tools/__pycache__/ascii_art_generator.cpython-313.pyc +0 -0
  152. package/tools/__pycache__/base64_tools.cpython-313.pyc +0 -0
  153. package/tools/__pycache__/base_tool.cpython-313.pyc +0 -0
  154. package/tools/__pycache__/case_tool.cpython-313.pyc +0 -0
  155. package/tools/__pycache__/column_tools.cpython-313.pyc +0 -0
  156. package/tools/__pycache__/cron_tool.cpython-313.pyc +0 -0
  157. package/tools/__pycache__/curl_history.cpython-313.pyc +0 -0
  158. package/tools/__pycache__/curl_processor.cpython-313.pyc +0 -0
  159. package/tools/__pycache__/curl_settings.cpython-313.pyc +0 -0
  160. package/tools/__pycache__/curl_tool.cpython-313.pyc +0 -0
  161. package/tools/__pycache__/diff_viewer.cpython-313.pyc +0 -0
  162. package/tools/__pycache__/email_extraction_tool.cpython-313.pyc +0 -0
  163. package/tools/__pycache__/email_header_analyzer.cpython-313.pyc +0 -0
  164. package/tools/__pycache__/extraction_tools.cpython-313.pyc +0 -0
  165. package/tools/__pycache__/find_replace.cpython-313.pyc +0 -0
  166. package/tools/__pycache__/folder_file_reporter.cpython-313.pyc +0 -0
  167. package/tools/__pycache__/folder_file_reporter_adapter.cpython-313.pyc +0 -0
  168. package/tools/__pycache__/generator_tools.cpython-313.pyc +0 -0
  169. package/tools/__pycache__/hash_generator.cpython-313.pyc +0 -0
  170. package/tools/__pycache__/html_tool.cpython-313.pyc +0 -0
  171. package/tools/__pycache__/huggingface_helper.cpython-313.pyc +0 -0
  172. package/tools/__pycache__/jsonxml_tool.cpython-313.pyc +0 -0
  173. package/tools/__pycache__/line_tools.cpython-313.pyc +0 -0
  174. package/tools/__pycache__/list_comparator.cpython-313.pyc +0 -0
  175. package/tools/__pycache__/markdown_tools.cpython-313.pyc +0 -0
  176. package/tools/__pycache__/mcp_widget.cpython-313.pyc +0 -0
  177. package/tools/__pycache__/notes_widget.cpython-313.pyc +0 -0
  178. package/tools/__pycache__/number_base_converter.cpython-313.pyc +0 -0
  179. package/tools/__pycache__/regex_extractor.cpython-313.pyc +0 -0
  180. package/tools/__pycache__/slug_generator.cpython-313.pyc +0 -0
  181. package/tools/__pycache__/sorter_tools.cpython-313.pyc +0 -0
  182. package/tools/__pycache__/string_escape_tool.cpython-313.pyc +0 -0
  183. package/tools/__pycache__/text_statistics_tool.cpython-313.pyc +0 -0
  184. package/tools/__pycache__/text_wrapper.cpython-313.pyc +0 -0
  185. package/tools/__pycache__/timestamp_converter.cpython-313.pyc +0 -0
  186. package/tools/__pycache__/tool_loader.cpython-313.pyc +0 -0
  187. package/tools/__pycache__/translator_tools.cpython-313.pyc +0 -0
  188. package/tools/__pycache__/url_link_extractor.cpython-313.pyc +0 -0
  189. package/tools/__pycache__/url_parser.cpython-313.pyc +0 -0
  190. package/tools/__pycache__/whitespace_tools.cpython-313.pyc +0 -0
  191. package/tools/__pycache__/word_frequency_counter.cpython-313.pyc +0 -0
@@ -1,480 +1,480 @@
1
- """
2
- Folder File Reporter Adapter for Pomera Integration
3
-
4
- This adapter integrates the Folder File Reporter into Pomera's dropdown tool system,
5
- allowing it to work with the main application's 7-tab Input/Output system instead of
6
- having its own separate tabs.
7
- """
8
-
9
- import tkinter as tk
10
- from tkinter import ttk, filedialog
11
- import os
12
- from tools.folder_file_reporter import FolderFileReporter
13
-
14
-
15
- class FolderFileReporterAdapter:
16
- """
17
- Adapter that integrates FolderFileReporter into Pomera's tool system.
18
-
19
- This adapter creates a simplified UI in the tool settings panel and generates
20
- reports into the main application's active Input/Output tabs.
21
- """
22
-
23
- def __init__(self, parent_app):
24
- """
25
- Initialize the adapter.
26
-
27
- Args:
28
- parent_app: Reference to the main Pomera application
29
- """
30
- self.app = parent_app
31
- self.reporter = None
32
-
33
- # UI variables
34
- self.input_folder_var = tk.StringVar()
35
- self.output_folder_var = tk.StringVar()
36
-
37
- # Add trace callbacks to save settings when folders change
38
- self.input_folder_var.trace_add('write', lambda *args: self._on_folder_changed())
39
- self.output_folder_var.trace_add('write', lambda *args: self._on_folder_changed())
40
-
41
- # Field selection variables
42
- self.field_selections = {
43
- 'path': tk.BooleanVar(value=True),
44
- 'name': tk.BooleanVar(value=True),
45
- 'size': tk.BooleanVar(value=True),
46
- 'date_modified': tk.BooleanVar(value=True)
47
- }
48
-
49
- # Configuration variables
50
- self.separator = tk.StringVar(value=" | ")
51
- self.folders_only = tk.BooleanVar(value=False)
52
- self.recursion_mode = tk.StringVar(value="full")
53
- self.recursion_depth = tk.IntVar(value=2)
54
- self.size_format = tk.StringVar(value="human")
55
- self.date_format = tk.StringVar(value="%Y-%m-%d %H:%M:%S")
56
-
57
- # Add trace callbacks to save settings when any option changes
58
- for field_var in self.field_selections.values():
59
- field_var.trace_add('write', lambda *args: self._on_setting_changed())
60
-
61
- self.separator.trace_add('write', lambda *args: self._on_setting_changed())
62
- self.folders_only.trace_add('write', lambda *args: self._on_setting_changed())
63
- self.recursion_mode.trace_add('write', lambda *args: self._on_setting_changed())
64
- self.recursion_depth.trace_add('write', lambda *args: self._on_setting_changed())
65
- self.size_format.trace_add('write', lambda *args: self._on_setting_changed())
66
- self.date_format.trace_add('write', lambda *args: self._on_setting_changed())
67
-
68
- def create_ui(self, parent_frame):
69
- """
70
- Create the tool settings UI in the provided frame.
71
-
72
- Args:
73
- parent_frame: The tool settings frame to add UI elements to
74
- """
75
- # Main container with scrollbar
76
- canvas = tk.Canvas(parent_frame)
77
- scrollbar = ttk.Scrollbar(parent_frame, orient="vertical", command=canvas.yview)
78
- scrollable_frame = ttk.Frame(canvas)
79
-
80
- scrollable_frame.bind(
81
- "<Configure>",
82
- lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
83
- )
84
-
85
- canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
86
- canvas.configure(yscrollcommand=scrollbar.set)
87
-
88
- # Pack canvas and scrollbar
89
- canvas.pack(side="left", fill="both", expand=True)
90
- scrollbar.pack(side="right", fill="y")
91
-
92
- # Info message (replacing title)
93
- info_text = ("Select folders and configure options, then click 'Generate Reports'.\n"
94
- "Reports will appear in the currently active Input and Output tabs.")
95
- info_label = ttk.Label(scrollable_frame, text=info_text, font=('Arial', 9),
96
- foreground='gray', justify='center')
97
- info_label.pack(pady=(10, 10))
98
-
99
- # Three-column layout
100
- columns_frame = ttk.Frame(scrollable_frame)
101
- columns_frame.pack(fill='both', expand=True, padx=10)
102
-
103
- left_column = ttk.Frame(columns_frame)
104
- left_column.grid(row=0, column=0, sticky='nsew', padx=(0, 10))
105
-
106
- middle_column = ttk.Frame(columns_frame)
107
- middle_column.grid(row=0, column=1, sticky='nsew', padx=(5, 5))
108
-
109
- right_column = ttk.Frame(columns_frame)
110
- right_column.grid(row=0, column=2, sticky='nsew', padx=(10, 0))
111
-
112
- columns_frame.grid_columnconfigure(0, weight=1)
113
- columns_frame.grid_columnconfigure(1, weight=1)
114
- columns_frame.grid_columnconfigure(2, weight=1)
115
-
116
- # LEFT COLUMN
117
- # Input folder selection - label and field on same line
118
- input_frame = ttk.Frame(left_column)
119
- input_frame.grid(row=0, column=0, sticky='ew', pady=(0, 5))
120
-
121
- ttk.Label(input_frame, text="Input Folder:", font=('TkDefaultFont', 9, 'bold')).pack(side='left', padx=(0, 5))
122
- ttk.Entry(input_frame, textvariable=self.input_folder_var, width=30).pack(side='left', fill='x', expand=True, padx=(0, 5))
123
- ttk.Button(input_frame, text="Browse...", command=self._browse_input_folder).pack(side='left')
124
-
125
- left_column.grid_columnconfigure(0, weight=1)
126
-
127
- # Folders Only checkbox - above Information Fields
128
- ttk.Checkbutton(left_column, text="Folders Only", variable=self.folders_only).grid(row=1, column=0, sticky='w', pady=(15, 10))
129
-
130
- # Information Fields
131
- fields_label = ttk.Label(left_column, text="Information Fields:", font=('TkDefaultFont', 9, 'bold'))
132
- fields_label.grid(row=2, column=0, sticky='w', pady=(5, 5))
133
-
134
- fields_frame = ttk.Frame(left_column)
135
- fields_frame.grid(row=3, column=0, sticky='w', pady=(0, 15))
136
-
137
- ttk.Checkbutton(fields_frame, text="Path", variable=self.field_selections['path']).grid(row=0, column=0, sticky='w', pady=2)
138
- ttk.Checkbutton(fields_frame, text="File Name", variable=self.field_selections['name']).grid(row=1, column=0, sticky='w', pady=2)
139
- ttk.Checkbutton(fields_frame, text="Size", variable=self.field_selections['size']).grid(row=2, column=0, sticky='w', pady=2)
140
- ttk.Checkbutton(fields_frame, text="Date Modified", variable=self.field_selections['date_modified']).grid(row=3, column=0, sticky='w', pady=2)
141
-
142
- # MIDDLE COLUMN
143
- # Output folder selection - label and field on same line
144
- output_frame = ttk.Frame(middle_column)
145
- output_frame.grid(row=0, column=0, sticky='ew', pady=(0, 5))
146
-
147
- ttk.Label(output_frame, text="Output Folder:", font=('TkDefaultFont', 9, 'bold')).pack(side='left', padx=(0, 5))
148
- ttk.Entry(output_frame, textvariable=self.output_folder_var, width=30).pack(side='left', fill='x', expand=True, padx=(0, 5))
149
- ttk.Button(output_frame, text="Browse...", command=self._browse_output_folder).pack(side='left')
150
-
151
- middle_column.grid_columnconfigure(0, weight=1)
152
-
153
- # Recursion
154
- recursion_label = ttk.Label(middle_column, text="Recursion:", font=('TkDefaultFont', 9, 'bold'))
155
- recursion_label.grid(row=1, column=0, sticky='w', pady=(15, 5))
156
-
157
- recursion_frame = ttk.Frame(middle_column)
158
- recursion_frame.grid(row=2, column=0, sticky='w', pady=(0, 15))
159
-
160
- ttk.Radiobutton(recursion_frame, text="None", variable=self.recursion_mode, value="none", command=self._update_depth_visibility).grid(row=0, column=0, sticky='w', pady=2)
161
-
162
- limited_frame = ttk.Frame(recursion_frame)
163
- limited_frame.grid(row=1, column=0, sticky='w', pady=2)
164
- ttk.Radiobutton(limited_frame, text="Limited", variable=self.recursion_mode, value="limited", command=self._update_depth_visibility).pack(side='left')
165
-
166
- self.depth_frame = ttk.Frame(limited_frame)
167
- self.depth_frame.pack(side='left', padx=(5, 0))
168
- ttk.Label(self.depth_frame, text="Depth:").pack(side='left', padx=(0, 5))
169
- ttk.Spinbox(self.depth_frame, from_=1, to=20, width=5, textvariable=self.recursion_depth).pack(side='left')
170
-
171
- ttk.Radiobutton(recursion_frame, text="Full", variable=self.recursion_mode, value="full", command=self._update_depth_visibility).grid(row=2, column=0, sticky='w', pady=2)
172
-
173
- # Generate Reports button under Recursion section
174
- process_btn = ttk.Button(middle_column, text="Generate Reports", command=self._generate_reports)
175
- process_btn.grid(row=3, column=0, sticky='w', pady=(10, 0))
176
-
177
- # RIGHT COLUMN
178
- # Separator - label and field on same line
179
- separator_frame = ttk.Frame(right_column)
180
- separator_frame.grid(row=0, column=0, sticky='ew', pady=(0, 5))
181
-
182
- ttk.Label(separator_frame, text="Separator:", font=('TkDefaultFont', 9, 'bold')).pack(side='left', padx=(0, 5))
183
- ttk.Entry(separator_frame, textvariable=self.separator, width=15).pack(side='left')
184
-
185
- # Separator tip below
186
- ttk.Label(right_column, text="(Use \\t for tab, \\n for newline)", font=('TkDefaultFont', 8), foreground='gray').grid(row=1, column=0, sticky='w', pady=(0, 10))
187
-
188
- right_column.grid_columnconfigure(0, weight=1)
189
-
190
- # Date Format - label and field on same line
191
- date_frame = ttk.Frame(right_column)
192
- date_frame.grid(row=2, column=0, sticky='ew', pady=(5, 5))
193
-
194
- ttk.Label(date_frame, text="Date Format:", font=('TkDefaultFont', 9, 'bold')).pack(side='left', padx=(0, 5))
195
- ttk.Entry(date_frame, textvariable=self.date_format, width=20).pack(side='left')
196
-
197
- # Date format tip below
198
- ttk.Label(right_column, text="(e.g., %Y-%m-%d %H:%M:%S)", font=('TkDefaultFont', 8), foreground='gray').grid(row=3, column=0, sticky='w', pady=(0, 10))
199
-
200
- # Size Format
201
- size_label = ttk.Label(right_column, text="Size Format:", font=('TkDefaultFont', 9, 'bold'))
202
- size_label.grid(row=4, column=0, sticky='w', pady=(5, 5))
203
-
204
- size_frame = ttk.Frame(right_column)
205
- size_frame.grid(row=5, column=0, sticky='w', pady=(0, 5))
206
-
207
- ttk.Radiobutton(size_frame, text="Bytes", variable=self.size_format, value="bytes").grid(row=0, column=0, sticky='w', pady=2)
208
- ttk.Radiobutton(size_frame, text="Human Readable (KB, MB, GB)", variable=self.size_format, value="human").grid(row=1, column=0, sticky='w', pady=2)
209
-
210
- # Update depth visibility based on initial mode
211
- self._update_depth_visibility()
212
-
213
- return parent_frame
214
-
215
- def _update_depth_visibility(self):
216
- """Show/hide depth spinbox based on recursion mode."""
217
- if hasattr(self, 'depth_frame'):
218
- if self.recursion_mode.get() == "limited":
219
- self.depth_frame.pack(side='left', padx=(5, 0))
220
- else:
221
- self.depth_frame.pack_forget()
222
-
223
- def _browse_input_folder(self):
224
- """Open folder browser for Input folder selection."""
225
- folder = filedialog.askdirectory(title="Select Input Folder", parent=self.app)
226
- if folder:
227
- self.input_folder_var.set(folder)
228
- self._save_all_settings()
229
-
230
- def _browse_output_folder(self):
231
- """Open folder browser for Output folder selection."""
232
- folder = filedialog.askdirectory(title="Select Output Folder", parent=self.app)
233
- if folder:
234
- self.output_folder_var.set(folder)
235
- self._save_all_settings()
236
-
237
- def _on_folder_changed(self):
238
- """Callback when folder paths change (typed or browsed)."""
239
- # Debounce the save to avoid excessive writes when typing
240
- if hasattr(self, '_save_timer'):
241
- self.app.after_cancel(self._save_timer)
242
- self._save_timer = self.app.after(1000, self._save_all_settings) # Save after 1 second of no changes
243
-
244
- def _on_setting_changed(self):
245
- """Callback when any setting changes (checkboxes, radio buttons, etc)."""
246
- # Debounce the save to avoid excessive writes
247
- if hasattr(self, '_setting_save_timer'):
248
- self.app.after_cancel(self._setting_save_timer)
249
- self._setting_save_timer = self.app.after(500, self._save_all_settings) # Save after 0.5 seconds
250
-
251
- def _save_all_settings(self):
252
- """Save all settings (folders, fields, options) when they change."""
253
- if hasattr(self.app, 'settings') and hasattr(self.app, 'save_settings'):
254
- # Update the settings
255
- if "tool_settings" in self.app.settings:
256
- if "Folder File Reporter" not in self.app.settings["tool_settings"]:
257
- self.app.settings["tool_settings"]["Folder File Reporter"] = {}
258
-
259
- # Save folder paths
260
- self.app.settings["tool_settings"]["Folder File Reporter"]["last_input_folder"] = self.input_folder_var.get()
261
- self.app.settings["tool_settings"]["Folder File Reporter"]["last_output_folder"] = self.output_folder_var.get()
262
-
263
- # Save field selections
264
- self.app.settings["tool_settings"]["Folder File Reporter"]["field_selections"] = {
265
- field: var.get() for field, var in self.field_selections.items()
266
- }
267
-
268
- # Save other options
269
- self.app.settings["tool_settings"]["Folder File Reporter"]["separator"] = self.separator.get()
270
- self.app.settings["tool_settings"]["Folder File Reporter"]["folders_only"] = self.folders_only.get()
271
- self.app.settings["tool_settings"]["Folder File Reporter"]["recursion_mode"] = self.recursion_mode.get()
272
- self.app.settings["tool_settings"]["Folder File Reporter"]["recursion_depth"] = self.recursion_depth.get()
273
- self.app.settings["tool_settings"]["Folder File Reporter"]["size_format"] = self.size_format.get()
274
- self.app.settings["tool_settings"]["Folder File Reporter"]["date_format"] = self.date_format.get()
275
-
276
- # Save to file
277
- self.app.save_settings()
278
-
279
- def _generate_reports(self):
280
- """
281
- Generate reports and write them to the active Input/Output tabs.
282
- """
283
- input_folder = self.input_folder_var.get().strip()
284
- output_folder = self.output_folder_var.get().strip()
285
-
286
- if not input_folder and not output_folder:
287
- if self.app.dialog_manager:
288
- self.app.dialog_manager.show_warning(
289
- "No Folders Selected",
290
- "Please select at least one folder to generate a report."
291
- )
292
- else:
293
- tk.messagebox.showwarning(
294
- "No Folders Selected",
295
- "Please select at least one folder to generate a report.",
296
- parent=self.app
297
- )
298
- return
299
-
300
- # Get active Input and Output text widgets
301
- input_tab_index = self.app.input_notebook.index(self.app.input_notebook.select())
302
- output_tab_index = self.app.output_notebook.index(self.app.output_notebook.select())
303
-
304
- active_input_tab = self.app.input_tabs[input_tab_index]
305
- active_output_tab = self.app.output_tabs[output_tab_index]
306
-
307
- # Debug logging
308
- if hasattr(self.app, 'logger') and self.app.logger:
309
- self.app.logger.info(f"Active Input tab index: {input_tab_index}")
310
- self.app.logger.info(f"Active Output tab index: {output_tab_index}")
311
- self.app.logger.info(f"Input text widget: {active_input_tab.text}")
312
- self.app.logger.info(f"Output text widget: {active_output_tab.text}")
313
-
314
- try:
315
- # Create reporter WITHOUT calling _create_ui (which would create its own text widgets)
316
- # We'll manually set up the reporter to use the main app's text widgets
317
- reporter = FolderFileReporter.__new__(FolderFileReporter)
318
-
319
- # Manually initialize the reporter without calling __init__ (which calls _create_ui)
320
- reporter.parent = None # We don't need a parent since we're not creating UI
321
- reporter.dialog_manager = self.app.dialog_manager
322
- reporter.input_text_widget = active_input_tab.text
323
- reporter.output_text_widget = active_output_tab.text
324
- reporter.settings_file = "settings.json"
325
- reporter.tool_key = "Folder File Reporter"
326
-
327
- # Initialize UI variables
328
- reporter.input_folder_path = tk.StringVar()
329
- reporter.output_folder_path = tk.StringVar()
330
-
331
- # Field selection variables
332
- reporter.field_selections = {
333
- 'path': tk.BooleanVar(value=True),
334
- 'name': tk.BooleanVar(value=True),
335
- 'size': tk.BooleanVar(value=True),
336
- 'date_modified': tk.BooleanVar(value=True)
337
- }
338
-
339
- # Configuration variables
340
- reporter.separator = tk.StringVar(value=" | ")
341
- reporter.folders_only = tk.BooleanVar(value=False)
342
- reporter.recursion_mode = tk.StringVar(value="full")
343
- reporter.recursion_depth = tk.IntVar(value=2)
344
- reporter.size_format = tk.StringVar(value="human")
345
- reporter.date_format = tk.StringVar(value="%Y-%m-%d %H:%M:%S")
346
-
347
- # Use the main app's text widgets directly
348
- reporter.input_text = active_input_tab.text
349
- reporter.output_text = active_output_tab.text
350
-
351
- # Debug: Verify text widgets are set correctly
352
- if hasattr(self.app, 'logger') and self.app.logger:
353
- self.app.logger.info(f"Reporter input_text set to: {reporter.input_text}")
354
- self.app.logger.info(f"Reporter output_text set to: {reporter.output_text}")
355
-
356
- # CRITICAL: Enable output tab for writing (Pomera keeps output tabs disabled/read-only)
357
- active_output_tab.text.config(state="normal")
358
-
359
- # Set the folder paths
360
- reporter.input_folder_path.set(input_folder)
361
- reporter.output_folder_path.set(output_folder)
362
-
363
- # Debug: Log folder paths
364
- if hasattr(self.app, 'logger') and self.app.logger:
365
- self.app.logger.info(f"Input folder path: {input_folder}")
366
- self.app.logger.info(f"Output folder path: {output_folder}")
367
-
368
- # Transfer all settings from adapter to reporter
369
- for field, var in self.field_selections.items():
370
- reporter.field_selections[field].set(var.get())
371
-
372
- reporter.separator.set(self.separator.get())
373
- reporter.folders_only.set(self.folders_only.get())
374
- reporter.recursion_mode.set(self.recursion_mode.get())
375
- reporter.recursion_depth.set(self.recursion_depth.get())
376
- reporter.size_format.set(self.size_format.get())
377
- reporter.date_format.set(self.date_format.get())
378
-
379
- # Generate the reports
380
- reporter.generate_report()
381
-
382
- # Re-disable output tab to maintain Pomera's read-only convention
383
- active_output_tab.text.config(state="disabled")
384
-
385
- # Verify content was written
386
- if hasattr(self.app, 'logger') and self.app.logger:
387
- input_content = active_input_tab.text.get("1.0", "end-1c")
388
- output_content = active_output_tab.text.get("1.0", "end-1c")
389
- self.app.logger.info(f"Generated folder reports - Input: {input_folder}, Output: {output_folder}")
390
- self.app.logger.info(f"Input tab content length: {len(input_content)} chars")
391
- self.app.logger.info(f"Output tab content length: {len(output_content)} chars")
392
- if input_content:
393
- self.app.logger.info(f"Input tab first 100 chars: {input_content[:100]}")
394
- if output_content:
395
- self.app.logger.info(f"Output tab first 100 chars: {output_content[:100]}")
396
-
397
- except Exception as e:
398
- error_msg = f"Error generating reports: {e}"
399
- self.app.logger.error(error_msg, exc_info=True)
400
- if self.app.dialog_manager:
401
- self.app.dialog_manager.show_error("Report Generation Error", error_msg)
402
- else:
403
- tk.messagebox.showerror("Report Generation Error", error_msg, parent=self.app)
404
-
405
- def get_default_settings(self):
406
- """
407
- Get default settings for the tool.
408
-
409
- Returns:
410
- dict: Default settings
411
- """
412
- return {
413
- 'last_input_folder': '',
414
- 'last_output_folder': '',
415
- 'field_selections': {
416
- 'path': True,
417
- 'name': True,
418
- 'size': True,
419
- 'date_modified': True
420
- },
421
- 'separator': ' | ',
422
- 'folders_only': False,
423
- 'recursion_mode': 'full',
424
- 'recursion_depth': 2,
425
- 'size_format': 'human',
426
- 'date_format': '%Y-%m-%d %H:%M:%S'
427
- }
428
-
429
- def load_settings(self, settings):
430
- """
431
- Load settings from the application settings.
432
-
433
- Args:
434
- settings: Dictionary of settings to load
435
- """
436
- if 'last_input_folder' in settings:
437
- self.input_folder_var.set(settings['last_input_folder'])
438
- if 'last_output_folder' in settings:
439
- self.output_folder_var.set(settings['last_output_folder'])
440
-
441
- # Load field selections
442
- if 'field_selections' in settings:
443
- for field, value in settings['field_selections'].items():
444
- if field in self.field_selections:
445
- self.field_selections[field].set(value)
446
-
447
- # Load other settings
448
- if 'separator' in settings:
449
- self.separator.set(settings['separator'])
450
- if 'folders_only' in settings:
451
- self.folders_only.set(settings['folders_only'])
452
- if 'recursion_mode' in settings:
453
- self.recursion_mode.set(settings['recursion_mode'])
454
- if 'recursion_depth' in settings:
455
- self.recursion_depth.set(settings['recursion_depth'])
456
- if 'size_format' in settings:
457
- self.size_format.set(settings['size_format'])
458
- if 'date_format' in settings:
459
- self.date_format.set(settings['date_format'])
460
-
461
- def save_settings(self):
462
- """
463
- Save current settings.
464
-
465
- Returns:
466
- dict: Current settings to save
467
- """
468
- return {
469
- 'last_input_folder': self.input_folder_var.get(),
470
- 'last_output_folder': self.output_folder_var.get(),
471
- 'field_selections': {
472
- field: var.get() for field, var in self.field_selections.items()
473
- },
474
- 'separator': self.separator.get(),
475
- 'folders_only': self.folders_only.get(),
476
- 'recursion_mode': self.recursion_mode.get(),
477
- 'recursion_depth': self.recursion_depth.get(),
478
- 'size_format': self.size_format.get(),
479
- 'date_format': self.date_format.get()
480
- }
1
+ """
2
+ Folder File Reporter Adapter for Pomera Integration
3
+
4
+ This adapter integrates the Folder File Reporter into Pomera's dropdown tool system,
5
+ allowing it to work with the main application's 7-tab Input/Output system instead of
6
+ having its own separate tabs.
7
+ """
8
+
9
+ import tkinter as tk
10
+ from tkinter import ttk, filedialog
11
+ import os
12
+ from tools.folder_file_reporter import FolderFileReporter
13
+
14
+
15
+ class FolderFileReporterAdapter:
16
+ """
17
+ Adapter that integrates FolderFileReporter into Pomera's tool system.
18
+
19
+ This adapter creates a simplified UI in the tool settings panel and generates
20
+ reports into the main application's active Input/Output tabs.
21
+ """
22
+
23
+ def __init__(self, parent_app):
24
+ """
25
+ Initialize the adapter.
26
+
27
+ Args:
28
+ parent_app: Reference to the main Pomera application
29
+ """
30
+ self.app = parent_app
31
+ self.reporter = None
32
+
33
+ # UI variables
34
+ self.input_folder_var = tk.StringVar()
35
+ self.output_folder_var = tk.StringVar()
36
+
37
+ # Add trace callbacks to save settings when folders change
38
+ self.input_folder_var.trace_add('write', lambda *args: self._on_folder_changed())
39
+ self.output_folder_var.trace_add('write', lambda *args: self._on_folder_changed())
40
+
41
+ # Field selection variables
42
+ self.field_selections = {
43
+ 'path': tk.BooleanVar(value=True),
44
+ 'name': tk.BooleanVar(value=True),
45
+ 'size': tk.BooleanVar(value=True),
46
+ 'date_modified': tk.BooleanVar(value=True)
47
+ }
48
+
49
+ # Configuration variables
50
+ self.separator = tk.StringVar(value=" | ")
51
+ self.folders_only = tk.BooleanVar(value=False)
52
+ self.recursion_mode = tk.StringVar(value="full")
53
+ self.recursion_depth = tk.IntVar(value=2)
54
+ self.size_format = tk.StringVar(value="human")
55
+ self.date_format = tk.StringVar(value="%Y-%m-%d %H:%M:%S")
56
+
57
+ # Add trace callbacks to save settings when any option changes
58
+ for field_var in self.field_selections.values():
59
+ field_var.trace_add('write', lambda *args: self._on_setting_changed())
60
+
61
+ self.separator.trace_add('write', lambda *args: self._on_setting_changed())
62
+ self.folders_only.trace_add('write', lambda *args: self._on_setting_changed())
63
+ self.recursion_mode.trace_add('write', lambda *args: self._on_setting_changed())
64
+ self.recursion_depth.trace_add('write', lambda *args: self._on_setting_changed())
65
+ self.size_format.trace_add('write', lambda *args: self._on_setting_changed())
66
+ self.date_format.trace_add('write', lambda *args: self._on_setting_changed())
67
+
68
+ def create_ui(self, parent_frame):
69
+ """
70
+ Create the tool settings UI in the provided frame.
71
+
72
+ Args:
73
+ parent_frame: The tool settings frame to add UI elements to
74
+ """
75
+ # Main container with scrollbar
76
+ canvas = tk.Canvas(parent_frame)
77
+ scrollbar = ttk.Scrollbar(parent_frame, orient="vertical", command=canvas.yview)
78
+ scrollable_frame = ttk.Frame(canvas)
79
+
80
+ scrollable_frame.bind(
81
+ "<Configure>",
82
+ lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
83
+ )
84
+
85
+ canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
86
+ canvas.configure(yscrollcommand=scrollbar.set)
87
+
88
+ # Pack canvas and scrollbar
89
+ canvas.pack(side="left", fill="both", expand=True)
90
+ scrollbar.pack(side="right", fill="y")
91
+
92
+ # Info message (replacing title)
93
+ info_text = ("Select folders and configure options, then click 'Generate Reports'.\n"
94
+ "Reports will appear in the currently active Input and Output tabs.")
95
+ info_label = ttk.Label(scrollable_frame, text=info_text, font=('Arial', 9),
96
+ foreground='gray', justify='center')
97
+ info_label.pack(pady=(10, 10))
98
+
99
+ # Three-column layout
100
+ columns_frame = ttk.Frame(scrollable_frame)
101
+ columns_frame.pack(fill='both', expand=True, padx=10)
102
+
103
+ left_column = ttk.Frame(columns_frame)
104
+ left_column.grid(row=0, column=0, sticky='nsew', padx=(0, 10))
105
+
106
+ middle_column = ttk.Frame(columns_frame)
107
+ middle_column.grid(row=0, column=1, sticky='nsew', padx=(5, 5))
108
+
109
+ right_column = ttk.Frame(columns_frame)
110
+ right_column.grid(row=0, column=2, sticky='nsew', padx=(10, 0))
111
+
112
+ columns_frame.grid_columnconfigure(0, weight=1)
113
+ columns_frame.grid_columnconfigure(1, weight=1)
114
+ columns_frame.grid_columnconfigure(2, weight=1)
115
+
116
+ # LEFT COLUMN
117
+ # Input folder selection - label and field on same line
118
+ input_frame = ttk.Frame(left_column)
119
+ input_frame.grid(row=0, column=0, sticky='ew', pady=(0, 5))
120
+
121
+ ttk.Label(input_frame, text="Input Folder:", font=('TkDefaultFont', 9, 'bold')).pack(side='left', padx=(0, 5))
122
+ ttk.Entry(input_frame, textvariable=self.input_folder_var, width=30).pack(side='left', fill='x', expand=True, padx=(0, 5))
123
+ ttk.Button(input_frame, text="Browse...", command=self._browse_input_folder).pack(side='left')
124
+
125
+ left_column.grid_columnconfigure(0, weight=1)
126
+
127
+ # Folders Only checkbox - above Information Fields
128
+ ttk.Checkbutton(left_column, text="Folders Only", variable=self.folders_only).grid(row=1, column=0, sticky='w', pady=(15, 10))
129
+
130
+ # Information Fields
131
+ fields_label = ttk.Label(left_column, text="Information Fields:", font=('TkDefaultFont', 9, 'bold'))
132
+ fields_label.grid(row=2, column=0, sticky='w', pady=(5, 5))
133
+
134
+ fields_frame = ttk.Frame(left_column)
135
+ fields_frame.grid(row=3, column=0, sticky='w', pady=(0, 15))
136
+
137
+ ttk.Checkbutton(fields_frame, text="Path", variable=self.field_selections['path']).grid(row=0, column=0, sticky='w', pady=2)
138
+ ttk.Checkbutton(fields_frame, text="File Name", variable=self.field_selections['name']).grid(row=1, column=0, sticky='w', pady=2)
139
+ ttk.Checkbutton(fields_frame, text="Size", variable=self.field_selections['size']).grid(row=2, column=0, sticky='w', pady=2)
140
+ ttk.Checkbutton(fields_frame, text="Date Modified", variable=self.field_selections['date_modified']).grid(row=3, column=0, sticky='w', pady=2)
141
+
142
+ # MIDDLE COLUMN
143
+ # Output folder selection - label and field on same line
144
+ output_frame = ttk.Frame(middle_column)
145
+ output_frame.grid(row=0, column=0, sticky='ew', pady=(0, 5))
146
+
147
+ ttk.Label(output_frame, text="Output Folder:", font=('TkDefaultFont', 9, 'bold')).pack(side='left', padx=(0, 5))
148
+ ttk.Entry(output_frame, textvariable=self.output_folder_var, width=30).pack(side='left', fill='x', expand=True, padx=(0, 5))
149
+ ttk.Button(output_frame, text="Browse...", command=self._browse_output_folder).pack(side='left')
150
+
151
+ middle_column.grid_columnconfigure(0, weight=1)
152
+
153
+ # Recursion
154
+ recursion_label = ttk.Label(middle_column, text="Recursion:", font=('TkDefaultFont', 9, 'bold'))
155
+ recursion_label.grid(row=1, column=0, sticky='w', pady=(15, 5))
156
+
157
+ recursion_frame = ttk.Frame(middle_column)
158
+ recursion_frame.grid(row=2, column=0, sticky='w', pady=(0, 15))
159
+
160
+ ttk.Radiobutton(recursion_frame, text="None", variable=self.recursion_mode, value="none", command=self._update_depth_visibility).grid(row=0, column=0, sticky='w', pady=2)
161
+
162
+ limited_frame = ttk.Frame(recursion_frame)
163
+ limited_frame.grid(row=1, column=0, sticky='w', pady=2)
164
+ ttk.Radiobutton(limited_frame, text="Limited", variable=self.recursion_mode, value="limited", command=self._update_depth_visibility).pack(side='left')
165
+
166
+ self.depth_frame = ttk.Frame(limited_frame)
167
+ self.depth_frame.pack(side='left', padx=(5, 0))
168
+ ttk.Label(self.depth_frame, text="Depth:").pack(side='left', padx=(0, 5))
169
+ ttk.Spinbox(self.depth_frame, from_=1, to=20, width=5, textvariable=self.recursion_depth).pack(side='left')
170
+
171
+ ttk.Radiobutton(recursion_frame, text="Full", variable=self.recursion_mode, value="full", command=self._update_depth_visibility).grid(row=2, column=0, sticky='w', pady=2)
172
+
173
+ # Generate Reports button under Recursion section
174
+ process_btn = ttk.Button(middle_column, text="Generate Reports", command=self._generate_reports)
175
+ process_btn.grid(row=3, column=0, sticky='w', pady=(10, 0))
176
+
177
+ # RIGHT COLUMN
178
+ # Separator - label and field on same line
179
+ separator_frame = ttk.Frame(right_column)
180
+ separator_frame.grid(row=0, column=0, sticky='ew', pady=(0, 5))
181
+
182
+ ttk.Label(separator_frame, text="Separator:", font=('TkDefaultFont', 9, 'bold')).pack(side='left', padx=(0, 5))
183
+ ttk.Entry(separator_frame, textvariable=self.separator, width=15).pack(side='left')
184
+
185
+ # Separator tip below
186
+ ttk.Label(right_column, text="(Use \\t for tab, \\n for newline)", font=('TkDefaultFont', 8), foreground='gray').grid(row=1, column=0, sticky='w', pady=(0, 10))
187
+
188
+ right_column.grid_columnconfigure(0, weight=1)
189
+
190
+ # Date Format - label and field on same line
191
+ date_frame = ttk.Frame(right_column)
192
+ date_frame.grid(row=2, column=0, sticky='ew', pady=(5, 5))
193
+
194
+ ttk.Label(date_frame, text="Date Format:", font=('TkDefaultFont', 9, 'bold')).pack(side='left', padx=(0, 5))
195
+ ttk.Entry(date_frame, textvariable=self.date_format, width=20).pack(side='left')
196
+
197
+ # Date format tip below
198
+ ttk.Label(right_column, text="(e.g., %Y-%m-%d %H:%M:%S)", font=('TkDefaultFont', 8), foreground='gray').grid(row=3, column=0, sticky='w', pady=(0, 10))
199
+
200
+ # Size Format
201
+ size_label = ttk.Label(right_column, text="Size Format:", font=('TkDefaultFont', 9, 'bold'))
202
+ size_label.grid(row=4, column=0, sticky='w', pady=(5, 5))
203
+
204
+ size_frame = ttk.Frame(right_column)
205
+ size_frame.grid(row=5, column=0, sticky='w', pady=(0, 5))
206
+
207
+ ttk.Radiobutton(size_frame, text="Bytes", variable=self.size_format, value="bytes").grid(row=0, column=0, sticky='w', pady=2)
208
+ ttk.Radiobutton(size_frame, text="Human Readable (KB, MB, GB)", variable=self.size_format, value="human").grid(row=1, column=0, sticky='w', pady=2)
209
+
210
+ # Update depth visibility based on initial mode
211
+ self._update_depth_visibility()
212
+
213
+ return parent_frame
214
+
215
+ def _update_depth_visibility(self):
216
+ """Show/hide depth spinbox based on recursion mode."""
217
+ if hasattr(self, 'depth_frame'):
218
+ if self.recursion_mode.get() == "limited":
219
+ self.depth_frame.pack(side='left', padx=(5, 0))
220
+ else:
221
+ self.depth_frame.pack_forget()
222
+
223
+ def _browse_input_folder(self):
224
+ """Open folder browser for Input folder selection."""
225
+ folder = filedialog.askdirectory(title="Select Input Folder", parent=self.app)
226
+ if folder:
227
+ self.input_folder_var.set(folder)
228
+ self._save_all_settings()
229
+
230
+ def _browse_output_folder(self):
231
+ """Open folder browser for Output folder selection."""
232
+ folder = filedialog.askdirectory(title="Select Output Folder", parent=self.app)
233
+ if folder:
234
+ self.output_folder_var.set(folder)
235
+ self._save_all_settings()
236
+
237
+ def _on_folder_changed(self):
238
+ """Callback when folder paths change (typed or browsed)."""
239
+ # Debounce the save to avoid excessive writes when typing
240
+ if hasattr(self, '_save_timer'):
241
+ self.app.after_cancel(self._save_timer)
242
+ self._save_timer = self.app.after(1000, self._save_all_settings) # Save after 1 second of no changes
243
+
244
+ def _on_setting_changed(self):
245
+ """Callback when any setting changes (checkboxes, radio buttons, etc)."""
246
+ # Debounce the save to avoid excessive writes
247
+ if hasattr(self, '_setting_save_timer'):
248
+ self.app.after_cancel(self._setting_save_timer)
249
+ self._setting_save_timer = self.app.after(500, self._save_all_settings) # Save after 0.5 seconds
250
+
251
+ def _save_all_settings(self):
252
+ """Save all settings (folders, fields, options) when they change."""
253
+ if hasattr(self.app, 'settings') and hasattr(self.app, 'save_settings'):
254
+ # Update the settings
255
+ if "tool_settings" in self.app.settings:
256
+ if "Folder File Reporter" not in self.app.settings["tool_settings"]:
257
+ self.app.settings["tool_settings"]["Folder File Reporter"] = {}
258
+
259
+ # Save folder paths
260
+ self.app.settings["tool_settings"]["Folder File Reporter"]["last_input_folder"] = self.input_folder_var.get()
261
+ self.app.settings["tool_settings"]["Folder File Reporter"]["last_output_folder"] = self.output_folder_var.get()
262
+
263
+ # Save field selections
264
+ self.app.settings["tool_settings"]["Folder File Reporter"]["field_selections"] = {
265
+ field: var.get() for field, var in self.field_selections.items()
266
+ }
267
+
268
+ # Save other options
269
+ self.app.settings["tool_settings"]["Folder File Reporter"]["separator"] = self.separator.get()
270
+ self.app.settings["tool_settings"]["Folder File Reporter"]["folders_only"] = self.folders_only.get()
271
+ self.app.settings["tool_settings"]["Folder File Reporter"]["recursion_mode"] = self.recursion_mode.get()
272
+ self.app.settings["tool_settings"]["Folder File Reporter"]["recursion_depth"] = self.recursion_depth.get()
273
+ self.app.settings["tool_settings"]["Folder File Reporter"]["size_format"] = self.size_format.get()
274
+ self.app.settings["tool_settings"]["Folder File Reporter"]["date_format"] = self.date_format.get()
275
+
276
+ # Save to file
277
+ self.app.save_settings()
278
+
279
+ def _generate_reports(self):
280
+ """
281
+ Generate reports and write them to the active Input/Output tabs.
282
+ """
283
+ input_folder = self.input_folder_var.get().strip()
284
+ output_folder = self.output_folder_var.get().strip()
285
+
286
+ if not input_folder and not output_folder:
287
+ if self.app.dialog_manager:
288
+ self.app.dialog_manager.show_warning(
289
+ "No Folders Selected",
290
+ "Please select at least one folder to generate a report."
291
+ )
292
+ else:
293
+ tk.messagebox.showwarning(
294
+ "No Folders Selected",
295
+ "Please select at least one folder to generate a report.",
296
+ parent=self.app
297
+ )
298
+ return
299
+
300
+ # Get active Input and Output text widgets
301
+ input_tab_index = self.app.input_notebook.index(self.app.input_notebook.select())
302
+ output_tab_index = self.app.output_notebook.index(self.app.output_notebook.select())
303
+
304
+ active_input_tab = self.app.input_tabs[input_tab_index]
305
+ active_output_tab = self.app.output_tabs[output_tab_index]
306
+
307
+ # Debug logging
308
+ if hasattr(self.app, 'logger') and self.app.logger:
309
+ self.app.logger.info(f"Active Input tab index: {input_tab_index}")
310
+ self.app.logger.info(f"Active Output tab index: {output_tab_index}")
311
+ self.app.logger.info(f"Input text widget: {active_input_tab.text}")
312
+ self.app.logger.info(f"Output text widget: {active_output_tab.text}")
313
+
314
+ try:
315
+ # Create reporter WITHOUT calling _create_ui (which would create its own text widgets)
316
+ # We'll manually set up the reporter to use the main app's text widgets
317
+ reporter = FolderFileReporter.__new__(FolderFileReporter)
318
+
319
+ # Manually initialize the reporter without calling __init__ (which calls _create_ui)
320
+ reporter.parent = None # We don't need a parent since we're not creating UI
321
+ reporter.dialog_manager = self.app.dialog_manager
322
+ reporter.input_text_widget = active_input_tab.text
323
+ reporter.output_text_widget = active_output_tab.text
324
+ reporter.settings_file = "settings.json"
325
+ reporter.tool_key = "Folder File Reporter"
326
+
327
+ # Initialize UI variables
328
+ reporter.input_folder_path = tk.StringVar()
329
+ reporter.output_folder_path = tk.StringVar()
330
+
331
+ # Field selection variables
332
+ reporter.field_selections = {
333
+ 'path': tk.BooleanVar(value=True),
334
+ 'name': tk.BooleanVar(value=True),
335
+ 'size': tk.BooleanVar(value=True),
336
+ 'date_modified': tk.BooleanVar(value=True)
337
+ }
338
+
339
+ # Configuration variables
340
+ reporter.separator = tk.StringVar(value=" | ")
341
+ reporter.folders_only = tk.BooleanVar(value=False)
342
+ reporter.recursion_mode = tk.StringVar(value="full")
343
+ reporter.recursion_depth = tk.IntVar(value=2)
344
+ reporter.size_format = tk.StringVar(value="human")
345
+ reporter.date_format = tk.StringVar(value="%Y-%m-%d %H:%M:%S")
346
+
347
+ # Use the main app's text widgets directly
348
+ reporter.input_text = active_input_tab.text
349
+ reporter.output_text = active_output_tab.text
350
+
351
+ # Debug: Verify text widgets are set correctly
352
+ if hasattr(self.app, 'logger') and self.app.logger:
353
+ self.app.logger.info(f"Reporter input_text set to: {reporter.input_text}")
354
+ self.app.logger.info(f"Reporter output_text set to: {reporter.output_text}")
355
+
356
+ # CRITICAL: Enable output tab for writing (Pomera keeps output tabs disabled/read-only)
357
+ active_output_tab.text.config(state="normal")
358
+
359
+ # Set the folder paths
360
+ reporter.input_folder_path.set(input_folder)
361
+ reporter.output_folder_path.set(output_folder)
362
+
363
+ # Debug: Log folder paths
364
+ if hasattr(self.app, 'logger') and self.app.logger:
365
+ self.app.logger.info(f"Input folder path: {input_folder}")
366
+ self.app.logger.info(f"Output folder path: {output_folder}")
367
+
368
+ # Transfer all settings from adapter to reporter
369
+ for field, var in self.field_selections.items():
370
+ reporter.field_selections[field].set(var.get())
371
+
372
+ reporter.separator.set(self.separator.get())
373
+ reporter.folders_only.set(self.folders_only.get())
374
+ reporter.recursion_mode.set(self.recursion_mode.get())
375
+ reporter.recursion_depth.set(self.recursion_depth.get())
376
+ reporter.size_format.set(self.size_format.get())
377
+ reporter.date_format.set(self.date_format.get())
378
+
379
+ # Generate the reports
380
+ reporter.generate_report()
381
+
382
+ # Re-disable output tab to maintain Pomera's read-only convention
383
+ active_output_tab.text.config(state="disabled")
384
+
385
+ # Verify content was written
386
+ if hasattr(self.app, 'logger') and self.app.logger:
387
+ input_content = active_input_tab.text.get("1.0", "end-1c")
388
+ output_content = active_output_tab.text.get("1.0", "end-1c")
389
+ self.app.logger.info(f"Generated folder reports - Input: {input_folder}, Output: {output_folder}")
390
+ self.app.logger.info(f"Input tab content length: {len(input_content)} chars")
391
+ self.app.logger.info(f"Output tab content length: {len(output_content)} chars")
392
+ if input_content:
393
+ self.app.logger.info(f"Input tab first 100 chars: {input_content[:100]}")
394
+ if output_content:
395
+ self.app.logger.info(f"Output tab first 100 chars: {output_content[:100]}")
396
+
397
+ except Exception as e:
398
+ error_msg = f"Error generating reports: {e}"
399
+ self.app.logger.error(error_msg, exc_info=True)
400
+ if self.app.dialog_manager:
401
+ self.app.dialog_manager.show_error("Report Generation Error", error_msg)
402
+ else:
403
+ tk.messagebox.showerror("Report Generation Error", error_msg, parent=self.app)
404
+
405
+ def get_default_settings(self):
406
+ """
407
+ Get default settings for the tool.
408
+
409
+ Returns:
410
+ dict: Default settings
411
+ """
412
+ return {
413
+ 'last_input_folder': '',
414
+ 'last_output_folder': '',
415
+ 'field_selections': {
416
+ 'path': True,
417
+ 'name': True,
418
+ 'size': True,
419
+ 'date_modified': True
420
+ },
421
+ 'separator': ' | ',
422
+ 'folders_only': False,
423
+ 'recursion_mode': 'full',
424
+ 'recursion_depth': 2,
425
+ 'size_format': 'human',
426
+ 'date_format': '%Y-%m-%d %H:%M:%S'
427
+ }
428
+
429
+ def load_settings(self, settings):
430
+ """
431
+ Load settings from the application settings.
432
+
433
+ Args:
434
+ settings: Dictionary of settings to load
435
+ """
436
+ if 'last_input_folder' in settings:
437
+ self.input_folder_var.set(settings['last_input_folder'])
438
+ if 'last_output_folder' in settings:
439
+ self.output_folder_var.set(settings['last_output_folder'])
440
+
441
+ # Load field selections
442
+ if 'field_selections' in settings:
443
+ for field, value in settings['field_selections'].items():
444
+ if field in self.field_selections:
445
+ self.field_selections[field].set(value)
446
+
447
+ # Load other settings
448
+ if 'separator' in settings:
449
+ self.separator.set(settings['separator'])
450
+ if 'folders_only' in settings:
451
+ self.folders_only.set(settings['folders_only'])
452
+ if 'recursion_mode' in settings:
453
+ self.recursion_mode.set(settings['recursion_mode'])
454
+ if 'recursion_depth' in settings:
455
+ self.recursion_depth.set(settings['recursion_depth'])
456
+ if 'size_format' in settings:
457
+ self.size_format.set(settings['size_format'])
458
+ if 'date_format' in settings:
459
+ self.date_format.set(settings['date_format'])
460
+
461
+ def save_settings(self):
462
+ """
463
+ Save current settings.
464
+
465
+ Returns:
466
+ dict: Current settings to save
467
+ """
468
+ return {
469
+ 'last_input_folder': self.input_folder_var.get(),
470
+ 'last_output_folder': self.output_folder_var.get(),
471
+ 'field_selections': {
472
+ field: var.get() for field, var in self.field_selections.items()
473
+ },
474
+ 'separator': self.separator.get(),
475
+ 'folders_only': self.folders_only.get(),
476
+ 'recursion_mode': self.recursion_mode.get(),
477
+ 'recursion_depth': self.recursion_depth.get(),
478
+ 'size_format': self.size_format.get(),
479
+ 'date_format': self.date_format.get()
480
+ }