1 /* -*- mode: c; c-basic-offset: 4; -*-
2  *
3  * explorer-tools.c - Implementation for the GUI 'tools' that allow
4  *                    direct interaction with the mouse.
5  *
6  * Fyre - rendering and interactive exploration of chaotic functions
7  * Copyright (C) 2004-2006 David Trowbridge and Micah Dowty
8  *
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU General Public License
11  * as published by the Free Software Foundation; either version 2
12  * of the License, or (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
22  *
23  */
24 
25 #include "explorer.h"
26 #include "de-jong.h"
27 #include <stdlib.h>
28 #include <string.h>
29 #include <math.h>
30 
31 typedef void (ToolHandler)(Explorer *self, ToolInput *i);
32 
33 typedef struct _ToolInfo {
34     gchar *menu_name;
35     ToolHandler *handler;
36     ToolFlags flags;
37 } ToolInfo;
38 
39 static const ToolInfo* explorer_get_current_tool(Explorer *self);
40 static void explorer_fill_toolinput_relative_positions(Explorer *self, ToolInput *ti);
41 
42 static gboolean on_motion_notify(GtkWidget *widget, GdkEvent *event, gpointer user_data);
43 static gboolean on_button_press(GtkWidget *widget, GdkEvent *event, gpointer user_data);
44 static gboolean on_button_release(GtkWidget *widget, GdkEvent *event, gpointer user_data);
45 static void on_tool_activate(GtkWidget *widget, gpointer user_data);
46 
47 static void tool_grab(Explorer *self, ToolInput *i);
48 static void tool_blur(Explorer *self, ToolInput *i);
49 static void tool_zoom(Explorer *self, ToolInput *i);
50 static void tool_rotate(Explorer *self, ToolInput *i);
51 static void tool_exposure_gamma(Explorer *self, ToolInput *i);
52 static void tool_a_b(Explorer *self, ToolInput *i);
53 static void tool_a_c(Explorer *self, ToolInput *i);
54 static void tool_a_d(Explorer *self, ToolInput *i);
55 static void tool_b_c(Explorer *self, ToolInput *i);
56 static void tool_b_d(Explorer *self, ToolInput *i);
57 static void tool_c_d(Explorer *self, ToolInput *i);
58 static void tool_ab_cd(Explorer *self, ToolInput *i);
59 static void tool_ac_bd(Explorer *self, ToolInput *i);
60 static void tool_initial_offset(Explorer *self, ToolInput *i);
61 static void tool_initial_scale(Explorer *self, ToolInput *i);
62 
63 
64 /* A table of tool handlers and menu item names */
65 static const ToolInfo tool_table[] = {
66 
67     {"tool_grab",           tool_grab,           TOOL_USE_MOTION_EVENTS},
68     {"tool_blur",           tool_blur,           TOOL_USE_MOTION_EVENTS},
69     {"tool_zoom",           tool_zoom,           TOOL_USE_IDLE},
70     {"tool_rotate",         tool_rotate,         TOOL_USE_MOTION_EVENTS},
71     {"tool_exposure_gamma", tool_exposure_gamma, TOOL_USE_MOTION_EVENTS},
72     {"tool_a_b",            tool_a_b,            TOOL_USE_MOTION_EVENTS},
73     {"tool_a_c",            tool_a_c,            TOOL_USE_MOTION_EVENTS},
74     {"tool_a_d",            tool_a_d,            TOOL_USE_MOTION_EVENTS},
75     {"tool_b_c",            tool_b_c,            TOOL_USE_MOTION_EVENTS},
76     {"tool_b_d",            tool_b_d,            TOOL_USE_MOTION_EVENTS},
77     {"tool_c_d",            tool_c_d,            TOOL_USE_MOTION_EVENTS},
78     {"tool_ab_cd",          tool_ab_cd,          TOOL_USE_MOTION_EVENTS},
79     {"tool_ac_bd",          tool_ac_bd,          TOOL_USE_MOTION_EVENTS},
80     {"tool_initial_offset", tool_initial_offset, TOOL_USE_MOTION_EVENTS},
81     {"tool_initial_scale",  tool_initial_scale,  TOOL_USE_MOTION_EVENTS},
82 
83     {NULL,},
84 };
85 
86 
87 /************************************************************************************/
88 /**************************************************** Initialization / Finalization */
89 /************************************************************************************/
90 
explorer_init_tools(Explorer * self)91 void explorer_init_tools(Explorer *self) {
92     gtk_widget_add_events(self->view,
93 			  GDK_BUTTON_PRESS_MASK |
94 			  GDK_BUTTON_RELEASE_MASK |
95 			  GDK_BUTTON_MOTION_MASK |
96 			  GDK_POINTER_MOTION_HINT_MASK);
97 
98     glade_xml_signal_connect_data(self->xml, "on_tool_activate",  G_CALLBACK(on_tool_activate),  self);
99 
100     g_signal_connect(self->view, "motion_notify_event",  G_CALLBACK(on_motion_notify),  self);
101     g_signal_connect(self->view, "button_press_event",   G_CALLBACK(on_button_press),   self);
102     g_signal_connect(self->view, "button_release_event", G_CALLBACK(on_button_release), self);
103 
104     self->current_tool = "None";
105 }
106 
107 
108 /************************************************************************************/
109 /****************************************************************** Tool Invocation */
110 /************************************************************************************/
111 
explorer_get_current_tool(Explorer * self)112 static const ToolInfo* explorer_get_current_tool(Explorer *self) {
113     /* Return the current tool, or NULL if no tool is active
114      */
115     const ToolInfo *current = tool_table;
116 
117     for (current=tool_table; current->menu_name; current++) {
118 	GtkWidget *w = glade_xml_get_widget(self->xml, current->menu_name);
119 	if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w)))
120 	    return current;
121     }
122     return NULL;
123 }
124 
explorer_fill_toolinput_relative_positions(Explorer * self,ToolInput * ti)125 static void explorer_fill_toolinput_relative_positions(Explorer *self, ToolInput *ti) {
126     /* Fill in the delta and click-relative positions in a given toolinfo
127      */
128 
129     /* Compute delta position */
130     ti->delta_x = ti->absolute_x - self->last_mouse_x;
131     ti->delta_y = ti->absolute_y - self->last_mouse_y;
132 
133     /* Compute click-relative position */
134     ti->click_relative_x = ti->absolute_x - self->last_click_x;
135     ti->click_relative_y = ti->absolute_y - self->last_click_y;
136 }
137 
on_motion_notify(GtkWidget * widget,GdkEvent * event,gpointer user_data)138 static gboolean on_motion_notify(GtkWidget *widget, GdkEvent *event, gpointer user_data) {
139     Explorer *self = EXPLORER(user_data);
140     const ToolInfo *tool = explorer_get_current_tool(self);
141     ToolInput ti;
142     memset(&ti, 0, sizeof(ti));
143 
144     /* Fill in the absolute position and state for the ToolInput */
145     if (event->motion.is_hint) {
146 	gint ix, iy;
147 	gdk_window_get_pointer(event->motion.window, &ix, &iy, &ti.state);
148 	ti.absolute_x = ix;
149 	ti.absolute_y = iy;
150     }
151     else {
152 	ti.absolute_x = event->motion.x;
153 	ti.absolute_y = event->motion.y;
154 	ti.state = event->motion.state;
155     }
156     explorer_fill_toolinput_relative_positions(self, &ti);
157     self->last_mouse_x = ti.absolute_x;
158     self->last_mouse_y = ti.absolute_y;
159 
160     if (tool && (tool->flags & TOOL_USE_MOTION_EVENTS)) {
161 	tool->handler(self, &ti);
162 
163 	/* Always push through one frame of updates manually
164 	 * before going on. This serves multiple purposes-
165 	 * if we're paused, we need this to get any response
166 	 * at all. This also forces an update to happen even
167 	 * if the idle handler won't be run for a while, making
168 	 * the GUI more responsive. This is especially important
169 	 * under Windows, where the idle handler seems to have
170 	 * a lower priority.
171 	 */
172 	explorer_run_iterations(self);
173     }
174 
175     return FALSE;
176 }
177 
on_tool_activate(GtkWidget * widget,gpointer user_data)178 static void on_tool_activate(GtkWidget *widget, gpointer user_data) {
179     Explorer *self = EXPLORER(user_data);
180 
181     if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
182 	gtk_label_get(GTK_LABEL(gtk_bin_get_child(GTK_BIN(widget))), &self->current_tool);
183 
184     self->status_dirty_flag = TRUE;
185 }
186 
on_button_press(GtkWidget * widget,GdkEvent * event,gpointer user_data)187 static gboolean on_button_press(GtkWidget *widget, GdkEvent *event, gpointer user_data) {
188     Explorer *self = EXPLORER(user_data);
189 
190     self->tool_active = TRUE;
191     self->last_mouse_x = event->button.x;
192     self->last_mouse_y = event->button.y;
193     self->last_click_x = event->button.x;
194     self->last_click_y = event->button.y;
195     return FALSE;
196 }
197 
on_button_release(GtkWidget * widget,GdkEvent * event,gpointer user_data)198 static gboolean on_button_release(GtkWidget *widget, GdkEvent *event, gpointer user_data) {
199     Explorer *self = EXPLORER(user_data);
200     self->tool_active = FALSE;
201     return FALSE;
202 }
203 
explorer_update_tools(Explorer * self)204 gboolean explorer_update_tools(Explorer *self) {
205     /* If we're using a tool that needs to be updated when idle, do it.
206      * Returns a boolean indicating whether a tool is active or not.
207      */
208     const ToolInfo *tool = explorer_get_current_tool(self);
209     ToolInput ti;
210     gint ix, iy;
211     GTimeVal now;
212     memset(&ti, 0, sizeof(ti));
213 
214     gdk_window_get_pointer(self->view->window, &ix, &iy, &ti.state);
215     ti.absolute_x = ix;
216     ti.absolute_y = iy;
217     explorer_fill_toolinput_relative_positions(self, &ti);
218 
219     /* Compute the delta time */
220     g_get_current_time(&now);
221     ti.delta_time = ((now.tv_usec - self->last_tool_idle_update.tv_usec) / 1000000.0 +
222 		     (now.tv_sec  - self->last_tool_idle_update.tv_sec ));
223     self->last_tool_idle_update = now;
224 
225     if (tool && self->tool_active && (tool->flags & TOOL_USE_IDLE)) {
226 	tool->handler(self, &ti);
227 	return TRUE;
228     }
229     else {
230 	return FALSE;
231     }
232 }
233 
234 
235 /************************************************************************************/
236 /******************************************************************** Tool handlers */
237 /************************************************************************************/
238 
tool_grab(Explorer * self,ToolInput * i)239 static void tool_grab(Explorer *self, ToolInput *i) {
240     double scale = 5.0 / DE_JONG(self->map)->zoom / HISTOGRAM_IMAGER(self->map)->width;
241     g_object_set(self->map,
242 		 "xoffset", DE_JONG(self->map)->xoffset + i->delta_x * scale,
243 		 "yoffset", DE_JONG(self->map)->yoffset + i->delta_y * scale,
244 		 NULL);
245 }
246 
tool_blur(Explorer * self,ToolInput * i)247 static void tool_blur(Explorer *self, ToolInput *i) {
248     g_object_set(self->map,
249 		 "blur_ratio",  DE_JONG(self->map)->blur_ratio  + i->delta_x * 0.002,
250 		 "blur_radius", DE_JONG(self->map)->blur_radius - i->delta_y * 0.001,
251 		 NULL);
252 }
253 
tool_zoom(Explorer * self,ToolInput * i)254 static void tool_zoom(Explorer *self, ToolInput *i) {
255     double p, scaled_p;
256     const double exponent = 1.4;
257 
258     /* Scale the zooming speed nonlinearly with the distance from click location */
259     p = i->click_relative_y * 0.01;
260     if (p < 0) {
261 	scaled_p = -pow(-p, exponent);
262     }
263     else {
264 	scaled_p = pow(p, exponent);
265     }
266 
267     g_object_set(self->map,
268 		 "zoom", DE_JONG(self->map)->zoom - scaled_p * i->delta_time,
269 		 NULL);
270 }
271 
tool_rotate(Explorer * self,ToolInput * i)272 static void tool_rotate(Explorer *self, ToolInput *i) {
273     /* We're a bit tricky here and also rotate the X and Y offset such that we're
274      * rotating around the center of the view, rather than rotating around the
275      * center of rendering coordinates.
276      */
277     double delta_r = -i->delta_x * 0.0089;
278     double sin_d_r = sin(delta_r);
279     double cos_d_r = cos(delta_r);
280     g_object_set(self->map,
281 		 "rotation", (gdouble) (DE_JONG(self->map)->rotation + delta_r),
282 		 "xoffset",  (gdouble) ( cos_d_r * DE_JONG(self->map)->xoffset + sin_d_r * DE_JONG(self->map)->yoffset),
283 		 "yoffset",  (gdouble) (-sin_d_r * DE_JONG(self->map)->xoffset + cos_d_r * DE_JONG(self->map)->yoffset),
284 		 NULL);
285 }
286 
tool_exposure_gamma(Explorer * self,ToolInput * i)287 static void tool_exposure_gamma(Explorer *self, ToolInput *i) {
288     g_object_set(self->map,
289 		 "exposure", HISTOGRAM_IMAGER(self->map)->exposure - i->delta_y * 0.001,
290 		 "gamma",    HISTOGRAM_IMAGER(self->map)->gamma    + i->delta_x * 0.001,
291 		 NULL);
292 }
293 
tool_a_b(Explorer * self,ToolInput * i)294 static void tool_a_b(Explorer *self, ToolInput *i) {
295     g_object_set(self->map,
296 		 "a", DE_JONG(self->map)->param.a + i->delta_x * 0.001,
297 		 "b", DE_JONG(self->map)->param.b + i->delta_y * 0.001,
298 		 NULL);
299 }
300 
tool_a_c(Explorer * self,ToolInput * i)301 static void tool_a_c(Explorer *self, ToolInput *i) {
302     g_object_set(self->map,
303 		 "a", DE_JONG(self->map)->param.a + i->delta_x * 0.001,
304 		 "c", DE_JONG(self->map)->param.c + i->delta_y * 0.001,
305 		 NULL);
306 }
307 
tool_a_d(Explorer * self,ToolInput * i)308 static void tool_a_d(Explorer *self, ToolInput *i) {
309     g_object_set(self->map,
310 		 "a", DE_JONG(self->map)->param.a + i->delta_x * 0.001,
311 		 "d", DE_JONG(self->map)->param.d + i->delta_y * 0.001,
312 		 NULL);
313 }
314 
tool_b_c(Explorer * self,ToolInput * i)315 static void tool_b_c(Explorer *self, ToolInput *i) {
316     g_object_set(self->map,
317 		 "b", DE_JONG(self->map)->param.b + i->delta_x * 0.001,
318 		 "c", DE_JONG(self->map)->param.c + i->delta_y * 0.001,
319 		 NULL);
320 }
321 
tool_b_d(Explorer * self,ToolInput * i)322 static void tool_b_d(Explorer *self, ToolInput *i) {
323     g_object_set(self->map,
324 		 "b", DE_JONG(self->map)->param.b + i->delta_x * 0.001,
325 		 "d", DE_JONG(self->map)->param.d + i->delta_y * 0.001,
326 		 NULL);
327 }
328 
tool_c_d(Explorer * self,ToolInput * i)329 static void tool_c_d(Explorer *self, ToolInput *i) {
330     g_object_set(self->map,
331 		 "c", DE_JONG(self->map)->param.c + i->delta_x * 0.001,
332 		 "d", DE_JONG(self->map)->param.d + i->delta_y * 0.001,
333 		 NULL);
334 }
335 
tool_ab_cd(Explorer * self,ToolInput * i)336 static void tool_ab_cd(Explorer *self, ToolInput *i) {
337     g_object_set(self->map,
338 		 "a", DE_JONG(self->map)->param.a + i->delta_x * 0.001,
339 		 "b", DE_JONG(self->map)->param.b + i->delta_x * 0.001,
340 		 "c", DE_JONG(self->map)->param.c + i->delta_y * 0.001,
341 		 "d", DE_JONG(self->map)->param.d + i->delta_y * 0.001,
342 		 NULL);
343 }
344 
tool_ac_bd(Explorer * self,ToolInput * i)345 static void tool_ac_bd(Explorer *self, ToolInput *i) {
346     g_object_set(self->map,
347 		 "a", DE_JONG(self->map)->param.a + i->delta_x * 0.001,
348 		 "b", DE_JONG(self->map)->param.b + i->delta_y * 0.001,
349 		 "c", DE_JONG(self->map)->param.c + i->delta_x * 0.001,
350 		 "d", DE_JONG(self->map)->param.d + i->delta_y * 0.001,
351 		 NULL);
352 }
353 
tool_initial_offset(Explorer * self,ToolInput * i)354 static void tool_initial_offset(Explorer *self, ToolInput *i) {
355     g_object_set(self->map,
356 		 "initial_xoffset", DE_JONG(self->map)->initial_xoffset + i->delta_x * 0.001,
357 		 "initial_yoffset", DE_JONG(self->map)->initial_yoffset + i->delta_y * 0.001,
358 		 NULL);
359 }
360 
tool_initial_scale(Explorer * self,ToolInput * i)361 static void tool_initial_scale(Explorer *self, ToolInput *i) {
362     g_object_set(self->map,
363 		 "initial_xscale", DE_JONG(self->map)->initial_xscale + i->delta_x * 0.001,
364 		 "initial_yscale", DE_JONG(self->map)->initial_yscale - i->delta_y * 0.001,
365 		 NULL);
366 }
367 
368 /* The End */
369