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

Revision 16298, 16.8 KB checked in by ghudson, 24 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r16297, 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
496}
497
498static void
499scroll_menu_unmap (GtkWidget *widget)
500{
501        ScrollMenu *self = SCROLL_MENU (widget);
502
503        if (GTK_WIDGET_CLASS (parent_class)->unmap)
504                GTK_WIDGET_CLASS (parent_class)->unmap (widget);
505
506        if (GTK_WIDGET_MAPPED (self->up_scroll))
507                gtk_widget_unmap (self->up_scroll);
508        if (GTK_WIDGET_MAPPED (self->down_scroll))
509                gtk_widget_unmap (self->down_scroll);
510}
511
512static void
513adjust_item (ScrollMenu *self, GtkWidget *widget)
514{
515        GtkWidget *menu = GTK_WIDGET (self);
516        GtkRequisition requisition;
517        int top;
518        int bottom;
519        int move;
520
521        if ( ! self->scroll)
522                return;
523
524        top = (GTK_CONTAINER (self)->border_width +
525               menu->style->klass->ythickness);
526
527        bottom = menu->allocation.height -
528                (GTK_CONTAINER (self)->border_width +
529                 menu->style->klass->ythickness);
530
531        gtk_widget_get_child_requisition (self->up_scroll, &requisition);
532        top += requisition.height;
533
534        gtk_widget_get_child_requisition (self->down_scroll, &requisition);
535        bottom -= requisition.height;
536
537        move = 0;
538
539        if (widget->allocation.y < top) {
540                move = - (top - widget->allocation.y);
541        } else if (widget->allocation.y + widget->allocation.height > bottom) {
542                move = (widget->allocation.y +
543                        widget->allocation.height -
544                        bottom);
545        }
546
547        if (move != 0)
548                scroll_by (self, move);
549}
550
551static void
552scroll_menu_move_current (GtkMenuShell *menu_shell, GtkMenuDirectionType direction)
553{
554        GtkWidget *current_active = menu_shell->active_menu_item;
555
556        if (GTK_MENU_SHELL_CLASS (parent_class)->move_current)
557                GTK_MENU_SHELL_CLASS (parent_class)->move_current (menu_shell, direction);
558
559        if (menu_shell->active_menu_item != NULL &&
560            menu_shell->active_menu_item != current_active)
561                adjust_item (SCROLL_MENU (menu_shell), menu_shell->active_menu_item);
562}
563
564static void
565scroll_menu_remove (GtkContainer *container, GtkWidget *widget)
566{
567        ScrollMenu *self = SCROLL_MENU (container);
568
569        if (self->up_scroll == widget) {
570                gtk_widget_unref (self->up_scroll);
571                self->up_scroll = NULL;
572        } else if (self->down_scroll == widget) {
573                gtk_widget_unref (self->down_scroll);
574                self->down_scroll = NULL;
575        } else if (GTK_CONTAINER_CLASS (parent_class)->remove) {
576                GTK_CONTAINER_CLASS (parent_class)->remove (container, widget);
577        }
578}
579
580static gboolean
581scroll_menu_leave_notify (GtkWidget *widget, GdkEventCrossing *event)
582{
583        ScrollMenu *self = SCROLL_MENU (widget);
584        GtkWidget *event_widget;
585
586        event_widget = gtk_get_event_widget ((GdkEvent*) event);
587
588        if (widget == event_widget ||
589            widget == self->up_scroll ||
590            widget == self->down_scroll) {
591                if (self->in_up) {
592                        scroll_leave_notify (self->up_scroll, event,
593                                             GINT_TO_POINTER (SCROLL_UP));
594                } else if (self->in_down) {
595                        scroll_leave_notify (self->down_scroll, event,
596                                             GINT_TO_POINTER (SCROLL_DOWN));
597                }
598        }
599
600        if (GTK_WIDGET_CLASS (parent_class)->leave_notify_event)
601                return GTK_WIDGET_CLASS (parent_class)->leave_notify_event (widget, event);
602
603        return FALSE;
604}
605
606static gboolean
607scroll_menu_enter_notify (GtkWidget *widget, GdkEventCrossing *event)
608{
609        ScrollMenu *self = SCROLL_MENU (widget);
610        GtkWidget *event_widget;
611
612        event_widget = gtk_get_event_widget ((GdkEvent*) event);
613
614        if (self->up_scroll == event_widget) {
615                return scroll_enter_notify (self->up_scroll, event,
616                                            GINT_TO_POINTER (SCROLL_UP));
617        } else if (self->down_scroll == event_widget) {
618                return scroll_enter_notify (self->down_scroll, event,
619                                            GINT_TO_POINTER (SCROLL_DOWN));
620        }
621
622        if (self->in_up) {
623                GdkWindow *window = gdk_window_at_pointer (NULL, NULL);
624                if (window != self->up_scroll->window)
625                        scroll_leave_notify (self->up_scroll, event,
626                                             GINT_TO_POINTER (SCROLL_UP));
627        } else if (self->in_down) {
628                GdkWindow *window = gdk_window_at_pointer (NULL, NULL);
629                if (window != self->down_scroll->window)
630                        scroll_leave_notify (self->down_scroll, event,
631                                             GINT_TO_POINTER (SCROLL_DOWN));
632        }
633
634       
635        if (GTK_WIDGET_CLASS (parent_class)->enter_notify_event)
636                return GTK_WIDGET_CLASS (parent_class)->enter_notify_event (widget, event);
637
638        return FALSE;
639}
640
641GtkWidget *
642scroll_menu_new (void)
643{
644        return (GtkWidget *)gtk_type_new (scroll_menu_get_type ());
645}
646
647void
648scroll_menu_set_screen_size (ScrollMenu *self,
649                             int screen_top,
650                             int screen_bottom)
651{
652        g_return_if_fail (self != NULL);
653        g_return_if_fail (IS_SCROLL_MENU (self));
654
655        self->screen_top = screen_top;
656        self->screen_bottom = screen_bottom;
657}
Note: See TracBrowser for help on using the repository browser.