From 92f4723a64196599c735857760005c62bfa6b6b2 Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 4 Sep 2022 03:21:30 +0000 Subject: Simplifying TUI drawing code. Making table drawing a bit more configurable. --- main.c | 433 ++++++++++++++++++++++++++++------------------------------------- 1 file changed, 189 insertions(+), 244 deletions(-) diff --git a/main.c b/main.c index aa076f8..8549ec2 100644 --- a/main.c +++ b/main.c @@ -44,8 +44,6 @@ 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. @@ -92,17 +90,23 @@ void print_task(const task_t *task) { printf("t[6]: '%" PRIu32 "'\n", task->times[6]); } -char* get_time_task(uint32_t time, char* string) { +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.2f d", (double)time / (double)seconds_per_day); + sprintf(string, "%5.2fd", (double)time / (double)seconds_per_day); } - double hours = (double)time / 3600.0; - double minutes = (time - (time_t)hours) / 60.0; - sprinf(string, "%3.0f:%02f", hours, minutes); + 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 { + sprintf(string, "%2dsec", time); + } + return string; } @@ -266,13 +270,13 @@ bool export_to_csv(const database_t *db, const char *path_name) { 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] + "sunday", + "monday", + "tuesday", + "wednesday", + "thursday", + "friday", + "saturday" ); char name[MAX_TASK_NAME]; @@ -525,14 +529,10 @@ void update_timers(database_t *db) { active_task->times[start_week_day] += next_start - start_time; - -// fprintf(stderr, "Added '%zu' on %s\n", next_start - start_time, DAYS_OF_WEEK[start_week_day]); - start_time = next_start; } db->modified_on = stop_time; -// fprintf(stderr, "> done\n"); } @@ -541,53 +541,71 @@ char *line_buffer; int size_x, size_y, pos_x, pos_y; uint8_t selected_layout = 0; -#define TABLE_HEADERS_SIZE 9 +#define NUM_OF_COLUMNS 9 typedef struct { - int table_size; - int table_headers_size; - char *table_headers[TABLE_HEADERS_SIZE]; + int columns_size; + char *table_headers[NUM_OF_COLUMNS]; + int column_widths[NUM_OF_COLUMNS]; + char alignments[NUM_OF_COLUMNS]; } layout_t; layout_t *layouts = NULL; -void initialize_layouts() { - layout_t *layout = NULL; +void initialize_tui() { 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 Time Tracker v1 "; - layout->table_headers[1] = " Sun "; - layout->table_headers[2] = " Mon "; - layout->table_headers[3] = " Tue "; - layout->table_headers[4] = " Wed "; - layout->table_headers[5] = " Thu "; - layout->table_headers[6] = " Fri "; - layout->table_headers[7] = " Sat "; - layout->table_headers[8] = " Total "; -// layout->table_headers = { " Task ", " Mon ", " Tue ", " Wed ", " Thu ", " Fri ", " Sat ", " Sun ", " Total " }; - + 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 = { + " Task Time Tracker v1 ", + " Sun ", + " Mon ", + " Tue ", + " Wed ", + " Thu ", + " Fri ", + " Sat ", + " 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] = " TTT "; - layout->table_headers[1] = " S "; - layout->table_headers[2] = " M "; - layout->table_headers[3] = " T "; - layout->table_headers[4] = " W "; - layout->table_headers[5] = " T "; - layout->table_headers[6] = " F "; - layout->table_headers[7] = " S "; - layout->table_headers[8] = " # "; -// layout->table_headers = { " T ", " M ", " T ", " W ", " T ", " F ", " S ", " S ", " # " }; + 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 = { + " TTT v1 ", + " S ", + " M ", + " T ", + " W ", + " T ", + " F ", + " S ", + " # ", + }, + }; + + 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. + + start_color(); + init_pair(1, COLOR_BLUE, COLOR_BLACK); + init_pair(2, COLOR_BLACK, COLOR_CYAN); + init_pair(3, COLOR_WHITE, COLOR_BLUE); } void initialization() { @@ -606,41 +624,94 @@ void free_memory() { layouts = NULL; } -void draw_header() { - + +void draw_tui() { layout_t *layout = &layouts[selected_layout]; - int table_size = layout->table_size; + int columns_size = layout->columns_size; char **table_headers = layout->table_headers; - mvaddch(0, 0, ACS_ULCORNER); - for (int idx = 1; idx < size_x-1; idx++) { - addch(ACS_HLINE); + 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). + 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]; } - 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), - }; - time_t now = time(NULL); - uint8_t today = localtime(&now)->tm_wday + 1; + clear(); - 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 = 1; + // Draw outer border. + box(stdscr, 0, 0); + + // 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++) { + mvaddch(y, x, ACS_VLINE); + } + mvaddch(size_y - 1, x, ACS_BTEE); + } + + // Draw headers. + // TODO Missing some spacing on the initial column. + x = 0; + 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; + } + + mvaddstr(0, header_position, layout->table_headers[idx]); + x += layout->column_widths[idx]; + } + + + // Draw tasks. + int y = 0; + for (task_t *task = database.tasks; task < database.tasks + database.count; task++) { + y++; + int x = 0; + x++; + mvaddstr(y, x, task->name); +// x += 1 + layouts->column_widths[0]; +// mvaddstr(y, x, + } + + // WIP TODO TASK BUG TEST HACK WARNING + + 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), + }; + mvaddstr(0, columns[0]+2, table_headers[0]); for (int idx = 1; idx < 9; idx++) { // TODO When does the week start? @@ -650,67 +721,44 @@ void draw_header() { attron(COLOR_PAIR(color_pair)); } +// 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)); } } -} - -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; + /////////////////////////////////////////////////////////////////////////// + // Table - 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, - }; + 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 (uint32_t idx = 0; idx < database.count; idx++){ + for (size_t idx = 0; idx < database.count; idx++){ - task = &database.tasks[idx]; + x_task = &database.tasks[idx]; // Enable highlight color on selected entry. color_pair = 0; - if (task == active_task) { + if (x_task == active_task) { color_pair = 1; } - if (task == selected_task) { + if (x_task == selected_task) { color_pair = 2; } - if (task == active_task && task == selected_task) { + if (x_task == active_task && x_task == selected_task) { color_pair = 3; } if (color_pair == 1 || color_pair == 3) { @@ -723,28 +771,36 @@ void draw_table() { 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 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, task->name, copy_length); + memcpy(line_buffer+2, x_task->name, copy_length); - uint32_t *time = task->times; - 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]); + 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)); // Update totals. total = 0; - for(int idx = DAYS_ON_WEEK-1; idx >= 0; idx--) { - total += time[idx]; - totals[idx] += time[idx]; + for(int idx = 7-1; idx >= 0; idx--) { + total += times[idx]; + totals[idx] += times[idx]; } - totals[DAYS_ON_WEEK] += total; - sprintf(line_buffer + columns[8], "%10" PRIu64, total); // BUG This causes "corrupted size vs. prev_size because it is writing beyond line_buffer size. + 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); @@ -774,73 +830,9 @@ void draw_table() { 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[]) { @@ -930,26 +922,14 @@ int main(int argc, char *argv[]) { initialization(); - initialize_layouts(); + initialize_tui(); - 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. - timeout(1000); // Make getch() timeout after timeout(...) miliseconds. - - 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); - + // 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. ch = KEY_RESIZE; do { @@ -1045,13 +1025,9 @@ int main(int argc, char *argv[]) { 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: @@ -1069,9 +1045,7 @@ int main(int argc, char *argv[]) { if (size_x >= 60 && size_y > 2) { selected_layout = size_x > 100 ? 0 : 1; - draw_header(); - draw_table(); - draw_footer(); + draw_tui(); } else { const char *INVALID_WINDOW_MESSAGE = "Please expand window."; @@ -1091,32 +1065,3 @@ int main(int argc, char *argv[]) { 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); -} -- cgit v1.2.3