1
/* -*- mode: c; c-basic-offset: 4; -*-
3
* explorer-tools.c - Implementation for the GUI 'tools' that allow
4
* direct interaction with the mouse.
6
* Fyre - rendering and interactive exploration of chaotic functions
7
* Copyright (C) 2004-2005 David Trowbridge and Micah Dowty
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.
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.
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.
31
typedef void (ToolHandler)(Explorer *self, ToolInput *i);
33
typedef struct _ToolInfo {
39
static const ToolInfo* explorer_get_current_tool(Explorer *self);
40
static void explorer_fill_toolinput_relative_positions(Explorer *self, ToolInput *ti);
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);
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);
64
/* A table of tool handlers and menu item names */
65
static const ToolInfo tool_table[] = {
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},
87
/************************************************************************************/
88
/**************************************************** Initialization / Finalization */
89
/************************************************************************************/
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);
98
glade_xml_signal_connect_data(self->xml, "on_tool_activate", G_CALLBACK(on_tool_activate), self);
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);
104
self->current_tool = "None";
108
/************************************************************************************/
109
/****************************************************************** Tool Invocation */
110
/************************************************************************************/
112
static const ToolInfo* explorer_get_current_tool(Explorer *self) {
113
/* Return the current tool, or NULL if no tool is active
115
const ToolInfo *current = tool_table;
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)))
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
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;
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;
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);
142
memset(&ti, 0, sizeof(ti));
144
/* Fill in the absolute position and state for the ToolInput */
145
if (event->motion.is_hint) {
147
gdk_window_get_pointer(event->motion.window, &ix, &iy, &ti.state);
152
ti.absolute_x = event->motion.x;
153
ti.absolute_y = event->motion.y;
154
ti.state = event->motion.state;
156
explorer_fill_toolinput_relative_positions(self, &ti);
157
self->last_mouse_x = ti.absolute_x;
158
self->last_mouse_y = ti.absolute_y;
160
if (tool && (tool->flags & TOOL_USE_MOTION_EVENTS)) {
161
tool->handler(self, &ti);
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
172
explorer_run_iterations(self);
178
static void on_tool_activate(GtkWidget *widget, gpointer user_data) {
179
Explorer *self = EXPLORER(user_data);
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);
184
self->status_dirty_flag = TRUE;
187
static gboolean on_button_press(GtkWidget *widget, GdkEvent *event, gpointer user_data) {
188
Explorer *self = EXPLORER(user_data);
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;
198
static gboolean on_button_release(GtkWidget *widget, GdkEvent *event, gpointer user_data) {
199
Explorer *self = EXPLORER(user_data);
200
self->tool_active = FALSE;
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.
208
const ToolInfo *tool = explorer_get_current_tool(self);
212
memset(&ti, 0, sizeof(ti));
214
gdk_window_get_pointer(self->view->window, &ix, &iy, &ti.state);
217
explorer_fill_toolinput_relative_positions(self, &ti);
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;
225
if (tool && self->tool_active && (tool->flags & TOOL_USE_IDLE)) {
226
tool->handler(self, &ti);
235
/************************************************************************************/
236
/******************************************************************** Tool handlers */
237
/************************************************************************************/
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,
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,
254
static void tool_zoom(Explorer *self, ToolInput *i) {
256
const double exponent = 1.4;
258
/* Scale the zooming speed nonlinearly with the distance from click location */
259
p = i->click_relative_y * 0.01;
261
scaled_p = -pow(-p, exponent);
264
scaled_p = pow(p, exponent);
267
g_object_set(self->map,
268
"zoom", DE_JONG(self->map)->zoom - scaled_p * i->delta_time,
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.
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),
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,