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