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.BRUSHSTYLE_SOLID))
     74          dc.SetPen(self._get_pen(dc, event))
     75          dc.DrawRectangle(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.BRUSHSTYLE_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_progress_box(self, dc, rect, event):
    166          if event.get_data("progress"):
    167              self._set_progress_color(dc, event)
    168              progress_rect = self._get_progress_rect(rect, event)
    169              dc.DrawRectangle(progress_rect)
    170
    171 -    def _set_progress_color(self, dc, event):
    172          progress_color = event.get_progress_color()
    173          dc.SetBrush(wx.Brush(wx.Colour(progress_color[0], progress_color[1], progress_color[2])))
    174
    175 -    def _get_progress_rect(self, event_rect, event):
    176          HEIGHT_FACTOR = 0.35
    177          h = event_rect.height * HEIGHT_FACTOR
    178          y = event_rect.y + (event_rect.height - h)
    179          rw, _ = self.scene._calc_width_and_height_for_period_event(event)
    180          rx = self.scene._calc_x_pos_for_period_event(event)
    181          w = rw * event.get_data("progress") / 100.0
    182          return wx.Rect(int(rx), int(y), int(w), int(h))
    183
    184 -    def _draw_balloon_indicator(self, dc, event, rect):
    185          """
    186          The data contents indicator is a small triangle drawn in the upper
    187          right corner of the event rectangle.
    188          """
    189          corner_x = rect.X + rect.Width
    190          points = (
    191              wx.Point(corner_x - DATA_INDICATOR_SIZE, rect.Y),
    192              wx.Point(corner_x, rect.Y),
    193              wx.Point(corner_x, rect.Y + DATA_INDICATOR_SIZE),
    194          )
    195          dc.SetBrush(self._get_balloon_indicator_brush(event))
    196          dc.SetPen(wx.TRANSPARENT_PEN)
    197          dc.DrawPolygon(points)
    198
    199 -    def _draw_text(self, dc, rect, event):
    200          # Ensure that we can't draw content outside inner rectangle
    201          if self._there_is_room_for_the_text(rect):
    202              self._draw_the_text(dc, rect, event)
    203
    204 -    def _there_is_room_for_the_text(self, rect):
    205          return self._get_inner_rect(rect).Width > 0
    206
    207 -    def _draw_the_text(self, dc, rect, event):
    208          self._set_text_foreground_color(dc, event)
    209          if event.is_container() and EXTENDED_CONTAINER_HEIGHT.enabled():
    210              EXTENDED_CONTAINER_HEIGHT.draw_container_text_top_adjusted(event.get_text(), dc, rect)
    211          else:
    212              self._draw_normal_text(dc, rect, event)
    213          dc.DestroyClippingRegion()
    214
    215 -    def _draw_normal_text(self, dc, rect, event):
    216          self._set_clipping_rect(dc, rect)
    217          dc.DrawText(self._get_text(event), self._calc_x_pos(dc, rect, event), self._calc_y_pos(rect))
    218
    219 -    def _get_text(self, event):
    220          if event.get_progress() == 100 and self.view_properties.get_display_checkmark_on_events_done():
    221              return "\u2714" + event.get_text()
    222          else:
    223              return event.get_text()
    224
    225 -    def _get_inner_rect(self, rect):
    226          # Under windows with wx 3.0 the following lined doesn't work !!!
    227          # return wx.Rect(*rect).Deflate(INNER_PADDING, INNER_PADDING)
    228          # The x-coordinate is corrupted
    229          r = wx.Rect(*rect)
    230          r.Deflate(INNER_PADDING, INNER_PADDING)
    231          return r
    232
    233 -    def _set_clipping_rect(self, dc, rect):
    234          dc.SetClippingRegion(self._get_inner_rect(rect))
    235
    236 -    def _calc_x_pos(self, dc, rect, event):
    237          inner_rect = self._get_inner_rect(rect)
    238          text_x = inner_rect.X
    239          text_x = self._adjust_x_for_edge_icons(event, rect, text_x)
    240          text_x = self._adjust_x_for_centered_text(dc, event, inner_rect, text_x)
    241          return text_x
    242
    243 -    def _adjust_x_for_edge_icons(self, event, rect, text_x):
    244          if self._event_has_edge_icons(event):
    245              text_x += rect.Height // 2
    246          return text_x
    247
    248 -    def _adjust_x_for_centered_text(self, dc, event, inner_rect, text_x):
    249          if self.center_text:
    250              text_x = self._center_text(dc, event, inner_rect, text_x)
    251          return text_x
    252
    253 -    def _event_has_edge_icons(self, event):
    254          return event.get_fuzzy() or event.get_locked()
    255
    256 -    def _calc_y_pos(self, rect):
    257          return self._get_inner_rect(rect).Y
    258
    259 -    def _center_text(self, dc, event, inner_rect, text_x):
    260          width, _ = dc.GetTextExtent(self._get_text(event))
    261          return max(text_x, text_x + (inner_rect.width - width) // 2)
    262
    263 -    def _set_text_foreground_color(self, dc, event):
    264          try:
    265              dc.SetTextForeground(get_colour(event.get_category().font_color))
    266          except:
    267              dc.SetTextForeground(wx.BLACK)
    268
    269 -    def _draw_handles(self, dc, event, rect):
    270
    271          def draw_frame_around_event():
    272              small_rect = wx.Rect(*rect)
    273              small_rect.Deflate(1, 1)
    274              border_color = self._get_border_color(event)
    275              border_color = darken_color(border_color)
    276              pen = wx.Pen(border_color, 1, wx.PENSTYLE_SOLID)
    277              dc.SetBrush(wx.TRANSPARENT_BRUSH)
    278              dc.SetPen(pen)
    279              dc.DrawRectangle(small_rect)
    280
    281          dc.SetClippingRegion(rect)
    282          draw_frame_around_event()
    283          self._draw_all_handles(dc, rect, event)
    284          dc.DestroyClippingRegion()
    285
    286 -    def _draw_all_handles(self, dc, rect, event):
    287
    288          def inflate_clipping_region():
    289              big_rect = wx.Rect(*rect)
    290              big_rect.Inflate(HANDLE_SIZE, HANDLE_SIZE)
    291              dc.DestroyClippingRegion()
    292              dc.SetClippingRegion(big_rect)
    293
    294          def set_pen_and_brush():
    295              dc.SetBrush(wx.BLACK_BRUSH)
    296              dc.SetPen(wx.BLACK_PEN)
    297
    298          def create_handle_rect():
    299              HALF_EVENT_HEIGHT = rect.Height // 2
    300              y = rect.Y + HALF_EVENT_HEIGHT - HALF_HANDLE_SIZE
    301              x = rect.X - HALF_HANDLE_SIZE + 1
    302              return wx.Rect(x, y, HANDLE_SIZE, HANDLE_SIZE)
    303
    304          def draw_rect(handle_rect, offset):
    305              handle_rect.Offset(offset, 0)
    306              dc.DrawRectangle(handle_rect)
    307
    308          def draw_handle_rects(handle_rect):
    309              HALF_EVENT_WIDTH = rect.Width // 2
    310              EVENT_WIDTH = rect.Width
    311              draw_rect(handle_rect, 0)
    312              draw_rect(handle_rect, EVENT_WIDTH - 2)
    313              if not event.ends_today:
    314                  draw_rect(handle_rect, -HALF_EVENT_WIDTH)
    315
    316          inflate_clipping_region()
    317          set_pen_and_brush()
    318          handle_rect = create_handle_rect()
    319          draw_handle_rects(handle_rect)
    320
    321 -    def _draw_hyperlink(self, dc, rect, event):
    322          if event.get_hyperlink():
    323              dc.DrawBitmap(self._get_hyperlink_bitmap(), rect.x + rect.width - 14, rect.y + 4, True)
    324
    325 -    def _inflate_clipping_region(self, dc, rect):
    326          copy = wx.Rect(*rect)
    327          copy.Inflate(10, 0)
    328          dc.DestroyClippingRegion()
    329          dc.SetClippingRegion(copy)
    330
    331 -    def _get_hyperlink_bitmap(self):
    332          return self._get_bitmap(self.view_properties.get_hyperlink_icon())
    333
    334 -    def _get_lock_bitmap(self):
    335          return self._get_bitmap(self.view_properties.get_locked_icon())
    336
    337 -    def _get_fuzzy_bitmap(self):
    338          return self._get_bitmap(self.view_properties.get_fuzzy_icon())
    339
    340 -    def _get_bitmap(self, name):
    341          return wx.Bitmap(os.path.join(EVENT_ICONS_DIR, name))
    342
    343 -    def _draw_milestone_event(self, dc, rect, scene, event, selected):
    344
    345          def create_handle_rect():
    346              HALF_EVENT_HEIGHT = rect.Height // 2
    347              y = rect.Y + HALF_EVENT_HEIGHT - HALF_HANDLE_SIZE
    348              x = rect.X - HALF_HANDLE_SIZE + 1
    349              return wx.Rect(x, y, HANDLE_SIZE, HANDLE_SIZE)
    350
    351          def draw_shape():
    352              # draw_diamond_shape()
    353              draw_rectangle_shape()
    354              # draw_circle_shape()
    355
    356          def draw_rectangle_shape():
    357              dc.DestroyClippingRegion()
    358              dc.SetPen(self._black_solid_pen(1))
    359              if event.get_category() is None:
    360                  dc.SetBrush(wx.Brush(wx.Colour(*event.get_default_color()), wx.BRUSHSTYLE_SOLID))
    361              else:
    362                  dc.SetBrush(wx.Brush(wx.Colour(*event.get_category().get_color()), wx.BRUSHSTYLE_SOLID))
    363              dc.DrawRectangle(rect)
    364
    365          def draw_circle_shape():
    366              half_size = rect.width // 2
    367              dc.DestroyClippingRegion()
    368              dc.SetPen(self._black_solid_pen(1))
    369              dc.SetBrush(wx.Brush(wx.Colour(*event.get_default_color()), wx.BRUSHSTYLE_SOLID))
    370              dc.DrawCircle(rect.x + half_size, rect.y + half_size, 2 * rect.width // 3)
    371
    372          def draw_diamond_shape():
    373              SIZE = 2
    374              x = rect.x
    375              y = rect.y
    376              half_size = rect.width // 2
    377              points = (wx.Point(x - SIZE, y + half_size),
    378                        wx.Point(x + half_size, y - SIZE),
    379                        wx.Point(x + rect.width + SIZE, y + half_size),
    380                        wx.Point(x + half_size, y + rect.width + SIZE))
    381              dc.DestroyClippingRegion()
    382              dc.SetPen(self._black_solid_pen(1))
    383              dc.SetBrush(wx.Brush(wx.Colour(*event.get_default_color()), wx.BUSHSTYLE_SOLID))
    384              dc.DrawPolygon(points)
    385
    386          def draw_label():
    387              x_offset = 6
    388              y_offset = 2
    389              try:
    390                  label = event.get_text()[0]
    391              except IndexError:
    392                  label = " "
    393              dc.DrawText(label, rect.x + x_offset, rect.y + y_offset)
    394
    395          def draw_move_handle():
    396              dc.SetBrush(self._black_solid_brush())
    397              handle_rect = create_handle_rect()
    398              handle_rect.Offset(rect.Width // 2, 0)
    399              dc.DrawRectangle(handle_rect)
    400
    401          draw_shape()
    402          draw_label()
    403          if selected:
    404              draw_move_handle()
    405
    406 -    def _black_solid_pen(self, size):
    407          return wx.Pen(wx.Colour(0, 0, 0), size, wx.PENSTYLE_SOLID)
    408
    409 -    def _black_solid_brush(self):
    410          return wx.Brush(wx.Colour(0, 0, 0), wx.BRUSHSTYLE_SOLID)
    411