1 /*
2  *  Copyright (C) 1995, 1996  Karl-Johan Johnsson.
3  */
4 
5 #include <X11/IntrinsicP.h>
6 #include <X11/StringDefs.h>
7 #include <X11/Shell.h>
8 #include <stdio.h>
9 
10 #include "Compat.h"
11 
12 #include "MenuI.h"
13 #include "MenuKnappP.h"
14 #include "MenuShell.h"
15 
16 static XtResource resources[] = {
17     {XtNrightMargin, XtCRightMargin, XtRDimension, sizeof(Dimension),
18      XtOffsetOf(MenuKnappRec, knapp.right_margin),
19      XtRImmediate, (XtPointer)24},
20 #define offset(field) XtOffsetOf(MenuKnappRec, menu_knapp.field)
21     {XtNmenuName, XtCMenuName, XtRString, sizeof(String),
22      offset(menu_name), XtRImmediate, (XtPointer)NULL},
23     {XtNarrowSize, XtCArrowSize, XtRDimension, sizeof(Dimension),
24      offset(arrow_size), XtRImmediate, (XtPointer)6},
25     {XtNarrowOffset, XtCArrowOffset, XtRDimension, sizeof(Dimension),
26      offset(arrow_offset), XtRImmediate, (XtPointer)8},
27     {XtNarrowShadowWidth, XtCShadowWidth, XtRDimension, sizeof(Dimension),
28      offset(arrow_shadow_width), XtRImmediate, (XtPointer)1},
29     {XtNmultiClickTime, XtCMultiClickTime, XtRInt, sizeof(int),
30      offset(multi_click_time), XtRImmediate, (XtPointer)200},
31     {XtNpopdownTime, XtCMultiClickTime, XtRInt, sizeof(int),
32      offset(popdown_time), XtRImmediate, (XtPointer)200},
33 #undef offset
34 };
35 
36 static void	Initialize(Widget, Widget, ArgList, Cardinal*);
37 static void	Redisplay(Widget, XEvent*, Region);
38 static void	Destroy(Widget);
39 static Boolean	SetValues(Widget, Widget, Widget, ArgList, Cardinal*);
40 
41 static void	popup_and_grab(Widget, XEvent*, String*, Cardinal*);
42 static void	popdown_and_notify_if(Widget, XEvent*, String*, Cardinal*);
43 static void	set_unless(Widget, XEvent*, String*, Cardinal*);
44 
45 static XtActionsRec actions[] = {
46     {"popup-and-grab",		popup_and_grab},
47     {"popdown-and-notify-if",	popdown_and_notify_if},
48     {"set-unless",		set_unless},
49 };
50 
51 static char translations[] =
52 "<BtnDown>:	popup-and-grab() \n"
53 "<BtnUp>:	popdown-and-notify-if() \n";
54 
55 MenuKnappClassRec menuKnappClassRec = {
56     {                                   /* core fields                  */
57         (WidgetClass) &knappClassRec,   /* superclass                   */
58         "MenuKnapp",                    /* class_name                   */
59         sizeof(MenuKnappRec),	        /* widget_size                  */
60         NULL,		                /* class_initialize             */
61         NULL,                           /* class_part_initialize        */
62         FALSE,                          /* class_inited                 */
63         Initialize,                     /* initialize                   */
64         NULL,                           /* initialize_hook              */
65         XtInheritRealize,               /* realize                      */
66         actions,                        /* actions                      */
67         XtNumber(actions),              /* num_actions                  */
68         resources,                      /* resources                    */
69         XtNumber(resources),            /* num_resources                */
70         NULLQUARK,                      /* xrm_class                    */
71         TRUE,                           /* compress_motion              */
72 #if (XtSpecificationRelease < 4)
73 	True,				/* compress exposure		*/
74 #elif (XtSpecificationRelease < 6)
75         XtExposeCompressMaximal,	/* compress_exposure		*/
76 #else
77         XtExposeCompressMaximal | XtExposeNoRegion, /* compress_exposure*/
78 #endif
79         TRUE,                           /* compress_enterleave          */
80         FALSE,                          /* visible_interest             */
81         Destroy,			/* destroy                      */
82         XtInheritResize,                /* resize                       */
83         Redisplay,                      /* expose                       */
84         SetValues,                      /* set_values                   */
85         NULL,                           /* set_values_hook              */
86         XtInheritSetValuesAlmost,       /* set_values_almost            */
87         NULL,                           /* get_values_hook              */
88         NULL,                           /* accept_focus                 */
89         XtVersion,                      /* version                      */
90         NULL,                           /* callback_private             */
91         translations,                   /* tm_table                     */
92         XtInheritQueryGeometry,         /* query_geometry               */
93         XtInheritDisplayAccelerator,    /* display_accelerator          */
94         NULL                            /* extension                    */
95     },
96     {					/* shadow fields		*/
97 	XtInheritPixelOffset,		/* pixel_offset			*/
98 	False,				/* use_arm_for_background	*/
99 	XtInheritAllocShadowColors,	/* alloc_shadow_colors		*/
100 	XtInheritAllocShadowPixmaps,	/* alloc_shadow_pixmaps		*/
101 	XtInheritAllocArmColor,		/* alloc_arm_color		*/
102 	XtInheritAllocArmPixmap,	/* alloc_arm_pixmap		*/
103 	XtInheritAllocGCs,		/* alloc_gcs			*/
104 	NULL,				/* extension			*/
105     },
106     {                                   /* knapp fields                 */
107         NULL,                           /* extension                    */
108     },
109     {					/* menu_knapp fields		*/
110 	NULL,				/* extension			*/
111     }
112 };
113 
114 WidgetClass menuKnappWidgetClass = (WidgetClass)&menuKnappClassRec;
115 
116 /*************************************************************************/
117 
draw_arrow(MenuKnappWidget w,int inverted)118 static void draw_arrow(MenuKnappWidget w, int inverted)
119 {
120     Display	*disp = XtDisplay(w);
121     Window	win = XtWindow(w);
122     int		s = w->menu_knapp.arrow_size;
123     int		y = ((int)w->core.height - s) / 2;
124     int		x;
125 
126     if (s <= 0)
127 	return;
128 
129     x = w->core.width + w->menu_knapp.arrow_offset -
130 	w->shadow.shadow_width - w->knapp.right_margin;
131 
132     if (w->shadow.line_mode) {
133 	short	sw = w->shadow.shadow_width;
134 	XPoint	point[4];
135 
136 	XClearArea(disp, win, x - sw, y - sw,
137 		   s + 2 * sw, s + 2 * sw, False);
138 
139 	if (inverted)
140 	    sw = 0;
141 	else
142 	    sw = (sw + 1)/2;
143 
144 	point[0].x = point[3].x = x + s - sw;
145 	point[1].x = x + sw;
146 	point[2].x = x + s/2;
147 
148 	point[0].y = point[1].y = point[3].y = y + sw;
149 	point[2].y = y + s - sw;
150 
151 	XDrawLines(disp, win,
152 		   inverted ? w->shadow.light_gc : w->shadow.dark_gc,
153 		   point, 4, CoordModeOrigin);
154     } else {
155 	XPoint	point[6];
156 	GC	light_gc, dark_gc;
157 	short	sw = w->menu_knapp.arrow_shadow_width;
158 
159 	if (inverted) {
160 	    light_gc = w->shadow.dark_gc;
161 	    dark_gc = w->shadow.light_gc;
162 	} else {
163 	    light_gc = w->shadow.light_gc;
164 	    dark_gc = w->shadow.dark_gc;
165 	}
166 
167 	if (sw == 0)
168 	    return;
169 	else if (sw == 1) {
170 	    point[0].x = point[3].x = x + s;
171 	    point[1].x = x;
172 	    point[2].x = (2 * x + s) / 2;
173 
174 	    point[0].y = point[1].y = point[3].y = y;
175 	    point[2].y = y + s;
176 	    point[3].y--;
177 
178 	    if (inverted && w->shadow.arm_gc != 0)
179 		XFillPolygon(disp, win, w->shadow.arm_gc, point, 3,
180 			     Convex, CoordModeOrigin);
181 
182 	    XDrawLines(disp, win, light_gc, point, 3, CoordModeOrigin);
183 	    XDrawLines(disp, win, dark_gc, &point[2], 2, CoordModeOrigin);
184 	} else {
185 	    point[0].x = x + s;
186 	    point[1].x = x;
187 	    point[2].x = point[3].x = (2 * x + s) / 2;
188 	    point[4].x = x + (3 * sw) / 2;
189 	    point[5].x = x + s - (3 * sw) / 2;
190 
191 	    point[0].y = point[1].y = y;
192 	    point[2].y = y + s;
193 	    point[3].y = y + s - (9 * sw) / 4;
194 	    point[4].y = point[5].y = y + sw;
195 
196 	    if (inverted && w->shadow.arm_gc != 0)
197 		XFillPolygon(disp, win, w->shadow.arm_gc, &point[3], 3,
198 			     Convex, CoordModeOrigin);
199 
200 	    XFillPolygon(disp, win, light_gc,
201 			 point, 6, Nonconvex, CoordModeOrigin);
202 	    point[4] = point[5];
203 	    point[5] = point[0];
204 	    XFillPolygon(disp, win, dark_gc,
205 			 &point[2], 4, Convex, CoordModeOrigin);
206 	}
207     }
208 }
209 
find_menu(MenuKnappWidget w)210 static Widget find_menu(MenuKnappWidget w)
211 {
212     Widget loop;
213 
214     if (w->menu_knapp.menu_name)
215 	for (loop = (Widget)w ; loop ; loop = XtParent(loop)) {
216 	    Widget	temp = XtNameToWidget(loop, w->menu_knapp.menu_name);
217 
218 	    if (temp)
219 		return temp;
220 	}
221 
222     return NULL;
223 }
224 
set_unless(Widget gw,XEvent * event,String * params,Cardinal * no_params)225 static void set_unless(Widget gw, XEvent *event,
226 		       String *params, Cardinal *no_params)
227 {
228     MenuKnappWidget	w = (MenuKnappWidget)gw;
229 
230     if (w->menu_knapp.menu_state == MenuStateDown)
231 	XtCallActionProc((Widget)w, "set", event, params, *no_params);
232 }
233 
popup_and_grab(Widget gw,XEvent * event,String * params,Cardinal * no_params)234 static void popup_and_grab(Widget gw, XEvent *event,
235 			   String *params, Cardinal *no_params)
236 {
237     MenuKnappWidget	w = (MenuKnappWidget)gw;
238     Widget		menu = w->menu_knapp.menu;
239     Screen		*screen = XtScreen(w);
240     Arg			args[2];
241     Position		x, y;
242     int			tmp;
243 
244     if (!w->knapp.active || event->type != ButtonPress ||
245 	w->menu_knapp.menu_state != MenuStateDown)
246 	return;
247 
248     if (!menu && !(menu = w->menu_knapp.menu = find_menu(w))) {
249 	fprintf(stderr, "MenuKnapp: Couldn't find popup menu widget: %s\n",
250 		w->menu_knapp.menu_name ? w->menu_knapp.menu_name : "(null)");
251 	return;
252     }
253 
254     if (!XtIsSubclass(menu, menuShellWidgetClass)) {
255 	fprintf(stderr, "MenuKnapp: Widget %s is not a "
256 		"subclass of menuShell.\n", XtName(menu));
257 	return;
258     }
259 
260     XtAddGrab((Widget)w, True, True);
261     if (XtGrabPointer((Widget)w, True,
262 		      ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
263 		      GrabModeAsync, GrabModeAsync, None,
264 		      None, event->xbutton.time) != GrabSuccess) {
265 	XtRemoveGrab((Widget)w);
266 	fputs("MenuKnapp: Failed to grab pointer.\n", stderr);
267 	return;
268     }
269 
270     if (!XtIsRealized(menu))
271 	XtRealizeWidget(menu);
272 
273     SetActiveMenuShell(menu, True);
274 
275     XtTranslateCoords((Widget)w, 0, 0, &x, &y);
276     y += w->core.height + w->core.border_width;
277     if (x < 0)
278 	x = 0;
279     else {
280 	tmp = WidthOfScreen(screen) - menu->core.width;
281 	if (tmp >= 0 && x > tmp)
282 	    x = tmp;
283     }
284     if (y < 0)
285 	y = 0;
286     else {
287 	tmp = HeightOfScreen(screen) - menu->core.height;
288 	if (tmp >= 0 && y > tmp)
289 	    y = tmp;
290     }
291 
292     w->menu_knapp.menu_state = MenuStateUp;
293     w->menu_knapp.start_time = event->xbutton.time;
294     XtSetArg(args[0], XtNx, x);
295     XtSetArg(args[1], XtNy, y);
296     XtSetValues(menu, args, 2);
297     XtPopup(menu, XtGrabNone);
298     XtAddGrab(menu, False, False);
299     XClearWindow(XtDisplay(w), XtWindow(w));
300     Redisplay((Widget)w, NULL, NULL);
301 }
302 
popdown_timer(XtPointer client_data,XtIntervalId * id)303 static void popdown_timer(XtPointer client_data, XtIntervalId *id)
304 {
305     MenuKnappWidget	w = (MenuKnappWidget)client_data;
306 
307     w->menu_knapp.timer = 0;
308     w->menu_knapp.menu_state = MenuStateDown;
309     PopdownMenuShell(w->menu_knapp.menu);
310 #if 0
311     XtRemoveGrab((Widget)w);
312 #endif
313     PostNotifyMenuShell(w->menu_knapp.menu);
314     draw_arrow(w, True);
315 }
316 
popdown_and_notify_if(Widget gw,XEvent * event,String * params,Cardinal * no_params)317 static void popdown_and_notify_if(Widget gw, XEvent *event,
318 				  String *params, Cardinal *no_params)
319 {
320     MenuKnappWidget	w = (MenuKnappWidget)gw;
321     unsigned int	x, y;
322     Time		t;
323 
324     if (event->type != ButtonRelease ||
325 	w->menu_knapp.menu_state != MenuStateUp)
326 	return;
327 
328     x = event->xbutton.x;
329     y = event->xbutton.y;
330     t = event->xbutton.time;
331 
332     if (event->xbutton.window == XtWindow(w) &&
333 	x < w->core.width && y < w->core.height &&
334 	t <= w->menu_knapp.start_time + w->menu_knapp.multi_click_time)
335 	return;
336 
337     /*
338      *  We wouldn't want an active grab when we're calling the callbacks...
339      */
340     XtUngrabPointer((Widget)w, event->xbutton.time);
341     XtRemoveGrab(w->menu_knapp.menu);
342     XtRemoveGrab((Widget)w);
343     SetActiveMenuShell(w->menu_knapp.menu, False);
344     XFlush(XtDisplay(w));
345     if (!NotifyMenuShell(w->menu_knapp.menu)) {
346 	popdown_timer((XtPointer)w, 0);
347 	return;
348     }
349 
350     w->menu_knapp.menu_state = MenuStateWaiting;
351     w->menu_knapp.timer =
352 	XtAppAddTimeOut(XtWidgetToApplicationContext((Widget)w),
353 			w->menu_knapp.popdown_time, popdown_timer,
354 			(XtPointer)w);
355 }
356 
357 /*************************************************************************/
358 
Initialize(Widget grequest,Widget gnew,ArgList args,Cardinal * no_args)359 static void Initialize(Widget grequest, Widget gnew,
360 		       ArgList args, Cardinal *no_args)
361 {
362     MenuKnappWidget	new = (MenuKnappWidget)gnew;
363 
364     new->menu_knapp.menu       = find_menu(new);
365     new->menu_knapp.start_time = 0;
366     new->menu_knapp.menu_state = MenuStateDown;
367     new->menu_knapp.timer      = 0;
368 }
369 
Redisplay(Widget gw,XEvent * event,Region region)370 static void Redisplay(Widget gw, XEvent *event, Region region)
371 {
372     MenuKnappWidget	w = (MenuKnappWidget)gw;
373 
374     knappWidgetClass->core_class.expose((Widget)w, NULL, NULL);
375 
376     if (w->menu_knapp.arrow_shadow_width > 0)
377 	draw_arrow(w, w->menu_knapp.menu_state == MenuStateDown);
378 }
379 
Destroy(Widget gw)380 static void Destroy(Widget gw)
381 {
382     MenuKnappWidget	w = (MenuKnappWidget)gw;
383 
384     if (w->menu_knapp.timer)
385 	XtRemoveTimeOut(w->menu_knapp.timer);
386     w->menu_knapp.timer = 0;
387     w->menu_knapp.menu_state = MenuStateDown;
388 }
389 
SetValues(Widget gcurrent,Widget grequest,Widget gnew,ArgList args,Cardinal * num_args)390 static Boolean SetValues(Widget gcurrent,
391 			 Widget grequest,
392 			 Widget gnew,
393 			 ArgList args,
394 			 Cardinal *num_args)
395 {
396     MenuKnappWidget	new = (MenuKnappWidget)gnew;
397     MenuKnappWidget	current = (MenuKnappWidget)gcurrent;
398     Boolean		redisplay = False;
399 
400     if (new->menu_knapp.menu_name != current->menu_knapp.menu_name) {
401 	if (new->menu_knapp.menu_state == MenuStateWaiting) {
402 	    if (new->menu_knapp.timer)
403 		XtRemoveTimeOut(new->menu_knapp.timer);
404 	    popdown_timer((XtPointer)new, 0);
405 	}
406 	new->menu_knapp.menu = find_menu(new);
407     }
408 
409     if (new->menu_knapp.arrow_shadow_width !=
410 	current->menu_knapp.arrow_shadow_width ||
411 	new->menu_knapp.arrow_size   != current->menu_knapp.arrow_size ||
412 	new->menu_knapp.arrow_offset != current->menu_knapp.arrow_offset)
413 	redisplay = True;
414 
415     return redisplay;
416 }
417