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, gmDispatcher, gmTools, gmDateTime
18 from Gnumed.pycommon import gmShellAPI, gmPG2, gmCfg, gmMatchProvider
19
20 from Gnumed.business import gmPerson, gmEMRStructItems, gmClinNarrative, gmSurgery
21 from Gnumed.business import gmForms, gmDocuments, gmPersonSearch
22
23 from Gnumed.wxpython import gmListWidgets
24 from Gnumed.wxpython import gmEMRStructWidgets
25 from Gnumed.wxpython import gmRegetMixin
26 from Gnumed.wxpython import gmPhraseWheel
27 from Gnumed.wxpython import gmGuiHelpers
28 from Gnumed.wxpython import gmPatSearchWidgets
29 from Gnumed.wxpython import gmCfgWidgets
30 from Gnumed.wxpython import gmDocumentWidgets
31
32 from Gnumed.exporters import gmPatientExporter
33
34
35 _log = logging.getLogger('gm.ui')
36 _log.info(__version__)
37
38
39
41
42
43 if patient is None:
44 patient = gmPerson.gmCurrentPatient()
45
46 if not patient.connected:
47 gmDispatcher.send(signal = 'statustext', msg = _('Cannot move progress notes. No active patient.'))
48 return False
49
50 if parent is None:
51 parent = wx.GetApp().GetTopWindow()
52
53 emr = patient.get_emr()
54
55 if encounters is None:
56 encs = emr.get_encounters(episodes = episodes)
57 encounters = gmEMRStructWidgets.select_encounters (
58 parent = parent,
59 patient = patient,
60 single_selection = False,
61 encounters = encs
62 )
63
64 notes = emr.get_clin_narrative (
65 encounters = encounters,
66 episodes = episodes
67 )
68
69
70 if move_all:
71 selected_narr = notes
72 else:
73 selected_narr = gmListWidgets.get_choices_from_list (
74 parent = parent,
75 caption = _('Moving progress notes between encounters ...'),
76 single_selection = False,
77 can_return_empty = True,
78 data = notes,
79 msg = _('\n Select the progress notes to move from the list !\n\n'),
80 columns = [_('when'), _('who'), _('type'), _('entry')],
81 choices = [
82 [ narr['date'].strftime('%x %H:%M'),
83 narr['provider'],
84 gmClinNarrative.soap_cat2l10n[narr['soap_cat']],
85 narr['narrative'].replace('\n', '/').replace('\r', '/')
86 ] for narr in notes
87 ]
88 )
89
90 if not selected_narr:
91 return True
92
93
94 enc2move2 = gmEMRStructWidgets.select_encounters (
95 parent = parent,
96 patient = patient,
97 single_selection = True
98 )
99
100 if not enc2move2:
101 return True
102
103 for narr in selected_narr:
104 narr['pk_encounter'] = enc2move2['pk_encounter']
105 narr.save()
106
107 return True
108
110
111
112 if patient is None:
113 patient = gmPerson.gmCurrentPatient()
114
115 if not patient.connected:
116 gmDispatcher.send(signal = 'statustext', msg = _('Cannot edit progress notes. No active patient.'))
117 return False
118
119 if parent is None:
120 parent = wx.GetApp().GetTopWindow()
121
122 emr = patient.get_emr()
123
124 def delete(item):
125 if item is None:
126 return False
127 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
128 parent,
129 -1,
130 caption = _('Deleting progress note'),
131 question = _(
132 'Are you positively sure you want to delete this\n'
133 'progress note from the medical record ?\n'
134 '\n'
135 'Note that even if you chose to delete the entry it will\n'
136 'still be (invisibly) kept in the audit trail to protect\n'
137 'you from litigation because physical deletion is known\n'
138 'to be unlawful in some jurisdictions.\n'
139 ),
140 button_defs = (
141 {'label': _('Delete'), 'tooltip': _('Yes, delete the progress note.'), 'default': False},
142 {'label': _('Cancel'), 'tooltip': _('No, do NOT delete the progress note.'), 'default': True}
143 )
144 )
145 decision = dlg.ShowModal()
146
147 if decision != wx.ID_YES:
148 return False
149
150 gmClinNarrative.delete_clin_narrative(narrative = item['pk_narrative'])
151 return True
152
153 def edit(item):
154 if item is None:
155 return False
156
157 dlg = gmGuiHelpers.cMultilineTextEntryDlg (
158 parent,
159 -1,
160 title = _('Editing progress note'),
161 msg = _('This is the original progress note:'),
162 data = item.format(left_margin = u' ', fancy = True),
163 text = item['narrative']
164 )
165 decision = dlg.ShowModal()
166
167 if decision != wx.ID_SAVE:
168 return False
169
170 val = dlg.value
171 dlg.Destroy()
172 if val.strip() == u'':
173 return False
174
175 item['narrative'] = val
176 item.save_payload()
177
178 return True
179
180 def refresh(lctrl):
181 notes = emr.get_clin_narrative (
182 encounters = encounters,
183 episodes = episodes,
184 providers = [ gmPerson.gmCurrentProvider()['short_alias'] ]
185 )
186 lctrl.set_string_items(items = [
187 [ narr['date'].strftime('%x %H:%M'),
188 gmClinNarrative.soap_cat2l10n[narr['soap_cat']],
189 narr['narrative'].replace('\n', '/').replace('\r', '/')
190 ] for narr in notes
191 ])
192 lctrl.set_data(data = notes)
193
194
195 gmListWidgets.get_choices_from_list (
196 parent = parent,
197 caption = _('Managing progress notes'),
198 msg = _(
199 '\n'
200 ' This list shows the progress notes by %s.\n'
201 '\n'
202 ) % gmPerson.gmCurrentProvider()['short_alias'],
203 columns = [_('when'), _('type'), _('entry')],
204 single_selection = True,
205 can_return_empty = False,
206 edit_callback = edit,
207 delete_callback = delete,
208 refresh_callback = refresh,
209 ignore_OK_button = True
210 )
211
213
214 if parent is None:
215 parent = wx.GetApp().GetTopWindow()
216
217 searcher = wx.TextEntryDialog (
218 parent = parent,
219 message = _('Enter (regex) term to search for across all EMRs:'),
220 caption = _('Text search across all EMRs'),
221 style = wx.OK | wx.CANCEL | wx.CENTRE
222 )
223 result = searcher.ShowModal()
224
225 if result != wx.ID_OK:
226 return
227
228 wx.BeginBusyCursor()
229 term = searcher.GetValue()
230 searcher.Destroy()
231 results = gmClinNarrative.search_text_across_emrs(search_term = term)
232 wx.EndBusyCursor()
233
234 if len(results) == 0:
235 gmGuiHelpers.gm_show_info (
236 _(
237 'Nothing found for search term:\n'
238 ' "%s"'
239 ) % term,
240 _('Search results')
241 )
242 return
243
244 items = [ [gmPerson.cIdentity(aPK_obj = r['pk_patient'])['description_gender'], r['narrative'], r['src_table']] for r in results ]
245
246 selected_patient = gmListWidgets.get_choices_from_list (
247 parent = parent,
248 caption = _('Search results for %s') % term,
249 choices = items,
250 columns = [_('Patient'), _('Match'), _('Match location')],
251 data = [ r['pk_patient'] for r in results ],
252 single_selection = True,
253 can_return_empty = False
254 )
255
256 if selected_patient is None:
257 return
258
259 wx.CallAfter(gmPatSearchWidgets.set_active_patient, patient = gmPerson.cIdentity(aPK_obj = selected_patient))
260
262
263
264 if patient is None:
265 patient = gmPerson.gmCurrentPatient()
266
267 if not patient.connected:
268 gmDispatcher.send(signal = 'statustext', msg = _('Cannot search EMR. No active patient.'))
269 return False
270
271 if parent is None:
272 parent = wx.GetApp().GetTopWindow()
273
274 searcher = wx.TextEntryDialog (
275 parent = parent,
276 message = _('Enter search term:'),
277 caption = _('Text search of entire EMR of active patient'),
278 style = wx.OK | wx.CANCEL | wx.CENTRE
279 )
280 result = searcher.ShowModal()
281
282 if result != wx.ID_OK:
283 searcher.Destroy()
284 return False
285
286 wx.BeginBusyCursor()
287 val = searcher.GetValue()
288 searcher.Destroy()
289 emr = patient.get_emr()
290 rows = emr.search_narrative_simple(val)
291 wx.EndBusyCursor()
292
293 if len(rows) == 0:
294 gmGuiHelpers.gm_show_info (
295 _(
296 'Nothing found for search term:\n'
297 ' "%s"'
298 ) % val,
299 _('Search results')
300 )
301 return True
302
303 txt = u''
304 for row in rows:
305 txt += u'%s: %s\n' % (
306 row['soap_cat'],
307 row['narrative']
308 )
309
310 txt += u' %s: %s - %s %s\n' % (
311 _('Encounter'),
312 row['encounter_started'].strftime('%x %H:%M'),
313 row['encounter_ended'].strftime('%H:%M'),
314 row['encounter_type']
315 )
316 txt += u' %s: %s\n' % (
317 _('Episode'),
318 row['episode']
319 )
320 txt += u' %s: %s\n\n' % (
321 _('Health issue'),
322 row['health_issue']
323 )
324
325 msg = _(
326 'Search term was: "%s"\n'
327 '\n'
328 'Search results:\n\n'
329 '%s\n'
330 ) % (val, txt)
331
332 dlg = wx.MessageDialog (
333 parent = parent,
334 message = msg,
335 caption = _('Search results for %s') % val,
336 style = wx.OK | wx.STAY_ON_TOP
337 )
338 dlg.ShowModal()
339 dlg.Destroy()
340
341 return True
342
344
345
346 pat = gmPerson.gmCurrentPatient()
347 if not pat.connected:
348 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export EMR for Medistar. No active patient.'))
349 return False
350
351 if encounter is None:
352 encounter = pat.get_emr().active_encounter
353
354 if parent is None:
355 parent = wx.GetApp().GetTopWindow()
356
357
358 aWildcard = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files"))
359
360 aDefDir = os.path.abspath(os.path.expanduser(os.path.join('~', 'gnumed','export')))
361
362 fname = '%s-%s-%s-%s-%s.txt' % (
363 'Medistar-MD',
364 time.strftime('%Y-%m-%d',time.localtime()),
365 pat['lastnames'].replace(' ', '-'),
366 pat['firstnames'].replace(' ', '_'),
367 pat.get_formatted_dob(format = '%Y-%m-%d')
368 )
369 dlg = wx.FileDialog (
370 parent = parent,
371 message = _("Save EMR extract for MEDISTAR import as..."),
372 defaultDir = aDefDir,
373 defaultFile = fname,
374 wildcard = aWildcard,
375 style = wx.SAVE
376 )
377 choice = dlg.ShowModal()
378 fname = dlg.GetPath()
379 dlg.Destroy()
380 if choice != wx.ID_OK:
381 return False
382
383 wx.BeginBusyCursor()
384 _log.debug('exporting encounter for medistar import to [%s]', fname)
385 exporter = gmPatientExporter.cMedistarSOAPExporter()
386 successful, fname = exporter.export_to_file (
387 filename = fname,
388 encounter = encounter,
389 soap_cats = u'soap',
390 export_to_import_file = True
391 )
392 if not successful:
393 gmGuiHelpers.gm_show_error (
394 _('Error exporting progress notes for MEDISTAR import.'),
395 _('MEDISTAR progress notes export')
396 )
397 wx.EndBusyCursor()
398 return False
399
400 gmDispatcher.send(signal = 'statustext', msg = _('Successfully exported progress notes into file [%s] for Medistar import.') % fname, beep=False)
401
402 wx.EndBusyCursor()
403 return True
404
406 """soap_cats needs to be a list"""
407
408 pat = gmPerson.gmCurrentPatient()
409 emr = pat.get_emr()
410
411 if parent is None:
412 parent = wx.GetApp().GetTopWindow()
413
414 selected_soap = {}
415 selected_issue_pks = []
416 selected_episode_pks = []
417 selected_narrative_pks = []
418
419 while 1:
420
421 all_issues = emr.get_health_issues()
422 all_issues.insert(0, gmEMRStructItems.get_dummy_health_issue())
423 dlg = gmEMRStructWidgets.cIssueListSelectorDlg (
424 parent = parent,
425 id = -1,
426 issues = all_issues,
427 msg = _('\n In the list below mark the health issues you want to report on.\n')
428 )
429 selection_idxs = []
430 for idx in range(len(all_issues)):
431 if all_issues[idx]['pk_health_issue'] in selected_issue_pks:
432 selection_idxs.append(idx)
433 if len(selection_idxs) != 0:
434 dlg.set_selections(selections = selection_idxs)
435 btn_pressed = dlg.ShowModal()
436 selected_issues = dlg.get_selected_item_data()
437 dlg.Destroy()
438
439 if btn_pressed == wx.ID_CANCEL:
440 return selected_soap.values()
441
442 selected_issue_pks = [ i['pk_health_issue'] for i in selected_issues ]
443
444 while 1:
445
446 all_epis = emr.get_episodes(issues = selected_issue_pks)
447
448 if len(all_epis) == 0:
449 gmDispatcher.send(signal = 'statustext', msg = _('No episodes recorded for the health issues selected.'))
450 break
451
452 dlg = gmEMRStructWidgets.cEpisodeListSelectorDlg (
453 parent = parent,
454 id = -1,
455 episodes = all_epis,
456 msg = _(
457 '\n These are the episodes known for the health issues just selected.\n\n'
458 ' Now, mark the the episodes you want to report on.\n'
459 )
460 )
461 selection_idxs = []
462 for idx in range(len(all_epis)):
463 if all_epis[idx]['pk_episode'] in selected_episode_pks:
464 selection_idxs.append(idx)
465 if len(selection_idxs) != 0:
466 dlg.set_selections(selections = selection_idxs)
467 btn_pressed = dlg.ShowModal()
468 selected_epis = dlg.get_selected_item_data()
469 dlg.Destroy()
470
471 if btn_pressed == wx.ID_CANCEL:
472 break
473
474 selected_episode_pks = [ i['pk_episode'] for i in selected_epis ]
475
476
477 all_narr = emr.get_clin_narrative(episodes = selected_episode_pks, soap_cats = soap_cats)
478
479 if len(all_narr) == 0:
480 gmDispatcher.send(signal = 'statustext', msg = _('No narrative available for selected episodes.'))
481 continue
482
483 dlg = cNarrativeListSelectorDlg (
484 parent = parent,
485 id = -1,
486 narrative = all_narr,
487 msg = _(
488 '\n This is the narrative (type %s) for the chosen episodes.\n\n'
489 ' Now, mark the entries you want to include in your report.\n'
490 ) % u'/'.join([ gmClinNarrative.soap_cat2l10n[cat] for cat in gmTools.coalesce(soap_cats, list(u'soap')) ])
491 )
492 selection_idxs = []
493 for idx in range(len(all_narr)):
494 if all_narr[idx]['pk_narrative'] in selected_narrative_pks:
495 selection_idxs.append(idx)
496 if len(selection_idxs) != 0:
497 dlg.set_selections(selections = selection_idxs)
498 btn_pressed = dlg.ShowModal()
499 selected_narr = dlg.get_selected_item_data()
500 dlg.Destroy()
501
502 if btn_pressed == wx.ID_CANCEL:
503 continue
504
505 selected_narrative_pks = [ i['pk_narrative'] for i in selected_narr ]
506 for narr in selected_narr:
507 selected_soap[narr['pk_narrative']] = narr
508
510
512
513 narrative = kwargs['narrative']
514 del kwargs['narrative']
515
516 gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs)
517
518 self.SetTitle(_('Select the narrative you are interested in ...'))
519
520 self._LCTRL_items.set_columns([_('when'), _('who'), _('type'), _('entry')])
521
522 self._LCTRL_items.set_string_items (
523 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 ]
524 )
525 self._LCTRL_items.set_column_widths()
526 self._LCTRL_items.set_data(data = narrative)
527
528 from Gnumed.wxGladeWidgets import wxgMoveNarrativeDlg
529
531
533
534 self.encounter = kwargs['encounter']
535 self.source_episode = kwargs['episode']
536 del kwargs['encounter']
537 del kwargs['episode']
538
539 wxgMoveNarrativeDlg.wxgMoveNarrativeDlg.__init__(self, *args, **kwargs)
540
541 self.LBL_source_episode.SetLabel(u'%s%s' % (self.source_episode['description'], gmTools.coalesce(self.source_episode['health_issue'], u'', u' (%s)')))
542 self.LBL_encounter.SetLabel('%s: %s %s - %s' % (
543 self.encounter['started'].strftime('%x').decode(gmI18N.get_encoding()),
544 self.encounter['l10n_type'],
545 self.encounter['started'].strftime('%H:%M'),
546 self.encounter['last_affirmed'].strftime('%H:%M')
547 ))
548 pat = gmPerson.gmCurrentPatient()
549 emr = pat.get_emr()
550 narr = emr.get_clin_narrative(episodes=[self.source_episode['pk_episode']], encounters=[self.encounter['pk_encounter']])
551 if len(narr) == 0:
552 narr = [{'narrative': _('There is no narrative for this episode in this encounter.')}]
553 self.LBL_narrative.SetLabel(u'\n'.join([n['narrative'] for n in narr]))
554
555
577
578 from Gnumed.wxGladeWidgets import wxgSoapPluginPnl
579
580 -class cSoapPluginPnl(wxgSoapPluginPnl.wxgSoapPluginPnl, gmRegetMixin.cRegetOnPaintMixin):
581 """A panel for in-context editing of progress notes.
582
583 Expects to be used as a notebook page.
584
585 Left hand side:
586 - problem list (health issues and active episodes)
587 - hints area
588
589 Right hand side:
590 - previous notes
591 - notebook with progress note editors
592 - encounter details fields
593
594 Listens to patient change signals, thus acts on the current patient.
595 """
606
607
608
610
611 if not self.__encounter_valid_for_save():
612 return False
613
614 emr = self.__pat.get_emr()
615 enc = emr.active_encounter
616
617 enc['pk_type'] = self._PRW_encounter_type.GetData()
618 enc['started'] = self._PRW_encounter_start.GetData().get_pydt()
619 enc['last_affirmed'] = self._PRW_encounter_end.GetData().get_pydt()
620 rfe = self._TCTRL_rfe.GetValue().strip()
621 if len(rfe) == 0:
622 enc['reason_for_encounter'] = None
623 else:
624 enc['reason_for_encounter'] = rfe
625 aoe = self._TCTRL_aoe.GetValue().strip()
626 if len(aoe) == 0:
627 enc['assessment_of_encounter'] = None
628 else:
629 enc['assessment_of_encounter'] = aoe
630
631 enc.save_payload()
632
633 return True
634
635
636
638 self._LCTRL_active_problems.set_columns([_('Last'), _('Problem'), _('Health issue')])
639 self._LCTRL_active_problems.set_string_items()
640
641 self._splitter_main.SetSashGravity(0.5)
642 self._splitter_left.SetSashGravity(0.5)
643 self._splitter_right.SetSashGravity(1.0)
644
645
646 splitter_size = self._splitter_main.GetSizeTuple()[0]
647 self._splitter_main.SetSashPosition(splitter_size * 3 / 10, True)
648
649 splitter_size = self._splitter_left.GetSizeTuple()[1]
650 self._splitter_left.SetSashPosition(splitter_size * 6 / 20, True)
651
652 splitter_size = self._splitter_right.GetSizeTuple()[1]
653 self._splitter_right.SetSashPosition(splitter_size * 15 / 20, True)
654
655
656
657
658 self._NB_soap_editors.DeleteAllPages()
659
661 """
662 Clear all information from input panel
663 """
664 self._LCTRL_active_problems.set_string_items()
665
666 self._TCTRL_recent_notes.SetValue(u'')
667
668 self._PRW_encounter_type.SetText(suppress_smarts = True)
669 self._PRW_encounter_start.SetText(suppress_smarts = True)
670 self._PRW_encounter_end.SetText(suppress_smarts = True)
671 self._TCTRL_rfe.SetValue(u'')
672 self._TCTRL_aoe.SetValue(u'')
673
674 self._NB_soap_editors.DeleteAllPages()
675 self._NB_soap_editors.add_editor()
676
677 self._lbl_hints.SetLabel(u'')
678
680 """Update health problems list.
681 """
682
683 self._LCTRL_active_problems.set_string_items()
684
685 emr = self.__pat.get_emr()
686 problems = emr.get_problems (
687 include_closed_episodes = self._CHBOX_show_closed_episodes.IsChecked(),
688 include_irrelevant_issues = self._CHBOX_irrelevant_issues.IsChecked()
689 )
690
691 list_items = []
692 active_problems = []
693 for problem in problems:
694 if not problem['problem_active']:
695 if not problem['is_potential_problem']:
696 continue
697
698 active_problems.append(problem)
699
700 if problem['type'] == 'issue':
701 issue = emr.problem2issue(problem)
702 last_encounter = emr.get_last_encounter(issue_id = issue['pk_health_issue'])
703 if last_encounter is None:
704 last = issue['modified_when'].strftime('%m/%Y')
705 else:
706 last = last_encounter['last_affirmed'].strftime('%m/%Y')
707
708 list_items.append([last, problem['problem'], gmTools.u_left_arrow])
709
710 elif problem['type'] == 'episode':
711 epi = emr.problem2episode(problem)
712 last_encounter = emr.get_last_encounter(episode_id = epi['pk_episode'])
713 if last_encounter is None:
714 last = epi['episode_modified_when'].strftime('%m/%Y')
715 else:
716 last = last_encounter['last_affirmed'].strftime('%m/%Y')
717
718 list_items.append ([
719 last,
720 problem['problem'],
721 gmTools.coalesce(initial = epi['health_issue'], instead = gmTools.u_diameter)
722 ])
723
724 self._LCTRL_active_problems.set_string_items(items = list_items)
725 self._LCTRL_active_problems.set_column_widths()
726 self._LCTRL_active_problems.set_data(data = active_problems)
727
728 showing_potential_problems = (
729 self._CHBOX_show_closed_episodes.IsChecked()
730 or
731 self._CHBOX_irrelevant_issues.IsChecked()
732 )
733 if showing_potential_problems:
734 self._SZR_problem_list_staticbox.SetLabel(_('%s (active+potential) problems') % len(list_items))
735 else:
736 self._SZR_problem_list_staticbox.SetLabel(_('%s active problems') % len(list_items))
737
738 return True
739
741 soap = u''
742 emr = self.__pat.get_emr()
743 prev_enc = emr.get_last_but_one_encounter(issue_id = problem['pk_health_issue'])
744 if prev_enc is not None:
745 soap += prev_enc.format (
746 issues = [ problem['pk_health_issue'] ],
747 with_soap = True,
748 with_docs = False,
749 with_tests = False,
750 patient = self.__pat,
751 fancy_header = False
752 )
753
754 tmp = emr.active_encounter.format_soap (
755 soap_cats = 'soap',
756 emr = emr,
757 issues = [ problem['pk_health_issue'] ],
758 )
759 if len(tmp) > 0:
760 soap += _('Current encounter:') + u'\n'
761 soap += u'\n'.join(tmp) + u'\n'
762
763 if problem['summary'] is not None:
764 soap += u'\n-- %s ----------\n%s' % (
765 _('Cumulative summary'),
766 gmTools.wrap (
767 text = problem['summary'],
768 width = 45,
769 initial_indent = u' ',
770 subsequent_indent = u' '
771 ).strip('\n')
772 )
773
774 return soap
775
777 soap = u''
778 emr = self.__pat.get_emr()
779 prev_enc = emr.get_last_but_one_encounter(episode_id = problem['pk_episode'])
780 if prev_enc is not None:
781 soap += prev_enc.format (
782 episodes = [ problem['pk_episode'] ],
783 with_soap = True,
784 with_docs = False,
785 with_tests = False,
786 patient = self.__pat,
787 fancy_header = False
788 )
789 else:
790 if problem['pk_health_issue'] is not None:
791 prev_enc = emr.get_last_but_one_encounter(episode_id = problem['pk_health_issue'])
792 if prev_enc is not None:
793 soap += prev_enc.format (
794 with_soap = True,
795 with_docs = False,
796 with_tests = False,
797 patient = self.__pat,
798 issues = [ problem['pk_health_issue'] ],
799 fancy_header = False
800 )
801
802 tmp = emr.active_encounter.format_soap (
803 soap_cats = 'soap',
804 emr = emr,
805 issues = [ problem['pk_health_issue'] ],
806 )
807 if len(tmp) > 0:
808 soap += _('Current encounter:') + u'\n'
809 soap += u'\n'.join(tmp) + u'\n'
810
811 if problem['summary'] is not None:
812 soap += u'\n-- %s ----------\n%s' % (
813 _('Cumulative summary'),
814 gmTools.wrap (
815 text = problem['summary'],
816 width = 45,
817 initial_indent = u' ',
818 subsequent_indent = u' '
819 ).strip('\n')
820 )
821
822 return soap
823
826
828 """This refreshes the recent-notes part."""
829
830 soap = u''
831 caption = u'<?>'
832
833 if problem['type'] == u'issue':
834 caption = problem['problem'][:35]
835 soap = self.__get_soap_for_issue_problem(problem = problem)
836
837 elif problem['type'] == u'episode':
838 caption = problem['problem'][:35]
839 soap = self.__get_soap_for_episode_problem(problem = problem)
840
841 self._TCTRL_recent_notes.SetValue(soap)
842 self._TCTRL_recent_notes.ShowPosition(self._TCTRL_recent_notes.GetLastPosition())
843 self._SZR_recent_notes_staticbox.SetLabel(_('Most recent notes on %s%s%s') % (
844 gmTools.u_left_double_angle_quote,
845 caption,
846 gmTools.u_right_double_angle_quote
847 ))
848
849 self._TCTRL_recent_notes.Refresh()
850
851 return True
852
880
882 """Assumes that the field data is valid."""
883
884 emr = self.__pat.get_emr()
885 enc = emr.active_encounter
886
887 data = {
888 'pk_type': self._PRW_encounter_type.GetData(),
889 'reason_for_encounter': gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u''),
890 'assessment_of_encounter': gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u''),
891 'pk_location': enc['pk_location'],
892 'pk_patient': enc['pk_patient']
893 }
894
895 if self._PRW_encounter_start.GetData() is None:
896 data['started'] = None
897 else:
898 data['started'] = self._PRW_encounter_start.GetData().get_pydt()
899
900 if self._PRW_encounter_end.GetData() is None:
901 data['last_affirmed'] = None
902 else:
903 data['last_affirmed'] = self._PRW_encounter_end.GetData().get_pydt()
904
905 return not enc.same_payload(another_object = data)
906
908
909 found_error = False
910
911 if self._PRW_encounter_type.GetData() is None:
912 found_error = True
913 msg = _('Cannot save encounter: missing type.')
914
915 if self._PRW_encounter_start.GetData() is None:
916 found_error = True
917 msg = _('Cannot save encounter: missing start time.')
918
919 if self._PRW_encounter_end.GetData() is None:
920 found_error = True
921 msg = _('Cannot save encounter: missing end time.')
922
923 if found_error:
924 gmDispatcher.send(signal = 'statustext', msg = msg, beep = True)
925 return False
926
927 return True
928
929
930
932 """Configure enabled event signals."""
933
934 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection)
935 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
936 gmDispatcher.connect(signal = u'episode_mod_db', receiver = self._on_episode_issue_mod_db)
937 gmDispatcher.connect(signal = u'health_issue_mod_db', receiver = self._on_episode_issue_mod_db)
938 gmDispatcher.connect(signal = u'doc_mod_db', receiver = self._on_doc_mod_db)
939 gmDispatcher.connect(signal = u'current_encounter_modified', receiver = self._on_current_encounter_modified)
940 gmDispatcher.connect(signal = u'current_encounter_switched', receiver = self._on_current_encounter_switched)
941
942
943 self.__pat.register_pre_selection_callback(callback = self._pre_selection_callback)
944 gmDispatcher.send(signal = u'register_pre_exit_callback', callback = self._pre_exit_callback)
945
947 """Another patient is about to be activated.
948
949 Patient change will not proceed before this returns True.
950 """
951
952
953 if not self.__pat.connected:
954 return True
955 return self._NB_soap_editors.warn_on_unsaved_soap()
956
958 """The client is about to be shut down.
959
960 Shutdown will not proceed before this returns.
961 """
962 if not self.__pat.connected:
963 return True
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979 emr = self.__pat.get_emr()
980 saved = self._NB_soap_editors.save_all_editors (
981 emr = emr,
982 episode_name_candidates = [
983 gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u''),
984 gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u'')
985 ]
986 )
987 if not saved:
988 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save all editors. Some were kept open.'), beep = True)
989 return True
990
992 wx.CallAfter(self.__on_pre_patient_selection)
993
995 self.__reset_ui_content()
996
998 wx.CallAfter(self._schedule_data_reget)
999
1001 wx.CallAfter(self.__refresh_current_editor)
1002
1004 wx.CallAfter(self._schedule_data_reget)
1005
1007 wx.CallAfter(self.__refresh_encounter)
1008
1010 wx.CallAfter(self.__on_current_encounter_switched)
1011
1013 self.__refresh_encounter()
1014
1015
1016
1018 """Show related note at the bottom."""
1019 pass
1020
1022 """Show related note at the bottom."""
1023 emr = self.__pat.get_emr()
1024 self.__refresh_recent_notes (
1025 problem = self._LCTRL_active_problems.get_selected_item_data(only_one = True)
1026 )
1027
1029 """Open progress note editor for this problem.
1030 """
1031 problem = self._LCTRL_active_problems.get_selected_item_data(only_one = True)
1032 if problem is None:
1033 return True
1034
1035 dbcfg = gmCfg.cCfgSQL()
1036 allow_duplicate_editors = bool(dbcfg.get2 (
1037 option = u'horstspace.soap_editor.allow_same_episode_multiple_times',
1038 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1039 bias = u'user',
1040 default = False
1041 ))
1042 if self._NB_soap_editors.add_editor(problem = problem, allow_same_problem = allow_duplicate_editors):
1043 return True
1044
1045 gmGuiHelpers.gm_show_error (
1046 aMessage = _(
1047 'Cannot open progress note editor for\n\n'
1048 '[%s].\n\n'
1049 ) % problem['problem'],
1050 aTitle = _('opening progress note editor')
1051 )
1052 event.Skip()
1053 return False
1054
1056 self.__refresh_problem_list()
1057
1059 self.__refresh_problem_list()
1060
1061
1062
1066
1070
1074
1085
1090
1091
1092
1096
1120
1121
1122
1131
1143
1144
1145
1147 self.__refresh_problem_list()
1148 self.__refresh_encounter()
1149 return True
1150
1334
1335 from Gnumed.wxGladeWidgets import wxgSoapNoteExpandoEditAreaPnl
1336
1338
1340
1341 try:
1342 self.problem = kwargs['problem']
1343 del kwargs['problem']
1344 except KeyError:
1345 self.problem = None
1346
1347 wxgSoapNoteExpandoEditAreaPnl.wxgSoapNoteExpandoEditAreaPnl.__init__(self, *args, **kwargs)
1348
1349 self.fields = [
1350 self._TCTRL_Soap,
1351 self._TCTRL_sOap,
1352 self._TCTRL_soAp,
1353 self._TCTRL_soaP
1354 ]
1355
1356 self.__init_ui()
1357 self.__register_interests()
1358
1365
1369
1371 if self.problem is None:
1372 return
1373 if self.problem['summary'] is None:
1374 self._TCTRL_summary.SetValue(u'')
1375 else:
1376 self._TCTRL_summary.SetValue(self.problem['summary'])
1377
1379 if self.problem is None:
1380 self._PNL_visual_soap.refresh(document_folder = None)
1381 return
1382
1383 if self.problem['type'] == u'issue':
1384 self._PNL_visual_soap.refresh(document_folder = None)
1385 return
1386
1387 if self.problem['type'] == u'episode':
1388 pat = gmPerson.gmCurrentPatient()
1389 doc_folder = pat.get_document_folder()
1390 emr = pat.get_emr()
1391 self._PNL_visual_soap.refresh (
1392 document_folder = doc_folder,
1393 episodes = [self.problem['pk_episode']],
1394 encounter = emr.active_encounter['pk_encounter']
1395 )
1396 return
1397
1399 for field in self.fields:
1400 field.SetValue(u'')
1401 self._TCTRL_summary.SetValue(u'')
1402 self._PNL_visual_soap.clear()
1403
1405 fname, discard_unmodified = select_visual_progress_note_template(parent = self)
1406 if fname is None:
1407 return False
1408
1409 if self.problem is None:
1410 issue = None
1411 episode = None
1412 elif self.problem['type'] == 'issue':
1413 issue = self.problem['pk_health_issue']
1414 episode = None
1415 else:
1416 issue = self.problem['pk_health_issue']
1417 episode = gmEMRStructItems.problem2episode(self.problem)
1418
1419 wx.CallAfter (
1420 edit_visual_progress_note,
1421 filename = fname,
1422 episode = episode,
1423 discard_unmodified = discard_unmodified,
1424 health_issue = issue
1425 )
1426
1427 - def save(self, emr=None, episode_name_candidates=None):
1428
1429 if self.empty:
1430 return True
1431
1432
1433 if (self.problem is None) or (self.problem['type'] == 'issue'):
1434
1435 episode_name_candidates.append(u'')
1436 for candidate in episode_name_candidates:
1437 if candidate is None:
1438 continue
1439 epi_name = candidate.strip().replace('\r', '//').replace('\n', '//')
1440 break
1441
1442 dlg = wx.TextEntryDialog (
1443 parent = self,
1444 message = _('Enter a short working name for this new problem:'),
1445 caption = _('Creating a problem (episode) to save the notelet under ...'),
1446 defaultValue = epi_name,
1447 style = wx.OK | wx.CANCEL | wx.CENTRE
1448 )
1449 decision = dlg.ShowModal()
1450 if decision != wx.ID_OK:
1451 return False
1452
1453 epi_name = dlg.GetValue().strip()
1454 if epi_name == u'':
1455 gmGuiHelpers.gm_show_error(_('Cannot save a new problem without a name.'), _('saving progress note'))
1456 return False
1457
1458
1459 new_episode = emr.add_episode(episode_name = epi_name[:45], pk_health_issue = None, is_open = True)
1460 new_episode['summary'] = self._TCTRL_summary.GetValue().strip()
1461 new_episode.save()
1462
1463 if self.problem is not None:
1464 issue = emr.problem2issue(self.problem)
1465 if not gmEMRStructWidgets.move_episode_to_issue(episode = new_episode, target_issue = issue, save_to_backend = True):
1466 gmGuiHelpers.gm_show_warning (
1467 _(
1468 'The new episode:\n'
1469 '\n'
1470 ' "%s"\n'
1471 '\n'
1472 'will remain unassociated despite the editor\n'
1473 'having been invoked from the health issue:\n'
1474 '\n'
1475 ' "%s"'
1476 ) % (
1477 new_episode['description'],
1478 issue['description']
1479 ),
1480 _('saving progress note')
1481 )
1482
1483 epi_id = new_episode['pk_episode']
1484
1485
1486 else:
1487 epi_id = self.problem['pk_episode']
1488
1489 emr.add_notes(notes = self.soap, episode = epi_id)
1490
1491
1492
1493 if self.problem['type'] == 'episode':
1494 new_summary = self._TCTRL_summary.GetValue().strip()
1495 epi = emr.problem2episode(self.problem)
1496 if epi['summary'] is None:
1497 epi['summary'] = new_summary
1498 epi.save()
1499 else:
1500 if epi['summary'].strip() != new_summary:
1501 epi['summary'] = new_summary
1502 epi.save()
1503
1504 return True
1505
1506
1507
1509 for field in self.fields:
1510 wx_expando.EVT_ETC_LAYOUT_NEEDED(field, field.GetId(), self._on_expando_needs_layout)
1511 wx_expando.EVT_ETC_LAYOUT_NEEDED(self._TCTRL_summary, self._TCTRL_summary.GetId(), self._on_expando_needs_layout)
1512
1514
1515
1516
1517 self.Fit()
1518
1519 if self.HasScrollbar(wx.VERTICAL):
1520
1521 expando = self.FindWindowById(evt.GetId())
1522 y_expando = expando.GetPositionTuple()[1]
1523 h_expando = expando.GetSizeTuple()[1]
1524 line_cursor = expando.PositionToXY(expando.GetInsertionPoint())[1] + 1
1525 y_cursor = int(round((float(line_cursor) / expando.NumberOfLines) * h_expando))
1526 y_desired_visible = y_expando + y_cursor
1527
1528 y_view = self.ViewStart[1]
1529 h_view = self.GetClientSizeTuple()[1]
1530
1531
1532
1533
1534
1535
1536
1537
1538 if y_desired_visible < y_view:
1539
1540 self.Scroll(0, y_desired_visible)
1541
1542 if y_desired_visible > h_view:
1543
1544 self.Scroll(0, y_desired_visible)
1545
1546
1547
1549 note = []
1550
1551 tmp = self._TCTRL_Soap.GetValue().strip()
1552 if tmp != u'':
1553 note.append(['s', tmp])
1554
1555 tmp = self._TCTRL_sOap.GetValue().strip()
1556 if tmp != u'':
1557 note.append(['o', tmp])
1558
1559 tmp = self._TCTRL_soAp.GetValue().strip()
1560 if tmp != u'':
1561 note.append(['a', tmp])
1562
1563 tmp = self._TCTRL_soaP.GetValue().strip()
1564 if tmp != u'':
1565 note.append(['p', tmp])
1566
1567 return note
1568
1569 soap = property(_get_soap, lambda x:x)
1570
1572 for field in self.fields:
1573 if field.GetValue().strip() != u'':
1574 return False
1575
1576 summary = self._TCTRL_summary.GetValue().strip()
1577 if self.problem is None:
1578 if summary != u'':
1579 return False
1580 else:
1581 if self.problem['summary'] is None:
1582 if summary != u'':
1583 return False
1584 else:
1585 if summary != self.problem['summary'].strip():
1586 return False
1587
1588 return True
1589
1590 empty = property(_get_empty, lambda x:x)
1591
1592 -class cSoapLineTextCtrl(wx_expando.ExpandoTextCtrl):
1593
1594 - def __init__(self, *args, **kwargs):
1595
1596 wx_expando.ExpandoTextCtrl.__init__(self, *args, **kwargs)
1597
1598 self.__keyword_separators = regex.compile("[!?'\".,:;)}\]\r\n\s\t]+")
1599
1600 self.__register_interests()
1601
1602
1603
1604 - def _wrapLine(self, line, dc, width):
1605
1606 if (wx.MAJOR_VERSION > 1) and (wx.MINOR_VERSION > 8):
1607 return super(cSoapLineTextCtrl, self)._wrapLine(line, dc, width)
1608
1609
1610
1611
1612 pte = dc.GetPartialTextExtents(line)
1613 width -= wx.SystemSettings.GetMetric(wx.SYS_VSCROLL_X)
1614 idx = 0
1615 start = 0
1616 count = 0
1617 spc = -1
1618 while idx < len(pte):
1619 if line[idx] == ' ':
1620 spc = idx
1621 if pte[idx] - start > width:
1622
1623 count += 1
1624
1625 if spc != -1:
1626 idx = spc + 1
1627 spc = -1
1628 if idx < len(pte):
1629 start = pte[idx]
1630 else:
1631 idx += 1
1632 return count
1633
1634
1635
1637
1638
1639 wx.EVT_CHAR(self, self.__on_char)
1640 wx.EVT_SET_FOCUS(self, self.__on_focus)
1641
1642 - def __on_focus(self, evt):
1643 evt.Skip()
1644 wx.CallAfter(self._after_on_focus)
1645
1646 - def _after_on_focus(self):
1647 evt = wx.PyCommandEvent(wx_expando.wxEVT_ETC_LAYOUT_NEEDED, self.GetId())
1648 evt.SetEventObject(self)
1649 evt.height = None
1650 evt.numLines = None
1651 self.GetEventHandler().ProcessEvent(evt)
1652
1653 - def __on_char(self, evt):
1654 char = unichr(evt.GetUnicodeKey())
1655
1656 if self.LastPosition == 1:
1657 evt.Skip()
1658 return
1659
1660 explicit_expansion = False
1661 if evt.GetModifiers() == (wx.MOD_CMD | wx.MOD_ALT):
1662 if evt.GetKeyCode() != 13:
1663 evt.Skip()
1664 return
1665 explicit_expansion = True
1666
1667 if not explicit_expansion:
1668 if self.__keyword_separators.match(char) is None:
1669 evt.Skip()
1670 return
1671
1672 caret_pos, line_no = self.PositionToXY(self.InsertionPoint)
1673 line = self.GetLineText(line_no)
1674 word = self.__keyword_separators.split(line[:caret_pos])[-1]
1675
1676 if (
1677 (not explicit_expansion)
1678 and
1679 (word != u'$$steffi')
1680 and
1681 (word not in [ r[0] for r in gmPG2.get_text_expansion_keywords() ])
1682 ):
1683 evt.Skip()
1684 return
1685
1686 start = self.InsertionPoint - len(word)
1687 wx.CallAfter(self.replace_keyword_with_expansion, word, start, explicit_expansion)
1688
1689 evt.Skip()
1690 return
1691
1692 - def replace_keyword_with_expansion(self, keyword=None, position=None, show_list=False):
1693
1694 if show_list:
1695 candidates = gmPG2.get_keyword_expansion_candidates(keyword = keyword)
1696 if len(candidates) == 0:
1697 return
1698 if len(candidates) == 1:
1699 keyword = candidates[0]
1700 else:
1701 keyword = gmListWidgets.get_choices_from_list (
1702 parent = self,
1703 msg = _(
1704 'Several macros match the keyword [%s].\n'
1705 '\n'
1706 'Please select the expansion you want to happen.'
1707 ) % keyword,
1708 caption = _('Selecting text macro'),
1709 choices = candidates,
1710 columns = [_('Keyword')],
1711 single_selection = True,
1712 can_return_empty = False
1713 )
1714 if keyword is None:
1715 return
1716
1717 expansion = gmPG2.expand_keyword(keyword = keyword)
1718
1719 if expansion is None:
1720 return
1721
1722 if expansion == u'':
1723 return
1724
1725 self.Replace (
1726 position,
1727 position + len(keyword),
1728 expansion
1729 )
1730
1731 self.SetInsertionPoint(position + len(expansion) + 1)
1732 self.ShowPosition(position + len(expansion) + 1)
1733
1734 return
1735
1736
1737
1768
1769 gmCfgWidgets.configure_string_option (
1770 message = _(
1771 'Enter the shell command with which to start\n'
1772 'the image editor for visual progress notes.\n'
1773 '\n'
1774 'Any "%(img)s" included with the arguments\n'
1775 'will be replaced by the file name of the\n'
1776 'note template.'
1777 ),
1778 option = u'external.tools.visual_soap_editor_cmd',
1779 bias = 'user',
1780 default_value = None,
1781 validator = is_valid
1782 )
1783
1785 if parent is None:
1786 parent = wx.GetApp().GetTopWindow()
1787
1788 dlg = wx.FileDialog (
1789 parent = parent,
1790 message = _('Choose file to use as template for new visual progress note'),
1791 defaultDir = os.path.expanduser('~'),
1792 defaultFile = '',
1793
1794 style = wx.OPEN | wx.HIDE_READONLY | wx.FILE_MUST_EXIST
1795 )
1796 result = dlg.ShowModal()
1797
1798 if result == wx.ID_CANCEL:
1799 dlg.Destroy()
1800 return None
1801
1802 full_filename = dlg.GetPath()
1803 dlg.Hide()
1804 dlg.Destroy()
1805 return full_filename
1806
1808
1809 if parent is None:
1810 parent = wx.GetApp().GetTopWindow()
1811
1812
1813 from Gnumed.wxpython import gmFormWidgets
1814 template = gmFormWidgets.manage_form_templates (
1815 parent = parent,
1816 template_types = [gmDocuments.DOCUMENT_TYPE_VISUAL_PROGRESS_NOTE],
1817 active_only = True
1818 )
1819
1820
1821 if template is None:
1822 fname = select_file_as_visual_progress_note_template(parent = parent)
1823 if fname is None:
1824 return (None, None)
1825
1826 ext = os.path.splitext(fname)[1]
1827 tmp_name = gmTools.get_unique_filename(suffix = ext)
1828 _log.debug('visual progress note from file: [%s] -> [%s]', fname, tmp_name)
1829 shutil.copy2(fname, tmp_name)
1830 return (tmp_name, False)
1831
1832 filename = template.export_to_file()
1833 if filename is None:
1834 gmDispatcher.send(signal = u'statustext', msg = _('Cannot export visual progress note template for [%s].') % template['name_long'])
1835 return (None, None)
1836 return (filename, True)
1837
1838
1840 """This assumes <filename> contains an image which can be handled by the configured image editor."""
1841
1842 if doc_part is not None:
1843 filename = doc_part.export_to_file()
1844 if filename is None:
1845 gmDispatcher.send(signal = u'statustext', msg = _('Cannot export visual progress note to file.'))
1846 return None
1847
1848 dbcfg = gmCfg.cCfgSQL()
1849 cmd = dbcfg.get2 (
1850 option = u'external.tools.visual_soap_editor_cmd',
1851 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1852 bias = 'user'
1853 )
1854
1855 if cmd is None:
1856 gmDispatcher.send(signal = u'statustext', msg = _('Editor for visual progress note not configured.'), beep = False)
1857 cmd = configure_visual_progress_note_editor()
1858 if cmd is None:
1859 gmDispatcher.send(signal = u'statustext', msg = _('Editor for visual progress note not configured.'), beep = True)
1860 return None
1861
1862 if u'%(img)s' in cmd:
1863 cmd % {u'img': filename}
1864 else:
1865 cmd = u'%s %s' % (cmd, filename)
1866
1867 if discard_unmodified:
1868 original_stat = os.stat(filename)
1869 original_md5 = gmTools.file2md5(filename)
1870
1871 success = gmShellAPI.run_command_in_shell(cmd, blocking = True)
1872 if not success:
1873 gmGuiHelpers.gm_show_error (
1874 _(
1875 'There was a problem with running the editor\n'
1876 'for visual progress notes.\n'
1877 '\n'
1878 ' [%s]\n'
1879 '\n'
1880 ) % cmd,
1881 _('Editing visual progress note')
1882 )
1883 return None
1884
1885 try:
1886 open(filename, 'r').close()
1887 except StandardError:
1888 _log.exception('problem accessing visual progress note file [%s]', filename)
1889 gmGuiHelpers.gm_show_error (
1890 _(
1891 'There was a problem reading the visual\n'
1892 'progress note from the file:\n'
1893 '\n'
1894 ' [%s]\n'
1895 '\n'
1896 ) % filename,
1897 _('Saving visual progress note')
1898 )
1899 return None
1900
1901 if discard_unmodified:
1902 modified_stat = os.stat(filename)
1903
1904 if original_stat.st_size == modified_stat.st_size:
1905 modified_md5 = gmTools.file2md5(filename)
1906
1907 if original_md5 == modified_md5:
1908 _log.debug('visual progress note (template) not modified')
1909
1910 msg = _(
1911 u'You either created a visual progress note from a template\n'
1912 u'in the database (rather than from a file on disk) or you\n'
1913 u'edited an existing visual progress note.\n'
1914 u'\n'
1915 u'The template/original was not modified at all, however.\n'
1916 u'\n'
1917 u'Do you still want to save the unmodified image as a\n'
1918 u'visual progress note into the EMR of the patient ?\n'
1919 )
1920 save_unmodified = gmGuiHelpers.gm_show_question (
1921 msg,
1922 _('Saving visual progress note')
1923 )
1924 if not save_unmodified:
1925 _log.debug('user discarded unmodified note')
1926 return
1927
1928 if doc_part is not None:
1929 doc_part.update_data_from_file(fname = filename)
1930 doc_part.set_reviewed(technically_abnormal = False, clinically_relevant = True)
1931 return None
1932
1933 if not isinstance(episode, gmEMRStructItems.cEpisode):
1934 if episode is None:
1935 episode = _('visual progress notes')
1936 pat = gmPerson.gmCurrentPatient()
1937 emr = pat.get_emr()
1938 episode = emr.add_episode(episode_name = episode.strip(), pk_health_issue = health_issue, is_open = False)
1939
1940 doc = gmDocumentWidgets.save_file_as_new_document (
1941 filename = filename,
1942 document_type = gmDocuments.DOCUMENT_TYPE_VISUAL_PROGRESS_NOTE,
1943 episode = episode,
1944 unlock_patient = True
1945 )
1946 doc.set_reviewed(technically_abnormal = False, clinically_relevant = True)
1947
1948 return doc
1949
1951 """Phrasewheel to allow selection of visual SOAP template."""
1952
1978
1984
1985 from Gnumed.wxGladeWidgets import wxgVisualSoapPresenterPnl
1986
1988
1993
1994
1995
1996 - def refresh(self, document_folder=None, episodes=None, encounter=None):
1997
1998 self.clear()
1999 if document_folder is not None:
2000 soap_docs = document_folder.get_visual_progress_notes(episodes = episodes, encounter = encounter)
2001 if len(soap_docs) > 0:
2002 for soap_doc in soap_docs:
2003 parts = soap_doc.parts
2004 if len(parts) == 0:
2005 continue
2006 part = parts[0]
2007 fname = part.export_to_file()
2008 if fname is None:
2009 continue
2010
2011
2012 img = gmGuiHelpers.file2scaled_image (
2013 filename = fname,
2014 height = 30
2015 )
2016
2017 bmp = wx_genstatbmp.GenStaticBitmap(self, -1, img, style = wx.NO_BORDER)
2018
2019
2020 img = gmGuiHelpers.file2scaled_image (
2021 filename = fname,
2022 height = 150
2023 )
2024 tip = agw_stt.SuperToolTip (
2025 u'',
2026 bodyImage = img,
2027 header = _('Created: %s') % part['date_generated'].strftime('%Y %B %d').encode(gmI18N.get_encoding()),
2028 footer = gmTools.coalesce(part['doc_comment'], u'').strip()
2029 )
2030 tip.SetTopGradientColor('white')
2031 tip.SetMiddleGradientColor('white')
2032 tip.SetBottomGradientColor('white')
2033 tip.SetTarget(bmp)
2034
2035 bmp.doc_part = part
2036 bmp.Bind(wx.EVT_LEFT_UP, self._on_bitmap_leftclicked)
2037
2038 self._SZR_soap.Add(bmp, 0, wx.LEFT | wx.RIGHT | wx.TOP | wx.BOTTOM | wx.EXPAND, 3)
2039 self.__bitmaps.append(bmp)
2040
2041 self.GetParent().Layout()
2042
2044 while self._SZR_soap.Detach(0):
2045 pass
2046 for bmp in self.__bitmaps:
2047 bmp.Destroy()
2048 self.__bitmaps = []
2049
2051 wx.CallAfter (
2052 edit_visual_progress_note,
2053 doc_part = evt.GetEventObject().doc_part,
2054 discard_unmodified = True
2055 )
2056
2057 from Gnumed.wxGladeWidgets import wxgVisualSoapPnl
2058
2060
2067
2068
2069
2076
2077 - def refresh(self, patient=None, encounter=None):
2111
2152
2153
2154
2163
2165 self._BTN_delete.Enable(False)
2166
2183
2213
2276
2294
2295
2296
2297 if __name__ == '__main__':
2298
2299 if len(sys.argv) < 2:
2300 sys.exit()
2301
2302 if sys.argv[1] != 'test':
2303 sys.exit()
2304
2305 gmI18N.activate_locale()
2306 gmI18N.install_domain(domain = 'gnumed')
2307
2308
2317
2324
2337
2338
2339 test_cSoapNoteExpandoEditAreaPnl()
2340
2341
2342
2343