Line | Count | Source |
1 | | /* $OpenBSD: menu.c,v 1.73 2026/10/15 22:16:32 nicm Exp $ */ |
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 window *w; |
28 | | int flags; |
29 | | |
30 | | char *style; |
31 | | char *border_style; |
32 | | char *selected_style; |
33 | | |
34 | | struct grid_cell style_gc; |
35 | | struct grid_cell border_style_gc; |
36 | | struct grid_cell selected_style_gc; |
37 | | enum box_lines border_lines; |
38 | | |
39 | | struct cmd_find_state fs; |
40 | | key_code key; |
41 | | struct mouse_event m; |
42 | | struct screen s; |
43 | | |
44 | | u_int px; |
45 | | u_int py; |
46 | | |
47 | | struct menu *menu; |
48 | | int choice; |
49 | | |
50 | | menu_choice_cb cb; |
51 | | void *data; |
52 | | }; |
53 | | |
54 | | void |
55 | | menu_get_size(struct menu *menu, enum box_lines lines, u_int *sx, u_int *sy) |
56 | 0 | { |
57 | 0 | if (lines == BOX_LINES_NONE) { |
58 | 0 | *sx = menu->item_width + 2; |
59 | 0 | *sy = menu->count; |
60 | 0 | } else { |
61 | 0 | *sx = menu->width + 4; |
62 | 0 | *sy = menu->count + 2; |
63 | 0 | } |
64 | 0 | } |
65 | | |
66 | | void |
67 | | menu_add_items(struct menu *menu, const struct menu_item *items, |
68 | | struct cmdq_item *qitem, struct client *c, struct cmd_find_state *fs) |
69 | 0 | { |
70 | 0 | const struct menu_item *loop; |
71 | |
|
72 | 0 | for (loop = items; loop->name != NULL; loop++) |
73 | 0 | menu_add_item(menu, loop, qitem, c, fs); |
74 | 0 | } |
75 | | |
76 | | void |
77 | | menu_add_item(struct menu *menu, const struct menu_item *item, |
78 | | struct cmdq_item *qitem, struct client *c, struct cmd_find_state *fs) |
79 | 0 | { |
80 | 0 | struct menu_item *new_item; |
81 | 0 | const char *key = NULL, *cmd, *suffix = ""; |
82 | 0 | char *s, *trimmed, *name; |
83 | 0 | u_int width, max_width; |
84 | 0 | int line; |
85 | 0 | size_t keylen, slen; |
86 | |
|
87 | 0 | line = (item == NULL || item->name == NULL || *item->name == '\0'); |
88 | 0 | if (line && menu->count == 0) |
89 | 0 | return; |
90 | 0 | if (line && menu->items[menu->count - 1].name == NULL) |
91 | 0 | return; |
92 | | |
93 | 0 | menu->items = xreallocarray(menu->items, menu->count + 1, |
94 | 0 | sizeof *menu->items); |
95 | 0 | new_item = &menu->items[menu->count++]; |
96 | 0 | memset(new_item, 0, sizeof *new_item); |
97 | |
|
98 | 0 | if (line) |
99 | 0 | return; |
100 | | |
101 | 0 | if (fs != NULL) |
102 | 0 | s = format_single_from_state(qitem, item->name, c, fs); |
103 | 0 | else |
104 | 0 | s = format_single(qitem, item->name, c, NULL, NULL, NULL); |
105 | 0 | if (*s == '\0') { /* no item if empty after format expanded */ |
106 | 0 | free(s); |
107 | 0 | menu->count--; |
108 | 0 | return; |
109 | 0 | } |
110 | 0 | max_width = c->tty.sx - 4; |
111 | |
|
112 | 0 | slen = strlen(s); |
113 | 0 | if (*s != '-' && item->key != KEYC_UNKNOWN && item->key != KEYC_NONE) { |
114 | 0 | key = key_string_lookup_key(item->key, 0); |
115 | 0 | keylen = strlen(key) + 3; /* 3 = space and two brackets */ |
116 | | |
117 | | /* |
118 | | * Add the key if it is shorter than a quarter of the available |
119 | | * space or there is space for the entire item text and the |
120 | | * key. |
121 | | */ |
122 | 0 | if (keylen <= max_width / 4) |
123 | 0 | max_width -= keylen; |
124 | 0 | else if (keylen >= max_width || slen >= max_width - keylen) |
125 | 0 | key = NULL; |
126 | 0 | } |
127 | |
|
128 | 0 | if (slen > max_width) { |
129 | 0 | max_width--; |
130 | 0 | suffix = ">"; |
131 | 0 | } |
132 | 0 | trimmed = format_trim_right(s, max_width); |
133 | 0 | if (key != NULL) { |
134 | 0 | xasprintf(&name, "%s%s#[default] #[align=right](%s)", |
135 | 0 | trimmed, suffix, key); |
136 | 0 | } else |
137 | 0 | xasprintf(&name, "%s%s", trimmed, suffix); |
138 | 0 | free(trimmed); |
139 | |
|
140 | 0 | new_item->name = name; |
141 | 0 | free(s); |
142 | |
|
143 | 0 | cmd = item->command; |
144 | 0 | if (cmd != NULL) { |
145 | 0 | if (fs != NULL) |
146 | 0 | s = format_single_from_state(qitem, cmd, c, fs); |
147 | 0 | else |
148 | 0 | s = format_single(qitem, cmd, c, NULL, NULL, NULL); |
149 | 0 | } else |
150 | 0 | s = NULL; |
151 | 0 | new_item->command = s; |
152 | 0 | new_item->key = item->key; |
153 | |
|
154 | 0 | width = format_width(new_item->name); |
155 | 0 | if (*new_item->name == '-') |
156 | 0 | width--; |
157 | 0 | if (width > menu->item_width) |
158 | 0 | menu->item_width = width; |
159 | 0 | if (width > menu->width) |
160 | 0 | menu->width = width; |
161 | 0 | } |
162 | | |
163 | | struct menu * |
164 | | menu_create(const char *title) |
165 | 0 | { |
166 | 0 | struct menu *menu; |
167 | |
|
168 | 0 | menu = xcalloc(1, sizeof *menu); |
169 | 0 | menu->title = xstrdup(title); |
170 | 0 | menu->width = format_width(title); |
171 | |
|
172 | 0 | return (menu); |
173 | 0 | } |
174 | | |
175 | | void |
176 | | menu_free(struct menu *menu) |
177 | 0 | { |
178 | 0 | u_int i; |
179 | |
|
180 | 0 | if (menu == NULL) |
181 | 0 | return; |
182 | | |
183 | 0 | for (i = 0; i < menu->count; i++) { |
184 | 0 | free((void *)menu->items[i].name); |
185 | 0 | free((void *)menu->items[i].command); |
186 | 0 | } |
187 | 0 | free(menu->items); |
188 | |
|
189 | 0 | free((void *)menu->title); |
190 | 0 | free(menu); |
191 | 0 | } |
192 | | |
193 | | static void |
194 | | menu_reapply_styles(struct menu_data *md) |
195 | 0 | { |
196 | 0 | struct options *o = md->w->options; |
197 | 0 | struct session *s = NULL; |
198 | 0 | struct winlink *wl = NULL; |
199 | 0 | struct window_pane *wp = NULL; |
200 | 0 | struct format_tree *ft; |
201 | 0 | struct style sytmp; |
202 | | |
203 | | /* The pane (or session) the menu was opened for may be gone. */ |
204 | 0 | if (cmd_find_valid_state(&md->fs)) { |
205 | 0 | s = md->fs.s; |
206 | 0 | wl = md->fs.wl; |
207 | 0 | wp = md->fs.wp; |
208 | 0 | o = md->fs.w->options; |
209 | 0 | } |
210 | 0 | ft = format_create_defaults(NULL, NULL, s, wl, wp); |
211 | | |
212 | | /* Reapply menu style from options. */ |
213 | 0 | memcpy(&md->style_gc, &grid_default_cell, sizeof md->style_gc); |
214 | 0 | style_apply(&md->style_gc, o, "menu-style", ft); |
215 | 0 | if (md->style != NULL) { |
216 | 0 | style_set(&sytmp, &grid_default_cell); |
217 | 0 | if (style_parse(&sytmp, &md->style_gc, md->style) == 0) { |
218 | 0 | md->style_gc.fg = sytmp.gc.fg; |
219 | 0 | md->style_gc.bg = sytmp.gc.bg; |
220 | 0 | } |
221 | 0 | } |
222 | | |
223 | | /* Reapply selected style from options. */ |
224 | 0 | memcpy(&md->selected_style_gc, &grid_default_cell, |
225 | 0 | sizeof md->selected_style_gc); |
226 | 0 | style_apply(&md->selected_style_gc, o, "menu-selected-style", ft); |
227 | 0 | if (md->selected_style != NULL) { |
228 | 0 | style_set(&sytmp, &grid_default_cell); |
229 | 0 | if (style_parse(&sytmp, &md->selected_style_gc, |
230 | 0 | md->selected_style) == 0) { |
231 | 0 | md->selected_style_gc.fg = sytmp.gc.fg; |
232 | 0 | md->selected_style_gc.bg = sytmp.gc.bg; |
233 | 0 | } |
234 | 0 | } |
235 | | |
236 | | /* Reapply border style from options. */ |
237 | 0 | memcpy(&md->border_style_gc, &grid_default_cell, |
238 | 0 | sizeof md->border_style_gc); |
239 | 0 | style_apply(&md->border_style_gc, o, "menu-border-style", ft); |
240 | 0 | if (md->border_style != NULL) { |
241 | 0 | style_set(&sytmp, &grid_default_cell); |
242 | 0 | if (style_parse(&sytmp, &md->border_style_gc, |
243 | 0 | md->border_style) == 0) { |
244 | 0 | md->border_style_gc.fg = sytmp.gc.fg; |
245 | 0 | md->border_style_gc.bg = sytmp.gc.bg; |
246 | 0 | } |
247 | 0 | } |
248 | |
|
249 | 0 | format_free(ft); |
250 | 0 | } |
251 | | |
252 | | void |
253 | | menu_update(struct menu_data *md) |
254 | 0 | { |
255 | 0 | struct screen *s = &md->s; |
256 | 0 | struct menu *menu = md->menu; |
257 | 0 | struct screen_write_ctx ctx; |
258 | |
|
259 | 0 | menu_reapply_styles(md); |
260 | |
|
261 | 0 | screen_write_start(&ctx, s); |
262 | 0 | screen_write_clearscreen(&ctx, 8); |
263 | |
|
264 | 0 | if (md->border_lines != BOX_LINES_NONE) { |
265 | 0 | screen_write_box(&ctx, menu_width(md), menu_height(md), |
266 | 0 | md->border_lines, &md->border_style_gc, menu->title); |
267 | 0 | } |
268 | |
|
269 | 0 | screen_write_menu(&ctx, menu, md->choice, md->border_lines, |
270 | 0 | &md->style_gc, &md->border_style_gc, &md->selected_style_gc); |
271 | 0 | screen_write_stop(&ctx); |
272 | 0 | } |
273 | | |
274 | | static void |
275 | | menu_free_data(struct menu_data *md) |
276 | 16.2k | { |
277 | 16.2k | if (md != NULL) { |
278 | 0 | if (md->cb != NULL) |
279 | 0 | md->cb(md->menu, UINT_MAX, KEYC_NONE, md->data); |
280 | |
|
281 | 0 | screen_free(&md->s); |
282 | 0 | menu_free(md->menu); |
283 | 0 | free(md->style); |
284 | 0 | free(md->selected_style); |
285 | 0 | free(md->border_style); |
286 | 0 | free(md); |
287 | 0 | } |
288 | 16.2k | } |
289 | | |
290 | | void |
291 | | menu_close(struct window *w) |
292 | 0 | { |
293 | 0 | if (w->menu != NULL) { |
294 | 0 | menu_free_data(w->menu); |
295 | 0 | w->menu = NULL; |
296 | |
|
297 | 0 | redraw_invalidate_scene(w); |
298 | 0 | window_update_focus(w); |
299 | 0 | server_redraw_window(w); |
300 | 0 | } |
301 | 0 | } |
302 | | |
303 | | void |
304 | | menu_destroy(struct window *w) |
305 | 16.2k | { |
306 | 16.2k | menu_free_data(w->menu); |
307 | 16.2k | w->menu = NULL; |
308 | 16.2k | } |
309 | | |
310 | | void |
311 | | menu_get_cursor(struct menu_data *md, u_int *cx, u_int *cy) |
312 | 0 | { |
313 | 0 | u_int border = (md->border_lines != BOX_LINES_NONE); |
314 | |
|
315 | 0 | *cx = md->px + 1 + border; |
316 | 0 | if (md->choice == -1) |
317 | 0 | *cy = md->py; |
318 | 0 | else |
319 | 0 | *cy = md->py + border + md->choice; |
320 | 0 | } |
321 | | |
322 | | struct screen * |
323 | | menu_screen(struct menu_data *md) |
324 | 0 | { |
325 | 0 | return (&md->s); |
326 | 0 | } |
327 | | |
328 | | u_int |
329 | | menu_width(struct menu_data *md) |
330 | 0 | { |
331 | 0 | u_int sx, sy; |
332 | |
|
333 | 0 | menu_get_size(md->menu, md->border_lines, &sx, &sy); |
334 | 0 | return (sx); |
335 | 0 | } |
336 | | |
337 | | u_int |
338 | | menu_height(struct menu_data *md) |
339 | 0 | { |
340 | 0 | u_int sx, sy; |
341 | |
|
342 | 0 | menu_get_size(md->menu, md->border_lines, &sx, &sy); |
343 | 0 | return (sy); |
344 | 0 | } |
345 | | |
346 | | u_int |
347 | | menu_x(struct menu_data *md) |
348 | 0 | { |
349 | 0 | return (md->px); |
350 | 0 | } |
351 | | |
352 | | u_int |
353 | | menu_y(struct menu_data *md) |
354 | 0 | { |
355 | 0 | return (md->py); |
356 | 0 | } |
357 | | |
358 | | int |
359 | | menu_key(struct client *c, struct menu_data *md, struct key_event *event) |
360 | 0 | { |
361 | 0 | struct menu *menu = md->menu; |
362 | 0 | struct mouse_event *m = &event->m; |
363 | 0 | u_int i; |
364 | 0 | int n = menu->count, old = md->choice; |
365 | 0 | int move; |
366 | 0 | const char *name = NULL; |
367 | 0 | const struct menu_item *item; |
368 | 0 | struct key_event saved_event; |
369 | 0 | struct cmdq_state *state; |
370 | 0 | enum cmd_parse_status status; |
371 | 0 | char *error; |
372 | 0 | key_code key; |
373 | 0 | u_int border; |
374 | |
|
375 | 0 | if (KEYC_IS_MOUSE(event->key)) { |
376 | | /* |
377 | | * A mouse move with no button held reports as a release, so |
378 | | * treat it as highlight-only: it must never select or close the |
379 | | * menu, otherwise a menu opened without a button already down |
380 | | * (such as a submenu opened from another menu) would vanish as |
381 | | * soon as the mouse moved over it. |
382 | | */ |
383 | 0 | move = MOUSE_DRAG(m->b) && MOUSE_RELEASE(m->b); |
384 | 0 | if (md->flags & MENU_NOMOUSE) { |
385 | 0 | if (MOUSE_BUTTONS(m->b) != MOUSE_BUTTON_1) |
386 | 0 | return (1); |
387 | 0 | return (0); |
388 | 0 | } |
389 | 0 | border = (md->border_lines != BOX_LINES_NONE); |
390 | 0 | if (m->x < md->px || m->x >= md->px + menu_width(md) || |
391 | 0 | m->y < md->py + border || |
392 | 0 | m->y >= md->py + border + n) { |
393 | 0 | if (~md->flags & MENU_STAYOPEN) { |
394 | 0 | if (!move && MOUSE_RELEASE(m->b)) |
395 | 0 | return (1); |
396 | 0 | } else { |
397 | 0 | if (!MOUSE_RELEASE(m->b) && |
398 | 0 | !MOUSE_WHEEL(m->b) && |
399 | 0 | !MOUSE_DRAG(m->b)) |
400 | 0 | return (1); |
401 | 0 | } |
402 | 0 | if (md->choice != -1) { |
403 | 0 | md->choice = -1; |
404 | 0 | server_redraw_window_menu(md->w); |
405 | 0 | } |
406 | 0 | return (0); |
407 | 0 | } |
408 | 0 | if (~md->flags & MENU_STAYOPEN) { |
409 | 0 | if (!move && MOUSE_RELEASE(m->b)) |
410 | 0 | goto chosen; |
411 | 0 | } else { |
412 | 0 | if (!MOUSE_WHEEL(m->b) && !MOUSE_DRAG(m->b)) |
413 | 0 | goto chosen; |
414 | 0 | } |
415 | 0 | md->choice = m->y - (md->py + border); |
416 | 0 | if (md->choice != old) |
417 | 0 | server_redraw_window_menu(md->w); |
418 | 0 | return (0); |
419 | 0 | } |
420 | 0 | for (i = 0; i < (u_int)n; i++) { |
421 | 0 | name = menu->items[i].name; |
422 | 0 | if (name == NULL || *name == '-') |
423 | 0 | continue; |
424 | 0 | key = (event->key & ~KEYC_MASK_FLAGS); |
425 | 0 | if (key == (menu->items[i].key & ~KEYC_MASK_FLAGS)) { |
426 | 0 | md->choice = i; |
427 | 0 | goto chosen; |
428 | 0 | } |
429 | 0 | } |
430 | 0 | switch (event->key & ~KEYC_MASK_FLAGS) { |
431 | 0 | case KEYC_BTAB: |
432 | 0 | case KEYC_UP: |
433 | 0 | case 'k': |
434 | 0 | if (old == -1) |
435 | 0 | old = 0; |
436 | 0 | do { |
437 | 0 | if (md->choice == -1 || md->choice == 0) |
438 | 0 | md->choice = n - 1; |
439 | 0 | else |
440 | 0 | md->choice--; |
441 | 0 | name = menu->items[md->choice].name; |
442 | 0 | } while ((name == NULL || *name == '-') && md->choice != old); |
443 | 0 | server_redraw_window_menu(md->w); |
444 | 0 | return (0); |
445 | 0 | case KEYC_BSPACE: |
446 | 0 | if (~md->flags & MENU_TAB) |
447 | 0 | break; |
448 | 0 | return (1); |
449 | 0 | case '\011': /* Tab */ |
450 | 0 | if (~md->flags & MENU_TAB) |
451 | 0 | break; |
452 | 0 | if (md->choice == n - 1) |
453 | 0 | return (1); |
454 | | /* FALLTHROUGH */ |
455 | 0 | case KEYC_DOWN: |
456 | 0 | case 'j': |
457 | 0 | if (old == -1) |
458 | 0 | old = 0; |
459 | 0 | do { |
460 | 0 | if (md->choice == -1 || md->choice == n - 1) |
461 | 0 | md->choice = 0; |
462 | 0 | else |
463 | 0 | md->choice++; |
464 | 0 | name = menu->items[md->choice].name; |
465 | 0 | } while ((name == NULL || *name == '-') && md->choice != old); |
466 | 0 | server_redraw_window_menu(md->w); |
467 | 0 | return (0); |
468 | 0 | case KEYC_PPAGE: |
469 | 0 | case 'b'|KEYC_CTRL: |
470 | 0 | if (md->choice < 6) |
471 | 0 | md->choice = 0; |
472 | 0 | else { |
473 | 0 | i = 5; |
474 | 0 | while (i > 0) { |
475 | 0 | md->choice--; |
476 | 0 | name = menu->items[md->choice].name; |
477 | 0 | if (md->choice != 0 && |
478 | 0 | (name != NULL && *name != '-')) |
479 | 0 | i--; |
480 | 0 | else if (md->choice == 0) |
481 | 0 | break; |
482 | 0 | } |
483 | 0 | } |
484 | 0 | server_redraw_window_menu(md->w); |
485 | 0 | break; |
486 | 0 | case KEYC_NPAGE: |
487 | 0 | if (md->choice > n - 6) { |
488 | 0 | md->choice = n - 1; |
489 | 0 | name = menu->items[md->choice].name; |
490 | 0 | } else { |
491 | 0 | i = 5; |
492 | 0 | while (i > 0) { |
493 | 0 | md->choice++; |
494 | 0 | name = menu->items[md->choice].name; |
495 | 0 | if (md->choice != n - 1 && |
496 | 0 | (name != NULL && *name != '-')) |
497 | 0 | i--; |
498 | 0 | else if (md->choice == n - 1) |
499 | 0 | break; |
500 | 0 | } |
501 | 0 | } |
502 | 0 | while ((name == NULL || *name == '-') && md->choice != 0) { |
503 | 0 | md->choice--; |
504 | 0 | name = menu->items[md->choice].name; |
505 | 0 | } |
506 | 0 | server_redraw_window_menu(md->w); |
507 | 0 | break; |
508 | 0 | case 'g': |
509 | 0 | case KEYC_HOME: |
510 | 0 | md->choice = 0; |
511 | 0 | name = menu->items[md->choice].name; |
512 | 0 | while ((name == NULL || *name == '-') && md->choice != n - 1) { |
513 | 0 | md->choice++; |
514 | 0 | name = menu->items[md->choice].name; |
515 | 0 | } |
516 | 0 | server_redraw_window_menu(md->w); |
517 | 0 | break; |
518 | 0 | case 'G': |
519 | 0 | case KEYC_END: |
520 | 0 | md->choice = n - 1; |
521 | 0 | name = menu->items[md->choice].name; |
522 | 0 | while ((name == NULL || *name == '-') && md->choice != 0) { |
523 | 0 | md->choice--; |
524 | 0 | name = menu->items[md->choice].name; |
525 | 0 | } |
526 | 0 | server_redraw_window_menu(md->w); |
527 | 0 | break; |
528 | 0 | case 'f'|KEYC_CTRL: |
529 | 0 | break; |
530 | 0 | case '\r': |
531 | 0 | goto chosen; |
532 | 0 | case '\033': /* Escape */ |
533 | 0 | case '['|KEYC_CTRL: |
534 | 0 | case 'c'|KEYC_CTRL: |
535 | 0 | case 'g'|KEYC_CTRL: |
536 | 0 | case 'q': |
537 | 0 | return (1); |
538 | 0 | } |
539 | 0 | return (0); |
540 | | |
541 | 0 | chosen: |
542 | 0 | if (md->choice == -1) |
543 | 0 | return (1); |
544 | 0 | item = &menu->items[md->choice]; |
545 | 0 | if (item->name == NULL || *item->name == '-') { |
546 | 0 | if (md->flags & MENU_STAYOPEN) |
547 | 0 | return (0); |
548 | 0 | return (1); |
549 | 0 | } |
550 | 0 | if (md->cb != NULL) { |
551 | 0 | md->cb(md->menu, md->choice, item->key, md->data); |
552 | 0 | md->cb = NULL; |
553 | 0 | return (1); |
554 | 0 | } |
555 | | |
556 | 0 | if (md->key != KEYC_NONE) { |
557 | 0 | memset(&saved_event, 0, sizeof saved_event); |
558 | 0 | saved_event.key = md->key; |
559 | 0 | memcpy(&saved_event.m, &md->m, sizeof saved_event.m); |
560 | 0 | event = &saved_event; |
561 | 0 | } else |
562 | 0 | event = NULL; |
563 | 0 | state = cmdq_new_state(&md->fs, event, 0); |
564 | |
|
565 | 0 | status = cmd_parse_and_append(item->command, NULL, c, state, &error); |
566 | 0 | if (status == CMD_PARSE_ERROR) { |
567 | 0 | cmdq_append(c, cmdq_get_error(error)); |
568 | 0 | free(error); |
569 | 0 | } |
570 | 0 | cmdq_free_state(state); |
571 | |
|
572 | 0 | return (1); |
573 | 0 | } |
574 | | |
575 | | void |
576 | | menu_resize(struct menu_data *md, struct window *w) |
577 | 0 | { |
578 | 0 | u_int nx, ny, sx, sy; |
579 | |
|
580 | 0 | if (md == NULL) |
581 | 0 | return; |
582 | 0 | nx = md->px; |
583 | 0 | ny = md->py; |
584 | |
|
585 | 0 | menu_get_size(md->menu, md->border_lines, &sx, &sy); |
586 | 0 | if (nx + sx > w->sx) { |
587 | 0 | if (w->sx <= sx) |
588 | 0 | nx = 0; |
589 | 0 | else |
590 | 0 | nx = w->sx - sx; |
591 | 0 | } |
592 | 0 | if (ny + sy > w->sy) { |
593 | 0 | if (w->sy <= sy) |
594 | 0 | ny = 0; |
595 | 0 | else |
596 | 0 | ny = w->sy - sy; |
597 | 0 | } |
598 | 0 | md->px = nx; |
599 | 0 | md->py = ny; |
600 | 0 | } |
601 | | |
602 | | int |
603 | | menu_display(struct menu *menu, int flags, int starting_choice, |
604 | | struct cmdq_item *item, u_int px, u_int py, struct client *c, |
605 | | enum box_lines lines, const char *style, const char *selected_style, |
606 | | const char *border_style, struct cmd_find_state *fs, menu_choice_cb cb, |
607 | | void *data) |
608 | 0 | { |
609 | 0 | struct menu_data *md; |
610 | 0 | struct key_event *event = NULL; |
611 | 0 | int choice; |
612 | 0 | const char *name; |
613 | 0 | u_int sx, sy; |
614 | 0 | struct window *w; |
615 | 0 | struct options *o; |
616 | |
|
617 | 0 | if (item != NULL) |
618 | 0 | event = cmdq_get_event(item); |
619 | 0 | if (fs == NULL || (event != NULL && event->m.valid)) |
620 | 0 | w = c->session->curw->window; |
621 | 0 | else |
622 | 0 | w = fs->w; |
623 | 0 | if (fs != NULL) |
624 | 0 | o = fs->w->options; |
625 | 0 | else |
626 | 0 | o = w->options; |
627 | |
|
628 | 0 | if (lines == BOX_LINES_DEFAULT) |
629 | 0 | lines = options_get_number(o, "menu-border-lines"); |
630 | 0 | menu_get_size(menu, lines, &sx, &sy); |
631 | 0 | if (sx >= w->sx) |
632 | 0 | px = 0; |
633 | 0 | else if (px + sx > w->sx) |
634 | 0 | px = w->sx - sx; |
635 | 0 | if (sy >= w->sy) |
636 | 0 | py = 0; |
637 | 0 | else if (py + sy > w->sy) |
638 | 0 | py = w->sy - sy; |
639 | 0 | w->menu_last_px = px; |
640 | 0 | w->menu_last_py = py; |
641 | |
|
642 | 0 | md = xcalloc(1, sizeof *md); |
643 | 0 | md->w = w; |
644 | 0 | md->flags = flags; |
645 | 0 | md->border_lines = lines; |
646 | 0 | md->key = KEYC_NONE; |
647 | |
|
648 | 0 | if (event != NULL) { |
649 | 0 | md->key = event->key; |
650 | 0 | memcpy(&md->m, &event->m, sizeof md->m); |
651 | 0 | } |
652 | |
|
653 | 0 | if (style != NULL) |
654 | 0 | md->style = xstrdup(style); |
655 | 0 | if (selected_style != NULL) |
656 | 0 | md->selected_style = xstrdup(selected_style); |
657 | 0 | if (border_style != NULL) |
658 | 0 | md->border_style = xstrdup(border_style); |
659 | |
|
660 | 0 | if (fs != NULL) |
661 | 0 | cmd_find_copy_state(&md->fs, fs); |
662 | 0 | else if (cmd_find_from_window(&md->fs, w, 0) != 0) |
663 | 0 | cmd_find_clear_state(&md->fs, 0); |
664 | 0 | screen_init(&md->s, sx, sy, 0); |
665 | 0 | if (~md->flags & MENU_NOMOUSE) |
666 | 0 | md->s.mode |= (MODE_MOUSE_ALL|MODE_MOUSE_BUTTON); |
667 | 0 | md->s.mode &= ~MODE_CURSOR; |
668 | |
|
669 | 0 | md->px = px; |
670 | 0 | md->py = py; |
671 | |
|
672 | 0 | md->menu = menu; |
673 | 0 | md->choice = -1; |
674 | |
|
675 | 0 | md->cb = cb; |
676 | 0 | md->data = data; |
677 | |
|
678 | 0 | if (md->flags & MENU_NOMOUSE) { |
679 | 0 | if (starting_choice >= (int)menu->count) { |
680 | 0 | starting_choice = menu->count - 1; |
681 | 0 | choice = starting_choice + 1; |
682 | 0 | for (;;) { |
683 | 0 | name = menu->items[choice - 1].name; |
684 | 0 | if (name != NULL && *name != '-') { |
685 | 0 | md->choice = choice - 1; |
686 | 0 | break; |
687 | 0 | } |
688 | 0 | if (--choice == 0) |
689 | 0 | choice = menu->count; |
690 | 0 | if (choice == starting_choice + 1) |
691 | 0 | break; |
692 | 0 | } |
693 | 0 | } else if (starting_choice >= 0) { |
694 | 0 | choice = starting_choice; |
695 | 0 | for (;;) { |
696 | 0 | name = menu->items[choice].name; |
697 | 0 | if (name != NULL && *name != '-') { |
698 | 0 | md->choice = choice; |
699 | 0 | break; |
700 | 0 | } |
701 | 0 | if (++choice == (int)menu->count) |
702 | 0 | choice = 0; |
703 | 0 | if (choice == starting_choice) |
704 | 0 | break; |
705 | 0 | } |
706 | 0 | } |
707 | 0 | } |
708 | |
|
709 | 0 | menu_close(md->w); |
710 | 0 | md->w->menu = md; |
711 | |
|
712 | 0 | redraw_invalidate_scene(md->w); |
713 | 0 | window_update_focus(md->w); |
714 | 0 | server_redraw_window(md->w); |
715 | 0 | return (0); |
716 | 0 | } |