1 /* LIBGIMP - The GIMP Library
2 * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
3 *
4 * gimpscrolledpreview.c
5 *
6 * This library is free software: you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library. If not, see
18 * <https://www.gnu.org/licenses/>.
19 */
20
21 #include "config.h"
22
23 #include <gtk/gtk.h>
24
25 #include "libgimpmath/gimpmath.h"
26
27 #include "gimpwidgetstypes.h"
28
29 #include "gimpicons.h"
30 #include "gimppreviewarea.h"
31 #include "gimpscrolledpreview.h"
32 #include "gimp3migration.h"
33
34 #include "libgimp/libgimp-intl.h"
35
36
37 /**
38 * SECTION: gimpscrolledpreview
39 * @title: GimpScrolledPreview
40 * @short_description: A widget providing a #GimpPreview enhanced by
41 * scrolling capabilities.
42 *
43 * A widget providing a #GimpPreview enhanced by scrolling capabilities.
44 **/
45
46
47 #define POPUP_SIZE 100
48
49
50 typedef struct
51 {
52 GtkPolicyType hscr_policy;
53 GtkPolicyType vscr_policy;
54 gint drag_x;
55 gint drag_y;
56 gint drag_xoff;
57 gint drag_yoff;
58 gboolean in_drag;
59 gint frozen;
60 } GimpScrolledPreviewPrivate;
61
62 #define GIMP_SCROLLED_PREVIEW_GET_PRIVATE(obj) \
63 ((GimpScrolledPreviewPrivate *) ((GimpScrolledPreview *) (obj))->priv)
64
65
66 static void gimp_scrolled_preview_class_init (GimpScrolledPreviewClass *klass);
67 static void gimp_scrolled_preview_init (GimpScrolledPreview *preview);
68 static void gimp_scrolled_preview_dispose (GObject *object);
69
70 static void gimp_scrolled_preview_area_realize (GtkWidget *widget,
71 GimpScrolledPreview *preview);
72 static void gimp_scrolled_preview_area_unrealize (GtkWidget *widget,
73 GimpScrolledPreview *preview);
74 static void gimp_scrolled_preview_area_size_allocate (GtkWidget *widget,
75 GtkAllocation *allocation,
76 GimpScrolledPreview *preview);
77 static gboolean gimp_scrolled_preview_area_event (GtkWidget *area,
78 GdkEvent *event,
79 GimpScrolledPreview *preview);
80
81 static void gimp_scrolled_preview_h_scroll (GtkAdjustment *hadj,
82 GimpPreview *preview);
83 static void gimp_scrolled_preview_v_scroll (GtkAdjustment *vadj,
84 GimpPreview *preview);
85
86 static gboolean gimp_scrolled_preview_nav_button_press (GtkWidget *widget,
87 GdkEventButton *event,
88 GimpScrolledPreview *preview);
89
90 static gboolean gimp_scrolled_preview_nav_popup_event (GtkWidget *widget,
91 GdkEvent *event,
92 GimpScrolledPreview *preview);
93 static gboolean gimp_scrolled_preview_nav_popup_expose (GtkWidget *widget,
94 GdkEventExpose *event,
95 GimpScrolledPreview *preview);
96
97 static void gimp_scrolled_preview_set_cursor (GimpPreview *preview);
98
99
100 static GimpPreviewClass *parent_class = NULL;
101
102
103 GType
gimp_scrolled_preview_get_type(void)104 gimp_scrolled_preview_get_type (void)
105 {
106 static GType preview_type = 0;
107
108 if (! preview_type)
109 {
110 const GTypeInfo preview_info =
111 {
112 sizeof (GimpScrolledPreviewClass),
113 (GBaseInitFunc) NULL,
114 (GBaseFinalizeFunc) NULL,
115 (GClassInitFunc) gimp_scrolled_preview_class_init,
116 NULL, /* class_finalize */
117 NULL, /* class_data */
118 sizeof (GimpScrolledPreview),
119 0, /* n_preallocs */
120 (GInstanceInitFunc) gimp_scrolled_preview_init,
121 };
122
123 preview_type = g_type_register_static (GIMP_TYPE_PREVIEW,
124 "GimpScrolledPreview",
125 &preview_info,
126 G_TYPE_FLAG_ABSTRACT);
127 }
128
129 return preview_type;
130 }
131
132 static void
gimp_scrolled_preview_class_init(GimpScrolledPreviewClass * klass)133 gimp_scrolled_preview_class_init (GimpScrolledPreviewClass *klass)
134 {
135 GObjectClass *object_class = G_OBJECT_CLASS (klass);
136 GimpPreviewClass *preview_class = GIMP_PREVIEW_CLASS (klass);
137
138 parent_class = g_type_class_peek_parent (klass);
139
140 object_class->dispose = gimp_scrolled_preview_dispose;
141
142 preview_class->set_cursor = gimp_scrolled_preview_set_cursor;
143
144 g_type_class_add_private (object_class, sizeof (GimpScrolledPreviewPrivate));
145 }
146
147 static void
gimp_scrolled_preview_init(GimpScrolledPreview * preview)148 gimp_scrolled_preview_init (GimpScrolledPreview *preview)
149 {
150 GimpScrolledPreviewPrivate *priv;
151 GtkWidget *image;
152 GtkAdjustment *adj;
153
154 preview->priv = G_TYPE_INSTANCE_GET_PRIVATE (preview,
155 GIMP_TYPE_SCROLLED_PREVIEW,
156 GimpScrolledPreviewPrivate);
157
158 priv = GIMP_SCROLLED_PREVIEW_GET_PRIVATE (preview);
159
160 preview->nav_popup = NULL;
161
162 priv->hscr_policy = GTK_POLICY_AUTOMATIC;
163 priv->vscr_policy = GTK_POLICY_AUTOMATIC;
164
165 priv->in_drag = FALSE;
166 priv->frozen = 1; /* we are frozen during init */
167
168 /* scrollbars */
169 adj = GTK_ADJUSTMENT (gtk_adjustment_new (0, 0, GIMP_PREVIEW (preview)->width - 1, 1.0,
170 GIMP_PREVIEW (preview)->width,
171 GIMP_PREVIEW (preview)->width));
172
173 g_signal_connect (adj, "value-changed",
174 G_CALLBACK (gimp_scrolled_preview_h_scroll),
175 preview);
176
177 preview->hscr = gtk_scrollbar_new (GTK_ORIENTATION_HORIZONTAL, adj);
178 gtk_table_attach (GTK_TABLE (GIMP_PREVIEW (preview)->table),
179 preview->hscr, 0, 1, 1, 2,
180 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
181
182 adj = GTK_ADJUSTMENT (gtk_adjustment_new (0, 0, GIMP_PREVIEW (preview)->height - 1, 1.0,
183 GIMP_PREVIEW (preview)->height,
184 GIMP_PREVIEW (preview)->height));
185
186 g_signal_connect (adj, "value-changed",
187 G_CALLBACK (gimp_scrolled_preview_v_scroll),
188 preview);
189
190 preview->vscr = gtk_scrollbar_new (GTK_ORIENTATION_VERTICAL, adj);
191 gtk_table_attach (GTK_TABLE (GIMP_PREVIEW (preview)->table),
192 preview->vscr, 1, 2, 0, 1,
193 GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
194
195 /* Connect after here so that plug-ins get a chance to override the
196 * default behavior. See bug #364432.
197 */
198 g_signal_connect_after (GIMP_PREVIEW (preview)->area, "event",
199 G_CALLBACK (gimp_scrolled_preview_area_event),
200 preview);
201
202 g_signal_connect (GIMP_PREVIEW (preview)->area, "realize",
203 G_CALLBACK (gimp_scrolled_preview_area_realize),
204 preview);
205 g_signal_connect (GIMP_PREVIEW (preview)->area, "unrealize",
206 G_CALLBACK (gimp_scrolled_preview_area_unrealize),
207 preview);
208
209 g_signal_connect (GIMP_PREVIEW (preview)->area, "size-allocate",
210 G_CALLBACK (gimp_scrolled_preview_area_size_allocate),
211 preview);
212
213 /* navigation icon */
214 preview->nav_icon = gtk_event_box_new ();
215 gtk_table_attach (GTK_TABLE (GIMP_PREVIEW(preview)->table),
216 preview->nav_icon, 1,2, 1,2,
217 GTK_SHRINK, GTK_SHRINK, 0, 0);
218
219 image = gtk_image_new_from_icon_name (GIMP_ICON_DIALOG_NAVIGATION,
220 GTK_ICON_SIZE_MENU);
221 gtk_container_add (GTK_CONTAINER (preview->nav_icon), image);
222 gtk_widget_show (image);
223
224 g_signal_connect (preview->nav_icon, "button-press-event",
225 G_CALLBACK (gimp_scrolled_preview_nav_button_press),
226 preview);
227
228 priv->frozen = 0; /* thaw without actually calling draw/invalidate */
229 }
230
231 static void
gimp_scrolled_preview_dispose(GObject * object)232 gimp_scrolled_preview_dispose (GObject *object)
233 {
234 GimpScrolledPreview *preview = GIMP_SCROLLED_PREVIEW (object);
235
236 if (preview->nav_popup)
237 {
238 gtk_widget_destroy (preview->nav_popup);
239 preview->nav_popup = NULL;
240 }
241
242 G_OBJECT_CLASS (parent_class)->dispose (object);
243 }
244
245 static void
gimp_scrolled_preview_area_realize(GtkWidget * widget,GimpScrolledPreview * preview)246 gimp_scrolled_preview_area_realize (GtkWidget *widget,
247 GimpScrolledPreview *preview)
248 {
249 GdkDisplay *display = gtk_widget_get_display (widget);
250
251 g_return_if_fail (preview->cursor_move == NULL);
252
253 preview->cursor_move = gdk_cursor_new_for_display (display, GDK_HAND1);
254 }
255
256 static void
gimp_scrolled_preview_area_unrealize(GtkWidget * widget,GimpScrolledPreview * preview)257 gimp_scrolled_preview_area_unrealize (GtkWidget *widget,
258 GimpScrolledPreview *preview)
259 {
260 if (preview->cursor_move)
261 {
262 gdk_cursor_unref (preview->cursor_move);
263 preview->cursor_move = NULL;
264 }
265 }
266
267 static void
gimp_scrolled_preview_hscr_update(GimpScrolledPreview * preview)268 gimp_scrolled_preview_hscr_update (GimpScrolledPreview *preview)
269 {
270 GtkAdjustment *adj = gtk_range_get_adjustment (GTK_RANGE (preview->hscr));
271 gint width;
272
273 width = GIMP_PREVIEW (preview)->xmax - GIMP_PREVIEW (preview)->xmin;
274
275 gtk_adjustment_configure (adj,
276 gtk_adjustment_get_value (adj),
277 0, width,
278 1.0,
279 MAX (GIMP_PREVIEW (preview)->width / 2.0, 1.0),
280 GIMP_PREVIEW (preview)->width);
281 }
282
283 static void
gimp_scrolled_preview_vscr_update(GimpScrolledPreview * preview)284 gimp_scrolled_preview_vscr_update (GimpScrolledPreview *preview)
285 {
286 GtkAdjustment *adj = gtk_range_get_adjustment (GTK_RANGE (preview->vscr));
287 gint height;
288
289 height = GIMP_PREVIEW (preview)->ymax - GIMP_PREVIEW (preview)->ymin;
290
291 gtk_adjustment_configure (adj,
292 gtk_adjustment_get_value (adj),
293 0, height,
294 1.0,
295 MAX (GIMP_PREVIEW (preview)->height / 2.0, 1.0),
296 GIMP_PREVIEW (preview)->height);
297 }
298
299 static void
gimp_scrolled_preview_area_size_allocate(GtkWidget * widget,GtkAllocation * allocation,GimpScrolledPreview * preview)300 gimp_scrolled_preview_area_size_allocate (GtkWidget *widget,
301 GtkAllocation *allocation,
302 GimpScrolledPreview *preview)
303 {
304 GimpScrolledPreviewPrivate *priv = GIMP_SCROLLED_PREVIEW_GET_PRIVATE (preview);
305
306 gint width = GIMP_PREVIEW (preview)->xmax - GIMP_PREVIEW (preview)->xmin;
307 gint height = GIMP_PREVIEW (preview)->ymax - GIMP_PREVIEW (preview)->ymin;
308
309 gimp_scrolled_preview_freeze (preview);
310
311 GIMP_PREVIEW (preview)->width = MIN (width, allocation->width);
312 GIMP_PREVIEW (preview)->height = MIN (height, allocation->height);
313
314 gimp_scrolled_preview_hscr_update (preview);
315
316 switch (priv->hscr_policy)
317 {
318 case GTK_POLICY_AUTOMATIC:
319 gtk_widget_set_visible (preview->hscr,
320 width > GIMP_PREVIEW (preview)->width);
321 break;
322
323 case GTK_POLICY_ALWAYS:
324 gtk_widget_show (preview->hscr);
325 break;
326
327 case GTK_POLICY_NEVER:
328 gtk_widget_hide (preview->hscr);
329 break;
330 }
331
332 gimp_scrolled_preview_vscr_update (preview);
333
334 switch (priv->vscr_policy)
335 {
336 case GTK_POLICY_AUTOMATIC:
337 gtk_widget_set_visible (preview->vscr,
338 height > GIMP_PREVIEW (preview)->height);
339 break;
340
341 case GTK_POLICY_ALWAYS:
342 gtk_widget_show (preview->vscr);
343 break;
344
345 case GTK_POLICY_NEVER:
346 gtk_widget_hide (preview->vscr);
347 break;
348 }
349
350 gtk_widget_set_visible (preview->nav_icon,
351 gtk_widget_get_visible (preview->vscr) &&
352 gtk_widget_get_visible (preview->hscr) &&
353 GIMP_PREVIEW_GET_CLASS (preview)->draw_thumb);
354
355 gimp_scrolled_preview_thaw (preview);
356 }
357
358 static gboolean
gimp_scrolled_preview_area_event(GtkWidget * area,GdkEvent * event,GimpScrolledPreview * preview)359 gimp_scrolled_preview_area_event (GtkWidget *area,
360 GdkEvent *event,
361 GimpScrolledPreview *preview)
362 {
363 GimpScrolledPreviewPrivate *priv = GIMP_SCROLLED_PREVIEW_GET_PRIVATE (preview);
364 GdkEventButton *button_event = (GdkEventButton *) event;
365 GdkCursor *cursor;
366
367 switch (event->type)
368 {
369 case GDK_BUTTON_PRESS:
370 switch (button_event->button)
371 {
372 case 1:
373 case 2:
374 cursor = gdk_cursor_new_for_display (gtk_widget_get_display (area),
375 GDK_FLEUR);
376
377 if (gdk_pointer_grab (gtk_widget_get_window (area), TRUE,
378 GDK_BUTTON_RELEASE_MASK |
379 GDK_BUTTON_MOTION_MASK |
380 GDK_POINTER_MOTION_HINT_MASK,
381 NULL, cursor,
382 gdk_event_get_time (event)) == GDK_GRAB_SUCCESS)
383 {
384 gtk_widget_get_pointer (area, &priv->drag_x, &priv->drag_y);
385
386 priv->drag_xoff = GIMP_PREVIEW (preview)->xoff;
387 priv->drag_yoff = GIMP_PREVIEW (preview)->yoff;
388 priv->in_drag = TRUE;
389 gtk_grab_add (area);
390 }
391
392 gdk_cursor_unref (cursor);
393
394 break;
395
396 case 3:
397 return TRUE;
398 }
399 break;
400
401 case GDK_BUTTON_RELEASE:
402 if (priv->in_drag &&
403 (button_event->button == 1 || button_event->button == 2))
404 {
405 gdk_display_pointer_ungrab (gtk_widget_get_display (area),
406 gdk_event_get_time (event));
407
408 gtk_grab_remove (area);
409 priv->in_drag = FALSE;
410 }
411 break;
412
413 case GDK_MOTION_NOTIFY:
414 if (priv->in_drag)
415 {
416 GdkEventMotion *mevent = (GdkEventMotion *) event;
417 GtkAdjustment *hadj;
418 GtkAdjustment *vadj;
419 gint x, y;
420
421 hadj = gtk_range_get_adjustment (GTK_RANGE (preview->hscr));
422 vadj = gtk_range_get_adjustment (GTK_RANGE (preview->vscr));
423
424 gtk_widget_get_pointer (area, &x, &y);
425
426 x = priv->drag_xoff - (x - priv->drag_x);
427 y = priv->drag_yoff - (y - priv->drag_y);
428
429 x = CLAMP (x,
430 gtk_adjustment_get_lower (hadj),
431 gtk_adjustment_get_upper (hadj) -
432 gtk_adjustment_get_page_size (hadj));
433 y = CLAMP (y,
434 gtk_adjustment_get_lower (vadj),
435 gtk_adjustment_get_upper (vadj) -
436 gtk_adjustment_get_page_size (vadj));
437
438 if (GIMP_PREVIEW (preview)->xoff != x ||
439 GIMP_PREVIEW (preview)->yoff != y)
440 {
441 gtk_adjustment_set_value (hadj, x);
442 gtk_adjustment_set_value (vadj, y);
443
444 gimp_preview_draw (GIMP_PREVIEW (preview));
445 gimp_preview_invalidate (GIMP_PREVIEW (preview));
446 }
447
448 gdk_event_request_motions (mevent);
449 }
450 break;
451
452 case GDK_SCROLL:
453 {
454 GdkEventScroll *sevent = (GdkEventScroll *) event;
455 GdkScrollDirection direction = sevent->direction;
456 GtkAdjustment *adj;
457 gfloat value;
458
459 /* Ctrl-Scroll is reserved for zooming */
460 if (sevent->state & GDK_CONTROL_MASK)
461 return FALSE;
462
463 if (sevent->state & GDK_SHIFT_MASK)
464 switch (direction)
465 {
466 case GDK_SCROLL_UP: direction = GDK_SCROLL_LEFT; break;
467 case GDK_SCROLL_DOWN: direction = GDK_SCROLL_RIGHT; break;
468 case GDK_SCROLL_LEFT: direction = GDK_SCROLL_UP; break;
469 case GDK_SCROLL_RIGHT: direction = GDK_SCROLL_DOWN; break;
470 }
471
472 switch (direction)
473 {
474 case GDK_SCROLL_UP:
475 case GDK_SCROLL_DOWN:
476 default:
477 adj = gtk_range_get_adjustment (GTK_RANGE (preview->vscr));
478 break;
479
480 case GDK_SCROLL_RIGHT:
481 case GDK_SCROLL_LEFT:
482 adj = gtk_range_get_adjustment (GTK_RANGE (preview->hscr));
483 break;
484 }
485
486 value = gtk_adjustment_get_value (adj);
487
488 switch (direction)
489 {
490 case GDK_SCROLL_UP:
491 case GDK_SCROLL_LEFT:
492 value -= gtk_adjustment_get_page_increment (adj) / 2;
493 break;
494
495 case GDK_SCROLL_DOWN:
496 case GDK_SCROLL_RIGHT:
497 value += gtk_adjustment_get_page_increment (adj) / 2;
498 break;
499 }
500
501 gtk_adjustment_set_value (adj,
502 CLAMP (value,
503 gtk_adjustment_get_lower (adj),
504 gtk_adjustment_get_upper (adj) -
505 gtk_adjustment_get_page_size (adj)));
506 }
507 break;
508
509 default:
510 break;
511 }
512
513 return FALSE;
514 }
515
516 static void
gimp_scrolled_preview_h_scroll(GtkAdjustment * hadj,GimpPreview * preview)517 gimp_scrolled_preview_h_scroll (GtkAdjustment *hadj,
518 GimpPreview *preview)
519 {
520 GimpScrolledPreviewPrivate *priv = GIMP_SCROLLED_PREVIEW_GET_PRIVATE (preview);
521
522 preview->xoff = gtk_adjustment_get_value (hadj);
523
524 gimp_preview_area_set_offsets (GIMP_PREVIEW_AREA (preview->area),
525 preview->xoff, preview->yoff);
526
527 if (! (priv->in_drag || priv->frozen))
528 {
529 gimp_preview_draw (preview);
530 gimp_preview_invalidate (preview);
531 }
532 }
533
534 static void
gimp_scrolled_preview_v_scroll(GtkAdjustment * vadj,GimpPreview * preview)535 gimp_scrolled_preview_v_scroll (GtkAdjustment *vadj,
536 GimpPreview *preview)
537 {
538 GimpScrolledPreviewPrivate *priv = GIMP_SCROLLED_PREVIEW_GET_PRIVATE (preview);
539
540 preview->yoff = gtk_adjustment_get_value (vadj);
541
542 gimp_preview_area_set_offsets (GIMP_PREVIEW_AREA (preview->area),
543 preview->xoff, preview->yoff);
544
545 if (! (priv->in_drag || priv->frozen))
546 {
547 gimp_preview_draw (preview);
548 gimp_preview_invalidate (preview);
549 }
550 }
551
552 static gboolean
gimp_scrolled_preview_nav_button_press(GtkWidget * widget,GdkEventButton * event,GimpScrolledPreview * preview)553 gimp_scrolled_preview_nav_button_press (GtkWidget *widget,
554 GdkEventButton *event,
555 GimpScrolledPreview *preview)
556 {
557 GimpPreview *gimp_preview = GIMP_PREVIEW (preview);
558 GtkAdjustment *adj;
559
560 if (preview->nav_popup)
561 return TRUE;
562
563 if (event->type == GDK_BUTTON_PRESS && event->button == 1)
564 {
565 GtkStyle *style = gtk_widget_get_style (widget);
566 GtkWidget *outer;
567 GtkWidget *inner;
568 GtkWidget *area;
569 GdkCursor *cursor;
570 gint x, y;
571 gdouble h, v;
572
573 preview->nav_popup = gtk_window_new (GTK_WINDOW_POPUP);
574
575 gtk_window_set_screen (GTK_WINDOW (preview->nav_popup),
576 gtk_widget_get_screen (widget));
577
578 outer = gtk_frame_new (NULL);
579 gtk_frame_set_shadow_type (GTK_FRAME (outer), GTK_SHADOW_OUT);
580 gtk_container_add (GTK_CONTAINER (preview->nav_popup), outer);
581 gtk_widget_show (outer);
582
583 inner = gtk_frame_new (NULL);
584 gtk_frame_set_shadow_type (GTK_FRAME (inner), GTK_SHADOW_IN);
585 gtk_container_add (GTK_CONTAINER (outer), inner);
586 gtk_widget_show (inner);
587
588 area = g_object_new (GIMP_TYPE_PREVIEW_AREA,
589 "check-size", GIMP_CHECK_SIZE_SMALL_CHECKS,
590 "check-type", GIMP_PREVIEW_AREA (gimp_preview->area)->check_type,
591 NULL);
592
593 gtk_container_add (GTK_CONTAINER (inner), area);
594
595 g_signal_connect (area, "event",
596 G_CALLBACK (gimp_scrolled_preview_nav_popup_event),
597 preview);
598 g_signal_connect_after (area, "expose-event",
599 G_CALLBACK (gimp_scrolled_preview_nav_popup_expose),
600 preview);
601
602 GIMP_PREVIEW_GET_CLASS (preview)->draw_thumb (gimp_preview,
603 GIMP_PREVIEW_AREA (area),
604 POPUP_SIZE, POPUP_SIZE);
605 gtk_widget_realize (area);
606 gtk_widget_show (area);
607
608 gdk_window_get_origin (gtk_widget_get_window (widget), &x, &y);
609
610 adj = gtk_range_get_adjustment (GTK_RANGE (preview->hscr));
611 h = ((gtk_adjustment_get_value (adj) /
612 gtk_adjustment_get_upper (adj)) +
613 (gtk_adjustment_get_page_size (adj) /
614 gtk_adjustment_get_upper (adj)) / 2.0);
615
616 adj = gtk_range_get_adjustment (GTK_RANGE (preview->vscr));
617 v = ((gtk_adjustment_get_value (adj) /
618 gtk_adjustment_get_upper (adj)) +
619 (gtk_adjustment_get_page_size (adj) /
620 gtk_adjustment_get_upper (adj)) / 2.0);
621
622 x += event->x - h * (gdouble) GIMP_PREVIEW_AREA (area)->width;
623 y += event->y - v * (gdouble) GIMP_PREVIEW_AREA (area)->height;
624
625 gtk_window_move (GTK_WINDOW (preview->nav_popup),
626 x - 2 * style->xthickness,
627 y - 2 * style->ythickness);
628
629 gtk_widget_show (preview->nav_popup);
630
631 gtk_grab_add (area);
632
633 cursor = gdk_cursor_new_for_display (gtk_widget_get_display (widget),
634 GDK_FLEUR);
635
636 gdk_pointer_grab (gtk_widget_get_window (area), TRUE,
637 GDK_BUTTON_RELEASE_MASK |
638 GDK_BUTTON_MOTION_MASK |
639 GDK_POINTER_MOTION_HINT_MASK,
640 gtk_widget_get_window (area), cursor,
641 event->time);
642
643 gdk_cursor_unref (cursor);
644 }
645
646 return TRUE;
647 }
648
649 static gboolean
gimp_scrolled_preview_nav_popup_event(GtkWidget * widget,GdkEvent * event,GimpScrolledPreview * preview)650 gimp_scrolled_preview_nav_popup_event (GtkWidget *widget,
651 GdkEvent *event,
652 GimpScrolledPreview *preview)
653 {
654 switch (event->type)
655 {
656 case GDK_BUTTON_RELEASE:
657 {
658 GdkEventButton *button_event = (GdkEventButton *) event;
659
660 if (button_event->button == 1)
661 {
662 gtk_grab_remove (widget);
663 gdk_display_pointer_ungrab (gtk_widget_get_display (widget),
664 button_event->time);
665
666 gtk_widget_destroy (preview->nav_popup);
667 preview->nav_popup = NULL;
668 }
669 }
670 break;
671
672 case GDK_MOTION_NOTIFY:
673 {
674 GdkEventMotion *mevent = (GdkEventMotion *) event;
675 GtkAdjustment *hadj;
676 GtkAdjustment *vadj;
677 GtkAllocation allocation;
678 gint cx, cy;
679 gdouble x, y;
680
681 hadj = gtk_range_get_adjustment (GTK_RANGE (preview->hscr));
682 vadj = gtk_range_get_adjustment (GTK_RANGE (preview->vscr));
683
684 gtk_widget_get_allocation (widget, &allocation);
685
686 gtk_widget_get_pointer (widget, &cx, &cy);
687
688 x = cx * (gtk_adjustment_get_upper (hadj) -
689 gtk_adjustment_get_lower (hadj)) / allocation.width;
690 y = cy * (gtk_adjustment_get_upper (vadj) -
691 gtk_adjustment_get_lower (vadj)) / allocation.height;
692
693 x += (gtk_adjustment_get_lower (hadj) -
694 gtk_adjustment_get_page_size (hadj) / 2);
695 y += (gtk_adjustment_get_lower (vadj) -
696 gtk_adjustment_get_page_size (vadj) / 2);
697
698 gtk_adjustment_set_value (hadj,
699 CLAMP (x,
700 gtk_adjustment_get_lower (hadj),
701 gtk_adjustment_get_upper (hadj) -
702 gtk_adjustment_get_page_size (hadj)));
703 gtk_adjustment_set_value (vadj,
704 CLAMP (y,
705 gtk_adjustment_get_lower (vadj),
706 gtk_adjustment_get_upper (vadj) -
707 gtk_adjustment_get_page_size (vadj)));
708
709 gtk_widget_queue_draw (widget);
710 gdk_window_process_updates (gtk_widget_get_window (widget), FALSE);
711
712 gdk_event_request_motions (mevent);
713 }
714 break;
715
716 default:
717 break;
718 }
719
720 return FALSE;
721 }
722
723 static gboolean
gimp_scrolled_preview_nav_popup_expose(GtkWidget * widget,GdkEventExpose * event,GimpScrolledPreview * preview)724 gimp_scrolled_preview_nav_popup_expose (GtkWidget *widget,
725 GdkEventExpose *event,
726 GimpScrolledPreview *preview)
727 {
728 GtkAdjustment *adj;
729 GtkAllocation allocation;
730 cairo_t *cr;
731 gdouble x, y;
732 gdouble w, h;
733
734 adj = gtk_range_get_adjustment (GTK_RANGE (preview->hscr));
735
736 gtk_widget_get_allocation (widget, &allocation);
737
738 x = (gtk_adjustment_get_value (adj) /
739 (gtk_adjustment_get_upper (adj) -
740 gtk_adjustment_get_lower (adj)));
741 w = (gtk_adjustment_get_page_size (adj) /
742 (gtk_adjustment_get_upper (adj) -
743 gtk_adjustment_get_lower (adj)));
744
745 adj = gtk_range_get_adjustment (GTK_RANGE (preview->vscr));
746
747 y = (gtk_adjustment_get_value (adj) /
748 (gtk_adjustment_get_upper (adj) -
749 gtk_adjustment_get_lower (adj)));
750 h = (gtk_adjustment_get_page_size (adj) /
751 (gtk_adjustment_get_upper (adj) -
752 gtk_adjustment_get_lower (adj)));
753
754 if (w >= 1.0 && h >= 1.0)
755 return FALSE;
756
757 x = floor (x * (gdouble) allocation.width);
758 y = floor (y * (gdouble) allocation.height);
759 w = MAX (1, ceil (w * (gdouble) allocation.width));
760 h = MAX (1, ceil (h * (gdouble) allocation.height));
761
762 cr = gdk_cairo_create (gtk_widget_get_window (widget));
763
764 gdk_cairo_region (cr, event->region);
765 cairo_clip (cr);
766
767 cairo_rectangle (cr,
768 0, 0, allocation.width, allocation.height);
769
770 cairo_rectangle (cr, x, y, w, h);
771
772 cairo_set_source_rgba (cr, 0, 0, 0, 0.5);
773 cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
774 cairo_fill (cr);
775
776 cairo_rectangle (cr, x, y, w, h);
777
778 cairo_set_source_rgb (cr, 1, 1, 1);
779 cairo_set_line_width (cr, 2);
780 cairo_stroke (cr);
781
782 cairo_destroy (cr);
783
784 return FALSE;
785 }
786
787 static void
gimp_scrolled_preview_set_cursor(GimpPreview * preview)788 gimp_scrolled_preview_set_cursor (GimpPreview *preview)
789 {
790 if (! gtk_widget_get_realized (preview->area))
791 return;
792
793 if (preview->xmax - preview->xmin > preview->width ||
794 preview->ymax - preview->ymin > preview->height)
795 {
796 gdk_window_set_cursor (gtk_widget_get_window (preview->area),
797 GIMP_SCROLLED_PREVIEW (preview)->cursor_move);
798 }
799 else
800 {
801 gdk_window_set_cursor (gtk_widget_get_window (preview->area),
802 preview->default_cursor);
803 }
804 }
805
806 /**
807 * gimp_scrolled_preview_set_position:
808 * @preview: a #GimpScrolledPreview
809 * @x: horizontal scroll offset
810 * @y: vertical scroll offset
811 *
812 * Since: 2.4
813 **/
814 void
gimp_scrolled_preview_set_position(GimpScrolledPreview * preview,gint x,gint y)815 gimp_scrolled_preview_set_position (GimpScrolledPreview *preview,
816 gint x,
817 gint y)
818 {
819 GtkAdjustment *adj;
820
821 g_return_if_fail (GIMP_IS_SCROLLED_PREVIEW (preview));
822
823 gimp_scrolled_preview_freeze (preview);
824
825 gimp_scrolled_preview_hscr_update (preview);
826 gimp_scrolled_preview_vscr_update (preview);
827
828 adj = gtk_range_get_adjustment (GTK_RANGE (preview->hscr));
829 gtk_adjustment_set_value (adj, x - GIMP_PREVIEW (preview)->xmin);
830
831 adj = gtk_range_get_adjustment (GTK_RANGE (preview->vscr));
832 gtk_adjustment_set_value (adj, y - GIMP_PREVIEW (preview)->ymin);
833
834 gimp_scrolled_preview_thaw (preview);
835 }
836
837 /**
838 * gimp_scrolled_preview_set_policy
839 * @preview: a #GimpScrolledPreview
840 * @hscrollbar_policy: policy for horizontal scrollbar
841 * @vscrollbar_policy: policy for vertical scrollbar
842 *
843 * Since: 2.4
844 **/
845 void
gimp_scrolled_preview_set_policy(GimpScrolledPreview * preview,GtkPolicyType hscrollbar_policy,GtkPolicyType vscrollbar_policy)846 gimp_scrolled_preview_set_policy (GimpScrolledPreview *preview,
847 GtkPolicyType hscrollbar_policy,
848 GtkPolicyType vscrollbar_policy)
849 {
850 GimpScrolledPreviewPrivate *priv;
851
852 g_return_if_fail (GIMP_IS_SCROLLED_PREVIEW (preview));
853
854 priv = GIMP_SCROLLED_PREVIEW_GET_PRIVATE (preview);
855
856 priv->hscr_policy = hscrollbar_policy;
857 priv->vscr_policy = vscrollbar_policy;
858
859 gtk_widget_queue_resize (GIMP_PREVIEW (preview)->area);
860 }
861
862
863 /**
864 * gimp_scrolled_preview_freeze:
865 * @preview: a #GimpScrolledPreview
866 *
867 * While the @preview is frozen, it is not going to redraw itself in
868 * response to scroll events.
869 *
870 * This function should only be used to implement widgets derived from
871 * #GimpScrolledPreview. There is no point in calling this from a plug-in.
872 *
873 * Since: 2.4
874 **/
875 void
gimp_scrolled_preview_freeze(GimpScrolledPreview * preview)876 gimp_scrolled_preview_freeze (GimpScrolledPreview *preview)
877 {
878 GimpScrolledPreviewPrivate *priv;
879
880 g_return_if_fail (GIMP_IS_SCROLLED_PREVIEW (preview));
881
882 priv = GIMP_SCROLLED_PREVIEW_GET_PRIVATE (preview);
883
884 priv->frozen++;
885 }
886
887 /**
888 * gimp_scrolled_preview_thaw:
889 * @preview: a #GimpScrolledPreview
890 *
891 * While the @preview is frozen, it is not going to redraw itself in
892 * response to scroll events.
893 *
894 * This function should only be used to implement widgets derived from
895 * #GimpScrolledPreview. There is no point in calling this from a plug-in.
896 *
897 * Since: 2.4
898 **/
899 void
gimp_scrolled_preview_thaw(GimpScrolledPreview * preview)900 gimp_scrolled_preview_thaw (GimpScrolledPreview *preview)
901 {
902 GimpScrolledPreviewPrivate *priv;
903
904 g_return_if_fail (GIMP_IS_SCROLLED_PREVIEW (preview));
905
906 priv = GIMP_SCROLLED_PREVIEW_GET_PRIVATE (preview);
907
908 g_return_if_fail (priv->frozen > 0);
909
910 priv->frozen--;
911
912 if (! priv->frozen)
913 {
914 gimp_preview_draw (GIMP_PREVIEW (preview));
915 gimp_preview_invalidate (GIMP_PREVIEW (preview));
916 }
917 }
918