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