1 """GNUmed patient EMR tree browser.
2 """
3
4 __version__ = "$Revision: 1.111 $"
5 __author__ = "cfmoro1976@yahoo.es, sjtan@swiftdsl.com.au, Karsten.Hilbert@gmx.net"
6 __license__ = "GPL"
7
8
9 import sys, os.path, StringIO, codecs, logging
10
11
12
13 import wx
14
15
16
17 from Gnumed.pycommon import gmI18N, gmDispatcher, gmExceptions, gmTools
18 from Gnumed.exporters import gmPatientExporter
19 from Gnumed.business import gmEMRStructItems, gmPerson, gmSOAPimporter, gmPersonSearch
20 from Gnumed.wxpython import gmGuiHelpers
21 from Gnumed.wxpython import gmEMRStructWidgets
22 from Gnumed.wxpython import gmSOAPWidgets
23 from Gnumed.wxpython import gmAllergyWidgets
24 from Gnumed.wxpython import gmDemographicsWidgets
25 from Gnumed.wxpython import gmNarrativeWidgets
26 from Gnumed.wxpython import gmPatSearchWidgets
27 from Gnumed.wxpython import gmVaccWidgets
28 from Gnumed.wxpython import gmFamilyHistoryWidgets
29
30
31 _log = logging.getLogger('gm.ui')
32 _log.info(__version__)
33
34
36 """
37 Dump the patient's EMR from GUI client
38 @param parent - The parent widget
39 @type parent - A wx.Window instance
40 """
41
42 if parent is None:
43 raise TypeError('expected wx.Window instance as parent, got <None>')
44
45 pat = gmPerson.gmCurrentPatient()
46 if not pat.connected:
47 gmDispatcher.send(signal='statustext', msg=_('Cannot export EMR. No active patient.'))
48 return False
49
50
51 wc = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files"))
52 defdir = os.path.abspath(os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'EMR', pat['dirname'])))
53 gmTools.mkdir(defdir)
54 fname = '%s-%s_%s.txt' % (_('emr-export'), pat['lastnames'], pat['firstnames'])
55 dlg = wx.FileDialog (
56 parent = parent,
57 message = _("Save patient's EMR as..."),
58 defaultDir = defdir,
59 defaultFile = fname,
60 wildcard = wc,
61 style = wx.SAVE
62 )
63 choice = dlg.ShowModal()
64 fname = dlg.GetPath()
65 dlg.Destroy()
66 if choice != wx.ID_OK:
67 return None
68
69 _log.debug('exporting EMR to [%s]', fname)
70
71
72 output_file = codecs.open(fname, 'wb', encoding='utf8', errors='replace')
73 exporter = gmPatientExporter.cEmrExport(patient = pat)
74 exporter.set_output_file(output_file)
75 exporter.dump_constraints()
76 exporter.dump_demographic_record(True)
77 exporter.dump_clinical_record()
78 exporter.dump_med_docs()
79 output_file.close()
80
81 gmDispatcher.send('statustext', msg = _('EMR successfully exported to file: %s') % fname, beep = False)
82 return fname
83
84 -class cEMRTree(wx.TreeCtrl, gmGuiHelpers.cTreeExpansionHistoryMixin):
85 """This wx.TreeCtrl derivative displays a tree view of the medical record."""
86
87
88 - def __init__(self, parent, id, *args, **kwds):
89 """Set up our specialised tree.
90 """
91 kwds['style'] = wx.TR_HAS_BUTTONS | wx.NO_BORDER | wx.TR_SINGLE
92 wx.TreeCtrl.__init__(self, parent, id, *args, **kwds)
93
94 gmGuiHelpers.cTreeExpansionHistoryMixin.__init__(self)
95
96 self.__details_display = None
97 self.__details_display_mode = u'details'
98 self.__enable_display_mode_selection = None
99 self.__pat = gmPerson.gmCurrentPatient()
100 self.__curr_node = None
101 self.__exporter = gmPatientExporter.cEmrExport(patient = self.__pat)
102
103 self._old_cursor_pos = None
104
105 self.__make_popup_menus()
106 self.__register_events()
107
108
109
111 if not self.__pat.connected:
112 gmDispatcher.send(signal='statustext', msg=_('Cannot load clinical narrative. No active patient.'),)
113 return False
114
115 if not self.__populate_tree():
116 return False
117
118 return True
119
121 self.__details_display = narrative_display
122
124 self.__img_display = image_display
125
127 if not callable(callback):
128 raise ValueError('callback [%s] not callable' % callback)
129
130 self.__enable_display_mode_selection = callback
131
132
133
135 """Configures enabled event signals."""
136 wx.EVT_TREE_SEL_CHANGED (self, self.GetId(), self._on_tree_item_selected)
137 wx.EVT_TREE_ITEM_RIGHT_CLICK (self, self.GetId(), self._on_tree_item_right_clicked)
138
139
140
141 wx.EVT_TREE_ITEM_GETTOOLTIP(self, -1, self._on_tree_item_gettooltip)
142
143 gmDispatcher.connect(signal = 'narrative_mod_db', receiver = self._on_narrative_mod_db)
144 gmDispatcher.connect(signal = 'episode_mod_db', receiver = self._on_episode_mod_db)
145 gmDispatcher.connect(signal = 'health_issue_mod_db', receiver = self._on_issue_mod_db)
146 gmDispatcher.connect(signal = 'family_history_mod_db', receiver = self._on_issue_mod_db)
147
149 """Updates EMR browser data."""
150
151
152
153 wx.BeginBusyCursor()
154
155
156
157
158 self.DeleteAllItems()
159 root_item = self.AddRoot(_('EMR of %(lastnames)s, %(firstnames)s') % self.__pat.get_active_name())
160 self.SetItemPyData(root_item, None)
161 self.SetItemHasChildren(root_item, True)
162 self.__root_tooltip = self.__pat['description_gender'] + u'\n'
163 if self.__pat['deceased'] is None:
164 self.__root_tooltip += u' %s %s (%s)\n\n' % (
165 gmPerson.map_gender2symbol[self.__pat['gender']],
166 self.__pat.get_formatted_dob(format = '%d %b %Y', encoding = gmI18N.get_encoding()),
167 self.__pat['medical_age']
168 )
169 else:
170 template = u' %s %s - %s (%s)\n\n'
171 self.__root_tooltip += template % (
172 gmPerson.map_gender2symbol[self.__pat['gender']],
173 self.__pat.get_formatted_dob(format = '%d.%b %Y', encoding = gmI18N.get_encoding()),
174 self.__pat['deceased'].strftime('%d.%b %Y').decode(gmI18N.get_encoding()),
175 self.__pat['medical_age']
176 )
177 self.__root_tooltip += gmTools.coalesce(self.__pat['comment'], u'', u'%s\n\n')
178 doc = self.__pat.primary_provider
179 if doc is not None:
180 self.__root_tooltip += u'%s:\n' % _('Primary provider in this praxis')
181 self.__root_tooltip += u' %s %s %s (%s)%s\n\n' % (
182 gmTools.coalesce(doc['title'], gmPerson.map_gender2salutation(gender = doc['gender'])),
183 doc['firstnames'],
184 doc['lastnames'],
185 doc['short_alias'],
186 gmTools.bool2subst(doc['is_active'], u'', u' [%s]' % _('inactive'))
187 )
188 if not ((self.__pat['emergency_contact'] is None) and (self.__pat['pk_emergency_contact'] is None)):
189 self.__root_tooltip += _('In case of emergency contact:') + u'\n'
190 if self.__pat['emergency_contact'] is not None:
191 self.__root_tooltip += gmTools.wrap (
192 text = u'%s\n' % self.__pat['emergency_contact'],
193 width = 60,
194 initial_indent = u' ',
195 subsequent_indent = u' '
196 )
197 if self.__pat['pk_emergency_contact'] is not None:
198 contact = self.__pat.emergency_contact_in_database
199 self.__root_tooltip += u' %s\n' % contact['description_gender']
200 self.__root_tooltip = self.__root_tooltip.strip('\n')
201 if self.__root_tooltip == u'':
202 self.__root_tooltip = u' '
203
204
205 self.__exporter.get_historical_tree(self)
206 self.__curr_node = root_item
207
208 self.SelectItem(root_item)
209 self.Expand(root_item)
210 self.__update_text_for_selected_node()
211
212
213
214 wx.EndBusyCursor()
215 return True
216
218 """Displays information for the selected tree node."""
219
220 if self.__details_display is None:
221 self.__img_display.clear()
222 return
223
224 if self.__curr_node is None:
225 self.__img_display.clear()
226 return
227
228 node_data = self.GetPyData(self.__curr_node)
229 doc_folder = self.__pat.get_document_folder()
230
231 if isinstance(node_data, gmEMRStructItems.cHealthIssue):
232 self.__enable_display_mode_selection(True)
233 if self.__details_display_mode == u'details':
234 txt = node_data.format(left_margin=1, patient = self.__pat)
235 else:
236 txt = node_data.format_as_journal(left_margin = 1)
237
238 self.__img_display.refresh (
239 document_folder = doc_folder,
240 episodes = [ epi['pk_episode'] for epi in node_data.episodes ]
241 )
242
243 elif isinstance(node_data, type({})):
244 self.__enable_display_mode_selection(False)
245
246 txt = _('Pool of unassociated episodes:\n\n "%s"') % node_data['description']
247 self.__img_display.clear()
248
249 elif isinstance(node_data, gmEMRStructItems.cEpisode):
250 self.__enable_display_mode_selection(True)
251 if self.__details_display_mode == u'details':
252 txt = node_data.format(left_margin = 1, patient = self.__pat)
253 else:
254 txt = node_data.format_as_journal(left_margin = 1)
255 self.__img_display.refresh (
256 document_folder = doc_folder,
257 episodes = [node_data['pk_episode']]
258 )
259
260 elif isinstance(node_data, gmEMRStructItems.cEncounter):
261 self.__enable_display_mode_selection(False)
262 epi = self.GetPyData(self.GetItemParent(self.__curr_node))
263 txt = node_data.format (
264 episodes = [epi['pk_episode']],
265 with_soap = True,
266 left_margin = 1,
267 patient = self.__pat,
268 with_co_encountlet_hints = True
269 )
270 self.__img_display.refresh (
271 document_folder = doc_folder,
272 episodes = [epi['pk_episode']],
273 encounter = node_data['pk_encounter']
274 )
275
276
277 else:
278 self.__enable_display_mode_selection(False)
279 emr = self.__pat.get_emr()
280 txt = emr.format_summary(dob = self.__pat['dob'])
281 self.__img_display.clear()
282
283 self.__details_display.Clear()
284 self.__details_display.WriteText(txt)
285 self.__details_display.ShowPosition(0)
286
288
289
290 self.__epi_context_popup = wx.Menu(title = _('Episode Actions:'))
291
292 menu_id = wx.NewId()
293 self.__epi_context_popup.AppendItem(wx.MenuItem(self.__epi_context_popup, menu_id, _('Edit details')))
294 wx.EVT_MENU(self.__epi_context_popup, menu_id, self.__edit_episode)
295
296 menu_id = wx.NewId()
297 self.__epi_context_popup.AppendItem(wx.MenuItem(self.__epi_context_popup, menu_id, _('Delete')))
298 wx.EVT_MENU(self.__epi_context_popup, menu_id, self.__delete_episode)
299
300 menu_id = wx.NewId()
301 self.__epi_context_popup.AppendItem(wx.MenuItem(self.__epi_context_popup, menu_id, _('Promote')))
302 wx.EVT_MENU(self.__epi_context_popup, menu_id, self.__promote_episode_to_issue)
303
304 menu_id = wx.NewId()
305 self.__epi_context_popup.AppendItem(wx.MenuItem(self.__epi_context_popup, menu_id, _('Move encounters')))
306 wx.EVT_MENU(self.__epi_context_popup, menu_id, self.__move_encounters)
307
308
309 self.__enc_context_popup = wx.Menu(title = _('Encounter Actions:'))
310
311 menu_id = wx.NewId()
312 self.__enc_context_popup.AppendItem(wx.MenuItem(self.__enc_context_popup, menu_id, _('Move data to another episode')))
313 wx.EVT_MENU(self.__enc_context_popup, menu_id, self.__relink_encounter_data2episode)
314
315 menu_id = wx.NewId()
316 self.__enc_context_popup.AppendItem(wx.MenuItem(self.__enc_context_popup, menu_id, _('Edit details')))
317 wx.EVT_MENU(self.__enc_context_popup, menu_id, self.__edit_encounter_details)
318
319 item = self.__enc_context_popup.Append(-1, _('Edit progress notes'))
320 self.Bind(wx.EVT_MENU, self.__edit_progress_notes, item)
321
322 item = self.__enc_context_popup.Append(-1, _('Move progress notes'))
323 self.Bind(wx.EVT_MENU, self.__move_progress_notes, item)
324
325 item = self.__enc_context_popup.Append(-1, _('Export for Medistar'))
326 self.Bind(wx.EVT_MENU, self.__export_encounter_for_medistar, item)
327
328
329 self.__issue_context_popup = wx.Menu(title = _('Health Issue Actions:'))
330
331 menu_id = wx.NewId()
332 self.__issue_context_popup.AppendItem(wx.MenuItem(self.__issue_context_popup, menu_id, _('Edit details')))
333 wx.EVT_MENU(self.__issue_context_popup, menu_id, self.__edit_issue)
334
335 menu_id = wx.NewId()
336 self.__issue_context_popup.AppendItem(wx.MenuItem(self.__issue_context_popup, menu_id, _('Delete')))
337 wx.EVT_MENU(self.__issue_context_popup, menu_id, self.__delete_issue)
338
339 self.__issue_context_popup.AppendSeparator()
340
341 menu_id = wx.NewId()
342 self.__issue_context_popup.AppendItem(wx.MenuItem(self.__issue_context_popup, menu_id, _('Open to encounter level')))
343 wx.EVT_MENU(self.__issue_context_popup, menu_id, self.__expand_issue_to_encounter_level)
344
345
346
347
348 self.__root_context_popup = wx.Menu(title = _('EMR Actions:'))
349
350 menu_id = wx.NewId()
351 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Create health issue')))
352 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__create_issue)
353
354 item = self.__root_context_popup.Append(-1, _('Create episode'))
355 self.Bind(wx.EVT_MENU, self.__create_episode, item)
356
357 menu_id = wx.NewId()
358 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage allergies')))
359 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__document_allergy)
360
361 menu_id = wx.NewId()
362 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage family history')))
363 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_family_history)
364
365 menu_id = wx.NewId()
366 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage hospitalizations')))
367 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_hospital_stays)
368
369 menu_id = wx.NewId()
370 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage occupation')))
371 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_occupation)
372
373 menu_id = wx.NewId()
374 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage procedures')))
375 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_procedures)
376
377 menu_id = wx.NewId()
378 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage vaccinations')))
379 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_vaccinations)
380
381 self.__root_context_popup.AppendSeparator()
382
383
384 expand_menu = wx.Menu()
385 self.__root_context_popup.AppendMenu(wx.NewId(), _('Open EMR to ...'), expand_menu)
386
387 menu_id = wx.NewId()
388 expand_menu.AppendItem(wx.MenuItem(expand_menu, menu_id, _('... issue level')))
389 wx.EVT_MENU(expand_menu, menu_id, self.__expand_to_issue_level)
390
391 menu_id = wx.NewId()
392 expand_menu.AppendItem(wx.MenuItem(expand_menu, menu_id, _('... episode level')))
393 wx.EVT_MENU(expand_menu, menu_id, self.__expand_to_episode_level)
394
395 menu_id = wx.NewId()
396 expand_menu.AppendItem(wx.MenuItem(expand_menu, menu_id, _('... encounter level')))
397 wx.EVT_MENU(expand_menu, menu_id, self.__expand_to_encounter_level)
398
399 - def __handle_root_context(self, pos=wx.DefaultPosition):
400 self.PopupMenu(self.__root_context_popup, pos)
401
402 - def __handle_issue_context(self, pos=wx.DefaultPosition):
403
404 self.PopupMenu(self.__issue_context_popup, pos)
405
406 - def __handle_episode_context(self, pos=wx.DefaultPosition):
407
408 self.PopupMenu(self.__epi_context_popup, pos)
409
410 - def __handle_encounter_context(self, pos=wx.DefaultPosition):
411 self.PopupMenu(self.__enc_context_popup, pos)
412
413
414
423
426
430
432 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
433 parent = self,
434 id = -1,
435 caption = _('Deleting episode'),
436 button_defs = [
437 {'label': _('Yes, delete'), 'tooltip': _('Delete the episode if possible (it must be completely empty).')},
438 {'label': _('No, cancel'), 'tooltip': _('Cancel and do NOT delete the episode.')}
439 ],
440 question = _(
441 'Are you sure you want to delete this episode ?\n'
442 '\n'
443 ' "%s"\n'
444 ) % self.__curr_node_data['description']
445 )
446 result = dlg.ShowModal()
447 if result != wx.ID_YES:
448 return
449
450 try:
451 gmEMRStructItems.delete_episode(episode = self.__curr_node_data)
452 except gmExceptions.DatabaseObjectInUseError:
453 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete episode. There is still clinical data recorded for it.'))
454 return
455
456
457
468
470 encounter = self.GetPyData(self.__curr_node)
471 node_parent = self.GetItemParent(self.__curr_node)
472 episode = self.GetPyData(node_parent)
473
474 gmNarrativeWidgets.manage_progress_notes (
475 parent = self,
476 encounters = [encounter['pk_encounter']],
477 episodes = [episode['pk_episode']]
478 )
479
484
486
487 node_parent = self.GetItemParent(self.__curr_node)
488 owning_episode = self.GetPyData(node_parent)
489
490 episode_selector = gmNarrativeWidgets.cMoveNarrativeDlg (
491 self,
492 -1,
493 episode = owning_episode,
494 encounter = self.__curr_node_data
495 )
496
497 result = episode_selector.ShowModal()
498 episode_selector.Destroy()
499
500 if result == wx.ID_YES:
501 self.__populate_tree()
502
503
504
507
509 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
510 parent = self,
511 id = -1,
512 caption = _('Deleting health issue'),
513 button_defs = [
514 {'label': _('Yes, delete'), 'tooltip': _('Delete the health issue if possible (it must be completely empty).')},
515 {'label': _('No, cancel'), 'tooltip': _('Cancel and do NOT delete the health issue.')}
516 ],
517 question = _(
518 'Are you sure you want to delete this health issue ?\n'
519 '\n'
520 ' "%s"\n'
521 ) % self.__curr_node_data['description']
522 )
523 result = dlg.ShowModal()
524 if result != wx.ID_YES:
525 dlg.Destroy()
526 return
527
528 dlg.Destroy()
529
530 try:
531 gmEMRStructItems.delete_health_issue(health_issue = self.__curr_node_data)
532 except gmExceptions.DatabaseObjectInUseError:
533 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete health issue. There is still clinical data recorded for it.'))
534
536
537 if not self.__curr_node.IsOk():
538 return
539
540 self.Expand(self.__curr_node)
541
542 epi, epi_cookie = self.GetFirstChild(self.__curr_node)
543 while epi.IsOk():
544 self.Expand(epi)
545 epi, epi_cookie = self.GetNextChild(self.__curr_node, epi_cookie)
546
547
548
551
554
562
565
568
571
574
577
579
580 root_item = self.GetRootItem()
581
582 if not root_item.IsOk():
583 return
584
585 self.Expand(root_item)
586
587
588 issue, issue_cookie = self.GetFirstChild(root_item)
589 while issue.IsOk():
590 self.Collapse(issue)
591 epi, epi_cookie = self.GetFirstChild(issue)
592 while epi.IsOk():
593 self.Collapse(epi)
594 epi, epi_cookie = self.GetNextChild(issue, epi_cookie)
595 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
596
598
599 root_item = self.GetRootItem()
600
601 if not root_item.IsOk():
602 return
603
604 self.Expand(root_item)
605
606
607 issue, issue_cookie = self.GetFirstChild(root_item)
608 while issue.IsOk():
609 self.Expand(issue)
610 epi, epi_cookie = self.GetFirstChild(issue)
611 while epi.IsOk():
612 self.Collapse(epi)
613 epi, epi_cookie = self.GetNextChild(issue, epi_cookie)
614 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
615
617
618 root_item = self.GetRootItem()
619
620 if not root_item.IsOk():
621 return
622
623 self.Expand(root_item)
624
625
626 issue, issue_cookie = self.GetFirstChild(root_item)
627 while issue.IsOk():
628 self.Expand(issue)
629 epi, epi_cookie = self.GetFirstChild(issue)
630 while epi.IsOk():
631 self.Expand(epi)
632 epi, epi_cookie = self.GetNextChild(issue, epi_cookie)
633 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
634
641
642
643
645 wx.CallAfter(self.__update_text_for_selected_node)
646
648 wx.CallAfter(self.__populate_tree)
649
651 wx.CallAfter(self.__populate_tree)
652
654 sel_item = event.GetItem()
655 self.__curr_node = sel_item
656 self.__update_text_for_selected_node()
657 return True
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
797
798
799
800
801
802
803
804
805
806
807
809 """Right button clicked: display the popup for the tree"""
810
811 node = event.GetItem()
812 self.SelectItem(node)
813 self.__curr_node_data = self.GetPyData(node)
814 self.__curr_node = node
815
816 pos = wx.DefaultPosition
817 if isinstance(self.__curr_node_data, gmEMRStructItems.cHealthIssue):
818 self.__handle_issue_context(pos=pos)
819 elif isinstance(self.__curr_node_data, gmEMRStructItems.cEpisode):
820 self.__handle_episode_context(pos=pos)
821 elif isinstance(self.__curr_node_data, gmEMRStructItems.cEncounter):
822 self.__handle_encounter_context(pos=pos)
823 elif node == self.GetRootItem():
824 self.__handle_root_context()
825 elif type(self.__curr_node_data) == type({}):
826
827 pass
828 else:
829 print "error: unknown node type, no popup menu"
830 event.Skip()
831
833 """Used in sorting items.
834
835 -1: 1 < 2
836 0: 1 = 2
837 1: 1 > 2
838 """
839
840
841 if not node1:
842 _log.debug('invalid node 1')
843 return 0
844 if not node2:
845 _log.debug('invalid node 2')
846 return 0
847
848 if not node1.IsOk():
849 _log.debug('invalid node 1')
850 return 0
851 if not node2.IsOk():
852 _log.debug('invalid node 2')
853 return 0
854
855 item1 = self.GetPyData(node1)
856 item2 = self.GetPyData(node2)
857
858
859 if isinstance(item1, type({})):
860 return -1
861 if isinstance(item2, type({})):
862 return 1
863
864
865 if isinstance(item1, gmEMRStructItems.cEncounter):
866 if item1['started'] == item2['started']:
867 return 0
868 if item1['started'] > item2['started']:
869 return -1
870 return 1
871
872
873 if isinstance(item1, gmEMRStructItems.cEpisode):
874 start1 = item1.get_access_range()[0]
875 start2 = item2.get_access_range()[0]
876 if start1 == start2:
877 return 0
878 if start1 < start2:
879 return -1
880 return 1
881
882
883 if isinstance(item1, gmEMRStructItems.cHealthIssue):
884
885
886 if item1['grouping'] is None:
887 if item2['grouping'] is not None:
888 return 1
889
890
891 if item1['grouping'] is not None:
892 if item2['grouping'] is None:
893 return -1
894
895
896 if (item1['grouping'] is None) and (item2['grouping'] is None):
897 if item1['description'].lower() < item2['description'].lower():
898 return -1
899 if item1['description'].lower() > item2['description'].lower():
900 return 1
901 return 0
902
903
904 if item1['grouping'] < item2['grouping']:
905 return -1
906
907 if item1['grouping'] > item2['grouping']:
908 return 1
909
910 if item1['description'].lower() < item2['description'].lower():
911 return -1
912
913 if item1['description'].lower() > item2['description'].lower():
914 return 1
915
916 return 0
917
918 _log.error('unknown item type during sorting EMR tree:')
919 _log.error('item1: %s', type(item1))
920 _log.error('item2: %s', type(item2))
921
922 return 0
923
924
925
927 return self.__details_display_mode
928
930 if mode not in [u'details', u'journal']:
931 raise ValueError('details display mode must be one of "details", "journal"')
932 if self.__details_display_mode == mode:
933 return
934 self.__details_display_mode = mode
935 self.__update_text_for_selected_node()
936
937 details_display_mode = property(_get_details_display_mode, _set_details_display_mode)
938
939 from Gnumed.wxGladeWidgets import wxgScrolledEMRTreePnl
940
954
955 from Gnumed.wxGladeWidgets import wxgSplittedEMRTreeBrowserPnl
956
958 """A splitter window holding an EMR tree.
959
960 The left hand side displays a scrollable EMR tree while
961 on the right details for selected items are displayed.
962
963 Expects to be put into a Notebook.
964 """
971
973 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
974 return True
975
976
977
979 if self.GetParent().GetCurrentPage() == self:
980 self.repopulate_ui()
981 return True
982
986
990
991
992
994 """Fills UI with data."""
995 self._pnl_emr_tree.repopulate_ui()
996 self._splitter_browser.SetSashPosition(self._splitter_browser.GetSizeTuple()[0]/3, True)
997 return True
998
1000 if enable:
1001 self._RBTN_details.Enable(True)
1002 self._RBTN_journal.Enable(True)
1003 return
1004 self._RBTN_details.Enable(False)
1005 self._RBTN_journal.Enable(False)
1006
1009 wx.Panel.__init__(self, *args, **kwargs)
1010
1011 self.__do_layout()
1012 self.__register_events()
1013
1015 self.__journal = wx.TextCtrl (
1016 self,
1017 -1,
1018 _('No EMR data loaded.'),
1019 style = wx.TE_MULTILINE | wx.TE_READONLY
1020 )
1021 self.__journal.SetFont(wx.Font(10, wx.MODERN, wx.NORMAL, wx.NORMAL))
1022
1023 szr_outer = wx.BoxSizer(wx.VERTICAL)
1024 szr_outer.Add(self.__journal, 1, wx.EXPAND, 0)
1025
1026 self.SetAutoLayout(1)
1027 self.SetSizer(szr_outer)
1028 szr_outer.Fit(self)
1029 szr_outer.SetSizeHints(self)
1030 self.Layout()
1031
1033 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
1034
1036 """Expects to be in a Notebook."""
1037 if self.GetParent().GetCurrentPage() == self:
1038 self.repopulate_ui()
1039 return True
1040
1041
1042
1044 txt = StringIO.StringIO()
1045 exporter = gmPatientExporter.cEMRJournalExporter()
1046
1047
1048 try:
1049 exporter.export(txt)
1050 self.__journal.SetValue(txt.getvalue())
1051 except ValueError:
1052 _log.exception('cannot get EMR journal')
1053 self.__journal.SetValue (_(
1054 'An error occurred while retrieving the EMR\n'
1055 'in journal form for the active patient.\n\n'
1056 'Please check the log file for details.'
1057 ))
1058 txt.close()
1059 self.__journal.ShowPosition(self.__journal.GetLastPosition())
1060 return True
1061
1062
1063
1064 if __name__ == '__main__':
1065
1066 _log.info("starting emr browser...")
1067
1068 try:
1069
1070 patient = gmPersonSearch.ask_for_patient()
1071 if patient is None:
1072 print "No patient. Exiting gracefully..."
1073 sys.exit(0)
1074 gmPatSearchWidgets.set_active_patient(patient = patient)
1075
1076
1077 application = wx.PyWidgetTester(size=(800,600))
1078 emr_browser = cEMRBrowserPanel(application.frame, -1)
1079 emr_browser.refresh_tree()
1080
1081 application.frame.Show(True)
1082 application.MainLoop()
1083
1084
1085 if patient is not None:
1086 try:
1087 patient.cleanup()
1088 except:
1089 print "error cleaning up patient"
1090 except StandardError:
1091 _log.exception("unhandled exception caught !")
1092
1093 raise
1094
1095 _log.info("closing emr browser...")
1096
1097
1098