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):
    121                  self.SpecialScrollVerticallyOnMouseWheel(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          #--------------------------------------------------------
    245 -        def _on_pre_patient_unselection(self):
    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.DestroyLater()
    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          #--------------------------------------------------------
    307 -        def _on_zoom_fit_last_year_button_pressed(self, event):
    308                  self._PNL_timeline.fit_last_year()
    309
    310          #--------------------------------------------------------
    311 -        def _on_zoom_fit_care_period_button_pressed(self, event):
    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