summary refs log tree commit diff
path: root/menu.c
diff options
context:
space:
mode:
authoroga <oga>2008-05-20 14:50:51 +0000
committeroga <oga>2008-05-20 14:50:51 +0000
commit1e46ba72f706d7ab2b0f7a6a14054b908348d10e (patch)
tree0efe57cdad018b6a29f0c0b65113ef08589f953d /menu.c
parent3bb0b451f78d13b4624e52b726f51e4847a84a2f (diff)
downloadcwm-1e46ba72f706d7ab2b0f7a6a14054b908348d10e.tar.gz
cwm-1e46ba72f706d7ab2b0f7a6a14054b908348d10e.tar.xz
cwm-1e46ba72f706d7ab2b0f7a6a14054b908348d10e.zip
Pull out the behaviour in grab_label and search_start into one utility
function menu_filter(). The plan is to eventually merge in grab_menu too.
Shrinks the code a fair bit.

Also, change XMaskEvent for XWindowEvent to prevent getting exposes for other
windows. This is particuarly noticable on slow machines with a LOT of xterms
(todd, you're an odd man).

ok okan@, todd@.
Diffstat (limited to 'menu.c')
-rw-r--r--menu.c296
1 files changed, 296 insertions, 0 deletions
diff --git a/menu.c b/menu.c
new file mode 100644
index 0000000..a022f84
--- /dev/null
+++ b/menu.c
@@ -0,0 +1,296 @@
+/*
+ * Copyright (c) 2008 Owain G. Ainsworth <oga@openbsd.org>
+ * Copyright (c) 2004 Marius Aamodt Eriksen <marius@monkey.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "headers.h"
+#include "calmwm.h"
+
+#define KeyMask (KeyPressMask|ExposureMask)
+
+struct menu_ctx {
+	char			 searchstr[MENU_MAXENTRY + 1];
+	char			 dispstr[MENU_MAXENTRY*2 + 1];
+	char			 promptstr[MENU_MAXENTRY + 1];
+	int			 list;
+	int			 listing;
+	int			 changed;
+	int			 noresult;
+	int			 x;
+	int			 y; /* location */
+    	void (*match)(struct menu_q *, struct menu_q *, char *);
+    	void (*print)(struct menu *, int);
+};
+static struct menu	*menu_handle_key(XEvent *, struct menu_ctx *,
+			     struct menu_q *, struct menu_q *);
+static void		 menu_draw(struct screen_ctx *, struct menu_ctx *,
+			     struct menu_q *, struct menu_q *);
+
+struct menu *
+menu_filter(struct menu_q *menuq, char *prompt, char *initial, int dummy,
+    void (*match)(struct menu_q *, struct menu_q *, char *),
+    void (*print)(struct menu *, int))
+{
+	struct screen_ctx	*sc = screen_current();
+	struct menu_ctx		 mc;
+	struct menu_q		 resultq;
+	struct menu		*mi = NULL;
+	XEvent			 e;
+	Window			 focuswin;
+	int			 dx, dy, focusrevert;
+	char			 endchar = '«';
+	struct fontdesc		*font = DefaultFont;
+
+	TAILQ_INIT(&resultq);
+
+	bzero(&mc, sizeof(mc));
+
+	xu_ptr_getpos(sc->rootwin, &mc.x, &mc.y);
+
+	if (prompt == NULL)
+		prompt = "search";
+
+	if (initial != NULL)
+		strlcpy(mc.searchstr, initial, sizeof(mc.searchstr));
+	else
+		mc.searchstr[0] = '\0';
+
+	mc.match = match;
+	mc.print = print;
+
+	snprintf(mc.promptstr, sizeof(mc.promptstr), "%s»", prompt);
+	snprintf(mc.dispstr, sizeof(mc.dispstr), "%s%s%c", mc.promptstr,
+	    mc.searchstr, endchar);
+	dx = font_width(font, mc.dispstr, strlen(mc.dispstr));
+	dy = sc->fontheight;
+
+	XMoveResizeWindow(X_Dpy, sc->menuwin, mc.x, mc.y, dx, dy);
+	XSelectInput(X_Dpy, sc->menuwin, KeyMask);
+	XMapRaised(X_Dpy, sc->menuwin);
+
+	if (xu_ptr_grab(sc->menuwin, 0, Cursor_question) < 0) {
+		XUnmapWindow(X_Dpy, sc->menuwin);
+		return (NULL);
+	}
+
+	XGetInputFocus(X_Dpy, &focuswin, &focusrevert);
+	XSetInputFocus(X_Dpy, sc->menuwin, RevertToPointerRoot, CurrentTime);
+
+	for (;;) {
+		mc.changed = 0;
+
+		XWindowEvent(X_Dpy, sc->menuwin, KeyMask, &e);
+
+		switch (e.type) {
+		case KeyPress:
+			if ((mi = menu_handle_key(&e, &mc, menuq, &resultq))
+			    != NULL)
+				goto out;
+			/* FALLTHROUGH */
+		case Expose:
+			menu_draw(sc, &mc, menuq, &resultq);
+			break;
+		}
+	}
+out:
+	if (dummy == 0 && mi->dummy) { /* no match */
+		xfree (mi);
+		mi = NULL;
+		xu_ptr_ungrab();
+		XSetInputFocus(X_Dpy, focuswin, focusrevert, CurrentTime);
+	}
+
+	XUnmapWindow(X_Dpy, sc->menuwin);
+
+	return (mi);
+}
+
+static struct menu *
+menu_handle_key(XEvent *e, struct menu_ctx *mc, struct menu_q *menuq,
+    struct menu_q *resultq)
+{
+	struct menu	*mi;
+	enum ctltype	 ctl;
+	char		 chr;
+	size_t		 len;
+
+	if (input_keycodetrans(e->xkey.keycode, e->xkey.state,
+	    &ctl, &chr) < 0)
+		return (NULL);
+
+	switch (ctl) {
+	case CTL_ERASEONE:
+		if ((len = strlen(mc->searchstr)) > 0) {
+			mc->searchstr[len - 1] = '\0';
+			mc->changed = 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:
+		/*
+		 * Return whatever the cursor is currently on. Else
+		 * even if dummy is zero, we need to return something.
+		 */
+		if ((mi = TAILQ_FIRST(resultq)) == NULL) {
+			mi = xmalloc(sizeof *mi);
+			(void)strlcpy(mi->text,
+			    mc->searchstr, sizeof(mi->text));
+			mi->dummy = 1;
+		}
+		return (mi);
+	case CTL_WIPE:
+		mc->searchstr[0] = '\0';
+		mc->changed = 1;
+		break;
+	case CTL_ALL:
+		mc->list = !mc->list;
+		break;
+	case CTL_ABORT:
+		mi = xmalloc(sizeof *mi);
+		mi->text[0] = '\0';
+		mi->dummy = 1;
+		return (mi);
+	default:
+		break;
+	}
+
+	if (chr != '\0') {
+		char str[2];
+
+		str[0] = chr;
+		str[1] = '\0';
+		mc->changed = 1;
+		strlcat(mc->searchstr, str, sizeof(mc->searchstr));
+	}
+
+	mc->noresult = 0;
+	if (mc->changed && strlen(mc->searchstr) > 0) {
+		(*mc->match)(menuq, resultq, mc->searchstr);
+		/* If menuq is empty, never show we've failed */
+		mc->noresult = TAILQ_EMPTY(resultq) && !TAILQ_EMPTY(menuq);
+	} else if (mc->changed)
+		TAILQ_INIT(resultq);
+
+	 if (!mc->list && mc->listing && !mc->changed) {
+		TAILQ_INIT(resultq);
+		mc->listing = 0;
+	}
+
+	return (NULL);
+}
+
+static void
+menu_draw(struct screen_ctx *sc, struct menu_ctx *mc, struct menu_q *menuq,
+    struct menu_q *resultq)
+{
+	struct menu	*mi;
+	char		 endchar = '«';
+	int		 n = 0;
+	int		 dx, dy;
+	int		 xsave, ysave;
+	int		 warp;
+	struct fontdesc	*font = DefaultFont;
+
+	if (mc->list) {
+		if (TAILQ_EMPTY(resultq) && mc->list) {
+			/* Copy them all over. */
+			TAILQ_FOREACH(mi, menuq, entry)
+				TAILQ_INSERT_TAIL(resultq, mi,
+				    resultentry);
+
+			mc->listing = 1;
+		} else if (mc->changed)
+			mc->listing = 0;
+	}
+
+	snprintf(mc->dispstr, sizeof(mc->dispstr), "%s%s%c",
+	    mc->promptstr, mc->searchstr, endchar);
+	dx = font_width(font, mc->dispstr, strlen(mc->dispstr));
+	dy = sc->fontheight;
+
+	TAILQ_FOREACH(mi, resultq, resultentry) {
+		char *text;
+
+		if (mc->print != NULL) {
+			(*mc->print)(mi, mc->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 += sc->fontheight;
+	}
+
+	xsave = mc->x;
+	ysave = mc->y;
+	if (mc->x < 0)
+		mc->x = 0;
+	else if (mc->x + dx >= sc->xmax)
+		mc->x = sc->xmax - dx;
+
+	if (mc->y + dy >= sc->ymax)
+		mc->y = sc->ymax - dy;
+	/* never hide the top of the menu */
+        if (mc->y < 0)
+                mc->y = 0;
+
+	if (mc->x != xsave || mc->y != ysave)
+		xu_ptr_setpos(sc->rootwin, mc->x, mc->y);
+
+	XClearWindow(X_Dpy, sc->menuwin);
+	XMoveResizeWindow(X_Dpy, sc->menuwin, mc->x, mc->y, dx, dy);
+
+	font_draw(font, mc->dispstr, strlen(mc->dispstr), sc->menuwin,
+	    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->menuwin,
+		    0, n*sc->fontheight + font_ascent(font) + 1);
+		n++;
+	}
+
+	if (n > 1)
+		XFillRectangle(X_Dpy, sc->menuwin, sc->gc,
+		    0, sc->fontheight, dx, sc->fontheight);
+
+	if (mc->noresult)
+		XFillRectangle(X_Dpy, sc->menuwin, sc->gc,
+		    0, 0, dx, sc->fontheight);
+
+}