aboutsummaryrefslogtreecommitdiff
path: root/ttt.jai
diff options
context:
space:
mode:
Diffstat (limited to 'ttt.jai')
-rw-r--r--ttt.jai466
1 files changed, 183 insertions, 283 deletions
diff --git a/ttt.jai b/ttt.jai
index ad93b3b..6279f27 100644
--- a/ttt.jai
+++ b/ttt.jai
@@ -29,10 +29,9 @@
VERSION :: "2.0"; // Use only 3 chars (to fit layouts).
YEAR :: "2023";
-TASK_NAME_LENGTH :: 57;
-TASK_NAME_BYTES :: #run TASK_NAME_LENGTH+1; // TODO Get rid of this!
FIRST_DAY_OF_WEEK :: 1; // (0-6, Sunday = 0).
-NUM_WEEK_DAYS :: 7; // Just to be more clear about what we're looping about.
+NUM_WEEK_DAYS :: 7; // TODO This has to go - Just to be more clear about what we're looping about.
+NAME_SIZE :: 72; // TODO Use this instead of Task.name.count ?
APP_FOLDER_NAME :: ".task_time_tracker_v2"; // TODO Using _v2 to avoid erasing my work data.
DB_FILE_NAME :: "database.bin";
@@ -48,8 +47,6 @@ MAX_DATABASE_TASKS :: S64_MAX;
Task :: struct {
times : [NUM_WEEK_DAYS] s64;
name : [72] u8;
- //name_data : [70] u8;
- //name.data = cast(*~s8 u8) (0x80 ^ 0x01); // *name_data[0];
}
Database :: struct {
@@ -102,57 +99,54 @@ error_window : *WINDOW = null;
error_time_limit := Apollo_Time.{0, 0};
print_error :: (format :string, args : .. Any) {
- // NOTE Implement me please.
-// if stdscr == null || isendwin() == true { // Not in ncurses mode?
+ if stdscr == null || isendwin() == true { // Not in ncurses mode?
print(format, ..args);
print("\n");
-// }
-// else {
-// int w_size_x = size_x > 120 ? 120 : size_x - 2;
-// int w_size_y = 4;
-// if (error_window == NULL) {
-// error_window = newwin(w_size_y, w_size_x, (size_y - w_size_y) / 2, (size_x - w_size_x) / 2);
-// wattron(error_window, COLOR_PAIR(ERROR));
-// wborder(error_window, ' ', ' ', 0, 0, ACS_HLINE, ACS_HLINE, ACS_HLINE, ACS_HLINE);
-// mvwprintw(error_window, 0, 1, " Error ");
-// wmove(error_window, 1, 0);
-// }
-// else {
-// waddch(error_window, ' ');
-// }
-// vw_printw(error_window, format, args);
-// error_time_limit = time(NULL) + 5; // NOTE Instead of time use the current_time_monotonic()
-// }
+ }
+ else {
+ CHAR_SPACE :: #char " ";
+ w_size_x: s32 = ifx size_x > 120 then 120 else size_x - 2;
+ w_size_y: s32 = 4;
+ if (error_window == null) {
+ error_window = newwin(w_size_y, w_size_x, (size_y - w_size_y) / 2, (size_x - w_size_x) / 2);
+ wattron(error_window, COLOR_PAIR(xx Styles.ERROR));
+ wborder(error_window, CHAR_SPACE, CHAR_SPACE, 0, 0, ACS_HLINE, ACS_HLINE, ACS_HLINE, ACS_HLINE);
+ mvwprintw(error_window, 0, 1, " Error ");
+ wmove(error_window, 1, 0);
+ }
+ else {
+ waddch(error_window, CHAR_SPACE);
+ }
+ vw_printw(error_window, to_c_string(format), args);
+ error_time_limit = current_time_monotonic() + seconds_to_apollo(5);
+ }
}
draw_error_window :: () {
- /* NOTE Implement me please.
- if (error_window == NULL) {
- return;
- }
+ if (error_window == null) return;
// Hide error window after time-limit or if terminal is shrank.
- int w_size_x, w_size_y;
- getmaxyx(error_window, w_size_y, w_size_x);
- if (time(NULL) >= error_time_limit
+ w_size_x: s32;
+ w_size_y: s32;
+ getmaxyx(error_window, *w_size_y, *w_size_x);
+ if (current_time_monotonic() >= error_time_limit
|| size_x - w_size_x < 2
|| size_y - w_size_y < 2
) {
delwin(error_window);
- error_window = NULL;
+ error_window = null;
return;
}
// Adjust error window position.
- int pos_x = (size_x - w_size_x) / 2;
- int pos_y = (size_y - w_size_y) / 2;
+ pos_x := (size_x - w_size_x) / 2;
+ pos_y := (size_y - w_size_y) / 2;
mvwin(error_window, pos_y, pos_x);
// Avoid being overwritten by main window content.
refresh();
touchwin(error_window);
wrefresh(error_window);
- */
}
trigger_autosave :: () {
@@ -240,12 +234,6 @@ is_empty_string :: (str: string) -> bool {
return true;
}
-replace_char :: (str: string, find: u8, replace: u8) -> string {
- assert(false, "Use modules/String/module.jai:replace_chars");
- // TODO Use modules/String/module.jai:replace_chars
- return "";
-}
-
// Prints, on row y and column x, the time using 5 characters centered on space.
// Returns the result of a call to mvprintw.
mvprintw_time :: (y: s32, x: s32, time: s64, space: s32) -> int {
@@ -306,23 +294,16 @@ sub_int64 :: (x :s64, y :s64) -> s64 {
// Returns active task or NULL if none applies.
get_active_task :: inline (db: Database) -> *Task {
- task: *Task = null;
- if (db.active_idx >= 0) {
- task = *db.tasks[db.active_idx];
- }
- return task;
+ return ifx db.active_idx >= 0 then *db.tasks[db.active_idx] else null;
}
// Returns selected task or NULL if none applies.
get_selected_task :: inline (db: Database) -> *Task {
- task: *Task = null;
- if (db.selected_idx >= 0) {
- task = *db.tasks[db.selected_idx];
- }
- return task;
+ return ifx db.selected_idx >= 0 then *db.tasks[db.selected_idx] else null;
}
-contains_task :: inline (db: *Database, task: *Task) -> bool {
+// TODO Add description.
+contains_task :: inline (db: Database, task: *Task) -> bool {
return task >= db.tasks.data && task - db.tasks.data < db.tasks.count;
}
@@ -331,37 +312,14 @@ contains_task :: inline (db: *Database, task: *Task) -> bool {
add_task :: (db: *Database, task: Task = .{}) -> task: *Task {
assert(db != null, "Parameter 'db' is null.");
array_add(*db.tasks, task);
- return *db.tasks[db.tasks.count-1];
-}
-/*
-// Duplicates the given task. Duplicated task is appended to the database.
-// Returns success.
-bool duplicate_task(database_st *db, task_st *task) {
- assert(db != NULL);
- assert(task != NULL);
-
- // Create new task and keep task_idx (relative pointer) of original task).
- ptrdiff_t task_idx = task - db->tasks;
- task_st *new_task;
- if (create_task(db, &new_task) == false) {
- return false;
- }
-
- // If original task belonged to database, fix its pointer.
- if (0 <= task_idx && task_idx < db->count - 1) { // Compensate '- 1' for the new task.
- task = db->tasks + task_idx;
- }
-
- memcpy(new_task, task, SIZEOF_TASK_ST);
- // Add task time values to total times.
- for (int idx = 0; idx < NUM_WEEK_DAYS; idx++) {
- db->total_times[idx] = add_int64(db->total_times[idx], new_task->times[idx]);
+ for * db.total_times {
+ <<it = add_int64(<<it, task.times[it_index]);
}
- return true;
+ return *db.tasks[db.tasks.count-1];
}
-*/
+
// Deletes task from database.
// If possible, shrinks the database capacity.
// Returns success.
@@ -425,88 +383,86 @@ delete_task :: (db: *Database, task: *Task) -> bool {
return true;
}
-/*
+
// Moves task to index.
// Index gets clamped to [0, db->count[.
-void move_task_to_index(database_st *db, task_st *task, ptrdiff_t index) {
- assert(db != NULL);
- assert(task != NULL);
- assert(task >= db->tasks && task - db->tasks < db->count);
-
- ptrdiff_t target_index = index < 0 ? 0
- : index >= db->count ? db->count - 1
- : index;
+move_task_to_index :: (db: *Database, task: *Task, index: s64) {
+ assert(db != null, "Parameter 'db' is null.");
+ assert(task != null, "Parameter 'task' is null.");
+ assert(contains_task(db, task), "Database does not contain task.");
- task_st *target_task = db->tasks + target_index;
+ target_index := clamp(index, 0, db.tasks.count-1);
+ target_task := *db.tasks[target_index];
- if (target_task == task) {
- return;
- }
+ if (target_task == task) return;
// Move task to new location.
- task_st temp_task;
- memcpy(&temp_task, task, SIZEOF_TASK_ST);
- if (target_task > task) {
- memmove(task, task + 1, (target_task - task) * SIZEOF_TASK_ST);
+ temp_task := <<task;
+ if target_task > task {
+ //memmove(task, task + 1, (target_task - task) * SIZEOF_TASK_ST); TODO Maybe simplify this moves.
+ offset := task - db.tasks.data;
+ size := target_task - task;
+ for 0..size-1
+ db.tasks[offset + it] = db.tasks[offset + it + 1];
}
else {
- memmove(target_task + 1, target_task, (task - target_task) * SIZEOF_TASK_ST);
+ //memmove(target_task + 1, target_task, (task - target_task) * SIZEOF_TASK_ST); TODO Maybe simplify this moves.
+ offset := target_task - db.tasks.data;
+ size := task - target_task;
+ for < size-1..0
+ db.tasks[offset + it + 1] = db.tasks[offset + it];
}
- memcpy(target_task, &temp_task, SIZEOF_TASK_ST);
+ <<target_task = temp_task;
// Adjust active and selected tasks.
- ptrdiff_t source_index = task - db->tasks;
- if (db->active_task == source_index) {
- db->active_task = target_index;
+ source_index := task - db.tasks.data;
+ if (db.active_idx == source_index) {
+ db.active_idx = target_index;
}
- else if (source_index < db->active_task && db->active_task <= target_index) {
- db->active_task--;
+ else if (source_index < db.active_idx && db.active_idx <= target_index) {
+ db.active_idx -= 1;
}
- else if (target_index <= db->active_task && db->active_task < source_index) {
- db->active_task++;
+ else if (target_index <= db.active_idx && db.active_idx < source_index) {
+ db.active_idx += 1;
}
- db->selected_task = target_index;
+ db.selected_idx = target_index;
}
-*/
+
// Updates the times on the active task (and adjusts database totals).
update_times :: (db: *Database) {
assert(db != null);
- return;
- /*
- // Get current UTC time.
- time_t stop_time = time(NULL);
- // Get last modified on UTC time.
- time_t start_time = db->modified_on;
+ if db.active_idx < 0 return;
- // Keep track of this update.
- db->modified_on = stop_time;
-
- if (db->active_task < 0) {
- return;
- }
+ // Get time frame in UTC.
+ start_time := db.modified_on;
+ stop_time := current_time_consensus();
- task_st *active_task = db->tasks + db->active_task;
- uint8_t start_week_day;
+ active_task := *db.tasks[db.active_idx];
+ start_week_day: s8;
while (start_time < stop_time) {
- start_week_day = localtime(&start_time)->tm_wday;
+ start_week_day = to_calendar(start_time, .LOCAL).day_of_week_starting_at_0;
// 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;
- 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_of_day_cal := to_calendar(start_time, .LOCAL);
+ start_of_day_cal.hour = 0;
+ start_of_day_cal.minute = 0;
+ start_of_day_cal.second = 0;
+ start_of_day_cal.millisecond = 0;
+ start_of_day := calendar_to_apollo(start_of_day_cal);
+
+ next_day := start_of_day + #run seconds_to_apollo(SECONDS_IN_DAY);
+ next_start := ifx next_day < stop_time then next_day else stop_time;
+ elapsed_time := to_seconds(next_start - start_time);
+ active_task.times[start_week_day] += elapsed_time;
+ db.total_times[start_week_day] += elapsed_time;
start_time = next_start;
}
- */
+
+ // Keep track of this update.
+ db.modified_on = stop_time;
}
// Recalculates database totals.
@@ -797,37 +753,35 @@ import_from_csv :: (db: *Database, path: string) -> bool {
return true;
}
-/*
+
// Appends task to the end of the CSV file.
// Returns success.
-bool append_to_csv(task_st *task, const char *path) {
- assert(task != NULL);
- assert(path != NULL);
+append_to_csv :: (task: Task, path: string) -> success: bool {
+ assert(xx path, "Parameter 'path' is empty.");
- FILE *file = fopen(path, "a+");
- if (file == NULL) {
- print_error("Failed to open file '%s' while appending to CSV: %s.", path, strerror(errno));
+ file, file_success := file_open(path, true, true);
+ defer file_close(*file);
+ if file_success == false {
+ //print_error("Failed to open file '%s' while appending to CSV: %s.", path, strerror(errno)); // TODO Show internal error or something
return false;
}
- char last_char;
- fseek(file, -1, SEEK_END);
- fread(&last_char, SIZEOF_CHAR, 1, file);
- if (last_char != '\n') {
- fprintf(file, "\n");
+ file_size := file_length(file);
+ file_set_position(file, file_size-1);
+ last_char: u8;
+ file_read(file, *last_char, 1);
+ if (last_char != #char "\n") {
+ file_write(*file, "\n");
}
- char name[TASK_NAME_BYTES];
- memcpy(name, task->name, TASK_NAME_BYTES);
- replace_char(name, ',', ' ');
- fprintf(file, "%s,%" PRId64 ",%" PRId64 ",%" PRId64 ",%" PRId64 ",%" PRId64 ",%" PRId64 ",%" PRId64 "\n",
- name, task->times[0], task->times[1], task->times[2], task->times[3], task->times[4], task->times[5], task->times[6]
- );
+ task_name := copy_temporary_string(xx task.name); // TODO Cleanup this temp mess.
+ replace_chars(task_name, ",", #char " ");
+ csv_line := tprint("%,%,%,%,%,%,%,%\n", task_name, task.times[0], task.times[1], task.times[2], task.times[3], task.times[4], task.times[5], task.times[6]);
+ file_write(*file, csv_line);
- fclose(file);
return true;
}
-*/
+
// Selects task by index.
// Index gets clamped to [0, db->count[.
select_task_by_index :: (db: *Database, index: s64) {
@@ -1157,33 +1111,22 @@ draw_tui :: (db: *Database, layout: *Layout) {
x += 1;
mvprintw_time(xx y, xx x, total_time, xx layout.columns[L_TOTAL_IDX].width);
}
-/*
-void *mem_alloc(size_t mem_size, const char *error_tag) {
- void *mem_pointer = malloc(mem_size);
- if (mem_pointer == NULL && mem_size > 0) {
- print_error("Failed to allocate memory (%s): %s.", (error_tag == NULL ? "undefined" : error_tag), strerror(errno));
- exit(EXIT_FAILURE);
- }
- return mem_pointer;
-}
-*/
free_memory :: () {
reset_database(*database);
reset_database(*archive);
- //free(string_buffer); string_buffer = NULL;
free(app_directory);
free(db_file_path);
free(ar_file_path);
//reset_temporary_storage();
}
-read_input_with_space :: (row: int, column: int, style: s32, length: int, space: int) -> string {
+read_input_string_padded :: (row: int, column: int, style: s32, length: int, padding: int) -> string {
str := talloc_string(length);
memset(str.data, 0, str.count);
attron(style | A_UNDERLINE);
- mvprintw(xx row, xx column, "%*s", space, "");
+ mvprintw(xx row, xx column, "%*s", padding, "");
echo();
curs_set(1);
mvgetnstr(xx row, xx column, str.data, xx length);
@@ -1193,37 +1136,28 @@ read_input_with_space :: (row: int, column: int, style: s32, length: int, space:
attrset(A_NORMAL);
return str;
}
-/*
-void read_input_to_string_buffer(int row, int column, int style, int length) {
- read_input_to_string_buffer_with_space(row, column, style, length, length);
+
+read_input_string :: (row: int, column: int, style: s32, length: int) -> string {
+ return read_input_string_padded(row, column, style, length, length);
}
// Returns success.
-bool read_input_to_int(int row, int style, const char *message, intmax_t *result) {
- assert(message != NULL);
- assert(result != NULL);
-
- attron(style);
- move(row, 1);
+read_input_int :: (row: int, style: s32, message: string) -> value: int, success: bool {
+ attron(xx style);
+ move(xx row, 1);
addch(ACS_CKBOARD);
- addstr(message);
+ addstr(message.data); // TODO Convert to C type
attrset(A_NORMAL);
// Get line number.
- int input_pos_x = getcurx(stdscr);
- int input_width = size_x - input_pos_x - 1;
- read_input_to_string_buffer(row, input_pos_x, style, input_width);
+ input_pos_x := getcurx(stdscr);
+ input_width := size_x - input_pos_x - 1;
+ str := read_input_string(row, input_pos_x, style, input_width);
- char *parser;
- errno = 0;
- *result = strtoimax(string_buffer, &parser, 10);
-
- bool success = (errno == 0 || errno == ERANGE) // No error OR value was clamped to limits (acceptable).
- && parser != string_buffer; // If no digits are found, parser will return the address of the input string.
-
- return success;
+ value, success := parse_int(*str);
+ return value, success;
}
-*/
+
// Retuns true if user presses enter, false otherwise.
read_enter_confirmation :: (row: int, style: int, message: string) -> bool {
assert(message.data != null);
@@ -1465,17 +1399,6 @@ main :: () {
clear();
getmaxyx(stdscr, *size_y, *size_x);
is_terminal_too_small = size_x < 60 || size_y < 3;
- new_size := 2047 | TASK_NAME_BYTES | (size_x + 1);
- //if (string_buffer_size < new_size) {
- //string_buffer_size = new_size;
- //string_buffer = realloc(string_buffer, string_buffer_size);
- //if (string_buffer == NULL && string_buffer_size > 0) {
- //print_error("Failed to allocate memory for string buffer: %s.", strerror(errno));
- //flushinp();
- //ungetch(#char "q");
- //break;
- //}
- //}
update_layout();
layout = *layouts[ifx size_x > 100 then Layouts.NORMAL else Layouts.COMPACT];
@@ -1506,8 +1429,7 @@ main :: () {
if (selected_task == null) continue;
// Change task name.
- // TODO remove TASK_NAME_LENGTH
- input := read_input_with_space(selected_task_row, 1, action_style, TASK_NAME_LENGTH, size_x - 2);
+ input := read_input_string_padded(selected_task_row, 1, action_style, Task.name.count, size_x - 2);
if is_empty_string(input) == false {
replace_chars(input, "\t\x0B\x0C\r", #char " ");
memcpy(selected_task.name.data, input.data, min(Task.name.count, input.count));
@@ -1615,58 +1537,41 @@ main :: () {
trigger_autosave();
break;
}
-
- case 'm':
- case 'M': {
- if (selected_task == NULL) {
- break;
- }
+*/
+ case #char "m"; #through;
+ case #char "M";
+ if selected_task == null continue;
- intmax_t value;
- if (read_input_to_int(selected_task_row, action_style, " Move to: ", &value) == false) {
- break;
- }
+ value, success := read_input_int(selected_task_row, action_style, " Move to: ");
+ if success == false continue;
- ptrdiff_t target_index = (value < 1 ? 1 : value > MAX_DATABASE_TASKS ? MAX_DATABASE_TASKS : value) - 1;
+ target_index := clamp(value, 1, MAX_DATABASE_TASKS) - 1;
move_task_to_index(db, selected_task, target_index);
trigger_autosave();
- break;
- }
-
- case 'g':
- case 'G': {
- if (selected_task == NULL) {
- break;
- }
+
+ case #char "g"; #through;
+ case #char "G";
+ if selected_task == null continue;
- intmax_t value;
- if (read_input_to_int(selected_task_row, action_style, " Go to: ", &value) == false) {
- break;
- }
+ value, success := read_input_int(selected_task_row, action_style, " Go to: ");
+ if success == false continue;
- ptrdiff_t target_index = (value < 1 ? 1 : value > MAX_DATABASE_TASKS ? MAX_DATABASE_TASKS : value) - 1;
+ target_index := clamp(value, 1, MAX_DATABASE_TASKS) - 1;
select_task_by_index(db, target_index);
- break;
- }
- case 'd':
- case 'D':{
- if (selected_task == NULL) {
- break;
- }
+ case #char "d"; #through;
+ case #char "D";
+ if selected_task == null continue;
- if (is_database_full(db)) {
+ if is_database_full(db) {
read_enter_confirmation(selected_task_row, error_style, " Unable to duplicate entry: database is full. ");
- break;
+ continue;
}
- if (duplicate_task(db, selected_task) == false) {
- break;
- }
+ if (add_task(db, selected_task) == null) continue; // TODO Show error?
+
trigger_autosave();
- break;
- }
- */
+
case KEY_F5;
update_total_times(db);
trigger_autosave();
@@ -1700,39 +1605,34 @@ main :: () {
reset_database(*archive);
db = *database;
}
-/*
- case 'a':
- case 'A': {
- if (db != &database || selected_task == NULL || selected_task == active_task) {
- break;
- }
+
+ case #char "a"; #through;
+ case #char "A";
+ if (db != *database || selected_task == null || selected_task == active_task) continue;
+
if (append_to_csv(selected_task, ar_file_path) == false) {
print_error("Failed to archive entry.");
- break;
+ continue;
}
- delete_task(&database, selected_task);
+ delete_task(*database, selected_task);
trigger_autosave();
- break;
- }
-
- case 'r':
- case 'R': {
- if (db != &archive || selected_task == NULL) {
- break;
- }
- if (is_database_full(&database)) {
+
+ case #char "r"; #through;
+ case #char "R";
+ if (db != *archive || selected_task == null) continue;
+
+ if is_database_full(*database) {
read_enter_confirmation(selected_task_row, error_style, " Unable to restore entry: database is full. ");
- break;
+ continue;
}
- if (duplicate_task(&database, selected_task) == false) {
+
+ if (add_task(*database, selected_task) == null) {
print_error("Failed to restore entry.");
- break;
+ continue;
}
- delete_task(&archive, selected_task);
+ delete_task(*archive, selected_task);
trigger_autosave();
- break;
- }
- */
+
case KEY_HOME;
select_task_by_index(db, 0);
@@ -1754,26 +1654,26 @@ main :: () {
}
// Save any unsaved changes.
- show_processing();
+ show_processing();
error_saving := false;
- if (db == *archive) {
- if (export_to_csv(archive, ar_file_path) == false) {
- print_error("Failed to save archive.");
- error_saving |= true;
- }
- }
- if (countdown_to_autosave > 0 || is_autosave_enabled == false) {
- if (store_database(database, db_file_path) == false) {
- print_error("Failed to save database.");
- error_saving |= true;
- }
- }
- if (error_saving) {
- print_error("Press any key to close.");
- draw_error_window();
- timeout(INPUT_AWAIT_INF);
- getch();
- }
+ if (db == *archive) {
+ if (export_to_csv(archive, ar_file_path) == false) {
+ print_error("Failed to save archive.");
+ error_saving |= true;
+ }
+ }
+ if (countdown_to_autosave > 0 || is_autosave_enabled == false) {
+ if (store_database(database, db_file_path) == false) {
+ print_error("Failed to save database.");
+ error_saving |= true;
+ }
+ }
+ if (error_saving) {
+ print_error("Press any key to close.");
+ draw_error_window();
+ timeout(INPUT_AWAIT_INF);
+ getch();
+ }
endwin();
exit(xx ifx error_saving then 1 else 0);