From 98279a2d333e8ed14d036a9d5bd38200aa215b5a Mon Sep 17 00:00:00 2001 From: dam Date: Tue, 20 Sep 2022 17:20:54 +0000 Subject: Let's say this is the first working prototype. --- main.c | 875 +++++++++++++++++++++++++++++++++++------------------------------ 1 file changed, 472 insertions(+), 403 deletions(-) (limited to 'main.c') diff --git a/main.c b/main.c index b39fed1..ace006b 100644 --- a/main.c +++ b/main.c @@ -1,12 +1,13 @@ // Compilation command: -// - release: gcc main.c -Wall -O2 -m64 -lncurses -o ttt -// - debug : gcc main.c -Wall -g3 -m64 -lncurses -o ttt -D DEBUG +// - release: gcc main.c -Wall -pedantic -O2 -m64 -lncursesw -o ttt +// - debug : gcc main.c -Wall -pedantic -g3 -m64 -lncursesw -o ttt -D DEBUG #include #include #include #include +#include #include #include #include @@ -14,39 +15,40 @@ #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 >> 1) + 1) +#define AR_BIN_PATH_NAME "./archive.bin" typedef struct /*__attribute__((__packed__))*/ { - uint32_t times[7]; + int64_t times[7]; char name[MAX_TASK_NAME]; } task_t; typedef struct /*__attribute__((__packed__))*/ { - time_t modified_on; + task_t *tasks; size_t count; size_t capacity; - task_t *tasks; ptrdiff_t active_task; - ptrdiff_t selected_task; // TODO Maybe use this instead of indexes? + ptrdiff_t selected_task; + int64_t modified_on; + int64_t total_times[7]; } database_t; -const char *DB_FILE_SIGN = "TTT:B:01"; -const size_t SIZEOF_DB_FILE_SIGN = sizeof(DB_FILE_SIGN); +#define DB_FILE_SIGN_STR "TTT:B:01" +const char DB_FILE_SIGN[] = DB_FILE_SIGN_STR; +const size_t DB_FILE_SIGN_LENGTH = sizeof(DB_FILE_SIGN_STR)-1; const size_t SIZEOF_TASK_T = sizeof(task_t); const size_t SIZEOF_DATABASE_T = sizeof(database_t); +const int64_t SECONDS_IN_MINUTE = (int64_t)60; +const int64_t SECONDS_IN_HOUR = (int64_t)60*SECONDS_IN_MINUTE; +const int64_t SECONDS_IN_DAY = (int64_t)24*SECONDS_IN_HOUR; +const int64_t SECONDS_IN_YEAR = (int64_t)365*SECONDS_IN_DAY; database_t database; -database_t archive; // TODO To be implemented in the future. +database_t archive; // 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. @@ -79,43 +81,107 @@ char *replace_char(char *string, char find, char replace) { return string; } -void print_task(const task_t *task) { - printf("name: '%s'\n", task->name); - printf("t[0]: '%" PRIu32 "'\n", task->times[0]); - printf("t[1]: '%" PRIu32 "'\n", task->times[1]); - printf("t[2]: '%" PRIu32 "'\n", task->times[2]); - printf("t[3]: '%" PRIu32 "'\n", task->times[3]); - printf("t[4]: '%" PRIu32 "'\n", task->times[4]); - printf("t[5]: '%" PRIu32 "'\n", task->times[5]); - printf("t[6]: '%" PRIu32 "'\n", task->times[6]); -} - -char* format_time(uint32_t time, char* string) { - const time_t seconds_per_day = 25*60*60; - if (time > 24*60*60) { - // ---.-- d - // │ 3│ - // │ │ - sprintf(string, "%5.2fd", (double)time / (double)seconds_per_day); +char* format_time(intmax_t time, char* string, int length) { + int left_padding = (length - 5) / 2; + int right_padding = length - 5 - left_padding; + + if (time > (intmax_t)9999 * SECONDS_IN_YEAR) { + sprintf(string, "%*s ∞ %*s", + left_padding, "", + right_padding, ""); + } + else if (time > (intmax_t)9999 * SECONDS_IN_DAY) { + double value = (double)time / (double)SECONDS_IN_YEAR; + int decimals = + time > 99 * SECONDS_IN_YEAR ? 0 : + time > 9 * SECONDS_IN_YEAR ? 1 : + 2; + + sprintf(string, "%*s%4.*fy%*s", + left_padding, "", + decimals, + value, + right_padding, ""); } - else if (time > 60) { - double hours = (double)time / 3600.0; - double minutes = (time - (time_t)hours) / 60.0; - sprintf(string, "%02.0f:%02.0f", hours, minutes); + else if (time >= (intmax_t)100 * SECONDS_IN_HOUR) { + double value = (double)time / (double)SECONDS_IN_DAY; + int decimals = + time > 99 * SECONDS_IN_DAY ? 0 : + time > 9 * SECONDS_IN_DAY ? 1 : + 2; + + sprintf(string, "%*s%4.*fd%*s", + left_padding, "", + decimals, + value, + right_padding, ""); + } + else if (time >= SECONDS_IN_MINUTE) { + intmax_t hours = (double)time / (double)SECONDS_IN_HOUR; + intmax_t minutes = (time - (hours * SECONDS_IN_HOUR) ) / SECONDS_IN_MINUTE; + sprintf(string, "%*s%02jd:%02jd%*s", left_padding, "", hours, minutes, right_padding, ""); + } + else if (time >= 0) { + sprintf(string, "%*s%4jds%*s", left_padding, "", time, right_padding, ""); } else { - sprintf(string, "%2dsec", time); + sprintf(string, "%*s - %*s", left_padding, "", right_padding, ""); } return string; } +int64_t add_time(int64_t x, int64_t y) { + + if (y > 0 && x > INT64_MAX - y) + return INT64_MAX; + + if (y < 0 && x < INT64_MIN - y) + return INT64_MIN; + + return x + y; +} + +int64_t sub_time(int64_t x, int64_t y) { + + if (y < 0 && x > INT64_MAX + y) + return INT64_MAX; + + if (y > 0 && x < INT64_MIN + y) + return INT64_MIN; + + return x - y; +} + +// Returns active task or NULL if none applies. +task_t *get_active_task(database_t *db) { + assert(db != NULL); + + task_t *task = NULL; + if (db->active_task >= 0) { + task = db->tasks + db->active_task; + } + + return task; +} + +// Returns selected task or NULL if none applies. +task_t *get_selected_task(database_t *db) { + assert(db != NULL); + + task_t *task = NULL; + if (db->selected_task >= 0) { + task = db->tasks + db->selected_task; + } + + return task; +} // 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) { + if (db->count == PTRDIFF_MAX) { fprintf(stderr, "Database reached maximum capacity.\n"); return false; } @@ -124,7 +190,7 @@ bool create_task(database_t *db, task_t **task) { 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 >> 1 ? DB_MAX_CAP : // Protect against DB_MAX_CAP != power-of-two. + current_capacity > PTRDIFF_MAX >> 1 ? PTRDIFF_MAX : current_capacity << 1; task_t *new_tasks = realloc(db->tasks, new_capacity * SIZEOF_TASK_T); @@ -151,14 +217,44 @@ bool create_task(database_t *db, task_t **task) { return true; } +// Adds the given task to the database using (using create_task and memcpy). +// Returns success. +bool add_task(database_t *db, task_t *task) { + assert(db != NULL); + assert(task != NULL); + + task_t *new_task; + if (create_task(db, &new_task) == false) { + return false; + } + + memcpy(new_task, task, SIZEOF_TASK_T); + + // Add task timer values to total timers. + for (int idx = 0; idx < 7; idx++) { +// db->total_times[idx] += task->times[idx]; TODO + db->total_times[idx] = add_time(db->total_times[idx], task->times[idx]); + } + + return true; +} + +// Deletes the task provided in the pointer. If possible, shrinks the database capacity. +// Returns success. bool delete_task(database_t *db, task_t *task) { assert(db != NULL); assert(task != NULL); assert(task >= db->tasks && task < &db->tasks[db->count]); - + + // Remove task timer values from total timers. + for (int idx = 0; idx < 7; idx++) { +// db->total_times[idx] -= task->times[idx]; TODO + db->total_times[idx] = sub_time(db->total_times[idx], task->times[idx]); + } + // 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); + memmove(task, task + 1, (db->count - index - 1) * SIZEOF_TASK_T); db->count--; // Adjust selected task. @@ -190,9 +286,12 @@ bool delete_task(database_t *db, task_t *task) { return true; } -void clear_database(database_t *db) { +// Resets database to the initial state and deallocates all memory taken by tasks. +void reset_database(database_t *db) { free(db->tasks); memset(db, 0, SIZEOF_DATABASE_T); + db->active_task = -1; + db->selected_task = -1; } // Stores data from database into binary file. @@ -209,7 +308,7 @@ bool store_database(const database_t *db, const char *path_name) { return false; } - fwrite(DB_FILE_SIGN, sizeof(char), SIZEOF_DB_FILE_SIGN, file); + fwrite(DB_FILE_SIGN, sizeof(char), DB_FILE_SIGN_LENGTH, file); fwrite(db, SIZEOF_DATABASE_T, 1, file); fwrite(db->tasks, SIZEOF_TASK_T, db->count, file); @@ -232,9 +331,9 @@ bool load_database(database_t *db, const char *path_name) { } // 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) { + char file_signature[DB_FILE_SIGN_LENGTH]; + fread(&file_signature, sizeof(char), DB_FILE_SIGN_LENGTH, file); + if (strncmp(file_signature, DB_FILE_SIGN, DB_FILE_SIGN_LENGTH) != 0) { fprintf(stderr, "Invalid file signature.\n"); return false; } @@ -284,7 +383,7 @@ bool export_to_csv(const database_t *db, const char *path_name) { for (task_t *task = db->tasks; task < limit; task++) { memcpy(name, task->name, MAX_TASK_NAME); replace_char(name, ',', ' '); - fprintf(file, "%s,%" PRIu32 ",%" PRIu32 ",%" PRIu32 ",%" PRIu32 ",%" PRIu32 ",%" PRIu32 ",%" PRIu32 "\n", + fprintf(file, "%s,%" PRId64 ",%" PRId64 ",%" PRId64 ",%" PRId64 ",%" PRId64 ",%" PRId64 ",%" PRId64 "\n", name, task->times[0], task->times[1], @@ -312,43 +411,35 @@ bool import_from_csv(database_t *db, const char *path_name) { 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); + char *line_buffer = NULL; + size_t line_buffer_size = 0; + while(getline(&line_buffer, &line_buffer_size, file) != -1) { // Check if reached EOF. // Find task name string limits. - name_delimiter = strchr(line_buffer, ','); + char *name_delimiter = strchr(line_buffer, ','); + if (name_delimiter == NULL) { + continue; + } size_t name_length = (name_delimiter - line_buffer) + 1; if (name_length > MAX_TASK_NAME) { name_length = MAX_TASK_NAME; } + // Prepare new task. + task_t *task; + create_task(db, &task); + // 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, + "%" SCNd64 ",%" SCNd64 ",%" SCNd64 ",%" SCNd64 ",%" SCNd64 ",%" SCNd64 ",%" SCNd64, &task->times[0], &task->times[1], &task->times[2], @@ -363,8 +454,11 @@ bool import_from_csv(database_t *db, const char *path_name) { continue; } - // Add new database entry. - number_of_entries++; + // Add task timer values to total timers. + for (int idx = 0; idx < 7; idx++) { +// db->total_times[idx] += task->times[idx]; TODO + db->total_times[idx] = add_time(db->total_times[idx], task->times[idx]); + } } fclose(file); @@ -377,12 +471,8 @@ bool import_from_csv(database_t *db, const char *path_name) { enum TEST { T_NONE = 0x00, T_TIME = 0x01, - T_XXXX = 0x02, - T_SBIN = 0x04, - T_LBIN = 0x08, - T_ECSV = 0x10, - T_ICSV = 0x20, - T_SOS = 0x40, + T_SOT = 0x02, + T_TPF = 0x04, T_ALL = 0xFF, }; @@ -401,86 +491,13 @@ void prototype(int level) { } /////////////////////////////////////////////////////////////////////////// - // Prepare some data for testing. - /* - task_t tmp[] = { - { - .name = "ALPHA-TASK", - .times = { 0, 0, 0, 0, 0, 0, 0 }, - }, - { - .name = "BETA-TASK", - .times = { 1, 1, 1, 1, 1, 1, 1 }, - }, - { - .name = "DELTA-TASK", - .times = { 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) { + // Check size of types + if (level & T_SOT) { size_t size; char *name; - fprintf(stderr, "# check structs sizes ------------------------- \\\n"); + fprintf(stderr, "# check size of types ------------------------- \\\n"); + + fprintf(stderr, "sizeof(byte) = %u bits\n", CHAR_BIT); name = "database_t"; size = sizeof(database_t); @@ -494,9 +511,34 @@ void prototype(int level) { size = sizeof(time_t); fprintf(stderr, "sizeof(%s) = %zu bytes (%zu bits : %6.3f W64b)\n", name, size, size*8, ((double)size)*8.0/64.0); + name = "size_t"; + size = sizeof(size_t); + fprintf(stderr, "sizeof(%s) = %zu bytes (%zu bits : %6.3f W64b)\n", name, size, size*8, ((double)size)*8.0/64.0); + + name = "ptrdiff_t"; + size = sizeof(ptrdiff_t); + fprintf(stderr, "sizeof(%s) = %zu bytes (%zu bits : %6.3f W64b)\n", name, size, size*8, ((double)size)*8.0/64.0); + + name = "int"; + size = sizeof(int); + fprintf(stderr, "sizeof(%s) = %zu bytes (%zu bits : %6.3f W64b)\n", name, size, size*8, ((double)size)*8.0/64.0); + + + name = "DB_FILE_SIGN_LENGTH"; + size = DB_FILE_SIGN_LENGTH; + 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); } + + /////////////////////////////////////////////////////////////////////////// + // Check time print format + if (level & T_TPF) { + double times[7] = { 0.12345, 1.12345, 10.12345, 100.12345, 1000.12345, 10000.12345, 10000000000000.12345 }; + for (int idx = 0; idx < 7; idx++) { + fprintf(stderr, "%.2e\n", times[idx]); + } + } } @@ -510,9 +552,6 @@ void update_timers(database_t *db) { // Get last modified on UTC time. time_t start_time = db->modified_on; -// time_t diff = (double)stop_time - start_time; -// fprintf(stderr, "> diff: %zu seconds | %6.2f minutes | %6.2f hours\n", (time_t)diff, diff / 60.0, diff / (60.0*60.0)); - if (db->active_task < 0) { return; } @@ -523,11 +562,17 @@ void update_timers(database_t *db) { start_week_day = localtime(&start_time)->tm_wday; - // Get next week of day. - time_t next_day = (start_time / 86400) * 86400 + 86400; + // Get next day in local time. + struct tm *start_of_day_tm = localtime(&start_time); + start_of_day_tm->tm_sec = 0; + start_of_day_tm->tm_min = 0; + start_of_day_tm->tm_hour = 0; + time_t start_of_day = mktime(start_of_day_tm); + time_t next_day = start_of_day + SECONDS_IN_DAY; time_t next_start = next_day < stop_time ? next_day : stop_time; - - active_task->times[start_week_day] += next_start - start_time; + time_t elapsed_time = next_start - start_time; + active_task->times[start_week_day] += elapsed_time; + db->total_times[start_week_day] += elapsed_time; start_time = next_start; } @@ -535,6 +580,31 @@ void update_timers(database_t *db) { db->modified_on = stop_time; } +void update_total_timers(database_t *db) { + + int64_t *d0 = &db->total_times[0]; + int64_t *d1 = &db->total_times[1]; + int64_t *d2 = &db->total_times[2]; + int64_t *d3 = &db->total_times[3]; + int64_t *d4 = &db->total_times[4]; + int64_t *d5 = &db->total_times[5]; + int64_t *d6 = &db->total_times[6]; + memset(db->total_times, 7, sizeof(int64_t)); + +// for (task_t *task = db->tasks; task < db->tasks + db->count; task++) { +// int64_t *times = task->times; + for (size_t idx = 0; idx < db->count; idx++) { + int64_t *times = db->tasks[idx].times; + *d0 = add_time(*d0, times[0]); + *d1 = add_time(*d1, times[1]); + *d2 = add_time(*d2, times[2]); + *d3 = add_time(*d3, times[3]); + *d4 = add_time(*d4, times[4]); + *d5 = add_time(*d5, times[5]); + *d6 = add_time(*d6, times[6]); + } + +} char *line_buffer; @@ -544,10 +614,11 @@ uint8_t selected_layout = 0; #define NUM_OF_COLUMNS 9 typedef struct { - int columns_size; + int timers_offset; char *table_headers[NUM_OF_COLUMNS]; int column_widths[NUM_OF_COLUMNS]; char alignments[NUM_OF_COLUMNS]; + int alignment_offsets[NUM_OF_COLUMNS]; } layout_t; layout_t *layouts = NULL; @@ -560,8 +631,6 @@ void initialize_tui() { // Layout : 0 : normal. // TODO Headers must be dynamic according to FIRST_DAY_OF_WEEK layouts[0] = (layout_t) { - .columns_size = 8, - .column_widths = { -1, 7, 7, 7, 7, 7, 7, 7, 9 }, .alignments = { 'L', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C' }, .table_headers = { @@ -580,8 +649,6 @@ void initialize_tui() { // Layout : 1 : compact. // TODO Headers must be dynamic according to FIRST_DAY_OF_WEEK layouts[1] = (layout_t){ - .columns_size = 6, - .column_widths = { -1, 5, 5, 5, 5, 5, 5, 5, 5 }, .alignments = { 'L', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C' }, .table_headers = { @@ -597,8 +664,31 @@ void initialize_tui() { }, }; + // Calculate alignment_offsets. + for(layout_t *layout = layouts; layout <= layouts + 1; layout++) { + for (int idx = 0; idx < NUM_OF_COLUMNS; idx++) { + int offset; + switch(layout->alignments[idx]) { + default: + case 'L': + offset = 0; + break; + + case 'C': + offset = ((layout->column_widths[idx] - strlen(layout->table_headers[idx])) / 2); + break; + + case 'R': + offset = (layout->column_widths[idx] - strlen(layout->table_headers[idx])); + break; + } + layout->alignment_offsets[idx] = offset; + } + } + + setlocale(LC_ALL, "C.UTF-8"); // Sets locale for C library functions; Allows usage of UTF-8. initscr(); // Start curses mode. - cbreak(); // Line buffering disabled, Pass on everty thing to me. + cbreak(); // Line buffering disabled; pass on everty thing to me. keypad(stdscr, TRUE); // I need that nifty F1 curs_set(0); // Set cursor invisible. @@ -608,14 +698,8 @@ void initialize_tui() { init_pair(3, COLOR_WHITE, COLOR_BLUE); } -void initialization() { - if (load_database(&database, DB_BIN_PATH_NAME) == false) { - memset(&database, 0, SIZEOF_DATABASE_T); - } -} - void free_memory() { - clear_database(&database); + reset_database(&database); free(line_buffer); line_buffer = NULL; @@ -625,25 +709,20 @@ void free_memory() { } -void draw_tui() { +void draw_tui(database_t *db) { + layout_t *layout = &layouts[selected_layout]; - int columns_size = layout->columns_size; - char **table_headers = layout->table_headers; - time_t now_utc = time(NULL); - uint8_t today = localtime(&now_utc)->tm_wday; - int color_pair = 0; - // The first column expands to fill the remaining space (is dynamic). + // The first column expands to fill the remaining space dynamically. layout->column_widths[0] = size_x - (NUM_OF_COLUMNS - 1) - 2; for (int idx = 1; idx < NUM_OF_COLUMNS; idx++) { layout->column_widths[0] -= layout->column_widths[idx]; } - clear(); - - + // TODO Unsure if this is needed. + erase(); // Draw outer border. box(stdscr, 0, 0); @@ -651,7 +730,6 @@ void draw_tui() { // Draw column grids. int x = 0; for (int idx = 0; idx < NUM_OF_COLUMNS - 1; idx++) { - x += 1 + layout->column_widths[idx]; mvaddch(0, x, ACS_TTEE); for (int y = 1; y < size_y - 1; y++) { @@ -663,185 +741,160 @@ void draw_tui() { // Draw headers. // TODO Missing some spacing on the initial column. x = 0; + int idx_fdow; // TODO Index - first day of week. for (int idx = 0; idx < NUM_OF_COLUMNS; idx++) { x += 1; - int header_position = x; - switch(layout->alignments[idx]) { - case 'L': - break; - - case 'C': - header_position += ((layout->column_widths[idx] - strlen(layout->table_headers[idx])) / 2); - break; - - case 'R': - header_position += (layout->column_widths[idx] - strlen(layout->table_headers[idx])); - break; + idx_fdow = idx; + if (idx_fdow > 0 && idx_fdow < 8) { + idx_fdow = ((idx - 1) + FIRST_DAY_OF_WEEK) % 7 + 1; } - - mvaddstr(0, header_position, layout->table_headers[idx]); - x += layout->column_widths[idx]; + int header_position = x + layout->alignment_offsets[idx_fdow]; + mvaddstr(0, header_position, layout->table_headers[idx_fdow]); + x += layout->column_widths[idx_fdow]; } // Draw tasks. + uint64_t total_time = 0; + int times_offset = 1; + int column_width; int y = 0; - for (task_t *task = database.tasks; task < database.tasks + database.count; task++) { - y++; - int x = 0; - for (int idx = 0; idx < NUM_OF_COLUMNS; idx++) { - x++; - mvaddnstr(y, x, task->name, layout->column_widths[idx]); - break; // WIP TODO TASK BUG TEST HACK WARNING - } -// x += 1 + layouts->column_widths[0]; -// mvaddstr(y, x, - } - - - return; - - int start_columns = size_x - 1 - 8*columns_size; - int columns[] = { - 0, - start_columns+(columns_size*0), - start_columns+(columns_size*1), - start_columns+(columns_size*2), - start_columns+(columns_size*3), - start_columns+(columns_size*4), - start_columns+(columns_size*5), - start_columns+(columns_size*6), - start_columns+(columns_size*7), - }; + int available_rows = size_y - 2; + task_t *active_task = get_active_task(db); + task_t *selected_task = get_selected_task(db); - mvaddstr(0, columns[0]+2, table_headers[0]); - for (int idx = 1; idx < 9; idx++) { // TODO When does the week start? - mvaddch(0, columns[idx], ACS_TTEE); - - if (idx == today) { - attron(COLOR_PAIR(color_pair)); - } + // This is some sort of pagination to allow scrolling through the tasks. + task_t *start_task = db->tasks + (db->selected_task / available_rows) * available_rows; + for (task_t *task = start_task; available_rows > 0 && task < db->tasks + db->count; task++) { + available_rows--; + y++; -// mvaddch(0, columns[idx]+1, ACS_RTEE); - mvaddstr(0, columns[idx]+2, table_headers[idx]); -// mvaddch(0, columns[idx+1]-1, ACS_LTEE); - if (idx == today) { - attroff(COLOR_PAIR(color_pair)); + // TEST -- START + // Enable highlight color on selected entry. + int 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 == 1 || color_pair == 3) { + attron(A_BOLD); + } + attron(COLOR_PAIR(color_pair)); } - } - - /////////////////////////////////////////////////////////////////////////// - // Table - - task_t *x_task; - task_t *active_task; - task_t *selected_task; - - - // 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 (size_t idx = 0; idx < database.count; idx++){ + // TEST -- STOP - x_task = &database.tasks[idx]; - // Enable highlight color on selected entry. - color_pair = 0; - if (x_task == active_task) { - color_pair = 1; - } - if (x_task == selected_task) { - color_pair = 2; - } - if (x_task == active_task && x_task == selected_task) { - color_pair = 3; - } - if (color_pair == 1 || color_pair == 3) { - attron(A_BOLD); - } - attron(COLOR_PAIR(color_pair)); + int x = 0; - // Clear line_buffer and add string termination. - memset(line_buffer, ' ', size_x * sizeof(char)); - line_buffer[size_x-1] = '\0'; + // Task name. + x++; - // Check maximum available space for task name and print it accordingly. - size_t task_name_length = strlen(x_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, x_task->name, copy_length); + column_width = layout->column_widths[0]; + sprintf(line_buffer, "%*s", column_width, ""); + // TODO Which one should I use? +// memset(line_buffer, ' ', column_width); +// line_buffer[column_width] = '\0'; + mvaddnstr(y, x, line_buffer, column_width); + mvaddnstr(y, x, task->name, column_width); // TODO Trim at utf8-char using not-yet-implemented-function. - uint32_t *times = x_task->times; - sprintf(line_buffer + columns[1], "%8d", times[0]); - sprintf(line_buffer + columns[2], "%8d", times[1]); - sprintf(line_buffer + columns[3], "%8d", times[2]); - sprintf(line_buffer + columns[4], "%8d", times[3]); - sprintf(line_buffer + columns[5], "%8d", times[4]); - sprintf(line_buffer + columns[6], "%8d", times[5]); - sprintf(line_buffer + columns[7], "%8d", times[6]); -// char time_str[7]; -// sprintf(line_buffer + columns[1], "%s", format_time(times[0], time_str)); -// sprintf(line_buffer + columns[2], "%s", format_time(times[1], time_str)); -// sprintf(line_buffer + columns[3], "%s", format_time(times[2], time_str)); -// sprintf(line_buffer + columns[4], "%s", format_time(times[3], time_str)); -// sprintf(line_buffer + columns[5], "%s", format_time(times[4], time_str)); -// sprintf(line_buffer + columns[6], "%s", format_time(times[5], time_str)); -// sprintf(line_buffer + columns[7], "%s", format_time(times[6], time_str)); + x += layout->column_widths[0]; - // Update totals. - total = 0; - for(int idx = 7-1; idx >= 0; idx--) { - total += times[idx]; - totals[idx] += times[idx]; + // Task times. + total_time = 0; + for (int idx = 0; idx < 7; idx++) { + x++; + + idx_fdow = (idx + FIRST_DAY_OF_WEEK) % 7; + + int column_width = layout->column_widths[idx_fdow + times_offset]; + int64_t task_time = task->times[idx_fdow]; +// total_time += task_time; TODO + total_time = add_time(total_time, task_time); + if (task_time > 0) { + format_time(task_time, line_buffer, column_width); + } + else { + sprintf(line_buffer, "%*s", column_width, ""); + } + mvaddstr(y, x, line_buffer); + x += column_width; } - totals[7] += total; - sprintf(line_buffer + columns[8], "%6" PRIu64, total); // BUG This causes "corrupted size vs. prev_size because it is writing beyond line_buffer size. - addstr(line_buffer); - - // Disable highlight color on selected entry. + // Task total. + x++; + column_width = layout->column_widths[8]; // TODO + format_time(total_time, line_buffer, column_width); + mvaddstr(y, x, line_buffer); + + // TEST -- START attroff(COLOR_PAIR(color_pair)); attroff(A_BOLD); + // TEST -- STOP - // 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); + // Show current selected task and total number of tasks. + sprintf(line_buffer, " %td/%zd ", db->selected_task+1, db->count); + if (strlen(line_buffer) > layout->column_widths[0]) { + sprintf(line_buffer, "%td", db->selected_task+1); + } + mvaddstr(size_y-1, 1, line_buffer); } - // 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); + + // Daily totals. + y = size_y-1; + x = 0 + 1 + layout->column_widths[0]; + total_time = 0; + for (int idx = 0; idx < 7; idx++) { + x++; + idx_fdow = (idx + FIRST_DAY_OF_WEEK) % 7; + int64_t daily_total = db->total_times[idx_fdow]; + column_width = layout->column_widths[idx_fdow + times_offset]; +// total_time += daily_total; TODO + total_time = add_time(total_time, daily_total); + format_time(daily_total, line_buffer, column_width); + mvaddstr(y, x, line_buffer); + x += column_width; + } + x++; + column_width = layout->column_widths[7 + times_offset]; + format_time(total_time, line_buffer, column_width); + mvaddstr(y, x, line_buffer); + x += column_width; } - +database_t *db; int main(int argc, char *argv[]) { - // Make sure architecture uses 8bits per char. - assert(CHAR_BIT == 8); +// clock_t start, end; +// double cpu_time_used; +// +// start = clock(); +// load_database(&database, DB_BIN_PATH_NAME); +// end = clock(); +// cpu_time_used = ((double) (end - start)) / CLOCKS_PER_SEC; +// fprintf(stderr, "load: %f\n", cpu_time_used); +// +// start = clock(); +// update_total_timers(&database); +// end = clock(); +// cpu_time_used = ((double) (end - start)) / CLOCKS_PER_SEC; +// fprintf(stderr, "calc: %f\n", cpu_time_used); +// return 0; + + reset_database(&database); + reset_database(&archive); + db = &database; - // TODO Parse commands using: https://stackoverflow.com/questions/9642732/parsing-command-line-arguments-in-c if (argc > 1) { char *action; @@ -849,94 +902,81 @@ int main(int argc, char *argv[]) { 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; + do_action = strncmp(argv[idx], action, strlen(action)+1) == 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_time"; - do_action = strncmp(argv[idx], action, strlen(action)) == 0; + do_action = strncmp(argv[idx], action, strlen(action)+1) == 0; if (do_action) { prototype(T_TIME); 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; + action = "--t_sot"; + do_action = strncmp(argv[idx], action, strlen(action)+1) == 0; if (do_action) { - prototype(T_ICSV); + prototype(T_SOT); return EXIT_SUCCESS; } - action = "--t_ecsv"; - do_action = strncmp(argv[idx], action, strlen(action)) == 0; + action = "--t_tpf"; + do_action = strncmp(argv[idx], action, strlen(action)+1) == 0; if (do_action) { - load_database(&database, DB_BIN_PATH_NAME); - prototype(T_ECSV); + prototype(T_TPF); return EXIT_SUCCESS; } - action = "--t_lbin"; - do_action = strncmp(argv[idx], action, strlen(action)) == 0; + action = "--test"; + do_action = strncmp(argv[idx], action, strlen(action)+1) == 0; if (do_action) { - prototype(T_LBIN); + prototype(T_ALL); return EXIT_SUCCESS; } - action = "--t_csv2bin"; - do_action = strncmp(argv[idx], action, strlen(action)) == 0; + action = "--icsv"; + do_action = strncmp(argv[idx], action, strlen(action)+1) == 0; if (do_action) { - clear_database(&database); - import_from_csv(&database, DB_CSV_PATH_NAME); + if (argc < idx+1) { + fprintf(stdout, "Missing CSV file path to import.\n"); + } + load_database(&database, DB_BIN_PATH_NAME); + import_from_csv(&database, argv[idx+1]); store_database(&database, DB_BIN_PATH_NAME); return EXIT_SUCCESS; } - action = "--test"; - do_action = strncmp(argv[idx], action, strlen(action)) == 0; + action = "--ecsv"; + do_action = strncmp(argv[idx], action, strlen(action)+1) == 0; if (do_action) { - prototype(T_ALL); + if (argc < idx+1) { + fprintf(stdout, "Missing CSV file path to export.\n"); + } + load_database(&database, DB_BIN_PATH_NAME); + export_to_csv(&database, argv[idx+1]); 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_tui(); - int ch; - - + load_database(&database, DB_BIN_PATH_NAME); - // TODO When this is active, it cancels selecting text with the mouse, and breaks the creation of a new task. - timeout(10000); // Make getch() timeout after timeout(...) miliseconds. + // TODO + // When this is active, it cancels selecting text with the mouse, and breaks the creation of a new task. + // Fortunatelly, this only happens when we write on the line being selected. If we only update the places that changes, this problem goes away. + timeout(1000); // Make getch() timeout after timeout(...) miliseconds. - ch = KEY_RESIZE; + int ch = KEY_RESIZE; do { - task_t *active_task = database.tasks + database.active_task; - task_t *selected_task = database.tasks + database.selected_task; + task_t *active_task = get_active_task(&database); + task_t *selected_task = get_selected_task(&database); update_timers(&database); switch(ch) @@ -951,7 +991,7 @@ int main(int argc, char *argv[]) { // ERROR break; } - int row = database.count; + int row = database.selected_task; mvaddch(row, 0, ACS_DIAMOND); clrtoeol(); mvaddch(row, size_x-1, ACS_VLINE); @@ -980,7 +1020,7 @@ int main(int argc, char *argv[]) { case KEY_F(2): { - if (database.selected_task < 0) { + if (selected_task == NULL) { break; } // rename stuff @@ -996,34 +1036,43 @@ int main(int argc, char *argv[]) { case KEY_F(3): { - if (database.selected_task < 0) { + if (selected_task == NULL) { break; } delete_task(&database, selected_task); break; } + + case 'c': + if (active_task != NULL) { + db->selected_task = db->active_task; + } + break; case '\n': case ' ': - if (true) { - - task_t *next_task = selected_task; - if (active_task > 0) { - update_timers(&database); // TODO Should I keep this even though it always does? - 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); + { + task_t *next_task = selected_task; + if (active_task != NULL) { + update_timers(&database); // TODO Should I keep this even though it always does? + 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(); + clear(); getmaxyx(stdscr, size_y, size_x); - line_buffer = realloc(line_buffer, size_x * sizeof(char)); + line_buffer = realloc(line_buffer, size_x); + break; + + case KEY_BACKSPACE: + db = db == &archive ? &database : &archive; break; case KEY_LEFT: @@ -1032,33 +1081,53 @@ int main(int argc, char *argv[]) { case KEY_RIGHT: break; + case KEY_HOME: + if (db->count > 0) { + db->selected_task = 0; + } + break; + case KEY_UP: if (database.selected_task > 0) { database.selected_task--; } break; + case KEY_PPAGE: + database.selected_task -= (size_y - 2); + if (database.selected_task < 0) { + database.selected_task = 0; + } + break; + + case KEY_END: + if (db->count > 0) { + db->selected_task = db->count - 1; + } + break; + case KEY_DOWN: if (database.selected_task < database.count - 1) { database.selected_task++; } - break; + break; + + case KEY_NPAGE: + database.selected_task += (size_y - 2); + if (database.selected_task >= database.count) { + database.selected_task = database.count - 1; + } + break; } if (size_x >= 60 && size_y > 2) { selected_layout = size_x > 100 ? 0 : 1; - draw_tui(); + draw_tui(db); } 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'); update_timers(&database); -- cgit v1.2.3