aboutsummaryrefslogblamecommitdiffstats
path: root/menu.c
blob: a022f84bb1c5dc995f99ed619ec1642dcebff6e3 (plain) (tree)







































































































































































































































































































                                                                             
/*
 * 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);

}