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