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