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 self._PRW_encounter_start.add_callback_on_lose_focus(callback = self._on_encounter_start_lost_focus)
760 #--------------------------------------------------------
762 start = self._PRW_encounter_start.GetData().get_pydt() 763 if start is None: 764 return 765 766 end = self._PRW_encounter_end.GetData().get_pydt() 767 if end is None: 768 fts = gmDateTime.cFuzzyTimestamp ( 769 timestamp = start, 770 accuracy = gmDateTime.acc_minutes 771 ) 772 self._PRW_encounter_end.SetText(fts.format_accurately(), data = fts) 773 return 774 775 if start > end: 776 end = end.replace ( 777 year = start.year, 778 month = start.month, 779 day = start.day 780 ) 781 fts = gmDateTime.cFuzzyTimestamp ( 782 timestamp = end, 783 accuracy = gmDateTime.acc_minutes 784 ) 785 self._PRW_encounter_end.SetText(fts.format_accurately(), data = fts) 786 return 787 788 emr = self.__pat.get_emr() 789 if start != emr.active_encounter['started']: 790 end = end.replace ( 791 year = start.year, 792 month = start.month, 793 day = start.day 794 ) 795 fts = gmDateTime.cFuzzyTimestamp ( 796 timestamp = end, 797 accuracy = gmDateTime.acc_minutes 798 ) 799 self._PRW_encounter_end.SetText(fts.format_accurately(), data = fts) 800 return 801 802 return
803 #--------------------------------------------------------
804 - def __reset_ui_content(self):
805 """Clear all information from input panel.""" 806 807 self._LCTRL_active_problems.set_string_items() 808 809 self._TCTRL_recent_notes.SetValue(u'') 810 self._SZR_recent_notes_staticbox.SetLabel(_('Most recent notes on selected problem')) 811 812 self._PRW_encounter_type.SetText(suppress_smarts = True) 813 self._PRW_encounter_start.SetText(suppress_smarts = True) 814 self._PRW_encounter_end.SetText(suppress_smarts = True) 815 self._TCTRL_rfe.SetValue(u'') 816 self._TCTRL_aoe.SetValue(u'') 817 818 self._NB_soap_editors.DeleteAllPages() 819 self._NB_soap_editors.add_editor()
820 #--------------------------------------------------------
821 - def __refresh_problem_list(self):
822 """Update health problems list.""" 823 824 self._LCTRL_active_problems.set_string_items() 825 826 emr = self.__pat.get_emr() 827 problems = emr.get_problems ( 828 include_closed_episodes = self._CHBOX_show_closed_episodes.IsChecked(), 829 include_irrelevant_issues = self._CHBOX_irrelevant_issues.IsChecked() 830 ) 831 832 list_items = [] 833 active_problems = [] 834 for problem in problems: 835 if not problem['problem_active']: 836 if not problem['is_potential_problem']: 837 continue 838 839 active_problems.append(problem) 840 841 if problem['type'] == 'issue': 842 issue = emr.problem2issue(problem) 843 last_encounter = emr.get_last_encounter(issue_id = issue['pk_health_issue']) 844 if last_encounter is None: 845 last = issue['modified_when'].strftime('%m/%Y') 846 else: 847 last = last_encounter['last_affirmed'].strftime('%m/%Y') 848 849 list_items.append([last, problem['problem'], gmTools.u_down_left_arrow]) #gmTools.u_left_arrow 850 851 elif problem['type'] == 'episode': 852 epi = emr.problem2episode(problem) 853 last_encounter = emr.get_last_encounter(episode_id = epi['pk_episode']) 854 if last_encounter is None: 855 last = epi['episode_modified_when'].strftime('%m/%Y') 856 else: 857 last = last_encounter['last_affirmed'].strftime('%m/%Y') 858 859 list_items.append ([ 860 last, 861 problem['problem'], 862 gmTools.coalesce(initial = epi['health_issue'], instead = u'?') #gmTools.u_diameter 863 ]) 864 865 self._LCTRL_active_problems.set_string_items(items = list_items) 866 self._LCTRL_active_problems.set_column_widths() 867 self._LCTRL_active_problems.set_data(data = active_problems) 868 869 showing_potential_problems = ( 870 self._CHBOX_show_closed_episodes.IsChecked() 871 or 872 self._CHBOX_irrelevant_issues.IsChecked() 873 ) 874 if showing_potential_problems: 875 self._SZR_problem_list_staticbox.SetLabel(_('%s (active+potential) problems') % len(list_items)) 876 else: 877 self._SZR_problem_list_staticbox.SetLabel(_('%s active problems') % len(list_items)) 878 879 return True
880 #--------------------------------------------------------
881 - def __get_soap_for_issue_problem(self, problem=None):
882 soap = u'' 883 emr = self.__pat.get_emr() 884 prev_enc = emr.get_last_but_one_encounter(issue_id = problem['pk_health_issue']) 885 if prev_enc is not None: 886 soap += prev_enc.format ( 887 issues = [ problem['pk_health_issue'] ], 888 with_soap = True, 889 with_docs = False, 890 with_tests = False, 891 patient = self.__pat, 892 fancy_header = False, 893 with_rfe_aoe = True 894 ) 895 896 tmp = emr.active_encounter.format_soap ( 897 soap_cats = 'soap', 898 emr = emr, 899 issues = [ problem['pk_health_issue'] ], 900 ) 901 if len(tmp) > 0: 902 soap += _('Current encounter:') + u'\n' 903 soap += u'\n'.join(tmp) + u'\n' 904 905 if problem['summary'] is not None: 906 soap += u'\n-- %s ----------\n%s' % ( 907 _('Cumulative summary'), 908 gmTools.wrap ( 909 text = problem['summary'], 910 width = 45, 911 initial_indent = u' ', 912 subsequent_indent = u' ' 913 ).strip('\n') 914 ) 915 916 return soap
917 #--------------------------------------------------------
918 - def __get_soap_for_episode_problem(self, problem=None):
919 soap = u'' 920 emr = self.__pat.get_emr() 921 prev_enc = emr.get_last_but_one_encounter(episode_id = problem['pk_episode']) 922 if prev_enc is not None: 923 soap += prev_enc.format ( 924 episodes = [ problem['pk_episode'] ], 925 with_soap = True, 926 with_docs = False, 927 with_tests = False, 928 patient = self.__pat, 929 fancy_header = False, 930 with_rfe_aoe = True 931 ) 932 else: 933 if problem['pk_health_issue'] is not None: 934 prev_enc = emr.get_last_but_one_encounter(episode_id = problem['pk_health_issue']) 935 if prev_enc is not None: 936 soap += prev_enc.format ( 937 with_soap = True, 938 with_docs = False, 939 with_tests = False, 940 patient = self.__pat, 941 issues = [ problem['pk_health_issue'] ], 942 fancy_header = False, 943 with_rfe_aoe = True 944 ) 945 946 tmp = emr.active_encounter.format_soap ( 947 soap_cats = 'soap', 948 emr = emr, 949 issues = [ problem['pk_health_issue'] ], 950 ) 951 if len(tmp) > 0: 952 soap += _('Current encounter:') + u'\n' 953 soap += u'\n'.join(tmp) + u'\n' 954 955 if problem['summary'] is not None: 956 soap += u'\n-- %s ----------\n%s' % ( 957 _('Cumulative summary'), 958 gmTools.wrap ( 959 text = problem['summary'], 960 width = 45, 961 initial_indent = u' ', 962 subsequent_indent = u' ' 963 ).strip('\n') 964 ) 965 966 return soap
967 #--------------------------------------------------------
968 - def __refresh_current_editor(self):
969 self._NB_soap_editors.refresh_current_editor()
970 #--------------------------------------------------------
972 if not self.__patient_just_changed: 973 return 974 975 dbcfg = gmCfg.cCfgSQL() 976 auto_open_recent_problems = bool(dbcfg.get2 ( 977 option = u'horstspace.soap_editor.auto_open_latest_episodes', 978 workplace = gmSurgery.gmCurrentPractice().active_workplace, 979 bias = u'user', 980 default = True 981 )) 982 983 self.__patient_just_changed = False 984 emr = self.__pat.get_emr() 985 recent_epis = emr.active_encounter.get_episodes() 986 prev_enc = emr.get_last_but_one_encounter() 987 if prev_enc is not None: 988 recent_epis.extend(prev_enc.get_episodes()) 989 990 for epi in recent_epis: 991 if not epi['episode_open']: 992 continue 993 self._NB_soap_editors.add_editor(problem = epi, allow_same_problem = False)
994 #--------------------------------------------------------
995 - def __refresh_recent_notes(self, problem=None):
996 """This refreshes the recent-notes part.""" 997 998 soap = u'' 999 caption = u'<?>' 1000 1001 if problem['type'] == u'issue': 1002 caption = problem['problem'][:35] 1003 soap = self.__get_soap_for_issue_problem(problem = problem) 1004 1005 elif problem['type'] == u'episode': 1006 caption = problem['problem'][:35] 1007 soap = self.__get_soap_for_episode_problem(problem = problem) 1008 1009 self._TCTRL_recent_notes.SetValue(soap) 1010 self._TCTRL_recent_notes.ShowPosition(self._TCTRL_recent_notes.GetLastPosition()) 1011 self._SZR_recent_notes_staticbox.SetLabel(_('Most recent notes on %s%s%s') % ( 1012 gmTools.u_left_double_angle_quote, 1013 caption, 1014 gmTools.u_right_double_angle_quote 1015 )) 1016 1017 self._TCTRL_recent_notes.Refresh() 1018 1019 return True
1020 #--------------------------------------------------------
1021 - def __refresh_encounter(self):
1022 """Update encounter fields.""" 1023 1024 emr = self.__pat.get_emr() 1025 enc = emr.active_encounter 1026 self._PRW_encounter_type.SetText(value = enc['l10n_type'], data = enc['pk_type']) 1027 1028 fts = gmDateTime.cFuzzyTimestamp ( 1029 timestamp = enc['started'], 1030 accuracy = gmDateTime.acc_minutes 1031 ) 1032 self._PRW_encounter_start.SetText(fts.format_accurately(), data = fts) 1033 1034 fts = gmDateTime.cFuzzyTimestamp ( 1035 timestamp = enc['last_affirmed'], 1036 accuracy = gmDateTime.acc_minutes 1037 ) 1038 self._PRW_encounter_end.SetText(fts.format_accurately(), data = fts) 1039 1040 self._TCTRL_rfe.SetValue(gmTools.coalesce(enc['reason_for_encounter'], u'')) 1041 val, data = self._PRW_rfe_codes.generic_linked_codes2item_dict(enc.generic_codes_rfe) 1042 self._PRW_rfe_codes.SetText(val, data) 1043 1044 self._TCTRL_aoe.SetValue(gmTools.coalesce(enc['assessment_of_encounter'], u'')) 1045 val, data = self._PRW_aoe_codes.generic_linked_codes2item_dict(enc.generic_codes_aoe) 1046 self._PRW_aoe_codes.SetText(val, data) 1047 1048 self._PRW_encounter_type.Refresh() 1049 self._PRW_encounter_start.Refresh() 1050 self._PRW_encounter_end.Refresh() 1051 self._TCTRL_rfe.Refresh() 1052 self._PRW_rfe_codes.Refresh() 1053 self._TCTRL_aoe.Refresh() 1054 self._PRW_aoe_codes.Refresh()
1055 #--------------------------------------------------------
1056 - def __encounter_modified(self):
1057 """Assumes that the field data is valid.""" 1058 1059 emr = self.__pat.get_emr() 1060 enc = emr.active_encounter 1061 1062 data = { 1063 'pk_type': self._PRW_encounter_type.GetData(), 1064 'reason_for_encounter': gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u''), 1065 'assessment_of_encounter': gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u''), 1066 'pk_location': enc['pk_location'], 1067 'pk_patient': enc['pk_patient'] 1068 } 1069 1070 if self._PRW_encounter_start.GetData() is None: 1071 data['started'] = None 1072 else: 1073 data['started'] = self._PRW_encounter_start.GetData().get_pydt() 1074 1075 if self._PRW_encounter_end.GetData() is None: 1076 data['last_affirmed'] = None 1077 else: 1078 data['last_affirmed'] = self._PRW_encounter_end.GetData().get_pydt() 1079 1080 return not enc.same_payload(another_object = data)
1081 #--------------------------------------------------------
1082 - def __encounter_valid_for_save(self):
1083 1084 found_error = False 1085 1086 if self._PRW_encounter_type.GetData() is None: 1087 found_error = True 1088 msg = _('Cannot save encounter: missing type.') 1089 1090 if self._PRW_encounter_start.GetData() is None: 1091 found_error = True 1092 msg = _('Cannot save encounter: missing start time.') 1093 1094 if self._PRW_encounter_end.GetData() is None: 1095 found_error = True 1096 msg = _('Cannot save encounter: missing end time.') 1097 1098 if found_error: 1099 gmDispatcher.send(signal = 'statustext', msg = msg, beep = True) 1100 return False 1101 1102 return True
1103 #-------------------------------------------------------- 1104 # event handling 1105 #--------------------------------------------------------
1106 - def __register_interests(self):
1107 """Configure enabled event signals.""" 1108 # client internal signals 1109 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection) 1110 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection) 1111 gmDispatcher.connect(signal = u'episode_mod_db', receiver = self._on_episode_issue_mod_db) 1112 gmDispatcher.connect(signal = u'health_issue_mod_db', receiver = self._on_episode_issue_mod_db) 1113 gmDispatcher.connect(signal = u'episode_code_mod_db', receiver = self._on_episode_issue_mod_db) 1114 gmDispatcher.connect(signal = u'doc_mod_db', receiver = self._on_doc_mod_db) 1115 gmDispatcher.connect(signal = u'current_encounter_modified', receiver = self._on_current_encounter_modified) 1116 gmDispatcher.connect(signal = u'current_encounter_switched', receiver = self._on_current_encounter_switched) 1117 gmDispatcher.connect(signal = u'rfe_code_mod_db', receiver = self._on_current_encounter_modified) 1118 gmDispatcher.connect(signal = u'aoe_code_mod_db', receiver = self._on_current_encounter_modified) 1119 1120 # synchronous signals 1121 self.__pat.register_pre_selection_callback(callback = self._pre_selection_callback) 1122 gmDispatcher.send(signal = u'register_pre_exit_callback', callback = self._pre_exit_callback)
1123 #--------------------------------------------------------
1124 - def _pre_selection_callback(self):
1125 """Another patient is about to be activated. 1126 1127 Patient change will not proceed before this returns True. 1128 """ 1129 # don't worry about the encounter here - it will be offered 1130 # for editing higher up if anything was saved to the EMR 1131 if not self.__pat.connected: 1132 return True 1133 return self._NB_soap_editors.warn_on_unsaved_soap()
1134 #--------------------------------------------------------
1135 - def _pre_exit_callback(self):
1136 """The client is about to be shut down. 1137 1138 Shutdown will not proceed before this returns. 1139 """ 1140 if not self.__pat.connected: 1141 return True 1142 1143 # if self.__encounter_modified(): 1144 # do_save_enc = gmGuiHelpers.gm_show_question ( 1145 # aMessage = _( 1146 # 'You have modified the details\n' 1147 # 'of the current encounter.\n' 1148 # '\n' 1149 # 'Do you want to save those changes ?' 1150 # ), 1151 # aTitle = _('Starting new encounter') 1152 # ) 1153 # if do_save_enc: 1154 # if not self.save_encounter(): 1155 # gmDispatcher.send(signal = u'statustext', msg = _('Error saving current encounter.'), beep = True) 1156 1157 emr = self.__pat.get_emr() 1158 saved = self._NB_soap_editors.save_all_editors ( 1159 emr = emr, 1160 episode_name_candidates = [ 1161 gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u''), 1162 gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u'') 1163 ] 1164 ) 1165 if not saved: 1166 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save all editors. Some were kept open.'), beep = True) 1167 return True
1168 #--------------------------------------------------------
1169 - def _on_pre_patient_selection(self):
1170 wx.CallAfter(self.__on_pre_patient_selection)
1171 #--------------------------------------------------------
1172 - def __on_pre_patient_selection(self):
1173 self.__reset_ui_content()
1174 #--------------------------------------------------------
1175 - def _on_post_patient_selection(self):
1176 wx.CallAfter(self._schedule_data_reget) 1177 self.__patient_just_changed = True
1178 #--------------------------------------------------------
1179 - def _on_doc_mod_db(self):
1180 wx.CallAfter(self.__refresh_current_editor)
1181 #--------------------------------------------------------
1182 - def _on_episode_issue_mod_db(self):
1183 wx.CallAfter(self._schedule_data_reget)
1184 #--------------------------------------------------------
1186 wx.CallAfter(self.__refresh_encounter)
1187 #--------------------------------------------------------
1189 wx.CallAfter(self.__on_current_encounter_switched)
1190 #--------------------------------------------------------
1192 self.__refresh_encounter()
1193 #-------------------------------------------------------- 1194 # problem list specific events 1195 #--------------------------------------------------------
1196 - def _on_problem_focused(self, event):
1197 """Show related note at the bottom.""" 1198 pass
1199 #--------------------------------------------------------
1200 - def _on_problem_rclick(self, event):
1201 problem = self._LCTRL_active_problems.get_selected_item_data(only_one = True) 1202 if problem['type'] == u'issue': 1203 gmEMRStructWidgets.edit_health_issue(parent = self, issue = problem.get_as_health_issue()) 1204 return 1205 1206 if problem['type'] == u'episode': 1207 gmEMRStructWidgets.edit_episode(parent = self, episode = problem.get_as_episode()) 1208 return 1209 1210 event.Skip()
1211 #--------------------------------------------------------
1212 - def _on_problem_selected(self, event):
1213 """Show related note at the bottom.""" 1214 emr = self.__pat.get_emr() 1215 self.__refresh_recent_notes ( 1216 problem = self._LCTRL_active_problems.get_selected_item_data(only_one = True) 1217 )
1218 #--------------------------------------------------------
1219 - def _on_problem_activated(self, event):
1220 """Open progress note editor for this problem. 1221 """ 1222 problem = self._LCTRL_active_problems.get_selected_item_data(only_one = True) 1223 if problem is None: 1224 return True 1225 1226 dbcfg = gmCfg.cCfgSQL() 1227 allow_duplicate_editors = bool(dbcfg.get2 ( 1228 option = u'horstspace.soap_editor.allow_same_episode_multiple_times', 1229 workplace = gmSurgery.gmCurrentPractice().active_workplace, 1230 bias = u'user', 1231 default = False 1232 )) 1233 if self._NB_soap_editors.add_editor(problem = problem, allow_same_problem = allow_duplicate_editors): 1234 return True 1235 1236 gmGuiHelpers.gm_show_error ( 1237 aMessage = _( 1238 'Cannot open progress note editor for\n\n' 1239 '[%s].\n\n' 1240 ) % problem['problem'], 1241 aTitle = _('opening progress note editor') 1242 ) 1243 event.Skip() 1244 return False
1245 #--------------------------------------------------------
1246 - def _on_show_closed_episodes_checked(self, event):
1247 self.__refresh_problem_list()
1248 #--------------------------------------------------------
1249 - def _on_irrelevant_issues_checked(self, event):
1250 self.__refresh_problem_list()
1251 #-------------------------------------------------------- 1252 # SOAP editor specific buttons 1253 #--------------------------------------------------------
1254 - def _on_discard_editor_button_pressed(self, event):
1255 self._NB_soap_editors.close_current_editor() 1256 event.Skip()
1257 #--------------------------------------------------------
1258 - def _on_new_editor_button_pressed(self, event):
1259 self._NB_soap_editors.add_editor() 1260 event.Skip()
1261 #--------------------------------------------------------
1262 - def _on_clear_editor_button_pressed(self, event):
1263 self._NB_soap_editors.clear_current_editor() 1264 event.Skip()
1265 #--------------------------------------------------------
1266 - def _on_save_note_button_pressed(self, event):
1267 emr = self.__pat.get_emr() 1268 self._NB_soap_editors.save_current_editor ( 1269 emr = emr, 1270 episode_name_candidates = [ 1271 gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u''), 1272 gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u'') 1273 ] 1274 ) 1275 event.Skip()
1276 #--------------------------------------------------------
1277 - def _on_save_note_under_button_pressed(self, event):
1278 encounters = gmEMRStructWidgets.select_encounters ( 1279 parent = self, 1280 patient = self.__pat, 1281 single_selection = True 1282 ) 1283 # cancelled: 1284 if encounters is None: 1285 return 1286 if len(encounters) == 0: 1287 return 1288 1289 emr = self.__pat.get_emr() 1290 self._NB_soap_editors.save_current_editor ( 1291 emr = emr, 1292 encounter = encounter['pk_encounter'], 1293 episode_name_candidates = [ 1294 gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u''), 1295 gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u'') 1296 ] 1297 ) 1298 event.Skip()
1299 #--------------------------------------------------------
1300 - def _on_image_button_pressed(self, event):
1301 emr = self.__pat.get_emr() 1302 self._NB_soap_editors.add_visual_progress_note_to_current_problem() 1303 event.Skip()
1304 #-------------------------------------------------------- 1305 # encounter specific buttons 1306 #--------------------------------------------------------
1307 - def _on_save_encounter_button_pressed(self, event):
1308 self.save_encounter() 1309 event.Skip()
1310 #--------------------------------------------------------
1311 - def _on_new_encounter_button_pressed(self, event):
1312 1313 if self.__encounter_modified(): 1314 do_save_enc = gmGuiHelpers.gm_show_question ( 1315 aMessage = _( 1316 'You have modified the details\n' 1317 'of the current encounter.\n' 1318 '\n' 1319 'Do you want to save those changes ?' 1320 ), 1321 aTitle = _('Starting new encounter') 1322 ) 1323 if do_save_enc: 1324 if not self.save_encounter(): 1325 gmDispatcher.send(signal = u'statustext', msg = _('Error saving current encounter.'), beep = True) 1326 return False 1327 1328 emr = self.__pat.get_emr() 1329 gmDispatcher.send(signal = u'statustext', msg = _('Started new encounter for active patient.'), beep = True) 1330 1331 event.Skip() 1332 1333 wx.CallAfter(gmEMRStructWidgets.start_new_encounter, emr = emr)
1334 #-------------------------------------------------------- 1335 # other buttons 1336 #--------------------------------------------------------
1337 - def _on_save_all_button_pressed(self, event):
1338 self.save_encounter() 1339 time.sleep(0.3) 1340 event.Skip() 1341 wx.SafeYield() 1342 1343 wx.CallAfter(self._save_all_button_pressed_bottom_half) 1344 wx.SafeYield()
1345 #--------------------------------------------------------
1347 emr = self.__pat.get_emr() 1348 saved = self._NB_soap_editors.save_all_editors ( 1349 emr = emr, 1350 episode_name_candidates = [ 1351 gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u''), 1352 gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u'') 1353 ] 1354 ) 1355 if not saved: 1356 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save all editors. Some were kept open.'), beep = True)
1357 #-------------------------------------------------------- 1358 # reget mixin API 1359 #--------------------------------------------------------
1360 - def _populate_with_data(self):
1361 self.__refresh_problem_list() 1362 self.__refresh_encounter() 1363 self.__setup_initial_patient_editors() 1364 return True
1365 #============================================================
1366 -class cSoapNoteInputNotebook(wx.Notebook):
1367 """A notebook holding panels with progress note editors. 1368 1369 There can be one or several progress note editor panel 1370 for each episode being worked on. The editor class in 1371 each panel is configurable. 1372 1373 There will always be one open editor. 1374 """
1375 - def __init__(self, *args, **kwargs):
1376 1377 kwargs['style'] = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER 1378 1379 wx.Notebook.__init__(self, *args, **kwargs)
1380 #-------------------------------------------------------- 1381 # public API 1382 #--------------------------------------------------------
1383 - def add_editor(self, problem=None, allow_same_problem=False):
1384 """Add a progress note editor page. 1385 1386 The way <allow_same_problem> is currently used in callers 1387 it only applies to unassociated episodes. 1388 """ 1389 problem_to_add = problem 1390 1391 # determine label 1392 if problem_to_add is None: 1393 label = _('new problem') 1394 else: 1395 # normalize problem type 1396 if isinstance(problem_to_add, gmEMRStructItems.cEpisode): 1397 problem_to_add = gmEMRStructItems.episode2problem(episode = problem_to_add) 1398 1399 elif isinstance(problem_to_add, gmEMRStructItems.cHealthIssue): 1400 problem_to_add = gmEMRStructItems.health_issue2problem(episode = problem_to_add) 1401 1402 if not isinstance(problem_to_add, gmEMRStructItems.cProblem): 1403 raise TypeError('cannot open progress note editor for [%s]' % problem_to_add) 1404 1405 label = problem_to_add['problem'] 1406 # FIXME: configure maximum length 1407 if len(label) > 23: 1408 label = label[:21] + gmTools.u_ellipsis 1409 1410 # new unassociated problem or dupes allowed 1411 if (problem_to_add is None) or allow_same_problem: 1412 new_page = cSoapNoteExpandoEditAreaPnl(parent = self, id = -1, problem = problem_to_add) 1413 result = self.AddPage ( 1414 page = new_page, 1415 text = label, 1416 select = True 1417 ) 1418 return result 1419 1420 # real problem, no dupes allowed 1421 # - raise existing editor 1422 for page_idx in range(self.GetPageCount()): 1423 page = self.GetPage(page_idx) 1424 1425 # editor is for unassociated new problem 1426 if page.problem is None: 1427 continue 1428 1429 # editor is for episode 1430 if page.problem['type'] == 'episode': 1431 if page.problem['pk_episode'] == problem_to_add['pk_episode']: 1432 self.SetSelection(page_idx) 1433 gmDispatcher.send(signal = u'statustext', msg = u'Raising existing editor.', beep = True) 1434 return True 1435 continue 1436 1437 # editor is for health issue 1438 if page.problem['type'] == 'issue': 1439 if page.problem['pk_health_issue'] == problem_to_add['pk_health_issue']: 1440 self.SetSelection(page_idx) 1441 gmDispatcher.send(signal = u'statustext', msg = u'Raising existing editor.', beep = True) 1442 return True 1443 continue 1444 1445 # - or add new editor 1446 new_page = cSoapNoteExpandoEditAreaPnl(parent = self, id = -1, problem = problem_to_add) 1447 result = self.AddPage ( 1448 page = new_page, 1449 text = label, 1450 select = True 1451 ) 1452 1453 return result
1454 #--------------------------------------------------------
1455 - def close_current_editor(self):
1456 1457 page_idx = self.GetSelection() 1458 page = self.GetPage(page_idx) 1459 1460 if not page.empty: 1461 really_discard = gmGuiHelpers.gm_show_question ( 1462 _('Are you sure you really want to\n' 1463 'discard this progress note ?\n' 1464 ), 1465 _('Discarding progress note') 1466 ) 1467 if really_discard is False: 1468 return 1469 1470 self.DeletePage(page_idx) 1471 1472 # always keep one unassociated editor open 1473 if self.GetPageCount() == 0: 1474 self.add_editor()
1475 #--------------------------------------------------------
1476 - def save_current_editor(self, emr=None, episode_name_candidates=None, encounter=None):
1477 1478 page_idx = self.GetSelection() 1479 page = self.GetPage(page_idx) 1480 1481 if not page.save(emr = emr, episode_name_candidates = episode_name_candidates, encounter = encounter): 1482 return 1483 1484 self.DeletePage(page_idx) 1485 1486 # always keep one unassociated editor open 1487 if self.GetPageCount() == 0: 1488 self.add_editor()
1489 #--------------------------------------------------------
1490 - def warn_on_unsaved_soap(self):
1491 for page_idx in range(self.GetPageCount()): 1492 page = self.GetPage(page_idx) 1493 if page.empty: 1494 continue 1495 1496 gmGuiHelpers.gm_show_warning ( 1497 _('There are unsaved progress notes !\n'), 1498 _('Unsaved progress notes') 1499 ) 1500 return False 1501 1502 return True
1503 #--------------------------------------------------------
1504 - def save_all_editors(self, emr=None, episode_name_candidates=None):
1505 1506 _log.debug('saving editors: %s', self.GetPageCount()) 1507 1508 all_closed = True 1509 for page_idx in range((self.GetPageCount() - 1), -1, -1): 1510 _log.debug('#%s of %s', page_idx, self.GetPageCount()) 1511 try: 1512 self.ChangeSelection(page_idx) 1513 _log.debug('editor raised') 1514 except: 1515 _log.exception('cannot raise editor') 1516 page = self.GetPage(page_idx) 1517 if page.save(emr = emr, episode_name_candidates = episode_name_candidates): 1518 _log.debug('saved, deleting now') 1519 self.DeletePage(page_idx) 1520 else: 1521 _log.debug('not saved, not deleting') 1522 all_closed = False 1523 1524 # always keep one unassociated editor open 1525 if self.GetPageCount() == 0: 1526 self.add_editor() 1527 1528 return (all_closed is True)
1529 #--------------------------------------------------------
1530 - def clear_current_editor(self):
1531 page_idx = self.GetSelection() 1532 page = self.GetPage(page_idx) 1533 page.clear()
1534 #--------------------------------------------------------
1535 - def get_current_problem(self):
1536 page_idx = self.GetSelection() 1537 page = self.GetPage(page_idx) 1538 return page.problem
1539 #--------------------------------------------------------
1540 - def refresh_current_editor(self):
1541 page_idx = self.GetSelection() 1542 page = self.GetPage(page_idx) 1543 page.refresh()
1544 #--------------------------------------------------------
1546 page_idx = self.GetSelection() 1547 page = self.GetPage(page_idx) 1548 page.add_visual_progress_note()
1549 #============================================================ 1550 from Gnumed.wxGladeWidgets import wxgSoapNoteExpandoEditAreaPnl 1551
1552 -class cSoapNoteExpandoEditAreaPnl(wxgSoapNoteExpandoEditAreaPnl.wxgSoapNoteExpandoEditAreaPnl):
1553 """An Edit Area like panel for entering progress notes. 1554 1555 Subjective: Codes: 1556 expando text ctrl 1557 Objective: Codes: 1558 expando text ctrl 1559 Assessment: Codes: 1560 expando text ctrl 1561 Plan: Codes: 1562 expando text ctrl 1563 visual progress notes 1564 panel with images 1565 Episode summary: Codes: 1566 text ctrl 1567 1568 - knows the problem this edit area is about 1569 - can deal with issue or episode type problems 1570 """ 1571
1572 - def __init__(self, *args, **kwargs):
1573 1574 try: 1575 self.problem = kwargs['problem'] 1576 del kwargs['problem'] 1577 except KeyError: 1578 self.problem = None 1579 1580 wxgSoapNoteExpandoEditAreaPnl.wxgSoapNoteExpandoEditAreaPnl.__init__(self, *args, **kwargs) 1581 1582 self.soap_fields = [ 1583 self._TCTRL_Soap, 1584 self._TCTRL_sOap, 1585 self._TCTRL_soAp, 1586 self._TCTRL_soaP 1587 ] 1588 1589 self.__init_ui() 1590 self.__register_interests()
1591 #--------------------------------------------------------
1592 - def __init_ui(self):
1593 self.refresh_summary() 1594 if self.problem is not None: 1595 if self.problem['summary'] is None: 1596 self._TCTRL_episode_summary.SetValue(u'') 1597 self.refresh_visual_soap()
1598 #--------------------------------------------------------
1599 - def refresh(self):
1600 self.refresh_summary() 1601 self.refresh_visual_soap()
1602 #--------------------------------------------------------
1603 - def refresh_summary(self):
1604 self._TCTRL_episode_summary.SetValue(u'') 1605 self._PRW_episode_codes.SetText(u'', self._PRW_episode_codes.list2data_dict([])) 1606 self._LBL_summary.SetLabel(_('Episode summary')) 1607 1608 # new problem ? 1609 if self.problem is None: 1610 return 1611 1612 # issue-level problem ? 1613 if self.problem['type'] == u'issue': 1614 return 1615 1616 # episode-level problem 1617 caption = _(u'Summary (%s)') % ( 1618 gmDateTime.pydt_strftime ( 1619 self.problem['modified_when'], 1620 format = '%B %Y', 1621 accuracy = gmDateTime.acc_days 1622 ) 1623 ) 1624 self._LBL_summary.SetLabel(caption) 1625 1626 if self.problem['summary'] is not None: 1627 self._TCTRL_episode_summary.SetValue(self.problem['summary'].strip()) 1628 1629 val, data = self._PRW_episode_codes.generic_linked_codes2item_dict(self.problem.generic_codes) 1630 self._PRW_episode_codes.SetText(val, data)
1631 #--------------------------------------------------------
1632 - def refresh_visual_soap(self):
1633 if self.problem is None: 1634 self._PNL_visual_soap.refresh(document_folder = None) 1635 return 1636 1637 if self.problem['type'] == u'issue': 1638 self._PNL_visual_soap.refresh(document_folder = None) 1639 return 1640 1641 if self.problem['type'] == u'episode': 1642 pat = gmPerson.gmCurrentPatient() 1643 doc_folder = pat.get_document_folder() 1644 emr = pat.get_emr() 1645 self._PNL_visual_soap.refresh ( 1646 document_folder = doc_folder, 1647 episodes = [self.problem['pk_episode']], 1648 encounter = emr.active_encounter['pk_encounter'] 1649 ) 1650 return
1651 #--------------------------------------------------------
1652 - def clear(self):
1653 for field in self.soap_fields: 1654 field.SetValue(u'') 1655 self._TCTRL_episode_summary.SetValue(u'') 1656 self._LBL_summary.SetLabel(_('Episode summary')) 1657 self._PRW_episode_codes.SetText(u'', self._PRW_episode_codes.list2data_dict([])) 1658 self._PNL_visual_soap.clear()
1659 #--------------------------------------------------------
1660 - def add_visual_progress_note(self):
1661 fname, discard_unmodified = select_visual_progress_note_template(parent = self) 1662 if fname is None: 1663 return False 1664 1665 if self.problem is None: 1666 issue = None 1667 episode = None 1668 elif self.problem['type'] == 'issue': 1669 issue = self.problem['pk_health_issue'] 1670 episode = None 1671 else: 1672 issue = self.problem['pk_health_issue'] 1673 episode = gmEMRStructItems.problem2episode(self.problem) 1674 1675 wx.CallAfter ( 1676 edit_visual_progress_note, 1677 filename = fname, 1678 episode = episode, 1679 discard_unmodified = discard_unmodified, 1680 health_issue = issue 1681 )
1682 #--------------------------------------------------------
1683 - def save(self, emr=None, episode_name_candidates=None, encounter=None):
1684 1685 if self.empty: 1686 return True 1687 1688 # new episode (standalone=unassociated or new-in-issue) 1689 if (self.problem is None) or (self.problem['type'] == 'issue'): 1690 episode = self.__create_new_episode(emr = emr, episode_name_candidates = episode_name_candidates) 1691 # existing episode 1692 else: 1693 episode = emr.problem2episode(self.problem) 1694 1695 if encounter is None: 1696 encounter = emr.current_encounter['pk_encounter'] 1697 1698 soap_notes = [] 1699 for note in self.soap: 1700 saved, data = gmClinNarrative.create_clin_narrative ( 1701 soap_cat = note[0], 1702 narrative = note[1], 1703 episode_id = episode['pk_episode'], 1704 encounter_id = encounter 1705 ) 1706 if saved: 1707 soap_notes.append(data) 1708 1709 # codes per narrative ! 1710 # for note in soap_notes: 1711 # if note['soap_cat'] == u's': 1712 # codes = self._PRW_Soap_codes 1713 # elif note['soap_cat'] == u'o': 1714 # elif note['soap_cat'] == u'a': 1715 # elif note['soap_cat'] == u'p': 1716 1717 # set summary but only if not already set above for a 1718 # newly created episode (either standalone or within 1719 # a health issue) 1720 if self.problem is not None: 1721 if self.problem['type'] == 'episode': 1722 episode['summary'] = self._TCTRL_episode_summary.GetValue().strip() 1723 episode.save() 1724 1725 # codes for episode 1726 episode.generic_codes = [ d['data'] for d in self._PRW_episode_codes.GetData() ] 1727 1728 return True
1729 #-------------------------------------------------------- 1730 # internal helpers 1731 #--------------------------------------------------------
1732 - def __create_new_episode(self, emr=None, episode_name_candidates=None):
1733 1734 episode_name_candidates.append(self._TCTRL_episode_summary.GetValue().strip()) 1735 for candidate in episode_name_candidates: 1736 if candidate is None: 1737 continue 1738 epi_name = candidate.strip().replace('\r', '//').replace('\n', '//') 1739 break 1740 1741 dlg = wx.TextEntryDialog ( 1742 parent = self, 1743 message = _('Enter a short working name for this new problem:'), 1744 caption = _('Creating a problem (episode) to save the notelet under ...'), 1745 defaultValue = epi_name, 1746 style = wx.OK | wx.CANCEL | wx.CENTRE 1747 ) 1748 decision = dlg.ShowModal() 1749 if decision != wx.ID_OK: 1750 return None 1751 1752 epi_name = dlg.GetValue().strip() 1753 if epi_name == u'': 1754 gmGuiHelpers.gm_show_error(_('Cannot save a new problem without a name.'), _('saving progress note')) 1755 return None 1756 1757 # create episode 1758 new_episode = emr.add_episode(episode_name = epi_name[:45], pk_health_issue = None, is_open = True) 1759 new_episode['summary'] = self._TCTRL_episode_summary.GetValue().strip() 1760 new_episode.save() 1761 1762 if self.problem is not None: 1763 issue = emr.problem2issue(self.problem) 1764 if not gmEMRStructWidgets.move_episode_to_issue(episode = new_episode, target_issue = issue, save_to_backend = True): 1765 gmGuiHelpers.gm_show_warning ( 1766 _( 1767 'The new episode:\n' 1768 '\n' 1769 ' "%s"\n' 1770 '\n' 1771 'will remain unassociated despite the editor\n' 1772 'having been invoked from the health issue:\n' 1773 '\n' 1774 ' "%s"' 1775 ) % ( 1776 new_episode['description'], 1777 issue['description'] 1778 ), 1779 _('saving progress note') 1780 ) 1781 1782 return new_episode
1783 #-------------------------------------------------------- 1784 # event handling 1785 #--------------------------------------------------------
1786 - def __register_interests(self):
1787 for field in self.soap_fields: 1788 wx_expando.EVT_ETC_LAYOUT_NEEDED(field, field.GetId(), self._on_expando_needs_layout) 1789 wx_expando.EVT_ETC_LAYOUT_NEEDED(self._TCTRL_episode_summary, self._TCTRL_episode_summary.GetId(), self._on_expando_needs_layout)
1790 #--------------------------------------------------------
1791 - def _on_expando_needs_layout(self, evt):
1792 # need to tell ourselves to re-Layout to refresh scroll bars 1793 1794 # provoke adding scrollbar if needed 1795 #self.Fit() # works on Linux but not on Windows 1796 self.FitInside() # needed on Windows rather than self.Fit() 1797 1798 if self.HasScrollbar(wx.VERTICAL): 1799 # scroll panel to show cursor 1800 expando = self.FindWindowById(evt.GetId()) 1801 y_expando = expando.GetPositionTuple()[1] 1802 h_expando = expando.GetSizeTuple()[1] 1803 line_cursor = expando.PositionToXY(expando.GetInsertionPoint())[1] + 1 1804 y_cursor = int(round((float(line_cursor) / expando.NumberOfLines) * h_expando)) 1805 y_desired_visible = y_expando + y_cursor 1806 1807 y_view = self.ViewStart[1] 1808 h_view = self.GetClientSizeTuple()[1] 1809 1810 # print "expando:", y_expando, "->", h_expando, ", lines:", expando.NumberOfLines 1811 # print "cursor :", y_cursor, "at line", line_cursor, ", insertion point:", expando.GetInsertionPoint() 1812 # print "wanted :", y_desired_visible 1813 # print "view-y :", y_view 1814 # print "scroll2:", h_view 1815 1816 # expando starts before view 1817 if y_desired_visible < y_view: 1818 # print "need to scroll up" 1819 self.Scroll(0, y_desired_visible) 1820 1821 if y_desired_visible > h_view: 1822 # print "need to scroll down" 1823 self.Scroll(0, y_desired_visible)
1824 #-------------------------------------------------------- 1825 # properties 1826 #--------------------------------------------------------
1827 - def _get_soap(self):
1828 soap_notes = [] 1829 1830 tmp = self._TCTRL_Soap.GetValue().strip() 1831 if tmp != u'': 1832 soap_notes.append(['s', tmp]) 1833 1834 tmp = self._TCTRL_sOap.GetValue().strip() 1835 if tmp != u'': 1836 soap_notes.append(['o', tmp]) 1837 1838 tmp = self._TCTRL_soAp.GetValue().strip() 1839 if tmp != u'': 1840 soap_notes.append(['a', tmp]) 1841 1842 tmp = self._TCTRL_soaP.GetValue().strip() 1843 if tmp != u'': 1844 soap_notes.append(['p', tmp]) 1845 1846 return soap_notes
1847 1848 soap = property(_get_soap, lambda x:x) 1849 #--------------------------------------------------------
1850 - def _get_empty(self):
1851 1852 # soap fields 1853 for field in self.soap_fields: 1854 if field.GetValue().strip() != u'': 1855 return False 1856 1857 # summary 1858 summary = self._TCTRL_episode_summary.GetValue().strip() 1859 if self.problem is None: 1860 if summary != u'': 1861 return False 1862 elif self.problem['type'] == u'issue': 1863 if summary != u'': 1864 return False 1865 else: 1866 if self.problem['summary'] is None: 1867 if summary != u'': 1868 return False 1869 else: 1870 if summary != self.problem['summary'].strip(): 1871 return False 1872 1873 # codes 1874 new_codes = self._PRW_episode_codes.GetData() 1875 if self.problem is None: 1876 if len(new_codes) > 0: 1877 return False 1878 elif self.problem['type'] == u'issue': 1879 if len(new_codes) > 0: 1880 return False 1881 else: 1882 old_code_pks = self.problem.generic_codes 1883 if len(old_code_pks) != len(new_codes): 1884 return False 1885 for code in new_codes: 1886 if code['data'] not in old_code_pks: 1887 return False 1888 1889 return True
1890 1891 empty = property(_get_empty, lambda x:x)
1892 #============================================================
1893 -class cSoapLineTextCtrl(wx_expando.ExpandoTextCtrl):
1894
1895 - def __init__(self, *args, **kwargs):
1896 1897 wx_expando.ExpandoTextCtrl.__init__(self, *args, **kwargs) 1898 1899 self.__keyword_separators = regex.compile("[!?'\".,:;)}\]\r\n\s\t]+") 1900 1901 self.__register_interests()
1902 #------------------------------------------------ 1903 # fixup errors in platform expando.py 1904 #------------------------------------------------
1905 - def _wrapLine(self, line, dc, width):
1906 1907 if (wx.MAJOR_VERSION >= 2) and (wx.MINOR_VERSION > 8): 1908 return super(cSoapLineTextCtrl, self)._wrapLine(line, dc, width) 1909 1910 # THIS FIX LIFTED FROM TRUNK IN SVN: 1911 # Estimate where the control will wrap the lines and 1912 # return the count of extra lines needed. 1913 pte = dc.GetPartialTextExtents(line) 1914 width -= wx.SystemSettings.GetMetric(wx.SYS_VSCROLL_X) 1915 idx = 0 1916 start = 0 1917 count = 0 1918 spc = -1 1919 while idx < len(pte): 1920 if line[idx] == ' ': 1921 spc = idx 1922 if pte[idx] - start > width: 1923 # we've reached the max width, add a new line 1924 count += 1 1925 # did we see a space? if so restart the count at that pos 1926 if spc != -1: 1927 idx = spc + 1 1928 spc = -1 1929 if idx < len(pte): 1930 start = pte[idx] 1931 else: 1932 idx += 1 1933 return count
1934 #------------------------------------------------ 1935 # event handling 1936 #------------------------------------------------
1937 - def __register_interests(self):
1938 #wx.EVT_KEY_DOWN (self, self.__on_key_down) 1939 #wx.EVT_KEY_UP (self, self.__OnKeyUp) 1940 wx.EVT_CHAR(self, self.__on_char) 1941 wx.EVT_SET_FOCUS(self, self.__on_focus)
1942 #--------------------------------------------------------
1943 - def __on_focus(self, evt):
1944 evt.Skip() 1945 wx.CallAfter(self._after_on_focus)
1946 #--------------------------------------------------------
1947 - def _after_on_focus(self):
1948 #wx.CallAfter(self._adjustCtrl) 1949 evt = wx.PyCommandEvent(wx_expando.wxEVT_ETC_LAYOUT_NEEDED, self.GetId()) 1950 evt.SetEventObject(self) 1951 #evt.height = None 1952 #evt.numLines = None 1953 #evt.height = self.GetSize().height 1954 #evt.numLines = self.GetNumberOfLines() 1955 self.GetEventHandler().ProcessEvent(evt)
1956 #--------------------------------------------------------
1957 - def __on_char(self, evt):
1958 char = unichr(evt.GetUnicodeKey()) 1959 1960 if self.LastPosition == 1: 1961 evt.Skip() 1962 return 1963 1964 explicit_expansion = False 1965 if evt.GetModifiers() == (wx.MOD_CMD | wx.MOD_ALT): # portable CTRL-ALT-... 1966 if evt.GetKeyCode() != 13: 1967 evt.Skip() 1968 return 1969 explicit_expansion = True 1970 1971 if not explicit_expansion: 1972 if self.__keyword_separators.match(char) is None: 1973 evt.Skip() 1974 return 1975 1976 caret_pos, line_no = self.PositionToXY(self.InsertionPoint) 1977 line = self.GetLineText(line_no) 1978 word = self.__keyword_separators.split(line[:caret_pos])[-1] 1979 1980 if ( 1981 (not explicit_expansion) 1982 and 1983 (word != u'$$steffi') # Easter Egg ;-) 1984 and 1985 (word not in [ r[0] for r in gmPG2.get_text_expansion_keywords() ]) 1986 ): 1987 evt.Skip() 1988 return 1989 1990 start = self.InsertionPoint - len(word) 1991 wx.CallAfter(self.replace_keyword_with_expansion, word, start, explicit_expansion) 1992 1993 evt.Skip() 1994 return
1995 #------------------------------------------------
1996 - def replace_keyword_with_expansion(self, keyword=None, position=None, show_list=False):
1997 1998 if show_list: 1999 candidates = gmPG2.get_keyword_expansion_candidates(keyword = keyword) 2000 if len(candidates) == 0: 2001 return 2002 if len(candidates) == 1: 2003 keyword = candidates[0] 2004 else: 2005 keyword = gmListWidgets.get_choices_from_list ( 2006 parent = self, 2007 msg = _( 2008 'Several macros match the keyword [%s].\n' 2009 '\n' 2010 'Please select the expansion you want to happen.' 2011 ) % keyword, 2012 caption = _('Selecting text macro'), 2013 choices = candidates, 2014 columns = [_('Keyword')], 2015 single_selection = True, 2016 can_return_empty = False 2017 ) 2018 if keyword is None: 2019 return 2020 2021 expansion = gmPG2.expand_keyword(keyword = keyword) 2022 2023 if expansion is None: 2024 return 2025 2026 if expansion == u'': 2027 return 2028 2029 self.Replace ( 2030 position, 2031 position + len(keyword), 2032 expansion 2033 ) 2034 2035 self.SetInsertionPoint(position + len(expansion) + 1) 2036 self.ShowPosition(position + len(expansion) + 1) 2037 2038 return
2039 #============================================================ 2040 # visual progress notes 2041 #============================================================
2042 -def configure_visual_progress_note_editor():
2043 2044 def is_valid(value): 2045 2046 if value is None: 2047 gmDispatcher.send ( 2048 signal = 'statustext', 2049 msg = _('You need to actually set an editor.'), 2050 beep = True 2051 ) 2052 return False, value 2053 2054 if value.strip() == u'': 2055 gmDispatcher.send ( 2056 signal = 'statustext', 2057 msg = _('You need to actually set an editor.'), 2058 beep = True 2059 ) 2060 return False, value 2061 2062 found, binary = gmShellAPI.detect_external_binary(value) 2063 if not found: 2064 gmDispatcher.send ( 2065 signal = 'statustext', 2066 msg = _('The command [%s] is not found.') % value, 2067 beep = True 2068 ) 2069 return True, value 2070 2071 return True, binary
2072 #------------------------------------------ 2073 cmd = gmCfgWidgets.configure_string_option ( 2074 message = _( 2075 'Enter the shell command with which to start\n' 2076 'the image editor for visual progress notes.\n' 2077 '\n' 2078 'Any "%(img)s" included with the arguments\n' 2079 'will be replaced by the file name of the\n' 2080 'note template.' 2081 ), 2082 option = u'external.tools.visual_soap_editor_cmd', 2083 bias = 'user', 2084 default_value = None, 2085 validator = is_valid 2086 ) 2087 2088 return cmd 2089 #============================================================
2090 -def select_file_as_visual_progress_note_template(parent=None):
2091 if parent is None: 2092 parent = wx.GetApp().GetTopWindow() 2093 2094 dlg = wx.FileDialog ( 2095 parent = parent, 2096 message = _('Choose file to use as template for new visual progress note'), 2097 defaultDir = os.path.expanduser('~'), 2098 defaultFile = '', 2099 #wildcard = "%s (*)|*|%s (*.*)|*.*" % (_('all files'), _('all files (Win)')), 2100 style = wx.OPEN | wx.HIDE_READONLY | wx.FILE_MUST_EXIST 2101 ) 2102 result = dlg.ShowModal() 2103 2104 if result == wx.ID_CANCEL: 2105 dlg.Destroy() 2106 return None 2107 2108 full_filename = dlg.GetPath() 2109 dlg.Hide() 2110 dlg.Destroy() 2111 return full_filename
2112 #------------------------------------------------------------
2113 -def select_visual_progress_note_template(parent=None):
2114 2115 if parent is None: 2116 parent = wx.GetApp().GetTopWindow() 2117 2118 dlg = gmGuiHelpers.c3ButtonQuestionDlg ( 2119 parent, 2120 -1, 2121 caption = _('Visual progress note source'), 2122 question = _('From which source do you want to pick the image template ?'), 2123 button_defs = [ 2124 {'label': _('Database'), 'tooltip': _('List of templates in the database.'), 'default': True}, 2125 {'label': _('File'), 'tooltip': _('Files in the filesystem.'), 'default': False}, 2126 {'label': _('Device'), 'tooltip': _('Image capture devices (scanners, cameras, etc)'), 'default': False} 2127 ] 2128 ) 2129 result = dlg.ShowModal() 2130 dlg.Destroy() 2131 2132 # 1) select from template 2133 if result == wx.ID_YES: 2134 _log.debug('visual progress note template from: database template') 2135 from Gnumed.wxpython import gmFormWidgets 2136 template = gmFormWidgets.manage_form_templates ( 2137 parent = parent, 2138 template_types = [gmDocuments.DOCUMENT_TYPE_VISUAL_PROGRESS_NOTE], 2139 active_only = True 2140 ) 2141 if template is None: 2142 return (None, None) 2143 filename = template.export_to_file() 2144 if filename is None: 2145 gmDispatcher.send(signal = u'statustext', msg = _('Cannot export visual progress note template for [%s].') % template['name_long']) 2146 return (None, None) 2147 return (filename, True) 2148 2149 # 2) select from disk file 2150 if result == wx.ID_NO: 2151 _log.debug('visual progress note template from: disk file') 2152 fname = select_file_as_visual_progress_note_template(parent = parent) 2153 if fname is None: 2154 return (None, None) 2155 # create a copy of the picked file -- don't modify the original 2156 ext = os.path.splitext(fname)[1] 2157 tmp_name = gmTools.get_unique_filename(suffix = ext) 2158 _log.debug('visual progress note from file: [%s] -> [%s]', fname, tmp_name) 2159 shutil.copy2(fname, tmp_name) 2160 return (tmp_name, False) 2161 2162 # 3) acquire from capture device 2163 if result == wx.ID_CANCEL: 2164 _log.debug('visual progress note template from: image capture device') 2165 fnames = gmDocumentWidgets.acquire_images_from_capture_device(device = None, calling_window = parent) 2166 if fnames is None: 2167 return (None, None) 2168 if len(fnames) == 0: 2169 return (None, None) 2170 return (fnames[0], False) 2171 2172 _log.debug('no visual progress note template source selected') 2173 return (None, None)
2174 #------------------------------------------------------------
2175 -def edit_visual_progress_note(filename=None, episode=None, discard_unmodified=False, doc_part=None, health_issue=None):
2176 """This assumes <filename> contains an image which can be handled by the configured image editor.""" 2177 2178 if doc_part is not None: 2179 filename = doc_part.export_to_file() 2180 if filename is None: 2181 gmDispatcher.send(signal = u'statustext', msg = _('Cannot export visual progress note to file.')) 2182 return None 2183 2184 dbcfg = gmCfg.cCfgSQL() 2185 cmd = dbcfg.get2 ( 2186 option = u'external.tools.visual_soap_editor_cmd', 2187 workplace = gmSurgery.gmCurrentPractice().active_workplace, 2188 bias = 'user' 2189 ) 2190 2191 if cmd is None: 2192 gmDispatcher.send(signal = u'statustext', msg = _('Editor for visual progress note not configured.'), beep = False) 2193 cmd = configure_visual_progress_note_editor() 2194 if cmd is None: 2195 gmDispatcher.send(signal = u'statustext', msg = _('Editor for visual progress note not configured.'), beep = True) 2196 return None 2197 2198 if u'%(img)s' in cmd: 2199 cmd % {u'img': filename} 2200 else: 2201 cmd = u'%s %s' % (cmd, filename) 2202 2203 if discard_unmodified: 2204 original_stat = os.stat(filename) 2205 original_md5 = gmTools.file2md5(filename) 2206 2207 success = gmShellAPI.run_command_in_shell(cmd, blocking = True) 2208 if not success: 2209 gmGuiHelpers.gm_show_error ( 2210 _( 2211 'There was a problem with running the editor\n' 2212 'for visual progress notes.\n' 2213 '\n' 2214 ' [%s]\n' 2215 '\n' 2216 ) % cmd, 2217 _('Editing visual progress note') 2218 ) 2219 return None 2220 2221 try: 2222 open(filename, 'r').close() 2223 except StandardError: 2224 _log.exception('problem accessing visual progress note file [%s]', filename) 2225 gmGuiHelpers.gm_show_error ( 2226 _( 2227 'There was a problem reading the visual\n' 2228 'progress note from the file:\n' 2229 '\n' 2230 ' [%s]\n' 2231 '\n' 2232 ) % filename, 2233 _('Saving visual progress note') 2234 ) 2235 return None 2236 2237 if discard_unmodified: 2238 modified_stat = os.stat(filename) 2239 # same size ? 2240 if original_stat.st_size == modified_stat.st_size: 2241 modified_md5 = gmTools.file2md5(filename) 2242 # same hash ? 2243 if original_md5 == modified_md5: 2244 _log.debug('visual progress note (template) not modified') 2245 # ask user to decide 2246 msg = _( 2247 u'You either created a visual progress note from a template\n' 2248 u'in the database (rather than from a file on disk) or you\n' 2249 u'edited an existing visual progress note.\n' 2250 u'\n' 2251 u'The template/original was not modified at all, however.\n' 2252 u'\n' 2253 u'Do you still want to save the unmodified image as a\n' 2254 u'visual progress note into the EMR of the patient ?\n' 2255 ) 2256 save_unmodified = gmGuiHelpers.gm_show_question ( 2257 msg, 2258 _('Saving visual progress note') 2259 ) 2260 if not save_unmodified: 2261 _log.debug('user discarded unmodified note') 2262 return 2263 2264 if doc_part is not None: 2265 doc_part.update_data_from_file(fname = filename) 2266 doc_part.set_reviewed(technically_abnormal = False, clinically_relevant = True) 2267 return None 2268 2269 if not isinstance(episode, gmEMRStructItems.cEpisode): 2270 if episode is None: 2271 episode = _('visual progress notes') 2272 pat = gmPerson.gmCurrentPatient() 2273 emr = pat.get_emr() 2274 episode = emr.add_episode(episode_name = episode.strip(), pk_health_issue = health_issue, is_open = False) 2275 2276 doc = gmDocumentWidgets.save_file_as_new_document ( 2277 filename = filename, 2278 document_type = gmDocuments.DOCUMENT_TYPE_VISUAL_PROGRESS_NOTE, 2279 episode = episode, 2280 unlock_patient = True 2281 ) 2282 doc.set_reviewed(technically_abnormal = False, clinically_relevant = True) 2283 2284 return doc
2285 #============================================================
2286 -class cVisualSoapTemplatePhraseWheel(gmPhraseWheel.cPhraseWheel):
2287 """Phrasewheel to allow selection of visual SOAP template.""" 2288
2289 - def __init__(self, *args, **kwargs):
2290 2291 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs) 2292 2293 query = u""" 2294 SELECT 2295 pk AS data, 2296 name_short AS list_label, 2297 name_sort AS field_label 2298 FROM 2299 ref.paperwork_templates 2300 WHERE 2301 fk_template_type = (SELECT pk FROM ref.form_types WHERE name = '%s') AND ( 2302 name_long %%(fragment_condition)s 2303 OR 2304 name_short %%(fragment_condition)s 2305 ) 2306 ORDER BY list_label 2307 LIMIT 15 2308 """ % gmDocuments.DOCUMENT_TYPE_VISUAL_PROGRESS_NOTE 2309 2310 mp = gmMatchProvider.cMatchProvider_SQL2(queries = [query]) 2311 mp.setThresholds(2, 3, 5) 2312 2313 self.matcher = mp 2314 self.selection_only = True
2315 #--------------------------------------------------------
2316 - def _data2instance(self):
2317 if self.GetData() is None: 2318 return None 2319 2320 return gmForms.cFormTemplate(aPK_obj = self.GetData())
2321 #============================================================ 2322 from Gnumed.wxGladeWidgets import wxgVisualSoapPresenterPnl 2323
2324 -class cVisualSoapPresenterPnl(wxgVisualSoapPresenterPnl.wxgVisualSoapPresenterPnl):
2325
2326 - def __init__(self, *args, **kwargs):
2327 wxgVisualSoapPresenterPnl.wxgVisualSoapPresenterPnl.__init__(self, *args, **kwargs) 2328 self._SZR_soap = self.GetSizer() 2329 self.__bitmaps = []
2330 #-------------------------------------------------------- 2331 # external API 2332 #--------------------------------------------------------
2333 - def refresh(self, document_folder=None, episodes=None, encounter=None):
2334 2335 self.clear() 2336 if document_folder is not None: 2337 soap_docs = document_folder.get_visual_progress_notes(episodes = episodes, encounter = encounter) 2338 if len(soap_docs) > 0: 2339 for soap_doc in soap_docs: 2340 parts = soap_doc.parts 2341 if len(parts) == 0: 2342 continue 2343 part = parts[0] 2344 fname = part.export_to_file() 2345 if fname is None: 2346 continue 2347 2348 # create bitmap 2349 img = gmGuiHelpers.file2scaled_image ( 2350 filename = fname, 2351 height = 30 2352 ) 2353 #bmp = wx.StaticBitmap(self, -1, img, style = wx.NO_BORDER) 2354 bmp = wx_genstatbmp.GenStaticBitmap(self, -1, img, style = wx.NO_BORDER) 2355 2356 # create tooltip 2357 img = gmGuiHelpers.file2scaled_image ( 2358 filename = fname, 2359 height = 150 2360 ) 2361 tip = agw_stt.SuperToolTip ( 2362 u'', 2363 bodyImage = img, 2364 header = _('Created: %s') % part['date_generated'].strftime('%Y %B %d').decode(gmI18N.get_encoding()), 2365 footer = gmTools.coalesce(part['doc_comment'], u'').strip() 2366 ) 2367 tip.SetTopGradientColor('white') 2368 tip.SetMiddleGradientColor('white') 2369 tip.SetBottomGradientColor('white') 2370 tip.SetTarget(bmp) 2371 2372 bmp.doc_part = part 2373 bmp.Bind(wx.EVT_LEFT_UP, self._on_bitmap_leftclicked) 2374 # FIXME: add context menu for Delete/Clone/Add/Configure 2375 self._SZR_soap.Add(bmp, 0, wx.LEFT | wx.RIGHT | wx.TOP | wx.BOTTOM | wx.EXPAND, 3) 2376 self.__bitmaps.append(bmp) 2377 2378 self.GetParent().Layout()
2379 #--------------------------------------------------------
2380 - def clear(self):
2381 for child_idx in range(len(self._SZR_soap.GetChildren())): 2382 self._SZR_soap.Detach(child_idx) 2383 for bmp in self.__bitmaps: 2384 bmp.Destroy() 2385 self.__bitmaps = []
2386 #--------------------------------------------------------
2387 - def _on_bitmap_leftclicked(self, evt):
2388 wx.CallAfter ( 2389 edit_visual_progress_note, 2390 doc_part = evt.GetEventObject().doc_part, 2391 discard_unmodified = True 2392 )
2393 #============================================================ 2394 #from Gnumed.wxGladeWidgets import wxgVisualSoapPnl 2395 2396 #class cVisualSoapPnl(wxgVisualSoapPnl.wxgVisualSoapPnl): 2397 # 2398 # def __init__(self, *args, **kwargs): 2399 # 2400 # wxgVisualSoapPnl.wxgVisualSoapPnl.__init__(self, *args, **kwargs) 2401 # 2402 # # dummy episode to hold images 2403 # self.default_episode_name = _('visual progress notes') 2404 # #-------------------------------------------------------- 2405 # # external API 2406 # #-------------------------------------------------------- 2407 # def clear(self): 2408 # self._PRW_template.SetText(value = u'', data = None) 2409 # self._LCTRL_visual_soaps.set_columns([_('Sketches')]) 2410 # self._LCTRL_visual_soaps.set_string_items() 2411 # 2412 # self.show_image_and_metadata() 2413 # #-------------------------------------------------------- 2414 # def refresh(self, patient=None, encounter=None): 2415 # 2416 # self.clear() 2417 # 2418 # if patient is None: 2419 # patient = gmPerson.gmCurrentPatient() 2420 # 2421 # if not patient.connected: 2422 # return 2423 # 2424 # emr = patient.get_emr() 2425 # if encounter is None: 2426 # encounter = emr.active_encounter 2427 # 2428 # folder = patient.get_document_folder() 2429 # soap_docs = folder.get_documents ( 2430 # doc_type = gmDocuments.DOCUMENT_TYPE_VISUAL_PROGRESS_NOTE, 2431 # encounter = encounter['pk_encounter'] 2432 # ) 2433 # 2434 # if len(soap_docs) == 0: 2435 # self._BTN_delete.Enable(False) 2436 # return 2437 # 2438 # self._LCTRL_visual_soaps.set_string_items ([ 2439 # u'%s%s%s' % ( 2440 # gmTools.coalesce(sd['comment'], u'', u'%s\n'), 2441 # gmTools.coalesce(sd['ext_ref'], u'', u'%s\n'), 2442 # sd['episode'] 2443 # ) for sd in soap_docs 2444 # ]) 2445 # self._LCTRL_visual_soaps.set_data(soap_docs) 2446 # 2447 # self._BTN_delete.Enable(True) 2448 # #-------------------------------------------------------- 2449 # def show_image_and_metadata(self, doc=None): 2450 # 2451 # if doc is None: 2452 # self._IMG_soap.SetBitmap(wx.NullBitmap) 2453 # self._PRW_episode.SetText() 2454 # #self._PRW_comment.SetText(value = u'', data = None) 2455 # self._PRW_comment.SetValue(u'') 2456 # return 2457 # 2458 # parts = doc.parts 2459 # if len(parts) == 0: 2460 # gmDispatcher.send(signal = u'statustext', msg = _('No images in visual progress note.')) 2461 # return 2462 # 2463 # fname = parts[0].export_to_file() 2464 # if fname is None: 2465 # gmDispatcher.send(signal = u'statustext', msg = _('Cannot export visual progress note to file.')) 2466 # return 2467 # 2468 # img_data = None 2469 # rescaled_width = 300 2470 # try: 2471 # img_data = wx.Image(fname, wx.BITMAP_TYPE_ANY) 2472 # current_width = img_data.GetWidth() 2473 # current_height = img_data.GetHeight() 2474 # rescaled_height = (rescaled_width * current_height) / current_width 2475 # img_data.Rescale(rescaled_width, rescaled_height, quality = wx.IMAGE_QUALITY_HIGH) # w, h 2476 # bmp_data = wx.BitmapFromImage(img_data) 2477 # except: 2478 # _log.exception('cannot load visual progress note from [%s]', fname) 2479 # gmDispatcher.send(signal = u'statustext', msg = _('Cannot load visual progress note from [%s].') % fname) 2480 # del img_data 2481 # return 2482 # 2483 # del img_data 2484 # self._IMG_soap.SetBitmap(bmp_data) 2485 # 2486 # self._PRW_episode.SetText(value = doc['episode'], data = doc['pk_episode']) 2487 # if doc['comment'] is not None: 2488 # self._PRW_comment.SetValue(doc['comment'].strip()) 2489 # #-------------------------------------------------------- 2490 # # event handlers 2491 # #-------------------------------------------------------- 2492 # def _on_visual_soap_selected(self, event): 2493 # 2494 # doc = self._LCTRL_visual_soaps.get_selected_item_data(only_one = True) 2495 # self.show_image_and_metadata(doc = doc) 2496 # if doc is None: 2497 # return 2498 # 2499 # self._BTN_delete.Enable(True) 2500 # #-------------------------------------------------------- 2501 # def _on_visual_soap_deselected(self, event): 2502 # self._BTN_delete.Enable(False) 2503 # #-------------------------------------------------------- 2504 # def _on_visual_soap_activated(self, event): 2505 # 2506 # doc = self._LCTRL_visual_soaps.get_selected_item_data(only_one = True) 2507 # if doc is None: 2508 # self.show_image_and_metadata() 2509 # return 2510 # 2511 # parts = doc.parts 2512 # if len(parts) == 0: 2513 # gmDispatcher.send(signal = u'statustext', msg = _('No images in visual progress note.')) 2514 # return 2515 # 2516 # edit_visual_progress_note(doc_part = parts[0], discard_unmodified = True) 2517 # self.show_image_and_metadata(doc = doc) 2518 # 2519 # self._BTN_delete.Enable(True) 2520 # #-------------------------------------------------------- 2521 # def _on_from_template_button_pressed(self, event): 2522 # 2523 # template = self._PRW_template.GetData(as_instance = True) 2524 # if template is None: 2525 # return 2526 # 2527 # filename = template.export_to_file() 2528 # if filename is None: 2529 # gmDispatcher.send(signal = u'statustext', msg = _('Cannot export visual progress note template for [%s].') % template['name_long']) 2530 # return 2531 # 2532 # episode = self._PRW_episode.GetData(as_instance = True) 2533 # if episode is None: 2534 # episode = self._PRW_episode.GetValue().strip() 2535 # if episode == u'': 2536 # episode = self.default_episode_name 2537 # 2538 # # do not store note if not modified -- change if users complain 2539 # doc = edit_visual_progress_note(filename = filename, episode = episode, discard_unmodified = True) 2540 # if doc is None: 2541 # return 2542 # 2543 # if self._PRW_comment.GetValue().strip() == u'': 2544 # doc['comment'] = template['instance_type'] 2545 # else: 2546 # doc['comment'] = self._PRW_comment.GetValue().strip() 2547 # 2548 # doc.save() 2549 # self.show_image_and_metadata(doc = doc) 2550 # #-------------------------------------------------------- 2551 # def _on_from_file_button_pressed(self, event): 2552 # 2553 # dlg = wx.FileDialog ( 2554 # parent = self, 2555 # message = _('Choose a visual progress note template file'), 2556 # defaultDir = os.path.expanduser('~'), 2557 # defaultFile = '', 2558 # #wildcard = "%s (*)|*|%s (*.*)|*.*" % (_('all files'), _('all files (Win)')), 2559 # style = wx.OPEN | wx.HIDE_READONLY | wx.FILE_MUST_EXIST 2560 # ) 2561 # result = dlg.ShowModal() 2562 # if result == wx.ID_CANCEL: 2563 # dlg.Destroy() 2564 # return 2565 # 2566 # full_filename = dlg.GetPath() 2567 # dlg.Hide() 2568 # dlg.Destroy() 2569 # 2570 # # create a copy of the picked file -- don't modify the original 2571 # ext = os.path.splitext(full_filename)[1] 2572 # tmp_name = gmTools.get_unique_filename(suffix = ext) 2573 # _log.debug('visual progress note from file: [%s] -> [%s]', full_filename, tmp_name) 2574 # shutil.copy2(full_filename, tmp_name) 2575 # 2576 # episode = self._PRW_episode.GetData(as_instance = True) 2577 # if episode is None: 2578 # episode = self._PRW_episode.GetValue().strip() 2579 # if episode == u'': 2580 # episode = self.default_episode_name 2581 # 2582 # # always store note even if unmodified as we 2583 # # may simply want to store a clinical photograph 2584 # doc = edit_visual_progress_note(filename = tmp_name, episode = episode, discard_unmodified = False) 2585 # if self._PRW_comment.GetValue().strip() == u'': 2586 # # use filename as default comment (w/o extension) 2587 # doc['comment'] = os.path.splitext(os.path.split(full_filename)[1])[0] 2588 # else: 2589 # doc['comment'] = self._PRW_comment.GetValue().strip() 2590 # doc.save() 2591 # self.show_image_and_metadata(doc = doc) 2592 # 2593 # try: 2594 # os.remove(tmp_name) 2595 # except StandardError: 2596 # _log.exception('cannot remove [%s]', tmp_name) 2597 # 2598 # remove_original = gmGuiHelpers.gm_show_question ( 2599 # _( 2600 # 'Do you want to delete the original file\n' 2601 # '\n' 2602 # ' [%s]\n' 2603 # '\n' 2604 # 'from your computer ?' 2605 # ) % full_filename, 2606 # _('Saving visual progress note ...') 2607 # ) 2608 # if remove_original: 2609 # try: 2610 # os.remove(full_filename) 2611 # except StandardError: 2612 # _log.exception('cannot remove [%s]', full_filename) 2613 # #-------------------------------------------------------- 2614 # def _on_delete_button_pressed(self, event): 2615 # 2616 # doc = self._LCTRL_visual_soaps.get_selected_item_data(only_one = True) 2617 # if doc is None: 2618 # self.show_image_and_metadata() 2619 # return 2620 # 2621 # delete_it = gmGuiHelpers.gm_show_question ( 2622 # aMessage = _('Are you sure you want to delete the visual progress note ?'), 2623 # aTitle = _('Deleting visual progress note') 2624 # ) 2625 # if delete_it is True: 2626 # gmDocuments.delete_document ( 2627 # document_id = doc['pk_doc'], 2628 # encounter_id = doc['pk_encounter'] 2629 # ) 2630 # self.show_image_and_metadata() 2631 #============================================================ 2632 # main 2633 #------------------------------------------------------------ 2634 if __name__ == '__main__': 2635 2636 if len(sys.argv) < 2: 2637 sys.exit() 2638 2639 if sys.argv[1] != 'test': 2640 sys.exit() 2641 2642 gmI18N.activate_locale() 2643 gmI18N.install_domain(domain = 'gnumed') 2644 2645 #----------------------------------------
2646 - def test_select_narrative_from_episodes():
2647 pat = gmPersonSearch.ask_for_patient() 2648 gmPatSearchWidgets.set_active_patient(patient = pat) 2649 app = wx.PyWidgetTester(size = (200, 200)) 2650 sels = select_narrative_from_episodes() 2651 print "selected:" 2652 for sel in sels: 2653 print sel
2654 #----------------------------------------
2655 - def test_cSoapNoteExpandoEditAreaPnl():
2656 pat = gmPersonSearch.ask_for_patient() 2657 application = wx.PyWidgetTester(size=(800,500)) 2658 soap_input = cSoapNoteExpandoEditAreaPnl(application.frame, -1) 2659 application.frame.Show(True) 2660 application.MainLoop()
2661 #----------------------------------------
2662 - def test_cSoapPluginPnl():
2663 patient = gmPersonSearch.ask_for_patient() 2664 if patient is None: 2665 print "No patient. Exiting gracefully..." 2666 return 2667 gmPatSearchWidgets.set_active_patient(patient=patient) 2668 2669 application = wx.PyWidgetTester(size=(800,500)) 2670 soap_input = cSoapPluginPnl(application.frame, -1) 2671 application.frame.Show(True) 2672 soap_input._schedule_data_reget() 2673 application.MainLoop()
2674 #---------------------------------------- 2675 #test_select_narrative_from_episodes() 2676 test_cSoapNoteExpandoEditAreaPnl() 2677 #test_cSoapPluginPnl() 2678 2679 #============================================================ 2680