/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Copyright (C) 2002-2007 Imendio AB
- * Copyright (C) 2007-2008 Collabora Ltd.
+ * Copyright (C) 2007-2010 Collabora Ltd.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
#include <libempathy/empathy-gsettings.h>
#include <libempathy/empathy-utils.h>
#include <libempathy/empathy-dispatcher.h>
+#include <libempathy/empathy-marshal.h>
#include "empathy-chat.h"
#include "empathy-spell.h"
#include "empathy-contact-list-store.h"
#include "empathy-contact-list-view.h"
#include "empathy-contact-menu.h"
+#include "empathy-input-text-view.h"
#include "empathy-search-bar.h"
#include "empathy-theme-manager.h"
#include "empathy-smiley-manager.h"
#define DEBUG_FLAG EMPATHY_DEBUG_CHAT
#include <libempathy/empathy-debug.h>
-#define CHAT_DIR_CREATE_MODE (S_IRUSR | S_IWUSR | S_IXUSR)
-#define CHAT_FILE_CREATE_MODE (S_IRUSR | S_IWUSR)
#define IS_ENTER(v) (v == GDK_KEY_Return || v == GDK_KEY_ISO_Enter || v == GDK_KEY_KP_Enter)
-#define MAX_INPUT_HEIGHT 150
#define COMPOSING_STOP_TIMEOUT 5
#define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyChat)
TpHandleType handle_type;
gint contacts_width;
gboolean has_input_vscroll;
- gint topic_width;
/* TRUE if spell checking is enabled, FALSE otherwise.
* This is to keep track of the last state of spell checking
/* Source func ID for update_misspelled_words () */
guint update_misspelled_words_id;
+ /* Source func ID for save_paned_pos_timeout () */
+ guint save_paned_pos_id;
GtkWidget *widget;
GtkWidget *hpaned;
G_DEFINE_TYPE (EmpathyChat, empathy_chat, GTK_TYPE_BIN);
+static gboolean update_misspelled_words (gpointer data);
+
static void
chat_get_property (GObject *object,
guint param_id,
}
static void
-chat_message_received (EmpathyChat *chat, EmpathyMessage *message)
+chat_message_received (EmpathyChat *chat,
+ EmpathyMessage *message,
+ gboolean pending)
{
EmpathyChatPriv *priv = GET_PRIV (chat);
EmpathyContact *sender;
chat);
priv->unread_messages++;
- g_signal_emit (chat, signals[NEW_MESSAGE], 0, message);
+ g_signal_emit (chat, signals[NEW_MESSAGE], 0, message, pending);
}
static void
EmpathyMessage *message,
EmpathyChat *chat)
{
- chat_message_received (chat, message);
+ chat_message_received (chat, message, FALSE);
}
static void
g_free (str);
}
-/* WARNING: EXPLICIT CONTENT, keep away childrens!
- *
- * When a GtkLabel is set to wrap, it assume and hardcoded width. To change
- * that width we have to set a size request on the label... but that's not
- * possible because we want the window to be able to shrink, so we MUST request
- * width of 1. Note that the height of a wrapping label depends on its width.
- *
- * To work around that, here is what happens:
- * 1) size-request is first called, an hardcoded small width is requested by
- * GtkLabel, which means also a too big height. We do nothing.
- * 2) size-allocate is called with the full width available, that's the width
- * we really want to make wrap the label. We save that width and restart a
- * size-request/size-allocate round.
- * 3) size-request is called a 2nd time, now we can tell the pango layout its
- * width (we can't do that in step 2 because GtkLabel::size-request recreate
- * the layout each time). When the layout has its width, we can know the
- * height of the label and set its requisition. The width request is set to
- * 1px to make sure the window can shrink, the layout will fill all the
- * available width anyway.
- */
-static void
-chat_topic_label_size_request_cb (GtkLabel *label,
- GtkRequisition *requisition,
- EmpathyChat *chat)
-{
- EmpathyChatPriv *priv = GET_PRIV (chat);
-
- if (gtk_label_get_line_wrap (label) && priv->topic_width > 0) {
- PangoLayout *layout;
- PangoRectangle rect;
- gint ypad;
-
- layout = gtk_label_get_layout (label);
- pango_layout_set_width (layout, priv->topic_width * PANGO_SCALE);
- pango_layout_get_extents (layout, NULL, &rect);
- gtk_misc_get_padding (GTK_MISC (label), NULL, &ypad);
-
- requisition->width = 1;
- requisition->height = PANGO_PIXELS (rect.height) + ypad * 2;
- }
-}
-
static void
chat_topic_label_size_allocate_cb (GtkLabel *label,
GtkAllocation *allocation,
EmpathyChatPriv *priv = GET_PRIV (chat);
if (!gtk_label_get_line_wrap (label)) {
- priv->topic_width = -1;
-
if (pango_layout_is_ellipsized (gtk_label_get_layout (label)))
gtk_widget_show (priv->expander_topic);
else
return;
}
-
- if (priv->topic_width != allocation->width) {
- priv->topic_width = allocation->width;
- gtk_widget_queue_resize (GTK_WIDGET (label));
- }
}
static void
return TRUE;
}
-static gboolean
-chat_input_set_size_request_idle (gpointer sw)
-{
- gtk_widget_set_size_request (sw, -1, MAX_INPUT_HEIGHT);
-
- return FALSE;
-}
-
-static void
-chat_input_size_request_cb (GtkWidget *widget,
- GtkRequisition *requisition,
- EmpathyChat *chat)
-{
- EmpathyChatPriv *priv = GET_PRIV (chat);
- GtkWidget *sw;
-
- sw = gtk_widget_get_parent (widget);
- if (requisition->height >= MAX_INPUT_HEIGHT && !priv->has_input_vscroll) {
- g_idle_add (chat_input_set_size_request_idle, sw);
- gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
- GTK_POLICY_NEVER,
- GTK_POLICY_ALWAYS);
- priv->has_input_vscroll = TRUE;
- }
-
- if (requisition->height < MAX_INPUT_HEIGHT && priv->has_input_vscroll) {
- gtk_widget_set_size_request (sw, -1, -1);
- gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
- GTK_POLICY_NEVER,
- GTK_POLICY_NEVER);
- priv->has_input_vscroll = FALSE;
- }
-}
-
static void
chat_input_realize_cb (GtkWidget *widget,
EmpathyChat *chat)
gtk_menu_item_get_label (menu_item));
}
+
+static GtkWidget *
+chat_spelling_build_suggestions_menu (const gchar *code,
+ EmpathyChatSpell *chat_spell)
+{
+ GList *suggestions, *l;
+ GtkWidget *menu, *menu_item;
+
+ suggestions = empathy_spell_get_suggestions (code, chat_spell->word);
+ if (suggestions == NULL)
+ return NULL;
+
+ menu = gtk_menu_new ();
+ for (l = suggestions; l; l = l->next) {
+ menu_item = gtk_menu_item_new_with_label (l->data);
+ g_signal_connect (G_OBJECT (menu_item), "activate",
+ G_CALLBACK (chat_spelling_menu_activate_cb),
+ chat_spell);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
+ }
+ empathy_spell_free_suggestions (suggestions);
+
+ gtk_widget_show_all (menu);
+
+ return menu;
+}
+
static GtkWidget *
chat_spelling_build_menu (EmpathyChatSpell *chat_spell)
{
- GtkWidget *menu, *menu_item;
- GList *suggestions, *l;
-
- menu = gtk_menu_new ();
- suggestions = empathy_spell_get_suggestions (chat_spell->word);
- if (suggestions == NULL) {
- menu_item = gtk_menu_item_new_with_label (_("(No Suggestions)"));
- gtk_widget_set_sensitive (menu_item, FALSE);
- gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
- } else {
- for (l = suggestions; l; l = l->next) {
- menu_item = gtk_menu_item_new_with_label (l->data);
- g_signal_connect (G_OBJECT (menu_item),
- "activate",
- G_CALLBACK (chat_spelling_menu_activate_cb),
- chat_spell);
- gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
- }
- }
- empathy_spell_free_suggestions (suggestions);
+ GtkWidget *menu, *submenu, *item;
+ GList *codes, *l;
+
+ codes = empathy_spell_get_enabled_language_codes ();
+ g_assert (codes != NULL);
+
+ if (g_list_length (codes) > 1) {
+ menu = gtk_menu_new ();
+
+ for (l = codes; l; l = l->next) {
+ const gchar *code, *name;
+
+ code = l->data;
+ name = empathy_spell_get_language_name (code);
+ if (!name)
+ continue;
+
+ item = gtk_image_menu_item_new_with_label (name);
- gtk_widget_show_all (menu);
+ submenu = chat_spelling_build_suggestions_menu (
+ code, chat_spell);
+ if (submenu == NULL)
+ gtk_widget_set_sensitive (item, FALSE);
+ else
+ gtk_menu_item_set_submenu (GTK_MENU_ITEM (item),
+ submenu);
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
+ }
+ } else {
+ menu = chat_spelling_build_suggestions_menu (codes->data,
+ chat_spell);
+ if (menu == NULL) {
+ menu = gtk_menu_new ();
+ item = gtk_menu_item_new_with_label (_("(No Suggestions)"));
+ gtk_widget_set_sensitive (item, FALSE);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+ }
+ }
+ g_list_free (codes);
+
+ gtk_widget_show_all (menu);
- return menu;
+ return menu;
+}
+
+typedef struct {
+ EmpathyChat *chat;
+ gchar *word;
+ gchar *code;
+} EmpathyChatWord;
+
+static EmpathyChatWord *
+chat_word_new (EmpathyChat *chat,
+ const gchar *word,
+ const gchar *code)
+{
+ EmpathyChatWord *chat_word;
+
+ chat_word = g_slice_new0 (EmpathyChatWord);
+
+ chat_word->chat = g_object_ref (chat);
+ chat_word->word = g_strdup (word);
+ chat_word->code = g_strdup (code);
+
+ return chat_word;
+}
+
+static void
+chat_word_free (EmpathyChatWord *chat_word)
+{
+ g_object_unref (chat_word->chat);
+ g_free (chat_word->word);
+ g_free (chat_word->code);
+ g_slice_free (EmpathyChatWord, chat_word);
+}
+
+static void
+chat_add_to_dictionary_activate_cb (GtkMenuItem *menu_item,
+ EmpathyChatWord *chat_word)
+{
+ EmpathyChatPriv *priv = GET_PRIV (chat_word->chat);
+
+ empathy_spell_add_to_dictionary (chat_word->code,
+ chat_word->word);
+ priv->update_misspelled_words_id =
+ g_idle_add (update_misspelled_words, chat_word->chat);
+}
+
+static GtkWidget *
+chat_spelling_build_add_to_dictionary_item (EmpathyChatSpell *chat_spell)
+{
+ GtkWidget *menu, *item, *lang_item, *image;
+ GList *codes, *l;
+ gchar *label;
+ const gchar *code, *name;
+ EmpathyChatWord *chat_word;
+
+ codes = empathy_spell_get_enabled_language_codes ();
+ g_assert (codes != NULL);
+ if (g_list_length (codes) > 1) {
+ /* translators: %s is the selected word */
+ label = g_strdup_printf (_("Add '%s' to Dictionary"),
+ chat_spell->word);
+ item = gtk_image_menu_item_new_with_mnemonic (label);
+ g_free (label);
+ image = gtk_image_new_from_icon_name (GTK_STOCK_ADD,
+ GTK_ICON_SIZE_MENU);
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item),
+ image);
+
+ menu = gtk_menu_new ();
+
+ for (l = codes; l; l = l->next) {
+ code = l->data;
+ name = empathy_spell_get_language_name (code);
+ if (name == NULL)
+ continue;
+
+ lang_item = gtk_image_menu_item_new_with_label (name);
+
+ chat_word= chat_word_new (chat_spell->chat,
+ chat_spell->word, code);
+ g_object_set_data_full (G_OBJECT (lang_item),
+ "chat-word", chat_word,
+ (GDestroyNotify) chat_word_free);
+
+ g_signal_connect (G_OBJECT (lang_item), "activate",
+ G_CALLBACK (chat_add_to_dictionary_activate_cb),
+ chat_word);
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), lang_item);
+ }
+ gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), menu);
+ } else {
+ code = codes->data;
+ name = empathy_spell_get_language_name (code);
+ g_assert (name != NULL);
+ /* translators: first %s is the selected word,
+ * second %s is the language name of the target dictionary */
+ label = g_strdup_printf (_("Add '%s' to %s Dictionary"),
+ chat_spell->word, name);
+ item = gtk_image_menu_item_new_with_mnemonic (label);
+ g_free (label);
+ image = gtk_image_new_from_icon_name (GTK_STOCK_ADD,
+ GTK_ICON_SIZE_MENU);
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
+
+ chat_word = chat_word_new (chat_spell->chat, chat_spell->word,
+ code);
+ g_object_set_data_full (G_OBJECT (item), "chat-word", chat_word,
+ (GDestroyNotify) chat_word_free);
+
+ g_signal_connect (G_OBJECT (item), "activate",
+ G_CALLBACK (chat_add_to_dictionary_activate_cb),
+ chat_word);
+ }
+ g_list_free (codes);
+
+ gtk_widget_show_all (item);
+
+ return item;
}
static void
gchar *str = NULL;
EmpathyChatSpell *chat_spell;
GtkWidget *spell_menu;
+ GtkWidget *spell_item;
EmpathySmileyManager *smiley_manager;
GtkWidget *smiley_menu;
GtkWidget *image;
if (!EMP_STR_EMPTY (str)) {
chat_spell = chat_spell_new (chat, str, start, end);
g_object_set_data_full (G_OBJECT (menu),
- "chat_spell", chat_spell,
+ "chat-spell", chat_spell,
(GDestroyNotify) chat_spell_free);
item = gtk_separator_menu_item_new ();
gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
gtk_widget_show (item);
+ /* Spelling suggestions */
item = gtk_image_menu_item_new_with_mnemonic (_("_Spelling Suggestions"));
image = gtk_image_new_from_icon_name (GTK_STOCK_SPELL_CHECK,
GTK_ICON_SIZE_MENU);
spell_menu = chat_spelling_build_menu (chat_spell);
gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), spell_menu);
+
+ spell_item = gtk_separator_menu_item_new ();
+ gtk_menu_shell_append (GTK_MENU_SHELL (spell_menu), spell_item);
+ gtk_widget_show (spell_item);
+
+ /* Add to dictionary */
+ spell_item = chat_spelling_build_add_to_dictionary_item (chat_spell);
+
+ gtk_menu_shell_append (GTK_MENU_SHELL (spell_menu), spell_item);
+ gtk_widget_show (spell_item);
+
gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
gtk_widget_show (item);
}
for (; pending; pending = g_list_next (pending)) {
if (empathy_message_equal (message, pending->data)) {
+ g_object_unref (message);
return FALSE;
}
}
for (l = messages; l != NULL ; l = g_list_next (l)) {
EmpathyMessage *message = EMPATHY_MESSAGE (l->data);
- chat_message_received (chat, message);
+ chat_message_received (chat, message, TRUE);
}
}
priv->spell_checking_enabled = spell_checker;
}
+static gboolean
+save_paned_pos_timeout (gpointer data)
+{
+ EmpathyChat *self = data;
+ gint hpaned_pos;
+
+ hpaned_pos = gtk_paned_get_position (GTK_PANED (self->priv->hpaned));
+
+ g_settings_set_int (self->priv->gsettings_ui,
+ EMPATHY_PREFS_UI_CHAT_WINDOW_PANED_POS,
+ hpaned_pos);
+
+ return FALSE;
+}
+
static gboolean
chat_hpaned_pos_changed_cb (GtkWidget* hpaned,
GParamSpec *spec,
gpointer user_data)
{
EmpathyChat *chat = EMPATHY_CHAT (user_data);
- gint hpaned_pos;
- hpaned_pos = gtk_paned_get_position (GTK_PANED(hpaned));
- g_settings_set_int (chat->priv->gsettings_ui,
- EMPATHY_PREFS_UI_CHAT_WINDOW_PANED_POS,
- hpaned_pos);
+ if (chat->priv->save_paned_pos_id != 0)
+ g_source_remove (chat->priv->save_paned_pos_id);
+
+ chat->priv->save_paned_pos_id = g_timeout_add_seconds (1,
+ save_paned_pos_timeout, chat);
return TRUE;
}
empathy_builder_connect (gui, chat,
"expander_topic", "notify::expanded", chat_topic_expander_activate_cb,
"label_topic", "size-allocate", chat_topic_label_size_allocate_cb,
- "label_topic", "size-request", chat_topic_label_size_request_cb,
NULL);
g_free (filename);
gtk_widget_show (GTK_WIDGET (chat->view));
/* Add input GtkTextView */
- chat->input_text_view = g_object_new (GTK_TYPE_TEXT_VIEW,
- "pixels-above-lines", 2,
- "pixels-below-lines", 2,
- "pixels-inside-wrap", 1,
- "right-margin", 2,
- "left-margin", 2,
- "wrap-mode", GTK_WRAP_WORD_CHAR,
- NULL);
+ chat->input_text_view = empathy_input_text_view_new ();
+
g_signal_connect (chat->input_text_view, "key-press-event",
G_CALLBACK (chat_input_key_press_event_cb),
chat);
- g_signal_connect (chat->input_text_view, "size-request",
- G_CALLBACK (chat_input_size_request_cb),
- chat);
g_signal_connect (chat->input_text_view, "realize",
G_CALLBACK (chat_input_realize_cb),
chat);
g_object_unref (gui);
}
-static void
-chat_size_request (GtkWidget *widget,
- GtkRequisition *requisition)
-{
- GtkBin *bin = GTK_BIN (widget);
- GtkWidget *child;
-
- requisition->width = gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2;
- requisition->height = gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2;
-
- child = gtk_bin_get_child (bin);
-
- if (child && gtk_widget_get_visible (child))
- {
- GtkRequisition child_requisition;
-
- gtk_widget_size_request (child, &child_requisition);
-
- requisition->width += child_requisition.width;
- requisition->height += child_requisition.height;
- }
-}
-
static void
chat_size_allocate (GtkWidget *widget,
GtkAllocation *allocation)
if (priv->update_misspelled_words_id != 0)
g_source_remove (priv->update_misspelled_words_id);
+ if (priv->save_paned_pos_id != 0)
+ g_source_remove (priv->save_paned_pos_id);
+
g_object_unref (priv->gsettings_chat);
g_object_unref (priv->gsettings_ui);
object_class->set_property = chat_set_property;
object_class->constructed = chat_constructed;
- widget_class->size_request = chat_size_request;
widget_class->size_allocate = chat_size_allocate;
g_object_class_install_property (object_class,
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
- g_cclosure_marshal_VOID__OBJECT,
+ _empathy_marshal_VOID__OBJECT_BOOLEAN,
G_TYPE_NONE,
- 1, EMPATHY_TYPE_MESSAGE);
+ 2, EMPATHY_TYPE_MESSAGE, G_TYPE_BOOLEAN);
g_type_class_add_private (object_class, sizeof (EmpathyChatPriv));
}
gtk_text_buffer_copy_clipboard (buffer, clipboard);
}
+ else {
+ gint start_offset;
+ gint end_offset;
+ EmpathyChatPriv *priv = GET_PRIV (chat);
+
+ if (gtk_label_get_selection_bounds (GTK_LABEL (priv->label_topic),
+ &start_offset,
+ &end_offset)) {
+ gchar *start;
+ gchar *end;
+ gchar *selection;
+ const gchar *topic;
+ GtkClipboard *clipboard;
+
+ topic = gtk_label_get_text (GTK_LABEL (priv->label_topic));
+ start = g_utf8_offset_to_pointer (topic, start_offset);
+ end = g_utf8_offset_to_pointer (topic, end_offset);
+ selection = g_strndup (start, end - start);
+
+ clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+ gtk_clipboard_set_text (clipboard, selection, -1);
+
+ g_free (selection);
+ }
+ }
}
void