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 <gtk/gtk.h>
19 
20 /* utility */
21 #include "fcintl.h"
22 #include "log.h"
23 #include "mem.h"
24 #include "support.h"
25 
26 /* common */
27 #include "city.h"
28 #include "game.h"
29 #include "government.h"
30 
31 /* client */
32 #include "text.h"
33 #include "tilespec.h"
34 
35 /* client/gui-gtk-3.0 */
36 #include "graphics.h"
37 #include "gui_main.h"
38 #include "gui_stuff.h"
39 #include "happiness.h"
40 #include "mapview.h"
41 
42 /* semi-arbitrary number that controls the width of the happiness widget */
43 #define HAPPINESS_PIX_WIDTH 30
44 
45 #define	FEELING_WIDTH	(HAPPINESS_PIX_WIDTH * tileset_small_sprite_width(tileset))
46 #define	FEELING_HEIGHT	(tileset_small_sprite_height(tileset))
47 
48 #define NUM_HAPPINESS_MODIFIERS 6
49 
50 enum { CITIES, LUXURIES, BUILDINGS, NATIONALITY, UNITS, WONDERS };
51 
52 struct happiness_dialog {
53   struct city *pcity;
54   GtkWidget *win;
55   GtkWidget *shell;
56   GtkWidget *cityname_label;
57   cairo_surface_t *feeling_surfaces[NUM_HAPPINESS_MODIFIERS];
58   GtkWidget *feeling_images[NUM_HAPPINESS_MODIFIERS];
59   GtkWidget *happiness_ebox[NUM_HAPPINESS_MODIFIERS];
60   GtkWidget *happiness_label[NUM_HAPPINESS_MODIFIERS];
61   GtkWidget *close;
62 };
63 
64 #define SPECLIST_TAG dialog
65 #define SPECLIST_TYPE struct happiness_dialog
66 #include "speclist.h"
67 
68 #define dialog_list_iterate(dialoglist, pdialog) \
69     TYPED_LIST_ITERATE(struct happiness_dialog, dialoglist, pdialog)
70 #define dialog_list_iterate_end  LIST_ITERATE_END
71 
72 static struct dialog_list *dialog_list;
73 static struct happiness_dialog *get_happiness_dialog(struct city *pcity);
74 static struct happiness_dialog *create_happiness_dialog(struct city *pcity,
75                                                         bool low_dlg,
76                                                         GtkWidget *win);
77 static gboolean show_happiness_popup(GtkWidget *w,
78                                      GdkEventButton *ev,
79                                      gpointer data);
80 static gboolean show_happiness_button_release(GtkWidget *w,
81                                               GdkEventButton *ev,
82                                               gpointer data);
83 
84 /****************************************************************
85   Create happiness dialog
86 *****************************************************************/
happiness_dialog_init(void)87 void happiness_dialog_init(void)
88 {
89   dialog_list = dialog_list_new();
90 }
91 
92 /****************************************************************
93   Remove happiness dialog
94 *****************************************************************/
happiness_dialog_done(void)95 void happiness_dialog_done(void)
96 {
97   dialog_list_destroy(dialog_list);
98 }
99 
100 /****************************************************************
101   Return happiness dialog for a city
102 *****************************************************************/
get_happiness_dialog(struct city * pcity)103 static struct happiness_dialog *get_happiness_dialog(struct city *pcity)
104 {
105   dialog_list_iterate(dialog_list, pdialog) {
106     if (pdialog->pcity == pcity) {
107       return pdialog;
108     }
109   } dialog_list_iterate_end;
110 
111   return NULL;
112 }
113 
114 /****************************************************************
115   Popup for the happiness display.
116 *****************************************************************/
show_happiness_popup(GtkWidget * w,GdkEventButton * ev,gpointer data)117 static gboolean show_happiness_popup(GtkWidget *w,
118                                      GdkEventButton *ev,
119                                      gpointer data)
120 {
121   struct happiness_dialog *pdialog = g_object_get_data(G_OBJECT(w),
122                                                        "pdialog");
123 
124   if (ev->button == 1) {
125     GtkWidget *p, *label, *frame;
126     char buf[1024];
127 
128     switch (GPOINTER_TO_UINT(data)) {
129     case CITIES:
130       sz_strlcpy(buf, text_happiness_cities(pdialog->pcity));
131       break;
132     case LUXURIES:
133       sz_strlcpy(buf, text_happiness_luxuries(pdialog->pcity));
134       break;
135     case BUILDINGS:
136       sz_strlcpy(buf, text_happiness_buildings(pdialog->pcity));
137       break;
138     case NATIONALITY:
139       sz_strlcpy(buf, text_happiness_nationality(pdialog->pcity));
140       break;
141     case UNITS:
142       sz_strlcpy(buf, text_happiness_units(pdialog->pcity));
143       break;
144     case WONDERS:
145       sz_strlcpy(buf, text_happiness_wonders(pdialog->pcity));
146       break;
147     default:
148       return TRUE;
149     }
150 
151     p = gtk_window_new(GTK_WINDOW_POPUP);
152     gtk_widget_set_name(p, "Freeciv");
153     gtk_container_set_border_width(GTK_CONTAINER(p), 2);
154     gtk_window_set_transient_for(GTK_WINDOW(p), GTK_WINDOW(pdialog->win));
155     gtk_window_set_position(GTK_WINDOW(p), GTK_WIN_POS_MOUSE);
156 
157     frame = gtk_frame_new(NULL);
158     gtk_container_add(GTK_CONTAINER(p), frame);
159 
160     label = gtk_label_new(buf);
161     /* FIXME: there is no font option corresponding to this style name.
162      * Remove?: */
163     gtk_widget_set_name(label, "city_happiness_label");
164     gtk_widget_set_margin_left(label, 4);
165     gtk_widget_set_margin_right(label, 4);
166     gtk_widget_set_margin_top(label, 4);
167     gtk_widget_set_margin_bottom(label, 4);
168     gtk_container_add(GTK_CONTAINER(frame), label);
169     gtk_widget_show_all(p);
170 
171     gdk_device_grab(ev->device, gtk_widget_get_window(p),
172                     GDK_OWNERSHIP_NONE, TRUE, GDK_BUTTON_RELEASE_MASK, NULL,
173                     ev->time);
174     gtk_grab_add(p);
175 
176     g_signal_connect_after(p, "button_release_event",
177                            G_CALLBACK(show_happiness_button_release), NULL);
178   }
179 
180   return TRUE;
181 }
182 
183 /**************************************************************************
184   Clear the happiness popup.
185 **************************************************************************/
show_happiness_button_release(GtkWidget * w,GdkEventButton * ev,gpointer data)186 static gboolean show_happiness_button_release(GtkWidget *w,
187                                               GdkEventButton *ev,
188                                               gpointer data)
189 {
190   gtk_grab_remove(w);
191   gdk_device_ungrab(ev->device, ev->time);
192   gtk_widget_destroy(w);
193   return FALSE;
194 }
195 
196 /**************************************************************************
197   Create the happiness notebook page.
198 **************************************************************************/
create_happiness_dialog(struct city * pcity,bool low_dlg,GtkWidget * win)199 static struct happiness_dialog *create_happiness_dialog(struct city *pcity,
200                                                         bool low_dlg,
201                                                         GtkWidget *win)
202 {
203   int i;
204   struct happiness_dialog *pdialog;
205   GtkWidget *ebox, *label, *table;
206   char buf[700];
207 
208   static const char *happiness_label_str[NUM_HAPPINESS_MODIFIERS] = {
209     N_("Cities:"),
210     N_("Luxuries:"),
211     N_("Buildings:"),
212     N_("Nationality:"),
213     N_("Units:"),
214     N_("Wonders:"),
215   };
216   static bool happiness_label_str_done;
217 
218   pdialog = fc_malloc(sizeof(struct happiness_dialog));
219   pdialog->pcity = pcity;
220 
221   pdialog->shell = gtk_grid_new();
222   gtk_orientable_set_orientation(GTK_ORIENTABLE(pdialog->shell),
223                                  GTK_ORIENTATION_VERTICAL);
224 
225   pdialog->cityname_label = gtk_frame_new(_("Happiness"));
226   gtk_container_add(GTK_CONTAINER(pdialog->shell), pdialog->cityname_label);
227 
228   table = gtk_grid_new();
229   g_object_set(table, "margin", 4, NULL);
230   gtk_grid_set_row_spacing(GTK_GRID(table), 10);
231 
232   intl_slist(ARRAY_SIZE(happiness_label_str), happiness_label_str,
233              &happiness_label_str_done);
234 
235   gtk_container_add(GTK_CONTAINER(pdialog->cityname_label), table);
236 
237   for (i = 0; i < NUM_HAPPINESS_MODIFIERS; i++) {
238     GdkPixbuf *pb;
239 
240     /* set spacing between lines of citizens*/
241 
242     /* happiness labels */
243     label = gtk_label_new(happiness_label_str[i]);
244     pdialog->happiness_label[i] = label;
245     gtk_widget_set_name(label, "city_label");
246     gtk_widget_set_halign(label, GTK_ALIGN_START);
247     gtk_widget_set_valign(label, GTK_ALIGN_CENTER);
248 
249     gtk_grid_attach(GTK_GRID(table), label, 0, i, 1, 1);
250 
251     /* list of citizens */
252     ebox = gtk_event_box_new();
253     gtk_widget_set_margin_left(ebox, 5);
254     gtk_event_box_set_visible_window(GTK_EVENT_BOX(ebox), FALSE);
255     g_object_set_data(G_OBJECT(ebox), "pdialog", pdialog);
256     g_signal_connect(ebox, "button_press_event",
257                      G_CALLBACK(show_happiness_popup), GUINT_TO_POINTER(i));
258     pdialog->happiness_ebox[i] = ebox;
259 
260     pdialog->feeling_surfaces[i] = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
261                                                               FEELING_WIDTH, FEELING_HEIGHT);
262     pb = surface_get_pixbuf(pdialog->feeling_surfaces[i], FEELING_WIDTH, FEELING_HEIGHT);
263     pdialog->feeling_images[i] = gtk_image_new_from_pixbuf(pb);
264     g_object_unref(pb);
265     gtk_container_add(GTK_CONTAINER(ebox), pdialog->feeling_images[i]);
266     gtk_widget_set_halign(pdialog->feeling_images[i], GTK_ALIGN_START);
267     gtk_widget_set_valign(pdialog->feeling_images[i], GTK_ALIGN_START);
268 
269     gtk_grid_attach(GTK_GRID(table), ebox, 1, i, 1, 1);
270   }
271 
272   /* TRANS: the width of this text defines the width of the city dialog.
273    *        '%s' is either space or newline depending on screen real estate. */
274   fc_snprintf(buf, sizeof(buf),
275               _("Additional information is available%svia left "
276                 "click on the citizens."), low_dlg ? "\n" : " ");
277   label = gtk_label_new(buf);
278   gtk_widget_set_name(label, "city_label");
279   gtk_widget_set_halign(label, GTK_ALIGN_START);
280   gtk_widget_set_valign(label, GTK_ALIGN_CENTER);
281   gtk_grid_attach(GTK_GRID(table), label, 0, NUM_HAPPINESS_MODIFIERS, 2, 1);
282 
283   gtk_widget_show_all(pdialog->shell);
284   dialog_list_prepend(dialog_list, pdialog);
285   refresh_happiness_dialog(pcity);
286 
287   pdialog->win = win;
288 
289   return pdialog;
290 }
291 
292 /**************************************************************************
293   Refresh citizens surface
294 **************************************************************************/
refresh_feeling_surface(cairo_surface_t * dst,struct city * pcity,enum citizen_feeling index)295 static void refresh_feeling_surface(cairo_surface_t *dst, struct city *pcity,
296                                     enum citizen_feeling index)
297 {
298   enum citizen_category categories[MAX_CITY_SIZE];
299   int i;
300   int num_citizens = get_city_citizen_types(pcity, index, categories);
301   int offset = MIN(tileset_small_sprite_width(tileset), FEELING_WIDTH / num_citizens);
302   cairo_t *cr;
303 
304   cr = cairo_create(dst);
305 
306   for (i = 0; i < num_citizens; i++) {
307     cairo_set_source_surface(cr,
308                              get_citizen_sprite(tileset, categories[i], i, pcity)->surface,
309                              i * offset, 0);
310     cairo_rectangle(cr, i * offset, 0, offset, FEELING_HEIGHT);
311     cairo_fill(cr);
312   }
313 
314   cairo_destroy(cr);
315 }
316 
317 /**************************************************************************
318   Refresh whole happiness dialog
319 **************************************************************************/
refresh_happiness_dialog(struct city * pcity)320 void refresh_happiness_dialog(struct city *pcity)
321 {
322   int i;
323   struct happiness_dialog *pdialog = get_happiness_dialog(pcity);
324 
325   for (i = 0; i < FEELING_LAST; i++) {
326     GdkPixbuf *pb;
327 
328     refresh_feeling_surface(pdialog->feeling_surfaces[i], pdialog->pcity, i);
329 
330     pb = surface_get_pixbuf(pdialog->feeling_surfaces[i], FEELING_WIDTH, FEELING_HEIGHT);
331     gtk_image_set_from_pixbuf(GTK_IMAGE(pdialog->feeling_images[i]), pb);
332     g_object_unref(pb);
333   }
334 }
335 
336 /**************************************************************************
337   Close happiness dialog of given city
338 **************************************************************************/
close_happiness_dialog(struct city * pcity)339 void close_happiness_dialog(struct city *pcity)
340 {
341   struct happiness_dialog *pdialog = get_happiness_dialog(pcity);
342   int i;
343 
344   if (pdialog == NULL) {
345     /* City which is being investigated doesn't contain happiness tab */
346     return;
347   }
348 
349   gtk_widget_hide(pdialog->shell);
350 
351   dialog_list_remove(dialog_list, pdialog);
352 
353   gtk_widget_destroy(pdialog->shell);
354 
355   for (i = 0; i < NUM_HAPPINESS_MODIFIERS; i++) {
356     cairo_surface_destroy(pdialog->feeling_surfaces[i]);
357   }
358 
359   free(pdialog);
360 }
361 
362 /**************************************************************************
363   Create happiness dialog and get its widget
364 **************************************************************************/
get_top_happiness_display(struct city * pcity,bool low_dlg,GtkWidget * win)365 GtkWidget *get_top_happiness_display(struct city *pcity, bool low_dlg,
366                                      GtkWidget *win)
367 {
368   return create_happiness_dialog(pcity, low_dlg, win)->shell;
369 }
370