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