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