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

Source Code for Module Gnumed.wxpython.gmNarrativeWidgets

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