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