1 /* $OpenBSD: menu.c,v 1.5 2019/05/23 11:13:30 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 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 43 static void 44 menu_add_item(struct menu *menu, struct menu_item *item, struct client *c, 45 struct cmd_find_state *fs) 46 { 47 struct menu_item *new_item; 48 const char *key; 49 char *name; 50 u_int width; 51 52 menu->items = xreallocarray(menu->items, menu->count + 1, 53 sizeof *menu->items); 54 new_item = &menu->items[menu->count++]; 55 memset(new_item, 0, sizeof *new_item); 56 57 if (item == NULL || *item->name == '\0') /* horizontal line */ 58 return; 59 if (fs != NULL) { 60 name = format_single(NULL, item->name, c, fs->s, fs->wl, 61 fs->wp); 62 } else 63 name = xstrdup(item->name); 64 if (*name == '\0') { /* no item if empty after format expanded */ 65 menu->count--; 66 return; 67 } 68 if (item->key != KEYC_UNKNOWN) { 69 key = key_string_lookup_key(item->key); 70 xasprintf(&new_item->name, "%s #[align=right](%s)", name, key); 71 } else 72 xasprintf(&new_item->name, "%s", name); 73 free(name); 74 75 if (item->command != NULL) 76 new_item->command = xstrdup(item->command); 77 else 78 new_item->command = NULL; 79 new_item->key = item->key; 80 81 width = format_width(new_item->name); 82 if (width > menu->width) 83 menu->width = width; 84 } 85 86 static void 87 menu_parse_item(struct menu *menu, const char *s, struct client *c, 88 struct cmd_find_state *fs) 89 { 90 char *copy, *first; 91 const char *second, *third; 92 struct menu_item item; 93 94 first = copy = xstrdup(s); 95 if ((second = format_skip(first, ",")) != NULL) { 96 *(char *)second++ = '\0'; 97 if ((third = format_skip(second, ",")) != NULL) { 98 *(char *)third++ = '\0'; 99 100 item.name = first; 101 item.command = (char *)third; 102 item.key = key_string_lookup_string(second); 103 menu_add_item(menu, &item, c, fs); 104 } 105 } 106 free(copy); 107 } 108 109 struct menu * 110 menu_create(const char *s, struct client *c, struct cmd_find_state *fs, 111 const char *title) 112 { 113 struct menu *menu; 114 char *copy, *string, *next; 115 116 if (*s == '\0') 117 return (NULL); 118 119 menu = xcalloc(1, sizeof *menu); 120 menu->title = xstrdup(title); 121 122 copy = string = xstrdup(s); 123 do { 124 next = (char *)format_skip(string, "|"); 125 if (next != NULL) 126 *next++ = '\0'; 127 if (*string == '\0') 128 menu_add_item(menu, NULL, c, fs); 129 else 130 menu_parse_item(menu, string, c, fs); 131 string = next; 132 } while (next != NULL); 133 free(copy); 134 135 return (menu); 136 } 137 138 void 139 menu_free(struct menu *menu) 140 { 141 u_int i; 142 143 for (i = 0; i < menu->count; i++) { 144 free(menu->items[i].name); 145 free(menu->items[i].command); 146 } 147 free(menu->items); 148 149 free(menu->title); 150 free(menu); 151 } 152 153 static void 154 menu_draw_cb(struct client *c, __unused struct screen_redraw_ctx *ctx0) 155 { 156 struct menu_data *md = c->overlay_data; 157 struct tty *tty = &c->tty; 158 struct screen *s = &md->s; 159 struct menu *menu = md->menu; 160 struct screen_write_ctx ctx; 161 u_int i, px, py; 162 163 screen_write_start(&ctx, NULL, s); 164 screen_write_clearscreen(&ctx, 8); 165 screen_write_menu(&ctx, menu, md->choice); 166 screen_write_stop(&ctx); 167 168 px = md->px; 169 py = md->py; 170 171 for (i = 0; i < screen_size_y(&md->s); i++) 172 tty_draw_line(tty, NULL, s, 0, i, menu->width + 4, px, py + i); 173 174 if (~md->flags & MENU_NOMOUSE) 175 tty_update_mode(tty, MODE_MOUSE_ALL, NULL); 176 } 177 178 static void 179 menu_free_cb(struct client *c) 180 { 181 struct menu_data *md = c->overlay_data; 182 183 if (md->item != NULL) 184 md->item->flags &= ~CMDQ_WAITING; 185 186 if (md->cb != NULL) 187 md->cb(md->menu, UINT_MAX, KEYC_NONE, md->data); 188 189 screen_free(&md->s); 190 menu_free(md->menu); 191 free(md); 192 } 193 194 static int 195 menu_key_cb(struct client *c, struct key_event *event) 196 { 197 struct menu_data *md = c->overlay_data; 198 struct menu *menu = md->menu; 199 struct mouse_event *m = &event->m; 200 u_int i; 201 int count = menu->count, old = md->choice; 202 const struct menu_item *item; 203 struct cmdq_item *new_item; 204 struct cmd_parse_result *pr; 205 206 if (KEYC_IS_MOUSE(event->key)) { 207 if (md->flags & MENU_NOMOUSE) 208 return (0); 209 if (m->x < md->px || 210 m->x > md->px + 4 + menu->width || 211 m->y < md->py + 1 || 212 m->y > md->py + 1 + count - 1) { 213 if (MOUSE_RELEASE(m->b)) 214 return (1); 215 if (md->choice != -1) { 216 md->choice = -1; 217 c->flags |= CLIENT_REDRAWOVERLAY; 218 } 219 return (0); 220 } 221 md->choice = m->y - (md->py + 1); 222 if (MOUSE_RELEASE(m->b)) 223 goto chosen; 224 if (md->choice != old) 225 c->flags |= CLIENT_REDRAWOVERLAY; 226 return (0); 227 } 228 switch (event->key) { 229 case KEYC_UP: 230 do { 231 if (md->choice == -1 || md->choice == 0) 232 md->choice = count - 1; 233 else 234 md->choice--; 235 } while (menu->items[md->choice].name == NULL); 236 c->flags |= CLIENT_REDRAWOVERLAY; 237 return (0); 238 case KEYC_DOWN: 239 do { 240 if (md->choice == -1 || md->choice == count - 1) 241 md->choice = 0; 242 else 243 md->choice++; 244 } while (menu->items[md->choice].name == NULL); 245 c->flags |= CLIENT_REDRAWOVERLAY; 246 return (0); 247 case '\r': 248 goto chosen; 249 case '\033': /* Escape */ 250 case '\003': /* C-c */ 251 case '\007': /* C-g */ 252 case 'q': 253 return (1); 254 } 255 for (i = 0; i < (u_int)count; i++) { 256 if (event->key == menu->items[i].key) { 257 md->choice = i; 258 goto chosen; 259 } 260 } 261 return (0); 262 263 chosen: 264 if (md->choice == -1) 265 return (1); 266 item = &menu->items[md->choice]; 267 if (item->name == NULL) 268 return (1); 269 if (md->cb != NULL) { 270 md->cb(md->menu, md->choice, item->key, md->data); 271 md->cb = NULL; 272 return (1); 273 } 274 275 pr = cmd_parse_from_string(item->command, NULL); 276 switch (pr->status) { 277 case CMD_PARSE_EMPTY: 278 new_item = NULL; 279 break; 280 case CMD_PARSE_ERROR: 281 new_item = cmdq_get_error(pr->error); 282 free(pr->error); 283 cmdq_append(c, new_item); 284 break; 285 case CMD_PARSE_SUCCESS: 286 new_item = cmdq_get_command(pr->cmdlist, NULL, NULL, 0); 287 cmd_list_free(pr->cmdlist); 288 cmdq_append(c, new_item); 289 break; 290 } 291 return (1); 292 } 293 294 int 295 menu_display(struct menu *menu, int flags, struct cmdq_item *item, u_int px, 296 u_int py, struct client *c, struct cmd_find_state *fs, menu_choice_cb cb, 297 void *data) 298 { 299 struct menu_data *md; 300 301 if (c->tty.sx < menu->width + 4 || c->tty.sy < menu->count + 2) 302 return (-1); 303 304 md = xcalloc(1, sizeof *md); 305 md->item = item; 306 md->flags = flags; 307 308 if (fs != NULL) 309 cmd_find_copy_state(&md->fs, fs); 310 screen_init(&md->s, menu->width + 4, menu->count + 2, 0); 311 312 md->px = px; 313 md->py = py; 314 315 md->menu = menu; 316 md->choice = -1; 317 318 md->cb = cb; 319 md->data = data; 320 321 server_client_set_overlay(c, 0, menu_draw_cb, menu_key_cb, menu_free_cb, 322 md); 323 return (0); 324 } 325