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