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