]> Pileus Git - lackey/commitdiff
Add basic forms
authorAndy Spencer <andy753421@gmail.com>
Wed, 14 Jan 2015 19:46:40 +0000 (19:46 +0000)
committerAndy Spencer <andy753421@gmail.com>
Mon, 5 Dec 2016 08:28:53 +0000 (08:28 +0000)
makefile
src/form.c [new file with mode: 0644]
src/form.h [new file with mode: 0644]
src/test.c

index 14d6a71dda7331b616033a4a3b083f28a4f79a56..853d1c416697cfc499a763c58bda875e07a35bf6 100644 (file)
--- a/makefile
+++ b/makefile
@@ -15,15 +15,16 @@ LDFLAGS   ?= -lncursesw -lical
 
 # Sources
 PROG      ?= lackey
-PROG_SRC  ?= main view date cal args conf util
+PROG_SRC  ?= main view form date cal args conf util
 TEST      ?= test
-TEST_SRC  ?= test date cal conf util
+TEST_SRC  ?= test form date cal conf util
 VIEWS     ?= day week month year events todo settings help edit
 CALS      ?= dummy ical
 
 # Objects
 views/%.o  cals/%.o:  CFLAGS += -Isrc
 src/view.o views/%.o: CFLAGS += $(strip $(shell pkg-config --cflags ncursesw))
+src/form.o:           CFLAGS += $(strip $(shell pkg-config --cflags ncursesw))
 
 # Targets
 all: $(PROG)
diff --git a/src/form.c b/src/form.c
new file mode 100644 (file)
index 0000000..0bec405
--- /dev/null
@@ -0,0 +1,384 @@
+/*
+ * Copyright (C) 2015 Andy Spencer <andy753421@gmail.com>
+ *
+ * This program 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.
+ *
+ * This program 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
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define _XOPEN_SOURCE
+#define _XOPEN_SOURCE_EXTENDED
+
+#include <string.h>
+#include <ncurses.h>
+
+#include "date.h"
+#include "form.h"
+
+/* Constants */
+#define TEXT_WIDTH   (20)
+#define NUMBER_WIDTH (10)
+#define DATE_WIDTH   (4+1+2+1+2 +1+ 2+1+2)
+
+/* Local variables */
+static WINDOW *form_win;
+static int     form_row;
+static int     form_col;
+
+/* Helpeer functions */
+int label_width(const char *label)
+{
+       int len = 0;
+       for (int i = 0; label[i]; i++)
+               if (label[i] != '_')
+                       len++;
+       return len;
+}
+
+void label_print(WINDOW *win, const char *label)
+{
+       for (int i = 0; label[i]; i++) {
+               if (label[i] == '_' && label[i+1])
+                       waddch(form_win, label[++i]
+                               | A_UNDERLINE | A_BOLD);
+               else
+                       waddch(form_win, label[i]);
+       }
+}
+
+int field_width(form_field_t *field)
+{
+       // Calculate list width
+       int list_size = 0;
+       if (field->type == FORM_LIST) {
+               for (int i = 0; i < field->list.num; i++) {
+                       int width = strlen(field->list.map[i]);
+                       if (width > list_size)
+                               list_size = width;
+               }
+       }
+
+       // Calculate field size
+       int width = 0;
+       if (field->before)
+               width += strlen(field->before);
+       switch (field->type) {
+               case FORM_LABEL:  width += label_width(field->label); break;
+               case FORM_TEXT:   width += TEXT_WIDTH;                break;
+               case FORM_DATE:   width += DATE_WIDTH;                break;
+               case FORM_NUMBER: width += NUMBER_WIDTH;              break;
+               case FORM_BUTTON: width += label_width(field->label); break;
+               case FORM_LIST:   width += list_size;                 break;
+       }
+       if (field->after)
+               width += strlen(field->after);
+       return width;
+}
+
+void field_draw(WINDOW *win, form_field_t *field, int width, int hover)
+{
+       char **map   = field->list.map;
+       int    idx   = field->list.idx;
+
+       char *before = field->before ?: "";
+       char *after  = field->after  ?: "";
+
+       int boxed    = field->type == FORM_TEXT ||
+                      field->type == FORM_NUMBER;
+       int under    = 0;
+
+       int begin    = getcurx(win);
+       int maxstr   = width - strlen(before) - strlen(after);
+
+       if (hover)  wattron(win, A_REVERSE);
+       if (under)  wattron(win, A_UNDERLINE);
+       if (boxed)  waddch(win, '[');
+       if (boxed)  maxstr -= 2;
+
+       wprintw(win, "%s", before);
+       switch (field->type) {
+               case FORM_LABEL:
+                       label_print(win, field->label);
+                       break;
+               case FORM_TEXT:
+                       wprintw(win, "%-.*s",
+                               maxstr, field->text);
+                       break;
+               case FORM_DATE:
+                       if (no_date(&field->date))
+                               wprintw(win, "%s", "undefined");
+                       else
+                               wprintw(win, "%04d-%02d-%02d %02d:%02d",
+                                       field->date.year,  field->date.month+1,
+                                       field->date.day+1, field->date.hour,
+                                       field->date.min);
+                       break;
+               case FORM_NUMBER:
+                       wprintw(win, "%d", field->number);
+                       break;
+               case FORM_BUTTON:
+                       wprintw(win, "%s", field->label);
+                       break;
+               case FORM_LIST:
+                       if (map)
+                               wprintw(win, "%s", map[idx]);
+                       else
+                               wprintw(win, "%s", "undefined");
+                       break;
+       }
+       int pad = width-(getcurx(win)-begin)-boxed;
+       wprintw(win, "%-*s", pad, after);
+
+       if (boxed)  waddch(win, ']');
+       if (under)  wattroff(win, A_UNDERLINE);
+       if (hover)  wattroff(win, A_REVERSE);
+}
+
+int is_active(form_t *form, int r, int c)
+{
+       if (r == form_row && c == form_col)
+               return 1;
+       for (int i = c+1; i < form->cols && !form->fields[r][i]; i++)
+               if (r == form_row && i == form_col)
+                       return 1;
+       return 0;
+}
+
+int can_active(form_t *form, int r, int c)
+{
+       for (int i = c; i >= 0; i--) {
+               if (!form->fields[r][i])
+                       continue;
+               if (form->fields[r][i]->type != FORM_LABEL)
+                       return 1;
+               else
+                       return 0;
+       }
+       return 0;
+}
+
+void set_active(form_t *form, int ro, int co)
+{
+       // Set first field
+       if (ro==0 && co==0) {
+               for (int r = 0; r < form->rows; r++)
+               for (int c = 0; c < form->cols; c++) {
+                       if (can_active(form, r, c)) {
+                               form_row = r;
+                               form_col = c;
+                               return;
+                       }
+               }
+       }
+
+       // Move up/down
+       if (ro) {
+               for (int ri = form_row+ro; ri>=0 && ri<form->rows; ri+=ro) {
+                       if (can_active(form, ri, form_col)) {
+                               form_row = ri;
+                               return;
+                       }
+               }
+       }
+
+       // Move left/right
+       if (co) {
+               for (int ci = form_col+co; ci>=0 && ci<form->cols; ci+=co) {
+                       for (int ri = form_row; ri < form->rows; ri++)
+                               if (can_active(form, form_row, ci)) {
+                                       form_row = ri;
+                                       form_col = ci;
+                                       return;
+                               }
+                       for (int ri = form_row; ri >= 0; ri--)
+                               if (can_active(form, form_row, ci)) {
+                                       form_row = ri;
+                                       form_col = ci;
+                                       return;
+                               }
+               }
+       }
+}
+
+/* Initialize */
+void form_init(void)
+{
+       form_win = newwin(LINES, COLS, 0, 0);
+}
+
+/* Resize */
+void form_resize(void)
+{
+       mvwin(form_win, 0, 0);
+       wresize(form_win, LINES, COLS);
+}
+
+/* Run */
+int form_draw(form_t *form)
+{
+       // Calculate column width
+       //    do this by column, and right to left so that
+       //    we can add in the extra space available for
+       //    blank spaces.
+       int col_size[form->cols];
+       for (int c = form->cols-1; c >= 0; c--) {
+               col_size[c] = 0;
+               for (int r = 0; r < form->rows; r++) {
+                       form_field_t *field = form->fields[r][c];
+                       if (form->fields[r][c]) {
+                               int width = field_width(field);
+                               for (int i = c+1; i < form->cols; i++) {
+                                       if (form->fields[r][i])
+                                               break;
+                                       width -= col_size[i];
+                               }
+                               if (width > col_size[c])
+                                       col_size[c] = width;
+                       }
+               }
+       }
+
+       // Make sure we have an active field
+       if (!can_active(form, form_row, form_col))
+               set_active(form, 0, 0);
+
+       // Display form
+       for (int r = 0; r < form->rows; r++) {
+               for (int c = 0; c < form->cols; c++) {
+                       form_field_t *field = form->fields[r][c];
+                       // Calculate form field size
+                       int width = col_size[c];
+                       for (int i = c+1; i < form->cols; i++) {
+                               if (form->fields[r][i])
+                                       break;
+                               width += col_size[i];
+                       }
+                       // Draw the field
+                       if (field)
+                               field_draw(form_win, field, width,
+                                       is_active(form, r, c));
+                       else if (c == 0)
+                               wprintw(form_win, "%*s", width, "");
+               }
+               wprintw(form_win, "\n");
+       }
+       return 0;
+}
+
+int form_run(form_t *form, int key, mmask_t btn, int row, int col)
+{
+       // Check movement keys
+       switch (key) {
+               case 'h': set_active(form,  0, -1); goto redraw;
+               case 'j': set_active(form,  1,  0); goto redraw;
+               case 'k': set_active(form, -1,  0); goto redraw;
+               case 'l': set_active(form,  0,  1); goto redraw;
+       }
+
+       // Search for hotkeys
+       for (int r = 0; r < form->rows; r++)
+       for (int c = 0; c < form->cols; c++)
+               if (form->fields[r][c] &&
+                   form->fields[r][c]->hotkey == key) {
+                       form_row = r;
+                       form_col = c;
+                       goto redraw;
+               }
+       return 0;
+
+redraw:
+       werase(form_win);
+       form_draw(form);
+       wrefresh(form_win);
+       return 1;
+}
+
+/* Test functions */
+static form_field_t title     = TEXT('t');
+static form_field_t location  = TEXT('o');
+static form_field_t start     = DATE('s');
+static form_field_t end       = DATE('e');
+static form_field_t due_date  = DATE('u');
+static form_field_t completed = NUMBER('p', .after="%");
+static form_field_t calendar  = LIST('c');
+static form_field_t category  = LIST('g');
+static form_field_t repeat    = LIST('r');
+static form_field_t frequency = NUMBER(0);
+static form_field_t weekdays  = BUTTONS("Su Mo Tu We Th Fr Sa");
+static form_field_t details   = TEXT('d');
+
+static form_t edit = { 12, 4, {
+       { LABEL("_Title: "),    &title                                         },
+       { LABEL("L_ocation: "), &location                                      },
+       {                                                                      },
+       { LABEL("_Start: "),    &start,    LABEL("  _End: "),       &end       },
+       { LABEL("D_ue Date: "), &due_date, LABEL("  Com_pleted: "), &completed },
+       { LABEL("_Calendar: "), &calendar, LABEL("  Cate_gory: "),  &category  },
+       {                                                                      },
+       { LABEL("_Repeat: "),   &repeat,   LABEL("  Every: "),      &frequency },
+       {                                                                      },
+       { NULL,                 &weekdays                                      },
+       {                                                                      },
+       { LABEL("_Details: "),  &details                                       },
+} };
+
+void form_test(void)
+{
+       /* Setup Curses */
+       initscr();
+       cbreak();
+       noecho();
+       keypad(stdscr, TRUE);
+       start_color();
+       curs_set(false);
+       timeout(100);
+       use_default_colors();
+       mousemask(ALL_MOUSE_EVENTS, NULL);
+
+       /* Init */
+       form_init();
+
+       /* Run */
+       while (1) {
+               MEVENT btn;
+               int chr = getch();
+               if (chr == 'q')
+                       break;
+               if (chr == KEY_MOUSE)
+                       if (getmouse(&btn) != OK)
+                               continue;
+               switch (chr) {
+                       case KEY_RESIZE:
+                               form_resize();
+                               refresh();
+                               werase(form_win);
+                               form_draw(&edit);
+                               wrefresh(form_win);
+                               continue;
+                       case '\14': // Ctrl-L
+                               clear();
+                       case '\7':  // Ctrl-G
+                               refresh();
+                               werase(form_win);
+                               form_draw(&edit);
+                               wrefresh(form_win);
+                               continue;
+               }
+               if (form_run(&edit, chr, btn.bstate, btn.y, btn.x))
+                       continue;
+               if (chr == ERR) // timeout
+                       continue;
+       }
+
+       /* Finish */
+       endwin();
+}
diff --git a/src/form.h b/src/form.h
new file mode 100644 (file)
index 0000000..6e7d8d6
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2015 Andy Spencer <andy753421@gmail.com>
+ *
+ * This program 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.
+ *
+ * This program 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
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Widget Macros */
+#define TEXT(k,...)    {.type=FORM_TEXT,   .hotkey=k, __VA_ARGS__}
+#define NUMBER(k,...)  {.type=FORM_NUMBER, .hotkey=k, __VA_ARGS__}
+#define DATE(k,...)    {.type=FORM_DATE,   .hotkey=k, __VA_ARGS__}
+#define BUTTON(k,...)  {.type=FORM_BUTTON, .hotkey=k, __VA_ARGS__}
+#define LIST(k,...)    {.type=FORM_LIST,   .hotkey=k, __VA_ARGS__}
+
+#define HEAD(t,...)    &(form_field_t){.type=FORM_LABEL, .label=t, .attr.bold=1}
+#define LABEL(t,...)   &(form_field_t){.type=FORM_LABEL, .label=t}
+
+#define CHECK(t,...)     {.type=FORM_BUTTON, .label=t}
+#define TOGGLE(t,...)    {.type=FORM_BUTTON, .label=t}
+#define RADIO(t,...)     {.type=FORM_BUTTON, .label=t}
+#define BUTTONS(t,...)   {.type=FORM_BUTTON, .label=t} // LIST[buttons]
+
+/* Widget types */
+typedef enum {
+       FORM_LABEL,
+       FORM_TEXT,
+       FORM_DATE,
+       FORM_NUMBER,
+       FORM_BUTTON,
+       FORM_LIST
+} form_type_t;
+
+/* Text attributes */
+typedef struct {
+       int bold : 1;
+} form_attr_t;
+
+/* Form data type */
+typedef struct {
+       form_type_t type;
+       form_attr_t attr;
+       int         hotkey;
+       char       *before;
+       char       *label;
+       char       *after;
+       union {
+               char    text[256];
+               date_t  date;
+               int     number;
+               int     button;
+               struct {
+                       char **map;
+                       int    num;
+                       int    idx;
+               } list;
+       };
+} form_field_t;
+
+typedef struct {
+       int rows;
+       int cols;
+       form_field_t *fields[][10];
+} form_t;
+
+void form_init(void);
+
+int form_run(form_t *form, int key, mmask_t btn, int row, int col);
index 96f7f8c3dc31122888edaf01ef05c0647fc06d21..1e677dca20cef811860ce94194311fb1a5ffa076 100644 (file)
@@ -22,6 +22,7 @@
 
 void date_test(void);
 void conf_test(void);
+void form_test(void);
 void ical_test(void *path);
 
 int main(int argc, char **argv)
@@ -29,6 +30,7 @@ int main(int argc, char **argv)
        for (int i = 1; i < argc; i++) {
                if (match(argv[i], "date")) date_test();
                if (match(argv[i], "conf")) conf_test();
+               if (match(argv[i], "form")) form_test();
                if (match(argv[i], "ical")) ical_test(argv[++i]);
        }
        return 0;