crude scrolling
[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
277     curs_on();
278
279     /* move cursor to right spot */
280     ungetstr(menu->menu_items[insert_index]->title);
281     mvwgetnstr(menu->sub_win,
282         insert_index, // account for wrap later too
283         0,
284         temp,
285         MAX_CONTENTS_LENGTH
286     );
287     curs_off();
288
289     /* copy out */
290     new_contents = strdup(temp);
291     menu->menu_items[insert_index]->title = new_contents;
292
293     /* delete if empty - maybe move this to a cleanup stage */
294     if (strlen(new_contents) == 0) {
295         delete_item(menu, insert_index);
296     }
297
298     return 0;
299 }
300
301 int
302 menu_driver(Menu* menu, MenuAction action)
303 {
304
305     switch (action) {
306         case MENU_UP:
307             menu->selected_item = menu->selected_item-1 >= 0 ? menu->selected_item-1 : 0;
308             break;
309
310         case MENU_DOWN:
311             menu->selected_item = menu->selected_item+1 <= menu->menu_length-1 ? menu->selected_item+1 : menu->menu_length-1;
312             break;
313
314         case MENU_TOP:
315             menu->selected_item = 0;
316             break;
317
318         case MENU_BOTTOM:
319             menu->selected_item = menu->menu_length-1;
320             break;
321
322         case MENU_MOVE_UP:
323             if (menu->selected_item <= 0) break;
324             swap_item(menu, menu->selected_item, menu->selected_item-1);
325             menu->selected_item -= 1;
326             break;
327
328         case MENU_MOVE_DOWN:
329             if (menu->selected_item >= menu->menu_length-1) break;
330             swap_item(menu, menu->selected_item, menu->selected_item+1);
331             menu->selected_item += 1;
332             break;
333
334         case MENU_DELETE:
335             delete_item(menu, menu->selected_item);
336             break;
337
338         case MENU_APPEND:
339             insert_item(menu, create_blank_menuitem(), menu->menu_length);
340             menu_insert_mode(menu, menu->selected_item);
341             break;
342
343         case MENU_INSERT_ABOVE:
344             insert_item(menu, create_blank_menuitem(), menu->selected_item);
345             menu_insert_mode(menu, menu->selected_item);
346             break;
347
348         case MENU_INSERT_BELOW:
349             insert_item(menu, create_blank_menuitem(), menu->selected_item+1);
350             menu_insert_mode(menu, menu->selected_item); // inserted item is cur now
351             break;
352
353         case MENU_EDIT:
354             menu_insert_mode(menu, menu->selected_item);
355             break;
356
357         default: // This is here for debug, disable later
358             fprintf(stderr, "Invalid menu action");
359     }
360
361     return 0;
362 }
363
364 int
365 render_menu(Menu* menu)
366 {
367     /* draw outer menu (prob dont need this every render) */ 
368     /* wclear(menu->menu_win); */
369     wattron(menu->menu_win, COLOR_PAIR(
370         (menu->focused == true) ?
371         TS_MENU_SELECTED: TS_MENU_NONSELECTED       
372     ));
373     mvwprintw(menu->menu_win, 0, MENU_PAD_LEFT, menu->menu_name);
374     wattroff(menu->menu_win, COLOR_PAIR(0));
375
376     /* char buf[20]; */
377     /* sprintf(buf, "%d", items_visible(menu)); */
378     /* mvprintw(20, 20, buf); */
379
380     /* calculate scroll */
381     int visible;
382
383     visible = items_visible(menu);
384
385     if (menu->selected_item > menu->scroll_offset+visible) {
386         // may be dangerous, assumes render after every action
387         menu->scroll_offset += 1;
388     } else if (menu->selected_item < menu->scroll_offset) {
389         menu->scroll_offset -= 1;
390     }
391
392     /* draw inner menu */
393     wclear(menu->sub_win);
394
395     int curline = 0;
396     for (int i = menu->scroll_offset; i < menu->menu_length; i++) {
397         curline += render_item(menu, i, curline);
398     }
399
400     wrefresh(menu->sub_win);
401     wrefresh(menu->menu_win);
402
403     return 0;
404 }
405
406 int
407 render_item(Menu* menu, int item_index, int start_y)
408 {
409     MenuItem* curitem;
410     curitem = menu->menu_items[item_index];
411
412     /* color selected item */
413     wattron(menu->sub_win, COLOR_PAIR(
414        (item_index == menu->selected_item && menu->focused == true) ? 
415        TS_SELECTED : TS_NONSELECTED
416     ));
417     mvwprintw(menu->sub_win, start_y, 0, curitem->title);
418     wattroff(menu->sub_win, COLOR_PAIR(0));
419
420     /* display number of items */
421     if (strlen(curitem->description) > 0) {
422         mvwprintw(menu->sub_win, start_y+1, 0, curitem->description); 
423     }
424
425     return item_height(curitem);
426 }
427
428 int
429 item_height(MenuItem* menuitem)
430 {
431     int lines;
432
433     lines = 1;
434     if (strlen(menuitem->description) > 0) {
435         lines += 1;
436     }
437
438     return lines;
439 }
440
441 int
442 items_visible(Menu* menu)
443 {
444     int maxheight;
445     int maxwidth; // unused
446
447     getmaxyx(menu->sub_win, maxheight, maxwidth);
448
449     int i = menu->scroll_offset;
450     int lines = 0;
451     for (; i < menu->menu_length; i++) {
452
453         lines += item_height(menu->menu_items[i]);
454
455         if (lines >= maxheight) break;
456
457     }
458
459     return i;
460 }
461
462 int
463 free_menu(Menu* menu)
464 {
465     return 0;
466 }
467