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