1 """GNUmed patient EMR tree browser."""
2
3 __author__ = "cfmoro1976@yahoo.es, sjtan@swiftdsl.com.au, Karsten.Hilbert@gmx.net"
4 __license__ = "GPL v2 or later"
5
6
7 import sys
8 import os.path
9 import io
10 import logging
11
12
13
14 import wx
15 import wx.lib.mixins.treemixin as treemixin
16
17
18
19 from Gnumed.pycommon import gmI18N
20 from Gnumed.pycommon import gmDispatcher
21 from Gnumed.pycommon import gmExceptions
22 from Gnumed.pycommon import gmTools
23 from Gnumed.pycommon import gmDateTime
24 from Gnumed.pycommon import gmLog2
25
26 from Gnumed.exporters import gmPatientExporter
27
28 from Gnumed.business import gmGenericEMRItem
29 from Gnumed.business import gmEMRStructItems
30 from Gnumed.business import gmPerson
31 from Gnumed.business import gmSOAPimporter
32 from Gnumed.business import gmPersonSearch
33 from Gnumed.business import gmSoapDefs
34 from Gnumed.business import gmClinicalRecord
35
36 from Gnumed.wxpython import gmGuiHelpers
37 from Gnumed.wxpython import gmEMRStructWidgets
38 from Gnumed.wxpython import gmEncounterWidgets
39 from Gnumed.wxpython import gmSOAPWidgets
40 from Gnumed.wxpython import gmAllergyWidgets
41 from Gnumed.wxpython import gmDemographicsWidgets
42 from Gnumed.wxpython import gmNarrativeWidgets
43 from Gnumed.wxpython import gmNarrativeWorkflows
44 from Gnumed.wxpython import gmPatSearchWidgets
45 from Gnumed.wxpython import gmVaccWidgets
46 from Gnumed.wxpython import gmFamilyHistoryWidgets
47 from Gnumed.wxpython import gmFormWidgets
48 from Gnumed.wxpython import gmTimer
49 from Gnumed.wxpython import gmHospitalStayWidgets
50 from Gnumed.wxpython import gmProcedureWidgets
51 from Gnumed.wxpython import gmGenericEMRItemWorkflows
52
53
54 _log = logging.getLogger('gm.ui')
55
56
58 """
59 Dump the patient's EMR from GUI client
60 @param parent - The parent widget
61 @type parent - A wx.Window instance
62 """
63
64 if parent is None:
65 raise TypeError('expected wx.Window instance as parent, got <None>')
66
67 pat = gmPerson.gmCurrentPatient()
68 if not pat.connected:
69 gmDispatcher.send(signal='statustext', msg=_('Cannot export EMR. No active patient.'))
70 return False
71
72
73 wc = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files"))
74 defdir = os.path.abspath(os.path.expanduser(os.path.join('~', 'gnumed', pat.subdir_name)))
75 gmTools.mkdir(defdir)
76 fname = '%s-%s_%s.txt' % (_('emr-export'), pat['lastnames'], pat['firstnames'])
77 dlg = wx.FileDialog (
78 parent = parent,
79 message = _("Save patient's EMR as..."),
80 defaultDir = defdir,
81 defaultFile = fname,
82 wildcard = wc,
83 style = wx.FD_SAVE
84 )
85 choice = dlg.ShowModal()
86 fname = dlg.GetPath()
87 dlg.DestroyLater()
88 if choice != wx.ID_OK:
89 return None
90
91 _log.debug('exporting EMR to [%s]', fname)
92
93 output_file = io.open(fname, mode = 'wt', encoding = 'utf8', errors = 'replace')
94 exporter = gmPatientExporter.cEmrExport(patient = pat)
95 exporter.set_output_file(output_file)
96 exporter.dump_constraints()
97 exporter.dump_demographic_record(True)
98 exporter.dump_clinical_record()
99 exporter.dump_med_docs()
100 output_file.close()
101
102 gmDispatcher.send('statustext', msg = _('EMR successfully exported to file: %s') % fname, beep = False)
103 return fname
104
105
106 -class cEMRTree(wx.TreeCtrl, treemixin.ExpansionState):
107 """This wx.TreeCtrl derivative displays a tree view of a medical record."""
108
109
110 - def __init__(self, parent, id, *args, **kwds):
111 """Set up our specialised tree.
112 """
113 kwds['style'] = wx.TR_HAS_BUTTONS | wx.NO_BORDER | wx.TR_SINGLE
114 wx.TreeCtrl.__init__(self, parent, id, *args, **kwds)
115
116 self.__soap_display = None
117 self.__soap_display_mode = 'details'
118 self.__img_display = None
119 self.__cb__enable_display_mode_selection = lambda x:x
120 self.__cb__select_edit_mode = lambda x:x
121 self.__cb__add_soap_editor = lambda x:x
122 self.__pat = None
123 self.__curr_node = None
124 self.__expanded_nodes = None
125
126 self.__make_popup_menus()
127 self.__register_events()
128
129
130
131
133 return self.__soap_display
134
136 self.__soap_display = soap_display
137 self.__soap_display_prop_font = soap_display.GetFont()
138 self.__soap_display_mono_font = wx.Font(self.__soap_display_prop_font.GetNativeFontInfo())
139 self.__soap_display_mono_font.SetFamily(wx.FONTFAMILY_TELETYPE)
140 self.__soap_display_mono_font.SetPointSize(self.__soap_display_prop_font.GetPointSize() - 2)
141
142 soap_display = property(_get_soap_display, _set_soap_display)
143
144
146 return self.__img_display
147
150
151 image_display = property(_get_image_display, _set_image_display)
152
153
155 if not callable(callback):
156 raise ValueError('callback [%s] not callable' % callback)
157 self.__cb__enable_display_mode_selection = callback
158
159
161 if callback is None:
162 callback = lambda x:x
163 if not callable(callback):
164 raise ValueError('edit mode selector [%s] not callable' % callback)
165 self.__cb__select_edit_mode = callback
166
167 edit_mode_selector = property(lambda x:x, _set_edit_mode_selector)
168
169
171 if callback is None:
172 callback = lambda x:x
173 if not callable(callback):
174 raise ValueError('soap editor adder [%s] not callable' % callback)
175 self.__cb__add_soap_editor = callback
176
177 soap_editor_adder = property(lambda x:x, _set_soap_editor_adder)
178
179
180
181
183 if item is None:
184 return 'invalid item'
185
186 if not item.IsOk():
187 return 'invalid item'
188
189 try:
190 node_data = self.GetItemData(item)
191 except wx.wxAssertionError:
192 _log.exception('unfathomable self.GetItemData() problem occurred, faking root node')
193 _log.error('real node: %s', item)
194 _log.error('node.IsOk(): %s', item.IsOk())
195 _log.error('is root node: %s', item == self.GetRootItem())
196 _log.error('node attributes: %s', dir(item))
197 gmLog2.log_stack_trace()
198 return 'invalid item'
199
200 if isinstance(node_data, gmEMRStructItems.cHealthIssue):
201 return 'issue::%s' % node_data['pk_health_issue']
202 if isinstance(node_data, gmEMRStructItems.cEpisode):
203 return 'episode::%s' % node_data['pk_episode']
204 if isinstance(node_data, gmEMRStructItems.cEncounter):
205 return 'encounter::%s' % node_data['pk_encounter']
206
207 if isinstance(node_data, dict):
208 return 'dummy node::%s' % self.__pat.ID
209
210 return 'root node::%s' % self.__pat.ID
211
212
213
214
216 """Configures enabled event signals."""
217 self.Bind(wx.EVT_TREE_SEL_CHANGED, self._on_tree_item_selected)
218 self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self._on_tree_item_activated)
219 self.Bind(wx.EVT_TREE_ITEM_EXPANDING, self._on_tree_item_expanding)
220 self.Bind(wx.EVT_TREE_ITEM_MENU, self._on_tree_item_context_menu)
221
222
223
224 self.Bind(wx.EVT_TREE_ITEM_GETTOOLTIP, self._on_tree_item_gettooltip)
225
226
227 gmDispatcher.connect(signal = 'narrative_mod_db', receiver = self._on_narrative_mod_db)
228 gmDispatcher.connect(signal = 'clin.episode_mod_db', receiver = self._on_episode_mod_db)
229 gmDispatcher.connect(signal = 'clin.health_issue_mod_db', receiver = self._on_issue_mod_db)
230 gmDispatcher.connect(signal = 'clin.family_history_mod_db', receiver = self._on_issue_mod_db)
231
232
234 self.DeleteAllItems()
235 self.__expanded_nodes = None
236
237
239 """Updates EMR browser data."""
240
241
242
243 _log.debug('populating EMR tree')
244
245 wx.BeginBusyCursor()
246
247 if self.__pat is None:
248 self.clear_tree()
249 self.__expanded_nodes = None
250 wx.EndBusyCursor()
251 return True
252
253
254 root_item = self.__populate_root_node()
255 self.__curr_node = root_item
256 if self.__expanded_nodes is not None:
257 self.ExpansionState = self.__expanded_nodes
258 self.SelectItem(root_item)
259 self.Expand(root_item)
260 self.__update_text_for_selected_node()
261
262 wx.EndBusyCursor()
263 return True
264
265
267
268 self.DeleteAllItems()
269
270 root_item = self.AddRoot(_('EMR of %(lastnames)s, %(firstnames)s') % self.__pat.get_active_name())
271 self.SetItemData(root_item, None)
272 self.SetItemHasChildren(root_item, True)
273
274 self.__root_tooltip = self.__pat['description_gender'] + '\n'
275 if self.__pat['deceased'] is None:
276 self.__root_tooltip += ' %s (%s)\n\n' % (
277 self.__pat.get_formatted_dob(format = '%d %b %Y'),
278 self.__pat['medical_age']
279 )
280 else:
281 template = ' %s - %s (%s)\n\n'
282 self.__root_tooltip += template % (
283 self.__pat.get_formatted_dob(format = '%d.%b %Y'),
284 gmDateTime.pydt_strftime(self.__pat['deceased'], '%Y %b %d'),
285 self.__pat['medical_age']
286 )
287 self.__root_tooltip += gmTools.coalesce(self.__pat['comment'], '', '%s\n\n')
288 doc = self.__pat.primary_provider
289 if doc is not None:
290 self.__root_tooltip += '%s:\n' % _('Primary provider in this praxis')
291 self.__root_tooltip += ' %s %s %s (%s)%s\n\n' % (
292 gmTools.coalesce(doc['title'], gmPerson.map_gender2salutation(gender = doc['gender'])),
293 doc['firstnames'],
294 doc['lastnames'],
295 doc['short_alias'],
296 gmTools.bool2subst(doc['is_active'], '', ' [%s]' % _('inactive'))
297 )
298 if not ((self.__pat['emergency_contact'] is None) and (self.__pat['pk_emergency_contact'] is None)):
299 self.__root_tooltip += _('In case of emergency contact:') + '\n'
300 if self.__pat['emergency_contact'] is not None:
301 self.__root_tooltip += gmTools.wrap (
302 text = '%s\n' % self.__pat['emergency_contact'],
303 width = 60,
304 initial_indent = ' ',
305 subsequent_indent = ' '
306 )
307 if self.__pat['pk_emergency_contact'] is not None:
308 contact = self.__pat.emergency_contact_in_database
309 self.__root_tooltip += ' %s\n' % contact['description_gender']
310 self.__root_tooltip = self.__root_tooltip.strip('\n')
311 if self.__root_tooltip == '':
312 self.__root_tooltip = ' '
313
314 return root_item
315
316
318 """Displays information for the selected tree node."""
319
320 if self.__soap_display is None:
321 return
322
323 self.__soap_display.Clear()
324 self.__img_display.clear()
325
326 if self.__curr_node is None:
327 return
328
329 if not self.__curr_node.IsOk():
330 return
331
332 try:
333 node_data = self.GetItemData(self.__curr_node)
334 except wx.wxAssertionError:
335 node_data = None
336 _log.exception('unfathomable self.GetItemData() problem occurred, faking root node')
337 _log.error('real node: %s', self.__curr_node)
338 _log.error('node.IsOk(): %s', self.__curr_node.IsOk())
339 _log.error('is root node: %s', self.__curr_node == self.GetRootItem())
340 _log.error('node attributes: %s', dir(self.__curr_node))
341 gmLog2.log_stack_trace()
342
343 if isinstance(node_data, gmEMRStructItems.cHealthIssue):
344 self.__update_text_for_issue_node(node_data)
345 return
346
347
348 if isinstance(node_data, dict):
349 self.__update_text_for_pseudo_issue_node(node_data)
350 return
351
352 if isinstance(node_data, gmEMRStructItems.cEpisode):
353 self.__update_text_for_episode_node(node_data)
354 return
355
356 if isinstance(node_data, gmEMRStructItems.cEncounter):
357 self.__update_text_for_encounter_node(node_data)
358 return
359
360 if isinstance(node_data, gmGenericEMRItem.cGenericEMRItem):
361 self.__update_text_for_generic_node(node_data)
362 return
363
364
365 self.__update_text_for_root_node()
366
367
369
370
371 self.__root_context_popup = wx.Menu(title = _('EMR Actions:'))
372 item = self.__root_context_popup.Append(-1, _('Print EMR'))
373 self.Bind(wx.EVT_MENU, self.__print_emr, item)
374 item = self.__root_context_popup.Append(-1, _('Create health issue'))
375 self.Bind(wx.EVT_MENU, self.__create_issue, item)
376 item = self.__root_context_popup.Append(-1, _('Create episode'))
377 self.Bind(wx.EVT_MENU, self.__create_episode, item)
378 item = self.__root_context_popup.Append(-1, _('Create progress note'))
379 self.Bind(wx.EVT_MENU, self.__create_soap_editor, item)
380 item = self.__root_context_popup.Append(-1, _('Manage allergies'))
381 self.Bind(wx.EVT_MENU, self.__document_allergy, item)
382 item = self.__root_context_popup.Append(-1, _('Manage family history'))
383 self.Bind(wx.EVT_MENU, self.__manage_family_history, item)
384 item = self.__root_context_popup.Append(-1, _('Manage hospitalizations'))
385 self.Bind(wx.EVT_MENU, self.__manage_hospital_stays, item)
386 item = self.__root_context_popup.Append(-1, _('Manage occupation'))
387 self.Bind(wx.EVT_MENU, self.__manage_occupation, item)
388 item = self.__root_context_popup.Append(-1, _('Manage procedures'))
389 self.Bind(wx.EVT_MENU, self.__manage_procedures, item)
390 item = self.__root_context_popup.Append(-1, _('Manage vaccinations'))
391 self.Bind(wx.EVT_MENU, self.__manage_vaccinations, item)
392
393 self.__root_context_popup.AppendSeparator()
394
395
396 expand_menu = wx.Menu()
397 self.__root_context_popup.Append(wx.NewId(), _('Open EMR to ...'), expand_menu)
398 item = expand_menu.Append(-1, _('... issue level'))
399 self.Bind(wx.EVT_MENU, self.__expand_to_issue_level, item)
400 item = expand_menu.Append(-1, _('... episode level'))
401 self.Bind(wx.EVT_MENU, self.__expand_to_episode_level, item)
402 item = expand_menu.Append(-1, _('... encounter level'))
403 self.Bind(wx.EVT_MENU, self.__expand_to_encounter_level, item)
404
405
406 self.__issue_context_popup = wx.Menu(title = _('Health Issue Actions:'))
407 item = self.__issue_context_popup.Append(-1, _('Edit details'))
408 self.Bind(wx.EVT_MENU, self.__edit_issue, item)
409 item = self.__issue_context_popup.Append(-1, _('Delete'))
410 self.Bind(wx.EVT_MENU, self.__delete_issue, item)
411 self.__issue_context_popup.AppendSeparator()
412 item = self.__issue_context_popup.Append(-1, _('Open to encounter level'))
413 self.Bind(wx.EVT_MENU, self.__expand_issue_to_encounter_level, item)
414
415
416 item = self.__issue_context_popup.Append(-1, _('Create progress note'))
417 self.Bind(wx.EVT_MENU, self.__create_soap_editor, item)
418
419
420 self.__epi_context_popup = wx.Menu(title = _('Episode Actions:'))
421 item = self.__epi_context_popup.Append(-1, _('Toggle ongoing/closed'))
422 self.Bind(wx.EVT_MENU, self.__toggle_episode_open_close, item)
423 item = self.__epi_context_popup.Append(-1, _('Edit details'))
424 self.Bind(wx.EVT_MENU, self.__edit_episode, item)
425 item = self.__epi_context_popup.Append(-1, _('Delete'))
426 self.Bind(wx.EVT_MENU, self.__delete_episode, item)
427 item = self.__epi_context_popup.Append(-1, _('Promote'))
428 self.Bind(wx.EVT_MENU, self.__promote_episode_to_issue, item)
429 item = self.__epi_context_popup.Append(-1, _('Create progress note'))
430 self.Bind(wx.EVT_MENU, self.__create_soap_editor, item)
431 item = self.__epi_context_popup.Append(-1, _('Move encounters'))
432 self.Bind(wx.EVT_MENU, self.__move_encounters, item)
433
434
435 self.__enc_context_popup = wx.Menu(title = _('Encounter Actions:'))
436 item = self.__enc_context_popup.Append(-1, _('Move data to another episode'))
437 self.Bind(wx.EVT_MENU, self.__relink_encounter_data2episode, item)
438 item = self.__enc_context_popup.Append(-1, _('Edit details'))
439 self.Bind(wx.EVT_MENU, self.__edit_encounter_details, item)
440
441
442
443
444
445 item = self.__enc_context_popup.Append(-1, _('Move progress notes'))
446 self.Bind(wx.EVT_MENU, self.__move_progress_notes, item)
447 item = self.__enc_context_popup.Append(-1, _('Export for Medistar'))
448 self.Bind(wx.EVT_MENU, self.__export_encounter_for_medistar, item)
449
450
451 self.__generic_emr_item_context_popup = wx.Menu(title = _('Item Actions:'))
452 item = self.__generic_emr_item_context_popup.Append(-1, _('Edit item'))
453 self.Bind(wx.EVT_MENU, self.__on_edit_generic_emr_item, item)
454
455
457 self.__curr_node_data = self.GetItemData(self.__curr_node)
458
459 if isinstance(self.__curr_node_data, gmEMRStructItems.cHealthIssue):
460 self.PopupMenu(self.__issue_context_popup, pos)
461 return True
462
463 if isinstance(self.__curr_node_data, gmEMRStructItems.cEpisode):
464 self.PopupMenu(self.__epi_context_popup, pos)
465 return True
466
467 if isinstance(self.__curr_node_data, gmEMRStructItems.cEncounter):
468 self.PopupMenu(self.__enc_context_popup, pos)
469 return True
470
471 if isinstance(self.__curr_node_data, gmGenericEMRItem.cGenericEMRItem):
472 self.PopupMenu(self.__generic_emr_item_context_popup, pos)
473 return True
474
475 if self.__curr_node == self.GetRootItem():
476 self.PopupMenu(self.__root_context_popup, pos)
477 return True
478
479 return False
480
481
482
483
484
485
486
495
496
498 self.__curr_node_data['episode_open'] = not self.__curr_node_data['episode_open']
499 self.__curr_node_data.save()
500
501
504
505
508
509
511 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
512 parent = self,
513 id = -1,
514 caption = _('Deleting episode'),
515 button_defs = [
516 {'label': _('Yes, delete'), 'tooltip': _('Delete the episode if possible (it must be completely empty).')},
517 {'label': _('No, cancel'), 'tooltip': _('Cancel and do NOT delete the episode.')}
518 ],
519 question = _(
520 'Are you sure you want to delete this episode ?\n'
521 '\n'
522 ' "%s"\n'
523 ) % self.__curr_node_data['description']
524 )
525 result = dlg.ShowModal()
526 if result != wx.ID_YES:
527 return
528
529 if not gmEMRStructItems.delete_episode(episode = self.__curr_node_data):
530 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete episode. There is still clinical data recorded for it.'))
531
532
534 self.DeleteChildren(episode_node)
535
536 emr = self.__pat.emr
537 epi = self.GetItemData(episode_node)
538 encounters = emr.get_encounters(episodes = [epi['pk_episode']], skip_empty = True)
539 if len(encounters) == 0:
540 self.SetItemHasChildren(episode_node, False)
541 return
542
543 self.SetItemHasChildren(episode_node, True)
544
545 for enc in encounters:
546 label = '%s: %s' % (
547 enc['started'].strftime('%Y-%m-%d'),
548 gmTools.unwrap (
549 gmTools.coalesce (
550 gmTools.coalesce (
551 gmTools.coalesce (
552 enc.get_latest_soap (
553 soap_cat = 'a',
554 episode = epi['pk_episode']
555 ),
556 enc['assessment_of_encounter']
557 ),
558 enc['reason_for_encounter']
559 ),
560 enc['l10n_type']
561 ),
562 max_length = 40
563 )
564 )
565 encounter_node = self.AppendItem(episode_node, label)
566 self.SetItemData(encounter_node, enc)
567 self.SetItemHasChildren(encounter_node, True)
568
569 self.SortChildren(episode_node)
570
571
573 self.__cb__enable_display_mode_selection(True)
574 if self.__soap_display_mode == 'details':
575 txt = episode.format(left_margin = 1, patient = self.__pat)
576 font = self.__soap_display_prop_font
577 elif self.__soap_display_mode == 'journal':
578 txt = episode.format_as_journal(left_margin = 1)
579 font = self.__soap_display_prop_font
580 elif self.__soap_display_mode == 'revisions':
581 txt = episode.formatted_revision_history
582 font = self.__soap_display_mono_font
583 else:
584 txt = 'unknown SOAP display mode [%s]' % self.__soap_display_mode
585 font = self.__soap_display_prop_font
586 doc_folder = self.__pat.get_document_folder()
587 self.__img_display.refresh (
588 document_folder = doc_folder,
589 episodes = [ episode['pk_episode'] ]
590 )
591 self.__soap_display.SetFont(font)
592 self.__soap_display.WriteText(txt)
593 self.__soap_display.ShowPosition(0)
594
595
622
623
624
625
636
637
639 encounter = self.GetItemData(self.__curr_node)
640 node_parent = self.GetItemParent(self.__curr_node)
641 episode = self.GetItemData(node_parent)
642
643 gmNarrativeWorkflows.manage_progress_notes (
644 parent = self,
645 encounters = [encounter['pk_encounter']],
646 episodes = [episode['pk_episode']]
647 )
648
649
654
655
657
658 node_parent = self.GetItemParent(self.__curr_node)
659 owning_episode = self.GetItemData(node_parent)
660
661 episode_selector = gmNarrativeWidgets.cMoveNarrativeDlg (
662 self,
663 -1,
664 episode = owning_episode,
665 encounter = self.__curr_node_data
666 )
667
668 result = episode_selector.ShowModal()
669 episode_selector.DestroyLater()
670
671 if result == wx.ID_YES:
672 self.__populate_tree()
673
674
676 self.DeleteChildren(encounter_node)
677 encounter = self.GetItemData(encounter_node)
678 encounter_items = self.__pat.emr.get_generic_emr_items (
679 pk_encounters = [encounter['pk_encounter']],
680 pk_episodes = [self.GetItemData(self.GetItemParent(encounter_node))['pk_episode']]
681 )
682 if len(encounter_items) == 0:
683 self.SetItemHasChildren(encounter_node, False)
684 return
685
686 for enc_item in encounter_items:
687 item_node = self.AppendItem(encounter_node, '%s [%s] %s' % (
688 enc_item['clin_when'].strftime('%H:%M'),
689 enc_item.i18n_soap_cat,
690 enc_item.item_type_str
691 ))
692 self.SetItemData(item_node, enc_item)
693 self.SetItemHasChildren(item_node, False)
694
695
696
697
698
700 self.__cb__enable_display_mode_selection(True)
701 epi = self.GetItemData(self.GetItemParent(self.__curr_node))
702 if self.__soap_display_mode == 'revisions':
703 txt = encounter.formatted_revision_history
704 font = self.__soap_display_mono_font
705 else:
706 txt = encounter.format (
707 episodes = [epi['pk_episode']],
708 with_soap = True,
709 left_margin = 1,
710 patient = self.__pat,
711 with_co_encountlet_hints = True
712 )
713 font = self.__soap_display_prop_font
714 self.__soap_display.SetFont(font)
715 self.__soap_display.WriteText(txt)
716 self.__soap_display.ShowPosition(0)
717 self.__img_display.refresh (
718 document_folder = self.__pat.get_document_folder(),
719 episodes = [ epi['pk_episode'] ],
720 encounter = encounter['pk_encounter']
721 )
722
723
758
759
760
761
763 self.__cb__enable_display_mode_selection(False)
764 txt = gmTools.list2text (
765 generic_item.format(),
766 strip_leading_empty_lines = False,
767 strip_trailing_empty_lines = False,
768 strip_trailing_whitespace = True,
769 max_line_width = 85
770 )
771 self.__soap_display.SetFont(self.__soap_display_prop_font)
772 self.__soap_display.WriteText(txt)
773 self.__soap_display.ShowPosition(0)
774
775
777 self.__edit_generic_emr_item()
778
779
787
788
789
790
793
794
796 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
797 parent = self,
798 id = -1,
799 caption = _('Deleting health issue'),
800 button_defs = [
801 {'label': _('Yes, delete'), 'tooltip': _('Delete the health issue if possible (it must be completely empty).')},
802 {'label': _('No, cancel'), 'tooltip': _('Cancel and do NOT delete the health issue.')}
803 ],
804 question = _(
805 'Are you sure you want to delete this health issue ?\n'
806 '\n'
807 ' "%s"\n'
808 ) % self.__curr_node_data['description']
809 )
810 result = dlg.ShowModal()
811 if result != wx.ID_YES:
812 dlg.DestroyLater()
813 return
814
815 dlg.DestroyLater()
816
817 if not gmEMRStructItems.delete_health_issue(health_issue = self.__curr_node_data):
818 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete health issue. There is still clinical data recorded for it.'))
819
820
822
823 if not self.__curr_node.IsOk():
824 return
825
826 self.Expand(self.__curr_node)
827
828 epi, epi_cookie = self.GetFirstChild(self.__curr_node)
829 while epi.IsOk():
830 self.Expand(epi)
831 epi, epi_cookie = self.GetNextChild(self.__curr_node, epi_cookie)
832
833
835 self.DeleteChildren(issue_node)
836
837 issue = self.GetItemData(issue_node)
838 episodes = self.__pat.emr.get_episodes(issues = [issue['pk_health_issue']])
839 if len(episodes) == 0:
840 self.SetItemHasChildren(issue_node, False)
841 return
842
843 self.SetItemHasChildren(issue_node, True)
844
845 for episode in episodes:
846 range_str, range_str_verb, duration_str = episode.formatted_clinical_duration
847 episode_node = self.AppendItem(issue_node, '%s (%s)' % (
848 episode['description'],
849 range_str
850 ))
851 self.SetItemData(episode_node, episode)
852
853 self.SetItemHasChildren(episode_node, True)
854
855 self.SortChildren(issue_node)
856
857
859 self.__cb__enable_display_mode_selection(True)
860 if self.__soap_display_mode == 'details':
861 txt = issue.format(left_margin = 1, patient = self.__pat)
862 font = self.__soap_display_prop_font
863 elif self.__soap_display_mode == 'journal':
864 txt = issue.format_as_journal(left_margin = 1)
865 font = self.__soap_display_prop_font
866 elif self.__soap_display_mode == 'revisions':
867 txt = issue.formatted_revision_history
868 font = self.__soap_display_mono_font
869 else:
870 txt = 'invalid SOAP display mode [%s]' % self.__soap_display_mode
871 font = self.__soap_display_prop_font
872 epis = issue.episodes
873 if len(epis) > 0:
874 doc_folder = self.__pat.get_document_folder()
875 self.__img_display.refresh (
876 document_folder = doc_folder,
877 episodes = [ epi['pk_episode'] for epi in epis ],
878 do_async = True
879 )
880 self.__soap_display.SetFont(font)
881 self.__soap_display.WriteText(txt)
882 self.__soap_display.ShowPosition(0)
883
884
916
917
919 self.DeleteChildren(fake_issue_node)
920
921 episodes = self.__pat.emr.unlinked_episodes
922 if len(episodes) == 0:
923 self.SetItemHasChildren(fake_issue_node, False)
924 return
925
926 self.SetItemHasChildren(fake_issue_node, True)
927
928 for episode in episodes:
929 range_str, range_str_verb, duration_str = episode.formatted_clinical_duration
930 episode_node = self.AppendItem(fake_issue_node, '%s (%s)' % (
931 episode['description'],
932 range_str
933 ))
934 self.SetItemData(episode_node, episode)
935 if episode['episode_open']:
936 self.SetItemBold(fake_issue_node, True)
937
938 self.SetItemHasChildren(episode_node, True)
939
940 self.SortChildren(fake_issue_node)
941
942
944 self.__cb__enable_display_mode_selection(True)
945 if self.__soap_display_mode == 'details':
946 txt = _('Pool of unassociated episodes "%s":\n') % pseudo_issue['description']
947 epis = self.__pat.emr.get_episodes(unlinked_only = True, order_by = 'episode_open DESC, description')
948 if len(epis) > 0:
949 txt += '\n'
950 for epi in epis:
951 txt += epi.format (
952 left_margin = 1,
953 patient = self.__pat,
954 with_summary = True,
955 with_codes = False,
956 with_encounters = False,
957 with_documents = False,
958 with_hospital_stays = False,
959 with_procedures = False,
960 with_family_history = False,
961 with_tests = False,
962 with_vaccinations = False,
963 with_health_issue = False
964 )
965 txt += '\n'
966 else:
967 epis = self.__pat.emr.get_episodes(unlinked_only = True, order_by = 'episode_open DESC, description')
968 txt = ''
969 if len(epis) > 0:
970 txt += _(' Listing of unassociated episodes\n')
971 for epi in epis:
972 txt += ' %s\n' % (gmTools.u_box_horiz_4dashes * 60)
973 txt += epi.format (
974 left_margin = 1,
975 patient = self.__pat,
976 with_summary = False,
977 with_codes = False,
978 with_encounters = False,
979 with_documents = False,
980 with_hospital_stays = False,
981 with_procedures = False,
982 with_family_history = False,
983 with_tests = False,
984 with_vaccinations = False,
985 with_health_issue = False
986 )
987 txt += '\n'
988 txt += epi.format_as_journal(left_margin = 2)
989 self.__soap_display.SetFont(self.__soap_display_prop_font)
990 self.__soap_display.WriteText(txt)
991 self.__soap_display.ShowPosition(0)
992
993
994
995
998
999
1002
1003
1006
1007
1009 self.__cb__select_edit_mode(True)
1010 self.__cb__add_soap_editor(problem = self.__curr_node_data, allow_same_problem = False)
1011
1012
1014 dlg = gmAllergyWidgets.cAllergyManagerDlg(parent=self, id=-1)
1015
1016 if dlg.ShowModal() == wx.ID_OK:
1017 self.__expanded_nodes = self.ExpansionState
1018 self.__populate_tree()
1019 dlg.DestroyLater()
1020 return
1021
1022
1025
1026
1027 - def __manage_family_history(self, event):
1029
1030
1033
1034
1037
1038
1041
1042
1044
1045 root_item = self.GetRootItem()
1046
1047 if not root_item.IsOk():
1048 return
1049
1050 self.Expand(root_item)
1051
1052
1053 issue, issue_cookie = self.GetFirstChild(root_item)
1054 while issue.IsOk():
1055 self.Collapse(issue)
1056 epi, epi_cookie = self.GetFirstChild(issue)
1057 while epi.IsOk():
1058 self.Collapse(epi)
1059 epi, epi_cookie = self.GetNextChild(issue, epi_cookie)
1060 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
1061
1062
1064
1065 root_item = self.GetRootItem()
1066
1067 if not root_item.IsOk():
1068 return
1069
1070 self.Expand(root_item)
1071
1072
1073 issue, issue_cookie = self.GetFirstChild(root_item)
1074 while issue.IsOk():
1075 self.Expand(issue)
1076 epi, epi_cookie = self.GetFirstChild(issue)
1077 while epi.IsOk():
1078 self.Collapse(epi)
1079 epi, epi_cookie = self.GetNextChild(issue, epi_cookie)
1080 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
1081
1082
1084
1085 root_item = self.GetRootItem()
1086
1087 if not root_item.IsOk():
1088 return
1089
1090 self.Expand(root_item)
1091
1092
1093 issue, issue_cookie = self.GetFirstChild(root_item)
1094 while issue.IsOk():
1095 self.Expand(issue)
1096 epi, epi_cookie = self.GetFirstChild(issue)
1097 while epi.IsOk():
1098 self.Expand(epi)
1099 epi, epi_cookie = self.GetNextChild(issue, epi_cookie)
1100 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
1101
1102
1109
1110
1112 self.__curr_node_data = self.GetItemData(self.__curr_node)
1113 if isinstance(self.__curr_node_data, gmGenericEMRItem.cGenericEMRItem):
1114 self.__edit_generic_emr_item()
1115 return
1116
1117
1118
1119
1121 root_node = self.GetRootItem()
1122 self.DeleteChildren(root_node)
1123
1124 issues = [{
1125 'description': _('Unattributed episodes'),
1126 'laterality': None,
1127 'diagnostic_certainty_classification': None,
1128 'has_open_episode': False,
1129 'pk_health_issue': None
1130 }]
1131 issues.extend(self.__pat.emr.health_issues)
1132 for issue in issues:
1133 issue_node = self.AppendItem(root_node, '%s%s%s' % (
1134 issue['description'],
1135 gmTools.coalesce(issue['laterality'], '', ' [%s]', none_equivalents = [None, 'na']),
1136 gmTools.coalesce(issue['diagnostic_certainty_classification'], '', ' [%s]')
1137 ))
1138 self.SetItemBold(issue_node, issue['has_open_episode'])
1139 self.SetItemData(issue_node, issue)
1140
1141 self.SetItemHasChildren(issue_node, True)
1142
1143 self.SetItemHasChildren(root_node, (len(issues) != 0))
1144 self.SortChildren(root_node)
1145
1146
1148 self.__cb__enable_display_mode_selection(True)
1149 if self.__soap_display_mode == 'details':
1150 emr = self.__pat.emr
1151 txt = emr.format_summary()
1152 else:
1153 txt = self.__pat.emr.format_as_journal(left_margin = 1, patient = self.__pat)
1154 self.__soap_display.SetFont(self.__soap_display_prop_font)
1155 self.__soap_display.WriteText(txt)
1156 self.__soap_display.ShowPosition(0)
1157
1158
1159
1160
1162 self.__update_text_for_selected_node()
1163
1164
1166 self.__expanded_nodes = self.ExpansionState
1167 self.__populate_tree()
1168
1169
1171 self.__expanded_nodes = self.ExpansionState
1172 self.__populate_tree()
1173
1174
1176 event.Skip()
1177
1178 node = event.GetItem()
1179 if node == self.GetRootItem():
1180 self.__expand_root_node()
1181 return
1182
1183 node_data = self.GetItemData(node)
1184
1185 if isinstance(node_data, gmEMRStructItems.cHealthIssue):
1186 self.__expand_issue_node(issue_node = node)
1187 return
1188
1189 if isinstance(node_data, gmEMRStructItems.cEpisode):
1190 self.__expand_episode_node(episode_node = node)
1191 return
1192
1193
1194 if type(node_data) == type({}):
1195 self.__expand_pseudo_issue_node(fake_issue_node = node)
1196 return
1197
1198 if isinstance(node_data, gmEMRStructItems.cEncounter):
1199 self.__expand_encounter_node(encounter_node = node)
1200 return
1201
1202
1204 event.Skip()
1205 self.__curr_node = event.Item
1206 self.__edit_tree_item()
1207
1208
1210 sel_item = event.GetItem()
1211 self.__curr_node = sel_item
1212 self.__update_text_for_selected_node()
1213 return True
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1275 """Right button clicked: display the popup for the tree"""
1276 self.__curr_node = event.Item
1277 self.__show_context_menu(pos = event.Point)
1278 event.Skip()
1279
1280
1282 """Used in sorting items.
1283
1284 -1: 1 < 2
1285 0: 1 = 2
1286 1: 1 > 2
1287 """
1288
1289
1290 if not node1:
1291 _log.debug('invalid node 1')
1292 return 0
1293 if not node2:
1294 _log.debug('invalid node 2')
1295 return 0
1296
1297 if not node1.IsOk():
1298 _log.debug('invalid node 1')
1299 return 0
1300 if not node2.IsOk():
1301 _log.debug('invalid node 2')
1302 return 0
1303
1304 item1 = self.GetItemData(node1)
1305 item2 = self.GetItemData(node2)
1306
1307
1308 if isinstance(item1, type({})):
1309 return -1
1310 if isinstance(item2, type({})):
1311 return 1
1312
1313
1314 if isinstance(item1, gmEMRStructItems.cEncounter):
1315 if item1['started'] == item2['started']:
1316 return 0
1317 if item1['started'] > item2['started']:
1318 return -1
1319 return 1
1320
1321
1322 if isinstance(item1, gmEMRStructItems.cEpisode):
1323
1324 if item1['episode_open']:
1325 return -1
1326 if item2['episode_open']:
1327 return 1
1328 start1 = item1.best_guess_clinical_start_date
1329 start2 = item2.best_guess_clinical_start_date
1330 if start1 == start2:
1331 return 0
1332 if start1 < start2:
1333 return 1
1334 return -1
1335
1336
1337 if isinstance(item1, gmEMRStructItems.cHealthIssue):
1338
1339
1340 if item1['grouping'] is None:
1341 if item2['grouping'] is not None:
1342 return 1
1343
1344
1345 if item1['grouping'] is not None:
1346 if item2['grouping'] is None:
1347 return -1
1348
1349
1350 if (item1['grouping'] is None) and (item2['grouping'] is None):
1351 if item1['description'].lower() < item2['description'].lower():
1352 return -1
1353 if item1['description'].lower() > item2['description'].lower():
1354 return 1
1355 return 0
1356
1357
1358 if item1['grouping'] < item2['grouping']:
1359 return -1
1360
1361 if item1['grouping'] > item2['grouping']:
1362 return 1
1363
1364 if item1['description'].lower() < item2['description'].lower():
1365 return -1
1366
1367 if item1['description'].lower() > item2['description'].lower():
1368 return 1
1369
1370 return 0
1371
1372 _log.error('unknown item type during sorting EMR tree:')
1373 _log.error('item1: %s', type(item1))
1374 _log.error('item2: %s', type(item2))
1375
1376 return 0
1377
1378
1379
1380
1383
1385 if self.__pat == patient:
1386 return
1387 self.__pat = patient
1388 if patient is None:
1389 self.clear_tree()
1390 return
1391 return self.__populate_tree()
1392
1393 patient = property(_get_patient, _set_patient)
1394
1395
1397 return self.__soap_display_mode
1398
1400 if mode not in ['details', 'journal', 'revisions']:
1401 raise ValueError('details display mode must be one of "details", "journal", "revisions"')
1402 if self.__soap_display_mode == mode:
1403 return
1404 self.__soap_display_mode = mode
1405 self.__update_text_for_selected_node()
1406
1407 details_display_mode = property(_get_details_display_mode, _set_details_display_mode)
1408
1409
1410
1411 from Gnumed.wxGladeWidgets import wxgScrolledEMRTreePnl
1412
1422
1423
1424 from Gnumed.wxGladeWidgets import wxgSplittedEMRTreeBrowserPnl
1425
1427 """A splitter window holding an EMR tree.
1428
1429 The left hand side displays a scrollable EMR tree while
1430 on the right details for selected items are displayed.
1431
1432 Expects to be put into a Notebook.
1433 """
1444
1446 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
1447 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
1448 return True
1449
1450
1452 return self.__editing
1453
1455 self.__editing = editing
1456 self.enable_display_mode_selection(enable = not self.__editing)
1457 if self.__editing:
1458 self._BTN_switch_browse_edit.SetLabel(_('&Browse %s') % gmTools.u_ellipsis)
1459 self._PNL_browse.Hide()
1460 self._PNL_visual_soap.Hide()
1461 self._PNL_edit.Show()
1462 else:
1463 self._BTN_switch_browse_edit.SetLabel(_('&New notes %s') % gmTools.u_ellipsis)
1464 self._PNL_edit.Hide()
1465 self._PNL_visual_soap.Show()
1466 self._PNL_browse.Show()
1467 self._PNL_right_side.GetSizer().Layout()
1468
1469 editing = property(_get_editing, _set_editing)
1470
1471
1472
1473
1475 self._pnl_emr_tree._emr_tree.patient = None
1476 self._PNL_edit.patient = None
1477 return True
1478
1479
1481 if self.GetParent().GetCurrentPage() != self:
1482 return True
1483 self.repopulate_ui()
1484 return True
1485
1486
1489
1490
1493
1494
1497
1498
1501
1502
1503
1504
1506 """Fills UI with data."""
1507 pat = gmPerson.gmCurrentPatient()
1508 self._pnl_emr_tree._emr_tree.patient = pat
1509 self._PNL_edit.patient = pat
1510 self._splitter_browser.SetSashPosition(self._splitter_browser.GetSize()[0] // 3, True)
1511
1512 return True
1513
1514
1516 if self.editing:
1517 enable = False
1518 if enable:
1519 self._RBTN_details.Enable(True)
1520 self._RBTN_journal.Enable(True)
1521 self._RBTN_revisions.Enable(True)
1522 return
1523 self._RBTN_details.Enable(False)
1524 self._RBTN_journal.Enable(False)
1525 self._RBTN_revisions.Enable(False)
1526
1527
1529 self._PNL_edit._NB_soap_editors.add_editor(problem = problem, allow_same_problem = allow_same_problem)
1530
1531
1534
1535
1536 from Gnumed.wxGladeWidgets import wxgEMRJournalPluginPnl
1537
1539
1545
1546
1547
1548
1564
1565
1566
1568 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
1569 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
1570 return True
1571
1572
1573
1574
1576 self._TCTRL_journal.SetValue('')
1577 return True
1578
1579
1581 if self.GetParent().GetCurrentPage() != self:
1582 return True
1583 self.repopulate_ui()
1584 return True
1585
1586
1589
1590
1593
1594
1597
1598
1599 from Gnumed.wxGladeWidgets import wxgEMRListJournalPluginPnl
1600
1602
1614
1615
1616
1617
1619 self._LCTRL_journal.remove_items_safely()
1620 self._TCTRL_details.SetValue('')
1621
1622
1623 if self._RBTN_by_encounter.Value:
1624 order_by = 'encounter_started, pk_health_issue, pk_episode NULLS FIRST, scr, src_table, modified_when'
1625 date_col_header = _('Encounter')
1626 date_fields = ['encounter_started', 'modified_when']
1627 elif self._RBTN_by_last_modified.Value:
1628 order_by = 'modified_when, pk_health_issue, pk_episode NULLS FIRST, src_table, scr'
1629 date_col_header = _('Modified')
1630 date_fields = ['modified_when']
1631 elif self._RBTN_by_item_time.Value:
1632 order_by = 'clin_when, pk_health_issue, pk_episode NULLS FIRST, scr, src_table, modified_when'
1633 date_col_header = _('Happened')
1634 date_fields = ['clin_when', 'modified_when']
1635 else:
1636 raise ValueError('invalid EMR journal list sort state')
1637
1638 self._LCTRL_journal.set_columns([date_col_header, '', _('Entry'), _('Who / When')])
1639 self._LCTRL_journal.set_resize_column(3)
1640
1641
1642 journal = gmPerson.gmCurrentPatient().emr.get_generic_emr_items (
1643 pk_encounters = None,
1644 pk_episodes = None,
1645 pk_health_issues = None,
1646 use_active_encounter = True,
1647 order_by = order_by
1648 )
1649
1650
1651 items = []
1652 data = []
1653 prev_date = None
1654 for entry in journal:
1655 if entry['narrative'].strip() == '':
1656 continue
1657
1658 soap_cat = gmSoapDefs.soap_cat2l10n[entry['soap_cat']]
1659 who = '%s (%s)' % (entry['modified_by'], entry['date_modified'])
1660 try:
1661 entry_date = gmDateTime.pydt_strftime(entry[date_fields[0]], '%Y-%m-%d')
1662 except KeyError:
1663 entry_date = gmDateTime.pydt_strftime(entry[date_fields[1]], '%Y-%m-%d')
1664 if entry_date == prev_date:
1665 date2show = ''
1666 else:
1667 date2show = entry_date
1668 prev_date = entry_date
1669 lines_of_journal_entry = entry['narrative'].strip().split('\n')
1670 first_line = lines_of_journal_entry[0]
1671 items.append([date2show, soap_cat, first_line.rstrip(), who])
1672
1673
1674
1675
1676 data.append(entry)
1677 for line in lines_of_journal_entry[1:]:
1678 if line.strip() == '':
1679 continue
1680
1681 items.append(['', '', line.rstrip(), ''])
1682
1683
1684
1685
1686 data.append(entry)
1687
1688 self._LCTRL_journal.set_string_items(items)
1689
1690
1691
1692 self._LCTRL_journal.set_column_widths([wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE_USEHEADER])
1693 self._LCTRL_journal.set_data(data)
1694
1695 self._LCTRL_journal.SetFocus()
1696 return True
1697
1698
1699
1700
1702 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
1703 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
1704 return True
1705
1706
1708 if entry['src_table'] in self.__data:
1709 if entry['src_pk'] in self.__data[entry['src_table']]:
1710 return
1711
1712 else:
1713 self.__data[entry['src_table']] = {}
1714
1715 self.__data[entry['src_table']][entry['src_pk']] = {}
1716 self.__data[entry['src_table']][entry['src_pk']]['entry'] = entry
1717 self.__data[entry['src_table']][entry['src_pk']]['formatted_instance'] = None
1718 if entry['encounter_started'] is None:
1719 enc_duration = gmTools.u_diameter
1720 else:
1721 enc_duration = '%s - %s' % (
1722 gmDateTime.pydt_strftime(entry['encounter_started'], '%Y %b %d %H:%M'),
1723 gmDateTime.pydt_strftime(entry['encounter_last_affirmed'], '%H:%M')
1724 )
1725 self.__data[entry['src_table']][entry['src_pk']]['formatted_header'] = _(
1726 'Chart entry: %s [#%s in %s]\n'
1727 ' Modified: %s by %s (%s rev %s)\n'
1728 '\n'
1729 'Health issue: %s%s\n'
1730 'Episode: %s%s\n'
1731 'Encounter: %s%s'
1732 ) % (
1733 gmGenericEMRItem.generic_item_type_str(entry['src_table']),
1734 entry['src_pk'],
1735 entry['src_table'],
1736 entry['date_modified'],
1737 entry['modified_by'],
1738 gmTools.u_arrow2right,
1739 entry['row_version'],
1740 gmTools.coalesce(entry['health_issue'], gmTools.u_diameter, '%s'),
1741 gmTools.bool2subst(entry['issue_active'], ' (' + _('active') + ')', ' (' + _('inactive') + ')', ''),
1742 gmTools.coalesce(entry['episode'], gmTools.u_diameter, '%s'),
1743 gmTools.bool2subst(entry['episode_open'], ' (' + _('open') + ')', ' (' + _('closed') + ')', ''),
1744 enc_duration,
1745 gmTools.coalesce(entry['encounter_l10n_type'], '', ' (%s)'),
1746 )
1747 self.__data[entry['src_table']][entry['src_pk']]['formatted_root_item'] = _(
1748 '%s\n'
1749 '\n'
1750 ' rev %s (%s) by %s in <%s>'
1751 ) % (
1752 entry['narrative'].strip(),
1753 entry['row_version'],
1754 entry['date_modified'],
1755 entry['modified_by'],
1756 entry['src_table']
1757 )
1758
1759
1760
1761
1767
1768
1770 if self.GetParent().GetCurrentPage() != self:
1771 return True
1772 self.repopulate_ui()
1773 return True
1774
1775
1783
1784
1786 data = self._LCTRL_journal.get_item_data(item_idx = evt.Index)
1787 self._TCTRL_details.SetValue(data.format(eol = '\n'))
1788
1789 return
1790
1791 data = self._LCTRL_journal.get_item_data(item_idx = evt.Index)
1792 if self.__data[data['table']][data['pk']]['formatted_instance'] is None:
1793 txt = _(
1794 '%s\n'
1795 '%s\n'
1796 '%s'
1797 ) % (
1798 self.__data[data['table']][data['pk']]['formatted_header'],
1799 gmTools.u_box_horiz_4dashes * 40,
1800 self.__data[data['table']][data['pk']]['formatted_root_item']
1801 )
1802 self._TCTRL_details.SetValue(txt)
1803 self.__load_timer.Stop()
1804 self.__load_timer.Start(oneShot = True)
1805 return
1806
1807 txt = _(
1808 '%s\n'
1809 '%s\n'
1810 '%s'
1811 ) % (
1812 self.__data[data['table']][data['pk']]['formatted_header'],
1813 gmTools.u_box_horiz_4dashes * 40,
1814 self.__data[data['table']][data['pk']]['formatted_instance']
1815 )
1816 self._TCTRL_details.SetValue(txt)
1817
1818
1833
1834
1837
1838
1841
1842
1845
1846
1850
1851
1854
1855
1858
1859
1860
1861
1862
1863
1864
1865
1866 if __name__ == '__main__':
1867
1868 _log.info("starting emr browser...")
1869
1870
1871 patient = gmPersonSearch.ask_for_patient()
1872 if patient is None:
1873 print("No patient. Exiting gracefully...")
1874 sys.exit(0)
1875 gmPatSearchWidgets.set_active_patient(patient = patient)
1876
1877
1878 application = wx.PyWidgetTester(size=(800,600))
1879 emr_browser = cEMRBrowserPanel(application.frame, -1)
1880 emr_browser.refresh_tree()
1881
1882 application.frame.Show(True)
1883 application.MainLoop()
1884
1885
1886 if patient is not None:
1887 try:
1888 patient.cleanup()
1889 except Exception:
1890 print("error cleaning up patient")
1891
1892 _log.info("closing emr browser...")
1893
1894
1895