0 /*
1 * Copyright 2006-2007 Johan Veenhuizen
2 *
3 * Permission is hereby granted, free of charge, to any person obtaining a
4 * copy of this software and associated documentation files (the "Software"),
5 * to deal in the Software without restriction, including without limitation
6 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
7 * and/or sell copies of the Software, and to permit persons to whom the
8 * Software is furnished to do so, subject to the following conditions:
9 *
10 * The above copyright notice and this permission notice shall be included
11 * in all copies or substantial portions of the Software.
12 *
13 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 * DEALINGS IN THE SOFTWARE.
20 */
21
22 #include <assert.h>
23 #include <stdio.h>
24 #include <string.h>
25
26 #include <X11/cursorfont.h>
27 #include <X11/Xlib.h>
28 #include <X11/Xutil.h>
29
30 #include "global.h"
31 #include "list.h"
32 #include "menu.h"
33
34 #define ITEMPAD MAX(1, (font->ascent + font->descent) / 4)
35 #define ITEMHEIGHT (font->ascent + font->descent + 2 * ITEMPAD)
36 #define MAXWIDTH (DisplayWidth(display, screen) / 4)
37
38 static Cursor cursor = None;
39
40 static void menuevent(struct widget *, XEvent *);
41 static void trim(struct menu *);
42
43 /*
44 * Perform the action of the currently selected item.
45 */
46 void select_current_menuitem(struct menu *menu)
47 {
48 struct menuitem *ip;
49 LIST *lp;
50 int i;
51
52 i = 0;
53 LIST_FOREACH(lp, &menu->itemlist) {
54 if (i == menu->current) {
55 ip = LIST_ITEM(lp, struct menuitem, itemlink);
56 ip->select(ip->arg);
57 return;
58 }
59 i++;
60 }
61 }
62
63 void put_menuitem_first(struct menuitem *item)
64 {
65 if (item->menu != NULL) {
66 LIST_REMOVE(&item->itemlink);
67 LIST_INSERT_HEAD(&item->menu->itemlist, &item->itemlink);
68 REPAINT(item->menu);
69 }
70 }
71
72 void put_menuitem_last(struct menuitem *item)
73 {
74 if (item->menu != NULL) {
75 LIST_REMOVE(&item->itemlink);
76 LIST_INSERT_TAIL(&item->menu->itemlist, &item->itemlink);
77 REPAINT(item->menu);
78 }
79 }
80
81 static void prepare_repaint(struct widget *widget)
82 {
83 struct menu *menu = (struct menu *)widget;
84
85 XSetWindowBackground(display, WIDGET_XWINDOW(menu),
86 color_menu_bg.normal);
87 }
88
89 static void repaint(struct widget *widget)
90 {
91 struct menu *menu = (struct menu *)widget;
92 struct menuitem *ip;
93 LIST *lp;
94 int i;
95
96 /* clear */
97 XSetForeground(display, menu->gc, color_menu_fg.normal);
98 XFillRectangle(display, menu->pixmap, menu->gc,
99 0, 0, WIDGET_WIDTH(menu), WIDGET_HEIGHT(menu));
100 XSetForeground(display, menu->gc, color_menu_bg.normal);
101 XFillRectangle(display, menu->pixmap, menu->gc,
102 1, 1, WIDGET_WIDTH(menu) - 2, WIDGET_HEIGHT(menu) - 2);
103
104 i = 0;
105 LIST_FOREACH(lp, &menu->itemlist) {
106 ip = LIST_ITEM(lp, struct menuitem, itemlink);
107 if (i == menu->current) {
108 XSetForeground(display, menu->gc,
109 color_menu_selection_bg.shadow2);
110 XDrawLine(display, menu->pixmap, menu->gc,
111 1,
112 1 + ITEMPAD + i * ITEMHEIGHT,
113 WIDGET_WIDTH(menu) - 2,
114 1 + ITEMPAD + i * ITEMHEIGHT);
115 XDrawLine(display, menu->pixmap, menu->gc,
116 1,
117 1 + ITEMPAD + i * ITEMHEIGHT + ITEMHEIGHT - 1,
118 WIDGET_WIDTH(menu) - 2,
119 1 + ITEMPAD + i * ITEMHEIGHT + ITEMHEIGHT - 1);
120
121 XSetForeground(display, menu->gc,
122 color_menu_selection_bg.normal);
123 XFillRectangle(display, menu->pixmap, menu->gc,
124 1, 1 + ITEMPAD + i * ITEMHEIGHT + 1,
125 WIDGET_WIDTH(menu) - 2, ITEMHEIGHT - 2);
126
127 XSetForeground(display, menu->gc,
128 color_menu_selection_fg.normal);
129 } else
130 XSetForeground(display, menu->gc,
131 color_menu_fg.normal);
132 XDrawString(display, menu->pixmap, menu->gc,
133 1 + ITEMHEIGHT,
134 1 + ITEMPAD + i * ITEMHEIGHT + ITEMPAD + font->ascent,
135 ip->name, strlen(ip->name));
136 i++;
137 }
138
139 /* show */
140 if (WIDGET_MAPPED(menu))
141 XCopyArea(display,
142 menu->pixmap, WIDGET_XWINDOW(menu), menu->gc,
143 0, 0, WIDGET_WIDTH(menu), WIDGET_HEIGHT(menu), 0, 0);
144 }
145
146 /*
147 * Pop up and activate the menu at position (x, y).
148 */
149 void show_menu(struct menu *menu, int x, int y, int button)
150 {
151 int dw = DisplayWidth(display, screen);
152 int dh = DisplayHeight(display, screen);
153
154 if (LIST_EMPTY(&menu->itemlist))
155 return;
156
157 if (x + WIDGET_WIDTH(menu) >= dw)
158 x = MAX(0, x - WIDGET_WIDTH(menu) + 1);
159
160 if (y + WIDGET_HEIGHT(menu) >= dh)
161 y = MAX(0, y - WIDGET_HEIGHT(menu) + 1);
162
163 menu->button = button;
164
165 move_widget(&menu->widget, x, y);
166 XRaiseWindow(display, WIDGET_XWINDOW(menu));
167 map_widget(&menu->widget);
168 if (button != -1) {
169 XGrabPointer(display, WIDGET_XWINDOW(menu), False,
170 ButtonPressMask | ButtonReleaseMask |
171 ButtonMotionMask | PointerMotionMask,
172 GrabModeAsync, GrabModeAsync, None, None, CurrentTime);
173 }
174 }
175
176 /*
177 * Hide and inactivate the menu.
178 */
179 void hide_menu(struct menu *menu)
180 {
181 unmap_widget(&menu->widget);
182 if (menu->button != -1)
183 XUngrabPointer(display, CurrentTime);
184 menu->current = -1;
185 REPAINT(menu);
186 }
187
188 /*
189 * Handle menu events.
190 */
191 static void menuevent(struct widget *widget, XEvent *ep)
192 {
193 struct menu *mp = (struct menu *)widget;
194 int tmp;
195
196 switch (ep->type) {
197 case MotionNotify:
198 tmp = mp->current;
199 if (ep->xmotion.x < 0 ||
200 ep->xmotion.x > WIDGET_WIDTH(mp) - 1 ||
201 ep->xmotion.y < 0 ||
202 ep->xmotion.y > WIDGET_HEIGHT(mp) - 1) {
203 /* Outside menu window */
204 if (mp->current != -2)
205 mp->current = -1;
206 } else if (ep->xmotion.x < 1 ||
207 ep->xmotion.x > WIDGET_WIDTH(mp) - 2 ||
208 ep->xmotion.y < 1 + ITEMPAD ||
209 ep->xmotion.y > WIDGET_HEIGHT(mp) - 2 - ITEMPAD) {
210 /* Inside menu window but not on an item */
211 mp->current = -1;
212 } else {
213 /* On an item */
214 mp->current =
215 (ep->xmotion.y - ITEMPAD - 1) / ITEMHEIGHT;
216 }
217 if (tmp != mp->current)
218 REPAINT(mp);
219 break;
220 case ButtonPress:
221 if (mp->current == -1 &&
222 (ep->xbutton.x <= 0 ||
223 ep->xbutton.x >= WIDGET_WIDTH(mp) - 1 ||
224 ep->xbutton.y <= 0 ||
225 ep->xbutton.y >= WIDGET_HEIGHT(mp) - 1))
226 mp->current = -2;
227 break;
228 case ButtonRelease:
229 if (mp->current == -2)
230 hide_menu(mp);
231 else if (mp->current != -1 &&
232 (ep->xbutton.button == mp->button ||
233 ep->xbutton.button == 1)) {
234 select_current_menuitem(mp);
235 hide_menu(mp);
236 }
237 break;
238 case Expose:
239 XCopyArea(display, mp->pixmap, WIDGET_XWINDOW(mp),
240 mp->gc, ep->xexpose.x, ep->xexpose.y,
241 ep->xexpose.width, ep->xexpose.height,
242 ep->xexpose.x, ep->xexpose.y);
243 break;
244 default:
245 debug("menuevent: %s (%d)", eventname(ep->type), ep->type);
246 break;
247 }
248 }
249
250 /*
251 * Create an empty menu.
252 */
253 struct menu *create_menu(void)
254 {
255 XSetWindowAttributes attr;
256 XGCValues gcval;
257 struct menu *mp;
258
259 mp = MALLOC(sizeof (struct menu));
260
261 create_widget(&mp->widget, WIDGET_MENU, root, InputOutput,
262 0, 0, 1, 1);
263 attr.save_under = True;
264 XChangeWindowAttributes(display, WIDGET_XWINDOW(mp),
265 CWSaveUnder, &attr);
266
267 if (cursor == None)
268 cursor = XCreateFontCursor(display, XC_hand2);
269 XDefineCursor(display, WIDGET_XWINDOW(mp), cursor);
270
271 mp->pixmap = XCreatePixmap(display, WIDGET_XWINDOW(mp),
272 mp->pixmapwidth = WIDGET_WIDTH(mp),
273 mp->pixmapheight = WIDGET_HEIGHT(mp),
274 DefaultDepth(display, screen));
275
276 gcval.font = font->fid;
277 gcval.graphics_exposures = False;
278 mp->gc = XCreateGC(display, WIDGET_XWINDOW(mp),
279 GCFont | GCGraphicsExposures, &gcval);
280
281 LIST_INIT(&mp->itemlist);
282 mp->nitems = 0;
283 mp->current = -1;
284
285 mp->widget.event = menuevent;
286 XSelectInput(display, WIDGET_XWINDOW(mp), ExposureMask);
287
288 mp->widget.prepare_repaint = prepare_repaint;
289 mp->widget.repaint = repaint;
290
291 REPAINT(mp);
292
293 return mp;
294 }
295
296 void destroy_menu(struct menu *mp)
297 {
298 struct menuitem *ip;
299 LIST *lp;
300
301 LIST_FOREACH(lp, &mp->itemlist) {
302 ip = LIST_ITEM(lp, struct menuitem, itemlink);
303 LIST_REMOVE(lp);
304 ip->menu = NULL;
305 }
306 XFreeGC(display, mp->gc);
307 XFreePixmap(display, mp->pixmap);
308 destroy_widget(&mp->widget);
309 FREE(mp);
310 }
311
312 /*
313 * Resize the menu so that all items fit.
314 */
315 static void trim(struct menu *mp)
316 {
317 struct menuitem *ip;
318 LIST *lp;
319 int width;
320 int height;
321
322 width = 2 + 2 * ITEMHEIGHT;
323 height = 2 + 2 * ITEMPAD;
324 LIST_FOREACH(lp, &mp->itemlist) {
325 ip = LIST_ITEM(lp, struct menuitem, itemlink);
326 height += ITEMHEIGHT;
327 width = MAX(width,
328 stringwidth(ip->name) + 2 * (1 + ITEMHEIGHT));
329 }
330
331 resize_widget(&mp->widget, width, height);
332
333 if (width > mp->pixmapwidth || height > mp->pixmapheight) {
334 XFreePixmap(display, mp->pixmap);
335 if (width > mp->pixmapwidth)
336 mp->pixmapwidth = MAX(LARGER(mp->pixmapwidth),
337 width);
338 if (height > mp->pixmapheight)
339 mp->pixmapheight = MAX(LARGER(mp->pixmapheight),
340 height);
341 debug("increasing menu pixmap size (%dx%d)",
342 mp->pixmapwidth, mp->pixmapheight);
343 mp->pixmap = XCreatePixmap(display, WIDGET_XWINDOW(mp),
344 mp->pixmapwidth, mp->pixmapheight,
345 DefaultDepth(display, screen));
346 }
347 }
348
349 /*
350 * Add an item and automatically resize + repaint.
351 */
352 struct menuitem *create_menuitem(struct menu *menu, const char *name,
353 void (*select)(void *), void *arg)
354 {
355 struct menuitem *ip;
356
357 assert(name != NULL);
358
359 ip = MALLOC(sizeof (struct menuitem));
360
361 ip->name = STRDUP(name);
362 stringfit(ip->name, MAXWIDTH);
363
364 ip->select = select;
365 ip->arg = arg;
366
367 if (menu == NULL) {
368 ip->menu = NULL;
369 LIST_INIT(&ip->itemlink);
370 } else {
371 ip->menu = menu;
372 LIST_INSERT_HEAD(&menu->itemlist, &ip->itemlink);
373 menu->nitems++;
374 trim(menu);
375 REPAINT(menu);
376 }
377
378 return ip;
379 }
380
381 void rename_menuitem(struct menuitem *item, const char *name)
382 {
383 assert(name != NULL);
384
385 FREE(item->name);
386 item->name = STRDUP(name);
387 stringfit(item->name, MAXWIDTH);
388
389 if (item->menu != NULL) {
390 trim(item->menu);
391 REPAINT(item->menu);
392 }
393 }
394
395 void destroy_menuitem(struct menuitem *item)
396 {
397 if (item->menu != NULL) {
398 LIST_REMOVE(&item->itemlink);
399 item->menu->nitems--;
400 trim(item->menu);
401 REPAINT(item->menu);
402 }
403
404 FREE(item->name);
405 FREE(item);
406 }
407