Too long menus
- From: Matt Kimball <mkimball xmission com>
- To: gtk-devel-list redhat com
- Subject: Too long menus
- Date: Mon, 6 Sep 1999 18:06:57 -0600
Hello GTK+ folks.
One particular itch of mine about GTK+ apps is when you've got an
option menu with enough options that the menu height is actually
taller than your screen height. You can't get to some of the options
because they run off the screen.
So, how to fix this problem? I thought it might be nice if the menu
code truncated the menu to fit the screen and put arrows at the top
and/or bottom of the menu to indicate more options are available. And
if the user moves her mouse over the arrows, the menu would scroll
through the available options until she finds the one she needs.
So, I got to work and started hacking up the gtkmenu.c code yesterday.
As of today I've got scroll arrows working on menus which are too
long. A patch against GTK+ 1.2.4 is attached. It seems to apply
cleanly to the current CVS version too, but I haven't tested that.
The code could stand to be cleaned up and there are some rough edges
in the behavior, so I wouldn't necessarily expect you folks to include
the patch as-is.
But, do y'all think this is the best way to handle the problem? Is
this a solution worth pursuing? Any ideas how to avoid that obnoxious
flicker while scrolling?
--
Matt Kimball
mkimball@xmission.com
Index: gtk/gtkmenu.c
===================================================================
RCS file: /home/mrk/cvs/gtk+/gtk/gtkmenu.c,v
retrieving revision 1.1.1.1
retrieving revision 1.3
diff -u -r1.1.1.1 -r1.3
--- gtk/gtkmenu.c 1999/09/05 22:42:32 1.1.1.1
+++ gtk/gtkmenu.c 1999/09/06 23:38:07 1.3
@@ -38,6 +38,9 @@
#define MENU_ITEM_CLASS(w) GTK_MENU_ITEM_CLASS (GTK_OBJECT (w)->klass)
#define MENU_NEEDS_RESIZE(m) GTK_MENU_SHELL (m)->menu_flag
+#define MENU_SCROLL_ARROW_HEIGHT 16
+#define MENU_SCROLL_TIMEOUT 200
+
typedef struct _GtkMenuAttachData GtkMenuAttachData;
struct _GtkMenuAttachData
@@ -68,9 +71,25 @@
static void gtk_menu_show_all (GtkWidget *widget);
static void gtk_menu_hide_all (GtkWidget *widget);
static void gtk_menu_position (GtkMenu *menu);
+static void gtk_menu_top_position (GtkMenu *menu,
+ gint x,
+ gint y,
+ gint width,
+ gint height);
static void gtk_menu_reparent (GtkMenu *menu,
GtkWidget *new_parent,
gboolean unrealize);
+static void gtk_menu_set_scrolling (GtkMenu *menu,
+ gboolean scroll_up,
+ gboolean scroll_down);
+static gboolean gtk_menu_scroll_up (GtkMenu *menu,
+ guint scroll_count);
+static gboolean gtk_menu_scroll_down (GtkMenu *menu,
+ guint scroll_count);
+static gboolean gtk_menu_timeout_up (gpointer data);
+static gboolean gtk_menu_timeout_down (gpointer data);
+
+
static GtkMenuShellClass *parent_class = NULL;
static const gchar *attach_data_key = "gtk-menu-attach-data";
@@ -192,6 +211,13 @@
menu->position_func = NULL;
menu->position_func_data = NULL;
+ menu->first_scroll_child = -1;
+ menu->last_scroll_child = -1;
+ menu->top_scroll_arrow_lit = FALSE;
+ menu->bottom_scroll_arrow_lit = FALSE;
+ menu->scroll_up_source_tag = 0;
+ menu->scroll_down_source_tag = 0;
+
menu->toplevel = gtk_window_new (GTK_WINDOW_POPUP);
gtk_signal_connect (GTK_OBJECT (menu->toplevel),
"event",
@@ -419,6 +445,9 @@
widget = GTK_WIDGET (menu);
menu_shell = GTK_MENU_SHELL (menu);
+ menu->top_scroll_arrow_lit = FALSE;
+ menu->bottom_scroll_arrow_lit = FALSE;
+
menu_shell->parent_menu_shell = parent_menu_shell;
menu_shell->active = TRUE;
menu_shell->button = button;
@@ -534,6 +563,7 @@
}
gtk_menu_shell_deselect (menu_shell);
+ gtk_menu_set_scrolling (menu, FALSE, FALSE);
/* The X Grab, if present, will automatically be removed when we hide
* the window */
@@ -897,15 +927,20 @@
allocation->x, allocation->y,
allocation->width, allocation->height);
-
if (menu_shell->children)
{
- child_allocation.x = (GTK_CONTAINER (menu)->border_width +
- widget->style->klass->xthickness);
- child_allocation.y = (GTK_CONTAINER (menu)->border_width +
- widget->style->klass->ythickness);
+ gint child_num;
+
+ child_allocation.x = GTK_CONTAINER (menu)->border_width +
+ GTK_WIDGET (menu)->style->klass->xthickness;
+ child_allocation.y = GTK_CONTAINER (menu)->border_width +
+ GTK_WIDGET (menu)->style->klass->ythickness;
child_allocation.width = MAX (1, (gint)allocation->width - child_allocation.x * 2);
+
+ if (menu->first_scroll_child != -1)
+ child_allocation.y += MENU_SCROLL_ARROW_HEIGHT;
+ child_num = 0;
children = menu_shell->children;
while (children)
{
@@ -916,14 +951,32 @@
{
GtkRequisition child_requisition;
gtk_widget_get_child_requisition (child, &child_requisition);
-
- child_allocation.height = child_requisition.height;
-
- gtk_widget_size_allocate (child, &child_allocation);
- gtk_widget_queue_draw (child);
+
+ if (child_num < menu->first_scroll_child
+ || (child_num > menu->last_scroll_child
+ && menu->last_scroll_child != -1))
+ {
+ GtkAllocation outside_allocation;
+
+ outside_allocation.x = -child_requisition.width;
+ outside_allocation.y = -child_requisition.height;
+ outside_allocation.width = child_requisition.width;
+ outside_allocation.height = child_requisition.height;
+
+ gtk_widget_size_allocate (child, &outside_allocation);
+ }
+ else
+ {
+ child_allocation.height = child_requisition.height;
- child_allocation.y += child_allocation.height;
+ gtk_widget_size_allocate (child, &child_allocation);
+ gtk_widget_queue_draw (child);
+
+ child_allocation.y += child_allocation.height;
+ }
}
+
+ child_num++;
}
}
}
@@ -936,12 +989,50 @@
if (GTK_WIDGET_DRAWABLE (widget))
{
+ guint border;
+ guint width, height;
+
+ border = GTK_CONTAINER (widget)->border_width +
+ widget->style->klass->ythickness + 1;
+ gdk_window_get_size (widget->window, &width, &height);
+
gtk_paint_box (widget->style,
widget->window,
GTK_STATE_NORMAL,
GTK_SHADOW_OUT,
NULL, widget, "menu",
0, 0, -1, -1);
+
+ if (GTK_MENU (widget)->first_scroll_child != -1)
+ {
+ gtk_paint_arrow (widget->style,
+ widget->window,
+ GTK_MENU (widget)->top_scroll_arrow_lit ?
+ GTK_STATE_PRELIGHT : GTK_STATE_NORMAL,
+ GTK_SHADOW_OUT,
+ NULL, widget, "menu",
+ GTK_ARROW_UP,
+ TRUE,
+ width / 2 - MENU_SCROLL_ARROW_HEIGHT / 2 + 1,
+ border,
+ MENU_SCROLL_ARROW_HEIGHT - 2,
+ MENU_SCROLL_ARROW_HEIGHT - 2);
+ }
+ if (GTK_MENU (widget)->last_scroll_child != -1)
+ {
+ gtk_paint_arrow (widget->style,
+ widget->window,
+ GTK_MENU (widget)->bottom_scroll_arrow_lit ?
+ GTK_STATE_PRELIGHT : GTK_STATE_NORMAL,
+ GTK_SHADOW_OUT,
+ NULL, widget, "menu",
+ GTK_ARROW_DOWN,
+ TRUE,
+ width / 2 - MENU_SCROLL_ARROW_HEIGHT / 2 + 1,
+ height - border - MENU_SCROLL_ARROW_HEIGHT + 2,
+ MENU_SCROLL_ARROW_HEIGHT - 2,
+ MENU_SCROLL_ARROW_HEIGHT - 2);
+ }
}
}
@@ -1104,18 +1195,30 @@
gtk_menu_motion_notify (GtkWidget *widget,
GdkEventMotion *event)
{
+ gboolean in_window;
+ guint border;
+ gint width, height;
+ gboolean old_top_lit, old_bottom_lit;
+ gboolean scroll_up, scroll_down;
+
g_return_val_if_fail (widget != NULL, FALSE);
g_return_val_if_fail (GTK_IS_MENU (widget), FALSE);
+
+ gdk_window_get_size (event->window, &width, &height);
+
+ in_window = FALSE;
+ if (event->x >= 0 && event->x < width &&
+ event->y >= 0 && event->y < height)
+ in_window = TRUE;
+
+ border = GTK_CONTAINER (widget)->border_width +
+ widget->style->klass->ythickness;
if (GTK_MENU_SHELL (widget)->ignore_enter)
GTK_MENU_SHELL (widget)->ignore_enter = FALSE;
else
{
- gint width, height;
-
- gdk_window_get_size (event->window, &width, &height);
- if (event->x >= 0 && event->x < width &&
- event->y >= 0 && event->y < height)
+ if (in_window)
{
GdkEvent send_event;
@@ -1128,6 +1231,47 @@
}
}
+ old_top_lit = GTK_MENU (widget)->top_scroll_arrow_lit;
+ old_bottom_lit = GTK_MENU (widget)->bottom_scroll_arrow_lit;
+ GTK_MENU (widget)->top_scroll_arrow_lit = FALSE;
+ GTK_MENU (widget)->bottom_scroll_arrow_lit = FALSE;
+
+ scroll_up = FALSE;
+ scroll_down = FALSE;
+
+ if (event->window == widget->window
+ && event->y < border + MENU_SCROLL_ARROW_HEIGHT
+ && GTK_MENU (widget)->first_scroll_child != -1)
+ {
+ if (in_window)
+ GTK_MENU (widget)->top_scroll_arrow_lit = TRUE;
+
+ if (GTK_MENU_SHELL (widget)->button)
+ {
+ scroll_up = TRUE;
+ GTK_MENU (widget)->top_scroll_arrow_lit = TRUE;
+ }
+ }
+ if (event->window == widget->window
+ && event->y >= height - border - MENU_SCROLL_ARROW_HEIGHT
+ && GTK_MENU (widget)->last_scroll_child != -1)
+ {
+ if (in_window)
+ GTK_MENU (widget)->bottom_scroll_arrow_lit = TRUE;
+
+ if (GTK_MENU_SHELL (widget)->button)
+ {
+ scroll_down = TRUE;
+ GTK_MENU (widget)->bottom_scroll_arrow_lit = TRUE;
+ }
+ }
+
+ gtk_menu_set_scrolling (GTK_MENU (widget), scroll_up, scroll_down);
+
+ if (GTK_MENU (widget)->top_scroll_arrow_lit != old_top_lit
+ || GTK_MENU (widget)->bottom_scroll_arrow_lit != old_bottom_lit)
+ gtk_menu_paint (widget);
+
return FALSE;
}
@@ -1192,10 +1336,104 @@
if (y < 0)
y = 0;
}
-
- gtk_widget_set_uposition (GTK_MENU_SHELL (menu)->active ?
- menu->toplevel : menu->tearoff_window,
- x, y);
+
+ gtk_menu_top_position (menu, x, y, requisition.width, requisition.height);
+}
+
+/* Set the actual position of the toplevel window. Also, if part of the
+ * toplevel window would fall offscreen, truncate the window and note that
+ * we will use scroll arrows.
+ */
+static void
+gtk_menu_top_position (GtkMenu *menu,
+ gint x,
+ gint y,
+ gint width,
+ gint height)
+{
+ gint border_height;
+
+ border_height = GTK_CONTAINER (menu)->border_width +
+ GTK_WIDGET (menu)->style->klass->ythickness;
+
+ if (GTK_MENU_SHELL (menu)->active)
+ {
+ GList *child_list;
+ gint screen_height;
+ gint on_line;
+
+ child_list = GTK_MENU_SHELL (menu)->children;
+ screen_height = gdk_screen_height ();
+
+ on_line = y + border_height;
+
+ if (y < 0)
+ {
+ guint offset;
+
+ offset = 0;
+ menu->first_scroll_child = 0;
+ while (on_line < MENU_SCROLL_ARROW_HEIGHT + border_height
+ && child_list)
+ {
+ GtkWidget *child;
+
+ child = GTK_WIDGET (child_list->data);
+ if (GTK_WIDGET_VISIBLE (child))
+ {
+ gtk_widget_size_request (child, &child->requisition);
+
+ height -= child->requisition.height;
+ on_line += child->requisition.height;
+ y += child->requisition.height;
+ }
+ child_list = child_list->next;
+ menu->first_scroll_child++;
+ }
+ y -= MENU_SCROLL_ARROW_HEIGHT;
+ height += MENU_SCROLL_ARROW_HEIGHT;
+ }
+ else
+ menu->first_scroll_child = -1;
+
+ if (y + height >= screen_height)
+ {
+ menu->last_scroll_child = menu->first_scroll_child;
+ if (menu->last_scroll_child == -1)
+ menu->last_scroll_child = 0;
+
+ while (on_line + MENU_SCROLL_ARROW_HEIGHT + border_height
+ < screen_height
+ && child_list)
+ {
+ GtkWidget *child;
+
+ child = GTK_WIDGET (child_list->data);
+ if (GTK_WIDGET_VISIBLE (child))
+ {
+ gtk_widget_size_request (child, &child->requisition);
+
+ height = on_line + MENU_SCROLL_ARROW_HEIGHT + border_height
+ - y;
+ on_line += child->requisition.height;
+ }
+ child_list = child_list->next;
+ menu->last_scroll_child++;
+ }
+ menu->last_scroll_child -= 2;
+ if(menu->last_scroll_child < 0)
+ menu->last_scroll_child = menu->first_scroll_child;
+ }
+ else
+ menu->last_scroll_child = -1;
+
+ gtk_widget_set_uposition (menu->toplevel, x, y);
+ gtk_widget_set_usize (menu->toplevel, width, height);
+ }
+ else
+ {
+ gtk_widget_set_uposition (menu->tearoff_window, x, y);
+ }
}
/* Reparent the menu, taking care of the refcounting
@@ -1250,3 +1488,247 @@
gtk_container_foreach (GTK_CONTAINER (widget), (GtkCallback) gtk_widget_hide_all, NULL);
}
+/* Set the scrolling state of the menu. It is harmless to call this
+ * if there is no change in scrolling state. (That is, it won't interrupt
+ * or reset the scrolling timers if the scrolling settings are the same).
+ */
+static void
+gtk_menu_set_scrolling (GtkMenu *menu,
+ gboolean scroll_up,
+ gboolean scroll_down)
+{
+ if (!scroll_up && menu->scroll_up_source_tag)
+ {
+ g_source_remove (menu->scroll_up_source_tag);
+ menu->scroll_up_source_tag = 0;
+ }
+
+ if (!scroll_down && menu->scroll_down_source_tag)
+ {
+ g_source_remove (menu->scroll_down_source_tag);
+ menu->scroll_down_source_tag = 0;
+ }
+
+ if(scroll_up && !menu->scroll_up_source_tag)
+ {
+ if (gtk_menu_scroll_up (menu, 1))
+ menu->scroll_up_source_tag =
+ g_timeout_add (MENU_SCROLL_TIMEOUT, gtk_menu_timeout_up, menu);
+ }
+
+ if(scroll_down && !menu->scroll_down_source_tag)
+ {
+ if (gtk_menu_scroll_down (menu, 1))
+ menu->scroll_down_source_tag =
+ g_timeout_add (MENU_SCROLL_TIMEOUT, gtk_menu_timeout_down, menu);
+ }
+}
+
+static gboolean
+gtk_menu_scroll_up (GtkMenu *menu,
+ guint scroll_count)
+{
+ gboolean return_value;
+ gint x, y, width, height;
+ gint screen_height;
+ gint on_line;
+ gint last_line;
+ gint on_child;
+ gint border;
+ GList *child;
+ GtkAllocation allocation;
+
+ return_value = TRUE;
+
+ gdk_window_get_geometry (menu->toplevel->window,
+ &x, &y,
+ &width, &height,
+ NULL);
+ screen_height = gdk_screen_height ();
+
+ border = GTK_CONTAINER (menu)->border_width +
+ GTK_WIDGET (menu)->style->klass->ythickness;
+
+ menu->first_scroll_child--;
+ if (menu->first_scroll_child <= 0) {
+ menu->first_scroll_child = -1;
+ return_value = FALSE;
+ }
+
+ on_line = y + border;
+ last_line = on_line;
+ if (menu->first_scroll_child != -1)
+ on_line += MENU_SCROLL_ARROW_HEIGHT;
+
+ on_child = 0;
+ child = GTK_MENU_SHELL (menu)->children;
+ while (child && on_line + border + MENU_SCROLL_ARROW_HEIGHT < screen_height)
+ {
+ GtkWidget *widget;
+
+ widget = GTK_WIDGET (child->data);
+ if (on_child >= menu->first_scroll_child
+ && GTK_WIDGET_VISIBLE (widget))
+ {
+ last_line = on_line;
+ on_line += widget->allocation.height;
+ }
+
+ on_child++;
+ child = child->next;
+ }
+ if (child)
+ {
+ on_line = last_line;
+ on_child--;
+ }
+ if (child)
+ {
+ menu->last_scroll_child = on_child - 1;
+ if (menu->last_scroll_child < menu->first_scroll_child)
+ menu->last_scroll_child = menu->first_scroll_child;
+ on_line += MENU_SCROLL_ARROW_HEIGHT + border;
+ }
+ else
+ {
+ menu->last_scroll_child = -1;
+ on_line += border;
+ }
+
+ gdk_window_move_resize (menu->toplevel->window,
+ x, y, width, on_line - y);
+
+ allocation.x = 0;
+ allocation.y = 0;
+ allocation.width = width;
+ allocation.height = on_line - y;
+ gtk_widget_size_allocate (GTK_WIDGET (menu), &allocation);
+
+ return return_value;
+}
+
+static gboolean
+gtk_menu_scroll_down (GtkMenu *menu,
+ guint scroll_count)
+{
+ gboolean return_value;
+ gint x, y, width, height;
+ gint screen_height;
+ gint on_line;
+ gint last_line;
+ gint new_height;
+ gint on_child;
+ gint border;
+ GList *children_copy;
+ GList *child;
+ GtkAllocation allocation;
+
+ return_value = TRUE;
+
+ gdk_window_get_geometry (menu->toplevel->window,
+ &x, &y,
+ &width, &height,
+ NULL);
+ screen_height = gdk_screen_height ();
+
+ border = GTK_CONTAINER (menu)->border_width +
+ GTK_WIDGET (menu)->style->klass->ythickness;
+
+ menu->last_scroll_child++;
+ if (menu->last_scroll_child >=
+ g_list_length (GTK_MENU_SHELL (menu)->children)) {
+ menu->last_scroll_child = -1;
+ return_value = FALSE;
+ }
+
+ on_line = y + height - border;
+ last_line = on_line;
+ if (menu->last_scroll_child != -1)
+ on_line -= MENU_SCROLL_ARROW_HEIGHT;
+
+ on_child = g_list_length (GTK_MENU_SHELL (menu)->children) - 1;
+ children_copy = g_list_copy (GTK_MENU_SHELL (menu)->children);
+ children_copy = g_list_reverse(children_copy);
+ child = GTK_MENU_SHELL (menu)->children;
+ while (child && on_line - border - MENU_SCROLL_ARROW_HEIGHT >= 0)
+ {
+ GtkWidget *widget;
+
+ widget = GTK_WIDGET (child->data);
+ if ((on_child <= menu->last_scroll_child
+ || menu->last_scroll_child == -1)
+ && GTK_WIDGET_VISIBLE (widget))
+ {
+ last_line = on_line;
+ on_line -= widget->allocation.height;
+ }
+
+ on_child--;
+ child = child->next;
+ }
+ if (child)
+ {
+ on_line = last_line;
+ on_child++;
+ }
+ if (child)
+ {
+ menu->first_scroll_child = on_child + 1;
+ if (menu->first_scroll_child > menu->last_scroll_child
+ && menu->last_scroll_child != -1)
+ menu->first_scroll_child = menu->last_scroll_child;
+ on_line -= MENU_SCROLL_ARROW_HEIGHT + border;
+ }
+ else
+ {
+ menu->first_scroll_child = -1;
+ on_line -= border;
+ }
+
+ new_height = y + height - on_line;
+
+ gdk_window_move_resize (menu->toplevel->window,
+ x, on_line, width, new_height);
+
+ allocation.x = 0;
+ allocation.y = 0;
+ allocation.width = width;
+ allocation.height = new_height;
+ gtk_widget_size_allocate (GTK_WIDGET (menu), &allocation);
+
+ g_list_free(children_copy);
+
+ return return_value;
+}
+
+static gboolean
+gtk_menu_timeout_up (gpointer data)
+{
+ GtkMenu *menu;
+
+ menu = GTK_MENU (data);
+
+ if (gtk_menu_scroll_up (menu, 1))
+ return TRUE;
+ else
+ {
+ menu->scroll_up_source_tag = 0;
+ return FALSE;
+ }
+}
+
+static gboolean
+gtk_menu_timeout_down (gpointer data)
+{
+ GtkMenu *menu;
+
+ menu = GTK_MENU (data);
+
+ if (gtk_menu_scroll_down (menu, 1))
+ return TRUE;
+ else
+ {
+ menu->scroll_down_source_tag = 0;
+ return FALSE;
+ }
+}
Index: gtk/gtkmenu.h
===================================================================
RCS file: /home/mrk/cvs/gtk+/gtk/gtkmenu.h,v
retrieving revision 1.1.1.1
retrieving revision 1.3
diff -u -r1.1.1.1 -r1.3
--- gtk/gtkmenu.h 1999/09/05 22:42:35 1.1.1.1
+++ gtk/gtkmenu.h 1999/09/06 23:38:07 1.3
@@ -74,6 +74,16 @@
GtkWidget *tearoff_window;
guint torn_off : 1;
+
+ /* Internal information used for the scroll arrow handling */
+ gint first_scroll_child;
+ gint last_scroll_child;
+
+ gboolean top_scroll_arrow_lit;
+ gboolean bottom_scroll_arrow_lit;
+
+ guint scroll_up_source_tag;
+ guint scroll_down_source_tag;
};
struct _GtkMenuClass
Index: gtk/gtkoptionmenu.c
===================================================================
RCS file: /home/mrk/cvs/gtk+/gtk/gtkoptionmenu.c,v
retrieving revision 1.1.1.1
retrieving revision 1.2
diff -u -r1.1.1.1 -r1.2
--- gtk/gtkoptionmenu.c 1999/09/05 22:42:33 1.1.1.1
+++ gtk/gtkoptionmenu.c 1999/09/06 18:00:22 1.2
@@ -618,7 +618,6 @@
GtkWidget *active;
GtkWidget *child;
GList *children;
- gint shift_menu;
gint screen_width;
gint screen_height;
gint menu_xpos;
@@ -658,31 +657,6 @@
screen_width = gdk_screen_width ();
screen_height = gdk_screen_height ();
-
- shift_menu = FALSE;
- if (menu_ypos < 0)
- {
- menu_ypos = 0;
- shift_menu = TRUE;
- }
- else if ((menu_ypos + height) > screen_height)
- {
- menu_ypos -= ((menu_ypos + height) - screen_height);
- shift_menu = TRUE;
- }
-
- if (shift_menu)
- {
- if ((menu_xpos + GTK_WIDGET (option_menu)->allocation.width + width) <= screen_width)
- menu_xpos += GTK_WIDGET (option_menu)->allocation.width;
- else
- menu_xpos -= width;
- }
-
- if (menu_xpos < 0)
- menu_xpos = 0;
- else if ((menu_xpos + width) > screen_width)
- menu_xpos -= ((menu_xpos + width) - screen_width);
*x = menu_xpos;
*y = menu_ypos;
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]