wmii

git clone git://oldgit.suckless.org/wmii/
Log | Files | Refs | README | LICENSE

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