Replaced deprecated gtk_menu_popup() calls with modern constructs in gtk3.22-client
[freeciv.git] / client / gui-gtk-3.0 / gotodlg.c
blob668ebeeaa17fda02d8505daff986b20fb74ecb16
1 /***********************************************************************
2 Freeciv - Copyright (C) 1996 - A Kjeldberg, L Gregersen, P Unold
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; either version 2, or (at your option)
6 any later version.
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12 ***********************************************************************/
14 #ifdef HAVE_CONFIG_H
15 #include <fc_config.h>
16 #endif
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
22 #include <gtk/gtk.h>
23 #include <gdk/gdkkeysyms.h>
25 /* utility */
26 #include "astring.h"
27 #include "fcintl.h"
28 #include "log.h"
29 #include "support.h"
31 /* common */
32 #include "game.h"
33 #include "map.h"
34 #include "packets.h"
35 #include "player.h"
36 #include "unit.h"
37 #include "unitlist.h"
39 /* client */
40 #include "client_main.h"
41 #include "control.h"
42 #include "goto.h"
43 #include "options.h"
44 #include "text.h"
46 /* clien/gui-gtk-3.0 */
47 #include "plrdlg.h"
48 #include "dialogs.h"
49 #include "gui_main.h"
50 #include "gui_stuff.h"
51 #include "mapview.h"
53 #include "gotodlg.h"
56 static GtkWidget *dshell = NULL;
57 static GtkWidget *view;
58 static GtkWidget *source;
59 static GtkWidget *all_toggle;
60 static GtkListStore *goto_list_store;
61 static GtkTreeSelection *goto_list_selection;
62 struct tile *original_tile;
63 static bool gotodlg_updating = FALSE;
65 static void update_goto_dialog(GtkToggleButton *button);
66 static void update_source_label(void);
67 static void refresh_airlift_column(void);
68 static void refresh_airlift_button(void);
69 static void goto_selection_callback(GtkTreeSelection *selection, gpointer data);
71 static struct city *get_selected_city(void);
73 enum {
74 CMD_AIRLIFT = 1, CMD_GOTO
77 enum {
78 GD_COL_CITY_ID = 0, /* Not shown if not compiled with --enable-debug. */
79 GD_COL_CITY_NAME,
80 GD_COL_FLAG,
81 GD_COL_NATION,
82 GD_COL_AIRLIFT,
84 GD_COL_NUM
87 /**************************************************************************
88 User has responded to goto dialog
89 **************************************************************************/
90 static void goto_cmd_callback(GtkWidget *dlg, gint arg)
92 switch (arg) {
93 case GTK_RESPONSE_CANCEL:
94 center_tile_mapcanvas(original_tile);
95 break;
97 case CMD_AIRLIFT:
99 struct city *pdestcity = get_selected_city();
101 if (pdestcity) {
102 unit_list_iterate(get_units_in_focus(), punit) {
103 if (unit_can_airlift_to(punit, pdestcity)) {
104 request_unit_airlift(punit, pdestcity);
106 } unit_list_iterate_end;
109 break;
111 case CMD_GOTO:
113 struct city *pdestcity = get_selected_city();
115 if (pdestcity) {
116 unit_list_iterate(get_units_in_focus(), punit) {
117 send_goto_tile(punit, pdestcity->tile);
118 } unit_list_iterate_end;
121 break;
123 default:
124 break;
127 gtk_widget_destroy(dlg);
128 dshell = NULL;
132 /**************************************************************************
133 Create goto -dialog for gotoing or airlifting unit
134 **************************************************************************/
135 static void create_goto_dialog(void)
137 GtkWidget *sw, *label, *frame, *vbox;
138 GtkCellRenderer *rend;
139 GtkTreeViewColumn *col;
141 dshell = gtk_dialog_new_with_buttons(_("Goto/Airlift Unit"),
142 NULL,
144 GTK_STOCK_CANCEL,
145 GTK_RESPONSE_CANCEL,
146 _("Air_lift"),
147 CMD_AIRLIFT,
148 _("_Goto"),
149 CMD_GOTO,
150 NULL);
151 setup_dialog(dshell, toplevel);
152 gtk_window_set_position(GTK_WINDOW(dshell), GTK_WIN_POS_MOUSE);
153 gtk_dialog_set_default_response(GTK_DIALOG(dshell), CMD_GOTO);
154 g_signal_connect(dshell, "destroy",
155 G_CALLBACK(gtk_widget_destroyed), &dshell);
156 g_signal_connect(dshell, "response",
157 G_CALLBACK(goto_cmd_callback), NULL);
159 source = gtk_label_new("" /* filled in later */);
160 gtk_label_set_line_wrap(GTK_LABEL(source), TRUE);
161 gtk_label_set_justify(GTK_LABEL(source), GTK_JUSTIFY_CENTER);
162 gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dshell))),
163 source, FALSE, FALSE, 0);
165 label = g_object_new(GTK_TYPE_LABEL,
166 "use-underline", TRUE,
167 "label", _("Select destination ci_ty"),
168 "xalign", 0.0,
169 "yalign", 0.5,
170 NULL);
171 frame = gtk_frame_new("");
172 gtk_frame_set_label_widget(GTK_FRAME(frame), label);
173 gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dshell))),
174 frame, TRUE, TRUE, 0);
176 vbox = gtk_grid_new();
177 gtk_orientable_set_orientation(GTK_ORIENTABLE(vbox),
178 GTK_ORIENTATION_VERTICAL);
179 gtk_grid_set_row_spacing(GTK_GRID(vbox), 6);
180 gtk_container_add(GTK_CONTAINER(frame), vbox);
182 goto_list_store = gtk_list_store_new(GD_COL_NUM, G_TYPE_INT, G_TYPE_STRING,
183 GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING);
184 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(goto_list_store),
185 GD_COL_CITY_NAME, GTK_SORT_ASCENDING);
187 view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(goto_list_store));
188 gtk_widget_set_hexpand(view, TRUE);
189 gtk_widget_set_vexpand(view, TRUE);
190 g_object_unref(goto_list_store);
191 goto_list_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
192 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), TRUE);
193 gtk_tree_view_set_search_column(GTK_TREE_VIEW(view), GD_COL_CITY_NAME);
194 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(view), TRUE);
196 /* Set the mnemonic in the frame label to focus the city list */
197 gtk_label_set_mnemonic_widget(GTK_LABEL(label), view);
199 #ifdef DEBUG
200 rend = gtk_cell_renderer_text_new();
201 col = gtk_tree_view_column_new_with_attributes(_("Id"), rend,
202 "text", GD_COL_CITY_ID, NULL);
203 gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
204 gtk_tree_view_column_set_sort_column_id(col, GD_COL_CITY_ID);
205 #endif /* DEBUG */
207 rend = gtk_cell_renderer_text_new();
208 col = gtk_tree_view_column_new_with_attributes(_("City"), rend,
209 "text", GD_COL_CITY_NAME, NULL);
210 gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
211 gtk_tree_view_column_set_sort_column_id(col, GD_COL_CITY_NAME);
213 rend = gtk_cell_renderer_pixbuf_new();
214 col = gtk_tree_view_column_new_with_attributes(NULL, rend,
215 "pixbuf", GD_COL_FLAG, NULL);
216 gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
218 rend = gtk_cell_renderer_text_new();
219 col = gtk_tree_view_column_new_with_attributes(_("Nation"), rend,
220 "text", GD_COL_NATION, NULL);
221 gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
222 gtk_tree_view_column_set_sort_column_id(col, GD_COL_NATION);
224 rend = gtk_cell_renderer_text_new();
225 col = gtk_tree_view_column_new_with_attributes(_("Airlift"), rend,
226 "text", GD_COL_AIRLIFT, NULL);
227 gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
228 gtk_tree_view_column_set_sort_column_id(col, GD_COL_AIRLIFT);
230 sw = gtk_scrolled_window_new(NULL, NULL);
231 gtk_container_add(GTK_CONTAINER(sw), view);
232 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
233 GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
234 gtk_scrolled_window_set_min_content_height(GTK_SCROLLED_WINDOW(sw), 200);
236 gtk_container_add(GTK_CONTAINER(vbox), sw);
238 all_toggle = gtk_check_button_new_with_mnemonic(_("Show _All Cities"));
239 gtk_container_add(GTK_CONTAINER(vbox), all_toggle);
241 g_signal_connect(all_toggle, "toggled", G_CALLBACK(update_goto_dialog), NULL);
243 g_signal_connect(goto_list_selection, "changed",
244 G_CALLBACK(goto_selection_callback), NULL);
246 gtk_widget_show_all(dshell);
248 original_tile = get_center_tile_mapcanvas();
250 update_source_label();
251 update_goto_dialog(GTK_TOGGLE_BUTTON(all_toggle));
252 gtk_tree_view_focus(GTK_TREE_VIEW(view));
255 /****************************************************************
256 popup the dialog
257 *****************************************************************/
258 void popup_goto_dialog(void)
260 if (!can_client_issue_orders() || get_num_units_in_focus() == 0) {
261 return;
264 if (!dshell) {
265 create_goto_dialog();
268 gtk_window_present(GTK_WINDOW(dshell));
271 /**************************************************************************
272 Return currently selected city
273 **************************************************************************/
274 static struct city *get_selected_city(void)
276 GtkTreeModel *model;
277 GtkTreeIter it;
278 int city_id;
280 if (!gtk_tree_selection_get_selected(goto_list_selection, NULL, &it)) {
281 return NULL;
284 model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
286 gtk_tree_model_get(model, &it, GD_COL_CITY_ID, &city_id, -1);
288 return game_city_by_number(city_id);
291 /**************************************************************************
292 Appends the list of the city owned by the player in the goto dialog.
293 **************************************************************************/
294 static bool list_store_append_player_cities(GtkListStore *store,
295 const struct player *pplayer)
297 GtkTreeIter it;
298 struct nation_type *pnation = nation_of_player(pplayer);
299 const char *nation = nation_adjective_translation(pnation);
300 GdkPixbuf *pixbuf;
302 if (city_list_size(pplayer->cities) == 0) {
303 return FALSE;
306 pixbuf = get_flag(pnation);
308 city_list_iterate(pplayer->cities, pcity) {
309 gtk_list_store_append(store, &it);
310 gtk_list_store_set(store, &it,
311 GD_COL_CITY_ID, pcity->id,
312 GD_COL_CITY_NAME, city_name_get(pcity),
313 GD_COL_FLAG, pixbuf,
314 GD_COL_NATION, nation,
315 /* GD_COL_AIRLIFT is populated later */
316 -1);
317 } city_list_iterate_end;
318 g_object_unref(pixbuf);
320 return TRUE;
323 /**************************************************************************
324 Refresh the label that shows where the selected unit(s) currently are
325 (and the relevant cities' airlift capacities, if relevant).
326 **************************************************************************/
327 static void update_source_label(void)
329 /* Arbitrary limit to stop the label getting ridiculously long */
330 static const int max_cities = 10;
331 struct {
332 const struct city *city;
333 struct unit_list *units;
334 } cities[max_cities];
335 int ncities = 0;
336 bool too_many = FALSE;
337 bool no_city = FALSE; /* any units not in a city? */
338 struct astring strs[max_cities];
339 int nstrs;
340 char *last_str;
341 const char *descriptions[max_cities+1];
342 int i;
344 /* Sanity check: if no units selected, give up */
345 if (unit_list_size(get_units_in_focus()) == 0) {
346 gtk_label_set_text(GTK_LABEL(source), _("No units selected."));
347 return;
350 /* Divide selected units up into a list of unique cities */
351 unit_list_iterate(get_units_in_focus(), punit) {
352 const struct city *pcity = tile_city(unit_tile(punit));
353 if (pcity) {
354 /* Inefficient, but it's not a long list */
355 for (i = 0; i < ncities; i++) {
356 if (cities[i].city == pcity) {
357 unit_list_append(cities[i].units, punit);
358 break;
361 if (i == ncities) {
362 if (ncities < max_cities) {
363 cities[ncities].city = pcity;
364 cities[ncities].units = unit_list_new();
365 unit_list_append(cities[ncities].units, punit);
366 ncities++;
367 } else {
368 too_many = TRUE;
369 break;
372 } else {
373 no_city = TRUE;
375 } unit_list_iterate_end;
377 /* Describe the individual cities. */
378 for (i = 0; i < ncities; i++) {
379 const char *air_text = get_airlift_text(cities[i].units, NULL);
381 astr_init(&strs[i]);
382 if (air_text != NULL) {
383 astr_add(&strs[i],
384 /* TRANS: goto/airlift dialog. "Paris (airlift: 2/4)".
385 * A set of these appear in an "and"-separated list. */
386 _("%s (airlift: %s)"),
387 city_name_get(cities[i].city), air_text);
388 } else {
389 astr_add(&strs[i], "%s", city_name_get(cities[i].city));
391 descriptions[i] = astr_str(&strs[i]);
392 unit_list_destroy(cities[i].units);
394 if (too_many) {
395 /* TRANS: goto/airlift dialog. Too many cities to list, some omitted.
396 * Appears at the end of an "and"-separated list. */
397 descriptions[ncities] = last_str = fc_strdup(Q_("?gotodlg:more"));
398 nstrs = ncities+1;
399 } else if (no_city) {
400 /* TRANS: goto/airlift dialog. For units not currently in a city.
401 * Appears at the end of an "and"-separated list. */
402 descriptions[ncities] = last_str = fc_strdup(Q_("?gotodlg:no city"));
403 nstrs = ncities+1;
404 } else {
405 last_str = NULL;
406 nstrs = ncities;
409 /* Finally, update the label. */
411 struct astring label = ASTRING_INIT, list = ASTRING_INIT;
412 astr_set(&label,
413 /* TRANS: goto/airlift dialog. Current location of units; %s is an
414 * "and"-separated list of cities and associated info */
415 _("Currently in: %s"),
416 astr_build_and_list(&list, descriptions, nstrs));
417 astr_free(&list);
418 gtk_label_set_text(GTK_LABEL(source), astr_str(&label));
419 astr_free(&label);
422 /* Clear up. */
423 for (i = 0; i < ncities; i++) {
424 astr_free(&strs[i]);
426 free(last_str); /* might have been NULL */
429 /**************************************************************************
430 Refresh city list (in response to "all cities" checkbox changing).
431 **************************************************************************/
432 static void update_goto_dialog(GtkToggleButton *button)
434 bool nonempty = FALSE;
436 if (!client_has_player()) {
437 /* Case global observer. */
438 return;
441 gotodlg_updating = TRUE;
443 gtk_list_store_clear(goto_list_store);
445 if (gtk_toggle_button_get_active(button)) {
446 players_iterate(pplayer) {
447 nonempty |= list_store_append_player_cities(goto_list_store, pplayer);
448 } players_iterate_end;
449 } else {
450 nonempty |= list_store_append_player_cities(goto_list_store, client_player());
453 gotodlg_updating = FALSE;
455 refresh_airlift_column();
457 if (!nonempty) {
458 /* No selection causes callbacks to fire, causing also Airlift button
459 * to update. Do it here. */
460 refresh_airlift_button();
464 /**************************************************************************
465 Refresh airlift column in city list (without tearing everything down).
466 **************************************************************************/
467 static void refresh_airlift_column(void)
469 GtkTreeIter iter;
470 bool valid;
472 valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(goto_list_store), &iter);
473 while (valid) {
474 int city_id;
475 const struct city *pcity;
476 const char *air_text;
478 gtk_tree_model_get(GTK_TREE_MODEL(goto_list_store), &iter,
479 GD_COL_CITY_ID, &city_id, -1);
480 pcity = game_city_by_number(city_id);
481 fc_assert_ret(pcity != NULL);
482 air_text = get_airlift_text(get_units_in_focus(), pcity);
483 gtk_list_store_set(GTK_LIST_STORE(goto_list_store), &iter,
484 GD_COL_AIRLIFT, air_text ? air_text : "-", -1);
485 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(goto_list_store), &iter);
489 /**************************************************************************
490 Refresh the state of the "Airlift" button for the currently selected
491 unit(s) and city.
492 **************************************************************************/
493 static void refresh_airlift_button(void)
495 struct city *pdestcity = get_selected_city();
497 if (NULL != pdestcity) {
498 bool can_airlift = FALSE;
500 /* Allow action if any of the selected units can airlift. */
501 unit_list_iterate(get_units_in_focus(), punit) {
502 if (unit_can_airlift_to(punit, pdestcity)) {
503 can_airlift = TRUE;
504 break;
506 } unit_list_iterate_end;
508 if (can_airlift) {
509 gtk_dialog_set_response_sensitive(GTK_DIALOG(dshell),
510 CMD_AIRLIFT, TRUE);
511 return;
514 gtk_dialog_set_response_sensitive(GTK_DIALOG(dshell), CMD_AIRLIFT, FALSE);
517 /**************************************************************************
518 Update goto dialog. button tells if cities of all players or just
519 client's player should be listed.
520 **************************************************************************/
521 static void goto_selection_callback(GtkTreeSelection *selection,
522 gpointer data)
524 struct city *pdestcity;
526 if (gotodlg_updating) {
527 return;
530 pdestcity = get_selected_city();
532 if (NULL != pdestcity) {
533 center_tile_mapcanvas(city_tile(pdestcity));
535 refresh_airlift_button();
538 /**************************************************************************
539 Called when the set of units in focus has changed; updates airlift info
540 **************************************************************************/
541 void goto_dialog_focus_units_changed(void)
543 /* Is the dialog currently being displayed? */
544 if (dshell) {
545 /* Location of current units and ability to airlift may have changed */
546 update_source_label();
547 refresh_airlift_column();
548 refresh_airlift_button();