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.
7
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 ***********************************************************************/
13
14 #ifdef HAVE_CONFIG_H
15 #include <fc_config.h>
16 #endif
17
18 #include <stdarg.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22
23 #include <gtk/gtk.h>
24 #include <gdk/gdkkeysyms.h>
25
26 /* utility */
27 #include "fcintl.h"
28 #include "log.h"
29 #include "mem.h"
30 #include "support.h"
31
32 /* client */
33 #include "options.h"
34
35 /* client/gui-gtk-3.22 */
36 #include "colors.h"
37 #include "gui_main.h"
38
39 #include "gui_stuff.h"
40
41
42 static GList *dialog_list;
43
44 static GtkSizeGroup *gui_action;
45
46 static GtkCssProvider *dlg_tab_provider = NULL;
47
48
49 /**************************************************************************
50 Draw widget now
51 **************************************************************************/
gtk_expose_now(GtkWidget * w)52 void gtk_expose_now(GtkWidget *w)
53 {
54 gtk_widget_queue_draw(w);
55 }
56
57 /**************************************************************************
58 Set window position relative to reference window
59 **************************************************************************/
set_relative_window_position(GtkWindow * ref,GtkWindow * w,int px,int py)60 void set_relative_window_position(GtkWindow *ref, GtkWindow *w, int px, int py)
61 {
62 gint x, y, width, height;
63
64 gtk_window_get_position(ref, &x, &y);
65 gtk_window_get_size(ref, &width, &height);
66
67 x += px * width / 100;
68 y += py * height / 100;
69
70 gtk_window_move(w, x, y);
71 }
72
73 /**************************************************************************
74 Create new icon button with label
75 **************************************************************************/
icon_label_button_new(const gchar * icon_name,const gchar * label_text)76 GtkWidget *icon_label_button_new(const gchar *icon_name,
77 const gchar *label_text)
78 {
79 GtkWidget *button;
80 GtkWidget *image;
81
82 button = gtk_button_new_with_mnemonic(label_text);
83
84 if (icon_name != NULL) {
85 image = gtk_image_new_from_icon_name(icon_name, GTK_ICON_SIZE_BUTTON);
86 gtk_button_set_image(GTK_BUTTON(button), image);
87 }
88
89 return button;
90 }
91
92 /**************************************************************************
93 Changes the label (with mnemonic) on an existing stockbutton. See
94 gtk_stockbutton_new.
95 **************************************************************************/
gtk_stockbutton_set_label(GtkWidget * button,const gchar * label_text)96 void gtk_stockbutton_set_label(GtkWidget *button, const gchar *label_text)
97 {
98 gtk_button_set_label(GTK_BUTTON(button), label_text);
99 }
100
101 /**************************************************************************
102 Returns gettext-converted list of n strings. The individual strings
103 in the list are as returned by gettext(). In case of no NLS, the strings
104 will be the original strings, so caller should ensure that the originals
105 persist for as long as required. (For no NLS, still allocate the
106 list, for consistency.)
107
108 (This is not directly gui/gtk related, but it fits in here
109 because so far it is used for doing i18n for gtk titles...)
110 **************************************************************************/
intl_slist(int n,const char ** s,bool * done)111 void intl_slist(int n, const char **s, bool *done)
112 {
113 int i;
114
115 if (!*done) {
116 for(i=0; i<n; i++) {
117 s[i] = Q_(s[i]);
118 }
119
120 *done = TRUE;
121 }
122 }
123
124 /****************************************************************
125 Set itree to the beginning
126 *****************************************************************/
itree_begin(GtkTreeModel * model,ITree * it)127 void itree_begin(GtkTreeModel *model, ITree *it)
128 {
129 it->model = model;
130 it->end = !gtk_tree_model_get_iter_first(it->model, &it->it);
131 }
132
133 /****************************************************************
134 Return whether itree end has been reached
135 *****************************************************************/
itree_end(ITree * it)136 gboolean itree_end(ITree *it)
137 {
138 return it->end;
139 }
140
141 /****************************************************************
142 Make itree to go forward one step
143 *****************************************************************/
itree_next(ITree * it)144 void itree_next(ITree *it)
145 {
146 it->end = !gtk_tree_model_iter_next(it->model, &it->it);
147 }
148
149 /****************************************************************
150 Store values to itree
151 *****************************************************************/
itree_set(ITree * it,...)152 void itree_set(ITree *it, ...)
153 {
154 va_list ap;
155
156 va_start(ap, it);
157 gtk_tree_store_set_valist(GTK_TREE_STORE(it->model), &it->it, ap);
158 va_end(ap);
159 }
160
161 /****************************************************************
162 Get values from itree
163 *****************************************************************/
itree_get(ITree * it,...)164 void itree_get(ITree *it, ...)
165 {
166 va_list ap;
167
168 va_start(ap, it);
169 gtk_tree_model_get_valist(it->model, &it->it, ap);
170 va_end(ap);
171 }
172
173 /****************************************************************
174 Append one item to the end of tree store
175 *****************************************************************/
tstore_append(GtkTreeStore * store,ITree * it,ITree * parent)176 void tstore_append(GtkTreeStore *store, ITree *it, ITree *parent)
177 {
178 it->model = GTK_TREE_MODEL(store);
179 if (parent)
180 gtk_tree_store_append(GTK_TREE_STORE(it->model), &it->it, &parent->it);
181 else
182 gtk_tree_store_append(GTK_TREE_STORE(it->model), &it->it, NULL);
183 it->end = FALSE;
184 }
185
186 /****************************************************************
187 Return whether current itree item is selected
188 *****************************************************************/
itree_is_selected(GtkTreeSelection * selection,ITree * it)189 gboolean itree_is_selected(GtkTreeSelection *selection, ITree *it)
190 {
191 return gtk_tree_selection_iter_is_selected(selection, &it->it);
192 }
193
194 /****************************************************************
195 Add current itree item to selection
196 *****************************************************************/
itree_select(GtkTreeSelection * selection,ITree * it)197 void itree_select(GtkTreeSelection *selection, ITree *it)
198 {
199 gtk_tree_selection_select_iter(selection, &it->it);
200 }
201
202 /****************************************************************
203 Remove current itree item from selection
204 *****************************************************************/
itree_unselect(GtkTreeSelection * selection,ITree * it)205 void itree_unselect(GtkTreeSelection *selection, ITree *it)
206 {
207 gtk_tree_selection_unselect_iter(selection, &it->it);
208 }
209
210 /**************************************************************************
211 Return the selected row in a GtkTreeSelection.
212 If no row is selected return -1.
213 **************************************************************************/
gtk_tree_selection_get_row(GtkTreeSelection * selection)214 gint gtk_tree_selection_get_row(GtkTreeSelection *selection)
215 {
216 GtkTreeModel *model;
217 GtkTreeIter it;
218 gint row = -1;
219
220 if (gtk_tree_selection_get_selected(selection, &model, &it)) {
221 GtkTreePath *path;
222 gint *idx;
223
224 path = gtk_tree_model_get_path(model, &it);
225 idx = gtk_tree_path_get_indices(path);
226 row = idx[0];
227 gtk_tree_path_free(path);
228 }
229 return row;
230 }
231
232 /**************************************************************************
233 Give focus to view
234 **************************************************************************/
gtk_tree_view_focus(GtkTreeView * view)235 void gtk_tree_view_focus(GtkTreeView *view)
236 {
237 GtkTreeModel *model;
238 GtkTreePath *path;
239 GtkTreeIter iter;
240
241 if ((model = gtk_tree_view_get_model(view))
242 && gtk_tree_model_get_iter_first(model, &iter)
243 && (path = gtk_tree_model_get_path(model, &iter))) {
244 gtk_tree_view_set_cursor(view, path, NULL, FALSE);
245 gtk_tree_path_free(path);
246 gtk_widget_grab_focus(GTK_WIDGET(view));
247 }
248 }
249
250 /**************************************************************************
251 Create an auxiliary menubar (i.e., not the main menubar at the top of
252 the window).
253 **************************************************************************/
gtk_aux_menu_bar_new(void)254 GtkWidget *gtk_aux_menu_bar_new(void) {
255 GtkWidget *menubar = gtk_menu_bar_new();
256
257 /*
258 * Ubuntu Linux's Ayatana/Unity desktop environment likes to steal the
259 * application's main menu bar from its window and put it at the top of
260 * the screen. It needs a hint in order not to steal menu bars other
261 * than the main one. Gory details at
262 * https://bugs.launchpad.net/ubuntu/+source/freeciv/+bug/743265
263 */
264 if (g_object_class_find_property(
265 G_OBJECT_CLASS(GTK_MENU_BAR_GET_CLASS(menubar)), "ubuntu-local")) {
266 g_object_set(G_OBJECT(menubar), "ubuntu-local", TRUE, NULL);
267 }
268
269 return menubar;
270 }
271
272 /**************************************************************************
273 Generic close callback for all widgets
274 **************************************************************************/
close_callback(GtkDialog * dialog,gpointer data)275 static void close_callback(GtkDialog *dialog, gpointer data)
276 {
277 gtk_widget_destroy(GTK_WIDGET(dialog));
278 }
279
280 /**********************************************************************
281 This function handles new windows which are subwindows to the
282 toplevel window. It must be called on every dialog in the game,
283 so fullscreen windows are handled properly by the window manager.
284 ***********************************************************************/
setup_dialog(GtkWidget * shell,GtkWidget * parent)285 void setup_dialog(GtkWidget *shell, GtkWidget *parent)
286 {
287 if (GUI_GTK_OPTION(dialogs_on_top) || GUI_GTK_OPTION(fullscreen)) {
288 gtk_window_set_transient_for(GTK_WINDOW(shell),
289 GTK_WINDOW(parent));
290 gtk_window_set_type_hint(GTK_WINDOW(shell),
291 GDK_WINDOW_TYPE_HINT_DIALOG);
292 } else {
293 gtk_window_set_type_hint(GTK_WINDOW(shell),
294 GDK_WINDOW_TYPE_HINT_NORMAL);
295 }
296
297 /* Close dialog window on Escape keypress. */
298 if (GTK_IS_DIALOG(shell)) {
299 g_signal_connect_after(shell, "close", G_CALLBACK(close_callback), shell);
300 }
301 }
302
303 /**************************************************************************
304 Emit a dialog response.
305 **************************************************************************/
gui_dialog_response(struct gui_dialog * dlg,int response)306 static void gui_dialog_response(struct gui_dialog *dlg, int response)
307 {
308 if (dlg->response_callback) {
309 (*dlg->response_callback)(dlg, response, dlg->user_data);
310 }
311 }
312
313 /**************************************************************************
314 Default dialog response handler. Destroys the dialog.
315 **************************************************************************/
gui_dialog_destroyed(struct gui_dialog * dlg,int response,gpointer data)316 static void gui_dialog_destroyed(struct gui_dialog *dlg, int response,
317 gpointer data)
318 {
319 gui_dialog_destroy(dlg);
320 }
321
322 /**************************************************************************
323 Cleanups the leftovers after a dialog is destroyed.
324 **************************************************************************/
gui_dialog_destroy_handler(GtkWidget * w,struct gui_dialog * dlg)325 static void gui_dialog_destroy_handler(GtkWidget *w, struct gui_dialog *dlg)
326 {
327 if (dlg->type == GUI_DIALOG_TAB) {
328 GtkWidget *notebook = dlg->v.tab.notebook;
329 gulong handler_id = dlg->v.tab.handler_id;
330
331 g_signal_handler_disconnect(notebook, handler_id);
332 }
333
334 g_object_unref(dlg->gui_button);
335
336 if (*(dlg->source)) {
337 *(dlg->source) = NULL;
338 }
339
340 dialog_list = g_list_remove(dialog_list, dlg);
341
342 /* Raise the return dialog set by gui_dialog_set_return_dialog() */
343 if (dlg->return_dialog_id != -1) {
344 GList *it;
345 for (it = dialog_list; it; it = g_list_next(it)) {
346 struct gui_dialog * adialog = (struct gui_dialog *)it->data;
347 if (adialog->id == dlg->return_dialog_id) {
348 gui_dialog_raise(adialog);
349 break;
350 }
351 }
352 }
353
354 if (dlg->title) {
355 free(dlg->title);
356 }
357
358 free(dlg);
359 }
360
361 /**************************************************************************
362 Emit a delete event response on dialog deletion in case the end-user
363 needs to know when a deletion took place.
364 Popup dialog version
365 **************************************************************************/
gui_dialog_delete_handler(GtkWidget * widget,GdkEventAny * ev,gpointer data)366 static gint gui_dialog_delete_handler(GtkWidget *widget,
367 GdkEventAny *ev, gpointer data)
368 {
369 struct gui_dialog *dlg = data;
370
371 /* emit response signal. */
372 gui_dialog_response(dlg, GTK_RESPONSE_DELETE_EVENT);
373
374 /* do the destroy by default. */
375 return FALSE;
376 }
377
378 /**************************************************************************
379 Emit a delete event response on dialog deletion in case the end-user
380 needs to know when a deletion took place.
381 TAB version
382 **************************************************************************/
gui_dialog_delete_tab_handler(struct gui_dialog * dlg)383 static gint gui_dialog_delete_tab_handler(struct gui_dialog* dlg)
384 {
385 GtkWidget* notebook;
386 int n;
387
388 notebook = dlg->v.tab.notebook;
389 n = gtk_notebook_get_current_page(GTK_NOTEBOOK(notebook));
390 if (gtk_notebook_get_nth_page(GTK_NOTEBOOK(notebook), n)
391 != dlg->v.tab.child) {
392 gui_dialog_set_return_dialog(dlg, NULL);
393 }
394
395 /* emit response signal. */
396 gui_dialog_response(dlg, GTK_RESPONSE_DELETE_EVENT);
397
398 /* do the destroy by default. */
399 return FALSE;
400 }
401
402
403 /**************************************************************************
404 Allow the user to close a dialog using Escape or CTRL+W.
405 **************************************************************************/
gui_dialog_key_press_handler(GtkWidget * w,GdkEventKey * ev,gpointer data)406 static gboolean gui_dialog_key_press_handler(GtkWidget *w, GdkEventKey *ev,
407 gpointer data)
408 {
409 struct gui_dialog *dlg = data;
410
411 if (ev->keyval == GDK_KEY_Escape
412 || ((ev->state & GDK_CONTROL_MASK) && ev->keyval == GDK_KEY_w)) {
413 /* emit response signal. */
414 gui_dialog_response(dlg, GTK_RESPONSE_DELETE_EVENT);
415 }
416
417 /* propagate event further. */
418 return FALSE;
419 }
420
421 /**************************************************************************
422 Resets tab colour on tab activation.
423 **************************************************************************/
gui_dialog_switch_page_handler(GtkNotebook * notebook,GtkWidget * page,guint num,struct gui_dialog * dlg)424 static void gui_dialog_switch_page_handler(GtkNotebook *notebook,
425 GtkWidget *page,
426 guint num,
427 struct gui_dialog *dlg)
428 {
429 gint n;
430
431 n = gtk_notebook_page_num(GTK_NOTEBOOK(dlg->v.tab.notebook), dlg->vbox);
432
433 if (n == num) {
434 GtkStyleContext *context = gtk_widget_get_style_context(dlg->v.tab.label);
435
436 gtk_style_context_remove_class(context, "alert");
437 gtk_style_context_remove_class(context, "notice");
438 }
439 }
440
441 /**************************************************************************
442 Changes a tab into a window.
443 **************************************************************************/
gui_dialog_detach(struct gui_dialog * dlg)444 static void gui_dialog_detach(struct gui_dialog* dlg)
445 {
446 gint n;
447 GtkWidget *window, *notebook;
448 gulong handler_id;
449
450 if (dlg->type != GUI_DIALOG_TAB) {
451 return;
452 }
453 dlg->type = GUI_DIALOG_WINDOW;
454
455 /* Create a new reference to the main widget, so it won't be
456 * destroyed in gtk_notebook_remove_page() */
457 g_object_ref(dlg->vbox);
458
459 /* Remove widget from the notebook */
460 notebook = dlg->v.tab.notebook;
461 handler_id = dlg->v.tab.handler_id;
462 g_signal_handler_disconnect(notebook, handler_id);
463
464 n = gtk_notebook_page_num(GTK_NOTEBOOK(dlg->v.tab.notebook), dlg->vbox);
465 gtk_notebook_remove_page(GTK_NOTEBOOK(dlg->v.tab.notebook), n);
466
467
468 /* Create window and put the widget inside */
469 window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
470 gtk_window_set_title(GTK_WINDOW(window), dlg->title);
471 setup_dialog(window, toplevel);
472
473 gtk_container_add(GTK_CONTAINER(window), dlg->vbox);
474 dlg->v.window = window;
475 g_signal_connect(window, "delete_event",
476 G_CALLBACK(gui_dialog_delete_handler), dlg);
477
478 gtk_window_set_default_size(GTK_WINDOW(dlg->v.window),
479 dlg->default_width,
480 dlg->default_height);
481 gtk_widget_show_all(window);
482 }
483
484 /***************************************************************************
485 Someone has clicked on a label in a notebook
486 ***************************************************************************/
click_on_tab_callback(GtkWidget * w,GdkEventButton * button,gpointer data)487 static gboolean click_on_tab_callback(GtkWidget* w,
488 GdkEventButton* button,
489 gpointer data)
490 {
491 if (button->type != GDK_2BUTTON_PRESS) {
492 return FALSE;
493 }
494 if (button->button != 1) {
495 return FALSE;
496 }
497 gui_dialog_detach((struct gui_dialog*) data);
498 return TRUE;
499 }
500
501
502 /**************************************************************************
503 Creates a new dialog. It will be a tab or a window depending on the
504 current user setting of 'gui_gtk3_enable_tabs'.
505 Sets pdlg to point to the dialog once it is create, Zeroes pdlg on
506 dialog destruction.
507 user_data will be passed through response function
508 check_top indicates if the layout deision should depend on the parent.
509 **************************************************************************/
gui_dialog_new(struct gui_dialog ** pdlg,GtkNotebook * notebook,gpointer user_data,bool check_top)510 void gui_dialog_new(struct gui_dialog **pdlg, GtkNotebook *notebook,
511 gpointer user_data, bool check_top)
512 {
513 struct gui_dialog *dlg;
514 GtkWidget *vbox, *action_area;
515 static int dialog_id_counter;
516
517 dlg = fc_malloc(sizeof(*dlg));
518 dialog_list = g_list_prepend(dialog_list, dlg);
519
520 dlg->source = pdlg;
521 *pdlg = dlg;
522 dlg->user_data = user_data;
523 dlg->title = NULL;
524
525 dlg->default_width = 200;
526 dlg->default_height = 300;
527
528 if (GUI_GTK_OPTION(enable_tabs)) {
529 dlg->type = GUI_DIALOG_TAB;
530 } else {
531 dlg->type = GUI_DIALOG_WINDOW;
532 }
533
534 if (!gui_action) {
535 gui_action = gtk_size_group_new(GTK_SIZE_GROUP_VERTICAL);
536 }
537 dlg->gui_button = gtk_size_group_new(GTK_SIZE_GROUP_BOTH);
538
539 vbox = gtk_grid_new();
540 action_area = gtk_grid_new();
541 gtk_grid_set_row_spacing(GTK_GRID(action_area), 4);
542 gtk_grid_set_column_spacing(GTK_GRID(action_area), 4);
543 if (GUI_GTK_OPTION(enable_tabs)
544 && (check_top && notebook != GTK_NOTEBOOK(top_notebook))
545 && !GUI_GTK_OPTION(small_display_layout)) {
546 /* We expect this to be short (as opposed to tall); maximise usable
547 * height by putting buttons down the right hand side */
548 gtk_orientable_set_orientation(GTK_ORIENTABLE(action_area),
549 GTK_ORIENTATION_VERTICAL);
550 } else {
551 /* We expect this to be reasonably tall; maximise usable width by
552 * putting buttons along the bottom */
553 gtk_orientable_set_orientation(GTK_ORIENTABLE(vbox),
554 GTK_ORIENTATION_VERTICAL);
555 }
556
557 gtk_widget_show(vbox);
558 gtk_container_add(GTK_CONTAINER(vbox), action_area);
559 gtk_widget_show(action_area);
560
561 gtk_container_set_border_width(GTK_CONTAINER(vbox), 2);
562 gtk_container_set_border_width(GTK_CONTAINER(action_area), 2);
563
564 switch (dlg->type) {
565 case GUI_DIALOG_WINDOW:
566 {
567 GtkWidget *window;
568
569 window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
570 gtk_widget_set_name(window, "Freeciv");
571 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_MOUSE);
572 setup_dialog(window, toplevel);
573
574 gtk_container_add(GTK_CONTAINER(window), vbox);
575 dlg->v.window = window;
576 g_signal_connect(window, "delete_event",
577 G_CALLBACK(gui_dialog_delete_handler), dlg);
578
579 }
580 break;
581 case GUI_DIALOG_TAB:
582 {
583 GtkWidget *hbox, *label, *image, *button, *event_box;
584 gchar *buf;
585
586 hbox = gtk_grid_new();
587
588 label = gtk_label_new(NULL);
589 gtk_widget_set_halign(label, GTK_ALIGN_START);
590 gtk_widget_set_valign(label, GTK_ALIGN_CENTER);
591 gtk_widget_set_margin_start(label, 4);
592 gtk_widget_set_margin_end(label, 4);
593 gtk_widget_set_margin_top(label, 0);
594 gtk_widget_set_margin_bottom(label, 0);
595 gtk_container_add(GTK_CONTAINER(hbox), label);
596
597 button = gtk_button_new();
598 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
599 g_signal_connect_swapped(button, "clicked",
600 G_CALLBACK(gui_dialog_delete_tab_handler), dlg);
601
602 buf = g_strdup_printf(_("Close Tab:\n%s"), _("Ctrl+W"));
603 gtk_widget_set_tooltip_text(button, buf);
604 g_free(buf);
605
606 image = gtk_image_new_from_icon_name("window-close", GTK_ICON_SIZE_MENU);
607 gtk_widget_set_margin_start(image, 0);
608 gtk_widget_set_margin_end(image, 0);
609 gtk_widget_set_margin_top(image, 0);
610 gtk_widget_set_margin_bottom(image, 0);
611 gtk_button_set_image(GTK_BUTTON(button), image);
612
613 gtk_container_add(GTK_CONTAINER(hbox), button);
614
615 gtk_widget_show_all(hbox);
616
617 event_box = gtk_event_box_new();
618 gtk_event_box_set_visible_window(GTK_EVENT_BOX(event_box), FALSE);
619 gtk_container_add(GTK_CONTAINER(event_box), hbox);
620
621 gtk_notebook_append_page(GTK_NOTEBOOK(notebook), vbox, event_box);
622 dlg->v.tab.handler_id =
623 g_signal_connect(notebook, "switch-page",
624 G_CALLBACK(gui_dialog_switch_page_handler), dlg);
625 dlg->v.tab.child = vbox;
626
627 gtk_style_context_add_provider(gtk_widget_get_style_context(label),
628 GTK_STYLE_PROVIDER(dlg_tab_provider),
629 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
630 dlg->v.tab.label = label;
631 dlg->v.tab.notebook = GTK_WIDGET(notebook);
632
633 gtk_widget_add_events(event_box, GDK_BUTTON2_MOTION_MASK);
634 g_signal_connect(event_box, "button-press-event",
635 G_CALLBACK(click_on_tab_callback), dlg);
636 }
637 break;
638 }
639
640 dlg->vbox = vbox;
641 dlg->action_area = action_area;
642
643 dlg->response_callback = gui_dialog_destroyed;
644
645 dlg->id = dialog_id_counter;
646 dialog_id_counter++;
647 dlg->return_dialog_id = -1;
648
649 g_signal_connect(vbox, "destroy",
650 G_CALLBACK(gui_dialog_destroy_handler), dlg);
651 g_signal_connect(vbox, "key_press_event",
652 G_CALLBACK(gui_dialog_key_press_handler), dlg);
653
654 g_object_set_data(G_OBJECT(vbox), "gui-dialog-data", dlg);
655 }
656
657 /**************************************************************************
658 Called when a dialog button is activated.
659 **************************************************************************/
action_widget_activated(GtkWidget * button,GtkWidget * vbox)660 static void action_widget_activated(GtkWidget *button, GtkWidget *vbox)
661 {
662 struct gui_dialog *dlg =
663 g_object_get_data(G_OBJECT(vbox), "gui-dialog-data");
664 gpointer arg2 =
665 g_object_get_data(G_OBJECT(button), "gui-dialog-response-data");
666
667 gui_dialog_response(dlg, GPOINTER_TO_INT(arg2));
668 }
669
670 /**************************************************************************
671 Places a button into a dialog, taking care of setting up signals, etc.
672 **************************************************************************/
gui_dialog_pack_button(struct gui_dialog * dlg,GtkWidget * button,int response)673 static void gui_dialog_pack_button(struct gui_dialog *dlg, GtkWidget *button,
674 int response)
675 {
676 gint signal_id;
677
678 fc_assert_ret(GTK_IS_BUTTON(button));
679
680 g_object_set_data(G_OBJECT(button), "gui-dialog-response-data",
681 GINT_TO_POINTER(response));
682
683 if ((signal_id = g_signal_lookup("clicked", GTK_TYPE_BUTTON))) {
684 GClosure *closure;
685
686 closure = g_cclosure_new_object(G_CALLBACK(action_widget_activated),
687 G_OBJECT(dlg->vbox));
688 g_signal_connect_closure_by_id(button, signal_id, 0, closure, FALSE);
689 }
690
691 gtk_container_add(GTK_CONTAINER(dlg->action_area), button);
692 gtk_size_group_add_widget(gui_action, button);
693 gtk_size_group_add_widget(dlg->gui_button, button);
694 }
695
696 /**************************************************************************
697 Adds a button to a dialog.
698 **************************************************************************/
gui_dialog_add_button(struct gui_dialog * dlg,const char * icon_name,const char * text,int response)699 GtkWidget *gui_dialog_add_button(struct gui_dialog *dlg,
700 const char *icon_name,
701 const char *text, int response)
702 {
703 GtkWidget *button;
704
705 button = icon_label_button_new(icon_name, text);
706 gtk_widget_set_can_default(button, TRUE);
707 gui_dialog_pack_button(dlg, button, response);
708
709 return button;
710 }
711
712 /**************************************************************************
713 Adds a widget to a dialog.
714 **************************************************************************/
gui_dialog_add_widget(struct gui_dialog * dlg,GtkWidget * widget)715 GtkWidget *gui_dialog_add_widget(struct gui_dialog *dlg,
716 GtkWidget *widget)
717 {
718 gtk_container_add(GTK_CONTAINER(dlg->action_area), widget);
719 gtk_size_group_add_widget(gui_action, widget);
720
721 return widget;
722 }
723
724 /**************************************************************************
725 Changes the default dialog response.
726 **************************************************************************/
gui_dialog_set_default_response(struct gui_dialog * dlg,int response)727 void gui_dialog_set_default_response(struct gui_dialog *dlg, int response)
728 {
729 GList *children;
730 GList *list;
731
732 children = gtk_container_get_children(GTK_CONTAINER(dlg->action_area));
733
734 for (list = children; list; list = g_list_next(list)) {
735 GtkWidget *button = list->data;
736
737 if (GTK_IS_BUTTON(button)) {
738 gpointer data = g_object_get_data(G_OBJECT(button),
739 "gui-dialog-response-data");
740
741 if (response == GPOINTER_TO_INT(data)) {
742 gtk_widget_grab_default(button);
743 }
744 }
745 }
746
747 g_list_free(children);
748 }
749
750 /**************************************************************************
751 Change the sensitivity of a dialog button.
752 **************************************************************************/
gui_dialog_set_response_sensitive(struct gui_dialog * dlg,int response,bool setting)753 void gui_dialog_set_response_sensitive(struct gui_dialog *dlg,
754 int response, bool setting)
755 {
756 GList *children;
757 GList *list;
758
759 children = gtk_container_get_children(GTK_CONTAINER(dlg->action_area));
760
761 for (list = children; list; list = g_list_next(list)) {
762 GtkWidget *button = list->data;
763
764 if (GTK_IS_BUTTON(button)) {
765 gpointer data = g_object_get_data(G_OBJECT(button),
766 "gui-dialog-response-data");
767
768 if (response == GPOINTER_TO_INT(data)) {
769 gtk_widget_set_sensitive(button, setting);
770 }
771 }
772 }
773
774 g_list_free(children);
775 }
776
777 /**************************************************************************
778 Get the dialog's toplevel window.
779 **************************************************************************/
gui_dialog_get_toplevel(struct gui_dialog * dlg)780 GtkWidget *gui_dialog_get_toplevel(struct gui_dialog *dlg)
781 {
782 return gtk_widget_get_toplevel(dlg->vbox);
783 }
784
785 /**************************************************************************
786 Show the dialog contents, but not the dialog per se.
787 **************************************************************************/
gui_dialog_show_all(struct gui_dialog * dlg)788 void gui_dialog_show_all(struct gui_dialog *dlg)
789 {
790 gtk_widget_show_all(dlg->vbox);
791
792 if (dlg->type == GUI_DIALOG_TAB) {
793 GList *children;
794 GList *list;
795 gint num_visible = 0;
796
797 children = gtk_container_get_children(GTK_CONTAINER(dlg->action_area));
798
799 for (list = children; list; list = g_list_next(list)) {
800 GtkWidget *button = list->data;
801
802 if (!GTK_IS_BUTTON(button)) {
803 num_visible++;
804 } else {
805 gpointer data = g_object_get_data(G_OBJECT(button),
806 "gui-dialog-response-data");
807 int response = GPOINTER_TO_INT(data);
808
809 if (response != GTK_RESPONSE_CLOSE
810 && response != GTK_RESPONSE_CANCEL) {
811 num_visible++;
812 } else {
813 gtk_widget_hide(button);
814 }
815 }
816 }
817 g_list_free(children);
818
819 if (num_visible == 0) {
820 gtk_widget_hide(dlg->action_area);
821 }
822 }
823 }
824
825 /**************************************************************************
826 Notify the user the dialog has changed.
827 **************************************************************************/
gui_dialog_present(struct gui_dialog * dlg)828 void gui_dialog_present(struct gui_dialog *dlg)
829 {
830 fc_assert_ret(NULL != dlg);
831
832 switch (dlg->type) {
833 case GUI_DIALOG_WINDOW:
834 gtk_widget_show(dlg->v.window);
835 break;
836 case GUI_DIALOG_TAB:
837 {
838 GtkNotebook *notebook = GTK_NOTEBOOK(dlg->v.tab.notebook);
839 gint current, n;
840
841 current = gtk_notebook_get_current_page(notebook);
842 n = gtk_notebook_page_num(notebook, dlg->vbox);
843
844 if (current != n) {
845 GtkWidget *label = dlg->v.tab.label;
846
847 gtk_style_context_add_class(gtk_widget_get_style_context(label),
848 "notice");
849 }
850 }
851 break;
852 }
853 }
854
855 /**************************************************************************
856 Raise dialog to top.
857 **************************************************************************/
gui_dialog_raise(struct gui_dialog * dlg)858 void gui_dialog_raise(struct gui_dialog *dlg)
859 {
860 fc_assert_ret(NULL != dlg);
861
862 switch (dlg->type) {
863 case GUI_DIALOG_WINDOW:
864 gtk_window_present(GTK_WINDOW(dlg->v.window));
865 break;
866 case GUI_DIALOG_TAB:
867 {
868 GtkNotebook *notebook = GTK_NOTEBOOK(dlg->v.tab.notebook);
869 gint n;
870
871 n = gtk_notebook_page_num(notebook, dlg->vbox);
872 gtk_notebook_set_current_page(notebook, n);
873 }
874 break;
875 }
876 }
877
878 /**************************************************************************
879 Alert the user to an important event.
880 **************************************************************************/
gui_dialog_alert(struct gui_dialog * dlg)881 void gui_dialog_alert(struct gui_dialog *dlg)
882 {
883 fc_assert_ret(NULL != dlg);
884
885 switch (dlg->type) {
886 case GUI_DIALOG_WINDOW:
887 break;
888 case GUI_DIALOG_TAB:
889 {
890 GtkNotebook *notebook = GTK_NOTEBOOK(dlg->v.tab.notebook);
891 gint current, n;
892
893 current = gtk_notebook_get_current_page(notebook);
894 n = gtk_notebook_page_num(notebook, dlg->vbox);
895
896 if (current != n) {
897 GtkWidget *label = dlg->v.tab.label;
898 GtkStyleContext *context = gtk_widget_get_style_context(label);
899
900 /* Have only alert - remove notice if it exist. */
901 gtk_style_context_remove_class(context, "notice");
902 gtk_style_context_add_class(context, "alert");
903 }
904 }
905 break;
906 }
907 }
908
909 /**************************************************************************
910 Sets the dialog's default size (applies to toplevel windows only).
911 **************************************************************************/
gui_dialog_set_default_size(struct gui_dialog * dlg,int width,int height)912 void gui_dialog_set_default_size(struct gui_dialog *dlg, int width, int height)
913 {
914 dlg->default_width = width;
915 dlg->default_height = height;
916 switch (dlg->type) {
917 case GUI_DIALOG_WINDOW:
918 gtk_window_set_default_size(GTK_WINDOW(dlg->v.window), width, height);
919 break;
920 case GUI_DIALOG_TAB:
921 break;
922 }
923 }
924
925 /**************************************************************************
926 Changes a dialog's title.
927 **************************************************************************/
gui_dialog_set_title(struct gui_dialog * dlg,const char * title)928 void gui_dialog_set_title(struct gui_dialog *dlg, const char *title)
929 {
930 if (dlg->title) {
931 free(dlg->title);
932 }
933 dlg->title = fc_strdup(title);
934 switch (dlg->type) {
935 case GUI_DIALOG_WINDOW:
936 gtk_window_set_title(GTK_WINDOW(dlg->v.window), title);
937 break;
938 case GUI_DIALOG_TAB:
939 gtk_label_set_text_with_mnemonic(GTK_LABEL(dlg->v.tab.label), title);
940 break;
941 }
942 }
943
944 /**************************************************************************
945 Destroy a dialog.
946 **************************************************************************/
gui_dialog_destroy(struct gui_dialog * dlg)947 void gui_dialog_destroy(struct gui_dialog *dlg)
948 {
949 switch (dlg->type) {
950 case GUI_DIALOG_WINDOW:
951 gtk_widget_destroy(dlg->v.window);
952 break;
953 case GUI_DIALOG_TAB:
954 {
955 gint n;
956
957 n = gtk_notebook_page_num(GTK_NOTEBOOK(dlg->v.tab.notebook), dlg->vbox);
958 gtk_notebook_remove_page(GTK_NOTEBOOK(dlg->v.tab.notebook), n);
959 }
960 break;
961 }
962 }
963
964 /**************************************************************************
965 Destroy all dialogs.
966 **************************************************************************/
gui_dialog_destroy_all(void)967 void gui_dialog_destroy_all(void)
968 {
969 GList *it, *it_next;
970
971 for (it = dialog_list; it; it = it_next) {
972 it_next = g_list_next(it);
973
974 gui_dialog_destroy((struct gui_dialog *)it->data);
975 }
976 }
977
978 /**************************************************************************
979 Set the response callback for a dialog.
980 **************************************************************************/
gui_dialog_response_set_callback(struct gui_dialog * dlg,GUI_DIALOG_RESPONSE_FUN fun)981 void gui_dialog_response_set_callback(struct gui_dialog *dlg,
982 GUI_DIALOG_RESPONSE_FUN fun)
983 {
984 dlg->response_callback = fun;
985 }
986
987 /**************************************************************************
988 When the dlg dialog is destroyed the return_dialog will be raised
989 **************************************************************************/
gui_dialog_set_return_dialog(struct gui_dialog * dlg,struct gui_dialog * return_dialog)990 void gui_dialog_set_return_dialog(struct gui_dialog *dlg,
991 struct gui_dialog *return_dialog)
992 {
993 if (return_dialog == NULL) {
994 dlg->return_dialog_id = -1;
995 } else {
996 dlg->return_dialog_id = return_dialog->id;
997 }
998 }
999
1000 /**************************************************************************
1001 Updates a gui font style.
1002 **************************************************************************/
gui_update_font(const char * font_name,const char * font_value)1003 void gui_update_font(const char *font_name, const char *font_value)
1004 {
1005 char *str;
1006 GtkCssProvider *provider;
1007 PangoFontDescription *desc;
1008 int size;
1009 const char *fam;
1010 const char *style;
1011 const char *weight;
1012
1013 desc = pango_font_description_from_string(font_value);
1014
1015 if (desc == NULL) {
1016 return;
1017 }
1018
1019 fam = pango_font_description_get_family(desc);
1020
1021 if (fam == NULL) {
1022 return;
1023 }
1024
1025 if (pango_font_description_get_style(desc) == PANGO_STYLE_ITALIC) {
1026 style = "\n font-style: italic;";
1027 } else {
1028 style = "";
1029 }
1030
1031 if (pango_font_description_get_weight(desc) >= 700) {
1032 weight = "\n font-weight: bold;";
1033 } else {
1034 weight = "";
1035 }
1036
1037 size = pango_font_description_get_size(desc);
1038
1039 if (size != 0) {
1040 str = g_strdup_printf("#Freeciv #%s { font-family: %s; font-size: %dpx;%s%s}",
1041 font_name, fam, size / PANGO_SCALE, style, weight);
1042 } else {
1043 str = g_strdup_printf("#Freeciv #%s { font-family: %s;%s%s}",
1044 font_name, fam, style, weight);
1045 }
1046
1047 pango_font_description_free(desc);
1048
1049 provider = gtk_css_provider_new();
1050 gtk_css_provider_load_from_data(GTK_CSS_PROVIDER(provider),
1051 str, -1, NULL);
1052 gtk_style_context_add_provider_for_screen(
1053 gtk_widget_get_screen(toplevel), GTK_STYLE_PROVIDER(provider),
1054 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
1055 g_free(str);
1056 }
1057
1058 /****************************************************************************
1059 Update a font option which is not attached to a widget.
1060 ****************************************************************************/
gui_update_font_full(const char * font_name,const char * font_value,PangoFontDescription ** font_desc)1061 void gui_update_font_full(const char *font_name, const char *font_value,
1062 PangoFontDescription **font_desc)
1063 {
1064 PangoFontDescription *f_desc;
1065
1066 gui_update_font(font_name, font_value);
1067
1068 f_desc = pango_font_description_from_string(font_value);
1069 pango_font_description_free(*font_desc);
1070
1071 *font_desc = f_desc;
1072 }
1073
1074 /****************************************************************************
1075 Temporarily disable signal invocation of the given callback for the given
1076 GObject. Re-enable the signal with enable_gobject_callback.
1077 ****************************************************************************/
disable_gobject_callback(GObject * obj,GCallback cb)1078 void disable_gobject_callback(GObject *obj, GCallback cb)
1079 {
1080 gulong hid;
1081
1082 if (!obj || !cb) {
1083 return;
1084 }
1085
1086 hid = g_signal_handler_find(obj, G_SIGNAL_MATCH_FUNC,
1087 0, 0, NULL, cb, NULL);
1088 g_signal_handler_block(obj, hid);
1089 }
1090
1091 /****************************************************************************
1092 Re-enable a signal callback blocked by disable_gobject_callback.
1093 ****************************************************************************/
enable_gobject_callback(GObject * obj,GCallback cb)1094 void enable_gobject_callback(GObject *obj, GCallback cb)
1095 {
1096 gulong hid;
1097
1098 if (!obj || !cb) {
1099 return;
1100 }
1101
1102 hid = g_signal_handler_find(obj, G_SIGNAL_MATCH_FUNC,
1103 0, 0, NULL, cb, NULL);
1104 g_signal_handler_unblock(obj, hid);
1105 }
1106
1107 /**************************************************************************
1108 Convenience function to add a column to a GtkTreeView. Returns the added
1109 column, or NULL if an error occurred.
1110 **************************************************************************/
add_treeview_column(GtkWidget * view,const char * title,GType gtype,int model_index)1111 GtkTreeViewColumn *add_treeview_column(GtkWidget *view, const char *title,
1112 GType gtype, int model_index)
1113 {
1114 GtkTreeViewColumn *col;
1115 GtkCellRenderer *rend;
1116 const char *attr;
1117
1118 fc_assert_ret_val(view != NULL, NULL);
1119 fc_assert_ret_val(GTK_IS_TREE_VIEW(view), NULL);
1120 fc_assert_ret_val(title != NULL, NULL);
1121
1122 if (gtype == G_TYPE_BOOLEAN) {
1123 rend = gtk_cell_renderer_toggle_new();
1124 attr = "active";
1125 } else if (gtype == GDK_TYPE_PIXBUF) {
1126 rend = gtk_cell_renderer_pixbuf_new();
1127 attr = "pixbuf";
1128 } else {
1129 rend = gtk_cell_renderer_text_new();
1130 attr = "text";
1131 }
1132
1133 col = gtk_tree_view_column_new_with_attributes(title, rend, attr,
1134 model_index, NULL);
1135 gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
1136
1137 return col;
1138 }
1139
1140 /**************************************************************************
1141 Prepare dialog tab style provider.
1142 **************************************************************************/
dlg_tab_provider_prepare(void)1143 void dlg_tab_provider_prepare(void)
1144 {
1145 dlg_tab_provider = gtk_css_provider_new();
1146
1147 gtk_css_provider_load_from_data(dlg_tab_provider,
1148 ".alert {\n"
1149 "color: rgba(255, 0, 0, 255);\n"
1150 "}\n"
1151 ".notice {\n"
1152 "color: rgba(0, 0, 255, 255);\n"
1153 "}\n",
1154 -1, NULL);
1155 }
1156