hg clone static-http://projects.rickardlindberg.me/scm/timeline
changeset: 8063:deead63fc19f
user: Rickard Lindberg <rickard@rickardlindberg.me>
date: Sun Aug 24 12:12:25 2025 +0200
summary: Ensure listening on initial appearance
diff -r c2fde191bf90 -r deead63fc19f source/timelinelib/canvas/timelinecanvascontroller.py
--- a/source/timelinelib/canvas/timelinecanvascontroller.py Sun Aug 24 12:06:13 2025 +0200
+++ b/source/timelinelib/canvas/timelinecanvascontroller.py Sun Aug 24 12:12:25 2025 +0200
@@ -48,6 +48,7 @@
"""
self._debug_enabled = debug_enabled
self._appearance = Appearance()
+ self._appearance.listen_for_any(self._redraw_timeline)
self._monitoring = Monitoring()
self._view = view
self._drawing_algorithm = self._set_drawing_algorithm(drawer)
changeset: 8064:c8aee09d8e20
tag: tip
user: Rickard Lindberg <rickard@rickardlindberg.me>
date: Sun Aug 24 12:22:38 2025 +0200
summary: Write changelog for text to the left of period events
diff -r deead63fc19f -r c8aee09d8e20 documentation/changelog.rst
--- a/documentation/changelog.rst Sun Aug 24 12:12:25 2025 +0200
+++ b/documentation/changelog.rst Sun Aug 24 12:22:38 2025 +0200
@@ -9,6 +9,16 @@
* Beta versions: |betas|_
+New features, enhancements:
+
+* ``Text to the left of period events``
+ This is a new option (available in Preferences/General) that makes the event
+ text of period events appear to the left of the event box. Implementing this
+ feature required extensive changes in the logic of how a timeline is drawn.
+ Some details were deliberately changed because I think it yielded a better
+ result. Some details might have accidentally changed. If you think the
+ appearance has changed for the worse, please let us know.
+
Fixed crash reports and bugs:
* ``Image.Scale(): argument 1 has unexpected type 'float'``
changeset: 8061:25841312ce26
parent: 8058:2d20dc22fc76
user: Rickard Lindberg <rickard@rickardlindberg.me>
date: Sun Aug 24 12:04:28 2025 +0200
summary: Add config for text to the left of period events
diff -r 2d20dc22fc76 -r 25841312ce26 source/timelinelib/canvas/appearance.py
--- a/source/timelinelib/canvas/appearance.py Sun Aug 24 11:22:39 2025 +0200
+++ b/source/timelinelib/canvas/appearance.py Sun Aug 24 12:04:28 2025 +0200
@@ -59,6 +59,7 @@
self._build_property("time_scale_pos", 1)
self._build_property("use_bold_nowline", False)
self._build_property("milestone_as_circle", False)
+ self._build_property("text_to_the_left_of_period_events", False)
def _build_property(self, name, initial_value):
diff -r 2d20dc22fc76 -r 25841312ce26 source/timelinelib/canvas/drawing/scene.py
--- a/source/timelinelib/canvas/drawing/scene.py Sun Aug 24 11:22:39 2025 +0200
+++ b/source/timelinelib/canvas/drawing/scene.py Sun Aug 24 12:04:28 2025 +0200
@@ -282,6 +282,10 @@
inflate = 0
box_rect = self._calc_ideal_wx_rect(rx, ry, rw, rh)
text_rect = box_rect.Clone()
+ if self._appearance.get_text_to_the_left_of_period_events() and not event.is_container() and not event.is_subevent():
+ w = self._calc_width_for_non_period_event(event)
+ text_rect.X -= w
+ text_rect.Width = w
return EventRects(
event=event,
box_rect=box_rect.CloneInflate(inflate, inflate),
diff -r 2d20dc22fc76 -r 25841312ce26 source/timelinelib/config/dotfile.py
--- a/source/timelinelib/config/dotfile.py Sun Aug 24 11:22:39 2025 +0200
+++ b/source/timelinelib/config/dotfile.py Sun Aug 24 12:04:28 2025 +0200
@@ -348,6 +348,7 @@
{'name': 'use_sidebar_filter_hint', 'default': 'True'},
{'name': 'real_time_redraw_interval_navigate', 'default': 'False'},
{'name': 'milestone_as_circle', 'default': 'False'},
+ {'name': 'text_to_the_left_of_period_events', 'default': 'False'},
)
INT_CONFIGS = (
{'name': 'sidebar_width', 'default': '200'},
diff -r 2d20dc22fc76 -r 25841312ce26 source/timelinelib/wxgui/components/timelinepanelguicreator.py
--- a/source/timelinelib/wxgui/components/timelinepanelguicreator.py Sun Aug 24 11:22:39 2025 +0200
+++ b/source/timelinelib/wxgui/components/timelinepanelguicreator.py Sun Aug 24 12:04:28 2025 +0200
@@ -159,6 +159,7 @@
appearance.set_time_scale_pos(self._config.time_scale_pos)
appearance.set_use_bold_nowline(self._config.use_bold_nowline)
appearance.set_milestone_as_circle(self._config.milestone_as_circle)
+ appearance.set_text_to_the_left_of_period_events(self._config.text_to_the_left_of_period_events)
self._config.listen_for_any(update_appearance)
update_appearance()
diff -r 2d20dc22fc76 -r 25841312ce26 source/timelinelib/wxgui/dialogs/preferences/controller.py
--- a/source/timelinelib/wxgui/dialogs/preferences/controller.py Sun Aug 24 11:22:39 2025 +0200
+++ b/source/timelinelib/wxgui/dialogs/preferences/controller.py Sun Aug 24 12:04:28 2025 +0200
@@ -193,6 +193,9 @@
def on_milestone_as_circle(self, evt):
self.config.milestone_as_circle = self.view.GetMilestoneAsCircle()
+ def on_text_to_the_left_of_period_events(self, evt):
+ self.config.text_to_the_left_of_period_events = self.view.GetTextToTheLeftOfPeriodEvents()
+
def _set_initial_values(self):
self.view.SetOpenRecentCheckboxValue(self.config.open_recent_at_startup)
self.view.SetInertialScrollingCheckboxValue(self.config.use_inertial_scrolling)
@@ -240,6 +243,7 @@
self.view.SetUseSidebarTextColoring(self.config.use_sidebar_text_coloring)
self.view.SetUseSidebarFilterHint(self.config.use_sidebar_filter_hint)
self.view.SetMilestoneAsCircle(self.config.milestone_as_circle)
+ self.view.SetTextToTheLeftOfPeriodEvents(self.config.text_to_the_left_of_period_events)
def _week_index(self, week):
for (i, w) in self.weeks_map:
diff -r 2d20dc22fc76 -r 25841312ce26 source/timelinelib/wxgui/dialogs/preferences/view.py
--- a/source/timelinelib/wxgui/dialogs/preferences/view.py Sun Aug 24 11:22:39 2025 +0200
+++ b/source/timelinelib/wxgui/dialogs/preferences/view.py Sun Aug 24 12:04:28 2025 +0200
@@ -87,6 +87,11 @@
event_EVT_CHECKBOX="on_milestone_as_circle"
label="$(milestone_as_circle)"
/>
+ <CheckBox
+ name="text_to_the_left_of_period_events"
+ event_EVT_CHECKBOX="on_text_to_the_left_of_period_events"
+ label="$(text_to_the_left_of_period_events)"
+ />
<Button
name="select_tab_order"
event_EVT_BUTTON="on_tab_order_click"
@@ -510,6 +515,7 @@
"use_sidebar_text_coloring_text": _("Use colored text in sidebar instead of colored boxes"),
"use_sidebar_filter_hint_text": _("Use hint in sidebar filter textbox instead of label"),
"milestone_as_circle": _("Show milestone as a circle"),
+ "text_to_the_left_of_period_events": _("Text to the left of period events"),
}, title=_("Preferences"))
self.controller.on_init(config, ExperimentalFeatures())
self.font_sizer.Layout()
@@ -629,6 +635,12 @@
def SetMilestoneAsCircle(self, value):
self.milestone_as_circle_checkbox.SetValue(value)
+ def GetTextToTheLeftOfPeriodEvents(self):
+ return self.text_to_the_left_of_period_events.GetValue()
+
+ def SetTextToTheLeftOfPeriodEvents(self, value):
+ self.text_to_the_left_of_period_events.SetValue(value)
+
def SetWorkdayLength(self, value):
self.workday_length_choices.Select(value - 1)
diff -r 2d20dc22fc76 -r 25841312ce26 translations/timeline.pot
--- a/translations/timeline.pot Sun Aug 24 11:22:39 2025 +0200
+++ b/translations/timeline.pot Sun Aug 24 12:04:28 2025 +0200
@@ -8,7 +8,7 @@
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-08-24 10:32+0200\n"
+"POT-Creation-Date: 2025-08-24 11:53+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -834,8 +834,8 @@
msgstr ""
#: source\timelinelib\config\shortcut.py:45
-#: source\timelinelib\wxgui\components\timelinepanelguicreator.py:258
-#: source\timelinelib\wxgui\dialogs\preferences\view.py:485
+#: source\timelinelib\wxgui\components\timelinepanelguicreator.py:259
+#: source\timelinelib\wxgui\dialogs\preferences\view.py:490
msgid "Edit"
msgstr ""
@@ -851,7 +851,7 @@
#: source\timelinelib\config\shortcut.py:48
#: source\timelinelib\config\shortcut.py:51
#: source\timelinelib\config\shortcut.py:52
-#: source\timelinelib\wxgui\dialogs\preferences\view.py:495
+#: source\timelinelib\wxgui\dialogs\preferences\view.py:500
msgid "Navigate"
msgstr ""
@@ -1326,7 +1326,7 @@
#: source\timelinelib\help\pages.py:181
#: source\timelinelib\plugin\plugins\exporters\timelineexporter.py:76
#: source\timelinelib\wxgui\dialogs\export\view.py:77
-#: source\timelinelib\wxgui\dialogs\preferences\view.py:477
+#: source\timelinelib\wxgui\dialogs\preferences\view.py:482
msgid "Events"
msgstr ""
@@ -1451,7 +1451,7 @@
msgstr ""
#: source\timelinelib\help\pages.py:276
-#: source\timelinelib\wxgui\dialogs\preferences\view.py:486
+#: source\timelinelib\wxgui\dialogs\preferences\view.py:491
msgid "Experimental Features"
msgstr ""
@@ -2181,7 +2181,7 @@
msgstr ""
#: source\timelinelib\wxgui\components\categorytree.py:221
-#: source\timelinelib\wxgui\components\timelinepanelguicreator.py:252
+#: source\timelinelib\wxgui\components\timelinepanelguicreator.py:253
msgid "Delete"
msgstr ""
@@ -2386,51 +2386,51 @@
msgstr ""
#: source\timelinelib\wxgui\components\timelinepanelguicreator.py:91
-#: source\timelinelib\wxgui\dialogs\preferences\view.py:503
+#: source\timelinelib\wxgui\dialogs\preferences\view.py:508
#: source\timelinelib\wxgui\frames\mainframe\menus\viewmenu.py:43
#: source\timelinelib\wxgui\frames\mainframe\toolbarcreator.py:46
#: source\timelinelib\wxgui\frames\mainframe\toolbarcreator.py:56
msgid "Center"
msgstr ""
-#: source\timelinelib\wxgui\components\timelinepanelguicreator.py:259
+#: source\timelinelib\wxgui\components\timelinepanelguicreator.py:260
msgid "Duplicate..."
msgstr ""
-#: source\timelinelib\wxgui\components\timelinepanelguicreator.py:261
-msgid "Measure Duration of Events..."
-msgstr ""
-
#: source\timelinelib\wxgui\components\timelinepanelguicreator.py:262
-msgid "Mark Event as Done"
+msgid "Measure Duration of Events..."
msgstr ""
#: source\timelinelib\wxgui\components\timelinepanelguicreator.py:263
-msgid "Select Category..."
+msgid "Mark Event as Done"
msgstr ""
#: source\timelinelib\wxgui\components\timelinepanelguicreator.py:264
+msgid "Select Category..."
+msgstr ""
+
+#: source\timelinelib\wxgui\components\timelinepanelguicreator.py:265
msgid "Select Container..."
msgstr ""
-#: source\timelinelib\wxgui\components\timelinepanelguicreator.py:267
+#: source\timelinelib\wxgui\components\timelinepanelguicreator.py:268
msgid "Sticky Balloon"
msgstr ""
-#: source\timelinelib\wxgui\components\timelinepanelguicreator.py:274
+#: source\timelinelib\wxgui\components\timelinepanelguicreator.py:275
msgid "Goto URL"
msgstr ""
-#: source\timelinelib\wxgui\components\timelinepanelguicreator.py:305
+#: source\timelinelib\wxgui\components\timelinepanelguicreator.py:306
#, python-format
msgid "Are you sure you want to delete %d events?"
msgstr ""
-#: source\timelinelib\wxgui\components\timelinepanelguicreator.py:308
+#: source\timelinelib\wxgui\components\timelinepanelguicreator.py:309
msgid "Are you sure you want to delete this event?"
msgstr ""
-#: source\timelinelib\wxgui\components\timelinepanelguicreator.py:356
+#: source\timelinelib\wxgui\components\timelinepanelguicreator.py:357
#, python-format
msgid "%s events hidden"
msgstr ""
@@ -3005,7 +3005,7 @@
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\controller.py:73
-#: source\timelinelib\wxgui\dialogs\preferences\controller.py:216
+#: source\timelinelib\wxgui\dialogs\preferences\controller.py:219
msgid "Current"
msgstr ""
@@ -3013,228 +3013,232 @@
msgid "Default year, mont and day must have a numeric value"
msgstr ""
-#: source\timelinelib\wxgui\dialogs\preferences\view.py:458
+#: source\timelinelib\wxgui\dialogs\preferences\view.py:463
msgid "General"
msgstr ""
-#: source\timelinelib\wxgui\dialogs\preferences\view.py:459
-msgid "Open most recent timeline on startup"
-msgstr ""
-
-#: source\timelinelib\wxgui\dialogs\preferences\view.py:460
-msgid "Use inertial scrolling"
-msgstr ""
-
-#: source\timelinelib\wxgui\dialogs\preferences\view.py:461
-msgid "Never show period Events as point Events"
-msgstr ""
-
-#: source\timelinelib\wxgui\dialogs\preferences\view.py:462
-msgid "Center Event texts"
-msgstr ""
-
-#: source\timelinelib\wxgui\dialogs\preferences\view.py:463
-msgid "Uncheck time checkbox for new events"
-msgstr ""
-
#: source\timelinelib\wxgui\dialogs\preferences\view.py:464
-msgid "Balloon text below icon"
+msgid "Open most recent timeline on startup"
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\view.py:465
-msgid "Filter items in listbox export, on categories"
+msgid "Use inertial scrolling"
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\view.py:466
-msgid "Select Event Editor Tab Order"
+msgid "Never show period Events as point Events"
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\view.py:467
-msgid "Select Date format"
+msgid "Center Event texts"
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\view.py:468
-msgid "Date && Time"
+msgid "Uncheck time checkbox for new events"
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\view.py:469
-msgid "Week start on:"
-msgstr ""
-
-#: source\timelinelib\wxgui\dialogs\preferences\view.py:470
-msgid "Monday"
+msgid "Balloon text below icon"
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\view.py:470
-msgid "Sunday"
+msgid "Filter items in listbox export, on categories"
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\view.py:471
-msgid "Workday Length in hours:"
+msgid "Select Event Editor Tab Order"
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\view.py:472
-msgid "Fonts"
+msgid "Select Date format"
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\view.py:473
-msgid "Colours"
+msgid "Date && Time"
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\view.py:474
-msgid "Major Strips:"
+msgid "Week start on:"
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\view.py:475
-msgid "Minor Strips:"
+msgid "Monday"
+msgstr ""
+
+#: source\timelinelib\wxgui\dialogs\preferences\view.py:475
+msgid "Sunday"
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\view.py:476
-msgid "Balloons:"
+msgid "Workday Length in hours:"
+msgstr ""
+
+#: source\timelinelib\wxgui\dialogs\preferences\view.py:477
+msgid "Fonts"
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\view.py:478
-msgid "Eras"
+msgid "Colours"
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\view.py:479
-msgid "Event Editor"
+msgid "Major Strips:"
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\view.py:480
-msgid "Icons"
+msgid "Minor Strips:"
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\view.py:481
-msgid "Fuzzy icon"
-msgstr ""
-
-#: source\timelinelib\wxgui\dialogs\preferences\view.py:482
-msgid "Locked icon"
+msgid "Balloons:"
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\view.py:483
-msgid "Hyperlink icon"
+msgid "Eras"
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\view.py:484
-msgid "Legends:"
+msgid "Event Editor"
+msgstr ""
+
+#: source\timelinelib\wxgui\dialogs\preferences\view.py:485
+msgid "Icons"
+msgstr ""
+
+#: source\timelinelib\wxgui\dialogs\preferences\view.py:486
+msgid "Fuzzy icon"
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\view.py:487
-msgid "Minor strip divider line:"
+msgid "Locked icon"
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\view.py:488
-msgid "Major strip divider line:"
+msgid "Hyperlink icon"
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\view.py:489
-msgid "Now line:"
-msgstr ""
-
-#: source\timelinelib\wxgui\dialogs\preferences\view.py:490
-msgid "Weekends:"
-msgstr ""
-
-#: source\timelinelib\wxgui\dialogs\preferences\view.py:491
-msgid "Use bold line"
+msgid "Legends:"
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\view.py:492
-msgid "Background"
+msgid "Minor strip divider line:"
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\view.py:493
-msgid "Vertical space between Events (px)"
+msgid "Major strip divider line:"
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\view.py:494
-msgid ""
-"Real time redraw interval (s). Real time redraw is disabled if value is 0"
+msgid "Now line:"
+msgstr ""
+
+#: source\timelinelib\wxgui\dialogs\preferences\view.py:495
+msgid "Weekends:"
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\view.py:496
-msgid "Colorize weekends"
+msgid "Use bold line"
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\view.py:497
-msgid "Skip s in decade text"
+msgid "Background"
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\view.py:498
-msgid "Display checkmark when events are done"
+msgid "Vertical space between Events (px)"
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\view.py:499
-msgid "Never use time precision for events"
-msgstr ""
-
-#: source\timelinelib\wxgui\dialogs\preferences\view.py:500
-msgid "Use second precision for time"
+msgid ""
+"Real time redraw interval (s). Real time redraw is disabled if value is 0"
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\view.py:501
-msgid "Legend Position"
+msgid "Colorize weekends"
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\view.py:502
-msgid "Bottom-Left"
-msgstr ""
-
-#: source\timelinelib\wxgui\dialogs\preferences\view.py:502
-msgid "Top-Left"
-msgstr ""
-
-#: source\timelinelib\wxgui\dialogs\preferences\view.py:502
-msgid "Top-Right"
-msgstr ""
-
-#: source\timelinelib\wxgui\dialogs\preferences\view.py:502
-msgid "Bottom-Right"
+msgid "Skip s in decade text"
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\view.py:503
-msgid "Top"
-msgstr ""
-
-#: source\timelinelib\wxgui\dialogs\preferences\view.py:503
-msgid "Bottom"
+msgid "Display checkmark when events are done"
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\view.py:504
-msgid "Time scale position"
+msgid "Never use time precision for events"
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\view.py:505
-msgid "Default Year"
+msgid "Use second precision for time"
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\view.py:506
-msgid "Default Month"
+msgid "Legend Position"
+msgstr ""
+
+#: source\timelinelib\wxgui\dialogs\preferences\view.py:507
+msgid "Bottom-Left"
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\view.py:507
-msgid "Default Day"
+msgid "Top-Left"
+msgstr ""
+
+#: source\timelinelib\wxgui\dialogs\preferences\view.py:507
+msgid "Top-Right"
+msgstr ""
+
+#: source\timelinelib\wxgui\dialogs\preferences\view.py:507
+msgid "Bottom-Right"
+msgstr ""
+
+#: source\timelinelib\wxgui\dialogs\preferences\view.py:508
+msgid "Top"
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\view.py:508
-msgid "Use date default values"
+msgid "Bottom"
+msgstr ""
+
+#: source\timelinelib\wxgui\dialogs\preferences\view.py:509
+msgid "Time scale position"
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\view.py:510
-msgid "Use colored text in sidebar instead of colored boxes"
+msgid "Default Year"
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\view.py:511
-msgid "Use hint in sidebar filter textbox instead of label"
+msgid "Default Month"
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\view.py:512
-msgid "Show milestone as a circle"
+msgid "Default Day"
msgstr ""
#: source\timelinelib\wxgui\dialogs\preferences\view.py:513
+msgid "Use date default values"
+msgstr ""
+
+#: source\timelinelib\wxgui\dialogs\preferences\view.py:515
+msgid "Use colored text in sidebar instead of colored boxes"
+msgstr ""
+
+#: source\timelinelib\wxgui\dialogs\preferences\view.py:516
+msgid "Use hint in sidebar filter textbox instead of label"
+msgstr ""
+
+#: source\timelinelib\wxgui\dialogs\preferences\view.py:517
+msgid "Show milestone as a circle"
+msgstr ""
+
+#: source\timelinelib\wxgui\dialogs\preferences\view.py:518
+msgid "Text to the left of period events"
+msgstr ""
+
+#: source\timelinelib\wxgui\dialogs\preferences\view.py:519
#: source\timelinelib\wxgui\frames\mainframe\menus\editmenu.py:34
msgid "Preferences"
msgstr ""
changeset: 8062:c2fde191bf90
parent: 8061:25841312ce26
parent: 8060:3f04f6bd113d
user: Rickard Lindberg <rickard@rickardlindberg.me>
date: Sun Aug 24 12:06:13 2025 +0200
summary: Merge
diff -r 25841312ce26 -r c2fde191bf90 documentation/changelog.rst
--- a/documentation/changelog.rst Sun Aug 24 12:04:28 2025 +0200
+++ b/documentation/changelog.rst Sun Aug 24 12:06:13 2025 +0200
@@ -11,7 +11,12 @@
Fixed crash reports and bugs:
-* ``TypeError: DC.DrawCircle(): arguments did not match any overloaded call: overload 1: argument 1 has unexpected type 'float' overload 2: argument 1 has unexpected type 'float'``
+* ``Image.Scale(): argument 1 has unexpected type 'float'``
+ This makes it impossible to load icons in events.
+ The error appears after the upgrade to a newer version of wxPython.
+ The API now expects integers instead of flotas
+
+* ``TypeError: DC.DrawCircle(): arguments did not match any overloaded call overload 1: argument 1 has unexpected type 'float' overload 2: argument 1 has unexpected type 'float'``
This was a problem with the feature "Milestones can be drawn as circles"
introduced in version 2.11.0. The ``DC.DrawCircle`` API expects integers, but
we didn't ensure that.
diff -r 25841312ce26 -r c2fde191bf90 source/timelinelib/wxgui/components/propertyeditors/iconeditor.py
--- a/source/timelinelib/wxgui/components/propertyeditors/iconeditor.py Sun Aug 24 12:04:28 2025 +0200
+++ b/source/timelinelib/wxgui/components/propertyeditors/iconeditor.py Sun Aug 24 12:06:13 2025 +0200
@@ -43,7 +43,7 @@
factor = float(H) / float(h)
w = w * factor
h = h * factor
- image = image.Scale(w, h, wx.IMAGE_QUALITY_HIGH)
+ image = image.Scale(int(w), int(h), wx.IMAGE_QUALITY_HIGH)
return image.ConvertToBitmap()
except:
pass
changeset: 8059:5f4b15207f8d
parent: 8019:8d6eb11fd6ba
user: roger <roger@rolidata.se>
date: Sun Aug 24 11:40:11 2025 +0200
summary: Fixed problem caused by change of argument types in wxPython API Image.Scale()
diff -r 8d6eb11fd6ba -r 5f4b15207f8d documentation/changelog.rst
--- a/documentation/changelog.rst Sun Aug 10 07:59:56 2025 +0200
+++ b/documentation/changelog.rst Sun Aug 24 11:40:11 2025 +0200
@@ -11,7 +11,12 @@
Fixed crash reports and bugs:
-* ``TypeError: DC.DrawCircle(): arguments did not match any overloaded call: overload 1: argument 1 has unexpected type 'float' overload 2: argument 1 has unexpected type 'float'``
+* ``Image.Scale(): argument 1 has unexpected type 'float'``
+ This makes it impossible to load icons in events.
+ The error appears after the upgrade to a newer version of wxPython.
+ The API now expects integers instead of flotas
+
+* ``TypeError: DC.DrawCircle(): arguments did not match any overloaded call overload 1: argument 1 has unexpected type 'float' overload 2: argument 1 has unexpected type 'float'``
This was a problem with the feature "Milestones can be drawn as circles"
introduced in version 2.11.0. The ``DC.DrawCircle`` API expects integers, but
we didn't ensure that.
diff -r 8d6eb11fd6ba -r 5f4b15207f8d source/timelinelib/wxgui/components/propertyeditors/iconeditor.py
--- a/source/timelinelib/wxgui/components/propertyeditors/iconeditor.py Sun Aug 10 07:59:56 2025 +0200
+++ b/source/timelinelib/wxgui/components/propertyeditors/iconeditor.py Sun Aug 24 11:40:11 2025 +0200
@@ -43,7 +43,7 @@
factor = float(H) / float(h)
w = w * factor
h = h * factor
- image = image.Scale(w, h, wx.IMAGE_QUALITY_HIGH)
+ image = image.Scale(int(w), int(h), wx.IMAGE_QUALITY_HIGH)
return image.ConvertToBitmap()
except:
pass
changeset: 8060:3f04f6bd113d
parent: 8059:5f4b15207f8d
parent: 8058:2d20dc22fc76
user: roger <roger@rolidata.se>
date: Sun Aug 24 11:45:23 2025 +0200
summary: Fixed problem caused by change of argument types in wxPython API Image.Scale()
diff -r 5f4b15207f8d -r 3f04f6bd113d source/timelinelib/canvas/drawing/drawers/default.py
--- a/source/timelinelib/canvas/drawing/drawers/default.py Sun Aug 24 11:40:11 2025 +0200
+++ b/source/timelinelib/canvas/drawing/drawers/default.py Sun Aug 24 11:45:23 2025 +0200
@@ -25,7 +25,6 @@
from timelinelib.canvas.drawing.drawers.nowline import NowLine
from timelinelib.canvas.drawing.interface import Drawer
from timelinelib.canvas.drawing.scene import TimelineScene
-from timelinelib.features.experimental.experimentalfeatures import EXTENDED_CONTAINER_HEIGHT
from timelinelib.features.experimental.experimentalfeatures import EXTENDED_CONTAINER_STRATEGY
from timelinelib.utils import unique_based_on_eq
from timelinelib.wxgui.components.font import Font
@@ -119,16 +118,16 @@
"above_stretch": 0,
"below_stretch": 0,
}
- for (evt, rect) in self.scene.event_data:
- if rect.Y < self.scene.divider_y:
+ for event_rects in self.scene.event_data:
+ if event_rects.box_rect.Y < self.scene.divider_y:
measurements["above_stretch"] = max(
measurements["above_stretch"],
- self.scene.divider_y - rect.Y
+ self.scene.divider_y - event_rects.box_rect.Y
)
else:
measurements["below_stretch"] = max(
measurements["below_stretch"],
- rect.Bottom - self.scene.divider_y
+ event_rects.box_rect.Bottom - self.scene.divider_y
)
measurements["above_stretch"] += self.scene.get_baseline_padding() * 2
measurements["below_stretch"] += self.scene.get_baseline_padding() * 2
@@ -153,8 +152,6 @@
self.colorize_weekends = appearance.get_colorize_weekends()
self.outer_padding = OUTER_PADDING
self.outer_padding = appearance.get_vertical_space_between_events()
- if EXTENDED_CONTAINER_HEIGHT.enabled():
- self.outer_padding += EXTENDED_CONTAINER_HEIGHT.get_extra_outer_padding_to_avoid_vertical_overlapping()
self.appearance = appearance
self.dc = dc
self.time_type = timeline.get_time_type()
@@ -231,12 +228,11 @@
return self.snap(start), self.snap(end)
def event_at(self, x, y, alt_down=False):
- for (event, rect) in reversed(self.scene.event_data):
- if event.is_container():
- rect = self._adjust_container_rect_for_hittest(rect)
+ for event_rects in reversed(self.scene.event_data):
+ rect = event_rects.box_rect
if rect.Contains(wx.Point(x, y)):
- self.set_horizontal_mouse_position_factor(event, x)
- return event
+ self.set_horizontal_mouse_position_factor(event_rects.event, x)
+ return event_rects.event
return None
def set_horizontal_mouse_position_factor(self, event, x):
@@ -249,32 +245,26 @@
def get_events_in_rect(self, rect):
return [
- event
- for (event, rect)
+ event_rects.event
+ for event_rects
in self.scene.event_data
- if rect.Intersects(rect)
+ if event_rects.box_rect.Intersects(rect)
]
- def _adjust_container_rect_for_hittest(self, rect):
- if EXTENDED_CONTAINER_HEIGHT.enabled():
- return EXTENDED_CONTAINER_HEIGHT.get_vertical_larger_box_rect(rect)
- else:
- return rect
-
def event_with_rect_at(self, x, y, view_properties=None):
- for (event, rect) in self.scene.event_data:
- if view_properties.is_selected(event):
- if rect.Contains(wx.Point(x, y)):
- if event.is_container():
- return event, rect
+ for event_rects in self.scene.event_data:
+ if view_properties.is_selected(event_rects.event):
+ if event_rects.box_rect.Contains(wx.Point(x, y)):
+ if event_rects.event.is_container():
+ return event_rects.event, event_rects.box_rect
else:
- return event, rect
+ return event_rects.event, event_rects.box_rect
return None
def event_rect(self, evt):
- for (event, rect) in self.scene.event_data:
- if evt == event:
- return rect
+ for event_rects in self.scene.event_data:
+ if evt == event_rects.event:
+ return event_rects.box_rect
return None
def balloon_at(self, x, y):
@@ -385,13 +375,13 @@
DividerLine(self).draw()
def _draw_lines_to_non_period_events(self, view_properties):
- for (event, rect) in self.scene.event_data:
- if event.is_milestone():
+ for event_rects in self.scene.event_data:
+ if event_rects.event.is_milestone():
continue
- if not event.is_period():
- self._draw_line(view_properties, event, rect)
- elif not self.scene.never_show_period_events_as_point_events() and self._event_displayed_as_point_event(rect):
- self._draw_line(view_properties, event, rect)
+ if not event_rects.event.is_period():
+ self._draw_line(view_properties, event_rects.event, event_rects.box_rect)
+ elif not self.scene.never_show_period_events_as_point_events() and self._event_displayed_as_point_event(event_rects.box_rect):
+ self._draw_line(view_properties, event_rects.event, event_rects.box_rect)
def _event_displayed_as_point_event(self, rect):
return self.scene.divider_y > rect.Y
@@ -433,10 +423,10 @@
return y
def _get_container_y(self, subevent):
- for (event, rect) in self.scene.event_data:
- if event.is_container():
- if event is subevent.container:
- return rect.y - 1
+ for event_rects in self.scene.event_data:
+ if event_rects.event.is_container():
+ if event_rects.event is subevent.container:
+ return event_rects.box_rect.y - 1
return self.scene.divider_y
def _set_line_color(self, view_properties, event):
@@ -452,9 +442,9 @@
def _extract_categories(self):
return sort_categories(unique_based_on_eq(
- event.category
- for (event, _) in self.scene.event_data
- if event.category
+ event_rects.event.category
+ for event_rects in self.scene.event_data
+ if event_rects.event.category
))
def _draw_legend(self, view_properties, categories):
@@ -468,46 +458,37 @@
collection = []
amount = view_properties.hscroll_amount
if amount != 0:
- for (event, rect) in self.scene.event_data:
- if rect.Y < self.scene.divider_y:
- self._scroll_point_events(amount, event, rect, collection)
+ for event_rects in self.scene.event_data:
+ if event_rects.box_rect.Y < self.scene.divider_y:
+ self._scroll_point_events(amount, event_rects, collection)
else:
- self._scroll_period_events(amount, event, rect, collection)
+ self._scroll_period_events(amount, event_rects, collection)
self.scene.event_data = collection
- def _scroll_point_events(self, amount, event, rect, collection):
- rect.Y += amount
- if rect.Y < self.scene.divider_y - rect.height:
- collection.append((event, rect))
+ def _scroll_point_events(self, amount, event_rects, collection):
+ event_rects.move_y(amount)
+ if event_rects.box_rect.Y < self.scene.divider_y - event_rects.box_rect.height:
+ collection.append(event_rects)
- def _scroll_period_events(self, amount, event, rect, collection):
- rect.Y -= amount
- if rect.Y > self.scene.divider_y + rect.height:
- collection.append((event, rect))
+ def _scroll_period_events(self, amount, event_rects, collection):
+ event_rects.move_y(-amount)
+ if event_rects.box_rect.Y > self.scene.divider_y + event_rects.box_rect.height:
+ collection.append(event_rects)
def _draw_events(self, view_properties):
"""Draw all event boxes and the text inside them."""
self._scroll_events_vertically(view_properties)
self.dc.DestroyClippingRegion()
self._draw_lines_to_non_period_events(view_properties)
- for (event, rect) in self.scene.event_data:
+ for event_rects in self.scene.event_data:
self.dc.SetFont(self._event_text_font)
- if event.is_container():
- self._draw_container(event, rect, view_properties)
- else:
- self._draw_box(rect, event, view_properties)
+ self._draw_box(event_rects, view_properties)
- def _draw_container(self, event, rect, view_properties):
- 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)
-
- def _draw_box(self, rect, event, view_properties):
- self.dc.SetClippingRegion(rect)
- self._event_box_drawer.draw(self.dc, self.scene, rect, event, view_properties)
- if EXTENDED_CONTAINER_STRATEGY.enabled() and event.is_subevent() and not event.is_period():
- self._draw_time_marker_for_point_event(rect)
+ def _draw_box(self, event_rects, view_properties):
+ self.dc.SetClippingRegion(event_rects.bounding_rect)
+ self._event_box_drawer.draw(self.dc, self.scene, event_rects, view_properties)
+ if EXTENDED_CONTAINER_STRATEGY.enabled() and event_rects.event.is_subevent() and not event_rects.event.is_period():
+ self._draw_time_marker_for_point_event(event_rects.box_rect)
self.dc.DestroyClippingRegion()
def _draw_time_marker_for_point_event(self, rect):
@@ -524,13 +505,13 @@
top_event = None
top_rect = None
self.dc.SetTextForeground(BLACK)
- for (event, rect) in self.scene.event_data:
- if event.get_data("description") is not None or event.get_data("icon") is not None:
- sticky = view_properties.event_has_sticky_balloon(event)
- if view_properties.event_is_hovered(event) or sticky:
+ for event_rects in self.scene.event_data:
+ if event_rects.event.get_data("description") is not None or event_rects.event.get_data("icon") is not None:
+ sticky = view_properties.event_has_sticky_balloon(event_rects.event)
+ if view_properties.event_is_hovered(event_rects.event) or sticky:
if not sticky:
- top_event, top_rect = event, rect
- self._draw_ballon(event, rect, sticky)
+ top_event, top_rect = event_rects.event, event_rects.box_rect
+ self._draw_ballon(event_rects.event, event_rects.box_rect, sticky)
# Make the unsticky balloon appear on top
if top_event is not None:
self._draw_ballon(top_event, top_rect, False)
diff -r 5f4b15207f8d -r 3f04f6bd113d source/timelinelib/canvas/drawing/scene.py
--- a/source/timelinelib/canvas/drawing/scene.py Sun Aug 24 11:40:11 2025 +0200
+++ b/source/timelinelib/canvas/drawing/scene.py Sun Aug 24 11:45:23 2025 +0200
@@ -18,11 +18,12 @@
import wx
+from timelinelib.canvas.data import TimePeriod
+from timelinelib.canvas.drawing.rect import FloatingRect
+from timelinelib.canvas.drawing.rect import Rect
from timelinelib.canvas.drawing.utils import Metrics
-from timelinelib.canvas.data import TimePeriod
+from timelinelib.features.experimental.experimentalfeatures import EXTENDED_CONTAINER_HEIGHT
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
@@ -36,6 +37,11 @@
self._view_properties = view_properties
self._get_text_size_fn = get_text_size_fn
self._appearance = appearance
+ self._container_padding = 2
+ if EXTENDED_CONTAINER_HEIGHT.enabled():
+ self._container_title_height = self._get_text_height("")
+ else:
+ self._container_title_height = 0
self._outer_padding = 5
self._inner_padding = 3
self._baseline_padding = 15
@@ -111,7 +117,6 @@
return self._metrics.calc_width(time_period)
def get_closest_overlapping_event(self, selected_event, up=True):
- self._inflate_event_rects_to_get_right_dimensions_for_overlap_calculations()
rect = self._get_event_rect(selected_event)
# self._get_event_rect() returns None if the selected event isn't visible.
# (The selected event can be scrolled out of view). In this case the period
@@ -128,14 +133,10 @@
evt = self._get_overlapping_event(period, direction, selected_event, rect)
return evt, direction
- def _inflate_event_rects_to_get_right_dimensions_for_overlap_calculations(self):
- for (_, rect) in self.event_data:
- rect.Inflate(self._outer_padding, self._outer_padding)
-
def _get_event_rect(self, event):
- for (evt, rect) in self.event_data:
- if evt == event:
- return rect
+ for event_rects in self.event_data:
+ if event_rects.event == event:
+ return event_rects.box_rect
def _event_rect_drawn_as_period(self, event_rect):
return event_rect.Y >= self.divider_y
@@ -173,24 +174,24 @@
@staticmethod
def _get_next_overlapping_event(event_data, selected_event):
selected_event_found = False
- for (e, _) in event_data:
- if not selected_event.is_subevent() and e.is_subevent():
+ for event_rects in event_data:
+ if not selected_event.is_subevent() and event_rects.event.is_subevent():
continue
if selected_event_found:
- return e
+ return event_rects.event
else:
- if e == selected_event:
+ if event_rects.event == selected_event:
selected_event_found = True
@staticmethod
def _get_prev_overlapping_event(event_data, selected_event):
prev_event = None
- for (e, _) in event_data:
- if not selected_event.is_subevent() and e.is_subevent():
+ for event_rects in event_data:
+ if not selected_event.is_subevent() and event_rects.event.is_subevent():
continue
- if e == selected_event:
+ if event_rects.event == selected_event:
return prev_event
- prev_event = e
+ prev_event = event_rects.event
def _calc_event_sizes_and_positions(self):
self.events_from_db = self._db.get_events(self._view_properties.displayed_period)
@@ -218,21 +219,16 @@
def _calc_event_rects(self, events):
self.event_data = self._calc_non_overlapping_event_rects(events)
- self._deflate_rects(self.event_data)
return self.event_data
def _calc_non_overlapping_event_rects(self, events):
self.event_data = []
for event in events:
- rect = self._create_ideal_rect_for_event(event)
- self._prevent_overlapping_by_adjusting_rect_y(event, rect)
- self.event_data.append((event, rect))
+ event_rects = self._create_ideal_rect_for_event(event)
+ self._prevent_overlapping_by_adjusting_rect_y(event_rects)
+ self.event_data.append(event_rects)
return self.event_data
- def _deflate_rects(self, event_data):
- for (_, rect) in event_data:
- rect.Deflate(self._outer_padding, self._outer_padding)
-
def _create_ideal_rect_for_event(self, event):
self._reset_ends_today_when_start_date_is_in_future(event)
if event.ends_today:
@@ -241,21 +237,28 @@
return self._calc_ideal_rect_for_period_event(event)
else:
if self._is_subevent_in_extended_container_strategy(event):
- rect = self._calc_ideal_rect_for_period_event(event)
+ event_rects = self._calc_ideal_rect_for_period_event(event)
rw = self._calc_width_for_non_period_event(event)
- rect.SetWidth(rw)
+ event_rects.box_rect.SetWidth(rw)
+ event_rects.text_rect.SetWidth(rw)
if not event.is_period():
- self._extend_container_width_for_point_event(event, rect)
- return rect
+ self._extend_container_width_for_point_event(event_rects)
+ return event_rects
else:
- return self._calc_ideal_rect_for_non_period_event(event)
+ rect = self._calc_ideal_rect_for_non_period_event(event)
+ return EventRects(
+ event=event,
+ box_rect=rect,
+ text_rect=rect.Clone(),
+ height_padding=self._outer_padding,
+ )
- def _extend_container_width_for_point_event(self, subevent, subevent_rect):
+ def _extend_container_width_for_point_event(self, event_rects):
"""Make point events be enclosed by the container rectangle."""
- event, container_rect = self._get_container_data_for_subevent(subevent)
- right_x_pos_diff = subevent_rect.GetRight() - container_rect.GetRight()
+ container_event_rects = self._get_container_data_for_subevent(event_rects.event)
+ right_x_pos_diff = event_rects.bounding_rect.GetRight() - container_event_rects.box_rect.GetRight()
if right_x_pos_diff > 0:
- container_rect.SetRight(container_rect.GetRight() + right_x_pos_diff)
+ container_event_rects.extend_width(right_x_pos_diff)
def _reset_ends_today_when_start_date_is_in_future(self, event):
if event.ends_today and self._start_date_is_in_future(event):
@@ -272,17 +275,29 @@
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)
- return self._calc_ideal_wx_rect(rx, ry, rw, rh)
+ if event.is_container():
+ rh += self._container_title_height
+ inflate = self._container_padding
+ else:
+ inflate = 0
+ box_rect = self._calc_ideal_wx_rect(rx, ry, rw, rh)
+ text_rect = box_rect.Clone()
+ return EventRects(
+ event=event,
+ box_rect=box_rect.CloneInflate(inflate, inflate),
+ text_rect=text_rect.CloneInflate(inflate, inflate),
+ height_padding=self._outer_padding,
+ )
def _calc_width_for_period_event(self, event):
min_w = 5 * self._outer_padding
- return max(self._metrics.calc_width(event.get_time_period()) + 2 * self._outer_padding, min_w)
+ return max(self._metrics.calc_width(event.get_time_period()), min_w)
def _calc_height_for_period_event(self, event):
- return self._get_text_height(event.get_text()) + 2 * self._inner_padding + 2 * self._outer_padding
+ return self._get_text_height(event.get_text()) + 2 * self._inner_padding
def _calc_x_pos_for_period_event(self, event):
- return self._metrics.calc_x(event.get_time_period().start_time) - self._outer_padding
+ return self._metrics.calc_x(event.get_time_period().start_time)
def _calc_y_pos_for_period_event(self, event):
if self._is_subevent_in_extended_container_strategy(event):
@@ -291,22 +306,22 @@
if event.is_period():
return self._get_container_ry(event)
else:
- return self._metrics.half_height - self._baseline_padding
+ return self._metrics.half_height - self._baseline_padding - self._outer_padding
else:
- return self._metrics.half_height + self._baseline_padding
+ return self._metrics.half_height + self._baseline_padding + self._outer_padding
def _get_container_ry(self, subevent):
- for (event, rect) in self.event_data:
- if event == subevent.container:
- return rect.y
- return self._metrics.half_height + self._baseline_padding
+ for event_rect in self.event_data:
+ if event_rect.event == subevent.container:
+ return event_rect.box_rect.y
+ return self._metrics.half_height + self._baseline_padding + self._outer_padding
def _calc_ideal_rect_for_non_period_event(self, event):
if self.never_show_period_events_as_point_events() and event.is_period():
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)
+ return FloatingRect(x - 5, y1 - 5, 10, 10)
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)
@@ -320,7 +335,7 @@
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
+ rw = tw + 2 * self._inner_padding
if event.has_data():
rw += self._data_indicator_size // 3
if event.get_fuzzy() or event.get_locked():
@@ -328,11 +343,11 @@
return rw
def _calc_height_for_non_period_event(self, event):
- return self._get_text_height(event.get_text()) + 2 * self._inner_padding + 2 * self._outer_padding
+ return self._get_text_height(event.get_text()) + 2 * self._inner_padding
def _calc_x_pos_for_non_period_event(self, event, rw):
if self._appearance.get_draw_period_events_to_right():
- return self._metrics.calc_x(event.get_time_period().start_time) - self._outer_padding
+ return self._metrics.calc_x(event.get_time_period().start_time)
else:
return self._metrics.calc_x(event.mean_time()) - rw // 2
@@ -340,7 +355,7 @@
if event.is_milestone():
return self._metrics.half_height - rh // 2
else:
- return self._metrics.half_height - rh - self._baseline_padding
+ return self._metrics.half_height - rh - self._baseline_padding - self._outer_padding
def _get_text_size(self, text):
if len(text) > 0:
@@ -397,92 +412,125 @@
return time.is_weekend_day
def get_hidden_event_count(self):
- visible_events_count = len([rect for _, rect in self.event_data
- if rect.Y < self.height and (rect.Y + rect.Height) > 0])
+ visible_events_count = len([
+ event_rects
+ for event_rects
+ in self.event_data
+ if event_rects.box_rect.Y < self.height and (event_rects.box_rect.Y + event_rects.box_rect.Height) > 0
+ ])
return len(self.events_from_db) - visible_events_count
- def _prevent_overlapping_by_adjusting_rect_y(self, event, event_rect):
- if event_rect.is_allowed_to_overlap():
+ def _prevent_overlapping_by_adjusting_rect_y(self, event_rects):
+ if event_rects.box_rect.is_allowed_to_overlap():
return
- if self._is_subevent_in_extended_container_strategy(event):
- self._adjust_subevent_rect(event, event_rect)
- elif event.is_subevent() and self._display_as_period(event):
- self._adjust_subevent_rect(event, event_rect)
+ if self._is_subevent_in_extended_container_strategy(event_rects.event):
+ self._adjust_subevent_rect(event_rects)
+ elif event_rects.event.is_subevent() and self._display_as_period(event_rects.event):
+ self._adjust_subevent_rect(event_rects)
else:
- if self._display_as_period(event):
- self._adjust_period_rect(event_rect)
+ if self._display_as_period(event_rects.event):
+ self._adjust_period_rect(event_rects)
else:
- self._adjust_point_rect(event_rect)
+ self._adjust_point_rect(event_rects)
- def _adjust_period_rect(self, event_rect):
- rect = self._get_overlapping_period_rect_with_largest_y(event_rect)
+ def _adjust_period_rect(self, event_rects):
+ rect = self._get_overlapping_period_rect_with_largest_y(event_rects.bounding_rect)
if rect is not None:
- event_rect.Y = rect.Y + rect.height
+ event_rects.set_bounding_y(rect.Y + rect.height)
- def _adjust_subevent_rect(self, subevent, event_rect):
- rect = self._get_overlapping_subevent_rect_with_largest_y(subevent, event_rect)
+ def _adjust_subevent_rect(self, event_rects):
+ container_event_rects = self._get_container_data_for_subevent(event_rects.event)
+ event_rects.set_bounding_y(
+ container_event_rects.box_rect.Y
+ +
+ self._container_padding
+ +
+ self._container_title_height
+ -
+ event_rects.height_padding
+ )
+ rect = self._get_overlapping_subevent_rect_with_largest_y(event_rects)
if rect is not None:
- event_rect.Y = rect.Y + rect.height
- self._adjust_container_rect_height(subevent, event_rect)
+ event_rects.set_bounding_y(rect.Y + rect.height)
+ subevent_box_bottom = event_rects.box_rect.Bottom
+ container_box_bottom = container_event_rects.box_rect.Bottom
+ diff = subevent_box_bottom - container_box_bottom + self._container_padding
+ if diff > 0:
+ container_event_rects.extend_box_height(diff)
- def _adjust_container_rect_height(self, subevent, event_rect):
- container_evt, container_rect = self._get_container_data_for_subevent(subevent)
- _, th = self._get_text_size(container_evt.get_text())
- rh = th + 2 * (self._inner_padding + self._outer_padding)
- h = event_rect.Y - container_rect.Y + rh
- if container_rect.height < h:
- container_rect.Height = h
-
- def _get_overlapping_subevent_rect_with_largest_y(self, subevent, event_rect):
- event_data = self._get_list_with_overlapping_subevents(subevent, event_rect)
+ def _get_overlapping_subevent_rect_with_largest_y(self, event_rects):
+ event_data = self._get_list_with_overlapping_subevents(event_rects)
rect_with_largest_y = None
- for (_, rect) in event_data:
- if rect_with_largest_y is None or rect.Y > rect_with_largest_y.Y:
- rect_with_largest_y = rect
+ for event_rects in event_data:
+ if rect_with_largest_y is None or event_rects.bounding_rect.Y > rect_with_largest_y.Y:
+ rect_with_largest_y = event_rects.bounding_rect
return rect_with_largest_y
def _get_overlapping_period_rect_with_largest_y(self, event_rect):
event_data = self._get_list_with_overlapping_period_events(event_rect)
rect_with_largest_yh = None
- for (_, rect) in event_data:
- if rect_with_largest_yh is None or rect.Y + rect.Height > rect_with_largest_yh.Y + rect_with_largest_yh.Height:
- rect_with_largest_yh = rect
+ for event_rects in event_data:
+ if rect_with_largest_yh is None or event_rects.bounding_rect.Y + event_rects.bounding_rect.Height > rect_with_largest_yh.Y + rect_with_largest_yh.Height:
+ rect_with_largest_yh = event_rects.bounding_rect
return rect_with_largest_yh
def _get_list_with_overlapping_period_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)]
+ return [
+ event_rects
+ for event_rects
+ in self.event_data
+ if (
+ self._rects_overlap(event_rect, event_rects.bounding_rect)
+ and
+ event_rects.bounding_rect.Y >= self.divider_y
+ )
+ ]
- def _get_list_with_overlapping_subevents(self, subevent, event_rect):
- ls = [(event, rect) for (event, rect) in self.event_data
- if (event.is_subevent() and
- event.container is subevent.container and
- self._rects_overlap(event_rect, rect) and
- rect.Y >= self.divider_y)]
- return ls
+ def _get_list_with_overlapping_subevents(self, event_rects):
+ return [
+ inner_event_rects
+ for inner_event_rects
+ in self.event_data
+ if (
+ inner_event_rects.event.is_subevent()
+ and
+ inner_event_rects.event.container is event_rects.event.container
+ and
+ self._rects_overlap(event_rects.bounding_rect, inner_event_rects.bounding_rect)
+ and
+ inner_event_rects.bounding_rect.Y >= self.divider_y
+ )
+ ]
- def _adjust_point_rect(self, event_rect):
- rect = self._get_overlapping_point_rect_with_smallest_y(event_rect)
+ def _adjust_point_rect(self, event_rects):
+ rect = self._get_overlapping_point_rect_with_smallest_y(event_rects.bounding_rect)
if rect is not None:
- event_rect.Y = rect.Y - event_rect.height
+ event_rects.set_bounding_y(rect.Y - event_rects.bounding_rect.height)
def _get_overlapping_point_rect_with_smallest_y(self, event_rect):
event_data = self._get_list_with_overlapping_point_events(event_rect)
rect_with_smallest_y = None
- for (_, rect) in event_data:
- if rect_with_smallest_y is None or rect.Y < rect_with_smallest_y.Y:
- rect_with_smallest_y = rect
+ for event_rects in event_data:
+ if rect_with_smallest_y is None or event_rects.bounding_rect.Y < rect_with_smallest_y.Y:
+ rect_with_smallest_y = event_rects.bounding_rect
return rect_with_smallest_y
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) and
- not rect.is_allowed_to_overlap()]
+ return [
+ event_rects
+ for event_rects
+ in self.event_data
+ if (
+ self._rects_overlap(event_rect, event_rects.bounding_rect)
+ and
+ event_rects.bounding_rect.Y < self.divider_y
+ and
+ not event_rects.box_rect.is_allowed_to_overlap()
+ )
+ ]
def _rects_overlap(self, rect1, rect2):
- REMOVE_X_PADDING = 2 + self._outer_padding * 2
+ REMOVE_X_PADDING = 2
return (rect2.x + REMOVE_X_PADDING <= rect1.x + rect1.width and
rect1.x + REMOVE_X_PADDING <= rect2.x + rect2.width)
@@ -490,4 +538,79 @@
return EXTENDED_CONTAINER_STRATEGY.enabled() and event.is_subevent()
def _get_container_data_for_subevent(self, subevent):
- return [(event, rect) for (event, rect) in self.event_data if event is subevent.container][0]
+ return [
+ event_rects
+ for event_rects
+ in self.event_data
+ if event_rects.event is subevent.container
+ ][0]
+
+
+class EventRects:
+
+ def __init__(self, event, box_rect, text_rect, height_padding):
+ self.event = event
+ self.box_rect = box_rect
+ self.text_rect = text_rect
+ self.height_padding = height_padding
+
+ def replace_box_rect(self, box_rect):
+ return EventRects(
+ event=self.event,
+ box_rect=box_rect,
+ text_rect=self.text_rect,
+ height_padding=self.height_padding,
+ )
+
+ @property
+ def bounding_rect(self):
+ """
+ >>> box_rect = Rect(0, 0, 10, 10)
+ >>> text_rect = Rect(10, 10, 10, 10)
+ >>> event_rects = EventRects(event=None, box_rect=box_rect, text_rect=text_rect, height_padding=5)
+ >>> event_rects.bounding_rect
+ wx.Rect(0, -5, 20, 30)
+ """
+ min_x = min([self.box_rect.X, self.text_rect.X])
+ min_y = min([self.box_rect.Y, self.text_rect.Y])
+ max_right = max([self.box_rect.Right, self.text_rect.Right])
+ max_bottom = max([self.box_rect.Bottom, self.text_rect.Bottom])
+ return Rect(
+ x=min_x,
+ y=min_y,
+ width=max_right-min_x+1,
+ height=max_bottom-min_y+1,
+ ).CloneInflate(0, self.height_padding)
+
+ def set_bounding_y(self, y):
+ """
+ >>> box_rect = Rect(0, 0, 10, 5)
+ >>> text_rect = Rect(0, 0, 10, 5)
+ >>> event_rects = EventRects(event=None, box_rect=box_rect, text_rect=text_rect, height_padding=5)
+
+ >>> event_rects.box_rect
+ wx.Rect(0, 0, 10, 5)
+ >>> event_rects.bounding_rect
+ wx.Rect(0, -5, 10, 15)
+
+ >>> event_rects.set_bounding_y(5)
+ >>> event_rects.box_rect
+ wx.Rect(0, 10, 10, 5)
+ >>> event_rects.bounding_rect
+ wx.Rect(0, 5, 10, 15)
+ """
+ self.move_y(y - self.bounding_rect.Y)
+
+ def extend_box_height(self, height):
+ self.box_rect.Height += height
+
+ def extend_width(self, widht):
+ self.box_rect.Width += widht
+ self.text_rect.Width += widht
+
+ def move_y(self, amount):
+ self.box_rect.Y += amount
+ self.text_rect.Y += amount
+
+ def text_outside_box(self):
+ return self.text_rect.X < self.box_rect.X
diff -r 5f4b15207f8d -r 3f04f6bd113d source/timelinelib/canvas/eventboxdrawers/defaulteventboxdrawer.py
--- a/source/timelinelib/canvas/eventboxdrawers/defaulteventboxdrawer.py Sun Aug 24 11:40:11 2025 +0200
+++ b/source/timelinelib/canvas/eventboxdrawers/defaulteventboxdrawer.py Sun Aug 24 11:45:23 2025 +0200
@@ -22,7 +22,6 @@
from timelinelib.canvas.drawing.utils import black_solid_pen, black_solid_brush, get_colour, darken_color
from timelinelib.config.paths import EVENT_ICONS_DIR
-from timelinelib.features.experimental.experimentalfeatures import EXTENDED_CONTAINER_HEIGHT
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
@@ -39,7 +38,9 @@
class DefaultEventBoxDrawer:
- def draw(self, dc, scene, rect, event, view_properties):
+ def draw(self, dc, scene, event_rects, view_properties):
+ rect = event_rects.box_rect
+ event = event_rects.event
self.scene = scene
self.view_properties = view_properties
selected = view_properties.is_selected(event)
@@ -49,7 +50,7 @@
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)
+ self._draw_event_box(dc, event_rects, selected)
def _draw_period_event_as_symbol_below_divider_line(self, dc, rect, scene, event):
dc.DestroyClippingRegion()
@@ -61,12 +62,14 @@
dc.DrawLine(x, y0, x, y1)
dc.DrawCircle(x, y1, 2)
- def _draw_event_box(self, dc, rect, event, selected):
+ def _draw_event_box(self, dc, event_rects, selected):
+ event = event_rects.event
+ rect = event_rects.box_rect
self._draw_background(dc, rect, event)
self._draw_fuzzy_edges(dc, rect, event)
self._draw_locked_edges(dc, rect, event)
self._draw_progress_box(dc, rect, event)
- self._draw_text(dc, rect, event)
+ self._draw_text(dc, event_rects)
self._draw_contents_indicator(dc, event, rect)
self._draw_locked_edges(dc, rect, event)
self._draw_selection_handles(dc, event, rect, selected)
@@ -183,20 +186,17 @@
dc.SetPen(wx.TRANSPARENT_PEN)
dc.DrawPolygon(points)
- def _draw_text(self, dc, rect, event):
+ def _draw_text(self, dc, event_rects):
# Ensure that we can't draw content outside inner rectangle
- if self._there_is_room_for_the_text(rect):
- self._draw_the_text(dc, rect, event)
+ if self._there_is_room_for_the_text(event_rects.text_rect):
+ self._draw_the_text(dc, event_rects)
def _there_is_room_for_the_text(self, rect):
return deflate_rect(rect).Width > 0
- def _draw_the_text(self, dc, rect, event):
- self._set_text_foreground_color(dc, event)
- if event.is_container() and EXTENDED_CONTAINER_HEIGHT.enabled():
- EXTENDED_CONTAINER_HEIGHT.draw_container_text_top_adjusted(event.get_text(), dc, rect)
- else:
- self._draw_normal_text(dc, rect, event)
+ def _draw_the_text(self, dc, event_rects):
+ self._set_text_foreground_color(dc, event_rects)
+ self._draw_normal_text(dc, event_rects.text_rect, event_rects.event)
dc.DestroyClippingRegion()
def _draw_normal_text(self, dc, rect, event):
@@ -236,11 +236,15 @@
width, _ = dc.GetTextExtent(self._get_text(event))
return max(text_x, text_x + (inner_rect.width - width) // 2)
- def _set_text_foreground_color(self, dc, event):
- try:
- dc.SetTextForeground(get_colour(event.get_category().font_color))
- except AttributeError:
- dc.SetTextForeground(wx.BLACK)
+ def _set_text_foreground_color(self, dc, event_rects):
+ if event_rects.text_outside_box():
+ color = wx.BLACK
+ else:
+ try:
+ color = get_colour(event_rects.event.get_category().font_color)
+ except AttributeError:
+ color = wx.BLACK
+ dc.SetTextForeground(color)
def _draw_handles(self, dc, event, rect):
diff -r 5f4b15207f8d -r 3f04f6bd113d source/timelinelib/features/experimental/experimentalfeaturecontainersize.py
--- a/source/timelinelib/features/experimental/experimentalfeaturecontainersize.py Sun Aug 24 11:40:11 2025 +0200
+++ b/source/timelinelib/features/experimental/experimentalfeaturecontainersize.py Sun Aug 24 11:45:23 2025 +0200
@@ -16,46 +16,17 @@
# along with Timeline. If not, see <http://www.gnu.org/licenses/>.
-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"
DISPLAY_NAME = _("Extend Container height")
DESCRIPTION = _("""
Extend the height of a container so that the container name becomes visible.
-
- This also has the side effect that ordinary events come farther apart in
- the vertical direction.
-
- The font for the container name has a fixed size when you zoom vertically (Alt + Mouse wheel)
""")
-Y_OFFSET = -16
-PADDING = 12
-OUTER_PAADING = 4
-TEXT_OFFSET = -2
-INNER_PADDING = 3
-FONT_SIZE = 8
class ExperimentalFeatureContainerSize(ExperimentalFeature):
def __init__(self):
ExperimentalFeature.__init__(self, DISPLAY_NAME, DESCRIPTION, CONFIG_NAME)
-
- def get_extra_outer_padding_to_avoid_vertical_overlapping(self):
- return OUTER_PAADING
-
- def get_vertical_larger_box_rect(self, rect):
- 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(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)
- dc.SetFont(old_font)
diff -r 5f4b15207f8d -r 3f04f6bd113d test/unit/canvas/drawing/scene.py
--- a/test/unit/canvas/drawing/scene.py Sun Aug 24 11:40:11 2025 +0200
+++ b/test/unit/canvas/drawing/scene.py Sun Aug 24 11:45:23 2025 +0200
@@ -55,39 +55,39 @@
self.given_visible_event_at("5 Jan 2010")
self.given_visible_event_at("5 Jan 2010")
self.when_scene_is_created()
- self.assertTrue(self.scene.event_data[0][1].Y >
- self.scene.event_data[1][1].Y)
+ self.assertTrue(self.scene.event_data[0].bounding_rect.Y >
+ self.scene.event_data[1].bounding_rect.Y)
def test_point_events_on_different_dates_has_same_y_positions(self):
self.given_displayed_period("1 Jan 2010", "10 Jan 2010")
self.given_visible_event_at("2 Jan 2010")
self.given_visible_event_at("9 Jan 2010")
self.when_scene_is_created()
- self.assertEqual(self.scene.event_data[0][1].Y,
- self.scene.event_data[1][1].Y)
+ self.assertEqual(self.scene.event_data[0].bounding_rect.Y,
+ self.scene.event_data[1].bounding_rect.Y)
def test_period_events_with_same_period_has_different_y_positions(self):
self.given_displayed_period("1 Jan 2010", "12 Jan 2010")
self.given_visible_event_at("2 Jan 2010", "10 Jan 2010")
self.given_visible_event_at("2 Jan 2010", "10 Jan 2010")
self.when_scene_is_created()
- self.assertTrue(self.scene.event_data[0][1].Y <
- self.scene.event_data[1][1].Y)
+ self.assertTrue(self.scene.event_data[0].bounding_rect.Y <
+ self.scene.event_data[1].bounding_rect.Y)
def test_period_events_with_different_periods_has_same_y_positions(self):
self.given_displayed_period("1 Jan 2010", "12 Jan 2010")
self.given_visible_event_at("2 Jan 2010", "3 Jan 2010")
self.given_visible_event_at("8 Jan 2010", "10 Jan 2010")
self.when_scene_is_created()
- self.assertEqual(self.scene.event_data[0][1].Y,
- self.scene.event_data[1][1].Y)
+ self.assertEqual(self.scene.event_data[0].bounding_rect.Y,
+ self.scene.event_data[1].bounding_rect.Y)
def test_long_periods_are_not_drawn_very_far_outside_screen(self):
self.given_displayed_period("1 Jan 50 12:00", "1 Jan 50 13:00")
self.given_visible_event_at("1 Jan 0", "1 Jan 1000")
self.when_scene_is_created()
- event_x = self.scene.event_data[0][1].X
- event_width = self.scene.event_data[0][1].Width
+ event_x = self.scene.event_data[0].bounding_rect.X
+ event_width = self.scene.event_data[0].bounding_rect.Width
event_right_x = event_x + event_width
window_width = self.size[0]
self.assertTrue(event_x > -self.MAX_OUTSIDE_SCREEN)
@@ -108,7 +108,7 @@
self.given_visible_event_at("1 Jan 3017", "1 Feb 3017", ends_today=True)
self.assertTrue(self.db.get_first_event().ends_today)
self.when_scene_is_created()
- self.assertFalse(self.scene.event_data[0][0].ends_today)
+ self.assertFalse(self.scene.event_data[0].event.ends_today)
def setUp(self):
WxAppTestCase.setUp(self)
diff -r 5f4b15207f8d -r 3f04f6bd113d test/unit/canvas/eventboxdrawers/defaulteventboxdrawer.py
--- a/test/unit/canvas/eventboxdrawers/defaulteventboxdrawer.py Sun Aug 24 11:40:11 2025 +0200
+++ b/test/unit/canvas/eventboxdrawers/defaulteventboxdrawer.py Sun Aug 24 11:45:23 2025 +0200
@@ -19,11 +19,12 @@
import wx
from unittest.mock import Mock
-from timelinelib.test.cases.unit import UnitTestCase
+from timelinelib.canvas.drawing.rect import Rect
+from timelinelib.canvas.drawing.scene import EventRects
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
+from timelinelib.test.cases.unit import UnitTestCase
DEFAULT_TEXT = "test"
@@ -33,12 +34,12 @@
def test_when_rect_has_zero_width_text_is_not_drawn(self):
rect = Rect(0, 0, 0, 0)
- self.drawer._draw_text(self.dc, rect, self.event)
+ self.drawer._draw_text(self.dc, event_rects(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 = Rect(0, 0, INNER_PADDING * 2, 0)
- self.drawer._draw_text(self.dc, rect, self.event)
+ self.drawer._draw_text(self.dc, event_rects(rect, self.event))
self.assertEqual(self.dc.DrawText.call_count, 0)
def test_non_centered_text_is_left_aligned(self):
@@ -46,7 +47,7 @@
HEIGHT = 20
rect = Rect(0, 0, WIDTH, HEIGHT)
self.drawer.center_text = False
- self.drawer._draw_text(self.dc, rect, self.event)
+ self.drawer._draw_text(self.dc, event_rects(rect, self.event))
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)
@@ -55,7 +56,7 @@
HEIGHT = 20
rect = Rect(0, 0, WIDTH, HEIGHT)
self.drawer.center_text = True
- self.drawer._draw_text(self.dc, rect, self.event)
+ self.drawer._draw_text(self.dc, event_rects(rect, self.event))
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)
@@ -65,7 +66,7 @@
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.drawer._draw_text(self.dc, event_rects(rect, self.event))
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)
@@ -75,7 +76,7 @@
self.event.has_edge_icons.return_value = True
rect = Rect(0, 0, WIDTH, HEIGHT)
self.drawer.center_text = False
- self.drawer._draw_text(self.dc, rect, self.event)
+ self.drawer._draw_text(self.dc, event_rects(rect, self.event))
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)
@@ -85,7 +86,7 @@
self.event.has_edge_icons.return_value = True
rect = Rect(0, 0, WIDTH, HEIGHT)
self.drawer.center_text = False
- self.drawer._draw_text(self.dc, rect, self.event)
+ self.drawer._draw_text(self.dc, event_rects(rect, self.event))
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)
@@ -97,7 +98,7 @@
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.drawer._draw_text(self.dc, event_rects(rect, self.event))
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)
@@ -128,3 +129,12 @@
self.cat = Mock()
self.cat.font_color = (0, 0, 0)
self.event.get_category.return_value = self.cat
+
+
+def event_rects(rect, event):
+ return EventRects(
+ event=event,
+ box_rect=rect,
+ text_rect=rect.Clone(),
+ height_padding=0,
+ )
diff -r 5f4b15207f8d -r 3f04f6bd113d tools/generate-pot-file.py
--- a/tools/generate-pot-file.py Sun Aug 24 11:40:11 2025 +0200
+++ b/tools/generate-pot-file.py Sun Aug 24 11:45:23 2025 +0200
@@ -29,7 +29,17 @@
def generate_pot_file():
- subprocess.check_call(build_xgettext_command(parse_arguments()), cwd=ROOT_DIR)
+ arguments = parse_arguments()
+ subprocess.check_call(build_xgettext_command(arguments), cwd=ROOT_DIR)
+ lines = []
+ with open(arguments.outfile) as f:
+ for line in f:
+ if line.startswith("#: source"):
+ lines.append(line.replace("/", "\\"))
+ else:
+ lines.append(line)
+ with open(arguments.outfile, "w") as f:
+ f.write("".join(lines))
def parse_arguments():
diff -r 5f4b15207f8d -r 3f04f6bd113d translations/timeline.pot
--- a/translations/timeline.pot Sun Aug 24 11:40:11 2025 +0200
+++ b/translations/timeline.pot Sun Aug 24 11:45:23 2025 +0200
@@ -8,7 +8,7 @@
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-07-08 07:50+0000\n"
+"POT-Creation-Date: 2025-08-24 10:32+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -1109,22 +1109,15 @@
msgid "Unable to delete backup dbfile '%s'."
msgstr ""
-#: source\timelinelib\features\experimental\experimentalfeaturecontainersize.py:25
+#: source\timelinelib\features\experimental\experimentalfeaturecontainersize.py:23
msgid "Extend Container height"
msgstr ""
-#: source\timelinelib\features\experimental\experimentalfeaturecontainersize.py:26
+#: source\timelinelib\features\experimental\experimentalfeaturecontainersize.py:24
msgid ""
"\n"
" Extend the height of a container so that the container name "
"becomes visible.\n"
-"\n"
-" This also has the side effect that ordinary events come "
-"farther apart in\n"
-" the vertical direction.\n"
-"\n"
-" The font for the container name has a fixed size when you zoom "
-"vertically (Alt + Mouse wheel)\n"
" "
msgstr ""
@@ -2175,74 +2168,74 @@
msgstr ""
#: source\timelinelib\wxgui\components\categorychoice.py:89
-#: source\timelinelib\wxgui\components\categorytree.py:502
+#: source\timelinelib\wxgui\components\categorytree.py:503
msgid "Add Category"
msgstr ""
-#: source\timelinelib\wxgui\components\categorytree.py:212
+#: source\timelinelib\wxgui\components\categorytree.py:213
msgid "Edit..."
msgstr ""
-#: source\timelinelib\wxgui\components\categorytree.py:216
+#: source\timelinelib\wxgui\components\categorytree.py:217
msgid "Add..."
msgstr ""
-#: source\timelinelib\wxgui\components\categorytree.py:220
+#: source\timelinelib\wxgui\components\categorytree.py:221
#: source\timelinelib\wxgui\components\timelinepanelguicreator.py:252
msgid "Delete"
msgstr ""
-#: source\timelinelib\wxgui\components\categorytree.py:225
+#: source\timelinelib\wxgui\components\categorytree.py:226
msgid "Check All"
msgstr ""
-#: source\timelinelib\wxgui\components\categorytree.py:229
+#: source\timelinelib\wxgui\components\categorytree.py:230
msgid "Check children"
msgstr ""
-#: source\timelinelib\wxgui\components\categorytree.py:233
+#: source\timelinelib\wxgui\components\categorytree.py:234
msgid "Check all children"
msgstr ""
-#: source\timelinelib\wxgui\components\categorytree.py:237
+#: source\timelinelib\wxgui\components\categorytree.py:238
msgid "Check all parents"
msgstr ""
-#: source\timelinelib\wxgui\components\categorytree.py:241
+#: source\timelinelib\wxgui\components\categorytree.py:242
msgid "Check parents for checked children"
msgstr ""
-#: source\timelinelib\wxgui\components\categorytree.py:246
+#: source\timelinelib\wxgui\components\categorytree.py:247
msgid "Uncheck All"
msgstr ""
-#: source\timelinelib\wxgui\components\categorytree.py:250
+#: source\timelinelib\wxgui\components\categorytree.py:251
msgid "Uncheck children"
msgstr ""
-#: source\timelinelib\wxgui\components\categorytree.py:254
+#: source\timelinelib\wxgui\components\categorytree.py:255
msgid "Uncheck all children"
msgstr ""
-#: source\timelinelib\wxgui\components\categorytree.py:258
+#: source\timelinelib\wxgui\components\categorytree.py:259
msgid "Uncheck all parents"
msgstr ""
-#: source\timelinelib\wxgui\components\categorytree.py:496
+#: source\timelinelib\wxgui\components\categorytree.py:497
msgid "Edit Category"
msgstr ""
-#: source\timelinelib\wxgui\components\categorytree.py:508
+#: source\timelinelib\wxgui\components\categorytree.py:509
#, python-format
msgid "Are you sure you want to delete category '%s'?"
msgstr ""
-#: source\timelinelib\wxgui\components\categorytree.py:511
+#: source\timelinelib\wxgui\components\categorytree.py:512
#, python-format
msgid "Events belonging to '%s' will no longer belong to a category."
msgstr ""
-#: source\timelinelib\wxgui\components\categorytree.py:514
+#: source\timelinelib\wxgui\components\categorytree.py:515
#, python-format
msgid "Events belonging to '%s' will now belong to '%s'."
msgstr ""
changeset: 8058:2d20dc22fc76
user: Rickard Lindberg <rickard@rickardlindberg.me>
date: Sun Aug 24 11:22:39 2025 +0200
summary: Support for drawing text outside event box
diff -r 6c38d6c35d6e -r 2d20dc22fc76 source/timelinelib/canvas/drawing/scene.py
--- a/source/timelinelib/canvas/drawing/scene.py Sun Aug 24 10:47:57 2025 +0200
+++ b/source/timelinelib/canvas/drawing/scene.py Sun Aug 24 11:22:39 2025 +0200
@@ -280,11 +280,12 @@
inflate = self._container_padding
else:
inflate = 0
- rect = self._calc_ideal_wx_rect(rx, ry, rw, rh)
+ box_rect = self._calc_ideal_wx_rect(rx, ry, rw, rh)
+ text_rect = box_rect.Clone()
return EventRects(
event=event,
- box_rect=rect.CloneInflate(inflate, inflate),
- text_rect=rect.CloneInflate(inflate, inflate),
+ box_rect=box_rect.CloneInflate(inflate, inflate),
+ text_rect=text_rect.CloneInflate(inflate, inflate),
height_padding=self._outer_padding,
)
@@ -610,3 +611,6 @@
def move_y(self, amount):
self.box_rect.Y += amount
self.text_rect.Y += amount
+
+ def text_outside_box(self):
+ return self.text_rect.X < self.box_rect.X
diff -r 6c38d6c35d6e -r 2d20dc22fc76 source/timelinelib/canvas/eventboxdrawers/defaulteventboxdrawer.py
--- a/source/timelinelib/canvas/eventboxdrawers/defaulteventboxdrawer.py Sun Aug 24 10:47:57 2025 +0200
+++ b/source/timelinelib/canvas/eventboxdrawers/defaulteventboxdrawer.py Sun Aug 24 11:22:39 2025 +0200
@@ -65,12 +65,11 @@
def _draw_event_box(self, dc, event_rects, selected):
event = event_rects.event
rect = event_rects.box_rect
- text_rect = event_rects.text_rect
self._draw_background(dc, rect, event)
self._draw_fuzzy_edges(dc, rect, event)
self._draw_locked_edges(dc, rect, event)
self._draw_progress_box(dc, rect, event)
- self._draw_text(dc, text_rect, event)
+ self._draw_text(dc, event_rects)
self._draw_contents_indicator(dc, event, rect)
self._draw_locked_edges(dc, rect, event)
self._draw_selection_handles(dc, event, rect, selected)
@@ -187,17 +186,17 @@
dc.SetPen(wx.TRANSPARENT_PEN)
dc.DrawPolygon(points)
- def _draw_text(self, dc, rect, event):
+ def _draw_text(self, dc, event_rects):
# Ensure that we can't draw content outside inner rectangle
- if self._there_is_room_for_the_text(rect):
- self._draw_the_text(dc, rect, event)
+ if self._there_is_room_for_the_text(event_rects.text_rect):
+ self._draw_the_text(dc, event_rects)
def _there_is_room_for_the_text(self, rect):
return deflate_rect(rect).Width > 0
- def _draw_the_text(self, dc, rect, event):
- self._set_text_foreground_color(dc, event)
- self._draw_normal_text(dc, rect, event)
+ def _draw_the_text(self, dc, event_rects):
+ self._set_text_foreground_color(dc, event_rects)
+ self._draw_normal_text(dc, event_rects.text_rect, event_rects.event)
dc.DestroyClippingRegion()
def _draw_normal_text(self, dc, rect, event):
@@ -237,11 +236,15 @@
width, _ = dc.GetTextExtent(self._get_text(event))
return max(text_x, text_x + (inner_rect.width - width) // 2)
- def _set_text_foreground_color(self, dc, event):
- try:
- dc.SetTextForeground(get_colour(event.get_category().font_color))
- except AttributeError:
- dc.SetTextForeground(wx.BLACK)
+ def _set_text_foreground_color(self, dc, event_rects):
+ if event_rects.text_outside_box():
+ color = wx.BLACK
+ else:
+ try:
+ color = get_colour(event_rects.event.get_category().font_color)
+ except AttributeError:
+ color = wx.BLACK
+ dc.SetTextForeground(color)
def _draw_handles(self, dc, event, rect):
diff -r 6c38d6c35d6e -r 2d20dc22fc76 test/unit/canvas/eventboxdrawers/defaulteventboxdrawer.py
--- a/test/unit/canvas/eventboxdrawers/defaulteventboxdrawer.py Sun Aug 24 10:47:57 2025 +0200
+++ b/test/unit/canvas/eventboxdrawers/defaulteventboxdrawer.py Sun Aug 24 11:22:39 2025 +0200
@@ -19,11 +19,12 @@
import wx
from unittest.mock import Mock
-from timelinelib.test.cases.unit import UnitTestCase
+from timelinelib.canvas.drawing.rect import Rect
+from timelinelib.canvas.drawing.scene import EventRects
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
+from timelinelib.test.cases.unit import UnitTestCase
DEFAULT_TEXT = "test"
@@ -33,12 +34,12 @@
def test_when_rect_has_zero_width_text_is_not_drawn(self):
rect = Rect(0, 0, 0, 0)
- self.drawer._draw_text(self.dc, rect, self.event)
+ self.drawer._draw_text(self.dc, event_rects(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 = Rect(0, 0, INNER_PADDING * 2, 0)
- self.drawer._draw_text(self.dc, rect, self.event)
+ self.drawer._draw_text(self.dc, event_rects(rect, self.event))
self.assertEqual(self.dc.DrawText.call_count, 0)
def test_non_centered_text_is_left_aligned(self):
@@ -46,7 +47,7 @@
HEIGHT = 20
rect = Rect(0, 0, WIDTH, HEIGHT)
self.drawer.center_text = False
- self.drawer._draw_text(self.dc, rect, self.event)
+ self.drawer._draw_text(self.dc, event_rects(rect, self.event))
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)
@@ -55,7 +56,7 @@
HEIGHT = 20
rect = Rect(0, 0, WIDTH, HEIGHT)
self.drawer.center_text = True
- self.drawer._draw_text(self.dc, rect, self.event)
+ self.drawer._draw_text(self.dc, event_rects(rect, self.event))
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)
@@ -65,7 +66,7 @@
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.drawer._draw_text(self.dc, event_rects(rect, self.event))
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)
@@ -75,7 +76,7 @@
self.event.has_edge_icons.return_value = True
rect = Rect(0, 0, WIDTH, HEIGHT)
self.drawer.center_text = False
- self.drawer._draw_text(self.dc, rect, self.event)
+ self.drawer._draw_text(self.dc, event_rects(rect, self.event))
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)
@@ -85,7 +86,7 @@
self.event.has_edge_icons.return_value = True
rect = Rect(0, 0, WIDTH, HEIGHT)
self.drawer.center_text = False
- self.drawer._draw_text(self.dc, rect, self.event)
+ self.drawer._draw_text(self.dc, event_rects(rect, self.event))
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)
@@ -97,7 +98,7 @@
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.drawer._draw_text(self.dc, event_rects(rect, self.event))
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)
@@ -128,3 +129,12 @@
self.cat = Mock()
self.cat.font_color = (0, 0, 0)
self.event.get_category.return_value = self.cat
+
+
+def event_rects(rect, event):
+ return EventRects(
+ event=event,
+ box_rect=rect,
+ text_rect=rect.Clone(),
+ height_padding=0,
+ )
changeset: 8051:f691895e6a36
user: Rickard Lindberg <rickard@rickardlindberg.me>
date: Sun Aug 24 08:24:37 2025 +0200
summary: Move creation of EventRects further down the call stack
diff -r 18cc2c9d5ee2 -r f691895e6a36 source/timelinelib/canvas/drawing/scene.py
--- a/source/timelinelib/canvas/drawing/scene.py Sun Aug 24 07:59:31 2025 +0200
+++ b/source/timelinelib/canvas/drawing/scene.py Sun Aug 24 08:24:37 2025 +0200
@@ -218,13 +218,7 @@
def _calc_non_overlapping_event_rects(self, events):
self.event_data = []
for event in events:
- rect = self._create_ideal_rect_for_event(event)
- event_rects = EventRects(
- event=event,
- box_rect=rect,
- text_rect=rect.Clone(),
- height_padding=self._outer_padding,
- )
+ event_rects = self._create_ideal_rect_for_event(event)
self._prevent_overlapping_by_adjusting_rect_y(event_rects)
self.event_data.append(event_rects)
return self.event_data
@@ -234,7 +228,13 @@
if event.ends_today:
event.set_end_time(self._db.now)
if self._display_as_period(event):
- return self._calc_ideal_rect_for_period_event(event)
+ rect = self._calc_ideal_rect_for_period_event(event)
+ return EventRects(
+ event=event,
+ box_rect=rect,
+ text_rect=rect.Clone(),
+ height_padding=self._outer_padding,
+ )
else:
if self._is_subevent_in_extended_container_strategy(event):
rect = self._calc_ideal_rect_for_period_event(event)
@@ -242,9 +242,20 @@
rect.SetWidth(rw)
if not event.is_period():
self._extend_container_width_for_point_event(event, rect)
- return rect
+ return EventRects(
+ event=event,
+ box_rect=rect,
+ text_rect=rect.Clone(),
+ height_padding=self._outer_padding,
+ )
else:
- return self._calc_ideal_rect_for_non_period_event(event)
+ rect = self._calc_ideal_rect_for_non_period_event(event)
+ return EventRects(
+ event=event,
+ box_rect=rect,
+ text_rect=rect.Clone(),
+ height_padding=self._outer_padding,
+ )
def _extend_container_width_for_point_event(self, subevent, subevent_rect):
"""Make point events be enclosed by the container rectangle."""
changeset: 8057:6c38d6c35d6e
user: Rickard Lindberg <rickard@rickardlindberg.me>
date: Sun Aug 24 10:47:57 2025 +0200
summary: Fix text rect for point event in extended container
diff -r bd3d0f52b6e4 -r 6c38d6c35d6e source/timelinelib/canvas/drawing/scene.py
--- a/source/timelinelib/canvas/drawing/scene.py Sun Aug 24 10:35:30 2025 +0200
+++ b/source/timelinelib/canvas/drawing/scene.py Sun Aug 24 10:47:57 2025 +0200
@@ -240,6 +240,7 @@
event_rects = self._calc_ideal_rect_for_period_event(event)
rw = self._calc_width_for_non_period_event(event)
event_rects.box_rect.SetWidth(rw)
+ event_rects.text_rect.SetWidth(rw)
if not event.is_period():
self._extend_container_width_for_point_event(event_rects)
return event_rects
changeset: 8048:cfe7be006c13
user: Rickard Lindberg <rickard@rickardlindberg.me>
date: Sun Aug 24 07:50:24 2025 +0200
summary: Implement set_bounding_y in terms of move_y
diff -r 6ea1f3b8bad5 -r cfe7be006c13 source/timelinelib/canvas/drawing/scene.py
--- a/source/timelinelib/canvas/drawing/scene.py Sat Aug 23 20:17:15 2025 +0200
+++ b/source/timelinelib/canvas/drawing/scene.py Sun Aug 24 07:50:24 2025 +0200
@@ -572,9 +572,7 @@
>>> event_rects.bounding_rect
wx.Rect(0, 5, 10, 15)
"""
- diff = self.bounding_rect.Y - y
- self.box_rect.Y -= diff
- self.text_rect.Y -= diff
+ self.move_y(y - self.bounding_rect.Y)
def extend_bounding_height(self, height):
self.box_rect.Height += height
changeset: 8050:18cc2c9d5ee2
user: Rickard Lindberg <rickard@rickardlindberg.me>
date: Sun Aug 24 07:59:31 2025 +0200
summary: Fix misstake when checking is_allowed_to_overlap
diff -r c8fdd8e75b29 -r 18cc2c9d5ee2 source/timelinelib/canvas/drawing/scene.py
--- a/source/timelinelib/canvas/drawing/scene.py Sun Aug 24 07:58:07 2025 +0200
+++ b/source/timelinelib/canvas/drawing/scene.py Sun Aug 24 07:59:31 2025 +0200
@@ -498,7 +498,7 @@
and
event_rects.bounding_rect.Y < self.divider_y
and
- not event_rects.bounding_rect.is_allowed_to_overlap()
+ not event_rects.box_rect.is_allowed_to_overlap()
)
]
changeset: 8020:00063458b083
user: Rickard Lindberg <rickard@rickardlindberg.me>
date: Sat Aug 23 15:07:15 2025 +0200
summary: Introduce EventRects
diff -r 8d6eb11fd6ba -r 00063458b083 source/timelinelib/canvas/drawing/scene.py
--- a/source/timelinelib/canvas/drawing/scene.py Sun Aug 10 07:59:56 2025 +0200
+++ b/source/timelinelib/canvas/drawing/scene.py Sat Aug 23 15:07:15 2025 +0200
@@ -226,7 +226,7 @@
for event in events:
rect = self._create_ideal_rect_for_event(event)
self._prevent_overlapping_by_adjusting_rect_y(event, rect)
- self.event_data.append((event, rect))
+ self.event_data.append(EventRects(event=event, rect=rect))
return self.event_data
def _deflate_rects(self, event_data):
@@ -491,3 +491,42 @@
def _get_container_data_for_subevent(self, subevent):
return [(event, rect) for (event, rect) in self.event_data if event is subevent.container][0]
+
+
+class EventRects:
+
+ def __init__(self, event, rect):
+ self.event = event
+ self.rect = rect
+
+ def __iter__(self):
+ """
+ EventRects can unpack into an event and a rect for backwards
+ compatibility:
+
+ >>> event = object()
+ >>> rect = object()
+ >>> unpacked_event, unpacked_rect = EventRects(event, rect)
+ >>> unpacked_event is event
+ True
+ >>> unpacked_rect is rect
+ True
+ """
+ return iter(self.to_tuple())
+
+ def __getitem__(self, key):
+ """
+ EventRects can be indexed on its unpacked values:
+
+ >>> event = object()
+ >>> rect = object()
+ >>> event_rects = EventRects(event, rect)
+ >>> event_rects[0] is event
+ True
+ >>> event_rects[1] is rect
+ True
+ """
+ return self.to_tuple()[key]
+
+ def to_tuple(self):
+ return (self.event, self.rect)
changeset: 8047:6ea1f3b8bad5
user: Rickard Lindberg <rickard@rickardlindberg.me>
date: Sat Aug 23 20:17:15 2025 +0200
summary: Draw text inside text rect instead of box rect
diff -r 2179d111f0cc -r 6ea1f3b8bad5 source/timelinelib/canvas/drawing/drawers/default.py
--- a/source/timelinelib/canvas/drawing/drawers/default.py Sat Aug 23 19:56:56 2025 +0200
+++ b/source/timelinelib/canvas/drawing/drawers/default.py Sat Aug 23 20:17:15 2025 +0200
@@ -495,21 +495,25 @@
for event_rects in self.scene.event_data:
self.dc.SetFont(self._event_text_font)
if event_rects.event.is_container():
- self._draw_container(event_rects.event, event_rects.box_rect, view_properties)
+ self._draw_container(event_rects, view_properties)
else:
- self._draw_box(event_rects.box_rect, event_rects.event, view_properties)
+ self._draw_box(event_rects, view_properties)
- def _draw_container(self, event, rect, view_properties):
+ def _draw_container(self, event_rects, view_properties):
+ rect = event_rects.box_rect
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)
+ self._draw_box(
+ event_rects=event_rects.replace_box_rect(box_rect),
+ view_properties=view_properties,
+ )
- def _draw_box(self, rect, event, view_properties):
- self.dc.SetClippingRegion(rect)
- self._event_box_drawer.draw(self.dc, self.scene, rect, event, view_properties)
- if EXTENDED_CONTAINER_STRATEGY.enabled() and event.is_subevent() and not event.is_period():
- self._draw_time_marker_for_point_event(rect)
+ def _draw_box(self, event_rects, view_properties):
+ self.dc.SetClippingRegion(event_rects.bounding_rect)
+ self._event_box_drawer.draw(self.dc, self.scene, event_rects, view_properties)
+ if EXTENDED_CONTAINER_STRATEGY.enabled() and event_rects.event.is_subevent() and not event_rects.event.is_period():
+ self._draw_time_marker_for_point_event(event_rects.box_rect)
self.dc.DestroyClippingRegion()
def _draw_time_marker_for_point_event(self, rect):
diff -r 2179d111f0cc -r 6ea1f3b8bad5 source/timelinelib/canvas/drawing/scene.py
--- a/source/timelinelib/canvas/drawing/scene.py Sat Aug 23 19:56:56 2025 +0200
+++ b/source/timelinelib/canvas/drawing/scene.py Sat Aug 23 20:17:15 2025 +0200
@@ -222,7 +222,7 @@
event_rects = EventRects(
event=event,
box_rect=rect,
- text_rect=rect,
+ text_rect=rect.Clone(),
height_padding=self._outer_padding,
)
self._prevent_overlapping_by_adjusting_rect_y(event_rects)
@@ -527,6 +527,14 @@
self.text_rect = text_rect
self.height_padding = height_padding
+ def replace_box_rect(self, box_rect):
+ return EventRects(
+ event=self.event,
+ box_rect=box_rect,
+ text_rect=self.text_rect,
+ height_padding=self.height_padding,
+ )
+
@property
def bounding_rect(self):
"""
@@ -549,8 +557,9 @@
def set_bounding_y(self, y):
"""
- >>> rect = Rect(0, 0, 10, 5)
- >>> event_rects = EventRects(event=None, box_rect=rect, text_rect=rect, height_padding=5)
+ >>> box_rect = Rect(0, 0, 10, 5)
+ >>> text_rect = Rect(0, 0, 10, 5)
+ >>> event_rects = EventRects(event=None, box_rect=box_rect, text_rect=text_rect, height_padding=5)
>>> event_rects.box_rect
wx.Rect(0, 0, 10, 5)
@@ -565,12 +574,16 @@
"""
diff = self.bounding_rect.Y - y
self.box_rect.Y -= diff
+ self.text_rect.Y -= diff
def extend_bounding_height(self, height):
self.box_rect.Height += height
+ self.text_rect.Height += height
def extend_width(self, widht):
self.box_rect.Width += widht
+ self.text_rect.Width += widht
def move_y(self, amount):
self.box_rect.Y += amount
+ self.text_rect.Y += amount
diff -r 2179d111f0cc -r 6ea1f3b8bad5 source/timelinelib/canvas/eventboxdrawers/defaulteventboxdrawer.py
--- a/source/timelinelib/canvas/eventboxdrawers/defaulteventboxdrawer.py Sat Aug 23 19:56:56 2025 +0200
+++ b/source/timelinelib/canvas/eventboxdrawers/defaulteventboxdrawer.py Sat Aug 23 20:17:15 2025 +0200
@@ -39,7 +39,9 @@
class DefaultEventBoxDrawer:
- def draw(self, dc, scene, rect, event, view_properties):
+ def draw(self, dc, scene, event_rects, view_properties):
+ rect = event_rects.box_rect
+ event = event_rects.event
self.scene = scene
self.view_properties = view_properties
selected = view_properties.is_selected(event)
@@ -49,7 +51,7 @@
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)
+ self._draw_event_box(dc, event_rects, selected)
def _draw_period_event_as_symbol_below_divider_line(self, dc, rect, scene, event):
dc.DestroyClippingRegion()
@@ -61,12 +63,15 @@
dc.DrawLine(x, y0, x, y1)
dc.DrawCircle(x, y1, 2)
- def _draw_event_box(self, dc, rect, event, selected):
+ def _draw_event_box(self, dc, event_rects, selected):
+ event = event_rects.event
+ rect = event_rects.box_rect
+ text_rect = event_rects.text_rect
self._draw_background(dc, rect, event)
self._draw_fuzzy_edges(dc, rect, event)
self._draw_locked_edges(dc, rect, event)
self._draw_progress_box(dc, rect, event)
- self._draw_text(dc, rect, event)
+ self._draw_text(dc, text_rect, event)
self._draw_contents_indicator(dc, event, rect)
self._draw_locked_edges(dc, rect, event)
self._draw_selection_handles(dc, event, rect, selected)
changeset: 8019:8d6eb11fd6ba
user: Rickard Lindberg <rickard@rickardlindberg.me>
date: Sun Aug 10 07:59:56 2025 +0200
summary: Fix formatting issue in changelog.
diff -r b66471810a7c -r 8d6eb11fd6ba documentation/changelog.rst
--- a/documentation/changelog.rst Sun Aug 10 07:53:05 2025 +0200
+++ b/documentation/changelog.rst Sun Aug 10 07:59:56 2025 +0200
@@ -11,9 +11,7 @@
Fixed crash reports and bugs:
-* ``TypeError: DC.DrawCircle(): arguments did not match any overloaded call:
-overload 1: argument 1 has unexpected type 'float' overload 2: argument 1 has
-unexpected type 'float'``
+* ``TypeError: DC.DrawCircle(): arguments did not match any overloaded call: overload 1: argument 1 has unexpected type 'float' overload 2: argument 1 has unexpected type 'float'``
This was a problem with the feature "Milestones can be drawn as circles"
introduced in version 2.11.0. The ``DC.DrawCircle`` API expects integers, but
we didn't ensure that.
changeset: 8017:0d3bdb916bad
user: Rickard Lindberg <rickard@rickardlindberg.me>
date: Sun Aug 10 07:51:14 2025 +0200
summary: Ensure arguments to DC.DrawCircle are integers.
diff -r e36e19c5df16 -r 0d3bdb916bad documentation/changelog.rst
--- a/documentation/changelog.rst Sat Aug 09 20:37:42 2025 +0200
+++ b/documentation/changelog.rst Sun Aug 10 07:51:14 2025 +0200
@@ -9,6 +9,15 @@
* Beta versions: |betas|_
+Fixed crash reports and bugs:
+
+* ``TypeError: DC.DrawCircle(): arguments did not match any overloaded call:
+overload 1: argument 1 has unexpected type 'float' overload 2: argument 1 has
+unexpected type 'float'``
+ This was a problem with the feature "Milestones can be drawn as circles"
+ introduced in version 2.11.0. The ``DC.DrawCircle`` API expects integers, but
+ we didn't ensure that.
+
Version 2.11.0
--------------
**Released on 9 August 2025.**
diff -r e36e19c5df16 -r 0d3bdb916bad source/timelinelib/canvas/eventboxdrawers/defaultmilestonedrawer.py
--- a/source/timelinelib/canvas/eventboxdrawers/defaultmilestonedrawer.py Sat Aug 09 20:37:42 2025 +0200
+++ b/source/timelinelib/canvas/eventboxdrawers/defaultmilestonedrawer.py Sun Aug 10 07:51:14 2025 +0200
@@ -52,7 +52,11 @@
dc.DrawRectangle(self._rect)
def _draw_circle_background(self, dc):
- dc.DrawCircle(self._rect.X + self._rect.Width / 2, self._rect.Y + self._rect.Height / 2, self._rect.Width / 2)
+ dc.DrawCircle(
+ self._rect.X + self._rect.Width // 2,
+ self._rect.Y + self._rect.Height // 2,
+ self._rect.Width // 2
+ )
def draw_label(self, dc):
label = self._event.text[0] if self._event.text else " "
changeset: 8018:b66471810a7c
user: Rickard Lindberg <rickard@rickardlindberg.me>
date: Sun Aug 10 07:53:05 2025 +0200
summary: Draw milestones at the correct x-coordinate.
diff -r 0d3bdb916bad -r b66471810a7c documentation/changelog.rst
--- a/documentation/changelog.rst Sun Aug 10 07:51:14 2025 +0200
+++ b/documentation/changelog.rst Sun Aug 10 07:53:05 2025 +0200
@@ -18,6 +18,9 @@
introduced in version 2.11.0. The ``DC.DrawCircle`` API expects integers, but
we didn't ensure that.
+* Milestones were drawn slightly off center because of an incorrect
+ calculation.
+
Version 2.11.0
--------------
**Released on 9 August 2025.**
diff -r 0d3bdb916bad -r b66471810a7c source/timelinelib/canvas/drawing/scene.py
--- a/source/timelinelib/canvas/drawing/scene.py Sun Aug 10 07:51:14 2025 +0200
+++ b/source/timelinelib/canvas/drawing/scene.py Sun Aug 10 07:53:05 2025 +0200
@@ -314,7 +314,7 @@
if event.is_milestone():
rect = Rect(rx, ry, rw, rh)
rect.SetWidth(rect.GetHeight())
- rect.SetX(self._metrics.calc_x(event.get_time_period().start_time) - rw // 2)
+ rect.SetX(self._metrics.calc_x(event.get_time_period().start_time) - rect.Width // 2)
return FloatingRect(rect)
return self._calc_ideal_wx_rect(rx, ry, rw, rh)
changeset: 8016:e36e19c5df16
user: Rickard Lindberg <rickard@rickardlindberg.me>
date: Sat Aug 09 20:37:42 2025 +0200
summary: Add removed instruction to upload binaries to SF as well.
diff -r 87cd10564450 -r e36e19c5df16 documentation/release-instructions.rst
--- a/documentation/release-instructions.rst Sat Aug 09 20:29:19 2025 +0200
+++ b/documentation/release-instructions.rst Sat Aug 09 20:37:42 2025 +0200
@@ -63,3 +63,9 @@
1. Send email to thetimelineproj-user@lists.sourceforge.net
2. Post news to SF (http://sourceforge.net/p/thetimelineproj/news/?source=navbar)
(login required)
+
+Upload binaries to SF as well
+-----------------------------
+
+Ensure that the exe file has "Default Download For" Windows checkbox checked
+and ensure that the the zip file has all the others checked.