da064bac511fe03376f0e2b753afed58c979f6c9
[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 int
124 get_menu_length(Menu* menu)
125 {
126     return menu->menu_length;
127 }
128
129 char*
130 get_menu_name(Menu* menu)
131 {
132     return menu->menu_name;
133 }
134
135 void*
136 get_menu_userdata(Menu* menu)
137 {
138     return menu->userdata;
139 }
140
141 char*
142 get_menuitem_title(MenuItem* menuitem)
143 {
144     return menuitem->title;
145 }
146
147 char*
148 get_menuitem_descrip(MenuItem* menuitem)
149 {
150     return menuitem->description;
151 }
152
153 void*
154 get_menuitem_userdata(MenuItem* menuitem)
155 {
156     return menuitem->userdata;
157 }
158
159
160 /* setters */
161 int
162 set_menu_win(Menu* menu, WINDOW* win)
163 {
164     int height, width;
165
166     menu->menu_win = win;
167     getmaxyx(menu->menu_win, height, width);
168
169     menu->max_height = height-MENU_PAD_TOP-MENU_PAD_BOTTOM;
170     menu->max_width = width-MENU_PAD_LEFT-MENU_PAD_RIGHT;
171
172     return 0;
173 }
174
175 int
176 set_selected_item(Menu* menu, int selected_item)
177 {
178     menu->selected_item = selected_item;
179     return 0;
180 }
181
182 int
183 set_menu_focus(Menu* menu, bool focus)
184 {
185     menu->focused = focus;
186     return 0;
187 }
188
189 int
190 set_menu_userdata(Menu* menu, void* userdata)
191 {
192     menu->userdata = userdata;
193     return 0;
194 }
195
196 int
197 set_menu_renderitem(Menu* menu, void (*render_item)(Menu*, int, int))
198 {
199     menu->render_item = render_item;
200     return 0;
201 }
202
203 int
204 set_menu_itemheight(Menu* menu, int (*item_height)(MenuItem*))
205 {
206     menu->item_height = item_height;
207     return 0;
208 }
209
210 int
211 set_menuitem_descrip(MenuItem* menuitem, char* descrip)
212 {
213     menuitem->description = descrip;
214     return 0;
215 }
216
217 int
218 set_menuitem_userdata(MenuItem* menuitem, void* userdata)
219 {
220     menuitem->userdata = userdata;
221     return 0;
222 }
223
224
225 int
226 swap_item(Menu* menu, int src_index, int dest_index)
227 {
228     ar_swap_item((void**)menu->menu_items, src_index, dest_index);
229
230     return 0;
231 }
232
233 int
234 delete_item(Menu* menu, int index)
235 {
236     if (index < 0 || index > menu->menu_length-1) return -1;
237
238     for (int i = index; i <= menu->menu_length-1; i++) {
239         menu->menu_items[i] = menu->menu_items[i+1];
240     }
241
242     menu->menu_items = realloc(menu->menu_items, menu->menu_length*sizeof(MenuItem*)); 
243     menu->menu_items[menu->menu_length-1] = 0; // preserve null at end
244
245     menu->menu_length -= 1;
246
247     /* also move the current selected position if it's last */
248     if (menu->selected_item > menu->menu_length-1) {
249         menu->selected_item = menu->menu_length-1;
250     }
251
252     return 0;
253 }
254
255 int 
256 insert_item(Menu* menu, MenuItem* menuitem, int index)
257 { // note, this func does not validate index
258
259     /* resize array and insert */
260     menu->menu_items = realloc(menu->menu_items, (menu->menu_length+2)*sizeof(MenuItem*));
261
262     for (int i = menu->menu_length; i > index; i--) {
263         menu->menu_items[i] = menu->menu_items[i-1];
264     }
265
266     menu->menu_items[index] = menuitem;
267     menu->menu_items[menu->menu_length+1] = 0; // remember null at end   
268     menu->menu_length += 1;
269
270     /* move cursor pos */
271     menu->selected_item = index;
272
273     return 0;
274 }
275
276 int
277 menu_insert_mode(Menu* menu, int insert_index)
278 {
279     char temp[MAX_CONTENTS_LENGTH+1]; // remember null
280     char* new_contents;
281     int insert_pos;
282
283     curs_on();
284
285     // account for multiline items
286     insert_pos = menu->scroll_offset;
287     for (int i = 0; i < insert_index; i++) {
288         insert_pos += menu->item_height(menu->menu_items[i]);
289     }
290
291     /* move cursor to right spot */
292     ungetstr(menu->menu_items[insert_index]->title);
293     mvwgetnstr(menu->menu_win,
294         insert_pos, 
295         0,
296         temp,
297         MAX_CONTENTS_LENGTH
298     );
299     curs_off();
300
301     /* copy out */
302     new_contents = strdup(temp);
303     menu->menu_items[insert_index]->title = new_contents;
304
305     /* delete if empty - maybe move this to a cleanup stage */
306     if (strlen(new_contents) == 0) {
307         delete_item(menu, insert_index);
308     }
309
310     return 0;
311 }
312
313 int
314 menu_driver(Menu* menu, MenuAction action)
315 {
316
317     switch (action) {
318         case MENU_UP:
319             menu->selected_item = menu->selected_item-1 >= 0 ? menu->selected_item-1 : 0;
320             break;
321
322         case MENU_DOWN:
323             menu->selected_item = menu->selected_item+1 <= menu->menu_length-1 ? menu->selected_item+1 : menu->menu_length-1;
324             break;
325
326         case MENU_TOP:
327             menu->selected_item = 0;
328             break;
329
330         case MENU_BOTTOM:
331             menu->selected_item = menu->menu_length-1;
332             break;
333
334         case MENU_MOVE_UP:
335             if (menu->selected_item <= 0) break;
336             swap_item(menu, menu->selected_item, menu->selected_item-1);
337             menu->selected_item -= 1;
338             break;
339
340         case MENU_MOVE_DOWN:
341             if (menu->selected_item >= menu->menu_length-1) break;
342             swap_item(menu, menu->selected_item, menu->selected_item+1);
343             menu->selected_item += 1;
344             break;
345
346         case MENU_DELETE:
347             delete_item(menu, menu->selected_item);
348             break;
349
350         case MENU_APPEND:
351             insert_item(menu, create_blank_menuitem(), menu->menu_length);
352             render_menu(menu); // refresh after inserting
353             menu_insert_mode(menu, menu->selected_item);
354             break;
355
356         case MENU_INSERT_ABOVE:
357             insert_item(menu, create_blank_menuitem(), menu->selected_item);
358             render_menu(menu);
359             menu_insert_mode(menu, menu->selected_item);
360             break;
361
362         case MENU_INSERT_BELOW:
363             insert_item(menu, create_blank_menuitem(), menu->selected_item+1);
364             render_menu(menu);
365             menu_insert_mode(menu, menu->selected_item); // inserted item is cur now
366             break;
367
368         case MENU_EDIT:
369             menu_insert_mode(menu, menu->selected_item);
370             break;
371
372         default: // This is here for debug, disable later
373             fprintf(stderr, "Invalid menu action");
374     }
375
376     return 0;
377 }
378
379 int
380 render_menu(Menu* menu)
381 {
382     wclear(menu->menu_win);
383
384     /* calculate scroll */
385     int visible;
386
387     visible = items_visible(menu, menu->scroll_offset);
388
389     if (menu->selected_item >= menu->scroll_offset+visible) {
390         menu->scroll_offset = clamp(
391             menu->selected_item-items_visible_rev(menu, menu->selected_item)+1,
392             0,
393             floorzero(menu->menu_length-1)
394         );
395
396     } else if (menu->selected_item < menu->scroll_offset) {
397         menu->scroll_offset = clamp(
398             menu->selected_item,
399             0,
400             floorzero(menu->menu_length-1)
401         );
402     }
403
404     /* render menu items */
405     int curline = 0;
406     for (int i = menu->scroll_offset; i < menu->menu_length; i++) {
407         menu->render_item(menu, i, curline);
408         curline += menu->item_height(menu->menu_items[i]);
409     }
410
411     wrefresh(menu->menu_win);
412
413     return 0;
414 }
415
416 void
417 default_render_item(Menu* menu, int item_index, int start_y)
418 {
419     MenuItem* curitem;
420     int hlcolor;
421     curitem = menu->menu_items[item_index];
422
423     /* color selected item */
424     hlcolor = COLOR_PAIR((item_index == menu->selected_item && menu->focused == true) ? TS_SELECTED : TS_NONSELECTED);
425     wattron(menu->menu_win, hlcolor);
426     mvwprintw(menu->menu_win, start_y, 0, curitem->title);
427     wattroff(menu->menu_win, hlcolor);
428
429 }
430
431 /* int */
432 /* default_render_item(Menu* menu, int item_index, int start_y) */
433 /* { */
434 /*     MenuItem* curitem; */
435 /*     int hlcolor; */
436 /*     curitem = menu->menu_items[item_index]; */
437
438 /*     /1* color selected item *1/ */
439 /*     hlcolor = COLOR_PAIR((item_index == menu->selected_item && menu->focused == true) ? TS_SELECTED : TS_NONSELECTED); */
440 /*     wattron(menu->menu_win, hlcolor); */
441 /*     mvwprintw(menu->menu_win, start_y, 0, curitem->title); */
442 /*     wattroff(menu->menu_win, hlcolor); */
443
444 /*     /1* display number of items *1/ */
445 /*     if (strlen(curitem->description) > 0) { */
446 /*         wattron(menu->menu_win, COLOR_PAIR(TS_ITEMCOUNT)); */
447 /*         mvwprintw(menu->menu_win, start_y+1, 0, curitem->description); */ 
448 /*         wattroff(menu->menu_win, COLOR_PAIR(TS_ITEMCOUNT)); */
449 /*     } */
450
451 /*     return item_height(curitem); */
452 /* } */
453
454 int
455 default_item_height(MenuItem* menuitem)
456
457     return 1; // maybe account for line wrap by default
458 }
459
460 /* int */
461 /* item_height(MenuItem* menuitem) */
462 /* { */
463 /*     int lines; */
464
465 /*     lines = 1; */
466 /*     if (strlen(menuitem->description) > 0) { */
467 /*         lines += 1; */
468 /*     } */
469
470 /*     return lines; */
471 /* } */
472
473 int
474 items_visible(Menu* menu, int offset)
475 {
476     int maxheight;
477     int maxwidth; // unused
478
479     getmaxyx(menu->menu_win, maxheight, maxwidth);
480
481     int vis = 0;
482     int lines = 0;
483     for (int i = offset ; i < menu->menu_length; i++) {
484
485         lines += menu->item_height(menu->menu_items[i]);
486         if (lines > maxheight) break;
487         vis += 1;
488
489     }
490
491     return vis;
492 }
493
494 int
495 items_visible_rev(Menu* menu, int offset)
496 {
497     int maxheight;
498     int maxwidth; // unused
499
500     getmaxyx(menu->menu_win, maxheight, maxwidth);
501
502     int vis = 0;
503     int lines = 0;
504     for (int i = offset; i > 0; i--) {
505
506         lines += menu->item_height(menu->menu_items[i]);
507         if (lines > maxheight) break;
508         vis +=1;
509
510     }
511
512     return vis;
513 }
514
515 int
516 free_menu(Menu* menu)
517 {
518     return 0;
519 }
520