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)
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 ***********************************************************************/
15 #include <fc_config.h>
23 #include <gdk/gdkkeysyms.h>
40 #include "client_main.h"
46 /* clien/gui-gtk-4.0 */
50 #include "gui_stuff.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);
74 CMD_AIRLIFT
= 1, CMD_GOTO
78 GD_COL_CITY_ID
= 0, /* Not shown if not compiled with --enable-debug. */
87 /**************************************************************************
88 User has responded to goto dialog
89 **************************************************************************/
90 static void goto_cmd_callback(GtkWidget
*dlg
, gint arg
)
93 case GTK_RESPONSE_CANCEL
:
94 center_tile_mapcanvas(original_tile
);
99 struct city
*pdestcity
= get_selected_city();
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
;
113 struct city
*pdestcity
= get_selected_city();
116 unit_list_iterate(get_units_in_focus(), punit
) {
117 send_goto_tile(punit
, pdestcity
->tile
);
118 } unit_list_iterate_end
;
127 gtk_widget_destroy(dlg
);
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"),
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
);
165 label
= g_object_new(GTK_TYPE_LABEL
,
166 "use-underline", TRUE
,
167 "label", _("Select destination ci_ty"),
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
))),
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
);
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 /* FREECIV_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(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 /****************************************************************
257 *****************************************************************/
258 void popup_goto_dialog(void)
260 if (!can_client_issue_orders() || get_num_units_in_focus() == 0) {
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)
280 if (!gtk_tree_selection_get_selected(goto_list_selection
, NULL
, &it
)) {
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
)
298 struct nation_type
*pnation
= nation_of_player(pplayer
);
299 const char *nation
= nation_adjective_translation(pnation
);
302 if (city_list_size(pplayer
->cities
) == 0) {
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
),
314 GD_COL_NATION
, nation
,
315 /* GD_COL_AIRLIFT is populated later */
317 } city_list_iterate_end
;
318 g_object_unref(pixbuf
);
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;
332 const struct city
*city
;
333 struct unit_list
*units
;
334 } cities
[max_cities
];
336 bool too_many
= FALSE
;
337 bool no_city
= FALSE
; /* any units not in a city? */
338 struct astring strs
[max_cities
];
341 const char *descriptions
[max_cities
+1];
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."));
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
));
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
);
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
);
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
);
382 if (air_text
!= NULL
) {
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
);
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
);
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"));
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"));
409 /* Finally, update the label. */
411 struct astring label
= ASTRING_INIT
, list
= ASTRING_INIT
;
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
));
418 gtk_label_set_text(GTK_LABEL(source
), astr_str(&label
));
423 for (i
= 0; i
< ncities
; 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. */
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
;
450 nonempty
|= list_store_append_player_cities(goto_list_store
, client_player());
453 gotodlg_updating
= FALSE
;
455 refresh_airlift_column();
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)
472 valid
= gtk_tree_model_get_iter_first(GTK_TREE_MODEL(goto_list_store
), &iter
);
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
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
)) {
506 } unit_list_iterate_end
;
509 gtk_dialog_set_response_sensitive(GTK_DIALOG(dshell
),
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
,
524 struct city
*pdestcity
;
526 if (gotodlg_updating
) {
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? */
545 /* Location of current units and ability to airlift may have changed */
546 update_source_label();
547 refresh_airlift_column();
548 refresh_airlift_button();