added cstack to viewer + implemented inline highlighting
[smdp.git] / viewer.c
1 #include <ncurses.h>
2 #include <stdlib.h>
3 #include <string.h> // strchr
4 #include <unistd.h>
5
6 #include "include/parser.h"
7 #include "include/viewer.h"
8
9 static short white_ramp[24] = { 16, 232, 233, 234, 235, 236,
10                                237, 238, 239, 240, 241, 242,
11                                244, 245, 246, 247, 248, 249,
12                                250, 251, 252, 253, 254, 255 };
13
14 static short blue_ramp[24]  = { 16,  17,  17,  18,  18,  19,
15                                 19,  20,  20,  21,  27,  32,
16                                 33,  38,  39,  44,  45,  45,
17                                 81,  81,  51,  51, 123, 123 };
18
19 static short red_ramp[24]   = { 16,  52,  52,  53,  53,  89,
20                                 89,  90,  90, 126, 127, 127,
21                                163, 163, 164, 164, 200, 200,
22                                201, 201, 207, 207, 213, 213 };
23
24 int ncurses_display(deck_t *deck, int notrans, int nofade) {
25
26     int c = 0;          // char
27     int l = 0;          // line number
28     int colors = 0;     // amount of colors supported
29     int fade = 0;       // disable color fading by default
30     int trans = -1;     // enable transparency if term supports it
31     int max_lines = 0;  // max lines per slide
32     int max_cols = 0;   // max columns per line
33     int offset;         // text offset
34
35     // header line 1 is displayed at the top
36     int bar_top = (deck->headers > 0) ? 1 : 0;
37     // header line 2 and 3 are displayed at the bottom
38     int bar_bottom = (deck->headers > 1) ? 1 : 0;
39
40     slide_t *slide = deck->slide;
41     line_t *line;
42
43     while(slide) {
44         // set max_lines if line count exceeded
45         max_lines = (slide->lines > max_lines) ? slide->lines : max_lines;
46         line = slide->line;
47         while(line) {
48             // set max_cols if length exceeded
49             max_cols = (line->length > max_cols) ? line->length : max_cols;
50             line = line->next;
51         }
52         slide = slide->next;
53     }
54
55     // replace stdin with current tty if markdown input was piped
56     freopen("/dev/tty", "rw", stdin);
57
58     // init ncurses
59     initscr();
60
61     if((max_cols > COLS) ||
62        (max_lines + bar_top + bar_bottom + 2 > LINES)) {
63
64         fprintf(stderr, "Error: Terminal size %ix%i to small. Need at least %ix%i.\n",
65             COLS, LINES, max_cols, max_lines + bar_top + bar_bottom + 2);
66         endwin();
67         return(1);
68     }
69
70     // replace stdin with current tty if markdown input was piped
71     freopen("/dev/tty", "rw", stdin);
72
73     // disable cursor
74     curs_set(0);
75
76     // disable output of keyboard typing
77     noecho();
78
79     // make getch() process one char at a time
80     cbreak();
81
82     // enable arrow keys
83     keypad(stdscr,TRUE);
84
85     // set colors
86     if(has_colors() == TRUE) {
87         start_color();
88         use_default_colors();
89
90         if(notrans) trans = 0; // 0 is black
91
92         if(COLORS == 256) {
93             // 256 color mode
94             init_pair(CP_WHITE, 255, trans);
95             init_pair(CP_BLUE, 123, trans);
96             init_pair(CP_RED, 213, trans);
97             init_pair(CP_YELLOW, 208, trans);
98
99             // enable color fading
100             if(!nofade) fade = 1;
101         } else {
102
103             // 8 color mode
104             init_pair(CP_WHITE, 7, trans);
105             init_pair(CP_BLUE, 4, trans);
106             init_pair(CP_RED, 1, trans);
107             init_pair(CP_YELLOW, 3, trans);
108         }
109
110         colors = 1;
111     }
112
113     // set background color of main window
114     if(colors)
115         wbkgd(stdscr, COLOR_PAIR(CP_YELLOW));
116
117     // setup header
118     if(bar_top) {
119         line = deck->header;
120         offset = next_blank(line->text, 0) + 1;
121         // add text to header
122         mvwprintw(stdscr,
123                   0, (COLS - line->length + offset) / 2,
124                   "%s", &line->text->text[offset]);
125     }
126
127     // setup footer
128     if(bar_bottom) {
129         line = deck->header->next;
130         offset = next_blank(line->text, 0) + 1;
131         // add text to left footer
132         mvwprintw(stdscr,
133                   LINES - 1, 3,
134                   "%s", &line->text->text[offset]);
135
136         if(deck->headers > 2) {
137             line = deck->header->next->next;
138             offset = next_blank(line->text, 0) + 1;
139             // add text to right footer
140             mvwprintw(stdscr,
141                       LINES - 1, COLS - line->length + offset - 3,
142                       "%s", &line->text->text[offset]);
143         }
144     }
145
146     // make header + fooder visible
147     wrefresh(stdscr);
148
149     // setup main window
150     WINDOW *content = newwin(LINES - bar_top - bar_bottom, COLS, 0 + bar_top, 0);
151     if(colors)
152         wbkgd(content, COLOR_PAIR(CP_WHITE));
153
154     slide = deck->slide;
155     while(slide) {
156         // clear main window
157         werase(content);
158
159         line = slide->line;
160         l = 0;
161
162         // print lines
163         while(line) {
164             add_line(content, l, (COLS - max_cols) / 2, line, max_cols);
165             line = line->next;
166             l++;
167         }
168
169         // make content visible
170         wrefresh(content);
171
172         // fade in
173         if(fade)
174             fade_in(content, trans, colors);
175
176         // re-enable fading after any undefined key press
177         if(COLORS == 256 && !nofade) fade = 1;
178
179         // wait for user input
180         c = getch();
181
182         // evaluate user input
183         switch(c) {
184
185             // show previous slide
186             case KEY_UP:
187             case KEY_LEFT:
188             case 8:   // BACKSPACE (ascii)
189             case 127: // BACKSPACE (xterm)
190             case 263: // BACKSPACE (getty)
191             case 'h':
192             case 'k':
193                 if(slide->prev)
194                     slide = slide->prev;
195                 break;
196
197             // show next slide
198             case KEY_DOWN:
199             case KEY_RIGHT:
200             case '\n': // ENTER
201             case ' ':  // SPACE
202             case 'j':
203             case 'l':
204                 if(slide->next)
205                     slide = slide->next;
206                 break;
207
208             // quit
209             case 'q':
210                 // do not fade out on exit
211                 fade = 0;
212                 slide = (void*)0;
213                 break;
214
215             default:
216                 // disable fading on undefined key press
217                 fade = 0;
218                 break;
219         }
220
221         // fade out
222         if(fade)
223             fade_out(content, trans, colors);
224     }
225
226     endwin();
227
228     return(0);
229 }
230
231 void add_line(WINDOW *window, int y, int x, line_t *line, int max_cols) {
232     int i = 0; // increment
233     char *c; // char pointer for iteration
234     char *special = "\\*_"; // list of interpreted chars
235     cstack_t *stack = cstack_init();
236
237     if(line->text->text) {
238         int offset = 0; // text offset
239         offset = next_nonblank(line->text, 0);
240
241         // IS_CODE
242         if(CHECK_BIT(line->bits, IS_CODE)) {
243
244             // set static offset for code
245             offset = CODE_INDENT;
246
247             // reverse color for code blocks
248             wattron(window, A_REVERSE);
249
250             // print whole lines
251             mvwprintw(window,
252                       y, x,
253                       "%s", &line->text->text[offset]);
254
255         // IS_QUOTE
256         } else if(CHECK_BIT(line->bits, IS_QUOTE)) {
257             //TODO replace greater sign with color block
258
259             //FIXME remove dummy print code
260             mvwprintw(window,
261                       y, x,
262                       "%s", &line->text->text[offset]);
263
264         } else {
265
266             // IF_H1 || IF_H2
267             if(CHECK_BIT(line->bits, IS_H1) || CHECK_BIT(line->bits, IS_H2)) {
268
269                 // set headline color
270                 wattron(window, COLOR_PAIR(CP_BLUE));
271
272                 // enable underline for H1
273                 if(CHECK_BIT(line->bits, IS_H1))
274                     wattron(window, A_UNDERLINE);
275
276                 // skip hashes
277                 while(line->text->text[offset] == '#')
278                     offset = next_word(line->text, offset);
279
280                 // print whole lines
281                 mvwprintw(window,
282                       y, x,
283                       "%s", &line->text->text[offset]);
284
285                 wattroff(window, A_UNDERLINE);
286
287             } else {
288                 // move the cursor in position
289                 wmove(window, y, x);
290
291                 // for each char in line
292                 c = line->text->text;
293                 while(*c) {
294
295                     // if char is in special char list
296                     if(strchr(special, *c)) {
297
298                         // closing special char (or second backslash)
299                         if(is_attron(stack, *c)) {
300
301                             switch(*c) {
302                                 // print escaped backslash
303                                 case '\\':
304                                     wprintw(window, "%c", *c);
305                                     break;
306                                 // disable highlight
307                                 case '*':
308                                     wattron(window, COLOR_PAIR(CP_WHITE));
309                                     break;
310                                 // disable underline
311                                 case '_':
312                                     wattroff(window, A_UNDERLINE);
313                                     break;
314                             }
315
316                             // remove top special char from stack
317                             (stack->pop)(stack);
318
319                         // treat special as regular char
320                         } else if(is_attron(stack, '\\')) {
321                             wprintw(window, "%c", *c);
322
323                             // remove backslash from stack
324                             (stack->pop)(stack);
325
326                         // opening special char
327                         } else {
328                             switch(*c) {
329                                 // enable highlight
330                                 case '*':
331                                     wattron(window, COLOR_PAIR(CP_RED));
332                                     break;
333                                 // enable underline
334                                 case '_':
335                                     wattron(window, A_UNDERLINE);
336                                     break;
337                                 // do nothing for backslashes
338                             }
339
340                             // push special char to stack
341                             (stack->push)(stack, *c);
342                         }
343
344                     } else {
345                         // print regular char
346                         wprintw(window, "%c", *c);
347                     }
348
349                     c++;
350                 }
351
352                 //TODO pop stack until empty
353             }
354         }
355
356         // fill rest off line with spaces
357         for(i = getcurx(window) - x; i < max_cols; i++)
358             wprintw(window, "%s", " ");
359
360         // reset to default color
361         wattron(window, COLOR_PAIR(CP_WHITE));
362         wattroff(window, A_UNDERLINE);
363         wattroff(window, A_REVERSE);
364     }
365
366     (stack->delete)(stack);
367 }
368
369 int is_attron(cstack_t *stack, char c) {
370     // test if char is on top of stack
371     if(stack->head >= 0) {
372         if((stack->top)(stack) == c) {
373             return 1;
374         }
375     }
376     return 0;
377 }
378
379 void fade_out(WINDOW *window, int trans, int colors) {
380     int i; // increment
381     if(colors) {
382         for(i = 22; i >= 0; i--) {
383             // darken color pairs
384             init_pair(CP_WHITE, white_ramp[i], trans);
385             init_pair(CP_BLUE, blue_ramp[i], trans);
386             init_pair(CP_RED, red_ramp[i], trans);
387             // refresh window with new color
388             wrefresh(window);
389             // delay for our eyes to recognize the change
390             usleep(FADE_DELAY);
391         }
392     }
393 }
394
395 void fade_in(WINDOW *window, int trans, int colors) {
396     int i; // increment
397     if(colors) {
398         for(i = 0; i <= 23; i++) {
399             // lighten color pairs
400             init_pair(CP_WHITE, white_ramp[i], trans);
401             init_pair(CP_BLUE, blue_ramp[i], trans);
402             init_pair(CP_RED, red_ramp[i], trans);
403             // refresh window with new color
404             wrefresh(window);
405             // delay for our eyes to recognize the change
406             usleep(FADE_DELAY);
407         }
408     }
409 }
410