| 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 return True
740 #--------------------------------------------------------
741 # internal helpers
742 #--------------------------------------------------------
744 self._LCTRL_active_problems.set_columns([_('Last'), _('Problem'), _('In health issue')])
745 self._LCTRL_active_problems.set_string_items()
746
747 self._splitter_main.SetSashGravity(0.5)
748 self._splitter_left.SetSashGravity(0.5)
749
750 splitter_size = self._splitter_main.GetSizeTuple()[0]
751 self._splitter_main.SetSashPosition(splitter_size * 3 / 10, True)
752
753 splitter_size = self._splitter_left.GetSizeTuple()[1]
754 self._splitter_left.SetSashPosition(splitter_size * 6 / 20, True)
755
756 self._NB_soap_editors.DeleteAllPages()
757 self._NB_soap_editors.MoveAfterInTabOrder(self._PRW_aoe_codes)
758 #--------------------------------------------------------
760 """Clear all information from input panel."""
761
762 self._LCTRL_active_problems.set_string_items()
763
764 self._TCTRL_recent_notes.SetValue(u'')
765 self._SZR_recent_notes_staticbox.SetLabel(_('Most recent notes on selected problem'))
766
767 self._PRW_encounter_type.SetText(suppress_smarts = True)
768 self._PRW_encounter_start.SetText(suppress_smarts = True)
769 self._PRW_encounter_end.SetText(suppress_smarts = True)
770 self._TCTRL_rfe.SetValue(u'')
771 self._TCTRL_aoe.SetValue(u'')
772
773 self._NB_soap_editors.DeleteAllPages()
774 self._NB_soap_editors.add_editor()
775 #--------------------------------------------------------
777 """Update health problems list."""
778
779 self._LCTRL_active_problems.set_string_items()
780
781 emr = self.__pat.get_emr()
782 problems = emr.get_problems (
783 include_closed_episodes = self._CHBOX_show_closed_episodes.IsChecked(),
784 include_irrelevant_issues = self._CHBOX_irrelevant_issues.IsChecked()
785 )
786
787 list_items = []
788 active_problems = []
789 for problem in problems:
790 if not problem['problem_active']:
791 if not problem['is_potential_problem']:
792 continue
793
794 active_problems.append(problem)
795
796 if problem['type'] == 'issue':
797 issue = emr.problem2issue(problem)
798 last_encounter = emr.get_last_encounter(issue_id = issue['pk_health_issue'])
799 if last_encounter is None:
800 last = issue['modified_when'].strftime('%m/%Y')
801 else:
802 last = last_encounter['last_affirmed'].strftime('%m/%Y')
803
804 list_items.append([last, problem['problem'], gmTools.u_down_left_arrow]) #gmTools.u_left_arrow
805
806 elif problem['type'] == 'episode':
807 epi = emr.problem2episode(problem)
808 last_encounter = emr.get_last_encounter(episode_id = epi['pk_episode'])
809 if last_encounter is None:
810 last = epi['episode_modified_when'].strftime('%m/%Y')
811 else:
812 last = last_encounter['last_affirmed'].strftime('%m/%Y')
813
814 list_items.append ([
815 last,
816 problem['problem'],
817 gmTools.coalesce(initial = epi['health_issue'], instead = u'?') #gmTools.u_diameter
818 ])
819
820 self._LCTRL_active_problems.set_string_items(items = list_items)
821 self._LCTRL_active_problems.set_column_widths()
822 self._LCTRL_active_problems.set_data(data = active_problems)
823
824 showing_potential_problems = (
825 self._CHBOX_show_closed_episodes.IsChecked()
826 or
827 self._CHBOX_irrelevant_issues.IsChecked()
828 )
829 if showing_potential_problems:
830 self._SZR_problem_list_staticbox.SetLabel(_('%s (active+potential) problems') % len(list_items))
831 else:
832 self._SZR_problem_list_staticbox.SetLabel(_('%s active problems') % len(list_items))
833
834 return True
835 #--------------------------------------------------------
837 soap = u''
838 emr = self.__pat.get_emr()
839 prev_enc = emr.get_last_but_one_encounter(issue_id = problem['pk_health_issue'])
840 if prev_enc is not None:
841 soap += prev_enc.format (
842 issues = [ problem['pk_health_issue'] ],
843 with_soap = True,
844 with_docs = False,
845 with_tests = False,
846 patient = self.__pat,
847 fancy_header = False,
848 with_rfe_aoe = True
849 )
850
851 tmp = emr.active_encounter.format_soap (
852 soap_cats = 'soap',
853 emr = emr,
854 issues = [ problem['pk_health_issue'] ],
855 )
856 if len(tmp) > 0:
857 soap += _('Current encounter:') + u'\n'
858 soap += u'\n'.join(tmp) + u'\n'
859
860 if problem['summary'] is not None:
861 soap += u'\n-- %s ----------\n%s' % (
862 _('Cumulative summary'),
863 gmTools.wrap (
864 text = problem['summary'],
865 width = 45,
866 initial_indent = u' ',
867 subsequent_indent = u' '
868 ).strip('\n')
869 )
870
871 return soap
872 #--------------------------------------------------------
874 soap = u''
875 emr = self.__pat.get_emr()
876 prev_enc = emr.get_last_but_one_encounter(episode_id = problem['pk_episode'])
877 if prev_enc is not None:
878 soap += prev_enc.format (
879 episodes = [ problem['pk_episode'] ],
880 with_soap = True,
881 with_docs = False,
882 with_tests = False,
883 patient = self.__pat,
884 fancy_header = False,
885 with_rfe_aoe = True
886 )
887 else:
888 if problem['pk_health_issue'] is not None:
889 prev_enc = emr.get_last_but_one_encounter(episode_id = problem['pk_health_issue'])
890 if prev_enc is not None:
891 soap += prev_enc.format (
892 with_soap = True,
893 with_docs = False,
894 with_tests = False,
895 patient = self.__pat,
896 issues = [ problem['pk_health_issue'] ],
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 self._NB_soap_editors.refresh_current_editor()
925 #--------------------------------------------------------
927 if not self.__patient_just_changed:
928 return
929
930 dbcfg = gmCfg.cCfgSQL()
931 auto_open_recent_problems = bool(dbcfg.get2 (
932 option = u'horstspace.soap_editor.auto_open_latest_episodes',
933 workplace = gmSurgery.gmCurrentPractice().active_workplace,
934 bias = u'user',
935 default = True
936 ))
937
938 self.__patient_just_changed = False
939 emr = self.__pat.get_emr()
940 recent_epis = emr.active_encounter.get_episodes()
941 prev_enc = emr.get_last_but_one_encounter()
942 if prev_enc is not None:
943 recent_epis.extend(prev_enc.get_episodes())
944
945 for epi in recent_epis:
946 if not epi['episode_open']:
947 continue
948 self._NB_soap_editors.add_editor(problem = epi, allow_same_problem = False)
949 #--------------------------------------------------------
951 """This refreshes the recent-notes part."""
952
953 soap = u''
954 caption = u'<?>'
955
956 if problem['type'] == u'issue':
957 caption = problem['problem'][:35]
958 soap = self.__get_soap_for_issue_problem(problem = problem)
959
960 elif problem['type'] == u'episode':
961 caption = problem['problem'][:35]
962 soap = self.__get_soap_for_episode_problem(problem = problem)
963
964 self._TCTRL_recent_notes.SetValue(soap)
965 self._TCTRL_recent_notes.ShowPosition(self._TCTRL_recent_notes.GetLastPosition())
966 self._SZR_recent_notes_staticbox.SetLabel(_('Most recent notes on %s%s%s') % (
967 gmTools.u_left_double_angle_quote,
968 caption,
969 gmTools.u_right_double_angle_quote
970 ))
971
972 self._TCTRL_recent_notes.Refresh()
973
974 return True
975 #--------------------------------------------------------
977 """Update encounter fields."""
978
979 emr = self.__pat.get_emr()
980 enc = emr.active_encounter
981 self._PRW_encounter_type.SetText(value = enc['l10n_type'], data = enc['pk_type'])
982
983 fts = gmDateTime.cFuzzyTimestamp (
984 timestamp = enc['started'],
985 accuracy = gmDateTime.acc_minutes
986 )
987 self._PRW_encounter_start.SetText(fts.format_accurately(), data = fts)
988
989 fts = gmDateTime.cFuzzyTimestamp (
990 timestamp = enc['last_affirmed'],
991 accuracy = gmDateTime.acc_minutes
992 )
993 self._PRW_encounter_end.SetText(fts.format_accurately(), data = fts)
994
995 self._TCTRL_rfe.SetValue(gmTools.coalesce(enc['reason_for_encounter'], u''))
996 val, data = self._PRW_rfe_codes.generic_linked_codes2item_dict(enc.generic_codes_rfe)
997 self._PRW_rfe_codes.SetText(val, data)
998
999 self._TCTRL_aoe.SetValue(gmTools.coalesce(enc['assessment_of_encounter'], u''))
1000 val, data = self._PRW_aoe_codes.generic_linked_codes2item_dict(enc.generic_codes_aoe)
1001 self._PRW_aoe_codes.SetText(val, data)
1002
1003 self._PRW_encounter_type.Refresh()
1004 self._PRW_encounter_start.Refresh()
1005 self._PRW_encounter_end.Refresh()
1006 self._TCTRL_rfe.Refresh()
1007 self._PRW_rfe_codes.Refresh()
1008 self._TCTRL_aoe.Refresh()
1009 self._PRW_aoe_codes.Refresh()
1010 #--------------------------------------------------------
1012 """Assumes that the field data is valid."""
1013
1014 emr = self.__pat.get_emr()
1015 enc = emr.active_encounter
1016
1017 data = {
1018 'pk_type': self._PRW_encounter_type.GetData(),
1019 'reason_for_encounter': gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u''),
1020 'assessment_of_encounter': gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u''),
1021 'pk_location': enc['pk_location'],
1022 'pk_patient': enc['pk_patient']
1023 }
1024
1025 if self._PRW_encounter_start.GetData() is None:
1026 data['started'] = None
1027 else:
1028 data['started'] = self._PRW_encounter_start.GetData().get_pydt()
1029
1030 if self._PRW_encounter_end.GetData() is None:
1031 data['last_affirmed'] = None
1032 else:
1033 data['last_affirmed'] = self._PRW_encounter_end.GetData().get_pydt()
1034
1035 return not enc.same_payload(another_object = data)
1036 #--------------------------------------------------------
1038
1039 found_error = False
1040
1041 if self._PRW_encounter_type.GetData() is None:
1042 found_error = True
1043 msg = _('Cannot save encounter: missing type.')
1044
1045 if self._PRW_encounter_start.GetData() is None:
1046 found_error = True
1047 msg = _('Cannot save encounter: missing start time.')
1048
1049 if self._PRW_encounter_end.GetData() is None:
1050 found_error = True
1051 msg = _('Cannot save encounter: missing end time.')
1052
1053 if found_error:
1054 gmDispatcher.send(signal = 'statustext', msg = msg, beep = True)
1055 return False
1056
1057 return True
1058 #--------------------------------------------------------
1059 # event handling
1060 #--------------------------------------------------------
1062 """Configure enabled event signals."""
1063 # client internal signals
1064 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection)
1065 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
1066 gmDispatcher.connect(signal = u'episode_mod_db', receiver = self._on_episode_issue_mod_db)
1067 gmDispatcher.connect(signal = u'health_issue_mod_db', receiver = self._on_episode_issue_mod_db)
1068 gmDispatcher.connect(signal = u'episode_code_mod_db', receiver = self._on_episode_issue_mod_db)
1069 gmDispatcher.connect(signal = u'doc_mod_db', receiver = self._on_doc_mod_db)
1070 gmDispatcher.connect(signal = u'current_encounter_modified', receiver = self._on_current_encounter_modified)
1071 gmDispatcher.connect(signal = u'current_encounter_switched', receiver = self._on_current_encounter_switched)
1072 gmDispatcher.connect(signal = u'rfe_code_mod_db', receiver = self._on_current_encounter_modified)
1073 gmDispatcher.connect(signal = u'aoe_code_mod_db', receiver = self._on_current_encounter_modified)
1074
1075 # synchronous signals
1076 self.__pat.register_pre_selection_callback(callback = self._pre_selection_callback)
1077 gmDispatcher.send(signal = u'register_pre_exit_callback', callback = self._pre_exit_callback)
1078 #--------------------------------------------------------
1080 """Another patient is about to be activated.
1081
1082 Patient change will not proceed before this returns True.
1083 """
1084 # don't worry about the encounter here - it will be offered
1085 # for editing higher up if anything was saved to the EMR
1086 if not self.__pat.connected:
1087 return True
1088 return self._NB_soap_editors.warn_on_unsaved_soap()
1089 #--------------------------------------------------------
1091 """The client is about to be shut down.
1092
1093 Shutdown will not proceed before this returns.
1094 """
1095 if not self.__pat.connected:
1096 return True
1097
1098 # if self.__encounter_modified():
1099 # do_save_enc = gmGuiHelpers.gm_show_question (
1100 # aMessage = _(
1101 # 'You have modified the details\n'
1102 # 'of the current encounter.\n'
1103 # '\n'
1104 # 'Do you want to save those changes ?'
1105 # ),
1106 # aTitle = _('Starting new encounter')
1107 # )
1108 # if do_save_enc:
1109 # if not self.save_encounter():
1110 # gmDispatcher.send(signal = u'statustext', msg = _('Error saving current encounter.'), beep = True)
1111
1112 emr = self.__pat.get_emr()
1113 saved = self._NB_soap_editors.save_all_editors (
1114 emr = emr,
1115 episode_name_candidates = [
1116 gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u''),
1117 gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u'')
1118 ]
1119 )
1120 if not saved:
1121 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save all editors. Some were kept open.'), beep = True)
1122 return True
1123 #--------------------------------------------------------
1126 #--------------------------------------------------------
1129 #--------------------------------------------------------
1133 #--------------------------------------------------------
1136 #--------------------------------------------------------
1139 #--------------------------------------------------------
1142 #--------------------------------------------------------
1145 #--------------------------------------------------------
1148 #--------------------------------------------------------
1149 # problem list specific events
1150 #--------------------------------------------------------
1154 #--------------------------------------------------------
1156 problem = self._LCTRL_active_problems.get_selected_item_data(only_one = True)
1157 if problem['type'] == u'issue':
1158 gmEMRStructWidgets.edit_health_issue(parent = self, issue = problem.get_as_health_issue())
1159 return
1160
1161 if problem['type'] == u'episode':
1162 gmEMRStructWidgets.edit_episode(parent = self, episode = problem.get_as_episode())
1163 return
1164
1165 event.Skip()
1166 #--------------------------------------------------------
1168 """Show related note at the bottom."""
1169 emr = self.__pat.get_emr()
1170 self.__refresh_recent_notes (
1171 problem = self._LCTRL_active_problems.get_selected_item_data(only_one = True)
1172 )
1173 #--------------------------------------------------------
1175 """Open progress note editor for this problem.
1176 """
1177 problem = self._LCTRL_active_problems.get_selected_item_data(only_one = True)
1178 if problem is None:
1179 return True
1180
1181 dbcfg = gmCfg.cCfgSQL()
1182 allow_duplicate_editors = bool(dbcfg.get2 (
1183 option = u'horstspace.soap_editor.allow_same_episode_multiple_times',
1184 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1185 bias = u'user',
1186 default = False
1187 ))
1188 if self._NB_soap_editors.add_editor(problem = problem, allow_same_problem = allow_duplicate_editors):
1189 return True
1190
1191 gmGuiHelpers.gm_show_error (
1192 aMessage = _(
1193 'Cannot open progress note editor for\n\n'
1194 '[%s].\n\n'
1195 ) % problem['problem'],
1196 aTitle = _('opening progress note editor')
1197 )
1198 event.Skip()
1199 return False
1200 #--------------------------------------------------------
1203 #--------------------------------------------------------
1206 #--------------------------------------------------------
1207 # SOAP editor specific buttons
1208 #--------------------------------------------------------
1212 #--------------------------------------------------------
1216 #--------------------------------------------------------
1220 #--------------------------------------------------------
1231 #--------------------------------------------------------
1254 #--------------------------------------------------------
1259 #--------------------------------------------------------
1260 # encounter specific buttons
1261 #--------------------------------------------------------
1265 #--------------------------------------------------------
1289 #--------------------------------------------------------
1290 # other buttons
1291 #--------------------------------------------------------
1300 #--------------------------------------------------------
1312 #--------------------------------------------------------
1313 # reget mixin API
1314 #--------------------------------------------------------
1320 #============================================================
1322 """A notebook holding panels with progress note editors.
1323
1324 There can be one or several progress note editor panel
1325 for each episode being worked on. The editor class in
1326 each panel is configurable.
1327
1328 There will always be one open editor.
1329 """
1331
1332 kwargs['style'] = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER
1333
1334 wx.Notebook.__init__(self, *args, **kwargs)
1335 #--------------------------------------------------------
1336 # public API
1337 #--------------------------------------------------------
1339 """Add a progress note editor page.
1340
1341 The way <allow_same_problem> is currently used in callers
1342 it only applies to unassociated episodes.
1343 """
1344 problem_to_add = problem
1345
1346 # determine label
1347 if problem_to_add is None:
1348 label = _('new problem')
1349 else:
1350 # normalize problem type
1351 if isinstance(problem_to_add, gmEMRStructItems.cEpisode):
1352 problem_to_add = gmEMRStructItems.episode2problem(episode = problem_to_add)
1353
1354 elif isinstance(problem_to_add, gmEMRStructItems.cHealthIssue):
1355 problem_to_add = gmEMRStructItems.health_issue2problem(episode = problem_to_add)
1356
1357 if not isinstance(problem_to_add, gmEMRStructItems.cProblem):
1358 raise TypeError('cannot open progress note editor for [%s]' % problem_to_add)
1359
1360 label = problem_to_add['problem']
1361 # FIXME: configure maximum length
1362 if len(label) > 23:
1363 label = label[:21] + gmTools.u_ellipsis
1364
1365 # new unassociated problem or dupes allowed
1366 if (problem_to_add is None) or allow_same_problem:
1367 new_page = cSoapNoteExpandoEditAreaPnl(parent = self, id = -1, problem = problem_to_add)
1368 result = self.AddPage (
1369 page = new_page,
1370 text = label,
1371 select = True
1372 )
1373 return result
1374
1375 # real problem, no dupes allowed
1376 # - raise existing editor
1377 for page_idx in range(self.GetPageCount()):
1378 page = self.GetPage(page_idx)
1379
1380 # editor is for unassociated new problem
1381 if page.problem is None:
1382 continue
1383
1384 # editor is for episode
1385 if page.problem['type'] == 'episode':
1386 if page.problem['pk_episode'] == problem_to_add['pk_episode']:
1387 self.SetSelection(page_idx)
1388 gmDispatcher.send(signal = u'statustext', msg = u'Raising existing editor.', beep = True)
1389 return True
1390 continue
1391
1392 # editor is for health issue
1393 if page.problem['type'] == 'issue':
1394 if page.problem['pk_health_issue'] == problem_to_add['pk_health_issue']:
1395 self.SetSelection(page_idx)
1396 gmDispatcher.send(signal = u'statustext', msg = u'Raising existing editor.', beep = True)
1397 return True
1398 continue
1399
1400 # - or add new editor
1401 new_page = cSoapNoteExpandoEditAreaPnl(parent = self, id = -1, problem = problem_to_add)
1402 result = self.AddPage (
1403 page = new_page,
1404 text = label,
1405 select = True
1406 )
1407
1408 return result
1409 #--------------------------------------------------------
1411
1412 page_idx = self.GetSelection()
1413 page = self.GetPage(page_idx)
1414
1415 if not page.empty:
1416 really_discard = gmGuiHelpers.gm_show_question (
1417 _('Are you sure you really want to\n'
1418 'discard this progress note ?\n'
1419 ),
1420 _('Discarding progress note')
1421 )
1422 if really_discard is False:
1423 return
1424
1425 self.DeletePage(page_idx)
1426
1427 # always keep one unassociated editor open
1428 if self.GetPageCount() == 0:
1429 self.add_editor()
1430 #--------------------------------------------------------
1432
1433 page_idx = self.GetSelection()
1434 page = self.GetPage(page_idx)
1435
1436 if not page.save(emr = emr, episode_name_candidates = episode_name_candidates, encounter = encounter):
1437 return
1438
1439 self.DeletePage(page_idx)
1440
1441 # always keep one unassociated editor open
1442 if self.GetPageCount() == 0:
1443 self.add_editor()
1444 #--------------------------------------------------------
1446 for page_idx in range(self.GetPageCount()):
1447 page = self.GetPage(page_idx)
1448 if page.empty:
1449 continue
1450
1451 gmGuiHelpers.gm_show_warning (
1452 _('There are unsaved progress notes !\n'),
1453 _('Unsaved progress notes')
1454 )
1455 return False
1456
1457 return True
1458 #--------------------------------------------------------
1460
1461 _log.debug('saving editors: %s', self.GetPageCount())
1462
1463 all_closed = True
1464 for page_idx in range((self.GetPageCount() - 1), -1, -1):
1465 _log.debug('#%s of %s', page_idx, self.GetPageCount())
1466 try:
1467 self.ChangeSelection(page_idx)
1468 _log.debug('editor raised')
1469 except:
1470 _log.exception('cannot raise editor')
1471 page = self.GetPage(page_idx)
1472 if page.save(emr = emr, episode_name_candidates = episode_name_candidates):
1473 _log.debug('saved, deleting now')
1474 self.DeletePage(page_idx)
1475 else:
1476 _log.debug('not saved, not deleting')
1477 all_closed = False
1478
1479 # always keep one unassociated editor open
1480 if self.GetPageCount() == 0:
1481 self.add_editor()
1482
1483 return (all_closed is True)
1484 #--------------------------------------------------------
1489 #--------------------------------------------------------
1494 #--------------------------------------------------------
1499 #--------------------------------------------------------
1501 page_idx = self.GetSelection()
1502 page = self.GetPage(page_idx)
1503 page.add_visual_progress_note()
1504 #============================================================
1505 from Gnumed.wxGladeWidgets import wxgSoapNoteExpandoEditAreaPnl
1506
1507 -class cSoapNoteExpandoEditAreaPnl(wxgSoapNoteExpandoEditAreaPnl.wxgSoapNoteExpandoEditAreaPnl):
1508 """An Edit Area like panel for entering progress notes.
1509
1510 Subjective: Codes:
1511 expando text ctrl
1512 Objective: Codes:
1513 expando text ctrl
1514 Assessment: Codes:
1515 expando text ctrl
1516 Plan: Codes:
1517 expando text ctrl
1518 visual progress notes
1519 panel with images
1520 Episode summary: Codes:
1521 text ctrl
1522
1523 - knows the problem this edit area is about
1524 - can deal with issue or episode type problems
1525 """
1526
1528
1529 try:
1530 self.problem = kwargs['problem']
1531 del kwargs['problem']
1532 except KeyError:
1533 self.problem = None
1534
1535 wxgSoapNoteExpandoEditAreaPnl.wxgSoapNoteExpandoEditAreaPnl.__init__(self, *args, **kwargs)
1536
1537 self.soap_fields = [
1538 self._TCTRL_Soap,
1539 self._TCTRL_sOap,
1540 self._TCTRL_soAp,
1541 self._TCTRL_soaP
1542 ]
1543
1544 self.__init_ui()
1545 self.__register_interests()
1546 #--------------------------------------------------------
1548 self.refresh_summary()
1549 if self.problem is not None:
1550 if self.problem['summary'] is None:
1551 self._TCTRL_episode_summary.SetValue(u'')
1552 self.refresh_visual_soap()
1553 #--------------------------------------------------------
1557 #--------------------------------------------------------
1559 self._TCTRL_episode_summary.SetValue(u'')
1560 self._PRW_episode_codes.SetText(u'', self._PRW_episode_codes.list2data_dict([]))
1561 self._LBL_summary.SetLabel(_('Episode summary'))
1562
1563 # new problem ?
1564 if self.problem is None:
1565 return
1566
1567 # issue-level problem ?
1568 if self.problem['type'] == u'issue':
1569 return
1570
1571 # episode-level problem
1572 caption = _(u'Summary (%s)') % (
1573 gmDateTime.pydt_strftime (
1574 self.problem['modified_when'],
1575 format = '%B %Y',
1576 accuracy = gmDateTime.acc_days
1577 )
1578 )
1579 self._LBL_summary.SetLabel(caption)
1580
1581 if self.problem['summary'] is not None:
1582 self._TCTRL_episode_summary.SetValue(self.problem['summary'].strip())
1583
1584 val, data = self._PRW_episode_codes.generic_linked_codes2item_dict(self.problem.generic_codes)
1585 self._PRW_episode_codes.SetText(val, data)
1586 #--------------------------------------------------------
1588 if self.problem is None:
1589 self._PNL_visual_soap.refresh(document_folder = None)
1590 return
1591
1592 if self.problem['type'] == u'issue':
1593 self._PNL_visual_soap.refresh(document_folder = None)
1594 return
1595
1596 if self.problem['type'] == u'episode':
1597 pat = gmPerson.gmCurrentPatient()
1598 doc_folder = pat.get_document_folder()
1599 emr = pat.get_emr()
1600 self._PNL_visual_soap.refresh (
1601 document_folder = doc_folder,
1602 episodes = [self.problem['pk_episode']],
1603 encounter = emr.active_encounter['pk_encounter']
1604 )
1605 return
1606 #--------------------------------------------------------
1608 for field in self.soap_fields:
1609 field.SetValue(u'')
1610 self._TCTRL_episode_summary.SetValue(u'')
1611 self._LBL_summary.SetLabel(_('Episode summary'))
1612 self._PRW_episode_codes.SetText(u'', self._PRW_episode_codes.list2data_dict([]))
1613 self._PNL_visual_soap.clear()
1614 #--------------------------------------------------------
1616 fname, discard_unmodified = select_visual_progress_note_template(parent = self)
1617 if fname is None:
1618 return False
1619
1620 if self.problem is None:
1621 issue = None
1622 episode = None
1623 elif self.problem['type'] == 'issue':
1624 issue = self.problem['pk_health_issue']
1625 episode = None
1626 else:
1627 issue = self.problem['pk_health_issue']
1628 episode = gmEMRStructItems.problem2episode(self.problem)
1629
1630 wx.CallAfter (
1631 edit_visual_progress_note,
1632 filename = fname,
1633 episode = episode,
1634 discard_unmodified = discard_unmodified,
1635 health_issue = issue
1636 )
1637 #--------------------------------------------------------
1639
1640 if self.empty:
1641 return True
1642
1643 # new episode (standalone=unassociated or new-in-issue)
1644 if (self.problem is None) or (self.problem['type'] == 'issue'):
1645 episode = self.__create_new_episode(emr = emr, episode_name_candidates = episode_name_candidates)
1646 # existing episode
1647 else:
1648 episode = emr.problem2episode(self.problem)
1649
1650 if encounter is None:
1651 encounter = emr.current_encounter['pk_encounter']
1652
1653 soap_notes = []
1654 for note in self.soap:
1655 saved, data = gmClinNarrative.create_clin_narrative (
1656 soap_cat = note[0],
1657 narrative = note[1],
1658 episode_id = episode['pk_episode'],
1659 encounter_id = encounter
1660 )
1661 if saved:
1662 soap_notes.append(data)
1663
1664 # codes per narrative !
1665 # for note in soap_notes:
1666 # if note['soap_cat'] == u's':
1667 # codes = self._PRW_Soap_codes
1668 # elif note['soap_cat'] == u'o':
1669 # elif note['soap_cat'] == u'a':
1670 # elif note['soap_cat'] == u'p':
1671
1672 # set summary but only if not already set above for a
1673 # newly created episode (either standalone or within
1674 # a health issue)
1675 if self.problem is not None:
1676 if self.problem['type'] == 'episode':
1677 episode['summary'] = self._TCTRL_episode_summary.GetValue().strip()
1678 episode.save()
1679
1680 # codes for episode
1681 episode.generic_codes = [ d['data'] for d in self._PRW_episode_codes.GetData() ]
1682
1683 return True
1684 #--------------------------------------------------------
1685 # internal helpers
1686 #--------------------------------------------------------
1688
1689 episode_name_candidates.append(self._TCTRL_episode_summary.GetValue().strip())
1690 for candidate in episode_name_candidates:
1691 if candidate is None:
1692 continue
1693 epi_name = candidate.strip().replace('\r', '//').replace('\n', '//')
1694 break
1695
1696 dlg = wx.TextEntryDialog (
1697 parent = self,
1698 message = _('Enter a short working name for this new problem:'),
1699 caption = _('Creating a problem (episode) to save the notelet under ...'),
1700 defaultValue = epi_name,
1701 style = wx.OK | wx.CANCEL | wx.CENTRE
1702 )
1703 decision = dlg.ShowModal()
1704 if decision != wx.ID_OK:
1705 return None
1706
1707 epi_name = dlg.GetValue().strip()
1708 if epi_name == u'':
1709 gmGuiHelpers.gm_show_error(_('Cannot save a new problem without a name.'), _('saving progress note'))
1710 return None
1711
1712 # create episode
1713 new_episode = emr.add_episode(episode_name = epi_name[:45], pk_health_issue = None, is_open = True)
1714 new_episode['summary'] = self._TCTRL_episode_summary.GetValue().strip()
1715 new_episode.save()
1716
1717 if self.problem is not None:
1718 issue = emr.problem2issue(self.problem)
1719 if not gmEMRStructWidgets.move_episode_to_issue(episode = new_episode, target_issue = issue, save_to_backend = True):
1720 gmGuiHelpers.gm_show_warning (
1721 _(
1722 'The new episode:\n'
1723 '\n'
1724 ' "%s"\n'
1725 '\n'
1726 'will remain unassociated despite the editor\n'
1727 'having been invoked from the health issue:\n'
1728 '\n'
1729 ' "%s"'
1730 ) % (
1731 new_episode['description'],
1732 issue['description']
1733 ),
1734 _('saving progress note')
1735 )
1736
1737 return new_episode
1738 #--------------------------------------------------------
1739 # event handling
1740 #--------------------------------------------------------
1742 for field in self.soap_fields:
1743 wx_expando.EVT_ETC_LAYOUT_NEEDED(field, field.GetId(), self._on_expando_needs_layout)
1744 wx_expando.EVT_ETC_LAYOUT_NEEDED(self._TCTRL_episode_summary, self._TCTRL_episode_summary.GetId(), self._on_expando_needs_layout)
1745 #--------------------------------------------------------
1747 # need to tell ourselves to re-Layout to refresh scroll bars
1748
1749 # provoke adding scrollbar if needed
1750 #self.Fit() # works on Linux but not on Windows
1751 self.FitInside() # needed on Windows rather than self.Fit()
1752
1753 if self.HasScrollbar(wx.VERTICAL):
1754 # scroll panel to show cursor
1755 expando = self.FindWindowById(evt.GetId())
1756 y_expando = expando.GetPositionTuple()[1]
1757 h_expando = expando.GetSizeTuple()[1]
1758 line_cursor = expando.PositionToXY(expando.GetInsertionPoint())[1] + 1
1759 y_cursor = int(round((float(line_cursor) / expando.NumberOfLines) * h_expando))
1760 y_desired_visible = y_expando + y_cursor
1761
1762 y_view = self.ViewStart[1]
1763 h_view = self.GetClientSizeTuple()[1]
1764
1765 # print "expando:", y_expando, "->", h_expando, ", lines:", expando.NumberOfLines
1766 # print "cursor :", y_cursor, "at line", line_cursor, ", insertion point:", expando.GetInsertionPoint()
1767 # print "wanted :", y_desired_visible
1768 # print "view-y :", y_view
1769 # print "scroll2:", h_view
1770
1771 # expando starts before view
1772 if y_desired_visible < y_view:
1773 # print "need to scroll up"
1774 self.Scroll(0, y_desired_visible)
1775
1776 if y_desired_visible > h_view:
1777 # print "need to scroll down"
1778 self.Scroll(0, y_desired_visible)
1779 #--------------------------------------------------------
1780 # properties
1781 #--------------------------------------------------------
1783 soap_notes = []
1784
1785 tmp = self._TCTRL_Soap.GetValue().strip()
1786 if tmp != u'':
1787 soap_notes.append(['s', tmp])
1788
1789 tmp = self._TCTRL_sOap.GetValue().strip()
1790 if tmp != u'':
1791 soap_notes.append(['o', tmp])
1792
1793 tmp = self._TCTRL_soAp.GetValue().strip()
1794 if tmp != u'':
1795 soap_notes.append(['a', tmp])
1796
1797 tmp = self._TCTRL_soaP.GetValue().strip()
1798 if tmp != u'':
1799 soap_notes.append(['p', tmp])
1800
1801 return soap_notes
1802
1803 soap = property(_get_soap, lambda x:x)
1804 #--------------------------------------------------------
1806
1807 # soap fields
1808 for field in self.soap_fields:
1809 if field.GetValue().strip() != u'':
1810 return False
1811
1812 # summary
1813 summary = self._TCTRL_episode_summary.GetValue().strip()
1814 if self.problem is None:
1815 if summary != u'':
1816 return False
1817 elif self.problem['type'] == u'issue':
1818 if summary != u'':
1819 return False
1820 else:
1821 if self.problem['summary'] is None:
1822 if summary != u'':
1823 return False
1824 else:
1825 if summary != self.problem['summary'].strip():
1826 return False
1827
1828 # codes
1829 new_codes = self._PRW_episode_codes.GetData()
1830 if self.problem is None:
1831 if len(new_codes) > 0:
1832 return False
1833 elif self.problem['type'] == u'issue':
1834 if len(new_codes) > 0:
1835 return False
1836 else:
1837 old_code_pks = self.problem.generic_codes
1838 if len(old_code_pks) != len(new_codes):
1839 return False
1840 for code in new_codes:
1841 if code['data'] not in old_code_pks:
1842 return False
1843
1844 return True
1845
1846 empty = property(_get_empty, lambda x:x)
1847 #============================================================
1849
1851
1852 wx_expando.ExpandoTextCtrl.__init__(self, *args, **kwargs)
1853
1854 self.__keyword_separators = regex.compile("[!?'\".,:;)}\]\r\n\s\t]+")
1855
1856 self.__register_interests()
1857 #------------------------------------------------
1858 # fixup errors in platform expando.py
1859 #------------------------------------------------
1861
1862 if (wx.MAJOR_VERSION >= 2) and (wx.MINOR_VERSION > 8):
1863 return super(cSoapLineTextCtrl, self)._wrapLine(line, dc, width)
1864
1865 # THIS FIX LIFTED FROM TRUNK IN SVN:
1866 # Estimate where the control will wrap the lines and
1867 # return the count of extra lines needed.
1868 pte = dc.GetPartialTextExtents(line)
1869 width -= wx.SystemSettings.GetMetric(wx.SYS_VSCROLL_X)
1870 idx = 0
1871 start = 0
1872 count = 0
1873 spc = -1
1874 while idx < len(pte):
1875 if line[idx] == ' ':
1876 spc = idx
1877 if pte[idx] - start > width:
1878 # we've reached the max width, add a new line
1879 count += 1
1880 # did we see a space? if so restart the count at that pos
1881 if spc != -1:
1882 idx = spc + 1
1883 spc = -1
1884 if idx < len(pte):
1885 start = pte[idx]
1886 else:
1887 idx += 1
1888 return count
1889 #------------------------------------------------
1890 # event handling
1891 #------------------------------------------------
1893 #wx.EVT_KEY_DOWN (self, self.__on_key_down)
1894 #wx.EVT_KEY_UP (self, self.__OnKeyUp)
1895 wx.EVT_CHAR(self, self.__on_char)
1896 wx.EVT_SET_FOCUS(self, self.__on_focus)
1897 #--------------------------------------------------------
1901 #--------------------------------------------------------
1903 #wx.CallAfter(self._adjustCtrl)
1904 evt = wx.PyCommandEvent(wx_expando.wxEVT_ETC_LAYOUT_NEEDED, self.GetId())
1905 evt.SetEventObject(self)
1906 #evt.height = None
1907 #evt.numLines = None
1908 #evt.height = self.GetSize().height
1909 #evt.numLines = self.GetNumberOfLines()
1910 self.GetEventHandler().ProcessEvent(evt)
1911 #--------------------------------------------------------
1913 char = unichr(evt.GetUnicodeKey())
1914
1915 if self.LastPosition == 1:
1916 evt.Skip()
1917 return
1918
1919 explicit_expansion = False
1920 if evt.GetModifiers() == (wx.MOD_CMD | wx.MOD_ALT): # portable CTRL-ALT-...
1921 if evt.GetKeyCode() != 13:
1922 evt.Skip()
1923 return
1924 explicit_expansion = True
1925
1926 if not explicit_expansion:
1927 if self.__keyword_separators.match(char) is None:
1928 evt.Skip()
1929 return
1930
1931 caret_pos, line_no = self.PositionToXY(self.InsertionPoint)
1932 line = self.GetLineText(line_no)
1933 word = self.__keyword_separators.split(line[:caret_pos])[-1]
1934
1935 if (
1936 (not explicit_expansion)
1937 and
1938 (word != u'$$steffi') # Easter Egg ;-)
1939 and
1940 (word not in [ r[0] for r in gmPG2.get_text_expansion_keywords() ])
1941 ):
1942 evt.Skip()
1943 return
1944
1945 start = self.InsertionPoint - len(word)
1946 wx.CallAfter(self.replace_keyword_with_expansion, word, start, explicit_expansion)
1947
1948 evt.Skip()
1949 return
1950 #------------------------------------------------
1952
1953 if show_list:
1954 candidates = gmPG2.get_keyword_expansion_candidates(keyword = keyword)
1955 if len(candidates) == 0:
1956 return
1957 if len(candidates) == 1:
1958 keyword = candidates[0]
1959 else:
1960 keyword = gmListWidgets.get_choices_from_list (
1961 parent = self,
1962 msg = _(
1963 'Several macros match the keyword [%s].\n'
1964 '\n'
1965 'Please select the expansion you want to happen.'
1966 ) % keyword,
1967 caption = _('Selecting text macro'),
1968 choices = candidates,
1969 columns = [_('Keyword')],
1970 single_selection = True,
1971 can_return_empty = False
1972 )
1973 if keyword is None:
1974 return
1975
1976 expansion = gmPG2.expand_keyword(keyword = keyword)
1977
1978 if expansion is None:
1979 return
1980
1981 if expansion == u'':
1982 return
1983
1984 self.Replace (
1985 position,
1986 position + len(keyword),
1987 expansion
1988 )
1989
1990 self.SetInsertionPoint(position + len(expansion) + 1)
1991 self.ShowPosition(position + len(expansion) + 1)
1992
1993 return
1994 #============================================================
1995 # visual progress notes
1996 #============================================================
1998
1999 def is_valid(value):
2000
2001 if value is None:
2002 gmDispatcher.send (
2003 signal = 'statustext',
2004 msg = _('You need to actually set an editor.'),
2005 beep = True
2006 )
2007 return False, value
2008
2009 if value.strip() == u'':
2010 gmDispatcher.send (
2011 signal = 'statustext',
2012 msg = _('You need to actually set an editor.'),
2013 beep = True
2014 )
2015 return False, value
2016
2017 found, binary = gmShellAPI.detect_external_binary(value)
2018 if not found:
2019 gmDispatcher.send (
2020 signal = 'statustext',
2021 msg = _('The command [%s] is not found.') % value,
2022 beep = True
2023 )
2024 return True, value
2025
2026 return True, binary
2027 #------------------------------------------
2028 cmd = gmCfgWidgets.configure_string_option (
2029 message = _(
2030 'Enter the shell command with which to start\n'
2031 'the image editor for visual progress notes.\n'
2032 '\n'
2033 'Any "%(img)s" included with the arguments\n'
2034 'will be replaced by the file name of the\n'
2035 'note template.'
2036 ),
2037 option = u'external.tools.visual_soap_editor_cmd',
2038 bias = 'user',
2039 default_value = None,
2040 validator = is_valid
2041 )
2042
2043 return cmd
2044 #============================================================
2046 if parent is None:
2047 parent = wx.GetApp().GetTopWindow()
2048
2049 dlg = wx.FileDialog (
2050 parent = parent,
2051 message = _('Choose file to use as template for new visual progress note'),
2052 defaultDir = os.path.expanduser('~'),
2053 defaultFile = '',
2054 #wildcard = "%s (*)|*|%s (*.*)|*.*" % (_('all files'), _('all files (Win)')),
2055 style = wx.OPEN | wx.HIDE_READONLY | wx.FILE_MUST_EXIST
2056 )
2057 result = dlg.ShowModal()
2058
2059 if result == wx.ID_CANCEL:
2060 dlg.Destroy()
2061 return None
2062
2063 full_filename = dlg.GetPath()
2064 dlg.Hide()
2065 dlg.Destroy()
2066 return full_filename
2067 #------------------------------------------------------------
2069
2070 if parent is None:
2071 parent = wx.GetApp().GetTopWindow()
2072
2073 dlg = gmGuiHelpers.c3ButtonQuestionDlg (
2074 parent,
2075 -1,
2076 caption = _('Visual progress note source'),
2077 question = _('From which source do you want to pick the image template ?'),
2078 button_defs = [
2079 {'label': _('Database'), 'tooltip': _('List of templates in the database.'), 'default': True},
2080 {'label': _('File'), 'tooltip': _('Files in the filesystem.'), 'default': False},
2081 {'label': _('Device'), 'tooltip': _('Image capture devices (scanners, cameras, etc)'), 'default': False}
2082 ]
2083 )
2084 result = dlg.ShowModal()
2085 dlg.Destroy()
2086
2087 # 1) select from template
2088 if result == wx.ID_YES:
2089 _log.debug('visual progress note template from: database template')
2090 from Gnumed.wxpython import gmFormWidgets
2091 template = gmFormWidgets.manage_form_templates (
2092 parent = parent,
2093 template_types = [gmDocuments.DOCUMENT_TYPE_VISUAL_PROGRESS_NOTE],
2094 active_only = True
2095 )
2096 if template is None:
2097 return (None, None)
2098 filename = template.export_to_file()
2099 if filename is None:
2100 gmDispatcher.send(signal = u'statustext', msg = _('Cannot export visual progress note template for [%s].') % template['name_long'])
2101 return (None, None)
2102 return (filename, True)
2103
2104 # 2) select from disk file
2105 if result == wx.ID_NO:
2106 _log.debug('visual progress note template from: disk file')
2107 fname = select_file_as_visual_progress_note_template(parent = parent)
2108 if fname is None:
2109 return (None, None)
2110 # create a copy of the picked file -- don't modify the original
2111 ext = os.path.splitext(fname)[1]
2112 tmp_name = gmTools.get_unique_filename(suffix = ext)
2113 _log.debug('visual progress note from file: [%s] -> [%s]', fname, tmp_name)
2114 shutil.copy2(fname, tmp_name)
2115 return (tmp_name, False)
2116
2117 # 3) acquire from capture device
2118 if result == wx.ID_CANCEL:
2119 _log.debug('visual progress note template from: image capture device')
2120 fnames = gmDocumentWidgets.acquire_images_from_capture_device(device = None, calling_window = parent)
2121 if fnames is None:
2122 return (None, None)
2123 if len(fnames) == 0:
2124 return (None, None)
2125 return (fnames[0], False)
2126
2127 _log.debug('no visual progress note template source selected')
2128 return (None, None)
2129 #------------------------------------------------------------
2130 -def edit_visual_progress_note(filename=None, episode=None, discard_unmodified=False, doc_part=None, health_issue=None):
2131 """This assumes <filename> contains an image which can be handled by the configured image editor."""
2132
2133 if doc_part is not None:
2134 filename = doc_part.export_to_file()
2135 if filename is None:
2136 gmDispatcher.send(signal = u'statustext', msg = _('Cannot export visual progress note to file.'))
2137 return None
2138
2139 dbcfg = gmCfg.cCfgSQL()
2140 cmd = dbcfg.get2 (
2141 option = u'external.tools.visual_soap_editor_cmd',
2142 workplace = gmSurgery.gmCurrentPractice().active_workplace,
2143 bias = 'user'
2144 )
2145
2146 if cmd is None:
2147 gmDispatcher.send(signal = u'statustext', msg = _('Editor for visual progress note not configured.'), beep = False)
2148 cmd = configure_visual_progress_note_editor()
2149 if cmd is None:
2150 gmDispatcher.send(signal = u'statustext', msg = _('Editor for visual progress note not configured.'), beep = True)
2151 return None
2152
2153 if u'%(img)s' in cmd:
2154 cmd % {u'img': filename}
2155 else:
2156 cmd = u'%s %s' % (cmd, filename)
2157
2158 if discard_unmodified:
2159 original_stat = os.stat(filename)
2160 original_md5 = gmTools.file2md5(filename)
2161
2162 success = gmShellAPI.run_command_in_shell(cmd, blocking = True)
2163 if not success:
2164 gmGuiHelpers.gm_show_error (
2165 _(
2166 'There was a problem with running the editor\n'
2167 'for visual progress notes.\n'
2168 '\n'
2169 ' [%s]\n'
2170 '\n'
2171 ) % cmd,
2172 _('Editing visual progress note')
2173 )
2174 return None
2175
2176 try:
2177 open(filename, 'r').close()
2178 except StandardError:
2179 _log.exception('problem accessing visual progress note file [%s]', filename)
2180 gmGuiHelpers.gm_show_error (
2181 _(
2182 'There was a problem reading the visual\n'
2183 'progress note from the file:\n'
2184 '\n'
2185 ' [%s]\n'
2186 '\n'
2187 ) % filename,
2188 _('Saving visual progress note')
2189 )
2190 return None
2191
2192 if discard_unmodified:
2193 modified_stat = os.stat(filename)
2194 # same size ?
2195 if original_stat.st_size == modified_stat.st_size:
2196 modified_md5 = gmTools.file2md5(filename)
2197 # same hash ?
2198 if original_md5 == modified_md5:
2199 _log.debug('visual progress note (template) not modified')
2200 # ask user to decide
2201 msg = _(
2202 u'You either created a visual progress note from a template\n'
2203 u'in the database (rather than from a file on disk) or you\n'
2204 u'edited an existing visual progress note.\n'
2205 u'\n'
2206 u'The template/original was not modified at all, however.\n'
2207 u'\n'
2208 u'Do you still want to save the unmodified image as a\n'
2209 u'visual progress note into the EMR of the patient ?\n'
2210 )
2211 save_unmodified = gmGuiHelpers.gm_show_question (
2212 msg,
2213 _('Saving visual progress note')
2214 )
2215 if not save_unmodified:
2216 _log.debug('user discarded unmodified note')
2217 return
2218
2219 if doc_part is not None:
2220 doc_part.update_data_from_file(fname = filename)
2221 doc_part.set_reviewed(technically_abnormal = False, clinically_relevant = True)
2222 return None
2223
2224 if not isinstance(episode, gmEMRStructItems.cEpisode):
2225 if episode is None:
2226 episode = _('visual progress notes')
2227 pat = gmPerson.gmCurrentPatient()
2228 emr = pat.get_emr()
2229 episode = emr.add_episode(episode_name = episode.strip(), pk_health_issue = health_issue, is_open = False)
2230
2231 doc = gmDocumentWidgets.save_file_as_new_document (
2232 filename = filename,
2233 document_type = gmDocuments.DOCUMENT_TYPE_VISUAL_PROGRESS_NOTE,
2234 episode = episode,
2235 unlock_patient = True
2236 )
2237 doc.set_reviewed(technically_abnormal = False, clinically_relevant = True)
2238
2239 return doc
2240 #============================================================
2242 """Phrasewheel to allow selection of visual SOAP template."""
2243
2245
2246 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
2247
2248 query = u"""
2249 SELECT
2250 pk AS data,
2251 name_short AS list_label,
2252 name_sort AS field_label
2253 FROM
2254 ref.paperwork_templates
2255 WHERE
2256 fk_template_type = (SELECT pk FROM ref.form_types WHERE name = '%s') AND (
2257 name_long %%(fragment_condition)s
2258 OR
2259 name_short %%(fragment_condition)s
2260 )
2261 ORDER BY list_label
2262 LIMIT 15
2263 """ % gmDocuments.DOCUMENT_TYPE_VISUAL_PROGRESS_NOTE
2264
2265 mp = gmMatchProvider.cMatchProvider_SQL2(queries = [query])
2266 mp.setThresholds(2, 3, 5)
2267
2268 self.matcher = mp
2269 self.selection_only = True
2270 #--------------------------------------------------------
2272 if self.GetData() is None:
2273 return None
2274
2275 return gmForms.cFormTemplate(aPK_obj = self.GetData())
2276 #============================================================
2277 from Gnumed.wxGladeWidgets import wxgVisualSoapPresenterPnl
2278
2280
2282 wxgVisualSoapPresenterPnl.wxgVisualSoapPresenterPnl.__init__(self, *args, **kwargs)
2283 self._SZR_soap = self.GetSizer()
2284 self.__bitmaps = []
2285 #--------------------------------------------------------
2286 # external API
2287 #--------------------------------------------------------
2289
2290 self.clear()
2291 if document_folder is not None:
2292 soap_docs = document_folder.get_visual_progress_notes(episodes = episodes, encounter = encounter)
2293 if len(soap_docs) > 0:
2294 for soap_doc in soap_docs:
2295 parts = soap_doc.parts
2296 if len(parts) == 0:
2297 continue
2298 part = parts[0]
2299 fname = part.export_to_file()
2300 if fname is None:
2301 continue
2302
2303 # create bitmap
2304 img = gmGuiHelpers.file2scaled_image (
2305 filename = fname,
2306 height = 30
2307 )
2308 #bmp = wx.StaticBitmap(self, -1, img, style = wx.NO_BORDER)
2309 bmp = wx_genstatbmp.GenStaticBitmap(self, -1, img, style = wx.NO_BORDER)
2310
2311 # create tooltip
2312 img = gmGuiHelpers.file2scaled_image (
2313 filename = fname,
2314 height = 150
2315 )
2316 tip = agw_stt.SuperToolTip (
2317 u'',
2318 bodyImage = img,
2319 header = _('Created: %s') % part['date_generated'].strftime('%Y %B %d').decode(gmI18N.get_encoding()),
2320 footer = gmTools.coalesce(part['doc_comment'], u'').strip()
2321 )
2322 tip.SetTopGradientColor('white')
2323 tip.SetMiddleGradientColor('white')
2324 tip.SetBottomGradientColor('white')
2325 tip.SetTarget(bmp)
2326
2327 bmp.doc_part = part
2328 bmp.Bind(wx.EVT_LEFT_UP, self._on_bitmap_leftclicked)
2329 # FIXME: add context menu for Delete/Clone/Add/Configure
2330 self._SZR_soap.Add(bmp, 0, wx.LEFT | wx.RIGHT | wx.TOP | wx.BOTTOM | wx.EXPAND, 3)
2331 self.__bitmaps.append(bmp)
2332
2333 self.GetParent().Layout()
2334 #--------------------------------------------------------
2336 for child_idx in range(len(self._SZR_soap.GetChildren())):
2337 self._SZR_soap.Detach(child_idx)
2338 for bmp in self.__bitmaps:
2339 bmp.Destroy()
2340 self.__bitmaps = []
2341 #--------------------------------------------------------
2343 wx.CallAfter (
2344 edit_visual_progress_note,
2345 doc_part = evt.GetEventObject().doc_part,
2346 discard_unmodified = True
2347 )
2348 #============================================================
2349 #from Gnumed.wxGladeWidgets import wxgVisualSoapPnl
2350
2351 #class cVisualSoapPnl(wxgVisualSoapPnl.wxgVisualSoapPnl):
2352 #
2353 # def __init__(self, *args, **kwargs):
2354 #
2355 # wxgVisualSoapPnl.wxgVisualSoapPnl.__init__(self, *args, **kwargs)
2356 #
2357 # # dummy episode to hold images
2358 # self.default_episode_name = _('visual progress notes')
2359 # #--------------------------------------------------------
2360 # # external API
2361 # #--------------------------------------------------------
2362 # def clear(self):
2363 # self._PRW_template.SetText(value = u'', data = None)
2364 # self._LCTRL_visual_soaps.set_columns([_('Sketches')])
2365 # self._LCTRL_visual_soaps.set_string_items()
2366 #
2367 # self.show_image_and_metadata()
2368 # #--------------------------------------------------------
2369 # def refresh(self, patient=None, encounter=None):
2370 #
2371 # self.clear()
2372 #
2373 # if patient is None:
2374 # patient = gmPerson.gmCurrentPatient()
2375 #
2376 # if not patient.connected:
2377 # return
2378 #
2379 # emr = patient.get_emr()
2380 # if encounter is None:
2381 # encounter = emr.active_encounter
2382 #
2383 # folder = patient.get_document_folder()
2384 # soap_docs = folder.get_documents (
2385 # doc_type = gmDocuments.DOCUMENT_TYPE_VISUAL_PROGRESS_NOTE,
2386 # encounter = encounter['pk_encounter']
2387 # )
2388 #
2389 # if len(soap_docs) == 0:
2390 # self._BTN_delete.Enable(False)
2391 # return
2392 #
2393 # self._LCTRL_visual_soaps.set_string_items ([
2394 # u'%s%s%s' % (
2395 # gmTools.coalesce(sd['comment'], u'', u'%s\n'),
2396 # gmTools.coalesce(sd['ext_ref'], u'', u'%s\n'),
2397 # sd['episode']
2398 # ) for sd in soap_docs
2399 # ])
2400 # self._LCTRL_visual_soaps.set_data(soap_docs)
2401 #
2402 # self._BTN_delete.Enable(True)
2403 # #--------------------------------------------------------
2404 # def show_image_and_metadata(self, doc=None):
2405 #
2406 # if doc is None:
2407 # self._IMG_soap.SetBitmap(wx.NullBitmap)
2408 # self._PRW_episode.SetText()
2409 # #self._PRW_comment.SetText(value = u'', data = None)
2410 # self._PRW_comment.SetValue(u'')
2411 # return
2412 #
2413 # parts = doc.parts
2414 # if len(parts) == 0:
2415 # gmDispatcher.send(signal = u'statustext', msg = _('No images in visual progress note.'))
2416 # return
2417 #
2418 # fname = parts[0].export_to_file()
2419 # if fname is None:
2420 # gmDispatcher.send(signal = u'statustext', msg = _('Cannot export visual progress note to file.'))
2421 # return
2422 #
2423 # img_data = None
2424 # rescaled_width = 300
2425 # try:
2426 # img_data = wx.Image(fname, wx.BITMAP_TYPE_ANY)
2427 # current_width = img_data.GetWidth()
2428 # current_height = img_data.GetHeight()
2429 # rescaled_height = (rescaled_width * current_height) / current_width
2430 # img_data.Rescale(rescaled_width, rescaled_height, quality = wx.IMAGE_QUALITY_HIGH) # w, h
2431 # bmp_data = wx.BitmapFromImage(img_data)
2432 # except:
2433 # _log.exception('cannot load visual progress note from [%s]', fname)
2434 # gmDispatcher.send(signal = u'statustext', msg = _('Cannot load visual progress note from [%s].') % fname)
2435 # del img_data
2436 # return
2437 #
2438 # del img_data
2439 # self._IMG_soap.SetBitmap(bmp_data)
2440 #
2441 # self._PRW_episode.SetText(value = doc['episode'], data = doc['pk_episode'])
2442 # if doc['comment'] is not None:
2443 # self._PRW_comment.SetValue(doc['comment'].strip())
2444 # #--------------------------------------------------------
2445 # # event handlers
2446 # #--------------------------------------------------------
2447 # def _on_visual_soap_selected(self, event):
2448 #
2449 # doc = self._LCTRL_visual_soaps.get_selected_item_data(only_one = True)
2450 # self.show_image_and_metadata(doc = doc)
2451 # if doc is None:
2452 # return
2453 #
2454 # self._BTN_delete.Enable(True)
2455 # #--------------------------------------------------------
2456 # def _on_visual_soap_deselected(self, event):
2457 # self._BTN_delete.Enable(False)
2458 # #--------------------------------------------------------
2459 # def _on_visual_soap_activated(self, event):
2460 #
2461 # doc = self._LCTRL_visual_soaps.get_selected_item_data(only_one = True)
2462 # if doc is None:
2463 # self.show_image_and_metadata()
2464 # return
2465 #
2466 # parts = doc.parts
2467 # if len(parts) == 0:
2468 # gmDispatcher.send(signal = u'statustext', msg = _('No images in visual progress note.'))
2469 # return
2470 #
2471 # edit_visual_progress_note(doc_part = parts[0], discard_unmodified = True)
2472 # self.show_image_and_metadata(doc = doc)
2473 #
2474 # self._BTN_delete.Enable(True)
2475 # #--------------------------------------------------------
2476 # def _on_from_template_button_pressed(self, event):
2477 #
2478 # template = self._PRW_template.GetData(as_instance = True)
2479 # if template is None:
2480 # return
2481 #
2482 # filename = template.export_to_file()
2483 # if filename is None:
2484 # gmDispatcher.send(signal = u'statustext', msg = _('Cannot export visual progress note template for [%s].') % template['name_long'])
2485 # return
2486 #
2487 # episode = self._PRW_episode.GetData(as_instance = True)
2488 # if episode is None:
2489 # episode = self._PRW_episode.GetValue().strip()
2490 # if episode == u'':
2491 # episode = self.default_episode_name
2492 #
2493 # # do not store note if not modified -- change if users complain
2494 # doc = edit_visual_progress_note(filename = filename, episode = episode, discard_unmodified = True)
2495 # if doc is None:
2496 # return
2497 #
2498 # if self._PRW_comment.GetValue().strip() == u'':
2499 # doc['comment'] = template['instance_type']
2500 # else:
2501 # doc['comment'] = self._PRW_comment.GetValue().strip()
2502 #
2503 # doc.save()
2504 # self.show_image_and_metadata(doc = doc)
2505 # #--------------------------------------------------------
2506 # def _on_from_file_button_pressed(self, event):
2507 #
2508 # dlg = wx.FileDialog (
2509 # parent = self,
2510 # message = _('Choose a visual progress note template file'),
2511 # defaultDir = os.path.expanduser('~'),
2512 # defaultFile = '',
2513 # #wildcard = "%s (*)|*|%s (*.*)|*.*" % (_('all files'), _('all files (Win)')),
2514 # style = wx.OPEN | wx.HIDE_READONLY | wx.FILE_MUST_EXIST
2515 # )
2516 # result = dlg.ShowModal()
2517 # if result == wx.ID_CANCEL:
2518 # dlg.Destroy()
2519 # return
2520 #
2521 # full_filename = dlg.GetPath()
2522 # dlg.Hide()
2523 # dlg.Destroy()
2524 #
2525 # # create a copy of the picked file -- don't modify the original
2526 # ext = os.path.splitext(full_filename)[1]
2527 # tmp_name = gmTools.get_unique_filename(suffix = ext)
2528 # _log.debug('visual progress note from file: [%s] -> [%s]', full_filename, tmp_name)
2529 # shutil.copy2(full_filename, tmp_name)
2530 #
2531 # episode = self._PRW_episode.GetData(as_instance = True)
2532 # if episode is None:
2533 # episode = self._PRW_episode.GetValue().strip()
2534 # if episode == u'':
2535 # episode = self.default_episode_name
2536 #
2537 # # always store note even if unmodified as we
2538 # # may simply want to store a clinical photograph
2539 # doc = edit_visual_progress_note(filename = tmp_name, episode = episode, discard_unmodified = False)
2540 # if self._PRW_comment.GetValue().strip() == u'':
2541 # # use filename as default comment (w/o extension)
2542 # doc['comment'] = os.path.splitext(os.path.split(full_filename)[1])[0]
2543 # else:
2544 # doc['comment'] = self._PRW_comment.GetValue().strip()
2545 # doc.save()
2546 # self.show_image_and_metadata(doc = doc)
2547 #
2548 # try:
2549 # os.remove(tmp_name)
2550 # except StandardError:
2551 # _log.exception('cannot remove [%s]', tmp_name)
2552 #
2553 # remove_original = gmGuiHelpers.gm_show_question (
2554 # _(
2555 # 'Do you want to delete the original file\n'
2556 # '\n'
2557 # ' [%s]\n'
2558 # '\n'
2559 # 'from your computer ?'
2560 # ) % full_filename,
2561 # _('Saving visual progress note ...')
2562 # )
2563 # if remove_original:
2564 # try:
2565 # os.remove(full_filename)
2566 # except StandardError:
2567 # _log.exception('cannot remove [%s]', full_filename)
2568 # #--------------------------------------------------------
2569 # def _on_delete_button_pressed(self, event):
2570 #
2571 # doc = self._LCTRL_visual_soaps.get_selected_item_data(only_one = True)
2572 # if doc is None:
2573 # self.show_image_and_metadata()
2574 # return
2575 #
2576 # delete_it = gmGuiHelpers.gm_show_question (
2577 # aMessage = _('Are you sure you want to delete the visual progress note ?'),
2578 # aTitle = _('Deleting visual progress note')
2579 # )
2580 # if delete_it is True:
2581 # gmDocuments.delete_document (
2582 # document_id = doc['pk_doc'],
2583 # encounter_id = doc['pk_encounter']
2584 # )
2585 # self.show_image_and_metadata()
2586 #============================================================
2587 # main
2588 #------------------------------------------------------------
2589 if __name__ == '__main__':
2590
2591 if len(sys.argv) < 2:
2592 sys.exit()
2593
2594 if sys.argv[1] != 'test':
2595 sys.exit()
2596
2597 gmI18N.activate_locale()
2598 gmI18N.install_domain(domain = 'gnumed')
2599
2600 #----------------------------------------
2602 pat = gmPersonSearch.ask_for_patient()
2603 gmPatSearchWidgets.set_active_patient(patient = pat)
2604 app = wx.PyWidgetTester(size = (200, 200))
2605 sels = select_narrative_from_episodes()
2606 print "selected:"
2607 for sel in sels:
2608 print sel
2609 #----------------------------------------
2611 pat = gmPersonSearch.ask_for_patient()
2612 application = wx.PyWidgetTester(size=(800,500))
2613 soap_input = cSoapNoteExpandoEditAreaPnl(application.frame, -1)
2614 application.frame.Show(True)
2615 application.MainLoop()
2616 #----------------------------------------
2618 patient = gmPersonSearch.ask_for_patient()
2619 if patient is None:
2620 print "No patient. Exiting gracefully..."
2621 return
2622 gmPatSearchWidgets.set_active_patient(patient=patient)
2623
2624 application = wx.PyWidgetTester(size=(800,500))
2625 soap_input = cSoapPluginPnl(application.frame, -1)
2626 application.frame.Show(True)
2627 soap_input._schedule_data_reget()
2628 application.MainLoop()
2629 #----------------------------------------
2630 #test_select_narrative_from_episodes()
2631 test_cSoapNoteExpandoEditAreaPnl()
2632 #test_cSoapPluginPnl()
2633
2634 #============================================================
2635
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Thu Jul 28 03:57:33 2011 | http://epydoc.sourceforge.net |