diff options
Diffstat (limited to 'search.c')
-rw-r--r-- | search.c | 450 |
1 files changed, 450 insertions, 0 deletions
diff --git a/search.c b/search.c new file mode 100644 index 0000000..ab877cc --- /dev/null +++ b/search.c @@ -0,0 +1,450 @@ +/* + * calmwm - the calm window manager + * + * Copyright (c) 2004 Marius Aamodt Eriksen <marius@monkey.org> + * All rights reserved. + * + * $Id: search.c,v 1.1.1.1 2007/04/27 17:58:48 bernd Exp $ + */ + +#include "headers.h" +#include "calmwm.h" + +#define SearchMask (KeyPressMask|ExposureMask) + +static int _strsubmatch(char *, char *); + +void +search_init(struct screen_ctx *sc) +{ + sc->searchwin = XCreateSimpleWindow(G_dpy, sc->rootwin, 0, 0, + 1, 1, 1, sc->blackpixl, sc->whitepixl); +} + +/* + * ranking. each rank type is assigned a weight. multiply this by + * the rank given. add them up. simple linear combination. + */ + +/* + * Input: list of items, + * Output: choose one + * so, exactly like menus + */ + +struct menu * +search_start(struct menu_q *menuq, + void (*match)(struct menu_q *, struct menu_q *, char *), + void (*rank)(struct menu_q *resultq, char *search), + void (*print)(struct menu *mi, int print), + char *prompt) +{ + struct screen_ctx *sc = screen_current(); + int x, y, dx, dy, fontheight, + focusrevert, mutated, xmax, ymax, warp, added, beobnoxious = 0; + XEvent e; + char searchstr[MENU_MAXENTRY + 1]; + char dispstr[MENU_MAXENTRY*2 + 1]; + char promptstr[MENU_MAXENTRY + 1]; + Window focuswin; + struct menu *mi = NULL; + struct menu_q resultq; + char chr; + enum ctltype ctl; + size_t len; + u_int n; + static int list = 0; + int listing = 0; + char endchar = '«'; + struct fontdesc *font = DefaultFont; + + if (prompt == NULL) + prompt = "search"; + + TAILQ_INIT(&resultq); + + xmax = DisplayWidth(G_dpy, sc->which); + ymax = DisplayHeight(G_dpy, sc->which); + + xu_ptr_getpos(sc->rootwin, &x, &y); + + searchstr[0] = '\0'; + + snprintf(promptstr, sizeof(promptstr), "%s »", prompt); + dy = fontheight = font_ascent(font) + font_descent(font) + 1; + snprintf(dispstr, sizeof(dispstr), "%s%c", promptstr, endchar); + dx = font_width(font, dispstr, strlen(dispstr)); + + XMoveResizeWindow(G_dpy, sc->searchwin, x, y, dx, dy); + XSelectInput(G_dpy, sc->searchwin, SearchMask); + XMapRaised(G_dpy, sc->searchwin); + + /* + * TODO: eventually, the mouse should be able to select + * results as well. Right now we grab it only to set a fancy + * cursor. + */ + if (xu_ptr_grab(sc->searchwin, 0, G_cursor_question) < 0) { + XUnmapWindow(G_dpy, sc->searchwin); + return (NULL); + } + + XGetInputFocus(G_dpy, &focuswin, &focusrevert); + XSetInputFocus(G_dpy, sc->searchwin, RevertToPointerRoot, CurrentTime); + + for (;;) { + added = mutated = 0; + + XMaskEvent(G_dpy, SearchMask, &e); + + switch (e.type) { + case KeyPress: + /* + * XXX - C-s & C-r for next and prev. + */ + + if (input_keycodetrans(e.xkey.keycode, e.xkey.state, + &ctl, &chr, 1) < 0) + continue; + + switch (ctl) { + case CTL_ERASEONE: + if ((len = strlen(searchstr)) > 0) { + searchstr[len - 1] = '\0'; + mutated = 1; + } + break; + case CTL_UP: + mi = TAILQ_LAST(&resultq, menu_q); + if (mi == NULL) + break; + + TAILQ_REMOVE(&resultq, mi, resultentry); + TAILQ_INSERT_HEAD(&resultq, mi, resultentry); + break; + case CTL_DOWN: + mi = TAILQ_FIRST(&resultq); + if (mi == NULL) + break; + + TAILQ_REMOVE(&resultq, mi, resultentry); + TAILQ_INSERT_TAIL(&resultq, mi, resultentry); + break; + case CTL_RETURN: + /* This is just picking the match the + * cursor is over. */ + if ((mi = TAILQ_FIRST(&resultq)) != NULL) + goto found; + else + goto out; + case CTL_WIPE: + searchstr[0] = '\0'; + mutated = 1; + break; + case CTL_ALL: + list = !list; + break; + case CTL_ABORT: + goto out; + default: + break; + } + + if (chr != '\0') { + char str[2]; + + str[0] = chr; + str[1] = '\0'; + mutated = 1; + added = + strlcat(searchstr, str, sizeof(searchstr)); + } + + beobnoxious = 0; + if (mutated && strlen(searchstr) > 0) { + (*match)(menuq, &resultq, searchstr); + beobnoxious = TAILQ_EMPTY(&resultq); + if (!beobnoxious && rank != NULL) + (*rank)(&resultq, searchstr); + } else if (mutated) + TAILQ_INIT(&resultq); + + + if (!list && listing && !mutated) { + TAILQ_INIT(&resultq); + listing = 0; + } + + case Expose: + if (list) { + if (TAILQ_EMPTY(&resultq) && list) { + /* Copy them over and rank them. */ + TAILQ_FOREACH(mi, menuq, entry) + TAILQ_INSERT_TAIL(&resultq, mi, + resultentry); + if (rank != NULL) + (*rank)(&resultq, searchstr); + + listing = 1; + } else if (mutated) + listing = 0; + } + + snprintf(dispstr, sizeof(dispstr), "%s%s%c", + promptstr, searchstr, endchar); + dx = font_width(font, dispstr, strlen(dispstr)); + dy = fontheight; + + TAILQ_FOREACH(mi, &resultq, resultentry) { + char *text; + + if (print != NULL) { + (*print)(mi, listing); + text = mi->print; + } else { + mi->print[0] = '\0'; + text = mi->text; + } + + dx = MAX(dx, font_width(font, text, + MIN(strlen(text), MENU_MAXENTRY))); + dy += fontheight; + } + + /* + * Calculate new geometry. + * + * XXX - put this into a util function -- it's + * used elsewhere, too. + */ + warp = 0; + if (x < 0) { + x = 0; + warp = 1; + } + if (x + dx >= xmax) { + x = xmax - dx; + warp = 1; + } + + if (y < 0) { + y = 0; + warp = 1; + } + if (y + dy >= ymax) { + y = ymax - dy; + warp = 1; + } + + if (warp) + xu_ptr_setpos(sc->rootwin, x, y); + + XClearWindow(G_dpy, sc->searchwin); + XMoveResizeWindow(G_dpy, sc->searchwin, x, y, dx, dy); + + font_draw(font, dispstr, strlen(dispstr), sc->searchwin, + 0, font_ascent(font) + 1); + + n = 1; + TAILQ_FOREACH(mi, &resultq, resultentry) { + char *text = mi->print[0] != '\0' ? + mi->print : mi->text; + + font_draw(font, text, + MIN(strlen(text), MENU_MAXENTRY), + sc->searchwin, + 0, n*fontheight + font_ascent(font) + 1); + n++; + } + + if (n > 1) + XFillRectangle(G_dpy, sc->searchwin, sc->gc, + 0, fontheight, dx, fontheight); + + if (beobnoxious) + XFillRectangle(G_dpy, sc->searchwin, sc->gc, + 0, 0, dx, fontheight); + + break; + } + } + +out: + /* (if no match) */ + xu_ptr_ungrab(); + XSetInputFocus(G_dpy, focuswin, focusrevert, CurrentTime); +found: + XUnmapWindow(G_dpy, sc->searchwin); + + return (mi); +} + +/* + * Match: label, title, class. + */ + +void +search_match_client(struct menu_q *menuq, struct menu_q *resultq, char *search) +{ + struct winname *wn; + struct menu *mi, *tierp[4], *before = NULL; + int ntiers = sizeof(tierp)/sizeof(tierp[0]); + + TAILQ_INIT(resultq); + + memset(tierp, 0, sizeof(tierp)); + + /* + * In order of rank: + * + * 1. Look through labels. + * 2. Look at title history, from present to past. + * 3. Look at window class name. + */ + + TAILQ_FOREACH(mi, menuq, entry) { + int tier = -1, t; + struct client_ctx *cc = mi->ctx; + + /* First, try to match on labels. */ + if (cc->label != NULL && _strsubmatch(search, cc->label)) { + cc->matchname = cc->label; + tier = 0; + } + + /* Then, on window names. */ + if (tier < 0) { + TAILQ_FOREACH_REVERSE(wn, &cc->nameq, winname_q, entry) + if (_strsubmatch(search, wn->name)) { + cc->matchname = wn->name; + tier = 2; + break; + } + } + + /* + * See if there is a match on the window class + * name. + */ + + if (tier < 0 && _strsubmatch(search, cc->app_class)) { + cc->matchname = cc->app_class; + tier = 3; + } + + if (tier < 0) + continue; + + /* + * De-rank a client one tier if it's the current + * window. Furthermore, this is denoted by a "!" when + * printing the window name in the search menu. + */ + if (cc == client_current() && tier < ntiers - 1) + tier++; + + /* + * Clients that are hidden get ranked one up. + */ + if (cc->flags & CLIENT_HIDDEN && tier > 0) + tier--; + + assert(tier < ntiers); + + /* + * If you have a tierp, insert after it, and make it + * the new tierp. If you don't have a tierp, find the + * first nonzero tierp above you, insert after it. + * Always make your current tierp the newly inserted + * entry. + */ + + for (t = tier; t >= 0 && ((before = tierp[t]) == NULL); t--) + ; + + if (before == NULL) + TAILQ_INSERT_HEAD(resultq, mi, resultentry); + else + TAILQ_INSERT_AFTER(resultq, before, mi, resultentry); + + tierp[tier] = mi; + } +} + +void +search_print_client(struct menu *mi, int list) +{ + struct client_ctx *cc = mi->ctx; + char flag = ' '; + + if (cc == client_current()) + flag = '!'; + else if (cc->flags & CLIENT_HIDDEN) + flag = '&'; + + if (list) + cc->matchname = TAILQ_FIRST(&cc->nameq)->name; + + snprintf(mi->print, sizeof(mi->print), "%c%s", flag, cc->matchname); + + if (!list && cc->matchname != cc->name && + strlen(mi->print) < sizeof(mi->print) - 1) { + int diff = sizeof(mi->print) - 1 - strlen(mi->print); + const char *marker = ""; + char buf[MENU_MAXENTRY + 1]; + + /* One for the ':' */ + diff -= 1; + + if (strlen(cc->name) > diff) { + marker = ".."; + diff -= 2; + } else { + diff = strlen(cc->name); + } + + strlcpy(buf, mi->print, sizeof(buf)); + snprintf(mi->print, sizeof(mi->print), + "%s:%.*s%s", buf, diff, cc->name, marker); + } +} + +void +search_match_text(struct menu_q *menuq, struct menu_q *resultq, char *search) +{ + struct menu *mi; + + TAILQ_INIT(resultq); + + TAILQ_FOREACH(mi, menuq, entry) + if (_strsubmatch(search, mi->text)) + TAILQ_INSERT_TAIL(resultq, mi, resultentry); +} + +void +search_rank_text(struct menu_q *resultq, char *search) +{ + return; +} + +static int +_strsubmatch(char *sub, char *str) +{ + size_t len, sublen; + u_int n; + + if (sub == NULL || str == NULL) + return (0); + + len = strlen(str); + sublen = strlen(sub); + + if (sublen > len) + return (0); + + for (n = 0; n <= len - sublen; n++) + if (strncasecmp(sub, str + n, sublen) == 0) + return (1); + + return (0); +} |