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