menu.c
changeset 4 991bd8b0771e
parent 3 e969f3575b7a
child 5 e5018cae273f
equal deleted inserted replaced
3:e969f3575b7a 4:991bd8b0771e
       
     1 /*
       
     2  * (C)opyright MMVI Anselm R. Garbe <garbeam at gmail dot com>
       
     3  * (C)opyright MMVI Sander van Dijk <a dot h dot vandijk at gmail dot com>
       
     4  * See LICENSE file for license details.
       
     5  */
       
     6 
       
     7 #include "config.h"
       
     8 #include "draw.h"
       
     9 #include "util.h"
       
    10 
       
    11 #include <ctype.h>
       
    12 #include <stdlib.h>
       
    13 #include <stdio.h>
       
    14 #include <string.h>
       
    15 #include <sys/stat.h>
       
    16 #include <sys/wait.h>
       
    17 #include <time.h>
       
    18 #include <unistd.h>
       
    19 #include <X11/cursorfont.h>
       
    20 #include <X11/Xutil.h>
       
    21 #include <X11/keysym.h>
       
    22 
       
    23 typedef struct Item Item;
       
    24 
       
    25 struct Item {
       
    26 	Item *next;		/* traverses all items */
       
    27 	Item *left, *right;	/* traverses items matching current search pattern */
       
    28 	char *text;
       
    29 };
       
    30 
       
    31 static Display *dpy;
       
    32 static Window root;
       
    33 static Window win;
       
    34 static XRectangle rect;
       
    35 static Bool done = False;
       
    36 
       
    37 static Item *allitem = 0;	/* first of all items */
       
    38 static Item *item = 0;	/* first of pattern matching items */
       
    39 static Item *sel = 0;
       
    40 static Item *nextoff = 0;
       
    41 static Item *prevoff = 0;
       
    42 static Item *curroff = 0;
       
    43 
       
    44 static int screen;
       
    45 static char *title = 0;
       
    46 static char text[4096];
       
    47 static int ret = 0;
       
    48 static int nitem = 0;
       
    49 static unsigned int cmdw = 0;
       
    50 static unsigned int twidth = 0;
       
    51 static unsigned int cwidth = 0;
       
    52 static const int seek = 30;		/* 30px */
       
    53 
       
    54 static Brush brush = {0};
       
    55 
       
    56 static void draw_menu(void);
       
    57 static void kpress(XKeyEvent * e);
       
    58 
       
    59 static char version[] = "gridmenu - " VERSION ", (C)opyright MMVI Anselm R. Garbe\n";
       
    60 
       
    61 static void
       
    62 usage()
       
    63 {
       
    64 	fprintf(stderr, "%s", "usage: gridmenu [-v] [-t <title>]\n");
       
    65 	exit(1);
       
    66 }
       
    67 
       
    68 static void
       
    69 update_offsets()
       
    70 {
       
    71 	unsigned int tw, w = cmdw + 2 * seek;
       
    72 
       
    73 	if(!curroff)
       
    74 		return;
       
    75 
       
    76 	for(nextoff = curroff; nextoff; nextoff=nextoff->right) {
       
    77 		tw = textwidth(&brush.font, nextoff->text);
       
    78 		if(tw > rect.width / 3)
       
    79 			tw = rect.width / 3;
       
    80 		w += tw + brush.font.height;
       
    81 		if(w > rect.width)
       
    82 			break;
       
    83 	}
       
    84 
       
    85 	w = cmdw + 2 * seek;
       
    86 	for(prevoff = curroff; prevoff && prevoff->left; prevoff=prevoff->left) {
       
    87 		tw = textwidth(&brush.font, prevoff->left->text);
       
    88 		if(tw > rect.width / 3)
       
    89 			tw = rect.width / 3;
       
    90 		w += tw + brush.font.height;
       
    91 		if(w > rect.width)
       
    92 			break;
       
    93 	}
       
    94 }
       
    95 
       
    96 static void
       
    97 update_items(char *pattern)
       
    98 {
       
    99 	unsigned int plen = strlen(pattern);
       
   100 	Item *i, *j;
       
   101 
       
   102 	if(!pattern)
       
   103 		return;
       
   104 
       
   105 	if(!title || *pattern)
       
   106 		cmdw = cwidth;
       
   107 	else
       
   108 		cmdw = twidth;
       
   109 
       
   110 	item = j = 0;
       
   111 	nitem = 0;
       
   112 
       
   113 	for(i = allitem; i; i=i->next)
       
   114 		if(!plen || !strncmp(pattern, i->text, plen)) {
       
   115 			if(!j)
       
   116 				item = i;
       
   117 			else
       
   118 				j->right = i;
       
   119 			i->left = j;
       
   120 			i->right = 0;
       
   121 			j = i;
       
   122 			nitem++;
       
   123 		}
       
   124 	for(i = allitem; i; i=i->next)
       
   125 		if(plen && strncmp(pattern, i->text, plen)
       
   126 				&& strstr(i->text, pattern)) {
       
   127 			if(!j)
       
   128 				item = i;
       
   129 			else
       
   130 				j->right = i;
       
   131 			i->left = j;
       
   132 			i->right = 0;
       
   133 			j = i;
       
   134 			nitem++;
       
   135 		}
       
   136 
       
   137 	curroff = prevoff = nextoff = sel = item;
       
   138 
       
   139 	update_offsets();
       
   140 }
       
   141 
       
   142 /* creates brush structs for brush mode drawing */
       
   143 static void
       
   144 draw_menu()
       
   145 {
       
   146 	unsigned int offx = 0;
       
   147 	Item *i;
       
   148 
       
   149 	brush.rect = rect;
       
   150 	brush.rect.x = 0;
       
   151 	brush.rect.y = 0;
       
   152 	draw(dpy, &brush, False, 0);
       
   153 
       
   154 	/* print command */
       
   155 	if(!title || text[0]) {
       
   156 		cmdw = cwidth;
       
   157 		if(cmdw && item)
       
   158 			brush.rect.width = cmdw;
       
   159 		draw(dpy, &brush, False, text);
       
   160 	}
       
   161 	else {
       
   162 		cmdw = twidth;
       
   163 		brush.rect.width = cmdw;
       
   164 		draw(dpy, &brush, False, title);
       
   165 	}
       
   166 	offx += brush.rect.width;
       
   167 
       
   168 	if(curroff) {
       
   169 		brush.rect.x = offx;
       
   170 		brush.rect.width = seek;
       
   171 		offx += brush.rect.width;
       
   172 		draw(dpy, &brush, False, (curroff && curroff->left) ? "<" : 0);
       
   173 
       
   174 		/* determine maximum items */
       
   175 		for(i = curroff; i != nextoff; i=i->right) {
       
   176 			brush.border = False;
       
   177 			brush.rect.x = offx;
       
   178 			brush.rect.width = textwidth(&brush.font, i->text);
       
   179 			if(brush.rect.width > rect.width / 3)
       
   180 				brush.rect.width = rect.width / 3;
       
   181 			brush.rect.width += brush.font.height;
       
   182 			if(sel == i) {
       
   183 				swap((void **)&brush.fg, (void **)&brush.bg);
       
   184 				draw(dpy, &brush, True, i->text);
       
   185 				swap((void **)&brush.fg, (void **)&brush.bg);
       
   186 			}
       
   187 			else
       
   188 				draw(dpy, &brush, False, i->text);
       
   189 			offx += brush.rect.width;
       
   190 		}
       
   191 
       
   192 		brush.rect.x = rect.width - seek;
       
   193 		brush.rect.width = seek;
       
   194 		draw(dpy, &brush, False, nextoff ? ">" : 0);
       
   195 	}
       
   196 	XCopyArea(dpy, brush.drawable, win, brush.gc, 0, 0, rect.width,
       
   197 			rect.height, 0, 0);
       
   198 	XFlush(dpy);
       
   199 }
       
   200 
       
   201 static void
       
   202 kpress(XKeyEvent * e)
       
   203 {
       
   204 	KeySym ksym;
       
   205 	char buf[32];
       
   206 	int num, prev_nitem;
       
   207 	unsigned int i, len = strlen(text);
       
   208 
       
   209 	buf[0] = 0;
       
   210 	num = XLookupString(e, buf, sizeof(buf), &ksym, 0);
       
   211 
       
   212 	if(IsFunctionKey(ksym) || IsKeypadKey(ksym)
       
   213 			|| IsMiscFunctionKey(ksym) || IsPFKey(ksym)
       
   214 			|| IsPrivateKeypadKey(ksym))
       
   215 		return;
       
   216 
       
   217 	/* first check if a control mask is omitted */
       
   218 	if(e->state & ControlMask) {
       
   219 		switch (ksym) {
       
   220 		case XK_H:
       
   221 		case XK_h:
       
   222 			ksym = XK_BackSpace;
       
   223 			break;
       
   224 		case XK_I:
       
   225 		case XK_i:
       
   226 			ksym = XK_Tab;
       
   227 			break;
       
   228 		case XK_J:
       
   229 		case XK_j:
       
   230 			ksym = XK_Return;
       
   231 			break;
       
   232 		case XK_N:
       
   233 		case XK_n:
       
   234 			ksym = XK_Right;
       
   235 			break;
       
   236 		case XK_P:
       
   237 		case XK_p:
       
   238 			ksym = XK_Left;
       
   239 			break;
       
   240 		case XK_U:
       
   241 		case XK_u:
       
   242 			text[0] = 0;
       
   243 			update_items(text);
       
   244 			draw_menu();
       
   245 			return;
       
   246 			break;
       
   247 		case XK_bracketleft:
       
   248 			ksym = XK_Escape;
       
   249 			break;
       
   250 		default:	/* ignore other control sequences */
       
   251 			return;
       
   252 			break;
       
   253 		}
       
   254 	}
       
   255 	switch (ksym) {
       
   256 	case XK_Left:
       
   257 		if(!(sel && sel->left))
       
   258 			return;
       
   259 		sel=sel->left;
       
   260 		if(sel->right == curroff) {
       
   261 			curroff = prevoff;
       
   262 			update_offsets();
       
   263 		}
       
   264 		break;
       
   265 	case XK_Tab:
       
   266 		if(!sel)
       
   267 			return;
       
   268 		strncpy(text, sel->text, sizeof(text));
       
   269 		update_items(text);
       
   270 		break;
       
   271 	case XK_Right:
       
   272 		if(!(sel && sel->right))
       
   273 			return;
       
   274 		sel=sel->right;
       
   275 		if(sel == nextoff) {
       
   276 			curroff = nextoff;
       
   277 			update_offsets();
       
   278 		}
       
   279 		break;
       
   280 	case XK_Return:
       
   281 		if(e->state & ShiftMask) {
       
   282 			if(text)
       
   283 				fprintf(stdout, "%s", text);
       
   284 		}
       
   285 		else if(sel)
       
   286 			fprintf(stdout, "%s", sel->text);
       
   287 		else if(text)
       
   288 			fprintf(stdout, "%s", text);
       
   289 		fflush(stdout);
       
   290 		done = True;
       
   291 		break;
       
   292 	case XK_Escape:
       
   293 		ret = 1;
       
   294 		done = True;
       
   295 		break;
       
   296 	case XK_BackSpace:
       
   297 		if((i = len)) {
       
   298 			prev_nitem = nitem;
       
   299 			do {
       
   300 				text[--i] = 0;
       
   301 				update_items(text);
       
   302 			} while(i && nitem && prev_nitem == nitem);
       
   303 			update_items(text);
       
   304 		}
       
   305 		break;
       
   306 	default:
       
   307 		if((num == 1) && !iscntrl((int) buf[0])) {
       
   308 			buf[num] = 0;
       
   309 			if(len > 0)
       
   310 				strncat(text, buf, sizeof(text));
       
   311 			else
       
   312 				strncpy(text, buf, sizeof(text));
       
   313 			update_items(text);
       
   314 		}
       
   315 	}
       
   316 	draw_menu();
       
   317 }
       
   318 
       
   319 static char *
       
   320 read_allitems()
       
   321 {
       
   322 	static char *maxname = 0;
       
   323 	char *p, buf[1024];
       
   324 	unsigned int len = 0, max = 0;
       
   325 	Item *i, *new;
       
   326 
       
   327 	i = 0;
       
   328 	while(fgets(buf, sizeof(buf), stdin)) {
       
   329 		len = strlen(buf);
       
   330 		if (buf[len - 1] == '\n')
       
   331 			buf[len - 1] = 0;
       
   332 		p = estrdup(buf);
       
   333 		if(max < len) {
       
   334 			maxname = p;
       
   335 			max = len;
       
   336 		}
       
   337 
       
   338 		new = emalloc(sizeof(Item));
       
   339 		new->next = new->left = new->right = 0;
       
   340 		new->text = p;
       
   341 		if(!i)
       
   342 			allitem = new;
       
   343 		else 
       
   344 			i->next = new;
       
   345 		i = new;
       
   346 	}
       
   347 
       
   348 	return maxname;
       
   349 }
       
   350 
       
   351 int
       
   352 main(int argc, char *argv[])
       
   353 {
       
   354 	int i;
       
   355 	XSetWindowAttributes wa;
       
   356 	char *maxname;
       
   357 	XEvent ev;
       
   358 
       
   359 	/* command line args */
       
   360 	for(i = 1; i < argc; i++) {
       
   361 		if (argv[i][0] == '-')
       
   362 			switch (argv[i][1]) {
       
   363 			case 'v':
       
   364 				fprintf(stdout, "%s", version);
       
   365 				exit(0);
       
   366 				break;
       
   367 			case 't':
       
   368 				if(++i < argc)
       
   369 					title = argv[i];
       
   370 				else
       
   371 					usage();
       
   372 				break;
       
   373 			default:
       
   374 				usage();
       
   375 				break;
       
   376 			}
       
   377 		else
       
   378 			usage();
       
   379 	}
       
   380 
       
   381 	dpy = XOpenDisplay(0);
       
   382 	if(!dpy)
       
   383 		error("gridmenu: cannot open dpy\n");
       
   384 	screen = DefaultScreen(dpy);
       
   385 	root = RootWindow(dpy, screen);
       
   386 
       
   387 	maxname = read_allitems();
       
   388 
       
   389 	/* grab as early as possible, but after reading all items!!! */
       
   390 	while(XGrabKeyboard(dpy, root, True, GrabModeAsync,
       
   391 			 GrabModeAsync, CurrentTime) != GrabSuccess)
       
   392 		usleep(1000);
       
   393 
       
   394 	/* style */
       
   395 	loadcolors(dpy, screen, &brush, BGCOLOR, FGCOLOR, BORDERCOLOR);
       
   396 	loadfont(dpy, &brush.font, FONT);
       
   397 
       
   398 	wa.override_redirect = 1;
       
   399 	wa.background_pixmap = ParentRelative;
       
   400 	wa.event_mask = ExposureMask | ButtonPressMask | KeyPressMask
       
   401 		| SubstructureRedirectMask | SubstructureNotifyMask;
       
   402 
       
   403 	rect.width = DisplayWidth(dpy, screen);
       
   404 	rect.height = brush.font.height + 4;
       
   405 	rect.y = DisplayHeight(dpy, screen) - rect.height;
       
   406 	rect.x = 0;
       
   407 
       
   408 	win = XCreateWindow(dpy, root, rect.x, rect.y,
       
   409 			rect.width, rect.height, 0, DefaultDepth(dpy, screen),
       
   410 			CopyFromParent, DefaultVisual(dpy, screen),
       
   411 			CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa);
       
   412 	XDefineCursor(dpy, win, XCreateFontCursor(dpy, XC_xterm));
       
   413 	XFlush(dpy);
       
   414 
       
   415 	/* pixmap */
       
   416 	brush.gc = XCreateGC(dpy, win, 0, 0);
       
   417 	brush.drawable = XCreatePixmap(dpy, win, rect.width, rect.height,
       
   418 			DefaultDepth(dpy, screen));
       
   419 	XFlush(dpy);
       
   420 
       
   421 	if(maxname)
       
   422 		cwidth = textwidth(&brush.font, maxname) + brush.font.height;
       
   423 	if(cwidth > rect.width / 3)
       
   424 		cwidth = rect.width / 3;
       
   425 
       
   426 	if(title) {
       
   427 		twidth = textwidth(&brush.font, title) + brush.font.height;
       
   428 		if(twidth > rect.width / 3)
       
   429 			twidth = rect.width / 3;
       
   430 	}
       
   431 
       
   432 	cmdw = title ? twidth : cwidth;
       
   433 
       
   434 	text[0] = 0;
       
   435 	update_items(text);
       
   436 	XMapRaised(dpy, win);
       
   437 	draw_menu();
       
   438 	XFlush(dpy);
       
   439 
       
   440 	/* main event loop */
       
   441 	while(!XNextEvent(dpy, &ev)) {
       
   442 		switch (ev.type) {
       
   443 			case KeyPress:
       
   444 				kpress(&ev.xkey);
       
   445 				break;
       
   446 			case Expose:
       
   447 				if(ev.xexpose.count == 0) {
       
   448 					draw_menu();
       
   449 				}
       
   450 				break;
       
   451 			default:
       
   452 				break;
       
   453 		}
       
   454 		if(done)
       
   455 			break;
       
   456 	}
       
   457 
       
   458 	XUngrabKeyboard(dpy, CurrentTime);
       
   459 	XFreePixmap(dpy, brush.drawable);
       
   460 	XFreeGC(dpy, brush.gc);
       
   461 	XDestroyWindow(dpy, win);
       
   462 	XCloseDisplay(dpy);
       
   463 
       
   464 	return ret;
       
   465 }