Package Gnumed :: Package wxpython :: Module gmEMRTimelineWidgets
[frames] | no frames]

Source Code for Module Gnumed.wxpython.gmEMRTimelineWidgets

  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   
 18   
 19  # GNUmed libs 
 20  if __name__ == '__main__': 
 21          sys.path.insert(0, '../../') 
 22  from Gnumed.pycommon import gmDispatcher 
 23  from Gnumed.pycommon import gmTools 
 24  from Gnumed.pycommon import gmMimeLib 
 25  from Gnumed.pycommon import gmDateTime 
 26   
 27  from Gnumed.business import gmPerson 
 28   
 29  from Gnumed.wxpython import gmRegetMixin 
 30   
 31  from Gnumed.exporters import gmTimelineExporter 
 32   
 33   
 34  _log = logging.getLogger('gm.ui.tl') 
 35   
 36  #============================================================ 
 37  from Gnumed.timelinelib.canvas.data import TimePeriod 
 38   
 39  # activate experimental container features 
 40  from Gnumed.timelinelib.features.experimental import experimentalfeatures 
 41  experimentalfeatures.EXTENDED_CONTAINER_HEIGHT.set_active(True) 
 42  experimentalfeatures.EXTENDED_CONTAINER_STRATEGY.set_active(True) 
 43   
 44  from Gnumed.timelinelib.canvas.data.timeperiod import TimePeriod 
 45  from Gnumed.timelinelib.calendar.gregorian.gregorian import GregorianDateTime 
 46   
 47  #------------------------------------------------------------ 
 48  from Gnumed.timelinelib.canvas import TimelineCanvas    # works because of __init__.py 
 49   
50 -class cEMRTimelinePnl(TimelineCanvas):
51
52 - def __init__(self, *args, **kwargs):
53 TimelineCanvas.__init__(self, args[0]) # args[0] should be "parent" 54 55 self.__init_ui() 56 self.__register_interests()
57 58 #--------------------------------------------------------
59 - def __init_ui(self):
60 appearance = self.GetAppearance() 61 appearance.set_balloons_visible(True) 62 appearance.set_hide_events_done(False) 63 appearance.set_colorize_weekends(True) 64 appearance.set_display_checkmark_on_events_done(True) 65 66 self.InitDragScroll(direction = wx.BOTH) 67 self.InitZoomSelect() 68 return 69 """ 70 appearance.set_legend_visible(self.config.show_legend) 71 appearance.set_minor_strip_divider_line_colour(self.config.minor_strip_divider_line_colour) 72 appearance.set_major_strip_divider_line_colour(self.config.major_strip_divider_line_colour) 73 appearance.set_now_line_colour(self.config.now_line_colour) 74 appearance.set_weekend_colour(self.config.weekend_colour) 75 appearance.set_bg_colour(self.config.bg_colour) 76 appearance.set_draw_period_events_to_right(self.config.draw_point_events_to_right) 77 appearance.set_text_below_icon(self.config.text_below_icon) 78 appearance.set_minor_strip_font(self.config.minor_strip_font) 79 appearance.set_major_strip_font(self.config.major_strip_font) 80 appearance.set_balloon_font(self.config.balloon_font) 81 appearance.set_legend_font(self.config.legend_font) 82 appearance.set_center_event_texts(self.config.center_event_texts) 83 appearance.set_never_show_period_events_as_point_events(self.config.never_show_period_events_as_point_events) 84 appearance.set_week_start(self.config.get_week_start()) 85 appearance.set_use_inertial_scrolling(self.config.use_inertial_scrolling) 86 appearance.set_fuzzy_icon(self.config.fuzzy_icon) 87 appearance.set_locked_icon(self.config.locked_icon) 88 appearance.set_hyperlink_icon(self.config.hyperlink_icon) 89 appearance.set_vertical_space_between_events(self.config.vertical_space_between_events) 90 appearance.set_skip_s_in_decade_text(self.config.skip_s_in_decade_text) 91 appearance.set_never_use_time(self.config.never_use_time) 92 appearance.set_legend_pos(self.config.legend_pos) 93 """
94 #-------------------------------------------------------- 95 # event handling 96 #--------------------------------------------------------
97 - def __register_interests(self):
98 self.Bind(wx.EVT_MOUSEWHEEL, self._on_mousewheel_action) 99 self.Bind(wx.EVT_MOTION, self._on_mouse_motion) 100 self.Bind(wx.EVT_LEFT_DCLICK, self._on_left_dclick) 101 self.Bind(wx.EVT_LEFT_DOWN, self._on_left_down) 102 self.Bind(wx.EVT_LEFT_UP, self._on_left_up) 103 self.Bind(wx.EVT_RIGHT_DOWN, self._on_right_down) 104 self.Bind(wx.EVT_RIGHT_UP, self._on_right_up)
105 106 #self.Bind(wx.EVT_MIDDLE_DOWN, self._on_middle_down) 107 108 #--------------------------------------------------------
109 - def _on_mouse_motion(self, evt):
110 # not scrolling or zooming: 111 self.DisplayBalloons(evt) 112 113 # in case we are drag-scrolling: 114 self.DragScroll(evt) 115 116 # in case we are drag-zooming: 117 self.DragZoom(evt)
118 119 #--------------------------------------------------------
120 - def _on_mousewheel_action(self, evt):
122 123 #--------------------------------------------------------
124 - def _on_left_dclick(self, evt):
125 self.CenterAtCursor(evt)
126 127 #--------------------------------------------------------
128 - def _on_left_down(self, evt):
129 self.StartDragScroll(evt)
130 131 #--------------------------------------------------------
132 - def _on_left_up(self, evt):
133 self.StopDragScroll()
134 135 #--------------------------------------------------------
136 - def _on_right_down(self, evt):
137 self.StartZoomSelect(evt)
138 139 #--------------------------------------------------------
140 - def _on_right_up(self, evt):
141 # right down-up sequence w/o mouse motion leads to 142 # "cannot zoom in deeper than 1 minute" 143 try: 144 self.StopDragZoom() 145 except ValueError: 146 _log.exception('drag-zoom w/o mouse motion')
147 148 #-------------------------------------------------------- 149 # internal API 150 #--------------------------------------------------------
151 - def center_at_today(self):
152 now = gmDateTime.pydt_now_here() 153 g_now = GregorianDateTime(now.year, now.month, now.day, now.hour, now.minute, now.second).to_time() 154 self.Navigate(lambda tp: tp.center(g_now))
155 156 #--------------------------------------------------------
157 - def clear_timeline(self):
158 self.set_timeline(None)
159 160 #--------------------------------------------------------
161 - def open_timeline(self, tl_file):
162 from Gnumed.timelinelib.db import db_open 163 db = db_open(tl_file) 164 db.display_in_canvas(self) 165 self.fit_care_era()
166 167 #--------------------------------------------------------
168 - def export_as_svg(self, filename=None):
169 if filename is None: 170 filename = gmTools.get_unique_filename(suffix = '.svg') 171 self.SaveAsSvg(filename) 172 return filename
173 174 #--------------------------------------------------------
175 - def export_as_png(self, filename=None):
176 if filename is None: 177 filename = gmTools.get_unique_filename(suffix = '.png') 178 self.SaveAsPng(filename) 179 return filename
180 181 #--------------------------------------------------------
182 - def fit_all_events(self):
183 all_events = self.controller.get_timeline().get_all_events() 184 if len(all_events) == 0: 185 period4all_events = None 186 start = self._first_time(all_events) 187 end = self._last_time(all_events) 188 period4all_events = TimePeriod(start, end).zoom(-1) 189 190 if period4all_events is None: 191 return 192 if period4all_events.is_period(): 193 self.Navigate(lambda tp: tp.update(period4all_events.start_time, period4all_events.end_time)) 194 else: 195 self.Navigate(lambda tp: tp.center(period4all_events.mean_time()))
196 197 #--------------------------------------------------------
198 - def _first_time(self, events):
199 start_time = lambda event: event.get_start_time() 200 return start_time(min(events, key=start_time))
201 202 #--------------------------------------------------------
203 - def _last_time(self, events):
204 end_time = lambda event: event.get_end_time() 205 return end_time(max(events, key = end_time))
206 207 #--------------------------------------------------------
208 - def fit_care_era(self):
209 all_eras = self.controller.get_timeline().get_all_eras() 210 care_era = [ e for e in all_eras if e.name == gmTimelineExporter.ERA_NAME_CARE_PERIOD ][0] 211 era_period = care_era.time_period 212 if era_period.is_period(): 213 self.Navigate(lambda tp: tp.update(era_period.start_time, era_period.end_time)) 214 else: 215 self.Navigate(lambda tp: tp.center(era_period.mean_time()))
216 217 #--------------------------------------------------------
218 - def fit_last_year(self):
219 end = gmDateTime.pydt_now_here() 220 g_end = GregorianDateTime(end.year, end.month, end.day, end.hour, end.minute, end.second).to_time() 221 g_start = GregorianDateTime(end.year - 1, end.month, end.day, end.hour, end.minute, end.second).to_time() 222 last_year = TimePeriod(g_start, g_end) 223 self.Navigate(lambda tp: tp.update(last_year.start_time, last_year.end_time))
224 225 #============================================================ 226 from Gnumed.wxGladeWidgets import wxgEMRTimelinePluginPnl 227
228 -class cEMRTimelinePluginPnl(wxgEMRTimelinePluginPnl.wxgEMRTimelinePluginPnl, gmRegetMixin.cRegetOnPaintMixin):
229 """Panel holding a number of widgets. Used as notebook page."""
230 - def __init__(self, *args, **kwargs):
231 self.__tl_file = None 232 wxgEMRTimelinePluginPnl.wxgEMRTimelinePluginPnl.__init__(self, *args, **kwargs) 233 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 234 # self.__init_ui() 235 self.__register_interests()
236 237 #-------------------------------------------------------- 238 # event handling 239 #--------------------------------------------------------
240 - def __register_interests(self):
241 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
242 # gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._schedule_data_reget) 243 244 #--------------------------------------------------------
246 self._PNL_timeline.clear_timeline()
247 248 #--------------------------------------------------------
249 - def _on_refresh_button_pressed(self, event):
250 self._populate_with_data()
251 252 #--------------------------------------------------------
253 - def _on_save_button_pressed(self, event):
254 if self.__tl_file is None: 255 return 256 dlg = wx.FileDialog ( 257 parent = self, 258 message = _("Save timeline as images (SVG, PNG) under..."), 259 defaultDir = os.path.expanduser(os.path.join('~', 'gnumed')), 260 defaultFile = 'timeline.svg', 261 wildcard = '%s (*.svg)|*.svg' % _('SVG files'), 262 style = wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT 263 ) 264 choice = dlg.ShowModal() 265 fname = dlg.GetPath() 266 dlg.Destroy() 267 if choice != wx.ID_OK: 268 return False 269 self._PNL_timeline.export_as_svg(filename = fname) 270 self._PNL_timeline.export_as_png(filename = gmTools.fname_stem_with_path(fname) + '.png')
271 272 #--------------------------------------------------------
273 - def _on_print_button_pressed(self, event):
274 if self.__tl_file is None: 275 return 276 tl_image_file = self._PNL_timeline.export_as_png() 277 gmMimeLib.call_viewer_on_file(aFile = tl_image_file, block = None)
278 279 #--------------------------------------------------------
280 - def _on_export_area_button_pressed(self, event):
281 if self.__tl_file is None: 282 return 283 pat = gmPerson.gmCurrentPatient() 284 if not pat.connected: 285 return 286 pat.export_area.add_file(filename = self._PNL_timeline.export_as_png(), hint = _('timeline image (png)')) 287 pat.export_area.add_file(filename = self._PNL_timeline.export_as_svg(), hint = _('timeline image (svg)')) 288 pat.export_area.add_file(filename = self.__tl_file, hint = _('timeline data (xml)'))
289 290 #--------------------------------------------------------
291 - def _on_zoom_in_button_pressed(self, event):
292 self._PNL_timeline.zoom_in()
293 294 #--------------------------------------------------------
295 - def _on_zoom_out_button_pressed(self, event):
296 self._PNL_timeline.zoom_out()
297 298 #--------------------------------------------------------
299 - def _on_go2day_button_pressed(self, event):
300 self._PNL_timeline.center_at_today()
301 302 #--------------------------------------------------------
303 - def _on_zoom_fit_all_button_pressed(self, event):
304 self._PNL_timeline.fit_all_events()
305 306 #--------------------------------------------------------
308 self._PNL_timeline.fit_last_year()
309 310 #--------------------------------------------------------
312 self._PNL_timeline.fit_care_era()
313 314 #-------------------------------------------------------- 315 # notebook plugin glue 316 #--------------------------------------------------------
317 - def repopulate_ui(self):
318 self._populate_with_data()
319 320 #-------------------------------------------------------- 321 # internal API 322 #-------------------------------------------------------- 323 # def __init_ui(self): 324 # pass 325 #-------------------------------------------------------- 326 # reget mixin API 327 # 328 # remember to call 329 # self._schedule_data_reget() 330 # whenever you learn of data changes from database 331 # listener threads, dispatcher signals etc. 332 #--------------------------------------------------------
333 - def _populate_with_data(self):
334 pat = gmPerson.gmCurrentPatient() 335 if not pat.connected: 336 return True 337 338 wx.BeginBusyCursor() 339 try: 340 self.__tl_file = gmTimelineExporter.create_timeline_file(patient = pat) 341 self._PNL_timeline.open_timeline(self.__tl_file) 342 except Exception: # more specifically: TimelineIOError 343 _log.exception('cannot load EMR from timeline XML') 344 self._PNL_timeline.clear_timeline() 345 self.__tl_file = gmTimelineExporter.create_fake_timeline_file(patient = pat) 346 self._PNL_timeline.open_timeline(self.__tl_file) 347 return True 348 finally: 349 wx.EndBusyCursor() 350 351 return True
352 353 #============================================================ 354