pomera-ai-commander 0.1.0

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 (192) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +680 -0
  3. package/bin/pomera-ai-commander.js +62 -0
  4. package/core/__init__.py +66 -0
  5. package/core/__pycache__/__init__.cpython-313.pyc +0 -0
  6. package/core/__pycache__/app_context.cpython-313.pyc +0 -0
  7. package/core/__pycache__/async_text_processor.cpython-313.pyc +0 -0
  8. package/core/__pycache__/backup_manager.cpython-313.pyc +0 -0
  9. package/core/__pycache__/backup_recovery_manager.cpython-313.pyc +0 -0
  10. package/core/__pycache__/content_hash_cache.cpython-313.pyc +0 -0
  11. package/core/__pycache__/context_menu.cpython-313.pyc +0 -0
  12. package/core/__pycache__/data_validator.cpython-313.pyc +0 -0
  13. package/core/__pycache__/database_connection_manager.cpython-313.pyc +0 -0
  14. package/core/__pycache__/database_curl_settings_manager.cpython-313.pyc +0 -0
  15. package/core/__pycache__/database_promera_ai_settings_manager.cpython-313.pyc +0 -0
  16. package/core/__pycache__/database_schema.cpython-313.pyc +0 -0
  17. package/core/__pycache__/database_schema_manager.cpython-313.pyc +0 -0
  18. package/core/__pycache__/database_settings_manager.cpython-313.pyc +0 -0
  19. package/core/__pycache__/database_settings_manager_interface.cpython-313.pyc +0 -0
  20. package/core/__pycache__/dialog_manager.cpython-313.pyc +0 -0
  21. package/core/__pycache__/efficient_line_numbers.cpython-313.pyc +0 -0
  22. package/core/__pycache__/error_handler.cpython-313.pyc +0 -0
  23. package/core/__pycache__/error_service.cpython-313.pyc +0 -0
  24. package/core/__pycache__/event_consolidator.cpython-313.pyc +0 -0
  25. package/core/__pycache__/memory_efficient_text_widget.cpython-313.pyc +0 -0
  26. package/core/__pycache__/migration_manager.cpython-313.pyc +0 -0
  27. package/core/__pycache__/migration_test_suite.cpython-313.pyc +0 -0
  28. package/core/__pycache__/migration_validator.cpython-313.pyc +0 -0
  29. package/core/__pycache__/optimized_find_replace.cpython-313.pyc +0 -0
  30. package/core/__pycache__/optimized_pattern_engine.cpython-313.pyc +0 -0
  31. package/core/__pycache__/optimized_search_highlighter.cpython-313.pyc +0 -0
  32. package/core/__pycache__/performance_monitor.cpython-313.pyc +0 -0
  33. package/core/__pycache__/persistence_manager.cpython-313.pyc +0 -0
  34. package/core/__pycache__/progressive_stats_calculator.cpython-313.pyc +0 -0
  35. package/core/__pycache__/regex_pattern_cache.cpython-313.pyc +0 -0
  36. package/core/__pycache__/regex_pattern_library.cpython-313.pyc +0 -0
  37. package/core/__pycache__/search_operation_manager.cpython-313.pyc +0 -0
  38. package/core/__pycache__/settings_defaults_registry.cpython-313.pyc +0 -0
  39. package/core/__pycache__/settings_integrity_validator.cpython-313.pyc +0 -0
  40. package/core/__pycache__/settings_serializer.cpython-313.pyc +0 -0
  41. package/core/__pycache__/settings_validator.cpython-313.pyc +0 -0
  42. package/core/__pycache__/smart_stats_calculator.cpython-313.pyc +0 -0
  43. package/core/__pycache__/statistics_update_manager.cpython-313.pyc +0 -0
  44. package/core/__pycache__/stats_config_manager.cpython-313.pyc +0 -0
  45. package/core/__pycache__/streaming_text_handler.cpython-313.pyc +0 -0
  46. package/core/__pycache__/task_scheduler.cpython-313.pyc +0 -0
  47. package/core/__pycache__/visibility_monitor.cpython-313.pyc +0 -0
  48. package/core/__pycache__/widget_cache.cpython-313.pyc +0 -0
  49. package/core/app_context.py +482 -0
  50. package/core/async_text_processor.py +422 -0
  51. package/core/backup_manager.py +656 -0
  52. package/core/backup_recovery_manager.py +1034 -0
  53. package/core/content_hash_cache.py +509 -0
  54. package/core/context_menu.py +313 -0
  55. package/core/data_validator.py +1067 -0
  56. package/core/database_connection_manager.py +745 -0
  57. package/core/database_curl_settings_manager.py +609 -0
  58. package/core/database_promera_ai_settings_manager.py +447 -0
  59. package/core/database_schema.py +412 -0
  60. package/core/database_schema_manager.py +396 -0
  61. package/core/database_settings_manager.py +1508 -0
  62. package/core/database_settings_manager_interface.py +457 -0
  63. package/core/dialog_manager.py +735 -0
  64. package/core/efficient_line_numbers.py +511 -0
  65. package/core/error_handler.py +747 -0
  66. package/core/error_service.py +431 -0
  67. package/core/event_consolidator.py +512 -0
  68. package/core/mcp/__init__.py +43 -0
  69. package/core/mcp/__pycache__/__init__.cpython-313.pyc +0 -0
  70. package/core/mcp/__pycache__/protocol.cpython-313.pyc +0 -0
  71. package/core/mcp/__pycache__/schema.cpython-313.pyc +0 -0
  72. package/core/mcp/__pycache__/server_stdio.cpython-313.pyc +0 -0
  73. package/core/mcp/__pycache__/tool_registry.cpython-313.pyc +0 -0
  74. package/core/mcp/protocol.py +288 -0
  75. package/core/mcp/schema.py +251 -0
  76. package/core/mcp/server_stdio.py +299 -0
  77. package/core/mcp/tool_registry.py +2345 -0
  78. package/core/memory_efficient_text_widget.py +712 -0
  79. package/core/migration_manager.py +915 -0
  80. package/core/migration_test_suite.py +1086 -0
  81. package/core/migration_validator.py +1144 -0
  82. package/core/optimized_find_replace.py +715 -0
  83. package/core/optimized_pattern_engine.py +424 -0
  84. package/core/optimized_search_highlighter.py +553 -0
  85. package/core/performance_monitor.py +675 -0
  86. package/core/persistence_manager.py +713 -0
  87. package/core/progressive_stats_calculator.py +632 -0
  88. package/core/regex_pattern_cache.py +530 -0
  89. package/core/regex_pattern_library.py +351 -0
  90. package/core/search_operation_manager.py +435 -0
  91. package/core/settings_defaults_registry.py +1087 -0
  92. package/core/settings_integrity_validator.py +1112 -0
  93. package/core/settings_serializer.py +558 -0
  94. package/core/settings_validator.py +1824 -0
  95. package/core/smart_stats_calculator.py +710 -0
  96. package/core/statistics_update_manager.py +619 -0
  97. package/core/stats_config_manager.py +858 -0
  98. package/core/streaming_text_handler.py +723 -0
  99. package/core/task_scheduler.py +596 -0
  100. package/core/update_pattern_library.py +169 -0
  101. package/core/visibility_monitor.py +596 -0
  102. package/core/widget_cache.py +498 -0
  103. package/mcp.json +61 -0
  104. package/package.json +57 -0
  105. package/pomera.py +7483 -0
  106. package/pomera_mcp_server.py +144 -0
  107. package/tools/__init__.py +5 -0
  108. package/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  109. package/tools/__pycache__/ai_tools.cpython-313.pyc +0 -0
  110. package/tools/__pycache__/ascii_art_generator.cpython-313.pyc +0 -0
  111. package/tools/__pycache__/base64_tools.cpython-313.pyc +0 -0
  112. package/tools/__pycache__/base_tool.cpython-313.pyc +0 -0
  113. package/tools/__pycache__/case_tool.cpython-313.pyc +0 -0
  114. package/tools/__pycache__/column_tools.cpython-313.pyc +0 -0
  115. package/tools/__pycache__/cron_tool.cpython-313.pyc +0 -0
  116. package/tools/__pycache__/curl_history.cpython-313.pyc +0 -0
  117. package/tools/__pycache__/curl_processor.cpython-313.pyc +0 -0
  118. package/tools/__pycache__/curl_settings.cpython-313.pyc +0 -0
  119. package/tools/__pycache__/curl_tool.cpython-313.pyc +0 -0
  120. package/tools/__pycache__/diff_viewer.cpython-313.pyc +0 -0
  121. package/tools/__pycache__/email_extraction_tool.cpython-313.pyc +0 -0
  122. package/tools/__pycache__/email_header_analyzer.cpython-313.pyc +0 -0
  123. package/tools/__pycache__/extraction_tools.cpython-313.pyc +0 -0
  124. package/tools/__pycache__/find_replace.cpython-313.pyc +0 -0
  125. package/tools/__pycache__/folder_file_reporter.cpython-313.pyc +0 -0
  126. package/tools/__pycache__/folder_file_reporter_adapter.cpython-313.pyc +0 -0
  127. package/tools/__pycache__/generator_tools.cpython-313.pyc +0 -0
  128. package/tools/__pycache__/hash_generator.cpython-313.pyc +0 -0
  129. package/tools/__pycache__/html_tool.cpython-313.pyc +0 -0
  130. package/tools/__pycache__/huggingface_helper.cpython-313.pyc +0 -0
  131. package/tools/__pycache__/jsonxml_tool.cpython-313.pyc +0 -0
  132. package/tools/__pycache__/line_tools.cpython-313.pyc +0 -0
  133. package/tools/__pycache__/list_comparator.cpython-313.pyc +0 -0
  134. package/tools/__pycache__/markdown_tools.cpython-313.pyc +0 -0
  135. package/tools/__pycache__/mcp_widget.cpython-313.pyc +0 -0
  136. package/tools/__pycache__/notes_widget.cpython-313.pyc +0 -0
  137. package/tools/__pycache__/number_base_converter.cpython-313.pyc +0 -0
  138. package/tools/__pycache__/regex_extractor.cpython-313.pyc +0 -0
  139. package/tools/__pycache__/slug_generator.cpython-313.pyc +0 -0
  140. package/tools/__pycache__/sorter_tools.cpython-313.pyc +0 -0
  141. package/tools/__pycache__/string_escape_tool.cpython-313.pyc +0 -0
  142. package/tools/__pycache__/text_statistics_tool.cpython-313.pyc +0 -0
  143. package/tools/__pycache__/text_wrapper.cpython-313.pyc +0 -0
  144. package/tools/__pycache__/timestamp_converter.cpython-313.pyc +0 -0
  145. package/tools/__pycache__/tool_loader.cpython-313.pyc +0 -0
  146. package/tools/__pycache__/translator_tools.cpython-313.pyc +0 -0
  147. package/tools/__pycache__/url_link_extractor.cpython-313.pyc +0 -0
  148. package/tools/__pycache__/url_parser.cpython-313.pyc +0 -0
  149. package/tools/__pycache__/whitespace_tools.cpython-313.pyc +0 -0
  150. package/tools/__pycache__/word_frequency_counter.cpython-313.pyc +0 -0
  151. package/tools/ai_tools.py +2892 -0
  152. package/tools/ascii_art_generator.py +353 -0
  153. package/tools/base64_tools.py +184 -0
  154. package/tools/base_tool.py +511 -0
  155. package/tools/case_tool.py +309 -0
  156. package/tools/column_tools.py +396 -0
  157. package/tools/cron_tool.py +885 -0
  158. package/tools/curl_history.py +601 -0
  159. package/tools/curl_processor.py +1208 -0
  160. package/tools/curl_settings.py +503 -0
  161. package/tools/curl_tool.py +5467 -0
  162. package/tools/diff_viewer.py +1072 -0
  163. package/tools/email_extraction_tool.py +249 -0
  164. package/tools/email_header_analyzer.py +426 -0
  165. package/tools/extraction_tools.py +250 -0
  166. package/tools/find_replace.py +1751 -0
  167. package/tools/folder_file_reporter.py +1463 -0
  168. package/tools/folder_file_reporter_adapter.py +480 -0
  169. package/tools/generator_tools.py +1217 -0
  170. package/tools/hash_generator.py +256 -0
  171. package/tools/html_tool.py +657 -0
  172. package/tools/huggingface_helper.py +449 -0
  173. package/tools/jsonxml_tool.py +730 -0
  174. package/tools/line_tools.py +419 -0
  175. package/tools/list_comparator.py +720 -0
  176. package/tools/markdown_tools.py +562 -0
  177. package/tools/mcp_widget.py +1417 -0
  178. package/tools/notes_widget.py +973 -0
  179. package/tools/number_base_converter.py +373 -0
  180. package/tools/regex_extractor.py +572 -0
  181. package/tools/slug_generator.py +311 -0
  182. package/tools/sorter_tools.py +459 -0
  183. package/tools/string_escape_tool.py +393 -0
  184. package/tools/text_statistics_tool.py +366 -0
  185. package/tools/text_wrapper.py +431 -0
  186. package/tools/timestamp_converter.py +422 -0
  187. package/tools/tool_loader.py +710 -0
  188. package/tools/translator_tools.py +523 -0
  189. package/tools/url_link_extractor.py +262 -0
  190. package/tools/url_parser.py +205 -0
  191. package/tools/whitespace_tools.py +356 -0
  192. package/tools/word_frequency_counter.py +147 -0
@@ -0,0 +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
+ }