From f9a3de2c1930a79244b5bbcf5d1d1c47522d73a0 Mon Sep 17 00:00:00 2001
From: andrew <andrew@arobeia.co.uk>
Date: Tue, 3 Nov 2026 11:10:51 +0000
Subject: [PATCH] gtk: Allow activating units that are not idle

Avoids removing the gesture controller while it is processing.

See RM #2442

Signed-off-by:
---
 client/control.c                |  12 ----
 client/gui-gtk-4.0/citydlg.c    | 115 +++++++++++++-------------------
 client/gui-gtk-4.0/unitselect.c |   5 +-
 client/gui-gtk-5.0/citydlg.c    |  21 ++----
 client/gui-gtk-5.0/unitselect.c |   5 +-
 5 files changed, 56 insertions(+), 102 deletions(-)

diff --git a/client/control.c b/client/control.c
index 1f1b312783..36e67041b9 100644
--- a/client/control.c
+++ b/client/control.c
@@ -495,18 +495,6 @@ void clear_unit_orders(struct unit *punit)
   }
 }
 
-/**********************************************************************//**
-  Try to set unit to focus directly. Won't set unit to focus if
-  unit can't currently be at focus.
-**************************************************************************/
-void unit_focus_try(struct unit *punit)
-{
-  if (punit->activity == ACTIVITY_IDLE) {
-    /* Only idle units can be at focus. */
-    unit_focus_set(punit);
-  }
-}
-
 /**********************************************************************//**
   Sets the focus unit directly. The unit given will be given the
   focus; if NULL the focus will be cleared.
diff --git a/client/gui-gtk-4.0/citydlg.c b/client/gui-gtk-4.0/citydlg.c
index 61fcae210a..f67e84b8ca 100644
--- a/client/gui-gtk-4.0/citydlg.c
+++ b/client/gui-gtk-4.0/citydlg.c
@@ -2310,6 +2310,7 @@ static void city_dialog_update_supported_units(struct city_dialog *pdialog)
   } else {
     for (i = m; i < n; i++) {
       GtkWidget *cmd, *pix;
+      GtkGesture *gesture;
       struct unit_node node;
 
       cmd = gtk_button_new();
@@ -2326,9 +2327,18 @@ static void city_dialog_update_supported_units(struct city_dialog *pdialog)
       gtk_box_append(GTK_BOX(pdialog->overview.supported_unit_table),
                      cmd);
 
-      node.left = NULL;
-      node.middle = NULL;
-      node.right = NULL;
+      /* Set up the event controllers for the three mouse buttons */
+      node.left = GTK_EVENT_CONTROLLER(gtk_gesture_click_new());
+      gesture = gtk_gesture_click_new();
+      gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(gesture), 2);
+      node.middle = GTK_EVENT_CONTROLLER(gesture);
+      gesture = gtk_gesture_click_new();
+      gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(gesture), 3);
+      node.right = GTK_EVENT_CONTROLLER(gesture);
+
+      gtk_widget_add_controller(cmd, node.left);
+      gtk_widget_add_controller(cmd, node.middle);
+      gtk_widget_add_controller(cmd, node.right);
 
       unit_node_vector_append(nodes, node);
     }
@@ -2342,8 +2352,6 @@ static void city_dialog_update_supported_units(struct city_dialog *pdialog)
     pnode = unit_node_vector_get(nodes, i);
     if (pnode) {
       GtkWidget *cmd, *pix;
-      GtkGesture *gesture;
-      GtkEventController *controller;
 
       cmd = pnode->cmd;
       pix = pnode->pix;
@@ -2351,36 +2359,23 @@ static void city_dialog_update_supported_units(struct city_dialog *pdialog)
       put_unit_picture_city_overlays(punit, GTK_PICTURE(pix), pnode->height,
                                      punit->upkeep, happy_cost);
 
-      if (pnode->left != NULL) {
-        gtk_widget_remove_controller(cmd, pnode->left);
-        gtk_widget_remove_controller(cmd, pnode->middle);
-        gtk_widget_remove_controller(cmd, pnode->right);
-      }
+      gtk_event_controller_reset(pnode->left);
+      gtk_event_controller_reset(pnode->middle);
+      gtk_event_controller_reset(pnode->right);
 
       gtk_widget_set_tooltip_text(cmd, unit_description(punit));
 
-      controller = GTK_EVENT_CONTROLLER(gtk_gesture_click_new());
-      g_signal_connect(controller, "pressed",
+      g_signal_connect(pnode->left, "pressed",
                        G_CALLBACK(supported_unit_callback),
                        GINT_TO_POINTER(punit->id));
-      gtk_widget_add_controller(cmd, controller);
-      pnode->left = controller;
-      gesture = gtk_gesture_click_new();
-      gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(gesture), 2);
-      controller = GTK_EVENT_CONTROLLER(gesture);
-      g_signal_connect(controller, "released",
+
+      g_signal_connect(pnode->middle, "released",
                        G_CALLBACK(middle_supported_unit_release),
                        GINT_TO_POINTER(punit->id));
-      gtk_widget_add_controller(cmd, controller);
-      pnode->middle = controller;
-      gesture = gtk_gesture_click_new();
-      gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(gesture), 3);
-      controller = GTK_EVENT_CONTROLLER(gesture);
-      g_signal_connect(controller, "released",
+
+      g_signal_connect(pnode->right, "released",
                        G_CALLBACK(right_unit_release),
                        GINT_TO_POINTER(punit->id));
-      gtk_widget_add_controller(cmd, controller);
-      pnode->right = controller;
 
       if (city_owner(pdialog->pcity) != client.conn.playing) {
         gtk_widget_set_sensitive(cmd, FALSE);
@@ -2434,6 +2429,7 @@ static void city_dialog_update_present_units(struct city_dialog *pdialog)
   } else {
     for (i = m; i < n; i++) {
       GtkWidget *cmd, *pix;
+      GtkGesture *gesture;
       struct unit_node node;
 
       cmd = gtk_button_new();
@@ -2450,9 +2446,18 @@ static void city_dialog_update_present_units(struct city_dialog *pdialog)
       gtk_box_append(GTK_BOX(pdialog->overview.present_unit_table),
                      cmd);
 
-      node.left = NULL;
-      node.middle = NULL;
-      node.right = NULL;
+      /* Set up the event controllers for the three mouse buttons */
+      node.left = GTK_EVENT_CONTROLLER(gtk_gesture_click_new());
+      gesture = gtk_gesture_click_new();
+      gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(gesture), 2);
+      node.middle = GTK_EVENT_CONTROLLER(gesture);
+      gesture = gtk_gesture_click_new();
+      gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(gesture), 3);
+      node.right = GTK_EVENT_CONTROLLER(gesture);
+
+      gtk_widget_add_controller(cmd, node.left);
+      gtk_widget_add_controller(cmd, node.middle);
+      gtk_widget_add_controller(cmd, node.right);
 
       unit_node_vector_append(nodes, node);
     }
@@ -2465,44 +2470,29 @@ static void city_dialog_update_present_units(struct city_dialog *pdialog)
     pnode = unit_node_vector_get(nodes, i);
     if (pnode) {
       GtkWidget *cmd, *pix;
-      GtkEventController *controller;
-      GtkGesture *gesture;
 
       cmd = pnode->cmd;
       pix = pnode->pix;
 
       put_unit_picture(punit, GTK_PICTURE(pix), pnode->height);
 
-      if (pnode->left != NULL) {
-        gtk_widget_remove_controller(cmd, pnode->left);
-        gtk_widget_remove_controller(cmd, pnode->middle);
-        gtk_widget_remove_controller(cmd, pnode->right);
-      }
+      gtk_event_controller_reset(pnode->left);
+      gtk_event_controller_reset(pnode->middle);
+      gtk_event_controller_reset(pnode->right);
 
       gtk_widget_set_tooltip_text(cmd, unit_description(punit));
 
-      controller = GTK_EVENT_CONTROLLER(gtk_gesture_click_new());
-      g_signal_connect(controller, "pressed",
+      g_signal_connect(pnode->left, "pressed",
                        G_CALLBACK(present_unit_callback),
                        GINT_TO_POINTER(punit->id));
-      gtk_widget_add_controller(cmd, controller);
-      pnode->left = controller;
-      gesture = gtk_gesture_click_new();
-      gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(gesture), 2);
-      controller = GTK_EVENT_CONTROLLER(gesture);
-      g_signal_connect(controller, "released",
+
+      g_signal_connect(pnode->middle, "released",
                        G_CALLBACK(middle_present_unit_release),
                        GINT_TO_POINTER(punit->id));
-      gtk_widget_add_controller(cmd, controller);
-      pnode->middle = controller;
-      gesture = gtk_gesture_click_new();
-      gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(gesture), 3);
-      controller = GTK_EVENT_CONTROLLER(gesture);
-      g_signal_connect(controller, "released",
+
+      g_signal_connect(pnode->right, "released",
                        G_CALLBACK(right_unit_release),
                        GINT_TO_POINTER(punit->id));
-      gtk_widget_add_controller(cmd, controller);
-      pnode->right = controller;
 
       if (city_owner(pdialog->pcity) != client.conn.playing) {
         gtk_widget_set_sensitive(cmd, FALSE);
@@ -2809,7 +2799,7 @@ static gboolean middle_present_unit_release(GtkGestureClick *gesture,
       && NULL != (pcity = tile_city(unit_tile(punit)))
       && NULL != (pdialog = get_city_dialog(pcity))
       && can_client_issue_orders()) {
-    unit_focus_try(punit);
+    unit_focus_set(punit);
     close_city_dialog(pdialog);
   }
 
@@ -2832,7 +2822,7 @@ static gboolean middle_supported_unit_release(GtkGestureClick *gesture, int n_pr
       && NULL != (pcity = game_city_by_number(punit->homecity))
       && NULL != (pdialog = get_city_dialog(pcity))
       && can_client_issue_orders()) {
-    unit_focus_try(punit);
+    unit_focus_set(punit);
     close_city_dialog(pdialog);
   }
 
@@ -2850,7 +2840,7 @@ static gboolean right_unit_release(GtkGestureClick *gesture, int n_press,
 
   if (NULL != punit
       && can_client_issue_orders()) {
-    unit_focus_try(punit);
+    unit_focus_set(punit);
   }
 
   return TRUE;
@@ -2896,11 +2886,8 @@ static void unit_activate_callback(GSimpleAction *action, GVariant *parameter,
     player_unit_by_number(client_player(), GPOINTER_TO_INT(data));
 
   if (NULL != punit) {
-    /* FIXME: If unit is not idle to begin with, we can only request
-     *        idling, and as we have no server reply yet,
-     *        the unit_focus_try() below will fail. */
     request_new_unit_activity(punit, ACTIVITY_IDLE);
-    unit_focus_try(punit);
+    unit_focus_set(punit);
   }
 
   close_citydlg_unit_popover(g_object_get_data(G_OBJECT(action), "dlg"));
@@ -2921,11 +2908,8 @@ static void supported_unit_activate_close_callback(GSimpleAction *action,
     struct city *pcity =
       player_city_by_number(client_player(), punit->homecity);
 
-    /* FIXME: If unit is not idle to begin with, we can only request
-     *        idling, and as we have no server reply yet,
-     *        the unit_focus_try() below will fail. */
     request_new_unit_activity(punit, ACTIVITY_IDLE);
-    unit_focus_try(punit);
+    unit_focus_set(punit);
 
     if (NULL != pcity) {
       struct city_dialog *pdialog = get_city_dialog(pcity);
@@ -2953,11 +2937,8 @@ static void present_unit_activate_close_callback(GSimpleAction *action,
   if (NULL != punit) {
     struct city *pcity = tile_city(unit_tile(punit));
 
-    /* FIXME: If unit is not idle to begin with, we can only request
-     *        idling, and as we have no server reply yet,
-     *        the unit_focus_try() below will fail. */
     request_new_unit_activity(punit, ACTIVITY_IDLE);
-    unit_focus_try(punit);
+    unit_focus_set(punit);
 
     if (NULL != pcity) {
       struct city_dialog *pdialog = get_city_dialog(pcity);
diff --git a/client/gui-gtk-4.0/unitselect.c b/client/gui-gtk-4.0/unitselect.c
index 56be52d41b..c1ded34816 100644
--- a/client/gui-gtk-4.0/unitselect.c
+++ b/client/gui-gtk-4.0/unitselect.c
@@ -1173,11 +1173,8 @@ static void usdlg_cmd_focus_real(GtkTreeView *view)
 
     punit = player_unit_by_number(client_player(), uid);
     if (punit && unit_owner(punit) == client_player()) {
-      /* FIXME: If unit is not idle to begin with, we can only request
-       *        idling, and as we have no server reply yet,
-       *        the unit_focus_try() below will fail. */
       request_new_unit_activity(punit, ACTIVITY_IDLE);
-      unit_focus_try(punit);
+      unit_focus_set(punit);
       usdlg_destroy();
     }
   }
diff --git a/client/gui-gtk-5.0/citydlg.c b/client/gui-gtk-5.0/citydlg.c
index e12faddb13..ffad80dec8 100644
--- a/client/gui-gtk-5.0/citydlg.c
+++ b/client/gui-gtk-5.0/citydlg.c
@@ -3087,7 +3087,7 @@ static gboolean middle_present_unit_release(GtkGestureClick *gesture,
       && NULL != (pcity = tile_city(unit_tile(punit)))
       && NULL != (pdialog = get_city_dialog(pcity))
       && can_client_issue_orders()) {
-    unit_focus_try(punit);
+    unit_focus_set(punit);
     close_city_dialog(pdialog);
   }
 
@@ -3110,7 +3110,7 @@ static gboolean middle_supported_unit_release(GtkGestureClick *gesture, int n_pr
       && NULL != (pcity = game_city_by_number(punit->homecity))
       && NULL != (pdialog = get_city_dialog(pcity))
       && can_client_issue_orders()) {
-    unit_focus_try(punit);
+    unit_focus_set(punit);
     close_city_dialog(pdialog);
   }
 
@@ -3128,7 +3128,7 @@ static gboolean right_unit_release(GtkGestureClick *gesture, int n_press,
 
   if (NULL != punit
       && can_client_issue_orders()) {
-    unit_focus_try(punit);
+    unit_focus_set(punit);
   }
 
   return TRUE;
@@ -3174,11 +3174,8 @@ static void unit_activate_callback(GSimpleAction *action, GVariant *parameter,
     player_unit_by_number(client_player(), GPOINTER_TO_INT(data));
 
   if (NULL != punit) {
-    /* FIXME: If unit is not idle to begin with, we can only request
-     *        idling, and as we have no server reply yet,
-     *        the unit_focus_try() below will fail. */
     request_new_unit_activity(punit, ACTIVITY_IDLE);
-    unit_focus_try(punit);
+    unit_focus_set(punit);
   }
 
   close_citydlg_unit_popover(g_object_get_data(G_OBJECT(action), "dlg"));
@@ -3199,11 +3196,8 @@ static void supported_unit_activate_close_callback(GSimpleAction *action,
     struct city *pcity =
       player_city_by_number(client_player(), punit->homecity);
 
-    /* FIXME: If unit is not idle to begin with, we can only request
-     *        idling, and as we have no server reply yet,
-     *        the unit_focus_try() below will fail. */
     request_new_unit_activity(punit, ACTIVITY_IDLE);
-    unit_focus_try(punit);
+    unit_focus_set(punit);
 
     if (NULL != pcity) {
       struct city_dialog *pdialog = get_city_dialog(pcity);
@@ -3231,11 +3225,8 @@ static void present_unit_activate_close_callback(GSimpleAction *action,
   if (NULL != punit) {
     struct city *pcity = tile_city(unit_tile(punit));
 
-    /* FIXME: If unit is not idle to begin with, we can only request
-     *        idling, and as we have no server reply yet,
-     *        the unit_focus_try() below will fail. */
     request_new_unit_activity(punit, ACTIVITY_IDLE);
-    unit_focus_try(punit);
+    unit_focus_set(punit);
 
     if (NULL != pcity) {
       struct city_dialog *pdialog = get_city_dialog(pcity);
diff --git a/client/gui-gtk-5.0/unitselect.c b/client/gui-gtk-5.0/unitselect.c
index 1804d7fc5d..ae436196c9 100644
--- a/client/gui-gtk-5.0/unitselect.c
+++ b/client/gui-gtk-5.0/unitselect.c
@@ -1173,11 +1173,8 @@ static void usdlg_cmd_focus_real(GtkTreeView *view)
 
     punit = player_unit_by_number(client_player(), uid);
     if (punit && unit_owner(punit) == client_player()) {
-      /* FIXME: If unit is not idle to begin with, we can only request
-       *        idling, and as we have no server reply yet,
-       *        the unit_focus_try() below will fail. */
       request_new_unit_activity(punit, ACTIVITY_IDLE);
-      unit_focus_try(punit);
+      unit_focus_set(punit);
       usdlg_destroy();
     }
   }
-- 
2.47.3

