1 /* GIMP - The GNU Image Manipulation Program
2  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3  *
4  * gimptagentry.c
5  * Copyright (C) 2008 Aurimas Juška <aurisj@svn.gnome.org>
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19  */
20 
21 #include "config.h"
22 
23 #include <stdlib.h>
24 #include <string.h>
25 
26 #include <gegl.h>
27 #include <gtk/gtk.h>
28 
29 #include "widgets-types.h"
30 
31 #include "core/gimpcontainer.h"
32 #include "core/gimpcontext.h"
33 #include "core/gimptag.h"
34 #include "core/gimptagged.h"
35 #include "core/gimptaggedcontainer.h"
36 #include "core/gimpviewable.h"
37 
38 #include "gimpcombotagentry.h"
39 #include "gimptagentry.h"
40 #include "gimptagpopup.h"
41 
42 #include "gimp-intl.h"
43 
44 
45 #define MENU_SCROLL_STEP1            8
46 #define MENU_SCROLL_STEP2           15
47 #define MENU_SCROLL_FAST_ZONE        8
48 #define MENU_SCROLL_TIMEOUT1        50
49 #define MENU_SCROLL_TIMEOUT2        20
50 
51 #define GIMP_TAG_POPUP_MARGIN        5
52 #define GIMP_TAG_POPUP_PADDING       2
53 #define GIMP_TAG_POPUP_LINE_SPACING  2
54 
55 enum
56 {
57   PROP_0,
58   PROP_OWNER
59 };
60 
61 struct _PopupTagData
62 {
63   GimpTag      *tag;
64   GdkRectangle  bounds;
65   GtkStateType  state;
66 };
67 
68 
69 static void     gimp_tag_popup_constructed             (GObject        *object);
70 static void     gimp_tag_popup_dispose                 (GObject        *object);
71 static void     gimp_tag_popup_set_property            (GObject        *object,
72                                                         guint           property_id,
73                                                         const GValue   *value,
74                                                         GParamSpec     *pspec);
75 static void     gimp_tag_popup_get_property            (GObject        *object,
76                                                         guint           property_id,
77                                                         GValue         *value,
78                                                         GParamSpec     *pspec);
79 
80 static gboolean gimp_tag_popup_border_expose           (GtkWidget      *widget,
81                                                         GdkEventExpose *event,
82                                                         GimpTagPopup   *popup);
83 static gboolean gimp_tag_popup_list_expose             (GtkWidget      *widget,
84                                                         GdkEventExpose *event,
85                                                         GimpTagPopup   *popup);
86 static gboolean gimp_tag_popup_border_event            (GtkWidget      *widget,
87                                                         GdkEvent       *event);
88 static gboolean gimp_tag_popup_list_event              (GtkWidget      *widget,
89                                                         GdkEvent       *event,
90                                                         GimpTagPopup   *popup);
91 static gboolean gimp_tag_popup_is_in_tag               (PopupTagData   *tag_data,
92                                                         gint            x,
93                                                         gint            y);
94 static void     gimp_tag_popup_queue_draw_tag          (GimpTagPopup   *widget,
95                                                         PopupTagData   *tag_data);
96 static void     gimp_tag_popup_toggle_tag              (GimpTagPopup   *popup,
97                                                         PopupTagData   *tag_data);
98 static void     gimp_tag_popup_check_can_toggle        (GimpTagged     *tagged,
99                                                         GimpTagPopup   *popup);
100 static gint     gimp_tag_popup_layout_tags             (GimpTagPopup   *popup,
101                                                         gint            width);
102 static gboolean gimp_tag_popup_scroll_timeout          (gpointer        data);
103 static void     gimp_tag_popup_remove_scroll_timeout   (GimpTagPopup   *popup);
104 static gboolean gimp_tag_popup_scroll_timeout_initial  (gpointer        data);
105 static void     gimp_tag_popup_start_scrolling         (GimpTagPopup   *popup);
106 static void     gimp_tag_popup_stop_scrolling          (GimpTagPopup   *popup);
107 static void     gimp_tag_popup_scroll_by               (GimpTagPopup   *popup,
108                                                         gint            step);
109 static void     gimp_tag_popup_handle_scrolling        (GimpTagPopup   *popup,
110                                                         gint            x,
111                                                         gint            y,
112                                                         gboolean        enter,
113                                                         gboolean        motion);
114 
115 static gboolean gimp_tag_popup_button_scroll           (GimpTagPopup   *popup,
116                                                         GdkEventButton *event);
117 
118 static void     get_arrows_visible_area                (GimpTagPopup   *combo_entry,
119                                                         GdkRectangle   *border,
120                                                         GdkRectangle   *upper,
121                                                         GdkRectangle   *lower,
122                                                         gint           *arrow_space);
123 static void     get_arrows_sensitive_area              (GimpTagPopup   *popup,
124                                                         GdkRectangle   *upper,
125                                                         GdkRectangle   *lower);
126 
127 
128 G_DEFINE_TYPE (GimpTagPopup, gimp_tag_popup, GTK_TYPE_WINDOW);
129 
130 #define parent_class gimp_tag_popup_parent_class
131 
132 
133 static void
gimp_tag_popup_class_init(GimpTagPopupClass * klass)134 gimp_tag_popup_class_init (GimpTagPopupClass *klass)
135 {
136   GObjectClass *object_class = G_OBJECT_CLASS (klass);
137 
138   object_class->constructed  = gimp_tag_popup_constructed;
139   object_class->dispose      = gimp_tag_popup_dispose;
140   object_class->set_property = gimp_tag_popup_set_property;
141   object_class->get_property = gimp_tag_popup_get_property;
142 
143   g_object_class_install_property (object_class, PROP_OWNER,
144                                    g_param_spec_object ("owner", NULL, NULL,
145                                                         GIMP_TYPE_COMBO_TAG_ENTRY,
146                                                         GIMP_PARAM_READWRITE |
147                                                         G_PARAM_CONSTRUCT_ONLY));
148 }
149 
150 static void
gimp_tag_popup_init(GimpTagPopup * popup)151 gimp_tag_popup_init (GimpTagPopup *popup)
152 {
153   GtkWidget *widget = GTK_WIDGET (popup);
154 
155   popup->upper_arrow_state = GTK_STATE_NORMAL;
156   popup->lower_arrow_state = GTK_STATE_NORMAL;
157 
158   gtk_widget_add_events (widget,
159                          GDK_BUTTON_PRESS_MASK   |
160                          GDK_BUTTON_RELEASE_MASK |
161                          GDK_POINTER_MOTION_MASK |
162                          GDK_KEY_RELEASE_MASK    |
163                          GDK_SCROLL_MASK);
164 
165   popup->frame = gtk_frame_new (NULL);
166   gtk_frame_set_shadow_type (GTK_FRAME (popup->frame), GTK_SHADOW_OUT);
167   gtk_container_add (GTK_CONTAINER (popup), popup->frame);
168   gtk_widget_show (popup->frame);
169 
170   popup->alignment = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
171   gtk_container_add (GTK_CONTAINER (popup->frame), popup->alignment);
172   gtk_widget_show (popup->alignment);
173 
174   popup->tag_area = gtk_drawing_area_new ();
175   gtk_widget_add_events (popup->tag_area,
176                          GDK_BUTTON_PRESS_MASK   |
177                          GDK_BUTTON_RELEASE_MASK |
178                          GDK_POINTER_MOTION_MASK);
179   gtk_container_add (GTK_CONTAINER (popup->alignment), popup->tag_area);
180   gtk_widget_show (popup->tag_area);
181 
182   g_signal_connect (popup->alignment, "expose-event",
183                     G_CALLBACK (gimp_tag_popup_border_expose),
184                     popup);
185   g_signal_connect (popup, "event",
186                     G_CALLBACK (gimp_tag_popup_border_event),
187                     NULL);
188   g_signal_connect (popup->tag_area, "expose-event",
189                     G_CALLBACK (gimp_tag_popup_list_expose),
190                     popup);
191   g_signal_connect (popup->tag_area, "event",
192                     G_CALLBACK (gimp_tag_popup_list_event),
193                     popup);
194 }
195 
196 static void
gimp_tag_popup_constructed(GObject * object)197 gimp_tag_popup_constructed (GObject *object)
198 {
199   GimpTagPopup        *popup = GIMP_TAG_POPUP (object);
200   GimpTaggedContainer *container;
201   GtkWidget           *entry;
202   GtkAllocation        entry_allocation;
203   GtkStyle            *frame_style;
204   gint                 x;
205   gint                 y;
206   gint                 width;
207   gint                 height;
208   gint                 popup_height;
209   GHashTable          *tag_hash;
210   GList               *tag_list;
211   GList               *tag_iterator;
212   gint                 i;
213   gint                 max_height;
214   gint                 screen_height;
215   gchar              **current_tags;
216   gint                 current_count;
217   GdkRectangle         popup_rects[2]; /* variants of popup placement */
218   GdkRectangle         popup_rect; /* best popup rect in screen coordinates */
219 
220   G_OBJECT_CLASS (parent_class)->constructed (object);
221 
222   entry = GTK_WIDGET (popup->combo_entry);
223 
224   gtk_window_set_screen (GTK_WINDOW (popup), gtk_widget_get_screen (entry));
225 
226   popup->context = gtk_widget_create_pango_context (GTK_WIDGET (popup));
227   popup->layout  = pango_layout_new (popup->context);
228 
229   gtk_widget_get_allocation (entry, &entry_allocation);
230 
231   gtk_widget_style_get (GTK_WIDGET (popup),
232                         "scroll-arrow-vlength", &popup->scroll_arrow_height,
233                         NULL);
234 
235   pango_layout_set_attributes (popup->layout,
236                                popup->combo_entry->normal_item_attr);
237 
238   current_tags  = gimp_tag_entry_parse_tags (GIMP_TAG_ENTRY (popup->combo_entry));
239   current_count = g_strv_length (current_tags);
240 
241   container = GIMP_TAG_ENTRY (popup->combo_entry)->container;
242 
243   tag_hash = container->tag_ref_counts;
244   tag_list = g_hash_table_get_keys (tag_hash);
245   tag_list = g_list_sort (tag_list, gimp_tag_compare_func);
246 
247   popup->tag_count = g_list_length (tag_list);
248   popup->tag_data  = g_new0 (PopupTagData, popup->tag_count);
249 
250   for (i = 0, tag_iterator = tag_list;
251        i < popup->tag_count;
252        i++, tag_iterator = g_list_next (tag_iterator))
253     {
254       PopupTagData *tag_data = &popup->tag_data[i];
255       gint          j;
256 
257       tag_data->tag   = tag_iterator->data;
258       tag_data->state = GTK_STATE_NORMAL;
259 
260       g_object_ref (tag_data->tag);
261 
262       for (j = 0; j < current_count; j++)
263         {
264           if (! gimp_tag_compare_with_string (tag_data->tag, current_tags[j]))
265             {
266               tag_data->state = GTK_STATE_SELECTED;
267               break;
268             }
269         }
270     }
271 
272   g_list_free (tag_list);
273   g_strfreev (current_tags);
274 
275   if (GIMP_TAG_ENTRY (popup->combo_entry)->mode == GIMP_TAG_ENTRY_MODE_QUERY)
276     {
277       for (i = 0; i < popup->tag_count; i++)
278         {
279           if (popup->tag_data[i].state != GTK_STATE_SELECTED)
280             {
281               popup->tag_data[i].state = GTK_STATE_INSENSITIVE;
282             }
283         }
284 
285       gimp_container_foreach (GIMP_CONTAINER (container),
286                               (GFunc) gimp_tag_popup_check_can_toggle,
287                               popup);
288     }
289 
290   frame_style = gtk_widget_get_style (popup->frame);
291 
292   width  = (entry_allocation.width -
293             2 * frame_style->xthickness);
294   height = (gimp_tag_popup_layout_tags (popup, width) +
295             2 * frame_style->ythickness);
296 
297   gdk_window_get_origin (gtk_widget_get_window (entry), &x, &y);
298 
299   max_height = entry_allocation.height * 10;
300 
301   screen_height = gdk_screen_get_height (gtk_widget_get_screen (entry));
302 
303   popup_height = MIN (height, max_height);
304 
305   popup_rects[0].x      = x;
306   popup_rects[0].y      = 0;
307   popup_rects[0].width  = entry_allocation.width;
308   popup_rects[0].height = y + entry_allocation.height;
309 
310   popup_rects[1].x      = x;
311   popup_rects[1].y      = y;
312   popup_rects[1].width  = popup_rects[0].width;
313   popup_rects[1].height = screen_height - popup_rects[0].height;
314 
315   if (popup_rects[0].height >= popup_height)
316     {
317       popup_rect = popup_rects[0];
318       popup_rect.y += popup_rects[0].height - popup_height;
319       popup_rect.height = popup_height;
320     }
321   else if (popup_rects[1].height >= popup_height)
322     {
323       popup_rect = popup_rects[1];
324       popup_rect.height = popup_height;
325     }
326   else
327     {
328       if (popup_rects[0].height >= popup_rects[1].height)
329         {
330           popup_rect = popup_rects[0];
331           popup_rect.y += popup->scroll_arrow_height + frame_style->ythickness;
332         }
333       else
334         {
335           popup_rect = popup_rects[1];
336           popup_rect.y -= popup->scroll_arrow_height + frame_style->ythickness;
337         }
338 
339       popup_height = popup_rect.height;
340     }
341 
342   if (popup_height < height)
343     {
344       popup->arrows_visible    = TRUE;
345       popup->upper_arrow_state = GTK_STATE_INSENSITIVE;
346 
347       gtk_alignment_set_padding (GTK_ALIGNMENT (popup->alignment),
348                                  popup->scroll_arrow_height + 2,
349                                  popup->scroll_arrow_height + 2, 0, 0);
350 
351       popup_height -= 2 * popup->scroll_arrow_height + 4;
352 
353       popup->scroll_height = height - popup_height;
354       popup->scroll_y      = 0;
355       popup->scroll_step   = 0;
356     }
357 
358   gtk_widget_set_size_request (popup->tag_area, width, popup_height);
359 
360   gtk_window_move (GTK_WINDOW (popup), popup_rect.x, popup_rect.y);
361   gtk_window_resize (GTK_WINDOW (popup), popup_rect.width, popup_rect.height);
362 }
363 
364 static void
gimp_tag_popup_dispose(GObject * object)365 gimp_tag_popup_dispose (GObject *object)
366 {
367   GimpTagPopup *popup = GIMP_TAG_POPUP (object);
368 
369   gimp_tag_popup_remove_scroll_timeout (popup);
370 
371   g_clear_object (&popup->combo_entry);
372   g_clear_object (&popup->layout);
373   g_clear_object (&popup->context);
374 
375   if (popup->tag_data)
376     {
377       gint i;
378 
379       for (i = 0; i < popup->tag_count; i++)
380         {
381           g_object_unref (popup->tag_data[i].tag);
382         }
383 
384       g_clear_pointer (&popup->tag_data, g_free);
385     }
386 
387   G_OBJECT_CLASS (parent_class)->dispose (object);
388 }
389 
390 static void
gimp_tag_popup_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)391 gimp_tag_popup_set_property (GObject      *object,
392                              guint         property_id,
393                              const GValue *value,
394                              GParamSpec   *pspec)
395 {
396   GimpTagPopup *popup = GIMP_TAG_POPUP (object);
397 
398   switch (property_id)
399     {
400     case PROP_OWNER:
401       popup->combo_entry = g_value_dup_object (value);
402       break;
403 
404     default:
405       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
406       break;
407     }
408 }
409 
410 static void
gimp_tag_popup_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)411 gimp_tag_popup_get_property (GObject    *object,
412                              guint       property_id,
413                              GValue     *value,
414                              GParamSpec *pspec)
415 {
416   GimpTagPopup *popup = GIMP_TAG_POPUP (object);
417 
418   switch (property_id)
419     {
420     case PROP_OWNER:
421       g_value_set_object (value, popup->combo_entry);
422       break;
423 
424     default:
425       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
426       break;
427     }
428 }
429 
430 /**
431  * gimp_tag_popup_new:
432  * @combo_entry: #GimpComboTagEntry which is owner of the popup window.
433  *
434  * Tag popup widget is only useful for for #GimpComboTagEntry and
435  * should not be used elsewhere.
436  *
437  * Return value: a newly created #GimpTagPopup widget.
438  **/
439 GtkWidget *
gimp_tag_popup_new(GimpComboTagEntry * combo_entry)440 gimp_tag_popup_new (GimpComboTagEntry *combo_entry)
441 {
442   g_return_val_if_fail (GIMP_IS_COMBO_TAG_ENTRY (combo_entry), NULL);
443 
444   return g_object_new (GIMP_TYPE_TAG_POPUP,
445                        "type",  GTK_WINDOW_POPUP,
446                        "owner", combo_entry,
447                        NULL);
448 }
449 
450 /**
451  * gimp_tag_popup_show:
452  * @tag_popup:  an instance of #GimpTagPopup
453  *
454  * Show tag popup widget. If mouse grab cannot be obtained for widget,
455  * it is destroyed.
456  **/
457 void
gimp_tag_popup_show(GimpTagPopup * popup)458 gimp_tag_popup_show (GimpTagPopup *popup)
459 {
460   GtkWidget *widget;
461 
462   g_return_if_fail (GIMP_IS_TAG_POPUP (popup));
463 
464   widget = GTK_WIDGET (popup);
465 
466   gtk_widget_show (widget);
467 
468   gtk_grab_add (widget);
469   gtk_widget_grab_focus (widget);
470 
471   if (gdk_pointer_grab (gtk_widget_get_window (widget), TRUE,
472                         GDK_BUTTON_PRESS_MASK   |
473                         GDK_BUTTON_RELEASE_MASK |
474                         GDK_POINTER_MOTION_MASK,
475                         NULL, NULL,
476                         GDK_CURRENT_TIME) != GDK_GRAB_SUCCESS)
477     {
478       /* pointer grab must be attained otherwise user would have
479        * problems closing the popup window.
480        */
481       gtk_grab_remove (widget);
482       gtk_widget_destroy (widget);
483     }
484 }
485 
486 static gint
gimp_tag_popup_layout_tags(GimpTagPopup * popup,gint width)487 gimp_tag_popup_layout_tags (GimpTagPopup *popup,
488                             gint          width)
489 {
490   PangoFontMetrics *font_metrics;
491   gint              x;
492   gint              y;
493   gint              height = 0;
494   gint              i;
495   gint              line_height;
496   gint              space_width;
497 
498   x = GIMP_TAG_POPUP_MARGIN;
499   y = GIMP_TAG_POPUP_MARGIN;
500 
501   font_metrics = pango_context_get_metrics (popup->context,
502                                             pango_context_get_font_description (popup->context),
503                                             NULL);
504 
505   line_height = PANGO_PIXELS ((pango_font_metrics_get_ascent (font_metrics) +
506                                pango_font_metrics_get_descent (font_metrics)));
507   space_width = PANGO_PIXELS (pango_font_metrics_get_approximate_char_width (font_metrics));
508 
509   pango_font_metrics_unref (font_metrics);
510 
511   for (i = 0; i < popup->tag_count; i++)
512     {
513       PopupTagData *tag_data = &popup->tag_data[i];
514       gint          w, h;
515 
516       pango_layout_set_text (popup->layout,
517                              gimp_tag_get_name (tag_data->tag), -1);
518       pango_layout_get_pixel_size (popup->layout, &w, &h);
519 
520       tag_data->bounds.width  = w + 2 * GIMP_TAG_POPUP_PADDING;
521       tag_data->bounds.height = h + 2 * GIMP_TAG_POPUP_PADDING;
522 
523       if (x + space_width + tag_data->bounds.width +
524           GIMP_TAG_POPUP_MARGIN - 1 > width)
525         {
526           x = GIMP_TAG_POPUP_MARGIN;
527           y += line_height + 2 * GIMP_TAG_POPUP_PADDING + GIMP_TAG_POPUP_LINE_SPACING;
528         }
529 
530       tag_data->bounds.x = x;
531       tag_data->bounds.y = y;
532 
533       x += tag_data->bounds.width + space_width;
534     }
535 
536   if (gtk_widget_get_direction (GTK_WIDGET (popup)) == GTK_TEXT_DIR_RTL)
537     {
538       for (i = 0; i < popup->tag_count; i++)
539         {
540           PopupTagData *tag_data = &popup->tag_data[i];
541 
542           tag_data->bounds.x = (width -
543                                 tag_data->bounds.x -
544                                 tag_data->bounds.width);
545         }
546     }
547 
548   height = y + line_height + GIMP_TAG_POPUP_MARGIN;
549 
550   return height;
551 }
552 
553 static gboolean
gimp_tag_popup_border_expose(GtkWidget * widget,GdkEventExpose * event,GimpTagPopup * popup)554 gimp_tag_popup_border_expose (GtkWidget      *widget,
555                               GdkEventExpose *event,
556                               GimpTagPopup   *popup)
557 {
558   GdkWindow    *window = gtk_widget_get_window (widget);
559   GtkStyle     *style  = gtk_widget_get_style (widget);
560   GdkRectangle  border;
561   GdkRectangle  upper;
562   GdkRectangle  lower;
563   gint          arrow_space;
564   gint          arrow_size;
565 
566   if (event->window != gtk_widget_get_window (widget))
567     return FALSE;
568 
569   get_arrows_visible_area (popup, &border, &upper, &lower, &arrow_space);
570 
571   arrow_size = 0.7 * arrow_space;
572 
573   gtk_paint_box (style, window,
574                  GTK_STATE_NORMAL,
575                  GTK_SHADOW_OUT,
576                  &event->area, widget, "menu",
577                  0, 0, -1, -1);
578 
579   if (popup->arrows_visible)
580     {
581       /*  upper arrow  */
582 
583       gtk_paint_box (style, window,
584                      popup->upper_arrow_state,
585                      GTK_SHADOW_OUT,
586                      &event->area, widget, "menu",
587                      upper.x,
588                      upper.y,
589                      upper.width,
590                      upper.height);
591 
592       gtk_paint_arrow (style, window,
593                        popup->upper_arrow_state,
594                        GTK_SHADOW_OUT,
595                        &event->area, widget, "menu_scroll_arrow_up",
596                        GTK_ARROW_UP,
597                        TRUE,
598                        upper.x + (upper.width - arrow_size) / 2,
599                        upper.y + style->ythickness + (arrow_space - arrow_size) / 2,
600                        arrow_size, arrow_size);
601 
602       /*  lower arrow  */
603 
604       gtk_paint_box (style, window,
605                      popup->lower_arrow_state,
606                      GTK_SHADOW_OUT,
607                      &event->area, widget, "menu",
608                      lower.x,
609                      lower.y,
610                      lower.width,
611                      lower.height);
612 
613       gtk_paint_arrow (style, window,
614                        popup->lower_arrow_state,
615                        GTK_SHADOW_OUT,
616                        &event->area, widget, "menu_scroll_arrow_down",
617                        GTK_ARROW_DOWN,
618                        TRUE,
619                        lower.x + (lower.width - arrow_size) / 2,
620                        lower.y + style->ythickness + (arrow_space - arrow_size) / 2,
621                        arrow_size, arrow_size);
622     }
623 
624   return FALSE;
625 }
626 
627 static gboolean
gimp_tag_popup_border_event(GtkWidget * widget,GdkEvent * event)628 gimp_tag_popup_border_event (GtkWidget *widget,
629                              GdkEvent  *event)
630 {
631   GimpTagPopup *popup = GIMP_TAG_POPUP (widget);
632 
633   if (event->type == GDK_BUTTON_PRESS)
634     {
635       GdkEventButton *button_event = (GdkEventButton *) event;
636       GtkAllocation   allocation;
637       gint            x;
638       gint            y;
639 
640       if (button_event->window == gtk_widget_get_window (widget) &&
641           gimp_tag_popup_button_scroll (popup, button_event))
642         {
643           return TRUE;
644         }
645 
646       gtk_widget_get_allocation (widget, &allocation);
647 
648       gdk_window_get_pointer (gtk_widget_get_window (widget), &x, &y, NULL);
649 
650       if (button_event->window != gtk_widget_get_window (popup->tag_area) &&
651           (x < allocation.y                    ||
652            y < allocation.x                    ||
653            x > allocation.x + allocation.width ||
654            y > allocation.y + allocation.height))
655         {
656           /* user has clicked outside the popup area,
657            * which means it should be hidden.
658            */
659           gtk_grab_remove (widget);
660           gdk_display_pointer_ungrab (gtk_widget_get_display (widget),
661                                       GDK_CURRENT_TIME);
662           gtk_widget_destroy (widget);
663         }
664     }
665   else if (event->type == GDK_MOTION_NOTIFY)
666     {
667       GdkEventMotion *motion_event = (GdkEventMotion *) event;
668       gint            x, y;
669 
670       gdk_window_get_pointer (gtk_widget_get_window (widget), &x, &y, NULL);
671 
672       gimp_tag_popup_handle_scrolling (popup, x, y,
673                                        motion_event->window ==
674                                        gtk_widget_get_window (widget),
675                                        TRUE);
676     }
677   else if (event->type == GDK_BUTTON_RELEASE)
678     {
679       GdkEventButton *button_event = (GdkEventButton *) event;
680 
681       popup->single_select_disabled = TRUE;
682 
683       if (button_event->window == gtk_widget_get_window (widget) &&
684           gimp_tag_popup_button_scroll (popup, button_event))
685         {
686           return TRUE;
687         }
688     }
689   else if (event->type == GDK_GRAB_BROKEN)
690     {
691       gtk_grab_remove (widget);
692       gdk_display_pointer_ungrab (gtk_widget_get_display (widget),
693                                   GDK_CURRENT_TIME);
694       gtk_widget_destroy (widget);
695     }
696   else if (event->type == GDK_KEY_PRESS)
697     {
698       gtk_grab_remove (widget);
699       gdk_display_pointer_ungrab (gtk_widget_get_display (widget),
700                                   GDK_CURRENT_TIME);
701       gtk_widget_destroy (widget);
702     }
703   else if (event->type == GDK_SCROLL)
704     {
705       GdkEventScroll *scroll_event = (GdkEventScroll *) event;
706 
707       switch (scroll_event->direction)
708         {
709         case GDK_SCROLL_RIGHT:
710         case GDK_SCROLL_DOWN:
711           gimp_tag_popup_scroll_by (popup, MENU_SCROLL_STEP2);
712           return TRUE;
713 
714         case GDK_SCROLL_LEFT:
715         case GDK_SCROLL_UP:
716           gimp_tag_popup_scroll_by (popup, - MENU_SCROLL_STEP2);
717           return TRUE;
718         }
719     }
720 
721   return FALSE;
722 }
723 
724 static gboolean
gimp_tag_popup_list_expose(GtkWidget * widget,GdkEventExpose * event,GimpTagPopup * popup)725 gimp_tag_popup_list_expose (GtkWidget      *widget,
726                             GdkEventExpose *event,
727                             GimpTagPopup   *popup)
728 {
729   GdkWindow      *window = gtk_widget_get_window (widget);
730   GtkStyle       *style  = gtk_widget_get_style (widget);
731   cairo_t        *cr;
732   PangoAttribute *attribute;
733   PangoAttrList  *attributes;
734   gint            i;
735 
736   cr = gdk_cairo_create (event->window);
737 
738   gdk_cairo_region (cr, event->region);
739   cairo_clip (cr);
740 
741   cairo_set_line_width (cr, 1.0);
742   cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
743 
744   for (i = 0; i < popup->tag_count; i++)
745     {
746       PopupTagData *tag_data = &popup->tag_data[i];
747 
748       pango_layout_set_text (popup->layout,
749                              gimp_tag_get_name (tag_data->tag), -1);
750 
751       switch (tag_data->state)
752         {
753         case GTK_STATE_SELECTED:
754           attributes = pango_attr_list_copy (popup->combo_entry->selected_item_attr);
755           break;
756 
757         case GTK_STATE_INSENSITIVE:
758           attributes = pango_attr_list_copy (popup->combo_entry->insensitive_item_attr);
759           break;
760 
761         default:
762           attributes = pango_attr_list_copy (popup->combo_entry->normal_item_attr);
763           break;
764         }
765 
766       if (tag_data == popup->prelight &&
767           tag_data->state != GTK_STATE_INSENSITIVE)
768         {
769           attribute = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
770           pango_attr_list_insert (attributes, attribute);
771         }
772 
773       pango_layout_set_attributes (popup->layout, attributes);
774       pango_attr_list_unref (attributes);
775 
776       if (tag_data->state == GTK_STATE_SELECTED)
777         {
778           gdk_cairo_set_source_color (cr,
779                                       &popup->combo_entry->selected_item_color);
780 
781           cairo_rectangle (cr,
782                            tag_data->bounds.x - 1,
783                            tag_data->bounds.y - popup->scroll_y,
784                            tag_data->bounds.width + 2,
785                            tag_data->bounds.height);
786           cairo_fill (cr);
787 
788           cairo_translate (cr, 0.5, 0.5);
789 
790           cairo_move_to (cr,
791                          tag_data->bounds.x,
792                          tag_data->bounds.y - popup->scroll_y - 1);
793           cairo_line_to (cr,
794                          tag_data->bounds.x + tag_data->bounds.width - 1,
795                          tag_data->bounds.y - popup->scroll_y - 1);
796 
797           cairo_move_to (cr,
798                          tag_data->bounds.x,
799                          tag_data->bounds.y - popup->scroll_y + tag_data->bounds.height);
800           cairo_line_to (cr,
801                          tag_data->bounds.x + tag_data->bounds.width - 1,
802                          tag_data->bounds.y - popup->scroll_y + tag_data->bounds.height);
803 
804           cairo_stroke (cr);
805 
806           cairo_translate (cr, -0.5, -0.5);
807         }
808 
809       cairo_move_to (cr,
810                      (tag_data->bounds.x +
811                       GIMP_TAG_POPUP_PADDING),
812                      (tag_data->bounds.y -
813                       popup->scroll_y +
814                       GIMP_TAG_POPUP_PADDING));
815 
816       pango_cairo_show_layout (cr, popup->layout);
817 
818       if (tag_data == popup->prelight              &&
819           tag_data->state != GTK_STATE_INSENSITIVE &&
820           ! popup->single_select_disabled)
821         {
822           gtk_paint_focus (style, window,
823                            tag_data->state,
824                            &event->area, widget, NULL,
825                            tag_data->bounds.x,
826                            tag_data->bounds.y - popup->scroll_y,
827                            tag_data->bounds.width,
828                            tag_data->bounds.height);
829         }
830     }
831 
832   cairo_destroy (cr);
833 
834   return FALSE;
835 }
836 
837 static gboolean
gimp_tag_popup_list_event(GtkWidget * widget,GdkEvent * event,GimpTagPopup * popup)838 gimp_tag_popup_list_event (GtkWidget    *widget,
839                            GdkEvent     *event,
840                            GimpTagPopup *popup)
841 {
842   if (event->type == GDK_BUTTON_PRESS)
843     {
844       GdkEventButton *button_event = (GdkEventButton *) event;
845       gint            x;
846       gint            y;
847       gint            i;
848 
849       popup->single_select_disabled = TRUE;
850 
851       x = button_event->x;
852       y = button_event->y + popup->scroll_y;
853 
854       for (i = 0; i < popup->tag_count; i++)
855         {
856           PopupTagData *tag_data = &popup->tag_data[i];
857 
858           if (gimp_tag_popup_is_in_tag (tag_data, x, y))
859             {
860               gimp_tag_popup_toggle_tag (popup, tag_data);
861               gtk_widget_queue_draw (widget);
862               break;
863             }
864         }
865     }
866   else if (event->type == GDK_MOTION_NOTIFY)
867     {
868       GdkEventMotion *motion_event = (GdkEventMotion *) event;
869       PopupTagData   *prelight     = NULL;
870       gint            x;
871       gint            y;
872       gint            i;
873 
874       x = motion_event->x;
875       y = motion_event->y + popup->scroll_y;
876 
877       for (i = 0; i < popup->tag_count; i++)
878         {
879           PopupTagData *tag_data = &popup->tag_data[i];
880 
881           if (gimp_tag_popup_is_in_tag (tag_data, x, y))
882             {
883               prelight = tag_data;
884               break;
885             }
886         }
887 
888       if (prelight != popup->prelight)
889         {
890           if (popup->prelight)
891             gimp_tag_popup_queue_draw_tag (popup, popup->prelight);
892 
893           popup->prelight = prelight;
894 
895           if (popup->prelight)
896             gimp_tag_popup_queue_draw_tag (popup, popup->prelight);
897         }
898     }
899   else if (event->type == GDK_BUTTON_RELEASE &&
900            ! popup->single_select_disabled)
901     {
902       GdkEventButton *button_event = (GdkEventButton *) event;
903       gint            x;
904       gint            y;
905       gint            i;
906 
907       popup->single_select_disabled = TRUE;
908 
909       x = button_event->x;
910       y = button_event->y + popup->scroll_y;
911 
912       for (i = 0; i < popup->tag_count; i++)
913         {
914           PopupTagData *tag_data = &popup->tag_data[i];
915 
916           if (gimp_tag_popup_is_in_tag (tag_data, x, y))
917             {
918               gimp_tag_popup_toggle_tag (popup, tag_data);
919               gtk_widget_destroy (GTK_WIDGET (popup));
920               break;
921             }
922         }
923     }
924 
925   return FALSE;
926 }
927 
928 static gboolean
gimp_tag_popup_is_in_tag(PopupTagData * tag_data,gint x,gint y)929 gimp_tag_popup_is_in_tag (PopupTagData *tag_data,
930                           gint          x,
931                           gint          y)
932 {
933   if (x >= tag_data->bounds.x                          &&
934       y >= tag_data->bounds.y                          &&
935       x <  tag_data->bounds.x + tag_data->bounds.width &&
936       y <  tag_data->bounds.y + tag_data->bounds.height)
937     {
938       return TRUE;
939     }
940 
941   return FALSE;
942 }
943 
944 static void
gimp_tag_popup_queue_draw_tag(GimpTagPopup * popup,PopupTagData * tag_data)945 gimp_tag_popup_queue_draw_tag (GimpTagPopup *popup,
946                                PopupTagData *tag_data)
947 {
948   gtk_widget_queue_draw_area (popup->tag_area,
949                               tag_data->bounds.x,
950                               tag_data->bounds.y - popup->scroll_y,
951                               tag_data->bounds.width,
952                               tag_data->bounds.height);
953 }
954 
955 static void
gimp_tag_popup_toggle_tag(GimpTagPopup * popup,PopupTagData * tag_data)956 gimp_tag_popup_toggle_tag (GimpTagPopup *popup,
957                            PopupTagData *tag_data)
958 {
959   gchar    **current_tags;
960   GString   *tag_str;
961   gint       length;
962   gint       i;
963   gboolean   tag_toggled_off = FALSE;
964 
965   if (tag_data->state == GTK_STATE_NORMAL)
966     {
967       tag_data->state = GTK_STATE_SELECTED;
968     }
969   else if (tag_data->state == GTK_STATE_SELECTED)
970     {
971       tag_data->state = GTK_STATE_NORMAL;
972     }
973   else
974     {
975       return;
976     }
977 
978   current_tags = gimp_tag_entry_parse_tags (GIMP_TAG_ENTRY (popup->combo_entry));
979   tag_str = g_string_new ("");
980   length = g_strv_length (current_tags);
981   for (i = 0; i < length; i++)
982     {
983       if (! gimp_tag_compare_with_string (tag_data->tag, current_tags[i]))
984         {
985           tag_toggled_off = TRUE;
986         }
987       else
988         {
989           if (tag_str->len)
990             {
991               g_string_append (tag_str, gimp_tag_entry_get_separator ());
992               g_string_append_c (tag_str, ' ');
993             }
994 
995           g_string_append (tag_str, current_tags[i]);
996         }
997     }
998 
999   if (! tag_toggled_off)
1000     {
1001       /* this tag was not selected yet, so it needs to be toggled on */
1002 
1003       if (tag_str->len)
1004         {
1005           g_string_append (tag_str, gimp_tag_entry_get_separator ());
1006           g_string_append_c (tag_str, ' ');
1007         }
1008 
1009       g_string_append (tag_str, gimp_tag_get_name (tag_data->tag));
1010     }
1011 
1012   gimp_tag_entry_set_tag_string (GIMP_TAG_ENTRY (popup->combo_entry),
1013                                  tag_str->str);
1014 
1015   g_string_free (tag_str, TRUE);
1016   g_strfreev (current_tags);
1017 
1018   if (GIMP_TAG_ENTRY (popup->combo_entry)->mode == GIMP_TAG_ENTRY_MODE_QUERY)
1019     {
1020       GimpTaggedContainer *container;
1021 
1022       container = GIMP_TAG_ENTRY (popup->combo_entry)->container;
1023 
1024       for (i = 0; i < popup->tag_count; i++)
1025         {
1026           if (popup->tag_data[i].state != GTK_STATE_SELECTED)
1027             {
1028               popup->tag_data[i].state = GTK_STATE_INSENSITIVE;
1029             }
1030         }
1031 
1032       gimp_container_foreach (GIMP_CONTAINER (container),
1033                               (GFunc) gimp_tag_popup_check_can_toggle,
1034                               popup);
1035     }
1036 }
1037 
1038 static int
gimp_tag_popup_data_compare(const void * a,const void * b)1039 gimp_tag_popup_data_compare (const void *a,
1040                              const void *b)
1041 {
1042   return gimp_tag_compare_func (((PopupTagData *) a)->tag,
1043                                 ((PopupTagData *) b)->tag);
1044 }
1045 
1046 static void
gimp_tag_popup_check_can_toggle(GimpTagged * tagged,GimpTagPopup * popup)1047 gimp_tag_popup_check_can_toggle (GimpTagged   *tagged,
1048                                  GimpTagPopup *popup)
1049 {
1050   GList *iterator;
1051 
1052   for (iterator = gimp_tagged_get_tags (tagged);
1053        iterator;
1054        iterator = g_list_next (iterator))
1055     {
1056       PopupTagData  search_key;
1057       PopupTagData *search_result;
1058 
1059       search_key.tag = iterator->data;
1060 
1061       search_result =
1062         (PopupTagData *) bsearch (&search_key,
1063                                   popup->tag_data, popup->tag_count,
1064                                   sizeof (PopupTagData),
1065                                   gimp_tag_popup_data_compare);
1066 
1067       if (search_result)
1068         {
1069           if (search_result->state == GTK_STATE_INSENSITIVE)
1070             {
1071               search_result->state = GTK_STATE_NORMAL;
1072             }
1073         }
1074     }
1075 }
1076 
1077 static gboolean
gimp_tag_popup_scroll_timeout(gpointer data)1078 gimp_tag_popup_scroll_timeout (gpointer data)
1079 {
1080   GimpTagPopup *popup = data;
1081   gboolean      touchscreen_mode;
1082 
1083   g_object_get (gtk_widget_get_settings (GTK_WIDGET (popup)),
1084                 "gtk-touchscreen-mode", &touchscreen_mode,
1085                 NULL);
1086 
1087   gimp_tag_popup_scroll_by (popup, popup->scroll_step);
1088 
1089   return TRUE;
1090 }
1091 
1092 static void
gimp_tag_popup_remove_scroll_timeout(GimpTagPopup * popup)1093 gimp_tag_popup_remove_scroll_timeout (GimpTagPopup *popup)
1094 {
1095   if (popup->scroll_timeout_id)
1096     {
1097       g_source_remove (popup->scroll_timeout_id);
1098       popup->scroll_timeout_id = 0;
1099     }
1100 }
1101 
1102 static gboolean
gimp_tag_popup_scroll_timeout_initial(gpointer data)1103 gimp_tag_popup_scroll_timeout_initial (gpointer data)
1104 {
1105   GimpTagPopup *popup = data;
1106   guint         timeout;
1107   gboolean      touchscreen_mode;
1108 
1109   g_object_get (gtk_widget_get_settings (GTK_WIDGET (popup)),
1110                 "gtk-timeout-repeat",   &timeout,
1111                 "gtk-touchscreen-mode", &touchscreen_mode,
1112                 NULL);
1113 
1114   gimp_tag_popup_scroll_by (popup, popup->scroll_step);
1115 
1116   gimp_tag_popup_remove_scroll_timeout (popup);
1117 
1118   popup->scroll_timeout_id =
1119     gdk_threads_add_timeout (timeout,
1120                              gimp_tag_popup_scroll_timeout,
1121                              popup);
1122 
1123   return FALSE;
1124 }
1125 
1126 static void
gimp_tag_popup_start_scrolling(GimpTagPopup * popup)1127 gimp_tag_popup_start_scrolling (GimpTagPopup *popup)
1128 {
1129   guint    timeout;
1130   gboolean touchscreen_mode;
1131 
1132   g_object_get (gtk_widget_get_settings (GTK_WIDGET (popup)),
1133                 "gtk-timeout-repeat",   &timeout,
1134                 "gtk-touchscreen-mode", &touchscreen_mode,
1135                 NULL);
1136 
1137   gimp_tag_popup_scroll_by (popup, popup->scroll_step);
1138 
1139   popup->scroll_timeout_id =
1140     gdk_threads_add_timeout (timeout,
1141                              gimp_tag_popup_scroll_timeout_initial,
1142                              popup);
1143 }
1144 
1145 static void
gimp_tag_popup_stop_scrolling(GimpTagPopup * popup)1146 gimp_tag_popup_stop_scrolling (GimpTagPopup *popup)
1147 {
1148   gboolean touchscreen_mode;
1149 
1150   gimp_tag_popup_remove_scroll_timeout (popup);
1151 
1152   g_object_get (gtk_widget_get_settings (GTK_WIDGET (popup)),
1153                 "gtk-touchscreen-mode", &touchscreen_mode,
1154                 NULL);
1155 
1156   if (! touchscreen_mode)
1157     {
1158       popup->upper_arrow_prelight = FALSE;
1159       popup->lower_arrow_prelight = FALSE;
1160     }
1161 }
1162 
1163 static void
gimp_tag_popup_scroll_by(GimpTagPopup * popup,gint step)1164 gimp_tag_popup_scroll_by (GimpTagPopup *popup,
1165                           gint          step)
1166 {
1167   GtkStateType arrow_state;
1168   gint         new_scroll_y = popup->scroll_y + step;
1169 
1170   arrow_state = popup->upper_arrow_state;
1171 
1172   if (new_scroll_y < 0)
1173     {
1174       new_scroll_y = 0;
1175 
1176       if (arrow_state != GTK_STATE_INSENSITIVE)
1177         gimp_tag_popup_stop_scrolling (popup);
1178 
1179       arrow_state = GTK_STATE_INSENSITIVE;
1180     }
1181   else
1182     {
1183       arrow_state = (popup->upper_arrow_prelight ?
1184                      GTK_STATE_PRELIGHT : GTK_STATE_NORMAL);
1185     }
1186 
1187   if (arrow_state != popup->upper_arrow_state)
1188     {
1189       popup->upper_arrow_state = arrow_state;
1190       gtk_widget_queue_draw (GTK_WIDGET (popup));
1191     }
1192 
1193   arrow_state = popup->lower_arrow_state;
1194 
1195   if (new_scroll_y >= popup->scroll_height)
1196     {
1197       new_scroll_y = popup->scroll_height - 1;
1198 
1199       if (arrow_state != GTK_STATE_INSENSITIVE)
1200         gimp_tag_popup_stop_scrolling (popup);
1201 
1202       arrow_state = GTK_STATE_INSENSITIVE;
1203     }
1204   else
1205     {
1206       arrow_state = (popup->lower_arrow_prelight ?
1207                      GTK_STATE_PRELIGHT : GTK_STATE_NORMAL);
1208     }
1209 
1210   if (arrow_state != popup->lower_arrow_state)
1211     {
1212       popup->lower_arrow_state = arrow_state;
1213       gtk_widget_queue_draw (GTK_WIDGET (popup));
1214     }
1215 
1216   if (new_scroll_y != popup->scroll_y)
1217     {
1218       popup->scroll_y = new_scroll_y;
1219 
1220       gdk_window_scroll (gtk_widget_get_window (popup->tag_area), 0, -step);
1221     }
1222 }
1223 
1224 static void
gimp_tag_popup_handle_scrolling(GimpTagPopup * popup,gint x,gint y,gboolean enter,gboolean motion)1225 gimp_tag_popup_handle_scrolling (GimpTagPopup *popup,
1226                                  gint          x,
1227                                  gint          y,
1228                                  gboolean      enter,
1229                                  gboolean      motion)
1230 {
1231   GdkRectangle rect;
1232   gboolean     in_arrow;
1233   gboolean     scroll_fast = FALSE;
1234   gboolean     touchscreen_mode;
1235 
1236   g_object_get (gtk_widget_get_settings (GTK_WIDGET (popup)),
1237                 "gtk-touchscreen-mode", &touchscreen_mode,
1238                 NULL);
1239 
1240   /*  upper arrow handling  */
1241 
1242   get_arrows_sensitive_area (popup, &rect, NULL);
1243 
1244   in_arrow = FALSE;
1245   if (popup->arrows_visible    &&
1246       x >= rect.x              &&
1247       x <  rect.x + rect.width &&
1248       y >= rect.y              &&
1249       y <  rect.y + rect.height)
1250     {
1251       in_arrow = TRUE;
1252     }
1253 
1254   if (touchscreen_mode)
1255     popup->upper_arrow_prelight = in_arrow;
1256 
1257   if (popup->upper_arrow_state != GTK_STATE_INSENSITIVE)
1258     {
1259       gboolean arrow_pressed = FALSE;
1260 
1261       if (popup->arrows_visible)
1262         {
1263           if (touchscreen_mode)
1264             {
1265               if (enter && popup->upper_arrow_prelight)
1266                 {
1267                   if (popup->scroll_timeout_id == 0)
1268                     {
1269                       gimp_tag_popup_remove_scroll_timeout (popup);
1270                       popup->scroll_step = -MENU_SCROLL_STEP2; /* always fast */
1271 
1272                       if (! motion)
1273                         {
1274                           /* Only do stuff on click. */
1275                           gimp_tag_popup_start_scrolling (popup);
1276                           arrow_pressed = TRUE;
1277                         }
1278                     }
1279                   else
1280                     {
1281                       arrow_pressed = TRUE;
1282                     }
1283                 }
1284               else if (! enter)
1285                 {
1286                   gimp_tag_popup_stop_scrolling (popup);
1287                 }
1288             }
1289           else /* !touchscreen_mode */
1290             {
1291               scroll_fast = (y < rect.y + MENU_SCROLL_FAST_ZONE);
1292 
1293               if (enter && in_arrow &&
1294                   (! popup->upper_arrow_prelight ||
1295                    popup->scroll_fast != scroll_fast))
1296                 {
1297                   popup->upper_arrow_prelight = TRUE;
1298                   popup->scroll_fast          = scroll_fast;
1299 
1300                   gimp_tag_popup_remove_scroll_timeout (popup);
1301                   popup->scroll_step = (scroll_fast ?
1302                                         -MENU_SCROLL_STEP2 : -MENU_SCROLL_STEP1);
1303 
1304                   popup->scroll_timeout_id =
1305                     gdk_threads_add_timeout (scroll_fast ?
1306                                              MENU_SCROLL_TIMEOUT2 :
1307                                              MENU_SCROLL_TIMEOUT1,
1308                                              gimp_tag_popup_scroll_timeout,
1309                                              popup);
1310                 }
1311               else if (! enter && ! in_arrow && popup->upper_arrow_prelight)
1312                 {
1313                   gimp_tag_popup_stop_scrolling (popup);
1314                 }
1315             }
1316         }
1317 
1318       /*  gimp_tag_popup_start_scrolling() might have hit the top of the
1319        *  tag_popup, so check if the button isn't insensitive before
1320        *  changing it to something else.
1321        */
1322       if (popup->upper_arrow_state != GTK_STATE_INSENSITIVE)
1323         {
1324           GtkStateType arrow_state = GTK_STATE_NORMAL;
1325 
1326           if (arrow_pressed)
1327             arrow_state = GTK_STATE_ACTIVE;
1328           else if (popup->upper_arrow_prelight)
1329             arrow_state = GTK_STATE_PRELIGHT;
1330 
1331           if (arrow_state != popup->upper_arrow_state)
1332             {
1333               popup->upper_arrow_state = arrow_state;
1334 
1335               gdk_window_invalidate_rect (gtk_widget_get_window (GTK_WIDGET (popup)),
1336                                           &rect, FALSE);
1337             }
1338         }
1339     }
1340 
1341   /*  lower arrow handling  */
1342 
1343   get_arrows_sensitive_area (popup, NULL, &rect);
1344 
1345   in_arrow = FALSE;
1346   if (popup->arrows_visible    &&
1347       x >= rect.x              &&
1348       x <  rect.x + rect.width &&
1349       y >= rect.y              &&
1350       y <  rect.y + rect.height)
1351     {
1352       in_arrow = TRUE;
1353     }
1354 
1355   if (touchscreen_mode)
1356     popup->lower_arrow_prelight = in_arrow;
1357 
1358   if (popup->lower_arrow_state != GTK_STATE_INSENSITIVE)
1359     {
1360       gboolean arrow_pressed = FALSE;
1361 
1362       if (popup->arrows_visible)
1363         {
1364           if (touchscreen_mode)
1365             {
1366               if (enter && popup->lower_arrow_prelight)
1367                 {
1368                   if (popup->scroll_timeout_id == 0)
1369                     {
1370                       gimp_tag_popup_remove_scroll_timeout (popup);
1371                       popup->scroll_step = MENU_SCROLL_STEP2; /* always fast */
1372 
1373                       if (! motion)
1374                         {
1375                           /* Only do stuff on click. */
1376                           gimp_tag_popup_start_scrolling (popup);
1377                           arrow_pressed = TRUE;
1378                         }
1379                     }
1380                   else
1381                     {
1382                       arrow_pressed = TRUE;
1383                     }
1384                 }
1385               else if (! enter)
1386                 {
1387                   gimp_tag_popup_stop_scrolling (popup);
1388                 }
1389             }
1390           else /* !touchscreen_mode */
1391             {
1392               scroll_fast = (y > rect.y + rect.height - MENU_SCROLL_FAST_ZONE);
1393 
1394               if (enter && in_arrow &&
1395                   (! popup->lower_arrow_prelight ||
1396                    popup->scroll_fast != scroll_fast))
1397                 {
1398                   popup->lower_arrow_prelight = TRUE;
1399                   popup->scroll_fast          = scroll_fast;
1400 
1401                   gimp_tag_popup_remove_scroll_timeout (popup);
1402                   popup->scroll_step = (scroll_fast ?
1403                                         MENU_SCROLL_STEP2 : MENU_SCROLL_STEP1);
1404 
1405                   popup->scroll_timeout_id =
1406                     gdk_threads_add_timeout (scroll_fast ?
1407                                              MENU_SCROLL_TIMEOUT2 :
1408                                              MENU_SCROLL_TIMEOUT1,
1409                                              gimp_tag_popup_scroll_timeout,
1410                                              popup);
1411                 }
1412               else if (! enter && ! in_arrow && popup->lower_arrow_prelight)
1413                 {
1414                   gimp_tag_popup_stop_scrolling (popup);
1415                 }
1416             }
1417         }
1418 
1419       /*  gimp_tag_popup_start_scrolling() might have hit the bottom of the
1420        *  popup, so check if the button isn't insensitive before
1421        *  changing it to something else.
1422        */
1423       if (popup->lower_arrow_state != GTK_STATE_INSENSITIVE)
1424         {
1425           GtkStateType arrow_state = GTK_STATE_NORMAL;
1426 
1427           if (arrow_pressed)
1428             arrow_state = GTK_STATE_ACTIVE;
1429           else if (popup->lower_arrow_prelight)
1430             arrow_state = GTK_STATE_PRELIGHT;
1431 
1432           if (arrow_state != popup->lower_arrow_state)
1433             {
1434               popup->lower_arrow_state = arrow_state;
1435 
1436               gdk_window_invalidate_rect (gtk_widget_get_window (GTK_WIDGET (popup)),
1437                                           &rect, FALSE);
1438             }
1439         }
1440     }
1441 }
1442 
1443 static gboolean
gimp_tag_popup_button_scroll(GimpTagPopup * popup,GdkEventButton * event)1444 gimp_tag_popup_button_scroll (GimpTagPopup   *popup,
1445                               GdkEventButton *event)
1446 {
1447   if (popup->upper_arrow_prelight || popup->lower_arrow_prelight)
1448     {
1449       gboolean touchscreen_mode;
1450 
1451       g_object_get (gtk_widget_get_settings (GTK_WIDGET (popup)),
1452                     "gtk-touchscreen-mode", &touchscreen_mode,
1453                     NULL);
1454 
1455       if (touchscreen_mode)
1456         gimp_tag_popup_handle_scrolling (popup,
1457                                          event->x_root,
1458                                          event->y_root,
1459                                          event->type == GDK_BUTTON_PRESS,
1460                                          FALSE);
1461 
1462       return TRUE;
1463     }
1464 
1465   return FALSE;
1466 }
1467 
1468 static void
get_arrows_visible_area(GimpTagPopup * popup,GdkRectangle * border,GdkRectangle * upper,GdkRectangle * lower,gint * arrow_space)1469 get_arrows_visible_area (GimpTagPopup *popup,
1470                          GdkRectangle *border,
1471                          GdkRectangle *upper,
1472                          GdkRectangle *lower,
1473                          gint         *arrow_space)
1474 {
1475   GtkWidget *widget = GTK_WIDGET (popup->alignment);
1476   guint      padding_top;
1477   guint      padding_bottom;
1478   guint      padding_left;
1479   guint      padding_right;
1480 
1481   gtk_alignment_get_padding (GTK_ALIGNMENT (popup->alignment),
1482                              &padding_top, &padding_bottom,
1483                              &padding_left, &padding_right);
1484 
1485   gtk_widget_get_allocation (widget, border);
1486 
1487   upper->x      = border->x + padding_left;
1488   upper->y      = border->y;
1489   upper->width  = border->width - padding_left - padding_right;
1490   upper->height = padding_top;
1491 
1492   lower->x      = border->x + padding_left;
1493   lower->y      = border->y + border->height - padding_bottom;
1494   lower->width  = border->width - padding_left - padding_right;
1495   lower->height = padding_bottom;
1496 
1497   *arrow_space = popup->scroll_arrow_height;
1498 }
1499 
1500 static void
get_arrows_sensitive_area(GimpTagPopup * popup,GdkRectangle * upper,GdkRectangle * lower)1501 get_arrows_sensitive_area (GimpTagPopup *popup,
1502                            GdkRectangle *upper,
1503                            GdkRectangle *lower)
1504 {
1505   GdkRectangle tmp_border;
1506   GdkRectangle tmp_upper;
1507   GdkRectangle tmp_lower;
1508   gint         tmp_arrow_space;
1509 
1510   get_arrows_visible_area (popup,
1511                            &tmp_border, &tmp_upper, &tmp_lower, &tmp_arrow_space);
1512 
1513   if (upper)
1514     *upper = tmp_upper;
1515 
1516   if (lower)
1517     *lower = tmp_lower;
1518 }
1519