1 /* -*- mode: c; c-basic-offset: 4; -*-
2 *
3 * explorer.c - An interactive GUI for manipulating a DeJong object and viewing its output
4 *
5 * Fyre - rendering and interactive exploration of chaotic functions
6 * Copyright (C) 2004-2006 David Trowbridge and Micah Dowty
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 *
22 */
23
24 #include <config.h>
25 #include <string.h>
26 #include <math.h>
27 #include <stdlib.h>
28 #include "explorer.h"
29 #include "parameter-editor.h"
30 #include "math-util.h"
31 #include "gui-util.h"
32 #include "histogram-view.h"
33 #include "de-jong.h"
34 #include "prefix.h"
35
36 static void explorer_class_init (ExplorerClass *klass);
37 static void explorer_init (Explorer *self);
38 static void explorer_dispose (GObject *gobject);
39
40 static gboolean explorer_auto_limit_update_rate (Explorer *self);
41 static gboolean limit_update_rate (GTimer* timer, float max_rate);
42 static gdouble explorer_get_iter_speed (Explorer *self);
43 static gchar* explorer_strdup_elapsed (Explorer *self);
44 static gchar* explorer_strdup_status (Explorer *self);
45 static gchar* explorer_strdup_speed (Explorer *self);
46 static gchar* explorer_strdup_quality (Explorer *self);
47 static void explorer_update_status_bar (Explorer *self);
48
49 static gdouble generate_random_param();
50
51 static void on_randomize (GtkWidget *widget, Explorer* self);
52 static void on_load_defaults (GtkWidget *widget, Explorer* self);
53 static void on_save (GtkWidget *widget, Explorer* self);
54 static void on_save_exr (GtkWidget *widget, Explorer* self);
55 static void on_quit (GtkWidget *widget, Explorer* self);
56 static void on_pause_rendering_toggle (GtkWidget *widget, Explorer* self);
57 static void on_load_from_image (GtkWidget *widget, Explorer* self);
58 static void on_widget_toggle (GtkWidget *widget, Explorer* self);
59 static void on_zoom_reset (GtkWidget *widget, Explorer* self);
60 static void on_zoom_in (GtkWidget *widget, Explorer* self);
61 static void on_zoom_out (GtkWidget *widget, Explorer* self);
62 static void on_render_time_changed (GtkWidget *widget, Explorer* self);
63 static void on_calculation_finished (IterativeMap *map, Explorer* self);
64 static gboolean on_interactive_prefs_delete (GtkWidget *widget, GdkEvent *event, Explorer* self);
65
66 static gchar *file_location = NULL;
67
68
69 /************************************************************************************/
70 /**************************************************** Initialization / Finalization */
71 /************************************************************************************/
72
explorer_get_type(void)73 GType explorer_get_type(void) {
74 static GType exp_type = 0;
75
76 if (!exp_type) {
77 static const GTypeInfo exp_info = {
78 sizeof(ExplorerClass),
79 NULL, /* base_init */
80 NULL, /* base_finalize */
81 (GClassInitFunc) explorer_class_init,
82 NULL, /* class_finalize */
83 NULL, /* class_data */
84 sizeof(Explorer),
85 0,
86 (GInstanceInitFunc) explorer_init,
87 };
88
89 exp_type = g_type_register_static(G_TYPE_OBJECT, "Explorer", &exp_info, 0);
90 }
91
92 return exp_type;
93 }
94
explorer_class_init(ExplorerClass * klass)95 static void explorer_class_init(ExplorerClass *klass) {
96 GObjectClass *object_class = (GObjectClass*) klass;
97
98 object_class->dispose = explorer_dispose;
99
100 glade_init();
101 }
102
explorer_init(Explorer * self)103 static void explorer_init(Explorer *self) {
104
105 if (g_file_test (FYRE_DATADIR "/explorer.glade", G_FILE_TEST_EXISTS))
106 self->xml = glade_xml_new (FYRE_DATADIR "/explorer.glade", NULL, NULL);
107 if (!self->xml)
108 self->xml = glade_xml_new(BR_DATADIR("/fyre/explorer.glade"), NULL, NULL);
109 if (!self->xml) {
110 GtkWidget *dialog;
111 dialog = gtk_message_dialog_new_with_markup(NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
112 "<b>Fyre can't find its data files.</b>\n\n"
113 "The main glade file could not be located.\n"
114 "We tried looking for it in the following places:\n"
115 "\n"
116 " %s\n"
117 " %s",
118 FYRE_DATADIR "/explorer.glade",
119 BR_DATADIR("/fyre/explorer.glade"));
120 gtk_dialog_run(GTK_DIALOG(dialog));
121 exit(0);
122 }
123
124 self->window = glade_xml_get_widget(self->xml, "explorer_window");
125 fyre_set_icon_later(self->window);
126 fyre_set_icon_later(glade_xml_get_widget(self->xml, "animation_window"));
127 fyre_set_icon_later(glade_xml_get_widget(self->xml, "interactive_prefs"));
128 fyre_set_icon_later(glade_xml_get_widget(self->xml, "cluster_window"));
129 fyre_set_icon_later(glade_xml_get_widget(self->xml, "about_window"));
130 fyre_set_icon_later(glade_xml_get_widget(self->xml, "error dialog"));
131
132 /* Connect signal handlers */
133 glade_xml_signal_connect_data(self->xml, "on_randomize", G_CALLBACK(on_randomize), self);
134 glade_xml_signal_connect_data(self->xml, "on_load_defaults", G_CALLBACK(on_load_defaults), self);
135 glade_xml_signal_connect_data(self->xml, "on_save", G_CALLBACK(on_save), self);
136 glade_xml_signal_connect_data(self->xml, "on_save_exr", G_CALLBACK(on_save_exr), self);
137 glade_xml_signal_connect_data(self->xml, "on_quit", G_CALLBACK(on_quit), self);
138 glade_xml_signal_connect_data(self->xml, "on_pause_rendering_toggle", G_CALLBACK(on_pause_rendering_toggle), self);
139 glade_xml_signal_connect_data(self->xml, "on_load_from_image", G_CALLBACK(on_load_from_image), self);
140 glade_xml_signal_connect_data(self->xml, "on_widget_toggle", G_CALLBACK(on_widget_toggle), self);
141 glade_xml_signal_connect_data(self->xml, "on_zoom_reset", G_CALLBACK(on_zoom_reset), self);
142 glade_xml_signal_connect_data(self->xml, "on_zoom_in", G_CALLBACK(on_zoom_in), self);
143 glade_xml_signal_connect_data(self->xml, "on_zoom_out", G_CALLBACK(on_zoom_out), self);
144 glade_xml_signal_connect_data(self->xml, "on_render_time_changed", G_CALLBACK(on_render_time_changed), self);
145 glade_xml_signal_connect_data(self->xml, "on_interactive_prefs_delete", G_CALLBACK(on_interactive_prefs_delete), self);
146
147 #ifndef HAVE_EXR
148 /* If we don't have OpenEXR support, gray out the menu item
149 * so it sits there taunting the user and not breaking HIG
150 */
151 gtk_widget_set_sensitive(glade_xml_get_widget(self->xml, "save_image_as_exr"), FALSE);
152 #endif
153
154 /* Set up the statusbar */
155 self->statusbar = GTK_STATUSBAR(glade_xml_get_widget(self->xml, "statusbar"));
156 self->render_status_context = gtk_statusbar_get_context_id(self->statusbar, "Rendering status");
157 self->speed_timer = g_timer_new();
158 self->auto_update_rate_timer = g_timer_new();
159 self->status_update_rate_timer = g_timer_new();
160 }
161
explorer_dispose(GObject * gobject)162 static void explorer_dispose(GObject *gobject) {
163 Explorer *self = EXPLORER(gobject);
164
165 explorer_dispose_animation(self);
166 explorer_dispose_cluster(self);
167 explorer_dispose_history(self);
168
169 if (self->speed_timer) {
170 g_timer_destroy(self->speed_timer);
171 self->speed_timer = NULL;
172 }
173
174 if (self->auto_update_rate_timer) {
175 g_timer_destroy(self->auto_update_rate_timer);
176 self->auto_update_rate_timer = NULL;
177 }
178 if (self->status_update_rate_timer) {
179 g_timer_destroy(self->status_update_rate_timer);
180 self->status_update_rate_timer = NULL;
181 }
182
183 if (self->map) {
184 g_object_unref(self->map);
185 self->map = NULL;
186 }
187 }
188
explorer_new(IterativeMap * map,Animation * animation)189 Explorer* explorer_new(IterativeMap *map, Animation *animation) {
190 Explorer *self = EXPLORER(g_object_new(explorer_get_type(), NULL));
191 GtkWidget *editor, *window, *scroll;
192 GtkRequisition win_req;
193
194 self->animation = ANIMATION(g_object_ref(animation));
195 self->map = ITERATIVE_MAP(g_object_ref(map));
196
197 /* Create the parameter editor */
198 editor = parameter_editor_new(PARAMETER_HOLDER(map));
199 gtk_box_pack_start(GTK_BOX(glade_xml_get_widget(self->xml, "parameter_editor_box")),
200 editor, FALSE, FALSE, 0);
201 gtk_widget_show_all(editor);
202
203 /* Create the view */
204 self->view = histogram_view_new(HISTOGRAM_IMAGER(map));
205 gtk_container_add(GTK_CONTAINER(glade_xml_get_widget(self->xml, "drawing_area_viewport")), self->view);
206 gtk_widget_show_all(self->view);
207
208 /* Set the initial render time */
209 on_render_time_changed(glade_xml_get_widget(self->xml, "render_time"), self);
210
211 explorer_init_history(self);
212 explorer_init_animation(self);
213 explorer_init_tools(self);
214 explorer_init_cluster(self);
215 explorer_init_about(self);
216
217 /* Start the iterative map rendering in the background, and get a callback every time a block
218 * of calculations finish so we can update the GUI.
219 */
220 iterative_map_start_calculation(self->map);
221 g_signal_connect(G_OBJECT(self->map), "calculation-finished",
222 G_CALLBACK(on_calculation_finished), self);
223
224 /* Set the window's default size to include our default image size.
225 * The cleanest way I know of to do this is to set the scrolled window's scrollbar policies
226 * to 'never' and get the window's size requests, set them back to automatic, then set the
227 * default size to that size request.
228 */
229 window = glade_xml_get_widget(self->xml, "explorer_window");
230 scroll = glade_xml_get_widget(self->xml, "main_scrolledwindow");
231 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
232 gtk_widget_size_request(window, &win_req);
233 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
234 gtk_window_set_default_size(GTK_WINDOW(window), win_req.width, win_req.height);
235 gtk_widget_show(window);
236
237 return self;
238 }
239
240
241 /************************************************************************************/
242 /*********************************************************************** Clustering */
243 /************************************************************************************/
244
245 #ifndef HAVE_GNET
246 /* Fake cluster functions, if gnet support is not available */
247
explorer_init_cluster(Explorer * self)248 void explorer_init_cluster (Explorer *self)
249 {
250 /* If we have no cluster support, disable that menu item */
251 gtk_widget_set_sensitive(glade_xml_get_widget(self->xml, "toggle_cluster_window"), FALSE);
252 }
253
explorer_dispose_cluster(Explorer * self)254 void explorer_dispose_cluster (Explorer *self) {}
255
256 #endif /* !HAVE_GNET */
257
258
259 /************************************************************************************/
260 /*********************************************************************** Parameters */
261 /************************************************************************************/
262
generate_random_param()263 static gdouble generate_random_param() {
264 return uniform_variate() * 12 - 6;
265 }
266
on_randomize(GtkWidget * widget,Explorer * self)267 static void on_randomize(GtkWidget *widget, Explorer* self) {
268 g_object_set(self->map,
269 "a", generate_random_param(),
270 "b", generate_random_param(),
271 "c", generate_random_param(),
272 "d", generate_random_param(),
273 NULL);
274 }
275
on_load_defaults(GtkWidget * widget,Explorer * self)276 static void on_load_defaults(GtkWidget *widget, Explorer* self) {
277 parameter_holder_reset_to_defaults(PARAMETER_HOLDER(self->map));
278 }
279
280
281 /************************************************************************************/
282 /******************************************************************** Misc GUI goop */
283 /************************************************************************************/
284
on_quit(GtkWidget * widget,Explorer * self)285 static void on_quit(GtkWidget *widget, Explorer* self)
286 {
287 gtk_main_quit();
288 }
289
on_widget_toggle(GtkWidget * widget,Explorer * self)290 static void on_widget_toggle(GtkWidget *widget, Explorer* self)
291 {
292 /* Toggle visibility of another widget. This widget should be named
293 * toggle_foo to control the visibility of a widget named foo.
294 */
295 const gchar *name;
296 GtkWidget *toggled;
297
298 name = gtk_widget_get_name(widget);
299 g_assert(!strncmp((void *) name, "toggle_", 7));
300 toggled = glade_xml_get_widget(self->xml, name+7);
301
302 if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
303 gtk_widget_show(toggled);
304 else
305 gtk_widget_hide(toggled);
306 }
307
on_zoom_reset(GtkWidget * widget,Explorer * self)308 static void on_zoom_reset(GtkWidget *widget, Explorer* self)
309 {
310 g_object_set(self->map, "zoom", 1.0, NULL);
311 }
312
on_zoom_in(GtkWidget * widget,Explorer * self)313 static void on_zoom_in(GtkWidget *widget, Explorer* self)
314 {
315 g_object_set(self->map,
316 "zoom", DE_JONG(self->map)->zoom + 2.0,
317 NULL);
318 }
319
on_zoom_out(GtkWidget * widget,Explorer * self)320 static void on_zoom_out(GtkWidget *widget, Explorer* self)
321 {
322 g_object_set(self->map,
323 "zoom", DE_JONG(self->map)->zoom - 2.0,
324 NULL);
325 }
326
327 #if (GTK_CHECK_VERSION(2, 4, 0))
328 static void
update_image_preview(GtkFileChooser * chooser,GtkImage * image)329 update_image_preview (GtkFileChooser *chooser, GtkImage *image) {
330 GdkPixbuf *image_pixbuf, *temp;
331 static GdkPixbuf *emblem_pixbuf = NULL;
332 gchar *filename;
333 GdkPixmap *pixmap;
334 gint width, height;
335
336 if (emblem_pixbuf == NULL) {
337 emblem_pixbuf = gdk_pixbuf_new_from_file (FYRE_DATADIR "/metadata-emblem.png", NULL);
338 if (!emblem_pixbuf)
339 emblem_pixbuf = gdk_pixbuf_new_from_file (BR_DATADIR ("/fyre/metadata-emblem.png"), NULL);
340 }
341
342 filename = gtk_file_chooser_get_filename (chooser);
343 if (filename == NULL) {
344 gtk_file_chooser_set_preview_widget_active (chooser, FALSE);
345 return;
346 }
347
348 image_pixbuf = gdk_pixbuf_new_from_file_at_size (filename, 112, 112, NULL);
349 if (image_pixbuf == NULL) {
350 gtk_file_chooser_set_preview_widget_active (chooser, FALSE);
351 return;
352 }
353 width = gdk_pixbuf_get_width (image_pixbuf);
354 height = gdk_pixbuf_get_height (image_pixbuf);
355
356 pixmap = gdk_pixmap_new (GTK_WIDGET (image)->window, width + 16, height + 16, -1);
357 gdk_draw_rectangle (pixmap, GTK_WIDGET (image)->style->bg_gc[GTK_STATE_NORMAL], TRUE, 0, 0, width + 16, height + 16);
358 gdk_draw_pixbuf (pixmap, NULL, image_pixbuf, 0, 0, 0, 0, width - 1, height - 1, GDK_RGB_DITHER_NONE, 0, 0);
359
360 temp = gdk_pixbuf_new_from_file (filename, NULL);
361 if (temp) {
362 if (gdk_pixbuf_get_option (temp, "tEXt::fyre_params"))
363 gdk_draw_pixbuf (pixmap, NULL, emblem_pixbuf, 0, 0, width - 16, height - 16, 31, 31, GDK_RGB_DITHER_NONE, 0, 0);
364 else if (gdk_pixbuf_get_option (temp, "tEXt::de_jong_params"))
365 gdk_draw_pixbuf (pixmap, NULL, emblem_pixbuf, 0, 0, width - 16, height - 16, 31, 31, GDK_RGB_DITHER_NONE, 0, 0);
366 gdk_pixbuf_unref (temp);
367 }
368
369 if (image_pixbuf)
370 gdk_pixbuf_unref (image_pixbuf);
371
372 gtk_image_set_from_pixmap (GTK_IMAGE (image), pixmap, NULL);
373 gdk_pixmap_unref (pixmap);
374 gtk_file_chooser_set_preview_widget_active (chooser, TRUE);
375 }
376 #endif
377
on_load_from_image(GtkWidget * widget,Explorer * self)378 static void on_load_from_image (GtkWidget *widget, Explorer* self) {
379 GtkWidget *dialog, *image;
380 GError *error = NULL;
381 gchar *filename = NULL;
382
383 #if (GTK_CHECK_VERSION(2, 4, 0))
384 dialog = gtk_file_chooser_dialog_new ("Open Image Parameters",
385 GTK_WINDOW (glade_xml_get_widget (self->xml, "explorer_window")),
386 GTK_FILE_CHOOSER_ACTION_OPEN,
387 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
388 GTK_STOCK_OPEN, GTK_RESPONSE_OK,
389 NULL);
390 gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
391 image = gtk_image_new ();
392 gtk_file_chooser_set_preview_widget (GTK_FILE_CHOOSER (dialog), image);
393 g_signal_connect (G_OBJECT (dialog), "update-preview", G_CALLBACK (update_image_preview), image);
394 if (file_location)
395 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), file_location);
396
397 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK) {
398 filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
399 histogram_imager_load_image_file (HISTOGRAM_IMAGER (self->map), filename, &error);
400
401 if (file_location)
402 g_free (file_location);
403 file_location = gtk_file_chooser_get_current_folder (GTK_FILE_CHOOSER (dialog));
404 }
405 #else
406 dialog = gtk_file_selection_new ("Open Image Parameters");
407
408 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK) {
409 filename = gtk_file_selection_get_filename (GTK_FILE_SELECTION (dialog));
410 histogram_imager_load_image_file (HISTOGRAM_IMAGER (self->map), filename, &error);
411 }
412 #endif
413 gtk_widget_destroy (dialog);
414
415 if (error) {
416 GtkWidget *dialog, *label;
417 gchar *text;
418
419 dialog = glade_xml_get_widget (self->xml, "error dialog");
420 label = glade_xml_get_widget (self->xml, "error label");
421
422 text = g_strdup_printf ("<span weight=\"bold\" size=\"larger\">Could not load \"%s\"</span>\n\n%s", filename, error->message);
423 gtk_label_set_markup (GTK_LABEL (label), text);
424 g_free (text);
425 g_error_free (error);
426
427 gtk_dialog_run (GTK_DIALOG (dialog));
428 gtk_widget_hide (dialog);
429 }
430 g_free (filename);
431 }
432
on_save(GtkWidget * widget,Explorer * self)433 static void on_save (GtkWidget *widget, Explorer* self) {
434 GtkWidget *dialog;
435 GError *error = NULL;
436 gchar *filename = NULL;
437
438 #if (GTK_CHECK_VERSION(2, 4, 0))
439 dialog = gtk_file_chooser_dialog_new ("Save Image",
440 GTK_WINDOW (glade_xml_get_widget (self->xml, "explorer_window")),
441 GTK_FILE_CHOOSER_ACTION_SAVE,
442 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
443 GTK_STOCK_SAVE, GTK_RESPONSE_OK,
444 NULL);
445 gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
446 if (file_location != NULL)
447 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), file_location);
448 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK) {
449 filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
450 histogram_imager_save_image_file (HISTOGRAM_IMAGER (self->map), filename, &error);
451
452 if (file_location)
453 g_free (file_location);
454 file_location = gtk_file_chooser_get_current_folder (GTK_FILE_CHOOSER (dialog));
455 }
456 #else
457 dialog = gtk_file_selection_new ("Save Image");
458 gtk_file_selection_set_filename (GTK_FILE_SELECTION (dialog), "rendering.png");
459
460 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK) {
461 filename = g_strdup (gtk_file_selection_get_filename (GTK_FILE_SELECTION (dialog)));
462 histogram_imager_save_image_file (HISTOGRAM_IMAGER (self->map), filename, &error);
463 }
464 #endif
465 gtk_widget_destroy (dialog);
466
467 if (error) {
468 GtkWidget *dialog, *label;
469 gchar *text;
470
471 dialog = glade_xml_get_widget (self->xml, "error dialog");
472 label = glade_xml_get_widget (self->xml, "error label");
473
474 text = g_strdup_printf ("<span weight=\"bold\" size=\"larger\">Could not save \"%s\"</span>\n\n%s", filename, error->message);
475 gtk_label_set_markup (GTK_LABEL (label), text);
476 g_free (text);
477 g_error_free (error);
478
479 gtk_dialog_run (GTK_DIALOG (dialog));
480 gtk_widget_hide (dialog);
481 }
482
483 if (filename)
484 g_free (filename);
485 }
486
on_save_exr(GtkWidget * widget,Explorer * self)487 static void on_save_exr (GtkWidget *widget, Explorer* self) {
488 #ifdef HAVE_EXR
489 GtkWidget *dialog;
490 GError *error = NULL;
491 gchar *filename = NULL;
492
493 #if (GTK_CHECK_VERSION(2, 4, 0))
494 dialog = gtk_file_chooser_dialog_new ("Save OpenEXR Image",
495 GTK_WINDOW (glade_xml_get_widget (self->xml, "explorer_window")),
496 GTK_FILE_CHOOSER_ACTION_SAVE,
497 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
498 GTK_STOCK_SAVE, GTK_RESPONSE_OK,
499 NULL);
500 gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
501 if (file_location != NULL)
502 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), file_location);
503
504 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK) {
505 filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
506 exr_save_image_file (HISTOGRAM_IMAGER (self->map), filename, &error);
507
508 if (file_location)
509 g_free (file_location);
510 file_location = gtk_file_chooser_get_current_folder (GTK_FILE_CHOOSER (dialog));
511 }
512 #else
513 dialog = gtk_file_selection_new ("Save OpenEXR Image");
514 gtk_file_selection_set_filename (GTK_FILE_SELECTION (dialog), "rendering.exr");
515
516 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK) {
517 const gchar *filename;
518 filename = g_strdup (gtk_file_selection_get_filename (GTK_FILE_SELECTION (dialog)));
519 exr_save_image_file (HISTOGRAM_IMAGER (self->map), filename, &error);
520 }
521 #endif /* GTK_CHECK_VERSION */
522 gtk_widget_destroy (dialog);
523
524 if (error) {
525 GtkWidget *dialog, *label;
526 gchar *text;
527
528 dialog = glade_xml_get_widget (self->xml, "error dialog");
529 label = glade_xml_get_widget (self->xml, "error label");
530
531 text = g_strdup_printf ("<span weight=\"bold\" size=\"larger\">Could not save \"%s\"</span>\n\n%s", filename, error->message);
532 gtk_label_set_markup (GTK_LABEL (label), text);
533 g_free (text);
534 g_error_free (error);
535
536 gtk_dialog_run (GTK_DIALOG (dialog));
537 gtk_widget_hide (dialog);
538 }
539
540 if (filename)
541 g_free (filename);
542 #endif /* HAVE_EXR */
543 }
544
on_render_time_changed(GtkWidget * widget,Explorer * self)545 static void on_render_time_changed(GtkWidget *widget, Explorer* self) {
546 double v = gtk_range_get_adjustment(GTK_RANGE(widget))->value;
547 self->map->render_time = v / 1000.0; /* Milliseconds to seconds */
548 }
549
on_interactive_prefs_delete(GtkWidget * widget,GdkEvent * event,Explorer * self)550 static gboolean on_interactive_prefs_delete(GtkWidget *widget, GdkEvent *event, Explorer* self) {
551 /* Just hide the window when the user tries to close it */
552 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(glade_xml_get_widget(self->xml, "toggle_interactive_prefs")), FALSE);
553 return TRUE;
554 }
555
556
557 /************************************************************************************/
558 /************************************************************************ Rendering */
559 /************************************************************************************/
560
on_pause_rendering_toggle(GtkWidget * widget,Explorer * self)561 static void on_pause_rendering_toggle(GtkWidget *widget, Explorer* self) {
562 if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
563 iterative_map_stop_calculation(self->map);
564 else
565 iterative_map_start_calculation(self->map);
566
567 /* Since the user is changing the pause state, let's refrain from
568 * messing with it on explorer_restore_pause().
569 */
570 self->unpause_on_restore = FALSE;
571
572 /* Update the speed shown in the status bar */
573 self->status_dirty_flag = TRUE;
574 explorer_update_gui(self);
575 }
576
explorer_force_pause(Explorer * self)577 void explorer_force_pause (Explorer *self)
578 {
579 /* Force rendering to pause now, but keep note of its original state
580 * so that explorer_restore_pause() can undo this as necessary.
581 */
582 GtkCheckMenuItem *paused = GTK_CHECK_MENU_ITEM(glade_xml_get_widget(self->xml, "pause_menu"));
583 gboolean original_state = gtk_check_menu_item_get_active(paused);
584 gtk_check_menu_item_set_active(paused, TRUE);
585
586 /* Now, on_pause_rendering_toggle just disabled unpause_on_restore since
587 * typically only a user changes the pause_menu state. We want to turn
588 * that back on if we just paused it and originally it was unpaused,
589 * so that explorer_restore_pause() does the Right Thing (tm).
590 */
591 self->unpause_on_restore = !original_state;
592 }
593
explorer_restore_pause(Explorer * self)594 void explorer_restore_pause (Explorer *self)
595 {
596 if (self->unpause_on_restore) {
597 GtkCheckMenuItem *paused = GTK_CHECK_MENU_ITEM(glade_xml_get_widget(self->xml, "pause_menu"));
598
599 self->unpause_on_restore = FALSE;
600 gtk_check_menu_item_set_active(paused, FALSE);
601 }
602 }
603
on_calculation_finished(IterativeMap * map,Explorer * self)604 static void on_calculation_finished(IterativeMap *map, Explorer* self)
605 {
606 explorer_update_gui(self);
607 explorer_update_animation(self);
608 explorer_update_tools(self);
609 }
610
explorer_run_iterations(Explorer * self)611 void explorer_run_iterations(Explorer *self) {
612 iterative_map_calculate_timed(self->map, self->map->render_time);
613 }
614
limit_update_rate(GTimer * timer,float max_rate)615 static gboolean limit_update_rate(GTimer *timer, float max_rate) {
616 /* Limit the frame rate to the given value. This should be called once per
617 * frame, and will return FALSE if it's alright to render another frame,
618 * or TRUE otherwise.
619 */
620
621 if (g_timer_elapsed(timer, NULL) < (1.0 / max_rate))
622 return TRUE;
623
624 g_timer_start(timer);
625 return FALSE;
626 }
627
explorer_auto_limit_update_rate(Explorer * self)628 static gboolean explorer_auto_limit_update_rate(Explorer *self) {
629 /* Automatically determine a good maximum frame rate based on the current
630 * elapsed time, and use limit_update_rate() to limit us to that.
631 * Returns 1 if a frame should not be rendered.
632 *
633 * 'gamma' determines the nonlinearity. At gamma=1 we ramp down the period
634 * linearly. (not the rate) The other parameters determine the speed and the
635 * maximum/minimum rates. Voodoo below!
636 */
637
638 const double initial_rate = 60;
639 const double final_rate = 0.1;
640 const double ramp_down_seconds = 120;
641 const double gamma = 0.9;
642
643 /* Convert the user-friendly constants above into constants in nonlinear period space */
644 double rate, elapsed;
645 static gboolean init = FALSE;
646 static double pow_initial_period, pow_period_scale, one_over_gamma;
647 if (!init) {
648 pow_initial_period = pow(1.0 / initial_rate, gamma);
649 pow_period_scale = (pow(1.0 / final_rate, gamma) - pow_initial_period) / ramp_down_seconds;
650 one_over_gamma = 1.0 / gamma;
651 init = TRUE;
652 }
653
654 /* Now it's just a simple linear function followed by a nonlinear
655 * transformation back to rate space.
656 */
657 elapsed = histogram_imager_get_elapsed_time(HISTOGRAM_IMAGER(self->map));
658 if (elapsed > ramp_down_seconds)
659 rate = final_rate;
660 else
661 rate = 1.0 / pow(pow_initial_period + pow_period_scale * elapsed, one_over_gamma);
662
663 return limit_update_rate(self->auto_update_rate_timer, rate);
664 }
665
explorer_update_gui(Explorer * self)666 void explorer_update_gui(Explorer *self) {
667 /* If the GUI needs updating, update it. This includes limiting the maximum
668 * update rate, updating the iteration count display, and actually rendering
669 * frames to the drawing area.
670 */
671
672 /* If we have rendering changes we're trying to push through as quickly
673 * as possible, don't bother with the status bar or with frame rate limiting.
674 */
675 if (HISTOGRAM_IMAGER(self->map)->render_dirty_flag) {
676 histogram_view_update(HISTOGRAM_VIEW(self->view));
677 return;
678 }
679
680 /* If we have an important status change to report, update both
681 * the status bar and the view without frame rate limiting.
682 */
683 if (self->status_dirty_flag) {
684 explorer_update_status_bar(self);
685 histogram_view_update(HISTOGRAM_VIEW(self->view));
686 return;
687 }
688
689 /* Update the status bar at a fixed rate. This will give the user
690 * the impression that things are moving along steadily even when
691 * we're actually updating the view very slowly later in the render.
692 */
693 if (!limit_update_rate(self->status_update_rate_timer, 2.0 )) {
694 explorer_update_status_bar(self);
695 }
696
697 /* Use our funky automatic frame rate adjuster to time normal view updates.
698 * This will slow down updates nonlinearly as rendering progresses,
699 * to give good interactive response while making batch rendering
700 * still fairly efficient.
701 */
702 if (!explorer_auto_limit_update_rate(self)) {
703 histogram_view_update(HISTOGRAM_VIEW(self->view));
704 }
705 }
706
explorer_update_status_bar(Explorer * self)707 static void explorer_update_status_bar(Explorer *self)
708 {
709 gchar *status = explorer_strdup_status(self);
710
711 if (self->render_status_message_id)
712 gtk_statusbar_remove(self->statusbar, self->render_status_context, self->render_status_message_id);
713
714 self->render_status_message_id = gtk_statusbar_push(self->statusbar, self->render_status_context, status);
715 g_free(status);
716
717 self->status_dirty_flag = FALSE;
718 }
719
explorer_strdup_status(Explorer * self)720 static gchar* explorer_strdup_status (Explorer *self)
721 {
722 gchar *status;
723 gchar *elapsed = explorer_strdup_elapsed(self);
724 gchar *speed = explorer_strdup_speed(self);
725 gchar *quality = explorer_strdup_quality(self);
726
727 status = g_strdup_printf("Elapsed time: %s\t\t"
728 "Iterations: %.3e\t\t"
729 "Speed: %s\t\t"
730 "Quality: %s\t\t"
731 "Current tool: %s",
732 elapsed,
733 self->map->iterations,
734 speed,
735 quality,
736 self->current_tool);
737
738 g_free(elapsed);
739 g_free(speed);
740 g_free(quality);
741 return status;
742 }
743
explorer_strdup_elapsed(Explorer * self)744 static gchar* explorer_strdup_elapsed (Explorer *self)
745 {
746 gulong elapsed = (gulong) histogram_imager_get_elapsed_time(HISTOGRAM_IMAGER(self->map));
747 return g_strdup_printf("%02ld:%02ld:%02ld",
748 elapsed / (60*60),
749 (elapsed / 60) % 60,
750 elapsed % 60);
751 }
752
explorer_strdup_speed(Explorer * self)753 static gchar* explorer_strdup_speed (Explorer *self)
754 {
755 if (iterative_map_is_calculation_running(self->map))
756 return g_strdup_printf("%.3e/sec", explorer_get_iter_speed(self));
757 else
758 return g_strdup("Paused");
759 }
760
explorer_strdup_quality(Explorer * self)761 static gchar* explorer_strdup_quality (Explorer *self)
762 {
763 gdouble q = histogram_imager_compute_quality(HISTOGRAM_IMAGER(self->map));
764 if (q > (G_MAXDOUBLE / 2))
765 return g_strdup("N/A");
766 else
767 return g_strdup_printf("%.3f", q);
768 }
769
explorer_get_iter_speed(Explorer * self)770 static gdouble explorer_get_iter_speed(Explorer *self)
771 {
772 double elapsed = g_timer_elapsed(self->speed_timer, NULL);
773 double iter_diff = self->map->iterations - self->last_iterations;
774
775 if (iter_diff < 0) {
776 /* Calculation restarted */
777 g_timer_start(self->speed_timer);
778 self->last_iterations = self->map->iterations;
779 }
780 else if (iter_diff > 0 && elapsed > 1.0) {
781 g_timer_start(self->speed_timer);
782 self->last_iterations = self->map->iterations;
783 self->iter_speed = iter_diff / elapsed;
784 }
785 return self->iter_speed;
786 }
787
788 /* The End */
789