diff options
Diffstat (limited to 'lwlib/xlwmenu.c')
-rw-r--r-- | lwlib/xlwmenu.c | 1265 |
1 files changed, 1265 insertions, 0 deletions
diff --git a/lwlib/xlwmenu.c b/lwlib/xlwmenu.c new file mode 100644 index 00000000000..4d4efd4a01c --- /dev/null +++ b/lwlib/xlwmenu.c @@ -0,0 +1,1265 @@ +/* Implements a lightweight menubar widget. + Copyright (C) 1992 Lucid, Inc. + +This file is part of the Lucid Widget Library. + +The Lucid Widget Library 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. + +The Lucid Widget 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 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, 675 Mass Ave, Cambridge, MA 02139, USA. */ + +/* Created by devin@lucid.com */ + +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <stdio.h> + +#include <sys/types.h> +#include <X11/Xos.h> +#include <X11/IntrinsicP.h> +#include <X11/StringDefs.h> +#include <X11/cursorfont.h> +#include <X11/bitmaps/gray> +#include "xlwmenuP.h" +#include <string.h> + +static char +xlwMenuTranslations [] = +"<BtnDown>: start()\n\ +<BtnMotion>: drag()\n\ +<BtnUp>: select()\n\ +"; + +#define offset(field) XtOffset(XlwMenuWidget, field) +static XtResource +xlwMenuResources[] = +{ + {XtNfont, XtCFont, XtRFontStruct, sizeof(XFontStruct *), + offset(menu.font),XtRString, "XtDefaultFont"}, + {XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel), + offset(menu.foreground), XtRString, "XtDefaultForeground"}, + {XtNbuttonForeground, XtCButtonForeground, XtRPixel, sizeof(Pixel), + offset(menu.button_foreground), XtRString, "XtDefaultForeground"}, + {XtNmargin, XtCMargin, XtRDimension, sizeof(Dimension), + offset(menu.margin), XtRImmediate, (XtPointer)0}, + {XtNhorizontalSpacing, XtCMargin, XtRDimension, sizeof(Dimension), + offset(menu.horizontal_spacing), XtRImmediate, (XtPointer)3}, + {XtNverticalSpacing, XtCMargin, XtRDimension, sizeof(Dimension), + offset(menu.vertical_spacing), XtRImmediate, (XtPointer)1}, + {XtNarrowSpacing, XtCMargin, XtRDimension, sizeof(Dimension), + offset(menu.arrow_spacing), XtRImmediate, (XtPointer)10}, + + {XmNshadowThickness, XmCShadowThickness, XmRHorizontalDimension, + sizeof (Dimension), offset (menu.shadow_thickness), + XtRImmediate, (XtPointer) 2}, + {XmNtopShadowColor, XmCTopShadowColor, XtRPixel, sizeof (Pixel), + offset (menu.top_shadow_color), XtRImmediate, (XtPointer)-1}, + {XmNbottomShadowColor, XmCBottomShadowColor, XtRPixel, sizeof (Pixel), + offset (menu.bottom_shadow_color), XtRImmediate, (XtPointer)-1}, + {XmNtopShadowPixmap, XmCTopShadowPixmap, XtRPixmap, sizeof (Pixmap), + offset (menu.top_shadow_pixmap), XtRImmediate, (XtPointer)None}, + {XmNbottomShadowPixmap, XmCBottomShadowPixmap, XtRPixmap, sizeof (Pixmap), + offset (menu.bottom_shadow_pixmap), XtRImmediate, (XtPointer)None}, + + {XtNopen, XtCCallback, XtRCallback, sizeof(XtPointer), + offset(menu.open), XtRCallback, (XtPointer)NULL}, + {XtNselect, XtCCallback, XtRCallback, sizeof(XtPointer), + offset(menu.select), XtRCallback, (XtPointer)NULL}, + {XtNmenu, XtCMenu, XtRPointer, sizeof(XtPointer), + offset(menu.contents), XtRImmediate, (XtPointer)NULL}, + {XtNcursor, XtCCursor, XtRCursor, sizeof(Cursor), + offset(menu.cursor_shape), XtRString, (XtPointer)"right_ptr"}, + {XtNhorizontal, XtCHorizontal, XtRInt, sizeof(int), + offset(menu.horizontal), XtRImmediate, (XtPointer)True}, +}; +#undef offset + +static Boolean XlwMenuSetValues(); +static void XlwMenuRealize(); +static void XlwMenuRedisplay(); +static void XlwMenuResize(); +static void XlwMenuInitialize(); +static void XlwMenuRedisplay(); +static void XlwMenuDestroy(); +static void XlwMenuClassInitialize(); +static void Start(); +static void Drag(); +static void Select(); + +static XtActionsRec +xlwMenuActionsList [] = +{ + {"start", Start}, + {"drag", Drag}, + {"select", Select}, +}; + +#define SuperClass ((CoreWidgetClass)&coreClassRec) + +XlwMenuClassRec xlwMenuClassRec = +{ + { /* CoreClass fields initialization */ + (WidgetClass) SuperClass, /* superclass */ + "XlwMenu", /* class_name */ + sizeof(XlwMenuRec), /* size */ + XlwMenuClassInitialize, /* class_initialize */ + NULL, /* class_part_initialize */ + FALSE, /* class_inited */ + XlwMenuInitialize, /* initialize */ + NULL, /* initialize_hook */ + XlwMenuRealize, /* realize */ + xlwMenuActionsList, /* actions */ + XtNumber(xlwMenuActionsList), /* num_actions */ + xlwMenuResources, /* resources */ + XtNumber(xlwMenuResources), /* resource_count */ + NULLQUARK, /* xrm_class */ + TRUE, /* compress_motion */ + TRUE, /* compress_exposure */ + TRUE, /* compress_enterleave */ + FALSE, /* visible_interest */ + XlwMenuDestroy, /* destroy */ + XlwMenuResize, /* resize */ + XlwMenuRedisplay, /* expose */ + XlwMenuSetValues, /* set_values */ + NULL, /* set_values_hook */ + XtInheritSetValuesAlmost, /* set_values_almost */ + NULL, /* get_values_hook */ + NULL, /* accept_focus */ + XtVersion, /* version */ + NULL, /* callback_private */ + xlwMenuTranslations, /* tm_table */ + XtInheritQueryGeometry, /* query_geometry */ + XtInheritDisplayAccelerator, /* display_accelerator */ + NULL /* extension */ + }, /* XlwMenuClass fields initialization */ + { + 0 /* dummy */ + }, +}; + +WidgetClass xlwMenuWidgetClass = (WidgetClass) &xlwMenuClassRec; + +/* Utilities */ +static void +push_new_stack (XlwMenuWidget mw, widget_value* val) +{ + if (!mw->menu.new_stack) + { + mw->menu.new_stack_length = 10; + mw->menu.new_stack = + (widget_value**)XtCalloc (mw->menu.new_stack_length, + sizeof (widget_value*)); + } + else if (mw->menu.new_depth == mw->menu.new_stack_length) + { + mw->menu.new_stack_length *= 2; + mw->menu.new_stack = + (widget_value**)XtRealloc ((char*)mw->menu.new_stack, + mw->menu.new_stack_length * sizeof (widget_value*)); + } + mw->menu.new_stack [mw->menu.new_depth++] = val; +} + +static void +pop_new_stack_if_no_contents (XlwMenuWidget mw) +{ + if (mw->menu.new_depth) + { + if (!mw->menu.new_stack [mw->menu.new_depth - 1]->contents) + mw->menu.new_depth -= 1; + } +} + +static void +make_old_stack_space (XlwMenuWidget mw, int n) +{ + if (!mw->menu.old_stack) + { + mw->menu.old_stack_length = 10; + mw->menu.old_stack = + (widget_value**)XtCalloc (mw->menu.old_stack_length, + sizeof (widget_value*)); + } + else if (mw->menu.old_stack_length < n) + { + mw->menu.old_stack_length *= 2; + mw->menu.old_stack = + (widget_value**)XtRealloc ((char*)mw->menu.old_stack, + mw->menu.old_stack_length * sizeof (widget_value*)); + } +} + +/* Size code */ +static Boolean +all_dashes_p (char* s) +{ + char* p; + for (p = s; *p == '-'; p++); + return !*p; +} + +static int +string_width (XlwMenuWidget mw, char* s) +{ + XCharStruct xcs; + int drop; + + XTextExtents (mw->menu.font, s, strlen (s), &drop, &drop, &drop, &xcs); + return xcs.width; +} + +static int +arrow_width (XlwMenuWidget mw) +{ + return mw->menu.font->ascent / 2 | 1; +} + +static XtResource +nameResource[] = +{ + {"labelString", "LabelString", XtRString, sizeof(String), + 0, XtRImmediate, 0}, +}; + +static char* +resource_widget_value (XlwMenuWidget mw, widget_value* val) +{ + if (!val->toolkit_data) + { + char* resourced_name = NULL; + char* complete_name; + XtGetSubresources ((Widget) mw, + (XtPointer) &resourced_name, + val->name, val->name, + nameResource, 1, NULL, 0); + if (!resourced_name) + resourced_name = val->name; + if (!val->value) + complete_name = (char *) strdup (resourced_name); + else + { + int complete_length = + strlen (resourced_name) + strlen (val->value) + 2; + complete_name = XtMalloc (complete_length); + *complete_name = 0; + strcat (complete_name, resourced_name); + strcat (complete_name, " "); + strcat (complete_name, val->value); + } + + val->toolkit_data = complete_name; + val->free_toolkit_data = True; + } + return (char*)val->toolkit_data; +} + +/* Returns the sizes of an item */ +static void +size_menu_item (XlwMenuWidget mw, widget_value* val, int horizontal_p, + int* label_width, int* rest_width, int* height) +{ + if (all_dashes_p (val->name)) + { + *height = 2; + *label_width = 1; + *rest_width = 0; + } + else + { + *height = + mw->menu.font->ascent + mw->menu.font->descent + + 2 * mw->menu.vertical_spacing + 2 * mw->menu.shadow_thickness; + + *label_width = + string_width (mw, resource_widget_value (mw, val)) + + mw->menu.horizontal_spacing + mw->menu.shadow_thickness; + + *rest_width = mw->menu.horizontal_spacing + mw->menu.shadow_thickness; + if (!horizontal_p) + { + if (val->contents) + *rest_width += arrow_width (mw) + mw->menu.arrow_spacing; + else if (val->key) + *rest_width += + string_width (mw, val->key) + mw->menu.arrow_spacing; + } + } +} + +static void +size_menu (XlwMenuWidget mw, int level) +{ + int label_width = 0; + int rest_width = 0; + int max_rest_width = 0; + int height = 0; + int horizontal_p = mw->menu.horizontal && (level == 0); + widget_value* val; + window_state* ws; + + if (level >= mw->menu.old_depth) + abort (); + + ws = &mw->menu.windows [level]; + ws->width = 0; + ws->height = 0; + ws->label_width = 0; + + for (val = mw->menu.old_stack [level]->contents; val; val = val->next) + { + size_menu_item (mw, val, horizontal_p, &label_width, &rest_width, + &height); + if (horizontal_p) + { + ws->width += label_width + rest_width; + if (height > ws->height) + ws->height = height; + } + else + { + if (label_width > ws->label_width) + ws->label_width = label_width; + if (rest_width > max_rest_width) + max_rest_width = rest_width; + ws->height += height; + } + } + + if (horizontal_p) + ws->label_width = 0; + else + ws->width = ws->label_width + max_rest_width; + + ws->width += 2 * mw->menu.shadow_thickness; + ws->height += 2 * mw->menu.shadow_thickness; +} + + +/* Display code */ +static void +draw_arrow (XlwMenuWidget mw, Window window, GC gc, int x, int y, int width) +{ + XPoint points [3]; + points [0].x = x; + points [0].y = y + mw->menu.font->ascent; + points [1].x = x; + points [1].y = y; + points [2].x = x + width; + points [2].y = y + mw->menu.font->ascent / 2; + + XFillPolygon (XtDisplay (mw), window, gc, points, 3, Convex, + CoordModeOrigin); +} + +static void +draw_shadow_rectangle (XlwMenuWidget mw, Window window, + int x, int y, int width, int height, int erase_p) +{ + Display *dpy = XtDisplay (mw); + GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc; + GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc; + int thickness = mw->menu.shadow_thickness; + XPoint points [4]; + points [0].x = x; + points [0].y = y; + points [1].x = x + width; + points [1].y = y; + points [2].x = x + width - thickness; + points [2].y = y + thickness; + points [3].x = x; + points [3].y = y + thickness; + XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin); + points [0].x = x; + points [0].y = y + thickness; + points [1].x = x; + points [1].y = y + height; + points [2].x = x + thickness; + points [2].y = y + height - thickness; + points [3].x = x + thickness; + points [3].y = y + thickness; + XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin); + points [0].x = x + width; + points [0].y = y; + points [1].x = x + width - thickness; + points [1].y = y + thickness; + points [2].x = x + width - thickness; + points [2].y = y + height - thickness; + points [3].x = x + width; + points [3].y = y + height - thickness; + XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin); + points [0].x = x; + points [0].y = y + height; + points [1].x = x + width; + points [1].y = y + height; + points [2].x = x + width; + points [2].y = y + height - thickness; + points [3].x = x + thickness; + points [3].y = y + height - thickness; + XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin); +} + + +/* Display the menu item and increment where.x and where.y to show how large +** the menu item was. +*/ +static void +display_menu_item (XlwMenuWidget mw, widget_value* val, window_state* ws, + XPoint* where, Boolean highlighted_p, Boolean horizontal_p, + Boolean just_compute_p) +{ + GC deco_gc; + GC text_gc; + int font_ascent = mw->menu.font->ascent; + int font_descent = mw->menu.font->descent; + int shadow = mw->menu.shadow_thickness; + int separator_p = all_dashes_p (val->name); + int h_spacing = mw->menu.horizontal_spacing; + int v_spacing = mw->menu.vertical_spacing; + int label_width; + int rest_width; + int height; + int width; + int button_p; + + /* compute the sizes of the item */ + size_menu_item (mw, val, horizontal_p, &label_width, &rest_width, &height); + + if (horizontal_p) + width = label_width + rest_width; + else + { + label_width = ws->label_width; + width = ws->width - 2 * shadow; + } + + /* see if it should be a button in the menubar */ + button_p = horizontal_p && val->call_data; + + /* Only highlight an enabled item that has a callback. */ + if (highlighted_p) + if (!val->enabled || !(val->call_data || val->contents)) + highlighted_p = 0; + + /* do the drawing. */ + if (!just_compute_p) + { + /* Add the shadow border of the containing menu */ + int x = where->x + shadow; + int y = where->y + shadow; + + /* pick the foreground and background GC. */ + if (val->enabled) + text_gc = button_p ? mw->menu.button_gc : mw->menu.foreground_gc; + else + text_gc = + button_p ? mw->menu.inactive_button_gc : mw->menu.inactive_gc; + deco_gc = mw->menu.foreground_gc; + + if (separator_p) + { + XDrawLine (XtDisplay (mw), ws->window, mw->menu.shadow_bottom_gc, + x, y, x + width, y); + XDrawLine (XtDisplay (mw), ws->window, mw->menu.shadow_top_gc, + x, y + 1, x + width, y + 1); + } + else + { + char* display_string = resource_widget_value (mw, val); + draw_shadow_rectangle (mw, ws->window, x, y, width, height, True); + XDrawString (XtDisplay (mw), ws->window, text_gc, + x + h_spacing + shadow, + y + v_spacing + shadow + font_ascent, + display_string, strlen (display_string)); + + if (!horizontal_p) + { + if (val->contents) + { + int a_w = arrow_width (mw); + draw_arrow (mw, ws->window, deco_gc, + x + label_width + mw->menu.arrow_spacing, + y + v_spacing + shadow, a_w); + } + else if (val->key) + { + XDrawString (XtDisplay (mw), ws->window, text_gc, + x + label_width + mw->menu.arrow_spacing, + y + v_spacing + shadow + font_ascent, + val->key, strlen (val->key)); + } + } + + else if (button_p) + { +#if 1 + XDrawRectangle (XtDisplay (mw), ws->window, deco_gc, + x + shadow, y + shadow, + label_width + h_spacing - 1, + font_ascent + font_descent + 2 * v_spacing - 1); +#else + highlighted_p = True; +#endif + } + + if (highlighted_p) + draw_shadow_rectangle (mw, ws->window, x, y, width, height, False); + } + } + + where->x += width; + where->y += height; +} + +static void +display_menu (XlwMenuWidget mw, int level, Boolean just_compute_p, + XPoint* highlighted_pos, XPoint* hit, widget_value** hit_return, + widget_value* this, widget_value* that) +{ + widget_value* val; + widget_value* following_item; + window_state* ws; + XPoint where; + int horizontal_p = mw->menu.horizontal && (level == 0); + int highlighted_p; + int just_compute_this_one_p; + + if (level >= mw->menu.old_depth) + abort (); + + if (level < mw->menu.old_depth - 1) + following_item = mw->menu.old_stack [level + 1]; + else + following_item = NULL; + + if (hit) + *hit_return = NULL; + + where.x = 0; + where.y = 0; + + ws = &mw->menu.windows [level]; + for (val = mw->menu.old_stack [level]->contents; val; val = val->next) + { + highlighted_p = val == following_item; + if (highlighted_p && highlighted_pos) + { + if (horizontal_p) + highlighted_pos->x = where.x; + else + highlighted_pos->y = where.y; + } + + just_compute_this_one_p = + just_compute_p || ((this || that) && val != this && val != that); + + display_menu_item (mw, val, ws, &where, highlighted_p, horizontal_p, + just_compute_this_one_p); + + if (highlighted_p && highlighted_pos) + { + if (horizontal_p) + highlighted_pos->y = where.y; + else + highlighted_pos->x = where.x; + } + + if (hit + && !*hit_return + && (horizontal_p ? hit->x < where.x : hit->y < where.y) + && !all_dashes_p (val->name)) + *hit_return = val; + + if (horizontal_p) + where.y = 0; + else + where.x = 0; + } + + if (!just_compute_p) + draw_shadow_rectangle (mw, ws->window, 0, 0, ws->width, ws->height, False); +} + +/* Motion code */ +static void +set_new_state (XlwMenuWidget mw, widget_value* val, int level) +{ + int i; + + mw->menu.new_depth = 0; + for (i = 0; i < level; i++) + push_new_stack (mw, mw->menu.old_stack [i]); + push_new_stack (mw, val); +} + +static void +make_windows_if_needed (XlwMenuWidget mw, int n) +{ + int i; + int start_at; + XSetWindowAttributes xswa; + int mask; + Window root = RootWindowOfScreen (DefaultScreenOfDisplay (XtDisplay (mw))); + window_state* windows; + + if (mw->menu.windows_length >= n) + return; + + xswa.save_under = True; + xswa.override_redirect = True; + xswa.background_pixel = mw->core.background_pixel; + xswa.border_pixel = mw->core.border_pixel; + xswa.event_mask = + ExposureMask | ButtonMotionMask | PointerMotionHintMask + | ButtonReleaseMask | ButtonPressMask; + xswa.cursor = mw->menu.cursor_shape; + mask = CWSaveUnder | CWOverrideRedirect | CWBackPixel | CWBorderPixel + | CWEventMask | CWCursor; + + if (!mw->menu.windows) + { + mw->menu.windows = + (window_state*)XtMalloc (n * sizeof (window_state)); + start_at = 0; + } + else + { + mw->menu.windows = + (window_state*)XtRealloc ((char*)mw->menu.windows, + n * sizeof (window_state)); + start_at = mw->menu.windows_length; + } + mw->menu.windows_length = n; + + windows = mw->menu.windows; + + for (i = start_at; i < n; i++) + { + windows [i].x = 0; + windows [i].y = 0; + windows [i].width = 1; + windows [i].height = 1; + windows [i].window = + XCreateWindow (XtDisplay (mw), root, 0, 0, 1, 1, + 0, 0, CopyFromParent, CopyFromParent, mask, &xswa); + } +} + +/* Make the window fit in the screen */ +static void +fit_to_screen (XlwMenuWidget mw, window_state* ws, window_state* previous_ws, + Boolean horizontal_p) +{ + int screen_width = WidthOfScreen (XtScreen (mw)); + int screen_height = HeightOfScreen (XtScreen (mw)); + + if (ws->x < 0) + ws->x = 0; + else if (ws->x + ws->width > screen_width) + { + if (!horizontal_p) + ws->x = previous_ws->x - ws->width; + else + ws->x = screen_width - ws->width; + } + if (ws->y < 0) + ws->y = 0; + else if (ws->y + ws->height > screen_height) + { + if (horizontal_p) + ws->y = previous_ws->y - ws->height; + else + ws->y = screen_height - ws->height; + } +} + +/* Updates old_stack from new_stack and redisplays. */ +static void +remap_menubar (XlwMenuWidget mw) +{ + int i; + int last_same; + XPoint selection_position; + int old_depth = mw->menu.old_depth; + int new_depth = mw->menu.new_depth; + widget_value** old_stack; + widget_value** new_stack; + window_state* windows; + widget_value* old_selection; + widget_value* new_selection; + + /* Check that enough windows and old_stack are ready. */ + make_windows_if_needed (mw, new_depth); + make_old_stack_space (mw, new_depth); + windows = mw->menu.windows; + old_stack = mw->menu.old_stack; + new_stack = mw->menu.new_stack; + + /* compute the last identical different entry */ + for (i = 1; i < old_depth && i < new_depth; i++) + if (old_stack [i] != new_stack [i]) + break; + last_same = i - 1; + + /* Memorize the previously selected item to be able to refresh it */ + old_selection = last_same + 1 < old_depth ? old_stack [last_same + 1] : NULL; + if (old_selection && !old_selection->enabled) + old_selection = NULL; + new_selection = last_same + 1 < new_depth ? new_stack [last_same + 1] : NULL; + if (new_selection && !new_selection->enabled) + new_selection = NULL; + + /* updates old_state from new_state. It has to be done now because + display_menu (called below) uses the old_stack to know what to display. */ + for (i = last_same + 1; i < new_depth; i++) + old_stack [i] = new_stack [i]; + mw->menu.old_depth = new_depth; + + /* refresh the last seletion */ + selection_position.x = 0; + selection_position.y = 0; + display_menu (mw, last_same, new_selection == old_selection, + &selection_position, NULL, NULL, old_selection, new_selection); + + /* Now popup the new menus */ + for (i = last_same + 1; i < new_depth && new_stack [i]->contents; i++) + { + window_state* previous_ws = &windows [i - 1]; + window_state* ws = &windows [i]; + + ws->x = + previous_ws->x + selection_position.x + mw->menu.shadow_thickness; + if (!mw->menu.horizontal || i > 1) + ws->x += mw->menu.shadow_thickness; + ws->y = + previous_ws->y + selection_position.y + mw->menu.shadow_thickness; + + size_menu (mw, i); + + fit_to_screen (mw, ws, previous_ws, mw->menu.horizontal && i == 1); + + XClearWindow (XtDisplay (mw), ws->window); + XMoveResizeWindow (XtDisplay (mw), ws->window, ws->x, ws->y, + ws->width, ws->height); + XMapRaised (XtDisplay (mw), ws->window); + display_menu (mw, i, False, &selection_position, NULL, NULL, NULL, NULL); + } + + /* unmap the menus that popped down */ + for (i = new_depth - 1; i < old_depth; i++) + if (i >= new_depth || !new_stack [i]->contents) + XUnmapWindow (XtDisplay (mw), windows [i].window); +} + +static Boolean +motion_event_is_in_menu (XlwMenuWidget mw, XMotionEvent* ev, int level, + XPoint* relative_pos) +{ + window_state* ws = &mw->menu.windows [level]; + int x = level == 0 ? ws->x : ws->x + mw->menu.shadow_thickness; + int y = level == 0 ? ws->y : ws->y + mw->menu.shadow_thickness; + relative_pos->x = ev->x_root - x; + relative_pos->y = ev->y_root - y; + return (x < ev->x_root && ev->x_root < x + ws->width + && y < ev->y_root && ev->y_root < y + ws->height); +} + +static Boolean +map_event_to_widget_value (XlwMenuWidget mw, XMotionEvent* ev, + widget_value** val, int* level) +{ + int i; + XPoint relative_pos; + window_state* ws; + + *val = NULL; + + /* Find the window */ + for (i = mw->menu.old_depth - 1; i >= 0; i--) + { + ws = &mw->menu.windows [i]; + if (ws && motion_event_is_in_menu (mw, ev, i, &relative_pos)) + { + display_menu (mw, i, True, NULL, &relative_pos, val, NULL, NULL); + + if (*val) + { + *level = i + 1; + return True; + } + } + } + return False; +} + +/* Procedures */ +static void +make_drawing_gcs (XlwMenuWidget mw) +{ + XGCValues xgcv; + + xgcv.font = mw->menu.font->fid; + xgcv.foreground = mw->menu.foreground; + xgcv.background = mw->core.background_pixel; + mw->menu.foreground_gc = XtGetGC ((Widget)mw, + GCFont | GCForeground | GCBackground, + &xgcv); + + xgcv.font = mw->menu.font->fid; + xgcv.foreground = mw->menu.button_foreground; + xgcv.background = mw->core.background_pixel; + mw->menu.button_gc = XtGetGC ((Widget)mw, + GCFont | GCForeground | GCBackground, + &xgcv); + + xgcv.font = mw->menu.font->fid; + xgcv.foreground = mw->menu.foreground; + xgcv.background = mw->core.background_pixel; + xgcv.fill_style = FillStippled; + xgcv.stipple = mw->menu.gray_pixmap; + mw->menu.inactive_gc = XtGetGC ((Widget)mw, + (GCFont | GCForeground | GCBackground + | GCFillStyle | GCStipple), &xgcv); + + xgcv.font = mw->menu.font->fid; + xgcv.foreground = mw->menu.button_foreground; + xgcv.background = mw->core.background_pixel; + xgcv.fill_style = FillStippled; + xgcv.stipple = mw->menu.gray_pixmap; + mw->menu.inactive_button_gc = XtGetGC ((Widget)mw, + (GCFont | GCForeground | GCBackground + | GCFillStyle | GCStipple), &xgcv); + + xgcv.font = mw->menu.font->fid; + xgcv.foreground = mw->core.background_pixel; + xgcv.background = mw->menu.foreground; + mw->menu.background_gc = XtGetGC ((Widget)mw, + GCFont | GCForeground | GCBackground, + &xgcv); +} + +static void +release_drawing_gcs (XlwMenuWidget mw) +{ + XtReleaseGC ((Widget) mw, mw->menu.foreground_gc); + XtReleaseGC ((Widget) mw, mw->menu.button_gc); + XtReleaseGC ((Widget) mw, mw->menu.inactive_gc); + XtReleaseGC ((Widget) mw, mw->menu.inactive_button_gc); + XtReleaseGC ((Widget) mw, mw->menu.background_gc); + /* let's get some segvs if we try to use these... */ + mw->menu.foreground_gc = (GC) -1; + mw->menu.button_gc = (GC) -1; + mw->menu.inactive_gc = (GC) -1; + mw->menu.inactive_button_gc = (GC) -1; + mw->menu.background_gc = (GC) -1; +} + +#define MINL(x,y) ((((unsigned long) (x)) < ((unsigned long) (y))) \ + ? ((unsigned long) (x)) : ((unsigned long) (y))) + +static void +make_shadow_gcs (XlwMenuWidget mw) +{ + XGCValues xgcv; + unsigned long pm = 0; + Display *dpy = XtDisplay ((Widget) mw); + Colormap cmap = DefaultColormapOfScreen (XtScreen ((Widget) mw)); + XColor topc, botc; + int top_frobbed = 0, bottom_frobbed = 0; + + if (mw->menu.top_shadow_color == -1) + mw->menu.top_shadow_color = mw->core.background_pixel; + if (mw->menu.bottom_shadow_color == -1) + mw->menu.bottom_shadow_color = mw->menu.foreground; + + if (mw->menu.top_shadow_color == mw->core.background_pixel || + mw->menu.top_shadow_color == mw->menu.foreground) + { + topc.pixel = mw->core.background_pixel; + XQueryColor (dpy, cmap, &topc); + /* don't overflow/wrap! */ + topc.red = MINL (65535, topc.red * 1.2); + topc.green = MINL (65535, topc.green * 1.2); + topc.blue = MINL (65535, topc.blue * 1.2); + if (XAllocColor (dpy, cmap, &topc)) + { + mw->menu.top_shadow_color = topc.pixel; + top_frobbed = 1; + } + } + if (mw->menu.bottom_shadow_color == mw->menu.foreground || + mw->menu.bottom_shadow_color == mw->core.background_pixel) + { + botc.pixel = mw->core.background_pixel; + XQueryColor (dpy, cmap, &botc); + botc.red *= 0.6; + botc.green *= 0.6; + botc.blue *= 0.6; + if (XAllocColor (dpy, cmap, &botc)) + { + mw->menu.bottom_shadow_color = botc.pixel; + bottom_frobbed = 1; + } + } + + if (top_frobbed && bottom_frobbed) + { + int top_avg = ((topc.red / 3) + (topc.green / 3) + (topc.blue / 3)); + int bot_avg = ((botc.red / 3) + (botc.green / 3) + (botc.blue / 3)); + if (bot_avg > top_avg) + { + Pixel tmp = mw->menu.top_shadow_color; + mw->menu.top_shadow_color = mw->menu.bottom_shadow_color; + mw->menu.bottom_shadow_color = tmp; + } + else if (topc.pixel == botc.pixel) + { + if (botc.pixel == mw->menu.foreground) + mw->menu.top_shadow_color = mw->core.background_pixel; + else + mw->menu.bottom_shadow_color = mw->menu.foreground; + } + } + + if (!mw->menu.top_shadow_pixmap && + mw->menu.top_shadow_color == mw->core.background_pixel) + { + mw->menu.top_shadow_pixmap = mw->menu.gray_pixmap; + mw->menu.top_shadow_color = mw->menu.foreground; + } + if (!mw->menu.bottom_shadow_pixmap && + mw->menu.bottom_shadow_color == mw->core.background_pixel) + { + mw->menu.bottom_shadow_pixmap = mw->menu.gray_pixmap; + mw->menu.bottom_shadow_color = mw->menu.foreground; + } + + xgcv.fill_style = FillStippled; + xgcv.foreground = mw->menu.top_shadow_color; + xgcv.stipple = mw->menu.top_shadow_pixmap; + pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0); + mw->menu.shadow_top_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv); + + xgcv.foreground = mw->menu.bottom_shadow_color; + xgcv.stipple = mw->menu.bottom_shadow_pixmap; + pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0); + mw->menu.shadow_bottom_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv); +} + + +static void +release_shadow_gcs (XlwMenuWidget mw) +{ + XtReleaseGC ((Widget) mw, mw->menu.shadow_top_gc); + XtReleaseGC ((Widget) mw, mw->menu.shadow_bottom_gc); +} + +static void +XlwMenuInitialize (Widget request, Widget new, ArgList args, + Cardinal *num_args) +{ + /* Get the GCs and the widget size */ + XlwMenuWidget mw = (XlwMenuWidget)new; + + XSetWindowAttributes xswa; + int mask; + + Window window = RootWindowOfScreen (DefaultScreenOfDisplay (XtDisplay (mw))); + Display* display = XtDisplay (mw); + +/* mw->menu.cursor = XCreateFontCursor (display, mw->menu.cursor_shape); */ + mw->menu.cursor = mw->menu.cursor_shape; + + mw->menu.gray_pixmap = XCreatePixmapFromBitmapData (display, window, + gray_bits, gray_width, + gray_height, 1, 0, 1); + + make_drawing_gcs (mw); + make_shadow_gcs (mw); + + xswa.background_pixel = mw->core.background_pixel; + xswa.border_pixel = mw->core.border_pixel; + mask = CWBackPixel | CWBorderPixel; + + mw->menu.popped_up = False; + + mw->menu.old_depth = 1; + mw->menu.old_stack = (widget_value**)XtMalloc (sizeof (widget_value*)); + mw->menu.old_stack_length = 1; + mw->menu.old_stack [0] = mw->menu.contents; + + mw->menu.new_depth = 0; + mw->menu.new_stack = 0; + mw->menu.new_stack_length = 0; + push_new_stack (mw, mw->menu.contents); + + mw->menu.windows = (window_state*)XtMalloc (sizeof (window_state)); + mw->menu.windows_length = 1; + mw->menu.windows [0].x = 0; + mw->menu.windows [0].y = 0; + mw->menu.windows [0].width = 0; + mw->menu.windows [0].height = 0; + size_menu (mw, 0); + + mw->core.width = mw->menu.windows [0].width; + mw->core.height = mw->menu.windows [0].height; +} + +static void +XlwMenuClassInitialize () +{ +} + +static void +XlwMenuRealize (Widget w, Mask *valueMask, XSetWindowAttributes *attributes) +{ + XlwMenuWidget mw = (XlwMenuWidget)w; + XSetWindowAttributes xswa; + int mask; + + (*xlwMenuWidgetClass->core_class.superclass->core_class.realize) + (w, valueMask, attributes); + + xswa.save_under = True; + xswa.cursor = mw->menu.cursor_shape; + mask = CWSaveUnder | CWCursor; + XChangeWindowAttributes (XtDisplay (w), XtWindow (w), mask, &xswa); + + mw->menu.windows [0].window = XtWindow (w); + mw->menu.windows [0].x = w->core.x; + mw->menu.windows [0].y = w->core.y; + mw->menu.windows [0].width = w->core.width; + mw->menu.windows [0].height = w->core.height; +} + +/* Only the toplevel menubar/popup is a widget so it's the only one that + receives expose events through Xt. So we repaint all the other panes + when receiving an Expose event. */ +static void +XlwMenuRedisplay (Widget w, XEvent* ev, Region region) +{ + XlwMenuWidget mw = (XlwMenuWidget)w; + int i; + + for (i = 0; i < mw->menu.old_depth; i++) + display_menu (mw, i, False, NULL, NULL, NULL, NULL, NULL); +} + +static void +XlwMenuDestroy (Widget w) +{ + int i; + XlwMenuWidget mw = (XlwMenuWidget) w; + + release_drawing_gcs (mw); + release_shadow_gcs (mw); + + /* this doesn't come from the resource db but is created explicitly + so we must free it ourselves. */ + XFreePixmap (XtDisplay (mw), mw->menu.gray_pixmap); + mw->menu.gray_pixmap = (Pixmap) -1; + + /* Don't free mw->menu.contents because that comes from our creator. + The `*_stack' elements are just pointers into `contents' so leave + that alone too. But free the stacks themselves. */ + if (mw->menu.old_stack) XtFree ((char *) mw->menu.old_stack); + if (mw->menu.new_stack) XtFree ((char *) mw->menu.new_stack); + + /* Remember, you can't free anything that came from the resource + database. This includes: + mw->menu.cursor + mw->menu.top_shadow_pixmap + mw->menu.bottom_shadow_pixmap + mw->menu.font + Also the color cells of top_shadow_color, bottom_shadow_color, + foreground, and button_foreground will never be freed until this + client exits. Nice, eh? + */ + + /* start from 1 because the one in slot 0 is w->core.window */ + for (i = 1; i < mw->menu.windows_length; i++) + XDestroyWindow (XtDisplay (mw), mw->menu.windows [i].window); + if (mw->menu.windows) + XtFree ((char *) mw->menu.windows); +} + +static Boolean +XlwMenuSetValues (Widget current, Widget request, Widget new) +{ + XlwMenuWidget oldmw = (XlwMenuWidget)current; + XlwMenuWidget newmw = (XlwMenuWidget)new; + Boolean redisplay = False; + int i; + + if (newmw->menu.contents + && newmw->menu.contents->contents + && newmw->menu.contents->contents->change >= VISIBLE_CHANGE) + redisplay = True; + + if (newmw->core.background_pixel != oldmw->core.background_pixel + || newmw->menu.foreground != oldmw->menu.foreground) + { + release_drawing_gcs (newmw); + make_drawing_gcs (newmw); + redisplay = True; + + for (i = 0; i < oldmw->menu.windows_length; i++) + { + XSetWindowBackground (XtDisplay (oldmw), + oldmw->menu.windows [i].window, + newmw->core.background_pixel); + /* clear windows and generate expose events */ + XClearArea (XtDisplay (oldmw), oldmw->menu.windows[i].window, + 0, 0, 0, 0, True); + } + } + + return redisplay; +} + +static void +XlwMenuResize (Widget w) +{ + XlwMenuWidget mw = (XlwMenuWidget)w; + + mw->menu.windows [0].width = mw->core.width; + mw->menu.windows [0].height = mw->core.height; +} + +/* Action procedures */ +static void +handle_single_motion_event (XlwMenuWidget mw, XMotionEvent* ev) +{ + widget_value* val; + int level; + + if (!map_event_to_widget_value (mw, ev, &val, &level)) + pop_new_stack_if_no_contents (mw); + else + set_new_state (mw, val, level); + remap_menubar (mw); + + /* Sync with the display. Makes it feel better on X terms. */ + XSync (XtDisplay (mw), False); +} + +static void +handle_motion_event (XlwMenuWidget mw, XMotionEvent* ev) +{ + int x = ev->x_root; + int y = ev->y_root; + int state = ev->state; + + handle_single_motion_event (mw, ev); + + /* allow motion events to be generated again */ + if (ev->is_hint + && XQueryPointer (XtDisplay (mw), ev->window, + &ev->root, &ev->subwindow, + &ev->x_root, &ev->y_root, + &ev->x, &ev->y, + &ev->state) + && ev->state == state + && (ev->x_root != x || ev->y_root != y)) + handle_single_motion_event (mw, ev); +} + +static void +Start (Widget w, XEvent *ev, String *params, Cardinal *num_params) +{ + XlwMenuWidget mw = (XlwMenuWidget)w; + + XtCallCallbackList ((Widget)mw, mw->menu.open, NULL); + + /* notes the absolute position of the menubar window */ + mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x; + mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y; + + /* handles the down like a move, slots are compatible */ + handle_motion_event (mw, &ev->xmotion); +} + +static void +Drag (Widget w, XEvent *ev, String *params, Cardinal *num_params) +{ + XlwMenuWidget mw = (XlwMenuWidget)w; + handle_motion_event (mw, &ev->xmotion); +} + +static void +Select (Widget w, XEvent *ev, String *params, Cardinal *num_params) +{ + XlwMenuWidget mw = (XlwMenuWidget)w; + widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1]; + + /* pop down everything */ + mw->menu.new_depth = 1; + remap_menubar (mw); + + if (mw->menu.popped_up) + { + mw->menu.popped_up = False; + XtUngrabPointer ((Widget)mw, ev->xmotion.time); + XtPopdown (XtParent (mw)); + } + + /* callback */ + XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)selected_item); + +} + + +/* Special code to pop-up a menu */ +void +pop_up_menu (XlwMenuWidget mw, XButtonPressedEvent* event) +{ + int x = event->x_root; + int y = event->y_root; + int w; + int h; + int borderwidth = mw->menu.shadow_thickness; + Screen* screen = XtScreen (mw); + + XtCallCallbackList ((Widget)mw, mw->menu.open, NULL); + + size_menu (mw, 0); + + w = mw->menu.windows [0].width; + h = mw->menu.windows [0].height; + + x -= borderwidth; + y -= borderwidth; + if (x < borderwidth) + x = borderwidth; + if (x + w + 2 * borderwidth > WidthOfScreen (screen)) + x = WidthOfScreen (screen) - w - 2 * borderwidth; + if (y < borderwidth) + y = borderwidth; + if (y + h + 2 * borderwidth> HeightOfScreen (screen)) + y = HeightOfScreen (screen) - h - 2 * borderwidth; + + mw->menu.popped_up = True; + XtConfigureWidget (XtParent (mw), x, y, w, h, + XtParent (mw)->core.border_width); + XtPopup (XtParent (mw), XtGrabExclusive); + display_menu (mw, 0, False, NULL, NULL, NULL, NULL, NULL); + XtGrabPointer ((Widget)mw, False, + (ButtonMotionMask | PointerMotionHintMask | ButtonReleaseMask + | ButtonPressMask), + GrabModeAsync, GrabModeAsync, None, mw->menu.cursor_shape, + event->time); + + mw->menu.windows [0].x = x + borderwidth; + mw->menu.windows [0].y = y + borderwidth; + + handle_motion_event (mw, (XMotionEvent*)event); +} |