Annotation of src/usr.bin/tmux/menu.c, Revision 1.35
1.35 ! nicm 1: /* $OpenBSD: menu.c,v 1.34 2021/07/21 08:06:36 nicm Exp $ */
1.1 nicm 2:
3: /*
4: * Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
5: *
6: * Permission to use, copy, modify, and distribute this software for any
7: * purpose with or without fee is hereby granted, provided that the above
8: * copyright notice and this permission notice appear in all copies.
9: *
10: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14: * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15: * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16: * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17: */
18:
19: #include <sys/types.h>
20:
21: #include <stdlib.h>
22: #include <string.h>
23:
24: #include "tmux.h"
25:
26: struct menu_data {
27: struct cmdq_item *item;
28: int flags;
29:
30: struct cmd_find_state fs;
31: struct screen s;
32:
33: u_int px;
34: u_int py;
35:
36: struct menu *menu;
37: int choice;
38:
39: menu_choice_cb cb;
40: void *data;
41: };
42:
1.8 nicm 43: void
44: menu_add_items(struct menu *menu, const struct menu_item *items,
45: struct cmdq_item *qitem, struct client *c, struct cmd_find_state *fs)
46: {
47: const struct menu_item *loop;
48:
49: for (loop = items; loop->name != NULL; loop++)
50: menu_add_item(menu, loop, qitem, c, fs);
51: }
52:
53: void
54: menu_add_item(struct menu *menu, const struct menu_item *item,
1.6 nicm 55: struct cmdq_item *qitem, struct client *c, struct cmd_find_state *fs)
1.1 nicm 56: {
57: struct menu_item *new_item;
1.8 nicm 58: const char *key, *cmd;
59: char *s, *name;
1.1 nicm 60: u_int width;
1.8 nicm 61: int line;
62:
63: line = (item == NULL || item->name == NULL || *item->name == '\0');
64: if (line && menu->count == 0)
65: return;
1.1 nicm 66:
67: menu->items = xreallocarray(menu->items, menu->count + 1,
68: sizeof *menu->items);
69: new_item = &menu->items[menu->count++];
70: memset(new_item, 0, sizeof *new_item);
71:
1.8 nicm 72: if (line)
1.1 nicm 73: return;
1.8 nicm 74:
75: if (fs != NULL)
1.24 nicm 76: s = format_single_from_state(qitem, item->name, c, fs);
1.8 nicm 77: else
78: s = format_single(qitem, item->name, c, NULL, NULL, NULL);
79: if (*s == '\0') { /* no item if empty after format expanded */
1.1 nicm 80: menu->count--;
81: return;
82: }
1.9 nicm 83: if (*s != '-' && item->key != KEYC_UNKNOWN && item->key != KEYC_NONE) {
1.29 nicm 84: key = key_string_lookup_key(item->key, 0);
1.8 nicm 85: xasprintf(&name, "%s#[default] #[align=right](%s)", s, key);
1.1 nicm 86: } else
1.8 nicm 87: xasprintf(&name, "%s", s);
88: new_item->name = name;
89: free(s);
90:
91: cmd = item->command;
92: if (cmd != NULL) {
93: if (fs != NULL)
1.24 nicm 94: s = format_single_from_state(qitem, cmd, c, fs);
1.8 nicm 95: else
96: s = format_single(qitem, cmd, c, NULL, NULL, NULL);
1.6 nicm 97: } else
1.8 nicm 98: s = NULL;
99: new_item->command = s;
1.1 nicm 100: new_item->key = item->key;
101:
102: width = format_width(new_item->name);
103: if (width > menu->width)
104: menu->width = width;
105: }
106:
107: struct menu *
1.8 nicm 108: menu_create(const char *title)
1.1 nicm 109: {
110: struct menu *menu;
111:
112: menu = xcalloc(1, sizeof *menu);
113: menu->title = xstrdup(title);
1.30 nicm 114: menu->width = format_width(title);
1.1 nicm 115:
116: return (menu);
117: }
118:
119: void
120: menu_free(struct menu *menu)
121: {
122: u_int i;
123:
124: for (i = 0; i < menu->count; i++) {
1.8 nicm 125: free((void *)menu->items[i].name);
126: free((void *)menu->items[i].command);
1.1 nicm 127: }
128: free(menu->items);
129:
1.8 nicm 130: free((void *)menu->title);
1.1 nicm 131: free(menu);
132: }
133:
1.35 ! nicm 134: struct screen *
! 135: menu_mode_cb(__unused struct client *c, void *data, __unused u_int *cx,
! 136: __unused u_int *cy)
1.15 nicm 137: {
1.35 ! nicm 138: struct menu_data *md = data;
1.15 nicm 139:
1.27 nicm 140: return (&md->s);
1.15 nicm 141: }
142:
1.35 ! nicm 143: int
! 144: menu_check_cb(__unused struct client *c, void *data, u_int px, u_int py)
! 145: {
! 146: struct menu_data *md = data;
! 147: struct menu *menu = md->menu;
! 148:
! 149: if (px < md->px || px > md->px + menu->width + 3)
! 150: return (1);
! 151: if (py < md->py || py > md->py + menu->count + 1)
! 152: return (1);
! 153: return (0);
! 154: }
! 155:
! 156: void
! 157: menu_draw_cb(struct client *c, void *data,
! 158: __unused struct screen_redraw_ctx *rctx)
1.1 nicm 159: {
1.35 ! nicm 160: struct menu_data *md = data;
1.1 nicm 161: struct tty *tty = &c->tty;
162: struct screen *s = &md->s;
163: struct menu *menu = md->menu;
164: struct screen_write_ctx ctx;
1.13 nicm 165: u_int i, px = md->px, py = md->py;
1.22 nicm 166: struct grid_cell gc;
167:
1.25 nicm 168: style_apply(&gc, c->session->curw->window->options, "mode-style", NULL);
1.1 nicm 169:
1.27 nicm 170: screen_write_start(&ctx, s);
1.1 nicm 171: screen_write_clearscreen(&ctx, 8);
1.22 nicm 172: screen_write_menu(&ctx, menu, md->choice, &gc);
1.1 nicm 173: screen_write_stop(&ctx);
174:
1.27 nicm 175: for (i = 0; i < screen_size_y(&md->s); i++) {
176: tty_draw_line(tty, s, 0, i, menu->width + 4, px, py + i,
177: &grid_default_cell, NULL);
178: }
1.1 nicm 179: }
180:
1.35 ! nicm 181: void
! 182: menu_free_cb(__unused struct client *c, void *data)
1.1 nicm 183: {
1.35 ! nicm 184: struct menu_data *md = data;
1.1 nicm 185:
186: if (md->item != NULL)
1.10 nicm 187: cmdq_continue(md->item);
1.1 nicm 188:
1.3 nicm 189: if (md->cb != NULL)
190: md->cb(md->menu, UINT_MAX, KEYC_NONE, md->data);
191:
1.1 nicm 192: screen_free(&md->s);
193: menu_free(md->menu);
194: free(md);
195: }
196:
1.35 ! nicm 197: int
! 198: menu_key_cb(struct client *c, void *data, struct key_event *event)
1.1 nicm 199: {
1.35 ! nicm 200: struct menu_data *md = data;
1.1 nicm 201: struct menu *menu = md->menu;
202: struct mouse_event *m = &event->m;
203: u_int i;
204: int count = menu->count, old = md->choice;
1.31 nicm 205: const char *name = NULL;
1.1 nicm 206: const struct menu_item *item;
1.21 nicm 207: struct cmdq_state *state;
208: enum cmd_parse_status status;
209: char *error;
1.1 nicm 210:
211: if (KEYC_IS_MOUSE(event->key)) {
1.12 nicm 212: if (md->flags & MENU_NOMOUSE) {
213: if (MOUSE_BUTTONS(m->b) != 0)
214: return (1);
1.1 nicm 215: return (0);
1.12 nicm 216: }
1.1 nicm 217: if (m->x < md->px ||
218: m->x > md->px + 4 + menu->width ||
219: m->y < md->py + 1 ||
220: m->y > md->py + 1 + count - 1) {
1.32 nicm 221: if (~md->flags & MENU_STAYOPEN) {
222: if (MOUSE_RELEASE(m->b))
223: return (1);
224: } else {
225: if (!MOUSE_RELEASE(m->b) &&
226: MOUSE_WHEEL(m->b) == 0 &&
227: !MOUSE_DRAG(m->b))
228: return (1);
229: }
1.1 nicm 230: if (md->choice != -1) {
231: md->choice = -1;
232: c->flags |= CLIENT_REDRAWOVERLAY;
233: }
234: return (0);
235: }
1.32 nicm 236: if (~md->flags & MENU_STAYOPEN) {
237: if (MOUSE_RELEASE(m->b))
238: goto chosen;
239: } else {
240: if (MOUSE_WHEEL(m->b) == 0 && !MOUSE_DRAG(m->b))
241: goto chosen;
242: }
1.7 nicm 243: md->choice = m->y - (md->py + 1);
1.1 nicm 244: if (md->choice != old)
245: c->flags |= CLIENT_REDRAWOVERLAY;
246: return (0);
247: }
1.11 nicm 248: for (i = 0; i < (u_int)count; i++) {
249: name = menu->items[i].name;
250: if (name == NULL || *name == '-')
251: continue;
252: if (event->key == menu->items[i].key) {
253: md->choice = i;
254: goto chosen;
255: }
256: }
1.29 nicm 257: switch (event->key & ~KEYC_MASK_FLAGS) {
1.1 nicm 258: case KEYC_UP:
1.11 nicm 259: case 'k':
1.9 nicm 260: if (old == -1)
261: old = 0;
1.1 nicm 262: do {
263: if (md->choice == -1 || md->choice == 0)
264: md->choice = count - 1;
265: else
266: md->choice--;
1.9 nicm 267: name = menu->items[md->choice].name;
268: } while ((name == NULL || *name == '-') && md->choice != old);
1.1 nicm 269: c->flags |= CLIENT_REDRAWOVERLAY;
270: return (0);
1.26 nicm 271: case KEYC_BSPACE:
272: if (~md->flags & MENU_TAB)
273: break;
274: return (1);
275: case '\011': /* Tab */
276: if (~md->flags & MENU_TAB)
277: break;
278: if (md->choice == count - 1)
279: return (1);
280: /* FALLTHROUGH */
1.1 nicm 281: case KEYC_DOWN:
1.11 nicm 282: case 'j':
1.9 nicm 283: if (old == -1)
284: old = 0;
1.1 nicm 285: do {
286: if (md->choice == -1 || md->choice == count - 1)
287: md->choice = 0;
1.9 nicm 288: else
289: md->choice++;
290: name = menu->items[md->choice].name;
291: } while ((name == NULL || *name == '-') && md->choice != old);
1.1 nicm 292: c->flags |= CLIENT_REDRAWOVERLAY;
293: return (0);
1.26 nicm 294: case 'g':
295: case KEYC_PPAGE:
296: case '\002': /* C-b */
297: if (md->choice > 5)
298: md->choice -= 5;
299: else
300: md->choice = 0;
301: while (md->choice != count && (name == NULL || *name == '-'))
302: md->choice++;
303: if (md->choice == count)
304: md->choice = -1;
305: c->flags |= CLIENT_REDRAWOVERLAY;
306: break;
307: case 'G':
308: case KEYC_NPAGE:
309: if (md->choice > count - 6)
310: md->choice = count - 1;
311: else
312: md->choice += 5;
313: while (md->choice != -1 && (name == NULL || *name == '-'))
314: md->choice--;
315: c->flags |= CLIENT_REDRAWOVERLAY;
316: break;
317: case '\006': /* C-f */
318: break;
1.1 nicm 319: case '\r':
320: goto chosen;
321: case '\033': /* Escape */
322: case '\003': /* C-c */
323: case '\007': /* C-g */
324: case 'q':
325: return (1);
326: }
327: return (0);
328:
329: chosen:
330: if (md->choice == -1)
331: return (1);
332: item = &menu->items[md->choice];
1.33 nicm 333: if (item->name == NULL || *item->name == '-') {
334: if (md->flags & MENU_STAYOPEN)
335: return (0);
1.1 nicm 336: return (1);
1.33 nicm 337: }
1.1 nicm 338: if (md->cb != NULL) {
339: md->cb(md->menu, md->choice, item->key, md->data);
1.3 nicm 340: md->cb = NULL;
1.1 nicm 341: return (1);
342: }
1.5 nicm 343:
1.21 nicm 344: if (md->item != NULL)
345: event = cmdq_get_event(md->item);
346: else
347: event = NULL;
348: state = cmdq_new_state(&md->fs, event, 0);
349:
350: status = cmd_parse_and_append(item->command, NULL, c, state, &error);
351: if (status == CMD_PARSE_ERROR) {
352: cmdq_append(c, cmdq_get_error(error));
353: free(error);
1.1 nicm 354: }
1.21 nicm 355: cmdq_free_state(state);
356:
1.1 nicm 357: return (1);
358: }
359:
1.35 ! nicm 360: struct menu_data *
! 361: menu_prepare(struct menu *menu, int flags, struct cmdq_item *item, u_int px,
1.1 nicm 362: u_int py, struct client *c, struct cmd_find_state *fs, menu_choice_cb cb,
363: void *data)
364: {
365: struct menu_data *md;
1.23 nicm 366: u_int i;
367: const char *name;
1.1 nicm 368:
369: if (c->tty.sx < menu->width + 4 || c->tty.sy < menu->count + 2)
1.35 ! nicm 370: return (NULL);
1.14 nicm 371: if (px + menu->width + 4 > c->tty.sx)
372: px = c->tty.sx - menu->width - 4;
373: if (py + menu->count + 2 > c->tty.sy)
374: py = c->tty.sy - menu->count - 2;
1.1 nicm 375:
376: md = xcalloc(1, sizeof *md);
377: md->item = item;
378: md->flags = flags;
379:
1.3 nicm 380: if (fs != NULL)
381: cmd_find_copy_state(&md->fs, fs);
1.1 nicm 382: screen_init(&md->s, menu->width + 4, menu->count + 2, 0);
1.27 nicm 383: if (~md->flags & MENU_NOMOUSE)
384: md->s.mode |= MODE_MOUSE_ALL;
1.28 nicm 385: md->s.mode &= ~MODE_CURSOR;
1.1 nicm 386:
387: md->px = px;
388: md->py = py;
389:
390: md->menu = menu;
1.23 nicm 391: if (md->flags & MENU_NOMOUSE) {
392: for (i = 0; i < menu->count; i++) {
393: name = menu->items[i].name;
394: if (name != NULL && *name != '-')
395: break;
396: }
397: if (i != menu->count)
398: md->choice = i;
399: else
400: md->choice = -1;
401: } else
402: md->choice = -1;
1.1 nicm 403:
404: md->cb = cb;
405: md->data = data;
1.35 ! nicm 406: return (md);
! 407: }
! 408:
! 409: int
! 410: menu_display(struct menu *menu, int flags, struct cmdq_item *item, u_int px,
! 411: u_int py, struct client *c, struct cmd_find_state *fs, menu_choice_cb cb,
! 412: void *data)
! 413: {
! 414: struct menu_data *md;
1.1 nicm 415:
1.35 ! nicm 416: md = menu_prepare(menu, flags, item, px, py, c, fs, cb, data);
! 417: if (md == NULL)
! 418: return (-1);
1.15 nicm 419: server_client_set_overlay(c, 0, NULL, menu_mode_cb, menu_draw_cb,
1.34 nicm 420: menu_key_cb, menu_free_cb, NULL, md);
1.1 nicm 421: return (0);
422: }