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