source: trunk/third/gnome-core/panel/scroll-menu.c @ 17152

Revision 17152, 17.1 KB checked in by ghudson, 23 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r17151, which included commits to RCS files with non-trunk default branches.
Line 
1/* Scroll menu: a scrolling menu (if it's beyond screen size)
2 * (C) 2000  Eazel, Inc.
3 *
4 * Authors:  George Lebl
5 */
6
7#include <gtk/gtk.h>
8
9#include "scroll-menu.h"
10
11static void scroll_menu_class_init      (ScrollMenuClass  *klass);
12static void scroll_menu_init            (ScrollMenu       *self);
13static void scroll_menu_size_request    (GtkWidget        *widget,
14                                         GtkRequisition   *requisition);
15static void scroll_menu_size_allocate   (GtkWidget        *widget,
16                                         GtkAllocation    *allocation);
17static void scroll_menu_map             (GtkWidget        *widget);
18static void scroll_menu_unmap           (GtkWidget        *widget);
19static void scroll_menu_destroy         (GtkObject        *object);
20static void scroll_menu_move_current    (GtkMenuShell     *menu_shell,
21                                         GtkMenuDirectionType direction);
22static void scroll_menu_remove          (GtkContainer     *container,
23                                         GtkWidget        *widget);
24static gboolean scroll_menu_enter_notify(GtkWidget        *widget,
25                                         GdkEventCrossing *event);
26static gboolean scroll_menu_leave_notify(GtkWidget        *widget,
27                                         GdkEventCrossing *event);
28
29static GtkMenuClass *parent_class = NULL;
30
31enum {
32        SCROLL_UP,
33        SCROLL_DOWN
34};
35
36#define SCROLLER_HEIGHT 15
37#define SCROLL_BY 15
38#define SCROLL_TIMEOUT 150
39#define ARROW_PAD 3
40
41GtkType
42scroll_menu_get_type (void)
43{
44        static GtkType scroll_menu_type = 0;
45
46        if (scroll_menu_type == 0) {
47                GtkType menu_type;
48
49                GtkTypeInfo scroll_menu_info = {
50                        "ScrollMenu",
51                        sizeof (ScrollMenu),
52                        sizeof (ScrollMenuClass),
53                        (GtkClassInitFunc) scroll_menu_class_init,
54                        (GtkObjectInitFunc) scroll_menu_init,
55                        (GtkArgSetFunc) NULL,
56                        (GtkArgGetFunc) NULL,
57                        NULL
58                };
59
60                menu_type = gtk_menu_get_type ();
61
62                scroll_menu_type = gtk_type_unique (menu_type,
63                                                    &scroll_menu_info);
64                parent_class = gtk_type_class (menu_type);
65        }
66
67        return scroll_menu_type;
68}
69
70static void
71scroll_menu_class_init (ScrollMenuClass *klass)
72{
73        GtkObjectClass *object_class = (GtkObjectClass*) klass;
74        GtkWidgetClass *widget_class = (GtkWidgetClass*) klass;
75        GtkContainerClass *container_class = (GtkContainerClass*) klass;
76        GtkMenuShellClass *menushell_class = (GtkMenuShellClass*) klass;
77       
78        widget_class->size_request = scroll_menu_size_request;
79        widget_class->size_allocate = scroll_menu_size_allocate;
80        widget_class->map = scroll_menu_map;
81        widget_class->unmap = scroll_menu_unmap;
82        widget_class->enter_notify_event = scroll_menu_enter_notify;
83        widget_class->leave_notify_event = scroll_menu_leave_notify;
84
85        menushell_class->move_current = scroll_menu_move_current;
86
87        container_class->remove = scroll_menu_remove;
88
89        object_class->destroy = scroll_menu_destroy;
90}
91
92static void
93scroll_by (ScrollMenu *self, int move)
94{
95        GtkMenuShell *menu_shell = GTK_MENU_SHELL (self);
96        GtkAllocation allocation;
97        GList *children;
98        GtkWidget *child;
99
100        if (self->offset + move < 0) {
101                move = - self->offset;
102        } else if (self->offset + move > self->max_offset) {
103                move = self->max_offset - self->offset;
104        }
105
106        if (move == 0)
107                return;
108
109        self->offset += move;
110
111        children = menu_shell->children;
112        while (children) {
113                child = children->data;
114                children = children->next;
115
116                if (GTK_WIDGET_VISIBLE (child)) {
117                        allocation = child->allocation;
118                        allocation.y -= move;
119
120                        gtk_widget_size_allocate (child, &allocation);
121                        gtk_widget_queue_draw (child);
122                }
123        }
124}
125
126static gboolean
127scroll_timeout (gpointer data)
128{
129        ScrollMenu *self = SCROLL_MENU (data);
130        GtkMenuShell *menu_shell = GTK_MENU_SHELL (self);
131
132        if (self->scroll_by == 0 ||
133            menu_shell->children == NULL) {
134                return TRUE;
135        }
136
137        scroll_by (self, self->scroll_by);
138
139        return TRUE;
140}
141
142static void
143scroll_draw (GtkWidget *scroll, gboolean active, int direction)
144{
145        GtkArrowType arrow_type;
146
147        if( ! GTK_WIDGET_DRAWABLE(scroll))
148                return;
149
150        gdk_window_clear_area (scroll->window,
151                               0, 0,
152                               scroll->allocation.width,
153                               scroll->allocation.height);
154
155        gtk_draw_shadow (scroll->style,
156                         scroll->window,
157                         scroll->state,
158                         active ? GTK_SHADOW_IN : GTK_SHADOW_OUT,
159                         0, 0,
160                         scroll->allocation.width,
161                         scroll->allocation.height);
162
163        if (direction == SCROLL_UP) {
164                arrow_type = GTK_ARROW_UP;
165        } else {
166                arrow_type = GTK_ARROW_DOWN;
167        }
168
169        gtk_draw_arrow (scroll->style,
170                        scroll->window,
171                        scroll->state,
172                        active ? GTK_SHADOW_IN : GTK_SHADOW_OUT,
173                        arrow_type,
174                        TRUE,
175                        (scroll->allocation.width / 2) -
176                          SCROLLER_HEIGHT / 2 - ARROW_PAD,
177                        ARROW_PAD,
178                        SCROLLER_HEIGHT - ARROW_PAD * 2,
179                        SCROLLER_HEIGHT - ARROW_PAD * 2);
180}
181
182static gboolean
183scroll_expose (GtkWidget *scroll, GdkEventExpose *event, gpointer data)
184{
185        ScrollMenu *self = SCROLL_MENU (gtk_object_get_user_data (GTK_OBJECT (scroll)));
186        int direction = GPOINTER_TO_INT (data);
187        gboolean active = FALSE;
188
189        if (direction == SCROLL_UP) {
190                active = self->in_up;
191        } else {
192                active = self->in_down;
193        }
194
195        scroll_draw (scroll, active, direction);
196       
197        return FALSE;
198}
199
200static void
201scroll_realize (GtkWidget *scroll, gpointer data)
202{
203        ScrollMenu *self = SCROLL_MENU (gtk_object_get_user_data (GTK_OBJECT (scroll)));
204        int direction = GPOINTER_TO_INT (data);
205        gboolean active = FALSE;
206
207        if (direction == SCROLL_UP) {
208                active = self->in_up;
209        } else {
210                active = self->in_down;
211        }
212
213        scroll_draw (scroll, active, direction);
214}
215
216static gboolean
217scroll_enter_notify (GtkWidget *scroll, GdkEventCrossing *event, gpointer data)
218{
219        ScrollMenu *self = SCROLL_MENU (gtk_object_get_user_data (GTK_OBJECT (scroll)));
220        int direction = GPOINTER_TO_INT (data);
221
222        if (direction == SCROLL_UP) {
223                self->in_up = TRUE;
224                self->scroll_by = -(SCROLL_BY);
225        } else {
226                self->in_down = TRUE;
227                self->scroll_by = SCROLL_BY;
228        }
229
230        gtk_widget_queue_draw (scroll);
231
232        if (self->scroll_timeout == 0) {
233                self->scroll_timeout = gtk_timeout_add (SCROLL_TIMEOUT,
234                                                        scroll_timeout,
235                                                        self);
236        }
237       
238        return FALSE;
239}
240
241static gboolean
242scroll_leave_notify (GtkWidget *scroll, GdkEventCrossing *event, gpointer data)
243{
244        ScrollMenu *self = SCROLL_MENU (gtk_object_get_user_data (GTK_OBJECT (scroll)));
245        int direction = GPOINTER_TO_INT (data);
246
247        self->scroll_by = 0;
248
249        if (direction == SCROLL_UP) {
250                self->in_up = FALSE;
251        } else {
252                self->in_down = FALSE;
253        }
254
255        gtk_widget_queue_draw (scroll);
256
257        if (self->scroll_timeout != 0) {
258                gtk_timeout_remove (self->scroll_timeout);
259                self->scroll_timeout = 0;
260        }
261       
262        return FALSE;
263}
264
265static GtkWidget *
266make_scroller (ScrollMenu *self, int direction)
267{
268        GtkWidget *scroll;
269
270        scroll = gtk_drawing_area_new ();
271        gtk_widget_set_events(scroll,
272                              gtk_widget_get_events(scroll) |
273                              GDK_BUTTON_PRESS_MASK |
274                              GDK_ENTER_NOTIFY_MASK |
275                              GDK_LEAVE_NOTIFY_MASK);
276        gtk_widget_set_usize (scroll, 0, SCROLLER_HEIGHT);
277        gtk_widget_show (scroll);
278
279        gtk_object_set_user_data (GTK_OBJECT (scroll), self);
280
281        gtk_signal_connect (GTK_OBJECT (scroll), "enter_notify_event",
282                            GTK_SIGNAL_FUNC (scroll_enter_notify),
283                            GINT_TO_POINTER (direction));
284        gtk_signal_connect (GTK_OBJECT (scroll), "leave_notify_event",
285                            GTK_SIGNAL_FUNC (scroll_leave_notify),
286                            GINT_TO_POINTER (direction));
287
288        gtk_signal_connect_after(GTK_OBJECT(scroll), "realize",
289                                 GTK_SIGNAL_FUNC(scroll_realize),
290                                 GINT_TO_POINTER (direction));
291        gtk_signal_connect(GTK_OBJECT(scroll), "expose_event",
292                           GTK_SIGNAL_FUNC(scroll_expose),
293                           GINT_TO_POINTER (direction));
294
295        return scroll;
296}
297
298static void
299scroll_menu_init (ScrollMenu *self)
300{
301        self->screen_top = 0;
302        self->screen_bottom = gdk_screen_height ();
303
304        self->up_scroll = make_scroller (self, SCROLL_UP);
305        gtk_widget_ref (self->up_scroll);
306        gtk_object_sink (GTK_OBJECT (self->up_scroll));
307        gtk_widget_set_parent (self->up_scroll, GTK_WIDGET(self));
308
309        self->down_scroll = make_scroller (self, SCROLL_DOWN);
310        gtk_widget_ref (self->down_scroll);
311        gtk_object_sink (GTK_OBJECT (self->up_scroll));
312        gtk_widget_set_parent (self->down_scroll, GTK_WIDGET(self));
313
314        self->scroll = FALSE;
315
316        self->offset = 0;
317        self->max_offset = 0;
318        self->scroll_by = 0;
319        self->scroll_timeout = 0;
320}
321
322static void
323scroll_menu_destroy (GtkObject *object)
324{
325        ScrollMenu *self = SCROLL_MENU (object);
326
327        if (self->up_scroll != NULL) {
328                /* this should remove the widget and thus cause
329                 * a NULL and an unref */
330                gtk_widget_destroy (self->up_scroll);
331                g_assert (self->up_scroll == NULL);
332        }
333
334        if (self->down_scroll != NULL) {
335                /* this should remove the widget and thus cause
336                 * a NULL and an unref */
337                gtk_widget_destroy (self->down_scroll);
338                g_assert (self->down_scroll == NULL);
339        }
340
341        if (self->scroll_timeout != 0) {
342                gtk_timeout_remove (self->scroll_timeout);
343                self->scroll_timeout = 0;
344        }
345
346        if (GTK_OBJECT_CLASS (parent_class)->destroy)
347                GTK_OBJECT_CLASS (parent_class)->destroy (object);
348}
349
350static void
351scroll_menu_size_request (GtkWidget *widget,
352                          GtkRequisition *requisition)
353{
354        ScrollMenu *self = SCROLL_MENU (widget);
355        GtkRequisition child_requisition;
356        int screen_height;
357
358        if (GTK_WIDGET_CLASS (parent_class)->size_request)
359                GTK_WIDGET_CLASS (parent_class)->size_request (widget,
360                                                               requisition);
361
362        screen_height = self->screen_bottom - self->screen_top;
363
364        if (requisition->height > screen_height) {
365                int button_height;
366
367                button_height = 0;
368                gtk_widget_size_request (self->up_scroll, &child_requisition);
369                button_height += child_requisition.height;
370                gtk_widget_size_request (self->down_scroll, &child_requisition);
371                button_height += child_requisition.height;
372
373                self->max_offset =
374                        (requisition->height - screen_height) + button_height;
375
376                if (self->offset > self->max_offset)
377                        self->offset = self->max_offset;
378
379                requisition->height = screen_height;
380                self->scroll = TRUE;
381        } else {
382                self->offset = 0;
383                self->max_offset = 0;
384                self->scroll = FALSE;
385        }
386}
387
388static void
389scroll_menu_size_allocate (GtkWidget *widget,
390                           GtkAllocation *allocation)
391{
392        ScrollMenu *self;
393        GtkMenu *menu;
394        GtkMenuShell *menu_shell;
395        GtkWidget *child;
396        GtkAllocation child_allocation;
397        GtkRequisition child_requisition;
398        GList *children;
399        int top_scroller_height;
400
401        g_return_if_fail (widget != NULL);
402        g_return_if_fail (IS_SCROLL_MENU (widget));
403        g_return_if_fail (allocation != NULL);
404
405        self = SCROLL_MENU (widget);
406        menu = GTK_MENU (widget);
407        menu_shell = GTK_MENU_SHELL (widget);
408
409        widget->allocation = *allocation;
410        if (GTK_WIDGET_REALIZED (widget)) {
411                gdk_window_move_resize (widget->window,
412                                        allocation->x, allocation->y,
413                                        allocation->width, allocation->height);
414        }
415
416        top_scroller_height = 0;
417
418        if (self->scroll) {
419                if (GTK_WIDGET_MAPPED (widget) &&
420                    ! GTK_WIDGET_MAPPED (self->up_scroll))
421                        gtk_widget_map (self->up_scroll);
422                if (self->up_scroll->window)
423                        gdk_window_raise (self->up_scroll->window);
424
425                if (GTK_WIDGET_MAPPED (widget) &&
426                    ! GTK_WIDGET_MAPPED (self->down_scroll))
427                        gtk_widget_map (self->down_scroll);
428                if (self->down_scroll->window)
429                        gdk_window_raise (self->down_scroll->window);
430
431                gtk_widget_get_child_requisition (self->up_scroll, &child_requisition);
432                child_allocation.x = 0;
433                child_allocation.y = 0;
434                child_allocation.width = allocation->width;
435                child_allocation.height = child_requisition.height;
436                gtk_widget_size_allocate (self->up_scroll, &child_allocation);
437
438                top_scroller_height = child_requisition.height;
439
440                gtk_widget_get_child_requisition (self->down_scroll, &child_requisition);
441                child_allocation.x = 0;
442                child_allocation.y = allocation->height - child_requisition.height;
443                child_allocation.width = allocation->width;
444                child_allocation.height = child_requisition.height;
445                gtk_widget_size_allocate (self->down_scroll, &child_allocation);
446        } else {
447                if (GTK_WIDGET_MAPPED (widget)) {
448                        if (GTK_WIDGET_MAPPED (self->up_scroll))
449                                gtk_widget_unmap (self->up_scroll);
450                        if (GTK_WIDGET_MAPPED (self->down_scroll))
451                                gtk_widget_unmap (self->down_scroll);
452                }
453        }
454
455        if (menu_shell->children) {
456                child_allocation.x = (GTK_CONTAINER (menu)->border_width +
457                                      widget->style->klass->xthickness);
458                child_allocation.y = (GTK_CONTAINER (menu)->border_width +
459                                      widget->style->klass->ythickness) - self->offset + top_scroller_height;
460                child_allocation.width = MAX (1, (gint)allocation->width - child_allocation.x * 2);
461
462                children = menu_shell->children;
463                while (children) {
464                        child = children->data;
465                        children = children->next;
466
467                        if (GTK_WIDGET_VISIBLE (child)) {
468                                gtk_widget_get_child_requisition (child, &child_requisition);
469
470                                child_allocation.height = child_requisition.height;
471
472                                gtk_widget_size_allocate (child, &child_allocation);
473                                gtk_widget_queue_draw (child);
474
475                                child_allocation.y += child_allocation.height;
476                        }
477                }
478        }
479}
480
481static void
482scroll_menu_map (GtkWidget *widget)
483{
484        ScrollMenu *self = SCROLL_MENU (widget);
485
486        if (GTK_WIDGET_CLASS (parent_class)->map)
487                GTK_WIDGET_CLASS (parent_class)->map (widget);
488
489        if (self->scroll) {
490                if ( ! GTK_WIDGET_MAPPED (self->up_scroll))
491                        gtk_widget_map (self->up_scroll);
492                if ( ! GTK_WIDGET_MAPPED (self->down_scroll))
493                        gtk_widget_map (self->down_scroll);
494
495                if (self->up_scroll->window)
496                        gdk_window_raise (self->up_scroll->window);
497                if (self->down_scroll->window)
498                        gdk_window_raise (self->down_scroll->window);
499        }
500
501}
502
503static void
504scroll_menu_unmap (GtkWidget *widget)
505{
506        ScrollMenu *self = SCROLL_MENU (widget);
507
508        if (GTK_WIDGET_CLASS (parent_class)->unmap)
509                GTK_WIDGET_CLASS (parent_class)->unmap (widget);
510
511        if (GTK_WIDGET_MAPPED (self->up_scroll))
512                gtk_widget_unmap (self->up_scroll);
513        if (GTK_WIDGET_MAPPED (self->down_scroll))
514                gtk_widget_unmap (self->down_scroll);
515}
516
517static void
518adjust_item (ScrollMenu *self, GtkWidget *widget)
519{
520        GtkWidget *menu = GTK_WIDGET (self);
521        GtkRequisition requisition;
522        int top;
523        int bottom;
524        int move;
525
526        if ( ! self->scroll)
527                return;
528
529        top = (GTK_CONTAINER (self)->border_width +
530               menu->style->klass->ythickness);
531
532        bottom = menu->allocation.height -
533                (GTK_CONTAINER (self)->border_width +
534                 menu->style->klass->ythickness);
535
536        gtk_widget_get_child_requisition (self->up_scroll, &requisition);
537        top += requisition.height;
538
539        gtk_widget_get_child_requisition (self->down_scroll, &requisition);
540        bottom -= requisition.height;
541
542        move = 0;
543
544        if (widget->allocation.y < top) {
545                move = - (top - widget->allocation.y);
546        } else if (widget->allocation.y + widget->allocation.height > bottom) {
547                move = (widget->allocation.y +
548                        widget->allocation.height -
549                        bottom);
550        }
551
552        if (move != 0)
553                scroll_by (self, move);
554}
555
556static void
557scroll_menu_move_current (GtkMenuShell *menu_shell, GtkMenuDirectionType direction)
558{
559        GtkWidget *current_active = menu_shell->active_menu_item;
560
561        if (GTK_MENU_SHELL_CLASS (parent_class)->move_current)
562                GTK_MENU_SHELL_CLASS (parent_class)->move_current (menu_shell, direction);
563
564        if (menu_shell->active_menu_item != NULL &&
565            menu_shell->active_menu_item != current_active)
566                adjust_item (SCROLL_MENU (menu_shell), menu_shell->active_menu_item);
567}
568
569static void
570scroll_menu_remove (GtkContainer *container, GtkWidget *widget)
571{
572        ScrollMenu *self = SCROLL_MENU (container);
573
574        if (self->up_scroll == widget) {
575                gtk_widget_unref (self->up_scroll);
576                self->up_scroll = NULL;
577        } else if (self->down_scroll == widget) {
578                gtk_widget_unref (self->down_scroll);
579                self->down_scroll = NULL;
580        } else if (GTK_CONTAINER_CLASS (parent_class)->remove) {
581                GTK_CONTAINER_CLASS (parent_class)->remove (container, widget);
582        }
583}
584
585static gboolean
586scroll_menu_leave_notify (GtkWidget *widget, GdkEventCrossing *event)
587{
588        ScrollMenu *self = SCROLL_MENU (widget);
589        GtkWidget *event_widget;
590
591        event_widget = gtk_get_event_widget ((GdkEvent*) event);
592
593        if (widget == event_widget ||
594            widget == self->up_scroll ||
595            widget == self->down_scroll) {
596                if (self->in_up) {
597                        scroll_leave_notify (self->up_scroll, event,
598                                             GINT_TO_POINTER (SCROLL_UP));
599                } else if (self->in_down) {
600                        scroll_leave_notify (self->down_scroll, event,
601                                             GINT_TO_POINTER (SCROLL_DOWN));
602                }
603        }
604
605        if (GTK_WIDGET_CLASS (parent_class)->leave_notify_event)
606                return GTK_WIDGET_CLASS (parent_class)->leave_notify_event (widget, event);
607
608        return FALSE;
609}
610
611static gboolean
612scroll_menu_enter_notify (GtkWidget *widget, GdkEventCrossing *event)
613{
614        ScrollMenu *self = SCROLL_MENU (widget);
615        GtkWidget *event_widget;
616
617        event_widget = gtk_get_event_widget ((GdkEvent*) event);
618
619        if (self->up_scroll == event_widget) {
620                return scroll_enter_notify (self->up_scroll, event,
621                                            GINT_TO_POINTER (SCROLL_UP));
622        } else if (self->down_scroll == event_widget) {
623                return scroll_enter_notify (self->down_scroll, event,
624                                            GINT_TO_POINTER (SCROLL_DOWN));
625        }
626
627        if (self->in_up) {
628                GdkWindow *window = gdk_window_at_pointer (NULL, NULL);
629                if (window != self->up_scroll->window)
630                        scroll_leave_notify (self->up_scroll, event,
631                                             GINT_TO_POINTER (SCROLL_UP));
632        } else if (self->in_down) {
633                GdkWindow *window = gdk_window_at_pointer (NULL, NULL);
634                if (window != self->down_scroll->window)
635                        scroll_leave_notify (self->down_scroll, event,
636                                             GINT_TO_POINTER (SCROLL_DOWN));
637        }
638
639       
640        if (GTK_WIDGET_CLASS (parent_class)->enter_notify_event)
641                return GTK_WIDGET_CLASS (parent_class)->enter_notify_event (widget, event);
642
643        return FALSE;
644}
645
646GtkWidget *
647scroll_menu_new (void)
648{
649        return (GtkWidget *)gtk_type_new (scroll_menu_get_type ());
650}
651
652void
653scroll_menu_set_screen_size (ScrollMenu *self,
654                             int screen_top,
655                             int screen_bottom)
656{
657        g_return_if_fail (self != NULL);
658        g_return_if_fail (IS_SCROLL_MENU (self));
659
660        if (self->screen_top != screen_top ||
661            self->screen_bottom != screen_bottom) {
662                self->screen_top = screen_top;
663                self->screen_bottom = screen_bottom;
664                gtk_widget_queue_resize (GTK_WIDGET (self));
665        }
666}
Note: See TracBrowser for help on using the repository browser.