|
|
|
|
@@ -84,6 +84,26 @@ _check_subwin_width(int cols, int width)
|
|
|
|
|
return cols <= 1 ? 1 : CLAMP(width, 1, cols - 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Visible chat-area height in lines: stdscr minus the title bar (1), the
|
|
|
|
|
// status bar (1), the input prompt area (1), and the separator (1).
|
|
|
|
|
int
|
|
|
|
|
win_page_space(void)
|
|
|
|
|
{
|
|
|
|
|
return getmaxy(stdscr) - 4;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// True when the user has scrolled away from the live tail and there is
|
|
|
|
|
// content below the visible viewport. Derived from y_pos and the current
|
|
|
|
|
// height of the pad — no sticky flag, no possibility of staleness.
|
|
|
|
|
gboolean
|
|
|
|
|
win_is_paged(const ProfWin* window)
|
|
|
|
|
{
|
|
|
|
|
if (!window || !window->layout || !window->layout->win)
|
|
|
|
|
return FALSE;
|
|
|
|
|
int total_rows = getcury(window->layout->win);
|
|
|
|
|
return (total_rows - window->layout->y_pos) > win_page_space();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
|
win_roster_cols(void)
|
|
|
|
|
{
|
|
|
|
|
@@ -117,7 +137,6 @@ _win_create_simple_layout(void)
|
|
|
|
|
wbkgd(layout->base.win, theme_attrs(THEME_TEXT));
|
|
|
|
|
layout->base.buffer = buffer_create();
|
|
|
|
|
layout->base.y_pos = 0;
|
|
|
|
|
layout->base.paged = 0;
|
|
|
|
|
layout->base.unread_msg = 0;
|
|
|
|
|
scrollok(layout->base.win, TRUE);
|
|
|
|
|
|
|
|
|
|
@@ -135,7 +154,6 @@ _win_create_split_layout(void)
|
|
|
|
|
wbkgd(layout->base.win, theme_attrs(THEME_TEXT));
|
|
|
|
|
layout->base.buffer = buffer_create();
|
|
|
|
|
layout->base.y_pos = 0;
|
|
|
|
|
layout->base.paged = 0;
|
|
|
|
|
layout->base.unread_msg = 0;
|
|
|
|
|
scrollok(layout->base.win, TRUE);
|
|
|
|
|
layout->subwin = NULL;
|
|
|
|
|
@@ -150,7 +168,7 @@ win_create_console(void)
|
|
|
|
|
{
|
|
|
|
|
ProfConsoleWin* new_win = malloc(sizeof(ProfConsoleWin));
|
|
|
|
|
new_win->window.type = WIN_CONSOLE;
|
|
|
|
|
new_win->window.scroll_state = WIN_SCROLL_INNER;
|
|
|
|
|
new_win->window.scroll_edges = (ScrollEdges){ FALSE, FALSE };
|
|
|
|
|
new_win->window.layout = _win_create_split_layout();
|
|
|
|
|
|
|
|
|
|
return &new_win->window;
|
|
|
|
|
@@ -162,7 +180,7 @@ win_create_chat(const char* const barejid)
|
|
|
|
|
assert(barejid != NULL);
|
|
|
|
|
ProfChatWin* new_win = malloc(sizeof(ProfChatWin));
|
|
|
|
|
new_win->window.type = WIN_CHAT;
|
|
|
|
|
new_win->window.scroll_state = WIN_SCROLL_INNER;
|
|
|
|
|
new_win->window.scroll_edges = (ScrollEdges){ FALSE, FALSE };
|
|
|
|
|
new_win->window.layout = _win_create_simple_layout();
|
|
|
|
|
|
|
|
|
|
new_win->barejid = strdup(barejid);
|
|
|
|
|
@@ -196,7 +214,7 @@ win_create_muc(const char* const roomjid)
|
|
|
|
|
int cols = getmaxx(stdscr);
|
|
|
|
|
|
|
|
|
|
new_win->window.type = WIN_MUC;
|
|
|
|
|
new_win->window.scroll_state = WIN_SCROLL_INNER;
|
|
|
|
|
new_win->window.scroll_edges = (ScrollEdges){ FALSE, FALSE };
|
|
|
|
|
ProfLayoutSplit* layout = malloc(sizeof(ProfLayoutSplit));
|
|
|
|
|
layout->base.type = LAYOUT_SPLIT;
|
|
|
|
|
|
|
|
|
|
@@ -215,7 +233,6 @@ win_create_muc(const char* const roomjid)
|
|
|
|
|
layout->memcheck = LAYOUT_SPLIT_MEMCHECK;
|
|
|
|
|
layout->base.buffer = buffer_create();
|
|
|
|
|
layout->base.y_pos = 0;
|
|
|
|
|
layout->base.paged = 0;
|
|
|
|
|
layout->base.unread_msg = 0;
|
|
|
|
|
scrollok(layout->base.win, TRUE);
|
|
|
|
|
new_win->window.layout = (ProfLayout*)layout;
|
|
|
|
|
@@ -254,7 +271,7 @@ win_create_config(const char* const roomjid, DataForm* form, ProfConfWinCallback
|
|
|
|
|
assert(form != NULL);
|
|
|
|
|
ProfConfWin* new_win = malloc(sizeof(ProfConfWin));
|
|
|
|
|
new_win->window.type = WIN_CONFIG;
|
|
|
|
|
new_win->window.scroll_state = WIN_SCROLL_INNER;
|
|
|
|
|
new_win->window.scroll_edges = (ScrollEdges){ FALSE, FALSE };
|
|
|
|
|
new_win->window.layout = _win_create_simple_layout();
|
|
|
|
|
new_win->roomjid = strdup(roomjid);
|
|
|
|
|
new_win->form = form;
|
|
|
|
|
@@ -273,7 +290,7 @@ win_create_private(const char* const fulljid)
|
|
|
|
|
assert(fulljid != NULL);
|
|
|
|
|
ProfPrivateWin* new_win = malloc(sizeof(ProfPrivateWin));
|
|
|
|
|
new_win->window.type = WIN_PRIVATE;
|
|
|
|
|
new_win->window.scroll_state = WIN_SCROLL_INNER;
|
|
|
|
|
new_win->window.scroll_edges = (ScrollEdges){ FALSE, FALSE };
|
|
|
|
|
new_win->window.layout = _win_create_simple_layout();
|
|
|
|
|
new_win->fulljid = strdup(fulljid);
|
|
|
|
|
new_win->unread = 0;
|
|
|
|
|
@@ -290,7 +307,7 @@ win_create_xmlconsole(void)
|
|
|
|
|
{
|
|
|
|
|
ProfXMLWin* new_win = malloc(sizeof(ProfXMLWin));
|
|
|
|
|
new_win->window.type = WIN_XML;
|
|
|
|
|
new_win->window.scroll_state = WIN_SCROLL_INNER;
|
|
|
|
|
new_win->window.scroll_edges = (ScrollEdges){ FALSE, FALSE };
|
|
|
|
|
new_win->window.layout = _win_create_simple_layout();
|
|
|
|
|
|
|
|
|
|
new_win->memcheck = PROFXMLWIN_MEMCHECK;
|
|
|
|
|
@@ -305,7 +322,7 @@ win_create_plugin(const char* const plugin_name, const char* const tag)
|
|
|
|
|
assert(tag != NULL);
|
|
|
|
|
ProfPluginWin* new_win = malloc(sizeof(ProfPluginWin));
|
|
|
|
|
new_win->window.type = WIN_PLUGIN;
|
|
|
|
|
new_win->window.scroll_state = WIN_SCROLL_INNER;
|
|
|
|
|
new_win->window.scroll_edges = (ScrollEdges){ FALSE, FALSE };
|
|
|
|
|
new_win->window.layout = _win_create_simple_layout();
|
|
|
|
|
|
|
|
|
|
new_win->tag = strdup(tag);
|
|
|
|
|
@@ -322,7 +339,7 @@ win_create_vcard(vCard* vcard)
|
|
|
|
|
assert(vcard != NULL);
|
|
|
|
|
ProfVcardWin* new_win = malloc(sizeof(ProfVcardWin));
|
|
|
|
|
new_win->window.type = WIN_VCARD;
|
|
|
|
|
new_win->window.scroll_state = WIN_SCROLL_INNER;
|
|
|
|
|
new_win->window.scroll_edges = (ScrollEdges){ FALSE, FALSE };
|
|
|
|
|
new_win->window.layout = _win_create_simple_layout();
|
|
|
|
|
|
|
|
|
|
new_win->vcard = vcard;
|
|
|
|
|
@@ -336,7 +353,7 @@ win_create_ai(AISession* session)
|
|
|
|
|
{
|
|
|
|
|
ProfAiWin* new_win = g_new0(ProfAiWin, 1);
|
|
|
|
|
new_win->window.type = WIN_AI;
|
|
|
|
|
new_win->window.scroll_state = WIN_SCROLL_INNER;
|
|
|
|
|
new_win->window.scroll_edges = (ScrollEdges){ FALSE, FALSE };
|
|
|
|
|
new_win->window.layout = _win_create_simple_layout();
|
|
|
|
|
|
|
|
|
|
new_win->message_buffer = buffer_create();
|
|
|
|
|
@@ -722,11 +739,13 @@ win_page_up(ProfWin* window, int scroll_size)
|
|
|
|
|
int* page_start = &(window->layout->y_pos);
|
|
|
|
|
int page_start_initial = *page_start;
|
|
|
|
|
// Size of the visible chat page
|
|
|
|
|
int page_space = getmaxy(stdscr) - 4;
|
|
|
|
|
int page_space = win_page_space();
|
|
|
|
|
if (scroll_size == 0)
|
|
|
|
|
scroll_size = page_space;
|
|
|
|
|
win_scroll_state_t* scroll_state = &window->scroll_state;
|
|
|
|
|
*scroll_state = (*scroll_state == WIN_SCROLL_REACHED_BOTTOM) ? WIN_SCROLL_INNER : *scroll_state;
|
|
|
|
|
ScrollEdges* edges = &window->scroll_edges;
|
|
|
|
|
// Going up means the bottom of the DB may have new content again; allow
|
|
|
|
|
// a re-fetch on the next page-down.
|
|
|
|
|
edges->db_exhausted_below = FALSE;
|
|
|
|
|
*page_start -= scroll_size;
|
|
|
|
|
gboolean has_scroll_past_buffer = *page_start < 0;
|
|
|
|
|
|
|
|
|
|
@@ -736,16 +755,18 @@ win_page_up(ProfWin* window, int scroll_size)
|
|
|
|
|
|
|
|
|
|
gboolean is_still_fetching_mam = first_entry && (first_entry->theme_item == THEME_ROOMINFO && g_strcmp0(first_entry->message, LOADING_MESSAGE) == 0);
|
|
|
|
|
if (!is_still_fetching_mam) {
|
|
|
|
|
// WIN_SCROLL_REACHED_TOP means that we reached top of DB and there is no need to refetch data from it
|
|
|
|
|
gboolean is_db_offset_jump_needed = false;
|
|
|
|
|
if (*scroll_state != WIN_SCROLL_REACHED_TOP) {
|
|
|
|
|
// db_exhausted_above suppresses further upward DB fetches; once
|
|
|
|
|
// a previous fetch returned empty, there is nothing older.
|
|
|
|
|
gboolean is_db_offset_jump_needed = FALSE;
|
|
|
|
|
if (!edges->db_exhausted_above) {
|
|
|
|
|
db_history_result_t db_response = chatwin_db_history(chatwin, NULL, NULL, TRUE);
|
|
|
|
|
is_db_offset_jump_needed = db_response == DB_RESPONSE_SUCCESS;
|
|
|
|
|
*scroll_state = db_response == DB_RESPONSE_EMPTY ? WIN_SCROLL_REACHED_TOP : WIN_SCROLL_INNER;
|
|
|
|
|
log_debug("Scroll state after DB history fetch: %s", *scroll_state ? "WIN_SCROLL_REACHED_TOP" : "WIN_SCROLL_INNER");
|
|
|
|
|
edges->db_exhausted_above = (db_response == DB_RESPONSE_EMPTY);
|
|
|
|
|
log_debug("Scroll edges after DB history fetch: above=%d below=%d",
|
|
|
|
|
edges->db_exhausted_above, edges->db_exhausted_below);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (*scroll_state == WIN_SCROLL_REACHED_TOP) {
|
|
|
|
|
if (edges->db_exhausted_above) {
|
|
|
|
|
*page_start = 0;
|
|
|
|
|
if (prefs_get_boolean(PREF_MAM)) {
|
|
|
|
|
win_print_loading_history(window);
|
|
|
|
|
@@ -777,9 +798,7 @@ win_page_up(ProfWin* window, int scroll_size)
|
|
|
|
|
win_update_virtual(window);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update scrolling state
|
|
|
|
|
int total_rows = getcury(window->layout->win);
|
|
|
|
|
window->layout->paged = (total_rows) - *page_start > page_space;
|
|
|
|
|
// paged is derived on demand from y_pos / total_rows; nothing to set here.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
@@ -788,13 +807,15 @@ win_page_down(ProfWin* window, int scroll_size)
|
|
|
|
|
int total_rows = getcury(window->layout->win);
|
|
|
|
|
int total_rows_with_unread = total_rows + window->layout->unread_msg;
|
|
|
|
|
int* page_start = &(window->layout->y_pos);
|
|
|
|
|
int page_space = getmaxy(stdscr) - 4;
|
|
|
|
|
int page_space = win_page_space();
|
|
|
|
|
int page_start_initial = *page_start;
|
|
|
|
|
|
|
|
|
|
if (scroll_size == 0)
|
|
|
|
|
scroll_size = page_space;
|
|
|
|
|
win_scroll_state_t* scroll_state = &window->scroll_state;
|
|
|
|
|
*scroll_state = (*scroll_state == WIN_SCROLL_REACHED_TOP) ? WIN_SCROLL_INNER : *scroll_state;
|
|
|
|
|
ScrollEdges* edges = &window->scroll_edges;
|
|
|
|
|
// Going down means the top of the DB may have new content again; allow
|
|
|
|
|
// a re-fetch on the next page-up (e.g. MAM late delivery).
|
|
|
|
|
edges->db_exhausted_above = FALSE;
|
|
|
|
|
|
|
|
|
|
*page_start += scroll_size;
|
|
|
|
|
|
|
|
|
|
@@ -805,7 +826,7 @@ win_page_down(ProfWin* window, int scroll_size)
|
|
|
|
|
|
|
|
|
|
if ((past_bottom || at_page_space_and_past_unread) && is_chat) {
|
|
|
|
|
int bf_size = buffer_size(window->layout->buffer);
|
|
|
|
|
if (bf_size > 0 && *scroll_state != WIN_SCROLL_REACHED_BOTTOM) {
|
|
|
|
|
if (bf_size > 0 && !edges->db_exhausted_below) {
|
|
|
|
|
// How many lines are left until end of the screen
|
|
|
|
|
int current_offset = total_rows - *page_start;
|
|
|
|
|
GDateTime* now = g_date_time_new_now_local();
|
|
|
|
|
@@ -814,7 +835,7 @@ win_page_down(ProfWin* window, int scroll_size)
|
|
|
|
|
auto_gchar gchar* end_date = g_date_time_format_iso8601(now);
|
|
|
|
|
db_history_result_t db_response = chatwin_db_history((ProfChatWin*)window, start, end_date, FALSE);
|
|
|
|
|
if (db_response == DB_RESPONSE_EMPTY)
|
|
|
|
|
*scroll_state = WIN_SCROLL_REACHED_BOTTOM;
|
|
|
|
|
edges->db_exhausted_below = TRUE;
|
|
|
|
|
|
|
|
|
|
// similar to page_up (see explanation there)
|
|
|
|
|
// Get offset of the message that was the latest prior to loading messages from the DB
|
|
|
|
|
@@ -826,34 +847,22 @@ win_page_down(ProfWin* window, int scroll_size)
|
|
|
|
|
|
|
|
|
|
total_rows = getcury(window->layout->win);
|
|
|
|
|
|
|
|
|
|
// near the end, but will display only part of the page space, move a bit back to show full page
|
|
|
|
|
if ((total_rows - *page_start) < page_space) {
|
|
|
|
|
*page_start = total_rows - page_space;
|
|
|
|
|
// went past end, show last page
|
|
|
|
|
} else if (*page_start >= total_rows) {
|
|
|
|
|
*page_start = total_rows - page_space - 1;
|
|
|
|
|
// Clamp to the last full page so the viewport never goes past the buffer
|
|
|
|
|
// and always shows a full page when one is available.
|
|
|
|
|
int last_page_start = total_rows > page_space ? total_rows - page_space : 0;
|
|
|
|
|
if (*page_start > last_page_start) {
|
|
|
|
|
*page_start = last_page_start;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
window->layout->paged = 1;
|
|
|
|
|
|
|
|
|
|
// update only if position has changed
|
|
|
|
|
if ((page_start_initial != *page_start) || window->layout->unread_msg) {
|
|
|
|
|
win_update_virtual(window);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Switch off page if no messages left to read.
|
|
|
|
|
* TODO: update buffer end handling to check messages just after last entry.
|
|
|
|
|
*/
|
|
|
|
|
if (*scroll_state == WIN_SCROLL_REACHED_BOTTOM) {
|
|
|
|
|
window->layout->paged = 0;
|
|
|
|
|
window->layout->unread_msg = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Non-chat windows have no DB to fetch from, so WIN_SCROLL_REACHED_BOTTOM
|
|
|
|
|
// never fires; reset paged once the buffer's last line is on screen.
|
|
|
|
|
if (window->type != WIN_CHAT
|
|
|
|
|
&& (total_rows - *page_start) <= page_space + 1) {
|
|
|
|
|
window->layout->paged = 0;
|
|
|
|
|
// Once the user is back at the buffer's last page the unread badge no
|
|
|
|
|
// longer reflects anything they have not seen — clear it. paged itself
|
|
|
|
|
// is derived from y_pos so there is no flag to flip.
|
|
|
|
|
if (*page_start >= last_page_start) {
|
|
|
|
|
window->layout->unread_msg = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -862,8 +871,7 @@ void
|
|
|
|
|
win_sub_page_down(ProfWin* window)
|
|
|
|
|
{
|
|
|
|
|
if (window->layout->type == LAYOUT_SPLIT) {
|
|
|
|
|
int rows = getmaxy(stdscr);
|
|
|
|
|
int page_space = rows - 4;
|
|
|
|
|
int page_space = win_page_space();
|
|
|
|
|
ProfLayoutSplit* split_layout = (ProfLayoutSplit*)window->layout;
|
|
|
|
|
int sub_y = getcury(split_layout->subwin);
|
|
|
|
|
int* sub_y_pos = &(split_layout->sub_y_pos);
|
|
|
|
|
@@ -886,8 +894,7 @@ void
|
|
|
|
|
win_sub_page_up(ProfWin* window)
|
|
|
|
|
{
|
|
|
|
|
if (window->layout->type == LAYOUT_SPLIT) {
|
|
|
|
|
int rows = getmaxy(stdscr);
|
|
|
|
|
int page_space = rows - 4;
|
|
|
|
|
int page_space = win_page_space();
|
|
|
|
|
ProfLayoutSplit* split_layout = (ProfLayoutSplit*)window->layout;
|
|
|
|
|
int* sub_y_pos = &(split_layout->sub_y_pos);
|
|
|
|
|
|
|
|
|
|
@@ -914,7 +921,6 @@ win_clear(ProfWin* window)
|
|
|
|
|
int y = getcury(window->layout->win);
|
|
|
|
|
int* page_start = &(window->layout->y_pos);
|
|
|
|
|
*page_start = y;
|
|
|
|
|
window->layout->paged = 1;
|
|
|
|
|
window->layout->unread_msg = 0;
|
|
|
|
|
win_update_virtual(window);
|
|
|
|
|
}
|
|
|
|
|
@@ -1024,14 +1030,12 @@ win_refresh_with_subwin(ProfWin* window)
|
|
|
|
|
void
|
|
|
|
|
win_move_to_end(ProfWin* window)
|
|
|
|
|
{
|
|
|
|
|
window->layout->paged = 0;
|
|
|
|
|
window->layout->unread_msg = 0;
|
|
|
|
|
|
|
|
|
|
int rows = getmaxy(stdscr);
|
|
|
|
|
int y = getcury(window->layout->win);
|
|
|
|
|
int size = rows - 3;
|
|
|
|
|
int page_space = win_page_space();
|
|
|
|
|
|
|
|
|
|
window->layout->y_pos = y - (size - 1);
|
|
|
|
|
window->layout->y_pos = y - page_space;
|
|
|
|
|
if (window->layout->y_pos < 0) {
|
|
|
|
|
window->layout->y_pos = 0;
|
|
|
|
|
}
|
|
|
|
|
@@ -1808,12 +1812,13 @@ win_newline(ProfWin* window)
|
|
|
|
|
static void
|
|
|
|
|
_win_printf(ProfWin* window, const char* show_char, int pad_indent, GDateTime* timestamp, int flags, theme_item_t theme_item, const char* const display_from, const char* const from_jid, const char* const message_id, const char* const message, ...)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
/* Prevent printing and buffer update when user is viewing message history [SCROLLING]*/
|
|
|
|
|
if (window->layout->paged && wins_is_current(window)) {
|
|
|
|
|
window->layout->unread_msg++;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
/* Append the message to the curses pad and the in-memory buffer
|
|
|
|
|
* unconditionally. When the user is scrolled up (paged), the new line
|
|
|
|
|
* lands on rows below the visible viewport so it does not disturb what
|
|
|
|
|
* they are reading; they encounter it naturally on page-down. The
|
|
|
|
|
* unread badge is bumped so the title bar still reflects new activity.
|
|
|
|
|
*/
|
|
|
|
|
const gboolean is_paged_now = win_is_paged(window) && wins_is_current(window);
|
|
|
|
|
|
|
|
|
|
if (timestamp == NULL) {
|
|
|
|
|
timestamp = g_date_time_new_now_local();
|
|
|
|
|
@@ -1830,6 +1835,10 @@ _win_printf(ProfWin* window, const char* show_char, int pad_indent, GDateTime* t
|
|
|
|
|
_win_print_internal(window, show_char, pad_indent, timestamp, flags, theme_item, display_from, msg, NULL);
|
|
|
|
|
buffer_append(window->layout->buffer, show_char, pad_indent, timestamp, flags, theme_item, display_from, from_jid, msg, NULL, message_id, y_start_pos, getcury(window->layout->win));
|
|
|
|
|
|
|
|
|
|
if (is_paged_now) {
|
|
|
|
|
window->layout->unread_msg++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inp_nonblocking(TRUE);
|
|
|
|
|
g_date_time_unref(timestamp);
|
|
|
|
|
|
|
|
|
|
|