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