#include <gtk/gtk.h>
#include <glade/glade.h>
-#include <libempathy/empathy-session.h>
#include <libempathy/empathy-contact-manager.h>
#include <libempathy/gossip-debug.h>
#include <libempathy/gossip-utils.h>
#include <libempathy/gossip-conf.h>
+#include <libempathy/empathy-marshal.h>
#include "gossip-chat.h"
#include "gossip-chat-window.h"
#define COMPOSING_STOP_TIMEOUT 5
struct _GossipChatPriv {
- EmpathyTpChat *tp_chat;
- GossipChatWindow *window;
-
- GtkTooltips *tooltips;
- guint composing_stop_timeout_id;
- gboolean sensitive;
- gchar *id;
+ EmpathyContactManager *manager;
+ EmpathyTpChat *tp_chat;
+ GossipChatWindow *window;
+ GtkTooltips *tooltips;
+ guint composing_stop_timeout_id;
+ gboolean sensitive;
+ gchar *id;
+ GSList *sent_messages;
+ gint sent_messages_index;
+ GList *compositors;
+ guint scroll_idle_id;
/* Used to automatically shrink a window that has temporarily
* grown due to long input.
*/
- gint padding_height;
- gint default_window_height;
- gint last_input_height;
- gboolean vscroll_visible;
+ gint padding_height;
+ gint default_window_height;
+ gint last_input_height;
+ gboolean vscroll_visible;
};
typedef struct {
static void chat_message_received_cb (EmpathyTpChat *tp_chat,
GossipMessage *message,
GossipChat *chat);
+void chat_sent_message_add (GossipChat *chat,
+ const gchar *str);
+const gchar * chat_sent_message_get_next (GossipChat *chat);
+const gchar * chat_sent_message_get_last (GossipChat *chat);
static gboolean chat_input_key_press_event_cb (GtkWidget *widget,
GdkEventKey *event,
GossipChat *chat);
static void chat_composing_stop (GossipChat *chat);
static void chat_composing_remove_timeout (GossipChat *chat);
static gboolean chat_composing_stop_timeout_cb (GossipChat *chat);
+static void chat_state_changed_cb (EmpathyTpChat *tp_chat,
+ GossipContact *contact,
+ TelepathyChannelChatState state,
+ GossipChat *chat);
+static gboolean chat_scroll_down_idle_func (GossipChat *chat);
enum {
COMPOSING,
LAST_SIGNAL
};
-static guint chat_signals[LAST_SIGNAL] = { 0 };
+static guint signals[LAST_SIGNAL] = { 0 };
G_DEFINE_TYPE (GossipChat, gossip_chat, G_TYPE_OBJECT);
object_class->finalize = chat_finalize;
- chat_signals[COMPOSING] =
+ signals[COMPOSING] =
g_signal_new ("composing",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_LAST,
G_TYPE_NONE,
1, G_TYPE_BOOLEAN);
- chat_signals[NEW_MESSAGE] =
+ signals[NEW_MESSAGE] =
g_signal_new ("new-message",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
- g_cclosure_marshal_VOID__OBJECT,
+ empathy_marshal_VOID__OBJECT_BOOLEAN,
G_TYPE_NONE,
- 1, GOSSIP_TYPE_MESSAGE);
+ 2, GOSSIP_TYPE_MESSAGE, G_TYPE_BOOLEAN);
- chat_signals[NAME_CHANGED] =
+ signals[NAME_CHANGED] =
g_signal_new ("name-changed",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_LAST,
G_TYPE_NONE,
1, G_TYPE_POINTER);
- chat_signals[STATUS_CHANGED] =
+ signals[STATUS_CHANGED] =
g_signal_new ("status-changed",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_LAST,
priv = GET_PRIV (chat);
- priv->tooltips = gtk_tooltips_new ();
-
+ priv->manager = empathy_contact_manager_new ();
+ priv->tooltips = g_object_ref_sink (gtk_tooltips_new ());
priv->default_window_height = -1;
priv->vscroll_visible = FALSE;
priv->sensitive = TRUE;
+ priv->sent_messages = NULL;
+ priv->sent_messages_index = -1;
g_signal_connect (chat->input_text_view,
"key_press_event",
"changed",
G_CALLBACK (chat_input_text_buffer_changed_cb),
chat);
- g_signal_connect (GOSSIP_CHAT (chat)->view,
+ g_signal_connect (chat->view,
"focus_in_event",
G_CALLBACK (chat_text_view_focus_in_event_cb),
chat);
"misspelled",
"underline", PANGO_UNDERLINE_ERROR,
NULL);
+
+
+
+ /* Turn off scrolling temporarily */
+ gossip_chat_view_scroll (chat->view, FALSE);
+#if 0
+FIXME:
+ /* Add messages from last conversation */
+ log_manager = gossip_session_get_log_manager (gossip_app_get_session ());
+ messages = gossip_log_get_last_for_contact (log_manager, priv->contact);
+ num_messages = g_list_length (messages);
+
+ for (l = messages, i = 0; l; l = l->next, i++) {
+ message = l->data;
+
+ if (num_messages - i > 10) {
+ continue;
+ }
+
+ sender = gossip_message_get_sender (message);
+ if (gossip_contact_equal (priv->own_contact, sender)) {
+ gossip_chat_view_append_message_from_self (view,
+ message,
+ priv->own_contact,
+ priv->own_avatar);
+ } else {
+ gossip_chat_view_append_message_from_other (view,
+ message,
+ sender,
+ priv->other_avatar);
+ }
+ }
+
+ g_list_foreach (messages, (GFunc) g_object_unref, NULL);
+ g_list_free (messages);
+#endif
+ /* Turn back on scrolling */
+ gossip_chat_view_scroll (chat->view, TRUE);
+
+ /* Scroll to the most recent messages, we reference the chat
+ * for the duration of the scroll func.
+ */
+ priv->scroll_idle_id = g_idle_add ((GSourceFunc) chat_scroll_down_idle_func,
+ g_object_ref (chat));
+
}
static void
gossip_debug (DEBUG_DOMAIN, "Finalized: %p", object);
+ g_slist_foreach (priv->sent_messages, (GFunc) g_free, NULL);
+ g_slist_free (priv->sent_messages);
+
+ g_list_foreach (priv->compositors, (GFunc) g_object_unref, NULL);
+ g_list_free (priv->compositors);
+
chat_composing_remove_timeout (chat);
- g_object_unref (GOSSIP_CHAT (object)->account);
+ g_object_unref (chat->account);
+ g_object_unref (priv->manager);
+ g_object_unref (priv->tooltips);
if (priv->tp_chat) {
g_object_unref (priv->tp_chat);
}
+ if (priv->scroll_idle_id) {
+ g_source_remove (priv->scroll_idle_id);
+ }
+
+ g_free (priv->id);
+
G_OBJECT_CLASS (gossip_chat_parent_class)->finalize (object);
}
priv = GET_PRIV (chat);
- if (msg == NULL || msg[0] == '\0') {
+ if (G_STR_EMPTY (msg)) {
return;
}
+ chat_sent_message_add (chat, msg);
+
if (g_str_has_prefix (msg, "/clear")) {
gossip_chat_view_clear (chat->view);
return;
}
+ /* FIXME: add here something to let group/privrate chat handle
+ * some special messages */
+
/* FIXME: gossip_app_set_not_away ();*/
- own_contact = gossip_chat_get_own_contact (chat);
+ own_contact = empathy_contact_manager_get_user (priv->manager, chat->account);
message = gossip_message_new (msg);
gossip_message_set_sender (message, own_contact);
// FIXME: gossip_sound_play (GOSSIP_SOUND_CHAT);
}
- g_signal_emit_by_name (chat, "new-message", message);
+ g_signal_emit (chat, signals[NEW_MESSAGE], 0, message, FALSE);
+}
+
+void
+chat_sent_message_add (GossipChat *chat,
+ const gchar *str)
+{
+ GossipChatPriv *priv;
+ GSList *list;
+ GSList *item;
+
+ priv = GET_PRIV (chat);
+
+ /* Save the sent message in our repeat buffer */
+ list = priv->sent_messages;
+
+ /* Remove any other occurances of this msg */
+ while ((item = g_slist_find_custom (list, str, (GCompareFunc) strcmp)) != NULL) {
+ list = g_slist_remove_link (list, item);
+ g_free (item->data);
+ g_slist_free1 (item);
+ }
+
+ /* Trim the list to the last 10 items */
+ while (g_slist_length (list) > 10) {
+ item = g_slist_last (list);
+ if (item) {
+ list = g_slist_remove_link (list, item);
+ g_free (item->data);
+ g_slist_free1 (item);
+ }
+ }
+
+ /* Add new message */
+ list = g_slist_prepend (list, g_strdup (str));
+
+ /* Set list and reset the index */
+ priv->sent_messages = list;
+ priv->sent_messages_index = -1;
+}
+
+const gchar *
+chat_sent_message_get_next (GossipChat *chat)
+{
+ GossipChatPriv *priv;
+ gint max;
+
+ priv = GET_PRIV (chat);
+
+ if (!priv->sent_messages) {
+ gossip_debug (DEBUG_DOMAIN,
+ "No sent messages, next message is NULL");
+ return NULL;
+ }
+
+ max = g_slist_length (priv->sent_messages) - 1;
+
+ if (priv->sent_messages_index < max) {
+ priv->sent_messages_index++;
+ }
+
+ gossip_debug (DEBUG_DOMAIN,
+ "Returning next message index:%d",
+ priv->sent_messages_index);
+
+ return g_slist_nth_data (priv->sent_messages, priv->sent_messages_index);
+}
+
+const gchar *
+chat_sent_message_get_last (GossipChat *chat)
+{
+ GossipChatPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_CHAT (chat), NULL);
+
+ priv = GET_PRIV (chat);
+
+ if (!priv->sent_messages) {
+ gossip_debug (DEBUG_DOMAIN,
+ "No sent messages, last message is NULL");
+ return NULL;
+ }
+
+ if (priv->sent_messages_index >= 0) {
+ priv->sent_messages_index--;
+ }
+
+ gossip_debug (DEBUG_DOMAIN,
+ "Returning last message index:%d",
+ priv->sent_messages_index);
+
+ return g_slist_nth_data (priv->sent_messages, priv->sent_messages_index);
}
static gboolean
return TRUE;
}
+ /* Catch ctrl+up/down so we can traverse messages we sent */
+ if ((event->state & GDK_CONTROL_MASK) &&
+ (event->keyval == GDK_Up ||
+ event->keyval == GDK_Down)) {
+ GtkTextBuffer *buffer;
+ const gchar *str;
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
+
+ if (event->keyval == GDK_Up) {
+ str = chat_sent_message_get_next (chat);
+ } else {
+ str = chat_sent_message_get_last (chat);
+ }
+
+ g_signal_handlers_block_by_func (buffer,
+ chat_input_text_buffer_changed_cb,
+ chat);
+ gtk_text_buffer_set_text (buffer, str ? str : "", -1);
+ g_signal_handlers_unblock_by_func (buffer,
+ chat_input_text_buffer_changed_cb,
+ chat);
+
+ return TRUE;
+ }
+
/* Catch enter but not ctrl/shift-enter */
- if (IS_ENTER (event->keyval) && !(event->state & GDK_SHIFT_MASK)) {
+ if (IS_ENTER (event->keyval) && !(event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
GtkTextView *view;
/* This is to make sure that kinput2 gets the enter. And if
}
text_view_sw = gtk_widget_get_parent (GTK_WIDGET (chat->view));
- if (IS_ENTER (event->keyval) && (event->state & GDK_SHIFT_MASK)) {
+
+ if (IS_ENTER (event->keyval) && (event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
/* Newline for shift-enter. */
return FALSE;
}
/* Just restart the timeout */
chat_composing_remove_timeout (chat);
} else {
- /* FIXME:
- gossip_session_send_composing (gossip_app_get_session (),
- priv->contact, TRUE);
- */
+ empathy_tp_chat_set_state (priv->tp_chat,
+ TP_CHANNEL_CHAT_STATE_COMPOSING);
}
priv->composing_stop_timeout_id = g_timeout_add (
priv = GET_PRIV (chat);
chat_composing_remove_timeout (chat);
- /* FIXME:
- gossip_session_send_composing (gossip_app_get_session (),
- priv->contact, FALSE);*/
+ empathy_tp_chat_set_state (priv->tp_chat,
+ TP_CHANNEL_CHAT_STATE_ACTIVE);
}
static void
priv = GET_PRIV (chat);
priv->composing_stop_timeout_id = 0;
- /* FIXME:
- gossip_session_send_composing (gossip_app_get_session (),
- priv->contact, FALSE);*/
+ empathy_tp_chat_set_state (priv->tp_chat,
+ TP_CHANNEL_CHAT_STATE_PAUSED);
+
+ return FALSE;
+}
+
+static void
+chat_state_changed_cb (EmpathyTpChat *tp_chat,
+ GossipContact *contact,
+ TelepathyChannelChatState state,
+ GossipChat *chat)
+{
+ GossipChatPriv *priv;
+ GossipContact *own_contact;
+ GList *l;
+ gboolean was_composing;
+
+ priv = GET_PRIV (chat);
+
+ own_contact = gossip_contact_get_user (contact);
+ if (gossip_contact_equal (own_contact, contact)) {
+ /* We don't care about our own chat state */
+ return;
+ }
+
+ was_composing = (priv->compositors != NULL);
+
+ /* Find the contact in the list. After that l is the list elem or NULL */
+ for (l = priv->compositors; l; l = l->next) {
+ if (gossip_contact_equal (contact, l->data)) {
+ break;
+ }
+ }
+
+ switch (state) {
+ case TP_CHANNEL_CHAT_STATE_GONE:
+ case TP_CHANNEL_CHAT_STATE_INACTIVE:
+ case TP_CHANNEL_CHAT_STATE_ACTIVE:
+ /* Contact is not composing */
+ if (l) {
+ priv->compositors = g_list_remove_link (priv->compositors, l);
+ g_object_unref (l->data);
+ g_list_free1 (l);
+ }
+ break;
+ case TP_CHANNEL_CHAT_STATE_PAUSED:
+ case TP_CHANNEL_CHAT_STATE_COMPOSING:
+ /* Contact is composing */
+ if (!l) {
+ priv->compositors = g_list_prepend (priv->compositors,
+ g_object_ref (contact));
+ }
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ gossip_debug (DEBUG_DOMAIN, "Was composing: %s now composing: %s",
+ was_composing ? "yes" : "no",
+ priv->compositors ? "yes" : "no");
+
+ if ((was_composing && !priv->compositors) ||
+ (!was_composing && priv->compositors)) {
+ /* Composing state changed */
+ g_signal_emit (chat, signals[COMPOSING], 0,
+ (gboolean) priv->compositors);
+ }
+}
+
+/* Scroll down after the back-log has been received. */
+static gboolean
+chat_scroll_down_idle_func (GossipChat *chat)
+{
+ GossipChatPriv *priv;
+
+ priv = GET_PRIV (chat);
+
+ gossip_chat_scroll_down (chat);
+ g_object_unref (chat);
+
+ priv->scroll_idle_id = 0;
return FALSE;
}
return NULL;
}
-GdkPixbuf *
-gossip_chat_get_status_pixbuf (GossipChat *chat)
-{
- g_return_val_if_fail (GOSSIP_IS_CHAT (chat), NULL);
-
- if (GOSSIP_CHAT_GET_CLASS (chat)->get_status_pixbuf) {
- return GOSSIP_CHAT_GET_CLASS (chat)->get_status_pixbuf (chat);
- }
-
- return NULL;
-}
-
-GossipContact *
-gossip_chat_get_contact (GossipChat *chat)
+const gchar *
+gossip_chat_get_status_icon_name (GossipChat *chat)
{
g_return_val_if_fail (GOSSIP_IS_CHAT (chat), NULL);
- if (GOSSIP_CHAT_GET_CLASS (chat)->get_contact) {
- return GOSSIP_CHAT_GET_CLASS (chat)->get_contact (chat);
+ if (GOSSIP_CHAT_GET_CLASS (chat)->get_status_icon_name) {
+ return GOSSIP_CHAT_GET_CLASS (chat)->get_status_icon_name (chat);
}
return NULL;
}
-GossipContact *
-gossip_chat_get_own_contact (GossipChat *chat)
-{
- EmpathyContactManager *manager;
-
- g_return_val_if_fail (GOSSIP_IS_CHAT (chat), NULL);
-
- manager = empathy_session_get_contact_manager ();
-
- return empathy_contact_manager_get_own (manager, chat->account);
-}
GtkWidget *
gossip_chat_get_widget (GossipChat *chat)
return (priv->tp_chat != NULL);
}
-gboolean
-gossip_chat_get_show_contacts (GossipChat *chat)
-{
- g_return_val_if_fail (GOSSIP_IS_CHAT (chat), FALSE);
-
- if (GOSSIP_CHAT_GET_CLASS (chat)->get_show_contacts) {
- return GOSSIP_CHAT_GET_CLASS (chat)->get_show_contacts (chat);
- }
-
- return FALSE;
-}
-
-void
-gossip_chat_set_show_contacts (GossipChat *chat,
- gboolean show)
-{
- g_return_if_fail (GOSSIP_IS_CHAT (chat));
-
- if (GOSSIP_CHAT_GET_CLASS (chat)->set_show_contacts) {
- GOSSIP_CHAT_GET_CLASS (chat)->set_show_contacts (chat, show);
- }
-}
-
void
gossip_chat_save_geometry (GossipChat *chat,
gint x,
g_signal_connect (tp_chat, "message-received",
G_CALLBACK (chat_message_received_cb),
chat);
+ g_signal_connect (tp_chat, "chat-state-changed",
+ G_CALLBACK (chat_state_changed_cb),
+ chat);
g_signal_connect (tp_chat, "destroy",
G_CALLBACK (chat_destroy_cb),
chat);
g_return_val_if_fail (GOSSIP_IS_CHAT (chat), FALSE);
- window = gossip_chat_get_window (GOSSIP_CHAT (chat));
+ window = gossip_chat_get_window (chat);
if (!window) {
return TRUE;
}
return FALSE;
}
- my_contact = gossip_get_own_contact_from_contact (gossip_message_get_sender (message));
+ my_contact = gossip_contact_get_user (gossip_message_get_sender (message));
to = gossip_contact_get_name (my_contact);
if (!to) {
return FALSE;