]> Pileus Git - lackey/blob - src/form.c
9a05747970dc79f394674a004bd74cfd84c79eb8
[lackey] / src / form.c
1 /*
2  * Copyright (C) 2015 Andy Spencer <andy753421@gmail.com>
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17
18 #define _XOPEN_SOURCE
19 #define _XOPEN_SOURCE_EXTENDED
20
21 #include <string.h>
22 #include <ncurses.h>
23
24 #include "date.h"
25 #include "form.h"
26
27 /* Constants */
28 #define TEXT_WIDTH   (20)
29 #define NUMBER_WIDTH (10)
30 #define DATE_WIDTH   (4+1+2+1+2 +1+ 2+1+2)
31
32 /* Local variables */
33 static WINDOW *form_win;
34 static int     form_row;
35 static int     form_col;
36
37 /* Helpeer functions */
38 int label_width(const char *label)
39 {
40         int len = 0;
41         for (int i = 0; label[i]; i++)
42                 if (label[i] != '_')
43                         len++;
44         return len;
45 }
46
47 void label_print(WINDOW *win, const char *label)
48 {
49         for (int i = 0; label[i]; i++) {
50                 if (label[i] == '_' && label[i+1])
51                         waddch(form_win, label[++i]
52                                 | A_UNDERLINE | A_BOLD);
53                 else
54                         waddch(form_win, label[i]);
55         }
56 }
57
58 int field_width(form_field_t *field)
59 {
60         // Calculate list width
61         int list_size = 0;
62         if (field->type == FORM_LIST) {
63                 for (int i = 0; i < field->list.num; i++) {
64                         int width = strlen(field->list.map[i]);
65                         if (width > list_size)
66                                 list_size = width;
67                 }
68         }
69
70         // Calculate field size
71         int width = 0;
72         if (field->before)
73                 width += strlen(field->before);
74         switch (field->type) {
75                 case FORM_LABEL:  width += label_width(field->label); break;
76                 case FORM_TEXT:   width += TEXT_WIDTH;                break;
77                 case FORM_DATE:   width += DATE_WIDTH;                break;
78                 case FORM_NUMBER: width += NUMBER_WIDTH;              break;
79                 case FORM_BUTTON: width += label_width(field->label); break;
80                 case FORM_LIST:   width += list_size;                 break;
81         }
82         if (field->after)
83                 width += strlen(field->after);
84         return width;
85 }
86
87 void field_sizes(form_t *form, int *col_size)
88 {
89         // Do this by column, and right to left so that we can add
90         // in the extra space available for blank spaces.
91         for (int c = form->cols-1; c >= 0; c--) {
92                 col_size[c] = 0;
93                 for (int r = 0; r < form->rows; r++) {
94                         form_field_t *field = form->fields[r][c];
95                         if (form->fields[r][c]) {
96                                 int width = field_width(field);
97                                 for (int i = c+1; i < form->cols; i++) {
98                                         if (form->fields[r][i])
99                                                 break;
100                                         width -= col_size[i];
101                                 }
102                                 if (width > col_size[c])
103                                         col_size[c] = width;
104                         }
105                 }
106         }
107 }
108
109 void field_draw(WINDOW *win, form_field_t *field, int width, int hover)
110 {
111         char **map   = field->list.map;
112         int    idx   = field->list.idx;
113
114         char *before = field->before ?: "";
115         char *after  = field->after  ?: "";
116
117         int boxed    = field->type == FORM_TEXT ||
118                        field->type == FORM_NUMBER;
119         int under    = 0;
120
121         int begin    = getcurx(win);
122         int maxstr   = width - strlen(before) - strlen(after);
123
124         if (hover)  wattron(win, A_REVERSE);
125         if (under)  wattron(win, A_UNDERLINE);
126         if (boxed)  waddch(win, '[');
127         if (boxed)  maxstr -= 2;
128
129         wprintw(win, "%s", before);
130         switch (field->type) {
131                 case FORM_LABEL:
132                         label_print(win, field->label);
133                         break;
134                 case FORM_TEXT:
135                         wprintw(win, "%-.*s",
136                                 maxstr, field->text);
137                         break;
138                 case FORM_DATE:
139                         if (no_date(&field->date))
140                                 wprintw(win, "%s", "undefined");
141                         else
142                                 wprintw(win, "%04d-%02d-%02d %02d:%02d",
143                                         field->date.year,  field->date.month+1,
144                                         field->date.day+1, field->date.hour,
145                                         field->date.min);
146                         break;
147                 case FORM_NUMBER:
148                         wprintw(win, "%d", field->number);
149                         break;
150                 case FORM_BUTTON:
151                         wprintw(win, "%s", field->label);
152                         break;
153                 case FORM_LIST:
154                         if (map)
155                                 wprintw(win, "%s", map[idx]);
156                         else
157                                 wprintw(win, "%s", "undefined");
158                         break;
159         }
160         int pad = width-(getcurx(win)-begin)-boxed;
161         wprintw(win, "%-*s", pad, after);
162
163         if (boxed)  waddch(win, ']');
164         if (under)  wattroff(win, A_UNDERLINE);
165         if (hover)  wattroff(win, A_REVERSE);
166 }
167
168 int is_active(form_t *form, int r, int c)
169 {
170         if (r == form_row && c == form_col)
171                 return 1;
172         for (int i = c+1; i < form->cols && !form->fields[r][i]; i++)
173                 if (r == form_row && i == form_col)
174                         return 1;
175         return 0;
176 }
177
178 int can_active(form_t *form, int r, int c)
179 {
180         for (int i = c; i >= 0; i--) {
181                 if (!form->fields[r][i])
182                         continue;
183                 if (form->fields[r][i]->type != FORM_LABEL)
184                         return 1;
185                 else
186                         return 0;
187         }
188         return 0;
189 }
190
191 void set_active(form_t *form, int ro, int co)
192 {
193         // Set first field
194         if (ro==0 && co==0) {
195                 for (int r = 0; r < form->rows; r++)
196                 for (int c = 0; c < form->cols; c++) {
197                         if (can_active(form, r, c)) {
198                                 form_row = r;
199                                 form_col = c;
200                                 return;
201                         }
202                 }
203         }
204
205         // Move up/down
206         if (ro) {
207                 for (int ri = form_row+ro; ri>=0 && ri<form->rows; ri+=ro) {
208                         if (can_active(form, ri, form_col)) {
209                                 form_row = ri;
210                                 return;
211                         }
212                 }
213         }
214
215         // Move left/right
216         if (co) {
217                 for (int ci = form_col+co; ci>=0 && ci<form->cols; ci+=co) {
218                         for (int ri = form_row; ri < form->rows; ri++)
219                                 if (can_active(form, form_row, ci)) {
220                                         form_row = ri;
221                                         form_col = ci;
222                                         return;
223                                 }
224                         for (int ri = form_row; ri >= 0; ri--)
225                                 if (can_active(form, form_row, ci)) {
226                                         form_row = ri;
227                                         form_col = ci;
228                                         return;
229                                 }
230                 }
231         }
232 }
233
234 /* Initialize */
235 void form_init(void)
236 {
237         form_win = newwin(LINES, COLS, 0, 0);
238 }
239
240 /* Resize */
241 void form_resize(void)
242 {
243         mvwin(form_win, 0, 0);
244         wresize(form_win, LINES, COLS);
245 }
246
247 /* Run */
248 int form_draw(form_t *form)
249 {
250         int col_size[form->cols];
251
252         // Calculate column width
253         field_sizes(form, col_size);
254
255         // Make sure we have an active field
256         if (!can_active(form, form_row, form_col))
257                 set_active(form, 0, 0);
258
259         // Display form
260         for (int r = 0; r < form->rows; r++) {
261                 for (int c = 0; c < form->cols; c++) {
262                         form_field_t *field = form->fields[r][c];
263                         // Calculate form field size
264                         int width = col_size[c];
265                         for (int i = c+1; i < form->cols; i++) {
266                                 if (form->fields[r][i])
267                                         break;
268                                 width += col_size[i];
269                         }
270                         // Draw the field
271                         if (field)
272                                 field_draw(form_win, field, width,
273                                         is_active(form, r, c));
274                         else if (c == 0)
275                                 wprintw(form_win, "%*s", width, "");
276                 }
277                 wprintw(form_win, "\n");
278         }
279         return 0;
280 }
281
282 int form_run(form_t *form, int key, mmask_t btn, int row, int col)
283 {
284         // Check movement keys
285         switch (key) {
286                 case 'h': set_active(form,  0, -1); goto redraw;
287                 case 'j': set_active(form,  1,  0); goto redraw;
288                 case 'k': set_active(form, -1,  0); goto redraw;
289                 case 'l': set_active(form,  0,  1); goto redraw;
290         }
291
292         // Handle mouse movement
293         if (key == KEY_MOUSE && row < form->rows) {
294                 int pos=0, col_size[form->cols];
295                 field_sizes(form, col_size);
296                 for (int c = 0; c < form->cols; c++) {
297                         if (pos < col && col < pos+col_size[c]) {
298                                 if (can_active(form, row, c)) {
299                                         form_row = row;
300                                         form_col = c;
301                                         goto redraw;
302                                 }
303                         }
304                         pos += col_size[c];
305                 }
306         }
307
308         // Search for hotkeys
309         for (int r = 0; r < form->rows; r++)
310         for (int c = 0; c < form->cols; c++)
311                 if (form->fields[r][c] &&
312                     form->fields[r][c]->hotkey == key) {
313                         form_row = r;
314                         form_col = c;
315                         goto redraw;
316                 }
317         return 0;
318
319 redraw:
320         werase(form_win);
321         form_draw(form);
322         wrefresh(form_win);
323         return 1;
324 }
325
326 /* Test functions */
327 static form_field_t title     = TEXT('t');
328 static form_field_t location  = TEXT('o');
329 static form_field_t start     = DATE('s');
330 static form_field_t end       = DATE('e');
331 static form_field_t due_date  = DATE('u');
332 static form_field_t completed = NUMBER('p', .after="%");
333 static form_field_t calendar  = LIST('c');
334 static form_field_t category  = LIST('g');
335 static form_field_t repeat    = LIST('r');
336 static form_field_t frequency = NUMBER(0);
337 static form_field_t weekdays  = BUTTONS("Su Mo Tu We Th Fr Sa");
338 static form_field_t details   = TEXT('d');
339
340 static form_t edit = { 12, 4, {
341         { LABEL("_Title: "),    &title                                         },
342         { LABEL("L_ocation: "), &location                                      },
343         {                                                                      },
344         { LABEL("_Start: "),    &start,    LABEL("  _End: "),       &end       },
345         { LABEL("D_ue Date: "), &due_date, LABEL("  Com_pleted: "), &completed },
346         { LABEL("_Calendar: "), &calendar, LABEL("  Cate_gory: "),  &category  },
347         {                                                                      },
348         { LABEL("_Repeat: "),   &repeat,   LABEL("  Every: "),      &frequency },
349         {                                                                      },
350         { NULL,                 &weekdays                                      },
351         {                                                                      },
352         { LABEL("_Details: "),  &details                                       },
353 } };
354
355 void form_test(void)
356 {
357         /* Setup Curses */
358         initscr();
359         cbreak();
360         noecho();
361         keypad(stdscr, TRUE);
362         start_color();
363         curs_set(false);
364         timeout(100);
365         use_default_colors();
366         mousemask(ALL_MOUSE_EVENTS, NULL);
367
368         /* Init */
369         form_init();
370
371         /* Run */
372         while (1) {
373                 MEVENT btn;
374                 int chr = getch();
375                 if (chr == 'q')
376                         break;
377                 if (chr == KEY_MOUSE)
378                         if (getmouse(&btn) != OK)
379                                 continue;
380                 switch (chr) {
381                         case KEY_RESIZE:
382                                 form_resize();
383                                 refresh();
384                                 werase(form_win);
385                                 form_draw(&edit);
386                                 wrefresh(form_win);
387                                 continue;
388                         case '\14': // Ctrl-L
389                                 clear();
390                         case '\7':  // Ctrl-G
391                                 refresh();
392                                 werase(form_win);
393                                 form_draw(&edit);
394                                 wrefresh(form_win);
395                                 continue;
396                 }
397                 if (form_run(&edit, chr, btn.bstate, btn.y, btn.x))
398                         continue;
399                 if (chr == ERR) // timeout
400                         continue;
401         }
402
403         /* Finish */
404         endwin();
405 }