From c524ece546df1daefb94f1449215a9da48c43db9 Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Wed, 26 Nov 2025 17:57:34 +0200 Subject: [PATCH 01/45] ncurses template --- Makefile | 10 ++++- TODO.md | 1 + src/main.c | 87 ++++++++++++++++++++++++++++++++++------ src/ui/app.c | 109 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/ui/app.h | 32 +++++++++++++++ 5 files changed, 225 insertions(+), 14 deletions(-) create mode 100644 src/ui/app.c create mode 100644 src/ui/app.h diff --git a/Makefile b/Makefile index 9491643..9dc6ff5 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,9 +7,15 @@ BIN_DIR := bin SRCS := $(wildcard $(SRC_DIR)/*.c) OBJS := $(SRCS:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o) +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)/main.h $(CC) $(CFLAGS) -c $< -o $@ +$(OBJ_DIR)/ui/%.o: $(SRC_DIR)/ui/%.c $(SRC_DIR)/ui/%.h + $(CC) $(CFLAGS) -c $< -o $@ + bbs-client: $(OBJS) $(CC) $(CFLAGS) $^ -o $(BIN_DIR)/$@ @@ -21,7 +27,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 $(BIN_DIR)/bbs-client run: $(BIN_DIR)/bbs-client diff --git a/TODO.md b/TODO.md index 349a124..5c3d246 100644 --- a/TODO.md +++ b/TODO.md @@ -1,2 +1,3 @@ TODO: +- [ ] More universal Makefile - [ ] Check and fix registration and login \ No newline at end of file diff --git a/src/main.c b/src/main.c index 4240e1f..43a80c3 100644 --- a/src/main.c +++ b/src/main.c @@ -1,23 +1,86 @@ #include "main.h" #include #include +#include #include #include #include #include #include -/* TODO: перед загрузкой файла спрашивать права доступа - TODO: сделать при интерфес выбора по пунктам меню */ +#include "ui/app.h" +#include + +void event_loop(app_t *app); + +int main(int argc, char **argv) { + app_t *app; + app = calloc(1, sizeof(app_t)); + + init_nc(); + + app = init_app(); + + wrefresh(app->menu_win); + wrefresh(app->action_win); + + event_loop(app); + + destroy_app(app); + endwin(); -int main(int argc, char *argv[]) { - params_t params; - init_params(¶ms); - analyze_args(argc, argv, ¶ms); - get_missing_params(¶ms); - int sd = init_client(); - connect_to_server(sd, ¶ms); - query_loop(sd, ¶ms); - clear_params(¶ms); return 0; -} \ No newline at end of file +} + +void event_loop(app_t *app) { + int c; + + for (;;) { + c = wgetch(app->win); + + switch (c) { + // 113 is q key. This exits the program + case 113: + return; + case KEY_F(9): + return; + case 'e': + // ac_edit(app); + break; + // 27 is ESC key. Usage of ESC is not recommended, but for now this clears + // the menus + case 27: + // app->menu_bar->selected_menu = -1; + break; + // 9 or \t is horizontal tab + case 9: + // if (app->active == N_MENU) { + // app->active = N_ACTION; + // } else { + // app->active = N_MENU; + // } + break; + default: + break; + } + + draw_borders(app); + + wnoutrefresh(app->win); + wnoutrefresh(app->menu_win); + wnoutrefresh(app->action_win); + doupdate(); + }; +} + +// int main(int argc, char *argv[]) { +// params_t params; +// init_params(¶ms); +// analyze_args(argc, argv, ¶ms); +// get_missing_params(¶ms); +// int sd = init_client(); +// connect_to_server(sd, ¶ms); +// query_loop(sd, ¶ms); +// clear_params(¶ms); +// return 0; +// } \ No newline at end of file diff --git a/src/ui/app.c b/src/ui/app.c new file mode 100644 index 0000000..5dc7c0e --- /dev/null +++ b/src/ui/app.c @@ -0,0 +1,109 @@ +#include "app.h" +// #include "action.h" +#include + +void ac_file(WINDOW *win, int is_action_w); + +app_t *init_app() { + // get values from terminal size + int 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->cur_y = y_max; + _app->cur_x = x_max; + + _app->win = newwin(_app->cur_y, _app->cur_x, 0, 0); + + // define the width for each sub window + int menu_w_x = x_max / 6; + int action_w_x = x_max - menu_w_x - 2; + + // create the menu window + _app->menu_win = newwin(y_max - 4, menu_w_x, 2, 1); + + // create the action window + _app->action_win = newwin(y_max - 4, action_w_x, 2, menu_w_x + 1); + + // dialogue + _app->modal.win = NULL; + + // print decorative bars + print_bars(_app); + + // here goes box borders + draw_borders(_app); + + // init menus +// _app->menus = malloc(3 * sizeof(menu_t)); +// _app->menus[0] = init_menu("(F)ile", 'f'); +// _app->menus[1] = init_menu("(E)dit", 'e'); +// _app->menus[2] = init_menu("(A)bout", 'a'); + + // set active window by default +// _app->active = N_MENU; + + // call the action window content by default +// ac_file(_app->action_win, _app->active); + + // refresh the windows + wnoutrefresh(_app->win); + wnoutrefresh(_app->menu_win); + wnoutrefresh(_app->action_win); + doupdate(); + + return _app; +} + +void init_nc() { + initscr(); + noecho(); + curs_set(0); + keypad(stdscr, TRUE); + + 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); + } +} + +void draw_borders(app_t *app) { + clear(); + box(app->win, 0, 0); + + // refresh the windows + wnoutrefresh(app->win); + wnoutrefresh(app->menu_win); + wnoutrefresh(app->action_win); + doupdate(); +} + +void print_bars(app_t *app) { + wattrset(app->win, A_REVERSE); + + // print top and bottom bars + for (int i = 1; i < app->cur_x - 1; i++) { + mvwprintw(app->win, 1, i, " "); + mvwprintw(app->win, app->cur_y - 2, i, " "); + } + + // add content to the top bar + mvwprintw(app->win, 1, 2, "example curses app | "); + mvwprintw(app->win, 1, 20, "size %d, %d", app->cur_x, app->cur_y); + + // add content to the bottom bar + mvwprintw(app->win, app->cur_y - 2, 2, "F1 - Help | F9 - Quit"); + + wattroff(app->win, A_REVERSE); +} + +void destroy_app(app_t *app) { + delwin(app->win); + free(app); +} diff --git a/src/ui/app.h b/src/ui/app.h new file mode 100644 index 0000000..9f4b37e --- /dev/null +++ b/src/ui/app.h @@ -0,0 +1,32 @@ +#ifndef N_APP_H +#define N_APP_H + +#include + +typedef enum { + none = 0, + login = 1, +} dialogue_e; + +typedef struct { + WINDOW *win; + WINDOW *menu_win; + WINDOW *action_win; + struct { + dialogue_e active; + WINDOW *win; + } modal; + + // menu_bar_t *menu_bar; + // menu_t *menus; + // active_win_e active; + int cur_y, cur_x; +} app_t; + +app_t *init_app(); +void init_nc(); +void destroy_app(app_t *app); +void draw_borders(app_t *app); +void print_bars(app_t *app); + +#endif // N_APP_H \ No newline at end of file From fd73fa7628fa9b37213094b8890b0b511ea4336d Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Thu, 27 Nov 2025 17:19:02 +0200 Subject: [PATCH 02/45] refactoring; modal window init --- Makefile | 5 +++- src/client.c | 2 ++ src/client.h | 8 +++++++ src/connection.h | 13 +++++++++++ src/deps.mk | 18 +++++++++++++++ src/file_processor.c | 8 +++---- src/file_processor.h | 13 +++++++++++ src/main.c | 11 +++++++++ src/main.h | 34 ++++++---------------------- src/modal.c | 20 ++++++++++++++++ src/modal.h | 9 ++++++++ src/query.c | 8 +++++-- src/query.h | 10 ++++++++ src/server.c | 4 +++- src/server.h | 8 +++++++ src/ui.c | 1 - src/ui/app.c | 20 ++++++++-------- src/ui/app.h | 9 ++++++-- src/ui/widget.c | 0 src/ui/widget.h | 16 +++++++++++++ src/ui/widget/button.h | 15 ++++++++++++ src/ui/widget/dialogue.c | 49 ++++++++++++++++++++++++++++++++++++++++ src/ui/widget/dialogue.h | 18 +++++++++++++++ 23 files changed, 252 insertions(+), 47 deletions(-) create mode 100644 src/client.h create mode 100644 src/connection.h create mode 100644 src/file_processor.h create mode 100644 src/modal.c create mode 100644 src/modal.h create mode 100644 src/query.h create mode 100644 src/server.h create mode 100644 src/ui/widget.c create mode 100644 src/ui/widget.h create mode 100644 src/ui/widget/button.h create mode 100644 src/ui/widget/dialogue.c create mode 100644 src/ui/widget/dialogue.h diff --git a/Makefile b/Makefile index 9dc6ff5..ae8def9 100644 --- a/Makefile +++ b/Makefile @@ -10,12 +10,15 @@ OBJS := $(SRCS:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o) 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)/main.h +$(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 $@ + bbs-client: $(OBJS) $(CC) $(CFLAGS) $^ -o $(BIN_DIR)/$@ diff --git a/src/client.c b/src/client.c index 059dadc..74db12b 100644 --- a/src/client.c +++ b/src/client.c @@ -1,4 +1,6 @@ +#include "file_processor.h" #include "main.h" +#include "client.h" #include #include #include diff --git a/src/client.h b/src/client.h new file mode 100644 index 0000000..6b599d7 --- /dev/null +++ b/src/client.h @@ -0,0 +1,8 @@ +#ifndef CLIENT_H +#define CLIENT_H +#include "main.h" + +int process_client_command(char *line, int l_len, query_args_t *q_args); +void print_prompt(params_t *params); + +#endif \ No newline at end of file diff --git a/src/connection.h b/src/connection.h new file mode 100644 index 0000000..3f8df6e --- /dev/null +++ b/src/connection.h @@ -0,0 +1,13 @@ +#ifndef CONNECTION_H +#define CONNECTION_H +#include "main.h" + +void connect_to_server(int sd, params_t *params); +void init_params(params_t *restrict); +void clear_params(params_t *params); +void get_missing_params(params_t *restrict); +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..27694cd 100644 --- a/src/deps.mk +++ b/src/deps.mk @@ -0,0 +1,18 @@ +client.o: src/client.c src/file_processor.h src/main.h src/types.h \ + src/client.h +connection.o: src/connection.c src/main.h src/types.h +file_processor.o: src/file_processor.c src/client.h src/main.h \ + src/types.h src/query.h +main.o: src/main.c src/main.h src/types.h src/modal.h src/ui/app.h \ + src/ui/widget/dialogue.h src/ui/widget/../widget.h \ + src/ui/widget/../../main.h src/ui/widget/button.h \ + src/ui/widget/../../main.h src/ui/widget/dialogue.h +modal.o: src/modal.c src/ui/app.h src/ui/widget/dialogue.h \ + src/ui/widget/../widget.h src/ui/widget/../../main.h \ + src/ui/widget/../../types.h src/ui/widget/button.h \ + src/ui/widget/../../main.h +query.o: src/query.c src/file_processor.h src/main.h src/types.h \ + src/connection.h src/client.h src/server.h +server.o: src/server.c src/client.h src/main.h src/types.h \ + src/connection.h src/query.h src/ui.h +ui.o: src/ui.c diff --git a/src/file_processor.c b/src/file_processor.c index dd4e5f7..685dbea 100644 --- a/src/file_processor.c +++ b/src/file_processor.c @@ -1,4 +1,6 @@ +#include "client.h" #include "main.h" +#include "query.h" #include #include #include @@ -58,17 +60,16 @@ void file_list(file_args_t *f_args, query_args_t *q_args) { 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; fl_clear(&f_args->l_start, &f_args->l_current); @@ -240,7 +241,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)); diff --git a/src/file_processor.h b/src/file_processor.h new file mode 100644 index 0000000..149b12b --- /dev/null +++ b/src/file_processor.h @@ -0,0 +1,13 @@ +#ifndef FILE_PROCESSOR_H +#define FILE_PROCESSOR_H +#include "main.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); + +#endif \ No newline at end of file diff --git a/src/main.c b/src/main.c index 43a80c3..7cf8086 100644 --- a/src/main.c +++ b/src/main.c @@ -9,6 +9,7 @@ #include #include "ui/app.h" +#include "ui/widget/dialogue.h" #include void event_loop(app_t *app); @@ -24,6 +25,11 @@ int main(int argc, char **argv) { wrefresh(app->menu_win); wrefresh(app->action_win); + // temporal + app->modal.active = login; + dialogue_t *dialogue = init_dialogue("Login"); + draw_dialogue(dialogue, app->cur_x, app->cur_y); + event_loop(app); destroy_app(app); @@ -38,6 +44,11 @@ void event_loop(app_t *app) { for (;;) { c = wgetch(app->win); + // if (app->modal.active != none_active) { + // modal_event_loop(c, app); + // continue; + // } + switch (c) { // 113 is q key. This exits the program case 113: diff --git a/src/main.h b/src/main.h index 59e8f72..3f66ab6 100644 --- a/src/main.h +++ b/src/main.h @@ -1,3 +1,6 @@ +#ifndef MAIN_H +#define MAIN_H + #include #include "types.h" @@ -6,6 +9,7 @@ #define LISTEN_QLEN 32 #define INBUFSIZE 1024 #define DOWNLOADS_DIR "./Downloads" +#define DIALOGUE_TITLE 64 typedef struct params { unsigned short port; @@ -29,8 +33,9 @@ enum state { STATE_UPLOAD_FILE, STATE_UPLOAD_REQUESTED, STATE_ASK_USER_BEFORE_LOGIN, - ERR, + STATE_ERR, }; + typedef struct p_file { char *name; size_t size; @@ -63,29 +68,4 @@ typedef struct query_args { params_t *params; } 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/modal.c b/src/modal.c new file mode 100644 index 0000000..ef7d8c9 --- /dev/null +++ b/src/modal.c @@ -0,0 +1,20 @@ +#include "ui/app.h" +#include + +int32_t m_login(char evt_c, app_t *app); + +int32_t modal_event_loop(char evt_c, app_t *app) { + switch (app->modal.active) { + + case none_active: + return 0; + case login: + m_login(evt_c, app); + break; + } + return 1; +} + +int32_t m_login(char evt_c, app_t *app) { + +} \ No newline at end of file diff --git a/src/modal.h b/src/modal.h new file mode 100644 index 0000000..ff8cd07 --- /dev/null +++ b/src/modal.h @@ -0,0 +1,9 @@ +#ifndef MODAL_H +#define MODAL_H + +#include "ui/app.h" +#include + +int32_t modal_event_loop(char evt_c, app_t *app); + +#endif \ No newline at end of file diff --git a/src/query.c b/src/query.c index 2b38272..fc25c27 100644 --- a/src/query.c +++ b/src/query.c @@ -1,4 +1,8 @@ +#include "file_processor.h" +#include "connection.h" +#include "client.h" #include "main.h" +#include "server.h" #include #include #include @@ -19,6 +23,7 @@ static void init_query_args(query_args_t *q_args, int sd, char *buf, enum state state, int buf_used, params_t *params); 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); void query_loop(int sd, params_t *params) { fd_set readfds; @@ -26,7 +31,6 @@ void query_loop(int sd, params_t *params) { size_t qlen; int sr; char buf[INBUFSIZE]; - char *bufs = buf; static file_args_t file_args; static query_args_t query_args; @@ -114,7 +118,7 @@ int process_query(query_args_t *query_args, file_args_t *file_args) { wait_side(query_args); break; case UPLOAD_FILE: - case ERR: + case STATE_ERR: break; case STATE_FILE_LIST: file_list(file_args, query_args); diff --git a/src/query.h b/src/query.h new file mode 100644 index 0000000..c0d2576 --- /dev/null +++ b/src/query.h @@ -0,0 +1,10 @@ +#ifndef QUERY_H +#define QUERY_H + +#include "main.h" +void query_loop(int sd, params_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); + +#endif \ No newline at end of file diff --git a/src/server.c b/src/server.c index aa30c80..9581c2f 100644 --- a/src/server.c +++ b/src/server.c @@ -1,4 +1,7 @@ +#include "client.h" +#include "connection.h" #include "main.h" +#include "query.h" #include "ui.h" #include #include @@ -19,7 +22,6 @@ 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; char *cptr = strchr(line, ' '); 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 index 836650d..1581b3b 100644 --- a/src/ui.c +++ b/src/ui.c @@ -3,7 +3,6 @@ #include uint32_t print_ask_list(char title[], const char *options[]) { - char *option; char c; uint32_t i, answer; diff --git a/src/ui/app.c b/src/ui/app.c index 5dc7c0e..b8b6212 100644 --- a/src/ui/app.c +++ b/src/ui/app.c @@ -29,7 +29,7 @@ app_t *init_app() { _app->action_win = newwin(y_max - 4, action_w_x, 2, menu_w_x + 1); // dialogue - _app->modal.win = NULL; + _app->modal.dialogue.win = NULL; // print decorative bars print_bars(_app); @@ -38,16 +38,16 @@ app_t *init_app() { draw_borders(_app); // init menus -// _app->menus = malloc(3 * sizeof(menu_t)); -// _app->menus[0] = init_menu("(F)ile", 'f'); -// _app->menus[1] = init_menu("(E)dit", 'e'); -// _app->menus[2] = init_menu("(A)bout", 'a'); + // _app->menus = malloc(3 * sizeof(menu_t)); + // _app->menus[0] = init_menu("(F)ile", 'f'); + // _app->menus[1] = init_menu("(E)dit", 'e'); + // _app->menus[2] = init_menu("(A)bout", 'a'); // set active window by default -// _app->active = N_MENU; + // _app->active = N_MENU; // call the action window content by default -// ac_file(_app->action_win, _app->active); + // ac_file(_app->action_win, _app->active); // refresh the windows wnoutrefresh(_app->win); @@ -70,6 +70,7 @@ void init_nc() { 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); } } @@ -94,8 +95,9 @@ void print_bars(app_t *app) { } // add content to the top bar - mvwprintw(app->win, 1, 2, "example curses app | "); - mvwprintw(app->win, 1, 20, "size %d, %d", app->cur_x, app->cur_y); + mvwprintw(app->win, 1, 2, "example cprint_barsurses app | "); + mvwprintw(app->win, 1, sizeof "example cprint_barsurses app | " + 1, + "size %d, %d", app->cur_x, app->cur_y); // add content to the bottom bar mvwprintw(app->win, app->cur_y - 2, 2, "F1 - Help | F9 - Quit"); diff --git a/src/ui/app.h b/src/ui/app.h index 9f4b37e..71af5a5 100644 --- a/src/ui/app.h +++ b/src/ui/app.h @@ -2,9 +2,14 @@ #define N_APP_H #include +#include "widget/dialogue.h" typedef enum { - none = 0, + MODAL_COLOR_PAIR = 3, +} color; + +typedef enum { + none_active = 0, login = 1, } dialogue_e; @@ -14,7 +19,7 @@ typedef struct { WINDOW *action_win; struct { dialogue_e active; - WINDOW *win; + dialogue_t dialogue; } modal; // menu_bar_t *menu_bar; diff --git a/src/ui/widget.c b/src/ui/widget.c new file mode 100644 index 0000000..e69de29 diff --git a/src/ui/widget.h b/src/ui/widget.h new file mode 100644 index 0000000..726e473 --- /dev/null +++ b/src/ui/widget.h @@ -0,0 +1,16 @@ +#ifndef WIDGET_H +#define WIDGET_H + +#include "../main.h" +#include + +typedef struct { + char title[DIALOGUE_TITLE]; + uint32_t x; + uint32_t y; + uint32_t m_x; + uint32_t m_y; + char color_pair; +} widget_t; + +#endif \ 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..524c0e7 --- /dev/null +++ b/src/ui/widget/button.h @@ -0,0 +1,15 @@ +#ifndef BUTTON_H +#define BUTTON_H + +#include "../../main.h" +#include "../widget.h" +#include + +typedef struct { + widget_t widget; + char value; + uint32_t is_focused : 1; + uint32_t is_disabled : 1; +} button_t; + +#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..414e6cd --- /dev/null +++ b/src/ui/widget/dialogue.c @@ -0,0 +1,49 @@ +#include "dialogue.h" +#include "../app.h" +#include +#include +#include +#include + +dialogue_t *init_dialogue(char title[]) { + dialogue_t *dialogue = malloc(sizeof(dialogue_t)); + dialogue->win = 0; + dialogue->buttons = NULL; + strcpy(dialogue->w.title, title); + return dialogue; +} + +int32_t draw_dialogue(dialogue_t *d, uint32_t cur_x, uint32_t cur_y) { + d->w.x = 50; + d->w.y = 20; + d->w.m_x = (cur_x - d->w.x) / 2; + d->w.m_y = (cur_y - d->w.y) / 2; + + d->win = newwin(d->w.y, d->w.x, d->w.m_y, d->w.m_x); + + wcolor_set(d->win, MODAL_COLOR_PAIR, NULL); + wattrset(d->win, A_REVERSE); + + for (int xp = 1; xp < d->w.x; xp++) { + for (int yp = 1; yp < d->w.y - 1; yp++) { + mvwprintw(d->win, yp, xp, " "); + } + } + + 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, d->w.title); + mvwprintw(d->win, 0, title_mx_pos + strlen(d->w.title), " "); + wattroff(d->win, A_REVERSE); + wrefresh(d->win); + + return 0; +}; + +void destroy_active_dialogue(dialogue_t *d) { + delwin(d->win); + free(d->win); + d->win = NULL; +} \ 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..9e2cd4e --- /dev/null +++ b/src/ui/widget/dialogue.h @@ -0,0 +1,18 @@ +#ifndef DIALOGUE_H +#define DIALOGUE_H + +#include +#include "../widget.h" +#include "button.h" + +typedef struct { + WINDOW *win; + widget_t w; + button_t *buttons; // ! should be NULL-terminated +} dialogue_t; + +dialogue_t *init_dialogue(char title[]); +int32_t draw_dialogue(dialogue_t *d, uint32_t cur_x, uint32_t cur_y); +void destroy_active_dialogue(dialogue_t *d); + +#endif \ No newline at end of file From b9d378a3a6718be5e6f6deb5d6971ba61d9e0c7e Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Fri, 28 Nov 2025 13:59:55 +0200 Subject: [PATCH 03/45] dialogue color improvement, implementation of print_multiline_text function. --- src/deps.mk | 2 +- src/main.c | 5 ++- src/main.h | 1 + src/ui/app.h | 1 + src/ui/widget.c | 66 ++++++++++++++++++++++++++++++++++++++++ src/ui/widget.h | 10 ++++++ src/ui/widget/dialogue.c | 26 ++++++++++------ src/ui/widget/dialogue.h | 5 +-- 8 files changed, 102 insertions(+), 14 deletions(-) diff --git a/src/deps.mk b/src/deps.mk index 27694cd..7a21821 100644 --- a/src/deps.mk +++ b/src/deps.mk @@ -3,7 +3,7 @@ client.o: src/client.c src/file_processor.h src/main.h src/types.h \ connection.o: src/connection.c src/main.h src/types.h file_processor.o: src/file_processor.c src/client.h src/main.h \ src/types.h src/query.h -main.o: src/main.c src/main.h src/types.h src/modal.h src/ui/app.h \ +main.o: src/main.c src/main.h src/types.h src/ui/app.h \ src/ui/widget/dialogue.h src/ui/widget/../widget.h \ src/ui/widget/../../main.h src/ui/widget/button.h \ src/ui/widget/../../main.h src/ui/widget/dialogue.h diff --git a/src/main.c b/src/main.c index 7cf8086..697875e 100644 --- a/src/main.c +++ b/src/main.c @@ -27,7 +27,10 @@ int main(int argc, char **argv) { // temporal app->modal.active = login; - dialogue_t *dialogue = init_dialogue("Login"); + dialogue_t *dialogue = init_dialogue("Login", + "Select your login " + "option", + 50, 20); draw_dialogue(dialogue, app->cur_x, app->cur_y); event_loop(app); diff --git a/src/main.h b/src/main.h index 3f66ab6..715e7c2 100644 --- a/src/main.h +++ b/src/main.h @@ -10,6 +10,7 @@ #define INBUFSIZE 1024 #define DOWNLOADS_DIR "./Downloads" #define DIALOGUE_TITLE 64 +#define DIALOGUE_TEXT 128 typedef struct params { unsigned short port; diff --git a/src/ui/app.h b/src/ui/app.h index 71af5a5..af9a141 100644 --- a/src/ui/app.h +++ b/src/ui/app.h @@ -6,6 +6,7 @@ typedef enum { MODAL_COLOR_PAIR = 3, + MODAL_BORDER_COLOR_PAIR = 4, } color; typedef enum { diff --git a/src/ui/widget.c b/src/ui/widget.c index e69de29..61c22fa 100644 --- a/src/ui/widget.c +++ b/src/ui/widget.c @@ -0,0 +1,66 @@ +#include "widget.h" +#include "../main.h" +#include +#include +#include + +int32_t get_max_line_len(const char *text) { + 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 + 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; + } + } + 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]; + + if (attrs & PMT_POS_CENTER) { + m_line_len = get_max_line_len(text); + } + + 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) + + return 1; +} \ No newline at end of file diff --git a/src/ui/widget.h b/src/ui/widget.h index 726e473..110b48e 100644 --- a/src/ui/widget.h +++ b/src/ui/widget.h @@ -2,6 +2,7 @@ #define WIDGET_H #include "../main.h" +#include #include typedef struct { @@ -13,4 +14,13 @@ typedef struct { char color_pair; } widget_t; +enum pmt_attrs { + PMT_POS_CENTER = 01, + PMT_ALIGN_CENTER = 03, +}; + +int32_t get_max_line_len(const char *text); +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/dialogue.c b/src/ui/widget/dialogue.c index 414e6cd..c9b5a3e 100644 --- a/src/ui/widget/dialogue.c +++ b/src/ui/widget/dialogue.c @@ -5,37 +5,43 @@ #include #include -dialogue_t *init_dialogue(char title[]) { +dialogue_t *init_dialogue(const char title[], const char text[], + const uint32_t width, const uint32_t height) { dialogue_t *dialogue = malloc(sizeof(dialogue_t)); dialogue->win = 0; dialogue->buttons = NULL; + dialogue->w.x = width; + dialogue->w.y = height; strcpy(dialogue->w.title, title); + strcpy(dialogue->text, text); return dialogue; } int32_t draw_dialogue(dialogue_t *d, uint32_t cur_x, uint32_t cur_y) { - d->w.x = 50; - d->w.y = 20; d->w.m_x = (cur_x - d->w.x) / 2; d->w.m_y = (cur_y - d->w.y) / 2; d->win = newwin(d->w.y, d->w.x, d->w.m_y, d->w.m_x); - - wcolor_set(d->win, MODAL_COLOR_PAIR, NULL); - wattrset(d->win, A_REVERSE); + color_set(MODAL_COLOR_PAIR, NULL); + wattrset(d->win, COLOR_PAIR(MODAL_COLOR_PAIR) | A_BOLD | A_REVERSE); + + /* background */ for (int xp = 1; xp < d->w.x; xp++) { for (int yp = 1; yp < d->w.y - 1; yp++) { mvwprintw(d->win, yp, xp, " "); } } - uint32_t title_mx_pos = (d->w.x - strlen(d->w.title)) / 2; - + /* 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, d->w.title); + 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 */ + print_multiline_text(d->win, d->text, d->w.x, 1, 1, PMT_ALIGN_CENTER); wattroff(d->win, A_REVERSE); wrefresh(d->win); diff --git a/src/ui/widget/dialogue.h b/src/ui/widget/dialogue.h index 9e2cd4e..7c0a20e 100644 --- a/src/ui/widget/dialogue.h +++ b/src/ui/widget/dialogue.h @@ -9,10 +9,11 @@ typedef struct { WINDOW *win; widget_t w; button_t *buttons; // ! should be NULL-terminated + char text[DIALOGUE_TEXT]; } dialogue_t; -dialogue_t *init_dialogue(char title[]); -int32_t draw_dialogue(dialogue_t *d, uint32_t cur_x, uint32_t cur_y); +dialogue_t *init_dialogue(const char title[], const char text[], const uint32_t w, const uint32_t h); +int32_t draw_dialogue(dialogue_t *d, const uint32_t cur_x, const uint32_t cur_y); void destroy_active_dialogue(dialogue_t *d); #endif \ No newline at end of file From 21bdd5f172df21566c78dc6e2af9a1770e67557e Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Mon, 1 Dec 2025 18:01:12 +0200 Subject: [PATCH 04/45] element groups: initial support, improves login dialog init --- Makefile | 3 ++ TODO.md | 2 +- src/deps.mk | 12 ++++--- src/main.c | 15 ++++----- src/ui/modals/login.c | 28 ++++++++++++++++ src/ui/modals/login.h | 7 ++++ src/ui/widget.h | 13 ++++++-- src/ui/widget/button.c | 38 ++++++++++++++++++++++ src/ui/widget/button.h | 9 ++++-- src/ui/widget/dialogue.c | 6 ++-- src/ui/widget/dialogue.h | 3 +- src/ui/widget/group.c | 69 ++++++++++++++++++++++++++++++++++++++++ src/ui/widget/group.h | 27 ++++++++++++++++ 13 files changed, 211 insertions(+), 21 deletions(-) create mode 100644 src/ui/modals/login.c create mode 100644 src/ui/modals/login.h create mode 100644 src/ui/widget/button.c create mode 100644 src/ui/widget/group.c create mode 100644 src/ui/widget/group.h diff --git a/Makefile b/Makefile index ae8def9..1904c76 100644 --- a/Makefile +++ b/Makefile @@ -19,6 +19,9 @@ $(OBJ_DIR)/ui/%.o: $(SRC_DIR)/ui/%.c $(SRC_DIR)/ui/%.h $(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) $(CC) $(CFLAGS) $^ -o $(BIN_DIR)/$@ diff --git a/TODO.md b/TODO.md index 5c3d246..341ab40 100644 --- a/TODO.md +++ b/TODO.md @@ -1,3 +1,3 @@ TODO: -- [ ] More universal Makefile +- [x] More universal Makefile - [ ] Check and fix registration and login \ No newline at end of file diff --git a/src/deps.mk b/src/deps.mk index 7a21821..22899d9 100644 --- a/src/deps.mk +++ b/src/deps.mk @@ -3,14 +3,16 @@ client.o: src/client.c src/file_processor.h src/main.h src/types.h \ connection.o: src/connection.c src/main.h src/types.h file_processor.o: src/file_processor.c src/client.h src/main.h \ src/types.h src/query.h -main.o: src/main.c src/main.h src/types.h src/ui/app.h \ - src/ui/widget/dialogue.h src/ui/widget/../widget.h \ - src/ui/widget/../../main.h src/ui/widget/button.h \ - src/ui/widget/../../main.h src/ui/widget/dialogue.h +main.o: src/main.c src/ui/app.h src/ui/widget/dialogue.h \ + src/ui/widget/../widget.h src/ui/widget/../../main.h \ + src/ui/widget/../../types.h src/ui/widget/button.h \ + src/ui/widget/../../main.h src/ui/widget/group.h \ + src/ui/widget/dialogue.h src/ui/modals/login.h \ + src/ui/modals/../widget/dialogue.h modal.o: src/modal.c src/ui/app.h src/ui/widget/dialogue.h \ src/ui/widget/../widget.h src/ui/widget/../../main.h \ src/ui/widget/../../types.h src/ui/widget/button.h \ - src/ui/widget/../../main.h + src/ui/widget/../../main.h src/ui/widget/group.h query.o: src/query.c src/file_processor.h src/main.h src/types.h \ src/connection.h src/client.h src/server.h server.o: src/server.c src/client.h src/main.h src/types.h \ diff --git a/src/main.c b/src/main.c index 697875e..7848d12 100644 --- a/src/main.c +++ b/src/main.c @@ -1,16 +1,17 @@ -#include "main.h" -#include -#include #include +#include #include #include +#include #include #include #include +#include #include "ui/app.h" #include "ui/widget/dialogue.h" -#include +#include "ui/modals/login.h" + void event_loop(app_t *app); @@ -27,10 +28,8 @@ int main(int argc, char **argv) { // temporal app->modal.active = login; - dialogue_t *dialogue = init_dialogue("Login", - "Select your login " - "option", - 50, 20); + + dialogue_t *dialogue = init_login_modal(); draw_dialogue(dialogue, app->cur_x, app->cur_y); event_loop(app); diff --git a/src/ui/modals/login.c b/src/ui/modals/login.c new file mode 100644 index 0000000..9ea4adf --- /dev/null +++ b/src/ui/modals/login.c @@ -0,0 +1,28 @@ +#include "../widget/dialogue.h" +#include "../widget/group.h" +#include + +typedef struct { + button_t *element; + enum w_type type; +} group_bt_t; + +/* TODO: improve init process (make it more readable) */ + +dialogue_t *init_login_modal() { + group_el_t children[] = {{.type = w_button}, + {.type = w_button}, + {.type = w_button}, + {.type = w_end}}; + + dialogue_t *d = init_dialogue("Login", + "Select your login " + "option", + 50, 20); + d->ch_group = init_group(children); + group_bt_t *ch_btns = (group_bt_t *)d->ch_group->elements; + strcpy(ch_btns[0].element->w.title, "Anonymous"); + strcpy(ch_btns[1].element->w.title, "User"); + strcpy(ch_btns[2].element->w.title, "Register"); + return d; +} \ No newline at end of file diff --git a/src/ui/modals/login.h b/src/ui/modals/login.h new file mode 100644 index 0000000..54cefcd --- /dev/null +++ b/src/ui/modals/login.h @@ -0,0 +1,7 @@ +#ifndef LOGIN_MODAL_H +#define LOGIN_MODAL_H + +#include "../widget/dialogue.h" +dialogue_t *init_login_modal(); + +#endif \ No newline at end of file diff --git a/src/ui/widget.h b/src/ui/widget.h index 110b48e..3fb6539 100644 --- a/src/ui/widget.h +++ b/src/ui/widget.h @@ -2,16 +2,23 @@ #define WIDGET_H #include "../main.h" -#include #include +#include + +enum w_type { + w_button, + w_box, + w_group, + w_end, +}; typedef struct { + uint32_t id; char title[DIALOGUE_TITLE]; uint32_t x; uint32_t y; uint32_t m_x; uint32_t m_y; - char color_pair; } widget_t; enum pmt_attrs { @@ -23,4 +30,6 @@ int32_t get_max_line_len(const char *text); 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); + +typedef void (*callback_t) (widget_t*, void*); #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..2328d43 --- /dev/null +++ b/src/ui/widget/button.c @@ -0,0 +1,38 @@ +#include "button.h" +#include +#include +#include + +button_t *init_button() { + button_t *btn = malloc(sizeof(button_t)); + btn->is_disabled = 0; + btn->is_focused = 0; + btn->is_hovered = 0; + btn->callback = NULL; + return btn; +} + +int32_t draw_button(WINDOW *win, button_t *btn, uint32_t cur_x, uint32_t cur_y) { + char title[DIALOGUE_TITLE + 6]; + + if (btn->is_focused) { + sprintf(title, "[< %s >]", btn->w.title); + } else { + sprintf(title, "[ %s ]", btn->w.title); + } + + if (btn->is_hovered) { + wattrset(win, COLOR_PAIR(1) | A_BOLD | A_REVERSE); + } else { + wattrset(win, COLOR_PAIR(1) | A_BOLD); + } + + mvwprintw(win, cur_y, cur_x, "%s", title); + + wattroff(win, A_REVERSE | A_BOLD); + 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 index 524c0e7..398c637 100644 --- a/src/ui/widget/button.h +++ b/src/ui/widget/button.h @@ -6,10 +6,15 @@ #include typedef struct { - widget_t widget; - char value; + widget_t w; + char hotkey; uint32_t is_focused : 1; uint32_t is_disabled : 1; + uint32_t is_hovered : 1; + callback_t *callback; } button_t; +button_t *init_button(); +int32_t draw_button(WINDOW *win, button_t *btn, uint32_t cur_x, uint32_t cur_y); +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 index c9b5a3e..413aa6a 100644 --- a/src/ui/widget/dialogue.c +++ b/src/ui/widget/dialogue.c @@ -1,5 +1,6 @@ #include "dialogue.h" #include "../app.h" +#include "group.h" #include #include #include @@ -9,7 +10,7 @@ dialogue_t *init_dialogue(const char title[], const char text[], const uint32_t width, const uint32_t height) { dialogue_t *dialogue = malloc(sizeof(dialogue_t)); dialogue->win = 0; - dialogue->buttons = NULL; + dialogue->ch_group = NULL; dialogue->w.x = width; dialogue->w.y = height; strcpy(dialogue->w.title, title); @@ -23,7 +24,6 @@ int32_t draw_dialogue(dialogue_t *d, uint32_t cur_x, uint32_t cur_y) { d->win = newwin(d->w.y, d->w.x, d->w.m_y, d->w.m_x); - color_set(MODAL_COLOR_PAIR, NULL); wattrset(d->win, COLOR_PAIR(MODAL_COLOR_PAIR) | A_BOLD | A_REVERSE); /* background */ @@ -43,6 +43,8 @@ int32_t draw_dialogue(dialogue_t *d, uint32_t cur_x, uint32_t cur_y) { /* text */ print_multiline_text(d->win, d->text, d->w.x, 1, 1, PMT_ALIGN_CENTER); wattroff(d->win, A_REVERSE); + // temp!!! + draw_group(d->win, d->ch_group, 1, 4); wrefresh(d->win); return 0; diff --git a/src/ui/widget/dialogue.h b/src/ui/widget/dialogue.h index 7c0a20e..242b147 100644 --- a/src/ui/widget/dialogue.h +++ b/src/ui/widget/dialogue.h @@ -4,11 +4,12 @@ #include #include "../widget.h" #include "button.h" +#include "group.h" typedef struct { WINDOW *win; widget_t w; - button_t *buttons; // ! should be NULL-terminated + group_t *ch_group; char text[DIALOGUE_TEXT]; } dialogue_t; diff --git a/src/ui/widget/group.c b/src/ui/widget/group.c new file mode 100644 index 0000000..3a24288 --- /dev/null +++ b/src/ui/widget/group.c @@ -0,0 +1,69 @@ +#include "group.h" +#include "button.h" +#include +#include +#include +#include + +group_t *init_group(group_el_t *children) { + group_t *group = malloc(sizeof(group_t)); + + /* count elements */ + uint32_t count = 0; + for (count = 0; children[count].type != w_end; count++) + ; + count++; /* TODO: improve counting */ + group->elements = malloc(sizeof(group_el_t) * count); + memcpy(group->elements, children, sizeof(group_el_t) * count); + + /* init child elements */ + for (int i = 0; group->elements[i].type != w_end; i++) { + group_el_t *el = &(group->elements)[i]; + switch (el->type) { + case w_button: + el->element = init_button(); + break; + case w_box: + case w_group: + case w_end: + break; + } + } + group->direction = horizontal; /* horizontal by default */ + return group; +} + +void draw_group(WINDOW *win, group_t *group, uint32_t cur_x, uint32_t cur_y) { + group_el_t *children = group->elements; + int32_t x_used = 0; /* used space */ + for (int i = 0; children[i].type != w_end; i++) { + group_el_t *el = &children[i]; + switch (el->type) { + case w_button: + x_used += draw_button(win, (button_t *)el->element, cur_x + x_used + 1 * i, cur_y); + break; + case w_box: + case w_group: + case w_end: + break; + } + } +} + +void destroy_group(group_t *group) { + group_el_t *children = group->elements; + for (int i = 0; children[i].type != w_end; 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; + } + } + 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..93a6291 --- /dev/null +++ b/src/ui/widget/group.h @@ -0,0 +1,27 @@ +#ifndef GROUP_H +#define GROUP_H + +#include "../widget.h" + +enum g_direction { + horizontal, + vertical, +}; + +typedef struct { + void *element; + enum w_type type; +} group_el_t; + +typedef struct { + group_el_t *elements; + widget_t w; + uint32_t w_active_id; + enum g_direction direction; +} group_t; + +group_t *init_group(group_el_t *children); +void draw_group(WINDOW *win, group_t *group, uint32_t cur_x, uint32_t cur_y); +void destroy_group(group_t *group); + +#endif \ No newline at end of file From afbce332b33b817d5e0f28c66723cb6796e3d88d Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Mon, 1 Dec 2025 18:02:58 +0200 Subject: [PATCH 05/45] refreshes setup.sh --- setup.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 From 32b9628ff21e4e4df5cb17931cc07cb7dbc74814 Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Tue, 2 Dec 2025 17:11:27 +0200 Subject: [PATCH 06/45] updates widget positions --- TODO.md | 4 +++- src/deps.mk | 2 +- src/main.c | 6 ++++-- src/main.h | 1 + src/ui/modals/login.c | 29 ++++++++++++++++++----------- src/ui/modals/login.h | 4 +++- src/ui/widget.c | 13 +++++++++++++ src/ui/widget.h | 5 ++++- src/ui/widget/button.c | 27 ++++++++++++++++++--------- src/ui/widget/button.h | 4 ++-- src/ui/widget/dialogue.c | 13 +++++++++---- src/ui/widget/dialogue.h | 2 +- src/ui/widget/group.c | 36 +++++++++++++++++++++++------------- src/ui/widget/group.h | 10 ++++++++-- 14 files changed, 108 insertions(+), 48 deletions(-) diff --git a/TODO.md b/TODO.md index 341ab40..ce6f03e 100644 --- a/TODO.md +++ b/TODO.md @@ -1,3 +1,5 @@ TODO: - [x] More universal Makefile -- [ ] Check and fix registration and login \ No newline at end of file +- [ ] Check and fix registration and login +- [x] UI: destroy child elements everywhere +- [ ] use parent widgets pointers to count the margins correctly \ No newline at end of file diff --git a/src/deps.mk b/src/deps.mk index 22899d9..386a9ca 100644 --- a/src/deps.mk +++ b/src/deps.mk @@ -8,7 +8,7 @@ main.o: src/main.c src/ui/app.h src/ui/widget/dialogue.h \ src/ui/widget/../../types.h src/ui/widget/button.h \ src/ui/widget/../../main.h src/ui/widget/group.h \ src/ui/widget/dialogue.h src/ui/modals/login.h \ - src/ui/modals/../widget/dialogue.h + src/ui/modals/../widget/dialogue.h src/ui/modals/../app.h modal.o: src/modal.c src/ui/app.h src/ui/widget/dialogue.h \ src/ui/widget/../widget.h src/ui/widget/../../main.h \ src/ui/widget/../../types.h src/ui/widget/button.h \ diff --git a/src/main.c b/src/main.c index 7848d12..69ee61f 100644 --- a/src/main.c +++ b/src/main.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -12,6 +13,7 @@ #include "ui/widget/dialogue.h" #include "ui/modals/login.h" +uint32_t m_id = 0; void event_loop(app_t *app); @@ -29,8 +31,8 @@ int main(int argc, char **argv) { // temporal app->modal.active = login; - dialogue_t *dialogue = init_login_modal(); - draw_dialogue(dialogue, app->cur_x, app->cur_y); + init_login_modal(app); + draw_dialogue(app, &(app->modal.dialogue), app->cur_x, app->cur_y); event_loop(app); diff --git a/src/main.h b/src/main.h index 715e7c2..9aed41c 100644 --- a/src/main.h +++ b/src/main.h @@ -2,6 +2,7 @@ #define MAIN_H #include +#include #include "types.h" #define LOCAL_PORT 1999 diff --git a/src/ui/modals/login.c b/src/ui/modals/login.c index 9ea4adf..112dff2 100644 --- a/src/ui/modals/login.c +++ b/src/ui/modals/login.c @@ -1,5 +1,7 @@ +#include "../app.h" #include "../widget/dialogue.h" #include "../widget/group.h" +#include #include typedef struct { @@ -9,20 +11,25 @@ typedef struct { /* TODO: improve init process (make it more readable) */ -dialogue_t *init_login_modal() { - group_el_t children[] = {{.type = w_button}, - {.type = w_button}, - {.type = w_button}, - {.type = w_end}}; +dialogue_t *init_login_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}}; dialogue_t *d = init_dialogue("Login", "Select your login " "option", 50, 20); - d->ch_group = init_group(children); - group_bt_t *ch_btns = (group_bt_t *)d->ch_group->elements; - strcpy(ch_btns[0].element->w.title, "Anonymous"); - strcpy(ch_btns[1].element->w.title, "User"); - strcpy(ch_btns[2].element->w.title, "Register"); - return d; + + memcpy(&(app->modal.dialogue), d, sizeof(dialogue_t)); + free(d); + app->modal.dialogue.ch_group = + init_group(&(app->modal.dialogue.win), d->w.id, children, horizontal); + group_bt_t *ch_btns = (group_bt_t *)app->modal.dialogue.ch_group->elements; + ch_btns[0].element->is_hovered = 1; + + return NULL; } \ No newline at end of file diff --git a/src/ui/modals/login.h b/src/ui/modals/login.h index 54cefcd..a6d92e3 100644 --- a/src/ui/modals/login.h +++ b/src/ui/modals/login.h @@ -2,6 +2,8 @@ #define LOGIN_MODAL_H #include "../widget/dialogue.h" -dialogue_t *init_login_modal(); +#include "../app.h" + +dialogue_t *init_login_modal(app_t *app); #endif \ No newline at end of file diff --git a/src/ui/widget.c b/src/ui/widget.c index 61c22fa..582376f 100644 --- a/src/ui/widget.c +++ b/src/ui/widget.c @@ -4,6 +4,19 @@ #include #include +extern uint32_t m_id; + +void init_widget(widget_t *w, WINDOW **win, char *title, uint32_t parent_id) { + w->id = m_id++; + w->x = 0; + w->y = 0; + w->m_x = 0; + w->m_y = 0; + w->parent_win = win; + w->parent_id = parent_id; + strcpy(w->title, title); +} + int32_t get_max_line_len(const char *text) { uint32_t nl_pos = 0; // new line position uint32_t c_start = 0; // current line start position diff --git a/src/ui/widget.h b/src/ui/widget.h index 3fb6539..41334d3 100644 --- a/src/ui/widget.h +++ b/src/ui/widget.h @@ -6,10 +6,10 @@ #include enum w_type { + w_end, w_button, w_box, w_group, - w_end, }; typedef struct { @@ -19,6 +19,8 @@ typedef struct { uint32_t y; uint32_t m_x; uint32_t m_y; + WINDOW **parent_win; + uint32_t parent_id; } widget_t; enum pmt_attrs { @@ -26,6 +28,7 @@ enum pmt_attrs { PMT_ALIGN_CENTER = 03, }; +void init_widget(widget_t *w, WINDOW **win, char *title, uint32_t parent_id); int32_t get_max_line_len(const char *text); uint32_t print_multiline_text(WINDOW *win, const char *text, const uint32_t win_width, const uint32_t y, diff --git a/src/ui/widget/button.c b/src/ui/widget/button.c index 2328d43..e590c35 100644 --- a/src/ui/widget/button.c +++ b/src/ui/widget/button.c @@ -1,19 +1,30 @@ -#include "button.h" #include +#include #include #include -button_t *init_button() { +#include "../app.h" +#include "../widget.h" +#include "button.h" + +button_t *init_button(WINDOW **win, uint32_t parent_id, char *label) { button_t *btn = malloc(sizeof(button_t)); + init_widget(&(btn->w), win, label, parent_id); btn->is_disabled = 0; btn->is_focused = 0; btn->is_hovered = 0; btn->callback = NULL; + btn->w.x = strlen(btn->w.title) + 4; // + "[ ]" + btn->w.y = 1; // TODO: detect new line + if (btn->is_focused) btn->w.x += 2; // + "<>" return btn; } -int32_t draw_button(WINDOW *win, button_t *btn, uint32_t cur_x, uint32_t cur_y) { +int32_t draw_button(button_t *btn) { char title[DIALOGUE_TITLE + 6]; + WINDOW *win = *(btn->w.parent_win); + uint32_t cur_y = btn->w.m_y; + uint32_t cur_x = btn->w.m_x; if (btn->is_focused) { sprintf(title, "[< %s >]", btn->w.title); @@ -22,17 +33,15 @@ int32_t draw_button(WINDOW *win, button_t *btn, uint32_t cur_x, uint32_t cur_y) } if (btn->is_hovered) { - wattrset(win, COLOR_PAIR(1) | A_BOLD | A_REVERSE); + wattrset(win, COLOR_PAIR(MODAL_COLOR_PAIR) | A_BOLD | A_REVERSE); } else { - wattrset(win, COLOR_PAIR(1) | A_BOLD); + wattrset(win, COLOR_PAIR(0) | A_REVERSE); } mvwprintw(win, cur_y, cur_x, "%s", title); - wattroff(win, A_REVERSE | A_BOLD); + wattroff(win, A_BOLD | A_REVERSE); return strlen(title); } -void destroy_button(button_t *btn) { - free(btn); -} \ No newline at end of file +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 index 398c637..84a270e 100644 --- a/src/ui/widget/button.h +++ b/src/ui/widget/button.h @@ -14,7 +14,7 @@ typedef struct { callback_t *callback; } button_t; -button_t *init_button(); -int32_t draw_button(WINDOW *win, button_t *btn, uint32_t cur_x, uint32_t cur_y); +button_t *init_button(WINDOW **win, uint32_t parent_id, char *label); +int32_t draw_button(button_t *btn); 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 index 413aa6a..b8a12b8 100644 --- a/src/ui/widget/dialogue.c +++ b/src/ui/widget/dialogue.c @@ -18,13 +18,14 @@ dialogue_t *init_dialogue(const char title[], const char text[], return dialogue; } -int32_t draw_dialogue(dialogue_t *d, uint32_t cur_x, uint32_t cur_y) { +int32_t draw_dialogue(void *app, dialogue_t *d, uint32_t cur_x, uint32_t cur_y) { + app_t *_app = (app_t *) app; d->w.m_x = (cur_x - d->w.x) / 2; d->w.m_y = (cur_y - d->w.y) / 2; d->win = newwin(d->w.y, d->w.x, d->w.m_y, d->w.m_x); - wattrset(d->win, COLOR_PAIR(MODAL_COLOR_PAIR) | A_BOLD | A_REVERSE); + wattrset(d->win, COLOR_PAIR(0) | A_BOLD | A_REVERSE); /* background */ for (int xp = 1; xp < d->w.x; xp++) { @@ -43,14 +44,18 @@ int32_t draw_dialogue(dialogue_t *d, uint32_t cur_x, uint32_t cur_y) { /* text */ print_multiline_text(d->win, d->text, d->w.x, 1, 1, PMT_ALIGN_CENTER); wattroff(d->win, A_REVERSE); - // temp!!! - draw_group(d->win, d->ch_group, 1, 4); + d->ch_group->w.m_x = (d->w.x - d->ch_group->w.x) / 2; + d->ch_group->w.m_y = 4; + draw_group(d->win, d->ch_group); wrefresh(d->win); return 0; }; void destroy_active_dialogue(dialogue_t *d) { + if (d->ch_group) { + destroy_group(d->ch_group); + } delwin(d->win); free(d->win); d->win = NULL; diff --git a/src/ui/widget/dialogue.h b/src/ui/widget/dialogue.h index 242b147..babf27f 100644 --- a/src/ui/widget/dialogue.h +++ b/src/ui/widget/dialogue.h @@ -14,7 +14,7 @@ typedef struct { } dialogue_t; dialogue_t *init_dialogue(const char title[], const char text[], const uint32_t w, const uint32_t h); -int32_t draw_dialogue(dialogue_t *d, const uint32_t cur_x, const uint32_t cur_y); +int32_t draw_dialogue(void *app, dialogue_t *d, const uint32_t cur_x, const uint32_t cur_y); void destroy_active_dialogue(dialogue_t *d); #endif \ No newline at end of file diff --git a/src/ui/widget/group.c b/src/ui/widget/group.c index 3a24288..0ca9bb8 100644 --- a/src/ui/widget/group.c +++ b/src/ui/widget/group.c @@ -2,45 +2,55 @@ #include "button.h" #include #include -#include #include -group_t *init_group(group_el_t *children) { +group_t *init_group(WINDOW **win, uint32_t par_id, group_el_init_t *children, + enum g_direction direction) { group_t *group = malloc(sizeof(group_t)); /* count elements */ uint32_t count = 0; - for (count = 0; children[count].type != w_end; count++) + for (; children[count].type != w_end; count++) ; count++; /* TODO: improve counting */ - group->elements = malloc(sizeof(group_el_t) * count); - memcpy(group->elements, children, sizeof(group_el_t) * count); + group->elements = calloc(count, sizeof(group_el_t)); + group_el_t *elements = group->elements; + for (int i = 0; i < count; i++) { + elements[i].type = children[i].type; + } /* init child elements */ - for (int i = 0; group->elements[i].type != w_end; i++) { - group_el_t *el = &(group->elements)[i]; - switch (el->type) { + for (int i = 0; elements[i].type != w_end; i++) { + switch (elements[i].type) { case w_button: - el->element = init_button(); + elements[i].element = init_button(win, par_id, children[i].label); + if (direction == horizontal) { + button_t *b = (button_t *)elements[i].element; + b->w.m_x = group->w.x; + b->w.m_y = group->w.m_y; + group->w.x += b->w.x + 1; + if (group->w.y < b->w.y) + group->w.y = b->w.y; + } break; case w_box: case w_group: case w_end: + group->w.x--; break; } } - group->direction = horizontal; /* horizontal by default */ + return group; } -void draw_group(WINDOW *win, group_t *group, uint32_t cur_x, uint32_t cur_y) { +void draw_group(WINDOW *win, group_t *group) { group_el_t *children = group->elements; - int32_t x_used = 0; /* used space */ for (int i = 0; children[i].type != w_end; i++) { group_el_t *el = &children[i]; switch (el->type) { case w_button: - x_used += draw_button(win, (button_t *)el->element, cur_x + x_used + 1 * i, cur_y); + draw_button((button_t *)el->element); break; case w_box: case w_group: diff --git a/src/ui/widget/group.h b/src/ui/widget/group.h index 93a6291..44f05f5 100644 --- a/src/ui/widget/group.h +++ b/src/ui/widget/group.h @@ -13,6 +13,11 @@ typedef struct { enum w_type type; } group_el_t; +typedef struct { + enum w_type type; + char label[DIALOGUE_TITLE]; +} group_el_init_t; + typedef struct { group_el_t *elements; widget_t w; @@ -20,8 +25,9 @@ typedef struct { enum g_direction direction; } group_t; -group_t *init_group(group_el_t *children); -void draw_group(WINDOW *win, group_t *group, uint32_t cur_x, uint32_t cur_y); +group_t *init_group(WINDOW **win, uint32_t par_id, group_el_init_t *children, + enum g_direction dir); +void draw_group(WINDOW *win, group_t *group); void destroy_group(group_t *group); #endif \ No newline at end of file From 24f51eb22bb4c7c85abddfd860f8ebd5be9d524a Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Wed, 3 Dec 2025 12:14:38 +0200 Subject: [PATCH 07/45] dialogue: count dimensions correctly --- TODO.md | 2 +- src/main.c | 2 +- src/ui/modals/login.c | 6 ++--- src/ui/widget.c | 11 +++++--- src/ui/widget.h | 10 +++---- src/ui/widget/button.c | 10 +++---- src/ui/widget/button.h | 2 +- src/ui/widget/dialogue.c | 57 +++++++++++++++++++++++++++++----------- src/ui/widget/dialogue.h | 6 ++--- src/ui/widget/group.c | 8 +++--- src/ui/widget/group.h | 2 +- 11 files changed, 73 insertions(+), 43 deletions(-) diff --git a/TODO.md b/TODO.md index ce6f03e..f8a36eb 100644 --- a/TODO.md +++ b/TODO.md @@ -2,4 +2,4 @@ TODO: - [x] More universal Makefile - [ ] Check and fix registration and login - [x] UI: destroy child elements everywhere -- [ ] use parent widgets pointers to count the margins correctly \ No newline at end of file +- [x] use parent widgets pointers to count the margins correctly \ No newline at end of file diff --git a/src/main.c b/src/main.c index 69ee61f..f4eee3a 100644 --- a/src/main.c +++ b/src/main.c @@ -32,7 +32,7 @@ int main(int argc, char **argv) { app->modal.active = login; init_login_modal(app); - draw_dialogue(app, &(app->modal.dialogue), app->cur_x, app->cur_y); + draw_dialogue(app, &(app->modal.dialogue)); event_loop(app); diff --git a/src/ui/modals/login.c b/src/ui/modals/login.c index 112dff2..b4f05fd 100644 --- a/src/ui/modals/login.c +++ b/src/ui/modals/login.c @@ -1,6 +1,7 @@ #include "../app.h" #include "../widget/dialogue.h" #include "../widget/group.h" +#include #include #include @@ -21,13 +22,12 @@ dialogue_t *init_login_modal(app_t *app) { dialogue_t *d = init_dialogue("Login", "Select your login " - "option", - 50, 20); + "option"); memcpy(&(app->modal.dialogue), d, sizeof(dialogue_t)); free(d); app->modal.dialogue.ch_group = - init_group(&(app->modal.dialogue.win), d->w.id, children, horizontal); + init_group(&(app->modal.dialogue.win), &(d->w), children, horizontal); group_bt_t *ch_btns = (group_bt_t *)app->modal.dialogue.ch_group->elements; ch_btns[0].element->is_hovered = 1; diff --git a/src/ui/widget.c b/src/ui/widget.c index 582376f..f8df5d2 100644 --- a/src/ui/widget.c +++ b/src/ui/widget.c @@ -6,22 +6,23 @@ extern uint32_t m_id; -void init_widget(widget_t *w, WINDOW **win, char *title, uint32_t parent_id) { +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->parent_id = parent_id; + w->w_parent = w_parent; strcpy(w->title, title); } -int32_t get_max_line_len(const char *text) { +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) { @@ -30,8 +31,10 @@ int32_t get_max_line_len(const char *text) { 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 { @@ -62,7 +65,7 @@ uint32_t print_multiline_text(WINDOW *win, const char *text, char l_buf[DIALOGUE_TEXT]; if (attrs & PMT_POS_CENTER) { - m_line_len = get_max_line_len(text); + m_line_len = get_max_line_len(text, NULL); } for (; text[i]; i++, c_line_len++) { diff --git a/src/ui/widget.h b/src/ui/widget.h index 41334d3..fbef381 100644 --- a/src/ui/widget.h +++ b/src/ui/widget.h @@ -12,15 +12,15 @@ enum w_type { w_group, }; -typedef struct { +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; - WINDOW **parent_win; - uint32_t parent_id; + WINDOW *const* parent_win; + struct widget_t *w_parent; } widget_t; enum pmt_attrs { @@ -28,8 +28,8 @@ enum pmt_attrs { PMT_ALIGN_CENTER = 03, }; -void init_widget(widget_t *w, WINDOW **win, char *title, uint32_t parent_id); -int32_t get_max_line_len(const char *text); +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); diff --git a/src/ui/widget/button.c b/src/ui/widget/button.c index e590c35..0ef5218 100644 --- a/src/ui/widget/button.c +++ b/src/ui/widget/button.c @@ -7,9 +7,9 @@ #include "../widget.h" #include "button.h" -button_t *init_button(WINDOW **win, uint32_t parent_id, char *label) { +button_t *init_button(WINDOW **win, widget_t *w_parent, char *label) { button_t *btn = malloc(sizeof(button_t)); - init_widget(&(btn->w), win, label, parent_id); + init_widget(&(btn->w), w_parent, win, label); btn->is_disabled = 0; btn->is_focused = 0; btn->is_hovered = 0; @@ -23,8 +23,8 @@ button_t *init_button(WINDOW **win, uint32_t parent_id, char *label) { int32_t draw_button(button_t *btn) { char title[DIALOGUE_TITLE + 6]; WINDOW *win = *(btn->w.parent_win); - uint32_t cur_y = btn->w.m_y; - uint32_t cur_x = btn->w.m_x; + 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->is_focused) { sprintf(title, "[< %s >]", btn->w.title); @@ -38,7 +38,7 @@ int32_t draw_button(button_t *btn) { wattrset(win, COLOR_PAIR(0) | A_REVERSE); } - mvwprintw(win, cur_y, cur_x, "%s", title); + mvwprintw(win, margin_y, margin_x, "%s", title); wattroff(win, A_BOLD | A_REVERSE); return strlen(title); diff --git a/src/ui/widget/button.h b/src/ui/widget/button.h index 84a270e..492fbe8 100644 --- a/src/ui/widget/button.h +++ b/src/ui/widget/button.h @@ -14,7 +14,7 @@ typedef struct { callback_t *callback; } button_t; -button_t *init_button(WINDOW **win, uint32_t parent_id, char *label); +button_t *init_button(WINDOW **win, widget_t *w_parent, char *label); int32_t draw_button(button_t *btn); 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 index b8a12b8..763a318 100644 --- a/src/ui/widget/dialogue.c +++ b/src/ui/widget/dialogue.c @@ -1,30 +1,58 @@ -#include "dialogue.h" -#include "../app.h" -#include "group.h" #include #include #include #include +#include -dialogue_t *init_dialogue(const char title[], const char text[], - const uint32_t width, const uint32_t height) { +#include "../app.h" +#include "dialogue.h" +#include "group.h" + +dialogue_t *init_dialogue(const char title[], const char text[]) { dialogue_t *dialogue = malloc(sizeof(dialogue_t)); dialogue->win = 0; dialogue->ch_group = NULL; - dialogue->w.x = width; - dialogue->w.y = height; + dialogue->w.x = 0; + dialogue->w.y = 0; strcpy(dialogue->w.title, title); strcpy(dialogue->text, text); return dialogue; } -int32_t draw_dialogue(void *app, dialogue_t *d, uint32_t cur_x, uint32_t cur_y) { - app_t *_app = (app_t *) app; - d->w.m_x = (cur_x - d->w.x) / 2; - d->w.m_y = (cur_y - d->w.y) / 2; +int32_t draw_dialogue(void *_app, dialogue_t *d) { + app_t *app = (app_t *)_app; - d->win = newwin(d->w.y, d->w.x, d->w.m_y, d->w.m_x); + /* count dimensions */ + uint32_t x = 1; /* when uses box */ + uint32_t y = 1; /* when uses box */ + 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 */ + + if (d->ch_group) { + d->ch_group->w.m_y = y; + if (line_max_len < d->ch_group->w.x) { + line_max_len = d->ch_group->w.x; + d->ch_group->w.m_x = 1; /* when uses box */ + } else { + /* TODO: centering */ + } + y += d->ch_group->w.y; + } + 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_x = (app->cur_x - x) / 2; + d->w.m_y = (app->cur_y - y) / 2; + + /* render window */ + d->win = newwin(y, x, d->w.m_y, d->w.m_x); wattrset(d->win, COLOR_PAIR(0) | A_BOLD | A_REVERSE); /* background */ @@ -42,10 +70,9 @@ int32_t draw_dialogue(void *app, dialogue_t *d, uint32_t cur_x, uint32_t cur_y) mvwprintw(d->win, 0, title_mx_pos + strlen(d->w.title), " "); /* text */ - print_multiline_text(d->win, d->text, d->w.x, 1, 1, PMT_ALIGN_CENTER); + wattroff(d->win, A_BOLD); + print_multiline_text(d->win, d->text, d->w.x, 2, 1, PMT_ALIGN_CENTER); wattroff(d->win, A_REVERSE); - d->ch_group->w.m_x = (d->w.x - d->ch_group->w.x) / 2; - d->ch_group->w.m_y = 4; draw_group(d->win, d->ch_group); wrefresh(d->win); diff --git a/src/ui/widget/dialogue.h b/src/ui/widget/dialogue.h index babf27f..37ce0a5 100644 --- a/src/ui/widget/dialogue.h +++ b/src/ui/widget/dialogue.h @@ -1,10 +1,10 @@ #ifndef DIALOGUE_H #define DIALOGUE_H -#include #include "../widget.h" #include "button.h" #include "group.h" +#include typedef struct { WINDOW *win; @@ -13,8 +13,8 @@ typedef struct { char text[DIALOGUE_TEXT]; } dialogue_t; -dialogue_t *init_dialogue(const char title[], const char text[], const uint32_t w, const uint32_t h); -int32_t draw_dialogue(void *app, dialogue_t *d, const uint32_t cur_x, const uint32_t cur_y); +dialogue_t *init_dialogue(const char title[], const char text[]); +int32_t draw_dialogue(void *app, dialogue_t *d); void destroy_active_dialogue(dialogue_t *d); #endif \ No newline at end of file diff --git a/src/ui/widget/group.c b/src/ui/widget/group.c index 0ca9bb8..d600e86 100644 --- a/src/ui/widget/group.c +++ b/src/ui/widget/group.c @@ -4,7 +4,7 @@ #include #include -group_t *init_group(WINDOW **win, uint32_t par_id, group_el_init_t *children, +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)); @@ -12,8 +12,8 @@ group_t *init_group(WINDOW **win, uint32_t par_id, group_el_init_t *children, uint32_t count = 0; for (; children[count].type != w_end; count++) ; - count++; /* TODO: improve counting */ group->elements = calloc(count, sizeof(group_el_t)); + group->w.w_parent = w_parent; group_el_t *elements = group->elements; for (int i = 0; i < count; i++) { elements[i].type = children[i].type; @@ -23,7 +23,7 @@ group_t *init_group(WINDOW **win, uint32_t par_id, group_el_init_t *children, for (int i = 0; elements[i].type != w_end; i++) { switch (elements[i].type) { case w_button: - elements[i].element = init_button(win, par_id, children[i].label); + elements[i].element = init_button(win, &(group->w), children[i].label); if (direction == horizontal) { button_t *b = (button_t *)elements[i].element; b->w.m_x = group->w.x; @@ -40,7 +40,7 @@ group_t *init_group(WINDOW **win, uint32_t par_id, group_el_init_t *children, break; } } - + return group; } diff --git a/src/ui/widget/group.h b/src/ui/widget/group.h index 44f05f5..4e32872 100644 --- a/src/ui/widget/group.h +++ b/src/ui/widget/group.h @@ -25,7 +25,7 @@ typedef struct { enum g_direction direction; } group_t; -group_t *init_group(WINDOW **win, uint32_t par_id, group_el_init_t *children, +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); void destroy_group(group_t *group); From eee2bc0ccb847c7c3f9d9fadfd924a48b87e9b52 Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Wed, 3 Dec 2025 14:40:53 +0200 Subject: [PATCH 08/45] initial callback support --- src/deps.mk | 6 +++--- src/main.c | 39 ++++++++++++--------------------------- src/ui/modals/login.c | 7 ++----- src/ui/widget.c | 1 + src/ui/widget.h | 2 +- src/ui/widget/button.c | 16 ++++------------ src/ui/widget/button.h | 5 +---- src/ui/widget/dialogue.c | 20 +++++++++++++++++++- src/ui/widget/dialogue.h | 1 + src/ui/widget/group.c | 27 +++++++++++++++++---------- src/ui/widget/group.h | 2 ++ 11 files changed, 63 insertions(+), 63 deletions(-) diff --git a/src/deps.mk b/src/deps.mk index 386a9ca..76352f4 100644 --- a/src/deps.mk +++ b/src/deps.mk @@ -6,9 +6,9 @@ file_processor.o: src/file_processor.c src/client.h src/main.h \ main.o: src/main.c src/ui/app.h src/ui/widget/dialogue.h \ src/ui/widget/../widget.h src/ui/widget/../../main.h \ src/ui/widget/../../types.h src/ui/widget/button.h \ - src/ui/widget/../../main.h src/ui/widget/group.h \ - src/ui/widget/dialogue.h src/ui/modals/login.h \ - src/ui/modals/../widget/dialogue.h src/ui/modals/../app.h + src/ui/widget/../../main.h src/ui/widget/group.h src/ui/modals/login.h \ + src/ui/modals/../widget/dialogue.h src/ui/modals/../app.h \ + src/ui/widget/dialogue.h modal.o: src/modal.c src/ui/app.h src/ui/widget/dialogue.h \ src/ui/widget/../widget.h src/ui/widget/../../main.h \ src/ui/widget/../../types.h src/ui/widget/button.h \ diff --git a/src/main.c b/src/main.c index f4eee3a..7e51e9a 100644 --- a/src/main.c +++ b/src/main.c @@ -1,17 +1,17 @@ +#include +#include +#include #include #include -#include #include #include -#include #include #include #include -#include #include "ui/app.h" -#include "ui/widget/dialogue.h" #include "ui/modals/login.h" +#include "ui/widget/dialogue.h" uint32_t m_id = 0; @@ -30,7 +30,7 @@ int main(int argc, char **argv) { // temporal app->modal.active = login; - + init_login_modal(app); draw_dialogue(app, &(app->modal.dialogue)); @@ -47,35 +47,17 @@ void event_loop(app_t *app) { for (;;) { c = wgetch(app->win); - - // if (app->modal.active != none_active) { - // modal_event_loop(c, app); - // continue; - // } - switch (c) { // 113 is q key. This exits the program case 113: return; case KEY_F(9): return; - case 'e': - // ac_edit(app); - break; - // 27 is ESC key. Usage of ESC is not recommended, but for now this clears - // the menus - case 27: - // app->menu_bar->selected_menu = -1; - break; - // 9 or \t is horizontal tab - case 9: - // if (app->active == N_MENU) { - // app->active = N_ACTION; - // } else { - // app->active = N_MENU; - // } - break; default: + if (app->modal.active != none_active) { + app->modal.dialogue.w.callback(app->modal.dialogue.win, + &(app->modal.dialogue), (void *)&c); + } break; } @@ -84,6 +66,9 @@ void event_loop(app_t *app) { wnoutrefresh(app->win); wnoutrefresh(app->menu_win); wnoutrefresh(app->action_win); + if (app->modal.active != none_active) { + wnoutrefresh(app->modal.dialogue.win); + } doupdate(); }; } diff --git a/src/ui/modals/login.c b/src/ui/modals/login.c index b4f05fd..b1e9fb8 100644 --- a/src/ui/modals/login.c +++ b/src/ui/modals/login.c @@ -20,16 +20,13 @@ dialogue_t *init_login_modal(app_t *app) { {.type = w_button, .label = "Register"}, {.type = w_end}}; - dialogue_t *d = init_dialogue("Login", - "Select your login " - "option"); + dialogue_t *d = init_dialogue("Login", "Select your login " + "option"); memcpy(&(app->modal.dialogue), d, sizeof(dialogue_t)); free(d); app->modal.dialogue.ch_group = init_group(&(app->modal.dialogue.win), &(d->w), children, horizontal); - group_bt_t *ch_btns = (group_bt_t *)app->modal.dialogue.ch_group->elements; - ch_btns[0].element->is_hovered = 1; return NULL; } \ No newline at end of file diff --git a/src/ui/widget.c b/src/ui/widget.c index f8df5d2..492aba1 100644 --- a/src/ui/widget.c +++ b/src/ui/widget.c @@ -14,6 +14,7 @@ void init_widget(widget_t *w, widget_t *w_parent, WINDOW **win, char *title) { w->m_y = 0; w->parent_win = win; w->w_parent = w_parent; + w->callback = NULL; strcpy(w->title, title); } diff --git a/src/ui/widget.h b/src/ui/widget.h index fbef381..b88bb78 100644 --- a/src/ui/widget.h +++ b/src/ui/widget.h @@ -21,6 +21,7 @@ typedef struct widget_t { uint32_t m_y; WINDOW *const* parent_win; struct widget_t *w_parent; + void (*callback) (WINDOW *win, void *widget, void *data); } widget_t; enum pmt_attrs { @@ -34,5 +35,4 @@ 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); -typedef void (*callback_t) (widget_t*, void*); #endif \ No newline at end of file diff --git a/src/ui/widget/button.c b/src/ui/widget/button.c index 0ef5218..31b6117 100644 --- a/src/ui/widget/button.c +++ b/src/ui/widget/button.c @@ -11,30 +11,22 @@ 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->is_focused = 0; - btn->is_hovered = 0; - btn->callback = NULL; - btn->w.x = strlen(btn->w.title) + 4; // + "[ ]" + btn->w.x = strlen(btn->w.title) + 6; // + "[ ]" btn->w.y = 1; // TODO: detect new line - if (btn->is_focused) btn->w.x += 2; // + "<>" return btn; } -int32_t draw_button(button_t *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->is_focused) { + if (btn->w.id == active_id) { sprintf(title, "[< %s >]", btn->w.title); - } else { - sprintf(title, "[ %s ]", btn->w.title); - } - - if (btn->is_hovered) { 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); } diff --git a/src/ui/widget/button.h b/src/ui/widget/button.h index 492fbe8..ce44c57 100644 --- a/src/ui/widget/button.h +++ b/src/ui/widget/button.h @@ -8,13 +8,10 @@ typedef struct { widget_t w; char hotkey; - uint32_t is_focused : 1; uint32_t is_disabled : 1; - uint32_t is_hovered : 1; - callback_t *callback; } button_t; button_t *init_button(WINDOW **win, widget_t *w_parent, char *label); -int32_t draw_button(button_t *btn); +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 index 763a318..17075ad 100644 --- a/src/ui/widget/dialogue.c +++ b/src/ui/widget/dialogue.c @@ -8,12 +8,28 @@ #include "dialogue.h" #include "group.h" +void default_callback(WINDOW *win, void *widget, void *data) { + dialogue_t *d = (void *)widget; + char key = *((char *)data); + switch (key) { + case '\t': + if (d->ch_group->w_active_id != d->ch_group->last_id) { + d->ch_group->w_active_id = d->ch_group->w_active_id + 1; + } else { + d->ch_group->w_active_id = d->ch_group->first_id; + } + draw_group(win, d->ch_group); + break; + } +} + dialogue_t *init_dialogue(const char title[], const char text[]) { dialogue_t *dialogue = malloc(sizeof(dialogue_t)); dialogue->win = 0; dialogue->ch_group = NULL; dialogue->w.x = 0; dialogue->w.y = 0; + dialogue->w.callback = default_callback; strcpy(dialogue->w.title, title); strcpy(dialogue->text, text); return dialogue; @@ -52,7 +68,9 @@ int32_t draw_dialogue(void *_app, dialogue_t *d) { d->w.m_y = (app->cur_y - y) / 2; /* render window */ - d->win = newwin(y, x, d->w.m_y, d->w.m_x); + 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 */ diff --git a/src/ui/widget/dialogue.h b/src/ui/widget/dialogue.h index 37ce0a5..f2f5273 100644 --- a/src/ui/widget/dialogue.h +++ b/src/ui/widget/dialogue.h @@ -16,5 +16,6 @@ typedef struct { dialogue_t *init_dialogue(const char title[], const char text[]); int32_t draw_dialogue(void *app, dialogue_t *d); void destroy_active_dialogue(dialogue_t *d); +void default_callback(WINDOW *win, void *widget, void *data); #endif \ No newline at end of file diff --git a/src/ui/widget/group.c b/src/ui/widget/group.c index d600e86..6bdfd9c 100644 --- a/src/ui/widget/group.c +++ b/src/ui/widget/group.c @@ -9,23 +9,25 @@ group_t *init_group(WINDOW **win, widget_t *w_parent, group_el_init_t *children, group_t *group = malloc(sizeof(group_t)); /* count elements */ - uint32_t count = 0; - for (; children[count].type != w_end; count++) + group->count = 0; + for (; children[group->count].type != w_end; group->count++) ; - group->elements = calloc(count, sizeof(group_el_t)); + group->elements = calloc(group->count, sizeof(group_el_t)); group->w.w_parent = w_parent; group_el_t *elements = group->elements; - for (int i = 0; i < count; i++) { + for (int i = 0; i < group->count; i++) { elements[i].type = children[i].type; } /* init child elements */ - for (int i = 0; elements[i].type != w_end; i++) { + uint32_t el_id = 0; + for (int i = 0; i < group->count; i++) { switch (elements[i].type) { case w_button: elements[i].element = init_button(win, &(group->w), children[i].label); if (direction == horizontal) { button_t *b = (button_t *)elements[i].element; + el_id = b->w.id; b->w.m_x = group->w.x; b->w.m_y = group->w.m_y; group->w.x += b->w.x + 1; @@ -36,21 +38,26 @@ group_t *init_group(WINDOW **win, widget_t *w_parent, group_el_init_t *children, case w_box: case w_group: case w_end: - group->w.x--; break; } + if (i == 0) { + group->first_id = el_id; + group->w_active_id = el_id; + } else { + group->last_id = el_id; + } } - + return group; } void draw_group(WINDOW *win, group_t *group) { group_el_t *children = group->elements; - for (int i = 0; children[i].type != w_end; i++) { + 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); + draw_button((button_t *)el->element, group->w_active_id); break; case w_box: case w_group: @@ -62,7 +69,7 @@ void draw_group(WINDOW *win, group_t *group) { void destroy_group(group_t *group) { group_el_t *children = group->elements; - for (int i = 0; children[i].type != w_end; i++) { + for (int i = 0; i < group->count; i++) { group_el_t *el = &children[i]; switch (el->type) { case w_button: diff --git a/src/ui/widget/group.h b/src/ui/widget/group.h index 4e32872..f8029b4 100644 --- a/src/ui/widget/group.h +++ b/src/ui/widget/group.h @@ -23,6 +23,8 @@ typedef struct { widget_t w; uint32_t w_active_id; enum g_direction direction; + uint32_t count; + uint32_t first_id, last_id; } group_t; group_t *init_group(WINDOW **win, widget_t *w_parent, group_el_init_t *children, From 568876a7efc81c6f43ca3a9f2d2e1c1020e54790 Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Fri, 5 Dec 2025 16:16:51 +0200 Subject: [PATCH 09/45] callback system for ui widgets --- src/deps.mk | 12 ++++++------ src/main.c | 34 +++++++++++++++++++++------------- src/ui/modals/login.c | 25 +++++++++++++++++++++++-- src/ui/modals/login.h | 3 +++ src/ui/widget.h | 9 ++++++++- src/ui/widget/dialogue.c | 31 +++++++++++++------------------ src/ui/widget/dialogue.h | 9 ++++++--- src/ui/widget/group.c | 39 +++++++++++++++++++++++++++++++++++---- src/ui/widget/group.h | 11 ++++++++++- 9 files changed, 125 insertions(+), 48 deletions(-) diff --git a/src/deps.mk b/src/deps.mk index 76352f4..c761491 100644 --- a/src/deps.mk +++ b/src/deps.mk @@ -5,14 +5,14 @@ file_processor.o: src/file_processor.c src/client.h src/main.h \ src/types.h src/query.h main.o: src/main.c src/ui/app.h src/ui/widget/dialogue.h \ src/ui/widget/../widget.h src/ui/widget/../../main.h \ - src/ui/widget/../../types.h src/ui/widget/button.h \ - src/ui/widget/../../main.h src/ui/widget/group.h src/ui/modals/login.h \ - src/ui/modals/../widget/dialogue.h src/ui/modals/../app.h \ - src/ui/widget/dialogue.h + src/ui/widget/../../types.h src/ui/widget/../app.h \ + src/ui/widget/button.h src/ui/widget/../../main.h src/ui/widget/group.h \ + src/ui/modals/login.h src/ui/modals/../widget/dialogue.h \ + src/ui/modals/../app.h src/ui/widget/dialogue.h modal.o: src/modal.c src/ui/app.h src/ui/widget/dialogue.h \ src/ui/widget/../widget.h src/ui/widget/../../main.h \ - src/ui/widget/../../types.h src/ui/widget/button.h \ - src/ui/widget/../../main.h src/ui/widget/group.h + src/ui/widget/../../types.h src/ui/widget/../app.h \ + src/ui/widget/button.h src/ui/widget/../../main.h src/ui/widget/group.h query.o: src/query.c src/file_processor.h src/main.h src/types.h \ src/connection.h src/client.h src/server.h server.o: src/server.c src/client.h src/main.h src/types.h \ diff --git a/src/main.c b/src/main.c index 7e51e9a..405b3a1 100644 --- a/src/main.c +++ b/src/main.c @@ -16,6 +16,7 @@ uint32_t m_id = 0; void event_loop(app_t *app); +void app_refresh(app_t *app); int main(int argc, char **argv) { app_t *app; @@ -32,7 +33,8 @@ int main(int argc, char **argv) { app->modal.active = login; init_login_modal(app); - draw_dialogue(app, &(app->modal.dialogue)); + draw_dialogue(&(app->modal.dialogue)); + app_refresh(app); event_loop(app); @@ -45,6 +47,8 @@ int main(int argc, char **argv) { void event_loop(app_t *app) { int c; + callback_args_t d_args = {.app = app, .win = NULL}; + for (;;) { c = wgetch(app->win); switch (c) { @@ -55,24 +59,28 @@ void event_loop(app_t *app) { return; default: if (app->modal.active != none_active) { - app->modal.dialogue.w.callback(app->modal.dialogue.win, - &(app->modal.dialogue), (void *)&c); + d_args.win = app->modal.dialogue.win; + app->modal.dialogue.w.callback(&d_args, &(app->modal.dialogue), + (void *)&c, NULL); } break; } - - draw_borders(app); - - wnoutrefresh(app->win); - wnoutrefresh(app->menu_win); - wnoutrefresh(app->action_win); - if (app->modal.active != none_active) { - wnoutrefresh(app->modal.dialogue.win); - } - doupdate(); + app_refresh(app); }; } +void app_refresh(app_t *app) { + draw_borders(app); + + wnoutrefresh(app->win); + wnoutrefresh(app->menu_win); + wnoutrefresh(app->action_win); + if (app->modal.active != none_active) { + wnoutrefresh(app->modal.dialogue.win); + } + doupdate(); +} + // int main(int argc, char *argv[]) { // params_t params; // init_params(¶ms); diff --git a/src/ui/modals/login.c b/src/ui/modals/login.c index b1e9fb8..d15d31d 100644 --- a/src/ui/modals/login.c +++ b/src/ui/modals/login.c @@ -12,6 +12,24 @@ typedef struct { /* TODO: improve init process (make it more readable) */ +void group_callback(callback_args_t *args, void *widget, void *data, + void *resp_data) { + group_resp_t response; + callback_args_t ch_args = {.win = args->win, .app = NULL}; + group_default_callback(&ch_args, widget, data, (void *)&response); + if (response.value > -1) { + // TODO: finish actions + switch (response.value) { + case 0: + exit(10); + case 1: + exit(11); + case 2: + exit(12); + } + } +} + dialogue_t *init_login_modal(app_t *app) { if (app == NULL) return NULL; @@ -20,13 +38,16 @@ dialogue_t *init_login_modal(app_t *app) { {.type = w_button, .label = "Register"}, {.type = w_end}}; - dialogue_t *d = init_dialogue("Login", "Select your login " - "option"); + dialogue_t *d = init_dialogue("Login", + "Select your login " + "option", + app->cur_y, app->cur_x); memcpy(&(app->modal.dialogue), d, sizeof(dialogue_t)); free(d); app->modal.dialogue.ch_group = init_group(&(app->modal.dialogue.win), &(d->w), children, horizontal); + app->modal.dialogue.ch_group->w.callback = group_callback; return NULL; } \ No newline at end of file diff --git a/src/ui/modals/login.h b/src/ui/modals/login.h index a6d92e3..bd61a33 100644 --- a/src/ui/modals/login.h +++ b/src/ui/modals/login.h @@ -4,6 +4,9 @@ #include "../widget/dialogue.h" #include "../app.h" +typedef struct { + +} ilm_action_args; dialogue_t *init_login_modal(app_t *app); #endif \ No newline at end of file diff --git a/src/ui/widget.h b/src/ui/widget.h index b88bb78..eadc596 100644 --- a/src/ui/widget.h +++ b/src/ui/widget.h @@ -12,6 +12,11 @@ enum w_type { w_group, }; +typedef struct { + WINDOW *win; + void *app; +} callback_args_t; + typedef struct widget_t { uint32_t id; char title[DIALOGUE_TITLE]; @@ -21,9 +26,11 @@ typedef struct widget_t { uint32_t m_y; WINDOW *const* parent_win; struct widget_t *w_parent; - void (*callback) (WINDOW *win, void *widget, void *data); + void (*callback) (callback_args_t *args, void *widget, void *data, void *resp_data); } widget_t; +typedef void (*callback_t) (callback_args_t *args, void *widget, void *data, void *resp_data); + enum pmt_attrs { PMT_POS_CENTER = 01, PMT_ALIGN_CENTER = 03, diff --git a/src/ui/widget/dialogue.c b/src/ui/widget/dialogue.c index 17075ad..8ae7ec2 100644 --- a/src/ui/widget/dialogue.c +++ b/src/ui/widget/dialogue.c @@ -4,40 +4,36 @@ #include #include -#include "../app.h" #include "dialogue.h" #include "group.h" -void default_callback(WINDOW *win, void *widget, void *data) { + +void dialogue_default_callback(callback_args_t *args, void *widget, void *data, + void *callback) { dialogue_t *d = (void *)widget; char key = *((char *)data); switch (key) { - case '\t': - if (d->ch_group->w_active_id != d->ch_group->last_id) { - d->ch_group->w_active_id = d->ch_group->w_active_id + 1; - } else { - d->ch_group->w_active_id = d->ch_group->first_id; - } - draw_group(win, d->ch_group); - break; + default: + d->ch_group->w.callback(args, d->ch_group, data, callback); + draw_dialogue(d); } } -dialogue_t *init_dialogue(const char title[], const char text[]) { +dialogue_t *init_dialogue(const char title[], const char text[], uint32_t cur_y, uint32_t cur_x) { dialogue_t *dialogue = malloc(sizeof(dialogue_t)); dialogue->win = 0; dialogue->ch_group = NULL; dialogue->w.x = 0; dialogue->w.y = 0; - dialogue->w.callback = default_callback; + dialogue->cur_y = cur_y; + dialogue->cur_x = cur_x; + dialogue->w.callback = dialogue_default_callback; strcpy(dialogue->w.title, title); strcpy(dialogue->text, text); return dialogue; } -int32_t draw_dialogue(void *_app, dialogue_t *d) { - app_t *app = (app_t *)_app; - +int32_t draw_dialogue(dialogue_t *d) { /* count dimensions */ uint32_t x = 1; /* when uses box */ uint32_t y = 1; /* when uses box */ @@ -64,8 +60,8 @@ int32_t draw_dialogue(void *_app, dialogue_t *d) { y += 1; /* when uses box */ d->w.x = x; d->w.y = y; - d->w.m_x = (app->cur_x - x) / 2; - d->w.m_y = (app->cur_y - y) / 2; + d->w.m_y = (d->cur_y - y) / 2; + d->w.m_x = (d->cur_x - x) / 2; /* render window */ if (d->win == NULL) { @@ -92,7 +88,6 @@ int32_t draw_dialogue(void *_app, dialogue_t *d) { print_multiline_text(d->win, d->text, d->w.x, 2, 1, PMT_ALIGN_CENTER); wattroff(d->win, A_REVERSE); draw_group(d->win, d->ch_group); - wrefresh(d->win); return 0; }; diff --git a/src/ui/widget/dialogue.h b/src/ui/widget/dialogue.h index f2f5273..4ea3615 100644 --- a/src/ui/widget/dialogue.h +++ b/src/ui/widget/dialogue.h @@ -10,12 +10,15 @@ typedef struct { WINDOW *win; widget_t w; group_t *ch_group; + uint32_t cur_y; + uint32_t cur_x; char text[DIALOGUE_TEXT]; } dialogue_t; -dialogue_t *init_dialogue(const char title[], const char text[]); -int32_t draw_dialogue(void *app, dialogue_t *d); +dialogue_t *init_dialogue(const char title[], const char text[], uint32_t cur_x, + uint32_t cur_y); +int32_t draw_dialogue(dialogue_t *d); void destroy_active_dialogue(dialogue_t *d); -void default_callback(WINDOW *win, void *widget, void *data); +void dialogue_default_callback(callback_args_t *args, void *widget, void *data, void *); #endif \ No newline at end of file diff --git a/src/ui/widget/group.c b/src/ui/widget/group.c index 6bdfd9c..a728c18 100644 --- a/src/ui/widget/group.c +++ b/src/ui/widget/group.c @@ -4,6 +4,35 @@ #include #include +void group_default_callback(callback_args_t *args, void *widget, void *data, void *resp_data) { + group_t *g = (void *)widget; + char key = *((char *)data); + group_resp_t *response = (group_resp_t *) resp_data; + switch (key) { + case '\t': + if (g->active_id != g->last_id) { + g->active_id = g->active_id + 1; + } else { + g->active_id = g->first_id; + } + if (resp_data != NULL) { + response->active_id = g->active_id; + response->value = -1; + } + break; + case '\n': + if (resp_data != NULL) { + for (int i=0; i < g->count; i++) { + if (g->elements[i].id == g->active_id) { + response->active_id = g->active_id; + response->value = i; + } + } + } + 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)); @@ -14,6 +43,7 @@ group_t *init_group(WINDOW **win, widget_t *w_parent, group_el_init_t *children, ; group->elements = calloc(group->count, sizeof(group_el_t)); group->w.w_parent = w_parent; + group->w.callback = group_default_callback; group_el_t *elements = group->elements; for (int i = 0; i < group->count; i++) { elements[i].type = children[i].type; @@ -25,9 +55,10 @@ group_t *init_group(WINDOW **win, widget_t *w_parent, group_el_init_t *children, switch (elements[i].type) { case w_button: elements[i].element = init_button(win, &(group->w), children[i].label); + button_t *b = (button_t *)elements[i].element; + el_id = b->w.id; + elements[i].id = b->w.id; if (direction == horizontal) { - button_t *b = (button_t *)elements[i].element; - el_id = b->w.id; b->w.m_x = group->w.x; b->w.m_y = group->w.m_y; group->w.x += b->w.x + 1; @@ -42,7 +73,7 @@ group_t *init_group(WINDOW **win, widget_t *w_parent, group_el_init_t *children, } if (i == 0) { group->first_id = el_id; - group->w_active_id = el_id; + group->active_id = el_id; } else { group->last_id = el_id; } @@ -57,7 +88,7 @@ void draw_group(WINDOW *win, group_t *group) { group_el_t *el = &children[i]; switch (el->type) { case w_button: - draw_button((button_t *)el->element, group->w_active_id); + draw_button((button_t *)el->element, group->active_id); break; case w_box: case w_group: diff --git a/src/ui/widget/group.h b/src/ui/widget/group.h index f8029b4..9684a4b 100644 --- a/src/ui/widget/group.h +++ b/src/ui/widget/group.h @@ -2,6 +2,7 @@ #define GROUP_H #include "../widget.h" +#include enum g_direction { horizontal, @@ -9,6 +10,7 @@ enum g_direction { }; typedef struct { + uint32_t id; void *element; enum w_type type; } group_el_t; @@ -21,15 +23,22 @@ typedef struct { typedef struct { group_el_t *elements; widget_t w; - uint32_t w_active_id; + uint32_t active_id; enum g_direction direction; uint32_t count; uint32_t first_id, last_id; + void *action_args; } group_t; +typedef struct { + int32_t value; + uint32_t active_id; +} group_resp_t; + 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); void destroy_group(group_t *group); +void group_default_callback(callback_args_t *args, void *widget, void *data, void *resp_data); #endif \ No newline at end of file From c9a3dada2fd6929e378dd8a6b8bdce75f7c9993c Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Mon, 8 Dec 2025 17:02:20 +0200 Subject: [PATCH 10/45] finished modal callbacks system --- TODO.md | 3 +- src/deps.mk | 16 ++++------ src/main.c | 69 ++++++++++++++++++++++++++++------------ src/modal.c | 20 ------------ src/modal.h | 9 ------ src/ui/app.c | 26 +++++---------- src/ui/app.h | 13 +++----- src/ui/modals/login.c | 40 +++++++++++------------ src/ui/modals/login.h | 3 -- src/ui/widget.h | 12 ++++--- src/ui/widget/dialogue.c | 37 +++++++++++++-------- src/ui/widget/dialogue.h | 7 ++-- src/ui/widget/group.c | 21 +++++++----- src/ui/widget/group.h | 2 +- 14 files changed, 138 insertions(+), 140 deletions(-) delete mode 100644 src/modal.c delete mode 100644 src/modal.h diff --git a/TODO.md b/TODO.md index f8a36eb..50eb12d 100644 --- a/TODO.md +++ b/TODO.md @@ -2,4 +2,5 @@ TODO: - [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 \ No newline at end of file +- [x] use parent widgets pointers to count the margins correctly +- [ ] Hotkeys for buttons \ No newline at end of file diff --git a/src/deps.mk b/src/deps.mk index c761491..1b3488f 100644 --- a/src/deps.mk +++ b/src/deps.mk @@ -3,16 +3,12 @@ client.o: src/client.c src/file_processor.h src/main.h src/types.h \ connection.o: src/connection.c src/main.h src/types.h file_processor.o: src/file_processor.c src/client.h src/main.h \ src/types.h src/query.h -main.o: src/main.c src/ui/app.h src/ui/widget/dialogue.h \ - src/ui/widget/../widget.h src/ui/widget/../../main.h \ - src/ui/widget/../../types.h src/ui/widget/../app.h \ - src/ui/widget/button.h src/ui/widget/../../main.h src/ui/widget/group.h \ - src/ui/modals/login.h src/ui/modals/../widget/dialogue.h \ - src/ui/modals/../app.h src/ui/widget/dialogue.h -modal.o: src/modal.c src/ui/app.h src/ui/widget/dialogue.h \ - src/ui/widget/../widget.h src/ui/widget/../../main.h \ - src/ui/widget/../../types.h src/ui/widget/../app.h \ - src/ui/widget/button.h src/ui/widget/../../main.h src/ui/widget/group.h +main.o: src/main.c src/connection.h src/main.h src/types.h src/ui/app.h \ + src/ui/widget/dialogue.h src/ui/widget/../widget.h \ + src/ui/widget/../../main.h src/ui/widget/button.h \ + src/ui/widget/../../main.h src/ui/widget/group.h src/ui/modals/login.h \ + src/ui/modals/../widget/dialogue.h src/ui/modals/../app.h \ + src/ui/widget.h src/ui/widget/dialogue.h query.o: src/query.c src/file_processor.h src/main.h src/types.h \ src/connection.h src/client.h src/server.h server.o: src/server.c src/client.h src/main.h src/types.h \ diff --git a/src/main.c b/src/main.c index 405b3a1..697ef65 100644 --- a/src/main.c +++ b/src/main.c @@ -9,8 +9,11 @@ #include #include +#include "connection.h" +#include "main.h" #include "ui/app.h" #include "ui/modals/login.h" +#include "ui/widget.h" #include "ui/widget/dialogue.h" uint32_t m_id = 0; @@ -20,21 +23,21 @@ void app_refresh(app_t *app); int main(int argc, char **argv) { app_t *app; + params_t params; app = calloc(1, sizeof(app_t)); init_nc(); app = init_app(); + init_params(¶ms); + app->params = ¶ms; + analyze_args(argc, argv, ¶ms); - wrefresh(app->menu_win); - wrefresh(app->action_win); + wrefresh(app->left_win); + wrefresh(app->right_win); // temporal - app->modal.active = login; - - init_login_modal(app); - draw_dialogue(&(app->modal.dialogue)); - app_refresh(app); + app->modal.type = login; event_loop(app); @@ -44,27 +47,35 @@ int main(int argc, char **argv) { return 0; } +void app_draw_modal(app_t *app); + void event_loop(app_t *app) { int c; - callback_args_t d_args = {.app = app, .win = NULL}; - + callback_args_t d_args = {.app = app, + .win = NULL, + .widget = NULL, + .data = (void *)&c, + .resp_data = NULL}; + app_draw_modal(app); + app_refresh(app); for (;;) { c = wgetch(app->win); switch (c) { - // 113 is q key. This exits the program - case 113: + // 113 is q key. This exits the program + case 113: return; - case KEY_F(9): + case KEY_F(9): return; - default: - if (app->modal.active != none_active) { + default: + if (app->modal.type != none) { d_args.win = app->modal.dialogue.win; - app->modal.dialogue.w.callback(&d_args, &(app->modal.dialogue), - (void *)&c, NULL); + d_args.widget = &(app->modal.dialogue); + app->modal.dialogue.w.callback(&d_args); } break; } + app_draw_modal(app); app_refresh(app); }; } @@ -73,14 +84,32 @@ void app_refresh(app_t *app) { draw_borders(app); wnoutrefresh(app->win); - wnoutrefresh(app->menu_win); - wnoutrefresh(app->action_win); - if (app->modal.active != none_active) { - wnoutrefresh(app->modal.dialogue.win); + wnoutrefresh(app->left_win); + wnoutrefresh(app->right_win); + if (app->modal.type != none) { + if (app->modal.dialogue.is_initiated == 0) { + app->modal.type = none; + } else { + wnoutrefresh(app->modal.dialogue.win); + } } doupdate(); } +void app_draw_modal(app_t *app) { + if (app->modal.type != none && !app->modal.dialogue.is_initiated) { + switch (app->modal.type) { + case login: + init_login_modal(app); + break; + case none: + default: + break; + } + draw_dialogue(&(app->modal.dialogue)); + } +} + // int main(int argc, char *argv[]) { // params_t params; // init_params(¶ms); diff --git a/src/modal.c b/src/modal.c deleted file mode 100644 index ef7d8c9..0000000 --- a/src/modal.c +++ /dev/null @@ -1,20 +0,0 @@ -#include "ui/app.h" -#include - -int32_t m_login(char evt_c, app_t *app); - -int32_t modal_event_loop(char evt_c, app_t *app) { - switch (app->modal.active) { - - case none_active: - return 0; - case login: - m_login(evt_c, app); - break; - } - return 1; -} - -int32_t m_login(char evt_c, app_t *app) { - -} \ No newline at end of file diff --git a/src/modal.h b/src/modal.h deleted file mode 100644 index ff8cd07..0000000 --- a/src/modal.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef MODAL_H -#define MODAL_H - -#include "ui/app.h" -#include - -int32_t modal_event_loop(char evt_c, app_t *app); - -#endif \ No newline at end of file diff --git a/src/ui/app.c b/src/ui/app.c index b8b6212..0dd7a0d 100644 --- a/src/ui/app.c +++ b/src/ui/app.c @@ -23,13 +23,15 @@ app_t *init_app() { int action_w_x = x_max - menu_w_x - 2; // create the menu window - _app->menu_win = newwin(y_max - 4, menu_w_x, 2, 1); + _app->left_win = newwin(y_max - 4, menu_w_x, 2, 1); // create the action window - _app->action_win = newwin(y_max - 4, action_w_x, 2, menu_w_x + 1); + _app->right_win = newwin(y_max - 4, action_w_x, 2, menu_w_x + 1); // dialogue _app->modal.dialogue.win = NULL; + _app->modal.dialogue.is_initiated = 0; + _app->modal.type = none; // print decorative bars print_bars(_app); @@ -37,22 +39,10 @@ app_t *init_app() { // here goes box borders draw_borders(_app); - // init menus - // _app->menus = malloc(3 * sizeof(menu_t)); - // _app->menus[0] = init_menu("(F)ile", 'f'); - // _app->menus[1] = init_menu("(E)dit", 'e'); - // _app->menus[2] = init_menu("(A)bout", 'a'); - - // set active window by default - // _app->active = N_MENU; - - // call the action window content by default - // ac_file(_app->action_win, _app->active); - // refresh the windows wnoutrefresh(_app->win); - wnoutrefresh(_app->menu_win); - wnoutrefresh(_app->action_win); + wnoutrefresh(_app->left_win); + wnoutrefresh(_app->right_win); doupdate(); return _app; @@ -80,8 +70,8 @@ void draw_borders(app_t *app) { // refresh the windows wnoutrefresh(app->win); - wnoutrefresh(app->menu_win); - wnoutrefresh(app->action_win); + wnoutrefresh(app->left_win); + wnoutrefresh(app->right_win); doupdate(); } diff --git a/src/ui/app.h b/src/ui/app.h index af9a141..31c7a67 100644 --- a/src/ui/app.h +++ b/src/ui/app.h @@ -10,23 +10,20 @@ typedef enum { } color; typedef enum { - none_active = 0, + none = 0, login = 1, } dialogue_e; typedef struct { WINDOW *win; - WINDOW *menu_win; - WINDOW *action_win; + WINDOW *left_win; + WINDOW *right_win; struct { - dialogue_e active; + dialogue_e type; dialogue_t dialogue; } modal; - - // menu_bar_t *menu_bar; - // menu_t *menus; - // active_win_e active; int cur_y, cur_x; + params_t *params; } app_t; app_t *init_app(); diff --git a/src/ui/modals/login.c b/src/ui/modals/login.c index d15d31d..7fc7a59 100644 --- a/src/ui/modals/login.c +++ b/src/ui/modals/login.c @@ -2,7 +2,6 @@ #include "../widget/dialogue.h" #include "../widget/group.h" #include -#include #include typedef struct { @@ -10,22 +9,21 @@ typedef struct { enum w_type type; } group_bt_t; -/* TODO: improve init process (make it more readable) */ - -void group_callback(callback_args_t *args, void *widget, void *data, - void *resp_data) { - group_resp_t response; - callback_args_t ch_args = {.win = args->win, .app = NULL}; - group_default_callback(&ch_args, widget, data, (void *)&response); - if (response.value > -1) { +void login_modal_group_callback(callback_args_t *args) { + group_resp_t *response = (group_resp_t *)args->resp_data; + callback_args_t ch_args; + memcpy(&ch_args, args, sizeof(callback_args_t)); + ch_args.app = NULL; + group_default_callback(&ch_args); + if (response->value > -1) { // TODO: finish actions - switch (response.value) { + switch (response->value) { case 0: - exit(10); + // exit(10); case 1: - exit(11); + // exit(11); case 2: - exit(12); + // exit(12); } } } @@ -38,16 +36,14 @@ dialogue_t *init_login_modal(app_t *app) { {.type = w_button, .label = "Register"}, {.type = w_end}}; - dialogue_t *d = init_dialogue("Login", - "Select your login " - "option", - app->cur_y, app->cur_x); - - memcpy(&(app->modal.dialogue), d, sizeof(dialogue_t)); - free(d); + init_dialogue(&(app->modal.dialogue), "Login", + "Select your login " + "option", + app->cur_y, app->cur_x); app->modal.dialogue.ch_group = - init_group(&(app->modal.dialogue.win), &(d->w), children, horizontal); - app->modal.dialogue.ch_group->w.callback = group_callback; + init_group(&(app->modal.dialogue.win), &(app->modal.dialogue.w), children, + horizontal); + app->modal.dialogue.ch_group->w.callback = login_modal_group_callback; return NULL; } \ No newline at end of file diff --git a/src/ui/modals/login.h b/src/ui/modals/login.h index bd61a33..a6d92e3 100644 --- a/src/ui/modals/login.h +++ b/src/ui/modals/login.h @@ -4,9 +4,6 @@ #include "../widget/dialogue.h" #include "../app.h" -typedef struct { - -} ilm_action_args; dialogue_t *init_login_modal(app_t *app); #endif \ No newline at end of file diff --git a/src/ui/widget.h b/src/ui/widget.h index eadc596..980b111 100644 --- a/src/ui/widget.h +++ b/src/ui/widget.h @@ -2,19 +2,23 @@ #define WIDGET_H #include "../main.h" -#include #include +#include enum w_type { w_end, w_button, w_box, w_group, + w_input, }; typedef struct { WINDOW *win; void *app; + void *widget; + void *data; + void *resp_data; } callback_args_t; typedef struct widget_t { @@ -24,12 +28,12 @@ typedef struct widget_t { uint32_t y; uint32_t m_x; uint32_t m_y; - WINDOW *const* parent_win; + WINDOW *const *parent_win; struct widget_t *w_parent; - void (*callback) (callback_args_t *args, void *widget, void *data, void *resp_data); + void (*callback)(callback_args_t *args); } widget_t; -typedef void (*callback_t) (callback_args_t *args, void *widget, void *data, void *resp_data); +typedef void (*callback_t)(callback_args_t *args); enum pmt_attrs { PMT_POS_CENTER = 01, diff --git a/src/ui/widget/dialogue.c b/src/ui/widget/dialogue.c index 8ae7ec2..3b3dc5d 100644 --- a/src/ui/widget/dialogue.c +++ b/src/ui/widget/dialogue.c @@ -1,26 +1,36 @@ #include #include -#include #include #include #include "dialogue.h" #include "group.h" +#include "../app.h" - -void dialogue_default_callback(callback_args_t *args, void *widget, void *data, - void *callback) { - dialogue_t *d = (void *)widget; - char key = *((char *)data); +void dialogue_default_callback(callback_args_t *args) { + dialogue_t *d = (void *)args->widget; + char key = *((char *)args->data); + app_t *app = (app_t *)args->app; + callback_args_t new_args; + group_resp_t response; + memcpy(&new_args, args, sizeof(callback_args_t)); + new_args.widget = d->ch_group; + new_args.resp_data = &response; switch (key) { default: - d->ch_group->w.callback(args, d->ch_group, data, callback); - draw_dialogue(d); + d->ch_group->w.callback(&new_args); + if (response.value > -1) { + d->is_initiated = 0; + destroy_dialogue(d); + app->modal.type = none; + } else { + draw_dialogue(d); + } } } -dialogue_t *init_dialogue(const char title[], const char text[], uint32_t cur_y, uint32_t cur_x) { - dialogue_t *dialogue = malloc(sizeof(dialogue_t)); +void init_dialogue(dialogue_t *dialogue, const char title[], const char text[], uint32_t cur_y, + uint32_t cur_x) { dialogue->win = 0; dialogue->ch_group = NULL; dialogue->w.x = 0; @@ -28,12 +38,13 @@ dialogue_t *init_dialogue(const char title[], const char text[], uint32_t cur_y, dialogue->cur_y = cur_y; dialogue->cur_x = cur_x; dialogue->w.callback = dialogue_default_callback; + dialogue->is_initiated = 1; strcpy(dialogue->w.title, title); strcpy(dialogue->text, text); - return dialogue; } int32_t draw_dialogue(dialogue_t *d) { + if (!d->is_initiated) return -1; /* count dimensions */ uint32_t x = 1; /* when uses box */ uint32_t y = 1; /* when uses box */ @@ -92,11 +103,11 @@ int32_t draw_dialogue(dialogue_t *d) { return 0; }; -void destroy_active_dialogue(dialogue_t *d) { +void destroy_dialogue(dialogue_t *d) { if (d->ch_group) { destroy_group(d->ch_group); } delwin(d->win); - free(d->win); d->win = NULL; + d->is_initiated = 0; } \ No newline at end of file diff --git a/src/ui/widget/dialogue.h b/src/ui/widget/dialogue.h index 4ea3615..1b5196b 100644 --- a/src/ui/widget/dialogue.h +++ b/src/ui/widget/dialogue.h @@ -13,12 +13,13 @@ typedef struct { uint32_t cur_y; uint32_t cur_x; char text[DIALOGUE_TEXT]; + uint32_t is_initiated : 1; } dialogue_t; -dialogue_t *init_dialogue(const char title[], const char text[], uint32_t cur_x, +void init_dialogue(dialogue_t *d, const char title[], const char text[], uint32_t cur_x, uint32_t cur_y); int32_t draw_dialogue(dialogue_t *d); -void destroy_active_dialogue(dialogue_t *d); -void dialogue_default_callback(callback_args_t *args, void *widget, void *data, void *); +void destroy_dialogue(dialogue_t *d); +void dialogue_default_callback(callback_args_t *args); #endif \ No newline at end of file diff --git a/src/ui/widget/group.c b/src/ui/widget/group.c index a728c18..ed49232 100644 --- a/src/ui/widget/group.c +++ b/src/ui/widget/group.c @@ -4,10 +4,10 @@ #include #include -void group_default_callback(callback_args_t *args, void *widget, void *data, void *resp_data) { - group_t *g = (void *)widget; - char key = *((char *)data); - group_resp_t *response = (group_resp_t *) resp_data; +void group_default_callback(callback_args_t *args) { + group_t *g = (group_t *)args->widget; + char key = *((char *)args->data); + group_resp_t *response = (group_resp_t *) args->resp_data; switch (key) { case '\t': if (g->active_id != g->last_id) { @@ -15,13 +15,13 @@ void group_default_callback(callback_args_t *args, void *widget, void *data, voi } else { g->active_id = g->first_id; } - if (resp_data != NULL) { + if (args->resp_data != NULL) { response->active_id = g->active_id; response->value = -1; } break; case '\n': - if (resp_data != NULL) { + if (args->resp_data != NULL) { for (int i=0; i < g->count; i++) { if (g->elements[i].id == g->active_id) { response->active_id = g->active_id; @@ -41,9 +41,12 @@ group_t *init_group(WINDOW **win, widget_t *w_parent, group_el_init_t *children, group->count = 0; for (; children[group->count].type != w_end; group->count++) ; - group->elements = calloc(group->count, sizeof(group_el_t)); 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 (int i = 0; i < group->count; i++) { elements[i].type = children[i].type; @@ -112,6 +115,8 @@ void destroy_group(group_t *group) { break; } } - free(group->elements); + 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 index 9684a4b..5670d2a 100644 --- a/src/ui/widget/group.h +++ b/src/ui/widget/group.h @@ -39,6 +39,6 @@ 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); void destroy_group(group_t *group); -void group_default_callback(callback_args_t *args, void *widget, void *data, void *resp_data); +void group_default_callback(callback_args_t *args); #endif \ No newline at end of file From 1f8523635165d150a38a4b9781063ab2a67b9b64 Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Tue, 9 Dec 2025 20:41:26 +0200 Subject: [PATCH 11/45] implementation of input --- src/deps.mk | 7 +-- src/main.c | 16 +++--- src/main.h | 1 + src/ui/app.h | 1 + src/ui/modals/ask_server_addr.c | 46 ++++++++++++++++ src/ui/modals/ask_server_addr.h | 9 ++++ src/ui/widget.c | 23 +++++++- src/ui/widget.h | 11 ++++ src/ui/widget/dialogue.c | 4 +- src/ui/widget/dialogue.h | 2 +- src/ui/widget/group.c | 93 +++++++++++++++++++++++++++------ src/ui/widget/group.h | 1 + src/ui/widget/input.c | 69 ++++++++++++++++++++++++ src/ui/widget/input.h | 19 +++++++ 14 files changed, 273 insertions(+), 29 deletions(-) create mode 100644 src/ui/modals/ask_server_addr.c create mode 100644 src/ui/modals/ask_server_addr.h create mode 100644 src/ui/widget/input.c create mode 100644 src/ui/widget/input.h diff --git a/src/deps.mk b/src/deps.mk index 1b3488f..d3e8ba8 100644 --- a/src/deps.mk +++ b/src/deps.mk @@ -6,9 +6,10 @@ file_processor.o: src/file_processor.c src/client.h src/main.h \ main.o: src/main.c src/connection.h src/main.h src/types.h src/ui/app.h \ src/ui/widget/dialogue.h src/ui/widget/../widget.h \ src/ui/widget/../../main.h src/ui/widget/button.h \ - src/ui/widget/../../main.h src/ui/widget/group.h src/ui/modals/login.h \ - src/ui/modals/../widget/dialogue.h src/ui/modals/../app.h \ - src/ui/widget.h src/ui/widget/dialogue.h + src/ui/widget/../../main.h src/ui/widget/group.h \ + src/ui/modals/ask_server_addr.h src/ui/modals/../app.h \ + src/ui/modals/../widget/dialogue.h src/ui/modals/login.h src/ui/widget.h \ + src/ui/widget/dialogue.h query.o: src/query.c src/file_processor.h src/main.h src/types.h \ src/connection.h src/client.h src/server.h server.o: src/server.c src/client.h src/main.h src/types.h \ diff --git a/src/main.c b/src/main.c index 697ef65..0e855c6 100644 --- a/src/main.c +++ b/src/main.c @@ -12,6 +12,7 @@ #include "connection.h" #include "main.h" #include "ui/app.h" +#include "ui/modals/ask_server_addr.h" #include "ui/modals/login.h" #include "ui/widget.h" #include "ui/widget/dialogue.h" @@ -37,7 +38,7 @@ int main(int argc, char **argv) { wrefresh(app->right_win); // temporal - app->modal.type = login; + app->modal.type = ask_server_addr; event_loop(app); @@ -62,12 +63,12 @@ void event_loop(app_t *app) { for (;;) { c = wgetch(app->win); switch (c) { - // 113 is q key. This exits the program - case 113: + // 113 is q key. This exits the program + case 113: return; - case KEY_F(9): + case KEY_F(9): return; - default: + default: if (app->modal.type != none) { d_args.win = app->modal.dialogue.win; d_args.widget = &(app->modal.dialogue); @@ -102,11 +103,14 @@ void app_draw_modal(app_t *app) { case login: init_login_modal(app); break; + case ask_server_addr: + init_asa_modal(app); + break; case none: default: break; } - draw_dialogue(&(app->modal.dialogue)); + draw_dialogue(NULL, &(app->modal.dialogue)); } } diff --git a/src/main.h b/src/main.h index 9aed41c..401df1a 100644 --- a/src/main.h +++ b/src/main.h @@ -12,6 +12,7 @@ #define DOWNLOADS_DIR "./Downloads" #define DIALOGUE_TITLE 64 #define DIALOGUE_TEXT 128 +#define INPUT_TEXT DIALOGUE_TITLE typedef struct params { unsigned short port; diff --git a/src/ui/app.h b/src/ui/app.h index 31c7a67..f455242 100644 --- a/src/ui/app.h +++ b/src/ui/app.h @@ -12,6 +12,7 @@ typedef enum { typedef enum { none = 0, login = 1, + ask_server_addr = 2, } dialogue_e; typedef struct { diff --git a/src/ui/modals/ask_server_addr.c b/src/ui/modals/ask_server_addr.c new file mode 100644 index 0000000..98d0956 --- /dev/null +++ b/src/ui/modals/ask_server_addr.c @@ -0,0 +1,46 @@ +#include "../app.h" +#include "../widget/dialogue.h" +#include "../widget/group.h" +#include +#include + +typedef struct { + button_t *element; + enum w_type type; +} group_bt_t; + +void asa_modal_group_callback(callback_args_t *args) { + group_resp_t *response = (group_resp_t *)args->resp_data; + callback_args_t ch_args; + memcpy(&ch_args, args, sizeof(callback_args_t)); + ch_args.app = NULL; + group_default_callback(&ch_args); + if (response->value > -1) { + switch (response->value) { + case 0: + // exit(10); + case 1: + // exit(11); + case 2: + // exit(12); + } + } +} + +dialogue_t *init_asa_modal(app_t *app) { + if (app == NULL) + return NULL; + group_el_init_t children[] = { + {.type = w_input, .label = "Address", .length = 15}, + {.type = w_input, .label = "Port", .length = 5}, + {.type = w_end}}; + + init_dialogue(&(app->modal.dialogue), "Connect to server", + "There is information needed", app->cur_y, app->cur_x); + app->modal.dialogue.ch_group = + init_group(&(app->modal.dialogue.win), &(app->modal.dialogue.w), children, + horizontal); + app->modal.dialogue.ch_group->w.callback = asa_modal_group_callback; + + 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/widget.c b/src/ui/widget.c index 492aba1..914269f 100644 --- a/src/ui/widget.c +++ b/src/ui/widget.c @@ -5,6 +5,7 @@ #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++; @@ -35,7 +36,8 @@ int32_t get_max_line_len(const char *text, uint32_t *line_count) { line_num++; } } - if (line_count != NULL) *line_count = line_num; + if (line_count != NULL) + *line_count = line_num; if (c_len > m_len) { return c_len; } else { @@ -80,4 +82,23 @@ uint32_t print_multiline_text(WINDOW *win, const char *text, PRINT_TEXT(win, l_buf, text, i, c_line_len, win_width, line_v_pos, attrs) 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 index 980b111..99b18d5 100644 --- a/src/ui/widget.h +++ b/src/ui/widget.h @@ -21,6 +21,16 @@ typedef struct { void *resp_data; } 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]; @@ -30,6 +40,7 @@ typedef struct widget_t { uint32_t m_y; WINDOW *const *parent_win; struct widget_t *w_parent; + enum rsize rsize; void (*callback)(callback_args_t *args); } widget_t; diff --git a/src/ui/widget/dialogue.c b/src/ui/widget/dialogue.c index 3b3dc5d..391cec1 100644 --- a/src/ui/widget/dialogue.c +++ b/src/ui/widget/dialogue.c @@ -24,7 +24,7 @@ void dialogue_default_callback(callback_args_t *args) { destroy_dialogue(d); app->modal.type = none; } else { - draw_dialogue(d); + draw_dialogue(NULL, d); } } } @@ -43,7 +43,7 @@ void init_dialogue(dialogue_t *dialogue, const char title[], const char text[], strcpy(dialogue->text, text); } -int32_t draw_dialogue(dialogue_t *d) { +int32_t draw_dialogue(void *e, dialogue_t *d) { if (!d->is_initiated) return -1; /* count dimensions */ uint32_t x = 1; /* when uses box */ diff --git a/src/ui/widget/dialogue.h b/src/ui/widget/dialogue.h index 1b5196b..8b6d45a 100644 --- a/src/ui/widget/dialogue.h +++ b/src/ui/widget/dialogue.h @@ -18,7 +18,7 @@ typedef struct { void init_dialogue(dialogue_t *d, const char title[], const char text[], uint32_t cur_x, uint32_t cur_y); -int32_t draw_dialogue(dialogue_t *d); +int32_t draw_dialogue(void *, dialogue_t *d); void destroy_dialogue(dialogue_t *d); void dialogue_default_callback(callback_args_t *args); diff --git a/src/ui/widget/group.c b/src/ui/widget/group.c index ed49232..06ae403 100644 --- a/src/ui/widget/group.c +++ b/src/ui/widget/group.c @@ -1,5 +1,6 @@ #include "group.h" #include "button.h" +#include "input.h" #include #include #include @@ -7,7 +8,8 @@ void group_default_callback(callback_args_t *args) { group_t *g = (group_t *)args->widget; char key = *((char *)args->data); - group_resp_t *response = (group_resp_t *) args->resp_data; + group_resp_t *response = (group_resp_t *)args->resp_data; + input_t *inpt; switch (key) { case '\t': if (g->active_id != g->last_id) { @@ -22,7 +24,7 @@ void group_default_callback(callback_args_t *args) { break; case '\n': if (args->resp_data != NULL) { - for (int i=0; i < g->count; i++) { + for (int i = 0; i < g->count; i++) { if (g->elements[i].id == g->active_id) { response->active_id = g->active_id; response->value = i; @@ -30,6 +32,40 @@ void group_default_callback(callback_args_t *args) { } } break; + case '\177': + for (int i = 0; i < g->count; i++) { + if (g->elements[i].id == g->active_id) { + if (g->elements[i].type == w_input) { + inpt = (input_t *)g->elements[i].element; + if (inpt->value_len) { + inpt->value[--inpt->value_len] = '\0'; + } + break; + } + } + } + if (args->resp_data != NULL) { + response->active_id = g->active_id; + response->value = -1; + } + break; + default: + for (int i = 0; i < g->count; i++) { + if (g->elements[i].id == g->active_id) { + if (g->elements[i].type == w_input) { + inpt = (input_t *)g->elements[i].element; + if (inpt->max_len > inpt->value_len) { + inpt->value[inpt->value_len++] = key; + inpt->value[inpt->value_len] = '\0'; + } + break; + } + } + } + if (args->resp_data != NULL) { + response->active_id = g->active_id; + response->value = -1; + } } } @@ -53,32 +89,37 @@ group_t *init_group(WINDOW **win, widget_t *w_parent, group_el_init_t *children, } /* init child elements */ - uint32_t el_id = 0; for (int 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); - button_t *b = (button_t *)elements[i].element; - el_id = b->w.id; - elements[i].id = b->w.id; - if (direction == horizontal) { - b->w.m_x = group->w.x; - b->w.m_y = group->w.m_y; - group->w.x += b->w.x + 1; - if (group->w.y < b->w.y) - group->w.y = b->w.y; - } + 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); + w = &(((input_t *)elements[i].element)->w); + break; + } + // set dimensions + elements[i].id = w->id; + if (direction == horizontal) { + w->m_x = 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 = el_id; - group->active_id = el_id; + group->first_id = w->id; + group->active_id = w->id; } else { - group->last_id = el_id; + group->last_id = w->id; } } @@ -97,6 +138,23 @@ void draw_group(WINDOW *win, group_t *group) { case w_group: case w_end: break; + case w_input: + draw_input((input_t *)el->element, group->active_id); + break; + } + } + for (int i = 0; i < group->count; i++) { + if (group->elements[i].id == group->active_id && + group->elements[i].type == w_input) { + input_t *input = group->elements[i].element; + 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 + 1; + if (input->max_len == input->value_len) { + wmove(win, margin_y + 1, margin_x + input->value_len); + } else { + wmove(win, margin_y + 1, margin_x + input->value_len + 1); // x: +1: margin left from the border + } + curs_set(1); } } } @@ -113,6 +171,9 @@ void destroy_group(group_t *group) { case w_group: case w_end: break; + case w_input: + destroy_input(el->element); + break; } } if (group->elements != NULL) { diff --git a/src/ui/widget/group.h b/src/ui/widget/group.h index 5670d2a..fd88aa1 100644 --- a/src/ui/widget/group.h +++ b/src/ui/widget/group.h @@ -18,6 +18,7 @@ typedef struct { typedef struct { enum w_type type; char label[DIALOGUE_TITLE]; + uint32_t length; } group_el_init_t; typedef struct { diff --git a/src/ui/widget/input.c b/src/ui/widget/input.c new file mode 100644 index 0000000..374cd65 --- /dev/null +++ b/src/ui/widget/input.c @@ -0,0 +1,69 @@ +#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) { + 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 + 2; // with borders + 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; + return input; +} + +int32_t draw_input(input_t *input, uint32_t active_id) { + 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 + 1; // +1: margin left from the border + + 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); + + // TODO: correct sizes (x) + + 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; + mvwprintw(win, margin_y + 1, margin_x, "%s", input->value); + + for (int i = 0; i < input->w.x - input->value_len - 2; i++) { + mvwprintw(win, margin_y + 1, margin_x + i + input->value_len, " "); + } + + 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..a15f4c8 --- /dev/null +++ b/src/ui/widget/input.h @@ -0,0 +1,19 @@ +#ifndef INPUT_H +#define INPUT_H + +#include "../../main.h" +#include "../widget.h" +#include + +typedef struct { + widget_t w; + char value[INPUT_TEXT]; + uint32_t value_len; + uint32_t max_len; + uint32_t is_disabled : 1; +} input_t; + +input_t *init_input(WINDOW **win, widget_t *w_parent, char *label, uint32_t size); +int32_t draw_input(input_t *input, uint32_t active_id); +void destroy_input(input_t *input); +#endif \ No newline at end of file From ad1fa9e973d1f4cef86f410ca467b9fe767b24cd Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Tue, 9 Dec 2025 20:44:33 +0200 Subject: [PATCH 12/45] updates TODO --- TODO.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index 50eb12d..ea611ba 100644 --- a/TODO.md +++ b/TODO.md @@ -3,4 +3,6 @@ TODO: - [ ] 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 \ No newline at end of file +- [ ] Hotkeys for buttons +- [ ] Simpler refresh on typing / entering in input +- [ ] Group elements as lists \ No newline at end of file From 369f7de279fff79bc017b8b821116ced8842a864 Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Wed, 10 Dec 2025 20:11:35 +0200 Subject: [PATCH 13/45] improvement of detection of active elements (not finished) --- src/main.c | 2 +- src/ui/modals/ask_server_addr.c | 19 ++++-- src/ui/modals/login.c | 4 +- src/ui/widget.h | 4 ++ src/ui/widget/dialogue.c | 107 +++++++++++++++++++++++++------- src/ui/widget/dialogue.h | 20 ++++-- src/ui/widget/group.c | 100 ++++++++++++++--------------- src/ui/widget/group.h | 7 +-- 8 files changed, 170 insertions(+), 93 deletions(-) diff --git a/src/main.c b/src/main.c index 0e855c6..b4f7dbc 100644 --- a/src/main.c +++ b/src/main.c @@ -110,7 +110,7 @@ void app_draw_modal(app_t *app) { default: break; } - draw_dialogue(NULL, &(app->modal.dialogue)); + draw_dialogue(&(app->modal.dialogue)); } } diff --git a/src/ui/modals/ask_server_addr.c b/src/ui/modals/ask_server_addr.c index 98d0956..48c0e21 100644 --- a/src/ui/modals/ask_server_addr.c +++ b/src/ui/modals/ask_server_addr.c @@ -30,17 +30,26 @@ void asa_modal_group_callback(callback_args_t *args) { dialogue_t *init_asa_modal(app_t *app) { if (app == NULL) return NULL; - group_el_init_t children[] = { + 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"}, + {.type = w_button, .label = "Cancel"}, + {.type = w_end}}; + init_dialogue(&(app->modal.dialogue), "Connect to server", "There is information needed", app->cur_y, app->cur_x); - app->modal.dialogue.ch_group = - init_group(&(app->modal.dialogue.win), &(app->modal.dialogue.w), children, - horizontal); - app->modal.dialogue.ch_group->w.callback = asa_modal_group_callback; + dialogue_t *d = &(app->modal.dialogue); + + d->g_content = init_group(&(d->win), &(d->w), content, horizontal); + d->g_content->w.callback = asa_modal_group_callback; + + d->g_action = init_group(&(d->win), &(d->w), actions, horizontal); + d->g_action->w.callback = asa_modal_group_callback; + + dialogue_init_active_id(d); return NULL; } \ No newline at end of file diff --git a/src/ui/modals/login.c b/src/ui/modals/login.c index 7fc7a59..ca6ce10 100644 --- a/src/ui/modals/login.c +++ b/src/ui/modals/login.c @@ -40,10 +40,10 @@ dialogue_t *init_login_modal(app_t *app) { "Select your login " "option", app->cur_y, app->cur_x); - app->modal.dialogue.ch_group = + app->modal.dialogue.g_content = init_group(&(app->modal.dialogue.win), &(app->modal.dialogue.w), children, horizontal); - app->modal.dialogue.ch_group->w.callback = login_modal_group_callback; + app->modal.dialogue.g_content->w.callback = login_modal_group_callback; return NULL; } \ No newline at end of file diff --git a/src/ui/widget.h b/src/ui/widget.h index 99b18d5..c57111b 100644 --- a/src/ui/widget.h +++ b/src/ui/widget.h @@ -38,6 +38,10 @@ typedef struct widget_t { 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; diff --git a/src/ui/widget/dialogue.c b/src/ui/widget/dialogue.c index 391cec1..a894645 100644 --- a/src/ui/widget/dialogue.c +++ b/src/ui/widget/dialogue.c @@ -3,9 +3,9 @@ #include #include +#include "../app.h" #include "dialogue.h" #include "group.h" -#include "../app.h" void dialogue_default_callback(callback_args_t *args) { dialogue_t *d = (void *)args->widget; @@ -14,25 +14,43 @@ void dialogue_default_callback(callback_args_t *args) { callback_args_t new_args; group_resp_t response; memcpy(&new_args, args, sizeof(callback_args_t)); - new_args.widget = d->ch_group; + new_args.widget = d->g_content; new_args.resp_data = &response; switch (key) { + case '\t': + // TODO: active task + if (d->active.type == g_content) { + if (d->active.id != d->g_content->last_id) { + d->active.id = d->active.id + 1; + } else { + d->active.id = d->g_content->first_id; + } + } + if (d->active.type == g_action) { + if (d->active.id != d->g_action->last_id) { + d->active.id = d->active.id + 1; + } else { + d->active.id = d->g_action->first_id; + } + } + draw_dialogue(d); + break; default: - d->ch_group->w.callback(&new_args); + d->g_content->w.callback(&new_args); if (response.value > -1) { d->is_initiated = 0; destroy_dialogue(d); app->modal.type = none; } else { - draw_dialogue(NULL, d); + draw_dialogue(d); } } } -void init_dialogue(dialogue_t *dialogue, const char title[], const char text[], uint32_t cur_y, - uint32_t cur_x) { +void init_dialogue(dialogue_t *dialogue, const char title[], const char text[], + uint32_t cur_y, uint32_t cur_x) { dialogue->win = 0; - dialogue->ch_group = NULL; + dialogue->g_content = NULL; dialogue->w.x = 0; dialogue->w.y = 0; dialogue->cur_y = cur_y; @@ -43,32 +61,70 @@ void init_dialogue(dialogue_t *dialogue, const char title[], const char text[], strcpy(dialogue->text, text); } -int32_t draw_dialogue(void *e, dialogue_t *d) { - if (!d->is_initiated) return -1; +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) { + 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) { + 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 = 1; /* when uses box */ \ + } else { \ + /* TODO: centering */ \ + } \ + y += group->w.y; \ + } + +int32_t draw_dialogue(dialogue_t *d) { + if (!d->is_initiated) + return -1; /* 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 */ - if (d->ch_group) { - d->ch_group->w.m_y = y; - if (line_max_len < d->ch_group->w.x) { - line_max_len = d->ch_group->w.x; - d->ch_group->w.m_x = 1; /* when uses box */ - } else { - /* TODO: centering */ - } - y += d->ch_group->w.y; - } + /* 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 */ + + /* space for action block */ + + y += 2; + d->w.x = x; d->w.y = y; d->w.m_y = (d->cur_y - y) / 2; @@ -96,16 +152,21 @@ int32_t draw_dialogue(void *e, dialogue_t *d) { /* 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); - draw_group(d->win, d->ch_group); - + if (d->g_content != NULL) { + draw_group(d->win, d->g_content, d->active.id); + } + if (d->g_action != NULL) { + draw_group(d->win, d->g_action, d->active.id); + } return 0; }; void destroy_dialogue(dialogue_t *d) { - if (d->ch_group) { - destroy_group(d->ch_group); + if (d->g_content) { + destroy_group(d->g_content); } delwin(d->win); d->win = NULL; diff --git a/src/ui/widget/dialogue.h b/src/ui/widget/dialogue.h index 8b6d45a..cf4d4ca 100644 --- a/src/ui/widget/dialogue.h +++ b/src/ui/widget/dialogue.h @@ -5,21 +5,31 @@ #include "button.h" #include "group.h" #include +#include + +enum g_type { g_content, g_action }; typedef struct { - WINDOW *win; widget_t w; - group_t *ch_group; + WINDOW *win; + group_t *g_content; + group_t *g_action; + struct { + int32_t id; + enum g_type type; + uint32_t y, x; + } active; uint32_t cur_y; uint32_t cur_x; char text[DIALOGUE_TEXT]; uint32_t is_initiated : 1; } dialogue_t; -void init_dialogue(dialogue_t *d, const char title[], const char text[], uint32_t cur_x, - uint32_t cur_y); -int32_t draw_dialogue(void *, dialogue_t *d); +void init_dialogue(dialogue_t *d, const char title[], const char text[], + uint32_t cur_x, uint32_t cur_y); +int32_t draw_dialogue(dialogue_t *d); void destroy_dialogue(dialogue_t *d); 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/group.c b/src/ui/widget/group.c index 06ae403..f09b660 100644 --- a/src/ui/widget/group.c +++ b/src/ui/widget/group.c @@ -5,67 +5,59 @@ #include #include +// TODO: refactor + +#define MAKE_RESPONSE_M1(args, resp_data, response) \ + if (args->resp_data != NULL) { \ + response->active_id = g->active_id; \ + response->value = -1; \ + } + +#define FIND_ELEMENT(args, element_ptr, element_idx) \ + if (args->resp_data != NULL) { \ + for (int i = 0; i < g->count; i++) { \ + if (g->elements[i].id == g->active_id) { \ + element_ptr = &(g->elements[i]); \ + element_idx = i; \ + break; \ + } \ + } \ + } + void group_default_callback(callback_args_t *args) { group_t *g = (group_t *)args->widget; char key = *((char *)args->data); group_resp_t *response = (group_resp_t *)args->resp_data; - input_t *inpt; + input_t *input; + group_el_t *element_ptr; + int32_t element_idx = -1; switch (key) { - case '\t': - if (g->active_id != g->last_id) { - g->active_id = g->active_id + 1; - } else { - g->active_id = g->first_id; - } - if (args->resp_data != NULL) { - response->active_id = g->active_id; - response->value = -1; - } + case '\n': /* Enter */ + FIND_ELEMENT(args, element_ptr, element_idx); + response->active_id = g->active_id; + response->value = element_idx; break; - case '\n': - if (args->resp_data != NULL) { - for (int i = 0; i < g->count; i++) { - if (g->elements[i].id == g->active_id) { - response->active_id = g->active_id; - response->value = i; - } + case '\177': /* DEL */ + FIND_ELEMENT(args, element_ptr, element_idx); + if (element_ptr->type == w_input) { + input = (input_t *)element_ptr->element; + if (input->value_len) { + input->value[--input->value_len] = '\0'; } } - break; - case '\177': - for (int i = 0; i < g->count; i++) { - if (g->elements[i].id == g->active_id) { - if (g->elements[i].type == w_input) { - inpt = (input_t *)g->elements[i].element; - if (inpt->value_len) { - inpt->value[--inpt->value_len] = '\0'; - } - break; - } - } - } - if (args->resp_data != NULL) { - response->active_id = g->active_id; - response->value = -1; - } + MAKE_RESPONSE_M1(args, resp_data, response); break; default: - for (int i = 0; i < g->count; i++) { - if (g->elements[i].id == g->active_id) { - if (g->elements[i].type == w_input) { - inpt = (input_t *)g->elements[i].element; - if (inpt->max_len > inpt->value_len) { - inpt->value[inpt->value_len++] = key; - inpt->value[inpt->value_len] = '\0'; - } - break; - } + FIND_ELEMENT(args, element_ptr, element_idx); + if (element_ptr->type == w_input) { + input = (input_t *)element_ptr->element; + if (input->max_len > input->value_len) { + input->value[input->value_len++] = key; + input->value[input->value_len] = '\0'; } } - if (args->resp_data != NULL) { - response->active_id = g->active_id; - response->value = -1; - } + MAKE_RESPONSE_M1(args, resp_data, response); + break; } } @@ -126,20 +118,20 @@ group_t *init_group(WINDOW **win, widget_t *w_parent, group_el_init_t *children, return group; } -void draw_group(WINDOW *win, group_t *group) { +void draw_group(WINDOW *win, group_t *group, int32_t active_id) { 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: - draw_button((button_t *)el->element, group->active_id); + 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, group->active_id); + draw_input((input_t *)el->element, active_id); break; } } @@ -152,7 +144,9 @@ void draw_group(WINDOW *win, group_t *group) { if (input->max_len == input->value_len) { wmove(win, margin_y + 1, margin_x + input->value_len); } else { - wmove(win, margin_y + 1, margin_x + input->value_len + 1); // x: +1: margin left from the border + wmove(win, margin_y + 1, + margin_x + input->value_len + + 1); // x: +1: margin left from the border } curs_set(1); } diff --git a/src/ui/widget/group.h b/src/ui/widget/group.h index fd88aa1..4f89452 100644 --- a/src/ui/widget/group.h +++ b/src/ui/widget/group.h @@ -22,13 +22,12 @@ typedef struct { } group_el_init_t; typedef struct { - group_el_t *elements; widget_t w; - uint32_t active_id; + group_el_t *elements; + uint32_t active_id; // TODO: needs to be removed enum g_direction direction; uint32_t count; uint32_t first_id, last_id; - void *action_args; } group_t; typedef struct { @@ -38,7 +37,7 @@ typedef struct { 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); +void draw_group(WINDOW *win, group_t *group, int32_t active_id); void destroy_group(group_t *group); void group_default_callback(callback_args_t *args); From d2e5bddbcbf62731174868880487eefce1533322 Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Wed, 10 Dec 2025 20:11:44 +0200 Subject: [PATCH 14/45] fixes Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 1904c76..c75c8f0 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ deps.mk: $(SRCS) $(CC) -MM $^ > $(SRC_DIR)/$@ clean: - rm -f $(OBJ_DIR)/*.o $(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 From 4395e1184320f15426a70a9ebbe47f26e5f5fbba Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Thu, 11 Dec 2025 17:55:52 +0200 Subject: [PATCH 15/45] fixes and improves tab navigation mechanism. --- TODO.md | 3 +- src/common.h | 8 ++++ src/connection.c | 17 ++------ src/connection.h | 2 +- src/deps.mk | 14 +++---- src/server.c | 3 +- src/ui/app.c | 14 +++---- src/ui/app.h | 5 ++- src/ui/modals/ask_server_addr.c | 48 +++++++++++++--------- src/ui/modals/login.c | 30 +++++++------- src/ui/widget.h | 1 + src/ui/widget/button.c | 8 +++- src/ui/widget/dialogue.c | 72 ++++++++++++++++++++------------- src/ui/widget/dialogue.h | 7 ++-- src/ui/widget/group.c | 67 +++++++++++++----------------- src/ui/widget/group.h | 17 +++++--- src/ui/widget/input.c | 13 +++--- 17 files changed, 180 insertions(+), 149 deletions(-) create mode 100644 src/common.h diff --git a/TODO.md b/TODO.md index ea611ba..48eb438 100644 --- a/TODO.md +++ b/TODO.md @@ -5,4 +5,5 @@ TODO: - [x] use parent widgets pointers to count the margins correctly - [ ] Hotkeys for buttons - [ ] Simpler refresh on typing / entering in input -- [ ] Group elements as lists \ No newline at end of file +- [ ] Group elements as lists +- [ ] Process arrows \ 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..b30df8d 100644 --- a/src/connection.c +++ b/src/connection.c @@ -41,20 +41,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 index 3f8df6e..79b9296 100644 --- a/src/connection.h +++ b/src/connection.h @@ -5,7 +5,7 @@ void connect_to_server(int sd, params_t *params); void init_params(params_t *restrict); void clear_params(params_t *params); -void get_missing_params(params_t *restrict); +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); diff --git a/src/deps.mk b/src/deps.mk index d3e8ba8..b4f2c1a 100644 --- a/src/deps.mk +++ b/src/deps.mk @@ -5,13 +5,13 @@ file_processor.o: src/file_processor.c src/client.h src/main.h \ src/types.h src/query.h main.o: src/main.c src/connection.h src/main.h src/types.h src/ui/app.h \ src/ui/widget/dialogue.h src/ui/widget/../widget.h \ - src/ui/widget/../../main.h src/ui/widget/button.h \ - src/ui/widget/../../main.h src/ui/widget/group.h \ - src/ui/modals/ask_server_addr.h src/ui/modals/../app.h \ - src/ui/modals/../widget/dialogue.h src/ui/modals/login.h src/ui/widget.h \ - src/ui/widget/dialogue.h + src/ui/widget/../../main.h src/ui/widget/../../common.h \ + src/ui/widget/button.h src/ui/widget/../../main.h src/ui/widget/group.h \ + src/ui/../common.h src/ui/modals/ask_server_addr.h \ + src/ui/modals/../app.h src/ui/modals/../widget/dialogue.h \ + src/ui/modals/login.h src/ui/widget.h src/ui/widget/dialogue.h query.o: src/query.c src/file_processor.h src/main.h src/types.h \ src/connection.h src/client.h src/server.h -server.o: src/server.c src/client.h src/main.h src/types.h \ - src/connection.h src/query.h src/ui.h +server.o: src/server.c src/client.h src/main.h src/types.h src/query.h \ + src/ui.h ui.o: src/ui.c diff --git a/src/server.c b/src/server.c index 9581c2f..7b8ee30 100644 --- a/src/server.c +++ b/src/server.c @@ -1,5 +1,4 @@ #include "client.h" -#include "connection.h" #include "main.h" #include "query.h" #include "ui.h" @@ -69,7 +68,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; } diff --git a/src/ui/app.c b/src/ui/app.c index 0dd7a0d..50d04c7 100644 --- a/src/ui/app.c +++ b/src/ui/app.c @@ -13,10 +13,10 @@ app_t *init_app() { app_t *_app = calloc(1, sizeof(app_t)); // associate values for screen size - _app->cur_y = y_max; - _app->cur_x = x_max; + _app->coordinates.max_x = _app->coordinates.cur_x = x_max; + _app->coordinates.max_y = _app->coordinates.cur_y = y_max; - _app->win = newwin(_app->cur_y, _app->cur_x, 0, 0); + _app->win = newwin(_app->coordinates.cur_y, _app->coordinates.cur_x, 0, 0); // define the width for each sub window int menu_w_x = x_max / 6; @@ -79,18 +79,18 @@ void print_bars(app_t *app) { wattrset(app->win, A_REVERSE); // print top and bottom bars - for (int i = 1; i < app->cur_x - 1; i++) { + for (int i = 1; i < app->coordinates.cur_x - 1; i++) { mvwprintw(app->win, 1, i, " "); - mvwprintw(app->win, app->cur_y - 2, i, " "); + mvwprintw(app->win, app->coordinates.cur_y - 2, i, " "); } // add content to the top bar mvwprintw(app->win, 1, 2, "example cprint_barsurses app | "); mvwprintw(app->win, 1, sizeof "example cprint_barsurses app | " + 1, - "size %d, %d", app->cur_x, app->cur_y); + "size %d, %d", app->coordinates.cur_x, app->coordinates.cur_y); // add content to the bottom bar - mvwprintw(app->win, app->cur_y - 2, 2, "F1 - Help | F9 - Quit"); + mvwprintw(app->win, app->coordinates.cur_y - 2, 2, "F1 - Help | F9 - Quit"); wattroff(app->win, A_REVERSE); } diff --git a/src/ui/app.h b/src/ui/app.h index f455242..70a851d 100644 --- a/src/ui/app.h +++ b/src/ui/app.h @@ -1,8 +1,9 @@ #ifndef N_APP_H #define N_APP_H -#include #include "widget/dialogue.h" +#include "../common.h" +#include typedef enum { MODAL_COLOR_PAIR = 3, @@ -23,8 +24,8 @@ typedef struct { dialogue_e type; dialogue_t dialogue; } modal; - int cur_y, cur_x; params_t *params; + coordinates_t coordinates; } app_t; app_t *init_app(); diff --git a/src/ui/modals/ask_server_addr.c b/src/ui/modals/ask_server_addr.c index 48c0e21..a85445d 100644 --- a/src/ui/modals/ask_server_addr.c +++ b/src/ui/modals/ask_server_addr.c @@ -1,7 +1,11 @@ +#include "../../connection.h" #include "../app.h" #include "../widget/dialogue.h" #include "../widget/group.h" +#include "../widget/input.h" #include +#include +#include #include typedef struct { @@ -9,20 +13,27 @@ typedef struct { enum w_type type; } group_bt_t; -void asa_modal_group_callback(callback_args_t *args) { - group_resp_t *response = (group_resp_t *)args->resp_data; - callback_args_t ch_args; - memcpy(&ch_args, args, sizeof(callback_args_t)); - ch_args.app = NULL; - group_default_callback(&ch_args); - if (response->value > -1) { - switch (response->value) { +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 *)args->widget; + memcpy(&d_args, args, sizeof(callback_args_t)); + d_args.app = NULL; + d_args.resp_data = &response; + 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: - // exit(10); + get_ip_port(app->params, in_ip->value, in_port->value); + destroy_dialogue(d); + app->modal.type = none; + break; case 1: - // exit(11); - case 2: - // exit(12); + destroy_app(app); + exit(0); } } } @@ -35,19 +46,18 @@ dialogue_t *init_asa_modal(app_t *app) { {.type = w_input, .label = "Port", .length = 5}, {.type = w_end}}; - group_el_init_t actions[] = {{.type = w_button, .label = "Connect"}, - {.type = w_button, .label = "Cancel"}, - {.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.dialogue), "Connect to server", - "There is information needed", app->cur_y, app->cur_x); + "There is information needed", &(app->coordinates)); dialogue_t *d = &(app->modal.dialogue); + d->w.callback = asa_modal_dialogue_callback; d->g_content = init_group(&(d->win), &(d->w), content, horizontal); - d->g_content->w.callback = asa_modal_group_callback; - d->g_action = init_group(&(d->win), &(d->w), actions, horizontal); - d->g_action->w.callback = asa_modal_group_callback; dialogue_init_active_id(d); diff --git a/src/ui/modals/login.c b/src/ui/modals/login.c index ca6ce10..25d6d0b 100644 --- a/src/ui/modals/login.c +++ b/src/ui/modals/login.c @@ -2,6 +2,7 @@ #include "../widget/dialogue.h" #include "../widget/group.h" #include +#include #include typedef struct { @@ -10,20 +11,21 @@ typedef struct { } group_bt_t; void login_modal_group_callback(callback_args_t *args) { - group_resp_t *response = (group_resp_t *)args->resp_data; - callback_args_t ch_args; - memcpy(&ch_args, args, sizeof(callback_args_t)); - ch_args.app = NULL; - group_default_callback(&ch_args); - if (response->value > -1) { - // TODO: finish actions - switch (response->value) { + int32_t response; + callback_args_t d_args; + app_t *app = args->app; + dialogue_t *d = (dialogue_t *)args->widget; + memcpy(&d_args, args, sizeof(callback_args_t)); + d_args.app = NULL; + d_args.resp_data = &response; + dialogue_default_callback(&d_args); + if (response > -1) { + switch (response) { case 0: - // exit(10); + break; case 1: - // exit(11); - case 2: - // exit(12); + // exit(0); + break; } } } @@ -39,11 +41,11 @@ dialogue_t *init_login_modal(app_t *app) { init_dialogue(&(app->modal.dialogue), "Login", "Select your login " "option", - app->cur_y, app->cur_x); + &(app->coordinates)); app->modal.dialogue.g_content = init_group(&(app->modal.dialogue.win), &(app->modal.dialogue.w), children, horizontal); - app->modal.dialogue.g_content->w.callback = login_modal_group_callback; + app->modal.dialogue.w.callback = login_modal_group_callback; return NULL; } \ No newline at end of file diff --git a/src/ui/widget.h b/src/ui/widget.h index c57111b..dad9346 100644 --- a/src/ui/widget.h +++ b/src/ui/widget.h @@ -19,6 +19,7 @@ typedef struct { void *widget; void *data; void *resp_data; + uint32_t active_id; } callback_args_t; enum rsize { diff --git a/src/ui/widget/button.c b/src/ui/widget/button.c index 31b6117..f389dbc 100644 --- a/src/ui/widget/button.c +++ b/src/ui/widget/button.c @@ -9,10 +9,12 @@ 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); + 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.y = 1; // TODO: detect new line + btn->w.cur.y = 0; + btn->w.cur.x = 0; return btn; } @@ -31,6 +33,8 @@ int32_t draw_button(button_t *btn, uint32_t active_id) { } 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); diff --git a/src/ui/widget/dialogue.c b/src/ui/widget/dialogue.c index a894645..5dc7ee9 100644 --- a/src/ui/widget/dialogue.c +++ b/src/ui/widget/dialogue.c @@ -7,54 +7,71 @@ #include "dialogue.h" #include "group.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; \ + } + void dialogue_default_callback(callback_args_t *args) { dialogue_t *d = (void *)args->widget; char key = *((char *)args->data); app_t *app = (app_t *)args->app; callback_args_t new_args; - group_resp_t response; + 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.widget = d->g_content; - new_args.resp_data = &response; + new_args.active_id = d->active.id; + switch (key) { case '\t': - // TODO: active task if (d->active.type == g_content) { - if (d->active.id != d->g_content->last_id) { - d->active.id = d->active.id + 1; - } else { - d->active.id = d->g_content->first_id; - } + 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; + draw_dialogue(d); + break; + case '\n': if (d->active.type == g_action) { - if (d->active.id != d->g_action->last_id) { - d->active.id = d->active.id + 1; - } else { - d->active.id = d->g_action->first_id; + new_args.widget = new_args.widget = d->g_action; + group_default_callback(&new_args); + } else { + for (int i = 0; i < d->g_action->count; i++) { + if (d->g_action->elements[i].is_default) { + *resp_value = i; + return; + } } } draw_dialogue(d); break; default: - d->g_content->w.callback(&new_args); - if (response.value > -1) { - d->is_initiated = 0; - destroy_dialogue(d); - app->modal.type = none; + if (d->active.type == g_content) { + new_args.widget = new_args.widget = d->g_content; + group_default_callback(&new_args); } else { + new_args.widget = new_args.widget = d->g_action; + group_default_callback(&new_args); + } + if (*resp_value == -1) { draw_dialogue(d); } } } void init_dialogue(dialogue_t *dialogue, const char title[], const char text[], - uint32_t cur_y, uint32_t cur_x) { + coordinates_t *p_coordinates) { dialogue->win = 0; dialogue->g_content = NULL; dialogue->w.x = 0; dialogue->w.y = 0; - dialogue->cur_y = cur_y; - dialogue->cur_x = cur_x; + dialogue->p_coordinates = p_coordinates; dialogue->w.callback = dialogue_default_callback; dialogue->is_initiated = 1; strcpy(dialogue->w.title, title); @@ -123,12 +140,10 @@ int32_t draw_dialogue(dialogue_t *d) { /* space for action block */ - y += 2; - d->w.x = x; d->w.y = y; - d->w.m_y = (d->cur_y - y) / 2; - d->w.m_x = (d->cur_x - x) / 2; + 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) { @@ -156,10 +171,10 @@ int32_t draw_dialogue(dialogue_t *d) { 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); + 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); + draw_group(d->win, d->g_action, d->active.id, &(d->w)); } return 0; }; @@ -168,6 +183,9 @@ void destroy_dialogue(dialogue_t *d) { 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; diff --git a/src/ui/widget/dialogue.h b/src/ui/widget/dialogue.h index cf4d4ca..f527318 100644 --- a/src/ui/widget/dialogue.h +++ b/src/ui/widget/dialogue.h @@ -2,6 +2,7 @@ #define DIALOGUE_H #include "../widget.h" +#include "../../common.h" #include "button.h" #include "group.h" #include @@ -17,16 +18,14 @@ typedef struct { struct { int32_t id; enum g_type type; - uint32_t y, x; } active; - uint32_t cur_y; - uint32_t cur_x; char text[DIALOGUE_TEXT]; uint32_t is_initiated : 1; + coordinates_t *p_coordinates; } dialogue_t; void init_dialogue(dialogue_t *d, const char title[], const char text[], - uint32_t cur_x, uint32_t cur_y); + coordinates_t *p_coordinates); int32_t draw_dialogue(dialogue_t *d); void destroy_dialogue(dialogue_t *d); void dialogue_default_callback(callback_args_t *args); diff --git a/src/ui/widget/group.c b/src/ui/widget/group.c index f09b660..e165cfa 100644 --- a/src/ui/widget/group.c +++ b/src/ui/widget/group.c @@ -9,36 +9,23 @@ #define MAKE_RESPONSE_M1(args, resp_data, response) \ if (args->resp_data != NULL) { \ - response->active_id = g->active_id; \ - response->value = -1; \ - } - -#define FIND_ELEMENT(args, element_ptr, element_idx) \ - if (args->resp_data != NULL) { \ - for (int i = 0; i < g->count; i++) { \ - if (g->elements[i].id == g->active_id) { \ - element_ptr = &(g->elements[i]); \ - element_idx = i; \ - break; \ - } \ - } \ + *response = -1; \ } void group_default_callback(callback_args_t *args) { group_t *g = (group_t *)args->widget; char key = *((char *)args->data); - group_resp_t *response = (group_resp_t *)args->resp_data; + int32_t *response = (int32_t *)args->resp_data; input_t *input; group_el_t *element_ptr; int32_t element_idx = -1; switch (key) { case '\n': /* Enter */ - FIND_ELEMENT(args, element_ptr, element_idx); - response->active_id = g->active_id; - response->value = element_idx; + FIND_ACTIVE_ELEMENT(g, args->active_id, element_ptr, element_idx); + *response = element_idx; break; case '\177': /* DEL */ - FIND_ELEMENT(args, element_ptr, element_idx); + 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) { @@ -48,7 +35,7 @@ void group_default_callback(callback_args_t *args) { MAKE_RESPONSE_M1(args, resp_data, response); break; default: - FIND_ELEMENT(args, element_ptr, element_idx); + 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) { @@ -76,12 +63,13 @@ group_t *init_group(WINDOW **win, widget_t *w_parent, group_el_init_t *children, } group->elements = calloc(group->count, sizeof(group_el_t)); group_el_t *elements = group->elements; - for (int i = 0; i < group->count; i++) { + 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 (int i = 0; i < group->count; i++) { + for (int32_t i = 0; i < group->count; i++) { widget_t *w; switch (elements[i].type) { case w_button: @@ -101,7 +89,7 @@ group_t *init_group(WINDOW **win, widget_t *w_parent, group_el_init_t *children, // set dimensions elements[i].id = w->id; if (direction == horizontal) { - w->m_x = group->w.x; + w->m_x = group->w.x + 1; w->m_y = group->w.m_y; group->w.x += w->x + 1; if (group->w.y < w->y) @@ -109,7 +97,6 @@ group_t *init_group(WINDOW **win, widget_t *w_parent, group_el_init_t *children, } if (i == 0) { group->first_id = w->id; - group->active_id = w->id; } else { group->last_id = w->id; } @@ -118,8 +105,11 @@ group_t *init_group(WINDOW **win, widget_t *w_parent, group_el_init_t *children, return group; } -void draw_group(WINDOW *win, group_t *group, int32_t active_id) { +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) { @@ -135,22 +125,23 @@ void draw_group(WINDOW *win, group_t *group, int32_t active_id) { break; } } - for (int i = 0; i < group->count; i++) { - if (group->elements[i].id == group->active_id && - group->elements[i].type == w_input) { - input_t *input = group->elements[i].element; - 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 + 1; - if (input->max_len == input->value_len) { - wmove(win, margin_y + 1, margin_x + input->value_len); - } else { - wmove(win, margin_y + 1, - margin_x + input->value_len + - 1); // x: +1: margin left from the border - } - curs_set(1); + /* move cursor */ + FIND_ACTIVE_ELEMENT(group, active_id, active_element, active_idx); + + if (active_element != NULL && active_element->id == active_id && + active_element->type == w_input) { + input_t *input = active_element->element; + dialog_w->cur.y = input->w.cur.y; + if (input->max_len == input->value_len) { + dialog_w->cur.x = input->w.cur.x + input->value_len - 1; + } else { + dialog_w->cur.x = input->w.cur.x + input->value_len; } } + if (dialog_w->cur.y || dialog_w->cur.x) { + wmove(win, dialog_w->cur.y, dialog_w->cur.x); + curs_set(1); + } } void destroy_group(group_t *group) { diff --git a/src/ui/widget/group.h b/src/ui/widget/group.h index 4f89452..73681be 100644 --- a/src/ui/widget/group.h +++ b/src/ui/widget/group.h @@ -13,10 +13,12 @@ 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; char label[DIALOGUE_TITLE]; uint32_t length; } group_el_init_t; @@ -24,20 +26,23 @@ typedef struct { typedef struct { widget_t w; group_el_t *elements; - uint32_t active_id; // TODO: needs to be removed enum g_direction direction; uint32_t count; uint32_t first_id, last_id; } group_t; -typedef struct { - int32_t value; - uint32_t active_id; -} group_resp_t; +#define FIND_ACTIVE_ELEMENT(g, active_id, element_ptr, element_idx) \ + for (int i = 0; i < g->count; i++) { \ + if (g->elements[i].id == active_id) { \ + element_ptr = &(g->elements[i]); \ + element_idx = i; \ + break; \ + } \ + } 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); +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); diff --git a/src/ui/widget/input.c b/src/ui/widget/input.c index 374cd65..176ed8c 100644 --- a/src/ui/widget/input.c +++ b/src/ui/widget/input.c @@ -13,7 +13,7 @@ input_t *init_input(WINDOW **win, widget_t *w_parent, char *label, init_widget(&(input->w), w_parent, win, label); input->is_disabled = 0; input->w.y = 3; // with borders - input->w.x = length + 2; // with borders + input->w.x = length + 2; // with borders uint32_t t_len = strlen(input->w.title) + 4; // with borders and space if (input->w.x < t_len) input->w.x = t_len; @@ -27,7 +27,7 @@ input_t *init_input(WINDOW **win, widget_t *w_parent, char *label, int32_t draw_input(input_t *input, uint32_t active_id) { 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 + 1; // +1: margin left from the border + uint32_t margin_x = input->w.m_x + input->w.w_parent->m_x; wattrset(win, COLOR_PAIR(MODAL_COLOR_PAIR)); @@ -41,9 +41,9 @@ int32_t draw_input(input_t *input, uint32_t active_id) { 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); + 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); // TODO: correct sizes (x) @@ -57,7 +57,8 @@ int32_t draw_input(input_t *input, uint32_t active_id) { uint32_t x_c = 0; mvwprintw(win, margin_y + 1, margin_x, "%s", input->value); - + input->w.cur.y = margin_y + 1; + input->w.cur.x = margin_x; for (int i = 0; i < input->w.x - input->value_len - 2; i++) { mvwprintw(win, margin_y + 1, margin_x + i + input->value_len, " "); } From dc74baecf7ffcd0cb89fddea078b464bed2f1ddf Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Fri, 12 Dec 2025 12:21:44 +0200 Subject: [PATCH 16/45] ui: cursor fix --- TODO.md | 3 ++- src/ui/widget/dialogue.c | 26 ++++++++++++++++++++++++++ src/ui/widget/group.c | 18 +----------------- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/TODO.md b/TODO.md index 48eb438..010957e 100644 --- a/TODO.md +++ b/TODO.md @@ -6,4 +6,5 @@ TODO: - [ ] Hotkeys for buttons - [ ] Simpler refresh on typing / entering in input - [ ] Group elements as lists -- [ ] Process arrows \ No newline at end of file +- [ ] Process arrows +- [ ] active_id -> просто указатель на элемент diff --git a/src/ui/widget/dialogue.c b/src/ui/widget/dialogue.c index 5dc7ee9..b9d9c21 100644 --- a/src/ui/widget/dialogue.c +++ b/src/ui/widget/dialogue.c @@ -6,6 +6,7 @@ #include "../app.h" #include "dialogue.h" #include "group.h" +#include "input.h" #define INCR_ACTIVE_ID(d, current, next) \ if (d->active.id != d->current->last_id) { \ @@ -119,6 +120,8 @@ void dialogue_init_active_id(dialogue_t *dialogue) { int32_t draw_dialogue(dialogue_t *d) { if (!d->is_initiated) return -1; + 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 */ @@ -176,6 +179,29 @@ int32_t draw_dialogue(dialogue_t *d) { if (d->g_action != NULL) { draw_group(d->win, d->g_action, d->active.id, &(d->w)); } + + /* move cursor */ + FIND_ACTIVE_ELEMENT(d->g_content, d->active.id, ae_ptr, ae_idx); + if (ae_ptr == 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; + if (input->max_len == input->value_len) { + d->w.cur.x = input->w.cur.x + input->value_len - 1; + } else { + d->w.cur.x = input->w.cur.x + input->value_len; + } + if (d->w.cur.y || d->w.cur.x) { + wmove(d->win, d->w.cur.y, d->w.cur.x); + curs_set(1); + } + } else { + curs_set(0); + } + } + return 0; }; diff --git a/src/ui/widget/group.c b/src/ui/widget/group.c index e165cfa..d13abef 100644 --- a/src/ui/widget/group.c +++ b/src/ui/widget/group.c @@ -1,6 +1,7 @@ #include "group.h" #include "button.h" #include "input.h" +#include #include #include #include @@ -125,23 +126,6 @@ void draw_group(WINDOW *win, group_t *group, int32_t active_id, break; } } - /* move cursor */ - FIND_ACTIVE_ELEMENT(group, active_id, active_element, active_idx); - - if (active_element != NULL && active_element->id == active_id && - active_element->type == w_input) { - input_t *input = active_element->element; - dialog_w->cur.y = input->w.cur.y; - if (input->max_len == input->value_len) { - dialog_w->cur.x = input->w.cur.x + input->value_len - 1; - } else { - dialog_w->cur.x = input->w.cur.x + input->value_len; - } - } - if (dialog_w->cur.y || dialog_w->cur.x) { - wmove(win, dialog_w->cur.y, dialog_w->cur.x); - curs_set(1); - } } void destroy_group(group_t *group) { From 6018d99e0747c7175aabda27103fb636ea4cdc02 Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Fri, 12 Dec 2025 14:23:06 +0200 Subject: [PATCH 17/45] ui: correct reaction on arrow keys press --- src/main.c | 6 ++--- src/ui/app.c | 9 +++++-- src/ui/widget/dialogue.c | 54 +++++++++++++++++++++++++++++++++++----- src/ui/widget/group.c | 5 ++-- 4 files changed, 60 insertions(+), 14 deletions(-) diff --git a/src/main.c b/src/main.c index b4f7dbc..e200c47 100644 --- a/src/main.c +++ b/src/main.c @@ -51,7 +51,7 @@ int main(int argc, char **argv) { void app_draw_modal(app_t *app); void event_loop(app_t *app) { - int c; + int32_t c; callback_args_t d_args = {.app = app, .win = NULL, @@ -63,10 +63,8 @@ void event_loop(app_t *app) { for (;;) { c = wgetch(app->win); switch (c) { - // 113 is q key. This exits the program - case 113: - return; case KEY_F(9): + destroy_app(app); return; default: if (app->modal.type != none) { diff --git a/src/ui/app.c b/src/ui/app.c index 50d04c7..d0ff3e3 100644 --- a/src/ui/app.c +++ b/src/ui/app.c @@ -1,5 +1,6 @@ #include "app.h" // #include "action.h" +#include #include void ac_file(WINDOW *win, int is_action_w); @@ -39,6 +40,8 @@ app_t *init_app() { // here goes box borders draw_borders(_app); + keypad(_app->win, TRUE); + // refresh the windows wnoutrefresh(_app->win); wnoutrefresh(_app->left_win); @@ -50,9 +53,10 @@ app_t *init_app() { void init_nc() { initscr(); + cbreak(); + keypad(stdscr, TRUE); noecho(); curs_set(0); - keypad(stdscr, TRUE); if (!has_colors()) { printf("Your terminal does not support color\n"); @@ -97,5 +101,6 @@ void print_bars(app_t *app) { void destroy_app(app_t *app) { delwin(app->win); - free(app); + // free(app); + endwin(); } diff --git a/src/ui/widget/dialogue.c b/src/ui/widget/dialogue.c index b9d9c21..e8c22d8 100644 --- a/src/ui/widget/dialogue.c +++ b/src/ui/widget/dialogue.c @@ -18,17 +18,41 @@ 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; \ + } \ + } \ + } + void dialogue_default_callback(callback_args_t *args) { dialogue_t *d = (void *)args->widget; - char key = *((char *)args->data); + 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; switch (key) { + case KEY_RIGHT: case '\t': if (d->active.type == g_content) { INCR_ACTIVE_ID(d, g_content, g_action); @@ -38,6 +62,15 @@ void dialogue_default_callback(callback_args_t *args) { *resp_value = -1; draw_dialogue(d); break; + case KEY_LEFT: + if (d->active.type == g_content) { + 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; + draw_dialogue(d); + break; case '\n': if (d->active.type == g_action) { new_args.widget = new_args.widget = d->g_action; @@ -52,14 +85,22 @@ void dialogue_default_callback(callback_args_t *args) { } draw_dialogue(d); break; + case KEY_UP: + CH_GROUP(d, g_action, g_content); + draw_dialogue(d); + break; + case KEY_DOWN: + CH_GROUP(d, g_content, g_action); + draw_dialogue(d); + break; default: + /* run callback function */ if (d->active.type == g_content) { new_args.widget = new_args.widget = d->g_content; - group_default_callback(&new_args); } else { new_args.widget = new_args.widget = d->g_action; - group_default_callback(&new_args); } + group_default_callback(&new_args); if (*resp_value == -1) { draw_dialogue(d); } @@ -121,7 +162,7 @@ int32_t draw_dialogue(dialogue_t *d) { if (!d->is_initiated) return -1; group_el_t *ae_ptr = NULL; /* active element */ - uint32_t ae_idx; /* active element */ + uint32_t ae_idx; /* active element */ /* count dimensions */ uint32_t x = 1; /* when uses box */ uint32_t y = 1; /* when uses box */ @@ -182,7 +223,8 @@ int32_t draw_dialogue(dialogue_t *d) { /* move cursor */ FIND_ACTIVE_ELEMENT(d->g_content, d->active.id, ae_ptr, ae_idx); - if (ae_ptr == NULL) FIND_ACTIVE_ELEMENT(d->g_action, d->active.id, ae_ptr, ae_idx); + if (ae_ptr == 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) { diff --git a/src/ui/widget/group.c b/src/ui/widget/group.c index d13abef..ad4bb16 100644 --- a/src/ui/widget/group.c +++ b/src/ui/widget/group.c @@ -15,7 +15,7 @@ void group_default_callback(callback_args_t *args) { group_t *g = (group_t *)args->widget; - char key = *((char *)args->data); + int32_t key = *((int32_t *)args->data); int32_t *response = (int32_t *)args->resp_data; input_t *input; group_el_t *element_ptr; @@ -25,7 +25,8 @@ void group_default_callback(callback_args_t *args) { FIND_ACTIVE_ELEMENT(g, args->active_id, element_ptr, element_idx); *response = element_idx; break; - case '\177': /* DEL */ + 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; From 79d4b88c8d757dc01d73051fbde038c96d5ba71d Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Mon, 15 Dec 2025 17:47:27 +0200 Subject: [PATCH 18/45] ui integration into event loop; login modals --- src/connection.c | 11 +- src/connection.h | 3 +- src/deps.mk | 27 +++-- src/main.c | 118 +++++++--------------- src/main.h | 5 + src/query.c | 91 +++++++++-------- src/query.h | 5 +- src/server.c | 41 ++++---- src/ui/app.c | 56 +++++++++- src/ui/app.h | 12 ++- src/ui/modals/ask_server_addr.c | 15 +-- src/ui/modals/login.h | 9 -- src/ui/modals/login_credentials.c | 64 ++++++++++++ src/ui/modals/login_credentials.h | 9 ++ src/ui/modals/{login.c => login_option.c} | 22 ++-- src/ui/modals/login_option.h | 9 ++ src/ui/widget/dialogue.c | 12 ++- 17 files changed, 314 insertions(+), 195 deletions(-) delete mode 100644 src/ui/modals/login.h create mode 100644 src/ui/modals/login_credentials.c create mode 100644 src/ui/modals/login_credentials.h rename src/ui/modals/{login.c => login_option.c} (74%) create mode 100644 src/ui/modals/login_option.h diff --git a/src/connection.c b/src/connection.c index b30df8d..18b0223 100644 --- a/src/connection.c +++ b/src/connection.c @@ -1,4 +1,5 @@ #include "main.h" +#include "ui/app.h" #include #include #include @@ -10,13 +11,15 @@ #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"); + destroy_app(app); exit(2); } } diff --git a/src/connection.h b/src/connection.h index 79b9296..0de36c4 100644 --- a/src/connection.h +++ b/src/connection.h @@ -1,8 +1,9 @@ #ifndef CONNECTION_H #define CONNECTION_H #include "main.h" +#include "ui/app.h" -void connect_to_server(int sd, params_t *params); +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); diff --git a/src/deps.mk b/src/deps.mk index b4f2c1a..b4962d2 100644 --- a/src/deps.mk +++ b/src/deps.mk @@ -1,17 +1,24 @@ client.o: src/client.c src/file_processor.h src/main.h src/types.h \ src/client.h -connection.o: src/connection.c src/main.h src/types.h +connection.o: src/connection.c src/main.h src/types.h src/ui/app.h \ + src/ui/../common.h src/ui/widget/dialogue.h src/ui/widget/../widget.h \ + src/ui/widget/../../main.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/client.h src/main.h \ - src/types.h src/query.h -main.o: src/main.c src/connection.h src/main.h src/types.h src/ui/app.h \ + src/types.h src/query.h src/ui/app.h src/ui/../common.h \ src/ui/widget/dialogue.h src/ui/widget/../widget.h \ src/ui/widget/../../main.h src/ui/widget/../../common.h \ + src/ui/widget/button.h src/ui/widget/../../main.h src/ui/widget/group.h +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/dialogue.h src/ui/widget/../widget.h \ + src/ui/widget/../../main.h src/ui/widget/../../common.h \ src/ui/widget/button.h src/ui/widget/../../main.h src/ui/widget/group.h \ - src/ui/../common.h src/ui/modals/ask_server_addr.h \ - src/ui/modals/../app.h src/ui/modals/../widget/dialogue.h \ - src/ui/modals/login.h src/ui/widget.h src/ui/widget/dialogue.h -query.o: src/query.c src/file_processor.h src/main.h src/types.h \ - src/connection.h src/client.h src/server.h -server.o: src/server.c src/client.h src/main.h src/types.h src/query.h \ - src/ui.h + src/query.h src/ui/widget.h src/ui/widget/dialogue.h +query.o: src/query.c src/client.h src/main.h src/types.h src/connection.h \ + src/ui/app.h src/ui/../common.h src/ui/widget/dialogue.h \ + src/ui/widget/../widget.h src/ui/widget/../../main.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/server.h +server.o: src/server.c src/client.h src/main.h src/types.h src/ui.h ui.o: src/ui.c diff --git a/src/main.c b/src/main.c index e200c47..8a63bc1 100644 --- a/src/main.c +++ b/src/main.c @@ -11,20 +11,21 @@ #include "connection.h" #include "main.h" +#include "query.h" #include "ui/app.h" -#include "ui/modals/ask_server_addr.h" -#include "ui/modals/login.h" #include "ui/widget.h" #include "ui/widget/dialogue.h" uint32_t m_id = 0; -void event_loop(app_t *app); -void app_refresh(app_t *app); +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(); @@ -37,89 +38,44 @@ int main(int argc, char **argv) { wrefresh(app->left_win); wrefresh(app->right_win); - // temporal - app->modal.type = ask_server_addr; + init_query_args(q_args, app->params); + q_args->buf = malloc(INBUFSIZE); + app->query_args = q_args; - event_loop(app); + /* 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_ASK_LOGIN_TYPE; + } + + query_loop(app); + clear_params(¶ms); destroy_app(app); - endwin(); - return 0; + return OK; } -void app_draw_modal(app_t *app); - -void event_loop(app_t *app) { +int32_t process_user_input(app_t *app, callback_args_t *d_args) { int32_t c; - - callback_args_t d_args = {.app = app, - .win = NULL, - .widget = NULL, - .data = (void *)&c, - .resp_data = NULL}; + c = wgetch(app->win); + switch (c) { + case KEY_F(9): + destroy_app(app); + return OK; + default: + if (app->modal.win != NULL) { + d_args->data = (void *)&c; + d_args->win = app->modal.win; + d_args->widget = &(app->modal); + app->modal.w.callback(d_args); + } + break; + } app_draw_modal(app); app_refresh(app); - for (;;) { - c = wgetch(app->win); - switch (c) { - case KEY_F(9): - destroy_app(app); - return; - default: - if (app->modal.type != none) { - d_args.win = app->modal.dialogue.win; - d_args.widget = &(app->modal.dialogue); - app->modal.dialogue.w.callback(&d_args); - } - break; - } - app_draw_modal(app); - app_refresh(app); - }; -} - -void app_refresh(app_t *app) { - draw_borders(app); - - wnoutrefresh(app->win); - wnoutrefresh(app->left_win); - wnoutrefresh(app->right_win); - if (app->modal.type != none) { - if (app->modal.dialogue.is_initiated == 0) { - app->modal.type = none; - } else { - wnoutrefresh(app->modal.dialogue.win); - } - } - doupdate(); -} - -void app_draw_modal(app_t *app) { - if (app->modal.type != none && !app->modal.dialogue.is_initiated) { - switch (app->modal.type) { - case login: - init_login_modal(app); - break; - case ask_server_addr: - init_asa_modal(app); - break; - case none: - default: - break; - } - draw_dialogue(&(app->modal.dialogue)); - } -} - -// int main(int argc, char *argv[]) { -// params_t params; -// init_params(¶ms); -// analyze_args(argc, argv, ¶ms); -// get_missing_params(¶ms); -// int sd = init_client(); -// connect_to_server(sd, ¶ms); -// query_loop(sd, ¶ms); -// clear_params(¶ms); -// return 0; -// } \ No newline at end of file + return OK; +} \ No newline at end of file diff --git a/src/main.h b/src/main.h index 401df1a..79cfd19 100644 --- a/src/main.h +++ b/src/main.h @@ -20,9 +20,14 @@ typedef struct params { char *uname; char *pass; char privileges; + int32_t sd; } params_t; enum state { + S_N_D, + S_ASK_SEVER_IP, + S_ASK_LOGIN_TYPE, + S_ASK_LOGIN_USER, WAIT_SERVER_INIT, WAIT_SERVER, WAIT_REGISTER, diff --git a/src/query.c b/src/query.c index fc25c27..8bb7c13 100644 --- a/src/query.c +++ b/src/query.c @@ -1,12 +1,9 @@ -#include "file_processor.h" -#include "connection.h" -#include "client.h" -#include "main.h" -#include "server.h" #include #include +#include #include #include +#include #include #include #include @@ -19,34 +16,45 @@ #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 "client.h" +#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]; static file_args_t file_args; - static query_args_t query_args; + callback_args_t d_args = { + .app = app, .win = NULL, .widget = 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; 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; + 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; } } sr = select(maxfd + 1, &readfds, NULL, NULL, NULL); @@ -57,41 +65,41 @@ void query_loop(int sd, params_t *params) { 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 = 1; + if (-1 == 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 (-1 == process_user_input(app, &d_args)) { + close_session(sd); + exit(1); } } } @@ -155,6 +163,10 @@ int process_query(query_args_t *query_args, file_args_t *file_args) { 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; } @@ -218,12 +230,11 @@ static void wait_side(query_args_t *q_args) { } } -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; diff --git a/src/query.h b/src/query.h index c0d2576..a068e68 100644 --- a/src/query.h +++ b/src/query.h @@ -2,9 +2,12 @@ #define QUERY_H #include "main.h" -void query_loop(int sd, params_t *); +#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 7b8ee30..37bd40a 100644 --- a/src/server.c +++ b/src/server.c @@ -1,6 +1,5 @@ #include "client.h" #include "main.h" -#include "query.h" #include "ui.h" #include #include @@ -29,26 +28,26 @@ 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; - } - } - write(q_args->sd, params->uname, strlen(params->uname)); + // 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; + // } + // } + // write(q_args->sd, params->uname, strlen(params->uname)); return 0; } diff --git a/src/ui/app.c b/src/ui/app.c index d0ff3e3..a8752c1 100644 --- a/src/ui/app.c +++ b/src/ui/app.c @@ -1,7 +1,12 @@ #include "app.h" +#include "modals/ask_server_addr.h" +#include "modals/login_credentials.h" +#include "modals/login_option.h" // #include "action.h" #include +#include #include +#include void ac_file(WINDOW *win, int is_action_w); @@ -30,9 +35,8 @@ app_t *init_app() { _app->right_win = newwin(y_max - 4, action_w_x, 2, menu_w_x + 1); // dialogue - _app->modal.dialogue.win = NULL; - _app->modal.dialogue.is_initiated = 0; - _app->modal.type = none; + _app->modal.win = NULL; + _app->modal.is_initiated = 0; // print decorative bars print_bars(_app); @@ -98,6 +102,52 @@ void print_bars(app_t *app) { 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.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 WAIT_SERVER_INIT: + case WAIT_SERVER: + case WAIT_REGISTER: + case WAIT_REGISTER_CONFIRMATION: + case WAIT_CLIENT: + case UPLOAD_FILE: + case STATE_FILE_LIST: + case STATE_FILE_SELECT: + case STATE_FILE_DOWNLOAD: + case STATE_UPLOAD_PARAMS: + case STATE_UPLOAD_FILE: + case STATE_UPLOAD_REQUESTED: + case STATE_ASK_USER_BEFORE_LOGIN: + case STATE_ERR: + case S_N_D: + return; + } + } + draw_dialogue(&(app->modal)); +} void destroy_app(app_t *app) { delwin(app->win); diff --git a/src/ui/app.h b/src/ui/app.h index 70a851d..3137735 100644 --- a/src/ui/app.h +++ b/src/ui/app.h @@ -1,8 +1,8 @@ #ifndef N_APP_H #define N_APP_H -#include "widget/dialogue.h" #include "../common.h" +#include "widget/dialogue.h" #include typedef enum { @@ -20,12 +20,12 @@ typedef struct { WINDOW *win; WINDOW *left_win; WINDOW *right_win; - struct { - dialogue_e type; - dialogue_t dialogue; - } modal; + dialogue_t modal; params_t *params; coordinates_t coordinates; + query_args_t *query_args; + file_args_t *file_args; + int32_t read_fd; } app_t; app_t *init_app(); @@ -33,5 +33,7 @@ void init_nc(); void destroy_app(app_t *app); 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 index a85445d..c5652a0 100644 --- a/src/ui/modals/ask_server_addr.c +++ b/src/ui/modals/ask_server_addr.c @@ -1,12 +1,13 @@ +#include +#include +#include +#include + #include "../../connection.h" #include "../app.h" #include "../widget/dialogue.h" #include "../widget/group.h" #include "../widget/input.h" -#include -#include -#include -#include typedef struct { button_t *element; @@ -28,8 +29,8 @@ void asa_modal_dialogue_callback(callback_args_t *args) { switch (response) { case 0: get_ip_port(app->params, in_ip->value, in_port->value); + connect_to_server(app); destroy_dialogue(d); - app->modal.type = none; break; case 1: destroy_app(app); @@ -51,9 +52,9 @@ dialogue_t *init_asa_modal(app_t *app) { {.type = w_button, .label = "Cancel", .is_default = 0}, {.type = w_end}}; - init_dialogue(&(app->modal.dialogue), "Connect to server", + init_dialogue(&(app->modal), "Connect to server", "There is information needed", &(app->coordinates)); - dialogue_t *d = &(app->modal.dialogue); + dialogue_t *d = &(app->modal); d->w.callback = asa_modal_dialogue_callback; d->g_content = init_group(&(d->win), &(d->w), content, horizontal); diff --git a/src/ui/modals/login.h b/src/ui/modals/login.h deleted file mode 100644 index a6d92e3..0000000 --- a/src/ui/modals/login.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef LOGIN_MODAL_H -#define LOGIN_MODAL_H - -#include "../widget/dialogue.h" -#include "../app.h" - -dialogue_t *init_login_modal(app_t *app); - -#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..bb343be --- /dev/null +++ b/src/ui/modals/login_credentials.c @@ -0,0 +1,64 @@ +#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 *)args->widget; + memcpy(&d_args, args, sizeof(callback_args_t)); + d_args.app = NULL; + d_args.resp_data = &response; + 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: + app->query_args->state = S_ASK_LOGIN_USER; + destroy_dialogue(d); + break; + case 1: + destroy_app(app); + exit(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 = 10}, + {.type = w_input, .label = "Password", .length = 10}, + {.type = w_end}}; + + group_el_init_t actions[] = { + {.type = w_button, .label = "Login", .is_default = 1}, + {.type = w_button, .label = "Cancel", .is_default = 0}, + {.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.c b/src/ui/modals/login_option.c similarity index 74% rename from src/ui/modals/login.c rename to src/ui/modals/login_option.c index 25d6d0b..17088dc 100644 --- a/src/ui/modals/login.c +++ b/src/ui/modals/login_option.c @@ -1,16 +1,17 @@ -#include "../app.h" -#include "../widget/dialogue.h" -#include "../widget/group.h" #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_group_callback(callback_args_t *args) { +void login_modal_option_cb(callback_args_t *args) { int32_t response; callback_args_t d_args; app_t *app = args->app; @@ -24,13 +25,14 @@ void login_modal_group_callback(callback_args_t *args) { case 0: break; case 1: - // exit(0); + destroy_dialogue(d); + app->query_args->state = S_ASK_LOGIN_USER; break; } } } -dialogue_t *init_login_modal(app_t *app) { +dialogue_t *init_login_option_modal(app_t *app) { if (app == NULL) return NULL; group_el_init_t children[] = {{.type = w_button, .label = "Anonymous"}, @@ -38,14 +40,14 @@ dialogue_t *init_login_modal(app_t *app) { {.type = w_button, .label = "Register"}, {.type = w_end}}; - init_dialogue(&(app->modal.dialogue), "Login", + init_dialogue(&(app->modal), "Login", "Select your login " "option", &(app->coordinates)); - app->modal.dialogue.g_content = - init_group(&(app->modal.dialogue.win), &(app->modal.dialogue.w), children, + app->modal.g_content = + init_group(&(app->modal.win), &(app->modal.w), children, horizontal); - app->modal.dialogue.w.callback = login_modal_group_callback; + app->modal.w.callback = login_modal_option_cb; 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/widget/dialogue.c b/src/ui/widget/dialogue.c index e8c22d8..211efde 100644 --- a/src/ui/widget/dialogue.c +++ b/src/ui/widget/dialogue.c @@ -75,13 +75,17 @@ void dialogue_default_callback(callback_args_t *args) { if (d->active.type == g_action) { new_args.widget = new_args.widget = d->g_action; group_default_callback(&new_args); - } else { + } 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; } draw_dialogue(d); break; @@ -222,8 +226,10 @@ int32_t draw_dialogue(dialogue_t *d) { } /* move cursor */ - FIND_ACTIVE_ELEMENT(d->g_content, d->active.id, ae_ptr, ae_idx); - if (ae_ptr == NULL) + 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) { From ba33bab4d1f77f81202da1149932c382e3f69ee3 Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Tue, 16 Dec 2025 13:48:41 +0200 Subject: [PATCH 19/45] ui: dialogue: fixes positioning --- src/ui/modals/login_credentials.c | 10 +++++----- src/ui/modals/login_option.c | 3 ++- src/ui/widget/dialogue.c | 4 ++-- src/ui/widget/group.c | 4 ++-- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/ui/modals/login_credentials.c b/src/ui/modals/login_credentials.c index bb343be..bf06d85 100644 --- a/src/ui/modals/login_credentials.c +++ b/src/ui/modals/login_credentials.c @@ -1,7 +1,7 @@ +#include #include #include #include -#include #include "../app.h" #include "../widget/dialogue.h" @@ -41,8 +41,8 @@ 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 = 10}, - {.type = w_input, .label = "Password", .length = 10}, + {.type = w_input, .label = "Username", .length = 15}, + {.type = w_input, .label = "Password", .length = 15}, {.type = w_end}}; group_el_init_t actions[] = { @@ -50,8 +50,8 @@ dialogue_t *init_login_credentials_modal(app_t *app) { {.type = w_button, .label = "Cancel", .is_default = 0}, {.type = w_end}}; - init_dialogue(&(app->modal), "Credentials", - "There is information needed", &(app->coordinates)); + init_dialogue(&(app->modal), "Credentials", "There is information needed", + &(app->coordinates)); dialogue_t *d = &(app->modal); d->w.callback = init_login_credentials_modal_cb; diff --git a/src/ui/modals/login_option.c b/src/ui/modals/login_option.c index 17088dc..6f33a00 100644 --- a/src/ui/modals/login_option.c +++ b/src/ui/modals/login_option.c @@ -44,10 +44,11 @@ dialogue_t *init_login_option_modal(app_t *app) { "Select your login " "option", &(app->coordinates)); - app->modal.g_content = + 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/widget/dialogue.c b/src/ui/widget/dialogue.c index 211efde..3f54427 100644 --- a/src/ui/widget/dialogue.c +++ b/src/ui/widget/dialogue.c @@ -155,9 +155,9 @@ void dialogue_init_active_id(dialogue_t *dialogue) { group->w.m_y = y; \ if (line_max_len < group->w.x) { \ line_max_len = group->w.x; \ - group->w.m_x = 1; /* when uses box */ \ + group->w.m_x = 0; /* when uses box */ \ } else { \ - /* TODO: centering */ \ + group->w.m_x = (line_max_len - group->w.x) / 2; \ } \ y += group->w.y; \ } diff --git a/src/ui/widget/group.c b/src/ui/widget/group.c index ad4bb16..076c891 100644 --- a/src/ui/widget/group.c +++ b/src/ui/widget/group.c @@ -53,7 +53,7 @@ void group_default_callback(callback_args_t *args) { 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++) @@ -91,7 +91,7 @@ group_t *init_group(WINDOW **win, widget_t *w_parent, group_el_init_t *children, // set dimensions elements[i].id = w->id; if (direction == horizontal) { - w->m_x = group->w.x + 1; + 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) From b5d638bbc939d67cef9c660f936db70af32e79bd Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Tue, 16 Dec 2025 16:40:18 +0200 Subject: [PATCH 20/45] ui: server message --- TODO.md | 5 ++++ src/client.c | 4 +-- src/connection.c | 3 ++ src/file_processor.c | 6 ++-- src/main.c | 2 +- src/main.h | 27 +++++++++++------ src/query.c | 29 ++++++++++-------- src/server.c | 48 +++++++++++++++--------------- src/ui/app.c | 23 +++++++++------ src/ui/modals/ask_server_addr.c | 1 + src/ui/modals/server_message.c | 52 +++++++++++++++++++++++++++++++++ src/ui/modals/server_message.h | 9 ++++++ src/ui/widget/dialogue.c | 2 +- 13 files changed, 151 insertions(+), 60 deletions(-) create mode 100644 src/ui/modals/server_message.c create mode 100644 src/ui/modals/server_message.h diff --git a/TODO.md b/TODO.md index 010957e..ed94ff1 100644 --- a/TODO.md +++ b/TODO.md @@ -8,3 +8,8 @@ TODO: - [ ] Group elements as lists - [ ] Process arrows - [ ] active_id -> просто указатель на элемент + +INPUT: +- [ ] hidden text for passwords +- [ ] move cursor through the text in input + diff --git a/src/client.c b/src/client.c index 74db12b..1424ec0 100644 --- a/src/client.c +++ b/src/client.c @@ -10,14 +10,14 @@ 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; + q_args->state = S_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; + q_args->state = S_UPLOAD_REQUESTED; } else { print_prompt(q_args->params); q_args->state = WAIT_CLIENT; diff --git a/src/connection.c b/src/connection.c index 18b0223..3895a84 100644 --- a/src/connection.c +++ b/src/connection.c @@ -1,6 +1,7 @@ #include "main.h" #include "ui/app.h" #include +#include #include #include #include @@ -22,6 +23,7 @@ void connect_to_server(app_t *app) { destroy_app(app); exit(2); } + app->params->is_connected = TRUE; } void init_params(params_t *params) { @@ -30,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) { diff --git a/src/file_processor.c b/src/file_processor.c index 685dbea..6fc8ea5 100644 --- a/src/file_processor.c +++ b/src/file_processor.c @@ -35,7 +35,7 @@ void file_list(file_args_t *f_args, query_args_t *q_args) { 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; + q_args->state = S_FILE_SELECT; idx = 1; break; } @@ -71,7 +71,7 @@ void file_select(file_args_t *f_args, query_args_t *q_args) { 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; } @@ -111,7 +111,7 @@ void file_select(file_args_t *f_args, query_args_t *q_args) { } 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); } diff --git a/src/main.c b/src/main.c index 8a63bc1..6fa0fa7 100644 --- a/src/main.c +++ b/src/main.c @@ -49,7 +49,7 @@ int main(int argc, char **argv) { app->query_args->state = S_ASK_SEVER_IP; } else { connect_to_server(app); - app->query_args->state = S_ASK_LOGIN_TYPE; + app->query_args->state = S_WAIT_SERVER; } query_loop(app); diff --git a/src/main.h b/src/main.h index 79cfd19..69ca55b 100644 --- a/src/main.h +++ b/src/main.h @@ -11,7 +11,7 @@ #define INBUFSIZE 1024 #define DOWNLOADS_DIR "./Downloads" #define DIALOGUE_TITLE 64 -#define DIALOGUE_TEXT 128 +#define DIALOGUE_TEXT 4096 #define INPUT_TEXT DIALOGUE_TITLE typedef struct params { @@ -21,6 +21,7 @@ typedef struct params { char *pass; char privileges; int32_t sd; + uint32_t is_connected : 1; } params_t; enum state { @@ -28,20 +29,22 @@ enum state { 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_REQUESTED, + S_ASK_USER_BEFORE_LOGIN, + S_ERR, 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, - STATE_ERR, }; typedef struct p_file { @@ -74,6 +77,12 @@ typedef struct query_args { char *buf; char from_server; params_t *params; + char *next_server_command; + struct { + char *text; + uint64_t capacity; + uint64_t size; + } server_message; } query_args_t; #endif \ No newline at end of file diff --git a/src/query.c b/src/query.c index 8bb7c13..a753073 100644 --- a/src/query.c +++ b/src/query.c @@ -49,8 +49,8 @@ void query_loop(app_t *app) { FD_ZERO(&readfds); FD_SET(STDIN_FILENO, &readfds); FD_SET(sd, &readfds); - int maxfd = sd; - if (query_args->state == STATE_UPLOAD_FILE) { + 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) @@ -122,35 +122,36 @@ int process_query(query_args_t *query_args, file_args_t *file_args) { case WAIT_SERVER: case WAIT_SERVER_INIT: case WAIT_CLIENT: + case S_WAIT_SERVER: case WAIT_REGISTER_CONFIRMATION: wait_side(query_args); break; case UPLOAD_FILE: - case STATE_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: + case S_UPLOAD_PARAMS: + case S_UPLOAD_FILE: if (file_upload(query_args)) { /* upload finishes */ user_request_description(query_args); @@ -158,7 +159,7 @@ int process_query(query_args_t *query_args, file_args_t *file_args) { query_args->state = WAIT_CLIENT; } break; - case STATE_ASK_USER_BEFORE_LOGIN: + case S_ASK_USER_BEFORE_LOGIN: break; case WAIT_REGISTER: wait_register(query_args); @@ -215,7 +216,7 @@ static void wait_side(query_args_t *q_args) { char *query = NULL; if (q_args->state == WAIT_SERVER || q_args->state == WAIT_SERVER_INIT || - q_args->from_server) { + q_args->from_server || q_args->state == S_WAIT_SERVER) { while ((qlen = query_extract_from_buf(q_args->buf, &buf_used, &query))) { process_server_command(query, qlen, q_args); free(query); @@ -238,6 +239,10 @@ void init_query_args(query_args_t *q_args, params_t *params) { 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; } void user_request_description(query_args_t *q_args) { diff --git a/src/server.c b/src/server.c index 37bd40a..9f4e73e 100644 --- a/src/server.c +++ b/src/server.c @@ -21,6 +21,7 @@ int process_server_command(char *line, int l_len, query_args_t *q_args) { params_t *params = q_args->params; const char *login_options[] = {"Username", "Anonymous", "Register", NULL}; uint32_t answer; + uint64_t new_capacity; char *cptr = strchr(line, ' '); if (cptr != NULL && cptr > line) @@ -28,27 +29,11 @@ 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; - // } - // } - // write(q_args->sd, params->uname, strlen(params->uname)); - + 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; } @@ -99,8 +84,25 @@ int process_server_command(char *line, int l_len, query_args_t *q_args) { return 0; } - if (q_args->state == WAIT_SERVER_INIT) { - write(STDOUT_FILENO, line, l_len); + 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; + } } else { char buf[INBUFSIZE + sizeof("server: ")] = "server: "; strcat(buf, line); diff --git a/src/ui/app.c b/src/ui/app.c index a8752c1..4587580 100644 --- a/src/ui/app.c +++ b/src/ui/app.c @@ -2,6 +2,8 @@ #include "modals/ask_server_addr.h" #include "modals/login_credentials.h" #include "modals/login_option.h" +#include "modals/server_message.h" +// #include "modals/server_message.h" // #include "action.h" #include #include @@ -128,20 +130,24 @@ void app_draw_modal(app_t *app) { case S_ASK_LOGIN_USER: init_login_credentials_modal(app); break; + case S_PRINT_SERVER_MESSAGE: + init_server_message_modal(app); + break; case WAIT_SERVER_INIT: case WAIT_SERVER: case WAIT_REGISTER: case WAIT_REGISTER_CONFIRMATION: case WAIT_CLIENT: case UPLOAD_FILE: - case STATE_FILE_LIST: - case STATE_FILE_SELECT: - case STATE_FILE_DOWNLOAD: - case STATE_UPLOAD_PARAMS: - case STATE_UPLOAD_FILE: - case STATE_UPLOAD_REQUESTED: - case STATE_ASK_USER_BEFORE_LOGIN: - case STATE_ERR: + case S_FILE_LIST: + case S_FILE_SELECT: + case S_FILE_DOWNLOAD: + case S_UPLOAD_PARAMS: + case S_UPLOAD_FILE: + case S_UPLOAD_REQUESTED: + case S_ASK_USER_BEFORE_LOGIN: + case S_ERR: + case S_WAIT_SERVER: case S_N_D: return; } @@ -151,6 +157,5 @@ void app_draw_modal(app_t *app) { void destroy_app(app_t *app) { delwin(app->win); - // free(app); endwin(); } diff --git a/src/ui/modals/ask_server_addr.c b/src/ui/modals/ask_server_addr.c index c5652a0..8c1957a 100644 --- a/src/ui/modals/ask_server_addr.c +++ b/src/ui/modals/ask_server_addr.c @@ -31,6 +31,7 @@ void asa_modal_dialogue_callback(callback_args_t *args) { get_ip_port(app->params, in_ip->value, in_port->value); connect_to_server(app); destroy_dialogue(d); + app->query_args->state = S_WAIT_SERVER; break; case 1: destroy_app(app); diff --git a/src/ui/modals/server_message.c b/src/ui/modals/server_message.c new file mode 100644 index 0000000..2386841 --- /dev/null +++ b/src/ui/modals/server_message.c @@ -0,0 +1,52 @@ +#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 *)args->widget; + memcpy(&d_args, args, sizeof(callback_args_t)); + d_args.app = NULL; + d_args.resp_data = &response; + dialogue_default_callback(&d_args); + if (response > -1) { + switch (response) { + case 0: + destroy_dialogue(d); + // TODO: macros + free(app->query_args->server_message.text); + app->query_args->server_message.capacity = 0; + app->query_args->server_message.size = 0; + app->query_args->state = S_ASK_LOGIN_USER; + 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/widget/dialogue.c b/src/ui/widget/dialogue.c index 3f54427..5b73cdb 100644 --- a/src/ui/widget/dialogue.c +++ b/src/ui/widget/dialogue.c @@ -155,7 +155,7 @@ void dialogue_init_active_id(dialogue_t *dialogue) { group->w.m_y = y; \ if (line_max_len < group->w.x) { \ line_max_len = group->w.x; \ - group->w.m_x = 0; /* when uses box */ \ + group->w.m_x = 0; \ } else { \ group->w.m_x = (line_max_len - group->w.x) / 2; \ } \ From 1e2e180be42dd107a0001127b50549237e632014 Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Wed, 17 Dec 2025 15:12:41 +0200 Subject: [PATCH 21/45] ui: login process (without error processing) --- src/main.c | 2 -- src/main.h | 7 ++++--- src/query.c | 30 ++++++++++++++++++++++++------ src/server.c | 17 +++++++++++------ src/ui/app.c | 1 + src/ui/modals/login_credentials.c | 14 +++++++++++--- src/ui/modals/server_message.c | 4 ++-- src/ui/widget/dialogue.c | 2 -- 8 files changed, 53 insertions(+), 24 deletions(-) diff --git a/src/main.c b/src/main.c index 6fa0fa7..ca8ce7e 100644 --- a/src/main.c +++ b/src/main.c @@ -75,7 +75,5 @@ int32_t process_user_input(app_t *app, callback_args_t *d_args) { } break; } - app_draw_modal(app); - app_refresh(app); return OK; } \ No newline at end of file diff --git a/src/main.h b/src/main.h index 69ca55b..665e856 100644 --- a/src/main.h +++ b/src/main.h @@ -39,6 +39,7 @@ enum state { S_UPLOAD_REQUESTED, S_ASK_USER_BEFORE_LOGIN, S_ERR, + S_NEXT_ACTION, WAIT_SERVER_INIT, WAIT_SERVER, WAIT_REGISTER, @@ -70,12 +71,12 @@ 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 { diff --git a/src/query.c b/src/query.c index a753073..5768280 100644 --- a/src/query.c +++ b/src/query.c @@ -32,6 +32,7 @@ void query_loop(app_t *app) { query_args_t *query_args = app->query_args; fd_set readfds; int32_t sd = app->params->sd; + app->query_args->sd = app->params->sd; size_t qlen; int sr; static file_args_t file_args; @@ -57,10 +58,25 @@ void query_loop(app_t *app) { 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 (-1 == 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)) { @@ -72,7 +88,7 @@ void query_loop(app_t *app) { exit(1); } else { query_args->buf_used = qlen; - query_args->from_server = 1; + query_args->from_server = TRUE; if (-1 == process_query(query_args, &file_args)) { close_session(sd); exit(1); @@ -83,7 +99,8 @@ void query_loop(app_t *app) { 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, query_args->buf, INBUFSIZE); + query_args->buf_used = + read(query_args->file->fd, query_args->buf, INBUFSIZE); process_query(query_args, &file_args); } @@ -122,6 +139,7 @@ int process_query(query_args_t *query_args, file_args_t *file_args) { 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); diff --git a/src/server.c b/src/server.c index 9f4e73e..1bcb513 100644 --- a/src/server.c +++ b/src/server.c @@ -16,6 +16,14 @@ void ask_uname_and_password(params_t *params); void ask_register(params_t *params, char *email); +#define PRINT_SRV_MESSAGE(q_args) \ + 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; @@ -29,11 +37,8 @@ int process_server_command(char *line, int l_len, query_args_t *q_args) { /* LOGIN */ if (!strncmp(line, "login>", ws_pos)) { - 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); - } + PRINT_SRV_MESSAGE(q_args); + q_args->state = S_ASK_LOGIN_TYPE; return 0; } @@ -101,7 +106,7 @@ int process_server_command(char *line, int l_len, query_args_t *q_args) { } 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; + q_args->server_message.text[q_args->server_message.size + l_len] = 0; } } else { char buf[INBUFSIZE + sizeof("server: ")] = "server: "; diff --git a/src/ui/app.c b/src/ui/app.c index 4587580..979b74b 100644 --- a/src/ui/app.c +++ b/src/ui/app.c @@ -149,6 +149,7 @@ void app_draw_modal(app_t *app) { case S_ERR: case S_WAIT_SERVER: case S_N_D: + case S_NEXT_ACTION: return; } } diff --git a/src/ui/modals/login_credentials.c b/src/ui/modals/login_credentials.c index bf06d85..3cfe6d5 100644 --- a/src/ui/modals/login_credentials.c +++ b/src/ui/modals/login_credentials.c @@ -2,6 +2,7 @@ #include #include #include +#include #include "../app.h" #include "../widget/dialogue.h" @@ -21,13 +22,20 @@ void init_login_credentials_modal_cb(callback_args_t *args) { memcpy(&d_args, args, sizeof(callback_args_t)); d_args.app = NULL; d_args.resp_data = &response; - input_t *in_ip = d->g_content->elements[0].element; - input_t *in_port = d->g_content->elements[1].element; + 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->query_args->state = S_ASK_LOGIN_USER; + 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; destroy_dialogue(d); break; case 1: diff --git a/src/ui/modals/server_message.c b/src/ui/modals/server_message.c index 2386841..81a5b1c 100644 --- a/src/ui/modals/server_message.c +++ b/src/ui/modals/server_message.c @@ -25,11 +25,11 @@ void server_message_modal_cb(callback_args_t *args) { switch (response) { case 0: destroy_dialogue(d); - // TODO: macros + /* TODO: macros */ free(app->query_args->server_message.text); app->query_args->server_message.capacity = 0; app->query_args->server_message.size = 0; - app->query_args->state = S_ASK_LOGIN_USER; + app->query_args->state = S_NEXT_ACTION; break; } } diff --git a/src/ui/widget/dialogue.c b/src/ui/widget/dialogue.c index 5b73cdb..0fe6a63 100644 --- a/src/ui/widget/dialogue.c +++ b/src/ui/widget/dialogue.c @@ -186,8 +186,6 @@ int32_t draw_dialogue(dialogue_t *d) { x += 1; /* when uses box */ y += 1; /* when uses box */ - /* space for action block */ - d->w.x = x; d->w.y = y; d->w.m_y = (d->p_coordinates->max_y - y) / 2; From f43eadd02875abb7e72c060935ede1dc81e61ce2 Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Wed, 17 Dec 2025 20:32:56 +0200 Subject: [PATCH 22/45] ui: window: file list init --- .gitignore | 3 ++- src/main.c | 6 +++-- src/query.c | 1 - src/ui/app.c | 53 +++++++++++++++++++++++++++--------------- src/ui/app.h | 13 ++++++----- src/ui/widget/button.c | 2 +- src/ui/widget/input.c | 4 ++-- 7 files changed, 50 insertions(+), 32 deletions(-) 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/src/main.c b/src/main.c index ca8ce7e..b8fc0ae 100644 --- a/src/main.c +++ b/src/main.c @@ -44,14 +44,16 @@ int main(int argc, char **argv) { /* 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); destroy_app(app); diff --git a/src/query.c b/src/query.c index 5768280..030aaac 100644 --- a/src/query.c +++ b/src/query.c @@ -32,7 +32,6 @@ void query_loop(app_t *app) { query_args_t *query_args = app->query_args; fd_set readfds; int32_t sd = app->params->sd; - app->query_args->sd = app->params->sd; size_t qlen; int sr; static file_args_t file_args; diff --git a/src/ui/app.c b/src/ui/app.c index 979b74b..79c39b1 100644 --- a/src/ui/app.c +++ b/src/ui/app.c @@ -6,6 +6,7 @@ // #include "modals/server_message.h" // #include "action.h" #include +#include #include #include #include @@ -13,42 +14,45 @@ void ac_file(WINDOW *win, int is_action_w); app_t *init_app() { - // get values from terminal size + /* get values from terminal size */ int y_max, x_max; getmaxyx(stdscr, y_max, x_max); - // allocate app struct memory + /* allocate app struct memory */ app_t *_app = calloc(1, sizeof(app_t)); - // associate values for screen size + /* 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 - int menu_w_x = x_max / 6; - int action_w_x = x_max - menu_w_x - 2; + /* define the width for each sub window */ + int32_t left_w_x = x_max / 3; + int32_t right_w_x = x_max - left_w_x - 2; - // create the menu window - _app->left_win = newwin(y_max - 4, menu_w_x, 2, 1); + /* 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, action_w_x, 2, menu_w_x + 1); + /* create the action window */ + _app->right_win = newwin(y_max - 4, right_w_x, 2, left_w_x + 1); - // dialogue + /* dialogue */ _app->modal.win = NULL; _app->modal.is_initiated = 0; - // print decorative bars + /* default win */ + _app->active_win = aw_left; + + /* print decorative bars */ print_bars(_app); - // here goes box borders + /* here goes box borders */ draw_borders(_app); keypad(_app->win, TRUE); - // refresh the windows + /* refresh the windows */ wnoutrefresh(_app->win); wnoutrefresh(_app->left_win); wnoutrefresh(_app->right_win); @@ -70,15 +74,17 @@ void init_nc() { 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); + init_pair(modal_color_pair, COLOR_BLUE, COLOR_WHITE); } } void draw_borders(app_t *app) { clear(); box(app->win, 0, 0); + box(app->left_win, 0, 0); + box(app->right_win, 0, 0); - // refresh the windows + /* refresh the windows */ wnoutrefresh(app->win); wnoutrefresh(app->left_win); wnoutrefresh(app->right_win); @@ -88,22 +94,31 @@ void draw_borders(app_t *app) { void print_bars(app_t *app) { wattrset(app->win, A_REVERSE); - // print top and bottom bars + /* 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 + /* add content to the top bar */ mvwprintw(app->win, 1, 2, "example cprint_barsurses app | "); mvwprintw(app->win, 1, sizeof "example cprint_barsurses app | " + 1, "size %d, %d", app->coordinates.cur_x, app->coordinates.cur_y); - // add content to the bottom bar + /* add content to the bottom bar */ mvwprintw(app->win, app->coordinates.cur_y - 2, 2, "F1 - Help | F9 - Quit"); wattroff(app->win, A_REVERSE); } + +void draw_file_list(app_t *app) { + WINDOW *win = app->left_win; + int sz_y, sz_x; + getmaxyx(win, sz_y, sz_x); + + +} + void app_refresh(app_t *app) { draw_borders(app); diff --git a/src/ui/app.h b/src/ui/app.h index 3137735..65a7e19 100644 --- a/src/ui/app.h +++ b/src/ui/app.h @@ -6,15 +6,15 @@ #include typedef enum { - MODAL_COLOR_PAIR = 3, - MODAL_BORDER_COLOR_PAIR = 4, + modal_color_pair = 3, + modal_border_color_pair = 4, } color; typedef enum { - none = 0, - login = 1, - ask_server_addr = 2, -} dialogue_e; + aw_left, + aw_right, + aw_modal +} active_win_t; typedef struct { WINDOW *win; @@ -26,6 +26,7 @@ typedef struct { query_args_t *query_args; file_args_t *file_args; int32_t read_fd; + active_win_t active_win; } app_t; app_t *init_app(); diff --git a/src/ui/widget/button.c b/src/ui/widget/button.c index f389dbc..a3e8ebd 100644 --- a/src/ui/widget/button.c +++ b/src/ui/widget/button.c @@ -26,7 +26,7 @@ int32_t draw_button(button_t *btn, uint32_t active_id) { if (btn->w.id == active_id) { sprintf(title, "[< %s >]", btn->w.title); - wattrset(win, COLOR_PAIR(MODAL_COLOR_PAIR) | A_BOLD | A_REVERSE); + 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); diff --git a/src/ui/widget/input.c b/src/ui/widget/input.c index 176ed8c..f9b35b3 100644 --- a/src/ui/widget/input.c +++ b/src/ui/widget/input.c @@ -29,7 +29,7 @@ int32_t draw_input(input_t *input, uint32_t active_id) { 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)); + 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); @@ -50,7 +50,7 @@ int32_t draw_input(input_t *input, uint32_t active_id) { margin_x++; if (input->w.id == active_id) { - wattrset(win, COLOR_PAIR(MODAL_COLOR_PAIR) | A_BOLD | A_REVERSE); + wattrset(win, COLOR_PAIR(modal_color_pair) | A_BOLD | A_REVERSE); } else { wattrset(win, COLOR_PAIR(0) | A_BOLD); } From e3fbe1b40a1ff2cc16d29ffb10905437b1a6d942 Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Thu, 18 Dec 2025 15:06:57 +0200 Subject: [PATCH 23/45] ui: file list (not finished) --- TODO.md | 1 + src/deps.mk | 10 +++++++--- src/file_processor.c | 6 +++--- src/main.c | 8 +++++--- src/main.h | 1 + src/server.c | 21 +++++++++++++-------- src/ui/app.c | 9 +-------- src/ui/widget.h | 2 +- src/ui/widget/file_list.c | 31 +++++++++++++++++++++++++++++++ src/ui/widget/file_list.h | 14 ++++++++++++++ 10 files changed, 77 insertions(+), 26 deletions(-) create mode 100644 src/ui/widget/file_list.c create mode 100644 src/ui/widget/file_list.h diff --git a/TODO.md b/TODO.md index ed94ff1..076307c 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,6 @@ TODO: - [x] More universal Makefile +- [ ] Draw only when something changes in UI - [ ] Check and fix registration and login - [x] UI: destroy child elements everywhere - [x] use parent widgets pointers to count the margins correctly diff --git a/src/deps.mk b/src/deps.mk index b4962d2..2028220 100644 --- a/src/deps.mk +++ b/src/deps.mk @@ -8,17 +8,21 @@ file_processor.o: src/file_processor.c src/client.h src/main.h \ src/types.h src/query.h src/ui/app.h src/ui/../common.h \ src/ui/widget/dialogue.h src/ui/widget/../widget.h \ src/ui/widget/../../main.h src/ui/widget/../../common.h \ - src/ui/widget/button.h src/ui/widget/../../main.h src/ui/widget/group.h + src/ui/widget/button.h src/ui/widget/../../main.h src/ui/widget/group.h \ + src/ui/widget/file_list.h 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/dialogue.h src/ui/widget/../widget.h \ src/ui/widget/../../main.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/dialogue.h + src/query.h src/ui/widget.h src/ui/widget/dialogue.h \ + src/ui/widget/file_list.h query.o: src/query.c src/client.h src/main.h src/types.h src/connection.h \ src/ui/app.h src/ui/../common.h src/ui/widget/dialogue.h \ src/ui/widget/../widget.h src/ui/widget/../../main.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/server.h -server.o: src/server.c src/client.h src/main.h src/types.h src/ui.h +server.o: src/server.c src/client.h src/main.h src/types.h src/ui.h \ + src/ui/widget/file_list.h src/ui/widget/../widget.h \ + src/ui/widget/../../main.h ui.o: src/ui.c diff --git a/src/file_processor.c b/src/file_processor.c index 6fc8ea5..d61dcb3 100644 --- a/src/file_processor.c +++ b/src/file_processor.c @@ -1,6 +1,7 @@ #include "client.h" #include "main.h" #include "query.h" +#include "ui/widget/file_list.h" #include #include #include @@ -36,6 +37,7 @@ void file_list(file_args_t *f_args, query_args_t *q_args) { &query))) { if (!strcmp("list_end\n", query)) { q_args->state = S_FILE_SELECT; + draw_file_list(q_args->file_list_ui, &(f_args->l_start)); idx = 1; break; } @@ -43,8 +45,6 @@ void file_list(file_args_t *f_args, query_args_t *q_args) { qbuf_used += strlen(qbuf); 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); free(query); query = NULL; qbuf_used = 0; @@ -53,7 +53,7 @@ void file_list(file_args_t *f_args, query_args_t *q_args) { while ((qlen = query_extract_from_buf(q_args->buf, &(q_args->buf_used), &query))) { - write(STDOUT_FILENO, query, strlen(query)); + // write(STDOUT_FILENO, query, strlen(query)); } } diff --git a/src/main.c b/src/main.c index b8fc0ae..996f956 100644 --- a/src/main.c +++ b/src/main.c @@ -15,6 +15,7 @@ #include "ui/app.h" #include "ui/widget.h" #include "ui/widget/dialogue.h" +#include "ui/widget/file_list.h" uint32_t m_id = 0; @@ -41,19 +42,20 @@ int main(int argc, char **argv) { 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)); /* 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); destroy_app(app); diff --git a/src/main.h b/src/main.h index 665e856..441ab1c 100644 --- a/src/main.h +++ b/src/main.h @@ -84,6 +84,7 @@ typedef struct query_args { uint64_t capacity; uint64_t size; } server_message; + void *file_list_ui; /* TODO: find solution to insert actual type without recursion */ } query_args_t; #endif \ No newline at end of file diff --git a/src/server.c b/src/server.c index 1bcb513..e14e7ce 100644 --- a/src/server.c +++ b/src/server.c @@ -1,6 +1,7 @@ #include "client.h" #include "main.h" #include "ui.h" +#include "ui/widget/file_list.h" #include #include #include @@ -37,8 +38,14 @@ int process_server_command(char *line, int l_len, query_args_t *q_args) { /* LOGIN */ if (!strncmp(line, "login>", ws_pos)) { - PRINT_SRV_MESSAGE(q_args); - q_args->state = S_ASK_LOGIN_TYPE; + if (q_args->server_message.size > 0) { + PRINT_SRV_MESSAGE(q_args); + } + 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; + } return 0; } @@ -65,9 +72,8 @@ 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; + write(q_args->sd, "file list", sizeof("file list")); + q_args->state = S_FILE_LIST; return 0; } else { write(STDOUT_FILENO, line, strlen(line)); @@ -83,9 +89,8 @@ 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; + write(q_args->sd, "file list", sizeof("file list")); + q_args->state = S_FILE_LIST; return 0; } diff --git a/src/ui/app.c b/src/ui/app.c index 79c39b1..0f2ab65 100644 --- a/src/ui/app.c +++ b/src/ui/app.c @@ -3,6 +3,7 @@ #include "modals/login_credentials.h" #include "modals/login_option.h" #include "modals/server_message.h" +#include "widget/file_list.h" // #include "modals/server_message.h" // #include "action.h" #include @@ -111,14 +112,6 @@ void print_bars(app_t *app) { wattroff(app->win, A_REVERSE); } -void draw_file_list(app_t *app) { - WINDOW *win = app->left_win; - int sz_y, sz_x; - getmaxyx(win, sz_y, sz_x); - - -} - void app_refresh(app_t *app) { draw_borders(app); diff --git a/src/ui/widget.h b/src/ui/widget.h index dad9346..601e9b6 100644 --- a/src/ui/widget.h +++ b/src/ui/widget.h @@ -45,7 +45,7 @@ typedef struct widget_t { } cur; WINDOW *const *parent_win; struct widget_t *w_parent; - enum rsize rsize; + // enum rsize rsize; void (*callback)(callback_args_t *args); } widget_t; diff --git a/src/ui/widget/file_list.c b/src/ui/widget/file_list.c new file mode 100644 index 0000000..c5df124 --- /dev/null +++ b/src/ui/widget/file_list.c @@ -0,0 +1,31 @@ +#include "file_list.h" +#include +#include +#include +#include +#include + +file_list_t *init_file_list(WINDOW **win) { + file_list_t *fl_ui = malloc(sizeof(file_list_t)); + init_widget(&(fl_ui->w), NULL, win,""); + return fl_ui; +} + +void draw_file_list(file_list_t *fl_ui, fl_item_t **fl_start) { + int32_t sz_y, sz_x; + int32_t p_y, p_x; + getmaxyx(*(fl_ui->w.parent_win), sz_y, sz_x); + p_y = 1; + p_x = 1; + fl_item_t *el = *fl_start; + + do { + p_x = 1; + mvwprintw(*(fl_ui->w.parent_win), p_y, p_x, "%s%n", el->name, &p_x); + for (uint i = 1; p_x < sz_x; i++) { + mvwprintw(*(fl_ui->w.parent_win), p_y, ++p_x, " "); + } + p_y++; + } while ((el = el->next) != NULL && p_y < sz_y); + +} \ 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..500b98b --- /dev/null +++ b/src/ui/widget/file_list.h @@ -0,0 +1,14 @@ +#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; +} file_list_t; + +void draw_file_list(file_list_t *fl_ui, fl_item_t **fl_start); +file_list_t *init_file_list(WINDOW **win); +#endif \ No newline at end of file From b669b4be8a22616bc7d3e0eef6cffdde7d49f1b5 Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Fri, 26 Dec 2025 17:34:51 +0200 Subject: [PATCH 24/45] ui: file list --- src/connection.c | 1 + src/deps.mk | 29 +++++----- src/file_processor.c | 5 +- src/main.c | 9 +-- src/query.c | 8 +-- src/ui/app.c | 26 +++++++-- src/ui/app.h | 6 +- src/ui/modals/ask_server_addr.c | 5 +- src/ui/modals/login_credentials.c | 6 +- src/ui/modals/login_option.c | 5 +- src/ui/modals/server_message.c | 5 +- src/ui/widget.h | 1 - src/ui/widget/dialogue.c | 7 ++- src/ui/widget/dialogue.h | 2 +- src/ui/widget/file_list.c | 91 +++++++++++++++++++++++++++++-- src/ui/widget/file_list.h | 7 ++- 16 files changed, 164 insertions(+), 49 deletions(-) diff --git a/src/connection.c b/src/connection.c index 3895a84..a4cab2f 100644 --- a/src/connection.c +++ b/src/connection.c @@ -24,6 +24,7 @@ void connect_to_server(app_t *app) { exit(2); } app->params->is_connected = TRUE; + print_bars(app); } void init_params(params_t *params) { diff --git a/src/deps.mk b/src/deps.mk index 2028220..2875dba 100644 --- a/src/deps.mk +++ b/src/deps.mk @@ -1,24 +1,25 @@ client.o: src/client.c src/file_processor.h src/main.h src/types.h \ src/client.h connection.o: src/connection.c src/main.h src/types.h src/ui/app.h \ - src/ui/../common.h src/ui/widget/dialogue.h src/ui/widget/../widget.h \ - src/ui/widget/../../main.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/client.h src/main.h \ - src/types.h src/query.h src/ui/app.h src/ui/../common.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/../../main.h src/ui/widget/../../common.h \ - src/ui/widget/button.h src/ui/widget/../../main.h src/ui/widget/group.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/client.h src/main.h \ + src/types.h src/query.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/ui/widget/file_list.h 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/dialogue.h src/ui/widget/../widget.h \ - src/ui/widget/../../main.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/dialogue.h \ - src/ui/widget/file_list.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/client.h src/main.h src/types.h src/connection.h \ - src/ui/app.h src/ui/../common.h src/ui/widget/dialogue.h \ - src/ui/widget/../widget.h src/ui/widget/../../main.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/server.h diff --git a/src/file_processor.c b/src/file_processor.c index d61dcb3..a717e14 100644 --- a/src/file_processor.c +++ b/src/file_processor.c @@ -30,6 +30,8 @@ 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; + file_list_t *fui = (file_list_t *) q_args->file_list_ui; + fui->start = f_args->l_start; if (qbuf_used == 0) qbuf[0] = 0; @@ -37,7 +39,7 @@ void file_list(file_args_t *f_args, query_args_t *q_args) { &query))) { if (!strcmp("list_end\n", query)) { q_args->state = S_FILE_SELECT; - draw_file_list(q_args->file_list_ui, &(f_args->l_start)); + draw_file_list(fui); idx = 1; break; } @@ -45,6 +47,7 @@ void file_list(file_args_t *f_args, query_args_t *q_args) { qbuf_used += strlen(qbuf); if (strchr(qbuf, '\n') == NULL) continue; fl_add(&(f_args->l_current), &(f_args->l_start), qbuf); + fui->count++; free(query); query = NULL; qbuf_used = 0; diff --git a/src/main.c b/src/main.c index 996f956..dcae9b5 100644 --- a/src/main.c +++ b/src/main.c @@ -14,7 +14,6 @@ #include "query.h" #include "ui/app.h" #include "ui/widget.h" -#include "ui/widget/dialogue.h" #include "ui/widget/file_list.h" uint32_t m_id = 0; @@ -69,14 +68,10 @@ int32_t process_user_input(app_t *app, callback_args_t *d_args) { switch (c) { case KEY_F(9): destroy_app(app); - return OK; + return ERR; default: - if (app->modal.win != NULL) { d_args->data = (void *)&c; - d_args->win = app->modal.win; - d_args->widget = &(app->modal); - app->modal.w.callback(d_args); - } + app->active_callback(d_args); break; } return OK; diff --git a/src/query.c b/src/query.c index 030aaac..0374af3 100644 --- a/src/query.c +++ b/src/query.c @@ -36,7 +36,7 @@ void query_loop(app_t *app) { int sr; static file_args_t file_args; callback_args_t d_args = { - .app = app, .win = NULL, .widget = NULL, .data = NULL, .resp_data = NULL}; + .app = app, .widget = NULL, .data = NULL, .resp_data = NULL}; init_file_args(&file_args); query_args->sd = sd; @@ -65,7 +65,7 @@ void query_loop(app_t *app) { free(query_args->next_server_command); query_args->next_server_command = NULL; query_args->from_server = TRUE; - if (-1 == process_query(query_args, &file_args)) { + if (ERR == process_query(query_args, &file_args)) { close_session(sd); exit(1); } @@ -88,7 +88,7 @@ void query_loop(app_t *app) { } else { query_args->buf_used = qlen; query_args->from_server = TRUE; - if (-1 == process_query(query_args, &file_args)) { + if (ERR == process_query(query_args, &file_args)) { close_session(sd); exit(1); } @@ -113,7 +113,7 @@ void query_loop(app_t *app) { /* query_args->buf_used = qlen; query_args->from_server = 0; */ // if (-1 == process_query(&query_args, &file_args)) { - if (-1 == process_user_input(app, &d_args)) { + if (ERR == process_user_input(app, &d_args)) { close_session(sd); exit(1); } diff --git a/src/ui/app.c b/src/ui/app.c index 0f2ab65..2aa9845 100644 --- a/src/ui/app.c +++ b/src/ui/app.c @@ -7,16 +7,19 @@ // #include "modals/server_message.h" // #include "action.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 */ - int y_max, x_max; + int32_t y_max, x_max; getmaxyx(stdscr, y_max, x_max); /* allocate app struct memory */ @@ -29,7 +32,7 @@ app_t *init_app() { _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 / 3; + int32_t left_w_x = x_max / 10 * 4; int32_t right_w_x = x_max - left_w_x - 2; /* create the menu window */ @@ -43,7 +46,7 @@ app_t *init_app() { _app->modal.is_initiated = 0; /* default win */ - _app->active_win = aw_left; + _app->active_win_type = aw_left; /* print decorative bars */ print_bars(_app); @@ -93,6 +96,8 @@ void draw_borders(app_t *app) { } void print_bars(app_t *app) { + char top_text[64] = "Hello!"; + wattrset(app->win, A_REVERSE); /* print top and bottom bars */ @@ -102,9 +107,14 @@ void print_bars(app_t *app) { } /* add content to the top bar */ - mvwprintw(app->win, 1, 2, "example cprint_barsurses app | "); - mvwprintw(app->win, 1, sizeof "example cprint_barsurses app | " + 1, - "size %d, %d", app->coordinates.cur_x, app->coordinates.cur_y); + 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 | F9 - Quit"); @@ -160,6 +170,10 @@ void app_draw_modal(app_t *app) { case S_NEXT_ACTION: 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)); } diff --git a/src/ui/app.h b/src/ui/app.h index 65a7e19..945d27d 100644 --- a/src/ui/app.h +++ b/src/ui/app.h @@ -2,6 +2,7 @@ #define N_APP_H #include "../common.h" +#include "widget.h" #include "widget/dialogue.h" #include @@ -26,7 +27,10 @@ typedef struct { query_args_t *query_args; file_args_t *file_args; int32_t read_fd; - active_win_t active_win; + 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(); diff --git a/src/ui/modals/ask_server_addr.c b/src/ui/modals/ask_server_addr.c index 8c1957a..1e663c9 100644 --- a/src/ui/modals/ask_server_addr.c +++ b/src/ui/modals/ask_server_addr.c @@ -18,10 +18,11 @@ 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 *)args->widget; + 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.widget = 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); @@ -30,7 +31,7 @@ void asa_modal_dialogue_callback(callback_args_t *args) { case 0: get_ip_port(app->params, in_ip->value, in_port->value); connect_to_server(app); - destroy_dialogue(d); + destroy_dialogue(d, app); app->query_args->state = S_WAIT_SERVER; break; case 1: diff --git a/src/ui/modals/login_credentials.c b/src/ui/modals/login_credentials.c index 3cfe6d5..ac87088 100644 --- a/src/ui/modals/login_credentials.c +++ b/src/ui/modals/login_credentials.c @@ -18,10 +18,11 @@ 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 *)args->widget; + 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.widget = 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); @@ -36,7 +37,8 @@ void init_login_credentials_modal_cb(callback_args_t *args) { 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; - destroy_dialogue(d); + destroy_dialogue(d,app); + print_bars(app); break; case 1: destroy_app(app); diff --git a/src/ui/modals/login_option.c b/src/ui/modals/login_option.c index 6f33a00..7cfc548 100644 --- a/src/ui/modals/login_option.c +++ b/src/ui/modals/login_option.c @@ -15,17 +15,18 @@ 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 *)args->widget; + 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.widget = app->active_widget; dialogue_default_callback(&d_args); if (response > -1) { switch (response) { case 0: break; case 1: - destroy_dialogue(d); + destroy_dialogue(d, app); app->query_args->state = S_ASK_LOGIN_USER; break; } diff --git a/src/ui/modals/server_message.c b/src/ui/modals/server_message.c index 81a5b1c..505de0f 100644 --- a/src/ui/modals/server_message.c +++ b/src/ui/modals/server_message.c @@ -16,15 +16,16 @@ 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 *)args->widget; + 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.widget = app->active_widget; dialogue_default_callback(&d_args); if (response > -1) { switch (response) { case 0: - destroy_dialogue(d); + destroy_dialogue(d, app); /* TODO: macros */ free(app->query_args->server_message.text); app->query_args->server_message.capacity = 0; diff --git a/src/ui/widget.h b/src/ui/widget.h index 601e9b6..530c991 100644 --- a/src/ui/widget.h +++ b/src/ui/widget.h @@ -14,7 +14,6 @@ enum w_type { }; typedef struct { - WINDOW *win; void *app; void *widget; void *data; diff --git a/src/ui/widget/dialogue.c b/src/ui/widget/dialogue.c index 0fe6a63..2a411c2 100644 --- a/src/ui/widget/dialogue.c +++ b/src/ui/widget/dialogue.c @@ -5,6 +5,7 @@ #include "../app.h" #include "dialogue.h" +#include "file_list.h" #include "group.h" #include "input.h" @@ -251,7 +252,8 @@ int32_t draw_dialogue(dialogue_t *d) { return 0; }; -void destroy_dialogue(dialogue_t *d) { +void destroy_dialogue(dialogue_t *d, void *_app) { + app_t *app = (app_t *)_app; if (d->g_content) { destroy_group(d->g_content); } @@ -261,4 +263,7 @@ void destroy_dialogue(dialogue_t *d) { delwin(d->win); d->win = NULL; d->is_initiated = 0; + app->active_win_type = aw_left; + app->active_win = app->left_win; + app->active_callback = file_list_cb; } \ No newline at end of file diff --git a/src/ui/widget/dialogue.h b/src/ui/widget/dialogue.h index f527318..d91ba1b 100644 --- a/src/ui/widget/dialogue.h +++ b/src/ui/widget/dialogue.h @@ -27,7 +27,7 @@ typedef struct { 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 destroy_dialogue(dialogue_t *d, void *app); void dialogue_default_callback(callback_args_t *args); void dialogue_init_active_id(dialogue_t *dialogue); diff --git a/src/ui/widget/file_list.c b/src/ui/widget/file_list.c index c5df124..af857f0 100644 --- a/src/ui/widget/file_list.c +++ b/src/ui/widget/file_list.c @@ -1,31 +1,114 @@ #include "file_list.h" +#include "../app.h" #include #include #include #include #include +void file_list_cb(callback_args_t *args) { + app_t *app = args->app; + file_list_t *fui = app->query_args->file_list_ui; + int32_t key = *((int32_t *)args->data); + switch (key) { + case KEY_DOWN: + if (fui->current_idx < fui->count - 1) { + fui->current_idx++; + redraw_file_list(fui); + } + break; + case KEY_UP: + if (fui->current_idx > 0) { + fui->current_idx--; + redraw_file_list(fui); + } + break; + } +} + file_list_t *init_file_list(WINDOW **win) { file_list_t *fl_ui = malloc(sizeof(file_list_t)); - init_widget(&(fl_ui->w), NULL, win,""); + init_widget(&(fl_ui->w), NULL, win, ""); + fl_ui->current_idx = 0; + fl_ui->current_page = 0; + fl_ui->pages = 0; + fl_ui->count = 0; return fl_ui; } -void draw_file_list(file_list_t *fl_ui, fl_item_t **fl_start) { +void reset_file_list(file_list_t *fl_ui) { + fl_ui->current_idx = 0; + fl_ui->current_page = 0; + fl_ui->pages = 0; + fl_ui->count = 0; +} + +void draw_file_list(file_list_t *fl_ui) { int32_t sz_y, sz_x; int32_t p_y, p_x; getmaxyx(*(fl_ui->w.parent_win), sz_y, sz_x); p_y = 1; p_x = 1; - fl_item_t *el = *fl_start; + fl_item_t *el = fl_ui->start; + int32_t cur_el_idx = 0; + curs_set(0); do { + if (cur_el_idx == fl_ui->current_idx) { + wattrset(*(fl_ui->w.parent_win), A_BOLD | A_REVERSE); + } p_x = 1; mvwprintw(*(fl_ui->w.parent_win), p_y, p_x, "%s%n", el->name, &p_x); for (uint i = 1; p_x < sz_x; i++) { - mvwprintw(*(fl_ui->w.parent_win), p_y, ++p_x, " "); + mvwprintw(*(fl_ui->w.parent_win), p_y, ++p_x, " "); + } + if (cur_el_idx == fl_ui->current_idx) { + wattroff(*(fl_ui->w.parent_win), A_BOLD | A_REVERSE); } p_y++; + cur_el_idx++; } while ((el = el->next) != NULL && p_y < sz_y); +} +void redraw_file_list(file_list_t *fl_ui) { + static uint32_t active_idx = 0; + int32_t sz_y, sz_x; + int32_t p_y, p_x; + getmaxyx(*(fl_ui->w.parent_win), sz_y, sz_x); + int32_t sz_y_f = sz_y - 2; // factual size (without box) + p_y = 1; + p_x = 1; + fl_item_t *el = fl_ui->start; + int32_t cur_el_idx = 0; + bool sm_size = sz_y_f < fl_ui->count; + bool print_all = false; + int32_t diff = 0; + + if (fl_ui->current_idx >= sz_y_f) { + print_all = true; + diff = fl_ui->current_idx - sz_y_f + 1; + } else if (active_idx - fl_ui->current_idx == 1) { + print_all = true; + } + + do { + if (cur_el_idx == fl_ui->current_idx) { + wattrset(*(fl_ui->w.parent_win), A_BOLD | A_REVERSE); + } else if (cur_el_idx != active_idx && !print_all) { + p_y++; + cur_el_idx++; + continue; + } + p_x = 1; + mvwprintw(*(fl_ui->w.parent_win), p_y-diff, p_x, "%s%n", el->name, &p_x); + for (uint i = 1; p_x < sz_x; i++) { + mvwprintw(*(fl_ui->w.parent_win), p_y-diff, ++p_x, " "); + } + if (cur_el_idx == fl_ui->current_idx) { + wattroff(*(fl_ui->w.parent_win), A_BOLD | A_REVERSE); + } + p_y++; + cur_el_idx++; + } while ((el = el->next) != NULL && p_y-diff < sz_y); + active_idx = fl_ui->current_idx; } \ No newline at end of file diff --git a/src/ui/widget/file_list.h b/src/ui/widget/file_list.h index 500b98b..5a1a254 100644 --- a/src/ui/widget/file_list.h +++ b/src/ui/widget/file_list.h @@ -7,8 +7,13 @@ typedef struct { fl_item_t *current; fl_item_t *start; int32_t current_idx; + uint32_t count; + uint32_t pages; + uint32_t current_page; } file_list_t; -void draw_file_list(file_list_t *fl_ui, fl_item_t **fl_start); +void draw_file_list(file_list_t *fl_ui); +void redraw_file_list(file_list_t *fl_ui); file_list_t *init_file_list(WINDOW **win); +void file_list_cb(callback_args_t *args); #endif \ No newline at end of file From 071bc5951a129f7d1a3114cd08c8bd5bc9850427 Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Mon, 29 Dec 2025 17:12:19 +0200 Subject: [PATCH 25/45] ui optimizations; file list improvements --- src/deps.mk | 4 +- src/file_processor.c | 31 +++++++--- src/file_processor.h | 1 + src/server.c | 14 ++--- src/ui/widget/dialogue.c | 6 +- src/ui/widget/file_list.c | 121 +++++++++++++++++++++----------------- src/ui/widget/file_list.h | 8 ++- src/ui/widget/input.c | 6 +- 8 files changed, 106 insertions(+), 85 deletions(-) diff --git a/src/deps.mk b/src/deps.mk index 2875dba..45f02a5 100644 --- a/src/deps.mk +++ b/src/deps.mk @@ -23,7 +23,5 @@ query.o: src/query.c src/client.h src/main.h src/types.h src/connection.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/server.h -server.o: src/server.c src/client.h src/main.h src/types.h src/ui.h \ - src/ui/widget/file_list.h src/ui/widget/../widget.h \ - src/ui/widget/../../main.h +server.o: src/server.c src/main.h src/types.h src/ui.h ui.o: src/ui.c diff --git a/src/file_processor.c b/src/file_processor.c index a717e14..d33ffff 100644 --- a/src/file_processor.c +++ b/src/file_processor.c @@ -20,7 +20,7 @@ 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); +void fl_clear(fl_item_t **start, fl_item_t **current); static void clear_file_in_query(query_args_t *q_args); @@ -30,10 +30,12 @@ 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; - file_list_t *fui = (file_list_t *) q_args->file_list_ui; - fui->start = f_args->l_start; + file_list_t *fui = (file_list_t *)q_args->file_list_ui; + 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))) { @@ -45,18 +47,29 @@ void file_list(file_args_t *f_args, query_args_t *q_args) { } 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); - fui->count++; + fui->current_count++; free(query); query = NULL; qbuf_used = 0; qbuf[0] = 0; } + uint32_t current_page, pages, current_count, full_count; + while ((qlen = query_extract_from_buf(q_args->buf, &(q_args->buf_used), &query))) { - // write(STDOUT_FILENO, query, strlen(query)); + if (4 == sscanf(query, "=== PAGE %u/%u COUNT: %u/%u ===", ¤t_page, + &pages, ¤t_count, &full_count)) { + fui->current_page = current_page; + fui->current_count = current_count; + fui->full_count = full_count; + fui->pages = pages; + draw_file_list(fui); + return; + } } } @@ -71,7 +84,7 @@ void file_select(file_args_t *f_args, query_args_t *q_args) { uint32_t qlen; char send_buf[256]; struct stat st = {0}; - + if (!(sscanf(buf, "%u", &filenum))) { write(q_args->sd, buf, strlen(buf)); q_args->state = S_FILE_LIST; @@ -286,7 +299,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 index 149b12b..45206f3 100644 --- a/src/file_processor.h +++ b/src/file_processor.h @@ -9,5 +9,6 @@ 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 fl_clear(fl_item_t **start, fl_item_t **arg_current); #endif \ No newline at end of file diff --git a/src/server.c b/src/server.c index e14e7ce..f6b96ee 100644 --- a/src/server.c +++ b/src/server.c @@ -1,7 +1,5 @@ -#include "client.h" #include "main.h" #include "ui.h" -#include "ui/widget/file_list.h" #include #include #include @@ -114,12 +112,12 @@ int process_server_command(char *line, int l_len, query_args_t *q_args) { q_args->server_message.text[q_args->server_message.size + l_len] = 0; } } 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); - } + /* 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); + } */ } return 0; diff --git a/src/ui/widget/dialogue.c b/src/ui/widget/dialogue.c index 2a411c2..5ba5b59 100644 --- a/src/ui/widget/dialogue.c +++ b/src/ui/widget/dialogue.c @@ -199,10 +199,8 @@ int32_t draw_dialogue(dialogue_t *d) { wattrset(d->win, COLOR_PAIR(0) | A_BOLD | A_REVERSE); /* background */ - for (int xp = 1; xp < d->w.x; xp++) { - for (int yp = 1; yp < d->w.y - 1; yp++) { - mvwprintw(d->win, yp, xp, " "); - } + for (int yp = 1; yp < d->w.y - 1; yp++) { + mvwprintw(d->win, yp, 1, "%*s", d->w.x, ""); } /* title */ diff --git a/src/ui/widget/file_list.c b/src/ui/widget/file_list.c index af857f0..bf27b9e 100644 --- a/src/ui/widget/file_list.c +++ b/src/ui/widget/file_list.c @@ -1,26 +1,46 @@ #include "file_list.h" +#include "../../file_processor.h" #include "../app.h" #include +#include #include +#include #include -#include #include +#include + +void reset_file_list(file_list_t *fl_ui); void file_list_cb(callback_args_t *args) { app_t *app = args->app; file_list_t *fui = app->query_args->file_list_ui; int32_t key = *((int32_t *)args->data); + char switch_page_query[10]; + int32_t spq_len = 0; switch (key) { case KEY_DOWN: - if (fui->current_idx < fui->count - 1) { + if (fui->current_idx < fui->current_count - 1) { fui->current_idx++; - redraw_file_list(fui); + 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(switch_page_query, "p%u\n%n", fui->current_page + 1, &spq_len); + write(app->query_args->sd, switch_page_query, spq_len); + reset_file_list(fui); } break; case KEY_UP: if (fui->current_idx > 0) { fui->current_idx--; - redraw_file_list(fui); + draw_file_list(fui); + } else if (fui->current_page > 1) { + fl_clear(fui->start, fui->current); + app->query_args->state = S_FILE_LIST; + sprintf(switch_page_query, "p%u\n%n", fui->current_page - 1, &spq_len); + write(app->query_args->sd, switch_page_query, spq_len); + reset_file_list(fui); + fui->activate_last = true; } break; } @@ -32,7 +52,9 @@ file_list_t *init_file_list(WINDOW **win) { fl_ui->current_idx = 0; fl_ui->current_page = 0; fl_ui->pages = 0; - fl_ui->count = 0; + fl_ui->current_count = 0; + fl_ui->full_count = 0; + fl_ui->activate_last = false; return fl_ui; } @@ -40,75 +62,66 @@ void reset_file_list(file_list_t *fl_ui) { fl_ui->current_idx = 0; fl_ui->current_page = 0; fl_ui->pages = 0; - fl_ui->count = 0; + fl_ui->current_count = 0; + fl_ui->full_count = 0; + fl_ui->activate_last = false; } void draw_file_list(file_list_t *fl_ui) { - int32_t sz_y, sz_x; - int32_t p_y, p_x; - getmaxyx(*(fl_ui->w.parent_win), sz_y, sz_x); - p_y = 1; - p_x = 1; - fl_item_t *el = fl_ui->start; - int32_t cur_el_idx = 0; - curs_set(0); - - do { - if (cur_el_idx == fl_ui->current_idx) { - wattrset(*(fl_ui->w.parent_win), A_BOLD | A_REVERSE); - } - p_x = 1; - mvwprintw(*(fl_ui->w.parent_win), p_y, p_x, "%s%n", el->name, &p_x); - for (uint i = 1; p_x < sz_x; i++) { - mvwprintw(*(fl_ui->w.parent_win), p_y, ++p_x, " "); - } - if (cur_el_idx == fl_ui->current_idx) { - wattroff(*(fl_ui->w.parent_win), A_BOLD | A_REVERSE); - } - p_y++; - cur_el_idx++; - } while ((el = el->next) != NULL && p_y < sz_y); -} - -void redraw_file_list(file_list_t *fl_ui) { - static uint32_t active_idx = 0; int32_t sz_y, sz_x; int32_t p_y, p_x; getmaxyx(*(fl_ui->w.parent_win), sz_y, sz_x); int32_t sz_y_f = sz_y - 2; // factual size (without box) p_y = 1; p_x = 1; - fl_item_t *el = fl_ui->start; + fl_item_t *el = *(fl_ui->start); int32_t cur_el_idx = 0; - bool sm_size = sz_y_f < fl_ui->count; - bool print_all = false; - int32_t diff = 0; + WINDOW *win = *(fl_ui->w.parent_win); + curs_set(0); - if (fl_ui->current_idx >= sz_y_f) { - print_all = true; - diff = fl_ui->current_idx - sz_y_f + 1; - } else if (active_idx - fl_ui->current_idx == 1) { - print_all = true; + 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 (cur_el_idx == fl_ui->current_idx) { - wattrset(*(fl_ui->w.parent_win), A_BOLD | A_REVERSE); - } else if (cur_el_idx != active_idx && !print_all) { + if (p_y < 1) { p_y++; cur_el_idx++; continue; } - p_x = 1; - mvwprintw(*(fl_ui->w.parent_win), p_y-diff, p_x, "%s%n", el->name, &p_x); - for (uint i = 1; p_x < sz_x; i++) { - mvwprintw(*(fl_ui->w.parent_win), p_y-diff, ++p_x, " "); - } if (cur_el_idx == fl_ui->current_idx) { - wattroff(*(fl_ui->w.parent_win), A_BOLD | A_REVERSE); + wattrset(win, A_BOLD | A_REVERSE); + } + 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 - p_x, ""); + if (cur_el_idx == fl_ui->current_idx) { + wattroff(win, A_BOLD | A_REVERSE); } p_y++; cur_el_idx++; - } while ((el = el->next) != NULL && p_y-diff < sz_y); - active_idx = fl_ui->current_idx; + } 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 - 1, ""); + } + char p_info[24]; + uint32_t p_len; + p_x = 1; + + sprintf(p_info, "page: %u/%u files: %u/%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(win, A_BOLD); + mvwprintw(win, p_y, p_x, "%*s%s%*s", l_pad, "", p_info, l_pad, ""); + wattroff(win, A_BOLD); } \ No newline at end of file diff --git a/src/ui/widget/file_list.h b/src/ui/widget/file_list.h index 5a1a254..afc996d 100644 --- a/src/ui/widget/file_list.h +++ b/src/ui/widget/file_list.h @@ -4,12 +4,14 @@ #include typedef struct { widget_t w; - fl_item_t *current; - fl_item_t *start; + fl_item_t **current; + fl_item_t **start; int32_t current_idx; - uint32_t count; + uint32_t current_count; + uint32_t full_count; uint32_t pages; uint32_t current_page; + uint32_t activate_last : 1; } file_list_t; void draw_file_list(file_list_t *fl_ui); diff --git a/src/ui/widget/input.c b/src/ui/widget/input.c index f9b35b3..bccf676 100644 --- a/src/ui/widget/input.c +++ b/src/ui/widget/input.c @@ -56,12 +56,10 @@ int32_t draw_input(input_t *input, uint32_t active_id) { } uint32_t x_c = 0; - mvwprintw(win, margin_y + 1, margin_x, "%s", input->value); + 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; - for (int i = 0; i < input->w.x - input->value_len - 2; i++) { - mvwprintw(win, margin_y + 1, margin_x + i + input->value_len, " "); - } wattroff(win, A_BOLD | A_REVERSE); return 0; From f1757b4ca756f66841f82175ffd86280b2d02d0b Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Tue, 30 Dec 2025 19:19:54 +0200 Subject: [PATCH 26/45] ui: progress bar for file download --- TODO.md | 4 +-- src/deps.mk | 8 +++-- src/file_processor.c | 60 +++++++++++++++++++++++++++++++----- src/file_processor.h | 2 ++ src/main.h | 1 + src/query.c | 2 ++ src/ui/app.c | 7 +++-- src/ui/modals/download_pr.c | 35 +++++++++++++++++++++ src/ui/modals/download_pr.h | 9 ++++++ src/ui/widget.h | 1 + src/ui/widget/file_list.c | 30 ++++++++++-------- src/ui/widget/file_list.h | 8 ++--- src/ui/widget/group.c | 11 +++++++ src/ui/widget/input.c | 2 -- src/ui/widget/progress_bar.c | 58 ++++++++++++++++++++++++++++++++++ src/ui/widget/progress_bar.h | 12 ++++++++ 16 files changed, 218 insertions(+), 32 deletions(-) create mode 100644 src/ui/modals/download_pr.c create mode 100644 src/ui/modals/download_pr.h create mode 100644 src/ui/widget/progress_bar.c create mode 100644 src/ui/widget/progress_bar.h diff --git a/TODO.md b/TODO.md index 076307c..bf705ab 100644 --- a/TODO.md +++ b/TODO.md @@ -7,8 +7,8 @@ TODO: - [ ] Hotkeys for buttons - [ ] Simpler refresh on typing / entering in input - [ ] Group elements as lists -- [ ] Process arrows -- [ ] active_id -> просто указатель на элемент +- [x] Process arrows +- [ ] Optimize modals re-drawings INPUT: - [ ] hidden text for passwords diff --git a/src/deps.mk b/src/deps.mk index 45f02a5..080b537 100644 --- a/src/deps.mk +++ b/src/deps.mk @@ -1,5 +1,6 @@ client.o: src/client.c src/file_processor.h src/main.h src/types.h \ - src/client.h + src/ui/widget/file_list.h src/ui/widget/../widget.h \ + src/ui/widget/../../main.h src/client.h 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 \ @@ -10,7 +11,8 @@ file_processor.o: src/file_processor.c src/client.h src/main.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/ui/widget/file_list.h + src/ui/widget/dialogue.h src/ui/widget/file_list.h \ + src/ui/widget/progress_bar.h 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 \ @@ -22,6 +24,6 @@ query.o: src/query.c src/client.h src/main.h src/types.h src/connection.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/server.h + src/ui/widget/file_list.h src/server.h server.o: src/server.c src/main.h src/types.h src/ui.h ui.o: src/ui.c diff --git a/src/file_processor.c b/src/file_processor.c index d33ffff..3d4f92f 100644 --- a/src/file_processor.c +++ b/src/file_processor.c @@ -1,7 +1,9 @@ #include "client.h" #include "main.h" #include "query.h" +#include "ui/widget/dialogue.h" #include "ui/widget/file_list.h" +#include "ui/widget/progress_bar.h" #include #include #include @@ -19,7 +21,7 @@ #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); +fl_item_t *fl_select(fl_item_t *start, int num); void fl_clear(fl_item_t **start, fl_item_t **current); static void clear_file_in_query(query_args_t *q_args); @@ -30,7 +32,7 @@ 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; - file_list_t *fui = (file_list_t *)q_args->file_list_ui; + ui_file_list_t *fui = (ui_file_list_t *)q_args->file_list_ui; fui->start = &(f_args->l_start); fui->current = &(f_args->l_current); @@ -73,6 +75,7 @@ void file_list(file_args_t *f_args, query_args_t *q_args) { } } +/* 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; @@ -131,16 +134,61 @@ void file_select(file_args_t *f_args, query_args_t *q_args) { 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); + print_prompt(q_args->params); + 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) * 1; /* every 1% */ if (it_interval == 0) it_interval = 1; } + 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; if (size_rest == 0) size_rest = f_selected->size; @@ -148,7 +196,7 @@ 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); + pb->procent = progress; } if (it_count % it_interval) q_args->buf_used = 0; @@ -161,10 +209,8 @@ 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); free(f_selected->name); - print_prompt(q_args->params); q_args->state = WAIT_CLIENT; } } @@ -287,7 +333,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) { diff --git a/src/file_processor.h b/src/file_processor.h index 45206f3..0c98c71 100644 --- a/src/file_processor.h +++ b/src/file_processor.h @@ -1,6 +1,7 @@ #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); @@ -10,5 +11,6 @@ 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 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); #endif \ No newline at end of file diff --git a/src/main.h b/src/main.h index 441ab1c..5e25b7e 100644 --- a/src/main.h +++ b/src/main.h @@ -85,6 +85,7 @@ typedef struct query_args { uint64_t size; } server_message; void *file_list_ui; /* TODO: find solution to insert actual type without recursion */ + void *progress_bar; } query_args_t; #endif \ No newline at end of file diff --git a/src/query.c b/src/query.c index 0374af3..a954283 100644 --- a/src/query.c +++ b/src/query.c @@ -40,6 +40,7 @@ void query_loop(app_t *app) { init_file_args(&file_args); query_args->sd = sd; + app->file_args = &file_args; for (;;) { /* update screen */ @@ -260,6 +261,7 @@ void init_query_args(query_args_t *q_args, params_t *params) { q_args->server_message.capacity = 0; q_args->server_message.size = 0; q_args->next_server_command = NULL; + q_args->progress_bar = NULL; } void user_request_description(query_args_t *q_args) { diff --git a/src/ui/app.c b/src/ui/app.c index 2aa9845..8cc4ae0 100644 --- a/src/ui/app.c +++ b/src/ui/app.c @@ -1,9 +1,10 @@ #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/server_message.h" -#include "widget/file_list.h" +#include "widget/progress_bar.h" // #include "modals/server_message.h" // #include "action.h" #include @@ -151,6 +152,9 @@ void app_draw_modal(app_t *app) { case S_PRINT_SERVER_MESSAGE: init_server_message_modal(app); break; + case S_FILE_DOWNLOAD: + init_dwn_pr_modal(app); + break; case WAIT_SERVER_INIT: case WAIT_SERVER: case WAIT_REGISTER: @@ -159,7 +163,6 @@ void app_draw_modal(app_t *app) { case UPLOAD_FILE: case S_FILE_LIST: case S_FILE_SELECT: - case S_FILE_DOWNLOAD: case S_UPLOAD_PARAMS: case S_UPLOAD_FILE: case S_UPLOAD_REQUESTED: diff --git a/src/ui/modals/download_pr.c b/src/ui/modals/download_pr.c new file mode 100644 index 0000000..86ed3ac --- /dev/null +++ b/src/ui/modals/download_pr.c @@ -0,0 +1,35 @@ +#include "../app.h" +#include "../widget/dialogue.h" +#include "../widget/group.h" +#include +#include +#include + +void init_dwn_pr_modal_cb(callback_args_t *args) { +} + +dialogue_t *init_dwn_pr_modal(app_t *app) { + 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, "Downloading file: %s", app->file_args->f_selected.name); + 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), "Download", text, &(app->coordinates)); + dialogue_t *d = &(app->modal); + + d->w.callback = init_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; + + 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..adc5c5e --- /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); + +#endif \ No newline at end of file diff --git a/src/ui/widget.h b/src/ui/widget.h index 530c991..9052f20 100644 --- a/src/ui/widget.h +++ b/src/ui/widget.h @@ -11,6 +11,7 @@ enum w_type { w_box, w_group, w_input, + w_progress, }; typedef struct { diff --git a/src/ui/widget/file_list.c b/src/ui/widget/file_list.c index bf27b9e..55081bc 100644 --- a/src/ui/widget/file_list.c +++ b/src/ui/widget/file_list.c @@ -1,6 +1,7 @@ #include "file_list.h" #include "../../file_processor.h" #include "../app.h" +#include "progress_bar.h" #include #include #include @@ -9,14 +10,16 @@ #include #include -void reset_file_list(file_list_t *fl_ui); +void reset_file_list(ui_file_list_t *fl_ui); void file_list_cb(callback_args_t *args) { app_t *app = args->app; - file_list_t *fui = app->query_args->file_list_ui; + ui_file_list_t *fui = app->query_args->file_list_ui; int32_t key = *((int32_t *)args->data); - char switch_page_query[10]; - int32_t spq_len = 0; + 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) { @@ -25,8 +28,8 @@ void file_list_cb(callback_args_t *args) { } else if (fui->current_page < fui->pages) { fl_clear(fui->start, fui->current); app->query_args->state = S_FILE_LIST; - sprintf(switch_page_query, "p%u\n%n", fui->current_page + 1, &spq_len); - write(app->query_args->sd, switch_page_query, spq_len); + sprintf(query, "p%u\n%n", fui->current_page + 1, &q_len); + write(app->query_args->sd, query, q_len); reset_file_list(fui); } break; @@ -37,17 +40,20 @@ void file_list_cb(callback_args_t *args) { } else if (fui->current_page > 1) { fl_clear(fui->start, fui->current); app->query_args->state = S_FILE_LIST; - sprintf(switch_page_query, "p%u\n%n", fui->current_page - 1, &spq_len); - write(app->query_args->sd, switch_page_query, spq_len); + sprintf(query, "p%u\n%n", 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; } } -file_list_t *init_file_list(WINDOW **win) { - file_list_t *fl_ui = malloc(sizeof(file_list_t)); +ui_file_list_t *init_file_list(WINDOW **win) { + ui_file_list_t *fl_ui = malloc(sizeof(ui_file_list_t)); init_widget(&(fl_ui->w), NULL, win, ""); fl_ui->current_idx = 0; fl_ui->current_page = 0; @@ -58,7 +64,7 @@ file_list_t *init_file_list(WINDOW **win) { return fl_ui; } -void reset_file_list(file_list_t *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; @@ -67,7 +73,7 @@ void reset_file_list(file_list_t *fl_ui) { fl_ui->activate_last = false; } -void draw_file_list(file_list_t *fl_ui) { +void draw_file_list(ui_file_list_t *fl_ui) { int32_t sz_y, sz_x; int32_t p_y, p_x; getmaxyx(*(fl_ui->w.parent_win), sz_y, sz_x); diff --git a/src/ui/widget/file_list.h b/src/ui/widget/file_list.h index afc996d..c91df5d 100644 --- a/src/ui/widget/file_list.h +++ b/src/ui/widget/file_list.h @@ -12,10 +12,10 @@ typedef struct { uint32_t pages; uint32_t current_page; uint32_t activate_last : 1; -} file_list_t; +} ui_file_list_t; -void draw_file_list(file_list_t *fl_ui); -void redraw_file_list(file_list_t *fl_ui); -file_list_t *init_file_list(WINDOW **win); +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); void file_list_cb(callback_args_t *args); #endif \ No newline at end of file diff --git a/src/ui/widget/group.c b/src/ui/widget/group.c index 076c891..4c93138 100644 --- a/src/ui/widget/group.c +++ b/src/ui/widget/group.c @@ -1,6 +1,7 @@ #include "group.h" #include "button.h" #include "input.h" +#include "progress_bar.h" #include #include #include @@ -87,6 +88,10 @@ group_t *init_group(WINDOW **win, widget_t *w_parent, group_el_init_t *children, init_input(win, &(group->w), children[i].label, children[i].length); 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; } // set dimensions elements[i].id = w->id; @@ -125,6 +130,9 @@ void draw_group(WINDOW *win, group_t *group, int32_t active_id, 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; } } } @@ -144,6 +152,9 @@ void destroy_group(group_t *group) { case w_input: destroy_input(el->element); break; + case w_progress: + destroy_ui_progress_bar(el->element); + break; } } if (group->elements != NULL) { diff --git a/src/ui/widget/input.c b/src/ui/widget/input.c index bccf676..aa56fbc 100644 --- a/src/ui/widget/input.c +++ b/src/ui/widget/input.c @@ -45,8 +45,6 @@ int32_t draw_input(input_t *input, uint32_t active_id) { 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); - // TODO: correct sizes (x) - margin_x++; if (input->w.id == active_id) { 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 From 48460e4aa212842bcd513f076bf097041ea1d4d3 Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Wed, 31 Dec 2025 15:50:46 +0200 Subject: [PATCH 27/45] ui: optimization of dialogue refresh --- src/file_processor.c | 20 ++++++++++++-------- src/main.h | 1 + src/query.c | 1 + src/ui/app.c | 13 +++++++++---- src/ui/modals/download_pr.c | 1 + src/ui/widget/dialogue.c | 27 ++++++++++++++++++--------- src/ui/widget/dialogue.h | 2 ++ 7 files changed, 44 insertions(+), 21 deletions(-) diff --git a/src/file_processor.c b/src/file_processor.c index 3d4f92f..c330a3f 100644 --- a/src/file_processor.c +++ b/src/file_processor.c @@ -1,9 +1,3 @@ -#include "client.h" -#include "main.h" -#include "query.h" -#include "ui/widget/dialogue.h" -#include "ui/widget/file_list.h" -#include "ui/widget/progress_bar.h" #include #include #include @@ -20,6 +14,13 @@ #include #include +#include "client.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 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); @@ -158,7 +159,7 @@ int32_t ui_file_select(file_args_t *f_args, query_args_t *q_args, int32_t idx) { 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); + /* 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); @@ -181,7 +182,7 @@ void file_download(file_args_t *f_args, query_args_t *q_args) { static uint32_t it_count = 0; static size_t it_interval = 0; if (it_interval == 0) { - it_interval = (f_selected->size / INBUFSIZE / 100) * 1; /* every 1% */ + it_interval = (f_selected->size / INBUFSIZE / 100) * 5; /* every 1% */ if (it_interval == 0) it_interval = 1; } @@ -189,6 +190,7 @@ void file_download(file_args_t *f_args, query_args_t *q_args) { 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; if (size_rest == 0) size_rest = f_selected->size; @@ -197,6 +199,7 @@ void file_download(file_args_t *f_args, query_args_t *q_args) { it_count++; if (!(it_count % it_interval)) { pb->procent = progress; + d->needs_update = true; } if (it_count % it_interval) q_args->buf_used = 0; @@ -211,6 +214,7 @@ void file_download(file_args_t *f_args, query_args_t *q_args) { f_selected->size = 0; close(f_args->file_d); free(f_selected->name); + d->needs_destroy = true; q_args->state = WAIT_CLIENT; } } diff --git a/src/main.h b/src/main.h index 5e25b7e..6dba465 100644 --- a/src/main.h +++ b/src/main.h @@ -86,6 +86,7 @@ typedef struct query_args { } server_message; void *file_list_ui; /* TODO: find solution to insert actual type without recursion */ void *progress_bar; + void *active_dialogue; } query_args_t; #endif \ No newline at end of file diff --git a/src/query.c b/src/query.c index a954283..a72187d 100644 --- a/src/query.c +++ b/src/query.c @@ -262,6 +262,7 @@ void init_query_args(query_args_t *q_args, params_t *params) { q_args->server_message.size = 0; q_args->next_server_command = NULL; q_args->progress_bar = NULL; + q_args->active_dialogue = NULL; } void user_request_description(query_args_t *q_args) { diff --git a/src/ui/app.c b/src/ui/app.c index 8cc4ae0..75f6131 100644 --- a/src/ui/app.c +++ b/src/ui/app.c @@ -4,9 +4,7 @@ #include "modals/login_credentials.h" #include "modals/login_option.h" #include "modals/server_message.h" -#include "widget/progress_bar.h" -// #include "modals/server_message.h" -// #include "action.h" +#include "widget/dialogue.h" #include #include #include @@ -84,6 +82,9 @@ void init_nc() { } 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); @@ -178,7 +179,11 @@ void app_draw_modal(app_t *app) { app->active_win_type = aw_modal; app->active_widget = &(app->modal); } - draw_dialogue(&(app->modal)); + if (app->modal.needs_destroy) { + destroy_dialogue(&(app->modal), app); + } else { + draw_dialogue(&(app->modal)); + } } void destroy_app(app_t *app) { diff --git a/src/ui/modals/download_pr.c b/src/ui/modals/download_pr.c index 86ed3ac..4a77bec 100644 --- a/src/ui/modals/download_pr.c +++ b/src/ui/modals/download_pr.c @@ -28,6 +28,7 @@ dialogue_t *init_dwn_pr_modal(app_t *app) { 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); diff --git a/src/ui/widget/dialogue.c b/src/ui/widget/dialogue.c index 5ba5b59..f0321c5 100644 --- a/src/ui/widget/dialogue.c +++ b/src/ui/widget/dialogue.c @@ -52,6 +52,7 @@ void dialogue_default_callback(callback_args_t *args) { memcpy(&new_args, args, sizeof(callback_args_t)); new_args.active_id = d->active.id; int32_t diff; + d->needs_update = true; switch (key) { case KEY_RIGHT: case '\t': @@ -61,7 +62,7 @@ void dialogue_default_callback(callback_args_t *args) { INCR_ACTIVE_ID(d, g_action, g_content); } *resp_value = -1; - draw_dialogue(d); + break; case KEY_LEFT: if (d->active.type == g_content) { @@ -70,7 +71,7 @@ void dialogue_default_callback(callback_args_t *args) { DECR_ACTIVE_ID(d, g_action, g_content); } *resp_value = -1; - draw_dialogue(d); + break; case '\n': if (d->active.type == g_action) { @@ -88,15 +89,15 @@ void dialogue_default_callback(callback_args_t *args) { *resp_value = d->active.id - d->g_content->first_id; return; } - draw_dialogue(d); + break; case KEY_UP: CH_GROUP(d, g_action, g_content); - draw_dialogue(d); + break; case KEY_DOWN: CH_GROUP(d, g_content, g_action); - draw_dialogue(d); + break; default: /* run callback function */ @@ -106,9 +107,6 @@ void dialogue_default_callback(callback_args_t *args) { new_args.widget = new_args.widget = d->g_action; } group_default_callback(&new_args); - if (*resp_value == -1) { - draw_dialogue(d); - } } } @@ -120,7 +118,9 @@ void init_dialogue(dialogue_t *dialogue, const char title[], const char text[], dialogue->w.y = 0; dialogue->p_coordinates = p_coordinates; dialogue->w.callback = dialogue_default_callback; - dialogue->is_initiated = 1; + dialogue->is_initiated = true; + dialogue->needs_update = true; + dialogue->needs_destroy = false; strcpy(dialogue->w.title, title); strcpy(dialogue->text, text); } @@ -166,6 +166,10 @@ void dialogue_init_active_id(dialogue_t *dialogue) { 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 */ @@ -196,6 +200,7 @@ int32_t draw_dialogue(dialogue_t *d) { 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 */ @@ -215,6 +220,7 @@ int32_t draw_dialogue(dialogue_t *d) { /* 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)); } @@ -247,6 +253,8 @@ int32_t draw_dialogue(dialogue_t *d) { } } + d->needs_update = false; + return 0; }; @@ -264,4 +272,5 @@ void destroy_dialogue(dialogue_t *d, void *_app) { app->active_win_type = aw_left; app->active_win = app->left_win; app->active_callback = file_list_cb; + app->query_args->active_dialogue = NULL; } \ No newline at end of file diff --git a/src/ui/widget/dialogue.h b/src/ui/widget/dialogue.h index d91ba1b..87d62c3 100644 --- a/src/ui/widget/dialogue.h +++ b/src/ui/widget/dialogue.h @@ -21,6 +21,8 @@ typedef struct { } 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; From fded75e3a830721139c76fc3e251becbabe94860 Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Wed, 31 Dec 2025 18:35:22 +0200 Subject: [PATCH 28/45] ui: notification when a file is downloaded --- TODO.md | 5 +++- src/file_processor.c | 15 ++++++++--- src/main.h | 1 + src/query.c | 1 + src/server.c | 2 +- src/ui/app.c | 17 +++++++----- src/ui/modals/notification.c | 51 ++++++++++++++++++++++++++++++++++++ src/ui/modals/notification.h | 9 +++++++ 8 files changed, 90 insertions(+), 11 deletions(-) create mode 100644 src/ui/modals/notification.c create mode 100644 src/ui/modals/notification.h diff --git a/TODO.md b/TODO.md index bf705ab..bde70e7 100644 --- a/TODO.md +++ b/TODO.md @@ -8,7 +8,10 @@ TODO: - [ ] Simpler refresh on typing / entering in input - [ ] Group elements as lists - [x] Process arrows -- [ ] Optimize modals re-drawings +- [x] Optimize modals re-drawings +- [ ] Bug with file list on downloading files +- [ ] Remember page numbers +- [ ] "Server message" to notification INPUT: - [ ] hidden text for passwords diff --git a/src/file_processor.c b/src/file_processor.c index c330a3f..bb6ade0 100644 --- a/src/file_processor.c +++ b/src/file_processor.c @@ -34,6 +34,7 @@ void file_list(file_args_t *f_args, query_args_t *q_args) { 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); @@ -45,6 +46,9 @@ void file_list(file_args_t *f_args, query_args_t *q_args) { if (!strcmp("list_end\n", query)) { q_args->state = S_FILE_SELECT; draw_file_list(fui); + if (d != NULL && d->is_initiated) { + d->needs_update = true; + } idx = 1; break; } @@ -159,7 +163,7 @@ int32_t ui_file_select(file_args_t *f_args, query_args_t *q_args, int32_t idx) { 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); */ + 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); @@ -186,6 +190,7 @@ void file_download(file_args_t *f_args, query_args_t *q_args) { 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; @@ -213,9 +218,13 @@ void file_download(file_args_t *f_args, query_args_t *q_args) { it_interval = 0; f_selected->size = 0; close(f_args->file_d); - free(f_selected->name); d->needs_destroy = true; - q_args->state = WAIT_CLIENT; + 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); + write(q_args->sd, "file list\n", sizeof("file list\n")-1); + q_args->state = S_FILE_LIST; } } } diff --git a/src/main.h b/src/main.h index 6dba465..54f857f 100644 --- a/src/main.h +++ b/src/main.h @@ -87,6 +87,7 @@ typedef struct query_args { void *file_list_ui; /* TODO: find solution to insert actual type without recursion */ void *progress_bar; void *active_dialogue; + char *notification; } query_args_t; #endif \ No newline at end of file diff --git a/src/query.c b/src/query.c index a72187d..5a9638e 100644 --- a/src/query.c +++ b/src/query.c @@ -263,6 +263,7 @@ void init_query_args(query_args_t *q_args, params_t *params) { 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) { diff --git a/src/server.c b/src/server.c index f6b96ee..604f8d5 100644 --- a/src/server.c +++ b/src/server.c @@ -87,7 +87,7 @@ int process_server_command(char *line, int l_len, query_args_t *q_args) { /* WELCOME MES */ if (!strncmp(line, "Welcome, ", ws_pos)) { - write(q_args->sd, "file list", sizeof("file list")); + write(q_args->sd, "file list\n", sizeof("file list\n")-1); q_args->state = S_FILE_LIST; return 0; } diff --git a/src/ui/app.c b/src/ui/app.c index 75f6131..f24b12d 100644 --- a/src/ui/app.c +++ b/src/ui/app.c @@ -3,6 +3,7 @@ #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 "widget/dialogue.h" #include @@ -139,6 +140,10 @@ void app_refresh(app_t *app) { } void app_draw_modal(app_t *app) { + if (app->modal.needs_destroy) { + destroy_dialogue(&(app->modal), app); + app_refresh(app); + } if (!app->modal.is_initiated) { switch (app->query_args->state) { case S_ASK_SEVER_IP: @@ -172,18 +177,18 @@ void app_draw_modal(app_t *app) { case S_WAIT_SERVER: case S_N_D: case S_NEXT_ACTION: - return; + 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); } - if (app->modal.needs_destroy) { - destroy_dialogue(&(app->modal), app); - } else { - draw_dialogue(&(app->modal)); - } + draw_dialogue(&(app->modal)); } void destroy_app(app_t *app) { diff --git a/src/ui/modals/notification.c b/src/ui/modals/notification.c new file mode 100644 index 0000000..c9ddd6c --- /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.widget = app->active_widget; + dialogue_default_callback(&d_args); + + app->query_args->state = WAIT_CLIENT; + destroy_dialogue(d, app); +} + +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 From 6a9dad95579763cb9ecd340b34188e37cdb265cc Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Thu, 1 Jan 2026 13:45:01 +0200 Subject: [PATCH 29/45] ui: possibility to hide input value --- TODO.md | 2 -- src/ui/modals/login_credentials.c | 11 +++++++---- src/ui/widget/group.c | 2 +- src/ui/widget/group.h | 1 + src/ui/widget/input.c | 14 +++++++++++--- src/ui/widget/input.h | 4 +++- 6 files changed, 23 insertions(+), 11 deletions(-) diff --git a/TODO.md b/TODO.md index bde70e7..6a32339 100644 --- a/TODO.md +++ b/TODO.md @@ -1,11 +1,9 @@ TODO: - [x] More universal Makefile -- [ ] Draw only when something changes in UI - [ ] 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 -- [ ] Simpler refresh on typing / entering in input - [ ] Group elements as lists - [x] Process arrows - [x] Optimize modals re-drawings diff --git a/src/ui/modals/login_credentials.c b/src/ui/modals/login_credentials.c index ac87088..b5dd5d9 100644 --- a/src/ui/modals/login_credentials.c +++ b/src/ui/modals/login_credentials.c @@ -37,7 +37,7 @@ void init_login_credentials_modal_cb(callback_args_t *args) { 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; - destroy_dialogue(d,app); + destroy_dialogue(d, app); print_bars(app); break; case 1: @@ -52,12 +52,15 @@ dialogue_t *init_login_credentials_modal(app_t *app) { return NULL; group_el_init_t content[] = { {.type = w_input, .label = "Username", .length = 15}, - {.type = w_input, .label = "Password", .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 = 1}, - {.type = w_button, .label = "Cancel", .is_default = 0}, + {.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", diff --git a/src/ui/widget/group.c b/src/ui/widget/group.c index 4c93138..d5f1f62 100644 --- a/src/ui/widget/group.c +++ b/src/ui/widget/group.c @@ -85,7 +85,7 @@ group_t *init_group(WINDOW **win, widget_t *w_parent, group_el_init_t *children, break; case w_input: elements[i].element = - init_input(win, &(group->w), children[i].label, children[i].length); + 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: diff --git a/src/ui/widget/group.h b/src/ui/widget/group.h index 73681be..291aa8b 100644 --- a/src/ui/widget/group.h +++ b/src/ui/widget/group.h @@ -19,6 +19,7 @@ typedef struct { 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; diff --git a/src/ui/widget/input.c b/src/ui/widget/input.c index aa56fbc..26a066f 100644 --- a/src/ui/widget/input.c +++ b/src/ui/widget/input.c @@ -8,7 +8,7 @@ #include "input.h" input_t *init_input(WINDOW **win, widget_t *w_parent, char *label, - uint32_t length) { + 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; @@ -21,10 +21,12 @@ input_t *init_input(WINDOW **win, widget_t *w_parent, char *label, input->value[0] = '\0'; input->value_len = 0; input->max_len = length; + input->is_hidden = is_hidden_value; 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; @@ -54,8 +56,14 @@ int32_t draw_input(input_t *input, uint32_t active_id) { } uint32_t x_c = 0; - mvwprintw(win, margin_y + 1, margin_x, "%s%*s", input->value, - input->w.x - input->value_len - 2, ""); + 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; diff --git a/src/ui/widget/input.h b/src/ui/widget/input.h index a15f4c8..9bc33e5 100644 --- a/src/ui/widget/input.h +++ b/src/ui/widget/input.h @@ -11,9 +11,11 @@ typedef struct { 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); +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 From 34e58daa426ce002ed81bab5c150ec91992574de Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Fri, 2 Jan 2026 12:45:41 +0200 Subject: [PATCH 30/45] ui: move cursor through the text in input --- TODO.md | 4 ++-- src/ui/widget/dialogue.c | 44 ++++++++++++++++++++++++++++------------ src/ui/widget/group.c | 26 ++++++++++++++++++------ src/ui/widget/input.c | 3 ++- src/ui/widget/input.h | 1 + 5 files changed, 56 insertions(+), 22 deletions(-) diff --git a/TODO.md b/TODO.md index 6a32339..bf10a36 100644 --- a/TODO.md +++ b/TODO.md @@ -12,6 +12,6 @@ TODO: - [ ] "Server message" to notification INPUT: -- [ ] hidden text for passwords -- [ ] move cursor through the text in input +- [x] hidden text for passwords +- [x] move cursor through the text in input diff --git a/src/ui/widget/dialogue.c b/src/ui/widget/dialogue.c index f0321c5..5cb051c 100644 --- a/src/ui/widget/dialogue.c +++ b/src/ui/widget/dialogue.c @@ -53,8 +53,8 @@ void dialogue_default_callback(callback_args_t *args) { new_args.active_id = d->active.id; int32_t diff; d->needs_update = true; - switch (key) { - case KEY_RIGHT: + input_t *input; + switch (key) { case '\t': if (d->active.type == g_content) { INCR_ACTIVE_ID(d, g_content, g_action); @@ -62,16 +62,40 @@ void dialogue_default_callback(callback_args_t *args) { INCR_ACTIVE_ID(d, g_action, g_content); } *resp_value = -1; - + 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) { - DECR_ACTIVE_ID(d, g_content, g_action); + 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': if (d->active.type == g_action) { @@ -89,15 +113,12 @@ void dialogue_default_callback(callback_args_t *args) { *resp_value = d->active.id - d->g_content->first_id; return; } - break; case KEY_UP: CH_GROUP(d, g_action, g_content); - break; case KEY_DOWN: CH_GROUP(d, g_content, g_action); - break; default: /* run callback function */ @@ -239,11 +260,8 @@ int32_t draw_dialogue(dialogue_t *d) { if (ae_ptr->type == w_input) { input_t *input = ae_ptr->element; d->w.cur.y = input->w.cur.y; - if (input->max_len == input->value_len) { - d->w.cur.x = input->w.cur.x + input->value_len - 1; - } else { - d->w.cur.x = input->w.cur.x + input->value_len; - } + 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(1); diff --git a/src/ui/widget/group.c b/src/ui/widget/group.c index d5f1f62..2b8828c 100644 --- a/src/ui/widget/group.c +++ b/src/ui/widget/group.c @@ -5,10 +5,9 @@ #include #include #include +#include #include -// TODO: refactor - #define MAKE_RESPONSE_M1(args, resp_data, response) \ if (args->resp_data != NULL) { \ *response = -1; \ @@ -21,6 +20,7 @@ void group_default_callback(callback_args_t *args) { input_t *input; group_el_t *element_ptr; int32_t element_idx = -1; + u_int32_t start_pos = 0; switch (key) { case '\n': /* Enter */ FIND_ACTIVE_ELEMENT(g, args->active_id, element_ptr, element_idx); @@ -32,7 +32,13 @@ void group_default_callback(callback_args_t *args) { if (element_ptr->type == w_input) { input = (input_t *)element_ptr->element; if (input->value_len) { - input->value[--input->value_len] = '\0'; + 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); @@ -42,8 +48,15 @@ void group_default_callback(callback_args_t *args) { if (element_ptr->type == w_input) { input = (input_t *)element_ptr->element; if (input->max_len > input->value_len) { - input->value[input->value_len++] = key; - input->value[input->value_len] = '\0'; + 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'; + } } } MAKE_RESPONSE_M1(args, resp_data, response); @@ -85,7 +98,8 @@ group_t *init_group(WINDOW **win, widget_t *w_parent, group_el_init_t *children, break; case w_input: elements[i].element = - init_input(win, &(group->w), children[i].label, children[i].length, children[i].is_hidden_value); + 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: diff --git a/src/ui/widget/input.c b/src/ui/widget/input.c index 26a066f..a9e36a9 100644 --- a/src/ui/widget/input.c +++ b/src/ui/widget/input.c @@ -13,7 +13,7 @@ input_t *init_input(WINDOW **win, widget_t *w_parent, char *label, init_widget(&(input->w), w_parent, win, label); input->is_disabled = 0; input->w.y = 3; // with borders - input->w.x = length + 2; // 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; @@ -22,6 +22,7 @@ input_t *init_input(WINDOW **win, widget_t *w_parent, char *label, input->value_len = 0; input->max_len = length; input->is_hidden = is_hidden_value; + input->cur_pos = 0; return input; } diff --git a/src/ui/widget/input.h b/src/ui/widget/input.h index 9bc33e5..10731a9 100644 --- a/src/ui/widget/input.h +++ b/src/ui/widget/input.h @@ -8,6 +8,7 @@ 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; From 23fda5232da874f185837a3db25148cc65da12a2 Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Fri, 2 Jan 2026 14:15:51 +0200 Subject: [PATCH 31/45] ui: info panel --- src/main.c | 2 +- src/ui/widget.c | 27 +++++++++++++++++++++++++-- src/ui/widget.h | 3 +-- src/ui/widget/dialogue.c | 2 +- src/ui/widget/file_list.c | 17 ++++++++++++++++- src/ui/widget/file_list.h | 3 ++- 6 files changed, 46 insertions(+), 8 deletions(-) diff --git a/src/main.c b/src/main.c index dcae9b5..fed60d0 100644 --- a/src/main.c +++ b/src/main.c @@ -41,7 +41,7 @@ int main(int argc, char **argv) { 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)); + 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(); diff --git a/src/ui/widget.c b/src/ui/widget.c index 914269f..e2dcfdf 100644 --- a/src/ui/widget.c +++ b/src/ui/widget.c @@ -2,6 +2,7 @@ #include "../main.h" #include #include +#include #include extern uint32_t m_id; @@ -57,15 +58,35 @@ int32_t get_max_line_len(const char *text, uint32_t *line_count) { 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, +uint32_t print_multiline_text(WINDOW *win, const char *_text, 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; + uint32_t win_width = getmaxx(win) - 2; /* -2: borders */ 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); @@ -81,6 +102,8 @@ uint32_t print_multiline_text(WINDOW *win, const char *text, PRINT_TEXT(win, l_buf, text, i, c_line_len, win_width, line_v_pos, attrs) + free(text); + return 1; } diff --git a/src/ui/widget.h b/src/ui/widget.h index 9052f20..be738a9 100644 --- a/src/ui/widget.h +++ b/src/ui/widget.h @@ -58,8 +58,7 @@ enum pmt_attrs { 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, +uint32_t print_multiline_text(WINDOW *win, const char *text, 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/dialogue.c b/src/ui/widget/dialogue.c index 5cb051c..bf052ae 100644 --- a/src/ui/widget/dialogue.c +++ b/src/ui/widget/dialogue.c @@ -239,7 +239,7 @@ int32_t draw_dialogue(dialogue_t *d) { /* 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); + print_multiline_text(d->win, d->text, 2, 1, PMT_ALIGN_CENTER); wattroff(d->win, A_REVERSE); if (d->g_content != NULL) { diff --git a/src/ui/widget/file_list.c b/src/ui/widget/file_list.c index 55081bc..d71e2c4 100644 --- a/src/ui/widget/file_list.c +++ b/src/ui/widget/file_list.c @@ -52,7 +52,7 @@ void file_list_cb(callback_args_t *args) { } } -ui_file_list_t *init_file_list(WINDOW **win) { +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, ""); fl_ui->current_idx = 0; @@ -61,6 +61,7 @@ ui_file_list_t *init_file_list(WINDOW **win) { fl_ui->current_count = 0; fl_ui->full_count = 0; fl_ui->activate_last = false; + fl_ui->info_win = info_win; return fl_ui; } @@ -81,6 +82,7 @@ void draw_file_list(ui_file_list_t *fl_ui) { 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; WINDOW *win = *(fl_ui->w.parent_win); curs_set(0); @@ -102,6 +104,7 @@ void draw_file_list(ui_file_list_t *fl_ui) { } if (cur_el_idx == fl_ui->current_idx) { wattrset(win, A_BOLD | A_REVERSE); + active_el = el; } p_x = 1; mvwprintw(win, p_y, p_x, "%s%n", el->name, &p_x); @@ -130,4 +133,16 @@ void draw_file_list(ui_file_list_t *fl_ui) { wattrset(win, A_BOLD); mvwprintw(win, p_y, p_x, "%*s%s%*s", l_pad, "", p_info, l_pad, ""); wattroff(win, A_BOLD); + + + /* draw file info */ + p_y = 1; + p_x = 1; + WINDOW *i_win = *fl_ui->info_win; + wclear(i_win); + + mvwprintw(i_win, p_y++, p_x, "Size: %zu", active_el->size); + 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, 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 index c91df5d..896f314 100644 --- a/src/ui/widget/file_list.h +++ b/src/ui/widget/file_list.h @@ -12,10 +12,11 @@ typedef struct { uint32_t pages; uint32_t current_page; uint32_t activate_last : 1; + WINDOW *const* info_win; } 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); +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 From bdb194679efdd877b96686b3e03a522cf3306aa1 Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Mon, 5 Jan 2026 16:09:23 +0200 Subject: [PATCH 32/45] ui: brings back text centering in modals | fixes error on exit | small clean-up --- src/connection.c | 3 +- src/deps.mk | 3 +- src/main.c | 10 ++++-- src/main.h | 1 - src/query.c | 1 - src/server.c | 1 - src/ui.c | 25 -------------- src/ui.h | 3 -- src/ui/app.c | 19 +++++------ src/ui/app.h | 2 +- src/ui/modals/ask_server_addr.c | 3 +- src/ui/modals/download_pr.c | 4 +-- src/ui/modals/login_credentials.c | 3 +- src/ui/modals/upload_dialogue.c | 57 +++++++++++++++++++++++++++++++ src/ui/modals/upload_dialogue.h | 9 +++++ src/ui/widget.c | 4 +-- src/ui/widget.h | 2 +- src/ui/widget/dialogue.c | 2 +- src/ui/widget/file_list.c | 2 +- src/ui/widget/file_list.h | 1 + src/ui/widget/group.c | 2 +- 21 files changed, 96 insertions(+), 61 deletions(-) delete mode 100644 src/ui.c delete mode 100644 src/ui.h create mode 100644 src/ui/modals/upload_dialogue.c create mode 100644 src/ui/modals/upload_dialogue.h diff --git a/src/connection.c b/src/connection.c index a4cab2f..8096162 100644 --- a/src/connection.c +++ b/src/connection.c @@ -20,8 +20,7 @@ void connect_to_server(app_t *app) { if (-1 == connect(app->params->sd, (struct sockaddr *)&server, sizeof(server))) { perror("connect"); - destroy_app(app); - exit(2); + destroy_app(app, 2); } app->params->is_connected = TRUE; print_bars(app); diff --git a/src/deps.mk b/src/deps.mk index 080b537..94ec06b 100644 --- a/src/deps.mk +++ b/src/deps.mk @@ -25,5 +25,4 @@ query.o: src/query.c src/client.h src/main.h src/types.h src/connection.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.h -ui.o: src/ui.c +server.o: src/server.c src/main.h src/types.h diff --git a/src/main.c b/src/main.c index fed60d0..7361c6d 100644 --- a/src/main.c +++ b/src/main.c @@ -57,7 +57,7 @@ int main(int argc, char **argv) { query_loop(app); clear_params(¶ms); - destroy_app(app); + destroy_app(app, 0); return OK; } @@ -67,8 +67,12 @@ int32_t process_user_input(app_t *app, callback_args_t *d_args) { c = wgetch(app->win); switch (c) { case KEY_F(9): - destroy_app(app); - return ERR; + destroy_app(app, 0); + return OK; + case 'U': + case 'u': + app->query_args->state = S_UPLOAD_FILE; + break; default: d_args->data = (void *)&c; app->active_callback(d_args); diff --git a/src/main.h b/src/main.h index 54f857f..ca06b5e 100644 --- a/src/main.h +++ b/src/main.h @@ -45,7 +45,6 @@ enum state { WAIT_REGISTER, WAIT_REGISTER_CONFIRMATION, WAIT_CLIENT, - UPLOAD_FILE, }; typedef struct p_file { diff --git a/src/query.c b/src/query.c index 5a9638e..b600d00 100644 --- a/src/query.c +++ b/src/query.c @@ -144,7 +144,6 @@ int process_query(query_args_t *query_args, file_args_t *file_args) { case WAIT_REGISTER_CONFIRMATION: wait_side(query_args); break; - case UPLOAD_FILE: case S_ERR: break; case S_FILE_LIST: diff --git a/src/server.c b/src/server.c index 604f8d5..b7b51a4 100644 --- a/src/server.c +++ b/src/server.c @@ -1,5 +1,4 @@ #include "main.h" -#include "ui.h" #include #include #include diff --git a/src/ui.c b/src/ui.c deleted file mode 100644 index 1581b3b..0000000 --- a/src/ui.c +++ /dev/null @@ -1,25 +0,0 @@ -#include -#include -#include - -uint32_t print_ask_list(char title[], const char *options[]) { - 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 index f24b12d..8c820b6 100644 --- a/src/ui/app.c +++ b/src/ui/app.c @@ -5,6 +5,7 @@ #include "modals/login_option.h" #include "modals/notification.h" #include "modals/server_message.h" +#include "modals/upload_dialogue.h" #include "widget/dialogue.h" #include #include @@ -90,12 +91,6 @@ void draw_borders(app_t *app) { box(app->win, 0, 0); box(app->left_win, 0, 0); box(app->right_win, 0, 0); - - /* refresh the windows */ - wnoutrefresh(app->win); - wnoutrefresh(app->left_win); - wnoutrefresh(app->right_win); - doupdate(); } void print_bars(app_t *app) { @@ -120,7 +115,8 @@ void print_bars(app_t *app) { 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 | F9 - Quit"); + mvwprintw(app->win, app->coordinates.cur_y - 2, 2, + "F1 - Help | U - upload | F9 - Quit"); wattroff(app->win, A_REVERSE); } @@ -161,16 +157,17 @@ void app_draw_modal(app_t *app) { case S_FILE_DOWNLOAD: init_dwn_pr_modal(app); break; + case S_UPLOAD_FILE: + init_upload_dialogue_modal(app); + break; case WAIT_SERVER_INIT: case WAIT_SERVER: case WAIT_REGISTER: case WAIT_REGISTER_CONFIRMATION: case WAIT_CLIENT: - case UPLOAD_FILE: case S_FILE_LIST: case S_FILE_SELECT: case S_UPLOAD_PARAMS: - case S_UPLOAD_FILE: case S_UPLOAD_REQUESTED: case S_ASK_USER_BEFORE_LOGIN: case S_ERR: @@ -191,7 +188,9 @@ void app_draw_modal(app_t *app) { draw_dialogue(&(app->modal)); } -void destroy_app(app_t *app) { +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 index 945d27d..c810972 100644 --- a/src/ui/app.h +++ b/src/ui/app.h @@ -35,7 +35,7 @@ typedef struct { app_t *init_app(); void init_nc(); -void destroy_app(app_t *app); +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); diff --git a/src/ui/modals/ask_server_addr.c b/src/ui/modals/ask_server_addr.c index 1e663c9..5c80b19 100644 --- a/src/ui/modals/ask_server_addr.c +++ b/src/ui/modals/ask_server_addr.c @@ -35,8 +35,7 @@ void asa_modal_dialogue_callback(callback_args_t *args) { app->query_args->state = S_WAIT_SERVER; break; case 1: - destroy_app(app); - exit(0); + destroy_app(app, 0); } } } diff --git a/src/ui/modals/download_pr.c b/src/ui/modals/download_pr.c index 4a77bec..e32de93 100644 --- a/src/ui/modals/download_pr.c +++ b/src/ui/modals/download_pr.c @@ -5,7 +5,7 @@ #include #include -void init_dwn_pr_modal_cb(callback_args_t *args) { +void dwn_pr_modal_cb(callback_args_t *args) { } dialogue_t *init_dwn_pr_modal(app_t *app) { @@ -23,7 +23,7 @@ dialogue_t *init_dwn_pr_modal(app_t *app) { init_dialogue(&(app->modal), "Download", text, &(app->coordinates)); dialogue_t *d = &(app->modal); - d->w.callback = init_dwn_pr_modal_cb; + 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); diff --git a/src/ui/modals/login_credentials.c b/src/ui/modals/login_credentials.c index b5dd5d9..32c8616 100644 --- a/src/ui/modals/login_credentials.c +++ b/src/ui/modals/login_credentials.c @@ -41,8 +41,7 @@ void init_login_credentials_modal_cb(callback_args_t *args) { print_bars(app); break; case 1: - destroy_app(app); - exit(0); + destroy_app(app, 0); } } } diff --git a/src/ui/modals/upload_dialogue.c b/src/ui/modals/upload_dialogue.c new file mode 100644 index 0000000..7a1f770 --- /dev/null +++ b/src/ui/modals/upload_dialogue.c @@ -0,0 +1,57 @@ +#include "../app.h" +#include "../widget/dialogue.h" +#include "../widget/group.h" +#include +#include +#include + +void upload_dialogue_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.widget = app->active_widget; + dialogue_default_callback(&d_args); + if (response > -1) { + switch (response) { + case 0: + app->query_args->state = S_WAIT_SERVER; + break; + case 1: + break; + } + } +} + +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}, /* length is ignored */ + {.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), "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->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/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/widget.c b/src/ui/widget.c index e2dcfdf..d1b6f8f 100644 --- a/src/ui/widget.c +++ b/src/ui/widget.c @@ -58,14 +58,14 @@ int32_t get_max_line_len(const char *text, uint32_t *line_count) { mvwprintw(win, line_v_pos, x, "%s", l_buf); \ } -uint32_t print_multiline_text(WINDOW *win, const char *_text, const uint32_t y, +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; - uint32_t win_width = getmaxx(win) - 2; /* -2: borders */ char l_buf[DIALOGUE_TEXT]; char *text = malloc(strlen(_text) + 1); strcpy(text, _text); diff --git a/src/ui/widget.h b/src/ui/widget.h index be738a9..aafebca 100644 --- a/src/ui/widget.h +++ b/src/ui/widget.h @@ -58,7 +58,7 @@ enum pmt_attrs { 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 y, +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/dialogue.c b/src/ui/widget/dialogue.c index bf052ae..5cb051c 100644 --- a/src/ui/widget/dialogue.c +++ b/src/ui/widget/dialogue.c @@ -239,7 +239,7 @@ int32_t draw_dialogue(dialogue_t *d) { /* 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, 2, 1, PMT_ALIGN_CENTER); + 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) { diff --git a/src/ui/widget/file_list.c b/src/ui/widget/file_list.c index d71e2c4..f7ac1a0 100644 --- a/src/ui/widget/file_list.c +++ b/src/ui/widget/file_list.c @@ -144,5 +144,5 @@ void draw_file_list(ui_file_list_t *fl_ui) { mvwprintw(i_win, p_y++, p_x, "Size: %zu", active_el->size); 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, p_y, p_x, 0); + 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 index 896f314..a739744 100644 --- a/src/ui/widget/file_list.h +++ b/src/ui/widget/file_list.h @@ -2,6 +2,7 @@ #define FILE_LIST_H #include "../widget.h" #include + typedef struct { widget_t w; fl_item_t **current; diff --git a/src/ui/widget/group.c b/src/ui/widget/group.c index 2b8828c..a4d15dc 100644 --- a/src/ui/widget/group.c +++ b/src/ui/widget/group.c @@ -107,7 +107,7 @@ group_t *init_group(WINDOW **win, widget_t *w_parent, group_el_init_t *children, w = &(((ui_progress_bar_t *)elements[i].element)->w); break; } - // set dimensions + /* set dimensions */ elements[i].id = w->id; if (direction == horizontal) { w->m_x = group->w.m_x + 1 + group->w.x; From 9b44285c56a7d8a8bc6bdf59ebc519b9f4519702 Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Mon, 5 Jan 2026 16:47:24 +0200 Subject: [PATCH 33/45] ui: show file sizes in human-friendly --- src/ui/modals/ask_server_addr.c | 1 - src/ui/modals/upload_dialogue.c | 8 +++---- src/ui/widget/file_list.c | 40 +++++++++++++++++++++++++++++---- 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/src/ui/modals/ask_server_addr.c b/src/ui/modals/ask_server_addr.c index 5c80b19..6ac6f29 100644 --- a/src/ui/modals/ask_server_addr.c +++ b/src/ui/modals/ask_server_addr.c @@ -1,5 +1,4 @@ #include -#include #include #include diff --git a/src/ui/modals/upload_dialogue.c b/src/ui/modals/upload_dialogue.c index 7a1f770..79dfed7 100644 --- a/src/ui/modals/upload_dialogue.c +++ b/src/ui/modals/upload_dialogue.c @@ -29,10 +29,10 @@ void upload_dialogue_modal_cb(callback_args_t *args) { 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}, /* length is ignored */ - {.type = w_end}}; + group_el_init_t content[] = { + {.type = w_input, .label = "File description", .length = 30}, + {.type = w_input, .label = "File path", .length = 30}, + {.type = w_end}}; group_el_init_t actions[] = { {.type = w_button, .label = "Upload", .is_default = 1}, diff --git a/src/ui/widget/file_list.c b/src/ui/widget/file_list.c index f7ac1a0..62d97fa 100644 --- a/src/ui/widget/file_list.c +++ b/src/ui/widget/file_list.c @@ -4,6 +4,7 @@ #include "progress_bar.h" #include #include +#include #include #include #include @@ -11,6 +12,7 @@ #include void reset_file_list(ui_file_list_t *fl_ui); +void size_to_text(size_t size, char *text); void file_list_cb(callback_args_t *args) { app_t *app = args->app; @@ -47,12 +49,12 @@ void file_list_cb(callback_args_t *args) { } break; case '\n': - ui_file_select(app->file_args, app->query_args, fui->current_idx+1); + 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 *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, ""); fl_ui->current_idx = 0; @@ -134,15 +136,45 @@ void draw_file_list(ui_file_list_t *fl_ui) { mvwprintw(win, p_y, p_x, "%*s%s%*s", l_pad, "", p_info, l_pad, ""); wattroff(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: %zu", active_el->size); + 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); +} + +#define BYTES_IN_GB 1073741824 +#define BYTES_IN_MB 1048576 +#define BYTES_IN_KB 1024 + +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; + sprintf(text, "%zu,%zu GB", gb, mb); + } else if (mb > 0) { + size = size % BYTES_IN_MB; + kb = size / BYTES_IN_KB; + sprintf(text, "%zu,%zu MB", mb, kb); + } else if (kb > 0) { + size = size % BYTES_IN_KB; + sprintf(text, "%zu,%zu KB", kb, size); + } else { + sprintf(text, "%zu B", size); + } } \ No newline at end of file From cb79978cdf972cdf2f588123243c66d60f2e52cd Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Tue, 6 Jan 2026 14:49:37 +0200 Subject: [PATCH 34/45] ui: fs file list (not finished) --- src/deps.mk | 1 + src/file_processor.h | 1 - src/fs.c | 39 ++++++++ src/fs.h | 8 ++ src/ui/modals/upload_dialogue.c | 2 +- src/ui/widget.h | 1 + src/ui/widget/file_list.c | 38 +------- src/ui/widget/fs_file_list.c | 160 ++++++++++++++++++++++++++++++++ src/ui/widget/fs_file_list.h | 23 +++++ src/ui/widget/group.c | 12 +++ 10 files changed, 250 insertions(+), 35 deletions(-) create mode 100644 src/fs.c create mode 100644 src/fs.h create mode 100644 src/ui/widget/fs_file_list.c create mode 100644 src/ui/widget/fs_file_list.h diff --git a/src/deps.mk b/src/deps.mk index 94ec06b..a154278 100644 --- a/src/deps.mk +++ b/src/deps.mk @@ -13,6 +13,7 @@ file_processor.o: src/file_processor.c src/client.h src/main.h \ src/ui/widget/../../main.h src/ui/widget/group.h \ src/ui/widget/dialogue.h src/ui/widget/file_list.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 \ diff --git a/src/file_processor.h b/src/file_processor.h index 0c98c71..0a3c050 100644 --- a/src/file_processor.h +++ b/src/file_processor.h @@ -12,5 +12,4 @@ int file_upload_start(query_args_t *q_args); int file_upload(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); - #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/ui/modals/upload_dialogue.c b/src/ui/modals/upload_dialogue.c index 79dfed7..154f1af 100644 --- a/src/ui/modals/upload_dialogue.c +++ b/src/ui/modals/upload_dialogue.c @@ -31,7 +31,7 @@ dialogue_t *init_upload_dialogue_modal(app_t *app) { return NULL; group_el_init_t content[] = { {.type = w_input, .label = "File description", .length = 30}, - {.type = w_input, .label = "File path", .length = 30}, + {.type = w_fs_file_list, .label = "File", .length = 30}, {.type = w_end}}; group_el_init_t actions[] = { diff --git a/src/ui/widget.h b/src/ui/widget.h index aafebca..57ed7b3 100644 --- a/src/ui/widget.h +++ b/src/ui/widget.h @@ -12,6 +12,7 @@ enum w_type { w_group, w_input, w_progress, + w_fs_file_list, }; typedef struct { diff --git a/src/ui/widget/file_list.c b/src/ui/widget/file_list.c index 62d97fa..63497f6 100644 --- a/src/ui/widget/file_list.c +++ b/src/ui/widget/file_list.c @@ -1,6 +1,7 @@ #include "file_list.h" #include "../../file_processor.h" #include "../app.h" +#include "../../fs.h" #include "progress_bar.h" #include #include @@ -12,7 +13,6 @@ #include void reset_file_list(ui_file_list_t *fl_ui); -void size_to_text(size_t size, char *text); void file_list_cb(callback_args_t *args) { app_t *app = args->app; @@ -79,14 +79,14 @@ void reset_file_list(ui_file_list_t *fl_ui) { void draw_file_list(ui_file_list_t *fl_ui) { int32_t sz_y, sz_x; int32_t p_y, p_x; - getmaxyx(*(fl_ui->w.parent_win), sz_y, sz_x); - int32_t sz_y_f = sz_y - 2; // factual size (without box) + 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) 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; - WINDOW *win = *(fl_ui->w.parent_win); curs_set(0); if (fl_ui->activate_last) { @@ -144,37 +144,9 @@ void draw_file_list(ui_file_list_t *fl_ui) { 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); -} - -#define BYTES_IN_GB 1073741824 -#define BYTES_IN_MB 1048576 -#define BYTES_IN_KB 1024 - -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; - sprintf(text, "%zu,%zu GB", gb, mb); - } else if (mb > 0) { - size = size % BYTES_IN_MB; - kb = size / BYTES_IN_KB; - sprintf(text, "%zu,%zu MB", mb, kb); - } else if (kb > 0) { - size = size % BYTES_IN_KB; - sprintf(text, "%zu,%zu KB", kb, size); - } else { - sprintf(text, "%zu B", size); - } } \ 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..b1186ad --- /dev/null +++ b/src/ui/widget/fs_file_list.c @@ -0,0 +1,160 @@ +#include "../../file_processor.h" +#include "../app.h" +#include "fs_file_list.h" +#include "progress_bar.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void reset_file_list(ui_fs_file_list_t *fl_ui); + +static void fl_add(fl_item_t **f_start, fl_item_t **f_current, + struct dirent *dent) { + fl_item_t *c_item = malloc(sizeof(fl_item_t)); + c_item->name = dent->d_name; + c_item->description = NULL; + c_item->owner = NULL; + c_item->size = 0; + c_item->next = NULL; + if (*f_start == NULL) { + *f_start = c_item; + *f_current = c_item; + } else { + (*f_current)->next = c_item; + } +} + +fl_item_t *get_files_from_fs(char *path) { + DIR *dir; + struct dirent *dent; + + fl_item_t *l_start = NULL, *l_current = NULL; + + dir = opendir(path); + if (!dir) { + return NULL; + errno = 1; + } + while ((dent = readdir(dir)) != NULL) { + fl_add(&l_start, &l_current, dent); + } + return l_start; +} + +void fs_file_list_cb(callback_args_t *args) { + app_t *app = args->app; + ui_fs_file_list_t *fui = app->query_args->file_list_ui; + int32_t key = *((int32_t *)args->data); + 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_fs_file_list(fui); + } else { + fl_clear(fui->start, fui->current); + reset_file_list(fui); + } + break; + case KEY_UP: + if (fui->current_idx > 0) { + fui->current_idx--; + draw_fs_file_list(fui); + } else { + fl_clear(fui->start, fui->current); + reset_fs_file_list(fui); + fui->activate_last = true; + } + break; + case '\n': + /* here goes file select */ + 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)); + init_widget(&(fl_ui->w), w_parent, win, ""); + fl_ui->current_idx = 0; + fl_ui->current_count = 0; + fl_ui->activate_last = false; + fl_ui->info_win = NULL; + return fl_ui; +} + +void reset_fs_file_list(ui_fs_file_list_t *fl_ui) { + fl_ui->current_idx = 0; + fl_ui->current_count = 0; + fl_ui->activate_last = false; +} + +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) + 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(0); + + 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(win, 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 - p_x, ""); + if (cur_el_idx == fl_ui->current_idx) { + wattroff(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(win, p_y, p_x, "%*s", sz_x - 1, ""); + } + char p_info[24]; + uint32_t p_len; + p_x = 1; + + uint32_t l_pad = (sz_x - p_len) / 2; + + wattrset(win, A_BOLD); + mvwprintw(win, p_y, p_x, "%*s%s%*s", l_pad, "", p_info, l_pad, ""); + wattroff(win, A_BOLD); +} + +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..0413116 --- /dev/null +++ b/src/ui/widget/fs_file_list.h @@ -0,0 +1,23 @@ +#ifndef FS_FILE_LIST_H +#define FS_FILE_LIST_H +#include "../widget.h" +#include +#include + +typedef struct { + widget_t w; + fl_item_t **current; + fl_item_t **start; + int32_t current_idx; + uint32_t current_count; + uint32_t activate_last : 1; + WINDOW *const* info_win; +} 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 index a4d15dc..ce2d9ea 100644 --- a/src/ui/widget/group.c +++ b/src/ui/widget/group.c @@ -1,5 +1,7 @@ #include "group.h" #include "button.h" +#include "file_list.h" +#include "fs_file_list.h" #include "input.h" #include "progress_bar.h" #include @@ -106,6 +108,10 @@ group_t *init_group(WINDOW **win, widget_t *w_parent, group_el_init_t *children, 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; @@ -147,6 +153,9 @@ void draw_group(WINDOW *win, group_t *group, int32_t active_id, 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; } } } @@ -169,6 +178,9 @@ void destroy_group(group_t *group) { 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) { From 4661a36acfafc4baab85853810f68a064c12bd22 Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Thu, 15 Jan 2026 15:04:36 +0200 Subject: [PATCH 35/45] ui: file list modal (not finished) --- src/ui/app.c | 2 +- src/ui/modals/server_message.c | 2 +- src/ui/modals/upload_dialogue.c | 6 ++---- src/ui/widget.h | 2 +- src/ui/widget/dialogue.c | 36 +++++++++++++++++++++++---------- src/ui/widget/file_list.c | 2 +- src/ui/widget/fs_file_list.c | 33 +++++++++++++++--------------- src/ui/widget/group.c | 12 ++++++++++- src/ui/widget/group.h | 14 +++++-------- 9 files changed, 64 insertions(+), 45 deletions(-) diff --git a/src/ui/app.c b/src/ui/app.c index 8c820b6..68fab99 100644 --- a/src/ui/app.c +++ b/src/ui/app.c @@ -71,7 +71,7 @@ void init_nc() { cbreak(); keypad(stdscr, TRUE); noecho(); - curs_set(0); + curs_set(false); if (!has_colors()) { printf("Your terminal does not support color\n"); diff --git a/src/ui/modals/server_message.c b/src/ui/modals/server_message.c index 505de0f..53ec6b5 100644 --- a/src/ui/modals/server_message.c +++ b/src/ui/modals/server_message.c @@ -20,7 +20,7 @@ void server_message_modal_cb(callback_args_t *args) { memcpy(&d_args, args, sizeof(callback_args_t)); d_args.app = NULL; d_args.resp_data = &response; - d_args.widget = app->active_widget; + d_args.element = app->active_widget; dialogue_default_callback(&d_args); if (response > -1) { switch (response) { diff --git a/src/ui/modals/upload_dialogue.c b/src/ui/modals/upload_dialogue.c index 154f1af..a7f9c91 100644 --- a/src/ui/modals/upload_dialogue.c +++ b/src/ui/modals/upload_dialogue.c @@ -13,7 +13,7 @@ void upload_dialogue_modal_cb(callback_args_t *args) { memcpy(&d_args, args, sizeof(callback_args_t)); d_args.app = NULL; d_args.resp_data = &response; - d_args.widget = app->active_widget; + d_args.element = app->active_widget; dialogue_default_callback(&d_args); if (response > -1) { switch (response) { @@ -30,12 +30,11 @@ 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_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 = "Upload", .is_default = 1}, {.type = w_button, .label = "Cancel", .is_default = 0}, {.type = w_end}}; @@ -48,7 +47,6 @@ dialogue_t *init_upload_dialogue_modal(app_t *app) { 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); diff --git a/src/ui/widget.h b/src/ui/widget.h index 57ed7b3..6db125a 100644 --- a/src/ui/widget.h +++ b/src/ui/widget.h @@ -17,7 +17,7 @@ enum w_type { typedef struct { void *app; - void *widget; + void *element; void *data; void *resp_data; uint32_t active_id; diff --git a/src/ui/widget/dialogue.c b/src/ui/widget/dialogue.c index 5cb051c..56c4b4f 100644 --- a/src/ui/widget/dialogue.c +++ b/src/ui/widget/dialogue.c @@ -43,7 +43,7 @@ } void dialogue_default_callback(callback_args_t *args) { - dialogue_t *d = (void *)args->widget; + 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; @@ -54,7 +54,7 @@ void dialogue_default_callback(callback_args_t *args) { int32_t diff; d->needs_update = true; input_t *input; - switch (key) { + switch (key) { case '\t': if (d->active.type == g_content) { INCR_ACTIVE_ID(d, g_content, g_action); @@ -99,7 +99,7 @@ void dialogue_default_callback(callback_args_t *args) { break; case '\n': if (d->active.type == g_action) { - new_args.widget = new_args.widget = d->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++) { @@ -115,17 +115,29 @@ void dialogue_default_callback(callback_args_t *args) { } break; case KEY_UP: - CH_GROUP(d, g_action, g_content); + 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: - CH_GROUP(d, g_content, g_action); + 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.widget = new_args.widget = d->g_content; + new_args.element = new_args.element = d->g_content; } else { - new_args.widget = new_args.widget = d->g_action; + new_args.element = new_args.element = d->g_action; } group_default_callback(&new_args); } @@ -152,7 +164,8 @@ void dialogue_init_active_id(dialogue_t *dialogue) { 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) { + 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; @@ -162,7 +175,8 @@ void dialogue_init_active_id(dialogue_t *dialogue) { } 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) { + 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; @@ -264,10 +278,10 @@ int32_t draw_dialogue(dialogue_t *d) { 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(1); + curs_set(true); } } else { - curs_set(0); + curs_set(false); } } diff --git a/src/ui/widget/file_list.c b/src/ui/widget/file_list.c index 63497f6..b9400e8 100644 --- a/src/ui/widget/file_list.c +++ b/src/ui/widget/file_list.c @@ -87,7 +87,7 @@ void draw_file_list(ui_file_list_t *fl_ui) { fl_item_t *el = *(fl_ui->start); fl_item_t *active_el = *(fl_ui->start); int32_t cur_el_idx = 0; - curs_set(0); + curs_set(false); if (fl_ui->activate_last) { fl_ui->current_idx = fl_ui->current_count - 1; diff --git a/src/ui/widget/fs_file_list.c b/src/ui/widget/fs_file_list.c index b1186ad..0d1733a 100644 --- a/src/ui/widget/fs_file_list.c +++ b/src/ui/widget/fs_file_list.c @@ -1,6 +1,6 @@ +#include "fs_file_list.h" #include "../../file_processor.h" #include "../app.h" -#include "fs_file_list.h" #include "progress_bar.h" #include #include @@ -27,6 +27,7 @@ static void fl_add(fl_item_t **f_start, fl_item_t **f_current, *f_current = c_item; } else { (*f_current)->next = c_item; + *f_current = c_item; } } @@ -48,8 +49,7 @@ fl_item_t *get_files_from_fs(char *path) { } void fs_file_list_cb(callback_args_t *args) { - app_t *app = args->app; - ui_fs_file_list_t *fui = app->query_args->file_list_ui; + ui_fs_file_list_t *fui = args->element; int32_t key = *((int32_t *)args->data); int32_t q_len = 0; fl_item_t *f_item; @@ -87,6 +87,9 @@ ui_fs_file_list_t *init_fs_file_list(WINDOW **win, widget_t *w_parent) { fl_ui->current_count = 0; fl_ui->activate_last = false; fl_ui->info_win = NULL; + fl_ui->start = malloc(sizeof(fl_item_t *)); /* For compatibility reasons */ + *(fl_ui->start) = get_files_from_fs("./"); + fl_ui->w.callback = fs_file_list_cb; return fl_ui; } @@ -102,12 +105,12 @@ void draw_fs_file_list(ui_fs_file_list_t *fl_ui) { 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; fl_item_t *el = *(fl_ui->start); fl_item_t *active_el = *(fl_ui->start); int32_t cur_el_idx = 0; - curs_set(0); if (fl_ui->activate_last) { fl_ui->current_idx = fl_ui->current_count - 1; @@ -118,6 +121,8 @@ void draw_fs_file_list(ui_fs_file_list_t *fl_ui) { p_y -= fl_ui->current_idx - sz_y_f + 2; } + wattrset(win, A_REVERSE); /* Match with window background */ + do { if (p_y < 1) { p_y++; @@ -125,14 +130,14 @@ void draw_fs_file_list(ui_fs_file_list_t *fl_ui) { continue; } if (cur_el_idx == fl_ui->current_idx) { - wattrset(win, A_BOLD | A_REVERSE); + 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 - p_x, ""); + mvwprintw(win, p_y, p_x + 1, "%*s", sz_x_f - p_x, ""); if (cur_el_idx == fl_ui->current_idx) { - wattroff(win, A_BOLD | A_REVERSE); + wattroff(win, COLOR_PAIR(3) | A_BOLD); } p_y++; cur_el_idx++; @@ -141,20 +146,16 @@ void draw_fs_file_list(ui_fs_file_list_t *fl_ui) { p_x = 1; for (; p_y < sz_y_f; p_y++) { - mvwprintw(win, p_y, p_x, "%*s", sz_x - 1, ""); + mvwprintw(win, p_y, p_x, "%*s", sz_x_f - 1, ""); } - char p_info[24]; - uint32_t p_len; - p_x = 1; - uint32_t l_pad = (sz_x - p_len) / 2; - - wattrset(win, A_BOLD); - mvwprintw(win, p_y, p_x, "%*s%s%*s", l_pad, "", p_info, l_pad, ""); - wattroff(win, A_BOLD); + 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->start); free(fui); } \ No newline at end of file diff --git a/src/ui/widget/group.c b/src/ui/widget/group.c index ce2d9ea..7228ce7 100644 --- a/src/ui/widget/group.c +++ b/src/ui/widget/group.c @@ -16,13 +16,15 @@ } void group_default_callback(callback_args_t *args) { - group_t *g = (group_t *)args->widget; + 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); @@ -60,6 +62,13 @@ void group_default_callback(callback_args_t *args) { 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); + } } MAKE_RESPONSE_M1(args, resp_data, response); break; @@ -124,6 +133,7 @@ group_t *init_group(WINDOW **win, widget_t *w_parent, group_el_init_t *children, } if (i == 0) { group->first_id = w->id; + group->last_id = w->id; } else { group->last_id = w->id; } diff --git a/src/ui/widget/group.h b/src/ui/widget/group.h index 291aa8b..4516dc6 100644 --- a/src/ui/widget/group.h +++ b/src/ui/widget/group.h @@ -32,18 +32,14 @@ typedef struct { uint32_t first_id, last_id; } group_t; -#define FIND_ACTIVE_ELEMENT(g, active_id, element_ptr, element_idx) \ - for (int i = 0; i < g->count; i++) { \ - if (g->elements[i].id == active_id) { \ - element_ptr = &(g->elements[i]); \ - element_idx = i; \ - break; \ - } \ - } +#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 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); From a447ff622552f74f0b2dfe627974c19465cc0eb5 Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Thu, 15 Jan 2026 22:09:07 +0200 Subject: [PATCH 36/45] ui: file list modal: fixes navigation, improves modal size --- TODO.md | 2 ++ src/deps.mk | 29 ----------------------------- src/ui/widget/fs_file_list.c | 5 ++++- src/ui/widget/group.c | 4 ++-- 4 files changed, 8 insertions(+), 32 deletions(-) diff --git a/TODO.md b/TODO.md index bf10a36..ec1af49 100644 --- a/TODO.md +++ b/TODO.md @@ -10,8 +10,10 @@ TODO: - [ ] Bug with file list on downloading files - [ ] Remember page numbers - [ ] "Server message" to notification +- [ ] file list -> list template INPUT: - [x] hidden text for passwords + - [x] move cursor through the text in input diff --git a/src/deps.mk b/src/deps.mk index a154278..e69de29 100644 --- a/src/deps.mk +++ b/src/deps.mk @@ -1,29 +0,0 @@ -client.o: src/client.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/client.h -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/client.h src/main.h \ - src/types.h src/query.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/ui/widget/dialogue.h src/ui/widget/file_list.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/client.h src/main.h src/types.h src/connection.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 diff --git a/src/ui/widget/fs_file_list.c b/src/ui/widget/fs_file_list.c index 0d1733a..fe739c9 100644 --- a/src/ui/widget/fs_file_list.c +++ b/src/ui/widget/fs_file_list.c @@ -1,6 +1,5 @@ #include "fs_file_list.h" #include "../../file_processor.h" -#include "../app.h" #include "progress_bar.h" #include #include @@ -32,6 +31,7 @@ static void fl_add(fl_item_t **f_start, fl_item_t **f_current, } fl_item_t *get_files_from_fs(char *path) { + /* TODO: separate files from directories, mark directories, filter empty fields */ DIR *dir; struct dirent *dent; @@ -82,6 +82,7 @@ void fs_file_list_cb(callback_args_t *args) { 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->current_count = 0; @@ -90,6 +91,8 @@ ui_fs_file_list_t *init_fs_file_list(WINDOW **win, widget_t *w_parent) { fl_ui->start = malloc(sizeof(fl_item_t *)); /* For compatibility reasons */ *(fl_ui->start) = get_files_from_fs("./"); 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; } diff --git a/src/ui/widget/group.c b/src/ui/widget/group.c index 7228ce7..e39dd06 100644 --- a/src/ui/widget/group.c +++ b/src/ui/widget/group.c @@ -1,6 +1,5 @@ #include "group.h" #include "button.h" -#include "file_list.h" #include "fs_file_list.h" #include "input.h" #include "progress_bar.h" @@ -65,9 +64,10 @@ void group_default_callback(callback_args_t *args) { } else { widget = (widget_t *) element_ptr->element; if (widget->callback != NULL) { - memccpy(&new_args, args, 1, sizeof(callback_args_t *)); + 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); From 167b5c635a040cb52362ef3ca2142dca1aa8a881 Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Fri, 16 Jan 2026 16:34:40 +0200 Subject: [PATCH 37/45] ui: file list (not completed) --- src/deps.mk | 29 +++++++ src/query.c | 2 +- src/ui/modals/ask_server_addr.c | 2 +- src/ui/modals/login_credentials.c | 2 +- src/ui/modals/login_option.c | 2 +- src/ui/modals/notification.c | 2 +- src/ui/widget/dialogue.c | 19 +++++ src/ui/widget/fs_file_list.c | 126 ++++++++++++++++++------------ src/ui/widget/fs_file_list.h | 15 +++- 9 files changed, 142 insertions(+), 57 deletions(-) diff --git a/src/deps.mk b/src/deps.mk index e69de29..a154278 100644 --- a/src/deps.mk +++ b/src/deps.mk @@ -0,0 +1,29 @@ +client.o: src/client.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/client.h +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/client.h src/main.h \ + src/types.h src/query.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/ui/widget/dialogue.h src/ui/widget/file_list.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/client.h src/main.h src/types.h src/connection.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 diff --git a/src/query.c b/src/query.c index b600d00..dcbcf74 100644 --- a/src/query.c +++ b/src/query.c @@ -36,7 +36,7 @@ void query_loop(app_t *app) { int sr; static file_args_t file_args; callback_args_t d_args = { - .app = app, .widget = NULL, .data = NULL, .resp_data = NULL}; + .app = app, .element = NULL, .data = NULL, .resp_data = NULL}; init_file_args(&file_args); query_args->sd = sd; diff --git a/src/ui/modals/ask_server_addr.c b/src/ui/modals/ask_server_addr.c index 6ac6f29..7600700 100644 --- a/src/ui/modals/ask_server_addr.c +++ b/src/ui/modals/ask_server_addr.c @@ -21,7 +21,7 @@ void asa_modal_dialogue_callback(callback_args_t *args) { memcpy(&d_args, args, sizeof(callback_args_t)); d_args.app = NULL; d_args.resp_data = &response; - d_args.widget = app->active_widget; + 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); diff --git a/src/ui/modals/login_credentials.c b/src/ui/modals/login_credentials.c index 32c8616..3e421ff 100644 --- a/src/ui/modals/login_credentials.c +++ b/src/ui/modals/login_credentials.c @@ -22,7 +22,7 @@ void init_login_credentials_modal_cb(callback_args_t *args) { memcpy(&d_args, args, sizeof(callback_args_t)); d_args.app = NULL; d_args.resp_data = &response; - d_args.widget = app->active_widget; + 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); diff --git a/src/ui/modals/login_option.c b/src/ui/modals/login_option.c index 7cfc548..6c56e52 100644 --- a/src/ui/modals/login_option.c +++ b/src/ui/modals/login_option.c @@ -19,7 +19,7 @@ void login_modal_option_cb(callback_args_t *args) { memcpy(&d_args, args, sizeof(callback_args_t)); d_args.app = NULL; d_args.resp_data = &response; - d_args.widget = app->active_widget; + d_args.element = app->active_widget; dialogue_default_callback(&d_args); if (response > -1) { switch (response) { diff --git a/src/ui/modals/notification.c b/src/ui/modals/notification.c index c9ddd6c..bf98ab6 100644 --- a/src/ui/modals/notification.c +++ b/src/ui/modals/notification.c @@ -20,7 +20,7 @@ void init_notification_modal_cb(callback_args_t *args) { memcpy(&d_args, args, sizeof(callback_args_t)); d_args.app = NULL; d_args.resp_data = &response; - d_args.widget = app->active_widget; + d_args.element = app->active_widget; dialogue_default_callback(&d_args); app->query_args->state = WAIT_CLIENT; diff --git a/src/ui/widget/dialogue.c b/src/ui/widget/dialogue.c index 56c4b4f..45c1d0e 100644 --- a/src/ui/widget/dialogue.c +++ b/src/ui/widget/dialogue.c @@ -42,6 +42,18 @@ } \ } +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); @@ -54,6 +66,7 @@ void dialogue_default_callback(callback_args_t *args) { int32_t diff; d->needs_update = true; input_t *input; + widget_t *widget; switch (key) { case '\t': if (d->active.type == g_content) { @@ -98,6 +111,12 @@ void dialogue_default_callback(callback_args_t *args) { *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); diff --git a/src/ui/widget/fs_file_list.c b/src/ui/widget/fs_file_list.c index fe739c9..623dfd4 100644 --- a/src/ui/widget/fs_file_list.c +++ b/src/ui/widget/fs_file_list.c @@ -1,5 +1,4 @@ #include "fs_file_list.h" -#include "../../file_processor.h" #include "progress_bar.h" #include #include @@ -7,35 +6,60 @@ #include #include #include +#include #include +#include #include #include void reset_file_list(ui_fs_file_list_t *fl_ui); -static void fl_add(fl_item_t **f_start, fl_item_t **f_current, - struct dirent *dent) { - fl_item_t *c_item = malloc(sizeof(fl_item_t)); - c_item->name = dent->d_name; - c_item->description = NULL; - c_item->owner = NULL; - c_item->size = 0; +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; } } -fl_item_t *get_files_from_fs(char *path) { - /* TODO: separate files from directories, mark directories, filter empty fields */ +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; - fl_item_t *l_start = NULL, *l_current = NULL; + 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) { @@ -43,39 +67,60 @@ fl_item_t *get_files_from_fs(char *path) { errno = 1; } while ((dent = readdir(dir)) != NULL) { - fl_add(&l_start, &l_current, dent); + 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) { + char d_name[64] = "./"; + if (fui->current->d_type == DT_DIR) { + if (!strcmp("/..", fui->current->name)) { + /* TODO: slice the last "/abc" */ + } else { + strcat(d_name, fui->current->name + 1); /* skip "/" in the name */ + } + fui->start = get_files_from_fs(d_name); + fui->current = fui->start; + draw_fs_file_list(fui); } - return l_start; } 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; - fl_item_t *f_item; + fs_fl_item_t *f_item; ui_progress_bar_t *pb; switch (key) { case KEY_DOWN: - if (fui->current_idx < fui->current_count - 1) { + if (fui->current->next != NULL) { + fui->current = fui->current->next; fui->current_idx++; draw_fs_file_list(fui); - } else { - fl_clear(fui->start, fui->current); - reset_file_list(fui); } break; case KEY_UP: - if (fui->current_idx > 0) { + if (fui->current->prev != NULL) { + fui->current = fui->current->prev; fui->current_idx--; draw_fs_file_list(fui); - } else { - fl_clear(fui->start, fui->current); - reset_fs_file_list(fui); - fui->activate_last = true; } break; case '\n': - /* here goes file select */ + select_item(fui); break; } } @@ -85,22 +130,16 @@ ui_fs_file_list_t *init_fs_file_list(WINDOW **win, widget_t *w_parent) { WINDOW *win_par = *(w_parent->w_parent->parent_win); init_widget(&(fl_ui->w), w_parent, win, ""); fl_ui->current_idx = 0; - fl_ui->current_count = 0; - fl_ui->activate_last = false; fl_ui->info_win = NULL; - fl_ui->start = malloc(sizeof(fl_item_t *)); /* For compatibility reasons */ - *(fl_ui->start) = get_files_from_fs("./"); + fl_ui->start = get_files_from_fs("./"); + 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; - fl_ui->current_count = 0; - fl_ui->activate_last = false; -} +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; @@ -111,39 +150,31 @@ void draw_fs_file_list(ui_fs_file_list_t *fl_ui) { int32_t sz_x_f = sz_x - 2; // actual size (without box) 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; - - if (fl_ui->activate_last) { - fl_ui->current_idx = fl_ui->current_count - 1; - fl_ui->activate_last = false; - } + 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 window background */ + wattrset(win, A_REVERSE); /* Match with modal background */ do { if (p_y < 1) { p_y++; - cur_el_idx++; continue; } - if (cur_el_idx == fl_ui->current_idx) { + 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 (cur_el_idx == fl_ui->current_idx) { + if (el == fl_ui->current) { wattroff(win, COLOR_PAIR(3) | A_BOLD); } p_y++; - cur_el_idx++; } while ((el = el->next) != NULL && p_y < sz_y_f); p_x = 1; @@ -158,7 +189,6 @@ void draw_fs_file_list(ui_fs_file_list_t *fl_ui) { } void destroy_fs_file_list(ui_fs_file_list_t *fui) { - fl_clear(fui->start, fui->current); - free(fui->start); + 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 index 0413116..b36f0d0 100644 --- a/src/ui/widget/fs_file_list.h +++ b/src/ui/widget/fs_file_list.h @@ -3,15 +3,22 @@ #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; - fl_item_t **current; - fl_item_t **start; + fs_fl_item_t *current; + fs_fl_item_t *start; int32_t current_idx; - uint32_t current_count; - uint32_t activate_last : 1; WINDOW *const* info_win; + char d_path[512]; } ui_fs_file_list_t; void draw_fs_file_list(ui_fs_file_list_t *fl_ui); From 83157a55f354b663f496a001295bd112070c277a Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Tue, 20 Jan 2026 15:17:40 +0200 Subject: [PATCH 38/45] ui: selection of upload file (needs next step realization) --- src/main.h | 6 +++++- src/ui/modals/upload_dialogue.c | 13 ++++++++++-- src/ui/widget/fs_file_list.c | 37 +++++++++++++++++++++++++-------- src/ui/widget/fs_file_list.h | 2 +- 4 files changed, 45 insertions(+), 13 deletions(-) diff --git a/src/main.h b/src/main.h index ca06b5e..f00298c 100644 --- a/src/main.h +++ b/src/main.h @@ -13,6 +13,9 @@ #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; @@ -49,6 +52,7 @@ enum state { typedef struct p_file { char *name; + char *path; size_t size; size_t rest; int fd; @@ -83,7 +87,7 @@ typedef struct query_args { uint64_t capacity; uint64_t size; } server_message; - void *file_list_ui; /* TODO: find solution to insert actual type without recursion */ + void *file_list_ui; /* TODO: find solution to insert actual type without void pointers */ void *progress_bar; void *active_dialogue; char *notification; diff --git a/src/ui/modals/upload_dialogue.c b/src/ui/modals/upload_dialogue.c index a7f9c91..53997ef 100644 --- a/src/ui/modals/upload_dialogue.c +++ b/src/ui/modals/upload_dialogue.c @@ -1,8 +1,11 @@ #include "../app.h" #include "../widget/dialogue.h" #include "../widget/group.h" +#include "../widget/fs_file_list.h" #include #include +#include +#include #include void upload_dialogue_modal_cb(callback_args_t *args) { @@ -15,12 +18,19 @@ void upload_dialogue_modal_cb(callback_args_t *args) { 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; if (response > -1) { switch (response) { case 0: app->query_args->state = S_WAIT_SERVER; break; case 1: + app->query_args->file = malloc(sizeof(p_file_t)); + app->query_args->file->path = malloc(strlen(fui->d_path) + strlen(fui->current->name) + 2); + strcpy(app->query_args->file->path, fui->current->name); + sprintf(app->query_args->file->path, "%s/%s", fui->d_path, fui->current->name); + destroy_dialogue(d, app); + app->query_args->state = S_UPLOAD_PARAMS; break; } } @@ -35,8 +45,7 @@ dialogue_t *init_upload_dialogue_modal(app_t *app) { {.type = w_end}}; group_el_init_t actions[] = { - {.type = w_button, .label = "Cancel", .is_default = 0}, - {.type = w_end}}; + {.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", diff --git a/src/ui/widget/fs_file_list.c b/src/ui/widget/fs_file_list.c index 623dfd4..d84b8d8 100644 --- a/src/ui/widget/fs_file_list.c +++ b/src/ui/widget/fs_file_list.c @@ -1,5 +1,6 @@ #include "fs_file_list.h" #include "progress_bar.h" +#include "../app.h" #include #include #include @@ -79,22 +80,39 @@ fs_fl_item_t *get_files_from_fs(char *path) { 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; + 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) { - char d_name[64] = "./"; +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)) { - /* TODO: slice the last "/abc" */ + 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 { - strcat(d_name, fui->current->name + 1); /* skip "/" in the name */ + 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->start = get_files_from_fs(d_name); fui->current = fui->start; + fui->current_idx = 0; draw_fs_file_list(fui); + } else if (fui->current->d_type == DT_REG) { + *resp_data = 1; } } @@ -120,7 +138,7 @@ void fs_file_list_cb(callback_args_t *args) { } break; case '\n': - select_item(fui); + select_item(fui, (int32_t *) args->resp_data); break; } } @@ -131,7 +149,8 @@ ui_fs_file_list_t *init_fs_file_list(WINDOW **win, widget_t *w_parent) { init_widget(&(fl_ui->w), w_parent, win, ""); fl_ui->current_idx = 0; fl_ui->info_win = NULL; - fl_ui->start = get_files_from_fs("./"); + 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; diff --git a/src/ui/widget/fs_file_list.h b/src/ui/widget/fs_file_list.h index b36f0d0..db380d8 100644 --- a/src/ui/widget/fs_file_list.h +++ b/src/ui/widget/fs_file_list.h @@ -18,7 +18,7 @@ typedef struct { fs_fl_item_t *start; int32_t current_idx; WINDOW *const* info_win; - char d_path[512]; + char *d_path; } ui_fs_file_list_t; void draw_fs_file_list(ui_fs_file_list_t *fl_ui); From 975f4b9dcdbb68c444c991576b80bebb8140cc56 Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Tue, 20 Jan 2026 19:30:01 +0200 Subject: [PATCH 39/45] ui: next step of file upload (not finished) --- src/file_processor.c | 29 +++++++++-- src/file_processor.h | 1 + src/ui/app.c | 6 ++- src/ui/modals/upload_dialogue.c | 14 +++--- src/ui/modals/upload_props_dialogue.c | 70 +++++++++++++++++++++++++++ src/ui/modals/upload_props_dialogue.h | 9 ++++ src/ui/widget/dialogue.c | 4 +- src/ui/widget/fs_file_list.c | 1 - 8 files changed, 117 insertions(+), 17 deletions(-) create mode 100644 src/ui/modals/upload_props_dialogue.c create mode 100644 src/ui/modals/upload_props_dialogue.h diff --git a/src/file_processor.c b/src/file_processor.c index bb6ade0..7150001 100644 --- a/src/file_processor.c +++ b/src/file_processor.c @@ -195,7 +195,7 @@ void file_download(file_args_t *f_args, query_args_t *q_args) { 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; + dialogue_t *d = (dialogue_t *)q_args->active_dialogue; if (size_rest == 0) size_rest = f_selected->size; @@ -219,11 +219,12 @@ void file_download(file_args_t *f_args, query_args_t *q_args) { f_selected->size = 0; 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); + 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); - write(q_args->sd, "file list\n", sizeof("file list\n")-1); + write(q_args->sd, "file list\n", sizeof("file list\n") - 1); q_args->state = S_FILE_LIST; } } @@ -259,6 +260,26 @@ 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; + 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); diff --git a/src/file_processor.h b/src/file_processor.h index 0a3c050..8892dad 100644 --- a/src/file_processor.h +++ b/src/file_processor.h @@ -10,6 +10,7 @@ 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); #endif \ No newline at end of file diff --git a/src/ui/app.c b/src/ui/app.c index 68fab99..9986c36 100644 --- a/src/ui/app.c +++ b/src/ui/app.c @@ -6,6 +6,7 @@ #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 @@ -138,7 +139,6 @@ void app_refresh(app_t *app) { void app_draw_modal(app_t *app) { if (app->modal.needs_destroy) { destroy_dialogue(&(app->modal), app); - app_refresh(app); } if (!app->modal.is_initiated) { switch (app->query_args->state) { @@ -160,6 +160,9 @@ void app_draw_modal(app_t *app) { case S_UPLOAD_FILE: 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: @@ -167,7 +170,6 @@ void app_draw_modal(app_t *app) { case WAIT_CLIENT: case S_FILE_LIST: case S_FILE_SELECT: - case S_UPLOAD_PARAMS: case S_UPLOAD_REQUESTED: case S_ASK_USER_BEFORE_LOGIN: case S_ERR: diff --git a/src/ui/modals/upload_dialogue.c b/src/ui/modals/upload_dialogue.c index 53997ef..922005f 100644 --- a/src/ui/modals/upload_dialogue.c +++ b/src/ui/modals/upload_dialogue.c @@ -1,11 +1,10 @@ #include "../app.h" #include "../widget/dialogue.h" -#include "../widget/group.h" #include "../widget/fs_file_list.h" +#include "../widget/group.h" +#include "../../file_processor.h" #include #include -#include -#include #include void upload_dialogue_modal_cb(callback_args_t *args) { @@ -18,17 +17,16 @@ void upload_dialogue_modal_cb(callback_args_t *args) { 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; + ui_fs_file_list_t *fui = + (ui_fs_file_list_t *)d->g_content->elements[0].element; if (response > -1) { switch (response) { case 0: app->query_args->state = S_WAIT_SERVER; + destroy_dialogue(d, app); break; case 1: - app->query_args->file = malloc(sizeof(p_file_t)); - app->query_args->file->path = malloc(strlen(fui->d_path) + strlen(fui->current->name) + 2); - strcpy(app->query_args->file->path, fui->current->name); - sprintf(app->query_args->file->path, "%s/%s", fui->d_path, fui->current->name); + file_upload_open(fui->d_path, fui->current->name, app->query_args); destroy_dialogue(d, app); app->query_args->state = S_UPLOAD_PARAMS; break; diff --git a/src/ui/modals/upload_props_dialogue.c b/src/ui/modals/upload_props_dialogue.c new file mode 100644 index 0000000..5b1e796 --- /dev/null +++ b/src/ui/modals/upload_props_dialogue.c @@ -0,0 +1,70 @@ +#include "../app.h" +#include "../widget/dialogue.h" +#include "../widget/fs_file_list.h" +#include "../widget/group.h" +#include +#include +#include +#include +#include +#include + +void upload_props_dialogue_modal_cb(callback_args_t *args) { + /* TODO: finish */ + 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); + char query[256]; + int32_t query_len = 0; + if (response > -1) { + switch (response) { + case 0: + 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; + case 1: + query_len = sprintf(query, "file upload \"%s\" %zu 1\n", app->query_args->file->name, app->query_args->file->size); + destroy_dialogue(d, app); + app->query_args->state = S_UPLOAD_REQUESTED; + write(app->query_args->sd, query, query_len+1); + 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 = "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/dialogue.c b/src/ui/widget/dialogue.c index 45c1d0e..2dc038a 100644 --- a/src/ui/widget/dialogue.c +++ b/src/ui/widget/dialogue.c @@ -49,11 +49,10 @@ widget_t *get_active_widget(dialogue_t *d) { } else { g = d->g_content; } - widget_t *w = (widget_t *) g->elements[d->active.id - g->first_id].element; + 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); @@ -324,4 +323,5 @@ void destroy_dialogue(dialogue_t *d, void *_app) { 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/fs_file_list.c b/src/ui/widget/fs_file_list.c index d84b8d8..12de02a 100644 --- a/src/ui/widget/fs_file_list.c +++ b/src/ui/widget/fs_file_list.c @@ -1,6 +1,5 @@ #include "fs_file_list.h" #include "progress_bar.h" -#include "../app.h" #include #include #include From c6e6fdffd22a6c198f1bac9cd862db2ca4b32ac5 Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Wed, 21 Jan 2026 17:43:50 +0200 Subject: [PATCH 40/45] ui: file upload basic process (need improvement) --- src/deps.mk | 8 +++---- src/file_processor.c | 22 ++++++----------- src/file_processor.h | 1 + src/main.c | 6 +++-- src/main.h | 2 ++ src/query.c | 34 ++++----------------------- src/ui/app.c | 3 ++- src/ui/modals/upload_dialogue.c | 2 +- src/ui/modals/upload_props_dialogue.c | 28 ++++++++++++++-------- 9 files changed, 44 insertions(+), 62 deletions(-) diff --git a/src/deps.mk b/src/deps.mk index a154278..2737a6c 100644 --- a/src/deps.mk +++ b/src/deps.mk @@ -7,12 +7,12 @@ connection.o: src/connection.c src/main.h src/types.h src/ui/app.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/client.h src/main.h \ - src/types.h src/query.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/types.h src/file_processor.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/file_list.h \ - src/ui/widget/progress_bar.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 \ diff --git a/src/file_processor.c b/src/file_processor.c index 7150001..3c30ae3 100644 --- a/src/file_processor.c +++ b/src/file_processor.c @@ -15,6 +15,7 @@ #include #include "client.h" +#include "file_processor.h" #include "main.h" #include "query.h" #include "ui/widget/dialogue.h" @@ -25,8 +26,6 @@ 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); -static void clear_file_in_query(query_args_t *q_args); - void file_list(file_args_t *f_args, query_args_t *q_args) { uint32_t qlen; char *query = NULL; @@ -229,7 +228,7 @@ void file_download(file_args_t *f_args, query_args_t *q_args) { } } } - +/* obsolete */ int32_t file_upload_request(char *query, query_args_t *q_args) { char qbuf[INBUFSIZE]; char fpath[512]; @@ -277,6 +276,7 @@ int32_t file_upload_open(char *dpath, char *fname, query_args_t *q_args) { strcpy(q_args->file->name, fname); q_args->file->size = fsize; q_args->file->rest = fsize; + q_args->file->description = NULL; return 0; } @@ -292,13 +292,6 @@ 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% -} */ - 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) { @@ -310,17 +303,16 @@ int32_t file_upload(query_args_t *q_args) { 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); + /* TODO: уведомление пользователю */ 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; } diff --git a/src/file_processor.h b/src/file_processor.h index 8892dad..4345e45 100644 --- a/src/file_processor.h +++ b/src/file_processor.h @@ -13,4 +13,5 @@ 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/main.c b/src/main.c index 7361c6d..acfea8a 100644 --- a/src/main.c +++ b/src/main.c @@ -71,8 +71,10 @@ int32_t process_user_input(app_t *app, callback_args_t *d_args) { return OK; case 'U': case 'u': - app->query_args->state = S_UPLOAD_FILE; - break; + 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); diff --git a/src/main.h b/src/main.h index f00298c..4579c4c 100644 --- a/src/main.h +++ b/src/main.h @@ -39,6 +39,7 @@ enum state { S_FILE_DOWNLOAD, S_UPLOAD_PARAMS, S_UPLOAD_FILE, + S_UPLOAD_FILE_SELECT, S_UPLOAD_REQUESTED, S_ASK_USER_BEFORE_LOGIN, S_ERR, @@ -53,6 +54,7 @@ enum state { typedef struct p_file { char *name; char *path; + char *description; size_t size; size_t rest; int fd; diff --git a/src/query.c b/src/query.c index dcbcf74..138390b 100644 --- a/src/query.c +++ b/src/query.c @@ -163,7 +163,6 @@ int process_query(query_args_t *query_args, file_args_t *file_args) { if (!file_upload_start(query_args)) { query_args->state = S_UPLOAD_FILE; } else { - print_prompt(query_args->params); query_args->state = WAIT_CLIENT; } break; @@ -172,8 +171,8 @@ int process_query(query_args_t *query_args, file_args_t *file_args) { if (file_upload(query_args)) { /* 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; } break; case S_ASK_USER_BEFORE_LOGIN: @@ -266,30 +265,7 @@ void init_query_args(query_args_t *q_args, params_t *params) { } 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/ui/app.c b/src/ui/app.c index 9986c36..d4c7417 100644 --- a/src/ui/app.c +++ b/src/ui/app.c @@ -157,7 +157,7 @@ void app_draw_modal(app_t *app) { case S_FILE_DOWNLOAD: init_dwn_pr_modal(app); break; - case S_UPLOAD_FILE: + case S_UPLOAD_FILE_SELECT: init_upload_dialogue_modal(app); break; case S_UPLOAD_PARAMS: @@ -176,6 +176,7 @@ void app_draw_modal(app_t *app) { case S_WAIT_SERVER: case S_N_D: case S_NEXT_ACTION: + case S_UPLOAD_FILE: if (app->query_args->notification != NULL) { init_notification_modal(app); } else { diff --git a/src/ui/modals/upload_dialogue.c b/src/ui/modals/upload_dialogue.c index 922005f..e7db56b 100644 --- a/src/ui/modals/upload_dialogue.c +++ b/src/ui/modals/upload_dialogue.c @@ -8,7 +8,7 @@ #include void upload_dialogue_modal_cb(callback_args_t *args) { - int32_t response; + int32_t response = -1; callback_args_t d_args; app_t *app = args->app; dialogue_t *d = (dialogue_t *)app->active_widget; diff --git a/src/ui/modals/upload_props_dialogue.c b/src/ui/modals/upload_props_dialogue.c index 5b1e796..ff1f0dd 100644 --- a/src/ui/modals/upload_props_dialogue.c +++ b/src/ui/modals/upload_props_dialogue.c @@ -1,7 +1,7 @@ #include "../app.h" #include "../widget/dialogue.h" -#include "../widget/fs_file_list.h" #include "../widget/group.h" +#include "../widget/input.h" #include #include #include @@ -10,8 +10,7 @@ #include void upload_props_dialogue_modal_cb(callback_args_t *args) { - /* TODO: finish */ - int32_t response; + int32_t response = -1; callback_args_t d_args; app_t *app = args->app; dialogue_t *d = (dialogue_t *)app->active_widget; @@ -22,9 +21,21 @@ void upload_props_dialogue_modal_cb(callback_args_t *args) { 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); + destroy_dialogue(d, app); + 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) @@ -33,12 +44,6 @@ void upload_props_dialogue_modal_cb(callback_args_t *args) { app->query_args->file = NULL; app->query_args->state = S_WAIT_SERVER; break; - case 1: - query_len = sprintf(query, "file upload \"%s\" %zu 1\n", app->query_args->file->name, app->query_args->file->size); - destroy_dialogue(d, app); - app->query_args->state = S_UPLOAD_REQUESTED; - write(app->query_args->sd, query, query_len+1); - break; } } } @@ -51,7 +56,10 @@ dialogue_t *init_upload_props_dialogue_modal(app_t *app) { {.type = w_end}}; group_el_init_t actions[] = { - {.type = w_button, .label = "Cancel", .is_default = 0}, {.type = w_end}}; + {.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", From 346ded71bc0266c1a714f50d1c1a5ef4823f43c5 Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Wed, 21 Jan 2026 17:45:22 +0200 Subject: [PATCH 41/45] updates TODO --- TODO.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/TODO.md b/TODO.md index ec1af49..7f08a4f 100644 --- a/TODO.md +++ b/TODO.md @@ -12,8 +12,8 @@ TODO: - [ ] "Server message" to notification - [ ] file list -> list template -INPUT: -- [x] hidden text for passwords - -- [x] move cursor through the text in input +UPLOAD: +- [ ] Progress bar +- [ ] Show notifications and messages from the server (universal way) + use notification or something From 50952f324d76376095449b6e0648095ef5c33baa Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Thu, 22 Jan 2026 16:26:36 +0200 Subject: [PATCH 42/45] ui: finished (kinda) file upload --- TODO.md | 2 +- src/deps.mk | 2 +- src/file_processor.c | 23 ++++++++++++++++++--- src/query.c | 10 +++++++-- src/server.c | 17 ++++++++-------- src/ui/app.c | 6 ++++-- src/ui/modals/download_pr.c | 18 +++++++++-------- src/ui/modals/download_pr.h | 2 +- src/ui/modals/server_message.c | 1 + src/ui/widget/dialogue.c | 37 +++++++++++++++++++++++++++++++--- 10 files changed, 88 insertions(+), 30 deletions(-) diff --git a/TODO.md b/TODO.md index 7f08a4f..ea721d1 100644 --- a/TODO.md +++ b/TODO.md @@ -14,6 +14,6 @@ TODO: UPLOAD: - [ ] Progress bar -- [ ] Show notifications and messages from the server (universal way) +- [x] Show notifications and messages from the server (universal way) use notification or something diff --git a/src/deps.mk b/src/deps.mk index 2737a6c..3c35080 100644 --- a/src/deps.mk +++ b/src/deps.mk @@ -25,5 +25,5 @@ query.o: src/query.c src/client.h src/main.h src/types.h src/connection.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 + src/ui/widget/file_list.h src/server.h src/ui/widget/dialogue.h server.o: src/server.c src/main.h src/types.h diff --git a/src/file_processor.c b/src/file_processor.c index 3c30ae3..4041bc5 100644 --- a/src/file_processor.c +++ b/src/file_processor.c @@ -204,9 +204,9 @@ void file_download(file_args_t *f_args, query_args_t *q_args) { if (!(it_count % it_interval)) { pb->procent = progress; d->needs_update = true; - } - if (it_count % it_interval) + } else { q_args->buf_used = 0; + } if (size_rest < qlen) { size_rest = 0; } else { @@ -292,18 +292,35 @@ int32_t file_upload_start(query_args_t *q_args) { } int32_t file_upload(query_args_t *q_args) { + 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 { - /* TODO: уведомление пользователю */ + it_count = 0; + it_interval = 0; return 1; } return 0; diff --git a/src/query.c b/src/query.c index 138390b..9e9dd93 100644 --- a/src/query.c +++ b/src/query.c @@ -22,6 +22,7 @@ #include "main.h" #include "server.h" #include "ui/app.h" +#include "ui/widget/dialogue.h" static void wait_side(query_args_t *q_args); void user_request_description(query_args_t *q_args); @@ -44,6 +45,9 @@ void query_loop(app_t *app) { for (;;) { /* update screen */ + if (query_args->state == S_WAIT_SERVER && app->modal.is_initiated) { + destroy_dialogue(&(app->modal), app); + } app_draw_modal(app); app_refresh(app); @@ -134,7 +138,7 @@ 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: @@ -168,11 +172,13 @@ int process_query(query_args_t *query_args, file_args_t *file_args) { break; case S_UPLOAD_PARAMS: case S_UPLOAD_FILE: - if (file_upload(query_args)) { + if ((res = file_upload(query_args)) == 1) { /* upload finishes */ user_request_description(query_args); clear_file_in_query(query_args); query_args->state = S_WAIT_SERVER; + } else if (res == -1) { + /* TODO: Error */ } break; case S_ASK_USER_BEFORE_LOGIN: diff --git a/src/server.c b/src/server.c index b7b51a4..947b948 100644 --- a/src/server.c +++ b/src/server.c @@ -14,7 +14,7 @@ void ask_uname_and_password(params_t *params); void ask_register(params_t *params, char *email); -#define PRINT_SRV_MESSAGE(q_args) \ +#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); \ @@ -36,7 +36,7 @@ int process_server_command(char *line, int l_len, query_args_t *q_args) { /* LOGIN */ if (!strncmp(line, "login>", ws_pos)) { if (q_args->server_message.size > 0) { - PRINT_SRV_MESSAGE(q_args); + 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)); @@ -110,13 +110,12 @@ int process_server_command(char *line, int l_len, query_args_t *q_args) { strncat(q_args->server_message.text, line, l_len); q_args->server_message.text[q_args->server_message.size + l_len] = 0; } - } 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); - } */ + 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--; + } } return 0; diff --git a/src/ui/app.c b/src/ui/app.c index d4c7417..1441f16 100644 --- a/src/ui/app.c +++ b/src/ui/app.c @@ -155,7 +155,10 @@ void app_draw_modal(app_t *app) { init_server_message_modal(app); break; case S_FILE_DOWNLOAD: - init_dwn_pr_modal(app); + 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); @@ -176,7 +179,6 @@ void app_draw_modal(app_t *app) { case S_WAIT_SERVER: case S_N_D: case S_NEXT_ACTION: - case S_UPLOAD_FILE: if (app->query_args->notification != NULL) { init_notification_modal(app); } else { diff --git a/src/ui/modals/download_pr.c b/src/ui/modals/download_pr.c index e32de93..bfc3606 100644 --- a/src/ui/modals/download_pr.c +++ b/src/ui/modals/download_pr.c @@ -5,22 +5,24 @@ #include #include -void dwn_pr_modal_cb(callback_args_t *args) { -} +void dwn_pr_modal_cb(callback_args_t *args) {} -dialogue_t *init_dwn_pr_modal(app_t *app) { +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}}; + group_el_init_t content[] = { + {.type = w_progress, .label = "", .length = 30}, /* length is ignored */ + {.type = w_end}}; char text[256]; - sprintf(text, "Downloading file: %s", app->file_args->f_selected.name); + 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 = 0}, {.type = w_end}}; + {.type = w_button, .label = "Cancel", .is_default = 1}, {.type = w_end}}; app->modal.w.parent_win = &app->win; - init_dialogue(&(app->modal), "Download", text, &(app->coordinates)); + init_dialogue(&(app->modal), is_upload ? "Upload" : "Download", text, + &(app->coordinates)); dialogue_t *d = &(app->modal); d->w.callback = dwn_pr_modal_cb; diff --git a/src/ui/modals/download_pr.h b/src/ui/modals/download_pr.h index adc5c5e..b5274e7 100644 --- a/src/ui/modals/download_pr.h +++ b/src/ui/modals/download_pr.h @@ -4,6 +4,6 @@ #include "../widget/dialogue.h" #include "../app.h" -dialogue_t *init_dwn_pr_modal(app_t *app); +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/server_message.c b/src/ui/modals/server_message.c index 53ec6b5..9b0e6f2 100644 --- a/src/ui/modals/server_message.c +++ b/src/ui/modals/server_message.c @@ -28,6 +28,7 @@ void server_message_modal_cb(callback_args_t *args) { destroy_dialogue(d, app); /* 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; diff --git a/src/ui/widget/dialogue.c b/src/ui/widget/dialogue.c index 2dc038a..08d4217 100644 --- a/src/ui/widget/dialogue.c +++ b/src/ui/widget/dialogue.c @@ -187,10 +187,12 @@ void dialogue_init_active_id(dialogue_t *dialogue) { dialogue->active.type = g_content; widget_t *w = (widget_t *)dialogue->g_content->elements[i].element; dialogue->active.id = w->id; - break; + return; + } } - } else if (dialogue->g_action != NULL) { + } + 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 || @@ -198,12 +200,41 @@ void dialogue_init_active_id(dialogue_t *dialogue) { dialogue->active.type = g_action; widget_t *w = (widget_t *)dialogue->g_action->elements[i].element; dialogue->active.id = w->id; - break; + 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; \ From b81211c7640339ae8872171d4a174604327c81c3 Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Fri, 23 Jan 2026 13:50:26 +0200 Subject: [PATCH 43/45] ui: close modal on pressing escape (not everywhere) --- src/deps.mk | 29 --------------------------- src/main.c | 4 ++-- src/query.c | 3 --- src/ui/app.c | 4 +++- src/ui/modals/ask_server_addr.c | 2 +- src/ui/modals/login_credentials.c | 2 +- src/ui/modals/login_option.c | 2 +- src/ui/modals/notification.c | 2 +- src/ui/modals/server_message.c | 2 +- src/ui/modals/upload_dialogue.c | 27 +++++++++++++------------ src/ui/modals/upload_props_dialogue.c | 2 +- src/ui/widget/dialogue.c | 6 ++++-- 12 files changed, 29 insertions(+), 56 deletions(-) diff --git a/src/deps.mk b/src/deps.mk index 3c35080..e69de29 100644 --- a/src/deps.mk +++ b/src/deps.mk @@ -1,29 +0,0 @@ -client.o: src/client.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/client.h -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/client.h src/main.h \ - src/types.h src/file_processor.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/client.h src/main.h src/types.h src/connection.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 src/ui/widget/dialogue.h -server.o: src/server.c src/main.h src/types.h diff --git a/src/main.c b/src/main.c index acfea8a..3b60225 100644 --- a/src/main.c +++ b/src/main.c @@ -76,8 +76,8 @@ int32_t process_user_input(app_t *app, callback_args_t *d_args) { break; } default: - d_args->data = (void *)&c; - app->active_callback(d_args); + d_args->data = (void *)&c; + app->active_callback(d_args); break; } return OK; diff --git a/src/query.c b/src/query.c index 9e9dd93..76da448 100644 --- a/src/query.c +++ b/src/query.c @@ -45,9 +45,6 @@ void query_loop(app_t *app) { for (;;) { /* update screen */ - if (query_args->state == S_WAIT_SERVER && app->modal.is_initiated) { - destroy_dialogue(&(app->modal), app); - } app_draw_modal(app); app_refresh(app); diff --git a/src/ui/app.c b/src/ui/app.c index 1441f16..993234c 100644 --- a/src/ui/app.c +++ b/src/ui/app.c @@ -73,6 +73,7 @@ void init_nc() { keypad(stdscr, TRUE); noecho(); curs_set(false); + set_escdelay(50); if (!has_colors()) { printf("Your terminal does not support color\n"); @@ -137,7 +138,8 @@ void app_refresh(app_t *app) { } void app_draw_modal(app_t *app) { - if (app->modal.needs_destroy) { + 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) { diff --git a/src/ui/modals/ask_server_addr.c b/src/ui/modals/ask_server_addr.c index 7600700..75347da 100644 --- a/src/ui/modals/ask_server_addr.c +++ b/src/ui/modals/ask_server_addr.c @@ -30,7 +30,7 @@ void asa_modal_dialogue_callback(callback_args_t *args) { case 0: get_ip_port(app->params, in_ip->value, in_port->value); connect_to_server(app); - destroy_dialogue(d, app); + d->needs_destroy = true; app->query_args->state = S_WAIT_SERVER; break; case 1: diff --git a/src/ui/modals/login_credentials.c b/src/ui/modals/login_credentials.c index 3e421ff..4104591 100644 --- a/src/ui/modals/login_credentials.c +++ b/src/ui/modals/login_credentials.c @@ -37,7 +37,7 @@ void init_login_credentials_modal_cb(callback_args_t *args) { 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; - destroy_dialogue(d, app); + d->needs_destroy = true; print_bars(app); break; case 1: diff --git a/src/ui/modals/login_option.c b/src/ui/modals/login_option.c index 6c56e52..fab7cef 100644 --- a/src/ui/modals/login_option.c +++ b/src/ui/modals/login_option.c @@ -26,7 +26,7 @@ void login_modal_option_cb(callback_args_t *args) { case 0: break; case 1: - destroy_dialogue(d, app); + d->needs_destroy = true; app->query_args->state = S_ASK_LOGIN_USER; break; } diff --git a/src/ui/modals/notification.c b/src/ui/modals/notification.c index bf98ab6..151a9ee 100644 --- a/src/ui/modals/notification.c +++ b/src/ui/modals/notification.c @@ -24,7 +24,7 @@ void init_notification_modal_cb(callback_args_t *args) { dialogue_default_callback(&d_args); app->query_args->state = WAIT_CLIENT; - destroy_dialogue(d, app); + d->needs_destroy = true; } dialogue_t *init_notification_modal(app_t *app) { diff --git a/src/ui/modals/server_message.c b/src/ui/modals/server_message.c index 9b0e6f2..c9bcea8 100644 --- a/src/ui/modals/server_message.c +++ b/src/ui/modals/server_message.c @@ -25,7 +25,7 @@ void server_message_modal_cb(callback_args_t *args) { if (response > -1) { switch (response) { case 0: - destroy_dialogue(d, app); + d->needs_destroy = true; /* TODO: macros */ free(app->query_args->server_message.text); app->query_args->server_message.text = NULL; diff --git a/src/ui/modals/upload_dialogue.c b/src/ui/modals/upload_dialogue.c index e7db56b..3f4e0e8 100644 --- a/src/ui/modals/upload_dialogue.c +++ b/src/ui/modals/upload_dialogue.c @@ -1,8 +1,8 @@ +#include "../../file_processor.h" #include "../app.h" #include "../widget/dialogue.h" #include "../widget/fs_file_list.h" #include "../widget/group.h" -#include "../../file_processor.h" #include #include #include @@ -19,18 +19,19 @@ void upload_dialogue_modal_cb(callback_args_t *args) { dialogue_default_callback(&d_args); ui_fs_file_list_t *fui = (ui_fs_file_list_t *)d->g_content->elements[0].element; - if (response > -1) { - switch (response) { - case 0: - app->query_args->state = S_WAIT_SERVER; - destroy_dialogue(d, app); - break; - case 1: - file_upload_open(fui->d_path, fui->current->name, app->query_args); - destroy_dialogue(d, app); - app->query_args->state = S_UPLOAD_PARAMS; - break; - } + 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; } } diff --git a/src/ui/modals/upload_props_dialogue.c b/src/ui/modals/upload_props_dialogue.c index ff1f0dd..1251193 100644 --- a/src/ui/modals/upload_props_dialogue.c +++ b/src/ui/modals/upload_props_dialogue.c @@ -31,7 +31,7 @@ void upload_props_dialogue_modal_cb(callback_args_t *args) { 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); - destroy_dialogue(d, app); + d->needs_destroy = true; app->query_args->state = S_UPLOAD_REQUESTED; write(app->query_args->sd, query, query_len); break; diff --git a/src/ui/widget/dialogue.c b/src/ui/widget/dialogue.c index 08d4217..1f63d27 100644 --- a/src/ui/widget/dialogue.c +++ b/src/ui/widget/dialogue.c @@ -75,6 +75,9 @@ void dialogue_default_callback(callback_args_t *args) { } *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 == @@ -188,7 +191,6 @@ void dialogue_init_active_id(dialogue_t *dialogue) { widget_t *w = (widget_t *)dialogue->g_content->elements[i].element; dialogue->active.id = w->id; return; - } } } @@ -201,7 +203,6 @@ void dialogue_init_active_id(dialogue_t *dialogue) { widget_t *w = (widget_t *)dialogue->g_action->elements[i].element; dialogue->active.id = w->id; return; - } } } @@ -350,6 +351,7 @@ void destroy_dialogue(dialogue_t *d, void *_app) { 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; From dd9540004b7c3f2135dbc7893f5ba30489e0c923 Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Fri, 23 Jan 2026 18:13:53 +0200 Subject: [PATCH 44/45] UI: file list improvements --- src/deps.mk | 30 ++++++++++++++++++++ src/file_processor.c | 60 ++++++++++++++++++++------------------- src/query.c | 1 - src/server.c | 19 +++++++++---- src/ui/widget/file_list.c | 36 ++++++++++++----------- src/ui/widget/file_list.h | 3 +- 6 files changed, 97 insertions(+), 52 deletions(-) diff --git a/src/deps.mk b/src/deps.mk index e69de29..83714ae 100644 --- a/src/deps.mk +++ b/src/deps.mk @@ -0,0 +1,30 @@ +client.o: src/client.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/client.h +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/client.h src/main.h \ + src/types.h src/file_processor.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/client.h src/main.h src/types.h src/connection.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 4041bc5..e3eee23 100644 --- a/src/file_processor.c +++ b/src/file_processor.c @@ -36,44 +36,42 @@ void file_list(file_args_t *f_args, query_args_t *q_args) { dialogue_t *d = (dialogue_t *)q_args->active_dialogue; fui->start = &(f_args->l_start); fui->current = &(f_args->l_current); + bool list_end_reached = false; if (qbuf_used == 0) qbuf[0] = 0; + if (!list_end_reached) { + while ((qlen = query_extract_from_buf(q_args->buf, &(q_args->buf_used), + &query))) { + if (!strcmp("list_end\n", query)) { + idx = 1; + list_end_reached = true; + break; + } + strcat(qbuf, query); + qbuf_used += strlen(qbuf); + if (strchr(qbuf, '\n') == NULL) + continue; + fl_add(&(f_args->l_current), &(f_args->l_start), qbuf); + 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))) { - if (!strcmp("list_end\n", query)) { - q_args->state = S_FILE_SELECT; - draw_file_list(fui); + if (4 == sscanf(query, "=== PAGE %u/%u COUNT: %u/%u ===", &fui->current_page, + &fui->pages, &fui->current_count, &fui->full_count)) { if (d != NULL && d->is_initiated) { d->needs_update = true; } - idx = 1; - break; - } - strcat(qbuf, query); - qbuf_used += strlen(qbuf); - if (strchr(qbuf, '\n') == NULL) - continue; - fl_add(&(f_args->l_current), &(f_args->l_start), qbuf); - fui->current_count++; - free(query); - query = NULL; - qbuf_used = 0; - qbuf[0] = 0; - } - - uint32_t current_page, pages, current_count, full_count; - - while ((qlen = query_extract_from_buf(q_args->buf, &(q_args->buf_used), - &query))) { - if (4 == sscanf(query, "=== PAGE %u/%u COUNT: %u/%u ===", ¤t_page, - &pages, ¤t_count, &full_count)) { - fui->current_page = current_page; - fui->current_count = current_count; - fui->full_count = full_count; - fui->pages = pages; draw_file_list(fui); + q_args->state = S_FILE_SELECT; return; } } @@ -195,6 +193,8 @@ void file_download(file_args_t *f_args, query_args_t *q_args) { 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; @@ -223,7 +223,9 @@ void file_download(file_args_t *f_args, query_args_t *q_args) { q_args->notification = malloc(strlen(answer) + 1); strcpy(q_args->notification, answer); free(f_selected->name); - write(q_args->sd, "file list\n", sizeof("file list\n") - 1); + 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; } } diff --git a/src/query.c b/src/query.c index 76da448..ad35e3c 100644 --- a/src/query.c +++ b/src/query.c @@ -22,7 +22,6 @@ #include "main.h" #include "server.h" #include "ui/app.h" -#include "ui/widget/dialogue.h" static void wait_side(query_args_t *q_args); void user_request_description(query_args_t *q_args); diff --git a/src/server.c b/src/server.c index 947b948..0c0622d 100644 --- a/src/server.c +++ b/src/server.c @@ -1,4 +1,5 @@ #include "main.h" +#include "ui/widget/file_list.h" #include #include #include @@ -14,7 +15,7 @@ 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) \ +#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); \ @@ -28,6 +29,9 @@ int process_server_command(char *line, int l_len, query_args_t *q_args) { const char *login_options[] = {"Username", "Anonymous", "Register", NULL}; 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) @@ -69,7 +73,9 @@ 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")) { - write(q_args->sd, "file list", sizeof("file list")); + 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 { @@ -86,7 +92,9 @@ int process_server_command(char *line, int l_len, query_args_t *q_args) { /* WELCOME MES */ if (!strncmp(line, "Welcome, ", ws_pos)) { - write(q_args->sd, "file list\n", sizeof("file list\n")-1); + 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; } @@ -110,8 +118,9 @@ int process_server_command(char *line, int l_len, query_args_t *q_args) { 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) { + 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/ui/widget/file_list.c b/src/ui/widget/file_list.c index b9400e8..618807f 100644 --- a/src/ui/widget/file_list.c +++ b/src/ui/widget/file_list.c @@ -1,7 +1,7 @@ #include "file_list.h" #include "../../file_processor.h" -#include "../app.h" #include "../../fs.h" +#include "../app.h" #include "progress_bar.h" #include #include @@ -30,7 +30,8 @@ void file_list_cb(callback_args_t *args) { } else if (fui->current_page < fui->pages) { fl_clear(fui->start, fui->current); app->query_args->state = S_FILE_LIST; - sprintf(query, "p%u\n%n", fui->current_page + 1, &q_len); + 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); } @@ -42,7 +43,8 @@ void file_list_cb(callback_args_t *args) { } else if (fui->current_page > 1) { fl_clear(fui->start, fui->current); app->query_args->state = S_FILE_LIST; - sprintf(query, "p%u\n%n", fui->current_page - 1, &q_len); + 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; @@ -57,13 +59,15 @@ void file_list_cb(callback_args_t *args) { 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 = 0; - fl_ui->pages = 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; } @@ -79,9 +83,9 @@ void reset_file_list(ui_file_list_t *fl_ui) { void draw_file_list(ui_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) + 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); @@ -105,14 +109,14 @@ void draw_file_list(ui_file_list_t *fl_ui) { continue; } if (cur_el_idx == fl_ui->current_idx) { - wattrset(win, A_BOLD | A_REVERSE); + wattrset(parent_win, 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 - p_x, ""); + 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(win, A_BOLD | A_REVERSE); + wattroff(parent_win, A_BOLD | A_REVERSE); } p_y++; cur_el_idx++; @@ -121,7 +125,7 @@ void draw_file_list(ui_file_list_t *fl_ui) { p_x = 1; for (; p_y < sz_y_f; p_y++) { - mvwprintw(win, p_y, p_x, "%*s", sz_x - 1, ""); + mvwprintw(parent_win, p_y, p_x, "%*s", sz_x - 1, ""); } char p_info[24]; uint32_t p_len; @@ -132,9 +136,9 @@ void draw_file_list(ui_file_list_t *fl_ui) { uint32_t l_pad = (sz_x - p_len) / 2; - wattrset(win, A_BOLD); - mvwprintw(win, p_y, p_x, "%*s%s%*s", l_pad, "", p_info, l_pad, ""); - wattroff(win, A_BOLD); + 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; diff --git a/src/ui/widget/file_list.h b/src/ui/widget/file_list.h index a739744..d30c7fa 100644 --- a/src/ui/widget/file_list.h +++ b/src/ui/widget/file_list.h @@ -12,8 +12,9 @@ typedef struct { uint32_t full_count; uint32_t pages; uint32_t current_page; - uint32_t activate_last : 1; + 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); From 482980b600d25d537cd0cfcd97c4762ca837cbed Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Mon, 26 Jan 2026 13:04:59 +0200 Subject: [PATCH 45/45] ui: get rid of client files. changes pagination way in the file list. --- TODO.md | 6 ++--- src/client.c | 46 --------------------------------------- src/client.h | 8 ------- src/deps.mk | 18 ++++++--------- src/file_processor.c | 45 +++++++++++++------------------------- src/query.c | 20 +++++------------ src/ui/widget/file_list.c | 4 ++-- 7 files changed, 32 insertions(+), 115 deletions(-) delete mode 100644 src/client.c delete mode 100644 src/client.h diff --git a/TODO.md b/TODO.md index ea721d1..5cc57a0 100644 --- a/TODO.md +++ b/TODO.md @@ -7,13 +7,13 @@ TODO: - [ ] Group elements as lists - [x] Process arrows - [x] Optimize modals re-drawings -- [ ] Bug with file list on downloading files -- [ ] Remember page numbers +- [x] Bug with file list on downloading files +- [x] Remember page numbers - [ ] "Server message" to notification - [ ] file list -> list template UPLOAD: -- [ ] Progress bar +- [x] Progress bar - [x] Show notifications and messages from the server (universal way) use notification or something diff --git a/src/client.c b/src/client.c deleted file mode 100644 index 1424ec0..0000000 --- a/src/client.c +++ /dev/null @@ -1,46 +0,0 @@ -#include "file_processor.h" -#include "main.h" -#include "client.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 = S_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 = S_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/client.h b/src/client.h deleted file mode 100644 index 6b599d7..0000000 --- a/src/client.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef CLIENT_H -#define CLIENT_H -#include "main.h" - -int process_client_command(char *line, int l_len, query_args_t *q_args); -void print_prompt(params_t *params); - -#endif \ No newline at end of file diff --git a/src/deps.mk b/src/deps.mk index 83714ae..76aa036 100644 --- a/src/deps.mk +++ b/src/deps.mk @@ -1,17 +1,13 @@ -client.o: src/client.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/client.h 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/client.h src/main.h \ - src/types.h src/file_processor.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 \ +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 \ @@ -20,8 +16,8 @@ main.o: src/main.c src/connection.h src/main.h src/types.h src/ui/app.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/client.h src/main.h src/types.h src/connection.h \ - src/ui/app.h src/ui/../common.h src/ui/widget.h src/ui/../main.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 \ diff --git a/src/file_processor.c b/src/file_processor.c index e3eee23..15a3649 100644 --- a/src/file_processor.c +++ b/src/file_processor.c @@ -14,7 +14,6 @@ #include #include -#include "client.h" #include "file_processor.h" #include "main.h" #include "query.h" @@ -36,44 +35,33 @@ void file_list(file_args_t *f_args, query_args_t *q_args) { dialogue_t *d = (dialogue_t *)q_args->active_dialogue; fui->start = &(f_args->l_start); fui->current = &(f_args->l_current); - bool list_end_reached = false; if (qbuf_used == 0) qbuf[0] = 0; - if (!list_end_reached) { - while ((qlen = query_extract_from_buf(q_args->buf, &(q_args->buf_used), - &query))) { - if (!strcmp("list_end\n", query)) { - idx = 1; - list_end_reached = true; - break; - } - strcat(qbuf, query); - qbuf_used += strlen(qbuf); - if (strchr(qbuf, '\n') == NULL) - continue; - fl_add(&(f_args->l_current), &(f_args->l_start), qbuf); - 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))) { - if (4 == sscanf(query, "=== PAGE %u/%u COUNT: %u/%u ===", &fui->current_page, - &fui->pages, &fui->current_count, &fui->full_count)) { + 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; - return; + break; } + strcat(qbuf, query); + qbuf_used += strlen(qbuf); + if (strchr(qbuf, '\n') == NULL) + continue; + fl_add(&(f_args->l_current), &(f_args->l_start), qbuf); + fui->current_count++; + free(query); + query = NULL; + qbuf_used = 0; + qbuf[0] = 0; } } @@ -98,7 +86,6 @@ void file_select(file_args_t *f_args, query_args_t *q_args) { } if (filenum == 0) { - print_prompt(q_args->params); q_args->state = WAIT_CLIENT; return; } @@ -125,7 +112,6 @@ 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; @@ -166,7 +152,6 @@ int32_t ui_file_select(file_args_t *f_args, query_args_t *q_args, int32_t idx) { 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 -2; diff --git a/src/query.c b/src/query.c index ad35e3c..f3a571b 100644 --- a/src/query.c +++ b/src/query.c @@ -16,7 +16,6 @@ #include #include -#include "client.h" #include "connection.h" #include "file_processor.h" #include "main.h" @@ -233,19 +232,10 @@ 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 || q_args->state == S_WAIT_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; } } @@ -269,5 +259,5 @@ void init_query_args(query_args_t *q_args, params_t *params) { void user_request_description(query_args_t *q_args) { strcat(q_args->file->description, "\n\n:END:\n"); write(q_args->sd, q_args->file->description, - strlen(q_args->file->description)-1); + strlen(q_args->file->description) - 1); } \ No newline at end of file diff --git a/src/ui/widget/file_list.c b/src/ui/widget/file_list.c index 618807f..d0a3111 100644 --- a/src/ui/widget/file_list.c +++ b/src/ui/widget/file_list.c @@ -127,11 +127,11 @@ void draw_file_list(ui_file_list_t *fl_ui) { for (; p_y < sz_y_f; p_y++) { mvwprintw(parent_win, p_y, p_x, "%*s", sz_x - 1, ""); } - char p_info[24]; + char p_info[64]; uint32_t p_len; p_x = 1; - sprintf(p_info, "page: %u/%u files: %u/%u%n", fl_ui->current_page, + 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;