/* Contains code from GTK - The GIMP Toolkit * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include "../libtopmenu-client/topmenu-monitor.h" #include "menuitem-proxy.h" static gboolean static_data_ok = FALSE; static const gchar *pname_visible; static const gchar *pname_sensitive; static const gchar *pname_label; static const gchar *pname_submenu; static void init_static_data() { if (!static_data_ok) { pname_visible = g_intern_string("visible"); pname_sensitive = g_intern_string("sensitive"); pname_label = g_intern_string("label"); pname_submenu = g_intern_string("submenu"); static_data_ok = TRUE; } } static void free_timeval (GTimeVal *val) { g_slice_free (GTimeVal, val); } static void get_offsets (GtkMenu *menu, gint *horizontal_offset, gint *vertical_offset) { gint vertical_padding; gint horizontal_padding; gtk_widget_style_get (GTK_WIDGET (menu), "horizontal-offset", horizontal_offset, "vertical-offset", vertical_offset, "horizontal-padding", &horizontal_padding, "vertical-padding", &vertical_padding, NULL); *vertical_offset -= GTK_WIDGET (menu)->style->ythickness; *vertical_offset -= vertical_padding; *horizontal_offset += horizontal_padding; } static void menu_item_position_menu (GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer user_data) { GtkMenuItem *menu_item; GtkWidget *widget; GtkMenuItem *parent_menu_item; GdkScreen *screen; gint twidth, theight; gint tx, ty; GtkTextDirection direction; GdkRectangle monitor; gint monitor_num; gint horizontal_offset; gint vertical_offset; gint parent_xthickness; gint available_left, available_right; g_return_if_fail (menu != NULL); g_return_if_fail (x != NULL); g_return_if_fail (y != NULL); menu_item = GTK_MENU_ITEM (user_data); widget = GTK_WIDGET (user_data); if (push_in) *push_in = FALSE; direction = gtk_widget_get_direction (widget); twidth = GTK_WIDGET (menu)->requisition.width; theight = GTK_WIDGET (menu)->requisition.height; screen = gtk_widget_get_screen (GTK_WIDGET (menu)); monitor_num = gdk_screen_get_monitor_at_window (screen, menu_item->event_window); if (monitor_num < 0) monitor_num = 0; gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor); if (!gdk_window_get_origin (widget->window, &tx, &ty)) { g_warning ("Menu not on screen"); return; } tx += widget->allocation.x; ty += widget->allocation.y; get_offsets (menu, &horizontal_offset, &vertical_offset); available_left = tx - monitor.x; available_right = monitor.x + monitor.width - (tx + widget->allocation.width); if (GTK_IS_MENU_BAR (widget->parent)) { menu_item->from_menubar = TRUE; } else if (GTK_IS_MENU (widget->parent)) { if (GTK_MENU (widget->parent)->parent_menu_item) menu_item->from_menubar = GTK_MENU_ITEM (GTK_MENU (widget->parent)->parent_menu_item)->from_menubar; else menu_item->from_menubar = FALSE; } else { menu_item->from_menubar = FALSE; } switch (menu_item->submenu_placement) { case GTK_TOP_BOTTOM: if (direction == GTK_TEXT_DIR_LTR) menu_item->submenu_direction = GTK_DIRECTION_RIGHT; else { menu_item->submenu_direction = GTK_DIRECTION_LEFT; tx += widget->allocation.width - twidth; } if ((ty + widget->allocation.height + theight) <= monitor.y + monitor.height) ty += widget->allocation.height; else if ((ty - theight) >= monitor.y) ty -= theight; else if (monitor.y + monitor.height - (ty + widget->allocation.height) > ty) ty += widget->allocation.height; else ty -= theight; break; case GTK_LEFT_RIGHT: if (GTK_IS_MENU (widget->parent)) parent_menu_item = GTK_MENU_ITEM (GTK_MENU (widget->parent)->parent_menu_item); else parent_menu_item = NULL; parent_xthickness = widget->parent->style->xthickness; if (parent_menu_item && !GTK_MENU (widget->parent)->torn_off) { menu_item->submenu_direction = parent_menu_item->submenu_direction; } else { if (direction == GTK_TEXT_DIR_LTR) menu_item->submenu_direction = GTK_DIRECTION_RIGHT; else menu_item->submenu_direction = GTK_DIRECTION_LEFT; } switch (menu_item->submenu_direction) { case GTK_DIRECTION_LEFT: if (tx - twidth - parent_xthickness - horizontal_offset >= monitor.x || available_left >= available_right) tx -= twidth + parent_xthickness + horizontal_offset; else { menu_item->submenu_direction = GTK_DIRECTION_RIGHT; tx += widget->allocation.width + parent_xthickness + horizontal_offset; } break; case GTK_DIRECTION_RIGHT: if (tx + widget->allocation.width + parent_xthickness + horizontal_offset + twidth <= monitor.x + monitor.width || available_right >= available_left) tx += widget->allocation.width + parent_xthickness + horizontal_offset; else { menu_item->submenu_direction = GTK_DIRECTION_LEFT; tx -= twidth + parent_xthickness + horizontal_offset; } break; } ty += vertical_offset; /* If the height of the menu doesn't fit we move it upward. */ ty = CLAMP (ty, monitor.y, MAX (monitor.y, monitor.y + monitor.height - theight)); break; } /* If we have negative, tx, here it is because we can't get * the menu all the way on screen. Favor the left portion. */ *x = CLAMP (tx, monitor.x, MAX (monitor.x, monitor.x + monitor.width - twidth)); *y = ty; gtk_menu_set_monitor (menu, monitor_num); if (!gtk_widget_get_visible (menu->toplevel)) { gtk_window_set_type_hint (GTK_WINDOW (menu->toplevel), menu_item->from_menubar? GDK_WINDOW_TYPE_HINT_DROPDOWN_MENU : GDK_WINDOW_TYPE_HINT_POPUP_MENU); } } static void handle_menuitem_notify(GtkMenuItem *item, GParamSpec *pspec, GtkMenuItem *proxy) { // Note that it is OK to compare strings by pointer as they are all interned if (pspec->name == pname_submenu) { // Nothing to do! } else if (pspec->name == pname_label) { const gchar *label = gtk_menu_item_get_label(item); gtk_menu_item_set_label(proxy, label); } else if (pspec->name == pname_sensitive) { gtk_widget_set_sensitive(GTK_WIDGET(proxy), gtk_widget_get_sensitive(GTK_WIDGET(item))); } else if (pspec->name == pname_visible) { if (gtk_widget_get_visible(GTK_WIDGET(item))) { gtk_widget_show(GTK_WIDGET(proxy)); } else { gtk_widget_hide(GTK_WIDGET(proxy)); } } } static gboolean handle_menuitem_mnemonic_activate(GtkMenuItem *item, gboolean cycling, GtkMenuItem *proxy) { TopMenuMonitor *monitor = topmenu_monitor_get_instance(); GtkWidget *parent = gtk_widget_get_parent(GTK_WIDGET(proxy)); if (parent && monitor->available) { GtkMenuShell *parent_shell = GTK_MENU_SHELL(parent); if (GTK_IS_MENU_BAR(parent_shell) || parent_shell->active) { gtk_widget_mnemonic_activate(GTK_WIDGET(proxy), cycling); return TRUE; } } return FALSE; } static gboolean handle_menu_leave_notify(GtkMenu *menu, GdkEvent *event, GtkMenuItem *item) { return TRUE; } static void handle_parent_move_current(GtkMenuShell *shell, GtkMenuDirectionType dir, GtkMenuItem *item) { if (dir == GTK_MENU_DIR_CHILD) { GtkWidget *submenu = gtk_menu_item_get_submenu(item); if (submenu) { gtk_menu_shell_select_first(GTK_MENU_SHELL(submenu), TRUE); } } } static void handle_proxy_select(GtkMenuItem *proxy, GtkMenuItem *item) { GtkWidget *submenu = gtk_menu_item_get_submenu(item); if (submenu) { if (!gtk_widget_is_sensitive(GTK_WIDGET(submenu))) return; GtkMenuShell *parent_shell = GTK_MENU_SHELL(GTK_WIDGET(proxy)->parent); GTimeVal *popup_time = g_slice_new0(GTimeVal); g_get_current_time(popup_time); g_object_set_data_full(G_OBJECT(submenu), "gtk-menu-exact-popup-time", popup_time, (GDestroyNotify) free_timeval); g_signal_connect_object(submenu, "leave-notify-event", G_CALLBACK(handle_menu_leave_notify), item, 0); g_signal_connect_object(gtk_widget_get_parent(GTK_WIDGET(proxy)), "move-current", G_CALLBACK(handle_parent_move_current), item, 0); gtk_menu_popup(GTK_MENU(submenu), GTK_WIDGET(parent_shell), GTK_WIDGET(proxy), menu_item_position_menu, proxy, parent_shell->button, 0); } } static void handle_proxy_deselect(GtkMenuItem *proxy, GtkMenuItem *item) { GtkWidget *submenu = gtk_menu_item_get_submenu(item); if (submenu) { g_signal_handlers_disconnect_by_func(submenu, handle_menu_leave_notify, item); g_signal_handlers_disconnect_by_func(gtk_widget_get_parent(GTK_WIDGET(proxy)), handle_parent_move_current, item); gtk_menu_popdown(GTK_MENU(submenu)); } } static void handle_proxy_activate(GtkMenuItem *proxy, GtkMenuItem *item) { GtkWidget *submenu = gtk_menu_item_get_submenu(item); if (submenu) { // Do nothing! } else { gtk_menu_item_activate(item); } } static void handle_proxy_activate_item(GtkMenuItem *proxy, GtkMenuItem *item) { GtkWidget *submenu = gtk_menu_item_get_submenu(item); if (submenu) { GtkMenuShell *parent = GTK_MENU_SHELL(gtk_widget_get_parent(GTK_WIDGET(proxy))); if (parent) { if (!parent->active) { //gtk_grab_add(GTK_WIDGET(parent)); //parent->have_grab = TRUE; parent->active = TRUE; } gtk_menu_shell_select_item(parent, GTK_WIDGET(proxy)); gtk_menu_shell_select_first(GTK_MENU_SHELL(submenu), TRUE); } } } static GtkWidget *construct_image_widget_proxy(GtkImage *widget) { GtkImageType itype = gtk_image_get_storage_type(widget); gchar *icon_name; GtkIconSize icon_size; switch (itype) { case GTK_IMAGE_STOCK: gtk_image_get_stock(widget, &icon_name, &icon_size); return gtk_image_new_from_stock(icon_name, icon_size); case GTK_IMAGE_EMPTY: return gtk_image_new(); default: return gtk_image_new(); } } GtkMenuItem *topmenu_create_proxy_menu_item(GtkMenuItem *item) { init_static_data(); GtkMenuItem *proxy = NULL; const gchar *label = gtk_menu_item_get_label(item); if (GTK_IS_IMAGE_MENU_ITEM(item)) { GtkImageMenuItem *iitem = GTK_IMAGE_MENU_ITEM(item); if (gtk_image_menu_item_get_use_stock(iitem)) { proxy = GTK_MENU_ITEM(gtk_image_menu_item_new_from_stock(label, NULL)); } else { GtkWidget *iwidget = gtk_image_menu_item_get_image(iitem); proxy = GTK_MENU_ITEM(gtk_image_menu_item_new_with_mnemonic(label)); if (iwidget) { // Let's suppport some common widget types if (GTK_IS_IMAGE(iwidget)) { gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(proxy), construct_image_widget_proxy(GTK_IMAGE(iwidget))); } } } gtk_image_menu_item_set_always_show_image(GTK_IMAGE_MENU_ITEM(proxy), gtk_image_menu_item_get_always_show_image(iitem)); } else if (GTK_IS_SEPARATOR_MENU_ITEM(item)) { proxy = GTK_MENU_ITEM(gtk_separator_menu_item_new()); } else { proxy = GTK_MENU_ITEM(gtk_menu_item_new_with_mnemonic(label)); } gtk_widget_set_sensitive(GTK_WIDGET(proxy), gtk_widget_get_sensitive(GTK_WIDGET(item))); if (gtk_widget_get_visible(GTK_WIDGET(item))) { gtk_widget_show(GTK_WIDGET(proxy)); } g_signal_connect_object(item, "notify", G_CALLBACK(handle_menuitem_notify), proxy, 0); g_signal_connect_object(item, "mnemonic-activate", G_CALLBACK(handle_menuitem_mnemonic_activate), proxy, 0); g_signal_connect_object(proxy, "select", G_CALLBACK(handle_proxy_select), item, 0); g_signal_connect_object(proxy, "deselect", G_CALLBACK(handle_proxy_deselect), item, 0); g_signal_connect_object(proxy, "activate", G_CALLBACK(handle_proxy_activate), item, 0); g_signal_connect_object(proxy, "activate-item", G_CALLBACK(handle_proxy_activate_item), item, 0); return proxy; }