fixed screen dying on popup open
[taskasaur.git] / menu.c
1
2 #include <stdlib.h>
3 #include <stddef.h>
4 #include <string.h>
5 #include <stdbool.h>
6 #include <ncurses.h>
7
8 #include "headers/menu.h"
9 #include "headers/render.h"
10 #include "headers/utils.h"
11
12 #define MENU_PAD_TOP 2
13 #define MENU_PAD_BOTTOM 2
14 #define MENU_PAD_LEFT 2
15 #define MENU_PAD_RIGHT 1
16
17 #define MAX_CONTENTS_LENGTH 256
18
19 typedef struct MenuItem {
20     char* title;
21     char* description;
22     void* userdata;
23 } MenuItem;
24
25 typedef struct Menu {
26     char* menu_name;
27     MenuItem** menu_items;
28     int menu_length;
29     int selected_item;
30     int scroll_offset;
31     bool focused;
32     WINDOW* menu_win;
33     int max_height;
34     int max_width;
35     void* userdata;
36     void (*render_item)(Menu*, int, int);
37     int (*item_height)(MenuItem*);
38 } Menu;
39
40 int swap_item(Menu* menu, int src_index, int dest_index);
41
42 /* insert mode */
43 int menu_insert_mode(Menu* menu, int insert_index);
44
45 /* prob temp for now */
46 MenuItem* create_blank_menuitem(void);
47
48 /* rendering stuff */
49 void default_render_item(Menu* menu, int item_index, int start_y);
50 int default_item_height(MenuItem* menuitem);
51 int items_visible(Menu* menu, int offset);
52 int items_visible_rev(Menu* menu, int offset);
53
54 MenuItem*
55 create_menuitem(char* title)
56 {
57     MenuItem* new_menuitem;
58     TodoItem* new_userdata;
59
60     new_menuitem = malloc(sizeof(MenuItem));
61     new_menuitem->title = title;
62     new_menuitem->description = strdup(""); //TEMP FOR NOW
63
64     new_userdata = malloc(sizeof(TodoItem));
65     new_userdata->item_name = title;
66     new_userdata->description = strdup("");
67     new_userdata->due = strdup("");
68     new_userdata->subtask_list = malloc(0);
69     new_userdata->subtask_count = 0;
70     
71     new_menuitem->userdata = new_userdata;
72
73     return new_menuitem;
74 }
75
76 MenuItem*
77 create_blank_menuitem(void)
78 {
79     return create_menuitem(strdup(""));
80 }
81
82 Menu* 
83 create_menu(char* menu_name, MenuItem** item_list)
84 {
85     Menu* new_menu;
86
87     new_menu = malloc(sizeof(Menu));
88     new_menu->menu_name = menu_name;
89     new_menu->menu_items = item_list;
90     new_menu->menu_length = array_length(MenuItem*, item_list);
91     new_menu->selected_item = 0;
92     new_menu->scroll_offset = 0;
93     new_menu->focused = false;
94     new_menu->userdata = NULL;
95     new_menu->render_item = *default_render_item;
96     new_menu->item_height = *default_item_height;
97     set_menu_win(new_menu, stdscr);
98
99     return new_menu;
100 }
101
102 /* getters */
103 WINDOW*
104 get_menu_win(Menu* menu)
105 {
106     return menu->menu_win;
107 }
108
109 MenuItem*
110 get_menu_item(Menu* menu, int index)
111 {
112     if (index < 0 || index >= menu->menu_length) return NULL;
113
114     return menu->menu_items[index];
115 }
116
117 int
118 get_selected_item(Menu* menu)
119 {
120     return menu->selected_item;
121 }
122
123 MenuItem*
124 get_selected_menuitem(Menu* menu)
125 {
126     return get_menu_item(menu, get_selected_item(menu));
127 }
128
129 bool
130 get_menu_focused(Menu* menu)
131 {
132     return menu->focused;
133 }
134
135 int
136 get_menu_length(Menu* menu)
137 {
138     return menu->menu_length;
139 }
140
141 char*
142 get_menu_name(Menu* menu)
143 {
144     return menu->menu_name;
145 }
146
147 void*
148 get_menu_userdata(Menu* menu)
149 {
150     return menu->userdata;
151 }
152
153 char*
154 get_menuitem_title(MenuItem* menuitem)
155 {
156     return menuitem->title;
157 }
158
159 char*
160 get_menuitem_descrip(MenuItem* menuitem)
161 {
162     return menuitem->description;
163 }
164
165 void*
166 get_menuitem_userdata(MenuItem* menuitem)
167 {
168     return menuitem->userdata;
169 }
170
171
172 /* setters */
173 int
174 set_menu_win(Menu* menu, WINDOW* win)
175 {
176     int height, width;
177
178     menu->menu_win = win;
179     getmaxyx(menu->menu_win, height, width);
180
181     menu->max_height = height-MENU_PAD_TOP-MENU_PAD_BOTTOM;
182     menu->max_width = width-MENU_PAD_LEFT-MENU_PAD_RIGHT;
183
184     return 0;
185 }
186
187 int
188 set_selected_item(Menu* menu, int selected_item)
189 {
190     menu->selected_item = selected_item;
191     return 0;
192 }
193
194 int
195 set_menu_focus(Menu* menu, bool focus)
196 {
197     menu->focused = focus;
198     return 0;
199 }
200
201 int
202 set_menu_userdata(Menu* menu, void* userdata)
203 {
204     menu->userdata = userdata;
205     return 0;
206 }
207
208 int
209 set_menu_renderitem(Menu* menu, void (*render_item)(Menu*, int, int))
210 {
211     menu->render_item = render_item;
212     return 0;
213 }
214
215 int
216 set_menu_itemheight(Menu* menu, int (*item_height)(MenuItem*))
217 {
218     menu->item_height = item_height;
219     return 0;
220 }
221
222 int
223 set_menuitem_descrip(MenuItem* menuitem, char* descrip)
224 {
225     menuitem->description = descrip;
226     return 0;
227 }
228
229 int
230 set_menuitem_userdata(MenuItem* menuitem, void* userdata)
231 {
232     menuitem->userdata = userdata;
233     return 0;
234 }
235
236
237 int
238 swap_item(Menu* menu, int src_index, int dest_index)
239 {
240     ar_swap_item((void**)menu->menu_items, src_index, dest_index);
241
242     return 0;
243 }
244
245 int
246 delete_item(Menu* menu, int index)
247 {
248     if (index < 0 || index > menu->menu_length-1) return -1;
249
250     for (int i = index; i <= menu->menu_length-1; i++) {
251         menu->menu_items[i] = menu->menu_items[i+1];
252     }
253
254     menu->menu_items = realloc(menu->menu_items, menu->menu_length*sizeof(MenuItem*)); 
255     menu->menu_items[menu->menu_length-1] = 0; // preserve null at end
256
257     menu->menu_length -= 1;
258
259     /* also move the current selected position if it's last */
260     if (menu->selected_item > menu->menu_length-1) {
261         menu->selected_item = menu->menu_length-1;
262     }
263
264     return 0;
265 }
266
267 int 
268 insert_item(Menu* menu, MenuItem* menuitem, int index)
269 { // note, this func does not validate index
270
271     /* resize array and insert */
272     menu->menu_items = realloc(menu->menu_items, (menu->menu_length+2)*sizeof(MenuItem*));
273
274     for (int i = menu->menu_length; i > index; i--) {
275         menu->menu_items[i] = menu->menu_items[i-1];
276     }
277
278     menu->menu_items[index] = menuitem;
279     menu->menu_items[menu->menu_length+1] = 0; // remember null at end   
280     menu->menu_length += 1;
281
282     /* move cursor pos */
283     menu->selected_item = index;
284
285     return 0;
286 }
287
288 int
289 menu_insert_mode(Menu* menu, int insert_index)
290 {
291     char temp[MAX_CONTENTS_LENGTH+1]; // remember null
292     char* new_contents;
293     int insert_pos;
294
295     curs_on();
296
297     // account for multiline items
298     insert_pos = menu->scroll_offset;
299     for (int i = 0; i < insert_index; i++) {
300         insert_pos += menu->item_height(menu->menu_items[i]);
301     }
302
303     /* move cursor to right spot */
304     ungetstr(menu->menu_items[insert_index]->title);
305     mvwgetnstr(menu->menu_win,
306         insert_pos, 
307         0,
308         temp,
309         MAX_CONTENTS_LENGTH
310     );
311     curs_off();
312
313     /* copy out */
314     new_contents = strdup(temp);
315     menu->menu_items[insert_index]->title = new_contents;
316
317     /* delete if empty - maybe move this to a cleanup stage */
318     if (strlen(new_contents) == 0) {
319         delete_item(menu, insert_index);
320     }
321
322     return 0;
323 }
324
325 int
326 menu_driver(Menu* menu, MenuAction action)
327 {
328
329     switch (action) {
330         case MENU_UP:
331             menu->selected_item = menu->selected_item-1 >= 0 ? menu->selected_item-1 : 0;
332             break;
333
334         case MENU_DOWN:
335             menu->selected_item = menu->selected_item+1 <= menu->menu_length-1 ? menu->selected_item+1 : menu->menu_length-1;
336             break;
337
338         case MENU_TOP:
339             menu->selected_item = 0;
340             break;
341
342         case MENU_BOTTOM:
343             menu->selected_item = menu->menu_length-1;
344             break;
345
346         case MENU_MOVE_UP:
347             if (menu->selected_item <= 0) break;
348             swap_item(menu, menu->selected_item, menu->selected_item-1);
349             menu->selected_item -= 1;
350             break;
351
352         case MENU_MOVE_DOWN:
353             if (menu->selected_item >= menu->menu_length-1) break;
354             swap_item(menu, menu->selected_item, menu->selected_item+1);
355             menu->selected_item += 1;
356             break;
357
358         case MENU_DELETE:
359             delete_item(menu, menu->selected_item);
360             break;
361
362         case MENU_APPEND:
363             insert_item(menu, create_blank_menuitem(), menu->menu_length);
364             render_menu(menu); // refresh after inserting
365             menu_insert_mode(menu, menu->selected_item);
366             break;
367
368         case MENU_INSERT_ABOVE:
369             insert_item(menu, create_blank_menuitem(), menu->selected_item);
370             render_menu(menu);
371             menu_insert_mode(menu, menu->selected_item);
372             break;
373
374         case MENU_INSERT_BELOW:
375             insert_item(menu, create_blank_menuitem(), menu->selected_item+1);
376             render_menu(menu);
377             menu_insert_mode(menu, menu->selected_item); // inserted item is cur now
378             break;
379
380         case MENU_EDIT:
381             menu_insert_mode(menu, menu->selected_item);
382             break;
383
384         default: // This is here for debug, disable later
385             fprintf(stderr, "Invalid menu action");
386     }
387
388     return 0;
389 }
390
391 int
392 render_menu(Menu* menu)
393 {
394     wclear(menu->menu_win);
395
396     /* calculate scroll */
397     int visible;
398
399     visible = items_visible(menu, menu->scroll_offset);
400
401     if (menu->selected_item >= menu->scroll_offset+visible) {
402         menu->scroll_offset = clamp(
403             menu->selected_item-items_visible_rev(menu, menu->selected_item)+1,
404             0,
405             floorzero(menu->menu_length-1)
406         );
407
408     } else if (menu->selected_item < menu->scroll_offset) {
409         menu->scroll_offset = clamp(
410             menu->selected_item,
411             0,
412             floorzero(menu->menu_length-1)
413         );
414     }
415
416     /* render menu items */
417     int curline = 0;
418     for (int i = menu->scroll_offset; i < menu->menu_length; i++) {
419         menu->render_item(menu, i, curline);
420         curline += menu->item_height(menu->menu_items[i]);
421     }
422
423     wrefresh(menu->menu_win);
424
425     return 0;
426 }
427
428 void
429 default_render_item(Menu* menu, int item_index, int start_y)
430 {
431     MenuItem* curitem;
432     int hlcolor;
433
434     curitem = menu->menu_items[item_index];
435
436     /* color selected item */
437     hlcolor = COLOR_PAIR((item_index == menu->selected_item && menu->focused == true) ? TS_SELECTED : TS_NONSELECTED);
438     wattron(menu->menu_win, hlcolor);
439     mvwprintw(menu->menu_win, start_y, 0, curitem->title);
440     wattroff(menu->menu_win, hlcolor);
441 }
442
443
444 int
445 default_item_height(MenuItem* menuitem)
446
447     return 1; // maybe account for line wrap by default
448 }
449
450 int
451 items_visible(Menu* menu, int offset)
452 {
453     int maxheight;
454     int maxwidth; // unused
455
456     getmaxyx(menu->menu_win, maxheight, maxwidth);
457
458     int vis = 0;
459     int lines = 0;
460     for (int i = offset ; i < menu->menu_length; i++) {
461
462         lines += menu->item_height(menu->menu_items[i]);
463         if (lines > maxheight) break;
464         vis += 1;
465
466     }
467
468     return vis;
469 }
470
471 int
472 items_visible_rev(Menu* menu, int offset)
473 {
474     int maxheight;
475     int maxwidth; // unused
476
477     getmaxyx(menu->menu_win, maxheight, maxwidth);
478
479     int vis = 0;
480     int lines = 0;
481     for (int i = offset; i > 0; i--) {
482
483         lines += menu->item_height(menu->menu_items[i]);
484         if (lines > maxheight) break;
485         vis +=1;
486
487     }
488
489     return vis;
490 }
491
492 int
493 free_menu(Menu* menu)
494 {
495     return 0;
496 }
497