{"thread":{"id":"64146","subject":"[PATCH] gitk: make the 'Tags and Heads' window geometry sticky","startedAt":"2025-09-13T18:11:59Z","lastAt":"2025-09-18T17:58:07Z","messageCount":2,"participants":["Michael Rappazzo","Johannes Sixt"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"526253","messageId":"20250913181153.46575-1-rappazzo@gmail.com","threadId":"64146","inReplyTo":null,"subject":"[PATCH] gitk: make the 'Tags and Heads' window geometry sticky","fromName":"Michael Rappazzo","fromEmail":"rappazzo@gmail.com","sentAt":"2025-09-13T18:10:06Z","receivedAt":"2025-09-13T18:11:59Z","isPatch":true,"sender":{"key":"rappazzo@gmail.com","avatar":"https://avatars.githubusercontent.com/u/525287?v=4"},"body":"Currently, the Tags and Heads window always opens at a default position\nand size, requiring users to reposition it each time. \n\nThis change saves and restores the Tags and Heads window size and position\nrelative to the main gitk window. The geometry is stored in the config file\nas `geometry(showrefs)` and persists between gitk sessions. The window\nposition is stored relative to the main window, so it maintains the same\nspatial relationship when the main window is moved or when gitk is restarted\non different monitors.\n\nSigned-off-by: Michael Rappazzo <rappazzo@gmail.com>\n---\n gitk | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--\n 1 file changed, 73 insertions(+), 2 deletions(-)\n\ndiff --git a/gitk b/gitk\nindex 6e4d71d585..49b2ceefc0 100755\n--- a/gitk\n+++ b/gitk\n@@ -3114,6 +3114,19 @@ proc savestuff {w} {\n         puts $f \"set geometry(pwsash1) \\\"[.tf.histframe.pwclist sashpos 1] 1\\\"\"\n         puts $f \"set geometry(botwidth) [winfo width .bleft]\"\n         puts $f \"set geometry(botheight) [winfo height .bleft]\"\n+        if {[winfo exists .showrefs]} {\n+            set geom [parse_geometry [wm geometry .showrefs]]\n+            set main_geom [parse_geometry [wm geometry .]]\n+            if {[llength $geom] == 4 && [llength $main_geom] == 4} {\n+                lassign $geom w h x y\n+                lassign $main_geom mw mh mx my\n+                set rel_x [expr {$x - $mx}]\n+                set rel_y [expr {$y - $my}]\n+                puts $f \"set geometry(showrefs) \\\"${w}x${h}+${rel_x}+${rel_y}\\\"\"\n+            }\n+        } elseif {[info exists geometry(showrefs)]} {\n+            puts $f \"set geometry(showrefs) \\\"$geometry(showrefs)\\\"\"\n+        }\n \n         array set view_save {}\n         array set views {}\n@@ -10207,11 +10220,13 @@ proc showrefs {} {\n     if {[winfo exists $top]} {\n         raise $top\n         refill_reflist\n+        wm protocol $top WM_DELETE_WINDOW [list destroy_showrefs $top]\n         return\n     }\n     ttk_toplevel $top\n     wm title $top [mc \"Tags and heads: %s\" [file tail [pwd]]]\n     make_transient $top .\n+    wm protocol $top WM_DELETE_WINDOW [list destroy_showrefs $top]\n     text $top.list -background $bgcolor -foreground $fgcolor \\\n         -selectbackground $selectbgcolor -font mainfont \\\n         -xscrollcommand \"$top.xsb set\" -yscrollcommand \"$top.ysb set\" \\\n@@ -10237,8 +10252,8 @@ proc showrefs {} {\n     ttk::checkbutton $top.sort -text [mc \"Sort refs by type\"] \\\n         -variable sortrefsbytype -command {refill_reflist}\n     grid $top.sort - -sticky w -pady 2\n-    ttk::button $top.close -command [list destroy $top] -text [mc \"Close\"]\n-    bind $top <Key-Escape> [list destroy $top]\n+    ttk::button $top.close -command [list destroy_showrefs $top] -text [mc \"Close\"]\n+    bind $top <Key-Escape> [list destroy_showrefs $top]\n     grid $top.close -\n     grid columnconfigure $top 0 -weight 1\n     grid rowconfigure $top 0 -weight 1\n@@ -10247,6 +10262,7 @@ proc showrefs {} {\n     bind $top.list <ButtonRelease-1> {sel_reflist %W %x %y; break}\n     set reflist {}\n     refill_reflist\n+    bind $top <Map> [list restore_showrefs_geometry $top]\n }\n \n proc sel_reflist {w x y} {\n@@ -10279,6 +10295,61 @@ proc reflistfilter_change {n1 n2 op} {\n     after 200 refill_reflist\n }\n \n+proc parse_geometry {geom_string} {\n+    if {[regexp {^(\\d+)x(\\d+)\\+(-?\\d+)\\+(-?\\d+)$} $geom_string -> w h x y]} {\n+        return [list $w $h $x $y]\n+    }\n+    return {}\n+}\n+\n+proc save_showrefs_geometry {top} {\n+    global geometry\n+\n+    if {![winfo exists $top] || ![winfo exists .]} return\n+\n+    set geom [parse_geometry [wm geometry $top]]\n+    set main_geom [parse_geometry [wm geometry .]]\n+    if {[llength $geom] == 4 && [llength $main_geom] == 4} {\n+        lassign $geom w h x y\n+        lassign $main_geom mw mh mx my\n+        set rel_x [expr {$x - $mx}]\n+        set rel_y [expr {$y - $my}]\n+        set geometry(showrefs) \"${w}x${h}+${rel_x}+${rel_y}\"\n+    }\n+}\n+\n+proc restore_showrefs_geometry {top} {\n+    global geometry\n+\n+    if {![info exists geometry(showrefs)] || ![winfo exists $top] || ![winfo exists .]} return\n+\n+    set saved_geom [parse_geometry $geometry(showrefs)]\n+    set main_geom [parse_geometry [wm geometry .]]\n+    if {[llength $saved_geom] == 4 && [llength $main_geom] == 4} {\n+        lassign $saved_geom w h rel_x rel_y\n+        lassign $main_geom mw mh mx my\n+\n+        set abs_x [expr {$mx + $rel_x}]\n+        set abs_y [expr {$my + $rel_y}]\n+\n+        # Ensure window stays on screen\n+        set screen_w [winfo screenwidth .]\n+        set screen_h [winfo screenheight .]\n+        if {$abs_x < 0} { set abs_x 0 }\n+        if {$abs_y < 0} { set abs_y 0 }\n+        if {$abs_x + $w > $screen_w} { set abs_x [expr {$screen_w - $w}] }\n+        if {$abs_y + $h > $screen_h} { set abs_y [expr {$screen_h - $h}] }\n+\n+        wm geometry $top \"${w}x${h}+${abs_x}+${abs_y}\"\n+    }\n+    bind $top <Map> {}\n+}\n+\n+proc destroy_showrefs {top} {\n+    save_showrefs_geometry $top\n+    destroy $top\n+}\n+\n proc refill_reflist {} {\n     global reflist reflistfilter showrefstop headids tagids otherrefids sortrefsbytype\n     global curview upstreamofref\n-- \n2.51.0\n\n"},{"id":"526714","messageId":"3fd3f64f-6ab7-4b4a-a834-a9c5c1e89d5b@kdbg.org","threadId":"64146","inReplyTo":"20250913181153.46575-1-rappazzo@gmail.com","subject":"Re: [PATCH] gitk: make the 'Tags and Heads' window geometry sticky","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2025-09-18T17:57:58Z","receivedAt":"2025-09-18T17:58:07Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 13.09.25 um 20:10 schrieb Michael Rappazzo:\n> Currently, the Tags and Heads window always opens at a default position\n> and size, requiring users to reposition it each time. \n> \n> This change saves and restores the Tags and Heads window size and position\n> relative to the main gitk window. The geometry is stored in the config file\n> as `geometry(showrefs)` and persists between gitk sessions. The window\n> position is stored relative to the main window, so it maintains the same\n> spatial relationship when the main window is moved or when gitk is restarted\n> on different monitors.\n\nThank you for this patch. In general, I like the direction this is\ngoing. I am baffled, though, by the sheer number of lines that must be\nadded to achieve the goal.\n\nI tested the patch Windows and Linux (KDE), and found some deficiencies\non both.\n\nDuring a session, on Windows, size and position are retained and on\nLinux, only the size is retained (the position is always the default).\nIn both environments, persistence across session happens only when Gitk\nis closed while the Tags and Heads window is open, but not when it is\nclosed via any of the means available.\n\nTo reduce the code a bit, would it be possible to set the global\ngeometry(showrefs) from a handler of the Configure event? Then it does\nnot have to be set on any other occasion.\n\n> \n> Signed-off-by: Michael Rappazzo <rappazzo@gmail.com>\n> ---\n>  gitk | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--\n>  1 file changed, 73 insertions(+), 2 deletions(-)\n\n> +proc parse_geometry {geom_string} {\n> +    if {[regexp {^(\\d+)x(\\d+)\\+(-?\\d+)\\+(-?\\d+)$} $geom_string -> w h x y]} {\n> +        return [list $w $h $x $y]\n> +    }\n> +    return {}\n> +}\n\nAre there any occasions where it is expected that the regular expression\ndoes not match? If not, let's fail with an error. Then we don't have to\nverify the return value at the call sites.\n\n> +proc restore_showrefs_geometry {top} {\n> +    global geometry\n> +\n> +    if {![info exists geometry(showrefs)] || ![winfo exists $top] || ![winfo exists .]} return\n> +\n> +    set saved_geom [parse_geometry $geometry(showrefs)]\n> +    set main_geom [parse_geometry [wm geometry .]]\n> +    if {[llength $saved_geom] == 4 && [llength $main_geom] == 4} {\n> +        lassign $saved_geom w h rel_x rel_y\n> +        lassign $main_geom mw mh mx my\n> +\n> +        set abs_x [expr {$mx + $rel_x}]\n> +        set abs_y [expr {$my + $rel_y}]\n> +\n> +        # Ensure window stays on screen\n> +        set screen_w [winfo screenwidth .]\n> +        set screen_h [winfo screenheight .]\n> +        if {$abs_x < 0} { set abs_x 0 }\n> +        if {$abs_y < 0} { set abs_y 0 }\n> +        if {$abs_x + $w > $screen_w} { set abs_x [expr {$screen_w - $w}] }\n> +        if {$abs_y + $h > $screen_h} { set abs_y [expr {$screen_h - $h}] }\n\nConsider the case where the stored height exceeds $screen_h. The title\nbar is moved out of the screen due to the order of these corrections.\nLet's correct the lower and right bounds first, and the upper and left\nbounds second. Then the title bar stays on the screen.\n\n> +\n> +        wm geometry $top \"${w}x${h}+${abs_x}+${abs_y}\"\n> +    }\n> +    bind $top <Map> {}\n> +}\n-- Hannes\n\n"}]}