Package Gnumed :: Package timelinelib :: Package canvas :: Module timelinecanvascontroller |
[frames] | no frames]

# Source Code for Module Gnumed.timelinelib.canvas.timelinecanvascontroller

      1  # Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018  Rickard Lindberg, Roger Lindberg
      2  #
      3  # This file is part of Timeline.
      4  #
      5  # Timeline is free software: you can redistribute it and/or modify
      6  # it under the terms of the GNU General Public License as published by
      7  # the Free Software Foundation, either version 3 of the License, or
      8  # (at your option) any later version.
      9  #
     10  # Timeline is distributed in the hope that it will be useful,
     11  # but WITHOUT ANY WARRANTY; without even the implied warranty of
     12  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13  # GNU General Public License for more details.
     14  #
     15  # You should have received a copy of the GNU General Public License
     16  # along with Timeline.  If not, see <http://www.gnu.org/licenses/>.
     17
     18
     19  import wx
     20
     21  from timelinelib.canvas.appearance import Appearance
     22  from timelinelib.canvas.backgrounddrawers.defaultbgdrawer import DefaultBackgroundDrawer
     23  from timelinelib.canvas.drawing import get_drawer
     24  from timelinelib.canvas.drawing.viewproperties import ViewProperties
     25  from timelinelib.canvas.eventboxdrawers.defaulteventboxdrawer import DefaultEventBoxDrawer
     26  from timelinelib.canvas.events import create_timeline_redrawn_event
     27  from timelinelib import DEBUG_ENABLED
     28  from timelinelib.monitoring import Monitoring
     29  from timelinelib.wxgui.components.font import Font
     30
     31
     32 -class TimelineCanvasController(object):
     33
     34 -    def __init__(self, view, drawer=None):
     35          """
     36          The purpose of the drawer argument is make testing easier. A test can
     37          mock a drawer and use the mock by sending it in the drawer argument.
     38          Normally the drawer is collected with the get_drawer() method.
     39          """
     40          self.appearance = None
     41          self.monitoring = Monitoring()
     42          self.view = view
     43          self._set_drawing_algorithm(drawer)
     44          self.timeline = None
     45          self.set_appearance(Appearance())
     46          self.set_event_box_drawer(DefaultEventBoxDrawer())
     47          self.set_background_drawer(self.get_saved_background_drawer())
     48          self._fast_draw = False
     49          self._set_initial_values_to_member_variables()
     50          self._set_colors_and_styles()
     51
     52      @property
     53 -    def scene(self):
     54          return self.drawing_algorithm.scene
     55
     56 -    def get_appearance(self):
     57          return self.appearance
     58
     59 -    def set_appearance(self, appearance):
     60          if self.appearance is not None:
     61              self.appearance.unlisten(self._redraw_timeline)
     62          self.appearance = appearance
     63          self.appearance.listen_for_any(self._redraw_timeline)
     64          self.redraw_timeline()
     65
     66 -    def get_saved_background_drawer(self):
     67          return DefaultBackgroundDrawer()
     68
     69 -    def set_event_box_drawer(self, event_box_drawer):
     70          self.drawing_algorithm.set_event_box_drawer(event_box_drawer)
     71
     72 -    def set_background_drawer(self, drawer):
     73          self.drawing_algorithm.set_background_drawer(drawer)
     74
     75 -    def get_timeline(self):
     76          return self.timeline
     77
     78 -    def get_view_properties(self):
     79          return self.view_properties
     80
     81 -    def set_timeline(self, timeline):
     82          """Inform what timeline to draw."""
     83          self._unregister_timeline(self.timeline)
     84          if timeline is None:
     85              self._set_null_timeline()
     86          else:
     87              self._set_non_null_timeline(timeline)
     88
     89 -    def use_fast_draw(self, value):
     90          self._fast_draw = value
     91
     92 -    def navigate(self, navigation_fn):
     93          old_period = self.view_properties.displayed_period
     94          new_period = navigation_fn(old_period)
     95          MIN_ZOOM_DELTA, min_zoom_error_text = self.time_type.get_min_zoom_delta()
     96          if new_period.delta() < MIN_ZOOM_DELTA:
     97              raise ValueError(min_zoom_error_text)
     98          min_time = self.time_type.get_min_time()
     99          if min_time is not None and new_period.start_time < min_time:
    100              raise ValueError(_("Can't scroll more to the left"))
    101          max_time = self.time_type.get_max_time()
    102          if max_time is not None and new_period.end_time > max_time:
    103              raise ValueError(_("Can't scroll more to the right"))
    104          self.view_properties.displayed_period = new_period
    105          self.redraw_timeline()
    106
    107 -    def _set_null_timeline(self):
    108          self.timeline = None
    109          self.time_type = None
    110          self.view.Disable()
    111
    112 -    def _set_non_null_timeline(self, timeline):
    113          self.timeline = timeline
    114          self.time_type = timeline.get_time_type()
    115          self.timeline.register(self._timeline_changed)
    116          self.view_properties.unlisten(self._redraw_timeline)
    117          properties_loaded = self._load_view_properties()
    118          if properties_loaded:
    119              self.view_properties.listen_for_any(self._redraw_timeline)
    120              self._redraw_timeline()
    121              self.view.Enable()
    122              self.view.SetFocus()
    123
    124 -    def _load_view_properties(self):
    125          properties_loaded = True
    126          try:
    127              self.view_properties.clear_db_specific()
    128              self.timeline.load_view_properties(self.view_properties)
    129              if self.view_properties.displayed_period is None:
    130                  default_tp = self.time_type.get_default_time_period()
    131                  self.view_properties.displayed_period = default_tp
    132              self.view_properties.hscroll_amount = 0
    133          except Exception:
    134              properties_loaded = False
    135          return properties_loaded
    136
    137 -    def _unregister_timeline(self, timeline):
    138          if timeline is not None:
    139              timeline.unregister(self._timeline_changed)
    140
    141 -    def get_time_period(self):
    142          """Return currently displayed time period."""
    143          if self.timeline is None:
    144              raise Exception(_("No timeline set"))
    145          return self.view_properties.displayed_period
    146
    147 -    def redraw_timeline(self):
    148          self._redraw_timeline()
    149
    150 -    def window_resized(self):
    151          self._redraw_timeline()
    152
    153 -    def _one_and_only_one_event_selected(self):
    154          selected_event_ids = self.view_properties.get_selected_event_ids()
    155          nbr_of_selected_event_ids = len(selected_event_ids)
    156          return nbr_of_selected_event_ids == 1
    157
    158 -    def _get_first_selected_event(self):
    159          selected_event_ids = self.view_properties.get_selected_event_ids()
    160          if len(selected_event_ids) > 0:
    161              event_id = selected_event_ids[0]
    162              return self.timeline.find_event_with_id(event_id)
    163          return None
    164
    165 -    def get_time(self, x):
    166          return self.drawing_algorithm.get_time(x)
    167
    168 -    def event_with_rect_at(self, x, y, alt_down=False):
    169          return self.drawing_algorithm.event_with_rect_at(x, y, alt_down)
    170
    171 -    def event_at(self, x, y, alt_down=False):
    172          return self.drawing_algorithm.event_at(x, y, alt_down)
    173
    174 -    def set_selected(self, event, is_selected):
    175          self.view_properties.set_selected(event, is_selected)
    176
    177 -    def clear_selected(self):
    178          self.view_properties.clear_selected()
    179
    180 -    def select_all_events(self):
    181          self.view_properties.select_all_events()
    182
    183 -    def is_selected(self, event):
    184          return self.view_properties.is_selected(event)
    185
    186 -    def set_hovered_event(self, event):
    187          self.view_properties.change_hovered_event(event)
    188
    189 -    def get_hovered_event(self):
    190          return self.view_properties.hovered_event
    191
    192 -    def set_selection_rect(self, cursor):
    193          self.view_properties.set_selection_rect(cursor.rect)
    194          self._fast_draw = True
    195          self.redraw_timeline()
    196
    197 -    def remove_selection_rect(self):
    198          self.view_properties.set_selection_rect(None)
    199          self._fast_draw = True
    200          self.redraw_timeline()
    201
    202 -    def get_hscroll_amount(self):
    203          return self.view_properties.hscroll_amount
    204
    205 -    def set_hscroll_amount(self, amount):
    206          self.view_properties.hscroll_amount = amount
    207
    208 -    def set_period_selection(self, period):
    209          if period is None:
    210              self.view_properties.period_selection = None
    211          else:
    212              self.view_properties.period_selection = (period.start_time, period.end_time)
    213          self._redraw_timeline()
    214
    215 -    def select_events_in_rect(self, rect):
    216          self.view_properties.set_all_selected(self.get_events_in_rect(rect))
    217
    218 -    def event_has_sticky_balloon(self, event):
    219          return self.view_properties.event_has_sticky_balloon(event)
    220
    221 -    def set_event_sticky_balloon(self, event, is_sticky):
    222          self.view_properties.set_event_has_sticky_balloon(event, is_sticky)
    223          self.redraw_timeline()
    224
    225 -    def add_highlight(self, event, clear):
    226          self.view_properties.add_highlight(event, clear)
    227
    228 -    def tick_highlights(self):
    229          self.view_properties.tick_highlights(limit=15)
    230
    231 -    def has_higlights(self):
    232          return self.view_properties.has_higlights()
    233
    234 -    def filter_events(self, events):
    235          return self.view_properties.filter_events(events)
    236
    237 -    def event_is_period(self, event):
    238          return self.drawing_algorithm.event_is_period(event.get_time_period())
    239
    240 -    def snap(self, time):
    241          return self.drawing_algorithm.snap(time)
    242
    243 -    def get_selected_events(self):
    244          return self.timeline.find_event_with_ids(
    245              self.view_properties.get_selected_event_ids()
    246          )
    247
    248 -    def get_events_in_rect(self, rect):
    249          return self.drawing_algorithm.get_events_in_rect(rect)
    250
    251 -    def get_hidden_event_count(self):
    252          return self.drawing_algorithm.get_hidden_event_count()
    253
    254 -    def increment_font_size(self):
    255          self.drawing_algorithm.increment_font_size()
    256          self._redraw_timeline()
    257
    258 -    def decrement_font_size(self):
    259          self.drawing_algorithm.decrement_font_size()
    260          self._redraw_timeline()
    261
    262 -    def get_closest_overlapping_event(self, event, up):
    263          return self.drawing_algorithm.get_closest_overlapping_event(event, up=up)
    264
    265 -    def balloon_at(self, cursor):
    266          return self.drawing_algorithm.balloon_at(*cursor.pos)
    267
    268 -    def _timeline_changed(self, state_change):
    269          self._redraw_timeline()
    270
    271 -    def _set_initial_values_to_member_variables(self):
    272          self.timeline = None
    273          self.view_properties = ViewProperties()
    274          self.dragscroll_timer_running = False
    275
    276 -    def _set_colors_and_styles(self):
    277          """Define the look and feel of the drawing area."""
    278          self.view.SetBackgroundColour(wx.WHITE)
    279          self.view.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
    280          self.view.set_default_cursor()
    281          self.view.Disable()
    282
    283 -    def _redraw_timeline(self):
    284
    285          def display_monitor_result(dc):
    286              (width, height) = self.view.GetSizeTuple()
    287              redraw_time = self.monitoring.timer_elapsed_ms
    288              self.monitoring.count_timeline_redraw()
    289              dc.SetTextForeground((255, 0, 0))
    290              dc.SetFont(Font(12, weight=wx.FONTWEIGHT_BOLD))
    291              index, is_in_transaction, history = self.timeline.transactions_status()
    292              dc.DrawText("Undo buffer size: %d" % len(history), width - 300, height - 100)
    293              dc.DrawText("Undo buffer pos: %d" % index, width - 300, height - 80)
    294              dc.DrawText("Redraw count: %d" % self.monitoring._timeline_redraw_count, width - 300, height - 60)
    295              dc.DrawText("Last redraw time: %.3f ms" % redraw_time, width - 300, height - 40)
    296
    297          def fn_draw(dc):
    298              self.monitoring.timer_start()
    299              self.drawing_algorithm.draw(dc, self.timeline, self.view_properties, self.appearance, fast_draw=self._fast_draw)
    300              self.monitoring.timer_end()
    301              if DEBUG_ENABLED:
    302                  display_monitor_result(dc)
    303              self._fast_draw = False
    304
    305          if self.timeline and self.view_properties.displayed_period:
    306              self.view_properties.divider_position = (float(self.view.GetDividerPosition()) / 100.0)
    307              self.view.redraw_surface(fn_draw)
    308              self.view.PostEvent(create_timeline_redrawn_event())
    309
    310 -    def _set_drawing_algorithm(self, drawer):
    311          """
    312          The drawer interface:
    313              methods:
    314                  draw(d, t, p, a, f)
    315                  set_event_box_drawer(d)
    316                  set_background_drawer(d)
    317                  get_time(x)
    318                  event_with_rect_at(x, y, k)
    319                  event_at(x, y, k)
    320                  event_is_period(p)
    321                  snap(t)
    322                  get_events_in_rect(r)
    323                  get_hidden_event_count()
    324                  increment_font_size()
    325                  decrement_font_size()
    326                  get_closest_overlapping_event(...)
    327                  balloon_at(c)
    328              properties:
    329                  scene
    330          """
    331          if drawer is not None:
    332              self.drawing_algorithm = drawer
    333          else:
    334              self.drawing_algorithm = get_drawer()
    335