/* Functions for creating and updating GTK widgets. Copyright (C) 2002 Free Software Foundation, Inc. This file is part of GNU Emacs. GNU Emacs is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. GNU Emacs 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 General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Emacs; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "config.h" #ifdef USE_GTK #include "lisp.h" #include "xterm.h" #include "blockinput.h" #include "gtkutil.h" #include /*********************************************************************** Menus and dialog functions. ***********************************************************************/ /* Linked list of all allocated struct menu_gtk_data. Used for marking during GC. */ static struct menu_gtk_data *menu_gtk_data_list = 0; /* The next two variables and functions are taken from lwlib. */ static widget_value *widget_value_free_list = 0; static int malloc_cpt = 0; widget_value * malloc_widget_value () { widget_value *wv; if (widget_value_free_list) { wv = widget_value_free_list; widget_value_free_list = wv->free_list; wv->free_list = 0; } else { wv = (widget_value *) malloc (sizeof (widget_value)); malloc_cpt++; } memset (wv, 0, sizeof (widget_value)); return wv; } /* this is analogous to free(). It frees only what was allocated by malloc_widget_value(), and no substructures. */ void free_widget_value (wv) widget_value *wv; { if (wv->free_list) abort (); if (malloc_cpt > 25) { /* When the number of already allocated cells is too big, We free it. */ free (wv); malloc_cpt--; } else { wv->free_list = widget_value_free_list; widget_value_free_list = wv; } } /* Allocate and return a utf8 version of STR. If STR is already utf8 or NULL, just return STR. If not, a new string is allocated and the caller must free the result with g_free(). */ static char* get_utf8_string (str) char *str; { char *utf8_str = str; /* If not UTF-8, try current locale. */ if (str && !g_utf8_validate (str, -1, NULL)) utf8_str = g_locale_to_utf8 (str, -1, 0, 0, 0); return utf8_str; } /* Allocate and initialize CL_DATA if NULL, otherwise increase ref_count */ struct menu_gtk_data* make_cl_data (cl_data, f) struct menu_gtk_data *cl_data; FRAME_PTR f; { if (! cl_data) { cl_data = (struct menu_gtk_data*) xmalloc (sizeof (*cl_data)); cl_data->f = f; cl_data->menu_bar_vector = f->menu_bar_vector; cl_data->menu_bar_items_used = f->menu_bar_items_used; cl_data->ref_count = 0; cl_data->next = menu_gtk_data_list; if (menu_gtk_data_list) menu_gtk_data_list->prev = cl_data; cl_data->prev = 0; menu_gtk_data_list = cl_data; } cl_data->ref_count++; return cl_data; } /* Function that marks all lisp data during GC. */ void xg_mark_data () { struct menu_gtk_data *iter = menu_gtk_data_list; while (iter) { mark_object (&iter->menu_bar_vector); iter = iter->next; } } /* Callback called when a menu item is destroyed. Used to free data. */ static void menuitem_destroy_callback (w, client_data) GtkWidget *w; gpointer client_data; { struct menu_gtk_data *data = (struct menu_gtk_data*) client_data; if (data && data->ref_count > 0) { data->ref_count--; if (data->ref_count == 0) { if (data == menu_gtk_data_list) { menu_gtk_data_list = data->next; if (menu_gtk_data_list) menu_gtk_data_list->prev = 0; } else { data->prev->next = data->next; if (data->next) data->next->prev = data->prev; } xfree (data); } } } /* Make and return a menu item with the key to the right. Unfortunately, keys don't line up as nicely as in Motif, but the MacOS X version doesn't either, so I guess that is OK. */ static GtkWidget* make_menu_item (utf8_label, utf8_key, item, group) char *utf8_label; char *utf8_key; widget_value* item; GSList **group; { GtkWidget *w; GtkWidget *wtoadd = 0; if (utf8_key) { GtkWidget *wlbl; GtkWidget *wkey; wtoadd = gtk_hbox_new (FALSE, 0); wlbl = gtk_label_new_with_mnemonic (utf8_label); wkey = gtk_label_new (utf8_key); gtk_misc_set_alignment (GTK_MISC (wlbl), 0.0, 0.5); gtk_misc_set_alignment (GTK_MISC (wkey), 0.0, 0.5); gtk_box_pack_start (GTK_BOX (wtoadd), wlbl, TRUE, TRUE, 0); gtk_box_pack_start (GTK_BOX (wtoadd), wkey, FALSE, FALSE, 0); gtk_widget_show (wlbl); gtk_widget_show (wkey); gtk_widget_show (wtoadd); } if (item->button_type == BUTTON_TYPE_TOGGLE) { *group = NULL; if (utf8_key) w = gtk_check_menu_item_new (); else w = gtk_check_menu_item_new_with_mnemonic (utf8_label); gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (w), item->selected); } else if (item->button_type == BUTTON_TYPE_RADIO) { if (utf8_key) w = gtk_radio_menu_item_new (*group); else w = gtk_radio_menu_item_new_with_mnemonic (*group, utf8_label); *group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (w)); if (item->selected) gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (w), TRUE); } else { *group = NULL; if (utf8_key) w = gtk_menu_item_new (); else w = gtk_menu_item_new_with_mnemonic (utf8_label); } if (wtoadd) gtk_container_add (GTK_CONTAINER (w), wtoadd); if (! item->enabled) gtk_widget_set_sensitive (w, FALSE); return w; } /* Return non-zero if NAME specifies a separator (GTK only has one separator type) */ static int xg_separator_p (char *name) { return strcmp (name, "--") == 0 || strcmp (name, "--:") == 0 || strcmp (name, "---") == 0; } /* Create a full menu tree specified by DATA. */ static GtkWidget* createMenus (data, f, activate_cb, select_cb, deactivate_cb, help_cb, isPopup, isMenubar, topmenu, cl_data) widget_value* data; FRAME_PTR f; GCallback activate_cb; GCallback select_cb; GCallback deactivate_cb; int isMenubar; GtkWidget *topmenu; struct menu_gtk_data *cl_data; { widget_value* item; GtkWidget* wmenu = topmenu; GSList* group = NULL; if (! topmenu) { if (! isMenubar) wmenu = gtk_menu_new (); else wmenu = gtk_menu_bar_new (); if (deactivate_cb) g_signal_connect(wmenu, "deactivate", deactivate_cb, 0); } if (! isMenubar) { GtkWidget *tearoff = gtk_tearoff_menu_item_new (); gtk_menu_shell_append (GTK_MENU_SHELL (wmenu), tearoff); gtk_widget_show (tearoff); } for (item = data; item; item = item->next) { GtkWidget *w; if (isPopup && !item->contents && !item->call_data && !xg_separator_p (item->name)) { char *utf8_label; /* A title for a popup. We do the same as GTK does when creating titles, but it is very ugly looking. */ group = NULL; utf8_label = get_utf8_string (item->name); gtk_menu_set_title(GTK_MENU(wmenu), utf8_label); w = gtk_menu_item_new_with_mnemonic (utf8_label); gtk_widget_set_sensitive (w, FALSE); if (utf8_label && utf8_label != item->name) g_free (utf8_label); } else if (xg_separator_p (item->name)) { group = NULL; /* GTK only have one separator type. */ w = gtk_separator_menu_item_new (); } else { char *utf8_label; char *utf8_key; utf8_label = get_utf8_string (item->name); utf8_key = get_utf8_string (item->key); w = make_menu_item (utf8_label, utf8_key, item, &group); if (utf8_label && utf8_label != item->name) g_free (utf8_label); if (utf8_key && utf8_key != item->key) g_free (utf8_key); /* Assume "Help" is the last menu in the menubar. */ if (isMenubar && ! item->next) gtk_menu_item_right_justify (GTK_MENU_ITEM (w)); /* final item, not a submenu */ if (item->call_data && ! item->contents) { if (select_cb) { cl_data = make_cl_data (cl_data, f); g_signal_connect(w, "activate", select_cb, item->call_data); g_signal_connect(w, "destroy", menuitem_destroy_callback, cl_data); /* Put cl_data in widget, so select_cb can get it */ g_object_set_data (G_OBJECT (w), G_FRAME_DATA, (gpointer)cl_data); } /* help_cb NYI */ } if (item->contents) { GtkWidget *submenu = createMenus (item->contents, f, activate_cb, select_cb, deactivate_cb, help_cb, 0, 0, 0, cl_data); gtk_menu_item_set_submenu (GTK_MENU_ITEM (w), submenu); if (activate_cb) g_signal_connect(w, "activate", activate_cb, 0); gtk_widget_show (submenu); } } gtk_menu_shell_append (GTK_MENU_SHELL (wmenu), w); gtk_widget_show (w); } return wmenu; } static char* get_dialog_title (char key) { char *title = ""; switch (key) { case 'E': case 'e': title = "Error"; break; case 'I': case 'i': title = "Information"; break; case 'L': case 'l': title = "Prompt"; break; case 'P': case 'p': title = "Prompt"; break; case 'Q': case 'q': title = "Question"; break; } return title; } static GtkWidget* createDialog (wv, select_cb, deactivate_cb) widget_value* wv; GCallback select_cb; GCallback deactivate_cb; { char *title = get_dialog_title (wv->name[0]); int total_buttons = wv->name[1] - '0'; int right_buttons = wv->name[4] - '0'; int left_buttons; int button_nr = 0; int button_spacing = 10; GtkWidget *wdialog = gtk_dialog_new (); widget_value* item; if (right_buttons == 0) right_buttons = total_buttons/2; left_buttons = total_buttons - right_buttons; gtk_window_set_title (GTK_WINDOW (wdialog), title); if (deactivate_cb) { g_signal_connect(wdialog, "close", deactivate_cb, 0); g_signal_connect(wdialog, "response", deactivate_cb, 0); } for (item = wv->contents; item; item = item->next) { char *utf8_label = get_utf8_string (item->value); GtkWidget *w; GtkRequisition req; if (strcmp (item->name, "message") == 0) { w = gtk_label_new (utf8_label); gtk_box_pack_start (GTK_BOX (GTK_DIALOG (wdialog)->vbox), gtk_label_new (""), FALSE, FALSE, 0); gtk_box_pack_start (GTK_BOX (GTK_DIALOG (wdialog)->vbox), w, TRUE, TRUE, 0); gtk_misc_set_alignment (GTK_MISC (w), 0.1, 0.5); gtk_widget_realize (w); /* Try to make dialog look better. */ gtk_widget_size_request (w, &req); gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (wdialog)->vbox), req.height); if (strlen(item->value) > 0) button_spacing = 2*req.width/strlen(item->value); } else { w = gtk_button_new_with_mnemonic (utf8_label); if (! item->enabled) gtk_widget_set_sensitive (w, FALSE); if (select_cb) g_signal_connect(w, "clicked", select_cb, item->call_data); gtk_box_pack_start (GTK_BOX (GTK_DIALOG (wdialog)->action_area), w, TRUE, TRUE, button_spacing); if (++button_nr == left_buttons) gtk_box_pack_start (GTK_BOX (GTK_DIALOG (wdialog)->action_area), gtk_label_new (""), TRUE, TRUE, button_spacing); } if (utf8_label && utf8_label != item->value) g_free (utf8_label); gtk_widget_show (w); } return wdialog; } /* Create a menubar, popup menu or dialog, depending on the TYPE argument. */ GtkWidget* xg_create_widget (type, name, f, val, pop_up_p, activate_cb, select_cb, deactivate_cb, help_cb) char* type; char* name; FRAME_PTR f; widget_value* val; int pop_up_p; GCallback activate_cb; GCallback select_cb; GCallback deactivate_cb; GCallback help_cb; { GtkWidget *w = 0; if (strcmp (type, "dialog") == 0) { w = createDialog (val, select_cb, deactivate_cb); gtk_window_set_transient_for (GTK_WINDOW (w), GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f))); } else if (strcmp (type, "menubar") == 0 || strcmp (type, "popup") == 0) { w = createMenus (val->contents, f, activate_cb, select_cb, deactivate_cb, help_cb, pop_up_p, strcmp (type, "menubar") == 0, 0, 0); } else { fprintf (stderr, "bad type in xg_create_widget: %s, doing nothing\n", type); } return w; } /* Remove all children in a GTK container. */ static void remove_all_children (w) GtkWidget *w; { GList *list = gtk_container_get_children (GTK_CONTAINER (w)); GList *iter; if (! list) return; for (iter = g_list_copy (list); iter; iter = g_list_next (iter)) { GtkWidget *witem = GTK_WIDGET (iter->data); gtk_container_remove (GTK_CONTAINER (w), witem); } g_list_free (iter); } /* Update the MENUBAR. If DEEP_P is non-zero, rebuild all but the top level menu names in the MENUBAR. If DEEP_P is zero, just rebuild everything. */ void xg_modify_menubar_widgets (menubar, f, val, deep_p, activate_cb, select_cb, deactivate_cb, help_cb) GtkWidget *menubar; FRAME_PTR f; widget_value* val; int deep_p; GCallback activate_cb; GCallback select_cb; GCallback deactivate_cb; GCallback help_cb; { GList *list = gtk_container_get_children (GTK_CONTAINER (menubar)); GList *iter; if (! list) return; if (! deep_p) { widget_value* cur = val->contents; int is_changed = 0; /* Check if the change is actually no change. */ for (iter = g_list_first (list); iter && cur && ! is_changed; cur = cur->next, iter = g_list_next (iter)) { GtkMenuItem *witem = GTK_MENU_ITEM (iter->data); GtkLabel *wlabel = GTK_LABEL (gtk_bin_get_child (GTK_BIN (witem))); char *utf8_label = get_utf8_string (cur->name); is_changed = strcmp (utf8_label, gtk_label_get_label (wlabel)); } is_changed = ! (cur == 0 && iter == 0); if (! is_changed) { return; } /* Keep menubar, but change everything else. This is simple, but not efficient. Ok for now. */ remove_all_children (menubar); (void)createMenus (val->contents, f, activate_cb, select_cb, deactivate_cb, help_cb, 0, 1, menubar, 0); } else { /* Keep the top level submenu names intact, but change everything else. This is simple, but not efficient. We must keep the submenu names (GTK menu item widgets) since the X Window in the XEvent that activates the menu are those widgets. */ widget_value* cur; for (cur = val->contents; cur; cur = cur->next) { for (iter = list ; iter; iter = g_list_next (iter)) { GtkMenuItem *witem = GTK_MENU_ITEM (iter->data); GtkWidget *wlbl = gtk_bin_get_child (GTK_BIN (witem)); const char *text = gtk_label_get_text (GTK_LABEL (wlbl)); GtkWidget *sub = gtk_menu_item_get_submenu (witem); GtkWidget *newsub; if (strcmp (text, cur->name)) continue; if (sub) gtk_menu_item_remove_submenu (witem); newsub = createMenus (cur->contents, f, activate_cb, select_cb, deactivate_cb, help_cb, 0, 0, 0, 0); gtk_menu_item_set_submenu (witem, newsub); break; } } } } /* Recompute all the widgets of frame F, when the menu bar has been changed. Value is non-zero if widgets were updated. */ int xg_update_frame_menubar (f) FRAME_PTR f; { struct x_output *x = f->output_data.x; GtkRequisition req; if (!x->menubar_widget || GTK_WIDGET_MAPPED (x->menubar_widget)) return 0; BLOCK_INPUT; gtk_box_pack_start (GTK_BOX (x->vbox_widget), x->menubar_widget, FALSE, FALSE, 0); gtk_box_reorder_child (GTK_BOX (x->vbox_widget), x->menubar_widget, 0); gtk_widget_show (x->menubar_widget); gtk_widget_size_request (x->menubar_widget, &req); FRAME_MENUBAR_HEIGHT (f) = req.height; xg_set_wm_size_hints (f, 0, 0); gtk_window_resize (GTK_WINDOW (x->widget), PIXEL_WIDTH (f), req.height + PIXEL_HEIGHT (f)); gdk_window_process_all_updates (); SET_FRAME_GARBAGED (f); UNBLOCK_INPUT; } /* Get rid of the menu bar of frame F, and free its storage. This is used when deleting a frame, and when turning off the menu bar. */ void xg_free_frame_menubar (f) FRAME_PTR f; { struct x_output *x = f->output_data.x; if (x->menubar_widget) { int rows = f->height; int columns = f->width; int menubar_height = FRAME_MENUBAR_HEIGHT (f); BLOCK_INPUT; gtk_container_remove (GTK_CONTAINER (x->vbox_widget), x->menubar_widget); /* The menubar and its children shall be deleted when removed from the container. */ x->menubar_widget = 0; FRAME_MENUBAR_HEIGHT (f) = 0; /* base_height is now changed. */ xg_set_wm_size_hints (f, 0, 0); SET_FRAME_GARBAGED (f); UNBLOCK_INPUT; } } /*********************************************************************** General functions for creating widgets, resizing, events, e.t.c. ***********************************************************************/ /* Set this to non-zero to skip Emacs event processing. For dialogs and popups. */ int xg_pass_through_events = FALSE; /* Function to handle resize of our widgets. Since Emacs has some layouts that does not fit well with GTK standard containers, we do most layout manually. */ void xg_resize_widgets (f, pixelwidth, pixelheight) FRAME_PTR f; int pixelwidth, pixelheight; { int mbheight = FRAME_MENUBAR_HEIGHT (f); int rows = PIXEL_TO_CHAR_HEIGHT (f, pixelheight - mbheight); int columns = PIXEL_TO_CHAR_WIDTH (f, pixelwidth); if (FRAME_GTK_WIDGET (f) && (columns != FRAME_WIDTH (f) || rows != FRAME_HEIGHT (f) || pixelwidth != PIXEL_WIDTH (f) || pixelheight != PIXEL_HEIGHT (f))) { struct x_output *x = f->output_data.x; GtkAllocation all; all.y = mbheight; all.x = 0; all.width = pixelwidth; all.height = pixelheight - mbheight; gtk_widget_size_allocate (x->edit_widget, &all); gdk_window_process_all_updates (); change_frame_size (f, rows, columns, 0, 1, 0); SET_FRAME_GARBAGED (f); cancel_mouse_face (f); } } /* Update our widget size to be COLS/ROWS */ void xg_frame_set_char_size (f, cols, rows) FRAME_PTR f; int cols; int rows; { int pixelheight = CHAR_TO_PIXEL_HEIGHT (f, rows) + FRAME_MENUBAR_HEIGHT (f); int pixelwidth = CHAR_TO_PIXEL_WIDTH (f, cols); xg_resize_widgets (f, pixelwidth, pixelheight); } /* Convert an X Window to its corresponding GtkWidget. Must be done like this, because GtkWidget:s can have "hidden" X Window that aren't accessible. Return 0 if no widget match WDESC. */ GtkWidget* xg_win_to_widget (wdesc) Window wdesc; { BLOCK_INPUT; gpointer gdkwin = gdk_xid_table_lookup (wdesc); GtkWidget *gwdesc = 0; if (gdkwin) { GdkEvent event; event.any.window = gdkwin; gwdesc = gtk_get_event_widget (&event); } UNBLOCK_INPUT; return gwdesc; } static void xg_pix2Gcolor (c, pixel) GdkColor *c; unsigned long pixel; { c->red = c->green = c->blue = 0; c->pixel = pixel; /* I've looked at the Gdk source, this works for X */ } int xg_create_frame_widgets (f) FRAME_PTR f; { GtkWidget *wtop; GtkWidget *wvbox; GtkWidget *wfixed; GdkColor bg; int i; BLOCK_INPUT; wtop = gtk_window_new (GTK_WINDOW_TOPLEVEL); wvbox = gtk_vbox_new (FALSE, 0); wfixed = gtk_fixed_new (); /* Must have this to place scrollbars */ if (! wtop || ! wvbox || ! wfixed) { if (wtop) gtk_widget_destroy (wtop); if (wvbox) gtk_widget_destroy (wvbox); if (wfixed) gtk_widget_destroy (wfixed); return 0; } FRAME_GTK_OUTER_WIDGET (f) = wtop; FRAME_GTK_WIDGET (f) = wfixed; f->output_data.x->vbox_widget = wvbox; gtk_fixed_set_has_window (GTK_FIXED (wfixed), TRUE); gtk_widget_set_size_request (wfixed, PIXEL_WIDTH (f), PIXEL_HEIGHT (f)); gtk_container_add (GTK_CONTAINER (wtop), wvbox); gtk_box_pack_end (GTK_BOX (wvbox), wfixed, TRUE, TRUE, 0); gtk_widget_set_double_buffered (wvbox, FALSE); gtk_widget_set_double_buffered (wfixed, FALSE); gtk_widget_set_double_buffered (wtop, FALSE); /* GTK documents says use gtk_window_set_resizable. But then a user can't shrink the window from its starting size. */ gtk_window_set_policy (GTK_WINDOW (wtop), TRUE, TRUE, TRUE); gtk_window_set_wmclass (GTK_WINDOW (wtop), SDATA (Vx_resource_name), SDATA (Vx_resource_class)); /* Convert our geometry parameters into a geometry string and specify it. GTK will itself handle calculating the real position this way. */ if (f->output_data.x->size_hint_flags & USPosition) { int left = f->output_data.x->left_pos; int xneg = f->output_data.x->size_hint_flags & XNegative; int top = f->output_data.x->top_pos; int yneg = f->output_data.x->size_hint_flags & YNegative; char geom_str[32]; if (xneg) left = -left; if (yneg) top = -top; sprintf (geom_str, "=%dx%d%c%d%c%d", PIXEL_WIDTH (f), PIXEL_HEIGHT (f) + FRAME_MENUBAR_HEIGHT (f), (xneg ? '-' : '+'), left, (yneg ? '-' : '+'), top); if (!gtk_window_parse_geometry (GTK_WINDOW (wtop), geom_str)) fprintf (stderr, "Failed to parse: '%s'\n", geom_str); } gtk_widget_add_events (wfixed, GDK_POINTER_MOTION_MASK | GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_KEY_PRESS_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_FOCUS_CHANGE_MASK | GDK_STRUCTURE_MASK | GDK_VISIBILITY_NOTIFY_MASK); /* Must realize the windows so the X window gets created. It is used by callers of this function. */ gtk_widget_realize (wfixed); FRAME_X_WINDOW (f) = GTK_WIDGET_TO_X_WIN (wfixed); /* Since GTK clears its window by filling with the background color, we must keep X and GTK background in sync. */ xg_pix2Gcolor (&bg, (f)->output_data.x->background_pixel); gdk_window_set_background (wfixed->window, &bg); /* GTK does not set any border, and they look bad with GTK. */ f->output_data.x->border_width = 0; f->output_data.x->internal_border_width = 0; UNBLOCK_INPUT; return 1; } /* Set the normal size hints for the window manager, for frame F. FLAGS is the flags word to use--or 0 meaning preserve the flags that the window now has. If USER_POSITION is nonzero, we set the User Position flag (this is useful when FLAGS is 0). */ void xg_set_wm_size_hints (f, flags, user_position) FRAME_PTR f; long flags; int user_position; { if (FRAME_GTK_OUTER_WIDGET (f)) { /* Must use GTK routines here, otherwise GTK resets the size hints to its own defaults. */ GdkGeometry size_hints; gint hint_flags = 0; int base_width, base_height; int min_rows = 0, min_cols = 0; int win_gravity = f->output_data.x->win_gravity; if (flags) { memset (&size_hints, 0, sizeof (size_hints)); f->output_data.x->size_hints = size_hints; f->output_data.x->hint_flags = hint_flags; } else flags = f->output_data.x->size_hint_flags; size_hints = f->output_data.x->size_hints; hint_flags = f->output_data.x->hint_flags; hint_flags |= GDK_HINT_RESIZE_INC | GDK_HINT_MIN_SIZE; size_hints.width_inc = FONT_WIDTH (f->output_data.x->font); size_hints.height_inc = f->output_data.x->line_height; hint_flags |= GDK_HINT_BASE_SIZE; base_width = CHAR_TO_PIXEL_WIDTH (f, 0); base_height = CHAR_TO_PIXEL_HEIGHT (f, 0) + FRAME_MENUBAR_HEIGHT (f); check_frame_size (f, &min_rows, &min_cols); size_hints.base_width = base_width; size_hints.base_height = base_height; size_hints.min_width = base_width + min_cols * size_hints.width_inc; size_hints.min_height = base_height + min_rows * size_hints.height_inc; /* These currently have a one to one mapping with the X values, but I don't think we should rely on that. */ hint_flags |= GDK_HINT_WIN_GRAVITY; size_hints.win_gravity = 0; if (win_gravity == NorthWestGravity) size_hints.win_gravity = GDK_GRAVITY_NORTH_WEST; if (win_gravity == NorthGravity) size_hints.win_gravity = GDK_GRAVITY_NORTH; if (win_gravity == NorthEastGravity) size_hints.win_gravity = GDK_GRAVITY_NORTH_EAST; if (win_gravity == WestGravity) size_hints.win_gravity = GDK_GRAVITY_WEST; if (win_gravity == CenterGravity) size_hints.win_gravity = GDK_GRAVITY_CENTER; if (win_gravity == EastGravity) size_hints.win_gravity = GDK_GRAVITY_EAST; if (win_gravity == SouthWestGravity) size_hints.win_gravity = GDK_GRAVITY_SOUTH_WEST; if (win_gravity == SouthGravity) size_hints.win_gravity = GDK_GRAVITY_SOUTH; if (win_gravity == SouthEastGravity) size_hints.win_gravity = GDK_GRAVITY_SOUTH_EAST; if (win_gravity == StaticGravity) size_hints.win_gravity = GDK_GRAVITY_STATIC; if (flags & PPosition) hint_flags |= GDK_HINT_POS; if (flags & USPosition) hint_flags |= GDK_HINT_USER_POS; if (flags & USSize) hint_flags |= GDK_HINT_USER_SIZE; if (user_position) { hint_flags &= ~GDK_HINT_POS; hint_flags |= GDK_HINT_USER_POS; } BLOCK_INPUT; gtk_window_set_geometry_hints (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)), FRAME_GTK_OUTER_WIDGET (f), &size_hints, hint_flags); f->output_data.x->size_hints = size_hints; f->output_data.x->hint_flags = hint_flags; UNBLOCK_INPUT; } } /*********************************************************************** Scrollbar functions ***********************************************************************/ /* Setting scrollbar values invokes the callback. Use this variable to indicate that callback should do nothing. */ int xg_ignore_gtk_scrollbar = 0; /* After we send a scroll bar event, x_set_toolkit_scroll_bar_thumb will be called. For some reason that needs to be debugged, it gets called with bad values. Thus, we set this variable to ignore those calls. */ int xg_ignore_next_thumb = 0; /* SET_SCROLL_BAR_X_WINDOW assumes the second argument fits in 32 bits. But we want to store pointers, and they may be larger than 32 bits. Keep a mapping from int to pointers to get around the 32 bit limitation. */ static struct { GtkWidget **widgets; int max_size; int used; } id_to_widget = { 0, 0, 0 }; /* Grow this much every time we need to allocate more */ #define ID_TO_WIDGET_INCR 32 /* Store a pointer in id_to_widget and return the int. */ static int xg_store_widget_in_map (w) GtkWidget *w; { int i; if (id_to_widget.max_size == id_to_widget.used) { int new_size = id_to_widget.max_size + ID_TO_WIDGET_INCR; id_to_widget.widgets = xrealloc (id_to_widget.widgets, sizeof (GtkWidget *)*new_size); for (i = id_to_widget.max_size; i < new_size; ++i) id_to_widget.widgets[i] = 0; id_to_widget.max_size = new_size; } /* Just loop over the array and find a free place. After all, how many scrollbars are we creating? Should be a small number. The check above guarantees we will find a free place. */ for (i = 0; i < id_to_widget.max_size; ++i) { if (! id_to_widget.widgets[i]) { id_to_widget.widgets[i] = w; ++id_to_widget.used; return i; } } /* Should never end up here */ abort (); } /* Remove a pointer from id_to_widget. Called when scrollbar is destroyed. */ static void xg_remove_widget_from_map (idx) int idx; { if (idx < id_to_widget.max_size && id_to_widget.widgets[idx] != 0) { id_to_widget.widgets[idx] = 0; --id_to_widget.used; } } /* Get widget pointer from id_to_widget. */ static GtkWidget* xg_get_widget_from_map (idx) int idx; { if (idx < id_to_widget.max_size && id_to_widget.widgets[idx] != 0) return id_to_widget.widgets[idx]; return 0; } /* Callback invoked when scrollbars are destroyed. We free pointer to last scrollbar value here and remove the id. */ static void xg_gtk_scroll_destroy (widget, data) GtkWidget *widget; gpointer data; { gpointer p; int id = (int)data; p = g_object_get_data (G_OBJECT (widget), G_LAST_SB_DATA); if (p) xfree (p); xg_remove_widget_from_map (id); } void xg_create_scroll_bar (f, bar, scroll_callback, scroll_bar_name) FRAME_PTR f; struct scroll_bar *bar; GCallback scroll_callback; char *scroll_bar_name; { GtkWidget *wscroll; GtkObject *vadj; int scroll_id; /* Page, step increment values are not so important here, they will be corrected in x_set_toolkit_scroll_bar_thumb. */ vadj = gtk_adjustment_new (GTK_SB_MIN, GTK_SB_MIN, GTK_SB_MAX, 0.1, 0.1, 0.1); /* KOKO, set fg & bg, cursor? */ wscroll = gtk_vscrollbar_new (GTK_ADJUSTMENT (vadj)); gtk_widget_set_name (wscroll, scroll_bar_name); gtk_range_set_update_policy (GTK_RANGE (wscroll), GTK_UPDATE_CONTINUOUS); scroll_id = xg_store_widget_in_map (wscroll); g_signal_connect (vadj, "value-changed", scroll_callback, (gpointer)bar); g_signal_connect (wscroll, "destroy", G_CALLBACK(xg_gtk_scroll_destroy), (gpointer)scroll_id); gtk_fixed_put (GTK_FIXED (f->output_data.x->edit_widget), wscroll, 0, 0); SET_SCROLL_BAR_X_WINDOW (bar, scroll_id); } void xg_show_scroll_bar (scrollbar_id) int scrollbar_id; { GtkWidget *w = xg_get_widget_from_map (scrollbar_id); if (w) gtk_widget_show (w); } void xg_remove_scroll_bar (f, scrollbar_id) FRAME_PTR f; int scrollbar_id; { GtkWidget *w = xg_get_widget_from_map (scrollbar_id); if (w) { gtk_widget_destroy (w); SET_FRAME_GARBAGED (f); } } /* Update the position of the vertical scrollbar wscroll in frame F. */ void xg_update_scrollbar_pos (f, scrollbar_id, top, left, width, height) FRAME_PTR f; int scrollbar_id; int top; int left; int width; int height; { int gtop = top; int gheight = max (height, 1); GtkWidget *wscroll = xg_get_widget_from_map (scrollbar_id); if (wscroll) { gtk_fixed_move (GTK_FIXED (f->output_data.x->edit_widget), wscroll, left, gtop); gtk_widget_set_size_request (wscroll, width, gheight); gtk_widget_queue_draw (wscroll); /* Since we are not using a pure gtk event loop, we must force out pending update events with this call. */ gdk_window_process_all_updates (); } } /* Set the thumb size and position of scroll bar BAR. We are currently displaying PORTION out of a whole WHOLE, and our position POSITION. */ void xg_set_toolkit_scroll_bar_thumb (bar, portion, position, whole) struct scroll_bar *bar; int portion, position, whole; { GtkWidget *wscroll = xg_get_widget_from_map (SCROLL_BAR_X_WINDOW (bar)); if (wscroll && whole > 0 && ! xg_ignore_next_thumb) { GtkAdjustment* adj = gtk_range_get_adjustment (GTK_RANGE (wscroll)); gdouble top = GTK_SB_RANGE * ((gdouble) position / whole); gdouble shown = GTK_SB_RANGE * ((gdouble) portion / whole); adj->page_size = (int)shown; /* Assume a page increment is about 95% of the page size */ adj->page_increment = (int) (0.95*adj->page_size); /* Average 30 chars per line */ adj->step_increment = (int) (adj->page_size/30.0); /* gtk_range_set_value invokes the callback. Set ignore_gtk_scrollbar to make the callback do nothing */ xg_ignore_gtk_scrollbar = 1; gtk_range_set_value (GTK_RANGE (wscroll), top); xg_ignore_gtk_scrollbar = 0; } /* Make sure the scrollbar is redrawn with new thumb */ gtk_widget_queue_draw (wscroll); gdk_window_process_all_updates (); /* See comment in gtkutil.c */ xg_ignore_next_thumb = 0; } #endif /* USE_GTK */