2008-05-20 08:50:51 -06:00
|
|
|
/*
|
2011-05-11 07:53:51 -06:00
|
|
|
* calmwm - the calm window manager
|
|
|
|
*
|
2008-05-20 08:50:51 -06:00
|
|
|
* 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.
|
2011-05-11 07:53:51 -06:00
|
|
|
*
|
2017-12-19 12:38:43 -07:00
|
|
|
* $OpenBSD: menu.c,v 1.105 2017/12/19 19:38:43 okan Exp $
|
2008-05-20 08:50:51 -06:00
|
|
|
*/
|
|
|
|
|
2015-01-19 07:54:16 -07:00
|
|
|
#include <sys/types.h>
|
2009-12-14 21:10:42 -07:00
|
|
|
#include <sys/queue.h>
|
|
|
|
|
2012-11-08 20:52:02 -07:00
|
|
|
#include <ctype.h>
|
2009-12-14 21:10:42 -07:00
|
|
|
#include <err.h>
|
|
|
|
#include <errno.h>
|
2015-01-19 07:54:16 -07:00
|
|
|
#include <limits.h>
|
2014-01-20 11:58:03 -07:00
|
|
|
#include <stdarg.h>
|
2012-11-08 20:52:02 -07:00
|
|
|
#include <stdio.h>
|
2009-12-14 21:10:42 -07:00
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
|
2008-05-20 08:50:51 -06:00
|
|
|
#include "calmwm.h"
|
|
|
|
|
2011-06-27 06:46:54 -06:00
|
|
|
#define PROMPT_SCHAR "\xc2\xbb"
|
|
|
|
#define PROMPT_ECHAR "\xc2\xab"
|
2008-05-20 08:50:51 -06:00
|
|
|
|
2011-03-22 04:47:59 -06:00
|
|
|
enum ctltype {
|
|
|
|
CTL_NONE = -1,
|
|
|
|
CTL_ERASEONE = 0, CTL_WIPE, CTL_UP, CTL_DOWN, CTL_RETURN,
|
2012-11-07 07:39:44 -07:00
|
|
|
CTL_TAB, CTL_ABORT, CTL_ALL
|
2011-03-22 04:47:59 -06:00
|
|
|
};
|
|
|
|
|
2008-05-20 08:50:51 -06:00
|
|
|
struct menu_ctx {
|
2013-05-02 14:18:35 -06:00
|
|
|
struct screen_ctx *sc;
|
2008-05-20 08:50:51 -06:00
|
|
|
char searchstr[MENU_MAXENTRY + 1];
|
|
|
|
char dispstr[MENU_MAXENTRY*2 + 1];
|
|
|
|
char promptstr[MENU_MAXENTRY + 1];
|
|
|
|
int list;
|
|
|
|
int listing;
|
|
|
|
int changed;
|
2008-05-21 08:11:19 -06:00
|
|
|
int prev;
|
|
|
|
int entry;
|
|
|
|
int num;
|
2012-11-07 07:39:44 -07:00
|
|
|
int flags;
|
2015-06-05 12:43:36 -06:00
|
|
|
struct geom geom;
|
2013-04-05 11:07:25 -06:00
|
|
|
void (*match)(struct menu_q *, struct menu_q *, char *);
|
|
|
|
void (*print)(struct menu *, int);
|
2008-05-20 08:50:51 -06:00
|
|
|
};
|
|
|
|
static struct menu *menu_handle_key(XEvent *, struct menu_ctx *,
|
|
|
|
struct menu_q *, struct menu_q *);
|
2016-10-03 12:43:49 -06:00
|
|
|
static void menu_handle_move(struct menu_ctx *,
|
|
|
|
struct menu_q *, int, int);
|
|
|
|
static struct menu *menu_handle_release(struct menu_ctx *,
|
|
|
|
struct menu_q *, int, int);
|
2013-05-20 14:04:36 -06:00
|
|
|
static void menu_draw(struct menu_ctx *, struct menu_q *,
|
|
|
|
struct menu_q *);
|
|
|
|
static void menu_draw_entry(struct menu_ctx *, struct menu_q *,
|
2008-05-21 08:11:19 -06:00
|
|
|
int, int);
|
2013-05-20 14:04:36 -06:00
|
|
|
static int menu_calc_entry(struct menu_ctx *, int, int);
|
2015-06-30 12:42:50 -06:00
|
|
|
static struct menu *menu_complete_path(struct menu_ctx *);
|
2013-05-20 14:04:36 -06:00
|
|
|
static int menu_keycode(XKeyEvent *, enum ctltype *, char *);
|
2008-05-21 08:11:19 -06:00
|
|
|
|
2008-05-20 08:50:51 -06:00
|
|
|
struct menu *
|
2014-01-21 08:42:44 -07:00
|
|
|
menu_filter(struct screen_ctx *sc, struct menu_q *menuq, const char *prompt,
|
|
|
|
const char *initial, int flags,
|
2008-05-20 08:50:51 -06:00
|
|
|
void (*match)(struct menu_q *, struct menu_q *, char *),
|
|
|
|
void (*print)(struct menu *, int))
|
|
|
|
{
|
|
|
|
struct menu_ctx mc;
|
|
|
|
struct menu_q resultq;
|
|
|
|
struct menu *mi = NULL;
|
|
|
|
XEvent e;
|
|
|
|
Window focuswin;
|
2017-12-11 13:58:18 -07:00
|
|
|
int focusrevert, xsave, ysave, xcur, ycur;
|
2008-05-20 08:50:51 -06:00
|
|
|
|
|
|
|
TAILQ_INIT(&resultq);
|
|
|
|
|
2015-06-08 09:08:44 -06:00
|
|
|
xu_ptr_getpos(sc->rootwin, &xsave, &ysave);
|
2011-03-22 07:50:40 -06:00
|
|
|
|
2017-12-12 08:37:16 -07:00
|
|
|
(void)memset(&mc, 0, sizeof(mc));
|
2012-11-07 07:39:44 -07:00
|
|
|
mc.sc = sc;
|
|
|
|
mc.flags = flags;
|
2015-06-08 09:08:44 -06:00
|
|
|
mc.match = match;
|
|
|
|
mc.print = print;
|
|
|
|
mc.entry = mc.prev = -1;
|
|
|
|
mc.geom.x = xsave;
|
|
|
|
mc.geom.y = ysave;
|
2008-05-20 08:50:51 -06:00
|
|
|
|
2015-06-05 09:01:51 -06:00
|
|
|
if (mc.flags & CWM_MENU_LIST)
|
|
|
|
mc.list = 1;
|
|
|
|
|
2017-12-11 13:58:18 -07:00
|
|
|
(void)strlcpy(mc.promptstr, prompt, sizeof(mc.promptstr));
|
2008-05-20 08:50:51 -06:00
|
|
|
if (initial != NULL)
|
2011-07-25 09:10:24 -06:00
|
|
|
(void)strlcpy(mc.searchstr, initial, sizeof(mc.searchstr));
|
2008-05-20 08:50:51 -06:00
|
|
|
else
|
|
|
|
mc.searchstr[0] = '\0';
|
|
|
|
|
2017-12-11 13:58:18 -07:00
|
|
|
XSelectInput(X_Dpy, sc->menu.win, MENUMASK);
|
2016-09-28 18:21:55 -06:00
|
|
|
XMapRaised(X_Dpy, sc->menu.win);
|
2008-05-20 08:50:51 -06:00
|
|
|
|
2016-09-30 12:28:06 -06:00
|
|
|
if (XGrabPointer(X_Dpy, sc->menu.win, False, MENUGRABMASK,
|
|
|
|
GrabModeAsync, GrabModeAsync, None, Conf.cursor[CF_QUESTION],
|
|
|
|
CurrentTime) != GrabSuccess) {
|
2016-09-28 18:21:55 -06:00
|
|
|
XUnmapWindow(X_Dpy, sc->menu.win);
|
2014-09-07 13:27:30 -06:00
|
|
|
return(NULL);
|
2008-05-20 08:50:51 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
XGetInputFocus(X_Dpy, &focuswin, &focusrevert);
|
2016-09-28 18:21:55 -06:00
|
|
|
XSetInputFocus(X_Dpy, sc->menu.win, RevertToPointerRoot, CurrentTime);
|
2008-05-20 08:50:51 -06:00
|
|
|
|
2008-05-23 12:48:57 -06:00
|
|
|
/* make sure keybindings don't remove keys from the menu stream */
|
2016-09-28 18:21:55 -06:00
|
|
|
XGrabKeyboard(X_Dpy, sc->menu.win, True,
|
2008-05-23 12:48:57 -06:00
|
|
|
GrabModeAsync, GrabModeAsync, CurrentTime);
|
|
|
|
|
2008-05-20 08:50:51 -06:00
|
|
|
for (;;) {
|
|
|
|
mc.changed = 0;
|
|
|
|
|
2017-12-11 13:58:18 -07:00
|
|
|
XWindowEvent(X_Dpy, sc->menu.win, MENUMASK, &e);
|
2008-05-20 08:50:51 -06:00
|
|
|
|
|
|
|
switch (e.type) {
|
|
|
|
case KeyPress:
|
|
|
|
if ((mi = menu_handle_key(&e, &mc, menuq, &resultq))
|
|
|
|
!= NULL)
|
|
|
|
goto out;
|
|
|
|
/* FALLTHROUGH */
|
|
|
|
case Expose:
|
2013-05-20 14:04:36 -06:00
|
|
|
menu_draw(&mc, menuq, &resultq);
|
2008-05-20 08:50:51 -06:00
|
|
|
break;
|
2008-05-21 08:11:19 -06:00
|
|
|
case MotionNotify:
|
2016-10-03 12:43:49 -06:00
|
|
|
menu_handle_move(&mc, &resultq,
|
|
|
|
e.xbutton.x, e.xbutton.y);
|
2008-05-21 08:11:19 -06:00
|
|
|
break;
|
|
|
|
case ButtonRelease:
|
2016-10-03 12:43:49 -06:00
|
|
|
if ((mi = menu_handle_release(&mc, &resultq,
|
|
|
|
e.xbutton.x, e.xbutton.y)) != NULL)
|
2008-05-21 08:11:19 -06:00
|
|
|
goto out;
|
|
|
|
break;
|
2011-03-22 05:03:05 -06:00
|
|
|
default:
|
|
|
|
break;
|
2008-05-20 08:50:51 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
out:
|
2012-11-07 07:39:44 -07:00
|
|
|
if ((mc.flags & CWM_MENU_DUMMY) == 0 && mi->dummy) {
|
|
|
|
/* no mouse based match */
|
2012-11-07 13:34:39 -07:00
|
|
|
free(mi);
|
2008-05-20 08:50:51 -06:00
|
|
|
mi = NULL;
|
|
|
|
}
|
|
|
|
|
2011-03-22 05:09:52 -06:00
|
|
|
XSetInputFocus(X_Dpy, focuswin, focusrevert, CurrentTime);
|
2011-03-22 07:50:40 -06:00
|
|
|
/* restore if user didn't move */
|
|
|
|
xu_ptr_getpos(sc->rootwin, &xcur, &ycur);
|
2015-06-05 12:43:36 -06:00
|
|
|
if (xcur == mc.geom.x && ycur == mc.geom.y)
|
2011-03-22 07:50:40 -06:00
|
|
|
xu_ptr_setpos(sc->rootwin, xsave, ysave);
|
2016-09-30 12:28:06 -06:00
|
|
|
XUngrabPointer(X_Dpy, CurrentTime);
|
2011-03-22 05:09:52 -06:00
|
|
|
|
2016-09-28 18:21:55 -06:00
|
|
|
XMoveResizeWindow(X_Dpy, sc->menu.win, 0, 0, 1, 1);
|
|
|
|
XUnmapWindow(X_Dpy, sc->menu.win);
|
2008-05-23 12:48:57 -06:00
|
|
|
XUngrabKeyboard(X_Dpy, CurrentTime);
|
2008-05-20 08:50:51 -06:00
|
|
|
|
2014-09-07 13:27:30 -06:00
|
|
|
return(mi);
|
2008-05-20 08:50:51 -06:00
|
|
|
}
|
|
|
|
|
2012-11-07 07:39:44 -07:00
|
|
|
static struct menu *
|
|
|
|
menu_complete_path(struct menu_ctx *mc)
|
|
|
|
{
|
2015-06-30 12:42:50 -06:00
|
|
|
struct screen_ctx *sc = mc->sc;
|
2012-11-07 07:39:44 -07:00
|
|
|
struct menu *mi, *mr;
|
|
|
|
struct menu_q menuq;
|
2017-12-19 12:38:43 -07:00
|
|
|
int mflags = (CWM_MENU_DUMMY);
|
2012-11-07 07:39:44 -07:00
|
|
|
|
|
|
|
mr = xcalloc(1, sizeof(*mr));
|
|
|
|
|
|
|
|
TAILQ_INIT(&menuq);
|
2013-04-08 07:02:31 -06:00
|
|
|
|
2017-12-19 12:38:43 -07:00
|
|
|
if ((mi = menu_filter(sc, &menuq, mc->searchstr, NULL, mflags,
|
|
|
|
search_match_path, search_print_text)) != NULL) {
|
2012-11-07 07:39:44 -07:00
|
|
|
mr->abort = mi->abort;
|
|
|
|
mr->dummy = mi->dummy;
|
2015-03-28 16:09:10 -06:00
|
|
|
if (mi->text[0] != '\0')
|
|
|
|
snprintf(mr->text, sizeof(mr->text), "%s \"%s\"",
|
|
|
|
mc->searchstr, mi->text);
|
|
|
|
else if (!mr->abort)
|
|
|
|
strlcpy(mr->text, mc->searchstr, sizeof(mr->text));
|
2012-11-07 07:39:44 -07:00
|
|
|
}
|
|
|
|
|
2012-12-17 07:32:39 -07:00
|
|
|
menuq_clear(&menuq);
|
2012-11-07 07:39:44 -07:00
|
|
|
|
2014-09-07 13:27:30 -06:00
|
|
|
return(mr);
|
2012-11-07 07:39:44 -07:00
|
|
|
}
|
|
|
|
|
2008-05-20 08:50:51 -06:00
|
|
|
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;
|
2012-08-07 08:05:49 -06:00
|
|
|
char chr[32];
|
2008-05-20 08:50:51 -06:00
|
|
|
size_t len;
|
2012-08-07 08:05:49 -06:00
|
|
|
int clen, i;
|
|
|
|
wchar_t wc;
|
2008-05-20 08:50:51 -06:00
|
|
|
|
2012-08-07 08:05:49 -06:00
|
|
|
if (menu_keycode(&e->xkey, &ctl, chr) < 0)
|
2014-09-07 13:27:30 -06:00
|
|
|
return(NULL);
|
2008-05-20 08:50:51 -06:00
|
|
|
|
|
|
|
switch (ctl) {
|
|
|
|
case CTL_ERASEONE:
|
|
|
|
if ((len = strlen(mc->searchstr)) > 0) {
|
2012-08-07 08:05:49 -06:00
|
|
|
clen = 1;
|
|
|
|
while (mbtowc(&wc, &mc->searchstr[len-clen], MB_CUR_MAX) == -1)
|
|
|
|
clen++;
|
|
|
|
for (i = 1; i <= clen; i++)
|
|
|
|
mc->searchstr[len - i] = '\0';
|
2008-05-20 08:50:51 -06:00
|
|
|
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) {
|
2014-09-06 10:18:08 -06:00
|
|
|
mi = xmalloc(sizeof(*mi));
|
2008-05-20 08:50:51 -06:00
|
|
|
(void)strlcpy(mi->text,
|
|
|
|
mc->searchstr, sizeof(mi->text));
|
|
|
|
mi->dummy = 1;
|
|
|
|
}
|
2010-02-09 18:23:05 -07:00
|
|
|
mi->abort = 0;
|
2014-09-07 13:27:30 -06:00
|
|
|
return(mi);
|
2008-05-20 08:50:51 -06:00
|
|
|
case CTL_WIPE:
|
|
|
|
mc->searchstr[0] = '\0';
|
|
|
|
mc->changed = 1;
|
|
|
|
break;
|
2012-11-07 07:39:44 -07:00
|
|
|
case CTL_TAB:
|
|
|
|
if ((mi = TAILQ_FIRST(resultq)) != NULL) {
|
2013-04-05 11:07:25 -06:00
|
|
|
/*
|
2012-11-07 07:39:44 -07:00
|
|
|
* - We are in exec_path menu mode
|
|
|
|
* - It is equal to the input
|
|
|
|
* We got a command, launch the file menu
|
|
|
|
*/
|
|
|
|
if ((mc->flags & CWM_MENU_FILE) &&
|
|
|
|
(strncmp(mc->searchstr, mi->text,
|
|
|
|
strlen(mi->text))) == 0)
|
2014-09-07 13:27:30 -06:00
|
|
|
return(menu_complete_path(mc));
|
2012-11-07 07:39:44 -07:00
|
|
|
|
2013-04-05 11:07:25 -06:00
|
|
|
/*
|
2012-11-07 07:39:44 -07:00
|
|
|
* Put common prefix of the results into searchstr
|
|
|
|
*/
|
|
|
|
(void)strlcpy(mc->searchstr,
|
|
|
|
mi->text, sizeof(mc->searchstr));
|
|
|
|
while ((mi = TAILQ_NEXT(mi, resultentry)) != NULL) {
|
|
|
|
i = 0;
|
|
|
|
while (tolower(mc->searchstr[i]) ==
|
|
|
|
tolower(mi->text[i]))
|
|
|
|
i++;
|
|
|
|
mc->searchstr[i] = '\0';
|
|
|
|
}
|
|
|
|
mc->changed = 1;
|
|
|
|
}
|
|
|
|
break;
|
2008-05-20 08:50:51 -06:00
|
|
|
case CTL_ALL:
|
|
|
|
mc->list = !mc->list;
|
|
|
|
break;
|
|
|
|
case CTL_ABORT:
|
2014-09-06 10:18:08 -06:00
|
|
|
mi = xmalloc(sizeof(*mi));
|
2008-05-20 08:50:51 -06:00
|
|
|
mi->text[0] = '\0';
|
|
|
|
mi->dummy = 1;
|
2010-02-09 18:23:05 -07:00
|
|
|
mi->abort = 1;
|
2014-09-07 13:27:30 -06:00
|
|
|
return(mi);
|
2008-05-20 08:50:51 -06:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2012-08-07 08:05:49 -06:00
|
|
|
if (chr[0] != '\0') {
|
2008-05-20 08:50:51 -06:00
|
|
|
mc->changed = 1;
|
2012-08-07 08:05:49 -06:00
|
|
|
(void)strlcat(mc->searchstr, chr, sizeof(mc->searchstr));
|
2008-05-20 08:50:51 -06:00
|
|
|
}
|
|
|
|
|
2017-04-25 06:08:05 -06:00
|
|
|
if (mc->changed) {
|
|
|
|
if (mc->searchstr[0] != '\0')
|
|
|
|
(*mc->match)(menuq, resultq, mc->searchstr);
|
|
|
|
} else if (!mc->list && mc->listing) {
|
2008-05-20 08:50:51 -06:00
|
|
|
TAILQ_INIT(resultq);
|
|
|
|
mc->listing = 0;
|
|
|
|
}
|
|
|
|
|
2014-09-07 13:27:30 -06:00
|
|
|
return(NULL);
|
2008-05-20 08:50:51 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2013-05-20 14:04:36 -06:00
|
|
|
menu_draw(struct menu_ctx *mc, struct menu_q *menuq, struct menu_q *resultq)
|
2008-05-20 08:50:51 -06:00
|
|
|
{
|
2013-05-20 14:04:36 -06:00
|
|
|
struct screen_ctx *sc = mc->sc;
|
2011-05-05 09:32:24 -06:00
|
|
|
struct menu *mi;
|
2015-06-26 11:17:46 -06:00
|
|
|
struct geom area;
|
2012-10-23 10:08:59 -06:00
|
|
|
int n, xsave, ysave;
|
2016-09-28 11:06:33 -06:00
|
|
|
XGlyphInfo extents;
|
2008-05-20 08:50:51 -06:00
|
|
|
|
|
|
|
if (mc->list) {
|
2015-06-05 08:54:04 -06:00
|
|
|
if (TAILQ_EMPTY(resultq)) {
|
2008-05-20 08:50:51 -06:00
|
|
|
/* Copy them all over. */
|
|
|
|
TAILQ_FOREACH(mi, menuq, entry)
|
2015-06-08 09:08:44 -06:00
|
|
|
TAILQ_INSERT_TAIL(resultq, mi, resultentry);
|
2008-05-20 08:50:51 -06:00
|
|
|
|
|
|
|
mc->listing = 1;
|
|
|
|
} else if (mc->changed)
|
|
|
|
mc->listing = 0;
|
|
|
|
}
|
|
|
|
|
2017-12-11 13:58:18 -07:00
|
|
|
(void)snprintf(mc->dispstr, sizeof(mc->dispstr), "%s%s%s%s",
|
|
|
|
mc->promptstr, PROMPT_SCHAR, mc->searchstr, PROMPT_ECHAR);
|
|
|
|
XftTextExtentsUtf8(X_Dpy, sc->xftfont,
|
|
|
|
(const FcChar8*)mc->dispstr, strlen(mc->dispstr), &extents);
|
|
|
|
mc->geom.w = extents.xOff;
|
|
|
|
mc->geom.h = sc->xftfont->height + 1;
|
|
|
|
mc->num = 1;
|
2008-05-20 08:50:51 -06:00
|
|
|
|
|
|
|
TAILQ_FOREACH(mi, resultq, resultentry) {
|
2016-12-06 14:54:10 -07:00
|
|
|
(*mc->print)(mi, mc->listing);
|
2016-09-28 11:06:33 -06:00
|
|
|
XftTextExtentsUtf8(X_Dpy, sc->xftfont,
|
|
|
|
(const FcChar8*)mi->print,
|
|
|
|
MIN(strlen(mi->print), MENU_MAXENTRY), &extents);
|
|
|
|
mc->geom.w = MAX(mc->geom.w, extents.xOff);
|
2015-06-05 12:43:36 -06:00
|
|
|
mc->geom.h += sc->xftfont->height + 1;
|
2008-05-21 08:11:19 -06:00
|
|
|
mc->num++;
|
2008-05-20 08:50:51 -06:00
|
|
|
}
|
|
|
|
|
2015-11-11 07:22:01 -07:00
|
|
|
area = screen_area(sc, mc->geom.x, mc->geom.y, CWM_GAP);
|
2015-06-26 11:17:46 -06:00
|
|
|
area.w += area.x - Conf.bwidth * 2;
|
|
|
|
area.h += area.y - Conf.bwidth * 2;
|
2011-05-05 09:32:24 -06:00
|
|
|
|
2015-06-05 12:43:36 -06:00
|
|
|
xsave = mc->geom.x;
|
|
|
|
ysave = mc->geom.y;
|
2008-05-20 08:50:51 -06:00
|
|
|
|
2012-10-23 10:13:59 -06:00
|
|
|
/* Never hide the top, or left side, of the menu. */
|
2015-06-26 11:17:46 -06:00
|
|
|
if (mc->geom.x + mc->geom.w >= area.w)
|
|
|
|
mc->geom.x = area.w - mc->geom.w;
|
|
|
|
if (mc->geom.x < area.x) {
|
|
|
|
mc->geom.x = area.x;
|
|
|
|
mc->geom.w = MIN(mc->geom.w, (area.w - area.x));
|
2012-10-23 09:50:15 -06:00
|
|
|
}
|
2015-06-26 11:17:46 -06:00
|
|
|
if (mc->geom.y + mc->geom.h >= area.h)
|
|
|
|
mc->geom.y = area.h - mc->geom.h;
|
|
|
|
if (mc->geom.y < area.y) {
|
|
|
|
mc->geom.y = area.y;
|
|
|
|
mc->geom.h = MIN(mc->geom.h, (area.h - area.y));
|
2011-05-05 09:32:24 -06:00
|
|
|
}
|
2008-05-20 08:50:51 -06:00
|
|
|
|
2015-06-05 12:43:36 -06:00
|
|
|
if (mc->geom.x != xsave || mc->geom.y != ysave)
|
|
|
|
xu_ptr_setpos(sc->rootwin, mc->geom.x, mc->geom.y);
|
2008-05-20 08:50:51 -06:00
|
|
|
|
2016-09-28 18:21:55 -06:00
|
|
|
XClearWindow(X_Dpy, sc->menu.win);
|
|
|
|
XMoveResizeWindow(X_Dpy, sc->menu.win, mc->geom.x, mc->geom.y,
|
2015-06-05 12:43:36 -06:00
|
|
|
mc->geom.w, mc->geom.h);
|
2008-05-20 08:50:51 -06:00
|
|
|
|
2017-12-11 13:58:18 -07:00
|
|
|
n = 1;
|
|
|
|
XftDrawStringUtf8(sc->menu.xftdraw,
|
|
|
|
&sc->xftcolor[CWM_COLOR_MENU_FONT], sc->xftfont,
|
|
|
|
0, sc->xftfont->ascent,
|
|
|
|
(const FcChar8*)mc->dispstr, strlen(mc->dispstr));
|
2008-05-20 08:50:51 -06:00
|
|
|
|
|
|
|
TAILQ_FOREACH(mi, resultq, resultentry) {
|
2013-05-02 11:25:15 -06:00
|
|
|
int y = n * (sc->xftfont->height + 1) + sc->xftfont->ascent + 1;
|
2012-10-23 09:32:38 -06:00
|
|
|
|
|
|
|
/* Stop drawing when menu doesn't fit inside the screen. */
|
2015-06-26 11:17:46 -06:00
|
|
|
if (mc->geom.y + y > area.h)
|
2012-10-23 09:32:38 -06:00
|
|
|
break;
|
2008-05-20 08:50:51 -06:00
|
|
|
|
2016-09-28 18:21:55 -06:00
|
|
|
XftDrawStringUtf8(sc->menu.xftdraw,
|
2016-09-28 11:06:33 -06:00
|
|
|
&sc->xftcolor[CWM_COLOR_MENU_FONT], sc->xftfont,
|
|
|
|
0, y,
|
|
|
|
(const FcChar8*)mi->print, strlen(mi->print));
|
2008-05-20 08:50:51 -06:00
|
|
|
n++;
|
|
|
|
}
|
2017-12-11 13:58:18 -07:00
|
|
|
if (n > 1)
|
2013-12-02 12:49:26 -07:00
|
|
|
menu_draw_entry(mc, resultq, 1, 1);
|
2012-12-16 19:28:45 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2013-05-20 14:04:36 -06:00
|
|
|
menu_draw_entry(struct menu_ctx *mc, struct menu_q *resultq,
|
|
|
|
int entry, int active)
|
2012-12-16 19:28:45 -07:00
|
|
|
{
|
2013-05-20 14:04:36 -06:00
|
|
|
struct screen_ctx *sc = mc->sc;
|
|
|
|
struct menu *mi;
|
2017-12-11 13:58:18 -07:00
|
|
|
int color, i = 1;
|
2012-12-16 19:28:45 -07:00
|
|
|
|
|
|
|
TAILQ_FOREACH(mi, resultq, resultentry)
|
|
|
|
if (entry == i++)
|
|
|
|
break;
|
|
|
|
if (mi == NULL)
|
|
|
|
return;
|
2012-12-16 19:53:29 -07:00
|
|
|
|
2015-07-01 08:36:42 -06:00
|
|
|
color = (active) ? CWM_COLOR_MENU_FG : CWM_COLOR_MENU_BG;
|
2016-09-28 18:21:55 -06:00
|
|
|
XftDrawRect(sc->menu.xftdraw, &sc->xftcolor[color], 0,
|
2015-06-05 12:43:36 -06:00
|
|
|
(sc->xftfont->height + 1) * entry, mc->geom.w,
|
2013-05-02 11:25:15 -06:00
|
|
|
(sc->xftfont->height + 1) + sc->xftfont->descent);
|
2015-07-01 08:36:42 -06:00
|
|
|
color = (active) ? CWM_COLOR_MENU_FONT_SEL : CWM_COLOR_MENU_FONT;
|
2016-09-28 18:21:55 -06:00
|
|
|
XftDrawStringUtf8(sc->menu.xftdraw,
|
2016-09-28 11:06:33 -06:00
|
|
|
&sc->xftcolor[color], sc->xftfont,
|
|
|
|
0, (sc->xftfont->height + 1) * entry + sc->xftfont->ascent + 1,
|
|
|
|
(const FcChar8*)mi->print, strlen(mi->print));
|
2008-05-21 08:11:19 -06:00
|
|
|
}
|
|
|
|
|
2009-06-26 06:21:58 -06:00
|
|
|
static void
|
2016-10-03 12:43:49 -06:00
|
|
|
menu_handle_move(struct menu_ctx *mc, struct menu_q *resultq, int x, int y)
|
2008-05-21 08:11:19 -06:00
|
|
|
{
|
|
|
|
mc->prev = mc->entry;
|
2016-10-03 12:43:49 -06:00
|
|
|
mc->entry = menu_calc_entry(mc, x, y);
|
2008-05-21 08:11:19 -06:00
|
|
|
|
2012-12-16 19:28:45 -07:00
|
|
|
if (mc->prev == mc->entry)
|
|
|
|
return;
|
|
|
|
|
2008-05-21 08:11:19 -06:00
|
|
|
if (mc->prev != -1)
|
2013-05-20 14:04:36 -06:00
|
|
|
menu_draw_entry(mc, resultq, mc->prev, 0);
|
2008-05-21 08:11:19 -06:00
|
|
|
if (mc->entry != -1) {
|
2016-09-30 12:28:06 -06:00
|
|
|
XChangeActivePointerGrab(X_Dpy, MENUGRABMASK,
|
|
|
|
Conf.cursor[CF_NORMAL], CurrentTime);
|
2013-05-20 14:04:36 -06:00
|
|
|
menu_draw_entry(mc, resultq, mc->entry, 1);
|
2016-09-30 12:28:06 -06:00
|
|
|
}
|
2008-05-21 08:11:19 -06:00
|
|
|
}
|
|
|
|
|
2009-06-26 06:21:58 -06:00
|
|
|
static struct menu *
|
2016-10-03 12:43:49 -06:00
|
|
|
menu_handle_release(struct menu_ctx *mc, struct menu_q *resultq, int x, int y)
|
2008-05-21 08:11:19 -06:00
|
|
|
{
|
2013-05-20 14:04:36 -06:00
|
|
|
struct menu *mi;
|
2017-12-11 13:58:18 -07:00
|
|
|
int entry, i = 1;
|
2008-05-21 08:11:19 -06:00
|
|
|
|
2016-10-03 12:43:49 -06:00
|
|
|
entry = menu_calc_entry(mc, x, y);
|
2008-05-21 08:11:19 -06:00
|
|
|
|
|
|
|
TAILQ_FOREACH(mi, resultq, resultentry)
|
|
|
|
if (entry == i++)
|
|
|
|
break;
|
|
|
|
if (mi == NULL) {
|
2009-06-19 18:22:39 -06:00
|
|
|
mi = xmalloc(sizeof(*mi));
|
2008-05-21 08:11:19 -06:00
|
|
|
mi->text[0] = '\0';
|
|
|
|
mi->dummy = 1;
|
|
|
|
}
|
2014-09-07 13:27:30 -06:00
|
|
|
return(mi);
|
2008-05-21 08:11:19 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
2013-05-20 14:04:36 -06:00
|
|
|
menu_calc_entry(struct menu_ctx *mc, int x, int y)
|
2008-05-21 08:11:19 -06:00
|
|
|
{
|
2013-05-20 14:04:36 -06:00
|
|
|
struct screen_ctx *sc = mc->sc;
|
|
|
|
int entry;
|
2008-07-11 08:21:28 -06:00
|
|
|
|
2013-05-02 11:25:15 -06:00
|
|
|
entry = y / (sc->xftfont->height + 1);
|
2008-05-21 08:11:19 -06:00
|
|
|
|
|
|
|
/* in bounds? */
|
2015-06-05 12:43:36 -06:00
|
|
|
if (x < 0 || x > mc->geom.w || y < 0 ||
|
2013-05-02 11:25:15 -06:00
|
|
|
y > (sc->xftfont->height + 1) * mc->num ||
|
|
|
|
entry < 0 || entry >= mc->num)
|
2008-05-21 08:11:19 -06:00
|
|
|
entry = -1;
|
|
|
|
|
2017-12-11 13:58:18 -07:00
|
|
|
if (entry == 0)
|
2008-05-21 08:11:19 -06:00
|
|
|
entry = -1;
|
2008-05-20 08:50:51 -06:00
|
|
|
|
2014-09-07 13:27:30 -06:00
|
|
|
return(entry);
|
2008-05-20 08:50:51 -06:00
|
|
|
}
|
2011-03-22 04:47:59 -06:00
|
|
|
|
|
|
|
static int
|
2012-08-07 08:05:49 -06:00
|
|
|
menu_keycode(XKeyEvent *ev, enum ctltype *ctl, char *chr)
|
2011-03-22 04:47:59 -06:00
|
|
|
{
|
2014-01-03 08:29:06 -07:00
|
|
|
KeySym ks;
|
2011-03-22 04:47:59 -06:00
|
|
|
|
|
|
|
*ctl = CTL_NONE;
|
2012-08-07 08:05:49 -06:00
|
|
|
chr[0] = '\0';
|
2011-03-22 04:47:59 -06:00
|
|
|
|
2012-08-07 08:05:49 -06:00
|
|
|
ks = XkbKeycodeToKeysym(X_Dpy, ev->keycode, 0,
|
2016-10-03 12:43:49 -06:00
|
|
|
(ev->state & ShiftMask) ? 1 : 0);
|
2011-03-22 04:47:59 -06:00
|
|
|
|
|
|
|
/* Look for control characters. */
|
|
|
|
switch (ks) {
|
|
|
|
case XK_BackSpace:
|
|
|
|
*ctl = CTL_ERASEONE;
|
|
|
|
break;
|
2016-08-28 09:23:24 -06:00
|
|
|
case XK_KP_Enter:
|
2011-03-22 04:47:59 -06:00
|
|
|
case XK_Return:
|
|
|
|
*ctl = CTL_RETURN;
|
|
|
|
break;
|
2012-11-07 07:39:44 -07:00
|
|
|
case XK_Tab:
|
|
|
|
*ctl = CTL_TAB;
|
|
|
|
break;
|
2011-03-22 04:47:59 -06:00
|
|
|
case XK_Up:
|
|
|
|
*ctl = CTL_UP;
|
|
|
|
break;
|
|
|
|
case XK_Down:
|
|
|
|
*ctl = CTL_DOWN;
|
|
|
|
break;
|
|
|
|
case XK_Escape:
|
|
|
|
*ctl = CTL_ABORT;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2016-10-03 12:43:49 -06:00
|
|
|
if (*ctl == CTL_NONE && (ev->state & ControlMask)) {
|
2011-03-22 04:47:59 -06:00
|
|
|
switch (ks) {
|
|
|
|
case XK_s:
|
|
|
|
case XK_S:
|
|
|
|
/* Emacs "next" */
|
|
|
|
*ctl = CTL_DOWN;
|
|
|
|
break;
|
|
|
|
case XK_r:
|
|
|
|
case XK_R:
|
|
|
|
/* Emacs "previous" */
|
|
|
|
*ctl = CTL_UP;
|
|
|
|
break;
|
|
|
|
case XK_u:
|
|
|
|
case XK_U:
|
|
|
|
*ctl = CTL_WIPE;
|
|
|
|
break;
|
|
|
|
case XK_h:
|
|
|
|
case XK_H:
|
|
|
|
*ctl = CTL_ERASEONE;
|
|
|
|
break;
|
|
|
|
case XK_a:
|
|
|
|
case XK_A:
|
|
|
|
*ctl = CTL_ALL;
|
|
|
|
break;
|
2016-09-20 13:58:54 -06:00
|
|
|
case XK_bracketleft:
|
|
|
|
*ctl = CTL_ABORT;
|
|
|
|
break;
|
2011-03-22 04:47:59 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-03 12:43:49 -06:00
|
|
|
if (*ctl == CTL_NONE && (ev->state & Mod1Mask)) {
|
2011-03-22 04:47:59 -06:00
|
|
|
switch (ks) {
|
|
|
|
case XK_j:
|
|
|
|
case XK_J:
|
|
|
|
/* Vi "down" */
|
|
|
|
*ctl = CTL_DOWN;
|
|
|
|
break;
|
|
|
|
case XK_k:
|
|
|
|
case XK_K:
|
|
|
|
/* Vi "up" */
|
|
|
|
*ctl = CTL_UP;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (*ctl != CTL_NONE)
|
2014-09-07 13:27:30 -06:00
|
|
|
return(0);
|
2011-03-22 04:47:59 -06:00
|
|
|
|
2012-08-07 08:05:49 -06:00
|
|
|
if (XLookupString(ev, chr, 32, &ks, NULL) < 0)
|
2014-09-07 13:27:30 -06:00
|
|
|
return(-1);
|
2011-03-22 04:47:59 -06:00
|
|
|
|
2014-09-07 13:27:30 -06:00
|
|
|
return(0);
|
2011-03-22 04:47:59 -06:00
|
|
|
}
|
2012-12-17 07:32:39 -07:00
|
|
|
|
2014-01-20 11:58:03 -07:00
|
|
|
void
|
|
|
|
menuq_add(struct menu_q *mq, void *ctx, const char *fmt, ...)
|
|
|
|
{
|
|
|
|
va_list ap;
|
|
|
|
struct menu *mi;
|
|
|
|
|
|
|
|
mi = xcalloc(1, sizeof(*mi));
|
|
|
|
mi->ctx = ctx;
|
|
|
|
|
|
|
|
va_start(ap, fmt);
|
2016-04-28 10:28:38 -06:00
|
|
|
if (fmt != NULL)
|
|
|
|
(void)vsnprintf(mi->text, sizeof(mi->text), fmt, ap);
|
|
|
|
else
|
|
|
|
mi->text[0] = '\0';
|
2014-01-20 11:58:03 -07:00
|
|
|
va_end(ap);
|
|
|
|
|
|
|
|
TAILQ_INSERT_TAIL(mq, mi, entry);
|
|
|
|
}
|
|
|
|
|
2012-12-17 07:32:39 -07:00
|
|
|
void
|
|
|
|
menuq_clear(struct menu_q *mq)
|
|
|
|
{
|
|
|
|
struct menu *mi;
|
|
|
|
|
|
|
|
while ((mi = TAILQ_FIRST(mq)) != NULL) {
|
|
|
|
TAILQ_REMOVE(mq, mi, entry);
|
|
|
|
free(mi);
|
|
|
|
}
|
|
|
|
}
|
2016-09-30 09:12:19 -06:00
|
|
|
|
|
|
|
void
|
|
|
|
menu_windraw(struct screen_ctx *sc, Window win, const char *fmt, ...)
|
|
|
|
{
|
|
|
|
va_list ap;
|
|
|
|
int i;
|
|
|
|
char *text;
|
|
|
|
XGlyphInfo extents;
|
|
|
|
|
|
|
|
va_start(ap, fmt);
|
|
|
|
i = vasprintf(&text, fmt, ap);
|
|
|
|
va_end(ap);
|
|
|
|
|
|
|
|
if (i < 0 || text == NULL)
|
|
|
|
err(1, "vasprintf");
|
|
|
|
|
|
|
|
XftTextExtentsUtf8(X_Dpy, sc->xftfont, (const FcChar8*)text,
|
|
|
|
strlen(text), &extents);
|
|
|
|
|
|
|
|
XReparentWindow(X_Dpy, sc->menu.win, win, 0, 0);
|
|
|
|
XMoveResizeWindow(X_Dpy, sc->menu.win, 0, 0,
|
|
|
|
extents.xOff, sc->xftfont->height);
|
|
|
|
XMapWindow(X_Dpy, sc->menu.win);
|
|
|
|
XClearWindow(X_Dpy, sc->menu.win);
|
|
|
|
|
|
|
|
XftDrawStringUtf8(sc->menu.xftdraw, &sc->xftcolor[CWM_COLOR_MENU_FONT],
|
|
|
|
sc->xftfont, 0, sc->xftfont->ascent + 1,
|
|
|
|
(const FcChar8*)text, strlen(text));
|
|
|
|
|
|
|
|
free(text);
|
|
|
|
}
|