1 /* Pioneers - Implementation of the excellent Settlers of Catan board game.
2 * Go buy a copy.
3 *
4 * Copyright (C) 1999 Dave Cole
5 * Copyright (C) 2004,2011 Roland Clobus
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21
22 #include "config.h"
23 #include "frontend.h"
24 #include "histogram.h"
25 #include "theme.h"
26
27 static const int DIALOG_HEIGHT = 270;
28 static const int DIALOG_WIDTH = 450;
29 static const int GRID_DIVISIONS = 4;
30 static const int BAR_SEPARATION = 3;
31 static const int CHIT_DIAGRAM_SEPARATION = 3;
32 static const int SPACING_AROUND = 6;
33
34 static void histogram_update(gint roll);
35
36 static GtkWidget *histogram_dlg;
37 static GtkWidget *histogram_area;
38 static gint last_roll;
39
40 static int histogram[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
41
42 /*
43 *
44 * Non-Gui stuff -- maintain dice histogram state
45 *
46 */
47
histogram_dice_rolled(gint roll,G_GNUC_UNUSED gint playernum)48 void histogram_dice_rolled(gint roll, G_GNUC_UNUSED gint playernum)
49 {
50 g_assert(roll >= 2 && roll <= 12);
51
52 ++histogram[roll];
53 if (histogram_dlg)
54 histogram_update(roll);
55 }
56
histogram_dice_retrieve(gint roll)57 static gint histogram_dice_retrieve(gint roll)
58 {
59 g_assert(roll >= 2 && roll <= 12);
60 return histogram[roll];
61 }
62
63 /*
64 *
65 * GUI Stuff -- draw a pretty histogram picture
66 *
67 */
68
draw_histogram_cb(GtkWidget * widget,cairo_t * cr,G_GNUC_UNUSED gpointer user_data)69 static gboolean draw_histogram_cb(GtkWidget * widget, cairo_t * cr,
70 G_GNUC_UNUSED gpointer user_data)
71 {
72 gint w;
73 gint h;
74 gint total;
75 gint max;
76 gdouble le;
77 gdouble mi;
78 gdouble ri;
79 gint grid_width;
80 gint grid_height;
81 gint grid_offset_x;
82 gint grid_offset_y;
83 gdouble by_36;
84 gdouble expected_low_y, expected_high_y;
85 gint i;
86 gchar buff[30];
87 gint label_width, label_height; /* Maximum size of the labels of the y-axis */
88 gint width, height; /* size of the individual labels */
89 gdouble bar_width;
90 PangoLayout *layout;
91 gboolean seven_thrown;
92 gboolean draw_labels_and_chits;
93 gint CHIT_RADIUS;
94 GdkPixbuf *pixbuf;
95 GtkAllocation allocation;
96 MapTheme *theme;
97
98 cairo_set_line_width(cr, 1.0);
99
100 gtk_widget_get_allocation(widget, &allocation);
101 w = allocation.width;
102 h = allocation.height;
103
104 /* Calculate the highest dice throw */
105 max = 0;
106 for (i = 2; i <= 12; i++) {
107 if (histogram_dice_retrieve(i) > max) {
108 max = histogram_dice_retrieve(i);
109 }
110 /* Make max a multiple of GRID_DIVISIONS */
111 if (max % GRID_DIVISIONS != 0)
112 max += GRID_DIVISIONS - (max % GRID_DIVISIONS);
113 }
114 if (max == 0)
115 max = GRID_DIVISIONS;
116
117 /* Calculate size of the labels of the y-axis */
118 sprintf(buff, "%d", max);
119 layout = gtk_widget_create_pango_layout(widget, buff);
120 pango_layout_get_pixel_size(layout, &label_width, &label_height);
121
122 CHIT_RADIUS = guimap_get_chit_radius(layout, TRUE);
123
124 /* Determine if the drawing area is large enough to draw the labels */
125 draw_labels_and_chits = TRUE;
126 if (label_width + (CHIT_RADIUS + 1) * 2 * 11 > w)
127 draw_labels_and_chits = FALSE;
128 if (label_height * 5 + CHIT_RADIUS * 2 > h)
129 draw_labels_and_chits = FALSE;
130
131 grid_offset_x =
132 (draw_labels_and_chits ? label_width : 0) + SPACING_AROUND;
133 grid_width = w - grid_offset_x - SPACING_AROUND;
134 grid_offset_y = (draw_labels_and_chits ? label_height : 0);
135 grid_height =
136 h - grid_offset_y -
137 (draw_labels_and_chits ? 2 * CHIT_RADIUS : 0) -
138 CHIT_DIAGRAM_SEPARATION;
139
140 /* horizontal grid */
141 for (i = 0; i <= GRID_DIVISIONS; ++i) {
142 gdouble y =
143 grid_offset_y + grid_height - 0.5 -
144 (gdouble) i * grid_height / GRID_DIVISIONS;
145 gdk_cairo_set_source_rgba(cr, &lightblue);
146 cairo_move_to(cr, grid_offset_x, y);
147 cairo_line_to(cr, w - SPACING_AROUND, y);
148 cairo_stroke(cr);
149
150 if (draw_labels_and_chits) {
151 sprintf(buff, "%d", i * max / GRID_DIVISIONS);
152 pango_layout_set_text(layout, buff, -1);
153 pango_layout_get_pixel_size(layout, &width,
154 &height);
155 gdk_cairo_set_source_rgba(cr, &black);
156 cairo_move_to(cr,
157 label_width - width + SPACING_AROUND,
158 y - height / 2.0);
159 pango_cairo_show_layout(cr, layout);
160 }
161 }
162
163 bar_width = (grid_width - 12 * BAR_SEPARATION) / 11.0;
164 grid_offset_x += BAR_SEPARATION;
165
166 /* the filling of the bars */
167 theme = theme_get_current();
168 if (gdk_pixbuf_get_has_alpha
169 (theme_get_terrain_pixbuf(SEA_TERRAIN))) {
170 GdkPixbuf *p;
171 int w;
172 int h;
173
174 /* The image has transparency, so it is probably a hexagon.
175 Take the middle rectangle.
176 It doesn't tile the best, but it is better than tiling the hexagon.
177 */
178 w = gdk_pixbuf_get_width(theme->scaledata
179 [SEA_TERRAIN].native_image);
180 h = gdk_pixbuf_get_height(theme->scaledata
181 [SEA_TERRAIN].native_image);
182 p = gdk_pixbuf_new_subpixbuf(theme->scaledata
183 [SEA_TERRAIN].native_image, 0,
184 h / 4, w, h / 2);
185 pixbuf = gdk_pixbuf_copy(p);
186 g_object_unref(p);
187 } else {
188 pixbuf =
189 gdk_pixbuf_copy(theme->
190 scaledata[SEA_TERRAIN].native_image);
191 }
192
193 /* histogram bars */
194 for (i = 2; i <= 12; i++) {
195 gdouble bh =
196 (gdouble) grid_height * histogram_dice_retrieve(i) /
197 max + 0.5;
198 gdouble x =
199 grid_offset_x + (i - 2) * (bar_width + BAR_SEPARATION);
200 gdk_cairo_set_source_pixbuf(cr, pixbuf, grid_offset_x,
201 grid_offset_y);
202 cairo_pattern_set_extend(cairo_get_source(cr),
203 CAIRO_EXTEND_REPEAT);
204 cairo_rectangle(cr, x, grid_height + grid_offset_y - bh,
205 bar_width, bh);
206 cairo_fill(cr);
207
208 if (draw_labels_and_chits) {
209 sprintf(buff, "%d", histogram_dice_retrieve(i));
210 pango_layout_set_markup(layout, buff, -1);
211 pango_layout_get_pixel_size(layout, &width,
212 &height);
213 gdk_cairo_set_source_rgba(cr, &black);
214 cairo_move_to(cr, x + (bar_width - width) / 2,
215 grid_height + grid_offset_y - bh -
216 height);
217 pango_cairo_show_layout(cr, layout);
218
219 draw_dice_roll(layout, cr, x + bar_width / 2,
220 h - CHIT_RADIUS - 1,
221 CHIT_RADIUS,
222 i, SEA_TERRAIN, i == last_roll);
223 }
224 }
225 g_object_unref(pixbuf);
226
227 /* expected value */
228 seven_thrown = histogram_dice_retrieve(7) != 0;
229
230 total = 0;
231 for (i = 2; i <= 12; i++) {
232 total += histogram_dice_retrieve(i);
233 }
234
235 by_36 = total * grid_height / max / (seven_thrown ? 36.0 : 30.0);
236 expected_low_y = grid_height + grid_offset_y - 1 - by_36;
237 expected_high_y =
238 grid_height + grid_offset_y - 1 -
239 (seven_thrown ? 6 : 5) * by_36;
240
241 le = grid_offset_x + bar_width / 2;
242 mi = le + 5 * (bar_width + BAR_SEPARATION);
243 ri = mi + 5 * (bar_width + BAR_SEPARATION);
244
245 gdk_cairo_set_source_rgba(cr, &red);
246 cairo_move_to(cr, le, expected_low_y);
247 cairo_line_to(cr,
248 mi - (seven_thrown ? 0 : bar_width + BAR_SEPARATION),
249 expected_high_y);
250 cairo_move_to(cr,
251 mi + (seven_thrown ? 0 : bar_width + BAR_SEPARATION),
252 expected_high_y);
253 cairo_line_to(cr, ri, expected_low_y);
254 cairo_stroke(cr);
255
256 g_object_unref(layout);
257
258 return TRUE;
259 }
260
histogram_destroyed_cb(GtkWidget * widget,gpointer arg)261 static void histogram_destroyed_cb(GtkWidget * widget, gpointer arg)
262 {
263 gtk_widget_destroyed(histogram_area, &histogram_area);
264 gtk_widget_destroyed(widget, arg);
265 }
266
histogram_create_dlg(GtkWindow * parent_window)267 GtkWidget *histogram_create_dlg(GtkWindow * parent_window)
268 {
269 GtkWidget *dlg_vbox;
270
271 if (histogram_dlg != NULL) {
272 return histogram_dlg;
273 }
274
275 /* Dialog caption */
276 histogram_dlg = gtk_dialog_new_with_buttons(_("Dice Histogram"),
277 parent_window,
278 GTK_DIALOG_DESTROY_WITH_PARENT,
279 /* Button text */
280 _("_Close"),
281 GTK_RESPONSE_CLOSE,
282 NULL);
283 g_signal_connect(G_OBJECT(histogram_dlg), "destroy",
284 G_CALLBACK(histogram_destroyed_cb),
285 &histogram_dlg);
286 gtk_window_set_default_size(GTK_WINDOW(histogram_dlg),
287 DIALOG_WIDTH, DIALOG_HEIGHT);
288
289 dlg_vbox = gtk_dialog_get_content_area(GTK_DIALOG(histogram_dlg));
290
291 histogram_area = gtk_drawing_area_new();
292 g_signal_connect(G_OBJECT(histogram_area), "draw",
293 G_CALLBACK(draw_histogram_cb), NULL);
294 gtk_box_pack_start(GTK_BOX(dlg_vbox), histogram_area, TRUE, TRUE,
295 SPACING_AROUND);
296 gtk_widget_show(histogram_area);
297
298 gtk_widget_show(histogram_dlg);
299 g_signal_connect(histogram_dlg, "response",
300 G_CALLBACK(gtk_widget_destroy), NULL);
301
302 histogram_update(0);
303
304 return histogram_dlg;
305 }
306
histogram_update(gint roll)307 static void histogram_update(gint roll)
308 {
309 last_roll = roll;
310 gtk_widget_queue_draw(histogram_area);
311 }
312
histogram_theme_changed(void)313 static void histogram_theme_changed(void)
314 {
315 if (histogram_dlg)
316 gtk_widget_queue_draw(histogram_area);
317 }
318
histogram_init(void)319 void histogram_init(void)
320 {
321 theme_register_callback(G_CALLBACK(histogram_theme_changed));
322 }
323
histogram_reset(void)324 void histogram_reset(void)
325 {
326 gint i;
327 for (i = 2; i <= 12; ++i)
328 histogram[i] = 0;
329 if (histogram_dlg)
330 histogram_update(0);
331 }
332