diff --git a/src/command/cmd_ac.c b/src/command/cmd_ac.c index b84d4d2e..03c6e404 100644 --- a/src/command/cmd_ac.c +++ b/src/command/cmd_ac.c @@ -471,6 +471,7 @@ cmd_ac_init(void) wins_ac = autocomplete_new(); autocomplete_add(wins_ac, "unread"); + autocomplete_add(wins_ac, "attention"); autocomplete_add(wins_ac, "prune"); autocomplete_add(wins_ac, "swap"); diff --git a/src/command/cmd_defs.c b/src/command/cmd_defs.c index 4ebc3432..0ebcf04f 100644 --- a/src/command/cmd_defs.c +++ b/src/command/cmd_defs.c @@ -973,6 +973,7 @@ static struct cmd_t command_defs[] = { parse_args, 0, 3, NULL, CMD_SUBFUNCS( { "unread", cmd_wins_unread }, + { "attention", cmd_wins_attention }, { "prune", cmd_wins_prune }, { "swap", cmd_wins_swap }) CMD_MAINFUNC(cmd_wins) @@ -981,6 +982,7 @@ static struct cmd_t command_defs[] = { CMD_SYN( "/wins", "/wins unread", + "/wins attention", "/wins prune", "/wins swap ") CMD_DESC( @@ -988,6 +990,7 @@ static struct cmd_t command_defs[] = { "Passing no argument will list all currently active windows and information about their usage.") CMD_ARGS( { "unread", "List windows with unread messages." }, + { "attention", "List windows that have been marked with the attention flag (alt+f). You can toggle between marked windows with alt+m." }, { "prune", "Close all windows with no unread messages." }, { "swap ", "Swap windows, target may be an empty position." }) CMD_NOEXAMPLES diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c index a56f0cfc..901c81a3 100644 --- a/src/command/cmd_funcs.c +++ b/src/command/cmd_funcs.c @@ -1298,6 +1298,13 @@ cmd_wins_unread(ProfWin* window, const char* const command, gchar** args) return TRUE; } +gboolean +cmd_wins_attention(ProfWin* window, const char* const command, gchar** args) +{ + cons_show_wins_attention(); + return TRUE; +} + gboolean cmd_wins_prune(ProfWin* window, const char* const command, gchar** args) { diff --git a/src/command/cmd_funcs.h b/src/command/cmd_funcs.h index 0785963b..aadcb55f 100644 --- a/src/command/cmd_funcs.h +++ b/src/command/cmd_funcs.h @@ -209,6 +209,7 @@ gboolean cmd_otr_sendfile(ProfWin* window, const char* const command, gchar** ar gboolean cmd_wins(ProfWin* window, const char* const command, gchar** args); gboolean cmd_wins_unread(ProfWin* window, const char* const command, gchar** args); +gboolean cmd_wins_attention(ProfWin* window, const char* const command, gchar** args); gboolean cmd_wins_prune(ProfWin* window, const char* const command, gchar** args); gboolean cmd_wins_swap(ProfWin* window, const char* const command, gchar** args); diff --git a/src/ui/console.c b/src/ui/console.c index 3eff47d5..74a7740f 100644 --- a/src/ui/console.c +++ b/src/ui/console.c @@ -506,6 +506,26 @@ cons_show_wins(gboolean unread) cons_alert(NULL); } +void +cons_show_wins_attention() { + ProfWin* console = wins_get_console(); + cons_show(""); + GSList* window_strings = wins_create_summary_attention(); + + GSList* curr = window_strings; + while (curr) { + if (g_strstr_len(curr->data, strlen(curr->data), " unread") > 0) { + win_println(console, THEME_CMD_WINS_UNREAD, "-", "%s", curr->data); + } else { + win_println(console, THEME_DEFAULT, "-", "%s", curr->data); + } + curr = g_slist_next(curr); + } + g_slist_free_full(window_strings, free); + + cons_alert(NULL); +} + void cons_show_room_invites(GList* invites) { diff --git a/src/ui/core.c b/src/ui/core.c index 4e4e39a0..f4ee4a86 100644 --- a/src/ui/core.c +++ b/src/ui/core.c @@ -996,6 +996,20 @@ ui_win_unread(int index) } } +gboolean +ui_win_has_attention(int index) +{ + gboolean ret = FALSE; + + ProfWin* window = wins_get_by_num(index); + if (window) { + ret = win_has_attention(window); + } + + return ret; +} + + char* ui_ask_password(gboolean confirm) { diff --git a/src/ui/inputwin.c b/src/ui/inputwin.c index 74fcfbb4..b2b2ea33 100644 --- a/src/ui/inputwin.c +++ b/src/ui/inputwin.c @@ -125,6 +125,8 @@ static int _inp_rl_win_20_handler(int count, int key); static int _inp_rl_win_prev_handler(int count, int key); static int _inp_rl_win_next_handler(int count, int key); static int _inp_rl_win_next_unread_handler(int count, int key); +static int _inp_rl_win_attention_handler(int count, int key); +static int _inp_rl_win_attention_next_handler(int count, int key); static int _inp_rl_win_pageup_handler(int count, int key); static int _inp_rl_win_pagedown_handler(int count, int key); static int _inp_rl_subwin_pageup_handler(int count, int key); @@ -480,6 +482,8 @@ _inp_rl_startup_hook(void) rl_bind_keyseq("\\e\\e[C", _inp_rl_win_next_handler); rl_bind_keyseq("\\ea", _inp_rl_win_next_unread_handler); + rl_bind_keyseq("\\ef", _inp_rl_win_attention_handler); + rl_bind_keyseq("\\em", _inp_rl_win_attention_next_handler); rl_bind_keyseq("\\e\\e[5~", _inp_rl_subwin_pageup_handler); rl_bind_keyseq("\\e[5;3~", _inp_rl_subwin_pageup_handler); @@ -806,6 +810,31 @@ _inp_rl_win_next_unread_handler(int count, int key) return 0; } +static int +_inp_rl_win_attention_handler(int count, int key) { + ProfWin* current = wins_get_current(); + if ( current ) { + gboolean attention = win_toggle_attention(current); + if (attention) { + win_println(current, THEME_DEFAULT, "!", "Attention flag has been activated"); + } else { + win_println(current, THEME_DEFAULT, "!", "Attention flag has been deactivated"); + } + win_redraw(current); + } + return 0; +} + +static int +_inp_rl_win_attention_next_handler(int count, int key) { + ProfWin* window = wins_get_next_attention(); + if (window) { + ui_focus_win(window); + } + return 0; +} + + static int _inp_rl_win_pageup_handler(int count, int key) { diff --git a/src/ui/titlebar.c b/src/ui/titlebar.c index dee6a7e2..98af2b24 100644 --- a/src/ui/titlebar.c +++ b/src/ui/titlebar.c @@ -67,6 +67,7 @@ static void _show_contact_presence(ProfChatWin* chatwin, int pos, int maxpos); static void _show_privacy(ProfChatWin* chatwin); static void _show_muc_privacy(ProfMucWin* mucwin); static void _show_scrolled(ProfWin* current); +static void _show_attention(ProfWin* current, gboolean attention); void create_title_bar(void) @@ -210,6 +211,7 @@ _title_bar_draw(void) assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK); _show_contact_presence(chatwin, pos, maxrightpos); _show_privacy(chatwin); + _show_attention(current, chatwin->has_attention); _show_scrolled(current); if (typing) { @@ -219,6 +221,7 @@ _title_bar_draw(void) ProfMucWin* mucwin = (ProfMucWin*)current; assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK); _show_muc_privacy(mucwin); + _show_attention(current, mucwin->has_attention); _show_scrolled(current); } @@ -228,6 +231,26 @@ _title_bar_draw(void) inp_put_back(); } +static void +_show_attention(ProfWin* current, gboolean attention) +{ + int bracket_attrs = theme_attrs(THEME_TITLE_BRACKET); + int text_attrs = theme_attrs(THEME_TITLE_TEXT); + + if (attention) { + wprintw(win, " "); + wattron(win, bracket_attrs); + wprintw(win, "["); + wattroff(win, bracket_attrs); + wattron(win, text_attrs); + wprintw(win, "ATTENTION"); + wattroff(win, text_attrs); + wattron(win, bracket_attrs); + wprintw(win, "]"); + wattroff(win, bracket_attrs); + } +} + static void _show_scrolled(ProfWin* current) { diff --git a/src/ui/ui.h b/src/ui/ui.h index 391b906c..f9ef5cff 100644 --- a/src/ui/ui.h +++ b/src/ui/ui.h @@ -75,6 +75,9 @@ int ui_close_all_wins(void); int ui_close_read_wins(void); void ui_close_win(int index); int ui_win_unread(int index); +gboolean ui_win_has_attention(int index); +gboolean win_has_attention(ProfWin* window); +gboolean win_toggle_attention(ProfWin* window); char* ui_ask_password(gboolean confirm); char* ui_get_line(void); char* ui_ask_pgp_passphrase(const char* hint, int prev_fail); @@ -256,6 +259,7 @@ void cons_show_contacts(GSList* list); void cons_show_roster(GSList* list); void cons_show_roster_group(const char* const group, GSList* list); void cons_show_wins(gboolean unread); +void cons_show_wins_attention(); char* cons_get_string(ProfConsoleWin* conswin); void cons_show_status(const char* const barejid); void cons_show_info(PContact pcontact); diff --git a/src/ui/win_types.h b/src/ui/win_types.h index e740b543..f2237a79 100644 --- a/src/ui/win_types.h +++ b/src/ui/win_types.h @@ -176,6 +176,7 @@ typedef struct prof_chat_win_t // For LMC char* last_message; char* last_msg_id; + gboolean has_attention; } ProfChatWin; typedef struct prof_muc_win_t @@ -196,6 +197,7 @@ typedef struct prof_muc_win_t // For LMC char* last_message; char* last_msg_id; + gboolean has_attention; } ProfMucWin; typedef struct prof_conf_win_t ProfConfWin; diff --git a/src/ui/window.c b/src/ui/window.c index b6676774..4fb7d5f7 100644 --- a/src/ui/window.c +++ b/src/ui/window.c @@ -158,7 +158,7 @@ win_create_chat(const char* const barejid) new_win->outgoing_char = NULL; new_win->last_message = NULL; new_win->last_msg_id = NULL; - + new_win->has_attention = FALSE; new_win->memcheck = PROFCHATWIN_MEMCHECK; return &new_win->window; @@ -213,6 +213,7 @@ win_create_muc(const char* const roomjid) new_win->is_omemo = FALSE; new_win->last_message = NULL; new_win->last_msg_id = NULL; + new_win->has_attention = FALSE; new_win->memcheck = PROFMUCWIN_MEMCHECK; @@ -1841,6 +1842,39 @@ win_unread(ProfWin* window) } } +gboolean +win_has_attention(ProfWin* window) +{ + if (window->type == WIN_CHAT) { + ProfChatWin* chatwin = (ProfChatWin*)window; + assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK); + return chatwin->has_attention; + } else if (window->type == WIN_MUC) { + ProfMucWin* mucwin = (ProfMucWin*)window; + assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK); + return mucwin->has_attention; + } + return FALSE; +} + +gboolean +win_toggle_attention(ProfWin* window) +{ + if (window->type == WIN_CHAT) { + ProfChatWin* chatwin = (ProfChatWin*)window; + assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK); + chatwin->has_attention = !chatwin->has_attention; + return chatwin->has_attention; + } else if (window->type == WIN_MUC) { + ProfMucWin* mucwin = (ProfMucWin*)window; + assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK); + mucwin->has_attention = !mucwin->has_attention; + return mucwin->has_attention; + } + return FALSE; +} + + void win_sub_print(WINDOW* win, char* msg, gboolean newline, gboolean wrap, int indent) { diff --git a/src/ui/window_list.c b/src/ui/window_list.c index a90c7141..c17ced50 100644 --- a/src/ui/window_list.c +++ b/src/ui/window_list.c @@ -1102,6 +1102,51 @@ wins_create_summary(gboolean unread) return result; } +GSList* +wins_create_summary_attention() +{ + GSList* result = NULL; + + GList* keys = g_hash_table_get_keys(windows); + keys = g_list_sort(keys, _wins_cmp_num); + GList* curr = keys; + + while (curr) { + ProfWin* window = g_hash_table_lookup(windows, curr->data); + gboolean has_attention = FALSE; + if (window->type == WIN_CHAT) { + ProfChatWin* chatwin = (ProfChatWin*)window; + assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK); + has_attention = chatwin->has_attention; + } else if (window->type == WIN_MUC) { + ProfMucWin* mucwin = (ProfMucWin*)window; + assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK); + has_attention = mucwin->has_attention; + } + if ( has_attention) { + GString* line = g_string_new(""); + + int ui_index = GPOINTER_TO_INT(curr->data); + char* winstring = win_to_string(window); + if (!winstring) { + g_string_free(line, TRUE); + continue; + } + + g_string_append_printf(line, "%d: %s", ui_index, winstring); + free(winstring); + + result = g_slist_append(result, strdup(line->str)); + g_string_free(line, TRUE); + } + curr = g_list_next(curr); + } + + g_list_free(keys); + + return result; +} + char* win_autocomplete(const char* const search_str, gboolean previous, void* context) { @@ -1160,6 +1205,55 @@ wins_get_next_unread(void) return NULL; } +ProfWin* +wins_get_next_attention(void) +{ + // get and sort win nums + GList* values = g_hash_table_get_values(windows); + values = g_list_sort(values, _wins_cmp_num); + GList* curr = values; + + ProfWin* current_window = wins_get_by_num( current); + + // search the current window + while(curr) { + ProfWin* window = curr->data; + if( current_window == window ) { + current_window = window; + curr = g_list_next(curr); + break; + } + curr = g_list_next(curr); + } + + // Start from current window + while ( current_window && curr) { + ProfWin* window = curr->data; + if (win_has_attention(window)) { + g_list_free(values); + return window; + } + curr = g_list_next(curr); + } + // Start from begin + curr = values; + while ( current_window && curr) { + ProfWin* window = curr->data; + if( current_window == window) { + // we are at current again + break; + } + if (win_has_attention(window)) { + g_list_free(values); + return window; + } + curr = g_list_next(curr); + } + + g_list_free(values); + return NULL; +} + void wins_add_urls_ac(const ProfWin* const win, const ProfMessage* const message) { diff --git a/src/ui/window_list.h b/src/ui/window_list.h index e02358f9..06ae6d7e 100644 --- a/src/ui/window_list.h +++ b/src/ui/window_list.h @@ -74,6 +74,7 @@ ProfWin* wins_get_by_string(const char* str); ProfWin* wins_get_next(void); ProfWin* wins_get_previous(void); ProfWin* wins_get_next_unread(void); +ProfWin* wins_get_next_attention(void); int wins_get_num(ProfWin* window); int wins_get_current_num(void); void wins_close_by_num(int i); @@ -87,6 +88,7 @@ void wins_lost_connection(void); void wins_reestablished_connection(void); gboolean wins_tidy(void); GSList* wins_create_summary(gboolean unread); +GSList* wins_create_summary_attention(); void wins_destroy(void); GList* wins_get_nums(void); void wins_swap(int source_win, int target_win); diff --git a/tests/unittests/ui/stub_ui.c b/tests/unittests/ui/stub_ui.c index dc36678b..578ebcda 100644 --- a/tests/unittests/ui/stub_ui.c +++ b/tests/unittests/ui/stub_ui.c @@ -210,6 +210,20 @@ ui_win_unread(int index) return 0; } +gboolean +ui_win_has_attention(int index) { + return FALSE; +} + +gboolean +win_has_attention(ProfWin* window){ + return FALSE; +} + +gboolean +win_toggle_attention(ProfWin* window){ + return FALSE; +} char* ui_ask_password(gboolean confirm) { @@ -828,6 +842,12 @@ void cons_show_wins(gboolean unread) { } + +void +cons_show_wins_attention() +{ +} + void cons_show_status(const char* const barejid) {