Gnumed.timelinelib.db.strategies

Package Gnumed :: Package timelinelib :: Package db :: Module strategies |
[frames] | no frames]

# Source Code for Module Gnumed.timelinelib.db.strategies

      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  from timelinelib.db.interface import ContainerStrategy
     20  from timelinelib.canvas.data.subevent import Subevent
     21
     22
     23 -class DefaultContainerStrategy(ContainerStrategy):
     24
     25 -    def __init__(self, container):
     26          ContainerStrategy.__init__(self, container)
     27
     28 -    def register_subevent(self, subevent):
     29          if not isinstance(subevent, Subevent):
     30              raise TypeError("Expected Subevent object")
     31          if self._is_subevent_missing(subevent):
     32              self._append_subevent(subevent)
     33              if len(self.container.subevents) != 1:
     34                  self._adjust_time_period(subevent)
     35
     36 -    def _append_subevent(self, subevent):
     37          self.container.subevents.append(subevent)
     38          self._sort_subevents()
     39
     40 -    def _sort_subevents(self):
     41          without_sort_order = []
     42          with_sort_order = []
     43          for subevent in self.container.subevents:
     44              if subevent.sort_order is None:
     45                  without_sort_order.append(subevent)
     46              else:
     47                  with_sort_order.append(subevent)
     48          self.container.subevents = sorted(
     49              with_sort_order,
     50              key=lambda subevent: subevent.sort_order
     51          ) + without_sort_order
     52
     53 -    def unregister_subevent(self, subevent):
     54          if self._is_subevent_missing(subevent):
     55              return
     56          self.container.subevents.remove(subevent)
     57
     58 -    def update(self, subevent):
     59          self.unregister_subevent(subevent)
     60          self.register_subevent(subevent)
     61
     62 -    def allow_ends_today_on_subevents(self):
     63          return False
     64
     65 -    def _adjust_time_period(self, new_event):
     66          """
     67          If the event to be added to the container overlaps any other
     68          event in the container or if the new event is outside of the
     69          container time period the container time period must be adjusted.
     70          """
     71          event = self._event_totally_overlapping_new_event(new_event)
     72          if event is not None:
     73              self._adjust_when_new_event_is_totally_overlapped(new_event, event)
     74          else:
     75              events = self._events_overlapped_by_new_event(new_event)
     76              if len(events) > 0:
     77                  self._adjust_when_new_event_partially_overlaps_other_events(new_event, events)
     78
     79 -    def _adjust_when_new_event_is_totally_overlapped(self, new_event, event):
     80          # Situation:
     81          #    event:       +--------------------------------------------+
     82          #    new_event:                  +-------------+
     83          #                 |- left_delta -|             |- right_delta -|
     84          #
     85          #      or         +----------+
     86          #      or                                  +-------------------+
     87          #      or         +--------------------------------------------+
     88          #      or                      +
     89          #      or         +
     90          #      or                                                      +
     91          left_delta = event.start_to_start(new_event).delta()
     92          right_delta = new_event.end_to_end(event).delta()
     93          if left_delta > right_delta:
     94              self._move_events_left(new_event, event)
     95          else:
     96              self._move_events_right(new_event, event)
     97
     98 -    def _move_events_left(self, new_event, event):
     99          delta = new_event.start_to_end(event).delta()
    100          latest_start_time = event.get_time_period().start_time
    101          self._move_early_events_left(new_event, latest_start_time, delta)
    102
    103 -    def _move_events_right(self, new_event, event):
    104          delta = event.start_to_end(new_event).delta()
    105          earliest_start_time = event.get_time_period().start_time
    106          self._move_late_events_right(new_event, earliest_start_time, delta)
    107
    108 -    def _adjust_when_new_event_partially_overlaps_other_events(self, new_event, events):
    109          # Situation:
    110          #                                 V = threshold_time
    111          #                            |-td-|
    112          #    new_event:              +----------------------+
    113          #    events:                 +-------------+
    114          #      or              +-------------+
    115          #      or                        +-------------+
    116          #      or                             +-------------+
    117          #      or                                      +-------------+
    118          threshold_time = self._calc_threshold_time(new_event)
    119          event = self._some_event_in_new_event_threshold_time(new_event,
    120                                                               events,
    121                                                               threshold_time)
    122          if event is not None:
    123              self._adjust_threshold_triggered_events(new_event, event, threshold_time)
    124          earliest_start = self._earliest_start_time_for_event_that_starts_within_new_event(new_event,
    125                                                                                            events,
    126                                                                                            threshold_time)
    127          if earliest_start is not None:
    128              self._adjust_events_starting_in_new_event(new_event, earliest_start)
    129
    130 -    def _calc_threshold_time(self, new_event):
    131          td = new_event.time_span() * 0.2
    132          threshold_time = new_event.get_time_period().start_time + td
    133          return threshold_time
    134
    135 -    def _some_event_in_new_event_threshold_time(self, new_event, events, end):
    136          start = new_event.get_time_period().start_time
    137          for event in events:
    138              if event is new_event:
    139                  continue
    140              if (event.get_time_period().end_time >= start and event.get_time_period().end_time <= end):
    141                  return event
    142              if (event.get_time_period().start_time <= start and event.get_time_period().end_time > end):
    143                  return event
    144          return None
    145
    146 -    def _adjust_threshold_triggered_events(self, new_event, event, threshold_time):
    147          delta = event.get_time_period().end_time - new_event.get_time_period().start_time
    148          self._move_early_events_left(new_event, threshold_time, delta)
    149
    150 -    def _earliest_start_time_for_event_that_starts_within_new_event(self, new_event, events, thr):
    151          start = new_event.get_time_period().start_time
    152          end = new_event.get_time_period().end_time
    153          min_start = None
    154          for event in events:
    155              if not event.is_period():
    156                  if event.get_time_period().start_time < thr:
    157                      continue
    158              if (event.get_time_period().start_time >= start and event.get_time_period().start_time <= end):
    159                  if min_start is None:
    160                      min_start = event.get_time_period().start_time
    161                  else:
    162                      if event.get_time_period().start_time < min_start:
    163                          min_start = event.get_time_period().start_time
    164          return min_start
    165
    166 -    def _adjust_events_starting_in_new_event(self, new_event, earliest_start):
    167          delta = new_event.get_time_period().end_time - earliest_start
    168          self._move_late_events_right(new_event, earliest_start, delta)
    169
    170 -    def _event_totally_overlapping_new_event(self, new_event):
    171          for event in self.container.subevents:
    172              if event is new_event:
    173                  continue
    174              if (self._event_totally_overlaps_new_event(new_event, event)):
    175                  return event
    176          return None
    177
    178 -    def _event_totally_overlaps_new_event(self, new_event, event):
    179          return (event.get_time_period().start_time <= new_event.get_time_period().start_time and
    180                  event.get_time_period().end_time >= new_event.get_time_period().end_time)
    181
    182 -    def _events_overlapped_by_new_event(self, new_event):
    183          overlapping_events = []
    184          for event in self.container.subevents:
    185              if event is not new_event:
    186                  if (self._starts_within(event, new_event) or self._ends_within(event, new_event)):
    187                      overlapping_events.append(event)
    188          return overlapping_events
    189
    190 -    def _starts_within(self, event, new_event):
    191          s1 = event.get_time_period().start_time >= new_event.get_time_period().start_time
    192          s2 = event.get_time_period().start_time <= new_event.get_time_period().end_time
    193          return (s1 and s2)
    194
    195 -    def _ends_within(self, event, new_event):
    196          s1 = event.get_time_period().end_time >= new_event.get_time_period().start_time
    197          s2 = event.get_time_period().end_time <= new_event.get_time_period().end_time
    198          return (s1 and s2)
    199
    200 -    def _move_early_events_left(self, new_event, latest_start_time, delta):
    201          delta = -delta
    202          for event in self.container.subevents:
    203              if event is new_event:
    204                  continue
    205              if event.get_time_period().start_time <= latest_start_time:
    206                  event.move_delta(delta)
    207
    208 -    def _move_late_events_right(self, new_event, earliest_start_time, delta):
    209          for event in self.container.subevents:
    210              if event is new_event:
    211                  continue
    212              if event.get_time_period().start_time >= earliest_start_time:
    213                  event.move_delta(delta)
    214
    215 -    def _is_subevent_missing(self, subevent):
    216          for x in self.container.subevents:
    217              if x is subevent:
    218                  return False
    219          return True
    220
    221
    222 -class ExtendedContainerStrategy(DefaultContainerStrategy):
    223
    224 -    def register_subevent(self, subevent):
    225          if not isinstance(subevent, Subevent):
    226              raise TypeError("Expected Subevent object")
    227          if self._is_subevent_missing(subevent):
    228              self._append_subevent(subevent)
    229
    230 -    def allow_ends_today_on_subevents(self):
    231          return True
    232