Migrate from GTK3 to GTK4 - #786
Conversation
Migrate from GTK 3.0 to GTK 4.0 version requirements and update all trivial API changes: Container.add→append/set_child, pack_start→append, show_all removal, delete-event→close-request, connect_signals→manual connections, STYLE_CLASS constants→strings, get_toplevel→get_root, removed widgets (Arrow, HBox, IconSize), set_shadow_type removal, and HeaderBar/Button API updates. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Run gtk4-builder-tool simplify --3to4 on date_range.ui, edit_activity.ui, and preferences.ui. Then strip remaining deprecated elements: GtkAlignment→GtkBox, GtkButtonBox→GtkBox, remove shadow_type/border_width/padding/relief/use_stock/events properties, remove all <signal> elements (connected manually in Python), and remove <packing> blocks. Delete unused stats.ui (no Python code references it, targets GTK+ 2.16). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Menu system: Gtk.Menu/MenuItem → Gio.Menu + PopoverMenu with
window-scoped SimpleActions
- Dialogs: ReportChooserDialog rewritten to use Gtk.FileDialog
async API; MessageDialog uses named params; AboutDialog updated
- Styling: add_hint() replaced with set_placeholder_text(),
override_background_color → CSS provider, get_background_color →
lookup_color("theme_bg_color"), get_style().font_desc → default
- Cursors: CursorType enums → Cursor.new_from_name() strings
- Colors: Gdk.Color removed, RGBA-only in ColorUtils
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add SceneEvent dataclass shim to preserve event-passing interface for Scene/Sprite consumers without rewriting 40+ handler sites - Replace do_draw() override with set_draw_func() callback - Replace do_configure_event with draw_func width/height params - Replace set_events() + GTK3 event signals with GTK4 controllers: EventControllerMotion, GestureClick, EventControllerScroll, EventControllerKey - Remove GDK window tracking (_window/get_window/get_pointer); store mouse coords from motion controller instead - Replace IconTheme.get_default() with get_for_display() - Update Icon.load_icon to use lookup_icon() + file path - Fix Totals widget: enter/leave-notify → Scene on-mouse-over/out, style-updated → notify::css-classes - Fix Overview.on_key_press for EventControllerKey signature - Fix layout.py pointer query to use scene.mouse_x/y Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- CmdLineEntry, TimeInput, TagsEntry: Replace Gtk.Window(POPUP) with Gtk.Popover, remove manual move()/resize() positioning - Replace key-press-event/focus-out-event/focus-in-event with EventControllerKey/EventControllerFocus on all Entry subclasses - Replace button-press-event on TreeView with GestureClick - Remove _parent_click_watcher pattern (Popover auto-dismisses) - Remove EntryCompletion from ActivityEntry and CategoryEntry (removed in GTK4; CmdLineEntry popup pattern is the replacement) - Update icon-press signal handlers for GTK4 signature (no event) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove GTK3 DnD (enable_model_drag_source/dest, TargetFlags, drag_data_get/received) — marked TODO for GTK4 DragSource/DropTarget - Replace button-press/release-event on tree lists with GestureClick - Replace key-press-event on tree lists with EventControllerKey - Replace focus-out-event on tags textarea with EventControllerFocus - Add EventControllerKey for preferences window (Ctrl+W, Escape) - Update all handler signatures for GTK4 controller params Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Update Flatpak runtime to GNOME 47 (GTK4) - Update README: gir1.2-gtk-3.0 → gir1.2-gtk-4.0, Gtk3 → Gtk4 - Fix po/wscript shebang: python2 → python3 - Remove dead _test_label (GTK4 Label doesn't accept positional args) - Remove GtkEventBox from edit_activity.ui (replaced with GtkBox) - Remove AtkObject accessibility blocks from all UI files - Add tests/test_gtk4_smoke.py: verifies Scene, SceneEvent, and UI file loading work under GTK4 - All 45 tests pass (41 existing + 4 new) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Calendar: day-selected-double-click → day-selected, select_month → select_day(GLib.DateTime), get_date tuple → GLib.DateTime - facttree: has_toplevel_focus() → is_active() - Entry subclasses: remove parent=parent from __init__ (not writable in GTK4), append to parent container explicitly instead - tags.py Tag: gtk.Style() → pango.FontDescription(graphics._font_desc) - edit_activity: focus_in/out_event → EventControllerFocus - dayline: get_color(StateFlags.NORMAL) → get_color() - edit_activity: day_preview.set_child → .append (GtkBox not single-child) - preferences: remove dead DnD methods using removed GTK3 APIs - overview: register menu actions on app (not window, which lacks add_action) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- DayLine: guard against width=0 in on_enter_frame (first frame before layout) - Add margin-start/end/top/bottom to main containers in all UI files to replace the border_width/padding that was stripped from GtkAlignment wrappers during conversion - Remove leftover GtkEntryCompletion objects from edit_activity.ui - Remove dead DnD methods from preferences.py (used removed APIs) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The GtkEntryCompletion objects were removed but two entries still referenced them via <property name="completion">. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- TimeInput: remove GestureClick on Entry (interfered with text input), add secondary dropdown icon instead, fix icon-release signature (no event param in GTK4) - Calendar UI: remove invalid year/month/day/resize_toplevel properties (GTK4 Calendar uses GDateTime) - DayLine: guard against zero width on first frame Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Switch from icon-release to icon-press signal - Remove popup-on-focus-in (caused focus fight with Popover stealing focus → immediate focus-out → hide loop) - Popup now only shows via dropdown icon click Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use raw strings for regex patterns with \D and \s - Set key controller to CAPTURE phase so window-level shortcuts (Ctrl+Space, Ctrl+N, arrows) work even when a child has focus Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
datetime.utcfromtimestamp() is deprecated since Python 3.12. Replace with fromtimestamp(ts, tz=timezone.utc).replace(tzinfo=None) to get the same naive-UTC datetime the codebase expects. For .date() calls, use fromtimestamp(ts, tz=timezone.utc).date(). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
GTK4 Calendar.get_date() returns GLib.DateTime, not a (y,m,d) tuple. Use get_year()/get_month()/get_day_of_month() methods. Month is already 1-based in GLib.DateTime (unlike GTK3's 0-based). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Defer set_cursor(start_editing=True) to idle callback so the GestureClick gesture finishes processing before TreeView tries to enter edit mode. Use set_cursor instead of set_cursor_on_cell which is more reliable in GTK4. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All set_cursor_on_cell calls replaced with set_cursor + grab_focus deferred via GLib.idle_add. GTK4 TreeView requires the gesture to finish processing before editing can start. Applied consistently to: add button, edit button, double-click-to-edit, and F2 key handlers for both category and activity lists. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove per-tree EventControllerKey that was intercepting keystrokes meant for the CellEditable entry widget. Move Delete/F2 handling to the window-level key controller, which skips handling when a cell is in edit mode (editable=True). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
GTK 4.22 has a bug where TreeView set_cursor(start_editing=True) triggers a css_node_insert_after assertion failure and never creates the CellEditable widget. Work around this by using a simple modal dialog with an Entry for all add/edit operations on categories and activities. Double-click and F2 also open the dialog instead of trying inline editing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The activity and category ScrolledWindows and their parent boxes needed hexpand=1 to fill the available space in the GtkPaned. Without it, GTK4 gave them minimum width, truncating text. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
get_allocation().width can be very small before full layout in GTK4, causing tags to wrap at tiny widths and show truncated. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Setting Tag.width=0 before creating the Label caused Label._bounds_width=0, making Pango wrap text at 0 pixels wide (one character per line). Remove the premature width/height=0 initialization — the correct size is set by __setattr__ when self.text is assigned. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The autocomplete tags text view and its parent containers needed hexpand to fill the available width. Without it, text wrapped at a few pixels wide showing one character per line. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
EventControllerKey on the window intercepts ALL keystrokes in GTK4, preventing TextViews and other editable widgets from receiving input. Replace with ShortcutController that only handles specific key combinations (Ctrl+W, Escape, Delete, F2) and lets all other keys pass through to focused widgets. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Avoid window-wide Delete shortcut intercepting text editing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Even ShortcutController on the window prevents the tags TextView from receiving keyboard input in GTK4. Remove all keyboard interception — the window can be closed via the title bar button. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DrawingArea does not inherit the theme foreground color, so get_color() returns black regardless of theme. Use the root window's style context instead, and update colors on map to ensure the correct color is applied after the widget is in the tree. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Will this pr make your previous one obsolete? |
The dark theme? Yes. |
|
Can you comment a bit more on your process? I see Claude is involved with development, but how did you get to this result? Have you reviewed the result thoroughly? What is your own skill level (i.e. are you capable of fully reviewing and understanding all the produced code)? How did you test this? Especially since this is a big PR, I would like to avoid a situation where there is a lot of AI-generated code of mixed quality that is submitted here without further review, and where review comments are not understood by the PR submitter and just fed back into the AI. This leads to a big workload for the maintainers, without building any sort of contributor trust or skill. The bigger question (for the project) is also whether to accept AI-generated code at all. I have not personally made up my mind about this, but this is something to discuss in a separate issue if we want. |
|
Hi @matthijskooijman Thanks for the questions. The migration is mostly AI-generated. My motivation wasn't to avoid writing code, but to automate what I saw as a largely mechanical migration from GTK3 to GTK4. Hamster already works well for my own use, but I'd like to see it continue to work on modern desktops and eventually make it easier to add new features. After the initial migration, my work was mainly getting it into a working state: fixing compile errors, resolving runtime issues, and addressing UI glitches until it behaved correctly. I tested it primarily against my own daily workflow, so while I'm reasonably confident it covers the common paths, I haven't exhaustively tested every feature. I don't have prior GTK development experience, so the GTK-specific parts were the hardest for me. I do understand most of the resulting code and the application itself, but for GTK4-specific APIs I relied on AI as a tool while verifying the results through building, testing, and iterating. Regarding review, I understand the concern about AI-generated contributions creating extra work. My intention isn't to act as a proxy between reviewers and an LLM. If review comments identify problems, I'll work to understand them and verify any proposed changes before updating the PR. Whether the project wants to accept AI-assisted contributions at all is a separate policy decision, and I'm happy to respect whatever direction the project chooses. If this PR isn't the right fit for the project, I'll completely understand. |
|
Wow, complements for this big undertaking. I have installed your branch from source to take it for a spin (on Xubuntu 26.04). It runs and the mouse interaction seems mostly okay, but I ran into a number of issues with keyboard interaction (eg. can't close edit/update window with the escape key, can't save with the enter key from cmdline). Especially the cmdline editing is flaky and practically unusable (eg. try editing the end time of a completed task in the cmdline). The Stop tracking button on the main window doesn't work (traceback complains of missing dt.timezone in StopTracking). Several icons are missing in both the main window and the edit/update window (using adwaita-icon-theme). In short, while your progress is impressive, I feel that considerable additional testing is needed before merging this PR. |
…amster datetime hamster's datetime module shadows stdlib, so dt.timezone and dt.datetime.fromtimestamp(ts, tz=...) failed with AttributeError/ValueError. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The GTK3 on_window_key_pressed was never wired up after migration. Added EventControllerKey on the window and updated the method signature and body to GTK4 (keyval/state args, get_visible()). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
GTK4 Entry consumes Enter internally to emit activate, so the window-level EventControllerKey never sees it. Connect the cmdline's activate signal directly to trigger save. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
EventControllerKey in BUBBLE phase never sees keys consumed by Entry widgets. ShortcutController handles this correctly. Enter-to-save already uses the activate signal. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Ctrl+W was missing. LOCAL scope didn't fire when child widgets (fact_tree, filter_entry) had focus; MANAGED scope propagates through the window's shortcut manager. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Scene.__on_key_press/release always returned True, preventing Escape/Ctrl+W from reaching the window's ShortcutController. Return the actual handled state instead. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adwaita GTK4 only ships list-add-symbolic and list-remove-symbolic. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Enter returned early when no popup tree selection existed (e.g. popup never opened), skipping _select_time entirely. Handle Enter before the tree selection check so it always confirms the typed time. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
TimeInput's key handler consumed Enter without emitting activate. Now emits activate after confirming the time value, and edit_activity connects all Entry activate signals to save. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace get_style_context().add_class/remove_class with
add_css_class/remove_css_class (deprecated since GTK 4.10)
- Replace _style.get_color() with widget.get_color()
- Replace get_property("visible") with get_visible()
- Remove no-op visible=True from UI files (default in GTK4)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
GTK4 Popover autohide=True (default) grabs focus when popup() is called. The autocomplete popup appears on every keystroke, stealing focus and causing the Entry to re-select all text when focus returns. Set autohide=False since we dismiss manually. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The inline edit dialog created by _show_edit_dialog had no keyboard shortcut to dismiss it. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Thanks, @GeraldJansen, it took me some time to get back into it :) Please take it for another spin |
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
I have and it seems much improved. Stop tracking is fixed and keyboard interaction too. Great work! The icons are still missing on my platform (Xubuntu 26.04, XFCE4, default themes and icons). Also, the timeline in the edit/update window is completely absent or invisible. (For me, the timeline is just unnecessary and complicated eye candy, but others may love it). This will probably be my last feedback. I am now retired and have practically stopped using Hamster. I just wanted to provide some concrete feedback in recognition of all the effort you are putting into this. All the best. |
DayLine (DrawingArea) had no hexpand/vexpand, so GTK4 gave it 0 width inside the horizontal day_preview Box. Also add missing Gdk import for the lookup_color fallback path. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Thanks @GeraldJansen for the feedback! I completely missed the timeline, as I never noticed it. I had to spin up the Flatpak version to understand what I was missing :) I also considered dropping it, but the fix was so small that there was really no reason to remove it.
As for the icons, Which specific icons are missing on XFCE? Is it all of them (nav arrows, add/remove, menu, search, etc.), or only certain ones?
|
Overview's window-level EventControllerKey forwarded keys to FactTree even when FactTree already had focus, causing double dispatch (e.g. Enter opened the edit window twice). Guard with has_focus() check. Also fix stale fact_controller guard (check window != None) and invalid \s escape sequences in datetime.py. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace GTK3 EntryCompletion with Popover+ListBox autocomplete for ActivityEntry and CategoryEntry (GTK4 removed EntryCompletion) - Fix CmdLineEntry Backspace: use CAPTURE phase so ignore_stroke is set before GtkText processes the key and fires "changed" - Replace hardcoded projecthamster#555 color in preferences unsorted painter with GTK's native sensitive=False for theme-aware dimming Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@GeraldJansen ... i would try to run the app with |



Summary
Complete migration of Hamster Time Tracker from GTK3 to GTK4, covering all UI files, Python code, and the graphics/animation framework.
What's included
gtk4-builder-tool+ extensive manual cleanupgtk_css_node_insert_afterassertion bug)can_focus=Falseand emptyinternal-child="accessible"ATK blocks removed from all UI files (breaks descendant focus in GTK4)utcfromtimestampreplaced with timezone-aware equivalentKey GTK3→GTK4 behavioral differences discovered
can_focus=Falseon a container preventsgrab_focus()on ALL descendants — unlike GTK3 where it only affected the container itselfget_color()returns black regardless of theme; must use root window's style contextset_cursor(start_editing=True)triggers assertion failureNot included (deferred)
add_provider_for_screen→add_provider_for_display)test_round_triptest failure (missingdt.timezoneexport, unrelated to GTK4)Test plan
🤖 Generated with Claude Code