]> git.0d.be Git - empathy.git/blobdiff - libempathy-gtk/empathy-chat.c
Merge branch 'sasl'
[empathy.git] / libempathy-gtk / empathy-chat.c
index c14e315ed7460403359bac259484ac3f5e5e6407..a92298cfae7d13370055dd7eb37a9e101e231c48 100644 (file)
@@ -1,7 +1,7 @@
 /* -*- 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)
@@ -87,7 +86,6 @@ struct _EmpathyChatPriv {
        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
@@ -101,6 +99,8 @@ struct _EmpathyChatPriv {
 
        /* 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;
@@ -172,6 +172,8 @@ static guint signals[LAST_SIGNAL] = { 0 };
 
 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,
@@ -1108,7 +1110,9 @@ chat_state_changed_cb (EmpathyTpChat      *tp_chat,
 }
 
 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;
@@ -1127,7 +1131,7 @@ chat_message_received (EmpathyChat *chat, EmpathyMessage *message)
                               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
@@ -1135,7 +1139,7 @@ chat_message_received_cb (EmpathyTpChat  *tp_chat,
                          EmpathyMessage *message,
                          EmpathyChat    *chat)
 {
-       chat_message_received (chat, message);
+       chat_message_received (chat, message, FALSE);
 }
 
 static void
@@ -1176,48 +1180,6 @@ chat_send_error_cb (EmpathyTpChat          *tp_chat,
        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,
@@ -1226,8 +1188,6 @@ chat_topic_label_size_allocate_cb (GtkLabel *label,
        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
@@ -1235,11 +1195,6 @@ chat_topic_label_size_allocate_cb (GtkLabel *label,
 
                return;
        }
-
-       if (priv->topic_width != allocation->width) {
-               priv->topic_width = allocation->width;
-               gtk_widget_queue_resize (GTK_WIDGET (label));
-       }
 }
 
 static void
@@ -1657,40 +1612,6 @@ chat_text_view_focus_in_event_cb (GtkWidget  *widget,
        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)
@@ -1763,33 +1684,196 @@ chat_spelling_menu_activate_cb (GtkMenuItem     *menu_item,
                                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
@@ -1814,6 +1898,7 @@ chat_input_populate_popup_cb (GtkTextView *view,
        gchar                *str = NULL;
        EmpathyChatSpell     *chat_spell;
        GtkWidget            *spell_menu;
+       GtkWidget            *spell_item;
        EmpathySmileyManager *smiley_manager;
        GtkWidget            *smiley_menu;
        GtkWidget            *image;
@@ -1871,13 +1956,14 @@ chat_input_populate_popup_cb (GtkTextView *view,
        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);
@@ -1886,6 +1972,17 @@ chat_input_populate_popup_cb (GtkTextView *view,
                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);
        }
@@ -1909,6 +2006,7 @@ chat_log_filter (TplEntry *log,
 
        for (; pending; pending = g_list_next (pending)) {
                if (empathy_message_equal (message, pending->data)) {
+                       g_object_unref (message);
                        return FALSE;
                }
        }
@@ -1935,7 +2033,7 @@ show_pending_messages (EmpathyChat *chat) {
 
        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);
        }
 }
 
@@ -2396,18 +2494,33 @@ conf_spell_checking_cb (GSettings *gsettings_chat,
        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;
 }
@@ -2440,7 +2553,6 @@ chat_create_ui (EmpathyChat *chat)
        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);
@@ -2461,20 +2573,11 @@ chat_create_ui (EmpathyChat *chat)
        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);
@@ -2535,29 +2638,6 @@ chat_create_ui (EmpathyChat *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)
@@ -2595,6 +2675,9 @@ chat_finalize (GObject *object)
        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);
 
@@ -2674,7 +2757,6 @@ empathy_chat_class_init (EmpathyChatClass *klass)
        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,
@@ -2751,9 +2833,9 @@ empathy_chat_class_init (EmpathyChatClass *klass)
                              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));
 }
@@ -3231,6 +3313,31 @@ empathy_chat_copy (EmpathyChat *chat)
 
                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