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