X-Git-Url: https://code.delx.au/gnu-emacs/blobdiff_plain/a97e413807f78ab5a781f4135decbd10a7a4144e..f9e65eb300c487a85de743edc0bafd6434d6db5e:/src/gtkutil.c diff --git a/src/gtkutil.c b/src/gtkutil.c index 4713d13d17..61e7974935 100644 --- a/src/gtkutil.c +++ b/src/gtkutil.c @@ -35,6 +35,8 @@ Boston, MA 02111-1307, USA. */ #define FRAME_TOTAL_PIXEL_HEIGHT(f) \ (PIXEL_HEIGHT (f) + FRAME_MENUBAR_HEIGHT (f) + FRAME_TOOLBAR_HEIGHT (f)) + + /*********************************************************************** Utility functions @@ -180,7 +182,7 @@ static void xg_list_insert (xg_list_node *list, xg_list_node *node) { xg_list_node *list_start = list->next; - + if (list_start) list_start->prev = node; node->next = list_start; node->prev = 0; @@ -213,7 +215,7 @@ 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); @@ -242,7 +244,7 @@ xg_set_geometry (f) 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) @@ -260,7 +262,7 @@ xg_set_geometry (f) } } - + /* Resize the outer window of frame F after chainging the height. This happend when the menu bar or the tool bar is added or removed. COLUMNS/ROWS is the size the edit area shall have after the resize. */ @@ -285,6 +287,24 @@ xg_resize_outer_widget (f, columns, rows) gdk_window_process_all_updates (); } +/* This gets called after the frame F has been cleared. Since that is + done with X calls, we need to redraw GTK widget (scroll bars). */ +void +xg_frame_cleared (f) + FRAME_PTR f; +{ + GtkWidget *w = f->output_data.x->widget; + + if (w) + { + gtk_container_set_reallocate_redraws (GTK_CONTAINER (w), TRUE); + gtk_container_foreach (GTK_CONTAINER (w), + (GtkCallback) gtk_widget_queue_draw, + 0); + gdk_window_process_all_updates (); + } +} + /* 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. @@ -299,7 +319,7 @@ xg_resize_widgets (f, pixelwidth, pixelheight) int tbheight = FRAME_TOOLBAR_HEIGHT (f); int rows = PIXEL_TO_CHAR_HEIGHT (f, pixelheight - mbheight - tbheight); 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))) @@ -314,7 +334,8 @@ xg_resize_widgets (f, pixelwidth, pixelheight) all.height = pixelheight - mbheight - tbheight; gtk_widget_size_allocate (x->edit_widget, &all); - gdk_window_process_all_updates (); + + xg_frame_cleared (f); change_frame_size (f, rows, columns, 0, 1, 0); SET_FRAME_GARBAGED (f); @@ -333,7 +354,7 @@ xg_frame_set_char_size (f, cols, rows) int pixelheight = CHAR_TO_PIXEL_HEIGHT (f, rows) + FRAME_MENUBAR_HEIGHT (f) + FRAME_TOOLBAR_HEIGHT (f); int pixelwidth = CHAR_TO_PIXEL_WIDTH (f, cols); - + /* Take into account the size of the scroll bar. Always use the number of columns occupied by the scroll bar here otherwise we might end up with a frame width that is not a multiple of the @@ -345,7 +366,7 @@ xg_frame_set_char_size (f, cols, rows) : (FRAME_SCROLL_BAR_COLS (f) * FONT_WIDTH (f->output_data.x->font))); - x_compute_fringe_widths (f, 0); + compute_fringe_widths (f, 0); /* Must resize our top level widget. Font size may have changed, but not rows/cols. */ @@ -374,7 +395,7 @@ xg_win_to_widget (wdesc) event.any.window = gdkwin; gwdesc = gtk_get_event_widget (&event); } - + UNBLOCK_INPUT; return gwdesc; } @@ -404,13 +425,13 @@ xg_create_frame_widgets (f) GtkRcStyle *style; int i; char *title = 0; - + BLOCK_INPUT; wtop = gtk_window_new (GTK_WINDOW_TOPLEVEL); wvbox = gtk_vbox_new (FALSE, 0); wfixed = gtk_fixed_new (); /* Must have this to place scroll bars */ - + if (! wtop || ! wvbox || ! wfixed) { if (wtop) gtk_widget_destroy (wtop); @@ -430,7 +451,7 @@ xg_create_frame_widgets (f) else if (! NILP (f->name)) title = SDATA (f->name); if (title) gtk_window_set_title (GTK_WINDOW (wtop), title); - + FRAME_GTK_OUTER_WIDGET (f) = wtop; FRAME_GTK_WIDGET (f) = wfixed; f->output_data.x->vbox_widget = wvbox; @@ -440,7 +461,7 @@ xg_create_frame_widgets (f) 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); @@ -456,11 +477,11 @@ xg_create_frame_widgets (f) later on when tool bar items are added. */ if (FRAME_EXTERNAL_TOOL_BAR (f) && FRAME_TOOLBAR_HEIGHT (f) == 0) FRAME_TOOLBAR_HEIGHT (f) = 34; - + 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); @@ -472,7 +493,7 @@ xg_create_frame_widgets (f) GTK is to destroy the widget. We want Emacs to do that instead. */ g_signal_connect (G_OBJECT (wtop), "delete-event", G_CALLBACK (gtk_true), 0); - + /* Convert our geometry parameters into a geometry string and specify it. GTK will itself handle calculating the real position this way. */ @@ -508,7 +529,7 @@ xg_create_frame_widgets (f) /* Must use g_strdup because gtk_widget_modify_style does g_free. */ style->bg_pixmap_name[GTK_STATE_NORMAL] = g_strdup (""); gtk_widget_modify_style (wfixed, style); - + /* 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; @@ -538,7 +559,7 @@ x_wm_set_size_hint (f, flags, user_position) 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)); @@ -547,7 +568,7 @@ x_wm_set_size_hint (f, flags, user_position) } 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; @@ -567,7 +588,7 @@ x_wm_set_size_hint (f, flags, user_position) 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; @@ -648,7 +669,7 @@ static char * get_dialog_title (char key) { char *title = ""; - + switch (key) { case 'E': case 'e': title = "Error"; @@ -746,7 +767,7 @@ create_dialog (wv, select_cb, deactivate_cb) g_signal_connect (G_OBJECT (wdialog), "delete-event", G_CALLBACK (dialog_delete_callback), 0); - + if (deactivate_cb) { g_signal_connect (G_OBJECT (wdialog), "close", deactivate_cb, 0); @@ -876,7 +897,7 @@ xg_get_file_name (f, prompt, default_filename, mustmatch_p) GtkFileSelection *filesel; int filesel_done = XG_FILE_NOT_DONE; char *fn = 0; - + filewin = gtk_file_selection_new (prompt); filesel = GTK_FILE_SELECTION (filewin); @@ -909,9 +930,9 @@ xg_get_file_name (f, prompt, default_filename, mustmatch_p) gtk_file_selection_hide_fileop_buttons (filesel); } - + gtk_widget_show_all (filewin); - + while (filesel_done == XG_FILE_NOT_DONE) gtk_main_iteration (); @@ -950,7 +971,7 @@ static xg_list_node xg_menu_item_cb_list; The menu bar and all sub menus under the menu bar in a frame share the same structure, hence the reference count. - + Returns CL_DATA if CL_DATA is not NULL, or a pointer to a newly allocated xg_menu_cb_data if CL_DATA is NULL. */ static xg_menu_cb_data * @@ -1069,7 +1090,7 @@ menuitem_highlight_callback (w, event, client_data) { xg_menu_item_cb_data *data = (xg_menu_item_cb_data*) client_data; gpointer call_data = event->type == GDK_LEAVE_NOTIFY ? 0 : client_data; - + if (! NILP (data->help) && data->cl_data->highlight_cb) { GtkCallback func = (GtkCallback) data->cl_data->highlight_cb; @@ -1125,14 +1146,14 @@ make_widget_for_menu_item (utf8_label, utf8_key) GtkWidget *wlbl; GtkWidget *wkey; GtkWidget *wbox; - + wbox = 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 (wbox), wlbl, TRUE, TRUE, 0); gtk_box_pack_start (GTK_BOX (wbox), wkey, FALSE, FALSE, 0); @@ -1146,7 +1167,7 @@ make_widget_for_menu_item (utf8_label, utf8_key) UTF8_LABEL is the text for the menu item (GTK uses UTF8 internally). UTF8_KEY is the text representing the key binding. ITEM is the widget_value describing the menu item. - + GROUP is an in/out parameter. If the menu item to be created is not part of any radio menu group, *GROUP contains NULL on entry and exit. If the menu item to be created is part of a radio menu group, on entry @@ -1164,10 +1185,10 @@ make_menu_item (utf8_label, utf8_key, item, group) { GtkWidget *w; GtkWidget *wtoadd = 0; - + if (utf8_key) wtoadd = make_widget_for_menu_item (utf8_label, utf8_key); - + if (item->button_type == BUTTON_TYPE_TOGGLE) { *group = NULL; @@ -1189,7 +1210,7 @@ make_menu_item (utf8_label, utf8_key, item, group) 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); @@ -1230,7 +1251,7 @@ tearoff_remove (widget, event, client_data) /* Callback invoked when a menu is detached. It sets the xg_did_tearoff variable. WIDGET is the GtkTearoffMenuItem. - CLIENT_DATA is not used. */ + CLIENT_DATA is not used. */ static void tearoff_activate (widget, client_data) GtkWidget *widget; @@ -1257,7 +1278,7 @@ xg_keep_popup (menu, submenu) /* Find the top widget for the detached menu. */ p = gtk_widget_get_toplevel (submenu); - + /* Delay destroying the menu until the detached menu is removed. */ g_signal_connect (G_OBJECT (p), "unmap_event", G_CALLBACK (tearoff_remove), menu); @@ -1308,7 +1329,7 @@ xg_create_one_menuitem (item, f, select_cb, highlight_cb, cl_data, group) cb_data->help = item->help; cb_data->cl_data = cl_data; cb_data->call_data = item->call_data; - + g_signal_connect (G_OBJECT (w), "destroy", G_CALLBACK (menuitem_destroy_callback), @@ -1344,6 +1365,10 @@ xg_create_one_menuitem (item, f, select_cb, highlight_cb, cl_data, group) return w; } +static GtkWidget *create_menus P_ ((widget_value *, FRAME_PTR, GCallback, + GCallback, GCallback, int, int, int, + GtkWidget *, xg_menu_cb_data *, char *)); + /* Create a full menu tree specified by DATA. F is the frame the created menu belongs to. SELECT_CB is the callback to use when a menu item is selected. @@ -1395,7 +1420,7 @@ create_menus (data, f, select_cb, deactivate_cb, highlight_cb, g_object_set_data (G_OBJECT (wmenu), XG_FRAME_DATA, (gpointer)cl_data); g_signal_connect (G_OBJECT (wmenu), "destroy", G_CALLBACK (menu_destroy_callback), cl_data); - + if (name) gtk_widget_set_name (wmenu, name); @@ -1406,7 +1431,7 @@ create_menus (data, f, select_cb, deactivate_cb, highlight_cb, g_signal_connect (G_OBJECT (wmenu), "grab-notify", G_CALLBACK (menu_grab_callback), 0); } - + if (! menu_bar_p && add_tearoff_p) { GtkWidget *tearoff = gtk_tearoff_menu_item_new (); @@ -1419,7 +1444,7 @@ create_menus (data, f, select_cb, deactivate_cb, highlight_cb, for (item = data; item; item = item->next) { GtkWidget *w; - + if (pop_up_p && !item->contents && !item->call_data && !xg_separator_p (item->name)) { @@ -1576,18 +1601,16 @@ remove_from_container (wcont, list) GtkWidget *wcont; GList *list; { - /* We must copy list because gtk_container_remove changes it. */ - GList *clist = g_list_copy (list); GList *iter; - for (iter = clist; iter; iter = g_list_next (iter)) + for (iter = list; iter; iter = g_list_next (iter)) { GtkWidget *w = GTK_WIDGET (iter->data); /* Add a ref to w so we can explicitly destroy it later. */ gtk_widget_ref (w); gtk_container_remove (GTK_CONTAINER (wcont), w); - + /* If there is a menu under this widget that has been detached, there is a reference to it, and just removing w from the container does not destroy the submenu. By explicitly @@ -1595,39 +1618,44 @@ remove_from_container (wcont, list) removing the detached window also if there was one. */ gtk_widget_destroy (w); } - g_list_free (clist); } /* Update the top level names in MENUBAR (i.e. not submenus). F is the frame the menu bar belongs to. - LIST is a list with the current menu bar names (menu item widgets). + *LIST is a list with the current menu bar names (menu item widgets). + ITER is the item within *LIST that shall be updated. + POS is the numerical position, starting at 0, of ITER in *LIST. VAL describes what the menu bar shall look like after the update. SELECT_CB is the callback to use when a menu item is selected. HIGHLIGHT_CB is the callback to call when entering/leaving menu items. + CL_DATA points to the callback data to be used for this menu bar. This function calls itself to walk through the menu bar names. */ static void -xg_update_menubar (menubar, f, list, val, select_cb, highlight_cb, cl_data) +xg_update_menubar (menubar, f, list, iter, pos, val, + select_cb, highlight_cb, cl_data) GtkWidget *menubar; FRAME_PTR f; - GList *list; + GList **list; + GList *iter; + int pos; widget_value *val; GCallback select_cb; GCallback highlight_cb; xg_menu_cb_data *cl_data; { - if (! list && ! val) + if (! iter && ! val) return; - else if (list && ! val) + else if (iter && ! val) { - /* Item(s) have been removed. Remove all remaining items from list. */ - remove_from_container (menubar, list); + /* Item(s) have been removed. Remove all remaining items. */ + remove_from_container (menubar, iter); /* All updated. */ val = 0; - list = 0; + iter = 0; } - else if (! list && val) + else if (! iter && val) { /* Item(s) added. Add all new items in one call. */ create_menus (val, f, select_cb, 0, highlight_cb, @@ -1635,65 +1663,58 @@ xg_update_menubar (menubar, f, list, val, select_cb, highlight_cb, cl_data) /* All updated. */ val = 0; - list = 0; + iter = 0; } - /* Below this neither list or val is NULL */ - else if (xg_item_label_same_p (GTK_MENU_ITEM (list->data), val->name)) + /* Below this neither iter or val is NULL */ + else if (xg_item_label_same_p (GTK_MENU_ITEM (iter->data), val->name)) { /* This item is still the same, check next item. */ val = val->next; - list = g_list_next (list); + iter = g_list_next (iter); + ++pos; } else /* This item is changed. */ { - GtkMenuItem *witem = GTK_MENU_ITEM (list->data); + GtkMenuItem *witem = GTK_MENU_ITEM (iter->data); GtkMenuItem *witem2 = 0; - int pos = 0; int val_in_menubar = 0; - int list_in_new_menubar = 0; - GList *list2; - GList *iter; + int iter_in_new_menubar = 0; + GList *iter2; widget_value *cur; - - /* Get position number for witem. */ - list2 = gtk_container_get_children (GTK_CONTAINER (menubar)); - for (iter = list2; iter; iter = g_list_next (iter)) - { - if (list->data == iter->data) break; - ++pos; - } - /* See if the changed entry (val) is present later in the menu bar */ - for (iter = g_list_next (list); - iter && ! val_in_menubar; - iter = g_list_next (iter)) + for (iter2 = iter; + iter2 && ! val_in_menubar; + iter2 = g_list_next (iter2)) { - witem2 = GTK_MENU_ITEM (iter->data); + witem2 = GTK_MENU_ITEM (iter2->data); val_in_menubar = xg_item_label_same_p (witem2, val->name); } - /* See if the current entry (list) is present later in the + /* See if the current entry (iter) is present later in the specification for the new menu bar. */ - for (cur = val; cur && ! list_in_new_menubar; cur = cur->next) - list_in_new_menubar = xg_item_label_same_p (witem, cur->name); + for (cur = val; cur && ! iter_in_new_menubar; cur = cur->next) + iter_in_new_menubar = xg_item_label_same_p (witem, cur->name); - if (val_in_menubar && ! list_in_new_menubar) + if (val_in_menubar && ! iter_in_new_menubar) { + int nr = pos; + /* This corresponds to: Current: A B C New: A C Remove B. */ - + gtk_widget_ref (GTK_WIDGET (witem)); gtk_container_remove (GTK_CONTAINER (menubar), GTK_WIDGET (witem)); gtk_widget_destroy (GTK_WIDGET (witem)); /* Must get new list since the old changed. */ - list = gtk_container_get_children (GTK_CONTAINER (menubar)); - while (pos-- > 0) list = g_list_next (list); + g_list_free (*list); + *list = iter = gtk_container_get_children (GTK_CONTAINER (menubar)); + while (nr-- > 0) iter = g_list_next (iter); } - else if (! val_in_menubar && ! list_in_new_menubar) + else if (! val_in_menubar && ! iter_in_new_menubar) { /* This corresponds to: Current: A B C @@ -1711,19 +1732,21 @@ xg_update_menubar (menubar, f, list, val, select_cb, highlight_cb, cl_data) is up to date when leaving the minibuffer. */ GtkLabel *wlabel = GTK_LABEL (gtk_bin_get_child (GTK_BIN (witem))); char *utf8_label = get_utf8_string (val->name); - + gtk_label_set_text_with_mnemonic (wlabel, utf8_label); - list = g_list_next (list); + iter = g_list_next (iter); val = val->next; + ++pos; } - else if (! val_in_menubar && list_in_new_menubar) + else if (! val_in_menubar && iter_in_new_menubar) { /* This corresponds to: Current: A B C New: A X B C Insert X. */ + int nr = pos; GList *group = 0; GtkWidget *w = xg_create_one_menuitem (val, f, @@ -1735,13 +1758,16 @@ xg_update_menubar (menubar, f, list, val, select_cb, highlight_cb, cl_data) gtk_widget_set_name (w, MENU_ITEM_NAME); gtk_menu_shell_insert (GTK_MENU_SHELL (menubar), w, pos); - list = gtk_container_get_children (GTK_CONTAINER (menubar)); - while (pos-- > 0) list = g_list_next (list); - list = g_list_next (list); + g_list_free (*list); + *list = iter = gtk_container_get_children (GTK_CONTAINER (menubar)); + while (nr-- > 0) iter = g_list_next (iter); + iter = g_list_next (iter); val = val->next; + ++pos; } - else /* if (val_in_menubar && list_in_new_menubar) */ + else /* if (val_in_menubar && iter_in_new_menubar) */ { + int nr = pos; /* This corresponds to: Current: A B C New: A C B @@ -1753,16 +1779,17 @@ xg_update_menubar (menubar, f, list, val, select_cb, highlight_cb, cl_data) GTK_WIDGET (witem2), pos); gtk_widget_unref (GTK_WIDGET (witem2)); + g_list_free (*list); + *list = iter = gtk_container_get_children (GTK_CONTAINER (menubar)); + while (nr-- > 0) iter = g_list_next (iter); val = val->next; - list = gtk_container_get_children (GTK_CONTAINER (menubar)); - while (pos-- > 0) list = g_list_next (list); - list = g_list_next (list); + ++pos; } - } /* Update the rest of the menu bar. */ - xg_update_menubar (menubar, f, list, val, select_cb, highlight_cb, cl_data); + xg_update_menubar (menubar, f, list, iter, pos, val, + select_cb, highlight_cb, cl_data); } /* Update the menu item W so it corresponds to VAL. @@ -1785,8 +1812,8 @@ xg_update_menu_item (val, w, select_cb, highlight_cb, cl_data) const char *old_label = 0; const char *old_key = 0; xg_menu_item_cb_data *cb_data; - - wchild = gtk_bin_get_child (GTK_BIN (w)); + + wchild = gtk_bin_get_child (GTK_BIN (w)); utf8_label = get_utf8_string (val->name); utf8_key = get_utf8_string (val->key); @@ -1797,6 +1824,8 @@ xg_update_menu_item (val, w, select_cb, highlight_cb, cl_data) wlbl = GTK_LABEL (list->data); wkey = GTK_LABEL (list->next->data); + g_list_free (list); + if (! utf8_key) { /* Remove the key and keep just the label. */ @@ -1805,6 +1834,7 @@ xg_update_menu_item (val, w, select_cb, highlight_cb, cl_data) gtk_container_add (GTK_CONTAINER (w), GTK_WIDGET (wlbl)); wkey = 0; } + } else /* Just a label. */ { @@ -1815,18 +1845,20 @@ xg_update_menu_item (val, w, select_cb, highlight_cb, cl_data) { GtkWidget *wtoadd = make_widget_for_menu_item (utf8_label, utf8_key); GList *list = gtk_container_get_children (GTK_CONTAINER (wtoadd)); + wlbl = GTK_LABEL (list->data); wkey = GTK_LABEL (list->next->data); + g_list_free (list); gtk_container_remove (GTK_CONTAINER (w), wchild); gtk_container_add (GTK_CONTAINER (w), wtoadd); } } - + if (wkey) old_key = gtk_label_get_label (wkey); if (wlbl) old_label = gtk_label_get_label (wlbl); - + if (wkey && utf8_key && (! old_key || strcmp (utf8_key, old_key) != 0)) gtk_label_set_text (wkey, utf8_key); @@ -1835,7 +1867,7 @@ xg_update_menu_item (val, w, select_cb, highlight_cb, cl_data) if (utf8_key && utf8_key != val->key) g_free (utf8_key); if (utf8_label && utf8_label != val->name) g_free (utf8_label); - + if (! val->enabled && GTK_WIDGET_SENSITIVE (w)) gtk_widget_set_sensitive (w, FALSE); else if (val->enabled && ! GTK_WIDGET_SENSITIVE (w)) @@ -1848,7 +1880,7 @@ xg_update_menu_item (val, w, select_cb, highlight_cb, cl_data) cb_data->call_data = val->call_data; cb_data->help = val->help; cb_data->cl_data = cl_data; - + /* We assume the callback functions don't change. */ if (val->call_data && ! val->contents) { @@ -1944,10 +1976,10 @@ xg_update_submenu (submenu, f, val, widget_value *cur; int has_tearoff_p = 0; GList *first_radio = 0; - + if (submenu) list = gtk_container_get_children (GTK_CONTAINER (submenu)); - + for (cur = val, iter = list; cur && iter; iter = g_list_next (iter), cur = cur->next) @@ -2039,7 +2071,7 @@ xg_update_submenu (submenu, f, val, if (cur && first_radio) remove_from_container (submenu, first_radio); else remove_from_container (submenu, iter); } - + if (cur) { /* More items added. Create them. */ @@ -2055,7 +2087,9 @@ xg_update_submenu (submenu, f, val, cl_data, 0); } - + + if (list) g_list_free (list); + return newsub; } @@ -2080,17 +2114,16 @@ xg_modify_menubar_widgets (menubar, f, val, deep_p, { xg_menu_cb_data *cl_data; GList *list = gtk_container_get_children (GTK_CONTAINER (menubar)); - GList *iter; if (! list) return; - + cl_data = (xg_menu_cb_data*) g_object_get_data (G_OBJECT (menubar), XG_FRAME_DATA); if (! deep_p) { widget_value *cur = val->contents; - xg_update_menubar (menubar, f, list, cur, + xg_update_menubar (menubar, f, &list, list, 0, cur, select_cb, highlight_cb, cl_data); } else @@ -2106,6 +2139,7 @@ xg_modify_menubar_widgets (menubar, f, val, deep_p, for (cur = val->contents; cur; cur = cur->next) { + GList *iter; GtkWidget *sub = 0; GtkWidget *newsub; GtkMenuItem *witem; @@ -2120,7 +2154,7 @@ xg_modify_menubar_widgets (menubar, f, val, deep_p, break; } } - + newsub = xg_update_submenu (sub, f, cur->contents, @@ -2137,6 +2171,7 @@ xg_modify_menubar_widgets (menubar, f, val, deep_p, } } + g_list_free (list); gtk_widget_show_all (menubar); } @@ -2149,7 +2184,7 @@ xg_update_frame_menubar (f) { struct x_output *x = f->output_data.x; GtkRequisition req; - + if (!x->menubar_widget || GTK_WIDGET_MAPPED (x->menubar_widget)) return 0; @@ -2167,9 +2202,11 @@ xg_update_frame_menubar (f) /* The height has changed, resize outer widget and set columns rows to what we had before adding the menu bar. */ xg_resize_outer_widget (f, FRAME_WIDTH (f), FRAME_HEIGHT (f)); - + SET_FRAME_GARBAGED (f); UNBLOCK_INPUT; + + return 1; } /* Get rid of the menu bar of frame F, and free its storage. @@ -2211,11 +2248,6 @@ free_frame_menubar (f) to indicate that callback should do nothing. */ int xg_ignore_gtk_scrollbar; -/* 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; - /* 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 integer index to widget pointers @@ -2291,9 +2323,30 @@ xg_get_widget_from_map (idx) return 0; } +/* Return the scrollbar id for X Window WID. + Return -1 if WID not in id_to_widget. */ +int +xg_get_scroll_id_for_window (wid) + Window wid; +{ + int idx; + GtkWidget *w; + + w = xg_win_to_widget (wid); + + if (w) + { + for (idx = 0; idx < id_to_widget.max_size; ++idx) + if (id_to_widget.widgets[idx] == w) + return idx; + } + + return -1; +} + /* Callback invoked when scroll bar WIDGET is destroyed. DATA is the index into id_to_widget for WIDGET. - We free pointer to last scroll bar value here and remove the index. */ + We free pointer to last scroll bar values here and remove the index. */ static void xg_gtk_scroll_destroy (widget, data) GtkWidget *widget; @@ -2301,7 +2354,7 @@ xg_gtk_scroll_destroy (widget, data) { gpointer p; int id = (int)data; - + p = g_object_get_data (G_OBJECT (widget), XG_LAST_SB_DATA); if (p) xfree (p); xg_remove_widget_from_map (id); @@ -2309,9 +2362,10 @@ xg_gtk_scroll_destroy (widget, data) /* Callback for button press/release events. Used to start timer so that the scroll bar repetition timer in GTK gets handeled. + Also, sets bar->dragging to Qnil when dragging (button release) is done. WIDGET is the scroll bar widget the event is for (not used). EVENT contains the event. - USER_DATA is 0 (not used). + USER_DATA points to the struct scrollbar structure. Returns FALSE to tell GTK that it shall continue propagate the event to widgets. */ @@ -2323,9 +2377,13 @@ scroll_bar_button_cb (widget, event, user_data) { if (event->type == GDK_BUTTON_PRESS && ! xg_timer) xg_start_timer (); - else if (event->type == GDK_BUTTON_RELEASE && xg_timer) - xg_stop_timer (); - + else if (event->type == GDK_BUTTON_RELEASE) + { + struct scroll_bar *bar = (struct scroll_bar *) user_data; + if (xg_timer) xg_stop_timer (); + bar->dragging = Qnil; + } + return FALSE; } @@ -2345,7 +2403,7 @@ xg_create_scroll_bar (f, bar, scroll_callback, 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 (XG_SB_MIN, XG_SB_MIN, XG_SB_MAX, @@ -2354,9 +2412,9 @@ xg_create_scroll_bar (f, bar, scroll_callback, scroll_bar_name) 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 (G_OBJECT (vadj), "value-changed", scroll_callback, @@ -2371,14 +2429,14 @@ xg_create_scroll_bar (f, bar, scroll_callback, scroll_bar_name) g_signal_connect (G_OBJECT (wscroll), "button-press-event", G_CALLBACK (scroll_bar_button_cb), - (gpointer)1); + (gpointer)bar); g_signal_connect (G_OBJECT (wscroll), "button-release-event", G_CALLBACK (scroll_bar_button_cb), - 0); - + (gpointer)bar); + gtk_fixed_put (GTK_FIXED (f->output_data.x->edit_widget), - wscroll, 0, 0); + wscroll, -1, -1); /* Set the cursor to an arrow. */ xg_set_cursor (wscroll, &xg_left_ptr_cursor); @@ -2410,13 +2468,36 @@ xg_remove_scroll_bar (f, scrollbar_id) } } +/* Find left/top for widget W in GtkFixed widget WFIXED. */ +static void +xg_find_top_left_in_fixed (w, wfixed, left, top) + GtkWidget *w, *wfixed; + int *left, *top; +{ + GList *iter; + + for (iter = GTK_FIXED (wfixed)->children; iter; iter = g_list_next (iter)) + { + GtkFixedChild *child = (GtkFixedChild *) iter->data; + + if (child->widget == w) + { + *left = child->x; + *top = child->y; + return; + } + } + + /* Shall never end up here. */ + abort (); +} /* Update the position of the vertical scroll bar represented by SCROLLBAR_ID in frame F. TOP/LEFT are the new pixel positions where the bar shall appear. WIDTH, HEIGHT is the size in pixels the bar shall have. */ void -xg_update_scrollbar_pos (f, scrollbar_id, top, left, width, height) +xg_update_scrollbar_pos (f, scrollbar_id, top, left, width, height) FRAME_PTR f; int scrollbar_id; int top; @@ -2424,34 +2505,108 @@ xg_update_scrollbar_pos (f, scrollbar_id, top, left, width, height) int width; int height; { - GtkWidget *wscroll = xg_get_widget_from_map (scrollbar_id); - if (wscroll) - { - int gheight = max (height, 1); - - gtk_fixed_move (GTK_FIXED (f->output_data.x->edit_widget), - wscroll, left, top); + GtkWidget *wscroll = xg_get_widget_from_map (scrollbar_id); + + if (wscroll) + { + GtkWidget *wfixed = f->output_data.x->edit_widget; + int gheight = max (height, 1); + int canon_width = FRAME_SCROLL_BAR_COLS (f) * CANON_X_UNIT (f); + int winextra = canon_width > width ? (canon_width - width) / 2 : 0; + int bottom = top + gheight; + + gint slider_width; + int oldtop, oldleft, oldbottom; + GtkRequisition req; + + /* Get old values. */ + xg_find_top_left_in_fixed (wscroll, wfixed, &oldleft, &oldtop); + gtk_widget_size_request (wscroll, &req); + oldbottom = oldtop + req.height; + + /* Scroll bars in GTK has a fixed width, so if we say width 16, it + will only be its fixed width (14 is default) anyway, the rest is + blank. We are drawing the mode line across scroll bars when + the frame is split: + |bar| |fringe| + ---------------- + mode line + ---------------- + |bar| |fringe| + + When we "unsplit" the frame: + + |bar| |fringe| + -| |-| | + m¦ |i| | + -| |-| | + | | | | + + + the remains of the mode line can be seen in these blank spaces. + So we must clear them explicitly. + GTK scroll bars should do that, but they don't. + Also, the scroll bar canonical width may be wider than the width + passed in here. */ + + if (oldtop != -1 && oldleft != -1) + { + int gtkextra; + int xl, xr, wblank; + int bottomdiff, topdiff; + + gtk_widget_style_get (wscroll, "slider_width", &slider_width, NULL); + gtkextra = width > slider_width ? (width - slider_width) / 2 : 0; + + xl = left - winextra; + wblank = gtkextra + winextra; + xr = left + gtkextra + slider_width; + bottomdiff = abs (oldbottom - bottom); + topdiff = abs (oldtop - top); + + if (oldtop > top) + { + gdk_window_clear_area (wfixed->window, xl, top, wblank, topdiff); + gdk_window_clear_area (wfixed->window, xr, top, wblank, topdiff); + } + else if (oldtop < top) + { + gdk_window_clear_area (wfixed->window, xl, oldtop, wblank, + topdiff); + gdk_window_clear_area (wfixed->window, xr, oldtop, wblank, + topdiff); + } - gtk_widget_set_size_request (wscroll, width, gheight); + if (oldbottom > bottom) + { + gdk_window_clear_area (wfixed->window, xl, bottom, wblank, + bottomdiff); + gdk_window_clear_area (wfixed->window, xr, bottom, wblank, + bottomdiff); + } + else if (oldbottom < bottom) + { + gdk_window_clear_area (wfixed->window, xl, oldbottom, wblank, + bottomdiff); + gdk_window_clear_area (wfixed->window, xr, oldbottom, wblank, + bottomdiff); + } + } - /* Must force out update so wscroll gets the resize. - Otherwise, the gdk_window_clear clears the old window size. */ - gdk_window_process_all_updates (); + /* Move and resize to new values. */ + gtk_fixed_move (GTK_FIXED (wfixed), wscroll, left, top); + gtk_widget_set_size_request (wscroll, width, gheight); - /* The scroll bar doesn't explicitly redraw the whole window - when a resize occurs. Since the scroll bar seems to be fixed - in width it doesn't fill the space reserved, so we must clear - the whole window. */ - gdk_window_clear (wscroll->window); + gtk_container_set_reallocate_redraws (GTK_CONTAINER (wfixed), TRUE); - /* 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 (); + /* Make GTK draw the new sizes. We are not using a pure GTK event + loop so we need to do this. */ + gdk_window_process_all_updates (); - SET_FRAME_GARBAGED (f); - cancel_mouse_face (f); - } + SET_FRAME_GARBAGED (f); + cancel_mouse_face (f); + } } /* Set the thumb size and position of scroll bar BAR. We are currently @@ -2466,15 +2621,25 @@ xg_set_toolkit_scroll_bar_thumb (bar, portion, position, whole) FRAME_PTR f = XFRAME (WINDOW_FRAME (XWINDOW (bar->window))); BLOCK_INPUT; - if (wscroll && ! xg_ignore_next_thumb) + + if (wscroll && NILP (bar->dragging)) { GtkAdjustment *adj; gdouble shown; gdouble top; int size, value; - + int new_upper, new_step; + adj = gtk_range_get_adjustment (GTK_RANGE (wscroll)); + /* We do the same as for MOTIF in xterm.c, assume 30 chars per line + rather than the real portion value. This makes the thumb less likely + to resize and that looks better. */ + portion = XFASTINT (XWINDOW (bar->window)->height) * 30; + /* When the thumb is at the bottom, position == whole. + So we need to increase `whole' to make space for the thumb. */ + whole += portion; + if (whole <= 0) top = 0, shown = 1; else @@ -2491,26 +2656,40 @@ xg_set_toolkit_scroll_bar_thumb (bar, portion, position, whole) value = min (value, whole - size); value = max (value, XG_SB_MIN); - adj->upper = max (whole, size); - adj->page_size = (int)size; - - /* Assume a page increment is about 95% of the page size */ - adj->page_increment = (int) (0.95*adj->page_size); - - /* Assume all lines are equal. */ - adj->step_increment = portion / max (1, FRAME_HEIGHT (f)); - /* 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), (gdouble)value); + + new_upper = max (whole, size); + new_step = portion / max (1, FRAME_HEIGHT (f)); + + if ((int) adj->page_size != size + || (int) adj->upper != new_upper + || (int) adj->step_increment != new_step) + { + adj->page_size = (int) size; + + gtk_range_set_range (GTK_RANGE (wscroll), adj->lower, + (gdouble) new_upper); + + /* Assume all lines are of equal size. */ + /* Assume a page increment is about 95% of the page size */ + gtk_range_set_increments (GTK_RANGE (wscroll), + portion / max (1, FRAME_HEIGHT (f)), + (int) (0.95*adj->page_size)); + + } + + if ((int) gtk_range_get_value (GTK_RANGE (wscroll)) != value) + gtk_range_set_value (GTK_RANGE (wscroll), (gdouble)value); + xg_ignore_gtk_scrollbar = 0; + + /* Make GTK draw the new thumb. We are not using a pure GTK event + loop so we need to do this. */ + gdk_window_process_all_updates (); } - /* Make sure the scroll bar is redrawn with new thumb */ - gtk_widget_queue_draw (wscroll); - gdk_window_process_all_updates (); - xg_ignore_next_thumb = 0; UNBLOCK_INPUT; } @@ -2541,7 +2720,7 @@ xg_tool_bar_callback (w, client_data) return; idx *= TOOL_BAR_ITEM_NSLOTS; - + key = AREF (f->tool_bar_items, idx + TOOL_BAR_ITEM_KEY); XSETFRAME (frame, f); event.kind = TOOL_BAR_EVENT; @@ -2635,7 +2814,7 @@ xg_tool_bar_help_callback (w, event, client_data) } if (! f || ! f->n_tool_bar_items || NILP (f->tool_bar_items)) - return; + return FALSE; if (event->type == GDK_ENTER_NOTIFY) { @@ -2655,6 +2834,44 @@ xg_tool_bar_help_callback (w, event, client_data) } +/* This callback is called when a tool bar item shall be redrawn. + It modifies the expose event so that the GtkImage widget redraws the + whole image. This to overcome a bug that makes GtkImage draw the image + in the wrong place when it tries to redraw just a part of the image. + W is the GtkImage to be redrawn. + EVENT is the expose event for W. + CLIENT_DATA is unused. + + Returns FALSE to tell GTK to keep processing this event. */ +static gboolean +xg_tool_bar_item_expose_callback (w, event, client_data) + GtkWidget *w; + GdkEventExpose *event; + gpointer client_data; +{ + event->area.x = event->area.y = 0; + event->area.width = event->area.height = 1000; + return FALSE; +} + +/* This callback is called when a tool bar shall be redrawn. + We need to update the tool bar from here in case the image cache + has deleted the pixmaps used in the tool bar. + W is the GtkToolbar to be redrawn. + EVENT is the expose event for W. + CLIENT_DATA is pointing to the frame for this tool bar. + + Returns FALSE to tell GTK to keep processing this event. */ +static gboolean +xg_tool_bar_expose_callback (w, event, client_data) + GtkWidget *w; + GdkEventExpose *event; + gpointer client_data; +{ + update_frame_tool_bar((FRAME_PTR)client_data); + return FALSE; +} + static void xg_create_tool_bar (f) FRAME_PTR f; @@ -2667,27 +2884,44 @@ xg_create_tool_bar (f) x->handlebox_widget = gtk_handle_box_new (); gtk_container_add (GTK_CONTAINER (x->handlebox_widget), x->toolbar_widget); - + gtk_box_pack_start (GTK_BOX (x->vbox_widget), x->handlebox_widget, FALSE, FALSE, 0); - + gtk_box_reorder_child (GTK_BOX (x->vbox_widget), x->handlebox_widget, vbox_pos); + gtk_widget_set_name (x->toolbar_widget, "emacs-toolbar"); + + /* We only have icons, so override any user setting. We could + use the caption property of the toolbar item (see update_frame_tool_bar + below), but some of those strings are long, making the toolbar so + long it does not fit on the screen. The GtkToolbar widget makes every + item equal size, so the longest caption determine the size of every + tool bar item. I think the creators of the GtkToolbar widget + counted on 4 or 5 character long strings. */ + gtk_toolbar_set_style (GTK_TOOLBAR (x->toolbar_widget), GTK_TOOLBAR_ICONS); + gtk_toolbar_set_orientation (GTK_TOOLBAR (x->toolbar_widget), + GTK_ORIENTATION_HORIZONTAL); + g_signal_connect (G_OBJECT (x->handlebox_widget), "child-detached", G_CALLBACK (xg_tool_bar_detach_callback), f); g_signal_connect (G_OBJECT (x->handlebox_widget), "child-attached", G_CALLBACK (xg_tool_bar_attach_callback), f); + g_signal_connect (G_OBJECT (x->toolbar_widget), + "expose-event", + G_CALLBACK (xg_tool_bar_expose_callback), + f); gtk_widget_show_all (x->handlebox_widget); gtk_widget_size_request (x->toolbar_widget, &req); FRAME_TOOLBAR_HEIGHT (f) = req.height; - + /* The height has changed, resize outer widget and set columns rows to what we had before adding the tool bar. */ xg_resize_outer_widget (f, FRAME_WIDTH (f), FRAME_HEIGHT (f)); - + SET_FRAME_GARBAGED (f); } @@ -2698,19 +2932,21 @@ update_frame_tool_bar (f) int i; GtkRequisition old_req, new_req; GList *icon_list; + GList *iter; struct x_output *x = f->output_data.x; if (! FRAME_GTK_WIDGET (f)) return; BLOCK_INPUT; - + if (! x->toolbar_widget) xg_create_tool_bar (f); gtk_widget_size_request (x->toolbar_widget, &old_req); icon_list = gtk_container_get_children (GTK_CONTAINER (x->toolbar_widget)); + iter = icon_list; for (i = 0; i < f->n_tool_bar_items; ++i) { @@ -2722,9 +2958,9 @@ update_frame_tool_bar (f) int img_id; struct image *img; Lisp_Object image; - GtkWidget *wicon = icon_list ? GTK_WIDGET (icon_list->data) : 0; + GtkWidget *wicon = iter ? GTK_WIDGET (iter->data) : 0; - if (icon_list) icon_list = g_list_next (icon_list); + if (iter) iter = g_list_next (iter); /* If image is a vector, choose the image according to the button state. */ @@ -2762,7 +2998,7 @@ update_frame_tool_bar (f) if (wicon) gtk_widget_hide (wicon); continue; } - + if (! wicon) { GdkPixmap *gpix = gdk_pixmap_foreign_new (img->pixmap); @@ -2775,17 +3011,24 @@ update_frame_tool_bar (f) w, GTK_SIGNAL_FUNC (xg_tool_bar_callback), (gpointer)i); - + /* Save the image so we can see if an update is needed when this function is called again. */ g_object_set_data (G_OBJECT (w), XG_TOOL_BAR_IMAGE_DATA, - (gpointer)img); + (gpointer)img->pixmap); + + /* Catch expose events to overcome an annoying redraw bug, see + comment for xg_tool_bar_item_expose_callback. */ + g_signal_connect (G_OBJECT (w), + "expose-event", + G_CALLBACK (xg_tool_bar_item_expose_callback), + 0); /* We must set sensitive on the button that is the parent of the GtkImage parent. Go upwards until we find the button. */ while (! GTK_IS_BUTTON (w)) w = gtk_widget_get_parent (w); - + if (w) { /* Save the frame in the button so the xg_tool_bar_callback @@ -2812,14 +3055,13 @@ update_frame_tool_bar (f) /* The child of the tool bar is a button. Inside that button is a vbox. Inside that vbox is the GtkImage. */ GtkWidget *wvbox = gtk_bin_get_child (GTK_BIN (wicon)); - GList *ch = gtk_container_get_children (GTK_CONTAINER (wvbox)); - GtkImage *wimage = GTK_IMAGE (ch->data); - struct image *old_img = g_object_get_data (G_OBJECT (wimage), - XG_TOOL_BAR_IMAGE_DATA); - - if (! old_img - || old_img->pixmap != img->pixmap - || old_img->mask != img->mask) + GList *chlist = gtk_container_get_children (GTK_CONTAINER (wvbox)); + GtkImage *wimage = GTK_IMAGE (chlist->data); + Pixmap old_img = (Pixmap)g_object_get_data (G_OBJECT (wimage), + XG_TOOL_BAR_IMAGE_DATA); + g_list_free (chlist); + + if (old_img != img->pixmap) { GdkPixmap *gpix = gdk_pixmap_foreign_new (img->pixmap); GdkBitmap *gmask = img->mask ? @@ -2829,22 +3071,22 @@ update_frame_tool_bar (f) } g_object_set_data (G_OBJECT (wimage), XG_TOOL_BAR_IMAGE_DATA, - (gpointer)img); + (gpointer)img->pixmap); gtk_widget_set_sensitive (wicon, enabled_p); gtk_widget_show (wicon); } - + #undef PROP } /* Remove buttons not longer needed. We just hide them so they can be reused later on. */ - while (icon_list) + while (iter) { - GtkWidget *w = GTK_WIDGET (icon_list->data); + GtkWidget *w = GTK_WIDGET (iter->data); gtk_widget_hide (w); - icon_list = g_list_next (icon_list); + iter = g_list_next (iter); } gtk_widget_size_request (x->toolbar_widget, &new_req); @@ -2857,6 +3099,8 @@ update_frame_tool_bar (f) /* Must force out update so changed images gets redrawn. */ gdk_window_process_all_updates (); + if (icon_list) g_list_free (icon_list); + UNBLOCK_INPUT; } @@ -2893,7 +3137,6 @@ void xg_initialize () { xg_ignore_gtk_scrollbar = 0; - xg_ignore_next_thumb = 0; xg_left_ptr_cursor = 0; xg_did_tearoff = 0;