changes in popup menu is now preserved
[taskasaur.git] / render.c
1
2 #include "headers/render.h"
3 #include <string.h>
4 #include "config.h"
5
6 #define POPUP_MENU_PAD_TOP 4
7 #define POPUP_MENU_PAD_BOTTOM 2
8 #define POPUP_MENU_PAD_LEFT 2
9 #define POPUP_MENU_PAD_RIGHT 1
10
11 int init_tscolors(void);
12 int create_todowin(void);
13
14 /* menu render callbacks */
15 void render_menuitem(Menu* menu, int item_index, int start_y);
16 int menuitem_height(MenuItem* menuitem);
17
18 void render_popup_menuitem(Menu* menu, int item_index, int start_y);
19 int popup_menuitem_height(MenuItem* menuitem);
20
21
22 /* init stuff */
23 int
24 init_tscurses(void)
25 {
26     initscr();
27     cbreak();
28     curs_off();
29     keypad(stdscr, TRUE);
30
31     /* need to error check this */
32     if (has_colors() == FALSE) {
33         fprintf(stderr, "Your terminal does not support color.\n");
34
35         /* maybe just return 1 */
36         /* exit_tscurses(); */
37         /* exit(1); */
38         return 1;
39     }
40     start_color();
41     init_tscolors();
42
43     return 0;
44 }
45
46 int
47 exit_tscurses(void)
48 {
49     endwin();
50
51     return 0;
52 }
53
54 int
55 init_tscolors(void)
56 {
57     init_pair(TS_SELECTED, selected_color, COLOR_BLACK);
58     init_pair(TS_NONSELECTED, non_selected_color, COLOR_BLACK);
59     init_pair(TS_MENU_SELECTED, menu_selected_color, COLOR_BLACK);
60     init_pair(TS_MENU_NONSELECTED, menu_non_selected_color, COLOR_BLACK);
61     init_pair(TS_ITEMCOUNT, item_count_color, COLOR_BLACK);
62
63     return 0;
64 }
65
66 /* cursor */
67 int
68 curs_on(void)
69 {
70     echo();
71     curs_set(1);
72     return 0;
73 }
74
75 int
76 curs_off(void)
77 {
78     noecho();
79     curs_set(0);
80     return 0;
81 }
82
83 /* wins */
84 int
85 create_todowin(void)
86 {
87     return 0;
88 }
89
90 /* board menu */
91 BoardMenu*
92 create_board_menu(Board* board)
93 {
94     BoardMenu* new_boardmenu;
95
96     new_boardmenu = malloc(sizeof(BoardMenu));
97
98     new_boardmenu->menu_list = make_menus(board, board->todolist_count);
99     new_boardmenu->menu_count = board->todolist_count;
100     new_boardmenu->selected = 0;
101     new_boardmenu->popupmenu = NULL;
102     new_boardmenu->popup_open = 0;
103
104     return new_boardmenu;
105 }
106
107 Menu**
108 make_menus(Board* board, int todolist_length)
109 {
110
111     Menu** menu_list;
112
113     menu_list = malloc(todolist_length*sizeof(Menu*));
114
115     for (int i = 0; i < todolist_length; i++) {
116
117         /* read from parsed */
118         TodoList* todo_list = board->todolist_list[i];
119         MenuItem** item_list = todolist_to_menuitem(todo_list->item_list, todo_list->item_count);
120
121         Menu* new_menu = create_menu(todo_list->list_name, item_list);
122
123         /* make window */
124         WINDOW* win = newwin(20, MENU_WIDTH, 1, 1+MENU_WIDTH*i);
125         box(win, 0, 0);
126
127         /* some menu settings */
128         set_menu_win(new_menu, win);
129         set_menu_focus(new_menu, i == 0); // make first win focused
130
131         /* set menu render callbacks */
132         set_menu_renderitem(new_menu, *render_menuitem);
133         set_menu_itemheight(new_menu, *menuitem_height);
134
135         /* refresh */
136         refresh();
137         wrefresh(win);
138
139         menu_list[i] = new_menu;
140     }
141
142     return menu_list;
143 }
144
145 void
146 render_menuitem(Menu* menu, int item_index, int start_y)
147 {
148     MenuItem* curitem;
149     WINDOW* menu_win;
150     TodoItem* item_data;
151     int hlcolor;
152
153     curitem = get_menu_item(menu, item_index);
154     menu_win = get_menu_win(menu);
155     item_data = (TodoItem*)get_menuitem_userdata(curitem);
156
157     /* color selected item */
158     hlcolor = COLOR_PAIR((item_index == get_selected_item(menu) && get_menu_focused(menu) == true) ? TS_SELECTED : TS_NONSELECTED);
159     wattron(menu_win, hlcolor);
160     mvwprintw(menu_win, start_y, 0, get_menuitem_title(curitem));
161     wattroff(menu_win, hlcolor);
162
163     /* item tag line */
164     wmove(menu_win, start_y+1, 0);
165     wattron(menu_win, COLOR_PAIR(TS_ITEMCOUNT));
166     /* display tiny character to indicate item has a descrip */
167     if (strlen(item_data->description) > 0) {
168         wprintw(menu_win, "# "); 
169     }
170     /* display number of complete tasks */
171     if (item_data->subtask_count > 0) {
172         int tasks_complete = 0;
173         for (int i = 0; i < item_data->subtask_count; i++) {
174             if (item_data->subtask_list[i]->done == SubTaskState_done)
175                 tasks_complete += 1;
176         }
177         wprintw(menu_win, "[%d/%d]", tasks_complete, item_data->subtask_count);
178     }
179     wattroff(menu_win, COLOR_PAIR(TS_ITEMCOUNT));
180 }
181
182 int
183 menuitem_height(MenuItem* menuitem)
184 {
185     TodoItem* item_data;
186     int lines;
187
188     item_data = get_menuitem_userdata(menuitem);
189     lines = 1;
190     if (item_data->subtask_count > 0 || strlen(item_data->description) > 0) {
191         lines += 1;
192     }
193
194     return lines;
195 }
196
197 MenuItem** 
198 todolist_to_menuitem(TodoItem** item_list, int list_length)
199 {
200     MenuItem** items;
201
202     items = malloc((list_length+1)*sizeof(MenuItem*));
203     for (int i = 0; i < list_length; i++) {
204         MenuItem* new_menuitem;
205         new_menuitem =  create_menuitem(item_list[i]->item_name);
206         /* using same struct, careful if it gets freed */
207         set_menuitem_userdata(new_menuitem, item_list[i]);
208
209         items[i] = new_menuitem;
210     }
211
212     items[list_length] = 0; //null terminate
213     return items;
214 }
215
216 Board*
217 boardmenu_to_board(BoardMenu* boardmenu)
218 { // STRINGS are sharing the same address as the one in MENU
219   // and MENUITEM, this may break something if u free this board
220   // consider copying the string
221
222     Board* newboard = malloc(sizeof(Board));
223     TodoList** new_todolist_list = malloc(sizeof(TodoList*));
224
225     for (int i = 0; i < boardmenu->menu_count; i++) {
226         Menu* curmenu = boardmenu->menu_list[i];
227
228         TodoList* new_todolist = malloc(sizeof(TodoList));
229         TodoItem** new_item_list = malloc(sizeof(TodoItem*));
230         new_todolist->list_name = strdup(get_menu_name(curmenu));
231         new_todolist->item_count = get_menu_length(curmenu);
232
233         for (int j = 0; j < get_menu_length(curmenu); j++) {
234             MenuItem* curmenuitem = get_menu_item(curmenu, j);
235             TodoItem* itemdata = get_menuitem_userdata(curmenuitem);
236
237             TodoItem* new_todoitem = malloc(sizeof(TodoItem));
238             SubTask** new_subtask_list = malloc(itemdata->subtask_count*sizeof(SubTask*));
239
240             new_todoitem->item_name = strdup(get_menuitem_title(curmenuitem));
241             new_todoitem->description = strdup(itemdata->description);
242             new_todoitem->due = strdup(itemdata->due);
243             new_todoitem->subtask_count = itemdata->subtask_count;
244             for (int k = 0; k < itemdata->subtask_count; k++) {
245                 SubTask* new_subtask = malloc(sizeof(SubTask));
246
247                 new_subtask->subtask_name = itemdata->subtask_list[k]->subtask_name;
248                 new_subtask->done = itemdata->subtask_list[k]->done;
249
250                 new_subtask_list[k] = new_subtask;
251             }
252
253             new_todoitem->subtask_list = new_subtask_list;
254
255             new_item_list[j] = new_todoitem;
256         }
257
258         new_todolist->item_list = new_item_list;
259         new_todolist_list[i] = new_todolist;
260
261     }
262
263     newboard->board_name = strdup("");
264     newboard->todolist_list = new_todolist_list;
265     newboard->todolist_count = boardmenu->menu_count;
266
267     return newboard;
268 }
269
270 int
271 render_board(Board* board)
272 {
273
274     return 0;
275 }
276
277 int
278 set_selected_menu(BoardMenu* boardmenu, int index)
279 {
280     Menu* old_menu;
281     Menu* new_menu;
282     int new_pos;
283     
284     old_menu = boardmenu->menu_list[boardmenu->selected];
285     new_menu = boardmenu->menu_list[index];
286
287     set_menu_focus(old_menu, false);
288     set_menu_focus(new_menu, true);
289
290     /* also try to jump to a similar position if possible */
291     /* rn theres a bug if old menu is empty */
292     new_pos = min(get_selected_item(old_menu), get_menu_length(new_menu)-1);
293     if (new_pos < 0) new_pos = 0;
294     set_selected_item(new_menu, new_pos);
295
296     boardmenu->selected = index;
297
298     return 0;
299 }
300
301 int
302 swap_menu(BoardMenu* boardmenu, int src_index, int dest_index)
303 {
304     /* reposition menus */
305     mvwin(get_menu_win(boardmenu->menu_list[src_index]),
306         1, 1+MENU_WIDTH*dest_index
307     );
308     mvwin(get_menu_win(boardmenu->menu_list[dest_index]),
309         1, 1+MENU_WIDTH*src_index
310     );
311     refresh();
312     wrefresh(get_menu_win(boardmenu->menu_list[src_index]));
313     wrefresh(get_menu_win(boardmenu->menu_list[dest_index]));
314     /* wclear(get_menu_win(boardmenu->menu_list[src_index])); */
315     /* wclear(get_menu_win(boardmenu->menu_list[dest_index])); */
316     /* touchwin(get_menu_win(boardmenu->menu_list[src_index])); */
317     /* touchwin(get_menu_win(boardmenu->menu_list[dest_index])); */
318     clear();
319
320     /* swap in array */
321     ar_swap_item((void*)boardmenu->menu_list, src_index, dest_index);
322
323     return 0;
324 }
325
326 /* popup */
327 PopupMenu*
328 make_popupmenu(TodoItem* itemdata)
329 {
330     PopupMenu* new_popupmenu;
331     MenuItem** subtask_menuitems;
332     Menu* popupmenu_menu;
333     WINDOW* popupmenu_win;
334     WINDOW* popupmenu_menu_win;
335
336     new_popupmenu = malloc(sizeof(PopupMenu));
337
338     subtask_menuitems = subtasklist_to_menuitem(itemdata->subtask_list, itemdata->subtask_count);
339     popupmenu_menu = create_menu(strdup(""), subtask_menuitems);
340
341     /* popup win */
342     int maxheight, maxwidth;
343     getmaxyx(stdscr, maxheight, maxwidth);
344     popupmenu_win = newwin(
345         maxheight-2*POPUP_BORDER,
346         maxwidth-2*2*POPUP_BORDER,
347         POPUP_BORDER,
348         POPUP_BORDER*2
349     );
350
351     int popup_maxheight, popup_maxwidth;
352     getmaxyx(popupmenu_win, popup_maxheight, popup_maxwidth);
353     popupmenu_menu_win = derwin(
354         popupmenu_win,
355         popup_maxheight-POPUP_MENU_PAD_TOP-POPUP_MENU_PAD_BOTTOM,
356         popup_maxwidth-POPUP_MENU_PAD_LEFT-POPUP_MENU_PAD_RIGHT,
357         POPUP_MENU_PAD_TOP,
358         POPUP_MENU_PAD_LEFT
359     );
360
361     set_menu_win(popupmenu_menu, popupmenu_menu_win);
362     set_menu_userdata(popupmenu_menu, itemdata);
363     set_menu_focus(popupmenu_menu, 1);
364     set_menu_renderitem(popupmenu_menu, render_popup_menuitem);
365     set_menu_itemheight(popupmenu_menu, popup_menuitem_height);
366     box(popupmenu_win, 0, 0);
367
368     /* move this stuff to render phase later? */
369     mvwprintw(popupmenu_win, 1, 2, itemdata->item_name);
370     mvwprintw(popupmenu_win, 2, 2, (strlen(itemdata->description) > 0) ? itemdata->description : "no description");
371
372     /* don't forget to free popupmenu after */
373     new_popupmenu->win = popupmenu_win;
374     new_popupmenu->menu = popupmenu_menu;
375
376     return new_popupmenu;
377 }
378
379 void
380 render_popup_menuitem(Menu* menu, int item_index, int start_y)
381 {
382     MenuItem* curitem;
383     WINDOW* menu_win;
384     int hlcolor;
385
386     curitem = get_menu_item(menu, item_index);
387     menu_win = get_menu_win(menu);
388
389     /* color selected item */
390     hlcolor = COLOR_PAIR((item_index == get_selected_item(menu) && get_menu_focused(menu) == true) ? TS_SELECTED : TS_NONSELECTED);
391     wattron(menu_win, hlcolor);
392
393     wmove(menu_win, start_y, 0);
394     /* print subtask done indicator */
395     if (strlen(get_menuitem_title(curitem)) > 0)
396         wprintw(
397             menu_win,
398             (((SubTask*)get_menuitem_userdata(curitem))->done == SubTaskState_done) ? "[X] " : "[ ] "
399             /* (((TodoItem*)get_menu_userdata(menu))->subtask_list[item_index]->done == SubTaskState_done) ? "[X] " : "[ ] " */
400         );
401     wprintw(menu_win, get_menuitem_title(curitem));
402     /* wprintw(menu_win, ((TodoItem*)get_menu_userdata(menu))->subtask_list[item_index]->subtask_name); */
403
404     wattroff(menu_win, hlcolor);
405 }
406
407 int
408 popup_menuitem_height(MenuItem* menuitem)
409 {
410     return 1; // account for wrap later
411 }
412
413 /* save menu state to userdata */
414 /* sorta duct tape rn */
415 /* find a way to tie TodoItem and MenuItem together better in the future */
416 void
417 close_popupmenu(Menu* popupmenu_menu)
418 {
419     TodoItem* item_data;
420     SubTask** updated_subtasklist;
421
422     item_data = (TodoItem*)get_menu_userdata(popupmenu_menu);
423     updated_subtasklist = menuitem_to_subtasklist(
424         get_menu_itemlist(popupmenu_menu),
425         get_menu_length(popupmenu_menu)
426     );
427
428     /* remember to free old subtask list */
429     item_data->subtask_list = updated_subtasklist;
430     item_data->subtask_count = get_menu_length(popupmenu_menu);
431
432     /* this is messy but since subtask rendering uses menuitem title instead
433      * of userdata subtask title, this needs to be done :( */ 
434     /* for (int i = 0; i < get_menu_length(popupmenu_menu); i++) { */
435     /*     set_menuitem_title( */
436     /*         get_menu_item(popupmenu_menu, i), */
437     /*         strdup(item_data->subtask_list[i]->subtask_name) */
438     /*     ); */
439     /* } */
440 }
441
442 /* this is copy paste of other, prob abstract */
443 MenuItem**
444 subtasklist_to_menuitem(SubTask** subtask_list, int list_length)
445 {
446     MenuItem** items;
447
448     items = malloc((list_length+1)*sizeof(MenuItem*));
449     for (int i = 0; i < list_length; i++) {
450         MenuItem* new_menuitem;
451         new_menuitem =  create_menuitem(subtask_list[i]->subtask_name);
452         /* using same struct, careful if it gets freed */
453         set_menuitem_userdata(new_menuitem, subtask_list[i]);
454
455         items[i] = new_menuitem;
456     }
457
458     items[list_length] = 0; //null terminate
459     return items;
460 }
461
462 SubTask**
463 menuitem_to_subtasklist(MenuItem** menuitem_list, int list_length)
464 {
465     SubTask** new_subtasklist;
466
467     new_subtasklist = malloc(list_length*sizeof(SubTask*));
468
469     for (int i = 0; i < list_length; i++) {
470         SubTask* cur_subtask;
471         SubTask* new_subtask;
472
473         cur_subtask = (SubTask*)get_menuitem_userdata(menuitem_list[i]);
474         new_subtask = malloc(sizeof(SubTask));
475
476         new_subtask->subtask_name = strdup(cur_subtask->subtask_name);
477         new_subtask->done = cur_subtask->done;
478
479         new_subtasklist[i] = new_subtask;
480     }
481
482     /* new_subtasklist[list_length] = 0; */
483
484     return new_subtasklist;
485 }
486
487
488 /* helpers */
489 int
490 ungetstr(char* str)
491 {
492     // ignore null character (it's fine even if strlen = 0)
493     for (int i = strlen(str)-1; i >= 0; i--) {
494         ungetch(str[i]);
495     }
496
497     return 0; 
498 }
499