Gnumed.timelinelib.canvas.timelinecanvascontroller

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  CHOICE_WHOLE_PERIOD = _("Whole Timeline")
     33  CHOICE_VISIBLE_PERIOD = _("Visible Period")
     34  CHOICES = (CHOICE_WHOLE_PERIOD, CHOICE_VISIBLE_PERIOD)
     35
     36
     37 -class TimelineCanvasController(object):
     38
     39 -    def __init__(self, view, drawer=None):
     40          """
     41          The purpose of the drawer argument is make testing easier. A test can
     42          mock a drawer and use the mock by sending it in the drawer argument.
     43          Normally the drawer is collected with the get_drawer() method.
     44          """
     45          self.appearance = None
     46          self.monitoring = Monitoring()
     47          self.view = view
     48          self._set_drawing_algorithm(drawer)
     49          self.timeline = None
     50          self.set_appearance(Appearance())
     51          self.set_event_box_drawer(DefaultEventBoxDrawer())
     52          self.set_background_drawer(self.get_saved_background_drawer())
     53          self._fast_draw = False
     54          self._set_initial_values_to_member_variables()
     55          self._set_colors_and_styles()
     56          self._set_search_choises()
     57
     58      @property
     59 -    def scene(self):
     60          return self.drawing_algorithm.scene
     61
     62 -    def get_appearance(self):
     63          return self.appearance
     64
     65 -    def set_appearance(self, appearance):
     66          if self.appearance is not None:
     67              self.appearance.unlisten(self._redraw_timeline)
     68          self.appearance = appearance
     69          self.appearance.listen_for_any(self._redraw_timeline)
     70          self.redraw_timeline()
     71
     72 -    def get_saved_background_drawer(self):
     73          return DefaultBackgroundDrawer()
     74
     75 -    def set_event_box_drawer(self, event_box_drawer):
     76          self.drawing_algorithm.set_event_box_drawer(event_box_drawer)
     77
     78 -    def set_background_drawer(self, drawer):
     79          self.drawing_algorithm.set_background_drawer(drawer)
     80
     81 -    def get_timeline(self):
     82          return self.timeline
     83
     84 -    def get_view_properties(self):
     85          return self.view_properties
     86
     87 -    def set_timeline(self, timeline):
     88          """Inform what timeline to draw."""
     89          self._unregister_timeline(self.timeline)
     90          if timeline is None:
     91              self._set_null_timeline()
     92          else:
     93              self._set_non_null_timeline(timeline)
     94
     95 -    def use_fast_draw(self, value):
     96          self._fast_draw = value
     97
     98 -    def navigate(self, navigation_fn):
     99          old_period = self.view_properties.displayed_period
    100          new_period = navigation_fn(old_period)
    101          MIN_ZOOM_DELTA, min_zoom_error_text = self.time_type.get_min_zoom_delta()
    102          if new_period.delta() < MIN_ZOOM_DELTA:
    103              raise ValueError(min_zoom_error_text)
    104          min_time = self.time_type.get_min_time()
    105          if min_time is not None and new_period.start_time < min_time:
    106              raise ValueError(_("Can't scroll more to the left"))
    107          max_time = self.time_type.get_max_time()
    108          if max_time is not None and new_period.end_time > max_time:
    109              raise ValueError(_("Can't scroll more to the right"))
    110          self.view_properties.displayed_period = new_period
    111          self.redraw_timeline()
    112
    113 -    def _set_null_timeline(self):
    114          self.timeline = None
    115          self.time_type = None
    116          self.view.Disable()
    117
    118 -    def _set_non_null_timeline(self, timeline):
    119          self.timeline = timeline
    120          self.time_type = timeline.get_time_type()
    121          self.timeline.register(self._timeline_changed)
    122          self.view_properties.unlisten(self._redraw_timeline)
    123          properties_loaded = self._load_view_properties()
    124          if properties_loaded:
    125              self.view_properties.listen_for_any(self._redraw_timeline)
    126              self._redraw_timeline()
    127              self.view.Enable()
    128              self.view.SetFocus()
    129
    130 -    def _load_view_properties(self):
    131          properties_loaded = True
    132          try:
    133              self.view_properties.clear_db_specific()
    134              self.timeline.load_view_properties(self.view_properties)
    135              if self.view_properties.displayed_period is None:
    136                  default_tp = self.time_type.get_default_time_period()
    137                  self.view_properties.displayed_period = default_tp
    138              self.view_properties.hscroll_amount = 0
    139          except Exception:
    140              properties_loaded = False
    141          return properties_loaded
    142
    143 -    def _unregister_timeline(self, timeline):
    144          if timeline is not None:
    145              timeline.unregister(self._timeline_changed)
    146
    147 -    def get_time_period(self):
    148          """Return currently displayed time period."""
    149          if self.timeline is None:
    150              raise Exception(_("No timeline set"))
    151          return self.view_properties.displayed_period
    152
    153 -    def redraw_timeline(self):
    154          self._redraw_timeline()
    155
    156 -    def window_resized(self):
    157          self._redraw_timeline()
    158
    159 -    def _one_and_only_one_event_selected(self):
    160          selected_event_ids = self.view_properties.get_selected_event_ids()
    161          nbr_of_selected_event_ids = len(selected_event_ids)
    162          return nbr_of_selected_event_ids == 1
    163
    164 -    def _get_first_selected_event(self):
    165          selected_event_ids = self.view_properties.get_selected_event_ids()
    166          if len(selected_event_ids) > 0:
    167              event_id = selected_event_ids[0]
    168              return self.timeline.find_event_with_id(event_id)
    169          return None
    170
    171 -    def get_time(self, x):
    172          return self.drawing_algorithm.get_time(x)
    173
    174 -    def event_with_rect_at(self, x, y, alt_down=False):
    175          return self.drawing_algorithm.event_with_rect_at(x, y, alt_down)
    176
    177 -    def event_at(self, x, y, alt_down=False):
    178          return self.drawing_algorithm.event_at(x, y, alt_down)
    179
    180 -    def set_selected(self, event, is_selected):
    181          self.view_properties.set_selected(event, is_selected)
    182
    183 -    def clear_selected(self):
    184          self.view_properties.clear_selected()
    185
    186 -    def select_all_events(self):
    187          self.view_properties.select_all_events()
    188
    189 -    def is_selected(self, event):
    190          return self.view_properties.is_selected(event)
    191
    192 -    def set_hovered_event(self, event):
    193          self.view_properties.change_hovered_event(event)
    194
    195 -    def get_hovered_event(self):
    196          return self.view_properties.hovered_event
    197
    198 -    def set_selection_rect(self, cursor):
    199          self.view_properties.set_selection_rect(cursor.rect)
    200          self._fast_draw = True
    201          self.redraw_timeline()
    202
    203 -    def remove_selection_rect(self):
    204          self.view_properties.set_selection_rect(None)
    205          self._fast_draw = True
    206          self.redraw_timeline()
    207
    208 -    def get_hscroll_amount(self):
    209          return self.view_properties.hscroll_amount
    210
    211 -    def set_hscroll_amount(self, amount):
    212          self.view_properties.hscroll_amount = amount
    213
    214 -    def set_period_selection(self, period):
    215          if period is None:
    216              self.view_properties.period_selection = None
    217          else:
    218              self.view_properties.period_selection = (period.start_time, period.end_time)
    219          self._redraw_timeline()
    220
    221 -    def select_events_in_rect(self, rect):
    222          self.view_properties.set_all_selected(self.get_events_in_rect(rect))
    223
    224 -    def event_has_sticky_balloon(self, event):
    225          return self.view_properties.event_has_sticky_balloon(event)
    226
    227 -    def set_event_sticky_balloon(self, event, is_sticky):
    228          self.view_properties.set_event_has_sticky_balloon(event, is_sticky)
    229          self.redraw_timeline()
    230
    231 -    def add_highlight(self, event, clear):
    232          self.view_properties.add_highlight(event, clear)
    233
    234 -    def tick_highlights(self):
    235          self.view_properties.tick_highlights(limit=15)
    236
    237 -    def has_higlights(self):
    238          return self.view_properties.has_higlights()
    239
    240 -    def get_period_choices(self):
    241          return CHOICES
    242
    243 -    def _set_search_choises(self):
    244          self._search_choice_functions = {
    245              CHOICE_WHOLE_PERIOD: self._choose_whole_period,
    246              CHOICE_VISIBLE_PERIOD: self._choose_visible_period
    247          }
    248
    249 -    def filter_events(self, events, search_period):
    250          return self._search_choice_functions[search_period](events)
    251
    252 -    def _choose_whole_period(self, events):
    253          return self.view_properties.filter_events(events)
    254
    255 -    def _choose_visible_period(self, events):
    256          events = self.view_properties.filter_events(events)
    257          period = self.view_properties.displayed_period
    258          return [e for e in events if period.overlaps(e.get_time_period())]
    259
    260 -    def event_is_period(self, event):
    261          return self.drawing_algorithm.event_is_period(event.get_time_period())
    262
    263 -    def snap(self, time):
    264          return self.drawing_algorithm.snap(time)
    265
    266 -    def get_selected_events(self):
    267          return self.timeline.find_event_with_ids(
    268              self.view_properties.get_selected_event_ids()
    269          )
    270
    271 -    def get_events_in_rect(self, rect):
    272          return self.drawing_algorithm.get_events_in_rect(rect)
    273
    274 -    def get_hidden_event_count(self):
    275          return self.drawing_algorithm.get_hidden_event_count()
    276
    277 -    def increment_font_size(self):
    278          self.drawing_algorithm.increment_font_size()
    279          self._redraw_timeline()
    280
    281 -    def decrement_font_size(self):
    282          self.drawing_algorithm.decrement_font_size()
    283          self._redraw_timeline()
    284
    285 -    def get_closest_overlapping_event(self, event, up):
    286          return self.drawing_algorithm.get_closest_overlapping_event(event, up=up)
    287
    288 -    def balloon_at(self, cursor):
    289          return self.drawing_algorithm.balloon_at(*cursor.pos)
    290
    291 -    def _timeline_changed(self, state_change):
    292          self._redraw_timeline()
    293
    294 -    def _set_initial_values_to_member_variables(self):
    295          self.timeline = None
    296          self.view_properties = ViewProperties()
    297          self.dragscroll_timer_running = False
    298
    299 -    def _set_colors_and_styles(self):
    300          """Define the look and feel of the drawing area."""
    301          self.view.SetBackgroundColour(wx.WHITE)
    302          self.view.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
    303          self.view.set_default_cursor()
    304          self.view.Disable()
    305
    306 -    def _redraw_timeline(self):
    307
    308          def display_monitor_result(dc):
    309              (width, height) = self.view.GetSize()
    310              redraw_time = self.monitoring.timer_elapsed_ms
    311              self.monitoring.count_timeline_redraw()
    312              dc.SetTextForeground((255, 0, 0))
    313              dc.SetFont(Font(12, weight=wx.FONTWEIGHT_BOLD))
    314              index, is_in_transaction, history = self.timeline.transactions_status()
    315              dc.DrawText("Undo buffer size: %d" % len(history), width - 300, height - 100)
    316              dc.DrawText("Undo buffer pos: %d" % index, width - 300, height - 80)
    317              dc.DrawText("Redraw count: %d" % self.monitoring._timeline_redraw_count, width - 300, height - 60)
    318              dc.DrawText("Last redraw time: %.3f ms" % redraw_time, width - 300, height - 40)
    319
    320          def fn_draw(dc):
    321              self.monitoring.timer_start()
    322              self.drawing_algorithm.draw(dc, self.timeline, self.view_properties, self.appearance, fast_draw=self._fast_draw)
    323              self.monitoring.timer_end()
    324              if DEBUG_ENABLED:
    325                  display_monitor_result(dc)
    326              self._fast_draw = False
    327
    328          if self.timeline and self.view_properties.displayed_period:
    329              self.view_properties.divider_position = (float(self.view.GetDividerPosition()) / 100.0)
    330              self.view.RedrawSurface(fn_draw)
    331              self.view.PostEvent(create_timeline_redrawn_event())
    332
    333 -    def _set_drawing_algorithm(self, drawer):
    334          """
    335          The drawer interface:
    336              methods:
    337                  draw(d, t, p, a, f)
    338                  set_event_box_drawer(d)
    339                  set_background_drawer(d)
    340                  get_time(x)
    341                  event_with_rect_at(x, y, k)
    342                  event_at(x, y, k)
    343                  event_is_period(p)
    344                  snap(t)
    345                  get_events_in_rect(r)
    346                  get_hidden_event_count()
    347                  increment_font_size()
    348                  decrement_font_size()
    349                  get_closest_overlapping_event(...)
    350                  balloon_at(c)
    351              properties:
    352                  scene
    353          """
    354          if drawer is not None:
    355              self.drawing_algorithm = drawer
    356          else:
    357              self.drawing_algorithm = get_drawer()
    358