site.c 16.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
/*
 *   This file is part of telnet-site.
 *
 *   telnet-site is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   telnet-site is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
15
 *   along with telnet-site.  If not, see <https://www.gnu.org/licenses/>.
16 17
 */

mbionchi's avatar
mbionchi committed
18 19 20
#include "site.h"

#include "anim.h"
21 22 23
#include "render.h"
#include "scroll.h"
#include "data.h"
24
#include "winch.h"
25
#include "log.h"
26

27
#include <errno.h>
28 29 30 31 32
#include <ncurses.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
mbionchi's avatar
mbionchi committed
33
#include <poll.h>
34 35
#include <string.h>
#include <sys/stat.h>
36
#include <signal.h>
mbionchi's avatar
mbionchi committed
37 38
#include <unistd.h>
#include <sys/wait.h>
39

40 41 42 43 44 45 46 47
size_t get_index_width(struct section **sections, size_t n_sections) {
    size_t max_len = 0;
    size_t i;
    for (i=0; i<n_sections; i++) {
        size_t title_len = strlen(sections[i]->title);
        if (title_len > max_len) {
            max_len = title_len;
        }
48
    }
49
    return max_len;
50 51
}

mbionchi's avatar
mbionchi committed
52
int reload_content = 0;
mbionchi's avatar
mbionchi committed
53

54
/*
55 56 57 58 59
 * NOTE:
 *   scrollok(window, 0) needs to be called every time before
 *   doing mvwprintw to avoid forced scroll caused by the
 *   cursor possibly reacing the end of screen, and scrollok(window, 1)
 *   needs to be called after the mvwprintw call.
60
 * TODO:
61
 *   - error handling everywhere please
mbionchi's avatar
mbionchi committed
62
 *   - refactor the event loop, it's horrifying atm
63
 */
64
int site(char *path) {
mbionchi's avatar
mbionchi committed
65 66 67
    struct window index_window;
    struct window separator_window;
    struct window content_window;
mbionchi's avatar
mbionchi committed
68

mbionchi's avatar
mbionchi committed
69 70
    enum mode mode = COMMAND;

71 72 73
    int selected_index = 0;
    int scroll = 0;
    size_t n_sections;
74
    DIR *dir = opendir(path);
75
    if (dir == NULL) {
76 77
        log_(LOG_ERR, "%s:%s:%u: %s: %s", binary_name, __FILE__, __LINE__, strerror(errno), path);
        return 1;
78
    }
mbionchi's avatar
mbionchi committed
79
    struct section **sections = read_sections(dir, path, &n_sections);
80
    closedir(dir);
mbionchi's avatar
mbionchi committed
81 82 83 84

    int rv = read_content_from_section(&content_window.content, sections[selected_index]);
    if (rv != 0) {
        gen_err_opening(&content_window.content);
85
    }
mbionchi's avatar
mbionchi committed
86

87 88 89
    int index_left_margin = 2;
    int index_cols = get_index_width(sections, n_sections);
    int index_scroll_threshold = 5;
mbionchi's avatar
mbionchi committed
90

91 92 93
    int separator_cols = strlen(SEPARATOR_SELECTED);
    int separator_rows = LINES;
    int separator_left_margin = index_left_margin + index_cols;
mbionchi's avatar
mbionchi committed
94

95 96
    int content_left_margin = index_left_margin + index_cols + separator_cols;
    int content_right_margin = 8;
mbionchi's avatar
mbionchi committed
97 98 99 100 101 102

    index_window.cols = get_index_width(sections, n_sections);
    index_window.rows = LINES;
    index_window.scroll = 0;
    index_window.window = newwin(index_window.rows, index_window.cols, 0, index_left_margin);

mbionchi's avatar
mbionchi committed
103 104 105 106 107 108 109
    index_window.content.type = STATIC;
    index_window.content.lines = malloc(sizeof(struct static_content));
    index_window.content.lines->anim_refs = NULL;
    index_window.content.lines->n_formatted = gen_index(sections,
                                                        n_sections,
                                                        &index_window.content.lines->formatted,
                                                        index_window.cols);
mbionchi's avatar
mbionchi committed
110

111 112 113 114 115
    separator_window.cols = strlen(SEPARATOR_SELECTED);
    separator_window.rows = LINES;
    separator_window.scroll = 1;
    separator_window.window = newwin(separator_window.rows, separator_window.cols, 0, separator_left_margin);

mbionchi's avatar
mbionchi committed
116 117 118 119 120
    content_window.cols = COLS - content_left_margin - content_right_margin;
    content_window.rows = LINES;
    content_window.scroll = 0;
    content_window.window = newwin(content_window.rows, content_window.cols, 0, content_left_margin);

mbionchi's avatar
mbionchi committed
121 122 123 124 125 126 127 128
    if (content_window.content.type == STATIC && content_window.content.lines != NULL) {
        content_window.content.lines->anim_refs = NULL;
        content_window.content.lines->n_formatted = flow_nlines(content_window.content.lines->raw,
                                                                content_window.content.lines->n_raw,
                                                                &content_window.content.lines->formatted,
                                                                content_window.cols,
                                                                0);
    }
mbionchi's avatar
mbionchi committed
129

mbionchi's avatar
mbionchi committed
130
    make_scrollable(index_window.window);
131
    make_scrollable(separator_window.window);
mbionchi's avatar
mbionchi committed
132 133 134
    if (content_window.content.type == STATIC && content_window.content.lines != NULL) {
        make_scrollable(content_window.window);
    }
mbionchi's avatar
mbionchi committed
135 136

    render_ncontent(&index_window);
137
    render_separator(&separator_window);
mbionchi's avatar
mbionchi committed
138 139 140
    if (content_window.content.type == STATIC && content_window.content.lines != NULL) {
        render_ncontent(&content_window);
    }
mbionchi's avatar
mbionchi committed
141

142
    refresh();
mbionchi's avatar
mbionchi committed
143
    wrefresh(index_window.window);
144
    wrefresh(separator_window.window);
mbionchi's avatar
mbionchi committed
145 146 147 148 149
    if (content_window.content.type == STATIC && content_window.content.lines != NULL) {
        wrefresh(content_window.window);
    } else if (content_window.content.type == DYNAMIC && content_window.content.dlib != NULL) {
        content_window.content.dlib->init_fun(content_window.window);
    }
mbionchi's avatar
mbionchi committed
150

151
    int ch = getch();
mbionchi's avatar
mbionchi committed
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
    while (ch != 'q' || mode == INSERT) {
        switch (ch) {
            case 'i':
                if (mode == INSERT &&
                        content_window.content.type == DYNAMIC &&
                        content_window.content.dlib != NULL &&
                        content_window.content.dlib->getch_fun != NULL) {
                    content_window.content.dlib->getch_fun(ch);
                } else if (content_window.content.type == DYNAMIC &&
                           content_window.content.dlib != NULL &&
                           content_window.content.dlib->setmode_fun != NULL) {
                    mode = INSERT;
                    content_window.content.dlib->setmode_fun(INSERT);
                }
                break;
            /*
             * '\e' is non-standard, but should be supported by most compilers.  it stands
             * for ESC
             */
            case '\e':
                if (mode != COMMAND) {
                    mode = COMMAND;
                    if (content_window.content.type == DYNAMIC &&
                            content_window.content.dlib != NULL &&
                            content_window.content.dlib->setmode_fun != NULL) {
                        content_window.content.dlib->setmode_fun(COMMAND);
                    }
                }
                break;
181
            case 'J':
mbionchi's avatar
mbionchi committed
182 183 184 185 186 187 188 189 190 191
                if (mode == COMMAND) {
                    if (selected_index < n_sections-1) {
                        selected_index++;
                        reload_content = 1;
                        if (separator_window.scroll + index_scroll_threshold < separator_window.rows ||
                                index_window.content.lines->n_formatted <= index_window.scroll + index_window.rows) {
                            scroll_separator(&separator_window, 1);
                        } else {
                            scroll_ncontent(&index_window, 1);
                        }
192
                    }
mbionchi's avatar
mbionchi committed
193 194 195 196 197
                } else if (mode == INSERT &&
                        content_window.content.type == DYNAMIC &&
                        content_window.content.dlib != NULL &&
                        content_window.content.dlib->getch_fun != NULL) {
                    content_window.content.dlib->getch_fun(ch);
198 199 200
                }
                break;
            case 'K':
mbionchi's avatar
mbionchi committed
201 202 203 204 205 206 207 208 209 210
                if (mode == COMMAND) {
                    if (selected_index > 0) {
                        selected_index--;
                        reload_content = 1;
                        if (separator_window.scroll >= index_scroll_threshold ||
                                index_window.scroll == 0) {
                            scroll_separator(&separator_window, -1);
                        } else {
                            scroll_ncontent(&index_window, -1);
                        }
211
                    }
mbionchi's avatar
mbionchi committed
212 213 214 215 216
                } else if (mode == INSERT &&
                        content_window.content.type == DYNAMIC &&
                        content_window.content.dlib != NULL &&
                        content_window.content.dlib->getch_fun != NULL) {
                    content_window.content.dlib->getch_fun(ch);
217 218
                }
                break;
mbionchi's avatar
mbionchi committed
219
            case KEY_DOWN:
220
            case 'j':
mbionchi's avatar
mbionchi committed
221 222 223 224 225 226 227 228 229 230 231 232 233 234
                if (mode == COMMAND) {
                    if (content_window.content.type == STATIC) {
                        scroll_ncontent(&content_window, 1);
                    } else if (content_window.content.type == DYNAMIC &&
                               content_window.content.dlib != NULL &&
                               content_window.content.dlib->scroll_fun != NULL) {
                        content_window.content.dlib->scroll_fun(1);
                    }
                } else if (mode == INSERT &&
                           content_window.content.type == DYNAMIC &&
                           content_window.content.dlib != NULL &&
                           content_window.content.dlib->getch_fun != NULL) {
                    content_window.content.dlib->getch_fun(ch);
                }
235
                break;
mbionchi's avatar
mbionchi committed
236
            case KEY_UP:
237
            case 'k':
mbionchi's avatar
mbionchi committed
238 239 240 241 242 243 244 245 246 247 248 249 250 251
                if (mode == COMMAND) {
                    if (content_window.content.type == STATIC) {
                        scroll_ncontent(&content_window, -1);
                    } else if (content_window.content.type == DYNAMIC &&
                               content_window.content.dlib != NULL &&
                               content_window.content.dlib->scroll_fun != NULL) {
                        content_window.content.dlib->scroll_fun(-1);
                    }
                } else if (mode == INSERT &&
                        content_window.content.type == DYNAMIC &&
                        content_window.content.dlib != NULL &&
                        content_window.content.dlib->getch_fun != NULL) {
                    content_window.content.dlib->getch_fun(ch);
                }
252
                break;
mbionchi's avatar
mbionchi committed
253
            case KEY_HOME:
254
            case 'g':
mbionchi's avatar
mbionchi committed
255 256 257 258 259 260 261 262 263 264 265 266
                if (mode == COMMAND && content_window.content.type == STATIC) {
                    if (content_window.scroll > 0) {
                        wclear(content_window.window);
                        content_window.scroll = 0;
                        render_ncontent(&content_window);
                        wrefresh(content_window.window);
                    }
                } else if (mode == INSERT &&
                        content_window.content.type == DYNAMIC &&
                        content_window.content.dlib != NULL &&
                        content_window.content.dlib->getch_fun != NULL) {
                    content_window.content.dlib->getch_fun(ch);
mbionchi's avatar
mbionchi committed
267
                }
268
                break;
mbionchi's avatar
mbionchi committed
269
            case KEY_END:
270
            case 'G':
mbionchi's avatar
mbionchi committed
271 272 273 274 275 276 277 278 279 280 281 282
                if (mode == COMMAND && content_window.content.type == STATIC) {
                    if (content_window.content.lines->n_formatted > content_window.rows) {
                        wclear(content_window.window);
                        content_window.scroll = content_window.content.lines->n_formatted - content_window.rows;
                        render_ncontent(&content_window);
                        wrefresh(content_window.window);
                    }
                } else if (mode == INSERT &&
                        content_window.content.type == DYNAMIC &&
                        content_window.content.dlib != NULL &&
                        content_window.content.dlib->getch_fun != NULL) {
                    content_window.content.dlib->getch_fun(ch);
mbionchi's avatar
mbionchi committed
283
                }
284
                break;
mbionchi's avatar
mbionchi committed
285
            case ERR:
mbionchi's avatar
mbionchi committed
286 287 288 289 290 291
                if (content_window.content.type == STATIC && content_window.content.lines != NULL) {
                    anim_tick(&content_window);
                    if (had_winch) {
                        delwin(index_window.window);
                        delwin(separator_window.window);
                        delwin(content_window.window);
mbionchi's avatar
mbionchi committed
292

293
                        endwin();
mbionchi's avatar
mbionchi committed
294
                        refresh();
mbionchi's avatar
mbionchi committed
295

mbionchi's avatar
mbionchi committed
296 297
                        index_window.rows = LINES;
                        index_window.window = newwin(index_window.rows, index_window.cols, 0, index_left_margin);
mbionchi's avatar
mbionchi committed
298

mbionchi's avatar
mbionchi committed
299 300 301
                        separator_window.rows = LINES;
                        separator_window.scroll = selected_index+1;
                        separator_window.window = newwin(separator_window.rows, separator_window.cols, 0, separator_left_margin);
mbionchi's avatar
mbionchi committed
302

mbionchi's avatar
mbionchi committed
303 304 305 306
                        content_window.cols = COLS - content_left_margin - content_right_margin;
                        content_window.rows = LINES;
                        content_window.scroll = 0;
                        content_window.window = newwin(content_window.rows, content_window.cols, 0, content_left_margin);
mbionchi's avatar
mbionchi committed
307

mbionchi's avatar
mbionchi committed
308 309
                        free_nlines(content_window.content.lines->formatted, content_window.content.lines->n_formatted);
                        free_anim_refs(&content_window);
310

mbionchi's avatar
mbionchi committed
311 312 313 314 315 316
                        content_window.content.lines->n_formatted =
                            flow_nlines(content_window.content.lines->raw,
                                        content_window.content.lines->n_raw,
                                        &content_window.content.lines->formatted,
                                        content_window.cols,
                                        0);
mbionchi's avatar
mbionchi committed
317

mbionchi's avatar
mbionchi committed
318 319 320
                        make_scrollable(index_window.window);
                        make_scrollable(separator_window.window);
                        make_scrollable(content_window.window);
mbionchi's avatar
mbionchi committed
321

mbionchi's avatar
mbionchi committed
322 323 324
                        render_ncontent(&index_window);
                        render_separator(&separator_window);
                        render_ncontent(&content_window);
mbionchi's avatar
mbionchi committed
325

326

mbionchi's avatar
mbionchi committed
327
                        refresh();
mbionchi's avatar
mbionchi committed
328

mbionchi's avatar
mbionchi committed
329 330 331
                        wrefresh(index_window.window);
                        wrefresh(separator_window.window);
                        wrefresh(content_window.window);
mbionchi's avatar
mbionchi committed
332

mbionchi's avatar
mbionchi committed
333 334
                        had_winch = 0;
                    }
mbionchi's avatar
mbionchi committed
335 336
                }
                if (reload_content) {
mbionchi's avatar
mbionchi committed
337 338 339 340 341 342 343
                    if (content_window.content.type == STATIC) {
                        free_content(&content_window);
                        free_anim_refs(&content_window);
                    } else if (content_window.content.type == DYNAMIC &&
                               content_window.content.dlib != NULL &&
                               content_window.content.dlib->kill_fun != NULL) {
                        content_window.content.dlib->kill_fun();
344
                    }
mbionchi's avatar
mbionchi committed
345

mbionchi's avatar
mbionchi committed
346 347 348 349 350 351 352 353 354 355 356 357 358
                    rv = read_content_from_section(&content_window.content, sections[selected_index]);
                    if (rv != 0) {
                        gen_err_opening(&content_window.content);
                    }

                    if (content_window.content.type == STATIC && content_window.content.lines != NULL) {
                        content_window.content.lines->n_formatted =
                            flow_nlines(content_window.content.lines->raw,
                                        content_window.content.lines->n_raw,
                                        &content_window.content.lines->formatted,
                                        content_window.cols,
                                        0);
                    }
mbionchi's avatar
mbionchi committed
359 360
                    content_window.scroll = 0;

mbionchi's avatar
mbionchi committed
361 362 363 364 365 366 367
                    if (content_window.content.type == STATIC && content_window.content.lines != NULL) {
                        wclear(content_window.window);
                        render_ncontent(&content_window);
                        wrefresh(content_window.window);
                    } else if (content_window.content.type == DYNAMIC && content_window.content.dlib != NULL) {
                        content_window.content.dlib->init_fun(content_window.window);
                    }
mbionchi's avatar
mbionchi committed
368 369 370

                    reload_content = 0;
                }
mbionchi's avatar
mbionchi committed
371 372 373 374 375
                if (content_window.content.type == DYNAMIC &&
                        content_window.content.dlib != NULL &&
                        content_window.content.dlib->getch_fun != NULL) {
                    content_window.content.dlib->getch_fun(ERR);
                }
mbionchi's avatar
mbionchi committed
376
                break;
377
            default:
mbionchi's avatar
mbionchi committed
378 379 380 381 382
                if (content_window.content.type == DYNAMIC &&
                        content_window.content.dlib != NULL &&
                        content_window.content.dlib->getch_fun != NULL) {
                    content_window.content.dlib->getch_fun(ch);
                }
383 384 385 386
                break;
        }
        ch = getch();
    }
mbionchi's avatar
mbionchi committed
387 388 389
    free_content(&content_window);
    free_anim_refs(&content_window);
    delwin(index_window.window);
390
    delwin(separator_window.window);
mbionchi's avatar
mbionchi committed
391
    delwin(content_window.window);
392
    return 0;
393
}