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