mirror of
https://github.com/ZoiteChat/zoitechat.git
synced 2026-07-02 01:59:24 +00:00
Add reply cache, composer bar, and nick-menu replies
This commit is contained in:
@@ -163,6 +163,8 @@ typedef struct session_gui
|
||||
*op_xpm, /* icon to the left of nickname */
|
||||
*namelistinfo, /* label above userlist */
|
||||
*input_box,
|
||||
*reply_box,
|
||||
*reply_label,
|
||||
*flag_wid[NUM_FLAG_WIDS], /* channelmode buttons */
|
||||
*limit_entry, /* +l */
|
||||
*key_entry; /* +k */
|
||||
|
||||
@@ -173,6 +173,7 @@ enum
|
||||
#define TAG_UTIL 1 /* dcc, notify, chanlist */
|
||||
|
||||
static void mg_apply_emoji_fallback_widget (GtkWidget *widget);
|
||||
static void mg_reply_show_child (GtkWidget *widget, gpointer data);
|
||||
|
||||
#define MG_CONFIG_SAVE_DEBOUNCE_MS 250
|
||||
|
||||
@@ -731,6 +732,164 @@ mg_inputbox_focus (GtkWidget *widget, GdkEventFocus *event, session_gui *gui)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
mg_client_tag_allowed (server *serv, const char *tag)
|
||||
{
|
||||
char **deny;
|
||||
int i;
|
||||
|
||||
if (!serv->have_message_tags)
|
||||
return FALSE;
|
||||
|
||||
if (!serv->clienttagdeny || !*serv->clienttagdeny)
|
||||
return TRUE;
|
||||
|
||||
deny = g_strsplit (serv->clienttagdeny, ",", 0);
|
||||
for (i = 0; deny[i]; i++)
|
||||
{
|
||||
if (!strcmp (deny[i], "*") || !strcmp (deny[i], tag) || (deny[i][0] == '+' && !strcmp (deny[i] + 1, tag)))
|
||||
{
|
||||
g_strfreev (deny);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
g_strfreev (deny);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
mg_send_typing (session *sess, const char *state)
|
||||
{
|
||||
char tags[32];
|
||||
|
||||
if (!sess || !sess->server->connected || !mg_client_tag_allowed (sess->server, "typing") || !sess->channel[0])
|
||||
return;
|
||||
|
||||
if (sess->type != SESS_CHANNEL && sess->type != SESS_DIALOG)
|
||||
return;
|
||||
|
||||
g_snprintf (tags, sizeof (tags), "+typing=%s", state);
|
||||
sess->server->p_tagmsg (sess->server, tags, sess->channel);
|
||||
}
|
||||
|
||||
static int
|
||||
mg_typing_pause_cb (session *sess)
|
||||
{
|
||||
sess->typing_timeout_tag = 0;
|
||||
if (sess->typing_status == 1)
|
||||
{
|
||||
mg_send_typing (sess, "paused");
|
||||
sess->typing_status = 2;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
mg_typing_update (session *sess, const char *text)
|
||||
{
|
||||
if (!sess)
|
||||
return;
|
||||
|
||||
if (sess->typing_timeout_tag)
|
||||
{
|
||||
fe_timeout_remove (sess->typing_timeout_tag);
|
||||
sess->typing_timeout_tag = 0;
|
||||
}
|
||||
|
||||
if (!text || !*text || text[0] == prefs.hex_input_command_char[0])
|
||||
{
|
||||
if (sess->typing_status)
|
||||
mg_send_typing (sess, "done");
|
||||
sess->typing_status = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (sess->typing_status != 1)
|
||||
{
|
||||
mg_send_typing (sess, "active");
|
||||
sess->typing_status = 1;
|
||||
}
|
||||
sess->typing_timeout_tag = fe_timeout_add_seconds (6, mg_typing_pause_cb, sess);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
mg_reply_show_child (GtkWidget *widget, gpointer data)
|
||||
{
|
||||
gtk_widget_show (widget);
|
||||
}
|
||||
|
||||
void
|
||||
mg_reply_update (session *sess)
|
||||
{
|
||||
char *nick;
|
||||
char *text;
|
||||
char *markup;
|
||||
|
||||
if (!sess || !sess->gui || !sess->gui->reply_box || !sess->gui->reply_label)
|
||||
return;
|
||||
|
||||
if (!sess->reply_msgid)
|
||||
{
|
||||
gtk_widget_hide (sess->gui->reply_box);
|
||||
return;
|
||||
}
|
||||
|
||||
nick = g_markup_escape_text (sess->reply_nick ? sess->reply_nick : _("message"), -1);
|
||||
text = g_markup_escape_text (sess->reply_text ? sess->reply_text : _("Original message unavailable"), -1);
|
||||
markup = g_strdup_printf ("<span foreground='#7d8790'>↪ Replying to <b>%s</b> · %.160s</span>", nick, text);
|
||||
gtk_label_set_markup (GTK_LABEL (sess->gui->reply_label), markup);
|
||||
gtk_container_foreach (GTK_CONTAINER (sess->gui->reply_box), mg_reply_show_child, NULL);
|
||||
gtk_widget_show (sess->gui->reply_box);
|
||||
g_free (markup);
|
||||
g_free (text);
|
||||
g_free (nick);
|
||||
}
|
||||
|
||||
static void
|
||||
mg_reply_cancel_cb (GtkWidget *wid, session *sess)
|
||||
{
|
||||
reply_state_clear (sess);
|
||||
mg_reply_update (sess);
|
||||
}
|
||||
|
||||
static void
|
||||
mg_send_reply_or_text (session *sess, char *cmd)
|
||||
{
|
||||
char *reply_cmd;
|
||||
|
||||
if (!sess->reply_msgid || cmd[0] == prefs.hex_input_command_char[0])
|
||||
{
|
||||
handle_multiline (sess, cmd, TRUE, FALSE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sess->server->connected || !mg_client_tag_allowed (sess->server, "reply"))
|
||||
{
|
||||
PrintText (sess, _("Replies are not supported on this server. Sending normally.\n"));
|
||||
reply_state_clear (sess);
|
||||
mg_reply_update (sess);
|
||||
handle_multiline (sess, cmd, TRUE, FALSE);
|
||||
return;
|
||||
}
|
||||
|
||||
reply_cmd = g_strdup_printf ("%cREPLY %s %s", prefs.hex_input_command_char[0], sess->reply_msgid, cmd);
|
||||
handle_multiline (sess, reply_cmd, TRUE, FALSE);
|
||||
g_free (reply_cmd);
|
||||
reply_state_clear (sess);
|
||||
mg_reply_update (sess);
|
||||
}
|
||||
|
||||
static void
|
||||
mg_inputbox_changed (GtkEditable *editable, session_gui *gui)
|
||||
{
|
||||
key_check_replace_on_change (editable, NULL);
|
||||
if (current_sess && current_sess->gui == gui)
|
||||
mg_typing_update (current_sess, gtk_entry_get_text (GTK_ENTRY (editable)));
|
||||
}
|
||||
|
||||
void
|
||||
mg_inputbox_cb (GtkWidget *igad, session_gui *gui)
|
||||
{
|
||||
@@ -772,7 +931,7 @@ mg_inputbox_cb (GtkWidget *igad, session_gui *gui)
|
||||
}
|
||||
|
||||
if (sess)
|
||||
handle_multiline (sess, cmd, TRUE, FALSE);
|
||||
mg_send_reply_or_text (sess, cmd);
|
||||
|
||||
g_free (cmd);
|
||||
}
|
||||
@@ -4541,6 +4700,20 @@ mg_create_entry (session *sess, GtkWidget *box)
|
||||
};
|
||||
const char *emoji_fallback_icon_name;
|
||||
|
||||
gui->reply_box = mg_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 6);
|
||||
gtk_widget_set_name (gui->reply_box, "zoitechat-replybar");
|
||||
gtk_widget_set_no_show_all (gui->reply_box, TRUE);
|
||||
gtk_box_pack_start (GTK_BOX (box), gui->reply_box, 0, 0, 0);
|
||||
gui->reply_label = gtk_label_new ("");
|
||||
gtk_label_set_ellipsize (GTK_LABEL (gui->reply_label), PANGO_ELLIPSIZE_END);
|
||||
gtk_box_pack_start (GTK_BOX (gui->reply_box), gui->reply_label, TRUE, TRUE, 8);
|
||||
but = gtk_button_new_with_label ("×");
|
||||
gtk_button_set_relief (GTK_BUTTON (but), GTK_RELIEF_NONE);
|
||||
gtk_widget_set_can_focus (but, FALSE);
|
||||
gtk_box_pack_start (GTK_BOX (gui->reply_box), but, FALSE, FALSE, 0);
|
||||
g_signal_connect (G_OBJECT (but), "clicked", G_CALLBACK (mg_reply_cancel_cb), sess);
|
||||
gtk_widget_hide (gui->reply_box);
|
||||
|
||||
hbox = mg_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 0);
|
||||
gtk_box_pack_start (GTK_BOX (box), hbox, 0, 0, 0);
|
||||
|
||||
@@ -4562,7 +4735,7 @@ mg_create_entry (session *sess, GtkWidget *box)
|
||||
g_signal_connect (G_OBJECT (entry), "activate",
|
||||
G_CALLBACK (mg_inputbox_cb), gui);
|
||||
g_signal_connect (G_OBJECT (entry), "changed",
|
||||
G_CALLBACK (key_check_replace_on_change), NULL);
|
||||
G_CALLBACK (mg_inputbox_changed), gui);
|
||||
gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
|
||||
|
||||
gtk_widget_set_name (entry, "zoitechat-inputbox");
|
||||
@@ -5194,6 +5367,12 @@ fe_set_channel (session *sess)
|
||||
chan_rename (sess->res->tab, sess->channel, prefs.hex_gui_tab_trunc);
|
||||
}
|
||||
|
||||
void
|
||||
fe_set_typing (session *sess, const char *nick, const char *state)
|
||||
{
|
||||
fe_userlist_set_typing (sess, nick, state);
|
||||
}
|
||||
|
||||
void
|
||||
mg_changui_new (session *sess, restore_gui *res, int tab, int focus)
|
||||
{
|
||||
|
||||
@@ -48,6 +48,7 @@ void mg_dnd_drop_file (session *sess, char *target, char *uri);
|
||||
void mg_change_layout (int type);
|
||||
void mg_update_meters (session_gui *);
|
||||
void mg_inputbox_cb (GtkWidget *igad, session_gui *gui);
|
||||
void mg_reply_update (session *sess);
|
||||
void mg_create_icon_item (char *label, char *stock, GtkWidget *menu, void *callback, void *userdata);
|
||||
GtkWidget *mg_submenu (GtkWidget *menu, char *text);
|
||||
/* DND */
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include "../common/zoitechatc.h"
|
||||
#include "../common/cfgfiles.h"
|
||||
#include "../common/outbound.h"
|
||||
#include "../common/inbound.h"
|
||||
#include "../common/ignore.h"
|
||||
#include "../common/fe.h"
|
||||
#include "../common/server.h"
|
||||
@@ -784,6 +785,25 @@ fe_userlist_update (session *sess, struct User *user)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
menu_reply_to_latest_cb (GtkWidget *wid, gpointer data)
|
||||
{
|
||||
reply_item *item;
|
||||
|
||||
item = reply_cache_latest_from (current_sess, str_copy);
|
||||
if (!item)
|
||||
{
|
||||
PrintText (current_sess, _("No recent message to reply to.\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
reply_state_set (current_sess, item->msgid, current_sess->channel, item->nick, item->text);
|
||||
mg_reply_update (current_sess);
|
||||
if (current_sess->gui && current_sess->gui->input_box)
|
||||
gtk_widget_grab_focus (current_sess->gui->input_box);
|
||||
}
|
||||
|
||||
void
|
||||
menu_nickmenu (session *sess, GdkEventButton *event, char *nick, int num_sel)
|
||||
{
|
||||
@@ -827,6 +847,12 @@ menu_nickmenu (session *sess, GdkEventButton *event, char *nick, int num_sel)
|
||||
else
|
||||
menu_create (menu, popup_list, str_copy, FALSE);
|
||||
|
||||
if (num_sel <= 1)
|
||||
{
|
||||
menu_quick_item_with_callback (menu_reply_to_latest_cb, _("Reply"), menu, 0);
|
||||
menu_quick_item (0, 0, menu, XCMENU_SHADED, 0, 0);
|
||||
}
|
||||
|
||||
if (num_sel == 0) /* xtext click */
|
||||
menu_add_plugin_items (menu, "\x5$NICK", str_copy);
|
||||
else /* userlist treeview click */
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "fe-gtk.h"
|
||||
|
||||
@@ -53,6 +54,36 @@ enum
|
||||
|
||||
static void userlist_store_color (GtkListStore *store, GtkTreeIter *iter, ThemeSemanticToken token, gboolean has_token);
|
||||
|
||||
static const char *
|
||||
userlist_typing_suffix (session *sess, struct User *user)
|
||||
{
|
||||
static const char *active[] = { " [✎]", " [✎.]", " [✎..]" };
|
||||
|
||||
if (!user || !user->typing)
|
||||
return "";
|
||||
|
||||
if (user->typing == 2)
|
||||
return " [✎…]";
|
||||
|
||||
return active[sess->typing_animation_frame % G_N_ELEMENTS (active)];
|
||||
}
|
||||
|
||||
static char *
|
||||
userlist_nick_markup (session *sess, struct User *user)
|
||||
{
|
||||
char *nick = g_markup_escape_text (user->nick, -1);
|
||||
const char *typing = userlist_typing_suffix (sess, user);
|
||||
|
||||
if (*typing)
|
||||
{
|
||||
char *marked = g_strdup_printf ("%s%s", nick, typing);
|
||||
g_free (nick);
|
||||
return marked;
|
||||
}
|
||||
|
||||
return nick;
|
||||
}
|
||||
|
||||
static const char *
|
||||
userlist_prefix_color (char prefix)
|
||||
{
|
||||
@@ -463,6 +494,87 @@ fe_userlist_remove (session *sess, struct User *user)
|
||||
return sel;
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
userlist_typing_tick (session *sess)
|
||||
{
|
||||
GtkTreeModel *model;
|
||||
GtkTreeIter iter;
|
||||
gboolean valid;
|
||||
gboolean keep = FALSE;
|
||||
time_t now = time (NULL);
|
||||
|
||||
if (!sess || !sess->res || !sess->res->user_model)
|
||||
return FALSE;
|
||||
|
||||
sess->typing_animation_frame++;
|
||||
model = GTK_TREE_MODEL (sess->res->user_model);
|
||||
valid = gtk_tree_model_get_iter_first (model, &iter);
|
||||
|
||||
while (valid)
|
||||
{
|
||||
struct User *user = NULL;
|
||||
|
||||
gtk_tree_model_get (model, &iter, COL_USER, &user, -1);
|
||||
if (user && user->typing)
|
||||
{
|
||||
char *nick;
|
||||
|
||||
if ((user->typing == 1 && now - user->typing_time >= 6) || (user->typing == 2 && now - user->typing_time >= 30))
|
||||
user->typing = 0;
|
||||
|
||||
nick = userlist_nick_markup (sess, user);
|
||||
gtk_list_store_set (sess->res->user_model, &iter, COL_NICK, nick, -1);
|
||||
g_free (nick);
|
||||
if (user->typing)
|
||||
keep = TRUE;
|
||||
}
|
||||
valid = gtk_tree_model_iter_next (model, &iter);
|
||||
}
|
||||
|
||||
if (!keep)
|
||||
{
|
||||
sess->typing_animation_tag = 0;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void
|
||||
fe_userlist_set_typing (session *sess, const char *nick, const char *state)
|
||||
{
|
||||
struct User *user;
|
||||
GtkTreeIter *iter;
|
||||
int sel;
|
||||
|
||||
if (!sess || !nick || !sess->res || !sess->res->user_model)
|
||||
return;
|
||||
|
||||
user = userlist_find (sess, nick);
|
||||
if (!user)
|
||||
return;
|
||||
|
||||
if (!strcmp (state, "active"))
|
||||
user->typing = 1;
|
||||
else if (!strcmp (state, "paused"))
|
||||
user->typing = 2;
|
||||
else
|
||||
user->typing = 0;
|
||||
user->typing_time = time (NULL);
|
||||
|
||||
iter = find_row (sess, GTK_TREE_VIEW (sess->gui->user_tree), GTK_TREE_MODEL (sess->res->user_model), user, &sel);
|
||||
if (iter)
|
||||
{
|
||||
char *nick = userlist_nick_markup (sess, user);
|
||||
gtk_list_store_set (sess->res->user_model, iter, COL_NICK, nick, -1);
|
||||
g_free (nick);
|
||||
}
|
||||
|
||||
if (user->typing && !sess->typing_animation_tag)
|
||||
sess->typing_animation_tag = fe_timeout_add (350, userlist_typing_tick, sess);
|
||||
}
|
||||
|
||||
void
|
||||
fe_userlist_rehash (session *sess, struct User *user)
|
||||
{
|
||||
@@ -493,9 +605,14 @@ fe_userlist_rehash (session *sess, struct User *user)
|
||||
}
|
||||
}
|
||||
|
||||
gtk_list_store_set (GTK_LIST_STORE (sess->res->user_model), iter,
|
||||
{
|
||||
char *nick = userlist_nick_markup (sess, user);
|
||||
gtk_list_store_set (GTK_LIST_STORE (sess->res->user_model), iter,
|
||||
COL_NICK, nick,
|
||||
COL_HOST, user->hostname,
|
||||
-1);
|
||||
g_free (nick);
|
||||
}
|
||||
userlist_store_color (GTK_LIST_STORE (sess->res->user_model), iter, nick_token, have_nick_token);
|
||||
}
|
||||
|
||||
@@ -506,7 +623,6 @@ fe_userlist_insert (session *sess, struct User *newuser, gboolean sel)
|
||||
GdkPixbuf *pix = get_user_icon (sess->server, newuser);
|
||||
GtkTreeIter iter;
|
||||
char *nick;
|
||||
char *nick_escaped;
|
||||
char *prefix = NULL;
|
||||
char *prefix_escaped;
|
||||
char prefix_text[2];
|
||||
@@ -530,8 +646,7 @@ fe_userlist_insert (session *sess, struct User *newuser, gboolean sel)
|
||||
}
|
||||
}
|
||||
|
||||
nick_escaped = g_markup_escape_text (newuser->nick, -1);
|
||||
nick = nick_escaped;
|
||||
nick = userlist_nick_markup (sess, newuser);
|
||||
if (!prefs.hex_gui_ulist_icons)
|
||||
{
|
||||
if (newuser->prefix[0] != '\0' && newuser->prefix[0] != ' ')
|
||||
@@ -559,7 +674,7 @@ fe_userlist_insert (session *sess, struct User *newuser, gboolean sel)
|
||||
userlist_store_color (GTK_LIST_STORE (model), &iter, nick_token, have_nick_token);
|
||||
|
||||
g_free (prefix);
|
||||
g_free (nick_escaped);
|
||||
g_free (nick);
|
||||
|
||||
userlist_row_map_set (sess, model, newuser, &iter);
|
||||
|
||||
@@ -757,6 +872,7 @@ userlist_add_columns (GtkTreeView * treeview)
|
||||
gtk_tree_view_column_pack_start (column, renderer, TRUE);
|
||||
gtk_tree_view_column_add_attribute (column, renderer, "markup", COL_NICK);
|
||||
gtk_tree_view_column_add_attribute (column, renderer, THEME_GTK_FOREGROUND_PROPERTY, COL_GDKCOLOR);
|
||||
|
||||
column = gtk_tree_view_get_column (GTK_TREE_VIEW (treeview), 1);
|
||||
gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
|
||||
gtk_tree_view_column_set_expand (column, TRUE);
|
||||
|
||||
@@ -26,6 +26,7 @@ GtkWidget *userlist_create (GtkWidget *box);
|
||||
GtkListStore *userlist_create_model (session *sess);
|
||||
void userlist_show (session *sess);
|
||||
void userlist_select (session *sess, char *name);
|
||||
void fe_userlist_set_typing (session *sess, const char *nick, const char *state);
|
||||
char **userlist_selection_list (GtkWidget *widget, int *num_ret);
|
||||
GdkPixbuf *get_user_icon (server *serv, struct User *user);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user