subtask indicator in popup menu
[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     int hlcolor;
151
152     curitem = get_menu_item(menu, item_index);
153     menu_win = get_menu_win(menu);
154
155     /* color selected item */
156     hlcolor = COLOR_PAIR((item_index == get_selected_item(menu) && get_menu_focused(menu) == true) ? TS_SELECTED : TS_NONSELECTED);
157     wattron(menu_win, hlcolor);
158     mvwprintw(menu_win, start_y, 0, get_menuitem_title(curitem));
159     wattroff(menu_win, hlcolor);
160
161     /* display number of items */
162     if (strlen(get_menuitem_descrip(curitem)) > 0) {
163         wattron(menu_win, COLOR_PAIR(TS_ITEMCOUNT));
164         mvwprintw(menu_win, start_y+1, 0, get_menuitem_descrip(curitem)); 
165         wattroff(menu_win, COLOR_PAIR(TS_ITEMCOUNT));
166     }
167
168 }
169
170 int
171 menuitem_height(MenuItem* menuitem)
172 {
173     int lines;
174
175     lines = 1;
176     if (strlen(get_menuitem_descrip(menuitem)) > 0) {
177         lines += 1;
178     }
179
180     return lines;
181 }
182
183 MenuItem** 
184 todolist_to_menuitem(TodoItem** item_list, int list_length)
185 {
186     MenuItem** items;
187
188     items = malloc((list_length+1)*sizeof(MenuItem*));
189     for (int i = 0; i < list_length; i++) {
190         MenuItem* new_menuitem;
191         new_menuitem =  create_menuitem(item_list[i]->item_name);
192         /* using same struct, careful if it gets freed */
193         set_menuitem_userdata(new_menuitem, item_list[i]);
194
195         items[i] = new_menuitem;
196     }
197
198     items[list_length] = 0; //null terminate
199     return items;
200 }
201
202 Board*
203 boardmenu_to_board(BoardMenu* boardmenu)
204 { // STRINGS are sharing the same address as the one in MENU
205   // and MENUITEM, this may break something if u free this board
206   // consider copying the string
207
208     Board* newboard = malloc(sizeof(Board));
209     TodoList** new_todolist_list = malloc(sizeof(TodoList*));
210
211     for (int i = 0; i < boardmenu->menu_count; i++) {
212         Menu* curmenu = boardmenu->menu_list[i];
213
214         TodoList* new_todolist = malloc(sizeof(TodoList));
215         TodoItem** new_item_list = malloc(sizeof(TodoItem*));
216         new_todolist->list_name = strdup(get_menu_name(curmenu));
217         new_todolist->item_count = get_menu_length(curmenu);
218
219         for (int j = 0; j < get_menu_length(curmenu); j++) {
220             MenuItem* curmenuitem = get_menu_item(curmenu, j);
221             TodoItem* itemdata = get_menuitem_userdata(curmenuitem);
222
223             TodoItem* new_todoitem = malloc(sizeof(TodoItem));
224             SubTask** new_subtask_list = malloc(itemdata->subtask_count*sizeof(SubTask*));
225
226             new_todoitem->item_name = strdup(get_menuitem_title(curmenuitem));
227             new_todoitem->description = strdup(itemdata->description);
228             new_todoitem->due = strdup(itemdata->due);
229             new_todoitem->subtask_count = itemdata->subtask_count;
230             for (int k = 0; k < itemdata->subtask_count; k++) {
231                 SubTask* new_subtask = malloc(sizeof(SubTask));
232
233                 new_subtask->subtask_name = itemdata->subtask_list[k]->subtask_name;
234                 new_subtask->done = itemdata->subtask_list[k]->done;
235
236                 new_subtask_list[k] = new_subtask;
237             }
238
239             new_todoitem->subtask_list = new_subtask_list;
240
241             new_item_list[j] = new_todoitem;
242         }
243
244         new_todolist->item_list = new_item_list;
245         new_todolist_list[i] = new_todolist;
246
247     }
248
249     newboard->board_name = strdup("");
250     newboard->todolist_list = new_todolist_list;
251     newboard->todolist_count = boardmenu->menu_count;
252
253     return newboard;
254 }
255
256 int
257 render_board(Board* board)
258 {
259
260     return 0;
261 }
262
263 int
264 set_selected_menu(BoardMenu* boardmenu, int index)
265 {
266     Menu* old_menu;
267     Menu* new_menu;
268     int new_pos;
269     
270     old_menu = boardmenu->menu_list[boardmenu->selected];
271     new_menu = boardmenu->menu_list[index];
272
273     set_menu_focus(old_menu, false);
274     set_menu_focus(new_menu, true);
275
276     /* also try to jump to a similar position if possible */
277     /* rn theres a bug if old menu is empty */
278     new_pos = min(get_selected_item(old_menu), get_menu_length(new_menu)-1);
279     if (new_pos < 0) new_pos = 0;
280     set_selected_item(new_menu, new_pos);
281
282     boardmenu->selected = index;
283
284     return 0;
285 }
286
287 int
288 swap_menu(BoardMenu* boardmenu, int src_index, int dest_index)
289 {
290     /* reposition menus */
291     mvwin(get_menu_win(boardmenu->menu_list[src_index]),
292         1, 1+MENU_WIDTH*dest_index
293     );
294     mvwin(get_menu_win(boardmenu->menu_list[dest_index]),
295         1, 1+MENU_WIDTH*src_index
296     );
297     refresh();
298     wrefresh(get_menu_win(boardmenu->menu_list[src_index]));
299     wrefresh(get_menu_win(boardmenu->menu_list[dest_index]));
300     /* wclear(get_menu_win(boardmenu->menu_list[src_index])); */
301     /* wclear(get_menu_win(boardmenu->menu_list[dest_index])); */
302     /* touchwin(get_menu_win(boardmenu->menu_list[src_index])); */
303     /* touchwin(get_menu_win(boardmenu->menu_list[dest_index])); */
304     clear();
305
306     /* swap in array */
307     ar_swap_item((void*)boardmenu->menu_list, src_index, dest_index);
308
309     return 0;
310 }
311
312 /* menuitem */
313 int
314 update_menuitem_descrip(MenuItem* menuitem)
315 { /* need to do something about colored text */
316
317     TodoItem* item_data;    
318     char* new_descrip;
319
320     item_data = (TodoItem*)get_menuitem_userdata(menuitem);
321     new_descrip = strdup("");
322
323     if (strlen(item_data->description) > 0) {
324         /* strcat(new_descrip, "☰ "); */
325         strcat(new_descrip, "# ");
326     }
327     if (strlen(item_data->due) > 0) {
328         strcat(new_descrip, item_data->due);
329         strcat(new_descrip, " ");
330     }
331     if (item_data->subtask_count > 0) {
332
333         int tasks_complete = 0;
334         for (int i = 0; i < item_data->subtask_count; i++) {
335             if (item_data->subtask_list[i]->done == SubTaskState_done) {
336                 tasks_complete += 1;
337             }
338         }
339
340         /* [, # done, /, # total, ], null */
341         char subtask_done[4]; // assume there wont be more than 999 subtasks (possibly danger?)
342         snprintf(subtask_done, 4, "%d", tasks_complete);
343         int substask_len = 1+item_data->subtask_count+1+strlen(subtask_done)+1+1;
344         char subtask_text[substask_len];
345         sprintf(subtask_text, "[%s/%d]", subtask_done, item_data->subtask_count);
346         strcat(new_descrip, subtask_text);
347     }
348
349     /* free old string */
350     if (strlen(new_descrip) > 0) {
351         free(get_menuitem_descrip(menuitem));
352         set_menuitem_descrip(menuitem, new_descrip); 
353     }
354
355     return 0;
356 }
357
358 /* popup */
359 PopupMenu*
360 make_popupmenu(TodoItem* itemdata)
361 {
362     PopupMenu* new_popupmenu;
363     MenuItem** subtask_menuitems;
364     Menu* popupmenu_menu;
365     WINDOW* popupmenu_win;
366     WINDOW* popupmenu_menu_win;
367
368     new_popupmenu = malloc(sizeof(PopupMenu));
369
370     subtask_menuitems = subtasklist_to_menuitem(itemdata->subtask_list, itemdata->subtask_count);
371     popupmenu_menu = create_menu(strdup(""), subtask_menuitems);
372
373     /* popup win */
374     int maxheight, maxwidth;
375     getmaxyx(stdscr, maxheight, maxwidth);
376     popupmenu_win = newwin(
377         maxheight-2*POPUP_BORDER,
378         maxwidth-2*2*POPUP_BORDER,
379         POPUP_BORDER,
380         POPUP_BORDER*2
381     );
382
383     int popup_maxheight, popup_maxwidth;
384     getmaxyx(popupmenu_win, popup_maxheight, popup_maxwidth);
385     popupmenu_menu_win = derwin(
386         popupmenu_win,
387         popup_maxheight-POPUP_MENU_PAD_TOP-POPUP_MENU_PAD_BOTTOM,
388         popup_maxwidth-POPUP_MENU_PAD_LEFT-POPUP_MENU_PAD_RIGHT,
389         POPUP_MENU_PAD_TOP,
390         POPUP_MENU_PAD_LEFT
391     );
392
393     set_menu_win(popupmenu_menu, popupmenu_menu_win);
394     set_menu_focus(popupmenu_menu, 1);
395     set_menu_renderitem(popupmenu_menu, render_popup_menuitem);
396     set_menu_itemheight(popupmenu_menu, popup_menuitem_height);
397     box(popupmenu_win, 0, 0);
398
399     /* move this stuff to render phase later? */
400     mvwprintw(popupmenu_win, 1, 2, itemdata->item_name);
401     mvwprintw(popupmenu_win, 2, 2, (strlen(itemdata->description) > 0) ? itemdata->description : "no description");
402
403     /* don't forget to free popupmenu after */
404     new_popupmenu->win = popupmenu_win;
405     new_popupmenu->menu = popupmenu_menu;
406
407     return new_popupmenu;
408 }
409
410 void
411 render_popup_menuitem(Menu* menu, int item_index, int start_y)
412 {
413     MenuItem* curitem;
414     WINDOW* menu_win;
415     int hlcolor;
416
417     curitem = get_menu_item(menu, item_index);
418     menu_win = get_menu_win(menu);
419
420     /* color selected item */
421     hlcolor = COLOR_PAIR((item_index == get_selected_item(menu) && get_menu_focused(menu) == true) ? TS_SELECTED : TS_NONSELECTED);
422     wattron(menu_win, hlcolor);
423
424     wmove(menu_win, start_y, 0);
425     /* print subtask done indicator */
426     if (strlen(get_menuitem_title(curitem)) > 0)
427         wprintw(
428             menu_win,
429             (((SubTask*)get_menuitem_userdata(curitem))->done == SubTaskState_done) ? "[X] " : "[ ] "
430         );
431     wprintw(menu_win, get_menuitem_title(curitem));
432
433     wattroff(menu_win, hlcolor);
434 }
435
436 int
437 popup_menuitem_height(MenuItem* menuitem)
438 {
439     return 1; // account for wrap later
440 }
441
442 int
443 render_popupmenu(PopupMenu* popupmenu)
444 {
445     render_menu(popupmenu->menu);
446     wrefresh(popupmenu->win);
447
448     return 0;
449 }
450
451 /* this is copy paste of other, prob abstract */
452 MenuItem**
453 subtasklist_to_menuitem(SubTask** subtask_list, int list_length)
454 {
455     MenuItem** items;
456
457     items = malloc((list_length+1)*sizeof(MenuItem*));
458     for (int i = 0; i < list_length; i++) {
459         MenuItem* new_menuitem;
460         new_menuitem =  create_menuitem(subtask_list[i]->subtask_name);
461         /* using same struct, careful if it gets freed */
462         set_menuitem_userdata(new_menuitem, subtask_list[i]);
463
464         items[i] = new_menuitem;
465     }
466
467     items[list_length] = 0; //null terminate
468     return items;
469 }
470
471 /* helpers */
472 int
473 ungetstr(char* str)
474 {
475     // ignore null character (it's fine even if strlen = 0)
476     for (int i = strlen(str)-1; i >= 0; i--) {
477         ungetch(str[i]);
478     }
479
480     return 0; 
481 }
482