2 * This program is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU General Public License
4 * as published by the Free Software Foundation; either version 2
5 * of the License, or (at your option) any later version.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
16 * See the COPYING file for license information.
18 * Guillaume Chazarain <guichaz@gmail.com>
21 /****************************************************************
22 * Callbacks to control the gl_widget through its parent window *
23 ****************************************************************/
25 #include <gdk/gdkkeysyms.h> /* GDK_* */
28 #include "callbacks.h"
29 #include "math_floats.h" /* atan2f(), powf() */
31 #include "rendering.h"
34 #include "zoom_frame.h"
35 #include "move_pointer.h"
38 #include "next_image.h"
39 #include "gliv-image.h"
42 extern options_struct
*options
;
43 extern GlivImage
*current_image
;
44 extern GtkWidget
*gl_widget
;
46 /* Left button saved position in the root window. */
47 static gfloat old1_root_x
, old1_root_y
;
49 /* Where the left button press occurred in the window. */
50 static gfloat press1_win_x
, press1_win_y
;
52 /* Where the middle button press occurred in the window. */
53 static gfloat press2_win_x
, press2_win_y
;
55 /* Right button saved position in the window. */
56 static gint old3_win_x
, old3_win_y
;
58 /* If the middle button is released. */
59 static gboolean mouse_wheel_zoom
= TRUE
;
61 static gboolean
button_press_event(GtkWidget
* unused
, GdkEventButton
* event
)
63 if (current_image
== NULL
)
66 switch (event
->button
) {
68 old1_root_x
= event
->x_root
;
69 old1_root_y
= event
->y_root
;
71 press1_win_x
= event
->x
;
72 press1_win_y
= event
->y
;
76 mouse_wheel_zoom
= FALSE
;
77 press2_win_x
= event
->x
;
78 press2_win_y
= event
->y
;
82 /* We now have one of the zoom rectangle vertex. */
83 old3_win_x
= (gint
) event
->x
;
84 old3_win_y
= (gint
) event
->y
;
91 #define CHANGE_IMAGE_SCREEN_RATIO 20
93 static void detect_image_change(GdkEventButton
* event
)
95 int dx
= event
->x
- press2_win_x
;
96 int dy
= event
->y
- press2_win_y
;
99 if (!(event
->state
& GDK_CONTROL_MASK
))
102 if (dx
> rt
->scr_width
/ CHANGE_IMAGE_SCREEN_RATIO
||
103 dy
> rt
->scr_height
/ CHANGE_IMAGE_SCREEN_RATIO
)
105 else if (-dx
> rt
->scr_width
/ CHANGE_IMAGE_SCREEN_RATIO
||
106 -dy
> rt
->scr_height
/ CHANGE_IMAGE_SCREEN_RATIO
)
113 static gboolean
button_release_event(GtkWidget
* unused
, GdkEventButton
* event
)
115 if (current_image
== NULL
)
118 switch (event
->button
) {
120 refresh(APPEND_HISTORY
);
124 mouse_wheel_zoom
= TRUE
;
125 detect_image_change(event
);
135 /* Checks whether the pointer is on a border then moves it and returns TRUE. */
136 static gboolean
warp_pointer(gfloat x
, gfloat y
)
142 new_x
= rt
->scr_width
- 1;
145 } else if (x
>= rt
->scr_width
- 1.0) {
155 new_y
= rt
->scr_height
- 1;
158 } else if (y
>= rt
->scr_height
- 1.0) {
166 move_pointer(new_x
, new_y
);
172 * Remember the pointer position to know the sliding distance, the angle,
173 * or the zoom when the first button is pressed.
175 static void update_coordinates1(GdkEventMotion
* event
)
177 old1_root_x
= event
->x_root
;
178 old1_root_y
= event
->y_root
;
181 static gint
process_move(GdkEventMotion
* event
)
183 gfloat root_x
, root_y
;
185 root_x
= event
->x_root
;
186 root_y
= event
->y_root
;
188 matrix_move(root_x
- old1_root_x
, root_y
- old1_root_y
);
190 return REFRESH_BURST
;
193 static gint
process_rotation(GdkEventMotion
* event
)
195 gfloat center_x
, center_y
, x0
, y0
, x1
, y1
, angle
;
198 gdk_window_get_root_origin(gl_widget
->window
, &win_x
, &win_y
);
200 /* Window center in root coordinates. */
201 center_x
= win_x
+ rt
->wid_size
->width
/ 2.0;
202 center_y
= win_y
+ rt
->wid_size
->height
/ 2.0;
204 /* First rotation vector. */
205 x0
= old1_root_x
- center_x
;
206 y0
= old1_root_y
- center_y
;
208 /* Second rotation vector. */
209 x1
= event
->x_root
- center_x
;
210 y1
= event
->y_root
- center_y
;
212 angle
= atan2f(y0
, x0
) + atan2f(x1
, y1
) - PI
/ 2.0;
214 matrix_rotate(angle
);
216 return REFRESH_BURST
| REFRESH_STATUS
;
219 static gint
process_zoom(GdkEventMotion
* event
)
221 gfloat center_x
, center_y
, delta
, ratio
;
223 delta
= event
->y_root
- old1_root_y
;
224 ratio
= powf(ZOOM_FACTOR
, delta
/ 10.0);
226 if (options
->zoom_pointer
) {
227 center_x
= press1_win_x
;
228 center_y
= press1_win_y
;
230 center_x
= rt
->wid_size
->width
/ 2.0;
231 center_y
= rt
->wid_size
->height
/ 2.0;
234 matrix_zoom(ratio
, center_x
, center_y
);
236 return REFRESH_BURST
| REFRESH_STATUS
;
239 static gboolean
motion_notify_event(GtkWidget
* unused
, GdkEventMotion
* event
)
241 /* move_pointer() generates a motion_notify_event, useless here. */
242 static gboolean skip
= FALSE
;
246 /* We come just after a move_pointer(). */
248 update_coordinates1(event
);
252 if (rt
->cursor_hidden
)
255 schedule_hide_cursor();
257 if (current_image
== NULL
)
260 if (event
->state
& GDK_BUTTON1_MASK
) {
262 if (event
->state
& (GDK_CONTROL_MASK
| GDK_SHIFT_MASK
)) {
263 if (event
->state
& GDK_CONTROL_MASK
)
264 what
= process_rotation(event
);
266 if (event
->state
& GDK_SHIFT_MASK
)
267 what
|= process_zoom(event
);
269 what
= process_move(event
);
273 update_coordinates1(event
);
274 skip
= warp_pointer(event
->x_root
, event
->y_root
);
276 } else if (event
->state
& GDK_BUTTON3_MASK
) {
278 /* We draw the zoom rectangle. */
279 set_zoom_frame(old3_win_x
, old3_win_y
,
280 (gint
) event
->x
- old3_win_x
,
281 (gint
) event
->y
- old3_win_y
);
289 /* Called when pressing an arrow key. */
290 static void move_or_rotate(guint state
, gfloat x
, gfloat y
, gfloat angle
)
292 guint what
= REFRESH_IMAGE
| APPEND_HISTORY
;
294 if (state
& GDK_CONTROL_MASK
) {
295 matrix_rotate(angle
);
296 what
|= REFRESH_STATUS
;
303 /* Mask to keep track of which arrow keys are pressed. */
304 #define ARROW_UP (1 << 0)
305 #define ARROW_LEFT (1 << 1)
306 #define ARROW_RIGHT (1 << 2)
307 #define ARROW_DOWN (1 << 3)
309 /* Called twice to know how far we move in the two directions. */
310 static gfloat
get_arrow_move(guint state
, guint flag_pos
, guint flag_neg
)
314 flags
= state
& (flag_pos
| flag_neg
);
316 if (flags
== (flag_pos
| flag_neg
) || flags
== 0)
317 /* Both keys are pressed, or none => don't move. */
320 if (state
& flag_pos
)
328 /* An arrow key or something else was either pressed or released. */
329 static gboolean
process_arrow_key(GdkEventKey
* event
, gboolean press
)
331 static guint arrow_state
= 0;
335 if (current_image
== NULL
)
338 switch (event
->keyval
) {
342 angle
= BIG_ROTATION
;
348 angle
= SMALL_ROTATION
;
354 angle
= -SMALL_ROTATION
;
360 angle
= -BIG_ROTATION
;
364 /* Key not found, try a keyboard accelerator. */
369 arrow_state
|= arrow
;
371 arrow_state
&= ~arrow
;
374 x
= get_arrow_move(arrow_state
, ARROW_LEFT
, ARROW_RIGHT
);
375 y
= get_arrow_move(arrow_state
, ARROW_UP
, ARROW_DOWN
);
377 move_or_rotate(event
->state
, x
, y
, angle
);
383 static gboolean
key_press_event(GtkWidget
* unused
, GdkEventKey
* event
)
385 /* Most keys are handled via keyboard accelerators. */
386 switch (event
->keyval
) {
394 case GDK_KP_Page_Down
:
404 case GDK_KP_Subtract
:
405 zoom_in(1.0 / ZOOM_FACTOR
);
410 zoom_in(ZOOM_FACTOR
);
414 /* Key not found, try an arrow key, */
415 return process_arrow_key(event
, TRUE
);
421 static gboolean
key_release_event(GtkWidget
* unused
, GdkEventKey
* event
)
423 return process_arrow_key(event
, FALSE
);
426 static gboolean
scroll_event(GtkWidget
* unused
, GdkEventScroll
* event
)
428 if (current_image
== NULL
)
431 if (mouse_wheel_zoom
) {
433 if (event
->direction
== GDK_SCROLL_UP
)
434 zoom_in(1.0 / ZOOM_FACTOR
);
436 else if (event
->direction
== GDK_SCROLL_DOWN
)
437 zoom_in(ZOOM_FACTOR
);
440 /* mouse_wheel_zoom == FALSE */
442 if (event
->direction
== GDK_SCROLL_UP
)
445 else if (event
->direction
== GDK_SCROLL_DOWN
)
452 void install_callbacks(gpointer object
)
454 g_signal_connect(object
, "button-press-event",
455 G_CALLBACK(button_press_event
), NULL
);
457 g_signal_connect(object
, "button-release-event",
458 G_CALLBACK(button_release_event
), NULL
);
460 g_signal_connect(object
, "motion-notify-event",
461 G_CALLBACK(motion_notify_event
), NULL
);
463 g_signal_connect(object
, "key-press-event",
464 G_CALLBACK(key_press_event
), NULL
);
466 g_signal_connect(object
, "key-release-event",
467 G_CALLBACK(key_release_event
), NULL
);
469 g_signal_connect(object
, "delete-event", G_CALLBACK(gui_quit
), NULL
);
471 g_signal_connect(object
, "scroll-event", G_CALLBACK(scroll_event
), NULL
);
473 g_signal_connect_after(object
, "button-press-event",
474 G_CALLBACK(set_correct_cursor
), NULL
);
476 g_signal_connect_after(object
, "button-release-event",
477 G_CALLBACK(set_correct_cursor
), NULL
);
479 g_signal_connect_after(object
, "key-press-event",
480 G_CALLBACK(set_correct_cursor
), NULL
);
482 g_signal_connect_after(object
, "key-release-event",
483 G_CALLBACK(set_correct_cursor
), NULL
);
485 g_signal_connect_after(object
, "focus-in-event",
486 G_CALLBACK(set_correct_cursor
), NULL
);
489 static gboolean
finish(gpointer data
)
497 * We call process_events() when rebuilding the images menus and to wait for a
500 void process_events(void)
502 GTimeVal beginning
, now
;
504 gboolean stop
= FALSE
;
506 if (!gtk_events_pending())
509 g_idle_add(finish
, &stop
);
510 g_get_current_time(&beginning
);
512 while (!stop
&& delay
< G_USEC_PER_SEC
/ 10 && gtk_events_pending()) {
513 gtk_main_iteration_do(FALSE
);
514 g_get_current_time(&now
);
515 delay
= (now
.tv_sec
- beginning
.tv_sec
) * G_USEC_PER_SEC
+
516 now
.tv_usec
- beginning
.tv_usec
;
520 g_idle_remove_by_data(&stop
);