// Compilation command: // - release: gcc main.c -Wall -O2 -m64 -lncurses -o ttt // - debug : gcc main.c -Wall -g3 -m64 -lncurses -o ttt -D DEBUG #include #include #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 58 // Maximum task name length, including null-terminator. #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" #define DB_MAX_CAP PTRDIFF_MAX typedef struct /*__attribute__((__packed__))*/ { uint32_t time[7]; char name[MAX_TASK_NAME]; } task_t; typedef struct /*__attribute__((__packed__))*/ { uint64_t modified_on; size_t count; size_t capacity; task_t *tasks; ptrdiff_t active_task; ptrdiff_t selected_task; // TODO Maybe use this instead of indexes? } database_t; const char *DB_FILE_SIGN = "TTT:B:01"; const size_t SIZEOF_DB_FILE_SIGN = sizeof(DB_FILE_SIGN); const size_t SIZEOF_TASK_T = sizeof(task_t); const size_t SIZEOF_DATABASE_T = sizeof(database_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*); database_t database; database_t archive; // TODO To be implemented in the future. // Given an UTF8 encoded string, truncate it to length without breaking any UTF8 character. // The string should have capacity for 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(char *string, size_t length) { // Check for special cases where no truncation is required. if (length == 0 || string[length-1] == '\0') { return 0; } // Search for a non-UTF8-sequence-item so we can truncate the string. size_t idx = length - 1; while(idx > 0 && ((string[idx] & 0xC0) == 0x80)) { idx--; } string[idx] = '\0'; return length - idx; } // Uses strchr to replace all instances of find by replace. // Returns string. 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]); } // Macro used to calculate index of task on a database. #define task_idx(db,task) ((ptrdiff_t)(task - db->tasks)) // Creates new task returned in the pointer. If necessary, expands database capacity. // Returns success. bool create_task(database_t *db, task_t **task) { assert(db != NULL); if (db->count == DB_MAX_CAP) { fprintf(stderr, "Database reached maximum capacity.\n"); return false; } // If necessary, expand database capacity. size_t current_capacity = db->capacity; if((db->count + 1) > current_capacity) { size_t new_capacity = current_capacity == 0 ? 2 : current_capacity >= DB_MAX_CAP >> 2 ? DB_MAX_CAP : current_capacity << 1; task_t *new_tasks = realloc(db->tasks, new_capacity * SIZEOF_TASK_T); if (new_tasks == NULL) { fprintf(stderr, "Failed to expand database.\n"); return false; } db->capacity = new_capacity; db->tasks = new_tasks; } // Prepare new task. *task = &db->tasks[db->count]; memset(*task, 0, SIZEOF_TASK_T); db->count++; // Adjust selected task. if (db->selected_task < 0) { db->selected_task = db->count-1; } return true; } bool delete_task(database_t *db, task_t *task) { assert(db != NULL); assert(task != NULL); assert(task >= db->tasks && task < &db->tasks[db->count]); // Move tasks after the index position to their new positions. ptrdiff_t index = task - db->tasks; memmove(task, task+1, (db->count - index - 1) * SIZEOF_TASK_T); db->count--; // Adjust selected task. if (db->selected_task >= db->count) { db->selected_task--; } // Adjust active task. if (db->active_task > index) { db->active_task--; } else if (db->active_task == index) { db->active_task = -1; } // If possible, shrink database capacity. size_t current_capacity = db->capacity; if (db->count <= (current_capacity >> 2)) { size_t new_capacity = current_capacity >> 1; task_t *new_tasks = realloc(db->tasks, new_capacity * SIZEOF_TASK_T); if (new_tasks == NULL) { fprintf(stderr, "Failed to shrink database.\n"); return false; } db->capacity = new_capacity; db->tasks = new_tasks; } return true; } void clear_database(database_t *db) { free(db->tasks); memset(db, 0, SIZEOF_TASK_T); } // Stores data from database into binary file. // Returns success. bool store_database(const database_t *db, const char *path_name) { assert(db != NULL); assert(path_name != NULL); // Open file. FILE *file = fopen(path_name, "w"); if (file == NULL) { fprintf(stderr, "Failed to open file '%s' while storing database: %s.\n", path_name, strerror(errno)); return false; } fwrite(DB_FILE_SIGN, sizeof(char), SIZEOF_DB_FILE_SIGN, file); fwrite(db, SIZEOF_DATABASE_T, 1, file); fwrite(db->tasks, SIZEOF_TASK_T, db->count, file); fclose(file); return true; } // Loads data from binary file into database. // Returns success. bool load_database(database_t *db, const char *path_name) { assert(db != NULL); assert(path_name != NULL); // Open file. FILE *file = fopen(path_name, "r"); if (file == NULL) { fprintf(stderr, "Failed to open file '%s' while loading database: %s.\n", path_name, strerror(errno)); return false; } // Validate file signature. char file_signature[SIZEOF_DB_FILE_SIGN]; fread(&file_signature, sizeof(char), SIZEOF_DB_FILE_SIGN, file); if (strncmp(file_signature, DB_FILE_SIGN, SIZEOF_DB_FILE_SIGN) != 0) { fprintf(stderr, "Invalid file signature.\n"); return false; } // Read database structure. fread(db, SIZEOF_DATABASE_T, 1, file); // Restore database capacity. db->tasks = calloc(db->capacity, SIZEOF_TASK_T); // Read database entries. fread(db->tasks, SIZEOF_TASK_T, db->count, file); // Make sure we are reading all the file. assert(fgetc(file) == EOF); fclose(file); return true; } // Exports data into CSV file. // Returns success. bool export_to_csv(const database_t *db, const char *path_name) { assert(db != NULL); assert(path_name != NULL); FILE *file = fopen(path_name, "w"); if (file == NULL) { fprintf(stderr, "Failed to open file '%s' while exporting to CSV: %s.\n", path_name, strerror(errno)); return false; } 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 < db->count; idx++) { const task_t *task = &db->tasks[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); return true; } // Imports CSV file into database. // Returns success. bool import_from_csv(database_t *db, const char *path_name) { assert(db != NULL); assert(path_name != NULL); FILE *file = fopen(path_name, "r"); if (file == NULL) { fprintf(stderr, "Failed to open file '%s' while importing from CSV: %s.\n", path_name, strerror(errno)); return false; } uint32_t number_of_entries = 0; task_t *task; char *line_buffer = NULL; size_t line_buffer_size = 0; ssize_t read_characters = 0; char *name_delimiter; // Skip header line. fscanf(file, "%*[^\n]\n"); // Parse CSV file. while(true) { read_characters = getline(&line_buffer, &line_buffer_size, file); // Check if reached EOF. if (read_characters == -1) { break; } // Prepare new task. create_task(db, &task); // Find task name string limits. name_delimiter = strchr(line_buffer, ','); size_t name_length = (name_delimiter - line_buffer) + 1; if (name_length > MAX_TASK_NAME) { name_length = MAX_TASK_NAME; } // Import task name. memcpy(task->name, line_buffer, name_length); truncate_string_utf8(task->name, name_length); // Parse task times. if(sscanf(name_delimiter+1, "%" SCNu32 ",%" SCNu32 ",%" SCNu32 ",%" SCNu32 ",%" SCNu32 ",%" SCNu32 ",%" SCNu32, &task->time[0], &task->time[1], &task->time[2], &task->time[3], &task->time[4], &task->time[5], &task->time[6] ) != 7) { replace_char(line_buffer, '\n', ' '); fprintf(stderr, "Discarding invalid line '%s' and continuing.\n", line_buffer); delete_task(db, task); continue; } // Add new database entry. number_of_entries++; } fclose(file); free(line_buffer); return true; } enum TEST { T_NONE = 0x00, T_UTC = 0x01, T_DAY = 0x02, T_SBIN = 0x04, T_LBIN = 0x08, T_ECSV = 0x10, T_ICSV = 0x20, T_SOS = 0x40, T_ALL = 0xFF, }; void prototype(int level) { const char *done = "# -- done -- -- -- /\n"; /////////////////////////////////////////////////////////////////////////// // Get current UTC time. if (level & T_UTC) { fprintf(stderr, "# current UTC time --------------------------- \\\n"); time_t x = time(NULL); printf("%s", ctime(&x)); fprintf(stderr, done); } /////////////////////////////////////////////////////////////////////////// // Get current day of the week. if (level & T_DAY) { 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. /* task_t tmp[] = { { .name = "ALPHA-TASK", .time = { 0, 0, 0, 0, 0, 0, 0 }, }, { .name = "BETA-TASK", .time = { 1, 1, 1, 1, 1, 1, 1 }, }, { .name = "DELTA-TASK", .time = { 2, 2, 2, 2, 2, 2, 2 }, } }; uint32_t number_of_items = sizeof(tmp) / sizeof(task_t); database.tasks = calloc(number_of_items, SIZEOF_TASK_T); database.capacity = number_of_items; database.count = number_of_items; memcpy(database.tasks, &tmp, SIZEOF_TASK_T * number_of_items); */ /////////////////////////////////////////////////////////////////////////// // Store database from memory to binary file. if (level & T_SBIN) { fprintf(stderr, "# store database ------------------------------ \\\n"); store_database(&database, DB_BIN_PATH_NAME); fprintf(stderr, done); } /////////////////////////////////////////////////////////////////////////// // Load database from binary file to memory. if (level & T_LBIN) { fprintf(stderr, "# load database ------------------------------- \\\n"); database_t db_load; memset(&db_load, 0, SIZEOF_DATABASE_T); load_database(&db_load, DB_BIN_PATH_NAME); fprintf(stderr, "loaded %zu entries.\n", db_load.count); for (uint32_t idx = 0; idx < db_load.count; idx++) { print_task(&db_load.tasks[idx]); } clear_database(&db_load); fprintf(stderr, done); } /////////////////////////////////////////////////////////////////////////// // Export database to CSV file. if (level & T_ECSV) { fprintf(stderr, "# export to CSV ------------------------------- \\\n"); export_to_csv(&database, DB_CSV_PATH_NAME); fprintf(stderr, done); } /////////////////////////////////////////////////////////////////////////// // Import database from CSV file. if (level & T_ICSV) { fprintf(stderr, "# import from CSV ----------------------------- \\\n"); database_t db_import; memset(&db_import, 0, SIZEOF_DATABASE_T); import_from_csv(&db_import, DB_CSV_PATH_NAME); fprintf(stderr, "imported %zu entries.\n", db_import.count); for (uint32_t idx = 0; idx < db_import.count; idx++) { print_task(&db_import.tasks[idx]); } clear_database(&db_import); fprintf(stderr, done); } /////////////////////////////////////////////////////////////////////////// // Check size of structs if (level & T_SOS) { size_t size; char *name; fprintf(stderr, "# check structs sizes ------------------------- \\\n"); name = "database_t"; size = sizeof(database_t); fprintf(stderr, "sizeof(%s) = %zu bytes (%zu bits : %6.3f W64b)\n", name, size, size*8, ((double)size)*8.0/64.0); name = "task_t"; size = sizeof(task_t); fprintf(stderr, "sizeof(%s) = %zu bytes (%zu bits : %6.3f W64b)\n", name, size, size*8, ((double)size)*8.0/64.0); fprintf(stderr, done); } } char *line_buffer; int size_x, size_y, pos_x, pos_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 initialization() { if (load_database(&database, DB_BIN_PATH_NAME) == false) { memset(&database, 0, SIZEOF_DATABASE_T); } } void free_memory() { clear_database(&database); free(line_buffer); line_buffer = NULL; free(layouts); layouts = NULL; } 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; task_t *active_task; task_t *selected_task; layout_t *layout = &layouts[selected_layout]; int table_size = layout->table_size; // char **table_headers = layout->table_headers; // TODO Maybe move this to side function? start_color(); init_pair(1, COLOR_BLUE, COLOR_BLACK); init_pair(2, COLOR_BLACK, COLOR_CYAN); init_pair(3, COLOR_WHITE, COLOR_BLUE); int color_pair; 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, }; // Draw rows with tasks. move(1, 0); active_task = database.tasks + database.active_task; selected_task = database.tasks + database.selected_task; uint64_t total = 0; uint64_t totals[8] = {0, 0, 0, 0, 0, 0, 0, 0}; for (uint32_t idx = 0; idx < database.count; idx++){ task = &database.tasks[idx]; // Enable highlight color on selected entry. color_pair = 0; if (task == active_task) { color_pair = 1; } if (task == selected_task) { color_pair = 2; } if (task == active_task && task == selected_task) { color_pair = 3; } if (color_pair > 0) { attron(COLOR_PAIR(color_pair)); } attron(COLOR_PAIR(color_pair)); // 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); uint32_t *time = task->time; sprintf(line_buffer + columns[1], "%8d", time[0]); sprintf(line_buffer + columns[2], "%8d", time[1]); sprintf(line_buffer + columns[3], "%8d", time[2]); sprintf(line_buffer + columns[4], "%8d", time[3]); sprintf(line_buffer + columns[5], "%8d", time[4]); sprintf(line_buffer + columns[6], "%8d", time[5]); sprintf(line_buffer + columns[7], "%8d", time[6]); // Update totals. total = 0; for(int idx = DAYS_ON_WEEK-1; idx >= 0; idx--) { total += time[idx]; totals[idx] += time[idx]; } totals[DAYS_ON_WEEK] += total; sprintf(line_buffer + columns[8], "%10" PRIu64, total); addstr(line_buffer); // Disable highlight color on selected entry. if (color_pair > 0) { attroff(COLOR_PAIR(color_pair)); } // Print columns separators. pos_y = getcury(stdscr); for (int c_idx = 0; c_idx < sizeof(columns)/sizeof(int); c_idx++) { mvaddch(pos_y, columns[c_idx], ACS_VLINE); } // Go to next line. pos_y++; move(pos_y, 0); } // Print totals memset(line_buffer, ' ', size_x * sizeof(char)); line_buffer[size_x-1] = '\0'; sprintf(line_buffer + columns[1], "%8" PRIu64, totals[0]); sprintf(line_buffer + columns[2], "%8" PRIu64, totals[1]); sprintf(line_buffer + columns[3], "%8" PRIu64, totals[2]); sprintf(line_buffer + columns[4], "%8" PRIu64, totals[3]); sprintf(line_buffer + columns[5], "%8" PRIu64, totals[4]); sprintf(line_buffer + columns[6], "%8" PRIu64, totals[5]); sprintf(line_buffer + columns[7], "%8" PRIu64, totals[6]); mvaddstr(pos_y, 0, line_buffer); pos_y++; // Draw empty rows. // TODO Do this properly. int dummy = 100; while (dummy > 0) { // Clear current line. move(pos_y, 0); clrtoeol(); // Print columns separators. pos_y = getcury(stdscr); for (int c_idx = 0; c_idx < sizeof(columns)/sizeof(int); c_idx++) { mvaddch(pos_y, columns[c_idx], ACS_VLINE); } pos_y++; move(pos_y, 0); dummy--; } } void draw_footer() { const char *app_name_version = "TTT v1"; 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_version); addch(' '); for (int idx = strlen(app_name_version) + 3; idx < col - 1; idx ++) { addch(ACS_HLINE); } addch(ACS_LRCORNER); // TODO This code is now repeated accross the header, table and footer. Clean this up. layout_t *layout = &layouts[selected_layout]; int table_size = layout->table_size; // char **table_headers = layout->table_headers; 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(row-1, columns[0]+2, table_headers[0]); for (int idx = 1; idx < 9; idx++) { mvaddch(row-1, columns[idx], ACS_BTEE); } } WINDOW *create_newwin(int height, int width, int starty, int startx); void destroy_win(WINDOW *local_win); int main(int argc, char *argv[]) { // Make sure architecture uses 8bits per char. assert(CHAR_BIT == 8); // TODO Parse commands using: https://stackoverflow.com/questions/9642732/parsing-command-line-arguments-in-c if (argc > 1) { char *action; bool do_action = false; for (int idx = 1; idx < argc; idx++) { action = "--version"; do_action = strncmp(argv[idx], action, strlen(action)) == 0; action = "-v"; do_action |= strncmp(argv[idx], action, strlen(action)) == 0; if (do_action) { fprintf(stdout, "Task Time Tracker v1.0\n"); return EXIT_SUCCESS; } action = "--fake_bin"; do_action = strncmp(argv[idx], action, strlen(action)) == 0; if (do_action) { prototype(T_SBIN); return EXIT_SUCCESS; } action = "--t_sos"; do_action = strncmp(argv[idx], action, strlen(action)) == 0; if (do_action) { prototype(T_SOS); return EXIT_SUCCESS; } action = "--t_icsv"; do_action = strncmp(argv[idx], action, strlen(action)) == 0; if (do_action) { prototype(T_ICSV); return EXIT_SUCCESS; } action = "--t_ecsv"; do_action = strncmp(argv[idx], action, strlen(action)) == 0; if (do_action) { prototype(T_ECSV); return EXIT_SUCCESS; } action = "--t_lbin"; do_action = strncmp(argv[idx], action, strlen(action)) == 0; if (do_action) { prototype(T_LBIN); return EXIT_SUCCESS; } action = "--t_csv2bin"; do_action = strncmp(argv[idx], action, strlen(action)) == 0; if (do_action) { clear_database(&database); import_from_csv(&database, DB_CSV_PATH_NAME); store_database(&database, DB_BIN_PATH_NAME); return EXIT_SUCCESS; } action = "--test"; do_action = strncmp(argv[idx], action, strlen(action)) == 0; if (do_action) { prototype(T_ALL); return EXIT_SUCCESS; } } fprintf(stdout, "Unkown command '%s'.\nUse '%s --help' for list of commands.\n", argv[1], argv[0]); return EXIT_FAILURE; } initialization(); 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 of the window. startx = (COLS - width) / 2; // refresh(); my_win = create_newwin(height, width, starty, startx); ch = KEY_RESIZE; do { task_t *active_task = database.tasks + database.active_task; task_t *selected_task = database.tasks + database.selected_task; switch(ch) { case KEY_F(1): { task_t *new_task; create_task(&database, &new_task); int row = database.count; mvaddch(row, 0, ACS_DIAMOND); clrtoeol(); mvaddch(row, size_x-1, ACS_VLINE); curs_set(1); mvgetnstr(row, 2, new_task->name, MAX_TASK_NAME-1); // TODO Move this empty-name-cleaning code elsewhere. bool is_empty = true; int size = strlen(new_task->name); for (int idx=0; idx < size; idx++) { char name_char = new_task->name[idx]; if (name_char != ' ' && name_char != '\t') { is_empty = false; break; } } if (strlen(new_task->name) == 0 || is_empty) { strcpy(new_task->name, "-- new task --"); } new_task->name[MAX_TASK_NAME-1] = '\0'; char *name = new_task->name; truncate_string_utf8(name, MAX_TASK_NAME-1); curs_set(0); break; } case KEY_F(2): { if (database.selected_task < 0) { break; } // rename stuff int row = database.selected_task + 1; mvaddch(row, 0, ACS_DIAMOND); clrtoeol(); mvaddch(row, size_x-1, ACS_VLINE); curs_set(1); mvgetnstr(row, 2, selected_task->name, MAX_TASK_NAME-1); curs_set(0); break; } case KEY_F(3): { if (database.selected_task < 0) { break; } delete_task(&database, selected_task); break; } case '\n': case ' ': if (true) { task_t *next_task = selected_task; if (active_task > 0) { // TODO Add remaining time to task. database.active_task = -1; } if (active_task != next_task) { database.active_task = next_task - database.tasks; } database.modified_on = time(NULL); store_database(&database, DB_BIN_PATH_NAME); } break; 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: if (database.selected_task > 0) { database.selected_task--; } break; case KEY_DOWN: if (database.selected_task < database.count - 1) { database.selected_task++; } break; } if (size_x >= 60 && size_y > 2) { selected_layout = size_x > 100 ? 0 : 1; 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); } // TEST // if (ch == KEY_F(1) || ch == KEY_F(2)) { // mvprintw(1, 1, "db:'%u'/'%u'", database.count, database.capacity); // } } while((ch = getch()) != 'q'); store_database(&database, DB_BIN_PATH_NAME); 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); }