Rickard's Projects

This site hosts my projects.

Projects

Recent events

2025-07-12 12:31 Rickard pushed to timeline

changeset:   8007:c13d8a7fe816
tag:         tip
user:        Rickard Lindberg <rickard@rickardlindberg.me>
date:        Sat Jul 12 12:31:22 2025 +0200
summary:     Milestones do not affect position of point events.

diff -r 7ab76958ed12 -r c13d8a7fe816 documentation/changelog.rst
--- a/documentation/changelog.rst Sat Jul 12 12:09:31 2025 +0200
+++ b/documentation/changelog.rst Sat Jul 12 12:31:22 2025 +0200
@@ -41,6 +41,8 @@
 * When milestones overlap, the one you visually click is the one that becomes
   selected. Previously the "first match" became selected.
 
+* Milestones do not affect position of point events.
+
 Windows installer:
 
 * The Windows installer is now built on Linux/Wine and only for 64 bit platforms.
diff -r 7ab76958ed12 -r c13d8a7fe816 source/timelinelib/canvas/drawing/scene.py
--- a/source/timelinelib/canvas/drawing/scene.py Sat Jul 12 12:09:31 2025 +0200
+++ b/source/timelinelib/canvas/drawing/scene.py Sat Jul 12 12:31:22 2025 +0200
@@ -478,7 +478,8 @@
     def _get_list_with_overlapping_point_events(self, event_rect):
         return [(event, rect) for (event, rect) in self.event_data
                 if (self._rects_overlap(event_rect, rect) and
-                    rect.Y < self.divider_y)]
+                    rect.Y < self.divider_y) and
+                    not rect.is_allowed_to_overlap()]
 
     def _rects_overlap(self, rect1, rect2):
         REMOVE_X_PADDING = 2 + self._outer_padding * 2

2025-07-12 12:12 Rickard pushed to timeline

changeset:   8006:7ab76958ed12
user:        Rickard Lindberg <rickard@rickardlindberg.me>
date:        Sat Jul 12 12:09:31 2025 +0200
summary:     Select the milestone that was visually clicked instead of the first matching one.

diff -r 25c17562e3f3 -r 7ab76958ed12 documentation/changelog.rst
--- a/documentation/changelog.rst Fri Jul 11 18:11:51 2025 +0200
+++ b/documentation/changelog.rst Sat Jul 12 12:09:31 2025 +0200
@@ -38,6 +38,9 @@
   divider line if preference "Never show period Events as point Events" is
   checked.
 
+* When milestones overlap, the one you visually click is the one that becomes
+  selected. Previously the "first match" became selected.
+
 Windows installer:
 
 * The Windows installer is now built on Linux/Wine and only for 64 bit platforms.
diff -r 25c17562e3f3 -r 7ab76958ed12 source/timelinelib/canvas/drawing/drawers/default.py
--- a/source/timelinelib/canvas/drawing/drawers/default.py Fri Jul 11 18:11:51 2025 +0200
+++ b/source/timelinelib/canvas/drawing/drawers/default.py Sat Jul 12 12:09:31 2025 +0200
@@ -231,19 +231,13 @@
         return self.snap(start), self.snap(end)
 
     def event_at(self, x, y, alt_down=False):
-        container_event = None
-        for (event, rect) in self.scene.event_data:
+        for (event, rect) in reversed(self.scene.event_data):
             if event.is_container():
                 rect = self._adjust_container_rect_for_hittest(rect)
             if rect.Contains(wx.Point(x, y)):
                 self.set_horizontal_mouse_position_factor(event, x)
-                if event.is_container():
-                    if self.scene.view_properties.is_selected(event):
-                        return event
-                    container_event = event
-                else:
-                    return event
-        return container_event
+                return event
+        return None
 
     def set_horizontal_mouse_position_factor(self, event, x):
         try:

2025-07-11 18:12 Rickard pushed to timeline

changeset:   8005:25c17562e3f3
user:        Rickard Lindberg <rickard@rickardlindberg.me>
date:        Fri Jul 11 18:11:51 2025 +0200
summary:     Prevent "SyntaxWarning: invalid escape sequence '\ '".

diff -r 43e78c60f59a -r 25c17562e3f3 source/timelinelib/canvas/drawing/drawers/ballondrawer.py
--- a/source/timelinelib/canvas/drawing/drawers/ballondrawer.py Fri Jul 11 12:38:41 2025 +0200
+++ b/source/timelinelib/canvas/drawing/drawers/ballondrawer.py Fri Jul 11 18:11:51 2025 +0200
@@ -115,11 +115,11 @@
                     W
            |----------------|
              ______________           _
-            /              \          |             R = Corner Radius
+            *              *          |             R = Corner Radius
            |                |         |            AA = Left Arrow-leg angle
            |  W_ARROW       |         |  H     MARGIN = Text margin
            |     |--|       |         |             * = Starting point
-            \____    ______/          _
+            *____    ______*          _
                 /  /                  |
                /_/                    |  H_ARROW
               *                       -

2025-07-11 12:41 Rickard pushed to timeline

changeset:   8004:43e78c60f59a
user:        Rickard Lindberg <rickard@rickardlindberg.me>
date:        Fri Jul 11 12:38:41 2025 +0200
summary:     Get rid of symbol_rect by introducing FloatingRect.

diff -r c23d402b991b -r 43e78c60f59a source/timelinelib/canvas/drawing/drawers/default.py
--- a/source/timelinelib/canvas/drawing/drawers/default.py Fri Jul 11 12:15:03 2025 +0200
+++ b/source/timelinelib/canvas/drawing/drawers/default.py Fri Jul 11 12:38:41 2025 +0200
@@ -243,10 +243,6 @@
                     container_event = event
                 else:
                     return event
-            else:
-                if hasattr(event, 'symbol_rect'):
-                    if event.symbol_rect.Contains(wx.Point(x, y)):
-                        return event
         return container_event
 
     def set_horizontal_mouse_position_factor(self, event, x):
@@ -548,8 +544,6 @@
     def _draw_ballon(self, event, event_rect, sticky):
         """Draw one ballon on a selected event that has 'description' data."""
         ballon_drawer = BallonDrawer(self.dc, self.scene, self.appearance, event)
-        if hasattr(event, 'symbol_rect'):
-            event_rect = event.symbol_rect
         self.balloon_data.append(ballon_drawer.draw(event_rect, sticky))
 
     def get_period_xpos(self, time_period):
diff -r c23d402b991b -r 43e78c60f59a source/timelinelib/canvas/drawing/rect.py
--- a/source/timelinelib/canvas/drawing/rect.py Fri Jul 11 12:15:03 2025 +0200
+++ b/source/timelinelib/canvas/drawing/rect.py Fri Jul 11 12:38:41 2025 +0200
@@ -32,4 +32,13 @@
         return clone
 
     def Clone(self):
-        return Rect(self.X, self.Y, self.Width, self.Height)
+        return self.__class__(self.X, self.Y, self.Width, self.Height)
+
+    def is_allowed_to_overlap(self):
+        return False
+
+
+class FloatingRect(Rect):
+
+    def is_allowed_to_overlap(self):
+        return True
diff -r c23d402b991b -r 43e78c60f59a source/timelinelib/canvas/drawing/scene.py
--- a/source/timelinelib/canvas/drawing/scene.py Fri Jul 11 12:15:03 2025 +0200
+++ b/source/timelinelib/canvas/drawing/scene.py Fri Jul 11 12:38:41 2025 +0200
@@ -22,6 +22,7 @@
 from timelinelib.canvas.data import TimePeriod
 from timelinelib.features.experimental.experimentalfeatures import EXTENDED_CONTAINER_STRATEGY
 from timelinelib.canvas.drawing.rect import Rect
+from timelinelib.canvas.drawing.rect import FloatingRect
 
 
 FORWARD = 1
@@ -302,7 +303,10 @@
 
     def _calc_ideal_rect_for_non_period_event(self, event):
         if self.never_show_period_events_as_point_events() and event.is_period():
-            return self._calc_invisible_wx_rect()
+            x = self.x_pos_for_time(event.mean_time())
+            y0 = self.divider_y
+            y1 = y0 + 10
+            return FloatingRect(x - 5, y1 - 5, 10, 10).CloneInflate(self._outer_padding, self._outer_padding)
         rw = self._calc_width_for_non_period_event(event)
         rh = self._calc_height_for_non_period_event(event)
         rx = self._calc_x_pos_for_non_period_event(event, rw)
@@ -311,12 +315,9 @@
             rect = Rect(rx, ry, rw, rh)
             rect.SetWidth(rect.GetHeight())
             rect.SetX(self._metrics.calc_x(event.get_time_period().start_time) - rw // 2)
-            return rect
+            return FloatingRect(rect)
         return self._calc_ideal_wx_rect(rx, ry, rw, rh)
 
-    def _calc_invisible_wx_rect(self):
-        return self._calc_ideal_wx_rect(-1, -1, 0, 0)
-
     def _calc_width_for_non_period_event(self, event):
         tw, th = self._get_text_size(event.get_text())
         rw = tw + 2 * self._inner_padding + 2 * self._outer_padding
@@ -401,7 +402,7 @@
         return len(self.events_from_db) - visible_events_count
 
     def _prevent_overlapping_by_adjusting_rect_y(self, event, event_rect):
-        if event.is_milestone():
+        if event_rect.is_allowed_to_overlap():
             return
         if self._is_subevent_in_extended_container_strategy(event):
             self._adjust_subevent_rect(event, event_rect)
diff -r c23d402b991b -r 43e78c60f59a source/timelinelib/canvas/eventboxdrawers/defaulteventboxdrawer.py
--- a/source/timelinelib/canvas/eventboxdrawers/defaulteventboxdrawer.py Fri Jul 11 12:15:03 2025 +0200
+++ b/source/timelinelib/canvas/eventboxdrawers/defaulteventboxdrawer.py Fri Jul 11 12:38:41 2025 +0200
@@ -46,21 +46,20 @@
         self.center_text = scene.center_text()
         if event.is_milestone():
             DefaultMilestoneDrawer(rect, event, selected, view_properties).draw(dc)
-        elif scene.never_show_period_events_as_point_events() and rect.y < scene.divider_y and event.is_period():
-            self._draw_period_event_as_symbol_below_divider_line(dc, scene, event)
+        elif scene.never_show_period_events_as_point_events() and rect.is_allowed_to_overlap() and event.is_period():
+            self._draw_period_event_as_symbol_below_divider_line(dc, rect, scene, event)
         else:
             self._draw_event_box(dc, rect, event, selected)
 
-    def _draw_period_event_as_symbol_below_divider_line(self, dc, scene, event):
+    def _draw_period_event_as_symbol_below_divider_line(self, dc, rect, scene, event):
         dc.DestroyClippingRegion()
-        x = scene.x_pos_for_time(event.mean_time())
+        x = rect.X + rect.Width // 2
         y0 = scene.divider_y
-        y1 = y0 + 10
+        y1 = rect.Y + rect.Height // 2
         dc.SetBrush(black_solid_brush())
         dc.SetPen(black_solid_pen(1))
         dc.DrawLine(x, y0, x, y1)
         dc.DrawCircle(x, y1, 2)
-        event.symbol_rect = Rect(x - 5, y1 - 5, 10, 10)
 
     def _draw_event_box(self, dc, rect, event, selected):
         self._draw_background(dc, rect, event)

2025-07-11 12:15 Rickard pushed to timeline

changeset:   8001:c9f68492c01b
user:        Rickard Lindberg <rickard@rickardlindberg.me>
date:        Fri Jul 11 11:51:21 2025 +0200
summary:     Cursor.rect returns a Rect object.

diff -r 546241b2cec8 -r c9f68492c01b source/timelinelib/canvas/drawing/drawers/default.py
--- a/source/timelinelib/canvas/drawing/drawers/default.py Fri Jul 11 11:37:43 2025 +0200
+++ b/source/timelinelib/canvas/drawing/drawers/default.py Fri Jul 11 11:51:21 2025 +0200
@@ -188,7 +188,7 @@
         if view_properties._selection_rect:
             self.dc.SetPen(wx.BLACK_PEN)
             self.dc.SetBrush(wx.Brush(wx.WHITE, style=BRUSHSTYLE_TRANSPARENT))
-            self.dc.DrawRectangle(*view_properties._selection_rect)
+            self.dc.DrawRectangle(view_properties._selection_rect)
 
     def _perform_normal_drawing(self, view_properties):
         self._draw_period_selection(view_properties)
diff -r 546241b2cec8 -r c9f68492c01b source/timelinelib/wxgui/cursor.py
--- a/source/timelinelib/wxgui/cursor.py Fri Jul 11 11:37:43 2025 +0200
+++ b/source/timelinelib/wxgui/cursor.py Fri Jul 11 11:51:21 2025 +0200
@@ -21,6 +21,9 @@
 """
 
 
+from timelinelib.canvas.drawing.rect import Rect
+
+
 class Cursor:
 
     def __init__(self, x=0, y=0):
@@ -54,7 +57,7 @@
     def rect(self):
         x0, y0 = self._start_pos
         x1, y1 = self._current_pos
-        return min(x0, x1), min(y0, y1), abs(x1 - x0), abs(y0 - y1)
+        return Rect(min(x0, x1), min(y0, y1), abs(x1 - x0), abs(y0 - y1))
 
     def has_moved(self):
         return self._has_moved
diff -r 546241b2cec8 -r c9f68492c01b test/specs/wxgui/cursor.py
--- a/test/specs/wxgui/cursor.py Fri Jul 11 11:37:43 2025 +0200
+++ b/test/specs/wxgui/cursor.py Fri Jul 11 11:51:21 2025 +0200
@@ -18,6 +18,7 @@
 
 from timelinelib.wxgui.cursor import Cursor
 from timelinelib.test.cases.unit import UnitTestCase
+from timelinelib.canvas.drawing.rect import Rect
 
 
 X = 3
@@ -63,7 +64,7 @@
 
     def test_can_calculate_rect(self):
         self.cursor.move(XX, YY)
-        self.assertEqual((XX, YY, X - XX, Y - YY), self.cursor.rect)
+        self.assertEqual(Rect(XX, YY, X - XX, Y - YY), self.cursor.rect)
 
     def setUp(self):
         self.cursor = Cursor(X, Y)

changeset:   8003:c23d402b991b
user:        Rickard Lindberg <rickard@rickardlindberg.me>
date:        Fri Jul 11 12:15:03 2025 +0200
summary:     Ensure Rect is only instantiated with (x, y, width, height).

diff -r 72cfb98d5e1c -r c23d402b991b source/timelinelib/canvas/drawing/graphobject.py
--- a/source/timelinelib/canvas/drawing/graphobject.py Fri Jul 11 11:55:40 2025 +0200
+++ b/source/timelinelib/canvas/drawing/graphobject.py Fri Jul 11 12:15:03 2025 +0200
@@ -83,7 +83,7 @@
     @property
     def rect(self):
         """Getter property."""
-        return Rect(self._rect)
+        return Rect(*self._rect)
 
     @property
     def width(self):
diff -r 72cfb98d5e1c -r c23d402b991b source/timelinelib/canvas/drawing/rect.py
--- a/source/timelinelib/canvas/drawing/rect.py Fri Jul 11 11:55:40 2025 +0200
+++ b/source/timelinelib/canvas/drawing/rect.py Fri Jul 11 12:15:03 2025 +0200
@@ -22,6 +22,14 @@
 class Rect(wx.Rect):
 
     def CloneInflate(self, dx, dy):
-        clone = Rect(self)
+        clone = self.Clone()
         clone.Inflate(dx, dy)
         return clone
+
+    def CloneDeflate(self, dx, dy):
+        clone = self.Clone()
+        clone.Deflate(dx, dy)
+        return clone
+
+    def Clone(self):
+        return Rect(self.X, self.Y, self.Width, self.Height)
diff -r 72cfb98d5e1c -r c23d402b991b source/timelinelib/canvas/eventboxdrawers/defaulteventboxdrawer.py
--- a/source/timelinelib/canvas/eventboxdrawers/defaulteventboxdrawer.py Fri Jul 11 11:55:40 2025 +0200
+++ b/source/timelinelib/canvas/eventboxdrawers/defaulteventboxdrawer.py Fri Jul 11 12:15:03 2025 +0200
@@ -246,8 +246,7 @@
     def _draw_handles(self, dc, event, rect):
 
         def draw_frame_around_event():
-            small_rect = Rect(*rect)
-            small_rect.Deflate(1, 1)
+            small_rect = rect.CloneDeflate(1, 1)
             border_color = event.get_border_color()
             border_color = darken_color(border_color)
             pen = wx.Pen(border_color, 1, wx.PENSTYLE_SOLID)
@@ -276,10 +275,8 @@
 
     @staticmethod
     def _inflate_clipping_region(dc, rect):
-        copy = Rect(*rect)
-        copy.Inflate(10, 0)
         dc.DestroyClippingRegion()
-        dc.SetClippingRegion(copy)
+        dc.SetClippingRegion(rect.CloneInflate(10, 0))
 
     def _get_hyperlink_bitmap(self):
         return get_bitmap(self.view_properties.get_hyperlink_icon())
@@ -292,7 +289,7 @@
 
 
 def deflate_rect(rect, dx=INNER_PADDING, dy=INNER_PADDING):
-    return Rect(*rect).Deflate(dx, dy)
+    return rect.CloneDeflate(dx, dy)
 
 
 def center_point_with_offset(rect, dx=0, dy=0):
diff -r 72cfb98d5e1c -r c23d402b991b source/timelinelib/canvas/eventboxdrawers/gradienteventboxdrawer.py
--- a/source/timelinelib/canvas/eventboxdrawers/gradienteventboxdrawer.py Fri Jul 11 11:55:40 2025 +0200
+++ b/source/timelinelib/canvas/eventboxdrawers/gradienteventboxdrawer.py Fri Jul 11 12:15:03 2025 +0200
@@ -37,4 +37,4 @@
 
 
 def deflate_rect(rect, dx=1, dy=1):
-    return Rect(*rect).Deflate(dx, dy)
+    return rect.CloneDeflate(dx, dy)
diff -r 72cfb98d5e1c -r c23d402b991b source/timelinelib/canvas/eventboxdrawers/othergradienteventboxdrawer.py
--- a/source/timelinelib/canvas/eventboxdrawers/othergradienteventboxdrawer.py Fri Jul 11 11:55:40 2025 +0200
+++ b/source/timelinelib/canvas/eventboxdrawers/othergradienteventboxdrawer.py Fri Jul 11 12:15:03 2025 +0200
@@ -123,4 +123,4 @@
 
 
 def deflate_rect(rect, dx=1, dy=1):
-    return Rect(*rect).Deflate(dx, dy)
+    return rect.CloneDeflate(dx, dy)
diff -r 72cfb98d5e1c -r c23d402b991b source/timelinelib/canvas/gc.py
--- a/source/timelinelib/canvas/gc.py Fri Jul 11 11:55:40 2025 +0200
+++ b/source/timelinelib/canvas/gc.py Fri Jul 11 12:15:03 2025 +0200
@@ -120,13 +120,13 @@
 
 
 def left_half_of_rect(rect):
-    r = Rect(*rect).Deflate(1, 1)
+    r = rect.CloneDeflate(1, 1)
     r.SetWidth(r.GetWidth() // 2)
     return r
 
 
 def right_half_of_rect(rect):
-    r = Rect(*rect).Deflate(1, 1)
+    r = rect.CloneDeflate(1, 1)
     r.SetWidth(r.GetWidth() // 2)
     r.SetPosition(wx.Point(r.GetX() + r.GetWidth(), r.GetY()))
     return r
diff -r 72cfb98d5e1c -r c23d402b991b test/unit/canvas/drawing/rect.py
--- a/test/unit/canvas/drawing/rect.py Fri Jul 11 11:55:40 2025 +0200
+++ b/test/unit/canvas/drawing/rect.py Fri Jul 11 12:15:03 2025 +0200
@@ -27,3 +27,15 @@
         inner_box_tuple = (10, 10, 10, 10)
         self.assertTrue(box.Intersects(Rect(*inner_box_tuple)))
         self.assertTrue(box.Intersects(inner_box_tuple))
+
+    def test_clone_and_inflate(self):
+        self.assertEqual(
+            Rect(0, 0, 10, 10).CloneInflate(1, 2),
+            Rect(-1, -2, 12, 14)
+        )
+
+    def test_clone_and_deflate(self):
+        self.assertEqual(
+            Rect(0, 0, 10, 10).CloneDeflate(1, 2),
+            Rect(1, 2, 8, 6)
+        )

2025-07-11 11:37 Rickard pushed to timeline

changeset:   7999:1d3b1ab07169
user:        Rickard Lindberg <rickard@rickardlindberg.me>
date:        Fri Jul 11 11:22:15 2025 +0200
summary:     Add Rect.CloneInflate and use it in _draw_container.

diff -r 9dc171f0d9e0 -r 1d3b1ab07169 source/timelinelib/canvas/drawing/drawers/default.py
--- a/source/timelinelib/canvas/drawing/drawers/default.py Fri Jul 11 11:13:12 2025 +0200
+++ b/source/timelinelib/canvas/drawing/drawers/default.py Fri Jul 11 11:22:15 2025 +0200
@@ -505,7 +505,7 @@
                 self._draw_box(rect, event, view_properties)
 
     def _draw_container(self, event, rect, view_properties):
-        box_rect = Rect(rect.X - 2, rect.Y - 2, rect.Width + 4, rect.Height + 4)
+        box_rect = rect.CloneInflate(2, 2)
         if EXTENDED_CONTAINER_HEIGHT.enabled():
             box_rect = EXTENDED_CONTAINER_HEIGHT.get_vertical_larger_box_rect(rect)
         self._draw_box(box_rect, event, view_properties)
diff -r 9dc171f0d9e0 -r 1d3b1ab07169 source/timelinelib/canvas/drawing/rect.py
--- a/source/timelinelib/canvas/drawing/rect.py Fri Jul 11 11:13:12 2025 +0200
+++ b/source/timelinelib/canvas/drawing/rect.py Fri Jul 11 11:22:15 2025 +0200
@@ -20,4 +20,8 @@
 
 
 class Rect(wx.Rect):
-    pass
+
+    def CloneInflate(self, dx, dy):
+        clone = Rect(self)
+        clone.Inflate(dx, dy)
+        return clone

changeset:   8000:546241b2cec8
user:        Rickard Lindberg <rickard@rickardlindberg.me>
date:        Fri Jul 11 11:37:43 2025 +0200
summary:     There is no need to convert Rect.Intersects to a Rect object.

diff -r 1d3b1ab07169 -r 546241b2cec8 source/timelinelib/canvas/drawing/drawers/default.py
--- a/source/timelinelib/canvas/drawing/drawers/default.py Fri Jul 11 11:22:15 2025 +0200
+++ b/source/timelinelib/canvas/drawing/drawers/default.py Fri Jul 11 11:37:43 2025 +0200
@@ -32,7 +32,6 @@
 from wx import BRUSHSTYLE_TRANSPARENT
 import timelinelib.wxgui.components.font as font
 from timelinelib.canvas.drawing.drawers.ballondrawer import BallonDrawer
-from timelinelib.canvas.drawing.rect import Rect
 
 
 OUTER_PADDING = 5  # Space between event boxes (pixels)
@@ -259,8 +258,12 @@
             event.horizontal_mouse_position_factor = None
 
     def get_events_in_rect(self, rect):
-        wx_rect = Rect(*rect)
-        return [event for (event, rect) in self.scene.event_data if rect.Intersects(wx_rect)]
+        return [
+            event
+            for (event, rect)
+            in self.scene.event_data
+            if rect.Intersects(rect)
+        ]
 
     def _adjust_container_rect_for_hittest(self, rect):
         if EXTENDED_CONTAINER_HEIGHT.enabled():
diff -r 1d3b1ab07169 -r 546241b2cec8 test/unit/canvas/drawing/rect.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/unit/canvas/drawing/rect.py Fri Jul 11 11:37:43 2025 +0200
@@ -0,0 +1,29 @@
+# Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018  Rickard Lindberg, Roger Lindberg
+#
+# This file is part of Timeline.
+#
+# Timeline is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Timeline is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Timeline.  If not, see <http://www.gnu.org/licenses/>.
+
+
+from timelinelib.test.cases.unit import UnitTestCase
+from timelinelib.canvas.drawing.rect import Rect
+
+
+class describe_rect(UnitTestCase):
+
+    def test_intersects_can_take_both_rect_and_tuple(self):
+        box = Rect(0, 0, 100, 100)
+        inner_box_tuple = (10, 10, 10, 10)
+        self.assertTrue(box.Intersects(Rect(*inner_box_tuple)))
+        self.assertTrue(box.Intersects(inner_box_tuple))

2025-07-11 11:13 Rickard pushed to timeline

changeset:   7998:9dc171f0d9e0
user:        Rickard Lindberg <rickard@rickardlindberg.me>
date:        Fri Jul 11 11:13:12 2025 +0200
summary:     wx.DC.DrawPolygon expects integers.

diff -r 8f466597a40a -r 9dc171f0d9e0 source/timelinelib/canvas/gc.py
--- a/source/timelinelib/canvas/gc.py Fri Jul 11 10:17:31 2025 +0200
+++ b/source/timelinelib/canvas/gc.py Fri Jul 11 11:13:12 2025 +0200
@@ -100,7 +100,7 @@
         self._points.append(self._create_point(x, y, radius, end_angle))
 
     def _create_point(self, x, y, radius, angle):
-        return x + radius * math.cos(angle), y + radius * math.sin(angle)
+        return int(x + radius * math.cos(angle)), int(y + radius * math.sin(angle))
 
     def CloseSubpath(self):
         if self._points:

2025-07-11 10:17 Rickard pushed to timeline

changeset:   7997:8f466597a40a
user:        Rickard Lindberg <rickard@rickardlindberg.me>
date:        Fri Jul 11 10:17:31 2025 +0200
summary:     Replace wx.Rect with subclass Rect that can attract more functionality.

diff -r a01f67e463eb -r 8f466597a40a source/timelinelib/canvas/drawing/drawers/ballondrawer.py
--- a/source/timelinelib/canvas/drawing/drawers/ballondrawer.py Thu Jul 10 11:02:13 2025 +0200
+++ b/source/timelinelib/canvas/drawing/drawers/ballondrawer.py Fri Jul 11 10:17:31 2025 +0200
@@ -25,6 +25,7 @@
 from timelinelib.canvas.gc import create_gc
 import timelinelib.wxgui.components.font as font
 from timelinelib.wxgui.utils import load_bitmap
+from timelinelib.canvas.drawing.rect import Rect
 
 BALLOON_RADIUS = 12
 ARROW_OFFSET = BALLOON_RADIUS + 25
@@ -200,7 +201,7 @@
         by = top_y
         bw = W + R + 1
         bh = H + R + H_ARROW + 1
-        bounding_rect = wx.Rect(bx, by, bw, bh)
+        bounding_rect = Rect(bx, by, bw, bh)
         return bounding_rect, left_x + BALLOON_RADIUS, top_y + BALLOON_RADIUS
 
     def draw_icon(self, x, y):
diff -r a01f67e463eb -r 8f466597a40a source/timelinelib/canvas/drawing/drawers/default.py
--- a/source/timelinelib/canvas/drawing/drawers/default.py Thu Jul 10 11:02:13 2025 +0200
+++ b/source/timelinelib/canvas/drawing/drawers/default.py Fri Jul 11 10:17:31 2025 +0200
@@ -32,6 +32,7 @@
 from wx import BRUSHSTYLE_TRANSPARENT
 import timelinelib.wxgui.components.font as font
 from timelinelib.canvas.drawing.drawers.ballondrawer import BallonDrawer
+from timelinelib.canvas.drawing.rect import Rect
 
 
 OUTER_PADDING = 5  # Space between event boxes (pixels)
@@ -258,7 +259,7 @@
             event.horizontal_mouse_position_factor = None
 
     def get_events_in_rect(self, rect):
-        wx_rect = wx.Rect(*rect)
+        wx_rect = Rect(*rect)
         return [event for (event, rect) in self.scene.event_data if rect.Intersects(wx_rect)]
 
     def _adjust_container_rect_for_hittest(self, rect):
@@ -504,7 +505,7 @@
                 self._draw_box(rect, event, view_properties)
 
     def _draw_container(self, event, rect, view_properties):
-        box_rect = wx.Rect(rect.X - 2, rect.Y - 2, rect.Width + 4, rect.Height + 4)
+        box_rect = Rect(rect.X - 2, rect.Y - 2, rect.Width + 4, rect.Height + 4)
         if EXTENDED_CONTAINER_HEIGHT.enabled():
             box_rect = EXTENDED_CONTAINER_HEIGHT.get_vertical_larger_box_rect(rect)
         self._draw_box(box_rect, event, view_properties)
diff -r a01f67e463eb -r 8f466597a40a source/timelinelib/canvas/drawing/drawers/legenddrawer.py
--- a/source/timelinelib/canvas/drawing/drawers/legenddrawer.py Thu Jul 10 11:02:13 2025 +0200
+++ b/source/timelinelib/canvas/drawing/drawers/legenddrawer.py Fri Jul 11 10:17:31 2025 +0200
@@ -24,6 +24,7 @@
 import timelinelib.wxgui.components.font as font
 from timelinelib.canvas.drawing.graphobject import GraphObject
 from timelinelib.canvas.drawing.utils import darken_color
+from timelinelib.canvas.drawing.rect import Rect
 
 
 # INNER_PADDING = 3  # Space inside event box to text (pixels)
@@ -94,7 +95,7 @@
     def _draw_rectangle(self, go):
         self._dc.SetBrush(go.brush_color)
         self._dc.SetPen(go.pen_color)
-        self._dc.DrawRectangle(wx.Rect(*go.rect))
+        self._dc.DrawRectangle(Rect(*go.rect))
 
     def _create_graph_object(self):
         tw, th = self._get_text_metrics(self._categories)
diff -r a01f67e463eb -r 8f466597a40a source/timelinelib/canvas/drawing/rect.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/source/timelinelib/canvas/drawing/rect.py Fri Jul 11 10:17:31 2025 +0200
@@ -0,0 +1,23 @@
+# Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018  Rickard Lindberg, Roger Lindberg
+#
+# This file is part of Timeline.
+#
+# Timeline is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Timeline is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Timeline.  If not, see <http://www.gnu.org/licenses/>.
+
+
+import wx
+
+
+class Rect(wx.Rect):
+    pass
diff -r a01f67e463eb -r 8f466597a40a source/timelinelib/canvas/drawing/scene.py
--- a/source/timelinelib/canvas/drawing/scene.py Thu Jul 10 11:02:13 2025 +0200
+++ b/source/timelinelib/canvas/drawing/scene.py Fri Jul 11 10:17:31 2025 +0200
@@ -21,6 +21,7 @@
 from timelinelib.canvas.drawing.utils import Metrics
 from timelinelib.canvas.data import TimePeriod
 from timelinelib.features.experimental.experimentalfeatures import EXTENDED_CONTAINER_STRATEGY
+from timelinelib.canvas.drawing.rect import Rect
 
 
 FORWARD = 1
@@ -270,7 +271,6 @@
         rh = self._calc_height_for_period_event(event)
         rx = self._calc_x_pos_for_period_event(event)
         ry = self._calc_y_pos_for_period_event(event)
-        rect = wx.Rect(rx, ry, rw, rh)
         return self._calc_ideal_wx_rect(rx, ry, rw, rh)
 
     def _calc_width_for_period_event(self, event):
@@ -308,7 +308,7 @@
         rx = self._calc_x_pos_for_non_period_event(event, rw)
         ry = self._calc_y_pos_for_non_period_event(event, rh)
         if event.is_milestone():
-            rect = wx.Rect(rx, ry, rw, rh)
+            rect = Rect(rx, ry, rw, rh)
             rect.SetWidth(rect.GetHeight())
             rect.SetX(self._metrics.calc_x(event.get_time_period().start_time) - rw // 2)
             return rect
@@ -362,7 +362,7 @@
         right_edge_x = rx + rw
         if right_edge_x > self.width + MARGIN:
             rw = rw - right_edge_x + self.width + MARGIN
-        return wx.Rect(rx, ry, rw, rh)
+        return Rect(rx, ry, rw, rh)
 
     def _calc_strips_sizes_and_positions(self):
         """Fill the two arrays `minor_strip_data` and `major_strip_data`."""
diff -r a01f67e463eb -r 8f466597a40a source/timelinelib/canvas/eventboxdrawers/defaulteventboxdrawer.py
--- a/source/timelinelib/canvas/eventboxdrawers/defaulteventboxdrawer.py Thu Jul 10 11:02:13 2025 +0200
+++ b/source/timelinelib/canvas/eventboxdrawers/defaulteventboxdrawer.py Fri Jul 11 10:17:31 2025 +0200
@@ -26,6 +26,7 @@
 from timelinelib.canvas.eventboxdrawers.defaultmilestonedrawer import DefaultMilestoneDrawer
 from timelinelib.canvas.eventboxdrawers.handlerect import HandleRect, MIDDLE_HANDLE, LEFT_HANDLE, RIGHT_HANDLE
 from timelinelib.wxgui.utils import load_bitmap
+from timelinelib.canvas.drawing.rect import Rect
 
 
 HANDLE_SIZE = 4
@@ -59,7 +60,7 @@
         dc.SetPen(black_solid_pen(1))
         dc.DrawLine(x, y0, x, y1)
         dc.DrawCircle(x, y1, 2)
-        event.symbol_rect = wx.Rect(x - 5, y1 - 5, 10, 10)
+        event.symbol_rect = Rect(x - 5, y1 - 5, 10, 10)
 
     def _draw_event_box(self, dc, rect, event, selected):
         self._draw_background(dc, rect, event)
@@ -166,7 +167,7 @@
             rx = 0
         if w > self.scene.width:
             w = self.scene.width
-        return wx.Rect(int(rx), int(y), int(w), int(h))
+        return Rect(int(rx), int(y), int(w), int(h))
 
     def _draw_balloon_indicator(self, dc, event, rect):
         """
@@ -245,7 +246,7 @@
     def _draw_handles(self, dc, event, rect):
 
         def draw_frame_around_event():
-            small_rect = wx.Rect(*rect)
+            small_rect = Rect(*rect)
             small_rect.Deflate(1, 1)
             border_color = event.get_border_color()
             border_color = darken_color(border_color)
@@ -275,7 +276,7 @@
 
     @staticmethod
     def _inflate_clipping_region(dc, rect):
-        copy = wx.Rect(*rect)
+        copy = Rect(*rect)
         copy.Inflate(10, 0)
         dc.DestroyClippingRegion()
         dc.SetClippingRegion(copy)
@@ -291,7 +292,7 @@
 
 
 def deflate_rect(rect, dx=INNER_PADDING, dy=INNER_PADDING):
-    return wx.Rect(*rect).Deflate(dx, dy)
+    return Rect(*rect).Deflate(dx, dy)
 
 
 def center_point_with_offset(rect, dx=0, dy=0):
diff -r a01f67e463eb -r 8f466597a40a source/timelinelib/canvas/eventboxdrawers/gradienteventboxdrawer.py
--- a/source/timelinelib/canvas/eventboxdrawers/gradienteventboxdrawer.py Thu Jul 10 11:02:13 2025 +0200
+++ b/source/timelinelib/canvas/eventboxdrawers/gradienteventboxdrawer.py Fri Jul 11 10:17:31 2025 +0200
@@ -22,6 +22,7 @@
 from timelinelib.canvas.drawing.utils import lighten_color
 from timelinelib.canvas.eventboxdrawers.defaulteventboxdrawer import DefaultEventBoxDrawer
 import timelinelib.meta.overrides as mark
+from timelinelib.canvas.drawing.rect import Rect
 
 
 class GradientEventBoxDrawer(DefaultEventBoxDrawer):
@@ -36,4 +37,4 @@
 
 
 def deflate_rect(rect, dx=1, dy=1):
-    return wx.Rect(*rect).Deflate(dx, dy)
+    return Rect(*rect).Deflate(dx, dy)
diff -r a01f67e463eb -r 8f466597a40a source/timelinelib/canvas/eventboxdrawers/handlerect.py
--- a/source/timelinelib/canvas/eventboxdrawers/handlerect.py Thu Jul 10 11:02:13 2025 +0200
+++ b/source/timelinelib/canvas/eventboxdrawers/handlerect.py Fri Jul 11 10:17:31 2025 +0200
@@ -20,13 +20,14 @@
 
 from timelinelib.canvas.drawing.utils import black_solid_pen
 from timelinelib.canvas.drawing.utils import black_solid_brush
+from timelinelib.canvas.drawing.rect import Rect
 
 LEFT_HANDLE = 'left'
 MIDDLE_HANDLE = 'middle'
 RIGHT_HANDLE = 'right'
 
 
-class HandleRect(wx.Rect):
+class HandleRect(Rect):
     """
     This class represents the little squared rectangle showing up when an event
     is selected. It's a handle with which you can resize or move the event.
@@ -35,7 +36,7 @@
     SIZE = 4
 
     def __init__(self, rect, pos=MIDDLE_HANDLE):
-        wx.Rect.__init__(self, wx.Point(0, 0), wx.Size(self.SIZE, self.SIZE))
+        Rect.__init__(self, wx.Point(0, 0), wx.Size(self.SIZE, self.SIZE))
         {LEFT_HANDLE: self._translate_to_left_edge,
          MIDDLE_HANDLE: self._translate_to_middle,
          RIGHT_HANDLE: self._translate_to_right_edge}[pos](rect)
diff -r a01f67e463eb -r 8f466597a40a source/timelinelib/canvas/eventboxdrawers/othergradienteventboxdrawer.py
--- a/source/timelinelib/canvas/eventboxdrawers/othergradienteventboxdrawer.py Thu Jul 10 11:02:13 2025 +0200
+++ b/source/timelinelib/canvas/eventboxdrawers/othergradienteventboxdrawer.py Fri Jul 11 10:17:31 2025 +0200
@@ -23,6 +23,7 @@
 from timelinelib.canvas.eventboxdrawers.defaulteventboxdrawer import DefaultEventBoxDrawer
 from timelinelib.canvas.gc import create_gc
 import timelinelib.meta.overrides as mark
+from timelinelib.canvas.drawing.rect import Rect
 
 
 GRADIENT_STYLE_LEFT = 1
@@ -122,4 +123,4 @@
 
 
 def deflate_rect(rect, dx=1, dy=1):
-    return wx.Rect(*rect).Deflate(dx, dy)
+    return Rect(*rect).Deflate(dx, dy)
diff -r a01f67e463eb -r 8f466597a40a source/timelinelib/canvas/gc.py
--- a/source/timelinelib/canvas/gc.py Thu Jul 10 11:02:13 2025 +0200
+++ b/source/timelinelib/canvas/gc.py Fri Jul 11 10:17:31 2025 +0200
@@ -20,6 +20,8 @@
 
 import wx
 
+from timelinelib.canvas.drawing.rect import Rect
+
 
 class FakeGC:
 
@@ -55,7 +57,7 @@
         # Otherwise if the alpha value of the Item(1).Colour is 0 it means that
         # the event has a fuzzy left side.
         self.dc.SetPen(wx.Pen(wx.WHITE, style=wx.PENSTYLE_TRANSPARENT))
-        rect = wx.Rect(x, y, width, height)
+        rect = Rect(x, y, width, height)
         color = self._gradient_stops.Item(1).Colour[:3]
         transparent_colour = wx.Colour(*color, 0)
         opaque_colour = wx.Colour(*color, 255)
@@ -118,13 +120,13 @@
 
 
 def left_half_of_rect(rect):
-    r = wx.Rect(*rect).Deflate(1, 1)
+    r = Rect(*rect).Deflate(1, 1)
     r.SetWidth(r.GetWidth() // 2)
     return r
 
 
 def right_half_of_rect(rect):
-    r = wx.Rect(*rect).Deflate(1, 1)
+    r = Rect(*rect).Deflate(1, 1)
     r.SetWidth(r.GetWidth() // 2)
     r.SetPosition(wx.Point(r.GetX() + r.GetWidth(), r.GetY()))
-    return r
\ No newline at end of file
+    return r
diff -r a01f67e463eb -r 8f466597a40a source/timelinelib/features/experimental/experimentalfeaturecontainersize.py
--- a/source/timelinelib/features/experimental/experimentalfeaturecontainersize.py Thu Jul 10 11:02:13 2025 +0200
+++ b/source/timelinelib/features/experimental/experimentalfeaturecontainersize.py Fri Jul 11 10:17:31 2025 +0200
@@ -19,6 +19,7 @@
 import wx
 from timelinelib.features.experimental.experimentalfeature import ExperimentalFeature
 from timelinelib.wxgui.components.font import Font
+from timelinelib.canvas.drawing.rect import Rect
 
 
 CONFIG_NAME = "Extend Container height"
@@ -48,12 +49,12 @@
         return OUTER_PAADING
 
     def get_vertical_larger_box_rect(self, rect):
-        return wx.Rect(rect.X - 2, rect.Y - 2 - PADDING, rect.Width + 4, rect.Height + 4 + PADDING)
+        return Rect(rect.X - 2, rect.Y - 2 - PADDING, rect.Width + 4, rect.Height + 4 + PADDING)
 
     def draw_container_text_top_adjusted(self, text, dc, rect):
         old_font = dc.GetFont()
         dc.SetFont(Font(FONT_SIZE))
-        dc.SetClippingRegion(wx.Rect(rect.X, rect.Y + Y_OFFSET, rect.Width, rect.Height))
+        dc.SetClippingRegion(Rect(rect.X, rect.Y + Y_OFFSET, rect.Width, rect.Height))
         text_x = rect.X + INNER_PADDING
         text_y = rect.Y + INNER_PADDING + TEXT_OFFSET
         dc.DrawText(text, text_x, text_y)
diff -r a01f67e463eb -r 8f466597a40a source/timelinelib/wxgui/components/categorytree.py
--- a/source/timelinelib/wxgui/components/categorytree.py Thu Jul 10 11:02:13 2025 +0200
+++ b/source/timelinelib/wxgui/components/categorytree.py Fri Jul 11 10:17:31 2025 +0200
@@ -27,6 +27,7 @@
 from timelinelib.wxgui.dialogs.editcategory.view import EditCategoryDialog
 from timelinelib.canvas.data.immutable import InvalidOperationError
 import timelinelib.wxgui.utils as gui_utils
+from timelinelib.canvas.drawing.rect import Rect
 
 
 class CustomCategoryTree(wx.ScrolledWindow):
@@ -326,10 +327,10 @@
     def _render_checkbox(self, item):
         color = item.get("color", None)
         (w, h) = (17, 17)
-        bounding_rect = wx.Rect(item["x"] + self.model.INDENT_PX,
-                                item["y"] + (self.model.ITEM_HEIGHT_PX - h) // 2,
-                                w,
-                                h)
+        bounding_rect = Rect(item["x"] + self.model.INDENT_PX,
+                             item["y"] + (self.model.ITEM_HEIGHT_PX - h) // 2,
+                             w,
+                             h)
         if item["visible"]:
             flag = wx.CONTROL_CHECKED
         else:
@@ -543,4 +544,4 @@
 
     def _save_category_parent(self, category, parent):
         category.parent = parent
-        category.save()
\ No newline at end of file
+        category.save()
diff -r a01f67e463eb -r 8f466597a40a test/specs/wxgui/components/canvas/noop.py
--- a/test/specs/wxgui/components/canvas/noop.py Thu Jul 10 11:02:13 2025 +0200
+++ b/test/specs/wxgui/components/canvas/noop.py Fri Jul 11 10:17:31 2025 +0200
@@ -26,6 +26,7 @@
 from timelinelib.wxgui.components.maincanvas.noop import NoOpInputHandler
 from timelinelib.wxgui.cursor import Cursor
 from timelinelib.wxgui.keyboard import Keyboard
+from timelinelib.canvas.drawing.rect import Rect
 
 
 class NoOpInputHandlerSpec(UnitTestCase):
@@ -34,7 +35,7 @@
         event = an_event()
         time = human_time_to_gregorian("1 Jan 2011")
         self.given_time_at_x_is(10, time)
-        self.given_event_with_rect_at(Cursor(10, 10), event, wx.Rect(0, 0, 20, 20))
+        self.given_event_with_rect_at(Cursor(10, 10), event, Rect(0, 0, 20, 20))
         self.given_event_selected(event)
         self.canvas.HitResizeHandle.return_value = None
         self.canvas.HitMoveHandle.return_value = True
@@ -45,7 +46,7 @@
         event = an_event_with(ends_today=True)
         time = human_time_to_gregorian("1 Jan 2011")
         self.given_time_at_x_is(10, time)
-        self.given_event_with_rect_at(Cursor(10, 10), event, wx.Rect(0, 0, 20, 20))
+        self.given_event_with_rect_at(Cursor(10, 10), event, Rect(0, 0, 20, 20))
         self.given_event_selected(event)
         self.handler.left_mouse_down(Cursor(10, 10), Keyboard(False, False, False))
         self.assertEqual(0, self.canvas.SetInputHandler.call_count)
@@ -54,7 +55,7 @@
         event = an_event_with(ends_today=True)
         time = human_time_to_gregorian("1 Jan 2011")
         self.given_time_at_x_is(10, time)
-        self.given_event_with_rect_at(Cursor(10, 10), event, wx.Rect(0, 0, 20, 20))
+        self.given_event_with_rect_at(Cursor(10, 10), event, Rect(0, 0, 20, 20))
         self.given_event_selected(event)
         self.handler.mouse_moved(Cursor(10, 10), Keyboard(False, False, False))
         self.assertEqual(0, self.canvas._set_move_cursor.call_count)
diff -r a01f67e463eb -r 8f466597a40a test/specs/wxgui/components/canvas/timelinecanvascontroller.py
--- a/test/specs/wxgui/components/canvas/timelinecanvascontroller.py Thu Jul 10 11:02:13 2025 +0200
+++ b/test/specs/wxgui/components/canvas/timelinecanvascontroller.py Fri Jul 11 10:17:31 2025 +0200
@@ -29,6 +29,7 @@
 from timelinelib.test.utils import human_time_to_gregorian
 from timelinelib.wxgui.components.timelinepanelguicreator import InputHandlerState
 from timelinelib.config.dotfile import Config
+from timelinelib.canvas.drawing.rect import Rect
 
 
 ANY_Y = 0
@@ -76,7 +77,7 @@
         if description is not None:
             event.set_data("description", description)
         self.db.save_event(event)
-        self.mock_drawer.events_and_rects.append((event, wx.Rect(pos[0], pos[1], size[0], size[1])))
+        self.mock_drawer.events_and_rects.append((event, Rect(pos[0], pos[1], size[0], size[1])))
         return event
 
     def given_time_at_x_is(self, x, time):
diff -r a01f67e463eb -r 8f466597a40a test/unit/canvas/eventboxdrawers/defaulteventboxdrawer.py
--- a/test/unit/canvas/eventboxdrawers/defaulteventboxdrawer.py Thu Jul 10 11:02:13 2025 +0200
+++ b/test/unit/canvas/eventboxdrawers/defaulteventboxdrawer.py Fri Jul 11 10:17:31 2025 +0200
@@ -23,6 +23,7 @@
 from timelinelib.canvas.eventboxdrawers.defaulteventboxdrawer import DefaultEventBoxDrawer
 from timelinelib.canvas.eventboxdrawers.defaulteventboxdrawer import INNER_PADDING
 from timelinelib.canvas.eventboxdrawers.defaultmilestonedrawer import DefaultMilestoneDrawer
+from timelinelib.canvas.drawing.rect import Rect
 
 
 DEFAULT_TEXT = "test"
@@ -31,61 +32,61 @@
 class describe_default_exventbox_drawer_draw_text(UnitTestCase):
 
     def test_when_rect_has_zero_width_text_is_not_drawn(self):
-        rect = wx.Rect(0, 0, 0, 0)
+        rect = Rect(0, 0, 0, 0)
         self.drawer._draw_text(self.dc, rect, self.event)
         self.assertEqual(self.dc.DrawText.call_count, 0)
 
     def test_when_rect_has_inner_padding_width_text_is_not_drawn(self):
-        rect = wx.Rect(0, 0, INNER_PADDING * 2, 0)
+        rect = Rect(0, 0, INNER_PADDING * 2, 0)
         self.drawer._draw_text(self.dc, rect, self.event)
         self.assertEqual(self.dc.DrawText.call_count, 0)
 
     def test_non_centered_text_is_left_aligned(self):
         WIDTH = 100
         HEIGHT = 20
-        rect = wx.Rect(0, 0, WIDTH, HEIGHT)
+        rect = Rect(0, 0, WIDTH, HEIGHT)
         self.drawer.center_text = False
         self.drawer._draw_text(self.dc, rect, self.event)
-        self.dc.SetClippingRegion.assert_called_with(wx.Rect(INNER_PADDING, INNER_PADDING, WIDTH - 2 * INNER_PADDING, HEIGHT - 2 * INNER_PADDING))
+        self.dc.SetClippingRegion.assert_called_with(Rect(INNER_PADDING, INNER_PADDING, WIDTH - 2 * INNER_PADDING, HEIGHT - 2 * INNER_PADDING))
         self.dc.DrawText.assert_called_with(DEFAULT_TEXT, INNER_PADDING, INNER_PADDING)
 
     def test_centered_text_is_not_left_aligned(self):
         WIDTH = 100
         HEIGHT = 20
-        rect = wx.Rect(0, 0, WIDTH, HEIGHT)
+        rect = Rect(0, 0, WIDTH, HEIGHT)
         self.drawer.center_text = True
         self.drawer._draw_text(self.dc, rect, self.event)
-        self.dc.SetClippingRegion.assert_called_with(wx.Rect(INNER_PADDING, INNER_PADDING, WIDTH - 2 * INNER_PADDING, HEIGHT - 2 * INNER_PADDING))
+        self.dc.SetClippingRegion.assert_called_with(Rect(INNER_PADDING, INNER_PADDING, WIDTH - 2 * INNER_PADDING, HEIGHT - 2 * INNER_PADDING))
         self.dc.DrawText.assert_called_with(DEFAULT_TEXT, (WIDTH - 2 * INNER_PADDING - 50) // 2 + INNER_PADDING, INNER_PADDING)
 
     def test_centered_text_is_left_aligned_if_text_is_too_long_to_fit(self):
         WIDTH = 100
         HEIGHT = 20
-        rect = wx.Rect(0, 0, WIDTH, HEIGHT)
+        rect = Rect(0, 0, WIDTH, HEIGHT)
         self.dc.GetTextExtent.return_value = (500, 0)
         self.drawer.center_text = True
         self.drawer._draw_text(self.dc, rect, self.event)
-        self.dc.SetClippingRegion.assert_called_with(wx.Rect(INNER_PADDING, INNER_PADDING, WIDTH - 2 * INNER_PADDING, HEIGHT - 2 * INNER_PADDING))
+        self.dc.SetClippingRegion.assert_called_with(Rect(INNER_PADDING, INNER_PADDING, WIDTH - 2 * INNER_PADDING, HEIGHT - 2 * INNER_PADDING))
         self.dc.DrawText.assert_called_with(DEFAULT_TEXT, INNER_PADDING, INNER_PADDING)
 
     def test_non_centered_text_is_left_ajusted_when_fuzzy(self):
         WIDTH = 100
         HEIGHT = 20
         self.event.has_edge_icons.return_value = True
-        rect = wx.Rect(0, 0, WIDTH, HEIGHT)
+        rect = Rect(0, 0, WIDTH, HEIGHT)
         self.drawer.center_text = False
         self.drawer._draw_text(self.dc, rect, self.event)
-        self.dc.SetClippingRegion.assert_called_with(wx.Rect(INNER_PADDING, INNER_PADDING, WIDTH - 2 * INNER_PADDING, HEIGHT - 2 * INNER_PADDING))
+        self.dc.SetClippingRegion.assert_called_with(Rect(INNER_PADDING, INNER_PADDING, WIDTH - 2 * INNER_PADDING, HEIGHT - 2 * INNER_PADDING))
         self.dc.DrawText.assert_called_with(DEFAULT_TEXT, INNER_PADDING + HEIGHT // 2, INNER_PADDING)
 
     def test_non_centered_text_is_left_ajusted_when_locked(self):
         WIDTH = 100
         HEIGHT = 20
         self.event.has_edge_icons.return_value = True
-        rect = wx.Rect(0, 0, WIDTH, HEIGHT)
+        rect = Rect(0, 0, WIDTH, HEIGHT)
         self.drawer.center_text = False
         self.drawer._draw_text(self.dc, rect, self.event)
-        self.dc.SetClippingRegion.assert_called_with(wx.Rect(INNER_PADDING, INNER_PADDING, WIDTH - 2 * INNER_PADDING, HEIGHT - 2 * INNER_PADDING))
+        self.dc.SetClippingRegion.assert_called_with(Rect(INNER_PADDING, INNER_PADDING, WIDTH - 2 * INNER_PADDING, HEIGHT - 2 * INNER_PADDING))
         self.dc.DrawText.assert_called_with(DEFAULT_TEXT, INNER_PADDING + HEIGHT // 2, INNER_PADDING)
 
     def test_if_checkmark_is_to_be_used_it_is_placed_as_first_char_in_text(self):
@@ -93,11 +94,11 @@
         HEIGHT = 20
         self.event.has_edge_icons.return_value = True
         self.event.get_progress.return_value = 100
-        rect = wx.Rect(0, 0, WIDTH, HEIGHT)
+        rect = Rect(0, 0, WIDTH, HEIGHT)
         self.drawer.center_text = False
         self.drawer.view_properties.get_display_checkmark_on_events_done.return_value = True
         self.drawer._draw_text(self.dc, rect, self.event)
-        self.dc.SetClippingRegion.assert_called_with(wx.Rect(INNER_PADDING, INNER_PADDING, WIDTH - 2 * INNER_PADDING, HEIGHT - 2 * INNER_PADDING))
+        self.dc.SetClippingRegion.assert_called_with(Rect(INNER_PADDING, INNER_PADDING, WIDTH - 2 * INNER_PADDING, HEIGHT - 2 * INNER_PADDING))
         self.dc.DrawText.assert_called_with("\u2714" + DEFAULT_TEXT, INNER_PADDING + HEIGHT // 2, INNER_PADDING)
 
     def test_milestone_with_no_text_can_be_drawn(self):
@@ -105,7 +106,7 @@
             self.event.text = ""
             self.event.get_color.return_value = (127, 127, 127)
             self.dc.GetTextExtent.return_value = wx.Size(10, 10)
-            rect = wx.Rect(0, 0, 100, 20)
+            rect = Rect(0, 0, 100, 20)
             scene = Mock()
             try:
                 DefaultMilestoneDrawer(rect, self.event, False, self.drawer.view_properties).draw(self.dc)

2025-07-10 11:05 Rickard pushed to timeline

changeset:   7996:a01f67e463eb
user:        Rickard Lindberg <rickard@rickardlindberg.me>
date:        Thu Jul 10 11:02:13 2025 +0200
summary:     Show balloon for period events shown as points below the divider line.

diff -r 7f208da5f70c -r a01f67e463eb documentation/changelog.rst
--- a/documentation/changelog.rst Tue Jul 08 10:29:58 2025 +0200
+++ b/documentation/changelog.rst Thu Jul 10 11:02:13 2025 +0200
@@ -34,6 +34,10 @@
 * ``Corrupted icon file causes exception``
   Use a default icon when icon file is corrupted and disable error dialog.
 
+* Balloons are now shown for period events that are shown as points below the
+  divider line if preference "Never show period Events as point Events" is
+  checked.
+
 Windows installer:
 
 * The Windows installer is now built on Linux/Wine and only for 64 bit platforms.
diff -r 7f208da5f70c -r a01f67e463eb source/timelinelib/canvas/drawing/drawers/default.py
--- a/source/timelinelib/canvas/drawing/drawers/default.py Tue Jul 08 10:29:58 2025 +0200
+++ b/source/timelinelib/canvas/drawing/drawers/default.py Thu Jul 10 11:02:13 2025 +0200
@@ -544,6 +544,8 @@
     def _draw_ballon(self, event, event_rect, sticky):
         """Draw one ballon on a selected event that has 'description' data."""
         ballon_drawer = BallonDrawer(self.dc, self.scene, self.appearance, event)
+        if hasattr(event, 'symbol_rect'):
+            event_rect = event.symbol_rect
         self.balloon_data.append(ballon_drawer.draw(event_rect, sticky))
 
     def get_period_xpos(self, time_period):

2025-07-08 10:30 Rickard pushed to timeline

changeset:   7995:7f208da5f70c
user:        Rickard Lindberg <rickard@rickardlindberg.me>
date:        Tue Jul 08 10:29:58 2025 +0200
summary:     Ignore build artifacts.

diff -r 63ba50e3d1f4 -r 7f208da5f70c .hgignore
--- a/.hgignore Tue Jul 08 10:10:08 2025 +0200
+++ b/.hgignore Tue Jul 08 10:29:58 2025 +0200
@@ -1,5 +1,8 @@
 syntax: glob
 *.bak
+Dockerfile*.ci.files
+timeline-*-Win64Setup.exe
+timeline-*.zip
 
 mikado.png
 mikado.pdf