Gnumed.timelinelib.canvas.eventboxdrawers.defaulteventboxdrawer

Package Gnumed :: Package timelinelib :: Package canvas :: Package eventboxdrawers :: Module defaulteventboxdrawer |
[frames] | no frames]

# Source Code for Module Gnumed.timelinelib.canvas.eventboxdrawers.defaulteventboxdrawer

      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 os
     20  import math
     21
     22  import wx
     23
     24  from timelinelib.canvas.drawing.utils import darken_color
     25  from timelinelib.canvas.drawing.utils import get_colour
     26  from timelinelib.config.paths import EVENT_ICONS_DIR
     27  from timelinelib.features.experimental.experimentalfeatures import EXTENDED_CONTAINER_HEIGHT
     28
     29
     30  HANDLE_SIZE = 4
     31  HALF_HANDLE_SIZE = HANDLE_SIZE / 2
     32  DATA_INDICATOR_SIZE = 10
     33  INNER_PADDING = 3  # Space inside event box to text (pixels)
     34  GRAY = (200, 200, 200)
     35
     36
     37 -class DefaultEventBoxDrawer(object):
     38
     39 -    def draw(self, dc, scene, rect, event, view_properties):
     40          self.scene = scene
     41          self.view_properties = view_properties
     42          selected = view_properties.is_selected(event)
     43          self.center_text = scene.center_text()
     44          if event.is_milestone():
     45              self._draw_milestone_event(dc, rect, scene, event, selected)
     46          elif scene.never_show_period_events_as_point_events() and rect.y < scene.divider_y and event.is_period():
     47              self._draw_period_event_as_symbol_below_divider_line(dc, scene, event)
     48          else:
     49              self._draw_event_box(dc, rect, event, selected)
     50
     51 -    def _draw_period_event_as_symbol_below_divider_line(self, dc, scene, event):
     52          dc.DestroyClippingRegion()
     53          x = scene.x_pos_for_time(event.mean_time())
     54          y0 = scene.divider_y
     55          y1 = y0 + 10
     56          dc.SetBrush(self._black_solid_brush())
     57          dc.SetPen(self._black_solid_pen(1))
     58          dc.DrawLine(x, y0, x, y1)
     59          dc.DrawCircle(x, y1, 2)
     60
     61 -    def _draw_event_box(self, dc, rect, event, selected):
     62          self._draw_background(dc, rect, event)
     63          self._draw_fuzzy_edges(dc, rect, event)
     64          self._draw_locked_edges(dc, rect, event)
     65          self._draw_progress_box(dc, rect, event)
     66          self._draw_text(dc, rect, event)
     67          self._draw_contents_indicator(dc, event, rect)
     68          self._draw_locked_edges(dc, rect, event)
     69          self._draw_selection_handles(dc, event, rect, selected)
     70          self._draw_hyperlink(dc, rect, event)
     71
     72 -    def _draw_background(self, dc, rect, event):
     73          dc.SetBrush(wx.Brush(self._get_event_color(event), wx.PENSTYLE_SOLID))
     74          dc.SetPen(self._get_pen(dc, event))
     75          dc.DrawRectangleRect(rect)
     76
     77 -    def _get_pen(self, dc, event):
     78          pen = self._get_thin_border_pen(event)
     79          if self.view_properties.is_highlighted(event):
     80              if self.view_properties.get_highlight_count(event) % 2 == 0:
     81                  dc.DestroyClippingRegion()
     82                  pen = self._get_thick_border_pen(event)
     83          return pen
     84
     85 -    def _draw_fuzzy_edges(self, dc, rect, event):
     86          if event.get_fuzzy():
     87              self._draw_fuzzy_start(dc, rect, event)
     88              if not event.get_ends_today():
     89                  self._draw_fuzzy_end(dc, rect, event)
     90
     91 -    def _draw_locked_edges(self, dc, rect, event):
     92          if event.get_ends_today():
     93              self._draw_locked_end(dc, event, rect)
     94          if event.get_locked():
     95              self._draw_locked_start(dc, event, rect)
     96              self._draw_locked_end(dc, event, rect)
     97
     98 -    def _draw_contents_indicator(self, dc, event, rect):
     99          if event.has_balloon_data():
    100              self._draw_balloon_indicator(dc, event, rect)
    101
    102 -    def _draw_selection_handles(self, dc, event, rect, selected):
    103          if not event.locked and selected:
    104              self._draw_handles(dc, event, rect)
    105
    106 -    def _get_thin_border_pen(self, event):
    107          return self._get_border_pen(event)
    108
    109 -    def _get_thick_border_pen(self, event):
    110          return self._get_border_pen(event, thickness=8)
    111
    112 -    def _get_border_pen(self, event, thickness=1):
    113          return wx.Pen(self._get_border_color(event), thickness, wx.PENSTYLE_SOLID)
    114
    115 -    def _get_balloon_indicator_brush(self, event):
    116          base_color = self._get_event_color(event)
    117          darker_color = darken_color(base_color, 0.6)
    118          brush = wx.Brush(darker_color, wx.PENSTYLE_SOLID)
    119          return brush
    120
    121 -    def _get_border_color(self, event):
    122          return darken_color(self._get_event_color(event))
    123
    124 -    def _get_event_color(self, event):
    125          try:
    126              return event.get_category().color
    127          except:
    128              return event.get_default_color()
    129
    130 -    def _draw_fuzzy_start(self, dc, rect, event):
    131          self._inflate_clipping_region(dc, rect)
    132          dc.DrawBitmap(self._get_fuzzy_bitmap(), rect.x - 4, rect.y + 4, True)
    133
    134 -    def _draw_fuzzy_end(self, dc, rect, event):
    135          self._inflate_clipping_region(dc, rect)
    136          dc.DrawBitmap(self._get_fuzzy_bitmap(), rect.x + rect.width - 8, rect.y + 4, True)
    137
    138 -    def draw_fuzzy(self, dc, event, p1, p2, p3, p4, p5):
    139          self._erase_outzide_fuzzy_box(dc, p1, p2, p3)
    140          self._erase_outzide_fuzzy_box(dc, p3, p4, p5)
    141          self._draw_fuzzy_border(dc, event, p2, p3, p5)
    142
    143 -    def _erase_outzide_fuzzy_box(self, dc, p1, p2, p3):
    144          dc.SetBrush(wx.WHITE_BRUSH)
    145          dc.SetPen(wx.WHITE_PEN)
    146          dc.DrawPolygon((p1, p2, p3))
    147
    148 -    def _draw_fuzzy_border(self, dc, event, p1, p2, p3):
    149          gc = wx.GraphicsContext.Create(dc)
    150          path = gc.CreatePath()
    151          path.MoveToPoint(p1.x, p1.y)
    152          path.AddLineToPoint(p2.x, p2.y)
    153          path.AddLineToPoint(p3.x, p3.y)
    154          gc.SetPen(self._get_thin_border_pen(event))
    155          gc.StrokePath(path)
    156
    157 -    def _draw_locked_start(self, dc, event, rect):
    158          self._inflate_clipping_region(dc, rect)
    159          dc.DrawBitmap(self._get_lock_bitmap(), rect.x - 7, rect.y + 3, True)
    160
    161 -    def _draw_locked_end(self, dc, event, rect):
    162          self._inflate_clipping_region(dc, rect)
    163          dc.DrawBitmap(self._get_lock_bitmap(), rect.x + rect.width - 8, rect.y + 3, True)
    164
    165 -    def _draw_locked(self, dc, event, rect, x, start_angle, end_angle):
    166          y = rect.y + rect.height / 2
    167          r = rect.height / 2.5
    168          dc.SetBrush(wx.WHITE_BRUSH)
    169          dc.SetPen(wx.WHITE_PEN)
    170          dc.DrawCircle(x, y, r)
    171          dc.SetPen(self._get_thin_border_pen(event))
    172          self.draw_segment(dc, event, x, y, r, start_angle, end_angle)
    173
    174 -    def draw_segment(self, dc, event, x0, y0, r, start_angle, end_angle):
    175          gc = wx.GraphicsContext.Create(dc)
    176          path = gc.CreatePath()
    177          segment_length = 2.0 * (end_angle - start_angle) * r
    178          delta = (end_angle - start_angle) / segment_length
    179          angle = start_angle
    180          x1 = r * math.cos(angle) + x0
    181          y1 = r * math.sin(angle) + y0
    182          path.MoveToPoint(x1, y1)
    183          while angle < end_angle:
    184              angle += delta
    185              if angle > end_angle:
    186                  angle = end_angle
    187              x2 = r * math.cos(angle) + x0
    188              y2 = r * math.sin(angle) + y0
    189              path.AddLineToPoint(x2, y2)
    190              x1 = x2
    191              y1 = y2
    192          gc.SetPen(self._get_thin_border_pen(event))
    193          gc.StrokePath(path)
    194
    195 -    def _draw_progress_box(self, dc, rect, event):
    196          if event.get_data("progress"):
    197              self._set_progress_color(dc, event)
    198              progress_rect = self._get_progress_rect(rect, event)
    199              dc.DrawRectangleRect(progress_rect)
    200
    201 -    def _set_progress_color(self, dc, event):
    202          progress_color = event.get_progress_color()
    203          dc.SetBrush(wx.Brush(wx.Colour(progress_color[0], progress_color[1], progress_color[2])))
    204
    205 -    def _get_progress_rect(self, event_rect, event):
    206          HEIGHT_FACTOR = 0.35
    207          h = event_rect.height * HEIGHT_FACTOR
    208          y = event_rect.y + (event_rect.height - h)
    209          rw, _ = self.scene._calc_width_and_height_for_period_event(event)
    210          rx = self.scene._calc_x_pos_for_period_event(event)
    211          w = rw * event.get_data("progress") / 100.0
    212          return wx.Rect(rx, y, w, h)
    213
    214 -    def _draw_balloon_indicator(self, dc, event, rect):
    215          """
    216          The data contents indicator is a small triangle drawn in the upper
    217          right corner of the event rectangle.
    218          """
    219          corner_x = rect.X + rect.Width
    220          points = (
    221              wx.Point(corner_x - DATA_INDICATOR_SIZE, rect.Y),
    222              wx.Point(corner_x, rect.Y),
    223              wx.Point(corner_x, rect.Y + DATA_INDICATOR_SIZE),
    224          )
    225          dc.SetBrush(self._get_balloon_indicator_brush(event))
    226          dc.SetPen(wx.TRANSPARENT_PEN)
    227          dc.DrawPolygon(points)
    228
    229 -    def _draw_text(self, dc, rect, event):
    230          # Ensure that we can't draw content outside inner rectangle
    231          if self._there_is_room_for_the_text(rect):
    232              self._draw_the_text(dc, rect, event)
    233
    234 -    def _there_is_room_for_the_text(self, rect):
    235          return self._get_inner_rect(rect).Width > 0
    236
    237 -    def _draw_the_text(self, dc, rect, event):
    238          self._set_text_foreground_color(dc, event)
    239          if event.is_container() and EXTENDED_CONTAINER_HEIGHT.enabled():
    240              EXTENDED_CONTAINER_HEIGHT.draw_container_text_top_adjusted(event.get_text(), dc, rect)
    241          else:
    242              self._draw_normal_text(dc, rect, event)
    243          dc.DestroyClippingRegion()
    244
    245 -    def _draw_normal_text(self, dc, rect, event):
    246          self._set_clipping_rect(dc, rect)
    247          dc.DrawText(self._get_text(event), self._calc_x_pos(dc, rect, event), self._calc_y_pos(rect))
    248
    249 -    def _get_text(self, event):
    250          if event.get_progress() == 100 and self.view_properties.get_display_checkmark_on_events_done():
    251              return u"\u2714" + event.get_text()
    252          else:
    253              return event.get_text()
    254
    255 -    def _get_inner_rect(self, rect):
    256          # Under windows with wx 3.0 the following lined doesn't work !!!
    257          # return wx.Rect(*rect).Deflate(INNER_PADDING, INNER_PADDING)
    258          # The x-coordinate is corrupted
    259          r = wx.Rect(*rect)
    260          r.Deflate(INNER_PADDING, INNER_PADDING)
    261          return r
    262
    263 -    def _set_clipping_rect(self, dc, rect):
    264          dc.SetClippingRect(self._get_inner_rect(rect))
    265
    266 -    def _calc_x_pos(self, dc, rect, event):
    267          inner_rect = self._get_inner_rect(rect)
    268          text_x = inner_rect.X
    269          text_x = self._adjust_x_for_edge_icons(event, rect, text_x)
    270          text_x = self._adjust_x_for_centered_text(dc, event, inner_rect, text_x)
    271          return text_x
    272
    273 -    def _adjust_x_for_edge_icons(self, event, rect, text_x):
    274          if self._event_has_edge_icons(event):
    275              text_x += rect.Height / 2
    276          return text_x
    277
    278 -    def _adjust_x_for_centered_text(self, dc, event, inner_rect, text_x):
    279          if self.center_text:
    280              text_x = self._center_text(dc, event, inner_rect, text_x)
    281          return text_x
    282
    283 -    def _event_has_edge_icons(self, event):
    284          return event.get_fuzzy() or event.get_locked()
    285
    286 -    def _calc_y_pos(self, rect):
    287          return self._get_inner_rect(rect).Y
    288
    289 -    def _center_text(self, dc, event, inner_rect, text_x):
    290          width, _ = dc.GetTextExtent(self._get_text(event))
    291          return max(text_x, text_x + (inner_rect.width - width) / 2)
    292
    293 -    def _set_text_foreground_color(self, dc, event):
    294          try:
    295              dc.SetTextForeground(get_colour(event.get_category().font_color))
    296          except:
    297              dc.SetTextForeground(wx.BLACK)
    298
    299 -    def _draw_handles(self, dc, event, rect):
    300
    301          def draw_frame_around_event():
    302              small_rect = wx.Rect(*rect)
    303              small_rect.Deflate(1, 1)
    304              border_color = self._get_border_color(event)
    305              border_color = darken_color(border_color)
    306              pen = wx.Pen(border_color, 1, wx.PENSTYLE_SOLID)
    307              dc.SetBrush(wx.TRANSPARENT_BRUSH)
    308              dc.SetPen(pen)
    309              dc.DrawRectangleRect(small_rect)
    310
    311          dc.SetClippingRect(rect)
    312          draw_frame_around_event()
    313          self._draw_all_handles(dc, rect, event)
    314          dc.DestroyClippingRegion()
    315
    316 -    def _draw_all_handles(self, dc, rect, event):
    317
    318          def inflate_clipping_region():
    319              big_rect = wx.Rect(*rect)
    320              big_rect.Inflate(HANDLE_SIZE, HANDLE_SIZE)
    321              dc.DestroyClippingRegion()
    322              dc.SetClippingRect(big_rect)
    323
    324          def set_pen_and_brush():
    325              dc.SetBrush(wx.BLACK_BRUSH)
    326              dc.SetPen(wx.BLACK_PEN)
    327
    328          def create_handle_rect():
    329              HALF_EVENT_HEIGHT = rect.Height / 2
    330              y = rect.Y + HALF_EVENT_HEIGHT - HALF_HANDLE_SIZE
    331              x = rect.X - HALF_HANDLE_SIZE + 1
    332              return wx.Rect(x, y, HANDLE_SIZE, HANDLE_SIZE)
    333
    334          def draw_rect(handle_rect, offset):
    335              handle_rect.OffsetXY(offset, 0)
    336              dc.DrawRectangleRect(handle_rect)
    337
    338          def draw_handle_rects(handle_rect):
    339              HALF_EVENT_WIDTH = rect.Width / 2
    340              EVENT_WIDTH = rect.Width
    341              draw_rect(handle_rect, 0)
    342              draw_rect(handle_rect, EVENT_WIDTH - 2)
    343              if not event.ends_today:
    344                  draw_rect(handle_rect, -HALF_EVENT_WIDTH)
    345
    346          inflate_clipping_region()
    347          set_pen_and_brush()
    348          handle_rect = create_handle_rect()
    349          draw_handle_rects(handle_rect)
    350
    351 -    def _draw_hyperlink(self, dc, rect, event):
    352          if event.get_hyperlink():
    353              dc.DrawBitmap(self._get_hyperlink_bitmap(), rect.x + rect.width - 14, rect.y + 4, True)
    354
    355 -    def _inflate_clipping_region(self, dc, rect):
    356          copy = wx.Rect(*rect)
    357          copy.Inflate(10, 0)
    358          dc.DestroyClippingRegion()
    359          dc.SetClippingRect(copy)
    360
    361 -    def _get_hyperlink_bitmap(self):
    362          return self._get_bitmap(self.view_properties.get_hyperlink_icon())
    363
    364 -    def _get_lock_bitmap(self):
    365          return self._get_bitmap(self.view_properties.get_locked_icon())
    366
    367 -    def _get_fuzzy_bitmap(self):
    368          return self._get_bitmap(self.view_properties.get_fuzzy_icon())
    369
    370 -    def _get_bitmap(self, name):
    371          return wx.Bitmap(os.path.join(EVENT_ICONS_DIR, name))
    372
    373 -    def _draw_milestone_event(self, dc, rect, scene, event, selected):
    374
    375          def create_handle_rect():
    376              HALF_EVENT_HEIGHT = rect.Height / 2
    377              y = rect.Y + HALF_EVENT_HEIGHT - HALF_HANDLE_SIZE
    378              x = rect.X - HALF_HANDLE_SIZE + 1
    379              return wx.Rect(x, y, HANDLE_SIZE, HANDLE_SIZE)
    380
    381          def draw_shape():
    382              # draw_diamond_shape()
    383              draw_rectangle_shape()
    384              # draw_circle_shape()
    385
    386          def draw_rectangle_shape():
    387              dc.DestroyClippingRegion()
    388              dc.SetPen(self._black_solid_pen(1))
    389              if event.get_category() is None:
    390                  dc.SetBrush(wx.Brush(wx.Colour(*event.get_default_color()), wx.PENSTYLE_SOLID))
    391              else:
    392                  dc.SetBrush(wx.Brush(wx.Colour(*event.get_category().get_color()), wx.PENSTYLE_SOLID))
    393              dc.DrawRectangleRect(rect)
    394
    395          def draw_circle_shape():
    396              half_size = rect.width / 2
    397              dc.DestroyClippingRegion()
    398              dc.SetPen(self._black_solid_pen(1))
    399              dc.SetBrush(wx.Brush(wx.Colour(*event.get_default_color()), wx.PENSTYLE_SOLID))
    400              dc.DrawCircle(rect.x + half_size, rect.y + half_size, 2 * rect.width / 3)
    401
    402          def draw_diamond_shape():
    403              SIZE = 2
    404              x = rect.x
    405              y = rect.y
    406              half_size = rect.width / 2
    407              points = (wx.Point(x - SIZE, y + half_size),
    408                        wx.Point(x + half_size, y - SIZE),
    409                        wx.Point(x + rect.width + SIZE, y + half_size),
    410                        wx.Point(x + half_size, y + rect.width + SIZE))
    411              dc.DestroyClippingRegion()
    412              dc.SetPen(self._black_solid_pen(1))
    413              dc.SetBrush(wx.Brush(wx.Colour(*event.get_default_color()), wx.PENSTYLE_SOLID))
    414              dc.DrawPolygon(points)
    415
    416          def draw_label():
    417              x_offset = 6
    418              y_offset = 2
    419              try:
    420                  label = event.get_text()[0]
    421              except IndexError:
    422                  label = " "
    423              dc.DrawText(label, rect.x + x_offset, rect.y + y_offset)
    424
    425          def draw_move_handle():
    426              dc.SetBrush(self._black_solid_brush())
    427              handle_rect = create_handle_rect()
    428              handle_rect.OffsetXY(rect.Width / 2, 0)
    429              dc.DrawRectangleRect(handle_rect)
    430
    431          draw_shape()
    432          draw_label()
    433          if selected:
    434              draw_move_handle()
    435
    436 -    def _black_solid_pen(self, size):
    437          return wx.Pen(wx.Colour(0, 0, 0), size, wx.PENSTYLE_SOLID)
    438
    439 -    def _black_solid_brush(self):
    440          return wx.Brush(wx.Colour(0, 0, 0), wx.PENSTYLE_SOLID)
    441