aboutsummaryrefslogtreecommitdiff
path: root/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'main.c')
-rw-r--r--main.c875
1 files changed, 472 insertions, 403 deletions
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 <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <limits.h>
+#include <locale.h>
#include <ncurses.h>
#include <stdbool.h>
#include <stddef.h>
@@ -14,39 +15,40 @@
#include <string.h>
#include <time.h>
-#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);