4 * Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
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.
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.
19 #include <sys/types.h>
27 struct cmdq_item
*item
;
30 struct grid_cell style
;
31 struct grid_cell border_style
;
32 struct grid_cell selected_style
;
33 enum box_lines border_lines
;
35 struct cmd_find_state fs
;
49 menu_add_items(struct menu
*menu
, const struct menu_item
*items
,
50 struct cmdq_item
*qitem
, struct client
*c
, struct cmd_find_state
*fs
)
52 const struct menu_item
*loop
;
54 for (loop
= items
; loop
->name
!= NULL
; loop
++)
55 menu_add_item(menu
, loop
, qitem
, c
, fs
);
59 menu_add_item(struct menu
*menu
, const struct menu_item
*item
,
60 struct cmdq_item
*qitem
, struct client
*c
, struct cmd_find_state
*fs
)
62 struct menu_item
*new_item
;
63 const char *key
= NULL
, *cmd
, *suffix
= "";
64 char *s
, *trimmed
, *name
;
65 u_int width
, max_width
;
69 line
= (item
== NULL
|| item
->name
== NULL
|| *item
->name
== '\0');
70 if (line
&& menu
->count
== 0)
72 if (line
&& menu
->items
[menu
->count
- 1].name
== NULL
)
75 menu
->items
= xreallocarray(menu
->items
, menu
->count
+ 1,
77 new_item
= &menu
->items
[menu
->count
++];
78 memset(new_item
, 0, sizeof *new_item
);
84 s
= format_single_from_state(qitem
, item
->name
, c
, fs
);
86 s
= format_single(qitem
, item
->name
, c
, NULL
, NULL
, NULL
);
87 if (*s
== '\0') { /* no item if empty after format expanded */
91 max_width
= c
->tty
.sx
- 4;
94 if (*s
!= '-' && item
->key
!= KEYC_UNKNOWN
&& item
->key
!= KEYC_NONE
) {
95 key
= key_string_lookup_key(item
->key
, 0);
96 keylen
= strlen(key
) + 3; /* 3 = space and two brackets */
99 * Add the key if it is shorter than a quarter of the available
100 * space or there is space for the entire item text and the
103 if (keylen
<= max_width
/ 4)
105 else if (keylen
>= max_width
|| slen
>= max_width
- keylen
)
109 if (slen
> max_width
) {
113 trimmed
= format_trim_right(s
, max_width
);
115 xasprintf(&name
, "%s%s#[default] #[align=right](%s)",
116 trimmed
, suffix
, key
);
118 xasprintf(&name
, "%s%s", trimmed
, suffix
);
121 new_item
->name
= name
;
127 s
= format_single_from_state(qitem
, cmd
, c
, fs
);
129 s
= format_single(qitem
, cmd
, c
, NULL
, NULL
, NULL
);
132 new_item
->command
= s
;
133 new_item
->key
= item
->key
;
135 width
= format_width(new_item
->name
);
136 if (*new_item
->name
== '-')
138 if (width
> menu
->width
)
143 menu_create(const char *title
)
147 menu
= xcalloc(1, sizeof *menu
);
148 menu
->title
= xstrdup(title
);
149 menu
->width
= format_width(title
);
155 menu_free(struct menu
*menu
)
159 for (i
= 0; i
< menu
->count
; i
++) {
160 free((void *)menu
->items
[i
].name
);
161 free((void *)menu
->items
[i
].command
);
165 free((void *)menu
->title
);
170 menu_mode_cb(__unused
struct client
*c
, void *data
, u_int
*cx
, u_int
*cy
)
172 struct menu_data
*md
= data
;
175 if (md
->choice
== -1)
178 *cy
= md
->py
+ 1 + md
->choice
;
183 /* Return parts of the input range which are not obstructed by the menu. */
185 menu_check_cb(__unused
struct client
*c
, void *data
, u_int px
, u_int py
,
186 u_int nx
, struct overlay_ranges
*r
)
188 struct menu_data
*md
= data
;
189 struct menu
*menu
= md
->menu
;
191 server_client_overlay_range(md
->px
, md
->py
, menu
->width
+ 4,
192 menu
->count
+ 2, px
, py
, nx
, r
);
196 menu_draw_cb(struct client
*c
, void *data
,
197 __unused
struct screen_redraw_ctx
*rctx
)
199 struct menu_data
*md
= data
;
200 struct tty
*tty
= &c
->tty
;
201 struct screen
*s
= &md
->s
;
202 struct menu
*menu
= md
->menu
;
203 struct screen_write_ctx ctx
;
204 u_int i
, px
= md
->px
, py
= md
->py
;
206 screen_write_start(&ctx
, s
);
207 screen_write_clearscreen(&ctx
, 8);
209 if (md
->border_lines
!= BOX_LINES_NONE
) {
210 screen_write_box(&ctx
, menu
->width
+ 4, menu
->count
+ 2,
211 md
->border_lines
, &md
->border_style
, menu
->title
);
214 screen_write_menu(&ctx
, menu
, md
->choice
, md
->border_lines
,
215 &md
->style
, &md
->border_style
, &md
->selected_style
);
216 screen_write_stop(&ctx
);
218 for (i
= 0; i
< screen_size_y(&md
->s
); i
++) {
219 tty_draw_line(tty
, s
, 0, i
, menu
->width
+ 4, px
, py
+ i
,
220 &grid_default_cell
, NULL
);
225 menu_free_cb(__unused
struct client
*c
, void *data
)
227 struct menu_data
*md
= data
;
229 if (md
->item
!= NULL
)
230 cmdq_continue(md
->item
);
233 md
->cb(md
->menu
, UINT_MAX
, KEYC_NONE
, md
->data
);
241 menu_key_cb(struct client
*c
, void *data
, struct key_event
*event
)
243 struct menu_data
*md
= data
;
244 struct menu
*menu
= md
->menu
;
245 struct mouse_event
*m
= &event
->m
;
247 int count
= menu
->count
, old
= md
->choice
;
248 const char *name
= NULL
;
249 const struct menu_item
*item
;
250 struct cmdq_state
*state
;
251 enum cmd_parse_status status
;
254 if (KEYC_IS_MOUSE(event
->key
)) {
255 if (md
->flags
& MENU_NOMOUSE
) {
256 if (MOUSE_BUTTONS(m
->b
) != MOUSE_BUTTON_1
)
261 m
->x
> md
->px
+ 4 + menu
->width
||
263 m
->y
> md
->py
+ 1 + count
- 1) {
264 if (~md
->flags
& MENU_STAYOPEN
) {
265 if (MOUSE_RELEASE(m
->b
))
268 if (!MOUSE_RELEASE(m
->b
) &&
269 !MOUSE_WHEEL(m
->b
) &&
273 if (md
->choice
!= -1) {
275 c
->flags
|= CLIENT_REDRAWOVERLAY
;
279 if (~md
->flags
& MENU_STAYOPEN
) {
280 if (MOUSE_RELEASE(m
->b
))
283 if (!MOUSE_WHEEL(m
->b
) && !MOUSE_DRAG(m
->b
))
286 md
->choice
= m
->y
- (md
->py
+ 1);
287 if (md
->choice
!= old
)
288 c
->flags
|= CLIENT_REDRAWOVERLAY
;
291 for (i
= 0; i
< (u_int
)count
; i
++) {
292 name
= menu
->items
[i
].name
;
293 if (name
== NULL
|| *name
== '-')
295 if (event
->key
== menu
->items
[i
].key
) {
300 switch (event
->key
& ~KEYC_MASK_FLAGS
) {
306 if (md
->choice
== -1 || md
->choice
== 0)
307 md
->choice
= count
- 1;
310 name
= menu
->items
[md
->choice
].name
;
311 } while ((name
== NULL
|| *name
== '-') && md
->choice
!= old
);
312 c
->flags
|= CLIENT_REDRAWOVERLAY
;
315 if (~md
->flags
& MENU_TAB
)
318 case '\011': /* Tab */
319 if (~md
->flags
& MENU_TAB
)
321 if (md
->choice
== count
- 1)
329 if (md
->choice
== -1 || md
->choice
== count
- 1)
333 name
= menu
->items
[md
->choice
].name
;
334 } while ((name
== NULL
|| *name
== '-') && md
->choice
!= old
);
335 c
->flags
|= CLIENT_REDRAWOVERLAY
;
345 name
= menu
->items
[md
->choice
].name
;
346 if (md
->choice
!= 0 &&
347 (name
!= NULL
&& *name
!= '-'))
349 else if (md
->choice
== 0)
353 c
->flags
|= CLIENT_REDRAWOVERLAY
;
356 if (md
->choice
> count
- 6) {
357 md
->choice
= count
- 1;
358 name
= menu
->items
[md
->choice
].name
;
363 name
= menu
->items
[md
->choice
].name
;
364 if (md
->choice
!= count
- 1 &&
365 (name
!= NULL
&& *name
!= '-'))
367 else if (md
->choice
== count
- 1)
371 while (name
== NULL
|| *name
== '-') {
373 name
= menu
->items
[md
->choice
].name
;
375 c
->flags
|= CLIENT_REDRAWOVERLAY
;
380 name
= menu
->items
[md
->choice
].name
;
381 while (name
== NULL
|| *name
== '-') {
383 name
= menu
->items
[md
->choice
].name
;
385 c
->flags
|= CLIENT_REDRAWOVERLAY
;
389 md
->choice
= count
- 1;
390 name
= menu
->items
[md
->choice
].name
;
391 while (name
== NULL
|| *name
== '-') {
393 name
= menu
->items
[md
->choice
].name
;
395 c
->flags
|= CLIENT_REDRAWOVERLAY
;
401 case '\033': /* Escape */
410 if (md
->choice
== -1)
412 item
= &menu
->items
[md
->choice
];
413 if (item
->name
== NULL
|| *item
->name
== '-') {
414 if (md
->flags
& MENU_STAYOPEN
)
418 if (md
->cb
!= NULL
) {
419 md
->cb(md
->menu
, md
->choice
, item
->key
, md
->data
);
424 if (md
->item
!= NULL
)
425 event
= cmdq_get_event(md
->item
);
428 state
= cmdq_new_state(&md
->fs
, event
, 0);
430 status
= cmd_parse_and_append(item
->command
, NULL
, c
, state
, &error
);
431 if (status
== CMD_PARSE_ERROR
) {
432 cmdq_append(c
, cmdq_get_error(error
));
435 cmdq_free_state(state
);
441 menu_set_style(struct client
*c
, struct grid_cell
*gc
, const char *style
,
445 struct options
*o
= c
->session
->curw
->window
->options
;
447 memcpy(gc
, &grid_default_cell
, sizeof *gc
);
448 style_apply(gc
, o
, option
, NULL
);
450 style_set(&sytmp
, &grid_default_cell
);
451 if (style_parse(&sytmp
, gc
, style
) == 0) {
452 gc
->fg
= sytmp
.gc
.fg
;
453 gc
->bg
= sytmp
.gc
.bg
;
459 menu_prepare(struct menu
*menu
, int flags
, int starting_choice
,
460 struct cmdq_item
*item
, u_int px
, u_int py
, struct client
*c
,
461 enum box_lines lines
, const char *style
, const char *selected_style
,
462 const char *border_style
, struct cmd_find_state
*fs
, menu_choice_cb cb
,
465 struct menu_data
*md
;
468 struct options
*o
= c
->session
->curw
->window
->options
;
470 if (c
->tty
.sx
< menu
->width
+ 4 || c
->tty
.sy
< menu
->count
+ 2)
472 if (px
+ menu
->width
+ 4 > c
->tty
.sx
)
473 px
= c
->tty
.sx
- menu
->width
- 4;
474 if (py
+ menu
->count
+ 2 > c
->tty
.sy
)
475 py
= c
->tty
.sy
- menu
->count
- 2;
477 if (lines
== BOX_LINES_DEFAULT
)
478 lines
= options_get_number(o
, "menu-border-lines");
480 md
= xcalloc(1, sizeof *md
);
483 md
->border_lines
= lines
;
485 menu_set_style(c
, &md
->style
, style
, "menu-style");
486 menu_set_style(c
, &md
->selected_style
, selected_style
,
487 "menu-selected-style");
488 menu_set_style(c
, &md
->border_style
, border_style
, "menu-border-style");
491 cmd_find_copy_state(&md
->fs
, fs
);
492 screen_init(&md
->s
, menu
->width
+ 4, menu
->count
+ 2, 0);
493 if (~md
->flags
& MENU_NOMOUSE
)
494 md
->s
.mode
|= (MODE_MOUSE_ALL
|MODE_MOUSE_BUTTON
);
495 md
->s
.mode
&= ~MODE_CURSOR
;
503 if (md
->flags
& MENU_NOMOUSE
) {
504 if (starting_choice
>= (int)menu
->count
) {
505 starting_choice
= menu
->count
- 1;
506 choice
= starting_choice
+ 1;
508 name
= menu
->items
[choice
- 1].name
;
509 if (name
!= NULL
&& *name
!= '-') {
510 md
->choice
= choice
- 1;
514 choice
= menu
->count
;
515 if (choice
== starting_choice
+ 1)
518 } else if (starting_choice
>= 0) {
519 choice
= starting_choice
;
521 name
= menu
->items
[choice
].name
;
522 if (name
!= NULL
&& *name
!= '-') {
526 if (++choice
== (int)menu
->count
)
528 if (choice
== starting_choice
)
540 menu_display(struct menu
*menu
, int flags
, int starting_choice
,
541 struct cmdq_item
*item
, u_int px
, u_int py
, struct client
*c
,
542 enum box_lines lines
, const char *style
, const char *selected_style
,
543 const char *border_style
, struct cmd_find_state
*fs
, menu_choice_cb cb
,
546 struct menu_data
*md
;
548 md
= menu_prepare(menu
, flags
, starting_choice
, item
, px
, py
, c
, lines
,
549 style
, selected_style
, border_style
, fs
, cb
, data
);
552 server_client_set_overlay(c
, 0, NULL
, menu_mode_cb
, menu_draw_cb
,
553 menu_key_cb
, menu_free_cb
, NULL
, md
);