| Trees | Indices | Help |
|
|---|
|
|
1 """GNUmed narrative handling widgets."""
2 #================================================================
3 # $Source: /cvsroot/gnumed/gnumed/gnumed/client/wxpython/gmNarrativeWidgets.py,v $
4 # $Id: gmNarrativeWidgets.py,v 1.46 2010/02/07 15:16:32 ncq Exp $
5 __version__ = "$Revision: 1.46 $"
6 __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
7
8 import sys, logging, os, os.path, time, re as regex
9
10
11 import wx
12 import wx.lib.expando as wxexpando
13
14
15 if __name__ == '__main__':
16 sys.path.insert(0, '../../')
17 from Gnumed.pycommon import gmI18N, gmDispatcher, gmTools, gmDateTime, gmPG2, gmCfg
18 from Gnumed.business import gmPerson, gmEMRStructItems, gmClinNarrative, gmSurgery
19 from Gnumed.exporters import gmPatientExporter
20 from Gnumed.wxpython import gmListWidgets, gmEMRStructWidgets, gmRegetMixin, gmGuiHelpers, gmPatSearchWidgets
21 from Gnumed.wxGladeWidgets import wxgMoveNarrativeDlg, wxgSoapNoteExpandoEditAreaPnl
22
23
24 _log = logging.getLogger('gm.ui')
25 _log.info(__version__)
26 #============================================================
27 # narrative related widgets/functions
28 #------------------------------------------------------------
29 -def move_progress_notes_to_another_encounter(parent=None, encounters=None, episodes=None, patient=None, move_all=False):
30
31 # sanity checks
32 if patient is None:
33 patient = gmPerson.gmCurrentPatient()
34
35 if not patient.connected:
36 gmDispatcher.send(signal = 'statustext', msg = _('Cannot move progress notes. No active patient.'))
37 return False
38
39 if parent is None:
40 parent = wx.GetApp().GetTopWindow()
41
42 emr = patient.get_emr()
43
44 if encounters is None:
45 encs = emr.get_encounters(episodes = episodes)
46 encounters = gmEMRStructWidgets.select_encounters (
47 parent = parent,
48 patient = patient,
49 single_selection = False,
50 encounters = encs
51 )
52
53 notes = emr.get_clin_narrative (
54 encounters = encounters,
55 episodes = episodes
56 )
57
58 # which narrative
59 if move_all:
60 selected_narr = notes
61 else:
62 selected_narr = gmListWidgets.get_choices_from_list (
63 parent = parent,
64 caption = _('Moving progress notes between encounters ...'),
65 single_selection = False,
66 can_return_empty = True,
67 data = notes,
68 msg = _('\n Select the progress notes to move from the list !\n\n'),
69 columns = [_('when'), _('who'), _('type'), _('entry')],
70 choices = [
71 [ narr['date'].strftime('%x %H:%M'),
72 narr['provider'],
73 gmClinNarrative.soap_cat2l10n[narr['soap_cat']],
74 narr['narrative'].replace('\n', '/').replace('\r', '/')
75 ] for narr in notes
76 ]
77 )
78
79 if not selected_narr:
80 return True
81
82 # which encounter to move to
83 enc2move2 = gmEMRStructWidgets.select_encounters (
84 parent = parent,
85 patient = patient,
86 single_selection = True
87 )
88
89 if not enc2move2:
90 return True
91
92 for narr in selected_narr:
93 narr['pk_encounter'] = enc2move2['pk_encounter']
94 narr.save()
95
96 return True
97 #------------------------------------------------------------
99
100 # sanity checks
101 if patient is None:
102 patient = gmPerson.gmCurrentPatient()
103
104 if not patient.connected:
105 gmDispatcher.send(signal = 'statustext', msg = _('Cannot edit progress notes. No active patient.'))
106 return False
107
108 if parent is None:
109 parent = wx.GetApp().GetTopWindow()
110
111 emr = patient.get_emr()
112 #--------------------------
113 def delete(item):
114 if item is None:
115 return False
116 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
117 parent,
118 -1,
119 caption = _('Deleting progress note'),
120 question = _(
121 'Are you positively sure you want to delete this\n'
122 'progress note from the medical record ?\n'
123 '\n'
124 'Note that even if you chose to delete the entry it will\n'
125 'still be (invisibly) kept in the audit trail to protect\n'
126 'you from litigation because physical deletion is known\n'
127 'to be unlawful in some jurisdictions.\n'
128 ),
129 button_defs = (
130 {'label': _('Delete'), 'tooltip': _('Yes, delete the progress note.'), 'default': False},
131 {'label': _('Cancel'), 'tooltip': _('No, do NOT delete the progress note.'), 'default': True}
132 )
133 )
134 decision = dlg.ShowModal()
135
136 if decision != wx.ID_YES:
137 return False
138
139 gmClinNarrative.delete_clin_narrative(narrative = item['pk_narrative'])
140 return True
141 #--------------------------
142 def edit(item):
143 if item is None:
144 return False
145
146 dlg = gmGuiHelpers.cMultilineTextEntryDlg (
147 parent,
148 -1,
149 title = _('Editing progress note'),
150 msg = _('This is the original progress note:'),
151 data = item.format(left_margin = u' ', fancy = True),
152 text = item['narrative']
153 )
154 decision = dlg.ShowModal()
155
156 if decision != wx.ID_SAVE:
157 return False
158
159 val = dlg.value
160 dlg.Destroy()
161 if val.strip() == u'':
162 return False
163
164 item['narrative'] = val
165 item.save_payload()
166
167 return True
168 #--------------------------
169 def refresh(lctrl):
170 notes = emr.get_clin_narrative (
171 encounters = encounters,
172 episodes = episodes,
173 providers = [ gmPerson.gmCurrentProvider()['short_alias'] ]
174 )
175 lctrl.set_string_items(items = [
176 [ narr['date'].strftime('%x %H:%M'),
177 gmClinNarrative.soap_cat2l10n[narr['soap_cat']],
178 narr['narrative'].replace('\n', '/').replace('\r', '/')
179 ] for narr in notes
180 ])
181 lctrl.set_data(data = notes)
182 #--------------------------
183
184 gmListWidgets.get_choices_from_list (
185 parent = parent,
186 caption = _('Managing progress notes'),
187 msg = _(
188 '\n'
189 ' This list shows the progress notes by %s.\n'
190 '\n'
191 ) % gmPerson.gmCurrentProvider()['short_alias'],
192 columns = [_('when'), _('type'), _('entry')],
193 single_selection = True,
194 can_return_empty = False,
195 edit_callback = edit,
196 delete_callback = delete,
197 refresh_callback = refresh,
198 ignore_OK_button = True
199 )
200 #------------------------------------------------------------
202
203 if parent is None:
204 parent = wx.GetApp().GetTopWindow()
205
206 searcher = wx.TextEntryDialog (
207 parent = parent,
208 message = _('Enter (regex) term to search for across all EMRs:'),
209 caption = _('Text search across all EMRs'),
210 style = wx.OK | wx.CANCEL | wx.CENTRE
211 )
212 result = searcher.ShowModal()
213
214 if result != wx.ID_OK:
215 return
216
217 wx.BeginBusyCursor()
218 term = searcher.GetValue()
219 searcher.Destroy()
220 results = gmClinNarrative.search_text_across_emrs(search_term = term)
221 wx.EndBusyCursor()
222
223 if len(results) == 0:
224 gmGuiHelpers.gm_show_info (
225 _(
226 'Nothing found for search term:\n'
227 ' "%s"'
228 ) % term,
229 _('Search results')
230 )
231 return
232
233 items = [ [gmPerson.cIdentity(aPK_obj = r['pk_patient'])['description_gender'], r['narrative'], r['src_table']] for r in results ]
234
235 selected_patient = gmListWidgets.get_choices_from_list (
236 parent = parent,
237 caption = _('Search results for %s') % term,
238 choices = items,
239 columns = [_('Patient'), _('Match'), _('Match location')],
240 data = [ r['pk_patient'] for r in results ],
241 single_selection = True,
242 can_return_empty = False
243 )
244
245 if selected_patient is None:
246 return
247
248 wx.CallAfter(gmPatSearchWidgets.set_active_patient, patient = gmPerson.cIdentity(aPK_obj = selected_patient))
249 #------------------------------------------------------------
251
252 # sanity checks
253 if patient is None:
254 patient = gmPerson.gmCurrentPatient()
255
256 if not patient.connected:
257 gmDispatcher.send(signal = 'statustext', msg = _('Cannot search EMR. No active patient.'))
258 return False
259
260 if parent is None:
261 parent = wx.GetApp().GetTopWindow()
262
263 searcher = wx.TextEntryDialog (
264 parent = parent,
265 message = _('Enter search term:'),
266 caption = _('Text search of entire EMR of active patient'),
267 style = wx.OK | wx.CANCEL | wx.CENTRE
268 )
269 result = searcher.ShowModal()
270
271 if result != wx.ID_OK:
272 searcher.Destroy()
273 return False
274
275 wx.BeginBusyCursor()
276 val = searcher.GetValue()
277 searcher.Destroy()
278 emr = patient.get_emr()
279 rows = emr.search_narrative_simple(val)
280 wx.EndBusyCursor()
281
282 if len(rows) == 0:
283 gmGuiHelpers.gm_show_info (
284 _(
285 'Nothing found for search term:\n'
286 ' "%s"'
287 ) % val,
288 _('Search results')
289 )
290 return True
291
292 txt = u''
293 for row in rows:
294 txt += u'%s: %s\n' % (
295 row['soap_cat'],
296 row['narrative']
297 )
298
299 txt += u' %s: %s - %s %s\n' % (
300 _('Encounter'),
301 row['encounter_started'].strftime('%x %H:%M'),
302 row['encounter_ended'].strftime('%H:%M'),
303 row['encounter_type']
304 )
305 txt += u' %s: %s\n' % (
306 _('Episode'),
307 row['episode']
308 )
309 txt += u' %s: %s\n\n' % (
310 _('Health issue'),
311 row['health_issue']
312 )
313
314 msg = _(
315 'Search term was: "%s"\n'
316 '\n'
317 'Search results:\n\n'
318 '%s\n'
319 ) % (val, txt)
320
321 dlg = wx.MessageDialog (
322 parent = parent,
323 message = msg,
324 caption = _('Search results for %s') % val,
325 style = wx.OK | wx.STAY_ON_TOP
326 )
327 dlg.ShowModal()
328 dlg.Destroy()
329
330 return True
331 #------------------------------------------------------------
333
334 # sanity checks
335 pat = gmPerson.gmCurrentPatient()
336 if not pat.connected:
337 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export EMR for Medistar. No active patient.'))
338 return False
339
340 if encounter is None:
341 encounter = pat.get_emr().active_encounter
342
343 if parent is None:
344 parent = wx.GetApp().GetTopWindow()
345
346 # get file name
347 aWildcard = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files"))
348 # FIXME: make configurable
349 aDefDir = os.path.abspath(os.path.expanduser(os.path.join('~', 'gnumed','export')))
350 # FIXME: make configurable
351 fname = '%s-%s-%s-%s-%s.txt' % (
352 'Medistar-MD',
353 time.strftime('%Y-%m-%d',time.localtime()),
354 pat['lastnames'].replace(' ', '-'),
355 pat['firstnames'].replace(' ', '_'),
356 pat.get_formatted_dob(format = '%Y-%m-%d')
357 )
358 dlg = wx.FileDialog (
359 parent = parent,
360 message = _("Save EMR extract for MEDISTAR import as..."),
361 defaultDir = aDefDir,
362 defaultFile = fname,
363 wildcard = aWildcard,
364 style = wx.SAVE
365 )
366 choice = dlg.ShowModal()
367 fname = dlg.GetPath()
368 dlg.Destroy()
369 if choice != wx.ID_OK:
370 return False
371
372 wx.BeginBusyCursor()
373 _log.debug('exporting encounter for medistar import to [%s]', fname)
374 exporter = gmPatientExporter.cMedistarSOAPExporter()
375 successful, fname = exporter.export_to_file (
376 filename = fname,
377 encounter = encounter,
378 soap_cats = u'soap',
379 export_to_import_file = True
380 )
381 if not successful:
382 gmGuiHelpers.gm_show_error (
383 _('Error exporting progress notes for MEDISTAR import.'),
384 _('MEDISTAR progress notes export')
385 )
386 wx.EndBusyCursor()
387 return False
388
389 gmDispatcher.send(signal = 'statustext', msg = _('Successfully exported progress notes into file [%s] for Medistar import.') % fname, beep=False)
390
391 wx.EndBusyCursor()
392 return True
393 #------------------------------------------------------------
395 """soap_cats needs to be a list"""
396
397 pat = gmPerson.gmCurrentPatient()
398 emr = pat.get_emr()
399
400 if parent is None:
401 parent = wx.GetApp().GetTopWindow()
402
403 selected_soap = {}
404 selected_issue_pks = []
405 selected_episode_pks = []
406 selected_narrative_pks = []
407
408 while 1:
409 # 1) select health issues to select episodes from
410 all_issues = emr.get_health_issues()
411 all_issues.insert(0, gmEMRStructItems.get_dummy_health_issue())
412 dlg = gmEMRStructWidgets.cIssueListSelectorDlg (
413 parent = parent,
414 id = -1,
415 issues = all_issues,
416 msg = _('\n In the list below mark the health issues you want to report on.\n')
417 )
418 selection_idxs = []
419 for idx in range(len(all_issues)):
420 if all_issues[idx]['pk_health_issue'] in selected_issue_pks:
421 selection_idxs.append(idx)
422 if len(selection_idxs) != 0:
423 dlg.set_selections(selections = selection_idxs)
424 btn_pressed = dlg.ShowModal()
425 selected_issues = dlg.get_selected_item_data()
426 dlg.Destroy()
427
428 if btn_pressed == wx.ID_CANCEL:
429 return selected_soap.values()
430
431 selected_issue_pks = [ i['pk_health_issue'] for i in selected_issues ]
432
433 while 1:
434 # 2) select episodes to select items from
435 all_epis = emr.get_episodes(issues = selected_issue_pks)
436
437 if len(all_epis) == 0:
438 gmDispatcher.send(signal = 'statustext', msg = _('No episodes recorded for the health issues selected.'))
439 break
440
441 dlg = gmEMRStructWidgets.cEpisodeListSelectorDlg (
442 parent = parent,
443 id = -1,
444 episodes = all_epis,
445 msg = _(
446 '\n These are the episodes known for the health issues just selected.\n\n'
447 ' Now, mark the the episodes you want to report on.\n'
448 )
449 )
450 selection_idxs = []
451 for idx in range(len(all_epis)):
452 if all_epis[idx]['pk_episode'] in selected_episode_pks:
453 selection_idxs.append(idx)
454 if len(selection_idxs) != 0:
455 dlg.set_selections(selections = selection_idxs)
456 btn_pressed = dlg.ShowModal()
457 selected_epis = dlg.get_selected_item_data()
458 dlg.Destroy()
459
460 if btn_pressed == wx.ID_CANCEL:
461 break
462
463 selected_episode_pks = [ i['pk_episode'] for i in selected_epis ]
464
465 # 3) select narrative corresponding to the above constraints
466 all_narr = emr.get_clin_narrative(episodes = selected_episode_pks, soap_cats = soap_cats)
467
468 if len(all_narr) == 0:
469 gmDispatcher.send(signal = 'statustext', msg = _('No narrative available for selected episodes.'))
470 continue
471
472 dlg = cNarrativeListSelectorDlg (
473 parent = parent,
474 id = -1,
475 narrative = all_narr,
476 msg = _(
477 '\n This is the narrative (type %s) for the chosen episodes.\n\n'
478 ' Now, mark the entries you want to include in your report.\n'
479 ) % u'/'.join([ gmClinNarrative.soap_cat2l10n[cat] for cat in gmTools.coalesce(soap_cats, list(u'soap')) ])
480 )
481 selection_idxs = []
482 for idx in range(len(all_narr)):
483 if all_narr[idx]['pk_narrative'] in selected_narrative_pks:
484 selection_idxs.append(idx)
485 if len(selection_idxs) != 0:
486 dlg.set_selections(selections = selection_idxs)
487 btn_pressed = dlg.ShowModal()
488 selected_narr = dlg.get_selected_item_data()
489 dlg.Destroy()
490
491 if btn_pressed == wx.ID_CANCEL:
492 continue
493
494 selected_narrative_pks = [ i['pk_narrative'] for i in selected_narr ]
495 for narr in selected_narr:
496 selected_soap[narr['pk_narrative']] = narr
497 #------------------------------------------------------------
499
501
502 narrative = kwargs['narrative']
503 del kwargs['narrative']
504
505 gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs)
506
507 self.SetTitle(_('Select the narrative you are interested in ...'))
508 # FIXME: add epi/issue
509 self._LCTRL_items.set_columns([_('when'), _('who'), _('type'), _('entry')]) #, _('Episode'), u'', _('Health Issue')])
510 # FIXME: date used should be date of encounter, not date_modified
511 self._LCTRL_items.set_string_items (
512 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 ]
513 )
514 self._LCTRL_items.set_column_widths()
515 self._LCTRL_items.set_data(data = narrative)
516 #------------------------------------------------------------
518
520
521 self.encounter = kwargs['encounter']
522 self.source_episode = kwargs['episode']
523 del kwargs['encounter']
524 del kwargs['episode']
525
526 wxgMoveNarrativeDlg.wxgMoveNarrativeDlg.__init__(self, *args, **kwargs)
527
528 self.LBL_source_episode.SetLabel(u'%s%s' % (self.source_episode['description'], gmTools.coalesce(self.source_episode['health_issue'], u'', u' (%s)')))
529 self.LBL_encounter.SetLabel('%s: %s %s - %s' % (
530 self.encounter['started'].strftime('%x').decode(gmI18N.get_encoding()),
531 self.encounter['l10n_type'],
532 self.encounter['started'].strftime('%H:%M'),
533 self.encounter['last_affirmed'].strftime('%H:%M')
534 ))
535 pat = gmPerson.gmCurrentPatient()
536 emr = pat.get_emr()
537 narr = emr.get_clin_narrative(episodes=[self.source_episode['pk_episode']], encounters=[self.encounter['pk_encounter']])
538 if len(narr) == 0:
539 narr = [{'narrative': _('There is no narrative for this episode in this encounter.')}]
540 self.LBL_narrative.SetLabel(u'\n'.join([n['narrative'] for n in narr]))
541
542 #------------------------------------------------------------
564 #============================================================
565 from Gnumed.wxGladeWidgets import wxgSoapPluginPnl
566
568 """A panel for in-context editing of progress notes.
569
570 Expects to be used as a notebook page.
571
572 Left hand side:
573 - problem list (health issues and active episodes)
574 - hints area
575
576 Right hand side:
577 - previous notes
578 - notebook with progress note editors
579 - encounter details fields
580
581 Listens to patient change signals, thus acts on the current patient.
582 """
584
585 wxgSoapPluginPnl.wxgSoapPluginPnl.__init__(self, *args, **kwargs)
586 gmRegetMixin.cRegetOnPaintMixin.__init__(self)
587
588 self.__pat = gmPerson.gmCurrentPatient()
589 self.__init_ui()
590 self.__reset_ui_content()
591
592 self.__register_interests()
593 #--------------------------------------------------------
594 # public API
595 #--------------------------------------------------------
597
598 if not self.__encounter_valid_for_save():
599 return False
600
601 emr = self.__pat.get_emr()
602 enc = emr.active_encounter
603
604 enc['pk_type'] = self._PRW_encounter_type.GetData()
605 enc['started'] = self._PRW_encounter_start.GetData().get_pydt()
606 enc['last_affirmed'] = self._PRW_encounter_end.GetData().get_pydt()
607 rfe = self._TCTRL_rfe.GetValue().strip()
608 if len(rfe) == 0:
609 enc['reason_for_encounter'] = None
610 else:
611 enc['reason_for_encounter'] = rfe
612 aoe = self._TCTRL_aoe.GetValue().strip()
613 if len(aoe) == 0:
614 enc['assessment_of_encounter'] = None
615 else:
616 enc['assessment_of_encounter'] = aoe
617
618 enc.save_payload()
619
620 return True
621 #--------------------------------------------------------
622 # internal helpers
623 #--------------------------------------------------------
625 self._LCTRL_active_problems.set_columns([_('Last'), _('Problem'), _('Health issue')])
626 self._LCTRL_active_problems.set_string_items()
627
628 self._splitter_main.SetSashGravity(0.5)
629 self._splitter_left.SetSashGravity(0.5)
630 self._splitter_right.SetSashGravity(1.0)
631
632 splitter_size = self._splitter_main.GetSizeTuple()[0]
633 self._splitter_main.SetSashPosition(splitter_size * 3 / 10, True)
634
635 splitter_size = self._splitter_left.GetSizeTuple()[1]
636 self._splitter_left.SetSashPosition(splitter_size * 6 / 20, True)
637
638 splitter_size = self._splitter_right.GetSizeTuple()[1]
639 self._splitter_right.SetSashPosition(splitter_size * 15 / 20, True)
640
641 self._NB_soap_editors.DeleteAllPages()
642 #--------------------------------------------------------
644 """
645 Clear all information from input panel
646 """
647 self._LCTRL_active_problems.set_string_items()
648 self._lbl_hints.SetLabel(u'')
649 self._TCTRL_recent_notes.SetValue(u'')
650 self._NB_soap_editors.DeleteAllPages()
651 self._NB_soap_editors.add_editor()
652 self._PRW_encounter_type.SetText(suppress_smarts = True)
653 self._PRW_encounter_start.SetText(suppress_smarts = True)
654 self._PRW_encounter_end.SetText(suppress_smarts = True)
655 self._TCTRL_rfe.SetValue(u'')
656 self._TCTRL_aoe.SetValue(u'')
657 #--------------------------------------------------------
659 """Update health problems list.
660 """
661
662 self._LCTRL_active_problems.set_string_items()
663
664 emr = self.__pat.get_emr()
665 problems = emr.get_problems (
666 include_closed_episodes = self._CHBOX_show_closed_episodes.IsChecked(),
667 include_irrelevant_issues = self._CHBOX_irrelevant_issues.IsChecked()
668 )
669
670 list_items = []
671 active_problems = []
672 for problem in problems:
673 if not problem['problem_active']:
674 if not problem['is_potential_problem']:
675 continue
676
677 active_problems.append(problem)
678
679 if problem['type'] == 'issue':
680 issue = emr.problem2issue(problem)
681 last_encounter = emr.get_last_encounter(issue_id = issue['pk_health_issue'])
682 if last_encounter is None:
683 last = issue['modified_when'].strftime('%m/%Y')
684 else:
685 last = last_encounter['last_affirmed'].strftime('%m/%Y')
686
687 list_items.append([last, problem['problem'], gmTools.u_left_arrow])
688
689 elif problem['type'] == 'episode':
690 epi = emr.problem2episode(problem)
691 last_encounter = emr.get_last_encounter(episode_id = epi['pk_episode'])
692 if last_encounter is None:
693 last = epi['episode_modified_when'].strftime('%m/%Y')
694 else:
695 last = last_encounter['last_affirmed'].strftime('%m/%Y')
696
697 list_items.append ([
698 last,
699 problem['problem'],
700 gmTools.coalesce(initial = epi['health_issue'], instead = gmTools.u_diameter)
701 ])
702
703 self._LCTRL_active_problems.set_string_items(items = list_items)
704 self._LCTRL_active_problems.set_column_widths()
705 self._LCTRL_active_problems.set_data(data = active_problems)
706
707 showing_potential_problems = (
708 self._CHBOX_show_closed_episodes.IsChecked()
709 or
710 self._CHBOX_irrelevant_issues.IsChecked()
711 )
712 if showing_potential_problems:
713 self._SZR_problem_list_staticbox.SetLabel(_('%s (active+potential) problems') % len(list_items))
714 else:
715 self._SZR_problem_list_staticbox.SetLabel(_('%s active problems') % len(list_items))
716
717 return True
718 #--------------------------------------------------------
720 """This refreshes the recent-notes part."""
721
722 if problem is None:
723 soap = u''
724 caption = u'<?>'
725
726 elif problem['type'] == u'issue':
727 emr = self.__pat.get_emr()
728 soap = u''
729 caption = problem['problem'][:35]
730
731 prev_enc = emr.get_last_but_one_encounter(issue_id = problem['pk_health_issue'])
732 if prev_enc is not None:
733 soap += prev_enc.format (
734 with_soap = True,
735 with_docs = False,
736 with_tests = False,
737 patient = self.__pat,
738 issues = [ problem['pk_health_issue'] ],
739 fancy_header = False
740 )
741
742 tmp = emr.active_encounter.format_soap (
743 soap_cats = 'soap',
744 emr = emr,
745 issues = [ problem['pk_health_issue'] ],
746 )
747 if len(tmp) > 0:
748 soap += _('Current encounter:') + u'\n'
749 soap += u'\n'.join(tmp) + u'\n'
750
751 elif problem['type'] == u'episode':
752 emr = self.__pat.get_emr()
753 soap = u''
754 caption = problem['problem'][:35]
755
756 prev_enc = emr.get_last_but_one_encounter(episode_id = problem['pk_episode'])
757 if prev_enc is None:
758 if problem['pk_health_issue'] is not None:
759 prev_enc = emr.get_last_but_one_encounter(episode_id = problem['pk_health_issue'])
760 if prev_enc is not None:
761 soap += prev_enc.format (
762 with_soap = True,
763 with_docs = False,
764 with_tests = False,
765 patient = self.__pat,
766 issues = [ problem['pk_health_issue'] ],
767 fancy_header = False
768 )
769 else:
770 soap += prev_enc.format (
771 episodes = [ problem['pk_episode'] ],
772 with_soap = True,
773 with_docs = False,
774 with_tests = False,
775 patient = self.__pat,
776 fancy_header = False
777 )
778
779 tmp = emr.active_encounter.format_soap (
780 soap_cats = 'soap',
781 emr = emr,
782 issues = [ problem['pk_health_issue'] ],
783 )
784 if len(tmp) > 0:
785 soap += _('Current encounter:') + u'\n'
786 soap += u'\n'.join(tmp) + u'\n'
787
788 else:
789 soap = u''
790 caption = u'<?>'
791
792 self._TCTRL_recent_notes.SetValue(soap)
793 self._TCTRL_recent_notes.ShowPosition(self._TCTRL_recent_notes.GetLastPosition())
794 self._SZR_recent_notes_staticbox.SetLabel(_('Most recent notes on %s%s%s') % (
795 gmTools.u_left_double_angle_quote,
796 caption,
797 gmTools.u_right_double_angle_quote
798 ))
799
800 self._TCTRL_recent_notes.Refresh()
801
802 return True
803 #--------------------------------------------------------
805 """Update encounter fields.
806 """
807 emr = self.__pat.get_emr()
808 enc = emr.active_encounter
809 self._PRW_encounter_type.SetText(value = enc['l10n_type'], data = enc['pk_type'])
810
811 fts = gmDateTime.cFuzzyTimestamp (
812 timestamp = enc['started'],
813 accuracy = gmDateTime.acc_minutes
814 )
815 self._PRW_encounter_start.SetText(fts.format_accurately(), data=fts)
816
817 fts = gmDateTime.cFuzzyTimestamp (
818 timestamp = enc['last_affirmed'],
819 accuracy = gmDateTime.acc_minutes
820 )
821 self._PRW_encounter_end.SetText(fts.format_accurately(), data=fts)
822
823 self._TCTRL_rfe.SetValue(gmTools.coalesce(enc['reason_for_encounter'], u''))
824 self._TCTRL_aoe.SetValue(gmTools.coalesce(enc['assessment_of_encounter'], u''))
825
826 self._PRW_encounter_type.Refresh()
827 self._PRW_encounter_start.Refresh()
828 self._PRW_encounter_end.Refresh()
829 self._TCTRL_rfe.Refresh()
830 self._TCTRL_aoe.Refresh()
831 #--------------------------------------------------------
833 """Assumes that the field data is valid."""
834
835 emr = self.__pat.get_emr()
836 enc = emr.active_encounter
837
838 data = {
839 'pk_type': self._PRW_encounter_type.GetData(),
840 'reason_for_encounter': gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u''),
841 'assessment_of_encounter': gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u''),
842 'pk_location': enc['pk_location']
843 }
844
845 if self._PRW_encounter_start.GetData() is None:
846 data['started'] = None
847 else:
848 data['started'] = self._PRW_encounter_start.GetData().get_pydt()
849
850 if self._PRW_encounter_end.GetData() is None:
851 data['last_affirmed'] = None
852 else:
853 data['last_affirmed'] = self._PRW_encounter_end.GetData().get_pydt()
854
855 return enc.same_payload(another_object = data)
856 #--------------------------------------------------------
858
859 found_error = False
860
861 if self._PRW_encounter_type.GetData() is None:
862 found_error = True
863 msg = _('Cannot save encounter: missing type.')
864
865 if self._PRW_encounter_start.GetData() is None:
866 found_error = True
867 msg = _('Cannot save encounter: missing start time.')
868
869 if self._PRW_encounter_end.GetData() is None:
870 found_error = True
871 msg = _('Cannot save encounter: missing end time.')
872
873 if found_error:
874 gmDispatcher.send(signal = 'statustext', msg = msg, beep = True)
875 return False
876
877 return True
878 #--------------------------------------------------------
879 # event handling
880 #--------------------------------------------------------
882 """Configure enabled event signals."""
883 # client internal signals
884 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection)
885 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
886 gmDispatcher.connect(signal = u'episode_mod_db', receiver = self._on_episode_issue_mod_db)
887 gmDispatcher.connect(signal = u'health_issue_mod_db', receiver = self._on_episode_issue_mod_db)
888 gmDispatcher.connect(signal = u'current_encounter_modified', receiver = self._on_current_encounter_modified)
889 gmDispatcher.connect(signal = u'current_encounter_switched', receiver = self._on_current_encounter_modified)
890
891 # synchronous signals
892 self.__pat.register_pre_selection_callback(callback = self._pre_selection_callback)
893 gmDispatcher.send(signal = u'register_pre_exit_callback', callback = self._pre_exit_callback)
894 #--------------------------------------------------------
896 """Another patient is about to be activated.
897
898 Patient change will not proceed before this returns True.
899 """
900 # don't worry about the encounter here - it will be offered
901 # for editing higher up if anything was saved to the EMR
902 if not self.__pat.connected:
903 return True
904 return self._NB_soap_editors.warn_on_unsaved_soap()
905 #--------------------------------------------------------
907 """The client is about to be shut down.
908
909 Shutdown will not proceed before this returns.
910 """
911 if not self.__pat.connected:
912 return True
913
914 # if self.__encounter_modified():
915 # do_save_enc = gmGuiHelpers.gm_show_question (
916 # aMessage = _(
917 # 'You have modified the details\n'
918 # 'of the current encounter.\n'
919 # '\n'
920 # 'Do you want to save those changes ?'
921 # ),
922 # aTitle = _('Starting new encounter')
923 # )
924 # if do_save_enc:
925 # if not self.save_encounter():
926 # gmDispatcher.send(signal = u'statustext', msg = _('Error saving current encounter.'), beep = True)
927
928 emr = self.__pat.get_emr()
929 if not self._NB_soap_editors.save_all_editors(emr = emr, rfe = self._TCTRL_rfe.GetValue().strip(), aoe = self._TCTRL_aoe.GetValue().strip()):
930 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save all editors. Some were kept open.'), beep = True)
931 return True
932 #--------------------------------------------------------
935 #--------------------------------------------------------
938 #--------------------------------------------------------
941 #--------------------------------------------------------
944 #--------------------------------------------------------
947 #--------------------------------------------------------
951 #--------------------------------------------------------
953 """Show related note at the bottom."""
954 emr = self.__pat.get_emr()
955 self.__refresh_recent_notes (
956 problem = self._LCTRL_active_problems.get_selected_item_data(only_one = True)
957 )
958 #--------------------------------------------------------
960 """Open progress note editor for this problem.
961 """
962 problem = self._LCTRL_active_problems.get_selected_item_data(only_one = True)
963 if problem is None:
964 return True
965
966 dbcfg = gmCfg.cCfgSQL()
967 allow_duplicate_editors = bool(dbcfg.get2 (
968 option = u'horstspace.soap_editor.allow_same_episode_multiple_times',
969 workplace = gmSurgery.gmCurrentPractice().active_workplace,
970 bias = u'user',
971 default = False
972 ))
973 if self._NB_soap_editors.add_editor(problem = problem, allow_same_problem = allow_duplicate_editors):
974 return True
975
976 gmGuiHelpers.gm_show_error (
977 aMessage = _(
978 'Cannot open progress note editor for\n\n'
979 '[%s].\n\n'
980 ) % problem['problem'],
981 aTitle = _('opening progress note editor')
982 )
983 event.Skip()
984 return False
985 #--------------------------------------------------------
989 #--------------------------------------------------------
993 #--------------------------------------------------------
997 #--------------------------------------------------------
1004 #--------------------------------------------------------
1008 #--------------------------------------------------------
1017 #--------------------------------------------------------
1041 #--------------------------------------------------------
1044 #--------------------------------------------------------
1047 #--------------------------------------------------------
1048 # reget mixin API
1049 #--------------------------------------------------------
1054 #============================================================
1056 """A notebook holding panels with progress note editors.
1057
1058 There can be one or several progress note editor panel
1059 for each episode being worked on. The editor class in
1060 each panel is configurable.
1061
1062 There will always be one open editor.
1063 """
1065
1066 kwargs['style'] = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER
1067
1068 wx.Notebook.__init__(self, *args, **kwargs)
1069 #--------------------------------------------------------
1070 # public API
1071 #--------------------------------------------------------
1073 """Add a progress note editor page.
1074
1075 The way <allow_same_problem> is currently used in callers
1076 it only applies to unassociated episodes.
1077 """
1078 problem_to_add = problem
1079
1080 # determine label
1081 if problem_to_add is None:
1082 label = _('new problem')
1083 else:
1084 # normalize problem type
1085 if isinstance(problem_to_add, gmEMRStructItems.cEpisode):
1086 problem_to_add = gmEMRStructItems.episode2problem(episode = problem_to_add)
1087
1088 elif isinstance(problem_to_add, gmEMRStructItems.cHealthIssue):
1089 problem_to_add = gmEMRStructItems.health_issue2problem(episode = problem_to_add)
1090
1091 if not isinstance(problem_to_add, gmEMRStructItems.cProblem):
1092 raise TypeError('cannot open progress note editor for [%s]' % problem_to_add)
1093
1094 label = problem_to_add['problem']
1095 # FIXME: configure maximum length
1096 if len(label) > 23:
1097 label = label[:21] + gmTools.u_ellipsis
1098
1099 # new unassociated problem or dupes allowed
1100 if (problem_to_add is None) or allow_same_problem:
1101 new_page = cSoapNoteExpandoEditAreaPnl(parent = self, id = -1, problem = problem_to_add)
1102 result = self.AddPage (
1103 page = new_page,
1104 text = label,
1105 select = True
1106 )
1107 return result
1108
1109 # real problem, no dupes allowed
1110 # - raise existing editor
1111 for page_idx in range(self.GetPageCount()):
1112 page = self.GetPage(page_idx)
1113
1114 # editor is for unassociated new problem
1115 if page.problem is None:
1116 continue
1117
1118 # editor is for episode
1119 if page.problem['type'] == 'episode':
1120 if page.problem['pk_episode'] == problem_to_add['pk_episode']:
1121 self.SetSelection(page_idx)
1122 gmDispatcher.send(signal = u'statustext', msg = u'Raising existing editor.', beep = True)
1123 return True
1124 continue
1125
1126 # editor is for health issue
1127 if page.problem['type'] == 'issue':
1128 if page.problem['pk_health_issue'] == problem_to_add['pk_health_issue']:
1129 self.SetSelection(page_idx)
1130 gmDispatcher.send(signal = u'statustext', msg = u'Raising existing editor.', beep = True)
1131 return True
1132 continue
1133
1134 # - or add new editor
1135 new_page = cSoapNoteExpandoEditAreaPnl(parent = self, id = -1, problem = problem_to_add)
1136 result = self.AddPage (
1137 page = new_page,
1138 text = label,
1139 select = True
1140 )
1141
1142 return result
1143 #--------------------------------------------------------
1145
1146 page_idx = self.GetSelection()
1147 page = self.GetPage(page_idx)
1148
1149 if not page.empty:
1150 really_discard = gmGuiHelpers.gm_show_question (
1151 _('Are you sure you really want to\n'
1152 'discard this progress note ?\n'
1153 ),
1154 _('Discarding progress note')
1155 )
1156 if really_discard is False:
1157 return
1158
1159 self.DeletePage(page_idx)
1160
1161 # always keep one unassociated editor open
1162 if self.GetPageCount() == 0:
1163 self.add_editor()
1164 #--------------------------------------------------------
1166
1167 page_idx = self.GetSelection()
1168 page = self.GetPage(page_idx)
1169
1170 if not page.save(emr = emr, rfe = rfe, aoe = aoe):
1171 return
1172
1173 self.DeletePage(page_idx)
1174
1175 # always keep one unassociated editor open
1176 if self.GetPageCount() == 0:
1177 self.add_editor()
1178 #--------------------------------------------------------
1180 for page_idx in range(self.GetPageCount()):
1181 page = self.GetPage(page_idx)
1182 if page.empty:
1183 continue
1184
1185 gmGuiHelpers.gm_show_warning (
1186 _('There are unsaved progress notes !\n'),
1187 _('Unsaved progress notes')
1188 )
1189 return False
1190
1191 return True
1192 #--------------------------------------------------------
1194
1195 all_closed = True
1196 for page_idx in range(self.GetPageCount()):
1197 page = self.GetPage(page_idx)
1198 if page.save(emr = emr, rfe = rfe, aoe = aoe):
1199 self.DeletePage(page_idx)
1200 else:
1201 all_closed = False
1202
1203 # always keep one unassociated editor open
1204 if self.GetPageCount() == 0:
1205 self.add_editor()
1206
1207 return (all_closed is True)
1208 #--------------------------------------------------------
1213 #--------------------------------------------------------
1218 #============================================================
1219 -class cSoapNoteExpandoEditAreaPnl(wxgSoapNoteExpandoEditAreaPnl.wxgSoapNoteExpandoEditAreaPnl):
1220
1222
1223 try:
1224 self.problem = kwargs['problem']
1225 del kwargs['problem']
1226 except KeyError:
1227 self.problem = None
1228
1229 wxgSoapNoteExpandoEditAreaPnl.wxgSoapNoteExpandoEditAreaPnl.__init__(self, *args, **kwargs)
1230
1231 self.fields = [
1232 self._TCTRL_Soap,
1233 self._TCTRL_sOap,
1234 self._TCTRL_soAp,
1235 self._TCTRL_soaP
1236 ]
1237
1238 self.__register_interests()
1239 #--------------------------------------------------------
1243 #--------------------------------------------------------
1245
1246 if self.empty:
1247 return True
1248
1249 # new unassociated episode
1250 if (self.problem is None) or (self.problem['type'] == 'issue'):
1251
1252 epi_name = gmTools.coalesce (
1253 aoe,
1254 gmTools.coalesce (
1255 rfe,
1256 u''
1257 )
1258 ).strip().replace('\r', '//').replace('\n', '//')
1259
1260 dlg = wx.TextEntryDialog (
1261 parent = self,
1262 message = _('Enter a short working name for this new problem:'),
1263 caption = _('Creating a problem (episode) to save the notelet under ...'),
1264 defaultValue = epi_name,
1265 style = wx.OK | wx.CANCEL | wx.CENTRE
1266 )
1267 decision = dlg.ShowModal()
1268 if decision != wx.ID_OK:
1269 return False
1270
1271 epi_name = dlg.GetValue().strip()
1272 if epi_name == u'':
1273 gmGuiHelpers.gm_show_error(_('Cannot save a new problem without a name.'), _('saving progress note'))
1274 return False
1275
1276 # create episode
1277 new_episode = emr.add_episode(episode_name = epi_name[:45], pk_health_issue = None, is_open = True)
1278
1279 if self.problem is not None:
1280 issue = emr.problem2issue(self.problem)
1281 if not gmEMRStructWidgets.move_episode_to_issue(episode = new_episode, target_issue = issue, save_to_backend = True):
1282 gmGuiHelpers.gm_show_warning (
1283 _(
1284 'The new episode:\n'
1285 '\n'
1286 ' "%s"\n'
1287 '\n'
1288 'will remain unassociated despite the editor\n'
1289 'having been invoked from the health issue:\n'
1290 '\n'
1291 ' "%s"'
1292 ) % (
1293 new_episode['description'],
1294 issue['description']
1295 ),
1296 _('saving progress note')
1297 )
1298
1299 epi_id = new_episode['pk_episode']
1300 else:
1301 epi_id = self.problem['pk_episode']
1302
1303 emr.add_notes(notes = self.soap, episode = epi_id)
1304
1305 return True
1306 #--------------------------------------------------------
1307 # event handling
1308 #--------------------------------------------------------
1310 for field in self.fields:
1311 wxexpando.EVT_ETC_LAYOUT_NEEDED(field, field.GetId(), self._on_expando_needs_layout)
1312 #--------------------------------------------------------
1314 # need to tell ourselves to re-Layout to refresh scroll bars
1315
1316 # provoke adding scrollbar if needed
1317 self.Fit()
1318
1319 if self.HasScrollbar(wx.VERTICAL):
1320 # scroll panel to show cursor
1321 expando = self.FindWindowById(evt.GetId())
1322 y_expando = expando.GetPositionTuple()[1]
1323 h_expando = expando.GetSizeTuple()[1]
1324 line_cursor = expando.PositionToXY(expando.GetInsertionPoint())[1] + 1
1325 y_cursor = int(round((float(line_cursor) / expando.NumberOfLines) * h_expando))
1326 y_desired_visible = y_expando + y_cursor
1327
1328 y_view = self.ViewStart[1]
1329 h_view = self.GetClientSizeTuple()[1]
1330
1331 # print "expando:", y_expando, "->", h_expando, ", lines:", expando.NumberOfLines
1332 # print "cursor :", y_cursor, "at line", line_cursor, ", insertion point:", expando.GetInsertionPoint()
1333 # print "wanted :", y_desired_visible
1334 # print "view-y :", y_view
1335 # print "scroll2:", h_view
1336
1337 # expando starts before view
1338 if y_desired_visible < y_view:
1339 # print "need to scroll up"
1340 self.Scroll(0, y_desired_visible)
1341
1342 if y_desired_visible > h_view:
1343 # print "need to scroll down"
1344 self.Scroll(0, y_desired_visible)
1345 #--------------------------------------------------------
1346 # properties
1347 #--------------------------------------------------------
1349 note = []
1350
1351 tmp = self._TCTRL_Soap.GetValue().strip()
1352 if tmp != u'':
1353 note.append(['s', tmp])
1354
1355 tmp = self._TCTRL_sOap.GetValue().strip()
1356 if tmp != u'':
1357 note.append(['o', tmp])
1358
1359 tmp = self._TCTRL_soAp.GetValue().strip()
1360 if tmp != u'':
1361 note.append(['a', tmp])
1362
1363 tmp = self._TCTRL_soaP.GetValue().strip()
1364 if tmp != u'':
1365 note.append(['p', tmp])
1366
1367 return note
1368
1369 soap = property(_get_soap, lambda x:x)
1370 #--------------------------------------------------------
1372 for field in self.fields:
1373 if field.GetValue().strip() != u'':
1374 return False
1375 return True
1376
1377 empty = property(_get_empty, lambda x:x)
1378 #============================================================
1380
1382
1383 wxexpando.ExpandoTextCtrl.__init__(self, *args, **kwargs)
1384
1385 self.__keyword_separators = regex.compile("[!?'\".,:;)}\]\r\n\s\t]+")
1386
1387 self.__register_interests()
1388 #------------------------------------------------
1389 # event handling
1390 #------------------------------------------------
1392 #wx.EVT_KEY_DOWN (self, self.__on_key_down)
1393 #wx.EVT_KEY_UP (self, self.__OnKeyUp)
1394 wx.EVT_CHAR(self, self.__on_char)
1395 wx.EVT_SET_FOCUS(self, self.__on_focus)
1396 #--------------------------------------------------------
1400 #--------------------------------------------------------
1402 evt = wx.PyCommandEvent(wxexpando.wxEVT_ETC_LAYOUT_NEEDED, self.GetId())
1403 evt.SetEventObject(self)
1404 evt.height = None
1405 evt.numLines = None
1406 self.GetEventHandler().ProcessEvent(evt)
1407 #--------------------------------------------------------
1409 char = unichr(evt.GetUnicodeKey())
1410
1411 if self.LastPosition == 1:
1412 evt.Skip()
1413 return
1414
1415 explicit_expansion = False
1416 if evt.GetModifiers() == (wx.MOD_CMD | wx.MOD_ALT): # portable CTRL-ALT-...
1417 if evt.GetKeyCode() != 13:
1418 evt.Skip()
1419 return
1420 explicit_expansion = True
1421
1422 if not explicit_expansion:
1423 if self.__keyword_separators.match(char) is None:
1424 evt.Skip()
1425 return
1426
1427 caret_pos, line_no = self.PositionToXY(self.InsertionPoint)
1428 line = self.GetLineText(line_no)
1429 word = self.__keyword_separators.split(line[:caret_pos])[-1]
1430
1431 if (
1432 (not explicit_expansion)
1433 and
1434 (word != u'$$steffi') # Easter Egg ;-)
1435 and
1436 (word not in [ r[0] for r in gmPG2.get_text_expansion_keywords() ])
1437 ):
1438 evt.Skip()
1439 return
1440
1441 start = self.InsertionPoint - len(word)
1442 wx.CallAfter(self.replace_keyword_with_expansion, word, start, explicit_expansion)
1443
1444 evt.Skip()
1445 return
1446 #------------------------------------------------
1448
1449 if show_list:
1450 candidates = gmPG2.get_keyword_expansion_candidates(keyword = keyword)
1451 if len(candidates) == 0:
1452 return
1453 if len(candidates) == 1:
1454 keyword = candidates[0]
1455 else:
1456 keyword = gmListWidgets.get_choices_from_list (
1457 parent = self,
1458 msg = _(
1459 'Several macros match the keyword [%s].\n'
1460 '\n'
1461 'Please select the expansion you want to happen.'
1462 ) % keyword,
1463 caption = _('Selecting text macro'),
1464 choices = candidates,
1465 columns = [_('Keyword')],
1466 single_selection = True,
1467 can_return_empty = False
1468 )
1469 if keyword is None:
1470 return
1471
1472 expansion = gmPG2.expand_keyword(keyword = keyword)
1473
1474 if expansion is None:
1475 return
1476
1477 if expansion == u'':
1478 return
1479
1480 self.Replace (
1481 position,
1482 position + len(keyword),
1483 expansion
1484 )
1485
1486 self.SetInsertionPoint(position + len(expansion) + 1)
1487 self.ShowPosition(position + len(expansion) + 1)
1488
1489 return
1490 #============================================================
1491 # main
1492 #------------------------------------------------------------
1493 if __name__ == '__main__':
1494
1495 gmI18N.activate_locale()
1496 gmI18N.install_domain(domain = 'gnumed')
1497
1498 #----------------------------------------
1500 pat = gmPerson.ask_for_patient()
1501 gmPatSearchWidgets.set_active_patient(patient = pat)
1502 app = wx.PyWidgetTester(size = (200, 200))
1503 sels = select_narrative_from_episodes()
1504 print "selected:"
1505 for sel in sels:
1506 print sel
1507 #----------------------------------------
1509 pat = gmPerson.ask_for_patient()
1510 application = wx.PyWidgetTester(size=(800,500))
1511 soap_input = cSoapNoteExpandoEditAreaPnl(application.frame, -1)
1512 application.frame.Show(True)
1513 application.MainLoop()
1514 #----------------------------------------
1516 patient = gmPerson.ask_for_patient()
1517 if patient is None:
1518 print "No patient. Exiting gracefully..."
1519 return
1520 gmPatSearchWidgets.set_active_patient(patient=patient)
1521
1522 application = wx.PyWidgetTester(size=(800,500))
1523 soap_input = cSoapPluginPnl(application.frame, -1)
1524 application.frame.Show(True)
1525 soap_input._schedule_data_reget()
1526 application.MainLoop()
1527 #----------------------------------------
1528 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'):
1529 #test_select_narrative_from_episodes()
1530 test_cSoapNoteExpandoEditAreaPnl()
1531 #test_cSoapPluginPnl()
1532
1533 #============================================================
1534 # $Log: gmNarrativeWidgets.py,v $
1535 # Revision 1.46 2010/02/07 15:16:32 ncq
1536 # - support selectable, scrollabe "old" progress note in progress note editor
1537 # - support ignoring the OK button in the progress note selector
1538 #
1539 # Revision 1.45 2010/01/11 19:51:09 ncq
1540 # - cleanup
1541 # - warn-on-unsaved-soap and use in syn pre-selection callback
1542 #
1543 # Revision 1.44 2009/11/28 18:32:50 ncq
1544 # - finalize showing potential problems in problem list, too, and adjust box label
1545 #
1546 # Revision 1.43 2009/11/24 21:03:41 ncq
1547 # - display problems based on checkbox selection
1548 # - set recent notes label based on problem selection
1549 #
1550 # Revision 1.42 2009/11/15 01:10:09 ncq
1551 # - enhance move-progress-notes-to-another-encounter
1552 # - use enhanced new encounter start
1553 #
1554 # Revision 1.41 2009/11/13 21:08:24 ncq
1555 # - enable cross-EMR narrative search to activate matching
1556 # patient from result list
1557 #
1558 # Revision 1.40 2009/11/08 20:49:49 ncq
1559 # - implement search across all EMRs
1560 #
1561 # Revision 1.39 2009/09/13 18:45:25 ncq
1562 # - no more get-active-encounter()
1563 #
1564 # Revision 1.38 2009/09/01 22:36:59 ncq
1565 # - wx-CallAfter on start-new-encounter
1566 #
1567 # Revision 1.37 2009/07/23 16:41:13 ncq
1568 # - cleanup
1569 #
1570 # Revision 1.36 2009/07/02 20:55:48 ncq
1571 # - properly honor allow-same-problem on non-new editors only
1572 #
1573 # Revision 1.35 2009/07/01 17:09:06 ncq
1574 # - refresh fields explicitly when active encounter is switched
1575 #
1576 # Revision 1.34 2009/06/29 15:09:45 ncq
1577 # - inform user when nothing is found during search
1578 # - refresh recent-notes on problem single-click selection
1579 # but NOT anymore on editor changes
1580 #
1581 # Revision 1.33 2009/06/22 09:28:20 ncq
1582 # - improved wording as per list
1583 #
1584 # Revision 1.32 2009/06/20 22:39:27 ncq
1585 # - improved wording as per list discussion
1586 #
1587 # Revision 1.31 2009/06/11 12:37:25 ncq
1588 # - much simplified initial setup of list ctrls
1589 #
1590 # Revision 1.30 2009/06/04 16:33:13 ncq
1591 # - adjust to dob-less person
1592 # - use set-active-patient from pat-search-widgets
1593 #
1594 # Revision 1.29 2009/05/13 13:12:41 ncq
1595 # - cleanup
1596 #
1597 # Revision 1.28 2009/05/13 12:22:05 ncq
1598 # - move_progress_notes_to_another_encounter
1599 #
1600 # Revision 1.27 2009/04/16 12:51:02 ncq
1601 # - edit_* -> manage_progress_notes as it can delete now, too,
1602 # after being converted to using get_choices_from_list
1603 #
1604 # Revision 1.26 2009/04/13 10:56:21 ncq
1605 # - use same_payload on encounter to detect changes
1606 # - detect when current encounter is switched, not just modified
1607 #
1608 # Revision 1.25 2009/03/10 14:23:56 ncq
1609 # - comment
1610 #
1611 # Revision 1.24 2009/03/02 18:57:52 ncq
1612 # - make expando soap editor scroll to cursor when needed
1613 #
1614 # Revision 1.23 2009/02/24 13:22:06 ncq
1615 # - fix saving edited progress notes
1616 #
1617 # Revision 1.22 2009/02/17 08:07:37 ncq
1618 # - support explicit macro expansion
1619 #
1620 # Revision 1.21 2009/01/21 22:37:14 ncq
1621 # - do not fail save_all_editors() where not appropriate
1622 #
1623 # Revision 1.20 2009/01/21 18:53:57 ncq
1624 # - fix save_all_editors and call it with proper args
1625 #
1626 # Revision 1.19 2009/01/03 17:29:01 ncq
1627 # - listen on new current_encounter_modified
1628 # - detecting encounter field changes at exit/patient doesn't properly work
1629 # - refresh recent notes where needed
1630 #
1631 # Revision 1.18 2009/01/02 11:41:16 ncq
1632 # - improved event handling
1633 #
1634 # Revision 1.17 2008/12/27 15:50:41 ncq
1635 # - almost finish implementing soap saving
1636 #
1637 # Revision 1.16 2008/12/26 22:35:44 ncq
1638 # - edit_progress_notes
1639 # - implement most of new soap plugin functionality
1640 #
1641 # Revision 1.15 2008/11/24 11:10:29 ncq
1642 # - cleanup
1643 #
1644 # Revision 1.14 2008/11/23 12:47:02 ncq
1645 # - preset splitter ratios and gravity
1646 # - cleanup
1647 # - reorder recent notes with most recent on bottom as per list
1648 #
1649 # Revision 1.13 2008/11/20 20:35:50 ncq
1650 # - new soap plugin widgets
1651 #
1652 # Revision 1.12 2008/10/26 01:21:52 ncq
1653 # - factor out searching EMR for narrative
1654 #
1655 # Revision 1.11 2008/10/22 12:21:57 ncq
1656 # - use %x in strftime where appropriate
1657 #
1658 # Revision 1.10 2008/10/12 16:26:20 ncq
1659 # - consultation -> encounter
1660 #
1661 # Revision 1.9 2008/09/02 19:01:12 ncq
1662 # - adjust to clin health_issue fk_patient drop and related changes
1663 #
1664 # Revision 1.8 2008/07/28 15:46:05 ncq
1665 # - export_narrative_for_medistar_import
1666 #
1667 # Revision 1.7 2008/03/05 22:30:14 ncq
1668 # - new style logging
1669 #
1670 # Revision 1.6 2007/12/03 20:45:28 ncq
1671 # - improved docs
1672 #
1673 # Revision 1.5 2007/09/10 12:36:02 ncq
1674 # - improved wording in narrative selector at SOAP level
1675 #
1676 # Revision 1.4 2007/09/09 19:21:04 ncq
1677 # - get top level wx.App window if parent is None
1678 # - support filtering by soap_cats
1679 #
1680 # Revision 1.3 2007/09/07 22:45:58 ncq
1681 # - much improved select_narrative_from_episodes()
1682 #
1683 # Revision 1.2 2007/09/07 10:59:17 ncq
1684 # - greatly improve select_narrative_by_episodes
1685 # - remember selections
1686 # - properly levelled looping
1687 # - fix test suite
1688 #
1689 # Revision 1.1 2007/08/29 22:06:15 ncq
1690 # - factored out narrative widgets
1691 #
1692 #
1693
| Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Tue Feb 9 04:02:23 2010 | http://epydoc.sourceforge.net |