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