1
2 """GNUmed keyword expansion widgets."""
3
4 __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
5 __license__ = "GPL v2 or later"
6
7 import logging
8 import sys
9 import re as regex
10 import os.path
11
12
13 import wx
14
15
16 if __name__ == '__main__':
17 sys.path.insert(0, '../../')
18 from Gnumed.pycommon import gmDispatcher
19 from Gnumed.pycommon import gmPG2
20 from Gnumed.pycommon import gmTools
21 from Gnumed.business import gmKeywordExpansion
22 from Gnumed.wxpython import gmEditArea
23 from Gnumed.wxpython import gmListWidgets
24
25
26 _log = logging.getLogger('gm.ui')
27
28 _text_expansion_fillin_regex = r'\$<.*>\$'
29
30
32
34 if not isinstance(self, wx.TextCtrl):
35 raise TypeError('[%s]: can only be applied to wx.TextCtrl, not [%s]' % (cKeywordExpansion_TextCtrlMixin, self.__class__.__name__))
36
38 self.__keyword_separators = regex.compile("[!?'\".,:;)}\]\r\n\s\t]+")
39 self.Bind(wx.EVT_CHAR, self.__on_char_in_keyword_expansion_mixin)
40
42 self.Unbind(wx.EVT_CHAR)
43
44
45
47 evt.Skip()
48
49
50 if self.LastPosition == 1:
51 return
52
53 char = unichr(evt.GetUnicodeKey())
54
55 explicit_expansion = False
56 if evt.GetModifiers() == (wx.MOD_CMD | wx.MOD_ALT):
57 if evt.GetKeyCode() != 13:
58 return
59 explicit_expansion = True
60
61 if not explicit_expansion:
62
63
64
65 if self.__keyword_separators.match(char) is None:
66 return
67
68 caret_pos, line_no = self.PositionToXY(self.InsertionPoint)
69 line = self.GetLineText(line_no)
70 keyword = self.__keyword_separators.split(line[:caret_pos])[-1]
71
72 if (
73 (not explicit_expansion)
74 and
75 (keyword != u'$$steffi')
76 and
77 (keyword not in [ r[0] for r in gmKeywordExpansion.get_textual_expansion_keywords() ])
78 ):
79 return
80
81 start = self.InsertionPoint - len(keyword)
82 wx.CallAfter(self.__replace_keyword_with_expansion, keyword, start, explicit_expansion)
83
84 return
85
86
87
88 - def __replace_keyword_with_expansion(self, keyword=None, position=None, show_list=False):
89
90 expansion = expand_keyword(parent = self, keyword = keyword, show_list = show_list)
91
92 if expansion is None:
93 return
94
95 if expansion == u'':
96 return
97
98 if not self.IsMultiLine():
99 expansion_lines = gmTools.strip_leading_empty_lines (
100 lines = gmTools.strip_trailing_empty_lines (
101 text = expansion,
102 return_list = True
103 ),
104 return_list = True
105 )
106 if len(expansion_lines) == 0:
107 return
108 if len(expansion_lines) == 1:
109 expansion = expansion_lines[0]
110 else:
111 msg = _(
112 'The fragment <%s> expands to multiple lines !\n'
113 '\n'
114 'This text field can hold one line only, hwoever.\n'
115 '\n'
116 'Please select the line you want to insert:'
117 ) % keyword
118 expansion = gmListWidgets.get_choices_from_list (
119 parent = self,
120 msg = msg,
121 caption = _('Adapting multi-line expansion to single-line text field'),
122 choices = expansion_lines,
123 selections = [0],
124 columns = [_('Keyword expansion lines')],
125 single_selection = True,
126 can_return_empty = False
127 )
128 if expansion is None:
129 return
130
131 self.Replace (
132 position,
133 position + len(keyword),
134 expansion
135 )
136
137 self.SetInsertionPoint(position + len(expansion) + 1)
138 self.ShowPosition(position + len(expansion) + 1)
139
140 return
141
142
143 from Gnumed.wxGladeWidgets import wxgTextExpansionEditAreaPnl
144
145 -class cTextExpansionEditAreaPnl(wxgTextExpansionEditAreaPnl.wxgTextExpansionEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
146
147 - def __init__(self, *args, **kwds):
148
149 try:
150 data = kwds['expansion']
151 del kwds['expansion']
152 except KeyError:
153 data = None
154
155 wxgTextExpansionEditAreaPnl.wxgTextExpansionEditAreaPnl.__init__(self, *args, **kwds)
156 gmEditArea.cGenericEditAreaMixin.__init__(self)
157
158 self.mode = 'new'
159 self.data = data
160 if data is not None:
161 self.mode = 'edit'
162
163
164 self.__register_interests()
165
166 self.__data_filename = None
167
168
169
170
171
172
173 - def _valid_for_save(self):
174 validity = True
175
176 has_expansion = (
177 (self._TCTRL_expansion.GetValue().strip() != u'')
178 or
179 (self.__data_filename is not None)
180 or
181 ((self.data is not None) and (self.data['is_textual'] is False))
182 )
183
184 if has_expansion:
185 self.display_tctrl_as_valid(tctrl = self._TCTRL_expansion, valid = True)
186 self.display_tctrl_as_valid(tctrl = self._TCTRL_data_file, valid = True)
187 else:
188 validity = False
189 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save keyword expansion without text or data expansion.'), beep = True)
190 self.display_tctrl_as_valid(tctrl = self._TCTRL_expansion, valid = False)
191 self.display_tctrl_as_valid(tctrl = self._TCTRL_data_file, valid = False)
192 if self.data is None:
193 self._TCTRL_expansion.SetFocus()
194 else:
195 if self.data['is_textual']:
196 self._TCTRL_expansion.SetFocus()
197 else:
198 self._BTN_select_data_file.SetFocus()
199
200 if self._TCTRL_keyword.GetValue().strip() == u'':
201 validity = False
202 self.display_tctrl_as_valid(tctrl = self._TCTRL_keyword, valid = False)
203 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save keyword expansion without keyword.'), beep = True)
204 self._TCTRL_keyword.SetFocus()
205 else:
206 self.display_tctrl_as_valid(tctrl = self._TCTRL_keyword, valid = True)
207
208 return validity
209
210 - def _save_as_new(self):
211 expansion = gmKeywordExpansion.create_keyword_expansion (
212 keyword = self._TCTRL_keyword.GetValue().strip(),
213 text = self._TCTRL_expansion.GetValue(),
214 data_file = self.__data_filename,
215 public = self._RBTN_public.GetValue()
216 )
217
218 if expansion is None:
219 return False
220
221 expansion['is_encrypted'] = self._CHBOX_is_encrypted.IsChecked()
222 expansion.save()
223
224 self.data = expansion
225 return True
226
227 - def _save_as_update(self):
228
229 self.data['expansion'] = self._TCTRL_expansion.GetValue().strip()
230 self.data['is_encrypted'] = self._CHBOX_is_encrypted.IsChecked()
231 self.data.save()
232
233 if self.__data_filename is not None:
234 self.data.update_data_from_file(filename = self.__data_filename)
235
236 return True
237
238
239 - def _refresh_as_new(self):
240 self.__data_filename = None
241
242 self._TCTRL_keyword.SetValue(u'')
243 self._TCTRL_keyword.Enable(True)
244
245 self._LBL_data.Enable(False)
246 self._BTN_select_data_file.Enable(False)
247 self._TCTRL_data_file.SetValue(u'')
248 self._CHBOX_is_encrypted.SetValue(False)
249 self._CHBOX_is_encrypted.Enable(False)
250
251 self._LBL_text.Enable(False)
252 self._TCTRL_expansion.SetValue(u'')
253 self._TCTRL_expansion.Enable(False)
254
255 self._RBTN_public.Enable(False)
256 self._RBTN_private.Enable(False)
257 self._RBTN_public.SetValue(1)
258
259 self._TCTRL_keyword.SetFocus()
260
262 self._refresh_from_existing()
263
264 self._TCTRL_keyword.SetValue(u'%s%s' % (self.data, _(u'___copy')))
265 self._TCTRL_keyword.Enable(True)
266
267 self._RBTN_public.Enable(True)
268 self._RBTN_private.Enable(True)
269
270 self._TCTRL_keyword.SetFocus()
271
273 self.__data_filename = None
274
275 self._TCTRL_keyword.SetValue(self.data['keyword'])
276 self._TCTRL_keyword.Enable(False)
277
278 if self.data['is_textual']:
279 self._LBL_text.Enable(True)
280 self._TCTRL_expansion.SetValue(gmTools.coalesce(self.data['expansion'], u''))
281
282 self._LBL_data.Enable(False)
283 self._BTN_select_data_file.Enable(False)
284 self._TCTRL_data_file.SetValue(u'')
285 self._CHBOX_is_encrypted.SetValue(False)
286 self._CHBOX_is_encrypted.Enable(False)
287 else:
288 self._LBL_text.Enable(False)
289 self._TCTRL_expansion.SetValue(u'')
290
291 self._LBL_data.Enable(True)
292 self._BTN_select_data_file.Enable(True)
293 self._TCTRL_data_file.SetValue(_('Size: %s') % gmTools.size2str(self.data['data_size']))
294 self._CHBOX_is_encrypted.SetValue(self.data['is_encrypted'])
295 self._CHBOX_is_encrypted.Enable(True)
296
297 self._RBTN_public.Enable(False)
298 self._RBTN_private.Enable(False)
299 if self.data['public_expansion']:
300 self._RBTN_public.SetValue(1)
301 else:
302 self._RBTN_private.SetValue(1)
303
304 if self.data['is_textual']:
305 self._TCTRL_expansion.SetFocus()
306 else:
307 self._BTN_select_data_file.SetFocus()
308
309
310
312 self._TCTRL_keyword.Bind(wx.EVT_TEXT, self._on_keyword_modified)
313 self._TCTRL_expansion.Bind(wx.EVT_TEXT, self._on_expansion_modified)
314
315 - def _on_keyword_modified(self, evt):
316 if self._TCTRL_keyword.GetValue().strip() == u'':
317 self._LBL_text.Enable(False)
318 self._TCTRL_expansion.Enable(False)
319 self._LBL_data.Enable(False)
320 self._BTN_select_data_file.Enable(False)
321 self._CHBOX_is_encrypted.Enable(False)
322 self._RBTN_public.Enable(False)
323 self._RBTN_private.Enable(False)
324 return
325
326
327
328
329 self._LBL_text.Enable(True)
330 self._TCTRL_expansion.Enable(True)
331 self._LBL_data.Enable(True)
332 self._BTN_select_data_file.Enable(True)
333 self._RBTN_public.Enable(True)
334 self._RBTN_private.Enable(True)
335
337 if self._TCTRL_expansion.GetValue().strip() == u'':
338 self._LBL_data.Enable(True)
339 self._BTN_select_data_file.Enable(True)
340 return
341
342 self.__data_filename = None
343 self._LBL_data.Enable(False)
344 self._BTN_select_data_file.Enable(False)
345 self._TCTRL_data_file.SetValue(u'')
346 self._CHBOX_is_encrypted.Enable(False)
347
349 wildcards = [
350 u"%s (*)|*" % _('all files'),
351 u"%s (*.*)|*.*" % _('all files (Windows)')
352 ]
353
354 dlg = wx.FileDialog (
355 parent = self,
356 message = _('Choose the file containing the data snippet'),
357 wildcard = '|'.join(wildcards),
358 style = wx.OPEN | wx.HIDE_READONLY | wx.FILE_MUST_EXIST
359 )
360 result = dlg.ShowModal()
361 if result != wx.ID_CANCEL:
362 self.__data_filename = dlg.GetPath()
363 self._TCTRL_data_file.SetValue(self.__data_filename)
364 self._CHBOX_is_encrypted.SetValue(False)
365 self._CHBOX_is_encrypted.Enable(True)
366
367 dlg.Destroy()
368
369
379
380 def edit(expansion=None):
381 ea = cTextExpansionEditAreaPnl(parent, -1, expansion = expansion)
382 dlg = gmEditArea.cGenericEditAreaDlg2(parent, -1, edit_area = ea)
383 if expansion is None:
384 title = _('Adding keyword expansion')
385 else:
386 title = _('Editing keyword expansion "%s"') % expansion['keyword']
387 dlg.SetTitle(title)
388 if dlg.ShowModal() == wx.ID_OK:
389 return True
390
391 return False
392
393 def tooltip(expansion):
394 return expansion.format()
395
396 def refresh(lctrl=None):
397 expansions = gmKeywordExpansion.get_keyword_expansions(order_by = u'is_textual DESC, keyword, public_expansion', force_reload = True)
398 items = [[
399 e['keyword'],
400 gmTools.bool2subst(e['is_textual'], _('text'), _('data')),
401 gmTools.bool2subst(e['public_expansion'], _('public'), _('private'))
402 ] for e in expansions
403 ]
404 lctrl.set_string_items(items)
405 lctrl.set_data(expansions)
406
407
408 gmListWidgets.get_choices_from_list (
409 parent = parent,
410 msg = _('\nSelect the keyword you want to edit !\n'),
411 caption = _('Editing keyword-based expansions ...'),
412 columns = [_('Keyword'), _('Type'), _('Scope')],
413 single_selection = True,
414 edit_callback = edit,
415 new_callback = edit,
416 delete_callback = delete,
417 refresh_callback = refresh,
418 list_tooltip_callback = tooltip
419 )
420
421 from Gnumed.wxGladeWidgets import wxgTextExpansionFillInDlg
422
423 -class cTextExpansionFillInDlg(wxgTextExpansionFillInDlg.wxgTextExpansionFillInDlg):
424
425 - def __init__(self, *args, **kwds):
426 wxgTextExpansionFillInDlg.wxgTextExpansionFillInDlg.__init__(self, *args, **kwds)
427
428 self.__expansion = None
429 self.__init_ui()
430
431 - def __init_ui(self):
432 self._LBL_top_part.SetLabel(u'')
433 self._LBL_left_part.SetLabel(u'')
434 self._LBL_left_part.Hide()
435 self._TCTRL_fillin.SetValue(u'')
436 self._TCTRL_fillin.SetBackgroundColour('yellow')
437 self._TCTRL_fillin.Disable()
438 self._TCTRL_fillin.Hide()
439 self._LBL_right_part.SetLabel(u'')
440 self._LBL_right_part.Hide()
441 self._LBL_bottom_part.SetLabel(u'')
442 self._BTN_OK.Disable()
443 self._BTN_forward.Disable()
444 self._BTN_cancel.SetFocus()
445 self._LBL_hint.SetLabel(u'')
446
448 if self.__expansion is None:
449 return
450
451 if self.__new_expansion:
452 self.__filled_in = self.__expansion
453 self.__new_expansion = False
454 else:
455 self.__filled_in = (
456 self._LBL_top_part.GetLabel() +
457 self.__left_splitter +
458 self._LBL_left_part.GetLabel() +
459 self._TCTRL_fillin.GetValue().strip() +
460 self._LBL_right_part.GetLabel() +
461 self.__right_splitter +
462 self._LBL_bottom_part.GetLabel()
463 )
464
465
466 if regex.search(_text_expansion_fillin_regex, self.__filled_in) is None:
467
468 self._LBL_top_part.SetLabel(self.__filled_in)
469 self._LBL_left_part.SetLabel(u'')
470 self._LBL_left_part.Hide()
471 self._TCTRL_fillin.SetValue(u'')
472 self._TCTRL_fillin.Disable()
473 self._TCTRL_fillin.Hide()
474 self._LBL_right_part.SetLabel(u'')
475 self._LBL_right_part.Hide()
476 self._LBL_bottom_part.SetLabel(u'')
477 self._BTN_OK.Enable()
478 self._BTN_forward.Disable()
479 self._BTN_OK.SetDefault()
480 return
481
482
483 top, fillin, bottom = regex.split(r'(' + _text_expansion_fillin_regex + r')', self.__filled_in, maxsplit = 1)
484 top_parts = top.rsplit(u'\n', 1)
485 top_part = top_parts[0]
486 if len(top_parts) == 1:
487 self.__left_splitter = u''
488 left_part = u''
489 else:
490 self.__left_splitter = u'\n'
491 left_part = top_parts[1]
492 bottom_parts = bottom.split(u'\n', 1)
493 if len(bottom_parts) == 1:
494 parts = bottom_parts[0].split(u' ', 1)
495 right_part = parts[0]
496 if len(parts) == 1:
497 self.__right_splitter = u''
498 bottom_part = u''
499 else:
500 self.__right_splitter = u' '
501 bottom_part = parts[1]
502 else:
503 self.__right_splitter = u'\n'
504 right_part = bottom_parts[0]
505 bottom_part = bottom_parts[1]
506 hint = fillin.strip('$').strip('<').strip('>').strip()
507 self._LBL_top_part.SetLabel(top_part)
508 self._LBL_left_part.SetLabel(left_part)
509 self._LBL_left_part.Show()
510 self._TCTRL_fillin.Enable()
511 self._TCTRL_fillin.SetValue(u'')
512 self._TCTRL_fillin.Show()
513 self._LBL_right_part.SetLabel(right_part)
514 self._LBL_right_part.Show()
515 self._LBL_bottom_part.SetLabel(bottom_part)
516 self._BTN_OK.Disable()
517 self._BTN_forward.Enable()
518 self._BTN_forward.SetDefault()
519 self._LBL_hint.SetLabel(hint)
520 self._TCTRL_fillin.SetFocus()
521
522 self.Layout()
523 self.Fit()
524
525
526
527 - def _get_expansion(self):
528 return self.__expansion
529
530 - def _set_expansion(self, expansion):
531 self.__expansion = expansion
532 self.__new_expansion = True
533 self.__goto_next_fillin()
534 return
535
536 expansion = property(_get_expansion, _set_expansion)
537
538 - def _get_filled_in(self):
539 return self.__filled_in
540
541 filled_in_expansion = property(_get_filled_in, lambda x:x)
542
543 - def _set_keyword(self, keyword):
544 self.SetTitle(_('Expanding <%s>') % keyword)
545
546 keyword = property(lambda x:x, _set_keyword)
547
548
549
551 self.__goto_next_fillin()
552
610
611
612
613
614 if __name__ == '__main__':
615
616 if len(sys.argv) < 2:
617 sys.exit()
618
619 if sys.argv[1] != 'test':
620 sys.exit()
621
622 from Gnumed.pycommon import gmI18N
623 gmI18N.activate_locale()
624 gmI18N.install_domain(domain = 'gnumed')
625
626
628 expansion = u"""HEMORR²HAGES: Blutungsrisiko unter OAK
629 --------------------------------------
630 Am Heart J. 2006 Mar;151(3):713-9.
631
632 $<1 oder 0 eingeben>$ H epatische oder Nierenerkrankung
633 $<1 oder 0 eingeben>$ E thanolabusus
634 $<1 oder 0 eingeben>$ M alignom
635 $<1 oder 0 eingeben>$ O ld patient (> 75 Jahre)
636 $<1 oder 0 eingeben>$ R eduzierte Thrombozytenzahl/-funktion
637 $<2 oder 0 eingeben>$ R²ekurrente (frühere) große Blutung
638 $<1 oder 0 eingeben>$ H ypertonie (unkontrolliert)
639 $<1 oder 0 eingeben>$ A nämie
640 $<1 oder 0 eingeben>$ G enetische Faktoren
641 $<1 oder 0 eingeben>$ E xzessives Sturzrisiko
642 $<1 oder 0 eingeben>$ S Schlaganfall in der Anamnese
643 --------------------------------------
644 Summe Rate großer Blutungen
645 pro 100 Patientenjahre
646 0 1.9
647 1 2.5
648 2 5.3
649 3 8.4
650 4 10.4
651 >4 12.3
652
653 Bewertung: Summe = $<Summe ausrechnen und bewerten>$"""
654
655 app = wx.PyWidgetTester(size = (600, 600))
656 dlg = cTextExpansionFillInDlg(None, -1)
657 dlg.expansion = expansion
658 dlg.ShowModal()
659
660
661 test_fillin()
662