Package Gnumed :: Package wxpython :: Module gmKeywordExpansionWidgets
[frames] | no frames]

Source Code for Module Gnumed.wxpython.gmKeywordExpansionWidgets

  1  # -*- coding: utf8 -*- 
  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  #============================================================ 
31 -class cKeywordExpansion_TextCtrlMixin():
32
33 - def __init__(self):
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 # event handling 45 #--------------------------------------------------------
47 evt.Skip() 48 49 # empty ? 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): # portable CTRL-ALT-... 57 if evt.GetKeyCode() != 13: # CTRL-ALT-ENTER 58 return 59 explicit_expansion = True 60 61 if not explicit_expansion: 62 # user did not press CTRL-ALT-ENTER, 63 # however, did they last enter a 64 # "keyword sepearator", active character ? 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') # Easter Egg ;-) 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 # internal helpers 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 # self.__init_ui() 164 self.__register_interests() 165 166 self.__data_filename = None
167 #-------------------------------------------------------- 168 # def __init_ui(self, expansion=None): 169 # self._BTN_select_data_file.Enable(False) 170 #---------------------------------------------------------------- 171 # generic Edit Area mixin API 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 #----------------------------------------------------------------
272 - def _refresh_from_existing(self):
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 # event handling 310 #----------------------------------------------------------------
311 - def __register_interests(self):
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 # keyword is not empty 327 # mode must be new(_from_existing) or else 328 # we cannot modify the keyword in the first place 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 #----------------------------------------------------------------
336 - def _on_expansion_modified(self, evt):
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 #----------------------------------------------------------------
348 - def _on_select_data_file_button_pressed(self, event):
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 #============================================================
370 -def configure_keyword_text_expansion(parent=None):
371 372 if parent is None: 373 parent = wx.GetApp().GetTopWindow() 374 375 #---------------------- 376 def delete(expansion=None): 377 gmKeywordExpansion.delete_keyword_expansion(pk = expansion['pk_expansion']) 378 return True
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 #---------------------------------------------
447 - def __goto_next_fillin(self):
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 # anything to fill in ? 466 if regex.search(_text_expansion_fillin_regex, self.__filled_in) is None: 467 # no 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 # yes 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 # properties 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 # event handlers 549 #---------------------------------------------
550 - def _on_forward_button_pressed(self, event):
551 self.__goto_next_fillin()
552 #============================================================
553 -def expand_keyword(parent=None, keyword=None, show_list=False):
554 """Expand keyword and replace inside it. 555 556 Returns: 557 None: aborted or no expansion available 558 u'': empty expansion 559 u'<text>' the expansion 560 """ 561 if keyword is None: 562 return None 563 564 if parent is None: 565 parent = wx.GetApp().GetTopWindow() 566 567 if show_list: 568 candidates = gmKeywordExpansion.get_matching_textual_keywords(fragment = keyword) 569 if len(candidates) == 0: 570 return None 571 if len(candidates) == 1: 572 keyword = candidates[0] 573 else: 574 keyword = gmListWidgets.get_choices_from_list ( 575 parent = parent, 576 msg = _( 577 'Several macro keywords match the fragment [%s].\n' 578 '\n' 579 'Please select the expansion you want to happen.' 580 ) % keyword, 581 caption = _('Selecting text macro'), 582 choices = candidates, 583 columns = [_('Keyword')], 584 single_selection = True, 585 can_return_empty = False 586 ) 587 if keyword is None: 588 return None 589 590 expansion = gmKeywordExpansion.expand_keyword(keyword = keyword) 591 592 # not found 593 if expansion is None: 594 return None 595 596 # no replacement necessary: 597 if expansion.strip() == u'': 598 return expansion 599 600 if regex.search(_text_expansion_fillin_regex, expansion) is not None: 601 dlg = cTextExpansionFillInDlg(None, -1) 602 dlg.keyword = keyword 603 dlg.expansion = expansion 604 button = dlg.ShowModal() 605 if button == wx.ID_OK: 606 expansion = dlg.filled_in_expansion 607 dlg.Destroy() 608 609 return expansion
610 611 #============================================================ 612 # main 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 #----------------------------------------
627 - def test_fillin():
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 #app.MainLoop() 660 #---------------------------------------- 661 test_fillin() 662