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

Revision 15821, 16.5 KB checked in by ghudson, 24 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r15820, 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->up_scroll = make_scroller (self, SCROLL_UP);
302        gtk_widget_ref (self->up_scroll);
303        gtk_object_sink (GTK_OBJECT (self->up_scroll));
304        gtk_widget_set_parent (self->up_scroll, GTK_WIDGET(self));
305
306        self->down_scroll = make_scroller (self, SCROLL_DOWN);
307        gtk_widget_ref (self->down_scroll);
308        gtk_object_sink (GTK_OBJECT (self->up_scroll));
309        gtk_widget_set_parent (self->down_scroll, GTK_WIDGET(self));
310
311        self->scroll = FALSE;
312
313        self->offset = 0;
314        self->max_offset = 0;
315        self->scroll_by = 0;
316        self->scroll_timeout = 0;
317}
318
319static void
320scroll_menu_destroy (GtkObject *object)
321{
322        ScrollMenu *self = SCROLL_MENU (object);
323
324        if (self->up_scroll != NULL) {
325                /* this should remove the widget and thus cause
326                 * a NULL and an unref */
327                gtk_widget_destroy (self->up_scroll);
328                g_assert (self->up_scroll == NULL);
329        }
330
331        if (self->down_scroll != NULL) {
332                /* this should remove the widget and thus cause
333                 * a NULL and an unref */
334                gtk_widget_destroy (self->down_scroll);
335                g_assert (self->down_scroll == NULL);
336        }
337
338        if (self->scroll_timeout != 0) {
339                gtk_timeout_remove (self->scroll_timeout);
340                self->scroll_timeout = 0;
341        }
342
343        if (GTK_OBJECT_CLASS (parent_class)->destroy)
344                GTK_OBJECT_CLASS (parent_class)->destroy (object);
345}
346
347static void
348scroll_menu_size_request (GtkWidget *widget,
349                          GtkRequisition *requisition)
350{
351        ScrollMenu *self = SCROLL_MENU (widget);
352        GtkRequisition child_requisition;
353        int screen_height;
354
355        if (GTK_WIDGET_CLASS (parent_class)->size_request)
356                GTK_WIDGET_CLASS (parent_class)->size_request (widget,
357                                                               requisition);
358
359        screen_height = gdk_screen_height ();
360
361        if (requisition->height > screen_height) {
362                int button_height;
363
364                button_height = 0;
365                gtk_widget_size_request (self->up_scroll, &child_requisition);
366                button_height += child_requisition.height;
367                gtk_widget_size_request (self->down_scroll, &child_requisition);
368                button_height += child_requisition.height;
369
370                self->max_offset =
371                        (requisition->height - screen_height) + button_height;
372
373                if (self->offset > self->max_offset)
374                        self->offset = self->max_offset;
375
376                requisition->height = screen_height;
377                self->scroll = TRUE;
378        } else {
379                self->offset = 0;
380                self->max_offset = 0;
381                self->scroll = FALSE;
382        }
383}
384
385static void
386scroll_menu_size_allocate (GtkWidget *widget,
387                           GtkAllocation *allocation)
388{
389        ScrollMenu *self;
390        GtkMenu *menu;
391        GtkMenuShell *menu_shell;
392        GtkWidget *child;
393        GtkAllocation child_allocation;
394        GtkRequisition child_requisition;
395        GList *children;
396        int top_scroller_height;
397
398        g_return_if_fail (widget != NULL);
399        g_return_if_fail (IS_SCROLL_MENU (widget));
400        g_return_if_fail (allocation != NULL);
401
402        self = SCROLL_MENU (widget);
403        menu = GTK_MENU (widget);
404        menu_shell = GTK_MENU_SHELL (widget);
405
406        widget->allocation = *allocation;
407        if (GTK_WIDGET_REALIZED (widget)) {
408                gdk_window_move_resize (widget->window,
409                                        allocation->x, allocation->y,
410                                        allocation->width, allocation->height);
411        }
412
413        top_scroller_height = 0;
414
415        if (self->scroll) {
416                if (GTK_WIDGET_MAPPED (widget) &&
417                    ! GTK_WIDGET_MAPPED (self->up_scroll))
418                        gtk_widget_map (self->up_scroll);
419                if (self->up_scroll->window)
420                        gdk_window_raise (self->up_scroll->window);
421
422                if (GTK_WIDGET_MAPPED (widget) &&
423                    ! GTK_WIDGET_MAPPED (self->down_scroll))
424                        gtk_widget_map (self->down_scroll);
425                if (self->down_scroll->window)
426                        gdk_window_raise (self->down_scroll->window);
427
428                gtk_widget_get_child_requisition (self->up_scroll, &child_requisition);
429                child_allocation.x = 0;
430                child_allocation.y = 0;
431                child_allocation.width = allocation->width;
432                child_allocation.height = child_requisition.height;
433                gtk_widget_size_allocate (self->up_scroll, &child_allocation);
434
435                top_scroller_height = child_requisition.height;
436
437                gtk_widget_get_child_requisition (self->down_scroll, &child_requisition);
438                child_allocation.x = 0;
439                child_allocation.y = allocation->height - child_requisition.height;
440                child_allocation.width = allocation->width;
441                child_allocation.height = child_requisition.height;
442                gtk_widget_size_allocate (self->down_scroll, &child_allocation);
443        } else {
444                if (GTK_WIDGET_MAPPED (widget)) {
445                        if (GTK_WIDGET_MAPPED (self->up_scroll))
446                                gtk_widget_unmap (self->up_scroll);
447                        if (GTK_WIDGET_MAPPED (self->down_scroll))
448                                gtk_widget_unmap (self->down_scroll);
449                }
450        }
451
452        if (menu_shell->children) {
453                child_allocation.x = (GTK_CONTAINER (menu)->border_width +
454                                      widget->style->klass->xthickness);
455                child_allocation.y = (GTK_CONTAINER (menu)->border_width +
456                                      widget->style->klass->ythickness) - self->offset + top_scroller_height;
457                child_allocation.width = MAX (1, (gint)allocation->width - child_allocation.x * 2);
458
459                children = menu_shell->children;
460                while (children) {
461                        child = children->data;
462                        children = children->next;
463
464                        if (GTK_WIDGET_VISIBLE (child)) {
465                                gtk_widget_get_child_requisition (child, &child_requisition);
466
467                                child_allocation.height = child_requisition.height;
468
469                                gtk_widget_size_allocate (child, &child_allocation);
470                                gtk_widget_queue_draw (child);
471
472                                child_allocation.y += child_allocation.height;
473                        }
474                }
475        }
476}
477
478static void
479scroll_menu_map (GtkWidget *widget)
480{
481        ScrollMenu *self = SCROLL_MENU (widget);
482
483        if (GTK_WIDGET_CLASS (parent_class)->map)
484                GTK_WIDGET_CLASS (parent_class)->map (widget);
485
486        if (self->scroll) {
487                if ( ! GTK_WIDGET_MAPPED (self->up_scroll))
488                        gtk_widget_map (self->up_scroll);
489                if ( ! GTK_WIDGET_MAPPED (self->down_scroll))
490                        gtk_widget_map (self->down_scroll);
491        }
492
493}
494
495static void
496scroll_menu_unmap (GtkWidget *widget)
497{
498        ScrollMenu *self = SCROLL_MENU (widget);
499
500        if (GTK_WIDGET_CLASS (parent_class)->unmap)
501                GTK_WIDGET_CLASS (parent_class)->unmap (widget);
502
503        if (GTK_WIDGET_MAPPED (self->up_scroll))
504                gtk_widget_unmap (self->up_scroll);
505        if (GTK_WIDGET_MAPPED (self->down_scroll))
506                gtk_widget_unmap (self->down_scroll);
507}
508
509static void
510adjust_item (ScrollMenu *self, GtkWidget *widget)
511{
512        GtkWidget *menu = GTK_WIDGET (self);
513        GtkRequisition requisition;
514        int top;
515        int bottom;
516        int move;
517
518        if ( ! self->scroll)
519                return;
520
521        top = (GTK_CONTAINER (self)->border_width +
522               menu->style->klass->ythickness);
523
524        bottom = menu->allocation.height -
525                (GTK_CONTAINER (self)->border_width +
526                 menu->style->klass->ythickness);
527
528        gtk_widget_get_child_requisition (self->up_scroll, &requisition);
529        top += requisition.height;
530
531        gtk_widget_get_child_requisition (self->down_scroll, &requisition);
532        bottom -= requisition.height;
533
534        move = 0;
535
536        if (widget->allocation.y < top) {
537                move = - (top - widget->allocation.y);
538        } else if (widget->allocation.y + widget->allocation.height > bottom) {
539                move = (widget->allocation.y +
540                        widget->allocation.height -
541                        bottom);
542        }
543
544        if (move != 0)
545                scroll_by (self, move);
546}
547
548static void
549scroll_menu_move_current (GtkMenuShell *menu_shell, GtkMenuDirectionType direction)
550{
551        GtkWidget *current_active = menu_shell->active_menu_item;
552
553        if (GTK_MENU_SHELL_CLASS (parent_class)->move_current)
554                GTK_MENU_SHELL_CLASS (parent_class)->move_current (menu_shell, direction);
555
556        if (menu_shell->active_menu_item != NULL &&
557            menu_shell->active_menu_item != current_active)
558                adjust_item (SCROLL_MENU (menu_shell), menu_shell->active_menu_item);
559}
560
561static void
562scroll_menu_remove (GtkContainer *container, GtkWidget *widget)
563{
564        ScrollMenu *self = SCROLL_MENU (container);
565
566        if (self->up_scroll == widget) {
567                gtk_widget_unref (self->up_scroll);
568                self->up_scroll = NULL;
569        } else if (self->down_scroll == widget) {
570                gtk_widget_unref (self->down_scroll);
571                self->down_scroll = NULL;
572        } else if (GTK_CONTAINER_CLASS (parent_class)->remove) {
573                GTK_CONTAINER_CLASS (parent_class)->remove (container, widget);
574        }
575}
576
577static gboolean
578scroll_menu_leave_notify (GtkWidget *widget, GdkEventCrossing *event)
579{
580        ScrollMenu *self = SCROLL_MENU (widget);
581        GtkWidget *event_widget;
582
583        event_widget = gtk_get_event_widget ((GdkEvent*) event);
584
585        if (widget == event_widget ||
586            widget == self->up_scroll ||
587            widget == self->down_scroll) {
588                if (self->in_up) {
589                        scroll_leave_notify (self->up_scroll, event,
590                                             GINT_TO_POINTER (SCROLL_UP));
591                } else if (self->in_down) {
592                        scroll_leave_notify (self->down_scroll, event,
593                                             GINT_TO_POINTER (SCROLL_DOWN));
594                }
595        }
596
597        if (GTK_WIDGET_CLASS (parent_class)->leave_notify_event)
598                return GTK_WIDGET_CLASS (parent_class)->leave_notify_event (widget, event);
599
600        return FALSE;
601}
602
603static gboolean
604scroll_menu_enter_notify (GtkWidget *widget, GdkEventCrossing *event)
605{
606        ScrollMenu *self = SCROLL_MENU (widget);
607        GtkWidget *event_widget;
608
609        event_widget = gtk_get_event_widget ((GdkEvent*) event);
610
611        if (self->up_scroll == event_widget) {
612                return scroll_enter_notify (self->up_scroll, event,
613                                            GINT_TO_POINTER (SCROLL_UP));
614        } else if (self->down_scroll == event_widget) {
615                return scroll_enter_notify (self->down_scroll, event,
616                                            GINT_TO_POINTER (SCROLL_DOWN));
617        }
618
619        if (self->in_up) {
620                GdkWindow *window = gdk_window_at_pointer (NULL, NULL);
621                if (window != self->up_scroll->window)
622                        scroll_leave_notify (self->up_scroll, event,
623                                             GINT_TO_POINTER (SCROLL_UP));
624        } else if (self->in_down) {
625                GdkWindow *window = gdk_window_at_pointer (NULL, NULL);
626                if (window != self->down_scroll->window)
627                        scroll_leave_notify (self->down_scroll, event,
628                                             GINT_TO_POINTER (SCROLL_DOWN));
629        }
630
631       
632        if (GTK_WIDGET_CLASS (parent_class)->enter_notify_event)
633                return GTK_WIDGET_CLASS (parent_class)->enter_notify_event (widget, event);
634
635        return FALSE;
636}
637
638GtkWidget *
639scroll_menu_new (void)
640{
641        return (GtkWidget *)gtk_type_new (scroll_menu_get_type ());
642}
Note: See TracBrowser for help on using the repository browser.