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