menu.c (8818B)
1 #include "dat.h" 2 #include <ctype.h> 3 #include <strings.h> 4 #include <unistd.h> 5 #include "fns.h" 6 7 static Handlers handlers; 8 static int promptw; 9 10 void 11 menu_init(void) { 12 WinAttr wa; 13 14 wa.event_mask = ExposureMask | KeyPressMask; 15 menu.win = createwindow(&scr.root, Rect(-1, -1, 1, 1), scr.depth, InputOutput, 16 &wa, CWEventMask); 17 if(scr.xim) 18 menu.win->xic = XCreateIC(scr.xim, 19 XNInputStyle, XIMPreeditNothing | XIMStatusNothing, 20 XNClientWindow, menu.win->xid, 21 XNFocusWindow, menu.win->xid, 22 nil); 23 24 changeprop_long(menu.win, Net("WM_WINDOW_TYPE"), "ATOM", (long[]){ TYPE("MENU") }, 1); 25 changeprop_string(menu.win, "_WMII_TAGS", "sel"); 26 changeprop_textlist(menu.win, "WM_CLASS", "STRING", (char*[3]){ "wimenu", "wimenu" }); 27 28 sethandler(menu.win, &handlers); 29 mapwin(menu.win); 30 31 int i = 0; 32 while(!grabkeyboard(menu.win)) { 33 if(i++ > 1000) 34 fatal("can't grab keyboard"); 35 usleep(1000); 36 } 37 } 38 39 void 40 menu_show(void) { 41 Rectangle r; 42 43 if(menu.prompt) 44 promptw = textwidth(font, menu.prompt) + itempad; 45 46 r = textextents_l(font, "<", 1, nil); 47 menu.arrow = Pt(Dy(r) + itempad/2, Dx(r) + itempad/2); 48 49 menu.height = labelh(font); 50 51 freeimage(menu.buf); 52 menu.buf = allocimage(Dx(scr.rect), 53 !!menu.rows * 2 * menu.arrow.y + (menu.rows + 1) * menu.height, 54 menu.win->depth); 55 56 mapwin(menu.win); 57 raisewin(menu.win); 58 menu_draw(); 59 } 60 61 /* I'd prefer to use ⌃ and ⌄, but few fonts support them. */ 62 static void 63 drawarrow(Image *img, Rectangle r, int up, Color *col) { 64 Point p[3], pt; 65 66 pt = Pt(menu.arrow.x - itempad/2, menu.arrow.y - itempad/2 & ~1); 67 68 p[1] = Pt(r.min.x + Dx(r)/2, up ? r.min.y + itempad/4 : r.max.y - itempad/4); 69 p[0] = Pt(p[1].x - pt.x/2, up ? p[1].y + pt.y : p[1].y - pt.y); 70 p[2] = Pt(p[1].x + pt.x/2, p[0].y); 71 drawpoly(img, p, nelem(p), CapProjecting, 1, col); 72 } 73 74 static Rectangle 75 slice(Rectangle *rp, int x, int y) { 76 Rectangle r; 77 78 r = *rp; 79 if(x) rp->min.x += x, r.max.x = min(rp->min.x, rp->max.x); 80 if(y) rp->min.y += y, r.max.y = min(rp->min.y, rp->max.y); 81 return r; 82 } 83 84 static bool 85 nextrect(Item *i, Rectangle *rp, Rectangle *src) { 86 Rectangle r; 87 88 if(menu.rows) 89 r = slice(src, 0, menu.height); 90 else 91 r = slice(src, i->width, 0); 92 return (Dx(*src) >= 0 && Dy(*src) >= 0) && (*rp = r, 1); 93 } 94 95 void 96 menu_draw(void) { 97 Rectangle barr, extent, itemr, inputr, r, r2; 98 Item *item; 99 int inputw, offset; 100 101 barr = r2 = Rect(0, 0, Dx(menu.win->r), menu.height); 102 103 inputw = max(match.maxwidth + textwidth_l(font, input.string, min(input.filter_start, strlen(input.string))), 104 max(itempad + textwidth(font, input.string), 105 Dx(barr) / 3)); 106 107 /* Calculate items box, w/ and w/o arrows */ 108 if(menu.rows) { 109 menu.itemr = barr; 110 menu.itemr.max.y += Dy(barr) * (menu.rows - 1); 111 if(menu.ontop) 112 menu.itemr = rectaddpt(menu.itemr, Pt(0, Dy(barr))); 113 itemr = menu.itemr; 114 if(match.start != match.first) 115 menu.itemr = rectaddpt(menu.itemr, Pt(0, menu.arrow.y)); 116 } 117 else { 118 itemr = r2; 119 slice(&itemr, inputw + promptw, 0); 120 menu.itemr = Rect(itemr.min.x + menu.arrow.x, itemr.min.y, 121 itemr.max.x - menu.arrow.x, itemr.max.y); 122 } 123 124 fill(menu.buf, menu.buf->r, &cnorm.bg); 125 126 /* Draw items */ 127 item = match.start, r2 = menu.itemr; 128 nextrect(item, &r, &r2); 129 do { 130 match.end = item; 131 if(item->string) 132 fillstring(menu.buf, font, r, West, item->string, 133 (item == match.sel ? &csel : &cnorm), 0); 134 item = item->next; 135 } while(item != match.first && nextrect(item, &r, &r2)); 136 137 /* Adjust dimensions for arrows/number of items */ 138 if(menu.rows) 139 itemr.max.y = r.max.y + (match.end->next != match.first ? menu.arrow.y : 0); 140 else 141 itemr.max.x = r.max.x + menu.arrow.x; 142 if(menu.rows && !menu.ontop) 143 barr = rectaddpt(barr, Pt(0, itemr.max.y)); 144 145 /* Draw indicators */ 146 if(!menu.rows && match.start != match.first) 147 drawstring(menu.buf, font, itemr, West, "<", &cnorm.fg); 148 if(!menu.rows && match.end->next != match.first) 149 drawstring(menu.buf, font, itemr, East, ">", &cnorm.fg); 150 151 if(menu.rows && match.start != match.first) 152 drawarrow(menu.buf, itemr, 1, &cnorm.fg); 153 if(menu.rows && match.end->next != match.first) 154 drawarrow(menu.buf, itemr, 0, &cnorm.fg); 155 156 /* Draw prompt */ 157 r2 = barr; 158 if(menu.prompt) 159 drawstring(menu.buf, font, slice(&r2, promptw, 0), 160 West, menu.prompt, &cnorm.fg); 161 162 /* Border input/horizontal items */ 163 border(menu.buf, r2, 1, &cnorm.border); 164 165 /* Draw input */ 166 inputr = slice(&r2, inputw, 0); 167 drawstring(menu.buf, font, inputr, West, input.string, &cnorm.fg); 168 169 /* Draw cursor */ 170 extent = textextents_l(font, input.string, input.pos - input.string, &offset); 171 r2 = insetrect(inputr, 2); 172 r2.min.x = inputr.min.x - extent.min.x + offset + font->pad.min.x + itempad/2 - 1; 173 r2.max.x = r2.min.x + 1; 174 fill(menu.buf, r2, &cnorm.border); 175 176 /* Reshape window */ 177 r = scr.rect; 178 if(menu.ontop) 179 r.max.y = r.min.y + itemr.max.y; 180 else 181 r.min.y = r.max.y - barr.max.y; 182 reshapewin(menu.win, r); 183 184 /* Border window */ 185 r = rectsubpt(r, r.min); 186 border(menu.buf, r, 1, &cnorm.border); 187 copyimage(menu.win, r, menu.buf, ZP); 188 } 189 190 static Item* 191 pagestart(Item *i) { 192 Rectangle r, r2; 193 194 r = menu.itemr; 195 nextrect(i, &r2, &r); 196 while(i->prev != match.first->prev && nextrect(i->prev, &r2, &r)) 197 i = i->prev; 198 return i; 199 } 200 201 static void 202 selectitem(Item *i) { 203 if(i != match.sel) { 204 caret_set(input.filter_start, input.pos - input.string); 205 caret_insert(i->string, 0); 206 match.sel = i; 207 if(i == match.start->prev) 208 match.start = pagestart(i); 209 if(i == match.end->next) 210 match.start = i; 211 } 212 } 213 214 static void 215 paste(void *aux, char *str) { 216 if(str) 217 caret_insert(str, false); 218 menu_draw(); 219 } 220 221 static bool 222 kdown_event(Window *w, void *aux, XKeyEvent *e) { 223 char **action, **p; 224 char *key; 225 char buf[128]; 226 int num, status; 227 KeySym ksym; 228 229 if(XFilterEvent((XEvent*)e, w->xid)) 230 return false; 231 232 status = XLookupBoth; 233 if(w->xic) 234 num = Xutf8LookupString(w->xic, e, buf, sizeof buf - 1, &ksym, &status); 235 else 236 num = XLookupString(e, buf, sizeof buf - 1, &ksym, nil); 237 238 if(status != XLookupChars && status != XLookupKeySym && status != XLookupBoth) 239 return false; 240 241 if(status == XLookupKeySym || status == XLookupBoth) { 242 key = XKeysymToString(ksym); 243 if(IsKeypadKey(ksym)) 244 if(ksym == XK_KP_Enter) 245 ksym = XK_Return; 246 else if(ksym >= XK_KP_0 && ksym <= XK_KP_9) 247 ksym = (ksym - XK_KP_0) + XK_0; 248 249 if(IsFunctionKey(ksym) 250 || IsMiscFunctionKey(ksym) 251 || IsKeypadKey(ksym) 252 || IsPrivateKeypadKey(ksym) 253 || IsPFKey(ksym)) 254 return false; 255 action = find_key(key, e->state); 256 } 257 258 if(status == XLookupChars || action == nil || action[0] == nil) { 259 if(num && !iscntrl(buf[0])) { 260 buf[num] = '\0'; 261 caret_insert(buf, false); 262 update_filter(true); 263 menu_draw(); 264 } 265 } 266 else { 267 long mask = 0; 268 # define have(val) !!(mask & (1 << val)) 269 for(p=action+1; *p; p++) 270 mask |= 1 << getsym(*p); 271 int amount = ( 272 have(LCHAR) ? CHAR : 273 have(LWORD) ? WORD : 274 have(LLINE) ? LINE : 275 -1); 276 277 switch(getsym(action[0])) { 278 default: 279 return false; 280 case LHISTORY: 281 num = input.pos - input.string; 282 amount = have(LBACKWARD) ? BACKWARD : FORWARD; 283 caret_insert(history_search(amount, input.string, num), true); 284 input.pos = input.string + num; 285 update_filter(true); 286 break; 287 case LKILL: 288 caret_delete(BACKWARD, amount); 289 update_filter(true); 290 break; 291 case LDELETE: 292 caret_delete(FORWARD, amount); 293 update_filter(true); 294 break; 295 296 case LACCEPT: 297 srv.running = false; 298 if(!have(LLITERAL) && !match.sel && match.start->retstring) 299 if(input.filter_start == 0 && input.pos == input.end) 300 selectitem(match.start); 301 302 if(!have(LLITERAL) && match.sel && !strcmp(input.string, match.sel->string)) 303 lprint(1, "%s", match.sel->retstring); 304 else 305 lprint(1, "%s", input.string); 306 break; 307 case LBACKWARD: 308 caret_move(BACKWARD, amount); 309 update_input(); 310 break; 311 case LCOMPLETE: 312 if(have(LNEXT)) 313 selectitem(match.sel ? match.sel->next : match.first); 314 else if(have(LPREV)) 315 selectitem((match.sel ? match.sel : match.start)->prev); 316 else if(have(LFIRST)) { 317 match.start = match.first; 318 selectitem(match.start); 319 } 320 else if(have(LLAST)) 321 selectitem(match.first->prev); 322 else if(have(LNEXTPAGE)) 323 selectitem(match.end->next); 324 else if(have(LPREVPAGE)) { 325 match.start = pagestart(match.start->prev); 326 selectitem(match.start); 327 } 328 break; 329 case LFORWARD: 330 caret_move(FORWARD, amount); 331 update_input(); 332 break; 333 case LPASTE: 334 getselection(action[1] ? action[1] : "PRIMARY", paste, nil); 335 break; 336 case LREJECT: 337 srv.running = false; 338 result = 1; 339 break; 340 } 341 menu_draw(); 342 } 343 return false; 344 } 345 346 static bool 347 expose_event(Window *w, void *aux, XExposeEvent *e) { 348 349 USED(w); 350 menu_draw(); 351 return false; 352 } 353 354 static Handlers handlers = { 355 .expose = expose_event, 356 .kdown = kdown_event, 357 }; 358