Compare commits
57 Commits
v1.0.0
...
557a4affa6
| Author | SHA1 | Date | |
|---|---|---|---|
|
557a4affa6
|
|||
|
3b99b87c22
|
|||
|
f40b4001c7
|
|||
|
143feaac83
|
|||
|
972bd28b6f
|
|||
|
8421e3ee01
|
|||
|
bda1c18a52
|
|||
|
37cd828c21
|
|||
|
c746cad281
|
|||
|
0abfa4af0f
|
|||
|
ceede3001a
|
|||
|
29eeef56b8
|
|||
|
e6203f70a9
|
|||
|
d8a3fe0d7a
|
|||
|
7c060419f4
|
|||
|
c3b1797bd9
|
|||
|
e9d8988ef2
|
|||
|
476428b7de
|
|||
|
58eda93cbb
|
|||
|
21a6c077fd
|
|||
|
2e0f7501d7
|
|||
|
3809b5b96a
|
|||
|
79a313a03e
|
|||
|
c3dee6a8ef
|
|||
|
20d6e4a8b1
|
|||
|
65d591a455
|
|||
|
65b5e45cba
|
|||
|
e3f807bd7f
|
|||
|
9bd8817797
|
|||
|
e8e0babfaa
|
|||
|
3159a34c9a
|
|||
|
1267fc54bb
|
|||
|
613071c8f5
|
|||
|
0cf3febd2b
|
|||
|
76396a3022
|
|||
|
e900ffc0d7
|
|||
|
dd148396f1
|
|||
|
71939366c7
|
|||
|
a5734d1323
|
|||
|
14f177e3f9
|
|||
|
881d13b546
|
|||
|
a852107b62
|
|||
|
3acbe3eb33
|
|||
|
672998b17b
|
|||
|
01e6a6064d
|
|||
|
d0ec4b758b
|
|||
|
b27047e7d1
|
|||
|
71e935c8ad
|
|||
|
44a4374fd1
|
|||
|
76c620c142
|
|||
|
b49b728a4c
|
|||
|
bb0c5c173a
|
|||
|
b476873ede
|
|||
|
78a190b28f
|
|||
|
21dd4c988e
|
|||
|
e875f6fab3
|
|||
|
06a0b782cf
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1 +1,3 @@
|
||||
todd
|
||||
*.o
|
||||
*.todd
|
||||
|
||||
27
3rd-party/NCURSES-LICENSE.txt
vendored
Normal file
27
3rd-party/NCURSES-LICENSE.txt
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
-------------------------------------------------------------------------------
|
||||
-- Copyright (c) 1998-2004,2006 Free Software Foundation, Inc. --
|
||||
-- --
|
||||
-- Permission is hereby granted, free of charge, to any person obtaining a --
|
||||
-- copy of this software and associated documentation files (the --
|
||||
-- "Software"), to deal in the Software without restriction, including --
|
||||
-- without limitation the rights to use, copy, modify, merge, publish, --
|
||||
-- distribute, distribute with modifications, sublicense, and/or sell copies --
|
||||
-- of the Software, and to permit persons to whom the Software is furnished --
|
||||
-- to do so, subject to the following conditions: --
|
||||
-- --
|
||||
-- The above copyright notice and this permission notice shall be included --
|
||||
-- in all copies or substantial portions of the Software. --
|
||||
-- --
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS --
|
||||
-- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF --
|
||||
-- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN --
|
||||
-- NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, --
|
||||
-- DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR --
|
||||
-- OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE --
|
||||
-- USE OR OTHER DEALINGS IN THE SOFTWARE. --
|
||||
-- --
|
||||
-- Except as contained in this notice, the name(s) of the above copyright --
|
||||
-- holders shall not be used in advertising or otherwise to promote the --
|
||||
-- sale, use or other dealings in this Software without prior written --
|
||||
-- authorization. --
|
||||
-------------------------------------------------------------------------------
|
||||
16
3rd-party/STB-DS-LICENSE.txt
vendored
Normal file
16
3rd-party/STB-DS-LICENSE.txt
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
Copyright (c) 2017 Sean Barrett
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
1895
3rd-party/stb-ds.h
vendored
Normal file
1895
3rd-party/stb-ds.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
22
LICENSE.txt
Normal file
22
LICENSE.txt
Normal file
@@ -0,0 +1,22 @@
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2024 Zvonimir Rudinski
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
23
Makefile
Normal file
23
Makefile
Normal file
@@ -0,0 +1,23 @@
|
||||
.DEFAULT_GOAL := all
|
||||
|
||||
CC=gcc
|
||||
CFLAGS=-Wall -Wextra -Werror -pedantic -std=c99 -O3
|
||||
|
||||
todo.o:
|
||||
$(CC) $(CFLAGS) -c engine/todo.c -o todo.o
|
||||
|
||||
main.o:
|
||||
$(CC) $(CFLAGS) -c main.c -o main.o
|
||||
|
||||
clean:
|
||||
rm -f *.o
|
||||
rm -f todd
|
||||
rm /usr/local/bin/todd || true
|
||||
|
||||
todd: todo.o main.o
|
||||
$(CC) $(CFLAGS) todo.o main.o -lncurses -o todd
|
||||
|
||||
all: todd
|
||||
|
||||
install: todd
|
||||
cp todd /usr/local/bin/todd
|
||||
16
README.md
Normal file
16
README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# Todd
|
||||
`todd` is a to-do app written in C.
|
||||
## Requirements
|
||||
- GCC
|
||||
- GNU Make
|
||||
- ncurses development libraries
|
||||
## Building
|
||||
To build `todd` all you need to do is run `make`.
|
||||
## Usage
|
||||
```sh
|
||||
./todd <file.todd>
|
||||
```
|
||||
## 3rd Party
|
||||
Todd uses the following dependencies:
|
||||
- stb_ds - https://github.com/nothings/stb
|
||||
- ncurses - https://tldp.org/HOWTO/NCURSES-Programming-HOWTO/index.html
|
||||
15
color.h
Normal file
15
color.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#ifndef COLOR_H
|
||||
#define COLOR_H
|
||||
|
||||
#include <ncurses.h>
|
||||
|
||||
#define BORDERS_PRIMARY COLOR_BLACK
|
||||
#define BORDERS_SECONDARY COLOR_WHITE
|
||||
|
||||
#define DEFAULT_PRIMARY COLOR_WHITE
|
||||
#define DEFAULT_SECONDARY COLOR_BLACK
|
||||
|
||||
#define COMPLETED_PRIMARY COLOR_BLACK
|
||||
#define COMPLETED_SECONDARY COLOR_YELLOW
|
||||
|
||||
#endif
|
||||
56
engine/todo.c
Normal file
56
engine/todo.c
Normal file
@@ -0,0 +1,56 @@
|
||||
#include "todo.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
// Memory management
|
||||
void todo_free_item(TodoItem *item) { free(item->title); }
|
||||
|
||||
// Item operations
|
||||
TodoItem todo_create_item(char *title) {
|
||||
TodoItem item;
|
||||
item.title_length = strlen(title);
|
||||
item.title = malloc(item.title_length + 1);
|
||||
strcpy(item.title, title);
|
||||
item.completed = false;
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
void todo_mark_item(TodoItem *item, bool_t completed) {
|
||||
item->completed = completed;
|
||||
}
|
||||
|
||||
void todo_print_item(TodoItem *item) {
|
||||
printf("%s - ", item->title);
|
||||
if (item->completed) {
|
||||
printf("Completed\n");
|
||||
} else {
|
||||
printf("Not Completed\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Serialization
|
||||
char *todo_item_serialize(TodoItem *item, int *buffer_size_out) {
|
||||
// Layout of the serialized item:
|
||||
// title_length - 1 byte
|
||||
// title - title_length bytes
|
||||
// completed - 1 byte
|
||||
unsigned char title_length = item->title_length;
|
||||
unsigned char completed_length = sizeof(item->completed);
|
||||
int buffer_size = sizeof(unsigned char) + title_length + completed_length;
|
||||
|
||||
char *buffer = malloc(buffer_size);
|
||||
|
||||
// copy title length
|
||||
memcpy(buffer, &title_length, sizeof(unsigned char));
|
||||
// copy title
|
||||
memcpy(buffer + sizeof(unsigned char), item->title, title_length);
|
||||
// copy completed
|
||||
memcpy(buffer + sizeof(unsigned char) + title_length, &item->completed,
|
||||
completed_length);
|
||||
|
||||
*buffer_size_out = buffer_size;
|
||||
|
||||
return buffer;
|
||||
}
|
||||
25
engine/todo.h
Normal file
25
engine/todo.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#ifndef TODO_H
|
||||
#define TODO_H
|
||||
#define TODO_MAX_TITLE_LENGTH 255
|
||||
|
||||
#define true 1
|
||||
#define false 0
|
||||
typedef unsigned char bool_t;
|
||||
|
||||
typedef struct {
|
||||
unsigned char title_length;
|
||||
char *title;
|
||||
bool_t completed;
|
||||
} TodoItem;
|
||||
|
||||
// Memory management
|
||||
void todo_free_item(TodoItem *item);
|
||||
|
||||
// Item operations
|
||||
TodoItem todo_create_item(char *title);
|
||||
void todo_mark_item(TodoItem *item, bool_t completed);
|
||||
void todo_print_item(TodoItem *item);
|
||||
|
||||
// Serialization
|
||||
char *todo_item_serialize(TodoItem *item, int *buffer_size_out);
|
||||
#endif
|
||||
437
main.c
437
main.c
@@ -1,7 +1,438 @@
|
||||
#include "engine/todo.h"
|
||||
#include "color.h"
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <pwd.h>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
printf("Hello, world!\n");
|
||||
#define MAX_TODOS UINT_MAX
|
||||
#define MAX_PATH_LENGTH 1024
|
||||
#define STB_DS_IMPLEMENTATION
|
||||
|
||||
return 0;
|
||||
#define BORDERS_PAIR 1
|
||||
#define DEFAULT_PAIR 2
|
||||
#define COMPLETED_PAIR 3
|
||||
|
||||
#include "3rd-party/stb-ds.h"
|
||||
#define KEY_BINDINGS " [a]dd [v]view [m]ark [d]elete [w]rite [l]oad [q]uit"
|
||||
|
||||
bool dirty = false;
|
||||
char SAVE_FILE[MAX_PATH_LENGTH] = "";
|
||||
TodoItem *todos = NULL;
|
||||
|
||||
enum Command {
|
||||
ADD = 'a',
|
||||
VIEW = 'v',
|
||||
MARK = 'm',
|
||||
REMOVE = 'd',
|
||||
QUIT = 'q',
|
||||
|
||||
WRITE_TO_FILE = 'w',
|
||||
LOAD_FROM_FILE = 'l',
|
||||
|
||||
UP = 'k',
|
||||
DOWN = 'j',
|
||||
|
||||
UP_ARROW = KEY_UP,
|
||||
DOWN_ARROW = KEY_DOWN,
|
||||
SPACE = ' '
|
||||
};
|
||||
|
||||
void clear_todos(void) {
|
||||
for (int i = 0; i < arrlen(todos); i++) {
|
||||
todo_free_item(&todos[i]);
|
||||
}
|
||||
|
||||
arrfree(todos);
|
||||
}
|
||||
|
||||
void initialize_curses(int *width, int *height) {
|
||||
initscr();
|
||||
curs_set(0);
|
||||
int tw, th;
|
||||
getmaxyx(stdscr, th, tw);
|
||||
|
||||
*width = tw;
|
||||
*height = th;
|
||||
|
||||
start_color();
|
||||
init_pair(BORDERS_PAIR, BORDERS_PRIMARY, BORDERS_SECONDARY);
|
||||
init_pair(DEFAULT_PAIR, DEFAULT_PRIMARY, DEFAULT_SECONDARY);
|
||||
init_pair(COMPLETED_PAIR, COMPLETED_PRIMARY, COMPLETED_SECONDARY);
|
||||
// turn off echoing of keys, and enter cbreak mode,
|
||||
// where no buffering is performed on keyboard input
|
||||
cbreak();
|
||||
noecho();
|
||||
keypad(stdscr, TRUE);
|
||||
}
|
||||
|
||||
void draw_header(int width) {
|
||||
attron(COLOR_PAIR(BORDERS_PAIR));
|
||||
move(0, 0);
|
||||
addstr(" Todd - ");
|
||||
addstr(SAVE_FILE);
|
||||
for (int i = strlen(" Todd - ") + strlen(SAVE_FILE); i < width - 1; i++) {
|
||||
addch(' ');
|
||||
}
|
||||
}
|
||||
|
||||
void draw_footer(int width, int height) {
|
||||
move(height - 1, 0);
|
||||
addch(' ');
|
||||
int todos_count = arrlen(todos);
|
||||
int marked_count = 0;
|
||||
|
||||
for (int i = 0; i < todos_count; i++) {
|
||||
if (todos[i].completed) {
|
||||
marked_count++;
|
||||
}
|
||||
}
|
||||
|
||||
// get the length of the string
|
||||
int length = snprintf(NULL, 0, "%d/%d", marked_count, todos_count);
|
||||
// print the string
|
||||
printw("%d/%d", marked_count, todos_count);
|
||||
|
||||
// print the rest of the line but leave space for the key bindings
|
||||
for (unsigned long i = length; i < width - strlen(KEY_BINDINGS) - 1; i++) {
|
||||
addch(' ');
|
||||
}
|
||||
|
||||
// print the key bindings
|
||||
printw(KEY_BINDINGS);
|
||||
|
||||
attroff(COLOR_PAIR(BORDERS_PAIR));
|
||||
}
|
||||
|
||||
char alert(const char *message, int terminal_width, int terminal_height) {
|
||||
int window_height = 5;
|
||||
int window_width = terminal_width - 4;
|
||||
int window_y = terminal_height / 2 - 1;
|
||||
int window_x = 2;
|
||||
|
||||
int message_x = (terminal_width - 4 - strlen(message)) / 2;
|
||||
int message_y = 2;
|
||||
|
||||
// create a new window
|
||||
WINDOW *alert_window = newwin(window_height, window_width, window_y, window_x);
|
||||
// draw a border around the window
|
||||
box(alert_window, 0, 0);
|
||||
// print the message in the middle of the window
|
||||
mvwprintw(alert_window, message_y, message_x, "%s", message);
|
||||
// refresh the window
|
||||
wrefresh(alert_window);
|
||||
// wait for a key press
|
||||
char key = getch();
|
||||
|
||||
// delete the window
|
||||
delwin(alert_window);
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
void input_string(int terminal_width, int terminal_height, char *buffer, int buffer_size) {
|
||||
int window_height = 5;
|
||||
int window_width = terminal_width - 4;
|
||||
int window_y = terminal_height / 2 - 1;
|
||||
int window_x = 2;
|
||||
|
||||
int message_x = strlen("Enter todo title: ") + 1;
|
||||
int message_y = 2;
|
||||
|
||||
curs_set(1);
|
||||
// create a new window
|
||||
WINDOW *input_window = newwin(window_height, window_width, window_y, window_x);
|
||||
// draw a border around the window
|
||||
box(input_window, 0, 0);
|
||||
// print the message in the middle of the window
|
||||
mvwprintw(input_window, message_y, message_x, "Enter todo title: ");
|
||||
// refresh the window
|
||||
wrefresh(input_window);
|
||||
// wait for a key press
|
||||
echo();
|
||||
mvwgetnstr(input_window, message_y, message_x + strlen("Enter todo title: "), buffer, buffer_size);
|
||||
noecho();
|
||||
|
||||
// delete the window
|
||||
delwin(input_window);
|
||||
curs_set(0);
|
||||
}
|
||||
|
||||
void display_todos(int width, int height, int current_line) {
|
||||
int current_y = 1;
|
||||
int current_index = 0;
|
||||
const char *padding = "...";
|
||||
unsigned long title_length = 0;
|
||||
unsigned long padding_length = strlen(padding);
|
||||
|
||||
char *title = (char *)malloc(width);
|
||||
|
||||
attron(COLOR_PAIR(DEFAULT_PAIR));
|
||||
|
||||
// calculate the offset
|
||||
if (height - 4 <= current_line) {
|
||||
// ignore the first n todos
|
||||
current_index = current_line - (height - 4);
|
||||
}
|
||||
|
||||
// while we have space to print todos
|
||||
while (current_y < height - 2) {
|
||||
// check if we're out of bounds
|
||||
if (current_index >= arrlen(todos)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// copy the title to the buffer
|
||||
title_length = strlen(todos[current_index].title);
|
||||
strncpy(title, todos[current_index].title, width - padding_length - 1);
|
||||
// append the padding if needed
|
||||
if (title_length > strlen(title)) {
|
||||
strcat(title, padding);
|
||||
}
|
||||
|
||||
// move to the correct line
|
||||
move(current_y, 0);
|
||||
|
||||
// if the current todo is completed, print it as green
|
||||
if (todos[current_index].completed) {
|
||||
attroff(COLOR_PAIR(DEFAULT_PAIR));
|
||||
attron(COLOR_PAIR(COMPLETED_PAIR));
|
||||
}
|
||||
|
||||
// highlight the current line
|
||||
if (current_index == current_line) {
|
||||
attron(A_UNDERLINE);
|
||||
}
|
||||
|
||||
printw("%d. %s", current_index + 1, title);
|
||||
|
||||
// increment the current y and index
|
||||
current_y++;
|
||||
current_index++;
|
||||
|
||||
attroff(A_UNDERLINE);
|
||||
attroff(COLOR_PAIR(COMPLETED_PAIR));
|
||||
attron(COLOR_PAIR(DEFAULT_PAIR));
|
||||
}
|
||||
|
||||
free(title);
|
||||
}
|
||||
|
||||
// Command handlers
|
||||
void add_command_handler(int width, int height) {
|
||||
char title[TODO_MAX_TITLE_LENGTH];
|
||||
|
||||
input_string(width, height, title, TODO_MAX_TITLE_LENGTH);
|
||||
|
||||
TodoItem item = todo_create_item(title);
|
||||
arrput(todos, item);
|
||||
|
||||
// set the dirty flag
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
void view_command_handler(int width, int height, int index) {
|
||||
// check if the index is out of bounds
|
||||
if (index < 0 || index >= arrlen(todos)) {
|
||||
return;
|
||||
}
|
||||
|
||||
TodoItem item = todos[index];
|
||||
// create an alert in the middle of the screen
|
||||
alert(item.title, width, height);
|
||||
}
|
||||
|
||||
void mark_command_handler(int index) {
|
||||
// check if the index is out of bounds
|
||||
if (index < 0 || index >= arrlen(todos)) {
|
||||
return;
|
||||
}
|
||||
|
||||
TodoItem item = todos[index];
|
||||
|
||||
todo_mark_item(&item, !item.completed);
|
||||
|
||||
todos[index] = item;
|
||||
|
||||
// set the dirty flag
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
void remove_command_handler(int index) {
|
||||
// check if the index is out of bounds
|
||||
if (index < 0 || index >= arrlen(todos)) {
|
||||
return;
|
||||
}
|
||||
|
||||
arrdel(todos, index);
|
||||
|
||||
// set the dirty flag
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
void write_to_file_handler(void) {
|
||||
FILE *file = fopen(SAVE_FILE, "wb");
|
||||
int bs = 0;
|
||||
int todos_count = arrlen(todos);
|
||||
// write a magic value "TODD" to the file
|
||||
fwrite("TODD", sizeof(char), 4, file);
|
||||
// write the number of todos to the file
|
||||
fwrite(&todos_count, sizeof(int), 1, file);
|
||||
// write each todo to the file
|
||||
for (int i = 0; i < todos_count; i++) {
|
||||
char *buffer = todo_item_serialize(&todos[i], &bs);
|
||||
fwrite(buffer, sizeof(char), bs, file);
|
||||
free(buffer);
|
||||
}
|
||||
fclose(file);
|
||||
// reset the dirty flag
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
void load_from_file_handler(void) {
|
||||
// clear the current todos
|
||||
clear_todos();
|
||||
|
||||
FILE *file = fopen(SAVE_FILE, "rb");
|
||||
|
||||
// if unable to open the file, return
|
||||
if (file == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
char magic[5] = {'\0'};
|
||||
int todos_count = 0;
|
||||
// check the "TODD" (without the \0) magic value
|
||||
fread(magic, sizeof(char), 4, file);
|
||||
if (strcmp(magic, "TODD") != 0) {
|
||||
printf("Invalid file format\nExpected TODD, got %s\n", magic);
|
||||
return;
|
||||
}
|
||||
|
||||
// read the number of todos
|
||||
fread(&todos_count, sizeof(int), 1, file);
|
||||
|
||||
// read each todo
|
||||
for (int i = 0; i < todos_count; i++) {
|
||||
// read the title length
|
||||
unsigned char title_length = 0;
|
||||
fread(&title_length, sizeof(unsigned char), 1, file);
|
||||
|
||||
// read the title
|
||||
char *title = malloc(title_length + 1);
|
||||
fread(title, sizeof(char), title_length, file);
|
||||
title[title_length] = '\0';
|
||||
|
||||
// read the completed flag
|
||||
bool_t completed = false;
|
||||
fread(&completed, sizeof(bool_t), 1, file);
|
||||
|
||||
// create the todo item
|
||||
TodoItem item = todo_create_item(title);
|
||||
|
||||
// mark the item as completed if needed
|
||||
if (completed) {
|
||||
todo_mark_item(&item, true);
|
||||
}
|
||||
|
||||
// add the item to the list
|
||||
arrput(todos, item);
|
||||
}
|
||||
|
||||
// reset the dirty flag
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int terminal_width = 0;
|
||||
int terminal_height = 0;
|
||||
int key = 0;
|
||||
int running = 1;
|
||||
int current_line = 0;
|
||||
|
||||
// set the save file path if provided
|
||||
if (argc > 1 && strlen(argv[1]) < MAX_PATH_LENGTH) {
|
||||
strncpy(SAVE_FILE, argv[1], MAX_PATH_LENGTH);
|
||||
} else {
|
||||
// load the default save file which is "~/.todos.todd"
|
||||
struct passwd *pw = getpwuid(getuid());
|
||||
const char *homedir = pw->pw_dir;
|
||||
// check if we have enough space to store the path
|
||||
assert(strlen(homedir) + strlen("/.todos.todd") < MAX_PATH_LENGTH);
|
||||
// create the path
|
||||
snprintf(SAVE_FILE, MAX_PATH_LENGTH, "%s/.todos.todd", homedir);
|
||||
}
|
||||
|
||||
// if file exists, load it
|
||||
load_from_file_handler();
|
||||
initialize_curses(&terminal_width, &terminal_height);
|
||||
|
||||
while (running) {
|
||||
clear();
|
||||
draw_header(terminal_width);
|
||||
draw_footer(terminal_width, terminal_height);
|
||||
display_todos(terminal_width, terminal_height, current_line);
|
||||
refresh();
|
||||
|
||||
// wait for a key press
|
||||
key = getch();
|
||||
|
||||
// handle the key press
|
||||
switch (key) {
|
||||
case LOAD_FROM_FILE:
|
||||
load_from_file_handler();
|
||||
alert("Loaded!", terminal_width, terminal_height);
|
||||
break;
|
||||
case WRITE_TO_FILE:
|
||||
write_to_file_handler();
|
||||
alert("Saved!", terminal_width, terminal_height);
|
||||
break;
|
||||
case ADD:
|
||||
add_command_handler(terminal_width, terminal_height);
|
||||
break;
|
||||
case VIEW:
|
||||
view_command_handler(terminal_width, terminal_height, current_line);
|
||||
break;
|
||||
case MARK:
|
||||
case SPACE:
|
||||
mark_command_handler(current_line);
|
||||
break;
|
||||
case REMOVE:
|
||||
remove_command_handler(current_line);
|
||||
if (current_line >= arrlen(todos)) {
|
||||
current_line = arrlen(todos) - 1;
|
||||
}
|
||||
break;
|
||||
case QUIT:
|
||||
if (dirty) {
|
||||
key = alert("You have unsaved changes. Save changes? [y/n]", terminal_width, terminal_height);
|
||||
if (key == 'y') {
|
||||
write_to_file_handler();
|
||||
}
|
||||
}
|
||||
running = 0;
|
||||
break;
|
||||
case UP:
|
||||
case UP_ARROW:
|
||||
current_line--;
|
||||
if (current_line < 0) {
|
||||
current_line = 0;
|
||||
}
|
||||
break;
|
||||
case DOWN:
|
||||
case DOWN_ARROW:
|
||||
current_line++;
|
||||
if (current_line >= arrlen(todos)) {
|
||||
current_line = arrlen(todos) - 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
endwin();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user