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