Compare commits

..
Author SHA1 Message Date
zheiling b755b9a22f Correct cancel editing the file description after file upload 2026-07-28 15:46:18 +03:00
zheiling a35e0ad748 text_edit_window gets into shape 2026-07-27 19:42:57 +03:00
zheiling 4535f85d21 w_fl_draw: changes line size 2026-07-27 17:05:13 +03:00
zheiling da9990e969 text edit: continuing integration [needs fixes] 2026-07-24 19:05:58 +03:00
zheiling 21bcf119e3 text edit: integration [not finished] 2026-07-24 16:49:29 +03:00
zheiling 01303ffccc text edit: preparations for integration in work flow 2026-07-23 18:43:18 +03:00
zheiling 7cc0d865fa text edit: fixes cursor position on line concatenation 2026-07-23 18:28:07 +03:00
zheiling 23eaae7848 text edit: bottom indicator panel. line split/concatenate. arrow button navigation improvements. 2026-07-22 18:51:59 +03:00
zheiling daded00171 text edit: text navigation. 2026-07-21 18:13:02 +03:00
zheiling 269bd0aa72 text edit: changes line processing. 2026-07-20 18:41:51 +03:00
zheiling 35c666126e text edit: new line processing mechanism [not finished] 2026-07-17 16:43:11 +03:00
zheiling 84ab8a3777 text edit: move cursor logic (not finished). 2026-07-15 18:24:47 +03:00
zheiling bee3921c62 text edit: display text correctly 2026-07-15 15:44:45 +03:00
zheiling 2bc3ee438e further development in text edit window. main_dev_widget mode. 2026-07-14 19:12:26 +03:00
zheiling 63d56de4c8 text edit window [initial] 2026-07-13 18:23:52 +03:00
zheiling 0744a2cbaa fixes cancel download 2026-06-08 15:31:04 +03:00
zheiling 4be4bb926a new realization of cancel download [need review] 2026-06-05 16:27:05 +03:00
zheiling ae81b806eb cancel download: changes idea 2026-06-04 17:49:00 +03:00
zheiling 1ce2cccf59 Cancel donwload: scaffold 2026-06-02 15:38:30 +03:00
zheiling 4aca8fb5a4 ui: shows percentages in progress bar 2026-06-02 14:51:52 +03:00
zheiling 10442f3101 UI: automatically indent lines in dialogues 2026-06-01 18:49:53 +03:00
zheiling cc179e1b72 file download: a small fix 2026-05-29 14:30:10 +03:00
zheiling 8c87c46c46 +Cancel download [needs to test] 2026-05-28 16:44:44 +03:00
zheiling ba9631a513 ui: cancel option fo file upload 2026-05-19 16:08:39 +03:00
zheiling cd83bc4b16 fixes package processing on file load 2026-05-19 13:54:23 +03:00
34 changed files with 1183 additions and 267 deletions
+2
View File
@@ -1,5 +1,7 @@
cmake_minimum_required(VERSION 3.23) cmake_minimum_required(VERSION 3.23)
set(CACHE DEV_WIDGET BOOL OFF "Enable development widget mode")
project(ZBBSClientTUI project(ZBBSClientTUI
VERSION 1.0.0 VERSION 1.0.0
) )
+3 -1
View File
@@ -10,7 +10,9 @@ It will create all necessary file structure in order to run the program correctl
2. Build the binary running `$ make` or `$ make bbs-client` 2. Build the binary running `$ make` or `$ make bbs-client`
3. Run it by `$ make run` 3. Run it by `$ make run`
## Develop ## Development
### Widget development
In order to switch to widget/screen development, configure cmake with `-DDEV_WIDGET=ON`
### Name conventions: ### Name conventions:
+2 -21
View File
@@ -1,36 +1,17 @@
# TODO: # TODO:
## Current: ## Current:
- [ ] File upload multiline description (window) - [ ] Sorting in the file list (local files)
- [ ] Resume download
- [ ] Separate common structures in the common.h - [ ] Separate common structures in the common.h
- [ ] Sorting in the file list
- [ ] Cancel upload/download
- [x] Input: fix non-Latin letters
- [x] Get rid of unsigned integers
- [x] Check registration / enter server name again
- [x] PageUp/PageDown key functionality in lists
- [x] Permission checkboxes for file upload/edit
- [x] Upload menu disappears after alert (when opening link to non-existing directory for example).
## Global tasks: ## Global tasks:
- [ ] Testing - [ ] Testing
- [ ] Documentation - [ ] Documentation
## Bugs:
- [x] Sometimes segfaults while navigating through modal menu
## Features: ## Features:
- [x] Search files
- [x] Correct spacing in the search bar
- [x] Finish registration
- [x] Privilege settings for files
- [ ] Files removal - [ ] Files removal
- [ ] Encryption - [ ] Encryption
- [x] UTF-8 support
- [ ] Check MD5 sum after download/upload - [ ] Check MD5 sum after download/upload
- [ ] Hotkeys for buttons - [ ] Hotkeys for buttons
- [ ] Manipulation by UI-cursor - [ ] Manipulation by UI-cursor
## Fixes:
- [x] Check and fix registration and login
- [x] CMAKE
+6
View File
@@ -22,6 +22,12 @@ endif()
file(GLOB M_SOURCES "*.c") file(GLOB M_SOURCES "*.c")
if(DEV_WIDGET)
list(FILTER M_SOURCES EXCLUDE REGEX "main\\.c$")
else()
list(FILTER M_SOURCES EXCLUDE REGEX "main_dev_widget\\.c$")
endif()
target_sources(zbbs-client-tui target_sources(zbbs-client-tui
PRIVATE PRIVATE
${M_SOURCES} ${M_SOURCES}
+25 -21
View File
@@ -11,44 +11,46 @@
#include <string.h> #include <string.h>
#include <sys/types.h> #include <sys/types.h>
#include <unistd.h> #include <unistd.h>
#include <wchar.h>
void ac_file(WINDOW *win, int is_action_w); void ac_file(WINDOW *win, int is_action_w);
app_t *app_init(void) { app_t *app_init(app_t *app) {
app->top_text = NULL;
/* get values from terminal size */ /* get values from terminal size */
int32_t y_max, x_max; int32_t y_max, x_max;
getmaxyx(stdscr, 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)); if (app == NULL) {
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_x = app->coordinates.cur_x = x_max;
_app->coordinates.max_y = _app->coordinates.cur_y = y_max; app->coordinates.max_y = app->coordinates.cur_y = y_max;
_app->win = newwin(_app->coordinates.cur_y, _app->coordinates.cur_x, 0, 0); app->win = newwin(app->coordinates.cur_y, app->coordinates.cur_x, 0, 0);
/* dialogue */ /* dialogue */
_app->modal.win = NULL; app->modal.win = NULL;
_app->modal.is_initiated = 0; app->modal.is_initiated = 0;
/* print top and bottom bars */
_app->main_ui.b_keys_len = 0;
app_draw_bars(_app);
/* here goes box borders */
app_draw_borders(_app);
/* NULL to main_ui */ /* NULL to main_ui */
MAIN_UI_RESET(_app); MAIN_UI_RESET(app);
keypad(_app->win, TRUE); keypad(app->win, TRUE);
app->callback_after_notification = NULL;
app->modal.is_initiated = false;
app->modal.needs_destroy = false;
/* refresh the windows */ /* refresh the windows */
wnoutrefresh(_app->win); wnoutrefresh(app->win);
doupdate(); doupdate();
return _app; return app;
} }
void app_init_nc(void) { void app_init_nc(void) {
@@ -106,15 +108,17 @@ void app_draw_bars(app_t *app) {
mvwprintw(app->win, app->coordinates.cur_y - 2, i, " "); mvwprintw(app->win, app->coordinates.cur_y - 2, i, " ");
} }
/* add content to the top bar */ if (app->top_text != NULL) {
if (app->params && app->params->is_connected) { mvwaddnwstr(app->win, 1, 2, app->top_text, wcslen(app->top_text));
} else if (app->params && app->params->is_connected) {
int32_t ip_address = app->params->addr; int32_t ip_address = app->params->addr;
u_char ip_addr[4]; u_char ip_addr[4];
mempcpy(ip_addr, &ip_address, 4); mempcpy(ip_addr, &ip_address, 4);
sprintf(top_text, "Connected to %u.%u.%u.%u, user: %s", ip_addr[0], 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); ip_addr[1], ip_addr[2], ip_addr[3], app->params->uname);
mvwprintw(app->win, 1, 2, "%s", top_text);
} }
mvwprintw(app->win, 1, 2, "%s", top_text);
wattroff(app->win, A_REVERSE); wattroff(app->win, A_REVERSE);
/* add content to the bottom bar */ /* add content to the bottom bar */
+4 -2
View File
@@ -15,7 +15,7 @@ typedef enum {
modal_color_pair = 3, modal_color_pair = 3,
} app_color; } app_color;
typedef struct { typedef struct app_t {
WINDOW *win; WINDOW *win;
w_dialogue_t modal; w_dialogue_t modal;
params_t *params; params_t *params;
@@ -23,11 +23,13 @@ typedef struct {
query_args_t *query_args; query_args_t *query_args;
file_args_t *file_args; file_args_t *file_args;
void (*active_callback)(w_cb_args_t *args); void (*active_callback)(w_cb_args_t *args);
void (*callback_after_notification)(struct app_t *);
void *active_widget; void *active_widget;
wchar_t *top_text;
main_window_t main_ui; main_window_t main_ui;
} app_t; } app_t;
app_t *app_init(void); app_t *app_init(app_t *app);
void app_init_nc(void); void app_init_nc(void);
void app_destroy(app_t *app, int32_t exit_code); void app_destroy(app_t *app, int32_t exit_code);
void app_draw_borders(app_t *app); void app_draw_borders(app_t *app);
+1 -1
View File
@@ -31,7 +31,7 @@ bool connect_to_server(app_t *app) {
return false; return false;
} }
app->query_args->state = S_WAIT_SERVER; app->query_args->state = S_WAIT_SERVER;
app->params->is_connected = TRUE; app->params->is_connected = true;
app->query_args->sd = app->params->sd; app->query_args->sd = app->params->sd;
app_draw_bars(app); app_draw_bars(app);
return true; return true;
+2 -2
View File
@@ -1,9 +1,9 @@
/* SPDX-License-Identifier: MIT */ /* SPDX-License-Identifier: MIT */
/* Copyright (c) 2026 Oleksandr Zhylin */ /* Copyright (c) 2026 Oleksandr Zhylin */
#include "main.h"
#include <modals.h> #include <modals.h>
#include <widget.h> #include <widget.h>
#include "main.h"
static void (*active_callback_backup)(w_cb_args_t *) = NULL; static void (*active_callback_backup)(w_cb_args_t *) = NULL;
@@ -28,7 +28,7 @@ void app_draw_modal(app_t *app) {
case S_PRINT_SERVER_MESSAGE: case S_PRINT_SERVER_MESSAGE:
m_server_message_init(app); m_server_message_init(app);
break; break;
case S_FILE_DOWNLOAD: case S_DOWNLOAD_FILE:
m_load_pr_init(app, false); m_load_pr_init(app, false);
break; break;
case S_UPLOAD_FILE: case S_UPLOAD_FILE:
+136 -107
View File
@@ -101,62 +101,100 @@ int32_t ui_file_select(file_args_t *f_args, query_args_t *q_args, int32_t idx) {
return -2; return -2;
} }
server_send_string(q_args, "file download [%s]\n", f_selected->name); server_send_string(q_args, "file download [%s]\n", f_selected->name);
q_args->state = S_FILE_DOWNLOAD; q_args->state = S_DOWNLOAD_FILE;
q_args->file = malloc(sizeof(p_file_t));
q_args->file->name = f_selected->name;
q_args->file->rest = f_selected->size;
q_args->file->size = f_selected->size;
q_args->file->package_rest = PACKAGE_SIZE;
q_args->file->signal = sig_continue;
q_args->file->it_count = 0;
q_args->file->fd = f_args->file_d;
q_args->file->it_interval =
(f_selected->size / INBUFSIZE / 100); /* every 1% */
if (q_args->file->it_interval == 0)
q_args->file->it_interval = 1;
free(file_path); free(file_path);
return OK; return OK;
} }
#define file_download_receive_after(qlen, q_args, f_args, d, pb) \
{ \
if (qlen) { \
q_args->file->it_count++; \
q_args->file->package_rest -= qlen; \
if (!(q_args->file->it_count % q_args->file->it_interval)) { \
pb->percentage = progress; \
d->needs_update = true; \
} else { \
q_args->buf_used = 0; \
} \
if (q_args->file->rest < qlen) { \
q_args->file->rest = 0; \
} else { \
q_args->file->rest -= qlen; \
} \
if (q_args->file->rest == 0) { \
q_args->file->it_count = 0; \
q_args->file->it_interval = 0; \
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); \
strcpy(q_args->notification, answer); \
free(f_selected->name); \
server_send_string(q_args, "file list %d %d\n%n", fui->max_lines, \
fui->current_page, &a_len); \
q_args->state = S_FILE_LIST; \
return; \
} \
} \
}
/* TODO: revise */
void file_download(file_args_t *f_args, query_args_t *q_args) { void file_download(file_args_t *f_args, query_args_t *q_args) {
fl_item_t *f_selected = &(f_args->f_selected); fl_item_t *f_selected = &(f_args->f_selected);
static int32_t it_count = 0;
static size_t it_interval = 0;
if (it_interval == 0) {
it_interval = (f_selected->size / INBUFSIZE / 100) * 5; /* every 1% */
if (it_interval == 0)
it_interval = 1;
}
char answer[256]; char answer[256];
static size_t size_rest = 0; int32_t progress =
int32_t progress = (f_selected->size - size_rest) * 100 / f_selected->size; (f_selected->size - q_args->file->rest) * 100 / f_selected->size;
w_pgb_ui_t *pb = (w_pgb_ui_t *)q_args->progress_bar; w_pgb_ui_t *pb = (w_pgb_ui_t *)q_args->progress_bar;
w_dialogue_t *d = (w_dialogue_t *)q_args->active_dialogue; w_dialogue_t *d = (w_dialogue_t *)q_args->active_dialogue;
w_ui_file_list_t *fui = (w_ui_file_list_t *)q_args->main_ui->ui; w_ui_file_list_t *fui = (w_ui_file_list_t *)q_args->main_ui->ui;
int32_t a_len = 0; int32_t a_len = 0;
int32_t qlen;
if (size_rest == 0) int write_len = q_args->buf_used;
size_rest = f_selected->size;
int32_t qlen = write(f_args->file_d, q_args->buf, q_args->buf_used); if (q_args->file->package_rest <= q_args->buf_used) {
if (qlen) { write_len = q_args->file->package_rest;
it_count++; qlen = write(f_args->file_d, q_args->buf, write_len);
if (!(it_count % it_interval)) { file_download_receive_after(qlen, q_args, f_args, d, pb)
pb->procent = progress; if (q_args->file->signal == sig_continue) {
d->needs_update = true; server_send_string(q_args, "continue %d\n", PACKAGE_SIZE * 10);
} else { if (q_args->file->package_rest == 0) {
q_args->buf_used = 0; q_args->file->package_rest = PACKAGE_SIZE * 10;
} return;
if (size_rest < qlen) { }
size_rest = 0; int buf_offset = q_args->file->package_rest;
} else { write_len = q_args->buf_used - q_args->file->package_rest;
size_rest -= qlen; qlen = write(f_args->file_d, q_args->buf + buf_offset, write_len);
} q_args->file->package_rest = PACKAGE_SIZE * 10;
if (size_rest == 0) { file_download_receive_after(qlen, q_args, f_args, d, pb);
it_count = 0; } else if (q_args->file->signal == sig_cancel) {
it_interval = 0; server_send_string(q_args, "cancel\n");
f_selected->size = 0; FILE_CLEAN(q_args->file);
close(f_args->file_d);
d->needs_destroy = true; d->needs_destroy = true;
sprintf(answer, "File %s is downloaded from the server!",
f_selected->name);
q_args->notification = malloc(strlen(answer) + 1);
strcpy(q_args->notification, answer);
free(f_selected->name);
server_send_string(q_args, "file list %d %d\n%n", fui->max_lines,
fui->current_page, &a_len);
q_args->state = S_FILE_LIST;
} }
return;
} }
qlen = write(f_args->file_d, q_args->buf, write_len);
file_download_receive_after(qlen, q_args, f_args, d, pb)
} }
int32_t file_upload_open(char *dpath, char *fname, query_args_t *q_args) { int32_t file_upload_open(char *dpath, char *fname, query_args_t *q_args) {
@@ -191,91 +229,82 @@ int32_t file_upload_start(query_args_t *q_args) {
return 0; return 0;
} }
#define file_upload_send(send_len, package_rest, buf_indent) \
{ \
if (q_args->buf_used > 0) { \
int wlen = write(q_args->sd, q_args->buf + buf_indent, send_len); \
if (q_args->buf_used != wlen) { \
/* TODO: ошибка */ \
/* reset function before exit */ \
it_count = 0; \
it_interval = 0; \
package_rest = 0; \
ret = -1; \
} else { \
it_count++; \
if (!(it_count % it_interval)) { \
pb->percentage = progress; \
d->needs_update = true; \
} \
q_args->file->rest -= wlen; \
package_rest -= wlen; \
q_args->buf_used -= send_len; \
ret = 0; \
} \
} else { \
/* reset function before exit */ \
it_count = 0; \
it_interval = 0; \
package_rest = 0; \
ret = 1; \
} \
}
int32_t file_upload(query_args_t *q_args) { int32_t file_upload(query_args_t *q_args) {
static int32_t it_count = 0; static int32_t it_count = 0;
static size_t it_interval = 0; static size_t it_interval = 0;
static int package_rest = 0; static int package_rest = 0;
int ret = 0;
int buf_indent = 0;
int send_len = q_args->buf_used; int send_len = q_args->buf_used;
int32_t progress =
(q_args->file->size - q_args->file->rest) * 100 / q_args->file->size;
w_pgb_ui_t *pb = (w_pgb_ui_t *)q_args->progress_bar;
w_dialogue_t *d = (w_dialogue_t *)q_args->active_dialogue;
if (it_interval == 0) { if (it_interval == 0) {
it_interval = (q_args->file->size / INBUFSIZE / 100) * 5; /* every 1% */ it_interval = (q_args->file->size / INBUFSIZE / 100) * 5; /* every 1% */
if (it_interval == 0) if (it_interval == 0)
it_interval = 1; it_interval = 1;
} }
int32_t progress =
(q_args->file->size - q_args->file->rest) * 100 / q_args->file->size;
w_pgb_ui_t *pb = (w_pgb_ui_t *)q_args->progress_bar;
w_dialogue_t *d = (w_dialogue_t *)q_args->active_dialogue;
if (package_rest == 0) { if (package_rest < send_len) {
if (package_rest > 0) {
send_len -= package_rest;
buf_indent = package_rest;
file_upload_send(package_rest, package_rest, 0);
if (ret) {
/* TODO: error case */
}
}
package_rest = PACKAGE_SIZE;
s_file_pd_t fpd = {.signal = q_args->file->signal,
.package_size = package_rest};
write(q_args->sd, &fpd, sizeof(s_file_pd_t));
if (q_args->file->signal == sig_cancel) { if (q_args->file->signal == sig_cancel) {
/* reset function before exit */ /* reset function before exit */
it_count = 0; it_count = 0;
it_interval = 0; it_interval = 0;
return -2; return -2;
} }
package_rest = PACKAGE_SIZE;
s_file_pd_t fpd = {.signal = q_args->file->signal,
.package_size = package_rest};
write(q_args->sd, &fpd, sizeof(s_file_pd_t));
} else if (package_rest < send_len) {
send_len = package_rest;
} }
if (q_args->buf_used > 0) { file_upload_send(send_len, package_rest, buf_indent);
int wlen = write(q_args->sd, q_args->buf, send_len);
if (q_args->buf_used != wlen) { q_args->buf[0] = 0;
/* TODO: ошибка */ return ret;
/* reset function before exit */
it_count = 0;
it_interval = 0;
package_rest = 0;
return -1;
} else {
it_count++;
if (!(it_count % it_interval)) {
pb->procent = progress;
d->needs_update = true;
}
q_args->file->rest -= wlen;
package_rest -= wlen;
if (package_rest == 0) {
if (q_args->file->signal == sig_cancel) {
/* reset function before exit */
it_count = 0;
it_interval = 0;
return -2;
}
package_rest = PACKAGE_SIZE;
s_file_pd_t fpd = {.signal = q_args->file->signal,
.package_size = package_rest};
write(q_args->sd, &fpd, sizeof(s_file_pd_t));
/* send the rest */
if (q_args->buf_used > send_len) {
wlen = write(q_args->sd, q_args->buf + send_len,
q_args->buf_used - send_len);
if (q_args->buf_used != wlen) {
/* TODO: ошибка */
/* reset function before exit */
it_count = 0;
it_interval = 0;
package_rest = 0;
return -1;
}
q_args->file->rest -= wlen;
package_rest -= wlen;
}
}
q_args->buf[0] = 0;
q_args->buf_used = 0;
}
} else {
/* reset function before exit */
it_count = 0;
it_interval = 0;
package_rest = 0;
return 1;
}
return 0;
} }
void clear_file_in_query(query_args_t *q_args) { void clear_file_in_query(query_args_t *q_args) {
@@ -303,7 +332,7 @@ static void fl_add(fl_item_t **cur, fl_item_t **start, char *line) {
char *descr_begin = NULL; char *descr_begin = NULL;
int h_len; int h_len;
char *name_begin = strchr(line, '[') + 1; char *name_begin = strchr(line, '[') + 1;
char *name_end = strrchr(line, ']'); char *name_end = strchr(line, ']');
int name_len = name_end - name_begin; int name_len = name_end - name_begin;
fl_item_t *fitem = malloc(sizeof(fl_item_t)); fl_item_t *fitem = malloc(sizeof(fl_item_t));
sscanf(name_end, "] %zu %s%n", &(fitem->size), fowner, &h_len); sscanf(name_end, "] %zu %s%n", &(fitem->size), fowner, &h_len);
+17 -21
View File
@@ -6,7 +6,6 @@
#include <ncursesw/ncurses.h> #include <ncursesw/ncurses.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <stdint.h> #include <stdint.h>
#include <stdlib.h>
#include <sys/select.h> #include <sys/select.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/un.h> #include <sys/un.h>
@@ -15,10 +14,11 @@
#include <widget.h> #include <widget.h>
#include <windows.h> #include <windows.h>
#include "main.h"
#include "../app.h" #include "../app.h"
#include "alert.h"
#include "connection.h" #include "connection.h"
#include "file_list.h" // #include "file_list.h"
#include "main.h"
#include "query.h" #include "query.h"
int32_t m_id = 0; int32_t m_id = 0;
@@ -26,42 +26,38 @@ int32_t m_id = 0;
int32_t process_user_input(app_t *app, w_cb_args_t *d_args); int32_t process_user_input(app_t *app, w_cb_args_t *d_args);
int main(int argc, char **argv) { int main(int argc, char **argv) {
app_t *app; app_t app;
params_t params; params_t params;
query_args_t *q_args = malloc(sizeof(query_args_t)); query_args_t q_args;
app = calloc(1, sizeof(app_t));
app_init_nc(); app_init_nc();
app = app_init(); app.params = &params;
app_init(&app);
init_params(&params); init_params(&params);
app->params = &params;
analyze_args(argc, argv, &params); analyze_args(argc, argv, &params);
/* init alert */ /* init alert */
w_alert_init(app); w_alert_init(&app);
init_query_args(q_args, app->params); init_query_args(&q_args, app.params);
q_args->buf = malloc(INBUFSIZE); app.query_args = &q_args;
app->query_args = q_args;
main_window_set(app, mw_fl_server);
main_window_set(&app, mw_fl_server);
/* init client to connect to the server */ /* init client to connect to the server */
app->params->sd = init_client(); app.params->sd = init_client();
if (params.addr == 0) { if (params.addr == 0) {
app->query_args->state = S_ASK_SEVER_IP; app.query_args->state = S_ASK_SEVER_IP;
} else { } else {
connect_to_server(app); connect_to_server(&app);
} }
app->query_args->main_ui = &(app->main_ui); app.query_args->main_ui = &(app.main_ui);
query_loop(app); query_loop(&app);
clear_params(&params); clear_params(&params);
app_destroy(app, 0); app_destroy(&app, 0);
return OK; return OK;
} }
+7 -3
View File
@@ -42,7 +42,7 @@ enum state {
S_FILE_LIST, S_FILE_LIST,
S_WAIT_PASS, S_WAIT_PASS,
/* S_FILE_SELECT, */ /* S_FILE_SELECT, */
S_FILE_DOWNLOAD, S_DOWNLOAD_FILE,
S_UPLOAD_PARAMS, S_UPLOAD_PARAMS,
S_UPLOAD_FILE, S_UPLOAD_FILE,
/* S_UPLOAD_FILE_SELECT, */ /* S_UPLOAD_FILE_SELECT, */
@@ -52,6 +52,7 @@ enum state {
S_ERR, S_ERR,
S_NEXT_ACTION, S_NEXT_ACTION,
S_WAIT_REGISTER_CONFIRMATION, S_WAIT_REGISTER_CONFIRMATION,
S_WAIT_USER_DESCRIPTION,
/* Legacy */ /* Legacy */
WAIT_SERVER_INIT, WAIT_SERVER_INIT,
WAIT_SERVER, WAIT_SERVER,
@@ -73,6 +74,9 @@ typedef struct p_file {
char *description; char *description;
size_t size; size_t size;
size_t rest; size_t rest;
size_t package_rest;
int32_t it_count;
size_t it_interval;
enum package_signal signal; enum package_signal signal;
int fd; int fd;
} p_file_t; } p_file_t;
@@ -105,7 +109,7 @@ typedef struct main_window {
void *ui; /* structure which describes window UI */ void *ui; /* structure which describes window UI */
int32_t (*cb_b_press)(void *app, void *args); /* callback on button press */ int32_t (*cb_b_press)(void *app, void *args); /* callback on button press */
void (*cb_ui_refresh)(void *app); /* refresh callback */ void (*cb_ui_refresh)(void *app); /* refresh callback */
void (*reset)(void *app); /* function to reset the state of the window */ void (*reset)(void *app); /* function to reset the state of the window */
draw_f_t *draw; draw_f_t *draw;
struct action_key *b_keys; struct action_key *b_keys;
int32_t b_keys_len; int32_t b_keys_len;
@@ -116,7 +120,7 @@ typedef struct query_args {
int32_t buf_used; int32_t buf_used;
p_file_t *file; p_file_t *file;
enum state state; enum state state;
char *buf; char buf[INBUFSIZE];
bool from_server; bool from_server;
params_t *params; params_t *params;
struct { struct {
+116
View File
@@ -0,0 +1,116 @@
/* SPDX-License-Identifier: MIT */
/* Copyright (c) 2026 Oleksandr Zhylin */
#include <arpa/inet.h>
#include <modals.h>
#include <ncursesw/ncurses.h>
#include <netinet/in.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <termios.h>
#include <unistd.h>
#include <widget.h>
#include <windows.h>
#include "../app.h"
#include "alert.h"
#include "connection.h"
#include "main.h"
#include "query.h"
int32_t m_id = 0;
void mini_loop(app_t *app) {
w_cb_args_t d_args = {
.app = app, .element = NULL, .data = NULL, .resp_data.code = cbrc_none};
fd_set readfds;
while (true) {
app_draw_modal(app);
main_window_draw(app);
app_refresh(app);
FD_ZERO(&readfds);
FD_SET(STDIN_FILENO, &readfds);
int maxfd = STDIN_FILENO;
int sr = select(maxfd + 1, &readfds, NULL, NULL, NULL);
if (sr == -1) {
/* perror("select"); */
exit(3);
}
if (app->main_ui.cb_b_press != NULL && app->modal.is_initiated == false) {
if (ERR == app->main_ui.cb_b_press(app, &d_args)) {
w_alert("error while processing user input");
}
}
}
}
int main(void) {
app_t app;
file_args_t fargs;
query_args_t q_args;
params_t params;
p_file_t file;
init_query_args(&q_args, app.params);
app.params = &params;
app.query_args = &q_args;
app.file_args = &fargs;
app_init_nc();
init_params(&params);
w_alert_init(&app);
app_init(&app);
q_args.file = &file;
app.query_args->main_ui = &(app.main_ui);
file.name = "Test file #1";
params.is_connected = false;
app.modal.is_initiated = false;
main_window_set(&app, mw_f_desc);
mini_loop(&app);
exit(0);
}
int32_t process_user_input(app_t *app, w_cb_args_t *d_args) {
int32_t c;
w_ui_file_list_t *fui = (w_ui_file_list_t *)app->query_args->main_ui->ui;
c = wgetch(app->win);
switch (c) {
case KEY_F(9):
app_destroy(app, 0);
return OK;
case 'U':
case 'u':
if (!app->modal.is_initiated && !fui->active_search &&
app->main_ui.type != mw_fl_local) {
/* app->query_args->state = S_UPLOAD_FILE_SELECT; */
main_window_set(app, mw_fl_local);
main_window_draw(app);
d_args->element = app->main_ui.ui;
break;
}
case 's':
case 'S':
if (!app->modal.is_initiated && !fui->active_search) {
fui->active_search = true;
w_fl_draw(fui);
return OK;
}
default:
d_args->data = (void *)&c;
app->active_callback(d_args);
break;
}
return OK;
}
+25 -1
View File
@@ -1,17 +1,41 @@
#include "../server.h"
#include "../windows/file_list.h"
#include "alert.h"
#include "dialogue.h"
#include "group.h" #include "group.h"
#include "utils.h"
#include <ncursesw/ncurses.h> #include <ncursesw/ncurses.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include <widget.h> #include <widget.h>
void dwn_pr_modal_cb(w_cb_args_t *args) { void dwn_pr_modal_cb(w_cb_args_t *args) {
/* only cancel action */ /* only cancel action */
int32_t key = *((int32_t *)args->data); int32_t key = *((int32_t *)args->data);
if (key != '\n') { if (key != '\n') {
return; return;
} }
app_t *app = args->app; app_t *app = args->app;
app->query_args->file->signal = sig_cancel; w_dialogue_t *d = &(app->modal);
/* TODO: forward actual file name */
if (app->query_args->state == S_UPLOAD_FILE) {
bool response = w_bool_ask("Cancel file upload", "Yes", "No", false,
"You're going to cancel upload\n Are you sure?");
if (response) {
app->query_args->file->signal = sig_cancel;
}
} else if (app->query_args->state == S_DOWNLOAD_FILE) {
w_ui_file_list_t *fui = (w_ui_file_list_t *)app->query_args->main_ui->ui;
bool response =
w_bool_ask("Cancel file download", "Yes", "No", false,
"You're going to cancel download\n Are you sure?");
if (response) {
app->query_args->file->signal = sig_cancel;
}
}
} }
w_dialogue_t *m_load_pr_init(app_t *app, bool is_upload) { w_dialogue_t *m_load_pr_init(app_t *app, bool is_upload) {
-4
View File
@@ -40,10 +40,6 @@ void upload_props_dialogue_modal_cb(w_cb_args_t *args) {
app->query_args->file->size, privileges); app->query_args->file->size, privileges);
/* app->query_args->file->description = /* app->query_args->file->description =
malloc(desc_input->value_len + 9); reserve space for \n:END:\n */ malloc(desc_input->value_len + 9); reserve space for \n:END:\n */
app->query_args->file->description =
malloc(sizeof("[Empty description]\n:END:\n"));
strcpy(app->query_args->file->description,
"[Empty description]\n:END:\n");
d->needs_destroy = true; d->needs_destroy = true;
app->query_args->state = S_UPLOAD_REQUESTED; app->query_args->state = S_UPLOAD_REQUESTED;
break; break;
+29 -20
View File
@@ -30,6 +30,7 @@
#include "dialogue.h" #include "dialogue.h"
#include "file_processor.h" #include "file_processor.h"
#include "main.h" #include "main.h"
#include "main_window.h"
#include "server.h" #include "server.h"
#include "types.h" #include "types.h"
@@ -45,13 +46,6 @@ int32_t process_user_input(app_t *app, w_cb_args_t *d_args);
app_destroy(app, exit_code); \ app_destroy(app, exit_code); \
} }
#define FILE_CLEAN(app) \
if (app->query_args->file != NULL) { \
FREE_MLC(app->query_args->file->name); \
FREE_MLC(app->query_args->file->path); \
FREE_MLC(app->query_args->file); \
}
void query_loop(app_t *app) { void query_loop(app_t *app) {
query_args_t *query_args = app->query_args; query_args_t *query_args = app->query_args;
fd_set readfds; fd_set readfds;
@@ -84,12 +78,18 @@ void query_loop(app_t *app) {
} }
} }
if (query_args->state == S_NEXT_ACTION) { if (query_args->state == S_NEXT_ACTION) {
query_args->from_server = TRUE; if (app->callback_after_notification != NULL) {
if (ERR == process_query(app)) { app->callback_after_notification(app);
EXIT_APP("*** error while processing query ***", app, 1) app->callback_after_notification = NULL;
continue;
} else {
query_args->from_server = TRUE;
if (ERR == process_query(app)) {
EXIT_APP("*** error while processing query ***", app, 1)
}
query_args->state = S_WAIT_SERVER;
continue;
} }
query_args->state = S_WAIT_SERVER;
continue;
} else { } else {
sr = select(maxfd + 1, &readfds, NULL, NULL, NULL); sr = select(maxfd + 1, &readfds, NULL, NULL, NULL);
if (sr == -1) { if (sr == -1) {
@@ -150,9 +150,19 @@ int upload_confirm_cb(app_t *app, char *query, int q_len) {
w_notification("File upload", dc_normal, w_notification("File upload", dc_normal,
"File %s is uploaded to the server!", "File %s is uploaded to the server!",
app->query_args->file->name); app->query_args->file->name);
clear_file_in_query(app->query_args); int desc_answer =
app->query_args->state = S_FILE_LIST; w_bool_ask("Write file description", "Yes", "No", true,
app->modal.needs_destroy = true; "Would you like to write description for the file %s?",
app->query_args->file->name);
if (desc_answer) {
main_window_set(app, mw_f_desc);
app->query_args->state = S_WAIT_USER_DESCRIPTION;
} else {
server_send_string(app->query_args, "[Empty description]\n:END:\n");
app->query_args->state = WAIT_CLIENT;
main_window_set(app, mw_fl_server);
}
/* clear_file_in_query(app->query_args); */
return 0; return 0;
} }
return 1; /* TODO: Error case */ return 1; /* TODO: Error case */
@@ -183,14 +193,14 @@ int process_query(app_t *app) {
case S_FILE_LIST: case S_FILE_LIST:
file_list(file_args, query_args); file_list(file_args, query_args);
break; break;
case S_FILE_DOWNLOAD: case S_DOWNLOAD_FILE:
file_download(file_args, query_args); file_download(file_args, query_args);
break; break;
case S_UPLOAD_REQUESTED: case S_UPLOAD_REQUESTED:
if (!file_upload_start(query_args)) { if (!file_upload_start(query_args)) {
query_args->state = S_UPLOAD_FILE; query_args->state = S_UPLOAD_FILE;
} else { } else {
FILE_CLEAN(app); FILE_CLEAN(app->query_args->file);
query_args->state = WAIT_CLIENT; query_args->state = WAIT_CLIENT;
} }
break; break;
@@ -205,7 +215,7 @@ int process_query(app_t *app) {
/* cancel case */ /* cancel case */
w_notification("Alert", dc_alert, "File %s didn't upload!", w_notification("Alert", dc_alert, "File %s didn't upload!",
query_args->file->name); query_args->file->name);
FILE_CLEAN(app); FILE_CLEAN(app->query_args->file);
w_dialogue_t *d = &(app->modal); w_dialogue_t *d = &(app->modal);
d->needs_destroy = true; d->needs_destroy = true;
query_args->state = WAIT_CLIENT; query_args->state = WAIT_CLIENT;
@@ -284,7 +294,7 @@ static int process_command(app_t *app, wait_server_cb *callback) {
while ((qlen = query_extract_from_buf(q_args->buf, &(q_args->buf_used), while ((qlen = query_extract_from_buf(q_args->buf, &(q_args->buf_used),
&query))) { &query))) {
if (callback == NULL) { if (callback == NULL) {
process_server_command(query, qlen, app); res = process_server_command(query, qlen, app);
} else { } else {
res = callback(app, query, qlen); res = callback(app, query, qlen);
} }
@@ -298,7 +308,6 @@ static int process_command(app_t *app, wait_server_cb *callback) {
} }
void init_query_args(query_args_t *q_args, params_t *params) { void init_query_args(query_args_t *q_args, params_t *params) {
q_args->buf = NULL;
q_args->sd = -1; q_args->sd = -1;
q_args->state = S_N_D; q_args->state = S_N_D;
q_args->buf_used = 0; q_args->buf_used = 0;
+3 -3
View File
@@ -146,10 +146,10 @@ int process_server_command(char *line, int l_len, app_t *app) {
return 0; return 0;
} }
q_args->state = S_PREP_SERVER_MESSAGE; if (q_args->state == S_NEXT_ACTION) return 1;
server_print_message_cb(app, line, l_len);
return -1; q_args->state = S_PREP_SERVER_MESSAGE;
return server_print_message_cb(app, line, l_len);
} }
void ask_uname_and_password(params_t *params) { void ask_uname_and_password(params_t *params) {
+49 -3
View File
@@ -13,17 +13,51 @@ void u_d_arr_ptr_init(d_array_ptr_t *arr, int32_t init_capacity) {
memset(arr->arr, 0, sizeof(void *) * init_capacity); memset(arr->arr, 0, sizeof(void *) * init_capacity);
} }
void u_d_array_append(d_array_ptr_t *arr, void *el_ptr, size_t el_len) {
u_d_arr_ptr_add(arr, el_ptr, arr->length);
}
void u_d_arr_ptr_add(d_array_ptr_t *arr, void *ptr, int32_t idx) { void u_d_arr_ptr_add(d_array_ptr_t *arr, void *ptr, int32_t idx) {
if (idx > arr->capacity - 1) { if (idx > arr->capacity - 1) {
int32_t new_capacity = idx * 2; int32_t new_capacity = idx * 2;
arr->arr = realloc(arr->arr, new_capacity); arr->arr = realloc(arr->arr, new_capacity);
memset(arr->arr+arr->capacity, 0, arr->capacity - new_capacity); memset(arr->arr + arr->capacity, 0, arr->capacity - new_capacity);
arr->capacity = new_capacity; arr->capacity = new_capacity;
} }
if (arr->fist_el == NULL) arr->fist_el = ptr; if (arr->fist_el == NULL)
arr->fist_el = ptr;
if (arr->arr[idx] != NULL) {
memmove(arr->arr + idx + 1, arr->arr + idx, sizeof (void *) * (arr->length - idx));
}
arr->last_el = ptr; arr->last_el = ptr;
arr->arr[idx] = ptr; arr->arr[idx] = ptr;
arr->length = idx+1; arr->length = idx + 1;
}
void u_d_arr_ptr_remove_cb(d_array_ptr_t *arr, void *ptr, int32_t idx,
u_d_arr_free_callback *callback) {
void *_ptr = NULL;
int _idx = idx;
if (idx > 0) {
_ptr = arr->arr[idx];
} else if (ptr != NULL) {
for (int i = 0; i < arr->length; i++) {
if (arr->arr[i] == ptr) {
_ptr = arr->arr[i];
_idx = i;
}
}
}
callback(_ptr);
arr->arr[_idx] = NULL;
arr->length--;
memmove(arr->arr + _idx, arr->arr + _idx + 1,
(arr->length - _idx) * sizeof(void *));
arr->arr[arr->length] = NULL;
return;
} }
void u_d_arr_ptr_free(d_array_ptr_t *arr) { void u_d_arr_ptr_free(d_array_ptr_t *arr) {
@@ -32,3 +66,15 @@ void u_d_arr_ptr_free(d_array_ptr_t *arr) {
free(arr->arr); free(arr->arr);
arr->arr = NULL; arr->arr = NULL;
} }
void u_d_arr_free_cb(d_array_ptr_t *d_arr, u_d_arr_free_callback *callback) {
for (int i = 0; i < d_arr->length; i++) {
if (d_arr->arr[i] != NULL) {
callback(d_arr->arr[i]);
}
}
d_arr->capacity = 0;
d_arr->length = 0;
free(d_arr->arr);
d_arr->arr = NULL;
}
+7
View File
@@ -1,7 +1,10 @@
#ifndef D_ARRAY_H #ifndef D_ARRAY_H
#define D_ARRAY_H #define D_ARRAY_H
/* Dynamic array of numbers */ /* Dynamic array of numbers */
#include <stddef.h>
#include <stdint.h> #include <stdint.h>
typedef struct d_array_ptr_t { typedef struct d_array_ptr_t {
int32_t capacity; int32_t capacity;
int32_t length; int32_t length;
@@ -10,8 +13,12 @@ typedef struct d_array_ptr_t {
void *last_el; void *last_el;
} d_array_ptr_t; } d_array_ptr_t;
typedef void u_d_arr_free_callback(void *arg);
void u_d_arr_ptr_init(d_array_ptr_t *arr, int32_t init_capacity); void u_d_arr_ptr_init(d_array_ptr_t *arr, int32_t init_capacity);
void u_d_arr_ptr_add(d_array_ptr_t *arr, void *ptr, int32_t idx); void u_d_arr_ptr_add(d_array_ptr_t *arr, void *ptr, int32_t idx);
void u_d_arr_ptr_free(d_array_ptr_t *arr); void u_d_arr_ptr_free(d_array_ptr_t *arr);
void u_d_array_append(d_array_ptr_t *arr, void *el_ptr, size_t el_len);
void u_d_arr_free_cb(d_array_ptr_t *arr, u_d_arr_free_callback *callback);
#endif #endif
+63
View File
@@ -0,0 +1,63 @@
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
typedef struct dlist_el_t {
struct dlist_el_t *next;
struct dlist_el_t *previous;
void *el_ptr;
} dlist_el_t;
typedef struct dlist_t {
dlist_el_t *start;
dlist_el_t *current;
size_t len;
} dlist_t;
void dlist_add(dlist_t *dlist, void *el_ptr, int el_siz, bool prepend);
dlist_t *dlist_init(void *el_ptr, int el_siz) {
dlist_t *dlist = malloc(sizeof(dlist_t));
dlist->len = 0;
dlist->current = NULL;
dlist->start = NULL;
if (el_ptr != NULL) {
dlist_add(dlist, el_ptr, el_siz, false);
}
return dlist;
}
void dlist_add(dlist_t *dlist, void *el_ptr, int el_siz, bool prepend) {
dlist_el_t *d_el = malloc(sizeof(dlist_el_t));
d_el->el_ptr = malloc(el_siz);
memcpy(d_el->el_ptr, el_ptr, el_siz);
if (dlist->start == NULL) {
d_el->previous = NULL;
d_el->next = NULL;
dlist->start = d_el;
dlist->current = d_el;
} else if (prepend) {
d_el->previous = NULL;
d_el->next = dlist->start;
dlist->start = d_el;
} else {
d_el->next = NULL;
dlist->current->next = d_el;
dlist->current = d_el;
}
dlist->len++;
}
void *dlist_get_current(dlist_t *dlist) {
return dlist->current->el_ptr;
}
void *dlist_it_prev(dlist_t *dlist) {
dlist->current = dlist->current->previous;
return dlist->current;
}
+22 -5
View File
@@ -28,19 +28,19 @@ const char *u_utf8_wind_n(const char *s, int n) {
int32_t u_utf8_convert_to_wide(const char *utf8_str, int32_t u_utf8_convert_to_wide(const char *utf8_str,
wchar_t **restrict wide_str, wchar_t **restrict wide_str,
size_t *restrict len) { size_t *restrict o_len) {
// Step 1: Convert UTF-8 (MBCS) to wchar_t (wide) // Step 1: Convert UTF-8 (MBCS) to wchar_t (wide)
size_t wide_len = size_t wide_len =
u_utf8_code_points_count(utf8_str); // Get required length (-1 on error) u_utf8_code_points_count(utf8_str); // Get required length (-1 on error)
if (wide_len == (size_t)-1) { if (wide_len == (size_t)-1) {
*len = 0; *o_len = 0;
perror("mbstowcs failed"); perror("mbstowcs failed");
return 1; return 1;
} }
*wide_str = *wide_str =
malloc((wide_len + 1) * sizeof(wchar_t)); // +1 for null terminator malloc((wide_len + 1) * sizeof(wchar_t)); // +1 for null terminator
mbstowcs(*wide_str, utf8_str, wide_len + 1); // Perform conversion mbstowcs(*wide_str, utf8_str, wide_len + 1); // Perform conversion
*len = wide_len; *o_len = wide_len;
return 0; return 0;
} }
@@ -55,9 +55,9 @@ int32_t u_utf8_curs_printw(WINDOW *win, int32_t *y, int32_t *x,
if (multiline) { if (multiline) {
_y++; _y++;
for (int i = 1; max_len*i < nsize; i++) { for (int i = 1; max_len * i < nsize; i++) {
_x = *x; _x = *x;
mvwaddnwstr(win, _y++, _x, wline + max_len*i, max_len); mvwaddnwstr(win, _y++, _x, wline + max_len * i, max_len);
} }
} }
@@ -67,3 +67,20 @@ int32_t u_utf8_curs_printw(WINDOW *win, int32_t *y, int32_t *x,
free(wline); free(wline);
return nsize; return nsize;
} }
wchar_t u_utf8_get_full_letter(int key, WINDOW *win) {
char long_key[3];
int short_val_len = 1;
wchar_t wkey;
long_key[0] = key;
if ((key & 0xC0) == 0xC0) {
long_key[1] = wgetch(win);
long_key[2] = '\0';
short_val_len++;
} else {
long_key[1] = '\0';
}
mbstowcs(&wkey, (const char *)&long_key, 2);
return wkey;
}
+2 -1
View File
@@ -8,7 +8,8 @@
const char *u_utf8_wind_n(const char *s, int n); const char *u_utf8_wind_n(const char *s, int n);
size_t u_utf8_code_points_count(const char *s); size_t u_utf8_code_points_count(const char *s);
int32_t u_utf8_convert_to_wide(const char *utf8_str, wchar_t **restrict wide_str, int32_t u_utf8_convert_to_wide(const char *utf8_str, wchar_t **restrict wide_str,
size_t *restrict len); size_t *restrict o_len);
int32_t u_utf8_curs_printw(WINDOW *win, int32_t *y, int32_t *x, const char* utf8_str, int max_len, bool multiline); int32_t u_utf8_curs_printw(WINDOW *win, int32_t *y, int32_t *x, const char* utf8_str, int max_len, bool multiline);
wchar_t u_utf8_get_full_letter(int key, WINDOW *win);
#endif #endif
+8
View File
@@ -9,4 +9,12 @@
free(arg); \ free(arg); \
arg = NULL; \ arg = NULL; \
} }
#define FILE_CLEAN(file) \
if (file != NULL) { \
FREE_MLC(file->name); \
FREE_MLC(file->path); \
FREE_MLC(file); \
file = NULL; \
}
#endif #endif
+95 -3
View File
@@ -1,4 +1,6 @@
#include "alert.h" #include "alert.h"
#include "dialogue.h"
#include "widget_core.h"
#include <ncursesw/ncurses.h> #include <ncursesw/ncurses.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdint.h> #include <stdint.h>
@@ -15,10 +17,12 @@ void alert_cb(w_cb_args_t *args) {
void w_alert_init(app_t *_app) { app = _app; } void w_alert_init(app_t *_app) { app = _app; }
void w_alert(const char *message) { w_notification("Alert", dc_alert, message); } void w_alert(const char *message) {
w_notification("Alert", dc_alert, message);
}
void w_notification(const char *title, enum w_dialogue_color_scheme color, void w_notification(const char *title, enum w_dialogue_color_scheme color,
const char *f_message, ...) { const char *f_message, ...) {
if (app == NULL) if (app == NULL)
return; return;
@@ -36,7 +40,7 @@ void w_notification(const char *title, enum w_dialogue_color_scheme color,
d->w.callback = alert_cb; d->w.callback = alert_cb;
d->g_content = NULL; d->g_content = NULL;
d->g_action = w_group_init(&(d->win), &(d->w), actions, &(d->id_map), d->g_action = w_group_init(&(d->win), &(d->w), actions, &(d->id_map),
horizontal, g_action); horizontal, g_action);
w_dialogue_init_active_id(d); w_dialogue_init_active_id(d);
d->color_scheme = color; d->color_scheme = color;
@@ -59,3 +63,91 @@ void w_notification(const char *title, enum w_dialogue_color_scheme color,
app->modal.needs_update = true; app->modal.needs_update = true;
} }
} }
int w_bool_callback(app_t *app, w_dialogue_t *dialogue, int32_t key) {
w_cb_args_t args;
args.app = app;
args.active_el = dialogue;
args.element = dialogue;
int _key = key;
args.data = (void *)&_key;
w_dialogue_callback_default(&args);
if (args.resp_data.code == cbrp_val) {
switch (args.resp_data.val.val.num) {
case 1:
return 1;
case 2:
return 2;
default:
return -2;
}
}
return 0;
}
int w_bool_ask(const char *title, const char *text_y_btn,
const char *text_n_btn, bool yes_default, const char *f_message,
...) {
va_list args;
va_start(args, f_message);
if (app == NULL)
return -1;
void *callback = app->active_callback;
w_group_el_init_t actions[] = {{.type = w_button, .val.num = 1}, /* YES */
{.type = w_button, .val.num = 2}, /* NO */
{.type = w_end}};
strcpy(actions[0].label, text_y_btn);
strcpy(actions[1].label, text_n_btn);
w_dialogue_t *d = malloc(sizeof(w_dialogue_t));
w_dialogue_vinit(d, title, &(app->coordinates), f_message, &args);
d->w.callback = alert_cb;
d->g_content = NULL;
d->g_action = w_group_init(&(d->win), &(d->w), actions, &(d->id_map),
horizontal, g_action);
if (yes_default) {
d->active_el = d->g_action->elements;
} else {
d->active_el = d->g_action->elements + 1;
}
w_dialogue_draw(d);
wrefresh(d->win);
int c = 0;
bool response = false;
while ((c = wgetch(app->win))) {
int ret = w_bool_callback(app, d, c);
switch (ret) {
case 1:
response = true;
goto break_while;
case 2:
goto break_while;
}
w_dialogue_draw(d);
wrefresh(d->win);
}
break_while:
w_group_destroy(d->g_action);
delwin(d->win);
free(d);
app->active_callback = callback;
app_refresh(app);
if (app->modal.win != NULL) {
app->modal.needs_update = true;
}
return response;
}
+3 -1
View File
@@ -7,6 +7,8 @@
void w_alert_init(app_t *app); void w_alert_init(app_t *app);
void w_alert(const char *message); void w_alert(const char *message);
void w_notification(const char *title, enum w_dialogue_color_scheme color, void w_notification(const char *title, enum w_dialogue_color_scheme color,
const char *f_message, ...); const char *f_message, ...);
int w_bool_ask(const char *title, const char *yes_button,
const char *no_button, bool yes_default, const char *f_message, ...);
#endif #endif
+15 -6
View File
@@ -1,11 +1,14 @@
/* SPDX-License-Identifier: MIT */ /* SPDX-License-Identifier: MIT */
/* Copyright (c) 2026 Oleksandr Zhylin */ /* Copyright (c) 2026 Oleksandr Zhylin */
#include <ncursesw/ncurses.h> #include <ncursesw/ncurses.h>
#include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
#include <sys/types.h> #include <sys/types.h>
#include <wchar.h>
#include <widget.h> #include <widget.h>
#include <widget_core.h> #include <widget_core.h>
@@ -60,7 +63,7 @@ static void decr_active_id(w_dialogue_t *d) {
} }
static void case_callback(w_cb_args_t *args, w_cb_args_t *new_args) { static void case_callback(w_cb_args_t *args, w_cb_args_t *new_args) {
w_dialogue_t *d = (void *)args->element; w_dialogue_t *d = args->element;
/* run callback function */ /* run callback function */
if (d->active_el->g_type == g_content) { if (d->active_el->g_type == g_content) {
new_args->element = d->g_content; new_args->element = d->g_content;
@@ -74,7 +77,7 @@ static void case_callback(w_cb_args_t *args, w_cb_args_t *new_args) {
} }
void w_dialogue_callback_default(w_cb_args_t *args) { void w_dialogue_callback_default(w_cb_args_t *args) {
w_dialogue_t *d = (void *)args->element; w_dialogue_t *d = args->element;
int32_t key = *((int32_t *)args->data); int32_t key = *((int32_t *)args->data);
w_cb_args_t new_args; w_cb_args_t new_args;
memcpy(&new_args, args, sizeof(w_cb_args_t)); memcpy(&new_args, args, sizeof(w_cb_args_t));
@@ -237,7 +240,11 @@ int32_t w_dialogue_draw(w_dialogue_t *d) {
/* analyze text content */ /* analyze text content */
int32_t line_count = 0; int32_t line_count = 0;
int32_t line_max_len = 0; int32_t line_max_len = 0;
line_max_len = w_get_max_line_len(d->text, &line_count); wchar_t *main_text_wide = malloc(sizeof(wchar_t) * strlen(d->text));
size_t mwt_len;
u_utf8_convert_to_wide(d->text, &main_text_wide, &mwt_len);
line_count = w_text_divide_by_lines(
main_text_wide, mwt_len, d->p_coordinates->max_x * 0.77, &line_max_len);
y += line_count; y += line_count;
y += 2; /* margin for text */ y += 2; /* margin for text */
@@ -246,8 +253,8 @@ int32_t w_dialogue_draw(w_dialogue_t *d) {
DETECT_GROUP_SIZE(d->g_action, line_max_len, y, x); DETECT_GROUP_SIZE(d->g_action, line_max_len, y, x);
x += line_max_len; x += line_max_len;
x += 1; /* when uses box */ x += 2; /* when uses box */
y += 1; /* when uses box */ y += 2; /* when uses box */
d->w.sz.x = x; d->w.sz.x = x;
d->w.sz.y = y; d->w.sz.y = y;
@@ -283,7 +290,8 @@ int32_t w_dialogue_draw(w_dialogue_t *d) {
/* text */ /* text */
wattroff(d->win, A_BOLD); wattroff(d->win, A_BOLD);
/* mvwhline(d->win, d->w.sz.y - 3, 1, 0, d->w.sz.x - 2); */ /* mvwhline(d->win, d->w.sz.y - 3, 1, 0, d->w.sz.x - 2); */
w_print_multiline_text(d->win, d->text, d->w.sz.x, 2, 1, PMT_ALIGN_CENTER); w_print_multiline_text_wide(d->win, main_text_wide, mwt_len, line_max_len,
d->w.sz.x, 2, 0, PMT_ALIGN_CENTER);
wattroff(d->win, A_REVERSE); wattroff(d->win, A_REVERSE);
if (d->g_content != NULL) { if (d->g_content != NULL) {
@@ -325,6 +333,7 @@ void w_dialogue_destroy(w_dialogue_t *d, void *_app) {
d->win = NULL; d->win = NULL;
d->is_initiated = false; d->is_initiated = false;
d->needs_destroy = false; d->needs_destroy = false;
d->needs_update = false;
app->active_callback = (w_cb_t)app->main_ui.cb_b_press; app->active_callback = (w_cb_t)app->main_ui.cb_b_press;
app->query_args->active_dialogue = NULL; app->query_args->active_dialogue = NULL;
u_d_arr_ptr_free(&(d->id_map)); u_d_arr_ptr_free(&(d->id_map));
+2 -14
View File
@@ -16,10 +16,7 @@ bool w_input_default_key_action(w_cb_args_t *args) {
w_g_el_t *g_el = args->active_el; w_g_el_t *g_el = args->active_el;
w_input_t *input = g_el->element; w_input_t *input = g_el->element;
int32_t start_pos = 0; int32_t start_pos = 0;
wchar_t w_key[2];
app_t *app = args->app; app_t *app = args->app;
char long_key[3];
int short_val_len = 1;
switch (key) { switch (key) {
case KEY_BACKSPACE: case KEY_BACKSPACE:
@@ -47,23 +44,14 @@ bool w_input_default_key_action(w_cb_args_t *args) {
default: default:
if (input->value_len == input->max_len) if (input->value_len == input->max_len)
break; break;
long_key[0] = key; key = u_utf8_get_full_letter(key, app->win);
input->value_len++;
if ((key & 0xC0) == 0xC0) {
long_key[1] = wgetch(app->win);
long_key[2] = '\0';
short_val_len++;
} else {
long_key[1] = '\0';
}
mbstowcs(w_key, (const char *)&long_key, 2);
if (input->cur_pos > 0) { if (input->cur_pos > 0) {
start_pos = input->value_len - input->cur_pos - 1; start_pos = input->value_len - input->cur_pos - 1;
memmove(input->w_value + start_pos + 1, input->w_value + start_pos, memmove(input->w_value + start_pos + 1, input->w_value + start_pos,
sizeof(wchar_t) * (input->value_len - start_pos)); sizeof(wchar_t) * (input->value_len - start_pos));
input->w_value[start_pos] = key; input->w_value[start_pos] = key;
} else { } else {
input->w_value[input->value_len - 1] = w_key[0]; input->w_value[input->value_len - 1] = key;
input->w_value[input->value_len] = '\0'; input->w_value[input->value_len] = '\0';
} }
break; break;
+14 -8
View File
@@ -11,7 +11,7 @@ w_pgb_ui_t *w_pgb_ui_init(WINDOW **win, w_t *w_parent) {
WINDOW *win_par = *(w_parent->w_parent->parent_win); WINDOW *win_par = *(w_parent->w_parent->parent_win);
w_pgb_ui_t *pb = malloc(sizeof(w_pgb_ui_t)); w_pgb_ui_t *pb = malloc(sizeof(w_pgb_ui_t));
w_init(&pb->w, w_parent, win, ""); w_init(&pb->w, w_parent, win, "");
pb->procent = 0; pb->percentage = 0;
pb->text[0] = '\0'; pb->text[0] = '\0';
pb->w.sz.y = 4; pb->w.sz.y = 4;
pb->w.sz.x = getmaxx(win_par) / 10 * 9; pb->w.sz.x = getmaxx(win_par) / 10 * 9;
@@ -25,17 +25,23 @@ void w_pgb_ui_draw(w_pgb_ui_t *pb) {
wattrset(win, COLOR_PAIR(modal_color_pair)); wattrset(win, COLOR_PAIR(modal_color_pair));
/* top line */
mvwhline(win, margin_y, margin_x, 0, pb->w.sz.x - 1); mvwhline(win, margin_y, margin_x, 0, pb->w.sz.x - 1);
float procent = pb->procent; int center = pb->w.sz.x / 2 - 3;
procent /= 100;
int32_t filled_num = procent * pb->w.sz.x; mvwprintw(win, margin_y, margin_x + center, "| %d%% |", pb->percentage);
float percentage = pb->percentage;
percentage /= 100;
int32_t filled_num = percentage * pb->w.sz.x;
if (filled_num > 2) { if (filled_num > 2) {
filled_num -= 2; filled_num -= 2;
} }
for (int i = 1; i < 3; i++) { wattrset(win, COLOR_PAIR(1) | A_REVERSE);
wattrset(win, COLOR_PAIR(1) | A_REVERSE);
for (int i = 1; i < 3; i++) { /* two lines */
mvwprintw(win, margin_y + i, margin_x + 1, "%*s", filled_num, ""); mvwprintw(win, margin_y + i, margin_x + 1, "%*s", filled_num, "");
} }
@@ -43,12 +49,12 @@ void w_pgb_ui_draw(w_pgb_ui_t *pb) {
// bottom line // bottom line
mvwhline(win, margin_y + 3, margin_x, 0, pb->w.sz.x - 1); mvwhline(win, margin_y + 3, margin_x, 0, pb->w.sz.x - 1);
// left // left corners
mvwvline(win, margin_y, margin_x, ACS_ULCORNER, 1); mvwvline(win, margin_y, margin_x, ACS_ULCORNER, 1);
mvwvline(win, margin_y + 1, margin_x, 0, 1); mvwvline(win, margin_y + 1, margin_x, 0, 1);
mvwvline(win, margin_y + 2, margin_x, 0, 1); mvwvline(win, margin_y + 2, margin_x, 0, 1);
mvwvline(win, margin_y + 3, margin_x, ACS_LLCORNER, 1); mvwvline(win, margin_y + 3, margin_x, ACS_LLCORNER, 1);
// right // right corners
mvwvline(win, margin_y, margin_x + pb->w.sz.x - 2, ACS_URCORNER, 1); mvwvline(win, margin_y, margin_x + pb->w.sz.x - 2, ACS_URCORNER, 1);
mvwvline(win, margin_y + 1, margin_x + pb->w.sz.x - 2, 0, 1); mvwvline(win, margin_y + 1, margin_x + pb->w.sz.x - 2, 0, 1);
mvwvline(win, margin_y + 2, margin_x + pb->w.sz.x - 2, 0, 1); mvwvline(win, margin_y + 2, margin_x + pb->w.sz.x - 2, 0, 1);
+1 -1
View File
@@ -8,7 +8,7 @@
typedef struct { typedef struct {
w_t w; w_t w;
int32_t procent; int32_t percentage;
char text[24]; char text[24];
} w_pgb_ui_t; } w_pgb_ui_t;
+115 -5
View File
@@ -8,10 +8,12 @@
#include "widget_core.h" #include "widget_core.h"
#include "../main.h" #include "../main.h"
#include <ncursesw/ncurses.h> #include <ncursesw/ncurses.h>
#include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <utils.h> #include <utils.h>
#include <wchar.h>
extern int32_t m_id; extern int32_t m_id;
@@ -59,6 +61,49 @@ int32_t w_get_max_line_len(const char *text, int32_t *line_count) {
} }
} }
/* returns number of lines */
int32_t w_text_divide_by_lines(wchar_t *restrict text, size_t src_len,
int max_x, int *max_len) {
int cl_pos = 0;
int last_space = -1; /* space position in the current line */
int lines_num = 1;
for (int i = 0; text[i] != '\0'; i++, cl_pos++) {
switch (text[i]) {
case ' ': /* space symbol */
last_space = i;
break;
case '\n':
if (cl_pos > *max_len) {
*max_len = cl_pos;
}
last_space = -1;
cl_pos = 0;
lines_num++;
break;
default:
if (cl_pos > max_x) {
if (last_space > 0) {
int line_size = last_space - (i - cl_pos);
if (line_size > *max_len) {
*max_len = line_size;
}
text[last_space] = '\n';
i = last_space;
last_space = -1;
cl_pos = 0;
lines_num++;
}
}
}
}
cl_pos--;
if (cl_pos > *max_len) { /* for the last line */
*max_len = cl_pos;
}
return lines_num;
}
/* #define PRINT_TEXT(win, l_buf, text, i, c_line_len, win_width, line_v_pos, \ /* #define PRINT_TEXT(win, l_buf, text, i, c_line_len, win_width, line_v_pos, \
attrs) \ attrs) \
strncpy(l_buf, text + i - c_line_len, c_line_len); \ strncpy(l_buf, text + i - c_line_len, c_line_len); \
@@ -72,7 +117,7 @@ int32_t w_get_max_line_len(const char *text, int32_t *line_count) {
} }
*/ */
void PRINT_TEXT(WINDOW *win, char *l_buf, char *text, int32_t i, void line_print(WINDOW *win, char *l_buf, char *text, int32_t i,
int32_t c_line_len, int32_t win_width, int32_t line_v_pos, int32_t c_line_len, int32_t win_width, int32_t line_v_pos,
int32_t attrs, int32_t x, int32_t max_line_len) { int32_t attrs, int32_t x, int32_t max_line_len) {
int32_t utf8len = 0; int32_t utf8len = 0;
@@ -90,9 +135,27 @@ void PRINT_TEXT(WINDOW *win, char *l_buf, char *text, int32_t i,
} }
} }
void wline_print(WINDOW *win, wchar_t *w_text, int32_t i, int32_t c_line_len,
int32_t win_width, int32_t y, int32_t attrs, int32_t x,
int32_t max_line_len) {
wchar_t l_buf[DIALOGUE_TEXT];
int32_t b_len = c_line_len * sizeof(wchar_t);
memcpy(l_buf, w_text + i - c_line_len, b_len);
l_buf[c_line_len] = '\0';
if ((attrs & PMT_ALIGN_CENTER) == PMT_ALIGN_CENTER) {
int32_t _x = x + (win_width - c_line_len) / 2;
mvwaddnwstr(win, y, _x, l_buf, c_line_len);
} else if (attrs & PMT_POS_CENTER) {
int32_t _x = x + (win_width - max_line_len) / 2;
mvwaddnwstr(win, y, _x, l_buf, c_line_len);
} else {
mvwaddnwstr(win, y, x, l_buf, c_line_len);
}
}
int32_t w_print_multiline_text(WINDOW *win, const char *_text, int32_t w_print_multiline_text(WINDOW *win, const char *_text,
const int32_t win_width, const int32_t y, const int32_t win_width, const int32_t y,
const int32_t x, const int16_t attrs) { const int32_t x, const int16_t attrs) {
int16_t line_v_pos = y; int16_t line_v_pos = y;
int32_t c_line_len = 0; int32_t c_line_len = 0;
@@ -126,17 +189,64 @@ int32_t w_print_multiline_text(WINDOW *win, const char *_text,
for (; text[i]; i++, c_line_len++) { for (; text[i]; i++, c_line_len++) {
if (text[i] == '\n') { if (text[i] == '\n') {
PRINT_TEXT(win, l_buf, text, i, c_line_len, win_width, line_v_pos, attrs, line_print(win, l_buf, text, i, c_line_len, win_width, line_v_pos, attrs,
x, m_line_len); x, m_line_len);
c_line_len = -1; c_line_len = -1;
line_v_pos++; line_v_pos++;
} }
} }
PRINT_TEXT(win, l_buf, text, i, c_line_len, win_width, line_v_pos, attrs, x, line_print(win, l_buf, text, i, c_line_len, win_width, line_v_pos, attrs, x,
m_line_len); m_line_len);
free(text); free(text);
return 1; return 1;
} }
int32_t w_print_multiline_text_wide(WINDOW *win, const wchar_t *_text,
size_t text_len, int32_t max_line_size,
const int32_t win_width, const int32_t y,
const int32_t x, const int16_t attrs) {
int16_t line_v_pos = y;
int32_t c_line_len = 0;
int32_t i = 0;
int text_bytes_size = sizeof(wchar_t) * (text_len + 1);
wchar_t *text = malloc(text_bytes_size);
mempcpy(text, _text, text_bytes_size);
int l_size = 0;
for (int m = 0; text[m] != '\0'; m++) {
if (text[m] != '\n') {
if (l_size >= win_width) {
int j = m;
while (text[j] != ' ') {
j--;
}
text[j] = '\n';
l_size = 0;
} else {
l_size++;
}
} else {
l_size = 0;
}
}
for (; text[i]; i++, c_line_len++) {
if (text[i] == '\n') {
wline_print(win, text, i, c_line_len, win_width, line_v_pos, attrs, x,
max_line_size);
c_line_len = -1;
line_v_pos++;
}
}
wline_print(win, text, i, c_line_len, win_width, line_v_pos, attrs, x,
max_line_size);
free(text);
return 1;
}
+13 -7
View File
@@ -59,8 +59,8 @@ enum w_cbrp_code {
/* widget callback response data */ /* widget callback response data */
typedef struct { typedef struct {
enum w_cbrp_code code; enum w_cbrp_code code;
struct w_val_t val; struct w_val_t val;
} w_cbrp_data; } w_cbrp_data;
typedef struct { typedef struct {
@@ -103,10 +103,16 @@ enum w_pmt_attrs {
PMT_ALIGN_CENTER = 03, PMT_ALIGN_CENTER = 03,
}; };
void w_init(w_t *w, w_t *w_parent, WINDOW **win, char *title); void w_init(w_t *w, w_t *w_parent, WINDOW **win, char *title);
int32_t w_get_max_line_len(const char *text, int32_t *line_count); int32_t w_get_max_line_len(const char *text, int32_t *line_count);
int32_t w_print_multiline_text(WINDOW *win, const char *text, int32_t w_print_multiline_text(WINDOW *win, const char *text,
const int32_t win_width, const int32_t y, const int32_t win_width, const int32_t y,
const int32_t x, const int16_t attrs); const int32_t x, const int16_t attrs);
int32_t w_print_multiline_text_wide(WINDOW *win, const wchar_t *_text,
size_t text_len, int32_t max_line_size, const int32_t win_width,
const int32_t y, const int32_t x,
const int16_t attrs);
int32_t w_text_divide_by_lines(wchar_t *restrict text, size_t src_len,
int max_x, int *max_len);
#endif #endif
+6 -4
View File
@@ -175,7 +175,7 @@ void *w_fl_init(app_t *app) {
app->main_ui.b_keys_len = 4; app->main_ui.b_keys_len = 4;
app->main_ui.cb_b_press = (w_cb_press_t)process_user_input; app->main_ui.cb_b_press = (w_cb_press_t)process_user_input;
app->main_ui.reset = w_fl_reset_app; app->main_ui.reset = w_fl_reset_app;
app->main_ui.draw = (draw_f_t *) w_fl_draw; app->main_ui.draw = (draw_f_t *)w_fl_draw;
app->active_callback = w_fl_cb; app->active_callback = w_fl_cb;
fui->w.callback = w_fl_cb; fui->w.callback = w_fl_cb;
w_init(&(fui->w), NULL, &(app->win), ""); w_init(&(fui->w), NULL, &(app->win), "");
@@ -227,8 +227,8 @@ void w_fl_reset(w_ui_file_list_t *fl_ui) {
} }
void w_fl_reset_app(void *app) { void w_fl_reset_app(void *app) {
app_t *_app = (app_t *) app; app_t *_app = (app_t *)app;
w_ui_file_list_t *fl_ui = (w_ui_file_list_t *) _app->main_ui.ui; w_ui_file_list_t *fl_ui = (w_ui_file_list_t *)_app->main_ui.ui;
w_fl_reset(fl_ui); w_fl_reset(fl_ui);
} }
@@ -314,6 +314,7 @@ void w_fl_draw(w_ui_file_list_t *fui) {
curs_set(true); curs_set(true);
wmove(win, p_y, p_x + p_len); wmove(win, p_y, p_x + p_len);
} else { } else {
curs_set(false);
int32_t l_pad = 0; int32_t l_pad = 0;
sprintf(p_info, "P: %d/%d F: %d L: %d%n", fui->current_page, fui->pages, sprintf(p_info, "P: %d/%d F: %d L: %d%n", fui->current_page, fui->pages,
fui->current_count, fui->full_count, &p_len); fui->current_count, fui->full_count, &p_len);
@@ -350,7 +351,8 @@ void w_fl_draw(w_ui_file_list_t *fui) {
mvwprintw(i_win, p_y, p_x, "Name: "); mvwprintw(i_win, p_y, p_x, "Name: ");
p_x = 8; /* 7 (size of "Name: ") + 1 */ p_x = 8; /* 7 (size of "Name: ") + 1 */
u_utf8_curs_printw(i_win, &p_y, &p_x, active_el->name, sz_x - p_x, true); u_utf8_curs_printw(i_win, &p_y, &p_x, active_el->name, sz_x - p_x - 2,
true);
p_x = 1; p_x = 1;
size_to_text(active_el->size, size_text); size_to_text(active_el->size, size_text);
+8
View File
@@ -4,11 +4,15 @@
#include "main_window.h" #include "main_window.h"
#include "file_list.h" #include "file_list.h"
#include "fs_file_list.h" #include "fs_file_list.h"
#include "text_edit.h"
void main_window_set(app_t *app, enum main_window_type type) { void main_window_set(app_t *app, enum main_window_type type) {
if (app->main_ui.ui != NULL) { if (app->main_ui.ui != NULL) {
main_window_destroy(app); main_window_destroy(app);
} }
if (app->modal.is_initiated) {
w_dialogue_destroy(&(app->modal), app);
}
app->main_ui.type = type; app->main_ui.type = type;
switch (type) { switch (type) {
case mw_fl_server: case mw_fl_server:
@@ -19,10 +23,12 @@ void main_window_set(app_t *app, enum main_window_type type) {
w_lfl_init_win(app); w_lfl_init_win(app);
break; break;
case mw_f_desc: case mw_f_desc:
w_te_init_win(app);
break; break;
default: default:
break; break;
} }
app_draw_bars(app);
} }
void main_window_draw(app_t *app) { void main_window_draw(app_t *app) {
@@ -34,6 +40,7 @@ void main_window_draw(app_t *app) {
w_lfl_draw((w_lfl_ui_t *)app->main_ui.ui); w_lfl_draw((w_lfl_ui_t *)app->main_ui.ui);
break; break;
case mw_f_desc: case mw_f_desc:
w_te_draw((w_te_ui_t *)app->main_ui.ui);
break; break;
} }
app_draw_bbar(app); app_draw_bbar(app);
@@ -48,6 +55,7 @@ void main_window_destroy(app_t *app) {
w_lfl_destroy((w_lfl_ui_t **)&(app->main_ui.ui)); w_lfl_destroy((w_lfl_ui_t **)&(app->main_ui.ui));
break; break;
case mw_f_desc: case mw_f_desc:
w_te_destroy((w_te_ui_t **)&(app->main_ui.ui));
break; break;
} }
MAIN_UI_RESET(app); MAIN_UI_RESET(app);
+338
View File
@@ -0,0 +1,338 @@
/* SPDX-License-Identifier: MIT */
/* Copyright (c) 2026 Oleksandr Zhylin */
#include "../file_processor.h"
#include "../server.h"
#include "../windows/main_window.h"
#include <dirent.h>
#include <fcntl.h>
#include <ncursesw/ncurses.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <utils.h>
#include <wchar.h>
#include "d_array.h"
#include "file_list.h"
#include "text_edit.h"
#include "widget_core.h"
/* TODO: расширять capacity линии при достижении лимита */
#define INACTIVE_LINES 5
static void line_destroy_cb(void *line_arg) {
w_te_ui_line_t *line = line_arg;
free(line->text);
free(line);
}
static void w_te_cb(w_cb_args_t *args) {
app_t *app = args->app;
int32_t key = *((int32_t *)args->data);
w_te_ui_t *fui = app->query_args->main_ui->ui;
w_te_ui_line_t *new_line = NULL;
switch (key) {
case KEY_BACKSPACE:
case KEY_DL:
if (fui->cur_line->len == 0)
break;
if (fui->cur_line_pos == fui->cur_line->len && fui->cur_line->len > 0) {
fui->cur_line->text[--fui->cur_line_pos] = 0;
fui->cur_line->len--;
} else if (fui->cur_line_idx > 0) {
new_line = fui->lines_arr.arr[fui->cur_line_idx - 1];
if (fui->cur_line_pos == 0 && fui->cur_line->len > 0) {
wcscpy(new_line->text + new_line->len, fui->cur_line->text);
fui->cur_line_pos = new_line->len;
new_line->len += fui->cur_line->len;
} else {
fui->cur_line_pos = new_line->len;
}
u_d_arr_ptr_remove_cb(&(fui->lines_arr), fui->cur_line, fui->cur_line_idx,
&line_destroy_cb);
fui->cur_line = fui->lines_arr.arr[--fui->cur_line_idx];
fui->lines_count--;
} else {
int n = fui->cur_line->len - fui->cur_line_pos + 1;
memmove(fui->cur_line->text + fui->cur_line_pos - 1,
fui->cur_line->text + fui->cur_line_pos, n * sizeof(wchar_t));
fui->cur_line->len--;
fui->cur_line_pos--;
}
fui->sym_count--;
break;
case KEY_UP:
if (fui->cur_line_idx > 0) {
fui->cur_line = fui->lines_arr.arr[--fui->cur_line_idx];
fui->cur_line_pos = fui->cur_line->len;
if ((fui->cur_line_idx > 0) &&
(fui->cur_line_idx - fui->lines_top_indent == 0)) {
fui->lines_top_indent--;
}
}
break;
case KEY_DOWN:
if ((fui->lines_arr.arr[fui->cur_line_idx + 1] != NULL)) {
fui->cur_line = fui->lines_arr.arr[++fui->cur_line_idx];
fui->cur_line_pos = fui->cur_line->len;
if (fui->cur_line_idx >=
fui->lines_top_indent + fui->w.sz.y - INACTIVE_LINES) {
fui->lines_top_indent++;
}
}
break;
case KEY_LEFT:
if (fui->cur_line_pos > 0) {
fui->cur_line_pos--;
} else if (fui->cur_line_idx > 0) { /* ACT LIKE KEY TOP */
fui->cur_line = fui->lines_arr.arr[--fui->cur_line_idx];
fui->cur_line_pos = fui->cur_line->len;
if ((fui->cur_line_idx > 0) &&
(fui->cur_line_idx - fui->lines_top_indent == 0)) {
fui->lines_top_indent--;
}
}
break;
case KEY_RIGHT:
if (fui->cur_line_pos < fui->cur_line->len) {
fui->cur_line_pos++;
} else if ((fui->lines_arr.arr[fui->cur_line_idx + 1] !=
NULL)) { /* ACT LIKE KEY BOTTOM */
fui->cur_line = fui->lines_arr.arr[++fui->cur_line_idx];
fui->cur_line_pos = fui->cur_line->len;
if (fui->cur_line_idx >=
fui->lines_top_indent + fui->w.sz.y - INACTIVE_LINES) {
fui->lines_top_indent++;
}
}
break;
case '\n':
new_line = malloc(sizeof(w_te_ui_line_t));
new_line->text = malloc(sizeof(wchar_t) * INBUFSIZE);
new_line->capacity = new_line->len = INBUFSIZE;
new_line->text[0] = 0;
new_line->len = 0;
if (fui->cur_line_pos != 0) {
u_d_arr_ptr_add(&(fui->lines_arr), new_line, fui->cur_line_idx + 1);
wcscpy(new_line->text, fui->cur_line->text + fui->cur_line_pos);
new_line->len = fui->cur_line->len - fui->cur_line_pos;
fui->cur_line->text[fui->cur_line_pos] = 0;
fui->cur_line->len -= new_line->len;
} else {
u_d_array_append(&(fui->lines_arr), new_line, sizeof(w_te_ui_line_t));
}
fui->cur_line_pos = 0;
fui->cur_line = new_line;
fui->cur_line_idx++;
fui->sym_count++;
fui->lines_count++;
if (fui->cur_line_idx >= fui->w.sz.y - INACTIVE_LINES) {
fui->lines_top_indent++;
}
break;
default:
key = u_utf8_get_full_letter(key, app->win);
if (fui->cur_line_pos == fui->cur_line->len) {
fui->cur_line->text[fui->cur_line_pos++] = key;
fui->cur_line->text[fui->cur_line_pos] = 0;
} else {
int n = fui->cur_line->len - fui->cur_line_pos + 1;
memmove(fui->cur_line->text + fui->cur_line_pos + 1,
fui->cur_line->text + fui->cur_line_pos, n * sizeof(wchar_t));
fui->cur_line->text[fui->cur_line_pos++] = key;
}
fui->sym_count++;
fui->cur_line->len++;
}
}
static void w_te_cb_refresh(void *data) {
app_t *app = (app_t *)data;
if (app->main_ui.type != mw_f_desc) {
return;
}
w_te_ui_t *fui = app->main_ui.ui;
wnoutrefresh(fui->win);
}
static struct action_key action_keys[] = {
{.key = "Esc", .title = "Back", .code = '\33'},
{.key = "F2", .title = "Save", .code = KEY_F(2)},
{.key = "F8", .title = "Cancel", .code = KEY_F(8)},
{.key = "F9", .title = "Quit", .code = KEY_F(9)},
};
void callback_after_notification(app_t *app) {
clear_file_in_query(app->query_args);
int a_len;
main_window_set(app, mw_fl_server);
w_ui_file_list_t *fui = app->main_ui.ui;
server_send_string(app->query_args, "file list %d %d\n%n", fui->max_lines,
fui->current_page, &a_len);
app->query_args->state = S_FILE_LIST;
}
static int32_t process_user_input(app_t *app, w_cb_args_t *d_args) {
int32_t c;
w_te_ui_t *fui = (w_te_ui_t *)app->query_args->main_ui->ui;
c = wgetch(app->win);
int text_pos = 0;
w_te_ui_line_t *arr_line = NULL;
switch (c) {
case KEY_F(9):
app_destroy(app, 0);
return OK;
case KEY_F(2):
/* TODO: move to description edit */
app->query_args->file->description =
malloc(sizeof(wchar_t) * (fui->sym_count + 9));
for (int i = 0; i < fui->lines_count; i++) {
arr_line = fui->lines_arr.arr[i];
text_pos += wcstombs(app->query_args->file->description + text_pos,
arr_line->text, arr_line->len);
app->query_args->file->description[text_pos++] = '\n';
}
// app->query_args->file->description[--text_pos] = '\03'; /* ETX symbol */
app->query_args->file->description[text_pos] = '\03'; /* ETX symbol */
strncpy(app->query_args->file->description + text_pos, ":END:\n",
sizeof(":END:\n"));
server_send_string(app->query_args, app->query_args->file->description);
app->query_args->state = S_WAIT_SERVER;
app->callback_after_notification = callback_after_notification;
/* Save */
break;
case KEY_F(8):
server_send_string(app->query_args, "[Empty description]\n:END:\n");
app->query_args->state = S_WAIT_SERVER;
app->callback_after_notification = callback_after_notification;
/* Cancel */
break;
default:
d_args->data = (void *)&c;
app->active_callback(d_args);
break;
}
return OK;
}
void w_te_reset_app(void *app);
w_te_ui_t *w_te_init_win(app_t *app) {
wchar_t top_text[INBUFSIZE];
int top_text_len = 0;
w_te_ui_t *fui = malloc(sizeof(w_te_ui_t));
app->main_ui.ui = fui;
app->main_ui.type = mw_f_desc;
app->main_ui.cb_ui_refresh = w_te_cb_refresh;
app->main_ui.b_keys = action_keys;
app->main_ui.b_keys_len = 4;
app->main_ui.cb_b_press = (w_cb_press_t)process_user_input;
app->main_ui.reset = w_te_reset_app;
app->main_ui.draw = (draw_f_t *)w_te_draw;
app->active_callback = w_te_cb;
fui->w.callback = w_te_cb;
w_init(&(fui->w), NULL, &(app->win), "");
fui->file_ptr = app->query_args->file;
fui->w.sz.x = getmaxx(app->win);
fui->w.sz.y = getmaxy(app->win);
fui->w.parent_win = &(app->win);
/* * INIT UI * */
fui->win = newwin(fui->w.sz.y - 4, fui->w.sz.x - 2, 2, 1);
top_text_len =
swprintf(top_text, INBUFSIZE, L"Description: %s", fui->file_ptr->name);
app->top_text = malloc(sizeof(wchar_t) * (top_text_len + 1));
wcsncpy(app->top_text, top_text, top_text_len);
app->top_text[top_text_len] = 0;
u_d_arr_ptr_init(&(fui->lines_arr), INBUFSIZE);
fui->cur_line = malloc(sizeof(w_te_ui_line_t));
fui->cur_line->text = malloc(INBUFSIZE * sizeof(wchar_t));
fui->cur_line->len = 0;
fui->cur_line->capacity = INBUFSIZE;
u_d_array_append(&(fui->lines_arr), fui->cur_line, sizeof(w_te_ui_line_t));
fui->cur_line_idx = 0;
fui->sym_count = 0;
fui->lines_count = 1;
fui->lines_top_indent = 0;
fui->cur_line_pos = 0;
return fui;
}
void w_te_reset(w_te_ui_t *fui) {
fui->cur_line_pos = 0;
fui->sym_count = 0;
fui->lines_count = 1;
fui->lines_top_indent = 0;
u_d_arr_free_cb(&(fui->lines_arr), line_destroy_cb);
}
void w_te_reset_app(void *app) {
app_t *_app = app;
w_te_ui_t *fl_ui = (w_te_ui_t *)_app->main_ui.ui;
w_te_reset(fl_ui);
}
void w_te_draw(w_te_ui_t *fui) {
int32_t sz_y, sz_x;
int32_t p_y, p_x;
WINDOW *win = fui->win;
getmaxyx(win, sz_y, sz_x);
int32_t sz_y_f = sz_y - 1; // actual size (without box)
int32_t sz_x_f = sz_x - 1; // actual size (without box)
p_y = 0;
p_x = 0;
wattrset(win, COLOR_PAIR(modal_color_pair) | A_BOLD | A_REVERSE);
int i = 0;
for (i = 0; i < fui->w.sz.y - INACTIVE_LINES; i++) {
mvwprintw(win, p_y + i, p_x, "%*s", fui->w.sz.x, "");
}
wattrset(win, COLOR_PAIR(modal_color_pair) | A_BOLD);
int len = 0;
mvwprintw(win, p_y + i, p_x, "LC: %d SC: %d L: %d P: %d%n", fui->lines_count,
fui->sym_count, fui->cur_line_idx + 1, fui->cur_line_pos + 1, &len);
mvwprintw(win, p_y + i, p_x + len, "%*s", fui->w.sz.x - len, "");
wattrset(win, COLOR_PAIR(modal_color_pair) | A_BOLD | A_REVERSE);
w_te_ui_line_t *line_arr_el = NULL;
if (fui->lines_arr.length > 0) {
for (i = fui->lines_top_indent;
i < fui->lines_arr.length &&
i < fui->lines_top_indent + fui->w.sz.y - INACTIVE_LINES;
i++) {
line_arr_el = (w_te_ui_line_t *)fui->lines_arr.arr[i];
mvwaddnwstr(win, p_y++, p_x, line_arr_el->text, line_arr_el->len);
}
}
p_y--;
wmove(win, fui->cur_line_idx - fui->lines_top_indent, fui->cur_line_pos);
p_x = 1;
wattroff(win, A_BOLD);
curs_set(true);
}
void w_te_destroy(w_te_ui_t **fui) {
w_te_ui_t *_fui = *fui;
u_d_arr_free_cb(&(_fui->lines_arr), line_destroy_cb);
free(*fui);
*fui = NULL;
}
+42
View File
@@ -0,0 +1,42 @@
/* SPDX-License-Identifier: MIT */
/* Copyright (c) 2026 Oleksandr Zhylin */
#ifndef W_TEXT_EDIT_H /* LOCAL FILE LIST */
#define W_TEXT_EDIT_H
#include "../app.h"
#include <ncursesw/ncurses.h>
#include <stddef.h>
#include <stdint.h>
#include <sys/types.h>
#include <wchar.h>
#include <widget_core.h>
typedef struct {
int len;
wchar_t *text; /* TODO: make dynamic */
int capacity;
} w_te_ui_line_t;
typedef struct {
w_t w;
WINDOW *win;
p_file_t *file_ptr;
w_te_ui_line_t *cur_line;
int cur_line_idx;
int cur_line_pos;
d_array_ptr_t lines_arr;
int lines_count;
int lines_top_indent;
int sym_count;
} w_te_ui_t;
void w_te_draw(w_te_ui_t *fl_ui);
void w_te_destroy(w_te_ui_t **fui);
void w_te_reset(w_te_ui_t *fl_ui);
w_te_ui_t *w_te_init(WINDOW **win, w_t *w_parent);
w_te_ui_t *w_te_init_win(app_t *app);
void u_d_arr_ptr_remove_cb(d_array_ptr_t *arr, void *ptr, int32_t idx, u_d_arr_free_callback *callback);
#endif