From ada43f8d16a08fbcac9199591ed10a78675153d9 Mon Sep 17 00:00:00 2001 From: dam Date: Tue, 23 Aug 2022 16:40:40 +0000 Subject: Removed hash related stuff (kept in misc.c file just in case). Prototype implementation of truncate_string_utf8. --- main.c | 708 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ misc.c | 66 ++++++ readme.md | 29 +++ task_tracker.c | 724 --------------------------------------------------------- 4 files changed, 803 insertions(+), 724 deletions(-) create mode 100644 main.c create mode 100644 misc.c create mode 100644 readme.md delete mode 100644 task_tracker.c diff --git a/main.c b/main.c new file mode 100644 index 0000000..878e42c --- /dev/null +++ b/main.c @@ -0,0 +1,708 @@ +// Compilation command: +// - release: gcc main.c -Wall -O2 -m64 -lncurses -o ttt +// - debug : gcc main.c -Wall -g3 -m64 -lncurses -o ttt + + +#include +#include +#include +#include +#include +#include +#include +#include + +#define STR_(X) #X // Convert to string. +#define STR(X) STR_(X) // Force argument expansions before converting to string. + +#define MAX_TASK_NAME 127 +#define FIRST_DAY_OF_WEEK 1 // (0-6, Sunday = 0) + +#define LOG_FILE_NAME "log.txt" +#define DB_BIN_PATH_NAME "./database.bin" +#define DB_CSV_PATH_NAME "./database.csv" + + + + +typedef struct { + char name[MAX_TASK_NAME+1]; // Allow space for null termination char. + uint32_t time[7]; + uint8_t state; +} task_t; + + +const char* DB_BIN_FILE_SIGNATURE = "TTBF:1.0"; +const uint8_t DB_BIN_FILE_SIGNATURE_SIZE = sizeof(DB_BIN_FILE_SIGNATURE); +const size_t SIZEOF_TASK_T = sizeof(task_t); +const char* DAYS_OF_WEEK[] = { "sun", "mon", "tue", "wed", "thu", "fri", "sat" }; +const uint8_t DAYS_ON_WEEK = sizeof(DAYS_OF_WEEK)/sizeof(char*); + +task_t* tasks = NULL; +uint32_t tasks_count = 0; +uint32_t tasks_capacity = 0; +uint32_t selected_task = -1; + + +// Given an UTF8 encoded string, truncate it to length without breaking any UTF8 character. +// The string should have, at least length number of items. +// The terminating null byte ('\0') is included in length. +// The function returns the amount of items that got discarded counting from length. +size_t truncate_string_utf8(uint8_t* string, size_t length) { + + size_t idx = length - 1; + + // Check for special case where no truncation is required. + if (string[idx] == '\0') { + return 0; + } + + // Search for a non-UTF8-sequence-item so we can truncate the string. + while(idx > 0 && ((string[idx] & 0xC0) == 0x80)) { + idx--; + } + + string[idx] = '\0'; + +#if DEBUG + // TODO Check if there is a null byte before the place where we terminated the string. + size_t idx_dbd = idx - 1; + while(idx_dbd >= 0) { + assert(string[idx_dbd] != '\0' && "Found unexpected null byte. The user is at fault."); + idx_dbd--; + } +#endif + + return length - idx; +} + +char* replace_char(char* string, char find, char replace){ + char *idx = string; + while((idx = strchr(idx, find)) != NULL) { + *idx = replace; + *idx++; + } + return string; +} + +void print_task(const task_t* task) { + printf("name: '%s'\n", task->name); + printf("t[0]: '%" PRIu32 "'\n", task->time[0]); + printf("t[1]: '%" PRIu32 "'\n", task->time[1]); + printf("t[2]: '%" PRIu32 "'\n", task->time[2]); + printf("t[3]: '%" PRIu32 "'\n", task->time[3]); + printf("t[4]: '%" PRIu32 "'\n", task->time[4]); + printf("t[5]: '%" PRIu32 "'\n", task->time[5]); + printf("t[6]: '%" PRIu32 "'\n", task->time[6]); +} + +void store_database(const task_t* database, uint32_t number_of_entries, const uint8_t* path_name) { + + assert(database != NULL); + assert(path_name != NULL); + + // Open file. + FILE* file = fopen(path_name, "w"); + if (file == NULL) { + fprintf(stderr, "Failed to open database file: %s.\n", strerror(errno)); // TODO Fix message. + return; + } + + fwrite(DB_BIN_FILE_SIGNATURE, sizeof(char), DB_BIN_FILE_SIGNATURE_SIZE, file); + fwrite(&number_of_entries, sizeof(number_of_entries), 1, file); + fwrite(database, SIZEOF_TASK_T, number_of_entries, file); + + fclose(file); +} + +uint32_t load_database(task_t** database, const char* path_name) { + + assert(database != NULL); + assert(path_name != NULL); + + // Open file. + FILE* file = fopen(path_name, "r"); + if (file == NULL) { + fprintf(stderr, "Failed to open database file '%s': %s.\n", path_name, strerror(errno)); // TODO Fix message. + return 0; + } + + // Validate file signature. + char file_signature[DB_BIN_FILE_SIGNATURE_SIZE]; + fread(&file_signature, sizeof(char), DB_BIN_FILE_SIGNATURE_SIZE, file); + if (strncmp(file_signature, DB_BIN_FILE_SIGNATURE, DB_BIN_FILE_SIGNATURE_SIZE) != 0) { + fprintf(stderr, "Invalid file signature.\n"); + return 0; + } + + // Read number of entries. + uint32_t number_of_entries; + fread(&number_of_entries, sizeof(number_of_entries), 1, file); + + // Read database entries. + *database = calloc(number_of_entries, SIZEOF_TASK_T); + fread(*database, SIZEOF_TASK_T, number_of_entries, file); + + // Make sure we are reading all the file. + assert(fgetc(file) == EOF); + + fclose(file); + + return number_of_entries; +} + +void export_database(const task_t* database, uint32_t number_of_entries, const char* path_name) { + + assert(database != NULL); + assert(path_name != NULL); + + FILE* file = fopen(path_name, "w"); + if (file == NULL) { + fprintf(stderr, "Failed to open database file '%s': %s.\n", path_name, strerror(errno)); // TODO Fix message. + return; + } + + fprintf(file, "%s,%s,%s,%s,%s,%s,%s,%s\n", + "task", + DAYS_OF_WEEK[0], + DAYS_OF_WEEK[1], + DAYS_OF_WEEK[2], + DAYS_OF_WEEK[3], + DAYS_OF_WEEK[4], + DAYS_OF_WEEK[5], + DAYS_OF_WEEK[6] + ); + + for (uint32_t idx = 0; idx < number_of_entries; idx++) { + const task_t* task = &database[idx]; + fprintf(file, "%s,%" PRIu32 ",%" PRIu32 ",%" PRIu32 ",%" PRIu32 ",%" PRIu32 ",%" PRIu32 ",%" PRIu32 "\n", + task->name, + task->time[0], + task->time[1], + task->time[2], + task->time[3], + task->time[4], + task->time[5], + task->time[6] + ); + } + + fclose(file); +} + +uint32_t import_database(task_t** database, const char* path_name) { + + assert(database != NULL); + assert(path_name != NULL); + + FILE* file = fopen(path_name, "r"); + if (file == NULL) { + fprintf(stderr, "Failed to open database file '%s': %s.\n", path_name, strerror(errno)); // TODO Fix message. + return 0; + } + + uint32_t number_of_entries = 0; + uint32_t capacity = 0; + task_t task; + int error; + + fscanf(file, "%*[^\n]\n"); // Skip header line. + scan_csv: { + + error = fscanf(file, + "%" STR(MAX_TASK_NAME) "[^,],%" SCNu32 ",%" SCNu32 ",%" SCNu32 ",%" SCNu32 ",%" SCNu32 ",%" SCNu32 ",%" SCNu32 "\n", + task.name, + &task.time[0], + &task.time[1], + &task.time[2], + &task.time[3], + &task.time[4], + &task.time[5], + &task.time[6] + ); + + if (error != EOF) + { + number_of_entries++; + + // Expand tasks capacity if required. + if (number_of_entries > capacity) { + capacity = capacity == 0 ? 16 : capacity << 1; + *database = realloc(*database, capacity * SIZEOF_TASK_T); + if (errno != 0 || errno == ENOMEM) { + ; // TODO realloc failed + } + } + memcpy(&((*database)[number_of_entries-1]), &task, SIZEOF_TASK_T); + + goto scan_csv; + } + } + + assert(number_of_entries == 3); + *database = realloc(*database, number_of_entries * SIZEOF_TASK_T); + // TODO check for allocation errors. +// if (error != EOF) { +// fprintf(stderr, "Failed to parse database file: %s.\n", strerror(errno)); +// } + + + fclose(file); + + return number_of_entries; +} + + +void prt(uint8_t* str, uint8_t size) { // TODO Debug function... to be removed. + fprintf(stderr, ">"); + int count = 0; + for (uint8_t idx = 0; idx < size; idx++) { + if (idx % 2 == 0) { + fprintf(stderr, " "); + } + fprintf(stderr, "%02x", str[idx]); + } + fprintf(stderr, "\n"); +} + +void prototype() { + + int count; + uint8_t size = 20; + uint8_t* test_string; + test_string = calloc(size, sizeof(uint8_t)); + fprintf(stderr, "%s", "çéº𒐫\n"); // C3 A7 - C3 A9 - C2 BA - F0 92 90 AB + sprintf(test_string, "%s", "çéº𒐫\nxpto "); + + prt(test_string, size); +// test_string[10] = '\0'; +// prt(test_string, size); + uint8_t trunc = truncate_string_utf8(test_string, 3); + prt(test_string, size); + fprintf(stderr, ">>> %d : '%s'\n", trunc, test_string); + + + FILE* file = fopen("./test.txt", "w"); + + uint8_t length = strlen(test_string); + fwrite(test_string, sizeof(uint8_t), length, file); + +// fwrite(test_string, sizeof(uint8_t), size, file); + + fclose(file); +// truncate_string(test_string, 8); +// fprintf(stderr, "%s", (char*)test_string); + return; + + + + const char* done = "# -- done -- -- -- /\n"; + + /////////////////////////////////////////////////////////////////////////// + // Get current UTC time. + + fprintf(stderr, "# current UTC time --------------------------- \\\n"); + time_t x = time(NULL); + printf("%s", ctime(&x)); + fprintf(stderr, done); + + + /////////////////////////////////////////////////////////////////////////// + // Get current day of the week. + + fprintf(stderr, "# current day of the week --------------------- \\\n"); + time_t now_ut = time(NULL); + uint8_t week_day = localtime(&now_ut)->tm_wday; + fprintf(stderr, "%d\n", week_day); + fprintf(stderr, done); + + + /////////////////////////////////////////////////////////////////////////// + // Prepare some data for testing. + + tasks_count = 3; + tasks = calloc(tasks_count, SIZEOF_TASK_T); + + task_t tmp[] = { + { + .name = "TASK-00 : Sample task name.", + .time = { 3,2,1,0,1,2,3 }, + }, + { + .name = "TASK-01 : A wild task appears", + .time = { 1,2,3,4,5,6,7 }, + }, + { + .name = "BAZINGA : Not a task!", + .time = { 11,22,33,44,55,66,77 }, + } + }; + memcpy(tasks, &tmp, SIZEOF_TASK_T * tasks_count); + + + /////////////////////////////////////////////////////////////////////////// + // Store database from memory to binary file. + + fprintf(stderr, "# store database ------------------------------ \\\n"); + store_database(tasks, tasks_count, DB_BIN_PATH_NAME); + fprintf(stderr, done); + + + /////////////////////////////////////////////////////////////////////////// + // Load database from binary file to memory. + + fprintf(stderr, "# load database ------------------------------- \\\n"); + task_t* task_load = NULL; + uint32_t number_of_entries_load = load_database(&task_load, DB_BIN_PATH_NAME); + + fprintf(stderr, "loaded %" PRIu32 " entries.\n", number_of_entries_load); + for (uint32_t idx = 0; idx < number_of_entries_load; idx++) { + print_task(&task_load[idx]); + } + free(task_load); + fprintf(stderr, done); + + + /////////////////////////////////////////////////////////////////////////// + // Export database to CSV file. + + fprintf(stderr, "# export to CSV ------------------------------- \\\n"); + export_database(tasks, tasks_count, DB_CSV_PATH_NAME); + fprintf(stderr, done); + + + /////////////////////////////////////////////////////////////////////////// + // Import database from CSV file. + + fprintf(stderr, "# import from CSV ----------------------------- \\\n"); + task_t* task_import = NULL; + uint32_t number_of_entries_import = import_database(&task_import, DB_CSV_PATH_NAME); + + fprintf(stderr, "imported %" PRIu32 " entries.\n", number_of_entries_import); + for (uint32_t idx = 0; idx < number_of_entries_import; idx++) { + print_task(&task_import[idx]); + } + free(task_import); + fprintf(stderr, done); + + + /////////////////////////////////////////////////////////////////////////// + // Release memory and exit. + +// free(tasks); +// return; +} + + +char* line_buffer; +int size_x, size_y; +uint8_t selected_layout = 0; + +#define TABLE_HEADERS_SIZE 9 + +typedef struct { + int table_size; + int table_headers_size; + char* table_headers[TABLE_HEADERS_SIZE]; +} layout_t; + +layout_t* layouts = NULL; + + +void initialize_layouts() { + layout_t* layout = NULL; + + layouts = calloc(2, sizeof(layout_t)); + + // Layout : 0 : normal. + layout = &layouts[0]; + layout->table_size = 8; + layout->table_headers_size = TABLE_HEADERS_SIZE; + // TODO Headers must be dynamic according to FIRST_DAY_OF_WEEK + layout->table_headers[0] = " Task "; + layout->table_headers[1] = " Mon "; + layout->table_headers[2] = " Tue "; + layout->table_headers[3] = " Wed "; + layout->table_headers[4] = " Thu "; + layout->table_headers[5] = " Fri "; + layout->table_headers[6] = " Sat "; + layout->table_headers[7] = " Sun "; + layout->table_headers[8] = " Total "; +// layout->table_headers = { " Task ", " Mon ", " Tue ", " Wed ", " Thu ", " Fri ", " Sat ", " Sun ", " Total " }; + + // Layout : 1 : compact. + layout = &layouts[1]; + layout->table_size = 6; + layout->table_headers_size = TABLE_HEADERS_SIZE; + // TODO Headers must be dynamic according to FIRST_DAY_OF_WEEK + layout->table_headers[0] = " Task "; + layout->table_headers[1] = " M "; + layout->table_headers[2] = " T "; + layout->table_headers[3] = " W "; + layout->table_headers[4] = " T "; + layout->table_headers[5] = " F "; + layout->table_headers[6] = " S "; + layout->table_headers[7] = " S "; + layout->table_headers[8] = " # "; +// layout->table_headers = { " T ", " M ", " T ", " W ", " T ", " F ", " S ", " S ", " # " }; +} + +void free_memory() { + free(line_buffer); + free(tasks); // TODO Deallocate tasks. + free(layouts); +} + +void draw_header() { + + layout_t* layout = &layouts[selected_layout]; + int table_size = layout->table_size; + char** table_headers = layout->table_headers; + + mvaddch(0, 0, ACS_ULCORNER); + for (int idx = 1; idx < size_x-1; idx++) { + addch(ACS_HLINE); + } + addch(ACS_URCORNER); + + int start_columns = size_x - 1 - 8*table_size - 2; + int columns[] = { + 0, + start_columns+(table_size*0), + start_columns+(table_size*1), + start_columns+(table_size*2), + start_columns+(table_size*3), + start_columns+(table_size*4), + start_columns+(table_size*5), + start_columns+(table_size*6), + start_columns+(table_size*7), + }; + + mvaddstr(0, columns[0]+2, table_headers[0]); + for (int idx = 1; idx < 9; idx++) { + mvaddch(0, columns[idx], ACS_TTEE); + mvaddstr(0, columns[idx]+2, table_headers[idx]); + } +} + +void draw_table() { + task_t* task; + int x, y; + + layout_t* layout = &layouts[selected_layout]; + int table_size = layout->table_size; + char** table_headers = layout->table_headers; + + // TODO Colors + start_color(); + init_pair(1, COLOR_BLACK, COLOR_CYAN); + + + int start_columns = size_x - 1 - 8*table_size - 2; + int columns[] = { + 0, + start_columns+(table_size*0), + start_columns+(table_size*1), + start_columns+(table_size*2), + start_columns+(table_size*3), + start_columns+(table_size*4), + start_columns+(table_size*5), + start_columns+(table_size*6), + start_columns+(table_size*7), + size_x-1, + }; + + move(1, 0); + for (uint32_t idx = 0; idx < tasks_count; idx++){ + + task = &tasks[idx]; + + // Enable highlight color on selected entry. + if (idx == selected_task) { + attron(COLOR_PAIR(1)); + } + + // Clear line_buffer and add string termination. + memset(line_buffer, ' ', size_x * sizeof(char)); + line_buffer[size_x-1] = '\0'; + + // Check maximum available space for task name and print it accordingly. + size_t task_name_length = strlen(task->name)*sizeof(char); + size_t max_column_length = start_columns - 2; + size_t copy_length = task_name_length < max_column_length ? task_name_length : max_column_length; + memcpy(line_buffer+2, task->name, copy_length); + addstr(line_buffer); + + // Disable highlight color on selected entry. + if (idx == selected_task) { + attroff(COLOR_PAIR(1)); + } + + // Print columns separators. + getyx(stdscr,y,x); + for (int c_idx = 0; c_idx < sizeof(columns)/sizeof(int); c_idx++) { + mvaddch(y, columns[c_idx], ACS_VLINE); + } + + // Go to next line. + y++; + move(y, 0); + } +} + +void draw_footer() { + const char *app_name = "Task Tracker"; + const char *app_version = "v1.0"; + + int row, col; /* to store the number of rows and the number of colums of the screen */ + initscr(); /* start the curses mode */ + getmaxyx(stdscr,row,col); /* get the number of rows and columns */ + + mvaddch(row-1, 0, ACS_LLCORNER); + addch(' '); + printw(app_name); + addch(' '); + for (int idx = strlen(app_name) + 3; idx < col - strlen(app_version) - 3; idx ++) { + addch(ACS_HLINE); + } + addch(' '); + printw(app_version); + addch(' '); + addch(ACS_LRCORNER); +} + +WINDOW *create_newwin(int height, int width, int starty, int startx); +void destroy_win(WINDOW *local_win); + +int main(int argc, char *argv[]) { + + // TODO Parse commands using: https://stackoverflow.com/questions/9642732/parsing-command-line-arguments-in-c + if (argc > 1) { +// const char* command_version = { "--version", "-v" }; + + for (int idx = 1; idx < argc; idx++) { + if (strncmp(argv[idx], "-v", 2) == 0 || strncmp(argv[idx], "--version", 9) == 0) { + fprintf(stdout, "Task Time Tracker v1.0\n"); + return EXIT_SUCCESS; + } + } + + + fprintf(stdout, "Unkown command '%s'.\nUse '%s --help' for list of commands.\n", argv[1], argv[0]); + return EXIT_FAILURE; + } + + + + prototype(); // TODO + return EXIT_FAILURE; + initialize_layouts(); + + WINDOW *my_win; + int startx, starty, width, height; + int ch; + + initscr(); /* Start curses mode */ + cbreak(); /* Line buffering disabled, Pass on + * everty thing to me */ + keypad(stdscr, TRUE); /* I need that nifty F1 */ + curs_set(0); // Set cursor invisible. + + height = 3; + width = 10; + starty = (LINES - height) / 2; /* Calculating for a center placement */ + startx = (COLS - width) / 2; /* of the window */ + printw("Press F1 to exit"); + refresh(); + my_win = create_newwin(height, width, starty, startx); + + int rows; + int colums; + + ch = KEY_RESIZE; + do { + switch(ch) + { + case KEY_RESIZE: + erase(); + if (line_buffer != NULL) { + free(line_buffer); + } + getmaxyx(stdscr, size_y, size_x); + line_buffer = malloc(size_x * sizeof(char)); + break; + + case KEY_LEFT: + destroy_win(my_win); + my_win = create_newwin(height, width, starty,--startx); + break; + + case KEY_RIGHT: + destroy_win(my_win); + my_win = create_newwin(height, width, starty,++startx); + break; + + case KEY_UP: + selected_task = selected_task == 0 ? 0 : selected_task - 1; + destroy_win(my_win); + my_win = create_newwin(height, width, --starty,startx); + break; + + case KEY_DOWN: + selected_task = (selected_task+1) == tasks_count ? selected_task : selected_task + 1; + destroy_win(my_win); + my_win = create_newwin(height, width, ++starty,startx); + break; + } + + selected_layout = size_x > 100 ? 0 : 1; + + if (size_x >= 60 && size_y > 2) { + draw_header(); + draw_table(); + draw_footer(); + } + else { + const char* INVALID_WINDOW_MESSAGE = "Please expand window."; + mvaddstr(size_y / 2, (size_x - strlen(INVALID_WINDOW_MESSAGE)) / 2, INVALID_WINDOW_MESSAGE); + } + + } while((ch = getch()) != KEY_F(1)); + + free_memory(); + endwin(); + return EXIT_SUCCESS; +} + +WINDOW *create_newwin(int height, int width, int starty, int startx) { + WINDOW *local_win; + + local_win = newwin(height, width, starty, startx); + box(local_win, 0 , 0); /* 0, 0 gives default characters + * for the vertical and horizontal + * lines */ + wrefresh(local_win); /* Show that box */ + + return local_win; +} + +void destroy_win(WINDOW *local_win) { + /* box(local_win, ' ', ' '); : This won't produce the desired + * result of erasing the window. It will leave it's four corners + * and so an ugly remnant of window. + */ + wborder(local_win, ' ', ' ', ' ',' ',' ',' ',' ',' '); + /* The parameters taken are + * 1. win: the window on which to operate + * 2. ls: character to be used for the left side of the window + * 3. rs: character to be used for the right side of the window + * 4. ts: character to be used for the top side of the window + * 5. bs: character to be used for the bottom side of the window + * 6. tl: character to be used for the top left corner of the window + * 7. tr: character to be used for the top right corner of the window + * 8. bl: character to be used for the bottom left corner of the window + * 9. br: character to be used for the bottom right corner of the window + */ + wrefresh(local_win); + delwin(local_win); +} diff --git a/misc.c b/misc.c new file mode 100644 index 0000000..a192f18 --- /dev/null +++ b/misc.c @@ -0,0 +1,66 @@ + +uint32_t CRC32(const uint8_t data[], size_t data_length) { + + static const uint32_t crc32_table[] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, + 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, + 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, + 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, + 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, + 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, + 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, + 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, + 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, + 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, + 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, + 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, + 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, + 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, + 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, + 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, + 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, + 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, + 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, + 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d + }; + + uint32_t crc32 = 0xFFFFFFFFu; + + for (size_t i = 0; i < data_length; i++) { + const uint32_t lookupIndex = (crc32 ^ data[i]) & 0xff; + crc32 = (crc32 >> 8) ^ crc32_table[lookupIndex]; + } + + // Finalize the CRC-32 value by inverting all the bits + crc32 ^= 0xFFFFFFFFu; + return crc32; +} + +uint32_t hash_string(const char* string) { + size_t data_length = strlen(string); + uint8_t* data = (uint8_t*)string; + return CRC32(data, data_length); +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..d008702 --- /dev/null +++ b/readme.md @@ -0,0 +1,29 @@ +Task Time Tracker +================= + +# notes +- Only one task may be active; +- The log will be a circular array. During app startup the array will be loaded from a csv file. The log array should have a fixed length. Each string in the array should also have fixed length. Loading and storing it to a file will be implemented using readline and writeline operations. In order for the log file to be CSV compatible, we must always use the same format when writing to the log array. Best approach is to use a function that enfores the CSV format. The log should be written to a file every 5 minutes (not set in stone), if required; such may be done using a `log_is_dirty` flag. A possible structure for the log entries is: {uint64_t timestamp; uint8_t action[16]; uint8_t task_name[MAX_TASK_NAME+1]; uint8_t notes[16]; }. +- Truncate UTF8 string: + - If null-termination is found at or before the max_size; + +# to-do list +- [ ] Include check on number of char bits; +- [x] maybe rename to task-time-tracker? +- [ ] Remove hash stuff; +- [ ] Tasks should have a `modified_on` timestamp field; +- [ ] Status of task will allow to keep counting time even when the process gets terminated forcefully; +- [ ] Make sure task names don't include commas ','; +- [ ] Review code: char !uint8_t; +- [ ] Change task order (use task_t tmp_task + memcpy); +- [ ] Create task using keys: `c` and `C`; +- [ ] Delete task using keys: `d` and `D`; +- [ ] Change task name using keys: `F2`; +- [ ] Add/remove time using keys: `F3`; +- [ ] Add/remove time for any day of week; +- [ ] Toggle task visibility using keys: `v` and `V`; when pressed, all tasks are shown and hidden tasks are shown in a ghosty theme; Ruggin task cannot be hidden; +- [ ] Use TASK_STATUS enum [NONE, RUNNING, ARCHIVED]; +- [ ] Clone (replicate) task using keys: `r` and `R`; If task is active, mark newly created task as inactive; +- [ ] Space invaders on konami code; +- [ ] Rethink keys; +- [ ] Make sure that only one task is running at each time; diff --git a/task_tracker.c b/task_tracker.c deleted file mode 100644 index 02ed8c3..0000000 --- a/task_tracker.c +++ /dev/null @@ -1,724 +0,0 @@ -// Compilation command: -// - release: gcc task_tracker.c -Wall -O2 -m64 -lncurses -o task_tracker.x64 -// - debug : gcc task_tracker.c -Wall -g3 -m64 -lncurses -o task_tracker.x64 - - -#include -#include -#include -#include -#include -#include -#include -#include - -#define STR_(X) #X // Convert to string. -#define STR(X) STR_(X) // Force argument expansions before converting to string. - -#define MAX_TASK_NAME 127 -#define FIRST_DAY_OF_WEEK 1 // (0-6, Sunday = 0) - -#define LOG_FILE_NAME "log.txt" -#define DB_BIN_PATH_NAME "./database.bin" -#define DB_CSV_PATH_NAME "./database.csv" - - - - -typedef struct { - char name[MAX_TASK_NAME+1]; // Allow space for null termination char. - uint32_t time[7]; - uint8_t state; - uint32_t hash; // TODO Is this an overkill? Maybe decouple from task_t? -} task_t; - - -const char* DB_BIN_FILE_SIGNATURE = "TTBF:1.0"; -const uint8_t DB_BIN_FILE_SIGNATURE_SIZE = sizeof(DB_BIN_FILE_SIGNATURE); -const size_t SIZEOF_TASK_T = sizeof(task_t); -const char* DAYS_OF_WEEK[] = { "sun", "mon", "tue", "wed", "thu", "fri", "sat" }; -const uint8_t DAYS_ON_WEEK = sizeof(DAYS_OF_WEEK)/sizeof(char*); - -task_t* tasks = NULL; -uint32_t tasks_count = 0; -uint32_t tasks_capacity = 0; -uint32_t selected_task = -1; - - -char* replace_char(char* string, char find, char replace){ - char *idx = string; - while((idx = strchr(idx, find)) != NULL) { - *idx = replace; - *idx++; - } - return string; -} - -uint32_t CRC32(const uint8_t data[], size_t data_length) { - - static const uint32_t crc32_table[] = { - 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, - 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, - 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, - 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, - 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, - 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, - 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, - 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, - 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, - 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, - 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, - 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, - 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, - 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, - 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, - 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, - 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, - 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, - 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, - 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, - 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, - 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, - 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, - 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, - 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, - 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, - 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, - 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, - 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, - 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, - 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, - 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, - 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, - 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, - 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, - 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, - 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, - 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, - 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, - 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, - 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, - 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, - 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d - }; - - uint32_t crc32 = 0xFFFFFFFFu; - - for (size_t i = 0; i < data_length; i++) { - const uint32_t lookupIndex = (crc32 ^ data[i]) & 0xff; - crc32 = (crc32 >> 8) ^ crc32_table[lookupIndex]; - } - - // Finalize the CRC-32 value by inverting all the bits - crc32 ^= 0xFFFFFFFFu; - return crc32; -} - -uint32_t hash_string(const char* string) { - size_t data_length = strlen(string); - uint8_t* data = (uint8_t*)string; - return CRC32(data, data_length); -} - -void print_task(const task_t* task) { - printf("name: '%s'\n", task->name); - printf("hash: '%" PRIu32 "'\n", task->hash); - printf("t[0]: '%" PRIu32 "'\n", task->time[0]); - printf("t[1]: '%" PRIu32 "'\n", task->time[1]); - printf("t[2]: '%" PRIu32 "'\n", task->time[2]); - printf("t[3]: '%" PRIu32 "'\n", task->time[3]); - printf("t[4]: '%" PRIu32 "'\n", task->time[4]); - printf("t[5]: '%" PRIu32 "'\n", task->time[5]); - printf("t[6]: '%" PRIu32 "'\n", task->time[6]); -} - -void store_database(const task_t* database, uint32_t number_of_entries, const char* path_name) { - - assert(database != NULL); - assert(path_name != NULL); - - // Open file. - FILE* file = fopen(path_name, "w"); - if (file == NULL) { - fprintf(stderr, "Failed to open database file: %s.\n", strerror(errno)); // TODO Fix message. - return; - } - - fwrite(DB_BIN_FILE_SIGNATURE, sizeof(char), DB_BIN_FILE_SIGNATURE_SIZE, file); - fwrite(&number_of_entries, sizeof(number_of_entries), 1, file); - fwrite(database, SIZEOF_TASK_T, number_of_entries, file); - - fclose(file); -} - -uint32_t load_database(task_t** database, const char* path_name) { - - assert(database != NULL); - assert(path_name != NULL); - - // Open file. - FILE* file = fopen(path_name, "r"); - if (file == NULL) { - fprintf(stderr, "Failed to open database file '%s': %s.\n", path_name, strerror(errno)); // TODO Fix message. - return 0; - } - - // Validate file signature. - char file_signature[DB_BIN_FILE_SIGNATURE_SIZE]; - fread(&file_signature, sizeof(char), DB_BIN_FILE_SIGNATURE_SIZE, file); - if (strncmp(file_signature, DB_BIN_FILE_SIGNATURE, DB_BIN_FILE_SIGNATURE_SIZE) != 0) { - fprintf(stderr, "Invalid file signature.\n"); - return 0; - } - - // Read number of entries. - uint32_t number_of_entries; - fread(&number_of_entries, sizeof(number_of_entries), 1, file); - - // Read database entries. - *database = calloc(number_of_entries, SIZEOF_TASK_T); - fread(*database, SIZEOF_TASK_T, number_of_entries, file); - - // Make sure we are reading all the file. - assert(fgetc(file) == EOF); - - fclose(file); - - return number_of_entries; -} - -void export_database(const task_t* database, uint32_t number_of_entries, const char* path_name) { - - assert(database != NULL); - assert(path_name != NULL); - - FILE* file = fopen(path_name, "w"); - if (file == NULL) { - fprintf(stderr, "Failed to open database file '%s': %s.\n", path_name, strerror(errno)); // TODO Fix message. - return; - } - - fprintf(file, "%s,%s,%s,%s,%s,%s,%s,%s\n", - "task", - DAYS_OF_WEEK[0], - DAYS_OF_WEEK[1], - DAYS_OF_WEEK[2], - DAYS_OF_WEEK[3], - DAYS_OF_WEEK[4], - DAYS_OF_WEEK[5], - DAYS_OF_WEEK[6] - ); - - for (uint32_t idx = 0; idx < number_of_entries; idx++) { - const task_t* task = &database[idx]; - fprintf(file, "%s,%" PRIu32 ",%" PRIu32 ",%" PRIu32 ",%" PRIu32 ",%" PRIu32 ",%" PRIu32 ",%" PRIu32 "\n", - task->name, - task->time[0], - task->time[1], - task->time[2], - task->time[3], - task->time[4], - task->time[5], - task->time[6] - ); - } - - fclose(file); -} - -uint32_t import_database(task_t** database, const char* path_name) { - - assert(database != NULL); - assert(path_name != NULL); - - FILE* file = fopen(path_name, "r"); - if (file == NULL) { - fprintf(stderr, "Failed to open database file '%s': %s.\n", path_name, strerror(errno)); // TODO Fix message. - return 0; - } - - uint32_t number_of_entries = 0; - uint32_t capacity = 0; - task_t task; - int error; - - fscanf(file, "%*[^\n]\n"); // Skip header line. - scan_csv: { - - error = fscanf(file, - "%" STR(MAX_TASK_NAME) "[^,],%" SCNu32 ",%" SCNu32 ",%" SCNu32 ",%" SCNu32 ",%" SCNu32 ",%" SCNu32 ",%" SCNu32 "\n", - task.name, - &task.time[0], - &task.time[1], - &task.time[2], - &task.time[3], - &task.time[4], - &task.time[5], - &task.time[6] - ); - - if (error != EOF) - { - task.hash = hash_string(task.name); - number_of_entries++; - - // Expand tasks capacity if required. - if (number_of_entries > capacity) { - capacity = capacity == 0 ? 16 : capacity << 1; - *database = reallocarray(*database, capacity, SIZEOF_TASK_T); - if (errno != 0 || errno == ENOMEM) { - ; // TODO reallocarray failed - } - } - memcpy(&((*database)[number_of_entries-1]), &task, SIZEOF_TASK_T); - - goto scan_csv; - } - } - - assert(number_of_entries == 3); - *database = reallocarray(*database, number_of_entries, SIZEOF_TASK_T); - // TODO check for allocation errors. -// if (error != EOF) { -// fprintf(stderr, "Failed to parse database file: %s.\n", strerror(errno)); -// } - - - fclose(file); - - return number_of_entries; -} - -void add_task(char* name) { - - // Exit if task has already been added. - uint32_t name_hash = CRC32((uint8_t*)name, sizeof(name)); - for (uint32_t idx = 0; idx < tasks_count; idx++) - { - if (name_hash == tasks[idx].hash && strcmp(name, tasks[idx].name) == 0) - { - return; - } - } - - // Expand tasks capacity if required. - if (tasks_count >= tasks_capacity) - { - tasks_capacity = tasks_capacity == 0 ? 16 : tasks_capacity << 1; - tasks = realloc(tasks, SIZEOF_TASK_T * tasks_capacity); - } - - // Add new task entry. - task_t* new_task = &tasks[tasks_count]; - memset(new_task, '0', SIZEOF_TASK_T); - strncpy(new_task->name, name, MAX_TASK_NAME); - new_task->name[MAX_TASK_NAME] = '\0'; - printf(">>> %s\n", new_task->name); // TODO debug - new_task->hash = name_hash; - tasks_count++; -} - -void prototype() { - - const char* done = "# -- done -- -- -- /\n"; - - /////////////////////////////////////////////////////////////////////////// - // Get current UTC time. - - fprintf(stderr, "# current UTC time --------------------------- \\\n"); - time_t x = time(NULL); - printf("%s", ctime(&x)); - fprintf(stderr, done); - - - /////////////////////////////////////////////////////////////////////////// - // Get current day of the week. - - fprintf(stderr, "# current day of the week --------------------- \\\n"); - time_t now_ut = time(NULL); - uint8_t week_day = localtime(&now_ut)->tm_wday; - fprintf(stderr, "%d\n", week_day); - fprintf(stderr, done); - - - /////////////////////////////////////////////////////////////////////////// - // Prepare some data for testing. - - tasks_count = 3; - tasks = calloc(tasks_count, SIZEOF_TASK_T); - - task_t tmp[] = { - { - .name = "TASK-00 : Sample task name.", - .time = { 3,2,1,0,1,2,3 }, - .hash = hash_string("TASK-00 : Sample task name.") - }, - { - .name = "TASK-01 : A wild task appears", - .time = { 1,2,3,4,5,6,7 }, - .hash = hash_string("TASK-01 : A wild task appears") - }, - { - .name = "BAZINGA : Not a task!", - .time = { 11,22,33,44,55,66,77 }, - .hash = hash_string("BAZINGA : Not a task!") - } - }; - memcpy(tasks, &tmp, SIZEOF_TASK_T * tasks_count); - - - /////////////////////////////////////////////////////////////////////////// - // Store database from memory to binary file. - - fprintf(stderr, "# store database ------------------------------ \\\n"); - store_database(tasks, tasks_count, DB_BIN_PATH_NAME); - fprintf(stderr, done); - - - /////////////////////////////////////////////////////////////////////////// - // Load database from binary file to memory. - - fprintf(stderr, "# load database ------------------------------- \\\n"); - task_t* task_load = NULL; - uint32_t number_of_entries_load = load_database(&task_load, DB_BIN_PATH_NAME); - - fprintf(stderr, "loaded %" PRIu32 " entries.\n", number_of_entries_load); - for (uint32_t idx = 0; idx < number_of_entries_load; idx++) { - print_task(&task_load[idx]); - } - free(task_load); - fprintf(stderr, done); - - - /////////////////////////////////////////////////////////////////////////// - // Export database to CSV file. - - fprintf(stderr, "# export to CSV ------------------------------- \\\n"); - export_database(tasks, tasks_count, DB_CSV_PATH_NAME); - fprintf(stderr, done); - - - /////////////////////////////////////////////////////////////////////////// - // Import database from CSV file. - - fprintf(stderr, "# import from CSV ----------------------------- \\\n"); - task_t* task_import = NULL; - uint32_t number_of_entries_import = import_database(&task_import, DB_CSV_PATH_NAME); - - fprintf(stderr, "imported %" PRIu32 " entries.\n", number_of_entries_import); - for (uint32_t idx = 0; idx < number_of_entries_import; idx++) { - print_task(&task_import[idx]); - } - free(task_import); - fprintf(stderr, done); - - - /////////////////////////////////////////////////////////////////////////// - // Release memory and exit. - -// free(tasks); -// return; -} - - -char* line_buffer; -int size_x, size_y; -uint8_t selected_layout = 0; - -#define TABLE_HEADERS_SIZE 9 - -typedef struct { - int table_size; - int table_headers_size; - char* table_headers[TABLE_HEADERS_SIZE]; -} layout_t; - -layout_t* layouts = NULL; -// { -// .table_size = 8, -// .table_headers_size = 9, -// .table_headers = { " Task ", " Mon ", " Tue ", " Wed ", " Thu ", " Fri ", " Sat ", " Sun ", " Total " } -// }, -// { -// .table_size = 6, -// .table_headers_size = 9, -// .table_headers = { " T ", " M ", " T ", " W ", " T ", " F ", " S ", " S ", " # " } -// } -// }; - -void initialize_layouts() { - layout_t* layout = NULL; - - layouts = calloc(2, sizeof(layout_t)); - - // Layout : 0 : normal. - layout = &layouts[0]; - layout->table_size = 8; - layout->table_headers_size = TABLE_HEADERS_SIZE; - layout->table_headers[0] = " Task "; - layout->table_headers[1] = " Mon "; - layout->table_headers[2] = " Tue "; - layout->table_headers[3] = " Wed "; - layout->table_headers[4] = " Thu "; - layout->table_headers[5] = " Fri "; - layout->table_headers[6] = " Sat "; - layout->table_headers[7] = " Sun "; - layout->table_headers[8] = " Total "; -// layout->table_headers = { " Task ", " Mon ", " Tue ", " Wed ", " Thu ", " Fri ", " Sat ", " Sun ", " Total " }; - - // Layout : 1 : compact. - layout = &layouts[1]; - layout->table_size = 6; - layout->table_headers_size = TABLE_HEADERS_SIZE; - layout->table_headers[0] = " Task "; - layout->table_headers[1] = " M "; - layout->table_headers[2] = " T "; - layout->table_headers[3] = " W "; - layout->table_headers[4] = " T "; - layout->table_headers[5] = " F "; - layout->table_headers[6] = " S "; - layout->table_headers[7] = " S "; - layout->table_headers[8] = " # "; -// layout->table_headers = { " T ", " M ", " T ", " W ", " T ", " F ", " S ", " S ", " # " }; -} - -void free_memory() { - free(line_buffer); - free(tasks); // TODO Deallocate tasks. - free(layouts); -} - -void draw_header() { - - layout_t* layout = &layouts[selected_layout]; - int table_size = layout->table_size; - char** table_headers = layout->table_headers; - - mvaddch(0, 0, ACS_ULCORNER); - for (int idx = 1; idx < size_x-1; idx++) { - addch(ACS_HLINE); - } - addch(ACS_URCORNER); - - int start_columns = size_x - 1 - 8*table_size - 2; - int columns[] = { - 0, - start_columns+(table_size*0), - start_columns+(table_size*1), - start_columns+(table_size*2), - start_columns+(table_size*3), - start_columns+(table_size*4), - start_columns+(table_size*5), - start_columns+(table_size*6), - start_columns+(table_size*7), - }; - - mvaddstr(0, columns[0]+2, table_headers[0]); - for (int idx = 1; idx < 9; idx++) { - mvaddch(0, columns[idx], ACS_TTEE); - mvaddstr(0, columns[idx]+2, table_headers[idx]); - } -} - -void draw_table() { - task_t* task; - int x, y; - - layout_t* layout = &layouts[selected_layout]; - int table_size = layout->table_size; - char** table_headers = layout->table_headers; - - // TODO Colors - start_color(); - init_pair(1, COLOR_BLACK, COLOR_CYAN); - - - int start_columns = size_x - 1 - 8*table_size - 2; - int columns[] = { - 0, - start_columns+(table_size*0), - start_columns+(table_size*1), - start_columns+(table_size*2), - start_columns+(table_size*3), - start_columns+(table_size*4), - start_columns+(table_size*5), - start_columns+(table_size*6), - start_columns+(table_size*7), - size_x-1, - }; - - move(1, 0); - for (uint32_t idx = 0; idx < tasks_count; idx++){ - - task = &tasks[idx]; - - // Enable highlight color on selected entry. - if (idx == selected_task) { - attron(COLOR_PAIR(1)); - } - - // Clear line_buffer and add string termination. - memset(line_buffer, ' ', size_x * sizeof(char)); - line_buffer[size_x-1] = '\0'; - - // Check maximum available space for task name and print it accordingly. - size_t task_name_length = strlen(task->name)*sizeof(char); - size_t max_column_length = start_columns - 2; - size_t copy_length = task_name_length < max_column_length ? task_name_length : max_column_length; - memcpy(line_buffer+2, task->name, copy_length); - addstr(line_buffer); - - // Disable highlight color on selected entry. - if (idx == selected_task) { - attroff(COLOR_PAIR(1)); - } - - // Print columns separators. - getyx(stdscr,y,x); - for (int c_idx = 0; c_idx < sizeof(columns)/sizeof(int); c_idx++) { - mvaddch(y, columns[c_idx], ACS_VLINE); - } - - // Go to next line. - y++; - move(y, 0); - } -} - -void draw_footer() { - const char *app_name = "Task Tracker"; - const char *app_version = "v1.0"; - - int row, col; /* to store the number of rows and the number of colums of the screen */ - initscr(); /* start the curses mode */ - getmaxyx(stdscr,row,col); /* get the number of rows and columns */ - - mvaddch(row-1, 0, ACS_LLCORNER); - addch(' '); - printw(app_name); - addch(' '); - for (int idx = strlen(app_name) + 3; idx < col - strlen(app_version) - 3; idx ++) { - addch(ACS_HLINE); - } - addch(' '); - printw(app_version); - addch(' '); - addch(ACS_LRCORNER); -} - -WINDOW *create_newwin(int height, int width, int starty, int startx); -void destroy_win(WINDOW *local_win); - -int main(int argc, char *argv[]) { - - prototype(); // TODO - initialize_layouts(); - - WINDOW *my_win; - int startx, starty, width, height; - int ch; - - initscr(); /* Start curses mode */ - cbreak(); /* Line buffering disabled, Pass on - * everty thing to me */ - keypad(stdscr, TRUE); /* I need that nifty F1 */ - curs_set(0); // Set cursor invisible. - - height = 3; - width = 10; - starty = (LINES - height) / 2; /* Calculating for a center placement */ - startx = (COLS - width) / 2; /* of the window */ - printw("Press F1 to exit"); - refresh(); - my_win = create_newwin(height, width, starty, startx); - - int rows; - int colums; - - ch = KEY_RESIZE; - do { - switch(ch) - { - case KEY_RESIZE: - erase(); - if (line_buffer != NULL) { - free(line_buffer); - } - getmaxyx(stdscr, size_y, size_x); - line_buffer = malloc(size_x * sizeof(char)); - break; - - case KEY_LEFT: - destroy_win(my_win); - my_win = create_newwin(height, width, starty,--startx); - break; - - case KEY_RIGHT: - destroy_win(my_win); - my_win = create_newwin(height, width, starty,++startx); - break; - - case KEY_UP: - selected_task = selected_task == 0 ? 0 : selected_task - 1; - destroy_win(my_win); - my_win = create_newwin(height, width, --starty,startx); - break; - - case KEY_DOWN: - selected_task = (selected_task+1) == tasks_count ? selected_task : selected_task + 1; - destroy_win(my_win); - my_win = create_newwin(height, width, ++starty,startx); - break; - } - - selected_layout = size_x > 100 ? 0 : 1; - - if (size_x >= 60 && size_y > 2) { - draw_header(); - draw_table(); - draw_footer(); - } - else { - const char* INVALID_WINDOW_MESSAGE = "Please expand window."; - mvaddstr(size_y / 2, (size_x - strlen(INVALID_WINDOW_MESSAGE)) / 2, INVALID_WINDOW_MESSAGE); - } - - } while((ch = getch()) != KEY_F(1)); - - free_memory(); - endwin(); - return 0; -} - -WINDOW *create_newwin(int height, int width, int starty, int startx) { - WINDOW *local_win; - - local_win = newwin(height, width, starty, startx); - box(local_win, 0 , 0); /* 0, 0 gives default characters - * for the vertical and horizontal - * lines */ - wrefresh(local_win); /* Show that box */ - - return local_win; -} - -void destroy_win(WINDOW *local_win) { - /* box(local_win, ' ', ' '); : This won't produce the desired - * result of erasing the window. It will leave it's four corners - * and so an ugly remnant of window. - */ - wborder(local_win, ' ', ' ', ' ',' ',' ',' ',' ',' '); - /* The parameters taken are - * 1. win: the window on which to operate - * 2. ls: character to be used for the left side of the window - * 3. rs: character to be used for the right side of the window - * 4. ts: character to be used for the top side of the window - * 5. bs: character to be used for the bottom side of the window - * 6. tl: character to be used for the top left corner of the window - * 7. tr: character to be used for the top right corner of the window - * 8. bl: character to be used for the bottom left corner of the window - * 9. br: character to be used for the bottom right corner of the window - */ - wrefresh(local_win); - delwin(local_win); -} -- cgit v1.2.3