diff options
Diffstat (limited to 'group.c')
-rw-r--r-- | group.c | 634 |
1 files changed, 634 insertions, 0 deletions
@@ -0,0 +1,634 @@ +/* + * calmwm - the calm window manager + * + * Copyright (c) 2004 Andy Adamson <dros@monkey.org> + * Copyright (c) 2004,2005 Marius Aamodt Eriksen <marius@monkey.org> + * All rights reserved. + * + * $Id: group.c,v 1.1.1.1 2007/04/27 17:58:48 bernd Exp $ + */ + +#include "headers.h" +#include "calmwm.h" + +#define CALMWM_NGROUPS 9 + +int G_groupmode = 0; +int G_groupnamemode = 0; +struct group_ctx *G_group_active = NULL; +struct group_ctx *G_group_current = NULL; +struct group_ctx G_groups[CALMWM_NGROUPS]; +char G_group_name[256]; +int G_groupfocusset = 0; +Window G_groupfocuswin; +int G_groupfocusrevert; +int G_grouphideall = 0; +struct group_ctx_q G_groupq; + +#define GroupMask (KeyPressMask|ExposureMask) + +static char *shortcut_to_name[] = { + "XXX", "one", "two", "three", + "four", "five", "six", "seven", + "eight", "nine", +}; + +static void +_group_add(struct group_ctx *gc, struct client_ctx *cc) +{ + if (cc == NULL || gc == NULL) + errx(1, "_group_add: a ctx is NULL"); + + if (cc->group == gc) + return; + + if (cc->group != NULL) + TAILQ_REMOVE(&cc->group->clients, cc, group_entry); + + TAILQ_INSERT_TAIL(&gc->clients, cc, group_entry); + cc->group = gc; + cc->groupcommit = 0; +} + +static void +_group_remove(struct client_ctx *cc) +{ + if (cc == NULL || cc->group == NULL) + errx(1, "_group_remove: a ctx is NULL"); + + TAILQ_REMOVE(&cc->group->clients, cc, group_entry); + cc->group = NULL; + cc->groupcommit = 0; + cc->highlight = 0; + client_draw_border(cc); +} + +static void +_group_commit(struct group_ctx *gc) +{ + struct client_ctx *cc; + + if (gc == NULL) + errx(1, "_group_commit: ctx is null"); + + TAILQ_FOREACH(cc, &gc->clients, group_entry) + cc->groupcommit = 1; +} + +static void +_group_purge(struct group_ctx *gc) +{ + struct client_ctx *cc; + + if (gc == NULL) + errx(1, "_group_commit: ctx is null"); + + TAILQ_FOREACH(cc, &gc->clients, group_entry) + if (cc->groupcommit == 0) + _group_remove(cc); +} + + +static void +_group_hide(struct group_ctx *gc) +{ + struct client_ctx *cc; + + screen_updatestackingorder(); + + gc->nhidden = 0; + gc->highstack = 0; + TAILQ_FOREACH(cc, &gc->clients, group_entry) { + client_hide(cc); + gc->nhidden++; + if (cc->stackingorder > gc->highstack) + gc->highstack = cc->stackingorder; + } + gc->hidden = 1; /* XXX: equivalent to gc->nhidden > 0 */ +} + +static void +_group_show(struct group_ctx *gc) +{ + struct client_ctx *cc; + Window *winlist; + u_int i; + int lastempty = -1; + + winlist = (Window *) xcalloc(sizeof(*winlist) * (gc->highstack + 1)); + + /* + * Invert the stacking order as XRestackWindows() expects them + * top-to-bottom. + */ + TAILQ_FOREACH(cc, &gc->clients, group_entry) { + winlist[gc->highstack - cc->stackingorder] = cc->pwin; + client_unhide(cc); + } + + /* Un-sparseify */ + for (i = 0; i <= gc->highstack; i++) { + if (!winlist[i] && lastempty == -1) + lastempty = i; + else if (winlist[i] && lastempty != -1) { + winlist[lastempty] = winlist[i]; + if (++lastempty == i) + lastempty = -1; + } + } + + XRestackWindows(G_dpy, winlist, gc->nhidden); + xfree(winlist); + + gc->hidden = 0; + G_group_active = gc; +} + + + +static void +_group_destroy(struct group_ctx *gc) +{ + struct client_ctx *cc; + + if (gc->name != NULL) { + xfree(gc->name); + gc->name = NULL; + } + + while ((cc = TAILQ_FIRST(&gc->clients)) != NULL) { + TAILQ_REMOVE(&gc->clients, cc, group_entry); + cc->group = NULL; + cc->groupcommit = 0; + cc->highlight = 0; + client_draw_border(cc); + } +} + +void +group_init(void) +{ + int i; + + TAILQ_INIT(&G_groupq); + + for (i = 0; i < CALMWM_NGROUPS; i++) { + TAILQ_INIT(&G_groups[i].clients); + G_groups[i].hidden = 0; + G_groups[i].shortcut = i + 1; + TAILQ_INSERT_TAIL(&G_groupq, &G_groups[i], entry); + } + + G_group_current = G_group_active = &G_groups[0]; +} + +/* + * manipulate the 'current group' + */ + +#if 0 +/* set current group to the first empty group + * returns 0 on success, -1 if there are no empty groups + */ +int +group_new(void) +{ + int i; + + for (i=0; i < CALMWM_NGROUPS; i++) { + if (TAILQ_EMPTY(&G_groups[i].clients)) { + G_group_current = &G_groups[i]; + + return (0); + } + } + + return (-1); +} +#endif + +/* change the current group */ +int +group_select(int idx) +{ + struct group_ctx *gc = G_group_current; + struct client_ctx *cc; + + if (idx < 0 || idx >= CALMWM_NGROUPS) + return (-1); + + TAILQ_FOREACH(cc, &gc->clients, group_entry) { + cc->highlight = 0; + client_draw_border(cc); + } + + _group_commit(gc); + G_group_current = &G_groups[idx]; + + group_display_draw(screen_current()); + return (0); +} + +/* enter group mode */ +void +group_enter(void) +{ + if (G_groupmode != 0) + errx(1, "group_enter called twice"); + + if (G_group_current == NULL) + G_group_current = &G_groups[0]; + + /* setup input buffer */ + G_group_name[0] = '\0'; + + G_groupmode = 1; + + group_display_init(screen_current()); + group_display_draw(screen_current()); +} + +/* exit group mode */ +void +group_exit(int commit) +{ + struct group_ctx *gc = G_group_current; + struct client_ctx *cc; + + if (G_groupmode != 1) + errx(1, "group_exit called twice"); + + TAILQ_FOREACH(cc, &gc->clients, group_entry) { + cc->highlight = 0; + client_draw_border(cc); + } + + if (commit) { + _group_commit(gc); + } else { + /* abort */ + _group_purge(gc); + if (!TAILQ_EMPTY(&gc->clients)) + _group_destroy(gc); + } + + XUnmapWindow(G_dpy, screen_current()->groupwin); + + if (G_groupnamemode) { + XSetInputFocus(G_dpy, G_groupfocuswin, G_groupfocusrevert, + CurrentTime); + G_groupfocusset = 0; + } + + G_groupmode = G_groupnamemode = 0; +} + +void +group_click(struct client_ctx *cc) +{ + struct group_ctx *gc = G_group_current; + + if (gc == cc->group) + _group_remove(cc); + else + _group_add(gc, cc); + group_display_draw(screen_current()); +} + + +/* Used to add a newly mapped window to the active group */ + +void +group_sticky(struct client_ctx *cc) +{ + _group_add(G_group_active, cc); +} + +void +group_sticky_toggle_enter(struct client_ctx *cc) +{ + struct group_ctx *gc = G_group_active; + + if (gc == cc->group) { + _group_remove(cc); + cc->highlight = CLIENT_HIGHLIGHT_RED; + } else { + _group_add(gc, cc); + cc->highlight = CLIENT_HIGHLIGHT_BLUE; + } + + client_draw_border(cc); +} + +void +group_sticky_toggle_exit(struct client_ctx *cc) +{ + cc->highlight = 0; + client_draw_border(cc); +} + +/* + * selection list display + */ + +void +group_display_init(struct screen_ctx *sc) +{ + sc->groupwin = XCreateSimpleWindow(G_dpy, sc->rootwin, 0, 0, + 1, 1, 1, sc->blackpixl, sc->whitepixl); +} + +void +group_display_draw(struct screen_ctx *sc) +{ + struct group_ctx *gc = G_group_current; + int x, y, dx, dy, fontheight, titlelen; + struct client_ctx *cc; + char titlebuf[1024]; + struct fontdesc *font = DefaultFont; + + snprintf(titlebuf, sizeof(titlebuf), "Editing group %d", gc->shortcut); + + x = y = 0; + + fontheight = font_ascent(font) + font_descent(font) + 1; + dx = titlelen = font_width(font, titlebuf, strlen(titlebuf)); + dy = fontheight; + + TAILQ_FOREACH(cc, &gc->clients, group_entry) { + cc->highlight = CLIENT_HIGHLIGHT_BLUE; + client_draw_border(cc); + } + + XMoveResizeWindow(G_dpy, sc->groupwin, x, y, dx, dy); + + /* XXX */ + XSelectInput(G_dpy, sc->groupwin, GroupMask); + + XMapRaised(G_dpy, sc->groupwin); + XClearWindow(G_dpy, sc->groupwin); + font_draw(font, titlebuf, strlen(titlebuf), sc->groupwin, + 0, font_ascent(font) + 1); +} + +void +group_display_keypress(KeyCode k) +{ + struct group_ctx * gc = G_group_current; + char chr; + enum ctltype ctl; + int len; + + if (!G_groupnamemode) + return; + + if (input_keycodetrans(k, 0, &ctl, &chr, 1) < 0) + goto out; + + switch (ctl) { + case CTL_ERASEONE: + if ((len = strlen(G_group_name)) > 0) + G_group_name[len - 1] = '\0'; + break; + case CTL_RETURN: + if (gc->name != NULL) + xfree(gc->name); + + gc->name = xstrdup(G_group_name); + + group_exit(1); + return; + default: + break; + } + + if (chr != '\0') + snprintf(G_group_name, sizeof(G_group_name), "%s%c", + G_group_name, chr); + +out: + group_display_draw(screen_current()); +} + +/* if group_hidetoggle would produce no effect, toggle the group's hidden state + */ +void +_group_fix_hidden_state(struct group_ctx *gc) +{ + struct client_ctx *cc; + int same = 0; + + TAILQ_FOREACH(cc, &gc->clients, group_entry) { + if (gc->hidden == ((cc->flags & CLIENT_HIDDEN) ? 1 : 0)) + same++; + } + + if (same == 0) + gc->hidden = !gc->hidden; +} + +void +group_hidetoggle(int idx) +{ + struct group_ctx *gc; +#ifdef notyet + char buf[128]; +#endif + + if (idx < 0 || idx >= CALMWM_NGROUPS) + err(1, "group_hidetoggle: index out of range (%d)", idx); + + gc = &G_groups[idx]; + + _group_fix_hidden_state(gc); + + if (gc->hidden) + _group_show(gc); + else { + _group_hide(gc); + if (TAILQ_EMPTY(&gc->clients)) + G_group_active = gc; + } + +#ifdef notyet + snprintf(buf, sizeof(buf), "Group %d", idx + 1); + screen_infomsg(buf); +#endif +} + +#define GROUP_NEXT(gc, fwd) (fwd) ? \ + TAILQ_NEXT(gc, entry) : TAILQ_PREV(gc, group_ctx_q, entry) + +/* + * Jump to the next/previous active group. If none exist, then just + * stay put. + */ +void +group_slide(int fwd) +{ + struct group_ctx *gc, *showgroup = NULL; + + assert(G_group_active != NULL); + + gc = G_group_active; + for (;;) { + gc = GROUP_NEXT(gc, fwd); + if (gc == NULL) + gc = fwd ? TAILQ_FIRST(&G_groupq) : + TAILQ_LAST(&G_groupq, group_ctx_q); + if (gc == G_group_active) + break; + + if (!TAILQ_EMPTY(&gc->clients) && showgroup == NULL) + showgroup = gc; + else if (!gc->hidden) + _group_hide(gc); + } + + if (showgroup == NULL) + return; + + _group_hide(G_group_active); + + if (showgroup->hidden) + _group_show(showgroup); + else + G_group_active = showgroup; +} + +/* called when a client is deleted */ +void +group_client_delete(struct client_ctx *cc) +{ + if (cc->group == NULL) + return; + + TAILQ_REMOVE(&cc->group->clients, cc, group_entry); + cc->group = NULL; /* he he */ + cc->groupcommit = 0; +} + +void +group_menu(XButtonEvent *e) +{ + struct menu_q menuq; + struct menu *mi; + int i; + struct group_ctx *gc; + + TAILQ_INIT(&menuq); + + for (i = 0; i < CALMWM_NGROUPS; i++) { + gc = &G_groups[i]; + + if (TAILQ_EMPTY(&gc->clients)) + continue; + + if (gc->name == NULL) + gc->name = xstrdup(shortcut_to_name[gc->shortcut]); + + XCALLOC(mi, struct menu); + if (gc->hidden) + snprintf(mi->text, sizeof(mi->text), "%d: [%s]", + gc->shortcut, gc->name); + else + snprintf(mi->text, sizeof(mi->text), "%d: %s", + gc->shortcut, gc->name); + mi->ctx = gc; + TAILQ_INSERT_TAIL(&menuq, mi, entry); + } + + if (TAILQ_EMPTY(&menuq)) + return; + + mi = (struct menu *)grab_menu(e, &menuq); + + if (mi == NULL || mi->ctx == NULL) + goto cleanup; + + gc = (struct group_ctx *)mi->ctx; + + if (gc->hidden) + _group_show(gc); + else + _group_hide(gc); + + cleanup: + while ((mi = TAILQ_FIRST(&menuq)) != NULL) { + TAILQ_REMOVE(&menuq, mi, entry); + xfree(mi); + } +} + +void +group_namemode(void) +{ + G_groupnamemode = 1; + + group_display_draw(screen_current()); +} + +void +group_alltoggle(void) +{ + int i; + + for (i=0; i < CALMWM_NGROUPS; i++) { + if (G_grouphideall) + _group_show(&G_groups[i]); + else + _group_hide(&G_groups[i]); + } + + if (G_grouphideall) + G_grouphideall = 0; + else + G_grouphideall = 1; +} + +void +group_deletecurrent(void) +{ + _group_destroy(G_group_current); + XUnmapWindow(G_dpy, screen_current()->groupwin); + + G_groupmode = G_groupnamemode = 0; +} + +void +group_done(void) +{ + struct group_ctx *gc = G_group_current; + + if (gc->name != NULL) + xfree(gc->name); + + gc->name = xstrdup(shortcut_to_name[gc->shortcut]); + + group_exit(1); +} + +void +group_autogroup(struct client_ctx *cc) +{ + struct autogroupwin *aw; + struct group_ctx *gc; + char group[CALMWM_MAXNAMELEN]; + + if (cc->app_class == NULL || cc->app_name == NULL) + return; + + TAILQ_FOREACH(aw, &G_conf.autogroupq, entry) { + if (strcmp(aw->class, cc->app_class) == 0 && + (aw->name == NULL || strcmp(aw->name, cc->app_name) == 0)) { + strlcpy(group, aw->group, sizeof(group)); + break; + } + } + + TAILQ_FOREACH(gc, &G_groupq, entry) { + if (strcmp(shortcut_to_name[gc->shortcut], group) == 0) + _group_add(gc, cc); + } + +} |