| Home | Trees | Indices | Help |
|
|---|
|
|
1 """GNUmed patient EMR timeline browser.
2
3 Uses the excellent TheTimlineProject.
4 """
5 #================================================================
6 __author__ = "Karsten.Hilbert@gmx.net"
7 __license__ = "GPL v2 or later"
8
9 # std lib
10 import sys
11 import logging
12 import os.path
13
14
15 # 3rd party
16 import wx
17 import lxml.etree as lxml_etree
18
19
20 # GNUmed libs
21 if __name__ == '__main__':
22 sys.path.insert(0, '../../')
23 from Gnumed.pycommon import gmDispatcher
24 from Gnumed.pycommon import gmTools
25 from Gnumed.pycommon import gmMimeLib
26 from Gnumed.pycommon import gmDateTime
27
28 from Gnumed.business import gmPerson
29
30 from Gnumed.wxpython import gmRegetMixin
31
32 from Gnumed.exporters import gmTimelineExporter
33
34
35 _log = logging.getLogger('gm.ui.tl')
36
37 #============================================================
38 from Gnumed.timelinelib.canvas.data import TimePeriod
39
40 # activate experimental container features
41 from Gnumed.timelinelib.features.experimental import experimentalfeatures
42 experimentalfeatures.EXTENDED_CONTAINER_HEIGHT.set_active(True)
43 experimentalfeatures.EXTENDED_CONTAINER_STRATEGY.set_active(True)
44
45 from Gnumed.timelinelib.canvas.data.timeperiod import TimePeriod
46 from Gnumed.timelinelib.calendar.gregorian.gregorian import GregorianDateTime
47
48 #------------------------------------------------------------
49 from Gnumed.timelinelib.canvas import TimelineCanvas # works because of __init__.py
50
52
54 TimelineCanvas.__init__(self, args[0]) # args[0] should be "parent"
55
56 self.__init_ui()
57 self.__register_interests()
58
59 #--------------------------------------------------------
61 appearance = self.GetAppearance()
62 appearance.set_balloons_visible(True)
63 appearance.set_hide_events_done(False)
64 appearance.set_colorize_weekends(True)
65 appearance.set_display_checkmark_on_events_done(True)
66
67 self.InitDragScroll(direction = wx.BOTH)
68 self.InitZoomSelect()
69 return
70 """
71 appearance.set_legend_visible(self.config.show_legend)
72 appearance.set_minor_strip_divider_line_colour(self.config.minor_strip_divider_line_colour)
73 appearance.set_major_strip_divider_line_colour(self.config.major_strip_divider_line_colour)
74 appearance.set_now_line_colour(self.config.now_line_colour)
75 appearance.set_weekend_colour(self.config.weekend_colour)
76 appearance.set_bg_colour(self.config.bg_colour)
77 appearance.set_draw_period_events_to_right(self.config.draw_point_events_to_right)
78 appearance.set_text_below_icon(self.config.text_below_icon)
79 appearance.set_minor_strip_font(self.config.minor_strip_font)
80 appearance.set_major_strip_font(self.config.major_strip_font)
81 appearance.set_balloon_font(self.config.balloon_font)
82 appearance.set_legend_font(self.config.legend_font)
83 appearance.set_center_event_texts(self.config.center_event_texts)
84 appearance.set_never_show_period_events_as_point_events(self.config.never_show_period_events_as_point_events)
85 appearance.set_week_start(self.config.get_week_start())
86 appearance.set_use_inertial_scrolling(self.config.use_inertial_scrolling)
87 appearance.set_fuzzy_icon(self.config.fuzzy_icon)
88 appearance.set_locked_icon(self.config.locked_icon)
89 appearance.set_hyperlink_icon(self.config.hyperlink_icon)
90 appearance.set_vertical_space_between_events(self.config.vertical_space_between_events)
91 appearance.set_skip_s_in_decade_text(self.config.skip_s_in_decade_text)
92 appearance.set_never_use_time(self.config.never_use_time)
93 appearance.set_legend_pos(self.config.legend_pos)
94 """
95 #--------------------------------------------------------
96 # event handling
97 #--------------------------------------------------------
99 self.Bind(wx.EVT_MOUSEWHEEL, self._on_mousewheel_action)
100 self.Bind(wx.EVT_MOTION, self._on_mouse_motion)
101 self.Bind(wx.EVT_LEFT_DCLICK, self._on_left_dclick)
102 self.Bind(wx.EVT_LEFT_DOWN, self._on_left_down)
103 self.Bind(wx.EVT_LEFT_UP, self._on_left_up)
104 self.Bind(wx.EVT_RIGHT_DOWN, self._on_right_down)
105 self.Bind(wx.EVT_RIGHT_UP, self._on_right_up)
106
107 #self.Bind(wx.EVT_MIDDLE_DOWN, self._on_middle_down)
108
109 #--------------------------------------------------------
111 # not scrolling or zooming:
112 self.DisplayBalloons(evt)
113
114 # in case we are drag-scrolling:
115 self.DragScroll(evt)
116
117 # in case we are drag-zooming:
118 self.DragZoom(evt)
119
120 #--------------------------------------------------------
122 self.SpecialScrollVerticallyOnMouseWheel(evt)
123
124 #--------------------------------------------------------
126 self.CenterAtCursor(evt)
127
128 #--------------------------------------------------------
130 self.StartDragScroll(evt)
131
132 #--------------------------------------------------------
134 self.StopDragScroll()
135
136 #--------------------------------------------------------
138 self.StartZoomSelect(evt)
139
140 #--------------------------------------------------------
142 # right down-up sequence w/o mouse motion leads to
143 # "cannot zoom in deeper than 1 minute"
144 try:
145 self.StopDragZoom()
146 except ValueError:
147 _log.exception('drag-zoom w/o mouse motion')
148
149 #--------------------------------------------------------
150 # internal API
151 #--------------------------------------------------------
153 now = gmDateTime.pydt_now_here()
154 g_now = GregorianDateTime(now.year, now.month, now.day, now.hour, now.minute, now.second).to_time()
155 self.Navigate(lambda tp: tp.center(g_now))
156
157 #--------------------------------------------------------
159 self.SetTimeline(None)
160
161 #--------------------------------------------------------
163 if not self._validate_timeline_file(tl_filename):
164 gmDispatcher.send(signal = 'statustext', msg = 'Timeline file failed to validate.')
165 from Gnumed.timelinelib.db import db_open
166 db = db_open(tl_filename)
167 db.display_in_canvas(self)
168 self.fit_care_era()
169
170 #--------------------------------------------------------
172 if filename is None:
173 filename = gmTools.get_unique_filename(suffix = '.svg')
174 self.SaveAsSvg(filename)
175 return filename
176
177 #--------------------------------------------------------
179 if filename is None:
180 filename = gmTools.get_unique_filename(suffix = '.png')
181 self.SaveAsPng(filename)
182 return filename
183
184 #--------------------------------------------------------
186 all_events = self._controller.get_timeline().get_all_events()
187 if len(all_events) == 0:
188 period4all_events = None
189 start = self._first_time(all_events)
190 end = self._last_time(all_events)
191 period4all_events = TimePeriod(start, end).zoom(-1)
192
193 if period4all_events is None:
194 return
195 if period4all_events.is_period():
196 self.Navigate(lambda tp: tp.update(period4all_events.start_time, period4all_events.end_time))
197 else:
198 self.Navigate(lambda tp: tp.center(period4all_events.mean_time()))
199
200 #--------------------------------------------------------
202 start_time = lambda event: event.get_start_time()
203 return start_time(min(events, key=start_time))
204
205 #--------------------------------------------------------
209
210 #--------------------------------------------------------
212 all_eras = self._controller.get_timeline().get_all_eras()
213 care_era = [ e for e in all_eras if e.name == gmTimelineExporter.ERA_NAME_CARE_PERIOD ][0]
214 era_period = care_era.time_period
215 if era_period.is_period():
216 self.Navigate(lambda tp: tp.update(era_period.start_time, era_period.end_time))
217 else:
218 self.Navigate(lambda tp: tp.center(era_period.mean_time()))
219
220 #--------------------------------------------------------
222 end = gmDateTime.pydt_now_here()
223 g_end = GregorianDateTime(end.year, end.month, end.day, end.hour, end.minute, end.second).to_time()
224 g_start = GregorianDateTime(end.year - 1, end.month, end.day, end.hour, end.minute, end.second).to_time()
225 last_year = TimePeriod(g_start, g_end)
226 self.Navigate(lambda tp: tp.update(last_year.start_time, last_year.end_time))
227
228 #--------------------------------------------------------
230 xsd_name = 'timeline.xsd'
231 xsd_paths = [
232 os.path.join(gmTools.gmPaths().system_app_data_dir, 'resources', 'timeline', xsd_name),
233 # maybe in dev tree
234 os.path.join(gmTools.gmPaths().local_base_dir, 'resources', 'timeline', xsd_name)
235 ]
236 xml_schema = None
237 for xsd_filename in xsd_paths:
238 _log.debug('XSD: %s', xsd_filename)
239 if not os.path.exists(xsd_filename):
240 _log.warning('not found')
241 continue
242 try:
243 xml_schema = lxml_etree.XMLSchema(file = xsd_filename)
244 break
245 except lxml_etree.XMLSchemaParseError:
246 _log.exception('cannot parse')
247 if xml_schema is None:
248 _log.error('no XSD found')
249 return False
250
251 with open(tl_filename, encoding = 'utf-8') as tl_file:
252 try:
253 xml_doc = lxml_etree.parse(tl_file)
254 except lxml_etree.XMLSyntaxError:
255 _log.exception('[%s] does not parse as XML', tl_filename)
256 return False
257
258 if xml_schema.validate(xml_doc):
259 _log.debug('[%s] seems valid', tl_filename)
260 return True
261
262 _log.warning('[%s] does not validate against [%s]', tl_filename, xsd_filename)
263 for entry in xml_schema.error_log:
264 _log.debug(entry)
265 return False
266
267 #============================================================
268 from Gnumed.wxGladeWidgets import wxgEMRTimelinePluginPnl
269
270 -class cEMRTimelinePluginPnl(wxgEMRTimelinePluginPnl.wxgEMRTimelinePluginPnl, gmRegetMixin.cRegetOnPaintMixin):
271 """Panel holding a number of widgets. Used as notebook page."""
273 self.__tl_file = None
274 wxgEMRTimelinePluginPnl.wxgEMRTimelinePluginPnl.__init__(self, *args, **kwargs)
275 gmRegetMixin.cRegetOnPaintMixin.__init__(self)
276 # self.__init_ui()
277 self.__register_interests()
278
279 #--------------------------------------------------------
280 # event handling
281 #--------------------------------------------------------
283 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
284 # gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._schedule_data_reget)
285
286 #--------------------------------------------------------
288 self._PNL_timeline.clear_timeline()
289
290 #--------------------------------------------------------
293
294 #--------------------------------------------------------
314
315 #--------------------------------------------------------
321
322 #--------------------------------------------------------
332
333 #--------------------------------------------------------
336
337 #--------------------------------------------------------
340
341 #--------------------------------------------------------
344
345 #--------------------------------------------------------
348
349 #--------------------------------------------------------
352
353 #--------------------------------------------------------
356
357 #--------------------------------------------------------
358 # notebook plugin glue
359 #--------------------------------------------------------
362
363 #--------------------------------------------------------
364 # internal API
365 #--------------------------------------------------------
366 # def __init_ui(self):
367 # pass
368 #--------------------------------------------------------
369 # reget mixin API
370 #
371 # remember to call
372 # self._schedule_data_reget()
373 # whenever you learn of data changes from database
374 # listener threads, dispatcher signals etc.
375 #--------------------------------------------------------
377 pat = gmPerson.gmCurrentPatient()
378 if not pat.connected:
379 return True
380
381 wx.BeginBusyCursor()
382 try:
383 self.__tl_file = gmTimelineExporter.create_timeline_file(patient = pat)
384 self._PNL_timeline.open_timeline(self.__tl_file)
385 except Exception: # more specifically: TimelineIOError
386 _log.exception('cannot load EMR from timeline XML')
387 self._PNL_timeline.clear_timeline()
388 self.__tl_file = gmTimelineExporter.create_fake_timeline_file(patient = pat)
389 self._PNL_timeline.open_timeline(self.__tl_file)
390 return True
391 finally:
392 wx.EndBusyCursor()
393
394 return True
395
396 #============================================================
397
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Sat Feb 29 02:55:27 2020 | http://epydoc.sourceforge.net |