forked from balisujohn/localwriter
-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathmodule_config_dialog.py
More file actions
299 lines (245 loc) · 9.93 KB
/
Copy pathmodule_config_dialog.py
File metadata and controls
299 lines (245 loc) · 9.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
# WriterAgent - AI Writing Assistant for LibreOffice
# Copyright (c) 2026 KeithCu (modifications and relicensing)
#
# SPDX-License-Identifier: GPL-3.0-or-later
"""Modeless module config dialogs generated from module.yaml config_dialog specs."""
from __future__ import annotations
import logging
from typing import Any
import unohelper
from com.sun.star.awt import XActionListener, XTopWindowListener
from plugin.chatbot.dialogs import (
TabListener,
get_checkbox_state,
get_control_text,
get_optional,
is_checkbox_control,
set_checkbox_state,
set_control_text,
translate_dialog,
)
from plugin.framework.config import as_bool
from plugin.framework.i18n import _
from plugin.framework.uno_context import get_extension_url
log = logging.getLogger(__name__)
_active_dialogs: dict[str, Any] = {}
_TAB_PAGE_MAP = {
"btn_tab_general": 1,
"btn_tab_ocr": 2,
"btn_tab_tables": 3,
"btn_tab_advanced": 4,
}
def get_module_config_dialog_id(module_name: str) -> str | None:
from plugin.chatbot.settings_fields import find_module_manifest
manifest = find_module_manifest(module_name)
if not manifest:
return None
cfg_dialog = manifest.get("config_dialog") or {}
dialog_id = str(cfg_dialog.get("id") or "").strip()
return dialog_id or None
def get_module_config_field_specs(ctx: Any, module_name: str) -> list[dict[str, Any]]:
"""Field specs for a standalone module config dialog (flat control ids)."""
from plugin.chatbot.settings_fields import build_module_field_specs
return build_module_field_specs(module_name, ctx=ctx, control_ids="flat")
def apply_module_config_result(ctx: Any, module_name: str, result: dict[str, Any]) -> None:
"""Persist standalone module dialog values to writeragent.json."""
from plugin.chatbot.settings_fields import apply_field_specs_result
field_specs = get_module_config_field_specs(ctx, module_name)
apply_field_specs_result(ctx, result, field_specs)
def _option_labels(field: dict[str, Any]) -> tuple[str, ...]:
opts = field.get("options")
if not isinstance(opts, list):
return ()
labels: list[str] = []
for opt in opts:
if isinstance(opt, dict):
labels.append(_(str(opt.get("label") or opt.get("value") or "")))
elif opt is not None:
labels.append(_(str(opt)))
return tuple(labels)
def _set_field_options(ctrl: Any, field: dict[str, Any]) -> None:
labels = _option_labels(field)
if not labels:
log.warning("Module config field %s has no select options", field.get("name"))
return
model = ctrl.getModel() if hasattr(ctrl, "getModel") else None
if model is not None and hasattr(model, "StringItemList"):
model.StringItemList = labels
log.debug("Module config set %d options on %s", len(labels), field.get("name"))
return
if hasattr(ctrl, "addItem"):
try:
while ctrl.getItemCount() > 0:
ctrl.removeItems(0, 1)
except Exception:
pass
for label in labels:
ctrl.addItem(label, 0)
log.debug("Module config addItem populated %d options on %s", len(labels), field.get("name"))
return
log.warning("Module config control %s does not support option lists", field.get("name"))
class ModuleConfigDialog:
"""Modeless settings dialog for one MODULES entry with config_dialog metadata."""
def __init__(self, ctx: Any, module_name: str) -> None:
self._ctx = ctx
self._module_name = module_name
self._dlg: Any | None = None
self._closed = False
self._top_listener: Any | None = None
@classmethod
def show(cls, ctx: Any, module_name: str) -> None:
existing = _active_dialogs.get(module_name)
if existing is not None:
try:
existing.close()
except Exception:
log.debug("Failed to close prior module config dialog", exc_info=True)
dialog = cls(ctx, module_name)
_active_dialogs[module_name] = dialog
dialog._open()
def close(self) -> None:
if self._closed:
return
self._closed = True
_active_dialogs.pop(self._module_name, None)
dlg = self._dlg
self._dlg = None
if dlg is None:
return
try:
dlg.setVisible(False)
except Exception:
log.exception("Failed to hide module config dialog")
try:
dlg.dispose()
except Exception:
log.exception("Failed to dispose module config dialog")
def _open(self) -> None:
ctx = self._ctx
dialog_id = get_module_config_dialog_id(self._module_name)
if not dialog_id:
log.error("No config_dialog.id for module %s", self._module_name)
return
try:
smgr = ctx.getServiceManager()
base_url = get_extension_url()
dp = smgr.createInstanceWithContext("com.sun.star.awt.DialogProvider", ctx)
dlg = dp.createDialog(base_url + "/Dialogs/%s.xdl" % dialog_id)
except Exception:
log.exception("Failed to load module config dialog %s", dialog_id)
return
self._dlg = dlg
translate_dialog(dlg)
self._setup_tabs()
self._wire_buttons()
self._populate_fields(get_module_config_field_specs(ctx, self._module_name))
owner = self
class _TopWindowListener(unohelper.Base, XTopWindowListener):
def windowClosing(self, e):
owner.close()
def windowClosed(self, e):
pass
def windowOpened(self, e):
pass
def windowMinimized(self, e):
pass
def windowNormalized(self, e):
pass
def windowActivated(self, e):
pass
def windowDeactivated(self, e):
pass
def disposing(self, Source):
pass
self._top_listener = _TopWindowListener()
dlg.addTopWindowListener(self._top_listener)
dlg.setVisible(True)
def _setup_tabs(self) -> None:
assert self._dlg is not None
for tab_id, page_num in _TAB_PAGE_MAP.items():
btn = get_optional(self._dlg, tab_id)
if btn is not None:
btn.addActionListener(TabListener(self._dlg, page_num))
def _wire_buttons(self) -> None:
assert self._dlg is not None
owner = self
class _ApplyListener(unohelper.Base, XActionListener):
def actionPerformed(self, rEvent):
owner._apply(close=False)
def disposing(self, Source):
pass
class _OkListener(unohelper.Base, XActionListener):
def actionPerformed(self, rEvent):
owner._apply(close=True)
def disposing(self, Source):
pass
class _CloseListener(unohelper.Base, XActionListener):
def actionPerformed(self, rEvent):
owner.close()
def disposing(self, Source):
pass
apply_btn = get_optional(self._dlg, "btn_apply")
if apply_btn:
apply_btn.addActionListener(_ApplyListener())
ok_btn = get_optional(self._dlg, "btn_ok")
if ok_btn:
ok_btn.addActionListener(_OkListener())
close_btn = get_optional(self._dlg, "btn_close")
if close_btn:
close_btn.addActionListener(_CloseListener())
def _populate_fields(self, field_specs: list[dict[str, Any]]) -> None:
assert self._dlg is not None
for field in field_specs:
ctrl = self._dlg.getControl(field["name"])
if ctrl is None:
log.warning(
"Module config dialog %s missing control %r",
self._module_name,
field["name"],
)
continue
if is_checkbox_control(ctrl):
set_checkbox_state(ctrl, 1 if as_bool(field["value"]) else 0)
elif hasattr(ctrl, "setText"):
if "options" in field:
try:
_set_field_options(ctrl, field)
except Exception:
log.exception("Failed to set options for %s", field["name"])
ctrl.setText(str(field.get("value", "")))
else:
if "options" in field:
try:
_set_field_options(ctrl, field)
except Exception:
log.exception("Failed to set options for %s", field["name"])
set_control_text(ctrl, field["value"])
def _extract_result(self) -> dict[str, Any]:
assert self._dlg is not None
result: dict[str, Any] = {}
for field in get_module_config_field_specs(self._ctx, self._module_name):
name = field["name"]
ctrl = self._dlg.getControl(name)
if ctrl is None:
continue
if is_checkbox_control(ctrl):
result[name] = "true" if get_checkbox_state(ctrl) else "false"
elif hasattr(ctrl, "getText"):
result[name] = ctrl.getText()
else:
result[name] = get_control_text(ctrl)
return result
def _apply(self, *, close: bool) -> None:
try:
result = self._extract_result()
apply_module_config_result(self._ctx, self._module_name, result)
except Exception:
log.exception("Failed to apply module config for %s", self._module_name)
if close:
self.close()
def show_module_config_dialog(ctx: Any, module_name: str) -> None:
"""Open the modeless standalone config dialog for *module_name*."""
ModuleConfigDialog.show(ctx, module_name)
def show_vision_settings_dialog(ctx: Any) -> None:
"""Open Vision / OCR settings (vision module config_dialog)."""
show_module_config_dialog(ctx, "vision")