1 /***********************************************************************
2  Freeciv - Copyright (C) 1996 - A Kjeldberg, L Gregersen, P Unold
3    This program is free software; you can redistribute it and/or modify
4    it under the terms of the GNU General Public License as published by
5    the Free Software Foundation; either version 2, or (at your option)
6    any later version.
7 
8    This program is distributed in the hope that it will be useful,
9    but WITHOUT ANY WARRANTY; without even the implied warranty of
10    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11    GNU General Public License for more details.
12 ***********************************************************************/
13 
14 #ifdef HAVE_CONFIG_H
15 #include <fc_config.h>
16 #endif
17 
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <math.h> /* sqrt */
22 
23 #include <gtk/gtk.h>
24 
25 /* utility */
26 #include "fcintl.h"
27 #include "mem.h"
28 #include "shared.h"
29 #include "support.h"
30 
31 /* common */
32 #include "city.h"
33 #include "game.h"
34 #include "government.h"
35 #include "movement.h"
36 #include "specialist.h"
37 #include "tech.h"
38 #include "unit.h"
39 #include "map.h"
40 #include "research.h"
41 #include "version.h"
42 
43 /* client */
44 #include "client_main.h"
45 #include "climisc.h"
46 #include "helpdata.h"
47 #include "options.h"
48 #include "tilespec.h"
49 
50 /* client/gui-gtk-3.22 */
51 #include "colors.h"
52 #include "graphics.h"
53 #include "gui_main.h"
54 #include "gui_stuff.h"
55 
56 #include "helpdlg.h"
57 
58 #define TECH_TREE_DEPTH         20
59 
60 /*
61  * Globals.
62  */
63 static GtkWidget *help_dialog_shell;
64 static GtkWidget *help_view_sw;
65 
66 static GtkWidget *help_view;
67 
68 static GtkWidget *help_frame;
69 static GtkTextBuffer *help_text;
70 static GtkWidget *help_text_sw;
71 static GtkWidget *help_vbox;
72 static GtkWidget *help_tile;
73 static GtkWidget *help_box;
74 static GtkWidget *help_itable;
75 static GtkWidget *help_wtable;
76 static GtkWidget *help_utable;
77 static GtkWidget *help_ttable;
78 static GtkWidget *help_etable;
79 static GtkWidget *help_tree;
80 static GtkTreeStore *tstore;
81 
82 static GtkWidget *help_tree_sw;
83 static GtkWidget *help_tree_expand;
84 static GtkWidget *help_tree_collapse;
85 static GtkWidget *help_tree_buttons_hbox;
86 static GtkWidget *help_ilabel[6];
87 static GtkWidget *help_wlabel[6];
88 static GtkWidget *help_ulabel[5][5];
89 static GtkWidget *help_tlabel[2][5];
90 static GtkWidget *help_elabel[6];
91 
92 static bool help_advances[A_LAST];
93 
94 static GPtrArray *help_history;
95 static int	  help_history_pos;
96 
97 
98 static const char *help_ilabel_name[6] =
99 { N_("Cost:"), NULL, N_("Upkeep:"), NULL, N_("Requirement:"), NULL };
100 
101 static const char *help_wlabel_name[6] =
102 { N_("Cost:"), NULL, N_("Requirement:"), NULL, N_("Obsolete by:"), NULL };
103 
104 static const char *help_ulabel_name[5][5] =
105 {
106     { N_("Cost:"),		NULL, NULL, N_("Attack:"),	NULL },
107     { N_("Defense:"),		NULL, NULL, N_("Move:")	,	NULL },
108     { N_("FirePower:"),		NULL, NULL, N_("Hitpoints:"),	NULL },
109     { N_("Basic Upkeep:"),	NULL, NULL, N_("Vision:"),	NULL },
110     { N_("Requirement:"),	NULL, NULL, N_("Obsolete by:"),	NULL }
111 };
112 
113 static const char *help_tlabel_name[2][5] =
114 {
115     { N_("Move/Defense:"),	NULL, NULL, N_("Food/Res/Trade:"),	NULL },
116     { N_("Resources:"),		NULL, NULL, NULL,			NULL }
117 };
118 
119 static const char *help_elabel_name[6] =
120 /* TRANS: Label for build cost for extras in help. Will be followed by
121  * something like "3 MP" (where MP = Movement Points) */
122 { N_("Build:"), NULL,
123 /* TRANS: Extra conflicts in help. Will be followed by a list of extras
124  * that can't be built on the same tile as this one. */
125   N_("Conflicts with:"), NULL,
126 /* TRANS: Extra bonus in help. Will be followed by food/production/trade
127  * stats like "0/0/+1", "0/+50%/0" */
128   N_("Bonus (F/P/T):"), NULL };
129 
130 #define REQ_LABEL_NONE _("?tech:None")
131 #define REQ_LABEL_NEVER _("(Never)")
132 
133 static void create_help_dialog(void);
134 static void help_update_dialog(const struct help_item *pitem);
135 static void create_help_page(enum help_page_type type);
136 
137 static void select_help_item_string(const char *item,
138 				    enum help_page_type htype);
139 static void help_command_update(void);
140 static void help_command_callback(GtkWidget *w, gint response_id);
141 
142 /****************************************************************
143   Set topic specific title for help_frame
144 *****************************************************************/
set_title_topic(char * topic)145 static void set_title_topic(char *topic)
146 {
147   if (strcmp(topic, _(HELP_ABOUT_ITEM)) == 0) {
148     gtk_frame_set_label(GTK_FRAME(help_frame), freeciv_name_version());
149   } else {
150     gtk_frame_set_label(GTK_FRAME(help_frame), topic);
151   }
152   return;
153 }
154 
155 /****************************************************************
156   Close help dialog
157 *****************************************************************/
popdown_help_dialog(void)158 void popdown_help_dialog(void)
159 {
160   if(help_dialog_shell) {
161     gtk_widget_destroy(help_dialog_shell);
162   }
163 }
164 
165 /****************************************************************
166   Popup help dialog for given item of given type.
167 *****************************************************************/
popup_help_dialog_typed(const char * item,enum help_page_type htype)168 void popup_help_dialog_typed(const char *item, enum help_page_type htype)
169 {
170   if (!help_dialog_shell) {
171     create_help_dialog();
172     set_relative_window_position(GTK_WINDOW(toplevel),
173                                  GTK_WINDOW(help_dialog_shell), 10, 10);
174   }
175   gtk_window_present(GTK_WINDOW(help_dialog_shell));
176 
177   select_help_item_string(item, htype);
178 }
179 
180 /****************************************************************
181 Not sure if this should call Q_(item) as it does, or whether all
182 callers of this function should do so themselves... --dwp
183 *****************************************************************/
popup_help_dialog_string(const char * item)184 void popup_help_dialog_string(const char *item)
185 {
186   popup_help_dialog_typed(Q_(item), HELP_ANY);
187 }
188 
189 /**************************************************************************
190 Called by help_update_tech and itself
191 Creates a node in the given tree for the given tech, and creates child
192 nodes for any children it has up to levels deep. These are then expanded
193 if they are less than expanded_levels deep. Avoids generating redundant
194 subtrees, so that if Literacy occurs twice in a tech tree, only the first
195 will have children. Color codes the node based on when it will be
196 discovered: red >2 turns, yellow 1 turn, green 0 turns (discovered).
197 **************************************************************************/
create_tech_tree(int tech,int levels,GtkTreeIter * parent)198 static void create_tech_tree(int tech, int levels, GtkTreeIter *parent)
199 {
200   const struct research *presearch;
201   int	        bg;
202   int           turns_to_tech;
203   bool          original;
204   GtkTreeIter   l;
205   GValue        value = { 0, };
206 
207   if (advance_required(tech, AR_ONE) == A_LAST
208    && advance_required(tech, AR_TWO) == A_LAST) {
209     bg = COLOR_REQTREE_UNKNOWN;
210 
211     gtk_tree_store_append(tstore, &l, parent);
212     help_advances[tech] = TRUE;
213 
214     g_value_init(&value, G_TYPE_STRING);
215     g_value_set_static_string(&value, _("Removed"));
216     gtk_tree_store_set_value(tstore, &l, 0, &value);
217     g_value_unset(&value);
218 
219     gtk_tree_store_set(tstore, &l,
220 		       1, -1,
221 		       2, tech,
222 		       3, &get_color(tileset, bg)->color
223 		       -1);
224     return;
225   }
226 
227   presearch = research_get(client_player());
228 
229   bg = COLOR_REQTREE_BACKGROUND;
230   switch (research_invention_state(presearch, tech)) {
231   case TECH_UNKNOWN:
232     bg = COLOR_REQTREE_UNKNOWN;
233     break;
234   case TECH_KNOWN:
235     bg = COLOR_REQTREE_KNOWN;
236     break;
237   case TECH_PREREQS_KNOWN:
238     bg = COLOR_REQTREE_PREREQS_KNOWN;
239     break;
240   }
241   turns_to_tech = research_goal_unknown_techs(presearch, tech);
242 
243   /* l is the original in the tree. */
244   original = !help_advances[tech];
245 
246   gtk_tree_store_append(tstore, &l, parent);
247   help_advances[tech] = TRUE;
248 
249   g_value_init(&value, G_TYPE_STRING);
250   g_value_set_static_string(&value,
251                             research_advance_name_translation(presearch,
252                                                               tech));
253   gtk_tree_store_set_value(tstore, &l, 0, &value);
254   g_value_unset(&value);
255 
256   gtk_tree_store_set(tstore, &l,
257 		     1, turns_to_tech,
258 		     2, tech,
259 		     3, &get_color(tileset, bg)->color,
260 		     -1);
261 
262   if (--levels <= 0)
263       return;
264 
265   if (original) {
266     /* only add children to orginals */
267     if (advance_required(tech, AR_ONE) != A_NONE)
268       create_tech_tree(advance_required(tech, AR_ONE), levels, &l);
269     if (advance_required(tech, AR_TWO) != A_NONE)
270       create_tech_tree(advance_required(tech, AR_TWO), levels, &l);
271   }
272   return;
273 }
274 
275 /**************************************************************************
276 Selects the help page for the tech in the tree that was double clicked.
277 **************************************************************************/
help_tech_tree_activated_callback(GtkTreeView * view,GtkTreePath * path,GtkTreeViewColumn * col,gpointer data)278 static void help_tech_tree_activated_callback(GtkTreeView *view,
279 					      GtkTreePath *path,
280 					      GtkTreeViewColumn *col,
281 					      gpointer data)
282 {
283   GtkTreeIter it;
284   gint tech;
285 
286   gtk_tree_model_get_iter(GTK_TREE_MODEL(tstore), &it, path);
287   gtk_tree_model_get(GTK_TREE_MODEL(tstore), &it, 2, &tech, -1);
288   select_help_item_string(advance_name_translation(advance_by_number(tech)),
289                           HELP_TECH);
290 }
291 
292 /**************************************************************************
293 Called when "Expand All" button is clicked
294 **************************************************************************/
help_tech_tree_expand_callback(GtkWidget * w,gpointer data)295 static void help_tech_tree_expand_callback(GtkWidget *w, gpointer data)
296 {
297   gtk_tree_view_expand_all(GTK_TREE_VIEW(data));
298 }
299 
300 /**************************************************************************
301 Called when "Collapse All" button is clicked
302 **************************************************************************/
help_tech_tree_collapse_callback(GtkWidget * w,gpointer data)303 static void help_tech_tree_collapse_callback(GtkWidget *w, gpointer data)
304 {
305   gtk_tree_view_collapse_all(GTK_TREE_VIEW(data));
306 }
307 
308 /**************************************************************************
309   Hyperlink clicked
310 **************************************************************************/
help_hyperlink_callback(GtkWidget * w)311 static void help_hyperlink_callback(GtkWidget *w)
312 {
313   const char *s;
314   enum help_page_type type;
315 
316   s=gtk_label_get_text(GTK_LABEL(w));
317   type=GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(w), "page_type"));
318 
319   /* FIXME: May be able to skip, or may need to modify, advances[A_NONE]
320      below, depending on which i18n is done elsewhere.
321   */
322   if (strcmp(s, REQ_LABEL_NEVER) != 0
323       && strcmp(s, skip_intl_qualifier_prefix(REQ_LABEL_NONE)) != 0
324       && strcmp(s, advance_name_translation(advance_by_number(A_NONE))) != 0)
325     select_help_item_string(s, type);
326 }
327 
328 /**************************************************************************
329   Create new hyperlink button
330 **************************************************************************/
help_hyperlink_new(GtkWidget * label,enum help_page_type type)331 static GtkWidget *help_hyperlink_new(GtkWidget *label, enum help_page_type type)
332 {
333   GtkWidget *button;
334 
335   button = gtk_button_new();
336   gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
337   gtk_widget_set_halign(label, GTK_ALIGN_CENTER);
338   gtk_widget_set_valign(label, GTK_ALIGN_CENTER);
339   gtk_widget_set_name(label, "help_link");
340   gtk_container_add(GTK_CONTAINER(button), label);
341   gtk_widget_show(button);
342   g_signal_connect_swapped(button, "clicked",
343 			   G_CALLBACK(help_hyperlink_callback), label);
344   g_object_set_data(G_OBJECT(label), "page_type", GUINT_TO_POINTER(type));
345   return button;
346 }
347 
348 /**************************************************************************
349   Create new hyperlink button with text
350 **************************************************************************/
help_slink_new(const gchar * txt,enum help_page_type type)351 static GtkWidget *help_slink_new(const gchar *txt, enum help_page_type type)
352 {
353   GtkWidget *button, *label;
354 
355   label = gtk_label_new(txt);
356   gtk_widget_set_halign(label, GTK_ALIGN_CENTER);
357   gtk_widget_set_valign(label, GTK_ALIGN_CENTER);
358   button = help_hyperlink_new(label, type);
359 
360   return button;
361 }
362 
363 /**************************************************************************
364   Hide help box
365 **************************************************************************/
help_box_hide(void)366 static void help_box_hide(void)
367 {
368   gtk_widget_hide(help_box);
369 
370   gtk_widget_hide(help_tile);
371 
372   gtk_widget_hide(help_itable);
373   gtk_widget_hide(help_wtable);
374   gtk_widget_hide(help_utable);
375   gtk_widget_hide(help_ttable);
376   gtk_widget_hide(help_etable);
377 
378   gtk_widget_hide(help_tile); /* FIXME: twice? */
379 
380   gtk_widget_hide(help_vbox);
381   gtk_widget_hide(help_text_sw);
382 
383   gtk_widget_hide(help_tree_sw);
384   gtk_widget_hide(help_tree_buttons_hbox);
385 }
386 
387 /**************************************************************************
388   Completely destory help dialog
389 **************************************************************************/
help_destroy_callback(GtkWidget * w,gpointer data)390 static void help_destroy_callback(GtkWidget *w, gpointer data)
391 {
392   g_ptr_array_free(help_history, TRUE);
393   help_dialog_shell = NULL;
394 }
395 
396 /**************************************************************************
397   New topic activated from help dialog
398 **************************************************************************/
activated_topic(GtkTreeView * view,gpointer data)399 static void activated_topic(GtkTreeView *view, gpointer data)
400 {
401   GtkTreePath *path;
402   GtkTreeViewColumn *col;
403   GtkTreeModel *model;
404   GtkTreeIter it;
405   struct help_item *pitem;
406 
407   model = gtk_tree_view_get_model(view);
408 
409   gtk_tree_view_get_cursor(view, &path, &col);
410   gtk_tree_model_get_iter(model, &it, path);
411   gtk_tree_path_free(path);
412 
413   if (!path) {
414     return;
415   }
416 
417   gtk_tree_model_get(model, &it, 1, &pitem, -1);
418 
419   if (help_history_pos >= 0 &&
420       g_ptr_array_index(help_history, help_history_pos) == (gpointer) pitem) {
421     return;
422   }
423 
424   help_update_dialog(pitem);
425 
426   /* add to history. */
427   if (help_history_pos < help_history->len - 1) {
428     g_ptr_array_set_size(help_history, help_history_pos + 1);
429   }
430   help_history_pos++;
431 
432   g_ptr_array_add(help_history, (gpointer)pitem);
433   help_command_update();
434 }
435 
436 /**************************************************************************
437   Create help dialog
438 **************************************************************************/
create_help_dialog(void)439 static void create_help_dialog(void)
440 {
441   GtkWidget *hbox;
442   GtkWidget *button;
443   GtkWidget *text;
444   int	     i, j;
445   GtkCellRenderer   *rend;
446   GtkTreeViewColumn *col;
447   GArray            *array;
448   GtkTreeStore      *store;
449   GtkTreeSelection  *selection;
450 
451   help_history = g_ptr_array_new();
452   help_history_pos = -1;
453 
454   help_dialog_shell = gtk_dialog_new_with_buttons(_("Freeciv Help Browser"),
455                                                   NULL,
456                                                   0,
457                                                   _("_Back"),
458                                                   1,
459                                                   _("_Forward"),
460                                                   2,
461                                                   _("_Close"),
462                                                   GTK_RESPONSE_CLOSE,
463                                                   NULL);
464   setup_dialog(help_dialog_shell, toplevel);
465   gtk_dialog_set_default_response(GTK_DIALOG(help_dialog_shell),
466                                   GTK_RESPONSE_CLOSE);
467   gtk_widget_set_name(help_dialog_shell, "Freeciv");
468 
469   g_signal_connect(help_dialog_shell, "response",
470 		   G_CALLBACK(help_command_callback), NULL);
471   g_signal_connect(help_dialog_shell, "destroy",
472 		   G_CALLBACK(help_destroy_callback), NULL);
473 
474   hbox = gtk_grid_new();
475   gtk_grid_set_column_spacing(GTK_GRID(hbox), 5);
476   gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(help_dialog_shell))), hbox);
477   gtk_widget_show(hbox);
478 
479   /* build tree store. */
480   store = gtk_tree_store_new(2, G_TYPE_STRING, G_TYPE_POINTER);
481 
482   array = g_array_new(FALSE, FALSE, sizeof(GtkTreeIter));
483   help_items_iterate(pitem) {
484     GtkTreeIter *it, *parent;
485     const char *s;
486     int depth;
487 
488     for (s = pitem->topic; *s == ' '; s++) {
489       /* nothing */
490     }
491     depth = s - pitem->topic;
492 
493     array = g_array_set_size(array, depth+1);
494 
495     if (depth > 0) {
496       parent = &g_array_index(array, GtkTreeIter, depth-1);
497     } else {
498       parent = NULL;
499     }
500 
501     it = &g_array_index(array, GtkTreeIter, depth);
502     gtk_tree_store_append(store, it, parent);
503 
504     gtk_tree_store_set(store, it, 0, pitem->topic, 1, pitem, -1);
505   } help_items_iterate_end;
506 
507 
508   /* create tree view. */
509   help_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
510   g_object_unref(store);
511   gtk_tree_view_columns_autosize(GTK_TREE_VIEW(help_view));
512   gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(help_view), FALSE);
513 
514   g_signal_connect(help_view, "cursor-changed",
515 		   G_CALLBACK(activated_topic), NULL);
516 
517   selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(help_view));
518   gtk_tree_selection_set_mode(selection, GTK_SELECTION_BROWSE);
519 
520   rend = gtk_cell_renderer_text_new();
521   col = gtk_tree_view_column_new_with_attributes(NULL, rend, "text", 0, NULL);
522   gtk_tree_view_append_column(GTK_TREE_VIEW(help_view), col);
523 
524   help_view_sw = gtk_scrolled_window_new(NULL, NULL);
525   gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(help_view_sw),
526   			  GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
527   gtk_widget_set_size_request(help_view_sw, 190, -1);
528   gtk_container_add(GTK_CONTAINER(help_view_sw), help_view);
529   gtk_widget_show(help_view);
530   gtk_container_add(GTK_CONTAINER(hbox), help_view_sw);
531   gtk_widget_show(help_view_sw);
532 
533   help_frame = gtk_frame_new("");
534   gtk_container_add(GTK_CONTAINER(hbox), help_frame);
535   gtk_widget_set_size_request(help_frame, 520, 350);
536   gtk_widget_show(help_frame);
537 
538   help_box = gtk_grid_new();
539   gtk_grid_set_row_spacing(GTK_GRID(help_box), 5);
540   gtk_orientable_set_orientation(GTK_ORIENTABLE(help_box),
541                                  GTK_ORIENTATION_VERTICAL);
542   gtk_container_add(GTK_CONTAINER(help_frame), help_box);
543 
544   help_tile = gtk_image_new();
545 
546   gtk_container_add(GTK_CONTAINER(help_box), help_tile);
547 
548   help_itable = gtk_grid_new();
549   gtk_container_add(GTK_CONTAINER(help_box), help_itable);
550 
551   for (i = 0; i < 6; i++) {
552     help_ilabel[i] =
553       gtk_label_new(help_ilabel_name[i] ? _(help_ilabel_name[i]) : "");
554     gtk_widget_set_hexpand(help_ilabel[i], TRUE);
555 
556     if (i == 5) {
557       button = help_hyperlink_new(help_ilabel[i], HELP_TECH);
558       gtk_grid_attach(GTK_GRID(help_itable), button, i, 0, 1, 1);
559     } else {
560       gtk_grid_attach(GTK_GRID(help_itable), help_ilabel[i], i, 0, 1, 1);
561       gtk_widget_set_name(help_ilabel[i], "help_label");
562     }
563     gtk_widget_show(help_ilabel[i]);
564   }
565 
566   help_wtable = gtk_grid_new();
567   gtk_container_add(GTK_CONTAINER(help_box), help_wtable);
568 
569   for (i = 0; i < 6; i++) {
570     help_wlabel[i] =
571       gtk_label_new(help_wlabel_name[i] ? _(help_wlabel_name[i]) : "");
572     gtk_widget_set_hexpand(help_wlabel[i], TRUE);
573 
574     if (i == 3 || i == 5) {
575       button = help_hyperlink_new(help_wlabel[i], HELP_TECH);
576       gtk_grid_attach(GTK_GRID(help_wtable), button, i, 0, 1, 1);
577     } else {
578       gtk_grid_attach(GTK_GRID(help_wtable), help_wlabel[i], i, 0, 1, 1);
579       gtk_widget_set_name(help_wlabel[i], "help_label");
580     }
581     gtk_widget_show(help_wlabel[i]);
582   }
583 
584 
585   help_utable = gtk_grid_new();
586   gtk_container_add(GTK_CONTAINER(help_box), help_utable);
587 
588   for (i = 0; i < 5; i++) {
589     for (j = 0; j < 5; j++) {
590       help_ulabel[j][i] =
591         gtk_label_new(help_ulabel_name[j][i] ? _(help_ulabel_name[j][i]) : "");
592       gtk_widget_set_hexpand(help_ulabel[j][i], TRUE);
593 
594       if (j == 4 && (i == 1 || i == 4)) {
595         if (i == 1) {
596           button = help_hyperlink_new(help_ulabel[j][i], HELP_TECH);
597         } else {
598           button = help_hyperlink_new(help_ulabel[j][i], HELP_UNIT);
599         }
600 
601         gtk_grid_attach(GTK_GRID(help_utable), button, i, j, 1, 1);
602       } else {
603         gtk_grid_attach(GTK_GRID(help_utable), help_ulabel[j][i],
604                         i, j, 1, 1);
605         gtk_widget_set_name(help_ulabel[j][i], "help_label");
606       }
607       gtk_widget_show(help_ulabel[j][i]);
608     }
609   }
610 
611   help_ttable = gtk_grid_new();
612   gtk_container_add(GTK_CONTAINER(help_box), help_ttable);
613 
614   for (j = 0; j < 2; j++) {
615     for (i = 0; i < 5; i++) {
616       help_tlabel[j][i] =
617         gtk_label_new(help_tlabel_name[j][i] ? _(help_tlabel_name[j][i]) : "");
618       gtk_widget_set_hexpand(help_tlabel[j][i], TRUE);
619       gtk_widget_set_name(help_tlabel[j][i], "help_label");
620 
621       /* Ugly (but these numbers are hardcoded in help_update_terrain() too) */
622       if (j == 1 && i == 1) {
623         /* Extra wide cell for terrain specials */
624         gtk_grid_attach(GTK_GRID(help_ttable), help_tlabel[j][i],
625                         i, j, 4, 1);
626         gtk_widget_show(help_tlabel[j][i]);
627         break; /* skip rest of row */
628       } else {
629         gtk_grid_attach(GTK_GRID(help_ttable), help_tlabel[j][i],
630                         i, j, 1, 1);
631         gtk_widget_show(help_tlabel[j][i]);
632       }
633     }
634   }
635 
636   help_etable = gtk_grid_new();
637   gtk_container_add(GTK_CONTAINER(help_box), help_etable);
638 
639   for (i = 0; i < 6; i++) {
640     help_elabel[i] =
641       gtk_label_new(help_elabel_name[i] ? _(help_elabel_name[i]) : "");
642     gtk_widget_set_hexpand(help_elabel[i], TRUE);
643     gtk_grid_attach(GTK_GRID(help_etable), help_elabel[i], i % 4, i / 4, 1, 1);
644     gtk_widget_set_name(help_elabel[i], "help_label");
645     gtk_widget_show(help_elabel[i]);
646   }
647 
648   help_vbox = gtk_grid_new();
649   gtk_grid_set_row_spacing(GTK_GRID(help_vbox), 1);
650   gtk_orientable_set_orientation(GTK_ORIENTABLE(help_vbox),
651                                  GTK_ORIENTATION_VERTICAL);
652   gtk_container_set_border_width(GTK_CONTAINER(help_vbox), 5);
653   gtk_container_add(GTK_CONTAINER(help_box), help_vbox);
654 
655   text = gtk_text_view_new();
656   gtk_widget_set_hexpand(text, TRUE);
657   gtk_widget_set_vexpand(text, TRUE);
658   gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(text), FALSE);
659   gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
660   gtk_container_set_border_width(GTK_CONTAINER(text), 5);
661   gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD);
662   gtk_widget_set_name(text, "help_text");
663   help_text = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
664   gtk_widget_show(text);
665 
666   help_text_sw = gtk_scrolled_window_new(NULL, NULL);
667   gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(help_text_sw),
668 				 GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
669   gtk_container_add(GTK_CONTAINER(help_text_sw), text);
670   gtk_container_add(GTK_CONTAINER(help_box), help_text_sw);
671 
672   /* build tech store. */
673   tstore = gtk_tree_store_new(4,
674 			      G_TYPE_STRING,	/* tech name */
675 			      G_TYPE_INT,	/* turns to tech */
676 			      G_TYPE_INT,	/* tech id */
677 			      GDK_TYPE_RGBA);	/* color */
678   help_tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(tstore));
679   gtk_widget_set_hexpand(help_tree, TRUE);
680   gtk_widget_set_vexpand(help_tree, TRUE);
681   g_object_unref(tstore);
682   gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(help_tree), FALSE);
683 
684   g_signal_connect(help_tree, "row_activated",
685 		   G_CALLBACK(help_tech_tree_activated_callback), NULL);
686 
687 
688   col = gtk_tree_view_column_new();
689 
690   rend = gtk_cell_renderer_text_new();
691   g_object_set(rend, "weight", PANGO_WEIGHT_BOLD, NULL);
692   gtk_tree_view_column_pack_start(col, rend, TRUE);
693   gtk_tree_view_column_set_attributes(col, rend,
694 				      "text", 0,
695 				      "background-rgba", 3,
696 				      NULL);
697   rend = gtk_cell_renderer_text_new();
698   g_object_set(rend, "weight", PANGO_WEIGHT_BOLD, "xalign", 1.0, NULL);
699   gtk_tree_view_column_pack_start(col, rend, FALSE);
700   gtk_tree_view_column_set_attributes(col, rend,
701 				      "text", 1,
702 				      "background-rgba", 3,
703 				      NULL);
704 
705   gtk_tree_view_append_column(GTK_TREE_VIEW(help_tree), col);
706 
707 
708   help_tree_sw = gtk_scrolled_window_new(NULL, NULL);
709   gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(help_tree_sw),
710 				 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
711   gtk_container_add(GTK_CONTAINER(help_tree_sw), help_tree);
712   gtk_widget_show(help_tree);
713   gtk_container_add(GTK_CONTAINER(help_box), help_tree_sw);
714 
715   help_tree_expand =
716 	gtk_button_new_with_label(_("Expand All"));
717   help_tree_collapse = gtk_button_new_with_label(_("Collapse All"));
718 
719   g_signal_connect(help_tree_expand, "clicked",
720 		   G_CALLBACK(help_tech_tree_expand_callback), help_tree);
721   g_signal_connect(help_tree_collapse, "clicked",
722 		   G_CALLBACK(help_tech_tree_collapse_callback), help_tree);
723 
724   help_tree_buttons_hbox = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL);
725   gtk_container_add(GTK_CONTAINER(help_tree_buttons_hbox), help_tree_expand);
726   gtk_container_add(GTK_CONTAINER(help_tree_buttons_hbox), help_tree_collapse);
727   gtk_container_add(GTK_CONTAINER(help_box), help_tree_buttons_hbox);
728   gtk_widget_show_all(help_tree_buttons_hbox);
729 
730   create_help_page(HELP_TEXT);
731 }
732 
733 /**************************************************************************
734   Create page for help type
735 **************************************************************************/
create_help_page(enum help_page_type type)736 static void create_help_page(enum help_page_type type)
737 {
738 }
739 
740 /**************************************************************************
741   Set sprite to show for current help item.
742 **************************************************************************/
set_help_tile_from_sprite(struct sprite * spr)743 static void set_help_tile_from_sprite(struct sprite *spr)
744 {
745   GdkPixbuf *pix;
746 
747   if (spr == NULL) {
748     return;
749   }
750 
751   pix = sprite_get_pixbuf(spr);
752   gtk_image_set_from_pixbuf(GTK_IMAGE(help_tile), pix);
753   gtk_widget_show(help_tile);
754 }
755 
756 /**************************************************************************
757   Set sprite to show for current terrain.
758 **************************************************************************/
set_help_tile_from_terrain(struct terrain * pterr)759 static void set_help_tile_from_terrain(struct terrain *pterr)
760 {
761   struct canvas canvas = FC_STATIC_CANVAS_INIT;
762   cairo_t *cr;
763   int i;
764 
765   canvas.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
766                                               tileset_tile_width(tileset),
767                                               tileset_tile_height(tileset));
768 
769   cr = cairo_create(canvas.surface);
770   cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
771   cairo_paint(cr);
772   cairo_destroy(cr);
773 
774   for (i = 0; i < 3; i++) {
775     struct drawn_sprite sprs[80];
776     int count = fill_basic_terrain_layer_sprite_array(tileset, sprs,
777                                                       i, pterr);
778 
779     put_drawn_sprites(&canvas, 1.0, 0, 0, count, sprs, FALSE);
780   }
781 
782   gtk_image_set_from_surface(GTK_IMAGE(help_tile), canvas.surface);
783   gtk_widget_show(help_tile);
784   cairo_surface_destroy(canvas.surface);
785 }
786 
787 /**************************************************************************
788   Display updated help about improvement
789 **************************************************************************/
help_update_improvement(const struct help_item * pitem,char * title)790 static void help_update_improvement(const struct help_item *pitem,
791 				    char *title)
792 {
793   char buf[8192];
794   struct impr_type *imp = improvement_by_translated_name(title);
795 
796   create_help_page(HELP_IMPROVEMENT);
797 
798   if (imp  &&  !is_great_wonder(imp)) {
799     const char *req = skip_intl_qualifier_prefix(REQ_LABEL_NONE);
800     char req_buf[512];
801 
802     sprintf(buf, "%d", impr_build_shield_cost(imp));
803     gtk_label_set_text(GTK_LABEL(help_ilabel[1]), buf);
804     sprintf(buf, "%d", imp->upkeep);
805     gtk_label_set_text(GTK_LABEL(help_ilabel[3]), buf);
806 
807     /* FIXME: this should show ranges, negated reqs, and all the
808      * MAX_NUM_REQS reqs.
809      * Currently it's limited to 1 req but this code is partially prepared
810      * to be extended.  Remember MAX_NUM_REQS is a compile-time
811      * definition. */
812     requirement_vector_iterate(&imp->reqs, preq) {
813       if (!preq->present) {
814         continue;
815       }
816       req = universal_name_translation(&preq->source, req_buf, sizeof(req_buf));
817       break;
818     } requirement_vector_iterate_end;
819     gtk_label_set_text(GTK_LABEL(help_ilabel[5]), req);
820 /*    create_tech_tree(help_improvement_tree, 0, imp->tech_req, 3);*/
821   } else {
822     gtk_label_set_text(GTK_LABEL(help_ilabel[1]), "0");
823     gtk_label_set_text(GTK_LABEL(help_ilabel[3]), "0");
824     gtk_label_set_text(GTK_LABEL(help_ilabel[5]), REQ_LABEL_NEVER);
825 /*    create_tech_tree(help_improvement_tree, 0, advance_count(), 3);*/
826   }
827 
828   set_help_tile_from_sprite(get_building_sprite(tileset, imp));
829 
830   gtk_widget_show(help_itable);
831 
832   helptext_building(buf, sizeof(buf), client.conn.playing, pitem->text, imp);
833   gtk_text_buffer_set_text(help_text, buf, -1);
834   gtk_widget_show(help_text_sw);
835 }
836 
837 /**************************************************************************
838   Display updated help about wonder
839 **************************************************************************/
help_update_wonder(const struct help_item * pitem,char * title)840 static void help_update_wonder(const struct help_item *pitem,
841 			       char *title)
842 {
843   char buf[8192];
844   struct impr_type *imp = improvement_by_translated_name(title);
845 
846   create_help_page(HELP_WONDER);
847 
848   if (imp && is_great_wonder(imp)) {
849     int i;
850     char req_buf[512];
851 
852     sprintf(buf, "%d", impr_build_shield_cost(imp));
853     gtk_label_set_text(GTK_LABEL(help_wlabel[1]), buf);
854 
855     /* FIXME: this should show ranges, negated reqs, and all the
856      * MAX_NUM_REQS reqs.
857      * Currently it's limited to 1 req but this code is partially prepared
858      * to be extended.  Remember MAX_NUM_REQS is a compile-time
859      * definition. */
860     i = 0;
861     requirement_vector_iterate(&imp->reqs, preq) {
862       if (!preq->present) {
863         continue;
864       }
865       gtk_label_set_text(GTK_LABEL(help_wlabel[3 + i]),
866 			 universal_name_translation(&preq->source,
867 					     req_buf, sizeof(req_buf)));
868       i++;
869       break;
870     } requirement_vector_iterate_end;
871     gtk_label_set_text(GTK_LABEL(help_wlabel[5]), REQ_LABEL_NEVER);
872     requirement_vector_iterate(&imp->obsolete_by, pobs) {
873       if (pobs->source.kind == VUT_ADVANCE && pobs->present) {
874         gtk_label_set_text(GTK_LABEL(help_wlabel[5]),
875                            advance_name_translation
876                                (pobs->source.value.advance));
877         break;
878       }
879     } requirement_vector_iterate_end;
880 /*    create_tech_tree(help_improvement_tree, 0, imp->tech_req, 3);*/
881   } else {
882     /* can't find wonder */
883     gtk_label_set_text(GTK_LABEL(help_wlabel[1]), "0");
884     gtk_label_set_text(GTK_LABEL(help_wlabel[3]), REQ_LABEL_NEVER);
885     gtk_label_set_text(GTK_LABEL(help_wlabel[5]), skip_intl_qualifier_prefix(REQ_LABEL_NONE));
886 /*    create_tech_tree(help_improvement_tree, 0, advance_count(), 3); */
887   }
888 
889   set_help_tile_from_sprite(get_building_sprite(tileset, imp));
890 
891   gtk_widget_show(help_wtable);
892 
893   helptext_building(buf, sizeof(buf), client.conn.playing, pitem->text, imp);
894   gtk_text_buffer_set_text(help_text, buf, -1);
895   gtk_widget_show(help_text_sw);
896 }
897 
898 /**************************************************************************
899   Display updated help about unit type
900 **************************************************************************/
help_update_unit_type(const struct help_item * pitem,char * title)901 static void help_update_unit_type(const struct help_item *pitem,
902 				  char *title)
903 {
904   char buf[8192];
905   struct unit_type *utype = unit_type_by_translated_name(title);
906 
907   create_help_page(HELP_UNIT);
908 
909   if (utype) {
910     sprintf(buf, "%d", utype_build_shield_cost(utype));
911     gtk_label_set_text(GTK_LABEL(help_ulabel[0][1]), buf);
912     sprintf(buf, "%d", utype->attack_strength);
913     gtk_label_set_text(GTK_LABEL(help_ulabel[0][4]), buf);
914     sprintf(buf, "%d", utype->defense_strength);
915     gtk_label_set_text(GTK_LABEL(help_ulabel[1][1]), buf);
916     sprintf(buf, "%s", move_points_text(utype->move_rate, TRUE));
917     gtk_label_set_text(GTK_LABEL(help_ulabel[1][4]), buf);
918     sprintf(buf, "%d", utype->firepower);
919     gtk_label_set_text(GTK_LABEL(help_ulabel[2][1]), buf);
920     sprintf(buf, "%d", utype->hp);
921     gtk_label_set_text(GTK_LABEL(help_ulabel[2][4]), buf);
922     gtk_label_set_text(GTK_LABEL(help_ulabel[3][1]),
923 		       helptext_unit_upkeep_str(utype));
924     sprintf(buf, "%d", (int)sqrt((double)utype->vision_radius_sq));
925     gtk_label_set_text(GTK_LABEL(help_ulabel[3][4]), buf);
926     if (A_NEVER == utype->require_advance) {
927       gtk_label_set_text(GTK_LABEL(help_ulabel[4][1]), REQ_LABEL_NEVER);
928     } else {
929       gtk_label_set_text(GTK_LABEL(help_ulabel[4][1]),
930                          advance_name_translation(utype->require_advance));
931     }
932 /*    create_tech_tree(help_improvement_tree, 0, advance_number(utype->require_advance), 3);*/
933     if (U_NOT_OBSOLETED == utype->obsoleted_by) {
934       gtk_label_set_text(GTK_LABEL(help_ulabel[4][4]), skip_intl_qualifier_prefix(REQ_LABEL_NONE));
935     } else {
936       gtk_label_set_text(GTK_LABEL(help_ulabel[4][4]),
937 			 utype_name_translation(utype->obsoleted_by));
938     }
939 
940     helptext_unit(buf, sizeof(buf), client.conn.playing, pitem->text, utype);
941 
942     gtk_text_buffer_set_text(help_text, buf, -1);
943     gtk_widget_show(help_text_sw);
944 
945     set_help_tile_from_sprite(get_unittype_sprite(tileset, utype, direction8_invalid(),
946                                                   TRUE));
947   } else {
948     gtk_label_set_text(GTK_LABEL(help_ulabel[0][1]), "0");
949     gtk_label_set_text(GTK_LABEL(help_ulabel[0][4]), "0");
950     gtk_label_set_text(GTK_LABEL(help_ulabel[1][1]), "0");
951     gtk_label_set_text(GTK_LABEL(help_ulabel[1][4]), "0");
952     gtk_label_set_text(GTK_LABEL(help_ulabel[2][1]), "0");
953     gtk_label_set_text(GTK_LABEL(help_ulabel[2][4]), "0");
954     gtk_label_set_text(GTK_LABEL(help_ulabel[3][1]), "0");
955     gtk_label_set_text(GTK_LABEL(help_ulabel[3][4]), "0");
956 
957     gtk_label_set_text(GTK_LABEL(help_ulabel[4][1]), REQ_LABEL_NEVER);
958 /*    create_tech_tree(help_improvement_tree, 0, A_LAST, 3);*/
959     gtk_label_set_text(GTK_LABEL(help_ulabel[4][4]), skip_intl_qualifier_prefix(REQ_LABEL_NONE));
960 
961     gtk_text_buffer_set_text(help_text, buf, -1);
962     gtk_widget_show(help_text_sw);
963   }
964   gtk_widget_show(help_utable);
965 }
966 
967 /**************************************************************************
968   Cut str to at max len bytes in a utf8 friendly way
969 **************************************************************************/
fc_chomp(char * str,size_t len)970 static char *fc_chomp(char *str, size_t len)
971 {
972   gchar *i;
973 
974   if (!str || !*str)
975     return str;
976 
977   i = str + len;
978   for (i = g_utf8_find_prev_char(str, i);
979        (i && g_unichar_isspace(g_utf8_get_char(i)));
980        i = g_utf8_find_prev_char(str, i)) {
981     *i = '\0';
982   }
983   return str;
984 }
985 
986 /**************************************************************************
987   Display updated help about tech
988 **************************************************************************/
help_update_tech(const struct help_item * pitem,char * title)989 static void help_update_tech(const struct help_item *pitem, char *title)
990 {
991   int i, j;
992   GtkWidget *w, *hbox;
993   char buf[8192];
994   struct advance *padvance = advance_by_translated_name(title);
995 
996   create_help_page(HELP_TECH);
997 
998   if (padvance  &&  !is_future_tech(i = advance_number(padvance))) {
999     GtkTextBuffer *txt;
1000     size_t len;
1001 
1002     gtk_container_foreach(GTK_CONTAINER(help_vbox), (GtkCallback)gtk_widget_destroy, NULL);
1003 
1004     for (j = 0; j < ARRAY_SIZE(help_advances); j++) {
1005       help_advances[j] = FALSE;
1006     }
1007     gtk_tree_store_clear(tstore);
1008     create_tech_tree(i, TECH_TREE_DEPTH, NULL);
1009     gtk_widget_show(help_tree_sw);
1010     gtk_widget_show(help_tree_buttons_hbox);
1011 
1012     helptext_advance(buf, sizeof(buf), client.conn.playing, pitem->text, i);
1013     len = strlen(buf);
1014     fc_chomp(buf, len);
1015 
1016     set_help_tile_from_sprite(get_tech_sprite(tileset, i));
1017 
1018     w = gtk_text_view_new();
1019     gtk_widget_set_hexpand(w, TRUE);
1020     gtk_widget_set_vexpand(w, TRUE);
1021     gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(w), FALSE);
1022     gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(w), GTK_WRAP_WORD);
1023     gtk_widget_set_name(w, "help_text");
1024     gtk_container_set_border_width(GTK_CONTAINER(w), 5);
1025     gtk_text_view_set_editable(GTK_TEXT_VIEW(w), FALSE);
1026     gtk_container_add(GTK_CONTAINER(help_vbox), w);
1027     gtk_widget_show(w);
1028 
1029     txt = gtk_text_view_get_buffer(GTK_TEXT_VIEW(w));
1030     if (txt) {
1031       gtk_text_buffer_set_text(txt, buf, -1);
1032     }
1033 
1034     w = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
1035     g_object_set(w, "margin", 5, NULL);
1036     gtk_widget_set_hexpand(w, TRUE);
1037     gtk_widget_set_vexpand(w, TRUE);
1038     gtk_container_add(GTK_CONTAINER(help_vbox), w);
1039     gtk_widget_show(w);
1040 
1041     governments_iterate(pgov) {
1042       /* FIXME: need a more general mechanism for this, since this
1043        * helptext needs to be shown in all possible req source types. */
1044       requirement_vector_iterate(&pgov->reqs, preq) {
1045 	if (VUT_ADVANCE == preq->source.kind
1046 	    && preq->source.value.advance == padvance) {
1047 	  hbox = gtk_grid_new();
1048 	  gtk_container_add(GTK_CONTAINER(help_vbox), hbox);
1049 	  w = gtk_label_new(_("Allows"));
1050 	  gtk_container_add(GTK_CONTAINER(hbox), w);
1051 	  w = help_slink_new(government_name_translation(pgov),
1052                              HELP_GOVERNMENT);
1053 	  gtk_container_add(GTK_CONTAINER(hbox), w);
1054 	  gtk_widget_show_all(hbox);
1055 	}
1056       } requirement_vector_iterate_end;
1057     } governments_iterate_end;
1058 
1059     improvement_iterate(pimprove) {
1060       if (valid_improvement(pimprove)) {
1061         requirement_vector_iterate(&pimprove->reqs, preq) {
1062           if (VUT_ADVANCE == preq->source.kind
1063               && preq->source.value.advance == padvance) {
1064             hbox = gtk_grid_new();
1065             gtk_container_add(GTK_CONTAINER(help_vbox), hbox);
1066             w = gtk_label_new(_("Allows"));
1067             gtk_container_add(GTK_CONTAINER(hbox), w);
1068             w = help_slink_new(improvement_name_translation(pimprove),
1069                                is_great_wonder(pimprove)
1070                                ? HELP_WONDER
1071                                : HELP_IMPROVEMENT);
1072             gtk_container_add(GTK_CONTAINER(hbox), w);
1073             gtk_widget_show_all(hbox);
1074           }
1075         } requirement_vector_iterate_end;
1076         requirement_vector_iterate(&pimprove->obsolete_by, pobs) {
1077           if (pobs->source.kind == VUT_ADVANCE
1078               && pobs->source.value.advance == padvance
1079               && pobs->present) {
1080             hbox = gtk_grid_new();
1081             gtk_container_add(GTK_CONTAINER(help_vbox), hbox);
1082             w = gtk_label_new(_("Obsoletes"));
1083             gtk_container_add(GTK_CONTAINER(hbox), w);
1084             w = help_slink_new(improvement_name_translation(pimprove),
1085                                is_great_wonder(pimprove)
1086                                ? HELP_WONDER
1087                                : HELP_IMPROVEMENT);
1088             gtk_container_add(GTK_CONTAINER(hbox), w);
1089             gtk_widget_show_all(hbox);
1090           }
1091         } requirement_vector_iterate_end;
1092       }
1093     } improvement_iterate_end;
1094 
1095     unit_type_iterate(punittype) {
1096       if (padvance != punittype->require_advance) {
1097 	continue;
1098       }
1099       hbox = gtk_grid_new();
1100       gtk_container_add(GTK_CONTAINER(help_vbox), hbox);
1101       w = gtk_label_new(_("Allows"));
1102       gtk_container_add(GTK_CONTAINER(hbox), w);
1103       w = help_slink_new(utype_name_translation(punittype), HELP_UNIT);
1104       gtk_container_add(GTK_CONTAINER(hbox), w);
1105       gtk_widget_show_all(hbox);
1106     } unit_type_iterate_end;
1107 
1108     advance_iterate(A_NONE, ptest) {
1109       if (padvance == advance_requires(ptest, AR_ONE)) {
1110 	if (advance_by_number(A_NONE) == advance_requires(ptest, AR_TWO)) {
1111           hbox = gtk_grid_new();
1112           gtk_container_add(GTK_CONTAINER(help_vbox), hbox);
1113           w = gtk_label_new(_("Allows"));
1114           gtk_container_add(GTK_CONTAINER(hbox), w);
1115           w = help_slink_new(advance_name_translation(ptest), HELP_TECH);
1116           gtk_container_add(GTK_CONTAINER(hbox), w);
1117           gtk_widget_show_all(hbox);
1118 	} else {
1119           hbox = gtk_grid_new();
1120           gtk_container_add(GTK_CONTAINER(help_vbox), hbox);
1121           w = gtk_label_new(_("Allows"));
1122           gtk_container_add(GTK_CONTAINER(hbox), w);
1123           w = help_slink_new(advance_name_translation(ptest), HELP_TECH);
1124           gtk_container_add(GTK_CONTAINER(hbox), w);
1125           w = gtk_label_new(_("with"));
1126           gtk_container_add(GTK_CONTAINER(hbox), w);
1127           w = help_slink_new(advance_name_translation(advance_requires(ptest, AR_TWO)),
1128                              HELP_TECH);
1129           gtk_container_add(GTK_CONTAINER(hbox), w);
1130           w = gtk_label_new(Q_("?techhelp:"));
1131           gtk_container_add(GTK_CONTAINER(hbox), w);
1132           gtk_widget_show_all(hbox);
1133 	}
1134       }
1135       if (padvance == advance_requires(ptest, AR_TWO)) {
1136         hbox = gtk_grid_new();
1137         gtk_container_add(GTK_CONTAINER(help_vbox), hbox);
1138         w = gtk_label_new(_("Allows"));
1139         gtk_container_add(GTK_CONTAINER(hbox), w);
1140         w = help_slink_new(advance_name_translation(ptest), HELP_TECH);
1141         gtk_container_add(GTK_CONTAINER(hbox), w);
1142         w = gtk_label_new(_("with"));
1143         gtk_container_add(GTK_CONTAINER(hbox), w);
1144         w = help_slink_new(advance_name_translation(advance_requires(ptest, AR_ONE)),
1145                            HELP_TECH);
1146         gtk_container_add(GTK_CONTAINER(hbox), w);
1147         w = gtk_label_new(Q_("?techhelp:"));
1148         gtk_container_add(GTK_CONTAINER(hbox), w);
1149         gtk_widget_show_all(hbox);
1150       }
1151     } advance_iterate_end;
1152     gtk_widget_show(help_vbox);
1153   }
1154 }
1155 
1156 /**********************************************************************//**
1157   Add a line for an activity linking to help for result
1158 **************************************************************************/
add_act_help_for_terrain(const char * act_label,const char * result_link_label,enum help_page_type result_link_type,const char * descr_label)1159 static void add_act_help_for_terrain(const char *act_label,
1160                                      const char *result_link_label,
1161                                      enum help_page_type result_link_type,
1162                                      const char *descr_label)
1163 {
1164   GtkWidget *w;
1165   GtkWidget *hbox;
1166 
1167   hbox = gtk_grid_new();
1168   gtk_container_add(GTK_CONTAINER(help_vbox), hbox);
1169   w = gtk_label_new(act_label);
1170   gtk_container_add(GTK_CONTAINER(hbox), w);
1171   w = help_slink_new(result_link_label, result_link_type);
1172   gtk_container_add(GTK_CONTAINER(hbox), w);
1173   w = gtk_label_new(descr_label);
1174   gtk_container_add(GTK_CONTAINER(hbox), w);
1175 
1176   gtk_widget_show_all(hbox);
1177 }
1178 
1179 /**************************************************************************
1180   Create widgets about all extras of one cause activity to the terrain.
1181 **************************************************************************/
help_extras_of_act_for_terrain(struct terrain * pterr,enum unit_activity act,char * label)1182 static void help_extras_of_act_for_terrain(struct terrain *pterr,
1183                                            enum unit_activity act,
1184                                            char *label)
1185 {
1186   enum extra_cause cause = activity_to_extra_cause(act);
1187 
1188   extra_type_by_cause_iterate(cause, pextra) {
1189     if (pextra->buildable
1190         && requirement_fulfilled_by_terrain(pterr, &(pextra->reqs))) {
1191       add_act_help_for_terrain(label,
1192                                extra_name_translation(pextra), HELP_EXTRA,
1193                                helptext_extra_for_terrain_str(pextra, pterr,
1194                                                               act));
1195     }
1196   } extra_type_by_cause_iterate_end;
1197 }
1198 
1199 /**************************************************************************
1200   Display updated help about terrain
1201 **************************************************************************/
help_update_terrain(const struct help_item * pitem,char * title)1202 static void help_update_terrain(const struct help_item *pitem,
1203 				char *title)
1204 {
1205   char buf[8192];
1206   struct terrain *pterrain = terrain_by_translated_name(title);
1207 
1208   create_help_page(HELP_TERRAIN);
1209 
1210   if (pterrain) {
1211     struct universal for_terr = { .kind = VUT_TERRAIN, .value = { .terrain = pterrain }};
1212 
1213     set_help_tile_from_terrain(pterrain);
1214 
1215     {
1216       /* 25 => "1.25"; 50 => "1.5"; 100 => "2.0" */
1217       int defbonus = pterrain->defense_bonus + 100;
1218       int frac = defbonus % 100;
1219 
1220       if ((frac % 10) == 0) {
1221         frac /= 10;
1222       }
1223       sprintf(buf, "%d/%d.%d",
1224               pterrain->movement_cost, defbonus / 100, frac);
1225     }
1226     gtk_label_set_text(GTK_LABEL(help_tlabel[0][1]), buf);
1227 
1228     sprintf(buf, "%d/%d/%d",
1229 	    pterrain->output[O_FOOD],
1230 	    pterrain->output[O_SHIELD],
1231 	    pterrain->output[O_TRADE]);
1232     gtk_label_set_text(GTK_LABEL(help_tlabel[0][4]), buf);
1233 
1234     buf[0] = '\0';
1235     if (*(pterrain->resources)) {
1236       struct resource **r;
1237 
1238       for (r = pterrain->resources; *r; r++) {
1239         /* TRANS: " Whales (2/1/2)," */
1240         sprintf (buf + strlen (buf), " %s (%d/%d/%d),",
1241                  resource_name_translation(*r),
1242                  pterrain->output[O_FOOD]   + (*r)->output[O_FOOD],
1243                  pterrain->output[O_SHIELD] + (*r)->output[O_SHIELD],
1244                  pterrain->output[O_TRADE]  + (*r)->output[O_TRADE]);
1245       }
1246       buf[strlen (buf) - 1] = '.';
1247     } else {
1248       /* TRANS: "Resources: (none)" */
1249       sprintf (buf + strlen (buf), _("(none)"));
1250     }
1251     gtk_label_set_text(GTK_LABEL(help_tlabel[1][1]), buf);
1252 
1253     gtk_container_foreach(GTK_CONTAINER(help_vbox), (GtkCallback)gtk_widget_destroy, NULL);
1254 
1255     if (pterrain->irrigation_result != pterrain
1256         && pterrain->irrigation_result != T_NONE
1257         && pterrain->irrigation_time != 0
1258         && effect_cumulative_max(EFT_IRRIG_TF_POSSIBLE, &for_terr) > 0) {
1259       fc_snprintf(buf, sizeof(buf),
1260                   PL_("%d turn", "%d turns", pterrain->irrigation_time),
1261                   pterrain->irrigation_time);
1262       add_act_help_for_terrain(_("Irrig. Rslt/Time"),
1263                                terrain_name_translation(pterrain->irrigation_result),
1264                                HELP_TERRAIN, buf);
1265     }
1266 
1267     if (pterrain->mining_result != pterrain
1268         && pterrain->mining_result != T_NONE
1269         && pterrain->mining_time != 0
1270         && effect_cumulative_max(EFT_MINING_TF_POSSIBLE, &for_terr) > 0) {
1271       fc_snprintf(buf, sizeof(buf),
1272                   PL_("%d turn", "%d turns", pterrain->mining_time),
1273                   pterrain->mining_time);
1274       add_act_help_for_terrain(_("Mine Rslt/Time"),
1275                                terrain_name_translation(pterrain->mining_result),
1276                                HELP_TERRAIN, buf);
1277     }
1278 
1279     if (pterrain->transform_result != T_NONE
1280         && pterrain->transform_time != 0
1281         && effect_cumulative_max(EFT_TRANSFORM_POSSIBLE, &for_terr) > 0) {
1282       fc_snprintf(buf, sizeof(buf),
1283                   PL_("%d turn", "%d turns", pterrain->transform_time),
1284                   pterrain->transform_time);
1285       add_act_help_for_terrain(_("Trans. Rslt/Time"),
1286                                terrain_name_translation(pterrain->transform_result),
1287                                HELP_TERRAIN, buf);
1288     }
1289 
1290     if (pterrain->irrigation_result == pterrain
1291         && pterrain->irrigation_time != 0
1292         && effect_cumulative_max(EFT_IRRIG_POSSIBLE, &for_terr) > 0) {
1293       help_extras_of_act_for_terrain(pterrain, ACTIVITY_IRRIGATE, _("Build as irrigation"));
1294     }
1295     if (pterrain->mining_result == pterrain
1296         && pterrain->mining_time != 0
1297         && effect_cumulative_max(EFT_MINING_POSSIBLE, &for_terr) > 0) {
1298       help_extras_of_act_for_terrain(pterrain, ACTIVITY_MINE, _("Build as mine"));
1299     }
1300     if (pterrain->road_time != 0) {
1301       help_extras_of_act_for_terrain(pterrain, ACTIVITY_GEN_ROAD, _("Build as road"));
1302     }
1303     if (pterrain->base_time != 0) {
1304       help_extras_of_act_for_terrain(pterrain, ACTIVITY_BASE, _("Build as base"));
1305     }
1306     gtk_widget_show(help_vbox);
1307   }
1308 
1309   helptext_terrain(buf, sizeof(buf), client.conn.playing, pitem->text, pterrain);
1310 
1311   gtk_text_buffer_set_text(help_text, buf, -1);
1312   gtk_widget_show(help_text_sw);
1313 
1314   gtk_widget_show(help_ttable);
1315 }
1316 
1317 /**************************************************************************
1318   Help page for extras.
1319 **************************************************************************/
help_update_extra(const struct help_item * pitem,char * title)1320 static void help_update_extra(const struct help_item *pitem, char *title)
1321 {
1322   char buf[8192];
1323   struct extra_type *pextra = extra_type_by_translated_name(title);
1324 
1325   create_help_page(HELP_EXTRA);
1326 
1327   buf[0] = '\0';
1328   if (pextra == NULL) {
1329     strcat(buf, pitem->text);
1330   } else {
1331     struct road_type *proad = extra_road_get(pextra);
1332 
1333     /* Cost to build */
1334     if (pextra->buildable) {
1335       if (pextra->build_time != 0) {
1336         /* TRANS: "MP" = movement points */
1337         sprintf(buf, _("%d MP"), pextra->build_time);
1338       } else {
1339         /* TRANS: Build time depends on terrain. */
1340         sprintf(buf, _("Terrain specific"));
1341       }
1342     } else {
1343       sprintf(buf, "-");
1344     }
1345     gtk_label_set_text(GTK_LABEL(help_elabel[1]), buf);
1346     /* Conflicting extras */
1347     buf[0] = '\0';
1348     extra_type_iterate(pextra2) {
1349       if (!can_extras_coexist(pextra, pextra2)) {
1350         if (buf[0] != '\0') {
1351           strcat(buf, "/");
1352         }
1353         strcat(buf, extra_name_translation(pextra2));
1354       }
1355     } extra_type_iterate_end;
1356     /* TRANS: "Conflicts with: (none)" (extras) */
1357     gtk_label_set_text(GTK_LABEL(help_elabel[3]), buf[0] ? buf : _("(none)"));
1358 
1359     /* Bonus */
1360     if (proad != NULL) {
1361       const char *bonus = NULL;
1362 
1363       output_type_iterate(o) {
1364         if (proad->tile_incr[o] > 0) {
1365           /* TRANS: Road bonus depends on terrain. */
1366           bonus = _("Terrain specific");
1367           break;
1368         }
1369       } output_type_iterate_end;
1370       if (!bonus) {
1371         bonus = helptext_road_bonus_str(NULL, proad);
1372       }
1373       if (!bonus) {
1374         /* TRANS: No output bonus from a road */
1375         bonus = Q_("?bonus:None");
1376       }
1377       gtk_label_set_text(GTK_LABEL(help_elabel[5]), bonus);
1378     } else {
1379       gtk_label_set_text(GTK_LABEL(help_elabel[5]), Q_("?bonus:None"));
1380     }
1381 
1382     helptext_extra(buf, sizeof(buf), client.conn.playing, pitem->text, pextra);
1383   }
1384   gtk_widget_show(help_etable);
1385 
1386   gtk_text_buffer_set_text(help_text, buf, -1);
1387   gtk_widget_show(help_text_sw);
1388 }
1389 
1390 /**************************************************************************
1391   This is currently just a text page, with special text:
1392 **************************************************************************/
help_update_specialist(const struct help_item * pitem,char * title)1393 static void help_update_specialist(const struct help_item *pitem,
1394 				   char *title)
1395 {
1396   char buf[8192];
1397   struct specialist *pspec = specialist_by_translated_name(title);
1398 
1399   if (!pspec) {
1400     strcat(buf, pitem->text);
1401   } else {
1402     helptext_specialist(buf, sizeof(buf), client.conn.playing, pitem->text,
1403                         pspec);
1404   }
1405   create_help_page(HELP_TEXT);
1406   gtk_text_buffer_set_text(help_text, buf, -1);
1407   gtk_widget_show(help_text_sw);
1408 }
1409 
1410 /**************************************************************************
1411   This is currently just a text page, with special text:
1412 **************************************************************************/
help_update_government(const struct help_item * pitem,char * title)1413 static void help_update_government(const struct help_item *pitem,
1414 				   char *title)
1415 {
1416   char buf[8192];
1417   struct government *gov = government_by_translated_name(title);
1418 
1419   if (!gov) {
1420     strcat(buf, pitem->text);
1421   } else {
1422     helptext_government(buf, sizeof(buf), client.conn.playing, pitem->text, gov);
1423   }
1424   create_help_page(HELP_TEXT);
1425   gtk_text_buffer_set_text(help_text, buf, -1);
1426   gtk_widget_show(help_text_sw);
1427 }
1428 
1429 /**************************************************************************
1430   This is currently just a text page, with special text
1431 **************************************************************************/
help_update_nation(const struct help_item * pitem,char * title,struct nation_type * pnation)1432 static void help_update_nation(const struct help_item *pitem, char *title,
1433 			       struct nation_type *pnation)
1434 {
1435   char buf[4096];
1436 
1437   if (!pnation) {
1438     strcat(buf, pitem->text);
1439   } else {
1440     helptext_nation(buf, sizeof(buf), pnation, pitem->text);
1441   }
1442   create_help_page(HELP_TEXT);
1443   gtk_text_buffer_set_text(help_text, buf, -1);
1444   gtk_widget_show(help_text_sw);
1445 }
1446 
1447 /**************************************************************************
1448   Display updated help dialog
1449 **************************************************************************/
help_update_dialog(const struct help_item * pitem)1450 static void help_update_dialog(const struct help_item *pitem)
1451 {
1452   char *top;
1453 
1454   /* figure out what kind of item is required for pitem ingo */
1455 
1456   for (top = pitem->topic; *top == ' '; top++) {
1457     /* nothing */
1458   }
1459 
1460   help_box_hide();
1461   gtk_text_buffer_set_text(help_text, "", -1);
1462 
1463   switch(pitem->type) {
1464   case HELP_IMPROVEMENT:
1465     help_update_improvement(pitem, top);
1466     break;
1467   case HELP_WONDER:
1468     help_update_wonder(pitem, top);
1469     break;
1470   case HELP_UNIT:
1471     help_update_unit_type(pitem, top);
1472     break;
1473   case HELP_TECH:
1474     help_update_tech(pitem, top);
1475     break;
1476   case HELP_TERRAIN:
1477     help_update_terrain(pitem, top);
1478     break;
1479   case HELP_EXTRA:
1480     help_update_extra(pitem, top);
1481     break;
1482   case HELP_SPECIALIST:
1483     help_update_specialist(pitem, top);
1484     break;
1485   case HELP_GOVERNMENT:
1486     help_update_government(pitem, top);
1487     break;
1488   case HELP_NATIONS:
1489     help_update_nation(pitem, top, nation_by_translated_plural(top));
1490     break;
1491   case HELP_TEXT:
1492   default:
1493     /* it was a pure text item */
1494     create_help_page(HELP_TEXT);
1495 
1496     gtk_text_buffer_set_text(help_text, pitem->text, -1);
1497     gtk_widget_show(help_text_sw);
1498     break;
1499   }
1500   set_title_topic(pitem->topic);
1501 
1502   gtk_widget_show(help_box);
1503 }
1504 
1505 /**************************************************************************
1506   Add item at path to selection and scroll to its cell
1507 **************************************************************************/
help_item_zoom(GtkTreePath * path)1508 static void help_item_zoom(GtkTreePath *path)
1509 {
1510   GtkTreeModel *model;
1511   GtkTreeIter   it, child, item;
1512   GtkTreeSelection *selection;
1513 
1514   model = gtk_tree_view_get_model(GTK_TREE_VIEW(help_view));
1515   gtk_tree_model_get_iter(model, &item, path);
1516 
1517   for (child=item; gtk_tree_model_iter_parent(model, &it, &child); child=it) {
1518     GtkTreePath *it_path;
1519 
1520     it_path = gtk_tree_model_get_path(model, &it);
1521     gtk_tree_view_expand_row(GTK_TREE_VIEW(help_view), it_path, TRUE);
1522     gtk_tree_path_free(it_path);
1523   }
1524 
1525   selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(help_view));
1526   gtk_tree_selection_select_iter(selection, &item);
1527   gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(help_view), path, NULL,
1528 			       TRUE, 0.0, 0.0);
1529 }
1530 
1531 /****************************************************************
1532   Return path to help item.
1533 *****************************************************************/
help_item_path(const struct help_item * pitem)1534 static GtkTreePath *help_item_path(const struct help_item *pitem)
1535 {
1536   GtkTreePath *path;
1537   bool next;
1538 
1539   path = gtk_tree_path_new_first();
1540   next = FALSE;
1541   help_items_iterate(pitem2) {
1542     const char *s;
1543     int depth;
1544 
1545     for (s = pitem2->topic; *s == ' '; s++) {
1546       /* nothing */
1547     }
1548     depth = s - pitem2->topic + 1;
1549 
1550     while (depth < gtk_tree_path_get_depth(path)) {
1551       gtk_tree_path_up(path);
1552       gtk_tree_path_next(path);
1553       next = FALSE;
1554     }
1555     while (depth > gtk_tree_path_get_depth(path)) {
1556       gtk_tree_path_down(path);
1557       next = FALSE;
1558     }
1559 
1560     if (next) {
1561       gtk_tree_path_next(path);
1562     }
1563 
1564     if (pitem == pitem2)
1565       break;
1566 
1567     next = TRUE;
1568   } help_items_iterate_end;
1569 
1570   return path;
1571 }
1572 
1573 /****************************************************************
1574   Add item to selection
1575 *****************************************************************/
select_help_item_string(const char * item,enum help_page_type htype)1576 static void select_help_item_string(const char *item, enum help_page_type htype)
1577 {
1578   const struct help_item *pitem;
1579   int idx;
1580   GtkTreePath *path;
1581   GtkTreeViewColumn *col;
1582 
1583   if (!(pitem = get_help_item_spec(item, htype, &idx))) {
1584     return;
1585   }
1586 
1587   path = help_item_path(pitem);
1588   help_item_zoom(path);
1589 
1590   col = gtk_tree_view_get_column(GTK_TREE_VIEW(help_view), 0);
1591   gtk_tree_view_set_cursor(GTK_TREE_VIEW(help_view), path, col, FALSE);
1592   gtk_tree_path_free(path);
1593 }
1594 
1595 /**************************************************************************
1596   Set sensitivity of help dialog response buttons.
1597 **************************************************************************/
help_command_update(void)1598 static void help_command_update(void)
1599 {
1600   GtkDialog *dialog = GTK_DIALOG(help_dialog_shell);
1601 
1602   if (help_history_pos < 0) {
1603     gtk_dialog_set_response_sensitive(dialog, 1, FALSE);
1604     gtk_dialog_set_response_sensitive(dialog, 2, FALSE);
1605   } else {
1606     gtk_dialog_set_response_sensitive(dialog, 1, TRUE);
1607     gtk_dialog_set_response_sensitive(dialog, 2, TRUE);
1608 
1609     if (help_history_pos == 0) {
1610       gtk_dialog_set_response_sensitive(dialog, 1, FALSE);
1611     }
1612     if (help_history_pos >= help_history->len - 1) {
1613       gtk_dialog_set_response_sensitive(dialog, 2, FALSE);
1614     }
1615   }
1616 }
1617 
1618 /**************************************************************************
1619   User gave response to help dialog
1620 **************************************************************************/
help_command_callback(GtkWidget * w,gint response_id)1621 static void help_command_callback(GtkWidget *w, gint response_id)
1622 {
1623   GtkTreePath *path;
1624   const struct help_item *pitem;
1625 
1626   if (response_id == 1) {
1627     if (help_history_pos > 0) {
1628       help_history_pos--;
1629 
1630       pitem = g_ptr_array_index(help_history, help_history_pos);
1631       path = help_item_path(pitem);
1632       help_item_zoom(path);
1633       help_update_dialog(pitem);
1634       help_command_update();
1635     }
1636   } else if (response_id == 2) {
1637     if (help_history_pos < help_history->len - 1) {
1638       help_history_pos++;
1639 
1640       pitem = g_ptr_array_index(help_history, help_history_pos);
1641       path = help_item_path(pitem);
1642       help_item_zoom(path);
1643       help_update_dialog(pitem);
1644       help_command_update();
1645     }
1646   } else {
1647     gtk_widget_destroy(help_dialog_shell);
1648   }
1649 }
1650