about summary refs log tree commit diff
path: root/search.c
diff options
context:
space:
mode:
Diffstat (limited to 'search.c')
-rw-r--r--search.c450
1 files changed, 450 insertions, 0 deletions
diff --git a/search.c b/search.c
new file mode 100644
index 0000000..c56b794
--- /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$
+ */
+
+#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);
+}