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