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

Source Code for Module Gnumed.wxpython.gmNarrativeWidgets

   1  """GNUmed narrative handling widgets.""" 
   2  #================================================================ 
   3  __version__ = "$Revision: 1.46 $" 
   4  __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>" 
   5   
   6  import sys, logging, os, os.path, time, re as regex, shutil 
   7   
   8   
   9  import wx 
  10  import wx.lib.expando as wx_expando 
  11  import wx.lib.agw.supertooltip as agw_stt 
  12  import wx.lib.statbmp as wx_genstatbmp 
  13   
  14   
  15  if __name__ == '__main__': 
  16          sys.path.insert(0, '../../') 
  17  from Gnumed.pycommon import gmI18N 
  18  from Gnumed.pycommon import gmDispatcher 
  19  from Gnumed.pycommon import gmTools 
  20  from Gnumed.pycommon import gmDateTime 
  21  from Gnumed.pycommon import gmShellAPI 
  22  from Gnumed.pycommon import gmPG2 
  23  from Gnumed.pycommon import gmCfg 
  24  from Gnumed.pycommon import gmMatchProvider 
  25   
  26  from Gnumed.business import gmPerson 
  27  from Gnumed.business import gmEMRStructItems 
  28  from Gnumed.business import gmClinNarrative 
  29  from Gnumed.business import gmSurgery 
  30  from Gnumed.business import gmForms 
  31  from Gnumed.business import gmDocuments 
  32  from Gnumed.business import gmPersonSearch 
  33   
  34  from Gnumed.wxpython import gmListWidgets 
  35  from Gnumed.wxpython import gmEMRStructWidgets 
  36  from Gnumed.wxpython import gmRegetMixin 
  37  from Gnumed.wxpython import gmPhraseWheel 
  38  from Gnumed.wxpython import gmGuiHelpers 
  39  from Gnumed.wxpython import gmPatSearchWidgets 
  40  from Gnumed.wxpython import gmCfgWidgets 
  41  from Gnumed.wxpython import gmDocumentWidgets 
  42   
  43  from Gnumed.exporters import gmPatientExporter 
  44   
  45   
  46  _log = logging.getLogger('gm.ui') 
  47  _log.info(__version__) 
  48  #============================================================ 
  49  # narrative related widgets/functions 
  50  #------------------------------------------------------------ 
51 -def move_progress_notes_to_another_encounter(parent=None, encounters=None, episodes=None, patient=None, move_all=False):
52 53 # sanity checks 54 if patient is None: 55 patient = gmPerson.gmCurrentPatient() 56 57 if not patient.connected: 58 gmDispatcher.send(signal = 'statustext', msg = _('Cannot move progress notes. No active patient.')) 59 return False 60 61 if parent is None: 62 parent = wx.GetApp().GetTopWindow() 63 64 emr = patient.get_emr() 65 66 if encounters is None: 67 encs = emr.get_encounters(episodes = episodes) 68 encounters = gmEMRStructWidgets.select_encounters ( 69 parent = parent, 70 patient = patient, 71 single_selection = False, 72 encounters = encs 73 ) 74 # cancelled 75 if encounters is None: 76 return True 77 # none selected 78 if len(encounters) == 0: 79 return True 80 81 notes = emr.get_clin_narrative ( 82 encounters = encounters, 83 episodes = episodes 84 ) 85 86 # which narrative 87 if move_all: 88 selected_narr = notes 89 else: 90 selected_narr = gmListWidgets.get_choices_from_list ( 91 parent = parent, 92 caption = _('Moving progress notes between encounters ...'), 93 single_selection = False, 94 can_return_empty = True, 95 data = notes, 96 msg = _('\n Select the progress notes to move from the list !\n\n'), 97 columns = [_('when'), _('who'), _('type'), _('entry')], 98 choices = [ 99 [ narr['date'].strftime('%x %H:%M'), 100 narr['provider'], 101 gmClinNarrative.soap_cat2l10n[narr['soap_cat']], 102 narr['narrative'].replace('\n', '/').replace('\r', '/') 103 ] for narr in notes 104 ] 105 ) 106 107 if not selected_narr: 108 return True 109 110 # which encounter to move to 111 enc2move2 = gmEMRStructWidgets.select_encounters ( 112 parent = parent, 113 patient = patient, 114 single_selection = True 115 ) 116 117 if not enc2move2: 118 return True 119 120 for narr in selected_narr: 121 narr['pk_encounter'] = enc2move2['pk_encounter'] 122 narr.save() 123 124 return True
125 #------------------------------------------------------------
126 -def manage_progress_notes(parent=None, encounters=None, episodes=None, patient=None):
127 128 # sanity checks 129 if patient is None: 130 patient = gmPerson.gmCurrentPatient() 131 132 if not patient.connected: 133 gmDispatcher.send(signal = 'statustext', msg = _('Cannot edit progress notes. No active patient.')) 134 return False 135 136 if parent is None: 137 parent = wx.GetApp().GetTopWindow() 138 139 emr = patient.get_emr() 140 #-------------------------- 141 def delete(item): 142 if item is None: 143 return False 144 dlg = gmGuiHelpers.c2ButtonQuestionDlg ( 145 parent, 146 -1, 147 caption = _('Deleting progress note'), 148 question = _( 149 'Are you positively sure you want to delete this\n' 150 'progress note from the medical record ?\n' 151 '\n' 152 'Note that even if you chose to delete the entry it will\n' 153 'still be (invisibly) kept in the audit trail to protect\n' 154 'you from litigation because physical deletion is known\n' 155 'to be unlawful in some jurisdictions.\n' 156 ), 157 button_defs = ( 158 {'label': _('Delete'), 'tooltip': _('Yes, delete the progress note.'), 'default': False}, 159 {'label': _('Cancel'), 'tooltip': _('No, do NOT delete the progress note.'), 'default': True} 160 ) 161 ) 162 decision = dlg.ShowModal() 163 164 if decision != wx.ID_YES: 165 return False 166 167 gmClinNarrative.delete_clin_narrative(narrative = item['pk_narrative']) 168 return True
169 #-------------------------- 170 def edit(item): 171 if item is None: 172 return False 173 174 dlg = gmGuiHelpers.cMultilineTextEntryDlg ( 175 parent, 176 -1, 177 title = _('Editing progress note'), 178 msg = _('This is the original progress note:'), 179 data = item.format(left_margin = u' ', fancy = True), 180 text = item['narrative'] 181 ) 182 decision = dlg.ShowModal() 183 184 if decision != wx.ID_SAVE: 185 return False 186 187 val = dlg.value 188 dlg.Destroy() 189 if val.strip() == u'': 190 return False 191 192 item['narrative'] = val 193 item.save_payload() 194 195 return True 196 #-------------------------- 197 def refresh(lctrl): 198 notes = emr.get_clin_narrative ( 199 encounters = encounters, 200 episodes = episodes, 201 providers = [ gmPerson.gmCurrentProvider()['short_alias'] ] 202 ) 203 lctrl.set_string_items(items = [ 204 [ narr['date'].strftime('%x %H:%M'), 205 gmClinNarrative.soap_cat2l10n[narr['soap_cat']], 206 narr['narrative'].replace('\n', '/').replace('\r', '/') 207 ] for narr in notes 208 ]) 209 lctrl.set_data(data = notes) 210 #-------------------------- 211 212 gmListWidgets.get_choices_from_list ( 213 parent = parent, 214 caption = _('Managing progress notes'), 215 msg = _( 216 '\n' 217 ' This list shows the progress notes by %s.\n' 218 '\n' 219 ) % gmPerson.gmCurrentProvider()['short_alias'], 220 columns = [_('when'), _('type'), _('entry')], 221 single_selection = True, 222 can_return_empty = False, 223 edit_callback = edit, 224 delete_callback = delete, 225 refresh_callback = refresh, 226 ignore_OK_button = True 227 ) 228 #------------------------------------------------------------
229 -def search_narrative_across_emrs(parent=None):
230 231 if parent is None: 232 parent = wx.GetApp().GetTopWindow() 233 234 searcher = wx.TextEntryDialog ( 235 parent = parent, 236 message = _('Enter (regex) term to search for across all EMRs:'), 237 caption = _('Text search across all EMRs'), 238 style = wx.OK | wx.CANCEL | wx.CENTRE 239 ) 240 result = searcher.ShowModal() 241 242 if result != wx.ID_OK: 243 return 244 245 wx.BeginBusyCursor() 246 term = searcher.GetValue() 247 searcher.Destroy() 248 results = gmClinNarrative.search_text_across_emrs(search_term = term) 249 wx.EndBusyCursor() 250 251 if len(results) == 0: 252 gmGuiHelpers.gm_show_info ( 253 _( 254 'Nothing found for search term:\n' 255 ' "%s"' 256 ) % term, 257 _('Search results') 258 ) 259 return 260 261 items = [ [gmPerson.cIdentity(aPK_obj = 262 r['pk_patient'])['description_gender'], r['narrative'], 263 r['src_table']] for r in results ] 264 265 selected_patient = gmListWidgets.get_choices_from_list ( 266 parent = parent, 267 caption = _('Search results for %s') % term, 268 choices = items, 269 columns = [_('Patient'), _('Match'), _('Match location')], 270 data = [ r['pk_patient'] for r in results ], 271 single_selection = True, 272 can_return_empty = False 273 ) 274 275 if selected_patient is None: 276 return 277 278 wx.CallAfter(gmPatSearchWidgets.set_active_patient, patient = gmPerson.cIdentity(aPK_obj = selected_patient))
279 #------------------------------------------------------------
280 -def search_narrative_in_emr(parent=None, patient=None):
281 282 # sanity checks 283 if patient is None: 284 patient = gmPerson.gmCurrentPatient() 285 286 if not patient.connected: 287 gmDispatcher.send(signal = 'statustext', msg = _('Cannot search EMR. No active patient.')) 288 return False 289 290 if parent is None: 291 parent = wx.GetApp().GetTopWindow() 292 293 searcher = wx.TextEntryDialog ( 294 parent = parent, 295 message = _('Enter search term:'), 296 caption = _('Text search of entire EMR of active patient'), 297 style = wx.OK | wx.CANCEL | wx.CENTRE 298 ) 299 result = searcher.ShowModal() 300 301 if result != wx.ID_OK: 302 searcher.Destroy() 303 return False 304 305 wx.BeginBusyCursor() 306 val = searcher.GetValue() 307 searcher.Destroy() 308 emr = patient.get_emr() 309 rows = emr.search_narrative_simple(val) 310 wx.EndBusyCursor() 311 312 if len(rows) == 0: 313 gmGuiHelpers.gm_show_info ( 314 _( 315 'Nothing found for search term:\n' 316 ' "%s"' 317 ) % val, 318 _('Search results') 319 ) 320 return True 321 322 txt = u'' 323 for row in rows: 324 txt += u'%s: %s\n' % ( 325 row['soap_cat'], 326 row['narrative'] 327 ) 328 329 txt += u' %s: %s - %s %s\n' % ( 330 _('Encounter'), 331 row['encounter_started'].strftime('%x %H:%M'), 332 row['encounter_ended'].strftime('%H:%M'), 333 row['encounter_type'] 334 ) 335 txt += u' %s: %s\n' % ( 336 _('Episode'), 337 row['episode'] 338 ) 339 txt += u' %s: %s\n\n' % ( 340 _('Health issue'), 341 row['health_issue'] 342 ) 343 344 msg = _( 345 'Search term was: "%s"\n' 346 '\n' 347 'Search results:\n\n' 348 '%s\n' 349 ) % (val, txt) 350 351 dlg = wx.MessageDialog ( 352 parent = parent, 353 message = msg, 354 caption = _('Search results for %s') % val, 355 style = wx.OK | wx.STAY_ON_TOP 356 ) 357 dlg.ShowModal() 358 dlg.Destroy() 359 360 return True
361 #------------------------------------------------------------
362 -def export_narrative_for_medistar_import(parent=None, soap_cats=u'soap', encounter=None):
363 364 # sanity checks 365 pat = gmPerson.gmCurrentPatient() 366 if not pat.connected: 367 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export EMR for Medistar. No active patient.')) 368 return False 369 370 if encounter is None: 371 encounter = pat.get_emr().active_encounter 372 373 if parent is None: 374 parent = wx.GetApp().GetTopWindow() 375 376 # get file name 377 aWildcard = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files")) 378 # FIXME: make configurable 379 aDefDir = os.path.abspath(os.path.expanduser(os.path.join('~', 'gnumed','export'))) 380 # FIXME: make configurable 381 fname = '%s-%s-%s-%s-%s.txt' % ( 382 'Medistar-MD', 383 time.strftime('%Y-%m-%d',time.localtime()), 384 pat['lastnames'].replace(' ', '-'), 385 pat['firstnames'].replace(' ', '_'), 386 pat.get_formatted_dob(format = '%Y-%m-%d') 387 ) 388 dlg = wx.FileDialog ( 389 parent = parent, 390 message = _("Save EMR extract for MEDISTAR import as..."), 391 defaultDir = aDefDir, 392 defaultFile = fname, 393 wildcard = aWildcard, 394 style = wx.SAVE 395 ) 396 choice = dlg.ShowModal() 397 fname = dlg.GetPath() 398 dlg.Destroy() 399 if choice != wx.ID_OK: 400 return False 401 402 wx.BeginBusyCursor() 403 _log.debug('exporting encounter for medistar import to [%s]', fname) 404 exporter = gmPatientExporter.cMedistarSOAPExporter() 405 successful, fname = exporter.export_to_file ( 406 filename = fname, 407 encounter = encounter, 408 soap_cats = u'soap', 409 export_to_import_file = True 410 ) 411 if not successful: 412 gmGuiHelpers.gm_show_error ( 413 _('Error exporting progress notes for MEDISTAR import.'), 414 _('MEDISTAR progress notes export') 415 ) 416 wx.EndBusyCursor() 417 return False 418 419 gmDispatcher.send(signal = 'statustext', msg = _('Successfully exported progress notes into file [%s] for Medistar import.') % fname, beep=False) 420 421 wx.EndBusyCursor() 422 return True
423 #------------------------------------------------------------
424 -def select_narrative_from_episodes_new(parent=None, soap_cats=None):
425 """soap_cats needs to be a list""" 426 427 if parent is None: 428 parent = wx.GetApp().GetTopWindow() 429 430 pat = gmPerson.gmCurrentPatient() 431 emr = pat.get_emr() 432 433 selected_soap = {} 434 selected_narrative_pks = [] 435 436 #----------------------------------------------- 437 def pick_soap_from_episode(episode): 438 439 narr_for_epi = emr.get_clin_narrative(episodes = [episode['pk_episode']], soap_cats = soap_cats) 440 441 if len(narr_for_epi) == 0: 442 gmDispatcher.send(signal = 'statustext', msg = _('No narrative available for selected episode.')) 443 return True 444 445 dlg = cNarrativeListSelectorDlg ( 446 parent = parent, 447 id = -1, 448 narrative = narr_for_epi, 449 msg = _( 450 '\n This is the narrative (type %s) for the chosen episodes.\n' 451 '\n' 452 ' Now, mark the entries you want to include in your report.\n' 453 ) % u'/'.join([ gmClinNarrative.soap_cat2l10n[cat] for cat in gmTools.coalesce(soap_cats, list(u'soap')) ]) 454 ) 455 # selection_idxs = [] 456 # for idx in range(len(narr_for_epi)): 457 # if narr_for_epi[idx]['pk_narrative'] in selected_narrative_pks: 458 # selection_idxs.append(idx) 459 # if len(selection_idxs) != 0: 460 # dlg.set_selections(selections = selection_idxs) 461 btn_pressed = dlg.ShowModal() 462 selected_narr = dlg.get_selected_item_data() 463 dlg.Destroy() 464 465 if btn_pressed == wx.ID_CANCEL: 466 return True 467 468 selected_narrative_pks = [ i['pk_narrative'] for i in selected_narr ] 469 for narr in selected_narr: 470 selected_soap[narr['pk_narrative']] = narr 471 472 print "before returning from picking soap" 473 474 return True
475 #----------------------------------------------- 476 selected_episode_pks = [] 477 478 all_epis = [ epi for epi in emr.get_episodes() if epi.has_narrative ] 479 480 if len(all_epis) == 0: 481 gmDispatcher.send(signal = 'statustext', msg = _('No episodes recorded for the health issues selected.')) 482 return [] 483 484 dlg = gmEMRStructWidgets.cEpisodeListSelectorDlg ( 485 parent = parent, 486 id = -1, 487 episodes = all_epis, 488 msg = _('\n Select the the episode you want to report on.\n') 489 ) 490 # selection_idxs = [] 491 # for idx in range(len(all_epis)): 492 # if all_epis[idx]['pk_episode'] in selected_episode_pks: 493 # selection_idxs.append(idx) 494 # if len(selection_idxs) != 0: 495 # dlg.set_selections(selections = selection_idxs) 496 dlg.left_extra_button = ( 497 _('Pick SOAP'), 498 _('Pick SOAP entries from topmost selected episode'), 499 pick_soap_from_episode 500 ) 501 btn_pressed = dlg.ShowModal() 502 dlg.Destroy() 503 504 if btn_pressed == wx.ID_CANCEL: 505 return None 506 507 return selected_soap.values() 508 #------------------------------------------------------------
509 -def select_narrative_from_episodes(parent=None, soap_cats=None):
510 """soap_cats needs to be a list""" 511 512 pat = gmPerson.gmCurrentPatient() 513 emr = pat.get_emr() 514 515 if parent is None: 516 parent = wx.GetApp().GetTopWindow() 517 518 selected_soap = {} 519 selected_issue_pks = [] 520 selected_episode_pks = [] 521 selected_narrative_pks = [] 522 523 while 1: 524 # 1) select health issues to select episodes from 525 all_issues = emr.get_health_issues() 526 all_issues.insert(0, gmEMRStructItems.get_dummy_health_issue()) 527 dlg = gmEMRStructWidgets.cIssueListSelectorDlg ( 528 parent = parent, 529 id = -1, 530 issues = all_issues, 531 msg = _('\n In the list below mark the health issues you want to report on.\n') 532 ) 533 selection_idxs = [] 534 for idx in range(len(all_issues)): 535 if all_issues[idx]['pk_health_issue'] in selected_issue_pks: 536 selection_idxs.append(idx) 537 if len(selection_idxs) != 0: 538 dlg.set_selections(selections = selection_idxs) 539 btn_pressed = dlg.ShowModal() 540 selected_issues = dlg.get_selected_item_data() 541 dlg.Destroy() 542 543 if btn_pressed == wx.ID_CANCEL: 544 return selected_soap.values() 545 546 selected_issue_pks = [ i['pk_health_issue'] for i in selected_issues ] 547 548 while 1: 549 # 2) select episodes to select items from 550 all_epis = emr.get_episodes(issues = selected_issue_pks) 551 552 if len(all_epis) == 0: 553 gmDispatcher.send(signal = 'statustext', msg = _('No episodes recorded for the health issues selected.')) 554 break 555 556 dlg = gmEMRStructWidgets.cEpisodeListSelectorDlg ( 557 parent = parent, 558 id = -1, 559 episodes = all_epis, 560 msg = _( 561 '\n These are the episodes known for the health issues just selected.\n\n' 562 ' Now, mark the the episodes you want to report on.\n' 563 ) 564 ) 565 selection_idxs = [] 566 for idx in range(len(all_epis)): 567 if all_epis[idx]['pk_episode'] in selected_episode_pks: 568 selection_idxs.append(idx) 569 if len(selection_idxs) != 0: 570 dlg.set_selections(selections = selection_idxs) 571 btn_pressed = dlg.ShowModal() 572 selected_epis = dlg.get_selected_item_data() 573 dlg.Destroy() 574 575 if btn_pressed == wx.ID_CANCEL: 576 break 577 578 selected_episode_pks = [ i['pk_episode'] for i in selected_epis ] 579 580 # 3) select narrative corresponding to the above constraints 581 all_narr = emr.get_clin_narrative(episodes = selected_episode_pks, soap_cats = soap_cats) 582 583 if len(all_narr) == 0: 584 gmDispatcher.send(signal = 'statustext', msg = _('No narrative available for selected episodes.')) 585 continue 586 587 dlg = cNarrativeListSelectorDlg ( 588 parent = parent, 589 id = -1, 590 narrative = all_narr, 591 msg = _( 592 '\n This is the narrative (type %s) for the chosen episodes.\n\n' 593 ' Now, mark the entries you want to include in your report.\n' 594 ) % u'/'.join([ gmClinNarrative.soap_cat2l10n[cat] for cat in gmTools.coalesce(soap_cats, list(u'soap')) ]) 595 ) 596 selection_idxs = [] 597 for idx in range(len(all_narr)): 598 if all_narr[idx]['pk_narrative'] in selected_narrative_pks: 599 selection_idxs.append(idx) 600 if len(selection_idxs) != 0: 601 dlg.set_selections(selections = selection_idxs) 602 btn_pressed = dlg.ShowModal() 603 selected_narr = dlg.get_selected_item_data() 604 dlg.Destroy() 605 606 if btn_pressed == wx.ID_CANCEL: 607 continue 608 609 selected_narrative_pks = [ i['pk_narrative'] for i in selected_narr ] 610 for narr in selected_narr: 611 selected_soap[narr['pk_narrative']] = narr
612 #------------------------------------------------------------
613 -class cNarrativeListSelectorDlg(gmListWidgets.cGenericListSelectorDlg):
614
615 - def __init__(self, *args, **kwargs):
616 617 narrative = kwargs['narrative'] 618 del kwargs['narrative'] 619 620 gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs) 621 622 self.SetTitle(_('Select the narrative you are interested in ...')) 623 # FIXME: add epi/issue 624 self._LCTRL_items.set_columns([_('when'), _('who'), _('type'), _('entry')]) #, _('Episode'), u'', _('Health Issue')]) 625 # FIXME: date used should be date of encounter, not date_modified 626 self._LCTRL_items.set_string_items ( 627 items = [ [narr['date'].strftime('%x %H:%M'), narr['provider'], gmClinNarrative.soap_cat2l10n[narr['soap_cat']], narr['narrative'].replace('\n', '/').replace('\r', '/')] for narr in narrative ] 628 ) 629 self._LCTRL_items.set_column_widths() 630 self._LCTRL_items.set_data(data = narrative)
631 #------------------------------------------------------------ 632 from Gnumed.wxGladeWidgets import wxgMoveNarrativeDlg 633
634 -class cMoveNarrativeDlg(wxgMoveNarrativeDlg.wxgMoveNarrativeDlg):
635
636 - def __init__(self, *args, **kwargs):
637 638 self.encounter = kwargs['encounter'] 639 self.source_episode = kwargs['episode'] 640 del kwargs['encounter'] 641 del kwargs['episode'] 642 643 wxgMoveNarrativeDlg.wxgMoveNarrativeDlg.__init__(self, *args, **kwargs) 644 645 self.LBL_source_episode.SetLabel(u'%s%s' % (self.source_episode['description'], gmTools.coalesce(self.source_episode['health_issue'], u'', u' (%s)'))) 646 self.LBL_encounter.SetLabel('%s: %s %s - %s' % ( 647 self.encounter['started'].strftime('%x').decode(gmI18N.get_encoding()), 648 self.encounter['l10n_type'], 649 self.encounter['started'].strftime('%H:%M'), 650 self.encounter['last_affirmed'].strftime('%H:%M') 651 )) 652 pat = gmPerson.gmCurrentPatient() 653 emr = pat.get_emr() 654 narr = emr.get_clin_narrative(episodes=[self.source_episode['pk_episode']], encounters=[self.encounter['pk_encounter']]) 655 if len(narr) == 0: 656 narr = [{'narrative': _('There is no narrative for this episode in this encounter.')}] 657 self.LBL_narrative.SetLabel(u'\n'.join([n['narrative'] for n in narr]))
658 659 #------------------------------------------------------------
660 - def _on_move_button_pressed(self, event):
661 662 target_episode = self._PRW_episode_selector.GetData(can_create = False) 663 664 if target_episode is None: 665 gmDispatcher.send(signal='statustext', msg=_('Must select episode to move narrative to first.')) 666 # FIXME: set to pink 667 self._PRW_episode_selector.SetFocus() 668 return False 669 670 target_episode = gmEMRStructItems.cEpisode(aPK_obj=target_episode) 671 672 self.encounter.transfer_clinical_data ( 673 source_episode = self.source_episode, 674 target_episode = target_episode 675 ) 676 677 if self.IsModal(): 678 self.EndModal(wx.ID_OK) 679 else: 680 self.Close()
681 #============================================================ 682 from Gnumed.wxGladeWidgets import wxgSoapPluginPnl 683
684 -class cSoapPluginPnl(wxgSoapPluginPnl.wxgSoapPluginPnl, gmRegetMixin.cRegetOnPaintMixin):
685 """A panel for in-context editing of progress notes. 686 687 Expects to be used as a notebook page. 688 689 Left hand side: 690 - problem list (health issues and active episodes) 691 - previous notes 692 693 Right hand side: 694 - encounter details fields 695 - notebook with progress note editors 696 - visual progress notes 697 - hints 698 699 Listens to patient change signals, thus acts on the current patient. 700 """
701 - def __init__(self, *args, **kwargs):
702 703 wxgSoapPluginPnl.wxgSoapPluginPnl.__init__(self, *args, **kwargs) 704 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 705 706 self.__pat = gmPerson.gmCurrentPatient() 707 self.__patient_just_changed = False 708 self.__init_ui() 709 self.__reset_ui_content() 710 711 self.__register_interests()
712 #-------------------------------------------------------- 713 # public API 714 #--------------------------------------------------------
715 - def save_encounter(self):
716 717 if not self.__encounter_valid_for_save(): 718 return False 719 720 emr = self.__pat.get_emr() 721 enc = emr.active_encounter 722 723 enc['pk_type'] = self._PRW_encounter_type.GetData() 724 enc['started'] = self._PRW_encounter_start.GetData().get_pydt() 725 enc['last_affirmed'] = self._PRW_encounter_end.GetData().get_pydt() 726 rfe = self._TCTRL_rfe.GetValue().strip() 727 if len(rfe) == 0: 728 enc['reason_for_encounter'] = None 729 else: 730 enc['reason_for_encounter'] = rfe 731 aoe = self._TCTRL_aoe.GetValue().strip() 732 if len(aoe) == 0: 733 enc['assessment_of_encounter'] = None 734 else: 735 enc['assessment_of_encounter'] = aoe 736 737 enc.save_payload() 738 739 return True
740 #-------------------------------------------------------- 741 # internal helpers 742 #--------------------------------------------------------
743 - def __init_ui(self):
744 self._LCTRL_active_problems.set_columns([_('Last'), _('Problem'), _('In health issue')]) 745 self._LCTRL_active_problems.set_string_items() 746 747 self._splitter_main.SetSashGravity(0.5) 748 self._splitter_left.SetSashGravity(0.5) 749 750 splitter_size = self._splitter_main.GetSizeTuple()[0] 751 self._splitter_main.SetSashPosition(splitter_size * 3 / 10, True) 752 753 splitter_size = self._splitter_left.GetSizeTuple()[1] 754 self._splitter_left.SetSashPosition(splitter_size * 6 / 20, True) 755 756 self._NB_soap_editors.DeleteAllPages() 757 self._NB_soap_editors.MoveAfterInTabOrder(self._PRW_aoe_codes)
758 #--------------------------------------------------------
759 - def __reset_ui_content(self):
760 """Clear all information from input panel.""" 761 762 self._LCTRL_active_problems.set_string_items() 763 764 self._TCTRL_recent_notes.SetValue(u'') 765 self._SZR_recent_notes_staticbox.SetLabel(_('Most recent notes on selected problem')) 766 767 self._PRW_encounter_type.SetText(suppress_smarts = True) 768 self._PRW_encounter_start.SetText(suppress_smarts = True) 769 self._PRW_encounter_end.SetText(suppress_smarts = True) 770 self._TCTRL_rfe.SetValue(u'') 771 self._TCTRL_aoe.SetValue(u'') 772 773 self._NB_soap_editors.DeleteAllPages() 774 self._NB_soap_editors.add_editor()
775 #--------------------------------------------------------
776 - def __refresh_problem_list(self):
777 """Update health problems list.""" 778 779 self._LCTRL_active_problems.set_string_items() 780 781 emr = self.__pat.get_emr() 782 problems = emr.get_problems ( 783 include_closed_episodes = self._CHBOX_show_closed_episodes.IsChecked(), 784 include_irrelevant_issues = self._CHBOX_irrelevant_issues.IsChecked() 785 ) 786 787 list_items = [] 788 active_problems = [] 789 for problem in problems: 790 if not problem['problem_active']: 791 if not problem['is_potential_problem']: 792 continue 793 794 active_problems.append(problem) 795 796 if problem['type'] == 'issue': 797 issue = emr.problem2issue(problem) 798 last_encounter = emr.get_last_encounter(issue_id = issue['pk_health_issue']) 799 if last_encounter is None: 800 last = issue['modified_when'].strftime('%m/%Y') 801 else: 802 last = last_encounter['last_affirmed'].strftime('%m/%Y') 803 804 list_items.append([last, problem['problem'], gmTools.u_down_left_arrow]) #gmTools.u_left_arrow 805 806 elif problem['type'] == 'episode': 807 epi = emr.problem2episode(problem) 808 last_encounter = emr.get_last_encounter(episode_id = epi['pk_episode']) 809 if last_encounter is None: 810 last = epi['episode_modified_when'].strftime('%m/%Y') 811 else: 812 last = last_encounter['last_affirmed'].strftime('%m/%Y') 813 814 list_items.append ([ 815 last, 816 problem['problem'], 817 gmTools.coalesce(initial = epi['health_issue'], instead = u'?') #gmTools.u_diameter 818 ]) 819 820 self._LCTRL_active_problems.set_string_items(items = list_items) 821 self._LCTRL_active_problems.set_column_widths() 822 self._LCTRL_active_problems.set_data(data = active_problems) 823 824 showing_potential_problems = ( 825 self._CHBOX_show_closed_episodes.IsChecked() 826 or 827 self._CHBOX_irrelevant_issues.IsChecked() 828 ) 829 if showing_potential_problems: 830 self._SZR_problem_list_staticbox.SetLabel(_('%s (active+potential) problems') % len(list_items)) 831 else: 832 self._SZR_problem_list_staticbox.SetLabel(_('%s active problems') % len(list_items)) 833 834 return True
835 #--------------------------------------------------------
836 - def __get_soap_for_issue_problem(self, problem=None):
837 soap = u'' 838 emr = self.__pat.get_emr() 839 prev_enc = emr.get_last_but_one_encounter(issue_id = problem['pk_health_issue']) 840 if prev_enc is not None: 841 soap += prev_enc.format ( 842 issues = [ problem['pk_health_issue'] ], 843 with_soap = True, 844 with_docs = False, 845 with_tests = False, 846 patient = self.__pat, 847 fancy_header = False, 848 with_rfe_aoe = True 849 ) 850 851 tmp = emr.active_encounter.format_soap ( 852 soap_cats = 'soap', 853 emr = emr, 854 issues = [ problem['pk_health_issue'] ], 855 ) 856 if len(tmp) > 0: 857 soap += _('Current encounter:') + u'\n' 858 soap += u'\n'.join(tmp) + u'\n' 859 860 if problem['summary'] is not None: 861 soap += u'\n-- %s ----------\n%s' % ( 862 _('Cumulative summary'), 863 gmTools.wrap ( 864 text = problem['summary'], 865 width = 45, 866 initial_indent = u' ', 867 subsequent_indent = u' ' 868 ).strip('\n') 869 ) 870 871 return soap
872 #--------------------------------------------------------
873 - def __get_soap_for_episode_problem(self, problem=None):
874 soap = u'' 875 emr = self.__pat.get_emr() 876 prev_enc = emr.get_last_but_one_encounter(episode_id = problem['pk_episode']) 877 if prev_enc is not None: 878 soap += prev_enc.format ( 879 episodes = [ problem['pk_episode'] ], 880 with_soap = True, 881 with_docs = False, 882 with_tests = False, 883 patient = self.__pat, 884 fancy_header = False, 885 with_rfe_aoe = True 886 ) 887 else: 888 if problem['pk_health_issue'] is not None: 889 prev_enc = emr.get_last_but_one_encounter(episode_id = problem['pk_health_issue']) 890 if prev_enc is not None: 891 soap += prev_enc.format ( 892 with_soap = True, 893 with_docs = False, 894 with_tests = False, 895 patient = self.__pat, 896 issues = [ problem['pk_health_issue'] ], 897 fancy_header = False, 898 with_rfe_aoe = True 899 ) 900 901 tmp = emr.active_encounter.format_soap ( 902 soap_cats = 'soap', 903 emr = emr, 904 issues = [ problem['pk_health_issue'] ], 905 ) 906 if len(tmp) > 0: 907 soap += _('Current encounter:') + u'\n' 908 soap += u'\n'.join(tmp) + u'\n' 909 910 if problem['summary'] is not None: 911 soap += u'\n-- %s ----------\n%s' % ( 912 _('Cumulative summary'), 913 gmTools.wrap ( 914 text = problem['summary'], 915 width = 45, 916 initial_indent = u' ', 917 subsequent_indent = u' ' 918 ).strip('\n') 919 ) 920 921 return soap
922 #--------------------------------------------------------
923 - def __refresh_current_editor(self):
924 self._NB_soap_editors.refresh_current_editor()
925 #--------------------------------------------------------
927 if not self.__patient_just_changed: 928 return 929 930 dbcfg = gmCfg.cCfgSQL() 931 auto_open_recent_problems = bool(dbcfg.get2 ( 932 option = u'horstspace.soap_editor.auto_open_latest_episodes', 933 workplace = gmSurgery.gmCurrentPractice().active_workplace, 934 bias = u'user', 935 default = True 936 )) 937 938 self.__patient_just_changed = False 939 emr = self.__pat.get_emr() 940 recent_epis = emr.active_encounter.get_episodes() 941 prev_enc = emr.get_last_but_one_encounter() 942 if prev_enc is not None: 943 recent_epis.extend(prev_enc.get_episodes()) 944 945 for epi in recent_epis: 946 if not epi['episode_open']: 947 continue 948 self._NB_soap_editors.add_editor(problem = epi, allow_same_problem = False)
949 #--------------------------------------------------------
950 - def __refresh_recent_notes(self, problem=None):
951 """This refreshes the recent-notes part.""" 952 953 soap = u'' 954 caption = u'<?>' 955 956 if problem['type'] == u'issue': 957 caption = problem['problem'][:35] 958 soap = self.__get_soap_for_issue_problem(problem = problem) 959 960 elif problem['type'] == u'episode': 961 caption = problem['problem'][:35] 962 soap = self.__get_soap_for_episode_problem(problem = problem) 963 964 self._TCTRL_recent_notes.SetValue(soap) 965 self._TCTRL_recent_notes.ShowPosition(self._TCTRL_recent_notes.GetLastPosition()) 966 self._SZR_recent_notes_staticbox.SetLabel(_('Most recent notes on %s%s%s') % ( 967 gmTools.u_left_double_angle_quote, 968 caption, 969 gmTools.u_right_double_angle_quote 970 )) 971 972 self._TCTRL_recent_notes.Refresh() 973 974 return True
975 #--------------------------------------------------------
976 - def __refresh_encounter(self):
977 """Update encounter fields.""" 978 979 emr = self.__pat.get_emr() 980 enc = emr.active_encounter 981 self._PRW_encounter_type.SetText(value = enc['l10n_type'], data = enc['pk_type']) 982 983 fts = gmDateTime.cFuzzyTimestamp ( 984 timestamp = enc['started'], 985 accuracy = gmDateTime.acc_minutes 986 ) 987 self._PRW_encounter_start.SetText(fts.format_accurately(), data = fts) 988 989 fts = gmDateTime.cFuzzyTimestamp ( 990 timestamp = enc['last_affirmed'], 991 accuracy = gmDateTime.acc_minutes 992 ) 993 self._PRW_encounter_end.SetText(fts.format_accurately(), data = fts) 994 995 self._TCTRL_rfe.SetValue(gmTools.coalesce(enc['reason_for_encounter'], u'')) 996 val, data = self._PRW_rfe_codes.generic_linked_codes2item_dict(enc.generic_codes_rfe) 997 self._PRW_rfe_codes.SetText(val, data) 998 999 self._TCTRL_aoe.SetValue(gmTools.coalesce(enc['assessment_of_encounter'], u'')) 1000 val, data = self._PRW_aoe_codes.generic_linked_codes2item_dict(enc.generic_codes_aoe) 1001 self._PRW_aoe_codes.SetText(val, data) 1002 1003 self._PRW_encounter_type.Refresh() 1004 self._PRW_encounter_start.Refresh() 1005 self._PRW_encounter_end.Refresh() 1006 self._TCTRL_rfe.Refresh() 1007 self._PRW_rfe_codes.Refresh() 1008 self._TCTRL_aoe.Refresh() 1009 self._PRW_aoe_codes.Refresh()
1010 #--------------------------------------------------------
1011 - def __encounter_modified(self):
1012 """Assumes that the field data is valid.""" 1013 1014 emr = self.__pat.get_emr() 1015 enc = emr.active_encounter 1016 1017 data = { 1018 'pk_type': self._PRW_encounter_type.GetData(), 1019 'reason_for_encounter': gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u''), 1020 'assessment_of_encounter': gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u''), 1021 'pk_location': enc['pk_location'], 1022 'pk_patient': enc['pk_patient'] 1023 } 1024 1025 if self._PRW_encounter_start.GetData() is None: 1026 data['started'] = None 1027 else: 1028 data['started'] = self._PRW_encounter_start.GetData().get_pydt() 1029 1030 if self._PRW_encounter_end.GetData() is None: 1031 data['last_affirmed'] = None 1032 else: 1033 data['last_affirmed'] = self._PRW_encounter_end.GetData().get_pydt() 1034 1035 return not enc.same_payload(another_object = data)
1036 #--------------------------------------------------------
1037 - def __encounter_valid_for_save(self):
1038 1039 found_error = False 1040 1041 if self._PRW_encounter_type.GetData() is None: 1042 found_error = True 1043 msg = _('Cannot save encounter: missing type.') 1044 1045 if self._PRW_encounter_start.GetData() is None: 1046 found_error = True 1047 msg = _('Cannot save encounter: missing start time.') 1048 1049 if self._PRW_encounter_end.GetData() is None: 1050 found_error = True 1051 msg = _('Cannot save encounter: missing end time.') 1052 1053 if found_error: 1054 gmDispatcher.send(signal = 'statustext', msg = msg, beep = True) 1055 return False 1056 1057 return True
1058 #-------------------------------------------------------- 1059 # event handling 1060 #--------------------------------------------------------
1061 - def __register_interests(self):
1062 """Configure enabled event signals.""" 1063 # client internal signals 1064 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection) 1065 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection) 1066 gmDispatcher.connect(signal = u'episode_mod_db', receiver = self._on_episode_issue_mod_db) 1067 gmDispatcher.connect(signal = u'health_issue_mod_db', receiver = self._on_episode_issue_mod_db) 1068 gmDispatcher.connect(signal = u'episode_code_mod_db', receiver = self._on_episode_issue_mod_db) 1069 gmDispatcher.connect(signal = u'doc_mod_db', receiver = self._on_doc_mod_db) 1070 gmDispatcher.connect(signal = u'current_encounter_modified', receiver = self._on_current_encounter_modified) 1071 gmDispatcher.connect(signal = u'current_encounter_switched', receiver = self._on_current_encounter_switched) 1072 gmDispatcher.connect(signal = u'rfe_code_mod_db', receiver = self._on_current_encounter_modified) 1073 gmDispatcher.connect(signal = u'aoe_code_mod_db', receiver = self._on_current_encounter_modified) 1074 1075 # synchronous signals 1076 self.__pat.register_pre_selection_callback(callback = self._pre_selection_callback) 1077 gmDispatcher.send(signal = u'register_pre_exit_callback', callback = self._pre_exit_callback)
1078 #--------------------------------------------------------
1079 - def _pre_selection_callback(self):
1080 """Another patient is about to be activated. 1081 1082 Patient change will not proceed before this returns True. 1083 """ 1084 # don't worry about the encounter here - it will be offered 1085 # for editing higher up if anything was saved to the EMR 1086 if not self.__pat.connected: 1087 return True 1088 return self._NB_soap_editors.warn_on_unsaved_soap()
1089 #--------------------------------------------------------
1090 - def _pre_exit_callback(self):
1091 """The client is about to be shut down. 1092 1093 Shutdown will not proceed before this returns. 1094 """ 1095 if not self.__pat.connected: 1096 return True 1097 1098 # if self.__encounter_modified(): 1099 # do_save_enc = gmGuiHelpers.gm_show_question ( 1100 # aMessage = _( 1101 # 'You have modified the details\n' 1102 # 'of the current encounter.\n' 1103 # '\n' 1104 # 'Do you want to save those changes ?' 1105 # ), 1106 # aTitle = _('Starting new encounter') 1107 # ) 1108 # if do_save_enc: 1109 # if not self.save_encounter(): 1110 # gmDispatcher.send(signal = u'statustext', msg = _('Error saving current encounter.'), beep = True) 1111 1112 emr = self.__pat.get_emr() 1113 saved = self._NB_soap_editors.save_all_editors ( 1114 emr = emr, 1115 episode_name_candidates = [ 1116 gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u''), 1117 gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u'') 1118 ] 1119 ) 1120 if not saved: 1121 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save all editors. Some were kept open.'), beep = True) 1122 return True
1123 #--------------------------------------------------------
1124 - def _on_pre_patient_selection(self):
1125 wx.CallAfter(self.__on_pre_patient_selection)
1126 #--------------------------------------------------------
1127 - def __on_pre_patient_selection(self):
1128 self.__reset_ui_content()
1129 #--------------------------------------------------------
1130 - def _on_post_patient_selection(self):
1131 wx.CallAfter(self._schedule_data_reget) 1132 self.__patient_just_changed = True
1133 #--------------------------------------------------------
1134 - def _on_doc_mod_db(self):
1135 wx.CallAfter(self.__refresh_current_editor)
1136 #--------------------------------------------------------
1137 - def _on_episode_issue_mod_db(self):
1138 wx.CallAfter(self._schedule_data_reget)
1139 #--------------------------------------------------------
1141 wx.CallAfter(self.__refresh_encounter)
1142 #--------------------------------------------------------
1144 wx.CallAfter(self.__on_current_encounter_switched)
1145 #--------------------------------------------------------
1147 self.__refresh_encounter()
1148 #-------------------------------------------------------- 1149 # problem list specific events 1150 #--------------------------------------------------------
1151 - def _on_problem_focused(self, event):
1152 """Show related note at the bottom.""" 1153 pass
1154 #--------------------------------------------------------
1155 - def _on_problem_rclick(self, event):
1156 problem = self._LCTRL_active_problems.get_selected_item_data(only_one = True) 1157 if problem['type'] == u'issue': 1158 gmEMRStructWidgets.edit_health_issue(parent = self, issue = problem.get_as_health_issue()) 1159 return 1160 1161 if problem['type'] == u'episode': 1162 gmEMRStructWidgets.edit_episode(parent = self, episode = problem.get_as_episode()) 1163 return 1164 1165 event.Skip()
1166 #--------------------------------------------------------
1167 - def _on_problem_selected(self, event):
1168 """Show related note at the bottom.""" 1169 emr = self.__pat.get_emr() 1170 self.__refresh_recent_notes ( 1171 problem = self._LCTRL_active_problems.get_selected_item_data(only_one = True) 1172 )
1173 #--------------------------------------------------------
1174 - def _on_problem_activated(self, event):
1175 """Open progress note editor for this problem. 1176 """ 1177 problem = self._LCTRL_active_problems.get_selected_item_data(only_one = True) 1178 if problem is None: 1179 return True 1180 1181 dbcfg = gmCfg.cCfgSQL() 1182 allow_duplicate_editors = bool(dbcfg.get2 ( 1183 option = u'horstspace.soap_editor.allow_same_episode_multiple_times', 1184 workplace = gmSurgery.gmCurrentPractice().active_workplace, 1185 bias = u'user', 1186 default = False 1187 )) 1188 if self._NB_soap_editors.add_editor(problem = problem, allow_same_problem = allow_duplicate_editors): 1189 return True 1190 1191 gmGuiHelpers.gm_show_error ( 1192 aMessage = _( 1193 'Cannot open progress note editor for\n\n' 1194 '[%s].\n\n' 1195 ) % problem['problem'], 1196 aTitle = _('opening progress note editor') 1197 ) 1198 event.Skip() 1199 return False
1200 #--------------------------------------------------------
1201 - def _on_show_closed_episodes_checked(self, event):
1202 self.__refresh_problem_list()
1203 #--------------------------------------------------------
1204 - def _on_irrelevant_issues_checked(self, event):
1205 self.__refresh_problem_list()
1206 #-------------------------------------------------------- 1207 # SOAP editor specific buttons 1208 #--------------------------------------------------------
1209 - def _on_discard_editor_button_pressed(self, event):
1210 self._NB_soap_editors.close_current_editor() 1211 event.Skip()
1212 #--------------------------------------------------------
1213 - def _on_new_editor_button_pressed(self, event):
1214 self._NB_soap_editors.add_editor() 1215 event.Skip()
1216 #--------------------------------------------------------
1217 - def _on_clear_editor_button_pressed(self, event):
1218 self._NB_soap_editors.clear_current_editor() 1219 event.Skip()
1220 #--------------------------------------------------------
1221 - def _on_save_note_button_pressed(self, event):
1222 emr = self.__pat.get_emr() 1223 self._NB_soap_editors.save_current_editor ( 1224 emr = emr, 1225 episode_name_candidates = [ 1226 gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u''), 1227 gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u'') 1228 ] 1229 ) 1230 event.Skip()
1231 #--------------------------------------------------------
1232 - def _on_save_note_under_button_pressed(self, event):
1233 encounters = gmEMRStructWidgets.select_encounters ( 1234 parent = self, 1235 patient = self.__pat, 1236 single_selection = True 1237 ) 1238 # cancelled: 1239 if encounters is None: 1240 return 1241 if len(encounters) == 0: 1242 return 1243 1244 emr = self.__pat.get_emr() 1245 self._NB_soap_editors.save_current_editor ( 1246 emr = emr, 1247 encounter = encounter['pk_encounter'], 1248 episode_name_candidates = [ 1249 gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u''), 1250 gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u'') 1251 ] 1252 ) 1253 event.Skip()
1254 #--------------------------------------------------------
1255 - def _on_image_button_pressed(self, event):
1256 emr = self.__pat.get_emr() 1257 self._NB_soap_editors.add_visual_progress_note_to_current_problem() 1258 event.Skip()
1259 #-------------------------------------------------------- 1260 # encounter specific buttons 1261 #--------------------------------------------------------
1262 - def _on_save_encounter_button_pressed(self, event):
1263 self.save_encounter() 1264 event.Skip()
1265 #--------------------------------------------------------
1266 - def _on_new_encounter_button_pressed(self, event):
1267 1268 if self.__encounter_modified(): 1269 do_save_enc = gmGuiHelpers.gm_show_question ( 1270 aMessage = _( 1271 'You have modified the details\n' 1272 'of the current encounter.\n' 1273 '\n' 1274 'Do you want to save those changes ?' 1275 ), 1276 aTitle = _('Starting new encounter') 1277 ) 1278 if do_save_enc: 1279 if not self.save_encounter(): 1280 gmDispatcher.send(signal = u'statustext', msg = _('Error saving current encounter.'), beep = True) 1281 return False 1282 1283 emr = self.__pat.get_emr() 1284 gmDispatcher.send(signal = u'statustext', msg = _('Started new encounter for active patient.'), beep = True) 1285 1286 event.Skip() 1287 1288 wx.CallAfter(gmEMRStructWidgets.start_new_encounter, emr = emr)
1289 #-------------------------------------------------------- 1290 # other buttons 1291 #--------------------------------------------------------
1292 - def _on_save_all_button_pressed(self, event):
1293 self.save_encounter() 1294 time.sleep(0.3) 1295 event.Skip() 1296 wx.SafeYield() 1297 1298 wx.CallAfter(self._save_all_button_pressed_bottom_half) 1299 wx.SafeYield()
1300 #--------------------------------------------------------
1302 emr = self.__pat.get_emr() 1303 saved = self._NB_soap_editors.save_all_editors ( 1304 emr = emr, 1305 episode_name_candidates = [ 1306 gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u''), 1307 gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u'') 1308 ] 1309 ) 1310 if not saved: 1311 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save all editors. Some were kept open.'), beep = True)
1312 #-------------------------------------------------------- 1313 # reget mixin API 1314 #--------------------------------------------------------
1315 - def _populate_with_data(self):
1316 self.__refresh_problem_list() 1317 self.__refresh_encounter() 1318 self.__setup_initial_patient_editors() 1319 return True
1320 #============================================================
1321 -class cSoapNoteInputNotebook(wx.Notebook):
1322 """A notebook holding panels with progress note editors. 1323 1324 There can be one or several progress note editor panel 1325 for each episode being worked on. The editor class in 1326 each panel is configurable. 1327 1328 There will always be one open editor. 1329 """
1330 - def __init__(self, *args, **kwargs):
1331 1332 kwargs['style'] = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER 1333 1334 wx.Notebook.__init__(self, *args, **kwargs)
1335 #-------------------------------------------------------- 1336 # public API 1337 #--------------------------------------------------------
1338 - def add_editor(self, problem=None, allow_same_problem=False):
1339 """Add a progress note editor page. 1340 1341 The way <allow_same_problem> is currently used in callers 1342 it only applies to unassociated episodes. 1343 """ 1344 problem_to_add = problem 1345 1346 # determine label 1347 if problem_to_add is None: 1348 label = _('new problem') 1349 else: 1350 # normalize problem type 1351 if isinstance(problem_to_add, gmEMRStructItems.cEpisode): 1352 problem_to_add = gmEMRStructItems.episode2problem(episode = problem_to_add) 1353 1354 elif isinstance(problem_to_add, gmEMRStructItems.cHealthIssue): 1355 problem_to_add = gmEMRStructItems.health_issue2problem(episode = problem_to_add) 1356 1357 if not isinstance(problem_to_add, gmEMRStructItems.cProblem): 1358 raise TypeError('cannot open progress note editor for [%s]' % problem_to_add) 1359 1360 label = problem_to_add['problem'] 1361 # FIXME: configure maximum length 1362 if len(label) > 23: 1363 label = label[:21] + gmTools.u_ellipsis 1364 1365 # new unassociated problem or dupes allowed 1366 if (problem_to_add is None) or allow_same_problem: 1367 new_page = cSoapNoteExpandoEditAreaPnl(parent = self, id = -1, problem = problem_to_add) 1368 result = self.AddPage ( 1369 page = new_page, 1370 text = label, 1371 select = True 1372 ) 1373 return result 1374 1375 # real problem, no dupes allowed 1376 # - raise existing editor 1377 for page_idx in range(self.GetPageCount()): 1378 page = self.GetPage(page_idx) 1379 1380 # editor is for unassociated new problem 1381 if page.problem is None: 1382 continue 1383 1384 # editor is for episode 1385 if page.problem['type'] == 'episode': 1386 if page.problem['pk_episode'] == problem_to_add['pk_episode']: 1387 self.SetSelection(page_idx) 1388 gmDispatcher.send(signal = u'statustext', msg = u'Raising existing editor.', beep = True) 1389 return True 1390 continue 1391 1392 # editor is for health issue 1393 if page.problem['type'] == 'issue': 1394 if page.problem['pk_health_issue'] == problem_to_add['pk_health_issue']: 1395 self.SetSelection(page_idx) 1396 gmDispatcher.send(signal = u'statustext', msg = u'Raising existing editor.', beep = True) 1397 return True 1398 continue 1399 1400 # - or add new editor 1401 new_page = cSoapNoteExpandoEditAreaPnl(parent = self, id = -1, problem = problem_to_add) 1402 result = self.AddPage ( 1403 page = new_page, 1404 text = label, 1405 select = True 1406 ) 1407 1408 return result
1409 #--------------------------------------------------------
1410 - def close_current_editor(self):
1411 1412 page_idx = self.GetSelection() 1413 page = self.GetPage(page_idx) 1414 1415 if not page.empty: 1416 really_discard = gmGuiHelpers.gm_show_question ( 1417 _('Are you sure you really want to\n' 1418 'discard this progress note ?\n' 1419 ), 1420 _('Discarding progress note') 1421 ) 1422 if really_discard is False: 1423 return 1424 1425 self.DeletePage(page_idx) 1426 1427 # always keep one unassociated editor open 1428 if self.GetPageCount() == 0: 1429 self.add_editor()
1430 #--------------------------------------------------------
1431 - def save_current_editor(self, emr=None, episode_name_candidates=None, encounter=None):
1432 1433 page_idx = self.GetSelection() 1434 page = self.GetPage(page_idx) 1435 1436 if not page.save(emr = emr, episode_name_candidates = episode_name_candidates, encounter = encounter): 1437 return 1438 1439 self.DeletePage(page_idx) 1440 1441 # always keep one unassociated editor open 1442 if self.GetPageCount() == 0: 1443 self.add_editor()
1444 #--------------------------------------------------------
1445 - def warn_on_unsaved_soap(self):
1446 for page_idx in range(self.GetPageCount()): 1447 page = self.GetPage(page_idx) 1448 if page.empty: 1449 continue 1450 1451 gmGuiHelpers.gm_show_warning ( 1452 _('There are unsaved progress notes !\n'), 1453 _('Unsaved progress notes') 1454 ) 1455 return False 1456 1457 return True
1458 #--------------------------------------------------------
1459 - def save_all_editors(self, emr=None, episode_name_candidates=None):
1460 1461 _log.debug('saving editors: %s', self.GetPageCount()) 1462 1463 all_closed = True 1464 for page_idx in range((self.GetPageCount() - 1), -1, -1): 1465 _log.debug('#%s of %s', page_idx, self.GetPageCount()) 1466 try: 1467 self.ChangeSelection(page_idx) 1468 _log.debug('editor raised') 1469 except: 1470 _log.exception('cannot raise editor') 1471 page = self.GetPage(page_idx) 1472 if page.save(emr = emr, episode_name_candidates = episode_name_candidates): 1473 _log.debug('saved, deleting now') 1474 self.DeletePage(page_idx) 1475 else: 1476 _log.debug('not saved, not deleting') 1477 all_closed = False 1478 1479 # always keep one unassociated editor open 1480 if self.GetPageCount() == 0: 1481 self.add_editor() 1482 1483 return (all_closed is True)
1484 #--------------------------------------------------------
1485 - def clear_current_editor(self):
1486 page_idx = self.GetSelection() 1487 page = self.GetPage(page_idx) 1488 page.clear()
1489 #--------------------------------------------------------
1490 - def get_current_problem(self):
1491 page_idx = self.GetSelection() 1492 page = self.GetPage(page_idx) 1493 return page.problem
1494 #--------------------------------------------------------
1495 - def refresh_current_editor(self):
1496 page_idx = self.GetSelection() 1497 page = self.GetPage(page_idx) 1498 page.refresh()
1499 #--------------------------------------------------------
1501 page_idx = self.GetSelection() 1502 page = self.GetPage(page_idx) 1503 page.add_visual_progress_note()
1504 #============================================================ 1505 from Gnumed.wxGladeWidgets import wxgSoapNoteExpandoEditAreaPnl 1506
1507 -class cSoapNoteExpandoEditAreaPnl(wxgSoapNoteExpandoEditAreaPnl.wxgSoapNoteExpandoEditAreaPnl):
1508 """An Edit Area like panel for entering progress notes. 1509 1510 Subjective: Codes: 1511 expando text ctrl 1512 Objective: Codes: 1513 expando text ctrl 1514 Assessment: Codes: 1515 expando text ctrl 1516 Plan: Codes: 1517 expando text ctrl 1518 visual progress notes 1519 panel with images 1520 Episode summary: Codes: 1521 text ctrl 1522 1523 - knows the problem this edit area is about 1524 - can deal with issue or episode type problems 1525 """ 1526
1527 - def __init__(self, *args, **kwargs):
1528 1529 try: 1530 self.problem = kwargs['problem'] 1531 del kwargs['problem'] 1532 except KeyError: 1533 self.problem = None 1534 1535 wxgSoapNoteExpandoEditAreaPnl.wxgSoapNoteExpandoEditAreaPnl.__init__(self, *args, **kwargs) 1536 1537 self.soap_fields = [ 1538 self._TCTRL_Soap, 1539 self._TCTRL_sOap, 1540 self._TCTRL_soAp, 1541 self._TCTRL_soaP 1542 ] 1543 1544 self.__init_ui() 1545 self.__register_interests()
1546 #--------------------------------------------------------
1547 - def __init_ui(self):
1548 self.refresh_summary() 1549 if self.problem is not None: 1550 if self.problem['summary'] is None: 1551 self._TCTRL_episode_summary.SetValue(u'') 1552 self.refresh_visual_soap()
1553 #--------------------------------------------------------
1554 - def refresh(self):
1555 self.refresh_summary() 1556 self.refresh_visual_soap()
1557 #--------------------------------------------------------
1558 - def refresh_summary(self):
1559 self._TCTRL_episode_summary.SetValue(u'') 1560 self._PRW_episode_codes.SetText(u'', self._PRW_episode_codes.list2data_dict([])) 1561 self._LBL_summary.SetLabel(_('Episode summary')) 1562 1563 # new problem ? 1564 if self.problem is None: 1565 return 1566 1567 # issue-level problem ? 1568 if self.problem['type'] == u'issue': 1569 return 1570 1571 # episode-level problem 1572 caption = _(u'Summary (%s)') % ( 1573 gmDateTime.pydt_strftime ( 1574 self.problem['modified_when'], 1575 format = '%B %Y', 1576 accuracy = gmDateTime.acc_days 1577 ) 1578 ) 1579 self._LBL_summary.SetLabel(caption) 1580 1581 if self.problem['summary'] is not None: 1582 self._TCTRL_episode_summary.SetValue(self.problem['summary'].strip()) 1583 1584 val, data = self._PRW_episode_codes.generic_linked_codes2item_dict(self.problem.generic_codes) 1585 self._PRW_episode_codes.SetText(val, data)
1586 #--------------------------------------------------------
1587 - def refresh_visual_soap(self):
1588 if self.problem is None: 1589 self._PNL_visual_soap.refresh(document_folder = None) 1590 return 1591 1592 if self.problem['type'] == u'issue': 1593 self._PNL_visual_soap.refresh(document_folder = None) 1594 return 1595 1596 if self.problem['type'] == u'episode': 1597 pat = gmPerson.gmCurrentPatient() 1598 doc_folder = pat.get_document_folder() 1599 emr = pat.get_emr() 1600 self._PNL_visual_soap.refresh ( 1601 document_folder = doc_folder, 1602 episodes = [self.problem['pk_episode']], 1603 encounter = emr.active_encounter['pk_encounter'] 1604 ) 1605 return
1606 #--------------------------------------------------------
1607 - def clear(self):
1608 for field in self.soap_fields: 1609 field.SetValue(u'') 1610 self._TCTRL_episode_summary.SetValue(u'') 1611 self._LBL_summary.SetLabel(_('Episode summary')) 1612 self._PRW_episode_codes.SetText(u'', self._PRW_episode_codes.list2data_dict([])) 1613 self._PNL_visual_soap.clear()
1614 #--------------------------------------------------------
1615 - def add_visual_progress_note(self):
1616 fname, discard_unmodified = select_visual_progress_note_template(parent = self) 1617 if fname is None: 1618 return False 1619 1620 if self.problem is None: 1621 issue = None 1622 episode = None 1623 elif self.problem['type'] == 'issue': 1624 issue = self.problem['pk_health_issue'] 1625 episode = None 1626 else: 1627 issue = self.problem['pk_health_issue'] 1628 episode = gmEMRStructItems.problem2episode(self.problem) 1629 1630 wx.CallAfter ( 1631 edit_visual_progress_note, 1632 filename = fname, 1633 episode = episode, 1634 discard_unmodified = discard_unmodified, 1635 health_issue = issue 1636 )
1637 #--------------------------------------------------------
1638 - def save(self, emr=None, episode_name_candidates=None, encounter=None):
1639 1640 if self.empty: 1641 return True 1642 1643 # new episode (standalone=unassociated or new-in-issue) 1644 if (self.problem is None) or (self.problem['type'] == 'issue'): 1645 episode = self.__create_new_episode(emr = emr, episode_name_candidates = episode_name_candidates) 1646 # existing episode 1647 else: 1648 episode = emr.problem2episode(self.problem) 1649 1650 if encounter is None: 1651 encounter = emr.current_encounter['pk_encounter'] 1652 1653 soap_notes = [] 1654 for note in self.soap: 1655 saved, data = gmClinNarrative.create_clin_narrative ( 1656 soap_cat = note[0], 1657 narrative = note[1], 1658 episode_id = episode['pk_episode'], 1659 encounter_id = encounter 1660 ) 1661 if saved: 1662 soap_notes.append(data) 1663 1664 # codes per narrative ! 1665 # for note in soap_notes: 1666 # if note['soap_cat'] == u's': 1667 # codes = self._PRW_Soap_codes 1668 # elif note['soap_cat'] == u'o': 1669 # elif note['soap_cat'] == u'a': 1670 # elif note['soap_cat'] == u'p': 1671 1672 # set summary but only if not already set above for a 1673 # newly created episode (either standalone or within 1674 # a health issue) 1675 if self.problem is not None: 1676 if self.problem['type'] == 'episode': 1677 episode['summary'] = self._TCTRL_episode_summary.GetValue().strip() 1678 episode.save() 1679 1680 # codes for episode 1681 episode.generic_codes = [ d['data'] for d in self._PRW_episode_codes.GetData() ] 1682 1683 return True
1684 #-------------------------------------------------------- 1685 # internal helpers 1686 #--------------------------------------------------------
1687 - def __create_new_episode(self, emr=None, episode_name_candidates=None):
1688 1689 episode_name_candidates.append(self._TCTRL_episode_summary.GetValue().strip()) 1690 for candidate in episode_name_candidates: 1691 if candidate is None: 1692 continue 1693 epi_name = candidate.strip().replace('\r', '//').replace('\n', '//') 1694 break 1695 1696 dlg = wx.TextEntryDialog ( 1697 parent = self, 1698 message = _('Enter a short working name for this new problem:'), 1699 caption = _('Creating a problem (episode) to save the notelet under ...'), 1700 defaultValue = epi_name, 1701 style = wx.OK | wx.CANCEL | wx.CENTRE 1702 ) 1703 decision = dlg.ShowModal() 1704 if decision != wx.ID_OK: 1705 return None 1706 1707 epi_name = dlg.GetValue().strip() 1708 if epi_name == u'': 1709 gmGuiHelpers.gm_show_error(_('Cannot save a new problem without a name.'), _('saving progress note')) 1710 return None 1711 1712 # create episode 1713 new_episode = emr.add_episode(episode_name = epi_name[:45], pk_health_issue = None, is_open = True) 1714 new_episode['summary'] = self._TCTRL_episode_summary.GetValue().strip() 1715 new_episode.save() 1716 1717 if self.problem is not None: 1718 issue = emr.problem2issue(self.problem) 1719 if not gmEMRStructWidgets.move_episode_to_issue(episode = new_episode, target_issue = issue, save_to_backend = True): 1720 gmGuiHelpers.gm_show_warning ( 1721 _( 1722 'The new episode:\n' 1723 '\n' 1724 ' "%s"\n' 1725 '\n' 1726 'will remain unassociated despite the editor\n' 1727 'having been invoked from the health issue:\n' 1728 '\n' 1729 ' "%s"' 1730 ) % ( 1731 new_episode['description'], 1732 issue['description'] 1733 ), 1734 _('saving progress note') 1735 ) 1736 1737 return new_episode
1738 #-------------------------------------------------------- 1739 # event handling 1740 #--------------------------------------------------------
1741 - def __register_interests(self):
1742 for field in self.soap_fields: 1743 wx_expando.EVT_ETC_LAYOUT_NEEDED(field, field.GetId(), self._on_expando_needs_layout) 1744 wx_expando.EVT_ETC_LAYOUT_NEEDED(self._TCTRL_episode_summary, self._TCTRL_episode_summary.GetId(), self._on_expando_needs_layout)
1745 #--------------------------------------------------------
1746 - def _on_expando_needs_layout(self, evt):
1747 # need to tell ourselves to re-Layout to refresh scroll bars 1748 1749 # provoke adding scrollbar if needed 1750 #self.Fit() # works on Linux but not on Windows 1751 self.FitInside() # needed on Windows rather than self.Fit() 1752 1753 if self.HasScrollbar(wx.VERTICAL): 1754 # scroll panel to show cursor 1755 expando = self.FindWindowById(evt.GetId()) 1756 y_expando = expando.GetPositionTuple()[1] 1757 h_expando = expando.GetSizeTuple()[1] 1758 line_cursor = expando.PositionToXY(expando.GetInsertionPoint())[1] + 1 1759 y_cursor = int(round((float(line_cursor) / expando.NumberOfLines) * h_expando)) 1760 y_desired_visible = y_expando + y_cursor 1761 1762 y_view = self.ViewStart[1] 1763 h_view = self.GetClientSizeTuple()[1] 1764 1765 # print "expando:", y_expando, "->", h_expando, ", lines:", expando.NumberOfLines 1766 # print "cursor :", y_cursor, "at line", line_cursor, ", insertion point:", expando.GetInsertionPoint() 1767 # print "wanted :", y_desired_visible 1768 # print "view-y :", y_view 1769 # print "scroll2:", h_view 1770 1771 # expando starts before view 1772 if y_desired_visible < y_view: 1773 # print "need to scroll up" 1774 self.Scroll(0, y_desired_visible) 1775 1776 if y_desired_visible > h_view: 1777 # print "need to scroll down" 1778 self.Scroll(0, y_desired_visible)
1779 #-------------------------------------------------------- 1780 # properties 1781 #--------------------------------------------------------
1782 - def _get_soap(self):
1783 soap_notes = [] 1784 1785 tmp = self._TCTRL_Soap.GetValue().strip() 1786 if tmp != u'': 1787 soap_notes.append(['s', tmp]) 1788 1789 tmp = self._TCTRL_sOap.GetValue().strip() 1790 if tmp != u'': 1791 soap_notes.append(['o', tmp]) 1792 1793 tmp = self._TCTRL_soAp.GetValue().strip() 1794 if tmp != u'': 1795 soap_notes.append(['a', tmp]) 1796 1797 tmp = self._TCTRL_soaP.GetValue().strip() 1798 if tmp != u'': 1799 soap_notes.append(['p', tmp]) 1800 1801 return soap_notes
1802 1803 soap = property(_get_soap, lambda x:x) 1804 #--------------------------------------------------------
1805 - def _get_empty(self):
1806 1807 # soap fields 1808 for field in self.soap_fields: 1809 if field.GetValue().strip() != u'': 1810 return False 1811 1812 # summary 1813 summary = self._TCTRL_episode_summary.GetValue().strip() 1814 if self.problem is None: 1815 if summary != u'': 1816 return False 1817 elif self.problem['type'] == u'issue': 1818 if summary != u'': 1819 return False 1820 else: 1821 if self.problem['summary'] is None: 1822 if summary != u'': 1823 return False 1824 else: 1825 if summary != self.problem['summary'].strip(): 1826 return False 1827 1828 # codes 1829 new_codes = self._PRW_episode_codes.GetData() 1830 if self.problem is None: 1831 if len(new_codes) > 0: 1832 return False 1833 elif self.problem['type'] == u'issue': 1834 if len(new_codes) > 0: 1835 return False 1836 else: 1837 old_code_pks = self.problem.generic_codes 1838 if len(old_code_pks) != len(new_codes): 1839 return False 1840 for code in new_codes: 1841 if code['data'] not in old_code_pks: 1842 return False 1843 1844 return True
1845 1846 empty = property(_get_empty, lambda x:x)
1847 #============================================================
1848 -class cSoapLineTextCtrl(wx_expando.ExpandoTextCtrl):
1849
1850 - def __init__(self, *args, **kwargs):
1851 1852 wx_expando.ExpandoTextCtrl.__init__(self, *args, **kwargs) 1853 1854 self.__keyword_separators = regex.compile("[!?'\".,:;)}\]\r\n\s\t]+") 1855 1856 self.__register_interests()
1857 #------------------------------------------------ 1858 # fixup errors in platform expando.py 1859 #------------------------------------------------
1860 - def _wrapLine(self, line, dc, width):
1861 1862 if (wx.MAJOR_VERSION >= 2) and (wx.MINOR_VERSION > 8): 1863 return super(cSoapLineTextCtrl, self)._wrapLine(line, dc, width) 1864 1865 # THIS FIX LIFTED FROM TRUNK IN SVN: 1866 # Estimate where the control will wrap the lines and 1867 # return the count of extra lines needed. 1868 pte = dc.GetPartialTextExtents(line) 1869 width -= wx.SystemSettings.GetMetric(wx.SYS_VSCROLL_X) 1870 idx = 0 1871 start = 0 1872 count = 0 1873 spc = -1 1874 while idx < len(pte): 1875 if line[idx] == ' ': 1876 spc = idx 1877 if pte[idx] - start > width: 1878 # we've reached the max width, add a new line 1879 count += 1 1880 # did we see a space? if so restart the count at that pos 1881 if spc != -1: 1882 idx = spc + 1 1883 spc = -1 1884 if idx < len(pte): 1885 start = pte[idx] 1886 else: 1887 idx += 1 1888 return count
1889 #------------------------------------------------ 1890 # event handling 1891 #------------------------------------------------
1892 - def __register_interests(self):
1893 #wx.EVT_KEY_DOWN (self, self.__on_key_down) 1894 #wx.EVT_KEY_UP (self, self.__OnKeyUp) 1895 wx.EVT_CHAR(self, self.__on_char) 1896 wx.EVT_SET_FOCUS(self, self.__on_focus)
1897 #--------------------------------------------------------
1898 - def __on_focus(self, evt):
1899 evt.Skip() 1900 wx.CallAfter(self._after_on_focus)
1901 #--------------------------------------------------------
1902 - def _after_on_focus(self):
1903 #wx.CallAfter(self._adjustCtrl) 1904 evt = wx.PyCommandEvent(wx_expando.wxEVT_ETC_LAYOUT_NEEDED, self.GetId()) 1905 evt.SetEventObject(self) 1906 #evt.height = None 1907 #evt.numLines = None 1908 #evt.height = self.GetSize().height 1909 #evt.numLines = self.GetNumberOfLines() 1910 self.GetEventHandler().ProcessEvent(evt)
1911 #--------------------------------------------------------
1912 - def __on_char(self, evt):
1913 char = unichr(evt.GetUnicodeKey()) 1914 1915 if self.LastPosition == 1: 1916 evt.Skip() 1917 return 1918 1919 explicit_expansion = False 1920 if evt.GetModifiers() == (wx.MOD_CMD | wx.MOD_ALT): # portable CTRL-ALT-... 1921 if evt.GetKeyCode() != 13: 1922 evt.Skip() 1923 return 1924 explicit_expansion = True 1925 1926 if not explicit_expansion: 1927 if self.__keyword_separators.match(char) is None: 1928 evt.Skip() 1929 return 1930 1931 caret_pos, line_no = self.PositionToXY(self.InsertionPoint) 1932 line = self.GetLineText(line_no) 1933 word = self.__keyword_separators.split(line[:caret_pos])[-1] 1934 1935 if ( 1936 (not explicit_expansion) 1937 and 1938 (word != u'$$steffi') # Easter Egg ;-) 1939 and 1940 (word not in [ r[0] for r in gmPG2.get_text_expansion_keywords() ]) 1941 ): 1942 evt.Skip() 1943 return 1944 1945 start = self.InsertionPoint - len(word) 1946 wx.CallAfter(self.replace_keyword_with_expansion, word, start, explicit_expansion) 1947 1948 evt.Skip() 1949 return
1950 #------------------------------------------------
1951 - def replace_keyword_with_expansion(self, keyword=None, position=None, show_list=False):
1952 1953 if show_list: 1954 candidates = gmPG2.get_keyword_expansion_candidates(keyword = keyword) 1955 if len(candidates) == 0: 1956 return 1957 if len(candidates) == 1: 1958 keyword = candidates[0] 1959 else: 1960 keyword = gmListWidgets.get_choices_from_list ( 1961 parent = self, 1962 msg = _( 1963 'Several macros match the keyword [%s].\n' 1964 '\n' 1965 'Please select the expansion you want to happen.' 1966 ) % keyword, 1967 caption = _('Selecting text macro'), 1968 choices = candidates, 1969 columns = [_('Keyword')], 1970 single_selection = True, 1971 can_return_empty = False 1972 ) 1973 if keyword is None: 1974 return 1975 1976 expansion = gmPG2.expand_keyword(keyword = keyword) 1977 1978 if expansion is None: 1979 return 1980 1981 if expansion == u'': 1982 return 1983 1984 self.Replace ( 1985 position, 1986 position + len(keyword), 1987 expansion 1988 ) 1989 1990 self.SetInsertionPoint(position + len(expansion) + 1) 1991 self.ShowPosition(position + len(expansion) + 1) 1992 1993 return
1994 #============================================================ 1995 # visual progress notes 1996 #============================================================
1997 -def configure_visual_progress_note_editor():
1998 1999 def is_valid(value): 2000 2001 if value is None: 2002 gmDispatcher.send ( 2003 signal = 'statustext', 2004 msg = _('You need to actually set an editor.'), 2005 beep = True 2006 ) 2007 return False, value 2008 2009 if value.strip() == u'': 2010 gmDispatcher.send ( 2011 signal = 'statustext', 2012 msg = _('You need to actually set an editor.'), 2013 beep = True 2014 ) 2015 return False, value 2016 2017 found, binary = gmShellAPI.detect_external_binary(value) 2018 if not found: 2019 gmDispatcher.send ( 2020 signal = 'statustext', 2021 msg = _('The command [%s] is not found.') % value, 2022 beep = True 2023 ) 2024 return True, value 2025 2026 return True, binary
2027 #------------------------------------------ 2028 cmd = gmCfgWidgets.configure_string_option ( 2029 message = _( 2030 'Enter the shell command with which to start\n' 2031 'the image editor for visual progress notes.\n' 2032 '\n' 2033 'Any "%(img)s" included with the arguments\n' 2034 'will be replaced by the file name of the\n' 2035 'note template.' 2036 ), 2037 option = u'external.tools.visual_soap_editor_cmd', 2038 bias = 'user', 2039 default_value = None, 2040 validator = is_valid 2041 ) 2042 2043 return cmd 2044 #============================================================
2045 -def select_file_as_visual_progress_note_template(parent=None):
2046 if parent is None: 2047 parent = wx.GetApp().GetTopWindow() 2048 2049 dlg = wx.FileDialog ( 2050 parent = parent, 2051 message = _('Choose file to use as template for new visual progress note'), 2052 defaultDir = os.path.expanduser('~'), 2053 defaultFile = '', 2054 #wildcard = "%s (*)|*|%s (*.*)|*.*" % (_('all files'), _('all files (Win)')), 2055 style = wx.OPEN | wx.HIDE_READONLY | wx.FILE_MUST_EXIST 2056 ) 2057 result = dlg.ShowModal() 2058 2059 if result == wx.ID_CANCEL: 2060 dlg.Destroy() 2061 return None 2062 2063 full_filename = dlg.GetPath() 2064 dlg.Hide() 2065 dlg.Destroy() 2066 return full_filename
2067 #------------------------------------------------------------
2068 -def select_visual_progress_note_template(parent=None):
2069 2070 if parent is None: 2071 parent = wx.GetApp().GetTopWindow() 2072 2073 dlg = gmGuiHelpers.c3ButtonQuestionDlg ( 2074 parent, 2075 -1, 2076 caption = _('Visual progress note source'), 2077 question = _('From which source do you want to pick the image template ?'), 2078 button_defs = [ 2079 {'label': _('Database'), 'tooltip': _('List of templates in the database.'), 'default': True}, 2080 {'label': _('File'), 'tooltip': _('Files in the filesystem.'), 'default': False}, 2081 {'label': _('Device'), 'tooltip': _('Image capture devices (scanners, cameras, etc)'), 'default': False} 2082 ] 2083 ) 2084 result = dlg.ShowModal() 2085 dlg.Destroy() 2086 2087 # 1) select from template 2088 if result == wx.ID_YES: 2089 _log.debug('visual progress note template from: database template') 2090 from Gnumed.wxpython import gmFormWidgets 2091 template = gmFormWidgets.manage_form_templates ( 2092 parent = parent, 2093 template_types = [gmDocuments.DOCUMENT_TYPE_VISUAL_PROGRESS_NOTE], 2094 active_only = True 2095 ) 2096 if template is None: 2097 return (None, None) 2098 filename = template.export_to_file() 2099 if filename is None: 2100 gmDispatcher.send(signal = u'statustext', msg = _('Cannot export visual progress note template for [%s].') % template['name_long']) 2101 return (None, None) 2102 return (filename, True) 2103 2104 # 2) select from disk file 2105 if result == wx.ID_NO: 2106 _log.debug('visual progress note template from: disk file') 2107 fname = select_file_as_visual_progress_note_template(parent = parent) 2108 if fname is None: 2109 return (None, None) 2110 # create a copy of the picked file -- don't modify the original 2111 ext = os.path.splitext(fname)[1] 2112 tmp_name = gmTools.get_unique_filename(suffix = ext) 2113 _log.debug('visual progress note from file: [%s] -> [%s]', fname, tmp_name) 2114 shutil.copy2(fname, tmp_name) 2115 return (tmp_name, False) 2116 2117 # 3) acquire from capture device 2118 if result == wx.ID_CANCEL: 2119 _log.debug('visual progress note template from: image capture device') 2120 fnames = gmDocumentWidgets.acquire_images_from_capture_device(device = None, calling_window = parent) 2121 if fnames is None: 2122 return (None, None) 2123 if len(fnames) == 0: 2124 return (None, None) 2125 return (fnames[0], False) 2126 2127 _log.debug('no visual progress note template source selected') 2128 return (None, None)
2129 #------------------------------------------------------------
2130 -def edit_visual_progress_note(filename=None, episode=None, discard_unmodified=False, doc_part=None, health_issue=None):
2131 """This assumes <filename> contains an image which can be handled by the configured image editor.""" 2132 2133 if doc_part is not None: 2134 filename = doc_part.export_to_file() 2135 if filename is None: 2136 gmDispatcher.send(signal = u'statustext', msg = _('Cannot export visual progress note to file.')) 2137 return None 2138 2139 dbcfg = gmCfg.cCfgSQL() 2140 cmd = dbcfg.get2 ( 2141 option = u'external.tools.visual_soap_editor_cmd', 2142 workplace = gmSurgery.gmCurrentPractice().active_workplace, 2143 bias = 'user' 2144 ) 2145 2146 if cmd is None: 2147 gmDispatcher.send(signal = u'statustext', msg = _('Editor for visual progress note not configured.'), beep = False) 2148 cmd = configure_visual_progress_note_editor() 2149 if cmd is None: 2150 gmDispatcher.send(signal = u'statustext', msg = _('Editor for visual progress note not configured.'), beep = True) 2151 return None 2152 2153 if u'%(img)s' in cmd: 2154 cmd % {u'img': filename} 2155 else: 2156 cmd = u'%s %s' % (cmd, filename) 2157 2158 if discard_unmodified: 2159 original_stat = os.stat(filename) 2160 original_md5 = gmTools.file2md5(filename) 2161 2162 success = gmShellAPI.run_command_in_shell(cmd, blocking = True) 2163 if not success: 2164 gmGuiHelpers.gm_show_error ( 2165 _( 2166 'There was a problem with running the editor\n' 2167 'for visual progress notes.\n' 2168 '\n' 2169 ' [%s]\n' 2170 '\n' 2171 ) % cmd, 2172 _('Editing visual progress note') 2173 ) 2174 return None 2175 2176 try: 2177 open(filename, 'r').close() 2178 except StandardError: 2179 _log.exception('problem accessing visual progress note file [%s]', filename) 2180 gmGuiHelpers.gm_show_error ( 2181 _( 2182 'There was a problem reading the visual\n' 2183 'progress note from the file:\n' 2184 '\n' 2185 ' [%s]\n' 2186 '\n' 2187 ) % filename, 2188 _('Saving visual progress note') 2189 ) 2190 return None 2191 2192 if discard_unmodified: 2193 modified_stat = os.stat(filename) 2194 # same size ? 2195 if original_stat.st_size == modified_stat.st_size: 2196 modified_md5 = gmTools.file2md5(filename) 2197 # same hash ? 2198 if original_md5 == modified_md5: 2199 _log.debug('visual progress note (template) not modified') 2200 # ask user to decide 2201 msg = _( 2202 u'You either created a visual progress note from a template\n' 2203 u'in the database (rather than from a file on disk) or you\n' 2204 u'edited an existing visual progress note.\n' 2205 u'\n' 2206 u'The template/original was not modified at all, however.\n' 2207 u'\n' 2208 u'Do you still want to save the unmodified image as a\n' 2209 u'visual progress note into the EMR of the patient ?\n' 2210 ) 2211 save_unmodified = gmGuiHelpers.gm_show_question ( 2212 msg, 2213 _('Saving visual progress note') 2214 ) 2215 if not save_unmodified: 2216 _log.debug('user discarded unmodified note') 2217 return 2218 2219 if doc_part is not None: 2220 doc_part.update_data_from_file(fname = filename) 2221 doc_part.set_reviewed(technically_abnormal = False, clinically_relevant = True) 2222 return None 2223 2224 if not isinstance(episode, gmEMRStructItems.cEpisode): 2225 if episode is None: 2226 episode = _('visual progress notes') 2227 pat = gmPerson.gmCurrentPatient() 2228 emr = pat.get_emr() 2229 episode = emr.add_episode(episode_name = episode.strip(), pk_health_issue = health_issue, is_open = False) 2230 2231 doc = gmDocumentWidgets.save_file_as_new_document ( 2232 filename = filename, 2233 document_type = gmDocuments.DOCUMENT_TYPE_VISUAL_PROGRESS_NOTE, 2234 episode = episode, 2235 unlock_patient = True 2236 ) 2237 doc.set_reviewed(technically_abnormal = False, clinically_relevant = True) 2238 2239 return doc
2240 #============================================================
2241 -class cVisualSoapTemplatePhraseWheel(gmPhraseWheel.cPhraseWheel):
2242 """Phrasewheel to allow selection of visual SOAP template.""" 2243
2244 - def __init__(self, *args, **kwargs):
2245 2246 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs) 2247 2248 query = u""" 2249 SELECT 2250 pk AS data, 2251 name_short AS list_label, 2252 name_sort AS field_label 2253 FROM 2254 ref.paperwork_templates 2255 WHERE 2256 fk_template_type = (SELECT pk FROM ref.form_types WHERE name = '%s') AND ( 2257 name_long %%(fragment_condition)s 2258 OR 2259 name_short %%(fragment_condition)s 2260 ) 2261 ORDER BY list_label 2262 LIMIT 15 2263 """ % gmDocuments.DOCUMENT_TYPE_VISUAL_PROGRESS_NOTE 2264 2265 mp = gmMatchProvider.cMatchProvider_SQL2(queries = [query]) 2266 mp.setThresholds(2, 3, 5) 2267 2268 self.matcher = mp 2269 self.selection_only = True
2270 #--------------------------------------------------------
2271 - def _data2instance(self):
2272 if self.GetData() is None: 2273 return None 2274 2275 return gmForms.cFormTemplate(aPK_obj = self.GetData())
2276 #============================================================ 2277 from Gnumed.wxGladeWidgets import wxgVisualSoapPresenterPnl 2278
2279 -class cVisualSoapPresenterPnl(wxgVisualSoapPresenterPnl.wxgVisualSoapPresenterPnl):
2280
2281 - def __init__(self, *args, **kwargs):
2282 wxgVisualSoapPresenterPnl.wxgVisualSoapPresenterPnl.__init__(self, *args, **kwargs) 2283 self._SZR_soap = self.GetSizer() 2284 self.__bitmaps = []
2285 #-------------------------------------------------------- 2286 # external API 2287 #--------------------------------------------------------
2288 - def refresh(self, document_folder=None, episodes=None, encounter=None):
2289 2290 self.clear() 2291 if document_folder is not None: 2292 soap_docs = document_folder.get_visual_progress_notes(episodes = episodes, encounter = encounter) 2293 if len(soap_docs) > 0: 2294 for soap_doc in soap_docs: 2295 parts = soap_doc.parts 2296 if len(parts) == 0: 2297 continue 2298 part = parts[0] 2299 fname = part.export_to_file() 2300 if fname is None: 2301 continue 2302 2303 # create bitmap 2304 img = gmGuiHelpers.file2scaled_image ( 2305 filename = fname, 2306 height = 30 2307 ) 2308 #bmp = wx.StaticBitmap(self, -1, img, style = wx.NO_BORDER) 2309 bmp = wx_genstatbmp.GenStaticBitmap(self, -1, img, style = wx.NO_BORDER) 2310 2311 # create tooltip 2312 img = gmGuiHelpers.file2scaled_image ( 2313 filename = fname, 2314 height = 150 2315 ) 2316 tip = agw_stt.SuperToolTip ( 2317 u'', 2318 bodyImage = img, 2319 header = _('Created: %s') % part['date_generated'].strftime('%Y %B %d').decode(gmI18N.get_encoding()), 2320 footer = gmTools.coalesce(part['doc_comment'], u'').strip() 2321 ) 2322 tip.SetTopGradientColor('white') 2323 tip.SetMiddleGradientColor('white') 2324 tip.SetBottomGradientColor('white') 2325 tip.SetTarget(bmp) 2326 2327 bmp.doc_part = part 2328 bmp.Bind(wx.EVT_LEFT_UP, self._on_bitmap_leftclicked) 2329 # FIXME: add context menu for Delete/Clone/Add/Configure 2330 self._SZR_soap.Add(bmp, 0, wx.LEFT | wx.RIGHT | wx.TOP | wx.BOTTOM | wx.EXPAND, 3) 2331 self.__bitmaps.append(bmp) 2332 2333 self.GetParent().Layout()
2334 #--------------------------------------------------------
2335 - def clear(self):
2336 for child_idx in range(len(self._SZR_soap.GetChildren())): 2337 self._SZR_soap.Detach(child_idx) 2338 for bmp in self.__bitmaps: 2339 bmp.Destroy() 2340 self.__bitmaps = []
2341 #--------------------------------------------------------
2342 - def _on_bitmap_leftclicked(self, evt):
2343 wx.CallAfter ( 2344 edit_visual_progress_note, 2345 doc_part = evt.GetEventObject().doc_part, 2346 discard_unmodified = True 2347 )
2348 #============================================================ 2349 #from Gnumed.wxGladeWidgets import wxgVisualSoapPnl 2350 2351 #class cVisualSoapPnl(wxgVisualSoapPnl.wxgVisualSoapPnl): 2352 # 2353 # def __init__(self, *args, **kwargs): 2354 # 2355 # wxgVisualSoapPnl.wxgVisualSoapPnl.__init__(self, *args, **kwargs) 2356 # 2357 # # dummy episode to hold images 2358 # self.default_episode_name = _('visual progress notes') 2359 # #-------------------------------------------------------- 2360 # # external API 2361 # #-------------------------------------------------------- 2362 # def clear(self): 2363 # self._PRW_template.SetText(value = u'', data = None) 2364 # self._LCTRL_visual_soaps.set_columns([_('Sketches')]) 2365 # self._LCTRL_visual_soaps.set_string_items() 2366 # 2367 # self.show_image_and_metadata() 2368 # #-------------------------------------------------------- 2369 # def refresh(self, patient=None, encounter=None): 2370 # 2371 # self.clear() 2372 # 2373 # if patient is None: 2374 # patient = gmPerson.gmCurrentPatient() 2375 # 2376 # if not patient.connected: 2377 # return 2378 # 2379 # emr = patient.get_emr() 2380 # if encounter is None: 2381 # encounter = emr.active_encounter 2382 # 2383 # folder = patient.get_document_folder() 2384 # soap_docs = folder.get_documents ( 2385 # doc_type = gmDocuments.DOCUMENT_TYPE_VISUAL_PROGRESS_NOTE, 2386 # encounter = encounter['pk_encounter'] 2387 # ) 2388 # 2389 # if len(soap_docs) == 0: 2390 # self._BTN_delete.Enable(False) 2391 # return 2392 # 2393 # self._LCTRL_visual_soaps.set_string_items ([ 2394 # u'%s%s%s' % ( 2395 # gmTools.coalesce(sd['comment'], u'', u'%s\n'), 2396 # gmTools.coalesce(sd['ext_ref'], u'', u'%s\n'), 2397 # sd['episode'] 2398 # ) for sd in soap_docs 2399 # ]) 2400 # self._LCTRL_visual_soaps.set_data(soap_docs) 2401 # 2402 # self._BTN_delete.Enable(True) 2403 # #-------------------------------------------------------- 2404 # def show_image_and_metadata(self, doc=None): 2405 # 2406 # if doc is None: 2407 # self._IMG_soap.SetBitmap(wx.NullBitmap) 2408 # self._PRW_episode.SetText() 2409 # #self._PRW_comment.SetText(value = u'', data = None) 2410 # self._PRW_comment.SetValue(u'') 2411 # return 2412 # 2413 # parts = doc.parts 2414 # if len(parts) == 0: 2415 # gmDispatcher.send(signal = u'statustext', msg = _('No images in visual progress note.')) 2416 # return 2417 # 2418 # fname = parts[0].export_to_file() 2419 # if fname is None: 2420 # gmDispatcher.send(signal = u'statustext', msg = _('Cannot export visual progress note to file.')) 2421 # return 2422 # 2423 # img_data = None 2424 # rescaled_width = 300 2425 # try: 2426 # img_data = wx.Image(fname, wx.BITMAP_TYPE_ANY) 2427 # current_width = img_data.GetWidth() 2428 # current_height = img_data.GetHeight() 2429 # rescaled_height = (rescaled_width * current_height) / current_width 2430 # img_data.Rescale(rescaled_width, rescaled_height, quality = wx.IMAGE_QUALITY_HIGH) # w, h 2431 # bmp_data = wx.BitmapFromImage(img_data) 2432 # except: 2433 # _log.exception('cannot load visual progress note from [%s]', fname) 2434 # gmDispatcher.send(signal = u'statustext', msg = _('Cannot load visual progress note from [%s].') % fname) 2435 # del img_data 2436 # return 2437 # 2438 # del img_data 2439 # self._IMG_soap.SetBitmap(bmp_data) 2440 # 2441 # self._PRW_episode.SetText(value = doc['episode'], data = doc['pk_episode']) 2442 # if doc['comment'] is not None: 2443 # self._PRW_comment.SetValue(doc['comment'].strip()) 2444 # #-------------------------------------------------------- 2445 # # event handlers 2446 # #-------------------------------------------------------- 2447 # def _on_visual_soap_selected(self, event): 2448 # 2449 # doc = self._LCTRL_visual_soaps.get_selected_item_data(only_one = True) 2450 # self.show_image_and_metadata(doc = doc) 2451 # if doc is None: 2452 # return 2453 # 2454 # self._BTN_delete.Enable(True) 2455 # #-------------------------------------------------------- 2456 # def _on_visual_soap_deselected(self, event): 2457 # self._BTN_delete.Enable(False) 2458 # #-------------------------------------------------------- 2459 # def _on_visual_soap_activated(self, event): 2460 # 2461 # doc = self._LCTRL_visual_soaps.get_selected_item_data(only_one = True) 2462 # if doc is None: 2463 # self.show_image_and_metadata() 2464 # return 2465 # 2466 # parts = doc.parts 2467 # if len(parts) == 0: 2468 # gmDispatcher.send(signal = u'statustext', msg = _('No images in visual progress note.')) 2469 # return 2470 # 2471 # edit_visual_progress_note(doc_part = parts[0], discard_unmodified = True) 2472 # self.show_image_and_metadata(doc = doc) 2473 # 2474 # self._BTN_delete.Enable(True) 2475 # #-------------------------------------------------------- 2476 # def _on_from_template_button_pressed(self, event): 2477 # 2478 # template = self._PRW_template.GetData(as_instance = True) 2479 # if template is None: 2480 # return 2481 # 2482 # filename = template.export_to_file() 2483 # if filename is None: 2484 # gmDispatcher.send(signal = u'statustext', msg = _('Cannot export visual progress note template for [%s].') % template['name_long']) 2485 # return 2486 # 2487 # episode = self._PRW_episode.GetData(as_instance = True) 2488 # if episode is None: 2489 # episode = self._PRW_episode.GetValue().strip() 2490 # if episode == u'': 2491 # episode = self.default_episode_name 2492 # 2493 # # do not store note if not modified -- change if users complain 2494 # doc = edit_visual_progress_note(filename = filename, episode = episode, discard_unmodified = True) 2495 # if doc is None: 2496 # return 2497 # 2498 # if self._PRW_comment.GetValue().strip() == u'': 2499 # doc['comment'] = template['instance_type'] 2500 # else: 2501 # doc['comment'] = self._PRW_comment.GetValue().strip() 2502 # 2503 # doc.save() 2504 # self.show_image_and_metadata(doc = doc) 2505 # #-------------------------------------------------------- 2506 # def _on_from_file_button_pressed(self, event): 2507 # 2508 # dlg = wx.FileDialog ( 2509 # parent = self, 2510 # message = _('Choose a visual progress note template file'), 2511 # defaultDir = os.path.expanduser('~'), 2512 # defaultFile = '', 2513 # #wildcard = "%s (*)|*|%s (*.*)|*.*" % (_('all files'), _('all files (Win)')), 2514 # style = wx.OPEN | wx.HIDE_READONLY | wx.FILE_MUST_EXIST 2515 # ) 2516 # result = dlg.ShowModal() 2517 # if result == wx.ID_CANCEL: 2518 # dlg.Destroy() 2519 # return 2520 # 2521 # full_filename = dlg.GetPath() 2522 # dlg.Hide() 2523 # dlg.Destroy() 2524 # 2525 # # create a copy of the picked file -- don't modify the original 2526 # ext = os.path.splitext(full_filename)[1] 2527 # tmp_name = gmTools.get_unique_filename(suffix = ext) 2528 # _log.debug('visual progress note from file: [%s] -> [%s]', full_filename, tmp_name) 2529 # shutil.copy2(full_filename, tmp_name) 2530 # 2531 # episode = self._PRW_episode.GetData(as_instance = True) 2532 # if episode is None: 2533 # episode = self._PRW_episode.GetValue().strip() 2534 # if episode == u'': 2535 # episode = self.default_episode_name 2536 # 2537 # # always store note even if unmodified as we 2538 # # may simply want to store a clinical photograph 2539 # doc = edit_visual_progress_note(filename = tmp_name, episode = episode, discard_unmodified = False) 2540 # if self._PRW_comment.GetValue().strip() == u'': 2541 # # use filename as default comment (w/o extension) 2542 # doc['comment'] = os.path.splitext(os.path.split(full_filename)[1])[0] 2543 # else: 2544 # doc['comment'] = self._PRW_comment.GetValue().strip() 2545 # doc.save() 2546 # self.show_image_and_metadata(doc = doc) 2547 # 2548 # try: 2549 # os.remove(tmp_name) 2550 # except StandardError: 2551 # _log.exception('cannot remove [%s]', tmp_name) 2552 # 2553 # remove_original = gmGuiHelpers.gm_show_question ( 2554 # _( 2555 # 'Do you want to delete the original file\n' 2556 # '\n' 2557 # ' [%s]\n' 2558 # '\n' 2559 # 'from your computer ?' 2560 # ) % full_filename, 2561 # _('Saving visual progress note ...') 2562 # ) 2563 # if remove_original: 2564 # try: 2565 # os.remove(full_filename) 2566 # except StandardError: 2567 # _log.exception('cannot remove [%s]', full_filename) 2568 # #-------------------------------------------------------- 2569 # def _on_delete_button_pressed(self, event): 2570 # 2571 # doc = self._LCTRL_visual_soaps.get_selected_item_data(only_one = True) 2572 # if doc is None: 2573 # self.show_image_and_metadata() 2574 # return 2575 # 2576 # delete_it = gmGuiHelpers.gm_show_question ( 2577 # aMessage = _('Are you sure you want to delete the visual progress note ?'), 2578 # aTitle = _('Deleting visual progress note') 2579 # ) 2580 # if delete_it is True: 2581 # gmDocuments.delete_document ( 2582 # document_id = doc['pk_doc'], 2583 # encounter_id = doc['pk_encounter'] 2584 # ) 2585 # self.show_image_and_metadata() 2586 #============================================================ 2587 # main 2588 #------------------------------------------------------------ 2589 if __name__ == '__main__': 2590 2591 if len(sys.argv) < 2: 2592 sys.exit() 2593 2594 if sys.argv[1] != 'test': 2595 sys.exit() 2596 2597 gmI18N.activate_locale() 2598 gmI18N.install_domain(domain = 'gnumed') 2599 2600 #----------------------------------------
2601 - def test_select_narrative_from_episodes():
2602 pat = gmPersonSearch.ask_for_patient() 2603 gmPatSearchWidgets.set_active_patient(patient = pat) 2604 app = wx.PyWidgetTester(size = (200, 200)) 2605 sels = select_narrative_from_episodes() 2606 print "selected:" 2607 for sel in sels: 2608 print sel
2609 #----------------------------------------
2610 - def test_cSoapNoteExpandoEditAreaPnl():
2611 pat = gmPersonSearch.ask_for_patient() 2612 application = wx.PyWidgetTester(size=(800,500)) 2613 soap_input = cSoapNoteExpandoEditAreaPnl(application.frame, -1) 2614 application.frame.Show(True) 2615 application.MainLoop()
2616 #----------------------------------------
2617 - def test_cSoapPluginPnl():
2618 patient = gmPersonSearch.ask_for_patient() 2619 if patient is None: 2620 print "No patient. Exiting gracefully..." 2621 return 2622 gmPatSearchWidgets.set_active_patient(patient=patient) 2623 2624 application = wx.PyWidgetTester(size=(800,500)) 2625 soap_input = cSoapPluginPnl(application.frame, -1) 2626 application.frame.Show(True) 2627 soap_input._schedule_data_reget() 2628 application.MainLoop()
2629 #---------------------------------------- 2630 #test_select_narrative_from_episodes() 2631 test_cSoapNoteExpandoEditAreaPnl() 2632 #test_cSoapPluginPnl() 2633 2634 #============================================================ 2635