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