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