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)\n\n' % (
165 self.__pat.get_formatted_dob(format = '%d %b %Y', encoding = gmI18N.get_encoding()),
166 self.__pat['medical_age']
167 )
168 else:
169 template = u' %s - %s (%s)\n\n'
170 self.__root_tooltip += template % (
171 self.__pat.get_formatted_dob(format = '%d.%b %Y', encoding = gmI18N.get_encoding()),
172 self.__pat['deceased'].strftime('%d.%b %Y').decode(gmI18N.get_encoding()),
173 self.__pat['medical_age']
174 )
175 self.__root_tooltip += gmTools.coalesce(self.__pat['comment'], u'', u'%s\n\n')
176 doc = self.__pat.primary_provider
177 if doc is not None:
178 self.__root_tooltip += u'%s:\n' % _('Primary provider in this praxis')
179 self.__root_tooltip += u' %s %s %s (%s)%s\n\n' % (
180 gmTools.coalesce(doc['title'], gmPerson.map_gender2salutation(gender = doc['gender'])),
181 doc['firstnames'],
182 doc['lastnames'],
183 doc['short_alias'],
184 gmTools.bool2subst(doc['is_active'], u'', u' [%s]' % _('inactive'))
185 )
186 if not ((self.__pat['emergency_contact'] is None) and (self.__pat['pk_emergency_contact'] is None)):
187 self.__root_tooltip += _('In case of emergency contact:') + u'\n'
188 if self.__pat['emergency_contact'] is not None:
189 self.__root_tooltip += gmTools.wrap (
190 text = u'%s\n' % self.__pat['emergency_contact'],
191 width = 60,
192 initial_indent = u' ',
193 subsequent_indent = u' '
194 )
195 if self.__pat['pk_emergency_contact'] is not None:
196 contact = self.__pat.emergency_contact_in_database
197 self.__root_tooltip += u' %s\n' % contact['description_gender']
198 self.__root_tooltip = self.__root_tooltip.strip('\n')
199 if self.__root_tooltip == u'':
200 self.__root_tooltip = u' '
201
202
203 self.__exporter.get_historical_tree(self)
204 self.__curr_node = root_item
205
206 self.SelectItem(root_item)
207 self.Expand(root_item)
208 self.__update_text_for_selected_node()
209
210
211
212 wx.EndBusyCursor()
213 return True
214
216 """Displays information for the selected tree node."""
217
218 if self.__details_display is None:
219 self.__img_display.clear()
220 return
221
222 if self.__curr_node is None:
223 self.__img_display.clear()
224 return
225
226 node_data = self.GetPyData(self.__curr_node)
227 doc_folder = self.__pat.get_document_folder()
228
229 if isinstance(node_data, gmEMRStructItems.cHealthIssue):
230 self.__enable_display_mode_selection(True)
231 if self.__details_display_mode == u'details':
232 txt = node_data.format(left_margin=1, patient = self.__pat)
233 else:
234 txt = node_data.format_as_journal(left_margin = 1)
235
236 self.__img_display.refresh (
237 document_folder = doc_folder,
238 episodes = [ epi['pk_episode'] for epi in node_data.episodes ]
239 )
240
241 elif isinstance(node_data, type({})):
242 self.__enable_display_mode_selection(False)
243
244 txt = _('Pool of unassociated episodes:\n\n "%s"') % node_data['description']
245 self.__img_display.clear()
246
247 elif isinstance(node_data, gmEMRStructItems.cEpisode):
248 self.__enable_display_mode_selection(True)
249 if self.__details_display_mode == u'details':
250 txt = node_data.format(left_margin = 1, patient = self.__pat)
251 else:
252 txt = node_data.format_as_journal(left_margin = 1)
253 self.__img_display.refresh (
254 document_folder = doc_folder,
255 episodes = [node_data['pk_episode']]
256 )
257
258 elif isinstance(node_data, gmEMRStructItems.cEncounter):
259 self.__enable_display_mode_selection(False)
260 epi = self.GetPyData(self.GetItemParent(self.__curr_node))
261 txt = node_data.format (
262 episodes = [epi['pk_episode']],
263 with_soap = True,
264 left_margin = 1,
265 patient = self.__pat,
266 with_co_encountlet_hints = True
267 )
268 self.__img_display.refresh (
269 document_folder = doc_folder,
270 episodes = [epi['pk_episode']],
271 encounter = node_data['pk_encounter']
272 )
273
274
275 else:
276 self.__enable_display_mode_selection(False)
277 emr = self.__pat.get_emr()
278 txt = emr.format_summary(dob = self.__pat['dob'])
279 self.__img_display.clear()
280
281 self.__details_display.Clear()
282 self.__details_display.WriteText(txt)
283 self.__details_display.ShowPosition(0)
284
286
287
288 self.__epi_context_popup = wx.Menu(title = _('Episode Actions:'))
289
290 menu_id = wx.NewId()
291 self.__epi_context_popup.AppendItem(wx.MenuItem(self.__epi_context_popup, menu_id, _('Edit details')))
292 wx.EVT_MENU(self.__epi_context_popup, menu_id, self.__edit_episode)
293
294 menu_id = wx.NewId()
295 self.__epi_context_popup.AppendItem(wx.MenuItem(self.__epi_context_popup, menu_id, _('Delete')))
296 wx.EVT_MENU(self.__epi_context_popup, menu_id, self.__delete_episode)
297
298 menu_id = wx.NewId()
299 self.__epi_context_popup.AppendItem(wx.MenuItem(self.__epi_context_popup, menu_id, _('Promote')))
300 wx.EVT_MENU(self.__epi_context_popup, menu_id, self.__promote_episode_to_issue)
301
302 menu_id = wx.NewId()
303 self.__epi_context_popup.AppendItem(wx.MenuItem(self.__epi_context_popup, menu_id, _('Move encounters')))
304 wx.EVT_MENU(self.__epi_context_popup, menu_id, self.__move_encounters)
305
306
307 self.__enc_context_popup = wx.Menu(title = _('Encounter Actions:'))
308
309 menu_id = wx.NewId()
310 self.__enc_context_popup.AppendItem(wx.MenuItem(self.__enc_context_popup, menu_id, _('Move data to another episode')))
311 wx.EVT_MENU(self.__enc_context_popup, menu_id, self.__relink_encounter_data2episode)
312
313 menu_id = wx.NewId()
314 self.__enc_context_popup.AppendItem(wx.MenuItem(self.__enc_context_popup, menu_id, _('Edit details')))
315 wx.EVT_MENU(self.__enc_context_popup, menu_id, self.__edit_encounter_details)
316
317 item = self.__enc_context_popup.Append(-1, _('Edit progress notes'))
318 self.Bind(wx.EVT_MENU, self.__edit_progress_notes, item)
319
320 item = self.__enc_context_popup.Append(-1, _('Move progress notes'))
321 self.Bind(wx.EVT_MENU, self.__move_progress_notes, item)
322
323 item = self.__enc_context_popup.Append(-1, _('Export for Medistar'))
324 self.Bind(wx.EVT_MENU, self.__export_encounter_for_medistar, item)
325
326
327 self.__issue_context_popup = wx.Menu(title = _('Health Issue Actions:'))
328
329 menu_id = wx.NewId()
330 self.__issue_context_popup.AppendItem(wx.MenuItem(self.__issue_context_popup, menu_id, _('Edit details')))
331 wx.EVT_MENU(self.__issue_context_popup, menu_id, self.__edit_issue)
332
333 menu_id = wx.NewId()
334 self.__issue_context_popup.AppendItem(wx.MenuItem(self.__issue_context_popup, menu_id, _('Delete')))
335 wx.EVT_MENU(self.__issue_context_popup, menu_id, self.__delete_issue)
336
337 self.__issue_context_popup.AppendSeparator()
338
339 menu_id = wx.NewId()
340 self.__issue_context_popup.AppendItem(wx.MenuItem(self.__issue_context_popup, menu_id, _('Open to encounter level')))
341 wx.EVT_MENU(self.__issue_context_popup, menu_id, self.__expand_issue_to_encounter_level)
342
343
344
345
346 self.__root_context_popup = wx.Menu(title = _('EMR Actions:'))
347
348 menu_id = wx.NewId()
349 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Create health issue')))
350 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__create_issue)
351
352 item = self.__root_context_popup.Append(-1, _('Create episode'))
353 self.Bind(wx.EVT_MENU, self.__create_episode, item)
354
355 menu_id = wx.NewId()
356 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage allergies')))
357 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__document_allergy)
358
359 menu_id = wx.NewId()
360 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage family history')))
361 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_family_history)
362
363 menu_id = wx.NewId()
364 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage hospitalizations')))
365 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_hospital_stays)
366
367 menu_id = wx.NewId()
368 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage occupation')))
369 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_occupation)
370
371 menu_id = wx.NewId()
372 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage procedures')))
373 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_procedures)
374
375 menu_id = wx.NewId()
376 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage vaccinations')))
377 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_vaccinations)
378
379 self.__root_context_popup.AppendSeparator()
380
381
382 expand_menu = wx.Menu()
383 self.__root_context_popup.AppendMenu(wx.NewId(), _('Open EMR to ...'), expand_menu)
384
385 menu_id = wx.NewId()
386 expand_menu.AppendItem(wx.MenuItem(expand_menu, menu_id, _('... issue level')))
387 wx.EVT_MENU(expand_menu, menu_id, self.__expand_to_issue_level)
388
389 menu_id = wx.NewId()
390 expand_menu.AppendItem(wx.MenuItem(expand_menu, menu_id, _('... episode level')))
391 wx.EVT_MENU(expand_menu, menu_id, self.__expand_to_episode_level)
392
393 menu_id = wx.NewId()
394 expand_menu.AppendItem(wx.MenuItem(expand_menu, menu_id, _('... encounter level')))
395 wx.EVT_MENU(expand_menu, menu_id, self.__expand_to_encounter_level)
396
397 - def __handle_root_context(self, pos=wx.DefaultPosition):
398 self.PopupMenu(self.__root_context_popup, pos)
399
400 - def __handle_issue_context(self, pos=wx.DefaultPosition):
401
402 self.PopupMenu(self.__issue_context_popup, pos)
403
404 - def __handle_episode_context(self, pos=wx.DefaultPosition):
405
406 self.PopupMenu(self.__epi_context_popup, pos)
407
408 - def __handle_encounter_context(self, pos=wx.DefaultPosition):
409 self.PopupMenu(self.__enc_context_popup, pos)
410
411
412
421
424
428
430 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
431 parent = self,
432 id = -1,
433 caption = _('Deleting episode'),
434 button_defs = [
435 {'label': _('Yes, delete'), 'tooltip': _('Delete the episode if possible (it must be completely empty).')},
436 {'label': _('No, cancel'), 'tooltip': _('Cancel and do NOT delete the episode.')}
437 ],
438 question = _(
439 'Are you sure you want to delete this episode ?\n'
440 '\n'
441 ' "%s"\n'
442 ) % self.__curr_node_data['description']
443 )
444 result = dlg.ShowModal()
445 if result != wx.ID_YES:
446 return
447
448 if not gmEMRStructItems.delete_episode(episode = self.__curr_node_data):
449 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete episode. There is still clinical data recorded for it.'))
450
451
452
463
465 encounter = self.GetPyData(self.__curr_node)
466 node_parent = self.GetItemParent(self.__curr_node)
467 episode = self.GetPyData(node_parent)
468
469 gmNarrativeWidgets.manage_progress_notes (
470 parent = self,
471 encounters = [encounter['pk_encounter']],
472 episodes = [episode['pk_episode']]
473 )
474
479
481
482 node_parent = self.GetItemParent(self.__curr_node)
483 owning_episode = self.GetPyData(node_parent)
484
485 episode_selector = gmNarrativeWidgets.cMoveNarrativeDlg (
486 self,
487 -1,
488 episode = owning_episode,
489 encounter = self.__curr_node_data
490 )
491
492 result = episode_selector.ShowModal()
493 episode_selector.Destroy()
494
495 if result == wx.ID_YES:
496 self.__populate_tree()
497
498
499
502
504 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
505 parent = self,
506 id = -1,
507 caption = _('Deleting health issue'),
508 button_defs = [
509 {'label': _('Yes, delete'), 'tooltip': _('Delete the health issue if possible (it must be completely empty).')},
510 {'label': _('No, cancel'), 'tooltip': _('Cancel and do NOT delete the health issue.')}
511 ],
512 question = _(
513 'Are you sure you want to delete this health issue ?\n'
514 '\n'
515 ' "%s"\n'
516 ) % self.__curr_node_data['description']
517 )
518 result = dlg.ShowModal()
519 if result != wx.ID_YES:
520 dlg.Destroy()
521 return
522
523 dlg.Destroy()
524
525 try:
526 gmEMRStructItems.delete_health_issue(health_issue = self.__curr_node_data)
527 except gmExceptions.DatabaseObjectInUseError:
528 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete health issue. There is still clinical data recorded for it.'))
529
531
532 if not self.__curr_node.IsOk():
533 return
534
535 self.Expand(self.__curr_node)
536
537 epi, epi_cookie = self.GetFirstChild(self.__curr_node)
538 while epi.IsOk():
539 self.Expand(epi)
540 epi, epi_cookie = self.GetNextChild(self.__curr_node, epi_cookie)
541
542
543
546
549
557
560
563
566
569
572
574
575 root_item = self.GetRootItem()
576
577 if not root_item.IsOk():
578 return
579
580 self.Expand(root_item)
581
582
583 issue, issue_cookie = self.GetFirstChild(root_item)
584 while issue.IsOk():
585 self.Collapse(issue)
586 epi, epi_cookie = self.GetFirstChild(issue)
587 while epi.IsOk():
588 self.Collapse(epi)
589 epi, epi_cookie = self.GetNextChild(issue, epi_cookie)
590 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
591
593
594 root_item = self.GetRootItem()
595
596 if not root_item.IsOk():
597 return
598
599 self.Expand(root_item)
600
601
602 issue, issue_cookie = self.GetFirstChild(root_item)
603 while issue.IsOk():
604 self.Expand(issue)
605 epi, epi_cookie = self.GetFirstChild(issue)
606 while epi.IsOk():
607 self.Collapse(epi)
608 epi, epi_cookie = self.GetNextChild(issue, epi_cookie)
609 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
610
612
613 root_item = self.GetRootItem()
614
615 if not root_item.IsOk():
616 return
617
618 self.Expand(root_item)
619
620
621 issue, issue_cookie = self.GetFirstChild(root_item)
622 while issue.IsOk():
623 self.Expand(issue)
624 epi, epi_cookie = self.GetFirstChild(issue)
625 while epi.IsOk():
626 self.Expand(epi)
627 epi, epi_cookie = self.GetNextChild(issue, epi_cookie)
628 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
629
636
637
638
640 wx.CallAfter(self.__update_text_for_selected_node)
641
643 wx.CallAfter(self.__populate_tree)
644
646 wx.CallAfter(self.__populate_tree)
647
649 sel_item = event.GetItem()
650 self.__curr_node = sel_item
651 self.__update_text_for_selected_node()
652 return True
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
792
793
794
795
796
797
798
799
800
801
802
804 """Right button clicked: display the popup for the tree"""
805
806 node = event.GetItem()
807 self.SelectItem(node)
808 self.__curr_node_data = self.GetPyData(node)
809 self.__curr_node = node
810
811 pos = wx.DefaultPosition
812 if isinstance(self.__curr_node_data, gmEMRStructItems.cHealthIssue):
813 self.__handle_issue_context(pos=pos)
814 elif isinstance(self.__curr_node_data, gmEMRStructItems.cEpisode):
815 self.__handle_episode_context(pos=pos)
816 elif isinstance(self.__curr_node_data, gmEMRStructItems.cEncounter):
817 self.__handle_encounter_context(pos=pos)
818 elif node == self.GetRootItem():
819 self.__handle_root_context()
820 elif type(self.__curr_node_data) == type({}):
821
822 pass
823 else:
824 print "error: unknown node type, no popup menu"
825 event.Skip()
826
828 """Used in sorting items.
829
830 -1: 1 < 2
831 0: 1 = 2
832 1: 1 > 2
833 """
834
835
836 if not node1:
837 _log.debug('invalid node 1')
838 return 0
839 if not node2:
840 _log.debug('invalid node 2')
841 return 0
842
843 if not node1.IsOk():
844 _log.debug('invalid node 1')
845 return 0
846 if not node2.IsOk():
847 _log.debug('invalid node 2')
848 return 0
849
850 item1 = self.GetPyData(node1)
851 item2 = self.GetPyData(node2)
852
853
854 if isinstance(item1, type({})):
855 return -1
856 if isinstance(item2, type({})):
857 return 1
858
859
860 if isinstance(item1, gmEMRStructItems.cEncounter):
861 if item1['started'] == item2['started']:
862 return 0
863 if item1['started'] > item2['started']:
864 return -1
865 return 1
866
867
868 if isinstance(item1, gmEMRStructItems.cEpisode):
869 start1 = item1.get_access_range()[0]
870 start2 = item2.get_access_range()[0]
871 if start1 == start2:
872 return 0
873 if start1 < start2:
874 return -1
875 return 1
876
877
878 if isinstance(item1, gmEMRStructItems.cHealthIssue):
879
880
881 if item1['grouping'] is None:
882 if item2['grouping'] is not None:
883 return 1
884
885
886 if item1['grouping'] is not None:
887 if item2['grouping'] is None:
888 return -1
889
890
891 if (item1['grouping'] is None) and (item2['grouping'] is None):
892 if item1['description'].lower() < item2['description'].lower():
893 return -1
894 if item1['description'].lower() > item2['description'].lower():
895 return 1
896 return 0
897
898
899 if item1['grouping'] < item2['grouping']:
900 return -1
901
902 if item1['grouping'] > item2['grouping']:
903 return 1
904
905 if item1['description'].lower() < item2['description'].lower():
906 return -1
907
908 if item1['description'].lower() > item2['description'].lower():
909 return 1
910
911 return 0
912
913 _log.error('unknown item type during sorting EMR tree:')
914 _log.error('item1: %s', type(item1))
915 _log.error('item2: %s', type(item2))
916
917 return 0
918
919
920
922 return self.__details_display_mode
923
925 if mode not in [u'details', u'journal']:
926 raise ValueError('details display mode must be one of "details", "journal"')
927 if self.__details_display_mode == mode:
928 return
929 self.__details_display_mode = mode
930 self.__update_text_for_selected_node()
931
932 details_display_mode = property(_get_details_display_mode, _set_details_display_mode)
933
934 from Gnumed.wxGladeWidgets import wxgScrolledEMRTreePnl
935
949
950 from Gnumed.wxGladeWidgets import wxgSplittedEMRTreeBrowserPnl
951
953 """A splitter window holding an EMR tree.
954
955 The left hand side displays a scrollable EMR tree while
956 on the right details for selected items are displayed.
957
958 Expects to be put into a Notebook.
959 """
966
968 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
969 return True
970
971
972
974 if self.GetParent().GetCurrentPage() == self:
975 self.repopulate_ui()
976 return True
977
981
985
986
987
989 """Fills UI with data."""
990 self._pnl_emr_tree.repopulate_ui()
991 self._splitter_browser.SetSashPosition(self._splitter_browser.GetSizeTuple()[0]/3, True)
992 return True
993
995 if enable:
996 self._RBTN_details.Enable(True)
997 self._RBTN_journal.Enable(True)
998 return
999 self._RBTN_details.Enable(False)
1000 self._RBTN_journal.Enable(False)
1001
1004 wx.Panel.__init__(self, *args, **kwargs)
1005
1006 self.__do_layout()
1007 self.__register_events()
1008
1010 self.__journal = wx.TextCtrl (
1011 self,
1012 -1,
1013 _('No EMR data loaded.'),
1014 style = wx.TE_MULTILINE | wx.TE_READONLY
1015 )
1016 self.__journal.SetFont(wx.Font(10, wx.MODERN, wx.NORMAL, wx.NORMAL))
1017
1018 szr_outer = wx.BoxSizer(wx.VERTICAL)
1019 szr_outer.Add(self.__journal, 1, wx.EXPAND, 0)
1020
1021 self.SetAutoLayout(1)
1022 self.SetSizer(szr_outer)
1023 szr_outer.Fit(self)
1024 szr_outer.SetSizeHints(self)
1025 self.Layout()
1026
1028 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
1029
1031 """Expects to be in a Notebook."""
1032 if self.GetParent().GetCurrentPage() == self:
1033 self.repopulate_ui()
1034 return True
1035
1036
1037
1039 txt = StringIO.StringIO()
1040 exporter = gmPatientExporter.cEMRJournalExporter()
1041
1042
1043 try:
1044 exporter.export(txt)
1045 self.__journal.SetValue(txt.getvalue())
1046 except ValueError:
1047 _log.exception('cannot get EMR journal')
1048 self.__journal.SetValue (_(
1049 'An error occurred while retrieving the EMR\n'
1050 'in journal form for the active patient.\n\n'
1051 'Please check the log file for details.'
1052 ))
1053 txt.close()
1054 self.__journal.ShowPosition(self.__journal.GetLastPosition())
1055 return True
1056
1057
1058
1059 if __name__ == '__main__':
1060
1061 _log.info("starting emr browser...")
1062
1063 try:
1064
1065 patient = gmPersonSearch.ask_for_patient()
1066 if patient is None:
1067 print "No patient. Exiting gracefully..."
1068 sys.exit(0)
1069 gmPatSearchWidgets.set_active_patient(patient = patient)
1070
1071
1072 application = wx.PyWidgetTester(size=(800,600))
1073 emr_browser = cEMRBrowserPanel(application.frame, -1)
1074 emr_browser.refresh_tree()
1075
1076 application.frame.Show(True)
1077 application.MainLoop()
1078
1079
1080 if patient is not None:
1081 try:
1082 patient.cleanup()
1083 except:
1084 print "error cleaning up patient"
1085 except StandardError:
1086 _log.exception("unhandled exception caught !")
1087
1088 raise
1089
1090 _log.info("closing emr browser...")
1091
1092
1093