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