/*-
* Copyright (c) 2001 Jordan DeLong
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the author nor the names of contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "menu.h"
/* per-screen structure */
static struct menuscr {
GC menugc;
} *menuscr;
/* font to use */
#ifdef I18N
static XFontSet menufont;
static XFontSetExtents *menufont_extents;
#else
static XFontStruct *menufont = NULL;
#endif
/* decoration group */
static dgroup_t *menu_dgroup = NULL;
/* pixmaps */
static pixmap_t *submenu_bullet = NULL;
/* stacking layer */
int menu_stacklayer;
/* XContext to get menus */
XContext menu_context;
/* initialize the menu system */
int menu_init(char *fontname, dgroup_t *dgroup, pixmap_t *bullet) {
XGCValues gcvalues;
int i, cnt;
/* this is used to get menu_t's from windows */
menu_context = XUniqueContext();
/*
* get the font structure, fall back on a semi-decent
* font, and if we can't even get that just use 'fixed',
* which should always be there. If we can't use 'fixed'
* we give up.
*/
if (fontname) {
#ifdef I18N
menufont = XLoadQueryFontSet(display, fontname);
#else
menufont = XLoadQueryFont(display, fontname);
#endif
if (menufont)
goto gotfont;
PWARN("unable to get requested menu_font, trying default");
}
PWARN("using default font");
#ifdef I18N
menufont = XLoadQueryFontSet(display,
"-*-helvetica-medium-r-normal-*-12-*-*-*-*-*-*-*");
#else
menufont = XLoadQueryFont(display,
"-*-helvetica-medium-r-normal-*-12-*-*-*-*-*-*-*");
#endif
if (menufont)
goto gotfont;
PWARN("failed to load default font; trying 'fixed' as last resort");
#ifdef I18N
menufont = XLoadQueryFontSet(display, "fixed");
#else
menufont = XLoadQueryFont(display, "fixed");
#endif
if (menufont)
goto gotfont;
PWARN("failed to load font 'fixed', giving up on menus");
return -1;
gotfont:
#ifdef I18N
menufont_extents = XExtentsOfFontSet(menufont);
#endif
/* get the per-screen structure */
cnt = ScreenCount(display);
menuscr = calloc(cnt, sizeof(struct menuscr));
if (!menuscr)
return -1;
/* fill in the structure */
for (i = 0; i < cnt; i++) {
/*
* XXX: color needs to be in a parameter
*/
gcvalues.foreground = WhitePixel(display, i);
gcvalues.background = BlackPixel(display, i);
#ifdef I18N
menuscr[i].menugc = XCreateGC(display, RootWindow(display, i),
GCForeground | GCBackground, &gcvalues);
#else
gcvalues.font = menufont->fid;
menuscr[i].menugc = XCreateGC(display, RootWindow(display, i),
GCFont | GCForeground | GCBackground, &gcvalues);
#endif
}
menu_dgroup = dgroup;
submenu_bullet = bullet;
return 0;
}
/* shutdown the menu system */
void menu_shutdown() {
int i, cnt;
cnt = ScreenCount(display);
if (menuscr) {
for (i = 0; i < cnt; i++)
if (menuscr[i].menugc)
XFreeGC(display, menuscr[i].menugc);
free(menuscr);
}
if (menufont)
#ifdef I18N
XFreeFontSet(display, menufont);
#else
XFreeFont(display, menufont);
#endif
}
/* create a menu */
menu_t *menu_create() {
menu_t *menu;
menu = calloc(1, sizeof(menu_t));
if (!menu)
return NULL;
menu->forefather = menu;
return menu;
}
/* add an entry to a menu, pos is the position to add it at */
menuent_t *menu_addent(menu_t *menu, int pos, int type, char *text, void *dat) {
menuent_t *ent;
menuent_t **tmp;
menu_t **tmpsubmenus;
int i;
ent = calloc(1, sizeof(menuent_t));
if (!ent)
return NULL;
ent->type = type;
ent->text = text;
/* get space for the new entry */
tmp = realloc(menu->entries, (menu->entry_count + 1) * sizeof(menuent_t *));
if (!tmp)
goto free1;
menu->entries = tmp;
menu->entry_count++;
/* submenus need some special attention */
if (type == ET_SUBMENU) {
tmpsubmenus = realloc(menu->submenus, (menu->submenu_count + 1) * sizeof(menu_t *));
if (!tmpsubmenus)
goto free2;
menu->submenus = tmpsubmenus;
menu->submenus[menu->submenu_count] = (menu_t *) dat;
ent->dat.submenu = menu->submenu_count++;
/* set the forefather pointer, and set it on all subwindows of this one */
((menu_t *) dat)->forefather = menu->forefather;
for (i = 0; i < ((menu_t *) dat)->submenu_count; i++)
((menu_t *) dat)->submenus[i]->forefather = menu->forefather;
} else
ent->dat.dat = dat;
/* move to make space if necessary, and put it in */
if (pos == POS_LAST || pos >= menu->entry_count)
pos = menu->entry_count - 1;
else
memmove(&menu->entries[pos + 1], &menu->entries[pos],
(menu->entry_count - pos) * sizeof(menuent_t *));
menu->entries[pos] = ent;
return ent;
free2:
menu->entry_count--;
free1:
free(ent);
return NULL;
}
/* dealloc a menu entry */
void menu_freeent(menuent_t *ent) {
free(ent->text);
/* free entry-type specific data */
switch (ent->type) {
case ET_RESTART:
case ET_COMMAND:
if (ent->dat.cmd)
free(ent->dat.cmd);
break;
default:
break;
}
free(ent);
}
/* resize a menu */
void menu_size(menu_t *menu) {
screen_t *screen;
int width, height;
int strwidth, i;
width = MENU_MINWIDTH;
height = MENU_YBORDER * 2;
for (i = 0; i < menu->entry_count; i++) {
#ifdef I18N
height += menufont_extents->max_ink_extent.height;
strwidth = XmbTextEscapement(menufont, menu->entries[i]->text, strlen(menu->entries[i]->text));
#else
height += menufont->ascent + menufont->descent;
strwidth = XTextWidth(menufont, menu->entries[i]->text, strlen(menu->entries[i]->text));
#endif
if (submenu_bullet && menu->entries[i]->type == ET_SUBMENU)
strwidth += submenu_bullet->width;
if (width < strwidth)
width = strwidth;
}
width += MENU_XBORDER * 2;
/* fill in the client_t for each screen */
TAILQ_FOREACH(screen, &screen_list, s_list) {
menu->client[screen->num]->width = width;
menu->client[screen->num]->height = height;
client_sizeframe(menu->client[screen->num]);
}
}
/* realize a menu (called from post-init, i.e. start) */
int menu_realize(menu_t *menu) {
XSetWindowAttributes attr;
clientflags_t flags;
screen_t *screen;
Window wnd;
int i;
menu->client = calloc(screen_count, sizeof(client_t *));
if (!menu->client)
return -1;
menu->active_sub = calloc(screen_count, sizeof(menu_t *));
if (!menu->active_sub)
goto free;
bzero(&flags, sizeof(clientflags_t));
flags.internal = 1;
flags.nofocus = 1;
flags.noresize = 1;
flags.noiconify = 1;
flags.nodelete = 1;
flags.sticky = 1;
TAILQ_FOREACH(screen, &screen_list, s_list) {
attr.background_pixel = BlackPixel(display, screen->num);
wnd = XCreateWindow(display, RootWindow(display, screen->num),
0, 0, 50, 50, 0, CopyFromParent, CopyFromParent,
CopyFromParent, CWBackPixel, &attr);
menu->client[screen->num] = client_add(screen, wnd,
&flags, menu_dgroup);
if (!menu->client[screen->num])
return -1;
/* set layer */
menu->client[screen->num]->stacklayer = menu_stacklayer;
XSaveContext(display, menu->client[screen->num]->frame,
menu_context, (XPointer) menu);
XSelectInput(display, menu->client[screen->num]->window,
ExposureMask | ButtonPressMask | ButtonReleaseMask | EnterWindowMask);
plugin_setcontext(plugin_this, menu->client[screen->num]->window);
XMapWindow(display, menu->client[screen->num]->window);
}
menu_size(menu);
/* realize the submenus */
for (i = 0; i < menu->submenu_count; i++)
menu_realize(menu->submenus[i]);
return 0;
free:
free(menu->client);
return -1;
}
/* kill a menu, this may be before it's realized, so a lot is conditional */
void menu_delete(menu_t *menu) {
int cnt, i;
/* kill submenus */
for (i = 0; i < menu->submenu_count; i++)
menu_delete(menu->submenus[i]);
if (menu->submenus)
free(menu->submenus);
/* get rid of the menu entries */
for (i = 0; i < menu->entry_count; i++)
if (menu->entries[i])
menu_freeent(menu->entries[i]);
if (menu->entries)
free(menu->entries);
/* free created clients */
if (menu->client) {
cnt = ScreenCount(display);
for (i = 0; i < cnt; i++) {
plugin_rmcontext(menu->client[i]->window);
XDeleteContext(display, menu->client[i]->frame, menu_context);
if (menu->client[i])
client_rm(menu->client[i]);
}
free(menu->client);
}
/* free the active submenu pointers */
if (menu->active_sub)
free(menu->active_sub);
free(menu);
}
/* draw a menu entry */
static void menu_drawent(menu_t *menu, client_t *client, int i, int y) {
int tmpx, tmpy;
if (submenu_bullet && menu->entries[i]->type == ET_SUBMENU) {
tmpx = client->width - submenu_bullet->width;
#ifdef I18N
tmpy = y + (((menufont_extents->max_ink_extent.height) / 2) - submenu_bullet->height / 2);
#else
tmpy = y + (((menufont->ascent + menufont->descent) / 2) - submenu_bullet->height / 2);
#endif
XSetClipMask(display, menuscr[client->screen->num].menugc,
submenu_bullet->shapemasks[client->screen->num]);
XSetClipOrigin(display, menuscr[client->screen->num].menugc, tmpx, tmpy);
XCopyArea(display, submenu_bullet->pixmaps[client->screen->num], client->window,
menuscr[client->screen->num].menugc, 0, 0, submenu_bullet->width,
submenu_bullet->height, tmpx, tmpy);
XSetClipMask(display, menuscr[client->screen->num].menugc, None);
}
#ifdef I18N
XmbDrawString(display, client->window, menufont, menuscr[client->screen->num].menugc,
MENU_XBORDER, y + menufont_extents->max_logical_extent.height * 4 / 5, menu->entries[i]->text, strlen(menu->entries[i]->text));
#else
XDrawString(display, client->window, menuscr[client->screen->num].menugc,
MENU_XBORDER, y + menufont->ascent, menu->entries[i]->text, strlen(menu->entries[i]->text));
#endif
}
/* draw a menu; entidx is for use during menu_interact */
void menu_expose(menu_t *menu, client_t *client, XExposeEvent *e) {
int i, y, first, last;
/*
* be slightly intelligent about what lines to draw; it's
* probably not worth the time to worry about the width
* of the line getting drawn...
*/
y = MENU_YBORDER;
last = first = -1;
for (i = 0; i < menu->entry_count; i++) {
#ifdef I18N
y += menufont_extents->max_ink_extent.height;
#else
y += menufont->ascent + menufont->descent;
#endif
if (y > e->y && first == -1)
first = i - 1;
if (y > e->y + e->height && last == -1)
last = i;
}
/* now we draw the lines that need it */
if (first < 0) first = 0;
if (last == -1) last = menu->entry_count - 1;
#ifdef I18N
y = MENU_YBORDER + (menufont_extents->max_ink_extent.height) * first;
#else
y = MENU_YBORDER + (menufont->ascent + menufont->descent) * first;
#endif
for (i = first; i < last + 1; i++) {
menu_drawent(menu, client, i, y);
#ifdef I18N
y += menufont_extents->max_ink_extent.height;
#else
y += menufont->ascent + menufont->descent;
#endif
}
}
/* perform an action for a menu entry */
static void menu_action(menu_t *menu, menuent_t *ent, int snum) {
switch (ent->type) {
case ET_COMMAND:
action_exec(menu->client[snum]->screen->num, ent->dat.cmd);
break;
case ET_RESTART:
if (ent->dat.cmd)
restart_bin = ent->dat.cmd;
else
restart_bin = binary_name;
/* fall through */
case ET_EXIT:
restart_flag = 1;
break;
case ET_ABORT:
/* this is useful for debug */
if (!fork()) { abort(); exit(1); }
break;
}
}
/* open a menu */
static void menu_open(client_t *client, int x, int y) {
client->x = x;
client->y = y;
XMoveWindow(display, client->frame, client->x, client->y);
workspace_add_client(client->screen->desktop->current_space, client);
desktop_add_client(client);
XMapWindow(display, client->frame);
stacking_raise(client);
client->state = NormalState;
}
/* close a menu */
static void menu_close(menu_t *menu, client_t *client) {
int snum = client->screen->num;
/* close any open submenus */
if (menu->active_sub[snum]) {
menu_close(menu->active_sub[snum], menu->active_sub[snum]->client[snum]);
menu->active_sub[snum] = NULL;
}
if (client->workspace) {
desktop_rm_client(client);
workspace_rm_client(client);
}
XUnmapWindow(display, client->frame);
client->state = WithdrawnState;
}
/* draw (or erase) a highlight */
static void drawhighlight(client_t *client, int idx) {
#ifdef I18N
XFillRectangle(display, client->window, client->screen->xorgc, 2,
idx * (menufont_extents->max_ink_extent.height) + MENU_YBORDER,
client->width - 5, menufont_extents->max_ink_extent.height);
#else
XFillRectangle(display, client->window, client->screen->xorgc, 2,
idx * (menufont->ascent + menufont->descent) + MENU_YBORDER,
client->width - 5, menufont->ascent + menufont->descent);
#endif
}
/* open submenus or close them as neccessary while dragging across */
static void passopen(menu_t *menu, client_t *client, int entidx, int *oldentidx, int snum) {
if (menu->entries[entidx]->type == ET_SUBMENU) {
if (menu->active_sub[snum] != menu->submenus[menu->entries[entidx]->dat.submenu]) {
if (menu->active_sub[snum])
menu_close(menu, menu->active_sub[snum]->client[snum]);
menu->active_sub[snum] = menu->submenus[menu->entries[entidx]->dat.submenu];
#ifdef I18N
menu_open(menu->active_sub[snum]->client[snum],
client->x + FULLWIDTH(client),
client->y + MENU_YBORDER + (entidx * (menufont_extents->max_ink_extent.height)));
#else
menu_open(menu->active_sub[snum]->client[snum],
client->x + FULLWIDTH(client),
client->y + MENU_YBORDER + (entidx * (menufont->ascent + menufont->descent)));
#endif
}
*oldentidx = -1;
} else {
drawhighlight(client, entidx);
if (menu->active_sub[snum]) {
menu_close(menu, menu->active_sub[snum]->client[snum]);
menu->active_sub[snum] = NULL;
}
*oldentidx = entidx;
}
}
/* main menu operation routine, e == NULL if not from a keypress */
static void menu_interact(menu_t *menu, client_t *client, XButtonEvent *butev) {
XEvent event;
XMotionEvent *e;
menu_t *tmpmenu;
Window lastsubwin, dumwin;
int xpos, ypos, snum;
int oldentidx, entidx, mask;
int moving;
/* grab the pointer */
mask = ButtonReleaseMask | PointerMotionMask | ButtonMotionMask;
if (XGrabPointer(display, client->screen->root, 0, mask, GrabModeAsync,
GrabModeAsync, client->screen->root, None, CurrentTime) != GrabSuccess)
return;
stacking_raise(client);
snum = client->screen->num;
/* set the entry index var if we got a event structure */
if (butev) {
#ifdef I18N
oldentidx = entidx = (butev->y - MENU_YBORDER)
/ (menufont_extents->max_ink_extent.height);
#else
oldentidx = entidx = (butev->y - MENU_YBORDER)
/ (menufont->ascent + menufont->descent);
#endif
if (entidx >= 0 && entidx < menu->entry_count) {
XSync(display, 0);
while (XCheckMaskEvent(display, ExposureMask, &event))
event_handle(&event);
passopen(menu, client, entidx, &oldentidx, snum);
} else {
oldentidx = entidx = -1;
}
} else {
oldentidx = entidx = -1;
}
moving = 0;
lastsubwin = -1;
mask |= ExposureMask;
while (1) {
XMaskEvent(display, mask, &event);
switch (event.type) {
case MotionNotify:
e = &event.xmotion;
moving = 1;
/* see if we moved to another window */
if (e->subwindow != lastsubwin && e->subwindow != client->frame) {
lastsubwin = e->subwindow;
if (oldentidx != -1)
drawhighlight(client, oldentidx);
if (e->subwindow == None
|| XFindContext(display, e->subwindow, menu_context, (XPointer *) &tmpmenu) != 0) {
oldentidx = entidx = -1;
break;
}
if (menu->active_sub[snum] && menu->active_sub[snum]->client[snum]->frame != e->subwindow)
menu_close(menu, menu->active_sub[snum]->client[snum]);
menu = tmpmenu;
client = menu->client[snum];
oldentidx = -1;
}
/* get cordinates and erase old highlights, draw new if needed */
XTranslateCoordinates(display, e->root, client->window, e->x_root, e->y_root,
&xpos, &ypos, &dumwin);
if (ypos >= client->height - MENU_YBORDER || ypos <= MENU_YBORDER || xpos <= MENU_XBORDER
|| xpos >= client->width - MENU_XBORDER) {
if (oldentidx != -1)
drawhighlight(client, oldentidx);
entidx = oldentidx = -1;
break;
}
#ifdef I18N
entidx = (ypos - MENU_YBORDER) / (menufont_extents->max_ink_extent.height);
#else
entidx = (ypos - MENU_YBORDER) / (menufont->ascent + menufont->descent);
#endif
if (entidx != oldentidx) {
if (oldentidx != -1)
drawhighlight(client, oldentidx);
passopen(menu, client, entidx, &oldentidx, snum);
} else
oldentidx = entidx;
break;
case ButtonRelease:
XUngrabPointer(display, CurrentTime);
if (oldentidx != -1)
drawhighlight(client, oldentidx);
if (entidx != -1) {
if (menu->entries[entidx]->type == ET_SUBMENU)
return;
menu_action(menu, menu->entries[entidx], snum);
/*
* for now, always close when doing an action. at some point
* I may do break-off-able menus and such
*/
menu_close(menu->forefather, menu->forefather->client[snum]);
return;
}
if (moving)
menu_close(menu->forefather, menu->forefather->client[snum]);
return;
case Expose:
event_handle(&event);
break;
}
}
}
/* open or close a menu */
void menu_use(menu_t *menu, screen_t *screen) {
Window dumwin;
client_t *client;
int dumint, x, y;
client = menu->client[screen->num];
if (client->state == WithdrawnState) {
XQueryPointer(display, screen->root, &dumwin, &dumwin, &x, &y, &dumint, &dumint,
&dumint);
menu_open(client, x - client->width / 2, y);
menu_interact(menu, client, NULL);
} else {
/* need an if it's moved thing */
menu_close(menu, client);
}
}
/* a click on a menu, go into interaction with the menu */
void menu_click(menu_t *menu, client_t *client, XButtonEvent *e) {
menu_interact(menu, client, e);
}
syntax highlighted by Code2HTML, v. 0.9.1