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 #ifdef AUDIO_SDL
19 /* Though it would happily compile without this include,
20  * it is needed for sound to work.
21  * It defines "main" macro to rename our main() so that
22  * it can install SDL's own. */
23 #ifdef SDL2_PLAIN_INCLUDE
24 #include <SDL.h>
25 #elif AUDIO_SDL1_2
26 /* SDL */
27 #include <SDL/SDL.h>
28 #else  /* AUDIO_SDL1_2 */
29 /* SDL2 */
30 #include <SDL2/SDL.h>
31 #endif /* AUDIO_SDL1_2 */
32 #endif
33 
34 #ifdef HAVE_LOCALE_H
35 #include <locale.h>
36 #endif
37 #include <stdarg.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <time.h>
42 
43 #ifdef HAVE_UNISTD_H
44 #include <unistd.h>
45 #endif
46 
47 #include <gtk/gtk.h>
48 #include <gdk/gdkkeysyms.h>
49 
50 /* utility */
51 #include "fc_cmdline.h"
52 #include "fciconv.h"
53 #include "fcintl.h"
54 #include "log.h"
55 #include "mem.h"
56 #include "support.h"
57 
58 /* common */
59 #include "dataio.h"
60 #include "featured_text.h"
61 #include "game.h"
62 #include "government.h"
63 #include "map.h"
64 #include "unitlist.h"
65 #include "version.h"
66 
67 /* client */
68 #include "client_main.h"
69 #include "climisc.h"
70 #include "clinet.h"
71 #include "colors.h"
72 #include "connectdlg_common.h"
73 #include "control.h"
74 #include "editor.h"
75 #include "options.h"
76 #include "text.h"
77 #include "tilespec.h"
78 #include "zoom.h"
79 
80 /* client/gui-gtk-3.22 */
81 #include "chatline.h"
82 #include "citizensinfo.h"
83 #include "connectdlg.h"
84 #include "cma_fe.h"
85 #include "dialogs.h"
86 #include "diplodlg.h"
87 #include "editgui.h"
88 #include "gotodlg.h"
89 #include "graphics.h"
90 #include "gui_stuff.h"
91 #include "happiness.h"
92 #include "inteldlg.h"
93 #include "mapctrl.h"
94 #include "mapview.h"
95 #include "menu.h"
96 #include "messagewin.h"
97 #include "optiondlg.h"
98 #include "pages.h"
99 #include "plrdlg.h"
100 #include "luaconsole.h"
101 #include "spaceshipdlg.h"
102 #include "repodlgs.h"
103 #include "voteinfo_bar.h"
104 
105 #include "gui_main.h"
106 
107 const char *client_string = "gui-gtk-3.22";
108 
109 GtkWidget *map_canvas;                  /* GtkDrawingArea */
110 GtkWidget *map_horizontal_scrollbar;
111 GtkWidget *map_vertical_scrollbar;
112 
113 GtkWidget *overview_canvas;             /* GtkDrawingArea */
114 GtkWidget *overview_scrolled_window;    /* GtkScrolledWindow */
115 /* The two values below define the width and height of the map overview. The
116  * first set of values (2*62, 2*46) define the size for a netbook display. For
117  * bigger displays the values are doubled (default). */
118 #define OVERVIEW_CANVAS_STORE_WIDTH_NETBOOK  (2 * 64)
119 #define OVERVIEW_CANVAS_STORE_HEIGHT_NETBOOK (2 * 46)
120 #define OVERVIEW_CANVAS_STORE_WIDTH \
121   (2 * OVERVIEW_CANVAS_STORE_WIDTH_NETBOOK)
122 #define OVERVIEW_CANVAS_STORE_HEIGHT \
123   (2 * OVERVIEW_CANVAS_STORE_HEIGHT_NETBOOK)
124 int overview_canvas_store_width = OVERVIEW_CANVAS_STORE_WIDTH;
125 int overview_canvas_store_height = OVERVIEW_CANVAS_STORE_HEIGHT;
126 
127 GtkWidget *toplevel;
128 GdkWindow *root_window;
129 GtkWidget *toplevel_tabs;
130 GtkWidget *top_vbox;
131 GtkWidget *top_notebook, *bottom_notebook, *right_notebook;
132 GtkWidget *map_widget;
133 static GtkWidget *bottom_hpaned;
134 
135 PangoFontDescription *city_names_style = NULL;
136 PangoFontDescription *city_productions_style = NULL;
137 PangoFontDescription *reqtree_text_style = NULL;
138 
139 GtkWidget *main_frame_civ_name;
140 GtkWidget *main_label_info;
141 
142 GtkWidget *avbox, *ahbox, *conn_box;
143 GtkWidget* scroll_panel;
144 
145 GtkWidget *econ_label[10];
146 GtkWidget *bulb_label;
147 GtkWidget *sun_label;
148 GtkWidget *flake_label;
149 GtkWidget *government_label;
150 GtkWidget *timeout_label;
151 GtkWidget *turn_done_button;
152 
153 GtkWidget *unit_info_label;
154 GtkWidget *unit_info_box;
155 GtkWidget *unit_info_frame;
156 
157 GtkWidget *econ_ebox;
158 GtkWidget *bulb_ebox;
159 GtkWidget *sun_ebox;
160 GtkWidget *flake_ebox;
161 GtkWidget *government_ebox;
162 
163 const char *const gui_character_encoding = "UTF-8";
164 const bool gui_use_transliteration = FALSE;
165 
166 static GtkWidget *main_menubar;
167 static GtkWidget *unit_image_table;
168 static GtkWidget *unit_image;
169 static GtkWidget *unit_image_button;
170 static GtkWidget *unit_below_image[MAX_NUM_UNITS_BELOW];
171 static GtkWidget *unit_below_image_button[MAX_NUM_UNITS_BELOW];
172 static GtkWidget *more_arrow_pixmap;
173 static GtkWidget *more_arrow_pixmap_button;
174 static GtkWidget *more_arrow_pixmap_container;
175 
176 static int unit_id_top;
177 static int unit_ids[MAX_NUM_UNITS_BELOW];  /* ids of the units icons in
178                                             * information display: (or 0) */
179 GtkTextView *main_message_area;
180 GtkTextBuffer *message_buffer = NULL;
181 static GtkWidget *allied_chat_toggle_button;
182 
183 static enum Display_color_type display_color_type;  /* practically unused */
184 static gint timer_id;                               /*       ditto        */
185 static GIOChannel *srv_channel;
186 static guint srv_id;
187 gint cur_x, cur_y;
188 
189 static bool gui_up = FALSE;
190 
191 static struct video_mode vmode = { -1, -1 };
192 
193 static gboolean show_info_button_release(GtkWidget *w, GdkEventButton *ev, gpointer data);
194 static gboolean show_info_popup(GtkWidget *w, GdkEventButton *ev, gpointer data);
195 
196 static void end_turn_callback(GtkWidget *w, gpointer data);
197 static gboolean get_net_input(GIOChannel *source, GIOCondition condition,
198                               gpointer data);
199 static void set_wait_for_writable_socket(struct connection *pc,
200                                          bool socket_writable);
201 
202 static void print_usage(void);
203 static void parse_options(int argc, char **argv);
204 static gboolean toplevel_key_press_handler(GtkWidget *w, GdkEventKey *ev, gpointer data);
205 static gboolean toplevel_key_release_handler(GtkWidget *w, GdkEventKey *ev, gpointer data);
206 static gboolean mouse_scroll_mapcanvas(GtkWidget *w, GdkEventScroll *ev);
207 
208 static void tearoff_callback(GtkWidget *b, gpointer data);
209 static GtkWidget *detached_widget_new(void);
210 static GtkWidget *detached_widget_fill(GtkWidget *tearbox);
211 
212 static gboolean select_unit_image_callback(GtkWidget *w, GdkEvent *ev,
213                                            gpointer data);
214 static gboolean select_more_arrow_pixmap_callback(GtkWidget *w, GdkEvent *ev,
215                                                   gpointer data);
216 static gboolean quit_dialog_callback(void);
217 
218 static void allied_chat_button_toggled(GtkToggleButton *button,
219                                        gpointer user_data);
220 
221 static void free_unit_table(void);
222 
223 static void adjust_default_options(void);
224 
225 /****************************************************************************
226   Called by the tileset code to set the font size that should be used to
227   draw the city names and productions.
228 ****************************************************************************/
set_city_names_font_sizes(int my_city_names_font_size,int my_city_productions_font_size)229 void set_city_names_font_sizes(int my_city_names_font_size,
230 			       int my_city_productions_font_size)
231 {
232   /* Ignored in favour of client configuration */
233 }
234 
235 /**************************************************************************
236   Callback for freelog
237 **************************************************************************/
log_callback_utf8(enum log_level level,const char * message,bool file_too)238 static void log_callback_utf8(enum log_level level, const char *message,
239                               bool file_too)
240 {
241   if (!file_too || level <= LOG_FATAL) {
242     fc_fprintf(stderr, "%d: %s\n", level, message);
243   }
244 }
245 
246 /**************************************************************************
247  Called while in gtk_main() (which is all of the time)
248  TIMER_INTERVAL is now set by real_timer_callback()
249 **************************************************************************/
timer_callback(gpointer data)250 static gboolean timer_callback(gpointer data)
251 {
252   double seconds = real_timer_callback();
253 
254   timer_id = g_timeout_add(seconds * 1000, timer_callback, NULL);
255 
256   return FALSE;
257 }
258 
259 /**************************************************************************
260   Print extra usage information, including one line help on each option,
261   to stderr.
262 **************************************************************************/
print_usage(void)263 static void print_usage(void)
264 {
265   /* add client-specific usage information here */
266   fc_fprintf(stderr,
267              _("This client accepts the standard Gtk command-line options\n"
268                "after '--'. See the Gtk documentation.\n\n"));
269 
270   fc_fprintf(stderr,
271              _("Other gui-specific options are:\n"));
272 
273   fc_fprintf(stderr,
274              _("-r, --resolution WIDTHxHEIGHT\tAssume given resolution "
275                "screen\n"));
276 
277   fc_fprintf(stderr, "\n");
278 
279   /* TRANS: No full stop after the URL, could cause confusion. */
280   fc_fprintf(stderr, _("Report bugs at %s\n"), BUG_URL);
281 }
282 
283 /**************************************************************************
284   Search for command line options. right now, it's just help
285   semi-useless until we have options that aren't the same across all clients.
286 **************************************************************************/
parse_options(int argc,char ** argv)287 static void parse_options(int argc, char **argv)
288 {
289   int i = 1;
290 
291   while (i < argc) {
292     char *option = NULL;
293 
294     if (is_option("--help", argv[i])) {
295       print_usage();
296       exit(EXIT_SUCCESS);
297     } else if ((option = get_option_malloc("--resolution", argv, &i, argc, FALSE))) {
298       if (!string_to_video_mode(option, &vmode)) {
299         fc_fprintf(stderr, _("Illegal video mode '%s'"), option);
300         exit(EXIT_FAILURE);
301       }
302       free(option);
303     }
304     /* Can't check against unknown options, as those might be gtk options */
305 
306     i++;
307   }
308 }
309 
310 /**************************************************************************
311   Focus on widget. Returns whether focus was really changed.
312 **************************************************************************/
toplevel_focus(GtkWidget * w,GtkDirectionType arg)313 static gboolean toplevel_focus(GtkWidget *w, GtkDirectionType arg)
314 {
315   switch (arg) {
316     case GTK_DIR_TAB_FORWARD:
317     case GTK_DIR_TAB_BACKWARD:
318 
319       if (!gtk_widget_get_can_focus(w)) {
320 	return FALSE;
321       }
322 
323       if (!gtk_widget_is_focus(w)) {
324 	gtk_widget_grab_focus(w);
325 	return TRUE;
326       }
327       break;
328 
329     default:
330       break;
331   }
332   return FALSE;
333 }
334 
335 /**************************************************************************
336   When the chatline text view is resized, scroll it to the bottom. This
337   prevents users from accidentally missing messages when the chatline
338   gets scrolled up a small amount and stops scrolling down automatically.
339 **************************************************************************/
main_message_area_size_allocate(GtkWidget * widget,GtkAllocation * allocation,gpointer data)340 static void main_message_area_size_allocate(GtkWidget *widget,
341                                             GtkAllocation *allocation,
342                                             gpointer data)
343 {
344   static int old_width = 0, old_height = 0;
345 
346   if (allocation->width != old_width
347       || allocation->height != old_height) {
348     chatline_scroll_to_bottom(TRUE);
349     old_width = allocation->width;
350     old_height = allocation->height;
351   }
352 }
353 
354 /**************************************************************************
355   Focus on map canvas
356 **************************************************************************/
map_canvas_focus(void)357 gboolean map_canvas_focus(void)
358 {
359   gtk_window_present(GTK_WINDOW(toplevel));
360   gtk_notebook_set_current_page(GTK_NOTEBOOK(top_notebook), 0);
361   gtk_widget_grab_focus(map_canvas);
362   return TRUE;
363 }
364 
365 /**************************************************************************
366   In GTK+ keyboard events are recursively propagated from the hierarchy
367   parent down to its children. Sometimes this is not what we want.
368   E.g. The inputline is active, the user presses the 's' key, we want it
369   to be sent to the inputline, but because the main menu is further up
370   the hierarchy, it wins and the inputline never gets anything!
371   This function ensures an entry widget (like the inputline) always gets
372   first dibs at handling a keyboard event.
373 **************************************************************************/
toplevel_handler(GtkWidget * w,GdkEventKey * ev,gpointer data)374 static gboolean toplevel_handler(GtkWidget *w, GdkEventKey *ev, gpointer data)
375 {
376   GtkWidget *focus;
377 
378   focus = gtk_window_get_focus(GTK_WINDOW(toplevel));
379   if (focus) {
380     if (GTK_IS_ENTRY(focus)
381         || (GTK_IS_TEXT_VIEW(focus)
382             && gtk_text_view_get_editable(GTK_TEXT_VIEW(focus)))) {
383       /* Propagate event to currently focused entry widget. */
384       if (gtk_widget_event(focus, (GdkEvent *) ev)) {
385 	/* Do not propagate event to our children. */
386 	return TRUE;
387       }
388     }
389   }
390 
391   /* Continue propagating event to our children. */
392   return FALSE;
393 }
394 
395 /**************************************************************************
396   Handle keypress events when map canvas is in focus
397 **************************************************************************/
key_press_map_canvas(GtkWidget * w,GdkEventKey * ev,gpointer data)398 static gboolean key_press_map_canvas(GtkWidget *w, GdkEventKey *ev,
399                                      gpointer data)
400 {
401   if ((ev->state & GDK_SHIFT_MASK)) {
402     switch (ev->keyval) {
403 
404     case GDK_KEY_Left:
405       scroll_mapview(DIR8_WEST);
406       return TRUE;
407 
408     case GDK_KEY_Right:
409       scroll_mapview(DIR8_EAST);
410       return TRUE;
411 
412     case GDK_KEY_Up:
413       scroll_mapview(DIR8_NORTH);
414       return TRUE;
415 
416     case GDK_KEY_Down:
417       scroll_mapview(DIR8_SOUTH);
418       return TRUE;
419 
420     case GDK_KEY_Home:
421       key_center_capital();
422       return TRUE;
423 
424     case GDK_KEY_Page_Up:
425       g_signal_emit_by_name(main_message_area, "move_cursor",
426 	                          GTK_MOVEMENT_PAGES, -1, FALSE);
427       return TRUE;
428 
429     case GDK_KEY_Page_Down:
430       g_signal_emit_by_name(main_message_area, "move_cursor",
431 	                          GTK_MOVEMENT_PAGES, 1, FALSE);
432       return TRUE;
433 
434     default:
435       break;
436     }
437   } else if (!(ev->state & GDK_CONTROL_MASK)) {
438     switch (ev->keyval) {
439     default:
440       break;
441     }
442   }
443 
444   if (!(ev->state & GDK_CONTROL_MASK)) {
445     switch (ev->keyval) {
446     case GDK_KEY_plus:
447       zoom_step_up();
448       return TRUE;
449 
450     case GDK_KEY_minus:
451       zoom_step_down();
452       return TRUE;
453 
454     default:
455       break;
456     }
457   }
458 
459   /* Return here if observer */
460   if (client_is_observer()) {
461     return FALSE;
462   }
463 
464   fc_assert(MAX_NUM_BATTLEGROUPS == 4);
465 
466   if ((ev->state & GDK_CONTROL_MASK)) {
467     switch (ev->keyval) {
468 
469     case GDK_KEY_F1:
470       key_unit_assign_battlegroup(0, (ev->state & GDK_SHIFT_MASK));
471       return TRUE;
472 
473     case GDK_KEY_F2:
474       key_unit_assign_battlegroup(1, (ev->state & GDK_SHIFT_MASK));
475       return TRUE;
476 
477     case GDK_KEY_F3:
478       key_unit_assign_battlegroup(2, (ev->state & GDK_SHIFT_MASK));
479       return TRUE;
480 
481     case GDK_KEY_F4:
482       key_unit_assign_battlegroup(3, (ev->state & GDK_SHIFT_MASK));
483       return TRUE;
484 
485     default:
486       break;
487     };
488   } else if ((ev->state & GDK_SHIFT_MASK)) {
489     switch (ev->keyval) {
490 
491     case GDK_KEY_F1:
492       key_unit_select_battlegroup(0, FALSE);
493       return TRUE;
494 
495     case GDK_KEY_F2:
496       key_unit_select_battlegroup(1, FALSE);
497       return TRUE;
498 
499     case GDK_KEY_F3:
500       key_unit_select_battlegroup(2, FALSE);
501       return TRUE;
502 
503     case GDK_KEY_F4:
504       key_unit_select_battlegroup(3, FALSE);
505       return TRUE;
506 
507     default:
508       break;
509     };
510   }
511 
512   switch (ev->keyval) {
513 
514   case GDK_KEY_KP_Up:
515   case GDK_KEY_KP_8:
516   case GDK_KEY_Up:
517   case GDK_KEY_8:
518     key_unit_move(DIR8_NORTH);
519     return TRUE;
520 
521   case GDK_KEY_KP_Page_Up:
522   case GDK_KEY_KP_9:
523   case GDK_KEY_Page_Up:
524   case GDK_KEY_9:
525     key_unit_move(DIR8_NORTHEAST);
526     return TRUE;
527 
528   case GDK_KEY_KP_Right:
529   case GDK_KEY_KP_6:
530   case GDK_KEY_Right:
531   case GDK_KEY_6:
532     key_unit_move(DIR8_EAST);
533     return TRUE;
534 
535   case GDK_KEY_KP_Page_Down:
536   case GDK_KEY_KP_3:
537   case GDK_KEY_Page_Down:
538   case GDK_KEY_3:
539     key_unit_move(DIR8_SOUTHEAST);
540     return TRUE;
541 
542   case GDK_KEY_KP_Down:
543   case GDK_KEY_KP_2:
544   case GDK_KEY_Down:
545   case GDK_KEY_2:
546     key_unit_move(DIR8_SOUTH);
547     return TRUE;
548 
549   case GDK_KEY_KP_End:
550   case GDK_KEY_KP_1:
551   case GDK_KEY_End:
552   case GDK_KEY_1:
553     key_unit_move(DIR8_SOUTHWEST);
554     return TRUE;
555 
556   case GDK_KEY_KP_Left:
557   case GDK_KEY_KP_4:
558   case GDK_KEY_Left:
559   case GDK_KEY_4:
560     key_unit_move(DIR8_WEST);
561     return TRUE;
562 
563   case GDK_KEY_KP_Home:
564   case GDK_KEY_KP_7:
565   case GDK_KEY_Home:
566   case GDK_KEY_7:
567     key_unit_move(DIR8_NORTHWEST);
568     return TRUE;
569 
570   case GDK_KEY_KP_Begin:
571   case GDK_KEY_KP_5:
572   case GDK_KEY_5:
573     key_recall_previous_focus_unit();
574     return TRUE;
575 
576   case GDK_KEY_Escape:
577     key_cancel_action();
578     return TRUE;
579 
580   case GDK_KEY_b:
581     if (tiles_hilited_cities) {
582       buy_production_in_selected_cities();
583       return TRUE;
584     }
585     break;
586 
587   default:
588     break;
589   };
590 
591   return FALSE;
592 }
593 
594 /**************************************************************************
595   Handler for "key release" for toplevel window
596 **************************************************************************/
toplevel_key_release_handler(GtkWidget * w,GdkEventKey * ev,gpointer data)597 static gboolean toplevel_key_release_handler(GtkWidget *w, GdkEventKey *ev,
598                                              gpointer data)
599 {
600   /* inputline history code */
601   if (!gtk_widget_get_mapped(top_vbox) || inputline_has_focus()) {
602     return FALSE;
603   }
604 
605   if (editor_is_active()) {
606     return handle_edit_key_release(ev);
607   }
608 
609   return FALSE;
610 }
611 
612 /**************************************************************************
613   Handle a keyboard key press made in the client's toplevel window.
614 **************************************************************************/
toplevel_key_press_handler(GtkWidget * w,GdkEventKey * ev,gpointer data)615 static gboolean toplevel_key_press_handler(GtkWidget *w, GdkEventKey *ev,
616                                            gpointer data)
617 {
618   if (inputline_has_focus()) {
619     return FALSE;
620   }
621 
622   switch (ev->keyval) {
623 
624   case GDK_KEY_apostrophe:
625     /* Allow this even if not in main map view; chatline is present on
626      * some other pages too */
627 
628     /* Make the chatline visible if it's not currently.
629      * FIXME: should find the correct window, even when detached, from any
630      * other window; should scroll to the bottom automatically showing the
631      * latest text from other players; MUST NOT make spurious text windows
632      * at the bottom of other dialogs. */
633     if (gtk_widget_get_mapped(top_vbox)) {
634       /* The main game view is visible. May need to switch notebook. */
635       if (GUI_GTK_OPTION(message_chat_location) == GUI_GTK_MSGCHAT_MERGED) {
636         gtk_notebook_set_current_page(GTK_NOTEBOOK(top_notebook), 1);
637       } else {
638         gtk_notebook_set_current_page(GTK_NOTEBOOK(bottom_notebook), 0);
639       }
640     }
641 
642     /* If the chatline is (now) visible, focus it. */
643     if (inputline_is_visible()) {
644       inputline_grab_focus();
645       return TRUE;
646     } else {
647       break;
648     }
649 
650   default:
651     break;
652   }
653 
654   if (!gtk_widget_get_mapped(top_vbox)
655       || !can_client_change_view()) {
656     return FALSE;
657   }
658 
659   if (editor_is_active()) {
660     if (handle_edit_key_press(ev)) {
661       return TRUE;
662     }
663   }
664 
665   if (ev->state & GDK_SHIFT_MASK) {
666     switch (ev->keyval) {
667 
668     case GDK_KEY_Return:
669     case GDK_KEY_KP_Enter:
670       key_end_turn();
671       return TRUE;
672 
673     default:
674       break;
675     }
676   }
677 
678   if (0 == gtk_notebook_get_current_page(GTK_NOTEBOOK(top_notebook))) {
679     /* 0 means the map view is focused. */
680     return key_press_map_canvas(w, ev, data);
681   }
682 
683 #if 0
684   /* We are focused some other dialog, tab, or widget. */
685   if ((ev->state & GDK_CONTROL_MASK)) {
686   } else if ((ev->state & GDK_SHIFT_MASK)) {
687   } else {
688     switch (ev->keyval) {
689 
690     case GDK_KEY_F4:
691       map_canvas_focus();
692       return TRUE;
693 
694     default:
695       break;
696     };
697   }
698 #endif /* 0 */
699 
700   return FALSE;
701 }
702 
703 /**************************************************************************
704 Mouse/touchpad scrolling over the mapview
705 **************************************************************************/
mouse_scroll_mapcanvas(GtkWidget * w,GdkEventScroll * ev)706 static gboolean mouse_scroll_mapcanvas(GtkWidget *w, GdkEventScroll *ev)
707 {
708   int scroll_x, scroll_y, xstep, ystep;
709 
710   if (!can_client_change_view()) {
711     return FALSE;
712   }
713 
714   get_mapview_scroll_pos(&scroll_x, &scroll_y);
715   get_mapview_scroll_step(&xstep, &ystep);
716 
717   switch (ev->direction) {
718     case GDK_SCROLL_UP:
719       scroll_y -= ystep*2;
720       break;
721     case GDK_SCROLL_DOWN:
722       scroll_y += ystep*2;
723       break;
724     case GDK_SCROLL_RIGHT:
725       scroll_x += xstep*2;
726       break;
727     case GDK_SCROLL_LEFT:
728       scroll_x -= xstep*2;
729       break;
730     default:
731       return FALSE;
732   };
733 
734   set_mapview_scroll_pos(scroll_x, scroll_y);
735 
736   /* Emulating mouse move now */
737   if (!gtk_widget_has_focus(map_canvas)) {
738     gtk_widget_grab_focus(map_canvas);
739   }
740 
741   update_line(ev->x, ev->y);
742   if (rbutton_down && (ev->state & GDK_BUTTON3_MASK)) {
743     update_selection_rectangle(ev->x, ev->y);
744   }
745 
746   if (keyboardless_goto_button_down && hover_state == HOVER_NONE) {
747     maybe_activate_keyboardless_goto(cur_x, cur_y);
748   }
749 
750   control_mouse_cursor(canvas_pos_to_tile(cur_x, cur_y));
751 
752   return TRUE;
753 }
754 
755 /**************************************************************************
756   Reattaches the detached widget when the user destroys it.
757 **************************************************************************/
tearoff_destroy(GtkWidget * w,gpointer data)758 static void tearoff_destroy(GtkWidget *w, gpointer data)
759 {
760   GtkWidget *p, *b, *box;
761   GtkWidget *old_parent;
762 
763   box = GTK_WIDGET(data);
764   old_parent = gtk_widget_get_parent(box);
765   p = g_object_get_data(G_OBJECT(w), "parent");
766   b = g_object_get_data(G_OBJECT(w), "toggle");
767   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b), FALSE);
768 
769   gtk_widget_hide(w);
770   g_object_ref(box); /* Make sure reference count stays above 0
771                       * during the transition to new parent. */
772   gtk_container_remove(GTK_CONTAINER(old_parent), box);
773   gtk_container_add(GTK_CONTAINER(p), box);
774   g_object_unref(box);
775 }
776 
777 /**************************************************************************
778   Propagates a keypress in a tearoff back to the toplevel window.
779 **************************************************************************/
propagate_keypress(GtkWidget * w,GdkEventKey * ev)780 static gboolean propagate_keypress(GtkWidget *w, GdkEventKey *ev)
781 {
782   gtk_widget_event(toplevel, (GdkEvent *)ev);
783 
784   return FALSE;
785 }
786 
787 /**************************************************************************
788   Callback for the toggle button in the detachable widget: causes the
789   widget to detach or reattach.
790 **************************************************************************/
tearoff_callback(GtkWidget * b,gpointer data)791 static void tearoff_callback(GtkWidget *b, gpointer data)
792 {
793   GtkWidget *box = GTK_WIDGET(data);
794 
795   if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(b))) {
796     GtkWidget *temp_hide;
797     GtkWidget *old_parent;
798     GtkWidget *w;
799 
800     old_parent = gtk_widget_get_parent(box);
801     w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
802     setup_dialog(w, toplevel);
803     gtk_widget_set_name(w, "Freeciv");
804     gtk_window_set_title(GTK_WINDOW(w), _("Freeciv"));
805     gtk_window_set_position(GTK_WINDOW(w), GTK_WIN_POS_MOUSE);
806     g_signal_connect(w, "destroy", G_CALLBACK(tearoff_destroy), box);
807     g_signal_connect(w, "key_press_event",
808 	G_CALLBACK(propagate_keypress), NULL);
809 
810     g_object_set_data(G_OBJECT(w), "parent", gtk_widget_get_parent(box));
811     g_object_set_data(G_OBJECT(w), "toggle", b);
812     temp_hide = g_object_get_data(G_OBJECT(box), "hide-over-reparent");
813     if (temp_hide != NULL) {
814       gtk_widget_hide(temp_hide);
815     }
816 
817     g_object_ref(box); /* Make sure reference count stays above 0
818                         * during the transition to new parent. */
819     gtk_container_remove(GTK_CONTAINER(old_parent), box);
820     gtk_container_add(GTK_CONTAINER(w), box);
821     g_object_unref(box);
822     gtk_widget_show(w);
823     if (temp_hide != NULL) {
824       gtk_widget_show(temp_hide);
825     }
826   } else {
827     gtk_widget_destroy(gtk_widget_get_parent(box));
828   }
829 }
830 
831 /**************************************************************************
832  create the container for the widget that's able to be detached
833 **************************************************************************/
detached_widget_new(void)834 static GtkWidget *detached_widget_new(void)
835 {
836   GtkWidget *hgrid = gtk_grid_new();
837   gtk_grid_set_column_spacing(GTK_GRID(hgrid), 2);
838   return hgrid;
839 }
840 
841 /**************************************************************************
842  creates the toggle button necessary to detach and reattach the widget
843  and returns a vbox in which you fill your goodies.
844 **************************************************************************/
detached_widget_fill(GtkWidget * tearbox)845 static GtkWidget *detached_widget_fill(GtkWidget *tearbox)
846 {
847   GtkWidget *b, *fillbox;
848   static GtkCssProvider *detach_button_provider = NULL;
849 
850   if (detach_button_provider == NULL) {
851     detach_button_provider = gtk_css_provider_new();
852 
853     /* These toggle buttons run vertically down the side of many UI
854      * elements, so they need to be thin horizontally. */
855     gtk_css_provider_load_from_data(detach_button_provider,
856                                     ".detach_button {\n"
857                                     "  padding: 0px 0px 0px 0px;\n"
858                                     "  min-width: 6px;\n"
859                                     "}",
860                                     -1, NULL);
861   }
862 
863   b = gtk_toggle_button_new();
864   gtk_style_context_add_provider(gtk_widget_get_style_context(b),
865                                  GTK_STYLE_PROVIDER(detach_button_provider),
866                                  GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
867   gtk_style_context_add_class(gtk_widget_get_style_context(b),
868                               "detach_button");
869 
870   gtk_container_add(GTK_CONTAINER(tearbox), b);
871   g_signal_connect(b, "toggled", G_CALLBACK(tearoff_callback), tearbox);
872 
873   fillbox = gtk_grid_new();
874   gtk_orientable_set_orientation(GTK_ORIENTABLE(fillbox),
875                                  GTK_ORIENTATION_VERTICAL);
876 
877   gtk_container_add(GTK_CONTAINER(tearbox), fillbox);
878 
879   return fillbox;
880 }
881 
882 /**************************************************************************
883   Called to build the unit_below pixmap table.  This is the table on the
884   left of the screen that shows all of the inactive units in the current
885   tile.
886 
887   It may be called again if the tileset changes.
888 **************************************************************************/
populate_unit_image_table(void)889 static void populate_unit_image_table(void)
890 {
891   int i, width;
892   GtkWidget *table = unit_image_table;
893   GdkPixbuf *pix;
894 
895   /* get width of the overview window */
896   width = (overview_canvas_store_width > GUI_GTK_OVERVIEW_MIN_XSIZE) ? overview_canvas_store_width
897                                                : GUI_GTK_OVERVIEW_MIN_XSIZE;
898 
899   if (GUI_GTK_OPTION(small_display_layout)) {
900     /* We want arrow to appear if there is other units in addition
901        to active one in tile. Active unit is not counted, so there
902        can be 0 other units to not to display arrow. */
903     num_units_below = 1 - 1;
904   } else {
905     num_units_below = width / (int) tileset_tile_width(tileset);
906     num_units_below = CLIP(1, num_units_below, MAX_NUM_UNITS_BELOW);
907   }
908 
909   /* Top row: the active unit. */
910   /* Note, we ref this and other widgets here so that we can unref them
911    * in reset_unit_table. */
912   unit_image = gtk_image_new();
913   gtk_widget_add_events(unit_image, GDK_BUTTON_PRESS_MASK);
914   g_object_ref(unit_image);
915   unit_image_button = gtk_event_box_new();
916   gtk_widget_set_size_request(unit_image_button,
917                               tileset_tile_width(tileset), -1);
918   gtk_event_box_set_visible_window(GTK_EVENT_BOX(unit_image_button), FALSE);
919   g_object_ref(unit_image_button);
920   gtk_container_add(GTK_CONTAINER(unit_image_button), unit_image);
921   gtk_grid_attach(GTK_GRID(table), unit_image_button, 0, 0, 1, 1);
922   g_signal_connect(unit_image_button, "button_press_event",
923                    G_CALLBACK(select_unit_image_callback),
924                    GINT_TO_POINTER(-1));
925 
926   if (!GUI_GTK_OPTION(small_display_layout)) {
927     /* Bottom row: other units in the same tile. */
928     for (i = 0; i < num_units_below; i++) {
929       unit_below_image[i] = gtk_image_new();
930       g_object_ref(unit_below_image[i]);
931       gtk_widget_add_events(unit_below_image[i], GDK_BUTTON_PRESS_MASK);
932       unit_below_image_button[i] = gtk_event_box_new();
933       g_object_ref(unit_below_image_button[i]);
934       gtk_widget_set_size_request(unit_below_image_button[i],
935                                   tileset_tile_width(tileset), -1);
936       gtk_event_box_set_visible_window(GTK_EVENT_BOX(unit_below_image_button[i]), FALSE);
937       gtk_container_add(GTK_CONTAINER(unit_below_image_button[i]),
938                         unit_below_image[i]);
939       g_signal_connect(unit_below_image_button[i],
940                        "button_press_event",
941                        G_CALLBACK(select_unit_image_callback),
942                        GINT_TO_POINTER(i));
943 
944       gtk_grid_attach(GTK_GRID(table), unit_below_image_button[i],
945                       i, 1, 1, 1);
946     }
947   }
948 
949   /* create arrow (popup for all units on the selected tile) */
950   pix = sprite_get_pixbuf(get_arrow_sprite(tileset, ARROW_RIGHT));
951   more_arrow_pixmap = gtk_image_new_from_pixbuf(pix);
952   g_object_ref(more_arrow_pixmap);
953   more_arrow_pixmap_button = gtk_event_box_new();
954   g_object_ref(more_arrow_pixmap_button);
955   gtk_event_box_set_visible_window(GTK_EVENT_BOX(more_arrow_pixmap_button),
956                                    FALSE);
957   gtk_container_add(GTK_CONTAINER(more_arrow_pixmap_button),
958                     more_arrow_pixmap);
959   g_signal_connect(more_arrow_pixmap_button,
960                    "button_press_event",
961                    G_CALLBACK(select_more_arrow_pixmap_callback), NULL);
962   /* An extra layer so that we can hide the clickable button but keep
963    * an explicit size request to avoid the layout jumping around */
964   more_arrow_pixmap_container = gtk_frame_new(NULL);
965   gtk_frame_set_shadow_type(GTK_FRAME(more_arrow_pixmap_container), GTK_SHADOW_NONE);
966   gtk_widget_set_halign(more_arrow_pixmap_container, GTK_ALIGN_CENTER);
967   gtk_widget_set_valign(more_arrow_pixmap_container, GTK_ALIGN_CENTER);
968   g_object_ref(more_arrow_pixmap_container);
969   gtk_container_add(GTK_CONTAINER(more_arrow_pixmap_container),
970                     more_arrow_pixmap_button);
971   gtk_widget_set_size_request(more_arrow_pixmap_container,
972                               gdk_pixbuf_get_width(pix), -1);
973   g_object_unref(G_OBJECT(pix));
974 
975   if (!GUI_GTK_OPTION(small_display_layout)) {
976     /* Display on bottom row. */
977     gtk_grid_attach(GTK_GRID(table), more_arrow_pixmap_container,
978                     MAX_NUM_UNITS_BELOW, 1, 1, 1);
979   } else {
980     /* Display on top row (there is no bottom row). */
981     gtk_grid_attach(GTK_GRID(table), more_arrow_pixmap_container,
982                     MAX_NUM_UNITS_BELOW, 0, 1, 1);
983   }
984 
985   gtk_widget_show_all(table);
986 }
987 
988 /**************************************************************************
989   Free unit pixmap table.
990 **************************************************************************/
free_unit_table(void)991 static void free_unit_table(void)
992 {
993   if (unit_image_button) {
994     gtk_container_remove(GTK_CONTAINER(unit_image_table),
995                          unit_image_button);
996     g_object_unref(unit_image);
997     g_object_unref(unit_image_button);
998     if (!GUI_GTK_OPTION(small_display_layout)) {
999       int i;
1000 
1001       for (i = 0; i < num_units_below; i++) {
1002         gtk_container_remove(GTK_CONTAINER(unit_image_table),
1003                              unit_below_image_button[i]);
1004         g_object_unref(unit_below_image[i]);
1005         g_object_unref(unit_below_image_button[i]);
1006       }
1007     }
1008     gtk_container_remove(GTK_CONTAINER(unit_image_table),
1009                          more_arrow_pixmap_container);
1010     g_object_unref(more_arrow_pixmap);
1011     g_object_unref(more_arrow_pixmap_button);
1012     g_object_unref(more_arrow_pixmap_container);
1013   }
1014 }
1015 
1016 /**************************************************************************
1017   Called when the tileset is changed to reset the unit pixmap table.
1018 **************************************************************************/
reset_unit_table(void)1019 void reset_unit_table(void)
1020 {
1021   /* Unreference all of the widgets that we're about to reallocate, thus
1022    * avoiding a memory leak. Remove them from the container first, just
1023    * to be safe. Note, the widgets are ref'd in
1024    * populatate_unit_image_table. */
1025   free_unit_table();
1026 
1027   populate_unit_image_table();
1028 
1029   /* We have to force a redraw of the units.  And we explicitly have
1030    * to force a redraw of the focus unit, which is normally only
1031    * redrawn when the focus changes. We also have to force the 'more'
1032    * arrow to go away, both by expicitly hiding it and telling it to
1033    * do so (this will be reset immediately afterwards if necessary,
1034    * but we have to make the *internal* state consistent). */
1035   gtk_widget_hide(more_arrow_pixmap_button);
1036   set_unit_icons_more_arrow(FALSE);
1037   if (get_num_units_in_focus() == 1) {
1038     set_unit_icon(-1, head_of_units_in_focus());
1039   } else {
1040     set_unit_icon(-1, NULL);
1041   }
1042   update_unit_pix_label(get_units_in_focus());
1043 }
1044 
1045 /**************************************************************************
1046   Enable/Disable the game page menu bar.
1047 **************************************************************************/
enable_menus(bool enable)1048 void enable_menus(bool enable)
1049 {
1050   if (enable) {
1051     main_menubar = setup_menus(toplevel);
1052     /* Ensure the menus are really created before performing any operations
1053      * on them. */
1054     while (gtk_events_pending()) {
1055       gtk_main_iteration();
1056     }
1057     gtk_grid_attach_next_to(GTK_GRID(top_vbox), main_menubar, NULL, GTK_POS_TOP, 1, 1);
1058     menus_init();
1059     gtk_widget_show_all(main_menubar);
1060   } else {
1061     gtk_widget_destroy(main_menubar);
1062   }
1063 }
1064 
1065 /**************************************************************************
1066   Workaround for a crash that occurs when a button release event is
1067   emitted for a notebook with no pages. See PR#40743.
1068   FIXME: Remove this hack once gtk_notebook_button_release() in
1069   gtk/gtknotebook.c checks for NULL notebook->cur_page.
1070 **************************************************************************/
right_notebook_button_release(GtkWidget * widget,GdkEventButton * event)1071 static gboolean right_notebook_button_release(GtkWidget *widget,
1072                                               GdkEventButton *event)
1073 {
1074   if (event->type != GDK_BUTTON_RELEASE) {
1075     return FALSE;
1076   }
1077 
1078   if (!GTK_IS_NOTEBOOK(widget)
1079       || -1 == gtk_notebook_get_current_page(GTK_NOTEBOOK(widget))) {
1080     /* Make sure the default gtk handler
1081      * does NOT get called in this case. */
1082     return TRUE;
1083   }
1084 
1085   return FALSE;
1086 }
1087 
1088 /**************************************************************************
1089   Override background color for canvases
1090 **************************************************************************/
1091 #if 0
1092 static void setup_canvas_color_for_state(GtkStateFlags state)
1093 {
1094   gtk_widget_override_background_color(GTK_WIDGET(overview_canvas), state,
1095                                        &get_color(tileset, COLOR_OVERVIEW_UNKNOWN)->color);
1096   gtk_widget_override_background_color(GTK_WIDGET(map_canvas), state,
1097                                        &get_color(tileset, COLOR_OVERVIEW_UNKNOWN)->color);
1098 }
1099 #endif
1100 
1101 /**************************************************************************
1102   Do the heavy lifting for the widget setup.
1103 **************************************************************************/
setup_widgets(void)1104 static void setup_widgets(void)
1105 {
1106   GtkWidget *page, *ebox, *hgrid, *hgrid2, *label;
1107   GtkWidget *frame, *table, *table2, *paned, *hpaned, *sw, *text;
1108   GtkWidget *button, *view, *vgrid, *right_vbox = NULL;
1109   int i;
1110   char buf[256];
1111   GtkWidget *notebook, *statusbar;
1112   GtkWidget *dtach_lowbox = NULL;
1113   GdkPixbuf *pb;
1114 
1115   message_buffer = gtk_text_buffer_new(NULL);
1116 
1117   notebook = gtk_notebook_new();
1118 
1119   /* stop mouse wheel notebook page switching. */
1120   g_signal_connect(notebook, "scroll_event",
1121 		   G_CALLBACK(gtk_true), NULL);
1122 
1123   toplevel_tabs = notebook;
1124   gtk_notebook_set_show_tabs(GTK_NOTEBOOK(notebook), FALSE);
1125   gtk_notebook_set_show_border(GTK_NOTEBOOK(notebook), FALSE);
1126   vgrid = gtk_grid_new();
1127   gtk_orientable_set_orientation(GTK_ORIENTABLE(vgrid),
1128                                  GTK_ORIENTATION_VERTICAL);
1129   gtk_grid_set_row_spacing(GTK_GRID(vgrid), 4);
1130   gtk_container_add(GTK_CONTAINER(toplevel), vgrid);
1131   gtk_container_add(GTK_CONTAINER(vgrid), notebook);
1132   statusbar = create_statusbar();
1133   gtk_container_add(GTK_CONTAINER(vgrid), statusbar);
1134 
1135   gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
1136       create_main_page(), NULL);
1137   gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
1138       create_start_page(), NULL);
1139   gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
1140       create_scenario_page(), NULL);
1141   gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
1142       create_load_page(), NULL);
1143   gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
1144       create_network_page(), NULL);
1145 
1146   editgui_create_widgets();
1147 
1148   ingame_votebar = voteinfo_bar_new(FALSE);
1149   g_object_set(ingame_votebar, "margin", 2, NULL);
1150 
1151   /* *** everything in the top *** */
1152 
1153   page = gtk_scrolled_window_new(NULL, NULL);
1154   gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(page),
1155                                  GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1156   gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(page),
1157                                       GTK_SHADOW_ETCHED_IN);
1158   gtk_notebook_append_page(GTK_NOTEBOOK(notebook), page, NULL);
1159 
1160   top_vbox = gtk_grid_new();
1161   gtk_orientable_set_orientation(GTK_ORIENTABLE(top_vbox),
1162                                  GTK_ORIENTATION_VERTICAL);
1163   gtk_grid_set_row_spacing(GTK_GRID(top_vbox), 5);
1164   hgrid = gtk_grid_new();
1165 
1166   if (GUI_GTK_OPTION(small_display_layout)) {
1167     /* The window is divided into two horizontal panels: overview +
1168      * civinfo + unitinfo, main view + message window. */
1169     right_vbox = gtk_grid_new();
1170     gtk_orientable_set_orientation(GTK_ORIENTABLE(right_vbox),
1171                                    GTK_ORIENTATION_VERTICAL);
1172     gtk_container_add(GTK_CONTAINER(hgrid), right_vbox);
1173 
1174     paned = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL);
1175     gtk_container_add(GTK_CONTAINER(page), top_vbox);
1176     gtk_container_add(GTK_CONTAINER(top_vbox), hgrid);
1177     gtk_container_add(GTK_CONTAINER(right_vbox), paned);
1178     gtk_container_add(GTK_CONTAINER(right_vbox), ingame_votebar);
1179 
1180     /* Overview size designed for small displays (netbooks). */
1181     overview_canvas_store_width = OVERVIEW_CANVAS_STORE_WIDTH_NETBOOK;
1182     overview_canvas_store_height = OVERVIEW_CANVAS_STORE_HEIGHT_NETBOOK;
1183   } else {
1184     /* The window is divided into two vertical panes: overview +
1185      * + civinfo + unitinfo + main view, message window. */
1186     paned = gtk_paned_new(GTK_ORIENTATION_VERTICAL);
1187     gtk_container_add(GTK_CONTAINER(page), paned);
1188     gtk_paned_pack1(GTK_PANED(paned), top_vbox, TRUE, FALSE);
1189     gtk_container_add(GTK_CONTAINER(top_vbox), hgrid);
1190 
1191     /* Overview size designed for big displays (desktops). */
1192     overview_canvas_store_width = OVERVIEW_CANVAS_STORE_WIDTH;
1193     overview_canvas_store_height = OVERVIEW_CANVAS_STORE_HEIGHT;
1194   }
1195 
1196   /* this holds the overview canvas, production info, etc. */
1197   vgrid = gtk_grid_new();
1198   gtk_orientable_set_orientation(GTK_ORIENTABLE(vgrid),
1199                                  GTK_ORIENTATION_VERTICAL);
1200   gtk_grid_set_column_spacing(GTK_GRID(vgrid), 3);
1201   /* Put vgrid to the left of anything else in hgrid -- right_vbox is either
1202    * the chat/messages pane, or NULL which is OK */
1203   gtk_grid_attach_next_to(GTK_GRID(hgrid), vgrid, right_vbox,
1204                           GTK_POS_LEFT, 1, 1);
1205 
1206   /* overview canvas */
1207   ahbox = detached_widget_new();
1208   gtk_widget_set_hexpand(ahbox, FALSE);
1209   gtk_widget_set_vexpand(ahbox, FALSE);
1210   gtk_container_add(GTK_CONTAINER(vgrid), ahbox);
1211   avbox = detached_widget_fill(ahbox);
1212 
1213   overview_scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1214   gtk_container_set_border_width(GTK_CONTAINER (overview_scrolled_window), 1);
1215   gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (overview_scrolled_window),
1216                                     GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1217 
1218   overview_canvas = gtk_drawing_area_new();
1219   gtk_widget_set_halign(overview_canvas, GTK_ALIGN_CENTER);
1220   gtk_widget_set_valign(overview_canvas, GTK_ALIGN_CENTER);
1221   gtk_widget_set_size_request(overview_canvas, overview_canvas_store_width,
1222 		              overview_canvas_store_height);
1223   gtk_widget_set_size_request(overview_scrolled_window, overview_canvas_store_width,
1224 		              overview_canvas_store_height);
1225   gtk_widget_set_hexpand(overview_canvas, TRUE);
1226   gtk_widget_set_vexpand(overview_canvas, TRUE);
1227 
1228   gtk_widget_add_events(overview_canvas, GDK_EXPOSURE_MASK
1229         			        |GDK_BUTTON_PRESS_MASK
1230 				        |GDK_POINTER_MOTION_MASK);
1231   gtk_container_add(GTK_CONTAINER(avbox), overview_scrolled_window);
1232 
1233   gtk_container_add(GTK_CONTAINER(overview_scrolled_window),
1234                     overview_canvas);
1235 
1236   g_signal_connect(overview_canvas, "draw",
1237         	   G_CALLBACK(overview_canvas_draw), NULL);
1238 
1239   g_signal_connect(overview_canvas, "motion_notify_event",
1240         	   G_CALLBACK(move_overviewcanvas), NULL);
1241 
1242   g_signal_connect(overview_canvas, "button_press_event",
1243         	   G_CALLBACK(butt_down_overviewcanvas), NULL);
1244 
1245   /* The rest */
1246 
1247   ahbox = detached_widget_new();
1248   gtk_container_add(GTK_CONTAINER(vgrid), ahbox);
1249   gtk_widget_set_hexpand(ahbox, FALSE);
1250   avbox = detached_widget_fill(ahbox);
1251   gtk_widget_set_vexpand(avbox, TRUE);
1252   gtk_widget_set_valign(avbox, GTK_ALIGN_FILL);
1253 
1254   /* Info on player's civilization, when game is running. */
1255   frame = gtk_frame_new("");
1256   gtk_container_add(GTK_CONTAINER(avbox), frame);
1257 
1258   main_frame_civ_name = frame;
1259 
1260   vgrid = gtk_grid_new();
1261   gtk_orientable_set_orientation(GTK_ORIENTABLE(vgrid),
1262                                  GTK_ORIENTATION_VERTICAL);
1263   gtk_container_add(GTK_CONTAINER(frame), vgrid);
1264   gtk_widget_set_hexpand(vgrid, TRUE);
1265 
1266   ebox = gtk_event_box_new();
1267   gtk_widget_add_events(ebox, GDK_BUTTON_PRESS_MASK);
1268   gtk_event_box_set_visible_window(GTK_EVENT_BOX(ebox), FALSE);
1269   g_signal_connect(ebox, "button_press_event",
1270                    G_CALLBACK(show_info_popup), NULL);
1271   gtk_container_add(GTK_CONTAINER(vgrid), ebox);
1272 
1273   label = gtk_label_new(NULL);
1274   gtk_widget_set_halign(label, GTK_ALIGN_START);
1275   gtk_widget_set_valign(label, GTK_ALIGN_CENTER);
1276   gtk_widget_set_margin_start(label, 2);
1277   gtk_widget_set_margin_end(label, 2);
1278   gtk_widget_set_margin_top(label, 2);
1279   gtk_widget_set_margin_bottom(label, 2);
1280   gtk_container_add(GTK_CONTAINER(ebox), label);
1281   main_label_info = label;
1282 
1283   /* Production status */
1284   table = gtk_grid_new();
1285   gtk_widget_set_halign(table, GTK_ALIGN_CENTER);
1286   gtk_grid_set_column_homogeneous(GTK_GRID(table), TRUE);
1287   gtk_container_add(GTK_CONTAINER(avbox), table);
1288 
1289   /* citizens for taxrates */
1290   ebox = gtk_event_box_new();
1291   gtk_event_box_set_visible_window(GTK_EVENT_BOX(ebox), FALSE);
1292   gtk_grid_attach(GTK_GRID(table), ebox, 0, 0, 10, 1);
1293   econ_ebox = ebox;
1294 
1295   table2 = gtk_grid_new();
1296   gtk_container_add(GTK_CONTAINER(ebox), table2);
1297 
1298   for (i = 0; i < 10; i++) {
1299     struct sprite *spr;
1300 
1301     ebox = gtk_event_box_new();
1302     gtk_event_box_set_visible_window(GTK_EVENT_BOX(ebox), FALSE);
1303     gtk_widget_add_events(ebox, GDK_BUTTON_PRESS_MASK);
1304 
1305     gtk_grid_attach(GTK_GRID(table2), ebox, i, 0, 1, 1);
1306 
1307     g_signal_connect(ebox, "button_press_event",
1308                      G_CALLBACK(taxrates_callback), GINT_TO_POINTER(i));
1309 
1310     spr = i < 5 ? get_tax_sprite(tileset, O_SCIENCE) : get_tax_sprite(tileset, O_GOLD);
1311     pb = sprite_get_pixbuf(spr);
1312     econ_label[i] = gtk_image_new_from_pixbuf(pb);
1313     g_object_unref(pb);
1314     gtk_container_add(GTK_CONTAINER(ebox), econ_label[i]);
1315   }
1316 
1317   /* science, environmental, govt, timeout */
1318   pb = sprite_get_pixbuf(client_research_sprite());
1319   bulb_label = gtk_image_new_from_pixbuf(pb);
1320   g_object_unref(pb);
1321   pb = sprite_get_pixbuf(client_warming_sprite());
1322   sun_label = gtk_image_new_from_pixbuf(pb);
1323   g_object_unref(pb);
1324   pb = sprite_get_pixbuf(client_cooling_sprite());
1325   flake_label = gtk_image_new_from_pixbuf(pb);
1326   g_object_unref(pb);
1327   pb = sprite_get_pixbuf(client_government_sprite());
1328   government_label = gtk_image_new_from_pixbuf(pb);
1329   g_object_unref(pb);
1330 
1331   for (i = 0; i < 4; i++) {
1332     GtkWidget *w;
1333 
1334     ebox = gtk_event_box_new();
1335     gtk_event_box_set_visible_window(GTK_EVENT_BOX(ebox), FALSE);
1336 
1337     switch (i) {
1338     case 0:
1339       w = bulb_label;
1340       bulb_ebox = ebox;
1341       break;
1342     case 1:
1343       w = sun_label;
1344       sun_ebox = ebox;
1345       break;
1346     case 2:
1347       w = flake_label;
1348       flake_ebox = ebox;
1349       break;
1350     default:
1351     case 3:
1352       w = government_label;
1353       government_ebox = ebox;
1354       break;
1355     }
1356 
1357     gtk_widget_set_halign(w, GTK_ALIGN_START);
1358     gtk_widget_set_valign(w, GTK_ALIGN_START);
1359     gtk_widget_set_margin_start(w, 0);
1360     gtk_widget_set_margin_end(w, 0);
1361     gtk_widget_set_margin_top(w, 0);
1362     gtk_widget_set_margin_bottom(w, 0);
1363     gtk_container_add(GTK_CONTAINER(ebox), w);
1364     gtk_grid_attach(GTK_GRID(table), ebox, i, 1, 1, 1);
1365   }
1366 
1367   timeout_label = gtk_label_new("");
1368 
1369   frame = gtk_frame_new(NULL);
1370   gtk_grid_attach(GTK_GRID(table), frame, 4, 1, 6, 1);
1371   gtk_container_add(GTK_CONTAINER(frame), timeout_label);
1372 
1373 
1374   /* turn done */
1375   turn_done_button = gtk_button_new_with_label(_("Turn Done"));
1376 
1377   gtk_grid_attach(GTK_GRID(table), turn_done_button, 0, 2, 10, 1);
1378 
1379   g_signal_connect(turn_done_button, "clicked",
1380                    G_CALLBACK(end_turn_callback), NULL);
1381 
1382   fc_snprintf(buf, sizeof(buf), "%s:\n%s",
1383               _("Turn Done"), _("Shift+Return"));
1384   gtk_widget_set_tooltip_text(turn_done_button, buf);
1385 
1386   /* Selected unit status */
1387 
1388   unit_info_box = gtk_grid_new();
1389   gtk_widget_set_hexpand(unit_info_box, FALSE);
1390   gtk_orientable_set_orientation(GTK_ORIENTABLE(unit_info_box),
1391                                  GTK_ORIENTATION_VERTICAL);
1392   gtk_container_add(GTK_CONTAINER(avbox), unit_info_box);
1393 
1394   /* In edit mode the unit_info_box widget is replaced by the
1395    * editinfobox, so we need to add a ref here so that it is
1396    * not destroyed when removed from its container.
1397    * See editinfobox_refresh(). */
1398   g_object_ref(unit_info_box);
1399 
1400   unit_info_frame = gtk_frame_new("");
1401   gtk_container_add(GTK_CONTAINER(unit_info_box), unit_info_frame);
1402 
1403   sw = gtk_scrolled_window_new(NULL, NULL);
1404   gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw),
1405                                       GTK_SHADOW_OUT);
1406   gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
1407                                  GTK_POLICY_AUTOMATIC, GTK_POLICY_NEVER);
1408   gtk_container_add(GTK_CONTAINER(unit_info_frame), sw);
1409 
1410   label = gtk_label_new(NULL);
1411   gtk_widget_set_hexpand(label, TRUE);
1412   gtk_widget_set_halign(label, GTK_ALIGN_START);
1413   gtk_widget_set_valign(label, GTK_ALIGN_CENTER);
1414   gtk_widget_set_margin_start(label, 2);
1415   gtk_widget_set_margin_end(label, 2);
1416   gtk_widget_set_margin_top(label, 2);
1417   gtk_widget_set_margin_bottom(label, 2);
1418   gtk_container_add(GTK_CONTAINER(sw), label);
1419   unit_info_label = label;
1420 
1421   hgrid2 = gtk_grid_new();
1422   gtk_container_add(GTK_CONTAINER(unit_info_box), hgrid2);
1423 
1424   table = gtk_grid_new();
1425   g_object_set(table, "margin", 5, NULL);
1426   gtk_container_add(GTK_CONTAINER(hgrid2), table);
1427 
1428   gtk_grid_set_row_spacing(GTK_GRID(table), 2);
1429   gtk_grid_set_column_spacing(GTK_GRID(table), 2);
1430 
1431   unit_image_table = table;
1432 
1433   /* Map canvas, editor toolbar, and scrollbars */
1434 
1435   /* The top notebook containing the map view and dialogs. */
1436 
1437   top_notebook = gtk_notebook_new();
1438   gtk_notebook_set_tab_pos(GTK_NOTEBOOK(top_notebook), GTK_POS_BOTTOM);
1439   gtk_notebook_set_scrollable(GTK_NOTEBOOK(top_notebook), TRUE);
1440 
1441 
1442   if (GUI_GTK_OPTION(small_display_layout)) {
1443     gtk_paned_pack1(GTK_PANED(paned), top_notebook, TRUE, FALSE);
1444   } else if (GUI_GTK_OPTION(message_chat_location) == GUI_GTK_MSGCHAT_MERGED) {
1445     right_vbox = gtk_grid_new();
1446     gtk_orientable_set_orientation(GTK_ORIENTABLE(right_vbox),
1447                                    GTK_ORIENTATION_VERTICAL);
1448 
1449     gtk_container_add(GTK_CONTAINER(right_vbox), top_notebook);
1450     gtk_container_add(GTK_CONTAINER(right_vbox), ingame_votebar);
1451     gtk_container_add(GTK_CONTAINER(hgrid), right_vbox);
1452   } else {
1453     gtk_container_add(GTK_CONTAINER(hgrid), top_notebook);
1454   }
1455 
1456   map_widget = gtk_grid_new();
1457 
1458   vgrid = gtk_grid_new();
1459   gtk_orientable_set_orientation(GTK_ORIENTABLE(vgrid),
1460                                  GTK_ORIENTATION_VERTICAL);
1461   gtk_container_add(GTK_CONTAINER(vgrid), map_widget);
1462 
1463   gtk_container_add(GTK_CONTAINER(vgrid), editgui_get_editbar()->widget);
1464   g_object_set(editgui_get_editbar()->widget, "margin", 4, NULL);
1465 
1466   label = gtk_label_new(Q_("?noun:View"));
1467   gtk_notebook_append_page(GTK_NOTEBOOK(top_notebook), vgrid, label);
1468 
1469   frame = gtk_frame_new(NULL);
1470   gtk_grid_attach(GTK_GRID(map_widget), frame, 0, 0, 1, 1);
1471 
1472   map_canvas = gtk_drawing_area_new();
1473   gtk_widget_set_hexpand(map_canvas, TRUE);
1474   gtk_widget_set_vexpand(map_canvas, TRUE);
1475   gtk_widget_set_size_request(map_canvas, 300, 300);
1476   gtk_widget_set_can_focus(map_canvas, TRUE);
1477 
1478 #if 0
1479   setup_canvas_color_for_state(GTK_STATE_FLAG_NORMAL);
1480   setup_canvas_color_for_state(GTK_STATE_FLAG_ACTIVE);
1481   setup_canvas_color_for_state(GTK_STATE_FLAG_PRELIGHT);
1482   setup_canvas_color_for_state(GTK_STATE_FLAG_SELECTED);
1483   setup_canvas_color_for_state(GTK_STATE_FLAG_INSENSITIVE);
1484   setup_canvas_color_for_state(GTK_STATE_FLAG_INCONSISTENT);
1485   setup_canvas_color_for_state(GTK_STATE_FLAG_FOCUSED);
1486   setup_canvas_color_for_state(GTK_STATE_FLAG_BACKDROP);
1487 #endif
1488 
1489   gtk_widget_add_events(map_canvas, GDK_EXPOSURE_MASK
1490                                    |GDK_BUTTON_PRESS_MASK
1491                                    |GDK_BUTTON_RELEASE_MASK
1492                                    |GDK_KEY_PRESS_MASK
1493                                    |GDK_POINTER_MOTION_MASK
1494                                    |GDK_SCROLL_MASK);
1495 
1496   gtk_container_add(GTK_CONTAINER(frame), map_canvas);
1497 
1498   map_horizontal_scrollbar =
1499       gtk_scrollbar_new(GTK_ORIENTATION_HORIZONTAL, NULL);
1500   gtk_grid_attach(GTK_GRID(map_widget), map_horizontal_scrollbar, 0, 1, 1, 1);
1501 
1502   map_vertical_scrollbar =
1503       gtk_scrollbar_new(GTK_ORIENTATION_VERTICAL, NULL);
1504   gtk_grid_attach(GTK_GRID(map_widget), map_vertical_scrollbar, 1, 0, 1, 1);
1505 
1506   g_signal_connect(map_canvas, "draw",
1507                    G_CALLBACK(map_canvas_draw), NULL);
1508 
1509   g_signal_connect(map_canvas, "configure_event",
1510                    G_CALLBACK(map_canvas_configure), NULL);
1511 
1512   g_signal_connect(map_canvas, "motion_notify_event",
1513                    G_CALLBACK(move_mapcanvas), NULL);
1514 
1515   g_signal_connect(toplevel, "enter_notify_event",
1516                    G_CALLBACK(leave_mapcanvas), NULL);
1517 
1518   g_signal_connect(map_canvas, "button_press_event",
1519                    G_CALLBACK(butt_down_mapcanvas), NULL);
1520 
1521   g_signal_connect(map_canvas, "button_release_event",
1522                    G_CALLBACK(butt_release_mapcanvas), NULL);
1523 
1524   g_signal_connect(map_canvas, "scroll_event",
1525                    G_CALLBACK(mouse_scroll_mapcanvas), NULL);
1526 
1527   g_signal_connect(toplevel, "key_press_event",
1528                    G_CALLBACK(toplevel_key_press_handler), NULL);
1529 
1530   g_signal_connect(toplevel, "key_release_event",
1531                    G_CALLBACK(toplevel_key_release_handler), NULL);
1532 
1533   /* *** The message window -- this is a detachable widget *** */
1534 
1535   if (GUI_GTK_OPTION(message_chat_location) == GUI_GTK_MSGCHAT_MERGED) {
1536     bottom_hpaned = hpaned = paned;
1537     right_notebook = bottom_notebook = top_notebook;
1538   } else {
1539     dtach_lowbox = detached_widget_new();
1540     gtk_paned_pack2(GTK_PANED(paned), dtach_lowbox, FALSE, TRUE);
1541     avbox = detached_widget_fill(dtach_lowbox);
1542 
1543     vgrid = gtk_grid_new();
1544     gtk_orientable_set_orientation(GTK_ORIENTABLE(vgrid),
1545                                    GTK_ORIENTATION_VERTICAL);
1546     if (!GUI_GTK_OPTION(small_display_layout)) {
1547       gtk_container_add(GTK_CONTAINER(vgrid), ingame_votebar);
1548     }
1549     gtk_container_add(GTK_CONTAINER(avbox), vgrid);
1550 
1551     if (GUI_GTK_OPTION(small_display_layout)) {
1552       hpaned = gtk_paned_new(GTK_ORIENTATION_VERTICAL);
1553     } else {
1554       hpaned = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL);
1555     }
1556     gtk_container_add(GTK_CONTAINER(vgrid), hpaned);
1557     g_object_set(hpaned, "margin", 4, NULL);
1558     bottom_hpaned = hpaned;
1559 
1560     bottom_notebook = gtk_notebook_new();
1561     gtk_notebook_set_tab_pos(GTK_NOTEBOOK(bottom_notebook), GTK_POS_TOP);
1562     gtk_notebook_set_scrollable(GTK_NOTEBOOK(bottom_notebook), TRUE);
1563     gtk_paned_pack1(GTK_PANED(hpaned), bottom_notebook, TRUE, TRUE);
1564 
1565     right_notebook = gtk_notebook_new();
1566     g_object_ref(right_notebook);
1567     gtk_notebook_set_tab_pos(GTK_NOTEBOOK(right_notebook), GTK_POS_TOP);
1568     gtk_notebook_set_scrollable(GTK_NOTEBOOK(right_notebook), TRUE);
1569     g_signal_connect(right_notebook, "button-release-event",
1570                      G_CALLBACK(right_notebook_button_release), NULL);
1571     if (GUI_GTK_OPTION(message_chat_location) == GUI_GTK_MSGCHAT_SPLIT) {
1572       gtk_paned_pack2(GTK_PANED(hpaned), right_notebook, TRUE, TRUE);
1573     }
1574   }
1575 
1576   vgrid = gtk_grid_new();
1577   gtk_orientable_set_orientation(GTK_ORIENTABLE(vgrid),
1578                                  GTK_ORIENTATION_VERTICAL);
1579 
1580   sw = gtk_scrolled_window_new(NULL, NULL);
1581   gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw),
1582 				      GTK_SHADOW_ETCHED_IN);
1583   gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_AUTOMATIC,
1584   				 GTK_POLICY_ALWAYS);
1585   gtk_container_add(GTK_CONTAINER(vgrid), sw);
1586 
1587   label = gtk_label_new(_("Chat"));
1588   gtk_notebook_append_page(GTK_NOTEBOOK(bottom_notebook), vgrid, label);
1589 
1590   text = gtk_text_view_new_with_buffer(message_buffer);
1591   gtk_widget_set_hexpand(text, TRUE);
1592   gtk_widget_set_vexpand(text, TRUE);
1593   set_message_buffer_view_link_handlers(text);
1594   gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
1595   gtk_container_add(GTK_CONTAINER(sw), text);
1596   g_signal_connect(text, "size-allocate",
1597                    G_CALLBACK(main_message_area_size_allocate), NULL);
1598 
1599   gtk_widget_set_name(text, "chatline");
1600 
1601   gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD);
1602   gtk_widget_realize(text);
1603   gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 5);
1604 
1605   main_message_area = GTK_TEXT_VIEW(text);
1606   if (dtach_lowbox != NULL) {
1607     g_object_set_data(G_OBJECT(dtach_lowbox), "hide-over-reparent", main_message_area);
1608   }
1609 
1610   chat_welcome_message(TRUE);
1611 
1612   /* the chat line */
1613   view = inputline_toolkit_view_new();
1614   gtk_container_add(GTK_CONTAINER(vgrid), view);
1615   g_object_set(view, "margin", 3, NULL);
1616 
1617   button = gtk_check_button_new_with_label(_("Allies Only"));
1618   gtk_widget_set_focus_on_click(button, FALSE);
1619   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button),
1620                                GUI_GTK_OPTION(allied_chat_only));
1621   g_signal_connect(button, "toggled",
1622                    G_CALLBACK(allied_chat_button_toggled), NULL);
1623   inputline_toolkit_view_append_button(view, button);
1624   allied_chat_toggle_button = button;
1625 
1626   button = gtk_button_new_with_label(_("Clear links"));
1627   g_signal_connect(button, "clicked",
1628                    G_CALLBACK(link_marks_clear_all), NULL);
1629   inputline_toolkit_view_append_button(view, button);
1630 
1631   /* Other things to take care of */
1632 
1633   gtk_widget_show_all(gtk_bin_get_child(GTK_BIN(toplevel)));
1634 
1635   if (GUI_GTK_OPTION(enable_tabs)) {
1636     meswin_dialog_popup(FALSE);
1637   }
1638 
1639   gtk_notebook_set_current_page(GTK_NOTEBOOK(top_notebook), 0);
1640   gtk_notebook_set_current_page(GTK_NOTEBOOK(bottom_notebook), 0);
1641 
1642   if (!GUI_GTK_OPTION(map_scrollbars)) {
1643     gtk_widget_hide(map_horizontal_scrollbar);
1644     gtk_widget_hide(map_vertical_scrollbar);
1645   }
1646 }
1647 
1648 /**************************************************************************
1649  called from main().
1650 **************************************************************************/
ui_init(void)1651 void ui_init(void)
1652 {
1653   log_set_callback(log_callback_utf8);
1654   set_frame_by_frame_animation();
1655 }
1656 
1657 /**************************************************************************
1658   Entry point for whole freeciv client program.
1659 **************************************************************************/
main(int argc,char ** argv)1660 int main(int argc, char **argv)
1661 {
1662   return client_main(argc, argv);
1663 }
1664 
1665 /**************************************************************************
1666   Migrate gtk3 client specific options from gtk2 client options.
1667 **************************************************************************/
migrate_options_from_gtk2(void)1668 static void migrate_options_from_gtk2(void)
1669 {
1670   log_normal(_("Migrating options from gtk2 to gtk3 client"));
1671 
1672 #define MIGRATE_OPTION(opt) gui_options.gui_gtk3_##opt = gui_options.gui_gtk2_##opt;
1673 #define MIGRATE_STR_OPTION(opt) \
1674   strncpy(gui_options.gui_gtk3_##opt, gui_options.gui_gtk2_##opt, \
1675           sizeof(gui_options.gui_gtk3_##opt));
1676 
1677   /* Default theme name is never migrated */
1678   /* 'fullscreen', 'small_display_layout', and 'message_chat_location'
1679    * not migrated, as (unlike Gtk2), Gtk3-client tries to pick better
1680    * defaults for these in fresh installations based on screen size (see
1681    * adjust_default_options()); so user is probably better served by
1682    * getting these adaptive defaults than whatever they had for Gtk2.
1683    * Since 'fullscreen' isn't migrated, we don't need to worry about
1684    * preserving gui_gtk2_migrated_from_2_5 either. */
1685   MIGRATE_OPTION(map_scrollbars);
1686   MIGRATE_OPTION(dialogs_on_top);
1687   MIGRATE_OPTION(show_task_icons);
1688   MIGRATE_OPTION(enable_tabs);
1689   MIGRATE_OPTION(show_chat_message_time);
1690   MIGRATE_OPTION(new_messages_go_to_top);
1691   MIGRATE_OPTION(show_message_window_buttons);
1692   MIGRATE_OPTION(metaserver_tab_first);
1693   MIGRATE_OPTION(allied_chat_only);
1694   MIGRATE_OPTION(mouse_over_map_focus);
1695   MIGRATE_OPTION(chatline_autocompletion);
1696   MIGRATE_OPTION(citydlg_xsize);
1697   MIGRATE_OPTION(citydlg_ysize);
1698   MIGRATE_OPTION(popup_tech_help);
1699 
1700   MIGRATE_STR_OPTION(font_city_label);
1701   MIGRATE_STR_OPTION(font_notify_label);
1702   MIGRATE_STR_OPTION(font_spaceship_label);
1703   MIGRATE_STR_OPTION(font_help_label);
1704   MIGRATE_STR_OPTION(font_help_link);
1705   MIGRATE_STR_OPTION(font_help_text);
1706   MIGRATE_STR_OPTION(font_chatline);
1707   MIGRATE_STR_OPTION(font_beta_label);
1708   MIGRATE_STR_OPTION(font_small);
1709   MIGRATE_STR_OPTION(font_comment_label);
1710   MIGRATE_STR_OPTION(font_city_names);
1711   MIGRATE_STR_OPTION(font_city_productions);
1712   MIGRATE_STR_OPTION(font_reqtree_text);
1713 
1714 #undef MIGRATE_OPTION
1715 #undef MIGRATE_STR_OPTION
1716 
1717   gui_options.gui_gtk3_migrated_from_gtk2 = TRUE;
1718 }
1719 
1720 /**************************************************************************
1721   Migrate gtk3.22 client specific options from gtk3 client options.
1722 **************************************************************************/
migrate_options_from_gtk3(void)1723 static void migrate_options_from_gtk3(void)
1724 {
1725   log_normal(_("Migrating options from gtk3 to gtk3.22 client"));
1726 
1727 #define MIGRATE_OPTION(opt) GUI_GTK_OPTION(opt) = gui_options.gui_gtk3_##opt;
1728 #define MIGRATE_STR_OPTION(opt) \
1729   strncpy(GUI_GTK_OPTION(opt), gui_options.gui_gtk3_##opt,      \
1730           sizeof(GUI_GTK_OPTION(opt)));
1731 
1732   /* Default theme name is never migrated */
1733 
1734   /* Simulate gui-gtk3's migrate_options_from_2_5() */
1735   if (!gui_options.gui_gtk3_migrated_from_2_5) {
1736     log_normal(_("Migrating gtk3-client options from freeciv-2.5 options."));
1737     gui_options.gui_gtk3_fullscreen = gui_options.migrate_fullscreen;
1738     gui_options.gui_gtk3_migrated_from_2_5 = TRUE;
1739   }
1740 
1741   MIGRATE_OPTION(fullscreen);
1742   MIGRATE_OPTION(map_scrollbars);
1743   MIGRATE_OPTION(dialogs_on_top);
1744   MIGRATE_OPTION(show_task_icons);
1745   MIGRATE_OPTION(enable_tabs);
1746   MIGRATE_OPTION(show_chat_message_time);
1747   MIGRATE_OPTION(new_messages_go_to_top);
1748   MIGRATE_OPTION(show_message_window_buttons);
1749   MIGRATE_OPTION(metaserver_tab_first);
1750   MIGRATE_OPTION(allied_chat_only);
1751   MIGRATE_OPTION(message_chat_location);
1752   MIGRATE_OPTION(small_display_layout);
1753   MIGRATE_OPTION(mouse_over_map_focus);
1754   MIGRATE_OPTION(chatline_autocompletion);
1755   MIGRATE_OPTION(citydlg_xsize);
1756   MIGRATE_OPTION(citydlg_ysize);
1757   MIGRATE_OPTION(popup_tech_help);
1758 
1759   MIGRATE_STR_OPTION(font_city_label);
1760   MIGRATE_STR_OPTION(font_notify_label);
1761   MIGRATE_STR_OPTION(font_spaceship_label);
1762   MIGRATE_STR_OPTION(font_help_label);
1763   MIGRATE_STR_OPTION(font_help_link);
1764   MIGRATE_STR_OPTION(font_help_text);
1765   MIGRATE_STR_OPTION(font_chatline);
1766   MIGRATE_STR_OPTION(font_beta_label);
1767   MIGRATE_STR_OPTION(font_small);
1768   MIGRATE_STR_OPTION(font_comment_label);
1769   MIGRATE_STR_OPTION(font_city_names);
1770   MIGRATE_STR_OPTION(font_city_productions);
1771   MIGRATE_STR_OPTION(font_reqtree_text);
1772 
1773 #undef MIGRATE_OPTION
1774 #undef MIGRATE_STR_OPTION
1775 
1776   GUI_GTK_OPTION(migrated_from_gtk3) = TRUE;
1777 }
1778 
1779 /**************************************************************************
1780   Called from client_main(), is what it's named.
1781 **************************************************************************/
ui_main(int argc,char ** argv)1782 void ui_main(int argc, char **argv)
1783 {
1784   PangoFontDescription *toplevel_font_name;
1785   guint sig;
1786 
1787   parse_options(argc, argv);
1788 
1789   /* the locale has already been set in init_nls() and the Win32-specific
1790    * locale logic in gtk_init() causes problems with zh_CN (see PR#39475) */
1791   gtk_disable_setlocale();
1792 
1793   /* GTK withdraw gtk options. Process GTK arguments */
1794   gtk_init(&argc, &argv);
1795 
1796   dlg_tab_provider_prepare();
1797 
1798   toplevel = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1799   gtk_window_set_position(GTK_WINDOW(toplevel), GTK_WIN_POS_CENTER);
1800   if (vmode.width > 0 && vmode.height > 0) {
1801     gtk_window_resize(GTK_WINDOW(toplevel), vmode.width, vmode.height);
1802   }
1803   g_signal_connect(toplevel, "key_press_event",
1804                    G_CALLBACK(toplevel_handler), NULL);
1805 
1806   gtk_window_set_role(GTK_WINDOW(toplevel), "toplevel");
1807   gtk_widget_realize(toplevel);
1808   gtk_widget_set_name(toplevel, "Freeciv");
1809   root_window = gtk_widget_get_window(toplevel);
1810 
1811   if (gui_options.first_boot) {
1812     adjust_default_options();
1813     /* We're using fresh defaults for this version of this client,
1814      * so prevent any future migrations from other clients / versions */
1815     GUI_GTK_OPTION(migrated_from_gtk3) = TRUE;
1816     /* Avoid also marking gtk3 as migrated, so that it can have its own
1817      * run of its adjust_default_options() if it is ever run (as a
1818      * side effect of Gtk2->Gtk3 migration). */
1819   } else {
1820     if (!GUI_GTK_OPTION(migrated_from_gtk3)) {
1821       if (!gui_options.gui_gtk3_migrated_from_gtk2) {
1822         migrate_options_from_gtk2();
1823         /* We want a fresh look at screen-size-related options after Gtk2 */
1824         adjust_default_options();
1825         /* We don't ever want to consider pre-2.6 fullscreen option again
1826          * (even for gui-gtk3) */
1827         gui_options.gui_gtk3_migrated_from_2_5 = TRUE;
1828       }
1829       migrate_options_from_gtk3();
1830     }
1831   }
1832 
1833   if (GUI_GTK_OPTION(fullscreen)) {
1834     gtk_window_fullscreen(GTK_WINDOW(toplevel));
1835   }
1836 
1837   gtk_window_set_title(GTK_WINDOW (toplevel), _("Freeciv"));
1838 
1839   g_signal_connect(toplevel, "delete_event",
1840       G_CALLBACK(quit_dialog_callback), NULL);
1841 
1842   /* Disable GTK+ cursor key focus movement */
1843   sig = g_signal_lookup("focus", GTK_TYPE_WIDGET);
1844   g_signal_handlers_disconnect_matched(toplevel, G_SIGNAL_MATCH_ID, sig,
1845 				       0, 0, 0, 0);
1846   g_signal_connect(toplevel, "focus", G_CALLBACK(toplevel_focus), NULL);
1847 
1848 
1849   display_color_type = get_visual();
1850 
1851   options_iterate(client_optset, poption) {
1852     if (OT_FONT == option_type(poption)) {
1853       /* Force to call the appropriated callback. */
1854       option_changed(poption);
1855     }
1856   } options_iterate_end;
1857 
1858   toplevel_font_name = pango_context_get_font_description(
1859                            gtk_widget_get_pango_context(toplevel));
1860 
1861   if (NULL == city_names_style) {
1862     city_names_style = pango_font_description_copy(toplevel_font_name);
1863     log_error("city_names_style should have been set by options.");
1864   }
1865   if (NULL == city_productions_style) {
1866     city_productions_style = pango_font_description_copy(toplevel_font_name);
1867     log_error("city_productions_style should have been set by options.");
1868   }
1869   if (NULL == reqtree_text_style) {
1870     reqtree_text_style = pango_font_description_copy(toplevel_font_name);
1871     log_error("reqtree_text_style should have been set by options.");
1872   }
1873 
1874   tileset_init(tileset);
1875   tileset_load_tiles(tileset);
1876 
1877   /* keep the icon of the executable on Windows (see PR#36491) */
1878 #ifndef WIN32_NATIVE
1879   {
1880     GdkPixbuf *pixbuf = sprite_get_pixbuf(get_icon_sprite(tileset, ICON_FREECIV));
1881 
1882     /* Only call this after tileset_load_tiles is called. */
1883     gtk_window_set_icon(GTK_WINDOW(toplevel), pixbuf);
1884     g_object_unref(pixbuf);
1885   }
1886 #endif /* WIN32_NATIVE */
1887 
1888   setup_widgets();
1889   load_cursors();
1890   cma_fe_init();
1891   diplomacy_dialog_init();
1892   luaconsole_dialog_init();
1893   happiness_dialog_init();
1894   citizens_dialog_init();
1895   intel_dialog_init();
1896   spaceship_dialog_init();
1897   chatline_init();
1898   init_mapcanvas_and_overview();
1899 
1900   tileset_use_preferred_theme(tileset);
1901 
1902   gtk_widget_show(toplevel);
1903 
1904   /* assumes toplevel showing */
1905   set_client_state(C_S_DISCONNECTED);
1906 
1907   /* assumes client_state is set */
1908   timer_id = g_timeout_add(TIMER_INTERVAL, timer_callback, NULL);
1909 
1910   gui_up = TRUE;
1911   gtk_main();
1912   gui_up = FALSE;
1913 
1914   /* We have extra ref for unit_info_box that has protected
1915    * it from getting destroyed when editinfobox_refresh()
1916    * moves widgets around. Free that extra ref here. */
1917   g_object_unref(unit_info_box);
1918 
1919   destroy_server_scans();
1920   free_mapcanvas_and_overview();
1921   spaceship_dialog_done();
1922   intel_dialog_done();
1923   citizens_dialog_done();
1924   luaconsole_dialog_done();
1925   happiness_dialog_done();
1926   diplomacy_dialog_done();
1927   cma_fe_done();
1928   free_unit_table();
1929   editgui_free();
1930   gtk_widget_destroy(toplevel_tabs);
1931   gtk_widget_destroy(toplevel);
1932   message_buffer = NULL; /* Result of destruction of everything */
1933   tileset_free_tiles(tileset);
1934 }
1935 
1936 /**************************************************************************
1937   Return whether gui is currently running.
1938 **************************************************************************/
is_gui_up(void)1939 bool is_gui_up(void)
1940 {
1941   return gui_up;
1942 }
1943 
1944 /**************************************************************************
1945   Do any necessary UI-specific cleanup
1946 **************************************************************************/
ui_exit(void)1947 void ui_exit(void)
1948 {
1949   if (message_buffer != NULL) {
1950     g_object_unref(message_buffer);
1951     message_buffer = NULL;
1952   }
1953 }
1954 
1955 /**************************************************************************
1956   Return our GUI type
1957 **************************************************************************/
get_gui_type(void)1958 enum gui_type get_gui_type(void)
1959 {
1960   return GUI_GTK3_22;
1961 }
1962 
1963 /**************************************************************************
1964  obvious...
1965 **************************************************************************/
sound_bell(void)1966 void sound_bell(void)
1967 {
1968   gdk_display_beep(gdk_display_get_default());
1969 }
1970 
1971 /**************************************************************************
1972   Set one of the unit icons in information area based on punit.
1973   Use punit==NULL to clear icon.
1974   Index 'idx' is -1 for "active unit", or 0 to (num_units_below-1) for
1975   units below.  Also updates unit_ids[idx] for idx>=0.
1976 **************************************************************************/
set_unit_icon(int idx,struct unit * punit)1977 void set_unit_icon(int idx, struct unit *punit)
1978 {
1979   GtkWidget *w;
1980 
1981   fc_assert_ret(idx >= -1 && idx < num_units_below);
1982 
1983   if (idx == -1) {
1984     w = unit_image;
1985     unit_id_top = punit ? punit->id : 0;
1986   } else {
1987     w = unit_below_image[idx];
1988     unit_ids[idx] = punit ? punit->id : 0;
1989   }
1990 
1991   if (!w) {
1992     return;
1993   }
1994 
1995   if (punit) {
1996     put_unit_image(punit, GTK_IMAGE(w), -1);
1997   } else {
1998     gtk_image_clear(GTK_IMAGE(w));
1999   }
2000 }
2001 
2002 /**************************************************************************
2003   Set the "more arrow" for the unit icons to on(1) or off(0).
2004   Maintains a static record of current state to avoid unnecessary redraws.
2005   Note initial state should match initial gui setup (off).
2006 **************************************************************************/
set_unit_icons_more_arrow(bool onoff)2007 void set_unit_icons_more_arrow(bool onoff)
2008 {
2009   static bool showing = FALSE;
2010 
2011   if (!more_arrow_pixmap_button) {
2012     return;
2013   }
2014 
2015   if (onoff && !showing) {
2016     gtk_widget_show(more_arrow_pixmap_button);
2017     showing = TRUE;
2018   }
2019   else if(!onoff && showing) {
2020     gtk_widget_hide(more_arrow_pixmap_button);
2021     showing = FALSE;
2022   }
2023 }
2024 
2025 /****************************************************************************
2026   Called when the set of units in focus (get_units_in_focus()) changes.
2027   Standard updates like update_unit_info_label() are handled in the platform-
2028   independent code; we use this to keep the goto/airlift dialog up to date,
2029   if it's visible.
2030 ****************************************************************************/
real_focus_units_changed(void)2031 void real_focus_units_changed(void)
2032 {
2033   goto_dialog_focus_units_changed();
2034 }
2035 
2036 /**************************************************************************
2037  callback for clicking a unit icon underneath unit info box.
2038  these are the units on the same tile as the focus unit.
2039 **************************************************************************/
select_unit_image_callback(GtkWidget * w,GdkEvent * ev,gpointer data)2040 static gboolean select_unit_image_callback(GtkWidget *w, GdkEvent *ev,
2041                                            gpointer data)
2042 {
2043   int i = GPOINTER_TO_INT(data);
2044   struct unit *punit;
2045 
2046   if (i == -1) {
2047     punit = game_unit_by_number(unit_id_top);
2048     if (punit && unit_is_in_focus(punit)) {
2049       /* Clicking on the currently selected unit will center it. */
2050       center_tile_mapcanvas(unit_tile(punit));
2051     }
2052     return TRUE;
2053   }
2054 
2055   if (unit_ids[i] == 0) /* no unit displayed at this place */
2056     return TRUE;
2057 
2058   punit = game_unit_by_number(unit_ids[i]);
2059   if (NULL != punit && unit_owner(punit) == client.conn.playing) {
2060     /* Unit shouldn't be NULL but may be owned by an ally. */
2061     unit_focus_set(punit);
2062   }
2063 
2064   return TRUE;
2065 }
2066 
2067 /**************************************************************************
2068  callback for clicking a unit icon underneath unit info box.
2069  these are the units on the same tile as the focus unit.
2070 **************************************************************************/
select_more_arrow_pixmap_callback(GtkWidget * w,GdkEvent * ev,gpointer data)2071 static gboolean select_more_arrow_pixmap_callback(GtkWidget *w, GdkEvent *ev,
2072                                                   gpointer data)
2073 {
2074   struct unit *punit = game_unit_by_number(unit_id_top);
2075 
2076   if (punit) {
2077     unit_select_dialog_popup(unit_tile(punit));
2078   }
2079 
2080   return TRUE;
2081 }
2082 
2083 /**************************************************************************
2084   Button released when showing info popup
2085 **************************************************************************/
show_info_button_release(GtkWidget * w,GdkEventButton * ev,gpointer data)2086 static gboolean show_info_button_release(GtkWidget *w, GdkEventButton *ev,
2087                                          gpointer data)
2088 {
2089   gtk_grab_remove(w);
2090   gdk_seat_ungrab(gdk_device_get_seat(ev->device));
2091   gtk_widget_destroy(w);
2092 
2093   return FALSE;
2094 }
2095 
2096 /**************************************************************************
2097   Popup info box
2098 **************************************************************************/
show_info_popup(GtkWidget * w,GdkEventButton * ev,gpointer data)2099 static gboolean show_info_popup(GtkWidget *w, GdkEventButton *ev, gpointer data)
2100 {
2101   if (ev->button == 1) {
2102     GtkWidget *p;
2103 
2104     p = gtk_window_new(GTK_WINDOW_POPUP);
2105     gtk_container_set_border_width(GTK_CONTAINER(p), 4);
2106     gtk_window_set_transient_for(GTK_WINDOW(p), GTK_WINDOW(toplevel));
2107     gtk_window_set_position(GTK_WINDOW(p), GTK_WIN_POS_MOUSE);
2108 
2109     gtk_widget_new(GTK_TYPE_LABEL, "GtkWidget::parent", p,
2110 		   "GtkLabel::label", get_info_label_text_popup(),
2111                    "GtkWidget::visible", TRUE,
2112                    NULL);
2113     gtk_widget_show(p);
2114 
2115     gdk_seat_grab(gdk_device_get_seat(ev->device), gtk_widget_get_window(p),
2116                   GDK_SEAT_CAPABILITY_ALL_POINTING,
2117                   TRUE, NULL, (GdkEvent *)ev, NULL, NULL);
2118     gtk_grab_add(p);
2119 
2120     g_signal_connect_after(p, "button_release_event",
2121                            G_CALLBACK(show_info_button_release), NULL);
2122   }
2123 
2124   return TRUE;
2125 }
2126 
2127 /**************************************************************************
2128  user clicked "Turn Done" button
2129 **************************************************************************/
end_turn_callback(GtkWidget * w,gpointer data)2130 static void end_turn_callback(GtkWidget *w, gpointer data)
2131 {
2132     gtk_widget_set_sensitive(turn_done_button, FALSE);
2133     user_ended_turn();
2134 }
2135 
2136 /**************************************************************************
2137   Read input from server socket
2138 **************************************************************************/
get_net_input(GIOChannel * source,GIOCondition condition,gpointer data)2139 static gboolean get_net_input(GIOChannel *source, GIOCondition condition,
2140                               gpointer data)
2141 {
2142   input_from_server(g_io_channel_unix_get_fd(source));
2143 
2144   return TRUE;
2145 }
2146 
2147 /**************************************************************************
2148   Set socket writability state
2149 **************************************************************************/
set_wait_for_writable_socket(struct connection * pc,bool socket_writable)2150 static void set_wait_for_writable_socket(struct connection *pc,
2151 					 bool socket_writable)
2152 {
2153   static bool previous_state = FALSE;
2154 
2155   fc_assert_ret(pc == &client.conn);
2156 
2157   if (previous_state == socket_writable)
2158     return;
2159 
2160   log_debug("set_wait_for_writable_socket(%d)", socket_writable);
2161 
2162   g_source_remove(srv_id);
2163   srv_id = g_io_add_watch(srv_channel,
2164                           G_IO_IN | (socket_writable ? G_IO_OUT : 0) | G_IO_ERR,
2165                           get_net_input,
2166                           NULL);
2167 
2168   previous_state = socket_writable;
2169 }
2170 
2171 /**************************************************************************
2172  This function is called after the client succesfully
2173  has connected to the server
2174 **************************************************************************/
add_net_input(int sock)2175 void add_net_input(int sock)
2176 {
2177 #ifdef WIN32_NATIVE
2178   srv_channel = g_io_channel_win32_new_socket(sock);
2179 #else
2180   srv_channel = g_io_channel_unix_new(sock);
2181 #endif
2182   srv_id = g_io_add_watch(srv_channel,
2183                           G_IO_IN | G_IO_ERR,
2184                           get_net_input,
2185                           NULL);
2186   client.conn.notify_of_writable_data = set_wait_for_writable_socket;
2187 }
2188 
2189 /**************************************************************************
2190  This function is called if the client disconnects
2191  from the server
2192 **************************************************************************/
remove_net_input(void)2193 void remove_net_input(void)
2194 {
2195   g_source_remove(srv_id);
2196   g_io_channel_unref(srv_channel);
2197   gdk_window_set_cursor(root_window, NULL);
2198 }
2199 
2200 /****************************************************************
2201   This is the response callback for the dialog with the message:
2202   Are you sure you want to quit?
2203 ****************************************************************/
quit_dialog_response(GtkWidget * dialog,gint response)2204 static void quit_dialog_response(GtkWidget *dialog, gint response)
2205 {
2206   gtk_widget_destroy(dialog);
2207   if (response == GTK_RESPONSE_YES) {
2208     start_quitting();
2209     if (client.conn.used) {
2210       disconnect_from_server();
2211     }
2212     quit_gtk_main();
2213   }
2214 }
2215 
2216 /****************************************************************
2217   Exit gtk main loop.
2218 ****************************************************************/
quit_gtk_main(void)2219 void quit_gtk_main(void)
2220 {
2221   /* Quit gtk main loop. After this it will return to finish
2222    * ui_main() */
2223 
2224   gtk_main_quit();
2225 }
2226 
2227 /****************************************************************
2228   Popups the dialog with the message:
2229   Are you sure you want to quit?
2230 ****************************************************************/
popup_quit_dialog(void)2231 void popup_quit_dialog(void)
2232 {
2233   static GtkWidget *dialog;
2234 
2235   if (!dialog) {
2236     dialog = gtk_message_dialog_new(NULL,
2237 	0,
2238 	GTK_MESSAGE_WARNING,
2239 	GTK_BUTTONS_YES_NO,
2240 	_("Are you sure you want to quit?"));
2241     setup_dialog(dialog, toplevel);
2242 
2243     gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_MOUSE);
2244 
2245     g_signal_connect(dialog, "response",
2246 	G_CALLBACK(quit_dialog_response), NULL);
2247     g_signal_connect(dialog, "destroy",
2248 	G_CALLBACK(gtk_widget_destroyed), &dialog);
2249   }
2250 
2251   gtk_window_present(GTK_WINDOW(dialog));
2252 }
2253 
2254 /****************************************************************
2255   Popups the quit dialog.
2256 ****************************************************************/
quit_dialog_callback(void)2257 static gboolean quit_dialog_callback(void)
2258 {
2259   popup_quit_dialog();
2260   /* Stop emission of event. */
2261   return TRUE;
2262 }
2263 
2264 struct callback {
2265   void (*callback)(void *data);
2266   void *data;
2267 };
2268 
2269 /****************************************************************************
2270   A wrapper for the callback called through add_idle_callback.
2271 ****************************************************************************/
idle_callback_wrapper(gpointer data)2272 static gboolean idle_callback_wrapper(gpointer data)
2273 {
2274   struct callback *cb = data;
2275 
2276   (cb->callback)(cb->data);
2277   free(cb);
2278 
2279   return FALSE;
2280 }
2281 
2282 /****************************************************************************
2283   Enqueue a callback to be called during an idle moment.  The 'callback'
2284   function should be called sometimes soon, and passed the 'data' pointer
2285   as its data.
2286 ****************************************************************************/
add_idle_callback(void (callback)(void *),void * data)2287 void add_idle_callback(void (callback)(void *), void *data)
2288 {
2289   struct callback *cb = fc_malloc(sizeof(*cb));
2290 
2291   cb->callback = callback;
2292   cb->data = data;
2293   g_idle_add(idle_callback_wrapper, cb);
2294 }
2295 
2296 /****************************************************************************
2297   Option callback for the 'gui_gtk3_allied_chat_only' option.
2298   This updates the state of the associated toggle button.
2299 ****************************************************************************/
allied_chat_only_callback(struct option * poption)2300 static void allied_chat_only_callback(struct option *poption)
2301 {
2302   GtkWidget *button;
2303 
2304   button = allied_chat_toggle_button;
2305   fc_assert_ret(button != NULL);
2306   fc_assert_ret(GTK_IS_TOGGLE_BUTTON(button));
2307 
2308   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button),
2309                                option_bool_get(poption));
2310 }
2311 
2312 /****************************************************************************
2313   Change the city names font.
2314 ****************************************************************************/
apply_city_names_font(struct option * poption)2315 static void apply_city_names_font(struct option *poption)
2316 {
2317   gui_update_font_full(option_font_target(poption),
2318                        option_font_get(poption),
2319                        &city_names_style);
2320   update_city_descriptions();
2321 }
2322 
2323 /****************************************************************************
2324   Change the city productions font.
2325 ****************************************************************************/
apply_city_productions_font(struct option * poption)2326 static void apply_city_productions_font(struct option *poption)
2327 {
2328   gui_update_font_full(option_font_target(poption),
2329                        option_font_get(poption),
2330                        &city_productions_style);
2331   update_city_descriptions();
2332 }
2333 
2334 /****************************************************************************
2335   Change the city productions font.
2336 ****************************************************************************/
apply_reqtree_text_font(struct option * poption)2337 static void apply_reqtree_text_font(struct option *poption)
2338 {
2339   gui_update_font_full(option_font_target(poption),
2340                        option_font_get(poption),
2341                        &reqtree_text_style);
2342   science_report_dialog_redraw();
2343 }
2344 
2345 /****************************************************************************
2346   Extra initializers for client options.  Here we make set the callback
2347   for the specific gui-gtk-3.0 options.
2348 ****************************************************************************/
options_extra_init(void)2349 void options_extra_init(void)
2350 {
2351 
2352   struct option *poption;
2353 
2354 #define option_var_set_callback(var, callback)                              \
2355   if ((poption = optset_option_by_name(client_optset, GUI_GTK_OPTION_STR(var)))) { \
2356     option_set_changed_callback(poption, callback);                         \
2357   } else {                                                                  \
2358     log_error("Didn't find option %s!", GUI_GTK_OPTION_STR(var));       \
2359   }
2360 
2361   option_var_set_callback(allied_chat_only,
2362                           allied_chat_only_callback);
2363 
2364   option_var_set_callback(font_city_names,
2365                           apply_city_names_font);
2366   option_var_set_callback(font_city_productions,
2367                           apply_city_productions_font);
2368   option_var_set_callback(font_reqtree_text,
2369                           apply_reqtree_text_font);
2370 #undef option_var_set_callback
2371 }
2372 
2373 /**************************************************************************
2374   Set the chatline buttons to reflect the state of the game and current
2375   client options. This function should be called on game start.
2376 **************************************************************************/
refresh_chat_buttons(void)2377 void refresh_chat_buttons(void)
2378 {
2379   GtkWidget *button;
2380 
2381   button = allied_chat_toggle_button;
2382   fc_assert_ret(button != NULL);
2383   fc_assert_ret(GTK_IS_TOGGLE_BUTTON(button));
2384 
2385   /* Hide the "Allies Only" button for local games. */
2386   if (is_server_running()) {
2387     gtk_widget_hide(button);
2388   } else {
2389     gtk_widget_show(button);
2390     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button),
2391                                  GUI_GTK_OPTION(allied_chat_only));
2392   }
2393 }
2394 
2395 /**************************************************************************
2396   Handle a toggle of the "Allies Only" chat button.
2397 **************************************************************************/
allied_chat_button_toggled(GtkToggleButton * button,gpointer user_data)2398 static void allied_chat_button_toggled(GtkToggleButton *button,
2399                                        gpointer user_data)
2400 {
2401   GUI_GTK_OPTION(allied_chat_only) = gtk_toggle_button_get_active(button);
2402 }
2403 
2404 /**************************************************************************
2405   Insert build information to help
2406 **************************************************************************/
insert_client_build_info(char * outbuf,size_t outlen)2407 void insert_client_build_info(char *outbuf, size_t outlen)
2408 {
2409   cat_snprintf(outbuf, outlen, _("\nBuilt against gtk+ %d.%d.%d, using %d.%d.%d"
2410                                  "\nBuilt against glib %d.%d.%d, using %d.%d.%d"),
2411                GTK_MAJOR_VERSION, GTK_MINOR_VERSION, GTK_MICRO_VERSION,
2412                gtk_get_major_version(), gtk_get_minor_version(), gtk_get_micro_version(),
2413                GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION, GLIB_MICRO_VERSION,
2414                glib_major_version, glib_minor_version, glib_micro_version);
2415 }
2416 
2417 /**************************************************************************
2418   Return dimensions of primary monitor, if any
2419   (in 'application pixels')
2420 **************************************************************************/
monitor_size(GdkRectangle * rect_p)2421 static bool monitor_size(GdkRectangle *rect_p)
2422 {
2423   GdkDisplay *display;
2424   GdkMonitor *monitor;
2425 
2426   display = gdk_display_get_default();
2427   if (!display) {
2428     return FALSE;
2429   }
2430 
2431   monitor = gdk_display_get_primary_monitor(display);
2432   if (!monitor) {
2433     return FALSE;
2434   }
2435 
2436   gdk_monitor_get_geometry(monitor, rect_p);
2437   return TRUE;
2438 }
2439 
2440 /**************************************************************************
2441   Return width of the primary monitor
2442 **************************************************************************/
screen_width(void)2443 int screen_width(void)
2444 {
2445   GdkRectangle rect;
2446 
2447   if (vmode.width > 0) {
2448     return vmode.width;
2449   }
2450 
2451   if (monitor_size(&rect)) {
2452     return rect.width;
2453   } else {
2454     return 0;
2455   }
2456 }
2457 
2458 /**************************************************************************
2459   Return height of the primary monitor
2460 **************************************************************************/
screen_height(void)2461 int screen_height(void)
2462 {
2463   GdkRectangle rect;
2464 
2465   if (vmode.height > 0) {
2466     return vmode.height;
2467   }
2468 
2469   if (monitor_size(&rect)) {
2470     return rect.height;
2471   } else {
2472     return 0;
2473   }
2474 }
2475 
2476 /**************************************************************************
2477   Give resolution requested by user, if any.
2478 **************************************************************************/
resolution_request_get(void)2479 struct video_mode *resolution_request_get(void)
2480 {
2481   if (vmode.width > 0 && vmode.height > 0) {
2482     return &vmode;
2483   }
2484 
2485   return NULL;
2486 }
2487 
2488 /**************************************************************************
2489   Make dynamic adjustments to first-launch default options.
2490 **************************************************************************/
adjust_default_options(void)2491 static void adjust_default_options(void)
2492 {
2493   int scr_height = screen_height();
2494 
2495   if (scr_height > 0) {
2496     /* Adjust these options only if we do know the screen height. */
2497 
2498     if (scr_height <= 480) {
2499       /* Freeciv is practically unusable outside fullscreen mode in so
2500        * small display */
2501       log_verbose("Changing default to fullscreen due to very small screen");
2502       GUI_GTK_OPTION(fullscreen) = TRUE;
2503     }
2504     if (scr_height < 1024) {
2505       /* This is a small display */
2506       log_verbose("Defaulting to small widget layout due to small screen");
2507       GUI_GTK_OPTION(small_display_layout) = TRUE;
2508       log_verbose("Defaulting to merged messages/chat due to small screen");
2509       GUI_GTK_OPTION(message_chat_location) = GUI_GTK_MSGCHAT_MERGED;
2510     }
2511   }
2512 }
2513