1
/* Scroll menu: a scrolling menu (if it's beyond screen size)
9
#include "scroll-menu.h"
11
static void scroll_menu_class_init (ScrollMenuClass *klass);
12
static void scroll_menu_init (ScrollMenu *self);
13
static void scroll_menu_size_request (GtkWidget *widget,
14
GtkRequisition *requisition);
15
static void scroll_menu_size_allocate (GtkWidget *widget,
16
GtkAllocation *allocation);
17
static void scroll_menu_map (GtkWidget *widget);
18
static void scroll_menu_unmap (GtkWidget *widget);
19
static void scroll_menu_destroy (GtkObject *object);
20
static void scroll_menu_move_current (GtkMenuShell *menu_shell,
21
GtkMenuDirectionType direction);
22
static void scroll_menu_remove (GtkContainer *container,
24
static gboolean scroll_menu_enter_notify(GtkWidget *widget,
25
GdkEventCrossing *event);
26
static gboolean scroll_menu_leave_notify(GtkWidget *widget,
27
GdkEventCrossing *event);
29
static GtkMenuClass *parent_class = NULL;
36
#define SCROLLER_HEIGHT 15
38
#define SCROLL_TIMEOUT 150
42
scroll_menu_get_type (void)
44
static GtkType scroll_menu_type = 0;
46
if (scroll_menu_type == 0) {
49
GtkTypeInfo scroll_menu_info = {
52
sizeof (ScrollMenuClass),
53
(GtkClassInitFunc) scroll_menu_class_init,
54
(GtkObjectInitFunc) scroll_menu_init,
60
menu_type = gtk_menu_get_type ();
62
scroll_menu_type = gtk_type_unique (menu_type,
64
parent_class = gtk_type_class (menu_type);
67
return scroll_menu_type;
71
scroll_menu_class_init (ScrollMenuClass *klass)
73
GtkObjectClass *object_class = (GtkObjectClass*) klass;
74
GtkWidgetClass *widget_class = (GtkWidgetClass*) klass;
75
GtkContainerClass *container_class = (GtkContainerClass*) klass;
76
GtkMenuShellClass *menushell_class = (GtkMenuShellClass*) klass;
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;
85
menushell_class->move_current = scroll_menu_move_current;
87
container_class->remove = scroll_menu_remove;
89
object_class->destroy = scroll_menu_destroy;
93
scroll_by (ScrollMenu *self, int move)
95
GtkMenuShell *menu_shell = GTK_MENU_SHELL (self);
96
GtkAllocation allocation;
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;
109
self->offset += move;
111
children = menu_shell->children;
113
child = children->data;
114
children = children->next;
116
if (GTK_WIDGET_VISIBLE (child)) {
117
allocation = child->allocation;
118
allocation.y -= move;
120
gtk_widget_size_allocate (child, &allocation);
121
gtk_widget_queue_draw (child);
127
scroll_timeout (gpointer data)
129
ScrollMenu *self = SCROLL_MENU (data);
130
GtkMenuShell *menu_shell = GTK_MENU_SHELL (self);
132
if (self->scroll_by == 0 ||
133
menu_shell->children == NULL) {
137
scroll_by (self, self->scroll_by);
143
scroll_draw (GtkWidget *scroll, gboolean active, int direction)
145
GtkArrowType arrow_type;
147
if( ! GTK_WIDGET_DRAWABLE(scroll))
150
gdk_window_clear_area (scroll->window,
152
scroll->allocation.width,
153
scroll->allocation.height);
155
gtk_draw_shadow (scroll->style,
158
active ? GTK_SHADOW_IN : GTK_SHADOW_OUT,
160
scroll->allocation.width,
161
scroll->allocation.height);
163
if (direction == SCROLL_UP) {
164
arrow_type = GTK_ARROW_UP;
166
arrow_type = GTK_ARROW_DOWN;
169
gtk_draw_arrow (scroll->style,
172
active ? GTK_SHADOW_IN : GTK_SHADOW_OUT,
175
(scroll->allocation.width / 2) -
176
SCROLLER_HEIGHT / 2 - ARROW_PAD,
178
SCROLLER_HEIGHT - ARROW_PAD * 2,
179
SCROLLER_HEIGHT - ARROW_PAD * 2);
183
scroll_expose (GtkWidget *scroll, GdkEventExpose *event, gpointer data)
185
ScrollMenu *self = SCROLL_MENU (gtk_object_get_user_data (GTK_OBJECT (scroll)));
186
int direction = GPOINTER_TO_INT (data);
187
gboolean active = FALSE;
189
if (direction == SCROLL_UP) {
190
active = self->in_up;
192
active = self->in_down;
195
scroll_draw (scroll, active, direction);
201
scroll_realize (GtkWidget *scroll, gpointer data)
203
ScrollMenu *self = SCROLL_MENU (gtk_object_get_user_data (GTK_OBJECT (scroll)));
204
int direction = GPOINTER_TO_INT (data);
205
gboolean active = FALSE;
207
if (direction == SCROLL_UP) {
208
active = self->in_up;
210
active = self->in_down;
213
scroll_draw (scroll, active, direction);
217
scroll_enter_notify (GtkWidget *scroll, GdkEventCrossing *event, gpointer data)
219
ScrollMenu *self = SCROLL_MENU (gtk_object_get_user_data (GTK_OBJECT (scroll)));
220
int direction = GPOINTER_TO_INT (data);
222
if (direction == SCROLL_UP) {
224
self->scroll_by = -(SCROLL_BY);
226
self->in_down = TRUE;
227
self->scroll_by = SCROLL_BY;
230
gtk_widget_queue_draw (scroll);
232
if (self->scroll_timeout == 0) {
233
self->scroll_timeout = gtk_timeout_add (SCROLL_TIMEOUT,
242
scroll_leave_notify (GtkWidget *scroll, GdkEventCrossing *event, gpointer data)
244
ScrollMenu *self = SCROLL_MENU (gtk_object_get_user_data (GTK_OBJECT (scroll)));
245
int direction = GPOINTER_TO_INT (data);
249
if (direction == SCROLL_UP) {
252
self->in_down = FALSE;
255
gtk_widget_queue_draw (scroll);
257
if (self->scroll_timeout != 0) {
258
gtk_timeout_remove (self->scroll_timeout);
259
self->scroll_timeout = 0;
266
make_scroller (ScrollMenu *self, int direction)
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);
279
gtk_object_set_user_data (GTK_OBJECT (scroll), self);
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));
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));
299
scroll_menu_init (ScrollMenu *self)
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));
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));
311
self->scroll = FALSE;
314
self->max_offset = 0;
316
self->scroll_timeout = 0;
320
scroll_menu_destroy (GtkObject *object)
322
ScrollMenu *self = SCROLL_MENU (object);
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);
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);
338
if (self->scroll_timeout != 0) {
339
gtk_timeout_remove (self->scroll_timeout);
340
self->scroll_timeout = 0;
343
if (GTK_OBJECT_CLASS (parent_class)->destroy)
344
GTK_OBJECT_CLASS (parent_class)->destroy (object);
348
scroll_menu_size_request (GtkWidget *widget,
349
GtkRequisition *requisition)
351
ScrollMenu *self = SCROLL_MENU (widget);
352
GtkRequisition child_requisition;
355
if (GTK_WIDGET_CLASS (parent_class)->size_request)
356
GTK_WIDGET_CLASS (parent_class)->size_request (widget,
359
screen_height = gdk_screen_height ();
361
if (requisition->height > screen_height) {
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;
371
(requisition->height - screen_height) + button_height;
373
if (self->offset > self->max_offset)
374
self->offset = self->max_offset;
376
requisition->height = screen_height;
380
self->max_offset = 0;
381
self->scroll = FALSE;
386
scroll_menu_size_allocate (GtkWidget *widget,
387
GtkAllocation *allocation)
391
GtkMenuShell *menu_shell;
393
GtkAllocation child_allocation;
394
GtkRequisition child_requisition;
396
int top_scroller_height;
398
g_return_if_fail (widget != NULL);
399
g_return_if_fail (IS_SCROLL_MENU (widget));
400
g_return_if_fail (allocation != NULL);
402
self = SCROLL_MENU (widget);
403
menu = GTK_MENU (widget);
404
menu_shell = GTK_MENU_SHELL (widget);
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);
413
top_scroller_height = 0;
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);
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);
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);
435
top_scroller_height = child_requisition.height;
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);
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);
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);
459
children = menu_shell->children;
461
child = children->data;
462
children = children->next;
464
if (GTK_WIDGET_VISIBLE (child)) {
465
gtk_widget_get_child_requisition (child, &child_requisition);
467
child_allocation.height = child_requisition.height;
469
gtk_widget_size_allocate (child, &child_allocation);
470
gtk_widget_queue_draw (child);
472
child_allocation.y += child_allocation.height;
479
scroll_menu_map (GtkWidget *widget)
481
ScrollMenu *self = SCROLL_MENU (widget);
483
if (GTK_WIDGET_CLASS (parent_class)->map)
484
GTK_WIDGET_CLASS (parent_class)->map (widget);
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);
496
scroll_menu_unmap (GtkWidget *widget)
498
ScrollMenu *self = SCROLL_MENU (widget);
500
if (GTK_WIDGET_CLASS (parent_class)->unmap)
501
GTK_WIDGET_CLASS (parent_class)->unmap (widget);
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);
510
adjust_item (ScrollMenu *self, GtkWidget *widget)
512
GtkWidget *menu = GTK_WIDGET (self);
513
GtkRequisition requisition;
521
top = (GTK_CONTAINER (self)->border_width +
522
menu->style->klass->ythickness);
524
bottom = menu->allocation.height -
525
(GTK_CONTAINER (self)->border_width +
526
menu->style->klass->ythickness);
528
gtk_widget_get_child_requisition (self->up_scroll, &requisition);
529
top += requisition.height;
531
gtk_widget_get_child_requisition (self->down_scroll, &requisition);
532
bottom -= requisition.height;
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 -
545
scroll_by (self, move);
549
scroll_menu_move_current (GtkMenuShell *menu_shell, GtkMenuDirectionType direction)
551
GtkWidget *current_active = menu_shell->active_menu_item;
553
if (GTK_MENU_SHELL_CLASS (parent_class)->move_current)
554
GTK_MENU_SHELL_CLASS (parent_class)->move_current (menu_shell, direction);
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);
562
scroll_menu_remove (GtkContainer *container, GtkWidget *widget)
564
ScrollMenu *self = SCROLL_MENU (container);
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);
578
scroll_menu_leave_notify (GtkWidget *widget, GdkEventCrossing *event)
580
ScrollMenu *self = SCROLL_MENU (widget);
581
GtkWidget *event_widget;
583
event_widget = gtk_get_event_widget ((GdkEvent*) event);
585
if (widget == event_widget ||
586
widget == self->up_scroll ||
587
widget == self->down_scroll) {
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));
597
if (GTK_WIDGET_CLASS (parent_class)->leave_notify_event)
598
return GTK_WIDGET_CLASS (parent_class)->leave_notify_event (widget, event);
604
scroll_menu_enter_notify (GtkWidget *widget, GdkEventCrossing *event)
606
ScrollMenu *self = SCROLL_MENU (widget);
607
GtkWidget *event_widget;
609
event_widget = gtk_get_event_widget ((GdkEvent*) event);
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));
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));
632
if (GTK_WIDGET_CLASS (parent_class)->enter_notify_event)
633
return GTK_WIDGET_CLASS (parent_class)->enter_notify_event (widget, event);
639
scroll_menu_new (void)
641
return (GtkWidget *)gtk_type_new (scroll_menu_get_type ());