1 """GNUmed narrative handling widgets."""
2
3 __version__ = "$Revision: 1.46 $"
4 __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
5
6 import sys, logging, os, os.path, time, re as regex, shutil
7
8
9 import wx
10 import wx.lib.expando as wx_expando
11 import wx.lib.agw.supertooltip as agw_stt
12 import wx.lib.statbmp as wx_genstatbmp
13
14
15 if __name__ == '__main__':
16 sys.path.insert(0, '../../')
17 from Gnumed.pycommon import gmI18N
18 from Gnumed.pycommon import gmDispatcher
19 from Gnumed.pycommon import gmTools
20 from Gnumed.pycommon import gmDateTime
21 from Gnumed.pycommon import gmShellAPI
22 from Gnumed.pycommon import gmPG2
23 from Gnumed.pycommon import gmCfg
24 from Gnumed.pycommon import gmMatchProvider
25
26 from Gnumed.business import gmPerson, gmEMRStructItems, gmClinNarrative, gmSurgery
27 from Gnumed.business import gmForms, gmDocuments, gmPersonSearch
28
29 from Gnumed.wxpython import gmListWidgets
30 from Gnumed.wxpython import gmEMRStructWidgets
31 from Gnumed.wxpython import gmRegetMixin
32 from Gnumed.wxpython import gmPhraseWheel
33 from Gnumed.wxpython import gmGuiHelpers
34 from Gnumed.wxpython import gmPatSearchWidgets
35 from Gnumed.wxpython import gmCfgWidgets
36 from Gnumed.wxpython import gmDocumentWidgets
37
38 from Gnumed.exporters import gmPatientExporter
39
40
41 _log = logging.getLogger('gm.ui')
42 _log.info(__version__)
43
44
45
47
48
49 if patient is None:
50 patient = gmPerson.gmCurrentPatient()
51
52 if not patient.connected:
53 gmDispatcher.send(signal = 'statustext', msg = _('Cannot move progress notes. No active patient.'))
54 return False
55
56 if parent is None:
57 parent = wx.GetApp().GetTopWindow()
58
59 emr = patient.get_emr()
60
61 if encounters is None:
62 encs = emr.get_encounters(episodes = episodes)
63 encounters = gmEMRStructWidgets.select_encounters (
64 parent = parent,
65 patient = patient,
66 single_selection = False,
67 encounters = encs
68 )
69
70 notes = emr.get_clin_narrative (
71 encounters = encounters,
72 episodes = episodes
73 )
74
75
76 if move_all:
77 selected_narr = notes
78 else:
79 selected_narr = gmListWidgets.get_choices_from_list (
80 parent = parent,
81 caption = _('Moving progress notes between encounters ...'),
82 single_selection = False,
83 can_return_empty = True,
84 data = notes,
85 msg = _('\n Select the progress notes to move from the list !\n\n'),
86 columns = [_('when'), _('who'), _('type'), _('entry')],
87 choices = [
88 [ narr['date'].strftime('%x %H:%M'),
89 narr['provider'],
90 gmClinNarrative.soap_cat2l10n[narr['soap_cat']],
91 narr['narrative'].replace('\n', '/').replace('\r', '/')
92 ] for narr in notes
93 ]
94 )
95
96 if not selected_narr:
97 return True
98
99
100 enc2move2 = gmEMRStructWidgets.select_encounters (
101 parent = parent,
102 patient = patient,
103 single_selection = True
104 )
105
106 if not enc2move2:
107 return True
108
109 for narr in selected_narr:
110 narr['pk_encounter'] = enc2move2['pk_encounter']
111 narr.save()
112
113 return True
114
116
117
118 if patient is None:
119 patient = gmPerson.gmCurrentPatient()
120
121 if not patient.connected:
122 gmDispatcher.send(signal = 'statustext', msg = _('Cannot edit progress notes. No active patient.'))
123 return False
124
125 if parent is None:
126 parent = wx.GetApp().GetTopWindow()
127
128 emr = patient.get_emr()
129
130 def delete(item):
131 if item is None:
132 return False
133 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
134 parent,
135 -1,
136 caption = _('Deleting progress note'),
137 question = _(
138 'Are you positively sure you want to delete this\n'
139 'progress note from the medical record ?\n'
140 '\n'
141 'Note that even if you chose to delete the entry it will\n'
142 'still be (invisibly) kept in the audit trail to protect\n'
143 'you from litigation because physical deletion is known\n'
144 'to be unlawful in some jurisdictions.\n'
145 ),
146 button_defs = (
147 {'label': _('Delete'), 'tooltip': _('Yes, delete the progress note.'), 'default': False},
148 {'label': _('Cancel'), 'tooltip': _('No, do NOT delete the progress note.'), 'default': True}
149 )
150 )
151 decision = dlg.ShowModal()
152
153 if decision != wx.ID_YES:
154 return False
155
156 gmClinNarrative.delete_clin_narrative(narrative = item['pk_narrative'])
157 return True
158
159 def edit(item):
160 if item is None:
161 return False
162
163 dlg = gmGuiHelpers.cMultilineTextEntryDlg (
164 parent,
165 -1,
166 title = _('Editing progress note'),
167 msg = _('This is the original progress note:'),
168 data = item.format(left_margin = u' ', fancy = True),
169 text = item['narrative']
170 )
171 decision = dlg.ShowModal()
172
173 if decision != wx.ID_SAVE:
174 return False
175
176 val = dlg.value
177 dlg.Destroy()
178 if val.strip() == u'':
179 return False
180
181 item['narrative'] = val
182 item.save_payload()
183
184 return True
185
186 def refresh(lctrl):
187 notes = emr.get_clin_narrative (
188 encounters = encounters,
189 episodes = episodes,
190 providers = [ gmPerson.gmCurrentProvider()['short_alias'] ]
191 )
192 lctrl.set_string_items(items = [
193 [ narr['date'].strftime('%x %H:%M'),
194 gmClinNarrative.soap_cat2l10n[narr['soap_cat']],
195 narr['narrative'].replace('\n', '/').replace('\r', '/')
196 ] for narr in notes
197 ])
198 lctrl.set_data(data = notes)
199
200
201 gmListWidgets.get_choices_from_list (
202 parent = parent,
203 caption = _('Managing progress notes'),
204 msg = _(
205 '\n'
206 ' This list shows the progress notes by %s.\n'
207 '\n'
208 ) % gmPerson.gmCurrentProvider()['short_alias'],
209 columns = [_('when'), _('type'), _('entry')],
210 single_selection = True,
211 can_return_empty = False,
212 edit_callback = edit,
213 delete_callback = delete,
214 refresh_callback = refresh,
215 ignore_OK_button = True
216 )
217
219
220 if parent is None:
221 parent = wx.GetApp().GetTopWindow()
222
223 searcher = wx.TextEntryDialog (
224 parent = parent,
225 message = _('Enter (regex) term to search for across all EMRs:'),
226 caption = _('Text search across all EMRs'),
227 style = wx.OK | wx.CANCEL | wx.CENTRE
228 )
229 result = searcher.ShowModal()
230
231 if result != wx.ID_OK:
232 return
233
234 wx.BeginBusyCursor()
235 term = searcher.GetValue()
236 searcher.Destroy()
237 results = gmClinNarrative.search_text_across_emrs(search_term = term)
238 wx.EndBusyCursor()
239
240 if len(results) == 0:
241 gmGuiHelpers.gm_show_info (
242 _(
243 'Nothing found for search term:\n'
244 ' "%s"'
245 ) % term,
246 _('Search results')
247 )
248 return
249
250 items = [ [gmPerson.cIdentity(aPK_obj = r['pk_patient'])['description_gender'], r['narrative'], r['src_table']] for r in results ]
251
252 selected_patient = gmListWidgets.get_choices_from_list (
253 parent = parent,
254 caption = _('Search results for %s') % term,
255 choices = items,
256 columns = [_('Patient'), _('Match'), _('Match location')],
257 data = [ r['pk_patient'] for r in results ],
258 single_selection = True,
259 can_return_empty = False
260 )
261
262 if selected_patient is None:
263 return
264
265 wx.CallAfter(gmPatSearchWidgets.set_active_patient, patient = gmPerson.cIdentity(aPK_obj = selected_patient))
266
268
269
270 if patient is None:
271 patient = gmPerson.gmCurrentPatient()
272
273 if not patient.connected:
274 gmDispatcher.send(signal = 'statustext', msg = _('Cannot search EMR. No active patient.'))
275 return False
276
277 if parent is None:
278 parent = wx.GetApp().GetTopWindow()
279
280 searcher = wx.TextEntryDialog (
281 parent = parent,
282 message = _('Enter search term:'),
283 caption = _('Text search of entire EMR of active patient'),
284 style = wx.OK | wx.CANCEL | wx.CENTRE
285 )
286 result = searcher.ShowModal()
287
288 if result != wx.ID_OK:
289 searcher.Destroy()
290 return False
291
292 wx.BeginBusyCursor()
293 val = searcher.GetValue()
294 searcher.Destroy()
295 emr = patient.get_emr()
296 rows = emr.search_narrative_simple(val)
297 wx.EndBusyCursor()
298
299 if len(rows) == 0:
300 gmGuiHelpers.gm_show_info (
301 _(
302 'Nothing found for search term:\n'
303 ' "%s"'
304 ) % val,
305 _('Search results')
306 )
307 return True
308
309 txt = u''
310 for row in rows:
311 txt += u'%s: %s\n' % (
312 row['soap_cat'],
313 row['narrative']
314 )
315
316 txt += u' %s: %s - %s %s\n' % (
317 _('Encounter'),
318 row['encounter_started'].strftime('%x %H:%M'),
319 row['encounter_ended'].strftime('%H:%M'),
320 row['encounter_type']
321 )
322 txt += u' %s: %s\n' % (
323 _('Episode'),
324 row['episode']
325 )
326 txt += u' %s: %s\n\n' % (
327 _('Health issue'),
328 row['health_issue']
329 )
330
331 msg = _(
332 'Search term was: "%s"\n'
333 '\n'
334 'Search results:\n\n'
335 '%s\n'
336 ) % (val, txt)
337
338 dlg = wx.MessageDialog (
339 parent = parent,
340 message = msg,
341 caption = _('Search results for %s') % val,
342 style = wx.OK | wx.STAY_ON_TOP
343 )
344 dlg.ShowModal()
345 dlg.Destroy()
346
347 return True
348
350
351
352 pat = gmPerson.gmCurrentPatient()
353 if not pat.connected:
354 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export EMR for Medistar. No active patient.'))
355 return False
356
357 if encounter is None:
358 encounter = pat.get_emr().active_encounter
359
360 if parent is None:
361 parent = wx.GetApp().GetTopWindow()
362
363
364 aWildcard = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files"))
365
366 aDefDir = os.path.abspath(os.path.expanduser(os.path.join('~', 'gnumed','export')))
367
368 fname = '%s-%s-%s-%s-%s.txt' % (
369 'Medistar-MD',
370 time.strftime('%Y-%m-%d',time.localtime()),
371 pat['lastnames'].replace(' ', '-'),
372 pat['firstnames'].replace(' ', '_'),
373 pat.get_formatted_dob(format = '%Y-%m-%d')
374 )
375 dlg = wx.FileDialog (
376 parent = parent,
377 message = _("Save EMR extract for MEDISTAR import as..."),
378 defaultDir = aDefDir,
379 defaultFile = fname,
380 wildcard = aWildcard,
381 style = wx.SAVE
382 )
383 choice = dlg.ShowModal()
384 fname = dlg.GetPath()
385 dlg.Destroy()
386 if choice != wx.ID_OK:
387 return False
388
389 wx.BeginBusyCursor()
390 _log.debug('exporting encounter for medistar import to [%s]', fname)
391 exporter = gmPatientExporter.cMedistarSOAPExporter()
392 successful, fname = exporter.export_to_file (
393 filename = fname,
394 encounter = encounter,
395 soap_cats = u'soap',
396 export_to_import_file = True
397 )
398 if not successful:
399 gmGuiHelpers.gm_show_error (
400 _('Error exporting progress notes for MEDISTAR import.'),
401 _('MEDISTAR progress notes export')
402 )
403 wx.EndBusyCursor()
404 return False
405
406 gmDispatcher.send(signal = 'statustext', msg = _('Successfully exported progress notes into file [%s] for Medistar import.') % fname, beep=False)
407
408 wx.EndBusyCursor()
409 return True
410
412 """soap_cats needs to be a list"""
413
414 if parent is None:
415 parent = wx.GetApp().GetTopWindow()
416
417 pat = gmPerson.gmCurrentPatient()
418 emr = pat.get_emr()
419
420 selected_soap = {}
421 selected_narrative_pks = []
422
423
424 def pick_soap_from_episode(episode):
425
426 narr_for_epi = emr.get_clin_narrative(episodes = [episode['pk_episode']], soap_cats = soap_cats)
427
428 if len(narr_for_epi) == 0:
429 gmDispatcher.send(signal = 'statustext', msg = _('No narrative available for selected episode.'))
430 return True
431
432 dlg = cNarrativeListSelectorDlg (
433 parent = parent,
434 id = -1,
435 narrative = narr_for_epi,
436 msg = _(
437 '\n This is the narrative (type %s) for the chosen episodes.\n'
438 '\n'
439 ' Now, mark the entries you want to include in your report.\n'
440 ) % u'/'.join([ gmClinNarrative.soap_cat2l10n[cat] for cat in gmTools.coalesce(soap_cats, list(u'soap')) ])
441 )
442
443
444
445
446
447
448 btn_pressed = dlg.ShowModal()
449 selected_narr = dlg.get_selected_item_data()
450 dlg.Destroy()
451
452 if btn_pressed == wx.ID_CANCEL:
453 return True
454
455 selected_narrative_pks = [ i['pk_narrative'] for i in selected_narr ]
456 for narr in selected_narr:
457 selected_soap[narr['pk_narrative']] = narr
458
459 print "before returning from picking soap"
460
461 return True
462
463 selected_episode_pks = []
464
465 all_epis = [ epi for epi in emr.get_episodes() if epi.has_narrative ]
466
467 if len(all_epis) == 0:
468 gmDispatcher.send(signal = 'statustext', msg = _('No episodes recorded for the health issues selected.'))
469 return []
470
471 dlg = gmEMRStructWidgets.cEpisodeListSelectorDlg (
472 parent = parent,
473 id = -1,
474 episodes = all_epis,
475 msg = _('\n Select the the episode you want to report on.\n')
476 )
477
478
479
480
481
482
483 dlg.left_extra_button = (
484 _('Pick SOAP'),
485 _('Pick SOAP entries from topmost selected episode'),
486 pick_soap_from_episode
487 )
488 btn_pressed = dlg.ShowModal()
489 dlg.Destroy()
490
491 if btn_pressed == wx.ID_CANCEL:
492 return None
493
494 return selected_soap.values()
495
497 """soap_cats needs to be a list"""
498
499 pat = gmPerson.gmCurrentPatient()
500 emr = pat.get_emr()
501
502 if parent is None:
503 parent = wx.GetApp().GetTopWindow()
504
505 selected_soap = {}
506 selected_issue_pks = []
507 selected_episode_pks = []
508 selected_narrative_pks = []
509
510 while 1:
511
512 all_issues = emr.get_health_issues()
513 all_issues.insert(0, gmEMRStructItems.get_dummy_health_issue())
514 dlg = gmEMRStructWidgets.cIssueListSelectorDlg (
515 parent = parent,
516 id = -1,
517 issues = all_issues,
518 msg = _('\n In the list below mark the health issues you want to report on.\n')
519 )
520 selection_idxs = []
521 for idx in range(len(all_issues)):
522 if all_issues[idx]['pk_health_issue'] in selected_issue_pks:
523 selection_idxs.append(idx)
524 if len(selection_idxs) != 0:
525 dlg.set_selections(selections = selection_idxs)
526 btn_pressed = dlg.ShowModal()
527 selected_issues = dlg.get_selected_item_data()
528 dlg.Destroy()
529
530 if btn_pressed == wx.ID_CANCEL:
531 return selected_soap.values()
532
533 selected_issue_pks = [ i['pk_health_issue'] for i in selected_issues ]
534
535 while 1:
536
537 all_epis = emr.get_episodes(issues = selected_issue_pks)
538
539 if len(all_epis) == 0:
540 gmDispatcher.send(signal = 'statustext', msg = _('No episodes recorded for the health issues selected.'))
541 break
542
543 dlg = gmEMRStructWidgets.cEpisodeListSelectorDlg (
544 parent = parent,
545 id = -1,
546 episodes = all_epis,
547 msg = _(
548 '\n These are the episodes known for the health issues just selected.\n\n'
549 ' Now, mark the the episodes you want to report on.\n'
550 )
551 )
552 selection_idxs = []
553 for idx in range(len(all_epis)):
554 if all_epis[idx]['pk_episode'] in selected_episode_pks:
555 selection_idxs.append(idx)
556 if len(selection_idxs) != 0:
557 dlg.set_selections(selections = selection_idxs)
558 btn_pressed = dlg.ShowModal()
559 selected_epis = dlg.get_selected_item_data()
560 dlg.Destroy()
561
562 if btn_pressed == wx.ID_CANCEL:
563 break
564
565 selected_episode_pks = [ i['pk_episode'] for i in selected_epis ]
566
567
568 all_narr = emr.get_clin_narrative(episodes = selected_episode_pks, soap_cats = soap_cats)
569
570 if len(all_narr) == 0:
571 gmDispatcher.send(signal = 'statustext', msg = _('No narrative available for selected episodes.'))
572 continue
573
574 dlg = cNarrativeListSelectorDlg (
575 parent = parent,
576 id = -1,
577 narrative = all_narr,
578 msg = _(
579 '\n This is the narrative (type %s) for the chosen episodes.\n\n'
580 ' Now, mark the entries you want to include in your report.\n'
581 ) % u'/'.join([ gmClinNarrative.soap_cat2l10n[cat] for cat in gmTools.coalesce(soap_cats, list(u'soap')) ])
582 )
583 selection_idxs = []
584 for idx in range(len(all_narr)):
585 if all_narr[idx]['pk_narrative'] in selected_narrative_pks:
586 selection_idxs.append(idx)
587 if len(selection_idxs) != 0:
588 dlg.set_selections(selections = selection_idxs)
589 btn_pressed = dlg.ShowModal()
590 selected_narr = dlg.get_selected_item_data()
591 dlg.Destroy()
592
593 if btn_pressed == wx.ID_CANCEL:
594 continue
595
596 selected_narrative_pks = [ i['pk_narrative'] for i in selected_narr ]
597 for narr in selected_narr:
598 selected_soap[narr['pk_narrative']] = narr
599
601
603
604 narrative = kwargs['narrative']
605 del kwargs['narrative']
606
607 gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs)
608
609 self.SetTitle(_('Select the narrative you are interested in ...'))
610
611 self._LCTRL_items.set_columns([_('when'), _('who'), _('type'), _('entry')])
612
613 self._LCTRL_items.set_string_items (
614 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 ]
615 )
616 self._LCTRL_items.set_column_widths()
617 self._LCTRL_items.set_data(data = narrative)
618
619 from Gnumed.wxGladeWidgets import wxgMoveNarrativeDlg
620
622
624
625 self.encounter = kwargs['encounter']
626 self.source_episode = kwargs['episode']
627 del kwargs['encounter']
628 del kwargs['episode']
629
630 wxgMoveNarrativeDlg.wxgMoveNarrativeDlg.__init__(self, *args, **kwargs)
631
632 self.LBL_source_episode.SetLabel(u'%s%s' % (self.source_episode['description'], gmTools.coalesce(self.source_episode['health_issue'], u'', u' (%s)')))
633 self.LBL_encounter.SetLabel('%s: %s %s - %s' % (
634 self.encounter['started'].strftime('%x').decode(gmI18N.get_encoding()),
635 self.encounter['l10n_type'],
636 self.encounter['started'].strftime('%H:%M'),
637 self.encounter['last_affirmed'].strftime('%H:%M')
638 ))
639 pat = gmPerson.gmCurrentPatient()
640 emr = pat.get_emr()
641 narr = emr.get_clin_narrative(episodes=[self.source_episode['pk_episode']], encounters=[self.encounter['pk_encounter']])
642 if len(narr) == 0:
643 narr = [{'narrative': _('There is no narrative for this episode in this encounter.')}]
644 self.LBL_narrative.SetLabel(u'\n'.join([n['narrative'] for n in narr]))
645
646
668
669 from Gnumed.wxGladeWidgets import wxgSoapPluginPnl
670
671 -class cSoapPluginPnl(wxgSoapPluginPnl.wxgSoapPluginPnl, gmRegetMixin.cRegetOnPaintMixin):
672 """A panel for in-context editing of progress notes.
673
674 Expects to be used as a notebook page.
675
676 Left hand side:
677 - problem list (health issues and active episodes)
678 - previous notes
679
680 Right hand side:
681 - encounter details fields
682 - notebook with progress note editors
683 - visual progress notes
684 - hints
685
686 Listens to patient change signals, thus acts on the current patient.
687 """
699
700
701
703
704 if not self.__encounter_valid_for_save():
705 return False
706
707 emr = self.__pat.get_emr()
708 enc = emr.active_encounter
709
710 enc['pk_type'] = self._PRW_encounter_type.GetData()
711 enc['started'] = self._PRW_encounter_start.GetData().get_pydt()
712 enc['last_affirmed'] = self._PRW_encounter_end.GetData().get_pydt()
713 rfe = self._TCTRL_rfe.GetValue().strip()
714 if len(rfe) == 0:
715 enc['reason_for_encounter'] = None
716 else:
717 enc['reason_for_encounter'] = rfe
718 aoe = self._TCTRL_aoe.GetValue().strip()
719 if len(aoe) == 0:
720 enc['assessment_of_encounter'] = None
721 else:
722 enc['assessment_of_encounter'] = aoe
723
724 enc.save_payload()
725
726 return True
727
728
729
731 self._LCTRL_active_problems.set_columns([_('Last'), _('Problem'), _('Health issue')])
732 self._LCTRL_active_problems.set_string_items()
733
734 self._splitter_main.SetSashGravity(0.5)
735 self._splitter_left.SetSashGravity(0.5)
736 self._splitter_right.SetSashGravity(1.0)
737
738
739 splitter_size = self._splitter_main.GetSizeTuple()[0]
740 self._splitter_main.SetSashPosition(splitter_size * 3 / 10, True)
741
742 splitter_size = self._splitter_left.GetSizeTuple()[1]
743 self._splitter_left.SetSashPosition(splitter_size * 6 / 20, True)
744
745 splitter_size = self._splitter_right.GetSizeTuple()[1]
746 self._splitter_right.SetSashPosition(splitter_size * 15 / 20, True)
747
748
749
750
751 self._NB_soap_editors.DeleteAllPages()
752
754 """Clear all information from input panel."""
755
756 self._LCTRL_active_problems.set_string_items()
757
758 self._TCTRL_recent_notes.SetValue(u'')
759
760 self._PRW_encounter_type.SetText(suppress_smarts = True)
761 self._PRW_encounter_start.SetText(suppress_smarts = True)
762 self._PRW_encounter_end.SetText(suppress_smarts = True)
763 self._TCTRL_rfe.SetValue(u'')
764 self._TCTRL_aoe.SetValue(u'')
765
766 self._NB_soap_editors.DeleteAllPages()
767 self._NB_soap_editors.add_editor()
768
769 self._lbl_hints.SetLabel(u'')
770
772 """Update health problems list."""
773
774 self._LCTRL_active_problems.set_string_items()
775
776 emr = self.__pat.get_emr()
777 problems = emr.get_problems (
778 include_closed_episodes = self._CHBOX_show_closed_episodes.IsChecked(),
779 include_irrelevant_issues = self._CHBOX_irrelevant_issues.IsChecked()
780 )
781
782 list_items = []
783 active_problems = []
784 for problem in problems:
785 if not problem['problem_active']:
786 if not problem['is_potential_problem']:
787 continue
788
789 active_problems.append(problem)
790
791 if problem['type'] == 'issue':
792 issue = emr.problem2issue(problem)
793 last_encounter = emr.get_last_encounter(issue_id = issue['pk_health_issue'])
794 if last_encounter is None:
795 last = issue['modified_when'].strftime('%m/%Y')
796 else:
797 last = last_encounter['last_affirmed'].strftime('%m/%Y')
798
799 list_items.append([last, problem['problem'], gmTools.u_left_arrow])
800
801 elif problem['type'] == 'episode':
802 epi = emr.problem2episode(problem)
803 last_encounter = emr.get_last_encounter(episode_id = epi['pk_episode'])
804 if last_encounter is None:
805 last = epi['episode_modified_when'].strftime('%m/%Y')
806 else:
807 last = last_encounter['last_affirmed'].strftime('%m/%Y')
808
809 list_items.append ([
810 last,
811 problem['problem'],
812 gmTools.coalesce(initial = epi['health_issue'], instead = gmTools.u_diameter)
813 ])
814
815 self._LCTRL_active_problems.set_string_items(items = list_items)
816 self._LCTRL_active_problems.set_column_widths()
817 self._LCTRL_active_problems.set_data(data = active_problems)
818
819 showing_potential_problems = (
820 self._CHBOX_show_closed_episodes.IsChecked()
821 or
822 self._CHBOX_irrelevant_issues.IsChecked()
823 )
824 if showing_potential_problems:
825 self._SZR_problem_list_staticbox.SetLabel(_('%s (active+potential) problems') % len(list_items))
826 else:
827 self._SZR_problem_list_staticbox.SetLabel(_('%s active problems') % len(list_items))
828
829 return True
830
832 soap = u''
833 emr = self.__pat.get_emr()
834 prev_enc = emr.get_last_but_one_encounter(issue_id = problem['pk_health_issue'])
835 if prev_enc is not None:
836 soap += prev_enc.format (
837 issues = [ problem['pk_health_issue'] ],
838 with_soap = True,
839 with_docs = False,
840 with_tests = False,
841 patient = self.__pat,
842 fancy_header = False,
843 with_rfe_aoe = True
844 )
845
846 tmp = emr.active_encounter.format_soap (
847 soap_cats = 'soap',
848 emr = emr,
849 issues = [ problem['pk_health_issue'] ],
850 )
851 if len(tmp) > 0:
852 soap += _('Current encounter:') + u'\n'
853 soap += u'\n'.join(tmp) + u'\n'
854
855 if problem['summary'] is not None:
856 soap += u'\n-- %s ----------\n%s' % (
857 _('Cumulative summary'),
858 gmTools.wrap (
859 text = problem['summary'],
860 width = 45,
861 initial_indent = u' ',
862 subsequent_indent = u' '
863 ).strip('\n')
864 )
865
866 return soap
867
869 soap = u''
870 emr = self.__pat.get_emr()
871 prev_enc = emr.get_last_but_one_encounter(episode_id = problem['pk_episode'])
872 if prev_enc is not None:
873 soap += prev_enc.format (
874 episodes = [ problem['pk_episode'] ],
875 with_soap = True,
876 with_docs = False,
877 with_tests = False,
878 patient = self.__pat,
879 fancy_header = False,
880 with_rfe_aoe = True
881 )
882 else:
883 if problem['pk_health_issue'] is not None:
884 prev_enc = emr.get_last_but_one_encounter(episode_id = problem['pk_health_issue'])
885 if prev_enc is not None:
886 soap += prev_enc.format (
887 with_soap = True,
888 with_docs = False,
889 with_tests = False,
890 patient = self.__pat,
891 issues = [ problem['pk_health_issue'] ],
892 fancy_header = False,
893 with_rfe_aoe = True
894 )
895
896 tmp = emr.active_encounter.format_soap (
897 soap_cats = 'soap',
898 emr = emr,
899 issues = [ problem['pk_health_issue'] ],
900 )
901 if len(tmp) > 0:
902 soap += _('Current encounter:') + u'\n'
903 soap += u'\n'.join(tmp) + u'\n'
904
905 if problem['summary'] is not None:
906 soap += u'\n-- %s ----------\n%s' % (
907 _('Cumulative summary'),
908 gmTools.wrap (
909 text = problem['summary'],
910 width = 45,
911 initial_indent = u' ',
912 subsequent_indent = u' '
913 ).strip('\n')
914 )
915
916 return soap
917
920
944
946 """This refreshes the recent-notes part."""
947
948 soap = u''
949 caption = u'<?>'
950
951 if problem['type'] == u'issue':
952 caption = problem['problem'][:35]
953 soap = self.__get_soap_for_issue_problem(problem = problem)
954
955 elif problem['type'] == u'episode':
956 caption = problem['problem'][:35]
957 soap = self.__get_soap_for_episode_problem(problem = problem)
958
959 self._TCTRL_recent_notes.SetValue(soap)
960 self._TCTRL_recent_notes.ShowPosition(self._TCTRL_recent_notes.GetLastPosition())
961 self._SZR_recent_notes_staticbox.SetLabel(_('Most recent notes on %s%s%s') % (
962 gmTools.u_left_double_angle_quote,
963 caption,
964 gmTools.u_right_double_angle_quote
965 ))
966
967 self._TCTRL_recent_notes.Refresh()
968
969 return True
970
998
1000 """Assumes that the field data is valid."""
1001
1002 emr = self.__pat.get_emr()
1003 enc = emr.active_encounter
1004
1005 data = {
1006 'pk_type': self._PRW_encounter_type.GetData(),
1007 'reason_for_encounter': gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u''),
1008 'assessment_of_encounter': gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u''),
1009 'pk_location': enc['pk_location'],
1010 'pk_patient': enc['pk_patient']
1011 }
1012
1013 if self._PRW_encounter_start.GetData() is None:
1014 data['started'] = None
1015 else:
1016 data['started'] = self._PRW_encounter_start.GetData().get_pydt()
1017
1018 if self._PRW_encounter_end.GetData() is None:
1019 data['last_affirmed'] = None
1020 else:
1021 data['last_affirmed'] = self._PRW_encounter_end.GetData().get_pydt()
1022
1023 return not enc.same_payload(another_object = data)
1024
1026
1027 found_error = False
1028
1029 if self._PRW_encounter_type.GetData() is None:
1030 found_error = True
1031 msg = _('Cannot save encounter: missing type.')
1032
1033 if self._PRW_encounter_start.GetData() is None:
1034 found_error = True
1035 msg = _('Cannot save encounter: missing start time.')
1036
1037 if self._PRW_encounter_end.GetData() is None:
1038 found_error = True
1039 msg = _('Cannot save encounter: missing end time.')
1040
1041 if found_error:
1042 gmDispatcher.send(signal = 'statustext', msg = msg, beep = True)
1043 return False
1044
1045 return True
1046
1047
1048
1050 """Configure enabled event signals."""
1051
1052 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection)
1053 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
1054 gmDispatcher.connect(signal = u'episode_mod_db', receiver = self._on_episode_issue_mod_db)
1055 gmDispatcher.connect(signal = u'health_issue_mod_db', receiver = self._on_episode_issue_mod_db)
1056 gmDispatcher.connect(signal = u'doc_mod_db', receiver = self._on_doc_mod_db)
1057 gmDispatcher.connect(signal = u'current_encounter_modified', receiver = self._on_current_encounter_modified)
1058 gmDispatcher.connect(signal = u'current_encounter_switched', receiver = self._on_current_encounter_switched)
1059
1060
1061 self.__pat.register_pre_selection_callback(callback = self._pre_selection_callback)
1062 gmDispatcher.send(signal = u'register_pre_exit_callback', callback = self._pre_exit_callback)
1063
1065 """Another patient is about to be activated.
1066
1067 Patient change will not proceed before this returns True.
1068 """
1069
1070
1071 if not self.__pat.connected:
1072 return True
1073 return self._NB_soap_editors.warn_on_unsaved_soap()
1074
1076 """The client is about to be shut down.
1077
1078 Shutdown will not proceed before this returns.
1079 """
1080 if not self.__pat.connected:
1081 return True
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097 emr = self.__pat.get_emr()
1098 saved = self._NB_soap_editors.save_all_editors (
1099 emr = emr,
1100 episode_name_candidates = [
1101 gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u''),
1102 gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u'')
1103 ]
1104 )
1105 if not saved:
1106 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save all editors. Some were kept open.'), beep = True)
1107 return True
1108
1110 wx.CallAfter(self.__on_pre_patient_selection)
1111
1113 self.__reset_ui_content()
1114
1116 wx.CallAfter(self._schedule_data_reget)
1117 self.__patient_just_changed = True
1118
1120 wx.CallAfter(self.__refresh_current_editor)
1121
1123 wx.CallAfter(self._schedule_data_reget)
1124
1126 wx.CallAfter(self.__refresh_encounter)
1127
1129 wx.CallAfter(self.__on_current_encounter_switched)
1130
1132 self.__refresh_encounter()
1133
1134
1135
1137 """Show related note at the bottom."""
1138 pass
1139
1141 """Show related note at the bottom."""
1142 emr = self.__pat.get_emr()
1143 self.__refresh_recent_notes (
1144 problem = self._LCTRL_active_problems.get_selected_item_data(only_one = True)
1145 )
1146
1148 """Open progress note editor for this problem.
1149 """
1150 problem = self._LCTRL_active_problems.get_selected_item_data(only_one = True)
1151 if problem is None:
1152 return True
1153
1154 dbcfg = gmCfg.cCfgSQL()
1155 allow_duplicate_editors = bool(dbcfg.get2 (
1156 option = u'horstspace.soap_editor.allow_same_episode_multiple_times',
1157 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1158 bias = u'user',
1159 default = False
1160 ))
1161 if self._NB_soap_editors.add_editor(problem = problem, allow_same_problem = allow_duplicate_editors):
1162 return True
1163
1164 gmGuiHelpers.gm_show_error (
1165 aMessage = _(
1166 'Cannot open progress note editor for\n\n'
1167 '[%s].\n\n'
1168 ) % problem['problem'],
1169 aTitle = _('opening progress note editor')
1170 )
1171 event.Skip()
1172 return False
1173
1175 self.__refresh_problem_list()
1176
1178 self.__refresh_problem_list()
1179
1180
1181
1185
1189
1193
1204
1224
1229
1230
1231
1235
1259
1260
1261
1270
1282
1283
1284
1286 self.__refresh_problem_list()
1287 self.__refresh_encounter()
1288 self.__setup_initial_patient_editors()
1289 return True
1290
1474
1475 from Gnumed.wxGladeWidgets import wxgSoapNoteExpandoEditAreaPnl
1476
1478 """An Edit Area like panel for entering progress notes.
1479
1480 Subjective:
1481 expando text ctrl
1482 Objective:
1483 expando text ctrl
1484 Assessment:
1485 expando text ctrl
1486 Plan:
1487 expando text ctrl
1488 Problem summary:
1489 text ctrl
1490 visual progress notes
1491 panel with images
1492
1493 - knows the problem this edit area is about
1494 - can deal with issue or episode type problems
1495 """
1496
1498
1499 try:
1500 self.problem = kwargs['problem']
1501 del kwargs['problem']
1502 except KeyError:
1503 self.problem = None
1504
1505 wxgSoapNoteExpandoEditAreaPnl.wxgSoapNoteExpandoEditAreaPnl.__init__(self, *args, **kwargs)
1506
1507 self.fields = [
1508 self._TCTRL_Soap,
1509 self._TCTRL_sOap,
1510 self._TCTRL_soAp,
1511 self._TCTRL_soaP
1512 ]
1513
1514 self.__init_ui()
1515 self.__register_interests()
1516
1523
1527
1529 if self.problem is None:
1530 return
1531 if self.problem['summary'] is None:
1532 self._TCTRL_summary.SetValue(u'')
1533 else:
1534 self._TCTRL_summary.SetValue(self.problem['summary'])
1535
1537 if self.problem is None:
1538 self._PNL_visual_soap.refresh(document_folder = None)
1539 return
1540
1541 if self.problem['type'] == u'issue':
1542 self._PNL_visual_soap.refresh(document_folder = None)
1543 return
1544
1545 if self.problem['type'] == u'episode':
1546 pat = gmPerson.gmCurrentPatient()
1547 doc_folder = pat.get_document_folder()
1548 emr = pat.get_emr()
1549 self._PNL_visual_soap.refresh (
1550 document_folder = doc_folder,
1551 episodes = [self.problem['pk_episode']],
1552 encounter = emr.active_encounter['pk_encounter']
1553 )
1554 return
1555
1557 for field in self.fields:
1558 field.SetValue(u'')
1559 self._TCTRL_summary.SetValue(u'')
1560 self._PNL_visual_soap.clear()
1561
1563 fname, discard_unmodified = select_visual_progress_note_template(parent = self)
1564 if fname is None:
1565 return False
1566
1567 if self.problem is None:
1568 issue = None
1569 episode = None
1570 elif self.problem['type'] == 'issue':
1571 issue = self.problem['pk_health_issue']
1572 episode = None
1573 else:
1574 issue = self.problem['pk_health_issue']
1575 episode = gmEMRStructItems.problem2episode(self.problem)
1576
1577 wx.CallAfter (
1578 edit_visual_progress_note,
1579 filename = fname,
1580 episode = episode,
1581 discard_unmodified = discard_unmodified,
1582 health_issue = issue
1583 )
1584
1585 - def save(self, emr=None, episode_name_candidates=None, encounter=None):
1586
1587 if self.empty:
1588 return True
1589
1590
1591 if (self.problem is None) or (self.problem['type'] == 'issue'):
1592
1593 episode_name_candidates.append(u'')
1594 for candidate in episode_name_candidates:
1595 if candidate is None:
1596 continue
1597 epi_name = candidate.strip().replace('\r', '//').replace('\n', '//')
1598 break
1599
1600 dlg = wx.TextEntryDialog (
1601 parent = self,
1602 message = _('Enter a short working name for this new problem:'),
1603 caption = _('Creating a problem (episode) to save the notelet under ...'),
1604 defaultValue = epi_name,
1605 style = wx.OK | wx.CANCEL | wx.CENTRE
1606 )
1607 decision = dlg.ShowModal()
1608 if decision != wx.ID_OK:
1609 return False
1610
1611 epi_name = dlg.GetValue().strip()
1612 if epi_name == u'':
1613 gmGuiHelpers.gm_show_error(_('Cannot save a new problem without a name.'), _('saving progress note'))
1614 return False
1615
1616
1617 new_episode = emr.add_episode(episode_name = epi_name[:45], pk_health_issue = None, is_open = True)
1618 new_episode['summary'] = self._TCTRL_summary.GetValue().strip()
1619 new_episode.save()
1620
1621 if self.problem is not None:
1622 issue = emr.problem2issue(self.problem)
1623 if not gmEMRStructWidgets.move_episode_to_issue(episode = new_episode, target_issue = issue, save_to_backend = True):
1624 gmGuiHelpers.gm_show_warning (
1625 _(
1626 'The new episode:\n'
1627 '\n'
1628 ' "%s"\n'
1629 '\n'
1630 'will remain unassociated despite the editor\n'
1631 'having been invoked from the health issue:\n'
1632 '\n'
1633 ' "%s"'
1634 ) % (
1635 new_episode['description'],
1636 issue['description']
1637 ),
1638 _('saving progress note')
1639 )
1640
1641 epi_id = new_episode['pk_episode']
1642
1643
1644 else:
1645 epi_id = self.problem['pk_episode']
1646
1647 emr.add_notes(notes = self.soap, episode = epi_id, encounter = encounter)
1648
1649
1650
1651 if self.problem is not None:
1652 if self.problem['type'] == 'episode':
1653 epi = emr.problem2episode(self.problem)
1654 epi['summary'] = self._TCTRL_summary.GetValue().strip()
1655 epi.save()
1656
1657 return True
1658
1659
1660
1662 for field in self.fields:
1663 wx_expando.EVT_ETC_LAYOUT_NEEDED(field, field.GetId(), self._on_expando_needs_layout)
1664 wx_expando.EVT_ETC_LAYOUT_NEEDED(self._TCTRL_summary, self._TCTRL_summary.GetId(), self._on_expando_needs_layout)
1665
1667
1668
1669
1670 self.Fit()
1671
1672 if self.HasScrollbar(wx.VERTICAL):
1673
1674 expando = self.FindWindowById(evt.GetId())
1675 y_expando = expando.GetPositionTuple()[1]
1676 h_expando = expando.GetSizeTuple()[1]
1677 line_cursor = expando.PositionToXY(expando.GetInsertionPoint())[1] + 1
1678 y_cursor = int(round((float(line_cursor) / expando.NumberOfLines) * h_expando))
1679 y_desired_visible = y_expando + y_cursor
1680
1681 y_view = self.ViewStart[1]
1682 h_view = self.GetClientSizeTuple()[1]
1683
1684
1685
1686
1687
1688
1689
1690
1691 if y_desired_visible < y_view:
1692
1693 self.Scroll(0, y_desired_visible)
1694
1695 if y_desired_visible > h_view:
1696
1697 self.Scroll(0, y_desired_visible)
1698
1699
1700
1702 note = []
1703
1704 tmp = self._TCTRL_Soap.GetValue().strip()
1705 if tmp != u'':
1706 note.append(['s', tmp])
1707
1708 tmp = self._TCTRL_sOap.GetValue().strip()
1709 if tmp != u'':
1710 note.append(['o', tmp])
1711
1712 tmp = self._TCTRL_soAp.GetValue().strip()
1713 if tmp != u'':
1714 note.append(['a', tmp])
1715
1716 tmp = self._TCTRL_soaP.GetValue().strip()
1717 if tmp != u'':
1718 note.append(['p', tmp])
1719
1720 return note
1721
1722 soap = property(_get_soap, lambda x:x)
1723
1725 for field in self.fields:
1726 if field.GetValue().strip() != u'':
1727 return False
1728
1729 summary = self._TCTRL_summary.GetValue().strip()
1730 if self.problem is None:
1731 if summary != u'':
1732 return False
1733 else:
1734 if self.problem['summary'] is None:
1735 if summary != u'':
1736 return False
1737 else:
1738 if summary != self.problem['summary'].strip():
1739 return False
1740
1741 return True
1742
1743 empty = property(_get_empty, lambda x:x)
1744
1745 -class cSoapLineTextCtrl(wx_expando.ExpandoTextCtrl):
1746
1747 - def __init__(self, *args, **kwargs):
1748
1749 wx_expando.ExpandoTextCtrl.__init__(self, *args, **kwargs)
1750
1751 self.__keyword_separators = regex.compile("[!?'\".,:;)}\]\r\n\s\t]+")
1752
1753 self.__register_interests()
1754
1755
1756
1757 - def _wrapLine(self, line, dc, width):
1758
1759 if (wx.MAJOR_VERSION > 1) and (wx.MINOR_VERSION > 8):
1760 return super(cSoapLineTextCtrl, self)._wrapLine(line, dc, width)
1761
1762
1763
1764
1765 pte = dc.GetPartialTextExtents(line)
1766 width -= wx.SystemSettings.GetMetric(wx.SYS_VSCROLL_X)
1767 idx = 0
1768 start = 0
1769 count = 0
1770 spc = -1
1771 while idx < len(pte):
1772 if line[idx] == ' ':
1773 spc = idx
1774 if pte[idx] - start > width:
1775
1776 count += 1
1777
1778 if spc != -1:
1779 idx = spc + 1
1780 spc = -1
1781 if idx < len(pte):
1782 start = pte[idx]
1783 else:
1784 idx += 1
1785 return count
1786
1787
1788
1790
1791
1792 wx.EVT_CHAR(self, self.__on_char)
1793 wx.EVT_SET_FOCUS(self, self.__on_focus)
1794
1795 - def __on_focus(self, evt):
1796 evt.Skip()
1797 wx.CallAfter(self._after_on_focus)
1798
1799 - def _after_on_focus(self):
1800 evt = wx.PyCommandEvent(wx_expando.wxEVT_ETC_LAYOUT_NEEDED, self.GetId())
1801 evt.SetEventObject(self)
1802 evt.height = None
1803 evt.numLines = None
1804 self.GetEventHandler().ProcessEvent(evt)
1805
1806 - def __on_char(self, evt):
1807 char = unichr(evt.GetUnicodeKey())
1808
1809 if self.LastPosition == 1:
1810 evt.Skip()
1811 return
1812
1813 explicit_expansion = False
1814 if evt.GetModifiers() == (wx.MOD_CMD | wx.MOD_ALT):
1815 if evt.GetKeyCode() != 13:
1816 evt.Skip()
1817 return
1818 explicit_expansion = True
1819
1820 if not explicit_expansion:
1821 if self.__keyword_separators.match(char) is None:
1822 evt.Skip()
1823 return
1824
1825 caret_pos, line_no = self.PositionToXY(self.InsertionPoint)
1826 line = self.GetLineText(line_no)
1827 word = self.__keyword_separators.split(line[:caret_pos])[-1]
1828
1829 if (
1830 (not explicit_expansion)
1831 and
1832 (word != u'$$steffi')
1833 and
1834 (word not in [ r[0] for r in gmPG2.get_text_expansion_keywords() ])
1835 ):
1836 evt.Skip()
1837 return
1838
1839 start = self.InsertionPoint - len(word)
1840 wx.CallAfter(self.replace_keyword_with_expansion, word, start, explicit_expansion)
1841
1842 evt.Skip()
1843 return
1844
1845 - def replace_keyword_with_expansion(self, keyword=None, position=None, show_list=False):
1846
1847 if show_list:
1848 candidates = gmPG2.get_keyword_expansion_candidates(keyword = keyword)
1849 if len(candidates) == 0:
1850 return
1851 if len(candidates) == 1:
1852 keyword = candidates[0]
1853 else:
1854 keyword = gmListWidgets.get_choices_from_list (
1855 parent = self,
1856 msg = _(
1857 'Several macros match the keyword [%s].\n'
1858 '\n'
1859 'Please select the expansion you want to happen.'
1860 ) % keyword,
1861 caption = _('Selecting text macro'),
1862 choices = candidates,
1863 columns = [_('Keyword')],
1864 single_selection = True,
1865 can_return_empty = False
1866 )
1867 if keyword is None:
1868 return
1869
1870 expansion = gmPG2.expand_keyword(keyword = keyword)
1871
1872 if expansion is None:
1873 return
1874
1875 if expansion == u'':
1876 return
1877
1878 self.Replace (
1879 position,
1880 position + len(keyword),
1881 expansion
1882 )
1883
1884 self.SetInsertionPoint(position + len(expansion) + 1)
1885 self.ShowPosition(position + len(expansion) + 1)
1886
1887 return
1888
1889
1890
1921
1922 gmCfgWidgets.configure_string_option (
1923 message = _(
1924 'Enter the shell command with which to start\n'
1925 'the image editor for visual progress notes.\n'
1926 '\n'
1927 'Any "%(img)s" included with the arguments\n'
1928 'will be replaced by the file name of the\n'
1929 'note template.'
1930 ),
1931 option = u'external.tools.visual_soap_editor_cmd',
1932 bias = 'user',
1933 default_value = None,
1934 validator = is_valid
1935 )
1936
1938 if parent is None:
1939 parent = wx.GetApp().GetTopWindow()
1940
1941 dlg = wx.FileDialog (
1942 parent = parent,
1943 message = _('Choose file to use as template for new visual progress note'),
1944 defaultDir = os.path.expanduser('~'),
1945 defaultFile = '',
1946
1947 style = wx.OPEN | wx.HIDE_READONLY | wx.FILE_MUST_EXIST
1948 )
1949 result = dlg.ShowModal()
1950
1951 if result == wx.ID_CANCEL:
1952 dlg.Destroy()
1953 return None
1954
1955 full_filename = dlg.GetPath()
1956 dlg.Hide()
1957 dlg.Destroy()
1958 return full_filename
1959
1961
1962 if parent is None:
1963 parent = wx.GetApp().GetTopWindow()
1964
1965
1966 from Gnumed.wxpython import gmFormWidgets
1967 template = gmFormWidgets.manage_form_templates (
1968 parent = parent,
1969 template_types = [gmDocuments.DOCUMENT_TYPE_VISUAL_PROGRESS_NOTE],
1970 active_only = True
1971 )
1972
1973
1974 if template is None:
1975 fname = select_file_as_visual_progress_note_template(parent = parent)
1976 if fname is None:
1977 return (None, None)
1978
1979 ext = os.path.splitext(fname)[1]
1980 tmp_name = gmTools.get_unique_filename(suffix = ext)
1981 _log.debug('visual progress note from file: [%s] -> [%s]', fname, tmp_name)
1982 shutil.copy2(fname, tmp_name)
1983 return (tmp_name, False)
1984
1985 filename = template.export_to_file()
1986 if filename is None:
1987 gmDispatcher.send(signal = u'statustext', msg = _('Cannot export visual progress note template for [%s].') % template['name_long'])
1988 return (None, None)
1989 return (filename, True)
1990
1991
1993 """This assumes <filename> contains an image which can be handled by the configured image editor."""
1994
1995 if doc_part is not None:
1996 filename = doc_part.export_to_file()
1997 if filename is None:
1998 gmDispatcher.send(signal = u'statustext', msg = _('Cannot export visual progress note to file.'))
1999 return None
2000
2001 dbcfg = gmCfg.cCfgSQL()
2002 cmd = dbcfg.get2 (
2003 option = u'external.tools.visual_soap_editor_cmd',
2004 workplace = gmSurgery.gmCurrentPractice().active_workplace,
2005 bias = 'user'
2006 )
2007
2008 if cmd is None:
2009 gmDispatcher.send(signal = u'statustext', msg = _('Editor for visual progress note not configured.'), beep = False)
2010 cmd = configure_visual_progress_note_editor()
2011 if cmd is None:
2012 gmDispatcher.send(signal = u'statustext', msg = _('Editor for visual progress note not configured.'), beep = True)
2013 return None
2014
2015 if u'%(img)s' in cmd:
2016 cmd % {u'img': filename}
2017 else:
2018 cmd = u'%s %s' % (cmd, filename)
2019
2020 if discard_unmodified:
2021 original_stat = os.stat(filename)
2022 original_md5 = gmTools.file2md5(filename)
2023
2024 success = gmShellAPI.run_command_in_shell(cmd, blocking = True)
2025 if not success:
2026 gmGuiHelpers.gm_show_error (
2027 _(
2028 'There was a problem with running the editor\n'
2029 'for visual progress notes.\n'
2030 '\n'
2031 ' [%s]\n'
2032 '\n'
2033 ) % cmd,
2034 _('Editing visual progress note')
2035 )
2036 return None
2037
2038 try:
2039 open(filename, 'r').close()
2040 except StandardError:
2041 _log.exception('problem accessing visual progress note file [%s]', filename)
2042 gmGuiHelpers.gm_show_error (
2043 _(
2044 'There was a problem reading the visual\n'
2045 'progress note from the file:\n'
2046 '\n'
2047 ' [%s]\n'
2048 '\n'
2049 ) % filename,
2050 _('Saving visual progress note')
2051 )
2052 return None
2053
2054 if discard_unmodified:
2055 modified_stat = os.stat(filename)
2056
2057 if original_stat.st_size == modified_stat.st_size:
2058 modified_md5 = gmTools.file2md5(filename)
2059
2060 if original_md5 == modified_md5:
2061 _log.debug('visual progress note (template) not modified')
2062
2063 msg = _(
2064 u'You either created a visual progress note from a template\n'
2065 u'in the database (rather than from a file on disk) or you\n'
2066 u'edited an existing visual progress note.\n'
2067 u'\n'
2068 u'The template/original was not modified at all, however.\n'
2069 u'\n'
2070 u'Do you still want to save the unmodified image as a\n'
2071 u'visual progress note into the EMR of the patient ?\n'
2072 )
2073 save_unmodified = gmGuiHelpers.gm_show_question (
2074 msg,
2075 _('Saving visual progress note')
2076 )
2077 if not save_unmodified:
2078 _log.debug('user discarded unmodified note')
2079 return
2080
2081 if doc_part is not None:
2082 doc_part.update_data_from_file(fname = filename)
2083 doc_part.set_reviewed(technically_abnormal = False, clinically_relevant = True)
2084 return None
2085
2086 if not isinstance(episode, gmEMRStructItems.cEpisode):
2087 if episode is None:
2088 episode = _('visual progress notes')
2089 pat = gmPerson.gmCurrentPatient()
2090 emr = pat.get_emr()
2091 episode = emr.add_episode(episode_name = episode.strip(), pk_health_issue = health_issue, is_open = False)
2092
2093 doc = gmDocumentWidgets.save_file_as_new_document (
2094 filename = filename,
2095 document_type = gmDocuments.DOCUMENT_TYPE_VISUAL_PROGRESS_NOTE,
2096 episode = episode,
2097 unlock_patient = True
2098 )
2099 doc.set_reviewed(technically_abnormal = False, clinically_relevant = True)
2100
2101 return doc
2102
2104 """Phrasewheel to allow selection of visual SOAP template."""
2105
2131
2137
2138 from Gnumed.wxGladeWidgets import wxgVisualSoapPresenterPnl
2139
2141
2146
2147
2148
2149 - def refresh(self, document_folder=None, episodes=None, encounter=None):
2150
2151 self.clear()
2152 if document_folder is not None:
2153 soap_docs = document_folder.get_visual_progress_notes(episodes = episodes, encounter = encounter)
2154 if len(soap_docs) > 0:
2155 for soap_doc in soap_docs:
2156 parts = soap_doc.parts
2157 if len(parts) == 0:
2158 continue
2159 part = parts[0]
2160 fname = part.export_to_file()
2161 if fname is None:
2162 continue
2163
2164
2165 img = gmGuiHelpers.file2scaled_image (
2166 filename = fname,
2167 height = 30
2168 )
2169
2170 bmp = wx_genstatbmp.GenStaticBitmap(self, -1, img, style = wx.NO_BORDER)
2171
2172
2173 img = gmGuiHelpers.file2scaled_image (
2174 filename = fname,
2175 height = 150
2176 )
2177 tip = agw_stt.SuperToolTip (
2178 u'',
2179 bodyImage = img,
2180 header = _('Created: %s') % part['date_generated'].strftime('%Y %B %d').decode(gmI18N.get_encoding()),
2181 footer = gmTools.coalesce(part['doc_comment'], u'').strip()
2182 )
2183 tip.SetTopGradientColor('white')
2184 tip.SetMiddleGradientColor('white')
2185 tip.SetBottomGradientColor('white')
2186 tip.SetTarget(bmp)
2187
2188 bmp.doc_part = part
2189 bmp.Bind(wx.EVT_LEFT_UP, self._on_bitmap_leftclicked)
2190
2191 self._SZR_soap.Add(bmp, 0, wx.LEFT | wx.RIGHT | wx.TOP | wx.BOTTOM | wx.EXPAND, 3)
2192 self.__bitmaps.append(bmp)
2193
2194 self.GetParent().Layout()
2195
2197 while self._SZR_soap.Detach(0):
2198 pass
2199 for bmp in self.__bitmaps:
2200 bmp.Destroy()
2201 self.__bitmaps = []
2202
2204 wx.CallAfter (
2205 edit_visual_progress_note,
2206 doc_part = evt.GetEventObject().doc_part,
2207 discard_unmodified = True
2208 )
2209
2210 from Gnumed.wxGladeWidgets import wxgVisualSoapPnl
2211
2213
2220
2221
2222
2229
2230 - def refresh(self, patient=None, encounter=None):
2264
2305
2306
2307
2316
2318 self._BTN_delete.Enable(False)
2319
2336
2366
2429
2447
2448
2449
2450 if __name__ == '__main__':
2451
2452 if len(sys.argv) < 2:
2453 sys.exit()
2454
2455 if sys.argv[1] != 'test':
2456 sys.exit()
2457
2458 gmI18N.activate_locale()
2459 gmI18N.install_domain(domain = 'gnumed')
2460
2461
2470
2477
2490
2491
2492 test_cSoapNoteExpandoEditAreaPnl()
2493
2494
2495
2496