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 wxexpando 
  11   
  12   
  13  if __name__ == '__main__': 
  14          sys.path.insert(0, '../../') 
  15  from Gnumed.pycommon import gmI18N, gmDispatcher, gmTools, gmDateTime 
  16  from Gnumed.pycommon import gmShellAPI, gmPG2, gmCfg, gmMatchProvider 
  17  from Gnumed.business import gmPerson, gmEMRStructItems, gmClinNarrative, gmSurgery 
  18  from Gnumed.business import gmForms, gmDocuments, gmPersonSearch 
  19  from Gnumed.wxpython import gmListWidgets, gmEMRStructWidgets, gmRegetMixin 
  20  from Gnumed.wxpython import gmPhraseWheel, gmGuiHelpers, gmPatSearchWidgets 
  21  from Gnumed.wxpython import gmCfgWidgets, gmDocumentWidgets 
  22  from Gnumed.exporters import gmPatientExporter 
  23   
  24   
  25  _log = logging.getLogger('gm.ui') 
  26  _log.info(__version__) 
  27  #============================================================ 
  28  # narrative related widgets/functions 
  29  #------------------------------------------------------------ 
30 -def move_progress_notes_to_another_encounter(parent=None, encounters=None, episodes=None, patient=None, move_all=False):
31 32 # sanity checks 33 if patient is None: 34 patient = gmPerson.gmCurrentPatient() 35 36 if not patient.connected: 37 gmDispatcher.send(signal = 'statustext', msg = _('Cannot move progress notes. No active patient.')) 38 return False 39 40 if parent is None: 41 parent = wx.GetApp().GetTopWindow() 42 43 emr = patient.get_emr() 44 45 if encounters is None: 46 encs = emr.get_encounters(episodes = episodes) 47 encounters = gmEMRStructWidgets.select_encounters ( 48 parent = parent, 49 patient = patient, 50 single_selection = False, 51 encounters = encs 52 ) 53 54 notes = emr.get_clin_narrative ( 55 encounters = encounters, 56 episodes = episodes 57 ) 58 59 # which narrative 60 if move_all: 61 selected_narr = notes 62 else: 63 selected_narr = gmListWidgets.get_choices_from_list ( 64 parent = parent, 65 caption = _('Moving progress notes between encounters ...'), 66 single_selection = False, 67 can_return_empty = True, 68 data = notes, 69 msg = _('\n Select the progress notes to move from the list !\n\n'), 70 columns = [_('when'), _('who'), _('type'), _('entry')], 71 choices = [ 72 [ narr['date'].strftime('%x %H:%M'), 73 narr['provider'], 74 gmClinNarrative.soap_cat2l10n[narr['soap_cat']], 75 narr['narrative'].replace('\n', '/').replace('\r', '/') 76 ] for narr in notes 77 ] 78 ) 79 80 if not selected_narr: 81 return True 82 83 # which encounter to move to 84 enc2move2 = gmEMRStructWidgets.select_encounters ( 85 parent = parent, 86 patient = patient, 87 single_selection = True 88 ) 89 90 if not enc2move2: 91 return True 92 93 for narr in selected_narr: 94 narr['pk_encounter'] = enc2move2['pk_encounter'] 95 narr.save() 96 97 return True
98 #------------------------------------------------------------
99 -def manage_progress_notes(parent=None, encounters=None, episodes=None, patient=None):
100 101 # sanity checks 102 if patient is None: 103 patient = gmPerson.gmCurrentPatient() 104 105 if not patient.connected: 106 gmDispatcher.send(signal = 'statustext', msg = _('Cannot edit progress notes. No active patient.')) 107 return False 108 109 if parent is None: 110 parent = wx.GetApp().GetTopWindow() 111 112 emr = patient.get_emr() 113 #-------------------------- 114 def delete(item): 115 if item is None: 116 return False 117 dlg = gmGuiHelpers.c2ButtonQuestionDlg ( 118 parent, 119 -1, 120 caption = _('Deleting progress note'), 121 question = _( 122 'Are you positively sure you want to delete this\n' 123 'progress note from the medical record ?\n' 124 '\n' 125 'Note that even if you chose to delete the entry it will\n' 126 'still be (invisibly) kept in the audit trail to protect\n' 127 'you from litigation because physical deletion is known\n' 128 'to be unlawful in some jurisdictions.\n' 129 ), 130 button_defs = ( 131 {'label': _('Delete'), 'tooltip': _('Yes, delete the progress note.'), 'default': False}, 132 {'label': _('Cancel'), 'tooltip': _('No, do NOT delete the progress note.'), 'default': True} 133 ) 134 ) 135 decision = dlg.ShowModal() 136 137 if decision != wx.ID_YES: 138 return False 139 140 gmClinNarrative.delete_clin_narrative(narrative = item['pk_narrative']) 141 return True
142 #-------------------------- 143 def edit(item): 144 if item is None: 145 return False 146 147 dlg = gmGuiHelpers.cMultilineTextEntryDlg ( 148 parent, 149 -1, 150 title = _('Editing progress note'), 151 msg = _('This is the original progress note:'), 152 data = item.format(left_margin = u' ', fancy = True), 153 text = item['narrative'] 154 ) 155 decision = dlg.ShowModal() 156 157 if decision != wx.ID_SAVE: 158 return False 159 160 val = dlg.value 161 dlg.Destroy() 162 if val.strip() == u'': 163 return False 164 165 item['narrative'] = val 166 item.save_payload() 167 168 return True 169 #-------------------------- 170 def refresh(lctrl): 171 notes = emr.get_clin_narrative ( 172 encounters = encounters, 173 episodes = episodes, 174 providers = [ gmPerson.gmCurrentProvider()['short_alias'] ] 175 ) 176 lctrl.set_string_items(items = [ 177 [ narr['date'].strftime('%x %H:%M'), 178 gmClinNarrative.soap_cat2l10n[narr['soap_cat']], 179 narr['narrative'].replace('\n', '/').replace('\r', '/') 180 ] for narr in notes 181 ]) 182 lctrl.set_data(data = notes) 183 #-------------------------- 184 185 gmListWidgets.get_choices_from_list ( 186 parent = parent, 187 caption = _('Managing progress notes'), 188 msg = _( 189 '\n' 190 ' This list shows the progress notes by %s.\n' 191 '\n' 192 ) % gmPerson.gmCurrentProvider()['short_alias'], 193 columns = [_('when'), _('type'), _('entry')], 194 single_selection = True, 195 can_return_empty = False, 196 edit_callback = edit, 197 delete_callback = delete, 198 refresh_callback = refresh, 199 ignore_OK_button = True 200 ) 201 #------------------------------------------------------------
202 -def search_narrative_across_emrs(parent=None):
203 204 if parent is None: 205 parent = wx.GetApp().GetTopWindow() 206 207 searcher = wx.TextEntryDialog ( 208 parent = parent, 209 message = _('Enter (regex) term to search for across all EMRs:'), 210 caption = _('Text search across all EMRs'), 211 style = wx.OK | wx.CANCEL | wx.CENTRE 212 ) 213 result = searcher.ShowModal() 214 215 if result != wx.ID_OK: 216 return 217 218 wx.BeginBusyCursor() 219 term = searcher.GetValue() 220 searcher.Destroy() 221 results = gmClinNarrative.search_text_across_emrs(search_term = term) 222 wx.EndBusyCursor() 223 224 if len(results) == 0: 225 gmGuiHelpers.gm_show_info ( 226 _( 227 'Nothing found for search term:\n' 228 ' "%s"' 229 ) % term, 230 _('Search results') 231 ) 232 return 233 234 items = [ [gmPerson.cIdentity(aPK_obj = r['pk_patient'])['description_gender'], r['narrative'], r['src_table']] for r in results ] 235 236 selected_patient = gmListWidgets.get_choices_from_list ( 237 parent = parent, 238 caption = _('Search results for %s') % term, 239 choices = items, 240 columns = [_('Patient'), _('Match'), _('Match location')], 241 data = [ r['pk_patient'] for r in results ], 242 single_selection = True, 243 can_return_empty = False 244 ) 245 246 if selected_patient is None: 247 return 248 249 wx.CallAfter(gmPatSearchWidgets.set_active_patient, patient = gmPerson.cIdentity(aPK_obj = selected_patient))
250 #------------------------------------------------------------
251 -def search_narrative_in_emr(parent=None, patient=None):
252 253 # sanity checks 254 if patient is None: 255 patient = gmPerson.gmCurrentPatient() 256 257 if not patient.connected: 258 gmDispatcher.send(signal = 'statustext', msg = _('Cannot search EMR. No active patient.')) 259 return False 260 261 if parent is None: 262 parent = wx.GetApp().GetTopWindow() 263 264 searcher = wx.TextEntryDialog ( 265 parent = parent, 266 message = _('Enter search term:'), 267 caption = _('Text search of entire EMR of active patient'), 268 style = wx.OK | wx.CANCEL | wx.CENTRE 269 ) 270 result = searcher.ShowModal() 271 272 if result != wx.ID_OK: 273 searcher.Destroy() 274 return False 275 276 wx.BeginBusyCursor() 277 val = searcher.GetValue() 278 searcher.Destroy() 279 emr = patient.get_emr() 280 rows = emr.search_narrative_simple(val) 281 wx.EndBusyCursor() 282 283 if len(rows) == 0: 284 gmGuiHelpers.gm_show_info ( 285 _( 286 'Nothing found for search term:\n' 287 ' "%s"' 288 ) % val, 289 _('Search results') 290 ) 291 return True 292 293 txt = u'' 294 for row in rows: 295 txt += u'%s: %s\n' % ( 296 row['soap_cat'], 297 row['narrative'] 298 ) 299 300 txt += u' %s: %s - %s %s\n' % ( 301 _('Encounter'), 302 row['encounter_started'].strftime('%x %H:%M'), 303 row['encounter_ended'].strftime('%H:%M'), 304 row['encounter_type'] 305 ) 306 txt += u' %s: %s\n' % ( 307 _('Episode'), 308 row['episode'] 309 ) 310 txt += u' %s: %s\n\n' % ( 311 _('Health issue'), 312 row['health_issue'] 313 ) 314 315 msg = _( 316 'Search term was: "%s"\n' 317 '\n' 318 'Search results:\n\n' 319 '%s\n' 320 ) % (val, txt) 321 322 dlg = wx.MessageDialog ( 323 parent = parent, 324 message = msg, 325 caption = _('Search results for %s') % val, 326 style = wx.OK | wx.STAY_ON_TOP 327 ) 328 dlg.ShowModal() 329 dlg.Destroy() 330 331 return True
332 #------------------------------------------------------------
333 -def export_narrative_for_medistar_import(parent=None, soap_cats=u'soap', encounter=None):
334 335 # sanity checks 336 pat = gmPerson.gmCurrentPatient() 337 if not pat.connected: 338 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export EMR for Medistar. No active patient.')) 339 return False 340 341 if encounter is None: 342 encounter = pat.get_emr().active_encounter 343 344 if parent is None: 345 parent = wx.GetApp().GetTopWindow() 346 347 # get file name 348 aWildcard = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files")) 349 # FIXME: make configurable 350 aDefDir = os.path.abspath(os.path.expanduser(os.path.join('~', 'gnumed','export'))) 351 # FIXME: make configurable 352 fname = '%s-%s-%s-%s-%s.txt' % ( 353 'Medistar-MD', 354 time.strftime('%Y-%m-%d',time.localtime()), 355 pat['lastnames'].replace(' ', '-'), 356 pat['firstnames'].replace(' ', '_'), 357 pat.get_formatted_dob(format = '%Y-%m-%d') 358 ) 359 dlg = wx.FileDialog ( 360 parent = parent, 361 message = _("Save EMR extract for MEDISTAR import as..."), 362 defaultDir = aDefDir, 363 defaultFile = fname, 364 wildcard = aWildcard, 365 style = wx.SAVE 366 ) 367 choice = dlg.ShowModal() 368 fname = dlg.GetPath() 369 dlg.Destroy() 370 if choice != wx.ID_OK: 371 return False 372 373 wx.BeginBusyCursor() 374 _log.debug('exporting encounter for medistar import to [%s]', fname) 375 exporter = gmPatientExporter.cMedistarSOAPExporter() 376 successful, fname = exporter.export_to_file ( 377 filename = fname, 378 encounter = encounter, 379 soap_cats = u'soap', 380 export_to_import_file = True 381 ) 382 if not successful: 383 gmGuiHelpers.gm_show_error ( 384 _('Error exporting progress notes for MEDISTAR import.'), 385 _('MEDISTAR progress notes export') 386 ) 387 wx.EndBusyCursor() 388 return False 389 390 gmDispatcher.send(signal = 'statustext', msg = _('Successfully exported progress notes into file [%s] for Medistar import.') % fname, beep=False) 391 392 wx.EndBusyCursor() 393 return True
394 #------------------------------------------------------------
395 -def select_narrative_from_episodes(parent=None, soap_cats=None):
396 """soap_cats needs to be a list""" 397 398 pat = gmPerson.gmCurrentPatient() 399 emr = pat.get_emr() 400 401 if parent is None: 402 parent = wx.GetApp().GetTopWindow() 403 404 selected_soap = {} 405 selected_issue_pks = [] 406 selected_episode_pks = [] 407 selected_narrative_pks = [] 408 409 while 1: 410 # 1) select health issues to select episodes from 411 all_issues = emr.get_health_issues() 412 all_issues.insert(0, gmEMRStructItems.get_dummy_health_issue()) 413 dlg = gmEMRStructWidgets.cIssueListSelectorDlg ( 414 parent = parent, 415 id = -1, 416 issues = all_issues, 417 msg = _('\n In the list below mark the health issues you want to report on.\n') 418 ) 419 selection_idxs = [] 420 for idx in range(len(all_issues)): 421 if all_issues[idx]['pk_health_issue'] in selected_issue_pks: 422 selection_idxs.append(idx) 423 if len(selection_idxs) != 0: 424 dlg.set_selections(selections = selection_idxs) 425 btn_pressed = dlg.ShowModal() 426 selected_issues = dlg.get_selected_item_data() 427 dlg.Destroy() 428 429 if btn_pressed == wx.ID_CANCEL: 430 return selected_soap.values() 431 432 selected_issue_pks = [ i['pk_health_issue'] for i in selected_issues ] 433 434 while 1: 435 # 2) select episodes to select items from 436 all_epis = emr.get_episodes(issues = selected_issue_pks) 437 438 if len(all_epis) == 0: 439 gmDispatcher.send(signal = 'statustext', msg = _('No episodes recorded for the health issues selected.')) 440 break 441 442 dlg = gmEMRStructWidgets.cEpisodeListSelectorDlg ( 443 parent = parent, 444 id = -1, 445 episodes = all_epis, 446 msg = _( 447 '\n These are the episodes known for the health issues just selected.\n\n' 448 ' Now, mark the the episodes you want to report on.\n' 449 ) 450 ) 451 selection_idxs = [] 452 for idx in range(len(all_epis)): 453 if all_epis[idx]['pk_episode'] in selected_episode_pks: 454 selection_idxs.append(idx) 455 if len(selection_idxs) != 0: 456 dlg.set_selections(selections = selection_idxs) 457 btn_pressed = dlg.ShowModal() 458 selected_epis = dlg.get_selected_item_data() 459 dlg.Destroy() 460 461 if btn_pressed == wx.ID_CANCEL: 462 break 463 464 selected_episode_pks = [ i['pk_episode'] for i in selected_epis ] 465 466 # 3) select narrative corresponding to the above constraints 467 all_narr = emr.get_clin_narrative(episodes = selected_episode_pks, soap_cats = soap_cats) 468 469 if len(all_narr) == 0: 470 gmDispatcher.send(signal = 'statustext', msg = _('No narrative available for selected episodes.')) 471 continue 472 473 dlg = cNarrativeListSelectorDlg ( 474 parent = parent, 475 id = -1, 476 narrative = all_narr, 477 msg = _( 478 '\n This is the narrative (type %s) for the chosen episodes.\n\n' 479 ' Now, mark the entries you want to include in your report.\n' 480 ) % u'/'.join([ gmClinNarrative.soap_cat2l10n[cat] for cat in gmTools.coalesce(soap_cats, list(u'soap')) ]) 481 ) 482 selection_idxs = [] 483 for idx in range(len(all_narr)): 484 if all_narr[idx]['pk_narrative'] in selected_narrative_pks: 485 selection_idxs.append(idx) 486 if len(selection_idxs) != 0: 487 dlg.set_selections(selections = selection_idxs) 488 btn_pressed = dlg.ShowModal() 489 selected_narr = dlg.get_selected_item_data() 490 dlg.Destroy() 491 492 if btn_pressed == wx.ID_CANCEL: 493 continue 494 495 selected_narrative_pks = [ i['pk_narrative'] for i in selected_narr ] 496 for narr in selected_narr: 497 selected_soap[narr['pk_narrative']] = narr
498 #------------------------------------------------------------
499 -class cNarrativeListSelectorDlg(gmListWidgets.cGenericListSelectorDlg):
500
501 - def __init__(self, *args, **kwargs):
502 503 narrative = kwargs['narrative'] 504 del kwargs['narrative'] 505 506 gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs) 507 508 self.SetTitle(_('Select the narrative you are interested in ...')) 509 # FIXME: add epi/issue 510 self._LCTRL_items.set_columns([_('when'), _('who'), _('type'), _('entry')]) #, _('Episode'), u'', _('Health Issue')]) 511 # FIXME: date used should be date of encounter, not date_modified 512 self._LCTRL_items.set_string_items ( 513 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 ] 514 ) 515 self._LCTRL_items.set_column_widths() 516 self._LCTRL_items.set_data(data = narrative)
517 #------------------------------------------------------------ 518 from Gnumed.wxGladeWidgets import wxgMoveNarrativeDlg 519
520 -class cMoveNarrativeDlg(wxgMoveNarrativeDlg.wxgMoveNarrativeDlg):
521
522 - def __init__(self, *args, **kwargs):
523 524 self.encounter = kwargs['encounter'] 525 self.source_episode = kwargs['episode'] 526 del kwargs['encounter'] 527 del kwargs['episode'] 528 529 wxgMoveNarrativeDlg.wxgMoveNarrativeDlg.__init__(self, *args, **kwargs) 530 531 self.LBL_source_episode.SetLabel(u'%s%s' % (self.source_episode['description'], gmTools.coalesce(self.source_episode['health_issue'], u'', u' (%s)'))) 532 self.LBL_encounter.SetLabel('%s: %s %s - %s' % ( 533 self.encounter['started'].strftime('%x').decode(gmI18N.get_encoding()), 534 self.encounter['l10n_type'], 535 self.encounter['started'].strftime('%H:%M'), 536 self.encounter['last_affirmed'].strftime('%H:%M') 537 )) 538 pat = gmPerson.gmCurrentPatient() 539 emr = pat.get_emr() 540 narr = emr.get_clin_narrative(episodes=[self.source_episode['pk_episode']], encounters=[self.encounter['pk_encounter']]) 541 if len(narr) == 0: 542 narr = [{'narrative': _('There is no narrative for this episode in this encounter.')}] 543 self.LBL_narrative.SetLabel(u'\n'.join([n['narrative'] for n in narr]))
544 545 #------------------------------------------------------------
546 - def _on_move_button_pressed(self, event):
547 548 target_episode = self._PRW_episode_selector.GetData(can_create = False) 549 550 if target_episode is None: 551 gmDispatcher.send(signal='statustext', msg=_('Must select episode to move narrative to first.')) 552 # FIXME: set to pink 553 self._PRW_episode_selector.SetFocus() 554 return False 555 556 target_episode = gmEMRStructItems.cEpisode(aPK_obj=target_episode) 557 558 self.encounter.transfer_clinical_data ( 559 source_episode = self.source_episode, 560 target_episode = target_episode 561 ) 562 563 if self.IsModal(): 564 self.EndModal(wx.ID_OK) 565 else: 566 self.Close()
567 #============================================================ 568 from Gnumed.wxGladeWidgets import wxgSoapPluginPnl 569
570 -class cSoapPluginPnl(wxgSoapPluginPnl.wxgSoapPluginPnl, gmRegetMixin.cRegetOnPaintMixin):
571 """A panel for in-context editing of progress notes. 572 573 Expects to be used as a notebook page. 574 575 Left hand side: 576 - problem list (health issues and active episodes) 577 - hints area 578 579 Right hand side: 580 - previous notes 581 - notebook with progress note editors 582 - encounter details fields 583 - visual soap area 584 585 Listens to patient change signals, thus acts on the current patient. 586 """
587 - def __init__(self, *args, **kwargs):
588 589 wxgSoapPluginPnl.wxgSoapPluginPnl.__init__(self, *args, **kwargs) 590 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 591 592 self.__pat = gmPerson.gmCurrentPatient() 593 self.__init_ui() 594 self.__reset_ui_content() 595 596 self.__register_interests()
597 #-------------------------------------------------------- 598 # public API 599 #--------------------------------------------------------
600 - def save_encounter(self):
601 602 if not self.__encounter_valid_for_save(): 603 return False 604 605 emr = self.__pat.get_emr() 606 enc = emr.active_encounter 607 608 enc['pk_type'] = self._PRW_encounter_type.GetData() 609 enc['started'] = self._PRW_encounter_start.GetData().get_pydt() 610 enc['last_affirmed'] = self._PRW_encounter_end.GetData().get_pydt() 611 rfe = self._TCTRL_rfe.GetValue().strip() 612 if len(rfe) == 0: 613 enc['reason_for_encounter'] = None 614 else: 615 enc['reason_for_encounter'] = rfe 616 aoe = self._TCTRL_aoe.GetValue().strip() 617 if len(aoe) == 0: 618 enc['assessment_of_encounter'] = None 619 else: 620 enc['assessment_of_encounter'] = aoe 621 622 enc.save_payload() 623 624 return True
625 #-------------------------------------------------------- 626 # internal helpers 627 #--------------------------------------------------------
628 - def __init_ui(self):
629 self._LCTRL_active_problems.set_columns([_('Last'), _('Problem'), _('Health issue')]) 630 self._LCTRL_active_problems.set_string_items() 631 632 self._splitter_main.SetSashGravity(0.5) 633 self._splitter_left.SetSashGravity(0.5) 634 self._splitter_right.SetSashGravity(1.0) 635 self._splitter_soap.SetSashGravity(0.75) 636 637 splitter_size = self._splitter_main.GetSizeTuple()[0] 638 self._splitter_main.SetSashPosition(splitter_size * 3 / 10, True) 639 640 splitter_size = self._splitter_left.GetSizeTuple()[1] 641 self._splitter_left.SetSashPosition(splitter_size * 6 / 20, True) 642 643 splitter_size = self._splitter_right.GetSizeTuple()[1] 644 self._splitter_right.SetSashPosition(splitter_size * 15 / 20, True) 645 646 splitter_size = self._splitter_soap.GetSizeTuple()[0] 647 self._splitter_soap.SetSashPosition(splitter_size * 3 / 4, True) 648 649 self._NB_soap_editors.DeleteAllPages()
650 #--------------------------------------------------------
651 - def __reset_ui_content(self):
652 """ 653 Clear all information from input panel 654 """ 655 self._LCTRL_active_problems.set_string_items() 656 657 self._TCTRL_recent_notes.SetValue(u'') 658 659 self._PRW_encounter_type.SetText(suppress_smarts = True) 660 self._PRW_encounter_start.SetText(suppress_smarts = True) 661 self._PRW_encounter_end.SetText(suppress_smarts = True) 662 self._TCTRL_rfe.SetValue(u'') 663 self._TCTRL_aoe.SetValue(u'') 664 665 self._NB_soap_editors.DeleteAllPages() 666 self._NB_soap_editors.add_editor() 667 668 self._PNL_visual_soap.clear() 669 670 self._lbl_hints.SetLabel(u'')
671 #--------------------------------------------------------
672 - def __refresh_visual_soaps(self):
673 self._PNL_visual_soap.refresh()
674 #--------------------------------------------------------
675 - def __refresh_problem_list(self):
676 """Update health problems list. 677 """ 678 679 self._LCTRL_active_problems.set_string_items() 680 681 emr = self.__pat.get_emr() 682 problems = emr.get_problems ( 683 include_closed_episodes = self._CHBOX_show_closed_episodes.IsChecked(), 684 include_irrelevant_issues = self._CHBOX_irrelevant_issues.IsChecked() 685 ) 686 687 list_items = [] 688 active_problems = [] 689 for problem in problems: 690 if not problem['problem_active']: 691 if not problem['is_potential_problem']: 692 continue 693 694 active_problems.append(problem) 695 696 if problem['type'] == 'issue': 697 issue = emr.problem2issue(problem) 698 last_encounter = emr.get_last_encounter(issue_id = issue['pk_health_issue']) 699 if last_encounter is None: 700 last = issue['modified_when'].strftime('%m/%Y') 701 else: 702 last = last_encounter['last_affirmed'].strftime('%m/%Y') 703 704 list_items.append([last, problem['problem'], gmTools.u_left_arrow]) 705 706 elif problem['type'] == 'episode': 707 epi = emr.problem2episode(problem) 708 last_encounter = emr.get_last_encounter(episode_id = epi['pk_episode']) 709 if last_encounter is None: 710 last = epi['episode_modified_when'].strftime('%m/%Y') 711 else: 712 last = last_encounter['last_affirmed'].strftime('%m/%Y') 713 714 list_items.append ([ 715 last, 716 problem['problem'], 717 gmTools.coalesce(initial = epi['health_issue'], instead = gmTools.u_diameter) 718 ]) 719 720 self._LCTRL_active_problems.set_string_items(items = list_items) 721 self._LCTRL_active_problems.set_column_widths() 722 self._LCTRL_active_problems.set_data(data = active_problems) 723 724 showing_potential_problems = ( 725 self._CHBOX_show_closed_episodes.IsChecked() 726 or 727 self._CHBOX_irrelevant_issues.IsChecked() 728 ) 729 if showing_potential_problems: 730 self._SZR_problem_list_staticbox.SetLabel(_('%s (active+potential) problems') % len(list_items)) 731 else: 732 self._SZR_problem_list_staticbox.SetLabel(_('%s active problems') % len(list_items)) 733 734 return True
735 #--------------------------------------------------------
736 - def __refresh_recent_notes(self, problem=None):
737 """This refreshes the recent-notes part.""" 738 739 if problem is None: 740 soap = u'' 741 caption = u'<?>' 742 743 elif problem['type'] == u'issue': 744 emr = self.__pat.get_emr() 745 soap = u'' 746 caption = problem['problem'][:35] 747 748 prev_enc = emr.get_last_but_one_encounter(issue_id = problem['pk_health_issue']) 749 if prev_enc is not None: 750 soap += prev_enc.format ( 751 with_soap = True, 752 with_docs = False, 753 with_tests = False, 754 patient = self.__pat, 755 issues = [ problem['pk_health_issue'] ], 756 fancy_header = False 757 ) 758 759 tmp = emr.active_encounter.format_soap ( 760 soap_cats = 'soap', 761 emr = emr, 762 issues = [ problem['pk_health_issue'] ], 763 ) 764 if len(tmp) > 0: 765 soap += _('Current encounter:') + u'\n' 766 soap += u'\n'.join(tmp) + u'\n' 767 768 elif problem['type'] == u'episode': 769 emr = self.__pat.get_emr() 770 soap = u'' 771 caption = problem['problem'][:35] 772 773 prev_enc = emr.get_last_but_one_encounter(episode_id = problem['pk_episode']) 774 if prev_enc is None: 775 if problem['pk_health_issue'] is not None: 776 prev_enc = emr.get_last_but_one_encounter(episode_id = problem['pk_health_issue']) 777 if prev_enc is not None: 778 soap += prev_enc.format ( 779 with_soap = True, 780 with_docs = False, 781 with_tests = False, 782 patient = self.__pat, 783 issues = [ problem['pk_health_issue'] ], 784 fancy_header = False 785 ) 786 else: 787 soap += prev_enc.format ( 788 episodes = [ problem['pk_episode'] ], 789 with_soap = True, 790 with_docs = False, 791 with_tests = False, 792 patient = self.__pat, 793 fancy_header = False 794 ) 795 796 tmp = emr.active_encounter.format_soap ( 797 soap_cats = 'soap', 798 emr = emr, 799 issues = [ problem['pk_health_issue'] ], 800 ) 801 if len(tmp) > 0: 802 soap += _('Current encounter:') + u'\n' 803 soap += u'\n'.join(tmp) + u'\n' 804 805 else: 806 soap = u'' 807 caption = u'<?>' 808 809 self._TCTRL_recent_notes.SetValue(soap) 810 self._TCTRL_recent_notes.ShowPosition(self._TCTRL_recent_notes.GetLastPosition()) 811 self._SZR_recent_notes_staticbox.SetLabel(_('Most recent notes on %s%s%s') % ( 812 gmTools.u_left_double_angle_quote, 813 caption, 814 gmTools.u_right_double_angle_quote 815 )) 816 817 self._TCTRL_recent_notes.Refresh() 818 819 return True
820 #--------------------------------------------------------
821 - def __refresh_encounter(self):
822 """Update encounter fields. 823 """ 824 emr = self.__pat.get_emr() 825 enc = emr.active_encounter 826 self._PRW_encounter_type.SetText(value = enc['l10n_type'], data = enc['pk_type']) 827 828 fts = gmDateTime.cFuzzyTimestamp ( 829 timestamp = enc['started'], 830 accuracy = gmDateTime.acc_minutes 831 ) 832 self._PRW_encounter_start.SetText(fts.format_accurately(), data=fts) 833 834 fts = gmDateTime.cFuzzyTimestamp ( 835 timestamp = enc['last_affirmed'], 836 accuracy = gmDateTime.acc_minutes 837 ) 838 self._PRW_encounter_end.SetText(fts.format_accurately(), data=fts) 839 840 self._TCTRL_rfe.SetValue(gmTools.coalesce(enc['reason_for_encounter'], u'')) 841 self._TCTRL_aoe.SetValue(gmTools.coalesce(enc['assessment_of_encounter'], u'')) 842 843 self._PRW_encounter_type.Refresh() 844 self._PRW_encounter_start.Refresh() 845 self._PRW_encounter_end.Refresh() 846 self._TCTRL_rfe.Refresh() 847 self._TCTRL_aoe.Refresh()
848 #--------------------------------------------------------
849 - def __encounter_modified(self):
850 """Assumes that the field data is valid.""" 851 852 emr = self.__pat.get_emr() 853 enc = emr.active_encounter 854 855 data = { 856 'pk_type': self._PRW_encounter_type.GetData(), 857 'reason_for_encounter': gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u''), 858 'assessment_of_encounter': gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u''), 859 'pk_location': enc['pk_location'], 860 'pk_patient': enc['pk_patient'] 861 } 862 863 if self._PRW_encounter_start.GetData() is None: 864 data['started'] = None 865 else: 866 data['started'] = self._PRW_encounter_start.GetData().get_pydt() 867 868 if self._PRW_encounter_end.GetData() is None: 869 data['last_affirmed'] = None 870 else: 871 data['last_affirmed'] = self._PRW_encounter_end.GetData().get_pydt() 872 873 return not enc.same_payload(another_object = data)
874 #--------------------------------------------------------
875 - def __encounter_valid_for_save(self):
876 877 found_error = False 878 879 if self._PRW_encounter_type.GetData() is None: 880 found_error = True 881 msg = _('Cannot save encounter: missing type.') 882 883 if self._PRW_encounter_start.GetData() is None: 884 found_error = True 885 msg = _('Cannot save encounter: missing start time.') 886 887 if self._PRW_encounter_end.GetData() is None: 888 found_error = True 889 msg = _('Cannot save encounter: missing end time.') 890 891 if found_error: 892 gmDispatcher.send(signal = 'statustext', msg = msg, beep = True) 893 return False 894 895 return True
896 #-------------------------------------------------------- 897 # event handling 898 #--------------------------------------------------------
899 - def __register_interests(self):
900 """Configure enabled event signals.""" 901 # client internal signals 902 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection) 903 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection) 904 gmDispatcher.connect(signal = u'episode_mod_db', receiver = self._on_episode_issue_mod_db) 905 gmDispatcher.connect(signal = u'health_issue_mod_db', receiver = self._on_episode_issue_mod_db) 906 gmDispatcher.connect(signal = u'doc_mod_db', receiver = self._on_doc_mod_db) 907 gmDispatcher.connect(signal = u'current_encounter_modified', receiver = self._on_current_encounter_modified) 908 gmDispatcher.connect(signal = u'current_encounter_switched', receiver = self._on_current_encounter_switched) 909 910 # synchronous signals 911 self.__pat.register_pre_selection_callback(callback = self._pre_selection_callback) 912 gmDispatcher.send(signal = u'register_pre_exit_callback', callback = self._pre_exit_callback)
913 #--------------------------------------------------------
914 - def _pre_selection_callback(self):
915 """Another patient is about to be activated. 916 917 Patient change will not proceed before this returns True. 918 """ 919 # don't worry about the encounter here - it will be offered 920 # for editing higher up if anything was saved to the EMR 921 if not self.__pat.connected: 922 return True 923 return self._NB_soap_editors.warn_on_unsaved_soap()
924 #--------------------------------------------------------
925 - def _pre_exit_callback(self):
926 """The client is about to be shut down. 927 928 Shutdown will not proceed before this returns. 929 """ 930 if not self.__pat.connected: 931 return True 932 933 # if self.__encounter_modified(): 934 # do_save_enc = gmGuiHelpers.gm_show_question ( 935 # aMessage = _( 936 # 'You have modified the details\n' 937 # 'of the current encounter.\n' 938 # '\n' 939 # 'Do you want to save those changes ?' 940 # ), 941 # aTitle = _('Starting new encounter') 942 # ) 943 # if do_save_enc: 944 # if not self.save_encounter(): 945 # gmDispatcher.send(signal = u'statustext', msg = _('Error saving current encounter.'), beep = True) 946 947 emr = self.__pat.get_emr() 948 saved = self._NB_soap_editors.save_all_editors ( 949 emr = emr, 950 episode_name_candidates = [ 951 gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u''), 952 gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u'') 953 ] 954 ) 955 if not saved: 956 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save all editors. Some were kept open.'), beep = True) 957 return True
958 #--------------------------------------------------------
959 - def _on_pre_patient_selection(self):
960 wx.CallAfter(self.__on_pre_patient_selection)
961 #--------------------------------------------------------
962 - def __on_pre_patient_selection(self):
963 self.__reset_ui_content()
964 #--------------------------------------------------------
965 - def _on_post_patient_selection(self):
966 wx.CallAfter(self._schedule_data_reget)
967 #--------------------------------------------------------
968 - def _on_doc_mod_db(self):
969 wx.CallAfter(self.__refresh_visual_soaps)
970 #--------------------------------------------------------
971 - def _on_episode_issue_mod_db(self):
972 wx.CallAfter(self._schedule_data_reget)
973 #--------------------------------------------------------
975 wx.CallAfter(self.__refresh_encounter)
976 #--------------------------------------------------------
978 wx.CallAfter(self.__on_current_encounter_switched)
979 #--------------------------------------------------------
981 self.__refresh_encounter() 982 self.__refresh_visual_soaps()
983 #--------------------------------------------------------
984 - def _on_problem_focused(self, event):
985 """Show related note at the bottom.""" 986 pass
987 #--------------------------------------------------------
988 - def _on_problem_selected(self, event):
989 """Show related note at the bottom.""" 990 emr = self.__pat.get_emr() 991 self.__refresh_recent_notes ( 992 problem = self._LCTRL_active_problems.get_selected_item_data(only_one = True) 993 )
994 #--------------------------------------------------------
995 - def _on_problem_activated(self, event):
996 """Open progress note editor for this problem. 997 """ 998 problem = self._LCTRL_active_problems.get_selected_item_data(only_one = True) 999 if problem is None: 1000 return True 1001 1002 dbcfg = gmCfg.cCfgSQL() 1003 allow_duplicate_editors = bool(dbcfg.get2 ( 1004 option = u'horstspace.soap_editor.allow_same_episode_multiple_times', 1005 workplace = gmSurgery.gmCurrentPractice().active_workplace, 1006 bias = u'user', 1007 default = False 1008 )) 1009 if self._NB_soap_editors.add_editor(problem = problem, allow_same_problem = allow_duplicate_editors): 1010 return True 1011 1012 gmGuiHelpers.gm_show_error ( 1013 aMessage = _( 1014 'Cannot open progress note editor for\n\n' 1015 '[%s].\n\n' 1016 ) % problem['problem'], 1017 aTitle = _('opening progress note editor') 1018 ) 1019 event.Skip() 1020 return False
1021 #--------------------------------------------------------
1022 - def _on_discard_editor_button_pressed(self, event):
1023 self._NB_soap_editors.close_current_editor() 1024 event.Skip()
1025 #--------------------------------------------------------
1026 - def _on_new_editor_button_pressed(self, event):
1027 self._NB_soap_editors.add_editor() 1028 event.Skip()
1029 #--------------------------------------------------------
1030 - def _on_clear_editor_button_pressed(self, event):
1031 self._NB_soap_editors.clear_current_editor() 1032 event.Skip()
1033 #--------------------------------------------------------
1034 - def _on_save_all_button_pressed(self, event):
1035 self.save_encounter() 1036 time.sleep(0.3) 1037 event.Skip() 1038 wx.SafeYield() 1039 1040 wx.CallAfter(self._save_all_button_pressed_bottom_half) 1041 wx.SafeYield()
1042 #--------------------------------------------------------
1044 emr = self.__pat.get_emr() 1045 saved = self._NB_soap_editors.save_all_editors ( 1046 emr = emr, 1047 episode_name_candidates = [ 1048 gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u''), 1049 gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u'') 1050 ] 1051 ) 1052 if not saved: 1053 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save all editors. Some were kept open.'), beep = True)
1054 #--------------------------------------------------------
1055 - def _on_save_encounter_button_pressed(self, event):
1056 self.save_encounter() 1057 event.Skip()
1058 #--------------------------------------------------------
1059 - def _on_save_note_button_pressed(self, event):
1060 emr = self.__pat.get_emr() 1061 self._NB_soap_editors.save_current_editor ( 1062 emr = emr, 1063 episode_name_candidates = [ 1064 gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u''), 1065 gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u'') 1066 ] 1067 ) 1068 event.Skip()
1069 #--------------------------------------------------------
1070 - def _on_new_encounter_button_pressed(self, event):
1071 1072 if self.__encounter_modified(): 1073 do_save_enc = gmGuiHelpers.gm_show_question ( 1074 aMessage = _( 1075 'You have modified the details\n' 1076 'of the current encounter.\n' 1077 '\n' 1078 'Do you want to save those changes ?' 1079 ), 1080 aTitle = _('Starting new encounter') 1081 ) 1082 if do_save_enc: 1083 if not self.save_encounter(): 1084 gmDispatcher.send(signal = u'statustext', msg = _('Error saving current encounter.'), beep = True) 1085 return False 1086 1087 emr = self.__pat.get_emr() 1088 gmDispatcher.send(signal = u'statustext', msg = _('Started new encounter for active patient.'), beep = True) 1089 1090 event.Skip() 1091 1092 wx.CallAfter(gmEMRStructWidgets.start_new_encounter, emr = emr)
1093 #--------------------------------------------------------
1094 - def _on_show_closed_episodes_checked(self, event):
1095 self.__refresh_problem_list()
1096 #--------------------------------------------------------
1097 - def _on_irrelevant_issues_checked(self, event):
1098 self.__refresh_problem_list()
1099 #-------------------------------------------------------- 1100 # reget mixin API 1101 #--------------------------------------------------------
1102 - def _populate_with_data(self):
1103 self.__refresh_problem_list() 1104 self.__refresh_encounter() 1105 self.__refresh_visual_soaps() 1106 return True
1107 #============================================================
1108 -class cSoapNoteInputNotebook(wx.Notebook):
1109 """A notebook holding panels with progress note editors. 1110 1111 There can be one or several progress note editor panel 1112 for each episode being worked on. The editor class in 1113 each panel is configurable. 1114 1115 There will always be one open editor. 1116 """
1117 - def __init__(self, *args, **kwargs):
1118 1119 kwargs['style'] = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER 1120 1121 wx.Notebook.__init__(self, *args, **kwargs)
1122 #-------------------------------------------------------- 1123 # public API 1124 #--------------------------------------------------------
1125 - def add_editor(self, problem=None, allow_same_problem=False):
1126 """Add a progress note editor page. 1127 1128 The way <allow_same_problem> is currently used in callers 1129 it only applies to unassociated episodes. 1130 """ 1131 problem_to_add = problem 1132 1133 # determine label 1134 if problem_to_add is None: 1135 label = _('new problem') 1136 else: 1137 # normalize problem type 1138 if isinstance(problem_to_add, gmEMRStructItems.cEpisode): 1139 problem_to_add = gmEMRStructItems.episode2problem(episode = problem_to_add) 1140 1141 elif isinstance(problem_to_add, gmEMRStructItems.cHealthIssue): 1142 problem_to_add = gmEMRStructItems.health_issue2problem(episode = problem_to_add) 1143 1144 if not isinstance(problem_to_add, gmEMRStructItems.cProblem): 1145 raise TypeError('cannot open progress note editor for [%s]' % problem_to_add) 1146 1147 label = problem_to_add['problem'] 1148 # FIXME: configure maximum length 1149 if len(label) > 23: 1150 label = label[:21] + gmTools.u_ellipsis 1151 1152 # new unassociated problem or dupes allowed 1153 if (problem_to_add is None) or allow_same_problem: 1154 new_page = cSoapNoteExpandoEditAreaPnl(parent = self, id = -1, problem = problem_to_add) 1155 result = self.AddPage ( 1156 page = new_page, 1157 text = label, 1158 select = True 1159 ) 1160 return result 1161 1162 # real problem, no dupes allowed 1163 # - raise existing editor 1164 for page_idx in range(self.GetPageCount()): 1165 page = self.GetPage(page_idx) 1166 1167 # editor is for unassociated new problem 1168 if page.problem is None: 1169 continue 1170 1171 # editor is for episode 1172 if page.problem['type'] == 'episode': 1173 if page.problem['pk_episode'] == problem_to_add['pk_episode']: 1174 self.SetSelection(page_idx) 1175 gmDispatcher.send(signal = u'statustext', msg = u'Raising existing editor.', beep = True) 1176 return True 1177 continue 1178 1179 # editor is for health issue 1180 if page.problem['type'] == 'issue': 1181 if page.problem['pk_health_issue'] == problem_to_add['pk_health_issue']: 1182 self.SetSelection(page_idx) 1183 gmDispatcher.send(signal = u'statustext', msg = u'Raising existing editor.', beep = True) 1184 return True 1185 continue 1186 1187 # - or add new editor 1188 new_page = cSoapNoteExpandoEditAreaPnl(parent = self, id = -1, problem = problem_to_add) 1189 result = self.AddPage ( 1190 page = new_page, 1191 text = label, 1192 select = True 1193 ) 1194 1195 return result
1196 #--------------------------------------------------------
1197 - def close_current_editor(self):
1198 1199 page_idx = self.GetSelection() 1200 page = self.GetPage(page_idx) 1201 1202 if not page.empty: 1203 really_discard = gmGuiHelpers.gm_show_question ( 1204 _('Are you sure you really want to\n' 1205 'discard this progress note ?\n' 1206 ), 1207 _('Discarding progress note') 1208 ) 1209 if really_discard is False: 1210 return 1211 1212 self.DeletePage(page_idx) 1213 1214 # always keep one unassociated editor open 1215 if self.GetPageCount() == 0: 1216 self.add_editor()
1217 #--------------------------------------------------------
1218 - def save_current_editor(self, emr=None, episode_name_candidates=None):
1219 1220 page_idx = self.GetSelection() 1221 page = self.GetPage(page_idx) 1222 1223 if not page.save(emr = emr, episode_name_candidates = episode_name_candidates): 1224 return 1225 1226 self.DeletePage(page_idx) 1227 1228 # always keep one unassociated editor open 1229 if self.GetPageCount() == 0: 1230 self.add_editor()
1231 #--------------------------------------------------------
1232 - def warn_on_unsaved_soap(self):
1233 for page_idx in range(self.GetPageCount()): 1234 page = self.GetPage(page_idx) 1235 if page.empty: 1236 continue 1237 1238 gmGuiHelpers.gm_show_warning ( 1239 _('There are unsaved progress notes !\n'), 1240 _('Unsaved progress notes') 1241 ) 1242 return False 1243 1244 return True
1245 #--------------------------------------------------------
1246 - def save_all_editors(self, emr=None, episode_name_candidates=None):
1247 1248 _log.debug('saving editors: %s', self.GetPageCount()) 1249 1250 all_closed = True 1251 for page_idx in range((self.GetPageCount() - 1), 0, -1): 1252 _log.debug('#%s of %s', page_idx, self.GetPageCount()) 1253 try: 1254 self.ChangeSelection(page_idx) 1255 _log.debug('editor raised') 1256 except: 1257 _log.exception('cannot raise editor') 1258 page = self.GetPage(page_idx) 1259 if page.save(emr = emr, episode_name_candidates = episode_name_candidates): 1260 _log.debug('saved, deleting now') 1261 self.DeletePage(page_idx) 1262 else: 1263 _log.debug('not saved, not deleting') 1264 all_closed = False 1265 1266 # always keep one unassociated editor open 1267 if self.GetPageCount() == 0: 1268 self.add_editor() 1269 1270 return (all_closed is True)
1271 #--------------------------------------------------------
1272 - def clear_current_editor(self):
1273 page_idx = self.GetSelection() 1274 page = self.GetPage(page_idx) 1275 page.clear()
1276 #--------------------------------------------------------
1277 - def get_current_problem(self):
1278 page_idx = self.GetSelection() 1279 page = self.GetPage(page_idx) 1280 return page.problem
1281 #============================================================ 1282 from Gnumed.wxGladeWidgets import wxgSoapNoteExpandoEditAreaPnl 1283
1284 -class cSoapNoteExpandoEditAreaPnl(wxgSoapNoteExpandoEditAreaPnl.wxgSoapNoteExpandoEditAreaPnl):
1285
1286 - def __init__(self, *args, **kwargs):
1287 1288 try: 1289 self.problem = kwargs['problem'] 1290 del kwargs['problem'] 1291 except KeyError: 1292 self.problem = None 1293 1294 wxgSoapNoteExpandoEditAreaPnl.wxgSoapNoteExpandoEditAreaPnl.__init__(self, *args, **kwargs) 1295 1296 self.fields = [ 1297 self._TCTRL_Soap, 1298 self._TCTRL_sOap, 1299 self._TCTRL_soAp, 1300 self._TCTRL_soaP 1301 ] 1302 1303 self.__register_interests()
1304 #--------------------------------------------------------
1305 - def clear(self):
1306 for field in self.fields: 1307 field.SetValue(u'')
1308 #--------------------------------------------------------
1309 - def save(self, emr=None, episode_name_candidates=None):
1310 1311 if self.empty: 1312 return True 1313 1314 # new unassociated episode 1315 if (self.problem is None) or (self.problem['type'] == 'issue'): 1316 1317 episode_name_candidates.append(u'') 1318 for candidate in episode_name_candidates: 1319 if candidate is None: 1320 continue 1321 epi_name = candidate.strip().replace('\r', '//').replace('\n', '//') 1322 break 1323 1324 dlg = wx.TextEntryDialog ( 1325 parent = self, 1326 message = _('Enter a short working name for this new problem:'), 1327 caption = _('Creating a problem (episode) to save the notelet under ...'), 1328 defaultValue = epi_name, 1329 style = wx.OK | wx.CANCEL | wx.CENTRE 1330 ) 1331 decision = dlg.ShowModal() 1332 if decision != wx.ID_OK: 1333 return False 1334 1335 epi_name = dlg.GetValue().strip() 1336 if epi_name == u'': 1337 gmGuiHelpers.gm_show_error(_('Cannot save a new problem without a name.'), _('saving progress note')) 1338 return False 1339 1340 # create episode 1341 new_episode = emr.add_episode(episode_name = epi_name[:45], pk_health_issue = None, is_open = True) 1342 1343 if self.problem is not None: 1344 issue = emr.problem2issue(self.problem) 1345 if not gmEMRStructWidgets.move_episode_to_issue(episode = new_episode, target_issue = issue, save_to_backend = True): 1346 gmGuiHelpers.gm_show_warning ( 1347 _( 1348 'The new episode:\n' 1349 '\n' 1350 ' "%s"\n' 1351 '\n' 1352 'will remain unassociated despite the editor\n' 1353 'having been invoked from the health issue:\n' 1354 '\n' 1355 ' "%s"' 1356 ) % ( 1357 new_episode['description'], 1358 issue['description'] 1359 ), 1360 _('saving progress note') 1361 ) 1362 1363 epi_id = new_episode['pk_episode'] 1364 else: 1365 epi_id = self.problem['pk_episode'] 1366 1367 emr.add_notes(notes = self.soap, episode = epi_id) 1368 1369 return True
1370 #-------------------------------------------------------- 1371 # event handling 1372 #--------------------------------------------------------
1373 - def __register_interests(self):
1374 for field in self.fields: 1375 wxexpando.EVT_ETC_LAYOUT_NEEDED(field, field.GetId(), self._on_expando_needs_layout)
1376 #--------------------------------------------------------
1377 - def _on_expando_needs_layout(self, evt):
1378 # need to tell ourselves to re-Layout to refresh scroll bars 1379 1380 # provoke adding scrollbar if needed 1381 self.Fit() 1382 1383 if self.HasScrollbar(wx.VERTICAL): 1384 # scroll panel to show cursor 1385 expando = self.FindWindowById(evt.GetId()) 1386 y_expando = expando.GetPositionTuple()[1] 1387 h_expando = expando.GetSizeTuple()[1] 1388 line_cursor = expando.PositionToXY(expando.GetInsertionPoint())[1] + 1 1389 y_cursor = int(round((float(line_cursor) / expando.NumberOfLines) * h_expando)) 1390 y_desired_visible = y_expando + y_cursor 1391 1392 y_view = self.ViewStart[1] 1393 h_view = self.GetClientSizeTuple()[1] 1394 1395 # print "expando:", y_expando, "->", h_expando, ", lines:", expando.NumberOfLines 1396 # print "cursor :", y_cursor, "at line", line_cursor, ", insertion point:", expando.GetInsertionPoint() 1397 # print "wanted :", y_desired_visible 1398 # print "view-y :", y_view 1399 # print "scroll2:", h_view 1400 1401 # expando starts before view 1402 if y_desired_visible < y_view: 1403 # print "need to scroll up" 1404 self.Scroll(0, y_desired_visible) 1405 1406 if y_desired_visible > h_view: 1407 # print "need to scroll down" 1408 self.Scroll(0, y_desired_visible)
1409 #-------------------------------------------------------- 1410 # properties 1411 #--------------------------------------------------------
1412 - def _get_soap(self):
1413 note = [] 1414 1415 tmp = self._TCTRL_Soap.GetValue().strip() 1416 if tmp != u'': 1417 note.append(['s', tmp]) 1418 1419 tmp = self._TCTRL_sOap.GetValue().strip() 1420 if tmp != u'': 1421 note.append(['o', tmp]) 1422 1423 tmp = self._TCTRL_soAp.GetValue().strip() 1424 if tmp != u'': 1425 note.append(['a', tmp]) 1426 1427 tmp = self._TCTRL_soaP.GetValue().strip() 1428 if tmp != u'': 1429 note.append(['p', tmp]) 1430 1431 return note
1432 1433 soap = property(_get_soap, lambda x:x) 1434 #--------------------------------------------------------
1435 - def _get_empty(self):
1436 for field in self.fields: 1437 if field.GetValue().strip() != u'': 1438 return False 1439 return True
1440 1441 empty = property(_get_empty, lambda x:x)
1442 #============================================================
1443 -class cSoapLineTextCtrl(wxexpando.ExpandoTextCtrl):
1444
1445 - def __init__(self, *args, **kwargs):
1446 1447 wxexpando.ExpandoTextCtrl.__init__(self, *args, **kwargs) 1448 1449 self.__keyword_separators = regex.compile("[!?'\".,:;)}\]\r\n\s\t]+") 1450 1451 self.__register_interests()
1452 #------------------------------------------------ 1453 # event handling 1454 #------------------------------------------------
1455 - def __register_interests(self):
1456 #wx.EVT_KEY_DOWN (self, self.__on_key_down) 1457 #wx.EVT_KEY_UP (self, self.__OnKeyUp) 1458 wx.EVT_CHAR(self, self.__on_char) 1459 wx.EVT_SET_FOCUS(self, self.__on_focus)
1460 #--------------------------------------------------------
1461 - def __on_focus(self, evt):
1462 evt.Skip() 1463 wx.CallAfter(self._after_on_focus)
1464 #--------------------------------------------------------
1465 - def _after_on_focus(self):
1466 evt = wx.PyCommandEvent(wxexpando.wxEVT_ETC_LAYOUT_NEEDED, self.GetId()) 1467 evt.SetEventObject(self) 1468 evt.height = None 1469 evt.numLines = None 1470 self.GetEventHandler().ProcessEvent(evt)
1471 #--------------------------------------------------------
1472 - def __on_char(self, evt):
1473 char = unichr(evt.GetUnicodeKey()) 1474 1475 if self.LastPosition == 1: 1476 evt.Skip() 1477 return 1478 1479 explicit_expansion = False 1480 if evt.GetModifiers() == (wx.MOD_CMD | wx.MOD_ALT): # portable CTRL-ALT-... 1481 if evt.GetKeyCode() != 13: 1482 evt.Skip() 1483 return 1484 explicit_expansion = True 1485 1486 if not explicit_expansion: 1487 if self.__keyword_separators.match(char) is None: 1488 evt.Skip() 1489 return 1490 1491 caret_pos, line_no = self.PositionToXY(self.InsertionPoint) 1492 line = self.GetLineText(line_no) 1493 word = self.__keyword_separators.split(line[:caret_pos])[-1] 1494 1495 if ( 1496 (not explicit_expansion) 1497 and 1498 (word != u'$$steffi') # Easter Egg ;-) 1499 and 1500 (word not in [ r[0] for r in gmPG2.get_text_expansion_keywords() ]) 1501 ): 1502 evt.Skip() 1503 return 1504 1505 start = self.InsertionPoint - len(word) 1506 wx.CallAfter(self.replace_keyword_with_expansion, word, start, explicit_expansion) 1507 1508 evt.Skip() 1509 return
1510 #------------------------------------------------
1511 - def replace_keyword_with_expansion(self, keyword=None, position=None, show_list=False):
1512 1513 if show_list: 1514 candidates = gmPG2.get_keyword_expansion_candidates(keyword = keyword) 1515 if len(candidates) == 0: 1516 return 1517 if len(candidates) == 1: 1518 keyword = candidates[0] 1519 else: 1520 keyword = gmListWidgets.get_choices_from_list ( 1521 parent = self, 1522 msg = _( 1523 'Several macros match the keyword [%s].\n' 1524 '\n' 1525 'Please select the expansion you want to happen.' 1526 ) % keyword, 1527 caption = _('Selecting text macro'), 1528 choices = candidates, 1529 columns = [_('Keyword')], 1530 single_selection = True, 1531 can_return_empty = False 1532 ) 1533 if keyword is None: 1534 return 1535 1536 expansion = gmPG2.expand_keyword(keyword = keyword) 1537 1538 if expansion is None: 1539 return 1540 1541 if expansion == u'': 1542 return 1543 1544 self.Replace ( 1545 position, 1546 position + len(keyword), 1547 expansion 1548 ) 1549 1550 self.SetInsertionPoint(position + len(expansion) + 1) 1551 self.ShowPosition(position + len(expansion) + 1) 1552 1553 return
1554 #============================================================ 1555 # visual progress notes 1556 #============================================================ 1557 visual_progress_note_document_type = u'visual progress note' 1558 1559 #============================================================
1560 -def configure_visual_progress_note_editor():
1561 1562 def is_valid(value): 1563 1564 if value is None: 1565 gmDispatcher.send ( 1566 signal = 'statustext', 1567 msg = _('You need to actually set an editor.'), 1568 beep = True 1569 ) 1570 return False, value 1571 1572 if value.strip() == u'': 1573 gmDispatcher.send ( 1574 signal = 'statustext', 1575 msg = _('You need to actually set an editor.'), 1576 beep = True 1577 ) 1578 return False, value 1579 1580 found, binary = gmShellAPI.detect_external_binary(value) 1581 if not found: 1582 gmDispatcher.send ( 1583 signal = 'statustext', 1584 msg = _('The command [%s] is not found.') % value, 1585 beep = True 1586 ) 1587 return True, value 1588 1589 return True, binary
1590 #------------------------------------------ 1591 gmCfgWidgets.configure_string_option ( 1592 message = _( 1593 'Enter the shell command with which to start\n' 1594 'the image editor for visual progress notes.\n' 1595 '\n' 1596 'Any "%(img)s" included with the arguments\n' 1597 'will be replaced by the file name of the\n' 1598 'note template.' 1599 ), 1600 option = u'external.tools.visual_soap_editor_cmd', 1601 bias = 'user', 1602 default_value = None, 1603 validator = is_valid 1604 ) 1605 #============================================================
1606 -def edit_visual_progress_note(filename=None, episode=None, discard_unmodified=False, doc_part=None):
1607 """This assumes <filename> contains an image which can be handled by the configured image editor.""" 1608 1609 if doc_part is not None: 1610 filename = doc_part.export_to_file() 1611 if filename is None: 1612 gmDispatcher.send(signal = u'statustext', msg = _('Cannot export visual progress note to file.')) 1613 return None 1614 1615 dbcfg = gmCfg.cCfgSQL() 1616 cmd = dbcfg.get2 ( 1617 option = u'external.tools.visual_soap_editor_cmd', 1618 workplace = gmSurgery.gmCurrentPractice().active_workplace, 1619 bias = 'user' 1620 ) 1621 1622 if cmd is None: 1623 gmDispatcher.send(signal = u'statustext', msg = _('Editor for visual progress note not configured.'), beep = False) 1624 cmd = configure_visual_progress_note_editor() 1625 if cmd is None: 1626 gmDispatcher.send(signal = u'statustext', msg = _('Editor for visual progress note not configured.'), beep = True) 1627 return None 1628 1629 if u'%(img)s' in cmd: 1630 cmd % {u'img': filename} 1631 else: 1632 cmd = u'%s %s' % (cmd, filename) 1633 1634 if discard_unmodified: 1635 original_stat = os.stat(filename) 1636 original_md5 = gmTools.file2md5(filename) 1637 1638 success = gmShellAPI.run_command_in_shell(cmd, blocking = True) 1639 if not success: 1640 gmGuiHelpers.gm_show_error ( 1641 _( 1642 'There was a problem with running the editor\n' 1643 'for visual progress notes.\n' 1644 '\n' 1645 ' [%s]\n' 1646 '\n' 1647 ) % cmd, 1648 _('Editing visual progress note') 1649 ) 1650 return None 1651 1652 try: 1653 open(filename, 'r').close() 1654 except StandardError: 1655 _log.exception('problem accessing visual progress note file [%s]', filename) 1656 gmGuiHelpers.gm_show_error ( 1657 _( 1658 'There was a problem reading the visual\n' 1659 'progress note from the file:\n' 1660 '\n' 1661 ' [%s]\n' 1662 '\n' 1663 ) % filename, 1664 _('Saving visual progress note') 1665 ) 1666 return None 1667 1668 if discard_unmodified: 1669 modified_stat = os.stat(filename) 1670 # same size ? 1671 if original_stat.st_size == modified_stat.st_size: 1672 modified_md5 = gmTools.file2md5(filename) 1673 # same hash ? 1674 if original_md5 == modified_md5: 1675 _log.debug('visual progress note (template) not modified') 1676 # ask user to decide 1677 msg = _( 1678 u'This visual progress note was created from a\n' 1679 u'template in the database rather than from a file\n' 1680 u'but the image was not modified at all.\n' 1681 u'\n' 1682 u'Do you want to still save the unmodified\n' 1683 u'image as a visual progress note into the\n' 1684 u'EMR of the patient ?' 1685 ) 1686 save_unmodified = gmGuiHelpers.gm_show_question ( 1687 msg, 1688 _('Saving visual progress note') 1689 ) 1690 if not save_unmodified: 1691 _log.debug('user discarded unmodified note') 1692 return 1693 1694 if doc_part is not None: 1695 doc_part.update_data_from_file(fname = filename) 1696 doc_part.set_reviewed(technically_abnormal = False, clinically_relevant = True) 1697 return None 1698 1699 if not isinstance(episode, gmEMRStructItems.cEpisode): 1700 pat = gmPerson.gmCurrentPatient() 1701 emr = pat.get_emr() 1702 episode = emr.add_episode(episode_name = episode.strip(), is_open = False) 1703 1704 doc = gmDocumentWidgets.save_file_as_new_document ( 1705 filename = filename, 1706 document_type = visual_progress_note_document_type, 1707 episode = episode, 1708 unlock_patient = True 1709 ) 1710 doc.set_reviewed(technically_abnormal = False, clinically_relevant = True) 1711 1712 return doc
1713 #============================================================
1714 -class cVisualSoapTemplatePhraseWheel(gmPhraseWheel.cPhraseWheel):
1715 """Phrasewheel to allow selection of visual SOAP template.""" 1716
1717 - def __init__(self, *args, **kwargs):
1718 1719 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs) 1720 1721 query = u""" 1722 SELECT 1723 pk, 1724 name_short 1725 FROM 1726 ref.paperwork_templates 1727 WHERE 1728 fk_template_type = (SELECT pk FROM ref.form_types WHERE name = '%s') AND ( 1729 name_long %%(fragment_condition)s 1730 OR 1731 name_short %%(fragment_condition)s 1732 ) 1733 ORDER BY name_short 1734 LIMIT 15 1735 """ % visual_progress_note_document_type 1736 1737 mp = gmMatchProvider.cMatchProvider_SQL2(queries = [query]) 1738 mp.setThresholds(2, 3, 5) 1739 1740 self.matcher = mp 1741 self.selection_only = True
1742 #--------------------------------------------------------
1743 - def _data2instance(self):
1744 if self.data is None: 1745 return None 1746 1747 return gmForms.cFormTemplate(aPK_obj = self.data)
1748 #============================================================ 1749 from Gnumed.wxGladeWidgets import wxgVisualSoapPnl 1750
1751 -class cVisualSoapPnl(wxgVisualSoapPnl.wxgVisualSoapPnl):
1752
1753 - def __init__(self, *args, **kwargs):
1754 1755 wxgVisualSoapPnl.wxgVisualSoapPnl.__init__(self, *args, **kwargs) 1756 1757 # dummy episode to hold images 1758 self.default_episode_name = _('visual progress notes')
1759 #-------------------------------------------------------- 1760 # external API 1761 #--------------------------------------------------------
1762 - def clear(self):
1763 self._PRW_template.SetText(value = u'', data = None) 1764 self._LCTRL_visual_soaps.set_columns([_('Sketches')]) 1765 self._LCTRL_visual_soaps.set_string_items() 1766 1767 self.show_image_and_metadata()
1768 #--------------------------------------------------------
1769 - def refresh(self, patient=None, encounter=None):
1770 1771 self.clear() 1772 1773 if patient is None: 1774 patient = gmPerson.gmCurrentPatient() 1775 1776 if not patient.connected: 1777 return 1778 1779 emr = patient.get_emr() 1780 if encounter is None: 1781 encounter = emr.active_encounter 1782 1783 folder = patient.get_document_folder() 1784 soap_docs = folder.get_documents ( 1785 doc_type = visual_progress_note_document_type, 1786 encounter = encounter['pk_encounter'] 1787 ) 1788 1789 if len(soap_docs) == 0: 1790 self._BTN_delete.Enable(False) 1791 return 1792 1793 self._LCTRL_visual_soaps.set_string_items ([ 1794 u'%s%s%s' % ( 1795 gmTools.coalesce(sd['comment'], u'', u'%s\n'), 1796 gmTools.coalesce(sd['ext_ref'], u'', u'%s\n'), 1797 sd['episode'] 1798 ) for sd in soap_docs 1799 ]) 1800 self._LCTRL_visual_soaps.set_data(soap_docs) 1801 1802 self._BTN_delete.Enable(True)
1803 #--------------------------------------------------------
1804 - def show_image_and_metadata(self, doc=None):
1805 1806 if doc is None: 1807 self._IMG_soap.SetBitmap(wx.NullBitmap) 1808 self._PRW_episode.SetText() 1809 #self._PRW_comment.SetText(value = u'', data = None) 1810 self._PRW_comment.SetValue(u'') 1811 return 1812 1813 parts = doc.parts 1814 if len(parts) == 0: 1815 gmDispatcher.send(signal = u'statustext', msg = _('No images in visual progress note.')) 1816 return 1817 1818 fname = parts[0].export_to_file() 1819 if fname is None: 1820 gmDispatcher.send(signal = u'statustext', msg = _('Cannot export visual progress note to file.')) 1821 return 1822 1823 img_data = None 1824 rescaled_width = 300 1825 try: 1826 img_data = wx.Image(fname, wx.BITMAP_TYPE_ANY) 1827 current_width = img_data.GetWidth() 1828 current_height = img_data.GetHeight() 1829 rescaled_height = (rescaled_width * current_height) / current_width 1830 img_data.Rescale(rescaled_width, rescaled_height, quality = wx.IMAGE_QUALITY_HIGH) # w, h 1831 bmp_data = wx.BitmapFromImage(img_data) 1832 except: 1833 _log.exception('cannot load visual progress note from [%s]', fname) 1834 gmDispatcher.send(signal = u'statustext', msg = _('Cannot load visual progress note from [%s].') % fname) 1835 del img_data 1836 return 1837 1838 del img_data 1839 self._IMG_soap.SetBitmap(bmp_data) 1840 1841 self._PRW_episode.SetText(value = doc['episode'], data = doc['pk_episode']) 1842 if doc['comment'] is not None: 1843 self._PRW_comment.SetValue(doc['comment'].strip())
1844 #-------------------------------------------------------- 1845 # event handlers 1846 #--------------------------------------------------------
1847 - def _on_visual_soap_selected(self, event):
1848 1849 doc = self._LCTRL_visual_soaps.get_selected_item_data(only_one = True) 1850 self.show_image_and_metadata(doc = doc) 1851 if doc is None: 1852 return 1853 1854 self._BTN_delete.Enable(True)
1855 #--------------------------------------------------------
1856 - def _on_visual_soap_deselected(self, event):
1857 self._BTN_delete.Enable(False)
1858 #--------------------------------------------------------
1859 - def _on_visual_soap_activated(self, event):
1860 1861 doc = self._LCTRL_visual_soaps.get_selected_item_data(only_one = True) 1862 if doc is None: 1863 self.show_image_and_metadata() 1864 return 1865 1866 parts = doc.parts 1867 if len(parts) == 0: 1868 gmDispatcher.send(signal = u'statustext', msg = _('No images in visual progress note.')) 1869 return 1870 1871 edit_visual_progress_note(doc_part = parts[0], discard_unmodified = True) 1872 self.show_image_and_metadata(doc = doc) 1873 1874 self._BTN_delete.Enable(True)
1875 #--------------------------------------------------------
1876 - def _on_from_template_button_pressed(self, event):
1877 1878 template = self._PRW_template.GetData(as_instance = True) 1879 if template is None: 1880 return 1881 1882 filename = template.export_to_file() 1883 if filename is None: 1884 gmDispatcher.send(signal = u'statustext', msg = _('Cannot export visual progress note template for [%s].') % template['name_long']) 1885 return 1886 1887 episode = self._PRW_episode.GetData(as_instance = True) 1888 if episode is None: 1889 episode = self._PRW_episode.GetValue().strip() 1890 if episode == u'': 1891 episode = self.default_episode_name 1892 1893 # do not store note if not modified -- change if users complain 1894 doc = edit_visual_progress_note(filename = filename, episode = episode, discard_unmodified = True) 1895 if doc is None: 1896 return 1897 1898 if self._PRW_comment.GetValue().strip() == u'': 1899 doc['comment'] = template['instance_type'] 1900 else: 1901 doc['comment'] = self._PRW_comment.GetValue().strip() 1902 1903 doc.save() 1904 self.show_image_and_metadata(doc = doc)
1905 #--------------------------------------------------------
1906 - def _on_from_file_button_pressed(self, event):
1907 1908 dlg = wx.FileDialog ( 1909 parent = self, 1910 message = _('Choose a visual progress note template file'), 1911 defaultDir = os.path.expanduser('~'), 1912 defaultFile = '', 1913 #wildcard = "%s (*)|*|%s (*.*)|*.*" % (_('all files'), _('all files (Win)')), 1914 style = wx.OPEN | wx.HIDE_READONLY | wx.FILE_MUST_EXIST 1915 ) 1916 result = dlg.ShowModal() 1917 if result == wx.ID_CANCEL: 1918 dlg.Destroy() 1919 return 1920 1921 full_filename = dlg.GetPath() 1922 dlg.Hide() 1923 dlg.Destroy() 1924 1925 # create a copy of the picked file -- don't modify the original 1926 ext = os.path.splitext(full_filename)[1] 1927 tmp_name = gmTools.get_unique_filename(suffix = ext) 1928 _log.debug('visual progress note from file: [%s] -> [%s]', full_filename, tmp_name) 1929 shutil.copy2(full_filename, tmp_name) 1930 1931 episode = self._PRW_episode.GetData(as_instance = True) 1932 if episode is None: 1933 episode = self._PRW_episode.GetValue().strip() 1934 if episode == u'': 1935 episode = self.default_episode_name 1936 1937 # always store note even if unmodified as we 1938 # may simply want to store a clinical photograph 1939 doc = edit_visual_progress_note(filename = tmp_name, episode = episode, discard_unmodified = False) 1940 if self._PRW_comment.GetValue().strip() == u'': 1941 # use filename as default comment (w/o extension) 1942 doc['comment'] = os.path.splitext(os.path.split(full_filename)[1])[0] 1943 else: 1944 doc['comment'] = self._PRW_comment.GetValue().strip() 1945 doc.save() 1946 self.show_image_and_metadata(doc = doc) 1947 1948 try: 1949 os.remove(tmp_name) 1950 except StandardError: 1951 _log.exception('cannot remove [%s]', tmp_name) 1952 1953 remove_original = gmGuiHelpers.gm_show_question ( 1954 _( 1955 'Do you want to delete the original file\n' 1956 '\n' 1957 ' [%s]\n' 1958 '\n' 1959 'from your computer ?' 1960 ) % full_filename, 1961 _('Saving visual progress note ...') 1962 ) 1963 if remove_original: 1964 try: 1965 os.remove(full_filename) 1966 except StandardError: 1967 _log.exception('cannot remove [%s]', full_filename)
1968 #--------------------------------------------------------
1969 - def _on_delete_button_pressed(self, event):
1970 1971 doc = self._LCTRL_visual_soaps.get_selected_item_data(only_one = True) 1972 if doc is None: 1973 self.show_image_and_metadata() 1974 return 1975 1976 delete_it = gmGuiHelpers.gm_show_question ( 1977 aMessage = _('Are you sure you want to delete the visual progress note ?'), 1978 aTitle = _('Deleting visual progress note') 1979 ) 1980 if delete_it is True: 1981 gmDocuments.delete_document ( 1982 document_id = doc['pk_doc'], 1983 encounter_id = doc['pk_encounter'] 1984 ) 1985 self.show_image_and_metadata()
1986 #============================================================ 1987 # main 1988 #------------------------------------------------------------ 1989 if __name__ == '__main__': 1990 1991 if len(sys.argv) < 2: 1992 sys.exit() 1993 1994 if sys.argv[1] != 'test': 1995 sys.exit() 1996 1997 gmI18N.activate_locale() 1998 gmI18N.install_domain(domain = 'gnumed') 1999 2000 #----------------------------------------
2001 - def test_select_narrative_from_episodes():
2002 pat = gmPersonSearch.ask_for_patient() 2003 gmPatSearchWidgets.set_active_patient(patient = pat) 2004 app = wx.PyWidgetTester(size = (200, 200)) 2005 sels = select_narrative_from_episodes() 2006 print "selected:" 2007 for sel in sels: 2008 print sel
2009 #----------------------------------------
2010 - def test_cSoapNoteExpandoEditAreaPnl():
2011 pat = gmPersonSearch.ask_for_patient() 2012 application = wx.PyWidgetTester(size=(800,500)) 2013 soap_input = cSoapNoteExpandoEditAreaPnl(application.frame, -1) 2014 application.frame.Show(True) 2015 application.MainLoop()
2016 #----------------------------------------
2017 - def test_cSoapPluginPnl():
2018 patient = gmPersonSearch.ask_for_patient() 2019 if patient is None: 2020 print "No patient. Exiting gracefully..." 2021 return 2022 gmPatSearchWidgets.set_active_patient(patient=patient) 2023 2024 application = wx.PyWidgetTester(size=(800,500)) 2025 soap_input = cSoapPluginPnl(application.frame, -1) 2026 application.frame.Show(True) 2027 soap_input._schedule_data_reget() 2028 application.MainLoop()
2029 #---------------------------------------- 2030 #test_select_narrative_from_episodes() 2031 test_cSoapNoteExpandoEditAreaPnl() 2032 #test_cSoapPluginPnl() 2033 2034 #============================================================ 2035