diff --git a/.gitignore b/.gitignore index ea1fc4e..adb9908 100644 --- a/.gitignore +++ b/.gitignore @@ -70,4 +70,5 @@ build #Downloads contents Downloads -deps.mk \ No newline at end of file +deps.mk +src/deps.mk \ No newline at end of file diff --git a/Makefile b/Makefile index 9491643..c75c8f0 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -CFLAGS := -ggdb -Wall -pedantic +CFLAGS := -ggdb -Wall -pedantic -lcurses SRC_DIR := src OBJ_DIR := src/obj @@ -7,7 +7,19 @@ BIN_DIR := bin SRCS := $(wildcard $(SRC_DIR)/*.c) OBJS := $(SRCS:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o) -$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c $(SRC_DIR)/main.h +UI_SRCS := $(wildcard $(SRC_DIR)/ui/*.c $(SRC_DIR)/ui/**/*.c) +OBJS += $(UI_SRCS:$(SRC_DIR)/ui/%.c=$(OBJ_DIR)/ui/%.o) + +$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c $(SRC_DIR)/%.h + $(CC) $(CFLAGS) -c $< -o $@ + +$(OBJ_DIR)/ui/%.o: $(SRC_DIR)/ui/%.c $(SRC_DIR)/ui/%.h + $(CC) $(CFLAGS) -c $< -o $@ + +$(OBJ_DIR)/ui/widget/%.o: $(SRC_DIR)/ui/widget/%.c $(SRC_DIR)/ui/widget/%.h + $(CC) $(CFLAGS) -c $< -o $@ + +$(OBJ_DIR)/ui/modals/%.o: $(SRC_DIR)/ui/modals/%.c $(SRC_DIR)/ui/modals/%.h $(CC) $(CFLAGS) -c $< -o $@ bbs-client: $(OBJS) @@ -21,7 +33,7 @@ deps.mk: $(SRCS) $(CC) -MM $^ > $(SRC_DIR)/$@ clean: - rm -f $(OBJ_DIR)/*.o $(BIN_DIR)/bbs-client + rm -f $(OBJ_DIR)/*.o $(OBJ_DIR)/**/*.o $(OBJ_DIR)/ui/**/*.o $(BIN_DIR)/bbs-client run: $(BIN_DIR)/bbs-client diff --git a/TODO.md b/TODO.md index 349a124..5cc57a0 100644 --- a/TODO.md +++ b/TODO.md @@ -1,2 +1,19 @@ TODO: -- [ ] Check and fix registration and login \ No newline at end of file +- [x] More universal Makefile +- [ ] Check and fix registration and login +- [x] UI: destroy child elements everywhere +- [x] use parent widgets pointers to count the margins correctly +- [ ] Hotkeys for buttons +- [ ] Group elements as lists +- [x] Process arrows +- [x] Optimize modals re-drawings +- [x] Bug with file list on downloading files +- [x] Remember page numbers +- [ ] "Server message" to notification +- [ ] file list -> list template + +UPLOAD: +- [x] Progress bar +- [x] Show notifications and messages from the server (universal way) + use notification or something + diff --git a/setup.sh b/setup.sh index 3f848cb..900ac76 100755 --- a/setup.sh +++ b/setup.sh @@ -1,4 +1,7 @@ #!/bin/sh mkdir bin -mkdir Downloads \ No newline at end of file +mkdir Downloads +mkdir src/obj/ui +mkdir src/obj/ui/modals +mkdir src/obj/ui/widget \ No newline at end of file diff --git a/src/client.c b/src/client.c deleted file mode 100644 index 059dadc..0000000 --- a/src/client.c +++ /dev/null @@ -1,44 +0,0 @@ -#include "main.h" -#include -#include -#include -#include - -int process_client_command(char *line, int l_len, query_args_t *q_args) { - /* FILE LIST */ - if (!strncmp(line, "file list", sizeof("file list") - 1)) { - write(q_args->sd, "file list", sizeof("file list") - 1); - q_args->state = STATE_FILE_LIST; - return 0; - } - - /* FILE UPLOAD */ - if (!strncmp(line, "file upload", sizeof("file upload") - 1)) { - if (!file_upload_request(line, q_args)) { - q_args->state = STATE_UPLOAD_REQUESTED; - } else { - print_prompt(q_args->params); - q_args->state = WAIT_CLIENT; - } - return 0; - } - - /* FILE DOWNLOAD */ - if (!strncmp(line, "file download", sizeof("file download") - 1)) { - return 0; - } - - /* EXIT */ - if (!strncmp(line, "exit", sizeof("exit") - 1)) { - close(q_args->sd); - printf("Bye!\n"); - exit(0); - } - - return 0; -} - -void print_prompt(params_t *params) { - printf("%s> ", params->uname); - fflush(stdout); -} \ No newline at end of file diff --git a/src/common.h b/src/common.h new file mode 100644 index 0000000..76b7bcf --- /dev/null +++ b/src/common.h @@ -0,0 +1,8 @@ +#ifndef COMMON_H +#define COMMON_H +#include +typedef struct { + uint32_t cur_y, cur_x; + uint32_t max_y, max_x; +} coordinates_t; +#endif \ No newline at end of file diff --git a/src/connection.c b/src/connection.c index 311feb1..8096162 100644 --- a/src/connection.c +++ b/src/connection.c @@ -1,5 +1,7 @@ #include "main.h" +#include "ui/app.h" #include +#include #include #include #include @@ -10,15 +12,18 @@ #include #include -void connect_to_server(int sd, params_t *params) { +void connect_to_server(app_t *app) { struct sockaddr_in server; server.sin_family = AF_INET; - server.sin_addr.s_addr = params->addr; - server.sin_port = params->port; - if (-1 == connect(sd, (struct sockaddr *)&server, sizeof(server))) { + server.sin_addr.s_addr = app->params->addr; + server.sin_port = app->params->port; + if (-1 == + connect(app->params->sd, (struct sockaddr *)&server, sizeof(server))) { perror("connect"); - exit(2); + destroy_app(app, 2); } + app->params->is_connected = TRUE; + print_bars(app); } void init_params(params_t *params) { @@ -27,6 +32,7 @@ void init_params(params_t *params) { params->uname = NULL; params->privileges = 0; params->port = htons(SERVER_PORT); + params->is_connected = FALSE; } void clear_params(params_t *params) { @@ -41,20 +47,11 @@ void clear_params(params_t *params) { params->uname = NULL; } -void get_missing_params(params_t *params) { - size_t lsize; - char *bufptr = NULL; - +void get_ip_port(params_t *params, char *ip, char *port) { struct sockaddr_in addr; - - if (!params->addr) { - printf("host> "); - getline(&bufptr, &lsize, stdin); - inet_aton(bufptr, &(addr.sin_addr)); - params->addr = addr.sin_addr.s_addr; - free(bufptr); - bufptr = NULL; - } + inet_aton(ip, &(addr.sin_addr)); + params->addr = addr.sin_addr.s_addr; + params->port = htons(atoi(port)); } void close_session(int sd) { close(sd); } diff --git a/src/connection.h b/src/connection.h new file mode 100644 index 0000000..0de36c4 --- /dev/null +++ b/src/connection.h @@ -0,0 +1,14 @@ +#ifndef CONNECTION_H +#define CONNECTION_H +#include "main.h" +#include "ui/app.h" + +void connect_to_server(app_t *params); +void init_params(params_t *restrict); +void clear_params(params_t *params); +void get_ip_port(params_t *params, char *ip, char *port); +void close_session(int sd); +int init_client(); +void analyze_args(int argc, char *argv[], params_t *restrict); + +#endif \ No newline at end of file diff --git a/src/deps.mk b/src/deps.mk index e69de29..76aa036 100644 --- a/src/deps.mk +++ b/src/deps.mk @@ -0,0 +1,26 @@ +connection.o: src/connection.c src/main.h src/types.h src/ui/app.h \ + src/ui/../common.h src/ui/widget.h src/ui/../main.h \ + src/ui/widget/dialogue.h src/ui/widget/../widget.h \ + src/ui/widget/../../common.h src/ui/widget/button.h \ + src/ui/widget/../../main.h src/ui/widget/group.h +file_processor.o: src/file_processor.c src/file_processor.h src/main.h \ + src/types.h src/ui/widget/file_list.h src/ui/widget/../widget.h \ + src/ui/widget/../../main.h src/query.h src/ui/app.h src/ui/../common.h \ + src/ui/widget.h src/ui/widget/dialogue.h src/ui/widget/../../common.h \ + src/ui/widget/button.h src/ui/widget/../../main.h src/ui/widget/group.h \ + src/ui/widget/dialogue.h src/ui/widget/progress_bar.h +fs.o: src/fs.c +main.o: src/main.c src/connection.h src/main.h src/types.h src/ui/app.h \ + src/ui/../common.h src/ui/widget.h src/ui/../main.h \ + src/ui/widget/dialogue.h src/ui/widget/../widget.h \ + src/ui/widget/../../common.h src/ui/widget/button.h \ + src/ui/widget/../../main.h src/ui/widget/group.h src/query.h \ + src/ui/widget.h src/ui/widget/file_list.h +query.o: src/query.c src/connection.h src/main.h src/types.h src/ui/app.h \ + src/ui/../common.h src/ui/widget.h src/ui/../main.h \ + src/ui/widget/dialogue.h src/ui/widget/../widget.h \ + src/ui/widget/../../common.h src/ui/widget/button.h \ + src/ui/widget/../../main.h src/ui/widget/group.h src/file_processor.h \ + src/ui/widget/file_list.h src/server.h +server.o: src/server.c src/main.h src/types.h src/ui/widget/file_list.h \ + src/ui/widget/../widget.h src/ui/widget/../../main.h diff --git a/src/file_processor.c b/src/file_processor.c index dd4e5f7..15a3649 100644 --- a/src/file_processor.c +++ b/src/file_processor.c @@ -1,4 +1,3 @@ -#include "main.h" #include #include #include @@ -15,11 +14,16 @@ #include #include -static void fl_add(fl_item_t **cur, fl_item_t **start, char *fname); -static fl_item_t *fl_select(fl_item_t *start, int num); -static void fl_clear(fl_item_t **start, fl_item_t **current); +#include "file_processor.h" +#include "main.h" +#include "query.h" +#include "ui/widget/dialogue.h" +#include "ui/widget/file_list.h" +#include "ui/widget/progress_bar.h" -static void clear_file_in_query(query_args_t *q_args); +static void fl_add(fl_item_t **cur, fl_item_t **start, char *fname); +fl_item_t *fl_select(fl_item_t *start, int num); +void fl_clear(fl_item_t **start, fl_item_t **current); void file_list(file_args_t *f_args, query_args_t *q_args) { uint32_t qlen; @@ -27,56 +31,61 @@ void file_list(file_args_t *f_args, query_args_t *q_args) { static uint32_t idx = 1; static char qbuf[INBUFSIZE * 2]; static uint32_t qbuf_used = 0; + ui_file_list_t *fui = (ui_file_list_t *)q_args->file_list_ui; + dialogue_t *d = (dialogue_t *)q_args->active_dialogue; + fui->start = &(f_args->l_start); + fui->current = &(f_args->l_current); - if (qbuf_used == 0) qbuf[0] = 0; + if (qbuf_used == 0) + qbuf[0] = 0; while ((qlen = query_extract_from_buf(q_args->buf, &(q_args->buf_used), &query))) { - if (!strcmp("list_end\n", query)) { - q_args->state = STATE_FILE_SELECT; + if (!strncmp(":END:", query, sizeof(":END:") - 1)) { + sscanf(query, ":END: PAGE %u/%u COUNT: %u/%u\n", &fui->current_page, + &fui->pages, &fui->current_count, &fui->full_count); idx = 1; + if (d != NULL && d->is_initiated) { + d->needs_update = true; + } + draw_file_list(fui); + q_args->state = S_FILE_SELECT; break; } strcat(qbuf, query); qbuf_used += strlen(qbuf); - if (strchr(qbuf, '\n') == NULL) continue; + if (strchr(qbuf, '\n') == NULL) + continue; fl_add(&(f_args->l_current), &(f_args->l_start), qbuf); - printf("[%d] [%s | %s]\n%s\n", idx++, f_args->l_current->name, - f_args->l_current->owner, f_args->l_current->description); + fui->current_count++; free(query); query = NULL; qbuf_used = 0; qbuf[0] = 0; } - - while ((qlen = query_extract_from_buf(q_args->buf, &(q_args->buf_used), - &query))) { - write(STDOUT_FILENO, query, strlen(query)); - } } +/* file_select: old function, can be removed in the future */ void file_select(file_args_t *f_args, query_args_t *q_args) { char *buf = q_args->buf; fl_item_t *l_start = f_args->l_start; - fl_item_t *l_current = f_args->l_current; + // fl_item_t *l_current = f_args->l_current; fl_item_t *l_selected; /* from the list */ fl_item_t *f_selected = &(f_args->f_selected); /* new copy of file struct (list be cleared) */ uint32_t filenum; - uint32_t pagenum; uint32_t qlen; char send_buf[256]; struct stat st = {0}; - - if (!(sscanf(buf, "%d", &filenum))) { + + if (!(sscanf(buf, "%u", &filenum))) { write(q_args->sd, buf, strlen(buf)); - q_args->state = STATE_FILE_LIST; + q_args->state = S_FILE_LIST; fl_clear(&f_args->l_start, &f_args->l_current); return; } if (filenum == 0) { - print_prompt(q_args->params); q_args->state = WAIT_CLIENT; return; } @@ -103,27 +112,74 @@ void file_select(file_args_t *f_args, query_args_t *q_args) { qlen = sprintf(send_buf, "error: %s\n", f_selected->name); write(q_args->sd, send_buf, qlen); perror(f_selected->name); - print_prompt(q_args->params); q_args->state = WAIT_CLIENT; free(file_path); return; } qlen = sprintf(send_buf, "file download %s\n", f_selected->name); write(q_args->sd, send_buf, qlen); - q_args->state = STATE_FILE_DOWNLOAD; + q_args->state = S_FILE_DOWNLOAD; free(file_path); } +int32_t ui_file_select(file_args_t *f_args, query_args_t *q_args, int32_t idx) { + fl_item_t *l_selected; /* from the list */ + fl_item_t *f_selected = + &(f_args->f_selected); /* new copy of file struct (list be cleared) */ + uint32_t qlen; + char send_buf[256]; + struct stat st = {0}; + + l_selected = fl_select(f_args->l_start, idx); + + if (l_selected == NULL) { + return -1; + } + + memcpy(f_selected, l_selected, sizeof(fl_item_t)); + + if (stat(DOWNLOADS_DIR, &st) == -1) { + mkdir(DOWNLOADS_DIR, 0700); + } + char *file_path = malloc(sizeof(DOWNLOADS_DIR) + strlen(l_selected->name) + + 2); // + '/' + '\0' + f_selected->name = NULL; + sprintf(file_path, "%s/%s", DOWNLOADS_DIR, l_selected->name); + f_selected->name = strdup(l_selected->name); + fl_clear(&f_args->l_start, &f_args->l_current); + f_args->file_d = open(file_path, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (f_args->file_d == -1) { + qlen = sprintf(send_buf, "error: %s\n", f_selected->name); + write(q_args->sd, send_buf, qlen); + perror(f_selected->name); + q_args->state = WAIT_CLIENT; + free(file_path); + return -2; + } + qlen = sprintf(send_buf, "file download %s\n", f_selected->name); + write(q_args->sd, send_buf, qlen); + q_args->state = S_FILE_DOWNLOAD; + free(file_path); + return OK; +} + void file_download(file_args_t *f_args, query_args_t *q_args) { fl_item_t *f_selected = &(f_args->f_selected); static uint32_t it_count = 0; static size_t it_interval = 0; if (it_interval == 0) { - it_interval = (f_selected->size / INBUFSIZE / 100) * 10; /* every 10% */ + it_interval = (f_selected->size / INBUFSIZE / 100) * 5; /* every 1% */ if (it_interval == 0) it_interval = 1; } + char answer[256]; + static size_t size_rest = 0; + uint32_t progress = (f_selected->size - size_rest) * 100 / f_selected->size; + ui_progress_bar_t *pb = (ui_progress_bar_t *)q_args->progress_bar; + dialogue_t *d = (dialogue_t *)q_args->active_dialogue; + ui_file_list_t *fui = (ui_file_list_t *)q_args->file_list_ui; + uint32_t a_len = 0; if (size_rest == 0) size_rest = f_selected->size; @@ -131,10 +187,11 @@ void file_download(file_args_t *f_args, query_args_t *q_args) { if (qlen) { it_count++; if (!(it_count % it_interval)) { - printf("%zu / %zu\n", f_selected->size - size_rest, f_selected->size); - } - if (it_count % it_interval) + pb->procent = progress; + d->needs_update = true; + } else { q_args->buf_used = 0; + } if (size_rest < qlen) { size_rest = 0; } else { @@ -144,15 +201,21 @@ void file_download(file_args_t *f_args, query_args_t *q_args) { it_count = 0; it_interval = 0; f_selected->size = 0; - printf("File %s is downloaded\n", f_selected->name); close(f_args->file_d); + d->needs_destroy = true; + sprintf(answer, "File %s is downloaded from the server!", + f_selected->name); + q_args->notification = malloc(strlen(answer) + 1); + strcpy(q_args->notification, answer); free(f_selected->name); - print_prompt(q_args->params); - q_args->state = WAIT_CLIENT; + sprintf(answer, "file list %u %u\n%n", fui->max_lines, fui->current_page, + &a_len); + write(q_args->sd, answer, a_len); + q_args->state = S_FILE_LIST; } } } - +/* obsolete */ int32_t file_upload_request(char *query, query_args_t *q_args) { char qbuf[INBUFSIZE]; char fpath[512]; @@ -183,6 +246,27 @@ int32_t file_upload_request(char *query, query_args_t *q_args) { return 0; } +int32_t file_upload_open(char *dpath, char *fname, query_args_t *q_args) { + char a_perm; + q_args->file = malloc(sizeof(p_file_t)); + q_args->file->path = malloc(strlen(dpath) + strlen(fname) + 2); + sprintf(q_args->file->path, "%s/%s", dpath, fname); + int fd = open(q_args->file->path, O_RDONLY); + if (fd == -1) { + perror(q_args->file->path); + return -1; + } + size_t fsize = lseek(fd, 0, SEEK_END); + lseek(fd, 0, SEEK_SET); + q_args->file->fd = fd; + q_args->file->name = malloc(strlen(fname) + 1); + strcpy(q_args->file->name, fname); + q_args->file->size = fsize; + q_args->file->rest = fsize; + q_args->file->description = NULL; + return 0; +} + int32_t file_upload_start(query_args_t *q_args) { char *query; query_extract_from_buf(q_args->buf, &(q_args->buf_used), &query); @@ -195,35 +279,44 @@ int32_t file_upload_start(query_args_t *q_args) { } int32_t file_upload(query_args_t *q_args) { - /* static size_t it_int = 0; - static int it_count = 0; - - if (it_int == 0) { - it_int = (q_args->file->size / INBUFSIZE / 100) * 10; // every 10% -} */ - + static uint32_t it_count = 0; + static size_t it_interval = 0; + if (it_interval == 0) { + it_interval = (q_args->file->size / INBUFSIZE / 100) * 5; /* every 1% */ + if (it_interval == 0) + it_interval = 1; + } + uint32_t progress = + (q_args->file->size - q_args->file->rest) * 100 / q_args->file->size; + ui_progress_bar_t *pb = (ui_progress_bar_t *)q_args->progress_bar; + dialogue_t *d = (dialogue_t *)q_args->active_dialogue; if (q_args->buf_used > 0) { int wlen = write(q_args->sd, q_args->buf, q_args->buf_used); if (q_args->buf_used != wlen) { /* TODO: ошибка */ return -1; } else { + it_count++; + if (!(it_count % it_interval)) { + pb->procent = progress; + d->needs_update = true; + } q_args->buf[0] = 0; q_args->buf_used = 0; q_args->file->rest -= wlen; } } else { - /* file upload is finished */ - printf("Upload of %s is finished.\n", q_args->file->name); - /* TODO: ждать ответа от сервера */ - clear_file_in_query(q_args); + it_count = 0; + it_interval = 0; return 1; } return 0; } -static void clear_file_in_query(query_args_t *q_args) { +void clear_file_in_query(query_args_t *q_args) { free(q_args->file->name); + free(q_args->file->path); + free(q_args->file->description); free(q_args->file); q_args->file = NULL; } @@ -240,7 +333,6 @@ void init_file_args(file_args_t *f_args) { /* work with file list */ static void fl_add(fl_item_t **cur, fl_item_t **start, char *line) { char fname[128]; - size_t fsize; char fowner[32]; int h_len; fl_item_t *fitem = malloc(sizeof(fl_item_t)); @@ -271,7 +363,7 @@ static void fl_add(fl_item_t **cur, fl_item_t **start, char *line) { *cur = fitem; } -static fl_item_t *fl_select(fl_item_t *start, int num) { +fl_item_t *fl_select(fl_item_t *start, int num) { fl_item_t *current = start; for (; num != 1; num--) { if (current->next != NULL) { @@ -283,7 +375,7 @@ static fl_item_t *fl_select(fl_item_t *start, int num) { return current; } -static void fl_clear(fl_item_t **start, fl_item_t **arg_current) { +void fl_clear(fl_item_t **start, fl_item_t **arg_current) { if (*start == NULL) return; fl_item_t *next, *current; diff --git a/src/file_processor.h b/src/file_processor.h new file mode 100644 index 0000000..4345e45 --- /dev/null +++ b/src/file_processor.h @@ -0,0 +1,17 @@ +#ifndef FILE_PROCESSOR_H +#define FILE_PROCESSOR_H +#include "main.h" +#include "ui/widget/file_list.h" + +void file_list(file_args_t *f_args, query_args_t *q_args); +void file_download(file_args_t *f_args, query_args_t *q_args); +void file_select(file_args_t *fargs, query_args_t *q_args); +void init_file_args(file_args_t *fargs); +int file_upload_request(char *, query_args_t *q_args); +int file_upload_start(query_args_t *q_args); +int file_upload(query_args_t *q_args); +int32_t file_upload_open(char *dpath, char *fname, query_args_t *q_args); +void fl_clear(fl_item_t **start, fl_item_t **arg_current); +int32_t ui_file_select(file_args_t *f_args, query_args_t *q_args, int32_t idx); +void clear_file_in_query(query_args_t *q_args); +#endif \ No newline at end of file diff --git a/src/fs.c b/src/fs.c new file mode 100644 index 0000000..74a36ec --- /dev/null +++ b/src/fs.c @@ -0,0 +1,39 @@ +#include +#include +#include + +#define BYTES_IN_GB 1073741824 +#define BYTES_IN_MB 1048576 +#define BYTES_IN_KB 1024 + +#define PRINT_SIZE(text, f_arg, s_arg, unit) \ + s_arg /= 100; \ + if (s_arg > 0) { \ + sprintf(text, "%zu,%zu%s", f_arg, s_arg, unit); \ + } else { \ + sprintf(text, "%zu%s", f_arg, unit); \ + } + +void size_to_text(size_t size, char *text) { + size_t kb = 0; + size_t mb = 0; + size_t gb = 0; + + gb = size / BYTES_IN_GB; + mb = size / BYTES_IN_MB; + kb = size / BYTES_IN_KB; + if (gb > 0) { + size = size % BYTES_IN_GB; + mb = size / BYTES_IN_MB; + PRINT_SIZE(text, gb, mb, "GB"); + } else if (mb > 0) { + size = size % BYTES_IN_MB; + kb = size / BYTES_IN_KB; + PRINT_SIZE(text, mb, kb, "MB"); + } else if (kb > 0) { + size = size % BYTES_IN_KB; + PRINT_SIZE(text, kb, size, "KB"); + } else { + sprintf(text, "%zu B", size); + } +} \ No newline at end of file diff --git a/src/fs.h b/src/fs.h new file mode 100644 index 0000000..58b0b19 --- /dev/null +++ b/src/fs.h @@ -0,0 +1,8 @@ +#ifndef LOC_FS_H +#define LOC_FS_H + +#include + +void size_to_text(size_t size, char *text); + +#endif \ No newline at end of file diff --git a/src/main.c b/src/main.c index 4240e1f..3b60225 100644 --- a/src/main.c +++ b/src/main.c @@ -1,23 +1,84 @@ -#include "main.h" #include +#include #include +#include +#include #include #include #include #include #include -/* TODO: перед загрузкой файла спрашивать права доступа - TODO: сделать при интерфес выбора по пунктам меню */ +#include "connection.h" +#include "main.h" +#include "query.h" +#include "ui/app.h" +#include "ui/widget.h" +#include "ui/widget/file_list.h" -int main(int argc, char *argv[]) { +uint32_t m_id = 0; + +int32_t process_user_input(app_t *app, callback_args_t *d_args); + +int main(int argc, char **argv) { + app_t *app; params_t params; + query_args_t *q_args = malloc(sizeof(query_args_t)); + char buf[INBUFSIZE]; + + app = calloc(1, sizeof(app_t)); + + init_nc(); + + app = init_app(); init_params(¶ms); + app->params = ¶ms; analyze_args(argc, argv, ¶ms); - get_missing_params(¶ms); - int sd = init_client(); - connect_to_server(sd, ¶ms); - query_loop(sd, ¶ms); + + wrefresh(app->left_win); + wrefresh(app->right_win); + + init_query_args(q_args, app->params); + q_args->buf = malloc(INBUFSIZE); + app->query_args = q_args; + q_args->file_list_ui = init_file_list(&(app->left_win), &(app->right_win)); + + /* init client to connect to the server */ + app->params->sd = init_client(); + + if (params.addr == 0) { + app->query_args->state = S_ASK_SEVER_IP; + } else { + connect_to_server(app); + app->query_args->state = S_WAIT_SERVER; + } + + app->query_args->sd = app->params->sd; + + query_loop(app); clear_params(¶ms); - return 0; + destroy_app(app, 0); + + return OK; +} + +int32_t process_user_input(app_t *app, callback_args_t *d_args) { + int32_t c; + c = wgetch(app->win); + switch (c) { + case KEY_F(9): + destroy_app(app, 0); + return OK; + case 'U': + case 'u': + if (!app->modal.is_initiated) { + app->query_args->state = S_UPLOAD_FILE_SELECT; + break; + } + default: + d_args->data = (void *)&c; + app->active_callback(d_args); + break; + } + return OK; } \ No newline at end of file diff --git a/src/main.h b/src/main.h index 59e8f72..4579c4c 100644 --- a/src/main.h +++ b/src/main.h @@ -1,4 +1,8 @@ +#ifndef MAIN_H +#define MAIN_H + #include +#include #include "types.h" #define LOCAL_PORT 1999 @@ -6,6 +10,12 @@ #define LISTEN_QLEN 32 #define INBUFSIZE 1024 #define DOWNLOADS_DIR "./Downloads" +#define DIALOGUE_TITLE 64 +#define DIALOGUE_TEXT 4096 +#define INPUT_TEXT DIALOGUE_TITLE +#ifndef __USE_GNU +#define __USE_GNU 1 +#endif typedef struct params { unsigned short port; @@ -13,26 +23,38 @@ typedef struct params { char *uname; char *pass; char privileges; + int32_t sd; + uint32_t is_connected : 1; } params_t; enum state { + S_N_D, + S_ASK_SEVER_IP, + S_ASK_LOGIN_TYPE, + S_ASK_LOGIN_USER, + S_WAIT_SERVER, + S_PRINT_SERVER_MESSAGE, + S_FILE_LIST, + S_FILE_SELECT, + S_FILE_DOWNLOAD, + S_UPLOAD_PARAMS, + S_UPLOAD_FILE, + S_UPLOAD_FILE_SELECT, + S_UPLOAD_REQUESTED, + S_ASK_USER_BEFORE_LOGIN, + S_ERR, + S_NEXT_ACTION, WAIT_SERVER_INIT, WAIT_SERVER, WAIT_REGISTER, WAIT_REGISTER_CONFIRMATION, WAIT_CLIENT, - UPLOAD_FILE, - STATE_FILE_LIST, - STATE_FILE_SELECT, - STATE_FILE_DOWNLOAD, - STATE_UPLOAD_PARAMS, - STATE_UPLOAD_FILE, - STATE_UPLOAD_REQUESTED, - STATE_ASK_USER_BEFORE_LOGIN, - ERR, }; + typedef struct p_file { char *name; + char *path; + char *description; size_t size; size_t rest; int fd; @@ -54,38 +76,23 @@ typedef struct file_args { } file_args_t; typedef struct query_args { - int sd; - int buf_used; + int32_t sd; + int32_t buf_used; p_file_t *file; enum state state; char *buf; - char from_server; + uint32_t from_server : 1; params_t *params; + char *next_server_command; + struct { + char *text; + uint64_t capacity; + uint64_t size; + } server_message; + void *file_list_ui; /* TODO: find solution to insert actual type without void pointers */ + void *progress_bar; + void *active_dialogue; + char *notification; } query_args_t; -void init_params(params_t *restrict); -void analyze_args(int argc, char *argv[], params_t *restrict); -void get_missing_params(params_t *restrict); -void connect_to_server(int sd, params_t *params); -int init_client(); -void close_session(int sd); -void wait_register(query_args_t *q_args); -int process_query(query_args_t *query_args, file_args_t *file_args); -void query_loop(int sd, params_t *); -int query_extract_from_buf(char *buf, int *buf_used, char **output_line); -int process_client_command(char *line, int l_len, query_args_t *q_args); -int process_server_command(char *line, int l_len, query_args_t *q_args); -void ask_register(params_t *params, char *email); - -void clear_params(params_t *params); - -/* file processor */ -void file_list(file_args_t *f_args, query_args_t *q_args); -void file_download(file_args_t *f_args, query_args_t *q_args); -void file_select(file_args_t *fargs, query_args_t *q_args); -void init_file_args(file_args_t *fargs); -int file_upload_request(char *, query_args_t *q_args); -int file_upload_start(query_args_t *q_args); -int file_upload(query_args_t *q_args); - -void print_prompt(params_t *params); \ No newline at end of file +#endif \ No newline at end of file diff --git a/src/query.c b/src/query.c index 2b38272..f3a571b 100644 --- a/src/query.c +++ b/src/query.c @@ -1,8 +1,9 @@ -#include "main.h" #include #include +#include #include #include +#include #include #include #include @@ -15,79 +16,106 @@ #include #include -static void init_query_args(query_args_t *q_args, int sd, char *buf, - enum state state, int buf_used, params_t *params); +#include "connection.h" +#include "file_processor.h" +#include "main.h" +#include "server.h" +#include "ui/app.h" + static void wait_side(query_args_t *q_args); void user_request_description(query_args_t *q_args); +int process_query(query_args_t *query_args, file_args_t *file_args); +int32_t process_user_input(app_t *app, callback_args_t *d_args); -void query_loop(int sd, params_t *params) { +void query_loop(app_t *app) { + query_args_t *query_args = app->query_args; fd_set readfds; - + int32_t sd = app->params->sd; size_t qlen; int sr; - char buf[INBUFSIZE]; - char *bufs = buf; static file_args_t file_args; - static query_args_t query_args; + callback_args_t d_args = { + .app = app, .element = NULL, .data = NULL, .resp_data = NULL}; - init_query_args(&query_args, sd, buf, WAIT_SERVER_INIT, 0, params); init_file_args(&file_args); + query_args->sd = sd; + app->file_args = &file_args; for (;;) { + /* update screen */ + app_draw_modal(app); + app_refresh(app); + FD_ZERO(&readfds); FD_SET(STDIN_FILENO, &readfds); FD_SET(sd, &readfds); - int maxfd = sd; - if (query_args.state == STATE_UPLOAD_FILE) { - if (query_args.file != NULL) { - FD_SET(query_args.file->fd, &readfds); - if (query_args.file->fd > maxfd) - maxfd = query_args.file->fd; + int maxfd = app->params->is_connected ? sd : STDIN_FILENO; + if (query_args->state == S_UPLOAD_FILE) { + if (query_args->file != NULL) { + FD_SET(query_args->file->fd, &readfds); + if (query_args->file->fd > maxfd) + maxfd = query_args->file->fd; } } - sr = select(maxfd + 1, &readfds, NULL, NULL, NULL); - if (sr == -1) { - perror("select"); - exit(3); + if (query_args->state == S_NEXT_ACTION && + query_args->next_server_command != NULL) { + query_args->buf_used = strlen(query_args->next_server_command); + memcpy(query_args->buf, query_args->next_server_command, + query_args->buf_used); + free(query_args->next_server_command); + query_args->next_server_command = NULL; + query_args->from_server = TRUE; + if (ERR == process_query(query_args, &file_args)) { + close_session(sd); + exit(1); + } + continue; + } else { + sr = select(maxfd + 1, &readfds, NULL, NULL, NULL); + if (sr == -1) { + perror("select"); + exit(3); + } } if (FD_ISSET(sd, &readfds)) { /* process request from the server */ - qlen = read(sd, buf, INBUFSIZE); + qlen = read(sd, query_args->buf, INBUFSIZE); if (qlen == 0) { close_session(sd); printf("\n*** server closed the connection. ***\n"); exit(1); } else { - query_args.buf_used = qlen; - query_args.from_server = 1; - if (-1 == process_query(&query_args, &file_args)) { + query_args->buf_used = qlen; + query_args->from_server = TRUE; + if (ERR == process_query(query_args, &file_args)) { close_session(sd); exit(1); } } } - if (query_args.file && query_args.file->fd > -1 && - FD_ISSET(query_args.file->fd, &readfds)) { + if (query_args->file && query_args->file->fd > -1 && + FD_ISSET(query_args->file->fd, &readfds)) { /* process upload/download */ - query_args.buf_used = read(query_args.file->fd, buf, INBUFSIZE); - process_query(&query_args, &file_args); + query_args->buf_used = + read(query_args->file->fd, query_args->buf, INBUFSIZE); + process_query(query_args, &file_args); } if (FD_ISSET(STDIN_FILENO, &readfds)) { /* process request from the client */ - qlen = read(STDIN_FILENO, buf, INBUFSIZE); - if (qlen == 0) { + /* qlen = read(STDIN_FILENO, buf, INBUFSIZE); */ + /* if (qlen == 0) { close_session(sd); exit(0); - } else { - query_args.buf_used = qlen; - query_args.from_server = 0; - if (-1 == process_query(&query_args, &file_args)) { - close_session(sd); - exit(1); - } + } else { */ + /* query_args->buf_used = qlen; + query_args->from_server = 0; */ + // if (-1 == process_query(&query_args, &file_args)) { + if (ERR == process_user_input(app, &d_args)) { + close_session(sd); + exit(1); } } } @@ -105,52 +133,58 @@ void wait_register(query_args_t *q_args) { int process_query(query_args_t *query_args, file_args_t *file_args) { query_args->buf[query_args->buf_used] = 0; - + int32_t res = 0; switch (query_args->state) { case WAIT_SERVER: case WAIT_SERVER_INIT: case WAIT_CLIENT: + case S_NEXT_ACTION: + case S_WAIT_SERVER: case WAIT_REGISTER_CONFIRMATION: wait_side(query_args); break; - case UPLOAD_FILE: - case ERR: + case S_ERR: break; - case STATE_FILE_LIST: + case S_FILE_LIST: file_list(file_args, query_args); break; - case STATE_FILE_SELECT: + case S_FILE_SELECT: if (query_args->from_server) { wait_side(query_args); } else { file_select(file_args, query_args); } break; - case STATE_FILE_DOWNLOAD: + case S_FILE_DOWNLOAD: file_download(file_args, query_args); break; - case STATE_UPLOAD_REQUESTED: + case S_UPLOAD_REQUESTED: if (!file_upload_start(query_args)) { - query_args->state = STATE_UPLOAD_FILE; + query_args->state = S_UPLOAD_FILE; } else { - print_prompt(query_args->params); query_args->state = WAIT_CLIENT; } break; - case STATE_UPLOAD_PARAMS: - case STATE_UPLOAD_FILE: - if (file_upload(query_args)) { + case S_UPLOAD_PARAMS: + case S_UPLOAD_FILE: + if ((res = file_upload(query_args)) == 1) { /* upload finishes */ user_request_description(query_args); - print_prompt(query_args->params); - query_args->state = WAIT_CLIENT; + clear_file_in_query(query_args); + query_args->state = S_WAIT_SERVER; + } else if (res == -1) { + /* TODO: Error */ } break; - case STATE_ASK_USER_BEFORE_LOGIN: + case S_ASK_USER_BEFORE_LOGIN: break; case WAIT_REGISTER: wait_register(query_args); break; + case S_ASK_SEVER_IP: + case S_ASK_LOGIN_TYPE: + case S_N_D: + break; } return 0; } @@ -198,58 +232,32 @@ static void wait_side(query_args_t *q_args) { int buf_used = q_args->buf_used; char *query = NULL; - if (q_args->state == WAIT_SERVER || q_args->state == WAIT_SERVER_INIT || - q_args->from_server) { - while ((qlen = query_extract_from_buf(q_args->buf, &buf_used, &query))) { - process_server_command(query, qlen, q_args); - free(query); - query = NULL; - } - } else if (q_args->state == WAIT_CLIENT) { - while ((qlen = query_extract_from_buf(q_args->buf, &buf_used, &query))) { - process_client_command(query, qlen, q_args); - free(query); - query = NULL; - } + while ((qlen = query_extract_from_buf(q_args->buf, &buf_used, &query))) { + process_server_command(query, qlen, q_args); + free(query); + query = NULL; } } -static void init_query_args(query_args_t *q_args, int sd, char *buf, - enum state state, int buf_used, params_t *params) { - q_args->buf = buf; - q_args->sd = sd; - q_args->state = state; - q_args->buf_used = buf_used; +void init_query_args(query_args_t *q_args, params_t *params) { + q_args->buf = NULL; + q_args->sd = -1; + q_args->state = S_N_D; + q_args->buf_used = 0; q_args->params = params; q_args->file = NULL; q_args->from_server = 0; + q_args->server_message.text = NULL; + q_args->server_message.capacity = 0; + q_args->server_message.size = 0; + q_args->next_server_command = NULL; + q_args->progress_bar = NULL; + q_args->active_dialogue = NULL; + q_args->notification = NULL; } void user_request_description(query_args_t *q_args) { - printf("File description > "); - fflush(stdout); - char buf[INBUFSIZE]; /* пока ограничение на буфер */ - char *line_buf = NULL; - int blen = 0; - int line_len = 0; - size_t ll; - while (blen < INBUFSIZE - 6) { /* reserve space for ":END:\n" */ - line_len = getline(&line_buf, &ll, stdin); - if (line_len == 1) { - /* free(line); -> не работает */ - break; - } - if (blen == 0) { - strcpy(buf, line_buf); - } else { - strcat(buf, line_buf); - } - free(line_buf); - line_buf = NULL; - blen += line_len; - } - - strcat(buf, ":END:\n"); - blen += 6; - write(q_args->sd, buf, blen); + strcat(q_args->file->description, "\n\n:END:\n"); + write(q_args->sd, q_args->file->description, + strlen(q_args->file->description) - 1); } \ No newline at end of file diff --git a/src/query.h b/src/query.h new file mode 100644 index 0000000..a068e68 --- /dev/null +++ b/src/query.h @@ -0,0 +1,13 @@ +#ifndef QUERY_H +#define QUERY_H + +#include "main.h" +#include "ui/app.h" + +void query_loop(app_t *); +void wait_register(query_args_t *q_args); +int process_query(query_args_t *query_args, file_args_t *file_args); +int query_extract_from_buf(char *buf, int *buf_used, char **output_line); +void init_query_args(query_args_t *q_args, params_t *params); + +#endif \ No newline at end of file diff --git a/src/server.c b/src/server.c index aa30c80..0c0622d 100644 --- a/src/server.c +++ b/src/server.c @@ -1,5 +1,5 @@ #include "main.h" -#include "ui.h" +#include "ui/widget/file_list.h" #include #include #include @@ -15,12 +15,23 @@ void ask_uname_and_password(params_t *params); void ask_register(params_t *params, char *email); +#define PRINT_SRV_MESSAGE(q_args, l_len, line) \ + if (q_args->server_message.size > 0) { \ + q_args->state = S_PRINT_SERVER_MESSAGE; \ + q_args->next_server_command = malloc(l_len + 1); \ + strcpy(q_args->next_server_command, line); \ + return 0; \ + } + int process_server_command(char *line, int l_len, query_args_t *q_args) { int ws_pos = l_len; params_t *params = q_args->params; const char *login_options[] = {"Username", "Anonymous", "Register", NULL}; - char r_buf[INBUFSIZE]; uint32_t answer; + uint64_t new_capacity; + ui_file_list_t *fui = (ui_file_list_t *)q_args->file_list_ui; + char query[INBUFSIZE]; + int32_t q_len = 0; char *cptr = strchr(line, ' '); if (cptr != NULL && cptr > line) @@ -28,27 +39,14 @@ int process_server_command(char *line, int l_len, query_args_t *q_args) { /* LOGIN */ if (!strncmp(line, "login>", ws_pos)) { - if (params->uname == NULL) { - fflush(stdout); - answer = print_ask_list("Select login type:", login_options); - switch (answer) { - case 1: - ask_uname_and_password(q_args->params); - break; - case 2: - params->uname = malloc(sizeof("anonymous")); - strcpy(params->uname, "anonymous"); - q_args->state = WAIT_CLIENT; - break; - case 3: - wait_register(q_args); - return 3; - default: - return -1; - } + if (q_args->server_message.size > 0) { + PRINT_SRV_MESSAGE(q_args, l_len, line); + } + if (q_args->params->uname != NULL && q_args->params->pass != NULL) { + write(params->sd, q_args->params->uname, strlen(q_args->params->uname)); + } else { + q_args->state = S_ASK_LOGIN_TYPE; } - write(q_args->sd, params->uname, strlen(params->uname)); - return 0; } @@ -67,7 +65,7 @@ int process_server_command(char *line, int l_len, query_args_t *q_args) { free(params->pass); params->uname = NULL; params->pass = NULL; - get_missing_params(params); + // get_missing_params(params); write(q_args->sd, params->uname, strlen(params->uname)); return 0; } @@ -75,9 +73,10 @@ int process_server_command(char *line, int l_len, query_args_t *q_args) { /* REGISTER CONFIRMATION */ if (q_args->state == WAIT_REGISTER_CONFIRMATION) { if (!strcmp(line, "ok")) { - printf("Welcome, %s\n", q_args->params->uname); - print_prompt(q_args->params); - q_args->state = WAIT_CLIENT; + sprintf(query, "file list %u %u\n%n", fui->max_lines, fui->current_page, + &q_len); + write(q_args->sd, query, q_len); + q_args->state = S_FILE_LIST; return 0; } else { write(STDOUT_FILENO, line, strlen(line)); @@ -93,20 +92,38 @@ int process_server_command(char *line, int l_len, query_args_t *q_args) { /* WELCOME MES */ if (!strncmp(line, "Welcome, ", ws_pos)) { - printf("%s\n", line); - print_prompt(q_args->params); - q_args->state = WAIT_CLIENT; + sprintf(query, "file list %u %u\n%n", fui->max_lines, fui->current_page, + &q_len); + write(q_args->sd, query, q_len); + q_args->state = S_FILE_LIST; return 0; } - if (q_args->state == WAIT_SERVER_INIT) { - write(STDOUT_FILENO, line, l_len); - } else { - char buf[INBUFSIZE + sizeof("server: ")] = "server: "; - strcat(buf, line); - write(STDOUT_FILENO, buf, l_len + sizeof("server: ")); - if (q_args->state == WAIT_CLIENT) { - print_prompt(q_args->params); + if (q_args->state == S_WAIT_SERVER) { + if (q_args->server_message.text == NULL) { + q_args->server_message.text = malloc(l_len + 1); + memcpy(q_args->server_message.text, line, l_len); + q_args->server_message.text[l_len] = 0; + q_args->server_message.size = l_len + 1; + q_args->server_message.capacity = l_len + 1; + } else { + if (q_args->server_message.size + l_len > + q_args->server_message.capacity) { + new_capacity = (l_len + q_args->server_message.capacity) * 2; + q_args->server_message.text = + realloc(q_args->server_message.text, new_capacity); + q_args->server_message.capacity = new_capacity; + } + q_args->server_message.size += l_len; + strncat(q_args->server_message.text, line, l_len); + q_args->server_message.text[q_args->server_message.size + l_len] = 0; + } + char *end_char = strchr(q_args->server_message.text, + '\04'); /* Search for the end of the notification */ + if (end_char != NULL) { + *end_char = '\0'; /* Don't show this symbol */ + q_args->state = S_PRINT_SERVER_MESSAGE; + q_args->server_message.size--; } } diff --git a/src/server.h b/src/server.h new file mode 100644 index 0000000..6d6f1f1 --- /dev/null +++ b/src/server.h @@ -0,0 +1,8 @@ +#ifndef SERVER_H +#define SERVER_H + +#include "main.h" +int process_server_command(char *line, int l_len, query_args_t *q_args); +void ask_register(params_t *params, char *email); + +#endif \ No newline at end of file diff --git a/src/ui.c b/src/ui.c deleted file mode 100644 index 836650d..0000000 --- a/src/ui.c +++ /dev/null @@ -1,26 +0,0 @@ -#include -#include -#include - -uint32_t print_ask_list(char title[], const char *options[]) { - char *option; - char c; - uint32_t i, answer; - - printf("%s\n", title); - - for (i = 0; options[i] != NULL; i++) { - printf("[%u] %s\n", i + 1, options[i]); - } - - write(STDOUT_FILENO, "> ", sizeof "> "); - - while (!scanf("%u", &answer) || answer > i) { - printf("Input format is not correct! You need to specify between 1 and " - "%u\n > ", - i); - } - while ((c = getchar()) != '\n' && c != EOF); - - return answer; -} \ No newline at end of file diff --git a/src/ui.h b/src/ui.h deleted file mode 100644 index 9cc68e4..0000000 --- a/src/ui.h +++ /dev/null @@ -1,3 +0,0 @@ -#include - -uint32_t print_ask_list(char *title, const char *options[]); \ No newline at end of file diff --git a/src/ui/app.c b/src/ui/app.c new file mode 100644 index 0000000..993234c --- /dev/null +++ b/src/ui/app.c @@ -0,0 +1,203 @@ +#include "app.h" +#include "modals/ask_server_addr.h" +#include "modals/download_pr.h" +#include "modals/login_credentials.h" +#include "modals/login_option.h" +#include "modals/notification.h" +#include "modals/server_message.h" +#include "modals/upload_dialogue.h" +#include "modals/upload_props_dialogue.h" +#include "widget/dialogue.h" +#include +#include +#include +#include +#include +#include +#include +#include + +void ac_file(WINDOW *win, int is_action_w); + +app_t *init_app() { + /* get values from terminal size */ + int32_t y_max, x_max; + getmaxyx(stdscr, y_max, x_max); + + /* allocate app struct memory */ + app_t *_app = calloc(1, sizeof(app_t)); + + /* associate values for screen size */ + _app->coordinates.max_x = _app->coordinates.cur_x = x_max; + _app->coordinates.max_y = _app->coordinates.cur_y = y_max; + + _app->win = newwin(_app->coordinates.cur_y, _app->coordinates.cur_x, 0, 0); + + /* define the width for each sub window */ + int32_t left_w_x = x_max / 10 * 4; + int32_t right_w_x = x_max - left_w_x - 2; + + /* create the menu window */ + _app->left_win = newwin(y_max - 4, left_w_x, 2, 1); + + /* create the action window */ + _app->right_win = newwin(y_max - 4, right_w_x, 2, left_w_x + 1); + + /* dialogue */ + _app->modal.win = NULL; + _app->modal.is_initiated = 0; + + /* default win */ + _app->active_win_type = aw_left; + + /* print decorative bars */ + print_bars(_app); + + /* here goes box borders */ + draw_borders(_app); + + keypad(_app->win, TRUE); + + /* refresh the windows */ + wnoutrefresh(_app->win); + wnoutrefresh(_app->left_win); + wnoutrefresh(_app->right_win); + doupdate(); + + return _app; +} + +void init_nc() { + initscr(); + cbreak(); + keypad(stdscr, TRUE); + noecho(); + curs_set(false); + set_escdelay(50); + + if (!has_colors()) { + printf("Your terminal does not support color\n"); + } else { + start_color(); + init_pair(1, COLOR_RED, COLOR_BLACK); + init_pair(2, COLOR_GREEN, COLOR_BLACK); + init_pair(modal_color_pair, COLOR_BLUE, COLOR_WHITE); + } +} + +void draw_borders(app_t *app) { + if (app->modal.win != NULL && !app->modal.needs_update) { + return; + } + clear(); + box(app->win, 0, 0); + box(app->left_win, 0, 0); + box(app->right_win, 0, 0); +} + +void print_bars(app_t *app) { + char top_text[64] = "Hello!"; + + wattrset(app->win, A_REVERSE); + + /* print top and bottom bars */ + for (int i = 1; i < app->coordinates.cur_x - 1; i++) { + mvwprintw(app->win, 1, i, " "); + mvwprintw(app->win, app->coordinates.cur_y - 2, i, " "); + } + + /* add content to the top bar */ + if (app->params && app->params->is_connected) { + uint32_t ip_address = app->params->addr; + u_char ip_addr[4]; + mempcpy(ip_addr, &ip_address, 4); + sprintf(top_text, "Connected to %u.%u.%u.%u, user: %s", ip_addr[0], + ip_addr[1], ip_addr[2], ip_addr[3], app->params->uname); + } + mvwprintw(app->win, 1, 2, "%s", top_text); + + /* add content to the bottom bar */ + mvwprintw(app->win, app->coordinates.cur_y - 2, 2, + "F1 - Help | U - upload | F9 - Quit"); + + wattroff(app->win, A_REVERSE); +} + +void app_refresh(app_t *app) { + draw_borders(app); + + wnoutrefresh(app->win); + wnoutrefresh(app->left_win); + wnoutrefresh(app->right_win); + if (app->modal.win != NULL) { + if (app->modal.is_initiated != 0) { + wnoutrefresh(app->modal.win); + } + } + doupdate(); +} + +void app_draw_modal(app_t *app) { + if (app->modal.needs_destroy || + (app->query_args->state == S_WAIT_SERVER && app->modal.is_initiated)) { + destroy_dialogue(&(app->modal), app); + } + if (!app->modal.is_initiated) { + switch (app->query_args->state) { + case S_ASK_SEVER_IP: + init_asa_modal(app); + break; + case S_ASK_LOGIN_TYPE: + init_login_option_modal(app); + break; + case S_ASK_LOGIN_USER: + init_login_credentials_modal(app); + break; + case S_PRINT_SERVER_MESSAGE: + init_server_message_modal(app); + break; + case S_FILE_DOWNLOAD: + init_dwn_pr_modal(app, false); + break; + case S_UPLOAD_FILE: + init_dwn_pr_modal(app, true); + break; + case S_UPLOAD_FILE_SELECT: + init_upload_dialogue_modal(app); + break; + case S_UPLOAD_PARAMS: + init_upload_props_dialogue_modal(app); + break; + case WAIT_SERVER_INIT: + case WAIT_SERVER: + case WAIT_REGISTER: + case WAIT_REGISTER_CONFIRMATION: + case WAIT_CLIENT: + case S_FILE_LIST: + case S_FILE_SELECT: + case S_UPLOAD_REQUESTED: + case S_ASK_USER_BEFORE_LOGIN: + case S_ERR: + case S_WAIT_SERVER: + case S_N_D: + case S_NEXT_ACTION: + if (app->query_args->notification != NULL) { + init_notification_modal(app); + } else { + return; + } + } + app->active_callback = app->modal.w.callback; + app->active_win = app->modal.win; + app->active_win_type = aw_modal; + app->active_widget = &(app->modal); + } + draw_dialogue(&(app->modal)); +} + +void destroy_app(app_t *app, int32_t exit_code) { + delwin(app->win); + endwin(); + app->win = NULL; + exit(exit_code); +} diff --git a/src/ui/app.h b/src/ui/app.h new file mode 100644 index 0000000..c810972 --- /dev/null +++ b/src/ui/app.h @@ -0,0 +1,44 @@ +#ifndef N_APP_H +#define N_APP_H + +#include "../common.h" +#include "widget.h" +#include "widget/dialogue.h" +#include + +typedef enum { + modal_color_pair = 3, + modal_border_color_pair = 4, +} color; + +typedef enum { + aw_left, + aw_right, + aw_modal +} active_win_t; + +typedef struct { + WINDOW *win; + WINDOW *left_win; + WINDOW *right_win; + dialogue_t modal; + params_t *params; + coordinates_t coordinates; + query_args_t *query_args; + file_args_t *file_args; + int32_t read_fd; + active_win_t active_win_type; + void (*active_callback)(callback_args_t *args); + WINDOW *active_win; + void *active_widget; +} app_t; + +app_t *init_app(); +void init_nc(); +void destroy_app(app_t *app, int32_t exit_code); +void draw_borders(app_t *app); +void print_bars(app_t *app); +void app_refresh(app_t *app); +void app_draw_modal(app_t *app); + +#endif // N_APP_H \ No newline at end of file diff --git a/src/ui/modals/ask_server_addr.c b/src/ui/modals/ask_server_addr.c new file mode 100644 index 0000000..75347da --- /dev/null +++ b/src/ui/modals/ask_server_addr.c @@ -0,0 +1,66 @@ +#include +#include +#include + +#include "../../connection.h" +#include "../app.h" +#include "../widget/dialogue.h" +#include "../widget/group.h" +#include "../widget/input.h" + +typedef struct { + button_t *element; + enum w_type type; +} group_bt_t; + +void asa_modal_dialogue_callback(callback_args_t *args) { + int32_t response; + callback_args_t d_args; + app_t *app = args->app; + dialogue_t *d = (dialogue_t *)app->active_widget; + memcpy(&d_args, args, sizeof(callback_args_t)); + d_args.app = NULL; + d_args.resp_data = &response; + d_args.element = app->active_widget; + input_t *in_ip = d->g_content->elements[0].element; + input_t *in_port = d->g_content->elements[1].element; + dialogue_default_callback(&d_args); + if (response > -1) { + switch (response) { + case 0: + get_ip_port(app->params, in_ip->value, in_port->value); + connect_to_server(app); + d->needs_destroy = true; + app->query_args->state = S_WAIT_SERVER; + break; + case 1: + destroy_app(app, 0); + } + } +} + +dialogue_t *init_asa_modal(app_t *app) { + if (app == NULL) + return NULL; + group_el_init_t content[] = { + {.type = w_input, .label = "Address", .length = 15}, + {.type = w_input, .label = "Port", .length = 5}, + {.type = w_end}}; + + group_el_init_t actions[] = { + {.type = w_button, .label = "Connect", .is_default = 1}, + {.type = w_button, .label = "Cancel", .is_default = 0}, + {.type = w_end}}; + + init_dialogue(&(app->modal), "Connect to server", + "There is information needed", &(app->coordinates)); + dialogue_t *d = &(app->modal); + + d->w.callback = asa_modal_dialogue_callback; + d->g_content = init_group(&(d->win), &(d->w), content, horizontal); + d->g_action = init_group(&(d->win), &(d->w), actions, horizontal); + + dialogue_init_active_id(d); + + return NULL; +} \ No newline at end of file diff --git a/src/ui/modals/ask_server_addr.h b/src/ui/modals/ask_server_addr.h new file mode 100644 index 0000000..7b741a8 --- /dev/null +++ b/src/ui/modals/ask_server_addr.h @@ -0,0 +1,9 @@ +#ifndef ASK_SERVER_ADDR_H +#define ASK_SERVER_ADDR_H + +#include "../app.h" +#include "../widget/dialogue.h" + +dialogue_t *init_asa_modal(app_t *app); + +#endif \ No newline at end of file diff --git a/src/ui/modals/download_pr.c b/src/ui/modals/download_pr.c new file mode 100644 index 0000000..bfc3606 --- /dev/null +++ b/src/ui/modals/download_pr.c @@ -0,0 +1,38 @@ +#include "../app.h" +#include "../widget/dialogue.h" +#include "../widget/group.h" +#include +#include +#include + +void dwn_pr_modal_cb(callback_args_t *args) {} + +dialogue_t *init_dwn_pr_modal(app_t *app, bool is_upload) { + if (app == NULL) + return NULL; + group_el_init_t content[] = { + {.type = w_progress, .label = "", .length = 30}, /* length is ignored */ + {.type = w_end}}; + + char text[256]; + sprintf(text, "%s file: %s", is_upload ? "Uploading" : "Downloading", + is_upload ? app->query_args->file->name : app->file_args->f_selected.name); + group_el_init_t actions[] = { + {.type = w_button, .label = "Cancel", .is_default = 1}, {.type = w_end}}; + + app->modal.w.parent_win = &app->win; + init_dialogue(&(app->modal), is_upload ? "Upload" : "Download", text, + &(app->coordinates)); + dialogue_t *d = &(app->modal); + + d->w.callback = dwn_pr_modal_cb; + d->g_content = init_group(&(d->win), &(d->w), content, horizontal); + d->g_action = init_group(&(d->win), &(d->w), actions, horizontal); + + app->query_args->progress_bar = d->g_content->elements[0].element; + app->query_args->active_dialogue = d; + + dialogue_init_active_id(d); + + return NULL; +} \ No newline at end of file diff --git a/src/ui/modals/download_pr.h b/src/ui/modals/download_pr.h new file mode 100644 index 0000000..b5274e7 --- /dev/null +++ b/src/ui/modals/download_pr.h @@ -0,0 +1,9 @@ +#ifndef DOWNLOAD_PR_H +#define DOWNLOAD_PR_H + +#include "../widget/dialogue.h" +#include "../app.h" + +dialogue_t *init_dwn_pr_modal(app_t *app, bool is_upload); + +#endif \ No newline at end of file diff --git a/src/ui/modals/login_credentials.c b/src/ui/modals/login_credentials.c new file mode 100644 index 0000000..4104591 --- /dev/null +++ b/src/ui/modals/login_credentials.c @@ -0,0 +1,76 @@ +#include +#include +#include +#include +#include + +#include "../app.h" +#include "../widget/dialogue.h" +#include "../widget/group.h" +#include "../widget/input.h" + +typedef struct { + button_t *element; + enum w_type type; +} group_bt_t; + +void init_login_credentials_modal_cb(callback_args_t *args) { + int32_t response; + callback_args_t d_args; + app_t *app = args->app; + dialogue_t *d = (dialogue_t *)app->active_widget; + memcpy(&d_args, args, sizeof(callback_args_t)); + d_args.app = NULL; + d_args.resp_data = &response; + d_args.element = app->active_widget; + input_t *in_name = d->g_content->elements[0].element; + input_t *in_pass = d->g_content->elements[1].element; + dialogue_default_callback(&d_args); + if (response > -1) { + switch (response) { + case 0: + app->params->uname = malloc(in_name->value_len + 1); + app->params->pass = malloc(in_pass->value_len + 1); + strncpy(app->params->uname, in_name->value, in_name->value_len); + app->params->uname[in_name->value_len] = 0; + strncpy(app->params->pass, in_pass->value, in_pass->value_len); + app->params->pass[in_pass->value_len] = 0; + write(app->params->sd, app->params->uname, in_name->value_len); + app->query_args->state = S_WAIT_SERVER; + d->needs_destroy = true; + print_bars(app); + break; + case 1: + destroy_app(app, 0); + } + } +} + +dialogue_t *init_login_credentials_modal(app_t *app) { + if (app == NULL) + return NULL; + group_el_init_t content[] = { + {.type = w_input, .label = "Username", .length = 15}, + {.type = w_input, + .label = "Password", + .length = 15, + .is_hidden_value = true}, + {.type = w_end}}; + + group_el_init_t actions[] = { + {.type = w_button, .label = "Login", .is_default = true}, + {.type = w_button, .label = "Cancel", .is_default = false}, + {.type = w_end}}; + + init_dialogue(&(app->modal), "Credentials", "There is information needed", + &(app->coordinates)); + dialogue_t *d = &(app->modal); + + d->w.callback = init_login_credentials_modal_cb; + d->g_content = init_group(&(d->win), &(d->w), content, horizontal); + d->g_action = init_group(&(d->win), &(d->w), actions, horizontal); + + dialogue_init_active_id(d); + + return NULL; +} \ No newline at end of file diff --git a/src/ui/modals/login_credentials.h b/src/ui/modals/login_credentials.h new file mode 100644 index 0000000..c4c06de --- /dev/null +++ b/src/ui/modals/login_credentials.h @@ -0,0 +1,9 @@ +#ifndef LOGIN_CREDENTIALS_MODAL_H +#define LOGIN_CREDENTIALS_MODAL_H + +#include "../app.h" +#include "../widget/dialogue.h" + +dialogue_t *init_login_credentials_modal(app_t *app); + +#endif \ No newline at end of file diff --git a/src/ui/modals/login_option.c b/src/ui/modals/login_option.c new file mode 100644 index 0000000..fab7cef --- /dev/null +++ b/src/ui/modals/login_option.c @@ -0,0 +1,55 @@ +#include +#include +#include + +#include "../app.h" +#include "../widget/dialogue.h" +#include "../widget/group.h" + +typedef struct { + button_t *element; + enum w_type type; +} group_bt_t; + +void login_modal_option_cb(callback_args_t *args) { + int32_t response; + callback_args_t d_args; + app_t *app = args->app; + dialogue_t *d = (dialogue_t *)app->active_widget; + memcpy(&d_args, args, sizeof(callback_args_t)); + d_args.app = NULL; + d_args.resp_data = &response; + d_args.element = app->active_widget; + dialogue_default_callback(&d_args); + if (response > -1) { + switch (response) { + case 0: + break; + case 1: + d->needs_destroy = true; + app->query_args->state = S_ASK_LOGIN_USER; + break; + } + } +} + +dialogue_t *init_login_option_modal(app_t *app) { + if (app == NULL) + return NULL; + group_el_init_t children[] = {{.type = w_button, .label = "Anonymous"}, + {.type = w_button, .label = "User"}, + {.type = w_button, .label = "Register"}, + {.type = w_end}}; + + init_dialogue(&(app->modal), "Login", + "Select your login " + "option", + &(app->coordinates)); + app->modal.g_action = + init_group(&(app->modal.win), &(app->modal.w), children, + horizontal); + app->modal.w.callback = login_modal_option_cb; + dialogue_init_active_id(&(app->modal)); + + return NULL; +} \ No newline at end of file diff --git a/src/ui/modals/login_option.h b/src/ui/modals/login_option.h new file mode 100644 index 0000000..14414a4 --- /dev/null +++ b/src/ui/modals/login_option.h @@ -0,0 +1,9 @@ +#ifndef LOGIN_OPTION_MODAL_H +#define LOGIN_OPTION_MODAL_H + +#include "../widget/dialogue.h" +#include "../app.h" + +dialogue_t *init_login_option_modal(app_t *app); + +#endif \ No newline at end of file diff --git a/src/ui/modals/notification.c b/src/ui/modals/notification.c new file mode 100644 index 0000000..151a9ee --- /dev/null +++ b/src/ui/modals/notification.c @@ -0,0 +1,51 @@ +#include +#include +#include +#include + +#include "../app.h" +#include "../widget/dialogue.h" +#include "../widget/group.h" + +typedef struct { + button_t *element; + enum w_type type; +} group_bt_t; + +void init_notification_modal_cb(callback_args_t *args) { + int32_t response; + callback_args_t d_args; + app_t *app = args->app; + dialogue_t *d = (dialogue_t *)app->active_widget; + memcpy(&d_args, args, sizeof(callback_args_t)); + d_args.app = NULL; + d_args.resp_data = &response; + d_args.element = app->active_widget; + dialogue_default_callback(&d_args); + + app->query_args->state = WAIT_CLIENT; + d->needs_destroy = true; +} + +dialogue_t *init_notification_modal(app_t *app) { + if (app == NULL) + return NULL; + + group_el_init_t actions[] = { + {.type = w_button, .label = "OK", .is_default = 1}, {.type = w_end}}; + + init_dialogue(&(app->modal), "Notification", app->query_args->notification, + &(app->coordinates)); + free(app->query_args->notification); + app->query_args->notification = NULL; + dialogue_t *d = &(app->modal); + app->query_args->active_dialogue = d; + + d->w.callback = init_notification_modal_cb; + d->g_content = NULL; + d->g_action = init_group(&(d->win), &(d->w), actions, horizontal); + + dialogue_init_active_id(d); + + return NULL; +} \ No newline at end of file diff --git a/src/ui/modals/notification.h b/src/ui/modals/notification.h new file mode 100644 index 0000000..7f527fa --- /dev/null +++ b/src/ui/modals/notification.h @@ -0,0 +1,9 @@ +#ifndef NOTIFICATION_H +#define NOTIFICATION_H + +#include "../app.h" +#include "../widget/dialogue.h" + +dialogue_t *init_notification_modal(app_t *app); + +#endif \ No newline at end of file diff --git a/src/ui/modals/server_message.c b/src/ui/modals/server_message.c new file mode 100644 index 0000000..c9bcea8 --- /dev/null +++ b/src/ui/modals/server_message.c @@ -0,0 +1,54 @@ +#include +#include +#include +#include + +#include "../app.h" +#include "../widget/dialogue.h" +#include "../widget/group.h" + +typedef struct { + button_t *element; + enum w_type type; +} group_bt_t; + +void server_message_modal_cb(callback_args_t *args) { + int32_t response; + callback_args_t d_args; + app_t *app = args->app; + dialogue_t *d = (dialogue_t *)app->active_widget; + memcpy(&d_args, args, sizeof(callback_args_t)); + d_args.app = NULL; + d_args.resp_data = &response; + d_args.element = app->active_widget; + dialogue_default_callback(&d_args); + if (response > -1) { + switch (response) { + case 0: + d->needs_destroy = true; + /* TODO: macros */ + free(app->query_args->server_message.text); + app->query_args->server_message.text = NULL; + app->query_args->server_message.capacity = 0; + app->query_args->server_message.size = 0; + app->query_args->state = S_NEXT_ACTION; + break; + } + } +} + +void *init_server_message_modal(app_t *app) { + if (app == NULL) + return NULL; + group_el_init_t children[] = { + {.type = w_button, .label = "OK", .is_default = 1}, {.type = w_end}}; + + init_dialogue(&(app->modal), "Server response", + app->query_args->server_message.text, &(app->coordinates)); + app->modal.g_action = + init_group(&(app->modal.win), &(app->modal.w), children, horizontal); + app->modal.w.callback = server_message_modal_cb; + dialogue_init_active_id(&(app->modal)); + + return NULL; +} \ No newline at end of file diff --git a/src/ui/modals/server_message.h b/src/ui/modals/server_message.h new file mode 100644 index 0000000..e4abf49 --- /dev/null +++ b/src/ui/modals/server_message.h @@ -0,0 +1,9 @@ +#ifndef SERVER_MESSAGE_H +#define SERVER_MESSAGE_H + +#include "../widget/dialogue.h" +#include "../app.h" + +void *init_server_message_modal(app_t *app); + +#endif \ No newline at end of file diff --git a/src/ui/modals/upload_dialogue.c b/src/ui/modals/upload_dialogue.c new file mode 100644 index 0000000..3f4e0e8 --- /dev/null +++ b/src/ui/modals/upload_dialogue.c @@ -0,0 +1,63 @@ +#include "../../file_processor.h" +#include "../app.h" +#include "../widget/dialogue.h" +#include "../widget/fs_file_list.h" +#include "../widget/group.h" +#include +#include +#include + +void upload_dialogue_modal_cb(callback_args_t *args) { + int32_t response = -1; + callback_args_t d_args; + app_t *app = args->app; + dialogue_t *d = (dialogue_t *)app->active_widget; + memcpy(&d_args, args, sizeof(callback_args_t)); + d_args.app = NULL; + d_args.resp_data = &response; + d_args.element = app->active_widget; + dialogue_default_callback(&d_args); + ui_fs_file_list_t *fui = + (ui_fs_file_list_t *)d->g_content->elements[0].element; + switch (response) { + case 0: + app->query_args->state = S_WAIT_SERVER; + d->needs_destroy = true; + break; + case 1: + file_upload_open(fui->d_path, fui->current->name, app->query_args); + d->needs_destroy = true; + app->query_args->state = S_UPLOAD_PARAMS; + break; + case -2: + d->needs_destroy = true; + app->query_args->state = WAIT_CLIENT; + } +} + +dialogue_t *init_upload_dialogue_modal(app_t *app) { + if (app == NULL) + return NULL; + group_el_init_t content[] = { + // {.type = w_input, .label = "File description", .length = 30}, + {.type = w_fs_file_list, .label = "File", .length = 30}, + {.type = w_end}}; + + group_el_init_t actions[] = { + {.type = w_button, .label = "Cancel", .is_default = 0}, {.type = w_end}}; + + app->modal.w.parent_win = &app->win; + init_dialogue(&(app->modal), "Upload new file", "Enter essential data", + &(app->coordinates)); + dialogue_t *d = &(app->modal); + + d->w.callback = upload_dialogue_modal_cb; + d->g_content = init_group(&(d->win), &(d->w), content, horizontal); + d->g_action = init_group(&(d->win), &(d->w), actions, horizontal); + + app->query_args->active_dialogue = d; + + dialogue_init_active_id(d); + + return NULL; +} \ No newline at end of file diff --git a/src/ui/modals/upload_dialogue.h b/src/ui/modals/upload_dialogue.h new file mode 100644 index 0000000..27bbac6 --- /dev/null +++ b/src/ui/modals/upload_dialogue.h @@ -0,0 +1,9 @@ +#ifndef UPLOAD_DIALOGUE_H +#define UPLOAD_DIALOGUE_H + +#include "../widget/dialogue.h" +#include "../app.h" + +dialogue_t *init_upload_dialogue_modal(app_t *app); + +#endif \ No newline at end of file diff --git a/src/ui/modals/upload_props_dialogue.c b/src/ui/modals/upload_props_dialogue.c new file mode 100644 index 0000000..1251193 --- /dev/null +++ b/src/ui/modals/upload_props_dialogue.c @@ -0,0 +1,78 @@ +#include "../app.h" +#include "../widget/dialogue.h" +#include "../widget/group.h" +#include "../widget/input.h" +#include +#include +#include +#include +#include +#include + +void upload_props_dialogue_modal_cb(callback_args_t *args) { + int32_t response = -1; + callback_args_t d_args; + app_t *app = args->app; + dialogue_t *d = (dialogue_t *)app->active_widget; + memcpy(&d_args, args, sizeof(callback_args_t)); + d_args.app = NULL; + d_args.resp_data = &response; + d_args.element = app->active_widget; + dialogue_default_callback(&d_args); + char query[256]; + int32_t query_len = 0; + input_t *desc_input = (input_t *)d->g_content->elements[0].element; + if (response > -1) { + switch (response) { + case 0: + query_len = + sprintf(query, "file upload \"%s\" %zu 1\n", + app->query_args->file->name, app->query_args->file->size); + app->query_args->file->description = + malloc(desc_input->value_len + 9); /* reserve space for \n:END:\n */ + strcpy(app->query_args->file->description, desc_input->value); + d->needs_destroy = true; + app->query_args->state = S_UPLOAD_REQUESTED; + write(app->query_args->sd, query, query_len); + break; + case 1: + if (app->query_args->file->name != NULL) + free(app->query_args->file->name); + if (app->query_args->file->path != NULL) + free(app->query_args->file->path); + free(app->query_args->file); + app->query_args->file = NULL; + app->query_args->state = S_WAIT_SERVER; + break; + } + } +} + +dialogue_t *init_upload_props_dialogue_modal(app_t *app) { + if (app == NULL) + return NULL; + group_el_init_t content[] = { + {.type = w_input, .label = "File description", .length = 30}, + {.type = w_end}}; + + group_el_init_t actions[] = { + {.type = w_button, .label = "Upload", .is_default = 1}, + {.type = w_button, .label = "Cancel", .is_default = 0}, + {.type = w_end}, + }; + + app->modal.w.parent_win = &app->win; + init_dialogue(&(app->modal), "Description for the file", + "Enter essential data", &(app->coordinates)); + dialogue_t *d = &(app->modal); + + d->w.callback = upload_props_dialogue_modal_cb; + d->g_content = init_group(&(d->win), &(d->w), content, horizontal); + d->g_action = init_group(&(d->win), &(d->w), actions, horizontal); + + app->query_args->active_dialogue = d; + + dialogue_init_active_id(d); + + return NULL; +} \ No newline at end of file diff --git a/src/ui/modals/upload_props_dialogue.h b/src/ui/modals/upload_props_dialogue.h new file mode 100644 index 0000000..4412f2f --- /dev/null +++ b/src/ui/modals/upload_props_dialogue.h @@ -0,0 +1,9 @@ +#ifndef UPLOAD_PROPS_DIALOGUE_H +#define UPLOAD_PROPS_DIALOGUE_H + +#include "../widget/dialogue.h" +#include "../app.h" + +dialogue_t *init_upload_props_dialogue_modal(app_t *app); + +#endif \ No newline at end of file diff --git a/src/ui/widget.c b/src/ui/widget.c new file mode 100644 index 0000000..d1b6f8f --- /dev/null +++ b/src/ui/widget.c @@ -0,0 +1,127 @@ +#include "widget.h" +#include "../main.h" +#include +#include +#include +#include + +extern uint32_t m_id; +int32_t rsize_to_value(int32_t size, enum rsize rsize); + +void init_widget(widget_t *w, widget_t *w_parent, WINDOW **win, char *title) { + w->id = m_id++; + w->x = 0; + w->y = 0; + w->m_x = 0; + w->m_y = 0; + w->parent_win = win; + w->w_parent = w_parent; + w->callback = NULL; + strcpy(w->title, title); +} + +int32_t get_max_line_len(const char *text, uint32_t *line_count) { + uint32_t nl_pos = 0; // new line position + uint32_t c_start = 0; // current line start position + uint32_t c_len = 0; // current line length + uint32_t m_len = 0; // max length + uint32_t line_num = 1; + for (int i = 0; text[i] != '\0'; i++, c_len++) { + if (text[i] == '\n') { + if (i - nl_pos > c_len || !m_len) { + m_len = c_len; + } + nl_pos = i; + c_start = i + 1; + c_len = 0; + line_num++; + } + } + if (line_count != NULL) + *line_count = line_num; + if (c_len > m_len) { + return c_len; + } else { + return m_len; + } +} + +#define PRINT_TEXT(win, l_buf, text, i, c_line_len, win_width, line_v_pos, \ + attrs) \ + strncpy(l_buf, text + i - c_line_len, c_line_len); \ + l_buf[c_line_len] = '\0'; \ + if ((attrs & PMT_ALIGN_CENTER) == PMT_ALIGN_CENTER) { \ + mvwprintw(win, line_v_pos, (win_width - c_line_len) / 2, "%s", l_buf); \ + } else if (attrs & PMT_POS_CENTER) { \ + mvwprintw(win, line_v_pos, (win_width - m_line_len) / 2, "%s", l_buf); \ + } else { \ + mvwprintw(win, line_v_pos, x, "%s", l_buf); \ + } + +uint32_t print_multiline_text(WINDOW *win, const char *_text, + const uint32_t win_width, const uint32_t y, + const uint32_t x, const uint16_t attrs) { + + uint16_t line_v_pos = y; + int32_t c_line_len = 0; + uint32_t m_line_len = 0; + uint32_t i = 0; + char l_buf[DIALOGUE_TEXT]; + char *text = malloc(strlen(_text) + 1); + strcpy(text, _text); + + uint l_size = 0; + for (uint m = 0; text[m] != '\0'; m++) { + if (text[m] != '\n') { + if (l_size >= win_width) { + uint j = m; + while (text[j] != ' ') { + j--; + } + text[j] = '\n'; + l_size = 0; + } else { + l_size++; + } + } else { + l_size = 0; + } + } + + if (attrs & PMT_POS_CENTER) { + m_line_len = get_max_line_len(text, NULL); + } + + for (; text[i]; i++, c_line_len++) { + if (text[i] == '\n') { + PRINT_TEXT(win, l_buf, text, i, c_line_len, win_width, line_v_pos, attrs) + c_line_len = -1; + line_v_pos++; + } + } + + PRINT_TEXT(win, l_buf, text, i, c_line_len, win_width, line_v_pos, attrs) + + free(text); + + return 1; +} + +int32_t rsize_to_value(int32_t size, enum rsize rsize) { + switch (rsize) { + case s_auto: + return size; + case s_1: + return size; + case s_1_2: + return size / 2; + case s_1_3: + return size / 3; + case s_2_3: + return size / 3 * 2; + case s_1_4: + return size / 4; + case s_3_4: + return size / 4 * 3; + } +} \ No newline at end of file diff --git a/src/ui/widget.h b/src/ui/widget.h new file mode 100644 index 0000000..6db125a --- /dev/null +++ b/src/ui/widget.h @@ -0,0 +1,65 @@ +#ifndef WIDGET_H +#define WIDGET_H + +#include "../main.h" +#include +#include + +enum w_type { + w_end, + w_button, + w_box, + w_group, + w_input, + w_progress, + w_fs_file_list, +}; + +typedef struct { + void *app; + void *element; + void *data; + void *resp_data; + uint32_t active_id; +} callback_args_t; + +enum rsize { + s_auto, + s_1, + s_1_2, + s_1_3, + s_2_3, + s_1_4, + s_3_4, +}; + +typedef struct widget_t { + uint32_t id; + char title[DIALOGUE_TITLE]; + uint32_t x; + uint32_t y; + uint32_t m_x; + uint32_t m_y; + struct { + uint32_t y; + uint32_t x; + } cur; + WINDOW *const *parent_win; + struct widget_t *w_parent; + // enum rsize rsize; + void (*callback)(callback_args_t *args); +} widget_t; + +typedef void (*callback_t)(callback_args_t *args); + +enum pmt_attrs { + PMT_POS_CENTER = 01, + PMT_ALIGN_CENTER = 03, +}; + +void init_widget(widget_t *w, widget_t *w_parent, WINDOW **win, char *title); +int32_t get_max_line_len(const char *text, uint32_t *line_count); +uint32_t print_multiline_text(WINDOW *win, const char *text, const uint32_t win_width, const uint32_t y, + const uint32_t x, const uint16_t attrs); + +#endif \ No newline at end of file diff --git a/src/ui/widget/button.c b/src/ui/widget/button.c new file mode 100644 index 0000000..a3e8ebd --- /dev/null +++ b/src/ui/widget/button.c @@ -0,0 +1,43 @@ +#include +#include +#include +#include + +#include "../app.h" +#include "../widget.h" +#include "button.h" + +button_t *init_button(WINDOW **win, widget_t *w_parent, char *label) { + button_t *btn = malloc(sizeof(button_t)); + init_widget(&(btn->w), w_parent, win, label); + btn->is_disabled = 0; + btn->w.x = strlen(btn->w.title) + 6; // + "[ ]" + btn->w.y = 1; // TODO: detect new line + btn->w.cur.y = 0; + btn->w.cur.x = 0; + return btn; +} + +int32_t draw_button(button_t *btn, uint32_t active_id) { + char title[DIALOGUE_TITLE + 6]; + WINDOW *win = *(btn->w.parent_win); + uint32_t margin_y = btn->w.m_y + btn->w.w_parent->m_y; + uint32_t margin_x = btn->w.m_x + btn->w.w_parent->m_x; + + if (btn->w.id == active_id) { + sprintf(title, "[< %s >]", btn->w.title); + wattrset(win, COLOR_PAIR(modal_color_pair) | A_BOLD | A_REVERSE); + } else { + sprintf(title, "[ %s ]", btn->w.title); + wattrset(win, COLOR_PAIR(0) | A_REVERSE); + } + + mvwprintw(win, margin_y, margin_x, "%s", title); + btn->w.cur.y = margin_y; + btn->w.cur.x = margin_x; + + wattroff(win, A_BOLD | A_REVERSE); + return strlen(title); +} + +void destroy_button(button_t *btn) { free(btn); } \ No newline at end of file diff --git a/src/ui/widget/button.h b/src/ui/widget/button.h new file mode 100644 index 0000000..ce44c57 --- /dev/null +++ b/src/ui/widget/button.h @@ -0,0 +1,17 @@ +#ifndef BUTTON_H +#define BUTTON_H + +#include "../../main.h" +#include "../widget.h" +#include + +typedef struct { + widget_t w; + char hotkey; + uint32_t is_disabled : 1; +} button_t; + +button_t *init_button(WINDOW **win, widget_t *w_parent, char *label); +int32_t draw_button(button_t *btn, uint32_t active_id); +void destroy_button(button_t *btn); +#endif \ No newline at end of file diff --git a/src/ui/widget/dialogue.c b/src/ui/widget/dialogue.c new file mode 100644 index 0000000..1f63d27 --- /dev/null +++ b/src/ui/widget/dialogue.c @@ -0,0 +1,360 @@ +#include +#include +#include +#include + +#include "../app.h" +#include "dialogue.h" +#include "file_list.h" +#include "group.h" +#include "input.h" + +#define INCR_ACTIVE_ID(d, current, next) \ + if (d->active.id != d->current->last_id) { \ + d->active.id = d->active.id + 1; \ + } else if (d->next != NULL) { \ + d->active.id = d->next->first_id; \ + d->active.type = next; \ + } else { \ + d->active.id = d->current->first_id; \ + } + +#define DECR_ACTIVE_ID(d, current, next) \ + if (d->active.id != d->current->first_id) { \ + d->active.id = d->active.id - 1; \ + } else if (d->next != NULL) { \ + d->active.id = d->next->last_id; \ + d->active.type = next; \ + } else { \ + d->active.id = d->current->last_id; \ + } + +#define CH_GROUP(d, current, next) \ + { \ + if (d->active.type == current) { \ + d->active.type = next; \ + diff = d->active.id - d->current->first_id; \ + if (diff > d->next->last_id - d->next->first_id) { \ + d->active.id = d->next->last_id; \ + } else { \ + d->active.id = d->next->first_id + diff; \ + } \ + } \ + } + +widget_t *get_active_widget(dialogue_t *d) { + group_t *g; + if (d->active.type == g_action) { + g = d->g_action; + } else { + g = d->g_content; + } + widget_t *w = (widget_t *)g->elements[d->active.id - g->first_id].element; + return w; +} + +void dialogue_default_callback(callback_args_t *args) { + dialogue_t *d = (void *)args->element; + int32_t key = *((int32_t *)args->data); + app_t *app = (app_t *)args->app; + callback_args_t new_args; + int32_t *resp_value = (int32_t *)args->resp_data; + group_el_t *default_element = NULL; + memcpy(&new_args, args, sizeof(callback_args_t)); + new_args.active_id = d->active.id; + int32_t diff; + d->needs_update = true; + input_t *input; + widget_t *widget; + switch (key) { + case '\t': + if (d->active.type == g_content) { + INCR_ACTIVE_ID(d, g_content, g_action); + } else if (d->active.type == g_action) { + INCR_ACTIVE_ID(d, g_action, g_content); + } + *resp_value = -1; + break; + case '\33': /* Esc key */ + *resp_value = -2; + break; + case KEY_RIGHT: + if (d->active.type == g_content) { + if (d->g_content->elements[d->active.id - d->g_content->first_id].type == + w_input) { + input = d->g_content->elements[d->active.id - d->g_content->first_id] + .element; + if (input->cur_pos > 0) { + input->cur_pos -= 1; + } + } else { + INCR_ACTIVE_ID(d, g_content, g_action); + } + } else if (d->active.type == g_action) { + INCR_ACTIVE_ID(d, g_action, g_content); + } + *resp_value = -1; + break; + case KEY_LEFT: + if (d->active.type == g_content) { + if (d->g_content->elements[d->active.id - d->g_content->first_id].type == + w_input) { + input = d->g_content->elements[d->active.id - d->g_content->first_id] + .element; + if (input->value_len > input->cur_pos) { + input->cur_pos += 1; + } + } else { + DECR_ACTIVE_ID(d, g_content, g_action); + } + } else if (d->active.type == g_action) { + DECR_ACTIVE_ID(d, g_action, g_content); + } + *resp_value = -1; + break; + case '\n': + widget = get_active_widget(d); + if (widget->callback != NULL) { /* Existing callback case */ + new_args.element = widget; + widget->callback(&new_args); + break; + } /* Default cases */ + if (d->active.type == g_action) { + new_args.element = new_args.element = d->g_action; + group_default_callback(&new_args); + } else if (d->g_action != NULL) { + for (int i = 0; i < d->g_action->count; i++) { + if (d->g_action->elements[i].is_default) { + *resp_value = i; + return; + } + } + } else { + /* TODO: temp solution, improve */ + *resp_value = d->active.id - d->g_content->first_id; + return; + } + break; + case KEY_UP: + if (d->g_content->elements[d->active.id - d->g_content->first_id].type == + w_fs_file_list) { + new_args.element = d->g_content; + group_default_callback(&new_args); + } else { + CH_GROUP(d, g_action, g_content); + } + break; + case KEY_DOWN: + if (d->g_content->elements[d->active.id - d->g_content->first_id].type == + w_fs_file_list) { + new_args.element = d->g_content; + group_default_callback(&new_args); + } else { + CH_GROUP(d, g_content, g_action); + } + break; + default: + /* run callback function */ + if (d->active.type == g_content) { + new_args.element = new_args.element = d->g_content; + } else { + new_args.element = new_args.element = d->g_action; + } + group_default_callback(&new_args); + } +} + +void init_dialogue(dialogue_t *dialogue, const char title[], const char text[], + coordinates_t *p_coordinates) { + dialogue->win = 0; + dialogue->g_content = NULL; + dialogue->w.x = 0; + dialogue->w.y = 0; + dialogue->p_coordinates = p_coordinates; + dialogue->w.callback = dialogue_default_callback; + dialogue->is_initiated = true; + dialogue->needs_update = true; + dialogue->needs_destroy = false; + strcpy(dialogue->w.title, title); + strcpy(dialogue->text, text); +} + +void dialogue_init_active_id(dialogue_t *dialogue) { + enum w_type widget_type; + + if (dialogue->g_content != NULL) { + for (int i = 0; i < dialogue->g_content->count; i++) { + widget_type = dialogue->g_content->elements[i].type; + if (widget_type == w_button || widget_type == w_input || + widget_type == w_fs_file_list) { /* Add here new types */ + dialogue->active.type = g_content; + widget_t *w = (widget_t *)dialogue->g_content->elements[i].element; + dialogue->active.id = w->id; + return; + } + } + } + if (dialogue->g_action != NULL) { + for (int i = 0; i < dialogue->g_action->count; i++) { + widget_type = dialogue->g_action->elements[i].type; + if (widget_type == w_button || widget_type == w_input || + widget_type == w_fs_file_list) { + dialogue->active.type = g_action; + widget_t *w = (widget_t *)dialogue->g_action->elements[i].element; + dialogue->active.id = w->id; + return; + } + } + } +} + +// void dialogue_init_active_id(dialogue_t *dialogue) { +// enum w_type widget_type; + +// if (dialogue->g_content != NULL) { +// for (int i = 0; i < dialogue->g_content->count; i++) { +// widget_type = dialogue->g_content->elements[i].type; +// if (widget_type == w_button || widget_type == w_input || +// widget_type == w_fs_file_list) { /* Add here new types */ +// dialogue->active.type = g_content; +// widget_t *w = (widget_t *)dialogue->g_content->elements[i].element; +// dialogue->active.id = w->id; +// break; +// } +// } +// } else if (dialogue->g_action != NULL) { +// for (int i = 0; i < dialogue->g_action->count; i++) { +// widget_type = dialogue->g_action->elements[i].type; +// if (widget_type == w_button || widget_type == w_input || +// widget_type == w_fs_file_list) { +// dialogue->active.type = g_action; +// widget_t *w = (widget_t *)dialogue->g_action->elements[i].element; +// dialogue->active.id = w->id; +// break; +// } +// } +// } +// } + +#define DETECT_GROUP_SIZE(group, line_max_len, y, x) \ + if (group) { \ + group->w.m_y = y; \ + if (line_max_len < group->w.x) { \ + line_max_len = group->w.x; \ + group->w.m_x = 0; \ + } else { \ + group->w.m_x = (line_max_len - group->w.x) / 2; \ + } \ + y += group->w.y; \ + } + +int32_t draw_dialogue(dialogue_t *d) { + if (!d->is_initiated) + return -1; + else if (!d->needs_update) { + return 0; + } + + group_el_t *ae_ptr = NULL; /* active element */ + uint32_t ae_idx; /* active element */ + /* count dimensions */ + uint32_t x = 1; /* when uses box */ + uint32_t y = 1; /* when uses box */ + + /* analyze text content */ + uint32_t line_count = 0; + uint32_t line_max_len = 0; + line_max_len = get_max_line_len(d->text, &line_count); + y += line_count; + y += 2; /* margin for text */ + + /* analyze groups */ + DETECT_GROUP_SIZE(d->g_content, line_max_len, y, x); + DETECT_GROUP_SIZE(d->g_action, line_max_len, y, x); + x += line_max_len; + + x += 1; /* when uses box */ + y += 1; /* when uses box */ + + d->w.x = x; + d->w.y = y; + d->w.m_y = (d->p_coordinates->max_y - y) / 2; + d->w.m_x = (d->p_coordinates->max_x - x) / 2; + + /* render window */ + if (d->win == NULL) { + d->win = newwin(y, x, d->w.m_y, d->w.m_x); + } + + wattrset(d->win, COLOR_PAIR(0) | A_BOLD | A_REVERSE); + + /* background */ + for (int yp = 1; yp < d->w.y - 1; yp++) { + mvwprintw(d->win, yp, 1, "%*s", d->w.x, ""); + } + + /* title */ + const uint32_t title_mx_pos = (d->w.x - strlen(d->w.title)) / 2; + box(d->win, 0, 0); + mvwprintw(d->win, 0, title_mx_pos - 1, " "); + mvwprintw(d->win, 0, title_mx_pos, "%s", d->w.title); + mvwprintw(d->win, 0, title_mx_pos + strlen(d->w.title), " "); + + /* text */ + wattroff(d->win, A_BOLD); + /* mvwhline(d->win, d->w.y - 3, 1, 0, d->w.x - 2); */ + print_multiline_text(d->win, d->text, d->w.x, 2, 1, PMT_ALIGN_CENTER); + wattroff(d->win, A_REVERSE); + + if (d->g_content != NULL) { + draw_group(d->win, d->g_content, d->active.id, &(d->w)); + } + if (d->g_action != NULL) { + draw_group(d->win, d->g_action, d->active.id, &(d->w)); + } + + /* move cursor */ + if (d->g_content != NULL) { + FIND_ACTIVE_ELEMENT(d->g_content, d->active.id, ae_ptr, ae_idx); + } + if (ae_ptr == NULL && d->g_action != NULL) + FIND_ACTIVE_ELEMENT(d->g_action, d->active.id, ae_ptr, ae_idx); + + if (ae_ptr != NULL && ae_ptr->id == d->active.id) { + if (ae_ptr->type == w_input) { + input_t *input = ae_ptr->element; + d->w.cur.y = input->w.cur.y; + d->w.cur.x = input->w.cur.x + input->value_len; + d->w.cur.x -= input->cur_pos; + if (d->w.cur.y || d->w.cur.x) { + wmove(d->win, d->w.cur.y, d->w.cur.x); + curs_set(true); + } + } else { + curs_set(false); + } + } + + d->needs_update = false; + + return 0; +}; + +void destroy_dialogue(dialogue_t *d, void *_app) { + app_t *app = (app_t *)_app; + if (d->g_content) { + destroy_group(d->g_content); + } + if (d->g_action) { + destroy_group(d->g_action); + } + delwin(d->win); + d->win = NULL; + d->is_initiated = 0; + d->needs_destroy = false; + app->active_win_type = aw_left; + app->active_win = app->left_win; + app->active_callback = file_list_cb; + app->query_args->active_dialogue = NULL; + app_refresh(app); +} \ No newline at end of file diff --git a/src/ui/widget/dialogue.h b/src/ui/widget/dialogue.h new file mode 100644 index 0000000..87d62c3 --- /dev/null +++ b/src/ui/widget/dialogue.h @@ -0,0 +1,36 @@ +#ifndef DIALOGUE_H +#define DIALOGUE_H + +#include "../widget.h" +#include "../../common.h" +#include "button.h" +#include "group.h" +#include +#include + +enum g_type { g_content, g_action }; + +typedef struct { + widget_t w; + WINDOW *win; + group_t *g_content; + group_t *g_action; + struct { + int32_t id; + enum g_type type; + } active; + char text[DIALOGUE_TEXT]; + uint32_t is_initiated : 1; + uint32_t needs_update : 1; + uint32_t needs_destroy: 1; + coordinates_t *p_coordinates; +} dialogue_t; + +void init_dialogue(dialogue_t *d, const char title[], const char text[], + coordinates_t *p_coordinates); +int32_t draw_dialogue(dialogue_t *d); +void destroy_dialogue(dialogue_t *d, void *app); +void dialogue_default_callback(callback_args_t *args); +void dialogue_init_active_id(dialogue_t *dialogue); + +#endif \ No newline at end of file diff --git a/src/ui/widget/file_list.c b/src/ui/widget/file_list.c new file mode 100644 index 0000000..d0a3111 --- /dev/null +++ b/src/ui/widget/file_list.c @@ -0,0 +1,156 @@ +#include "file_list.h" +#include "../../file_processor.h" +#include "../../fs.h" +#include "../app.h" +#include "progress_bar.h" +#include +#include +#include +#include +#include +#include +#include +#include + +void reset_file_list(ui_file_list_t *fl_ui); + +void file_list_cb(callback_args_t *args) { + app_t *app = args->app; + ui_file_list_t *fui = app->query_args->file_list_ui; + int32_t key = *((int32_t *)args->data); + char query[256]; + int32_t q_len = 0; + fl_item_t *f_item; + ui_progress_bar_t *pb; + switch (key) { + case KEY_DOWN: + if (fui->current_idx < fui->current_count - 1) { + fui->current_idx++; + draw_file_list(fui); + } else if (fui->current_page < fui->pages) { + fl_clear(fui->start, fui->current); + app->query_args->state = S_FILE_LIST; + sprintf(query, "file list %u %u\n%n", fui->max_lines, + fui->current_page + 1, &q_len); + write(app->query_args->sd, query, q_len); + reset_file_list(fui); + } + break; + case KEY_UP: + if (fui->current_idx > 0) { + fui->current_idx--; + draw_file_list(fui); + } else if (fui->current_page > 1) { + fl_clear(fui->start, fui->current); + app->query_args->state = S_FILE_LIST; + sprintf(query, "file list %u %u\n%n", fui->max_lines, + fui->current_page - 1, &q_len); + write(app->query_args->sd, query, q_len); + reset_file_list(fui); + fui->activate_last = true; + } + break; + case '\n': + ui_file_select(app->file_args, app->query_args, fui->current_idx + 1); + break; + } +} + +ui_file_list_t *init_file_list(WINDOW **win, WINDOW *const *info_win) { + ui_file_list_t *fl_ui = malloc(sizeof(ui_file_list_t)); + init_widget(&(fl_ui->w), NULL, win, ""); + WINDOW *parent_win = *(fl_ui->w.parent_win); + fl_ui->current_idx = 0; + fl_ui->current_page = 1; + fl_ui->pages = 1; + fl_ui->current_count = 0; + fl_ui->full_count = 0; + fl_ui->activate_last = false; + fl_ui->info_win = info_win; + fl_ui->max_lines = getmaxy(parent_win) - 3; /* 2+1 (bottom info line) */ + return fl_ui; +} + +void reset_file_list(ui_file_list_t *fl_ui) { + fl_ui->current_idx = 0; + fl_ui->current_page = 0; + fl_ui->pages = 0; + fl_ui->current_count = 0; + fl_ui->full_count = 0; + fl_ui->activate_last = false; +} + +void draw_file_list(ui_file_list_t *fl_ui) { + int32_t sz_y, sz_x; + int32_t p_y, p_x; + WINDOW *parent_win = *(fl_ui->w.parent_win); + getmaxyx(parent_win, sz_y, sz_x); + int32_t sz_y_f = sz_y - 2; /* TODO: можно заменить на поле max_lines */ + p_y = 1; + p_x = 1; + fl_item_t *el = *(fl_ui->start); + fl_item_t *active_el = *(fl_ui->start); + int32_t cur_el_idx = 0; + curs_set(false); + + if (fl_ui->activate_last) { + fl_ui->current_idx = fl_ui->current_count - 1; + fl_ui->activate_last = false; + } + + if (fl_ui->current_idx + 1 >= sz_y_f) { + p_y -= fl_ui->current_idx - sz_y_f + 2; + } + + do { + if (p_y < 1) { + p_y++; + cur_el_idx++; + continue; + } + if (cur_el_idx == fl_ui->current_idx) { + wattrset(parent_win, A_BOLD | A_REVERSE); + active_el = el; + } + p_x = 1; + mvwprintw(parent_win, p_y, p_x, "%s%n", el->name, &p_x); + mvwprintw(parent_win, p_y, p_x + 1, "%*s", sz_x - p_x, ""); + if (cur_el_idx == fl_ui->current_idx) { + wattroff(parent_win, A_BOLD | A_REVERSE); + } + p_y++; + cur_el_idx++; + } while ((el = el->next) != NULL && p_y < sz_y_f); + + p_x = 1; + + for (; p_y < sz_y_f; p_y++) { + mvwprintw(parent_win, p_y, p_x, "%*s", sz_x - 1, ""); + } + char p_info[64]; + uint32_t p_len; + p_x = 1; + + sprintf(p_info, "page: %u/%u files: %u left: %u%n", fl_ui->current_page, + fl_ui->pages, fl_ui->current_count, fl_ui->full_count, &p_len); + + uint32_t l_pad = (sz_x - p_len) / 2; + + wattrset(parent_win, A_BOLD); + mvwprintw(parent_win, p_y, p_x, "%*s%s%*s", l_pad, "", p_info, l_pad, ""); + wattroff(parent_win, A_BOLD); + + /* draw file info */ + p_y = 1; + p_x = 1; + WINDOW *i_win = *fl_ui->info_win; + wclear(i_win); + + char size_text[64]; + size_to_text(active_el->size, size_text); + + mvwprintw(i_win, p_y++, p_x, "Size: %s", size_text); + mvwprintw(i_win, p_y++, p_x, "Owner: %s", active_el->owner); + mvwprintw(i_win, p_y++, p_x, "Description: "); + print_multiline_text(i_win, active_el->description, sz_x, p_y, p_x, 0); +} \ No newline at end of file diff --git a/src/ui/widget/file_list.h b/src/ui/widget/file_list.h new file mode 100644 index 0000000..d30c7fa --- /dev/null +++ b/src/ui/widget/file_list.h @@ -0,0 +1,24 @@ +#ifndef FILE_LIST_H +#define FILE_LIST_H +#include "../widget.h" +#include + +typedef struct { + widget_t w; + fl_item_t **current; + fl_item_t **start; + int32_t current_idx; + uint32_t current_count; + uint32_t full_count; + uint32_t pages; + uint32_t current_page; + uint32_t max_lines; + WINDOW *const* info_win; + uint32_t activate_last : 1; +} ui_file_list_t; + +void draw_file_list(ui_file_list_t *fl_ui); +void redraw_file_list(ui_file_list_t *fl_ui); +ui_file_list_t *init_file_list(WINDOW **win, WINDOW *const* info_win); +void file_list_cb(callback_args_t *args); +#endif \ No newline at end of file diff --git a/src/ui/widget/fs_file_list.c b/src/ui/widget/fs_file_list.c new file mode 100644 index 0000000..12de02a --- /dev/null +++ b/src/ui/widget/fs_file_list.c @@ -0,0 +1,212 @@ +#include "fs_file_list.h" +#include "progress_bar.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void reset_file_list(ui_fs_file_list_t *fl_ui); + +struct fl_args { + char *name; + u_char d_type; +}; + +static void fl_add(fs_fl_item_t **f_start, fs_fl_item_t **f_current, + struct fl_args *fargs) { + fs_fl_item_t *c_item = malloc(sizeof(fs_fl_item_t)); + c_item->name = malloc(strlen(fargs->name) + 1); + strcpy(c_item->name, fargs->name); + c_item->next = NULL; + c_item->d_type = fargs->d_type; + if (*f_start == NULL) { + c_item->prev = NULL; + *f_start = c_item; + *f_current = c_item; + } else { + c_item->prev = *f_current; + (*f_current)->next = c_item; + *f_current = c_item; + } +} + +static void fl_clear(fs_fl_item_t **start, fs_fl_item_t **arg_current) { + if (*start == NULL) + return; + fs_fl_item_t *next, *current; + current = *start; + + do { + next = current->next; + free(current->name); + free(current); + } while ((current = next) != NULL); + *start = NULL; + *arg_current = NULL; +} + +fs_fl_item_t *get_files_from_fs(char *path) { + DIR *dir; + struct dirent *dent; + char name[64]; + struct fl_args f_args; + + fs_fl_item_t *d_start = NULL, *d_current = NULL; /* directories */ + fs_fl_item_t *f_start = NULL, *f_current = NULL; /* files */ + + dir = opendir(path); + if (!dir) { + return NULL; + errno = 1; + } + while ((dent = readdir(dir)) != NULL) { + if ((f_args.d_type = dent->d_type) == DT_DIR) { + if (!strcmp(dent->d_name, ".")) + continue; + sprintf(name, "/%s", dent->d_name); + f_args.name = name; + fl_add(&d_start, &d_current, &f_args); + } else { + f_args.name = dent->d_name; + sprintf(name, "%s", dent->d_name); + fl_add(&f_start, &f_current, &f_args); + } + } + if (d_current != NULL) + d_current->next = f_start; + if (f_start != NULL) + f_start->prev = d_current; + return d_start; +} + +void select_item(ui_fs_file_list_t *fui, int32_t *resp_data) { + char *bash_case = NULL; + char *selection = NULL; + size_t dp_current; + if (fui->current->d_type == DT_DIR) { + if (!strcmp("/..", fui->current->name)) { + bash_case = strrchr(fui->d_path, '/'); + if (bash_case != NULL) { + *bash_case = '\0'; + fl_clear(&(fui->start), &(fui->current)); + fui->start = get_files_from_fs(fui->d_path); + } + } else { + dp_current = strlen(fui->d_path); + selection = fui->current->name + 1; + fui->d_path = + realloc(fui->d_path, dp_current + strlen(selection) + 2); + sprintf(fui->d_path, "%s/%s", fui->d_path, selection); + fl_clear(&(fui->start), &(fui->current)); + fui->start = get_files_from_fs(fui->d_path); + } + fui->current = fui->start; + fui->current_idx = 0; + draw_fs_file_list(fui); + } else if (fui->current->d_type == DT_REG) { + *resp_data = 1; + } +} + +void fs_file_list_cb(callback_args_t *args) { + ui_fs_file_list_t *fui = args->element; + int32_t key = *((int32_t *)args->data); + int32_t q_len = 0; + fs_fl_item_t *f_item; + ui_progress_bar_t *pb; + switch (key) { + case KEY_DOWN: + if (fui->current->next != NULL) { + fui->current = fui->current->next; + fui->current_idx++; + draw_fs_file_list(fui); + } + break; + case KEY_UP: + if (fui->current->prev != NULL) { + fui->current = fui->current->prev; + fui->current_idx--; + draw_fs_file_list(fui); + } + break; + case '\n': + select_item(fui, (int32_t *) args->resp_data); + break; + } +} + +ui_fs_file_list_t *init_fs_file_list(WINDOW **win, widget_t *w_parent) { + ui_fs_file_list_t *fl_ui = malloc(sizeof(ui_fs_file_list_t)); + WINDOW *win_par = *(w_parent->w_parent->parent_win); + init_widget(&(fl_ui->w), w_parent, win, ""); + fl_ui->current_idx = 0; + fl_ui->info_win = NULL; + fl_ui->d_path = get_current_dir_name(); + fl_ui->start = get_files_from_fs(fl_ui->d_path); + fl_ui->current = fl_ui->start; + fl_ui->w.callback = fs_file_list_cb; + fl_ui->w.x = getmaxx(win_par) / 10 * 8; + fl_ui->w.y = getmaxy(win_par) / 10 * 8; + return fl_ui; +} + +void reset_fs_file_list(ui_fs_file_list_t *fl_ui) { fl_ui->current_idx = 0; } + +void draw_fs_file_list(ui_fs_file_list_t *fl_ui) { + int32_t sz_y, sz_x; + int32_t p_y, p_x; + WINDOW *win = *(fl_ui->w.parent_win); + getmaxyx(win, sz_y, sz_x); + int32_t sz_y_f = sz_y - 2; // actual size (without box) + int32_t sz_x_f = sz_x - 2; // actual size (without box) + p_y = 1; + p_x = 1; + fs_fl_item_t *el = fl_ui->start; + fs_fl_item_t *active_el = fl_ui->start; + + if (fl_ui->current_idx + 1 >= sz_y_f) { + p_y -= fl_ui->current_idx - sz_y_f + 2; + } + + wattrset(win, A_REVERSE); /* Match with modal background */ + + do { + if (p_y < 1) { + p_y++; + continue; + } + if (el == fl_ui->current) { + wattrset(win, COLOR_PAIR(3) | A_BOLD | A_REVERSE); + active_el = el; + } + p_x = 1; + mvwprintw(win, p_y, p_x, "%s%n", el->name, &p_x); + mvwprintw(win, p_y, p_x + 1, "%*s", sz_x_f - p_x, ""); + if (el == fl_ui->current) { + wattroff(win, COLOR_PAIR(3) | A_BOLD); + } + p_y++; + } while ((el = el->next) != NULL && p_y < sz_y_f); + + p_x = 1; + + for (; p_y < sz_y_f; p_y++) { + mvwprintw(win, p_y, p_x, "%*s", sz_x_f - 1, ""); + } + + wattroff(win, A_BOLD | A_REVERSE); + wattrset(win, COLOR_PAIR(0)); + curs_set(false); +} + +void destroy_fs_file_list(ui_fs_file_list_t *fui) { + fl_clear(&(fui->start), &(fui->current)); + free(fui); +} \ No newline at end of file diff --git a/src/ui/widget/fs_file_list.h b/src/ui/widget/fs_file_list.h new file mode 100644 index 0000000..db380d8 --- /dev/null +++ b/src/ui/widget/fs_file_list.h @@ -0,0 +1,30 @@ +#ifndef FS_FILE_LIST_H +#define FS_FILE_LIST_H +#include "../widget.h" +#include +#include +#include + +typedef struct fs_fl_item { + char *name; + struct fs_fl_item *next; + struct fs_fl_item *prev; + u_char d_type; +} fs_fl_item_t; + +typedef struct { + widget_t w; + fs_fl_item_t *current; + fs_fl_item_t *start; + int32_t current_idx; + WINDOW *const* info_win; + char *d_path; +} ui_fs_file_list_t; + +void draw_fs_file_list(ui_fs_file_list_t *fl_ui); +void redraw_fs_file_list(ui_fs_file_list_t *fl_ui); +void destroy_fs_file_list(ui_fs_file_list_t *fl_ui); +ui_fs_file_list_t *init_fs_file_list(WINDOW **win, widget_t *w_parent); +void fs_file_list_cb(callback_args_t *args); +void reset_fs_file_list(ui_fs_file_list_t *fl_ui); +#endif \ No newline at end of file diff --git a/src/ui/widget/group.c b/src/ui/widget/group.c new file mode 100644 index 0000000..e39dd06 --- /dev/null +++ b/src/ui/widget/group.c @@ -0,0 +1,200 @@ +#include "group.h" +#include "button.h" +#include "fs_file_list.h" +#include "input.h" +#include "progress_bar.h" +#include +#include +#include +#include +#include + +#define MAKE_RESPONSE_M1(args, resp_data, response) \ + if (args->resp_data != NULL) { \ + *response = -1; \ + } + +void group_default_callback(callback_args_t *args) { + group_t *g = (group_t *)args->element; + int32_t key = *((int32_t *)args->data); + int32_t *response = (int32_t *)args->resp_data; + input_t *input; + widget_t *widget; + group_el_t *element_ptr; + int32_t element_idx = -1; + u_int32_t start_pos = 0; + callback_args_t new_args; + switch (key) { + case '\n': /* Enter */ + FIND_ACTIVE_ELEMENT(g, args->active_id, element_ptr, element_idx); + *response = element_idx; + break; + case KEY_BACKSPACE: + case KEY_DL: + FIND_ACTIVE_ELEMENT(g, args->active_id, element_ptr, element_idx); + if (element_ptr->type == w_input) { + input = (input_t *)element_ptr->element; + if (input->value_len) { + if (input->cur_pos > 0) { + start_pos = input->value_len-- - input->cur_pos; + memmove(input->value + start_pos - 1, input->value + start_pos, + input->value_len - start_pos + 2); + } else { + input->value[--input->value_len] = '\0'; + } + } + } + MAKE_RESPONSE_M1(args, resp_data, response); + break; + default: + FIND_ACTIVE_ELEMENT(g, args->active_id, element_ptr, element_idx); + if (element_ptr->type == w_input) { + input = (input_t *)element_ptr->element; + if (input->max_len > input->value_len) { + if (input->cur_pos > 0) { + start_pos = input->value_len++ - input->cur_pos; + memmove(input->value + start_pos + 1, input->value + start_pos, + input->value_len - start_pos); + input->value[start_pos] = key; + } else { + input->value[input->value_len++] = key; + input->value[input->value_len] = '\0'; + } + } + } else { + widget = (widget_t *) element_ptr->element; + if (widget->callback != NULL) { + memccpy(&new_args, args, 1, sizeof(callback_args_t)); + new_args.element = element_ptr->element; + widget->callback(&new_args); + break; + } + } + MAKE_RESPONSE_M1(args, resp_data, response); + break; + } +} + +group_t *init_group(WINDOW **win, widget_t *w_parent, group_el_init_t *children, + enum g_direction direction) { + group_t *group = malloc(sizeof(group_t)); + init_widget(&(group->w), w_parent, win, ""); + /* count elements */ + group->count = 0; + for (; children[group->count].type != w_end; group->count++) + ; + group->w.w_parent = w_parent; + group->w.callback = group_default_callback; + if (group->count == 0) { + return group; + } + group->elements = calloc(group->count, sizeof(group_el_t)); + group_el_t *elements = group->elements; + for (int32_t i = 0; i < group->count; i++) { + elements[i].type = children[i].type; + elements[i].is_default = children[i].is_default; + } + + /* init child elements */ + for (int32_t i = 0; i < group->count; i++) { + widget_t *w; + switch (elements[i].type) { + case w_button: + elements[i].element = init_button(win, &(group->w), children[i].label); + w = &(((button_t *)elements[i].element)->w); + break; + case w_box: + case w_group: + case w_end: + break; + case w_input: + elements[i].element = + init_input(win, &(group->w), children[i].label, children[i].length, + children[i].is_hidden_value); + w = &(((input_t *)elements[i].element)->w); + break; + case w_progress: + elements[i].element = init_ui_progress_bar(win, &(group->w)); + w = &(((ui_progress_bar_t *)elements[i].element)->w); + break; + case w_fs_file_list: + elements[i].element = init_fs_file_list(win, &(group->w)); + w = &(((ui_progress_bar_t *)elements[i].element)->w); + break; + } + /* set dimensions */ + elements[i].id = w->id; + if (direction == horizontal) { + w->m_x = group->w.m_x + 1 + group->w.x; + w->m_y = group->w.m_y; + group->w.x += w->x + 1; + if (group->w.y < w->y) + group->w.y = w->y; + } + if (i == 0) { + group->first_id = w->id; + group->last_id = w->id; + } else { + group->last_id = w->id; + } + } + + return group; +} + +void draw_group(WINDOW *win, group_t *group, int32_t active_id, + widget_t *dialog_w) { + group_el_t *children = group->elements; + group_el_t *active_element = NULL; + uint32_t active_idx; + for (int i = 0; i < group->count; i++) { + group_el_t *el = &children[i]; + switch (el->type) { + case w_button: + draw_button((button_t *)el->element, active_id); + break; + case w_box: + case w_group: + case w_end: + break; + case w_input: + draw_input((input_t *)el->element, active_id); + break; + case w_progress: + draw_ui_progress_bar((ui_progress_bar_t *)el->element); + break; + case w_fs_file_list: + draw_fs_file_list((ui_fs_file_list_t *)el->element); + break; + } + } +} + +void destroy_group(group_t *group) { + group_el_t *children = group->elements; + for (int i = 0; i < group->count; i++) { + group_el_t *el = &children[i]; + switch (el->type) { + case w_button: + destroy_button(el->element); + break; + case w_box: + case w_group: + case w_end: + break; + case w_input: + destroy_input(el->element); + break; + case w_progress: + destroy_ui_progress_bar(el->element); + break; + case w_fs_file_list: + destroy_fs_file_list(el->element); + break; + } + } + if (group->elements != NULL) { + free(group->elements); + } + free(group); +} \ No newline at end of file diff --git a/src/ui/widget/group.h b/src/ui/widget/group.h new file mode 100644 index 0000000..4516dc6 --- /dev/null +++ b/src/ui/widget/group.h @@ -0,0 +1,46 @@ +#ifndef GROUP_H +#define GROUP_H + +#include "../widget.h" +#include + +enum g_direction { + horizontal, + vertical, +}; + +typedef struct { + uint32_t id; + void *element; + enum w_type type; + uint32_t is_default : 1; +} group_el_t; + +typedef struct { + enum w_type type; + uint32_t is_default : 1; + uint32_t is_hidden_value : 1; + char label[DIALOGUE_TITLE]; + uint32_t length; +} group_el_init_t; + +typedef struct { + widget_t w; + group_el_t *elements; + enum g_direction direction; + uint32_t count; + uint32_t first_id, last_id; +} group_t; + +#define FIND_ACTIVE_ELEMENT(g, active_id, element_ptr, element_idx) \ + element_idx = active_id - g->first_id; \ + element_ptr = &(g->elements[element_idx]); + +group_t *init_group(WINDOW **win, widget_t *w_parent, group_el_init_t *children, + enum g_direction dir); +void draw_group(WINDOW *win, group_t *group, int32_t active_id, + widget_t *dialog_w); +void destroy_group(group_t *group); +void group_default_callback(callback_args_t *args); + +#endif \ No newline at end of file diff --git a/src/ui/widget/input.c b/src/ui/widget/input.c new file mode 100644 index 0000000..a9e36a9 --- /dev/null +++ b/src/ui/widget/input.c @@ -0,0 +1,75 @@ +#include +#include +#include +#include + +#include "../app.h" +#include "../widget.h" +#include "input.h" + +input_t *init_input(WINDOW **win, widget_t *w_parent, char *label, + uint32_t length, uint32_t is_hidden_value) { + input_t *input = malloc(sizeof(input_t)); + init_widget(&(input->w), w_parent, win, label); + input->is_disabled = 0; + input->w.y = 3; // with borders + input->w.x = length + 3; // with borders and extra space for the last element + uint32_t t_len = strlen(input->w.title) + 4; // with borders and space + if (input->w.x < t_len) + input->w.x = t_len; + input->w.m_x = 1; + input->value[0] = '\0'; + input->value_len = 0; + input->max_len = length; + input->is_hidden = is_hidden_value; + input->cur_pos = 0; + return input; +} + +int32_t draw_input(input_t *input, uint32_t active_id) { + const char stars[] = "*******************"; + WINDOW *win = *(input->w.parent_win); + uint32_t margin_y = input->w.m_y + input->w.w_parent->m_y; + uint32_t margin_x = input->w.m_x + input->w.w_parent->m_x; + + wattrset(win, COLOR_PAIR(modal_color_pair)); + + mvwhline(win, margin_y, margin_x, 0, input->w.x); + mvwhline(win, margin_y + 2, margin_x, 0, input->w.x); + + mvwprintw(win, margin_y, margin_x + 1, " %s ", input->w.title); + + // left + mvwvline(win, margin_y, margin_x, ACS_ULCORNER, 1); + mvwvline(win, margin_y + 1, margin_x, 0, 1); + mvwvline(win, margin_y + 2, margin_x, ACS_LLCORNER, 1); + // right + mvwvline(win, margin_y, margin_x + input->w.x - 1, ACS_URCORNER, 1); + mvwvline(win, margin_y + 1, margin_x + input->w.x - 1, 0, 1); + mvwvline(win, margin_y + 2, margin_x + input->w.x - 1, ACS_LRCORNER, 1); + + margin_x++; + + if (input->w.id == active_id) { + wattrset(win, COLOR_PAIR(modal_color_pair) | A_BOLD | A_REVERSE); + } else { + wattrset(win, COLOR_PAIR(0) | A_BOLD); + } + + uint32_t x_c = 0; + if (input->is_hidden) { + mvwprintw(win, margin_y + 1, margin_x, "%.*s%*s", (int) input->value_len, stars, + input->w.x - input->value_len - 2, ""); + } else { + mvwprintw(win, margin_y + 1, margin_x, "%s%*s", input->value, + input->w.x - input->value_len - 2, ""); + } + + input->w.cur.y = margin_y + 1; + input->w.cur.x = margin_x; + + wattroff(win, A_BOLD | A_REVERSE); + return 0; +} + +void destroy_input(input_t *btn) { free(btn); } \ No newline at end of file diff --git a/src/ui/widget/input.h b/src/ui/widget/input.h new file mode 100644 index 0000000..10731a9 --- /dev/null +++ b/src/ui/widget/input.h @@ -0,0 +1,22 @@ +#ifndef INPUT_H +#define INPUT_H + +#include "../../main.h" +#include "../widget.h" +#include + +typedef struct { + widget_t w; + char value[INPUT_TEXT]; + uint32_t cur_pos; /* is relative to the end of a string */ + uint32_t value_len; + uint32_t max_len; + uint32_t is_disabled : 1; + uint32_t is_hidden : 1; +} input_t; + +input_t *init_input(WINDOW **win, widget_t *w_parent, char *label, + uint32_t size, uint32_t is_hidden_value); +int32_t draw_input(input_t *input, uint32_t active_id); +void destroy_input(input_t *input); +#endif \ No newline at end of file diff --git a/src/ui/widget/progress_bar.c b/src/ui/widget/progress_bar.c new file mode 100644 index 0000000..f81cd1e --- /dev/null +++ b/src/ui/widget/progress_bar.c @@ -0,0 +1,58 @@ +#include "progress_bar.h" +#include "../app.h" +#include +#include +#include + +ui_progress_bar_t *init_ui_progress_bar(WINDOW **win, widget_t *w_parent) { + WINDOW *win_par = *(w_parent->w_parent->parent_win); + ui_progress_bar_t *pb = malloc(sizeof(ui_progress_bar_t)); + init_widget(&pb->w, w_parent, win, ""); + pb->procent = 0; + pb->text[0] = '\0'; + pb->w.y = 4; + pb->w.x = getmaxx(win_par) / 10 * 9; + return pb; +} + +void draw_ui_progress_bar(ui_progress_bar_t *pb) { + WINDOW *win = *(pb->w.w_parent->parent_win); + uint32_t margin_y = pb->w.m_y + pb->w.w_parent->m_y; + uint32_t margin_x = pb->w.m_x + pb->w.w_parent->m_x + 1; + + wattrset(win, COLOR_PAIR(modal_color_pair)); + + mvwhline(win, margin_y, margin_x, 0, pb->w.x - 1); + + float procent = pb->procent; + procent /= 100; + uint32_t filled_num = procent * pb->w.x; + if (filled_num > 2) { + filled_num -= 2; + } + + for (int i = 1; i < 3; i++) { + wattrset(win, COLOR_PAIR(1) | A_REVERSE); + mvwprintw(win, margin_y + i, margin_x + 1, "%*s", filled_num, ""); + } + + wattrset(win, COLOR_PAIR(modal_color_pair)); + + // bottom line + mvwhline(win, margin_y + 3, margin_x, 0, pb->w.x - 1); + // left + mvwvline(win, margin_y, margin_x, ACS_ULCORNER, 1); + mvwvline(win, margin_y + 1, margin_x, 0, 1); + mvwvline(win, margin_y + 2, margin_x, 0, 1); + mvwvline(win, margin_y + 3, margin_x, ACS_LLCORNER, 1); + // right + mvwvline(win, margin_y, margin_x + pb->w.x - 2, ACS_URCORNER, 1); + mvwvline(win, margin_y + 1, margin_x + pb->w.x - 2, 0, 1); + mvwvline(win, margin_y + 2, margin_x + pb->w.x - 2, 0, 1); + mvwvline(win, margin_y + 3, margin_x + pb->w.x - 2, ACS_LRCORNER, 1); +} + +void destroy_ui_progress_bar(void *_pb) { + ui_progress_bar_t *pb = (ui_progress_bar_t *)_pb; + free(pb); +} \ No newline at end of file diff --git a/src/ui/widget/progress_bar.h b/src/ui/widget/progress_bar.h new file mode 100644 index 0000000..4279451 --- /dev/null +++ b/src/ui/widget/progress_bar.h @@ -0,0 +1,12 @@ +#ifndef PROGRESS_BAR_H +#define PROGRESS_BAR_H +#include "../widget.h" +typedef struct { + widget_t w; + uint32_t procent; + char text[24]; +} ui_progress_bar_t; +void destroy_ui_progress_bar(void *_pb); +void draw_ui_progress_bar(ui_progress_bar_t *pb); +ui_progress_bar_t *init_ui_progress_bar(WINDOW **win, widget_t *w_parent); +#endif \ No newline at end of file